Turn Document · Repository Implementation

Codex Desktop Login And Analyst Copilot

Islandflow Desktop now boots an official codex app-server bridge, lets each desktop user log into a ChatGPT-backed Codex account, exposes a narrow Electron IPC surface to the renderer, and adds an AI settings and copilot experience without turning AI into the live first-pass classifier.

Scope Electron bridge, auth, usage telemetry, renderer UI, tests
Beads islandflow-6tn
Validation Desktop tests, web tests, shared-type check, desktop typecheck, production web build

Summary

This turn added a desktop-only Codex capability that respects the repo plan: managed ChatGPT login comes first, the existing deterministic smart-money classifier remains the source of truth, and AI is used as a structured analyst copilot on top of existing Islandflow artifacts.

Managed ChatGPT browser login Device-code fallback Electron preload + IPC bridge Usage and rate-limit dashboard Smart-money and replay copilot actions Browser-safe degradation

Changes Made

  • Added a native desktop AI service in apps/desktop/src/desktop-ai.ts that starts codex app-server, handles account state, usage notifications, rate limits, task execution, and preference persistence.
  • Added preload and IPC plumbing in apps/desktop/src/preload.ts, apps/desktop/src/desktop-ai-ipc.ts, and apps/desktop/src/main.ts with trusted-origin enforcement.
  • Introduced shared AI contracts in packages/types/src/desktop-ai.ts for auth state, model controls, token usage, rate limits, task payloads, and compiled screen responses.
  • Added a renderer-side desktop AI provider in apps/web/app/desktop-ai.tsx and richer UI surfaces in apps/web/app/desktop-ai-panels.tsx.
  • Enabled previously latent routes for /signals, /charts, and /replay, plus a new /settings route.
  • Extended the terminal shell and styles so users can reach AI Settings, compile natural-language screens on Tape, run replay postmortems, and investigate smart-money events inline.
  • Added desktop tests for env scrubbing, token usage accounting, rate-limit snapshots, and logout state reset, plus updated web route and navigation tests.

Context

The desktop app was previously a secure Electron wrapper around the web terminal. That meant there was no authenticated native bridge, no preload API for AI state, and no desktop-only place to manage account status, model controls, or token telemetry. The goal of this turn was to add those capabilities without weakening the shell security model and without letting AI replace the deterministic classification pipeline.

Deliberate product boundary:

The Copilot works only from structured Islandflow payloads such as SmartMoneyEvent, ClassifierHitEvent, FlowPacket, and current replay slices. It does not become a freeform live classifier in v1.

Important Implementation Details

  • The child codex app-server environment now explicitly clears OPENAI_API_KEY and CODEX_API_KEY unless the selected profile mode is API-key-based, which prevents accidental auth-mode drift during ChatGPT subscription sessions.
  • All desktop AI access goes through a narrow preload bridge instead of exposing Node or Electron primitives to the renderer.
  • IPC handlers validate the sender URL with the existing trusted-origin policy before serving account or task requests.
  • Usage is persisted by threadId and turnId, then rolled up into today and lifetime dashboards using exact token notifications from the app-server.
  • Normalized cost is clearly labeled as an API-price estimate, not literal ChatGPT subscription billing.
  • The screen compiler returns a structured filter payload plus rationale and unhandled clauses, then lets the user apply the compiled filters instead of silently mutating the terminal state.
  • The browser build stays safe by exposing an unavailable state through the provider and disabling desktop-only actions outside Electron.

Expected Impact for End-Users

  • Desktop users can log into their own ChatGPT-backed Codex account from Islandflow Settings without sharing a subscription across users.
  • Users can see plan type, model defaults, reasoning controls, rate-limit windows, recent AI turns, and token usage from one place.
  • Selected smart-money events now have one-click explain, counter-thesis, burst summary, and watchlist synthesis actions directly inside the investigation drawer.
  • Replay sessions can produce a structured postmortem from the exact slice currently on screen.
  • Tape users can write a natural-language screen and translate it into the app’s existing filter model where possible.
  • Web-only sessions degrade cleanly instead of exposing broken or misleading AI controls.

Relevant Diff Snippets

These snippets are formatted as unified patch strings so they can be consumed by Diffs’ parsePatchFiles or PatchDiff flow from the official docs: https://diffs.com/docs.

Electron main process: preload, desktop AI service, and guarded IPC
diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts
@@
-import { app, BrowserWindow, shell } from "electron";
+import { app, BrowserWindow, ipcMain, shell } from "electron";
+import type { Event as ElectronEvent, IpcMainInvokeEvent } from "electron";
+import { fileURLToPath } from "node:url";
+import { IslandflowDesktopAiService } from "./desktop-ai.js";
+import {
+  DESKTOP_AI_CANCEL_LOGIN,
+  DESKTOP_AI_GET_STATE,
+  DESKTOP_AI_LOGIN_BROWSER,
+  DESKTOP_AI_LOGIN_DEVICE,
+  DESKTOP_AI_LOGOUT,
+  DESKTOP_AI_RUN_TASK,
+  DESKTOP_AI_STATE_CHANNEL,
+  DESKTOP_AI_UPDATE_PREFERENCES
+} from "./desktop-ai-ipc.js";
@@
+const PRELOAD_PATH = fileURLToPath(new URL("./preload.js", import.meta.url));
@@
+const registerDesktopAiIpc = (service: IslandflowDesktopAiService): void => {
+  const guard = (event: IpcMainInvokeEvent): void => {
+    const senderUrl = event.senderFrame?.url || event.sender.getURL();
+    if (!isTrustedAppUrl(senderUrl)) {
+      throw new Error(`Rejected desktop AI IPC from untrusted origin: ${senderUrl || "unknown"}`);
+    }
+  };
+
+  ipcMain.handle(DESKTOP_AI_GET_STATE, async (event) => {
+    guard(event);
+    await service.start();
+    return service.getState();
+  });
+  // login, logout, preference, and task handlers follow the same guard
+};
Renderer shell: settings route, topbar entrypoint, and in-context copilot surfaces
diff --git a/apps/web/app/terminal.tsx b/apps/web/app/terminal.tsx
@@
+import { useDesktopAi } from "./desktop-ai";
+import {
+  DesktopAiSettingsRoute,
+  ReplayCopilotPanel,
+  ScreenCompilerPanel,
+  SmartMoneyCopilotPanel
+} from "./desktop-ai-panels";
@@
+export const NAV_ITEMS = [
+  { href: "/", label: "Home" },
+  { href: "/tape", label: "Tape" },
+  { href: "/signals", label: "Signals" },
+  { href: "/charts", label: "Charts" },
+  { href: "/replay", label: "Replay" },
+  { href: "/settings", label: "Settings" }
+];
@@
+
@@
+
@@
+
Shared desktop AI contract: auth state, rate limits, usage, and structured tasks
diff --git a/packages/types/src/desktop-ai.ts b/packages/types/src/desktop-ai.ts
+export const IslandflowAiTaskRequestSchema = z.discriminatedUnion("kind", [
+  z.object({ kind: z.literal("smart-money-explain"), context: IslandflowAiSmartMoneyContextSchema }),
+  z.object({ kind: z.literal("smart-money-skeptic"), context: IslandflowAiSmartMoneyContextSchema }),
+  z.object({ kind: z.literal("smart-money-burst-summary"), context: IslandflowAiSmartMoneyContextSchema }),
+  z.object({ kind: z.literal("watchlist-synthesis"), context: IslandflowAiSmartMoneyContextSchema }),
+  z.object({ kind: z.literal("replay-postmortem"), context: IslandflowAiReplayContextSchema }),
+  z.object({ kind: z.literal("screen-compile"), context: IslandflowAiScreenCompileContextSchema })
+]);
+
+export type IslandflowAiState = {
+  desktopAvailable: boolean;
+  transportStatus: IslandflowAiTransportStatus;
+  transportError: string | null;
+  profiles: IslandflowAiProfileSlot[];
+  account: IslandflowAiAccountState;
+  preferences: IslandflowAiPreferences;
+  models: IslandflowAiModelSummary[];
+  rateLimitsByLimitId: Record<string, IslandflowAiRateLimitSnapshot>;
+  usage: IslandflowAiUsageDashboard;
+  tasks: IslandflowAiTaskSnapshot[];
+  updatedAt: number;
+};

The document does not embed the Diffs runtime directly, but the snippets above are already prepared in the patch-string format that Diffs documents for PatchDiff.

Validation

Desktop typecheck

bun --cwd=apps/desktop run typecheck

Desktop tests

bun --cwd=apps/desktop run test

Shared types

bun x tsc -p packages/types/tsconfig.json --noEmit

Web tests

bun test apps/web/app/terminal.test.ts apps/web/app/routes.test.ts

Web production build

bun --cwd=apps/web run build

Manual desktop runtime

No end-to-end interactive Electron sign-in was executed in this turn. The bridge, auth flows, and renderer integration were validated through type checks, unit tests, and the production web build.

Issues, Limitations, and Mitigations

  • Workspace provider and API-key profile support are intentionally left as reserved slots behind the same abstraction, not shipped as active shared-subscription behavior.
  • The desktop bridge launches an ephemeral Codex thread per analysis task, which is safer for v1 but means there is not yet a long-lived conversational analyst thread.
  • The screen compiler only applies filters that map onto today’s OptionFlowFilters model, and it explicitly returns unhandled clauses rather than pretending to support unsupported logic.
  • The recent task and usage dashboards depend on app-server notifications. When those notifications do not fire, the UI stays safe and honest rather than synthesizing made-up counters.
  • Renderer interactions were validated in build and unit test contexts, but not with a live packaged desktop binary in this turn.

Follow-up Work

  • Add an automated Electron integration test harness that exercises browser login completion, device-code completion, logout, and recovery after app-server restart.
  • Promote the reserved workspace-provider slot into a real enterprise or API-key-backed profile once the product decision is ready.
  • Persist richer per-task provenance so replay postmortems and smart-money analyses can be reopened with their original structured context, not only their output text.
  • Consider a dedicated Copilot activity log or side rail once users accumulate enough analyses that the compact recent-task list becomes too shallow.