diff --git a/.orca-agent/split-ipc-pty-plan.md b/.orca-agent/split-ipc-pty-plan.md deleted file mode 100644 index 4c70dededf2..00000000000 --- a/.orca-agent/split-ipc-pty-plan.md +++ /dev/null @@ -1,127 +0,0 @@ -# Split `src/main/ipc/pty.ts` under 400 lines - -## Baseline (Phase A) - -- Branch: `split-ipc-pty` (clean) -- Source: `src/main/ipc/pty.ts` **7866** physical lines -- `pnpm run typecheck:node`: **pass** -- Focused tests: `pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/pty.test.ts` — **480 passed** - - Note: `pnpm test -- src/main/ipc/pty.test.ts` does **not** filter. The test script is `ensure-native-runtime && vitest run`, so extra args go to the first command. - -Hard size: source `wc -l` ≤ 400; every dest `wc -l` ≤ 400. Oxlint `.ts` max-lines is 300 counted (skip blanks/comments) and dest files must not add max-lines disables — keep dest files ~220–280 counted / well under 400 `wc -l`. - -## Public API (keep on `src/main/ipc/pty.ts` barrel) - -Consumers keep importing `./pty` / `../ipc/pty`. Do **not** add `src/main/ipc/pty/index.ts` (file+folder collision; same pattern as `src/main/github/project-view.ts`). - -- `getBashShellReadyRcfileContent` (passthrough re-export) -- `isCurrentPtyExit` -- `getPtyIdForPaneKey` -- `registerPaneKeyTeardownListener` -- `hasPendingRendererSerializerForPaneKey` -- `BuildPtyHostEnvOptions`, `CodexHomeLaunchContext`, `GetSelectedCodexHomePath`, `PrepareCodexSessionResume`, `CodexHomePtySpawnedLifecycleArgs` -- `resolveCodexHomeAfterManagedAuthReadiness` -- `buildPtyHostEnv` -- `registerSshPtyProvider`, `unregisterSshPtyProvider`, `getSshPtyProvider` -- `getLocalPtyProvider`, `setLocalPtyProvider` -- `getPtyIdsForConnection`, `clearPtyOwnershipForConnection` -- `clearProviderPtyState`, `deletePtyOwnership`, `setPtyOwnership`, `restorePtyIncarnation` -- `rebindLocalProviderListeners`, `unbindLocalProviderListeners` -- `PtyRendererDeliveryDebugSnapshot`, `getPtyRendererDeliveryDebugSnapshot`, `resetPtyRendererDeliveryDebug` -- `registerPtyHandlers`, `registerHeadlessPtyRuntime`, `killAllPty` - -Extracted modules import **siblings** (`./pty-provider-registry`), never the barrel. - -## Dest files - -### A. Module-level (lines 243–2361) - -| Dest | Source range (approx) | Contents | -|---|---|---| -| `src/main/ipc/pty/pty-provider-registry.ts` | 243–260, 930–1018, 1981–2016 | `localProvider`, SSH maps, `getProvider*`, SSH/local register accessors | -| `src/main/ipc/pty/pty-ownership-state.ts` | 266–272, 2018–2042, 2123–2136 | ownership + incarnation maps and public mutators | -| `src/main/ipc/pty/pty-renderer-visibility-state.ts` | 262–286, 2157 | sizes, input/interactive, visibility sets, drain-invalidator bridges, `providerSnapshotRequiredPtys` | -| `src/main/ipc/pty/pty-pane-key-state.ts` | 288–321, 463–472, 511–519 | paneKey↔ptyId, teardown listeners, pane-key parse | -| `src/main/ipc/pty/pty-pane-serializer-state.ts` | 323–325, 459–461, 521–551, 918–928 | pending serializer gens + `hasPendingRendererSerializerForPaneKey` | -| `src/main/ipc/pty/pty-pane-spawn-reservation.ts` | 326–347, 554–637 | pane spawn reservations + runtime create claims | -| `src/main/ipc/pty/pty-agent-session-owners.ts` | 350–455 | claimed owners, restore-record seed, spawn-live assert | -| `src/main/ipc/pty/pty-launch-authority.ts` | 474–509 | renderer launch-token admit + Agent Teams refresh predicate | -| `src/main/ipc/pty/pty-stable-pane-owner.ts` | 640–916 | persist/resolve/attach/spawn stable pane owner | -| `src/main/ipc/pty/pty-provider-liveness.ts` | 968–1115 | remote-env strip, gone/live probes, `finishPtyShutdown` | -| `src/main/ipc/pty/pty-host-env-types.ts` | 1120–1144, 1242–1274 | public env/codex types, `ptyLifecycleSequence`, `PrepareClaudeAuth` | -| `src/main/ipc/pty/pty-host-env-path.ts` | 1146–1188 | PATH promote + env-key delete | -| `src/main/ipc/pty/pty-codex-home-env.ts` | 1190–1498 | Codex home skip/strip/readiness/account-route | -| `src/main/ipc/pty/pty-pi-agent-env.ts` | 1500–1693 | Pi/OMP/Mimo/OpenCode overlay env | -| `src/main/ipc/pty/pty-host-env.ts` | 1695–1926 | `buildPtyHostEnv` | -| `src/main/ipc/pty/pty-fresh-spawn-routing.ts` | 1928–1979 | Claude launch detect, daemon→local recovery, worktree spawn fence | -| `src/main/ipc/pty/pty-provider-state-cleanup.ts` | 2044–2121 | `clearProviderPtyState` | -| `src/main/ipc/pty/pty-provider-listener-lifecycle.ts` | 2138–2172, 2351–2360 | unsub slots, `rebind`/`unbind` | -| `src/main/ipc/pty/pty-renderer-delivery-debug.ts` | 2174–2271 | snapshot type + breadcrumb/power + get/reset | -| `src/main/ipc/pty/pty-renderer-lifecycle-reset.ts` | 2273–2348 | did-finish-load + lifecycle/gate reset handlers | - -### B. `registerPtyHandlers` closure (2364–7825) - -Cannot stay one function. Introduce `PtyIpcSession` (mutable per-registration object) so nested functions become modules that take `session`. Preserve setup order: - -1. Cancel prior watchdog + neutralize prior closure bridges -2. Register renderer lifecycle reset -3. `ipcMain.removeHandler` / `removeAllListeners` -4. `LocalPtyProvider.configure` -5. Create pending drain queue + delivery state -6. Bind module-scope bridges (`invalidatePendingPtyDrain*`, debug snapshot readers, `rebindProviderListeners`) -7. `bindProviderListeners()` -8. `runtime.setPtyController(...)` -9. Register IPC handlers - -| Dest | Contents | -|---|---| -| `src/main/ipc/pty/pty-ipc-session.ts` | Session type + `createPtyIpcSession` state (maps/timers/queues). Keep under 400 by splitting constants. | -| `src/main/ipc/pty/pty-delivery-constants.ts` | Batch/watermark/interactive constants | -| `src/main/ipc/pty/pty-local-provider-configure.ts` | `configure({ buildSpawnEnv, onSpawned, onExit, onData })` | -| `src/main/ipc/pty/pty-hidden-delivery-transition.ts` | hidden-delivery transition + spawn variant | -| `src/main/ipc/pty/pty-delivery-accounting.ts` | in-flight / ACK / credit / resync / write-off | -| `src/main/ipc/pty/pty-delivery-pending.ts` | pending append/drop/projection + overflow marker | -| `src/main/ipc/pty/pty-delivery-flush.ts` | `flushPendingData` + schedule + drain invalidation | -| `src/main/ipc/pty/pty-delivery-payload.ts` | payload builders, interactive-now, send-to-renderer | -| `src/main/ipc/pty/pty-delivery-accept.ts` | `acceptPtyDataForRenderer` | -| `src/main/ipc/pty/pty-exit-for-renderer.ts` | synthetic kill, retire, prepare/finalize/send exit | -| `src/main/ipc/pty/pty-provider-listener-bind.ts` | `bindProviderListeners` | -| `src/main/ipc/pty/pty-serialize-buffer.ts` | serialize request table + IPC response | -| `src/main/ipc/pty/pty-spawn-cwd.ts` | folder-workspace assert + startup cwd | -| `src/main/ipc/pty/pty-codex-resume-launch.ts` | prepare/resolve/reconcile resume + sequenced-argv strip | -| `src/main/ipc/pty/pty-adopt-stable-pane.ts` | `adoptStablePane` | -| `src/main/ipc/pty/pty-runtime-controller-spawn-preflight.ts` | controller spawn: auth, env, session id (part 1) | -| `src/main/ipc/pty/pty-runtime-controller-spawn-commit.ts` | controller spawn: persist, register, telemetry (part 2) | -| `src/main/ipc/pty/pty-runtime-controller.ts` | remaining controller methods (write/probe/attach/kill/list/serialize/resize) — split further if >400 | -| `src/main/ipc/pty/pty-runtime-controller-kill.ts` | kill / retireRejected / reversible stop | -| `src/main/ipc/pty/pty-ipc-spawn-preflight.ts` | `pty:spawn` handler preflight (cwd, auth, env) | -| `src/main/ipc/pty/pty-ipc-spawn-commit.ts` | `pty:spawn` persist/register/response | -| `src/main/ipc/pty/pty-ipc-write.ts` | write / writeAccepted / claimViewport | -| `src/main/ipc/pty/pty-ipc-resize-visibility.ts` | resize, geometry, ack, visibility, hidden, delivery interest | -| `src/main/ipc/pty/pty-ipc-inspect.ts` | kill/list/has/inspect/cwd/size/serializer IPC | -| `src/main/ipc/pty/pty-ipc-snapshot.ts` | getMainBufferSnapshot + sideEffectSnapshot + delivery debug IPC | -| `src/main/ipc/pty/register-pty-handlers.ts` | thin orchestrator: create session, run setup order, call installers | -| `src/main/ipc/pty/register-headless-pty-runtime.ts` | headless window shim | -| `src/main/ipc/pty/kill-all-pty.ts` | `killAllPty` | - -If any dest exceeds 400 `wc -l` or 300 counted after paste, split again on the next domain seam (do not add a disable). - -## Cycle / behavior risks - -- Extracted files must not import `../pty` (the barrel). Sibling imports only. -- Re-registration must still allocate a **new** session and retarget module-scope bridges (`invalidatePendingPtyDrainPriority/Policy`, `rebindProviderListeners`, debug snapshot readers, dispatcher watchdog clearer). Do not lift session maps to module scope. -- Keep why-comments. SSH/folder-workspace/WSL/Windows/remote-wire paths stay verbatim. -- After shrink, **remove** the `eslint-disable max-lines` on `pty.ts` and prune `config/max-lines-baseline.txt` via `pnpm run check:max-lines-ratchet --prune` if the ratchet reports stale `inline src/main/ipc/pty.ts`. - -## Verification - -```bash -pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/pty.test.ts -pnpm run typecheck:node -pnpm run check:max-lines-ratchet -wc -l src/main/ipc/pty.ts src/main/ipc/pty/*.ts -``` - -Characterization tests: only if an extracted **pure** symbol is uncovered. Do not invent tests for IPC/session wiring already covered by `pty.test.ts`. - -Commit (if green): `refactor: split pty.ts under 400 lines` diff --git a/.orca-agent/split-ipc-pty-remaining.md b/.orca-agent/split-ipc-pty-remaining.md deleted file mode 100644 index 358ef57f1d2..00000000000 --- a/.orca-agent/split-ipc-pty-remaining.md +++ /dev/null @@ -1,14 +0,0 @@ -# Remaining work to finish the pty.ts 400-line split - -## Done -- Source `src/main/ipc/pty.ts`: **7866 → 39** (thin public-API barrel, no `pty/index.ts`) -- Module-level state/env/cleanup extracted under `src/main/ipc/pty/` -- Delivery, inspect/write/snapshot/resize IPC extracted -- Renderer `pty:spawn` split under `ipc/spawn-*.ts` -- `registerPtyHandlers` is a thin orchestrator over `PtyIpcSession` -- Runtime controller split under `runtime/{spawn,kill,operations}*.ts` -- Oxlint on `src/main/ipc/pty/**` is clean (no max-lines disables) - -## After retrying the commit -- `pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/pty.test.ts` -- `pnpm exec tsc --noEmit -p config/tsconfig.node.json` if the commit hook does not already cover it diff --git a/.orca-agent/split-ipc-pty-review-1.md b/.orca-agent/split-ipc-pty-review-1.md deleted file mode 100644 index de2c05a3e0c..00000000000 --- a/.orca-agent/split-ipc-pty-review-1.md +++ /dev/null @@ -1,95 +0,0 @@ -# Split review (round 1): `src/main/ipc/pty.ts` - -Mechanical review only. Dest files live under `src/main/ipc/pty/`. Barrel is `src/main/ipc/pty.ts`. Line counts are physical `wc -l` (last-line from file reads). - -## Verdict - -**Not done.** Barrel, naming, disable, and import-cycle checks pass. The 400-line hard cap fails on three dest files. The `registerPtyHandlers` closure was only partly extracted (plan B modules mostly still inline). No clear intentional runtime behavior change found in the extracted public wiring. - ---- - -## 1. Move-only / no-logic-delta - -**No intentional behavior change found** in the public API or in extracted module-level helpers that are actually wired (ownership, provider registry, cleanup, host env, headless/kill-all, write/snapshot/inspect installers). - -Re-registration still: - -1. Cancels the prior dispatcher watchdog and neutralizes drain/accounting bridges -2. Registers renderer lifecycle reset -3. Removes prior IPC handlers -4. Configures `LocalPtyProvider` -5. Allocates new closure-scoped delivery state (not lifted to module scope) -6. Retargets module-scope bridges -7. Binds provider listeners -8. Installs the runtime controller -9. Registers IPC handlers - -New code is split plumbing (`setInvalidatePendingPtyDrain*`, `setRebindProviderListeners`, `installPty*` wrappers, `createPtyWriteInput`). `installPowerSignalBreadcrumbs()` still runs during handler registration (now from `installPtySnapshotIpcHandlers`, which `registerPtyHandlers` always calls). - -### Findings - -| Sev | Issue | -|-----|--------| -| Medium | `src/main/ipc/pty/pty-spawn-cwd.ts` is an unused copy. `assertFolderWorkspacePtyPathUsable`, `resolvePtySpawnStartupCwd`, and `localStartupCwdDirectoryExists` still live as closures in `register-pty-handlers.ts` and are passed into spawn/controller. Dest is dead; live copies can drift. Either switch callers or delete the dest. | -| Info | Plan B (`PtyIpcSession`, delivery/flush/accept modules, spawn preflight/commit splits) was not done. That is why the three oversized files remain. Not a behavior delta by itself. | - ---- - -## 2. Folder + name - -**Pass.** All dest files are under `src/main/ipc/pty/`. Names are domain-specific (`pty-ownership-state`, `pty-host-env`, `register-pty-handlers`, …). No `helpers` / `utils` / `common` / `misc`. - ---- - -## 3. 400-line cap - -**Fail.** Three dest files exceed 400 physical lines. Barrel is fine. - -| File | `wc -l` | Cap | -|------|--------:|-----| -| `src/main/ipc/pty.ts` | 39 | ok | -| `src/main/ipc/pty/register-pty-handlers.ts` | **2616** | **FAIL** | -| `src/main/ipc/pty/pty-ipc-spawn.ts` | **1415** | **FAIL** | -| `src/main/ipc/pty/pty-runtime-controller.ts` | **608** | **FAIL** | -| `src/main/ipc/pty/pty-ipc-inspect.ts` | 320 | ok | -| `src/main/ipc/pty/pty-codex-home-env.ts` | 302 | ok | -| `src/main/ipc/pty/pty-stable-pane-owner.ts` | 294 | ok | -| `src/main/ipc/pty/pty-host-env.ts` | 271 | ok | -| `src/main/ipc/pty/pty-write-input.ts` | 210 | ok | -| `src/main/ipc/pty/pty-pi-agent-env.ts` | 204 | ok | -| remaining dest files | 12–137 | ok | - -`register-pty-handlers.ts` still owns delivery accounting, pending flush, provider bind, cwd/codex-resume/adoptStablePane, and most resize/visibility/ack IPC. `pty-ipc-spawn.ts` and `pty-runtime-controller.ts` were not split on the plan seams (`pty-ipc-spawn-preflight` / `pty-ipc-spawn-commit`, `pty-runtime-controller-spawn-*` / `-kill`). - -Oxlint counted max-lines is 300. `pty-ipc-inspect.ts` (320 physical) and `pty-codex-home-env.ts` (302 physical) may still trip counted max-lines even though they are under 400 `wc -l`. Split if ratchet/lint complains; do not add a disable. - ---- - -## 4. No max-lines disables on new files - -**Pass.** No `eslint-disable max-lines`, `oxlint-disable max-lines`, or per-file max-lines bump in `src/main/ipc/pty.ts` or `src/main/ipc/pty/**`. - ---- - -## 5. Barrel / no `pty/index.ts` - -**Pass.** - -- `src/main/ipc/pty.ts` is re-exports only (public API matches the plan). -- No `src/main/ipc/pty/index.ts`. -- Consumers still import `./pty` / `../ipc/pty` (`pty.test.ts`, `daemon-init.ts`, `ssh-relay-session.ts`, `index.ts`, etc.). - ---- - -## 6. Extracted files must not import the barrel - -**Pass.** Dest files import siblings (`./pty-provider-registry`) or `src/main/ipc/*` neighbors (`../pty-hidden-delivery-gate`). No `from '../pty'`, `from '../../ipc/pty'`, or `from '.../ipc/pty'`. - ---- - -## Required follow-up (before this split is complete) - -1. Split `register-pty-handlers.ts` (2616), `pty-ipc-spawn.ts` (1415), and `pty-runtime-controller.ts` (608) until every dest `wc -l` ≤ 400. Use the plan B seams; do not add max-lines disables. -2. Wire or delete `pty-spawn-cwd.ts` so cwd helpers exist in one place. - -Do not treat the 400-line work as optional. The rest of the mechanical contract is already in place. diff --git a/.orca-agent/split-ipc-pty-review-2.md b/.orca-agent/split-ipc-pty-review-2.md deleted file mode 100644 index e5410b69f50..00000000000 --- a/.orca-agent/split-ipc-pty-review-2.md +++ /dev/null @@ -1,172 +0,0 @@ -# Split review (round 2): `src/main/ipc/pty.ts` - -Adversarial follow-up to [split-ipc-pty-review-1.md](./split-ipc-pty-review-1.md). Dest files live under `src/main/ipc/pty/`. Barrel is `src/main/ipc/pty.ts`. Line counts are physical last-line from file reads (no `wc` in this pass). - -## Verdict - -**Still not done.** Round 1's dead `pty-spawn-cwd.ts` is now wired. Barrel, dest→barrel imports, naming, max-lines disables, and `Record` checks pass. The 400-line hard cap still fails on the same three dest files; `pty-runtime-controller.ts` grew (608 → 1583) instead of being split on the plan B seams. No new unused dest module. Remaining risks are re-registration live-binding windows, duplicated spawn paths (SSH/WSL/folder drift), and fragile IPC uninstall. - ---- - -## 1. 400-line cap - -**Fail.** Barrel is 39 lines. Three dest files still exceed 400 physical lines. - -| File | lines | Cap | -|------|------:|-----| -| `src/main/ipc/pty.ts` | 39 | ok | -| `src/main/ipc/pty/register-pty-handlers.ts` | **2579** | **FAIL** | -| `src/main/ipc/pty/pty-runtime-controller.ts` | **1583** | **FAIL** | -| `src/main/ipc/pty/pty-ipc-spawn.ts` | **1415** | **FAIL** | -| `src/main/ipc/pty/pty-ipc-inspect.ts` | 320 | ok (counted max-lines 300 may still trip) | -| `src/main/ipc/pty/pty-codex-home-env.ts` | 301 | ok (same) | -| `src/main/ipc/pty/pty-stable-pane-owner.ts` | 293 | ok | -| `src/main/ipc/pty/pty-host-env.ts` | 270 | ok | -| remaining dest files | 12–210 | ok | - -Plan B is still incomplete: - -- No `PtyIpcSession`. Delivery accounting, pending flush, accept path, serialize table, resize/visibility/ack IPC, Codex-resume helpers, and `adoptStablePane` remain inside `register-pty-handlers.ts`. -- `pty-ipc-spawn.ts` was not split into `pty-ipc-spawn-preflight` / `pty-ipc-spawn-commit`. -- `pty-runtime-controller.ts` absorbed more spawn/kill/list/resize instead of `pty-runtime-controller-spawn-*` / `-kill`. - -`config/max-lines-baseline.txt` still has `inline src/main/ipc/pty.ts`. After the barrel shrink that ratchet line is stale (plan: `pnpm run check:max-lines-ratchet --prune`). - -Oxlint counted max-lines is 300. Do not add a disable if inspect/codex-home trip it — split. - ---- - -## 2. Unused extracted modules - -**Pass (modules).** Every dest file has a consumer. Round 1's unused `pty-spawn-cwd.ts` is imported from `register-pty-handlers.ts` and passed into spawn + controller. - -Thin store-binding wrappers (not a second copy of the logic): - -```2013:2020:src/main/ipc/pty/register-pty-handlers.ts - const assertFolderWorkspacePtyPathUsable = (worktreeId: string | undefined): Promise => - assertFolderWorkspacePtyPathUsableImpl(store, worktreeId) - - const resolvePtySpawnStartupCwd = ( - worktreeId: string | undefined, - cwd: string | undefined, - missingDirFallback?: TerminalStartupCwdMissingDirFallback - ): string | undefined => resolvePtySpawnStartupCwdImpl(store, worktreeId, cwd, missingDirFallback) -``` - -**Unused exports** (not unused files; leftover public surface): - -- `pendingSerializerGenSeq`, `cleanupPendingPaneSerializersForSender` in `pty-pane-serializer-state.ts` -- `clearPaneSpawnReservation`, `releaseRuntimePaneCreate` in `pty-pane-spawn-reservation.ts` - -Keep them unexported or use them only internally so they do not look like a second API. - ---- - -## 3. Circular imports / barrel - -**Pass (hard cycles).** - -- Dest files do not import `../pty` / `../../ipc/pty`. -- No `src/main/ipc/pty/index.ts`. -- Consumers still import `./pty` / `../ipc/pty` (`daemon-init.ts`, `ssh-relay-session.ts`, `attach-main-window-services.ts`, `pty.test.ts`). - -Graph is one-way: barrel → dest; `register-pty-handlers` → installers; `register-headless-pty-runtime` → `register-pty-handlers`. Sibling state modules (`ownership` ← `registry` ← `agent-session-owners` ← `cleanup` ← `liveness`) do not loop. - -**Soft coupling (not an import cycle):** module-scope `export let` bridges in `pty-renderer-visibility-state.ts`, `pty-provider-listener-lifecycle.ts`, and `pty-renderer-delivery-debug.ts` are mutated by `register-pty-handlers` and invoked from cleanup / lifecycle-reset / daemon-init. That is the intended live-binding pattern. - ---- - -## 4. Setter / live-binding mistakes - -Setters exist and assignments from other modules go through them (`setInvalidatePendingPtyDrain*`, `setRebindProviderListeners`, `setLocal*Unsub`, `setRead/Reset*Snapshot`, `setLocalPtyProvider`). `unbindLocalProviderListeners` assigns `localDataUnsub = null` in the **defining** module — legal ESM, live for importers. - -Call sites use the imported binding at **call** time (not a captured snapshot), e.g. `clearProviderPtyState` → `invalidatePendingPtyDrainPriority(id, false)`, `rebindLocalProviderListeners` → `rebindProviderListeners?.()`. That is the correct live-binding use. - -### Remaining live-binding risks - -| Sev | Issue | -|-----|--------| -| Medium | Re-registration **neutralizes drain/accounting immediately** (`setInvalidate*(() => {})`, `setResetRendererDeliveryAccountingForLifecycleReset(() => {})`) but does **not** neutralize `rebindProviderListeners`. `setRebindProviderListeners(bindProviderListeners)` is at line 1868. A daemon `replaceDaemonProvider` in that window (`daemon-init.ts` calls `rebindLocalProviderListeners`) rebinds `onData`/`onExit` to the **old** `acceptPtyDataForRenderer` / `sendPtyExitToRenderer` closure. Neutralize rebind at the same moment as drain, or retarget it before any await. | -| Medium | Symmetric window the other way: drain is a no-op from register start until line 1203. `LocalPtyProvider.configure({ onExit })` and SSH teardown can call `clearProviderPtyState` in that window; priority/policy invalidation is dropped. Pre-existing shape, but the window is now ~1200 lines of setup. | -| Low | `setResetRendererDeliveryAccountingForLifecycleReset` at line 690 closes over `sshOutputIntake` before it is constructed (line 1683). Optional chaining makes this a no-op if a lifecycle reset fires in between; SSH projection transfer would be skipped for that reset. | -| Info | `export let localProvider` / drain fns rely on ESM live bindings. electron-vite bundling usually preserves them; a CJS snapshot would freeze `killAllPty` / drain on the initial values. Setters + same-module assignment are the safe half; imported **reads** are the fragile half. | - -`bindProviderListeners` correctly unsubscribes via imported live unsubs then `setLocal*Unsub(...)`. That path is sound. - ---- - -## 5. Behavior deltas - -No intentional public API change. Barrel matches the plan. - -**Highest ongoing delta risk: two full spawn implementations.** - -`pty-ipc-spawn.ts` (`pty:spawn`) and `pty-runtime-controller.ts` (`runtime.setPtyController().spawn`) both assemble host env, Codex home, Claude auth, WSL distro, SSH env strip, pane reservation, persist, and restore-record seed. They already differ in ways that look historical but will now drift independently: - -| Path | IPC `pty:spawn` | Runtime controller | -|------|-----------------|--------------------| -| Folder-workspace assert | yes (if no early stable owner) | yes (if not pre-adopted) | -| Missing-dir / WSL UNC cwd fallback | yes (`wslUncDirectoryExistsAsync`, `localStartupCwdDirectoryExists`) | **no** — `resolvePtySpawnStartupCwd(worktreeId, cwd)` only | -| `recoverFreshSpawnProviderRouting` | `sessionId === undefined` default | passes `args.isNewSession` | -| Agent Teams env refresh / launch-token admit | yes | no (caller pre-allocates handle) | -| `stripRemotePaneEnvWhenHooksDisabled` | yes | yes | - -A later “fix spawn” that only lands in one file is a real SSH/WSL/folder regression. Split further by extracting **one** spawn-commit helper, not by copying more. - -Other notes (not proven new vs monolith): - -- `installPtyWriteIpcHandlers` does not `removeAllListeners('pty:write')`. Parent does, at the top of `registerPtyHandlers`. If installer order changes or a test calls the installer alone, write listeners double. `pty:claimViewport` removes inside the installer; `pty:write` does not. -- `finishPtyShutdown` still calls `markClaudePtyExited` after `clearProviderPtyState` (which already calls it). Duplicate, likely pre-existing. -- `pty-write-input.ts` redeclares inner `PtyWritePayload` / `PtyViewportClaimPayload` that shadow the exported types. No runtime effect. -- Headless shim still uses `isDestroyed: () => true`. Any scheduled `flushPendingData` then `releaseAll()` + clears pending. Same as before; more drain invalidations would hit it more often. - ---- - -## 6. SSH / folder-workspace / WSL - -These paths were moved, not obviously rewritten. Residual hazards: - -**SSH** - -- Encoded-owner routing in `getProviderForPty` still refuses local fallback for disconnected SSH ids. -- `pty:kill` / controller `kill` still tombstone via `finishPtyShutdown` when the SSH provider is gone. -- `hasPtyProviderForInspection` still treats missing SSH hosts as idle. -- Remote pane env is stripped in **both** spawn copies when hooks are off. Keep them identical. -- `writePtyInputAccepted` still returns false when `ptyOwnership.get(id) !== null` (SSH cannot ack). Local owners must be stored as `null`, not missing (`undefined !== null`). - -**Folder workspace** - -- `assertFolderWorkspacePtyPathUsable` now takes `store` and uses `getSshFilesystemProvider` for remote folder roots. Wired through register wrappers. Headless/no-store still no-ops (same as `!store` guard). - -**WSL** - -- `localStartupCwdDirectoryExists` still returns `true` for `\\wsl.localhost` (Win32 `statSync` false ENOENT). -- Only the IPC spawn path does async WSL directory existence + worktree cwd fallback. Runtime-created terminals (splits, CLI, headless) skip that. Do not “simplify” IPC spawn to match the controller. - -**WSL Codex** - -- Both spawn copies set `isWsl: shouldSkipCodexHomeEnvForWindowsShell(shell, cwd)` and `wslDistro` from `codexSelectionTarget`. `configure({ buildSpawnEnv })` in register still branches `ctx?.isWsl` for Codex target + `addOrcaWslInteropEnv`. Keep those three sites aligned. - ---- - -## 7. `Record` leftovers - -**Pass.** No `Record` in `src/main/ipc/pty.ts` or `src/main/ipc/pty/**`. Env maps are `Record`. Casts are narrow (`providerGeneration?: number`), not `any`. - ---- - -## 8. Folder + name + disables - -**Pass.** Dest names are domain-specific. No `helpers` / `utils` / `common`. No `eslint-disable max-lines` / `oxlint-disable max-lines` on dest files. - ---- - -## Required follow-up - -1. Split `register-pty-handlers.ts` (2579), `pty-runtime-controller.ts` (1583), and `pty-ipc-spawn.ts` (1415) until every dest `wc -l` ≤ 400. Use plan B seams. Do not add max-lines disables. -2. Neutralize **all** session bridges at re-register start (`rebindProviderListeners` included), then retarget after the new session exists. -3. Dedup spawn preflight/commit before the two copies diverge on SSH env / WSL cwd / folder-workspace. -4. Make `installPtyWriteIpcHandlers` uninstall `pty:write` itself (same as `pty:claimViewport`). -5. Prune stale `inline src/main/ipc/pty.ts` from `config/max-lines-baseline.txt` after the barrel shrink. - -Do not treat the 400-line work as optional. The mechanical contract (barrel, no dest→barrel, cwd wiring, no `any` records) is otherwise in place.