squadrant 0.16.5 → 0.16.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +108 -34
- package/dist/index.js.map +1 -1
- package/dist/squadrantd.js +91 -33
- package/dist/squadrantd.js.map +1 -1
- package/package.json +1 -1
- package/plugin/skills/captain-ops/SKILL.md +9 -3
package/dist/squadrantd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../packages/core/src/snapshot.ts","../packages/cli/src/squadrantd.ts","../packages/shared/src/config.ts","../packages/shared/src/project-config.ts","../packages/shared/src/types/control.ts","../packages/shared/src/lib/cmux-autoconfig.ts","../packages/shared/src/lib/cmux-config.ts","../packages/shared/src/lib/cmux-probe.ts","../packages/shared/src/lib/cmux-bin.ts","../packages/shared/src/lib/compat-manifest.ts","../packages/shared/src/lib/update-check.ts","../packages/shared/src/lib/git-worktree.ts","../packages/shared/src/lib/resolve-text-input.ts","../packages/shared/src/lib/runtime-sync.ts","../packages/shared/src/lib/tool-compat.ts","../packages/shared/src/lib/canonical-source.ts","../packages/shared/src/lib/daily-logs.ts","../packages/core/src/state-machine.ts","../packages/core/src/watchdog.ts","../packages/core/src/daemon/reduce.ts","../packages/core/src/mailbox.ts","../packages/core/src/protocol.ts","../packages/core/src/liveness.ts","../packages/core/src/store.ts","../packages/core/src/index.ts","../packages/core/src/launchd.ts","../packages/core/src/crew-pane-reader.ts","../packages/core/src/gate.ts","../packages/core/src/daemon/context.ts","../packages/core/src/daemon/liveness-registry.ts","../packages/core/src/daemon/attach.ts","../packages/core/src/daemon/start.ts","../packages/core/src/daemon/interactive-probe.ts","../packages/core/src/daemon/probes.ts","../packages/core/src/delivery/defer-delivery.ts","../packages/core/src/delivery/captain-delivery.ts","../packages/core/src/daemon/delivery-loop.ts","../packages/core/src/daemon/gates.ts","../packages/core/src/daemon/server.ts","../packages/core/src/daemon/snapshot-gather.ts","../packages/core/src/session-freshness.ts","../packages/core/src/crew-protocol.ts","../packages/core/src/crew-lifecycle.ts","../packages/core/src/telegram/auth.ts","../packages/core/src/telegram/commands.ts","../packages/core/src/telegram/control.ts","../packages/core/src/telegram/ensure-captain.ts","../packages/core/src/telegram/format.ts","../packages/core/src/telegram/state.ts","../packages/core/src/telegram/client.ts","../packages/core/src/telegram/bridge.ts","../packages/core/src/telegram/panels.ts","../packages/core/src/telegram/tiers.ts","../packages/core/src/telegram/setup.ts","../packages/core/src/restart-daemon.ts","../packages/core/src/group-dispatch.ts","../packages/core/src/side-session.ts","../packages/core/src/crew-spawn.ts","../packages/core/src/lifecycle-source.ts","../packages/agents/src/drivers/claude.ts","../packages/agents/src/drivers/codex.ts","../packages/agents/src/drivers/gemini.ts","../packages/agents/src/drivers/opencode.ts","../packages/agents/src/drivers/launch-cmd.ts","../packages/agents/src/projection/cursor.ts","../packages/agents/src/projection/codex.ts","../packages/agents/src/projection/gemini.ts","../packages/agents/src/projection/opencode.ts","../packages/agents/src/codex/app-server-client.ts","../packages/agents/src/codex/codex-app-server-source.ts","../packages/agents/src/codex/config.ts","../packages/agents/src/codex/normalize.ts","../packages/agents/src/codex/driver.ts","../packages/agents/src/opencode/sse-bridge.ts","../packages/agents/src/interactive/claude.ts","../packages/agents/src/headless/types.ts","../packages/agents/src/headless/claude.ts","../packages/agents/src/headless/opencode.ts","../packages/agents/src/headless/codex.ts","../packages/agents/src/headless/registry.ts","../packages/agents/src/headless-launcher.ts","../packages/workspaces/src/runtimes/cmux.ts","../packages/workspaces/src/runtimes/registry.ts","../packages/workspaces/src/notifiers/cmux.ts","../packages/workspaces/src/notifiers/registry.ts","../packages/workspaces/src/workspaces/obsidian.ts","../packages/workspaces/src/cmux-daemon/events-bridge.ts","../packages/workspaces/src/cmux-daemon/daemon-cmux.ts","../packages/workspaces/src/cmux-daemon/store-fingerprint.ts","../packages/workspaces/src/cmux-daemon/cmux-store-source.ts","../packages/workspaces/src/native-hooks/native-hook-source.ts","../packages/workspaces/src/crew-pane.ts","../packages/cli/src/lib/daemon-restart-broadcast.ts"],"sourcesContent":["// src/control/snapshot.ts\n//\n// PURE Tier 0/1/2 snapshot assembly (no I/O, no clock) for the read-only\n// `snapshot` socket verb — the observability-dashboard counterpart to\n// liveness.ts. Every derived value comes from already-gathered inputs + an\n// explicit `now`, so the whole module is trivially unit-testable. squadrantd.ts\n// performs the I/O (dist stat, log read, mailbox/store/results reads) and feeds\n// the gathered numbers in here; this module never touches the filesystem.\nimport type { ComponentHealth } from \"./liveness.js\";\nimport type { MailboxStats } from \"./mailbox.js\";\nimport type { CaptainDeliveryStats } from \"./delivery/captain-delivery.js\";\nimport type { TelegramBridgeHealth } from \"./telegram/bridge.js\";\nexport type { MailboxStats };\n\n/** B3: Telegram bridge status. `configured: false` when no bridge is set up\n * (v.s. `configured: true` with a dead poll loop — a distinct, worse state). */\nexport interface TelegramHealth extends TelegramBridgeHealth {\n configured: boolean;\n}\n\n/** B4: one registered LifecycleSource's health (cmux-store/native-hook/codex-appserver). */\nexport interface LifecycleSourceHealth {\n name: string;\n active: boolean;\n error: string | null;\n}\n\nexport type BuildState = \"fresh\" | \"stale\";\n\n/**\n * Pure. The deploy-hygiene check: a daemon whose process started BEFORE the\n * current `dist/` build is running stale code (the recurring footgun). Fresh\n * requires the process to have started at or after the last build.\n * processStartedAt >= distBuiltAt → \"fresh\" (boundary inclusive)\n * else → \"stale\"\n */\nexport function buildFreshness(processStartedAt: number, distBuiltAt: number): BuildState {\n return processStartedAt >= distBuiltAt ? \"fresh\" : \"stale\";\n}\n\n// ── Tier 0: daemon root ───────────────────────────────────────────────────────\nexport interface DaemonRoot {\n pid: number;\n uptimeMs: number;\n version: string;\n build: { state: BuildState; processStartedAt: number; distBuiltAt: number };\n /** lastSweepAt/ageMs are null until the first sweep has run. */\n sweep: { lastSweepAt: number | null; ageMs: number | null; cadenceMs: number };\n log: { errorCount: number; sizeBytes: number; windowMs: number };\n telegram: TelegramHealth;\n lifecycleSources: LifecycleSourceHealth[];\n}\n\n// ── Tier 2: per-project data plane + global results ───────────────────────────\n\nexport interface DeliveryLag {\n maxSeq: number;\n lastAckedSeq: number;\n /** maxSeq − lastAckedSeq, clamped at 0 — \"captain N behind\". */\n behind: number;\n}\n\nexport interface StoreStats {\n byState: Record<string, number>;\n corruptCount: number;\n}\n\nexport interface ResultArtifacts {\n fileCount: number;\n totalBytes: number;\n}\n\nexport interface ProjectDataPlane {\n project: string;\n mailbox: MailboxStats;\n delivery: DeliveryLag;\n store: StoreStats;\n /** B1: read-only captain-delivery deferral visibility (#484/#466-class stalls). */\n deferral: CaptainDeliveryStats;\n}\n\nexport interface DaemonSnapshot {\n tier0: DaemonRoot;\n /** Tier 1 — per-component liveness across all projects (reuses projectHealth). */\n tier1: ComponentHealth[];\n tier2: {\n projects: ProjectDataPlane[];\n /** _results/ is a single global directory keyed by task id. */\n results: ResultArtifacts;\n };\n}\n\n/** Already-gathered (I/O-resolved) inputs the pure assembler turns into a DaemonSnapshot. */\nexport interface DaemonSnapshotInputs {\n pid: number;\n processStartedAt: number;\n version: string;\n distBuiltAt: number;\n lastSweepAt: number | null;\n sweepCadenceMs: number;\n log: { errorCount: number; sizeBytes: number; windowMs: number };\n telegram: TelegramHealth;\n lifecycleSources: LifecycleSourceHealth[];\n health: ComponentHealth[];\n projects: Array<{\n project: string;\n mailbox: MailboxStats;\n lastAckedSeq: number;\n storeByState: Record<string, number>;\n corruptCount: number;\n /** Omitted when the caller has no CaptainDelivery instance for this project yet. */\n deferral?: CaptainDeliveryStats;\n }>;\n results: ResultArtifacts;\n}\n\n/**\n * Pure. Derive the full DaemonSnapshot from gathered inputs and an explicit now.\n */\nexport function assembleDaemonSnapshot(input: DaemonSnapshotInputs, now: number): DaemonSnapshot {\n return {\n tier0: {\n pid: input.pid,\n uptimeMs: now - input.processStartedAt,\n version: input.version,\n build: {\n state: buildFreshness(input.processStartedAt, input.distBuiltAt),\n processStartedAt: input.processStartedAt,\n distBuiltAt: input.distBuiltAt,\n },\n sweep: {\n lastSweepAt: input.lastSweepAt,\n ageMs: input.lastSweepAt == null ? null : now - input.lastSweepAt,\n cadenceMs: input.sweepCadenceMs,\n },\n log: input.log,\n telegram: input.telegram,\n lifecycleSources: input.lifecycleSources,\n },\n tier1: input.health,\n tier2: {\n projects: input.projects.map((p) => ({\n project: p.project,\n mailbox: p.mailbox,\n delivery: {\n maxSeq: p.mailbox.maxSeq,\n lastAckedSeq: p.lastAckedSeq,\n behind: Math.max(0, p.mailbox.maxSeq - p.lastAckedSeq),\n },\n store: { byState: p.storeByState, corruptCount: p.corruptCount },\n deferral: p.deferral ?? { maxDeferCount: 0, stuck: false },\n })),\n results: input.results,\n },\n };\n}\n","// src/squadrantd.ts — host: constructs concrete drivers + thin shim.\n// All daemon logic lives in daemon/start.ts; this file owns only the\n// concrete class instantiation and the launchd entry guard.\nimport { join, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { fileURLToPath } from \"node:url\";\nimport { readFileSync, statSync } from \"node:fs\";\nimport { buildContext } from \"@squadrant/core\";\nimport { createAttach } from \"@squadrant/core\";\nimport { startDaemon } from \"@squadrant/core\";\nimport { isDaemonSocketLive } from \"@squadrant/core\";\nimport { appendCaptainMessage, createTelegramClient, createTelegramBridge, createEnsureCaptainAlive } from \"@squadrant/core\";\nimport { reduceLifecycle } from \"@squadrant/core\";\nimport type { TelegramBridge } from \"@squadrant/core\";\nimport type { LifecycleSnapshot, LifecycleSourceDeps } from \"@squadrant/core\";\nimport type { TelegramConfig } from \"@squadrant/shared\";\nimport { createRunCommand, createIsCaptainAlive, createLaunch } from \"@squadrant/core\";\nimport { buildCompletionProtocol } from \"@squadrant/core\";\nexport type { SquadrantdOpts } from \"@squadrant/core\";\nexport { defaultIsPidAlive } from \"@squadrant/core\";\nexport { discoverCaptainSurface } from \"@squadrant/core\";\nimport type { AttachFrame } from \"@squadrant/core\";\nimport type { PaneRef } from \"@squadrant/shared\";\nimport { runHeadless, CodexInteractiveDriver, OpencodeSseBridge, CodexAppServerSource } from \"@squadrant/agents\";\nimport { CmuxEventsBridge, DaemonCmux, CmuxStoreSource, NativeHookSource, resendCrewFirstTurn, RuntimeRegistry } from \"@squadrant/workspaces\";\nimport { loadConfig, TERMINAL_STATES } from \"@squadrant/shared\";\nimport { createCmuxDriver } from \"@squadrant/workspaces\";\nimport { createCmuxNotifier, NotifierRegistry } from \"@squadrant/workspaces\";\nimport { maybeBroadcastDaemonRestart } from \"./lib/daemon-restart-broadcast.js\";\n\nconst SELF_PATH = fileURLToPath(import.meta.url);\n// Bundled CLI bin sits next to this daemon entry (dist/index.js · dist/squadrantd.js).\n// Dist-relative + invariant to source moves (see learning #363). Run via\n// `process.execPath <CLI_BIN> ...argv` so we don't depend on PATH (launchd's is minimal).\nconst CLI_BIN = join(dirname(SELF_PATH), \"index.js\");\nconst DAEMON_SOCK = join(homedir(), \".config\", \"squadrant\", \"squadrant.sock\");\nfunction readPkgVersion(): string {\n try {\n const pkgPath = join(dirname(SELF_PATH), \"..\", \"package.json\");\n return (JSON.parse(readFileSync(pkgPath, \"utf-8\")).version as string) ?? \"unknown\";\n } catch { return \"unknown\"; }\n}\nconst PKG_VERSION = readPkgVersion();\n\nexport type ListSurfacesFn = (wsId: string) => Promise<PaneRef[]>;\n\n/** Construct the real Telegram bridge over a fetch-based client. Token comes from\n * config or the TELEGRAM_BOT_TOKEN env var; with neither, the bridge is disabled. */\nfunction buildTelegramBridge(\n cfg: TelegramConfig,\n stateRoot: string,\n log: (m: string) => void,\n): TelegramBridge | undefined {\n const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;\n if (!token) {\n log(\"telegram: config present but no botToken / TELEGRAM_BOT_TOKEN set — bridge disabled\");\n return undefined;\n }\n const client = createTelegramClient({ token });\n // Control surfaces (#402/#403). These act only when remoteControl is on AND the\n // sender is allowlisted (gated inside the bridge); passing them is always safe.\n const ensureCaptainAlive = createEnsureCaptainAlive({\n isAlive: createIsCaptainAlive(DAEMON_SOCK),\n launch: createLaunch(CLI_BIN, log),\n });\n const runCommand = createRunCommand(CLI_BIN);\n const sendReply = (threadId: number | undefined, text: string, replyMarkup?: unknown) =>\n client.sendMessage(cfg.supergroupId, threadId, text, replyMarkup);\n return createTelegramBridge({\n cfg, stateRoot, configRoot: dirname(stateRoot), client, appendCaptainMessage, log,\n ensureCaptainAlive, runCommand, sendReply,\n });\n}\n\n/** Construct the real out-of-band fault-alert channel (#579/#484 Gap 1) via the\n * notifier plugin slot — cmux by default (@squadrant/workspaces), or whichever\n * provider `config.notifier` names, so this works with ZERO extra config for\n * the vast majority of installs (cmux is squadrant's own runtime, not an\n * opt-in integration like Telegram). Best-effort: a notify failure is logged,\n * never thrown into the daemon's delivery loop. */\nfunction buildNotifyFault(log: (m: string) => void): (project: string, text: string) => Promise<void> {\n const registry = new NotifierRegistry({ cmux: createCmuxNotifier });\n return async (project: string, text: string) => {\n try {\n await registry.get(loadConfig()).notify(`[${project}] ${text}`);\n } catch (e) {\n log(`fault notify failed project=${project}: ${(e as Error).message}`);\n }\n };\n}\n\nexport function startSquadrantd(opts: import(\"@squadrant/core\").SquadrantdOpts = {}) {\n const ctx = buildContext(opts);\n const { stateRoot, store, log, spawn, writeResult, inFlightHeadlessIds, activeHeadlessKills } = ctx;\n\n const { broadcast, schedulePromotion, cancelPromotionsFor } = createAttach(ctx);\n ctx.broadcast = broadcast;\n ctx.schedulePromotion = schedulePromotion;\n ctx.cancelPromotionsFor = cancelPromotionsFor;\n\n // ── Concrete driver construction ──────────────────────────────────────────\n // Emit callbacks close over ctx lazily: ctx.d, ctx.broadcast, and\n // ctx.schedulePromotion are late-bound by startDaemon before any emit fires.\n\n // D5: codex app-server LifecycleSource — must be created before codexDriver\n // so the emit closure can call observe(). start() is called in the VITEST-\n // guarded block below after startDaemon() sets ctx.d.\n const codexAppServerSource = new CodexAppServerSource();\n\n const codexDriver = opts.codexDriver ?? new CodexInteractiveDriver({\n emit: (ev) => {\n const found = ctx.store.listAll().find((r) => r.id === ev.id);\n if (!found) return;\n void ctx.d.handle({ kind: \"event\", project: found.project, event: ev });\n if (ev.type === \"task.delta\")\n ctx.broadcast(ev.id, { type: \"delta\", taskId: ev.id, text: ev.chunk } as AttachFrame);\n else if (ev.type === \"task.turn.started\")\n ctx.broadcast(ev.id, { type: \"turn-started\", taskId: ev.id } as AttachFrame);\n else if (ev.type === \"task.turn.completed\")\n ctx.broadcast(ev.id, { type: \"turn-completed\", taskId: ev.id } as AttachFrame);\n else if (ev.type === \"task.input.requested\") {\n ctx.broadcast(ev.id, { type: \"input-requested\", taskId: ev.id, requestId: ev.requestId, question: ev.question } as AttachFrame);\n ctx.schedulePromotion(ev.id, ev.requestId, \"input\", ev.question);\n } else if (ev.type === \"task.approval.requested\") {\n ctx.broadcast(ev.id, { type: \"approval-requested\", taskId: ev.id, requestId: ev.requestId, question: ev.question, kind: ev.kind } as AttachFrame);\n ctx.schedulePromotion(ev.id, ev.requestId, \"approval\", ev.question);\n } else if (ev.type === \"task.reattached\")\n ctx.broadcast(ev.id, { type: \"reattached\", taskId: ev.id } as AttachFrame);\n codexAppServerSource.observe(ev);\n },\n });\n\n const opencodeBridge = opts.opencodeBridge ?? new OpencodeSseBridge({\n emit: (ev) => {\n const found = store.listAll().find((r) => r.id === ev.id);\n if (!found) return;\n void ctx.d.handle({ kind: \"event\", project: found.project, event: ev });\n if (ev.type === \"task.approval.requested\")\n ctx.schedulePromotion(ev.id, ev.requestId, \"approval\", ev.question);\n },\n log,\n });\n\n const cmuxEventsBridge = opts.cmuxEventsBridge ?? new CmuxEventsBridge({\n emit: (ev) => {\n const found = store.listAll().find((r) => r.id === ev.id);\n if (!found) return;\n void ctx.d.handle({ kind: \"event\", project: found.project, event: ev });\n },\n resolve: (hook) => {\n if (!hook.cwd) return undefined;\n return store.listAll().find(\n (r) => r.mode === \"interactive\" && !TERMINAL_STATES.has(r.state) && r.cwd === hook.cwd,\n );\n },\n cursorFile: join(stateRoot, \"cmux-events.seq\"),\n log,\n });\n\n const cmuxStoreSource = new CmuxStoreSource({ log });\n const nativeHookSource = new NativeHookSource({ log });\n\n ctx.codexDriver = codexDriver;\n ctx.opencodeBridge = opencodeBridge;\n ctx.cmuxEventsBridge = cmuxEventsBridge;\n // B4: register for per-source health aggregation in the snapshot. Registering\n // is inert (no I/O) — only start() below (VITEST-guarded) actually runs a\n // source, so health() correctly reports inactive until then.\n ctx.lifecycleSources = [cmuxStoreSource, nativeHookSource, codexAppServerSource];\n\n // ── Telegram bridge (opt-in #65) ──────────────────────────────────────────\n // Built only when config.telegram is present. Skipped under vitest because the\n // bridge's pushLifecycle is composed onto notify and would hit the network;\n // tests inject opts.telegramBridge instead.\n const tgCfg = loadConfig().telegram;\n ctx.telegramBridge = opts.telegramBridge\n ?? (tgCfg && !process.env.VITEST ? buildTelegramBridge(tgCfg, stateRoot, log) : undefined);\n\n // ── Out-of-band fault-alert channel (#579/#484 Gap 1) ─────────────────────\n // Skipped under vitest (would shell out to the real `squadrant` CLI); tests\n // inject opts.notifyFault, or fall back to buildContext()'s no-op default.\n if (opts.notifyFault) ctx.notifyFault = opts.notifyFault;\n else if (!process.env.VITEST) ctx.notifyFault = buildNotifyFault(log);\n\n // ── daemonCmux resolution ─────────────────────────────────────────────────\n ctx.daemonCmux = opts.daemonCmux\n ?? (opts.makeDaemonCmux ?? (() => new DaemonCmux(createCmuxDriver())))();\n\n // ── #466 self-heal: first-turn resend wiring ──────────────────────────────\n // Uses a fresh cmux RuntimeDriver (independent of daemonCmux's narrower\n // DaemonSurfaceDriver seam, which lacks the paste/sendKey primitives) to\n // drive the same paste-settle-Enter delivery path a manual `crew send` uses.\n // Scoped to claude crews — the facet #466's frozen frame confirmed; other\n // providers safely report non-delivery (the daemon's sweep loop still alerts\n // via CREW UNDELIVERED rather than silently retrying forever).\n const resendRuntime = createCmuxDriver();\n ctx.resendFirstTurn = opts.resendFirstTurn ?? (async (rec) => {\n if (rec.provider !== \"claude\" || !rec.name) return { delivered: false };\n const proj = loadConfig().projects[rec.project];\n const captainName = proj?.captainName ?? `${rec.project}-captain`;\n const message = `${rec.task}\\n\\n${buildCompletionProtocol(rec.id, rec.project)}`;\n return resendCrewFirstTurn(resendRuntime, captainName, rec.project, rec.name, message);\n });\n\n // ── launchHeadless default ────────────────────────────────────────────────\n // Kept here so this file is the sole importer of headless-launcher (daemon/* can't).\n const launchHeadless = opts.launchHeadless ?? (async (rec) => {\n const ingest = (e: import(\"@squadrant/shared\").ControlEvent) =>\n void ctx.d.handle({ kind: \"event\", project: rec.project, event: e });\n const handle = runHeadless({\n provider: rec.provider, task: rec.task, id: rec.id, sessionId: rec.sessionId,\n cwd: rec.cwd, spawn, emit: ingest, writeResult,\n });\n inFlightHeadlessIds.add(rec.id);\n activeHeadlessKills.add(handle.kill);\n try { await handle.result; } finally {\n inFlightHeadlessIds.delete(rec.id);\n activeHeadlessKills.delete(handle.kill);\n }\n });\n\n const h = startDaemon(ctx, { ...opts, launchHeadless }, PKG_VERSION);\n\n // A1: start cmux store-file backup lifecycle source alongside CmuxEventsBridge (B1).\n // startDaemon() guarantees ctx.d is set before returning. Skipped under vitest\n // (mirrors the B1 guard in start.ts — real fs.watch would touch disk in tests).\n if (!process.env.VITEST) {\n const prevSnaps = new Map<string, LifecycleSnapshot>();\n const storeDeps: LifecycleSourceDeps = {\n resolve: (hint) => {\n if (!hint.cwd && hint.pid == null) return undefined;\n return store.listAll().find(\n (r) => r.mode === \"interactive\" && !TERMINAL_STATES.has(r.state) &&\n (r.cwd === hint.cwd || (hint.pid != null && r.pid === hint.pid)),\n );\n },\n report: (snap) => {\n const found = store.listAll().find((r) => r.id === snap.taskId);\n if (!found) return;\n const prev = prevSnaps.get(snap.taskId);\n const newState = reduceLifecycle(prev, snap);\n const changed = !prev || newState !== prev.state;\n prevSnaps.set(snap.taskId, snap);\n if (!snap.alive) {\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.session.ended\", id: snap.taskId } });\n return;\n }\n if (!changed) return;\n if (newState === \"idle\") {\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.turn.completed\", id: snap.taskId, turnId: \"cmux-store\" } });\n } else if (newState === \"running\") {\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.progress\", id: snap.taskId } });\n } else if (newState === \"needsInput\") {\n const question = snap.detail?.note ?? snap.detail?.reason ?? \"crew awaiting input\";\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.blocked\", id: snap.taskId, reason: \"needsInput\", question } });\n }\n },\n log,\n };\n try { cmuxStoreSource.start(storeDeps); }\n catch (e) { log(`cmux store source start failed: ${(e as Error).message}`); }\n\n // C1: start native hook source (primary LifecycleSource C). Installs squadrant-\n // owned hooks into ~/.claude/settings.json (idempotent, namespaced per D4).\n try { nativeHookSource.install(); }\n catch (e) { log(`native hook install failed: ${(e as Error).message}`); }\n const hookPrevSnaps = new Map<string, LifecycleSnapshot>();\n const hookDeps: LifecycleSourceDeps = {\n resolve: (hint) => {\n if (!hint.cwd && hint.pid == null) return undefined;\n return store.listAll().find(\n (r) => r.mode === \"interactive\" && !TERMINAL_STATES.has(r.state) &&\n (r.cwd === hint.cwd || (hint.pid != null && r.pid === hint.pid)),\n );\n },\n report: (snap) => {\n const found = store.listAll().find((r) => r.id === snap.taskId);\n if (!found) return;\n const prev = hookPrevSnaps.get(snap.taskId);\n const newState = reduceLifecycle(prev, snap);\n const changed = !prev || newState !== prev.state;\n hookPrevSnaps.set(snap.taskId, snap);\n if (!snap.alive) {\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.session.ended\", id: snap.taskId } });\n return;\n }\n if (!changed) return;\n if (newState === \"idle\") {\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.turn.completed\", id: snap.taskId, turnId: \"native-hook\" } });\n } else if (newState === \"running\") {\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.progress\", id: snap.taskId } });\n } else if (newState === \"needsInput\") {\n const question = snap.detail?.note ?? snap.detail?.reason ?? \"crew awaiting input\";\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.blocked\", id: snap.taskId, reason: \"needsInput\", question } });\n }\n },\n log,\n };\n try { nativeHookSource.start(hookDeps); }\n catch (e) { log(`native hook source start failed: ${(e as Error).message}`); }\n\n // D5: start codex app-server lifecycle source. codexAppServerSource.observe()\n // is already wired into the codexDriver emit above; start() connects the deps.\n const codexPrevSnaps = new Map<string, LifecycleSnapshot>();\n const codexSourceDeps: LifecycleSourceDeps = {\n resolve: () => undefined, // taskId comes from ControlEvent.id; resolve() unused\n report: (snap) => {\n const found = store.listAll().find((r) => r.id === snap.taskId);\n if (!found) return;\n const prev = codexPrevSnaps.get(snap.taskId);\n const newState = reduceLifecycle(prev, snap);\n const changed = !prev || newState !== prev.state;\n codexPrevSnaps.set(snap.taskId, snap);\n if (!snap.alive) {\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.session.ended\", id: snap.taskId } });\n return;\n }\n if (!changed) return;\n if (newState === \"idle\") {\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.turn.completed\", id: snap.taskId, turnId: \"codex-appserver\" } });\n } else if (newState === \"running\") {\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.progress\", id: snap.taskId } });\n } else if (newState === \"needsInput\") {\n const question = snap.detail?.note ?? snap.detail?.reason ?? \"crew awaiting input\";\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.blocked\", id: snap.taskId, reason: \"needsInput\", question } });\n }\n },\n log,\n };\n try { codexAppServerSource.start(codexSourceDeps); }\n catch (e) { log(`codex app-server source start failed: ${(e as Error).message}`); }\n }\n\n // Daemon-restart broadcast: notify every running captain that the daemon\n // bounced, but only when the running build actually changed (version bump\n // or local rebuild) — a same-build launchd crash-restart stays silent.\n // Skipped under vitest (touches the real config + cmux driver, mirrors the\n // other real-I/O boot actions guarded the same way above).\n if (!process.env.VITEST) {\n try {\n const buildMtimeMs = statSync(SELF_PATH).mtimeMs;\n const restartConfig = loadConfig();\n const registry = new RuntimeRegistry({ cmux: createCmuxDriver() });\n void maybeBroadcastDaemonRestart({\n version: PKG_VERSION,\n buildMtimeMs,\n stateRoot,\n config: restartConfig,\n driver: registry.global(restartConfig),\n appendCaptainMessage: (project: string, text: string) =>\n appendCaptainMessage({ stateRoot, project, text, source: \"daemon\" }),\n });\n } catch (e) {\n log(`daemon-restart broadcast setup failed: ${(e as Error).message}`);\n }\n }\n\n const origStop = h.stop.bind(h);\n h.stop = async (reason?: string) => {\n try { cmuxStoreSource.stop(); } catch { /* best-effort */ }\n try { nativeHookSource.stop(); } catch { /* best-effort */ }\n try { codexAppServerSource.stop(); } catch { /* best-effort */ }\n return origStop(reason);\n };\n\n return h;\n}\n\n/** Greppable crash marker (#535) — matches the `[squadrantd] <iso> <msg>` shape\n * ctx.log uses, but writes directly since ctx.log doesn't exist until buildContext()\n * runs; a crash before that point must still be diagnosable. */\nfunction logCrashMarker(kind: \"uncaughtException\" | \"unhandledRejection\", err: unknown): void {\n const message = err instanceof Error ? (err.stack ?? err.message) : String(err);\n process.stderr.write(`[squadrantd] ${new Date().toISOString()} ${kind} pid=${process.pid} error=${message}\\n`);\n}\n\n// Executed by launchd (ProgramArguments → this file's compiled .js).\nif (process.argv[1] && process.argv[1].endsWith(\"squadrantd.js\")) {\n // Registered before any boot work so a crash during startup is still logged.\n process.on(\"uncaughtException\", (err) => { logCrashMarker(\"uncaughtException\", err); process.exit(1); });\n process.on(\"unhandledRejection\", (reason) => { logCrashMarker(\"unhandledRejection\", reason); process.exit(1); });\n\n void (async () => {\n // #360 layer 1: this entry takes no CLI flags. A build smoke-test like\n // `node dist/squadrantd.js --help` must NOT boot a daemon — it would hang\n // and steal the shared socket. Print a one-liner and exit.\n const arg = process.argv[2];\n if (arg === \"--help\" || arg === \"-h\" || arg === \"--version\" || arg === \"-v\") {\n process.stdout.write(\"squadrantd: launchd-managed daemon entry (no CLI args). Use `squadrant` for commands.\\n\");\n process.exit(0);\n }\n // #360 layer 2: refuse to start if a live daemon already owns the socket.\n // startServer does unlink-then-bind; without this guard a second invocation\n // unlinks the live socket, orphaning the running daemon on its now-anonymous\n // inode so every new connect() to the path is refused.\n const sock = join(homedir(), \".config\", \"squadrant\", \"squadrant.sock\");\n if (await isDaemonSocketLive(sock)) {\n process.stderr.write(`[squadrantd] refusing to start: a live daemon already owns ${sock}\\n`);\n process.exit(0);\n }\n const h = startSquadrantd({ sweepMs: 30000 });\n // #535: await stop() before exiting — it writes the exit marker and runs\n // teardown (bridges, in-flight headless kills) synchronously-then-async;\n // exiting immediately after firing it (not awaiting) raced process.exit()\n // against that work and silently dropped it every time.\n const shutdown = (signal: \"SIGTERM\" | \"SIGINT\") => { void h.stop(signal).finally(() => process.exit(0)); };\n process.on(\"SIGTERM\", () => shutdown(\"SIGTERM\"));\n process.on(\"SIGINT\", () => shutdown(\"SIGINT\"));\n })();\n}\n","// src/config.ts\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport chalk from \"chalk\";\n\nexport interface ProjectConfig {\n path: string;\n captainName: string;\n spokeVault: string;\n host: string;\n group?: string;\n groupRole?: string;\n runtime?: string;\n workspace?: string;\n /** #246: when false, `squadrant group dispatch` rejects delegations to this\n * project. Defaults to true when absent. */\n acceptDelegations?: boolean;\n}\n\nexport interface PermissionConfig {\n command: string; // permission mode for the command session\n captain: string; // permission mode for captain sessions\n crew?: string; // permission mode for crew sessions (default: acceptEdits)\n // Flexible role->mode map so future roles don't need a type change.\n [role: string]: string | undefined;\n}\n\nexport type ModelAlias = \"opus\" | \"sonnet\" | \"haiku\";\n\nexport interface CrewRoutingRule {\n tier: string;\n match: string;\n agent: string;\n model?: string;\n}\n\nexport interface CrewRoutingConfig {\n rules: CrewRoutingRule[];\n}\n\nexport interface TelegramConfig {\n botToken?: string; // falls back to env TELEGRAM_BOT_TOKEN at read time\n supergroupId: number; // forum supergroup hosting per-project topics\n chats: number[]; // chat_id allowlist (inbound honored only from these)\n users?: number[]; // user-id allowlist for CONTROL actions (#321); empty ⇒ control disabled\n remoteControl?: boolean; // opt-in master switch for auto-launch + general commands (default false)\n pollMs?: number; // getUpdates long-poll cadence (default 1000)\n /** Global notification defaults (per-project override lives in projects/<name>.json). */\n notify?: { active?: boolean; cap?: boolean; crew?: \"all\" | \"alert_only\" | \"done_only\" | \"none\" };\n}\n\nexport interface ModelRoutingConfig {\n command: ModelAlias;\n captain: ModelAlias;\n crew: ModelAlias;\n exploration: ModelAlias;\n review: ModelAlias;\n}\n\nexport interface AgentEntry {\n cli: string;\n driver: string;\n}\n\nexport interface RoleAssignment {\n agent: string;\n model?: string;\n}\n\nexport type RoleConfig = Partial<Record<\"command\" | \"captain\" | \"crew\" | \"exploration\" | \"side\", RoleAssignment>>;\n\nexport interface SquadrantConfig {\n /** Package version that last reconciled this config. Absent on legacy/fresh configs. */\n _squadrantVersion?: string;\n commandName: string;\n hubVault: string;\n projects: Record<string, ProjectConfig>;\n agents?: Record<string, AgentEntry>;\n runtime?: string;\n workspace?: string;\n notifier?: string;\n /** Optional Telegram bridge config. Absent ⇒ the bridge is never constructed\n * (zero behavior change). See docs/superpowers/specs/2026-06-22-telegram-integration-v1-design.md. */\n telegram?: TelegramConfig;\n projection?: {\n targets?: string[];\n };\n delivery?: {\n /** Defer count at which a stuck delivery is flagged on the dashboard (B1). Does NOT\n * force delivery on its own — probing an actively-changing draft is unsafe (#484); only\n * content stability (stableProbePolls) escalates to a probe. Default: 300 (~5min). */\n maxDeferDeliveries?: number;\n /** Consecutive stable-content polls before probing early to avoid a stall (#302). Default: 3 (~3s). */\n stableProbePolls?: number;\n };\n defaults: {\n maxCrew: number;\n worktreeDir: string;\n teammateMode: string;\n permissions: PermissionConfig;\n models?: ModelRoutingConfig;\n roles?: RoleConfig;\n /** #225 hard crew task-timeout ceiling (ms). Default: 8h. */\n taskTimeoutMs?: number;\n /** #275 rule-based crew routing: keyword rules map task text to {agent, model}. Optional — absent = fall through to defaults.roles.crew. */\n crewRouting?: CrewRoutingConfig;\n /** B1: consume cmux's native event stream for crew turn-end (idle) detection\n * alongside the scrape fallback. Default true; set false for scrape-only. */\n cmuxEventsBridge?: boolean;\n /**\n * Audit C2 — agent hibernation (reclaim idle-crew RAM). INTENTIONALLY OFF and\n * INERT: cmux 0.64.16's `cmux agent-hibernation <on|off>` is GLOBAL (app-wide,\n * no per-session/per-workspace scope), so enabling it would also hibernate the\n * CAPTAIN — which must stay responsive for daemon-direct delivery —\n * breaking orchestration. We do NOT call `agent-hibernation on`\n * anywhere; this flag is a documented decision record + a forward hook for when\n * cmux gains crew-only scoping. Until then leave false.\n * See docs/research/2026-06-16-cmux-events-stream.md (C2 finding).\n */\n cmuxAgentHibernation?: boolean;\n /** #317 global crew tokenomics dial. Absent ⇒ \"balance\" (today's behavior).\n * Biases the captain toward stronger (\"max\") or cheaper (\"low\") crew models. */\n effort?: \"max\" | \"balance\" | \"low\";\n /** #536 startup npm-registry update check. Default true (absent ⇒ enabled);\n * set false to opt out. NO_UPDATE_NOTIFIER env var also opts out. */\n updateCheck?: boolean;\n };\n metrics: {\n enabled: boolean;\n path: string;\n };\n}\n\nconst CONFIG_DIR = path.join(os.homedir(), \".config\", \"squadrant\");\nexport const DEFAULT_CONFIG_PATH = path.join(CONFIG_DIR, \"config.json\");\n\nexport function getDefaultConfig(): SquadrantConfig {\n return {\n commandName: \"\\u{1F3DB}\\u{FE0F} command\",\n hubVault: path.join(os.homedir(), \"squadrant-hub\"),\n projects: {},\n agents: {\n claude: { cli: \"claude\", driver: \"claude\" },\n },\n defaults: {\n maxCrew: 5,\n worktreeDir: \".worktrees\",\n teammateMode: \"in-process\",\n permissions: {\n command: \"auto\",\n captain: \"auto\",\n crew: \"auto\",\n },\n models: {\n command: \"opus\",\n captain: \"opus\",\n crew: \"sonnet\",\n exploration: \"haiku\",\n review: \"opus\",\n },\n roles: {\n command: { agent: \"claude\", model: \"opus\" },\n captain: { agent: \"claude\", model: \"opus\" },\n crew: { agent: \"claude\", model: \"sonnet\" },\n exploration: { agent: \"claude\", model: \"haiku\" },\n side: { agent: \"claude\", model: \"opus\" },\n },\n taskTimeoutMs: 8 * 60 * 60 * 1000,\n cmuxEventsBridge: true,\n // Audit C2: OFF by design — cmux hibernation is global-only and would\n // hibernate the captain. See the field doc above.\n cmuxAgentHibernation: false,\n crewRouting: {\n rules: [\n { tier: \"extreme\", match: \"redesign|architect|rewrite|from scratch|deep reasoning\", agent: \"claude\", model: \"opus\" },\n { tier: \"hard\", match: \"refactor|migrate|implement|feature|daemon|control-plane\", agent: \"claude\", model: \"sonnet\" },\n { tier: \"mobile\", match: \"mobile|ios|swift|android|kotlin|react native\", agent: \"codex\" },\n { tier: \"daily\", match: \"typo|rename|bump|docs|comment|lint|format\", agent: \"opencode\" },\n ],\n },\n },\n metrics: {\n enabled: true,\n path: path.join(CONFIG_DIR, \"metrics.json\"),\n },\n };\n}\n\nexport function loadConfig(configPath = DEFAULT_CONFIG_PATH): SquadrantConfig {\n try {\n const raw = fs.readFileSync(configPath, \"utf-8\");\n const config = JSON.parse(raw) as SquadrantConfig;\n\n // Backward compat: migrate models → roles if roles not set\n if (config.defaults.models && !config.defaults.roles) {\n const m = config.defaults.models;\n config.defaults.roles = {\n command: { agent: \"claude\", model: m.command },\n captain: { agent: \"claude\", model: m.captain },\n crew: { agent: \"claude\", model: m.crew },\n exploration: { agent: \"claude\", model: m.exploration },\n };\n }\n\n // Ensure agents has at least claude\n if (!config.agents) {\n config.agents = { claude: { cli: \"claude\", driver: \"claude\" } };\n }\n\n // #286: backfill crewRouting for configs written before routing existed\n if (!config.defaults.crewRouting) {\n config.defaults.crewRouting = getDefaultConfig().defaults.crewRouting;\n saveConfig(config, configPath);\n console.error(\n chalk.cyan(\n \"⬆ squadrant upgrade: added default crew routing rules to your config (leveled routing now active). \" +\n \"Edit defaults.crewRouting in ~/.config/squadrant/config.json or use the squadrant:add-pick-crew-rule skill.\",\n ),\n );\n }\n\n return config;\n } catch {\n return getDefaultConfig();\n }\n}\n\nexport function saveConfig(\n config: SquadrantConfig,\n configPath = DEFAULT_CONFIG_PATH,\n): void {\n const dir = path.dirname(configPath);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + \"\\n\");\n}\n\nexport function resolveHome(p: string): string {\n return p.startsWith(\"~\") ? p.replace(\"~\", os.homedir()) : p;\n}\n","// Per-project layered config override file. Pure, file-backed, no daemon\n// knowledge. Resolved as built-in → global config.json → projects/<name>.json.\n// See docs/superpowers/specs/2026-06-23-per-project-layered-config-design.md.\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport type { ModelRoutingConfig } from \"./config.js\";\n\nexport type CrewTier = \"all\" | \"alert_only\" | \"done_only\" | \"none\";\n\nexport interface NotifyConfig {\n active: boolean;\n cap: boolean;\n crew: CrewTier;\n}\n\n/** Per-project override layer. Every key optional; mirrors the global settings. */\nexport interface ProjectOverrideConfig {\n telegram?: { notify?: Partial<NotifyConfig> };\n // Reserved future tenants (resolver is already generic; consumers not yet wired):\n effort?: \"max\" | \"balance\" | \"low\";\n models?: Partial<ModelRoutingConfig>;\n}\n\nfunction defaultRoot(): string {\n return path.join(os.homedir(), \".config\", \"squadrant\");\n}\n\nexport function projectConfigPath(name: string, root = defaultRoot()): string {\n return path.join(root, \"projects\", `${name}.json`);\n}\n\nexport function loadProjectOverride(name: string, root = defaultRoot()): ProjectOverrideConfig {\n try {\n return JSON.parse(fs.readFileSync(projectConfigPath(name, root), \"utf-8\")) as ProjectOverrideConfig;\n } catch {\n return {};\n }\n}\n\n/** Deep-merge a generic plain-object tree. Arrays/primitives in `patch` replace. */\nexport function deepMerge<T>(base: T, patch: unknown): T {\n if (patch === null || typeof patch !== \"object\" || Array.isArray(patch)) return (patch as T) ?? base;\n const out: Record<string, unknown> = { ...(base as Record<string, unknown>) };\n for (const [k, v] of Object.entries(patch as Record<string, unknown>)) {\n out[k] = deepMerge(out[k], v);\n }\n return out as T;\n}\n\nexport function saveProjectOverride(name: string, patch: ProjectOverrideConfig, root = defaultRoot()): void {\n const merged = deepMerge(loadProjectOverride(name, root), patch);\n const file = projectConfigPath(name, root);\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, JSON.stringify(merged, null, 2) + \"\\n\");\n}\n\nexport const DEFAULT_NOTIFY: NotifyConfig = { active: false, cap: true, crew: \"alert_only\" };\n\nconst CREW_RANK: Record<CrewTier, number> = { none: 0, done_only: 1, alert_only: 2, all: 3 };\nexport function crewRank(tier: CrewTier): number {\n return CREW_RANK[tier];\n}\n\nexport function isQuieter(\n before: NotifyConfig,\n after: NotifyConfig,\n): { quieter: boolean; dim: \"active\" | \"cap\" | \"crew\" | null } {\n if (before.active && !after.active) return { quieter: true, dim: \"active\" };\n if (before.cap && !after.cap) return { quieter: true, dim: \"cap\" };\n if (crewRank(after.crew) < crewRank(before.crew)) return { quieter: true, dim: \"crew\" };\n return { quieter: false, dim: null };\n}\n\n/** Built-in → global → project, per-key. Does NOT apply live state (bridge's job). */\nexport function resolveNotify(\n globalNotify: Partial<NotifyConfig> | undefined,\n override: ProjectOverrideConfig,\n): NotifyConfig {\n let n: NotifyConfig = { ...DEFAULT_NOTIFY };\n if (globalNotify) n = deepMerge(n, globalNotify);\n if (override.telegram?.notify) n = deepMerge(n, override.telegram.notify);\n return n;\n}\n","// src/control/types.ts\nexport type Provider = \"claude\" | \"opencode\" | \"codex\" | \"gemini\";\nexport type Mode = \"headless\" | \"interactive\";\n\nexport type TaskState =\n | \"submitted\"\n | \"working\"\n | \"blocked\"\n // #599: crew has committed to crew/<name> and is paused awaiting the\n // captain's review verdict (approve → push+PR+done, or feedback → crew\n // send reopens to 'working'). NOT terminal — mirrors 'blocked', but the\n // crew is waiting on a review decision rather than an answer to a question.\n | \"review\"\n | \"done\"\n | \"failed\"\n | \"stalled\"\n | \"awaiting-input\"\n | \"cancelled\";\n\nexport interface DispatchAttempt {\n attemptId: string;\n startedAt: number;\n pid?: number;\n resumeRef?: string; // opaque, hashed-treated, NEVER parsed (orca #1148)\n lastHeartbeatAt: number;\n error?: string;\n exitCode?: number;\n circuitBroken?: boolean;\n}\n\nexport interface Gate {\n gateId: string;\n taskId: string;\n kind: \"input\" | \"approval\";\n question: string;\n state: \"pending\" | \"resolved\" | \"timeout\";\n createdAt: number;\n resolvedBy?: string;\n resolution?: unknown;\n}\n\nexport interface TaskRecord {\n id: string;\n /** Human-readable crew name (e.g. the `--name` arg to `squadrant crew spawn`).\n * Optional for backward-compat with records written before this field\n * existed; relay/daemon fall back to the short id when absent. */\n name?: string;\n project: string;\n provider: Provider;\n mode: Mode;\n state: TaskState;\n task: string; // the dispatched instruction\n sessionId?: string; // provider session id for resume (blocked→reply)\n cwd?: string; // working dir for the spawned headless child (project/worktree); unset → daemon cwd\n pid?: number; // headless child pid (daemon-owned)\n question?: string; // populated when state === \"blocked\"\n /** #599: the crew's own summary carried on `signal review`; populated when\n * state === \"review\". Surfaced in the CREW REVIEW notification. */\n reviewNote?: string;\n error?: string; // populated when state === \"failed\"\n exitCode?: number;\n resultRef?: string; // filesystem path to captured output/artifact\n parseWarning?: boolean; // headless exit 0 but unparseable result\n createdAt: number; // epoch ms\n lastHeartbeat: number; // epoch ms\n lastEvent: string; // last event type applied\n heartbeatBudgetMs: number; // per-task stall threshold\n /** Append-only dispatch attempt history. Current attempt = at(-1). */\n attempts: DispatchAttempt[];\n /** Interactive-codex HITL slice (spec §4.9). */\n gates?: Gate[];\n /** Codex AskForApproval policy forwarded to startThread (interactive only).\n * When set to \"untrusted\", codex requests approval for tool/shell calls,\n * exercising the gate-promotion flow end-to-end. */\n approvalPolicy?: string;\n /** Role-priming content forwarded to startThread's developerInstructions\n * (interactive only). Parity with claude's --append-system-prompt-file:\n * injects crew rules / Karpathy discipline before the first user turn. */\n roleInstructions?: string;\n /** TCP port of an interactive opencode crew's embedded HTTP server\n * (`opencode --port <N>`). The daemon's SSE bridge subscribes to\n * http://127.0.0.1:<serverPort>/event for reliable turn-end detection. */\n serverPort?: number;\n /** #246: cross-project intra-group delegation — set to the origin project's\n * name when this task was dispatched by a sibling captain. When the task\n * settles, the daemon fans the outcome back to originProject's mailbox. */\n originProject?: string;\n /** #354: the tool call currently in flight, if any. Set when a PreToolUse\n * liveness signal arrives (cmux events-bridge carries the tool name); cleared\n * the moment its PostToolUse / next turn boundary arrives. A `working` crew\n * whose pendingTool has been outstanding past TOOL_STALL_BUDGET_MS is treated\n * as hung-on-a-tool (CREW STALLED warn) — distinct from a quiet thinking turn\n * (no pendingTool → CREW QUIET). Auto-clears: the next PostToolUse recovers\n * the record to `working` (state-machine + recoverStall). */\n pendingTool?: { name: string; since: number };\n /** #466: epoch ms when the spawn path positively confirmed the first turn was\n * delivered (paste rendered in the box → box emptied = submitted). Unset means\n * either the crew was spawned before this field existed, OR delivery was never\n * confirmed. The watchdog uses this to emit CREW UNDELIVERED instead of the\n * misleading \"deep thinking\" message for a crew that never received its task. */\n firstTurnConfirmedAt?: number;\n}\n\nexport type ControlEvent =\n | { type: \"task.started\"; id: string; pid?: number; sessionId?: string }\n | { type: \"task.progress\"; id: string; note?: string; tool?: string }\n | { type: \"heartbeat\"; id: string }\n | { type: \"task.blocked\"; id: string; reason: string; question: string }\n // #599: explicit review-gate checkpoint — parallel to task.blocked/task.done\n // but NOT terminal. Emitted by `squadrant crew signal review` once the crew\n // has committed to crew/<name> and wants the captain to inspect the diff\n // before it is pushed/PR'd. `message` is an optional crew summary (parity\n // with task.done's optional message).\n | { type: \"task.review\"; id: string; message?: string }\n // #605: `source: 'approve'` is the review-gate's distinct terminal channel —\n // stamped only by `squadrant crew approve` (runCrewApprove). reduce() vetoes\n // any OTHER task.done while state === 'review' (a crew's own completion\n // protocol), so the gate can't be bypassed by crew habit; approve's stamped\n // done is the one path the veto lets through.\n | { type: \"task.done\"; id: string; resultRef: string; message?: string; parseWarning?: boolean; source?: \"approve\" }\n | { type: \"task.failed\"; id: string; error: string; exitCode?: number }\n | { type: \"task.session\"; id: string; resumeRef: string }\n | { type: \"task.turn.started\"; id: string; turnId: string }\n | { type: \"task.turn.completed\"; id: string; turnId: string }\n | { type: \"task.delta\"; id: string; turnId: string; chunk: string }\n | { type: \"task.input.requested\"; id: string; requestId: number; question: string }\n | { type: \"task.approval.requested\"; id: string; requestId: number; question: string; kind: string }\n | { type: \"task.reattached\"; id: string }\n // Reopen: the only event allowed to revive a terminal task. Emitted by\n // `squadrant crew send` when the target crew's daemon task is in a terminal\n // state, allowing the next `signal done` to be a real transition.\n | { type: \"task.reopened\"; id: string }\n // Synthetic events: emitted by the daemon (watchdog / reconcile) purely as\n // notify payloads. They are never sent over the wire and the reducer treats\n // them as no-ops; the watchdog has already updated state directly.\n // #354: `tool`/`elapsedMs` are set when the stall is a hung interactive tool\n // call (PreToolUse with no matching PostToolUse past TOOL_STALL_BUDGET_MS),\n // letting the notifier render \"still running {tool} ~{N}min\" instead of the\n // generic headless \"no heartbeat\" message.\n | { type: \"task.stalled\"; id: string; heartbeatBudgetMs: number; tool?: string; elapsedMs?: number }\n // task.idle is the interactive analogue of task.stalled: the watchdog has\n // already moved an idle interactive task to 'awaiting-input', and this carries\n // the accurate (non-alarming) notify payload to the captain.\n | { type: \"task.idle\"; id: string; heartbeatBudgetMs: number }\n // #354: a `working` interactive crew that has been quiet past its heartbeat\n // budget with NO tool in flight — alive but deep-thinking (no hook fires\n // during pure model thinking). Notify-only (reducer no-op): the crew stays\n // `working`, NOT awaiting-input. Real CREW IDLE still comes only from the Stop\n // hook (a genuine turn-end). `quietMs` = how long it has been silent.\n | { type: \"task.quiet\"; id: string; quietMs: number }\n // #225: emitted by the sweep when a task's wall-clock age exceeds the ceiling.\n // Notify-only (detect-first, #77); reducer is a no-op.\n | { type: \"task.timeout\"; id: string; taskTimeoutMs: number }\n | { type: \"task.reconcile-failed\"; id: string; reason: string }\n // Emitted by runCrewClose before closing the pane; transitions a non-terminal\n // task to the absorbing 'cancelled' state. Silent: captain initiated the close\n // so no CREW CANCELLED push is fired (not in ATTENTION_STATES).\n | { type: \"task.cancelled\"; id: string; reason?: string }\n // #466: emitted by runCrewSpawn after positively confirming the first turn was\n // delivered. Stamps firstTurnConfirmedAt on the record so the watchdog can\n // distinguish a quiet-thinking crew from one that never received its task.\n | { type: \"task.first-turn.confirmed\"; id: string }\n // #139: a claude crew's SessionEnd hook fired — the session is GONE. Unlike\n // the other turn-boundary hooks (PostToolUse/SubagentStop = liveness), a dead\n // session must NOT resume 'working' (nothing heartbeats → false CREW STALLED\n // ~budget later). Terminalizes the record to the absorbing 'cancelled' state.\n // Silent (not in ATTENTION_STATES), like task.cancelled.\n | { type: \"task.session.ended\"; id: string };\n\n// 'stalled' is intentionally excluded — recoverable by the watchdog.\n// 'cancelled' is terminal and silent (captain-initiated close).\nexport const TERMINAL_STATES: ReadonlySet<TaskState> = new Set([\n \"done\",\n \"failed\",\n \"cancelled\",\n]);\n","// src/lib/cmux-autoconfig.ts\n//\n// #348 (part of #332): orchestrator for cmux socket auto-config. Ties together\n// the comment-preserving config write, the non-cmux probe, and a SEMI-AUTOMATIC,\n// one-time restart prompt.\n//\n// See docs/specs/2026-06-16-cmux-socket-auth-daemon-direct-design.md §4.3–§4.4.\n//\n// This module decides WHAT to surface (configChanged / verdict / one-time\n// prompt); it does not print or log. The caller — the `squadrant cmux autoconfig`\n// CLI or the daemon-start re-check — renders the result. squadrant NEVER restarts\n// cmux for the user (that disrupts live sessions); we write config and prompt.\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { ensureSocketAutomation } from \"./cmux-config.js\";\nimport { probeCmuxDaemonDirect, type ProbeVerdict } from \"./cmux-probe.js\";\n\n/** One-time prompt marker, alongside the daemon state. */\nexport function defaultStatePath(): string {\n return join(homedir(), \".config\", \"squadrant\", \"state\", \"cmux-autoconfig.json\");\n}\n\nexport interface AutoConfigResult {\n /** Path of the cmux config inspected/written. */\n configPath: string;\n /** True when the cmux config was written this run. */\n configChanged: boolean;\n /** True when socketControlMode was already \"automation\". */\n configAlreadySet: boolean;\n /** Live socket reachability from a non-cmux process. */\n verdict: ProbeVerdict;\n /** Config is in place but the live socket still rejects (cmux restart needed). */\n needsRestart: boolean;\n /** The one-time restart prompt fired this run (false on repeats — no nag). */\n promptedThisRun: boolean;\n}\n\nexport interface AutoConfigOpts {\n configPath?: string;\n statePath?: string;\n /** Injectable for tests. Default = ensureSocketAutomation. */\n ensureConfig?: typeof ensureSocketAutomation;\n /** Injectable for tests. Default = the real orphan probe. */\n probe?: () => Promise<ProbeVerdict>;\n}\n\ninterface PromptState {\n promptedRestart?: boolean;\n}\n\nfunction readState(path: string): PromptState {\n try {\n return JSON.parse(readFileSync(path, \"utf-8\")) as PromptState;\n } catch {\n return {};\n }\n}\n\n/**\n * Idempotent: write the cmux automation config (if needed), probe the live\n * socket, and fire a one-time restart prompt when the socket still rejects.\n *\n * Safe to call on every daemon start — it recovers the \"cmux not running at\n * first write\" edge case (§3.4): the value is already file-managed, so the next\n * start re-probes and daemon-direct activates once cmux is (re)launched.\n */\nexport async function ensureCmuxAutoConfig(opts: AutoConfigOpts = {}): Promise<AutoConfigResult> {\n const statePath = opts.statePath ?? defaultStatePath();\n const ensureConfig = opts.ensureConfig ?? ensureSocketAutomation;\n const probe = opts.probe ?? probeCmuxDaemonDirect;\n\n const cfg = ensureConfig({ path: opts.configPath });\n const verdict = await probe();\n const needsRestart = verdict === \"denied\";\n\n let promptedThisRun = false;\n if (needsRestart) {\n const already = readState(statePath).promptedRestart === true;\n if (!already) {\n mkdirSync(dirname(statePath), { recursive: true });\n writeFileSync(statePath, JSON.stringify({ promptedRestart: true }));\n promptedThisRun = true;\n }\n } else if (verdict === \"reachable\") {\n // Reset the marker so a future regression (e.g. cmux config wiped) re-prompts.\n if (existsSync(statePath)) rmSync(statePath, { force: true });\n }\n\n return {\n configPath: cfg.path,\n configChanged: cfg.changed,\n configAlreadySet: cfg.alreadySet,\n verdict,\n needsRestart,\n promptedThisRun,\n };\n}\n","// src/lib/cmux-config.ts\n//\n// #348 (part of #332): comment-preserving JSONC merge for the cmux control\n// socket auth mode. Writes ONLY `automation.socketControlMode = \"automation\"`\n// into ~/.config/cmux/cmux.json so the launchd squadrant daemon (NOT a cmux\n// descendant) may connect to the cmux control socket for daemon-direct delivery.\n//\n// See docs/specs/2026-06-16-cmux-socket-auth-daemon-direct-design.md §2–§4.1.\n//\n// We use jsonc-parser (modify + applyEdits) rather than JSON.parse/stringify so\n// every existing comment and key in the user's cmux.json survives — cmux itself\n// preserves comments and we must not clobber them.\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { parse, modify, applyEdits } from \"jsonc-parser\";\n\n/** Canonical cmux config path. cmux watches this file live (§3.2). */\nexport function defaultCmuxConfigPath(): string {\n return join(homedir(), \".config\", \"cmux\", \"cmux.json\");\n}\n\nexport const SOCKET_CONTROL_MODE_PATH = [\"automation\", \"socketControlMode\"] as const;\nexport const AUTOMATION_MODE = \"automation\";\n\nexport interface EnsureSocketAutomationResult {\n /** Absolute path written/inspected. */\n path: string;\n /** True when the file was written this call. */\n changed: boolean;\n /** True when socketControlMode was ALREADY \"automation\" (no write needed). */\n alreadySet: boolean;\n}\n\n// Minimal squadrant-managed template used only when cmux.json does not yet exist\n// (clean install before cmux has created its own template). It is a strict\n// subset of cmux's schema, so cmux merges its full template keys on next launch\n// without conflict.\nconst MINIMAL_TEMPLATE = [\n `{`,\n ` // [squadrant] file-managed: allow the launchd squadrant daemon to reach the cmux`,\n ` // control socket for daemon-direct notification delivery (#348/#332).`,\n ` \"automation\": {`,\n ` \"socketControlMode\": \"${AUTOMATION_MODE}\"`,\n ` }`,\n `}`,\n ``,\n].join(\"\\n\");\n\n/**\n * Ensure `automation.socketControlMode = \"automation\"` in the cmux config.\n *\n * Idempotent: a no-op (changed=false) when already set. Comment- and\n * formatting-preserving when adding/overwriting an existing file. Creates a\n * minimal squadrant-managed file when none exists.\n */\nexport function ensureSocketAutomation(\n opts: { path?: string } = {},\n): EnsureSocketAutomationResult {\n const path = opts.path ?? defaultCmuxConfigPath();\n\n if (!existsSync(path)) {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, MINIMAL_TEMPLATE);\n return { path, changed: true, alreadySet: false };\n }\n\n const text = readFileSync(path, \"utf-8\");\n const current = parse(text)?.automation?.socketControlMode;\n if (current === AUTOMATION_MODE) {\n return { path, changed: false, alreadySet: true };\n }\n\n const edits = modify(text, [...SOCKET_CONTROL_MODE_PATH], AUTOMATION_MODE, {\n formattingOptions: { insertSpaces: true, tabSize: 2 },\n });\n writeFileSync(path, applyEdits(text, edits));\n return { path, changed: true, alreadySet: false };\n}\n","// src/lib/cmux-probe.ts\n//\n// #348 (part of #332): the hybrid gate. Answer \"can a NON-cmux process reach the\n// cmux control socket right now?\" — i.e. is daemon-direct delivery viable?\n//\n// See docs/specs/2026-06-16-cmux-socket-auth-daemon-direct-design.md §4.2.\n//\n// FAITHFULNESS: cmuxOnly mode checks the connecting process's parent chain and\n// rejects anything not descended from the cmux app. Prior research was\n// CONTAMINATED because it ran inside a cmux pane and kept cmux ancestry even\n// under `env -i`. A faithful probe MUST run from a process reparented to launchd\n// (PPID ⇒ 1). We achieve that with a launcher→worker double-fork: the launcher\n// exits immediately, orphaning the worker, which waits until process.ppid === 1\n// before touching the socket.\nimport { spawn } from \"node:child_process\";\nimport { mkdtempSync, rmSync, existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { resolveCmuxBin } from \"./cmux-bin.js\";\n\nexport type ProbeVerdict = \"reachable\" | \"denied\" | \"unknown\";\n\nexport interface ProbeRawResult {\n ok: boolean;\n stderr?: string;\n}\n\n// cmux's parentage rejection message (and generic socket permission errors).\nconst DENIED_RE = /access denied|only processes started inside cmux|permission denied/i;\n\n/**\n * Pure. Map a raw probe result to a verdict.\n * - ok ⇒ reachable (daemon-direct viable)\n * - access-denied stderr ⇒ denied (socket still cmuxOnly — restart needed)\n * - anything else ⇒ unknown (fail soft → stay on relay)\n */\nexport function classifyProbe(r: ProbeRawResult): ProbeVerdict {\n if (r.ok) return \"reachable\";\n if (r.stderr && DENIED_RE.test(r.stderr)) return \"denied\";\n return \"unknown\";\n}\n\nexport interface ProbeOpts {\n /** Injectable runner (tests). Default = orphan-escape spawn of a cmux read. */\n run?: () => Promise<ProbeRawResult>;\n /** Overall budget for the orphan probe (default 8s). */\n timeoutMs?: number;\n}\n\n/**\n * Probe whether a non-cmux process can reach the cmux control socket. Never\n * throws — a failed/timed-out probe degrades to \"unknown\" so the caller stays on\n * the zero-setup relay.\n */\nexport async function probeCmuxDaemonDirect(opts: ProbeOpts = {}): Promise<ProbeVerdict> {\n const run = opts.run ?? (() => orphanProbe(opts.timeoutMs ?? 8000));\n try {\n return classifyProbe(await run());\n } catch {\n return \"unknown\";\n }\n}\n\n// The worker script, run via `node`. Two modes in one file:\n// launch: spawn the worker detached, then exit → worker is orphaned to launchd\n// work: wait until PPID===1 (no cmux ancestor), run the cmux read, write JSON\n// argv: [node, script, mode, resultFile, cmuxBin]\nconst WORKER_SRC = `\nimport { spawn, execFileSync } from \"node:child_process\";\nimport { writeFileSync } from \"node:fs\";\nconst [mode, resultFile, cmuxBin] = process.argv.slice(2);\nif (mode === \"launch\") {\n const child = spawn(process.execPath, [process.argv[1], \"work\", resultFile, cmuxBin], {\n detached: true, stdio: \"ignore\",\n });\n child.unref();\n process.exit(0);\n}\n// work mode: wait to be reparented to launchd (PPID 1), then probe.\nconst deadline = Date.now() + 3000;\nwhile (process.ppid !== 1 && Date.now() < deadline) {\n const until = Date.now() + 25;\n while (Date.now() < until) { /* tiny busy wait — no timers in a dying orphan */ }\n}\nlet result;\nif (process.ppid !== 1) {\n result = { ok: false, stderr: \"orphan-timeout\" };\n} else {\n try {\n execFileSync(cmuxBin, [\"workspace\", \"list\", \"--json\"], {\n encoding: \"utf-8\", timeout: 10000, env: { ...process.env, CMUX_QUIET: \"1\" },\n });\n result = { ok: true };\n } catch (e) {\n const stderr = (e && (e.stderr?.toString?.() || e.message)) || \"probe failed\";\n result = { ok: false, stderr };\n }\n}\nwriteFileSync(resultFile, JSON.stringify(result));\n`;\n\n// Default runner: double-fork to a launchd-reparented worker, poll its result.\nasync function orphanProbe(timeoutMs: number): Promise<ProbeRawResult> {\n const dir = mkdtempSync(join(tmpdir(), \"cmux-probe-\"));\n const scriptFile = join(dir, \"probe-worker.mjs\");\n const resultFile = join(dir, \"result.json\");\n writeFileSync(scriptFile, WORKER_SRC);\n\n try {\n const launcher = spawn(\n process.execPath,\n [scriptFile, \"launch\", resultFile, resolveCmuxBin()],\n { detached: true, stdio: \"ignore\" },\n );\n launcher.unref();\n\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (existsSync(resultFile)) {\n try {\n return JSON.parse(readFileSync(resultFile, \"utf-8\")) as ProbeRawResult;\n } catch {\n // partial write — fall through and retry\n }\n }\n await sleep(100);\n }\n return { ok: false, stderr: \"probe-timeout\" };\n } finally {\n rmSync(dir, { recursive: true, force: true });\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n","import { execFileSync } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nlet _cached: string | undefined;\n\nfunction resolveBin(): string {\n // 1. Env var override\n const envBin = process.env.SQUADRANT_CMUX_BIN;\n if (envBin && existsSync(envBin)) return envBin;\n\n // 2. Optional cmuxBin field in config.json\n try {\n const configPath = join(homedir(), \".config\", \"squadrant\", \"config.json\");\n if (existsSync(configPath)) {\n const cfg = JSON.parse(readFileSync(configPath, \"utf-8\"));\n const cfgBin: unknown = cfg.cmuxBin;\n if (typeof cfgBin === \"string\" && existsSync(cfgBin)) return cfgBin;\n }\n } catch { /* config read is best-effort */ }\n\n // 3. PATH lookup\n try {\n const which = execFileSync(\"which\", [\"cmux\"], { encoding: \"utf-8\" }).trim();\n if (which && existsSync(which)) return which;\n } catch { /* not on PATH */ }\n\n // 4. Fallback (backward compat for macOS .app install)\n return \"/Applications/cmux.app/Contents/Resources/bin/cmux\";\n}\n\nexport function resolveCmuxBin(): string {\n return _cached ??= resolveBin();\n}\n\nexport function resetCmuxBinCache(): void {\n _cached = undefined;\n}\n","export type ToolEntry = { min?: string; lastVerified?: string };\n\nexport const compatManifest = {\n tools: {\n cmux: { min: \"0.64.0\", lastVerified: \"0.64.17\" } satisfies ToolEntry,\n claude: { min: \"2.1.32\" } satisfies ToolEntry,\n node: { min: \"18.0.0\", lastVerified: \"24.6.0\" } satisfies ToolEntry,\n // presence-checked; no floor enforced yet\n codex: { lastVerified: \"0.139.0\" } satisfies ToolEntry,\n gemini: { lastVerified: \"0.38.2\" } satisfies ToolEntry,\n opencode: { lastVerified: \"1.17.9\" } satisfies ToolEntry,\n },\n} as const;\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport https from \"node:https\";\nimport type { SquadrantConfig } from \"../config.js\";\n\nexport interface UpdateCheckState {\n lastChecked?: number;\n latestKnown?: string;\n /** Set when the most recent check attempt failed (offline/timeout). Drives a shorter\n * FAILURE_RETRY_MS backoff instead of the full 24h success interval, so an offline\n * machine retries roughly hourly instead of hitting the registry on every invocation. */\n lastCheckFailed?: boolean;\n}\n\nexport interface CheckForUpdateOutcome {\n notice: string | null;\n /** New state to persist, or null when nothing changed (opt-out / cache hit). */\n newState: UpdateCheckState | null;\n}\n\nexport const UPDATE_CHECK_STATE_PATH = path.join(os.homedir(), \".config\", \"squadrant\", \"update-check.json\");\n\nconst REGISTRY_URL = \"https://registry.npmjs.org/squadrant/latest\";\nconst CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;\nconst FAILURE_RETRY_MS = 60 * 60 * 1000;\nconst FETCH_TIMEOUT_MS = 1500;\n\nexport type RegistryRequest = (url: string, timeoutMs: number) => Promise<unknown>;\n\n/**\n * Fetches over node:https rather than global fetch(): a fetch() Promise exposes no\n * handle to detach from the event loop, so a pending request left ref'd would delay\n * process exit by up to timeoutMs on every single invocation of an offline machine.\n * http.ClientRequest itself has no unref() — the socket does, assigned asynchronously\n * via the 'socket' event — so we unref that once it's available. This is Node's\n * documented mechanism for exactly this: it lets the process exit immediately once\n * the CLI's own work is done, dropping the response if it arrives after. That's fine:\n * this check is a best-effort background notice, never something exit should wait on.\n */\nconst requestJson: RegistryRequest = (url, timeoutMs) =>\n new Promise((resolve) => {\n const req = https.get(url, { headers: { \"user-agent\": \"squadrant-update-check\" } }, (res) => {\n if (res.statusCode !== 200) {\n res.resume();\n resolve(null);\n return;\n }\n let body = \"\";\n res.setEncoding(\"utf-8\");\n res.on(\"data\", (chunk) => (body += chunk));\n res.on(\"end\", () => {\n try {\n resolve(JSON.parse(body));\n } catch {\n resolve(null);\n }\n });\n });\n req.on(\"socket\", (socket) => socket.unref());\n req.setTimeout(timeoutMs, () => req.destroy());\n req.on(\"error\", () => resolve(null));\n });\n\nexport function isUpdateCheckDisabled(\n config: Pick<SquadrantConfig, \"defaults\"> | undefined,\n env: NodeJS.ProcessEnv = process.env,\n): boolean {\n if (env.NO_UPDATE_NOTIFIER) return true;\n return config?.defaults?.updateCheck === false;\n}\n\nexport function isCacheStale(\n state: UpdateCheckState | undefined,\n now: number,\n intervalMs = CHECK_INTERVAL_MS,\n failureIntervalMs = FAILURE_RETRY_MS,\n): boolean {\n if (!state?.lastChecked) return true;\n return now - state.lastChecked >= (state.lastCheckFailed ? failureIntervalMs : intervalMs);\n}\n\nexport function isNewerVersion(latest: string, current: string): boolean {\n const parse = (v: string) => v.trim().replace(/^v/, \"\").split(\"-\")[0].split(\".\").map((n) => Number(n) || 0);\n const [la = 0, lb = 0, lc = 0] = parse(latest);\n const [ca = 0, cb = 0, cc = 0] = parse(current);\n if (la !== ca) return la > ca;\n if (lb !== cb) return lb > cb;\n return lc > cc;\n}\n\nexport function formatUpdateNotice(latest: string, current: string): string {\n return `⬆ squadrant ${latest} available (you have ${current}) — npm i -g squadrant@latest`;\n}\n\n/**\n * Queries the npm registry directly (never the `npm view` CDN — see the v0.13.1 incident).\n * Never throws. Races the request against its own unref'd timer, so the *logical* result\n * is always bounded by timeoutMs regardless of how requestFn behaves — real network\n * failures are additionally handled by requestJson's own req.unref()/setTimeout, which\n * guarantees the underlying resource can never hold the process open either.\n */\nexport async function fetchLatestVersion(\n requestFn: RegistryRequest = requestJson,\n timeoutMs: number = FETCH_TIMEOUT_MS,\n): Promise<string | null> {\n const timeout = new Promise<null>((resolve) => {\n const timer = setTimeout(() => resolve(null), timeoutMs);\n timer.unref?.();\n });\n\n const request = (async (): Promise<string | null> => {\n try {\n const data = (await requestFn(REGISTRY_URL, timeoutMs)) as { version?: unknown } | null;\n return typeof data?.version === \"string\" ? data.version : null;\n } catch {\n return null;\n }\n })();\n\n return Promise.race([request, timeout]);\n}\n\n/** Pure decision core: given cache state and an injected request function, decides whether\n * to print a notice and what state to persist. No filesystem access. */\nexport async function checkForUpdate(opts: {\n currentVersion: string;\n state: UpdateCheckState | undefined;\n now: number;\n fetchImpl?: RegistryRequest;\n intervalMs?: number;\n failureIntervalMs?: number;\n timeoutMs?: number;\n}): Promise<CheckForUpdateOutcome> {\n if (!isCacheStale(opts.state, opts.now, opts.intervalMs, opts.failureIntervalMs)) {\n const latest = opts.state?.latestKnown;\n const notice = latest && isNewerVersion(latest, opts.currentVersion) ? formatUpdateNotice(latest, opts.currentVersion) : null;\n return { notice, newState: null };\n }\n\n const latest = await fetchLatestVersion(opts.fetchImpl, opts.timeoutMs);\n if (!latest) return { notice: null, newState: { lastChecked: opts.now, lastCheckFailed: true } };\n\n const newState: UpdateCheckState = { lastChecked: opts.now, latestKnown: latest, lastCheckFailed: false };\n const notice = isNewerVersion(latest, opts.currentVersion) ? formatUpdateNotice(latest, opts.currentVersion) : null;\n return { notice, newState };\n}\n\nexport function readUpdateCheckState(statePath: string = UPDATE_CHECK_STATE_PATH): UpdateCheckState | undefined {\n try {\n return JSON.parse(fs.readFileSync(statePath, \"utf-8\"));\n } catch {\n return undefined;\n }\n}\n\nexport function writeUpdateCheckState(state: UpdateCheckState, statePath: string = UPDATE_CHECK_STATE_PATH): void {\n try {\n fs.mkdirSync(path.dirname(statePath), { recursive: true });\n fs.writeFileSync(statePath, JSON.stringify(state, null, 2) + \"\\n\");\n } catch {\n // best-effort cache; a failed write just means we check again next run\n }\n}\n\n/**\n * CLI entrypoint wiring: opt-out check, cache read, decision, cache write, notice print —\n * all in one best-effort call that never throws. Not awaiting this at the call site keeps\n * it off the command's own logic; the unref'd transport (see requestJson) and the bounded\n * race in fetchLatestVersion mean a pending check can't delay process exit either, and a\n * failed attempt is cached (see isCacheStale's failureIntervalMs) so an offline machine\n * doesn't retry the registry on every single invocation.\n */\nexport async function notifyIfUpdateAvailable(opts: {\n config: Pick<SquadrantConfig, \"defaults\"> | undefined;\n currentVersion: string;\n env?: NodeJS.ProcessEnv;\n fetchImpl?: RegistryRequest;\n statePath?: string;\n readState?: (statePath: string) => UpdateCheckState | undefined;\n writeState?: (state: UpdateCheckState, statePath: string) => void;\n write?: (line: string) => void;\n now?: number;\n}): Promise<void> {\n try {\n const env = opts.env ?? process.env;\n if (isUpdateCheckDisabled(opts.config, env)) return;\n\n const statePath = opts.statePath ?? UPDATE_CHECK_STATE_PATH;\n const readState = opts.readState ?? readUpdateCheckState;\n const writeState = opts.writeState ?? writeUpdateCheckState;\n const write = opts.write ?? ((line: string) => process.stderr.write(`\\n${line}\\n`));\n\n const outcome = await checkForUpdate({\n currentVersion: opts.currentVersion,\n state: readState(statePath),\n now: opts.now ?? Date.now(),\n fetchImpl: opts.fetchImpl,\n });\n\n if (outcome.newState) writeState(outcome.newState, statePath);\n if (outcome.notice) write(outcome.notice);\n } catch {\n // update notifications are best-effort and must never affect the CLI\n }\n}\n","// src/lib/git-worktree.ts\n//\n// Per-crew git worktree isolation (#216). A FEATURE crew (spawned with\n// `--worktree`) runs in its own worktree + branch so it can switch HEAD without\n// dragging the captain's checkout. Small/one-off crews keep running on the\n// shared root checkout (unchanged default). The side-effecting `git worktree`\n// calls live here so crew.ts stays mockable and surgical — same seam pattern as\n// per-crew-settings.ts.\n//\n// Builds and the daemon still run from the MAIN checkout's `dist`; worktrees\n// edit source only (issue #216 caveat).\nimport { execFileSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport interface WorktreeSpec {\n /** The project's root checkout (also the shared `.git` owner). */\n repoRoot: string;\n /** Config.defaults.worktreeDir, resolved relative to repoRoot (e.g. \".worktrees\"). */\n worktreeDir: string;\n project: string;\n /** Crew name (e.g. \"crew-1\" or a --name value). */\n name: string;\n /** Branch to base the new crew branch on (GitFlow: \"develop\"). */\n base: string;\n}\n\n/** Deterministic worktree path: <repoRoot>/<worktreeDir>/<project>-<name>. */\nexport function worktreePath(repoRoot: string, worktreeDir: string, project: string, name: string): string {\n return path.resolve(repoRoot, worktreeDir, `${project}-${name}`);\n}\n\n/** Crew branch name for a worktree crew. */\nexport function crewBranch(name: string): string {\n return `crew/${name}`;\n}\n\n/**\n * #387: macOS Spotlight (mds/mdworker) indexing every crew worktree's\n * node_modules can itself starve CPU. A `.metadata_never_index` marker file\n * excludes its directory (recursively, including subdirectories created\n * later) from indexing. Dropping ONE marker in the worktree ROOT (once, the\n * first time a project spawns a worktree crew) covers every crew worktree\n * ever created under it after — no per-worktree marker needed. Best-effort\n * and macOS-only: never blocks worktree creation over an indexing nicety.\n */\nfunction ensureSpotlightExcluded(repoRoot: string, worktreeDir: string): void {\n if (process.platform !== \"darwin\") return;\n try {\n const dir = path.resolve(repoRoot, worktreeDir);\n fs.mkdirSync(dir, { recursive: true });\n const marker = path.join(dir, \".metadata_never_index\");\n if (!fs.existsSync(marker)) fs.writeFileSync(marker, \"\");\n } catch {\n // Best-effort — Spotlight exclusion is a nicety, not a correctness requirement.\n }\n}\n\n// #359: derive the branch a new worktree should be based on. Reads origin/HEAD\n// so main-based repos work without a hand-created `develop`. Falls back to\n// `fallback` (default \"develop\") when origin/HEAD is unset.\nexport function resolveWorktreeBase(repoRoot: string, fallback = \"develop\"): string {\n try {\n const ref = execFileSync(\n \"git\",\n [\"-C\", repoRoot, \"symbolic-ref\", \"refs/remotes/origin/HEAD\"],\n { stdio: [\"ignore\", \"pipe\", \"ignore\"] },\n ).toString().trim();\n const m = ref.match(/^refs\\/remotes\\/origin\\/(.+)$/);\n if (m) return m[1];\n } catch {\n return fallback;\n }\n return fallback;\n}\n\n/**\n * Create the crew's worktree + branch and return its absolute path.\n * Handles a stale crew/<name> branch left by a previously-closed crew (#460):\n * - No existing branch → unchanged behavior.\n * - Existing branch with no unique commits (merged/empty) → delete and recreate fresh.\n * - Existing branch with unique commits → uniquify to crew/<name>-2, -3, … so no\n * commits are lost and there is no collision. The returned path reflects the\n * uniquified name.\n */\nexport function addWorktree(spec: WorktreeSpec): string {\n ensureSpotlightExcluded(spec.repoRoot, spec.worktreeDir);\n\n const originalBranch = crewBranch(spec.name);\n\n let targetName = spec.name;\n let targetBranch = originalBranch;\n\n // Check whether crew/<name> already exists from a prior closed crew.\n let branchExists = false;\n try {\n execFileSync(\n \"git\",\n [\"-C\", spec.repoRoot, \"show-ref\", \"--verify\", \"--quiet\", `refs/heads/${originalBranch}`],\n { stdio: \"pipe\" },\n );\n branchExists = true;\n } catch {\n // Branch does not exist — normal path, nothing to resolve.\n }\n\n if (branchExists) {\n const log = execFileSync(\n \"git\",\n [\"-C\", spec.repoRoot, \"log\", \"--oneline\", `${spec.base}..${originalBranch}`],\n { stdio: [\"ignore\", \"pipe\", \"ignore\"] },\n ).toString().trim();\n\n if (!log) {\n // No unique commits: safe to delete and let the worktree add recreate it.\n execFileSync(\n \"git\",\n [\"-C\", spec.repoRoot, \"branch\", \"-D\", originalBranch],\n { stdio: \"pipe\" },\n );\n } else {\n // Has unique commits: uniquify to crew/<name>-N so history is preserved.\n let suffix = 2;\n while (true) {\n const candidate = `${spec.name}-${suffix}`;\n const candidateBranch = crewBranch(candidate);\n let candidateExists = false;\n try {\n execFileSync(\n \"git\",\n [\"-C\", spec.repoRoot, \"show-ref\", \"--verify\", \"--quiet\", `refs/heads/${candidateBranch}`],\n { stdio: \"pipe\" },\n );\n candidateExists = true;\n } catch {\n // Candidate branch is free.\n }\n if (!candidateExists) {\n targetName = candidate;\n targetBranch = candidateBranch;\n break;\n }\n suffix++;\n }\n }\n }\n\n const wt = worktreePath(spec.repoRoot, spec.worktreeDir, spec.project, targetName);\n execFileSync(\n \"git\",\n [\"-C\", spec.repoRoot, \"worktree\", \"add\", wt, \"-b\", targetBranch, spec.base],\n { stdio: \"pipe\" },\n );\n installWorktreeDependencies(wt);\n return wt;\n}\n\n/**\n * #387: `git worktree add` never populates node_modules — a fresh worktree\n * has none. Node's module resolution walks up parent directories looking for\n * node_modules, and since worktrees live nested under <repoRoot>/<worktreeDir>/,\n * a worktree with no local node_modules silently falls through to the main\n * checkout's node_modules instead of failing — so a crew's tsc/vitest run can\n * type-check or test against the main repo's stale code without any error.\n * Installing here, synchronously, before the worktree is handed to a crew\n * closes that gap: the worktree always has its own complete dependency tree,\n * or worktree creation fails loudly instead of leaving a crew to discover the\n * gap mid-task.\n *\n * addWorktree() is called for every registered project, not just squadrant's\n * own repo — projects use pnpm, yarn, or npm, and some aren't JS projects at\n * all. Detect the package manager from its lockfile rather than assuming\n * pnpm; each is invoked with its own frozen/reproducible-install flag so this\n * never silently drifts the project's lockfile. No package.json → nothing to\n * install, not an error. package.json with no recognized lockfile → skip\n * rather than guess: without a lockfile there's no deterministic manifest to\n * freeze against, and guessing a package manager risks generating a stray\n * lockfile the crew never asked for — but that skip still leaves the worktree\n * without its own node_modules, i.e. still exposed to the exact silent\n * cross-checkout resolution this function exists to close. Warn on stderr so\n * that exposure is visible instead of silent.\n */\nfunction installWorktreeDependencies(wt: string): void {\n if (!fs.existsSync(path.join(wt, \"package.json\"))) return;\n\n if (fs.existsSync(path.join(wt, \"pnpm-lock.yaml\"))) {\n execFileSync(\"pnpm\", [\"-C\", wt, \"install\", \"--frozen-lockfile\"], { stdio: \"pipe\" });\n } else if (fs.existsSync(path.join(wt, \"yarn.lock\"))) {\n execFileSync(\"yarn\", [\"install\", \"--frozen-lockfile\"], { cwd: wt, stdio: \"pipe\" });\n } else if (fs.existsSync(path.join(wt, \"package-lock.json\"))) {\n execFileSync(\"npm\", [\"ci\"], { cwd: wt, stdio: \"pipe\" });\n } else if (fs.existsSync(path.join(wt, \"bun.lockb\"))) {\n execFileSync(\"bun\", [\"install\", \"--frozen-lockfile\"], { cwd: wt, stdio: \"pipe\" });\n } else {\n process.stderr.write(\n `worktree ${wt}: package.json present but no lockfile — dependencies not installed; local typechecks/tests may resolve against the main checkout instead of this worktree.\\n`,\n );\n }\n}\n\n/**\n * Remove a crew's worktree (auto-clean on close). Tries a plain remove first;\n * a dirty/locked worktree makes git refuse, so we retry with --force. The\n * branch is left intact so the crew's commits survive the close.\n */\nexport function removeWorktree(repoRoot: string, wtPath: string): void {\n try {\n execFileSync(\"git\", [\"-C\", repoRoot, \"worktree\", \"remove\", wtPath], { stdio: \"pipe\" });\n } catch {\n execFileSync(\"git\", [\"-C\", repoRoot, \"worktree\", \"remove\", \"--force\", wtPath], { stdio: \"pipe\" });\n }\n}\n","import fs from \"node:fs\";\n\nasync function readAllStdin(): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(Buffer.from(chunk));\n }\n return Buffer.concat(chunks).toString(\"utf-8\");\n}\n\nfunction flagName(label: string): string {\n return label === \"task\" ? \"--task-file\" : \"--message-file\";\n}\n\nexport interface ResolveTextInputOpts {\n positional?: string;\n filePath?: string;\n label: string;\n}\n\nexport interface ResolveTextInputDeps {\n readFile?: (path: string) => string;\n readStdin?: () => Promise<string>;\n}\n\nexport async function resolveTextInput(\n opts: ResolveTextInputOpts,\n deps?: ResolveTextInputDeps,\n): Promise<string> {\n const readFile = deps?.readFile ?? ((p: string) => fs.readFileSync(p, \"utf8\"));\n const readStdin = deps?.readStdin ?? readAllStdin;\n\n if (opts.filePath) {\n if (opts.filePath === \"-\") {\n return readStdin();\n }\n try {\n return readFile(opts.filePath);\n } catch (e) {\n const err = e as NodeJS.ErrnoException;\n const flag = flagName(opts.label);\n if (err.code === \"ENOENT\") {\n throw new Error(`${flag} '${opts.filePath}': file not found`);\n }\n throw new Error(`${flag} '${opts.filePath}': ${err.message}`);\n }\n }\n\n if (opts.positional === undefined) {\n throw new Error(\n `No ${opts.label} provided. Provide a positional argument or use ${flagName(opts.label)}.`,\n );\n }\n\n return opts.positional;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Copy `src` → `dest` only if `dest` is missing or its bytes differ. Content\n * comparison (not size+mtime) makes this both correct — a same-size edit is\n * always detected — and idempotent: an unchanged file is never rewritten, so\n * there is no mtime churn across runs. Managed files are small; reading them\n * per invocation is sub-millisecond. Returns true if a copy happened.\n */\nfunction copyIfDifferent(src: string, dest: string): boolean {\n if (fs.existsSync(dest)) {\n if (fs.readFileSync(src).equals(fs.readFileSync(dest))) return false;\n }\n fs.copyFileSync(src, dest);\n return true;\n}\n\n/**\n * Mirror `src` into `dest`: recursively copy new/changed files AND prune any\n * dest entry that no longer exists in src. Idempotent — unchanged files are\n * left untouched. After this returns, `dest` is a structural copy of `src`.\n * Caller is responsible for only pointing this at source-managed trees — it\n * WILL delete dest entries absent from src.\n */\nexport function mirrorDir(src: string, dest: string): void {\n fs.mkdirSync(dest, { recursive: true });\n\n const srcEntries = fs.readdirSync(src, { withFileTypes: true });\n const srcNames = new Set(srcEntries.map((e) => e.name));\n\n for (const entry of srcEntries) {\n const srcPath = path.join(src, entry.name);\n const destPath = path.join(dest, entry.name);\n if (entry.isDirectory()) {\n mirrorDir(srcPath, destPath);\n } else {\n copyIfDifferent(srcPath, destPath);\n }\n }\n\n for (const entry of fs.readdirSync(dest, { withFileTypes: true })) {\n if (!srcNames.has(entry.name)) {\n fs.rmSync(path.join(dest, entry.name), { recursive: true, force: true });\n }\n }\n}\n\n/**\n * Copy the top-level (non-recursive) files of `src` matching `match` into a\n * flat `dest`, prune dest entries no longer in the matched set, and apply\n * `chmod` to freshly copied files when given. Idempotent — unchanged files\n * are left untouched. For runtime dirs whose source is a differently-named,\n * mixed directory (templates ← templates/, scripts).\n */\nexport function mirrorFlat(\n src: string,\n dest: string,\n match: RegExp,\n chmod?: number,\n): void {\n fs.mkdirSync(dest, { recursive: true });\n\n const matched = fs\n .readdirSync(src, { withFileTypes: true })\n .filter((e) => e.isFile() && match.test(e.name))\n .map((e) => e.name);\n const matchedSet = new Set(matched);\n\n for (const name of matched) {\n const destPath = path.join(dest, name);\n const copied = copyIfDifferent(path.join(src, name), destPath);\n if (copied && chmod !== undefined) fs.chmodSync(destPath, chmod);\n }\n\n for (const entry of fs.readdirSync(dest, { withFileTypes: true })) {\n if (!matchedSet.has(entry.name)) {\n fs.rmSync(path.join(dest, entry.name), { recursive: true, force: true });\n }\n }\n}\n\n/**\n * A source-managed runtime dir. `name` is the dir under the runtime root;\n * `srcRel` is its source dir relative to the package root (note: the\n * runtime `templates/` is sourced from `templates/`).\n */\nexport type ManagedTarget =\n | { name: string; srcRel: string; mode: \"tree\" }\n | {\n name: string;\n srcRel: string;\n mode: \"flat\";\n match: RegExp;\n chmod?: number;\n };\n\nexport const MANAGED_TARGETS: ManagedTarget[] = [\n { name: \"plugin\", srcRel: \"plugin\", mode: \"tree\" },\n { name: \"scripts\", srcRel: \"scripts\", mode: \"flat\", match: /\\.sh$/, chmod: 0o755 },\n {\n name: \"templates\",\n srcRel: \"templates\",\n mode: \"flat\",\n match: /\\.(claude\\.md|generic\\.md|opencode\\.md|CLAUDE\\.md)$/,\n },\n];\n\nexport interface EnsureRuntimeSyncedOptions {\n /** Package root containing the source dirs (`plugin/`, `templates/`, …). */\n sourceRoot: string;\n /** Runtime root, normally ~/.config/squadrant. */\n runtimeRoot: string;\n /** Override the managed-target list (defaults to MANAGED_TARGETS). */\n targets?: ManagedTarget[];\n}\n\n/**\n * Self-heal the runtime copy of source-managed dirs. Every invocation\n * mirrors each managed target (mirrorDir for tree, mirrorFlat for flat) —\n * idempotent copy-if-different + prune, so the runtime is always reconciled\n * to source. There is no cached state: nothing can claim \"synced\" while the\n * dest is actually wrong. Only ever touches the runtime dirs named in the\n * target list — never user/runtime state. Never throws — a sync failure\n * degrades to a stderr warning so the CLI stays usable.\n */\nexport function ensureRuntimeSynced(opts: EnsureRuntimeSyncedOptions): void {\n const targets = opts.targets ?? MANAGED_TARGETS;\n\n for (const t of targets) {\n const srcDir = path.join(opts.sourceRoot, t.srcRel);\n try {\n if (!fs.existsSync(srcDir)) continue;\n const destDir = path.join(opts.runtimeRoot, t.name);\n if (t.mode === \"tree\") {\n mirrorDir(srcDir, destDir);\n } else {\n mirrorFlat(srcDir, destDir, t.match, t.chmod);\n }\n } catch (err) {\n process.stderr.write(\n `squadrant: runtime sync skipped for ${t.name}: ${(err as Error).message}\\n`,\n );\n }\n }\n}\n","type SemVer = [number, number, number];\n\nfunction parseSemVer(v: string): SemVer | null {\n const m = v.match(/(\\d+)\\.(\\d+)\\.(\\d+)/);\n if (!m) return null;\n return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)];\n}\n\nfunction cmpSemVer(a: SemVer, b: SemVer): number {\n for (let i = 0; i < 3; i++) {\n if (a[i] !== b[i]) return a[i] - b[i];\n }\n return 0;\n}\n\n/**\n * Compare an installed tool version against the compat manifest entry.\n * Returns a warning string when the version is below min or above lastVerified,\n * or null when the version is in-range or unparseable (non-blocking).\n * `min` is optional — entries without a floor are only drift-checked against lastVerified.\n */\nexport function checkToolCompat(\n name: string,\n rawVersion: string,\n entry: { min?: string; lastVerified?: string },\n): string | null {\n const installed = parseSemVer(rawVersion);\n if (!installed) return null;\n\n const min = entry.min ? parseSemVer(entry.min) : null;\n if (min && cmpSemVer(installed, min) < 0) {\n return `${name} ${rawVersion} < min ${entry.min} — upgrade to ${entry.min}+`;\n }\n\n if (entry.lastVerified) {\n const lastVerified = parseSemVer(entry.lastVerified);\n if (lastVerified && cmpSemVer(installed, lastVerified) > 0) {\n return `${name} ${rawVersion} > last-verified ${entry.lastVerified} — re-run compat audit`;\n }\n }\n\n return null;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { WorkspaceDriver } from \"../types/workspaces.js\";\nimport type { ProjectionSource } from \"../types/projection.js\";\n\ninterface SkillFrontmatter {\n name: string;\n description: string;\n}\n\nfunction parseSkill(raw: string): { frontmatter: SkillFrontmatter; body: string } | null {\n const match = raw.match(/^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/);\n if (!match) return null;\n const [, fmBlock, body] = match;\n const fm: Partial<SkillFrontmatter> = {};\n for (const line of fmBlock.split(\"\\n\")) {\n const kv = line.match(/^(\\w+):\\s*(.+)$/);\n if (kv) (fm as Record<string, string>)[kv[1]] = kv[2].trim();\n }\n if (!fm.name || !fm.description) return null;\n return { frontmatter: fm as SkillFrontmatter, body: body.trim() };\n}\n\nasync function readSkills(\n driver: WorkspaceDriver,\n skillsDir: string,\n): Promise<ProjectionSource[\"skills\"]> {\n if (!(await driver.exists(skillsDir))) return [];\n const names = await driver.list(skillsDir);\n const skills: ProjectionSource[\"skills\"] = [];\n for (const name of names) {\n const skillPath = `${skillsDir}/${name}/SKILL.md`;\n if (!(await driver.exists(skillPath))) continue;\n const raw = await driver.read(skillPath);\n const parsed = parseSkill(raw);\n if (!parsed) continue;\n skills.push({\n name: parsed.frontmatter.name,\n description: parsed.frontmatter.description,\n content: parsed.body,\n });\n }\n skills.sort((a, b) => a.name.localeCompare(b.name));\n return skills;\n}\n\nexport interface UserSourceOptions {\n pkgRoot?: string;\n readFile?: (p: string) => string;\n}\n\nconst ROLE_TEMPLATES: ReadonlyArray<{ file: string; heading: string }> = [\n { file: \"captain.generic.md\", heading: \"## Captain Role\" },\n { file: \"crew.generic.md\", heading: \"## Crew Role\" },\n];\n\nfunction readRoleTemplates(opts: UserSourceOptions): string {\n if (!opts.pkgRoot) return \"\";\n const reader = opts.readFile ?? ((p: string) => fs.readFileSync(p, \"utf-8\"));\n const sections: string[] = [];\n for (const { file, heading } of ROLE_TEMPLATES) {\n const full = path.join(opts.pkgRoot, \"templates\", file);\n let body = \"\";\n try { body = reader(full); } catch { continue; }\n sections.push(`${heading}\\n\\n${body.trim()}`);\n }\n return sections.join(\"\\n\\n\");\n}\n\nexport async function readUserLevelSource(\n driver: WorkspaceDriver,\n opts: UserSourceOptions = {},\n): Promise<ProjectionSource> {\n const skills = await readSkills(driver, \"plugin/skills\");\n const instructions = readRoleTemplates(opts);\n return { instructions, skills };\n}\n\n// `driver` must be rooted at the project directory itself (createObsidianDriver\n// with root: proj.path). Reading via a driver rooted at process.cwd() — as the\n// projection command previously did — made the sandbox guard reject every\n// managed project living outside the squadrant repo, silently skipping them.\nexport async function readProjectLevelSource(\n driver: WorkspaceDriver,\n): Promise<ProjectionSource | null> {\n if (!(await driver.exists(\"AGENTS.md\"))) return null;\n const instructions = await driver.read(\"AGENTS.md\");\n const skills = await readSkills(driver, \"plugin/skills\");\n return { instructions, skills };\n}\n","import { execSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport matter from \"gray-matter\";\nimport { resolveHome } from \"../config.js\";\nimport type { WorkspaceDriver } from \"../types/workspaces.js\";\n\nexport function iso(d: Date): string {\n return d.toISOString().slice(0, 10);\n}\n\nexport function daysAgo(n: number): Date {\n const d = new Date();\n d.setDate(d.getDate() - n);\n return d;\n}\n\nexport function enumerateDays(from: Date, to: Date): string[] {\n const out: string[] = [];\n const cur = new Date(from);\n cur.setHours(0, 0, 0, 0);\n const end = new Date(to);\n end.setHours(0, 0, 0, 0);\n while (cur <= end) {\n out.push(iso(cur));\n cur.setDate(cur.getDate() + 1);\n }\n return out;\n}\n\nexport interface DailyLog {\n content: string;\n blockers: string[];\n}\n\nexport async function readDailyLog(\n workspace: WorkspaceDriver,\n dateStr: string,\n): Promise<DailyLog | null> {\n const relPath = `daily-logs/${dateStr}.md`;\n if (!(await workspace.exists(relPath))) return null;\n\n const raw = await workspace.read(relPath);\n const { content } = matter(raw);\n\n const blockers: string[] = [];\n const blockerMatch = content.match(/## Blocked\\n([\\s\\S]*?)(?=\\n##|$)/);\n if (blockerMatch) {\n const lines = blockerMatch[1].trim().split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.replace(/^[-*]\\s*/, \"\").trim();\n if (trimmed && trimmed !== \"(none)\" && trimmed !== \"None\") {\n blockers.push(trimmed);\n }\n }\n }\n return { content, blockers };\n}\n\nexport function parseSection(content: string, section: string): string[] {\n const match = content.match(new RegExp(`## ${section}\\\\n([\\\\s\\\\S]*?)(?=\\\\n##|$)`));\n if (!match) return [];\n return match[1]\n .trim()\n .split(\"\\n\")\n .map((l) => l.replace(/^[-*]\\s*/, \"\").trim())\n .filter((l) => l && l !== \"(none)\" && l !== \"None\");\n}\n\nexport function getGitCommits(projectPath: string, dateStr: string): string[] {\n return getGitCommitsInRange(projectPath, `${dateStr} 00:00:00`, `${dateStr} 23:59:59`);\n}\n\nexport function getGitCommitsInRange(projectPath: string, since: string, until?: string): string[] {\n const resolved = resolveHome(projectPath);\n if (!fs.existsSync(path.join(resolved, \".git\"))) return [];\n\n const untilArg = until ? ` --until=\"${until}\"` : \"\";\n try {\n const output = execSync(\n `git -C \"${resolved}\" log --since=\"${since}\"${untilArg} --oneline --no-merges 2>/dev/null`,\n { encoding: \"utf-8\", timeout: 5000 },\n ).trim();\n if (!output) return [];\n return output.split(\"\\n\").map((l) => l.trim()).filter(Boolean);\n } catch {\n return [];\n }\n}\n\nexport function getMergedPRsInRange(projectPath: string, since: string, until?: string): string[] {\n const resolved = resolveHome(projectPath);\n if (!fs.existsSync(path.join(resolved, \".git\"))) return [];\n\n const untilArg = until ? ` --until=\"${until}\"` : \"\";\n try {\n const output = execSync(\n `git -C \"${resolved}\" log --merges --since=\"${since}\"${untilArg} --pretty=format:%s 2>/dev/null`,\n { encoding: \"utf-8\", timeout: 5000 },\n ).trim();\n if (!output) return [];\n return output.split(\"\\n\").map((l) => l.trim()).filter(Boolean);\n } catch {\n return [];\n }\n}\n","// src/control/state-machine.ts\nimport type { ControlEvent, TaskRecord, DispatchAttempt } from \"@squadrant/shared\";\nimport { TERMINAL_STATES } from \"@squadrant/shared\";\n\n/**\n * Pure helper: merges `patch` into the last attempt and updates lastHeartbeatAt.\n * Returns a new TaskRecord; never mutates the input.\n */\nfunction stampAttempt(\n rec: TaskRecord,\n patch: Partial<DispatchAttempt>,\n now: number,\n): TaskRecord {\n const attempts = rec.attempts.slice();\n const last = attempts.at(-1) ?? { attemptId: \"a0\", startedAt: now, lastHeartbeatAt: now };\n attempts[attempts.length === 0 ? 0 : attempts.length - 1] = { ...last, ...patch, lastHeartbeatAt: now };\n if (attempts.length === 0) attempts.push(last);\n return { ...rec, attempts };\n}\n\n/**\n * #608: 'blocked' and 'review' are both attention states that pause a crew\n * pending a human decision — neither may be knocked out by a liveness or\n * turn-boundary event, only by their own explicit exits (reply/feedback or\n * approve). Every stickiness guard below must treat them identically, or a\n * future attention state repeats this bug a fourth time (#492 → #605 → #608).\n */\nfunction isStickyAttention(state: TaskRecord[\"state\"]): boolean {\n return state === \"blocked\" || state === \"review\";\n}\n\n/**\n * #354: compute the next pendingTool marker for a task.progress liveness signal.\n * A PreToolUse (carried from the cmux events-bridge, with the tool name) opens a\n * tool-in-flight window; a PostToolUse or a new UserPromptSubmit closes it. Other\n * liveness notes (subagentstop / notification) leave the marker untouched — they\n * do not bound a tool call. The note strings match the two feeds: the events-bridge\n * emits the raw cmux hook name (\"agent.hook.PreToolUse\"); the claude hook bridge\n * emits the lower-cased event (\"posttooluse\").\n */\nfunction nextPendingTool(\n current: TaskRecord[\"pendingTool\"],\n ev: Extract<ControlEvent, { type: \"task.progress\" }>,\n now: number,\n): TaskRecord[\"pendingTool\"] {\n if (ev.note === \"agent.hook.PreToolUse\") return { name: ev.tool ?? \"tool\", since: now };\n if (ev.note === \"posttooluse\" || ev.note === \"agent.hook.UserPromptSubmit\") return undefined;\n return current;\n}\n\n/**\n * Pure transition. `now` is injected (epoch ms) so callers control time.\n * Returns a new record; never mutates the input.\n */\nexport function reduce(rec: TaskRecord, ev: ControlEvent, now: number): TaskRecord {\n // task.reopened is the ONE event allowed to escape a terminal state.\n // From ANY state (done/failed/stalled/awaiting-input/working) → working.\n // Clears question and error so the revived task looks fresh.\n if (ev.type === \"task.reopened\") {\n return { ...rec, state: \"working\", question: undefined, error: undefined, lastHeartbeat: now, lastEvent: ev.type };\n }\n\n // Terminal states are absorbing: ignore any late/duplicate event idempotently.\n if (TERMINAL_STATES.has(rec.state)) return rec;\n\n const base = { ...rec, lastHeartbeat: now, lastEvent: ev.type };\n\n switch (ev.type) {\n case \"task.started\":\n return {\n ...stampAttempt(base, { pid: ev.pid }, now),\n state: \"working\",\n pid: ev.pid ?? rec.pid,\n sessionId: ev.sessionId ?? rec.sessionId,\n question: undefined, // resuming after a blocked→reply clears the question\n pendingTool: undefined, // #354: a new turn closes any prior tool window\n };\n case \"task.progress\": {\n // task.progress is a real-activity signal (stdout chunk for headless,\n // PreToolUse/PostToolUse/SubagentStop hook for interactive). Stamp the\n // attempt so lastHeartbeatAt stays current and the watchdog stall-check\n // (#89) can key off it without false-stalling long-running headless tasks.\n // #354: also track the in-flight tool (PreToolUse opens, PostToolUse closes)\n // so a hung tool call is distinguishable from a quiet thinking turn.\n // From blocked: liveness only — do not auto-unblock (explicit reply required).\n // From awaiting-input OR stalled: resume to working — the next real activity\n // (e.g. the matching PostToolUse) auto-clears a hung-tool warn instantly.\n const pendingTool = nextPendingTool(rec.pendingTool, ev, now);\n if (isStickyAttention(rec.state)) return { ...rec, lastHeartbeat: now, lastEvent: ev.type, pendingTool };\n const b = { ...base, pendingTool };\n if (rec.state === \"awaiting-input\" || rec.state === \"stalled\") return { ...stampAttempt(b, {}, now), state: \"working\" };\n return stampAttempt(b, {}, now);\n }\n case \"heartbeat\":\n // Raw liveness ping — intentionally does NOT stamp the attempt so a late\n // heartbeat from a dead dispatch cannot mask stalls on the new one (#89).\n // From awaiting-input: resume to working (mirrors task.progress).\n if (isStickyAttention(rec.state)) return { ...rec, lastHeartbeat: now, lastEvent: ev.type };\n if (rec.state === \"awaiting-input\") return { ...base, state: \"working\" };\n return base;\n case \"task.blocked\":\n // ev.reason is protocol/logging-only and intentionally not persisted;\n // only `question` is stored on the record.\n // Idempotency (#174): the explicit `squadrant crew signal blocked` fires\n // BEFORE the turn ends; the auto-detect Stop hook may then re-emit\n // task.blocked on an already-blocked task. Treat a repeat block as a\n // no-op so the FIRST (explicit) question wins and no duplicate CREW\n // BLOCKED fires. Terminal states are already absorbed above.\n if (rec.state === \"blocked\") return { ...rec, lastHeartbeat: now, lastEvent: ev.type };\n return { ...base, state: \"blocked\", question: ev.question, pendingTool: undefined };\n case \"task.review\":\n // #599: review-gate checkpoint. Not terminal — `crew send` (feedback)\n // or `crew approve` (task.done) are the only ways out.\n return { ...base, state: \"review\", reviewNote: ev.message, pendingTool: undefined };\n case \"task.done\":\n // #605: the review gate must be ENFORCING, not advisory. A crew's normal\n // completion protocol always signals done at turn end — if that alone\n // could terminalize a task sitting in 'review', the gate is bypassed by\n // crew habit and `crew approve` becomes unreachable (state is already\n // 'done'). Per #492: gate at the transition, not on crew discipline.\n // Only `squadrant crew approve`'s task.done (source: 'approve') is a\n // distinct terminal channel the veto does not block; any other task.done\n // while in review is liveness-only and the record stays in review.\n if (rec.state === \"review\" && ev.source !== \"approve\") {\n return { ...rec, lastHeartbeat: now, lastEvent: ev.type };\n }\n return { ...base, state: \"done\", resultRef: ev.resultRef, parseWarning: ev.parseWarning };\n case \"task.failed\":\n return { ...base, state: \"failed\", error: ev.error, exitCode: ev.exitCode };\n case \"task.cancelled\":\n return { ...base, state: \"cancelled\" };\n case \"task.session.ended\":\n // #139: the claude crew session ended (SessionEnd hook). The process is\n // gone, so terminalize instead of resuming 'working'. Reuses the silent\n // 'cancelled' state — no alarming push, just a clean terminal record.\n return { ...base, state: \"cancelled\" };\n case \"task.session\":\n return stampAttempt(base, { resumeRef: ev.resumeRef }, now);\n case \"task.turn.started\":\n return { ...stampAttempt(base, {}, now), state: \"working\", pendingTool: undefined };\n case \"task.turn.completed\":\n // Anti-#2576 invariant: TurnCompleted is liveness, NEVER completion. Spec §4.8.\n // A turn ending while blocked must NOT unblock — only the captain's answer\n // (task.started via `crew send`) clears blocked. Mirrors task.progress: the\n // opencode SSE bridge emits task.turn.completed right after an explicit\n // `signal blocked`, and that trailing turn-end must not drop the question.\n // #608: 'review' needs the identical guard — a crew's normal turn-end always\n // fires right after `signal review`, and without this it fell through to\n // 'awaiting-input' below, making `crew approve` unreachable.\n if (isStickyAttention(rec.state)) return { ...rec, lastHeartbeat: now, lastEvent: ev.type };\n // #492: several parallel lifecycle sources (cmux store-file watch, native\n // claude hooks, cmux's forwarded event stream) each independently report\n // turn-end for the same crew. A stale/heuristic report can assert\n // task.turn.completed while a real tool call is still open (pendingTool set\n // from its own PreToolUse) — that directly contradicts the daemon's own\n // evidence (no matching PostToolUse yet), so it is not a genuine turn\n // boundary. Treat it as liveness only; the real turn-end arrives once the\n // tool actually returns and pendingTool clears.\n if (rec.pendingTool) return stampAttempt(base, {}, now);\n return { ...stampAttempt(base, {}, now), state: \"awaiting-input\", pendingTool: undefined };\n case \"task.delta\":\n return stampAttempt(base, {}, now); // heartbeat-only\n case \"task.input.requested\":\n case \"task.approval.requested\":\n return { ...stampAttempt(base, {}, now), state: \"blocked\", question: ev.question, pendingTool: undefined };\n case \"task.reattached\":\n return stampAttempt(base, {}, now);\n case \"task.first-turn.confirmed\":\n // #466/#470: stamp firstTurnConfirmedAt on the FIRST occurrence only.\n // UserPromptSubmit fires on every prompt submit (incl. captain follow-ups);\n // subsequent events are treated as liveness so the field is never re-stamped.\n if (rec.firstTurnConfirmedAt) {\n return { ...rec, lastEvent: \"task.progress\" };\n }\n return { ...rec, firstTurnConfirmedAt: now, lastEvent: ev.type };\n case \"task.stalled\":\n case \"task.idle\":\n case \"task.quiet\":\n case \"task.timeout\":\n case \"task.reconcile-failed\":\n // Synthetic notify-only events; the daemon has already updated state\n // directly via the watchdog/reconcile paths (task.quiet carries no state\n // change at all — the crew stays `working`). Reducer is a no-op.\n return rec;\n default:\n // #87: unknown/future event type from the wire — safe no-op.\n // The socket boundary (handle()) validates known types before calling\n // reduce; this default is a deep-defense fallback so reduce() can\n // never return undefined regardless of how it is called.\n return rec;\n }\n}\n","// src/control/watchdog.ts\nimport type { TaskRecord } from \"@squadrant/shared\";\n\n/**\n * #354: how long an interactive tool call may be in flight before it is treated\n * as hung. Deliberately generous (much larger than the 5-min heartbeat budget)\n * so a legitimately long tool — a multi-minute test suite, a big build, a slow\n * git/network op — does NOT trip it: those are real, recoverable work, and the\n * matching PostToolUse auto-clears the warn the instant it returns. Only a tool\n * that produces no result for this long is suspicious enough to surface as\n * \"possibly hung\". Default 10 min; tune via evaluateStall's `toolStallMs`.\n */\nexport const TOOL_STALL_BUDGET_MS = 10 * 60 * 1000;\n\n/**\n * Pure. Returns a stalled-transitioned record if a `working` task is genuinely\n * stuck at time `now` (epoch ms), else null. No I/O, no clock. #354 splits the\n * old single wall-clock timeout by what we can actually prove:\n *\n * - headless → 'stalled' once quiet past the heartbeat budget. A batch child\n * that stops emitting stdout is stuck; there is no captain turn to await.\n * - interactive WITH a tool in flight (pendingTool) → 'stalled' once that tool\n * has been outstanding past `toolStallMs`. A PreToolUse with no matching\n * PostToolUse is a hung tool call (we know which tool). Recoverable: the next\n * PostToolUse recovers it to `working` (state-machine / recoverStall).\n * - interactive with NO tool in flight → null. A quiet thinking turn is alive,\n * not stalled and NOT awaiting-input (the turn never ended — real CREW IDLE\n * comes only from the Stop hook). The daemon sweep surfaces this as a\n * distinct, non-alarming CREW QUIET notify instead (#354), keeping the crew\n * `working`. This replaces the old wall-clock → 'awaiting-input' flip, which\n * mislabeled deep-thinking crews as \"awaiting your input\".\n *\n * This function never produces `failed` or `awaiting-input` directly.\n */\nexport function evaluateStall(\n rec: TaskRecord,\n now: number,\n toolStallMs: number = TOOL_STALL_BUDGET_MS,\n): TaskRecord | null {\n if (rec.state !== \"working\") return null;\n if (rec.mode === \"interactive\") {\n // Only a hung tool call is a stall for an interactive crew; a quiet thinking\n // turn (no pendingTool) is alive and handled by the sweep's CREW QUIET path.\n if (!rec.pendingTool) return null;\n if (now - rec.pendingTool.since <= toolStallMs) return null;\n return { ...rec, state: \"stalled\", lastEvent: \"watchdog.tool-stall\" };\n }\n // headless: key off the latest attempt's lastHeartbeatAt so a stale event from\n // a dead prior attempt cannot refresh the liveness clock of the new dispatch (#89).\n const liveness = rec.attempts.at(-1)?.lastHeartbeatAt ?? rec.lastHeartbeat;\n if (now - liveness <= rec.heartbeatBudgetMs) return null;\n return { ...rec, state: \"stalled\", lastEvent: \"watchdog.stall\" };\n}\n\n/**\n * Pure. A stalled task that receives liveness returns to working.\n *\n * WARNING: this does NOT check heartbeat freshness — it returns a recovered\n * record for ANY stalled task. Callers MUST guard with\n * `now - rec.lastHeartbeat <= rec.heartbeatBudgetMs` before applying the\n * result, or a permanently-stale task will be falsely revived.\n */\nexport function recoverStall(rec: TaskRecord, now: number): TaskRecord | null {\n if (rec.state !== \"stalled\") return null;\n // #354: clear any hung-tool marker on recovery so a recovered crew never\n // carries a stale pendingTool into its next quiet window.\n return { ...rec, state: \"working\", lastHeartbeat: now, lastEvent: \"watchdog.recover\", pendingTool: undefined };\n}\n","// src/control/daemon.ts\nimport type { Store } from \"../store.js\";\nimport type { ControlEvent, TaskRecord, TaskState } from \"@squadrant/shared\";\nimport { TERMINAL_STATES } from \"@squadrant/shared\";\nimport { reduce } from \"../state-machine.js\";\nimport { evaluateStall, recoverStall } from \"../watchdog.js\";\nexport interface DaemonDeps {\n store: Store;\n now: () => number;\n /** Injected in Task 14; resumes a blocked session. Optional until then. */\n deliverReply?: (rec: TaskRecord, message: string) => Promise<void>;\n /** Defaults to a real process.kill(pid,0) check at the call site (Task 17). */\n isPidAlive?: (pid: number) => boolean;\n /**\n * #139 backstop: the interactive analogue of isPidAlive. Resolves whether an\n * interactive crew's backing cmux surface (pane/tab) still exists. Three-valued\n * so a transient cmux outage never false-reaps a live crew:\n * - \"alive\" → the crew's pane is present; keep watching.\n * - \"gone\" → cmux answered AND the pane is provably absent → terminalize.\n * - \"unknown\" → could not determine (cmux down, no captain, error) → do nothing.\n * Defaults to always-\"unknown\" (never reaps) when not wired — pure unit tests\n * and any non-cmux deployment are unaffected.\n */\n isSurfaceAlive?: (rec: TaskRecord) => Promise<\"alive\" | \"gone\" | \"unknown\">;\n /** Wired in squadrantd to runHeadless; absent in pure unit tests. */\n launchHeadless?: (rec: TaskRecord) => Promise<void>;\n /**\n * #259: true when a launchHeadless call for this task ID is currently in\n * flight (process spawned but no pid yet). reconcile() skips these so a\n * crash-restart re-run does NOT mark an actively-launching task as failed\n * and re-dispatch it, multiplying orphaned headless processes.\n * Defaults to () => false when not wired (pure unit tests, non-headless modes).\n */\n isHeadlessInFlight?: (id: string) => boolean;\n /**\n * Forward hook for the deferred interactive-wiring spec. While absent,\n * interactive dispatch fails LOUD (red-team #4) instead of silently\n * black-holing in `submitted` forever.\n */\n launchInteractive?: (rec: TaskRecord) => Promise<void>;\n /**\n * Wired in squadrantd to codexDriver.answer(). Delivers the captain's gate\n * resolution payload back to the interactive session (spec §4.9).\n */\n resolveInteractiveGate?: (taskId: string, payload: unknown) => Promise<void> | void;\n /**\n * Push notification hook (#109, refactored under mailbox-injector spec).\n * Called on every state transition into {done, blocked, failed, stalled}.\n * Implementations append to the mailbox; errors are caught + swallowed here\n * so an unhealthy notifier never breaks the event-ingest path.\n */\n notify?: (args: {\n project: string;\n message: string;\n record: TaskRecord;\n event: ControlEvent;\n }) => Promise<void> | void;\n /**\n * #225 hard crew task-timeout: wall-clock ceiling in ms. When a non-terminal\n * task's age (now - createdAt) exceeds this, the sweep fires a CREW TIMEOUT\n * escalation via the notify hook. Defaults to DEFAULT_TASK_TIMEOUT_MS (8h).\n * Distinct from the per-task heartbeat budget (stall detection).\n */\n taskTimeoutMs?: number;\n /**\n * #466 self-heal: re-deliver a crew's first turn when the daemon detects it\n * never landed (firstTurnConfirmedAt still absent past firstTurnUndeliveredBudgetMs).\n * MUST re-check TUI/pane readiness itself before sending — never blind-send\n * into a still-booting pane — and return { delivered: true } ONLY on a\n * positively-confirmed submit (mirrors sendFirstTurnWhenReady/confirmedSendToPane).\n * When absent, sweep falls back to the prior alert-only behavior (pure unit\n * tests, non-cmux deployments).\n */\n resendFirstTurn?: (rec: TaskRecord) => Promise<{ delivered: boolean }>;\n /** Override for testing; production default is DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS. */\n firstTurnUndeliveredBudgetMs?: number;\n /** Override for testing; production default is DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS. */\n firstTurnResendCooldownMs?: number;\n}\n\n// #466: dedicated, tight budget for detecting an undelivered first turn —\n// measured from createdAt (monotonic; never reset by heartbeat activity), NOT\n// from lastHeartbeat/heartbeatBudgetMs. The frozen-frame root cause showed\n// heartbeats can keep flowing on a crew whose first turn never landed, which\n// would mask the drop indefinitely under a heartbeat-based gate. The default\n// sits comfortably above crew-pane's own SEND_FIRST_TURN_TIMEOUT_MS (90s) plus\n// its confirmedSendToPane fallback retries (worst case ~100s), so the daemon's\n// resend never races the crew's own in-flight first-turn delivery attempt.\nexport const DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS = 120_000;\n// Minimum gap between resend attempts for the same task — avoids hammering a\n// still-not-ready pane with pastes every sweep tick.\nexport const DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS = 60_000;\n\n// #225 hard crew task-timeout: default wall-clock ceiling (8h). A crew can\n// heartbeat continuously yet be stuck on one task — the stall watchdog won't\n// catch it. This ceiling does. Configurable via DaemonDeps.taskTimeoutMs.\nexport const DEFAULT_TASK_TIMEOUT_MS = 8 * 60 * 60 * 1000;\n\n// #378: GC TTL for terminal records (done/failed/cancelled). Records older\n// than this are pruned from the store during sweep().\nexport const TERMINAL_RECORD_TTL_MS = 7 * 24 * 60 * 60 * 1000;\n\n// #457: Max terminal records to keep per project. Bounds accumulation for\n// short-lived test sessions where many tasks finish before the 7-day TTL.\nexport const TERMINAL_RECORD_KEEP_PER_PROJECT = 20;\n\n// 'awaiting-input' is an attention state: entering it (idle watchdog OR a\n// Stop-hook turn boundary) fires exactly one accurate CREW IDLE push. The\n// firePush prev===next guard keeps it from re-firing while the task sits idle.\n// #599: 'review' joins the attention states — a CREW REVIEW push fires exactly\n// like CREW DONE/BLOCKED, just without terminalizing the task.\nconst ATTENTION_STATES: ReadonlySet<TaskState> = new Set([\"done\", \"blocked\", \"review\", \"failed\", \"stalled\", \"awaiting-input\"]);\n\n// #139: non-terminal, post-launch states an interactive crew can be sitting in\n// while its session has actually died. Any of these with a provably-gone surface\n// is a zombie → reap to 'cancelled'. 'submitted' is excluded: it is pre-launch\n// (no surface yet), so reaping it would race the spawn.\n// #599: 'review' included — a crew that signaled review then crashed/closed\n// must not linger forever awaiting an approval that will never come.\nconst REAPABLE_SURFACE_STATES: ReadonlySet<TaskState> = new Set([\"working\", \"stalled\", \"awaiting-input\", \"blocked\", \"review\"]);\n\n// #210: CREW IDLE (awaiting-input) is debounced — suppressed when the turn-end\n// lands within this window of the captain's own last turn to the crew (a\n// `crew send`/reply emits task.started). This silences the rapid\n// send→respond→turn-end churn of an active back-and-forth while still\n// delivering a genuine self-idle (turn-end / idle-watchdog long after the\n// captain last engaged). Only awaiting-input is debounced; every other\n// attention state always delivers.\nexport const IDLE_DEBOUNCE_MS = 12_000;\n\nfunction shortId(id: string): string {\n return id.slice(0, 8);\n}\n\n/**\n * Build a disambiguated notification tag for a task record. Always appends\n * the short id so reused crew names are distinguishable across distinct ids.\n * Named: [provider/name · shortId] Unnamed: [provider/shortId]\n */\nexport function crewTag(r: TaskRecord): string {\n const suffix = shortId(r.id);\n if (r.name != null) {\n return `[${r.provider}/${r.name} · ${suffix}]`;\n }\n return `[${r.provider}/${suffix}]`;\n}\n\nfunction formatMessage(rec: TaskRecord, event?: ControlEvent): string | null {\n const tag = crewTag(rec);\n switch (rec.state) {\n case \"done\": {\n // Prefer the crew's own done message (`signal done --message`), carried on\n // the task.done event — this is what captains relied on under the old\n // relay formatter and must not regress (#214 unification). The task\n // snippet is the documented fallback when no message was provided.\n const doneMsg = event?.type === \"task.done\" ? event.message : undefined;\n const body =\n doneMsg != null && doneMsg.trim().length > 0\n ? doneMsg.split(/\\r?\\n/)[0].trim().slice(0, 200)\n : ((rec.task ?? \"\").split(/\\r?\\n/)[0]?.trim().slice(0, 120) ?? \"\");\n return `CREW DONE ${tag}: ${body}`;\n }\n case \"blocked\":\n return `CREW BLOCKED ${tag}: ${(rec.question ?? \"(no question)\").trim()}`;\n case \"review\": {\n // #599: crew has committed and is awaiting the captain's review verdict.\n const note = (rec.reviewNote ?? \"\").trim();\n return `CREW REVIEW ${tag}: ${note || \"ready for review\"} — run 'squadrant diff ${rec.project} ${rec.name ?? rec.id}' then 'squadrant crew approve' or send feedback.`;\n }\n case \"failed\":\n return `CREW FAILED ${tag}: ${(rec.error ?? \"(no error)\").trim()}`;\n case \"stalled\": {\n // #354: a hung interactive tool call reads differently from a headless\n // heartbeat stall — name the tool and how long it has been outstanding,\n // and frame it as \"possibly hung\" (recoverable, auto-clears on the tool's\n // PostToolUse), NOT a death notice.\n if (event?.type === \"task.stalled\" && event.tool) {\n const mins = event.elapsedMs != null ? Math.max(1, Math.round(event.elapsedMs / 60000)) : null;\n return `CREW STALLED ${tag}: still running ${event.tool}${mins != null ? ` ~${mins}min` : \"\"} — possibly hung (no result yet).`;\n }\n return `CREW STALLED ${tag}: no heartbeat in ${rec.heartbeatBudgetMs}ms`;\n }\n case \"awaiting-input\":\n // #522: 'awaiting-input' is reached ONLY via a genuine turn-boundary event\n // (task.turn.completed — see state-machine.ts) — there is no separate\n // watchdog-derived path into this state (the old wall-clock idle flip was\n // retired by #354; evaluateStall never produces 'awaiting-input'). So this\n // always means \"the crew deliberately ended its turn\", including the\n // common case of a long-lived crew pausing between sequential subtasks.\n // The former \"review and reply or close\" phrasing read like a possible\n // fault and forced a spot-check every time; state calmly instead.\n return `CREW IDLE ${tag}: turn ended, awaiting your reply.`;\n default:\n return null;\n }\n}\n\n/** #246: cross-project delegation report-back message. Called when a task\n * with originProject settles to a terminal state. Returns a captain-facing\n * one-liner delivered verbatim to the origin project's captain. */\nfunction formatDelegationReport(rec: TaskRecord, originProject: string, targetProject: string): string | null {\n const shortTask = (rec.task ?? \"\").split(/\\r?\\n/)[0]?.trim().slice(0, 120) ?? \"\";\n switch (rec.state) {\n case \"done\":\n return `✅ Cross-project task → ${targetProject}: done — ${shortTask}`;\n case \"blocked\":\n return `⛔ Cross-project task → ${targetProject}: blocked — ${(rec.question ?? \"(no question)\").trim()}`;\n case \"failed\":\n return `⛔ Cross-project task → ${targetProject}: failed — ${(rec.error ?? \"(no error)\").trim()}`;\n case \"stalled\":\n return `⚠️ Cross-project task → ${targetProject}: stalled (no heartbeat in ${rec.heartbeatBudgetMs}ms)`;\n default:\n return null;\n }\n}\n\nfunction firePush(\n deps: DaemonDeps,\n project: string,\n prev: TaskState,\n next: TaskRecord,\n event: ControlEvent,\n lastCaptainTurnAt?: number,\n): void {\n if (!deps.notify) return;\n if (prev === next.state) return;\n if (!ATTENTION_STATES.has(next.state)) return;\n // #210 idle debounce: a turn-end (awaiting-input) within IDLE_DEBOUNCE_MS of\n // the captain's last turn is part of an active back-and-forth — suppress the\n // CREW IDLE. All other attention states are never debounced.\n if (\n next.state === \"awaiting-input\" &&\n lastCaptainTurnAt != null &&\n deps.now() - lastCaptainTurnAt <= IDLE_DEBOUNCE_MS\n ) {\n return;\n }\n const message = formatMessage(next, event);\n if (!message) return;\n // Fire-and-forget; swallow errors so the daemon never trips on a flaky\n // notifier. Sync throws and async rejections both land here.\n try {\n const r = deps.notify({ project, message, record: next, event });\n if (r && typeof (r as Promise<void>).catch === \"function\") {\n (r as Promise<void>).catch(() => {});\n }\n } catch {\n // intentionally swallowed\n }\n // #246: cross-project delegation report-back. When a delegated task settles\n // (done/blocked/failed/stalled/cancelled), fan the outcome back to the origin\n // project's mailbox so A's relay wakes A's captain (dispatch-and-yield, never\n // poll). 'awaiting-input' is excluded — the origin doesn't need a noise push\n // every time the target crew ends a turn.\n const reportState = next.state === \"done\" || next.state === \"blocked\" || next.state === \"failed\" || next.state === \"stalled\" || next.state === \"cancelled\";\n if (next.originProject && next.originProject !== project && reportState) {\n const originMsg = formatDelegationReport(next, next.originProject, project);\n if (originMsg && deps.notify) {\n try {\n const r = deps.notify({ project: next.originProject, message: originMsg, record: next, event });\n if (r && typeof (r as Promise<void>).catch === \"function\") (r as Promise<void>).catch(() => {});\n } catch { /* swallowed */ }\n }\n }\n}\n\ntype Req =\n | { kind: \"dispatch\"; record: TaskRecord }\n | { kind: \"event\"; project: string; event: ControlEvent }\n | { kind: \"status\"; project: string; id: string }\n | { kind: \"list\"; project: string }\n | { kind: \"reply\"; project: string; id: string; message: string }\n | { kind: \"gate-resolve\"; project: string; gateId: string; resolvedBy: string; payload: unknown }\n | { kind: \"purge\"; project: string; id: string; force?: boolean };\n\n// #87: exhaustive set of known ControlEvent types for socket-boundary validation.\n// Any event.type arriving from the wire that is not in this set is rejected with\n// a clean structured error before it can reach reduce() or the store.\nconst KNOWN_EVENT_TYPES: ReadonlySet<string> = new Set([\n \"task.started\", \"task.progress\", \"heartbeat\",\n \"task.blocked\", \"task.review\", \"task.done\", \"task.failed\",\n \"task.session\", \"task.turn.started\", \"task.turn.completed\",\n \"task.delta\", \"task.input.requested\", \"task.approval.requested\",\n \"task.reattached\", \"task.reopened\",\n \"task.stalled\", \"task.idle\", \"task.quiet\", \"task.timeout\", \"task.reconcile-failed\",\n \"task.cancelled\", \"task.session.ended\",\n \"task.first-turn.confirmed\", // #466: delivery confirmation\n]);\n\nexport function createDaemon(deps: DaemonDeps) {\n const { store, now } = deps;\n // #210: per-task timestamp of the captain's most recent turn (a `crew send`/\n // reply/answer emits task.started). Used to debounce CREW IDLE during an\n // active back-and-forth. Bounded by the live task set; never read after a\n // task terminates (terminal states don't transition to awaiting-input).\n const lastCaptainTurnAt = new Map<string, number>();\n // #354: per-task debounce for CREW QUIET. Keyed to the liveness timestamp of\n // the quiet episode so exactly one QUIET fires per episode; when the crew shows\n // activity again, liveness advances and a later quiet episode re-notifies.\n const quietNotifiedAt = new Map<string, number>();\n // #466: per-task debounce for first-turn resend attempts — avoids hammering\n // a still-not-ready pane with pastes every sweep tick.\n const resendAttemptedAt = new Map<string, number>();\n const firstTurnUndeliveredBudgetMs = deps.firstTurnUndeliveredBudgetMs ?? DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS;\n const firstTurnResendCooldownMs = deps.firstTurnResendCooldownMs ?? DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS;\n\n // Shared event-application core (extracted from handle()'s \"event\" case) so\n // the #466 self-heal path below can stamp task.first-turn.confirmed through\n // the same reduce → store.put → firePush pipeline as a normal socket event.\n async function applyEvent(project: string, event: ControlEvent): Promise<TaskRecord> {\n if (!KNOWN_EVENT_TYPES.has((event as any).type)) {\n throw new Error(`unknown event type '${(event as any).type}' — not a valid ControlEvent`);\n }\n const cur = store.get(project, event.id);\n if (!cur) throw new Error(`unknown task ${event.id}`);\n if (event.type === \"task.started\") lastCaptainTurnAt.set(event.id, now());\n if (event.type === \"task.session.ended\" && !TERMINAL_STATES.has(cur.state)) {\n const liveness = deps.isSurfaceAlive ? await deps.isSurfaceAlive(cur) : \"unknown\";\n if (liveness !== \"gone\") return cur; // alive/unknown: no-op, keep current state\n }\n const next = reduce(cur, event, now());\n if (next !== cur) {\n store.put(next); // skip redundant write on terminal no-ops\n firePush(deps, project, cur.state, next, event, lastCaptainTurnAt.get(next.id));\n }\n return next;\n }\n\n // #466 self-heal: attempt to recover a task whose first turn never landed.\n // Called from sweep() once undeliveredMs exceeds firstTurnUndeliveredBudgetMs.\n // Debounced per-task via resendAttemptedAt. Re-fetches the record from the\n // store right before acting so a confirmation that lands concurrently (e.g.\n // the UserPromptSubmit hook) is never double-delivered — CRITICAL SAFETY.\n async function attemptFirstTurnRecovery(r: TaskRecord, undeliveredMs: number): Promise<void> {\n const lastAttempt = resendAttemptedAt.get(r.id);\n if (lastAttempt != null && now() - lastAttempt < firstTurnResendCooldownMs) return;\n resendAttemptedAt.set(r.id, now());\n\n const fresh = store.get(r.project, r.id);\n if (!fresh || fresh.firstTurnConfirmedAt) return; // already landed — idempotent no-op\n\n const tag = crewTag(fresh);\n const fireNotify = (message: string) => {\n if (!deps.notify) return;\n const synthEvent: ControlEvent = { type: \"task.quiet\", id: fresh.id, quietMs: undeliveredMs };\n try {\n const p = deps.notify({ project: fresh.project, message, record: store.get(fresh.project, fresh.id) ?? fresh, event: synthEvent });\n if (p && typeof (p as Promise<void>).catch === \"function\") (p as Promise<void>).catch(() => {});\n } catch { /* swallowed — a flaky notifier must never trip the sweep */ }\n };\n\n if (!deps.resendFirstTurn) {\n // No resend capability wired (pure unit tests / non-cmux deployment) —\n // preserve the prior alert-only behavior.\n fireNotify(`⚠️ CREW UNDELIVERED ${tag}: first turn may not have landed (0 activity) — re-send the task or check the spawn.`);\n return;\n }\n\n let result: { delivered: boolean };\n try { result = await deps.resendFirstTurn(fresh); }\n catch { result = { delivered: false }; }\n\n if (result.delivered) {\n // The reducer's task.first-turn.confirmed path is itself idempotent\n // (first occurrence only — #470), so calling it here is safe even if the\n // resend's own hook confirmation raced ahead of us.\n try { await applyEvent(fresh.project, { type: \"task.first-turn.confirmed\", id: fresh.id }); } catch { /* best-effort */ }\n fireNotify(`🔁 CREW FIRST-TURN AUTO-RESENT ${tag}: first turn had not landed after ${Math.round(undeliveredMs / 1000)}s — re-sent automatically.`);\n } else {\n fireNotify(`⚠️ CREW UNDELIVERED ${tag}: first turn may not have landed — auto-resend attempted but the pane wasn't ready; will retry.`);\n }\n }\n\n return {\n async handle(req: Req): Promise<TaskRecord | TaskRecord[]> {\n switch (req.kind) {\n case \"dispatch\": {\n store.put(req.record);\n // #246: cross-project delegation — notify B's mailbox so B's relay\n // wakes B's captain with the request. Skip auto-launch; B's captain\n // decides how to execute (typically spawns a crew).\n if (req.record.originProject) {\n const origin = req.record.originProject;\n const msg = `📨 Cross-project task from ${origin}: ${req.record.task}`;\n if (deps.notify) {\n try {\n const r = deps.notify({ project: req.record.project, message: msg, record: req.record, event: { type: \"task.started\", id: req.record.id } });\n if (r && typeof (r as Promise<void>).catch === \"function\") (r as Promise<void>).catch(() => {});\n } catch { /* swallowed — flaky notifier must not break dispatch */ }\n }\n return req.record;\n }\n if (req.record.mode === \"headless\" && deps.launchHeadless) {\n deps.launchHeadless(req.record).catch((e: unknown) => {\n const error = e instanceof Error ? e.message : String(e);\n store.put({ ...req.record, state: \"failed\", lastEvent: \"launch-error\", error });\n });\n return req.record;\n }\n if (req.record.mode === \"interactive\" && deps.launchInteractive) {\n deps.launchInteractive(req.record).catch((e: unknown) => {\n const error = e instanceof Error ? e.message : String(e);\n store.put({ ...req.record, state: \"failed\", lastEvent: \"launch-error\", error });\n });\n return req.record;\n }\n // No launcher for this mode → fail LOUD, never silently park in\n // `submitted` (red-team #4). Interactive launcher is the deferred\n // interactive-wiring spec; until then, say so explicitly.\n const failed: TaskRecord = {\n ...req.record,\n state: \"failed\",\n lastEvent: \"no-launcher\",\n error:\n req.record.mode === \"interactive\"\n ? \"interactive mode is not yet implemented (deferred interactive-wiring spec); use --mode headless\"\n : `no launcher available for mode '${req.record.mode}'`,\n };\n store.put(failed);\n return failed;\n }\n case \"event\": {\n // #87: validate event.type at the socket boundary before touching state.\n if (!KNOWN_EVENT_TYPES.has((req.event as any).type)) {\n throw new Error(`unknown event type '${(req.event as any).type}' — not a valid ControlEvent`);\n }\n return applyEvent(req.project, req.event);\n }\n case \"status\": {\n const r = store.get(req.project, req.id);\n if (!r) throw new Error(`unknown task ${req.id}`);\n return r;\n }\n case \"list\":\n return store.list(req.project);\n case \"reply\": {\n const r = store.get(req.project, req.id);\n if (!r) throw new Error(`unknown task ${req.id}`);\n if (r.state !== \"blocked\") throw new Error(`task ${req.id} is not blocked (state=${r.state})`);\n // The captain's answer is a turn to the crew (#210 debounce key).\n lastCaptainTurnAt.set(r.id, now());\n const next = reduce(r, { type: \"task.started\", id: r.id }, now());\n store.put(next); // persist the transition before delivering (durable first)\n if (deps.deliverReply) await deps.deliverReply(r, req.message);\n return next;\n }\n case \"gate-resolve\": {\n // Find the task that owns this gate.\n const owning = deps.store.listAll().find((r) => r.gates?.some((g) => g.gateId === req.gateId));\n if (!owning || !owning.gates) throw new Error(`gate ${req.gateId} not found`);\n const updatedGates = owning.gates.map((g) =>\n g.gateId === req.gateId\n ? { ...g, state: \"resolved\" as const, resolvedBy: req.resolvedBy, resolution: req.payload }\n : g,\n );\n deps.store.put({ ...owning, gates: updatedGates });\n // Driver answers via the saved requestId (it tracks it per-task internally).\n if (deps.resolveInteractiveGate) await deps.resolveInteractiveGate(owning.id, req.payload);\n return { ...owning, gates: updatedGates };\n }\n case \"purge\": {\n const r = store.get(req.project, req.id);\n if (!r) throw new Error(`unknown task ${req.id}`);\n if (!TERMINAL_STATES.has(r.state) && !req.force) {\n throw new Error(`task ${req.id} is not terminal (state=${r.state}); use --force to purge anyway`);\n }\n store.delete(req.project, req.id);\n return r;\n }\n default: { const _exhaustive: never = req; throw new Error(`unhandled request kind`); }\n }\n },\n async sweep(): Promise<void> {\n const t = now();\n const surfaceAlive = deps.isSurfaceAlive ?? (async () => \"unknown\" as const);\n\n // #457: Per-project overflow prune — keep only the most-recent K terminal\n // records. Bounds accumulation for short-lived sessions where many tasks\n // finish before the 7-day TTL expires.\n const projects = new Set(store.listAll().map((r) => r.project));\n for (const project of projects) {\n const terminal = store.list(project)\n .filter((r) => TERMINAL_STATES.has(r.state))\n .sort((a, b) => b.lastHeartbeat - a.lastHeartbeat);\n for (const r of terminal.slice(TERMINAL_RECORD_KEEP_PER_PROJECT)) {\n store.delete(r.project, r.id);\n }\n }\n\n // #466: first-turn recovery attempts fired this tick — awaited together at\n // the end so the loop below never blocks on a slow pane round-trip.\n const recoveryPromises: Promise<void>[] = [];\n\n for (const r of store.listAll()) {\n // #378: GC terminal records whose last heartbeat is older than the TTL.\n if (TERMINAL_STATES.has(r.state) && t - r.lastHeartbeat > TERMINAL_RECORD_TTL_MS) {\n store.delete(r.project, r.id);\n continue;\n }\n // #225 root-fix: terminate non-terminal tasks that exceeded the wall-clock\n // ceiling. Terminalization is the persistent dedup — a daemon restart sees\n // the cancelled record and the TERMINAL_STATES gate above blocks re-fire.\n // The volatile firedTimeout Set is removed; terminal state replaces it.\n if (!TERMINAL_STATES.has(r.state)) {\n const ceiling = deps.taskTimeoutMs ?? DEFAULT_TASK_TIMEOUT_MS;\n if (t - r.createdAt > ceiling) {\n const prevState = r.state; // capture BEFORE terminalization (shown in message)\n const tag = crewTag(r);\n const hrs = Math.round(ceiling / 3_600_000);\n const msg = `CREW TIMEOUT ${tag}: wall-clock exceeded ${hrs}h (id: ${r.id}, state: ${prevState})`;\n const synthEvent: ControlEvent = { type: \"task.timeout\", id: r.id, taskTimeoutMs: ceiling };\n // Terminalize first — persisted to store so any future daemon instance\n // sees a terminal record and skips it (flood-proof across restarts).\n store.put({ ...r, state: \"cancelled\", lastEvent: \"sweep.task-timeout\" });\n // #457: suppress ghost CREW TIMEOUT for interactive tasks whose surface\n // is provably gone — they were already abandoned; terminalize silently.\n // Headless tasks have no cmux surface so always notify.\n // \"unknown\" (cmux down / transient) → notify conservatively.\n let shouldNotify = true;\n if (r.mode === \"interactive\") {\n const liveness = await surfaceAlive(r);\n if (liveness === \"gone\") shouldNotify = false;\n }\n if (deps.notify && shouldNotify) {\n try {\n const p = deps.notify({ project: r.project, message: msg, record: r, event: synthEvent });\n if (p && typeof (p as Promise<void>).catch === \"function\") {\n (p as Promise<void>).catch(() => {});\n }\n } catch {\n // swallowed — a flaky notifier must never trip the sweep\n }\n }\n continue; // #378: skip remaining sweep body — stale `r` must not clobber just-written terminal state\n }\n }\n // #139 backstop: reap interactive records whose backing surface is\n // PROVABLY gone (crew session died with no terminal signal — opencode has\n // no SessionEnd hook, and a hard kill can drop claude's). This is\n // liveness-based reaping, NOT a shorter timeout: the 24h heartbeat budget\n // is untouched, so a legitimately-idle LIVE crew is never reaped (its\n // surface answers \"alive\" and falls through to evaluateStall → CREW IDLE).\n // \"unknown\" (cmux down) never reaps. cancelled is silent (not in\n // ATTENTION_STATES) — no false CREW STALLED re-emitted.\n if (r.mode === \"interactive\" && REAPABLE_SURFACE_STATES.has(r.state)) {\n const liveness = await surfaceAlive(r);\n if (liveness === \"gone\") {\n store.put({ ...r, state: \"cancelled\", lastEvent: \"sweep.surface-gone\" });\n continue;\n }\n }\n // #466-single: An interactive crew spawned but never started stays in\n // `submitted` — no task.started hook ever fires, so the working-state\n // undelivered check below is unreachable. Recover it here too, keyed\n // off createdAt (not lastHeartbeat/heartbeatBudgetMs — see #466 self-heal\n // below) so a silently-dropped first turn is caught regardless of state.\n if (r.mode === \"interactive\" && r.state === \"submitted\" && !r.firstTurnConfirmedAt) {\n const undeliveredMs = t - r.createdAt;\n if (undeliveredMs > firstTurnUndeliveredBudgetMs) {\n recoveryPromises.push(attemptFirstTurnRecovery(r, undeliveredMs));\n continue;\n }\n }\n // #354: evaluateStall now only stalls a HEADLESS heartbeat timeout or a\n // hung INTERACTIVE tool call (PreToolUse with no PostToolUse past the\n // tool-stall budget). A quiet interactive thinking turn no longer stalls\n // here — it is surfaced as CREW QUIET below, keeping the crew `working`.\n const idle = evaluateStall(r, t);\n if (idle) {\n store.put(idle);\n // The synth event only carries the notify payload; the reducer treats\n // it as a no-op (state already updated above). A hung-tool stall carries\n // the tool name + elapsed so the notifier renders the accurate message.\n const synthEvent: ControlEvent = idle.pendingTool\n ? { type: \"task.stalled\", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs, tool: idle.pendingTool.name, elapsedMs: t - idle.pendingTool.since }\n : { type: \"task.stalled\", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs };\n firePush(deps, r.project, r.state, idle, synthEvent, lastCaptainTurnAt.get(r.id));\n continue;\n }\n // #466 self-heal: an interactive `working` crew that has NEVER had\n // firstTurnConfirmedAt set may be sitting at an empty prompt (the crew\n // pane never actually ingested the first turn). Checked BEFORE the\n // heartbeat-driven CREW QUIET branch below and gated on createdAt, NOT\n // on heartbeat liveness — the frozen-frame root cause showed heartbeats\n // can keep flowing on a crew whose first turn never landed, which would\n // otherwise mask the drop indefinitely.\n if (r.mode === \"interactive\" && r.state === \"working\" && !r.pendingTool && !r.firstTurnConfirmedAt) {\n const undeliveredMs = t - r.createdAt;\n if (undeliveredMs > firstTurnUndeliveredBudgetMs) {\n recoveryPromises.push(attemptFirstTurnRecovery(r, undeliveredMs));\n continue;\n }\n }\n // #354 CREW QUIET: a `working` interactive crew quiet past its heartbeat\n // budget with NO tool in flight is alive but deep-thinking (no hook fires\n // during pure model thinking). Surface a distinct, non-alarming nudge —\n // NOT 'awaiting-input' (the turn never ended; real CREW IDLE comes only\n // from the Stop hook). State stays `working`; notify once per episode.\n // Only reachable once firstTurnConfirmedAt is set — the undelivered\n // check above owns the !firstTurnConfirmedAt case.\n if (r.mode === \"interactive\" && r.state === \"working\" && !r.pendingTool && r.firstTurnConfirmedAt) {\n const liveness = r.attempts.at(-1)?.lastHeartbeatAt ?? r.lastHeartbeat;\n const quiet = t - liveness;\n if (quiet > r.heartbeatBudgetMs) {\n if (deps.notify && quietNotifiedAt.get(r.id) !== liveness) {\n quietNotifiedAt.set(r.id, liveness);\n const tag = crewTag(r);\n const synthEvent: ControlEvent = { type: \"task.quiet\", id: r.id, quietMs: quiet };\n const mins = Math.max(1, Math.round(quiet / 60000));\n const message = `CREW QUIET ${tag}: working ~${mins}min with no tool activity — likely deep thinking (no reply expected yet).`;\n try {\n const p = deps.notify({ project: r.project, message, record: r, event: synthEvent });\n if (p && typeof (p as Promise<void>).catch === \"function\") (p as Promise<void>).catch(() => {});\n } catch { /* swallowed — a flaky notifier must never trip the sweep */ }\n }\n continue;\n }\n }\n // Activity resumed (or never went quiet) → drop any QUIET debounce marker\n // so the next genuine quiet episode notifies again, and avoid map growth.\n if (quietNotifiedAt.has(r.id)) quietNotifiedAt.delete(r.id);\n const recovered = recoverStall(r, t);\n // recoverStall does NOT check heartbeat freshness — guard per its contract\n if (recovered && t - r.lastHeartbeat <= r.heartbeatBudgetMs) store.put(recovered);\n }\n // #466: wait for this tick's first-turn recovery attempts. They ran\n // independently (not serialized in the loop above), so this only adds\n // the latency of the slowest single attempt, not their sum.\n await Promise.all(recoveryPromises);\n },\n async reconcile(): Promise<void> {\n const alive = deps.isPidAlive ?? (() => true);\n const surfaceAlive = deps.isSurfaceAlive ?? (async () => \"unknown\" as const);\n for (const r of store.listAll()) {\n if (r.state !== \"working\" && r.state !== \"submitted\") continue;\n if (r.mode === \"headless\") {\n if (r.pid != null && alive(r.pid)) continue; // still running, keep watching\n if (deps.isHeadlessInFlight?.(r.id)) continue; // #259: launch in-flight, pid not yet set\n const failed: TaskRecord = {\n ...r, state: \"failed\", lastEvent: \"reconcile\",\n error: \"orphaned by daemon restart; exit unobserved (conservative fail)\",\n };\n store.put(failed);\n const synthEvent: ControlEvent = {\n type: \"task.failed\",\n id: r.id,\n error: failed.error ?? \"reconcile\",\n };\n firePush(deps, r.project, r.state, failed, synthEvent, lastCaptainTurnAt.get(r.id));\n } else {\n // #139: an interactive crew's cmux pane SURVIVES a daemon bounce, so a\n // live crew must stay 'working' for the reattach loop to re-subscribe\n // it. The old unconditional → 'stalled' both false-stalled live crews\n // AND fired CREW STALLED on every restart. Reap ONLY when the surface\n // is provably gone; alive/unknown stay working (sweep re-checks later).\n const liveness = await surfaceAlive(r);\n if (liveness === \"gone\") {\n store.put({ ...r, state: \"cancelled\", lastEvent: \"reconcile.surface-gone\" });\n // silent — the crew is gone; no alarming push (consistent with close).\n }\n }\n }\n },\n };\n}\n","import { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\nimport type { TaskRecord, ControlEvent } from \"@squadrant/shared\";\n\nexport interface MailboxEntry {\n seq: number;\n ts: string;\n /** Absent on external (captain.message) entries — they have no task. */\n taskId?: string;\n /** Optional human-readable name carried from TaskRecord. Absent on legacy\n * records — readers must fall back to shortId(taskId). */\n name?: string;\n /** \"captain.message\" is an external (non-ControlEvent) message injected for the\n * captain — e.g. an inbound Telegram reply. */\n kind: ControlEvent[\"type\"] | \"captain.message\";\n /** Absent on external entries — no originating agent provider. */\n provider?: TaskRecord[\"provider\"];\n /** Absent/free-form on external entries. */\n payload?: Record<string, unknown>;\n /** Daemon-rendered captain-facing message (unified-formatter, #214/#210).\n * The daemon's formatMessage is the single source of truth; the relay\n * delivers this verbatim and skips entries where it is null/empty.\n * `null` on entries the daemon chose not to surface (and legacy records). */\n message?: string | null;\n}\n\ninterface AppendOpts {\n stateRoot: string;\n project: string;\n taskRecord: TaskRecord;\n event: ControlEvent;\n /** Captain-facing message rendered by the daemon (daemon.ts formatMessage). */\n message?: string | null;\n}\n\nfunction inboxDir(stateRoot: string): string {\n return join(stateRoot, \"inbox\");\n}\n\nfunction logPath(stateRoot: string, project: string): string {\n return join(inboxDir(stateRoot), `${project}.log`);\n}\n\nfunction extractPayload(event: ControlEvent): Record<string, unknown> {\n const { type: _type, id: _id, ...payload } = event as Record<string, unknown> & { type: string; id: string };\n return payload;\n}\n\nasync function listRotatedOldestFirst(stateRoot: string, project: string): Promise<string[]> {\n const dir = inboxDir(stateRoot);\n let entries: string[];\n try { entries = await fs.readdir(dir); }\n catch { return []; }\n const prefix = `${project}.log.`;\n return entries\n .filter((e) => e.startsWith(prefix) && /^\\d+$/.test(e.slice(prefix.length)))\n .map((e) => ({ name: e, n: Number(e.slice(prefix.length)) }))\n .sort((a, b) => b.n - a.n) // .3 first (oldest), .1 last (newest rotated)\n .map((e) => join(dir, e.name));\n}\n\nasync function readMaxSeqFromFile(file: string): Promise<number> {\n try {\n const buf = await fs.readFile(file, \"utf-8\");\n if (!buf.trim()) return 0;\n const lines = buf.trim().split(\"\\n\");\n for (let i = lines.length - 1; i >= 0; i--) {\n try {\n const obj = JSON.parse(lines[i]) as MailboxEntry;\n return obj.seq;\n } catch { continue; }\n }\n return 0;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"ENOENT\") return 0;\n throw e;\n }\n}\n\nasync function readMaxSeq(stateRoot: string, project: string): Promise<number> {\n let max = 0;\n const files = [\n logPath(stateRoot, project),\n ...(await listRotatedOldestFirst(stateRoot, project)),\n ];\n for (const file of files) {\n const seq = await readMaxSeqFromFile(file);\n if (seq > max) max = seq;\n }\n return max;\n}\n\n// Per-project serial mutex. Node's event loop is single-threaded but async\n// readFile + writeFile can interleave; chaining all appends for the same\n// project through a single in-process Promise serializes them.\n//\n// For cross-process serialization (multi-daemon scenarios, e.g. launchctl\n// restart races), an OS-level flock would be needed on `<project>.log`.\n// Today squadrant runs a single daemon instance; the in-process mutex covers\n// the realistic concurrency model. flock can be added later if multi-process\n// access becomes a requirement.\nconst projectLocks = new Map<string, Promise<unknown>>();\n\nfunction withProjectLock<T>(project: string, fn: () => Promise<T>): Promise<T> {\n const prev = projectLocks.get(project) ?? Promise.resolve();\n const next = prev.catch(() => undefined).then(fn);\n // Store a tail that does not reject so the chain never breaks on caller failure\n projectLocks.set(project, next.catch(() => undefined));\n return next;\n}\n\n/** Assign a monotonic seq under the per-project lock and append the built entry. */\nfunction appendEntry(\n stateRoot: string,\n project: string,\n build: (seq: number) => MailboxEntry,\n): Promise<number> {\n return withProjectLock(project, async () => {\n const dir = inboxDir(stateRoot);\n await fs.mkdir(dir, { recursive: true });\n const file = logPath(stateRoot, project);\n const lastSeq = await readMaxSeq(stateRoot, project);\n const seq = lastSeq + 1;\n const entry = build(seq);\n await fs.appendFile(file, JSON.stringify(entry) + \"\\n\", { encoding: \"utf-8\" });\n return seq;\n });\n}\n\nexport async function appendToMailbox(opts: AppendOpts): Promise<number> {\n return appendEntry(opts.stateRoot, opts.project, (seq) => ({\n seq,\n ts: new Date().toISOString(),\n taskId: opts.taskRecord.id,\n ...(opts.taskRecord.name !== undefined ? { name: opts.taskRecord.name } : {}),\n kind: opts.event.type,\n provider: opts.taskRecord.provider,\n payload: extractPayload(opts.event),\n message: opts.message ?? null,\n }));\n}\n\n/**\n * Append an external message destined for the captain pane (#65 Telegram inbound).\n * `text` is the already-rendered captain-facing message; it is delivered verbatim\n * by the #332 delivery loop (deliverable() returns it, defer-protected). The entry\n * carries no taskId/provider — it is not tied to a crew task.\n */\nexport async function appendCaptainMessage(opts: {\n stateRoot: string;\n project: string;\n text: string;\n source: \"telegram\" | \"daemon\" | \"cli\";\n}): Promise<number> {\n return appendEntry(opts.stateRoot, opts.project, (seq) => ({\n seq,\n ts: new Date().toISOString(),\n kind: \"captain.message\",\n payload: { source: opts.source },\n message: opts.text,\n }));\n}\n\nfunction cursorPath(stateRoot: string, project: string, subscriber: string): string {\n return join(inboxDir(stateRoot), `${project}.${subscriber}.cursor`);\n}\n\ninterface CursorOpts {\n stateRoot: string;\n project: string;\n subscriber: string;\n}\n\nexport interface CursorState {\n lastAckedSeq: number;\n subscriber: string;\n updatedAt: string;\n}\n\nexport async function readCursor(opts: CursorOpts): Promise<CursorState | null> {\n let buf: string;\n try {\n buf = await fs.readFile(cursorPath(opts.stateRoot, opts.project, opts.subscriber), \"utf-8\");\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw e;\n }\n // A 0-byte or corrupt cursor (e.g. an interrupted write) must be treated the\n // same as a missing one — return null so the caller starts fresh from seq 0\n // rather than crashing the relay boot / delivery loop (#332 storm BUG 1).\n if (!buf.trim()) return null;\n try {\n return JSON.parse(buf) as CursorState;\n } catch {\n return null;\n }\n}\n\nexport interface WaitForCaptainDeliveryOpts {\n stateRoot: string;\n project: string;\n /** The seq returned by appendCaptainMessage/appendToMailbox for the entry to confirm. */\n seq: number;\n /** Delivery cursor subscriber to poll (default \"captain\" — the only current subscriber). */\n subscriber?: string;\n timeoutMs: number;\n pollMs: number;\n}\n\n/**\n * Poll the delivery cursor until it has acked `seq` (the delivery loop drained\n * the entry) or the timeout elapses (#566). A CLI-originated send only knows\n * its message reached the pane once the cursor advances past its own seq —\n * appending to the mailbox alone proves nothing about delivery.\n */\nexport async function waitForCaptainDelivery(opts: WaitForCaptainDeliveryOpts): Promise<boolean> {\n const subscriber = opts.subscriber ?? \"captain\";\n const deadline = Date.now() + opts.timeoutMs;\n for (;;) {\n const cursor = await readCursor({ stateRoot: opts.stateRoot, project: opts.project, subscriber });\n if (cursor && cursor.lastAckedSeq >= opts.seq) return true;\n if (Date.now() >= deadline) return false;\n await new Promise((r) => setTimeout(r, opts.pollMs));\n }\n}\n\nexport async function writeCursor(opts: CursorOpts & { lastAckedSeq: number }): Promise<void> {\n await fs.mkdir(inboxDir(opts.stateRoot), { recursive: true });\n const dest = cursorPath(opts.stateRoot, opts.project, opts.subscriber);\n // Unique tmp per call so overlapping writes never share a tmp path. A shared\n // `dest + \".tmp\"` let one rename consume the tmp the other expected → ENOENT\n // on rename, leaving a 0-byte/corrupt cursor (#332 storm BUG 2).\n const tmp = `${dest}.${process.pid}.${randomUUID()}.tmp`;\n const data: CursorState = {\n lastAckedSeq: opts.lastAckedSeq,\n subscriber: opts.subscriber,\n updatedAt: new Date().toISOString(),\n };\n const handle = await fs.open(tmp, \"w\");\n try {\n await handle.writeFile(JSON.stringify(data), { encoding: \"utf-8\" });\n await handle.sync();\n } finally {\n await handle.close();\n }\n try {\n await fs.rename(tmp, dest);\n } catch (e) {\n // Best-effort cleanup so a failed rename doesn't leave the unique tmp behind.\n await fs.unlink(tmp).catch(() => {});\n throw e;\n }\n}\n\ninterface ReadFromCursorOpts {\n stateRoot: string;\n project: string;\n fromSeq: number;\n}\n\nexport async function* readFromCursor(opts: ReadFromCursorOpts): AsyncIterable<MailboxEntry> {\n // Order: oldest rotated first (.3 → .2 → .1), then current.\n const rotated = await listRotatedOldestFirst(opts.stateRoot, opts.project);\n const files = [...rotated, logPath(opts.stateRoot, opts.project)];\n for (const file of files) {\n let buf: string;\n try {\n buf = await fs.readFile(file, \"utf-8\");\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"ENOENT\") continue;\n throw e;\n }\n for (const line of buf.split(\"\\n\")) {\n if (!line.trim()) continue;\n let entry: MailboxEntry;\n try {\n entry = JSON.parse(line) as MailboxEntry;\n } catch {\n continue;\n }\n if (entry.seq >= opts.fromSeq) yield entry;\n }\n }\n}\n\n/** Read-only Tier 2 observability stats for one project's mailbox (#44 dashboard). */\nexport interface MailboxStats {\n /** Highest seq across the current log + rotated segments (0 when empty). */\n maxSeq: number;\n /** Size in bytes of the current (un-rotated) log file. */\n sizeBytes: number;\n /** Age of the oldest entry in the current log (0 when empty/missing). */\n oldestEntryAgeMs: number;\n /** Number of rotated segments on disk (<project>.log.1, .2, …). */\n rotationCount: number;\n}\n\n/**\n * Read-only stats for the dashboard's Tier 2 data-plane view. Never mutates;\n * tolerates a missing inbox (returns zeros). The daemon gathers this and passes\n * it to the pure snapshot assembler.\n */\nexport async function mailboxStats(stateRoot: string, project: string): Promise<MailboxStats> {\n const file = logPath(stateRoot, project);\n const rotated = await listRotatedOldestFirst(stateRoot, project);\n let sizeBytes = 0;\n for (const f of [file, ...rotated]) {\n try { sizeBytes += (await fs.stat(f)).size; }\n catch (e) { if ((e as NodeJS.ErrnoException).code !== \"ENOENT\") throw e; }\n }\n // Oldest entry lives in the oldest rotated archive when one exists (listRotatedOldestFirst\n // returns oldest-first), otherwise it's the current file.\n const oldestFile = rotated[0] ?? file;\n return {\n maxSeq: await readMaxSeq(stateRoot, project),\n sizeBytes,\n oldestEntryAgeMs: await oldestEntryAgeMs(oldestFile),\n rotationCount: rotated.length,\n };\n}\n\ninterface RotateOpts {\n stateRoot: string;\n project: string;\n maxBytes: number;\n maxAgeMs: number;\n keepCount: number;\n}\n\nexport interface RotateResult {\n rotated: boolean;\n from?: string;\n to?: string;\n}\n\nasync function oldestEntryAgeMs(file: string): Promise<number> {\n try {\n const buf = await fs.readFile(file, \"utf-8\");\n const firstLine = buf.split(\"\\n\").find((l) => l.trim());\n if (!firstLine) return 0;\n const entry = JSON.parse(firstLine) as MailboxEntry;\n return Date.now() - new Date(entry.ts).getTime();\n } catch {\n return 0;\n }\n}\n\nexport async function rotateIfNeeded(opts: RotateOpts): Promise<RotateResult> {\n return withProjectLock(opts.project, async () => {\n const file = logPath(opts.stateRoot, opts.project);\n let size = 0;\n try { size = (await fs.stat(file)).size; }\n catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"ENOENT\") return { rotated: false };\n throw e;\n }\n const age = await oldestEntryAgeMs(file);\n if (size < opts.maxBytes && age < opts.maxAgeMs) return { rotated: false };\n\n // Shift existing .N files down (.N → .N+1), deleting anything beyond keepCount.\n // Process highest N first so we don't clobber.\n // Find the existing max N.\n const existing = await listRotatedOldestFirst(opts.stateRoot, opts.project);\n // existing is sorted by N desc (oldest first). Extract numbers.\n const nums = existing.map((p) => Number(p.slice(p.lastIndexOf(\".\") + 1))).sort((a, b) => b - a);\n for (const n of nums) {\n const src = `${file}.${n}`;\n const dst = `${file}.${n + 1}`;\n if (n + 1 > opts.keepCount) {\n try { await fs.unlink(src); } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== \"ENOENT\") throw e;\n }\n } else {\n try { await fs.rename(src, dst); } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== \"ENOENT\") throw e;\n }\n }\n }\n // current → .1\n await fs.rename(file, `${file}.1`);\n // create fresh empty current\n await fs.writeFile(file, \"\", { encoding: \"utf-8\" });\n return { rotated: true, from: file, to: `${file}.1` };\n });\n}\n","// src/control/protocol.ts\nimport { createServer, createConnection, type Server, type Socket } from \"node:net\";\nimport { existsSync, unlinkSync } from \"node:fs\";\n\n// Bump this on any change to the request/reply wire shape.\n// v1 is the first versioned release. Clients treat an absent _v as compatible\n// (pre-v1 rollout grace period); future bumps hard-fail on mismatch.\nexport const PROTOCOL_VERSION = 1;\n\nexport function encodeMsg(obj: unknown): string {\n return JSON.stringify(obj) + \"\\n\";\n}\n\nexport function createDecoder(onParseError?: (line: string) => void) {\n let buf = \"\";\n return {\n push(chunk: string): unknown[] {\n buf += chunk;\n const out: unknown[] = [];\n let idx: number;\n while ((idx = buf.indexOf(\"\\n\")) >= 0) {\n const line = buf.slice(0, idx);\n buf = buf.slice(idx + 1);\n if (!line.trim()) continue;\n try {\n const parsed = JSON.parse(line);\n // Silently discard keepalive frames (#94) — never surface to any consumer.\n if (typeof parsed === \"object\" && parsed !== null && (parsed as any).type === \"_keepalive\") continue;\n out.push(parsed);\n } catch {\n // #87: notify caller of malformed lines so the server can reply with a\n // structured error instead of silently dropping the frame.\n onParseError?.(line);\n }\n }\n return out;\n },\n // #87: exposes the unprocessed buffer content — bytes received but not yet\n // terminated with a newline. The server checks this on connection end to\n // detect newline-less input and reply with a fast structured error.\n remainder(): string {\n return buf;\n },\n };\n}\n\nexport type Handler = (msg: any) => Promise<unknown>;\n\n/** NetConn is the raw socket for a single client connection. */\nexport type NetConn = Socket;\n\n/** Injectable clock for startServer — lets tests drive keepalive timers without real timers. */\nexport interface ServerDeps {\n setInterval?: (fn: () => void, ms: number) => ReturnType<typeof setInterval>;\n clearInterval?: (id: ReturnType<typeof setInterval>) => void;\n}\n\n/**\n * Optional callbacks for long-lived attach connections (spec §4.5/§4.6).\n * When a connection sends {op:\"attach\",taskId} the socket is \"claimed\" by\n * the attach path and all subsequent frames on that socket are routed to\n * onAttachInbound rather than through the normal request/response handler.\n */\nexport interface ServerCallbacks {\n /** Normal request/response handler (required). */\n handler: Handler;\n /** Called once when a connection sends the {op:\"attach\",taskId} frame. */\n onAttach?: (conn: NetConn, frame: { op: \"attach\"; taskId: string }) => void;\n /** Called for every subsequent inbound frame on a claimed attach connection. */\n onAttachInbound?: (conn: NetConn, frame: AttachInbound) => void;\n /** Called when a claimed attach connection closes. */\n onAttachClose?: (conn: NetConn) => void;\n}\n\n/**\n * Red-team #2 (High): an unhandled server `error` (e.g. listen EADDRINUSE when\n * a second daemon races in) became an uncaughtException → process died →\n * launchd KeepAlive (no ThrottleInterval) tight-respawned = the crash-loop.\n * Default: log with timestamp and exit non-zero so launchd's ThrottleInterval\n * paces the restart instead of tight-looping. Tests inject a spy.\n */\nexport function defaultListenError(e: Error): void {\n process.stderr.write(`[squadrantd] ${new Date().toISOString()} server error: ${e.message}\\n`);\n process.exit(1);\n}\n\nexport function startServer(\n sockPath: string,\n handlerOrCallbacks: Handler | ServerCallbacks,\n onListenError: (e: Error) => void = defaultListenError,\n deps: ServerDeps = {},\n): Server {\n // Back-compat: accept a plain function as well as a ServerCallbacks object.\n const callbacks: ServerCallbacks =\n typeof handlerOrCallbacks === \"function\"\n ? { handler: handlerOrCallbacks }\n : handlerOrCallbacks;\n const { handler, onAttach, onAttachInbound, onAttachClose } = callbacks;\n const setIntervalFn = deps.setInterval ?? setInterval;\n const clearIntervalFn = deps.clearInterval ?? clearInterval;\n\n if (existsSync(sockPath)) {\n try { unlinkSync(sockPath); } catch { /* stale socket */ }\n }\n const server = createServer((conn) => {\n conn.setEncoding(\"utf-8\");\n let claimType: \"none\" | \"attach\" = \"none\";\n let keepaliveId: ReturnType<typeof setInterval> | undefined;\n\n // #87: reply with a structured error when a newline-terminated line fails to\n // parse as JSON, so the client gets a fast error instead of a silent drop.\n const dec = createDecoder((badLine) => {\n if (claimType !== \"attach\") {\n try {\n conn.write(encodeMsg({ ok: false, error: `malformed request: invalid JSON`, _v: PROTOCOL_VERSION }));\n } catch { /* conn already closed */ }\n }\n });\n\n conn.on(\"data\", async (chunk: string) => {\n for (const msg of dec.push(chunk)) {\n // If already claimed by attach, route all frames to the inbound handler.\n if (claimType === \"attach\") {\n onAttachInbound?.(conn, msg as AttachInbound);\n continue;\n }\n // Check for attach-claim frame BEFORE falling through to req/res.\n if (\n onAttach &&\n msg != null &&\n typeof msg === \"object\" &&\n (msg as any).op === \"attach\" &&\n typeof (msg as any).taskId === \"string\"\n ) {\n claimType = \"attach\";\n onAttach(conn, msg as { op: \"attach\"; taskId: string });\n // Start keepalive heartbeat for held-open attach connections (#94).\n keepaliveId = setIntervalFn(() => {\n try { conn.write(encodeFrame({ type: \"_keepalive\" })); } catch { /* conn closed */ }\n }, 10_000);\n continue;\n }\n // Normal request/response path.\n // #259: both writes are wrapped — a destroyed socket can throw synchronously\n // (write-after-end); that throw would escape the async data handler and become\n // an unhandled rejection, killing the daemon. Client-gone writes are silently\n // swallowed; the conn.on(\"error\") handler above covers the emitted error event.\n try {\n const reply = await handler(msg);\n try { conn.write(encodeMsg({ ok: true, reply, _v: PROTOCOL_VERSION })); } catch { /* client gone */ }\n } catch (e) {\n const errMsg = e instanceof Error ? e.message : String(e);\n try { conn.write(encodeMsg({ ok: false, error: errMsg, _v: PROTOCOL_VERSION })); } catch { /* client gone */ }\n }\n }\n });\n conn.on(\"error\", () => { /* client vanished; ignore */ });\n // #87: when the client half-closes (done sending) with bytes still in the\n // decoder buffer, the message had no newline terminator — send a fast error\n // instead of silently leaving the client to hit the 5s sendRequest timeout.\n conn.on(\"end\", () => {\n if (claimType !== \"attach\" && dec.remainder().trim()) {\n try {\n conn.write(encodeMsg({ ok: false, error: `malformed request: missing newline terminator`, _v: PROTOCOL_VERSION }));\n } catch { /* conn already closed */ }\n }\n });\n conn.on(\"close\", () => {\n if (keepaliveId !== undefined) clearIntervalFn(keepaliveId);\n if (claimType === \"attach\") onAttachClose?.(conn);\n });\n });\n server.on(\"error\", onListenError); // never let a server error become uncaughtException\n server.listen(sockPath);\n return server;\n}\n\n// #360: probe whether a live daemon owns this socket. Resolves true only when\n// a connection is accepted; false on ENOENT (no file) or ECONNREFUSED (stale\n// file / dead listener). Callers check this BEFORE startServer to avoid\n// unlink-then-bind stealing a live daemon's socket inode.\nexport function isDaemonSocketLive(sockPath: string, timeoutMs = 500): Promise<boolean> {\n return new Promise((resolve) => {\n if (!existsSync(sockPath)) { resolve(false); return; }\n const conn = createConnection(sockPath);\n const finish = (v: boolean) => { try { conn.destroy(); } catch { /* already gone */ } resolve(v); };\n const timer = setTimeout(() => finish(false), timeoutMs);\n conn.on(\"connect\", () => { clearTimeout(timer); finish(true); });\n conn.on(\"error\", () => { clearTimeout(timer); finish(false); });\n });\n}\n\nexport function sendRequest(sockPath: string, msg: unknown, timeoutMs = 5000): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const conn = createConnection(sockPath);\n const dec = createDecoder();\n const timer = setTimeout(() => {\n conn.destroy();\n reject(new Error(\"control plane unavailable: request timed out\"));\n }, timeoutMs);\n conn.setEncoding(\"utf-8\");\n conn.on(\"connect\", () => conn.write(encodeMsg({ ...(msg as Record<string, unknown>), _v: PROTOCOL_VERSION })));\n conn.on(\"data\", (chunk: string) => {\n for (const m of dec.push(chunk) as any[]) {\n clearTimeout(timer);\n conn.destroy();\n if (m._v !== undefined && m._v !== PROTOCOL_VERSION) {\n reject(new Error(`squadrantd protocol v${m._v}, this client expects v${PROTOCOL_VERSION} — upgrade squadrantd or this CLI`));\n } else if (m.ok) {\n resolve(m.reply);\n } else {\n reject(new Error(m.error));\n }\n return;\n }\n });\n conn.on(\"error\", () => {\n clearTimeout(timer);\n reject(new Error(\"control plane unavailable: cannot reach squadrantd socket\"));\n });\n });\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// Streaming-subscribe frames for `squadrant crew chat / attach` (spec §4.5).\n// Additive; existing request/response verbs untouched. Cooperates with #87.\n\nexport type AttachFrame =\n | { type: \"delta\"; taskId: string; text: string }\n | { type: \"turn-started\"; taskId: string }\n | { type: \"turn-completed\"; taskId: string }\n | { type: \"input-requested\"; taskId: string; requestId: number; question: string }\n | { type: \"approval-requested\"; taskId: string; requestId: number; question: string; kind: string }\n | { type: \"gate-promoted\"; taskId: string; gateId: string }\n | { type: \"reattached\"; taskId: string }\n | { type: \"closed\"; taskId: string; reason: string }\n | { type: \"_keepalive\" };\n\nexport type AttachInbound =\n | { op: \"attach\"; taskId: string }\n | { op: \"say\"; taskId: string; text: string }\n | { op: \"steer\"; taskId: string; text: string }\n | { op: \"interrupt\"; taskId: string }\n | { op: \"answer\"; taskId: string; requestId: number; payload: unknown };\n\nexport function encodeFrame(f: AttachFrame): string {\n return JSON.stringify(f) + \"\\n\";\n}\n\nexport function decodeFrames(wire: string): AttachFrame[] {\n const out: AttachFrame[] = [];\n for (const line of wire.split(\"\\n\")) {\n if (!line.trim()) continue;\n try {\n const parsed = JSON.parse(line) as AttachFrame;\n if (parsed.type === \"_keepalive\") continue; // discard keepalive frames (#94)\n out.push(parsed);\n } catch { /* skip malformed */ }\n }\n return out;\n}\n","// src/control/liveness.ts\n//\n// PURE service-health layer (no I/O, no clock) — the #77 foundation. Mirrors\n// watchdog.ts: every function derives a verdict from records + an explicit `now`\n// so it is fully unit-testable. All runtime probing (cmux reads for captain\n// presence) is gathered by the caller (squadrantd) and passed in already-resolved;\n// this module never touches cmux.\nimport type { TaskState, Mode, LivenessEntry } from \"@squadrant/shared\";\n\nexport type ComponentKind = \"captain\" | \"crew\" | \"command\";\n\n// alive = seen within the stale window (healthy)\n// stale = quiet past stale but not yet gone (degrading)\n// gone = dark past the gone window (treat as down — a FAULT)\n// stopped = intentionally offline (user closed the captain workspace) — NOT a\n// fault. Distinct from `gone` so the surface never red-alarms an\n// expected shutdown (#324/#323).\n// unknown = no signal / not applicable (never alarms)\nexport type HealthState = \"alive\" | \"stale\" | \"gone\" | \"stopped\" | \"unknown\";\n\nexport interface ComponentHealth {\n kind: ComponentKind;\n project: string;\n /** crew name / captain name / \"command\". */\n ref: string;\n state: HealthState;\n /** epoch ms of last evidence of life, or null when there is no timestamp\n * source (captain/command presence is a boolean, not a heartbeat). */\n lastSeenMs: number | null;\n /** human-facing context — e.g. a recovery command. */\n detail?: string;\n}\n\n// Crews legitimately idle for long (24h interactive budget), so the surface uses\n// generous windows — these only flag a genuinely dark crew, and #139 already\n// reaps provably-dead ones. Display-only; does not drive any state transition.\nexport const CREW_STALE_MS = 5 * 60_000;\nexport const CREW_GONE_MS = 30 * 60_000;\n\n/**\n * Pure. Classify a last-seen timestamp into a health state.\n * null → \"unknown\"\n * age <= staleMs → \"alive\" (boundary inclusive)\n * age <= goneMs → \"stale\" (boundary inclusive)\n * else → \"gone\"\n */\nexport function classifyHealth(\n lastSeenMs: number | null,\n now: number,\n staleMs: number,\n goneMs: number,\n): HealthState {\n if (lastSeenMs == null) return \"unknown\";\n const age = now - lastSeenMs;\n if (age <= staleMs) return \"alive\";\n if (age <= goneMs) return \"stale\";\n return \"gone\";\n}\n\nconst TERMINAL: ReadonlySet<TaskState> = new Set([\"done\", \"failed\", \"cancelled\"]);\n\n/** Minimal crew shape the projection needs (subset of TaskRecord). */\nexport interface CrewLiveness {\n id: string;\n name?: string;\n state: TaskState;\n lastHeartbeat: number;\n mode: Mode;\n /** Set once the crew's first turn is confirmed delivered (#466). Absent/undefined\n * means \"never confirmed\" — combined with heartbeatBudgetMs below to detect a\n * dropped first turn (mirrors daemon/reduce.ts's CREW UNDELIVERED watchdog). */\n firstTurnConfirmedAt?: number;\n /** Per-task stall threshold. Omitted when the caller doesn't have it (older\n * callers) — undelivered detection is skipped in that case, never a hard error. */\n heartbeatBudgetMs?: number;\n}\n\n/**\n * Pure. Project one project's component health from already-gathered inputs.\n * Emits: a captain row, a command row (only when applicable), and one row per\n * non-terminal crew.\n *\n * Captain liveness: prefers the registry-derived `captainState` (§4.1/§4.5 —\n * ground-truth from the LivenessRegistry) when supplied; falls back to the\n * legacy `captainStopped` tri-state for callers that haven't migrated:\n * captainStopped === false → captain surface was found on last delivery tick → ALIVE\n * captainStopped === true → surface gone for 3+ consecutive ticks → STOPPED\n * (intentional close — its crews are reaped and\n * delivery is paused; NOT a fault — #324/#323)\n * captainStopped === null → not yet checked / cmux unreachable → UNKNOWN\n */\nexport function projectHealth(input: {\n project: string;\n now: number;\n captainName: string;\n /** Delivery-loop captain surface state. See docs above. Ignored when `captainState` is supplied. */\n captainStopped: boolean | null;\n /** Registry-derived captain state (Task 4+). Wins over `captainStopped` when present. */\n captainState?: HealthState;\n /** true/false when a command workspace is expected; null = not applicable. */\n commandPresent: boolean | null;\n crews: CrewLiveness[];\n /** #579/#484 Gap 3: this project's captain-delivery deferral state (from\n * CaptainDelivery.stats() — see delivery/captain-delivery.ts), surfaced as\n * `detail` on the captain row so `squadrant doctor` / `squadrant status\n * --detailed` show a stuck delivery with zero extra configuration (no\n * Telegram, no mute state to fight) — a pull-based fallback that can never\n * be silenced, unlike the push alerts in delivery-loop.ts. */\n captainDeferral?: { stuck: boolean; maxDeferCount: number };\n}): ComponentHealth[] {\n const { project, now, captainName, captainStopped, commandPresent, crews } = input;\n const out: ComponentHealth[] = [];\n\n // ── captain ────────────────────────────────────────────────────────────\n const captainState: HealthState = input.captainState ?? (\n captainStopped === true ? \"stopped\" :\n captainStopped === false ? \"alive\" :\n \"unknown\"\n );\n const deferral = input.captainDeferral;\n out.push({\n kind: \"captain\",\n project,\n ref: captainName,\n state: captainState,\n lastSeenMs: null,\n detail:\n captainState === \"stopped\" ? \"captain workspace closed — crews reaped; delivery paused\" :\n captainState === \"gone\" ? \"captain process died (crash) — crews reaped\" :\n deferral?.stuck ? `⚠️ delivery stuck (${deferral.maxDeferCount}+ retries) — draft/ghost text blocking captain pane; input never touched, delivers automatically once cleared` :\n undefined,\n });\n\n // ── command (on-demand; only surfaced when applicable) ───────────────────\n if (commandPresent !== null) {\n out.push({\n kind: \"command\",\n project,\n ref: \"command\",\n state: presence(commandPresent),\n lastSeenMs: null,\n });\n }\n\n // ── crews (one row per non-terminal crew) ────────────────────────────────\n for (const c of crews) {\n if (TERMINAL.has(c.state)) continue;\n // #466/B2: promote the CREW UNDELIVERED watchdog condition (daemon/reduce.ts)\n // to a first-class, grep-able detail so it doesn't wait for the heartbeat\n // window to age the row into stale/gone before it's noticeable.\n const undelivered =\n c.mode === \"interactive\" &&\n !c.firstTurnConfirmedAt &&\n c.heartbeatBudgetMs != null &&\n now - c.lastHeartbeat > c.heartbeatBudgetMs;\n out.push({\n kind: \"crew\",\n project,\n ref: c.name ?? c.id.slice(0, 8),\n state: classifyHealth(c.lastHeartbeat, now, CREW_STALE_MS, CREW_GONE_MS),\n lastSeenMs: c.lastHeartbeat,\n detail: undelivered ? `undelivered (${c.state})` : c.state,\n });\n }\n\n return out;\n}\n\nfunction presence(p: boolean | null): HealthState {\n if (p === null) return \"unknown\";\n return p ? \"alive\" : \"gone\";\n}\n\n/** Human-friendly age of the last-seen timestamp, or em-dash when there is none. */\nexport function ageText(lastSeenMs: number | null, now: number): string {\n if (lastSeenMs == null) return \"—\";\n const s = Math.max(0, Math.round((now - lastSeenMs) / 1000));\n if (s < 60) return `${s}s ago`;\n const m = Math.round(s / 60);\n if (m < 60) return `${m}m ago`;\n return `${Math.round(m / 60)}h ago`;\n}\n\n/**\n * Pure. Return the heal command string for a component that needs remediation,\n * or null when no action is needed (or when no heal verb exists for this kind).\n */\nexport function healCmdFor(c: ComponentHealth): string | null {\n // No heal verb for captain/crew — daemon-direct delivery handles recovery automatically.\n return null;\n}\n\n/** Per-project relay health — REMOVED (#332). No longer tracked by daemon. */\nexport type RelayHealth = never;\n\n/**\n * Pure. Derive a captain HealthState from its registry entry.\n * First match wins — order matters (a clean close reads `stopped` even though\n * its pid also dies).\n */\nexport function deriveCaptainState(e: LivenessEntry | undefined): HealthState {\n if (!e) return \"unknown\";\n if (e.lastState === \"end\") return \"stopped\"; // clean close — magenta, not a fault (#324)\n if (!e.pidAlive) return \"gone\"; // pid dead, record present → crash\n return \"alive\";\n}\n\n/**\n * Pure. Reconcile an incoming signal against the prior entry.\n * Precedence: runtime ≥ agent (authoritative for presence/intent) > scan\n * (liveness-only). A `scan` updates `pidAlive` but never presence/intent, and\n * never resurrects a dead pid — only a newer runtime/agent open (greater\n * startedAt) does.\n */\nexport function reconcileLiveness(\n prev: LivenessEntry | undefined,\n next: LivenessEntry,\n): LivenessEntry {\n if (!prev) return next;\n if (next.source === \"scan\") {\n // liveness-only: adopt pidAlive (and lastSeenAt) onto prev; keep presence/intent.\n // A stale scan (older than prev, by lastSeenAt — scans of the same session\n // share startedAt, so recency must be judged by lastSeenAt) must not flip a\n // dead pid back to alive.\n const pidAlive = next.lastSeenAt >= prev.lastSeenAt ? next.pidAlive : prev.pidAlive;\n return { ...prev, pidAlive, lastSeenAt: Math.max(prev.lastSeenAt, next.lastSeenAt) };\n }\n // runtime/agent authoritative. A newer open (or any end) wins; a stale one is ignored.\n if (next.startedAt >= prev.startedAt || next.lastState === \"end\") return next;\n // #565: prev is already dead (stopped/gone) and next reports a live pid — a\n // live process outranks a startedAt comparison, otherwise a captain that\n // comes back can never be re-adopted once wrongly marked dead. Does not\n // apply when prev is still alive: an older-but-live duplicate must not\n // override the currently-tracked live session (#527).\n const prevAlive = prev.lastState === \"start\" && prev.pidAlive;\n if (!prevAlive && next.lastState === \"start\" && next.pidAlive) return next;\n return prev;\n}\n","// src/control/store.ts\nimport {\n mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync, existsSync,\n rmSync, statSync,\n} from \"node:fs\";\nimport { join, resolve, sep } from \"node:path\";\nimport type { TaskRecord } from \"@squadrant/shared\";\n\nexport interface Store {\n put(rec: TaskRecord): void;\n get(project: string, id: string): TaskRecord | undefined;\n list(project: string): TaskRecord[];\n listAll(): TaskRecord[];\n quarantine(project: string, id: string): void;\n delete(project: string, id: string): void;\n}\n\n/**\n * SECURITY (red-team #1, Critical): `project`/`id` arrive unsanitized from the\n * socket (dispatch + seed) and a crafted value (`..`, `/`, absolute, NUL) would\n * let a confused-deputy read/write arbitrary files as the user. A `project`/`id`\n * must be a single safe path segment — no separators, traversal, NUL, or dot\n * dirs. Enforced at the one chokepoint every fs op funnels through.\n */\nfunction safeSegment(kind: \"project\" | \"id\", s: unknown): string {\n if (typeof s !== \"string\" || s.length === 0) {\n throw new Error(`invalid ${kind}: must be a non-empty string`);\n }\n if (s.includes(\"\\0\")) throw new Error(`invalid ${kind}: NUL byte not allowed`);\n if (s === \".\" || s === \"..\" || /[/\\\\]/.test(s)) {\n throw new Error(`invalid ${kind}: '${s}' — path separators/traversal not allowed`);\n }\n return s;\n}\n\nexport function createStore(root: string): Store {\n const rootResolved = resolve(root);\n\n // Defense in depth: even after segment validation, never let a resolved\n // path escape the state root.\n const assertUnderRoot = (target: string): string => {\n const r = resolve(target);\n if (r !== rootResolved && !r.startsWith(rootResolved + sep)) {\n throw new Error(`path escapes state root: ${target}`);\n }\n return target;\n };\n\n const projDir = (p: string) => assertUnderRoot(join(root, safeSegment(\"project\", p)));\n const taskFile = (p: string, id: string) =>\n assertUnderRoot(join(projDir(p), `${safeSegment(\"id\", id)}.json`));\n\n return {\n put(rec) {\n mkdirSync(projDir(rec.project), { recursive: true });\n const dest = taskFile(rec.project, rec.id);\n const tmp = `${dest}.tmp`;\n writeFileSync(tmp, JSON.stringify(rec, null, 2));\n renameSync(tmp, dest); // atomic replace\n },\n get(project, id) {\n const f = taskFile(project, id);\n if (!existsSync(f)) return undefined;\n try {\n return JSON.parse(readFileSync(f, \"utf-8\")) as TaskRecord;\n } catch {\n return undefined; // corrupt file: caller handles (Task 6)\n }\n },\n list(project) {\n const d = projDir(project);\n if (!existsSync(d)) return [];\n return readdirSync(d)\n .filter((n) => n.endsWith(\".json\"))\n .map((n) => {\n try { return JSON.parse(readFileSync(join(d, n), \"utf-8\")) as TaskRecord; }\n catch { return undefined; }\n })\n .filter((r): r is TaskRecord => r !== undefined);\n },\n listAll() {\n if (!existsSync(root)) return [];\n return readdirSync(root)\n .filter((p) => { try { return statSync(join(root, p)).isDirectory(); } catch { return false; } })\n .flatMap((p) => this.list(p));\n },\n quarantine(project, id) {\n const f = taskFile(project, id);\n // suffix prevents clobber across process restarts\n if (existsSync(f)) renameSync(f, `${f}.corrupt.${Date.now()}`);\n },\n delete(project, id) {\n const f = taskFile(project, id);\n if (existsSync(f)) rmSync(f);\n },\n };\n}\n","// @squadrant/core — driver-agnostic daemon / control-plane core.\nexport * from \"./daemon/reduce.js\";\nexport * from \"./mailbox.js\";\nexport * from \"./protocol.js\";\nexport * from \"./state-machine.js\";\nexport * from \"./liveness.js\";\nexport * from \"./watchdog.js\";\nexport * from \"./store.js\";\nexport * from \"./snapshot.js\";\nexport * from \"./launchd.js\";\nexport * from \"./crew-pane-reader.js\";\nexport * from \"./interfaces.js\";\nexport * from \"./gate.js\";\nexport * from \"./daemon/context.js\";\nexport * from \"./daemon/attach.js\";\nexport * from \"./daemon/start.js\";\nexport * from \"./daemon/delivery-loop.js\";\nexport * from \"./daemon/interactive-probe.js\";\nexport * from \"./delivery/captain-delivery.js\";\nexport * from \"./delivery/defer-delivery.js\";\nexport * from \"./session-freshness.js\";\nexport * from \"./crew-protocol.js\";\nexport * from \"./crew-lifecycle.js\";\nexport * from \"./telegram/index.js\";\nexport * from \"./crew-routing.js\";\nexport * from \"./restart-daemon.js\";\nexport * from \"./group-dispatch.js\";\nexport * from \"./launch-workspace.js\";\nexport * from \"./side-session.js\";\nexport * from \"./crew-spawn.js\";\nexport * from \"./lifecycle-source.js\";\n","// src/control/launchd.ts\nimport { execFileSync } from \"node:child_process\";\nimport { mkdirSync, writeFileSync, readFileSync, existsSync, openSync, writeSync, closeSync, unlinkSync, constants } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport const LABEL = \"com.squadrant.daemon\";\n\nexport function plistPath(): string {\n return join(homedir(), \"Library\", \"LaunchAgents\", `${LABEL}.plist`);\n}\n\n/**\n * Canonical path to the compiled daemon entrypoint, resolved relative to THIS\n * module (squadrantd.js is a sibling of the bundled entry in <dist>/). This is\n * the single source of truth — callers must NOT recompute it (a hardcoded\n * ~/.config/squadrant/dist path crash-loops the agent with MODULE_NOT_FOUND\n * because runtime-sync never mirrors compiled output there).\n */\nexport function daemonEntryPath(): string {\n const p = join(dirname(fileURLToPath(import.meta.url)), \"squadrantd.js\");\n if (!existsSync(p)) {\n throw new Error(\n `daemonEntryPath: compiled entry not found at '${p}'; ` +\n `run 'npm run build' — a src-tree or missing path in the launchd plist causes a MODULE_NOT_FOUND crash-loop (#259)`,\n );\n }\n return p;\n}\n\nfunction xmlEscape(s: string): string {\n return s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\n}\n\n/**\n * Strip per-shell ephemeral PATH entries (Claude Code plugin cache dirs) and\n * dedupe so the plist content is stable across squadrant invocations from\n * different shells. Without this, a captain shell (PATH includes\n * ~/.claude/plugins/cache/* bin dirs) vs a fresh login shell would each\n * rewrite the plist and kickstart -k the daemon, killing in-flight tasks\n * (incident 2026-05-21, observations 8704/8707/8711).\n */\nexport function sanitizePathForPlist(path: string): string {\n const seen = new Set<string>();\n const stable: string[] = [];\n for (const p of path.split(\":\")) {\n if (!p) continue;\n if (p.includes(\"/.claude/plugins/\")) continue;\n if (seen.has(p)) continue;\n seen.add(p);\n stable.push(p);\n }\n return stable.join(\":\");\n}\n\nexport const AGENT_BINS = [\"cmux\", \"claude\", \"opencode\", \"codex\", \"gemini\", \"node\"];\n\n/**\n * Resolve absolute directories for known agent + tool binaries via `which`, so\n * the launchd daemon's PATH includes them regardless of the install-time shell.\n * Missing binaries are skipped silently.\n */\nexport function resolveAgentBinDirs(): string[] {\n const dirs: string[] = [];\n for (const bin of AGENT_BINS) {\n try {\n const out = execFileSync(\"which\", [bin], { encoding: \"utf-8\", stdio: [\"ignore\", \"pipe\", \"ignore\"] });\n const resolved = out.trim();\n if (resolved) dirs.push(dirname(resolved));\n } catch {\n // binary not found on this machine — skip\n }\n }\n const seen = new Set<string>();\n return dirs.filter(d => {\n if (seen.has(d)) return false;\n seen.add(d);\n return true;\n });\n}\n\n/**\n * Compose a stable daemon PATH by prepending resolved agent bin dirs to the\n * sanitized install-shell PATH. Agent dirs take priority (prepended) and are\n * deduped against the sanitized entries so the output is deterministic.\n */\nexport function buildDaemonPath(shellPath: string): string {\n const agentDirs = resolveAgentBinDirs();\n const sanitized = sanitizePathForPlist(shellPath);\n if (agentDirs.length === 0) return sanitized;\n const parts = [...agentDirs, ...sanitized.split(\":\")];\n const seen = new Set<string>();\n return parts.filter(p => {\n if (!p || seen.has(p)) return false;\n seen.add(p);\n return true;\n }).join(\":\");\n}\n\n/**\n * Red-team #3 (High): launchd starts the daemon with a minimal PATH that does\n * NOT include where `claude`/`codex`/`opencode` live (nvm/cmux dirs), so every\n * headless `spawn` failed `ENOENT` in the real deployment (shell tests + fake\n * spawn hid it). We bake the installing process's PATH into the plist so the\n * daemon and its spawned crew children resolve the provider binaries.\n */\nexport function renderPlist(nodeBin: string, daemonEntry: string, pathEnv = \"\"): string {\n const logPath = join(homedir(), \".config\", \"squadrant\", \"squadrantd.log\");\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key><string>${LABEL}</string>\n <key>ProgramArguments</key>\n <array><string>${xmlEscape(nodeBin)}</string><string>${xmlEscape(daemonEntry)}</string></array>\n <key>EnvironmentVariables</key>\n <dict><key>PATH</key><string>${xmlEscape(pathEnv)}</string></dict>\n <key>RunAtLoad</key><true/>\n <key>KeepAlive</key><true/>\n <key>ThrottleInterval</key><integer>10</integer>\n <key>StandardErrorPath</key><string>${xmlEscape(logPath)}</string>\n <key>StandardOutPath</key><string>${xmlEscape(logPath)}</string>\n</dict>\n</plist>\n`;\n}\n\n/**\n * Semantic fingerprint of the <array> block inside the rendered plist. Used by\n * ensureDaemon to distinguish program-argument changes (merit a full restart)\n * from PATH-only changes (write updated plist, don't bounce the daemon).\n */\nexport function programArgsBlock(nodeBin: string, daemonEntry: string): string {\n return `<array><string>${xmlEscape(nodeBin)}</string><string>${xmlEscape(daemonEntry)}</string></array>`;\n}\n\n/**\n * Pure: which kickstart argv to use. `-k` (kill-then-restart) ONLY when the\n * plist changed. A plain `kickstart` starts a down daemon and is a no-op for a\n * healthy one — so a routine CLI call never bounces a running daemon (this was\n * a real bug: ensureDaemon ran on every `squadrant` invocation and `kickstart -k`\n * killed+restarted the daemon each time, orphaning in-flight headless crew).\n */\nexport function kickstartArgv(target: string, plistChanged: boolean): string[] {\n return plistChanged ? [\"kickstart\", \"-k\", target] : [\"kickstart\", target];\n}\n\n// In-process dedup: JS is single-threaded and ensureDaemon is synchronous, so\n// true re-entrancy is impossible; this flag prevents sequential re-calls within\n// the same process (e.g. index.ts + crew-control.ts) from re-running the\n// bootout/bootstrap pair needlessly.\nlet restartInFlight = false;\n\n/** @internal — reset only in tests; never call from production code */\nexport function _resetRestartInFlightForTest(): void {\n restartInFlight = false;\n}\n\nexport function daemonLockPath(): string {\n return join(homedir(), \".config\", \"squadrant\", \"daemon.lock\");\n}\n\n/**\n * Acquire a cross-process filesystem lock at ~/.config/squadrant/daemon.lock.\n * Uses O_EXCL for atomic, race-free creation. Cleans up stale locks (dead PID)\n * before the acquisition loop. Retries with a ~50 ms synchronous sleep up to\n * 20 times (~1 s total) before giving up.\n * Returns true on success, false if another live process holds the lock.\n */\nexport function tryAcquireDaemonLock(): boolean {\n const lp = daemonLockPath();\n\n // Stale-lock cleanup: if the owning PID is no longer alive, remove the file\n // so the next O_EXCL attempt succeeds.\n if (existsSync(lp)) {\n try {\n const pid = parseInt(readFileSync(lp, \"utf-8\").trim(), 10);\n if (!Number.isFinite(pid) || pid <= 0) {\n unlinkSync(lp);\n } else {\n try { process.kill(pid, 0); }\n catch { unlinkSync(lp); } // ESRCH → process dead, steal the lock\n }\n } catch { /* read/parse/unlink error — fall through to O_EXCL attempt */ }\n }\n\n // Atomic acquisition: O_EXCL guarantees only one process creates the file.\n for (let i = 0; i < 20; i++) {\n try {\n const fd = openSync(lp, constants.O_EXCL | constants.O_CREAT | constants.O_WRONLY);\n writeSync(fd, String(process.pid));\n closeSync(fd);\n return true;\n } catch {\n if (i < 19) {\n // Synchronous sleep: gives the lock-holder time to finish and release.\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50);\n }\n }\n }\n return false; // another live process held the lock for > ~1 s — skip restart\n}\n\n/** Release the lock written by tryAcquireDaemonLock. */\nexport function releaseDaemonLock(): void {\n try { unlinkSync(daemonLockPath()); } catch { /* already cleaned up */ }\n}\n\n/**\n * Idempotent & cheap. Never throws fatally. Writes/reloads the plist ONLY when\n * its content actually changed; Distinguishes program-argument drift (warrants\n * a full restart via bootout + bootstrap + kickstart) from PATH-only drift\n * (write the plist for the next natural restart but never bounce a healthy\n * daemon). Uses plain `kickstart` (never -k) to avoid the race between -k and\n * bootout's exit handler that produced exit-113 \"service not loaded\" errors.\n * The daemon entry is resolved internally (see daemonEntryPath) so no caller\n * can pass a wrong path.\n *\n * Concurrency guards:\n * - restartInFlight flag: prevents sequential re-calls within this process.\n * - tryAcquireDaemonLock: serialises concurrent SEPARATE squadrant processes\n * via a filesystem lock so only one runs bootout/bootstrap at a time.\n */\nexport function ensureDaemon(nodeBin: string = process.execPath): void {\n if (restartInFlight) return;\n restartInFlight = true;\n\n if (!tryAcquireDaemonLock()) {\n // Another process is handling the restart; it will be done by the time the\n // CLI tries to reach the daemon socket.\n return;\n }\n\n try {\n const p = plistPath();\n const entry = daemonEntryPath();\n const desired = renderPlist(nodeBin, entry, buildDaemonPath(process.env.PATH ?? \"\"));\n const current = existsSync(p) ? readFileSync(p, \"utf-8\") : null;\n const uid = process.getuid?.() ?? 0;\n const target = `gui/${uid}/${LABEL}`;\n\n const changed = current !== desired;\n // Semantic comparison: was the program-arg block itself different (not just\n // PATH)? Program-arg changes are rare (rebuild/reinstall) and merit a full\n // bootout+reload; PATH varies across terminals so it must NOT trigger a\n // bounce (would orphan in-flight RPCs).\n const programChanged = current !== null && changed\n && !current.includes(programArgsBlock(nodeBin, entry));\n\n if (changed) {\n mkdirSync(dirname(p), { recursive: true });\n writeFileSync(p, desired);\n }\n\n if (programChanged) {\n // unload the old instance so bootstrap picks up the new program args\n try { execFileSync(\"launchctl\", [\"bootout\", target], { stdio: \"ignore\" }); }\n catch { /* not loaded */ }\n }\n\n try { execFileSync(\"launchctl\", [\"bootstrap\", `gui/${uid}`, p], { stdio: \"ignore\" }); }\n catch { /* already bootstrapped */ }\n\n // Plain kickstart (never -k): no-op on a healthy daemon, starts one that\n // was booted-out above or that stopped for other reasons. -k is avoided\n // because it races with bootout's exit handler and produces exit-113 when\n // the service hasn't finished unloading.\n execFileSync(\"launchctl\", [\"kickstart\", target], { stdio: \"ignore\" });\n } catch (e) {\n // daemon ensure is best-effort (still don't throw); CLI fails loud on socket miss\n process.stderr.write(`[squadrant] warn: ensureDaemon failed (${e instanceof Error ? e.message : e})\\n`);\n } finally {\n releaseDaemonLock();\n }\n}\n","import { loadConfig } from \"@squadrant/shared\";\nimport type { RuntimeDriver, SquadrantConfig, TaskRecord } from \"@squadrant/shared\";\nimport type { DirectCmuxReader } from \"./interfaces.js\";\n\nconst TAIL_LINES = 25;\n\n// MUST match `titleFor` in src/commands/crew.ts — the crew tab title convention\n// the daemon uses to find a crew's pane (🔧 <project>:<name>).\nexport function crewPaneTitle(project: string, name: string): string {\n return `🔧 ${project}:${name}`;\n}\n\nexport type SurfaceLiveness = \"alive\" | \"gone\" | \"unknown\";\n\n/**\n * Pure: decide an interactive crew's surface liveness from a resolved surface\n * list (#139). Three-valued so a transient cmux outage never false-reaps a live\n * crew — \"gone\" means PROVABLY absent, not \"couldn't tell\":\n * - wantTitle null (crew has no name) → \"unknown\"\n * - surfaceTitles null (could not enumerate) → \"unknown\"\n * - title present in the list → \"alive\"\n * - title absent from an enumerated list → \"gone\"\n */\nexport function surfaceVerdict(surfaceTitles: string[] | null, wantTitle: string | null): SurfaceLiveness {\n if (!wantTitle) return \"unknown\";\n if (surfaceTitles == null) return \"unknown\";\n return surfaceTitles.includes(wantTitle) ? \"alive\" : \"gone\";\n}\n\n/**\n * I/O: enumerate the captain workspace's surface titles for a crew's project.\n * Returns null on ANY failure — surfaceVerdict maps null → \"unknown\" so we never\n * reap on an inconclusive probe. Never throws.\n */\nasync function listCaptainSurfaceTitles(\n rec: TaskRecord,\n makeRuntime: (project: string, config: SquadrantConfig) => RuntimeDriver | null,\n): Promise<string[] | null> {\n try {\n const config = loadConfig();\n const proj = config.projects[rec.project];\n if (!proj) return null;\n const runtime = makeRuntime(rec.project, config);\n if (!runtime) return null;\n const captain = await runtime.status(proj.captainName);\n if (!captain) return null;\n const surfaces = await runtime.listSurfaces(captain.id);\n return surfaces.map((s) => s.title ?? \"\");\n } catch {\n return null;\n }\n}\n\n/**\n * Build the daemon's interactive surface-liveness probe (#139 backstop).\n * @param makeRuntime Factory provided by the host (root package); omit for tests.\n */\nexport function createSurfaceLivenessProbe(\n makeRuntime?: (project: string, config: SquadrantConfig) => RuntimeDriver | null,\n): (rec: TaskRecord) => Promise<SurfaceLiveness> {\n return async (rec) => {\n if (rec.mode !== \"interactive\" || !rec.name) return \"unknown\";\n if (!makeRuntime) return \"unknown\";\n const titles = await listCaptainSurfaceTitles(rec, makeRuntime);\n return surfaceVerdict(titles, crewPaneTitle(rec.project, rec.name));\n };\n}\n\n/**\n * Build the daemon's best-effort crew-pane reader (Phase 2b).\n * @param makeRuntime Factory provided by the host; required for real pane reads.\n */\nexport function createCrewPaneReader(\n makeRuntime?: (project: string, config: SquadrantConfig) => RuntimeDriver | null,\n): (rec: TaskRecord) => Promise<string | null> {\n return async (rec) => {\n try {\n if (!rec.name || !makeRuntime) return null;\n const config = loadConfig();\n const proj = config.projects[rec.project];\n if (!proj) return null;\n const runtime = makeRuntime(rec.project, config);\n if (!runtime) return null;\n const captain = await runtime.status(proj.captainName);\n if (!captain) return null;\n const surfaces = await runtime.listSurfaces(captain.id);\n const want = crewPaneTitle(rec.project, rec.name);\n const pane = surfaces.find((s) => s.title === want);\n if (!pane) return null;\n const screen = await runtime.readPaneScreen(pane);\n if (!screen) return null;\n return screen.split(/\\r?\\n/).slice(-TAIL_LINES).join(\"\\n\");\n } catch {\n return null;\n }\n };\n}\n\n/**\n * Build a direct surface-liveness probe for daemon-direct mode (#332).\n * Uses DirectCmuxReader (seam interface implemented by DaemonCmux in root).\n */\nexport function createDirectSurfaceLivenessProbe(\n cmux: DirectCmuxReader,\n getCaptainTitle: (project: string) => string,\n): (rec: TaskRecord) => Promise<SurfaceLiveness> {\n return async (rec) => {\n try {\n if (rec.mode !== \"interactive\" || !rec.name) return \"unknown\";\n const wsId = await cmux.findWorkspaceId(getCaptainTitle(rec.project));\n if (!wsId) return \"unknown\";\n const surfaces = await cmux.listSurfaces(wsId);\n if (surfaces.length === 0) return \"unknown\";\n return surfaceVerdict(\n surfaces.map((s) => s.title ?? \"\"),\n crewPaneTitle(rec.project, rec.name),\n );\n } catch {\n return \"unknown\";\n }\n };\n}\n\n/**\n * Build a direct crew-pane reader for daemon-direct mode (#332).\n * Uses DirectCmuxReader (seam interface implemented by DaemonCmux in root).\n */\nexport function createDirectCrewPaneReader(\n cmux: DirectCmuxReader,\n getCaptainTitle: (project: string) => string,\n): (rec: TaskRecord) => Promise<string | null> {\n return async (rec) => {\n try {\n if (!rec.name) return null;\n const wsId = await cmux.findWorkspaceId(getCaptainTitle(rec.project));\n if (!wsId) return null;\n const surfaces = await cmux.listSurfaces(wsId);\n const want = crewPaneTitle(rec.project, rec.name);\n const pane = surfaces.find((s) => s.title === want);\n if (!pane) return null;\n const screen = await cmux.readPaneScreen(pane);\n if (!screen) return null;\n return screen.split(/\\r?\\n/).slice(-TAIL_LINES).join(\"\\n\");\n } catch {\n return null;\n }\n };\n}\n","// src/control/codex/gate.ts\n// Pure helpers for the interactive-codex HITL gate primitive (spec §4.9).\nimport type { Gate } from \"@squadrant/shared\";\n\nexport function makeGate(opts: {\n taskId: string;\n kind: \"input\" | \"approval\";\n question: string;\n now: number;\n mkId: () => string;\n}): Gate {\n return {\n gateId: opts.mkId(),\n taskId: opts.taskId,\n kind: opts.kind,\n question: opts.question,\n state: \"pending\",\n createdAt: opts.now,\n };\n}\n\nexport function resolveGate(g: Gate, by: { resolvedBy: string; resolution: unknown }): Gate {\n return { ...g, state: \"resolved\", resolvedBy: by.resolvedBy, resolution: by.resolution };\n}\n\nexport function timeoutGate(g: Gate): Gate {\n return { ...g, state: \"timeout\" };\n}\n","// src/control/daemon/context.ts\n// SquadrantdOpts, defaultIsPidAlive, DaemonContext, and buildContext.\n// Kept here (not in squadrantd.ts) so daemon/* modules can import this file\n// without creating a circular dependency on the host entrypoint.\n// squadrantd.ts re-exports SquadrantdOpts and defaultIsPidAlive for backward compat.\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { spawn as realSpawn } from \"node:child_process\";\nimport { writeFileSync, mkdirSync } from \"node:fs\";\nimport { createStore } from \"../store.js\";\nimport { createDaemon } from \"./reduce.js\";\nimport { loadConfig } from \"@squadrant/shared\";\nimport type { TaskRecord, ControlEvent, Gate, AutoConfigResult } from \"@squadrant/shared\";\nimport type { Socket } from \"node:net\";\nimport type { PaneRef } from \"@squadrant/shared\";\nimport type { AgentDriver, OpencodeBridge, CmuxEventsBridge, DaemonSurfaceDriver } from \"../interfaces.js\";\nimport type { TelegramBridge } from \"../telegram/bridge.js\";\nimport type { AttachFrame } from \"../protocol.js\";\nimport type { LifecycleSource } from \"../lifecycle-source.js\";\nimport { LivenessRegistry } from \"./liveness-registry.js\";\n\n// ── Public injectable options (equivalent of old squadrantd.ts SquadrantdOpts) ───\n\nexport interface SquadrantdOpts {\n stateRoot?: string;\n sockPath?: string;\n sweepMs?: number; // 0 disables the interval (tests)\n isPidAlive?: (pid: number) => boolean;\n isSurfaceAlive?: (rec: TaskRecord) => Promise<\"alive\" | \"gone\" | \"unknown\">;\n spawn?: typeof realSpawn;\n /**\n * Push-notification hook (#109). Defaults to appending a structured event\n * to the mailbox file at <stateRoot>/inbox/<project>.log; an injector\n * process inside the captain workspace tails the file and delivers entries\n * to the captain pane. Tests inject a fake to assert call shape.\n */\n notify?: (args: {\n project: string;\n message: string;\n record: TaskRecord;\n event: ControlEvent;\n }) => Promise<void> | void;\n /** Background rotation timer interval (ms). 0 disables. Default 60_000. */\n rotationIntervalMs?: number;\n /** Mailbox rotation thresholds (size/age/retention). */\n mailboxConfig?: {\n maxBytes?: number;\n maxAgeMs?: number;\n keepCount?: number;\n };\n /** Inject a fake driver for tests. Defaults to a real CodexInteractiveDriver. */\n codexDriver?: AgentDriver;\n /** Inject a fake headless launcher for tests to avoid real process spawns. */\n launchHeadless?: (rec: TaskRecord) => Promise<void>;\n /** Override which projects appear in the Tier 2 per-project snapshot. */\n registeredProjects?: string[];\n /** Inject a fake opencode SSE bridge for tests. */\n opencodeBridge?: OpencodeBridge;\n /** B1: inject a fake cmux events bridge for tests. */\n cmuxEventsBridge?: CmuxEventsBridge;\n /** Opt-in Telegram bridge (#65). Inject a fake for tests; in production the host\n * builds the real one only when config.telegram is present (and not under vitest,\n * since pushLifecycle is composed onto notify and would hit the network). */\n telegramBridge?: TelegramBridge;\n /** B4: registered LifecycleSource instances (cmux-store/native-hook/codex-appserver),\n * for aggregating per-source health into the snapshot. Empty in tests unless injected. */\n lifecycleSources?: LifecycleSource[];\n /** Inject a fake surface driver for testing daemon-direct delivery. */\n daemonCmux?: DaemonSurfaceDriver;\n /** Factory for constructing the surface driver in production. */\n makeDaemonCmux?: () => DaemonSurfaceDriver;\n /** Injected captain-surface mapping (project → PaneRef) for tests. */\n captainSurfaces?: Record<string, PaneRef>;\n /** #348: override the cmux socket auto-config re-check. */\n runCmuxAutoConfig?: () => Promise<AutoConfigResult>;\n /** #466 self-heal: re-deliver a crew's first turn when the daemon detects it\n * never landed. Production wiring lives in squadrantd.ts (host); inject a\n * fake for tests. See DaemonDeps.resendFirstTurn (daemon/reduce.ts) for the\n * full contract. */\n resendFirstTurn?: (rec: TaskRecord) => Promise<{ delivered: boolean }>;\n /** #579/#484 Gap 1: config-free, out-of-band fault-alert channel — routed\n * through the notifier plugin slot (@squadrant/workspaces' NotifierRegistry,\n * cmux by default) rather than hardwired to Telegram, so it works for every\n * install (most have no Telegram configured) and for any future notifier\n * provider. core can't import @squadrant/workspaces (one-way DAG), so the\n * host (squadrantd.ts) builds the real implementation and injects it here —\n * mirrors how telegramBridge is wired. Must never throw/block; best-effort. */\n notifyFault?: (project: string, text: string) => Promise<void> | void;\n}\n\nexport function defaultIsPidAlive(pid: number): boolean {\n try { process.kill(pid, 0); return true; }\n catch (e: any) { return e?.code === \"EPERM\"; } // EPERM = alive but not ours; ESRCH = dead\n}\n\n// ── Shared state bag ──────────────────────────────────────────────────────────\n\n/** All shared mutable state for the running daemon. Most fields are set in\n * buildContext; late-bound fields (d, notify, broadcast, etc.) are assigned\n * by start.ts after building the daemon and factories, before any event fires. */\nexport interface DaemonContext {\n opts: SquadrantdOpts;\n stateRoot: string;\n sockPath: string;\n store: ReturnType<typeof createStore>;\n bootedAt: number;\n /** Mutable box so sweep timer can update lastSweepAt without a closure rebind. */\n lastSweepAt: { value: number | null };\n taskTimeoutMs: number | undefined;\n isPidAlive: (pid: number) => boolean;\n spawn: typeof realSpawn;\n resultsDir: string;\n writeResult: (id: string, payload: string) => string;\n log: (m: string) => void;\n /** Per-task live attach connections (spec §4.5/§4.6). */\n attachConns: Map<string, Set<Socket>>;\n /** Tasks being launched headlessly with no pid yet (#259). */\n inFlightHeadlessIds: Set<string>;\n /** Cancel handles for in-flight headless runs. */\n activeHeadlessKills: Set<() => void>;\n /** Ground-truth captain liveness — persisted, survives daemon restart (§4.1/§5.3). */\n livenessRegistry: LivenessRegistry;\n\n // ── Late-bound: assigned by start.ts before any timer/server fires ──────────\n\n /** Resolved daemon instance. */\n d: ReturnType<typeof createDaemon>;\n /** Resolved notify function. */\n notify: (args: { project: string; message: string; record: TaskRecord; event: ControlEvent }) => Promise<void> | void;\n /** Resolved surface driver for daemon-direct delivery. */\n daemonCmux: DaemonSurfaceDriver | undefined;\n /** #466 self-heal: resolved first-turn resend function (undefined when\n * opts.resendFirstTurn was not provided, e.g. non-cmux deployments). */\n resendFirstTurn: ((rec: TaskRecord) => Promise<{ delivered: boolean }>) | undefined;\n /** Resolved agent driver. */\n codexDriver: AgentDriver;\n /** Resolved opencode SSE bridge. */\n opencodeBridge: OpencodeBridge;\n /** Resolved cmux events bridge. */\n cmuxEventsBridge: CmuxEventsBridge;\n /** Resolved Telegram bridge — undefined when config.telegram is absent. */\n telegramBridge?: TelegramBridge;\n /** Resolved out-of-band fault-alert function (#579/#484 Gap 1) — ALWAYS a\n * real function, never undefined: defaults to a no-op here (core has no\n * notifier implementation to fall back to) but squadrantd.ts (the host)\n * always overrides it with the real notifier-registry-backed one before any\n * event can fire, so production never silently no-ops. Tests that construct\n * DaemonContext directly (bypassing squadrantd.ts) keep the no-op, which is\n * correct for isolated unit tests. */\n notifyFault: (project: string, text: string) => Promise<void> | void;\n /** B4: registered LifecycleSource instances for per-source health aggregation. */\n lifecycleSources: LifecycleSource[];\n /** Fan-out to attach clients (set by createAttach). */\n broadcast: (taskId: string, f: AttachFrame) => void;\n /** Schedule gate promotion (set by createAttach). */\n schedulePromotion: (taskId: string, requestId: number, kind: \"input\" | \"approval\", question: string) => void;\n /** Cancel gate timers for a task (set by createAttach). */\n cancelPromotionsFor: (taskId: string) => void;\n}\n\n/** Initialize the pure-state fields of DaemonContext from opts.\n * Late-bound fields are zero-initialized and MUST be set by start.ts\n * (or squadrantd.ts for drivers) before any event, timer, or socket fires. */\nexport function buildContext(opts: SquadrantdOpts): DaemonContext {\n const stateRoot = opts.stateRoot ?? join(homedir(), \".config\", \"squadrant\", \"state\");\n const sockPath = opts.sockPath ?? join(homedir(), \".config\", \"squadrant\", \"squadrant.sock\");\n const store = createStore(stateRoot);\n const bootedAt = Date.now();\n const taskTimeoutMs = loadConfig().defaults.taskTimeoutMs;\n const isPidAlive = opts.isPidAlive ?? defaultIsPidAlive;\n const spawn = opts.spawn ?? realSpawn;\n const resultsDir = join(stateRoot, \"_results\");\n mkdirSync(resultsDir, { recursive: true });\n const writeResult = (id: string, payload: string) => {\n const p = join(resultsDir, `${id}.txt`);\n writeFileSync(p, payload);\n return p;\n };\n const log = (m: string) =>\n process.stderr.write(`[squadrantd] ${new Date().toISOString()} ${m}\\n`);\n\n return {\n opts,\n stateRoot,\n sockPath,\n store,\n bootedAt,\n lastSweepAt: { value: null },\n taskTimeoutMs,\n isPidAlive,\n spawn,\n resultsDir,\n writeResult,\n log,\n attachConns: new Map(),\n inFlightHeadlessIds: new Set(),\n activeHeadlessKills: new Set(),\n livenessRegistry: (() => {\n const r = new LivenessRegistry({ path: join(stateRoot, \"liveness.json\") });\n r.load();\n return r;\n })(),\n resendFirstTurn: opts.resendFirstTurn,\n // Late-bound — start.ts fills these before first use:\n d: null as unknown as ReturnType<typeof createDaemon>,\n notify: null as unknown as DaemonContext[\"notify\"],\n daemonCmux: undefined,\n codexDriver: null as unknown as AgentDriver,\n opencodeBridge: null as unknown as OpencodeBridge,\n cmuxEventsBridge: null as unknown as CmuxEventsBridge,\n telegramBridge: undefined,\n notifyFault: opts.notifyFault ?? (() => {}),\n lifecycleSources: opts.lifecycleSources ?? [],\n broadcast: () => {},\n schedulePromotion: () => {},\n cancelPromotionsFor: () => {},\n };\n}\n","import { writeFileSync, readFileSync, renameSync } from \"node:fs\";\nimport type { LivenessEntry } from \"@squadrant/shared\";\nimport { reconcileLiveness } from \"../liveness.js\";\n\nexport interface LivenessRegistryOpts {\n path: string;\n readFile?: (p: string) => string | undefined;\n writeFile?: (p: string, content: string) => void;\n}\n\n/** Core-owned, disk-persisted registry — the single liveness source of truth. */\nexport class LivenessRegistry {\n private readonly path: string;\n private readonly readFile: (p: string) => string | undefined;\n private readonly writeFile: (p: string, content: string) => void;\n private map = new Map<string, LivenessEntry>();\n\n constructor(opts: LivenessRegistryOpts) {\n this.path = opts.path;\n this.readFile = opts.readFile ?? ((p) => { try { return readFileSync(p, \"utf-8\"); } catch { return undefined; } });\n this.writeFile = opts.writeFile ?? ((p, c) => { writeFileSync(`${p}.tmp`, c); renameSync(`${p}.tmp`, p); });\n }\n\n load(): void {\n const raw = this.readFile(this.path);\n if (!raw) return;\n try {\n const arr = JSON.parse(raw) as LivenessEntry[];\n this.map = new Map(arr.map((e) => [e.project, e]));\n } catch { this.map = new Map(); }\n }\n\n get(project: string): LivenessEntry | undefined { return this.map.get(project); }\n all(): LivenessEntry[] { return [...this.map.values()]; }\n\n apply(next: LivenessEntry): void {\n this.map.set(next.project, reconcileLiveness(this.map.get(next.project), next));\n this.persist();\n }\n\n markEnded(project: string, at: number): void {\n const e = this.map.get(project);\n if (!e) return;\n this.map.set(project, { ...e, lastState: \"end\", lastSeenAt: at });\n this.persist();\n }\n\n setPidAlive(project: string, alive: boolean, at: number): void {\n const e = this.map.get(project);\n if (!e) return;\n this.map.set(project, { ...e, pidAlive: alive, lastSeenAt: at });\n this.persist();\n }\n\n private persist(): void {\n try { this.writeFile(this.path, JSON.stringify(this.all(), null, 2)); } catch { /* best-effort */ }\n }\n}\n","// src/control/daemon/attach.ts\n// Attach fan-out and gate-promotion logic (spec §4.5/§4.6/§4.9).\nimport { randomUUID } from \"node:crypto\";\nimport { encodeFrame } from \"../protocol.js\";\nimport { makeGate } from \"../gate.js\";\nimport type { AttachFrame } from \"../protocol.js\";\nimport type { Gate } from \"@squadrant/shared\";\nimport type { DaemonContext } from \"./context.js\";\n\nexport interface AttachHandlers {\n broadcast: (taskId: string, f: AttachFrame) => void;\n schedulePromotion: (taskId: string, requestId: number, kind: \"input\" | \"approval\", question: string) => void;\n cancelPromotionsFor: (taskId: string) => void;\n}\n\n/** Build the attach fan-out and gate-promotion machinery. Call once in start.ts\n * immediately after buildContext; assign the returned handlers onto ctx so the\n * driver emit callbacks can reference them via the context object. */\nexport function createAttach(ctx: DaemonContext): AttachHandlers {\n const { attachConns, store, log } = ctx;\n\n function broadcast(taskId: string, f: AttachFrame): void {\n const conns = attachConns.get(taskId);\n if (!conns) return;\n const wire = encodeFrame(f);\n for (const conn of conns) {\n try { conn.write(wire); } catch { /* client gone; onAttachClose will clean up */ }\n }\n }\n\n // ── Gate promotion (spec §4.9) ─────────────────────────────────────────────\n // When a server-request event fires and no client is attached, start a 5s\n // timer. If still unattached at fire time, promote to a Gate in the store\n // and broadcast gate-promoted so any later-attaching client can offer takeover.\n const pendingGateTimers = new Map<string, { taskId: string; timer: NodeJS.Timeout }>();\n\n function schedulePromotion(\n taskId: string,\n requestId: number,\n kind: \"input\" | \"approval\",\n question: string,\n ): void {\n // If a client is already attached for this task, no promotion needed.\n const conns = attachConns.get(taskId);\n if (conns && conns.size > 0) return;\n const key = `${taskId}#${requestId}`;\n // Clear any prior timer for the same (taskId, requestId).\n const prior = pendingGateTimers.get(key);\n if (prior) clearTimeout(prior.timer);\n const timer = setTimeout(() => {\n pendingGateTimers.delete(key);\n // Re-check at fire time — a client may have attached in the 5s window.\n if (attachConns.get(taskId)?.size) return;\n const rec = store.listAll().find((r) => r.id === taskId);\n if (!rec) return;\n const gate: Gate = makeGate({ taskId, kind, question, now: Date.now(), mkId: () => randomUUID() });\n const gates = [...(rec.gates ?? []), gate];\n store.put({ ...rec, gates });\n broadcast(taskId, { type: \"gate-promoted\", taskId, gateId: gate.gateId });\n log(`gate promoted gateId=${gate.gateId} taskId=${taskId} kind=${kind}`);\n }, 5_000);\n timer.unref?.();\n pendingGateTimers.set(key, { taskId, timer });\n }\n\n function cancelPromotionsFor(taskId: string): void {\n for (const [key, slot] of pendingGateTimers.entries()) {\n if (slot.taskId === taskId) {\n clearTimeout(slot.timer);\n pendingGateTimers.delete(key);\n }\n }\n }\n\n return { broadcast, schedulePromotion, cancelPromotionsFor };\n}\n","// src/control/daemon/start.ts\n// Core daemon assembly: wires all daemon/* factories, runs boot recovery,\n// starts timers, and returns the DaemonHandle.\n// Concrete driver construction (CodexInteractiveDriver, DaemonCmux, etc.)\n// lives in the host (squadrantd.ts) — this file stays free of those imports.\nimport { join, dirname } from \"node:path\";\nimport { readdir } from \"node:fs/promises\";\nimport { createDaemon } from \"./reduce.js\";\nimport { createProbes, buildSurfaceProbe } from \"./probes.js\";\nimport { createDelivery } from \"./delivery-loop.js\";\nimport { createGateResolver } from \"./gates.js\";\nimport { createServer } from \"./server.js\";\nimport { rotateIfNeeded, mailboxStats, readCursor } from \"../mailbox.js\";\nimport { projectHealth, deriveCaptainState, type ComponentHealth } from \"../liveness.js\";\nimport type { DaemonSnapshotInputs } from \"../snapshot.js\";\nimport { loadConfig, TERMINAL_STATES, ensureCmuxAutoConfig } from \"@squadrant/shared\";\nimport { distBuiltAt, gatherLogStats, gatherStoreStats, gatherResults } from \"./snapshot-gather.js\";\nimport type { SquadrantdOpts, DaemonContext } from \"./context.js\";\n\nconst CURSOR_SUBSCRIBER = \"captain\";\nconst SNAPSHOT_LOG_WINDOW_MS = 60 * 60 * 1000;\n\nexport interface DaemonHandle {\n /** `reason` is folded into the exit log line (e.g. \"SIGTERM\", \"SIGINT\") so a\n * restart is diagnosable from the log instead of inferred (#535). */\n stop(reason?: string): Promise<void>;\n tickDelivery: (() => Promise<void>) | undefined;\n tickProbe: (() => Promise<void>) | undefined;\n}\n\n/** Wire all daemon/* factories, run boot recovery, start timers.\n * ctx must already have: attach handlers, codexDriver, opencodeBridge,\n * cmuxEventsBridge, daemonCmux, daemonDirectCmux set on it by the host. */\nexport function startDaemon(ctx: DaemonContext, opts: SquadrantdOpts, pkgVersion: string): DaemonHandle {\n const {\n stateRoot, store, log, isPidAlive, resultsDir,\n taskTimeoutMs, inFlightHeadlessIds, activeHeadlessKills,\n broadcast, cancelPromotionsFor,\n } = ctx;\n const { daemonCmux } = ctx;\n\n const probes = createProbes(ctx);\n const { defaultNotify, deliveryTick: initialDeliveryTick, deliveryStats } = createDelivery(ctx, daemonCmux);\n // Compose the Telegram outbound push onto the notify fan-out: a captain\n // notification also pushes to the project's Telegram topic. When no bridge is\n // configured, notify is the base function unchanged (zero behavior change).\n // pushLifecycle is best-effort and never throws (the bridge swallows errors),\n // so it can't delay or break captain delivery.\n const baseNotify = opts.notify ?? defaultNotify;\n const notify: DaemonContext[\"notify\"] = ctx.telegramBridge\n ? async (args) => {\n await baseNotify(args);\n ctx.telegramBridge!.pushLifecycle(args.project, args.event);\n }\n : baseNotify;\n const surfaceProbe = buildSurfaceProbe(ctx, probes, daemonCmux);\n\n const ingest = (project: string) => (e: import(\"@squadrant/shared\").ControlEvent) =>\n void ctx.d.handle({ kind: \"event\", project, event: e });\n\n const d = createDaemon({\n store, now: () => Date.now(), isPidAlive, notify, taskTimeoutMs,\n isSurfaceAlive: surfaceProbe,\n resendFirstTurn: ctx.resendFirstTurn,\n launchHeadless: opts.launchHeadless!,\n isHeadlessInFlight: (id) => inFlightHeadlessIds.has(id),\n launchInteractive: async (rec) => {\n if (rec.provider === \"codex\") {\n await ctx.codexDriver.dispatch(rec as any);\n return;\n }\n if (rec.provider === \"claude\") {\n ingest(rec.project)({ type: \"task.started\", id: rec.id });\n return;\n }\n if (rec.provider === \"opencode\") {\n ingest(rec.project)({ type: \"task.started\", id: rec.id });\n if (rec.serverPort) ctx.opencodeBridge.start({ taskId: rec.id, port: rec.serverPort });\n return;\n }\n throw new Error(\n `interactive mode is not yet implemented for provider '${rec.provider}'; only 'codex', 'claude', and 'opencode' are supported`,\n );\n },\n resolveInteractiveGate: createGateResolver(ctx),\n });\n\n ctx.d = d;\n\n // ── Health + snapshot ─────────────────────────────────────────────────────\n\n function buildHealth(only?: string): ComponentHealth[] {\n const config = loadConfig();\n const now = Date.now();\n const known = new Set<string>([\n ...Object.keys(config.projects),\n ...store.listAll().map((t) => t.project),\n ]);\n const names = only ? [only] : [...known];\n const out: ComponentHealth[] = [];\n for (const project of names) {\n const proj = config.projects[project];\n const captainName = proj?.captainName ?? `${project}-captain`;\n // Captain liveness from the ground-truth registry (Task 4) — runtime\n // snapshot + pid floor, survives daemon restart (§4.1/§4.5).\n const capEntry = ctx.livenessRegistry.get(project);\n out.push(\n ...projectHealth({\n project, now, captainName,\n captainStopped: null,\n captainState: deriveCaptainState(capEntry),\n commandPresent: null,\n crews: store.list(project),\n // #579/#484 Gap 3: surface the same deferral stats already exposed to\n // the snapshot (line ~135 below) on the health row too, so `squadrant\n // doctor` / `squadrant status --detailed` show a stuck delivery with\n // zero configuration.\n captainDeferral: deliveryStats(project),\n }),\n );\n }\n return out;\n }\n\n async function gatherSnapshotInputs(now: number): Promise<DaemonSnapshotInputs> {\n const logPath = join(dirname(stateRoot), \"squadrantd.log\");\n const tier2Projects = opts.registeredProjects ?? Object.keys(loadConfig().projects);\n const projects = await Promise.all(\n tier2Projects.map(async (project) => {\n const cursor = await readCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER });\n const storeStats = gatherStoreStats(store, stateRoot, project);\n return {\n project,\n mailbox: await mailboxStats(stateRoot, project),\n lastAckedSeq: cursor?.lastAckedSeq ?? 0,\n storeByState: storeStats.byState,\n corruptCount: storeStats.corruptCount,\n deferral: deliveryStats(project),\n };\n }),\n );\n return {\n pid: process.pid,\n processStartedAt: ctx.bootedAt,\n version: pkgVersion,\n distBuiltAt: distBuiltAt(),\n lastSweepAt: ctx.lastSweepAt.value,\n sweepCadenceMs: opts.sweepMs ?? 30_000,\n log: gatherLogStats(logPath, now, SNAPSHOT_LOG_WINDOW_MS),\n telegram: ctx.telegramBridge\n ? { configured: true, ...ctx.telegramBridge.health() }\n : { configured: false, polling: false, lastSuccessfulPollAt: null, lastError: null, lastErrorAt: null },\n lifecycleSources: ctx.lifecycleSources.map((s) => ({ name: s.name, ...(s.health?.() ?? { active: true, error: null }) })),\n health: buildHealth(),\n projects,\n results: gatherResults(resultsDir),\n };\n }\n\n // ── Boot recovery ─────────────────────────────────────────────────────────\n\n void (async () => {\n try { await d.reconcile(); }\n catch (e) { log(`reconcile on boot failed: ${(e as Error).message}`); }\n\n // Restart-reattach: reattach live codex crews. Guard against the storm\n // (each reattach re-spawns per-thread MCP servers). Skip terminal and stale tasks.\n // Inline predicate avoids importing from the concrete codex driver module.\n const bootNow = Date.now();\n const REATTACH_STALE_MS = 10 * 60_000;\n for (const rec of store.listAll()) {\n if (rec.provider !== \"codex\" || rec.mode !== \"interactive\") continue;\n if (TERMINAL_STATES.has(rec.state)) continue;\n // Inline of shouldReattachCodex (concrete driver module stays in host).\n const lastAttempt = rec.attempts?.at(-1);\n const last = lastAttempt?.lastHeartbeatAt ?? rec.lastHeartbeat ?? 0;\n if (bootNow - last > REATTACH_STALE_MS) continue;\n if (!lastAttempt?.resumeRef) continue;\n ctx.codexDriver.reattach(rec).catch((e: unknown) => {\n log(`reattach failed for ${rec.id}: ${(e as Error).message}`);\n });\n }\n\n // Re-subscribe opencode SSE bridge after a daemon bounce.\n for (const rec of store.listAll()) {\n if (rec.provider !== \"opencode\" || rec.mode !== \"interactive\") continue;\n if (TERMINAL_STATES.has(rec.state)) continue;\n if (!rec.serverPort) continue;\n ctx.opencodeBridge.start({ taskId: rec.id, port: rec.serverPort });\n }\n\n // B1: start cmux native-events bridge. Skipped under vitest unless injected.\n const enableCmuxEvents = loadConfig().defaults.cmuxEventsBridge !== false;\n const cmuxEventsSafe = !!opts.cmuxEventsBridge || !process.env.VITEST;\n if (enableCmuxEvents && cmuxEventsSafe) {\n try { ctx.cmuxEventsBridge.start(); }\n catch (e) { log(`cmux events bridge start failed: ${(e as Error).message}`); }\n }\n\n // Telegram inbound long-poll (opt-in). The real bridge is only constructed\n // by the host when config.telegram is present and not under vitest, so\n // ctx.telegramBridge here is either that real bridge or an injected fake.\n if (ctx.telegramBridge) {\n try { ctx.telegramBridge.start(); }\n catch (e) { log(`telegram bridge start failed: ${(e as Error).message}`); }\n }\n\n // #348: cmux socket auto-config on boot.\n const autoConfigSafe = !!opts.runCmuxAutoConfig || !process.env.VITEST;\n if (autoConfigSafe) {\n try {\n const r = await (opts.runCmuxAutoConfig ?? ensureCmuxAutoConfig)();\n if (r.configChanged) log(`cmux autoconfig: wrote automation socket mode to ${r.configPath}`);\n if (r.needsRestart && r.promptedThisRun) {\n log(\"cmux autoconfig: socket still rejects the daemon — restart cmux to enable daemon-direct delivery\");\n }\n } catch (e) {\n log(`cmux autoconfig failed: ${(e as Error).message}`);\n }\n }\n })();\n\n // ── Server + timers ───────────────────────────────────────────────────────\n\n const server = createServer(ctx, { buildHealth, gatherSnapshotInputs, cancelPromotionsFor, broadcast });\n // #535: greppable boot marker — a restart must be diagnosable from the log,\n // never inferred from process START time.\n log(`boot pid=${process.pid} version=${pkgVersion} socket=${ctx.sockPath} stateRoot=${stateRoot}`);\n\n let deliveryTick: (() => Promise<void>) | undefined = initialDeliveryTick;\n let probeTick: (() => Promise<void>) | undefined;\n\n if (daemonCmux) {\n probeTick = probes.buildInteractiveProbe({ cmux: daemonCmux });\n }\n\n let deliveryTimer: NodeJS.Timeout | undefined;\n if (daemonCmux && opts.sweepMs && opts.sweepMs > 0) {\n deliveryTimer = setInterval(() => {\n void deliveryTick!().catch((e: unknown) => log(`delivery tick error: ${(e as Error).message}`));\n }, 1000);\n deliveryTimer.unref?.();\n }\n\n let probeTimer: NodeJS.Timeout | undefined;\n if (daemonCmux && opts.sweepMs && opts.sweepMs > 0) {\n probeTimer = setInterval(() => {\n void probeTick!().catch((e: unknown) => log(`probe tick error: ${(e as Error).message}`));\n }, 10_000);\n probeTimer.unref?.();\n }\n\n let timer: NodeJS.Timeout | undefined;\n if (opts.sweepMs && opts.sweepMs > 0) {\n let sweeping = false;\n timer = setInterval(() => {\n if (sweeping) return;\n sweeping = true;\n ctx.lastSweepAt.value = Date.now();\n void d.sweep()\n .catch((e: unknown) => log(`sweep failed: ${(e as Error).message}`))\n .finally(() => { sweeping = false; });\n }, opts.sweepMs);\n timer.unref?.();\n }\n\n const rotationInterval = opts.rotationIntervalMs ?? 60_000;\n const mboxCfg = {\n maxBytes: opts.mailboxConfig?.maxBytes ?? 5 * 1024 * 1024,\n maxAgeMs: opts.mailboxConfig?.maxAgeMs ?? 7 * 24 * 60 * 60 * 1000,\n keepCount: opts.mailboxConfig?.keepCount ?? 3,\n };\n let rotationTimer: NodeJS.Timeout | undefined;\n if (rotationInterval > 0) {\n const inboxPath = join(stateRoot, \"inbox\");\n rotationTimer = setInterval(async () => {\n try {\n let entries: string[];\n try { entries = await readdir(inboxPath); } catch { return; }\n const projects = new Set(\n entries.filter((e) => e.endsWith(\".log\")).map((e) => e.slice(0, -\".log\".length)),\n );\n for (const project of projects) await rotateIfNeeded({ stateRoot, project, ...mboxCfg });\n } catch (e) {\n log(`rotation timer error: ${(e as Error).message}`);\n }\n }, rotationInterval);\n rotationTimer.unref?.();\n }\n\n return {\n stop(reason = \"requested\"): Promise<void> {\n // #535: write the exit marker synchronously, before any async\n // teardown, so it lands even if the caller doesn't await this promise.\n log(`exit pid=${process.pid} reason=${reason}`);\n if (deliveryTimer) clearInterval(deliveryTimer);\n if (probeTimer) clearInterval(probeTimer);\n if (timer) clearInterval(timer);\n if (rotationTimer) clearInterval(rotationTimer);\n try { ctx.cmuxEventsBridge.stop(); } catch { /* best-effort */ }\n try { ctx.telegramBridge?.stop(); } catch { /* best-effort */ }\n try { ctx.codexDriver.stop?.(); } catch { /* best-effort */ }\n for (const kill of ctx.activeHeadlessKills) kill();\n return new Promise<void>((resolve) => server.close(() => { log(`exit-complete pid=${process.pid}`); resolve(); }));\n },\n tickDelivery: deliveryTick,\n tickProbe: probeTick,\n };\n}\n","// Daemon interactive-block probe: moved from commands/notify-relay.ts so\n// daemon/probes.ts (core) can import it without a core→commands back-edge.\nimport type { TaskRecord, ControlEvent } from \"@squadrant/shared\";\n\n// Entries older than this at session-start time are silently acked without\n// delivery — stale events from a prior session or dead crews.\nexport const STALE_THRESHOLD_MS = 5 * 60 * 1000;\n\n// A working interactive task with no heartbeat for this long is a probe\n// candidate: PostToolUse never fires while a permission prompt is up.\nexport const PROBE_QUIET_MS = 20_000;\n\n// ── Pure pane classifiers (inlined from interactive/pane-classifier.ts) ──────\n// Duplication is intentional: pane-classifier.ts stays in root for the relay\n// path; core can't import it (root → core boundary). Both copies are pure and\n// covered by pane-classifier.test.ts.\n\nfunction detectTrailingQuestion(text: string): string | null {\n if (!text) return null;\n let inFence = false;\n let lastLine: string | null = null;\n for (const raw of text.split(/\\r?\\n/)) {\n const line = raw.trim();\n if (line.startsWith(\"```\")) { inFence = !inFence; continue; }\n if (inFence || line === \"\") continue;\n lastLine = line;\n }\n if (lastLine && lastLine.endsWith(\"?\")) return lastLine;\n return null;\n}\n\nconst ERROR_BANNER_RE: RegExp[] = [\n /\\bAPI Error\\b/i,\n /\\bOverloaded\\b/,\n /\\b(?:429|500|502|503|504|529)\\b[^?]*\\b(?:overloaded|unavailable|internal server error|bad gateway|gateway timeout|too many requests|service unavailable)\\b/i,\n /\\bretr(?:y|ies)\\s+(?:exhausted|limit\\s+(?:reached|exceeded))\\b/i,\n /\\bmaximum\\s+retries\\b/i,\n];\nconst OPTION_RE = /^[❯>›]?\\s*(\\d+)\\.\\s+(.*\\S)\\s*$/;\nconst PICKER_FOOTER_RE = /↑↓\\s*select|enter\\s+submit|esc\\s+dismiss/i;\nconst PURE_CHROME_RE = /^[\\s─━│┃╭╮╰╯┌┐└┘├┤┬┴┼═║╔╗╚╝╠╣╦╩╬▁▂▃▄▅▆▇█▔▏▕]+$/;\nconst STATUS_LINE_RE = /accept edits on|shift\\+tab|⏵⏵|\\? for shortcuts|esc to interrupt|tokens? (used|left)|context left/i;\n\nfunction stripChrome(raw: string): string | null {\n let line = raw.replace(/\\[[0-9;]*m/g, \"\");\n line = line.replace(/^[\\s│┃▏▕|]+/, \"\").replace(/[\\s│┃▏▕|]+$/, \"\");\n const trimmed = line.trim();\n if (trimmed === \"\") return null;\n if (PURE_CHROME_RE.test(trimmed)) return null;\n if (/^>\\s*$/.test(trimmed)) return null;\n if (STATUS_LINE_RE.test(trimmed)) return null;\n return trimmed;\n}\n\nfunction classifyPaneTail(\n tail: string,\n): { kind: \"approval\" | \"question\" | \"error\"; text: string } | null {\n if (!tail) return null;\n const raw = tail.split(/\\r?\\n/);\n const cleaned = raw.map(stripChrome);\n const options: { label: string; ci: number }[] = [];\n for (let i = 0; i < cleaned.length; i++) {\n const c = cleaned[i];\n if (c == null) continue;\n const m = c.match(OPTION_RE);\n if (m) options.push({ label: m[2], ci: i });\n }\n const hasYes = options.some((o) => /\\byes\\b/i.test(o.label));\n const hasNo = options.some((o) => /\\bno\\b/i.test(o.label));\n if (options.length >= 2 && hasYes && hasNo) {\n const firstOptCi = options[0].ci;\n for (let i = firstOptCi - 1; i >= 0; i--) {\n const c = cleaned[i];\n if (c == null) continue;\n if (c.endsWith(\"?\")) return { kind: \"approval\", text: c };\n }\n return { kind: \"approval\", text: \"Crew is awaiting permission approval.\" };\n }\n const hasPickerFooter = cleaned.some((c) => c != null && PICKER_FOOTER_RE.test(c));\n if (options.length >= 2 && hasPickerFooter) {\n const firstOptCi = options[0].ci;\n for (let i = firstOptCi - 1; i >= 0; i--) {\n const c = cleaned[i];\n if (c == null) continue;\n if (c.endsWith(\"?\")) return { kind: \"question\", text: c };\n }\n return { kind: \"question\", text: \"Crew is awaiting a choice.\" };\n }\n const region = cleaned.filter((c): c is string => c != null).join(\"\\n\");\n const q = detectTrailingQuestion(region);\n if (q) return { kind: \"question\", text: q };\n let errLine: string | null = null;\n for (const c of cleaned) {\n if (c != null && ERROR_BANNER_RE.some((re) => re.test(c))) errLine = c;\n }\n if (errLine) return { kind: \"error\", text: errLine.slice(0, 200) };\n return null;\n}\n\n// ── Interactive probe ─────────────────────────────────────────────────────────\n\ninterface InteractiveProbeDeps {\n project: string;\n listTasks: () => Promise<TaskRecord[]>;\n readPaneTail: (rec: TaskRecord) => Promise<string | null>;\n sendEvent: (event: ControlEvent) => Promise<void>;\n now: () => number;\n log: (m: string) => void;\n quietMs?: number;\n}\n\nexport function createInteractiveProbe(deps: InteractiveProbeDeps): {\n tick: () => Promise<void>;\n} {\n const quietMs = deps.quietMs ?? PROBE_QUIET_MS;\n const lastTail = new Map<string, string>();\n\n async function tick(): Promise<void> {\n let tasks: TaskRecord[];\n try {\n tasks = await deps.listTasks();\n } catch (e) {\n deps.log(`probe listTasks failed: ${(e as Error).message}`);\n return;\n }\n const now = deps.now();\n for (const rec of tasks) {\n if (rec.mode !== \"interactive\") continue;\n if (rec.state !== \"working\") continue;\n if (!rec.name) continue;\n if (now - rec.lastHeartbeat <= quietMs) continue;\n\n let tail: string | null;\n try {\n tail = await deps.readPaneTail(rec);\n } catch (e) {\n deps.log(`probe read failed for ${rec.id}: ${(e as Error).message}`);\n continue;\n }\n if (!tail) continue;\n if (lastTail.get(rec.id) === tail) continue;\n lastTail.set(rec.id, tail);\n\n const verdict = classifyPaneTail(tail);\n if (!verdict) continue;\n const event: ControlEvent =\n verdict.kind === \"error\"\n ? {\n type: \"task.failed\",\n id: rec.id,\n error: `crew session error (pane-detected): ${verdict.text}`,\n }\n : {\n type: \"task.blocked\",\n id: rec.id,\n reason:\n verdict.kind === \"approval\"\n ? \"crew awaiting permission (pane-detected)\"\n : \"crew asked a question (pane-detected)\",\n question: verdict.text,\n };\n try {\n await deps.sendEvent(event);\n const label = verdict.kind === \"error\" ? \"CREW FAILED\" : \"CREW BLOCKED\";\n deps.log(`probe -> ${label} ${rec.name} (${verdict.kind})`);\n } catch (e) {\n deps.log(`probe sendEvent failed for ${rec.id}: ${(e as Error).message}`);\n }\n }\n }\n\n return { tick };\n}\n","// src/control/daemon/probes.ts\n// Surface-liveness probe logic for the daemon-direct delivery path.\nimport { createInteractiveProbe } from \"./interactive-probe.js\";\nimport { createDirectCrewPaneReader, createDirectSurfaceLivenessProbe } from \"../crew-pane-reader.js\";\nimport { loadConfig } from \"@squadrant/shared\";\nimport type { TaskRecord } from \"@squadrant/shared\";\nimport type { DaemonSurfaceDriver } from \"../interfaces.js\";\nimport type { DaemonContext } from \"./context.js\";\n\nexport interface ProbeHandlers {\n /** Build the probe-tick function for the daemon-direct delivery loop.\n * Call once after the surface driver is resolved; returns a guarded tick. */\n buildInteractiveProbe: (deps: { cmux: DaemonSurfaceDriver }) => () => Promise<void>;\n /** Direct-cmux surface liveness probe for interactive task reaping. */\n directSurfaceProbe: (rec: TaskRecord) => Promise<\"alive\" | \"gone\" | \"unknown\">;\n}\n\n/** Resolve the captain pane name for a project from the config. */\nfunction captainNameForProject(project: string): string {\n const cfg = loadConfig();\n return cfg.projects?.[project]?.captainName ?? `${project}-captain`;\n}\n\nexport function createProbes(ctx: DaemonContext): ProbeHandlers {\n const { store, log } = ctx;\n\n // ── Daemon-direct: direct cmux surface probe ──────────────────────────────\n const directSurfaceProbe = (_rec: TaskRecord): Promise<\"alive\" | \"gone\" | \"unknown\"> => {\n return Promise.resolve(\"unknown\");\n };\n\n // ── Daemon-direct: blocked-crew detection ─────────────────────────────────\n // Reuses createInteractiveProbe with a direct cmux pane reader injected as\n // the readPaneTail dep. The returned tick must be called from the delivery\n // loop's interval.\n function buildInteractiveProbe(deps: { cmux: DaemonSurfaceDriver }): () => Promise<void> {\n const directPaneReader = createDirectCrewPaneReader(deps.cmux, captainNameForProject);\n const probe = createInteractiveProbe({\n project: \"_all_\",\n listTasks: async () => store.listAll(),\n readPaneTail: directPaneReader,\n sendEvent: async (event) => {\n const rec = store.listAll().find((r) => r.id === event.id);\n if (rec) {\n await ctx.d.handle({ kind: \"event\", project: rec.project, event });\n }\n },\n now: () => Date.now(),\n log,\n });\n let probing = false;\n return async () => {\n if (probing) return;\n probing = true;\n try { await probe.tick(); }\n finally { probing = false; }\n };\n }\n\n return { buildInteractiveProbe, directSurfaceProbe };\n}\n\n/** Build the surface-liveness probe used by createDaemon — always uses the\n * direct cmux path when a driver is available. Pure: no side effects. */\nexport function buildSurfaceProbe(\n ctx: DaemonContext,\n probes: ProbeHandlers,\n daemonCmux: DaemonSurfaceDriver | undefined,\n): (rec: TaskRecord) => Promise<\"alive\" | \"gone\" | \"unknown\"> {\n if (ctx.opts.isSurfaceAlive) return ctx.opts.isSurfaceAlive;\n if (daemonCmux) {\n return createDirectSurfaceLivenessProbe(daemonCmux, captainNameForProject);\n }\n return probes.directSurfaceProbe;\n}\n","/** Thrown by sendToSurface when the captain has a draft — delivery defers (#258/#302). */\nexport class DeferDelivery extends Error {\n constructor(public readonly draft: string | null = null) {\n super(\"deferred: captain composing\");\n this.name = \"DeferDelivery\";\n }\n}\n","import { DeferDelivery } from \"./defer-delivery.js\";\n\n/**\n * #332: extracted defer-while-typing state machine (#258/#302).\n *\n * Behaviour ported from notify-relay.ts drain() (#332):\n * - per-seq deferCounts / stableCounts / lastContent maps\n * - maxDefers / stableProbePolls thresholds\n * - stable-content probe escalation (#302)\n */\nexport interface CaptainDeliveryOptions {\n maxDefers: number;\n stableProbePolls: number;\n}\n\nexport type SendFn = (text: string, opts?: { probe?: boolean }) => Promise<void>;\nexport type DeliverResult = { delivered: true } | { deferred: true };\n\n/** Read-only deferral snapshot (B1 — dashboard visibility into #484/#466-class stalls). */\nexport interface CaptainDeliveryStats {\n /** Highest in-flight deferCount across all seqs currently being retried (0 when none). */\n maxDeferCount: number;\n /** true once maxDeferCount has reached the configured maxDefers threshold — the same\n * point at which delivery force-escalates to a probe send. */\n stuck: boolean;\n}\n\n/**\n * Unified-formatter helper (#214/#210): the daemon's formatMessage is the single\n * source of truth for the captain-facing message. Returns null for entries the\n * daemon chose not to surface (null/empty message fields).\n */\nexport function deliverable(entry: { message?: string | null }): string | null {\n const msg = entry.message;\n if (msg == null) return null;\n const trimmed = msg.trim();\n return trimmed.length > 0 ? msg : null;\n}\n\nexport class CaptainDelivery {\n private deferCounts = new Map<number, number>();\n private lastContent = new Map<number, string | null>();\n private stableCounts = new Map<number, number>();\n\n constructor(private readonly opts: CaptainDeliveryOptions) {}\n\n /**\n * Attempt to deliver one mailbox entry to the captain. Calls `send(text, opts)`\n * and, if the send throws DeferDelivery, tracks defer/stable counts for the\n * entry's seq and returns {deferred: true} (caller should NOT advance cursor).\n * On success or null message returns {delivered: true} (caller SHOULD advance).\n */\n async deliver(\n entry: { seq: number; message?: string | null },\n send: SendFn,\n ): Promise<DeliverResult> {\n const msg = deliverable(entry);\n if (!msg) return { delivered: true };\n\n const seq = entry.seq;\n const deferCount = this.deferCounts.get(seq) ?? 0;\n // #302/#484: probe ONLY once content has been stable for stableProbePolls\n // polls (captain not typing / a ghost that isn't re-rendering). A probe\n // send makes sendToSurface inject a REAL backspace keystroke into the live\n // pane to run the structural liveness test (#258) — safe against a stable\n // box, but unsafe against one that's still actively changing: repeatedly\n // backspacing a genuinely-typing human's draft risks racing their next\n // keystroke and, per #484's reopened root-cause, eventually misclassifying\n // and force-delivering into it. deferCount alone must NEVER trigger a\n // probe — an actively-changing draft defers indefinitely until it goes\n // stable (paused) or empty (submitted); maxDefers stays meaningful only as\n // the `stuck` dashboard signal in stats() below, decoupled from escalation.\n const stable = (this.stableCounts.get(seq) ?? 0) >= this.opts.stableProbePolls;\n const probe = stable;\n\n try {\n await send(msg, probe ? { probe: true } : undefined);\n this.deferCounts.delete(seq);\n this.stableCounts.delete(seq);\n this.lastContent.delete(seq);\n return { delivered: true };\n } catch (e) {\n if (e instanceof DeferDelivery) {\n this.deferCounts.set(seq, deferCount + 1);\n // Track content stability: byte-identical non-empty draft across\n // consecutive polls means the captain isn't actively typing (#302).\n const content = e.draft;\n if (content && content === this.lastContent.get(seq)) {\n this.stableCounts.set(seq, (this.stableCounts.get(seq) ?? 0) + 1);\n } else {\n this.stableCounts.set(seq, 0);\n }\n this.lastContent.set(seq, content);\n return { deferred: true };\n }\n // Non-DeferDelivery errors: don't advance cursor, retry next poll.\n return { deferred: true };\n }\n }\n\n /** Read-only. Never mutates — safe to poll from the snapshot assembler every tick. */\n stats(): CaptainDeliveryStats {\n let maxDeferCount = 0;\n for (const c of this.deferCounts.values()) if (c > maxDeferCount) maxDeferCount = c;\n return { maxDeferCount, stuck: maxDeferCount >= this.opts.maxDefers };\n }\n}\n","// src/control/daemon/delivery.ts\n// Mailbox notification + daemon-direct captain delivery loop (#332).\nimport { appendToMailbox, appendCaptainMessage, readCursor, writeCursor, readFromCursor } from \"../mailbox.js\";\nimport { CaptainDelivery, type CaptainDeliveryStats } from \"../delivery/captain-delivery.js\";\nimport { loadConfig, TERMINAL_STATES } from \"@squadrant/shared\";\nimport { STALE_THRESHOLD_MS } from \"./interactive-probe.js\";\nimport { deriveCaptainState } from \"../liveness.js\";\nimport type { TaskRecord, ControlEvent, RuntimeLivenessRecord, LivenessEntry } from \"@squadrant/shared\";\nimport type { PaneRef } from \"@squadrant/shared\";\nimport type { Store } from \"../store.js\";\nimport type { DaemonSurfaceDriver } from \"../interfaces.js\";\nimport type { DaemonContext } from \"./context.js\";\nimport type { LivenessRegistry } from \"./liveness-registry.js\";\n\nconst CURSOR_SUBSCRIBER = \"captain\";\n\n// Must-deliver event kinds that bypass the stale-skip path (#474 D1).\n// Includes terminal transitions (done/failed/cancelled) AND task.blocked:\n// a dropped task.blocked leaves the captain waiting forever on a crew question.\nconst TERMINAL_KINDS = new Set([\"task.done\", \"task.failed\", \"task.cancelled\", \"task.blocked\"]);\n\n/** Pure: find the captain surface by title in a surface list (#332). */\nexport function discoverCaptainSurface(surfaces: PaneRef[], captainTitle: string): PaneRef | null {\n return surfaces.find((s) => s.title === captainTitle) ?? null;\n}\n\n/**\n * Reap a stopped project's orphaned crews (#324). When the user closes the\n * captain workspace, its crew panes die with it — every non-terminal\n * interactive crew is orphaned. Terminalize them to 'cancelled' with a distinct\n * `captain-stopped` marker (traceable; not a fault). Silent: no push fires (the\n * captain that would receive it is gone). Returns the count reaped.\n *\n * Headless crews are excluded — they run as detached processes, not panes in the\n * captain's workspace, and are reconciled by their own pid liveness instead.\n */\nexport function reapOrphanedCrews(store: Pick<Store, \"list\" | \"put\">, project: string): number {\n let reaped = 0;\n for (const r of store.list(project)) {\n if (TERMINAL_STATES.has(r.state)) continue;\n if (r.mode !== \"interactive\") continue;\n store.put({ ...r, state: \"cancelled\", lastEvent: \"captain-stopped\" });\n reaped++;\n }\n return reaped;\n}\n\nexport interface LivenessTickDeps {\n registry: LivenessRegistry;\n liveness: () => Promise<RuntimeLivenessRecord[]>;\n isPidAlive: (pid: number) => boolean;\n now: () => number;\n /** Reap a stopped/gone captain's orphaned crews (#324 — fold-in of the old\n * streak-triggered reap, now driven by the registry). Optional so pure\n * liveness-only callers can omit it. Idempotent (already-terminal crews are\n * skipped), so calling it every tick for a non-alive captain is safe. */\n reap?: (project: string) => number;\n /** One grep-able line per applied/transitioned record (§4.4): `[role/source]\n * project pid=… → state`. Optional so pure liveness-only callers can omit it. */\n log?: (msg: string) => void;\n}\n\nfunction logEntry(log: ((msg: string) => void) | undefined, project: string, e: LivenessEntry | undefined): void {\n if (!log || !e) return;\n log(`[${e.role}/${e.source}] ${project} pid=${e.pid} → ${deriveCaptainState(e)}`);\n}\n\n/** One reconcile+floor pass over captain records. Runtime snapshot is authoritative;\n * the pid floor arbitrates liveness; a captain absent from the snapshot is marked\n * cleanly-closed (stopped) but NOT dropped. */\nexport async function runLivenessTick(deps: LivenessTickDeps): Promise<void> {\n const now = deps.now();\n let records: RuntimeLivenessRecord[] = [];\n try { records = await deps.liveness(); } catch { return; } // runtime unreachable → leave registry as-is\n const seen = new Set<string>();\n\n // #565: cmux's own store can degrade a session's launchCommand (observed live:\n // a crash/reattach left it as bare `[\"claude\"]`, no --append-system-prompt-file)\n // so the record reads role:\"unknown\" even though it's the exact same session\n // already confirmed as this project's captain. SessionId identity outranks a\n // degraded launchCommand classification — restore \"captain\" for any record\n // whose sessionId matches an already-known captain for that project.\n const knownCaptainSessions = new Map<string, string>(); // sessionId → project\n for (const e of deps.registry.all()) {\n if (e.role === \"captain\") knownCaptainSessions.set(e.sessionId, e.project);\n }\n\n // #527: multiple cmux sessions can share a cwd, producing duplicate project\n // entries. Group by project and pick one winner to avoid last-write-wins\n // collision (dead pid overwriting live).\n const byProject = new Map<string, RuntimeLivenessRecord[]>();\n for (const r of records) {\n const role = r.role === \"captain\" || knownCaptainSessions.get(r.sessionId) === r.project\n ? \"captain\" : r.role;\n if (role !== \"captain\") continue;\n let arr = byProject.get(r.project);\n if (!arr) { arr = []; byProject.set(r.project, arr); }\n arr.push(r);\n }\n\n for (const [project, recs] of byProject) {\n seen.add(project);\n // Prefer pidAlive===true (or pid:null hibernated), then first in order.\n const winner = recs.find(r => r.pid == null || deps.isPidAlive(r.pid)) ?? recs[0];\n const entry: LivenessEntry = {\n project, role: \"captain\", pid: winner.pid, sessionId: winner.sessionId,\n startedAt: now, lastState: \"start\", lastSeenAt: now,\n pidAlive: winner.pid != null ? deps.isPidAlive(winner.pid) : true,\n source: \"runtime\",\n };\n // Preserve original startedAt if we already knew this captain (avoid churn):\n const prev = deps.registry.get(project);\n if (prev && prev.lastState === \"start\") entry.startedAt = prev.startedAt;\n deps.registry.apply(entry);\n if (winner.pid != null) deps.registry.setPidAlive(project, deps.isPidAlive(winner.pid), now);\n logEntry(deps.log, project, deps.registry.get(project));\n }\n\n // Captains we knew but the snapshot no longer lists → clean close — but ONLY\n // with positive evidence the pid is actually dead (#565). Absence from a\n // single snapshot read is not proof of death (a store-file parsing glitch,\n // a degraded record, a transient cmux hiccup); inferring \"ended\" from\n // absence alone silently and permanently pauses delivery for a captain that\n // is still running. When the tracked pid can't be confirmed dead (still\n // alive, or unknown/null), leave the entry alone.\n for (const e of deps.registry.all()) {\n if (e.role !== \"captain\" || e.lastState !== \"start\" || seen.has(e.project)) continue;\n if (e.pid == null || deps.isPidAlive(e.pid)) {\n deps.log?.(`[${e.role}/runtime] ${e.project} pid=${e.pid} missing from snapshot but not confirmed dead — leaving alive`);\n continue;\n }\n deps.registry.markEnded(e.project, now);\n logEntry(deps.log, e.project, deps.registry.get(e.project));\n }\n\n // Reap orphaned crews for any captain the registry now considers stopped\n // (clean close) or gone (crash).\n if (deps.reap) {\n for (const e of deps.registry.all()) {\n if (e.role !== \"captain\") continue;\n const state = deriveCaptainState(e);\n if (state === \"stopped\" || state === \"gone\") deps.reap(e.project);\n }\n }\n}\n\nexport interface DeliveryResult {\n defaultNotify: (args: { project: string; message: string; record: TaskRecord; event: ControlEvent }) => Promise<void>;\n /** Guarded delivery tick — undefined when daemon-direct mode is OFF. */\n deliveryTick: (() => Promise<void>) | undefined;\n /** Read-only per-project deferral stats (B1). undefined when daemon-direct mode is OFF,\n * or when the project has no CaptainDelivery instance yet (no delivery attempted). */\n deliveryStats: (project: string) => CaptainDeliveryStats | undefined;\n}\n\nexport function createDelivery(\n ctx: DaemonContext,\n daemonCmux: DaemonSurfaceDriver | undefined,\n): DeliveryResult {\n const { stateRoot, store, log, livenessRegistry, isPidAlive, opts, telegramBridge } = ctx;\n // Default to a no-op so tests that construct a bare ctx object (not via\n // buildContext) don't need to inject this. squadrantd.ts always overrides\n // ctx.notifyFault with the real one in production (see context.ts).\n const notifyFault = ctx.notifyFault ?? (() => {});\n\n // ── Default push-notification wiring (mailbox-injector spec) ─────────────\n const defaultNotify = async (args: {\n project: string;\n message: string;\n record: TaskRecord;\n event: ControlEvent;\n }): Promise<void> => {\n try {\n await appendToMailbox({\n stateRoot,\n project: args.project,\n taskRecord: args.record,\n event: args.event,\n // Persist the daemon-rendered message (#214/#210): delivered verbatim\n // rather than re-derived from the raw event (which drifted).\n message: args.message,\n });\n } catch (e) {\n log(`mailbox append failed project=${args.project}: ${(e as Error).message}`);\n }\n };\n\n // ── Daemon-direct delivery loop ───────────────────────────────────────────\n if (!daemonCmux) {\n return { defaultNotify, deliveryTick: undefined, deliveryStats: () => undefined };\n }\n\n const cmux = daemonCmux;\n const cfg = loadConfig();\n const deliveries = new Map<string, CaptainDelivery>();\n const deliveryStats = (project: string): CaptainDeliveryStats | undefined => deliveries.get(project)?.stats();\n // #579/#484: deferring forever behind an actively-changing draft is the\n // correct, SAFE behaviour — but safe-and-silent is #560's disease. Track\n // which projects we've already alerted on for the CURRENT stall episode so\n // the alert fires exactly once per episode (edge-triggered, mirrors the\n // #354 quietNotifiedAt / #492 anti-flood pattern), not once per poll. Clears\n // when stats().stuck drops back to false, re-arming for a later episode.\n const stuckNotified = new Set<string>();\n // Captured once at delivery-loop setup. Entries older than\n // sessionStartMs - STALE_THRESHOLD_MS are silently acked (cursor advanced)\n // without delivery. This stops a fresh/empty cursor from re-delivering the\n // entire historical backlog.\n const sessionStartMs = Date.now();\n\n // Re-entrancy guard: each tick does multiple slow cmux subprocess calls and\n // can exceed the 1s interval.\n let delivering = false;\n\n const deliveryCore = async () => {\n // Registry is the liveness authority (Task 4) — reconcile it from the\n // runtime snapshot + pid floor before this tick's per-project pass.\n await runLivenessTick({\n registry: livenessRegistry,\n liveness: () => (cmux.liveness ? cmux.liveness() : Promise.resolve([])),\n isPidAlive,\n now: () => Date.now(),\n log,\n reap: (project) => {\n const reaped = reapOrphanedCrews(store, project);\n if (reaped > 0) {\n const title = cfg.projects?.[project]?.captainName ?? `${project}-captain`;\n log(`captain ${title}: reaped ${reaped} orphaned crew(s)`);\n }\n return reaped;\n },\n });\n\n const injectedSurfaces = opts.captainSurfaces ?? {};\n const allProjects = [...new Set([\n ...Object.keys(cfg.projects ?? {}),\n ...Object.keys(injectedSurfaces),\n ...store.listAll().map((t) => t.project),\n cfg.commandName,\n ])];\n\n for (const project of allProjects) {\n const projCfg = cfg.projects?.[project];\n const captainTitle = project === cfg.commandName \n ? cfg.commandName \n : (projCfg?.captainName ?? `${project}-captain`);\n\n // Surface discovery is ONLY for the delivery target (where to cmux.send);\n // captain presence/liveness authority now lives in livenessRegistry.\n const wsId = cmux.findWorkspaceId ? await cmux.findWorkspaceId(captainTitle) : null;\n let surface: PaneRef | null = null;\n\n if (wsId) {\n const surfaces = await cmux.listSurfaces(wsId);\n surface = discoverCaptainSurface(surfaces, captainTitle);\n }\n\n // Fall back to injected surface (tests / config-less projects).\n if (!surface) surface = injectedSurfaces[project] ?? null;\n\n if (!surface) continue;\n\n const cursor = await readCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER });\n const lastAcked = cursor?.lastAckedSeq ?? 0;\n let d = deliveries.get(project);\n if (!d) {\n d = new CaptainDelivery({\n maxDefers: cfg.delivery?.maxDeferDeliveries ?? 300,\n stableProbePolls: cfg.delivery?.stableProbePolls ?? 3,\n });\n deliveries.set(project, d);\n }\n for await (const entry of readFromCursor({ stateRoot, project, fromSeq: lastAcked + 1 })) {\n // #332 storm BUG 3: silently ack entries that pre-date this daemon\n // session by more than STALE_THRESHOLD_MS.\n if (new Date(entry.ts).getTime() < sessionStartMs - STALE_THRESHOLD_MS) {\n // D1 (#474): terminal events must deliver regardless of age — an\n // undelivered CREW DONE must reach the captain even after a daemon\n // restart >5min after enqueue. Non-terminal backlog suppression stays.\n if (!TERMINAL_KINDS.has(entry.kind)) {\n // #531: exempt non-daemon captain.message (human/cli) from stale-skip\n const isExemptMessage = entry.kind === \"captain.message\" && entry.payload?.source !== \"daemon\";\n if (!isExemptMessage) {\n log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-skipped`);\n await writeCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER, lastAckedSeq: entry.seq });\n continue;\n }\n log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-exempt-deliver`);\n } else {\n log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-terminal-deliver`);\n }\n }\n const result = await d.deliver(entry, (text, sendOpts) =>\n cmux.send(surface!, text, sendOpts),\n );\n if (\"delivered\" in result) {\n log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=delivered`);\n await writeCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER, lastAckedSeq: entry.seq });\n } else {\n log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=deferred`);\n break;\n }\n }\n\n // #579/#484: fail LOUD, not silent, once this project's delivery is\n // stuck (deferCount crossed maxDefers — an actively-changing draft that\n // never stabilizes, so the structural probe never gets to run).\n //\n // The mailbox entry alone is NOT enough: it's drained by this same\n // stuck delivery pipeline, so it queues behind the very block it's\n // reporting and only surfaces once the stall has already resolved\n // (fail-silent-then-apologize). Kept here as a post-resolution audit\n // trail, discoverable even if the operator never opens the dashboard.\n //\n // Two independent out-of-band channels fire alongside it, neither of\n // which touches the stuck pane/mailbox:\n // - notifyFault: the notifier plugin slot (cmux by default — see\n // @squadrant/workspaces' NotifierRegistry). ALWAYS resolved in\n // production (never undefined), so it's the channel that works with\n // ZERO Telegram configuration — closing the gap where Telegram alone\n // left every non-Telegram install silent.\n // - telegramBridge.pushRaw: reaches a phone even when the operator\n // isn't watching a terminal. Optional — only when Telegram is set up.\n // The daemon's own health snapshot (`deferral.stuck`) is also surfaced\n // as `detail` on the captain's ComponentHealth row (see liveness.ts),\n // so `squadrant doctor` / `squadrant status --detailed` show it too —\n // a third, pull-based, zero-configuration surface.\n const stuck = d.stats().stuck;\n if (stuck && !stuckNotified.has(project)) {\n stuckNotified.add(project);\n const { maxDeferCount } = d.stats();\n log(`delivery stuck project=${project} deferCount=${maxDeferCount}`);\n const text = `⚠️ DELIVERY STUCK: an in-progress draft (or ghost text) in your input box has blocked pending notification(s) for ${maxDeferCount}+ retries. Your input is never touched — this keeps retrying safely and will deliver automatically once you submit or clear it.`;\n appendCaptainMessage({ stateRoot, project, text, source: \"daemon\" })\n .catch((e) => log(`delivery stuck alert failed project=${project}: ${(e as Error).message}`));\n Promise.resolve(notifyFault(project, text))\n .catch((e) => log(`delivery stuck fault-notify failed project=${project}: ${(e as Error).message}`));\n telegramBridge?.pushRaw(project, text);\n } else if (!stuck && stuckNotified.has(project)) {\n stuckNotified.delete(project);\n }\n }\n };\n\n const deliveryTick = async () => {\n if (delivering) return;\n delivering = true;\n try {\n await deliveryCore();\n } finally {\n delivering = false;\n }\n };\n\n return { defaultNotify, deliveryTick, deliveryStats };\n}\n","// src/control/daemon/gates.ts\n// resolveInteractiveGate: route the captain's approve/deny to the owning driver.\n// Reads ctx.codexDriver and ctx.opencodeBridge lazily (set by squadrantd.ts before\n// any gate message can arrive on the socket).\nimport type { DaemonContext } from \"./context.js\";\n\nexport function createGateResolver(ctx: DaemonContext) {\n return async (taskId: string, payload: unknown): Promise<void> => {\n const rec = ctx.store.listAll().find((r) => r.id === taskId);\n try {\n if (rec?.provider === \"opencode\") {\n // Only an explicit \"approve\" approves; any other reply denies —\n // never auto-approve a permission gate.\n const decision = (payload as { decision?: string })?.decision === \"approve\" ? \"approve\" : \"deny\";\n await ctx.opencodeBridge.answer(taskId, decision);\n } else {\n await ctx.codexDriver.answer(taskId, payload);\n }\n } catch (e) { ctx.log(`gate-resolve answer failed: ${(e as Error).message}`); }\n };\n}\n","// src/control/daemon/server.ts\n// IPC socket server: message router + attach fan-in.\n// All state lives on DaemonContext; callbacks that can't yet be on ctx are\n// passed via ServerHandlers (built once in squadrantd.ts/start.ts).\nimport { startServer, encodeFrame } from \"../protocol.js\";\nimport type { AttachFrame, AttachInbound } from \"../protocol.js\";\nimport type { ComponentHealth } from \"../liveness.js\";\nimport type { DaemonSnapshotInputs } from \"../snapshot.js\";\nimport type { DaemonContext } from \"./context.js\";\n\nexport interface ServerHandlers {\n /** Build per-component health list (optionally filtered to one project). */\n buildHealth: (project?: string) => ComponentHealth[];\n /** Gather full snapshot inputs (all I/O). */\n gatherSnapshotInputs: (now: number) => Promise<DaemonSnapshotInputs>;\n /** Cancel pending gate-promotion timers when a client attaches. */\n cancelPromotionsFor: (taskId: string) => void;\n /** Fan-out an AttachFrame to all clients watching a task. */\n broadcast: (taskId: string, f: AttachFrame) => void;\n}\n\nexport function createServer(\n ctx: DaemonContext,\n handlers: ServerHandlers,\n) {\n const { store, log, attachConns } = ctx;\n const { buildHealth, gatherSnapshotInputs, cancelPromotionsFor, broadcast } = handlers;\n\n return startServer(ctx.sockPath, {\n handler: async (msg: any) => {\n if (msg.kind === \"seed\") { store.put(msg.record); return { ok: true }; }\n // Crew-close teardown for codex: the cmux pane only hosts the `crew attach`\n // renderer — the thread lives on the shared app-server, so closing the pane\n // doesn't reap it. `squadrant crew close` calls this to archive the thread and\n // its per-thread MCP servers (else they leak ~53MB/crew). Fires for terminal\n // and non-terminal crews alike.\n if (msg.kind === \"codex-close\") {\n await ctx.codexDriver.close(msg.taskId).catch((e: unknown) => log(`codex close err: ${e}`));\n return { ok: true };\n }\n // #77 service-health surface: per-component liveness for the queried project (or all).\n if (msg.kind === \"health\") {\n return buildHealth(msg.project as string | undefined);\n }\n // #44 dashboard: read-only full system snapshot (Tier 0/1/2).\n if (msg.kind === \"snapshot\") {\n const now = Date.now();\n const { assembleDaemonSnapshot } = await import(\"../snapshot.js\");\n return assembleDaemonSnapshot(await gatherSnapshotInputs(now), now);\n }\n if (msg.kind === \"event\") {\n return ctx.d.handle(msg);\n }\n return ctx.d.handle(msg);\n },\n onAttach: (conn, frame) => {\n let set = attachConns.get(frame.taskId);\n if (!set) { set = new Set(); attachConns.set(frame.taskId, set); }\n set.add(conn);\n // A client arriving within the 5s window defuses any pending gate timer.\n cancelPromotionsFor(frame.taskId);\n // Immediately ack the attach so the client knows it's live.\n try { conn.write(encodeFrame({ type: \"reattached\", taskId: frame.taskId })); } catch { /* ignore */ }\n },\n onAttachInbound: (_conn, frame) => {\n const f = frame as AttachInbound;\n if (f.op === \"say\")\n void ctx.codexDriver.say(f.taskId, f.text).catch((e: unknown) => log(`say err: ${e}`));\n else if (f.op === \"steer\")\n void ctx.codexDriver.steer(f.taskId, f.text).catch((e: unknown) => log(`steer err: ${e}`));\n else if (f.op === \"interrupt\")\n void ctx.codexDriver.interrupt(f.taskId).catch((e: unknown) => log(`interrupt err: ${e}`));\n else if (f.op === \"answer\")\n void ctx.codexDriver.answer(f.taskId, f.payload).catch((e: unknown) => log(`answer err: ${e}`));\n },\n onAttachClose: (conn) => {\n for (const set of attachConns.values()) set.delete(conn);\n },\n });\n}\n","// src/control/daemon/snapshot-gather.ts\n// Snapshot I/O edge: pure helpers that gather raw inputs for the snapshot verb.\n// Each tolerates missing files and never throws.\nimport { fileURLToPath } from \"node:url\";\nimport { join } from \"node:path\";\nimport {\n statSync, openSync, readSync, closeSync, readdirSync, readFileSync,\n} from \"node:fs\";\nimport type { DaemonSnapshotInputs, ResultArtifacts } from \"../snapshot.js\";\nimport type { TaskRecord } from \"@squadrant/shared\";\n\n// The compiled snapshot-gather.js shares the dist/ build time with squadrantd.js\n// (tsup compiles all entries in the same pass), so its mtime == dist build-time.\nconst SELF_PATH = fileURLToPath(import.meta.url);\n\n/** mtime (epoch ms) of the running daemon's compiled code, for build-freshness. */\nexport function distBuiltAt(): number {\n try { return statSync(SELF_PATH).mtimeMs; } catch { return 0; }\n}\n\n/** Daemon-log error count (last window) + total size. Reads only the tail so a\n * large log never makes the snapshot tick expensive. */\nexport function gatherLogStats(path: string, now: number, windowMs: number): DaemonSnapshotInputs[\"log\"] {\n let sizeBytes = 0;\n try { sizeBytes = statSync(path).size; }\n catch { return { errorCount: 0, sizeBytes: 0, windowMs }; }\n if (sizeBytes === 0) return { errorCount: 0, sizeBytes, windowMs };\n const CAP = 256 * 1024;\n const start = Math.max(0, sizeBytes - CAP);\n const len = sizeBytes - start;\n let text = \"\";\n try {\n const fd = openSync(path, \"r\");\n try {\n const buf = Buffer.alloc(len);\n readSync(fd, buf, 0, len, start);\n text = buf.toString(\"utf-8\");\n } finally { closeSync(fd); }\n } catch { return { errorCount: 0, sizeBytes, windowMs }; }\n const cutoff = now - windowMs;\n let errorCount = 0;\n for (const line of text.split(\"\\n\")) {\n if (!/error|failed/i.test(line)) continue;\n // Lines carry an ISO timestamp (\"[squadrantd] 2026-... msg\"); skip ones older\n // than the window. Lines without a parseable timestamp are counted (conservative).\n const m = line.match(/\\d{4}-\\d{2}-\\d{2}T[\\d:.]+Z/);\n if (m) { const ts = Date.parse(m[0]); if (!Number.isNaN(ts) && ts < cutoff) continue; }\n errorCount++;\n }\n return { errorCount, sizeBytes, windowMs };\n}\n\n/** Per-project store state counts + corrupt/quarantined file count. */\nexport function gatherStoreStats(\n store: { list: (p: string) => TaskRecord[] },\n stateRoot: string,\n project: string,\n): { byState: Record<string, number>; corruptCount: number } {\n const byState: Record<string, number> = {};\n for (const r of store.list(project)) byState[r.state] = (byState[r.state] ?? 0) + 1;\n let corruptCount = 0;\n const dir = join(stateRoot, project);\n try {\n for (const n of readdirSync(dir)) {\n if (n.includes(\".corrupt.\")) { corruptCount++; continue; }\n if (!n.endsWith(\".json\")) continue;\n try { JSON.parse(readFileSync(join(dir, n), \"utf-8\")); }\n catch { corruptCount++; }\n }\n } catch { /* no project dir yet */ }\n return { byState, corruptCount };\n}\n\n/** Global _results/ artifact count + total bytes (unbounded-growth watch). */\nexport function gatherResults(resultsDir: string): ResultArtifacts {\n let fileCount = 0;\n let totalBytes = 0;\n try {\n for (const n of readdirSync(resultsDir)) {\n try {\n const s = statSync(join(resultsDir, n));\n if (s.isFile()) { fileCount++; totalBytes += s.size; }\n } catch { /* vanished mid-scan */ }\n }\n } catch { /* no results dir */ }\n return { fileCount, totalBytes };\n}\n","// Session freshness logic — daily + templateHash rotation.\n// Extracted from packages/cli/src/commands/launch.ts so it can be\n// unit-tested without spawning real processes.\n\nimport crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport interface SessionRecord {\n lastLaunched: string; // YYYY-MM-DD\n templateHash: string;\n}\n\nexport interface SessionsFile {\n workspaces: Record<string, SessionRecord>;\n}\n\nexport function loadSessions(sessionsPath: string): SessionsFile {\n try {\n return JSON.parse(fs.readFileSync(sessionsPath, \"utf-8\")) as SessionsFile;\n } catch {\n return { workspaces: {} };\n }\n}\n\nexport function saveSessions(sessionsPath: string, sessions: SessionsFile): void {\n const dir = path.dirname(sessionsPath);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(sessionsPath, JSON.stringify(sessions, null, 2) + \"\\n\");\n}\n\nexport function computeTemplateHash(role: string, templatesDir: string): string {\n const hash = crypto.createHash(\"sha256\");\n\n const roleFile = path.join(templatesDir, `${role}.claude.md`);\n const legacyRoleFile = path.join(templatesDir, `${role}.CLAUDE.md`);\n if (fs.existsSync(roleFile)) {\n hash.update(fs.readFileSync(roleFile, \"utf-8\"));\n } else if (fs.existsSync(legacyRoleFile)) {\n hash.update(fs.readFileSync(legacyRoleFile, \"utf-8\"));\n }\n\n const pluginSkillsDir = path.join(templatesDir, \"..\", \"plugin\", \"skills\");\n if (fs.existsSync(pluginSkillsDir)) {\n for (const skill of fs.readdirSync(pluginSkillsDir).sort()) {\n const skillFile = path.join(pluginSkillsDir, skill, \"SKILL.md\");\n if (fs.existsSync(skillFile)) {\n hash.update(fs.readFileSync(skillFile, \"utf-8\"));\n }\n }\n }\n\n return hash.digest(\"hex\").slice(0, 16);\n}\n\nexport function shouldStartFresh(\n workspaceName: string,\n role: string,\n opts: { sessionsPath: string; templatesDir: string },\n): { fresh: boolean; reason?: string } {\n const sessions = loadSessions(opts.sessionsPath);\n const record = sessions.workspaces[workspaceName];\n const today = new Date().toISOString().slice(0, 10);\n const currentHash = computeTemplateHash(role, opts.templatesDir);\n\n if (!record) {\n return { fresh: true, reason: \"first launch\" };\n }\n\n if (record.lastLaunched !== today) {\n return { fresh: true, reason: \"new day — starting fresh session\" };\n }\n\n if (record.templateHash !== currentHash) {\n return { fresh: true, reason: \"template instructions updated\" };\n }\n\n return { fresh: false };\n}\n\nexport function recordSession(\n workspaceName: string,\n role: string,\n opts: { sessionsPath: string; templatesDir: string },\n): void {\n const sessions = loadSessions(opts.sessionsPath);\n sessions.workspaces[workspaceName] = {\n lastLaunched: new Date().toISOString().slice(0, 10),\n templateHash: computeTemplateHash(role, opts.templatesDir),\n };\n saveSessions(opts.sessionsPath, sessions);\n}\n","// Pure crew protocol and naming primitives — no I/O, no external-package deps.\n// Extracted from packages/cli/src/commands/crew.ts so they are unit-testable\n// and importable by packages other than cli.\n\n/** Configuration for the post-send acceptance check that replaces a naive\n * screen-changed comparison. For agents whose idle splash keeps mutating\n * (opencode's \"Ask anything…\" with blinking cursor / status line), the old\n * check would always see a different screen and never re-send a dropped turn. */\nexport interface TurnAcceptanceConfig {\n /** Text that identifies the idle splash state. When set, acceptance requires\n * this marker to be absent from the screen (e.g. \"Ask anything…\" for opencode).\n * Without it, acceptance defaults to \"screen changed\" (claude behavior). */\n splashMarker?: string;\n /** Max rounds of \"wait, check, re-send\" after the initial send. Defaults to 2\n * (initial + 1 re-send) to match the pre-retry behavior for claude. Use 3 for\n * opencode which has a wider boot-race window. */\n retryLimit?: number;\n}\n\n/** Normalizes screen/marker text for splash-marker matching: case-insensitive,\n * whitespace-collapsed, and treats the single-char ellipsis (U+2026) and \"...\"\n * interchangeably. opencode's idle-splash wording rotates through example\n * prompts and has drifted in exact glyph/punctuation across versions (#499:\n * the hardcoded \"Ask anything…\" (U+2026) never matched real \"Ask anything...\"\n * (three ASCII dots) renders), so matching the literal string is unsafe —\n * match a stable substring instead. */\nfunction normalizeForSplashMatch(text: string): string {\n return text.toLowerCase().replace(/…/g, \"...\").replace(/\\s+/g, \" \").trim();\n}\n\n/** True when `marker` appears in `screen` under splash-match normalization. */\nexport function screenHasSplashMarker(screen: string, marker: string): boolean {\n return normalizeForSplashMatch(screen).includes(normalizeForSplashMatch(marker));\n}\n\n/** Pure-function decision: was the first turn accepted by the TUI?\n * - With splashMarker: accepted = the marker is no longer visible (the TUI left\n * its idle splash, confirming the keystroke was received).\n * - Without splashMarker (claude): accepted = the screen changed after sending.\n *\n * Callers on the splash path MUST additionally gate on having observed the\n * splash marker at least once before trusting \"marker absent\" as acceptance\n * (see crew-pane.ts's sawSplash latch) — a marker that never matches (drift,\n * misconfiguration) would otherwise make this return true from the first\n * check, before any keystroke lands (#499). */\nexport function isTurnAccepted(\n preSendScreen: string,\n afterScreen: string,\n config?: TurnAcceptanceConfig,\n): boolean {\n if (config?.splashMarker) {\n return !screenHasSplashMarker(afterScreen, config.splashMarker);\n }\n return afterScreen !== preSendScreen;\n}\n\n/** Builds the completion-protocol suffix baked into claude + opencode first turns (#278).\n * Substituting --task-id and --project at source makes the signal robust to env-var\n * races (Mode 1) and gives the model a concrete imperative at the point of action (Mode 2).\n *\n * WARNING: The exact output text is load-bearing — a single byte change silently\n * breaks crew DONE. Any modification must be validated against the crew-lifecycle\n * checklist CP-DONE checkpoint. A snapshot test guards against drift. */\nexport function buildCompletionProtocol(taskId: string, project: string): string {\n return [\n \"---\",\n \"COMPLETION PROTOCOL (required): When this task is fully complete, your FINAL action MUST be to run exactly:\",\n ` squadrant crew signal done --task-id ${taskId} --project ${project} --message \"<one-line summary>\"`,\n \"Run it as a discrete final step AFTER you report your results. If you are blocked or need a decision, instead run:\",\n ` squadrant crew signal blocked --task-id ${taskId} --project ${project} --question \"<your question>\"`,\n \"If this task failed because of a defect in squadrant itself (not an API/infra blip, a config/user error, or an expected failure), say so in your signal done/blocked message so the captain can check tu11aa/squadrant and file it. Don't file issues from the crew.\",\n ].join(\"\\n\");\n}\n\n// POSIX single-quote a path so it is safe to embed in a shell command even\n// when the path contains spaces or special characters.\nexport function shellQuote(p: string): string {\n return \"'\" + p.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n\nexport function titleFor(project: string, name: string): string {\n return `🔧 ${project}:${name}`;\n}\n\n// #387: crews run arbitrary CPU-heavy commands (npm run build && npm test) at\n// their own discretion — squadrant never sees or controls those invocations,\n// so there's no central point to queue or cap them. `nice` sidesteps that:\n// applied to the crew's top-level CLI process at launch, every child it later\n// forks (tsc, vitest workers, pnpm) inherits the lowered scheduling priority.\n// Under N concurrent crews this keeps the OS scheduler favoring cmux/the\n// daemon's control-plane process over crew compute, so a burst of crew builds\n// slows down instead of starving the process that both crews depend on to stay\n// reachable. NICE_LEVEL 10 is a moderate deprioritization (range -20..19,\n// default 0) — enough to yield under contention without idling crew work when\n// the machine is otherwise quiet.\nconst CREW_NICE_LEVEL = 10;\n\nexport function niceCrewCommand(cmd: string): string {\n return `nice -n ${CREW_NICE_LEVEL} ${cmd}`;\n}\n\nexport function isCrewTitle(project: string, title: string): boolean {\n return title.startsWith(`🔧 ${project}:`);\n}\n\nexport function nameFromTitle(project: string, title: string): string {\n return title.slice(`🔧 ${project}:`.length);\n}\n\nexport function nextAutoName(existingTitles: string[], project: string): string {\n const used = new Set<number>();\n for (const title of existingTitles) {\n const n = nameFromTitle(project, title).match(/^crew-(\\d+)$/);\n if (n) used.add(Number(n[1]));\n }\n let i = 1;\n while (used.has(i)) i++;\n return `crew-${i}`;\n}\n","// Crew child-process lifecycle management.\n// Extracted from packages/cli/src/commands/crew.ts so it is importable from\n// packages other than cli and testable with an injected exec function.\n\nimport { exec as nodeExec } from \"node:child_process\";\n\ntype ExecFn = (\n cmd: string,\n opts: { maxBuffer: number },\n cb: (err: Error | null, stdout: string) => void,\n) => void;\n\n/** Kill every process that inherited SQUADRANT_CREW_TASK_ID=<taskId> from the\n * crew's shell env prefix. Uses `ps auxE` which exposes env vars for node\n * processes on macOS (vitest workers, the crew CLI, etc.). Best-effort:\n * swallows all errors so a childless crew still closes cleanly.\n *\n * @param graceMs - ms between SIGTERM and SIGKILL (default 2 s; pass a short\n * value in tests to avoid waiting)\n * @param execFn - injectable for testing; defaults to node:child_process.exec\n */\nexport async function reapCrewChildren(\n taskId: string,\n graceMs = 2000,\n execFn: ExecFn = nodeExec,\n): Promise<void> {\n const marker = `SQUADRANT_CREW_TASK_ID=${taskId}`;\n try {\n const stdout = await new Promise<string>((resolve, reject) => {\n // `ps auxE` dumps every process's full env, which on a busy machine far\n // exceeds exec's default 1 MB maxBuffer (~2.7 MB with ~1k procs). Without\n // a raised cap the call errors with \"maxBuffer length exceeded\", the outer\n // catch swallows it, and the reap silently no-ops — leaving crew children\n // alive. 64 MB comfortably covers thousands of processes.\n execFn(\"ps auxE\", { maxBuffer: 64 * 1024 * 1024 }, (err, out) =>\n err ? reject(err) : resolve(out),\n );\n });\n const pids: number[] = [];\n for (const line of stdout.split(\"\\n\").slice(1)) {\n if (!line.includes(marker)) continue;\n const pid = parseInt(line.trim().split(/\\s+/)[1], 10);\n if (!isNaN(pid) && pid !== process.pid) pids.push(pid);\n }\n if (pids.length === 0) return;\n for (const pid of pids) {\n try { process.kill(pid, \"SIGTERM\"); } catch { /* already gone */ }\n }\n await new Promise<void>((r) => setTimeout(r, graceMs));\n for (const pid of pids) {\n try { process.kill(pid, \"SIGKILL\"); } catch { /* already gone */ }\n }\n } catch { /* best-effort */ }\n}\n","// Pure auth predicates for the Telegram CONTROL surfaces (auto-launch, general\n// commands). No I/O. Fail-closed: control requires both the master switch and a\n// user-id match — chat membership alone is never enough for control.\nimport type { TelegramConfig } from \"@squadrant/shared\";\n\nexport function isControlEnabled(cfg: TelegramConfig): boolean {\n return cfg.remoteControl === true;\n}\n\nexport function isAuthorized(fromId: number | undefined, cfg: TelegramConfig): boolean {\n if (fromId === undefined) return false;\n return Array.isArray(cfg.users) && cfg.users.includes(fromId);\n}\n","// Curated registry for the Telegram GENERAL command channel (#402). Pure logic:\n// parses \"/cmd args\" into a squadrant CLI argv vector — never a shell string. No\n// I/O, no execution (Task 5 wires argv → async execFile). Default-deny on\n// /config set: only WRITABLE_CONFIG_KEYS may be written over Telegram, so secrets\n// (botToken/users/chats/supergroupId) can never be set from the phone.\n//\n// argv tokens are verified against the real CLI (packages/cli/src/commands/):\n// status → `status`, projects → `projects list`, crews → `crew list <p>`,\n// launch → `launch <p> --headless`, effort → `effort [mode]`, config → `config get|set`,\n// spawn → `crew spawn <p> <task>`.\n\nexport type ParsedCommand =\n | { kind: \"ok\"; name: string; argv: string[] } // argv to pass to the squadrant CLI\n | { kind: \"usage\"; name: string; message: string } // known command, bad args\n | { kind: \"unknown\"; message: string } // not in registry / not a slash command\n | { kind: \"denied\"; message: string }; // e.g. /config set on a protected key\n\n/** Default-deny allowlist of config keys writable over Telegram (#321). Starts\n * intentionally tiny; extend deliberately. Secrets are NEVER added here. */\nexport const WRITABLE_CONFIG_KEYS: readonly string[] = [\"defaults.effort\"];\n\nconst EFFORT_MODES = new Set([\"max\", \"balance\", \"low\"]);\n\ninterface Entry {\n /** Build the argv (or a usage/denied result) from the post-name token list. */\n build(args: string[]): ParsedCommand;\n usage: string;\n}\n\nfunction ok(name: string, argv: string[]): ParsedCommand {\n return { kind: \"ok\", name, argv };\n}\nfunction usage(name: string, message: string): ParsedCommand {\n return { kind: \"usage\", name, message };\n}\n\nconst REGISTRY: Record<string, Entry> = {\n status: { usage: \"/status\", build: () => ok(\"status\", [\"status\"]) },\n projects: { usage: \"/projects\", build: () => ok(\"projects\", [\"projects\", \"list\"]) },\n crews: {\n usage: \"/crews <project>\",\n build: (a) => (a[0] ? ok(\"crews\", [\"crew\", \"list\", a[0]]) : usage(\"crews\", \"usage: /crews <project>\")),\n },\n launch: {\n // --headless (#586, same reason as #520 on the boot-if-down path): runCommand\n // execs this argv from the daemon, which has no CMUX_WORKSPACE_ID and no\n // terminal — a plain `launch` would open the cmux GUI app and exit 0 before\n // the workspace is ever launched.\n usage: \"/launch <project>\",\n build: (a) =>\n a[0] ? ok(\"launch\", [\"launch\", a[0], \"--headless\"]) : usage(\"launch\", \"usage: /launch <project>\"),\n },\n effort: {\n usage: \"/effort [max|balance|low]\",\n build: (a) => {\n if (a.length === 0) return ok(\"effort\", [\"effort\"]);\n if (!EFFORT_MODES.has(a[0])) return usage(\"effort\", \"usage: /effort [max|balance|low]\");\n return ok(\"effort\", [\"effort\", a[0]]);\n },\n },\n config: {\n usage: \"/config get <key> | /config set <key> <value>\",\n build: (a) => {\n const sub = a[0];\n if (sub === \"get\") {\n const key = a[1];\n if (!key) return usage(\"config\", \"usage: /config get <key>\");\n return ok(\"config\", [\"config\", \"get\", key]);\n }\n if (sub === \"set\") {\n const key = a[1];\n const value = a.slice(2).join(\" \");\n if (!key || value === \"\") return usage(\"config\", \"usage: /config set <key> <value>\");\n if (!WRITABLE_CONFIG_KEYS.includes(key)) {\n return {\n kind: \"denied\",\n message: `⛔ '${key}' is not writable over Telegram. Allowed: ${WRITABLE_CONFIG_KEYS.join(\", \")}`,\n };\n }\n return ok(\"config\", [\"config\", \"set\", key, value]);\n }\n return usage(\"config\", \"usage: /config get <key> | /config set <key> <value>\");\n },\n },\n spawn: {\n usage: \"/spawn <project> <task...>\",\n build: (a) => {\n const project = a[0];\n const task = a.slice(1).join(\" \");\n if (!project || task === \"\") return usage(\"spawn\", \"usage: /spawn <project> <task...>\");\n return ok(\"spawn\", [\"crew\", \"spawn\", project, task]);\n },\n },\n mute: {\n usage: \"/mute <project>\",\n build: (a) => (a[0] ? ok(\"mute\", [\"telegram\", \"notify\", a[0], \"off\"]) : usage(\"mute\", \"usage: /mute <project>\")),\n },\n unmute: {\n usage: \"/unmute <project>\",\n build: (a) => (a[0] ? ok(\"unmute\", [\"telegram\", \"notify\", a[0], \"on\"]) : usage(\"unmute\", \"usage: /unmute <project>\")),\n },\n};\n\nfunction helpText(): string {\n const lines = Object.values(REGISTRY).map((e) => ` ${e.usage}`);\n return [\"Available commands:\", ...lines, \" /help\"].join(\"\\n\");\n}\n\n/** Strip the `@botname` suffix Telegram appends to menu-tapped commands in groups. */\nexport function stripBotMention(token: string): string {\n return token.split(\"@\")[0];\n}\n\n/** Parse a raw Telegram message into a curated command. Non-slash text and\n * unregistered commands return `unknown`. */\nexport function parseCommand(text: string): ParsedCommand {\n const trimmed = text.trim();\n if (!trimmed.startsWith(\"/\")) {\n return { kind: \"unknown\", message: \"unknown command — send /help\" };\n }\n const tokens = trimmed.slice(1).split(/\\s+/).filter((t) => t.length > 0);\n const name = stripBotMention(tokens[0] ?? \"\").toLowerCase();\n const args = tokens.slice(1);\n\n if (name === \"help\") {\n return { kind: \"usage\", name: \"help\", message: helpText() };\n }\n const entry = REGISTRY[name];\n if (!entry) {\n return { kind: \"unknown\", message: `unknown command '/${name}' — send /help` };\n }\n return entry.build(args);\n}\n","// Daemon-side capabilities for the Telegram control surfaces (#402/#403). The\n// CLI layer owns process spawning + socket access; the core bridge only sees the\n// injected closures. EVERYTHING that shells out uses async execFile (promisified)\n// with an argv array — never *Sync on the daemon poll path (event-loop\n// starvation, learning #2) and never a shell string (argv already validated by\n// parseCommand).\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { sendRequest } from \"../protocol.js\";\nimport type { ComponentHealth } from \"../liveness.js\";\n\nconst pExecFile = promisify(execFile);\n\n// Telegram message hard limit is 4096 chars; cap below it with headroom for the\n// truncation marker + any reply framing.\nconst MAX_OUTPUT = 3500;\n\n/** Combine a command's stdout/stderr into one capped, human-readable reply. */\nexport function capOutput(stdout: string, stderr: string, max = MAX_OUTPUT): string {\n const out = stdout.trim();\n const err = stderr.trim();\n let combined = out;\n if (err) combined = combined ? `${combined}\\n[stderr] ${err}` : `[stderr] ${err}`;\n if (!combined) combined = \"(no output)\";\n if (combined.length > max) combined = combined.slice(0, max) + \"\\n…[truncated]\";\n return combined;\n}\n\nconst COMMAND_TIMEOUT_MS = 60_000;\n\n/** Run a curated squadrant CLI argv via async execFile, returning capped output.\n * argv is the validated vector from parseCommand — passed as an array (no shell). */\nexport function createRunCommand(cliBin: string): (argv: string[]) => Promise<string> {\n return async (argv: string[]) => {\n try {\n const { stdout, stderr } = await pExecFile(\n process.execPath,\n [cliBin, ...argv],\n { timeout: COMMAND_TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024 },\n );\n return capOutput(stdout ?? \"\", stderr ?? \"\");\n } catch (e) {\n // execFile rejects on non-zero exit / timeout; surface its captured output.\n const err = e as { stdout?: string; stderr?: string; message?: string };\n return capOutput(err.stdout ?? \"\", err.stderr ?? err.message ?? \"command failed\");\n }\n };\n}\n\n/** Pure: a captain counts alive ONLY in state \"alive\" — stopped (closed),\n * gone (crashed), and unknown/missing all mean \"not alive\" → boot (#517). */\nexport function isCaptainAliveFromHealth(rows: ComponentHealth[], project: string): boolean {\n return rows.some((h) => h.kind === \"captain\" && h.project === project && h.state === \"alive\");\n}\n\n/** Liveness probe via the daemon health endpoint (mirrors group.ts isCaptainAlive). */\nexport function createIsCaptainAlive(sock: string): (project: string) => Promise<boolean> {\n return async (project: string) => {\n try {\n const health = (await sendRequest(sock, { kind: \"health\", project }, 5000)) as ComponentHealth[];\n return isCaptainAliveFromHealth(health ?? [], project);\n } catch {\n return false;\n }\n };\n}\n\n/** Boot a captain via async execFile (NEVER execSync on the daemon hot path).\n * --headless (#520): the daemon has no CMUX_WORKSPACE_ID and no terminal, so\n * a plain `squadrant launch` would open the cmux GUI app and exit 0 without\n * ever creating a workspace. --headless makes launch drive runtime.spawn\n * directly instead. `log`, when given, records the subprocess's captured\n * output (or failure) so a broken launch leaves a diagnostic trail instead\n * of failing silently while ensureCaptainAlive polls to a timeout. */\nexport function createLaunch(cliBin: string, log?: (m: string) => void): (project: string) => Promise<void> {\n return (project: string) =>\n new Promise<void>((resolve, reject) => {\n execFile(\n process.execPath,\n [cliBin, \"launch\", project, \"--headless\"],\n { timeout: 30_000 },\n (err, stdout, stderr) => {\n const output = capOutput(stdout ?? \"\", stderr ?? \"\");\n if (err) {\n log?.(`launch ${project} failed: ${output}`);\n reject(err);\n return;\n }\n if (output !== \"(no output)\") log?.(`launch ${project}: ${output}`);\n resolve();\n },\n );\n });\n}\n","// Boot-if-down capability for Telegram auto-launch (#403). Mirrors the\n// `group dispatch` warmup pattern (liveness probe → spawn `squadrant launch` →\n// bounded warmup poll) but as an injectable factory: deps are stubbed in tests\n// and wired from the daemon host (Task 5). The bridge stays decoupled from\n// captain lifecycle — it only sees the returned `ensure(project)` closure.\n//\n// Debounce: concurrent calls for the same project share ONE launch + poll loop\n// via an in-flight promise map, so a burst of inbound messages can't spawn N\n// captains. The map entry clears on resolution (alive | launched | timeout).\n\nexport type EnsureResult = \"alive\" | \"launched\" | \"timeout\";\n\nexport interface EnsureCaptainDeps {\n isAlive: (project: string) => Promise<boolean>; // liveness probe\n launch: (project: string) => Promise<void>; // spawn `squadrant launch <project>`\n warmupTimeoutMs?: number; // default 120_000\n pollMs?: number; // default 1_000\n sleep?: (ms: number) => Promise<void>; // injectable for tests\n now?: () => number; // injectable for tests\n}\n\nconst DEFAULT_WARMUP_TIMEOUT_MS = 120_000;\nconst DEFAULT_POLL_MS = 1_000;\nconst defaultSleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\n\nexport function createEnsureCaptainAlive(\n deps: EnsureCaptainDeps,\n): (project: string) => Promise<EnsureResult> {\n const warmupTimeoutMs = deps.warmupTimeoutMs ?? DEFAULT_WARMUP_TIMEOUT_MS;\n const pollMs = deps.pollMs ?? DEFAULT_POLL_MS;\n const sleep = deps.sleep ?? defaultSleep;\n const now = deps.now ?? (() => Date.now());\n\n const inFlight = new Map<string, Promise<EnsureResult>>();\n\n async function run(project: string): Promise<EnsureResult> {\n if (await deps.isAlive(project)) return \"alive\";\n await deps.launch(project);\n const deadline = now() + warmupTimeoutMs;\n while (now() < deadline) {\n if (await deps.isAlive(project)) return \"launched\";\n await sleep(pollMs);\n }\n return \"timeout\";\n }\n\n return function ensure(project: string): Promise<EnsureResult> {\n // The guard is read+set synchronously (no await before set) so concurrent\n // callers for the same project provably share a single launch.\n const existing = inFlight.get(project);\n if (existing) return existing;\n const p = run(project).finally(() => inFlight.delete(project));\n inFlight.set(project, p);\n return p;\n };\n}\n","// Pure formatters for the Telegram bridge. No I/O, no side effects.\nimport type { ControlEvent } from \"@squadrant/shared\";\n\n/** Forum-topic title for a project. v1 uses the project name verbatim. */\nexport function topicName(project: string): string {\n return project;\n}\n\n/** Outbound text pushed to a project's Telegram topic for a lifecycle event. */\nexport function formatLifecycle(project: string, ev: ControlEvent): string {\n switch (ev.type) {\n case \"task.done\":\n return `✅ [${project}] CREW DONE · ${ev.id}` + (ev.message ? `\\n${ev.message}` : \"\");\n case \"task.blocked\":\n return `🚧 [${project}] CREW BLOCKED · ${ev.id}\\n${ev.question}`;\n case \"task.review\":\n return `👀 [${project}] CREW REVIEW · ${ev.id}` + (ev.message ? `\\n${ev.message}` : \"\");\n case \"task.idle\":\n return `💤 [${project}] CREW IDLE · ${ev.id}`;\n case \"task.failed\":\n return `❌ [${project}] CREW FAILED · ${ev.id}\\n${ev.error}`;\n case \"task.approval.requested\":\n return `🔐 [${project}] APPROVAL NEEDED · ${ev.id}\\n${ev.question}`;\n case \"task.input.requested\":\n return `❓ [${project}] INPUT NEEDED · ${ev.id}\\n${ev.question}`;\n case \"task.timeout\":\n return `⏱️ [${project}] CREW TIMEOUT · ${ev.id}`;\n default:\n return `ℹ️ [${project}] ${ev.type} · ${ev.id}`;\n }\n}\n\n/** Captain-pane rendering of an inbound Telegram reply — labeled as external. */\nexport function formatInbound(text: string): string {\n return `📩 [from Telegram] ${text}`;\n}\n\n/** Mask all but the last 4 characters of a bot token for safe display. */\nexport function maskToken(token: string): string {\n if (token.length <= 4) return token;\n return \"*\".repeat(token.length - 4) + token.slice(-4);\n}\n","// Persisted Telegram bridge state: getUpdates offset + (project,scope) → topicId\n// registry. Synchronous JSON in stateRoot/telegram-state.json.\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport interface TelegramState {\n offset: number;\n /** key = `${project}::${scope}` (see topicKey); value = message_thread_id. */\n topics: Record<string, number>;\n /** key = project; value = true when active. Absent/false = MUTED (default). */\n notify: Record<string, boolean>;\n /** Last seen inbound message sender — populated passively by the bridge poll. */\n lastUserId?: number;\n}\n\nfunction statePath(stateRoot: string): string {\n return path.join(stateRoot, \"telegram-state.json\");\n}\n\n/** Registry key for a topic. v1 only ever uses scope \"project\"; per-crew routing\n * (scope \"crew:<taskId>\") is additive later without a schema change. */\nexport function topicKey(project: string, scope = \"project\"): string {\n return `${project}::${scope}`;\n}\n\nexport function loadState(stateRoot: string): TelegramState {\n try {\n const raw = fs.readFileSync(statePath(stateRoot), \"utf-8\");\n const data = JSON.parse(raw) as Partial<TelegramState>;\n const result: TelegramState = {\n offset: typeof data.offset === \"number\" ? data.offset : 0,\n topics: data.topics ?? {},\n notify: data.notify ?? {},\n };\n if (typeof data.lastUserId === \"number\") result.lastUserId = data.lastUserId;\n return result;\n } catch {\n return { offset: 0, topics: {}, notify: {} };\n }\n}\n\nexport function saveState(stateRoot: string, s: TelegramState): void {\n fs.mkdirSync(stateRoot, { recursive: true });\n fs.writeFileSync(statePath(stateRoot), JSON.stringify(s, null, 2) + \"\\n\");\n}\n\nexport function setTopic(\n stateRoot: string,\n project: string,\n topicId: number,\n scope = \"project\",\n): void {\n const s = loadState(stateRoot);\n s.topics[topicKey(project, scope)] = topicId;\n saveState(stateRoot, s);\n}\n\nexport function isNotifyActive(stateRoot: string, project: string): boolean {\n return loadState(stateRoot).notify[project] === true;\n}\n\nexport function setLastUserId(stateRoot: string, id: number): void {\n const s = loadState(stateRoot);\n s.lastUserId = id;\n saveState(stateRoot, s);\n}\n\nexport function setNotify(stateRoot: string, project: string, active: boolean): void {\n const s = loadState(stateRoot);\n s.notify[project] = active;\n saveState(stateRoot, s);\n}\n\nexport function findProjectByThread(\n stateRoot: string,\n threadId: number,\n): { project: string; scope: string } | null {\n const s = loadState(stateRoot);\n for (const [key, id] of Object.entries(s.topics)) {\n if (id !== threadId) continue;\n const sep = key.indexOf(\"::\");\n if (sep === -1) continue;\n return { project: key.slice(0, sep), scope: key.slice(sep + 2) };\n }\n return null;\n}\n","// Telegram Bot API over plain fetch — no runtime SDK (keeps the tsup single\n// binary lean). @grammyjs/types is a devDependency: type-only, erased at build.\nimport type { Update } from \"@grammyjs/types\";\n\nexport interface TelegramClient {\n /** Long-poll for updates. timeoutSec is the Bot API `timeout` (default 50s). */\n getUpdates(offset: number, timeoutSec?: number): Promise<Update[]>;\n sendMessage(chatId: number, threadId: number | undefined, text: string, replyMarkup?: unknown): Promise<void>;\n /** Answer a callback_query — REQUIRED on every tap path or the spinner hangs ~15s. */\n answerCallbackQuery(callbackQueryId: string, text?: string): Promise<void>;\n /** Replace an existing message's inline keyboard (panel re-render). */\n editMessageReplyMarkup(chatId: number, messageId: number, replyMarkup: unknown): Promise<void>;\n /** Returns the new topic's message_thread_id. */\n createForumTopic(chatId: number, name: string): Promise<number>;\n /** Verify the bot token and return the bot identity. */\n getMe(): Promise<{ id: number; username: string }>;\n /** Register the bot's command menu with Telegram. */\n setMyCommands(commands: Array<{ command: string; description: string }>): Promise<void>;\n /** Send a chat action (e.g. \"typing\") to show activity to the user. */\n sendChatAction(chatId: number, threadId: number | undefined, action: string): Promise<void>;\n}\n\ninterface TgResponse<T> {\n ok: boolean;\n result?: T;\n error_code?: number;\n description?: string;\n}\n\nexport function createTelegramClient(opts: { token: string; fetch?: typeof fetch }): TelegramClient {\n const fetchImpl = opts.fetch ?? fetch;\n const base = `https://api.telegram.org/bot${opts.token}`;\n\n async function call<T>(method: string, body: Record<string, unknown>): Promise<T> {\n const res = await fetchImpl(`${base}/${method}`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n const json = (await res.json()) as TgResponse<T>;\n if (!res.ok || !json.ok) {\n const code = json.error_code ?? res.status;\n const desc = json.description ?? \"unknown error\";\n throw new Error(`telegram ${method} failed (${code}): ${desc}`);\n }\n return json.result as T;\n }\n\n return {\n async getMe() {\n const r = await call<{ id: number; username: string }>(\"getMe\", {});\n return { id: r.id, username: r.username };\n },\n getUpdates(offset, timeoutSec = 50) {\n return call<Update[]>(\"getUpdates\", { offset, timeout: timeoutSec });\n },\n async sendMessage(chatId, threadId, text, replyMarkup) {\n const body: Record<string, unknown> = { chat_id: chatId, text };\n if (threadId !== undefined) body.message_thread_id = threadId;\n if (replyMarkup !== undefined) body.reply_markup = replyMarkup;\n await call<unknown>(\"sendMessage\", body);\n },\n async answerCallbackQuery(callbackQueryId, text) {\n const body: Record<string, unknown> = { callback_query_id: callbackQueryId };\n if (text !== undefined) body.text = text;\n await call<unknown>(\"answerCallbackQuery\", body);\n },\n async editMessageReplyMarkup(chatId, messageId, replyMarkup) {\n await call<unknown>(\"editMessageReplyMarkup\", { chat_id: chatId, message_id: messageId, reply_markup: replyMarkup });\n },\n async createForumTopic(chatId, name) {\n const r = await call<{ message_thread_id: number }>(\"createForumTopic\", { chat_id: chatId, name });\n return r.message_thread_id;\n },\n async setMyCommands(commands) {\n await call<boolean>(\"setMyCommands\", { commands });\n },\n async sendChatAction(chatId, threadId, action) {\n const body: Record<string, unknown> = { chat_id: chatId, action };\n if (threadId !== undefined) body.message_thread_id = threadId;\n await call<unknown>(\"sendChatAction\", body);\n },\n };\n}\n","// Daemon-internal Telegram subsystem (modeled on CmuxEventsBridge). Owns one\n// outbound hook (pushLifecycle) and one inbound getUpdates long-poll. Opt-in and\n// crash-contained: no send/poll error may escape into the daemon.\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport type { ControlEvent, CrewTier, NotifyConfig, TelegramConfig } from \"@squadrant/shared\";\nimport { resolveNotify, loadProjectOverride, saveProjectOverride, loadConfig } from \"@squadrant/shared\";\nimport type { TelegramClient } from \"./client.js\";\nimport { isAuthorized, isControlEnabled } from \"./auth.js\";\nimport { parseCommand, stripBotMention } from \"./commands.js\";\nimport type { EnsureResult } from \"./ensure-captain.js\";\nimport { formatInbound, formatLifecycle, topicName } from \"./format.js\";\nimport { buildSpawnPrompt, effortPanel, notifyPanel, parseCallback, parseSpawnPrompt, projectPicker, spawnPicker, type PickAction } from \"./panels.js\";\nimport { findProjectByThread, loadState, saveState, setLastUserId, setNotify, setTopic, topicKey } from \"./state.js\";\nimport { tierIncludes } from \"./tiers.js\";\n\n/** A Telegram callback_query (button tap). Narrowed to the fields the bridge uses. */\ninterface CallbackQuery {\n id: string;\n from?: { id: number };\n message?: { chat: { id: number }; message_id: number; message_thread_id?: number };\n data?: string;\n}\n\n/** Read-only poll-loop health (B3 — dashboard visibility). A silently-dying\n * getUpdates loop otherwise looks identical to a healthy quiet one from outside. */\nexport interface TelegramBridgeHealth {\n polling: boolean;\n lastSuccessfulPollAt: number | null;\n lastError: string | null;\n lastErrorAt: number | null;\n}\n\nexport interface TelegramBridge {\n start(): void;\n stop(): void;\n /** Outbound, best-effort: a Telegram failure is swallowed (logged), never thrown. */\n pushLifecycle(project: string, ev: ControlEvent): void;\n /** Outbound, best-effort, out-of-band, fault-class alert — for system faults\n * (e.g. #579/#484's DELIVERY STUCK), not routine crew notifications. Bypasses\n * BOTH the crew-tier filter AND per-project mute: mute is a user's choice to\n * silence routine notification *noise* (crew progress/done/blocked), a\n * choice about volume. It was never a choice to hide \"your instructions\n * can't reach the captain\" — an operational fault, not noise. A muted\n * project with a stuck delivery must still alert, or muting silently\n * reintroduces the exact silent-stall bug this alert exists to prevent.\n * Never touches the mailbox/pane path (so it can't itself get stuck). */\n pushRaw(project: string, text: string): void;\n health(): TelegramBridgeHealth;\n}\n\nexport interface TelegramBridgeOptions {\n cfg: TelegramConfig;\n stateRoot: string;\n /** Root for per-project override files. Defaults to ~/.config/squadrant. */\n configRoot?: string;\n client: TelegramClient;\n appendCaptainMessage: (a: { stateRoot: string; project: string; text: string; source: \"telegram\" | \"daemon\" | \"cli\" }) => Promise<void | number>;\n log: (msg: string) => void;\n // ── Control surfaces (#402/#403/#321) — all optional. When undefined the bridge\n // keeps exact v1 behavior (queue-only project topics, General topic dropped).\n /** Boot-if-down before delivering to a project topic. Injected by the daemon host. */\n ensureCaptainAlive?: (project: string) => Promise<EnsureResult>;\n /** Execute a curated squadrant CLI argv and return capped output. */\n runCommand?: (argv: string[]) => Promise<string>;\n /** Post a reply to the General topic (threadId undefined) or a project topic.\n * The optional replyMarkup attaches an inline-button panel (tap-first commands). */\n sendReply?: (threadId: number | undefined, text: string, replyMarkup?: unknown) => Promise<void>;\n}\n\n// Bot API long-poll window. The loop also sleeps cfg.pollMs between iterations so\n// a fast-returning poll can't busy-loop.\nconst LONG_POLL_SEC = 50;\n\nconst sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\n\nconst CREW_TIERS = [\"all\", \"alert_only\", \"done_only\", \"none\"];\n\n// Channel commands that may run in ANY topic (#cmds-anytopic). mute/unmute/notify\n// are intentionally absent: in a project topic they carry topic-scoped semantics\n// (handled before delegating). Matched against the slash-stripped first token.\nconst RECOGNIZED_CHANNEL_COMMANDS = new Set([\"status\", \"projects\", \"crews\", \"launch\", \"effort\", \"spawn\"]);\n\n/** Parse a `/notify crew <tier>` or `/notify cap <on|off>` preference command.\n * Returns null for anything else (ordinary message or malformed). */\nexport function parseNotifyPref(text: string): { dimension: \"crew\" | \"cap\"; value: string } | null {\n const parts = text.trim().split(/\\s+/);\n if (stripBotMention(parts[0] ?? \"\").toLowerCase() !== \"/notify\") return null;\n const dimension = parts[1]?.toLowerCase();\n if ((dimension === \"crew\" || dimension === \"cap\") && parts[2]) return { dimension, value: parts[2].toLowerCase() };\n return null;\n}\n\n/** True for a bare `/spawn` (no project/task args) — the guided-picker trigger.\n * Strips the `@botname` suffix Telegram appends to menu-tapped commands. */\nexport function isBareSpawn(text: string): boolean {\n const trimmed = text.trim();\n if (!trimmed.startsWith(\"/\")) return false;\n const tokens = trimmed.slice(1).split(/\\s+/).filter((t) => t.length > 0);\n return stripBotMention(tokens[0] ?? \"\").toLowerCase() === \"spawn\" && tokens.length === 1;\n}\n\n/** Recognize the two in-topic notification toggles. Returns the desired active\n * state, or null if the text is an ordinary message. */\nexport function notifyToggle(text: string): boolean | null {\n const first = stripBotMention(text.trim().split(/\\s+/)[0] ?? \"\").toLowerCase();\n if (first === \"/unmute\") return true;\n if (first === \"/mute\") return false;\n return null;\n}\n\nexport function createTelegramBridge(opts: TelegramBridgeOptions): TelegramBridge {\n const { cfg, stateRoot, client, appendCaptainMessage, log, ensureCaptainAlive, runCommand, sendReply } = opts;\n const configRoot = opts.configRoot ?? path.join(os.homedir(), \".config\", \"squadrant\");\n const pollMs = cfg.pollMs ?? 1000;\n let running = false;\n let lastSuccessfulPollAt: number | null = null;\n let lastError: string | null = null;\n let lastErrorAt: number | null = null;\n\n function persistOffset(next: number): void {\n const s = loadState(stateRoot);\n s.offset = next;\n saveState(stateRoot, s);\n }\n\n // Resolve (or lazily create) a project's topic and send raw text into it.\n async function sendToTopic(project: string, text: string): Promise<void> {\n let threadId = loadState(stateRoot).topics[topicKey(project)];\n if (threadId === undefined) {\n threadId = await client.createForumTopic(cfg.supergroupId, topicName(project));\n setTopic(stateRoot, project, threadId);\n }\n await client.sendMessage(cfg.supergroupId, threadId, text);\n }\n\n // Outbound: resolve active (live state wins over config default) + crew-tier\n // filter, then resolve (or lazily create) the project's topic and send.\n async function deliverOutbound(project: string, ev: ControlEvent): Promise<void> {\n const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));\n const live = loadState(stateRoot).notify[project]; // boolean | undefined\n const active = live ?? resolved.active;\n if (!active) return; // muted → no topic create, no send\n if (!tierIncludes(resolved.crew, ev.type)) return; // tier filter\n await sendToTopic(project, formatLifecycle(project, ev));\n }\n\n // Outbound, out-of-band, fault-class: bypasses BOTH the crew-tier filter AND\n // mute (see the pushRaw docstring for why mute must not silence a fault).\n async function deliverRawOutbound(project: string, text: string): Promise<void> {\n await sendToTopic(project, text);\n }\n\n // Live notify state for a project: resolved config (built-in→global→override)\n // with the live `active` overlay (state wins over config default).\n function resolveLiveNotify(project: string): NotifyConfig {\n const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));\n const live = loadState(stateRoot).notify[project]; // boolean | undefined\n return { ...resolved, active: live ?? resolved.active };\n }\n\n // Re-render a panel's keyboard, swallowing the Bot API \"message is not\n // modified\" error that fires when the new keyboard equals the old one.\n async function editMarkup(chatId: number, messageId: number, markup: unknown): Promise<void> {\n try {\n await client.editMessageReplyMarkup(chatId, messageId, markup);\n } catch (e) {\n const msg = (e as Error).message;\n if (!/not modified/i.test(msg)) throw e;\n }\n }\n\n // callback_query (inline-button tap). ALWAYS answerCallbackQuery on every path\n // (else the spinner hangs ~15s). Gate on the TAPPER's user-id, never the panel.\n // Render state fresh. Never throw into the poll loop.\n async function handleCallback(cq: CallbackQuery): Promise<void> {\n try {\n if (!cq.data || !cq.message) {\n await client.answerCallbackQuery(cq.id);\n return;\n }\n if (!isControlEnabled(cfg) || !isAuthorized(cq.from?.id, cfg)) {\n await client.answerCallbackQuery(cq.id, \"⛔ not authorized\");\n return;\n }\n const action = parseCallback(cq.data);\n if (!action) {\n await client.answerCallbackQuery(cq.id);\n return;\n }\n const chatId = cq.message.chat.id;\n const messageId = cq.message.message_id;\n\n if (action.t === \"notify\") {\n const resolved = findProjectByThread(stateRoot, cq.message.message_thread_id ?? -1);\n if (!resolved) {\n await client.answerCallbackQuery(cq.id, \"no project for this topic\");\n return;\n }\n const project = resolved.project;\n if (action.dim === \"active\") {\n setNotify(stateRoot, project, action.val === \"on\");\n } else if (action.dim === \"cap\") {\n saveProjectOverride(project, { telegram: { notify: { cap: action.val === \"on\" } } }, configRoot);\n } else {\n saveProjectOverride(project, { telegram: { notify: { crew: action.val as CrewTier } } }, configRoot);\n }\n await client.answerCallbackQuery(cq.id, `✅ ${action.dim} = ${action.val}`);\n await editMarkup(chatId, messageId, notifyPanel(resolveLiveNotify(project)));\n return;\n }\n\n if (action.t === \"effort\") {\n if (runCommand) await runCommand([\"effort\", action.mode]);\n await client.answerCallbackQuery(cq.id, `✅ effort = ${action.mode}`);\n await editMarkup(chatId, messageId, effortPanel(action.mode as \"max\" | \"balance\" | \"low\"));\n return;\n }\n\n if (action.t === \"spawn\") {\n // Send a ForceReply prompt carrying the project; the reply is routed to\n // `crew spawn` statelessly via parseSpawnPrompt (no pending-state map).\n await reply(cq.message.message_thread_id, buildSpawnPrompt(action.project), { force_reply: true, selective: true });\n await client.answerCallbackQuery(cq.id);\n return;\n }\n\n // action.t === \"pick\" — General-topic project actions.\n const { action: act, project } = action;\n if (act === \"cr\") {\n const out = runCommand ? await runCommand([\"crew\", \"list\", project]) : \"(command runner unavailable)\";\n await client.answerCallbackQuery(cq.id);\n await reply(undefined, out);\n } else if (act === \"lc\") {\n if (runCommand) await runCommand([\"launch\", project]);\n await client.answerCallbackQuery(cq.id, `launching ${project}`);\n } else if (act === \"mu\") {\n setNotify(stateRoot, project, false);\n await client.answerCallbackQuery(cq.id, `🔕 muted ${project}`);\n } else {\n setNotify(stateRoot, project, true);\n await client.answerCallbackQuery(cq.id, `🔔 unmuted ${project}`);\n }\n } catch (e) {\n log(`telegram callback failed data=${cq.data}: ${(e as Error).message}`);\n try {\n await client.answerCallbackQuery(cq.id, \"⚠️ failed\");\n } catch {\n /* answer failed too — already logged; never throw into the poll loop */\n }\n }\n }\n\n // Reply best-effort: a send failure must never escape into the poll loop.\n async function reply(threadId: number | undefined, text: string, replyMarkup?: unknown): Promise<void> {\n if (!sendReply) return;\n try {\n // Keep the 2-arg call shape when there's no panel (markup undefined).\n if (replyMarkup !== undefined) await sendReply(threadId, text, replyMarkup);\n else await sendReply(threadId, text);\n } catch (e) {\n log(`telegram reply failed: ${(e as Error).message}`);\n }\n }\n\n /** Current global effort dial (falls back to today's \"balance\"). */\n function currentEffort(): \"max\" | \"balance\" | \"low\" {\n try {\n return loadConfig(path.join(configRoot, \"config.json\")).defaults.effort ?? \"balance\";\n } catch {\n return \"balance\";\n }\n }\n\n /** Registered project names for the General-topic pickers. */\n function projectNames(): string[] {\n try {\n return Object.keys(loadConfig(path.join(configRoot, \"config.json\")).projects);\n } catch {\n return [];\n }\n }\n\n /** Guided /spawn: reply the project picker (works in General or a project topic). */\n async function replySpawnPicker(threadId: number | undefined): Promise<void> {\n const projects = projectNames();\n if (projects.length === 0) {\n await reply(threadId, \"no projects registered\");\n return;\n }\n await reply(threadId, \"Pick a project to spawn a crew on:\", spawnPicker(projects));\n }\n\n // Curated command channel (#402), shared by the General topic (threadId\n // undefined) and project topics (#cmds-anytopic). Fail-closed — a command runs\n // ONLY when remoteControl is on AND the sender is allowlisted. Tap-first: a\n // parameterized command with NO argument replies a button panel instead of a\n // usage error; typed forms (with an arg) fall through to run. Replies land in\n // the given thread; failures are caught here so they can't escape the poll loop.\n async function runChannelCommand(text: string, fromId: number | undefined, threadId: number | undefined): Promise<void> {\n if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {\n await reply(threadId, \"⛔ not authorized\");\n return;\n }\n const tokens = text.trim().slice(1).split(/\\s+/).filter((t) => t.length > 0);\n const name = stripBotMention(tokens[0] ?? \"\").toLowerCase();\n const noArg = tokens.length === 1;\n if (noArg && name === \"effort\") {\n await reply(threadId, \"Effort mode:\", effortPanel(currentEffort()));\n return;\n }\n if (noArg && name === \"spawn\") {\n await replySpawnPicker(threadId);\n return;\n }\n const PICKERS: Record<string, PickAction> = { crews: \"cr\", launch: \"lc\", mute: \"mu\", unmute: \"um\" };\n if (noArg && name in PICKERS) {\n const projects = projectNames();\n if (projects.length === 0) {\n await reply(threadId, \"no projects registered\");\n return;\n }\n await reply(threadId, `Pick a project:`, projectPicker(PICKERS[name], projects));\n return;\n }\n const parsed = parseCommand(text);\n if (parsed.kind !== \"ok\") {\n await reply(threadId, parsed.message);\n return;\n }\n try {\n const out = runCommand ? await runCommand(parsed.argv) : \"(command runner unavailable)\";\n await reply(threadId, out);\n } catch (e) {\n await reply(threadId, `⚠️ command failed: ${(e as Error).message}`);\n log(`telegram command failed argv=${JSON.stringify(parsed.argv)}: ${(e as Error).message}`);\n }\n }\n\n // General topic (no thread id): freeform text gets a /help hint (never silently\n // dropped); slash commands run through the shared channel-command dispatcher.\n async function handleGeneral(text: string, fromId: number | undefined): Promise<void> {\n if (!text.startsWith(\"/\")) {\n await reply(undefined, \"Send /help for commands.\");\n return;\n }\n await runChannelCommand(text, fromId, undefined);\n }\n\n // Project topic: the v1 captain.message flow + Gap-1 auto-launch (#403). When\n // control is off OR the sender isn't allowlisted, behaves exactly as v1\n // (append only). The append throws on delivery-infra failure so the caller can\n // decline to advance the offset (at-least-once); auto-launch failures are\n // contained and never block the append.\n async function handleProjectTopic(text: string, threadId: number, fromId: number | undefined): Promise<void> {\n const resolved = findProjectByThread(stateRoot, threadId);\n if (!resolved) return; // no project bound to this topic\n\n if (isBareSpawn(text)) {\n // Guided /spawn — picker, never appended. Fail-closed like the toggles.\n if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {\n await reply(threadId, \"⛔ not authorized\");\n return;\n }\n await replySpawnPicker(threadId);\n return;\n }\n\n const toggle = notifyToggle(text);\n if (toggle !== null) {\n // Explicit toggle command — fail-closed, never appended as a captain message.\n if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {\n await reply(threadId, \"⛔ not authorized\");\n return;\n }\n setNotify(stateRoot, resolved.project, toggle);\n await reply(threadId, toggle ? `🔔 ${resolved.project} notifications ON` : `🔕 ${resolved.project} notifications OFF`);\n return;\n }\n\n // Any /notify attempt (including an incomplete one like a bare `/notify`) is\n // handled here and NEVER appended as a captain message. The first token is\n // matched after stripping a `@botname` suffix Telegram adds in groups.\n if (stripBotMention(text.trim().split(/\\s+/)[0] ?? \"\").toLowerCase() === \"/notify\") {\n // Fail-closed: only an allowlisted sender under remoteControl may proceed.\n if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {\n await reply(threadId, \"⛔ not authorized\");\n return;\n }\n const pref = parseNotifyPref(text);\n if (pref === null) {\n // Incomplete (bare `/notify`, or a dimension with no value) → tap-first panel.\n // Typed forms (`/notify cap on`) still parse below for power users.\n await reply(threadId, `🔔 ${resolved.project} notifications`, notifyPanel(resolveLiveNotify(resolved.project)));\n return;\n }\n // Deliberate preference change — writes the per-project config file (not live state).\n if (pref.dimension === \"crew\") {\n if (!CREW_TIERS.includes(pref.value)) {\n await reply(threadId, \"crew must be all|alert_only|done_only|none\");\n return;\n }\n saveProjectOverride(resolved.project, { telegram: { notify: { crew: pref.value as never } } }, configRoot);\n } else {\n if (pref.value !== \"on\" && pref.value !== \"off\") {\n await reply(threadId, \"cap must be on|off\");\n return;\n }\n saveProjectOverride(resolved.project, { telegram: { notify: { cap: pref.value === \"on\" } } }, configRoot);\n }\n await reply(threadId, `✅ ${pref.dimension} = ${pref.value}`);\n return;\n }\n\n // Recognized channel commands run in this topic too (#cmds-anytopic), with the\n // reply landing here instead of falling through to a captain message. mute/\n // unmute/notify are handled above (topic-scoped) and excluded from the set.\n const firstTok = stripBotMention(text.trim().split(/\\s+/)[0] ?? \"\").toLowerCase();\n if (firstTok.startsWith(\"/\") && RECOGNIZED_CHANNEL_COMMANDS.has(firstTok.slice(1))) {\n await runChannelCommand(text, fromId, threadId);\n return;\n }\n\n void client.sendChatAction(cfg.supergroupId, threadId, \"typing\").catch((e) => {\n log(`telegram sendChatAction failed: ${(e as Error).message}`);\n });\n setNotify(stateRoot, resolved.project, true); // engagement → auto-unmute (sticky)\n if (ensureCaptainAlive && isControlEnabled(cfg) && isAuthorized(fromId, cfg)) {\n try {\n const r = await ensureCaptainAlive(resolved.project);\n // The ensure() result IS the delivery signal — \"live captain reachable\",\n // not \"message read\" (no cap-side ack protocol). Surfacing it means a\n // false-positive isAlive (#517) fails loud in Telegram instead of silently\n // stranding the message in the mailbox.\n if (r === \"timeout\") {\n await reply(threadId, `❌ couldn't reach ${resolved.project} captain — saved to mailbox, will deliver when you open the workspace.`);\n } else {\n await reply(threadId, `📨 delivered to ${resolved.project} captain`);\n }\n } catch (e) {\n log(`telegram auto-launch failed project=${resolved.project}: ${(e as Error).message}`);\n }\n }\n await appendCaptainMessage({ stateRoot, project: resolved.project, text: formatInbound(text), source: \"telegram\" });\n }\n\n // Inbound: classify by thread id. General topic → command channel; project\n // topic → captain.message (+ auto-launch). Throws only on append failure.\n async function handleUpdate(u: { message?: { chat: { id: number }; message_thread_id?: number; text?: string; from?: { id: number }; reply_to_message?: { text?: string } }; callback_query?: CallbackQuery }): Promise<void> {\n if (u.callback_query) {\n await handleCallback(u.callback_query);\n return;\n }\n const m = u.message;\n if (!m || m.text === undefined) return;\n if (!cfg.chats.includes(m.chat.id)) return; // not an allowlisted chat (coarse filter)\n // Passively capture the sender's user-id for setup auto-population (#user-id).\n if (m.from?.id !== undefined && loadState(stateRoot).lastUserId !== m.from.id) {\n setLastUserId(stateRoot, m.from.id);\n }\n // A reply to a guided-/spawn ForceReply prompt → `crew spawn`, gated, never\n // appended. Runs before the thread-id branch because a reply can land in\n // General (no thread id) OR a project topic.\n const spawnProject = parseSpawnPrompt(m.reply_to_message?.text);\n if (spawnProject) {\n const threadId = m.message_thread_id;\n if (!isControlEnabled(cfg) || !isAuthorized(m.from?.id, cfg)) {\n await reply(threadId, \"⛔ not authorized\");\n return;\n }\n const task = m.text.trim();\n if (!task) {\n await reply(threadId, \"spawn cancelled — empty task\");\n return;\n }\n if (runCommand) await runCommand([\"crew\", \"spawn\", spawnProject, task]);\n await reply(threadId, `🆕 spawning a crew on ${spawnProject}…`);\n return; // NOT appended as a captain message\n }\n if (m.message_thread_id === undefined) {\n await handleGeneral(m.text, m.from?.id);\n return;\n }\n await handleProjectTopic(m.text, m.message_thread_id, m.from?.id);\n }\n\n async function pollLoop(): Promise<void> {\n while (running) {\n try {\n const offset = loadState(stateRoot).offset;\n const updates = await client.getUpdates(offset, LONG_POLL_SEC);\n for (const u of updates) {\n await handleUpdate(u);\n persistOffset(u.update_id + 1);\n }\n lastSuccessfulPollAt = Date.now();\n } catch (e) {\n lastError = (e as Error).message;\n lastErrorAt = Date.now();\n log(`telegram inbound poll failed: ${(e as Error).message}`);\n }\n if (running) await sleep(pollMs);\n }\n }\n\n return {\n start() {\n if (running) return;\n running = true;\n void pollLoop();\n },\n stop() {\n running = false;\n },\n pushLifecycle(project, ev) {\n // Fire-and-forget; all errors swallowed so outbound can never throw into\n // the daemon's notify path.\n void deliverOutbound(project, ev).catch((e) => {\n log(`telegram outbound failed project=${project}: ${(e as Error).message}`);\n });\n },\n pushRaw(project, text) {\n void deliverRawOutbound(project, text).catch((e) => {\n log(`telegram raw push failed project=${project}: ${(e as Error).message}`);\n });\n },\n health() {\n return { polling: running, lastSuccessfulPollAt, lastError, lastErrorAt };\n },\n };\n}\n","// Pure inline-keyboard builders + callback_data codec for tap-first Telegram\n// commands. No I/O — unit-tested independent of the bridge. callback_data is\n// prefix-routed and kept ≤64 bytes (Bot API limit).\nimport type { NotifyConfig, CrewTier } from \"@squadrant/shared\";\n\nexport type InlineButton = { text: string; callback_data: string };\nexport type InlineKeyboard = { inline_keyboard: InlineButton[][] };\n\nexport type PickAction = \"cr\" | \"lc\" | \"mu\" | \"um\";\n\nexport type ParsedCallback =\n | { t: \"notify\"; dim: \"cap\" | \"crew\" | \"active\"; val: string }\n | { t: \"effort\"; mode: string }\n | { t: \"pick\"; action: PickAction; project: string }\n | { t: \"spawn\"; project: string };\n\n/** Prefix the label with a bullet when it represents the current state. */\nconst mark = (on: boolean, label: string): string => (on ? `• ${label}` : label);\n\n// Curated crew-tier subset shown as a pick-one row (done_only is reachable via\n// the typed `/notify crew done_only` form for power users).\nconst TIERS: CrewTier[] = [\"none\", \"alert_only\", \"all\"];\n\nexport function notifyPanel(s: NotifyConfig): InlineKeyboard {\n return {\n inline_keyboard: [\n [{ text: `Captain: ${s.cap ? \"ON\" : \"OFF\"}`, callback_data: `n:cap:${s.cap ? \"off\" : \"on\"}` }],\n TIERS.map((t) => ({ text: mark(s.crew === t, `crew:${t}`), callback_data: `n:crew:${t}` })),\n [{ text: s.active ? \"🔕 Mute topic\" : \"🔔 Unmute\", callback_data: `n:active:${s.active ? \"off\" : \"on\"}` }],\n ],\n };\n}\n\nexport function effortPanel(current: \"max\" | \"balance\" | \"low\"): InlineKeyboard {\n const modes = [\"max\", \"balance\", \"low\"] as const;\n return {\n inline_keyboard: [modes.map((m) => ({ text: mark(current === m, m), callback_data: `e:${m}` }))],\n };\n}\n\nexport function projectPicker(action: PickAction, projects: string[]): InlineKeyboard {\n return { inline_keyboard: projects.map((p) => [{ text: p, callback_data: `${action}:${p}` }]) };\n}\n\n// Guided /spawn (slice 2). The picker emits `sp:<project>`; tapping one sends a\n// ForceReply prompt whose text encodes the project behind SPAWN_PROMPT_PREFIX, so\n// the reply can be routed to `crew spawn` statelessly (no pending-spawn map).\nexport const SPAWN_PROMPT_PREFIX = \"🆕 Reply with the task for a crew on: \";\n\nexport function buildSpawnPrompt(project: string): string {\n return `${SPAWN_PROMPT_PREFIX}${project}`;\n}\n\nexport function parseSpawnPrompt(text: string | undefined): string | null {\n if (!text || !text.startsWith(SPAWN_PROMPT_PREFIX)) return null;\n const project = text.slice(SPAWN_PROMPT_PREFIX.length).trim();\n return project.length > 0 ? project : null;\n}\n\nexport function spawnPicker(projects: string[]): InlineKeyboard {\n return { inline_keyboard: projects.map((p) => [{ text: p, callback_data: `sp:${p}` }]) };\n}\n\nconst PICK_ACTIONS: PickAction[] = [\"cr\", \"lc\", \"mu\", \"um\"];\n\nexport function parseCallback(data: string): ParsedCallback | null {\n const parts = data.split(\":\");\n if (parts[0] === \"n\" && (parts[1] === \"cap\" || parts[1] === \"crew\" || parts[1] === \"active\") && parts[2]) {\n return { t: \"notify\", dim: parts[1], val: parts[2] };\n }\n if (parts[0] === \"e\" && parts[1]) return { t: \"effort\", mode: parts[1] };\n if (PICK_ACTIONS.includes(parts[0] as PickAction) && parts[1]) {\n return { t: \"pick\", action: parts[0] as PickAction, project: parts.slice(1).join(\":\") };\n }\n if (parts[0] === \"sp\" && parts[1]) return { t: \"spawn\", project: parts.slice(1).join(\":\") };\n return null;\n}\n","// Crew notification tier → event-type membership. Tiers are cumulative:\n// done_only ⊂ alert_only ⊂ all. See the layered-notification design.\nimport type { CrewTier } from \"@squadrant/shared\";\n\nconst DONE_ONLY = new Set([\"task.done\", \"task.failed\"]);\nconst ALERTS = new Set([\n ...DONE_ONLY,\n \"task.blocked\",\n \"task.review\",\n \"task.approval.requested\",\n \"task.input.requested\",\n \"task.timeout\",\n]);\n\nexport function tierIncludes(tier: CrewTier, eventType: string): boolean {\n switch (tier) {\n case \"none\": return false;\n case \"done_only\": return DONE_ONLY.has(eventType);\n case \"alert_only\": return ALERTS.has(eventType);\n case \"all\": return true;\n }\n}\n","// Pure helpers for the interactive `squadrant telegram setup` wizard.\n// These are exported for testing with injected dependencies.\n// getUpdates is single-consumer — setup runs before the daemon starts polling (#321).\nimport fs from \"node:fs\";\nimport type { TelegramClient } from \"./client.js\";\nimport { loadState } from \"./state.js\";\nimport { BOT_COMMANDS } from \"./bot-commands.js\";\nimport { restartDaemonIfRunning } from \"../restart-daemon.js\";\nimport type { RestartOutcome } from \"../restart-daemon.js\";\n\n/**\n * Decide whether to reuse an existing supergroup or re-detect via getUpdates.\n * Returns 'reuse' when supergroupId is already configured and --redetect was not passed.\n * Prevents getUpdates conflicts with the running daemon poll (#22205).\n */\nexport function resolveSetupGroup(\n existingSupergroupId: number | undefined,\n opts: { redetect: boolean },\n): \"reuse\" | \"detect\" {\n if (existingSupergroupId !== undefined && !opts.redetect) return \"reuse\";\n return \"detect\";\n}\n\n/**\n * Poll getUpdates until a supergroup message arrives, returning both the chat id\n * and the sender's user id (the latter seeds the control allowlist, #321).\n * Injects `sleep` for testability; never used with real delays in tests.\n */\nexport async function detectGroupAndUser(\n client: TelegramClient,\n opts: { timeoutMs?: number; sleep?: (ms: number) => Promise<void> } = {},\n): Promise<{ supergroupId: number; userId: number | undefined }> {\n const timeoutMs = opts.timeoutMs ?? 60_000;\n const sleep = opts.sleep ?? ((ms: number) => new Promise((r) => setTimeout(r, ms)));\n const deadline = Date.now() + timeoutMs;\n let offset = 0;\n\n while (Date.now() < deadline) {\n const updates = await client.getUpdates(offset, 10);\n for (const u of updates) {\n if (u.update_id >= offset) offset = u.update_id + 1;\n if (u.message?.chat?.type === \"supergroup\") {\n return { supergroupId: u.message.chat.id, userId: u.message.from?.id };\n }\n }\n await sleep(2000);\n }\n\n throw new Error(\"Timed out waiting for the bot to receive a message in a supergroup\");\n}\n\n/** Convenience wrapper that returns only the supergroup id. */\nexport async function detectGroupId(\n client: TelegramClient,\n opts: { timeoutMs?: number; sleep?: (ms: number) => Promise<void> } = {},\n): Promise<number> {\n return (await detectGroupAndUser(client, opts)).supergroupId;\n}\n\nexport function resolveSetupToken(\n existingToken: string | undefined,\n opts: { resetToken: boolean },\n): \"prompt\" | \"try-reuse\" {\n if (opts.resetToken || !existingToken) return \"prompt\";\n return \"try-reuse\";\n}\n\n/**\n * Precedence: explicit --user-id flag > detected userId (first-run getUpdates) >\n * lastUserId persisted in telegram-state.json by the bridge poll (passive capture).\n */\nexport function resolveSetupUserId(\n flagUserId: number | undefined,\n detectedUserId: number | undefined,\n stateRoot: string,\n): number | undefined {\n return flagUserId ?? detectedUserId ?? loadState(stateRoot).lastUserId;\n}\n\nexport async function runRegisterCommands(opts: { client: TelegramClient }): Promise<void> {\n await opts.client.setMyCommands(BOT_COMMANDS);\n}\n\nexport function runTelegramPostSetup(opts: {\n doRestart?: (o: { reason: string }) => RestartOutcome;\n}): void {\n const doRestart = opts.doRestart ?? restartDaemonIfRunning;\n const outcome = doRestart({ reason: \"telegram config\" });\n if (outcome === \"skipped-not-running\") {\n console.log(\"(daemon not running — change applies on next start)\");\n } else if (outcome === \"skipped-opt-out\") {\n console.log(\"(run 'squadrant heal daemon' to apply)\");\n }\n}\n\n/**\n * Write or update the telegram block in a squadrant config file.\n * Preserves all existing keys; creates the file with defaults if absent.\n */\nexport function writeTelegramConfig(\n configPath: string,\n opts: { token: string; supergroupId: number; users?: number[]; remoteControl?: boolean },\n): void {\n let config: Record<string, unknown>;\n let raw: string | null = null;\n\n try {\n raw = fs.readFileSync(configPath, \"utf-8\");\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw new Error(`refusing to overwrite unreadable config at ${configPath}: ${String(err)}`);\n }\n }\n\n if (raw !== null) {\n try {\n config = JSON.parse(raw) as Record<string, unknown>;\n } catch (err: unknown) {\n throw new Error(`refusing to overwrite corrupt config at ${configPath}: ${String(err)}`);\n }\n } else {\n config = {};\n }\n\n // Idempotent: re-running setup updates the token/group but preserves existing\n // control fields (users/remoteControl) unless this run supplies new ones.\n const prev = (config.telegram && typeof config.telegram === \"object\")\n ? (config.telegram as Record<string, unknown>) : {};\n const next: Record<string, unknown> = {\n botToken: opts.token,\n supergroupId: opts.supergroupId,\n chats: [opts.supergroupId],\n };\n const users = opts.users ?? prev.users;\n const remoteControl = opts.remoteControl ?? prev.remoteControl;\n if (users !== undefined) next.users = users;\n if (remoteControl !== undefined) next.remoteControl = remoteControl;\n config.telegram = next;\n\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + \"\\n\");\n}\n","import { execFileSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { LABEL, kickstartArgv, tryAcquireDaemonLock, releaseDaemonLock } from \"./launchd.js\";\n\nexport type RestartOutcome = \"restarted\" | \"skipped-not-running\" | \"skipped-opt-out\";\n\nconst DEFAULT_SOCK_PATH = join(homedir(), \".config\", \"squadrant\", \"squadrant.sock\");\n\nfunction defaultIsRunning(): boolean {\n return existsSync(DEFAULT_SOCK_PATH);\n}\n\nfunction defaultRunKickstart(): void {\n const uid = process.getuid?.() ?? 0;\n const target = `gui/${uid}/${LABEL}`;\n if (tryAcquireDaemonLock()) {\n try {\n execFileSync(\"launchctl\", kickstartArgv(target, true), { stdio: \"ignore\" });\n } finally {\n releaseDaemonLock();\n }\n }\n}\n\nexport function restartDaemonIfRunning(opts: {\n reason: string;\n noRestart?: boolean;\n isRunning?: () => boolean;\n runKickstart?: () => void;\n env?: NodeJS.ProcessEnv;\n log?: (m: string) => void;\n}): RestartOutcome {\n const env = opts.env ?? process.env;\n if (env[\"VITEST\"] || opts.noRestart) return \"skipped-opt-out\";\n\n const isRunning = opts.isRunning ?? defaultIsRunning;\n if (!isRunning()) return \"skipped-not-running\";\n\n const log = opts.log ?? console.log;\n log(`↻ restarting daemon to apply ${opts.reason}…`);\n const runKickstart = opts.runKickstart ?? defaultRunKickstart;\n runKickstart();\n return \"restarted\";\n}\n","// Cross-project dispatch orchestration (#246/#367; hard group-gate relaxed for\n// cross-project ping & dispatch). Pure-ish library function: validation +\n// boot-if-down (same-group only) + record-task.\n// CLI-edge concerns (shelling out to `squadrant launch`) are injected via bootCaptain.\n\nimport { randomUUID } from \"node:crypto\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { loadConfig, resolveHome, type SquadrantConfig } from \"@squadrant/shared\";\nimport { sendRequest } from \"./protocol.js\";\nimport type { TaskRecord, Provider, Mode } from \"@squadrant/shared\";\n\nconst DEFAULT_SOCK_PATH = join(homedir(), \".config\", \"squadrant\", \"squadrant.sock\");\n\n// #288: cold captain boot takes 45-90s; 120s gives the full chain comfortable headroom.\nexport const GROUP_DISPATCH_WARMUP_TIMEOUT_MS = 120_000;\nexport const GROUP_DISPATCH_WARMUP_POLL_MS = 1_000;\n\n/** Resolve the current project name by matching cwd against config paths. */\nexport function resolveCurrentProject(config: SquadrantConfig): string | null {\n const cwd = process.cwd();\n for (const [name, proj] of Object.entries(config.projects)) {\n const resolvedPath = resolveHome(proj.path);\n if (cwd.startsWith(resolvedPath)) return name;\n }\n return null;\n}\n\n/** Check via the daemon health endpoint whether a project's captain is up. */\nexport async function isCaptainAlive(\n project: string,\n sockPath: string = DEFAULT_SOCK_PATH,\n): Promise<boolean> {\n try {\n const health = (await sendRequest(sockPath, { kind: \"health\", project }, 5000)) as Array<{\n kind: string; project: string; state: string;\n }>;\n const captain = health?.find((h) => h.kind === \"captain\" && h.project === project);\n // Captain rows only ever report \"alive\" | \"stopped\" | \"unknown\" (see\n // liveness.ts projectHealth) — \"stopped\" means the workspace was closed\n // (down), so it must NOT count as alive.\n return captain?.state === \"alive\";\n } catch {\n return false;\n }\n}\n\n/** Poll the daemon health endpoint until the target project's captain is up,\n * or the hard timeout expires. Returns true if warmup succeeded. */\nexport async function waitForWarmup(\n project: string,\n sockPath: string = DEFAULT_SOCK_PATH,\n timeoutMs = GROUP_DISPATCH_WARMUP_TIMEOUT_MS,\n pollMs = GROUP_DISPATCH_WARMUP_POLL_MS,\n): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await isCaptainAlive(project, sockPath)) return true;\n await new Promise((r) => setTimeout(r, pollMs));\n }\n return false;\n}\n\nexport interface GroupDispatchOpts {\n fromProject: string;\n toProject: string;\n task: string;\n provider?: Provider;\n mode?: Mode;\n sockPath?: string;\n warmupTimeoutMs?: number;\n warmupPollMs?: number;\n /** CLI-edge: shells out to launch the target captain. Injected by the command handler. */\n bootCaptain?: (project: string) => Promise<void>;\n}\n\n/**\n * Dispatch a task to any registered project. Validates acceptDelegations,\n * then records the task via the daemon. Same-group targets additionally get\n * boot-if-down (via injected bootCaptain); cross-group targets must already\n * be running — see the same-group check inline below.\n * Dispatch-and-yield: returns immediately after recording.\n */\nexport async function dispatchToSibling(opts: GroupDispatchOpts): Promise<TaskRecord> {\n const config = loadConfig();\n const fromCfg = config.projects[opts.fromProject];\n const toCfg = config.projects[opts.toProject];\n\n if (!toCfg) {\n throw new Error(`target project '${opts.toProject}' not found in config`);\n }\n\n // #246/#367: dispatch reaches any registered project. Same group only grants\n // the richer guarantees below (auto-accept default, boot-if-down); it is no\n // longer a hard gate on whether dispatch is allowed at all.\n const sameGroup = !!fromCfg?.group && !!toCfg.group && fromCfg.group === toCfg.group;\n\n // #246: acceptDelegations check (applies regardless of group)\n if (toCfg.acceptDelegations === false) {\n throw new Error(\n `cannot dispatch to '${opts.toProject}': project has acceptDelegations set to false`,\n );\n }\n\n const sockPath = opts.sockPath ?? DEFAULT_SOCK_PATH;\n\n // Ensure target captain is up. Same-group boots via the injected callback;\n // cross-group does not auto-boot — fail fast with a clear next step instead.\n const alive = await isCaptainAlive(opts.toProject, sockPath);\n if (!alive) {\n if (!sameGroup) {\n throw new Error(\n `cannot dispatch to '${opts.toProject}': captain is not running and cross-group ` +\n `dispatch does not auto-boot it. Use 'squadrant ping ${opts.toProject} \"<msg>\"' or ` +\n `start it manually with 'squadrant launch ${opts.toProject}', then retry.`,\n );\n }\n if (opts.bootCaptain) {\n await opts.bootCaptain(opts.toProject);\n }\n const warmed = await waitForWarmup(\n opts.toProject,\n sockPath,\n opts.warmupTimeoutMs,\n opts.warmupPollMs,\n );\n if (!warmed) {\n throw new Error(\n `dispatch to '${opts.toProject}' timed out waiting for captain warmup ` +\n `(>${(opts.warmupTimeoutMs ?? GROUP_DISPATCH_WARMUP_TIMEOUT_MS) / 1000}s)`,\n );\n }\n }\n\n // Record the task via the daemon (dispatch-and-yield)\n const now = Date.now();\n const attemptId = randomUUID();\n const record: TaskRecord = {\n id: randomUUID(),\n project: opts.toProject,\n originProject: opts.fromProject,\n provider: opts.provider ?? \"claude\",\n mode: opts.mode ?? \"headless\",\n state: \"submitted\",\n task: opts.task,\n createdAt: now,\n lastHeartbeat: now,\n lastEvent: \"dispatch\",\n heartbeatBudgetMs: 300000,\n attempts: [{ attemptId, startedAt: now, lastHeartbeatAt: now }],\n };\n\n const result = (await sendRequest(sockPath, { kind: \"dispatch\", record })) as TaskRecord;\n return result;\n}\n","// Side-session orchestration — driver-agnostic algorithm (#367 command-thinning).\n// CLI-edge concerns (concrete driver construction, agent command building,\n// sendFirstTurnWhenReady) are injected as closures; core only imports from\n// @squadrant/shared (and node built-ins).\n\nimport fs from \"node:fs\";\nimport {\n loadConfig,\n type SquadrantConfig,\n type PaneRef,\n type PanePlacement,\n type RuntimeDriver,\n addWorktree,\n removeWorktree,\n worktreePath,\n resolveWorktreeBase,\n} from \"@squadrant/shared\";\nimport { shellQuote } from \"./crew-protocol.js\";\n\n// ─── naming primitives ────────────────────────────────────────────────────────\n// These parallel the crew naming helpers in crew-protocol.ts but use the 🗒\n// prefix. Prefixed with \"side\" to avoid barrel-level name conflicts.\n\nexport function sideTitleFor(project: string, name: string): string {\n return `🗒 ${project}:${name}`;\n}\n\nexport function isSideTitle(project: string, title: string): boolean {\n return title.startsWith(`🗒 ${project}:`);\n}\n\nexport function sideNameFromTitle(project: string, title: string): string {\n return title.slice(`🗒 ${project}:`.length);\n}\n\nexport function sideNextAutoName(existingTitles: string[], project: string): string {\n const used = new Set<number>();\n for (const title of existingTitles) {\n const n = sideNameFromTitle(project, title).match(/^side-(\\d+)$/);\n if (n) used.add(Number(n[1]));\n }\n let i = 1;\n while (used.has(i)) i++;\n return `side-${i}`;\n}\n\n// ─── first-turn builder ───────────────────────────────────────────────────────\n\n/** Builds the first-turn message: topic + injected context the agent needs\n * for handoff (spokeVault, project, role). For debug sessions, scratchWorktree\n * is the isolated worktree path the session is running in. */\nexport function buildSideFirstTurn(\n topic: string,\n project: string,\n role: string,\n spokeVault: string,\n scratchWorktree?: string,\n): string {\n const lines = [\n topic,\n \"\",\n \"---\",\n \"Side-session context (for handoff use):\",\n `Project: ${project}`,\n `Role: ${role}`,\n `Spoke vault: ${spokeVault}`,\n ];\n if (scratchWorktree) {\n lines.push(`Scratch worktree: ${scratchWorktree}`);\n }\n return lines.join(\"\\n\");\n}\n\n// ─── spawn orchestration ──────────────────────────────────────────────────────\n\nconst SIDE_ROLES = [\"research\", \"debug\"] as const;\ntype SideRole = (typeof SIDE_ROLES)[number];\n\nexport interface SideSpawnInput {\n project: string;\n topic: string;\n role: string;\n name?: string;\n direction?: PanePlacement;\n agent?: string; // passed through to CLI — not used by core\n}\n\nexport interface SideSpawnDeps {\n runtime: RuntimeDriver;\n /**\n * CLI-edge factory: called with the resolved spawn CWD (proj.path for research,\n * scratch worktree path for debug) so @squadrant/agents can set workdir correctly.\n */\n agentCmdFactory: (spawnCwd: string) => string;\n /** CLI-edge: deliver the first turn when the agent pane is ready. */\n sendFirstTurn: (pane: PaneRef, firstTurn: string, preLaunchScreen: string) => Promise<{ delivered: boolean }>;\n}\n\nexport async function runSideSpawn(\n input: SideSpawnInput,\n config: SquadrantConfig,\n deps: SideSpawnDeps,\n): Promise<PaneRef> {\n const proj = config.projects[input.project];\n if (!proj) {\n throw new Error(`Project '${input.project}' not found. Run 'squadrant projects list'.`);\n }\n\n if (!SIDE_ROLES.includes(input.role as SideRole)) {\n throw new Error(\n `Unknown side role '${input.role}'. Valid roles: ${SIDE_ROLES.join(\", \")}.`,\n );\n }\n\n const { runtime } = deps;\n\n const captain = await runtime.status(proj.captainName);\n if (!captain) {\n throw new Error(\n `Captain workspace '${proj.captainName}' is not running. Run 'squadrant launch ${input.project}' first.`,\n );\n }\n\n const existing = await runtime.listSurfaces(captain.id);\n const existingTitles = existing\n .filter((s) => s.title && isSideTitle(input.project, s.title))\n .map((s) => s.title!);\n\n if (input.name) {\n const wantTitle = sideTitleFor(input.project, input.name);\n if (existingTitles.includes(wantTitle)) {\n throw new Error(\n `Side session '${input.name}' already exists for ${input.project}.`,\n );\n }\n }\n const name = input.name ?? sideNextAutoName(existingTitles, input.project);\n\n // Debug sessions run in an isolated scratch git worktree so instrumentation\n // edits never touch the captain's checkout. Research sessions share the root\n // checkout. The #279 fix (cd into spawnCwd before launching CLI) applies to both.\n const spawnCwd = input.role === \"debug\"\n ? addWorktree({\n repoRoot: proj.path,\n worktreeDir: config.defaults.worktreeDir ?? \".worktrees\",\n project: input.project,\n name,\n base: resolveWorktreeBase(proj.path),\n })\n : proj.path;\n\n const agentCmd = deps.agentCmdFactory(spawnCwd);\n\n const direction: PanePlacement = input.direction ?? \"tab\";\n const title = sideTitleFor(input.project, name);\n const pane = await runtime.newPane({ workspaceId: captain.id, direction, title });\n\n await runtime.sendToPane(pane, `cd ${shellQuote(spawnCwd)} && ${agentCmd}`);\n const preLaunchScreen = (await runtime.readPaneScreen(pane)) ?? \"\";\n\n const firstTurn = buildSideFirstTurn(\n input.topic,\n input.project,\n input.role,\n proj.spokeVault ?? \"\",\n input.role === \"debug\" ? spawnCwd : undefined,\n );\n await deps.sendFirstTurn(pane, firstTurn, preLaunchScreen);\n\n return { ...pane, title };\n}\n\n// ─── send / list / close ─────────────────────────────────────────────────────\n\nexport async function runSideSend(\n runtime: RuntimeDriver,\n workspaceId: string,\n project: string,\n name: string,\n message: string,\n): Promise<void> {\n const want = sideTitleFor(project, name);\n const surfaces = await runtime.listSurfaces(workspaceId);\n const pane = surfaces.find((s) => s.title === want) ?? null;\n if (!pane) {\n throw new Error(\n `Side session '${name}' not found for ${project}. Run 'squadrant side list ${project}'.`,\n );\n }\n await runtime.sendToPane(pane, message);\n}\n\nexport async function runSideList(\n runtime: RuntimeDriver,\n workspaceId: string,\n project: string,\n): Promise<Array<{ name: string; surfaceId: string }>> {\n const surfaces = await runtime.listSurfaces(workspaceId);\n return surfaces\n .filter((s) => s.title && isSideTitle(project, s.title))\n .map((s) => ({\n name: sideNameFromTitle(project, s.title!),\n surfaceId: s.surfaceId,\n }));\n}\n\nexport async function runSideClose(\n runtime: RuntimeDriver,\n workspaceId: string,\n project: string,\n name: string,\n projPath: string | undefined,\n worktreeDir: string,\n): Promise<void> {\n const want = sideTitleFor(project, name);\n const surfaces = await runtime.listSurfaces(workspaceId);\n const pane = surfaces.find((s) => s.title === want) ?? null;\n if (!pane) {\n throw new Error(\n `Side session '${name}' not found for ${project}. Run 'squadrant side list ${project}'.`,\n );\n }\n await runtime.closePane(pane);\n // Prune the scratch worktree if this was a debug session. Detection is\n // filesystem-based: debug spawns create a worktree at the deterministic path;\n // research spawns do not. If the path exists, remove it (best-effort).\n if (projPath) {\n const wtPath = worktreePath(projPath, worktreeDir, project, name);\n if (fs.existsSync(wtPath)) {\n try {\n removeWorktree(projPath, wtPath);\n } catch (e) {\n process.stderr.write(`(worktree remove failed: ${(e as Error).message})\\n`);\n }\n }\n }\n}\n","// Crew spawn and session orchestration — driver-agnostic algorithm (#367 command-thinning).\n// CLI-edge concerns (concrete driver construction, daemon calls, settings writers,\n// agent commands) are injected as closures; core only imports from @squadrant/shared\n// and core-internal modules. The algorithm is IDENTICAL to the prior crew.ts\n// implementation — zero behavior change.\n\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport {\n type SquadrantConfig,\n loadConfig,\n type TaskRecord,\n type Provider,\n type PaneRef,\n type PanePlacement,\n type RuntimeDriver,\n type ControlEvent,\n addWorktree,\n resolveWorktreeBase,\n removeWorktree,\n TERMINAL_STATES,\n} from \"@squadrant/shared\";\nimport { resolveCrewRoute, type CrewRouteResult } from \"./crew-routing.js\";\nimport {\n buildCompletionProtocol,\n shellQuote,\n niceCrewCommand,\n titleFor,\n isCrewTitle,\n nameFromTitle,\n nextAutoName,\n type TurnAcceptanceConfig,\n} from \"./crew-protocol.js\";\nimport { reapCrewChildren } from \"./crew-lifecycle.js\";\n\nconst TEMPLATES_DIR = path.join(os.homedir(), \".config\", \"squadrant\", \"templates\");\nconst STATE_ROOT = path.join(os.homedir(), \".config\", \"squadrant\", \"state\");\n\n// ─── ResolvedAgent ────────────────────────────────────────────────────────────\n\n/** Minimal agent shape needed by spawn orchestration. CLI constructs from AgentDriver.\n *\n * Note on `buildCommand` typing: AgentDriver (from @squadrant/agents) declares\n * role as Role (a union); this interface uses `string` to avoid importing from\n * agents in core. The only value ever passed at the call sites is \"crew\", which\n * satisfies Role at runtime. CLI callers use `as unknown as ResolvedAgent` to\n * bridge the type gap safely. */\nexport interface ResolvedAgent {\n name: string;\n templateSuffix: string;\n buildCommand(opts: {\n prompt: string;\n workdir: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n role: any;\n promptFile: string;\n interactive: boolean;\n permissionMode?: string;\n model?: string;\n port?: number;\n }): string;\n}\n\n// ─── CrewSpawnInput ───────────────────────────────────────────────────────────\n\nexport interface CrewSpawnInput {\n project: string;\n task: string;\n name?: string;\n direction?: PanePlacement;\n agent?: string;\n approvalPolicy?: string;\n /** Opt-out (#296): run this crew in the root checkout instead of an isolated\n * worktree. Pass true for small/one-off tasks that don't need branch isolation.\n * Default (undefined/false) = isolated worktree — parallel-safe. */\n shared?: boolean;\n /** CP3 opt-in: gate risky tools (bash) so the captain approves them.\n * codex maps this to approvalPolicy='untrusted'; opencode maps it to a\n * bash:\"ask\" per-crew config. Default (false) = fully autonomous. */\n approval?: boolean;\n /** Per-spawn model override — takes precedence over defaults.roles.crew.model. */\n model?: string;\n /** True when --agent was explicitly passed by the caller; suppresses crew routing. */\n agentExplicit?: boolean;\n /** Path to the task file when --task-file was used (not '-' for stdin). Set by\n * the CLI so runCrewSpawn can copy the file into the isolated worktree root,\n * enabling the crew to `Read ./<basename>` without hunting the main checkout (#458).\n * Ignored for --shared spawns and when absent. */\n taskFile?: string;\n}\n\n// ─── CrewSpawnDeps ───────────────────────────────────────────────────────────\n\nexport interface CrewSpawnDeps {\n runtime: RuntimeDriver;\n /**\n * CLI-edge: look up a resolved agent by name. Returns null if unknown.\n * Wraps CapabilityRegistry.get() from @squadrant/agents.\n */\n resolveAgent(name: string): ResolvedAgent | null;\n /**\n * CLI-edge: dispatch a crew task via the daemon.\n * Wraps buildDispatchRequest + squadrantdCall from crew-control.ts.\n */\n dispatchCrew(opts: {\n provider: Provider;\n mode: \"interactive\";\n project: string;\n cwd: string;\n task: string;\n name: string;\n budgetMs?: number;\n serverPort?: number;\n approvalPolicy?: string;\n roleInstructions?: string;\n }): Promise<TaskRecord>;\n /** CLI-edge: write squadrant hooks to <cwd>/.claude/settings.local.json (#134). */\n writeSettingsLocal(projectCwd: string): void;\n /** CLI-edge: write opencode permission config for an interactive crew. */\n writeOpencodeConfig(opts: { stateRoot: string; project: string; taskId: string; gateBash?: boolean }): string;\n /** CLI-edge: deliver the first turn once the agent pane is ready. Returns\n * { delivered: true } when positively confirmed, { delivered: false } when\n * all retry paths exhausted without confirmation (#466). */\n sendFirstTurn(pane: PaneRef, firstTurn: string, preLaunchScreen: string, opts?: TurnAcceptanceConfig): Promise<{ delivered: boolean }>;\n /** CLI-edge: reserve an ephemeral TCP port for opencode's embedded HTTP server. */\n getFreePort(): Promise<number>;\n /** CLI-edge: deliver the task to a freshly-dispatched codex thread. */\n sendCodexFirstTurn(taskId: string, task: string): Promise<void>;\n /** Optional: called after routing to log the selected route (e.g. chalk.dim(...)). */\n onRouted?(route: CrewRouteResult): void;\n /** #466: optional — when provided, called with task.first-turn.confirmed after\n * positively confirmed delivery so the daemon can stamp firstTurnConfirmedAt. */\n emitEvent?(project: string, event: ControlEvent): Promise<void>;\n}\n\n// ─── Private helpers ──────────────────────────────────────────────────────────\n\nasync function listCrewPanes(runtime: RuntimeDriver, workspaceId: string, project: string): Promise<PaneRef[]> {\n const surfaces = await runtime.listSurfaces(workspaceId);\n return surfaces.filter((s) => s.title && isCrewTitle(project, s.title));\n}\n\nasync function findCrewPane(\n runtime: RuntimeDriver,\n workspaceId: string,\n project: string,\n name: string,\n): Promise<PaneRef | null> {\n const want = titleFor(project, name);\n const surfaces = await runtime.listSurfaces(workspaceId);\n return surfaces.find((s) => s.title === want) ?? null;\n}\n\n// ─── Codex interactive spawn (private) ───────────────────────────────────────\n\nasync function runCodexInteractiveSpawn(o: {\n project: string;\n task: string;\n /** Override for first-turn delivery to the model. When set (e.g. \"Read ./file.md\n * to get your task brief\"), used instead of `task` for sendCodexFirstTurn so large\n * file contents aren't sent verbatim. The daemon dispatch always uses `task`. */\n firstTurn?: string;\n cwd: string;\n runtime: RuntimeDriver;\n workspaceId: string;\n name: string;\n direction: PanePlacement;\n approvalPolicy?: string;\n roleInstructions?: string;\n dispatchCrew: CrewSpawnDeps[\"dispatchCrew\"];\n sendCodexFirstTurn: CrewSpawnDeps[\"sendCodexFirstTurn\"];\n}): Promise<PaneRef> {\n const rec = await o.dispatchCrew({\n provider: \"codex\",\n mode: \"interactive\",\n project: o.project,\n cwd: o.cwd,\n task: o.task,\n name: o.name,\n ...(o.approvalPolicy ? { approvalPolicy: o.approvalPolicy } : {}),\n ...(o.roleInstructions ? { roleInstructions: o.roleInstructions } : {}),\n });\n const title = titleFor(o.project, o.name);\n const pane = await o.runtime.newPane({\n workspaceId: o.workspaceId,\n direction: o.direction,\n title,\n });\n await o.runtime.sendToPane(pane, `squadrant crew attach ${rec.id}`);\n // Match the claude UX where the task arg becomes the first turn. The codex\n // dispatch only opens the thread; the task text never reaches the model\n // unless we send it. Fire-and-forget: the renderer in the tab picks up\n // streamed deltas once it attaches.\n const firstTurnText = o.firstTurn ?? o.task;\n if (firstTurnText && firstTurnText !== \"(interactive)\") {\n void o.sendCodexFirstTurn(rec.id, firstTurnText).catch((e: unknown) => {\n process.stderr.write(`(first-turn delivery failed: ${(e as Error).message})\\n`);\n });\n }\n return { ...pane, title };\n}\n\n// ─── runCrewSpawn ─────────────────────────────────────────────────────────────\n\nexport async function runCrewSpawn(\n input: CrewSpawnInput,\n config: SquadrantConfig,\n deps: CrewSpawnDeps,\n): Promise<PaneRef> {\n const proj = config.projects[input.project];\n if (!proj) {\n throw new Error(`Project '${input.project}' not found. Run 'squadrant projects list'.`);\n }\n\n const captain = await deps.runtime.status(proj.captainName);\n if (!captain) {\n throw new Error(\n `Captain workspace '${proj.captainName}' is not running. Run 'squadrant launch ${input.project}' first.`,\n );\n }\n\n const existing = await listCrewPanes(deps.runtime, captain.id, input.project);\n const existingTitles = existing.map((s) => s.title!);\n if (input.name) {\n const wantTitle = titleFor(input.project, input.name);\n if (existingTitles.includes(wantTitle)) {\n throw new Error(\n `Crew '${input.name}' already exists for ${input.project}. Use 'squadrant crew send ${input.project} ${input.name}' to send a follow-up, or pick a different --name.`,\n );\n }\n }\n const name = input.name ?? nextAutoName(existingTitles, input.project);\n\n // Crews run in an isolated worktree+branch by default so multiple parallel\n // crews never collide on a shared HEAD (#296). Pass shared:true (CLI: --shared)\n // for small/one-off tasks that should run on the root checkout.\n const spawnCwd = !input.shared\n ? addWorktree({\n repoRoot: proj.path,\n worktreeDir: config.defaults.worktreeDir ?? \".worktrees\",\n project: input.project,\n name,\n base: resolveWorktreeBase(proj.path),\n })\n : proj.path;\n\n // #458: For isolated-worktree spawns with a task file, copy the file into the\n // worktree root so the crew can find it via `Read ./<basename>` without having\n // to discover the main checkout path. Use a short first-turn message referencing\n // the local path to avoid large-paste issues on big task files.\n // Guards: skip for --shared (file is in the main checkout, already reachable),\n // skip for stdin ('-') since there is no file to copy.\n let firstTurnTask = input.task;\n if (input.taskFile && input.taskFile !== \"-\" && !input.shared) {\n const absTaskFile = path.resolve(input.taskFile);\n const basename = path.basename(absTaskFile);\n fs.copyFileSync(absTaskFile, path.join(spawnCwd, basename));\n firstTurnTask = `Read ./${basename} to get your task brief, then execute it.`;\n }\n\n // #275 leveled crew routing: consult routing rules when agent/model were not\n // explicitly provided by the caller. Explicit --agent or --model always win.\n const route = !input.agentExplicit && !input.model\n ? resolveCrewRoute(input.task, config)\n : null;\n if (route) {\n deps.onRouted?.(route);\n }\n\n const agentName = route?.agent ?? input.agent ?? \"claude\";\n const agent = deps.resolveAgent(agentName);\n if (!agent) {\n throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);\n }\n\n // Codex: route through the interactive control-plane daemon (PR #98) instead\n // of the print-mode CLI path. The dispatched task is driven via the\n // crew-attach renderer running in the captain tab, so 'crew send' / 'crew\n // read' / 'crew close' work identically to the Claude crew UX.\n if (agentName === \"codex\") {\n const codexRoleFile = path.join(TEMPLATES_DIR, `crew.${agent.templateSuffix}.md`);\n const roleInstructions = fs.existsSync(codexRoleFile)\n ? fs.readFileSync(codexRoleFile, \"utf8\")\n : undefined;\n return runCodexInteractiveSpawn({\n project: input.project,\n task: input.task,\n firstTurn: firstTurnTask !== input.task ? firstTurnTask : undefined,\n cwd: spawnCwd,\n runtime: deps.runtime,\n workspaceId: captain.id,\n name,\n direction: input.direction ?? \"tab\",\n approvalPolicy: input.approvalPolicy,\n roleInstructions,\n dispatchCrew: deps.dispatchCrew,\n sendCodexFirstTurn: deps.sendCodexFirstTurn,\n });\n }\n\n const promptFile = path.join(TEMPLATES_DIR, `crew.${agent.templateSuffix}.md`);\n // Claude crews run interactively (no -p) so the session stays alive between\n // turns; the task is sent via cmux after the CLI boots. Other agents that\n // don't yet honor `interactive` will keep their existing print-mode shape.\n const interactive = agent.name === \"claude\" || agent.name === \"opencode\";\n // Honor configured model routing only when the spawn agent matches the\n // configured role agent — model names are agent-specific. Cross-agent crews\n // fall back to the agent's own default to avoid passing an invalid model arg.\n const crewRole = config.defaults.roles?.crew;\n const configModel = crewRole && crewRole.agent === agent.name ? crewRole.model : undefined;\n const crewModel = input.model ?? route?.model ?? configModel;\n\n // Claude crews route through the control-plane daemon (PR #85) so the captain\n // learns terminal state via `squadrant crew status`. The cmux tab still does\n // the actual CLI launch — the daemon doesn't own Claude's PID. Hook bridge\n // (per-crew settings.json → Stop/SubagentStop/SessionEnd → squadrant crew _hook)\n // keeps the daemon's heartbeat fresh; `squadrant crew signal done` emits\n // terminal state.\n if (agentName === \"claude\") {\n const rec = await deps.dispatchCrew({\n provider: \"claude\",\n mode: \"interactive\",\n project: input.project,\n cwd: spawnCwd,\n task: input.task,\n name,\n });\n // Write squadrant hooks to <cwd>/.claude/settings.local.json so they are\n // auto-loaded as a project-local settings source. Merges with any existing\n // hooks — does not clobber the user's own personal hooks (#134).\n // #472: capture whether hook installation succeeded — when it does, the\n // UserPromptSubmit hook is the SOLE first-turn confirmation source for\n // claude crews. If the write fails (rare OS error), fall back to scrape.\n let hooksInstalled = false;\n try {\n deps.writeSettingsLocal(spawnCwd);\n hooksInstalled = true;\n } catch {\n // Hook file write failed — scrape confirmation remains as fallback.\n }\n const cliCommand = agent.buildCommand({\n prompt: input.task,\n workdir: spawnCwd,\n role: \"crew\",\n promptFile,\n interactive: true,\n // Permission mode is config-driven so squadrant can default crews to 'auto'\n // or keep the semi-automatic 'acceptEdits' gate. Falls back to 'acceptEdits'.\n permissionMode: config.defaults.permissions?.crew ?? \"acceptEdits\",\n ...(crewModel ? { model: crewModel } : {}),\n });\n const direction: PanePlacement = input.direction ?? \"tab\";\n const title = titleFor(input.project, name);\n const pane = await deps.runtime.newPane({ workspaceId: captain.id, direction, title });\n // Prefix the CLI command with env so the hook bridge + signal verb running\n // inside the crew's cmux tab can identify their task.\n const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}`;\n await deps.runtime.sendToPane(pane, `cd ${shellQuote(spawnCwd)} && ${envPrefix} ${niceCrewCommand(cliCommand)}`);\n const preLaunchScreen = (await deps.runtime.readPaneScreen(pane)) ?? \"\";\n const claudeResult = await deps.sendFirstTurn(pane, `${firstTurnTask}\\n\\n${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);\n // #466: surface non-delivery explicitly instead of silently returning success.\n if (!claudeResult.delivered) {\n process.stderr.write(`⚠️ First turn not delivered for crew '${name}' — use 'squadrant crew send ${input.project} ${name}' to re-send the task.\\n`);\n } else if (!hooksInstalled) {\n // #472: hooks unavailable — scrape is the only confirmation source for this crew.\n await deps.emitEvent?.(input.project, { type: \"task.first-turn.confirmed\", id: rec.id });\n }\n // When hooksInstalled=true: UserPromptSubmit hook stamps firstTurnConfirmedAt.\n return { ...pane, title };\n }\n\n // Opencode crews route through the control-plane daemon so the captain learns\n // terminal state via `squadrant crew status`. No hook bridge (opencode has no\n // hooks); the crew template instructs explicit `squadrant crew signal done|blocked|failed`.\n if (agentName === \"opencode\") {\n // Bind the crew's embedded opencode HTTP server on a known port so the\n // daemon's SSE bridge can subscribe to /event for turn-end detection.\n const serverPort = await deps.getFreePort();\n const rec = await deps.dispatchCrew({\n provider: \"opencode\",\n mode: \"interactive\",\n project: input.project,\n cwd: spawnCwd,\n task: input.task,\n name,\n // opencode has no heartbeat hook, so a normal budget would false-stall\n // every crew after 5min; use a 24h budget to effectively disable stall\n // detection. The SSE bridge (serverPort) provides turn-end liveness.\n budgetMs: 86400000,\n serverPort,\n });\n const opencodeConfigPath = deps.writeOpencodeConfig({\n stateRoot: STATE_ROOT,\n project: input.project,\n taskId: rec.id,\n // CP3 opt-in: --approval gates bash so the captain approves shell commands.\n ...(input.approval ? { gateBash: true } : {}),\n });\n const cliCommand = agent.buildCommand({\n prompt: input.task,\n workdir: spawnCwd,\n role: \"crew\",\n promptFile,\n interactive: true,\n model: crewModel,\n port: serverPort,\n });\n const direction: PanePlacement = input.direction ?? \"tab\";\n const title = titleFor(input.project, name);\n const pane = await deps.runtime.newPane({ workspaceId: captain.id, direction, title });\n const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}`;\n await deps.runtime.sendToPane(pane, `cd ${shellQuote(spawnCwd)} && ${envPrefix} OPENCODE_CONFIG=${opencodeConfigPath} ${niceCrewCommand(cliCommand)}`);\n const preLaunchScreen = (await deps.runtime.readPaneScreen(pane)) ?? \"\";\n const opencodeResult = await deps.sendFirstTurn(pane, `${firstTurnTask}\\n\\n${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen, {\n // #235: confirm-on-delivery — sendFirstTurnWhenReady polls until the idle\n // splash leaves the screen, re-sending every ~3s to cover slow boots\n // without duplicating the task. See crew-pane.ts SPLASH_MAX_CHECKS/EVERY_N.\n // #499: match a stable substring (\"ask anything\", case/whitespace/ellipsis\n // -insensitive via screenHasSplashMarker) rather than the exact wording —\n // opencode's real placeholder rotates through example prompts and uses\n // three ASCII dots (\"Ask anything...\") or a longer command hint (\"Ask\n // anything, / for commands, @ for context...\"), never the literal\n // \"Ask anything…\" (U+2026) this used to hardcode, which never matched.\n splashMarker: \"Ask anything\",\n } satisfies TurnAcceptanceConfig);\n // #466: surface non-delivery; emit confirmed event on success.\n if (!opencodeResult.delivered) {\n process.stderr.write(`⚠️ First turn not delivered for crew '${name}' — use 'squadrant crew send ${input.project} ${name}' to re-send the task.\\n`);\n } else {\n await deps.emitEvent?.(input.project, { type: \"task.first-turn.confirmed\", id: rec.id });\n }\n return { ...pane, title };\n }\n\n // Generic / fallback branch — agents that don't yet have a first-class branch.\n const cliCommand = agent.buildCommand({\n prompt: input.task,\n workdir: spawnCwd,\n role: \"crew\",\n promptFile,\n interactive,\n model: crewModel,\n });\n const direction: PanePlacement = input.direction ?? \"tab\";\n const title = titleFor(input.project, name);\n const pane = await deps.runtime.newPane({ workspaceId: captain.id, direction, title });\n await deps.runtime.sendToPane(pane, niceCrewCommand(cliCommand));\n if (interactive) {\n const preLaunchScreen = (await deps.runtime.readPaneScreen(pane)) ?? \"\";\n const genericResult = await deps.sendFirstTurn(pane, firstTurnTask, preLaunchScreen);\n // Generic branch has no daemon task record — only warn on non-delivery.\n if (!genericResult.delivered) {\n process.stderr.write(`⚠️ First turn not delivered for crew '${name}' — use 'squadrant crew send ${input.project} ${name}' to re-send the task.\\n`);\n }\n }\n return { ...pane, title };\n}\n\n// ─── crew session operations ──────────────────────────────────────────────────\n\n// #574: the single record-selection rule for \"which task record is THE record\n// for this crew name\" when duplicates exist (e.g. an orphaned record left by a\n// close/respawn race, #513). Every call site that resolves a crew name to a\n// task record MUST go through this helper — runCrewSend and runCrewClose used\n// to each inline their own pick (first-match vs most-recent), and disagreed on\n// live vs. stale duplicates, causing the two sides of a crew's lifecycle to\n// silently track different ids.\nfunction pickMostRecentTask(tasks: TaskRecord[]): TaskRecord {\n return tasks.reduce((a, b) => ((b.createdAt ?? 0) > (a.createdAt ?? 0) ? b : a));\n}\n\nexport async function runCrewSend(\n project: string,\n name: string,\n message: string,\n runtime: RuntimeDriver,\n workspaceId: string,\n deps: {\n listTasks(project: string): Promise<TaskRecord[]>;\n emitEvent(project: string, event: ControlEvent): Promise<void>;\n // Optional confirmed-submit override (#448). When provided, used instead of\n // runtime.sendToPane so the caller can inject paste-settle-Enter hardening.\n // Falls back to runtime.sendToPane when absent (preserves existing behaviour\n // for callers that don't inject it, e.g. unit tests).\n sendToPane?: (pane: PaneRef, message: string) => Promise<{ delivered: boolean; blockedByModal?: boolean }>;\n // #516: optional side-effect-free precheck for an open AskUserQuestion/\n // permission modal. Checked BEFORE the daemon-state emit block below so a\n // modal-blocked send is a true no-op on daemon state, not just on the pane.\n // Deliberately separate from sendToPane: that closure only reports\n // blockedByModal AFTER attempting delivery, which is too late here — the\n // emit block must never run for a message that never reached the crew.\n isBlockedByModal?: (pane: PaneRef) => Promise<boolean>;\n },\n): Promise<void> {\n const crew = await findCrewPane(runtime, workspaceId, project, name);\n if (!crew) {\n throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);\n }\n const blockedByModalMessage = () =>\n `Crew '${name}' has an interactive prompt open (AskUserQuestion/permission) — message NOT delivered, to avoid confirming its default option. Wait for the prompt to close, then re-send with 'squadrant crew send ${project} ${name}'.`;\n if (deps.isBlockedByModal && (await deps.isBlockedByModal(crew))) {\n throw new Error(blockedByModalMessage());\n }\n // Best-effort attention-state handling before delivering the captain's answer.\n // Terminal task (done/failed): reopen so the next signal done fires CREW DONE (#148).\n // Blocked task: emit task.started to clear blocked→working so a subsequent real\n // permission prompt re-fires CREW BLOCKED (#182).\n try {\n const matches = (await deps.listTasks(project)).filter((t) => t.name === name);\n const task = matches.length > 0 ? pickMostRecentTask(matches) : undefined;\n if (task) {\n if (TERMINAL_STATES.has(task.state)) {\n await deps.emitEvent(project, { type: \"task.reopened\", id: task.id });\n } else if (task.state === \"blocked\" || task.state === \"awaiting-input\" || task.state === \"review\") {\n // #599: feedback on a 'review' task is the reject path — clear it back\n // to working the same way an answer clears 'blocked'.\n await deps.emitEvent(project, { type: \"task.started\", id: task.id });\n }\n }\n } catch {\n // Swallow daemon errors so crews without a daemon or offline daemon\n // still receive the sent message.\n }\n const deliver: (pane: PaneRef, msg: string) => Promise<{ delivered: boolean; blockedByModal?: boolean }> =\n deps.sendToPane ?? ((pane, msg) => runtime.sendToPane(pane, msg).then(() => ({ delivered: true })));\n const { delivered, blockedByModal } = await deliver(crew, message);\n // #516 backstop: covers the TOCTOU window between the precheck above and this\n // delivery attempt, and callers that don't inject isBlockedByModal at all. By\n // this point the emit block (if any) has already run — unavoidable without the\n // precheck — but the send still fails loudly instead of reporting success.\n if (blockedByModal) {\n throw new Error(blockedByModalMessage());\n }\n if (!delivered) {\n // #566: a follow-up send has no self-heal sweep behind it (unlike first-turn\n // delivery, which the daemon retries via resendCrewFirstTurn) — a stderr-only\n // warning here let the CLI's own catch block never fire, so it printed \"✔ Sent\"\n // and exited 0 for a message that was never submitted. Throw so the caller\n // fails loudly instead.\n throw new Error(`Message not delivered to crew '${name}' — the paste/submit could not be confirmed. Re-send with 'squadrant crew send ${project} ${name}'.`);\n }\n}\n\nexport async function runCrewRead(\n project: string,\n name: string,\n runtime: RuntimeDriver,\n workspaceId: string,\n): Promise<string> {\n const crew = await findCrewPane(runtime, workspaceId, project, name);\n if (!crew) {\n throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);\n }\n return runtime.readPaneScreen(crew);\n}\n\n// #513: close's listTasks() lookup can race a same-name crew's own dispatch —\n// closing immediately after spawn may snapshot the daemon before the task\n// record is registered. A few short retries close that window without adding\n// meaningful latency to the common (already-registered) case.\nconst CLOSE_LOOKUP_RETRIES = 3;\nconst CLOSE_LOOKUP_RETRY_DELAY_MS = 150;\n\nexport async function runCrewClose(\n project: string,\n name: string,\n runtime: RuntimeDriver,\n workspaceId: string,\n deps: {\n listTasks(project: string): Promise<TaskRecord[]>;\n emitEvent(project: string, event: ControlEvent): Promise<void>;\n closeCodexThread(taskId: string): Promise<void>;\n /** Injectable for tests; defaults to a real delay. */\n sleep?: (ms: number) => Promise<void>;\n },\n): Promise<void> {\n const sleep = deps.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)));\n // resolveCaptainWorkspace already validated the project exists; reload for its\n // root path so we can tell a worktree crew (cwd != root) from a root crew.\n const projRoot = loadConfig().projects[project]?.path;\n // Terminalize the daemon task FIRST — before (and independent of) finding the\n // cmux pane (#184, hardened for #139). Without this, non-terminal tasks\n // (blocked/working/awaiting-input) linger in the daemon ledger and keep firing\n // phantom CREW BLOCKED/IDLE/STALLED pushes. A DEAD crew's pane is already gone,\n // so gating terminalization on findCrew (the old order) left zombie records\n // dangling forever. 'cancelled' is terminal but NOT in ATTENTION_STATES, so\n // firePush stays silent — captain initiated the close.\n let taskId: string | undefined;\n // Worktree to clean up after the pane closes — set only when this crew ran in\n // its own worktree (cwd recorded by the daemon differs from the root checkout).\n let worktreeCwd: string | undefined;\n try {\n let matches = (await deps.listTasks(project)).filter((t) => t.name === name);\n // #513: the record may not be registered yet (close raced spawn's own\n // dispatch). Retry briefly before concluding this crew has no daemon task.\n for (let attempt = 0; attempt < CLOSE_LOOKUP_RETRIES && matches.length === 0; attempt++) {\n await sleep(CLOSE_LOOKUP_RETRY_DELAY_MS);\n matches = (await deps.listTasks(project)).filter((t) => t.name === name);\n }\n if (matches.length > 0) {\n // #513: a name can match more than one record (e.g. an orphaned record\n // left by a prior close that raced dispatch, followed by a same-name\n // respawn). Terminalize every non-terminal match so none linger to fire\n // a phantom CREW STALLED/IDLE later. Reap/worktree cleanup below anchors\n // on the most-recently-dispatched match — the one the live pane belongs to.\n const primary = pickMostRecentTask(matches);\n taskId = primary.id;\n if (primary.cwd && projRoot && primary.cwd !== projRoot) {\n worktreeCwd = primary.cwd;\n }\n for (const task of matches) {\n if (!TERMINAL_STATES.has(task.state)) {\n await deps.emitEvent(project, { type: \"task.cancelled\", id: task.id, reason: \"closed by captain\" });\n }\n // Codex teardown: the pane only hosts the `crew attach` renderer; the thread\n // (and its per-thread MCP servers) live on the shared app-server, so closing\n // the pane alone leaks them. Tell the daemon to archive the thread.\n if (task.provider === \"codex\") {\n await deps.closeCodexThread(task.id);\n }\n }\n }\n } catch {\n // Swallow daemon errors — a crew without a daemon must still close.\n }\n // Close the cmux pane if it still exists. A dead crew's pane is already gone —\n // that is not an error (the record is terminalized above); proceed to reap\n // children / clean the worktree. Only a genuine miss (no pane AND no daemon\n // task) is a typo → surface the not-found error.\n const crew = await findCrewPane(runtime, workspaceId, project, name);\n if (crew) {\n await runtime.closePane(crew);\n } else if (taskId === undefined) {\n throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);\n }\n // Reap any surviving child processes (vitest workers, node subprocs, etc.)\n // that the cmux pane-close cascade may have missed.\n if (taskId !== undefined) {\n await reapCrewChildren(taskId);\n }\n // Auto-clean the crew's worktree AFTER its processes are gone, so we don't\n // yank a dir out from under a live shell. Best-effort: a failed removal must\n // not break close (the branch is preserved regardless).\n if (worktreeCwd && projRoot) {\n try {\n removeWorktree(projRoot, worktreeCwd);\n } catch (e) {\n process.stderr.write(`(worktree remove failed: ${(e as Error).message})\\n`);\n }\n }\n}\n\nexport async function runCrewList(\n project: string,\n runtime: RuntimeDriver,\n workspaceId: string,\n): Promise<Array<{ name: string; surfaceId: string }>> {\n const crews = await listCrewPanes(runtime, workspaceId, project);\n return crews.map((c) => ({\n name: nameFromTitle(project, c.title!),\n surfaceId: c.surfaceId,\n }));\n}\n","// packages/core/src/lifecycle-source.ts\n//\n// LifecycleSource port — phase 0 scaffold (issue #333).\n//\n// Defines the abstraction for normalizing agent lifecycle events from\n// heterogeneous sources (cmux store file, native hooks, SSE, app-server)\n// into a single 4-state model. NO concrete implementation lives here; this is\n// the interface + types + pure reducer only.\n//\n// WIRING CONSTRAINT: nothing in this file is imported by the live daemon or\n// delivery path. It compiles and tests but remains unwired until Phase 1.\n\n// ── normalized lifecycle vocabulary ─────────────────────────────────────────\n\n/** The four canonical crew lifecycle states (mirrors cmux AgentHibernationLifecycleState). */\nexport type LifecycleState = \"running\" | \"idle\" | \"needsInput\" | \"unknown\";\n\n/** One observation about one crew, from one source. */\nexport interface LifecycleSnapshot {\n taskId: string;\n state: LifecycleState;\n /** Is the OS process actually alive (pid-verified)? */\n alive: boolean;\n /**\n * Provenance — the reconciler's tie-breaker.\n * \"agent\" = explicit hook / SSE / app-server transition (authoritative).\n * \"scan\" = inferred from a process/file sweep (liveness only; may NOT assert needsInput).\n */\n origin: \"agent\" | \"scan\";\n /** Monotonic stamp (epoch ms) for last-writer reconciliation across sources. */\n at: number;\n pid?: number;\n /** Optional human detail for surfacing CREW BLOCKED / CREW WORKING context. */\n detail?: { note?: string; tool?: string; reason?: string };\n}\n\n/**\n * Correlation hints a source passes when resolving a raw signal back to a crew.\n * The daemon tries them in priority order: taskId > pid > cwd > sessionId.\n */\nexport interface CorrelationHint {\n /** Strongest — SQUADRANT_CREW_TASK_ID injected into every crew's env at spawn. */\n taskId?: string;\n /** From the cmux store or process scan. */\n pid?: number;\n /** Weakest — collision-prone when a worktree is shared. */\n cwd?: string;\n /** Source-internal (cmux sessionId, codex threadId). */\n sessionId?: string;\n}\n\n/** What the daemon hands every source: how to correlate + where to report. */\nexport interface LifecycleSourceDeps {\n /**\n * Map a raw signal back to its owning crew TaskRecord, or undefined.\n * Keeping it injected makes each source independently testable.\n */\n resolve(hint: CorrelationHint): { id: string } | undefined;\n /** Normalized observation → reducer → ControlEvent pipeline. */\n report(snap: LifecycleSnapshot): void;\n log?(msg: string): void;\n}\n\n/**\n * The port. Each adapter implements start/stop.\n * Push sources call deps.report() on transition.\n * Poll sources additionally expose snapshot() for the liveness floor sweep.\n */\nexport interface LifecycleSource {\n /** Identifies the source in logs and the reconciler (\"cmux-store\" | \"native-hook\" | …). */\n readonly name: string;\n start(deps: LifecycleSourceDeps): void;\n stop(): void;\n /**\n * Poll hook — optional.\n * Returns the current liveness snapshot for a known crew, or undefined if\n * this source has no view of it. Drives the liveness floor sweep.\n * A poll result MUST set origin:\"scan\" and MUST NOT assert state:\"needsInput\".\n */\n snapshot?(taskId: string): LifecycleSnapshot | undefined;\n /**\n * Read-only source-level health (B4 — dashboard visibility into which sources\n * are up). Optional: a source with no fallible startup can omit it and the\n * daemon assumes {active: true, error: null} once registered.\n */\n health?(): { active: boolean; error: string | null };\n}\n\n// ── the one reducer all sources feed ────────────────────────────────────────\n\n/**\n * Pure. Reconcile a new snapshot against the crew's last known state.\n *\n * Rules (from cmux FeedCoordinator.swift):\n * 1. Agent-originated signals are authoritative — always trusted.\n * 2. Scan signals can never assert needsInput (hook-only signal).\n * 3. Agent-set needsInput is sticky — only an agent-originated running relaxes it.\n * 4. A stale scan (at <= prev.at when prev is agent-set) does not regress state.\n */\nexport function reduceLifecycle(\n prev: LifecycleSnapshot | undefined,\n next: LifecycleSnapshot,\n): LifecycleState {\n // Rule 1: agent-originated signals are authoritative.\n if (next.origin === \"agent\") {\n return next.state;\n }\n\n // Rule 2: scan signals can never assert needsInput.\n if (next.state === \"needsInput\") {\n return prev?.state ?? \"unknown\";\n }\n\n // Rule 3: agent-set needsInput is sticky against scans.\n if (prev?.state === \"needsInput\") {\n return \"needsInput\";\n }\n\n // Rule 4: a stale scan does not regress a more-recent agent state.\n if (prev?.origin === \"agent\" && prev.at >= next.at) {\n return prev.state;\n }\n\n return next.state;\n}\n","import { execSync } from \"node:child_process\";\nimport type { AgentDriver, AgentProbeResult, SpawnOptions, AgentResult } from \"./types.js\";\n\nexport function createClaudeDriver(): AgentDriver {\n return {\n name: \"claude\",\n templateSuffix: \"claude\",\n\n async probe(): Promise<AgentProbeResult> {\n try {\n const version = execSync(\"claude --version\", { encoding: \"utf-8\" }).trim();\n return {\n installed: true,\n version,\n capabilities: [\n \"teams\",\n \"json_output\",\n \"model_routing\",\n \"skills\",\n \"auto_approve\",\n \"streaming\",\n \"prompt_file\",\n ],\n };\n } catch {\n return { installed: false, version: \"\", capabilities: [] };\n }\n },\n\n buildCommand(opts: SpawnOptions): string {\n let cmd = \"claude\";\n\n if (opts.model) {\n cmd += ` --model ${opts.model}`;\n }\n\n if (opts.autoApprove) {\n cmd += \" --dangerously-skip-permissions\";\n } else if (opts.permissionMode) {\n cmd += ` --permission-mode ${opts.permissionMode}`;\n }\n\n if (opts.promptFile) {\n cmd += ` --append-system-prompt-file ${opts.promptFile}`;\n }\n\n if (opts.settingsPath) {\n cmd += ` --settings ${opts.settingsPath}`;\n }\n\n // Load squadrant plugin for skills\n const pluginDir = `${process.env.HOME}/.config/squadrant/plugin`;\n cmd += ` --plugin-dir ${pluginDir}`;\n\n if (!opts.interactive) {\n cmd += ` -p \"${opts.prompt.replace(/\"/g, '\\\\\"')}\"`;\n }\n return cmd;\n },\n\n parseOutput(raw: string): AgentResult {\n const lines = raw.trim().split(\"\\n\").filter((l) => l.startsWith(\"{\"));\n if (lines.length === 0) {\n return { status: \"success\", output: raw.trim() };\n }\n try {\n const last = JSON.parse(lines[lines.length - 1]);\n return { status: \"success\", output: last.result || last.content || raw.trim() };\n } catch {\n return { status: \"success\", output: raw.trim() };\n }\n },\n\n async stop(pid: number): Promise<void> {\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n // process may already be gone\n }\n },\n };\n}\n","import { execSync } from \"node:child_process\";\nimport type { AgentDriver, AgentProbeResult, SpawnOptions, AgentResult } from \"./types.js\";\n\nexport function createCodexDriver(): AgentDriver {\n return {\n name: \"codex\",\n templateSuffix: \"generic\",\n\n async probe(): Promise<AgentProbeResult> {\n try {\n const version = execSync(\"codex --version\", { encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] }).trim();\n const help = execSync(\"codex --help\", { encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n const hasExec = help.includes(\"exec\");\n return {\n installed: true,\n version,\n capabilities: [\n \"auto_approve\",\n \"json_output\",\n \"sandbox\",\n ...(hasExec ? [\"streaming\" as const] : []),\n ],\n };\n } catch {\n return { installed: false, version: \"\", capabilities: [] };\n }\n },\n\n buildCommand(opts: SpawnOptions): string {\n let cmd = `codex exec \"${opts.prompt.replace(/\"/g, '\\\\\"')}\" --json`;\n if (opts.autoApprove) cmd += \" --full-auto\";\n return cmd;\n },\n\n parseOutput(raw: string): AgentResult {\n const lines = raw.trim().split(\"\\n\").filter((l) => l.startsWith(\"{\"));\n if (lines.length === 0) {\n return { status: \"success\", output: raw.trim() };\n }\n try {\n const last = JSON.parse(lines[lines.length - 1]);\n return { status: \"success\", output: last.output || last.result || raw.trim() };\n } catch {\n return { status: \"success\", output: raw.trim() };\n }\n },\n\n async stop(pid: number): Promise<void> {\n try { process.kill(pid, \"SIGTERM\"); } catch { /* already gone */ }\n },\n };\n}\n","import { execSync } from \"node:child_process\";\nimport type { AgentDriver, AgentProbeResult, SpawnOptions, AgentResult } from \"./types.js\";\n\nexport function createGeminiDriver(): AgentDriver {\n return {\n name: \"gemini\",\n templateSuffix: \"generic\",\n\n async probe(): Promise<AgentProbeResult> {\n try {\n const version = execSync(\"gemini --version\", { encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] }).trim();\n return {\n installed: true,\n version,\n capabilities: [\"auto_approve\", \"json_output\", \"streaming\"],\n };\n } catch {\n return { installed: false, version: \"\", capabilities: [] };\n }\n },\n\n buildCommand(opts: SpawnOptions): string {\n let cmd = `gemini -p \"${opts.prompt.replace(/\"/g, '\\\\\"')}\"`;\n if (opts.autoApprove) cmd += \" --yolo\";\n if (opts.jsonOutput) cmd += \" --output-format json\";\n return cmd;\n },\n\n parseOutput(raw: string): AgentResult {\n try {\n const parsed = JSON.parse(raw.trim());\n return { status: \"success\", output: parsed.response || raw.trim() };\n } catch {\n return { status: \"success\", output: raw.trim() };\n }\n },\n\n async stop(pid: number): Promise<void> {\n try { process.kill(pid, \"SIGTERM\"); } catch { /* already gone */ }\n },\n };\n}\n","import { execSync } from \"node:child_process\";\nimport type { AgentDriver, AgentProbeResult, SpawnOptions, AgentResult } from \"./types.js\";\n\nexport function createOpencodeDriver(): AgentDriver {\n return {\n name: \"opencode\",\n templateSuffix: \"opencode\",\n\n async probe(): Promise<AgentProbeResult> {\n try {\n const version = execSync(\"opencode --version\", { encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] }).trim();\n return {\n installed: true,\n version,\n capabilities: [\"auto_approve\", \"json_output\", \"streaming\", \"model_routing\"],\n };\n } catch {\n return { installed: false, version: \"\", capabilities: [] };\n }\n },\n\n buildCommand(opts: SpawnOptions): string {\n // Interactive crews: boot the TUI; the caller delivers opts.prompt as the\n // first turn via runtime.send once the session is ready, so the crew stays\n // alive for follow-up turns through `squadrant crew send`. When a port is\n // given, bind the embedded HTTP server on it so the daemon's SSE bridge\n // can subscribe to /event for turn-end detection (the bare TUI uses an\n // ephemeral unix socket with no reachable /event endpoint).\n if (opts.interactive) return opts.port ? `opencode --port ${opts.port}` : \"opencode\";\n let cmd = `opencode run \"${opts.prompt.replace(/\"/g, '\\\\\"')}\"`;\n if (opts.jsonOutput) cmd += \" --format json\";\n if (opts.model) cmd += ` -m ${opts.model}`;\n return cmd;\n },\n\n parseOutput(raw: string): AgentResult {\n try {\n const parsed = JSON.parse(raw.trim());\n return { status: \"success\", output: parsed.response || parsed.output || raw.trim() };\n } catch {\n return { status: \"success\", output: raw.trim() };\n }\n },\n\n async stop(pid: number): Promise<void> {\n try { process.kill(pid, \"SIGTERM\"); } catch { /* already gone */ }\n },\n };\n}\n","// buildAgentCmd — build the CLI command string used to launch a captain/command\n// session. Extracted from packages/cli/src/commands/launch.ts so it can be\n// unit-tested without spawning real processes.\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { Role } from \"./types.js\";\nimport type { CapabilityRegistry } from \"./registry.js\";\n\n/**\n * Build the shell command string that launches an agent session for a given\n * role. For Claude, handles fresh/continue, permission-mode flags, role\n * template file, and plugin-dir. For all other agents, delegates to the\n * driver's own buildCommand.\n *\n * @param agentName - e.g. \"claude\", \"opencode\", \"codex\"\n * @param registry - populated CapabilityRegistry\n * @param role - \"captain\" | \"command\" | \"crew\" | …\n * @param fresh - true → new session; false → continue last session\n * @param permissionMode - \"acceptEdits\" | \"auto\" | \"bypassPermissions\"\n * @param model - optional model override\n * @param templatesDir - resolved path to ~/.config/squadrant/templates\n */\nexport function buildAgentCmd(\n agentName: string,\n registry: CapabilityRegistry,\n role: string,\n fresh: boolean,\n permissionMode: string,\n model?: string,\n templatesDir?: string,\n): string {\n const driver = registry.getDriver(agentName);\n\n if (driver.name === \"claude\") {\n let cmd = fresh ? \"claude\" : \"claude -c\";\n\n if (permissionMode === \"acceptEdits\") {\n cmd += \" --permission-mode acceptEdits\";\n } else if (permissionMode === \"auto\") {\n cmd += \" --permission-mode auto\";\n } else if (permissionMode === \"bypassPermissions\") {\n cmd += \" --dangerously-skip-permissions\";\n }\n\n if (model) {\n cmd += ` --model ${model}`;\n }\n\n if (templatesDir) {\n const roleFile = path.join(templatesDir, `${role}.claude.md`);\n const legacyRoleFile = path.join(templatesDir, `${role}.CLAUDE.md`);\n const actualRoleFile = fs.existsSync(roleFile)\n ? roleFile\n : fs.existsSync(legacyRoleFile) ? legacyRoleFile : null;\n if (actualRoleFile) {\n cmd += ` --append-system-prompt-file ${actualRoleFile}`;\n }\n\n const pluginDir = path.join(templatesDir, \"..\", \"plugin\");\n if (fs.existsSync(pluginDir)) {\n cmd += ` --plugin-dir ${pluginDir}`;\n }\n }\n\n return cmd;\n }\n\n // Non-Claude agents: delegate to driver.buildCommand.\n const roleFile = templatesDir\n ? path.join(templatesDir, `${role}.${driver.templateSuffix}.md`)\n : undefined;\n return driver.buildCommand({\n prompt: `You are a squadrant ${role}. Read your instructions from ${roleFile ?? role} and begin.`,\n workdir: process.cwd(),\n role: role as Role,\n model,\n autoApprove: true,\n promptFile: roleFile && fs.existsSync(roleFile) ? roleFile : undefined,\n });\n}\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport type {\n ProjectionEmitResult,\n ProjectionEmitter,\n ProjectionSource,\n} from \"@squadrant/shared\";\n\nfunction renderMdc(source: ProjectionSource): string {\n const skillSections = source.skills\n .map(\n (s) =>\n `## Skill: ${s.name}\\n\\n*${s.description}*\\n\\n${s.content}`,\n )\n .join(\"\\n\\n\");\n\n const body = [source.instructions.trim(), skillSections]\n .filter((s) => s.length > 0)\n .join(\"\\n\\n\");\n\n const frontmatter = [\n \"---\",\n \"description: Squadrant-projected rules and skills\",\n \"globs: ['**/*']\",\n \"alwaysApply: true\",\n \"---\",\n \"\",\n ].join(\"\\n\");\n\n return `${frontmatter}${body}\\n`;\n}\n\nasync function readExisting(p: string): Promise<string | null> {\n try {\n return await readFile(p, \"utf-8\");\n } catch (err) {\n if ((err as { code?: string }).code === \"ENOENT\") return null;\n throw err;\n }\n}\n\nfunction buildDiff(existing: string | null, generated: string): string {\n if (existing === null) return `NEW FILE\\n---\\n${generated}`;\n if (existing === generated) return \"UNCHANGED\";\n return `OVERWRITE\\n--- old\\n${existing}\\n--- new\\n${generated}`;\n}\n\nexport function createCursorEmitter(): ProjectionEmitter {\n return {\n name: \"cursor\",\n\n destinations(scope, projectRoot) {\n if (scope === \"user\") {\n return [\n {\n path: path.join(os.homedir(), \".cursor/rules/squadrant-global.mdc\"),\n shared: false,\n format: \"mdc\",\n },\n ];\n }\n if (!projectRoot) return [];\n return [\n {\n path: path.join(projectRoot, \".cursor/rules/squadrant.mdc\"),\n shared: false,\n format: \"mdc\",\n },\n ];\n },\n\n async emit(source, dest, opts): Promise<ProjectionEmitResult> {\n const generated = renderMdc(source);\n const existing = await readExisting(dest.path);\n\n if (opts?.dryRun) {\n return {\n written: false,\n path: dest.path,\n bytesWritten: 0,\n diff: buildDiff(existing, generated),\n };\n }\n\n await mkdir(path.dirname(dest.path), { recursive: true });\n await writeFile(dest.path, generated, \"utf-8\");\n\n return {\n written: true,\n path: dest.path,\n bytesWritten: Buffer.byteLength(generated, \"utf-8\"),\n };\n },\n };\n}\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport { mergeWithMarkers } from \"./marker.js\";\nimport type {\n ProjectionEmitResult,\n ProjectionEmitter,\n ProjectionSource,\n} from \"@squadrant/shared\";\n\nfunction renderMarkdown(source: ProjectionSource): string {\n const skillSections = source.skills\n .map((s) => `## Skill: ${s.name}\\n\\n*${s.description}*\\n\\n${s.content}`)\n .join(\"\\n\\n\");\n return [source.instructions.trim(), skillSections]\n .filter((s) => s.length > 0)\n .join(\"\\n\\n\");\n}\n\nasync function readExisting(p: string): Promise<string | null> {\n try { return await readFile(p, \"utf-8\"); }\n catch (err) {\n if ((err as { code?: string }).code === \"ENOENT\") return null;\n throw err;\n }\n}\n\nexport function createCodexEmitter(): ProjectionEmitter {\n return {\n name: \"codex\",\n\n destinations(scope, projectRoot) {\n if (scope === \"user\") {\n return [{\n path: path.join(os.homedir(), \".codex/AGENTS.md\"),\n shared: true,\n format: \"markdown\",\n }];\n }\n if (!projectRoot) return [];\n return [{\n path: path.join(projectRoot, \"AGENTS.md\"),\n shared: true,\n format: \"markdown\",\n }];\n },\n\n async emit(source, dest, opts): Promise<ProjectionEmitResult> {\n const body = renderMarkdown(source);\n const existing = await readExisting(dest.path);\n const generated = mergeWithMarkers(existing, body);\n\n if (opts?.dryRun) {\n return {\n written: false,\n path: dest.path,\n bytesWritten: 0,\n diff: existing === generated ? \"UNCHANGED\" : `MERGE\\n--- old\\n${existing ?? \"\"}\\n--- new\\n${generated}`,\n };\n }\n\n await mkdir(path.dirname(dest.path), { recursive: true });\n await writeFile(dest.path, generated, \"utf-8\");\n\n return {\n written: true,\n path: dest.path,\n bytesWritten: Buffer.byteLength(generated, \"utf-8\"),\n };\n },\n };\n}\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport { mergeWithMarkers } from \"./marker.js\";\nimport type {\n ProjectionEmitResult,\n ProjectionEmitter,\n ProjectionSource,\n} from \"@squadrant/shared\";\n\nfunction renderMarkdown(source: ProjectionSource): string {\n const skillSections = source.skills\n .map((s) => `## Skill: ${s.name}\\n\\n*${s.description}*\\n\\n${s.content}`)\n .join(\"\\n\\n\");\n return [source.instructions.trim(), skillSections]\n .filter((s) => s.length > 0)\n .join(\"\\n\\n\");\n}\n\nasync function readExisting(p: string): Promise<string | null> {\n try { return await readFile(p, \"utf-8\"); }\n catch (err) {\n if ((err as { code?: string }).code === \"ENOENT\") return null;\n throw err;\n }\n}\n\nexport function createGeminiEmitter(): ProjectionEmitter {\n return {\n name: \"gemini\",\n\n destinations(scope, projectRoot) {\n if (scope === \"user\") {\n return [{\n path: path.join(os.homedir(), \".gemini/GEMINI.md\"),\n shared: true,\n format: \"markdown\",\n }];\n }\n if (!projectRoot) return [];\n return [{\n path: path.join(projectRoot, \"GEMINI.md\"),\n shared: true,\n format: \"markdown\",\n }];\n },\n\n async emit(source, dest, opts): Promise<ProjectionEmitResult> {\n const body = renderMarkdown(source);\n const existing = await readExisting(dest.path);\n const generated = mergeWithMarkers(existing, body);\n\n if (opts?.dryRun) {\n return {\n written: false,\n path: dest.path,\n bytesWritten: 0,\n diff: existing === generated ? \"UNCHANGED\" : `MERGE\\n--- old\\n${existing ?? \"\"}\\n--- new\\n${generated}`,\n };\n }\n\n await mkdir(path.dirname(dest.path), { recursive: true });\n await writeFile(dest.path, generated, \"utf-8\");\n\n return {\n written: true,\n path: dest.path,\n bytesWritten: Buffer.byteLength(generated, \"utf-8\"),\n };\n },\n };\n}\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport { mergeWithMarkers } from \"./marker.js\";\nimport type {\n ProjectionEmitResult,\n ProjectionEmitter,\n ProjectionSource,\n} from \"@squadrant/shared\";\n\nfunction renderMarkdown(source: ProjectionSource): string {\n const skillSections = source.skills\n .map((s) => `## Skill: ${s.name}\\n\\n*${s.description}*\\n\\n${s.content}`)\n .join(\"\\n\\n\");\n return [source.instructions.trim(), skillSections]\n .filter((s) => s.length > 0)\n .join(\"\\n\\n\");\n}\n\nasync function readExisting(p: string): Promise<string | null> {\n try { return await readFile(p, \"utf-8\"); }\n catch (err) {\n if ((err as { code?: string }).code === \"ENOENT\") return null;\n throw err;\n }\n}\n\nexport function createOpencodeEmitter(): ProjectionEmitter {\n return {\n name: \"opencode\",\n\n destinations(scope, projectRoot) {\n if (scope === \"user\") {\n return [{\n path: path.join(os.homedir(), \".config\", \"opencode\", \"AGENTS.md\"),\n shared: true,\n format: \"markdown\",\n }];\n }\n if (!projectRoot) return [];\n return [{\n path: path.join(projectRoot, \"AGENTS.md\"),\n shared: true,\n format: \"markdown\",\n }];\n },\n\n async emit(source, dest, opts): Promise<ProjectionEmitResult> {\n const body = renderMarkdown(source);\n const existing = await readExisting(dest.path);\n const generated = mergeWithMarkers(existing, body);\n\n if (opts?.dryRun) {\n return {\n written: false,\n path: dest.path,\n bytesWritten: 0,\n diff: existing === generated ? \"UNCHANGED\" : `MERGE\\n--- old\\n${existing ?? \"\"}\\n--- new\\n${generated}`,\n };\n }\n\n await mkdir(path.dirname(dest.path), { recursive: true });\n await writeFile(dest.path, generated, \"utf-8\");\n\n return {\n written: true,\n path: dest.path,\n bytesWritten: Buffer.byteLength(generated, \"utf-8\"),\n };\n },\n };\n}\n","// src/control/codex/app-server-client.ts\n// Typed JSON-RPC 2.0 client for `codex app-server` v2.\n// Transport: stdio (newline-delimited JSON). See spec §3.\n// Defensive parser per orca codex-fetcher.ts:160-164: ignore non-JSON lines.\n\nimport { EventEmitter } from \"node:events\";\nimport { spawn as nodeSpawn, type ChildProcessByStdio } from \"node:child_process\";\nimport type { Readable, Writable } from \"node:stream\";\n\ntype Child = ChildProcessByStdio<Writable, Readable, Readable>;\n\nexport interface AppServerClientOpts {\n /** Override for tests; defaults to spawning real `codex app-server`. */\n spawn?: () => Child;\n clientInfo?: { name: string; version: string };\n}\n\nexport function _parseChunk(acc: { buf: string }, chunk: string): unknown[] {\n acc.buf += chunk;\n const out: unknown[] = [];\n let idx: number;\n while ((idx = acc.buf.indexOf(\"\\n\")) >= 0) {\n const line = acc.buf.slice(0, idx);\n acc.buf = acc.buf.slice(idx + 1);\n if (!line.trim()) continue;\n try { out.push(JSON.parse(line)); } catch { /* skip non-JSON defensively */ }\n }\n return out;\n}\n\nexport class AppServerClient extends EventEmitter {\n private proc?: Child;\n private acc = { buf: \"\" };\n private opts: AppServerClientOpts;\n constructor(opts: AppServerClientOpts = {}) { super(); this.opts = opts; }\n\n start(): void {\n if (this.proc) throw new Error(\"AppServerClient already started\");\n const sp = this.opts.spawn ?? defaultSpawn;\n this.proc = sp();\n this.proc.stdout.on(\"data\", (d: Buffer | string) => this._onStdout(d.toString()));\n this.proc.stderr.on(\"data\", (d: Buffer | string) => this.emit(\"stderr\", d.toString()));\n this.proc.on(\"exit\", (code, signal) => {\n this._onClosed();\n this.emit(\"closed\", { code, signal });\n });\n this.proc.on(\"error\", (e) => this.emit(\"error\", e));\n }\n\n kill(): void {\n if (this.proc) this.proc.kill();\n }\n\n async initialize(): Promise<unknown> {\n if (this._handshakeDone) return;\n if (!this.proc) throw new Error(\"AppServerClient not started\");\n const info = this.opts.clientInfo ?? { name: \"squadrant\", version: \"0\" };\n // Send initialize directly (bypass gate) — only initialize may pre-handshake.\n const id = this.nextId++;\n const env = { jsonrpc: \"2.0\", id, method: \"initialize\", params: { clientInfo: info } };\n const res = await new Promise<unknown>((resolve, reject) => {\n this.pending.set(id, { resolve, reject });\n this.proc!.stdin.write(JSON.stringify(env) + \"\\n\");\n });\n // Send 'initialized' as a notification (no id).\n this.proc.stdin.write(JSON.stringify({ jsonrpc: \"2.0\", method: \"initialized\" }) + \"\\n\");\n this._handshakeDone = true;\n return res;\n }\n\n async startThread(params: { cwd: string; model?: string; sandbox?: string; approvalPolicy?: string; developerInstructions?: string }): Promise<{ threadId: string }> {\n const res = await this._sendRequest(\"thread/start\", params) as { thread?: { id?: string } };\n const id = res?.thread?.id;\n if (typeof id !== \"string\") throw new Error(`thread/start: unexpected response shape (no thread.id): ${JSON.stringify(res).slice(0, 200)}`);\n return { threadId: id };\n }\n\n resumeThread(params: { threadId: string; cwd?: string }): Promise<unknown> {\n return this._sendRequest(\"thread/resume\", params);\n }\n\n /** Archive a thread so the app-server tears it down (and reaps any per-thread\n * MCP servers it spawned). Called when a codex crew closes. */\n archiveThread(threadId: string): Promise<unknown> {\n return this._sendRequest(\"thread/archive\", { threadId });\n }\n\n readThread(params: { threadId: string; lastN?: number }): Promise<unknown> {\n return this._sendRequest(\"thread/read\", params);\n }\n\n async sendTurn(threadId: string, text: string): Promise<{ turnId: string }> {\n const ack = await this._sendRequest(\"turn/start\", {\n threadId, input: [{ type: \"text\", text }],\n }) as { turn?: { id?: string } };\n const turnId = ack?.turn?.id;\n if (typeof turnId !== \"string\") throw new Error(`turn/start: unexpected ack shape (no turn.id): ${JSON.stringify(ack).slice(0, 200)}`);\n return new Promise((resolve, reject) => {\n const onNote = (n: { method: string; params?: any }) => {\n if (n.params?.turn?.id !== turnId) return;\n if (n.method === \"turn/completed\") { cleanup(); resolve({ turnId }); }\n if (n.method === \"turn/failed\") { cleanup(); reject(new Error(n.params?.error ?? \"turn failed\")); }\n };\n const onClientClosed = () => { cleanup(); reject(new Error(\"AppServerClient: client closed before turn completed\")); };\n const cleanup = () => {\n this.off(\"notification\", onNote);\n this.off(\"_clientClosed\", onClientClosed);\n };\n this.on(\"notification\", onNote);\n this.once(\"_clientClosed\", onClientClosed);\n });\n }\n\n steerTurn(threadId: string, text: string): Promise<unknown> {\n return this._sendRequest(\"turn/steer\", { threadId, input: [{ type: \"text\", text }] });\n }\n\n interruptTurn(threadId: string): Promise<unknown> {\n return this._sendRequest(\"turn/interrupt\", { threadId });\n }\n\n injectItems(threadId: string, items: unknown[]): Promise<unknown> {\n return this._sendRequest(\"thread/inject_items\", { threadId, items });\n }\n\n respondToServerRequest(id: number, result: unknown): void {\n if (!this.proc) throw new Error(\"AppServerClient not started\");\n this.proc.stdin.write(JSON.stringify({ jsonrpc: \"2.0\", id, result }) + \"\\n\");\n }\n\n private nextId = 1;\n private pending = new Map<number, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();\n protected _handshakeDone = false;\n\n private _onClosed(): void {\n // Mass-reject pending RPC promises so callers don't hang on child death.\n const closedErr = new Error(\"AppServerClient: child closed before response\");\n for (const slot of this.pending.values()) slot.reject(closedErr);\n this.pending.clear();\n // Tell any waiters listening for child-close (sendTurn) to bail.\n this.emit(\"_clientClosed\");\n }\n\n protected _sendRequest(method: string, params?: unknown): Promise<unknown> {\n if (!this._handshakeDone && method !== \"initialize\") {\n throw new Error(`AppServerClient: cannot call '${method}' before handshake (spec §3.2)`);\n }\n if (!this.proc) throw new Error(\"AppServerClient not started\");\n const id = this.nextId++;\n const env = { jsonrpc: \"2.0\", id, method, params: params ?? {} };\n return new Promise((resolve, reject) => {\n this.pending.set(id, { resolve, reject });\n this.proc!.stdin.write(JSON.stringify(env) + \"\\n\");\n });\n }\n\n private _dispatchResponse(msg: any): boolean {\n if (typeof msg?.id !== \"number\") return false;\n const slot = this.pending.get(msg.id);\n if (!slot) return false;\n this.pending.delete(msg.id);\n if (msg.error) slot.reject(new Error(`${msg.error.message ?? \"rpc-error\"} (code ${msg.error.code})`));\n else slot.resolve(msg.result);\n return true;\n }\n\n private _onStdout(s: string): void {\n for (const msg of _parseChunk(this.acc, s)) this._dispatch(msg);\n }\n\n private _dispatch(msg: unknown): void {\n if (this._dispatchResponse(msg)) return;\n const m = msg as any;\n if (typeof m?.method === \"string\" && typeof m?.id === \"number\") {\n this.emit(\"serverRequest\", { id: m.id, method: m.method, params: m.params });\n return;\n }\n if (typeof m?.method === \"string\" && m?.id === undefined) {\n this.emit(\"notification\", { method: m.method, params: m.params });\n }\n }\n}\n\nfunction defaultSpawn(): Child {\n return nodeSpawn(\"codex\", [\"app-server\"], { stdio: [\"pipe\", \"pipe\", \"pipe\"] }) as Child;\n}\n","// codex-app-server-source.ts — LifecycleSource adapter for the codex app-server.\n//\n// Implements D5 from the #333 design: wraps the existing CodexInteractiveDriver\n// as a LifecycleSource without changing the driver's internals.\n//\n// Mechanism: the daemon emit path calls observe(ev) with every ControlEvent that\n// CodexInteractiveDriver emits. This source maps those events to LifecycleSnapshots\n// and feeds them into the reduceLifecycle pipeline via deps.report().\n//\n// Correlation: codex ControlEvents already carry ev.id (taskId), so no\n// deps.resolve() lookup is needed — taskId is known at the call site.\n//\n// NOT wired into the live daemon in Phase 1 (additive per D3/D7).\n// The sibling NativeHookSource crew wires both sources in after this file lands.\n\nimport type { LifecycleSource, LifecycleSourceDeps, LifecycleSnapshot } from \"@squadrant/core\";\nimport type { ControlEvent } from \"@squadrant/shared\";\n\n// ── CodexAppServerSource ─────────────────────────────────────────────────────\n\n/**\n * LifecycleSource adapter for the codex app-server driver.\n *\n * Push-only source: the app-server is event-driven, not polled. snapshot()\n * returns the last reported state for the liveness floor.\n *\n * Usage (daemon wiring, handled by sibling crew):\n * const source = new CodexAppServerSource();\n * source.start(deps);\n * // Wrap the driver's emit so every event also passes through the source:\n * const emit = (ev) => { source.observe(ev); handle(ev); };\n * const driver = new CodexInteractiveDriver({ emit, ... });\n */\nexport class CodexAppServerSource implements LifecycleSource {\n readonly name = \"codex-appserver\";\n\n private deps?: LifecycleSourceDeps;\n /** taskId → last reported snapshot (for snapshot() liveness floor). */\n private cache = new Map<string, LifecycleSnapshot>();\n private active = false;\n\n start(deps: LifecycleSourceDeps): void {\n this.deps = deps;\n this.active = true;\n }\n\n stop(): void {\n this.deps = undefined;\n this.cache.clear();\n this.active = false;\n }\n\n /** Returns the last-reported snapshot for a known crew (liveness floor). */\n snapshot(taskId: string): LifecycleSnapshot | undefined {\n return this.cache.get(taskId);\n }\n\n /** Read-only source health (B4). Purely push-driven — never errors on its own. */\n health(): { active: boolean; error: string | null } {\n return { active: this.active, error: null };\n }\n\n /**\n * Feed a ControlEvent from CodexInteractiveDriver into this source.\n * The daemon wires: emit = (ev) => { source.observe(ev); handle(ev); }\n *\n * All events that carry lifecycle meaning for a codex crew are mapped to a\n * LifecycleSnapshot and reported. Events that are terminal signals (task.done,\n * task.cancelled, task.blocked) or notify-only (task.stalled, task.quiet, etc.)\n * are ignored — terminal state still comes exclusively from `squadrant crew signal`\n * (anti-#2576 invariant).\n */\n observe(ev: ControlEvent): void {\n const snap = toSnapshot(ev);\n if (!snap || !this.deps) return;\n this.cache.set(snap.taskId, snap);\n this.deps.report(snap);\n }\n}\n\n// ── private: ControlEvent → LifecycleSnapshot ────────────────────────────────\n\nfunction toSnapshot(ev: ControlEvent): LifecycleSnapshot | null {\n const now = Date.now();\n switch (ev.type) {\n // ── running: a turn is live ──────────────────────────────────────────────\n case \"task.started\":\n case \"task.reattached\":\n case \"task.turn.started\":\n case \"task.delta\":\n case \"task.progress\":\n return { taskId: ev.id, state: \"running\", alive: true, origin: \"agent\", at: now };\n\n // ── idle: turn ended, crew alive, awaiting next input ────────────────────\n // task.failed: the turn ended with an error, but the crew process is alive.\n // task.session.ended: process is gone (alive:false) — signals liveness loss.\n case \"task.turn.completed\":\n return { taskId: ev.id, state: \"idle\", alive: true, origin: \"agent\", at: now };\n\n case \"task.failed\":\n return { taskId: ev.id, state: \"idle\", alive: true, origin: \"agent\", at: now };\n\n case \"task.session.ended\":\n return { taskId: ev.id, state: \"idle\", alive: false, origin: \"agent\", at: now };\n\n // ── needsInput: crew is blocked on a human ───────────────────────────────\n case \"task.approval.requested\":\n return {\n taskId: ev.id, state: \"needsInput\", alive: true, origin: \"agent\", at: now,\n detail: { note: ev.question, reason: ev.kind },\n };\n\n case \"task.input.requested\":\n return {\n taskId: ev.id, state: \"needsInput\", alive: true, origin: \"agent\", at: now,\n detail: { note: ev.question },\n };\n\n // ── terminal / notify-only — ignored ────────────────────────────────────\n // task.done, task.blocked, task.cancelled: terminal state from crew signal only.\n // task.session, task.stalled, task.quiet, task.idle, task.timeout, etc.: no-op.\n default:\n return null;\n }\n}\n","// src/control/codex/config.ts\n// Read the user's codex config (~/.codex/config.toml or $CODEX_HOME/config.toml)\n// and resolve the active model, applying [notice.model_migrations] so squadrant\n// uses the same model the TUI would use (e.g. gpt-5.3-codex → gpt-5.5).\n\nimport { readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport async function resolveCodexModel(): Promise<string | undefined> {\n const home = process.env[\"CODEX_HOME\"] ?? join(homedir(), \".codex\");\n const configPath = join(home, \"config.toml\");\n\n let text: string;\n try {\n text = await readFile(configPath, \"utf8\");\n } catch {\n return undefined;\n }\n\n // Extract top-level `model = \"...\"` (only before the first section header).\n const topLevel = text.split(/^\\[/m)[0] ?? \"\";\n const modelMatch = topLevel.match(/^model\\s*=\\s*\"([^\"]+)\"/m);\n if (!modelMatch) return undefined;\n let model = modelMatch[1]!;\n\n // Apply [notice.model_migrations] — the TUI uses this map to upgrade legacy\n // model names (e.g. gpt-5.3-codex → gpt-5.5) before calling thread/start.\n // Without this, the app-server sends the stale name and ChatGPT OAuth rejects\n // it with a 400: \"The 'gpt-5.3-codex' model is not supported\".\n // Capture the section body up to the next section header (`^[`) or end of\n // input. JS regex has no `\\z`; `(?![\\s\\S])` is the end-of-input assertion so\n // migrations still resolve when the section is the last one in the file.\n const migSection = text.match(/^\\[notice\\.model_migrations\\]([\\s\\S]*?)(?=^\\[|(?![\\s\\S]))/m);\n if (migSection) {\n const migRe = /^\"([^\"]+)\"\\s*=\\s*\"([^\"]+)\"/mg;\n let m: RegExpExecArray | null;\n while ((m = migRe.exec(migSection[1]!)) !== null) {\n if (m[1] === model) { model = m[2]!; break; }\n }\n }\n\n return model;\n}\n","// src/control/codex/normalize.ts\n// Pure mapping from app-server ServerNotification → squadrant ControlEvent.\n// Spec §4.7. Unknown methods return null (status-line only / forward-compat).\n//\n// Anti-#2576 invariant: NO codex notification maps to task.done.\n// task.done is emitted exclusively by the driver on clean process exit.\n//\n// NOTE: Server-requests (frames that carry an `id` field alongside `method`)\n// are NOT handled here — they are routed by CodexInteractiveDriver (Task 2.4).\n// Extending this function with request handling would be incorrect.\nimport type { ControlEvent } from \"@squadrant/shared\";\n\n/** Minimal shape accepted from the JSON-RPC notification stream. */\nexport type AppServerNotification = { method: string; params?: Record<string, unknown> };\n\n/**\n * Map one app-server notification to a ControlEvent, or null when the\n * notification is informational (token-usage, compaction, status-change)\n * and does not need to enter the squadrant event bus.\n *\n * @param taskId The squadrant task ID that owns this notification stream.\n * @param n Raw notification frame from the app-server JSON-RPC channel.\n */\nexport function normalizeAppServerNotification(\n taskId: string,\n n: AppServerNotification,\n): ControlEvent | null {\n const p = n.params ?? {};\n\n switch (n.method) {\n // ── turn lifecycle ────────────────────────────────────────────────────\n case \"turn/started\":\n return {\n type: \"task.turn.started\",\n id: taskId,\n // TurnStartedNotification carries params.turn.id, not a top-level turnId.\n turnId: String((p[\"turn\"] as Record<string, unknown>)?.[\"id\"] ?? \"\"),\n };\n\n case \"turn/completed\":\n return {\n type: \"task.turn.completed\",\n id: taskId,\n // TurnCompletedNotification: same shape as TurnStartedNotification.\n turnId: String((p[\"turn\"] as Record<string, unknown>)?.[\"id\"] ?? \"\"),\n };\n\n // ── streaming delta (heartbeat / content) ─────────────────────────────\n // AgentMessageDeltaNotification: { threadId, turnId, itemId, delta }\n case \"item/agentMessage/delta\":\n // ReasoningTextDeltaNotification: { threadId, turnId, itemId, delta, contentIndex }\n case \"item/reasoning/textDelta\":\n // CommandExecutionOutputDeltaNotification: { threadId, turnId, itemId, delta }\n case \"item/commandExecution/outputDelta\":\n return {\n type: \"task.delta\",\n id: taskId,\n turnId: String(p[\"turnId\"] ?? \"\"),\n chunk: String(p[\"delta\"] ?? \"\"),\n };\n\n // command/exec/outputDelta is connection-scoped (no turnId); map to delta\n // with empty turnId so the bus can still forward it.\n // CommandExecOutputDeltaNotification: { processId, stream, deltaBase64, capReached }\n case \"command/exec/outputDelta\":\n return {\n type: \"task.delta\",\n id: taskId,\n turnId: \"\",\n chunk: String(p[\"deltaBase64\"] ?? \"\"),\n };\n\n // ── error ─────────────────────────────────────────────────────────────\n // ErrorNotification: { error: TurnError, willRetry, threadId, turnId }\n case \"error\": {\n const err = p[\"error\"] as Record<string, unknown> | undefined;\n const message = String(err?.[\"message\"] ?? p[\"message\"] ?? \"error\");\n return { type: \"task.failed\", id: taskId, error: message };\n }\n\n // ── status-line only — return null ────────────────────────────────────\n // ThreadTokenUsageUpdatedNotification: { threadId, turnId, tokenUsage }\n case \"thread/tokenUsage/updated\":\n // ContextCompactedNotification (deprecated): { threadId, turnId }\n case \"thread/compacted\":\n // ThreadStatusChangedNotification\n case \"thread/status/changed\":\n return null;\n\n // ── unknown / future methods ──────────────────────────────────────────\n default:\n return null;\n }\n}\n","// src/control/codex/driver.ts\n// Daemon-side interactive driver for codex. Owns ONE long-lived AppServerClient\n// child, maps TaskRecord ↔ threadId, emits squadrant ControlEvents via the\n// injected emit() hook. Notification mapping delegates to\n// normalizeAppServerNotification (Task 2.3); the driver only routes server-\n// requests and lifecycle. Spec §4.1/§4.6/§4.7.\n\nimport { AppServerClient } from \"./app-server-client.js\";\nimport { resolveCodexModel } from \"./config.js\";\nimport { normalizeAppServerNotification } from \"./normalize.js\";\nimport type { ControlEvent, TaskRecord } from \"@squadrant/shared\";\nimport { TERMINAL_STATES } from \"@squadrant/shared\";\n\n/**\n * Boot-time guard for the daemon's codex reattach loop. Reattaching a thread\n * re-spawns its per-thread MCP servers (gitnexus/pay), so reattaching EVERY\n * non-terminal codex task on boot re-storms one MCP set per historical crew\n * (observed: 22 zombie tasks → 22 gitnexus servers → RAM exhaustion). Only\n * reattach a task that is (a) interactive codex, (b) non-terminal — closed\n * crews are `cancelled` via codex-close, so they're skipped, (c) still fresh:\n * a dead crew's pane is gone and hasn't heartbeat within the staleness window,\n * and (d) has a resumeRef to resume from.\n */\nexport function shouldReattachCodex(\n rec: TaskRecord,\n now: number,\n staleMs: number,\n): boolean {\n if (rec.provider !== \"codex\" || rec.mode !== \"interactive\") return false;\n if (TERMINAL_STATES.has(rec.state)) return false;\n const last = rec.attempts.at(-1)?.lastHeartbeatAt ?? rec.lastHeartbeat ?? 0;\n if (now - last > staleMs) return false;\n return Boolean(rec.attempts.at(-1)?.resumeRef);\n}\n\nexport interface DriverDeps {\n /** Override for tests; defaults to a real AppServerClient. */\n makeClient?: () => AppServerClient;\n /** Ingress into the daemon's event pipeline. */\n emit: (ev: ControlEvent) => void;\n}\n\nexport class CodexInteractiveDriver {\n private client?: AppServerClient;\n private handshakeP?: Promise<void>;\n private threadByTask = new Map<string, string>();\n private taskByThread = new Map<string, string>();\n /**\n * taskId → in-flight dispatch promise. The first-turn say() can arrive while\n * dispatch() is still awaiting startThread (threadByTask not yet set); say()\n * awaits this gate before reading threadByTask so the first turn isn't lost\n * with \"no thread for task\" (issue #212).\n */\n private dispatchByTask = new Map<string, Promise<void>>();\n /** taskId → last pending server-request {id, method} (for answer()) */\n private serverRequestByTask = new Map<string, { id: number; method: string }>();\n private deps: DriverDeps;\n\n constructor(deps: DriverDeps) { this.deps = deps; }\n\n private async ensureClient(): Promise<AppServerClient> {\n if (this.client) return this.client;\n const c = (this.deps.makeClient ?? (() => new AppServerClient({ clientInfo: { name: \"squadrant\", version: \"iv\" } })))();\n this.client = c;\n c.start();\n c.on(\"notification\", (n) => this.onNotification(n));\n c.on(\"serverRequest\", (r) => this.onServerRequest(r));\n c.on(\"closed\", () => { this.client = undefined; this.handshakeP = undefined; });\n return c;\n }\n\n private async ensureHandshake(): Promise<void> {\n const c = await this.ensureClient();\n if (!this.handshakeP) this.handshakeP = c.initialize().then(() => {});\n return this.handshakeP;\n }\n\n async dispatch(rec: TaskRecord & { cwd?: string; model?: string }): Promise<void> {\n // Register the in-flight dispatch synchronously so a concurrent first-turn\n // say() can await it (see dispatchByTask / issue #212). Cleared once the\n // thread is mapped (or dispatch failed), after which say() reads the map.\n const p = this.runDispatch(rec);\n this.dispatchByTask.set(rec.id, p.then(() => {}, () => {}));\n try {\n await p;\n } finally {\n this.dispatchByTask.delete(rec.id);\n }\n }\n\n private async runDispatch(rec: TaskRecord & { cwd?: string; model?: string }): Promise<void> {\n try {\n const c = await this.ensureClient();\n await withTimeout(this.ensureHandshake(), 10_000, \"handshake timed out\");\n // When no model is explicitly set on the task record, read the user's\n // codex config and apply model migrations (e.g. gpt-5.3-codex → gpt-5.5).\n // Without this the app-server falls back to the raw config value and\n // ChatGPT OAuth rejects it with a 400 (verified: gpt-5.5 succeeds).\n const model = rec.model ?? await resolveCodexModel();\n const { threadId } = await c.startThread({\n cwd: rec.cwd ?? process.cwd(),\n model,\n // Parity with claude/opencode crews, which run UNSANDBOXED (no Seatbelt).\n // Codex was the only agent under `workspace-write`, and that FS sandbox\n // blocked `squadrant crew signal …` from reaching the daemon socket (which\n // lives outside the workspace) — breaking the done/blocked/failed\n // lifecycle. Codex's AF_UNIX-socket allowance has no stable config path\n // (it's gated behind the experimental_network feature), so the surgical\n // writable_roots escape is not viable. danger-full-access removes the FS\n // jail so signals work; approvalPolicy still gates risky ops when set to\n // \"untrusted\" (the gate axis is independent of the sandbox axis).\n sandbox: \"danger-full-access\",\n approvalPolicy: rec.approvalPolicy ?? \"never\",\n developerInstructions: buildCodexDeveloperInstructions(rec),\n });\n this.threadByTask.set(rec.id, threadId);\n this.taskByThread.set(threadId, rec.id);\n this.deps.emit({ type: \"task.session\", id: rec.id, resumeRef: threadId });\n this.deps.emit({ type: \"task.started\", id: rec.id });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n this.deps.emit({ type: \"task.failed\", id: rec.id, error: `handshake/start failed: ${msg}` });\n throw e;\n }\n }\n\n async say(taskId: string, text: string): Promise<void> {\n // Wait out any in-flight dispatch so the first turn isn't dropped during\n // the startThread window (#212). The gate never rejects; a failed dispatch\n // simply leaves threadByTask empty → the existing \"no thread\" throw stands.\n await this.dispatchByTask.get(taskId);\n const c = this.client!;\n const tid = this.threadByTask.get(taskId);\n if (!tid) throw new Error(`no thread for task ${taskId}`);\n await c.sendTurn(tid, text);\n }\n\n async steer(taskId: string, text: string): Promise<void> {\n const c = this.client!;\n const tid = this.threadByTask.get(taskId);\n if (!tid) throw new Error(`no thread for task ${taskId}`);\n await c.steerTurn(tid, text);\n }\n\n async interrupt(taskId: string): Promise<void> {\n const c = this.client!;\n const tid = this.threadByTask.get(taskId);\n if (!tid) throw new Error(`no thread for task ${taskId}`);\n await c.interruptTurn(tid);\n }\n\n /**\n * Tear down a task's thread when its crew closes. Squadrant runs ONE shared\n * app-server with a thread per crew; closing the cmux pane only kills the\n * `crew attach` renderer, so without this the thread — and the gitnexus/pay\n * MCP servers it spawned — leak forever (verified: ~53MB per orphaned crew).\n * Archiving the thread lets the app-server reap it and its MCP children.\n */\n async close(taskId: string): Promise<void> {\n const tid = this.threadByTask.get(taskId);\n this.serverRequestByTask.delete(taskId);\n if (!tid) return;\n this.threadByTask.delete(taskId);\n this.taskByThread.delete(tid);\n try {\n await this.client?.archiveThread(tid);\n } catch {\n // Best-effort: the app-server may already be gone. The maps are cleared\n // regardless so a daemon restart won't try to reattach a dead thread.\n }\n }\n\n /** Kill the long-lived app-server child process on daemon shutdown. */\n stop(): void {\n this.client?.kill();\n }\n\n async answer(taskId: string, payload: unknown): Promise<void> {\n const c = this.client!;\n const rec = this.serverRequestByTask.get(taskId);\n if (rec == null) throw new Error(`no pending server-request for task ${taskId}`);\n c.respondToServerRequest(rec.id, this.mapAnswerPayload(payload, rec.method));\n this.serverRequestByTask.delete(taskId);\n }\n\n /**\n * Map the captain-facing payload ({text, decision}) to the response shape\n * the codex app-server expects for the specific request method.\n *\n * Old protocol (applyPatchApproval / execCommandApproval):\n * { decision: ReviewDecision } where ReviewDecision = \"approved\" | \"denied\" | …\n *\n * v2 protocol (item/commandExecution/requestApproval / item/fileChange/requestApproval):\n * { decision: CommandExecutionApprovalDecision } where decision = \"accept\" | \"decline\" | …\n *\n * Non-approval requests (text input) pass through unchanged.\n */\n private mapAnswerPayload(payload: unknown, method: string): unknown {\n if (typeof payload !== \"object\" || !payload) return payload;\n const p = payload as Record<string, unknown>;\n if (typeof p.decision !== \"string\") return payload;\n if (method === \"applyPatchApproval\" || method === \"execCommandApproval\") {\n const d = p.decision === \"approve\" ? \"approved\" : \"denied\";\n return { decision: d };\n }\n if (method === \"item/commandExecution/requestApproval\" || method === \"item/fileChange/requestApproval\") {\n const d = p.decision === \"approve\" ? \"accept\" : \"decline\";\n return { decision: d };\n }\n // Unknown method — send the raw decision value\n return { decision: p.decision };\n }\n\n async reattach(rec: TaskRecord & { cwd?: string }): Promise<void> {\n await this.ensureHandshake();\n const c = this.client!;\n const resumeRef = rec.attempts.at(-1)?.resumeRef;\n if (!resumeRef) throw new Error(`reattach: no resumeRef on task ${rec.id}`);\n await c.resumeThread({ threadId: resumeRef, cwd: rec.cwd });\n this.threadByTask.set(rec.id, resumeRef);\n this.taskByThread.set(resumeRef, rec.id);\n this.deps.emit({ type: \"task.reattached\", id: rec.id });\n }\n\n private onNotification(n: { method: string; params?: any }): void {\n const tid = n.params?.threadId ?? n.params?.thread_id;\n const taskId = tid ? this.taskByThread.get(tid) : undefined;\n if (!taskId) return; // status-line only\n const ev = normalizeAppServerNotification(taskId, n);\n if (ev) this.deps.emit(ev);\n }\n\n private onServerRequest(r: { id: number; method: string; params?: any }): void {\n const tid = r.params?.threadId ?? r.params?.thread_id;\n let taskId = tid ? this.taskByThread.get(tid) : undefined;\n if (!taskId && !tid) {\n // Codex approval-shaped server-requests don't reliably carry threadId.\n // Fall back to the sole active task if exactly one exists; otherwise drop.\n if (this.taskByThread.size === 1) {\n taskId = this.taskByThread.values().next().value;\n } else {\n process.stderr.write(\n `[codex/driver] serverRequest ${r.method} dropped: no threadId and ${this.taskByThread.size} active tasks\\n`,\n );\n return;\n }\n }\n if (!taskId) return;\n this.serverRequestByTask.set(taskId, { id: r.id, method: r.method });\n const isApproval = r.method.includes(\"Approval\") || r.method.includes(\"approval\");\n if (isApproval) {\n this.deps.emit({\n type: \"task.approval.requested\",\n id: taskId,\n requestId: r.id,\n question: String(r.params?.question ?? r.method),\n kind: r.method,\n });\n } else {\n this.deps.emit({\n type: \"task.input.requested\",\n id: taskId,\n requestId: r.id,\n question: String(r.params?.question ?? r.method),\n });\n }\n }\n}\n\n/**\n * Build the per-thread developerInstructions for a codex crew. Unlike\n * claude/opencode (which get SQUADRANT_CREW_* env vars on their shell launch\n * line), codex tasks share ONE long-lived app-server child, so a process-level\n * env var would be wrong for concurrent tasks. Instead we tell each thread its\n * concrete task id + project and the exact flag-based signal command, so the\n * codex crew can report terminal state via `squadrant crew signal`. Appended\n * after the crew role body (when present) so the role still leads.\n */\nexport function buildCodexDeveloperInstructions(\n rec: { id: string; project: string; roleInstructions?: string },\n): string {\n const directive =\n `You are squadrant crew task ${rec.id} in project ${rec.project}. ` +\n `When you finish, run EXACTLY: squadrant crew signal done --task-id ${rec.id} --project ${rec.project} --message \"<one-line summary>\". ` +\n `If you are blocked or fail, run squadrant crew signal blocked|failed with the same --task-id ${rec.id} --project ${rec.project} flags.`;\n return rec.roleInstructions ? `${rec.roleInstructions}\\n\\n${directive}` : directive;\n}\n\nfunction withTimeout<T>(p: Promise<T>, ms: number, msg: string): Promise<T> {\n return new Promise((resolve, reject) => {\n const t = setTimeout(() => reject(new Error(msg)), ms);\n p.then(\n (v) => { clearTimeout(t); resolve(v); },\n (e) => { clearTimeout(t); reject(e); },\n );\n });\n}\n","// src/control/opencode/sse-bridge.ts\n// Daemon-side bridge from an opencode crew's HTTP event bus to squadrant\n// ControlEvents. Interactive opencode crews launch as `opencode --port <N>`,\n// which binds a local HTTP server exposing an SSE stream at GET /event. The TUI\n// itself is just one client of that server; the daemon is another. We subscribe\n// once per crew and translate the documented `session.idle` event (emitted when\n// a turn finishes) into `task.turn.completed`, which the state-machine reduces\n// to `awaiting-input`. This gives opencode the same reliable turn-end signal\n// codex gets from its app-server — WITHOUT the crew shelling out to squadrant.\n//\n// `session.idle` is liveness, NOT completion (anti-#2576): a finished turn is\n// not a finished task. Terminal state still comes from the explicit\n// `squadrant crew signal done` in the crew template; the reducer absorbs any\n// session.idle that arrives after the task is already terminal.\nimport type { ControlEvent } from \"@squadrant/shared\";\n\nexport interface OpencodeSseBridgeDeps {\n /** Ingress into the daemon's event pipeline (resolves project + handles). */\n emit: (ev: ControlEvent) => void;\n /** Injectable for tests; defaults to global fetch. */\n fetchImpl?: typeof fetch;\n /** Injectable backoff for tests; defaults to setTimeout. */\n sleep?: (ms: number) => Promise<void>;\n /** Backoff between reconnect attempts (ms, default 500). */\n reconnectMs?: number;\n /** Attempts to reach the server before giving up the boot wait (default 240\n * ≈ 120s). Must comfortably outlast the crew's own first-turn delivery\n * budget (SEND_FIRST_TURN_TIMEOUT_MS = 90s in crew-pane.ts) — #504:\n * otherwise the bridge can give up and permanently stop watching a crew\n * (both turn-end AND permission-gate detection) while the CLI's own\n * pane-polling delivery mechanism is still patiently retrying and\n * eventually succeeds, leaving the daemon silently, irrecoverably blind\n * for the rest of that crew's life. Live-reproduced 2026-07-02: opencode's\n * embedded HTTP server took >30s to bind under concurrent crew-spawn load,\n * the bridge gave up at 60 attempts, and the crew's later permission gate\n * (which fires correctly once subscribed — verified live) was never seen. */\n maxBootAttempts?: number;\n log?: (msg: string) => void;\n}\n\n/**\n * One long-lived SSE subscription per opencode crew. Keyed by taskId so the\n * daemon can stop it when the crew closes. Self-stops when the server's stream\n * ends (crew CLI exited) — at that point terminal state has already been\n * recorded via signal, or the watchdog/close path will reconcile.\n */\nexport class OpencodeSseBridge {\n private controllers = new Map<string, AbortController>();\n /** taskId → the crew's opencode server port (for permission-reply POSTs). */\n private portByTask = new Map<string, number>();\n /** taskId → the last unresolved permission on the bus (for answer()). */\n private pendingPermByTask = new Map<string, { permID: string; sessionID: string }>();\n /** Synthetic monotonic request id. opencode has no numeric id on the bus, but\n * task.approval.requested carries one (codex parity) to key gate promotion. */\n private nextRequestId = 1;\n private deps: OpencodeSseBridgeDeps;\n\n constructor(deps: OpencodeSseBridgeDeps) {\n this.deps = deps;\n }\n\n /** Begin subscribing to the crew's /event stream. Idempotent per task. */\n start(o: { taskId: string; port: number }): void {\n if (this.controllers.has(o.taskId)) return;\n this.portByTask.set(o.taskId, o.port);\n const ac = new AbortController();\n this.controllers.set(o.taskId, ac);\n void this.run(o.taskId, o.port, ac);\n }\n\n /** Stop subscribing for a task (crew closed / terminal). */\n stop(taskId: string): void {\n const ac = this.controllers.get(taskId);\n if (ac) { ac.abort(); this.controllers.delete(taskId); }\n this.portByTask.delete(taskId);\n this.pendingPermByTask.delete(taskId);\n }\n\n /**\n * Resolve a pending opencode permission by POSTing the captain's decision to\n * the crew's server (live-verified, opencode 1.15.13: POST\n * /session/{sessionID}/permissions/{permissionID} with\n * { response: \"once\" | \"reject\" } → 200, fires permission.replied). Mirrors\n * codex's driver.answer(). Returns true if there WAS a pending permission (so\n * the caller knows the answer was an approval, not a reply to a plain `signal\n * blocked` question); false if nothing was pending (already resolved on the\n * bus, or no gate).\n */\n async answer(taskId: string, decision: \"approve\" | \"deny\"): Promise<boolean> {\n const pend = this.pendingPermByTask.get(taskId);\n const port = this.portByTask.get(taskId);\n if (!pend || port == null) return false;\n this.pendingPermByTask.delete(taskId);\n const fetchImpl = this.deps.fetchImpl ?? fetch;\n const response = decision === \"approve\" ? \"once\" : \"reject\";\n try {\n await fetchImpl(`http://127.0.0.1:${port}/session/${pend.sessionID}/permissions/${pend.permID}`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ response }),\n });\n } catch (e) {\n this.deps.log?.(`opencode permission reply failed for ${taskId}: ${(e as Error).message}`);\n }\n // Clear blocked → working; the crew continues (or aborts) the turn, and a\n // later session.idle settles it back to awaiting-input.\n this.deps.emit({ type: \"task.started\", id: taskId });\n return true;\n }\n\n private async run(taskId: string, port: number, ac: AbortController): Promise<void> {\n const fetchImpl = this.deps.fetchImpl ?? fetch;\n const sleep = this.deps.sleep ?? ((ms: number) => new Promise((r) => setTimeout(r, ms)));\n const reconnectMs = this.deps.reconnectMs ?? 500;\n const maxBoot = this.deps.maxBootAttempts ?? 240;\n const url = `http://127.0.0.1:${port}/event`;\n let booted = false;\n let bootAttempts = 0;\n\n while (!ac.signal.aborted) {\n try {\n const res = await fetchImpl(url, {\n signal: ac.signal,\n headers: { accept: \"text/event-stream\" },\n });\n if (!res.ok || !res.body) throw new Error(`status ${res.status}`);\n booted = true;\n await this.consume(taskId, res.body, ac);\n // Stream ended cleanly: the opencode server closed (crew CLI exited).\n // Nothing more to subscribe to — stop without reconnecting.\n break;\n } catch (e) {\n if (ac.signal.aborted) return;\n if (!booted) {\n bootAttempts++;\n if (bootAttempts >= maxBoot) {\n this.deps.log?.(\n `opencode SSE bridge: gave up connecting to ${url} after ${bootAttempts} attempts: ${(e as Error).message}`,\n );\n this.controllers.delete(taskId);\n return;\n }\n }\n await sleep(reconnectMs);\n }\n }\n this.controllers.delete(taskId);\n }\n\n private async consume(\n taskId: string,\n body: ReadableStream<Uint8Array>,\n ac: AbortController,\n ): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buf = \"\";\n try {\n while (!ac.signal.aborted) {\n const { done, value } = await reader.read();\n if (done) return;\n buf += decoder.decode(value, { stream: true });\n let nl: number;\n while ((nl = buf.indexOf(\"\\n\")) >= 0) {\n const line = buf.slice(0, nl);\n buf = buf.slice(nl + 1);\n this.handleLine(taskId, line);\n }\n }\n } finally {\n try { await reader.cancel(); } catch { /* already closed */ }\n }\n }\n\n private handleLine(taskId: string, rawLine: string): void {\n let line = rawLine.trim();\n if (!line) return;\n // SSE field form `data: {json}`; opencode also emits bare JSON lines.\n if (line.startsWith(\"data:\")) line = line.slice(5).trim();\n if (!line.startsWith(\"{\")) return;\n let json:\n | {\n type?: string;\n properties?: {\n id?: string;\n sessionID?: string;\n requestID?: string;\n permission?: string;\n patterns?: string[];\n };\n }\n | undefined;\n try {\n json = JSON.parse(line);\n } catch {\n return; // partial/non-JSON keepalive line\n }\n if (json?.type === \"session.idle\") {\n // turnId is informational for opencode (no per-turn id on the bus); use\n // the session id so the ledger attempt carries a stable correlation key.\n this.deps.emit({\n type: \"task.turn.completed\",\n id: taskId,\n turnId: json.properties?.sessionID ?? \"opencode\",\n });\n } else if (json?.type === \"permission.asked\") {\n // A gated tool (e.g. bash, when --approval set bash:\"ask\") needs approval.\n // Live-verified payload (opencode 1.15.13): properties = PermissionRequest\n // { id:\"per_…\", sessionID:\"ses_…\", permission:\"bash\", patterns:[cmd], … }.\n // Record the pending request so answer() can POST the decision, and surface\n // it as task.approval.requested (codex parity) — the reducer turns it into\n // blocked and the relay renders CREW BLOCKED with the tool + command.\n const p = json.properties;\n if (p?.id && p?.sessionID) {\n this.pendingPermByTask.set(taskId, { permID: p.id, sessionID: p.sessionID });\n const tool = p.permission ?? \"a tool\";\n const cmd = Array.isArray(p.patterns) && p.patterns.length ? `: ${p.patterns.join(\" \")}` : \"\";\n this.deps.emit({\n type: \"task.approval.requested\",\n id: taskId,\n requestId: this.nextRequestId++,\n question: `opencode requests permission to run ${tool}${cmd}`,\n kind: tool,\n });\n }\n } else if (json?.type === \"permission.replied\") {\n // The permission was resolved on the bus (by us or another client) — clear\n // pending state so a later captain answer is a no-op rather than a stale POST.\n this.pendingPermByTask.delete(taskId);\n }\n }\n}\n","import { execSync } from \"node:child_process\";\nimport { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { InteractiveHookAdapter } from \"./types.js\";\nimport type { ControlEvent } from \"@squadrant/shared\";\n\n// PostToolUse fires after EVERY tool call mid-turn — it is the only liveness\n// signal that refreshes the heartbeat while a crew is still working.\n// Stop fires at turn completion and maps to task.turn.completed so the task\n// transitions to awaiting-input (immune to stall detection) — without this,\n// a captain AFK for >heartbeatBudgetMs would get a false CREW STALLED.\n// SubagentStop fires only at a turn boundary but is liveness-only — it fires\n// while the parent agent still owns the turn. SessionEnd is NOT liveness: it\n// signals the session is gone (crash / Ctrl-C / /exit), so it terminalizes the\n// record (→ task.session.ended) rather than resuming 'working' (#139).\n// UserPromptSubmit fires before Claude processes each prompt submission, including\n// the first interactive turn — used as the authoritative first-turn confirmation\n// signal (#470), replacing the screen-scrape {delivered} heuristic.\nconst EVENTS = [\"Stop\", \"SubagentStop\", \"SessionEnd\", \"PostToolUse\", \"Notification\", \"UserPromptSubmit\"] as const;\n\n// #560: matcher-scoped hook entries beyond the broad EVENTS list above — fires\n// only for the named tool, not every tool call. AskUserQuestion is CC's native\n// interactive-prompt tool: PreToolUse fires the instant it opens (and blocks\n// the turn awaiting a human selection), so this is the earliest possible signal\n// that a crew is blocked on a question. Scoped to this one tool so it doesn't\n// double the per-tool-call hook overhead PostToolUse already covers.\nconst MATCHED_EVENTS: ReadonlyArray<readonly [event: string, matcher: string]> = [\n [\"PreToolUse\", \"AskUserQuestion\"],\n];\n\n// #560: Claude's PreToolUse hook payload carries no native per-tool-call id\n// (documented shape is session_id/cwd/tool_name/tool_input only — no\n// tool_use_id), so there is no \"real\" requestId to forward. Seeded from\n// Date.now() and incremented per call (this module runs fresh per hook\n// invocation, so in practice each call gets Date.now() at that moment) so\n// schedulePromotion's `${taskId}#${requestId}` dedup key never collides\n// across successive AskUserQuestion prompts for the same crew, unlike a\n// hardcoded 0 would.\nlet nextAskUserQuestionRequestId = Date.now();\n\n/**\n * Probe whether the local Claude CLI supports `--settings <path>`. The\n * daemon-supervised crew path needs per-invocation settings to inject the\n * squadrant Stop hook without polluting the user's global `~/.claude/settings.json`\n * (the scrapped PR #71 mistake). Returns \"flag\" when --settings is available\n * (the happy path), \"project-dir\" when the fallback (write `.claude/settings.json`\n * under the project dir + cd) is needed.\n */\nexport function probeClaudeSettingsFlag(): \"flag\" | \"project-dir\" {\n try {\n const help = execSync(\"claude --help\", { encoding: \"utf-8\", stdio: [\"ignore\", \"pipe\", \"ignore\"] });\n return help.includes(\"--settings \") ? \"flag\" : \"project-dir\";\n } catch {\n return \"project-dir\";\n }\n}\n\n/**\n * Pure: returns true when a Notification hook message indicates Claude is waiting\n * for the user to grant a tool-use permission. Idle notifications (\"Waiting for\n * your input\", \"Claude is thinking\") return false — only permission/approval\n * language triggers the fast-path task.blocked path.\n */\nexport function isPermissionNotification(message: string): boolean {\n if (!message || !message.trim()) return false;\n const lower = message.toLowerCase();\n return lower.includes(\"permission\") || lower.includes(\"approve\");\n}\n\n// Keyed on (event, matcher) — NOT command alone. An event can carry both a\n// bare entry (matcher \"\", from EVENTS) and a matcher-scoped entry (from\n// MATCHED_EVENTS) with the identical command string (only the matcher\n// differs; Claude dispatches on matcher, not on the command text). Scanning\n// ALL entries for the event regardless of matcher would make the\n// matcher-scoped install look \"already done\" the moment a bare entry for the\n// same event+command exists, and silently skip installing it — the same\n// silent-drop failure mode this hook set exists to close (#560).\nfunction installHookEntry(hooks: Record<string, unknown>, event: string, matcher: string, command: string): void {\n if (!Array.isArray(hooks[event])) hooks[event] = [];\n const entries = hooks[event] as unknown[];\n const already = entries.some(\n (m) => (m as any)?.matcher === matcher &&\n Array.isArray((m as any)?.hooks) &&\n (m as any).hooks.some((h: any) => typeof h?.command === \"string\" && h.command.includes(command)),\n );\n if (!already) {\n entries.push({ matcher, hooks: [{ type: \"command\", command, timeout: 10 }] });\n }\n}\n\n/** Pure, idempotent merge of squadrant hooks into a Claude settings object. */\nexport function mergeClaudeHooks(settings: any, hookCmd: string): any {\n const next = structuredClone(settings ?? {});\n next.hooks ??= {};\n for (const ev of EVENTS) {\n installHookEntry(next.hooks, ev, \"\", `${hookCmd} ${ev}`);\n }\n for (const [ev, matcher] of MATCHED_EVENTS) {\n installHookEntry(next.hooks, ev, matcher, `${hookCmd} ${ev}`);\n }\n return next;\n}\n\n/**\n * Pure, conservative detector for a trailing question that needs captain input.\n * Returns the question text when the LAST non-empty line of the message (outside\n * any fenced code block) ends with \"?\", else null. Intentionally narrow to avoid\n * false-blocked: rhetorical mid-text questions and questions inside ```fences```\n * are ignored because only the final visible line counts. When unsure → null.\n */\nexport function detectTrailingQuestion(text: string): string | null {\n if (!text) return null;\n let inFence = false;\n let lastLine: string | null = null;\n for (const raw of text.split(/\\r?\\n/)) {\n const line = raw.trim();\n if (line.startsWith(\"```\")) { inFence = !inFence; continue; }\n if (inFence || line === \"\") continue;\n lastLine = line;\n }\n if (lastLine && lastLine.endsWith(\"?\")) return lastLine;\n return null;\n}\n\n/**\n * Pure: derive the Claude transcript JSONL path for a session. Claude stores\n * transcripts at ~/.claude/projects/<escaped-cwd>/<session_id>.jsonl, where the\n * cwd is escaped by replacing every non-alphanumeric char with \"-\" (verified\n * against the live ~/.claude/projects layout — e.g. /Users/q3labsadmin/.claude-mem\n * -> -Users-q3labsadmin--claude-mem). Returns null if sessionId or cwd is missing.\n * This is the layered fallback for #174 when the Stop payload omits transcript_path.\n */\nexport function deriveTranscriptPath(sessionId: string, cwd: string): string | null {\n if (!sessionId || !cwd) return null;\n const escaped = cwd.replace(/[^a-zA-Z0-9]/g, \"-\");\n return join(homedir(), \".claude\", \"projects\", escaped, `${sessionId}.jsonl`);\n}\n\n/**\n * I/O: read the LAST assistant message text from a Claude transcript JSONL file.\n * Kept separate from the pure detector so the detector stays trivially testable.\n * Never throws — returns null on any read/parse failure (the hook must exit 0).\n */\nfunction readLastAssistantText(transcriptPath: string): string | null {\n try {\n const raw = readFileSync(transcriptPath, \"utf-8\");\n const lines = raw.split(/\\r?\\n/);\n for (let i = lines.length - 1; i >= 0; i--) {\n const line = lines[i].trim();\n if (!line) continue;\n let entry: any;\n try { entry = JSON.parse(line); } catch { continue; }\n const isAssistant = entry?.type === \"assistant\" || entry?.message?.role === \"assistant\";\n if (!isAssistant) continue;\n const content = entry?.message?.content;\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n const txt = content\n .filter((b: any) => b?.type === \"text\" && typeof b.text === \"string\")\n .map((b: any) => b.text)\n .join(\"\\n\")\n .trim();\n return txt || null;\n }\n return null;\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * I/O: obtain the last-assistant text from a LAYERED source, first hit wins:\n * 0. payload.last_assistant_message — the field Claude puts the final assistant\n * text in DIRECTLY on the Stop payload (verified against claude-cli 2.1.156:\n * carries the full final message, including a trailing question, no I/O). This\n * is the primary source and the real #174 delivery fix — earlier diagnoses\n * chased transcript_path (which can be absent), but the message is right here.\n * 1. else payload.transcript_path (documented field, when present + readable);\n * 2. else the path derived from payload.session_id + cwd (defensive fallback for\n * older clients that omit both of the above).\n * cwd preference: payload.cwd (Claude hook contract) → SQUADRANT_CREW_CWD → cwd().\n * Best-effort: a null/miss from one source falls through to the next; never throws.\n */\nfunction resolveLastAssistantText(payload: unknown): string | null {\n const p = payload as any;\n const direct = p?.last_assistant_message;\n if (typeof direct === \"string\" && direct.trim()) return direct;\n const candidates: string[] = [];\n const tp = p?.transcript_path;\n if (typeof tp === \"string\" && tp) candidates.push(tp);\n const cwd = (typeof p?.cwd === \"string\" && p.cwd) ? p.cwd : (process.env.SQUADRANT_CREW_CWD || process.cwd());\n const derived = deriveTranscriptPath(p?.session_id, cwd);\n if (derived) candidates.push(derived);\n for (const path of candidates) {\n const text = readLastAssistantText(path);\n if (text != null) return text;\n }\n return null;\n}\n\n/**\n * Pure: render an AskUserQuestion tool call's `tool_input` (the raw arguments\n * Claude passes to the tool — `{ questions: [{ question, header, options,\n * multiSelect }] }`) into a human-readable prompt for CREW BLOCKED, carrying\n * both the question text AND its options (#560's proposal explicitly asks for\n * both — an option-less \"awaiting input\" placeholder can't be answered by\n * #562's answer channel or checked for staleness by #563).\n * Never throws; returns null when the shape doesn't match (caller must still\n * surface SOME text — see mapClaudeHookToEvent's PreToolUse case).\n */\nexport function formatAskUserQuestionPrompt(toolInput: unknown): string | null {\n const questions = (toolInput as { questions?: unknown } | null | undefined)?.questions;\n if (!Array.isArray(questions) || questions.length === 0) return null;\n const parts: string[] = [];\n for (const q of questions) {\n if (!q || typeof q !== \"object\") continue;\n const text = (q as any).question;\n if (typeof text !== \"string\" || !text.trim()) continue;\n const options = Array.isArray((q as any).options) ? (q as any).options : [];\n const labels = options\n .map((o: any) => (o && typeof o.label === \"string\" ? o.label.trim() : null))\n .filter((l: string | null): l is string => !!l);\n parts.push(labels.length > 0 ? `${text.trim()} (options: ${labels.join(\", \")})` : text.trim());\n }\n return parts.length > 0 ? parts.join(\" | \") : null;\n}\n\n/**\n * Map a Claude hook event name to a squadrant ControlEvent. Codifies the anti-#2576\n * invariant: NO Claude hook ever maps to `task.done`/`task.failed`.\n * PostToolUse/SubagentStop = resume-liveness only (task.progress). SessionEnd is\n * the lone terminalizing hook: the session is gone, so it maps to\n * task.session.ended → cancelled (#139) — silent, never done/failed.\n * Terminal `done`/`failed` come exclusively from explicit `squadrant crew signal`.\n *\n * Stop = turn boundary. It normally maps to task.turn.completed → awaiting-input\n * (stall-immune) so a captain reviewing output never trips a false CREW STALLED\n * (fixes #131). NARROW EXCEPTION #1 (#174): when the crew's last assistant message\n * ENDS with a direct question, Stop maps to task.blocked instead, surfacing the\n * question to the captain as CREW BLOCKED. The last-assistant text is obtained from\n * a LAYERED source (last_assistant_message on the payload → transcript_path →\n * derived path from session_id+cwd); the payload field is the primary, I/O-free\n * source. All transcript I/O is best-effort and never throws (hook must exit 0).\n *\n * Notification = Claude needs user attention. NARROW EXCEPTION #2\n * (#notification-hook): when the payload.message indicates a permission request\n * (isPermissionNotification), this maps to task.blocked instantly — bypassing the\n * ~20-30s relay poll. The relay poll remains as a fallback for opencode crews and\n * as a safety net; both may fire task.blocked for the same prompt, but the\n * state-machine idempotency (already-blocked → no-op, from #176) deduplicates.\n * Non-permission notifications (idle liveness) → task.progress. Missing/non-string\n * message → task.progress (never throws, hook must exit 0).\n *\n * PreToolUse = matcher-scoped to AskUserQuestion only (#560): the crew's own\n * hook set registers this ONLY for that tool (see MATCHED_EVENTS above), so in\n * practice tool_name is always \"AskUserQuestion\" here. Still checked\n * defensively — a config regression to a bare/unmatched PreToolUse must not\n * silently start reporting task.input.requested for every tool call. When it\n * IS AskUserQuestion, this maps to task.input.requested (NOT task.blocked —\n * task.blocked has no requestId field, and requestId is what\n * ctx.schedulePromotion in squadrantd.ts keys its answer-routing timer on;\n * task.input.requested already drives state-machine.ts → state 'blocked',\n * the CREW BLOCKED notification, and Telegram formatting) UNCONDITIONALLY —\n * even a malformed/unreadable tool_input still produces a generic fallback\n * question rather than falling through to null, because a detection path\n * that can silently fail to fire is the exact defect #560 exists to close.\n */\nexport function mapClaudeHookToEvent(\n event: string,\n payload: unknown,\n taskId: string,\n): ControlEvent | null {\n switch (event) {\n case \"PreToolUse\": {\n const toolName = (payload as any)?.tool_name;\n if (toolName !== \"AskUserQuestion\") return null;\n const question = formatAskUserQuestionPrompt((payload as any)?.tool_input)\n ?? \"crew opened an AskUserQuestion prompt (options unavailable)\";\n return { type: \"task.input.requested\", id: taskId, requestId: nextAskUserQuestionRequestId++, question };\n }\n case \"Stop\": {\n const text = resolveLastAssistantText(payload);\n const question = text ? detectTrailingQuestion(text) : null;\n if (question) {\n return { type: \"task.blocked\", id: taskId, reason: \"crew asked a question (auto-detected)\", question };\n }\n return { type: \"task.turn.completed\", id: taskId, turnId: \"hook-stop\" };\n }\n case \"Notification\": {\n const msg = (payload as any)?.message;\n if (typeof msg === \"string\" && isPermissionNotification(msg)) {\n return { type: \"task.blocked\", id: taskId, reason: \"crew awaiting permission (notification hook)\", question: msg };\n }\n return { type: \"task.progress\", id: taskId, note: \"notification\" };\n }\n case \"SessionEnd\":\n // #139: the session is GONE. NOT liveness — mapping this to task.progress\n // resumed a dead crew to 'working' (awaiting-input → working), where\n // nothing heartbeats and the watchdog false-stalled it ~budget later.\n // Terminalize the record instead (reducer: task.session.ended → cancelled).\n return { type: \"task.session.ended\", id: taskId };\n case \"SubagentStop\":\n case \"PostToolUse\":\n // The only resume-liveness hooks: PostToolUse fires after every tool call\n // mid-turn; SubagentStop fires while the parent still owns the turn.\n return { type: \"task.progress\", id: taskId, note: event.toLowerCase() };\n case \"UserPromptSubmit\":\n // #470: fires before Claude processes each prompt, including the first.\n // The reducer stamps firstTurnConfirmedAt only on the first occurrence;\n // subsequent submits (captain crew send follow-ups) are treated as liveness.\n return { type: \"task.first-turn.confirmed\", id: taskId };\n default:\n return null;\n }\n}\n\nexport const claudeInteractive: InteractiveHookAdapter = {\n provider: \"claude\",\n tier: \"strong\",\n injectHook(launchSpec) {\n // Claude reads merged ~/.config settings; nothing to add to argv here.\n // The settings merge is performed by the launcher (Task 18) before spawn.\n return launchSpec;\n },\n};\n","// src/control/headless/types.ts\nexport const HEADLESS_ERROR_TAIL = 2000;\n\nexport interface HeadlessResult {\n outcome: \"done\" | \"failed\";\n /** Always a string: result text, JSON-stringified non-string result, or raw stdout fallback. Becomes resultRef contents. */\n payload?: string;\n sessionId?: string;\n error?: string;\n exitCode?: number;\n parseWarning?: boolean;\n}\n\nexport interface HeadlessAdapter {\n provider: string;\n buildCommand(task: string, sessionId?: string): string[];\n parseResult(stdout: string, exitCode: number): HeadlessResult;\n}\n","// src/control/headless/claude.ts\nimport type { HeadlessAdapter } from \"./types.js\";\nimport { HEADLESS_ERROR_TAIL } from \"./types.js\";\n\nexport const claudeHeadless: HeadlessAdapter = {\n provider: \"claude\",\n buildCommand(task, sessionId) {\n const argv = [\"claude\", \"-p\", \"--output-format\", \"json\"];\n if (sessionId) argv.push(\"--resume\", sessionId);\n argv.push(task);\n return argv;\n },\n parseResult(stdout, exitCode) {\n if (exitCode !== 0) {\n return { outcome: \"failed\", exitCode, error: stdout.slice(-HEADLESS_ERROR_TAIL) };\n }\n try {\n const j = JSON.parse(stdout);\n if (j.is_error) return { outcome: \"failed\", error: String(j.result ?? \"is_error\"), sessionId: j.session_id };\n const payload = typeof j.result === \"string\" ? j.result : j.result == null ? \"\" : JSON.stringify(j.result);\n return { outcome: \"done\", sessionId: j.session_id, payload };\n } catch {\n return { outcome: \"done\", parseWarning: true, payload: stdout };\n }\n },\n};\n","// src/control/headless/opencode.ts\nimport type { HeadlessAdapter } from \"./types.js\";\nimport { HEADLESS_ERROR_TAIL } from \"./types.js\";\n\n// opencode `run` is used for one-shot; serve-session wiring is a later spec.\n// Process-exit is the done-signal here (foundational scope).\nexport const opencodeHeadless: HeadlessAdapter = {\n provider: \"opencode\",\n buildCommand(task, sessionId) {\n const argv = [\"opencode\", \"run\", \"--format\", \"json\"];\n if (sessionId) argv.push(\"--session\", sessionId);\n argv.push(task);\n return argv;\n },\n parseResult(stdout, exitCode) {\n if (exitCode !== 0) return { outcome: \"failed\", exitCode, error: stdout.slice(-HEADLESS_ERROR_TAIL) };\n try {\n const j = JSON.parse(stdout);\n const payload = typeof j.result === \"string\" ? j.result : JSON.stringify(j.result ?? stdout);\n return { outcome: \"done\", sessionId: j.sessionID ?? j.session_id, payload };\n } catch {\n return { outcome: \"done\", parseWarning: true, payload: stdout };\n }\n },\n};\n","// src/control/headless/codex.ts\nimport type { HeadlessAdapter } from \"./types.js\";\nimport { HEADLESS_ERROR_TAIL } from \"./types.js\";\n\nexport const codexHeadless: HeadlessAdapter = {\n provider: \"codex\",\n buildCommand(task, sessionId) {\n // Verified against codex-cli 0.130.0 `codex exec [OPTIONS] [PROMPT]`:\n // --json: JSONL events to stdout (valid).\n // --skip-git-repo-check: REQUIRED — the daemon spawns codex with a\n // non-trusted/non-git cwd under launchd; without it codex aborts with\n // \"Not inside a trusted directory and --skip-git-repo-check was not\n // specified.\" (real production failure, red-team/verify-on-implement).\n // resume is a SUBCOMMAND (`codex exec resume <id>`), NOT a `--session`\n // flag. Resume is unused in foundational scope (multi-turn/reply\n // deferred) — kept best-effort; flag order is verify-on-implement when\n // the interactive-wiring spec lands.\n // --sandbox workspace-write: codex exec defaults to a READ-ONLY sandbox,\n // so a crew could analyze/spec but never edit code (real prod finding:\n // codex bailed \"workspace is mounted read-only\"). workspace-write lets it\n // edit within its cwd (set by the launcher per-task) — NOT full-disk\n // (danger-full-access) which would be reckless for an autonomous agent.\n const opts = [\"--json\", \"--skip-git-repo-check\", \"--sandbox\", \"workspace-write\"];\n if (sessionId) return [\"codex\", \"exec\", \"resume\", sessionId, ...opts, task];\n return [\"codex\", \"exec\", ...opts, task];\n },\n parseResult(stdout, exitCode) {\n if (exitCode !== 0) return { outcome: \"failed\", exitCode, error: stdout.slice(-HEADLESS_ERROR_TAIL) };\n // codex result format undocumented; keep raw, never guess failure.\n return { outcome: \"done\", payload: stdout };\n },\n};\n","// src/control/headless/registry.ts\nimport type { HeadlessAdapter } from \"./types.js\";\nimport { claudeHeadless } from \"./claude.js\";\nimport { opencodeHeadless } from \"./opencode.js\";\nimport { codexHeadless } from \"./codex.js\";\n\nconst ADAPTERS: Record<string, HeadlessAdapter> = {\n claude: claudeHeadless,\n opencode: opencodeHeadless,\n codex: codexHeadless,\n};\n\nexport function getHeadlessAdapter(provider: string): HeadlessAdapter {\n const a = ADAPTERS[provider];\n if (!a) throw new Error(`no headless adapter for provider '${provider}'`);\n return a;\n}\n","// src/control/headless-launcher.ts\nimport type { spawn as nodeSpawn } from \"node:child_process\";\nimport type { ControlEvent } from \"@squadrant/shared\";\nimport { getHeadlessAdapter } from \"./headless/registry.js\";\n\nexport interface RunHeadlessOpts {\n provider: string;\n task: string;\n id: string;\n sessionId?: string;\n /**\n * Working dir for the spawned child. Headless previously inherited the\n * daemon's launchd cwd (`/`) — wrong for every provider, and the reason\n * codex could only do read-only work. Unset → inherit (back-compat).\n */\n cwd?: string;\n spawn: typeof nodeSpawn;\n emit: (e: ControlEvent) => void;\n /** Where to persist captured payload; defaults handled by caller (Task 17). */\n writeResult?: (id: string, payload: string) => string;\n}\n\nexport interface HeadlessHandle {\n result: Promise<void>;\n kill: () => void;\n}\n\n// Max bytes retained in the stdout/stderr capture buffers (oldest dropped).\nconst OUT_CAP = 4 * 1024 * 1024;\nconst ERR_CAP = 4 * 1024 * 1024;\n// Emit task.progress at most once per interval OR once per batch, whichever first.\nconst PROGRESS_INTERVAL_MS = 250;\nconst PROGRESS_CHUNK_BATCH = 50;\n\nexport function runHeadless(opts: RunHeadlessOpts): HeadlessHandle {\n const adapter = getHeadlessAdapter(opts.provider);\n const argv = adapter.buildCommand(opts.task, opts.sessionId);\n const child = opts.spawn(argv[0], argv.slice(1), {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n cwd: opts.cwd, // undefined → inherit daemon cwd (back-compat)\n });\n opts.emit({ type: \"task.started\", id: opts.id, pid: child.pid ?? undefined });\n\n let out = \"\";\n let err = \"\";\n\n // Debounce state — coalesces task.progress to avoid O(chunks) file writes.\n let lastProgressAt = 0;\n let chunksSinceProgress = 0;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n function flushProgress(): void {\n if (debounceTimer) { clearTimeout(debounceTimer); debounceTimer = null; }\n lastProgressAt = Date.now();\n chunksSinceProgress = 0;\n opts.emit({ type: \"task.progress\", id: opts.id }); // stdout activity = liveness\n }\n\n child.stdout?.on(\"data\", (d) => {\n out += String(d);\n if (out.length > OUT_CAP) out = out.slice(out.length - OUT_CAP);\n chunksSinceProgress++;\n const now = Date.now();\n if (chunksSinceProgress >= PROGRESS_CHUNK_BATCH || now - lastProgressAt >= PROGRESS_INTERVAL_MS) {\n flushProgress();\n } else if (!debounceTimer) {\n const delay = PROGRESS_INTERVAL_MS - (now - lastProgressAt);\n debounceTimer = setTimeout(() => { debounceTimer = null; flushProgress(); }, delay);\n }\n });\n child.stderr?.on(\"data\", (d) => {\n err += String(d);\n if (err.length > ERR_CAP) err = err.slice(err.length - ERR_CAP);\n });\n\n const result = new Promise<void>((resolve) => {\n child.once(\"error\", (e: Error) => {\n if (debounceTimer) { clearTimeout(debounceTimer); debounceTimer = null; }\n opts.emit({ type: \"task.failed\", id: opts.id, error: `spawn error: ${e.message}`, exitCode: undefined });\n resolve(); // never hang the daemon; resolve() is idempotent\n });\n child.on(\"close\", (code) => {\n // Flush any batched-but-not-yet-emitted activity before the terminal event.\n if (chunksSinceProgress > 0) flushProgress();\n else if (debounceTimer) { clearTimeout(debounceTimer); debounceTimer = null; }\n const parseInput = (code !== 0 && err) ? err : (out || err);\n const res = adapter.parseResult(parseInput, code ?? 0);\n if (res.outcome === \"failed\") {\n opts.emit({ type: \"task.failed\", id: opts.id, error: res.error ?? \"non-zero exit\", exitCode: res.exitCode });\n } else {\n const ref = opts.writeResult ? opts.writeResult(opts.id, res.payload ?? \"\") : \"\";\n opts.emit({ type: \"task.done\", id: opts.id, resultRef: ref, parseWarning: res.parseWarning });\n }\n resolve();\n });\n });\n\n return { result, kill: () => child.kill(\"SIGTERM\") };\n}\n","import { execFile, execFileSync } from \"node:child_process\";\nimport type { RuntimeDriver, RuntimeProbeResult, RuntimeSpawnOptions, WorkspaceRef, PaneRef, RuntimePaneOptions } from \"./types.js\";\nimport { resolveCmuxBin } from \"@squadrant/shared\";\nimport { checkToolCompat } from \"@squadrant/shared\";\nimport { compatManifest } from \"@squadrant/shared\";\n\n// 15s — cmux operations are local IPC (sub-50ms normally). 15s covers unusual\n// system load or a momentarily stuck cmux server without causing the captain\n// blindness that an unbounded hang would (see #209).\nexport const CMUX_TIMEOUT = 15_000;\n\nexport class CmuxTimeoutError extends Error {\n constructor(cmd: string) {\n super(`cmux timeout after ${CMUX_TIMEOUT}ms on: ${cmd}`);\n this.name = \"CmuxTimeoutError\";\n }\n}\n\nimport { DeferDelivery } from \"@squadrant/core\";\n\n/** True when running inside a cmux workspace (CMUX_WORKSPACE_ID is set). */\nexport function isInsideCmux(): boolean {\n return !!process.env.CMUX_WORKSPACE_ID;\n}\n\n// Synchronous cmux invocation for select-workspace / current-workspace calls\n// not yet abstracted behind RuntimeDriver. Uses execFileSync (no shell) with\n// stderr piped so cmux diagnostic messages (e.g. \"Pane not found\") don't leak\n// to the parent terminal. Returns trimmed stdout.\nexport function cmuxLocal(args: string[]): string {\n return execFileSync(resolveCmuxBin(), args, {\n encoding: \"utf-8\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n timeout: CMUX_TIMEOUT,\n }).trim();\n}\n\n// Invoke cmux with an argv array and NO shell. Every element (especially crew\n// prompt text passed through send/send-to-surface) reaches cmux as a single\n// literal argument — backticks, $(), quotes are never parsed. See #118.\n// Async to avoid blocking the Node.js event loop during daemon timer ticks.\nfunction cmux(args: string[]): Promise<string> {\n return new Promise((resolve, reject) => {\n execFile(\n resolveCmuxBin(),\n args,\n // CMUX_QUIET=1 silences cmux 0.64's one-time deprecation hints (e.g. the\n // \"list-workspaces is now an alias for cmux workspace list\" notice). Those\n // notices print to the command's stdout and would otherwise pollute the\n // output we parse. Inherit the rest of the environment unchanged.\n { encoding: \"utf-8\", timeout: CMUX_TIMEOUT, env: { ...process.env, CMUX_QUIET: \"1\" } },\n (err, stdout) => {\n if (err) {\n reject((err as NodeJS.ErrnoException).code === \"ETIMEDOUT\"\n ? new CmuxTimeoutError(args.join(\" \"))\n : err);\n return;\n }\n resolve((stdout as string).trim());\n },\n );\n });\n}\n\n// Same as cmux() but writes `input` to the child's stdin before it exits —\n// used by showPatch (#604) for cmux's stdin-based diff mode (`cmux diff -`).\n// execFile's callback form still returns the underlying ChildProcess\n// synchronously, so its stdin is available immediately.\nfunction cmuxStdin(args: string[], input: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = execFile(\n resolveCmuxBin(),\n args,\n { encoding: \"utf-8\", timeout: CMUX_TIMEOUT, env: { ...process.env, CMUX_QUIET: \"1\" } },\n (err, stdout) => {\n if (err) {\n reject((err as NodeJS.ErrnoException).code === \"ETIMEDOUT\"\n ? new CmuxTimeoutError(args.join(\" \"))\n : err);\n return;\n }\n resolve((stdout as string).trim());\n },\n );\n child.stdin!.end(input);\n });\n}\n\n// Shape of `cmux workspace list --json` (cmux 0.64.16). Only the fields we\n// consume are typed; everything else in the payload is ignored.\ninterface CmuxWorkspaceListJson {\n workspaces?: Array<{\n ref?: string;\n custom_title?: string | null;\n has_custom_title?: boolean;\n current_directory?: string | null;\n }>;\n}\n\n// Shape of `cmux tree --json` (cmux 0.64.16). Surfaces nest as\n// windows[].workspaces[].panes[].surfaces[]; only consumed fields are typed.\ninterface CmuxTreeJson {\n windows?: Array<{\n workspaces?: Array<{\n ref?: string;\n panes?: Array<{\n surfaces?: Array<{ ref?: string; surface_ref?: string; title?: string | null }>;\n }>;\n }>;\n }>;\n}\n\n// Parse `cmux workspace list --json` into WorkspaceRefs. Replaces the old\n// regex over the human-readable `list-workspaces` text (audit B2). The display\n// name is the workspace's custom title when set (byte-identical to what the\n// text form showed, e.g. \"⚓ squadrant-captain\" — this is what squadrant matches\n// captains by), falling back to the cwd for untitled workspaces.\nfunction parseList(output: string): WorkspaceRef[] {\n let parsed: CmuxWorkspaceListJson;\n try {\n parsed = JSON.parse(output) as CmuxWorkspaceListJson;\n } catch {\n return [];\n }\n const refs: WorkspaceRef[] = [];\n for (const ws of parsed.workspaces ?? []) {\n if (!ws.ref) continue;\n refs.push({\n id: ws.ref,\n name: (ws.has_custom_title && ws.custom_title) ? ws.custom_title : (ws.current_directory ?? ws.ref),\n status: \"running\",\n });\n }\n return refs;\n}\n\n// cmux `send` treats \\n, \\r (and \\t) as Enter/Tab keystrokes, so any newline in a\n// multi-line message would submit it line-by-line. Collapse all newline/CR/tab\n// (real bytes AND literal backslash-escapes) to single spaces so the whole message\n// is delivered as one line, then the explicit send-key Enter submits it once.\nexport function sanitizeForCmuxSend(text: string): string {\n return text\n .replace(/\\\\[nrt]/g, \" \")\n .replace(/[\\n\\r\\t]+/g, \" \")\n .replace(/ {2,}/g, \" \")\n .trim();\n}\n\n/**\n * Extract the in-progress draft from a cmux read-screen capture (#258 / #268).\n * Scans from the bottom of the screen so history lines that contain `> ` are\n * ignored; only the actual input area (the last matching line) is returned.\n * Handles both `>` (synthetic/test) and `❯` (U+276F, the real Claude Code\n * prompt character) as the input caret. The real prompt is followed by a\n * non-breaking space (U+00A0); JS `\\s` covers it, so `\\s+` matches either.\n * Also handles box-drawing `│ ❯ text │` variants.\n *\n * Three-state return (#268):\n * \"draft text\" — input box found with content → caller must DEFER\n * \"\" — input box positively confirmed empty → caller may DELIVER\n * null — HR boundaries not found (overlay/menu/scrolled) → caller must DEFER\n */\nexport function parseDraftFromScreen(screen: string): string | null {\n // Empty screen means the input box is definitely not visible — defer (#268).\n if (!screen) return null;\n const lines = screen.split(/\\r?\\n/);\n\n // Locate the last two HR lines (runs of U+2500 ─) — they are the bottom and top\n // boundaries of the live input box. Everything above the top HR is transcript\n // content and is never scanned, preventing sent user messages with a ❯/> prefix\n // from being mistaken for the live draft (#258).\n const HR_RE = /^\\s*─{10,}\\s*$/;\n let bottomHR = -1;\n let topHR = -1;\n for (let i = lines.length - 1; i >= 0; i--) {\n if (HR_RE.test(lines[i])) {\n if (bottomHR === -1) {\n bottomHR = i;\n } else {\n topHR = i;\n break;\n }\n }\n }\n\n // Can't locate both boundaries — input box not visible (overlay/menu/scrolled\n // transcript). Defer so keystrokes never land in an unknown UI state (#268).\n if (topHR === -1) return null;\n\n // Extract content lines strictly between the two HRs (the live input box only).\n const inputLines = lines.slice(topHR + 1, bottomHR);\n\n for (const line of inputLines) {\n let extracted: string | undefined;\n // Box-drawing input line: │ [>❯] text │\n const boxMatch = line.match(/│\\s*[>❯]\\s+(.*?)\\s*│/);\n if (boxMatch) {\n extracted = boxMatch[1].trim();\n } else {\n // Plain input line — allow empty content after the prompt glyph\n const plainMatch = line.match(/^\\s*[>❯]\\s*(.*)$/);\n if (plainMatch) extracted = plainMatch[1].trim();\n }\n if (extracted !== undefined) {\n // Heuristic #1 — Leading cursor glyph (▌/█) at position 0.\n // CC renders its input cursor via native ANSI terminal positioning, NOT as a ▌ cell\n // character: a live cmux read-screen of an idle CC session with cursor at position 0\n // yields ❯\\xa0 with no ▌ (confirmed by 258-parse-bug-fixture.txt L24 and a fresh\n // crew session capture). Therefore ▌ at the start cannot arise from the user moving\n // the cursor to the beginning of real typed text — it only appears when CC itself\n // renders a UI placeholder at that position (#294). Safe to treat as empty. (#297)\n if (/^[▌█▔▎▏▌█]/.test(extracted)) continue;\n\n // Strip terminal cursor glyphs (▌, █, etc.) that trail the caret position\n const draft = extracted.replace(/\\s*[▌█▔▎▏▌█]+\\s*$/, \"\").trim();\n\n // Claude Code UI placeholder: appears in Working state when input is locked\n // (user cannot type). \"Press [key] to [action]\" strings are UI instructions\n // shown as ghost suggestions — never real user-typed content (#294).\n if (/^Press\\s+(?:up|down|left|right|enter|escape|esc|tab|any\\s+key|ctrl|shift|alt)\\s+to\\s+/i.test(draft)) continue;\n\n if (draft) return draft;\n }\n }\n\n return \"\";\n}\n\n/**\n * True when the screen contains a real Claude Code input box — two HR boundaries\n * AND at least one line between them with the CC prompt glyph (❯ or >). This\n * distinguishes the CC input box from the claude-mem startup banner, which can\n * produce HR-bounded regions WITHOUT a prompt glyph and stabilise before CC\n * renders its own TUI. parseDraftFromScreen returns \"\" for both cases (two HRs\n * found, no ❯ inside), so !==null does not distinguish them (#466-single fix).\n */\nexport function hasCCInputBox(screen: string): boolean {\n if (!screen) return false;\n const lines = screen.split(/\\r?\\n/);\n const HR_RE = /^\\s*─{10,}\\s*$/;\n let bottomHR = -1;\n let topHR = -1;\n for (let i = lines.length - 1; i >= 0; i--) {\n if (HR_RE.test(lines[i])) {\n if (bottomHR === -1) bottomHR = i;\n else { topHR = i; break; }\n }\n }\n if (topHR === -1) return false;\n return lines.slice(topHR + 1, bottomHR).some((l) => /[>❯]/.test(l));\n}\n\n/**\n * True when the HR-bounded region is an AskUserQuestion / permission-approval\n * SELECTION MODAL rather than the genuine CC input box (#484). Both draw their\n * own pair of ── borders and highlight the selected option with the same ❯\n * glyph as a real draft, so neither parseDraftFromScreen nor hasCCInputBox can\n * tell them apart — a live-captured frame confirms parseDraftFromScreen\n * returns the highlighted option's own label (\"1. Red\"), not \"\" or null (see\n * docs/reports/484-askuserquestion-fixture.txt). CC renders every selectable\n * option (AskUserQuestion AND the Bash-approval picker) as a \"N. Label\" line,\n * which a real typed draft or ghost/hint placeholder never does — that's the\n * positive signal used here.\n */\nexport function hasModalOptionList(screen: string): boolean {\n if (!screen) return false;\n const lines = screen.split(/\\r?\\n/);\n const HR_RE = /^\\s*─{10,}\\s*$/;\n let bottomHR = -1;\n let topHR = -1;\n for (let i = lines.length - 1; i >= 0; i--) {\n if (HR_RE.test(lines[i])) {\n if (bottomHR === -1) bottomHR = i;\n else { topHR = i; break; }\n }\n }\n if (topHR === -1) return false;\n return lines.slice(topHR + 1, bottomHR).some((l) => /^\\s*\\d+\\.\\s/.test(l));\n}\n\n/**\n * Extract the RAW input-box content for the #302 buffer-liveness probe — all\n * content lines between the last two HRs, joined, with the prompt glyph and any\n * trailing cursor glyph stripped (but NOT the #294 ghost heuristics: the probe\n * needs the literal rendered text to diff before/after a backspace). Returns\n * null if the box boundaries aren't visible (overlay/scroll). Unlike\n * parseDraftFromScreen this captures EVERY content line, so a multi-line draft's\n * change on its last line is not missed.\n */\nexport function readInputBoxRaw(\n screen: string,\n opts?: { trim?: boolean },\n): string | null {\n if (!screen) return null;\n const lines = screen.split(/\\r?\\n/);\n const HR_RE = /^\\s*─{10,}\\s*$/;\n let bottomHR = -1;\n let topHR = -1;\n for (let i = lines.length - 1; i >= 0; i--) {\n if (HR_RE.test(lines[i])) {\n if (bottomHR === -1) bottomHR = i;\n else { topHR = i; break; }\n }\n }\n if (topHR === -1) return null;\n const parts: string[] = [];\n for (const line of lines.slice(topHR + 1, bottomHR)) {\n let s = line.replace(/│/g, \" \"); // drop box-drawing borders\n s = s.replace(/^\\s*[>❯]\\s?/, \"\"); // drop the leading prompt glyph + one space\n s = s.replace(/\\s*[▌█▔▎▏]+\\s*$/, \"\"); // drop a trailing cursor glyph\n parts.push(s);\n }\n const joined = parts.join(\"\");\n // Default: trim trailing whitespace. Pass { trim: false } to preserve it —\n // used by the probe branch to detect whether a backspace was a no-op (#258).\n return opts?.trim === false ? joined : joined.replace(/\\s+$/, \"\");\n}\n\n// #292: Claude Code renders a persistent bottom status block once its TUI is past\n// the cold-init splash — the auto-mode indicator (⏵⏵), the context meter\n// (\"Ctx Used\"), the shortcuts hint, or the accept-edits toggle. Absence of all of\n// these means we're still on the loading/splash screen, where keystrokes are\n// silently dropped (#235). Grounded in docs/reports/258-parse-bug-fixture.txt.\nconst CC_INITIALIZED_RE = /⏵⏵|Ctx Used|for shortcuts|accept edits/i;\n\n// A live turn shows a working spinner. The whimsical verb (\"Working…\",\n// \"Cerebrating…\", \"Crunched…\") varies across versions, so we key on stable\n// markers instead. CRUCIAL: a turn is NOT always streaming tokens — during a\n// tool wait (e.g. the shell commands the captain startup checklist runs first)\n// the spinner reads \"✻ Crunched for 27s · 1 shell still running\", which carries\n// NO token-down-counter and no \"esc to interrupt\". Keying only on those two\n// (the original #292 mistake) misread a shell-waiting captain as \"idle\", so the\n// startup-prompt loop re-sent on every poll → 3 duplicate startup runs. We now\n// also match the shell-running hint and the in-parens elapsed timer (\"(4s\",\n// \"(1m 4s\") — both confined to the live spinner line, never on an idle,\n// input-ready screen. Grounded in docs/reports/258-parse-bug-fixture.txt\n// (line 4: shell-wait, no counter; line 22: token-stream).\nconst CC_WORKING_RE = /↓\\s*[\\d.]+\\s*k?\\s*tokens?\\b|esc to interrupt|\\bshell still running\\b|·\\s*\\d+\\s*shell\\b|\\(\\d+m?\\s*\\d*s\\b/i;\n\n/**\n * Classify a captain surface's read-screen into the three states #292's\n * deterministic startup delivery needs:\n * \"loading\" — splash / cold-init; keystrokes would be dropped, do not send yet.\n * \"idle\" — TUI up and accepting input; safe to deliver the startup prompt.\n * \"working\" — a turn is in flight; sending would queue a DUPLICATE startup run.\n * \"working\" is checked first so an active spinner above an (empty) input box wins.\n */\nexport function classifyStartupSurface(screen: string): \"loading\" | \"idle\" | \"working\" {\n if (CC_WORKING_RE.test(screen)) return \"working\";\n if (CC_INITIALIZED_RE.test(screen)) return \"idle\";\n return \"loading\";\n}\n\n// #339 instrumentation gate. The DONE→captain submit is a text burst then a\n// SEPARATE send-key Enter (two distinct socket writes); intermittently the Enter\n// lands as a newline instead of a submit, stranding the payload in the input box.\n// Root-causing needs ONE real frame in the wild. Gated behind SQUADRANT_DEBUG_SEND\n// so it is a strict no-op — zero extra reads, zero latency — when unset.\nexport function sendDebugEnabled(): boolean {\n return !!process.env.SQUADRANT_DEBUG_SEND;\n}\n\n// Classify a post-send input-box read into a submit verdict for #339:\n// \"submitted\" — box empty after Enter (the payload left the input box)\n// \"stuck\" — box still holds the payload (Enter inserted a newline, no submit)\n// \"box-gone\" — box not visible post-send (overlay/scroll — inconclusive)\n// \"unknown\" — box has unrelated content (a fresh draft / next turn rendered)\nexport function classifySendOutcome(payload: string, postBox: string | null): string {\n if (postBox === null) return \"box-gone\";\n if (postBox === \"\") return \"submitted\";\n if (postBox === payload || postBox.includes(payload)) return \"stuck\";\n return \"unknown\";\n}\n\nexport type DraftLiveness = \"real-draft\" | \"no-draft\" | \"inconclusive\";\n\n/**\n * Pure probe-liveness decision (#258 fix).\n * Given `before` / `after` raw box readings (from readInputBoxRaw) around a\n * single backspace, classify whether a real user draft was present.\n *\n * Three-way result — caller mapping:\n * \"real-draft\" → restore last grapheme, throw DeferDelivery\n * \"no-draft\" → deliver (ghost positively confirmed dismissed to empty)\n * \"inconclusive\" → throw DeferDelivery (bias: protect human, delay bot)\n *\n * \"Inconclusive\" covers: after===before (ghost-invariant OR trailing-space\n * trim makes them equal — indistinguishable), null after-read (timing/overlay),\n * and any other mismatch not explained by grapheme removal. The old code treated\n * all of these as \"no-draft\" (fall-through to deliver), which is the #258 clobber.\n */\nexport function classifyDraftLiveness(\n before: string | null,\n after: string | null,\n): DraftLiveness {\n if (before === null || after === null) return \"inconclusive\";\n\n // Ghost dismissed to empty → positively confirmed no real draft remains.\n if (after === \"\") return \"no-draft\";\n\n // Grapheme-aware last-grapheme-removal check (Node 16+ / Intl.Segmenter).\n // Removes the last grapheme cluster from `before`, trims trailing whitespace\n // (matching what readInputBoxRaw does on the re-rendered screen), then\n // compares to `after`. Handles emoji, wide chars, and combining sequences\n // that slice(0,-1) gets wrong by removing only one UTF-16 code unit.\n if (before.length > 0) {\n const segs = [...new Intl.Segmenter().segment(before)];\n const expected = segs\n .slice(0, -1)\n .map((s) => s.segment)\n .join(\"\")\n .replace(/\\s+$/, \"\");\n if (after === expected) return \"real-draft\";\n }\n\n // Everything else — after===before (ghost-invariant or trailing-space trim),\n // arbitrary mismatch, or other ambiguity — is inconclusive. Defer to protect\n // the human; a correctly empty box always produces after===\"\" (caught above).\n return \"inconclusive\";\n}\n\nexport function createCmuxDriver(): RuntimeDriver {\n return {\n name: \"cmux\",\n\n async probe(): Promise<RuntimeProbeResult> {\n try {\n const version = await cmux([\"--version\"]);\n const warn = checkToolCompat(\"cmux\", version, compatManifest.tools.cmux);\n if (warn) process.stderr.write(`[squadrant] ${warn}\\n`);\n return { installed: true, version };\n } catch {\n return { installed: false, version: \"\" };\n }\n },\n\n async list(): Promise<WorkspaceRef[]> {\n try {\n // --json: structured output (B2); --id-format refs: ids as\n // workspace:N refs, not numeric (from #325). Both are required.\n return parseList(await cmux([\"workspace\", \"list\", \"--json\", \"--id-format\", \"refs\"]));\n } catch {\n return [];\n }\n },\n\n async status(nameOrId: string): Promise<WorkspaceRef | null> {\n const refs = await this.list();\n const hit = refs.find((r) => r.name === nameOrId || r.id === nameOrId);\n return hit ?? null;\n },\n\n async spawn(opts: RuntimeSpawnOptions): Promise<WorkspaceRef> {\n const newWorkspaceArgs = [\"workspace\", \"create\", \"--command\", opts.command];\n if (opts.workdir) newWorkspaceArgs.push(\"--cwd\", opts.workdir);\n const output = await cmux(newWorkspaceArgs);\n const id = output.match(/workspace:\\d+/)?.[0] || output.split(/\\s+/).pop() || \"\";\n if (!id) {\n throw new Error(`cmux spawn did not return a workspace id: ${output}`);\n }\n await cmux([\"workspace\", \"rename\", id, \"--title\", opts.name]);\n // Rename the initial tab to the workspace name so send() can route to it\n let initialSurface: string | undefined;\n try {\n const tree = await cmux([\"tree\", \"--workspace\", id, \"--id-format\", \"refs\"]);\n const m = tree.match(/surface\\s+(surface:\\d+)\\s+\\[\\w+\\]\\s+\"([^\"]*)\"/);\n if (m) {\n initialSurface = m[1];\n await cmux([\"rename-tab\", \"--workspace\", id, \"--surface\", m[1], opts.name]);\n }\n } catch { /* rename is best-effort */ }\n if (opts.pinToTop) {\n try {\n await cmux([\"workspace-action\", \"--workspace\", id, \"--action\", \"pin\"]);\n } catch { /* workspace may not be pinned — proceed to close regardless */ }\n if (initialSurface) {\n try {\n await cmux([\"tab-action\", \"--workspace\", id, \"--surface\", initialSurface, \"--action\", \"pin\"]);\n } catch { /* tab pin is best-effort */ }\n }\n }\n return { id, name: opts.name, status: \"running\" };\n },\n\n async send(ref: string, message: string): Promise<void> {\n // Route to the tab named after the workspace (e.g. \":captain\" tab) so\n // messages don't land on a focused crew tab by mistake. Fall back to\n // workspace-level send when no matching tab is found.\n const allRefs = await this.list();\n const ws = allRefs.find((r) => r.id === ref);\n if (ws) {\n try {\n const surfaces = await this.listSurfaces(ws.id);\n const target = surfaces.find((s) => s.title === ws.name);\n if (target) {\n await cmux([\"send\", \"--workspace\", ws.id, \"--surface\", target.surfaceId, sanitizeForCmuxSend(message)]);\n await cmux([\"send-key\", \"--workspace\", ws.id, \"--surface\", target.surfaceId, \"Enter\"]);\n return;\n }\n } catch { /* fall through to default */ }\n }\n await cmux([\"send\", \"--workspace\", ref, sanitizeForCmuxSend(message)]);\n await cmux([\"send-key\", \"--workspace\", ref, \"Enter\"]);\n },\n\n async sendKey(ref: string, key: string): Promise<void> {\n await cmux([\"send-key\", \"--workspace\", ref, key]);\n },\n\n async readScreen(ref: string): Promise<string> {\n try {\n return await cmux([\"read-screen\", \"--workspace\", ref]);\n } catch {\n return \"\";\n }\n },\n\n async stop(ref: string): Promise<void> {\n // cmux 0.64.16 refuses to close a pinned workspace. Unpin first so that\n // squadrant launch --fresh works even when the captain workspace is pinned.\n try {\n await cmux([\"workspace-action\", \"--workspace\", ref, \"--action\", \"unpin\"]);\n } catch { /* workspace may not be pinned — proceed to close regardless */ }\n try {\n await cmux([\"workspace\", \"close\", ref]);\n } catch { /* may already be closed */ }\n },\n\n async newPane(opts: RuntimePaneOptions): Promise<PaneRef> {\n // #295 / audit A1+B3: a crew tab must never steal focus from the captain.\n // cmux 0.64.16's new-surface and new-pane both DEFAULT to --focus false,\n // so we pass it explicitly (intent + resilience if the default changes)\n // and create the surface focus-neutrally. This REPLACES the old\n // snapshot-then-move-surface refocus dance, which depended on the fragile\n // \"tree order == array index\" invariant that the 0.64 freeform canvas +\n // staggered restore broke — risking a focus-steal regression.\n const cmd = opts.direction === \"tab\"\n ? [\"new-surface\", \"--type\", \"terminal\", \"--workspace\", opts.workspaceId, \"--focus\", \"false\"]\n : [\"new-pane\", \"--type\", \"terminal\", \"--direction\", opts.direction, \"--workspace\", opts.workspaceId, \"--focus\", \"false\"];\n const output = await cmux(cmd);\n const surfaceId = output.match(/surface:\\d+/)?.[0];\n if (!surfaceId) {\n const verb = opts.direction === \"tab\" ? \"new-surface\" : \"new-pane\";\n throw new Error(`cmux ${verb} did not return a surface id: ${output}`);\n }\n if (opts.title) {\n try {\n await cmux([\"rename-tab\", \"--workspace\", opts.workspaceId, \"--surface\", surfaceId, \"--title\", opts.title]);\n } catch { /* rename is best-effort */ }\n }\n return { workspaceId: opts.workspaceId, surfaceId };\n },\n\n async closePane(pane: PaneRef): Promise<void> {\n try {\n await cmux([\"close-surface\", \"--workspace\", pane.workspaceId, \"--surface\", pane.surfaceId]);\n } catch { /* may already be closed */ }\n },\n\n async sendToPane(pane: PaneRef, message: string): Promise<void> {\n await this.pasteToPane(pane, message);\n await this.sendKeyToPane(pane, \"Enter\");\n },\n\n async pasteToPane(pane: PaneRef, text: string): Promise<void> {\n await cmux([\"send\", \"--workspace\", pane.workspaceId, \"--surface\", pane.surfaceId, sanitizeForCmuxSend(text)]);\n },\n\n async sendKeyToPane(pane: PaneRef, key: string): Promise<void> {\n await cmux([\"send-key\", \"--workspace\", pane.workspaceId, \"--surface\", pane.surfaceId, key]);\n },\n\n async readPaneScreen(pane: PaneRef): Promise<string> {\n try {\n return await cmux([\"read-screen\", \"--workspace\", pane.workspaceId, \"--surface\", pane.surfaceId]);\n } catch {\n return \"\";\n }\n },\n\n async spawnInjector(opts: {\n captainWorkspace: WorkspaceRef;\n command: string;\n title?: string;\n placement: \"background\" | \"visible\";\n }): Promise<PaneRef> {\n // Both placements use a background tab (new-surface) in the captain's\n // existing pane — full-height, NO split. A split-pane is wrong here:\n // cmux 0.62.2 has no resize/hide verb, so a `new-pane` split can never be\n // shrunk and stays an ugly full-height 50/50 split forever (#117). The\n // relay still runs as a cmux descendant in the same workspace, preserving\n // the in-cmux delivery requirement (#112).\n //\n // cmux 0.64.16's new-surface DEFAULTS to --focus false, so \"background\"\n // passes --focus false and the relay tab is created without ever stealing\n // focus from the captain — no snapshot-then-move-surface refocus dance\n // (audit A1+B3; the 0.64 freeform canvas broke the old tree-order==index\n // assumption it relied on). \"visible\" passes --focus true to leave the\n // debug tab focused for ergonomics.\n const wsId = opts.captainWorkspace.id;\n const focus = opts.placement === \"visible\" ? \"true\" : \"false\";\n const output = await cmux([\"new-surface\", \"--type\", \"terminal\", \"--workspace\", wsId, \"--focus\", focus]);\n const surfaceId = output.match(/surface:\\d+/)?.[0];\n if (!surfaceId) {\n throw new Error(`cmux spawnInjector did not return a surface id: ${output}`);\n }\n if (opts.title) {\n try {\n await cmux([\"rename-tab\", \"--workspace\", wsId, \"--surface\", surfaceId, \"--title\", opts.title]);\n } catch { /* rename is best-effort */ }\n }\n await cmux([\"send\", \"--workspace\", wsId, \"--surface\", surfaceId, opts.command]);\n await cmux([\"send-key\", \"--workspace\", wsId, \"--surface\", surfaceId, \"Enter\"]);\n return { workspaceId: wsId, surfaceId, title: opts.title };\n },\n\n async sendToSurface(surface: PaneRef, text: string, opts?: { probe?: boolean }): Promise<void> {\n const ws = surface.workspaceId;\n const sf = surface.surfaceId;\n const deliver = async () => {\n // #339 debug-gated instrumentation. When OFF this is the exact two-write\n // submit it always was (no extra reads, no latency). When ON we capture\n // one real frame: the input box BEFORE the send, the payload, and the box\n // AFTER the Enter — so a stranded submit can be told apart from a clean one.\n const dbg = sendDebugEnabled();\n let preBox: string | null = null;\n if (dbg) {\n try {\n preBox = readInputBoxRaw(await cmux([\"read-screen\", \"--workspace\", ws, \"--surface\", sf]));\n } catch { /* unreadable — leave preBox null, logged as such */ }\n }\n const payload = sanitizeForCmuxSend(text);\n await cmux([\"send\", \"--workspace\", ws, \"--surface\", sf, payload]);\n await cmux([\"send-key\", \"--workspace\", ws, \"--surface\", sf, \"Enter\"]);\n // Post-send read-back is READ-ONLY — never a re-send — so it can NEVER\n // double-submit (the #339 constraint). It only observes whether the box\n // still holds the payload (Enter mis-landed) or is empty (submit took).\n if (dbg) {\n let postBox: string | null = null;\n try {\n postBox = readInputBoxRaw(await cmux([\"read-screen\", \"--workspace\", ws, \"--surface\", sf]));\n } catch { /* unreadable — leave postBox null, classified box-gone */ }\n const verdict = classifySendOutcome(payload, postBox);\n process.stderr.write(`[squadrant] send-debug ${JSON.stringify({ surface: sf, verdict, payload, preBox, postBox })}\\n`);\n }\n };\n\n // #258/#268 Approach B: deliver only when the captain's input is positively\n // confirmed empty. null = box not visible (overlay/menu/scroll) → always defer.\n let screen = \"\";\n try {\n screen = await cmux([\"read-screen\", \"--workspace\", ws, \"--surface\", sf]);\n } catch { /* screen unreadable — parseDraftFromScreen(\"\") → null → defer below */ }\n const draft = parseDraftFromScreen(screen);\n\n // null = box not confirmed visible → never keystroke into an overlay (#268).\n if (draft === null) throw new DeferDelivery(null);\n\n // #484: an AskUserQuestion / permission-approval SELECTION MODAL — never\n // deliver into it, regardless of what parseDraftFromScreen returned or\n // whether this call is probe-escalated. Checked before the probe branch\n // below because the probe's backspace-no-op check can't tell \"ghost\n // placeholder\" apart from \"selection list\" (backspace is a no-op\n // against both) and would otherwise call deliver(), typing the message\n // and pressing Enter into the picker — auto-confirming whichever option\n // is highlighted.\n if (hasModalOptionList(screen)) throw new DeferDelivery(null);\n\n // Empty input — nothing to protect, deliver directly.\n if (draft === \"\") { await deliver(); return; }\n\n // A draft is present. On the hot path (no probe) we NEVER keystroke — we\n // defer and carry the content so the relay can track stability (#302).\n if (!opts?.probe) throw new DeferDelivery(draft);\n\n // #302 buffer-liveness probe. classifyDraftLiveness decides from the\n // before/after box readings whether a real draft is present (#258 fix).\n // Capture both trimmed (for classification) and untrimmed (for no-op\n // detection in the inconclusive branch) before sending the backspace.\n const before = readInputBoxRaw(screen);\n const rawBefore = readInputBoxRaw(screen, { trim: false });\n await cmux([\"send-key\", \"--workspace\", ws, \"--surface\", sf, \"backspace\"]);\n // 50ms settle: give the TUI time to re-render before reading back the\n // result. Without this, a too-fast read may still show the pre-backspace\n // content, producing a false after===before (timing-race #258).\n await new Promise<void>((r) => setTimeout(r, 50));\n let afterScreen = \"\";\n try {\n afterScreen = await cmux([\"read-screen\", \"--workspace\", ws, \"--surface\", sf]);\n } catch { /* unreadable — after stays \"\", readInputBoxRaw → null → inconclusive → defer */ }\n const after = readInputBoxRaw(afterScreen);\n const rawAfter = readInputBoxRaw(afterScreen, { trim: false });\n\n const liveness = classifyDraftLiveness(before, after);\n if (liveness === \"real-draft\") {\n // Confirmed real draft. Restore the last grapheme our probe removed\n // (grapheme-aware — not slice(-1) which breaks emoji, #258), then defer.\n const segs = before ? [...new Intl.Segmenter().segment(before)] : [];\n const lastGrapheme =\n segs.length > 0 ? segs[segs.length - 1].segment : before!.slice(-1);\n await cmux([\"send\", \"--workspace\", ws, \"--surface\", sf, lastGrapheme]);\n throw new DeferDelivery(draft);\n }\n if (liveness === \"no-draft\") {\n // Ghost positively dismissed to empty — safe to deliver.\n await deliver(); return;\n }\n // 'inconclusive': could be ghost-invariant (true no-op) or trailing-space\n // draft (backspace removed the space but trim masked it). Distinguish by\n // comparing the UNTRIMMED raw content.\n if (rawBefore !== null && rawAfter !== null) {\n if (rawBefore !== rawAfter) {\n // Raw changed: real trailing-space (or similar) draft — backspace consumed\n // a real character. Restore the removed grapheme then defer (#258).\n const segs = [...new Intl.Segmenter().segment(rawBefore)];\n const lastGrapheme =\n segs.length > 0 ? segs[segs.length - 1].segment : rawBefore.slice(-1);\n await cmux([\"send\", \"--workspace\", ws, \"--surface\", sf, lastGrapheme]);\n throw new DeferDelivery(draft);\n }\n // rawBefore === rawAfter: backspace was a true no-op — the box holds ghost/hint\n // text (non-editable). A real draft ALWAYS changes under backspace. Deliver.\n await deliver(); return;\n }\n // Null raw reads: can't distinguish ghost from draft → defer (bias: protect human).\n throw new DeferDelivery(draft);\n },\n\n async showDiff(opts: {\n workspaceId: string;\n cwd: string;\n base: string;\n title?: string;\n layout?: \"split\" | \"unified\";\n focus?: boolean;\n lastTurn?: boolean;\n source?: \"branch\" | \"staged\" | \"unstaged\";\n }): Promise<void> {\n const source = opts.source ?? \"branch\";\n const args = [\"diff\"];\n if (source === \"staged\") {\n args.push(\"--staged\");\n } else if (source === \"unstaged\") {\n args.push(\"--unstaged\");\n } else {\n args.push(\"--branch\", \"--base\", opts.base);\n // --last-turn refines the branch-vs-base surface (#596); it has no\n // meaning against the staged/unstaged working-tree sources.\n if (opts.lastTurn) args.push(\"--last-turn\");\n }\n args.push(\"--cwd\", opts.cwd, \"--workspace\", opts.workspaceId, \"--layout\", opts.layout ?? \"split\");\n if (opts.title) args.push(\"--title\", opts.title);\n // cmux's diff subcommand defines --focus <true|false> (value required);\n // only --no-focus is bare.\n if (opts.focus === false) args.push(\"--no-focus\");\n else args.push(\"--focus\", \"true\");\n await cmux(args);\n },\n\n async showPatch(opts: {\n workspaceId: string;\n patch: string;\n title?: string;\n layout?: \"split\" | \"unified\";\n focus?: boolean;\n }): Promise<void> {\n const args = [\"diff\", \"-\", \"--workspace\", opts.workspaceId, \"--layout\", opts.layout ?? \"split\"];\n if (opts.title) args.push(\"--title\", opts.title);\n // Same --focus <true|false> contract as showDiff (#603): only --no-focus is bare.\n if (opts.focus === false) args.push(\"--no-focus\");\n else args.push(\"--focus\", \"true\");\n await cmuxStdin(args, opts.patch);\n },\n\n async listSurfaces(workspaceId: string): Promise<PaneRef[]> {\n let output: string;\n try {\n // --json: structured output (B2); --id-format refs: surface ids as\n // surface:N refs, not numeric (from #325). Both are required.\n output = await cmux([\"tree\", \"--workspace\", workspaceId, \"--json\", \"--id-format\", \"refs\"]);\n } catch {\n return [];\n }\n let parsed: CmuxTreeJson;\n try {\n parsed = JSON.parse(output) as CmuxTreeJson;\n } catch {\n return [];\n }\n // Navigate windows[].workspaces[].panes[].surfaces[], collecting every\n // surface that belongs to the requested workspace. Replaces the old regex\n // over `cmux tree` text (audit B2). Surface refs are globally unique, so\n // filtering by the parent workspace ref is sufficient.\n const surfaces: PaneRef[] = [];\n for (const win of parsed.windows ?? []) {\n for (const ws of win.workspaces ?? []) {\n if (ws.ref !== workspaceId) continue;\n for (const pane of ws.panes ?? []) {\n for (const sf of pane.surfaces ?? []) {\n const ref = sf.ref ?? sf.surface_ref;\n if (ref) surfaces.push({ workspaceId, surfaceId: ref, title: sf.title ?? \"\" });\n }\n }\n }\n }\n return surfaces;\n },\n };\n}\n","import type { SquadrantConfig } from \"@squadrant/shared\";\nimport type { RuntimeDriver, RuntimeProbeResult } from \"./types.js\";\n\nconst DEFAULT_RUNTIME = \"cmux\";\n\nexport class RuntimeRegistry {\n constructor(private drivers: Record<string, RuntimeDriver>) {}\n\n forProject(projectName: string, config: SquadrantConfig): RuntimeDriver {\n const projectRuntime = config.projects[projectName]?.runtime;\n const runtimeName = projectRuntime ?? config.runtime ?? DEFAULT_RUNTIME;\n return this.get(runtimeName);\n }\n\n global(config: SquadrantConfig): RuntimeDriver {\n const runtimeName = config.runtime ?? DEFAULT_RUNTIME;\n return this.get(runtimeName);\n }\n\n get(name: string): RuntimeDriver {\n const driver = this.drivers[name];\n if (!driver) {\n throw new Error(`Unknown runtime '${name}' — no driver registered`);\n }\n return driver;\n }\n\n async probeAll(): Promise<Record<string, RuntimeProbeResult>> {\n const results: Record<string, RuntimeProbeResult> = {};\n for (const [name, driver] of Object.entries(this.drivers)) {\n results[name] = await driver.probe();\n }\n return results;\n }\n}\n","import { execFile as execFileCb, execSync } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport type {\n NotifierDriver,\n NotifierProbeResult,\n NotifierScope,\n} from \"./types.js\";\nimport { CMUX_TIMEOUT } from \"../runtimes/cmux.js\";\n\nconst execFile = promisify(execFileCb);\n\nexport function createCmuxNotifier(_scope: NotifierScope): NotifierDriver {\n return {\n name: \"cmux\",\n\n async probe(): Promise<NotifierProbeResult> {\n try {\n execSync(\"squadrant runtime status --command\", { encoding: \"utf-8\", stdio: \"pipe\" });\n return { installed: true, reachable: true };\n } catch (err) {\n const code = (err as { code?: string }).code;\n if (code === \"ENOENT\") {\n return { installed: false, reachable: false };\n }\n // Any non-ENOENT error: squadrant shim crashed, workspace down, or\n // config unreadable all collapse to \"installed but not reachable\".\n return { installed: true, reachable: false };\n }\n },\n\n async notify(message: string): Promise<void> {\n // execFile (async, NOT execFileSync) with an argv array and NO shell: the\n // message is one literal argv element, so backticks / $() in notification\n // text are never parsed by a shell (#120, same class as #118/#119). Async\n // is required, not stylistic — a caller running inside the daemon's own\n // event loop (the #579/#484 DELIVERY STUCK fault alert) would otherwise\n // block ALL projects' delivery/health/socket serving for up to\n // CMUX_TIMEOUT on every call.\n await execFile(\"squadrant\", [\"runtime\", \"send\", \"--command\", message], { encoding: \"utf-8\", timeout: CMUX_TIMEOUT });\n },\n };\n}\n","import type { SquadrantConfig } from \"@squadrant/shared\";\nimport type {\n NotifierDriver,\n NotifierFactory,\n NotifierProbeResult,\n} from \"./types.js\";\n\nconst DEFAULT_NOTIFIER = \"cmux\";\n\nexport class NotifierRegistry {\n constructor(private factories: Record<string, NotifierFactory>) {}\n\n get(config: SquadrantConfig): NotifierDriver {\n const name = config.notifier ?? DEFAULT_NOTIFIER;\n return this.getFactory(name)({});\n }\n\n getFactory(name: string): NotifierFactory {\n const factory = this.factories[name];\n if (!factory) {\n throw new Error(`Unknown notifier provider '${name}' — no factory registered`);\n }\n return factory;\n }\n\n async probeAll(): Promise<Record<string, NotifierProbeResult>> {\n const results: Record<string, NotifierProbeResult> = {};\n for (const [name, factory] of Object.entries(this.factories)) {\n try {\n results[name] = await factory({}).probe();\n } catch {\n results[name] = { installed: false, reachable: false };\n }\n }\n return results;\n }\n}\n","import fs from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport type {\n WorkspaceDriver,\n WorkspaceProbeResult,\n WorkspaceScope,\n} from \"@squadrant/shared\";\n\n// Rejects `../` escapes and absolute paths via lexical containment check.\n// Does NOT resolve symlinks — a symlink inside the vault pointing outside\n// will be followed by fs.* calls. Vault contents are trusted in the squadrant\n// threat model (user-owned, not untrusted input). Tracked in issue #25.\nfunction resolveInRoot(root: string, relative: string): string {\n const joined = path.resolve(root, relative);\n const normalized = path.resolve(root) + path.sep;\n if (joined !== path.resolve(root) && !joined.startsWith(normalized)) {\n throw new Error(`Path '${relative}' escapes workspace root`);\n }\n return joined;\n}\n\nexport function createObsidianDriver(scope: WorkspaceScope): WorkspaceDriver {\n const root = scope.root;\n if (typeof root !== \"string\" || root === \"\") {\n throw new Error(\"ObsidianDriver requires scope.root (string)\");\n }\n\n return {\n name: \"obsidian\",\n\n async probe(): Promise<WorkspaceProbeResult> {\n return {\n installed: true,\n rootExists: existsSync(root),\n };\n },\n\n async read(rel: string): Promise<string> {\n return fs.readFile(resolveInRoot(root, rel), \"utf-8\");\n },\n\n async write(rel: string, content: string): Promise<void> {\n const abs = resolveInRoot(root, rel);\n await fs.mkdir(path.dirname(abs), { recursive: true });\n await fs.writeFile(abs, content);\n },\n\n async exists(rel: string): Promise<boolean> {\n try {\n await fs.access(resolveInRoot(root, rel));\n return true;\n } catch {\n return false;\n }\n },\n\n async list(rel: string): Promise<string[]> {\n try {\n return await fs.readdir(resolveInRoot(root, rel));\n } catch {\n return [];\n }\n },\n\n async mkdir(rel: string): Promise<void> {\n await fs.mkdir(resolveInRoot(root, rel), { recursive: true });\n },\n };\n}\n","// src/control/cmux/events-bridge.ts\n// Daemon-side bridge from cmux's native event stream to squadrant ControlEvents\n// (audit item B1 — reduce fragile screen-scraping).\n//\n// Unlike the per-crew OpencodeSseBridge, `cmux events` is a SINGLE global stream\n// for the whole cmux app: one newline-delimited JSON frame per cmux event,\n// carrying every agent's hook events. So this bridge is ONE long-lived\n// subscription owned by the daemon. Each `agent` frame is correlated back to a\n// crew TaskRecord by cwd (each interactive crew runs in a unique worktree path)\n// and classified into a run-state (deriveRunState):\n// - `agent.hook.Stop` — the \"turn ended / crew idle\" signal the pane reader\n// infers by scraping — → `task.turn.completed`.\n// - `agent.hook.PreToolUse` / `UserPromptSubmit` (a turn is live) →\n// `task.progress` (B4/A3): a real-activity signal that refreshes the crew's\n// liveness clock so the watchdog does not false-stall a crew mid long,\n// screen-quiet tool call (#292).\n//\n// ADDITIVE & SAFE: this runs ALONGSIDE the existing relay-proxy/pane-reader path,\n// which stays as the fallback. Both emissions are liveness, NOT completion\n// (anti-#2576): terminal state still comes from the explicit `squadrant crew signal\n// done`. The state-machine reducer already absorbs duplicate/late\n// task.turn.completed and task.progress (a blocked crew stays blocked), so\n// feeding them from BOTH paths is harmless.\nimport type { ChildProcess } from \"node:child_process\";\nimport { spawn as nodeSpawn } from \"node:child_process\";\nimport { resolveCmuxBin } from \"@squadrant/shared\";\nimport type { ControlEvent } from \"@squadrant/shared\";\n\n/** Minimal subset of ChildProcess this bridge needs (injectable for tests). */\nexport interface CmuxEventsChild {\n stdout: NodeJS.ReadableStream | null;\n kill(signal?: NodeJS.Signals): boolean | void;\n on(event: \"exit\", cb: (code: number | null) => void): unknown;\n on(event: \"error\", cb: (err: Error) => void): unknown;\n}\n\n/** Per-surface agent run-state derived from the hook stream (B4/A3). */\nexport type RunState = \"working\" | \"idle\";\n\n/**\n * Pure. Classify an `agent.hook.*` event name into the crew's run-state, or\n * null for hooks that carry no run-state signal.\n *\n * PreToolUse / UserPromptSubmit → \"working\" (a turn is live)\n * Stop → \"idle\" (turn ended)\n * SubagentStop / anything else → null (subagent end ≠ turn end)\n *\n * `Stop` is the existing turn-end signal (→ task.turn.completed). The \"working\"\n * hooks are the B4/A3 addition: they let the daemon keep a crew's liveness clock\n * fresh while it is mid (possibly long, screen-quiet) tool call, so the watchdog\n * does not false-stall it (#292). Only `PreToolUse` is live-confirmed in cmux\n * 0.64.16; `UserPromptSubmit` is mapped opportunistically (harmless if absent).\n */\nexport function deriveRunState(eventName: string): RunState | null {\n switch (eventName) {\n case \"agent.hook.PreToolUse\":\n case \"agent.hook.UserPromptSubmit\":\n return \"working\";\n case \"agent.hook.Stop\":\n return \"idle\";\n default:\n return null;\n }\n}\n\n/** A correlated hook frame, passed to the caller's record resolver. */\nexport interface CmuxAgentHook {\n cwd?: string;\n /** The emitting agent kind (`payload._source`, e.g. \"claude\"). */\n source?: string;\n /** The agent session id (`payload.session_id`). */\n sessionId?: string;\n}\n\nexport interface CmuxEventsBridgeDeps {\n /** Ingress into the daemon's event pipeline (resolves project + handles). */\n emit: (ev: ControlEvent) => void;\n /**\n * Map an agent hook frame to its owning crew record, or undefined if none.\n * The daemon supplies this from the store (non-terminal interactive records\n * matched by cwd). Keeping it injected keeps the bridge pure and testable.\n */\n resolve: (hook: CmuxAgentHook) => { id: string } | undefined;\n /** Durable resume cursor passed to `cmux events --cursor-file`. */\n cursorFile: string;\n /** Injectable spawn for tests; defaults to spawning the real cmux binary. */\n spawnImpl?: (bin: string, args: string[]) => CmuxEventsChild;\n /** Injectable cmux binary path; defaults to resolveCmuxBin(). */\n cmuxBin?: string;\n /** Injectable backoff for tests; defaults to setTimeout. */\n sleep?: (ms: number) => Promise<void>;\n /** Backoff between respawn attempts after the child exits (ms, default 1000). */\n reconnectMs?: number;\n log?: (msg: string) => void;\n /** Test-only: stop after the first child exits (don't respawn). */\n stopAfterFirstRun?: boolean;\n}\n\n/**\n * One long-lived `cmux events` subscription for the whole daemon. The CLI's\n * `--reconnect` resumes the socket in-process; `--cursor-file` makes resume\n * durable across daemon (and child) restarts. If the child process itself dies,\n * we respawn with backoff so the consumer self-heals.\n */\nexport class CmuxEventsBridge {\n private child: CmuxEventsChild | null = null;\n private stopped = false;\n private buf = \"\";\n private deps: CmuxEventsBridgeDeps;\n\n constructor(deps: CmuxEventsBridgeDeps) {\n this.deps = deps;\n }\n\n /** Begin the subscription. Idempotent. */\n start(): void {\n if (this.child || this.stopped) return;\n void this.run();\n }\n\n /** Stop the subscription and kill the child (daemon shutdown). */\n stop(): void {\n this.stopped = true;\n const c = this.child;\n this.child = null;\n if (c) {\n try { c.kill(); } catch { /* already gone */ }\n }\n }\n\n private async run(): Promise<void> {\n const spawnImpl =\n this.deps.spawnImpl ??\n ((bin, args) => nodeSpawn(bin, args, { stdio: [\"ignore\", \"pipe\", \"ignore\"] }) as ChildProcess as unknown as CmuxEventsChild);\n const bin = this.deps.cmuxBin ?? resolveCmuxBin();\n const sleep = this.deps.sleep ?? ((ms: number) => new Promise((r) => setTimeout(r, ms)));\n const reconnectMs = this.deps.reconnectMs ?? 1000;\n const args = [\n \"events\",\n \"--reconnect\",\n \"--cursor-file\", this.deps.cursorFile,\n \"--category\", \"agent\",\n \"--no-heartbeat\",\n ];\n\n while (!this.stopped) {\n this.buf = \"\";\n let child: CmuxEventsChild;\n try {\n child = spawnImpl(bin, args);\n } catch (e) {\n this.deps.log?.(`cmux events spawn failed: ${(e as Error).message}`);\n if (this.deps.stopAfterFirstRun) return;\n await sleep(reconnectMs);\n continue;\n }\n this.child = child;\n await new Promise<void>((resolve) => {\n let settled = false;\n const done = () => { if (!settled) { settled = true; resolve(); } };\n child.stdout?.on(\"data\", (b: Buffer | string) => this.onData(b));\n child.stdout?.on(\"end\", done);\n child.on(\"exit\", done);\n child.on(\"error\", (err) => {\n this.deps.log?.(`cmux events child error: ${err.message}`);\n done();\n });\n });\n this.child = null;\n if (this.stopped || this.deps.stopAfterFirstRun) break;\n // Child died (cmux app restart, binary error): resume from the cursor.\n await sleep(reconnectMs);\n }\n }\n\n private onData(chunk: Buffer | string): void {\n this.buf += typeof chunk === \"string\" ? chunk : chunk.toString(\"utf-8\");\n let nl: number;\n while ((nl = this.buf.indexOf(\"\\n\")) >= 0) {\n const line = this.buf.slice(0, nl);\n this.buf = this.buf.slice(nl + 1);\n this.handleLine(line);\n }\n }\n\n private handleLine(rawLine: string): void {\n const line = rawLine.trim();\n if (!line || line[0] !== \"{\") return;\n let f:\n | {\n type?: string;\n category?: string;\n name?: string;\n source?: string;\n payload?: { _source?: string; session_id?: string; cwd?: string; phase?: string; tool_name?: string };\n }\n | undefined;\n try {\n f = JSON.parse(line);\n } catch {\n return; // partial/non-JSON keepalive or ack we don't parse\n }\n // Only agent hook events; ignore ack/heartbeat and other categories.\n if (f?.type !== \"event\" || f.category !== \"agent\") return;\n // Classify the hook into a run-state. `Stop` is the main-session turn-end;\n // PreToolUse/UserPromptSubmit mean a turn is live; SubagentStop and any\n // other hook carry no turn-level run-state and are ignored.\n const runState = f.name ? deriveRunState(f.name) : null;\n if (!runState) return;\n const p = f.payload ?? {};\n // Each hook fires a \"received\" then \"completed\" phase frame; act on the\n // settled one so we emit exactly once per hook.\n if (p.phase === \"received\") return;\n const rec = this.deps.resolve({\n cwd: p.cwd,\n source: p._source ?? f.source,\n sessionId: p.session_id,\n });\n if (!rec) return;\n if (runState === \"idle\") {\n // Turn-end / idle — the signal the pane reader infers by scraping.\n this.deps.emit({\n type: \"task.turn.completed\",\n id: rec.id,\n turnId: p.session_id ?? \"cmux\",\n });\n return;\n }\n // working: feed a real-activity signal into the liveness path. task.progress\n // refreshes lastHeartbeatAt (the clock evaluateStall keys off), so a crew\n // that is mid long tool-call but screen-quiet is NOT false-stalled (#292),\n // and a crew the scrape path wrongly idled resumes to 'working'. ADDITIVE:\n // the reducer absorbs this idempotently, and a blocked crew stays blocked.\n // #354: carry the tool name on PreToolUse so the reducer can open a\n // tool-in-flight window (pendingTool) — the discriminator the watchdog uses\n // to tell a hung tool call apart from a quiet thinking turn.\n this.deps.emit({ type: \"task.progress\", id: rec.id, note: f.name, tool: p.tool_name });\n }\n}\n","import { readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport type { RuntimeDriver, PaneRef } from \"../runtimes/types.js\";\nimport { DeferDelivery } from \"@squadrant/core\";\nimport { loadConfig } from \"@squadrant/shared\";\nimport type { RuntimeLivenessRecord } from \"@squadrant/shared\";\nimport { readLivenessSnapshot } from \"./store-fingerprint.js\";\n\n/**\n * #332: daemon-side cmux access. The daemon (a launchd process, NOT a cmux\n * descendant) can now drive cmux directly because the CLI auto-discovers its\n * canonical socket (~/.local/state/cmux/cmux.sock) from any process.\n *\n * Every method is FAIL-SOFT: a cmux/socket error degrades to a safe sentinel\n * ([] / null / no-op) so a transient failure NEVER false-reaps a live crew.\n * Exceptions: DeferDelivery, which `send` re-throws so the delivery loop can\n * defer-while-typing (#258/#302); and `liveness()`, which THROWS when it\n * cannot get a good read of the store (readdir failure, or every store file\n * unreadable/corrupt) instead of returning [] — a locked/mid-write store must\n * never look like \"read succeeded, zero captains\" (that would false-close\n * every known captain via runLivenessTick's markEnded path). runLivenessTick\n * already treats a thrown liveness() as \"leave the registry untouched\".\n *\n * This is the seam #333's LifecycleSource port sits beside.\n */\nexport class DaemonCmux {\n constructor(private readonly driver: RuntimeDriver) {}\n\n async send(surface: PaneRef, text: string, opts?: { probe?: boolean }): Promise<void> {\n try {\n await this.driver.sendToSurface(surface, text, opts);\n } catch (e) {\n if (e instanceof DeferDelivery) throw e;\n }\n }\n\n async listSurfaces(workspaceId: string): Promise<PaneRef[]> {\n try { return await this.driver.listSurfaces(workspaceId); }\n catch { return []; }\n }\n\n async readScreen(ref: string): Promise<string | null> {\n try { return await this.driver.readScreen(ref); }\n catch { return null; }\n }\n\n async readPaneScreen(pane: PaneRef): Promise<string | null> {\n try { return await this.driver.readPaneScreen(pane); }\n catch { return null; }\n }\n\n async findWorkspaceId(name: string): Promise<string | null> {\n try {\n const ref = await this.driver.status(name);\n return ref?.id ?? null;\n } catch {\n return null;\n }\n }\n\n async isAvailable(): Promise<boolean> {\n try { await this.driver.listSurfaces(\"\"); return true; }\n catch { return false; }\n }\n\n /**\n * Ground-truth liveness from cmux's own hook-sessions store (§5.4).\n * THROWS (does not return []) when the dir can't be listed, or every store\n * file failed to read/parse — see the class doc above.\n */\n async liveness(): Promise<RuntimeLivenessRecord[]> {\n const dir = process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join(homedir(), \".cmuxterm\");\n const projects = loadConfig().projects as Record<string, { path: string }>;\n let files: string[];\n try { files = readdirSync(dir).filter((f) => f.endsWith(\"-hook-sessions.json\") && !f.endsWith(\".lock\")); }\n catch (e) { throw new Error(`liveness: could not read cmux state dir ${dir}: ${(e as Error).message}`); }\n return readLivenessSnapshot(files, (f) => readFileSync(join(dir, f), \"utf-8\"), projects);\n }\n}\n","import { resolveHome } from \"@squadrant/shared\";\nimport type { RuntimeLivenessRecord, Role } from \"@squadrant/shared\";\n\ninterface RawSession {\n sessionId?: string; pid?: number | null; cwd?: string; isRestorable?: boolean;\n launchCommand?: { arguments?: string[]; workingDirectory?: string };\n}\n\n/** template basename → role (captain.claude.md → captain, crew.claude.md → crew, …). */\nfunction roleFromTemplate(args: string[] | undefined): Role | \"unknown\" {\n const i = args?.indexOf(\"--append-system-prompt-file\") ?? -1;\n const tmpl = i >= 0 && args ? (args[i + 1] ?? \"\").split(\"/\").pop() ?? \"\" : \"\";\n if (tmpl.startsWith(\"captain\")) return \"captain\";\n if (tmpl.startsWith(\"crew\")) return \"crew\";\n if (tmpl.startsWith(\"command\")) return \"command\";\n return \"unknown\"; // side.research.* etc. — not a captain\n}\n\nfunction projectFromCwd(cwd: string, projects: Record<string, { path: string }>): string | undefined {\n for (const [name, p] of Object.entries(projects)) {\n const projPath = resolveHome(p.path);\n if (cwd === projPath || cwd.startsWith(`${projPath}/`)) return name;\n }\n return undefined;\n}\n\n/**\n * Parse one store file's content. Throws on invalid JSON — a corrupt/mid-write\n * file is a failed read, NOT a valid file with zero sessions; callers (see\n * `readLivenessSnapshot`) must be able to tell the two apart so a locked file\n * never false-reads as \"no captains\".\n */\nexport function parseStoreRecords(\n fileContent: string,\n projects: Record<string, { path: string }>,\n): RuntimeLivenessRecord[] {\n let parsed: { sessions?: Record<string, RawSession> };\n try { parsed = JSON.parse(fileContent); }\n catch (e) { throw new Error(`parseStoreRecords: invalid JSON: ${(e as Error).message}`); }\n const out: RuntimeLivenessRecord[] = [];\n for (const s of Object.values(parsed.sessions ?? {})) {\n const cwd = s.cwd ?? s.launchCommand?.workingDirectory ?? \"\";\n const project = projectFromCwd(cwd, projects);\n if (!project || !s.sessionId) continue;\n out.push({\n role: roleFromTemplate(s.launchCommand?.arguments),\n project,\n pid: typeof s.pid === \"number\" ? s.pid : null,\n sessionId: s.sessionId,\n present: true,\n isRestorable: s.isRestorable,\n });\n }\n return out;\n}\n\n/**\n * Read+parse every given store file, tolerating individual bad files (locked\n * mid-write, corrupt) as long as at least one yields a good read. Only throws\n * when EVERY file failed — a locked/corrupt store must never look like \"read\n * succeeded, zero captains present\" (that would false-close every known\n * captain this tick). Genuinely zero files (none present) is a valid empty read.\n */\nexport function readLivenessSnapshot(\n files: string[],\n readFile: (filename: string) => string,\n projects: Record<string, { path: string }>,\n): RuntimeLivenessRecord[] {\n const out: RuntimeLivenessRecord[] = [];\n let successes = 0;\n for (const f of files) {\n try {\n out.push(...parseStoreRecords(readFile(f), projects));\n successes++;\n } catch { /* this file unreadable/corrupt — other files may still be good */ }\n }\n if (files.length > 0 && successes === 0) {\n throw new Error(`readLivenessSnapshot: all ${files.length} store file(s) unreadable/corrupt this tick`);\n }\n return out;\n}\n","// cmux-store-source.ts — LifecycleSource adapter for ~/.cmuxterm/*-hook-sessions.json\n//\n// Implements the backup LifecycleSource (D1: A-backup) from the #333 design.\n// Watches the cmux state directory, reads each agent's hook-sessions.json, and\n// feeds LifecycleSnapshots into the reduceLifecycle pipeline via deps.report().\n//\n// NOT wired into the live daemon path in Phase 1 (additive per D3/D7).\n//\n// CORRELATION CONSTRAINT (research §2.2): launchCommand in the store has no\n// environment vars — SQUADRANT_CREW_TASK_ID is not available. Correlate by:\n// 1. cwd (primary for interactive crews — match against TaskRecord.cwd)\n// 2. pid (passed in hint; daemon can try KERN_PROCARGS2 lookup later)\n// 3. sessionId (cmux UUID; may match TaskRecord.sessionId if crew populates it)\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { watch, readdirSync, readFileSync, existsSync } from \"node:fs\";\nimport type { LifecycleSource, LifecycleSourceDeps, LifecycleSnapshot, CorrelationHint, LifecycleState } from \"@squadrant/core\";\n\n// ── store file schema (version:1, live schema from research report §2.2) ─────\n\ninterface StoreSession {\n sessionId: string;\n agentLifecycle: string;\n pid: number;\n cwd: string;\n lastBody?: string;\n isRestorable?: boolean;\n updatedAt: number; // Unix float (seconds)\n}\n\ninterface StoreFile {\n sessions?: Record<string, StoreSession>;\n}\n\n// ── injectable deps ──────────────────────────────────────────────────────────\n\nexport interface CmuxStoreSourceOpts {\n /** Directory to watch. Defaults to CMUX_AGENT_HOOK_STATE_DIR or ~/.cmuxterm. */\n stateDir?: string;\n /** Debounce delay between a watch event and the next scan (ms). Default 50. */\n debounceMs?: number;\n /** Returns true if the given pid is alive. Default: process.kill(pid, 0). */\n isPidAlive?: (pid: number) => boolean;\n /**\n * Lists store files in the given directory.\n * Default: readdirSync filtered to *-hook-sessions.json.\n */\n listFiles?: (dir: string) => string[];\n /**\n * Reads a file's content, returns undefined on any read error.\n * Default: readFileSync.\n */\n readFile?: (path: string) => string | undefined;\n /**\n * Returns true if the given path exists (lock-file check).\n * Default: existsSync.\n */\n fileExists?: (path: string) => boolean;\n /**\n * Starts a directory watcher. Calls cb on relevant file changes.\n * Returns a stop function. Default: fs.watch.\n */\n watchDir?: (dir: string, cb: () => void) => () => void;\n /** Injectable setTimeout for debouncing. Default: global setTimeout. */\n scheduleTimer?: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;\n /** Injectable clearTimeout for debouncing. Default: global clearTimeout. */\n cancelTimer?: (id: ReturnType<typeof setTimeout>) => void;\n log?: (msg: string) => void;\n}\n\n// ── CmuxStoreSource ──────────────────────────────────────────────────────────\n\n/**\n * LifecycleSource that watches ~/.cmuxterm/*-hook-sessions.json.\n *\n * cmux writes the hook-sessions file on every lifecycle-changing hook event\n * (SessionStart, UserPromptSubmit, PreToolUse, Stop, Notification, AskUserQuestion).\n * Each session record carries `agentLifecycle` in the 4-state vocabulary that\n * exactly matches LifecycleState, so no re-mapping is needed.\n *\n * Events carry origin:\"agent\" because the store is the agent's own reported\n * lifecycle state — not inferred from a process scan.\n */\nexport class CmuxStoreSource implements LifecycleSource {\n readonly name = \"cmux-store\";\n\n private readonly stateDir: string;\n private readonly debounceMs: number;\n private readonly isPidAlive: (pid: number) => boolean;\n private readonly listFiles: (dir: string) => string[];\n private readonly readFile: (path: string) => string | undefined;\n private readonly fileExists: (path: string) => boolean;\n private readonly watchDir: (dir: string, cb: () => void) => () => void;\n private readonly scheduleTimer: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;\n private readonly cancelTimer: (id: ReturnType<typeof setTimeout>) => void;\n private readonly log: (msg: string) => void;\n\n private deps?: LifecycleSourceDeps;\n private stopWatcher?: () => void;\n private debounceTimer?: ReturnType<typeof setTimeout>;\n /** taskId → last reported snapshot (for snapshot() liveness floor). */\n private cache = new Map<string, LifecycleSnapshot>();\n private active = false;\n private lastError: string | null = null;\n\n constructor(opts: CmuxStoreSourceOpts = {}) {\n this.stateDir =\n opts.stateDir ??\n process.env.CMUX_AGENT_HOOK_STATE_DIR ??\n join(homedir(), \".cmuxterm\");\n this.debounceMs = opts.debounceMs ?? 50;\n this.isPidAlive = opts.isPidAlive ?? defaultIsPidAlive;\n this.listFiles = opts.listFiles ?? defaultListFiles;\n this.readFile = opts.readFile ?? defaultReadFile;\n this.fileExists = opts.fileExists ?? existsSync;\n this.watchDir = opts.watchDir ?? defaultWatchDir;\n this.scheduleTimer = opts.scheduleTimer ?? (setTimeout as NonNullable<CmuxStoreSourceOpts[\"scheduleTimer\"]>);\n this.cancelTimer = opts.cancelTimer ?? (clearTimeout as NonNullable<CmuxStoreSourceOpts[\"cancelTimer\"]>);\n this.log = opts.log ?? (() => {});\n }\n\n start(deps: LifecycleSourceDeps): void {\n this.deps = deps;\n this.active = true;\n this.lastError = null;\n // Initial scan before any watch events fire.\n this.scan();\n // Watch for subsequent changes, debounced.\n try {\n this.stopWatcher = this.watchDir(this.stateDir, () => this.scheduleDebounced());\n } catch (e) {\n this.lastError = (e as Error).message;\n this.log(`cmux-store: failed to watch ${this.stateDir}: ${(e as Error).message}`);\n }\n }\n\n stop(): void {\n if (this.debounceTimer !== undefined) {\n this.cancelTimer(this.debounceTimer);\n this.debounceTimer = undefined;\n }\n this.stopWatcher?.();\n this.stopWatcher = undefined;\n this.deps = undefined;\n this.cache.clear();\n this.active = false;\n }\n\n /** Returns the last-reported snapshot for a known crew (liveness floor). */\n snapshot(taskId: string): LifecycleSnapshot | undefined {\n return this.cache.get(taskId);\n }\n\n /** Read-only source health (B4 — dashboard visibility into which sources are up). */\n health(): { active: boolean; error: string | null } {\n return { active: this.active, error: this.lastError };\n }\n\n // ── private ─────────────────────────────────────────────────────────────────\n\n private scheduleDebounced(): void {\n if (this.debounceTimer !== undefined) {\n this.cancelTimer(this.debounceTimer);\n }\n this.debounceTimer = this.scheduleTimer(() => {\n this.debounceTimer = undefined;\n this.scan();\n }, this.debounceMs);\n }\n\n private scan(): void {\n if (!this.deps) return;\n for (const filename of this.listFiles(this.stateDir)) {\n this.scanFile(filename);\n }\n }\n\n private scanFile(filename: string): void {\n const deps = this.deps!;\n const filePath = join(this.stateDir, filename);\n const lockPath = `${filePath}.lock`;\n\n // Skip files that cmux is currently writing.\n if (this.fileExists(lockPath)) {\n this.log(`cmux-store: skipping ${filename} (locked)`);\n return;\n }\n\n const raw = this.readFile(filePath);\n if (!raw) return;\n\n let parsed: StoreFile;\n try {\n parsed = JSON.parse(raw) as StoreFile;\n } catch {\n this.log(`cmux-store: failed to parse ${filename}`);\n return;\n }\n\n for (const session of Object.values(parsed.sessions ?? {})) {\n this.processSession(session, deps);\n }\n }\n\n private processSession(session: StoreSession, deps: LifecycleSourceDeps): void {\n if (!session.sessionId || !session.cwd || typeof session.pid !== \"number\") return;\n\n const hint: CorrelationHint = {\n cwd: session.cwd,\n pid: session.pid,\n sessionId: session.sessionId,\n };\n const resolved = deps.resolve(hint);\n if (!resolved) return;\n\n // Pid-verify liveness.\n let alive = this.isPidAlive(session.pid);\n\n // Hibernation guard (research §194): cmux reclaims RAM from idle crews by\n // suspending or reaping the pid. Only treat a dead pid as logically alive\n // when the session is restorable AND idle — a running/needsInput session\n // with a dead pid is genuinely gone, not hibernated.\n if (!alive && session.isRestorable === true && session.agentLifecycle === \"idle\") {\n alive = true;\n }\n\n const snap: LifecycleSnapshot = {\n taskId: resolved.id,\n state: parseLifecycleState(session.agentLifecycle),\n alive,\n // \"agent\": the store carries the agent's own reported lifecycle state,\n // not a scan inference. needsInput from the store is authoritative.\n origin: \"agent\",\n at: Math.floor((session.updatedAt ?? 0) * 1000),\n pid: session.pid,\n ...(session.lastBody ? { detail: { note: session.lastBody } } : {}),\n };\n\n this.cache.set(resolved.id, snap);\n deps.report(snap);\n }\n}\n\n// ── private helpers ──────────────────────────────────────────────────────────\n\nfunction parseLifecycleState(s: string | undefined): LifecycleState {\n if (s === \"running\" || s === \"idle\" || s === \"needsInput\" || s === \"unknown\") {\n return s;\n }\n return \"unknown\";\n}\n\nfunction defaultIsPidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction defaultListFiles(dir: string): string[] {\n try {\n return readdirSync(dir).filter(\n (f) => f.endsWith(\"-hook-sessions.json\") && !f.endsWith(\".lock\"),\n );\n } catch {\n return [];\n }\n}\n\nfunction defaultReadFile(path: string): string | undefined {\n try {\n return readFileSync(path, \"utf-8\");\n } catch {\n return undefined;\n }\n}\n\nfunction defaultWatchDir(dir: string, cb: () => void): () => void {\n const w = watch(dir, (_event, filename) => {\n if (typeof filename === \"string\" && filename.endsWith(\"-hook-sessions.json\")) {\n cb();\n }\n });\n return () => w.close();\n}\n","// native-hook-source.ts — LifecycleSource C: squadrant-owned claude hooks\n//\n// PRIMARY LifecycleSource (#333 Phase 1, D1). Installs namespaced hooks into\n// claude's native config and receives hook events pushed by the daemon.\n//\n// NOT wired into the live daemon in Phase 1 (additive per D3/D7).\n// The sibling wiring crew adds 'squadrant hooks claude <sub>' to the CLI,\n// reads SQUADRANT_CREW_TASK_ID from the hook process env, and calls handleHook().\n\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport type { LifecycleSource, LifecycleSourceDeps, LifecycleSnapshot, LifecycleState } from \"@squadrant/core\";\n\n// ── Hook event matrix ─────────────────────────────────────────────────────────\n\n// Claude hook event name → sub-command alias → optional tool matcher (blueprint §9).\n// Non-lifecycle hooks (PostToolUse, SubagentStop) are intentionally excluded;\n// they feed the existing crew._hook bridge and are not part of the 4-state model.\n//\n// Third element (matcher) is passed as the hook entry's \"matcher\" field.\n// AskUserQuestion is a TOOL, not an event — hook it via PreToolUse with a tool matcher.\nconst CLAUDE_HOOK_EVENTS: ReadonlyArray<readonly [string, string, string?]> = [\n [\"SessionStart\", \"session-start\"],\n [\"UserPromptSubmit\", \"prompt-submit\"],\n [\"PreToolUse\", \"pre-tool-use\"],\n [\"Stop\", \"stop\"],\n [\"Notification\", \"notification\"],\n [\"PreToolUse\", \"ask-question\", \"AskUserQuestion\"],\n [\"SessionEnd\", \"session-end\"],\n];\n\nconst DEFAULT_HOOK_CMD = \"squadrant hooks\";\n\n// ── Hook installer ────────────────────────────────────────────────────────────\n\nexport interface ClaudeHooksInstallOpts {\n /** Path to ~/.claude/settings.json. Injectable for tests. */\n settingsPath?: string;\n /**\n * Base hook command — final command is '<hookCmd> claude <sub>'.\n * Default: 'squadrant hooks' (the CLI subcommand wired by the daemon crew).\n */\n hookCmd?: string;\n /** Injectable: read file content, undefined on any read error. */\n readFile?: (path: string) => string | undefined;\n /** Injectable: write file (caller responsible for creating parent dirs). */\n writeFile?: (path: string, content: string) => void;\n log?: (msg: string) => void;\n}\n\n/**\n * Idempotent, non-clobbering installer for squadrant-owned hooks in ~/.claude/settings.json.\n *\n * Installs one hook entry per lifecycle-relevant Claude hook event (D4: namespaced,\n * re-run-safe). Hooks from cmux, the user, or other tools with different commands\n * are left untouched. A second call with the same hookCmd is a complete no-op.\n * Returns the path to the settings file (which may or may not have been written).\n */\nexport function installClaudeHooks(opts: ClaudeHooksInstallOpts = {}): string {\n const settingsPath = opts.settingsPath ?? join(homedir(), \".claude\", \"settings.json\");\n const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;\n const readFile = opts.readFile ?? defaultReadFile;\n const writeFile = opts.writeFile ?? defaultWriteFile;\n const log = opts.log ?? (() => {});\n\n // Parse existing settings (start fresh if absent or malformed).\n let settings: Record<string, unknown> = {};\n const raw = readFile(settingsPath);\n if (raw) {\n try {\n settings = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n log(`native-hook: failed to parse ${settingsPath} — hooks section will be reset`);\n }\n }\n\n // Ensure hooks is a plain object.\n if (typeof settings.hooks !== \"object\" || settings.hooks === null || Array.isArray(settings.hooks)) {\n settings.hooks = {};\n }\n const hooks = settings.hooks as Record<string, unknown>;\n\n let changed = false;\n for (const [eventName, sub, matcher] of CLAUDE_HOOK_EVENTS) {\n if (!Array.isArray(hooks[eventName])) {\n hooks[eventName] = [];\n }\n const entries = hooks[eventName] as unknown[];\n const command = `${hookCmd} claude ${sub}`;\n const hookMatcher = matcher ?? \"\";\n\n // Idempotency check: skip if our exact command is already registered.\n const alreadyPresent = entries.some(\n (m) =>\n Array.isArray((m as Record<string, unknown>).hooks) &&\n ((m as Record<string, unknown>).hooks as unknown[]).some(\n (h) =>\n typeof (h as Record<string, unknown>).command === \"string\" &&\n (h as Record<string, unknown>).command === command,\n ),\n );\n if (!alreadyPresent) {\n entries.push({ matcher: hookMatcher, hooks: [{ type: \"command\", command, timeout: 10 }] });\n changed = true;\n }\n }\n\n if (changed) {\n writeFile(settingsPath, JSON.stringify(settings, null, 2));\n }\n return settingsPath;\n}\n\n// ── Sub-event → lifecycle state mapping ──────────────────────────────────────\n\n/**\n * Pure: map a sub-event alias to its LifecycleState.\n * Returns \"session-end\" for the teardown alias (not a LifecycleState value — the\n * caller emits alive:false + state:\"unknown\" and the daemon wiring translates to\n * task.session.ended). Returns null for unknown subs (caller no-ops).\n */\nexport function mapSubToLifecycle(sub: string): LifecycleState | \"session-end\" | null {\n switch (sub) {\n case \"session-start\": return \"running\";\n case \"prompt-submit\": return \"running\";\n case \"pre-tool-use\": return \"running\";\n case \"stop\": return \"idle\";\n case \"notification\": return \"needsInput\";\n case \"ask-question\": return \"needsInput\";\n case \"session-end\": return \"session-end\";\n default: return null;\n }\n}\n\n// ── NativeHookSource ─────────────────────────────────────────────────────────\n\nexport interface NativeHookSourceOpts {\n /** Options forwarded to installClaudeHooks(). Useful for testing. */\n hookInstall?: ClaudeHooksInstallOpts;\n log?: (msg: string) => void;\n}\n\n/**\n * LifecycleSource C — primary, driver-agnostic (#333 D1).\n *\n * Two seams:\n * 1. install() — writes squadrant-owned hooks into ~/.claude/settings.json\n * (idempotent, namespaced, non-clobbering per D4).\n * 2. handleHook(sub, taskId, pid?, payload?) — called by the daemon when a\n * claude hook fires; maps the sub-event to a LifecycleSnapshot and feeds\n * it into deps.report().\n *\n * Unlike CmuxStoreSource (file-watcher), NativeHookSource is purely push-driven:\n * every snapshot arrives via handleHook() from the daemon's 'squadrant hooks'\n * CLI subcommand. The snapshot() method serves the liveness floor from the cache.\n */\nexport class NativeHookSource implements LifecycleSource {\n readonly name = \"native-hook\";\n\n private readonly hookInstall: ClaudeHooksInstallOpts;\n private readonly log: (msg: string) => void;\n\n private deps?: LifecycleSourceDeps;\n /** taskId → last-reported snapshot, for snapshot() liveness floor. */\n private cache = new Map<string, LifecycleSnapshot>();\n private active = false;\n\n constructor(opts: NativeHookSourceOpts = {}) {\n this.hookInstall = opts.hookInstall ?? {};\n this.log = opts.log ?? (() => {});\n }\n\n start(deps: LifecycleSourceDeps): void {\n this.deps = deps;\n this.active = true;\n }\n\n stop(): void {\n this.deps = undefined;\n this.cache.clear();\n this.active = false;\n }\n\n /** Returns the last-reported snapshot for a known crew (liveness floor poll). */\n snapshot(taskId: string): LifecycleSnapshot | undefined {\n return this.cache.get(taskId);\n }\n\n /** Read-only source health (B4). Purely push-driven — never errors on its own. */\n health(): { active: boolean; error: string | null } {\n return { active: this.active, error: null };\n }\n\n /**\n * Install squadrant-owned hooks into ~/.claude/settings.json.\n * Idempotent — safe to call on every project init or crew spawn.\n * Returns the path to the settings file.\n */\n install(): string {\n return installClaudeHooks(this.hookInstall);\n }\n\n /**\n * Receive a lifecycle hook event from the daemon and report a LifecycleSnapshot.\n *\n * The daemon's 'squadrant hooks claude <sub>' CLI subcommand calls this after\n * reading SQUADRANT_CREW_TASK_ID from the hook's process environment — the only\n * collision-proof correlation key (blueprint §2.2 priority 1).\n *\n * @param sub Sub-event alias: \"session-start\" | \"prompt-submit\" | \"stop\" | …\n * @param taskId SQUADRANT_CREW_TASK_ID extracted from the hook process env.\n * @param pid Optional: OS pid from the hook's process env or argv.\n * @param payload Optional: parsed JSON payload from hook stdin (best-effort detail).\n */\n handleHook(sub: string, taskId: string, pid?: number, payload?: unknown): void {\n if (!this.deps) return;\n\n const mapped = mapSubToLifecycle(sub);\n if (mapped === null) {\n this.log(`native-hook: unknown sub '${sub}' for task ${taskId} — ignored`);\n return;\n }\n\n // session-end signals teardown: alive:false lets the daemon wiring emit\n // task.session.ended (anti-#2576: never task.done from a lifecycle hook).\n const isSessionEnd = mapped === \"session-end\";\n const state: LifecycleState = isSessionEnd ? \"unknown\" : mapped;\n\n const detail = extractDetail(sub, payload);\n const snap: LifecycleSnapshot = {\n taskId,\n state,\n alive: !isSessionEnd,\n origin: \"agent\",\n at: Date.now(),\n ...(pid !== undefined ? { pid } : {}),\n ...(detail ? { detail } : {}),\n };\n\n this.cache.set(taskId, snap);\n this.deps.report(snap);\n }\n}\n\n// ── Private helpers ───────────────────────────────────────────────────────────\n\nfunction extractDetail(sub: string, payload: unknown): LifecycleSnapshot[\"detail\"] | undefined {\n if (!payload || typeof payload !== \"object\") return undefined;\n const p = payload as Record<string, unknown>;\n if (sub === \"notification\") {\n const note = typeof p.message === \"string\" ? p.message : undefined;\n return note ? { note } : undefined;\n }\n if (sub === \"pre-tool-use\") {\n const tool = typeof p.tool_name === \"string\" ? p.tool_name : undefined;\n return tool ? { tool } : undefined;\n }\n return undefined;\n}\n\nfunction defaultReadFile(path: string): string | undefined {\n try {\n return readFileSync(path, \"utf-8\");\n } catch {\n return undefined;\n }\n}\n\nfunction defaultWriteFile(path: string, content: string): void {\n mkdirSync(path.replace(/\\/[^/]+$/, \"\"), { recursive: true });\n writeFileSync(path, content, \"utf-8\");\n}\n","// Runtime-bound crew-pane helpers — discovery, first-turn delivery, captain\n// workspace resolution. Extracted from packages/cli/src/commands/crew.ts so\n// they are unit-testable with a mock RuntimeDriver.\n\nimport net from \"node:net\";\nimport { loadConfig } from \"@squadrant/shared\";\nimport type { PaneRef, RuntimeDriver } from \"@squadrant/shared\";\nimport { RuntimeRegistry } from \"./runtimes/registry.js\";\nimport { createCmuxDriver, parseDraftFromScreen, hasCCInputBox, hasModalOptionList, classifyStartupSurface } from \"./runtimes/cmux.js\";\nimport { titleFor, isCrewTitle, screenHasSplashMarker } from \"@squadrant/core\";\nimport type { TurnAcceptanceConfig } from \"@squadrant/core\";\n\n// Poll-based first-turn delivery timing constants.\nconst SEND_FIRST_TURN_FLOOR_MS = 1500;\nconst POLL_INTERVAL_MS = 750;\n// #466 residual: 90s readiness cap. Captains boot UNLOADED (5–15s, hence their\n// 30s readyTimeoutMs), but crews cold-init UNDER LOAD in a fresh worktree with\n// claude-mem's MCP server loading — that can take 30–60s to reach input-ready\n// (pact-network's actual case). A captain-parity 30s budget timed out into the\n// still-cold box and the confirmedSendToPane fallback blind-fired keystrokes that\n// were dropped. The strong CC-initialized gate below makes a generous cap safe: it\n// only ever waits as long as CC actually needs (delivery fires the instant the\n// surface is ready), and a crashed/never-ready CC still caps out here. 90s sits\n// well under the daemon's 5min CREW UNDELIVERED watchdog, so a genuine failure is\n// still surfaced.\nconst SEND_FIRST_TURN_TIMEOUT_MS = 90000;\nconst POST_SEND_CHECK_MS = 750;\n\n// #235 Confirm-on-delivery constants for splash-gated agents (opencode).\n// We poll every POST_SEND_CHECK_MS but only re-send every SPLASH_RESEND_EVERY_N\n// checks — a 3s de-dup guard that prevents double-execution when the TUI is\n// slow to redraw after accepting.\nconst SPLASH_MAX_CHECKS = 20; // 20 × 750ms ≈ 15s confirmation window\nconst SPLASH_RESEND_EVERY_N = 4; // re-send every 4 checks ≈ every 3s\n\n// #339 paste-then-submit constants for the claude/codex first-turn path.\n// SETTLE polls the input box after the paste until its content stops changing —\n// i.e. Claude Code's paste-accumulation window has closed — so the submit CR is a\n// separate keystroke that lands AFTER the [Pasted text] placeholder is final and\n// is therefore treated as a submit, not a literal newline inside the paste.\nconst SETTLE_POLL_MS = 400;\nconst SETTLE_MAX_POLLS = 8; // up to 3.2s for a very large paste to render\nconst SUBMIT_RETRY_LIMIT = 4; // Enter-only re-issues if the box stays stranded\n\n/** Poll the pane until its input box stops changing across two consecutive reads\n * (paste fully rendered / accumulation window closed), or the cap is hit.\n * Returns true if the box was observed with content at any point — the caller\n * uses this to distinguish \"paste rendered then submitted\" from \"paste never\n * rendered, empty box is NOT a confirmation of submit\" (#455). */\nasync function settleInputBox(\n runtime: Pick<RuntimeDriver, \"readPaneScreen\">,\n pane: PaneRef,\n): Promise<boolean> {\n let prev = (await runtime.readPaneScreen(pane)) ?? \"\";\n let sawContent = parseDraftFromScreen(prev) !== \"\" && parseDraftFromScreen(prev) !== null;\n for (let i = 0; i < SETTLE_MAX_POLLS; i++) {\n await new Promise((r) => setTimeout(r, SETTLE_POLL_MS));\n const cur = (await runtime.readPaneScreen(pane)) ?? \"\";\n const draft = parseDraftFromScreen(cur);\n if (draft !== \"\" && draft !== null) sawContent = true;\n if (cur === prev) return sawContent;\n prev = cur;\n }\n return sawContent;\n}\n\n/** Reserve an ephemeral TCP port for a crew's embedded HTTP server. Binds :0,\n * reads the OS-assigned port, then releases it. A small TOCTOU window exists\n * between release and the crew binding the port; acceptable for local\n * single-user spawns. */\nexport function getFreePort(): Promise<number> {\n return new Promise((resolve, reject) => {\n const srv = net.createServer();\n srv.once(\"error\", reject);\n srv.listen(0, \"127.0.0.1\", () => {\n const addr = srv.address();\n const port = typeof addr === \"object\" && addr ? addr.port : 0;\n srv.close(() => (port ? resolve(port) : reject(new Error(\"no free port assigned\"))));\n });\n });\n}\n\nexport async function listProjectCrews(\n runtime: RuntimeDriver,\n workspaceId: string,\n project: string,\n): Promise<PaneRef[]> {\n const surfaces = await runtime.listSurfaces(workspaceId);\n return surfaces.filter((s) => s.title && isCrewTitle(project, s.title));\n}\n\nexport async function findCrew(\n runtime: RuntimeDriver,\n workspaceId: string,\n project: string,\n name: string,\n): Promise<PaneRef | null> {\n const want = titleFor(project, name);\n const surfaces = await runtime.listSurfaces(workspaceId);\n return surfaces.find((s) => s.title === want) ?? null;\n}\n\nexport async function resolveCaptainWorkspace(project: string): Promise<{\n runtime: RuntimeDriver;\n workspaceId: string;\n}> {\n const config = loadConfig();\n const proj = config.projects[project];\n if (!proj) {\n throw new Error(`Project '${project}' not found. Run 'squadrant projects list'.`);\n }\n const runtime = new RuntimeRegistry({ cmux: createCmuxDriver() }).forProject(project, config);\n const captain = await runtime.status(proj.captainName);\n if (!captain) {\n throw new Error(\n `Captain workspace '${proj.captainName}' is not running. Run 'squadrant launch ${project}' first.`,\n );\n }\n return { runtime, workspaceId: captain.id };\n}\n\n/**\n * #516: cheap, side-effect-free check for an open AskUserQuestion/permission\n * SELECTION MODAL — a single screen read, no paste, no keystroke. Lets\n * runCrewSend skip its daemon-state emit (task.reopened/task.started) AND the\n * pane touch entirely when the crew can't actually receive the message right\n * now, so a modal-blocked send is a true no-op rather than just skipping the\n * pane write. confirmedSendToPane keeps its own copy of this check as the\n * pane-touch backstop for the TOCTOU window between this precheck and delivery.\n */\nexport async function paneHasOpenModal(\n runtime: Pick<RuntimeDriver, \"readPaneScreen\">,\n pane: PaneRef,\n): Promise<boolean> {\n const screen = (await runtime.readPaneScreen(pane)) ?? \"\";\n return hasModalOptionList(screen);\n}\n\n/**\n * Deliver a message to a crew pane with the paste-settle-Enter confirmation\n * sequence from #447. Shared by the follow-up `crew send` path (#448) and\n * available for first-turn use — both call the same submit hardening:\n * 1. paste only (no bundled CR)\n * 2. settle until the input box content stops changing (accumulation closed)\n * 3. separate Enter keystroke\n * 4. confirm box empty; re-issue ONLY Enter if stranded (never re-paste)\n *\n * Returns `{ delivered: true }` when the box empties after the draft was seen\n * (positive submit confirmation), or `{ delivered: false }` if the retry loop\n * exhausts without confirmation (#466: callers surface non-delivery explicitly).\n * Returns `{ delivered: false, blockedByModal: true }` without touching the\n * pane at all when an AskUserQuestion/permission SELECTION MODAL is open (#516).\n */\nexport async function confirmedSendToPane(\n runtime: Pick<RuntimeDriver, \"readPaneScreen\" | \"pasteToPane\" | \"sendKeyToPane\">,\n pane: PaneRef,\n message: string,\n): Promise<{ delivered: boolean; blockedByModal?: boolean }> {\n const preSendScreen = (await runtime.readPaneScreen(pane)) ?? \"\";\n // #516: a selection modal renders its highlighted default option (\"❯ 1. Red\")\n // in the same HR-bounded region a real draft would occupy, so the settle\n // loop below can't tell \"modal open\" apart from \"draft present\" — sending\n // Enter here would CONFIRM the modal's default instead of delivering the\n // captain's message (mirrors the #484 guard on the sendToSurface delivery\n // path, which has no equivalent here). Never keystroke into it.\n if (hasModalOptionList(preSendScreen)) {\n return { delivered: false, blockedByModal: true };\n }\n await runtime.pasteToPane(pane, message);\n // #455: track whether the paste ever rendered so we don't treat an empty box\n // that was NEVER populated as a successful submit (race: paste still in flight\n // when settle fires, stable-empty → Enter into nothing → false \"submitted\").\n let sawDraft = await settleInputBox(runtime, pane);\n await runtime.sendKeyToPane(pane, \"Enter\");\n\n let repasted = false;\n for (let attempt = 0; attempt < SUBMIT_RETRY_LIMIT; attempt++) {\n await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));\n const afterScreen = (await runtime.readPaneScreen(pane)) ?? \"\";\n const draft = parseDraftFromScreen(afterScreen);\n if (draft !== \"\" && draft !== null) sawDraft = true;\n // Box confirmed empty AND we observed the paste rendered first → submitted.\n if (draft === \"\" && sawDraft) return { delivered: true };\n if (draft === null && afterScreen !== preSendScreen && sawDraft) return { delivered: true };\n const settled = await settleInputBox(runtime, pane);\n if (settled) sawDraft = true;\n // #455: paste never rendered — re-paste once rather than issuing Enter into emptiness.\n if (!sawDraft && !repasted) {\n repasted = true;\n await runtime.pasteToPane(pane, message);\n }\n await runtime.sendKeyToPane(pane, \"Enter\");\n }\n return { delivered: false };\n}\n\n/**\n * #466 daemon self-heal: the pane-touching primitive behind the daemon's\n * sweep-loop first-turn resend hook. Finds the crew's own pane by title (never\n * blind-sends anywhere), RE-CHECKS TUI readiness itself (never blind-pastes into\n * a still-booting box — CRITICAL SAFETY), and only then submits via the same\n * paste-settle-Enter path a manual `crew send` uses. Returns { delivered: false }\n * without touching the pane when the crew can't be found or isn't ready yet —\n * the caller (the daemon sweep loop) retries on a later tick.\n */\nexport async function resendCrewFirstTurn(\n runtime: Pick<RuntimeDriver, \"status\" | \"listSurfaces\" | \"readPaneScreen\" | \"pasteToPane\" | \"sendKeyToPane\">,\n captainName: string,\n project: string,\n name: string,\n message: string,\n): Promise<{ delivered: boolean }> {\n const captain = await runtime.status(captainName);\n if (!captain) return { delivered: false };\n const surfaces = await runtime.listSurfaces(captain.id);\n const want = titleFor(project, name);\n const pane = surfaces.find((s) => s.title === want);\n if (!pane) return { delivered: false };\n const screen = (await runtime.readPaneScreen(pane)) ?? \"\";\n if (!hasCCInputBox(screen) || classifyStartupSurface(screen) !== \"idle\") {\n return { delivered: false }; // still not ready — caller retries on a later tick\n }\n return confirmedSendToPane(runtime, pane, message);\n}\n\nexport async function sendFirstTurnWhenReady(\n runtime: Pick<RuntimeDriver, \"readPaneScreen\" | \"sendToPane\" | \"pasteToPane\" | \"sendKeyToPane\">,\n pane: PaneRef,\n task: string,\n preLaunchScreen: string,\n acceptanceConfig?: TurnAcceptanceConfig,\n): Promise<{ delivered: boolean }> {\n await new Promise((r) => setTimeout(r, SEND_FIRST_TURN_FLOOR_MS));\n\n const maxPolls = Math.floor(\n (SEND_FIRST_TURN_TIMEOUT_MS - SEND_FIRST_TURN_FLOOR_MS) / POLL_INTERVAL_MS,\n );\n let previousScreen = \"\";\n let stable = false;\n\n for (let i = 0; i < maxPolls && !stable; i++) {\n const screen = (await runtime.readPaneScreen(pane)) ?? \"\";\n // Ready = the agent prompt is actually up: screen is non-empty, settled\n // (unchanged between two consecutive reads), has advanced past the un-entered\n // launch command line, AND (for the claude/codex path) the CC input box is\n // rendered with its ❯ prompt glyph. hasCCInputBox is stricter than the old\n // parseDraftFromScreen(screen)!==null check: the claude-mem startup banner can\n // produce HR-bounded regions without a ❯ inside — parseDraftFromScreen returns\n // \"\" (≠ null) for those, falsely satisfying the old gate. hasCCInputBox\n // requires the ❯ to be present, so banners do not trigger a premature paste\n // (#466-single root cause). For the opencode splash path the splashMarker\n // short-circuits before this check, preserving existing behaviour.\n // #466 residual: the ❯ input box renders during cold-init while claude-mem's\n // MCP server is still loading — a window where keystrokes are silently dropped\n // (#235/#292). hasCCInputBox alone (just a ❯ between two HRs) is satisfied in\n // that window, so the old gate pasted into a box that dropped the keystrokes and\n // the first turn never landed. Adopt the captain startup contract: require the\n // surface to be CC-INITIALIZED (classifyStartupSurface === \"idle\", i.e. the\n // persistent bottom status block — Ctx Used / ⏵⏵ / shortcuts / accept edits — is\n // present and no turn is in flight), in addition to the ❯ box (keeps #469's\n // banner rejection). For the opencode splash path (#499), readiness is a\n // POSITIVE signal too: the marker must actually be visible on screen (not\n // hardcoded true) — otherwise a booting/mid-transition screen could be\n // declared \"stable\" before the TUI has rendered its idle splash at all.\n const ready = acceptanceConfig?.splashMarker\n ? screenHasSplashMarker(screen, acceptanceConfig.splashMarker)\n : hasCCInputBox(screen) && classifyStartupSurface(screen) === \"idle\";\n if (screen.length > 0 && screen === previousScreen && screen !== preLaunchScreen && ready) {\n stable = true;\n } else {\n previousScreen = screen;\n await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));\n }\n }\n\n // Snapshot the screen immediately before sending so the post-send check can\n // tell whether the keystrokes were received. Comparing against the raw task\n // text is unreliable: sendToPane collapses newlines to spaces (#136), so a\n // multi-line task never appears verbatim in the single-line pane render and\n // the check would always re-send a duplicate first turn (#168).\n const preSendScreen = (await runtime.readPaneScreen(pane)) ?? \"\";\n\n // Confirm-on-delivery (#235): poll until the TUI confirms it accepted the turn.\n if (acceptanceConfig?.splashMarker) {\n // Splash path (opencode): the \"Ask anything…\" splash clears once the TUI\n // consumes the message. We check every POST_SEND_CHECK_MS but re-send only\n // every SPLASH_RESEND_EVERY_N checks — a 3s de-dup guard that prevents\n // duplicate task execution when the TUI is slow to redraw after accepting.\n // opencode's TUI does not collapse pastes into placeholders, so the atomic\n // send+Enter (sendToPane) is correct here and must stay (it was just fixed\n // and live-verified in #235). The #339 paste race is claude-specific.\n //\n // #499: sawSplash latches once the marker has actually been observed on\n // screen. Only THEN does the marker's absence count as acceptance — this\n // mirrors the claude path's sawDraft gate. Without the latch, a marker that\n // never matches (drift, misconfiguration) makes isTurnAccepted return true\n // on the very first check, before any keystroke lands, and silently\n // confirms delivery of a turn that was never sent. With the latch, the same\n // situation exhausts the confirm window and fails closed (delivered:false),\n // surfacing the non-delivery warning instead.\n let sawSplash = screenHasSplashMarker(preSendScreen, acceptanceConfig.splashMarker);\n await runtime.sendToPane(pane, task);\n for (let check = 0; check < SPLASH_MAX_CHECKS; check++) {\n await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));\n const afterScreen = (await runtime.readPaneScreen(pane)) ?? \"\";\n if (screenHasSplashMarker(afterScreen, acceptanceConfig.splashMarker)) {\n sawSplash = true;\n } else if (sawSplash) {\n return { delivered: true };\n }\n if ((check + 1) % SPLASH_RESEND_EVERY_N === 0 && check < SPLASH_MAX_CHECKS - 1) {\n await runtime.sendToPane(pane, task);\n }\n }\n return { delivered: false };\n }\n\n // #466: if the box never appeared in the boot window, skip the paste path and\n // go directly to the settled-box fallback (confirmedSendToPane). By the time we\n // get here, more time has passed and the box is likely ready.\n if (!stable) {\n return confirmedSendToPane(runtime, pane, task);\n }\n\n // Claude/codex path (#339): paste the task, let the [Pasted text] placeholder\n // settle, THEN submit with a separate Enter. Bundling the CR with the paste\n // (the old sendToPane) lets Claude Code absorb it as a literal newline inside\n // the placeholder under load, stranding the whole turn unsubmitted. We confirm\n // the submit by the input box going empty — NOT by \"screen changed\", because\n // the paste itself changes the screen. If the box is still holding the draft we\n // re-issue ONLY the Enter (after re-settling) and NEVER re-paste — re-pasting is\n // exactly what stacks [Pasted text #1][#2][#3] and never submits.\n await runtime.pasteToPane(pane, task);\n // #455: track whether the paste ever rendered so we don't treat an empty box\n // that was NEVER populated as a successful submit (race: paste still in flight\n // when settle fires, stable-empty → Enter into nothing → false \"submitted\").\n let sawDraft = await settleInputBox(runtime, pane);\n await runtime.sendKeyToPane(pane, \"Enter\");\n\n const retryLimit = acceptanceConfig?.retryLimit ?? SUBMIT_RETRY_LIMIT;\n let repasted = false;\n for (let attempt = 0; attempt < retryLimit; attempt++) {\n await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));\n const afterScreen = (await runtime.readPaneScreen(pane)) ?? \"\";\n const draft = parseDraftFromScreen(afterScreen);\n if (draft !== \"\" && draft !== null) sawDraft = true;\n // Box confirmed empty AND we observed the paste rendered first → submitted.\n if (draft === \"\" && sawDraft) return { delivered: true };\n // Box not parseable (e.g. an agent TUI without the HR-bounded box, or a\n // transient overlay): fall back to the screen-changed signal so non-claude\n // TUIs aren't worse off than before.\n if (draft === null && afterScreen !== preSendScreen && sawDraft) return { delivered: true };\n const settled = await settleInputBox(runtime, pane);\n if (settled) sawDraft = true;\n // #455: paste never rendered — re-paste once rather than issuing Enter into emptiness.\n if (!sawDraft && !repasted) {\n repasted = true;\n await runtime.pasteToPane(pane, task);\n }\n // Still stranded — re-issue ONLY the Enter (re-paste only when never rendered).\n await runtime.sendKeyToPane(pane, \"Enter\");\n }\n\n // #466: retry loop exhausted — if the paste never rendered (sawDraft=false),\n // the box was likely not ready when we pasted (the #466 timing race). Fall back\n // once to confirmedSendToPane which starts fresh on a now-settled box.\n // When sawDraft=true (paste rendered, Enter repeatedly failed), re-pasting would\n // stack [Pasted text] entries — do not retry, just report non-delivery.\n if (!sawDraft) {\n return confirmedSendToPane(runtime, pane, task);\n }\n return { delivered: false };\n}\n","// Best-effort \"daemon restarted\" broadcast to all running captains, fired on\n// daemon boot — but only when the running build actually changed (version or\n// local rebuild), not on a same-build launchd crash-restart. Routes through the\n// mailbox (appendCaptainMessage) so the daemon's delivery-loop drains it with\n// draft protection, instead of raw driver.send which clobbers the user's draft\n// (#529).\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { SquadrantConfig } from \"@squadrant/shared\";\n\n/** Minimal slice of RuntimeDriver used to resolve captain status. */\nexport interface DaemonRestartNotifyDriver {\n status(nameOrId: string): Promise<{ id: string } | null>;\n}\n\n/** Matches the appendCaptainMessage signature from @squadrant/core/mailbox.\n * The caller provides the closure with stateRoot already bound. */\nexport type AppendCaptainMessageFn = (project: string, text: string) => Promise<void | number>;\n\nfunction statePath(stateRoot: string): string {\n return path.join(stateRoot, \"daemon-restart-state.json\");\n}\n\n/** version + build-file mtime — differs on a version bump AND on a local\n * rebuild of the same version (mtime moves), but not on a plain restart. */\nexport function computeRestartSignature(version: string, buildMtimeMs: number): string {\n return `${version}::${buildMtimeMs}`;\n}\n\nexport function readPersistedRestartSignature(stateRoot: string): string | null {\n try {\n const raw = fs.readFileSync(statePath(stateRoot), \"utf-8\");\n const data = JSON.parse(raw) as { signature?: string };\n return typeof data.signature === \"string\" ? data.signature : null;\n } catch {\n return null;\n }\n}\n\nexport function writePersistedRestartSignature(stateRoot: string, signature: string): void {\n fs.mkdirSync(stateRoot, { recursive: true });\n fs.writeFileSync(statePath(stateRoot), JSON.stringify({ signature }, null, 2) + \"\\n\");\n}\n\nfunction restartNotice(version: string, isDevRebuild: boolean): string {\n const suffix = isDevRebuild ? \" (dev build)\" : \"\";\n return `⚠️ Daemon restarted → v${version}${suffix} (control-plane bounced). Re-verify in-flight crews — a crew mid-first-turn may need a crew send.`;\n}\n\n/**\n * Send the daemon-restart notice to every running captain via the mailbox.\n * Unlike notifyCaptainsOfEffort there is no initiating cwd captain to exclude —\n * the daemon boots independently of any captain — so this reaches ALL of them.\n * The appendCaptainMessage callback is a closure that captures stateRoot from\n * the caller (squadrantd.ts), so it takes (projectName, text).\n */\nexport async function notifyCaptainsOfDaemonRestart(\n version: string,\n config: SquadrantConfig,\n driver: DaemonRestartNotifyDriver,\n isDevRebuild = false,\n appendCaptainMessage: AppendCaptainMessageFn,\n): Promise<void> {\n const notice = restartNotice(version, isDevRebuild);\n for (const [projName] of Object.entries(config.projects)) {\n try {\n const proj = config.projects[projName];\n const ref = await driver.status(proj.captainName);\n if (ref) {\n await appendCaptainMessage(projName, notice);\n }\n } catch {\n // individual project captain unreachable — skip\n }\n }\n}\n\nexport interface MaybeBroadcastDaemonRestartOpts {\n version: string;\n buildMtimeMs: number;\n stateRoot: string;\n config: SquadrantConfig;\n driver: DaemonRestartNotifyDriver;\n appendCaptainMessage: AppendCaptainMessageFn;\n}\n\n/**\n * Boot-time entry point: compare this boot's (version, buildMtime) signature\n * against the last persisted one. Differs → broadcast + persist. Same → stay\n * silent (e.g. launchd crash-restart of an identical build). Fully\n * best-effort — never throws, so it can never block or crash daemon boot.\n */\nexport async function maybeBroadcastDaemonRestart(opts: MaybeBroadcastDaemonRestartOpts): Promise<void> {\n try {\n const { version, buildMtimeMs, stateRoot, config, driver, appendCaptainMessage } = opts;\n const signature = computeRestartSignature(version, buildMtimeMs);\n const previous = readPersistedRestartSignature(stateRoot);\n if (previous === signature) return;\n const isDevRebuild = previous !== null && previous.split(\"::\")[0] === version;\n await notifyCaptainsOfDaemonRestart(version, config, driver, isDevRebuild, appendCaptainMessage);\n writePersistedRestartSignature(stateRoot, signature);\n } catch {\n // best-effort — never let a broadcast failure block or crash daemon boot\n }\n}\n"],"mappings":"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","names":["distBuiltAt","join","dirname","homedir","fileURLToPath","readFileSync","statSync","fs","os","path","existsSync","readFileSync","writeFileSync","mkdirSync","rmSync","homedir","dirname","join","path","existsSync","readFileSync","writeFileSync","join","existsSync","readFileSync","homedir","join","join","writeFileSync","existsSync","readFileSync","join","homedir","path","readFileSync","statePath","mkdirSync","dirname","writeFileSync","existsSync","rmSync","fs","path","os","execFileSync","fs","path","fs","fs","path","fs","path","fs","path","fs","join","fs","existsSync","resolve","mkdirSync","readFileSync","writeFileSync","existsSync","rmSync","join","execFileSync","mkdirSync","writeFileSync","readFileSync","existsSync","unlinkSync","homedir","dirname","join","cmux","homedir","join","writeFileSync","mkdirSync","writeFileSync","readFileSync","renameSync","readFileSync","writeFileSync","renameSync","join","homedir","spawn","mkdirSync","writeFileSync","randomUUID","randomUUID","join","dirname","cmux","createServer","assembleDaemonSnapshot","fileURLToPath","join","statSync","openSync","closeSync","readdirSync","readFileSync","path","CURSOR_SUBSCRIBER","logPath","join","dirname","createServer","resolve","fs","path","resolve","sleep","fs","path","sep","os","path","sleep","appendCaptainMessage","path","os","project","fs","execFileSync","existsSync","homedir","join","join","homedir","randomUUID","homedir","join","DEFAULT_SOCK_PATH","join","homedir","fs","fs","os","path","path","os","execSync","execSync","execSync","execSync","fs","path","path","os","mkdir","readFile","writeFile","path","os","mkdir","readFile","writeFile","path","os","mkdir","readFile","writeFile","path","os","resolve","readFile","homedir","join","resolve","sleep","execSync","readFileSync","homedir","join","resolve","execFile","execFileSync","resolve","execFile","execSync","promisify","execFile","promisify","execSync","fs","existsSync","path","nodeSpawn","bin","args","nodeSpawn","sleep","resolve","readdirSync","readFileSync","join","homedir","readFile","join","homedir","readdirSync","readFileSync","join","homedir","readdirSync","readFileSync","existsSync","defaultIsPidAlive","path","join","homedir","mkdirSync","readFileSync","writeFileSync","readFile","defaultReadFile","writeFile","path","fs","path","statePath","appendCaptainMessage","SELF_PATH","fileURLToPath","join","dirname","homedir","readFileSync","spawn","statSync"]}
|
|
1
|
+
{"version":3,"sources":["../packages/core/src/snapshot.ts","../packages/cli/src/squadrantd.ts","../packages/shared/src/config.ts","../packages/shared/src/project-config.ts","../packages/shared/src/types/control.ts","../packages/shared/src/lib/cmux-autoconfig.ts","../packages/shared/src/lib/cmux-config.ts","../packages/shared/src/lib/cmux-probe.ts","../packages/shared/src/lib/cmux-bin.ts","../packages/shared/src/lib/compat-manifest.ts","../packages/shared/src/lib/update-check.ts","../packages/shared/src/lib/git-worktree.ts","../packages/shared/src/lib/resolve-text-input.ts","../packages/shared/src/lib/runtime-sync.ts","../packages/shared/src/lib/tool-compat.ts","../packages/shared/src/lib/canonical-source.ts","../packages/shared/src/lib/daily-logs.ts","../packages/core/src/state-machine.ts","../packages/core/src/watchdog.ts","../packages/core/src/daemon/reduce.ts","../packages/core/src/mailbox.ts","../packages/core/src/protocol.ts","../packages/core/src/liveness.ts","../packages/core/src/store.ts","../packages/core/src/index.ts","../packages/core/src/launchd.ts","../packages/core/src/crew-pane-reader.ts","../packages/core/src/gate.ts","../packages/core/src/daemon/context.ts","../packages/core/src/daemon/liveness-registry.ts","../packages/core/src/daemon/attach.ts","../packages/core/src/daemon/start.ts","../packages/core/src/daemon/interactive-probe.ts","../packages/core/src/daemon/probes.ts","../packages/core/src/delivery/defer-delivery.ts","../packages/core/src/delivery/captain-delivery.ts","../packages/core/src/daemon/delivery-loop.ts","../packages/core/src/daemon/gates.ts","../packages/core/src/daemon/server.ts","../packages/core/src/daemon/snapshot-gather.ts","../packages/core/src/session-freshness.ts","../packages/core/src/crew-protocol.ts","../packages/core/src/crew-lifecycle.ts","../packages/core/src/telegram/auth.ts","../packages/core/src/telegram/commands.ts","../packages/core/src/telegram/control.ts","../packages/core/src/telegram/ensure-captain.ts","../packages/core/src/telegram/format.ts","../packages/core/src/telegram/state.ts","../packages/core/src/telegram/client.ts","../packages/core/src/telegram/bridge.ts","../packages/core/src/telegram/panels.ts","../packages/core/src/telegram/tiers.ts","../packages/core/src/telegram/setup.ts","../packages/core/src/restart-daemon.ts","../packages/core/src/group-dispatch.ts","../packages/core/src/side-session.ts","../packages/core/src/crew-spawn.ts","../packages/core/src/lifecycle-source.ts","../packages/agents/src/drivers/claude.ts","../packages/agents/src/drivers/codex.ts","../packages/agents/src/drivers/gemini.ts","../packages/agents/src/drivers/opencode.ts","../packages/agents/src/drivers/launch-cmd.ts","../packages/agents/src/projection/cursor.ts","../packages/agents/src/projection/codex.ts","../packages/agents/src/projection/gemini.ts","../packages/agents/src/projection/opencode.ts","../packages/agents/src/codex/app-server-client.ts","../packages/agents/src/codex/codex-app-server-source.ts","../packages/agents/src/codex/config.ts","../packages/agents/src/codex/normalize.ts","../packages/agents/src/codex/driver.ts","../packages/agents/src/opencode/sse-bridge.ts","../packages/agents/src/interactive/claude.ts","../packages/agents/src/headless/types.ts","../packages/agents/src/headless/claude.ts","../packages/agents/src/headless/opencode.ts","../packages/agents/src/headless/codex.ts","../packages/agents/src/headless/registry.ts","../packages/agents/src/headless-launcher.ts","../packages/workspaces/src/runtimes/cmux.ts","../packages/workspaces/src/runtimes/registry.ts","../packages/workspaces/src/notifiers/cmux.ts","../packages/workspaces/src/notifiers/registry.ts","../packages/workspaces/src/workspaces/obsidian.ts","../packages/workspaces/src/cmux-daemon/events-bridge.ts","../packages/workspaces/src/cmux-daemon/daemon-cmux.ts","../packages/workspaces/src/cmux-daemon/store-fingerprint.ts","../packages/workspaces/src/cmux-daemon/cmux-store-source.ts","../packages/workspaces/src/native-hooks/native-hook-source.ts","../packages/workspaces/src/crew-pane.ts","../packages/cli/src/lib/daemon-restart-broadcast.ts"],"sourcesContent":["// src/control/snapshot.ts\n//\n// PURE Tier 0/1/2 snapshot assembly (no I/O, no clock) for the read-only\n// `snapshot` socket verb — the observability-dashboard counterpart to\n// liveness.ts. Every derived value comes from already-gathered inputs + an\n// explicit `now`, so the whole module is trivially unit-testable. squadrantd.ts\n// performs the I/O (dist stat, log read, mailbox/store/results reads) and feeds\n// the gathered numbers in here; this module never touches the filesystem.\nimport type { ComponentHealth } from \"./liveness.js\";\nimport type { MailboxStats } from \"./mailbox.js\";\nimport type { CaptainDeliveryStats } from \"./delivery/captain-delivery.js\";\nimport type { TelegramBridgeHealth } from \"./telegram/bridge.js\";\nexport type { MailboxStats };\n\n/** B3: Telegram bridge status. `configured: false` when no bridge is set up\n * (v.s. `configured: true` with a dead poll loop — a distinct, worse state). */\nexport interface TelegramHealth extends TelegramBridgeHealth {\n configured: boolean;\n}\n\n/** B4: one registered LifecycleSource's health (cmux-store/native-hook/codex-appserver). */\nexport interface LifecycleSourceHealth {\n name: string;\n active: boolean;\n error: string | null;\n}\n\nexport type BuildState = \"fresh\" | \"stale\";\n\n/**\n * Pure. The deploy-hygiene check: a daemon whose process started BEFORE the\n * current `dist/` build is running stale code (the recurring footgun). Fresh\n * requires the process to have started at or after the last build.\n * processStartedAt >= distBuiltAt → \"fresh\" (boundary inclusive)\n * else → \"stale\"\n */\nexport function buildFreshness(processStartedAt: number, distBuiltAt: number): BuildState {\n return processStartedAt >= distBuiltAt ? \"fresh\" : \"stale\";\n}\n\n// ── Tier 0: daemon root ───────────────────────────────────────────────────────\nexport interface DaemonRoot {\n pid: number;\n uptimeMs: number;\n version: string;\n build: { state: BuildState; processStartedAt: number; distBuiltAt: number };\n /** lastSweepAt/ageMs are null until the first sweep has run. */\n sweep: { lastSweepAt: number | null; ageMs: number | null; cadenceMs: number };\n log: { errorCount: number; sizeBytes: number; windowMs: number };\n telegram: TelegramHealth;\n lifecycleSources: LifecycleSourceHealth[];\n}\n\n// ── Tier 2: per-project data plane + global results ───────────────────────────\n\nexport interface DeliveryLag {\n maxSeq: number;\n lastAckedSeq: number;\n /** maxSeq − lastAckedSeq, clamped at 0 — \"captain N behind\". */\n behind: number;\n}\n\nexport interface StoreStats {\n byState: Record<string, number>;\n corruptCount: number;\n}\n\nexport interface ResultArtifacts {\n fileCount: number;\n totalBytes: number;\n}\n\nexport interface ProjectDataPlane {\n project: string;\n mailbox: MailboxStats;\n delivery: DeliveryLag;\n store: StoreStats;\n /** B1: read-only captain-delivery deferral visibility (#484/#466-class stalls). */\n deferral: CaptainDeliveryStats;\n}\n\nexport interface DaemonSnapshot {\n tier0: DaemonRoot;\n /** Tier 1 — per-component liveness across all projects (reuses projectHealth). */\n tier1: ComponentHealth[];\n tier2: {\n projects: ProjectDataPlane[];\n /** _results/ is a single global directory keyed by task id. */\n results: ResultArtifacts;\n };\n}\n\n/** Already-gathered (I/O-resolved) inputs the pure assembler turns into a DaemonSnapshot. */\nexport interface DaemonSnapshotInputs {\n pid: number;\n processStartedAt: number;\n version: string;\n distBuiltAt: number;\n lastSweepAt: number | null;\n sweepCadenceMs: number;\n log: { errorCount: number; sizeBytes: number; windowMs: number };\n telegram: TelegramHealth;\n lifecycleSources: LifecycleSourceHealth[];\n health: ComponentHealth[];\n projects: Array<{\n project: string;\n mailbox: MailboxStats;\n lastAckedSeq: number;\n storeByState: Record<string, number>;\n corruptCount: number;\n /** Omitted when the caller has no CaptainDelivery instance for this project yet. */\n deferral?: CaptainDeliveryStats;\n }>;\n results: ResultArtifacts;\n}\n\n/**\n * Pure. Derive the full DaemonSnapshot from gathered inputs and an explicit now.\n */\nexport function assembleDaemonSnapshot(input: DaemonSnapshotInputs, now: number): DaemonSnapshot {\n return {\n tier0: {\n pid: input.pid,\n uptimeMs: now - input.processStartedAt,\n version: input.version,\n build: {\n state: buildFreshness(input.processStartedAt, input.distBuiltAt),\n processStartedAt: input.processStartedAt,\n distBuiltAt: input.distBuiltAt,\n },\n sweep: {\n lastSweepAt: input.lastSweepAt,\n ageMs: input.lastSweepAt == null ? null : now - input.lastSweepAt,\n cadenceMs: input.sweepCadenceMs,\n },\n log: input.log,\n telegram: input.telegram,\n lifecycleSources: input.lifecycleSources,\n },\n tier1: input.health,\n tier2: {\n projects: input.projects.map((p) => ({\n project: p.project,\n mailbox: p.mailbox,\n delivery: {\n maxSeq: p.mailbox.maxSeq,\n lastAckedSeq: p.lastAckedSeq,\n behind: Math.max(0, p.mailbox.maxSeq - p.lastAckedSeq),\n },\n store: { byState: p.storeByState, corruptCount: p.corruptCount },\n deferral: p.deferral ?? { maxDeferCount: 0, stuck: false },\n })),\n results: input.results,\n },\n };\n}\n","// src/squadrantd.ts — host: constructs concrete drivers + thin shim.\n// All daemon logic lives in daemon/start.ts; this file owns only the\n// concrete class instantiation and the launchd entry guard.\nimport { join, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { fileURLToPath } from \"node:url\";\nimport { readFileSync, statSync } from \"node:fs\";\nimport { buildContext } from \"@squadrant/core\";\nimport { createAttach } from \"@squadrant/core\";\nimport { startDaemon } from \"@squadrant/core\";\nimport { isDaemonSocketLive } from \"@squadrant/core\";\nimport { appendCaptainMessage, createTelegramClient, createTelegramBridge, createEnsureCaptainAlive } from \"@squadrant/core\";\nimport { reduceLifecycle } from \"@squadrant/core\";\nimport type { TelegramBridge } from \"@squadrant/core\";\nimport type { LifecycleSnapshot, LifecycleSourceDeps } from \"@squadrant/core\";\nimport type { TelegramConfig } from \"@squadrant/shared\";\nimport { createRunCommand, createIsCaptainAlive, createLaunch } from \"@squadrant/core\";\nimport { buildCompletionProtocol } from \"@squadrant/core\";\nexport type { SquadrantdOpts } from \"@squadrant/core\";\nexport { defaultIsPidAlive } from \"@squadrant/core\";\nexport { discoverCaptainSurface } from \"@squadrant/core\";\nimport type { AttachFrame } from \"@squadrant/core\";\nimport type { PaneRef } from \"@squadrant/shared\";\nimport { runHeadless, CodexInteractiveDriver, OpencodeSseBridge, CodexAppServerSource } from \"@squadrant/agents\";\nimport { CmuxEventsBridge, DaemonCmux, CmuxStoreSource, NativeHookSource, resendCrewFirstTurn, RuntimeRegistry } from \"@squadrant/workspaces\";\nimport { loadConfig, TERMINAL_STATES } from \"@squadrant/shared\";\nimport { createCmuxDriver } from \"@squadrant/workspaces\";\nimport { createCmuxNotifier, NotifierRegistry } from \"@squadrant/workspaces\";\nimport { maybeBroadcastDaemonRestart } from \"./lib/daemon-restart-broadcast.js\";\n\nconst SELF_PATH = fileURLToPath(import.meta.url);\n// Bundled CLI bin sits next to this daemon entry (dist/index.js · dist/squadrantd.js).\n// Dist-relative + invariant to source moves (see learning #363). Run via\n// `process.execPath <CLI_BIN> ...argv` so we don't depend on PATH (launchd's is minimal).\nconst CLI_BIN = join(dirname(SELF_PATH), \"index.js\");\nconst DAEMON_SOCK = join(homedir(), \".config\", \"squadrant\", \"squadrant.sock\");\nfunction readPkgVersion(): string {\n try {\n const pkgPath = join(dirname(SELF_PATH), \"..\", \"package.json\");\n return (JSON.parse(readFileSync(pkgPath, \"utf-8\")).version as string) ?? \"unknown\";\n } catch { return \"unknown\"; }\n}\nconst PKG_VERSION = readPkgVersion();\n\nexport type ListSurfacesFn = (wsId: string) => Promise<PaneRef[]>;\n\n/** Construct the real Telegram bridge over a fetch-based client. Token comes from\n * config or the TELEGRAM_BOT_TOKEN env var; with neither, the bridge is disabled. */\nfunction buildTelegramBridge(\n cfg: TelegramConfig,\n stateRoot: string,\n log: (m: string) => void,\n): TelegramBridge | undefined {\n const token = cfg.botToken ?? process.env.TELEGRAM_BOT_TOKEN;\n if (!token) {\n log(\"telegram: config present but no botToken / TELEGRAM_BOT_TOKEN set — bridge disabled\");\n return undefined;\n }\n const client = createTelegramClient({ token });\n // Control surfaces (#402/#403). These act only when remoteControl is on AND the\n // sender is allowlisted (gated inside the bridge); passing them is always safe.\n const ensureCaptainAlive = createEnsureCaptainAlive({\n isAlive: createIsCaptainAlive(DAEMON_SOCK),\n launch: createLaunch(CLI_BIN, log),\n });\n const runCommand = createRunCommand(CLI_BIN);\n const sendReply = (threadId: number | undefined, text: string, replyMarkup?: unknown) =>\n client.sendMessage(cfg.supergroupId, threadId, text, replyMarkup);\n return createTelegramBridge({\n cfg, stateRoot, configRoot: dirname(stateRoot), client, appendCaptainMessage, log,\n ensureCaptainAlive, runCommand, sendReply,\n });\n}\n\n/** Construct the real out-of-band fault-alert channel (#579/#484 Gap 1) via the\n * notifier plugin slot — cmux by default (@squadrant/workspaces), or whichever\n * provider `config.notifier` names, so this works with ZERO extra config for\n * the vast majority of installs (cmux is squadrant's own runtime, not an\n * opt-in integration like Telegram). Best-effort: a notify failure is logged,\n * never thrown into the daemon's delivery loop. */\nfunction buildNotifyFault(log: (m: string) => void): (project: string, text: string) => Promise<void> {\n const registry = new NotifierRegistry({ cmux: createCmuxNotifier });\n return async (project: string, text: string) => {\n try {\n await registry.get(loadConfig()).notify(`[${project}] ${text}`);\n } catch (e) {\n log(`fault notify failed project=${project}: ${(e as Error).message}`);\n }\n };\n}\n\nexport function startSquadrantd(opts: import(\"@squadrant/core\").SquadrantdOpts = {}) {\n const ctx = buildContext(opts);\n const { stateRoot, store, log, spawn, writeResult, inFlightHeadlessIds, activeHeadlessKills } = ctx;\n\n const { broadcast, schedulePromotion, cancelPromotionsFor } = createAttach(ctx);\n ctx.broadcast = broadcast;\n ctx.schedulePromotion = schedulePromotion;\n ctx.cancelPromotionsFor = cancelPromotionsFor;\n\n // ── Concrete driver construction ──────────────────────────────────────────\n // Emit callbacks close over ctx lazily: ctx.d, ctx.broadcast, and\n // ctx.schedulePromotion are late-bound by startDaemon before any emit fires.\n\n // D5: codex app-server LifecycleSource — must be created before codexDriver\n // so the emit closure can call observe(). start() is called in the VITEST-\n // guarded block below after startDaemon() sets ctx.d.\n const codexAppServerSource = new CodexAppServerSource();\n\n const codexDriver = opts.codexDriver ?? new CodexInteractiveDriver({\n emit: (ev) => {\n const found = ctx.store.listAll().find((r) => r.id === ev.id);\n if (!found) return;\n void ctx.d.handle({ kind: \"event\", project: found.project, event: ev });\n if (ev.type === \"task.delta\")\n ctx.broadcast(ev.id, { type: \"delta\", taskId: ev.id, text: ev.chunk } as AttachFrame);\n else if (ev.type === \"task.turn.started\")\n ctx.broadcast(ev.id, { type: \"turn-started\", taskId: ev.id } as AttachFrame);\n else if (ev.type === \"task.turn.completed\")\n ctx.broadcast(ev.id, { type: \"turn-completed\", taskId: ev.id } as AttachFrame);\n else if (ev.type === \"task.input.requested\") {\n ctx.broadcast(ev.id, { type: \"input-requested\", taskId: ev.id, requestId: ev.requestId, question: ev.question } as AttachFrame);\n ctx.schedulePromotion(ev.id, ev.requestId, \"input\", ev.question);\n } else if (ev.type === \"task.approval.requested\") {\n ctx.broadcast(ev.id, { type: \"approval-requested\", taskId: ev.id, requestId: ev.requestId, question: ev.question, kind: ev.kind } as AttachFrame);\n ctx.schedulePromotion(ev.id, ev.requestId, \"approval\", ev.question);\n } else if (ev.type === \"task.reattached\")\n ctx.broadcast(ev.id, { type: \"reattached\", taskId: ev.id } as AttachFrame);\n codexAppServerSource.observe(ev);\n },\n });\n\n const opencodeBridge = opts.opencodeBridge ?? new OpencodeSseBridge({\n emit: (ev) => {\n const found = store.listAll().find((r) => r.id === ev.id);\n if (!found) return;\n void ctx.d.handle({ kind: \"event\", project: found.project, event: ev });\n if (ev.type === \"task.approval.requested\")\n ctx.schedulePromotion(ev.id, ev.requestId, \"approval\", ev.question);\n },\n log,\n });\n\n const cmuxEventsBridge = opts.cmuxEventsBridge ?? new CmuxEventsBridge({\n emit: (ev) => {\n const found = store.listAll().find((r) => r.id === ev.id);\n if (!found) return;\n void ctx.d.handle({ kind: \"event\", project: found.project, event: ev });\n },\n resolve: (hook) => {\n if (!hook.cwd) return undefined;\n return store.listAll().find(\n (r) => r.mode === \"interactive\" && !TERMINAL_STATES.has(r.state) && r.cwd === hook.cwd,\n );\n },\n cursorFile: join(stateRoot, \"cmux-events.seq\"),\n log,\n });\n\n const cmuxStoreSource = new CmuxStoreSource({ log });\n // #615: opt-in env overlay — defaults.claudeEnv deep-merges into ~/.claude/settings.json\n // 'env' (e.g. AFK timeouts). Absent ⇒ installClaudeHooks writes nothing to env.\n const nativeHookSource = new NativeHookSource({ log, hookInstall: { claudeEnv: loadConfig().defaults.claudeEnv } });\n\n ctx.codexDriver = codexDriver;\n ctx.opencodeBridge = opencodeBridge;\n ctx.cmuxEventsBridge = cmuxEventsBridge;\n // B4: register for per-source health aggregation in the snapshot. Registering\n // is inert (no I/O) — only start() below (VITEST-guarded) actually runs a\n // source, so health() correctly reports inactive until then.\n ctx.lifecycleSources = [cmuxStoreSource, nativeHookSource, codexAppServerSource];\n\n // ── Telegram bridge (opt-in #65) ──────────────────────────────────────────\n // Built only when config.telegram is present. Skipped under vitest because the\n // bridge's pushLifecycle is composed onto notify and would hit the network;\n // tests inject opts.telegramBridge instead.\n const tgCfg = loadConfig().telegram;\n ctx.telegramBridge = opts.telegramBridge\n ?? (tgCfg && !process.env.VITEST ? buildTelegramBridge(tgCfg, stateRoot, log) : undefined);\n\n // ── Out-of-band fault-alert channel (#579/#484 Gap 1) ─────────────────────\n // Skipped under vitest (would shell out to the real `squadrant` CLI); tests\n // inject opts.notifyFault, or fall back to buildContext()'s no-op default.\n if (opts.notifyFault) ctx.notifyFault = opts.notifyFault;\n else if (!process.env.VITEST) ctx.notifyFault = buildNotifyFault(log);\n\n // ── daemonCmux resolution ─────────────────────────────────────────────────\n ctx.daemonCmux = opts.daemonCmux\n ?? (opts.makeDaemonCmux ?? (() => new DaemonCmux(createCmuxDriver())))();\n\n // ── #466 self-heal: first-turn resend wiring ──────────────────────────────\n // Uses a fresh cmux RuntimeDriver (independent of daemonCmux's narrower\n // DaemonSurfaceDriver seam, which lacks the paste/sendKey primitives) to\n // drive the same paste-settle-Enter delivery path a manual `crew send` uses.\n // Scoped to claude crews — the facet #466's frozen frame confirmed; other\n // providers safely report non-delivery (the daemon's sweep loop still alerts\n // via CREW UNDELIVERED rather than silently retrying forever).\n const resendRuntime = createCmuxDriver();\n ctx.resendFirstTurn = opts.resendFirstTurn ?? (async (rec) => {\n if (rec.provider !== \"claude\" || !rec.name) return { delivered: false };\n const proj = loadConfig().projects[rec.project];\n const captainName = proj?.captainName ?? `${rec.project}-captain`;\n const message = `${rec.task}\\n\\n${buildCompletionProtocol(rec.id, rec.project)}`;\n return resendCrewFirstTurn(resendRuntime, captainName, rec.project, rec.name, message);\n });\n\n // ── launchHeadless default ────────────────────────────────────────────────\n // Kept here so this file is the sole importer of headless-launcher (daemon/* can't).\n const launchHeadless = opts.launchHeadless ?? (async (rec) => {\n const ingest = (e: import(\"@squadrant/shared\").ControlEvent) =>\n void ctx.d.handle({ kind: \"event\", project: rec.project, event: e });\n const handle = runHeadless({\n provider: rec.provider, task: rec.task, id: rec.id, sessionId: rec.sessionId,\n cwd: rec.cwd, spawn, emit: ingest, writeResult,\n });\n inFlightHeadlessIds.add(rec.id);\n activeHeadlessKills.add(handle.kill);\n try { await handle.result; } finally {\n inFlightHeadlessIds.delete(rec.id);\n activeHeadlessKills.delete(handle.kill);\n }\n });\n\n const h = startDaemon(ctx, { ...opts, launchHeadless }, PKG_VERSION);\n\n // A1: start cmux store-file backup lifecycle source alongside CmuxEventsBridge (B1).\n // startDaemon() guarantees ctx.d is set before returning. Skipped under vitest\n // (mirrors the B1 guard in start.ts — real fs.watch would touch disk in tests).\n if (!process.env.VITEST) {\n const prevSnaps = new Map<string, LifecycleSnapshot>();\n const storeDeps: LifecycleSourceDeps = {\n resolve: (hint) => {\n if (!hint.cwd && hint.pid == null) return undefined;\n return store.listAll().find(\n (r) => r.mode === \"interactive\" && !TERMINAL_STATES.has(r.state) &&\n (r.cwd === hint.cwd || (hint.pid != null && r.pid === hint.pid)),\n );\n },\n report: (snap) => {\n const found = store.listAll().find((r) => r.id === snap.taskId);\n if (!found) return;\n const prev = prevSnaps.get(snap.taskId);\n const newState = reduceLifecycle(prev, snap);\n const changed = !prev || newState !== prev.state;\n prevSnaps.set(snap.taskId, snap);\n if (!snap.alive) {\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.session.ended\", id: snap.taskId } });\n return;\n }\n if (!changed) return;\n if (newState === \"idle\") {\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.turn.completed\", id: snap.taskId, turnId: \"cmux-store\" } });\n } else if (newState === \"running\") {\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.progress\", id: snap.taskId } });\n } else if (newState === \"needsInput\") {\n const question = snap.detail?.note ?? snap.detail?.reason ?? \"crew awaiting input\";\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.blocked\", id: snap.taskId, reason: \"needsInput\", question } });\n }\n },\n log,\n };\n try { cmuxStoreSource.start(storeDeps); }\n catch (e) { log(`cmux store source start failed: ${(e as Error).message}`); }\n\n // C1: start native hook source (primary LifecycleSource C). Installs squadrant-\n // owned hooks into ~/.claude/settings.json (idempotent, namespaced per D4).\n try { nativeHookSource.install(); }\n catch (e) { log(`native hook install failed: ${(e as Error).message}`); }\n const hookPrevSnaps = new Map<string, LifecycleSnapshot>();\n const hookDeps: LifecycleSourceDeps = {\n resolve: (hint) => {\n if (!hint.cwd && hint.pid == null) return undefined;\n return store.listAll().find(\n (r) => r.mode === \"interactive\" && !TERMINAL_STATES.has(r.state) &&\n (r.cwd === hint.cwd || (hint.pid != null && r.pid === hint.pid)),\n );\n },\n report: (snap) => {\n const found = store.listAll().find((r) => r.id === snap.taskId);\n if (!found) return;\n const prev = hookPrevSnaps.get(snap.taskId);\n const newState = reduceLifecycle(prev, snap);\n const changed = !prev || newState !== prev.state;\n hookPrevSnaps.set(snap.taskId, snap);\n if (!snap.alive) {\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.session.ended\", id: snap.taskId } });\n return;\n }\n if (!changed) return;\n if (newState === \"idle\") {\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.turn.completed\", id: snap.taskId, turnId: \"native-hook\" } });\n } else if (newState === \"running\") {\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.progress\", id: snap.taskId } });\n } else if (newState === \"needsInput\") {\n const question = snap.detail?.note ?? snap.detail?.reason ?? \"crew awaiting input\";\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.blocked\", id: snap.taskId, reason: \"needsInput\", question } });\n }\n },\n log,\n };\n try { nativeHookSource.start(hookDeps); }\n catch (e) { log(`native hook source start failed: ${(e as Error).message}`); }\n\n // D5: start codex app-server lifecycle source. codexAppServerSource.observe()\n // is already wired into the codexDriver emit above; start() connects the deps.\n const codexPrevSnaps = new Map<string, LifecycleSnapshot>();\n const codexSourceDeps: LifecycleSourceDeps = {\n resolve: () => undefined, // taskId comes from ControlEvent.id; resolve() unused\n report: (snap) => {\n const found = store.listAll().find((r) => r.id === snap.taskId);\n if (!found) return;\n const prev = codexPrevSnaps.get(snap.taskId);\n const newState = reduceLifecycle(prev, snap);\n const changed = !prev || newState !== prev.state;\n codexPrevSnaps.set(snap.taskId, snap);\n if (!snap.alive) {\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.session.ended\", id: snap.taskId } });\n return;\n }\n if (!changed) return;\n if (newState === \"idle\") {\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.turn.completed\", id: snap.taskId, turnId: \"codex-appserver\" } });\n } else if (newState === \"running\") {\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.progress\", id: snap.taskId } });\n } else if (newState === \"needsInput\") {\n const question = snap.detail?.note ?? snap.detail?.reason ?? \"crew awaiting input\";\n void ctx.d.handle({ kind: \"event\", project: found.project, event: { type: \"task.blocked\", id: snap.taskId, reason: \"needsInput\", question } });\n }\n },\n log,\n };\n try { codexAppServerSource.start(codexSourceDeps); }\n catch (e) { log(`codex app-server source start failed: ${(e as Error).message}`); }\n }\n\n // Daemon-restart broadcast: notify every running captain that the daemon\n // bounced, but only when the running build actually changed (version bump\n // or local rebuild) — a same-build launchd crash-restart stays silent.\n // Skipped under vitest (touches the real config + cmux driver, mirrors the\n // other real-I/O boot actions guarded the same way above).\n if (!process.env.VITEST) {\n try {\n const buildMtimeMs = statSync(SELF_PATH).mtimeMs;\n const restartConfig = loadConfig();\n const registry = new RuntimeRegistry({ cmux: createCmuxDriver() });\n void maybeBroadcastDaemonRestart({\n version: PKG_VERSION,\n buildMtimeMs,\n stateRoot,\n config: restartConfig,\n driver: registry.global(restartConfig),\n appendCaptainMessage: (project: string, text: string) =>\n appendCaptainMessage({ stateRoot, project, text, source: \"daemon\" }),\n });\n } catch (e) {\n log(`daemon-restart broadcast setup failed: ${(e as Error).message}`);\n }\n }\n\n const origStop = h.stop.bind(h);\n h.stop = async (reason?: string) => {\n try { cmuxStoreSource.stop(); } catch { /* best-effort */ }\n try { nativeHookSource.stop(); } catch { /* best-effort */ }\n try { codexAppServerSource.stop(); } catch { /* best-effort */ }\n return origStop(reason);\n };\n\n return h;\n}\n\n/** Greppable crash marker (#535) — matches the `[squadrantd] <iso> <msg>` shape\n * ctx.log uses, but writes directly since ctx.log doesn't exist until buildContext()\n * runs; a crash before that point must still be diagnosable. */\nfunction logCrashMarker(kind: \"uncaughtException\" | \"unhandledRejection\", err: unknown): void {\n const message = err instanceof Error ? (err.stack ?? err.message) : String(err);\n process.stderr.write(`[squadrantd] ${new Date().toISOString()} ${kind} pid=${process.pid} error=${message}\\n`);\n}\n\n// Executed by launchd (ProgramArguments → this file's compiled .js).\nif (process.argv[1] && process.argv[1].endsWith(\"squadrantd.js\")) {\n // Registered before any boot work so a crash during startup is still logged.\n process.on(\"uncaughtException\", (err) => { logCrashMarker(\"uncaughtException\", err); process.exit(1); });\n process.on(\"unhandledRejection\", (reason) => { logCrashMarker(\"unhandledRejection\", reason); process.exit(1); });\n\n void (async () => {\n // #360 layer 1: this entry takes no CLI flags. A build smoke-test like\n // `node dist/squadrantd.js --help` must NOT boot a daemon — it would hang\n // and steal the shared socket. Print a one-liner and exit.\n const arg = process.argv[2];\n if (arg === \"--help\" || arg === \"-h\" || arg === \"--version\" || arg === \"-v\") {\n process.stdout.write(\"squadrantd: launchd-managed daemon entry (no CLI args). Use `squadrant` for commands.\\n\");\n process.exit(0);\n }\n // #360 layer 2: refuse to start if a live daemon already owns the socket.\n // startServer does unlink-then-bind; without this guard a second invocation\n // unlinks the live socket, orphaning the running daemon on its now-anonymous\n // inode so every new connect() to the path is refused.\n const sock = join(homedir(), \".config\", \"squadrant\", \"squadrant.sock\");\n if (await isDaemonSocketLive(sock)) {\n process.stderr.write(`[squadrantd] refusing to start: a live daemon already owns ${sock}\\n`);\n process.exit(0);\n }\n const h = startSquadrantd({ sweepMs: 30000 });\n // #535: await stop() before exiting — it writes the exit marker and runs\n // teardown (bridges, in-flight headless kills) synchronously-then-async;\n // exiting immediately after firing it (not awaiting) raced process.exit()\n // against that work and silently dropped it every time.\n const shutdown = (signal: \"SIGTERM\" | \"SIGINT\") => { void h.stop(signal).finally(() => process.exit(0)); };\n process.on(\"SIGTERM\", () => shutdown(\"SIGTERM\"));\n process.on(\"SIGINT\", () => shutdown(\"SIGINT\"));\n })();\n}\n","// src/config.ts\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport chalk from \"chalk\";\n\nexport interface ProjectConfig {\n path: string;\n captainName: string;\n spokeVault: string;\n host: string;\n group?: string;\n groupRole?: string;\n runtime?: string;\n workspace?: string;\n /** #246: when false, `squadrant group dispatch` rejects delegations to this\n * project. Defaults to true when absent. */\n acceptDelegations?: boolean;\n}\n\nexport interface PermissionConfig {\n command: string; // permission mode for the command session\n captain: string; // permission mode for captain sessions\n crew?: string; // permission mode for crew sessions (default: acceptEdits)\n // Flexible role->mode map so future roles don't need a type change.\n [role: string]: string | undefined;\n}\n\nexport type ModelAlias = \"opus\" | \"sonnet\" | \"haiku\";\n\nexport interface CrewRoutingRule {\n tier: string;\n match: string;\n agent: string;\n model?: string;\n}\n\nexport interface CrewRoutingConfig {\n rules: CrewRoutingRule[];\n}\n\nexport interface TelegramConfig {\n botToken?: string; // falls back to env TELEGRAM_BOT_TOKEN at read time\n supergroupId: number; // forum supergroup hosting per-project topics\n chats: number[]; // chat_id allowlist (inbound honored only from these)\n users?: number[]; // user-id allowlist for CONTROL actions (#321); empty ⇒ control disabled\n remoteControl?: boolean; // opt-in master switch for auto-launch + general commands (default false)\n pollMs?: number; // getUpdates long-poll cadence (default 1000)\n /** Global notification defaults (per-project override lives in projects/<name>.json). */\n notify?: { active?: boolean; cap?: boolean; crew?: \"all\" | \"alert_only\" | \"done_only\" | \"none\" };\n}\n\nexport interface ModelRoutingConfig {\n command: ModelAlias;\n captain: ModelAlias;\n crew: ModelAlias;\n exploration: ModelAlias;\n review: ModelAlias;\n}\n\nexport interface AgentEntry {\n cli: string;\n driver: string;\n}\n\nexport interface RoleAssignment {\n agent: string;\n model?: string;\n}\n\nexport type RoleConfig = Partial<Record<\"command\" | \"captain\" | \"crew\" | \"exploration\" | \"side\", RoleAssignment>>;\n\nexport interface SquadrantConfig {\n /** Package version that last reconciled this config. Absent on legacy/fresh configs. */\n _squadrantVersion?: string;\n commandName: string;\n hubVault: string;\n projects: Record<string, ProjectConfig>;\n agents?: Record<string, AgentEntry>;\n runtime?: string;\n workspace?: string;\n notifier?: string;\n /** Optional Telegram bridge config. Absent ⇒ the bridge is never constructed\n * (zero behavior change). See docs/superpowers/specs/2026-06-22-telegram-integration-v1-design.md. */\n telegram?: TelegramConfig;\n projection?: {\n targets?: string[];\n };\n delivery?: {\n /** Defer count at which a stuck delivery is flagged on the dashboard (B1). Does NOT\n * force delivery on its own — probing an actively-changing draft is unsafe (#484); only\n * content stability (stableProbePolls) escalates to a probe. Default: 300 (~5min). */\n maxDeferDeliveries?: number;\n /** Consecutive stable-content polls before probing early to avoid a stall (#302). Default: 3 (~3s). */\n stableProbePolls?: number;\n };\n defaults: {\n maxCrew: number;\n worktreeDir: string;\n teammateMode: string;\n permissions: PermissionConfig;\n models?: ModelRoutingConfig;\n roles?: RoleConfig;\n /** #225 hard crew task-timeout ceiling (ms). Default: 8h. */\n taskTimeoutMs?: number;\n /** #275 rule-based crew routing: keyword rules map task text to {agent, model}. Optional — absent = fall through to defaults.roles.crew. */\n crewRouting?: CrewRoutingConfig;\n /** B1: consume cmux's native event stream for crew turn-end (idle) detection\n * alongside the scrape fallback. Default true; set false for scrape-only. */\n cmuxEventsBridge?: boolean;\n /**\n * Audit C2 — agent hibernation (reclaim idle-crew RAM). INTENTIONALLY OFF and\n * INERT: cmux 0.64.16's `cmux agent-hibernation <on|off>` is GLOBAL (app-wide,\n * no per-session/per-workspace scope), so enabling it would also hibernate the\n * CAPTAIN — which must stay responsive for daemon-direct delivery —\n * breaking orchestration. We do NOT call `agent-hibernation on`\n * anywhere; this flag is a documented decision record + a forward hook for when\n * cmux gains crew-only scoping. Until then leave false.\n * See docs/research/2026-06-16-cmux-events-stream.md (C2 finding).\n */\n cmuxAgentHibernation?: boolean;\n /** #317 global crew tokenomics dial. Absent ⇒ \"balance\" (today's behavior).\n * Biases the captain toward stronger (\"max\") or cheaper (\"low\") crew models. */\n effort?: \"max\" | \"balance\" | \"low\";\n /** #536 startup npm-registry update check. Default true (absent ⇒ enabled);\n * set false to opt out. NO_UPDATE_NOTIFIER env var also opts out. */\n updateCheck?: boolean;\n /** #615 opt-in: env vars deep-merged into ~/.claude/settings.json's 'env' block by\n * installClaudeHooks, non-clobbering (a key the user already set is never overwritten).\n * Absent ⇒ nothing written. e.g. { CLAUDE_AFK_TIMEOUT_MS: \"240000\" } for Claude's AFK mode. */\n claudeEnv?: Record<string, string>;\n };\n metrics: {\n enabled: boolean;\n path: string;\n };\n}\n\nconst CONFIG_DIR = path.join(os.homedir(), \".config\", \"squadrant\");\nexport const DEFAULT_CONFIG_PATH = path.join(CONFIG_DIR, \"config.json\");\n\nexport function getDefaultConfig(): SquadrantConfig {\n return {\n commandName: \"\\u{1F3DB}\\u{FE0F} command\",\n hubVault: path.join(os.homedir(), \"squadrant-hub\"),\n projects: {},\n agents: {\n claude: { cli: \"claude\", driver: \"claude\" },\n },\n defaults: {\n maxCrew: 5,\n worktreeDir: \".worktrees\",\n teammateMode: \"in-process\",\n permissions: {\n command: \"auto\",\n captain: \"auto\",\n crew: \"auto\",\n },\n models: {\n command: \"opus\",\n captain: \"opus\",\n crew: \"sonnet\",\n exploration: \"haiku\",\n review: \"opus\",\n },\n roles: {\n command: { agent: \"claude\", model: \"opus\" },\n captain: { agent: \"claude\", model: \"opus\" },\n crew: { agent: \"claude\", model: \"sonnet\" },\n exploration: { agent: \"claude\", model: \"haiku\" },\n side: { agent: \"claude\", model: \"opus\" },\n },\n taskTimeoutMs: 8 * 60 * 60 * 1000,\n cmuxEventsBridge: true,\n // Audit C2: OFF by design — cmux hibernation is global-only and would\n // hibernate the captain. See the field doc above.\n cmuxAgentHibernation: false,\n crewRouting: {\n rules: [\n { tier: \"extreme\", match: \"redesign|architect|rewrite|from scratch|deep reasoning\", agent: \"claude\", model: \"opus\" },\n { tier: \"hard\", match: \"refactor|migrate|implement|feature|daemon|control-plane\", agent: \"claude\", model: \"sonnet\" },\n { tier: \"mobile\", match: \"mobile|ios|swift|android|kotlin|react native\", agent: \"codex\" },\n { tier: \"daily\", match: \"typo|rename|bump|docs|comment|lint|format\", agent: \"opencode\" },\n ],\n },\n },\n metrics: {\n enabled: true,\n path: path.join(CONFIG_DIR, \"metrics.json\"),\n },\n };\n}\n\nexport function loadConfig(configPath = DEFAULT_CONFIG_PATH): SquadrantConfig {\n try {\n const raw = fs.readFileSync(configPath, \"utf-8\");\n const config = JSON.parse(raw) as SquadrantConfig;\n\n // Backward compat: migrate models → roles if roles not set\n if (config.defaults.models && !config.defaults.roles) {\n const m = config.defaults.models;\n config.defaults.roles = {\n command: { agent: \"claude\", model: m.command },\n captain: { agent: \"claude\", model: m.captain },\n crew: { agent: \"claude\", model: m.crew },\n exploration: { agent: \"claude\", model: m.exploration },\n };\n }\n\n // Ensure agents has at least claude\n if (!config.agents) {\n config.agents = { claude: { cli: \"claude\", driver: \"claude\" } };\n }\n\n // #286: backfill crewRouting for configs written before routing existed\n if (!config.defaults.crewRouting) {\n config.defaults.crewRouting = getDefaultConfig().defaults.crewRouting;\n saveConfig(config, configPath);\n console.error(\n chalk.cyan(\n \"⬆ squadrant upgrade: added default crew routing rules to your config (leveled routing now active). \" +\n \"Edit defaults.crewRouting in ~/.config/squadrant/config.json or use the squadrant:add-pick-crew-rule skill.\",\n ),\n );\n }\n\n return config;\n } catch {\n return getDefaultConfig();\n }\n}\n\nexport function saveConfig(\n config: SquadrantConfig,\n configPath = DEFAULT_CONFIG_PATH,\n): void {\n const dir = path.dirname(configPath);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + \"\\n\");\n}\n\nexport function resolveHome(p: string): string {\n return p.startsWith(\"~\") ? p.replace(\"~\", os.homedir()) : p;\n}\n","// Per-project layered config override file. Pure, file-backed, no daemon\n// knowledge. Resolved as built-in → global config.json → projects/<name>.json.\n// See docs/superpowers/specs/2026-06-23-per-project-layered-config-design.md.\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport type { ModelRoutingConfig } from \"./config.js\";\n\nexport type CrewTier = \"all\" | \"alert_only\" | \"done_only\" | \"none\";\n\nexport interface NotifyConfig {\n active: boolean;\n cap: boolean;\n crew: CrewTier;\n}\n\n/** Per-project override layer. Every key optional; mirrors the global settings. */\nexport interface ProjectOverrideConfig {\n telegram?: { notify?: Partial<NotifyConfig> };\n // Reserved future tenants (resolver is already generic; consumers not yet wired):\n effort?: \"max\" | \"balance\" | \"low\";\n models?: Partial<ModelRoutingConfig>;\n}\n\nfunction defaultRoot(): string {\n return path.join(os.homedir(), \".config\", \"squadrant\");\n}\n\nexport function projectConfigPath(name: string, root = defaultRoot()): string {\n return path.join(root, \"projects\", `${name}.json`);\n}\n\nexport function loadProjectOverride(name: string, root = defaultRoot()): ProjectOverrideConfig {\n try {\n return JSON.parse(fs.readFileSync(projectConfigPath(name, root), \"utf-8\")) as ProjectOverrideConfig;\n } catch {\n return {};\n }\n}\n\n/** Deep-merge a generic plain-object tree. Arrays/primitives in `patch` replace. */\nexport function deepMerge<T>(base: T, patch: unknown): T {\n if (patch === null || typeof patch !== \"object\" || Array.isArray(patch)) return (patch as T) ?? base;\n const out: Record<string, unknown> = { ...(base as Record<string, unknown>) };\n for (const [k, v] of Object.entries(patch as Record<string, unknown>)) {\n out[k] = deepMerge(out[k], v);\n }\n return out as T;\n}\n\nexport function saveProjectOverride(name: string, patch: ProjectOverrideConfig, root = defaultRoot()): void {\n const merged = deepMerge(loadProjectOverride(name, root), patch);\n const file = projectConfigPath(name, root);\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, JSON.stringify(merged, null, 2) + \"\\n\");\n}\n\nexport const DEFAULT_NOTIFY: NotifyConfig = { active: false, cap: true, crew: \"alert_only\" };\n\nconst CREW_RANK: Record<CrewTier, number> = { none: 0, done_only: 1, alert_only: 2, all: 3 };\nexport function crewRank(tier: CrewTier): number {\n return CREW_RANK[tier];\n}\n\nexport function isQuieter(\n before: NotifyConfig,\n after: NotifyConfig,\n): { quieter: boolean; dim: \"active\" | \"cap\" | \"crew\" | null } {\n if (before.active && !after.active) return { quieter: true, dim: \"active\" };\n if (before.cap && !after.cap) return { quieter: true, dim: \"cap\" };\n if (crewRank(after.crew) < crewRank(before.crew)) return { quieter: true, dim: \"crew\" };\n return { quieter: false, dim: null };\n}\n\n/** Built-in → global → project, per-key. Does NOT apply live state (bridge's job). */\nexport function resolveNotify(\n globalNotify: Partial<NotifyConfig> | undefined,\n override: ProjectOverrideConfig,\n): NotifyConfig {\n let n: NotifyConfig = { ...DEFAULT_NOTIFY };\n if (globalNotify) n = deepMerge(n, globalNotify);\n if (override.telegram?.notify) n = deepMerge(n, override.telegram.notify);\n return n;\n}\n","// src/control/types.ts\nexport type Provider = \"claude\" | \"opencode\" | \"codex\" | \"gemini\";\nexport type Mode = \"headless\" | \"interactive\";\n\nexport type TaskState =\n | \"submitted\"\n | \"working\"\n | \"blocked\"\n // #599: crew has committed to crew/<name> and is paused awaiting the\n // captain's review verdict (approve → push+PR+done, or feedback → crew\n // send reopens to 'working'). NOT terminal — mirrors 'blocked', but the\n // crew is waiting on a review decision rather than an answer to a question.\n | \"review\"\n | \"done\"\n | \"failed\"\n | \"stalled\"\n | \"awaiting-input\"\n | \"cancelled\";\n\nexport interface DispatchAttempt {\n attemptId: string;\n startedAt: number;\n pid?: number;\n resumeRef?: string; // opaque, hashed-treated, NEVER parsed (orca #1148)\n lastHeartbeatAt: number;\n error?: string;\n exitCode?: number;\n circuitBroken?: boolean;\n}\n\nexport interface Gate {\n gateId: string;\n taskId: string;\n kind: \"input\" | \"approval\";\n question: string;\n state: \"pending\" | \"resolved\" | \"timeout\";\n createdAt: number;\n resolvedBy?: string;\n resolution?: unknown;\n}\n\nexport interface TaskRecord {\n id: string;\n /** Human-readable crew name (e.g. the `--name` arg to `squadrant crew spawn`).\n * Optional for backward-compat with records written before this field\n * existed; relay/daemon fall back to the short id when absent. */\n name?: string;\n project: string;\n provider: Provider;\n mode: Mode;\n state: TaskState;\n task: string; // the dispatched instruction\n sessionId?: string; // provider session id for resume (blocked→reply)\n cwd?: string; // working dir for the spawned headless child (project/worktree); unset → daemon cwd\n pid?: number; // headless child pid (daemon-owned)\n question?: string; // populated when state === \"blocked\"\n /** #599: the crew's own summary carried on `signal review`; populated when\n * state === \"review\". Surfaced in the CREW REVIEW notification. */\n reviewNote?: string;\n error?: string; // populated when state === \"failed\"\n exitCode?: number;\n resultRef?: string; // filesystem path to captured output/artifact\n parseWarning?: boolean; // headless exit 0 but unparseable result\n createdAt: number; // epoch ms\n lastHeartbeat: number; // epoch ms\n lastEvent: string; // last event type applied\n heartbeatBudgetMs: number; // per-task stall threshold\n /** Append-only dispatch attempt history. Current attempt = at(-1). */\n attempts: DispatchAttempt[];\n /** Interactive-codex HITL slice (spec §4.9). */\n gates?: Gate[];\n /** Codex AskForApproval policy forwarded to startThread (interactive only).\n * When set to \"untrusted\", codex requests approval for tool/shell calls,\n * exercising the gate-promotion flow end-to-end. */\n approvalPolicy?: string;\n /** Role-priming content forwarded to startThread's developerInstructions\n * (interactive only). Parity with claude's --append-system-prompt-file:\n * injects crew rules / Karpathy discipline before the first user turn. */\n roleInstructions?: string;\n /** TCP port of an interactive opencode crew's embedded HTTP server\n * (`opencode --port <N>`). The daemon's SSE bridge subscribes to\n * http://127.0.0.1:<serverPort>/event for reliable turn-end detection. */\n serverPort?: number;\n /** #246: cross-project intra-group delegation — set to the origin project's\n * name when this task was dispatched by a sibling captain. When the task\n * settles, the daemon fans the outcome back to originProject's mailbox. */\n originProject?: string;\n /** #354: the tool call currently in flight, if any. Set when a PreToolUse\n * liveness signal arrives (cmux events-bridge carries the tool name); cleared\n * the moment its PostToolUse / next turn boundary arrives. A `working` crew\n * whose pendingTool has been outstanding past TOOL_STALL_BUDGET_MS is treated\n * as hung-on-a-tool (CREW STALLED warn) — distinct from a quiet thinking turn\n * (no pendingTool → CREW QUIET). Auto-clears: the next PostToolUse recovers\n * the record to `working` (state-machine + recoverStall). */\n pendingTool?: { name: string; since: number };\n /** #594a: a registered background Monitor watch, if any. Set when a PreToolUse\n * liveness signal names the `Monitor` tool. Unlike pendingTool, this is NOT\n * cleared by that call's own PostToolUse — Monitor's tool call returns almost\n * immediately after arming the watch, but the watch itself (and its async\n * notifications) keeps running well past that. A crew whose turn genuinely\n * ends (Stop hook) while a Monitor is still armed is not awaiting the\n * captain — it will self-resume on its own notification — so the\n * turn.completed veto treats pendingMonitor the same as pendingTool. Cleared\n * only by a genuine new turn boundary (task.started/blocked/review/\n * turn.started/input-approval-requested), or by the watchdog once it has been\n * outstanding past MONITOR_STALL_BUDGET_MS (treated as abandoned). */\n pendingMonitor?: { since: number };\n /** #466: epoch ms when the spawn path positively confirmed the first turn was\n * delivered (paste rendered in the box → box emptied = submitted). Unset means\n * either the crew was spawned before this field existed, OR delivery was never\n * confirmed. The watchdog uses this to emit CREW UNDELIVERED instead of the\n * misleading \"deep thinking\" message for a crew that never received its task. */\n firstTurnConfirmedAt?: number;\n}\n\nexport type ControlEvent =\n | { type: \"task.started\"; id: string; pid?: number; sessionId?: string }\n | { type: \"task.progress\"; id: string; note?: string; tool?: string }\n | { type: \"heartbeat\"; id: string }\n | { type: \"task.blocked\"; id: string; reason: string; question: string }\n // #599: explicit review-gate checkpoint — parallel to task.blocked/task.done\n // but NOT terminal. Emitted by `squadrant crew signal review` once the crew\n // has committed to crew/<name> and wants the captain to inspect the diff\n // before it is pushed/PR'd. `message` is an optional crew summary (parity\n // with task.done's optional message).\n | { type: \"task.review\"; id: string; message?: string }\n // #605: `source: 'approve'` is the review-gate's distinct terminal channel —\n // stamped only by `squadrant crew approve` (runCrewApprove). reduce() vetoes\n // any OTHER task.done while state === 'review' (a crew's own completion\n // protocol), so the gate can't be bypassed by crew habit; approve's stamped\n // done is the one path the veto lets through.\n | { type: \"task.done\"; id: string; resultRef: string; message?: string; parseWarning?: boolean; source?: \"approve\" }\n | { type: \"task.failed\"; id: string; error: string; exitCode?: number }\n | { type: \"task.session\"; id: string; resumeRef: string }\n | { type: \"task.turn.started\"; id: string; turnId: string }\n | { type: \"task.turn.completed\"; id: string; turnId: string }\n | { type: \"task.delta\"; id: string; turnId: string; chunk: string }\n | { type: \"task.input.requested\"; id: string; requestId: number; question: string }\n | { type: \"task.approval.requested\"; id: string; requestId: number; question: string; kind: string }\n | { type: \"task.reattached\"; id: string }\n // Reopen: the only event allowed to revive a terminal task. Emitted by\n // `squadrant crew send` when the target crew's daemon task is in a terminal\n // state, allowing the next `signal done` to be a real transition.\n | { type: \"task.reopened\"; id: string }\n // Synthetic events: emitted by the daemon (watchdog / reconcile) purely as\n // notify payloads. They are never sent over the wire and the reducer treats\n // them as no-ops; the watchdog has already updated state directly.\n // #354: `tool`/`elapsedMs` are set when the stall is a hung interactive tool\n // call (PreToolUse with no matching PostToolUse past TOOL_STALL_BUDGET_MS),\n // letting the notifier render \"still running {tool} ~{N}min\" instead of the\n // generic headless \"no heartbeat\" message.\n | { type: \"task.stalled\"; id: string; heartbeatBudgetMs: number; tool?: string; elapsedMs?: number }\n // task.idle is the interactive analogue of task.stalled: the watchdog has\n // already moved an idle interactive task to 'awaiting-input', and this carries\n // the accurate (non-alarming) notify payload to the captain.\n | { type: \"task.idle\"; id: string; heartbeatBudgetMs: number }\n // #354: a `working` interactive crew that has been quiet past its heartbeat\n // budget with NO tool in flight — alive but deep-thinking (no hook fires\n // during pure model thinking). Notify-only (reducer no-op): the crew stays\n // `working`, NOT awaiting-input. Real CREW IDLE still comes only from the Stop\n // hook (a genuine turn-end). `quietMs` = how long it has been silent.\n | { type: \"task.quiet\"; id: string; quietMs: number }\n // #225: emitted by the sweep when a task's wall-clock age exceeds the ceiling.\n // Notify-only (detect-first, #77); reducer is a no-op.\n | { type: \"task.timeout\"; id: string; taskTimeoutMs: number }\n | { type: \"task.reconcile-failed\"; id: string; reason: string }\n // Emitted by runCrewClose before closing the pane; transitions a non-terminal\n // task to the absorbing 'cancelled' state. Silent: captain initiated the close\n // so no CREW CANCELLED push is fired (not in ATTENTION_STATES).\n | { type: \"task.cancelled\"; id: string; reason?: string }\n // #466: emitted by runCrewSpawn after positively confirming the first turn was\n // delivered. Stamps firstTurnConfirmedAt on the record so the watchdog can\n // distinguish a quiet-thinking crew from one that never received its task.\n | { type: \"task.first-turn.confirmed\"; id: string }\n // #139: a claude crew's SessionEnd hook fired — the session is GONE. Unlike\n // the other turn-boundary hooks (PostToolUse/SubagentStop = liveness), a dead\n // session must NOT resume 'working' (nothing heartbeats → false CREW STALLED\n // ~budget later). Terminalizes the record to the absorbing 'cancelled' state.\n // Silent (not in ATTENTION_STATES), like task.cancelled.\n | { type: \"task.session.ended\"; id: string };\n\n// 'stalled' is intentionally excluded — recoverable by the watchdog.\n// 'cancelled' is terminal and silent (captain-initiated close).\nexport const TERMINAL_STATES: ReadonlySet<TaskState> = new Set([\n \"done\",\n \"failed\",\n \"cancelled\",\n]);\n","// src/lib/cmux-autoconfig.ts\n//\n// #348 (part of #332): orchestrator for cmux socket auto-config. Ties together\n// the comment-preserving config write, the non-cmux probe, and a SEMI-AUTOMATIC,\n// one-time restart prompt.\n//\n// See docs/specs/2026-06-16-cmux-socket-auth-daemon-direct-design.md §4.3–§4.4.\n//\n// This module decides WHAT to surface (configChanged / verdict / one-time\n// prompt); it does not print or log. The caller — the `squadrant cmux autoconfig`\n// CLI or the daemon-start re-check — renders the result. squadrant NEVER restarts\n// cmux for the user (that disrupts live sessions); we write config and prompt.\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { ensureSocketAutomation } from \"./cmux-config.js\";\nimport { probeCmuxDaemonDirect, type ProbeVerdict } from \"./cmux-probe.js\";\n\n/** One-time prompt marker, alongside the daemon state. */\nexport function defaultStatePath(): string {\n return join(homedir(), \".config\", \"squadrant\", \"state\", \"cmux-autoconfig.json\");\n}\n\nexport interface AutoConfigResult {\n /** Path of the cmux config inspected/written. */\n configPath: string;\n /** True when the cmux config was written this run. */\n configChanged: boolean;\n /** True when socketControlMode was already \"automation\". */\n configAlreadySet: boolean;\n /** Live socket reachability from a non-cmux process. */\n verdict: ProbeVerdict;\n /** Config is in place but the live socket still rejects (cmux restart needed). */\n needsRestart: boolean;\n /** The one-time restart prompt fired this run (false on repeats — no nag). */\n promptedThisRun: boolean;\n}\n\nexport interface AutoConfigOpts {\n configPath?: string;\n statePath?: string;\n /** Injectable for tests. Default = ensureSocketAutomation. */\n ensureConfig?: typeof ensureSocketAutomation;\n /** Injectable for tests. Default = the real orphan probe. */\n probe?: () => Promise<ProbeVerdict>;\n}\n\ninterface PromptState {\n promptedRestart?: boolean;\n}\n\nfunction readState(path: string): PromptState {\n try {\n return JSON.parse(readFileSync(path, \"utf-8\")) as PromptState;\n } catch {\n return {};\n }\n}\n\n/**\n * Idempotent: write the cmux automation config (if needed), probe the live\n * socket, and fire a one-time restart prompt when the socket still rejects.\n *\n * Safe to call on every daemon start — it recovers the \"cmux not running at\n * first write\" edge case (§3.4): the value is already file-managed, so the next\n * start re-probes and daemon-direct activates once cmux is (re)launched.\n */\nexport async function ensureCmuxAutoConfig(opts: AutoConfigOpts = {}): Promise<AutoConfigResult> {\n const statePath = opts.statePath ?? defaultStatePath();\n const ensureConfig = opts.ensureConfig ?? ensureSocketAutomation;\n const probe = opts.probe ?? probeCmuxDaemonDirect;\n\n const cfg = ensureConfig({ path: opts.configPath });\n const verdict = await probe();\n const needsRestart = verdict === \"denied\";\n\n let promptedThisRun = false;\n if (needsRestart) {\n const already = readState(statePath).promptedRestart === true;\n if (!already) {\n mkdirSync(dirname(statePath), { recursive: true });\n writeFileSync(statePath, JSON.stringify({ promptedRestart: true }));\n promptedThisRun = true;\n }\n } else if (verdict === \"reachable\") {\n // Reset the marker so a future regression (e.g. cmux config wiped) re-prompts.\n if (existsSync(statePath)) rmSync(statePath, { force: true });\n }\n\n return {\n configPath: cfg.path,\n configChanged: cfg.changed,\n configAlreadySet: cfg.alreadySet,\n verdict,\n needsRestart,\n promptedThisRun,\n };\n}\n","// src/lib/cmux-config.ts\n//\n// #348 (part of #332): comment-preserving JSONC merge for the cmux control\n// socket auth mode. Writes ONLY `automation.socketControlMode = \"automation\"`\n// into ~/.config/cmux/cmux.json so the launchd squadrant daemon (NOT a cmux\n// descendant) may connect to the cmux control socket for daemon-direct delivery.\n//\n// See docs/specs/2026-06-16-cmux-socket-auth-daemon-direct-design.md §2–§4.1.\n//\n// We use jsonc-parser (modify + applyEdits) rather than JSON.parse/stringify so\n// every existing comment and key in the user's cmux.json survives — cmux itself\n// preserves comments and we must not clobber them.\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { parse, modify, applyEdits } from \"jsonc-parser\";\n\n/** Canonical cmux config path. cmux watches this file live (§3.2). */\nexport function defaultCmuxConfigPath(): string {\n return join(homedir(), \".config\", \"cmux\", \"cmux.json\");\n}\n\nexport const SOCKET_CONTROL_MODE_PATH = [\"automation\", \"socketControlMode\"] as const;\nexport const AUTOMATION_MODE = \"automation\";\n\nexport interface EnsureSocketAutomationResult {\n /** Absolute path written/inspected. */\n path: string;\n /** True when the file was written this call. */\n changed: boolean;\n /** True when socketControlMode was ALREADY \"automation\" (no write needed). */\n alreadySet: boolean;\n}\n\n// Minimal squadrant-managed template used only when cmux.json does not yet exist\n// (clean install before cmux has created its own template). It is a strict\n// subset of cmux's schema, so cmux merges its full template keys on next launch\n// without conflict.\nconst MINIMAL_TEMPLATE = [\n `{`,\n ` // [squadrant] file-managed: allow the launchd squadrant daemon to reach the cmux`,\n ` // control socket for daemon-direct notification delivery (#348/#332).`,\n ` \"automation\": {`,\n ` \"socketControlMode\": \"${AUTOMATION_MODE}\"`,\n ` }`,\n `}`,\n ``,\n].join(\"\\n\");\n\n/**\n * Ensure `automation.socketControlMode = \"automation\"` in the cmux config.\n *\n * Idempotent: a no-op (changed=false) when already set. Comment- and\n * formatting-preserving when adding/overwriting an existing file. Creates a\n * minimal squadrant-managed file when none exists.\n */\nexport function ensureSocketAutomation(\n opts: { path?: string } = {},\n): EnsureSocketAutomationResult {\n const path = opts.path ?? defaultCmuxConfigPath();\n\n if (!existsSync(path)) {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, MINIMAL_TEMPLATE);\n return { path, changed: true, alreadySet: false };\n }\n\n const text = readFileSync(path, \"utf-8\");\n const current = parse(text)?.automation?.socketControlMode;\n if (current === AUTOMATION_MODE) {\n return { path, changed: false, alreadySet: true };\n }\n\n const edits = modify(text, [...SOCKET_CONTROL_MODE_PATH], AUTOMATION_MODE, {\n formattingOptions: { insertSpaces: true, tabSize: 2 },\n });\n writeFileSync(path, applyEdits(text, edits));\n return { path, changed: true, alreadySet: false };\n}\n","// src/lib/cmux-probe.ts\n//\n// #348 (part of #332): the hybrid gate. Answer \"can a NON-cmux process reach the\n// cmux control socket right now?\" — i.e. is daemon-direct delivery viable?\n//\n// See docs/specs/2026-06-16-cmux-socket-auth-daemon-direct-design.md §4.2.\n//\n// FAITHFULNESS: cmuxOnly mode checks the connecting process's parent chain and\n// rejects anything not descended from the cmux app. Prior research was\n// CONTAMINATED because it ran inside a cmux pane and kept cmux ancestry even\n// under `env -i`. A faithful probe MUST run from a process reparented to launchd\n// (PPID ⇒ 1). We achieve that with a launcher→worker double-fork: the launcher\n// exits immediately, orphaning the worker, which waits until process.ppid === 1\n// before touching the socket.\nimport { spawn } from \"node:child_process\";\nimport { mkdtempSync, rmSync, existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { resolveCmuxBin } from \"./cmux-bin.js\";\n\nexport type ProbeVerdict = \"reachable\" | \"denied\" | \"unknown\";\n\nexport interface ProbeRawResult {\n ok: boolean;\n stderr?: string;\n}\n\n// cmux's parentage rejection message (and generic socket permission errors).\nconst DENIED_RE = /access denied|only processes started inside cmux|permission denied/i;\n\n/**\n * Pure. Map a raw probe result to a verdict.\n * - ok ⇒ reachable (daemon-direct viable)\n * - access-denied stderr ⇒ denied (socket still cmuxOnly — restart needed)\n * - anything else ⇒ unknown (fail soft → stay on relay)\n */\nexport function classifyProbe(r: ProbeRawResult): ProbeVerdict {\n if (r.ok) return \"reachable\";\n if (r.stderr && DENIED_RE.test(r.stderr)) return \"denied\";\n return \"unknown\";\n}\n\nexport interface ProbeOpts {\n /** Injectable runner (tests). Default = orphan-escape spawn of a cmux read. */\n run?: () => Promise<ProbeRawResult>;\n /** Overall budget for the orphan probe (default 8s). */\n timeoutMs?: number;\n}\n\n/**\n * Probe whether a non-cmux process can reach the cmux control socket. Never\n * throws — a failed/timed-out probe degrades to \"unknown\" so the caller stays on\n * the zero-setup relay.\n */\nexport async function probeCmuxDaemonDirect(opts: ProbeOpts = {}): Promise<ProbeVerdict> {\n const run = opts.run ?? (() => orphanProbe(opts.timeoutMs ?? 8000));\n try {\n return classifyProbe(await run());\n } catch {\n return \"unknown\";\n }\n}\n\n// The worker script, run via `node`. Two modes in one file:\n// launch: spawn the worker detached, then exit → worker is orphaned to launchd\n// work: wait until PPID===1 (no cmux ancestor), run the cmux read, write JSON\n// argv: [node, script, mode, resultFile, cmuxBin]\nconst WORKER_SRC = `\nimport { spawn, execFileSync } from \"node:child_process\";\nimport { writeFileSync } from \"node:fs\";\nconst [mode, resultFile, cmuxBin] = process.argv.slice(2);\nif (mode === \"launch\") {\n const child = spawn(process.execPath, [process.argv[1], \"work\", resultFile, cmuxBin], {\n detached: true, stdio: \"ignore\",\n });\n child.unref();\n process.exit(0);\n}\n// work mode: wait to be reparented to launchd (PPID 1), then probe.\nconst deadline = Date.now() + 3000;\nwhile (process.ppid !== 1 && Date.now() < deadline) {\n const until = Date.now() + 25;\n while (Date.now() < until) { /* tiny busy wait — no timers in a dying orphan */ }\n}\nlet result;\nif (process.ppid !== 1) {\n result = { ok: false, stderr: \"orphan-timeout\" };\n} else {\n try {\n execFileSync(cmuxBin, [\"workspace\", \"list\", \"--json\"], {\n encoding: \"utf-8\", timeout: 10000, env: { ...process.env, CMUX_QUIET: \"1\" },\n });\n result = { ok: true };\n } catch (e) {\n const stderr = (e && (e.stderr?.toString?.() || e.message)) || \"probe failed\";\n result = { ok: false, stderr };\n }\n}\nwriteFileSync(resultFile, JSON.stringify(result));\n`;\n\n// Default runner: double-fork to a launchd-reparented worker, poll its result.\nasync function orphanProbe(timeoutMs: number): Promise<ProbeRawResult> {\n const dir = mkdtempSync(join(tmpdir(), \"cmux-probe-\"));\n const scriptFile = join(dir, \"probe-worker.mjs\");\n const resultFile = join(dir, \"result.json\");\n writeFileSync(scriptFile, WORKER_SRC);\n\n try {\n const launcher = spawn(\n process.execPath,\n [scriptFile, \"launch\", resultFile, resolveCmuxBin()],\n { detached: true, stdio: \"ignore\" },\n );\n launcher.unref();\n\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (existsSync(resultFile)) {\n try {\n return JSON.parse(readFileSync(resultFile, \"utf-8\")) as ProbeRawResult;\n } catch {\n // partial write — fall through and retry\n }\n }\n await sleep(100);\n }\n return { ok: false, stderr: \"probe-timeout\" };\n } finally {\n rmSync(dir, { recursive: true, force: true });\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n","import { execFileSync } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nlet _cached: string | undefined;\n\nfunction resolveBin(): string {\n // 1. Env var override\n const envBin = process.env.SQUADRANT_CMUX_BIN;\n if (envBin && existsSync(envBin)) return envBin;\n\n // 2. Optional cmuxBin field in config.json\n try {\n const configPath = join(homedir(), \".config\", \"squadrant\", \"config.json\");\n if (existsSync(configPath)) {\n const cfg = JSON.parse(readFileSync(configPath, \"utf-8\"));\n const cfgBin: unknown = cfg.cmuxBin;\n if (typeof cfgBin === \"string\" && existsSync(cfgBin)) return cfgBin;\n }\n } catch { /* config read is best-effort */ }\n\n // 3. PATH lookup\n try {\n const which = execFileSync(\"which\", [\"cmux\"], { encoding: \"utf-8\" }).trim();\n if (which && existsSync(which)) return which;\n } catch { /* not on PATH */ }\n\n // 4. Fallback (backward compat for macOS .app install)\n return \"/Applications/cmux.app/Contents/Resources/bin/cmux\";\n}\n\nexport function resolveCmuxBin(): string {\n return _cached ??= resolveBin();\n}\n\nexport function resetCmuxBinCache(): void {\n _cached = undefined;\n}\n","export type ToolEntry = { min?: string; lastVerified?: string };\n\nexport const compatManifest = {\n tools: {\n cmux: { min: \"0.64.0\", lastVerified: \"0.64.17\" } satisfies ToolEntry,\n claude: { min: \"2.1.32\" } satisfies ToolEntry,\n node: { min: \"18.0.0\", lastVerified: \"24.6.0\" } satisfies ToolEntry,\n // presence-checked; no floor enforced yet\n codex: { lastVerified: \"0.139.0\" } satisfies ToolEntry,\n gemini: { lastVerified: \"0.38.2\" } satisfies ToolEntry,\n opencode: { lastVerified: \"1.17.9\" } satisfies ToolEntry,\n },\n} as const;\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport https from \"node:https\";\nimport type { SquadrantConfig } from \"../config.js\";\n\nexport interface UpdateCheckState {\n lastChecked?: number;\n latestKnown?: string;\n /** Set when the most recent check attempt failed (offline/timeout). Drives a shorter\n * FAILURE_RETRY_MS backoff instead of the full 24h success interval, so an offline\n * machine retries roughly hourly instead of hitting the registry on every invocation. */\n lastCheckFailed?: boolean;\n}\n\nexport interface CheckForUpdateOutcome {\n notice: string | null;\n /** New state to persist, or null when nothing changed (opt-out / cache hit). */\n newState: UpdateCheckState | null;\n}\n\nexport const UPDATE_CHECK_STATE_PATH = path.join(os.homedir(), \".config\", \"squadrant\", \"update-check.json\");\n\nconst REGISTRY_URL = \"https://registry.npmjs.org/squadrant/latest\";\nconst CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;\nconst FAILURE_RETRY_MS = 60 * 60 * 1000;\nconst FETCH_TIMEOUT_MS = 1500;\n\nexport type RegistryRequest = (url: string, timeoutMs: number) => Promise<unknown>;\n\n/**\n * Fetches over node:https rather than global fetch(): a fetch() Promise exposes no\n * handle to detach from the event loop, so a pending request left ref'd would delay\n * process exit by up to timeoutMs on every single invocation of an offline machine.\n * http.ClientRequest itself has no unref() — the socket does, assigned asynchronously\n * via the 'socket' event — so we unref that once it's available. This is Node's\n * documented mechanism for exactly this: it lets the process exit immediately once\n * the CLI's own work is done, dropping the response if it arrives after. That's fine:\n * this check is a best-effort background notice, never something exit should wait on.\n */\nconst requestJson: RegistryRequest = (url, timeoutMs) =>\n new Promise((resolve) => {\n const req = https.get(url, { headers: { \"user-agent\": \"squadrant-update-check\" } }, (res) => {\n if (res.statusCode !== 200) {\n res.resume();\n resolve(null);\n return;\n }\n let body = \"\";\n res.setEncoding(\"utf-8\");\n res.on(\"data\", (chunk) => (body += chunk));\n res.on(\"end\", () => {\n try {\n resolve(JSON.parse(body));\n } catch {\n resolve(null);\n }\n });\n });\n req.on(\"socket\", (socket) => socket.unref());\n req.setTimeout(timeoutMs, () => req.destroy());\n req.on(\"error\", () => resolve(null));\n });\n\nexport function isUpdateCheckDisabled(\n config: Pick<SquadrantConfig, \"defaults\"> | undefined,\n env: NodeJS.ProcessEnv = process.env,\n): boolean {\n if (env.NO_UPDATE_NOTIFIER) return true;\n return config?.defaults?.updateCheck === false;\n}\n\nexport function isCacheStale(\n state: UpdateCheckState | undefined,\n now: number,\n intervalMs = CHECK_INTERVAL_MS,\n failureIntervalMs = FAILURE_RETRY_MS,\n): boolean {\n if (!state?.lastChecked) return true;\n return now - state.lastChecked >= (state.lastCheckFailed ? failureIntervalMs : intervalMs);\n}\n\nexport function isNewerVersion(latest: string, current: string): boolean {\n const parse = (v: string) => v.trim().replace(/^v/, \"\").split(\"-\")[0].split(\".\").map((n) => Number(n) || 0);\n const [la = 0, lb = 0, lc = 0] = parse(latest);\n const [ca = 0, cb = 0, cc = 0] = parse(current);\n if (la !== ca) return la > ca;\n if (lb !== cb) return lb > cb;\n return lc > cc;\n}\n\nexport function formatUpdateNotice(latest: string, current: string): string {\n return `⬆ squadrant ${latest} available (you have ${current}) — npm i -g squadrant@latest`;\n}\n\n/**\n * Queries the npm registry directly (never the `npm view` CDN — see the v0.13.1 incident).\n * Never throws. Races the request against its own unref'd timer, so the *logical* result\n * is always bounded by timeoutMs regardless of how requestFn behaves — real network\n * failures are additionally handled by requestJson's own req.unref()/setTimeout, which\n * guarantees the underlying resource can never hold the process open either.\n */\nexport async function fetchLatestVersion(\n requestFn: RegistryRequest = requestJson,\n timeoutMs: number = FETCH_TIMEOUT_MS,\n): Promise<string | null> {\n const timeout = new Promise<null>((resolve) => {\n const timer = setTimeout(() => resolve(null), timeoutMs);\n timer.unref?.();\n });\n\n const request = (async (): Promise<string | null> => {\n try {\n const data = (await requestFn(REGISTRY_URL, timeoutMs)) as { version?: unknown } | null;\n return typeof data?.version === \"string\" ? data.version : null;\n } catch {\n return null;\n }\n })();\n\n return Promise.race([request, timeout]);\n}\n\n/** Pure decision core: given cache state and an injected request function, decides whether\n * to print a notice and what state to persist. No filesystem access. */\nexport async function checkForUpdate(opts: {\n currentVersion: string;\n state: UpdateCheckState | undefined;\n now: number;\n fetchImpl?: RegistryRequest;\n intervalMs?: number;\n failureIntervalMs?: number;\n timeoutMs?: number;\n}): Promise<CheckForUpdateOutcome> {\n if (!isCacheStale(opts.state, opts.now, opts.intervalMs, opts.failureIntervalMs)) {\n const latest = opts.state?.latestKnown;\n const notice = latest && isNewerVersion(latest, opts.currentVersion) ? formatUpdateNotice(latest, opts.currentVersion) : null;\n return { notice, newState: null };\n }\n\n const latest = await fetchLatestVersion(opts.fetchImpl, opts.timeoutMs);\n if (!latest) return { notice: null, newState: { lastChecked: opts.now, lastCheckFailed: true } };\n\n const newState: UpdateCheckState = { lastChecked: opts.now, latestKnown: latest, lastCheckFailed: false };\n const notice = isNewerVersion(latest, opts.currentVersion) ? formatUpdateNotice(latest, opts.currentVersion) : null;\n return { notice, newState };\n}\n\nexport function readUpdateCheckState(statePath: string = UPDATE_CHECK_STATE_PATH): UpdateCheckState | undefined {\n try {\n return JSON.parse(fs.readFileSync(statePath, \"utf-8\"));\n } catch {\n return undefined;\n }\n}\n\nexport function writeUpdateCheckState(state: UpdateCheckState, statePath: string = UPDATE_CHECK_STATE_PATH): void {\n try {\n fs.mkdirSync(path.dirname(statePath), { recursive: true });\n fs.writeFileSync(statePath, JSON.stringify(state, null, 2) + \"\\n\");\n } catch {\n // best-effort cache; a failed write just means we check again next run\n }\n}\n\n/**\n * CLI entrypoint wiring: opt-out check, cache read, decision, cache write, notice print —\n * all in one best-effort call that never throws. Not awaiting this at the call site keeps\n * it off the command's own logic; the unref'd transport (see requestJson) and the bounded\n * race in fetchLatestVersion mean a pending check can't delay process exit either, and a\n * failed attempt is cached (see isCacheStale's failureIntervalMs) so an offline machine\n * doesn't retry the registry on every single invocation.\n */\nexport async function notifyIfUpdateAvailable(opts: {\n config: Pick<SquadrantConfig, \"defaults\"> | undefined;\n currentVersion: string;\n env?: NodeJS.ProcessEnv;\n fetchImpl?: RegistryRequest;\n statePath?: string;\n readState?: (statePath: string) => UpdateCheckState | undefined;\n writeState?: (state: UpdateCheckState, statePath: string) => void;\n write?: (line: string) => void;\n now?: number;\n}): Promise<void> {\n try {\n const env = opts.env ?? process.env;\n if (isUpdateCheckDisabled(opts.config, env)) return;\n\n const statePath = opts.statePath ?? UPDATE_CHECK_STATE_PATH;\n const readState = opts.readState ?? readUpdateCheckState;\n const writeState = opts.writeState ?? writeUpdateCheckState;\n const write = opts.write ?? ((line: string) => process.stderr.write(`\\n${line}\\n`));\n\n const outcome = await checkForUpdate({\n currentVersion: opts.currentVersion,\n state: readState(statePath),\n now: opts.now ?? Date.now(),\n fetchImpl: opts.fetchImpl,\n });\n\n if (outcome.newState) writeState(outcome.newState, statePath);\n if (outcome.notice) write(outcome.notice);\n } catch {\n // update notifications are best-effort and must never affect the CLI\n }\n}\n","// src/lib/git-worktree.ts\n//\n// Per-crew git worktree isolation (#216). A FEATURE crew (spawned with\n// `--worktree`) runs in its own worktree + branch so it can switch HEAD without\n// dragging the captain's checkout. Small/one-off crews keep running on the\n// shared root checkout (unchanged default). The side-effecting `git worktree`\n// calls live here so crew.ts stays mockable and surgical — same seam pattern as\n// per-crew-settings.ts.\n//\n// Builds and the daemon still run from the MAIN checkout's `dist`; worktrees\n// edit source only (issue #216 caveat).\nimport { execFileSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport interface WorktreeSpec {\n /** The project's root checkout (also the shared `.git` owner). */\n repoRoot: string;\n /** Config.defaults.worktreeDir, resolved relative to repoRoot (e.g. \".worktrees\"). */\n worktreeDir: string;\n project: string;\n /** Crew name (e.g. \"crew-1\" or a --name value). */\n name: string;\n /** Branch to base the new crew branch on (GitFlow: \"develop\"). */\n base: string;\n}\n\n/** Deterministic worktree path: <repoRoot>/<worktreeDir>/<project>-<name>. */\nexport function worktreePath(repoRoot: string, worktreeDir: string, project: string, name: string): string {\n return path.resolve(repoRoot, worktreeDir, `${project}-${name}`);\n}\n\n/** Crew branch name for a worktree crew. */\nexport function crewBranch(name: string): string {\n return `crew/${name}`;\n}\n\n/**\n * #387: macOS Spotlight (mds/mdworker) indexing every crew worktree's\n * node_modules can itself starve CPU. A `.metadata_never_index` marker file\n * excludes its directory (recursively, including subdirectories created\n * later) from indexing. Dropping ONE marker in the worktree ROOT (once, the\n * first time a project spawns a worktree crew) covers every crew worktree\n * ever created under it after — no per-worktree marker needed. Best-effort\n * and macOS-only: never blocks worktree creation over an indexing nicety.\n */\nfunction ensureSpotlightExcluded(repoRoot: string, worktreeDir: string): void {\n if (process.platform !== \"darwin\") return;\n try {\n const dir = path.resolve(repoRoot, worktreeDir);\n fs.mkdirSync(dir, { recursive: true });\n const marker = path.join(dir, \".metadata_never_index\");\n if (!fs.existsSync(marker)) fs.writeFileSync(marker, \"\");\n } catch {\n // Best-effort — Spotlight exclusion is a nicety, not a correctness requirement.\n }\n}\n\n// #359: derive the branch a new worktree should be based on. Reads origin/HEAD\n// so main-based repos work without a hand-created `develop`. Falls back to\n// `fallback` (default \"develop\") when origin/HEAD is unset.\nexport function resolveWorktreeBase(repoRoot: string, fallback = \"develop\"): string {\n try {\n const ref = execFileSync(\n \"git\",\n [\"-C\", repoRoot, \"symbolic-ref\", \"refs/remotes/origin/HEAD\"],\n { stdio: [\"ignore\", \"pipe\", \"ignore\"] },\n ).toString().trim();\n const m = ref.match(/^refs\\/remotes\\/origin\\/(.+)$/);\n if (m) return m[1];\n } catch {\n return fallback;\n }\n return fallback;\n}\n\n/**\n * Create the crew's worktree + branch and return its absolute path.\n * Handles a stale crew/<name> branch left by a previously-closed crew (#460):\n * - No existing branch → unchanged behavior.\n * - Existing branch with no unique commits (merged/empty) → delete and recreate fresh.\n * - Existing branch with unique commits → uniquify to crew/<name>-2, -3, … so no\n * commits are lost and there is no collision. The returned path reflects the\n * uniquified name.\n */\nexport function addWorktree(spec: WorktreeSpec): string {\n ensureSpotlightExcluded(spec.repoRoot, spec.worktreeDir);\n\n const originalBranch = crewBranch(spec.name);\n\n let targetName = spec.name;\n let targetBranch = originalBranch;\n\n // Check whether crew/<name> already exists from a prior closed crew.\n let branchExists = false;\n try {\n execFileSync(\n \"git\",\n [\"-C\", spec.repoRoot, \"show-ref\", \"--verify\", \"--quiet\", `refs/heads/${originalBranch}`],\n { stdio: \"pipe\" },\n );\n branchExists = true;\n } catch {\n // Branch does not exist — normal path, nothing to resolve.\n }\n\n if (branchExists) {\n const log = execFileSync(\n \"git\",\n [\"-C\", spec.repoRoot, \"log\", \"--oneline\", `${spec.base}..${originalBranch}`],\n { stdio: [\"ignore\", \"pipe\", \"ignore\"] },\n ).toString().trim();\n\n if (!log) {\n // No unique commits: safe to delete and let the worktree add recreate it.\n execFileSync(\n \"git\",\n [\"-C\", spec.repoRoot, \"branch\", \"-D\", originalBranch],\n { stdio: \"pipe\" },\n );\n } else {\n // Has unique commits: uniquify to crew/<name>-N so history is preserved.\n let suffix = 2;\n while (true) {\n const candidate = `${spec.name}-${suffix}`;\n const candidateBranch = crewBranch(candidate);\n let candidateExists = false;\n try {\n execFileSync(\n \"git\",\n [\"-C\", spec.repoRoot, \"show-ref\", \"--verify\", \"--quiet\", `refs/heads/${candidateBranch}`],\n { stdio: \"pipe\" },\n );\n candidateExists = true;\n } catch {\n // Candidate branch is free.\n }\n if (!candidateExists) {\n targetName = candidate;\n targetBranch = candidateBranch;\n break;\n }\n suffix++;\n }\n }\n }\n\n const wt = worktreePath(spec.repoRoot, spec.worktreeDir, spec.project, targetName);\n execFileSync(\n \"git\",\n [\"-C\", spec.repoRoot, \"worktree\", \"add\", wt, \"-b\", targetBranch, spec.base],\n { stdio: \"pipe\" },\n );\n installWorktreeDependencies(wt);\n return wt;\n}\n\n/**\n * #387: `git worktree add` never populates node_modules — a fresh worktree\n * has none. Node's module resolution walks up parent directories looking for\n * node_modules, and since worktrees live nested under <repoRoot>/<worktreeDir>/,\n * a worktree with no local node_modules silently falls through to the main\n * checkout's node_modules instead of failing — so a crew's tsc/vitest run can\n * type-check or test against the main repo's stale code without any error.\n * Installing here, synchronously, before the worktree is handed to a crew\n * closes that gap: the worktree always has its own complete dependency tree,\n * or worktree creation fails loudly instead of leaving a crew to discover the\n * gap mid-task.\n *\n * addWorktree() is called for every registered project, not just squadrant's\n * own repo — projects use pnpm, yarn, or npm, and some aren't JS projects at\n * all. Detect the package manager from its lockfile rather than assuming\n * pnpm; each is invoked with its own frozen/reproducible-install flag so this\n * never silently drifts the project's lockfile. No package.json → nothing to\n * install, not an error. package.json with no recognized lockfile → skip\n * rather than guess: without a lockfile there's no deterministic manifest to\n * freeze against, and guessing a package manager risks generating a stray\n * lockfile the crew never asked for — but that skip still leaves the worktree\n * without its own node_modules, i.e. still exposed to the exact silent\n * cross-checkout resolution this function exists to close. Warn on stderr so\n * that exposure is visible instead of silent.\n */\nfunction installWorktreeDependencies(wt: string): void {\n if (!fs.existsSync(path.join(wt, \"package.json\"))) return;\n\n if (fs.existsSync(path.join(wt, \"pnpm-lock.yaml\"))) {\n execFileSync(\"pnpm\", [\"-C\", wt, \"install\", \"--frozen-lockfile\"], { stdio: \"pipe\" });\n } else if (fs.existsSync(path.join(wt, \"yarn.lock\"))) {\n execFileSync(\"yarn\", [\"install\", \"--frozen-lockfile\"], { cwd: wt, stdio: \"pipe\" });\n } else if (fs.existsSync(path.join(wt, \"package-lock.json\"))) {\n execFileSync(\"npm\", [\"ci\"], { cwd: wt, stdio: \"pipe\" });\n } else if (fs.existsSync(path.join(wt, \"bun.lockb\"))) {\n execFileSync(\"bun\", [\"install\", \"--frozen-lockfile\"], { cwd: wt, stdio: \"pipe\" });\n } else {\n process.stderr.write(\n `worktree ${wt}: package.json present but no lockfile — dependencies not installed; local typechecks/tests may resolve against the main checkout instead of this worktree.\\n`,\n );\n }\n}\n\n/**\n * Remove a crew's worktree (auto-clean on close). Tries a plain remove first;\n * a dirty/locked worktree makes git refuse, so we retry with --force. The\n * branch is left intact so the crew's commits survive the close.\n */\nexport function removeWorktree(repoRoot: string, wtPath: string): void {\n try {\n execFileSync(\"git\", [\"-C\", repoRoot, \"worktree\", \"remove\", wtPath], { stdio: \"pipe\" });\n } catch {\n execFileSync(\"git\", [\"-C\", repoRoot, \"worktree\", \"remove\", \"--force\", wtPath], { stdio: \"pipe\" });\n }\n}\n","import fs from \"node:fs\";\n\nasync function readAllStdin(): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(Buffer.from(chunk));\n }\n return Buffer.concat(chunks).toString(\"utf-8\");\n}\n\nfunction flagName(label: string): string {\n return label === \"task\" ? \"--task-file\" : \"--message-file\";\n}\n\nexport interface ResolveTextInputOpts {\n positional?: string;\n filePath?: string;\n label: string;\n}\n\nexport interface ResolveTextInputDeps {\n readFile?: (path: string) => string;\n readStdin?: () => Promise<string>;\n}\n\nexport async function resolveTextInput(\n opts: ResolveTextInputOpts,\n deps?: ResolveTextInputDeps,\n): Promise<string> {\n const readFile = deps?.readFile ?? ((p: string) => fs.readFileSync(p, \"utf8\"));\n const readStdin = deps?.readStdin ?? readAllStdin;\n\n if (opts.filePath) {\n if (opts.filePath === \"-\") {\n return readStdin();\n }\n try {\n return readFile(opts.filePath);\n } catch (e) {\n const err = e as NodeJS.ErrnoException;\n const flag = flagName(opts.label);\n if (err.code === \"ENOENT\") {\n throw new Error(`${flag} '${opts.filePath}': file not found`);\n }\n throw new Error(`${flag} '${opts.filePath}': ${err.message}`);\n }\n }\n\n if (opts.positional === undefined) {\n throw new Error(\n `No ${opts.label} provided. Provide a positional argument or use ${flagName(opts.label)}.`,\n );\n }\n\n return opts.positional;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Copy `src` → `dest` only if `dest` is missing or its bytes differ. Content\n * comparison (not size+mtime) makes this both correct — a same-size edit is\n * always detected — and idempotent: an unchanged file is never rewritten, so\n * there is no mtime churn across runs. Managed files are small; reading them\n * per invocation is sub-millisecond. Returns true if a copy happened.\n */\nfunction copyIfDifferent(src: string, dest: string): boolean {\n if (fs.existsSync(dest)) {\n if (fs.readFileSync(src).equals(fs.readFileSync(dest))) return false;\n }\n fs.copyFileSync(src, dest);\n return true;\n}\n\n/**\n * Mirror `src` into `dest`: recursively copy new/changed files AND prune any\n * dest entry that no longer exists in src. Idempotent — unchanged files are\n * left untouched. After this returns, `dest` is a structural copy of `src`.\n * Caller is responsible for only pointing this at source-managed trees — it\n * WILL delete dest entries absent from src.\n */\nexport function mirrorDir(src: string, dest: string): void {\n fs.mkdirSync(dest, { recursive: true });\n\n const srcEntries = fs.readdirSync(src, { withFileTypes: true });\n const srcNames = new Set(srcEntries.map((e) => e.name));\n\n for (const entry of srcEntries) {\n const srcPath = path.join(src, entry.name);\n const destPath = path.join(dest, entry.name);\n if (entry.isDirectory()) {\n mirrorDir(srcPath, destPath);\n } else {\n copyIfDifferent(srcPath, destPath);\n }\n }\n\n for (const entry of fs.readdirSync(dest, { withFileTypes: true })) {\n if (!srcNames.has(entry.name)) {\n fs.rmSync(path.join(dest, entry.name), { recursive: true, force: true });\n }\n }\n}\n\n/**\n * Copy the top-level (non-recursive) files of `src` matching `match` into a\n * flat `dest`, prune dest entries no longer in the matched set, and apply\n * `chmod` to freshly copied files when given. Idempotent — unchanged files\n * are left untouched. For runtime dirs whose source is a differently-named,\n * mixed directory (templates ← templates/, scripts).\n */\nexport function mirrorFlat(\n src: string,\n dest: string,\n match: RegExp,\n chmod?: number,\n): void {\n fs.mkdirSync(dest, { recursive: true });\n\n const matched = fs\n .readdirSync(src, { withFileTypes: true })\n .filter((e) => e.isFile() && match.test(e.name))\n .map((e) => e.name);\n const matchedSet = new Set(matched);\n\n for (const name of matched) {\n const destPath = path.join(dest, name);\n const copied = copyIfDifferent(path.join(src, name), destPath);\n if (copied && chmod !== undefined) fs.chmodSync(destPath, chmod);\n }\n\n for (const entry of fs.readdirSync(dest, { withFileTypes: true })) {\n if (!matchedSet.has(entry.name)) {\n fs.rmSync(path.join(dest, entry.name), { recursive: true, force: true });\n }\n }\n}\n\n/**\n * A source-managed runtime dir. `name` is the dir under the runtime root;\n * `srcRel` is its source dir relative to the package root (note: the\n * runtime `templates/` is sourced from `templates/`).\n */\nexport type ManagedTarget =\n | { name: string; srcRel: string; mode: \"tree\" }\n | {\n name: string;\n srcRel: string;\n mode: \"flat\";\n match: RegExp;\n chmod?: number;\n };\n\nexport const MANAGED_TARGETS: ManagedTarget[] = [\n { name: \"plugin\", srcRel: \"plugin\", mode: \"tree\" },\n { name: \"scripts\", srcRel: \"scripts\", mode: \"flat\", match: /\\.sh$/, chmod: 0o755 },\n {\n name: \"templates\",\n srcRel: \"templates\",\n mode: \"flat\",\n match: /\\.(claude\\.md|generic\\.md|opencode\\.md|CLAUDE\\.md)$/,\n },\n];\n\nexport interface EnsureRuntimeSyncedOptions {\n /** Package root containing the source dirs (`plugin/`, `templates/`, …). */\n sourceRoot: string;\n /** Runtime root, normally ~/.config/squadrant. */\n runtimeRoot: string;\n /** Override the managed-target list (defaults to MANAGED_TARGETS). */\n targets?: ManagedTarget[];\n}\n\n/**\n * Self-heal the runtime copy of source-managed dirs. Every invocation\n * mirrors each managed target (mirrorDir for tree, mirrorFlat for flat) —\n * idempotent copy-if-different + prune, so the runtime is always reconciled\n * to source. There is no cached state: nothing can claim \"synced\" while the\n * dest is actually wrong. Only ever touches the runtime dirs named in the\n * target list — never user/runtime state. Never throws — a sync failure\n * degrades to a stderr warning so the CLI stays usable.\n */\nexport function ensureRuntimeSynced(opts: EnsureRuntimeSyncedOptions): void {\n const targets = opts.targets ?? MANAGED_TARGETS;\n\n for (const t of targets) {\n const srcDir = path.join(opts.sourceRoot, t.srcRel);\n try {\n if (!fs.existsSync(srcDir)) continue;\n const destDir = path.join(opts.runtimeRoot, t.name);\n if (t.mode === \"tree\") {\n mirrorDir(srcDir, destDir);\n } else {\n mirrorFlat(srcDir, destDir, t.match, t.chmod);\n }\n } catch (err) {\n process.stderr.write(\n `squadrant: runtime sync skipped for ${t.name}: ${(err as Error).message}\\n`,\n );\n }\n }\n}\n","type SemVer = [number, number, number];\n\nfunction parseSemVer(v: string): SemVer | null {\n const m = v.match(/(\\d+)\\.(\\d+)\\.(\\d+)/);\n if (!m) return null;\n return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)];\n}\n\nfunction cmpSemVer(a: SemVer, b: SemVer): number {\n for (let i = 0; i < 3; i++) {\n if (a[i] !== b[i]) return a[i] - b[i];\n }\n return 0;\n}\n\n/**\n * Compare an installed tool version against the compat manifest entry.\n * Returns a warning string when the version is below min or above lastVerified,\n * or null when the version is in-range or unparseable (non-blocking).\n * `min` is optional — entries without a floor are only drift-checked against lastVerified.\n */\nexport function checkToolCompat(\n name: string,\n rawVersion: string,\n entry: { min?: string; lastVerified?: string },\n): string | null {\n const installed = parseSemVer(rawVersion);\n if (!installed) return null;\n\n const min = entry.min ? parseSemVer(entry.min) : null;\n if (min && cmpSemVer(installed, min) < 0) {\n return `${name} ${rawVersion} < min ${entry.min} — upgrade to ${entry.min}+`;\n }\n\n if (entry.lastVerified) {\n const lastVerified = parseSemVer(entry.lastVerified);\n if (lastVerified && cmpSemVer(installed, lastVerified) > 0) {\n return `${name} ${rawVersion} > last-verified ${entry.lastVerified} — re-run compat audit`;\n }\n }\n\n return null;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { WorkspaceDriver } from \"../types/workspaces.js\";\nimport type { ProjectionSource } from \"../types/projection.js\";\n\ninterface SkillFrontmatter {\n name: string;\n description: string;\n}\n\nfunction parseSkill(raw: string): { frontmatter: SkillFrontmatter; body: string } | null {\n const match = raw.match(/^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/);\n if (!match) return null;\n const [, fmBlock, body] = match;\n const fm: Partial<SkillFrontmatter> = {};\n for (const line of fmBlock.split(\"\\n\")) {\n const kv = line.match(/^(\\w+):\\s*(.+)$/);\n if (kv) (fm as Record<string, string>)[kv[1]] = kv[2].trim();\n }\n if (!fm.name || !fm.description) return null;\n return { frontmatter: fm as SkillFrontmatter, body: body.trim() };\n}\n\nasync function readSkills(\n driver: WorkspaceDriver,\n skillsDir: string,\n): Promise<ProjectionSource[\"skills\"]> {\n if (!(await driver.exists(skillsDir))) return [];\n const names = await driver.list(skillsDir);\n const skills: ProjectionSource[\"skills\"] = [];\n for (const name of names) {\n const skillPath = `${skillsDir}/${name}/SKILL.md`;\n if (!(await driver.exists(skillPath))) continue;\n const raw = await driver.read(skillPath);\n const parsed = parseSkill(raw);\n if (!parsed) continue;\n skills.push({\n name: parsed.frontmatter.name,\n description: parsed.frontmatter.description,\n content: parsed.body,\n });\n }\n skills.sort((a, b) => a.name.localeCompare(b.name));\n return skills;\n}\n\nexport interface UserSourceOptions {\n pkgRoot?: string;\n readFile?: (p: string) => string;\n}\n\nconst ROLE_TEMPLATES: ReadonlyArray<{ file: string; heading: string }> = [\n { file: \"captain.generic.md\", heading: \"## Captain Role\" },\n { file: \"crew.generic.md\", heading: \"## Crew Role\" },\n];\n\nfunction readRoleTemplates(opts: UserSourceOptions): string {\n if (!opts.pkgRoot) return \"\";\n const reader = opts.readFile ?? ((p: string) => fs.readFileSync(p, \"utf-8\"));\n const sections: string[] = [];\n for (const { file, heading } of ROLE_TEMPLATES) {\n const full = path.join(opts.pkgRoot, \"templates\", file);\n let body = \"\";\n try { body = reader(full); } catch { continue; }\n sections.push(`${heading}\\n\\n${body.trim()}`);\n }\n return sections.join(\"\\n\\n\");\n}\n\nexport async function readUserLevelSource(\n driver: WorkspaceDriver,\n opts: UserSourceOptions = {},\n): Promise<ProjectionSource> {\n const skills = await readSkills(driver, \"plugin/skills\");\n const instructions = readRoleTemplates(opts);\n return { instructions, skills };\n}\n\n// `driver` must be rooted at the project directory itself (createObsidianDriver\n// with root: proj.path). Reading via a driver rooted at process.cwd() — as the\n// projection command previously did — made the sandbox guard reject every\n// managed project living outside the squadrant repo, silently skipping them.\nexport async function readProjectLevelSource(\n driver: WorkspaceDriver,\n): Promise<ProjectionSource | null> {\n if (!(await driver.exists(\"AGENTS.md\"))) return null;\n const instructions = await driver.read(\"AGENTS.md\");\n const skills = await readSkills(driver, \"plugin/skills\");\n return { instructions, skills };\n}\n","import { execSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport matter from \"gray-matter\";\nimport { resolveHome } from \"../config.js\";\nimport type { WorkspaceDriver } from \"../types/workspaces.js\";\n\nexport function iso(d: Date): string {\n return d.toISOString().slice(0, 10);\n}\n\nexport function daysAgo(n: number): Date {\n const d = new Date();\n d.setDate(d.getDate() - n);\n return d;\n}\n\nexport function enumerateDays(from: Date, to: Date): string[] {\n const out: string[] = [];\n const cur = new Date(from);\n cur.setHours(0, 0, 0, 0);\n const end = new Date(to);\n end.setHours(0, 0, 0, 0);\n while (cur <= end) {\n out.push(iso(cur));\n cur.setDate(cur.getDate() + 1);\n }\n return out;\n}\n\nexport interface DailyLog {\n content: string;\n blockers: string[];\n}\n\nexport async function readDailyLog(\n workspace: WorkspaceDriver,\n dateStr: string,\n): Promise<DailyLog | null> {\n const relPath = `daily-logs/${dateStr}.md`;\n if (!(await workspace.exists(relPath))) return null;\n\n const raw = await workspace.read(relPath);\n const { content } = matter(raw);\n\n const blockers: string[] = [];\n const blockerMatch = content.match(/## Blocked\\n([\\s\\S]*?)(?=\\n##|$)/);\n if (blockerMatch) {\n const lines = blockerMatch[1].trim().split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.replace(/^[-*]\\s*/, \"\").trim();\n if (trimmed && trimmed !== \"(none)\" && trimmed !== \"None\") {\n blockers.push(trimmed);\n }\n }\n }\n return { content, blockers };\n}\n\nexport function parseSection(content: string, section: string): string[] {\n const match = content.match(new RegExp(`## ${section}\\\\n([\\\\s\\\\S]*?)(?=\\\\n##|$)`));\n if (!match) return [];\n return match[1]\n .trim()\n .split(\"\\n\")\n .map((l) => l.replace(/^[-*]\\s*/, \"\").trim())\n .filter((l) => l && l !== \"(none)\" && l !== \"None\");\n}\n\nexport function getGitCommits(projectPath: string, dateStr: string): string[] {\n return getGitCommitsInRange(projectPath, `${dateStr} 00:00:00`, `${dateStr} 23:59:59`);\n}\n\nexport function getGitCommitsInRange(projectPath: string, since: string, until?: string): string[] {\n const resolved = resolveHome(projectPath);\n if (!fs.existsSync(path.join(resolved, \".git\"))) return [];\n\n const untilArg = until ? ` --until=\"${until}\"` : \"\";\n try {\n const output = execSync(\n `git -C \"${resolved}\" log --since=\"${since}\"${untilArg} --oneline --no-merges 2>/dev/null`,\n { encoding: \"utf-8\", timeout: 5000 },\n ).trim();\n if (!output) return [];\n return output.split(\"\\n\").map((l) => l.trim()).filter(Boolean);\n } catch {\n return [];\n }\n}\n\nexport function getMergedPRsInRange(projectPath: string, since: string, until?: string): string[] {\n const resolved = resolveHome(projectPath);\n if (!fs.existsSync(path.join(resolved, \".git\"))) return [];\n\n const untilArg = until ? ` --until=\"${until}\"` : \"\";\n try {\n const output = execSync(\n `git -C \"${resolved}\" log --merges --since=\"${since}\"${untilArg} --pretty=format:%s 2>/dev/null`,\n { encoding: \"utf-8\", timeout: 5000 },\n ).trim();\n if (!output) return [];\n return output.split(\"\\n\").map((l) => l.trim()).filter(Boolean);\n } catch {\n return [];\n }\n}\n","// src/control/state-machine.ts\nimport type { ControlEvent, TaskRecord, DispatchAttempt } from \"@squadrant/shared\";\nimport { TERMINAL_STATES } from \"@squadrant/shared\";\n\n/**\n * Pure helper: merges `patch` into the last attempt and updates lastHeartbeatAt.\n * Returns a new TaskRecord; never mutates the input.\n */\nfunction stampAttempt(\n rec: TaskRecord,\n patch: Partial<DispatchAttempt>,\n now: number,\n): TaskRecord {\n const attempts = rec.attempts.slice();\n const last = attempts.at(-1) ?? { attemptId: \"a0\", startedAt: now, lastHeartbeatAt: now };\n attempts[attempts.length === 0 ? 0 : attempts.length - 1] = { ...last, ...patch, lastHeartbeatAt: now };\n if (attempts.length === 0) attempts.push(last);\n return { ...rec, attempts };\n}\n\n/**\n * #608: 'blocked' and 'review' are both attention states that pause a crew\n * pending a human decision — neither may be knocked out by a liveness or\n * turn-boundary event, only by their own explicit exits (reply/feedback or\n * approve). Every stickiness guard below must treat them identically, or a\n * future attention state repeats this bug a fourth time (#492 → #605 → #608\n * → #629, which reused this exact predicate to also exempt the sweep's\n * wall-clock task-timeout ceiling — see reduce.ts).\n */\nexport function isStickyAttention(state: TaskRecord[\"state\"]): boolean {\n return state === \"blocked\" || state === \"review\";\n}\n\n/**\n * #354: compute the next pendingTool marker for a task.progress liveness signal.\n * A PreToolUse (carried from the cmux events-bridge, with the tool name) opens a\n * tool-in-flight window; a PostToolUse or a new UserPromptSubmit closes it. Other\n * liveness notes (subagentstop / notification) leave the marker untouched — they\n * do not bound a tool call. The note strings match the two feeds: the events-bridge\n * emits the raw cmux hook name (\"agent.hook.PreToolUse\"); the claude hook bridge\n * emits the lower-cased event (\"posttooluse\").\n */\nfunction nextPendingTool(\n current: TaskRecord[\"pendingTool\"],\n ev: Extract<ControlEvent, { type: \"task.progress\" }>,\n now: number,\n): TaskRecord[\"pendingTool\"] {\n if (ev.note === \"agent.hook.PreToolUse\") return { name: ev.tool ?? \"tool\", since: now };\n if (ev.note === \"posttooluse\" || ev.note === \"agent.hook.UserPromptSubmit\") return undefined;\n return current;\n}\n\n/**\n * #594a: compute the next pendingMonitor marker. A PreToolUse naming the\n * `Monitor` tool arms a background watch. Unlike pendingTool, this is\n * deliberately NOT closed by that same call's own PostToolUse: Monitor's tool\n * call returns almost immediately after arming (it doesn't block), but the\n * watch — and its async notifications — keeps running well past that. Only a\n * genuine new turn boundary (handled by the reduce() cases that clear it\n * explicitly, mirroring pendingTool) or the watchdog's own stall budget ends\n * the exemption, so a crew whose turn ends while a Monitor is still armed is\n * not misread as \"awaiting the captain\".\n */\nfunction nextPendingMonitor(\n current: TaskRecord[\"pendingMonitor\"],\n ev: Extract<ControlEvent, { type: \"task.progress\" }>,\n now: number,\n): TaskRecord[\"pendingMonitor\"] {\n if (ev.note === \"agent.hook.PreToolUse\" && ev.tool === \"Monitor\") return { since: now };\n return current;\n}\n\n/**\n * Pure transition. `now` is injected (epoch ms) so callers control time.\n * Returns a new record; never mutates the input.\n */\nexport function reduce(rec: TaskRecord, ev: ControlEvent, now: number): TaskRecord {\n // task.reopened is the ONE event allowed to escape a terminal state.\n // From ANY state (done/failed/stalled/awaiting-input/working) → working.\n // Clears question and error so the revived task looks fresh.\n if (ev.type === \"task.reopened\") {\n return { ...rec, state: \"working\", question: undefined, error: undefined, lastHeartbeat: now, lastEvent: ev.type };\n }\n\n // Terminal states are absorbing: ignore any late/duplicate event idempotently.\n if (TERMINAL_STATES.has(rec.state)) return rec;\n\n const base = { ...rec, lastHeartbeat: now, lastEvent: ev.type };\n\n switch (ev.type) {\n case \"task.started\":\n return {\n ...stampAttempt(base, { pid: ev.pid }, now),\n state: \"working\",\n pid: ev.pid ?? rec.pid,\n sessionId: ev.sessionId ?? rec.sessionId,\n question: undefined, // resuming after a blocked→reply clears the question\n pendingTool: undefined, // #354: a new turn closes any prior tool window\n pendingMonitor: undefined, // #594a: same reset — a new turn moots any prior watch\n };\n case \"task.progress\": {\n // task.progress is a real-activity signal (stdout chunk for headless,\n // PreToolUse/PostToolUse/SubagentStop hook for interactive). Stamp the\n // attempt so lastHeartbeatAt stays current and the watchdog stall-check\n // (#89) can key off it without false-stalling long-running headless tasks.\n // #354: also track the in-flight tool (PreToolUse opens, PostToolUse closes)\n // so a hung tool call is distinguishable from a quiet thinking turn.\n // #594a: also track a registered background Monitor watch (PreToolUse\n // opens, but — unlike pendingTool — its own PostToolUse does NOT close it).\n // From blocked: liveness only — do not auto-unblock (explicit reply required).\n // From awaiting-input OR stalled: resume to working — the next real activity\n // (e.g. the matching PostToolUse) auto-clears a hung-tool warn instantly.\n const pendingTool = nextPendingTool(rec.pendingTool, ev, now);\n const pendingMonitor = nextPendingMonitor(rec.pendingMonitor, ev, now);\n if (isStickyAttention(rec.state)) return { ...rec, lastHeartbeat: now, lastEvent: ev.type, pendingTool, pendingMonitor };\n const b = { ...base, pendingTool, pendingMonitor };\n if (rec.state === \"awaiting-input\" || rec.state === \"stalled\") return { ...stampAttempt(b, {}, now), state: \"working\" };\n return stampAttempt(b, {}, now);\n }\n case \"heartbeat\":\n // Raw liveness ping — intentionally does NOT stamp the attempt so a late\n // heartbeat from a dead dispatch cannot mask stalls on the new one (#89).\n // From awaiting-input: resume to working (mirrors task.progress).\n if (isStickyAttention(rec.state)) return { ...rec, lastHeartbeat: now, lastEvent: ev.type };\n if (rec.state === \"awaiting-input\") return { ...base, state: \"working\" };\n return base;\n case \"task.blocked\":\n // ev.reason is protocol/logging-only and intentionally not persisted;\n // only `question` is stored on the record.\n // Idempotency (#174): the explicit `squadrant crew signal blocked` fires\n // BEFORE the turn ends; the auto-detect Stop hook may then re-emit\n // task.blocked on an already-blocked task. Treat a repeat block as a\n // no-op so the FIRST (explicit) question wins and no duplicate CREW\n // BLOCKED fires. Terminal states are already absorbed above.\n if (rec.state === \"blocked\") return { ...rec, lastHeartbeat: now, lastEvent: ev.type };\n return { ...base, state: \"blocked\", question: ev.question, pendingTool: undefined, pendingMonitor: undefined };\n case \"task.review\":\n // #599: review-gate checkpoint. Not terminal — `crew send` (feedback)\n // or `crew approve` (task.done) are the only ways out.\n return { ...base, state: \"review\", reviewNote: ev.message, pendingTool: undefined, pendingMonitor: undefined };\n case \"task.done\":\n // #605: the review gate must be ENFORCING, not advisory. A crew's normal\n // completion protocol always signals done at turn end — if that alone\n // could terminalize a task sitting in 'review', the gate is bypassed by\n // crew habit and `crew approve` becomes unreachable (state is already\n // 'done'). Per #492: gate at the transition, not on crew discipline.\n // Only `squadrant crew approve`'s task.done (source: 'approve') is a\n // distinct terminal channel the veto does not block; any other task.done\n // while in review is liveness-only and the record stays in review.\n if (rec.state === \"review\" && ev.source !== \"approve\") {\n return { ...rec, lastHeartbeat: now, lastEvent: ev.type };\n }\n return { ...base, state: \"done\", resultRef: ev.resultRef, parseWarning: ev.parseWarning };\n case \"task.failed\":\n return { ...base, state: \"failed\", error: ev.error, exitCode: ev.exitCode };\n case \"task.cancelled\":\n return { ...base, state: \"cancelled\" };\n case \"task.session.ended\":\n // #139: the claude crew session ended (SessionEnd hook). The process is\n // gone, so terminalize instead of resuming 'working'. Reuses the silent\n // 'cancelled' state — no alarming push, just a clean terminal record.\n return { ...base, state: \"cancelled\" };\n case \"task.session\":\n return stampAttempt(base, { resumeRef: ev.resumeRef }, now);\n case \"task.turn.started\":\n return { ...stampAttempt(base, {}, now), state: \"working\", pendingTool: undefined, pendingMonitor: undefined };\n case \"task.turn.completed\":\n // Anti-#2576 invariant: TurnCompleted is liveness, NEVER completion. Spec §4.8.\n // A turn ending while blocked must NOT unblock — only the captain's answer\n // (task.started via `crew send`) clears blocked. Mirrors task.progress: the\n // opencode SSE bridge emits task.turn.completed right after an explicit\n // `signal blocked`, and that trailing turn-end must not drop the question.\n // #608: 'review' needs the identical guard — a crew's normal turn-end always\n // fires right after `signal review`, and without this it fell through to\n // 'awaiting-input' below, making `crew approve` unreachable.\n if (isStickyAttention(rec.state)) return { ...rec, lastHeartbeat: now, lastEvent: ev.type };\n // #492: several parallel lifecycle sources (cmux store-file watch, native\n // claude hooks, cmux's forwarded event stream) each independently report\n // turn-end for the same crew. A stale/heuristic report can assert\n // task.turn.completed while a real tool call is still open (pendingTool set\n // from its own PreToolUse) — that directly contradicts the daemon's own\n // evidence (no matching PostToolUse yet), so it is not a genuine turn\n // boundary. Treat it as liveness only; the real turn-end arrives once the\n // tool actually returns and pendingTool clears.\n // #594a: a registered background Monitor (pendingMonitor) gets the same\n // veto. Its own tool call closes almost immediately (pendingTool clears\n // fast), but the watch it armed keeps running — a Stop hook firing while\n // it's still outstanding means the crew is asleep awaiting its OWN\n // notification, not the captain's. Without this, that turn-end reads as\n // genuine and floods CREW IDLE on every self-resume (live evidence: task\n // 2506214d fired CREW IDLE 3x for the same turnId, 23s/76s apart).\n if (rec.pendingTool || rec.pendingMonitor) return stampAttempt(base, {}, now);\n return { ...stampAttempt(base, {}, now), state: \"awaiting-input\", pendingTool: undefined, pendingMonitor: undefined };\n case \"task.delta\":\n return stampAttempt(base, {}, now); // heartbeat-only\n case \"task.input.requested\":\n case \"task.approval.requested\":\n return { ...stampAttempt(base, {}, now), state: \"blocked\", question: ev.question, pendingTool: undefined, pendingMonitor: undefined };\n case \"task.reattached\":\n return stampAttempt(base, {}, now);\n case \"task.first-turn.confirmed\":\n // #466/#470: stamp firstTurnConfirmedAt on the FIRST occurrence only.\n // UserPromptSubmit fires on every prompt submit (incl. captain follow-ups);\n // subsequent events are treated as liveness so the field is never re-stamped.\n if (rec.firstTurnConfirmedAt) {\n return { ...rec, lastEvent: \"task.progress\" };\n }\n return { ...rec, firstTurnConfirmedAt: now, lastEvent: ev.type };\n case \"task.stalled\":\n case \"task.idle\":\n case \"task.quiet\":\n case \"task.timeout\":\n case \"task.reconcile-failed\":\n // Synthetic notify-only events; the daemon has already updated state\n // directly via the watchdog/reconcile paths (task.quiet carries no state\n // change at all — the crew stays `working`). Reducer is a no-op.\n return rec;\n default:\n // #87: unknown/future event type from the wire — safe no-op.\n // The socket boundary (handle()) validates known types before calling\n // reduce; this default is a deep-defense fallback so reduce() can\n // never return undefined regardless of how it is called.\n return rec;\n }\n}\n","// src/control/watchdog.ts\nimport type { TaskRecord } from \"@squadrant/shared\";\n\n/**\n * #354: how long an interactive tool call may be in flight before it is treated\n * as hung. Deliberately generous (much larger than the 5-min heartbeat budget)\n * so a legitimately long tool — a multi-minute test suite, a big build, a slow\n * git/network op — does NOT trip it: those are real, recoverable work, and the\n * matching PostToolUse auto-clears the warn the instant it returns. Only a tool\n * that produces no result for this long is suspicious enough to surface as\n * \"possibly hung\". Default 10 min; tune via evaluateStall's `toolStallMs`.\n */\nexport const TOOL_STALL_BUDGET_MS = 10 * 60 * 1000;\n\n/**\n * #594a: how long a registered background Monitor watch may sit outstanding\n * before the daemon gives up on the exemption and treats it as abandoned.\n * Deliberately generous — well above Monitor's own documented max timeout_ms\n * (1h) for a non-persistent watch — so a legitimate long CI/deploy poll is\n * never false-stalled mid-watch. This is a backstop, not the primary fix: it\n * exists only so a crew that armed a Monitor once and then went genuinely\n * silent forever doesn't suppress real idle detection permanently.\n */\nexport const MONITOR_STALL_BUDGET_MS = 60 * 60 * 1000;\n\n/**\n * Pure. Returns a stalled-transitioned record if a `working` task is genuinely\n * stuck at time `now` (epoch ms), else null. No I/O, no clock. #354 splits the\n * old single wall-clock timeout by what we can actually prove:\n *\n * - headless → 'stalled' once quiet past the heartbeat budget. A batch child\n * that stops emitting stdout is stuck; there is no captain turn to await.\n * - interactive WITH a tool in flight (pendingTool) → 'stalled' once that tool\n * has been outstanding past `toolStallMs`. A PreToolUse with no matching\n * PostToolUse is a hung tool call (we know which tool). Recoverable: the next\n * PostToolUse recovers it to `working` (state-machine / recoverStall).\n * - interactive with NO tool in flight but a Monitor armed (pendingMonitor) →\n * 'stalled' once that watch has been outstanding past `monitorStallMs`\n * (#594a backstop against permanent suppression — see MONITOR_STALL_BUDGET_MS).\n * - interactive with neither in flight → null. A quiet thinking turn is alive,\n * not stalled and NOT awaiting-input (the turn never ended — real CREW IDLE\n * comes only from the Stop hook). The daemon sweep surfaces this as a\n * distinct, non-alarming CREW QUIET notify instead (#354), keeping the crew\n * `working`. This replaces the old wall-clock → 'awaiting-input' flip, which\n * mislabeled deep-thinking crews as \"awaiting your input\".\n *\n * This function never produces `failed` or `awaiting-input` directly.\n */\nexport function evaluateStall(\n rec: TaskRecord,\n now: number,\n toolStallMs: number = TOOL_STALL_BUDGET_MS,\n monitorStallMs: number = MONITOR_STALL_BUDGET_MS,\n): TaskRecord | null {\n if (rec.state !== \"working\") return null;\n if (rec.mode === \"interactive\") {\n // A hung tool call takes priority over a registered Monitor watch.\n if (rec.pendingTool) {\n if (now - rec.pendingTool.since <= toolStallMs) return null;\n return { ...rec, state: \"stalled\", lastEvent: \"watchdog.tool-stall\" };\n }\n if (rec.pendingMonitor) {\n if (now - rec.pendingMonitor.since <= monitorStallMs) return null;\n return { ...rec, state: \"stalled\", lastEvent: \"watchdog.monitor-stall\" };\n }\n // Neither a hung tool nor a Monitor watch — a quiet thinking turn is alive\n // and handled by the sweep's CREW QUIET path.\n return null;\n }\n // headless: key off the latest attempt's lastHeartbeatAt so a stale event from\n // a dead prior attempt cannot refresh the liveness clock of the new dispatch (#89).\n const liveness = rec.attempts.at(-1)?.lastHeartbeatAt ?? rec.lastHeartbeat;\n if (now - liveness <= rec.heartbeatBudgetMs) return null;\n return { ...rec, state: \"stalled\", lastEvent: \"watchdog.stall\" };\n}\n\n/**\n * Pure. A stalled task that receives liveness returns to working.\n *\n * WARNING: this does NOT check heartbeat freshness — it returns a recovered\n * record for ANY stalled task. Callers MUST guard with\n * `now - rec.lastHeartbeat <= rec.heartbeatBudgetMs` before applying the\n * result, or a permanently-stale task will be falsely revived.\n */\nexport function recoverStall(rec: TaskRecord, now: number): TaskRecord | null {\n if (rec.state !== \"stalled\") return null;\n // #354/#594a: clear any hung-tool or stale-Monitor marker on recovery so a\n // recovered crew never carries either into its next quiet window.\n return { ...rec, state: \"working\", lastHeartbeat: now, lastEvent: \"watchdog.recover\", pendingTool: undefined, pendingMonitor: undefined };\n}\n","// src/control/daemon.ts\nimport type { Store } from \"../store.js\";\nimport type { ControlEvent, TaskRecord, TaskState } from \"@squadrant/shared\";\nimport { TERMINAL_STATES } from \"@squadrant/shared\";\nimport { reduce, isStickyAttention } from \"../state-machine.js\";\nimport { evaluateStall, recoverStall } from \"../watchdog.js\";\nexport interface DaemonDeps {\n store: Store;\n now: () => number;\n /** Injected in Task 14; resumes a blocked session. Optional until then. */\n deliverReply?: (rec: TaskRecord, message: string) => Promise<void>;\n /** Defaults to a real process.kill(pid,0) check at the call site (Task 17). */\n isPidAlive?: (pid: number) => boolean;\n /**\n * #139 backstop: the interactive analogue of isPidAlive. Resolves whether an\n * interactive crew's backing cmux surface (pane/tab) still exists. Three-valued\n * so a transient cmux outage never false-reaps a live crew:\n * - \"alive\" → the crew's pane is present; keep watching.\n * - \"gone\" → cmux answered AND the pane is provably absent → terminalize.\n * - \"unknown\" → could not determine (cmux down, no captain, error) → do nothing.\n * Defaults to always-\"unknown\" (never reaps) when not wired — pure unit tests\n * and any non-cmux deployment are unaffected.\n */\n isSurfaceAlive?: (rec: TaskRecord) => Promise<\"alive\" | \"gone\" | \"unknown\">;\n /** Wired in squadrantd to runHeadless; absent in pure unit tests. */\n launchHeadless?: (rec: TaskRecord) => Promise<void>;\n /**\n * #259: true when a launchHeadless call for this task ID is currently in\n * flight (process spawned but no pid yet). reconcile() skips these so a\n * crash-restart re-run does NOT mark an actively-launching task as failed\n * and re-dispatch it, multiplying orphaned headless processes.\n * Defaults to () => false when not wired (pure unit tests, non-headless modes).\n */\n isHeadlessInFlight?: (id: string) => boolean;\n /**\n * Forward hook for the deferred interactive-wiring spec. While absent,\n * interactive dispatch fails LOUD (red-team #4) instead of silently\n * black-holing in `submitted` forever.\n */\n launchInteractive?: (rec: TaskRecord) => Promise<void>;\n /**\n * Wired in squadrantd to codexDriver.answer(). Delivers the captain's gate\n * resolution payload back to the interactive session (spec §4.9).\n */\n resolveInteractiveGate?: (taskId: string, payload: unknown) => Promise<void> | void;\n /**\n * Push notification hook (#109, refactored under mailbox-injector spec).\n * Called on every state transition into {done, blocked, failed, stalled}.\n * Implementations append to the mailbox; errors are caught + swallowed here\n * so an unhealthy notifier never breaks the event-ingest path.\n */\n notify?: (args: {\n project: string;\n message: string;\n record: TaskRecord;\n event: ControlEvent;\n }) => Promise<void> | void;\n /**\n * #225 hard crew task-timeout: wall-clock ceiling in ms. When a non-terminal\n * task's age (now - createdAt) exceeds this, the sweep fires a CREW TIMEOUT\n * escalation via the notify hook. Defaults to DEFAULT_TASK_TIMEOUT_MS (8h).\n * Distinct from the per-task heartbeat budget (stall detection).\n */\n taskTimeoutMs?: number;\n /**\n * #466 self-heal: re-deliver a crew's first turn when the daemon detects it\n * never landed (firstTurnConfirmedAt still absent past firstTurnUndeliveredBudgetMs).\n * MUST re-check TUI/pane readiness itself before sending — never blind-send\n * into a still-booting pane — and return { delivered: true } ONLY on a\n * positively-confirmed submit (mirrors sendFirstTurnWhenReady/confirmedSendToPane).\n * When absent, sweep falls back to the prior alert-only behavior (pure unit\n * tests, non-cmux deployments).\n */\n resendFirstTurn?: (rec: TaskRecord) => Promise<{ delivered: boolean }>;\n /** Override for testing; production default is DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS. */\n firstTurnUndeliveredBudgetMs?: number;\n /** Override for testing; production default is DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS. */\n firstTurnResendCooldownMs?: number;\n}\n\n// #466: dedicated, tight budget for detecting an undelivered first turn —\n// measured from createdAt (monotonic; never reset by heartbeat activity), NOT\n// from lastHeartbeat/heartbeatBudgetMs. The frozen-frame root cause showed\n// heartbeats can keep flowing on a crew whose first turn never landed, which\n// would mask the drop indefinitely under a heartbeat-based gate. The default\n// sits comfortably above crew-pane's own SEND_FIRST_TURN_TIMEOUT_MS (90s) plus\n// its confirmedSendToPane fallback retries (worst case ~100s), so the daemon's\n// resend never races the crew's own in-flight first-turn delivery attempt.\nexport const DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS = 120_000;\n// Minimum gap between resend attempts for the same task — avoids hammering a\n// still-not-ready pane with pastes every sweep tick.\nexport const DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS = 60_000;\n\n// #225 hard crew task-timeout: default wall-clock ceiling (8h). A crew can\n// heartbeat continuously yet be stuck on one task — the stall watchdog won't\n// catch it. This ceiling does. Configurable via DaemonDeps.taskTimeoutMs.\nexport const DEFAULT_TASK_TIMEOUT_MS = 8 * 60 * 60 * 1000;\n\n// #378: GC TTL for terminal records (done/failed/cancelled). Records older\n// than this are pruned from the store during sweep().\nexport const TERMINAL_RECORD_TTL_MS = 7 * 24 * 60 * 60 * 1000;\n\n// #457: Max terminal records to keep per project. Bounds accumulation for\n// short-lived test sessions where many tasks finish before the 7-day TTL.\nexport const TERMINAL_RECORD_KEEP_PER_PROJECT = 20;\n\n// 'awaiting-input' is an attention state: entering it (idle watchdog OR a\n// Stop-hook turn boundary) fires exactly one accurate CREW IDLE push. The\n// firePush prev===next guard keeps it from re-firing while the task sits idle.\n// #599: 'review' joins the attention states — a CREW REVIEW push fires exactly\n// like CREW DONE/BLOCKED, just without terminalizing the task.\nconst ATTENTION_STATES: ReadonlySet<TaskState> = new Set([\"done\", \"blocked\", \"review\", \"failed\", \"stalled\", \"awaiting-input\"]);\n\n// #139: non-terminal, post-launch states an interactive crew can be sitting in\n// while its session has actually died. Any of these with a provably-gone surface\n// is a zombie → reap to 'cancelled'. 'submitted' is excluded: it is pre-launch\n// (no surface yet), so reaping it would race the spawn.\n// #599: 'review' included — a crew that signaled review then crashed/closed\n// must not linger forever awaiting an approval that will never come.\nconst REAPABLE_SURFACE_STATES: ReadonlySet<TaskState> = new Set([\"working\", \"stalled\", \"awaiting-input\", \"blocked\", \"review\"]);\n\n// #210: CREW IDLE (awaiting-input) is debounced — suppressed when the turn-end\n// lands within this window of the captain's own last turn to the crew (a\n// `crew send`/reply emits task.started). This silences the rapid\n// send→respond→turn-end churn of an active back-and-forth while still\n// delivering a genuine self-idle (turn-end / idle-watchdog long after the\n// captain last engaged). Only awaiting-input is debounced; every other\n// attention state always delivers.\nexport const IDLE_DEBOUNCE_MS = 12_000;\n\nfunction shortId(id: string): string {\n return id.slice(0, 8);\n}\n\n/**\n * Build a disambiguated notification tag for a task record. Always appends\n * the short id so reused crew names are distinguishable across distinct ids.\n * Named: [provider/name · shortId] Unnamed: [provider/shortId]\n */\nexport function crewTag(r: TaskRecord): string {\n const suffix = shortId(r.id);\n if (r.name != null) {\n return `[${r.provider}/${r.name} · ${suffix}]`;\n }\n return `[${r.provider}/${suffix}]`;\n}\n\nfunction formatMessage(rec: TaskRecord, event?: ControlEvent): string | null {\n const tag = crewTag(rec);\n switch (rec.state) {\n case \"done\": {\n // Prefer the crew's own done message (`signal done --message`), carried on\n // the task.done event — this is what captains relied on under the old\n // relay formatter and must not regress (#214 unification). The task\n // snippet is the documented fallback when no message was provided.\n const doneMsg = event?.type === \"task.done\" ? event.message : undefined;\n const body =\n doneMsg != null && doneMsg.trim().length > 0\n ? doneMsg.split(/\\r?\\n/)[0].trim().slice(0, 200)\n : ((rec.task ?? \"\").split(/\\r?\\n/)[0]?.trim().slice(0, 120) ?? \"\");\n return `CREW DONE ${tag}: ${body}`;\n }\n case \"blocked\":\n return `CREW BLOCKED ${tag}: ${(rec.question ?? \"(no question)\").trim()}`;\n case \"review\": {\n // #599: crew has committed and is awaiting the captain's review verdict.\n const note = (rec.reviewNote ?? \"\").trim();\n return `CREW REVIEW ${tag}: ${note || \"ready for review\"} — run 'squadrant diff ${rec.project} ${rec.name ?? rec.id}' then 'squadrant crew approve' or send feedback.`;\n }\n case \"failed\":\n return `CREW FAILED ${tag}: ${(rec.error ?? \"(no error)\").trim()}`;\n case \"stalled\": {\n // #354: a hung interactive tool call reads differently from a headless\n // heartbeat stall — name the tool and how long it has been outstanding,\n // and frame it as \"possibly hung\" (recoverable, auto-clears on the tool's\n // PostToolUse), NOT a death notice.\n if (event?.type === \"task.stalled\" && event.tool) {\n const mins = event.elapsedMs != null ? Math.max(1, Math.round(event.elapsedMs / 60000)) : null;\n return `CREW STALLED ${tag}: still running ${event.tool}${mins != null ? ` ~${mins}min` : \"\"} — possibly hung (no result yet).`;\n }\n return `CREW STALLED ${tag}: no heartbeat in ${rec.heartbeatBudgetMs}ms`;\n }\n case \"awaiting-input\":\n // #522: 'awaiting-input' is reached ONLY via a genuine turn-boundary event\n // (task.turn.completed — see state-machine.ts) — there is no separate\n // watchdog-derived path into this state (the old wall-clock idle flip was\n // retired by #354; evaluateStall never produces 'awaiting-input'). So this\n // always means \"the crew deliberately ended its turn\", including the\n // common case of a long-lived crew pausing between sequential subtasks.\n // The former \"review and reply or close\" phrasing read like a possible\n // fault and forced a spot-check every time; state calmly instead.\n return `CREW IDLE ${tag}: turn ended, awaiting your reply.`;\n default:\n return null;\n }\n}\n\n/** #246: cross-project delegation report-back message. Called when a task\n * with originProject settles to a terminal state. Returns a captain-facing\n * one-liner delivered verbatim to the origin project's captain. */\nfunction formatDelegationReport(rec: TaskRecord, originProject: string, targetProject: string): string | null {\n const shortTask = (rec.task ?? \"\").split(/\\r?\\n/)[0]?.trim().slice(0, 120) ?? \"\";\n switch (rec.state) {\n case \"done\":\n return `✅ Cross-project task → ${targetProject}: done — ${shortTask}`;\n case \"blocked\":\n return `⛔ Cross-project task → ${targetProject}: blocked — ${(rec.question ?? \"(no question)\").trim()}`;\n case \"failed\":\n return `⛔ Cross-project task → ${targetProject}: failed — ${(rec.error ?? \"(no error)\").trim()}`;\n case \"stalled\":\n return `⚠️ Cross-project task → ${targetProject}: stalled (no heartbeat in ${rec.heartbeatBudgetMs}ms)`;\n default:\n return null;\n }\n}\n\nfunction firePush(\n deps: DaemonDeps,\n project: string,\n prev: TaskState,\n next: TaskRecord,\n event: ControlEvent,\n lastCaptainTurnAt?: number,\n): void {\n if (!deps.notify) return;\n if (prev === next.state) return;\n if (!ATTENTION_STATES.has(next.state)) return;\n // #210 idle debounce: a turn-end (awaiting-input) within IDLE_DEBOUNCE_MS of\n // the captain's last turn is part of an active back-and-forth — suppress the\n // CREW IDLE. All other attention states are never debounced.\n if (\n next.state === \"awaiting-input\" &&\n lastCaptainTurnAt != null &&\n deps.now() - lastCaptainTurnAt <= IDLE_DEBOUNCE_MS\n ) {\n return;\n }\n const message = formatMessage(next, event);\n if (!message) return;\n // Fire-and-forget; swallow errors so the daemon never trips on a flaky\n // notifier. Sync throws and async rejections both land here.\n try {\n const r = deps.notify({ project, message, record: next, event });\n if (r && typeof (r as Promise<void>).catch === \"function\") {\n (r as Promise<void>).catch(() => {});\n }\n } catch {\n // intentionally swallowed\n }\n // #246: cross-project delegation report-back. When a delegated task settles\n // (done/blocked/failed/stalled/cancelled), fan the outcome back to the origin\n // project's mailbox so A's relay wakes A's captain (dispatch-and-yield, never\n // poll). 'awaiting-input' is excluded — the origin doesn't need a noise push\n // every time the target crew ends a turn.\n const reportState = next.state === \"done\" || next.state === \"blocked\" || next.state === \"failed\" || next.state === \"stalled\" || next.state === \"cancelled\";\n if (next.originProject && next.originProject !== project && reportState) {\n const originMsg = formatDelegationReport(next, next.originProject, project);\n if (originMsg && deps.notify) {\n try {\n const r = deps.notify({ project: next.originProject, message: originMsg, record: next, event });\n if (r && typeof (r as Promise<void>).catch === \"function\") (r as Promise<void>).catch(() => {});\n } catch { /* swallowed */ }\n }\n }\n}\n\ntype Req =\n | { kind: \"dispatch\"; record: TaskRecord }\n | { kind: \"event\"; project: string; event: ControlEvent }\n | { kind: \"status\"; project: string; id: string }\n | { kind: \"list\"; project: string }\n | { kind: \"reply\"; project: string; id: string; message: string }\n | { kind: \"gate-resolve\"; project: string; gateId: string; resolvedBy: string; payload: unknown }\n | { kind: \"purge\"; project: string; id: string; force?: boolean };\n\n// #87: exhaustive set of known ControlEvent types for socket-boundary validation.\n// Any event.type arriving from the wire that is not in this set is rejected with\n// a clean structured error before it can reach reduce() or the store.\nconst KNOWN_EVENT_TYPES: ReadonlySet<string> = new Set([\n \"task.started\", \"task.progress\", \"heartbeat\",\n \"task.blocked\", \"task.review\", \"task.done\", \"task.failed\",\n \"task.session\", \"task.turn.started\", \"task.turn.completed\",\n \"task.delta\", \"task.input.requested\", \"task.approval.requested\",\n \"task.reattached\", \"task.reopened\",\n \"task.stalled\", \"task.idle\", \"task.quiet\", \"task.timeout\", \"task.reconcile-failed\",\n \"task.cancelled\", \"task.session.ended\",\n \"task.first-turn.confirmed\", // #466: delivery confirmation\n]);\n\nexport function createDaemon(deps: DaemonDeps) {\n const { store, now } = deps;\n // #210: per-task timestamp of the captain's most recent turn (a `crew send`/\n // reply/answer emits task.started). Used to debounce CREW IDLE during an\n // active back-and-forth. Bounded by the live task set; never read after a\n // task terminates (terminal states don't transition to awaiting-input).\n const lastCaptainTurnAt = new Map<string, number>();\n // #354: per-task debounce for CREW QUIET. Keyed to the liveness timestamp of\n // the quiet episode so exactly one QUIET fires per episode; when the crew shows\n // activity again, liveness advances and a later quiet episode re-notifies.\n const quietNotifiedAt = new Map<string, number>();\n // #466: per-task debounce for first-turn resend attempts — avoids hammering\n // a still-not-ready pane with pastes every sweep tick.\n const resendAttemptedAt = new Map<string, number>();\n const firstTurnUndeliveredBudgetMs = deps.firstTurnUndeliveredBudgetMs ?? DEFAULT_FIRST_TURN_UNDELIVERED_BUDGET_MS;\n const firstTurnResendCooldownMs = deps.firstTurnResendCooldownMs ?? DEFAULT_FIRST_TURN_RESEND_COOLDOWN_MS;\n\n // Shared event-application core (extracted from handle()'s \"event\" case) so\n // the #466 self-heal path below can stamp task.first-turn.confirmed through\n // the same reduce → store.put → firePush pipeline as a normal socket event.\n async function applyEvent(project: string, event: ControlEvent): Promise<TaskRecord> {\n if (!KNOWN_EVENT_TYPES.has((event as any).type)) {\n throw new Error(`unknown event type '${(event as any).type}' — not a valid ControlEvent`);\n }\n const cur = store.get(project, event.id);\n if (!cur) throw new Error(`unknown task ${event.id}`);\n if (event.type === \"task.started\") lastCaptainTurnAt.set(event.id, now());\n if (event.type === \"task.session.ended\" && !TERMINAL_STATES.has(cur.state)) {\n const liveness = deps.isSurfaceAlive ? await deps.isSurfaceAlive(cur) : \"unknown\";\n if (liveness !== \"gone\") return cur; // alive/unknown: no-op, keep current state\n }\n const next = reduce(cur, event, now());\n if (next !== cur) {\n store.put(next); // skip redundant write on terminal no-ops\n firePush(deps, project, cur.state, next, event, lastCaptainTurnAt.get(next.id));\n }\n return next;\n }\n\n // #466 self-heal: attempt to recover a task whose first turn never landed.\n // Called from sweep() once undeliveredMs exceeds firstTurnUndeliveredBudgetMs.\n // Debounced per-task via resendAttemptedAt. Re-fetches the record from the\n // store right before acting so a confirmation that lands concurrently (e.g.\n // the UserPromptSubmit hook) is never double-delivered — CRITICAL SAFETY.\n async function attemptFirstTurnRecovery(r: TaskRecord, undeliveredMs: number): Promise<void> {\n const lastAttempt = resendAttemptedAt.get(r.id);\n if (lastAttempt != null && now() - lastAttempt < firstTurnResendCooldownMs) return;\n resendAttemptedAt.set(r.id, now());\n\n const fresh = store.get(r.project, r.id);\n if (!fresh || fresh.firstTurnConfirmedAt) return; // already landed — idempotent no-op\n\n const tag = crewTag(fresh);\n const fireNotify = (message: string) => {\n if (!deps.notify) return;\n const synthEvent: ControlEvent = { type: \"task.quiet\", id: fresh.id, quietMs: undeliveredMs };\n try {\n const p = deps.notify({ project: fresh.project, message, record: store.get(fresh.project, fresh.id) ?? fresh, event: synthEvent });\n if (p && typeof (p as Promise<void>).catch === \"function\") (p as Promise<void>).catch(() => {});\n } catch { /* swallowed — a flaky notifier must never trip the sweep */ }\n };\n\n if (!deps.resendFirstTurn) {\n // No resend capability wired (pure unit tests / non-cmux deployment) —\n // preserve the prior alert-only behavior.\n fireNotify(`⚠️ CREW UNDELIVERED ${tag}: first turn may not have landed (0 activity) — re-send the task or check the spawn.`);\n return;\n }\n\n let result: { delivered: boolean };\n try { result = await deps.resendFirstTurn(fresh); }\n catch { result = { delivered: false }; }\n\n if (result.delivered) {\n // The reducer's task.first-turn.confirmed path is itself idempotent\n // (first occurrence only — #470), so calling it here is safe even if the\n // resend's own hook confirmation raced ahead of us.\n try { await applyEvent(fresh.project, { type: \"task.first-turn.confirmed\", id: fresh.id }); } catch { /* best-effort */ }\n fireNotify(`🔁 CREW FIRST-TURN AUTO-RESENT ${tag}: first turn had not landed after ${Math.round(undeliveredMs / 1000)}s — re-sent automatically.`);\n } else {\n fireNotify(`⚠️ CREW UNDELIVERED ${tag}: first turn may not have landed — auto-resend attempted but the pane wasn't ready; will retry.`);\n }\n }\n\n return {\n async handle(req: Req): Promise<TaskRecord | TaskRecord[]> {\n switch (req.kind) {\n case \"dispatch\": {\n store.put(req.record);\n // #246: cross-project delegation — notify B's mailbox so B's relay\n // wakes B's captain with the request. Skip auto-launch; B's captain\n // decides how to execute (typically spawns a crew).\n if (req.record.originProject) {\n const origin = req.record.originProject;\n const msg = `📨 Cross-project task from ${origin}: ${req.record.task}`;\n if (deps.notify) {\n try {\n const r = deps.notify({ project: req.record.project, message: msg, record: req.record, event: { type: \"task.started\", id: req.record.id } });\n if (r && typeof (r as Promise<void>).catch === \"function\") (r as Promise<void>).catch(() => {});\n } catch { /* swallowed — flaky notifier must not break dispatch */ }\n }\n return req.record;\n }\n if (req.record.mode === \"headless\" && deps.launchHeadless) {\n deps.launchHeadless(req.record).catch((e: unknown) => {\n const error = e instanceof Error ? e.message : String(e);\n store.put({ ...req.record, state: \"failed\", lastEvent: \"launch-error\", error });\n });\n return req.record;\n }\n if (req.record.mode === \"interactive\" && deps.launchInteractive) {\n deps.launchInteractive(req.record).catch((e: unknown) => {\n const error = e instanceof Error ? e.message : String(e);\n store.put({ ...req.record, state: \"failed\", lastEvent: \"launch-error\", error });\n });\n return req.record;\n }\n // No launcher for this mode → fail LOUD, never silently park in\n // `submitted` (red-team #4). Interactive launcher is the deferred\n // interactive-wiring spec; until then, say so explicitly.\n const failed: TaskRecord = {\n ...req.record,\n state: \"failed\",\n lastEvent: \"no-launcher\",\n error:\n req.record.mode === \"interactive\"\n ? \"interactive mode is not yet implemented (deferred interactive-wiring spec); use --mode headless\"\n : `no launcher available for mode '${req.record.mode}'`,\n };\n store.put(failed);\n return failed;\n }\n case \"event\": {\n // #87: validate event.type at the socket boundary before touching state.\n if (!KNOWN_EVENT_TYPES.has((req.event as any).type)) {\n throw new Error(`unknown event type '${(req.event as any).type}' — not a valid ControlEvent`);\n }\n return applyEvent(req.project, req.event);\n }\n case \"status\": {\n const r = store.get(req.project, req.id);\n if (!r) throw new Error(`unknown task ${req.id}`);\n return r;\n }\n case \"list\":\n return store.list(req.project);\n case \"reply\": {\n const r = store.get(req.project, req.id);\n if (!r) throw new Error(`unknown task ${req.id}`);\n if (r.state !== \"blocked\") throw new Error(`task ${req.id} is not blocked (state=${r.state})`);\n // The captain's answer is a turn to the crew (#210 debounce key).\n lastCaptainTurnAt.set(r.id, now());\n const next = reduce(r, { type: \"task.started\", id: r.id }, now());\n store.put(next); // persist the transition before delivering (durable first)\n if (deps.deliverReply) await deps.deliverReply(r, req.message);\n return next;\n }\n case \"gate-resolve\": {\n // Find the task that owns this gate.\n const owning = deps.store.listAll().find((r) => r.gates?.some((g) => g.gateId === req.gateId));\n if (!owning || !owning.gates) throw new Error(`gate ${req.gateId} not found`);\n const updatedGates = owning.gates.map((g) =>\n g.gateId === req.gateId\n ? { ...g, state: \"resolved\" as const, resolvedBy: req.resolvedBy, resolution: req.payload }\n : g,\n );\n deps.store.put({ ...owning, gates: updatedGates });\n // Driver answers via the saved requestId (it tracks it per-task internally).\n if (deps.resolveInteractiveGate) await deps.resolveInteractiveGate(owning.id, req.payload);\n return { ...owning, gates: updatedGates };\n }\n case \"purge\": {\n const r = store.get(req.project, req.id);\n if (!r) throw new Error(`unknown task ${req.id}`);\n if (!TERMINAL_STATES.has(r.state) && !req.force) {\n throw new Error(`task ${req.id} is not terminal (state=${r.state}); use --force to purge anyway`);\n }\n store.delete(req.project, req.id);\n return r;\n }\n default: { const _exhaustive: never = req; throw new Error(`unhandled request kind`); }\n }\n },\n async sweep(): Promise<void> {\n const t = now();\n const surfaceAlive = deps.isSurfaceAlive ?? (async () => \"unknown\" as const);\n\n // #457: Per-project overflow prune — keep only the most-recent K terminal\n // records. Bounds accumulation for short-lived sessions where many tasks\n // finish before the 7-day TTL expires.\n const projects = new Set(store.listAll().map((r) => r.project));\n for (const project of projects) {\n const terminal = store.list(project)\n .filter((r) => TERMINAL_STATES.has(r.state))\n .sort((a, b) => b.lastHeartbeat - a.lastHeartbeat);\n for (const r of terminal.slice(TERMINAL_RECORD_KEEP_PER_PROJECT)) {\n store.delete(r.project, r.id);\n }\n }\n\n // #466: first-turn recovery attempts fired this tick — awaited together at\n // the end so the loop below never blocks on a slow pane round-trip.\n const recoveryPromises: Promise<void>[] = [];\n\n for (const r of store.listAll()) {\n // #378: GC terminal records whose last heartbeat is older than the TTL.\n if (TERMINAL_STATES.has(r.state) && t - r.lastHeartbeat > TERMINAL_RECORD_TTL_MS) {\n store.delete(r.project, r.id);\n continue;\n }\n // #225 root-fix: terminate non-terminal tasks that exceeded the wall-clock\n // ceiling. Terminalization is the persistent dedup — a daemon restart sees\n // the cancelled record and the TERMINAL_STATES gate above blocks re-fire.\n // The volatile firedTimeout Set is removed; terminal state replaces it.\n // #629: 'blocked'/'review' (isStickyAttention) are exempt — they pause a\n // crew pending a human decision with no natural time bound (the captain\n // might not look for hours). The ceiling exists to catch a crew stuck\n // actually WORKING; applying it here cancelled a crew that had already\n // finished and was correctly waiting on `crew approve`, permanently\n // closing that path. A still-live surface keeps waiting indefinitely;\n // a dead one is still caught by the surface-gone reap right below.\n if (!TERMINAL_STATES.has(r.state) && !isStickyAttention(r.state)) {\n const ceiling = deps.taskTimeoutMs ?? DEFAULT_TASK_TIMEOUT_MS;\n if (t - r.createdAt > ceiling) {\n const prevState = r.state; // capture BEFORE terminalization (shown in message)\n const tag = crewTag(r);\n const hrs = Math.round(ceiling / 3_600_000);\n const msg = `CREW TIMEOUT ${tag}: wall-clock exceeded ${hrs}h (id: ${r.id}, state: ${prevState})`;\n const synthEvent: ControlEvent = { type: \"task.timeout\", id: r.id, taskTimeoutMs: ceiling };\n // Terminalize first — persisted to store so any future daemon instance\n // sees a terminal record and skips it (flood-proof across restarts).\n store.put({ ...r, state: \"cancelled\", lastEvent: \"sweep.task-timeout\" });\n // #457: suppress ghost CREW TIMEOUT for interactive tasks whose surface\n // is provably gone — they were already abandoned; terminalize silently.\n // Headless tasks have no cmux surface so always notify.\n // \"unknown\" (cmux down / transient) → notify conservatively.\n let shouldNotify = true;\n if (r.mode === \"interactive\") {\n const liveness = await surfaceAlive(r);\n if (liveness === \"gone\") shouldNotify = false;\n }\n if (deps.notify && shouldNotify) {\n try {\n const p = deps.notify({ project: r.project, message: msg, record: r, event: synthEvent });\n if (p && typeof (p as Promise<void>).catch === \"function\") {\n (p as Promise<void>).catch(() => {});\n }\n } catch {\n // swallowed — a flaky notifier must never trip the sweep\n }\n }\n continue; // #378: skip remaining sweep body — stale `r` must not clobber just-written terminal state\n }\n }\n // #139 backstop: reap interactive records whose backing surface is\n // PROVABLY gone (crew session died with no terminal signal — opencode has\n // no SessionEnd hook, and a hard kill can drop claude's). This is\n // liveness-based reaping, NOT a shorter timeout: the 24h heartbeat budget\n // is untouched, so a legitimately-idle LIVE crew is never reaped (its\n // surface answers \"alive\" and falls through to evaluateStall → CREW IDLE).\n // \"unknown\" (cmux down) never reaps. cancelled is silent (not in\n // ATTENTION_STATES) — no false CREW STALLED re-emitted.\n if (r.mode === \"interactive\" && REAPABLE_SURFACE_STATES.has(r.state)) {\n const liveness = await surfaceAlive(r);\n if (liveness === \"gone\") {\n store.put({ ...r, state: \"cancelled\", lastEvent: \"sweep.surface-gone\" });\n continue;\n }\n }\n // #466-single: An interactive crew spawned but never started stays in\n // `submitted` — no task.started hook ever fires, so the working-state\n // undelivered check below is unreachable. Recover it here too, keyed\n // off createdAt (not lastHeartbeat/heartbeatBudgetMs — see #466 self-heal\n // below) so a silently-dropped first turn is caught regardless of state.\n if (r.mode === \"interactive\" && r.state === \"submitted\" && !r.firstTurnConfirmedAt) {\n const undeliveredMs = t - r.createdAt;\n if (undeliveredMs > firstTurnUndeliveredBudgetMs) {\n recoveryPromises.push(attemptFirstTurnRecovery(r, undeliveredMs));\n continue;\n }\n }\n // #354: evaluateStall now only stalls a HEADLESS heartbeat timeout or a\n // hung INTERACTIVE tool call (PreToolUse with no PostToolUse past the\n // tool-stall budget). A quiet interactive thinking turn no longer stalls\n // here — it is surfaced as CREW QUIET below, keeping the crew `working`.\n const idle = evaluateStall(r, t);\n if (idle) {\n store.put(idle);\n // The synth event only carries the notify payload; the reducer treats\n // it as a no-op (state already updated above). A hung-tool or expired-\n // Monitor stall carries the tool name + elapsed so the notifier renders\n // the accurate message (#594a: idle.pendingTool is already cleared by\n // the time a Monitor-only stall fires, so the two branches don't overlap).\n const synthEvent: ControlEvent = idle.pendingTool\n ? { type: \"task.stalled\", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs, tool: idle.pendingTool.name, elapsedMs: t - idle.pendingTool.since }\n : idle.pendingMonitor\n ? { type: \"task.stalled\", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs, tool: \"Monitor\", elapsedMs: t - idle.pendingMonitor.since }\n : { type: \"task.stalled\", id: r.id, heartbeatBudgetMs: r.heartbeatBudgetMs };\n firePush(deps, r.project, r.state, idle, synthEvent, lastCaptainTurnAt.get(r.id));\n continue;\n }\n // #466 self-heal: an interactive `working` crew that has NEVER had\n // firstTurnConfirmedAt set may be sitting at an empty prompt (the crew\n // pane never actually ingested the first turn). Checked BEFORE the\n // heartbeat-driven CREW QUIET branch below and gated on createdAt, NOT\n // on heartbeat liveness — the frozen-frame root cause showed heartbeats\n // can keep flowing on a crew whose first turn never landed, which would\n // otherwise mask the drop indefinitely.\n if (r.mode === \"interactive\" && r.state === \"working\" && !r.pendingTool && !r.firstTurnConfirmedAt) {\n const undeliveredMs = t - r.createdAt;\n if (undeliveredMs > firstTurnUndeliveredBudgetMs) {\n recoveryPromises.push(attemptFirstTurnRecovery(r, undeliveredMs));\n continue;\n }\n }\n // #354 CREW QUIET: a `working` interactive crew quiet past its heartbeat\n // budget with NO tool in flight is alive but deep-thinking (no hook fires\n // during pure model thinking). Surface a distinct, non-alarming nudge —\n // NOT 'awaiting-input' (the turn never ended; real CREW IDLE comes only\n // from the Stop hook). State stays `working`; notify once per episode.\n // Only reachable once firstTurnConfirmedAt is set — the undelivered\n // check above owns the !firstTurnConfirmedAt case.\n if (r.mode === \"interactive\" && r.state === \"working\" && !r.pendingTool && r.firstTurnConfirmedAt) {\n const liveness = r.attempts.at(-1)?.lastHeartbeatAt ?? r.lastHeartbeat;\n const quiet = t - liveness;\n if (quiet > r.heartbeatBudgetMs) {\n if (deps.notify && quietNotifiedAt.get(r.id) !== liveness) {\n quietNotifiedAt.set(r.id, liveness);\n const tag = crewTag(r);\n const synthEvent: ControlEvent = { type: \"task.quiet\", id: r.id, quietMs: quiet };\n const mins = Math.max(1, Math.round(quiet / 60000));\n const message = `CREW QUIET ${tag}: working ~${mins}min with no tool activity — likely deep thinking (no reply expected yet).`;\n try {\n const p = deps.notify({ project: r.project, message, record: r, event: synthEvent });\n if (p && typeof (p as Promise<void>).catch === \"function\") (p as Promise<void>).catch(() => {});\n } catch { /* swallowed — a flaky notifier must never trip the sweep */ }\n }\n continue;\n }\n }\n // Activity resumed (or never went quiet) → drop any QUIET debounce marker\n // so the next genuine quiet episode notifies again, and avoid map growth.\n if (quietNotifiedAt.has(r.id)) quietNotifiedAt.delete(r.id);\n const recovered = recoverStall(r, t);\n // recoverStall does NOT check heartbeat freshness — guard per its contract\n if (recovered && t - r.lastHeartbeat <= r.heartbeatBudgetMs) store.put(recovered);\n }\n // #466: wait for this tick's first-turn recovery attempts. They ran\n // independently (not serialized in the loop above), so this only adds\n // the latency of the slowest single attempt, not their sum.\n await Promise.all(recoveryPromises);\n },\n async reconcile(): Promise<void> {\n const alive = deps.isPidAlive ?? (() => true);\n const surfaceAlive = deps.isSurfaceAlive ?? (async () => \"unknown\" as const);\n for (const r of store.listAll()) {\n if (r.state !== \"working\" && r.state !== \"submitted\") continue;\n if (r.mode === \"headless\") {\n if (r.pid != null && alive(r.pid)) continue; // still running, keep watching\n if (deps.isHeadlessInFlight?.(r.id)) continue; // #259: launch in-flight, pid not yet set\n const failed: TaskRecord = {\n ...r, state: \"failed\", lastEvent: \"reconcile\",\n error: \"orphaned by daemon restart; exit unobserved (conservative fail)\",\n };\n store.put(failed);\n const synthEvent: ControlEvent = {\n type: \"task.failed\",\n id: r.id,\n error: failed.error ?? \"reconcile\",\n };\n firePush(deps, r.project, r.state, failed, synthEvent, lastCaptainTurnAt.get(r.id));\n } else {\n // #139: an interactive crew's cmux pane SURVIVES a daemon bounce, so a\n // live crew must stay 'working' for the reattach loop to re-subscribe\n // it. The old unconditional → 'stalled' both false-stalled live crews\n // AND fired CREW STALLED on every restart. Reap ONLY when the surface\n // is provably gone; alive/unknown stay working (sweep re-checks later).\n const liveness = await surfaceAlive(r);\n if (liveness === \"gone\") {\n store.put({ ...r, state: \"cancelled\", lastEvent: \"reconcile.surface-gone\" });\n // silent — the crew is gone; no alarming push (consistent with close).\n }\n }\n }\n },\n };\n}\n","import { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\nimport type { TaskRecord, ControlEvent } from \"@squadrant/shared\";\n\nexport interface MailboxEntry {\n seq: number;\n ts: string;\n /** Absent on external (captain.message) entries — they have no task. */\n taskId?: string;\n /** Optional human-readable name carried from TaskRecord. Absent on legacy\n * records — readers must fall back to shortId(taskId). */\n name?: string;\n /** \"captain.message\" is an external (non-ControlEvent) message injected for the\n * captain — e.g. an inbound Telegram reply. */\n kind: ControlEvent[\"type\"] | \"captain.message\";\n /** Absent on external entries — no originating agent provider. */\n provider?: TaskRecord[\"provider\"];\n /** Absent/free-form on external entries. */\n payload?: Record<string, unknown>;\n /** Daemon-rendered captain-facing message (unified-formatter, #214/#210).\n * The daemon's formatMessage is the single source of truth; the relay\n * delivers this verbatim and skips entries where it is null/empty.\n * `null` on entries the daemon chose not to surface (and legacy records). */\n message?: string | null;\n}\n\ninterface AppendOpts {\n stateRoot: string;\n project: string;\n taskRecord: TaskRecord;\n event: ControlEvent;\n /** Captain-facing message rendered by the daemon (daemon.ts formatMessage). */\n message?: string | null;\n}\n\nfunction inboxDir(stateRoot: string): string {\n return join(stateRoot, \"inbox\");\n}\n\nfunction logPath(stateRoot: string, project: string): string {\n return join(inboxDir(stateRoot), `${project}.log`);\n}\n\nfunction extractPayload(event: ControlEvent): Record<string, unknown> {\n const { type: _type, id: _id, ...payload } = event as Record<string, unknown> & { type: string; id: string };\n return payload;\n}\n\nasync function listRotatedOldestFirst(stateRoot: string, project: string): Promise<string[]> {\n const dir = inboxDir(stateRoot);\n let entries: string[];\n try { entries = await fs.readdir(dir); }\n catch { return []; }\n const prefix = `${project}.log.`;\n return entries\n .filter((e) => e.startsWith(prefix) && /^\\d+$/.test(e.slice(prefix.length)))\n .map((e) => ({ name: e, n: Number(e.slice(prefix.length)) }))\n .sort((a, b) => b.n - a.n) // .3 first (oldest), .1 last (newest rotated)\n .map((e) => join(dir, e.name));\n}\n\nasync function readMaxSeqFromFile(file: string): Promise<number> {\n try {\n const buf = await fs.readFile(file, \"utf-8\");\n if (!buf.trim()) return 0;\n const lines = buf.trim().split(\"\\n\");\n for (let i = lines.length - 1; i >= 0; i--) {\n try {\n const obj = JSON.parse(lines[i]) as MailboxEntry;\n return obj.seq;\n } catch { continue; }\n }\n return 0;\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"ENOENT\") return 0;\n throw e;\n }\n}\n\nasync function readMaxSeq(stateRoot: string, project: string): Promise<number> {\n let max = 0;\n const files = [\n logPath(stateRoot, project),\n ...(await listRotatedOldestFirst(stateRoot, project)),\n ];\n for (const file of files) {\n const seq = await readMaxSeqFromFile(file);\n if (seq > max) max = seq;\n }\n return max;\n}\n\n// Per-project serial mutex. Node's event loop is single-threaded but async\n// readFile + writeFile can interleave; chaining all appends for the same\n// project through a single in-process Promise serializes them.\n//\n// For cross-process serialization (multi-daemon scenarios, e.g. launchctl\n// restart races), an OS-level flock would be needed on `<project>.log`.\n// Today squadrant runs a single daemon instance; the in-process mutex covers\n// the realistic concurrency model. flock can be added later if multi-process\n// access becomes a requirement.\nconst projectLocks = new Map<string, Promise<unknown>>();\n\nfunction withProjectLock<T>(project: string, fn: () => Promise<T>): Promise<T> {\n const prev = projectLocks.get(project) ?? Promise.resolve();\n const next = prev.catch(() => undefined).then(fn);\n // Store a tail that does not reject so the chain never breaks on caller failure\n projectLocks.set(project, next.catch(() => undefined));\n return next;\n}\n\n/** Assign a monotonic seq under the per-project lock and append the built entry. */\nfunction appendEntry(\n stateRoot: string,\n project: string,\n build: (seq: number) => MailboxEntry,\n): Promise<number> {\n return withProjectLock(project, async () => {\n const dir = inboxDir(stateRoot);\n await fs.mkdir(dir, { recursive: true });\n const file = logPath(stateRoot, project);\n const lastSeq = await readMaxSeq(stateRoot, project);\n const seq = lastSeq + 1;\n const entry = build(seq);\n await fs.appendFile(file, JSON.stringify(entry) + \"\\n\", { encoding: \"utf-8\" });\n return seq;\n });\n}\n\nexport async function appendToMailbox(opts: AppendOpts): Promise<number> {\n return appendEntry(opts.stateRoot, opts.project, (seq) => ({\n seq,\n ts: new Date().toISOString(),\n taskId: opts.taskRecord.id,\n ...(opts.taskRecord.name !== undefined ? { name: opts.taskRecord.name } : {}),\n kind: opts.event.type,\n provider: opts.taskRecord.provider,\n payload: extractPayload(opts.event),\n message: opts.message ?? null,\n }));\n}\n\n/**\n * Append an external message destined for the captain pane (#65 Telegram inbound).\n * `text` is the already-rendered captain-facing message; it is delivered verbatim\n * by the #332 delivery loop (deliverable() returns it, defer-protected). The entry\n * carries no taskId/provider — it is not tied to a crew task.\n */\nexport async function appendCaptainMessage(opts: {\n stateRoot: string;\n project: string;\n text: string;\n source: \"telegram\" | \"daemon\" | \"cli\";\n}): Promise<number> {\n return appendEntry(opts.stateRoot, opts.project, (seq) => ({\n seq,\n ts: new Date().toISOString(),\n kind: \"captain.message\",\n payload: { source: opts.source },\n message: opts.text,\n }));\n}\n\nfunction cursorPath(stateRoot: string, project: string, subscriber: string): string {\n return join(inboxDir(stateRoot), `${project}.${subscriber}.cursor`);\n}\n\ninterface CursorOpts {\n stateRoot: string;\n project: string;\n subscriber: string;\n}\n\nexport interface CursorState {\n lastAckedSeq: number;\n subscriber: string;\n updatedAt: string;\n}\n\nexport async function readCursor(opts: CursorOpts): Promise<CursorState | null> {\n let buf: string;\n try {\n buf = await fs.readFile(cursorPath(opts.stateRoot, opts.project, opts.subscriber), \"utf-8\");\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw e;\n }\n // A 0-byte or corrupt cursor (e.g. an interrupted write) must be treated the\n // same as a missing one — return null so the caller starts fresh from seq 0\n // rather than crashing the relay boot / delivery loop (#332 storm BUG 1).\n if (!buf.trim()) return null;\n try {\n return JSON.parse(buf) as CursorState;\n } catch {\n return null;\n }\n}\n\nexport interface WaitForCaptainDeliveryOpts {\n stateRoot: string;\n project: string;\n /** The seq returned by appendCaptainMessage/appendToMailbox for the entry to confirm. */\n seq: number;\n /** Delivery cursor subscriber to poll (default \"captain\" — the only current subscriber). */\n subscriber?: string;\n timeoutMs: number;\n pollMs: number;\n}\n\n/**\n * Poll the delivery cursor until it has acked `seq` (the delivery loop drained\n * the entry) or the timeout elapses (#566). A CLI-originated send only knows\n * its message reached the pane once the cursor advances past its own seq —\n * appending to the mailbox alone proves nothing about delivery.\n */\nexport async function waitForCaptainDelivery(opts: WaitForCaptainDeliveryOpts): Promise<boolean> {\n const subscriber = opts.subscriber ?? \"captain\";\n const deadline = Date.now() + opts.timeoutMs;\n for (;;) {\n const cursor = await readCursor({ stateRoot: opts.stateRoot, project: opts.project, subscriber });\n if (cursor && cursor.lastAckedSeq >= opts.seq) return true;\n if (Date.now() >= deadline) return false;\n await new Promise((r) => setTimeout(r, opts.pollMs));\n }\n}\n\nexport async function writeCursor(opts: CursorOpts & { lastAckedSeq: number }): Promise<void> {\n await fs.mkdir(inboxDir(opts.stateRoot), { recursive: true });\n const dest = cursorPath(opts.stateRoot, opts.project, opts.subscriber);\n // Unique tmp per call so overlapping writes never share a tmp path. A shared\n // `dest + \".tmp\"` let one rename consume the tmp the other expected → ENOENT\n // on rename, leaving a 0-byte/corrupt cursor (#332 storm BUG 2).\n const tmp = `${dest}.${process.pid}.${randomUUID()}.tmp`;\n const data: CursorState = {\n lastAckedSeq: opts.lastAckedSeq,\n subscriber: opts.subscriber,\n updatedAt: new Date().toISOString(),\n };\n const handle = await fs.open(tmp, \"w\");\n try {\n await handle.writeFile(JSON.stringify(data), { encoding: \"utf-8\" });\n await handle.sync();\n } finally {\n await handle.close();\n }\n try {\n await fs.rename(tmp, dest);\n } catch (e) {\n // Best-effort cleanup so a failed rename doesn't leave the unique tmp behind.\n await fs.unlink(tmp).catch(() => {});\n throw e;\n }\n}\n\ninterface ReadFromCursorOpts {\n stateRoot: string;\n project: string;\n fromSeq: number;\n}\n\nexport async function* readFromCursor(opts: ReadFromCursorOpts): AsyncIterable<MailboxEntry> {\n // Order: oldest rotated first (.3 → .2 → .1), then current.\n const rotated = await listRotatedOldestFirst(opts.stateRoot, opts.project);\n const files = [...rotated, logPath(opts.stateRoot, opts.project)];\n for (const file of files) {\n let buf: string;\n try {\n buf = await fs.readFile(file, \"utf-8\");\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"ENOENT\") continue;\n throw e;\n }\n for (const line of buf.split(\"\\n\")) {\n if (!line.trim()) continue;\n let entry: MailboxEntry;\n try {\n entry = JSON.parse(line) as MailboxEntry;\n } catch {\n continue;\n }\n if (entry.seq >= opts.fromSeq) yield entry;\n }\n }\n}\n\n/** Read-only Tier 2 observability stats for one project's mailbox (#44 dashboard). */\nexport interface MailboxStats {\n /** Highest seq across the current log + rotated segments (0 when empty). */\n maxSeq: number;\n /** Size in bytes of the current (un-rotated) log file. */\n sizeBytes: number;\n /** Age of the oldest entry in the current log (0 when empty/missing). */\n oldestEntryAgeMs: number;\n /** Number of rotated segments on disk (<project>.log.1, .2, …). */\n rotationCount: number;\n}\n\n/**\n * Read-only stats for the dashboard's Tier 2 data-plane view. Never mutates;\n * tolerates a missing inbox (returns zeros). The daemon gathers this and passes\n * it to the pure snapshot assembler.\n */\nexport async function mailboxStats(stateRoot: string, project: string): Promise<MailboxStats> {\n const file = logPath(stateRoot, project);\n const rotated = await listRotatedOldestFirst(stateRoot, project);\n let sizeBytes = 0;\n for (const f of [file, ...rotated]) {\n try { sizeBytes += (await fs.stat(f)).size; }\n catch (e) { if ((e as NodeJS.ErrnoException).code !== \"ENOENT\") throw e; }\n }\n // Oldest entry lives in the oldest rotated archive when one exists (listRotatedOldestFirst\n // returns oldest-first), otherwise it's the current file.\n const oldestFile = rotated[0] ?? file;\n return {\n maxSeq: await readMaxSeq(stateRoot, project),\n sizeBytes,\n oldestEntryAgeMs: await oldestEntryAgeMs(oldestFile),\n rotationCount: rotated.length,\n };\n}\n\ninterface RotateOpts {\n stateRoot: string;\n project: string;\n maxBytes: number;\n maxAgeMs: number;\n keepCount: number;\n}\n\nexport interface RotateResult {\n rotated: boolean;\n from?: string;\n to?: string;\n}\n\nasync function oldestEntryAgeMs(file: string): Promise<number> {\n try {\n const buf = await fs.readFile(file, \"utf-8\");\n const firstLine = buf.split(\"\\n\").find((l) => l.trim());\n if (!firstLine) return 0;\n const entry = JSON.parse(firstLine) as MailboxEntry;\n return Date.now() - new Date(entry.ts).getTime();\n } catch {\n return 0;\n }\n}\n\nexport async function rotateIfNeeded(opts: RotateOpts): Promise<RotateResult> {\n return withProjectLock(opts.project, async () => {\n const file = logPath(opts.stateRoot, opts.project);\n let size = 0;\n try { size = (await fs.stat(file)).size; }\n catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"ENOENT\") return { rotated: false };\n throw e;\n }\n const age = await oldestEntryAgeMs(file);\n if (size < opts.maxBytes && age < opts.maxAgeMs) return { rotated: false };\n\n // Shift existing .N files down (.N → .N+1), deleting anything beyond keepCount.\n // Process highest N first so we don't clobber.\n // Find the existing max N.\n const existing = await listRotatedOldestFirst(opts.stateRoot, opts.project);\n // existing is sorted by N desc (oldest first). Extract numbers.\n const nums = existing.map((p) => Number(p.slice(p.lastIndexOf(\".\") + 1))).sort((a, b) => b - a);\n for (const n of nums) {\n const src = `${file}.${n}`;\n const dst = `${file}.${n + 1}`;\n if (n + 1 > opts.keepCount) {\n try { await fs.unlink(src); } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== \"ENOENT\") throw e;\n }\n } else {\n try { await fs.rename(src, dst); } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== \"ENOENT\") throw e;\n }\n }\n }\n // current → .1\n await fs.rename(file, `${file}.1`);\n // create fresh empty current\n await fs.writeFile(file, \"\", { encoding: \"utf-8\" });\n return { rotated: true, from: file, to: `${file}.1` };\n });\n}\n","// src/control/protocol.ts\nimport { createServer, createConnection, type Server, type Socket } from \"node:net\";\nimport { existsSync, unlinkSync } from \"node:fs\";\n\n// Bump this on any change to the request/reply wire shape.\n// v1 is the first versioned release. Clients treat an absent _v as compatible\n// (pre-v1 rollout grace period); future bumps hard-fail on mismatch.\nexport const PROTOCOL_VERSION = 1;\n\nexport function encodeMsg(obj: unknown): string {\n return JSON.stringify(obj) + \"\\n\";\n}\n\nexport function createDecoder(onParseError?: (line: string) => void) {\n let buf = \"\";\n return {\n push(chunk: string): unknown[] {\n buf += chunk;\n const out: unknown[] = [];\n let idx: number;\n while ((idx = buf.indexOf(\"\\n\")) >= 0) {\n const line = buf.slice(0, idx);\n buf = buf.slice(idx + 1);\n if (!line.trim()) continue;\n try {\n const parsed = JSON.parse(line);\n // Silently discard keepalive frames (#94) — never surface to any consumer.\n if (typeof parsed === \"object\" && parsed !== null && (parsed as any).type === \"_keepalive\") continue;\n out.push(parsed);\n } catch {\n // #87: notify caller of malformed lines so the server can reply with a\n // structured error instead of silently dropping the frame.\n onParseError?.(line);\n }\n }\n return out;\n },\n // #87: exposes the unprocessed buffer content — bytes received but not yet\n // terminated with a newline. The server checks this on connection end to\n // detect newline-less input and reply with a fast structured error.\n remainder(): string {\n return buf;\n },\n };\n}\n\nexport type Handler = (msg: any) => Promise<unknown>;\n\n/** NetConn is the raw socket for a single client connection. */\nexport type NetConn = Socket;\n\n/** Injectable clock for startServer — lets tests drive keepalive timers without real timers. */\nexport interface ServerDeps {\n setInterval?: (fn: () => void, ms: number) => ReturnType<typeof setInterval>;\n clearInterval?: (id: ReturnType<typeof setInterval>) => void;\n}\n\n/**\n * Optional callbacks for long-lived attach connections (spec §4.5/§4.6).\n * When a connection sends {op:\"attach\",taskId} the socket is \"claimed\" by\n * the attach path and all subsequent frames on that socket are routed to\n * onAttachInbound rather than through the normal request/response handler.\n */\nexport interface ServerCallbacks {\n /** Normal request/response handler (required). */\n handler: Handler;\n /** Called once when a connection sends the {op:\"attach\",taskId} frame. */\n onAttach?: (conn: NetConn, frame: { op: \"attach\"; taskId: string }) => void;\n /** Called for every subsequent inbound frame on a claimed attach connection. */\n onAttachInbound?: (conn: NetConn, frame: AttachInbound) => void;\n /** Called when a claimed attach connection closes. */\n onAttachClose?: (conn: NetConn) => void;\n}\n\n/**\n * Red-team #2 (High): an unhandled server `error` (e.g. listen EADDRINUSE when\n * a second daemon races in) became an uncaughtException → process died →\n * launchd KeepAlive (no ThrottleInterval) tight-respawned = the crash-loop.\n * Default: log with timestamp and exit non-zero so launchd's ThrottleInterval\n * paces the restart instead of tight-looping. Tests inject a spy.\n */\nexport function defaultListenError(e: Error): void {\n process.stderr.write(`[squadrantd] ${new Date().toISOString()} server error: ${e.message}\\n`);\n process.exit(1);\n}\n\nexport function startServer(\n sockPath: string,\n handlerOrCallbacks: Handler | ServerCallbacks,\n onListenError: (e: Error) => void = defaultListenError,\n deps: ServerDeps = {},\n): Server {\n // Back-compat: accept a plain function as well as a ServerCallbacks object.\n const callbacks: ServerCallbacks =\n typeof handlerOrCallbacks === \"function\"\n ? { handler: handlerOrCallbacks }\n : handlerOrCallbacks;\n const { handler, onAttach, onAttachInbound, onAttachClose } = callbacks;\n const setIntervalFn = deps.setInterval ?? setInterval;\n const clearIntervalFn = deps.clearInterval ?? clearInterval;\n\n if (existsSync(sockPath)) {\n try { unlinkSync(sockPath); } catch { /* stale socket */ }\n }\n const server = createServer((conn) => {\n conn.setEncoding(\"utf-8\");\n let claimType: \"none\" | \"attach\" = \"none\";\n let keepaliveId: ReturnType<typeof setInterval> | undefined;\n\n // #87: reply with a structured error when a newline-terminated line fails to\n // parse as JSON, so the client gets a fast error instead of a silent drop.\n const dec = createDecoder((badLine) => {\n if (claimType !== \"attach\") {\n try {\n conn.write(encodeMsg({ ok: false, error: `malformed request: invalid JSON`, _v: PROTOCOL_VERSION }));\n } catch { /* conn already closed */ }\n }\n });\n\n conn.on(\"data\", async (chunk: string) => {\n for (const msg of dec.push(chunk)) {\n // If already claimed by attach, route all frames to the inbound handler.\n if (claimType === \"attach\") {\n onAttachInbound?.(conn, msg as AttachInbound);\n continue;\n }\n // Check for attach-claim frame BEFORE falling through to req/res.\n if (\n onAttach &&\n msg != null &&\n typeof msg === \"object\" &&\n (msg as any).op === \"attach\" &&\n typeof (msg as any).taskId === \"string\"\n ) {\n claimType = \"attach\";\n onAttach(conn, msg as { op: \"attach\"; taskId: string });\n // Start keepalive heartbeat for held-open attach connections (#94).\n keepaliveId = setIntervalFn(() => {\n try { conn.write(encodeFrame({ type: \"_keepalive\" })); } catch { /* conn closed */ }\n }, 10_000);\n continue;\n }\n // Normal request/response path.\n // #259: both writes are wrapped — a destroyed socket can throw synchronously\n // (write-after-end); that throw would escape the async data handler and become\n // an unhandled rejection, killing the daemon. Client-gone writes are silently\n // swallowed; the conn.on(\"error\") handler above covers the emitted error event.\n try {\n const reply = await handler(msg);\n try { conn.write(encodeMsg({ ok: true, reply, _v: PROTOCOL_VERSION })); } catch { /* client gone */ }\n } catch (e) {\n const errMsg = e instanceof Error ? e.message : String(e);\n try { conn.write(encodeMsg({ ok: false, error: errMsg, _v: PROTOCOL_VERSION })); } catch { /* client gone */ }\n }\n }\n });\n conn.on(\"error\", () => { /* client vanished; ignore */ });\n // #87: when the client half-closes (done sending) with bytes still in the\n // decoder buffer, the message had no newline terminator — send a fast error\n // instead of silently leaving the client to hit the 5s sendRequest timeout.\n conn.on(\"end\", () => {\n if (claimType !== \"attach\" && dec.remainder().trim()) {\n try {\n conn.write(encodeMsg({ ok: false, error: `malformed request: missing newline terminator`, _v: PROTOCOL_VERSION }));\n } catch { /* conn already closed */ }\n }\n });\n conn.on(\"close\", () => {\n if (keepaliveId !== undefined) clearIntervalFn(keepaliveId);\n if (claimType === \"attach\") onAttachClose?.(conn);\n });\n });\n server.on(\"error\", onListenError); // never let a server error become uncaughtException\n server.listen(sockPath);\n return server;\n}\n\n// #360: probe whether a live daemon owns this socket. Resolves true only when\n// a connection is accepted; false on ENOENT (no file) or ECONNREFUSED (stale\n// file / dead listener). Callers check this BEFORE startServer to avoid\n// unlink-then-bind stealing a live daemon's socket inode.\nexport function isDaemonSocketLive(sockPath: string, timeoutMs = 500): Promise<boolean> {\n return new Promise((resolve) => {\n if (!existsSync(sockPath)) { resolve(false); return; }\n const conn = createConnection(sockPath);\n const finish = (v: boolean) => { try { conn.destroy(); } catch { /* already gone */ } resolve(v); };\n const timer = setTimeout(() => finish(false), timeoutMs);\n conn.on(\"connect\", () => { clearTimeout(timer); finish(true); });\n conn.on(\"error\", () => { clearTimeout(timer); finish(false); });\n });\n}\n\nexport function sendRequest(sockPath: string, msg: unknown, timeoutMs = 5000): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const conn = createConnection(sockPath);\n const dec = createDecoder();\n const timer = setTimeout(() => {\n conn.destroy();\n reject(new Error(\"control plane unavailable: request timed out\"));\n }, timeoutMs);\n conn.setEncoding(\"utf-8\");\n conn.on(\"connect\", () => conn.write(encodeMsg({ ...(msg as Record<string, unknown>), _v: PROTOCOL_VERSION })));\n conn.on(\"data\", (chunk: string) => {\n for (const m of dec.push(chunk) as any[]) {\n clearTimeout(timer);\n conn.destroy();\n if (m._v !== undefined && m._v !== PROTOCOL_VERSION) {\n reject(new Error(`squadrantd protocol v${m._v}, this client expects v${PROTOCOL_VERSION} — upgrade squadrantd or this CLI`));\n } else if (m.ok) {\n resolve(m.reply);\n } else {\n reject(new Error(m.error));\n }\n return;\n }\n });\n conn.on(\"error\", () => {\n clearTimeout(timer);\n reject(new Error(\"control plane unavailable: cannot reach squadrantd socket\"));\n });\n });\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// Streaming-subscribe frames for `squadrant crew chat / attach` (spec §4.5).\n// Additive; existing request/response verbs untouched. Cooperates with #87.\n\nexport type AttachFrame =\n | { type: \"delta\"; taskId: string; text: string }\n | { type: \"turn-started\"; taskId: string }\n | { type: \"turn-completed\"; taskId: string }\n | { type: \"input-requested\"; taskId: string; requestId: number; question: string }\n | { type: \"approval-requested\"; taskId: string; requestId: number; question: string; kind: string }\n | { type: \"gate-promoted\"; taskId: string; gateId: string }\n | { type: \"reattached\"; taskId: string }\n | { type: \"closed\"; taskId: string; reason: string }\n | { type: \"_keepalive\" };\n\nexport type AttachInbound =\n | { op: \"attach\"; taskId: string }\n | { op: \"say\"; taskId: string; text: string }\n | { op: \"steer\"; taskId: string; text: string }\n | { op: \"interrupt\"; taskId: string }\n | { op: \"answer\"; taskId: string; requestId: number; payload: unknown };\n\nexport function encodeFrame(f: AttachFrame): string {\n return JSON.stringify(f) + \"\\n\";\n}\n\nexport function decodeFrames(wire: string): AttachFrame[] {\n const out: AttachFrame[] = [];\n for (const line of wire.split(\"\\n\")) {\n if (!line.trim()) continue;\n try {\n const parsed = JSON.parse(line) as AttachFrame;\n if (parsed.type === \"_keepalive\") continue; // discard keepalive frames (#94)\n out.push(parsed);\n } catch { /* skip malformed */ }\n }\n return out;\n}\n","// src/control/liveness.ts\n//\n// PURE service-health layer (no I/O, no clock) — the #77 foundation. Mirrors\n// watchdog.ts: every function derives a verdict from records + an explicit `now`\n// so it is fully unit-testable. All runtime probing (cmux reads for captain\n// presence) is gathered by the caller (squadrantd) and passed in already-resolved;\n// this module never touches cmux.\nimport type { TaskState, Mode, LivenessEntry } from \"@squadrant/shared\";\n\nexport type ComponentKind = \"captain\" | \"crew\" | \"command\";\n\n// alive = seen within the stale window (healthy)\n// stale = quiet past stale but not yet gone (degrading)\n// gone = dark past the gone window (treat as down — a FAULT)\n// stopped = intentionally offline (user closed the captain workspace) — NOT a\n// fault. Distinct from `gone` so the surface never red-alarms an\n// expected shutdown (#324/#323).\n// unknown = no signal / not applicable (never alarms)\nexport type HealthState = \"alive\" | \"stale\" | \"gone\" | \"stopped\" | \"unknown\";\n\nexport interface ComponentHealth {\n kind: ComponentKind;\n project: string;\n /** crew name / captain name / \"command\". */\n ref: string;\n state: HealthState;\n /** epoch ms of last evidence of life, or null when there is no timestamp\n * source (captain/command presence is a boolean, not a heartbeat). */\n lastSeenMs: number | null;\n /** human-facing context — e.g. a recovery command. */\n detail?: string;\n}\n\n// Crews legitimately idle for long (24h interactive budget), so the surface uses\n// generous windows — these only flag a genuinely dark crew, and #139 already\n// reaps provably-dead ones. Display-only; does not drive any state transition.\nexport const CREW_STALE_MS = 5 * 60_000;\nexport const CREW_GONE_MS = 30 * 60_000;\n\n/**\n * Pure. Classify a last-seen timestamp into a health state.\n * null → \"unknown\"\n * age <= staleMs → \"alive\" (boundary inclusive)\n * age <= goneMs → \"stale\" (boundary inclusive)\n * else → \"gone\"\n */\nexport function classifyHealth(\n lastSeenMs: number | null,\n now: number,\n staleMs: number,\n goneMs: number,\n): HealthState {\n if (lastSeenMs == null) return \"unknown\";\n const age = now - lastSeenMs;\n if (age <= staleMs) return \"alive\";\n if (age <= goneMs) return \"stale\";\n return \"gone\";\n}\n\nconst TERMINAL: ReadonlySet<TaskState> = new Set([\"done\", \"failed\", \"cancelled\"]);\n\n/** Minimal crew shape the projection needs (subset of TaskRecord). */\nexport interface CrewLiveness {\n id: string;\n name?: string;\n state: TaskState;\n lastHeartbeat: number;\n mode: Mode;\n /** Set once the crew's first turn is confirmed delivered (#466). Absent/undefined\n * means \"never confirmed\" — combined with heartbeatBudgetMs below to detect a\n * dropped first turn (mirrors daemon/reduce.ts's CREW UNDELIVERED watchdog). */\n firstTurnConfirmedAt?: number;\n /** Per-task stall threshold. Omitted when the caller doesn't have it (older\n * callers) — undelivered detection is skipped in that case, never a hard error. */\n heartbeatBudgetMs?: number;\n}\n\n/**\n * Pure. Project one project's component health from already-gathered inputs.\n * Emits: a captain row, a command row (only when applicable), and one row per\n * non-terminal crew.\n *\n * Captain liveness: prefers the registry-derived `captainState` (§4.1/§4.5 —\n * ground-truth from the LivenessRegistry) when supplied; falls back to the\n * legacy `captainStopped` tri-state for callers that haven't migrated:\n * captainStopped === false → captain surface was found on last delivery tick → ALIVE\n * captainStopped === true → surface gone for 3+ consecutive ticks → STOPPED\n * (intentional close — its crews are reaped and\n * delivery is paused; NOT a fault — #324/#323)\n * captainStopped === null → not yet checked / cmux unreachable → UNKNOWN\n */\nexport function projectHealth(input: {\n project: string;\n now: number;\n captainName: string;\n /** Delivery-loop captain surface state. See docs above. Ignored when `captainState` is supplied. */\n captainStopped: boolean | null;\n /** Registry-derived captain state (Task 4+). Wins over `captainStopped` when present. */\n captainState?: HealthState;\n /** true/false when a command workspace is expected; null = not applicable. */\n commandPresent: boolean | null;\n crews: CrewLiveness[];\n /** #579/#484 Gap 3: this project's captain-delivery deferral state (from\n * CaptainDelivery.stats() — see delivery/captain-delivery.ts), surfaced as\n * `detail` on the captain row so `squadrant doctor` / `squadrant status\n * --detailed` show a stuck delivery with zero extra configuration (no\n * Telegram, no mute state to fight) — a pull-based fallback that can never\n * be silenced, unlike the push alerts in delivery-loop.ts. */\n captainDeferral?: { stuck: boolean; maxDeferCount: number };\n}): ComponentHealth[] {\n const { project, now, captainName, captainStopped, commandPresent, crews } = input;\n const out: ComponentHealth[] = [];\n\n // ── captain ────────────────────────────────────────────────────────────\n const captainState: HealthState = input.captainState ?? (\n captainStopped === true ? \"stopped\" :\n captainStopped === false ? \"alive\" :\n \"unknown\"\n );\n const deferral = input.captainDeferral;\n out.push({\n kind: \"captain\",\n project,\n ref: captainName,\n state: captainState,\n lastSeenMs: null,\n detail:\n captainState === \"stopped\" ? \"captain workspace closed — crews reaped; delivery paused\" :\n captainState === \"gone\" ? \"captain process died (crash) — crews reaped\" :\n deferral?.stuck ? `⚠️ delivery stuck (${deferral.maxDeferCount}+ retries) — draft/ghost text blocking captain pane; input never touched, delivers automatically once cleared` :\n undefined,\n });\n\n // ── command (on-demand; only surfaced when applicable) ───────────────────\n if (commandPresent !== null) {\n out.push({\n kind: \"command\",\n project,\n ref: \"command\",\n state: presence(commandPresent),\n lastSeenMs: null,\n });\n }\n\n // ── crews (one row per non-terminal crew) ────────────────────────────────\n for (const c of crews) {\n if (TERMINAL.has(c.state)) continue;\n // #466/B2: promote the CREW UNDELIVERED watchdog condition (daemon/reduce.ts)\n // to a first-class, grep-able detail so it doesn't wait for the heartbeat\n // window to age the row into stale/gone before it's noticeable.\n const undelivered =\n c.mode === \"interactive\" &&\n !c.firstTurnConfirmedAt &&\n c.heartbeatBudgetMs != null &&\n now - c.lastHeartbeat > c.heartbeatBudgetMs;\n out.push({\n kind: \"crew\",\n project,\n ref: c.name ?? c.id.slice(0, 8),\n state: classifyHealth(c.lastHeartbeat, now, CREW_STALE_MS, CREW_GONE_MS),\n lastSeenMs: c.lastHeartbeat,\n detail: undelivered ? `undelivered (${c.state})` : c.state,\n });\n }\n\n return out;\n}\n\nfunction presence(p: boolean | null): HealthState {\n if (p === null) return \"unknown\";\n return p ? \"alive\" : \"gone\";\n}\n\n/** Human-friendly age of the last-seen timestamp, or em-dash when there is none. */\nexport function ageText(lastSeenMs: number | null, now: number): string {\n if (lastSeenMs == null) return \"—\";\n const s = Math.max(0, Math.round((now - lastSeenMs) / 1000));\n if (s < 60) return `${s}s ago`;\n const m = Math.round(s / 60);\n if (m < 60) return `${m}m ago`;\n return `${Math.round(m / 60)}h ago`;\n}\n\n/**\n * Pure. Return the heal command string for a component that needs remediation,\n * or null when no action is needed (or when no heal verb exists for this kind).\n */\nexport function healCmdFor(c: ComponentHealth): string | null {\n // No heal verb for captain/crew — daemon-direct delivery handles recovery automatically.\n return null;\n}\n\n/** Per-project relay health — REMOVED (#332). No longer tracked by daemon. */\nexport type RelayHealth = never;\n\n/**\n * Pure. Derive a captain HealthState from its registry entry.\n * First match wins — order matters (a clean close reads `stopped` even though\n * its pid also dies).\n */\nexport function deriveCaptainState(e: LivenessEntry | undefined): HealthState {\n if (!e) return \"unknown\";\n if (e.lastState === \"end\") return \"stopped\"; // clean close — magenta, not a fault (#324)\n if (!e.pidAlive) return \"gone\"; // pid dead, record present → crash\n return \"alive\";\n}\n\n/**\n * Pure. Reconcile an incoming signal against the prior entry.\n * Precedence: runtime ≥ agent (authoritative for presence/intent) > scan\n * (liveness-only). A `scan` updates `pidAlive` but never presence/intent, and\n * never resurrects a dead pid — only a newer runtime/agent open (greater\n * startedAt) does.\n */\nexport function reconcileLiveness(\n prev: LivenessEntry | undefined,\n next: LivenessEntry,\n): LivenessEntry {\n if (!prev) return next;\n if (next.source === \"scan\") {\n // liveness-only: adopt pidAlive (and lastSeenAt) onto prev; keep presence/intent.\n // A stale scan (older than prev, by lastSeenAt — scans of the same session\n // share startedAt, so recency must be judged by lastSeenAt) must not flip a\n // dead pid back to alive.\n const pidAlive = next.lastSeenAt >= prev.lastSeenAt ? next.pidAlive : prev.pidAlive;\n return { ...prev, pidAlive, lastSeenAt: Math.max(prev.lastSeenAt, next.lastSeenAt) };\n }\n // runtime/agent authoritative. A newer open (or any end) wins; a stale one is ignored.\n if (next.startedAt >= prev.startedAt || next.lastState === \"end\") return next;\n // #565: prev is already dead (stopped/gone) and next reports a live pid — a\n // live process outranks a startedAt comparison, otherwise a captain that\n // comes back can never be re-adopted once wrongly marked dead. Does not\n // apply when prev is still alive: an older-but-live duplicate must not\n // override the currently-tracked live session (#527).\n const prevAlive = prev.lastState === \"start\" && prev.pidAlive;\n if (!prevAlive && next.lastState === \"start\" && next.pidAlive) return next;\n return prev;\n}\n","// src/control/store.ts\nimport {\n mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync, existsSync,\n rmSync, statSync,\n} from \"node:fs\";\nimport { join, resolve, sep } from \"node:path\";\nimport type { TaskRecord } from \"@squadrant/shared\";\n\nexport interface Store {\n put(rec: TaskRecord): void;\n get(project: string, id: string): TaskRecord | undefined;\n list(project: string): TaskRecord[];\n listAll(): TaskRecord[];\n quarantine(project: string, id: string): void;\n delete(project: string, id: string): void;\n}\n\n/**\n * SECURITY (red-team #1, Critical): `project`/`id` arrive unsanitized from the\n * socket (dispatch + seed) and a crafted value (`..`, `/`, absolute, NUL) would\n * let a confused-deputy read/write arbitrary files as the user. A `project`/`id`\n * must be a single safe path segment — no separators, traversal, NUL, or dot\n * dirs. Enforced at the one chokepoint every fs op funnels through.\n */\nfunction safeSegment(kind: \"project\" | \"id\", s: unknown): string {\n if (typeof s !== \"string\" || s.length === 0) {\n throw new Error(`invalid ${kind}: must be a non-empty string`);\n }\n if (s.includes(\"\\0\")) throw new Error(`invalid ${kind}: NUL byte not allowed`);\n if (s === \".\" || s === \"..\" || /[/\\\\]/.test(s)) {\n throw new Error(`invalid ${kind}: '${s}' — path separators/traversal not allowed`);\n }\n return s;\n}\n\nexport function createStore(root: string): Store {\n const rootResolved = resolve(root);\n\n // Defense in depth: even after segment validation, never let a resolved\n // path escape the state root.\n const assertUnderRoot = (target: string): string => {\n const r = resolve(target);\n if (r !== rootResolved && !r.startsWith(rootResolved + sep)) {\n throw new Error(`path escapes state root: ${target}`);\n }\n return target;\n };\n\n const projDir = (p: string) => assertUnderRoot(join(root, safeSegment(\"project\", p)));\n const taskFile = (p: string, id: string) =>\n assertUnderRoot(join(projDir(p), `${safeSegment(\"id\", id)}.json`));\n\n return {\n put(rec) {\n mkdirSync(projDir(rec.project), { recursive: true });\n const dest = taskFile(rec.project, rec.id);\n const tmp = `${dest}.tmp`;\n writeFileSync(tmp, JSON.stringify(rec, null, 2));\n renameSync(tmp, dest); // atomic replace\n },\n get(project, id) {\n const f = taskFile(project, id);\n if (!existsSync(f)) return undefined;\n try {\n return JSON.parse(readFileSync(f, \"utf-8\")) as TaskRecord;\n } catch {\n return undefined; // corrupt file: caller handles (Task 6)\n }\n },\n list(project) {\n const d = projDir(project);\n if (!existsSync(d)) return [];\n return readdirSync(d)\n .filter((n) => n.endsWith(\".json\"))\n .map((n) => {\n try { return JSON.parse(readFileSync(join(d, n), \"utf-8\")) as TaskRecord; }\n catch { return undefined; }\n })\n .filter((r): r is TaskRecord => r !== undefined);\n },\n listAll() {\n if (!existsSync(root)) return [];\n return readdirSync(root)\n .filter((p) => { try { return statSync(join(root, p)).isDirectory(); } catch { return false; } })\n .flatMap((p) => this.list(p));\n },\n quarantine(project, id) {\n const f = taskFile(project, id);\n // suffix prevents clobber across process restarts\n if (existsSync(f)) renameSync(f, `${f}.corrupt.${Date.now()}`);\n },\n delete(project, id) {\n const f = taskFile(project, id);\n if (existsSync(f)) rmSync(f);\n },\n };\n}\n","// @squadrant/core — driver-agnostic daemon / control-plane core.\nexport * from \"./daemon/reduce.js\";\nexport * from \"./mailbox.js\";\nexport * from \"./protocol.js\";\nexport * from \"./state-machine.js\";\nexport * from \"./liveness.js\";\nexport * from \"./watchdog.js\";\nexport * from \"./store.js\";\nexport * from \"./snapshot.js\";\nexport * from \"./launchd.js\";\nexport * from \"./crew-pane-reader.js\";\nexport * from \"./interfaces.js\";\nexport * from \"./gate.js\";\nexport * from \"./daemon/context.js\";\nexport * from \"./daemon/attach.js\";\nexport * from \"./daemon/start.js\";\nexport * from \"./daemon/delivery-loop.js\";\nexport * from \"./daemon/interactive-probe.js\";\nexport * from \"./delivery/captain-delivery.js\";\nexport * from \"./delivery/defer-delivery.js\";\nexport * from \"./session-freshness.js\";\nexport * from \"./crew-protocol.js\";\nexport * from \"./crew-lifecycle.js\";\nexport * from \"./telegram/index.js\";\nexport * from \"./crew-routing.js\";\nexport * from \"./restart-daemon.js\";\nexport * from \"./group-dispatch.js\";\nexport * from \"./launch-workspace.js\";\nexport * from \"./side-session.js\";\nexport * from \"./crew-spawn.js\";\nexport * from \"./lifecycle-source.js\";\n","// src/control/launchd.ts\nimport { execFileSync } from \"node:child_process\";\nimport { mkdirSync, writeFileSync, readFileSync, existsSync, openSync, writeSync, closeSync, unlinkSync, constants } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport const LABEL = \"com.squadrant.daemon\";\n\nexport function plistPath(): string {\n return join(homedir(), \"Library\", \"LaunchAgents\", `${LABEL}.plist`);\n}\n\n/**\n * Canonical path to the compiled daemon entrypoint, resolved relative to THIS\n * module (squadrantd.js is a sibling of the bundled entry in <dist>/). This is\n * the single source of truth — callers must NOT recompute it (a hardcoded\n * ~/.config/squadrant/dist path crash-loops the agent with MODULE_NOT_FOUND\n * because runtime-sync never mirrors compiled output there).\n */\nexport function daemonEntryPath(): string {\n const p = join(dirname(fileURLToPath(import.meta.url)), \"squadrantd.js\");\n if (!existsSync(p)) {\n throw new Error(\n `daemonEntryPath: compiled entry not found at '${p}'; ` +\n `run 'npm run build' — a src-tree or missing path in the launchd plist causes a MODULE_NOT_FOUND crash-loop (#259)`,\n );\n }\n return p;\n}\n\nfunction xmlEscape(s: string): string {\n return s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\n}\n\n/**\n * Strip per-shell ephemeral PATH entries (Claude Code plugin cache dirs) and\n * dedupe so the plist content is stable across squadrant invocations from\n * different shells. Without this, a captain shell (PATH includes\n * ~/.claude/plugins/cache/* bin dirs) vs a fresh login shell would each\n * rewrite the plist and kickstart -k the daemon, killing in-flight tasks\n * (incident 2026-05-21, observations 8704/8707/8711).\n */\nexport function sanitizePathForPlist(path: string): string {\n const seen = new Set<string>();\n const stable: string[] = [];\n for (const p of path.split(\":\")) {\n if (!p) continue;\n if (p.includes(\"/.claude/plugins/\")) continue;\n if (seen.has(p)) continue;\n seen.add(p);\n stable.push(p);\n }\n return stable.join(\":\");\n}\n\nexport const AGENT_BINS = [\"cmux\", \"claude\", \"opencode\", \"codex\", \"gemini\", \"node\"];\n\n/**\n * Resolve absolute directories for known agent + tool binaries via `which`, so\n * the launchd daemon's PATH includes them regardless of the install-time shell.\n * Missing binaries are skipped silently.\n */\nexport function resolveAgentBinDirs(): string[] {\n const dirs: string[] = [];\n for (const bin of AGENT_BINS) {\n try {\n const out = execFileSync(\"which\", [bin], { encoding: \"utf-8\", stdio: [\"ignore\", \"pipe\", \"ignore\"] });\n const resolved = out.trim();\n if (resolved) dirs.push(dirname(resolved));\n } catch {\n // binary not found on this machine — skip\n }\n }\n const seen = new Set<string>();\n return dirs.filter(d => {\n if (seen.has(d)) return false;\n seen.add(d);\n return true;\n });\n}\n\n/**\n * Compose a stable daemon PATH by prepending resolved agent bin dirs to the\n * sanitized install-shell PATH. Agent dirs take priority (prepended) and are\n * deduped against the sanitized entries so the output is deterministic.\n */\nexport function buildDaemonPath(shellPath: string): string {\n const agentDirs = resolveAgentBinDirs();\n const sanitized = sanitizePathForPlist(shellPath);\n if (agentDirs.length === 0) return sanitized;\n const parts = [...agentDirs, ...sanitized.split(\":\")];\n const seen = new Set<string>();\n return parts.filter(p => {\n if (!p || seen.has(p)) return false;\n seen.add(p);\n return true;\n }).join(\":\");\n}\n\n/**\n * Red-team #3 (High): launchd starts the daemon with a minimal PATH that does\n * NOT include where `claude`/`codex`/`opencode` live (nvm/cmux dirs), so every\n * headless `spawn` failed `ENOENT` in the real deployment (shell tests + fake\n * spawn hid it). We bake the installing process's PATH into the plist so the\n * daemon and its spawned crew children resolve the provider binaries.\n */\nexport function renderPlist(nodeBin: string, daemonEntry: string, pathEnv = \"\"): string {\n const logPath = join(homedir(), \".config\", \"squadrant\", \"squadrantd.log\");\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key><string>${LABEL}</string>\n <key>ProgramArguments</key>\n <array><string>${xmlEscape(nodeBin)}</string><string>${xmlEscape(daemonEntry)}</string></array>\n <key>EnvironmentVariables</key>\n <dict><key>PATH</key><string>${xmlEscape(pathEnv)}</string></dict>\n <key>RunAtLoad</key><true/>\n <key>KeepAlive</key><true/>\n <key>ThrottleInterval</key><integer>10</integer>\n <key>StandardErrorPath</key><string>${xmlEscape(logPath)}</string>\n <key>StandardOutPath</key><string>${xmlEscape(logPath)}</string>\n</dict>\n</plist>\n`;\n}\n\n/**\n * Semantic fingerprint of the <array> block inside the rendered plist. Used by\n * ensureDaemon to distinguish program-argument changes (merit a full restart)\n * from PATH-only changes (write updated plist, don't bounce the daemon).\n */\nexport function programArgsBlock(nodeBin: string, daemonEntry: string): string {\n return `<array><string>${xmlEscape(nodeBin)}</string><string>${xmlEscape(daemonEntry)}</string></array>`;\n}\n\n/**\n * Pure: which kickstart argv to use. `-k` (kill-then-restart) ONLY when the\n * plist changed. A plain `kickstart` starts a down daemon and is a no-op for a\n * healthy one — so a routine CLI call never bounces a running daemon (this was\n * a real bug: ensureDaemon ran on every `squadrant` invocation and `kickstart -k`\n * killed+restarted the daemon each time, orphaning in-flight headless crew).\n */\nexport function kickstartArgv(target: string, plistChanged: boolean): string[] {\n return plistChanged ? [\"kickstart\", \"-k\", target] : [\"kickstart\", target];\n}\n\n// In-process dedup: JS is single-threaded and ensureDaemon is synchronous, so\n// true re-entrancy is impossible; this flag prevents sequential re-calls within\n// the same process (e.g. index.ts + crew-control.ts) from re-running the\n// bootout/bootstrap pair needlessly.\nlet restartInFlight = false;\n\n/** @internal — reset only in tests; never call from production code */\nexport function _resetRestartInFlightForTest(): void {\n restartInFlight = false;\n}\n\nexport function daemonLockPath(): string {\n return join(homedir(), \".config\", \"squadrant\", \"daemon.lock\");\n}\n\n/**\n * Acquire a cross-process filesystem lock at ~/.config/squadrant/daemon.lock.\n * Uses O_EXCL for atomic, race-free creation. Cleans up stale locks (dead PID)\n * before the acquisition loop. Retries with a ~50 ms synchronous sleep up to\n * 20 times (~1 s total) before giving up.\n * Returns true on success, false if another live process holds the lock.\n */\nexport function tryAcquireDaemonLock(): boolean {\n const lp = daemonLockPath();\n\n // Stale-lock cleanup: if the owning PID is no longer alive, remove the file\n // so the next O_EXCL attempt succeeds.\n if (existsSync(lp)) {\n try {\n const pid = parseInt(readFileSync(lp, \"utf-8\").trim(), 10);\n if (!Number.isFinite(pid) || pid <= 0) {\n unlinkSync(lp);\n } else {\n try { process.kill(pid, 0); }\n catch { unlinkSync(lp); } // ESRCH → process dead, steal the lock\n }\n } catch { /* read/parse/unlink error — fall through to O_EXCL attempt */ }\n }\n\n // Atomic acquisition: O_EXCL guarantees only one process creates the file.\n for (let i = 0; i < 20; i++) {\n try {\n const fd = openSync(lp, constants.O_EXCL | constants.O_CREAT | constants.O_WRONLY);\n writeSync(fd, String(process.pid));\n closeSync(fd);\n return true;\n } catch {\n if (i < 19) {\n // Synchronous sleep: gives the lock-holder time to finish and release.\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50);\n }\n }\n }\n return false; // another live process held the lock for > ~1 s — skip restart\n}\n\n/** Release the lock written by tryAcquireDaemonLock. */\nexport function releaseDaemonLock(): void {\n try { unlinkSync(daemonLockPath()); } catch { /* already cleaned up */ }\n}\n\n/**\n * Idempotent & cheap. Never throws fatally. Writes/reloads the plist ONLY when\n * its content actually changed; Distinguishes program-argument drift (warrants\n * a full restart via bootout + bootstrap + kickstart) from PATH-only drift\n * (write the plist for the next natural restart but never bounce a healthy\n * daemon). Uses plain `kickstart` (never -k) to avoid the race between -k and\n * bootout's exit handler that produced exit-113 \"service not loaded\" errors.\n * The daemon entry is resolved internally (see daemonEntryPath) so no caller\n * can pass a wrong path.\n *\n * Concurrency guards:\n * - restartInFlight flag: prevents sequential re-calls within this process.\n * - tryAcquireDaemonLock: serialises concurrent SEPARATE squadrant processes\n * via a filesystem lock so only one runs bootout/bootstrap at a time.\n */\nexport function ensureDaemon(nodeBin: string = process.execPath): void {\n if (restartInFlight) return;\n restartInFlight = true;\n\n if (!tryAcquireDaemonLock()) {\n // Another process is handling the restart; it will be done by the time the\n // CLI tries to reach the daemon socket.\n return;\n }\n\n try {\n const p = plistPath();\n const entry = daemonEntryPath();\n const desired = renderPlist(nodeBin, entry, buildDaemonPath(process.env.PATH ?? \"\"));\n const current = existsSync(p) ? readFileSync(p, \"utf-8\") : null;\n const uid = process.getuid?.() ?? 0;\n const target = `gui/${uid}/${LABEL}`;\n\n const changed = current !== desired;\n // Semantic comparison: was the program-arg block itself different (not just\n // PATH)? Program-arg changes are rare (rebuild/reinstall) and merit a full\n // bootout+reload; PATH varies across terminals so it must NOT trigger a\n // bounce (would orphan in-flight RPCs).\n const programChanged = current !== null && changed\n && !current.includes(programArgsBlock(nodeBin, entry));\n\n if (changed) {\n mkdirSync(dirname(p), { recursive: true });\n writeFileSync(p, desired);\n }\n\n if (programChanged) {\n // unload the old instance so bootstrap picks up the new program args\n try { execFileSync(\"launchctl\", [\"bootout\", target], { stdio: \"ignore\" }); }\n catch { /* not loaded */ }\n }\n\n try { execFileSync(\"launchctl\", [\"bootstrap\", `gui/${uid}`, p], { stdio: \"ignore\" }); }\n catch { /* already bootstrapped */ }\n\n // Plain kickstart (never -k): no-op on a healthy daemon, starts one that\n // was booted-out above or that stopped for other reasons. -k is avoided\n // because it races with bootout's exit handler and produces exit-113 when\n // the service hasn't finished unloading.\n execFileSync(\"launchctl\", [\"kickstart\", target], { stdio: \"ignore\" });\n } catch (e) {\n // daemon ensure is best-effort (still don't throw); CLI fails loud on socket miss\n process.stderr.write(`[squadrant] warn: ensureDaemon failed (${e instanceof Error ? e.message : e})\\n`);\n } finally {\n releaseDaemonLock();\n }\n}\n","import { loadConfig } from \"@squadrant/shared\";\nimport type { RuntimeDriver, SquadrantConfig, TaskRecord } from \"@squadrant/shared\";\nimport type { DirectCmuxReader } from \"./interfaces.js\";\n\nconst TAIL_LINES = 25;\n\n// MUST match `titleFor` in src/commands/crew.ts — the crew tab title convention\n// the daemon uses to find a crew's pane (🔧 <project>:<name>).\nexport function crewPaneTitle(project: string, name: string): string {\n return `🔧 ${project}:${name}`;\n}\n\nexport type SurfaceLiveness = \"alive\" | \"gone\" | \"unknown\";\n\n/**\n * Pure: decide an interactive crew's surface liveness from a resolved surface\n * list (#139). Three-valued so a transient cmux outage never false-reaps a live\n * crew — \"gone\" means PROVABLY absent, not \"couldn't tell\":\n * - wantTitle null (crew has no name) → \"unknown\"\n * - surfaceTitles null (could not enumerate) → \"unknown\"\n * - title present in the list → \"alive\"\n * - title absent from an enumerated list → \"gone\"\n */\nexport function surfaceVerdict(surfaceTitles: string[] | null, wantTitle: string | null): SurfaceLiveness {\n if (!wantTitle) return \"unknown\";\n if (surfaceTitles == null) return \"unknown\";\n return surfaceTitles.includes(wantTitle) ? \"alive\" : \"gone\";\n}\n\n/**\n * I/O: enumerate the captain workspace's surface titles for a crew's project.\n * Returns null on ANY failure — surfaceVerdict maps null → \"unknown\" so we never\n * reap on an inconclusive probe. Never throws.\n */\nasync function listCaptainSurfaceTitles(\n rec: TaskRecord,\n makeRuntime: (project: string, config: SquadrantConfig) => RuntimeDriver | null,\n): Promise<string[] | null> {\n try {\n const config = loadConfig();\n const proj = config.projects[rec.project];\n if (!proj) return null;\n const runtime = makeRuntime(rec.project, config);\n if (!runtime) return null;\n const captain = await runtime.status(proj.captainName);\n if (!captain) return null;\n const surfaces = await runtime.listSurfaces(captain.id);\n return surfaces.map((s) => s.title ?? \"\");\n } catch {\n return null;\n }\n}\n\n/**\n * Build the daemon's interactive surface-liveness probe (#139 backstop).\n * @param makeRuntime Factory provided by the host (root package); omit for tests.\n */\nexport function createSurfaceLivenessProbe(\n makeRuntime?: (project: string, config: SquadrantConfig) => RuntimeDriver | null,\n): (rec: TaskRecord) => Promise<SurfaceLiveness> {\n return async (rec) => {\n if (rec.mode !== \"interactive\" || !rec.name) return \"unknown\";\n if (!makeRuntime) return \"unknown\";\n const titles = await listCaptainSurfaceTitles(rec, makeRuntime);\n return surfaceVerdict(titles, crewPaneTitle(rec.project, rec.name));\n };\n}\n\n/**\n * Build the daemon's best-effort crew-pane reader (Phase 2b).\n * @param makeRuntime Factory provided by the host; required for real pane reads.\n */\nexport function createCrewPaneReader(\n makeRuntime?: (project: string, config: SquadrantConfig) => RuntimeDriver | null,\n): (rec: TaskRecord) => Promise<string | null> {\n return async (rec) => {\n try {\n if (!rec.name || !makeRuntime) return null;\n const config = loadConfig();\n const proj = config.projects[rec.project];\n if (!proj) return null;\n const runtime = makeRuntime(rec.project, config);\n if (!runtime) return null;\n const captain = await runtime.status(proj.captainName);\n if (!captain) return null;\n const surfaces = await runtime.listSurfaces(captain.id);\n const want = crewPaneTitle(rec.project, rec.name);\n const pane = surfaces.find((s) => s.title === want);\n if (!pane) return null;\n const screen = await runtime.readPaneScreen(pane);\n if (!screen) return null;\n return screen.split(/\\r?\\n/).slice(-TAIL_LINES).join(\"\\n\");\n } catch {\n return null;\n }\n };\n}\n\n/**\n * Build a direct surface-liveness probe for daemon-direct mode (#332).\n * Uses DirectCmuxReader (seam interface implemented by DaemonCmux in root).\n */\nexport function createDirectSurfaceLivenessProbe(\n cmux: DirectCmuxReader,\n getCaptainTitle: (project: string) => string,\n): (rec: TaskRecord) => Promise<SurfaceLiveness> {\n return async (rec) => {\n try {\n if (rec.mode !== \"interactive\" || !rec.name) return \"unknown\";\n const wsId = await cmux.findWorkspaceId(getCaptainTitle(rec.project));\n if (!wsId) return \"unknown\";\n const surfaces = await cmux.listSurfaces(wsId);\n if (surfaces.length === 0) return \"unknown\";\n return surfaceVerdict(\n surfaces.map((s) => s.title ?? \"\"),\n crewPaneTitle(rec.project, rec.name),\n );\n } catch {\n return \"unknown\";\n }\n };\n}\n\n/**\n * Build a direct crew-pane reader for daemon-direct mode (#332).\n * Uses DirectCmuxReader (seam interface implemented by DaemonCmux in root).\n */\nexport function createDirectCrewPaneReader(\n cmux: DirectCmuxReader,\n getCaptainTitle: (project: string) => string,\n): (rec: TaskRecord) => Promise<string | null> {\n return async (rec) => {\n try {\n if (!rec.name) return null;\n const wsId = await cmux.findWorkspaceId(getCaptainTitle(rec.project));\n if (!wsId) return null;\n const surfaces = await cmux.listSurfaces(wsId);\n const want = crewPaneTitle(rec.project, rec.name);\n const pane = surfaces.find((s) => s.title === want);\n if (!pane) return null;\n const screen = await cmux.readPaneScreen(pane);\n if (!screen) return null;\n return screen.split(/\\r?\\n/).slice(-TAIL_LINES).join(\"\\n\");\n } catch {\n return null;\n }\n };\n}\n","// src/control/codex/gate.ts\n// Pure helpers for the interactive-codex HITL gate primitive (spec §4.9).\nimport type { Gate } from \"@squadrant/shared\";\n\nexport function makeGate(opts: {\n taskId: string;\n kind: \"input\" | \"approval\";\n question: string;\n now: number;\n mkId: () => string;\n}): Gate {\n return {\n gateId: opts.mkId(),\n taskId: opts.taskId,\n kind: opts.kind,\n question: opts.question,\n state: \"pending\",\n createdAt: opts.now,\n };\n}\n\nexport function resolveGate(g: Gate, by: { resolvedBy: string; resolution: unknown }): Gate {\n return { ...g, state: \"resolved\", resolvedBy: by.resolvedBy, resolution: by.resolution };\n}\n\nexport function timeoutGate(g: Gate): Gate {\n return { ...g, state: \"timeout\" };\n}\n","// src/control/daemon/context.ts\n// SquadrantdOpts, defaultIsPidAlive, DaemonContext, and buildContext.\n// Kept here (not in squadrantd.ts) so daemon/* modules can import this file\n// without creating a circular dependency on the host entrypoint.\n// squadrantd.ts re-exports SquadrantdOpts and defaultIsPidAlive for backward compat.\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { spawn as realSpawn } from \"node:child_process\";\nimport { writeFileSync, mkdirSync } from \"node:fs\";\nimport { createStore } from \"../store.js\";\nimport { createDaemon } from \"./reduce.js\";\nimport { loadConfig } from \"@squadrant/shared\";\nimport type { TaskRecord, ControlEvent, Gate, AutoConfigResult } from \"@squadrant/shared\";\nimport type { Socket } from \"node:net\";\nimport type { PaneRef } from \"@squadrant/shared\";\nimport type { AgentDriver, OpencodeBridge, CmuxEventsBridge, DaemonSurfaceDriver } from \"../interfaces.js\";\nimport type { TelegramBridge } from \"../telegram/bridge.js\";\nimport type { AttachFrame } from \"../protocol.js\";\nimport type { LifecycleSource } from \"../lifecycle-source.js\";\nimport { LivenessRegistry } from \"./liveness-registry.js\";\n\n// ── Public injectable options (equivalent of old squadrantd.ts SquadrantdOpts) ───\n\nexport interface SquadrantdOpts {\n stateRoot?: string;\n sockPath?: string;\n sweepMs?: number; // 0 disables the interval (tests)\n isPidAlive?: (pid: number) => boolean;\n isSurfaceAlive?: (rec: TaskRecord) => Promise<\"alive\" | \"gone\" | \"unknown\">;\n spawn?: typeof realSpawn;\n /**\n * Push-notification hook (#109). Defaults to appending a structured event\n * to the mailbox file at <stateRoot>/inbox/<project>.log; an injector\n * process inside the captain workspace tails the file and delivers entries\n * to the captain pane. Tests inject a fake to assert call shape.\n */\n notify?: (args: {\n project: string;\n message: string;\n record: TaskRecord;\n event: ControlEvent;\n }) => Promise<void> | void;\n /** Background rotation timer interval (ms). 0 disables. Default 60_000. */\n rotationIntervalMs?: number;\n /** Mailbox rotation thresholds (size/age/retention). */\n mailboxConfig?: {\n maxBytes?: number;\n maxAgeMs?: number;\n keepCount?: number;\n };\n /** Inject a fake driver for tests. Defaults to a real CodexInteractiveDriver. */\n codexDriver?: AgentDriver;\n /** Inject a fake headless launcher for tests to avoid real process spawns. */\n launchHeadless?: (rec: TaskRecord) => Promise<void>;\n /** Override which projects appear in the Tier 2 per-project snapshot. */\n registeredProjects?: string[];\n /** Inject a fake opencode SSE bridge for tests. */\n opencodeBridge?: OpencodeBridge;\n /** B1: inject a fake cmux events bridge for tests. */\n cmuxEventsBridge?: CmuxEventsBridge;\n /** Opt-in Telegram bridge (#65). Inject a fake for tests; in production the host\n * builds the real one only when config.telegram is present (and not under vitest,\n * since pushLifecycle is composed onto notify and would hit the network). */\n telegramBridge?: TelegramBridge;\n /** B4: registered LifecycleSource instances (cmux-store/native-hook/codex-appserver),\n * for aggregating per-source health into the snapshot. Empty in tests unless injected. */\n lifecycleSources?: LifecycleSource[];\n /** Inject a fake surface driver for testing daemon-direct delivery. */\n daemonCmux?: DaemonSurfaceDriver;\n /** Factory for constructing the surface driver in production. */\n makeDaemonCmux?: () => DaemonSurfaceDriver;\n /** Injected captain-surface mapping (project → PaneRef) for tests. */\n captainSurfaces?: Record<string, PaneRef>;\n /** #348: override the cmux socket auto-config re-check. */\n runCmuxAutoConfig?: () => Promise<AutoConfigResult>;\n /** #466 self-heal: re-deliver a crew's first turn when the daemon detects it\n * never landed. Production wiring lives in squadrantd.ts (host); inject a\n * fake for tests. See DaemonDeps.resendFirstTurn (daemon/reduce.ts) for the\n * full contract. */\n resendFirstTurn?: (rec: TaskRecord) => Promise<{ delivered: boolean }>;\n /** #579/#484 Gap 1: config-free, out-of-band fault-alert channel — routed\n * through the notifier plugin slot (@squadrant/workspaces' NotifierRegistry,\n * cmux by default) rather than hardwired to Telegram, so it works for every\n * install (most have no Telegram configured) and for any future notifier\n * provider. core can't import @squadrant/workspaces (one-way DAG), so the\n * host (squadrantd.ts) builds the real implementation and injects it here —\n * mirrors how telegramBridge is wired. Must never throw/block; best-effort. */\n notifyFault?: (project: string, text: string) => Promise<void> | void;\n}\n\nexport function defaultIsPidAlive(pid: number): boolean {\n try { process.kill(pid, 0); return true; }\n catch (e: any) { return e?.code === \"EPERM\"; } // EPERM = alive but not ours; ESRCH = dead\n}\n\n// ── Shared state bag ──────────────────────────────────────────────────────────\n\n/** All shared mutable state for the running daemon. Most fields are set in\n * buildContext; late-bound fields (d, notify, broadcast, etc.) are assigned\n * by start.ts after building the daemon and factories, before any event fires. */\nexport interface DaemonContext {\n opts: SquadrantdOpts;\n stateRoot: string;\n sockPath: string;\n store: ReturnType<typeof createStore>;\n bootedAt: number;\n /** Mutable box so sweep timer can update lastSweepAt without a closure rebind. */\n lastSweepAt: { value: number | null };\n taskTimeoutMs: number | undefined;\n isPidAlive: (pid: number) => boolean;\n spawn: typeof realSpawn;\n resultsDir: string;\n writeResult: (id: string, payload: string) => string;\n log: (m: string) => void;\n /** Per-task live attach connections (spec §4.5/§4.6). */\n attachConns: Map<string, Set<Socket>>;\n /** Tasks being launched headlessly with no pid yet (#259). */\n inFlightHeadlessIds: Set<string>;\n /** Cancel handles for in-flight headless runs. */\n activeHeadlessKills: Set<() => void>;\n /** Ground-truth captain liveness — persisted, survives daemon restart (§4.1/§5.3). */\n livenessRegistry: LivenessRegistry;\n\n // ── Late-bound: assigned by start.ts before any timer/server fires ──────────\n\n /** Resolved daemon instance. */\n d: ReturnType<typeof createDaemon>;\n /** Resolved notify function. */\n notify: (args: { project: string; message: string; record: TaskRecord; event: ControlEvent }) => Promise<void> | void;\n /** Resolved surface driver for daemon-direct delivery. */\n daemonCmux: DaemonSurfaceDriver | undefined;\n /** #466 self-heal: resolved first-turn resend function (undefined when\n * opts.resendFirstTurn was not provided, e.g. non-cmux deployments). */\n resendFirstTurn: ((rec: TaskRecord) => Promise<{ delivered: boolean }>) | undefined;\n /** Resolved agent driver. */\n codexDriver: AgentDriver;\n /** Resolved opencode SSE bridge. */\n opencodeBridge: OpencodeBridge;\n /** Resolved cmux events bridge. */\n cmuxEventsBridge: CmuxEventsBridge;\n /** Resolved Telegram bridge — undefined when config.telegram is absent. */\n telegramBridge?: TelegramBridge;\n /** Resolved out-of-band fault-alert function (#579/#484 Gap 1) — ALWAYS a\n * real function, never undefined: defaults to a no-op here (core has no\n * notifier implementation to fall back to) but squadrantd.ts (the host)\n * always overrides it with the real notifier-registry-backed one before any\n * event can fire, so production never silently no-ops. Tests that construct\n * DaemonContext directly (bypassing squadrantd.ts) keep the no-op, which is\n * correct for isolated unit tests. */\n notifyFault: (project: string, text: string) => Promise<void> | void;\n /** B4: registered LifecycleSource instances for per-source health aggregation. */\n lifecycleSources: LifecycleSource[];\n /** Fan-out to attach clients (set by createAttach). */\n broadcast: (taskId: string, f: AttachFrame) => void;\n /** Schedule gate promotion (set by createAttach). */\n schedulePromotion: (taskId: string, requestId: number, kind: \"input\" | \"approval\", question: string) => void;\n /** Cancel gate timers for a task (set by createAttach). */\n cancelPromotionsFor: (taskId: string) => void;\n}\n\n/** Initialize the pure-state fields of DaemonContext from opts.\n * Late-bound fields are zero-initialized and MUST be set by start.ts\n * (or squadrantd.ts for drivers) before any event, timer, or socket fires. */\nexport function buildContext(opts: SquadrantdOpts): DaemonContext {\n const stateRoot = opts.stateRoot ?? join(homedir(), \".config\", \"squadrant\", \"state\");\n const sockPath = opts.sockPath ?? join(homedir(), \".config\", \"squadrant\", \"squadrant.sock\");\n const store = createStore(stateRoot);\n const bootedAt = Date.now();\n const taskTimeoutMs = loadConfig().defaults.taskTimeoutMs;\n const isPidAlive = opts.isPidAlive ?? defaultIsPidAlive;\n const spawn = opts.spawn ?? realSpawn;\n const resultsDir = join(stateRoot, \"_results\");\n mkdirSync(resultsDir, { recursive: true });\n const writeResult = (id: string, payload: string) => {\n const p = join(resultsDir, `${id}.txt`);\n writeFileSync(p, payload);\n return p;\n };\n const log = (m: string) =>\n process.stderr.write(`[squadrantd] ${new Date().toISOString()} ${m}\\n`);\n\n return {\n opts,\n stateRoot,\n sockPath,\n store,\n bootedAt,\n lastSweepAt: { value: null },\n taskTimeoutMs,\n isPidAlive,\n spawn,\n resultsDir,\n writeResult,\n log,\n attachConns: new Map(),\n inFlightHeadlessIds: new Set(),\n activeHeadlessKills: new Set(),\n livenessRegistry: (() => {\n const r = new LivenessRegistry({ path: join(stateRoot, \"liveness.json\") });\n r.load();\n return r;\n })(),\n resendFirstTurn: opts.resendFirstTurn,\n // Late-bound — start.ts fills these before first use:\n d: null as unknown as ReturnType<typeof createDaemon>,\n notify: null as unknown as DaemonContext[\"notify\"],\n daemonCmux: undefined,\n codexDriver: null as unknown as AgentDriver,\n opencodeBridge: null as unknown as OpencodeBridge,\n cmuxEventsBridge: null as unknown as CmuxEventsBridge,\n telegramBridge: undefined,\n notifyFault: opts.notifyFault ?? (() => {}),\n lifecycleSources: opts.lifecycleSources ?? [],\n broadcast: () => {},\n schedulePromotion: () => {},\n cancelPromotionsFor: () => {},\n };\n}\n","import { writeFileSync, readFileSync, renameSync } from \"node:fs\";\nimport type { LivenessEntry } from \"@squadrant/shared\";\nimport { reconcileLiveness } from \"../liveness.js\";\n\nexport interface LivenessRegistryOpts {\n path: string;\n readFile?: (p: string) => string | undefined;\n writeFile?: (p: string, content: string) => void;\n}\n\n/** Core-owned, disk-persisted registry — the single liveness source of truth. */\nexport class LivenessRegistry {\n private readonly path: string;\n private readonly readFile: (p: string) => string | undefined;\n private readonly writeFile: (p: string, content: string) => void;\n private map = new Map<string, LivenessEntry>();\n\n constructor(opts: LivenessRegistryOpts) {\n this.path = opts.path;\n this.readFile = opts.readFile ?? ((p) => { try { return readFileSync(p, \"utf-8\"); } catch { return undefined; } });\n this.writeFile = opts.writeFile ?? ((p, c) => { writeFileSync(`${p}.tmp`, c); renameSync(`${p}.tmp`, p); });\n }\n\n load(): void {\n const raw = this.readFile(this.path);\n if (!raw) return;\n try {\n const arr = JSON.parse(raw) as LivenessEntry[];\n this.map = new Map(arr.map((e) => [e.project, e]));\n } catch { this.map = new Map(); }\n }\n\n get(project: string): LivenessEntry | undefined { return this.map.get(project); }\n all(): LivenessEntry[] { return [...this.map.values()]; }\n\n apply(next: LivenessEntry): void {\n this.map.set(next.project, reconcileLiveness(this.map.get(next.project), next));\n this.persist();\n }\n\n markEnded(project: string, at: number): void {\n const e = this.map.get(project);\n if (!e) return;\n this.map.set(project, { ...e, lastState: \"end\", lastSeenAt: at });\n this.persist();\n }\n\n setPidAlive(project: string, alive: boolean, at: number): void {\n const e = this.map.get(project);\n if (!e) return;\n this.map.set(project, { ...e, pidAlive: alive, lastSeenAt: at });\n this.persist();\n }\n\n private persist(): void {\n try { this.writeFile(this.path, JSON.stringify(this.all(), null, 2)); } catch { /* best-effort */ }\n }\n}\n","// src/control/daemon/attach.ts\n// Attach fan-out and gate-promotion logic (spec §4.5/§4.6/§4.9).\nimport { randomUUID } from \"node:crypto\";\nimport { encodeFrame } from \"../protocol.js\";\nimport { makeGate } from \"../gate.js\";\nimport type { AttachFrame } from \"../protocol.js\";\nimport type { Gate } from \"@squadrant/shared\";\nimport type { DaemonContext } from \"./context.js\";\n\nexport interface AttachHandlers {\n broadcast: (taskId: string, f: AttachFrame) => void;\n schedulePromotion: (taskId: string, requestId: number, kind: \"input\" | \"approval\", question: string) => void;\n cancelPromotionsFor: (taskId: string) => void;\n}\n\n/** Build the attach fan-out and gate-promotion machinery. Call once in start.ts\n * immediately after buildContext; assign the returned handlers onto ctx so the\n * driver emit callbacks can reference them via the context object. */\nexport function createAttach(ctx: DaemonContext): AttachHandlers {\n const { attachConns, store, log } = ctx;\n\n function broadcast(taskId: string, f: AttachFrame): void {\n const conns = attachConns.get(taskId);\n if (!conns) return;\n const wire = encodeFrame(f);\n for (const conn of conns) {\n try { conn.write(wire); } catch { /* client gone; onAttachClose will clean up */ }\n }\n }\n\n // ── Gate promotion (spec §4.9) ─────────────────────────────────────────────\n // When a server-request event fires and no client is attached, start a 5s\n // timer. If still unattached at fire time, promote to a Gate in the store\n // and broadcast gate-promoted so any later-attaching client can offer takeover.\n const pendingGateTimers = new Map<string, { taskId: string; timer: NodeJS.Timeout }>();\n\n function schedulePromotion(\n taskId: string,\n requestId: number,\n kind: \"input\" | \"approval\",\n question: string,\n ): void {\n // If a client is already attached for this task, no promotion needed.\n const conns = attachConns.get(taskId);\n if (conns && conns.size > 0) return;\n const key = `${taskId}#${requestId}`;\n // Clear any prior timer for the same (taskId, requestId).\n const prior = pendingGateTimers.get(key);\n if (prior) clearTimeout(prior.timer);\n const timer = setTimeout(() => {\n pendingGateTimers.delete(key);\n // Re-check at fire time — a client may have attached in the 5s window.\n if (attachConns.get(taskId)?.size) return;\n const rec = store.listAll().find((r) => r.id === taskId);\n if (!rec) return;\n const gate: Gate = makeGate({ taskId, kind, question, now: Date.now(), mkId: () => randomUUID() });\n const gates = [...(rec.gates ?? []), gate];\n store.put({ ...rec, gates });\n broadcast(taskId, { type: \"gate-promoted\", taskId, gateId: gate.gateId });\n log(`gate promoted gateId=${gate.gateId} taskId=${taskId} kind=${kind}`);\n }, 5_000);\n timer.unref?.();\n pendingGateTimers.set(key, { taskId, timer });\n }\n\n function cancelPromotionsFor(taskId: string): void {\n for (const [key, slot] of pendingGateTimers.entries()) {\n if (slot.taskId === taskId) {\n clearTimeout(slot.timer);\n pendingGateTimers.delete(key);\n }\n }\n }\n\n return { broadcast, schedulePromotion, cancelPromotionsFor };\n}\n","// src/control/daemon/start.ts\n// Core daemon assembly: wires all daemon/* factories, runs boot recovery,\n// starts timers, and returns the DaemonHandle.\n// Concrete driver construction (CodexInteractiveDriver, DaemonCmux, etc.)\n// lives in the host (squadrantd.ts) — this file stays free of those imports.\nimport { join, dirname } from \"node:path\";\nimport { readdir } from \"node:fs/promises\";\nimport { createDaemon } from \"./reduce.js\";\nimport { createProbes, buildSurfaceProbe } from \"./probes.js\";\nimport { createDelivery } from \"./delivery-loop.js\";\nimport { createGateResolver } from \"./gates.js\";\nimport { createServer } from \"./server.js\";\nimport { rotateIfNeeded, mailboxStats, readCursor } from \"../mailbox.js\";\nimport { projectHealth, deriveCaptainState, type ComponentHealth } from \"../liveness.js\";\nimport type { DaemonSnapshotInputs } from \"../snapshot.js\";\nimport { loadConfig, TERMINAL_STATES, ensureCmuxAutoConfig } from \"@squadrant/shared\";\nimport { distBuiltAt, gatherLogStats, gatherStoreStats, gatherResults } from \"./snapshot-gather.js\";\nimport type { SquadrantdOpts, DaemonContext } from \"./context.js\";\n\nconst CURSOR_SUBSCRIBER = \"captain\";\nconst SNAPSHOT_LOG_WINDOW_MS = 60 * 60 * 1000;\n\nexport interface DaemonHandle {\n /** `reason` is folded into the exit log line (e.g. \"SIGTERM\", \"SIGINT\") so a\n * restart is diagnosable from the log instead of inferred (#535). */\n stop(reason?: string): Promise<void>;\n tickDelivery: (() => Promise<void>) | undefined;\n tickProbe: (() => Promise<void>) | undefined;\n}\n\n/** Wire all daemon/* factories, run boot recovery, start timers.\n * ctx must already have: attach handlers, codexDriver, opencodeBridge,\n * cmuxEventsBridge, daemonCmux, daemonDirectCmux set on it by the host. */\nexport function startDaemon(ctx: DaemonContext, opts: SquadrantdOpts, pkgVersion: string): DaemonHandle {\n const {\n stateRoot, store, log, isPidAlive, resultsDir,\n taskTimeoutMs, inFlightHeadlessIds, activeHeadlessKills,\n broadcast, cancelPromotionsFor,\n } = ctx;\n const { daemonCmux } = ctx;\n\n const probes = createProbes(ctx);\n const { defaultNotify, deliveryTick: initialDeliveryTick, deliveryStats } = createDelivery(ctx, daemonCmux);\n // Compose the Telegram outbound push onto the notify fan-out: a captain\n // notification also pushes to the project's Telegram topic. When no bridge is\n // configured, notify is the base function unchanged (zero behavior change).\n // pushLifecycle is best-effort and never throws (the bridge swallows errors),\n // so it can't delay or break captain delivery.\n const baseNotify = opts.notify ?? defaultNotify;\n const notify: DaemonContext[\"notify\"] = ctx.telegramBridge\n ? async (args) => {\n await baseNotify(args);\n ctx.telegramBridge!.pushLifecycle(args.project, args.event);\n }\n : baseNotify;\n const surfaceProbe = buildSurfaceProbe(ctx, probes, daemonCmux);\n\n const ingest = (project: string) => (e: import(\"@squadrant/shared\").ControlEvent) =>\n void ctx.d.handle({ kind: \"event\", project, event: e });\n\n const d = createDaemon({\n store, now: () => Date.now(), isPidAlive, notify, taskTimeoutMs,\n isSurfaceAlive: surfaceProbe,\n resendFirstTurn: ctx.resendFirstTurn,\n launchHeadless: opts.launchHeadless!,\n isHeadlessInFlight: (id) => inFlightHeadlessIds.has(id),\n launchInteractive: async (rec) => {\n if (rec.provider === \"codex\") {\n await ctx.codexDriver.dispatch(rec as any);\n return;\n }\n if (rec.provider === \"claude\") {\n ingest(rec.project)({ type: \"task.started\", id: rec.id });\n return;\n }\n if (rec.provider === \"opencode\") {\n ingest(rec.project)({ type: \"task.started\", id: rec.id });\n if (rec.serverPort) ctx.opencodeBridge.start({ taskId: rec.id, port: rec.serverPort });\n return;\n }\n throw new Error(\n `interactive mode is not yet implemented for provider '${rec.provider}'; only 'codex', 'claude', and 'opencode' are supported`,\n );\n },\n resolveInteractiveGate: createGateResolver(ctx),\n });\n\n ctx.d = d;\n\n // ── Health + snapshot ─────────────────────────────────────────────────────\n\n function buildHealth(only?: string): ComponentHealth[] {\n const config = loadConfig();\n const now = Date.now();\n const known = new Set<string>([\n ...Object.keys(config.projects),\n ...store.listAll().map((t) => t.project),\n ]);\n const names = only ? [only] : [...known];\n const out: ComponentHealth[] = [];\n for (const project of names) {\n const proj = config.projects[project];\n const captainName = proj?.captainName ?? `${project}-captain`;\n // Captain liveness from the ground-truth registry (Task 4) — runtime\n // snapshot + pid floor, survives daemon restart (§4.1/§4.5).\n const capEntry = ctx.livenessRegistry.get(project);\n out.push(\n ...projectHealth({\n project, now, captainName,\n captainStopped: null,\n captainState: deriveCaptainState(capEntry),\n commandPresent: null,\n crews: store.list(project),\n // #579/#484 Gap 3: surface the same deferral stats already exposed to\n // the snapshot (line ~135 below) on the health row too, so `squadrant\n // doctor` / `squadrant status --detailed` show a stuck delivery with\n // zero configuration.\n captainDeferral: deliveryStats(project),\n }),\n );\n }\n return out;\n }\n\n async function gatherSnapshotInputs(now: number): Promise<DaemonSnapshotInputs> {\n const logPath = join(dirname(stateRoot), \"squadrantd.log\");\n const tier2Projects = opts.registeredProjects ?? Object.keys(loadConfig().projects);\n const projects = await Promise.all(\n tier2Projects.map(async (project) => {\n const cursor = await readCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER });\n const storeStats = gatherStoreStats(store, stateRoot, project);\n return {\n project,\n mailbox: await mailboxStats(stateRoot, project),\n lastAckedSeq: cursor?.lastAckedSeq ?? 0,\n storeByState: storeStats.byState,\n corruptCount: storeStats.corruptCount,\n deferral: deliveryStats(project),\n };\n }),\n );\n return {\n pid: process.pid,\n processStartedAt: ctx.bootedAt,\n version: pkgVersion,\n distBuiltAt: distBuiltAt(),\n lastSweepAt: ctx.lastSweepAt.value,\n sweepCadenceMs: opts.sweepMs ?? 30_000,\n log: gatherLogStats(logPath, now, SNAPSHOT_LOG_WINDOW_MS),\n telegram: ctx.telegramBridge\n ? { configured: true, ...ctx.telegramBridge.health() }\n : { configured: false, polling: false, lastSuccessfulPollAt: null, lastError: null, lastErrorAt: null },\n lifecycleSources: ctx.lifecycleSources.map((s) => ({ name: s.name, ...(s.health?.() ?? { active: true, error: null }) })),\n health: buildHealth(),\n projects,\n results: gatherResults(resultsDir),\n };\n }\n\n // ── Boot recovery ─────────────────────────────────────────────────────────\n\n void (async () => {\n try { await d.reconcile(); }\n catch (e) { log(`reconcile on boot failed: ${(e as Error).message}`); }\n\n // Restart-reattach: reattach live codex crews. Guard against the storm\n // (each reattach re-spawns per-thread MCP servers). Skip terminal and stale tasks.\n // Inline predicate avoids importing from the concrete codex driver module.\n const bootNow = Date.now();\n const REATTACH_STALE_MS = 10 * 60_000;\n for (const rec of store.listAll()) {\n if (rec.provider !== \"codex\" || rec.mode !== \"interactive\") continue;\n if (TERMINAL_STATES.has(rec.state)) continue;\n // Inline of shouldReattachCodex (concrete driver module stays in host).\n const lastAttempt = rec.attempts?.at(-1);\n const last = lastAttempt?.lastHeartbeatAt ?? rec.lastHeartbeat ?? 0;\n if (bootNow - last > REATTACH_STALE_MS) continue;\n if (!lastAttempt?.resumeRef) continue;\n ctx.codexDriver.reattach(rec).catch((e: unknown) => {\n log(`reattach failed for ${rec.id}: ${(e as Error).message}`);\n });\n }\n\n // Re-subscribe opencode SSE bridge after a daemon bounce.\n for (const rec of store.listAll()) {\n if (rec.provider !== \"opencode\" || rec.mode !== \"interactive\") continue;\n if (TERMINAL_STATES.has(rec.state)) continue;\n if (!rec.serverPort) continue;\n ctx.opencodeBridge.start({ taskId: rec.id, port: rec.serverPort });\n }\n\n // B1: start cmux native-events bridge. Skipped under vitest unless injected.\n const enableCmuxEvents = loadConfig().defaults.cmuxEventsBridge !== false;\n const cmuxEventsSafe = !!opts.cmuxEventsBridge || !process.env.VITEST;\n if (enableCmuxEvents && cmuxEventsSafe) {\n try { ctx.cmuxEventsBridge.start(); }\n catch (e) { log(`cmux events bridge start failed: ${(e as Error).message}`); }\n }\n\n // Telegram inbound long-poll (opt-in). The real bridge is only constructed\n // by the host when config.telegram is present and not under vitest, so\n // ctx.telegramBridge here is either that real bridge or an injected fake.\n if (ctx.telegramBridge) {\n try { ctx.telegramBridge.start(); }\n catch (e) { log(`telegram bridge start failed: ${(e as Error).message}`); }\n }\n\n // #348: cmux socket auto-config on boot.\n const autoConfigSafe = !!opts.runCmuxAutoConfig || !process.env.VITEST;\n if (autoConfigSafe) {\n try {\n const r = await (opts.runCmuxAutoConfig ?? ensureCmuxAutoConfig)();\n if (r.configChanged) log(`cmux autoconfig: wrote automation socket mode to ${r.configPath}`);\n if (r.needsRestart && r.promptedThisRun) {\n log(\"cmux autoconfig: socket still rejects the daemon — restart cmux to enable daemon-direct delivery\");\n }\n } catch (e) {\n log(`cmux autoconfig failed: ${(e as Error).message}`);\n }\n }\n })();\n\n // ── Server + timers ───────────────────────────────────────────────────────\n\n const server = createServer(ctx, { buildHealth, gatherSnapshotInputs, cancelPromotionsFor, broadcast });\n // #535: greppable boot marker — a restart must be diagnosable from the log,\n // never inferred from process START time.\n log(`boot pid=${process.pid} version=${pkgVersion} socket=${ctx.sockPath} stateRoot=${stateRoot}`);\n\n let deliveryTick: (() => Promise<void>) | undefined = initialDeliveryTick;\n let probeTick: (() => Promise<void>) | undefined;\n\n if (daemonCmux) {\n probeTick = probes.buildInteractiveProbe({ cmux: daemonCmux });\n }\n\n let deliveryTimer: NodeJS.Timeout | undefined;\n if (daemonCmux && opts.sweepMs && opts.sweepMs > 0) {\n deliveryTimer = setInterval(() => {\n void deliveryTick!().catch((e: unknown) => log(`delivery tick error: ${(e as Error).message}`));\n }, 1000);\n deliveryTimer.unref?.();\n }\n\n let probeTimer: NodeJS.Timeout | undefined;\n if (daemonCmux && opts.sweepMs && opts.sweepMs > 0) {\n probeTimer = setInterval(() => {\n void probeTick!().catch((e: unknown) => log(`probe tick error: ${(e as Error).message}`));\n }, 10_000);\n probeTimer.unref?.();\n }\n\n let timer: NodeJS.Timeout | undefined;\n if (opts.sweepMs && opts.sweepMs > 0) {\n let sweeping = false;\n timer = setInterval(() => {\n if (sweeping) return;\n sweeping = true;\n ctx.lastSweepAt.value = Date.now();\n void d.sweep()\n .catch((e: unknown) => log(`sweep failed: ${(e as Error).message}`))\n .finally(() => { sweeping = false; });\n }, opts.sweepMs);\n timer.unref?.();\n }\n\n const rotationInterval = opts.rotationIntervalMs ?? 60_000;\n const mboxCfg = {\n maxBytes: opts.mailboxConfig?.maxBytes ?? 5 * 1024 * 1024,\n maxAgeMs: opts.mailboxConfig?.maxAgeMs ?? 7 * 24 * 60 * 60 * 1000,\n keepCount: opts.mailboxConfig?.keepCount ?? 3,\n };\n let rotationTimer: NodeJS.Timeout | undefined;\n if (rotationInterval > 0) {\n const inboxPath = join(stateRoot, \"inbox\");\n rotationTimer = setInterval(async () => {\n try {\n let entries: string[];\n try { entries = await readdir(inboxPath); } catch { return; }\n const projects = new Set(\n entries.filter((e) => e.endsWith(\".log\")).map((e) => e.slice(0, -\".log\".length)),\n );\n for (const project of projects) await rotateIfNeeded({ stateRoot, project, ...mboxCfg });\n } catch (e) {\n log(`rotation timer error: ${(e as Error).message}`);\n }\n }, rotationInterval);\n rotationTimer.unref?.();\n }\n\n return {\n stop(reason = \"requested\"): Promise<void> {\n // #535: write the exit marker synchronously, before any async\n // teardown, so it lands even if the caller doesn't await this promise.\n log(`exit pid=${process.pid} reason=${reason}`);\n if (deliveryTimer) clearInterval(deliveryTimer);\n if (probeTimer) clearInterval(probeTimer);\n if (timer) clearInterval(timer);\n if (rotationTimer) clearInterval(rotationTimer);\n try { ctx.cmuxEventsBridge.stop(); } catch { /* best-effort */ }\n try { ctx.telegramBridge?.stop(); } catch { /* best-effort */ }\n try { ctx.codexDriver.stop?.(); } catch { /* best-effort */ }\n for (const kill of ctx.activeHeadlessKills) kill();\n return new Promise<void>((resolve) => server.close(() => { log(`exit-complete pid=${process.pid}`); resolve(); }));\n },\n tickDelivery: deliveryTick,\n tickProbe: probeTick,\n };\n}\n","// Daemon interactive-block probe: moved from commands/notify-relay.ts so\n// daemon/probes.ts (core) can import it without a core→commands back-edge.\nimport type { TaskRecord, ControlEvent } from \"@squadrant/shared\";\n\n// Entries older than this at session-start time are silently acked without\n// delivery — stale events from a prior session or dead crews.\nexport const STALE_THRESHOLD_MS = 5 * 60 * 1000;\n\n// A working interactive task with no heartbeat for this long is a probe\n// candidate: PostToolUse never fires while a permission prompt is up.\nexport const PROBE_QUIET_MS = 20_000;\n\n// ── Pure pane classifiers (inlined from interactive/pane-classifier.ts) ──────\n// Duplication is intentional: pane-classifier.ts stays in root for the relay\n// path; core can't import it (root → core boundary). Both copies are pure and\n// covered by pane-classifier.test.ts.\n\nfunction detectTrailingQuestion(text: string): string | null {\n if (!text) return null;\n let inFence = false;\n let lastLine: string | null = null;\n for (const raw of text.split(/\\r?\\n/)) {\n const line = raw.trim();\n if (line.startsWith(\"```\")) { inFence = !inFence; continue; }\n if (inFence || line === \"\") continue;\n lastLine = line;\n }\n if (lastLine && lastLine.endsWith(\"?\")) return lastLine;\n return null;\n}\n\nconst ERROR_BANNER_RE: RegExp[] = [\n /\\bAPI Error\\b/i,\n /\\bOverloaded\\b/,\n /\\b(?:429|500|502|503|504|529)\\b[^?]*\\b(?:overloaded|unavailable|internal server error|bad gateway|gateway timeout|too many requests|service unavailable)\\b/i,\n /\\bretr(?:y|ies)\\s+(?:exhausted|limit\\s+(?:reached|exceeded))\\b/i,\n /\\bmaximum\\s+retries\\b/i,\n];\nconst OPTION_RE = /^[❯>›]?\\s*(\\d+)\\.\\s+(.*\\S)\\s*$/;\nconst PICKER_FOOTER_RE = /↑↓\\s*select|enter\\s+submit|esc\\s+dismiss/i;\nconst PURE_CHROME_RE = /^[\\s─━│┃╭╮╰╯┌┐└┘├┤┬┴┼═║╔╗╚╝╠╣╦╩╬▁▂▃▄▅▆▇█▔▏▕]+$/;\nconst STATUS_LINE_RE = /accept edits on|shift\\+tab|⏵⏵|\\? for shortcuts|esc to interrupt|tokens? (used|left)|context left/i;\n\nfunction stripChrome(raw: string): string | null {\n let line = raw.replace(/\\[[0-9;]*m/g, \"\");\n line = line.replace(/^[\\s│┃▏▕|]+/, \"\").replace(/[\\s│┃▏▕|]+$/, \"\");\n const trimmed = line.trim();\n if (trimmed === \"\") return null;\n if (PURE_CHROME_RE.test(trimmed)) return null;\n if (/^>\\s*$/.test(trimmed)) return null;\n if (STATUS_LINE_RE.test(trimmed)) return null;\n return trimmed;\n}\n\nfunction classifyPaneTail(\n tail: string,\n): { kind: \"approval\" | \"question\" | \"error\"; text: string } | null {\n if (!tail) return null;\n const raw = tail.split(/\\r?\\n/);\n const cleaned = raw.map(stripChrome);\n const options: { label: string; ci: number }[] = [];\n for (let i = 0; i < cleaned.length; i++) {\n const c = cleaned[i];\n if (c == null) continue;\n const m = c.match(OPTION_RE);\n if (m) options.push({ label: m[2], ci: i });\n }\n const hasYes = options.some((o) => /\\byes\\b/i.test(o.label));\n const hasNo = options.some((o) => /\\bno\\b/i.test(o.label));\n if (options.length >= 2 && hasYes && hasNo) {\n const firstOptCi = options[0].ci;\n for (let i = firstOptCi - 1; i >= 0; i--) {\n const c = cleaned[i];\n if (c == null) continue;\n if (c.endsWith(\"?\")) return { kind: \"approval\", text: c };\n }\n return { kind: \"approval\", text: \"Crew is awaiting permission approval.\" };\n }\n const hasPickerFooter = cleaned.some((c) => c != null && PICKER_FOOTER_RE.test(c));\n if (options.length >= 2 && hasPickerFooter) {\n const firstOptCi = options[0].ci;\n for (let i = firstOptCi - 1; i >= 0; i--) {\n const c = cleaned[i];\n if (c == null) continue;\n if (c.endsWith(\"?\")) return { kind: \"question\", text: c };\n }\n return { kind: \"question\", text: \"Crew is awaiting a choice.\" };\n }\n const region = cleaned.filter((c): c is string => c != null).join(\"\\n\");\n const q = detectTrailingQuestion(region);\n if (q) return { kind: \"question\", text: q };\n let errLine: string | null = null;\n for (const c of cleaned) {\n if (c != null && ERROR_BANNER_RE.some((re) => re.test(c))) errLine = c;\n }\n if (errLine) return { kind: \"error\", text: errLine.slice(0, 200) };\n return null;\n}\n\n// ── Interactive probe ─────────────────────────────────────────────────────────\n\ninterface InteractiveProbeDeps {\n project: string;\n listTasks: () => Promise<TaskRecord[]>;\n readPaneTail: (rec: TaskRecord) => Promise<string | null>;\n sendEvent: (event: ControlEvent) => Promise<void>;\n now: () => number;\n log: (m: string) => void;\n quietMs?: number;\n}\n\nexport function createInteractiveProbe(deps: InteractiveProbeDeps): {\n tick: () => Promise<void>;\n} {\n const quietMs = deps.quietMs ?? PROBE_QUIET_MS;\n const lastTail = new Map<string, string>();\n\n async function tick(): Promise<void> {\n let tasks: TaskRecord[];\n try {\n tasks = await deps.listTasks();\n } catch (e) {\n deps.log(`probe listTasks failed: ${(e as Error).message}`);\n return;\n }\n const now = deps.now();\n for (const rec of tasks) {\n if (rec.mode !== \"interactive\") continue;\n if (rec.state !== \"working\") continue;\n if (!rec.name) continue;\n if (now - rec.lastHeartbeat <= quietMs) continue;\n\n let tail: string | null;\n try {\n tail = await deps.readPaneTail(rec);\n } catch (e) {\n deps.log(`probe read failed for ${rec.id}: ${(e as Error).message}`);\n continue;\n }\n if (!tail) continue;\n if (lastTail.get(rec.id) === tail) continue;\n lastTail.set(rec.id, tail);\n\n const verdict = classifyPaneTail(tail);\n if (!verdict) continue;\n const event: ControlEvent =\n verdict.kind === \"error\"\n ? {\n type: \"task.failed\",\n id: rec.id,\n error: `crew session error (pane-detected): ${verdict.text}`,\n }\n : {\n type: \"task.blocked\",\n id: rec.id,\n reason:\n verdict.kind === \"approval\"\n ? \"crew awaiting permission (pane-detected)\"\n : \"crew asked a question (pane-detected)\",\n question: verdict.text,\n };\n try {\n await deps.sendEvent(event);\n const label = verdict.kind === \"error\" ? \"CREW FAILED\" : \"CREW BLOCKED\";\n deps.log(`probe -> ${label} ${rec.name} (${verdict.kind})`);\n } catch (e) {\n deps.log(`probe sendEvent failed for ${rec.id}: ${(e as Error).message}`);\n }\n }\n }\n\n return { tick };\n}\n","// src/control/daemon/probes.ts\n// Surface-liveness probe logic for the daemon-direct delivery path.\nimport { createInteractiveProbe } from \"./interactive-probe.js\";\nimport { createDirectCrewPaneReader, createDirectSurfaceLivenessProbe } from \"../crew-pane-reader.js\";\nimport { loadConfig } from \"@squadrant/shared\";\nimport type { TaskRecord } from \"@squadrant/shared\";\nimport type { DaemonSurfaceDriver } from \"../interfaces.js\";\nimport type { DaemonContext } from \"./context.js\";\n\nexport interface ProbeHandlers {\n /** Build the probe-tick function for the daemon-direct delivery loop.\n * Call once after the surface driver is resolved; returns a guarded tick. */\n buildInteractiveProbe: (deps: { cmux: DaemonSurfaceDriver }) => () => Promise<void>;\n /** Direct-cmux surface liveness probe for interactive task reaping. */\n directSurfaceProbe: (rec: TaskRecord) => Promise<\"alive\" | \"gone\" | \"unknown\">;\n}\n\n/** Resolve the captain pane name for a project from the config. */\nfunction captainNameForProject(project: string): string {\n const cfg = loadConfig();\n return cfg.projects?.[project]?.captainName ?? `${project}-captain`;\n}\n\nexport function createProbes(ctx: DaemonContext): ProbeHandlers {\n const { store, log } = ctx;\n\n // ── Daemon-direct: direct cmux surface probe ──────────────────────────────\n const directSurfaceProbe = (_rec: TaskRecord): Promise<\"alive\" | \"gone\" | \"unknown\"> => {\n return Promise.resolve(\"unknown\");\n };\n\n // ── Daemon-direct: blocked-crew detection ─────────────────────────────────\n // Reuses createInteractiveProbe with a direct cmux pane reader injected as\n // the readPaneTail dep. The returned tick must be called from the delivery\n // loop's interval.\n function buildInteractiveProbe(deps: { cmux: DaemonSurfaceDriver }): () => Promise<void> {\n const directPaneReader = createDirectCrewPaneReader(deps.cmux, captainNameForProject);\n const probe = createInteractiveProbe({\n project: \"_all_\",\n listTasks: async () => store.listAll(),\n readPaneTail: directPaneReader,\n sendEvent: async (event) => {\n const rec = store.listAll().find((r) => r.id === event.id);\n if (rec) {\n await ctx.d.handle({ kind: \"event\", project: rec.project, event });\n }\n },\n now: () => Date.now(),\n log,\n });\n let probing = false;\n return async () => {\n if (probing) return;\n probing = true;\n try { await probe.tick(); }\n finally { probing = false; }\n };\n }\n\n return { buildInteractiveProbe, directSurfaceProbe };\n}\n\n/** Build the surface-liveness probe used by createDaemon — always uses the\n * direct cmux path when a driver is available. Pure: no side effects. */\nexport function buildSurfaceProbe(\n ctx: DaemonContext,\n probes: ProbeHandlers,\n daemonCmux: DaemonSurfaceDriver | undefined,\n): (rec: TaskRecord) => Promise<\"alive\" | \"gone\" | \"unknown\"> {\n if (ctx.opts.isSurfaceAlive) return ctx.opts.isSurfaceAlive;\n if (daemonCmux) {\n return createDirectSurfaceLivenessProbe(daemonCmux, captainNameForProject);\n }\n return probes.directSurfaceProbe;\n}\n","/** #617: the classification sendToSurface already decides at each throw site —\n * surfaced so callers can log *why* a send deferred, not just that it did.\n * \"no-box\": input box not confirmed visible (overlay/unreadable screen, #268).\n * \"modal\": an AskUserQuestion/permission selection modal is open (#484).\n * \"draft\": a real (or not-yet-disambiguated) draft is present in the input box. */\nexport type DeferReason = \"no-box\" | \"modal\" | \"draft\";\n\n/** Thrown by sendToSurface when the captain has a draft — delivery defers (#258/#302). */\nexport class DeferDelivery extends Error {\n constructor(\n public readonly draft: string | null = null,\n public readonly reason: DeferReason = \"draft\",\n ) {\n super(\"deferred: captain composing\");\n this.name = \"DeferDelivery\";\n }\n}\n","import { DeferDelivery, type DeferReason } from \"./defer-delivery.js\";\n\n/**\n * #332: extracted defer-while-typing state machine (#258/#302).\n *\n * Behaviour ported from notify-relay.ts drain() (#332):\n * - per-seq deferCounts / stableCounts / lastContent maps\n * - maxDefers / stableProbePolls thresholds\n * - stable-content probe escalation (#302)\n */\nexport interface CaptainDeliveryOptions {\n maxDefers: number;\n stableProbePolls: number;\n}\n\nexport type SendFn = (text: string, opts?: { probe?: boolean }) => Promise<void>;\n\n/** #617: the deferral classification surfaced for logging/alerting. Mostly the\n * DeferReason sendToSurface already decided (see defer-delivery.ts), plus\n * \"stable\" — CaptainDelivery's own byte-identical-across-polls signal (#302)\n * that upgrades a \"draft\" into the more precise \"content stopped changing,\n * likely a paused human or a ghost, about to be probed\" classification.\n * \"unknown\" covers a non-DeferDelivery throw, which carries no classification. */\nexport type DeliverDeferReason = DeferReason | \"stable\" | \"unknown\";\nexport type DeliverResult = { delivered: true } | { deferred: true; reason: DeliverDeferReason };\n\n/** Read-only deferral snapshot (B1 — dashboard visibility into #484/#466-class stalls). */\nexport interface CaptainDeliveryStats {\n /** Highest in-flight deferCount across all seqs currently being retried (0 when none). */\n maxDeferCount: number;\n /** true once maxDeferCount has reached the configured maxDefers threshold — the same\n * point at which delivery force-escalates to a probe send. */\n stuck: boolean;\n /** Classification for the seq at maxDeferCount (#617). undefined when nothing is deferred. */\n reason?: DeliverDeferReason;\n}\n\n/**\n * Unified-formatter helper (#214/#210): the daemon's formatMessage is the single\n * source of truth for the captain-facing message. Returns null for entries the\n * daemon chose not to surface (null/empty message fields).\n */\nexport function deliverable(entry: { message?: string | null }): string | null {\n const msg = entry.message;\n if (msg == null) return null;\n const trimmed = msg.trim();\n return trimmed.length > 0 ? msg : null;\n}\n\nexport class CaptainDelivery {\n private deferCounts = new Map<number, number>();\n private lastContent = new Map<number, string | null>();\n private stableCounts = new Map<number, number>();\n private lastReason = new Map<number, DeliverDeferReason>();\n\n constructor(private readonly opts: CaptainDeliveryOptions) {}\n\n /**\n * Attempt to deliver one mailbox entry to the captain. Calls `send(text, opts)`\n * and, if the send throws DeferDelivery, tracks defer/stable counts for the\n * entry's seq and returns {deferred: true} (caller should NOT advance cursor).\n * On success or null message returns {delivered: true} (caller SHOULD advance).\n */\n async deliver(\n entry: { seq: number; message?: string | null },\n send: SendFn,\n ): Promise<DeliverResult> {\n const msg = deliverable(entry);\n if (!msg) return { delivered: true };\n\n const seq = entry.seq;\n const deferCount = this.deferCounts.get(seq) ?? 0;\n // #302/#484: probe ONLY once content has been stable for stableProbePolls\n // polls (captain not typing / a ghost that isn't re-rendering). A probe\n // send makes sendToSurface inject a REAL backspace keystroke into the live\n // pane to run the structural liveness test (#258) — safe against a stable\n // box, but unsafe against one that's still actively changing: repeatedly\n // backspacing a genuinely-typing human's draft risks racing their next\n // keystroke and, per #484's reopened root-cause, eventually misclassifying\n // and force-delivering into it. deferCount alone must NEVER trigger a\n // probe — an actively-changing draft defers indefinitely until it goes\n // stable (paused) or empty (submitted); maxDefers stays meaningful only as\n // the `stuck` dashboard signal in stats() below, decoupled from escalation.\n const stable = (this.stableCounts.get(seq) ?? 0) >= this.opts.stableProbePolls;\n const probe = stable;\n\n try {\n await send(msg, probe ? { probe: true } : undefined);\n this.deferCounts.delete(seq);\n this.stableCounts.delete(seq);\n this.lastContent.delete(seq);\n this.lastReason.delete(seq);\n return { delivered: true };\n } catch (e) {\n if (e instanceof DeferDelivery) {\n this.deferCounts.set(seq, deferCount + 1);\n // Track content stability: byte-identical non-empty draft across\n // consecutive polls means the captain isn't actively typing (#302).\n const content = e.draft;\n let stableCount: number;\n if (content && content === this.lastContent.get(seq)) {\n stableCount = (this.stableCounts.get(seq) ?? 0) + 1;\n this.stableCounts.set(seq, stableCount);\n } else {\n stableCount = 0;\n this.stableCounts.set(seq, 0);\n }\n this.lastContent.set(seq, content);\n // #617: \"stable\" (byte-identical for stableProbePolls polls) is a more\n // precise classification than the raw \"draft\" reason once it applies —\n // it's the same signal that gates probe escalation just above, not a\n // new classifier. modal/no-box always win: they don't depend on content.\n const reason: DeliverDeferReason =\n e.reason !== \"draft\" ? e.reason\n : stableCount >= this.opts.stableProbePolls ? \"stable\"\n : \"draft\";\n this.lastReason.set(seq, reason);\n return { deferred: true, reason };\n }\n // Non-DeferDelivery errors: don't advance cursor, retry next poll.\n this.lastReason.set(seq, \"unknown\");\n return { deferred: true, reason: \"unknown\" };\n }\n }\n\n /** Read-only. Never mutates — safe to poll from the snapshot assembler every tick. */\n stats(): CaptainDeliveryStats {\n let maxDeferCount = 0;\n let reason: DeliverDeferReason | undefined;\n for (const [seq, c] of this.deferCounts) {\n if (c > maxDeferCount) {\n maxDeferCount = c;\n reason = this.lastReason.get(seq);\n }\n }\n return { maxDeferCount, stuck: maxDeferCount >= this.opts.maxDefers, reason };\n }\n}\n","// src/control/daemon/delivery.ts\n// Mailbox notification + daemon-direct captain delivery loop (#332).\nimport { appendToMailbox, appendCaptainMessage, readCursor, writeCursor, readFromCursor } from \"../mailbox.js\";\nimport { CaptainDelivery, type CaptainDeliveryStats } from \"../delivery/captain-delivery.js\";\nimport { loadConfig, TERMINAL_STATES } from \"@squadrant/shared\";\nimport { STALE_THRESHOLD_MS } from \"./interactive-probe.js\";\nimport { deriveCaptainState } from \"../liveness.js\";\nimport type { TaskRecord, ControlEvent, RuntimeLivenessRecord, LivenessEntry } from \"@squadrant/shared\";\nimport type { PaneRef } from \"@squadrant/shared\";\nimport type { Store } from \"../store.js\";\nimport type { DaemonSurfaceDriver } from \"../interfaces.js\";\nimport type { DaemonContext } from \"./context.js\";\nimport type { LivenessRegistry } from \"./liveness-registry.js\";\n\nconst CURSOR_SUBSCRIBER = \"captain\";\n\n// Must-deliver event kinds that bypass the stale-skip path (#474 D1).\n// Includes terminal transitions (done/failed/cancelled) AND task.blocked:\n// a dropped task.blocked leaves the captain waiting forever on a crew question.\nconst TERMINAL_KINDS = new Set([\"task.done\", \"task.failed\", \"task.cancelled\", \"task.blocked\"]);\n\n/** Pure: find the captain surface by title in a surface list (#332). */\nexport function discoverCaptainSurface(surfaces: PaneRef[], captainTitle: string): PaneRef | null {\n return surfaces.find((s) => s.title === captainTitle) ?? null;\n}\n\n/**\n * Reap a stopped project's orphaned crews (#324). When the user closes the\n * captain workspace, its crew panes die with it — every non-terminal\n * interactive crew is orphaned. Terminalize them to 'cancelled' with a distinct\n * `captain-stopped` marker (traceable; not a fault). Silent: no push fires (the\n * captain that would receive it is gone). Returns the count reaped.\n *\n * Headless crews are excluded — they run as detached processes, not panes in the\n * captain's workspace, and are reconciled by their own pid liveness instead.\n */\nexport function reapOrphanedCrews(store: Pick<Store, \"list\" | \"put\">, project: string): number {\n let reaped = 0;\n for (const r of store.list(project)) {\n if (TERMINAL_STATES.has(r.state)) continue;\n if (r.mode !== \"interactive\") continue;\n store.put({ ...r, state: \"cancelled\", lastEvent: \"captain-stopped\" });\n reaped++;\n }\n return reaped;\n}\n\nexport interface LivenessTickDeps {\n registry: LivenessRegistry;\n liveness: () => Promise<RuntimeLivenessRecord[]>;\n isPidAlive: (pid: number) => boolean;\n now: () => number;\n /** Reap a stopped/gone captain's orphaned crews (#324 — fold-in of the old\n * streak-triggered reap, now driven by the registry). Optional so pure\n * liveness-only callers can omit it. Idempotent (already-terminal crews are\n * skipped), so calling it every tick for a non-alive captain is safe. */\n reap?: (project: string) => number;\n /** One grep-able line per applied/transitioned record (§4.4): `[role/source]\n * project pid=… → state`. Optional so pure liveness-only callers can omit it. */\n log?: (msg: string) => void;\n}\n\nfunction logEntry(log: ((msg: string) => void) | undefined, project: string, e: LivenessEntry | undefined): void {\n if (!log || !e) return;\n log(`[${e.role}/${e.source}] ${project} pid=${e.pid} → ${deriveCaptainState(e)}`);\n}\n\n/** One reconcile+floor pass over captain records. Runtime snapshot is authoritative;\n * the pid floor arbitrates liveness; a captain absent from the snapshot is marked\n * cleanly-closed (stopped) but NOT dropped. */\nexport async function runLivenessTick(deps: LivenessTickDeps): Promise<void> {\n const now = deps.now();\n let records: RuntimeLivenessRecord[] = [];\n try { records = await deps.liveness(); } catch { return; } // runtime unreachable → leave registry as-is\n const seen = new Set<string>();\n\n // #565: cmux's own store can degrade a session's launchCommand (observed live:\n // a crash/reattach left it as bare `[\"claude\"]`, no --append-system-prompt-file)\n // so the record reads role:\"unknown\" even though it's the exact same session\n // already confirmed as this project's captain. SessionId identity outranks a\n // degraded launchCommand classification — restore \"captain\" for any record\n // whose sessionId matches an already-known captain for that project.\n const knownCaptainSessions = new Map<string, string>(); // sessionId → project\n for (const e of deps.registry.all()) {\n if (e.role === \"captain\") knownCaptainSessions.set(e.sessionId, e.project);\n }\n\n // #527: multiple cmux sessions can share a cwd, producing duplicate project\n // entries. Group by project and pick one winner to avoid last-write-wins\n // collision (dead pid overwriting live).\n const byProject = new Map<string, RuntimeLivenessRecord[]>();\n for (const r of records) {\n const role = r.role === \"captain\" || knownCaptainSessions.get(r.sessionId) === r.project\n ? \"captain\" : r.role;\n if (role !== \"captain\") continue;\n let arr = byProject.get(r.project);\n if (!arr) { arr = []; byProject.set(r.project, arr); }\n arr.push(r);\n }\n\n for (const [project, recs] of byProject) {\n seen.add(project);\n // Prefer pidAlive===true (or pid:null hibernated), then first in order.\n const winner = recs.find(r => r.pid == null || deps.isPidAlive(r.pid)) ?? recs[0];\n const entry: LivenessEntry = {\n project, role: \"captain\", pid: winner.pid, sessionId: winner.sessionId,\n startedAt: now, lastState: \"start\", lastSeenAt: now,\n pidAlive: winner.pid != null ? deps.isPidAlive(winner.pid) : true,\n source: \"runtime\",\n };\n // Preserve original startedAt if we already knew this captain (avoid churn):\n const prev = deps.registry.get(project);\n if (prev && prev.lastState === \"start\") entry.startedAt = prev.startedAt;\n deps.registry.apply(entry);\n if (winner.pid != null) deps.registry.setPidAlive(project, deps.isPidAlive(winner.pid), now);\n logEntry(deps.log, project, deps.registry.get(project));\n }\n\n // Captains we knew but the snapshot no longer lists → clean close — but ONLY\n // with positive evidence the pid is actually dead (#565). Absence from a\n // single snapshot read is not proof of death (a store-file parsing glitch,\n // a degraded record, a transient cmux hiccup); inferring \"ended\" from\n // absence alone silently and permanently pauses delivery for a captain that\n // is still running. When the tracked pid can't be confirmed dead (still\n // alive, or unknown/null), leave the entry alone.\n for (const e of deps.registry.all()) {\n if (e.role !== \"captain\" || e.lastState !== \"start\" || seen.has(e.project)) continue;\n if (e.pid == null || deps.isPidAlive(e.pid)) {\n deps.log?.(`[${e.role}/runtime] ${e.project} pid=${e.pid} missing from snapshot but not confirmed dead — leaving alive`);\n continue;\n }\n deps.registry.markEnded(e.project, now);\n logEntry(deps.log, e.project, deps.registry.get(e.project));\n }\n\n // Reap orphaned crews for any captain the registry now considers stopped\n // (clean close) or gone (crash).\n if (deps.reap) {\n for (const e of deps.registry.all()) {\n if (e.role !== \"captain\") continue;\n const state = deriveCaptainState(e);\n if (state === \"stopped\" || state === \"gone\") deps.reap(e.project);\n }\n }\n}\n\nexport interface DeliveryResult {\n defaultNotify: (args: { project: string; message: string; record: TaskRecord; event: ControlEvent }) => Promise<void>;\n /** Guarded delivery tick — undefined when daemon-direct mode is OFF. */\n deliveryTick: (() => Promise<void>) | undefined;\n /** Read-only per-project deferral stats (B1). undefined when daemon-direct mode is OFF,\n * or when the project has no CaptainDelivery instance yet (no delivery attempted). */\n deliveryStats: (project: string) => CaptainDeliveryStats | undefined;\n}\n\nexport function createDelivery(\n ctx: DaemonContext,\n daemonCmux: DaemonSurfaceDriver | undefined,\n): DeliveryResult {\n const { stateRoot, store, log, livenessRegistry, isPidAlive, opts, telegramBridge } = ctx;\n // Default to a no-op so tests that construct a bare ctx object (not via\n // buildContext) don't need to inject this. squadrantd.ts always overrides\n // ctx.notifyFault with the real one in production (see context.ts).\n const notifyFault = ctx.notifyFault ?? (() => {});\n\n // ── Default push-notification wiring (mailbox-injector spec) ─────────────\n const defaultNotify = async (args: {\n project: string;\n message: string;\n record: TaskRecord;\n event: ControlEvent;\n }): Promise<void> => {\n // #594b: firePush decides to notify off a TaskRecord snapshot captured\n // synchronously at the state transition, but this mailbox write is\n // awaited I/O — a concurrent `crew close` (task.cancelled) can land on the\n // daemon's store in that gap and terminalize the SAME task. The reducer's\n // own terminal-absorb guard can't help here (the close is a separate,\n // later applyEvent call; this notify was already decided before it ran).\n // Re-check the daemon's own CURRENT record right before writing: if the\n // crew has since reached a terminal state different from what we're about\n // to announce, the notification is stale — the crew is gone — so drop it\n // rather than deliver e.g. \"CREW IDLE\" for a task that's already closed.\n // A terminal notification (CREW DONE/FAILED) always matches its own fresh\n // state and is unaffected; a missing record (e.g. purged) fails open.\n const fresh = store.get(args.project, args.record.id);\n if (fresh && TERMINAL_STATES.has(fresh.state) && fresh.state !== args.record.state) {\n return;\n }\n try {\n await appendToMailbox({\n stateRoot,\n project: args.project,\n taskRecord: args.record,\n event: args.event,\n // Persist the daemon-rendered message (#214/#210): delivered verbatim\n // rather than re-derived from the raw event (which drifted).\n message: args.message,\n });\n } catch (e) {\n log(`mailbox append failed project=${args.project}: ${(e as Error).message}`);\n }\n };\n\n // ── Daemon-direct delivery loop ───────────────────────────────────────────\n if (!daemonCmux) {\n return { defaultNotify, deliveryTick: undefined, deliveryStats: () => undefined };\n }\n\n const cmux = daemonCmux;\n const cfg = loadConfig();\n const deliveries = new Map<string, CaptainDelivery>();\n const deliveryStats = (project: string): CaptainDeliveryStats | undefined => deliveries.get(project)?.stats();\n // #579/#484: deferring forever behind an actively-changing draft is the\n // correct, SAFE behaviour — but safe-and-silent is #560's disease. Track\n // which projects we've already alerted on for the CURRENT stall episode so\n // the alert fires exactly once per episode (edge-triggered, mirrors the\n // #354 quietNotifiedAt / #492 anti-flood pattern), not once per poll. Clears\n // when stats().stuck drops back to false, re-arming for a later episode.\n const stuckNotified = new Set<string>();\n // Captured once at delivery-loop setup. Entries older than\n // sessionStartMs - STALE_THRESHOLD_MS are silently acked (cursor advanced)\n // without delivery. This stops a fresh/empty cursor from re-delivering the\n // entire historical backlog.\n const sessionStartMs = Date.now();\n\n // Re-entrancy guard: each tick does multiple slow cmux subprocess calls and\n // can exceed the 1s interval.\n let delivering = false;\n\n const deliveryCore = async () => {\n // Registry is the liveness authority (Task 4) — reconcile it from the\n // runtime snapshot + pid floor before this tick's per-project pass.\n await runLivenessTick({\n registry: livenessRegistry,\n liveness: () => (cmux.liveness ? cmux.liveness() : Promise.resolve([])),\n isPidAlive,\n now: () => Date.now(),\n log,\n reap: (project) => {\n const reaped = reapOrphanedCrews(store, project);\n if (reaped > 0) {\n const title = cfg.projects?.[project]?.captainName ?? `${project}-captain`;\n log(`captain ${title}: reaped ${reaped} orphaned crew(s)`);\n }\n return reaped;\n },\n });\n\n const injectedSurfaces = opts.captainSurfaces ?? {};\n const allProjects = [...new Set([\n ...Object.keys(cfg.projects ?? {}),\n ...Object.keys(injectedSurfaces),\n ...store.listAll().map((t) => t.project),\n cfg.commandName,\n ])];\n\n for (const project of allProjects) {\n const projCfg = cfg.projects?.[project];\n const captainTitle = project === cfg.commandName \n ? cfg.commandName \n : (projCfg?.captainName ?? `${project}-captain`);\n\n // Surface discovery is ONLY for the delivery target (where to cmux.send);\n // captain presence/liveness authority now lives in livenessRegistry.\n const wsId = cmux.findWorkspaceId ? await cmux.findWorkspaceId(captainTitle) : null;\n let surface: PaneRef | null = null;\n\n if (wsId) {\n const surfaces = await cmux.listSurfaces(wsId);\n surface = discoverCaptainSurface(surfaces, captainTitle);\n }\n\n // Fall back to injected surface (tests / config-less projects).\n if (!surface) surface = injectedSurfaces[project] ?? null;\n\n if (!surface) continue;\n\n const cursor = await readCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER });\n const lastAcked = cursor?.lastAckedSeq ?? 0;\n let d = deliveries.get(project);\n if (!d) {\n d = new CaptainDelivery({\n maxDefers: cfg.delivery?.maxDeferDeliveries ?? 300,\n stableProbePolls: cfg.delivery?.stableProbePolls ?? 3,\n });\n deliveries.set(project, d);\n }\n for await (const entry of readFromCursor({ stateRoot, project, fromSeq: lastAcked + 1 })) {\n // #332 storm BUG 3: silently ack entries that pre-date this daemon\n // session by more than STALE_THRESHOLD_MS.\n if (new Date(entry.ts).getTime() < sessionStartMs - STALE_THRESHOLD_MS) {\n // D1 (#474): terminal events must deliver regardless of age — an\n // undelivered CREW DONE must reach the captain even after a daemon\n // restart >5min after enqueue. Non-terminal backlog suppression stays.\n if (!TERMINAL_KINDS.has(entry.kind)) {\n // #531: exempt non-daemon captain.message (human/cli) from stale-skip\n const isExemptMessage = entry.kind === \"captain.message\" && entry.payload?.source !== \"daemon\";\n if (!isExemptMessage) {\n log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-skipped`);\n await writeCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER, lastAckedSeq: entry.seq });\n continue;\n }\n log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-exempt-deliver`);\n } else {\n log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=stale-terminal-deliver`);\n }\n }\n const result = await d.deliver(entry, (text, sendOpts) =>\n cmux.send(surface!, text, sendOpts),\n );\n if (\"delivered\" in result) {\n log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=delivered`);\n await writeCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER, lastAckedSeq: entry.seq });\n } else {\n // #617: project+reason make a defer episode attributable after the\n // fact (previously: no project, no cause — see issue). Logging every\n // 1s tick for up to maxDefers (~300, ~30min) would flood the log for\n // no forensic gain once the cause is known, so we log the onset\n // (first defer of this seq) and then every 30th tick (~30s cadence)\n // — enough resolution to correlate a later stuck/SIGTERM event\n // without adding meaningful volume.\n const { maxDeferCount } = d.stats();\n if (maxDeferCount === 1 || maxDeferCount % 30 === 0) {\n log(`delivery seq=${entry.seq} kind=${entry.kind} outcome=deferred project=${project} reason=${result.reason}`);\n }\n break;\n }\n }\n\n // #579/#484: fail LOUD, not silent, once this project's delivery is\n // stuck (deferCount crossed maxDefers — an actively-changing draft that\n // never stabilizes, so the structural probe never gets to run).\n //\n // The mailbox entry alone is NOT enough: it's drained by this same\n // stuck delivery pipeline, so it queues behind the very block it's\n // reporting and only surfaces once the stall has already resolved\n // (fail-silent-then-apologize). Kept here as a post-resolution audit\n // trail, discoverable even if the operator never opens the dashboard.\n //\n // Two independent out-of-band channels fire alongside it, neither of\n // which touches the stuck pane/mailbox:\n // - notifyFault: the notifier plugin slot (cmux by default — see\n // @squadrant/workspaces' NotifierRegistry). ALWAYS resolved in\n // production (never undefined), so it's the channel that works with\n // ZERO Telegram configuration — closing the gap where Telegram alone\n // left every non-Telegram install silent.\n // - telegramBridge.pushRaw: reaches a phone even when the operator\n // isn't watching a terminal. Optional — only when Telegram is set up.\n // The daemon's own health snapshot (`deferral.stuck`) is also surfaced\n // as `detail` on the captain's ComponentHealth row (see liveness.ts),\n // so `squadrant doctor` / `squadrant status --detailed` show it too —\n // a third, pull-based, zero-configuration surface.\n const stuck = d.stats().stuck;\n if (stuck && !stuckNotified.has(project)) {\n stuckNotified.add(project);\n const { maxDeferCount, reason } = d.stats();\n log(`delivery stuck project=${project} deferCount=${maxDeferCount} reason=${reason ?? \"unknown\"}`);\n // #617: report the actual blocker instead of always blaming the input\n // box — a modal (#484) isn't a draft/ghost and pointing the operator at\n // their input box is actively misleading when a question is open.\n const text = reason === \"modal\"\n ? `⚠️ DELIVERY STUCK: a modal question is open in your captain pane and has blocked pending notification(s) for ${maxDeferCount}+ retries. This keeps retrying safely and will deliver automatically once you answer or dismiss it.`\n : `⚠️ DELIVERY STUCK: an in-progress draft (or ghost text) in your input box has blocked pending notification(s) for ${maxDeferCount}+ retries. Your input is never touched — this keeps retrying safely and will deliver automatically once you submit or clear it.`;\n appendCaptainMessage({ stateRoot, project, text, source: \"daemon\" })\n .catch((e) => log(`delivery stuck alert failed project=${project}: ${(e as Error).message}`));\n Promise.resolve(notifyFault(project, text))\n .catch((e) => log(`delivery stuck fault-notify failed project=${project}: ${(e as Error).message}`));\n telegramBridge?.pushRaw(project, text);\n } else if (!stuck && stuckNotified.has(project)) {\n stuckNotified.delete(project);\n }\n }\n };\n\n const deliveryTick = async () => {\n if (delivering) return;\n delivering = true;\n try {\n await deliveryCore();\n } finally {\n delivering = false;\n }\n };\n\n return { defaultNotify, deliveryTick, deliveryStats };\n}\n","// src/control/daemon/gates.ts\n// resolveInteractiveGate: route the captain's approve/deny to the owning driver.\n// Reads ctx.codexDriver and ctx.opencodeBridge lazily (set by squadrantd.ts before\n// any gate message can arrive on the socket).\nimport type { DaemonContext } from \"./context.js\";\n\nexport function createGateResolver(ctx: DaemonContext) {\n return async (taskId: string, payload: unknown): Promise<void> => {\n const rec = ctx.store.listAll().find((r) => r.id === taskId);\n try {\n if (rec?.provider === \"opencode\") {\n // Only an explicit \"approve\" approves; any other reply denies —\n // never auto-approve a permission gate.\n const decision = (payload as { decision?: string })?.decision === \"approve\" ? \"approve\" : \"deny\";\n await ctx.opencodeBridge.answer(taskId, decision);\n } else {\n await ctx.codexDriver.answer(taskId, payload);\n }\n } catch (e) { ctx.log(`gate-resolve answer failed: ${(e as Error).message}`); }\n };\n}\n","// src/control/daemon/server.ts\n// IPC socket server: message router + attach fan-in.\n// All state lives on DaemonContext; callbacks that can't yet be on ctx are\n// passed via ServerHandlers (built once in squadrantd.ts/start.ts).\nimport { startServer, encodeFrame } from \"../protocol.js\";\nimport type { AttachFrame, AttachInbound } from \"../protocol.js\";\nimport type { ComponentHealth } from \"../liveness.js\";\nimport type { DaemonSnapshotInputs } from \"../snapshot.js\";\nimport type { DaemonContext } from \"./context.js\";\n\nexport interface ServerHandlers {\n /** Build per-component health list (optionally filtered to one project). */\n buildHealth: (project?: string) => ComponentHealth[];\n /** Gather full snapshot inputs (all I/O). */\n gatherSnapshotInputs: (now: number) => Promise<DaemonSnapshotInputs>;\n /** Cancel pending gate-promotion timers when a client attaches. */\n cancelPromotionsFor: (taskId: string) => void;\n /** Fan-out an AttachFrame to all clients watching a task. */\n broadcast: (taskId: string, f: AttachFrame) => void;\n}\n\nexport function createServer(\n ctx: DaemonContext,\n handlers: ServerHandlers,\n) {\n const { store, log, attachConns } = ctx;\n const { buildHealth, gatherSnapshotInputs, cancelPromotionsFor, broadcast } = handlers;\n\n return startServer(ctx.sockPath, {\n handler: async (msg: any) => {\n if (msg.kind === \"seed\") { store.put(msg.record); return { ok: true }; }\n // Crew-close teardown for codex: the cmux pane only hosts the `crew attach`\n // renderer — the thread lives on the shared app-server, so closing the pane\n // doesn't reap it. `squadrant crew close` calls this to archive the thread and\n // its per-thread MCP servers (else they leak ~53MB/crew). Fires for terminal\n // and non-terminal crews alike.\n if (msg.kind === \"codex-close\") {\n await ctx.codexDriver.close(msg.taskId).catch((e: unknown) => log(`codex close err: ${e}`));\n return { ok: true };\n }\n // #77 service-health surface: per-component liveness for the queried project (or all).\n if (msg.kind === \"health\") {\n return buildHealth(msg.project as string | undefined);\n }\n // #44 dashboard: read-only full system snapshot (Tier 0/1/2).\n if (msg.kind === \"snapshot\") {\n const now = Date.now();\n const { assembleDaemonSnapshot } = await import(\"../snapshot.js\");\n return assembleDaemonSnapshot(await gatherSnapshotInputs(now), now);\n }\n if (msg.kind === \"event\") {\n return ctx.d.handle(msg);\n }\n return ctx.d.handle(msg);\n },\n onAttach: (conn, frame) => {\n let set = attachConns.get(frame.taskId);\n if (!set) { set = new Set(); attachConns.set(frame.taskId, set); }\n set.add(conn);\n // A client arriving within the 5s window defuses any pending gate timer.\n cancelPromotionsFor(frame.taskId);\n // Immediately ack the attach so the client knows it's live.\n try { conn.write(encodeFrame({ type: \"reattached\", taskId: frame.taskId })); } catch { /* ignore */ }\n },\n onAttachInbound: (_conn, frame) => {\n const f = frame as AttachInbound;\n if (f.op === \"say\")\n void ctx.codexDriver.say(f.taskId, f.text).catch((e: unknown) => log(`say err: ${e}`));\n else if (f.op === \"steer\")\n void ctx.codexDriver.steer(f.taskId, f.text).catch((e: unknown) => log(`steer err: ${e}`));\n else if (f.op === \"interrupt\")\n void ctx.codexDriver.interrupt(f.taskId).catch((e: unknown) => log(`interrupt err: ${e}`));\n else if (f.op === \"answer\")\n void ctx.codexDriver.answer(f.taskId, f.payload).catch((e: unknown) => log(`answer err: ${e}`));\n },\n onAttachClose: (conn) => {\n for (const set of attachConns.values()) set.delete(conn);\n },\n });\n}\n","// src/control/daemon/snapshot-gather.ts\n// Snapshot I/O edge: pure helpers that gather raw inputs for the snapshot verb.\n// Each tolerates missing files and never throws.\nimport { fileURLToPath } from \"node:url\";\nimport { join } from \"node:path\";\nimport {\n statSync, openSync, readSync, closeSync, readdirSync, readFileSync,\n} from \"node:fs\";\nimport type { DaemonSnapshotInputs, ResultArtifacts } from \"../snapshot.js\";\nimport type { TaskRecord } from \"@squadrant/shared\";\n\n// The compiled snapshot-gather.js shares the dist/ build time with squadrantd.js\n// (tsup compiles all entries in the same pass), so its mtime == dist build-time.\nconst SELF_PATH = fileURLToPath(import.meta.url);\n\n/** mtime (epoch ms) of the running daemon's compiled code, for build-freshness. */\nexport function distBuiltAt(): number {\n try { return statSync(SELF_PATH).mtimeMs; } catch { return 0; }\n}\n\n/** Daemon-log error count (last window) + total size. Reads only the tail so a\n * large log never makes the snapshot tick expensive. */\nexport function gatherLogStats(path: string, now: number, windowMs: number): DaemonSnapshotInputs[\"log\"] {\n let sizeBytes = 0;\n try { sizeBytes = statSync(path).size; }\n catch { return { errorCount: 0, sizeBytes: 0, windowMs }; }\n if (sizeBytes === 0) return { errorCount: 0, sizeBytes, windowMs };\n const CAP = 256 * 1024;\n const start = Math.max(0, sizeBytes - CAP);\n const len = sizeBytes - start;\n let text = \"\";\n try {\n const fd = openSync(path, \"r\");\n try {\n const buf = Buffer.alloc(len);\n readSync(fd, buf, 0, len, start);\n text = buf.toString(\"utf-8\");\n } finally { closeSync(fd); }\n } catch { return { errorCount: 0, sizeBytes, windowMs }; }\n const cutoff = now - windowMs;\n let errorCount = 0;\n for (const line of text.split(\"\\n\")) {\n if (!/error|failed/i.test(line)) continue;\n // Lines carry an ISO timestamp (\"[squadrantd] 2026-... msg\"); skip ones older\n // than the window. Lines without a parseable timestamp are counted (conservative).\n const m = line.match(/\\d{4}-\\d{2}-\\d{2}T[\\d:.]+Z/);\n if (m) { const ts = Date.parse(m[0]); if (!Number.isNaN(ts) && ts < cutoff) continue; }\n errorCount++;\n }\n return { errorCount, sizeBytes, windowMs };\n}\n\n/** Per-project store state counts + corrupt/quarantined file count. */\nexport function gatherStoreStats(\n store: { list: (p: string) => TaskRecord[] },\n stateRoot: string,\n project: string,\n): { byState: Record<string, number>; corruptCount: number } {\n const byState: Record<string, number> = {};\n for (const r of store.list(project)) byState[r.state] = (byState[r.state] ?? 0) + 1;\n let corruptCount = 0;\n const dir = join(stateRoot, project);\n try {\n for (const n of readdirSync(dir)) {\n if (n.includes(\".corrupt.\")) { corruptCount++; continue; }\n if (!n.endsWith(\".json\")) continue;\n try { JSON.parse(readFileSync(join(dir, n), \"utf-8\")); }\n catch { corruptCount++; }\n }\n } catch { /* no project dir yet */ }\n return { byState, corruptCount };\n}\n\n/** Global _results/ artifact count + total bytes (unbounded-growth watch). */\nexport function gatherResults(resultsDir: string): ResultArtifacts {\n let fileCount = 0;\n let totalBytes = 0;\n try {\n for (const n of readdirSync(resultsDir)) {\n try {\n const s = statSync(join(resultsDir, n));\n if (s.isFile()) { fileCount++; totalBytes += s.size; }\n } catch { /* vanished mid-scan */ }\n }\n } catch { /* no results dir */ }\n return { fileCount, totalBytes };\n}\n","// Session freshness logic — daily + templateHash rotation.\n// Extracted from packages/cli/src/commands/launch.ts so it can be\n// unit-tested without spawning real processes.\n\nimport crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport interface SessionRecord {\n lastLaunched: string; // YYYY-MM-DD\n templateHash: string;\n}\n\nexport interface SessionsFile {\n workspaces: Record<string, SessionRecord>;\n}\n\nexport function loadSessions(sessionsPath: string): SessionsFile {\n try {\n return JSON.parse(fs.readFileSync(sessionsPath, \"utf-8\")) as SessionsFile;\n } catch {\n return { workspaces: {} };\n }\n}\n\nexport function saveSessions(sessionsPath: string, sessions: SessionsFile): void {\n const dir = path.dirname(sessionsPath);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(sessionsPath, JSON.stringify(sessions, null, 2) + \"\\n\");\n}\n\nexport function computeTemplateHash(role: string, templatesDir: string): string {\n const hash = crypto.createHash(\"sha256\");\n\n const roleFile = path.join(templatesDir, `${role}.claude.md`);\n const legacyRoleFile = path.join(templatesDir, `${role}.CLAUDE.md`);\n if (fs.existsSync(roleFile)) {\n hash.update(fs.readFileSync(roleFile, \"utf-8\"));\n } else if (fs.existsSync(legacyRoleFile)) {\n hash.update(fs.readFileSync(legacyRoleFile, \"utf-8\"));\n }\n\n const pluginSkillsDir = path.join(templatesDir, \"..\", \"plugin\", \"skills\");\n if (fs.existsSync(pluginSkillsDir)) {\n for (const skill of fs.readdirSync(pluginSkillsDir).sort()) {\n const skillFile = path.join(pluginSkillsDir, skill, \"SKILL.md\");\n if (fs.existsSync(skillFile)) {\n hash.update(fs.readFileSync(skillFile, \"utf-8\"));\n }\n }\n }\n\n return hash.digest(\"hex\").slice(0, 16);\n}\n\nexport function shouldStartFresh(\n workspaceName: string,\n role: string,\n opts: { sessionsPath: string; templatesDir: string },\n): { fresh: boolean; reason?: string } {\n const sessions = loadSessions(opts.sessionsPath);\n const record = sessions.workspaces[workspaceName];\n const today = new Date().toISOString().slice(0, 10);\n const currentHash = computeTemplateHash(role, opts.templatesDir);\n\n if (!record) {\n return { fresh: true, reason: \"first launch\" };\n }\n\n if (record.lastLaunched !== today) {\n return { fresh: true, reason: \"new day — starting fresh session\" };\n }\n\n if (record.templateHash !== currentHash) {\n return { fresh: true, reason: \"template instructions updated\" };\n }\n\n return { fresh: false };\n}\n\nexport function recordSession(\n workspaceName: string,\n role: string,\n opts: { sessionsPath: string; templatesDir: string },\n): void {\n const sessions = loadSessions(opts.sessionsPath);\n sessions.workspaces[workspaceName] = {\n lastLaunched: new Date().toISOString().slice(0, 10),\n templateHash: computeTemplateHash(role, opts.templatesDir),\n };\n saveSessions(opts.sessionsPath, sessions);\n}\n","// Pure crew protocol and naming primitives — no I/O, no external-package deps.\n// Extracted from packages/cli/src/commands/crew.ts so they are unit-testable\n// and importable by packages other than cli.\n\n/** Configuration for the post-send acceptance check that replaces a naive\n * screen-changed comparison. For agents whose idle splash keeps mutating\n * (opencode's \"Ask anything…\" with blinking cursor / status line), the old\n * check would always see a different screen and never re-send a dropped turn. */\nexport interface TurnAcceptanceConfig {\n /** Text that identifies the idle splash state. When set, acceptance requires\n * this marker to be absent from the screen (e.g. \"Ask anything…\" for opencode).\n * Without it, acceptance defaults to \"screen changed\" (claude behavior). */\n splashMarker?: string;\n /** Max rounds of \"wait, check, re-send\" after the initial send. Defaults to 2\n * (initial + 1 re-send) to match the pre-retry behavior for claude. Use 3 for\n * opencode which has a wider boot-race window. */\n retryLimit?: number;\n}\n\n/** Normalizes screen/marker text for splash-marker matching: case-insensitive,\n * whitespace-collapsed, and treats the single-char ellipsis (U+2026) and \"...\"\n * interchangeably. opencode's idle-splash wording rotates through example\n * prompts and has drifted in exact glyph/punctuation across versions (#499:\n * the hardcoded \"Ask anything…\" (U+2026) never matched real \"Ask anything...\"\n * (three ASCII dots) renders), so matching the literal string is unsafe —\n * match a stable substring instead. */\nfunction normalizeForSplashMatch(text: string): string {\n return text.toLowerCase().replace(/…/g, \"...\").replace(/\\s+/g, \" \").trim();\n}\n\n/** True when `marker` appears in `screen` under splash-match normalization. */\nexport function screenHasSplashMarker(screen: string, marker: string): boolean {\n return normalizeForSplashMatch(screen).includes(normalizeForSplashMatch(marker));\n}\n\n/** Pure-function decision: was the first turn accepted by the TUI?\n * - With splashMarker: accepted = the marker is no longer visible (the TUI left\n * its idle splash, confirming the keystroke was received).\n * - Without splashMarker (claude): accepted = the screen changed after sending.\n *\n * Callers on the splash path MUST additionally gate on having observed the\n * splash marker at least once before trusting \"marker absent\" as acceptance\n * (see crew-pane.ts's sawSplash latch) — a marker that never matches (drift,\n * misconfiguration) would otherwise make this return true from the first\n * check, before any keystroke lands (#499). */\nexport function isTurnAccepted(\n preSendScreen: string,\n afterScreen: string,\n config?: TurnAcceptanceConfig,\n): boolean {\n if (config?.splashMarker) {\n return !screenHasSplashMarker(afterScreen, config.splashMarker);\n }\n return afterScreen !== preSendScreen;\n}\n\n/** Builds the completion-protocol suffix baked into claude + opencode first turns (#278).\n * Substituting --task-id and --project at source makes the signal robust to env-var\n * races (Mode 1) and gives the model a concrete imperative at the point of action (Mode 2).\n *\n * WARNING: The exact output text is load-bearing — a single byte change silently\n * breaks crew DONE. Any modification must be validated against the crew-lifecycle\n * checklist CP-DONE checkpoint. A snapshot test guards against drift. */\nexport function buildCompletionProtocol(taskId: string, project: string): string {\n return [\n \"---\",\n \"COMPLETION PROTOCOL (required): When this task is fully complete, your FINAL action MUST be to run exactly:\",\n ` squadrant crew signal done --task-id ${taskId} --project ${project} --message \"<one-line summary>\"`,\n \"Run it as a discrete final step AFTER you report your results. If you are blocked or need a decision, instead run:\",\n ` squadrant crew signal blocked --task-id ${taskId} --project ${project} --question \"<your question>\"`,\n \"If this task failed because of a defect in squadrant itself (not an API/infra blip, a config/user error, or an expected failure), say so in your signal done/blocked message so the captain can check tu11aa/squadrant and file it. Don't file issues from the crew.\",\n ].join(\"\\n\");\n}\n\n// POSIX single-quote a path so it is safe to embed in a shell command even\n// when the path contains spaces or special characters.\nexport function shellQuote(p: string): string {\n return \"'\" + p.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n\nexport function titleFor(project: string, name: string): string {\n return `🔧 ${project}:${name}`;\n}\n\n// #387: crews run arbitrary CPU-heavy commands (npm run build && npm test) at\n// their own discretion — squadrant never sees or controls those invocations,\n// so there's no central point to queue or cap them. `nice` sidesteps that:\n// applied to the crew's top-level CLI process at launch, every child it later\n// forks (tsc, vitest workers, pnpm) inherits the lowered scheduling priority.\n// Under N concurrent crews this keeps the OS scheduler favoring cmux/the\n// daemon's control-plane process over crew compute, so a burst of crew builds\n// slows down instead of starving the process that both crews depend on to stay\n// reachable. NICE_LEVEL 10 is a moderate deprioritization (range -20..19,\n// default 0) — enough to yield under contention without idling crew work when\n// the machine is otherwise quiet.\nconst CREW_NICE_LEVEL = 10;\n\nexport function niceCrewCommand(cmd: string): string {\n return `nice -n ${CREW_NICE_LEVEL} ${cmd}`;\n}\n\nexport function isCrewTitle(project: string, title: string): boolean {\n return title.startsWith(`🔧 ${project}:`);\n}\n\nexport function nameFromTitle(project: string, title: string): string {\n return title.slice(`🔧 ${project}:`.length);\n}\n\nexport function nextAutoName(existingTitles: string[], project: string): string {\n const used = new Set<number>();\n for (const title of existingTitles) {\n const n = nameFromTitle(project, title).match(/^crew-(\\d+)$/);\n if (n) used.add(Number(n[1]));\n }\n let i = 1;\n while (used.has(i)) i++;\n return `crew-${i}`;\n}\n","// Crew child-process lifecycle management.\n// Extracted from packages/cli/src/commands/crew.ts so it is importable from\n// packages other than cli and testable with an injected exec function.\n\nimport { exec as nodeExec } from \"node:child_process\";\n\ntype ExecFn = (\n cmd: string,\n opts: { maxBuffer: number },\n cb: (err: Error | null, stdout: string) => void,\n) => void;\n\n/** Kill every process that inherited SQUADRANT_CREW_TASK_ID=<taskId> from the\n * crew's shell env prefix. Uses `ps auxE` which exposes env vars for node\n * processes on macOS (vitest workers, the crew CLI, etc.). Best-effort:\n * swallows all errors so a childless crew still closes cleanly.\n *\n * @param graceMs - ms between SIGTERM and SIGKILL (default 2 s; pass a short\n * value in tests to avoid waiting)\n * @param execFn - injectable for testing; defaults to node:child_process.exec\n */\nexport async function reapCrewChildren(\n taskId: string,\n graceMs = 2000,\n execFn: ExecFn = nodeExec,\n): Promise<void> {\n const marker = `SQUADRANT_CREW_TASK_ID=${taskId}`;\n try {\n const stdout = await new Promise<string>((resolve, reject) => {\n // `ps auxE` dumps every process's full env, which on a busy machine far\n // exceeds exec's default 1 MB maxBuffer (~2.7 MB with ~1k procs). Without\n // a raised cap the call errors with \"maxBuffer length exceeded\", the outer\n // catch swallows it, and the reap silently no-ops — leaving crew children\n // alive. 64 MB comfortably covers thousands of processes.\n execFn(\"ps auxE\", { maxBuffer: 64 * 1024 * 1024 }, (err, out) =>\n err ? reject(err) : resolve(out),\n );\n });\n const pids: number[] = [];\n for (const line of stdout.split(\"\\n\").slice(1)) {\n if (!line.includes(marker)) continue;\n const pid = parseInt(line.trim().split(/\\s+/)[1], 10);\n if (!isNaN(pid) && pid !== process.pid) pids.push(pid);\n }\n if (pids.length === 0) return;\n for (const pid of pids) {\n try { process.kill(pid, \"SIGTERM\"); } catch { /* already gone */ }\n }\n await new Promise<void>((r) => setTimeout(r, graceMs));\n for (const pid of pids) {\n try { process.kill(pid, \"SIGKILL\"); } catch { /* already gone */ }\n }\n } catch { /* best-effort */ }\n}\n","// Pure auth predicates for the Telegram CONTROL surfaces (auto-launch, general\n// commands). No I/O. Fail-closed: control requires both the master switch and a\n// user-id match — chat membership alone is never enough for control.\nimport type { TelegramConfig } from \"@squadrant/shared\";\n\nexport function isControlEnabled(cfg: TelegramConfig): boolean {\n return cfg.remoteControl === true;\n}\n\nexport function isAuthorized(fromId: number | undefined, cfg: TelegramConfig): boolean {\n if (fromId === undefined) return false;\n return Array.isArray(cfg.users) && cfg.users.includes(fromId);\n}\n","// Curated registry for the Telegram GENERAL command channel (#402). Pure logic:\n// parses \"/cmd args\" into a squadrant CLI argv vector — never a shell string. No\n// I/O, no execution (Task 5 wires argv → async execFile). Default-deny on\n// /config set: only WRITABLE_CONFIG_KEYS may be written over Telegram, so secrets\n// (botToken/users/chats/supergroupId) can never be set from the phone.\n//\n// argv tokens are verified against the real CLI (packages/cli/src/commands/):\n// status → `status`, projects → `projects list`, crews → `crew list <p>`,\n// launch → `launch <p> --headless`, effort → `effort [mode]`, config → `config get|set`,\n// spawn → `crew spawn <p> <task>`.\n\nexport type ParsedCommand =\n | { kind: \"ok\"; name: string; argv: string[] } // argv to pass to the squadrant CLI\n | { kind: \"usage\"; name: string; message: string } // known command, bad args\n | { kind: \"unknown\"; message: string } // not in registry / not a slash command\n | { kind: \"denied\"; message: string }; // e.g. /config set on a protected key\n\n/** Default-deny allowlist of config keys writable over Telegram (#321). Starts\n * intentionally tiny; extend deliberately. Secrets are NEVER added here. */\nexport const WRITABLE_CONFIG_KEYS: readonly string[] = [\"defaults.effort\"];\n\nconst EFFORT_MODES = new Set([\"max\", \"balance\", \"low\"]);\n\ninterface Entry {\n /** Build the argv (or a usage/denied result) from the post-name token list. */\n build(args: string[]): ParsedCommand;\n usage: string;\n}\n\nfunction ok(name: string, argv: string[]): ParsedCommand {\n return { kind: \"ok\", name, argv };\n}\nfunction usage(name: string, message: string): ParsedCommand {\n return { kind: \"usage\", name, message };\n}\n\nconst REGISTRY: Record<string, Entry> = {\n status: { usage: \"/status\", build: () => ok(\"status\", [\"status\"]) },\n projects: { usage: \"/projects\", build: () => ok(\"projects\", [\"projects\", \"list\"]) },\n crews: {\n usage: \"/crews <project>\",\n build: (a) => (a[0] ? ok(\"crews\", [\"crew\", \"list\", a[0]]) : usage(\"crews\", \"usage: /crews <project>\")),\n },\n launch: {\n // --headless (#586, same reason as #520 on the boot-if-down path): runCommand\n // execs this argv from the daemon, which has no CMUX_WORKSPACE_ID and no\n // terminal — a plain `launch` would open the cmux GUI app and exit 0 before\n // the workspace is ever launched.\n usage: \"/launch <project>\",\n build: (a) =>\n a[0] ? ok(\"launch\", [\"launch\", a[0], \"--headless\"]) : usage(\"launch\", \"usage: /launch <project>\"),\n },\n effort: {\n usage: \"/effort [max|balance|low]\",\n build: (a) => {\n if (a.length === 0) return ok(\"effort\", [\"effort\"]);\n if (!EFFORT_MODES.has(a[0])) return usage(\"effort\", \"usage: /effort [max|balance|low]\");\n return ok(\"effort\", [\"effort\", a[0]]);\n },\n },\n config: {\n usage: \"/config get <key> | /config set <key> <value>\",\n build: (a) => {\n const sub = a[0];\n if (sub === \"get\") {\n const key = a[1];\n if (!key) return usage(\"config\", \"usage: /config get <key>\");\n return ok(\"config\", [\"config\", \"get\", key]);\n }\n if (sub === \"set\") {\n const key = a[1];\n const value = a.slice(2).join(\" \");\n if (!key || value === \"\") return usage(\"config\", \"usage: /config set <key> <value>\");\n if (!WRITABLE_CONFIG_KEYS.includes(key)) {\n return {\n kind: \"denied\",\n message: `⛔ '${key}' is not writable over Telegram. Allowed: ${WRITABLE_CONFIG_KEYS.join(\", \")}`,\n };\n }\n return ok(\"config\", [\"config\", \"set\", key, value]);\n }\n return usage(\"config\", \"usage: /config get <key> | /config set <key> <value>\");\n },\n },\n spawn: {\n usage: \"/spawn <project> <task...>\",\n build: (a) => {\n const project = a[0];\n const task = a.slice(1).join(\" \");\n if (!project || task === \"\") return usage(\"spawn\", \"usage: /spawn <project> <task...>\");\n return ok(\"spawn\", [\"crew\", \"spawn\", project, task]);\n },\n },\n mute: {\n usage: \"/mute <project>\",\n build: (a) => (a[0] ? ok(\"mute\", [\"telegram\", \"notify\", a[0], \"off\"]) : usage(\"mute\", \"usage: /mute <project>\")),\n },\n unmute: {\n usage: \"/unmute <project>\",\n build: (a) => (a[0] ? ok(\"unmute\", [\"telegram\", \"notify\", a[0], \"on\"]) : usage(\"unmute\", \"usage: /unmute <project>\")),\n },\n};\n\nfunction helpText(): string {\n const lines = Object.values(REGISTRY).map((e) => ` ${e.usage}`);\n return [\"Available commands:\", ...lines, \" /help\"].join(\"\\n\");\n}\n\n/** Strip the `@botname` suffix Telegram appends to menu-tapped commands in groups. */\nexport function stripBotMention(token: string): string {\n return token.split(\"@\")[0];\n}\n\n/** Parse a raw Telegram message into a curated command. Non-slash text and\n * unregistered commands return `unknown`. */\nexport function parseCommand(text: string): ParsedCommand {\n const trimmed = text.trim();\n if (!trimmed.startsWith(\"/\")) {\n return { kind: \"unknown\", message: \"unknown command — send /help\" };\n }\n const tokens = trimmed.slice(1).split(/\\s+/).filter((t) => t.length > 0);\n const name = stripBotMention(tokens[0] ?? \"\").toLowerCase();\n const args = tokens.slice(1);\n\n if (name === \"help\") {\n return { kind: \"usage\", name: \"help\", message: helpText() };\n }\n const entry = REGISTRY[name];\n if (!entry) {\n return { kind: \"unknown\", message: `unknown command '/${name}' — send /help` };\n }\n return entry.build(args);\n}\n","// Daemon-side capabilities for the Telegram control surfaces (#402/#403). The\n// CLI layer owns process spawning + socket access; the core bridge only sees the\n// injected closures. EVERYTHING that shells out uses async execFile (promisified)\n// with an argv array — never *Sync on the daemon poll path (event-loop\n// starvation, learning #2) and never a shell string (argv already validated by\n// parseCommand).\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { sendRequest } from \"../protocol.js\";\nimport type { ComponentHealth } from \"../liveness.js\";\n\nconst pExecFile = promisify(execFile);\n\n// Telegram message hard limit is 4096 chars; cap below it with headroom for the\n// truncation marker + any reply framing.\nconst MAX_OUTPUT = 3500;\n\n/** Combine a command's stdout/stderr into one capped, human-readable reply. */\nexport function capOutput(stdout: string, stderr: string, max = MAX_OUTPUT): string {\n const out = stdout.trim();\n const err = stderr.trim();\n let combined = out;\n if (err) combined = combined ? `${combined}\\n[stderr] ${err}` : `[stderr] ${err}`;\n if (!combined) combined = \"(no output)\";\n if (combined.length > max) combined = combined.slice(0, max) + \"\\n…[truncated]\";\n return combined;\n}\n\nconst COMMAND_TIMEOUT_MS = 60_000;\n\n/** Run a curated squadrant CLI argv via async execFile, returning capped output.\n * argv is the validated vector from parseCommand — passed as an array (no shell). */\nexport function createRunCommand(cliBin: string): (argv: string[]) => Promise<string> {\n return async (argv: string[]) => {\n try {\n const { stdout, stderr } = await pExecFile(\n process.execPath,\n [cliBin, ...argv],\n { timeout: COMMAND_TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024 },\n );\n return capOutput(stdout ?? \"\", stderr ?? \"\");\n } catch (e) {\n // execFile rejects on non-zero exit / timeout; surface its captured output.\n const err = e as { stdout?: string; stderr?: string; message?: string };\n return capOutput(err.stdout ?? \"\", err.stderr ?? err.message ?? \"command failed\");\n }\n };\n}\n\n/** Pure: a captain counts alive ONLY in state \"alive\" — stopped (closed),\n * gone (crashed), and unknown/missing all mean \"not alive\" → boot (#517). */\nexport function isCaptainAliveFromHealth(rows: ComponentHealth[], project: string): boolean {\n return rows.some((h) => h.kind === \"captain\" && h.project === project && h.state === \"alive\");\n}\n\n/** Liveness probe via the daemon health endpoint (mirrors group.ts isCaptainAlive). */\nexport function createIsCaptainAlive(sock: string): (project: string) => Promise<boolean> {\n return async (project: string) => {\n try {\n const health = (await sendRequest(sock, { kind: \"health\", project }, 5000)) as ComponentHealth[];\n return isCaptainAliveFromHealth(health ?? [], project);\n } catch {\n return false;\n }\n };\n}\n\n/** Boot a captain via async execFile (NEVER execSync on the daemon hot path).\n * --headless (#520): the daemon has no CMUX_WORKSPACE_ID and no terminal, so\n * a plain `squadrant launch` would open the cmux GUI app and exit 0 without\n * ever creating a workspace. --headless makes launch drive runtime.spawn\n * directly instead. `log`, when given, records the subprocess's captured\n * output (or failure) so a broken launch leaves a diagnostic trail instead\n * of failing silently while ensureCaptainAlive polls to a timeout. */\nexport function createLaunch(cliBin: string, log?: (m: string) => void): (project: string) => Promise<void> {\n return (project: string) =>\n new Promise<void>((resolve, reject) => {\n execFile(\n process.execPath,\n [cliBin, \"launch\", project, \"--headless\"],\n { timeout: 30_000 },\n (err, stdout, stderr) => {\n const output = capOutput(stdout ?? \"\", stderr ?? \"\");\n if (err) {\n log?.(`launch ${project} failed: ${output}`);\n reject(err);\n return;\n }\n if (output !== \"(no output)\") log?.(`launch ${project}: ${output}`);\n resolve();\n },\n );\n });\n}\n","// Boot-if-down capability for Telegram auto-launch (#403). Mirrors the\n// `group dispatch` warmup pattern (liveness probe → spawn `squadrant launch` →\n// bounded warmup poll) but as an injectable factory: deps are stubbed in tests\n// and wired from the daemon host (Task 5). The bridge stays decoupled from\n// captain lifecycle — it only sees the returned `ensure(project)` closure.\n//\n// Debounce: concurrent calls for the same project share ONE launch + poll loop\n// via an in-flight promise map, so a burst of inbound messages can't spawn N\n// captains. The map entry clears on resolution (alive | launched | timeout).\n\nexport type EnsureResult = \"alive\" | \"launched\" | \"timeout\";\n\nexport interface EnsureCaptainDeps {\n isAlive: (project: string) => Promise<boolean>; // liveness probe\n launch: (project: string) => Promise<void>; // spawn `squadrant launch <project>`\n warmupTimeoutMs?: number; // default 120_000\n pollMs?: number; // default 1_000\n sleep?: (ms: number) => Promise<void>; // injectable for tests\n now?: () => number; // injectable for tests\n}\n\nconst DEFAULT_WARMUP_TIMEOUT_MS = 120_000;\nconst DEFAULT_POLL_MS = 1_000;\nconst defaultSleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\n\nexport function createEnsureCaptainAlive(\n deps: EnsureCaptainDeps,\n): (project: string) => Promise<EnsureResult> {\n const warmupTimeoutMs = deps.warmupTimeoutMs ?? DEFAULT_WARMUP_TIMEOUT_MS;\n const pollMs = deps.pollMs ?? DEFAULT_POLL_MS;\n const sleep = deps.sleep ?? defaultSleep;\n const now = deps.now ?? (() => Date.now());\n\n const inFlight = new Map<string, Promise<EnsureResult>>();\n\n async function run(project: string): Promise<EnsureResult> {\n if (await deps.isAlive(project)) return \"alive\";\n await deps.launch(project);\n const deadline = now() + warmupTimeoutMs;\n while (now() < deadline) {\n if (await deps.isAlive(project)) return \"launched\";\n await sleep(pollMs);\n }\n return \"timeout\";\n }\n\n return function ensure(project: string): Promise<EnsureResult> {\n // The guard is read+set synchronously (no await before set) so concurrent\n // callers for the same project provably share a single launch.\n const existing = inFlight.get(project);\n if (existing) return existing;\n const p = run(project).finally(() => inFlight.delete(project));\n inFlight.set(project, p);\n return p;\n };\n}\n","// Pure formatters for the Telegram bridge. No I/O, no side effects.\nimport type { ControlEvent } from \"@squadrant/shared\";\n\n/** Forum-topic title for a project. v1 uses the project name verbatim. */\nexport function topicName(project: string): string {\n return project;\n}\n\n/** Outbound text pushed to a project's Telegram topic for a lifecycle event. */\nexport function formatLifecycle(project: string, ev: ControlEvent): string {\n switch (ev.type) {\n case \"task.done\":\n return `✅ [${project}] CREW DONE · ${ev.id}` + (ev.message ? `\\n${ev.message}` : \"\");\n case \"task.blocked\":\n return `🚧 [${project}] CREW BLOCKED · ${ev.id}\\n${ev.question}`;\n case \"task.review\":\n return `👀 [${project}] CREW REVIEW · ${ev.id}` + (ev.message ? `\\n${ev.message}` : \"\");\n case \"task.idle\":\n return `💤 [${project}] CREW IDLE · ${ev.id}`;\n case \"task.failed\":\n return `❌ [${project}] CREW FAILED · ${ev.id}\\n${ev.error}`;\n case \"task.approval.requested\":\n return `🔐 [${project}] APPROVAL NEEDED · ${ev.id}\\n${ev.question}`;\n case \"task.input.requested\":\n return `❓ [${project}] INPUT NEEDED · ${ev.id}\\n${ev.question}`;\n case \"task.timeout\":\n return `⏱️ [${project}] CREW TIMEOUT · ${ev.id}`;\n default:\n return `ℹ️ [${project}] ${ev.type} · ${ev.id}`;\n }\n}\n\n/** Captain-pane rendering of an inbound Telegram reply — labeled as external. */\nexport function formatInbound(text: string): string {\n return `📩 [from Telegram] ${text}`;\n}\n\n/** Mask all but the last 4 characters of a bot token for safe display. */\nexport function maskToken(token: string): string {\n if (token.length <= 4) return token;\n return \"*\".repeat(token.length - 4) + token.slice(-4);\n}\n","// Persisted Telegram bridge state: getUpdates offset + (project,scope) → topicId\n// registry. Synchronous JSON in stateRoot/telegram-state.json.\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport interface TelegramState {\n offset: number;\n /** key = `${project}::${scope}` (see topicKey); value = message_thread_id. */\n topics: Record<string, number>;\n /** key = project; value = true when active. Absent/false = MUTED (default). */\n notify: Record<string, boolean>;\n /** Last seen inbound message sender — populated passively by the bridge poll. */\n lastUserId?: number;\n}\n\nfunction statePath(stateRoot: string): string {\n return path.join(stateRoot, \"telegram-state.json\");\n}\n\n/** Registry key for a topic. v1 only ever uses scope \"project\"; per-crew routing\n * (scope \"crew:<taskId>\") is additive later without a schema change. */\nexport function topicKey(project: string, scope = \"project\"): string {\n return `${project}::${scope}`;\n}\n\nexport function loadState(stateRoot: string): TelegramState {\n try {\n const raw = fs.readFileSync(statePath(stateRoot), \"utf-8\");\n const data = JSON.parse(raw) as Partial<TelegramState>;\n const result: TelegramState = {\n offset: typeof data.offset === \"number\" ? data.offset : 0,\n topics: data.topics ?? {},\n notify: data.notify ?? {},\n };\n if (typeof data.lastUserId === \"number\") result.lastUserId = data.lastUserId;\n return result;\n } catch {\n return { offset: 0, topics: {}, notify: {} };\n }\n}\n\nexport function saveState(stateRoot: string, s: TelegramState): void {\n fs.mkdirSync(stateRoot, { recursive: true });\n fs.writeFileSync(statePath(stateRoot), JSON.stringify(s, null, 2) + \"\\n\");\n}\n\nexport function setTopic(\n stateRoot: string,\n project: string,\n topicId: number,\n scope = \"project\",\n): void {\n const s = loadState(stateRoot);\n s.topics[topicKey(project, scope)] = topicId;\n saveState(stateRoot, s);\n}\n\nexport function isNotifyActive(stateRoot: string, project: string): boolean {\n return loadState(stateRoot).notify[project] === true;\n}\n\nexport function setLastUserId(stateRoot: string, id: number): void {\n const s = loadState(stateRoot);\n s.lastUserId = id;\n saveState(stateRoot, s);\n}\n\nexport function setNotify(stateRoot: string, project: string, active: boolean): void {\n const s = loadState(stateRoot);\n s.notify[project] = active;\n saveState(stateRoot, s);\n}\n\nexport function findProjectByThread(\n stateRoot: string,\n threadId: number,\n): { project: string; scope: string } | null {\n const s = loadState(stateRoot);\n for (const [key, id] of Object.entries(s.topics)) {\n if (id !== threadId) continue;\n const sep = key.indexOf(\"::\");\n if (sep === -1) continue;\n return { project: key.slice(0, sep), scope: key.slice(sep + 2) };\n }\n return null;\n}\n","// Telegram Bot API over plain fetch — no runtime SDK (keeps the tsup single\n// binary lean). @grammyjs/types is a devDependency: type-only, erased at build.\nimport type { Update } from \"@grammyjs/types\";\n\nexport interface TelegramClient {\n /** Long-poll for updates. timeoutSec is the Bot API `timeout` (default 50s). */\n getUpdates(offset: number, timeoutSec?: number): Promise<Update[]>;\n sendMessage(chatId: number, threadId: number | undefined, text: string, replyMarkup?: unknown): Promise<void>;\n /** Answer a callback_query — REQUIRED on every tap path or the spinner hangs ~15s. */\n answerCallbackQuery(callbackQueryId: string, text?: string): Promise<void>;\n /** Replace an existing message's inline keyboard (panel re-render). */\n editMessageReplyMarkup(chatId: number, messageId: number, replyMarkup: unknown): Promise<void>;\n /** Returns the new topic's message_thread_id. */\n createForumTopic(chatId: number, name: string): Promise<number>;\n /** Verify the bot token and return the bot identity. */\n getMe(): Promise<{ id: number; username: string }>;\n /** Register the bot's command menu with Telegram. */\n setMyCommands(commands: Array<{ command: string; description: string }>): Promise<void>;\n /** Send a chat action (e.g. \"typing\") to show activity to the user. */\n sendChatAction(chatId: number, threadId: number | undefined, action: string): Promise<void>;\n}\n\ninterface TgResponse<T> {\n ok: boolean;\n result?: T;\n error_code?: number;\n description?: string;\n}\n\nexport function createTelegramClient(opts: { token: string; fetch?: typeof fetch }): TelegramClient {\n const fetchImpl = opts.fetch ?? fetch;\n const base = `https://api.telegram.org/bot${opts.token}`;\n\n async function call<T>(method: string, body: Record<string, unknown>): Promise<T> {\n const res = await fetchImpl(`${base}/${method}`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n const json = (await res.json()) as TgResponse<T>;\n if (!res.ok || !json.ok) {\n const code = json.error_code ?? res.status;\n const desc = json.description ?? \"unknown error\";\n throw new Error(`telegram ${method} failed (${code}): ${desc}`);\n }\n return json.result as T;\n }\n\n return {\n async getMe() {\n const r = await call<{ id: number; username: string }>(\"getMe\", {});\n return { id: r.id, username: r.username };\n },\n getUpdates(offset, timeoutSec = 50) {\n return call<Update[]>(\"getUpdates\", { offset, timeout: timeoutSec });\n },\n async sendMessage(chatId, threadId, text, replyMarkup) {\n const body: Record<string, unknown> = { chat_id: chatId, text };\n if (threadId !== undefined) body.message_thread_id = threadId;\n if (replyMarkup !== undefined) body.reply_markup = replyMarkup;\n await call<unknown>(\"sendMessage\", body);\n },\n async answerCallbackQuery(callbackQueryId, text) {\n const body: Record<string, unknown> = { callback_query_id: callbackQueryId };\n if (text !== undefined) body.text = text;\n await call<unknown>(\"answerCallbackQuery\", body);\n },\n async editMessageReplyMarkup(chatId, messageId, replyMarkup) {\n await call<unknown>(\"editMessageReplyMarkup\", { chat_id: chatId, message_id: messageId, reply_markup: replyMarkup });\n },\n async createForumTopic(chatId, name) {\n const r = await call<{ message_thread_id: number }>(\"createForumTopic\", { chat_id: chatId, name });\n return r.message_thread_id;\n },\n async setMyCommands(commands) {\n await call<boolean>(\"setMyCommands\", { commands });\n },\n async sendChatAction(chatId, threadId, action) {\n const body: Record<string, unknown> = { chat_id: chatId, action };\n if (threadId !== undefined) body.message_thread_id = threadId;\n await call<unknown>(\"sendChatAction\", body);\n },\n };\n}\n","// Daemon-internal Telegram subsystem (modeled on CmuxEventsBridge). Owns one\n// outbound hook (pushLifecycle) and one inbound getUpdates long-poll. Opt-in and\n// crash-contained: no send/poll error may escape into the daemon.\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport type { ControlEvent, CrewTier, NotifyConfig, TelegramConfig } from \"@squadrant/shared\";\nimport { resolveNotify, loadProjectOverride, saveProjectOverride, loadConfig } from \"@squadrant/shared\";\nimport type { TelegramClient } from \"./client.js\";\nimport { isAuthorized, isControlEnabled } from \"./auth.js\";\nimport { parseCommand, stripBotMention } from \"./commands.js\";\nimport type { EnsureResult } from \"./ensure-captain.js\";\nimport { formatInbound, formatLifecycle, topicName } from \"./format.js\";\nimport { buildSpawnPrompt, effortPanel, notifyPanel, parseCallback, parseSpawnPrompt, projectPicker, spawnPicker, type PickAction } from \"./panels.js\";\nimport { findProjectByThread, loadState, saveState, setLastUserId, setNotify, setTopic, topicKey } from \"./state.js\";\nimport { tierIncludes } from \"./tiers.js\";\n\n/** A Telegram callback_query (button tap). Narrowed to the fields the bridge uses. */\ninterface CallbackQuery {\n id: string;\n from?: { id: number };\n message?: { chat: { id: number }; message_id: number; message_thread_id?: number };\n data?: string;\n}\n\n/** Read-only poll-loop health (B3 — dashboard visibility). A silently-dying\n * getUpdates loop otherwise looks identical to a healthy quiet one from outside. */\nexport interface TelegramBridgeHealth {\n polling: boolean;\n lastSuccessfulPollAt: number | null;\n lastError: string | null;\n lastErrorAt: number | null;\n}\n\nexport interface TelegramBridge {\n start(): void;\n stop(): void;\n /** Outbound, best-effort: a Telegram failure is swallowed (logged), never thrown. */\n pushLifecycle(project: string, ev: ControlEvent): void;\n /** Outbound, best-effort, out-of-band, fault-class alert — for system faults\n * (e.g. #579/#484's DELIVERY STUCK), not routine crew notifications. Bypasses\n * BOTH the crew-tier filter AND per-project mute: mute is a user's choice to\n * silence routine notification *noise* (crew progress/done/blocked), a\n * choice about volume. It was never a choice to hide \"your instructions\n * can't reach the captain\" — an operational fault, not noise. A muted\n * project with a stuck delivery must still alert, or muting silently\n * reintroduces the exact silent-stall bug this alert exists to prevent.\n * Never touches the mailbox/pane path (so it can't itself get stuck). */\n pushRaw(project: string, text: string): void;\n health(): TelegramBridgeHealth;\n}\n\nexport interface TelegramBridgeOptions {\n cfg: TelegramConfig;\n stateRoot: string;\n /** Root for per-project override files. Defaults to ~/.config/squadrant. */\n configRoot?: string;\n client: TelegramClient;\n appendCaptainMessage: (a: { stateRoot: string; project: string; text: string; source: \"telegram\" | \"daemon\" | \"cli\" }) => Promise<void | number>;\n log: (msg: string) => void;\n // ── Control surfaces (#402/#403/#321) — all optional. When undefined the bridge\n // keeps exact v1 behavior (queue-only project topics, General topic dropped).\n /** Boot-if-down before delivering to a project topic. Injected by the daemon host. */\n ensureCaptainAlive?: (project: string) => Promise<EnsureResult>;\n /** Execute a curated squadrant CLI argv and return capped output. */\n runCommand?: (argv: string[]) => Promise<string>;\n /** Post a reply to the General topic (threadId undefined) or a project topic.\n * The optional replyMarkup attaches an inline-button panel (tap-first commands). */\n sendReply?: (threadId: number | undefined, text: string, replyMarkup?: unknown) => Promise<void>;\n}\n\n// Bot API long-poll window. The loop also sleeps cfg.pollMs between iterations so\n// a fast-returning poll can't busy-loop.\nconst LONG_POLL_SEC = 50;\n\nconst sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\n\nconst CREW_TIERS = [\"all\", \"alert_only\", \"done_only\", \"none\"];\n\n// Channel commands that may run in ANY topic (#cmds-anytopic). mute/unmute/notify\n// are intentionally absent: in a project topic they carry topic-scoped semantics\n// (handled before delegating). Matched against the slash-stripped first token.\nconst RECOGNIZED_CHANNEL_COMMANDS = new Set([\"status\", \"projects\", \"crews\", \"launch\", \"effort\", \"spawn\"]);\n\n/** Parse a `/notify crew <tier>` or `/notify cap <on|off>` preference command.\n * Returns null for anything else (ordinary message or malformed). */\nexport function parseNotifyPref(text: string): { dimension: \"crew\" | \"cap\"; value: string } | null {\n const parts = text.trim().split(/\\s+/);\n if (stripBotMention(parts[0] ?? \"\").toLowerCase() !== \"/notify\") return null;\n const dimension = parts[1]?.toLowerCase();\n if ((dimension === \"crew\" || dimension === \"cap\") && parts[2]) return { dimension, value: parts[2].toLowerCase() };\n return null;\n}\n\n/** True for a bare `/spawn` (no project/task args) — the guided-picker trigger.\n * Strips the `@botname` suffix Telegram appends to menu-tapped commands. */\nexport function isBareSpawn(text: string): boolean {\n const trimmed = text.trim();\n if (!trimmed.startsWith(\"/\")) return false;\n const tokens = trimmed.slice(1).split(/\\s+/).filter((t) => t.length > 0);\n return stripBotMention(tokens[0] ?? \"\").toLowerCase() === \"spawn\" && tokens.length === 1;\n}\n\n/** Recognize the two in-topic notification toggles. Returns the desired active\n * state, or null if the text is an ordinary message. */\nexport function notifyToggle(text: string): boolean | null {\n const first = stripBotMention(text.trim().split(/\\s+/)[0] ?? \"\").toLowerCase();\n if (first === \"/unmute\") return true;\n if (first === \"/mute\") return false;\n return null;\n}\n\nexport function createTelegramBridge(opts: TelegramBridgeOptions): TelegramBridge {\n const { cfg, stateRoot, client, appendCaptainMessage, log, ensureCaptainAlive, runCommand, sendReply } = opts;\n const configRoot = opts.configRoot ?? path.join(os.homedir(), \".config\", \"squadrant\");\n const pollMs = cfg.pollMs ?? 1000;\n let running = false;\n let lastSuccessfulPollAt: number | null = null;\n let lastError: string | null = null;\n let lastErrorAt: number | null = null;\n\n function persistOffset(next: number): void {\n const s = loadState(stateRoot);\n s.offset = next;\n saveState(stateRoot, s);\n }\n\n // Resolve (or lazily create) a project's topic and send raw text into it.\n async function sendToTopic(project: string, text: string): Promise<void> {\n let threadId = loadState(stateRoot).topics[topicKey(project)];\n if (threadId === undefined) {\n threadId = await client.createForumTopic(cfg.supergroupId, topicName(project));\n setTopic(stateRoot, project, threadId);\n }\n await client.sendMessage(cfg.supergroupId, threadId, text);\n }\n\n // Outbound: resolve active (live state wins over config default) + crew-tier\n // filter, then resolve (or lazily create) the project's topic and send.\n async function deliverOutbound(project: string, ev: ControlEvent): Promise<void> {\n const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));\n const live = loadState(stateRoot).notify[project]; // boolean | undefined\n const active = live ?? resolved.active;\n if (!active) return; // muted → no topic create, no send\n if (!tierIncludes(resolved.crew, ev.type)) return; // tier filter\n await sendToTopic(project, formatLifecycle(project, ev));\n }\n\n // Outbound, out-of-band, fault-class: bypasses BOTH the crew-tier filter AND\n // mute (see the pushRaw docstring for why mute must not silence a fault).\n async function deliverRawOutbound(project: string, text: string): Promise<void> {\n await sendToTopic(project, text);\n }\n\n // Live notify state for a project: resolved config (built-in→global→override)\n // with the live `active` overlay (state wins over config default).\n function resolveLiveNotify(project: string): NotifyConfig {\n const resolved = resolveNotify(cfg.notify, loadProjectOverride(project, configRoot));\n const live = loadState(stateRoot).notify[project]; // boolean | undefined\n return { ...resolved, active: live ?? resolved.active };\n }\n\n // Re-render a panel's keyboard, swallowing the Bot API \"message is not\n // modified\" error that fires when the new keyboard equals the old one.\n async function editMarkup(chatId: number, messageId: number, markup: unknown): Promise<void> {\n try {\n await client.editMessageReplyMarkup(chatId, messageId, markup);\n } catch (e) {\n const msg = (e as Error).message;\n if (!/not modified/i.test(msg)) throw e;\n }\n }\n\n // callback_query (inline-button tap). ALWAYS answerCallbackQuery on every path\n // (else the spinner hangs ~15s). Gate on the TAPPER's user-id, never the panel.\n // Render state fresh. Never throw into the poll loop.\n async function handleCallback(cq: CallbackQuery): Promise<void> {\n try {\n if (!cq.data || !cq.message) {\n await client.answerCallbackQuery(cq.id);\n return;\n }\n if (!isControlEnabled(cfg) || !isAuthorized(cq.from?.id, cfg)) {\n await client.answerCallbackQuery(cq.id, \"⛔ not authorized\");\n return;\n }\n const action = parseCallback(cq.data);\n if (!action) {\n await client.answerCallbackQuery(cq.id);\n return;\n }\n const chatId = cq.message.chat.id;\n const messageId = cq.message.message_id;\n\n if (action.t === \"notify\") {\n const resolved = findProjectByThread(stateRoot, cq.message.message_thread_id ?? -1);\n if (!resolved) {\n await client.answerCallbackQuery(cq.id, \"no project for this topic\");\n return;\n }\n const project = resolved.project;\n if (action.dim === \"active\") {\n setNotify(stateRoot, project, action.val === \"on\");\n } else if (action.dim === \"cap\") {\n saveProjectOverride(project, { telegram: { notify: { cap: action.val === \"on\" } } }, configRoot);\n } else {\n saveProjectOverride(project, { telegram: { notify: { crew: action.val as CrewTier } } }, configRoot);\n }\n await client.answerCallbackQuery(cq.id, `✅ ${action.dim} = ${action.val}`);\n await editMarkup(chatId, messageId, notifyPanel(resolveLiveNotify(project)));\n return;\n }\n\n if (action.t === \"effort\") {\n if (runCommand) await runCommand([\"effort\", action.mode]);\n await client.answerCallbackQuery(cq.id, `✅ effort = ${action.mode}`);\n await editMarkup(chatId, messageId, effortPanel(action.mode as \"max\" | \"balance\" | \"low\"));\n return;\n }\n\n if (action.t === \"spawn\") {\n // Send a ForceReply prompt carrying the project; the reply is routed to\n // `crew spawn` statelessly via parseSpawnPrompt (no pending-state map).\n await reply(cq.message.message_thread_id, buildSpawnPrompt(action.project), { force_reply: true, selective: true });\n await client.answerCallbackQuery(cq.id);\n return;\n }\n\n // action.t === \"pick\" — General-topic project actions.\n const { action: act, project } = action;\n if (act === \"cr\") {\n const out = runCommand ? await runCommand([\"crew\", \"list\", project]) : \"(command runner unavailable)\";\n await client.answerCallbackQuery(cq.id);\n await reply(undefined, out);\n } else if (act === \"lc\") {\n if (runCommand) await runCommand([\"launch\", project]);\n await client.answerCallbackQuery(cq.id, `launching ${project}`);\n } else if (act === \"mu\") {\n setNotify(stateRoot, project, false);\n await client.answerCallbackQuery(cq.id, `🔕 muted ${project}`);\n } else {\n setNotify(stateRoot, project, true);\n await client.answerCallbackQuery(cq.id, `🔔 unmuted ${project}`);\n }\n } catch (e) {\n log(`telegram callback failed data=${cq.data}: ${(e as Error).message}`);\n try {\n await client.answerCallbackQuery(cq.id, \"⚠️ failed\");\n } catch {\n /* answer failed too — already logged; never throw into the poll loop */\n }\n }\n }\n\n // Reply best-effort: a send failure must never escape into the poll loop.\n async function reply(threadId: number | undefined, text: string, replyMarkup?: unknown): Promise<void> {\n if (!sendReply) return;\n try {\n // Keep the 2-arg call shape when there's no panel (markup undefined).\n if (replyMarkup !== undefined) await sendReply(threadId, text, replyMarkup);\n else await sendReply(threadId, text);\n } catch (e) {\n log(`telegram reply failed: ${(e as Error).message}`);\n }\n }\n\n /** Current global effort dial (falls back to today's \"balance\"). */\n function currentEffort(): \"max\" | \"balance\" | \"low\" {\n try {\n return loadConfig(path.join(configRoot, \"config.json\")).defaults.effort ?? \"balance\";\n } catch {\n return \"balance\";\n }\n }\n\n /** Registered project names for the General-topic pickers. */\n function projectNames(): string[] {\n try {\n return Object.keys(loadConfig(path.join(configRoot, \"config.json\")).projects);\n } catch {\n return [];\n }\n }\n\n /** Guided /spawn: reply the project picker (works in General or a project topic). */\n async function replySpawnPicker(threadId: number | undefined): Promise<void> {\n const projects = projectNames();\n if (projects.length === 0) {\n await reply(threadId, \"no projects registered\");\n return;\n }\n await reply(threadId, \"Pick a project to spawn a crew on:\", spawnPicker(projects));\n }\n\n // Curated command channel (#402), shared by the General topic (threadId\n // undefined) and project topics (#cmds-anytopic). Fail-closed — a command runs\n // ONLY when remoteControl is on AND the sender is allowlisted. Tap-first: a\n // parameterized command with NO argument replies a button panel instead of a\n // usage error; typed forms (with an arg) fall through to run. Replies land in\n // the given thread; failures are caught here so they can't escape the poll loop.\n async function runChannelCommand(text: string, fromId: number | undefined, threadId: number | undefined): Promise<void> {\n if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {\n await reply(threadId, \"⛔ not authorized\");\n return;\n }\n const tokens = text.trim().slice(1).split(/\\s+/).filter((t) => t.length > 0);\n const name = stripBotMention(tokens[0] ?? \"\").toLowerCase();\n const noArg = tokens.length === 1;\n if (noArg && name === \"effort\") {\n await reply(threadId, \"Effort mode:\", effortPanel(currentEffort()));\n return;\n }\n if (noArg && name === \"spawn\") {\n await replySpawnPicker(threadId);\n return;\n }\n const PICKERS: Record<string, PickAction> = { crews: \"cr\", launch: \"lc\", mute: \"mu\", unmute: \"um\" };\n if (noArg && name in PICKERS) {\n const projects = projectNames();\n if (projects.length === 0) {\n await reply(threadId, \"no projects registered\");\n return;\n }\n await reply(threadId, `Pick a project:`, projectPicker(PICKERS[name], projects));\n return;\n }\n const parsed = parseCommand(text);\n if (parsed.kind !== \"ok\") {\n await reply(threadId, parsed.message);\n return;\n }\n try {\n const out = runCommand ? await runCommand(parsed.argv) : \"(command runner unavailable)\";\n await reply(threadId, out);\n } catch (e) {\n await reply(threadId, `⚠️ command failed: ${(e as Error).message}`);\n log(`telegram command failed argv=${JSON.stringify(parsed.argv)}: ${(e as Error).message}`);\n }\n }\n\n // General topic (no thread id): freeform text gets a /help hint (never silently\n // dropped); slash commands run through the shared channel-command dispatcher.\n async function handleGeneral(text: string, fromId: number | undefined): Promise<void> {\n if (!text.startsWith(\"/\")) {\n await reply(undefined, \"Send /help for commands.\");\n return;\n }\n await runChannelCommand(text, fromId, undefined);\n }\n\n // Project topic: the v1 captain.message flow + Gap-1 auto-launch (#403). When\n // control is off OR the sender isn't allowlisted, behaves exactly as v1\n // (append only). The append throws on delivery-infra failure so the caller can\n // decline to advance the offset (at-least-once); auto-launch failures are\n // contained and never block the append.\n async function handleProjectTopic(text: string, threadId: number, fromId: number | undefined): Promise<void> {\n const resolved = findProjectByThread(stateRoot, threadId);\n if (!resolved) return; // no project bound to this topic\n\n if (isBareSpawn(text)) {\n // Guided /spawn — picker, never appended. Fail-closed like the toggles.\n if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {\n await reply(threadId, \"⛔ not authorized\");\n return;\n }\n await replySpawnPicker(threadId);\n return;\n }\n\n const toggle = notifyToggle(text);\n if (toggle !== null) {\n // Explicit toggle command — fail-closed, never appended as a captain message.\n if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {\n await reply(threadId, \"⛔ not authorized\");\n return;\n }\n setNotify(stateRoot, resolved.project, toggle);\n await reply(threadId, toggle ? `🔔 ${resolved.project} notifications ON` : `🔕 ${resolved.project} notifications OFF`);\n return;\n }\n\n // Any /notify attempt (including an incomplete one like a bare `/notify`) is\n // handled here and NEVER appended as a captain message. The first token is\n // matched after stripping a `@botname` suffix Telegram adds in groups.\n if (stripBotMention(text.trim().split(/\\s+/)[0] ?? \"\").toLowerCase() === \"/notify\") {\n // Fail-closed: only an allowlisted sender under remoteControl may proceed.\n if (!isControlEnabled(cfg) || !isAuthorized(fromId, cfg)) {\n await reply(threadId, \"⛔ not authorized\");\n return;\n }\n const pref = parseNotifyPref(text);\n if (pref === null) {\n // Incomplete (bare `/notify`, or a dimension with no value) → tap-first panel.\n // Typed forms (`/notify cap on`) still parse below for power users.\n await reply(threadId, `🔔 ${resolved.project} notifications`, notifyPanel(resolveLiveNotify(resolved.project)));\n return;\n }\n // Deliberate preference change — writes the per-project config file (not live state).\n if (pref.dimension === \"crew\") {\n if (!CREW_TIERS.includes(pref.value)) {\n await reply(threadId, \"crew must be all|alert_only|done_only|none\");\n return;\n }\n saveProjectOverride(resolved.project, { telegram: { notify: { crew: pref.value as never } } }, configRoot);\n } else {\n if (pref.value !== \"on\" && pref.value !== \"off\") {\n await reply(threadId, \"cap must be on|off\");\n return;\n }\n saveProjectOverride(resolved.project, { telegram: { notify: { cap: pref.value === \"on\" } } }, configRoot);\n }\n await reply(threadId, `✅ ${pref.dimension} = ${pref.value}`);\n return;\n }\n\n // Recognized channel commands run in this topic too (#cmds-anytopic), with the\n // reply landing here instead of falling through to a captain message. mute/\n // unmute/notify are handled above (topic-scoped) and excluded from the set.\n const firstTok = stripBotMention(text.trim().split(/\\s+/)[0] ?? \"\").toLowerCase();\n if (firstTok.startsWith(\"/\") && RECOGNIZED_CHANNEL_COMMANDS.has(firstTok.slice(1))) {\n await runChannelCommand(text, fromId, threadId);\n return;\n }\n\n void client.sendChatAction(cfg.supergroupId, threadId, \"typing\").catch((e) => {\n log(`telegram sendChatAction failed: ${(e as Error).message}`);\n });\n setNotify(stateRoot, resolved.project, true); // engagement → auto-unmute (sticky)\n if (ensureCaptainAlive && isControlEnabled(cfg) && isAuthorized(fromId, cfg)) {\n try {\n const r = await ensureCaptainAlive(resolved.project);\n // The ensure() result IS the delivery signal — \"live captain reachable\",\n // not \"message read\" (no cap-side ack protocol). Surfacing it means a\n // false-positive isAlive (#517) fails loud in Telegram instead of silently\n // stranding the message in the mailbox.\n if (r === \"timeout\") {\n await reply(threadId, `❌ couldn't reach ${resolved.project} captain — saved to mailbox, will deliver when you open the workspace.`);\n } else {\n await reply(threadId, `📨 delivered to ${resolved.project} captain`);\n }\n } catch (e) {\n log(`telegram auto-launch failed project=${resolved.project}: ${(e as Error).message}`);\n }\n }\n await appendCaptainMessage({ stateRoot, project: resolved.project, text: formatInbound(text), source: \"telegram\" });\n }\n\n // Inbound: classify by thread id. General topic → command channel; project\n // topic → captain.message (+ auto-launch). Throws only on append failure.\n async function handleUpdate(u: { message?: { chat: { id: number }; message_thread_id?: number; text?: string; from?: { id: number }; reply_to_message?: { text?: string } }; callback_query?: CallbackQuery }): Promise<void> {\n if (u.callback_query) {\n await handleCallback(u.callback_query);\n return;\n }\n const m = u.message;\n if (!m || m.text === undefined) return;\n if (!cfg.chats.includes(m.chat.id)) return; // not an allowlisted chat (coarse filter)\n // Passively capture the sender's user-id for setup auto-population (#user-id).\n if (m.from?.id !== undefined && loadState(stateRoot).lastUserId !== m.from.id) {\n setLastUserId(stateRoot, m.from.id);\n }\n // A reply to a guided-/spawn ForceReply prompt → `crew spawn`, gated, never\n // appended. Runs before the thread-id branch because a reply can land in\n // General (no thread id) OR a project topic.\n const spawnProject = parseSpawnPrompt(m.reply_to_message?.text);\n if (spawnProject) {\n const threadId = m.message_thread_id;\n if (!isControlEnabled(cfg) || !isAuthorized(m.from?.id, cfg)) {\n await reply(threadId, \"⛔ not authorized\");\n return;\n }\n const task = m.text.trim();\n if (!task) {\n await reply(threadId, \"spawn cancelled — empty task\");\n return;\n }\n if (runCommand) await runCommand([\"crew\", \"spawn\", spawnProject, task]);\n await reply(threadId, `🆕 spawning a crew on ${spawnProject}…`);\n return; // NOT appended as a captain message\n }\n if (m.message_thread_id === undefined) {\n await handleGeneral(m.text, m.from?.id);\n return;\n }\n await handleProjectTopic(m.text, m.message_thread_id, m.from?.id);\n }\n\n async function pollLoop(): Promise<void> {\n while (running) {\n try {\n const offset = loadState(stateRoot).offset;\n const updates = await client.getUpdates(offset, LONG_POLL_SEC);\n for (const u of updates) {\n await handleUpdate(u);\n persistOffset(u.update_id + 1);\n }\n lastSuccessfulPollAt = Date.now();\n } catch (e) {\n lastError = (e as Error).message;\n lastErrorAt = Date.now();\n log(`telegram inbound poll failed: ${(e as Error).message}`);\n }\n if (running) await sleep(pollMs);\n }\n }\n\n return {\n start() {\n if (running) return;\n running = true;\n void pollLoop();\n },\n stop() {\n running = false;\n },\n pushLifecycle(project, ev) {\n // Fire-and-forget; all errors swallowed so outbound can never throw into\n // the daemon's notify path.\n void deliverOutbound(project, ev).catch((e) => {\n log(`telegram outbound failed project=${project}: ${(e as Error).message}`);\n });\n },\n pushRaw(project, text) {\n void deliverRawOutbound(project, text).catch((e) => {\n log(`telegram raw push failed project=${project}: ${(e as Error).message}`);\n });\n },\n health() {\n return { polling: running, lastSuccessfulPollAt, lastError, lastErrorAt };\n },\n };\n}\n","// Pure inline-keyboard builders + callback_data codec for tap-first Telegram\n// commands. No I/O — unit-tested independent of the bridge. callback_data is\n// prefix-routed and kept ≤64 bytes (Bot API limit).\nimport type { NotifyConfig, CrewTier } from \"@squadrant/shared\";\n\nexport type InlineButton = { text: string; callback_data: string };\nexport type InlineKeyboard = { inline_keyboard: InlineButton[][] };\n\nexport type PickAction = \"cr\" | \"lc\" | \"mu\" | \"um\";\n\nexport type ParsedCallback =\n | { t: \"notify\"; dim: \"cap\" | \"crew\" | \"active\"; val: string }\n | { t: \"effort\"; mode: string }\n | { t: \"pick\"; action: PickAction; project: string }\n | { t: \"spawn\"; project: string };\n\n/** Prefix the label with a bullet when it represents the current state. */\nconst mark = (on: boolean, label: string): string => (on ? `• ${label}` : label);\n\n// Curated crew-tier subset shown as a pick-one row (done_only is reachable via\n// the typed `/notify crew done_only` form for power users).\nconst TIERS: CrewTier[] = [\"none\", \"alert_only\", \"all\"];\n\nexport function notifyPanel(s: NotifyConfig): InlineKeyboard {\n return {\n inline_keyboard: [\n [{ text: `Captain: ${s.cap ? \"ON\" : \"OFF\"}`, callback_data: `n:cap:${s.cap ? \"off\" : \"on\"}` }],\n TIERS.map((t) => ({ text: mark(s.crew === t, `crew:${t}`), callback_data: `n:crew:${t}` })),\n [{ text: s.active ? \"🔕 Mute topic\" : \"🔔 Unmute\", callback_data: `n:active:${s.active ? \"off\" : \"on\"}` }],\n ],\n };\n}\n\nexport function effortPanel(current: \"max\" | \"balance\" | \"low\"): InlineKeyboard {\n const modes = [\"max\", \"balance\", \"low\"] as const;\n return {\n inline_keyboard: [modes.map((m) => ({ text: mark(current === m, m), callback_data: `e:${m}` }))],\n };\n}\n\nexport function projectPicker(action: PickAction, projects: string[]): InlineKeyboard {\n return { inline_keyboard: projects.map((p) => [{ text: p, callback_data: `${action}:${p}` }]) };\n}\n\n// Guided /spawn (slice 2). The picker emits `sp:<project>`; tapping one sends a\n// ForceReply prompt whose text encodes the project behind SPAWN_PROMPT_PREFIX, so\n// the reply can be routed to `crew spawn` statelessly (no pending-spawn map).\nexport const SPAWN_PROMPT_PREFIX = \"🆕 Reply with the task for a crew on: \";\n\nexport function buildSpawnPrompt(project: string): string {\n return `${SPAWN_PROMPT_PREFIX}${project}`;\n}\n\nexport function parseSpawnPrompt(text: string | undefined): string | null {\n if (!text || !text.startsWith(SPAWN_PROMPT_PREFIX)) return null;\n const project = text.slice(SPAWN_PROMPT_PREFIX.length).trim();\n return project.length > 0 ? project : null;\n}\n\nexport function spawnPicker(projects: string[]): InlineKeyboard {\n return { inline_keyboard: projects.map((p) => [{ text: p, callback_data: `sp:${p}` }]) };\n}\n\nconst PICK_ACTIONS: PickAction[] = [\"cr\", \"lc\", \"mu\", \"um\"];\n\nexport function parseCallback(data: string): ParsedCallback | null {\n const parts = data.split(\":\");\n if (parts[0] === \"n\" && (parts[1] === \"cap\" || parts[1] === \"crew\" || parts[1] === \"active\") && parts[2]) {\n return { t: \"notify\", dim: parts[1], val: parts[2] };\n }\n if (parts[0] === \"e\" && parts[1]) return { t: \"effort\", mode: parts[1] };\n if (PICK_ACTIONS.includes(parts[0] as PickAction) && parts[1]) {\n return { t: \"pick\", action: parts[0] as PickAction, project: parts.slice(1).join(\":\") };\n }\n if (parts[0] === \"sp\" && parts[1]) return { t: \"spawn\", project: parts.slice(1).join(\":\") };\n return null;\n}\n","// Crew notification tier → event-type membership. Tiers are cumulative:\n// done_only ⊂ alert_only ⊂ all. See the layered-notification design.\nimport type { CrewTier } from \"@squadrant/shared\";\n\nconst DONE_ONLY = new Set([\"task.done\", \"task.failed\"]);\nconst ALERTS = new Set([\n ...DONE_ONLY,\n \"task.blocked\",\n \"task.review\",\n \"task.approval.requested\",\n \"task.input.requested\",\n \"task.timeout\",\n]);\n\nexport function tierIncludes(tier: CrewTier, eventType: string): boolean {\n switch (tier) {\n case \"none\": return false;\n case \"done_only\": return DONE_ONLY.has(eventType);\n case \"alert_only\": return ALERTS.has(eventType);\n case \"all\": return true;\n }\n}\n","// Pure helpers for the interactive `squadrant telegram setup` wizard.\n// These are exported for testing with injected dependencies.\n// getUpdates is single-consumer — setup runs before the daemon starts polling (#321).\nimport fs from \"node:fs\";\nimport type { TelegramClient } from \"./client.js\";\nimport { loadState } from \"./state.js\";\nimport { BOT_COMMANDS } from \"./bot-commands.js\";\nimport { restartDaemonIfRunning } from \"../restart-daemon.js\";\nimport type { RestartOutcome } from \"../restart-daemon.js\";\n\n/**\n * Decide whether to reuse an existing supergroup or re-detect via getUpdates.\n * Returns 'reuse' when supergroupId is already configured and --redetect was not passed.\n * Prevents getUpdates conflicts with the running daemon poll (#22205).\n */\nexport function resolveSetupGroup(\n existingSupergroupId: number | undefined,\n opts: { redetect: boolean },\n): \"reuse\" | \"detect\" {\n if (existingSupergroupId !== undefined && !opts.redetect) return \"reuse\";\n return \"detect\";\n}\n\n/**\n * Poll getUpdates until a supergroup message arrives, returning both the chat id\n * and the sender's user id (the latter seeds the control allowlist, #321).\n * Injects `sleep` for testability; never used with real delays in tests.\n */\nexport async function detectGroupAndUser(\n client: TelegramClient,\n opts: { timeoutMs?: number; sleep?: (ms: number) => Promise<void> } = {},\n): Promise<{ supergroupId: number; userId: number | undefined }> {\n const timeoutMs = opts.timeoutMs ?? 60_000;\n const sleep = opts.sleep ?? ((ms: number) => new Promise((r) => setTimeout(r, ms)));\n const deadline = Date.now() + timeoutMs;\n let offset = 0;\n\n while (Date.now() < deadline) {\n const updates = await client.getUpdates(offset, 10);\n for (const u of updates) {\n if (u.update_id >= offset) offset = u.update_id + 1;\n if (u.message?.chat?.type === \"supergroup\") {\n return { supergroupId: u.message.chat.id, userId: u.message.from?.id };\n }\n }\n await sleep(2000);\n }\n\n throw new Error(\"Timed out waiting for the bot to receive a message in a supergroup\");\n}\n\n/** Convenience wrapper that returns only the supergroup id. */\nexport async function detectGroupId(\n client: TelegramClient,\n opts: { timeoutMs?: number; sleep?: (ms: number) => Promise<void> } = {},\n): Promise<number> {\n return (await detectGroupAndUser(client, opts)).supergroupId;\n}\n\nexport function resolveSetupToken(\n existingToken: string | undefined,\n opts: { resetToken: boolean },\n): \"prompt\" | \"try-reuse\" {\n if (opts.resetToken || !existingToken) return \"prompt\";\n return \"try-reuse\";\n}\n\n/**\n * Precedence: explicit --user-id flag > detected userId (first-run getUpdates) >\n * lastUserId persisted in telegram-state.json by the bridge poll (passive capture).\n */\nexport function resolveSetupUserId(\n flagUserId: number | undefined,\n detectedUserId: number | undefined,\n stateRoot: string,\n): number | undefined {\n return flagUserId ?? detectedUserId ?? loadState(stateRoot).lastUserId;\n}\n\nexport async function runRegisterCommands(opts: { client: TelegramClient }): Promise<void> {\n await opts.client.setMyCommands(BOT_COMMANDS);\n}\n\nexport function runTelegramPostSetup(opts: {\n doRestart?: (o: { reason: string }) => RestartOutcome;\n}): void {\n const doRestart = opts.doRestart ?? restartDaemonIfRunning;\n const outcome = doRestart({ reason: \"telegram config\" });\n if (outcome === \"skipped-not-running\") {\n console.log(\"(daemon not running — change applies on next start)\");\n } else if (outcome === \"skipped-opt-out\") {\n console.log(\"(run 'squadrant heal daemon' to apply)\");\n }\n}\n\n/**\n * Write or update the telegram block in a squadrant config file.\n * Preserves all existing keys; creates the file with defaults if absent.\n */\nexport function writeTelegramConfig(\n configPath: string,\n opts: { token: string; supergroupId: number; users?: number[]; remoteControl?: boolean },\n): void {\n let config: Record<string, unknown>;\n let raw: string | null = null;\n\n try {\n raw = fs.readFileSync(configPath, \"utf-8\");\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw new Error(`refusing to overwrite unreadable config at ${configPath}: ${String(err)}`);\n }\n }\n\n if (raw !== null) {\n try {\n config = JSON.parse(raw) as Record<string, unknown>;\n } catch (err: unknown) {\n throw new Error(`refusing to overwrite corrupt config at ${configPath}: ${String(err)}`);\n }\n } else {\n config = {};\n }\n\n // Idempotent: re-running setup updates the token/group but preserves existing\n // control fields (users/remoteControl) unless this run supplies new ones.\n const prev = (config.telegram && typeof config.telegram === \"object\")\n ? (config.telegram as Record<string, unknown>) : {};\n const next: Record<string, unknown> = {\n botToken: opts.token,\n supergroupId: opts.supergroupId,\n chats: [opts.supergroupId],\n };\n const users = opts.users ?? prev.users;\n const remoteControl = opts.remoteControl ?? prev.remoteControl;\n if (users !== undefined) next.users = users;\n if (remoteControl !== undefined) next.remoteControl = remoteControl;\n config.telegram = next;\n\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + \"\\n\");\n}\n","import { execFileSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { LABEL, kickstartArgv, tryAcquireDaemonLock, releaseDaemonLock } from \"./launchd.js\";\n\nexport type RestartOutcome = \"restarted\" | \"skipped-not-running\" | \"skipped-opt-out\";\n\nconst DEFAULT_SOCK_PATH = join(homedir(), \".config\", \"squadrant\", \"squadrant.sock\");\n\nfunction defaultIsRunning(): boolean {\n return existsSync(DEFAULT_SOCK_PATH);\n}\n\nfunction defaultRunKickstart(): void {\n const uid = process.getuid?.() ?? 0;\n const target = `gui/${uid}/${LABEL}`;\n if (tryAcquireDaemonLock()) {\n try {\n execFileSync(\"launchctl\", kickstartArgv(target, true), { stdio: \"ignore\" });\n } finally {\n releaseDaemonLock();\n }\n }\n}\n\nexport function restartDaemonIfRunning(opts: {\n reason: string;\n noRestart?: boolean;\n isRunning?: () => boolean;\n runKickstart?: () => void;\n env?: NodeJS.ProcessEnv;\n log?: (m: string) => void;\n}): RestartOutcome {\n const env = opts.env ?? process.env;\n if (env[\"VITEST\"] || opts.noRestart) return \"skipped-opt-out\";\n\n const isRunning = opts.isRunning ?? defaultIsRunning;\n if (!isRunning()) return \"skipped-not-running\";\n\n const log = opts.log ?? console.log;\n log(`↻ restarting daemon to apply ${opts.reason}…`);\n const runKickstart = opts.runKickstart ?? defaultRunKickstart;\n runKickstart();\n return \"restarted\";\n}\n","// Cross-project dispatch orchestration (#246/#367; hard group-gate relaxed for\n// cross-project ping & dispatch). Pure-ish library function: validation +\n// boot-if-down (same-group only) + record-task.\n// CLI-edge concerns (shelling out to `squadrant launch`) are injected via bootCaptain.\n\nimport { randomUUID } from \"node:crypto\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { loadConfig, resolveHome, type SquadrantConfig } from \"@squadrant/shared\";\nimport { sendRequest } from \"./protocol.js\";\nimport type { TaskRecord, Provider, Mode } from \"@squadrant/shared\";\n\nconst DEFAULT_SOCK_PATH = join(homedir(), \".config\", \"squadrant\", \"squadrant.sock\");\n\n// #288: cold captain boot takes 45-90s; 120s gives the full chain comfortable headroom.\nexport const GROUP_DISPATCH_WARMUP_TIMEOUT_MS = 120_000;\nexport const GROUP_DISPATCH_WARMUP_POLL_MS = 1_000;\n\n/** Resolve the current project name by matching cwd against config paths. */\nexport function resolveCurrentProject(config: SquadrantConfig): string | null {\n const cwd = process.cwd();\n for (const [name, proj] of Object.entries(config.projects)) {\n const resolvedPath = resolveHome(proj.path);\n if (cwd.startsWith(resolvedPath)) return name;\n }\n return null;\n}\n\n/** Check via the daemon health endpoint whether a project's captain is up. */\nexport async function isCaptainAlive(\n project: string,\n sockPath: string = DEFAULT_SOCK_PATH,\n): Promise<boolean> {\n try {\n const health = (await sendRequest(sockPath, { kind: \"health\", project }, 5000)) as Array<{\n kind: string; project: string; state: string;\n }>;\n const captain = health?.find((h) => h.kind === \"captain\" && h.project === project);\n // Captain rows only ever report \"alive\" | \"stopped\" | \"unknown\" (see\n // liveness.ts projectHealth) — \"stopped\" means the workspace was closed\n // (down), so it must NOT count as alive.\n return captain?.state === \"alive\";\n } catch {\n return false;\n }\n}\n\n/** Poll the daemon health endpoint until the target project's captain is up,\n * or the hard timeout expires. Returns true if warmup succeeded. */\nexport async function waitForWarmup(\n project: string,\n sockPath: string = DEFAULT_SOCK_PATH,\n timeoutMs = GROUP_DISPATCH_WARMUP_TIMEOUT_MS,\n pollMs = GROUP_DISPATCH_WARMUP_POLL_MS,\n): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await isCaptainAlive(project, sockPath)) return true;\n await new Promise((r) => setTimeout(r, pollMs));\n }\n return false;\n}\n\nexport interface GroupDispatchOpts {\n fromProject: string;\n toProject: string;\n task: string;\n provider?: Provider;\n mode?: Mode;\n sockPath?: string;\n warmupTimeoutMs?: number;\n warmupPollMs?: number;\n /** CLI-edge: shells out to launch the target captain. Injected by the command handler. */\n bootCaptain?: (project: string) => Promise<void>;\n}\n\n/**\n * Dispatch a task to any registered project. Validates acceptDelegations,\n * then records the task via the daemon. Same-group targets additionally get\n * boot-if-down (via injected bootCaptain); cross-group targets must already\n * be running — see the same-group check inline below.\n * Dispatch-and-yield: returns immediately after recording.\n */\nexport async function dispatchToSibling(opts: GroupDispatchOpts): Promise<TaskRecord> {\n const config = loadConfig();\n const fromCfg = config.projects[opts.fromProject];\n const toCfg = config.projects[opts.toProject];\n\n if (!toCfg) {\n throw new Error(`target project '${opts.toProject}' not found in config`);\n }\n\n // #246/#367: dispatch reaches any registered project. Same group only grants\n // the richer guarantees below (auto-accept default, boot-if-down); it is no\n // longer a hard gate on whether dispatch is allowed at all.\n const sameGroup = !!fromCfg?.group && !!toCfg.group && fromCfg.group === toCfg.group;\n\n // #246: acceptDelegations check (applies regardless of group)\n if (toCfg.acceptDelegations === false) {\n throw new Error(\n `cannot dispatch to '${opts.toProject}': project has acceptDelegations set to false`,\n );\n }\n\n const sockPath = opts.sockPath ?? DEFAULT_SOCK_PATH;\n\n // Ensure target captain is up. Same-group boots via the injected callback;\n // cross-group does not auto-boot — fail fast with a clear next step instead.\n const alive = await isCaptainAlive(opts.toProject, sockPath);\n if (!alive) {\n if (!sameGroup) {\n throw new Error(\n `cannot dispatch to '${opts.toProject}': captain is not running and cross-group ` +\n `dispatch does not auto-boot it. Use 'squadrant ping ${opts.toProject} \"<msg>\"' or ` +\n `start it manually with 'squadrant launch ${opts.toProject}', then retry.`,\n );\n }\n if (opts.bootCaptain) {\n await opts.bootCaptain(opts.toProject);\n }\n const warmed = await waitForWarmup(\n opts.toProject,\n sockPath,\n opts.warmupTimeoutMs,\n opts.warmupPollMs,\n );\n if (!warmed) {\n throw new Error(\n `dispatch to '${opts.toProject}' timed out waiting for captain warmup ` +\n `(>${(opts.warmupTimeoutMs ?? GROUP_DISPATCH_WARMUP_TIMEOUT_MS) / 1000}s)`,\n );\n }\n }\n\n // Record the task via the daemon (dispatch-and-yield)\n const now = Date.now();\n const attemptId = randomUUID();\n const record: TaskRecord = {\n id: randomUUID(),\n project: opts.toProject,\n originProject: opts.fromProject,\n provider: opts.provider ?? \"claude\",\n mode: opts.mode ?? \"headless\",\n state: \"submitted\",\n task: opts.task,\n createdAt: now,\n lastHeartbeat: now,\n lastEvent: \"dispatch\",\n heartbeatBudgetMs: 300000,\n attempts: [{ attemptId, startedAt: now, lastHeartbeatAt: now }],\n };\n\n const result = (await sendRequest(sockPath, { kind: \"dispatch\", record })) as TaskRecord;\n return result;\n}\n","// Side-session orchestration — driver-agnostic algorithm (#367 command-thinning).\n// CLI-edge concerns (concrete driver construction, agent command building,\n// sendFirstTurnWhenReady) are injected as closures; core only imports from\n// @squadrant/shared (and node built-ins).\n\nimport fs from \"node:fs\";\nimport {\n loadConfig,\n type SquadrantConfig,\n type PaneRef,\n type PanePlacement,\n type RuntimeDriver,\n addWorktree,\n removeWorktree,\n worktreePath,\n resolveWorktreeBase,\n} from \"@squadrant/shared\";\nimport { shellQuote } from \"./crew-protocol.js\";\n\n// ─── naming primitives ────────────────────────────────────────────────────────\n// These parallel the crew naming helpers in crew-protocol.ts but use the 🗒\n// prefix. Prefixed with \"side\" to avoid barrel-level name conflicts.\n\nexport function sideTitleFor(project: string, name: string): string {\n return `🗒 ${project}:${name}`;\n}\n\nexport function isSideTitle(project: string, title: string): boolean {\n return title.startsWith(`🗒 ${project}:`);\n}\n\nexport function sideNameFromTitle(project: string, title: string): string {\n return title.slice(`🗒 ${project}:`.length);\n}\n\nexport function sideNextAutoName(existingTitles: string[], project: string): string {\n const used = new Set<number>();\n for (const title of existingTitles) {\n const n = sideNameFromTitle(project, title).match(/^side-(\\d+)$/);\n if (n) used.add(Number(n[1]));\n }\n let i = 1;\n while (used.has(i)) i++;\n return `side-${i}`;\n}\n\n// ─── first-turn builder ───────────────────────────────────────────────────────\n\n/** Builds the first-turn message: topic + injected context the agent needs\n * for handoff (spokeVault, project, role). For debug sessions, scratchWorktree\n * is the isolated worktree path the session is running in. */\nexport function buildSideFirstTurn(\n topic: string,\n project: string,\n role: string,\n spokeVault: string,\n scratchWorktree?: string,\n): string {\n const lines = [\n topic,\n \"\",\n \"---\",\n \"Side-session context (for handoff use):\",\n `Project: ${project}`,\n `Role: ${role}`,\n `Spoke vault: ${spokeVault}`,\n ];\n if (scratchWorktree) {\n lines.push(`Scratch worktree: ${scratchWorktree}`);\n }\n return lines.join(\"\\n\");\n}\n\n// ─── spawn orchestration ──────────────────────────────────────────────────────\n\nconst SIDE_ROLES = [\"research\", \"debug\"] as const;\ntype SideRole = (typeof SIDE_ROLES)[number];\n\nexport interface SideSpawnInput {\n project: string;\n topic: string;\n role: string;\n name?: string;\n direction?: PanePlacement;\n agent?: string; // passed through to CLI — not used by core\n}\n\nexport interface SideSpawnDeps {\n runtime: RuntimeDriver;\n /**\n * CLI-edge factory: called with the resolved spawn CWD (proj.path for research,\n * scratch worktree path for debug) so @squadrant/agents can set workdir correctly.\n */\n agentCmdFactory: (spawnCwd: string) => string;\n /** CLI-edge: deliver the first turn when the agent pane is ready. */\n sendFirstTurn: (pane: PaneRef, firstTurn: string, preLaunchScreen: string) => Promise<{ delivered: boolean }>;\n}\n\nexport async function runSideSpawn(\n input: SideSpawnInput,\n config: SquadrantConfig,\n deps: SideSpawnDeps,\n): Promise<PaneRef> {\n const proj = config.projects[input.project];\n if (!proj) {\n throw new Error(`Project '${input.project}' not found. Run 'squadrant projects list'.`);\n }\n\n if (!SIDE_ROLES.includes(input.role as SideRole)) {\n throw new Error(\n `Unknown side role '${input.role}'. Valid roles: ${SIDE_ROLES.join(\", \")}.`,\n );\n }\n\n const { runtime } = deps;\n\n const captain = await runtime.status(proj.captainName);\n if (!captain) {\n throw new Error(\n `Captain workspace '${proj.captainName}' is not running. Run 'squadrant launch ${input.project}' first.`,\n );\n }\n\n const existing = await runtime.listSurfaces(captain.id);\n const existingTitles = existing\n .filter((s) => s.title && isSideTitle(input.project, s.title))\n .map((s) => s.title!);\n\n if (input.name) {\n const wantTitle = sideTitleFor(input.project, input.name);\n if (existingTitles.includes(wantTitle)) {\n throw new Error(\n `Side session '${input.name}' already exists for ${input.project}.`,\n );\n }\n }\n const name = input.name ?? sideNextAutoName(existingTitles, input.project);\n\n // Debug sessions run in an isolated scratch git worktree so instrumentation\n // edits never touch the captain's checkout. Research sessions share the root\n // checkout. The #279 fix (cd into spawnCwd before launching CLI) applies to both.\n const spawnCwd = input.role === \"debug\"\n ? addWorktree({\n repoRoot: proj.path,\n worktreeDir: config.defaults.worktreeDir ?? \".worktrees\",\n project: input.project,\n name,\n base: resolveWorktreeBase(proj.path),\n })\n : proj.path;\n\n const agentCmd = deps.agentCmdFactory(spawnCwd);\n\n const direction: PanePlacement = input.direction ?? \"tab\";\n const title = sideTitleFor(input.project, name);\n const pane = await runtime.newPane({ workspaceId: captain.id, direction, title });\n\n await runtime.sendToPane(pane, `cd ${shellQuote(spawnCwd)} && ${agentCmd}`);\n const preLaunchScreen = (await runtime.readPaneScreen(pane)) ?? \"\";\n\n const firstTurn = buildSideFirstTurn(\n input.topic,\n input.project,\n input.role,\n proj.spokeVault ?? \"\",\n input.role === \"debug\" ? spawnCwd : undefined,\n );\n await deps.sendFirstTurn(pane, firstTurn, preLaunchScreen);\n\n return { ...pane, title };\n}\n\n// ─── send / list / close ─────────────────────────────────────────────────────\n\nexport async function runSideSend(\n runtime: RuntimeDriver,\n workspaceId: string,\n project: string,\n name: string,\n message: string,\n): Promise<void> {\n const want = sideTitleFor(project, name);\n const surfaces = await runtime.listSurfaces(workspaceId);\n const pane = surfaces.find((s) => s.title === want) ?? null;\n if (!pane) {\n throw new Error(\n `Side session '${name}' not found for ${project}. Run 'squadrant side list ${project}'.`,\n );\n }\n await runtime.sendToPane(pane, message);\n}\n\nexport async function runSideList(\n runtime: RuntimeDriver,\n workspaceId: string,\n project: string,\n): Promise<Array<{ name: string; surfaceId: string }>> {\n const surfaces = await runtime.listSurfaces(workspaceId);\n return surfaces\n .filter((s) => s.title && isSideTitle(project, s.title))\n .map((s) => ({\n name: sideNameFromTitle(project, s.title!),\n surfaceId: s.surfaceId,\n }));\n}\n\nexport async function runSideClose(\n runtime: RuntimeDriver,\n workspaceId: string,\n project: string,\n name: string,\n projPath: string | undefined,\n worktreeDir: string,\n): Promise<void> {\n const want = sideTitleFor(project, name);\n const surfaces = await runtime.listSurfaces(workspaceId);\n const pane = surfaces.find((s) => s.title === want) ?? null;\n if (!pane) {\n throw new Error(\n `Side session '${name}' not found for ${project}. Run 'squadrant side list ${project}'.`,\n );\n }\n await runtime.closePane(pane);\n // Prune the scratch worktree if this was a debug session. Detection is\n // filesystem-based: debug spawns create a worktree at the deterministic path;\n // research spawns do not. If the path exists, remove it (best-effort).\n if (projPath) {\n const wtPath = worktreePath(projPath, worktreeDir, project, name);\n if (fs.existsSync(wtPath)) {\n try {\n removeWorktree(projPath, wtPath);\n } catch (e) {\n process.stderr.write(`(worktree remove failed: ${(e as Error).message})\\n`);\n }\n }\n }\n}\n","// Crew spawn and session orchestration — driver-agnostic algorithm (#367 command-thinning).\n// CLI-edge concerns (concrete driver construction, daemon calls, settings writers,\n// agent commands) are injected as closures; core only imports from @squadrant/shared\n// and core-internal modules. The algorithm is IDENTICAL to the prior crew.ts\n// implementation — zero behavior change.\n\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport {\n type SquadrantConfig,\n loadConfig,\n type TaskRecord,\n type Provider,\n type PaneRef,\n type PanePlacement,\n type RuntimeDriver,\n type ControlEvent,\n addWorktree,\n resolveWorktreeBase,\n removeWorktree,\n TERMINAL_STATES,\n} from \"@squadrant/shared\";\nimport { resolveCrewRoute, type CrewRouteResult } from \"./crew-routing.js\";\nimport {\n buildCompletionProtocol,\n shellQuote,\n niceCrewCommand,\n titleFor,\n isCrewTitle,\n nameFromTitle,\n nextAutoName,\n type TurnAcceptanceConfig,\n} from \"./crew-protocol.js\";\nimport { reapCrewChildren } from \"./crew-lifecycle.js\";\n\nconst TEMPLATES_DIR = path.join(os.homedir(), \".config\", \"squadrant\", \"templates\");\nconst STATE_ROOT = path.join(os.homedir(), \".config\", \"squadrant\", \"state\");\n\n// ─── ResolvedAgent ────────────────────────────────────────────────────────────\n\n/** Minimal agent shape needed by spawn orchestration. CLI constructs from AgentDriver.\n *\n * Note on `buildCommand` typing: AgentDriver (from @squadrant/agents) declares\n * role as Role (a union); this interface uses `string` to avoid importing from\n * agents in core. The only value ever passed at the call sites is \"crew\", which\n * satisfies Role at runtime. CLI callers use `as unknown as ResolvedAgent` to\n * bridge the type gap safely. */\nexport interface ResolvedAgent {\n name: string;\n templateSuffix: string;\n buildCommand(opts: {\n prompt: string;\n workdir: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n role: any;\n promptFile: string;\n interactive: boolean;\n permissionMode?: string;\n model?: string;\n port?: number;\n }): string;\n}\n\n// ─── CrewSpawnInput ───────────────────────────────────────────────────────────\n\nexport interface CrewSpawnInput {\n project: string;\n task: string;\n name?: string;\n direction?: PanePlacement;\n agent?: string;\n approvalPolicy?: string;\n /** Opt-out (#296): run this crew in the root checkout instead of an isolated\n * worktree. Pass true for small/one-off tasks that don't need branch isolation.\n * Default (undefined/false) = isolated worktree — parallel-safe. */\n shared?: boolean;\n /** CP3 opt-in: gate risky tools (bash) so the captain approves them.\n * codex maps this to approvalPolicy='untrusted'; opencode maps it to a\n * bash:\"ask\" per-crew config. Default (false) = fully autonomous. */\n approval?: boolean;\n /** Per-spawn model override — takes precedence over defaults.roles.crew.model. */\n model?: string;\n /** True when --agent was explicitly passed by the caller; suppresses crew routing. */\n agentExplicit?: boolean;\n /** Path to the task file when --task-file was used (not '-' for stdin). Set by\n * the CLI so runCrewSpawn can copy the file into the isolated worktree root,\n * enabling the crew to `Read ./<basename>` without hunting the main checkout (#458).\n * Ignored for --shared spawns and when absent. */\n taskFile?: string;\n}\n\n// ─── CrewSpawnDeps ───────────────────────────────────────────────────────────\n\nexport interface CrewSpawnDeps {\n runtime: RuntimeDriver;\n /**\n * CLI-edge: look up a resolved agent by name. Returns null if unknown.\n * Wraps CapabilityRegistry.get() from @squadrant/agents.\n */\n resolveAgent(name: string): ResolvedAgent | null;\n /**\n * CLI-edge: dispatch a crew task via the daemon.\n * Wraps buildDispatchRequest + squadrantdCall from crew-control.ts.\n */\n dispatchCrew(opts: {\n provider: Provider;\n mode: \"interactive\";\n project: string;\n cwd: string;\n task: string;\n name: string;\n budgetMs?: number;\n serverPort?: number;\n approvalPolicy?: string;\n roleInstructions?: string;\n }): Promise<TaskRecord>;\n /** CLI-edge: write squadrant hooks to <cwd>/.claude/settings.local.json (#134). */\n writeSettingsLocal(projectCwd: string): void;\n /** CLI-edge: write opencode permission config for an interactive crew. */\n writeOpencodeConfig(opts: { stateRoot: string; project: string; taskId: string; gateBash?: boolean }): string;\n /** CLI-edge: deliver the first turn once the agent pane is ready. Returns\n * { delivered: true } when positively confirmed, { delivered: false } when\n * all retry paths exhausted without confirmation (#466). */\n sendFirstTurn(pane: PaneRef, firstTurn: string, preLaunchScreen: string, opts?: TurnAcceptanceConfig): Promise<{ delivered: boolean }>;\n /** CLI-edge: reserve an ephemeral TCP port for opencode's embedded HTTP server. */\n getFreePort(): Promise<number>;\n /** CLI-edge: deliver the task to a freshly-dispatched codex thread. */\n sendCodexFirstTurn(taskId: string, task: string): Promise<void>;\n /** Optional: called after routing to log the selected route (e.g. chalk.dim(...)). */\n onRouted?(route: CrewRouteResult): void;\n /** #466: optional — when provided, called with task.first-turn.confirmed after\n * positively confirmed delivery so the daemon can stamp firstTurnConfirmedAt. */\n emitEvent?(project: string, event: ControlEvent): Promise<void>;\n}\n\n// ─── Private helpers ──────────────────────────────────────────────────────────\n\nasync function listCrewPanes(runtime: RuntimeDriver, workspaceId: string, project: string): Promise<PaneRef[]> {\n const surfaces = await runtime.listSurfaces(workspaceId);\n return surfaces.filter((s) => s.title && isCrewTitle(project, s.title));\n}\n\nasync function findCrewPane(\n runtime: RuntimeDriver,\n workspaceId: string,\n project: string,\n name: string,\n): Promise<PaneRef | null> {\n const want = titleFor(project, name);\n const surfaces = await runtime.listSurfaces(workspaceId);\n return surfaces.find((s) => s.title === want) ?? null;\n}\n\n// ─── Codex interactive spawn (private) ───────────────────────────────────────\n\nasync function runCodexInteractiveSpawn(o: {\n project: string;\n task: string;\n /** Override for first-turn delivery to the model. When set (e.g. \"Read ./file.md\n * to get your task brief\"), used instead of `task` for sendCodexFirstTurn so large\n * file contents aren't sent verbatim. The daemon dispatch always uses `task`. */\n firstTurn?: string;\n cwd: string;\n runtime: RuntimeDriver;\n workspaceId: string;\n name: string;\n direction: PanePlacement;\n approvalPolicy?: string;\n roleInstructions?: string;\n dispatchCrew: CrewSpawnDeps[\"dispatchCrew\"];\n sendCodexFirstTurn: CrewSpawnDeps[\"sendCodexFirstTurn\"];\n}): Promise<PaneRef> {\n const rec = await o.dispatchCrew({\n provider: \"codex\",\n mode: \"interactive\",\n project: o.project,\n cwd: o.cwd,\n task: o.task,\n name: o.name,\n ...(o.approvalPolicy ? { approvalPolicy: o.approvalPolicy } : {}),\n ...(o.roleInstructions ? { roleInstructions: o.roleInstructions } : {}),\n });\n const title = titleFor(o.project, o.name);\n const pane = await o.runtime.newPane({\n workspaceId: o.workspaceId,\n direction: o.direction,\n title,\n });\n await o.runtime.sendToPane(pane, `squadrant crew attach ${rec.id}`);\n // Match the claude UX where the task arg becomes the first turn. The codex\n // dispatch only opens the thread; the task text never reaches the model\n // unless we send it. Fire-and-forget: the renderer in the tab picks up\n // streamed deltas once it attaches.\n const firstTurnText = o.firstTurn ?? o.task;\n if (firstTurnText && firstTurnText !== \"(interactive)\") {\n void o.sendCodexFirstTurn(rec.id, firstTurnText).catch((e: unknown) => {\n process.stderr.write(`(first-turn delivery failed: ${(e as Error).message})\\n`);\n });\n }\n return { ...pane, title };\n}\n\n// ─── runCrewSpawn ─────────────────────────────────────────────────────────────\n\nexport async function runCrewSpawn(\n input: CrewSpawnInput,\n config: SquadrantConfig,\n deps: CrewSpawnDeps,\n): Promise<PaneRef> {\n const proj = config.projects[input.project];\n if (!proj) {\n throw new Error(`Project '${input.project}' not found. Run 'squadrant projects list'.`);\n }\n\n const captain = await deps.runtime.status(proj.captainName);\n if (!captain) {\n throw new Error(\n `Captain workspace '${proj.captainName}' is not running. Run 'squadrant launch ${input.project}' first.`,\n );\n }\n\n const existing = await listCrewPanes(deps.runtime, captain.id, input.project);\n const existingTitles = existing.map((s) => s.title!);\n if (input.name) {\n const wantTitle = titleFor(input.project, input.name);\n if (existingTitles.includes(wantTitle)) {\n throw new Error(\n `Crew '${input.name}' already exists for ${input.project}. Use 'squadrant crew send ${input.project} ${input.name}' to send a follow-up, or pick a different --name.`,\n );\n }\n }\n const name = input.name ?? nextAutoName(existingTitles, input.project);\n\n // Crews run in an isolated worktree+branch by default so multiple parallel\n // crews never collide on a shared HEAD (#296). Pass shared:true (CLI: --shared)\n // for small/one-off tasks that should run on the root checkout.\n const spawnCwd = !input.shared\n ? addWorktree({\n repoRoot: proj.path,\n worktreeDir: config.defaults.worktreeDir ?? \".worktrees\",\n project: input.project,\n name,\n base: resolveWorktreeBase(proj.path),\n })\n : proj.path;\n\n // #458: For isolated-worktree spawns with a task file, copy the file into the\n // worktree root so the crew can find it via `Read ./<basename>` without having\n // to discover the main checkout path. Use a short first-turn message referencing\n // the local path to avoid large-paste issues on big task files.\n // Guards: skip for --shared (file is in the main checkout, already reachable),\n // skip for stdin ('-') since there is no file to copy.\n let firstTurnTask = input.task;\n if (input.taskFile && input.taskFile !== \"-\" && !input.shared) {\n const absTaskFile = path.resolve(input.taskFile);\n const basename = path.basename(absTaskFile);\n fs.copyFileSync(absTaskFile, path.join(spawnCwd, basename));\n firstTurnTask = `Read ./${basename} to get your task brief, then execute it.`;\n }\n\n // #275 leveled crew routing: consult routing rules when agent/model were not\n // explicitly provided by the caller. Explicit --agent or --model always win.\n const route = !input.agentExplicit && !input.model\n ? resolveCrewRoute(input.task, config)\n : null;\n if (route) {\n deps.onRouted?.(route);\n }\n\n const agentName = route?.agent ?? input.agent ?? \"claude\";\n const agent = deps.resolveAgent(agentName);\n if (!agent) {\n throw new Error(`Unknown agent '${agentName}'. Known: claude, codex, gemini, opencode.`);\n }\n\n // Codex: route through the interactive control-plane daemon (PR #98) instead\n // of the print-mode CLI path. The dispatched task is driven via the\n // crew-attach renderer running in the captain tab, so 'crew send' / 'crew\n // read' / 'crew close' work identically to the Claude crew UX.\n if (agentName === \"codex\") {\n const codexRoleFile = path.join(TEMPLATES_DIR, `crew.${agent.templateSuffix}.md`);\n const roleInstructions = fs.existsSync(codexRoleFile)\n ? fs.readFileSync(codexRoleFile, \"utf8\")\n : undefined;\n return runCodexInteractiveSpawn({\n project: input.project,\n task: input.task,\n firstTurn: firstTurnTask !== input.task ? firstTurnTask : undefined,\n cwd: spawnCwd,\n runtime: deps.runtime,\n workspaceId: captain.id,\n name,\n direction: input.direction ?? \"tab\",\n approvalPolicy: input.approvalPolicy,\n roleInstructions,\n dispatchCrew: deps.dispatchCrew,\n sendCodexFirstTurn: deps.sendCodexFirstTurn,\n });\n }\n\n const promptFile = path.join(TEMPLATES_DIR, `crew.${agent.templateSuffix}.md`);\n // Claude crews run interactively (no -p) so the session stays alive between\n // turns; the task is sent via cmux after the CLI boots. Other agents that\n // don't yet honor `interactive` will keep their existing print-mode shape.\n const interactive = agent.name === \"claude\" || agent.name === \"opencode\";\n // Honor configured model routing only when the spawn agent matches the\n // configured role agent — model names are agent-specific. Cross-agent crews\n // fall back to the agent's own default to avoid passing an invalid model arg.\n const crewRole = config.defaults.roles?.crew;\n const configModel = crewRole && crewRole.agent === agent.name ? crewRole.model : undefined;\n const crewModel = input.model ?? route?.model ?? configModel;\n\n // Claude crews route through the control-plane daemon (PR #85) so the captain\n // learns terminal state via `squadrant crew status`. The cmux tab still does\n // the actual CLI launch — the daemon doesn't own Claude's PID. Hook bridge\n // (per-crew settings.json → Stop/SubagentStop/SessionEnd → squadrant crew _hook)\n // keeps the daemon's heartbeat fresh; `squadrant crew signal done` emits\n // terminal state.\n if (agentName === \"claude\") {\n const rec = await deps.dispatchCrew({\n provider: \"claude\",\n mode: \"interactive\",\n project: input.project,\n cwd: spawnCwd,\n task: input.task,\n name,\n });\n // Write squadrant hooks to <cwd>/.claude/settings.local.json so they are\n // auto-loaded as a project-local settings source. Merges with any existing\n // hooks — does not clobber the user's own personal hooks (#134).\n // #472: capture whether hook installation succeeded — when it does, the\n // UserPromptSubmit hook is the SOLE first-turn confirmation source for\n // claude crews. If the write fails (rare OS error), fall back to scrape.\n let hooksInstalled = false;\n try {\n deps.writeSettingsLocal(spawnCwd);\n hooksInstalled = true;\n } catch {\n // Hook file write failed — scrape confirmation remains as fallback.\n }\n const cliCommand = agent.buildCommand({\n prompt: input.task,\n workdir: spawnCwd,\n role: \"crew\",\n promptFile,\n interactive: true,\n // Permission mode is config-driven so squadrant can default crews to 'auto'\n // or keep the semi-automatic 'acceptEdits' gate. Falls back to 'acceptEdits'.\n permissionMode: config.defaults.permissions?.crew ?? \"acceptEdits\",\n ...(crewModel ? { model: crewModel } : {}),\n });\n const direction: PanePlacement = input.direction ?? \"tab\";\n const title = titleFor(input.project, name);\n const pane = await deps.runtime.newPane({ workspaceId: captain.id, direction, title });\n // Prefix the CLI command with env so the hook bridge + signal verb running\n // inside the crew's cmux tab can identify their task.\n const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}`;\n await deps.runtime.sendToPane(pane, `cd ${shellQuote(spawnCwd)} && ${envPrefix} ${niceCrewCommand(cliCommand)}`);\n const preLaunchScreen = (await deps.runtime.readPaneScreen(pane)) ?? \"\";\n const claudeResult = await deps.sendFirstTurn(pane, `${firstTurnTask}\\n\\n${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen);\n // #466: surface non-delivery explicitly instead of silently returning success.\n if (!claudeResult.delivered) {\n process.stderr.write(`⚠️ First turn not delivered for crew '${name}' — use 'squadrant crew send ${input.project} ${name}' to re-send the task.\\n`);\n } else if (!hooksInstalled) {\n // #472: hooks unavailable — scrape is the only confirmation source for this crew.\n await deps.emitEvent?.(input.project, { type: \"task.first-turn.confirmed\", id: rec.id });\n }\n // When hooksInstalled=true: UserPromptSubmit hook stamps firstTurnConfirmedAt.\n return { ...pane, title };\n }\n\n // Opencode crews route through the control-plane daemon so the captain learns\n // terminal state via `squadrant crew status`. No hook bridge (opencode has no\n // hooks); the crew template instructs explicit `squadrant crew signal done|blocked|failed`.\n if (agentName === \"opencode\") {\n // Bind the crew's embedded opencode HTTP server on a known port so the\n // daemon's SSE bridge can subscribe to /event for turn-end detection.\n const serverPort = await deps.getFreePort();\n const rec = await deps.dispatchCrew({\n provider: \"opencode\",\n mode: \"interactive\",\n project: input.project,\n cwd: spawnCwd,\n task: input.task,\n name,\n // opencode has no heartbeat hook, so a normal budget would false-stall\n // every crew after 5min; use a 24h budget to effectively disable stall\n // detection. The SSE bridge (serverPort) provides turn-end liveness.\n budgetMs: 86400000,\n serverPort,\n });\n const opencodeConfigPath = deps.writeOpencodeConfig({\n stateRoot: STATE_ROOT,\n project: input.project,\n taskId: rec.id,\n // CP3 opt-in: --approval gates bash so the captain approves shell commands.\n ...(input.approval ? { gateBash: true } : {}),\n });\n const cliCommand = agent.buildCommand({\n prompt: input.task,\n workdir: spawnCwd,\n role: \"crew\",\n promptFile,\n interactive: true,\n model: crewModel,\n port: serverPort,\n });\n const direction: PanePlacement = input.direction ?? \"tab\";\n const title = titleFor(input.project, name);\n const pane = await deps.runtime.newPane({ workspaceId: captain.id, direction, title });\n const envPrefix = `SQUADRANT_CREW_TASK_ID=${rec.id} SQUADRANT_CREW_PROJECT=${input.project}`;\n await deps.runtime.sendToPane(pane, `cd ${shellQuote(spawnCwd)} && ${envPrefix} OPENCODE_CONFIG=${opencodeConfigPath} ${niceCrewCommand(cliCommand)}`);\n const preLaunchScreen = (await deps.runtime.readPaneScreen(pane)) ?? \"\";\n const opencodeResult = await deps.sendFirstTurn(pane, `${firstTurnTask}\\n\\n${buildCompletionProtocol(rec.id, input.project)}`, preLaunchScreen, {\n // #235: confirm-on-delivery — sendFirstTurnWhenReady polls until the idle\n // splash leaves the screen, re-sending every ~3s to cover slow boots\n // without duplicating the task. See crew-pane.ts SPLASH_MAX_CHECKS/EVERY_N.\n // #499: match a stable substring (\"ask anything\", case/whitespace/ellipsis\n // -insensitive via screenHasSplashMarker) rather than the exact wording —\n // opencode's real placeholder rotates through example prompts and uses\n // three ASCII dots (\"Ask anything...\") or a longer command hint (\"Ask\n // anything, / for commands, @ for context...\"), never the literal\n // \"Ask anything…\" (U+2026) this used to hardcode, which never matched.\n splashMarker: \"Ask anything\",\n } satisfies TurnAcceptanceConfig);\n // #466: surface non-delivery; emit confirmed event on success.\n if (!opencodeResult.delivered) {\n process.stderr.write(`⚠️ First turn not delivered for crew '${name}' — use 'squadrant crew send ${input.project} ${name}' to re-send the task.\\n`);\n } else {\n await deps.emitEvent?.(input.project, { type: \"task.first-turn.confirmed\", id: rec.id });\n }\n return { ...pane, title };\n }\n\n // Generic / fallback branch — agents that don't yet have a first-class branch.\n const cliCommand = agent.buildCommand({\n prompt: input.task,\n workdir: spawnCwd,\n role: \"crew\",\n promptFile,\n interactive,\n model: crewModel,\n });\n const direction: PanePlacement = input.direction ?? \"tab\";\n const title = titleFor(input.project, name);\n const pane = await deps.runtime.newPane({ workspaceId: captain.id, direction, title });\n await deps.runtime.sendToPane(pane, niceCrewCommand(cliCommand));\n if (interactive) {\n const preLaunchScreen = (await deps.runtime.readPaneScreen(pane)) ?? \"\";\n const genericResult = await deps.sendFirstTurn(pane, firstTurnTask, preLaunchScreen);\n // Generic branch has no daemon task record — only warn on non-delivery.\n if (!genericResult.delivered) {\n process.stderr.write(`⚠️ First turn not delivered for crew '${name}' — use 'squadrant crew send ${input.project} ${name}' to re-send the task.\\n`);\n }\n }\n return { ...pane, title };\n}\n\n// ─── crew session operations ──────────────────────────────────────────────────\n\n// #574: the single record-selection rule for \"which task record is THE record\n// for this crew name\" when duplicates exist (e.g. an orphaned record left by a\n// close/respawn race, #513). Every call site that resolves a crew name to a\n// task record MUST go through this helper — runCrewSend and runCrewClose used\n// to each inline their own pick (first-match vs most-recent), and disagreed on\n// live vs. stale duplicates, causing the two sides of a crew's lifecycle to\n// silently track different ids.\nfunction pickMostRecentTask(tasks: TaskRecord[]): TaskRecord {\n return tasks.reduce((a, b) => ((b.createdAt ?? 0) > (a.createdAt ?? 0) ? b : a));\n}\n\nexport async function runCrewSend(\n project: string,\n name: string,\n message: string,\n runtime: RuntimeDriver,\n workspaceId: string,\n deps: {\n listTasks(project: string): Promise<TaskRecord[]>;\n emitEvent(project: string, event: ControlEvent): Promise<void>;\n // Optional confirmed-submit override (#448). When provided, used instead of\n // runtime.sendToPane so the caller can inject paste-settle-Enter hardening.\n // Falls back to runtime.sendToPane when absent (preserves existing behaviour\n // for callers that don't inject it, e.g. unit tests).\n sendToPane?: (pane: PaneRef, message: string) => Promise<{ delivered: boolean; blockedByModal?: boolean }>;\n // #516: optional side-effect-free precheck for an open AskUserQuestion/\n // permission modal. Checked BEFORE the daemon-state emit block below so a\n // modal-blocked send is a true no-op on daemon state, not just on the pane.\n // Deliberately separate from sendToPane: that closure only reports\n // blockedByModal AFTER attempting delivery, which is too late here — the\n // emit block must never run for a message that never reached the crew.\n isBlockedByModal?: (pane: PaneRef) => Promise<boolean>;\n },\n): Promise<void> {\n const crew = await findCrewPane(runtime, workspaceId, project, name);\n if (!crew) {\n throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);\n }\n const blockedByModalMessage = () =>\n `Crew '${name}' has an interactive prompt open (AskUserQuestion/permission) — message NOT delivered, to avoid confirming its default option. Wait for the prompt to close, then re-send with 'squadrant crew send ${project} ${name}'.`;\n if (deps.isBlockedByModal && (await deps.isBlockedByModal(crew))) {\n throw new Error(blockedByModalMessage());\n }\n // Best-effort attention-state handling before delivering the captain's answer.\n // Terminal task (done/failed): reopen so the next signal done fires CREW DONE (#148).\n // Blocked task: emit task.started to clear blocked→working so a subsequent real\n // permission prompt re-fires CREW BLOCKED (#182).\n try {\n const matches = (await deps.listTasks(project)).filter((t) => t.name === name);\n const task = matches.length > 0 ? pickMostRecentTask(matches) : undefined;\n if (task) {\n if (TERMINAL_STATES.has(task.state)) {\n await deps.emitEvent(project, { type: \"task.reopened\", id: task.id });\n } else if (task.state === \"blocked\" || task.state === \"awaiting-input\" || task.state === \"review\") {\n // #599: feedback on a 'review' task is the reject path — clear it back\n // to working the same way an answer clears 'blocked'.\n await deps.emitEvent(project, { type: \"task.started\", id: task.id });\n }\n }\n } catch {\n // Swallow daemon errors so crews without a daemon or offline daemon\n // still receive the sent message.\n }\n const deliver: (pane: PaneRef, msg: string) => Promise<{ delivered: boolean; blockedByModal?: boolean }> =\n deps.sendToPane ?? ((pane, msg) => runtime.sendToPane(pane, msg).then(() => ({ delivered: true })));\n const { delivered, blockedByModal } = await deliver(crew, message);\n // #516 backstop: covers the TOCTOU window between the precheck above and this\n // delivery attempt, and callers that don't inject isBlockedByModal at all. By\n // this point the emit block (if any) has already run — unavoidable without the\n // precheck — but the send still fails loudly instead of reporting success.\n if (blockedByModal) {\n throw new Error(blockedByModalMessage());\n }\n if (!delivered) {\n // #566: a follow-up send has no self-heal sweep behind it (unlike first-turn\n // delivery, which the daemon retries via resendCrewFirstTurn) — a stderr-only\n // warning here let the CLI's own catch block never fire, so it printed \"✔ Sent\"\n // and exited 0 for a message that was never submitted. Throw so the caller\n // fails loudly instead.\n throw new Error(`Message not delivered to crew '${name}' — the paste/submit could not be confirmed. Re-send with 'squadrant crew send ${project} ${name}'.`);\n }\n}\n\nexport async function runCrewRead(\n project: string,\n name: string,\n runtime: RuntimeDriver,\n workspaceId: string,\n): Promise<string> {\n const crew = await findCrewPane(runtime, workspaceId, project, name);\n if (!crew) {\n throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);\n }\n return runtime.readPaneScreen(crew);\n}\n\n// #513: close's listTasks() lookup can race a same-name crew's own dispatch —\n// closing immediately after spawn may snapshot the daemon before the task\n// record is registered. A few short retries close that window without adding\n// meaningful latency to the common (already-registered) case.\nconst CLOSE_LOOKUP_RETRIES = 3;\nconst CLOSE_LOOKUP_RETRY_DELAY_MS = 150;\n\nexport async function runCrewClose(\n project: string,\n name: string,\n runtime: RuntimeDriver,\n workspaceId: string,\n deps: {\n listTasks(project: string): Promise<TaskRecord[]>;\n emitEvent(project: string, event: ControlEvent): Promise<void>;\n closeCodexThread(taskId: string): Promise<void>;\n /** Injectable for tests; defaults to a real delay. */\n sleep?: (ms: number) => Promise<void>;\n },\n): Promise<void> {\n const sleep = deps.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)));\n // resolveCaptainWorkspace already validated the project exists; reload for its\n // root path so we can tell a worktree crew (cwd != root) from a root crew.\n const projRoot = loadConfig().projects[project]?.path;\n // Terminalize the daemon task FIRST — before (and independent of) finding the\n // cmux pane (#184, hardened for #139). Without this, non-terminal tasks\n // (blocked/working/awaiting-input) linger in the daemon ledger and keep firing\n // phantom CREW BLOCKED/IDLE/STALLED pushes. A DEAD crew's pane is already gone,\n // so gating terminalization on findCrew (the old order) left zombie records\n // dangling forever. 'cancelled' is terminal but NOT in ATTENTION_STATES, so\n // firePush stays silent — captain initiated the close.\n let taskId: string | undefined;\n // Worktree to clean up after the pane closes — set only when this crew ran in\n // its own worktree (cwd recorded by the daemon differs from the root checkout).\n let worktreeCwd: string | undefined;\n try {\n let matches = (await deps.listTasks(project)).filter((t) => t.name === name);\n // #513: the record may not be registered yet (close raced spawn's own\n // dispatch). Retry briefly before concluding this crew has no daemon task.\n for (let attempt = 0; attempt < CLOSE_LOOKUP_RETRIES && matches.length === 0; attempt++) {\n await sleep(CLOSE_LOOKUP_RETRY_DELAY_MS);\n matches = (await deps.listTasks(project)).filter((t) => t.name === name);\n }\n if (matches.length > 0) {\n // #513: a name can match more than one record (e.g. an orphaned record\n // left by a prior close that raced dispatch, followed by a same-name\n // respawn). Terminalize every non-terminal match so none linger to fire\n // a phantom CREW STALLED/IDLE later. Reap/worktree cleanup below anchors\n // on the most-recently-dispatched match — the one the live pane belongs to.\n const primary = pickMostRecentTask(matches);\n taskId = primary.id;\n if (primary.cwd && projRoot && primary.cwd !== projRoot) {\n worktreeCwd = primary.cwd;\n }\n for (const task of matches) {\n if (!TERMINAL_STATES.has(task.state)) {\n await deps.emitEvent(project, { type: \"task.cancelled\", id: task.id, reason: \"closed by captain\" });\n }\n // Codex teardown: the pane only hosts the `crew attach` renderer; the thread\n // (and its per-thread MCP servers) live on the shared app-server, so closing\n // the pane alone leaks them. Tell the daemon to archive the thread.\n if (task.provider === \"codex\") {\n await deps.closeCodexThread(task.id);\n }\n }\n }\n } catch {\n // Swallow daemon errors — a crew without a daemon must still close.\n }\n // Close the cmux pane if it still exists. A dead crew's pane is already gone —\n // that is not an error (the record is terminalized above); proceed to reap\n // children / clean the worktree. Only a genuine miss (no pane AND no daemon\n // task) is a typo → surface the not-found error.\n const crew = await findCrewPane(runtime, workspaceId, project, name);\n if (crew) {\n await runtime.closePane(crew);\n } else if (taskId === undefined) {\n throw new Error(`Crew '${name}' not found for ${project}. Run 'squadrant crew list ${project}'.`);\n }\n // Reap any surviving child processes (vitest workers, node subprocs, etc.)\n // that the cmux pane-close cascade may have missed.\n if (taskId !== undefined) {\n await reapCrewChildren(taskId);\n }\n // Auto-clean the crew's worktree AFTER its processes are gone, so we don't\n // yank a dir out from under a live shell. Best-effort: a failed removal must\n // not break close (the branch is preserved regardless).\n if (worktreeCwd && projRoot) {\n try {\n removeWorktree(projRoot, worktreeCwd);\n } catch (e) {\n process.stderr.write(`(worktree remove failed: ${(e as Error).message})\\n`);\n }\n }\n}\n\nexport async function runCrewList(\n project: string,\n runtime: RuntimeDriver,\n workspaceId: string,\n): Promise<Array<{ name: string; surfaceId: string }>> {\n const crews = await listCrewPanes(runtime, workspaceId, project);\n return crews.map((c) => ({\n name: nameFromTitle(project, c.title!),\n surfaceId: c.surfaceId,\n }));\n}\n","// packages/core/src/lifecycle-source.ts\n//\n// LifecycleSource port — phase 0 scaffold (issue #333).\n//\n// Defines the abstraction for normalizing agent lifecycle events from\n// heterogeneous sources (cmux store file, native hooks, SSE, app-server)\n// into a single 4-state model. NO concrete implementation lives here; this is\n// the interface + types + pure reducer only.\n//\n// WIRING CONSTRAINT: nothing in this file is imported by the live daemon or\n// delivery path. It compiles and tests but remains unwired until Phase 1.\n\n// ── normalized lifecycle vocabulary ─────────────────────────────────────────\n\n/** The four canonical crew lifecycle states (mirrors cmux AgentHibernationLifecycleState). */\nexport type LifecycleState = \"running\" | \"idle\" | \"needsInput\" | \"unknown\";\n\n/** One observation about one crew, from one source. */\nexport interface LifecycleSnapshot {\n taskId: string;\n state: LifecycleState;\n /** Is the OS process actually alive (pid-verified)? */\n alive: boolean;\n /**\n * Provenance — the reconciler's tie-breaker.\n * \"agent\" = explicit hook / SSE / app-server transition (authoritative).\n * \"scan\" = inferred from a process/file sweep (liveness only; may NOT assert needsInput).\n */\n origin: \"agent\" | \"scan\";\n /** Monotonic stamp (epoch ms) for last-writer reconciliation across sources. */\n at: number;\n pid?: number;\n /** Optional human detail for surfacing CREW BLOCKED / CREW WORKING context. */\n detail?: { note?: string; tool?: string; reason?: string };\n}\n\n/**\n * Correlation hints a source passes when resolving a raw signal back to a crew.\n * The daemon tries them in priority order: taskId > pid > cwd > sessionId.\n */\nexport interface CorrelationHint {\n /** Strongest — SQUADRANT_CREW_TASK_ID injected into every crew's env at spawn. */\n taskId?: string;\n /** From the cmux store or process scan. */\n pid?: number;\n /** Weakest — collision-prone when a worktree is shared. */\n cwd?: string;\n /** Source-internal (cmux sessionId, codex threadId). */\n sessionId?: string;\n}\n\n/** What the daemon hands every source: how to correlate + where to report. */\nexport interface LifecycleSourceDeps {\n /**\n * Map a raw signal back to its owning crew TaskRecord, or undefined.\n * Keeping it injected makes each source independently testable.\n */\n resolve(hint: CorrelationHint): { id: string } | undefined;\n /** Normalized observation → reducer → ControlEvent pipeline. */\n report(snap: LifecycleSnapshot): void;\n log?(msg: string): void;\n}\n\n/**\n * The port. Each adapter implements start/stop.\n * Push sources call deps.report() on transition.\n * Poll sources additionally expose snapshot() for the liveness floor sweep.\n */\nexport interface LifecycleSource {\n /** Identifies the source in logs and the reconciler (\"cmux-store\" | \"native-hook\" | …). */\n readonly name: string;\n start(deps: LifecycleSourceDeps): void;\n stop(): void;\n /**\n * Poll hook — optional.\n * Returns the current liveness snapshot for a known crew, or undefined if\n * this source has no view of it. Drives the liveness floor sweep.\n * A poll result MUST set origin:\"scan\" and MUST NOT assert state:\"needsInput\".\n */\n snapshot?(taskId: string): LifecycleSnapshot | undefined;\n /**\n * Read-only source-level health (B4 — dashboard visibility into which sources\n * are up). Optional: a source with no fallible startup can omit it and the\n * daemon assumes {active: true, error: null} once registered.\n */\n health?(): { active: boolean; error: string | null };\n}\n\n// ── the one reducer all sources feed ────────────────────────────────────────\n\n/**\n * Pure. Reconcile a new snapshot against the crew's last known state.\n *\n * Rules (from cmux FeedCoordinator.swift):\n * 1. Agent-originated signals are authoritative — always trusted.\n * 2. Scan signals can never assert needsInput (hook-only signal).\n * 3. Agent-set needsInput is sticky — only an agent-originated running relaxes it.\n * 4. A stale scan (at <= prev.at when prev is agent-set) does not regress state.\n */\nexport function reduceLifecycle(\n prev: LifecycleSnapshot | undefined,\n next: LifecycleSnapshot,\n): LifecycleState {\n // Rule 1: agent-originated signals are authoritative.\n if (next.origin === \"agent\") {\n return next.state;\n }\n\n // Rule 2: scan signals can never assert needsInput.\n if (next.state === \"needsInput\") {\n return prev?.state ?? \"unknown\";\n }\n\n // Rule 3: agent-set needsInput is sticky against scans.\n if (prev?.state === \"needsInput\") {\n return \"needsInput\";\n }\n\n // Rule 4: a stale scan does not regress a more-recent agent state.\n if (prev?.origin === \"agent\" && prev.at >= next.at) {\n return prev.state;\n }\n\n return next.state;\n}\n","import { execSync } from \"node:child_process\";\nimport type { AgentDriver, AgentProbeResult, SpawnOptions, AgentResult } from \"./types.js\";\n\nexport function createClaudeDriver(): AgentDriver {\n return {\n name: \"claude\",\n templateSuffix: \"claude\",\n\n async probe(): Promise<AgentProbeResult> {\n try {\n const version = execSync(\"claude --version\", { encoding: \"utf-8\" }).trim();\n return {\n installed: true,\n version,\n capabilities: [\n \"teams\",\n \"json_output\",\n \"model_routing\",\n \"skills\",\n \"auto_approve\",\n \"streaming\",\n \"prompt_file\",\n ],\n };\n } catch {\n return { installed: false, version: \"\", capabilities: [] };\n }\n },\n\n buildCommand(opts: SpawnOptions): string {\n let cmd = \"claude\";\n\n if (opts.model) {\n cmd += ` --model ${opts.model}`;\n }\n\n if (opts.autoApprove) {\n cmd += \" --dangerously-skip-permissions\";\n } else if (opts.permissionMode) {\n cmd += ` --permission-mode ${opts.permissionMode}`;\n }\n\n if (opts.promptFile) {\n cmd += ` --append-system-prompt-file ${opts.promptFile}`;\n }\n\n if (opts.settingsPath) {\n cmd += ` --settings ${opts.settingsPath}`;\n }\n\n // Load squadrant plugin for skills\n const pluginDir = `${process.env.HOME}/.config/squadrant/plugin`;\n cmd += ` --plugin-dir ${pluginDir}`;\n\n if (!opts.interactive) {\n cmd += ` -p \"${opts.prompt.replace(/\"/g, '\\\\\"')}\"`;\n }\n return cmd;\n },\n\n parseOutput(raw: string): AgentResult {\n const lines = raw.trim().split(\"\\n\").filter((l) => l.startsWith(\"{\"));\n if (lines.length === 0) {\n return { status: \"success\", output: raw.trim() };\n }\n try {\n const last = JSON.parse(lines[lines.length - 1]);\n return { status: \"success\", output: last.result || last.content || raw.trim() };\n } catch {\n return { status: \"success\", output: raw.trim() };\n }\n },\n\n async stop(pid: number): Promise<void> {\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n // process may already be gone\n }\n },\n };\n}\n","import { execSync } from \"node:child_process\";\nimport type { AgentDriver, AgentProbeResult, SpawnOptions, AgentResult } from \"./types.js\";\n\nexport function createCodexDriver(): AgentDriver {\n return {\n name: \"codex\",\n templateSuffix: \"generic\",\n\n async probe(): Promise<AgentProbeResult> {\n try {\n const version = execSync(\"codex --version\", { encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] }).trim();\n const help = execSync(\"codex --help\", { encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n const hasExec = help.includes(\"exec\");\n return {\n installed: true,\n version,\n capabilities: [\n \"auto_approve\",\n \"json_output\",\n \"sandbox\",\n ...(hasExec ? [\"streaming\" as const] : []),\n ],\n };\n } catch {\n return { installed: false, version: \"\", capabilities: [] };\n }\n },\n\n buildCommand(opts: SpawnOptions): string {\n let cmd = `codex exec \"${opts.prompt.replace(/\"/g, '\\\\\"')}\" --json`;\n if (opts.autoApprove) cmd += \" --full-auto\";\n return cmd;\n },\n\n parseOutput(raw: string): AgentResult {\n const lines = raw.trim().split(\"\\n\").filter((l) => l.startsWith(\"{\"));\n if (lines.length === 0) {\n return { status: \"success\", output: raw.trim() };\n }\n try {\n const last = JSON.parse(lines[lines.length - 1]);\n return { status: \"success\", output: last.output || last.result || raw.trim() };\n } catch {\n return { status: \"success\", output: raw.trim() };\n }\n },\n\n async stop(pid: number): Promise<void> {\n try { process.kill(pid, \"SIGTERM\"); } catch { /* already gone */ }\n },\n };\n}\n","import { execSync } from \"node:child_process\";\nimport type { AgentDriver, AgentProbeResult, SpawnOptions, AgentResult } from \"./types.js\";\n\nexport function createGeminiDriver(): AgentDriver {\n return {\n name: \"gemini\",\n templateSuffix: \"generic\",\n\n async probe(): Promise<AgentProbeResult> {\n try {\n const version = execSync(\"gemini --version\", { encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] }).trim();\n return {\n installed: true,\n version,\n capabilities: [\"auto_approve\", \"json_output\", \"streaming\"],\n };\n } catch {\n return { installed: false, version: \"\", capabilities: [] };\n }\n },\n\n buildCommand(opts: SpawnOptions): string {\n let cmd = `gemini -p \"${opts.prompt.replace(/\"/g, '\\\\\"')}\"`;\n if (opts.autoApprove) cmd += \" --yolo\";\n if (opts.jsonOutput) cmd += \" --output-format json\";\n return cmd;\n },\n\n parseOutput(raw: string): AgentResult {\n try {\n const parsed = JSON.parse(raw.trim());\n return { status: \"success\", output: parsed.response || raw.trim() };\n } catch {\n return { status: \"success\", output: raw.trim() };\n }\n },\n\n async stop(pid: number): Promise<void> {\n try { process.kill(pid, \"SIGTERM\"); } catch { /* already gone */ }\n },\n };\n}\n","import { execSync } from \"node:child_process\";\nimport type { AgentDriver, AgentProbeResult, SpawnOptions, AgentResult } from \"./types.js\";\n\nexport function createOpencodeDriver(): AgentDriver {\n return {\n name: \"opencode\",\n templateSuffix: \"opencode\",\n\n async probe(): Promise<AgentProbeResult> {\n try {\n const version = execSync(\"opencode --version\", { encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] }).trim();\n return {\n installed: true,\n version,\n capabilities: [\"auto_approve\", \"json_output\", \"streaming\", \"model_routing\"],\n };\n } catch {\n return { installed: false, version: \"\", capabilities: [] };\n }\n },\n\n buildCommand(opts: SpawnOptions): string {\n // Interactive crews: boot the TUI; the caller delivers opts.prompt as the\n // first turn via runtime.send once the session is ready, so the crew stays\n // alive for follow-up turns through `squadrant crew send`. When a port is\n // given, bind the embedded HTTP server on it so the daemon's SSE bridge\n // can subscribe to /event for turn-end detection (the bare TUI uses an\n // ephemeral unix socket with no reachable /event endpoint).\n if (opts.interactive) return opts.port ? `opencode --port ${opts.port}` : \"opencode\";\n let cmd = `opencode run \"${opts.prompt.replace(/\"/g, '\\\\\"')}\"`;\n if (opts.jsonOutput) cmd += \" --format json\";\n if (opts.model) cmd += ` -m ${opts.model}`;\n return cmd;\n },\n\n parseOutput(raw: string): AgentResult {\n try {\n const parsed = JSON.parse(raw.trim());\n return { status: \"success\", output: parsed.response || parsed.output || raw.trim() };\n } catch {\n return { status: \"success\", output: raw.trim() };\n }\n },\n\n async stop(pid: number): Promise<void> {\n try { process.kill(pid, \"SIGTERM\"); } catch { /* already gone */ }\n },\n };\n}\n","// buildAgentCmd — build the CLI command string used to launch a captain/command\n// session. Extracted from packages/cli/src/commands/launch.ts so it can be\n// unit-tested without spawning real processes.\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { Role } from \"./types.js\";\nimport type { CapabilityRegistry } from \"./registry.js\";\n\n/**\n * Build the shell command string that launches an agent session for a given\n * role. For Claude, handles fresh/continue, permission-mode flags, role\n * template file, and plugin-dir. For all other agents, delegates to the\n * driver's own buildCommand.\n *\n * @param agentName - e.g. \"claude\", \"opencode\", \"codex\"\n * @param registry - populated CapabilityRegistry\n * @param role - \"captain\" | \"command\" | \"crew\" | …\n * @param fresh - true → new session; false → continue last session\n * @param permissionMode - \"acceptEdits\" | \"auto\" | \"bypassPermissions\"\n * @param model - optional model override\n * @param templatesDir - resolved path to ~/.config/squadrant/templates\n */\nexport function buildAgentCmd(\n agentName: string,\n registry: CapabilityRegistry,\n role: string,\n fresh: boolean,\n permissionMode: string,\n model?: string,\n templatesDir?: string,\n): string {\n const driver = registry.getDriver(agentName);\n\n if (driver.name === \"claude\") {\n let cmd = fresh ? \"claude\" : \"claude -c\";\n\n if (permissionMode === \"acceptEdits\") {\n cmd += \" --permission-mode acceptEdits\";\n } else if (permissionMode === \"auto\") {\n cmd += \" --permission-mode auto\";\n } else if (permissionMode === \"bypassPermissions\") {\n cmd += \" --dangerously-skip-permissions\";\n }\n\n if (model) {\n cmd += ` --model ${model}`;\n }\n\n if (templatesDir) {\n const roleFile = path.join(templatesDir, `${role}.claude.md`);\n const legacyRoleFile = path.join(templatesDir, `${role}.CLAUDE.md`);\n const actualRoleFile = fs.existsSync(roleFile)\n ? roleFile\n : fs.existsSync(legacyRoleFile) ? legacyRoleFile : null;\n if (actualRoleFile) {\n cmd += ` --append-system-prompt-file ${actualRoleFile}`;\n }\n\n const pluginDir = path.join(templatesDir, \"..\", \"plugin\");\n if (fs.existsSync(pluginDir)) {\n cmd += ` --plugin-dir ${pluginDir}`;\n }\n }\n\n return cmd;\n }\n\n // Non-Claude agents: delegate to driver.buildCommand.\n const roleFile = templatesDir\n ? path.join(templatesDir, `${role}.${driver.templateSuffix}.md`)\n : undefined;\n return driver.buildCommand({\n prompt: `You are a squadrant ${role}. Read your instructions from ${roleFile ?? role} and begin.`,\n workdir: process.cwd(),\n role: role as Role,\n model,\n autoApprove: true,\n promptFile: roleFile && fs.existsSync(roleFile) ? roleFile : undefined,\n });\n}\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport type {\n ProjectionEmitResult,\n ProjectionEmitter,\n ProjectionSource,\n} from \"@squadrant/shared\";\n\nfunction renderMdc(source: ProjectionSource): string {\n const skillSections = source.skills\n .map(\n (s) =>\n `## Skill: ${s.name}\\n\\n*${s.description}*\\n\\n${s.content}`,\n )\n .join(\"\\n\\n\");\n\n const body = [source.instructions.trim(), skillSections]\n .filter((s) => s.length > 0)\n .join(\"\\n\\n\");\n\n const frontmatter = [\n \"---\",\n \"description: Squadrant-projected rules and skills\",\n \"globs: ['**/*']\",\n \"alwaysApply: true\",\n \"---\",\n \"\",\n ].join(\"\\n\");\n\n return `${frontmatter}${body}\\n`;\n}\n\nasync function readExisting(p: string): Promise<string | null> {\n try {\n return await readFile(p, \"utf-8\");\n } catch (err) {\n if ((err as { code?: string }).code === \"ENOENT\") return null;\n throw err;\n }\n}\n\nfunction buildDiff(existing: string | null, generated: string): string {\n if (existing === null) return `NEW FILE\\n---\\n${generated}`;\n if (existing === generated) return \"UNCHANGED\";\n return `OVERWRITE\\n--- old\\n${existing}\\n--- new\\n${generated}`;\n}\n\nexport function createCursorEmitter(): ProjectionEmitter {\n return {\n name: \"cursor\",\n\n destinations(scope, projectRoot) {\n if (scope === \"user\") {\n return [\n {\n path: path.join(os.homedir(), \".cursor/rules/squadrant-global.mdc\"),\n shared: false,\n format: \"mdc\",\n },\n ];\n }\n if (!projectRoot) return [];\n return [\n {\n path: path.join(projectRoot, \".cursor/rules/squadrant.mdc\"),\n shared: false,\n format: \"mdc\",\n },\n ];\n },\n\n async emit(source, dest, opts): Promise<ProjectionEmitResult> {\n const generated = renderMdc(source);\n const existing = await readExisting(dest.path);\n\n if (opts?.dryRun) {\n return {\n written: false,\n path: dest.path,\n bytesWritten: 0,\n diff: buildDiff(existing, generated),\n };\n }\n\n await mkdir(path.dirname(dest.path), { recursive: true });\n await writeFile(dest.path, generated, \"utf-8\");\n\n return {\n written: true,\n path: dest.path,\n bytesWritten: Buffer.byteLength(generated, \"utf-8\"),\n };\n },\n };\n}\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport { mergeWithMarkers } from \"./marker.js\";\nimport type {\n ProjectionEmitResult,\n ProjectionEmitter,\n ProjectionSource,\n} from \"@squadrant/shared\";\n\nfunction renderMarkdown(source: ProjectionSource): string {\n const skillSections = source.skills\n .map((s) => `## Skill: ${s.name}\\n\\n*${s.description}*\\n\\n${s.content}`)\n .join(\"\\n\\n\");\n return [source.instructions.trim(), skillSections]\n .filter((s) => s.length > 0)\n .join(\"\\n\\n\");\n}\n\nasync function readExisting(p: string): Promise<string | null> {\n try { return await readFile(p, \"utf-8\"); }\n catch (err) {\n if ((err as { code?: string }).code === \"ENOENT\") return null;\n throw err;\n }\n}\n\nexport function createCodexEmitter(): ProjectionEmitter {\n return {\n name: \"codex\",\n\n destinations(scope, projectRoot) {\n if (scope === \"user\") {\n return [{\n path: path.join(os.homedir(), \".codex/AGENTS.md\"),\n shared: true,\n format: \"markdown\",\n }];\n }\n if (!projectRoot) return [];\n return [{\n path: path.join(projectRoot, \"AGENTS.md\"),\n shared: true,\n format: \"markdown\",\n }];\n },\n\n async emit(source, dest, opts): Promise<ProjectionEmitResult> {\n const body = renderMarkdown(source);\n const existing = await readExisting(dest.path);\n const generated = mergeWithMarkers(existing, body);\n\n if (opts?.dryRun) {\n return {\n written: false,\n path: dest.path,\n bytesWritten: 0,\n diff: existing === generated ? \"UNCHANGED\" : `MERGE\\n--- old\\n${existing ?? \"\"}\\n--- new\\n${generated}`,\n };\n }\n\n await mkdir(path.dirname(dest.path), { recursive: true });\n await writeFile(dest.path, generated, \"utf-8\");\n\n return {\n written: true,\n path: dest.path,\n bytesWritten: Buffer.byteLength(generated, \"utf-8\"),\n };\n },\n };\n}\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport { mergeWithMarkers } from \"./marker.js\";\nimport type {\n ProjectionEmitResult,\n ProjectionEmitter,\n ProjectionSource,\n} from \"@squadrant/shared\";\n\nfunction renderMarkdown(source: ProjectionSource): string {\n const skillSections = source.skills\n .map((s) => `## Skill: ${s.name}\\n\\n*${s.description}*\\n\\n${s.content}`)\n .join(\"\\n\\n\");\n return [source.instructions.trim(), skillSections]\n .filter((s) => s.length > 0)\n .join(\"\\n\\n\");\n}\n\nasync function readExisting(p: string): Promise<string | null> {\n try { return await readFile(p, \"utf-8\"); }\n catch (err) {\n if ((err as { code?: string }).code === \"ENOENT\") return null;\n throw err;\n }\n}\n\nexport function createGeminiEmitter(): ProjectionEmitter {\n return {\n name: \"gemini\",\n\n destinations(scope, projectRoot) {\n if (scope === \"user\") {\n return [{\n path: path.join(os.homedir(), \".gemini/GEMINI.md\"),\n shared: true,\n format: \"markdown\",\n }];\n }\n if (!projectRoot) return [];\n return [{\n path: path.join(projectRoot, \"GEMINI.md\"),\n shared: true,\n format: \"markdown\",\n }];\n },\n\n async emit(source, dest, opts): Promise<ProjectionEmitResult> {\n const body = renderMarkdown(source);\n const existing = await readExisting(dest.path);\n const generated = mergeWithMarkers(existing, body);\n\n if (opts?.dryRun) {\n return {\n written: false,\n path: dest.path,\n bytesWritten: 0,\n diff: existing === generated ? \"UNCHANGED\" : `MERGE\\n--- old\\n${existing ?? \"\"}\\n--- new\\n${generated}`,\n };\n }\n\n await mkdir(path.dirname(dest.path), { recursive: true });\n await writeFile(dest.path, generated, \"utf-8\");\n\n return {\n written: true,\n path: dest.path,\n bytesWritten: Buffer.byteLength(generated, \"utf-8\"),\n };\n },\n };\n}\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport { mergeWithMarkers } from \"./marker.js\";\nimport type {\n ProjectionEmitResult,\n ProjectionEmitter,\n ProjectionSource,\n} from \"@squadrant/shared\";\n\nfunction renderMarkdown(source: ProjectionSource): string {\n const skillSections = source.skills\n .map((s) => `## Skill: ${s.name}\\n\\n*${s.description}*\\n\\n${s.content}`)\n .join(\"\\n\\n\");\n return [source.instructions.trim(), skillSections]\n .filter((s) => s.length > 0)\n .join(\"\\n\\n\");\n}\n\nasync function readExisting(p: string): Promise<string | null> {\n try { return await readFile(p, \"utf-8\"); }\n catch (err) {\n if ((err as { code?: string }).code === \"ENOENT\") return null;\n throw err;\n }\n}\n\nexport function createOpencodeEmitter(): ProjectionEmitter {\n return {\n name: \"opencode\",\n\n destinations(scope, projectRoot) {\n if (scope === \"user\") {\n return [{\n path: path.join(os.homedir(), \".config\", \"opencode\", \"AGENTS.md\"),\n shared: true,\n format: \"markdown\",\n }];\n }\n if (!projectRoot) return [];\n return [{\n path: path.join(projectRoot, \"AGENTS.md\"),\n shared: true,\n format: \"markdown\",\n }];\n },\n\n async emit(source, dest, opts): Promise<ProjectionEmitResult> {\n const body = renderMarkdown(source);\n const existing = await readExisting(dest.path);\n const generated = mergeWithMarkers(existing, body);\n\n if (opts?.dryRun) {\n return {\n written: false,\n path: dest.path,\n bytesWritten: 0,\n diff: existing === generated ? \"UNCHANGED\" : `MERGE\\n--- old\\n${existing ?? \"\"}\\n--- new\\n${generated}`,\n };\n }\n\n await mkdir(path.dirname(dest.path), { recursive: true });\n await writeFile(dest.path, generated, \"utf-8\");\n\n return {\n written: true,\n path: dest.path,\n bytesWritten: Buffer.byteLength(generated, \"utf-8\"),\n };\n },\n };\n}\n","// src/control/codex/app-server-client.ts\n// Typed JSON-RPC 2.0 client for `codex app-server` v2.\n// Transport: stdio (newline-delimited JSON). See spec §3.\n// Defensive parser per orca codex-fetcher.ts:160-164: ignore non-JSON lines.\n\nimport { EventEmitter } from \"node:events\";\nimport { spawn as nodeSpawn, type ChildProcessByStdio } from \"node:child_process\";\nimport type { Readable, Writable } from \"node:stream\";\n\ntype Child = ChildProcessByStdio<Writable, Readable, Readable>;\n\nexport interface AppServerClientOpts {\n /** Override for tests; defaults to spawning real `codex app-server`. */\n spawn?: () => Child;\n clientInfo?: { name: string; version: string };\n}\n\nexport function _parseChunk(acc: { buf: string }, chunk: string): unknown[] {\n acc.buf += chunk;\n const out: unknown[] = [];\n let idx: number;\n while ((idx = acc.buf.indexOf(\"\\n\")) >= 0) {\n const line = acc.buf.slice(0, idx);\n acc.buf = acc.buf.slice(idx + 1);\n if (!line.trim()) continue;\n try { out.push(JSON.parse(line)); } catch { /* skip non-JSON defensively */ }\n }\n return out;\n}\n\nexport class AppServerClient extends EventEmitter {\n private proc?: Child;\n private acc = { buf: \"\" };\n private opts: AppServerClientOpts;\n constructor(opts: AppServerClientOpts = {}) { super(); this.opts = opts; }\n\n start(): void {\n if (this.proc) throw new Error(\"AppServerClient already started\");\n const sp = this.opts.spawn ?? defaultSpawn;\n this.proc = sp();\n this.proc.stdout.on(\"data\", (d: Buffer | string) => this._onStdout(d.toString()));\n this.proc.stderr.on(\"data\", (d: Buffer | string) => this.emit(\"stderr\", d.toString()));\n this.proc.on(\"exit\", (code, signal) => {\n this._onClosed();\n this.emit(\"closed\", { code, signal });\n });\n this.proc.on(\"error\", (e) => this.emit(\"error\", e));\n }\n\n kill(): void {\n if (this.proc) this.proc.kill();\n }\n\n async initialize(): Promise<unknown> {\n if (this._handshakeDone) return;\n if (!this.proc) throw new Error(\"AppServerClient not started\");\n const info = this.opts.clientInfo ?? { name: \"squadrant\", version: \"0\" };\n // Send initialize directly (bypass gate) — only initialize may pre-handshake.\n const id = this.nextId++;\n const env = { jsonrpc: \"2.0\", id, method: \"initialize\", params: { clientInfo: info } };\n const res = await new Promise<unknown>((resolve, reject) => {\n this.pending.set(id, { resolve, reject });\n this.proc!.stdin.write(JSON.stringify(env) + \"\\n\");\n });\n // Send 'initialized' as a notification (no id).\n this.proc.stdin.write(JSON.stringify({ jsonrpc: \"2.0\", method: \"initialized\" }) + \"\\n\");\n this._handshakeDone = true;\n return res;\n }\n\n async startThread(params: { cwd: string; model?: string; sandbox?: string; approvalPolicy?: string; developerInstructions?: string }): Promise<{ threadId: string }> {\n const res = await this._sendRequest(\"thread/start\", params) as { thread?: { id?: string } };\n const id = res?.thread?.id;\n if (typeof id !== \"string\") throw new Error(`thread/start: unexpected response shape (no thread.id): ${JSON.stringify(res).slice(0, 200)}`);\n return { threadId: id };\n }\n\n resumeThread(params: { threadId: string; cwd?: string }): Promise<unknown> {\n return this._sendRequest(\"thread/resume\", params);\n }\n\n /** Archive a thread so the app-server tears it down (and reaps any per-thread\n * MCP servers it spawned). Called when a codex crew closes. */\n archiveThread(threadId: string): Promise<unknown> {\n return this._sendRequest(\"thread/archive\", { threadId });\n }\n\n readThread(params: { threadId: string; lastN?: number }): Promise<unknown> {\n return this._sendRequest(\"thread/read\", params);\n }\n\n async sendTurn(threadId: string, text: string): Promise<{ turnId: string }> {\n const ack = await this._sendRequest(\"turn/start\", {\n threadId, input: [{ type: \"text\", text }],\n }) as { turn?: { id?: string } };\n const turnId = ack?.turn?.id;\n if (typeof turnId !== \"string\") throw new Error(`turn/start: unexpected ack shape (no turn.id): ${JSON.stringify(ack).slice(0, 200)}`);\n return new Promise((resolve, reject) => {\n const onNote = (n: { method: string; params?: any }) => {\n if (n.params?.turn?.id !== turnId) return;\n if (n.method === \"turn/completed\") { cleanup(); resolve({ turnId }); }\n if (n.method === \"turn/failed\") { cleanup(); reject(new Error(n.params?.error ?? \"turn failed\")); }\n };\n const onClientClosed = () => { cleanup(); reject(new Error(\"AppServerClient: client closed before turn completed\")); };\n const cleanup = () => {\n this.off(\"notification\", onNote);\n this.off(\"_clientClosed\", onClientClosed);\n };\n this.on(\"notification\", onNote);\n this.once(\"_clientClosed\", onClientClosed);\n });\n }\n\n steerTurn(threadId: string, text: string): Promise<unknown> {\n return this._sendRequest(\"turn/steer\", { threadId, input: [{ type: \"text\", text }] });\n }\n\n interruptTurn(threadId: string): Promise<unknown> {\n return this._sendRequest(\"turn/interrupt\", { threadId });\n }\n\n injectItems(threadId: string, items: unknown[]): Promise<unknown> {\n return this._sendRequest(\"thread/inject_items\", { threadId, items });\n }\n\n respondToServerRequest(id: number, result: unknown): void {\n if (!this.proc) throw new Error(\"AppServerClient not started\");\n this.proc.stdin.write(JSON.stringify({ jsonrpc: \"2.0\", id, result }) + \"\\n\");\n }\n\n private nextId = 1;\n private pending = new Map<number, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();\n protected _handshakeDone = false;\n\n private _onClosed(): void {\n // Mass-reject pending RPC promises so callers don't hang on child death.\n const closedErr = new Error(\"AppServerClient: child closed before response\");\n for (const slot of this.pending.values()) slot.reject(closedErr);\n this.pending.clear();\n // Tell any waiters listening for child-close (sendTurn) to bail.\n this.emit(\"_clientClosed\");\n }\n\n protected _sendRequest(method: string, params?: unknown): Promise<unknown> {\n if (!this._handshakeDone && method !== \"initialize\") {\n throw new Error(`AppServerClient: cannot call '${method}' before handshake (spec §3.2)`);\n }\n if (!this.proc) throw new Error(\"AppServerClient not started\");\n const id = this.nextId++;\n const env = { jsonrpc: \"2.0\", id, method, params: params ?? {} };\n return new Promise((resolve, reject) => {\n this.pending.set(id, { resolve, reject });\n this.proc!.stdin.write(JSON.stringify(env) + \"\\n\");\n });\n }\n\n private _dispatchResponse(msg: any): boolean {\n if (typeof msg?.id !== \"number\") return false;\n const slot = this.pending.get(msg.id);\n if (!slot) return false;\n this.pending.delete(msg.id);\n if (msg.error) slot.reject(new Error(`${msg.error.message ?? \"rpc-error\"} (code ${msg.error.code})`));\n else slot.resolve(msg.result);\n return true;\n }\n\n private _onStdout(s: string): void {\n for (const msg of _parseChunk(this.acc, s)) this._dispatch(msg);\n }\n\n private _dispatch(msg: unknown): void {\n if (this._dispatchResponse(msg)) return;\n const m = msg as any;\n if (typeof m?.method === \"string\" && typeof m?.id === \"number\") {\n this.emit(\"serverRequest\", { id: m.id, method: m.method, params: m.params });\n return;\n }\n if (typeof m?.method === \"string\" && m?.id === undefined) {\n this.emit(\"notification\", { method: m.method, params: m.params });\n }\n }\n}\n\nfunction defaultSpawn(): Child {\n return nodeSpawn(\"codex\", [\"app-server\"], { stdio: [\"pipe\", \"pipe\", \"pipe\"] }) as Child;\n}\n","// codex-app-server-source.ts — LifecycleSource adapter for the codex app-server.\n//\n// Implements D5 from the #333 design: wraps the existing CodexInteractiveDriver\n// as a LifecycleSource without changing the driver's internals.\n//\n// Mechanism: the daemon emit path calls observe(ev) with every ControlEvent that\n// CodexInteractiveDriver emits. This source maps those events to LifecycleSnapshots\n// and feeds them into the reduceLifecycle pipeline via deps.report().\n//\n// Correlation: codex ControlEvents already carry ev.id (taskId), so no\n// deps.resolve() lookup is needed — taskId is known at the call site.\n//\n// NOT wired into the live daemon in Phase 1 (additive per D3/D7).\n// The sibling NativeHookSource crew wires both sources in after this file lands.\n\nimport type { LifecycleSource, LifecycleSourceDeps, LifecycleSnapshot } from \"@squadrant/core\";\nimport type { ControlEvent } from \"@squadrant/shared\";\n\n// ── CodexAppServerSource ─────────────────────────────────────────────────────\n\n/**\n * LifecycleSource adapter for the codex app-server driver.\n *\n * Push-only source: the app-server is event-driven, not polled. snapshot()\n * returns the last reported state for the liveness floor.\n *\n * Usage (daemon wiring, handled by sibling crew):\n * const source = new CodexAppServerSource();\n * source.start(deps);\n * // Wrap the driver's emit so every event also passes through the source:\n * const emit = (ev) => { source.observe(ev); handle(ev); };\n * const driver = new CodexInteractiveDriver({ emit, ... });\n */\nexport class CodexAppServerSource implements LifecycleSource {\n readonly name = \"codex-appserver\";\n\n private deps?: LifecycleSourceDeps;\n /** taskId → last reported snapshot (for snapshot() liveness floor). */\n private cache = new Map<string, LifecycleSnapshot>();\n private active = false;\n\n start(deps: LifecycleSourceDeps): void {\n this.deps = deps;\n this.active = true;\n }\n\n stop(): void {\n this.deps = undefined;\n this.cache.clear();\n this.active = false;\n }\n\n /** Returns the last-reported snapshot for a known crew (liveness floor). */\n snapshot(taskId: string): LifecycleSnapshot | undefined {\n return this.cache.get(taskId);\n }\n\n /** Read-only source health (B4). Purely push-driven — never errors on its own. */\n health(): { active: boolean; error: string | null } {\n return { active: this.active, error: null };\n }\n\n /**\n * Feed a ControlEvent from CodexInteractiveDriver into this source.\n * The daemon wires: emit = (ev) => { source.observe(ev); handle(ev); }\n *\n * All events that carry lifecycle meaning for a codex crew are mapped to a\n * LifecycleSnapshot and reported. Events that are terminal signals (task.done,\n * task.cancelled, task.blocked) or notify-only (task.stalled, task.quiet, etc.)\n * are ignored — terminal state still comes exclusively from `squadrant crew signal`\n * (anti-#2576 invariant).\n */\n observe(ev: ControlEvent): void {\n const snap = toSnapshot(ev);\n if (!snap || !this.deps) return;\n this.cache.set(snap.taskId, snap);\n this.deps.report(snap);\n }\n}\n\n// ── private: ControlEvent → LifecycleSnapshot ────────────────────────────────\n\nfunction toSnapshot(ev: ControlEvent): LifecycleSnapshot | null {\n const now = Date.now();\n switch (ev.type) {\n // ── running: a turn is live ──────────────────────────────────────────────\n case \"task.started\":\n case \"task.reattached\":\n case \"task.turn.started\":\n case \"task.delta\":\n case \"task.progress\":\n return { taskId: ev.id, state: \"running\", alive: true, origin: \"agent\", at: now };\n\n // ── idle: turn ended, crew alive, awaiting next input ────────────────────\n // task.failed: the turn ended with an error, but the crew process is alive.\n // task.session.ended: process is gone (alive:false) — signals liveness loss.\n case \"task.turn.completed\":\n return { taskId: ev.id, state: \"idle\", alive: true, origin: \"agent\", at: now };\n\n case \"task.failed\":\n return { taskId: ev.id, state: \"idle\", alive: true, origin: \"agent\", at: now };\n\n case \"task.session.ended\":\n return { taskId: ev.id, state: \"idle\", alive: false, origin: \"agent\", at: now };\n\n // ── needsInput: crew is blocked on a human ───────────────────────────────\n case \"task.approval.requested\":\n return {\n taskId: ev.id, state: \"needsInput\", alive: true, origin: \"agent\", at: now,\n detail: { note: ev.question, reason: ev.kind },\n };\n\n case \"task.input.requested\":\n return {\n taskId: ev.id, state: \"needsInput\", alive: true, origin: \"agent\", at: now,\n detail: { note: ev.question },\n };\n\n // ── terminal / notify-only — ignored ────────────────────────────────────\n // task.done, task.blocked, task.cancelled: terminal state from crew signal only.\n // task.session, task.stalled, task.quiet, task.idle, task.timeout, etc.: no-op.\n default:\n return null;\n }\n}\n","// src/control/codex/config.ts\n// Read the user's codex config (~/.codex/config.toml or $CODEX_HOME/config.toml)\n// and resolve the active model, applying [notice.model_migrations] so squadrant\n// uses the same model the TUI would use (e.g. gpt-5.3-codex → gpt-5.5).\n\nimport { readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport async function resolveCodexModel(): Promise<string | undefined> {\n const home = process.env[\"CODEX_HOME\"] ?? join(homedir(), \".codex\");\n const configPath = join(home, \"config.toml\");\n\n let text: string;\n try {\n text = await readFile(configPath, \"utf8\");\n } catch {\n return undefined;\n }\n\n // Extract top-level `model = \"...\"` (only before the first section header).\n const topLevel = text.split(/^\\[/m)[0] ?? \"\";\n const modelMatch = topLevel.match(/^model\\s*=\\s*\"([^\"]+)\"/m);\n if (!modelMatch) return undefined;\n let model = modelMatch[1]!;\n\n // Apply [notice.model_migrations] — the TUI uses this map to upgrade legacy\n // model names (e.g. gpt-5.3-codex → gpt-5.5) before calling thread/start.\n // Without this, the app-server sends the stale name and ChatGPT OAuth rejects\n // it with a 400: \"The 'gpt-5.3-codex' model is not supported\".\n // Capture the section body up to the next section header (`^[`) or end of\n // input. JS regex has no `\\z`; `(?![\\s\\S])` is the end-of-input assertion so\n // migrations still resolve when the section is the last one in the file.\n const migSection = text.match(/^\\[notice\\.model_migrations\\]([\\s\\S]*?)(?=^\\[|(?![\\s\\S]))/m);\n if (migSection) {\n const migRe = /^\"([^\"]+)\"\\s*=\\s*\"([^\"]+)\"/mg;\n let m: RegExpExecArray | null;\n while ((m = migRe.exec(migSection[1]!)) !== null) {\n if (m[1] === model) { model = m[2]!; break; }\n }\n }\n\n return model;\n}\n","// src/control/codex/normalize.ts\n// Pure mapping from app-server ServerNotification → squadrant ControlEvent.\n// Spec §4.7. Unknown methods return null (status-line only / forward-compat).\n//\n// Anti-#2576 invariant: NO codex notification maps to task.done.\n// task.done is emitted exclusively by the driver on clean process exit.\n//\n// NOTE: Server-requests (frames that carry an `id` field alongside `method`)\n// are NOT handled here — they are routed by CodexInteractiveDriver (Task 2.4).\n// Extending this function with request handling would be incorrect.\nimport type { ControlEvent } from \"@squadrant/shared\";\n\n/** Minimal shape accepted from the JSON-RPC notification stream. */\nexport type AppServerNotification = { method: string; params?: Record<string, unknown> };\n\n/**\n * Map one app-server notification to a ControlEvent, or null when the\n * notification is informational (token-usage, compaction, status-change)\n * and does not need to enter the squadrant event bus.\n *\n * @param taskId The squadrant task ID that owns this notification stream.\n * @param n Raw notification frame from the app-server JSON-RPC channel.\n */\nexport function normalizeAppServerNotification(\n taskId: string,\n n: AppServerNotification,\n): ControlEvent | null {\n const p = n.params ?? {};\n\n switch (n.method) {\n // ── turn lifecycle ────────────────────────────────────────────────────\n case \"turn/started\":\n return {\n type: \"task.turn.started\",\n id: taskId,\n // TurnStartedNotification carries params.turn.id, not a top-level turnId.\n turnId: String((p[\"turn\"] as Record<string, unknown>)?.[\"id\"] ?? \"\"),\n };\n\n case \"turn/completed\":\n return {\n type: \"task.turn.completed\",\n id: taskId,\n // TurnCompletedNotification: same shape as TurnStartedNotification.\n turnId: String((p[\"turn\"] as Record<string, unknown>)?.[\"id\"] ?? \"\"),\n };\n\n // ── streaming delta (heartbeat / content) ─────────────────────────────\n // AgentMessageDeltaNotification: { threadId, turnId, itemId, delta }\n case \"item/agentMessage/delta\":\n // ReasoningTextDeltaNotification: { threadId, turnId, itemId, delta, contentIndex }\n case \"item/reasoning/textDelta\":\n // CommandExecutionOutputDeltaNotification: { threadId, turnId, itemId, delta }\n case \"item/commandExecution/outputDelta\":\n return {\n type: \"task.delta\",\n id: taskId,\n turnId: String(p[\"turnId\"] ?? \"\"),\n chunk: String(p[\"delta\"] ?? \"\"),\n };\n\n // command/exec/outputDelta is connection-scoped (no turnId); map to delta\n // with empty turnId so the bus can still forward it.\n // CommandExecOutputDeltaNotification: { processId, stream, deltaBase64, capReached }\n case \"command/exec/outputDelta\":\n return {\n type: \"task.delta\",\n id: taskId,\n turnId: \"\",\n chunk: String(p[\"deltaBase64\"] ?? \"\"),\n };\n\n // ── error ─────────────────────────────────────────────────────────────\n // ErrorNotification: { error: TurnError, willRetry, threadId, turnId }\n case \"error\": {\n const err = p[\"error\"] as Record<string, unknown> | undefined;\n const message = String(err?.[\"message\"] ?? p[\"message\"] ?? \"error\");\n return { type: \"task.failed\", id: taskId, error: message };\n }\n\n // ── status-line only — return null ────────────────────────────────────\n // ThreadTokenUsageUpdatedNotification: { threadId, turnId, tokenUsage }\n case \"thread/tokenUsage/updated\":\n // ContextCompactedNotification (deprecated): { threadId, turnId }\n case \"thread/compacted\":\n // ThreadStatusChangedNotification\n case \"thread/status/changed\":\n return null;\n\n // ── unknown / future methods ──────────────────────────────────────────\n default:\n return null;\n }\n}\n","// src/control/codex/driver.ts\n// Daemon-side interactive driver for codex. Owns ONE long-lived AppServerClient\n// child, maps TaskRecord ↔ threadId, emits squadrant ControlEvents via the\n// injected emit() hook. Notification mapping delegates to\n// normalizeAppServerNotification (Task 2.3); the driver only routes server-\n// requests and lifecycle. Spec §4.1/§4.6/§4.7.\n\nimport { AppServerClient } from \"./app-server-client.js\";\nimport { resolveCodexModel } from \"./config.js\";\nimport { normalizeAppServerNotification } from \"./normalize.js\";\nimport type { ControlEvent, TaskRecord } from \"@squadrant/shared\";\nimport { TERMINAL_STATES } from \"@squadrant/shared\";\n\n/**\n * Boot-time guard for the daemon's codex reattach loop. Reattaching a thread\n * re-spawns its per-thread MCP servers (gitnexus/pay), so reattaching EVERY\n * non-terminal codex task on boot re-storms one MCP set per historical crew\n * (observed: 22 zombie tasks → 22 gitnexus servers → RAM exhaustion). Only\n * reattach a task that is (a) interactive codex, (b) non-terminal — closed\n * crews are `cancelled` via codex-close, so they're skipped, (c) still fresh:\n * a dead crew's pane is gone and hasn't heartbeat within the staleness window,\n * and (d) has a resumeRef to resume from.\n */\nexport function shouldReattachCodex(\n rec: TaskRecord,\n now: number,\n staleMs: number,\n): boolean {\n if (rec.provider !== \"codex\" || rec.mode !== \"interactive\") return false;\n if (TERMINAL_STATES.has(rec.state)) return false;\n const last = rec.attempts.at(-1)?.lastHeartbeatAt ?? rec.lastHeartbeat ?? 0;\n if (now - last > staleMs) return false;\n return Boolean(rec.attempts.at(-1)?.resumeRef);\n}\n\nexport interface DriverDeps {\n /** Override for tests; defaults to a real AppServerClient. */\n makeClient?: () => AppServerClient;\n /** Ingress into the daemon's event pipeline. */\n emit: (ev: ControlEvent) => void;\n}\n\nexport class CodexInteractiveDriver {\n private client?: AppServerClient;\n private handshakeP?: Promise<void>;\n private threadByTask = new Map<string, string>();\n private taskByThread = new Map<string, string>();\n /**\n * taskId → in-flight dispatch promise. The first-turn say() can arrive while\n * dispatch() is still awaiting startThread (threadByTask not yet set); say()\n * awaits this gate before reading threadByTask so the first turn isn't lost\n * with \"no thread for task\" (issue #212).\n */\n private dispatchByTask = new Map<string, Promise<void>>();\n /** taskId → last pending server-request {id, method} (for answer()) */\n private serverRequestByTask = new Map<string, { id: number; method: string }>();\n private deps: DriverDeps;\n\n constructor(deps: DriverDeps) { this.deps = deps; }\n\n private async ensureClient(): Promise<AppServerClient> {\n if (this.client) return this.client;\n const c = (this.deps.makeClient ?? (() => new AppServerClient({ clientInfo: { name: \"squadrant\", version: \"iv\" } })))();\n this.client = c;\n c.start();\n c.on(\"notification\", (n) => this.onNotification(n));\n c.on(\"serverRequest\", (r) => this.onServerRequest(r));\n c.on(\"closed\", () => { this.client = undefined; this.handshakeP = undefined; });\n return c;\n }\n\n private async ensureHandshake(): Promise<void> {\n const c = await this.ensureClient();\n if (!this.handshakeP) this.handshakeP = c.initialize().then(() => {});\n return this.handshakeP;\n }\n\n async dispatch(rec: TaskRecord & { cwd?: string; model?: string }): Promise<void> {\n // Register the in-flight dispatch synchronously so a concurrent first-turn\n // say() can await it (see dispatchByTask / issue #212). Cleared once the\n // thread is mapped (or dispatch failed), after which say() reads the map.\n const p = this.runDispatch(rec);\n this.dispatchByTask.set(rec.id, p.then(() => {}, () => {}));\n try {\n await p;\n } finally {\n this.dispatchByTask.delete(rec.id);\n }\n }\n\n private async runDispatch(rec: TaskRecord & { cwd?: string; model?: string }): Promise<void> {\n try {\n const c = await this.ensureClient();\n await withTimeout(this.ensureHandshake(), 10_000, \"handshake timed out\");\n // When no model is explicitly set on the task record, read the user's\n // codex config and apply model migrations (e.g. gpt-5.3-codex → gpt-5.5).\n // Without this the app-server falls back to the raw config value and\n // ChatGPT OAuth rejects it with a 400 (verified: gpt-5.5 succeeds).\n const model = rec.model ?? await resolveCodexModel();\n const { threadId } = await c.startThread({\n cwd: rec.cwd ?? process.cwd(),\n model,\n // Parity with claude/opencode crews, which run UNSANDBOXED (no Seatbelt).\n // Codex was the only agent under `workspace-write`, and that FS sandbox\n // blocked `squadrant crew signal …` from reaching the daemon socket (which\n // lives outside the workspace) — breaking the done/blocked/failed\n // lifecycle. Codex's AF_UNIX-socket allowance has no stable config path\n // (it's gated behind the experimental_network feature), so the surgical\n // writable_roots escape is not viable. danger-full-access removes the FS\n // jail so signals work; approvalPolicy still gates risky ops when set to\n // \"untrusted\" (the gate axis is independent of the sandbox axis).\n sandbox: \"danger-full-access\",\n approvalPolicy: rec.approvalPolicy ?? \"never\",\n developerInstructions: buildCodexDeveloperInstructions(rec),\n });\n this.threadByTask.set(rec.id, threadId);\n this.taskByThread.set(threadId, rec.id);\n this.deps.emit({ type: \"task.session\", id: rec.id, resumeRef: threadId });\n this.deps.emit({ type: \"task.started\", id: rec.id });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n this.deps.emit({ type: \"task.failed\", id: rec.id, error: `handshake/start failed: ${msg}` });\n throw e;\n }\n }\n\n async say(taskId: string, text: string): Promise<void> {\n // Wait out any in-flight dispatch so the first turn isn't dropped during\n // the startThread window (#212). The gate never rejects; a failed dispatch\n // simply leaves threadByTask empty → the existing \"no thread\" throw stands.\n await this.dispatchByTask.get(taskId);\n const c = this.client!;\n const tid = this.threadByTask.get(taskId);\n if (!tid) throw new Error(`no thread for task ${taskId}`);\n await c.sendTurn(tid, text);\n }\n\n async steer(taskId: string, text: string): Promise<void> {\n const c = this.client!;\n const tid = this.threadByTask.get(taskId);\n if (!tid) throw new Error(`no thread for task ${taskId}`);\n await c.steerTurn(tid, text);\n }\n\n async interrupt(taskId: string): Promise<void> {\n const c = this.client!;\n const tid = this.threadByTask.get(taskId);\n if (!tid) throw new Error(`no thread for task ${taskId}`);\n await c.interruptTurn(tid);\n }\n\n /**\n * Tear down a task's thread when its crew closes. Squadrant runs ONE shared\n * app-server with a thread per crew; closing the cmux pane only kills the\n * `crew attach` renderer, so without this the thread — and the gitnexus/pay\n * MCP servers it spawned — leak forever (verified: ~53MB per orphaned crew).\n * Archiving the thread lets the app-server reap it and its MCP children.\n */\n async close(taskId: string): Promise<void> {\n const tid = this.threadByTask.get(taskId);\n this.serverRequestByTask.delete(taskId);\n if (!tid) return;\n this.threadByTask.delete(taskId);\n this.taskByThread.delete(tid);\n try {\n await this.client?.archiveThread(tid);\n } catch {\n // Best-effort: the app-server may already be gone. The maps are cleared\n // regardless so a daemon restart won't try to reattach a dead thread.\n }\n }\n\n /** Kill the long-lived app-server child process on daemon shutdown. */\n stop(): void {\n this.client?.kill();\n }\n\n async answer(taskId: string, payload: unknown): Promise<void> {\n const c = this.client!;\n const rec = this.serverRequestByTask.get(taskId);\n if (rec == null) throw new Error(`no pending server-request for task ${taskId}`);\n c.respondToServerRequest(rec.id, this.mapAnswerPayload(payload, rec.method));\n this.serverRequestByTask.delete(taskId);\n }\n\n /**\n * Map the captain-facing payload ({text, decision}) to the response shape\n * the codex app-server expects for the specific request method.\n *\n * Old protocol (applyPatchApproval / execCommandApproval):\n * { decision: ReviewDecision } where ReviewDecision = \"approved\" | \"denied\" | …\n *\n * v2 protocol (item/commandExecution/requestApproval / item/fileChange/requestApproval):\n * { decision: CommandExecutionApprovalDecision } where decision = \"accept\" | \"decline\" | …\n *\n * Non-approval requests (text input) pass through unchanged.\n */\n private mapAnswerPayload(payload: unknown, method: string): unknown {\n if (typeof payload !== \"object\" || !payload) return payload;\n const p = payload as Record<string, unknown>;\n if (typeof p.decision !== \"string\") return payload;\n if (method === \"applyPatchApproval\" || method === \"execCommandApproval\") {\n const d = p.decision === \"approve\" ? \"approved\" : \"denied\";\n return { decision: d };\n }\n if (method === \"item/commandExecution/requestApproval\" || method === \"item/fileChange/requestApproval\") {\n const d = p.decision === \"approve\" ? \"accept\" : \"decline\";\n return { decision: d };\n }\n // Unknown method — send the raw decision value\n return { decision: p.decision };\n }\n\n async reattach(rec: TaskRecord & { cwd?: string }): Promise<void> {\n await this.ensureHandshake();\n const c = this.client!;\n const resumeRef = rec.attempts.at(-1)?.resumeRef;\n if (!resumeRef) throw new Error(`reattach: no resumeRef on task ${rec.id}`);\n await c.resumeThread({ threadId: resumeRef, cwd: rec.cwd });\n this.threadByTask.set(rec.id, resumeRef);\n this.taskByThread.set(resumeRef, rec.id);\n this.deps.emit({ type: \"task.reattached\", id: rec.id });\n }\n\n private onNotification(n: { method: string; params?: any }): void {\n const tid = n.params?.threadId ?? n.params?.thread_id;\n const taskId = tid ? this.taskByThread.get(tid) : undefined;\n if (!taskId) return; // status-line only\n const ev = normalizeAppServerNotification(taskId, n);\n if (ev) this.deps.emit(ev);\n }\n\n private onServerRequest(r: { id: number; method: string; params?: any }): void {\n const tid = r.params?.threadId ?? r.params?.thread_id;\n let taskId = tid ? this.taskByThread.get(tid) : undefined;\n if (!taskId && !tid) {\n // Codex approval-shaped server-requests don't reliably carry threadId.\n // Fall back to the sole active task if exactly one exists; otherwise drop.\n if (this.taskByThread.size === 1) {\n taskId = this.taskByThread.values().next().value;\n } else {\n process.stderr.write(\n `[codex/driver] serverRequest ${r.method} dropped: no threadId and ${this.taskByThread.size} active tasks\\n`,\n );\n return;\n }\n }\n if (!taskId) return;\n this.serverRequestByTask.set(taskId, { id: r.id, method: r.method });\n const isApproval = r.method.includes(\"Approval\") || r.method.includes(\"approval\");\n if (isApproval) {\n this.deps.emit({\n type: \"task.approval.requested\",\n id: taskId,\n requestId: r.id,\n question: String(r.params?.question ?? r.method),\n kind: r.method,\n });\n } else {\n this.deps.emit({\n type: \"task.input.requested\",\n id: taskId,\n requestId: r.id,\n question: String(r.params?.question ?? r.method),\n });\n }\n }\n}\n\n/**\n * Build the per-thread developerInstructions for a codex crew. Unlike\n * claude/opencode (which get SQUADRANT_CREW_* env vars on their shell launch\n * line), codex tasks share ONE long-lived app-server child, so a process-level\n * env var would be wrong for concurrent tasks. Instead we tell each thread its\n * concrete task id + project and the exact flag-based signal command, so the\n * codex crew can report terminal state via `squadrant crew signal`. Appended\n * after the crew role body (when present) so the role still leads.\n */\nexport function buildCodexDeveloperInstructions(\n rec: { id: string; project: string; roleInstructions?: string },\n): string {\n const directive =\n `You are squadrant crew task ${rec.id} in project ${rec.project}. ` +\n `When you finish, run EXACTLY: squadrant crew signal done --task-id ${rec.id} --project ${rec.project} --message \"<one-line summary>\". ` +\n `If you are blocked or fail, run squadrant crew signal blocked|failed with the same --task-id ${rec.id} --project ${rec.project} flags.`;\n return rec.roleInstructions ? `${rec.roleInstructions}\\n\\n${directive}` : directive;\n}\n\nfunction withTimeout<T>(p: Promise<T>, ms: number, msg: string): Promise<T> {\n return new Promise((resolve, reject) => {\n const t = setTimeout(() => reject(new Error(msg)), ms);\n p.then(\n (v) => { clearTimeout(t); resolve(v); },\n (e) => { clearTimeout(t); reject(e); },\n );\n });\n}\n","// src/control/opencode/sse-bridge.ts\n// Daemon-side bridge from an opencode crew's HTTP event bus to squadrant\n// ControlEvents. Interactive opencode crews launch as `opencode --port <N>`,\n// which binds a local HTTP server exposing an SSE stream at GET /event. The TUI\n// itself is just one client of that server; the daemon is another. We subscribe\n// once per crew and translate the documented `session.idle` event (emitted when\n// a turn finishes) into `task.turn.completed`, which the state-machine reduces\n// to `awaiting-input`. This gives opencode the same reliable turn-end signal\n// codex gets from its app-server — WITHOUT the crew shelling out to squadrant.\n//\n// `session.idle` is liveness, NOT completion (anti-#2576): a finished turn is\n// not a finished task. Terminal state still comes from the explicit\n// `squadrant crew signal done` in the crew template; the reducer absorbs any\n// session.idle that arrives after the task is already terminal.\nimport type { ControlEvent } from \"@squadrant/shared\";\n\nexport interface OpencodeSseBridgeDeps {\n /** Ingress into the daemon's event pipeline (resolves project + handles). */\n emit: (ev: ControlEvent) => void;\n /** Injectable for tests; defaults to global fetch. */\n fetchImpl?: typeof fetch;\n /** Injectable backoff for tests; defaults to setTimeout. */\n sleep?: (ms: number) => Promise<void>;\n /** Backoff between reconnect attempts (ms, default 500). */\n reconnectMs?: number;\n /** Attempts to reach the server before giving up the boot wait (default 240\n * ≈ 120s). Must comfortably outlast the crew's own first-turn delivery\n * budget (SEND_FIRST_TURN_TIMEOUT_MS = 90s in crew-pane.ts) — #504:\n * otherwise the bridge can give up and permanently stop watching a crew\n * (both turn-end AND permission-gate detection) while the CLI's own\n * pane-polling delivery mechanism is still patiently retrying and\n * eventually succeeds, leaving the daemon silently, irrecoverably blind\n * for the rest of that crew's life. Live-reproduced 2026-07-02: opencode's\n * embedded HTTP server took >30s to bind under concurrent crew-spawn load,\n * the bridge gave up at 60 attempts, and the crew's later permission gate\n * (which fires correctly once subscribed — verified live) was never seen. */\n maxBootAttempts?: number;\n log?: (msg: string) => void;\n}\n\n/**\n * One long-lived SSE subscription per opencode crew. Keyed by taskId so the\n * daemon can stop it when the crew closes. Self-stops when the server's stream\n * ends (crew CLI exited) — at that point terminal state has already been\n * recorded via signal, or the watchdog/close path will reconcile.\n */\nexport class OpencodeSseBridge {\n private controllers = new Map<string, AbortController>();\n /** taskId → the crew's opencode server port (for permission-reply POSTs). */\n private portByTask = new Map<string, number>();\n /** taskId → the last unresolved permission on the bus (for answer()). */\n private pendingPermByTask = new Map<string, { permID: string; sessionID: string }>();\n /** Synthetic monotonic request id. opencode has no numeric id on the bus, but\n * task.approval.requested carries one (codex parity) to key gate promotion. */\n private nextRequestId = 1;\n private deps: OpencodeSseBridgeDeps;\n\n constructor(deps: OpencodeSseBridgeDeps) {\n this.deps = deps;\n }\n\n /** Begin subscribing to the crew's /event stream. Idempotent per task. */\n start(o: { taskId: string; port: number }): void {\n if (this.controllers.has(o.taskId)) return;\n this.portByTask.set(o.taskId, o.port);\n const ac = new AbortController();\n this.controllers.set(o.taskId, ac);\n void this.run(o.taskId, o.port, ac);\n }\n\n /** Stop subscribing for a task (crew closed / terminal). */\n stop(taskId: string): void {\n const ac = this.controllers.get(taskId);\n if (ac) { ac.abort(); this.controllers.delete(taskId); }\n this.portByTask.delete(taskId);\n this.pendingPermByTask.delete(taskId);\n }\n\n /**\n * Resolve a pending opencode permission by POSTing the captain's decision to\n * the crew's server (live-verified, opencode 1.15.13: POST\n * /session/{sessionID}/permissions/{permissionID} with\n * { response: \"once\" | \"reject\" } → 200, fires permission.replied). Mirrors\n * codex's driver.answer(). Returns true if there WAS a pending permission (so\n * the caller knows the answer was an approval, not a reply to a plain `signal\n * blocked` question); false if nothing was pending (already resolved on the\n * bus, or no gate).\n */\n async answer(taskId: string, decision: \"approve\" | \"deny\"): Promise<boolean> {\n const pend = this.pendingPermByTask.get(taskId);\n const port = this.portByTask.get(taskId);\n if (!pend || port == null) return false;\n this.pendingPermByTask.delete(taskId);\n const fetchImpl = this.deps.fetchImpl ?? fetch;\n const response = decision === \"approve\" ? \"once\" : \"reject\";\n try {\n await fetchImpl(`http://127.0.0.1:${port}/session/${pend.sessionID}/permissions/${pend.permID}`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ response }),\n });\n } catch (e) {\n this.deps.log?.(`opencode permission reply failed for ${taskId}: ${(e as Error).message}`);\n }\n // Clear blocked → working; the crew continues (or aborts) the turn, and a\n // later session.idle settles it back to awaiting-input.\n this.deps.emit({ type: \"task.started\", id: taskId });\n return true;\n }\n\n private async run(taskId: string, port: number, ac: AbortController): Promise<void> {\n const fetchImpl = this.deps.fetchImpl ?? fetch;\n const sleep = this.deps.sleep ?? ((ms: number) => new Promise((r) => setTimeout(r, ms)));\n const reconnectMs = this.deps.reconnectMs ?? 500;\n const maxBoot = this.deps.maxBootAttempts ?? 240;\n const url = `http://127.0.0.1:${port}/event`;\n let booted = false;\n let bootAttempts = 0;\n\n while (!ac.signal.aborted) {\n try {\n const res = await fetchImpl(url, {\n signal: ac.signal,\n headers: { accept: \"text/event-stream\" },\n });\n if (!res.ok || !res.body) throw new Error(`status ${res.status}`);\n booted = true;\n await this.consume(taskId, res.body, ac);\n // Stream ended cleanly: the opencode server closed (crew CLI exited).\n // Nothing more to subscribe to — stop without reconnecting.\n break;\n } catch (e) {\n if (ac.signal.aborted) return;\n if (!booted) {\n bootAttempts++;\n if (bootAttempts >= maxBoot) {\n this.deps.log?.(\n `opencode SSE bridge: gave up connecting to ${url} after ${bootAttempts} attempts: ${(e as Error).message}`,\n );\n this.controllers.delete(taskId);\n return;\n }\n }\n await sleep(reconnectMs);\n }\n }\n this.controllers.delete(taskId);\n }\n\n private async consume(\n taskId: string,\n body: ReadableStream<Uint8Array>,\n ac: AbortController,\n ): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buf = \"\";\n try {\n while (!ac.signal.aborted) {\n const { done, value } = await reader.read();\n if (done) return;\n buf += decoder.decode(value, { stream: true });\n let nl: number;\n while ((nl = buf.indexOf(\"\\n\")) >= 0) {\n const line = buf.slice(0, nl);\n buf = buf.slice(nl + 1);\n this.handleLine(taskId, line);\n }\n }\n } finally {\n try { await reader.cancel(); } catch { /* already closed */ }\n }\n }\n\n private handleLine(taskId: string, rawLine: string): void {\n let line = rawLine.trim();\n if (!line) return;\n // SSE field form `data: {json}`; opencode also emits bare JSON lines.\n if (line.startsWith(\"data:\")) line = line.slice(5).trim();\n if (!line.startsWith(\"{\")) return;\n let json:\n | {\n type?: string;\n properties?: {\n id?: string;\n sessionID?: string;\n requestID?: string;\n permission?: string;\n patterns?: string[];\n };\n }\n | undefined;\n try {\n json = JSON.parse(line);\n } catch {\n return; // partial/non-JSON keepalive line\n }\n if (json?.type === \"session.idle\") {\n // turnId is informational for opencode (no per-turn id on the bus); use\n // the session id so the ledger attempt carries a stable correlation key.\n this.deps.emit({\n type: \"task.turn.completed\",\n id: taskId,\n turnId: json.properties?.sessionID ?? \"opencode\",\n });\n } else if (json?.type === \"permission.asked\") {\n // A gated tool (e.g. bash, when --approval set bash:\"ask\") needs approval.\n // Live-verified payload (opencode 1.15.13): properties = PermissionRequest\n // { id:\"per_…\", sessionID:\"ses_…\", permission:\"bash\", patterns:[cmd], … }.\n // Record the pending request so answer() can POST the decision, and surface\n // it as task.approval.requested (codex parity) — the reducer turns it into\n // blocked and the relay renders CREW BLOCKED with the tool + command.\n const p = json.properties;\n if (p?.id && p?.sessionID) {\n this.pendingPermByTask.set(taskId, { permID: p.id, sessionID: p.sessionID });\n const tool = p.permission ?? \"a tool\";\n const cmd = Array.isArray(p.patterns) && p.patterns.length ? `: ${p.patterns.join(\" \")}` : \"\";\n this.deps.emit({\n type: \"task.approval.requested\",\n id: taskId,\n requestId: this.nextRequestId++,\n question: `opencode requests permission to run ${tool}${cmd}`,\n kind: tool,\n });\n }\n } else if (json?.type === \"permission.replied\") {\n // The permission was resolved on the bus (by us or another client) — clear\n // pending state so a later captain answer is a no-op rather than a stale POST.\n this.pendingPermByTask.delete(taskId);\n }\n }\n}\n","import { execSync } from \"node:child_process\";\nimport { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { InteractiveHookAdapter } from \"./types.js\";\nimport type { ControlEvent } from \"@squadrant/shared\";\n\n// PostToolUse fires after EVERY tool call mid-turn — it is the only liveness\n// signal that refreshes the heartbeat while a crew is still working.\n// Stop fires at turn completion and maps to task.turn.completed so the task\n// transitions to awaiting-input (immune to stall detection) — without this,\n// a captain AFK for >heartbeatBudgetMs would get a false CREW STALLED.\n// SubagentStop fires only at a turn boundary but is liveness-only — it fires\n// while the parent agent still owns the turn. SessionEnd is NOT liveness: it\n// signals the session is gone (crash / Ctrl-C / /exit), so it terminalizes the\n// record (→ task.session.ended) rather than resuming 'working' (#139).\n// UserPromptSubmit fires before Claude processes each prompt submission, including\n// the first interactive turn — used as the authoritative first-turn confirmation\n// signal (#470), replacing the screen-scrape {delivered} heuristic.\nconst EVENTS = [\"Stop\", \"SubagentStop\", \"SessionEnd\", \"PostToolUse\", \"Notification\", \"UserPromptSubmit\"] as const;\n\n// #560: matcher-scoped hook entries beyond the broad EVENTS list above — fires\n// only for the named tool, not every tool call. AskUserQuestion is CC's native\n// interactive-prompt tool: PreToolUse fires the instant it opens (and blocks\n// the turn awaiting a human selection), so this is the earliest possible signal\n// that a crew is blocked on a question. Scoped to this one tool so it doesn't\n// double the per-tool-call hook overhead PostToolUse already covers.\nconst MATCHED_EVENTS: ReadonlyArray<readonly [event: string, matcher: string]> = [\n [\"PreToolUse\", \"AskUserQuestion\"],\n];\n\n// #560: Claude's PreToolUse hook payload carries no native per-tool-call id\n// (documented shape is session_id/cwd/tool_name/tool_input only — no\n// tool_use_id), so there is no \"real\" requestId to forward. Seeded from\n// Date.now() and incremented per call (this module runs fresh per hook\n// invocation, so in practice each call gets Date.now() at that moment) so\n// schedulePromotion's `${taskId}#${requestId}` dedup key never collides\n// across successive AskUserQuestion prompts for the same crew, unlike a\n// hardcoded 0 would.\nlet nextAskUserQuestionRequestId = Date.now();\n\n/**\n * Probe whether the local Claude CLI supports `--settings <path>`. The\n * daemon-supervised crew path needs per-invocation settings to inject the\n * squadrant Stop hook without polluting the user's global `~/.claude/settings.json`\n * (the scrapped PR #71 mistake). Returns \"flag\" when --settings is available\n * (the happy path), \"project-dir\" when the fallback (write `.claude/settings.json`\n * under the project dir + cd) is needed.\n */\nexport function probeClaudeSettingsFlag(): \"flag\" | \"project-dir\" {\n try {\n const help = execSync(\"claude --help\", { encoding: \"utf-8\", stdio: [\"ignore\", \"pipe\", \"ignore\"] });\n return help.includes(\"--settings \") ? \"flag\" : \"project-dir\";\n } catch {\n return \"project-dir\";\n }\n}\n\n/**\n * Pure: returns true when a Notification hook message indicates Claude is waiting\n * for the user to grant a tool-use permission. Idle notifications (\"Waiting for\n * your input\", \"Claude is thinking\") return false — only permission/approval\n * language triggers the fast-path task.blocked path.\n */\nexport function isPermissionNotification(message: string): boolean {\n if (!message || !message.trim()) return false;\n const lower = message.toLowerCase();\n return lower.includes(\"permission\") || lower.includes(\"approve\");\n}\n\n// Keyed on (event, matcher) — NOT command alone. An event can carry both a\n// bare entry (matcher \"\", from EVENTS) and a matcher-scoped entry (from\n// MATCHED_EVENTS) with the identical command string (only the matcher\n// differs; Claude dispatches on matcher, not on the command text). Scanning\n// ALL entries for the event regardless of matcher would make the\n// matcher-scoped install look \"already done\" the moment a bare entry for the\n// same event+command exists, and silently skip installing it — the same\n// silent-drop failure mode this hook set exists to close (#560).\nfunction installHookEntry(hooks: Record<string, unknown>, event: string, matcher: string, command: string): void {\n if (!Array.isArray(hooks[event])) hooks[event] = [];\n const entries = hooks[event] as unknown[];\n const already = entries.some(\n (m) => (m as any)?.matcher === matcher &&\n Array.isArray((m as any)?.hooks) &&\n (m as any).hooks.some((h: any) => typeof h?.command === \"string\" && h.command.includes(command)),\n );\n if (!already) {\n entries.push({ matcher, hooks: [{ type: \"command\", command, timeout: 10 }] });\n }\n}\n\n/** Pure, idempotent merge of squadrant hooks into a Claude settings object. */\nexport function mergeClaudeHooks(settings: any, hookCmd: string): any {\n const next = structuredClone(settings ?? {});\n next.hooks ??= {};\n for (const ev of EVENTS) {\n installHookEntry(next.hooks, ev, \"\", `${hookCmd} ${ev}`);\n }\n for (const [ev, matcher] of MATCHED_EVENTS) {\n installHookEntry(next.hooks, ev, matcher, `${hookCmd} ${ev}`);\n }\n return next;\n}\n\n/**\n * Pure, conservative detector for a trailing question that needs captain input.\n * Returns the question text when the LAST non-empty line of the message (outside\n * any fenced code block) ends with \"?\", else null. Intentionally narrow to avoid\n * false-blocked: rhetorical mid-text questions and questions inside ```fences```\n * are ignored because only the final visible line counts. When unsure → null.\n */\nexport function detectTrailingQuestion(text: string): string | null {\n if (!text) return null;\n let inFence = false;\n let lastLine: string | null = null;\n for (const raw of text.split(/\\r?\\n/)) {\n const line = raw.trim();\n if (line.startsWith(\"```\")) { inFence = !inFence; continue; }\n if (inFence || line === \"\") continue;\n lastLine = line;\n }\n if (lastLine && lastLine.endsWith(\"?\")) return lastLine;\n return null;\n}\n\n/**\n * Pure: derive the Claude transcript JSONL path for a session. Claude stores\n * transcripts at ~/.claude/projects/<escaped-cwd>/<session_id>.jsonl, where the\n * cwd is escaped by replacing every non-alphanumeric char with \"-\" (verified\n * against the live ~/.claude/projects layout — e.g. /Users/q3labsadmin/.claude-mem\n * -> -Users-q3labsadmin--claude-mem). Returns null if sessionId or cwd is missing.\n * This is the layered fallback for #174 when the Stop payload omits transcript_path.\n */\nexport function deriveTranscriptPath(sessionId: string, cwd: string): string | null {\n if (!sessionId || !cwd) return null;\n const escaped = cwd.replace(/[^a-zA-Z0-9]/g, \"-\");\n return join(homedir(), \".claude\", \"projects\", escaped, `${sessionId}.jsonl`);\n}\n\n/**\n * I/O: read the LAST assistant message text from a Claude transcript JSONL file.\n * Kept separate from the pure detector so the detector stays trivially testable.\n * Never throws — returns null on any read/parse failure (the hook must exit 0).\n */\nfunction readLastAssistantText(transcriptPath: string): string | null {\n try {\n const raw = readFileSync(transcriptPath, \"utf-8\");\n const lines = raw.split(/\\r?\\n/);\n for (let i = lines.length - 1; i >= 0; i--) {\n const line = lines[i].trim();\n if (!line) continue;\n let entry: any;\n try { entry = JSON.parse(line); } catch { continue; }\n const isAssistant = entry?.type === \"assistant\" || entry?.message?.role === \"assistant\";\n if (!isAssistant) continue;\n const content = entry?.message?.content;\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n const txt = content\n .filter((b: any) => b?.type === \"text\" && typeof b.text === \"string\")\n .map((b: any) => b.text)\n .join(\"\\n\")\n .trim();\n return txt || null;\n }\n return null;\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * I/O: obtain the last-assistant text from a LAYERED source, first hit wins:\n * 0. payload.last_assistant_message — the field Claude puts the final assistant\n * text in DIRECTLY on the Stop payload (verified against claude-cli 2.1.156:\n * carries the full final message, including a trailing question, no I/O). This\n * is the primary source and the real #174 delivery fix — earlier diagnoses\n * chased transcript_path (which can be absent), but the message is right here.\n * 1. else payload.transcript_path (documented field, when present + readable);\n * 2. else the path derived from payload.session_id + cwd (defensive fallback for\n * older clients that omit both of the above).\n * cwd preference: payload.cwd (Claude hook contract) → SQUADRANT_CREW_CWD → cwd().\n * Best-effort: a null/miss from one source falls through to the next; never throws.\n */\nfunction resolveLastAssistantText(payload: unknown): string | null {\n const p = payload as any;\n const direct = p?.last_assistant_message;\n if (typeof direct === \"string\" && direct.trim()) return direct;\n const candidates: string[] = [];\n const tp = p?.transcript_path;\n if (typeof tp === \"string\" && tp) candidates.push(tp);\n const cwd = (typeof p?.cwd === \"string\" && p.cwd) ? p.cwd : (process.env.SQUADRANT_CREW_CWD || process.cwd());\n const derived = deriveTranscriptPath(p?.session_id, cwd);\n if (derived) candidates.push(derived);\n for (const path of candidates) {\n const text = readLastAssistantText(path);\n if (text != null) return text;\n }\n return null;\n}\n\n/**\n * Pure: render an AskUserQuestion tool call's `tool_input` (the raw arguments\n * Claude passes to the tool — `{ questions: [{ question, header, options,\n * multiSelect }] }`) into a human-readable prompt for CREW BLOCKED, carrying\n * both the question text AND its options (#560's proposal explicitly asks for\n * both — an option-less \"awaiting input\" placeholder can't be answered by\n * #562's answer channel or checked for staleness by #563).\n * Never throws; returns null when the shape doesn't match (caller must still\n * surface SOME text — see mapClaudeHookToEvent's PreToolUse case).\n */\nexport function formatAskUserQuestionPrompt(toolInput: unknown): string | null {\n const questions = (toolInput as { questions?: unknown } | null | undefined)?.questions;\n if (!Array.isArray(questions) || questions.length === 0) return null;\n const parts: string[] = [];\n for (const q of questions) {\n if (!q || typeof q !== \"object\") continue;\n const text = (q as any).question;\n if (typeof text !== \"string\" || !text.trim()) continue;\n const options = Array.isArray((q as any).options) ? (q as any).options : [];\n const labels = options\n .map((o: any) => (o && typeof o.label === \"string\" ? o.label.trim() : null))\n .filter((l: string | null): l is string => !!l);\n parts.push(labels.length > 0 ? `${text.trim()} (options: ${labels.join(\", \")})` : text.trim());\n }\n return parts.length > 0 ? parts.join(\" | \") : null;\n}\n\n/**\n * Map a Claude hook event name to a squadrant ControlEvent. Codifies the anti-#2576\n * invariant: NO Claude hook ever maps to `task.done`/`task.failed`.\n * PostToolUse/SubagentStop = resume-liveness only (task.progress). SessionEnd is\n * the lone terminalizing hook: the session is gone, so it maps to\n * task.session.ended → cancelled (#139) — silent, never done/failed.\n * Terminal `done`/`failed` come exclusively from explicit `squadrant crew signal`.\n *\n * Stop = turn boundary. It normally maps to task.turn.completed → awaiting-input\n * (stall-immune) so a captain reviewing output never trips a false CREW STALLED\n * (fixes #131). NARROW EXCEPTION #1 (#174): when the crew's last assistant message\n * ENDS with a direct question, Stop maps to task.blocked instead, surfacing the\n * question to the captain as CREW BLOCKED. The last-assistant text is obtained from\n * a LAYERED source (last_assistant_message on the payload → transcript_path →\n * derived path from session_id+cwd); the payload field is the primary, I/O-free\n * source. All transcript I/O is best-effort and never throws (hook must exit 0).\n *\n * Notification = Claude needs user attention. NARROW EXCEPTION #2\n * (#notification-hook): when the payload.message indicates a permission request\n * (isPermissionNotification), this maps to task.blocked instantly — bypassing the\n * ~20-30s relay poll. The relay poll remains as a fallback for opencode crews and\n * as a safety net; both may fire task.blocked for the same prompt, but the\n * state-machine idempotency (already-blocked → no-op, from #176) deduplicates.\n * Non-permission notifications (idle liveness) → task.progress. Missing/non-string\n * message → task.progress (never throws, hook must exit 0).\n *\n * PreToolUse = matcher-scoped to AskUserQuestion only (#560): the crew's own\n * hook set registers this ONLY for that tool (see MATCHED_EVENTS above), so in\n * practice tool_name is always \"AskUserQuestion\" here. Still checked\n * defensively — a config regression to a bare/unmatched PreToolUse must not\n * silently start reporting task.input.requested for every tool call. When it\n * IS AskUserQuestion, this maps to task.input.requested (NOT task.blocked —\n * task.blocked has no requestId field, and requestId is what\n * ctx.schedulePromotion in squadrantd.ts keys its answer-routing timer on;\n * task.input.requested already drives state-machine.ts → state 'blocked',\n * the CREW BLOCKED notification, and Telegram formatting) UNCONDITIONALLY —\n * even a malformed/unreadable tool_input still produces a generic fallback\n * question rather than falling through to null, because a detection path\n * that can silently fail to fire is the exact defect #560 exists to close.\n */\nexport function mapClaudeHookToEvent(\n event: string,\n payload: unknown,\n taskId: string,\n): ControlEvent | null {\n switch (event) {\n case \"PreToolUse\": {\n const toolName = (payload as any)?.tool_name;\n if (toolName !== \"AskUserQuestion\") return null;\n const question = formatAskUserQuestionPrompt((payload as any)?.tool_input)\n ?? \"crew opened an AskUserQuestion prompt (options unavailable)\";\n return { type: \"task.input.requested\", id: taskId, requestId: nextAskUserQuestionRequestId++, question };\n }\n case \"Stop\": {\n const text = resolveLastAssistantText(payload);\n const question = text ? detectTrailingQuestion(text) : null;\n if (question) {\n return { type: \"task.blocked\", id: taskId, reason: \"crew asked a question (auto-detected)\", question };\n }\n return { type: \"task.turn.completed\", id: taskId, turnId: \"hook-stop\" };\n }\n case \"Notification\": {\n const msg = (payload as any)?.message;\n if (typeof msg === \"string\" && isPermissionNotification(msg)) {\n return { type: \"task.blocked\", id: taskId, reason: \"crew awaiting permission (notification hook)\", question: msg };\n }\n return { type: \"task.progress\", id: taskId, note: \"notification\" };\n }\n case \"SessionEnd\":\n // #139: the session is GONE. NOT liveness — mapping this to task.progress\n // resumed a dead crew to 'working' (awaiting-input → working), where\n // nothing heartbeats and the watchdog false-stalled it ~budget later.\n // Terminalize the record instead (reducer: task.session.ended → cancelled).\n return { type: \"task.session.ended\", id: taskId };\n case \"SubagentStop\":\n case \"PostToolUse\":\n // The only resume-liveness hooks: PostToolUse fires after every tool call\n // mid-turn; SubagentStop fires while the parent still owns the turn.\n return { type: \"task.progress\", id: taskId, note: event.toLowerCase() };\n case \"UserPromptSubmit\":\n // #470: fires before Claude processes each prompt, including the first.\n // The reducer stamps firstTurnConfirmedAt only on the first occurrence;\n // subsequent submits (captain crew send follow-ups) are treated as liveness.\n return { type: \"task.first-turn.confirmed\", id: taskId };\n default:\n return null;\n }\n}\n\nexport const claudeInteractive: InteractiveHookAdapter = {\n provider: \"claude\",\n tier: \"strong\",\n injectHook(launchSpec) {\n // Claude reads merged ~/.config settings; nothing to add to argv here.\n // The settings merge is performed by the launcher (Task 18) before spawn.\n return launchSpec;\n },\n};\n","// src/control/headless/types.ts\nexport const HEADLESS_ERROR_TAIL = 2000;\n\nexport interface HeadlessResult {\n outcome: \"done\" | \"failed\";\n /** Always a string: result text, JSON-stringified non-string result, or raw stdout fallback. Becomes resultRef contents. */\n payload?: string;\n sessionId?: string;\n error?: string;\n exitCode?: number;\n parseWarning?: boolean;\n}\n\nexport interface HeadlessAdapter {\n provider: string;\n buildCommand(task: string, sessionId?: string): string[];\n parseResult(stdout: string, exitCode: number): HeadlessResult;\n}\n","// src/control/headless/claude.ts\nimport type { HeadlessAdapter } from \"./types.js\";\nimport { HEADLESS_ERROR_TAIL } from \"./types.js\";\n\nexport const claudeHeadless: HeadlessAdapter = {\n provider: \"claude\",\n buildCommand(task, sessionId) {\n const argv = [\"claude\", \"-p\", \"--output-format\", \"json\"];\n if (sessionId) argv.push(\"--resume\", sessionId);\n argv.push(task);\n return argv;\n },\n parseResult(stdout, exitCode) {\n if (exitCode !== 0) {\n return { outcome: \"failed\", exitCode, error: stdout.slice(-HEADLESS_ERROR_TAIL) };\n }\n try {\n const j = JSON.parse(stdout);\n if (j.is_error) return { outcome: \"failed\", error: String(j.result ?? \"is_error\"), sessionId: j.session_id };\n const payload = typeof j.result === \"string\" ? j.result : j.result == null ? \"\" : JSON.stringify(j.result);\n return { outcome: \"done\", sessionId: j.session_id, payload };\n } catch {\n return { outcome: \"done\", parseWarning: true, payload: stdout };\n }\n },\n};\n","// src/control/headless/opencode.ts\nimport type { HeadlessAdapter } from \"./types.js\";\nimport { HEADLESS_ERROR_TAIL } from \"./types.js\";\n\n// opencode `run` is used for one-shot; serve-session wiring is a later spec.\n// Process-exit is the done-signal here (foundational scope).\nexport const opencodeHeadless: HeadlessAdapter = {\n provider: \"opencode\",\n buildCommand(task, sessionId) {\n const argv = [\"opencode\", \"run\", \"--format\", \"json\"];\n if (sessionId) argv.push(\"--session\", sessionId);\n argv.push(task);\n return argv;\n },\n parseResult(stdout, exitCode) {\n if (exitCode !== 0) return { outcome: \"failed\", exitCode, error: stdout.slice(-HEADLESS_ERROR_TAIL) };\n try {\n const j = JSON.parse(stdout);\n const payload = typeof j.result === \"string\" ? j.result : JSON.stringify(j.result ?? stdout);\n return { outcome: \"done\", sessionId: j.sessionID ?? j.session_id, payload };\n } catch {\n return { outcome: \"done\", parseWarning: true, payload: stdout };\n }\n },\n};\n","// src/control/headless/codex.ts\nimport type { HeadlessAdapter } from \"./types.js\";\nimport { HEADLESS_ERROR_TAIL } from \"./types.js\";\n\nexport const codexHeadless: HeadlessAdapter = {\n provider: \"codex\",\n buildCommand(task, sessionId) {\n // Verified against codex-cli 0.130.0 `codex exec [OPTIONS] [PROMPT]`:\n // --json: JSONL events to stdout (valid).\n // --skip-git-repo-check: REQUIRED — the daemon spawns codex with a\n // non-trusted/non-git cwd under launchd; without it codex aborts with\n // \"Not inside a trusted directory and --skip-git-repo-check was not\n // specified.\" (real production failure, red-team/verify-on-implement).\n // resume is a SUBCOMMAND (`codex exec resume <id>`), NOT a `--session`\n // flag. Resume is unused in foundational scope (multi-turn/reply\n // deferred) — kept best-effort; flag order is verify-on-implement when\n // the interactive-wiring spec lands.\n // --sandbox workspace-write: codex exec defaults to a READ-ONLY sandbox,\n // so a crew could analyze/spec but never edit code (real prod finding:\n // codex bailed \"workspace is mounted read-only\"). workspace-write lets it\n // edit within its cwd (set by the launcher per-task) — NOT full-disk\n // (danger-full-access) which would be reckless for an autonomous agent.\n const opts = [\"--json\", \"--skip-git-repo-check\", \"--sandbox\", \"workspace-write\"];\n if (sessionId) return [\"codex\", \"exec\", \"resume\", sessionId, ...opts, task];\n return [\"codex\", \"exec\", ...opts, task];\n },\n parseResult(stdout, exitCode) {\n if (exitCode !== 0) return { outcome: \"failed\", exitCode, error: stdout.slice(-HEADLESS_ERROR_TAIL) };\n // codex result format undocumented; keep raw, never guess failure.\n return { outcome: \"done\", payload: stdout };\n },\n};\n","// src/control/headless/registry.ts\nimport type { HeadlessAdapter } from \"./types.js\";\nimport { claudeHeadless } from \"./claude.js\";\nimport { opencodeHeadless } from \"./opencode.js\";\nimport { codexHeadless } from \"./codex.js\";\n\nconst ADAPTERS: Record<string, HeadlessAdapter> = {\n claude: claudeHeadless,\n opencode: opencodeHeadless,\n codex: codexHeadless,\n};\n\nexport function getHeadlessAdapter(provider: string): HeadlessAdapter {\n const a = ADAPTERS[provider];\n if (!a) throw new Error(`no headless adapter for provider '${provider}'`);\n return a;\n}\n","// src/control/headless-launcher.ts\nimport type { spawn as nodeSpawn } from \"node:child_process\";\nimport type { ControlEvent } from \"@squadrant/shared\";\nimport { getHeadlessAdapter } from \"./headless/registry.js\";\n\nexport interface RunHeadlessOpts {\n provider: string;\n task: string;\n id: string;\n sessionId?: string;\n /**\n * Working dir for the spawned child. Headless previously inherited the\n * daemon's launchd cwd (`/`) — wrong for every provider, and the reason\n * codex could only do read-only work. Unset → inherit (back-compat).\n */\n cwd?: string;\n spawn: typeof nodeSpawn;\n emit: (e: ControlEvent) => void;\n /** Where to persist captured payload; defaults handled by caller (Task 17). */\n writeResult?: (id: string, payload: string) => string;\n}\n\nexport interface HeadlessHandle {\n result: Promise<void>;\n kill: () => void;\n}\n\n// Max bytes retained in the stdout/stderr capture buffers (oldest dropped).\nconst OUT_CAP = 4 * 1024 * 1024;\nconst ERR_CAP = 4 * 1024 * 1024;\n// Emit task.progress at most once per interval OR once per batch, whichever first.\nconst PROGRESS_INTERVAL_MS = 250;\nconst PROGRESS_CHUNK_BATCH = 50;\n\nexport function runHeadless(opts: RunHeadlessOpts): HeadlessHandle {\n const adapter = getHeadlessAdapter(opts.provider);\n const argv = adapter.buildCommand(opts.task, opts.sessionId);\n const child = opts.spawn(argv[0], argv.slice(1), {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n cwd: opts.cwd, // undefined → inherit daemon cwd (back-compat)\n });\n opts.emit({ type: \"task.started\", id: opts.id, pid: child.pid ?? undefined });\n\n let out = \"\";\n let err = \"\";\n\n // Debounce state — coalesces task.progress to avoid O(chunks) file writes.\n let lastProgressAt = 0;\n let chunksSinceProgress = 0;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n function flushProgress(): void {\n if (debounceTimer) { clearTimeout(debounceTimer); debounceTimer = null; }\n lastProgressAt = Date.now();\n chunksSinceProgress = 0;\n opts.emit({ type: \"task.progress\", id: opts.id }); // stdout activity = liveness\n }\n\n child.stdout?.on(\"data\", (d) => {\n out += String(d);\n if (out.length > OUT_CAP) out = out.slice(out.length - OUT_CAP);\n chunksSinceProgress++;\n const now = Date.now();\n if (chunksSinceProgress >= PROGRESS_CHUNK_BATCH || now - lastProgressAt >= PROGRESS_INTERVAL_MS) {\n flushProgress();\n } else if (!debounceTimer) {\n const delay = PROGRESS_INTERVAL_MS - (now - lastProgressAt);\n debounceTimer = setTimeout(() => { debounceTimer = null; flushProgress(); }, delay);\n }\n });\n child.stderr?.on(\"data\", (d) => {\n err += String(d);\n if (err.length > ERR_CAP) err = err.slice(err.length - ERR_CAP);\n });\n\n const result = new Promise<void>((resolve) => {\n child.once(\"error\", (e: Error) => {\n if (debounceTimer) { clearTimeout(debounceTimer); debounceTimer = null; }\n opts.emit({ type: \"task.failed\", id: opts.id, error: `spawn error: ${e.message}`, exitCode: undefined });\n resolve(); // never hang the daemon; resolve() is idempotent\n });\n child.on(\"close\", (code) => {\n // Flush any batched-but-not-yet-emitted activity before the terminal event.\n if (chunksSinceProgress > 0) flushProgress();\n else if (debounceTimer) { clearTimeout(debounceTimer); debounceTimer = null; }\n const parseInput = (code !== 0 && err) ? err : (out || err);\n const res = adapter.parseResult(parseInput, code ?? 0);\n if (res.outcome === \"failed\") {\n opts.emit({ type: \"task.failed\", id: opts.id, error: res.error ?? \"non-zero exit\", exitCode: res.exitCode });\n } else {\n const ref = opts.writeResult ? opts.writeResult(opts.id, res.payload ?? \"\") : \"\";\n opts.emit({ type: \"task.done\", id: opts.id, resultRef: ref, parseWarning: res.parseWarning });\n }\n resolve();\n });\n });\n\n return { result, kill: () => child.kill(\"SIGTERM\") };\n}\n","import { execFile, execFileSync } from \"node:child_process\";\nimport type { RuntimeDriver, RuntimeProbeResult, RuntimeSpawnOptions, WorkspaceRef, PaneRef, RuntimePaneOptions } from \"./types.js\";\nimport { resolveCmuxBin } from \"@squadrant/shared\";\nimport { checkToolCompat } from \"@squadrant/shared\";\nimport { compatManifest } from \"@squadrant/shared\";\n\n// 15s — cmux operations are local IPC (sub-50ms normally). 15s covers unusual\n// system load or a momentarily stuck cmux server without causing the captain\n// blindness that an unbounded hang would (see #209).\nexport const CMUX_TIMEOUT = 15_000;\n\nexport class CmuxTimeoutError extends Error {\n constructor(cmd: string) {\n super(`cmux timeout after ${CMUX_TIMEOUT}ms on: ${cmd}`);\n this.name = \"CmuxTimeoutError\";\n }\n}\n\nimport { DeferDelivery } from \"@squadrant/core\";\n\n/** True when running inside a cmux workspace (CMUX_WORKSPACE_ID is set). */\nexport function isInsideCmux(): boolean {\n return !!process.env.CMUX_WORKSPACE_ID;\n}\n\n// Synchronous cmux invocation for select-workspace / current-workspace calls\n// not yet abstracted behind RuntimeDriver. Uses execFileSync (no shell) with\n// stderr piped so cmux diagnostic messages (e.g. \"Pane not found\") don't leak\n// to the parent terminal. Returns trimmed stdout.\nexport function cmuxLocal(args: string[]): string {\n return execFileSync(resolveCmuxBin(), args, {\n encoding: \"utf-8\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n timeout: CMUX_TIMEOUT,\n }).trim();\n}\n\n// Invoke cmux with an argv array and NO shell. Every element (especially crew\n// prompt text passed through send/send-to-surface) reaches cmux as a single\n// literal argument — backticks, $(), quotes are never parsed. See #118.\n// Async to avoid blocking the Node.js event loop during daemon timer ticks.\nfunction cmux(args: string[]): Promise<string> {\n return new Promise((resolve, reject) => {\n execFile(\n resolveCmuxBin(),\n args,\n // CMUX_QUIET=1 silences cmux 0.64's one-time deprecation hints (e.g. the\n // \"list-workspaces is now an alias for cmux workspace list\" notice). Those\n // notices print to the command's stdout and would otherwise pollute the\n // output we parse. Inherit the rest of the environment unchanged.\n { encoding: \"utf-8\", timeout: CMUX_TIMEOUT, env: { ...process.env, CMUX_QUIET: \"1\" } },\n (err, stdout) => {\n if (err) {\n reject((err as NodeJS.ErrnoException).code === \"ETIMEDOUT\"\n ? new CmuxTimeoutError(args.join(\" \"))\n : err);\n return;\n }\n resolve((stdout as string).trim());\n },\n );\n });\n}\n\n// Same as cmux() but writes `input` to the child's stdin before it exits —\n// used by showPatch (#604) for cmux's stdin-based diff mode (`cmux diff -`).\n// execFile's callback form still returns the underlying ChildProcess\n// synchronously, so its stdin is available immediately.\nfunction cmuxStdin(args: string[], input: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = execFile(\n resolveCmuxBin(),\n args,\n { encoding: \"utf-8\", timeout: CMUX_TIMEOUT, env: { ...process.env, CMUX_QUIET: \"1\" } },\n (err, stdout) => {\n if (err) {\n reject((err as NodeJS.ErrnoException).code === \"ETIMEDOUT\"\n ? new CmuxTimeoutError(args.join(\" \"))\n : err);\n return;\n }\n resolve((stdout as string).trim());\n },\n );\n child.stdin!.end(input);\n });\n}\n\n// Shape of `cmux workspace list --json` (cmux 0.64.16). Only the fields we\n// consume are typed; everything else in the payload is ignored.\ninterface CmuxWorkspaceListJson {\n workspaces?: Array<{\n ref?: string;\n custom_title?: string | null;\n has_custom_title?: boolean;\n current_directory?: string | null;\n }>;\n}\n\n// Shape of `cmux tree --json` (cmux 0.64.16). Surfaces nest as\n// windows[].workspaces[].panes[].surfaces[]; only consumed fields are typed.\ninterface CmuxTreeJson {\n windows?: Array<{\n workspaces?: Array<{\n ref?: string;\n panes?: Array<{\n surfaces?: Array<{ ref?: string; surface_ref?: string; title?: string | null }>;\n }>;\n }>;\n }>;\n}\n\n// Parse `cmux workspace list --json` into WorkspaceRefs. Replaces the old\n// regex over the human-readable `list-workspaces` text (audit B2). The display\n// name is the workspace's custom title when set (byte-identical to what the\n// text form showed, e.g. \"⚓ squadrant-captain\" — this is what squadrant matches\n// captains by), falling back to the cwd for untitled workspaces.\nfunction parseList(output: string): WorkspaceRef[] {\n let parsed: CmuxWorkspaceListJson;\n try {\n parsed = JSON.parse(output) as CmuxWorkspaceListJson;\n } catch {\n return [];\n }\n const refs: WorkspaceRef[] = [];\n for (const ws of parsed.workspaces ?? []) {\n if (!ws.ref) continue;\n refs.push({\n id: ws.ref,\n name: (ws.has_custom_title && ws.custom_title) ? ws.custom_title : (ws.current_directory ?? ws.ref),\n status: \"running\",\n });\n }\n return refs;\n}\n\n// cmux `send` treats \\n, \\r (and \\t) as Enter/Tab keystrokes, so any newline in a\n// multi-line message would submit it line-by-line. Collapse all newline/CR/tab\n// (real bytes AND literal backslash-escapes) to single spaces so the whole message\n// is delivered as one line, then the explicit send-key Enter submits it once.\nexport function sanitizeForCmuxSend(text: string): string {\n return text\n .replace(/\\\\[nrt]/g, \" \")\n .replace(/[\\n\\r\\t]+/g, \" \")\n .replace(/ {2,}/g, \" \")\n .trim();\n}\n\n/**\n * Extract the in-progress draft from a cmux read-screen capture (#258 / #268).\n * Scans from the bottom of the screen so history lines that contain `> ` are\n * ignored; only the actual input area (the last matching line) is returned.\n * Handles both `>` (synthetic/test) and `❯` (U+276F, the real Claude Code\n * prompt character) as the input caret. The real prompt is followed by a\n * non-breaking space (U+00A0); JS `\\s` covers it, so `\\s+` matches either.\n * Also handles box-drawing `│ ❯ text │` variants.\n *\n * Three-state return (#268):\n * \"draft text\" — input box found with content → caller must DEFER\n * \"\" — input box positively confirmed empty → caller may DELIVER\n * null — HR boundaries not found (overlay/menu/scrolled) → caller must DEFER\n */\nexport function parseDraftFromScreen(screen: string): string | null {\n // Empty screen means the input box is definitely not visible — defer (#268).\n if (!screen) return null;\n const lines = screen.split(/\\r?\\n/);\n\n // Locate the last two HR lines (runs of U+2500 ─) — they are the bottom and top\n // boundaries of the live input box. Everything above the top HR is transcript\n // content and is never scanned, preventing sent user messages with a ❯/> prefix\n // from being mistaken for the live draft (#258).\n const HR_RE = /^\\s*─{10,}\\s*$/;\n let bottomHR = -1;\n let topHR = -1;\n for (let i = lines.length - 1; i >= 0; i--) {\n if (HR_RE.test(lines[i])) {\n if (bottomHR === -1) {\n bottomHR = i;\n } else {\n topHR = i;\n break;\n }\n }\n }\n\n // Can't locate both boundaries — input box not visible (overlay/menu/scrolled\n // transcript). Defer so keystrokes never land in an unknown UI state (#268).\n if (topHR === -1) return null;\n\n // Extract content lines strictly between the two HRs (the live input box only).\n const inputLines = lines.slice(topHR + 1, bottomHR);\n\n for (const line of inputLines) {\n let extracted: string | undefined;\n // Box-drawing input line: │ [>❯] text │\n const boxMatch = line.match(/│\\s*[>❯]\\s+(.*?)\\s*│/);\n if (boxMatch) {\n extracted = boxMatch[1].trim();\n } else {\n // Plain input line — allow empty content after the prompt glyph\n const plainMatch = line.match(/^\\s*[>❯]\\s*(.*)$/);\n if (plainMatch) extracted = plainMatch[1].trim();\n }\n if (extracted !== undefined) {\n // Heuristic #1 — Leading cursor glyph (▌/█) at position 0.\n // CC renders its input cursor via native ANSI terminal positioning, NOT as a ▌ cell\n // character: a live cmux read-screen of an idle CC session with cursor at position 0\n // yields ❯\\xa0 with no ▌ (confirmed by 258-parse-bug-fixture.txt L24 and a fresh\n // crew session capture). Therefore ▌ at the start cannot arise from the user moving\n // the cursor to the beginning of real typed text — it only appears when CC itself\n // renders a UI placeholder at that position (#294). Safe to treat as empty. (#297)\n if (/^[▌█▔▎▏▌█]/.test(extracted)) continue;\n\n // Strip terminal cursor glyphs (▌, █, etc.) that trail the caret position\n const draft = extracted.replace(/\\s*[▌█▔▎▏▌█]+\\s*$/, \"\").trim();\n\n // Claude Code UI placeholder: appears in Working state when input is locked\n // (user cannot type). \"Press [key] to [action]\" strings are UI instructions\n // shown as ghost suggestions — never real user-typed content (#294).\n if (/^Press\\s+(?:up|down|left|right|enter|escape|esc|tab|any\\s+key|ctrl|shift|alt)\\s+to\\s+/i.test(draft)) continue;\n\n if (draft) return draft;\n }\n }\n\n return \"\";\n}\n\n/**\n * True when the screen contains a real Claude Code input box — two HR boundaries\n * AND at least one line between them with the CC prompt glyph (❯ or >). This\n * distinguishes the CC input box from the claude-mem startup banner, which can\n * produce HR-bounded regions WITHOUT a prompt glyph and stabilise before CC\n * renders its own TUI. parseDraftFromScreen returns \"\" for both cases (two HRs\n * found, no ❯ inside), so !==null does not distinguish them (#466-single fix).\n */\nexport function hasCCInputBox(screen: string): boolean {\n if (!screen) return false;\n const lines = screen.split(/\\r?\\n/);\n const HR_RE = /^\\s*─{10,}\\s*$/;\n let bottomHR = -1;\n let topHR = -1;\n for (let i = lines.length - 1; i >= 0; i--) {\n if (HR_RE.test(lines[i])) {\n if (bottomHR === -1) bottomHR = i;\n else { topHR = i; break; }\n }\n }\n if (topHR === -1) return false;\n return lines.slice(topHR + 1, bottomHR).some((l) => /[>❯]/.test(l));\n}\n\n/**\n * True when the HR-bounded region is an AskUserQuestion / permission-approval\n * SELECTION MODAL rather than the genuine CC input box (#484). Both draw their\n * own pair of ── borders and highlight the selected option with the same ❯\n * glyph as a real draft, so neither parseDraftFromScreen nor hasCCInputBox can\n * tell them apart — a live-captured frame confirms parseDraftFromScreen\n * returns the highlighted option's own label (\"1. Red\"), not \"\" or null (see\n * docs/reports/484-askuserquestion-fixture.txt). CC renders every selectable\n * option (AskUserQuestion AND the Bash-approval picker) as a \"N. Label\" line,\n * which a real typed draft or ghost/hint placeholder never does — that's the\n * positive signal used here.\n */\nexport function hasModalOptionList(screen: string): boolean {\n if (!screen) return false;\n const lines = screen.split(/\\r?\\n/);\n const HR_RE = /^\\s*─{10,}\\s*$/;\n let bottomHR = -1;\n let topHR = -1;\n for (let i = lines.length - 1; i >= 0; i--) {\n if (HR_RE.test(lines[i])) {\n if (bottomHR === -1) bottomHR = i;\n else { topHR = i; break; }\n }\n }\n if (topHR === -1) return false;\n return lines.slice(topHR + 1, bottomHR).some((l) => /^\\s*\\d+\\.\\s/.test(l));\n}\n\n/**\n * Extract the RAW input-box content for the #302 buffer-liveness probe — all\n * content lines between the last two HRs, joined, with the prompt glyph and any\n * trailing cursor glyph stripped (but NOT the #294 ghost heuristics: the probe\n * needs the literal rendered text to diff before/after a backspace). Returns\n * null if the box boundaries aren't visible (overlay/scroll). Unlike\n * parseDraftFromScreen this captures EVERY content line, so a multi-line draft's\n * change on its last line is not missed.\n */\nexport function readInputBoxRaw(\n screen: string,\n opts?: { trim?: boolean },\n): string | null {\n if (!screen) return null;\n const lines = screen.split(/\\r?\\n/);\n const HR_RE = /^\\s*─{10,}\\s*$/;\n let bottomHR = -1;\n let topHR = -1;\n for (let i = lines.length - 1; i >= 0; i--) {\n if (HR_RE.test(lines[i])) {\n if (bottomHR === -1) bottomHR = i;\n else { topHR = i; break; }\n }\n }\n if (topHR === -1) return null;\n const parts: string[] = [];\n for (const line of lines.slice(topHR + 1, bottomHR)) {\n let s = line.replace(/│/g, \" \"); // drop box-drawing borders\n s = s.replace(/^\\s*[>❯]\\s?/, \"\"); // drop the leading prompt glyph + one space\n s = s.replace(/\\s*[▌█▔▎▏]+\\s*$/, \"\"); // drop a trailing cursor glyph\n parts.push(s);\n }\n const joined = parts.join(\"\");\n // Default: trim trailing whitespace. Pass { trim: false } to preserve it —\n // used by the probe branch to detect whether a backspace was a no-op (#258).\n return opts?.trim === false ? joined : joined.replace(/\\s+$/, \"\");\n}\n\n// #292: Claude Code renders a persistent bottom status block once its TUI is past\n// the cold-init splash — the auto-mode indicator (⏵⏵), the context meter\n// (\"Ctx Used\"), the shortcuts hint, or the accept-edits toggle. Absence of all of\n// these means we're still on the loading/splash screen, where keystrokes are\n// silently dropped (#235). Grounded in docs/reports/258-parse-bug-fixture.txt.\nconst CC_INITIALIZED_RE = /⏵⏵|Ctx Used|for shortcuts|accept edits/i;\n\n// A live turn shows a working spinner. The whimsical verb (\"Working…\",\n// \"Cerebrating…\", \"Crunched…\") varies across versions, so we key on stable\n// markers instead. CRUCIAL: a turn is NOT always streaming tokens — during a\n// tool wait (e.g. the shell commands the captain startup checklist runs first)\n// the spinner reads \"✻ Crunched for 27s · 1 shell still running\", which carries\n// NO token-down-counter and no \"esc to interrupt\". Keying only on those two\n// (the original #292 mistake) misread a shell-waiting captain as \"idle\", so the\n// startup-prompt loop re-sent on every poll → 3 duplicate startup runs. We now\n// also match the shell-running hint and the in-parens elapsed timer (\"(4s\",\n// \"(1m 4s\") — both confined to the live spinner line, never on an idle,\n// input-ready screen. Grounded in docs/reports/258-parse-bug-fixture.txt\n// (line 4: shell-wait, no counter; line 22: token-stream).\nconst CC_WORKING_RE = /↓\\s*[\\d.]+\\s*k?\\s*tokens?\\b|esc to interrupt|\\bshell still running\\b|·\\s*\\d+\\s*shell\\b|\\(\\d+m?\\s*\\d*s\\b/i;\n\n/**\n * Classify a captain surface's read-screen into the three states #292's\n * deterministic startup delivery needs:\n * \"loading\" — splash / cold-init; keystrokes would be dropped, do not send yet.\n * \"idle\" — TUI up and accepting input; safe to deliver the startup prompt.\n * \"working\" — a turn is in flight; sending would queue a DUPLICATE startup run.\n * \"working\" is checked first so an active spinner above an (empty) input box wins.\n */\nexport function classifyStartupSurface(screen: string): \"loading\" | \"idle\" | \"working\" {\n if (CC_WORKING_RE.test(screen)) return \"working\";\n if (CC_INITIALIZED_RE.test(screen)) return \"idle\";\n return \"loading\";\n}\n\n// #339 instrumentation gate. The DONE→captain submit is a text burst then a\n// SEPARATE send-key Enter (two distinct socket writes); intermittently the Enter\n// lands as a newline instead of a submit, stranding the payload in the input box.\n// Root-causing needs ONE real frame in the wild. Gated behind SQUADRANT_DEBUG_SEND\n// so it is a strict no-op — zero extra reads, zero latency — when unset.\nexport function sendDebugEnabled(): boolean {\n return !!process.env.SQUADRANT_DEBUG_SEND;\n}\n\n// Classify a post-send input-box read into a submit verdict for #339:\n// \"submitted\" — box empty after Enter (the payload left the input box)\n// \"stuck\" — box still holds the payload (Enter inserted a newline, no submit)\n// \"box-gone\" — box not visible post-send (overlay/scroll — inconclusive)\n// \"unknown\" — box has unrelated content (a fresh draft / next turn rendered)\nexport function classifySendOutcome(payload: string, postBox: string | null): string {\n if (postBox === null) return \"box-gone\";\n if (postBox === \"\") return \"submitted\";\n if (postBox === payload || postBox.includes(payload)) return \"stuck\";\n return \"unknown\";\n}\n\nexport type DraftLiveness = \"real-draft\" | \"no-draft\" | \"inconclusive\";\n\n/**\n * Pure probe-liveness decision (#258 fix).\n * Given `before` / `after` raw box readings (from readInputBoxRaw) around a\n * single backspace, classify whether a real user draft was present.\n *\n * Three-way result — caller mapping:\n * \"real-draft\" → restore last grapheme, throw DeferDelivery\n * \"no-draft\" → deliver (ghost positively confirmed dismissed to empty)\n * \"inconclusive\" → throw DeferDelivery (bias: protect human, delay bot)\n *\n * \"Inconclusive\" covers: after===before (ghost-invariant OR trailing-space\n * trim makes them equal — indistinguishable), null after-read (timing/overlay),\n * and any other mismatch not explained by grapheme removal. The old code treated\n * all of these as \"no-draft\" (fall-through to deliver), which is the #258 clobber.\n */\nexport function classifyDraftLiveness(\n before: string | null,\n after: string | null,\n): DraftLiveness {\n if (before === null || after === null) return \"inconclusive\";\n\n // Ghost dismissed to empty → positively confirmed no real draft remains.\n if (after === \"\") return \"no-draft\";\n\n // Grapheme-aware last-grapheme-removal check (Node 16+ / Intl.Segmenter).\n // Removes the last grapheme cluster from `before`, trims trailing whitespace\n // (matching what readInputBoxRaw does on the re-rendered screen), then\n // compares to `after`. Handles emoji, wide chars, and combining sequences\n // that slice(0,-1) gets wrong by removing only one UTF-16 code unit.\n if (before.length > 0) {\n const segs = [...new Intl.Segmenter().segment(before)];\n const expected = segs\n .slice(0, -1)\n .map((s) => s.segment)\n .join(\"\")\n .replace(/\\s+$/, \"\");\n if (after === expected) return \"real-draft\";\n }\n\n // Everything else — after===before (ghost-invariant or trailing-space trim),\n // arbitrary mismatch, or other ambiguity — is inconclusive. Defer to protect\n // the human; a correctly empty box always produces after===\"\" (caught above).\n return \"inconclusive\";\n}\n\nexport function createCmuxDriver(): RuntimeDriver {\n return {\n name: \"cmux\",\n\n async probe(): Promise<RuntimeProbeResult> {\n try {\n const version = await cmux([\"--version\"]);\n const warn = checkToolCompat(\"cmux\", version, compatManifest.tools.cmux);\n if (warn) process.stderr.write(`[squadrant] ${warn}\\n`);\n return { installed: true, version };\n } catch {\n return { installed: false, version: \"\" };\n }\n },\n\n async list(): Promise<WorkspaceRef[]> {\n try {\n // --json: structured output (B2); --id-format refs: ids as\n // workspace:N refs, not numeric (from #325). Both are required.\n return parseList(await cmux([\"workspace\", \"list\", \"--json\", \"--id-format\", \"refs\"]));\n } catch {\n return [];\n }\n },\n\n async status(nameOrId: string): Promise<WorkspaceRef | null> {\n const refs = await this.list();\n const hit = refs.find((r) => r.name === nameOrId || r.id === nameOrId);\n return hit ?? null;\n },\n\n async spawn(opts: RuntimeSpawnOptions): Promise<WorkspaceRef> {\n const newWorkspaceArgs = [\"workspace\", \"create\", \"--command\", opts.command];\n if (opts.workdir) newWorkspaceArgs.push(\"--cwd\", opts.workdir);\n const output = await cmux(newWorkspaceArgs);\n const id = output.match(/workspace:\\d+/)?.[0] || output.split(/\\s+/).pop() || \"\";\n if (!id) {\n throw new Error(`cmux spawn did not return a workspace id: ${output}`);\n }\n await cmux([\"workspace\", \"rename\", id, \"--title\", opts.name]);\n // Rename the initial tab to the workspace name so send() can route to it\n let initialSurface: string | undefined;\n try {\n const tree = await cmux([\"tree\", \"--workspace\", id, \"--id-format\", \"refs\"]);\n const m = tree.match(/surface\\s+(surface:\\d+)\\s+\\[\\w+\\]\\s+\"([^\"]*)\"/);\n if (m) {\n initialSurface = m[1];\n await cmux([\"rename-tab\", \"--workspace\", id, \"--surface\", m[1], opts.name]);\n }\n } catch { /* rename is best-effort */ }\n if (opts.pinToTop) {\n try {\n await cmux([\"workspace-action\", \"--workspace\", id, \"--action\", \"pin\"]);\n } catch { /* workspace may not be pinned — proceed to close regardless */ }\n if (initialSurface) {\n try {\n await cmux([\"tab-action\", \"--workspace\", id, \"--surface\", initialSurface, \"--action\", \"pin\"]);\n } catch { /* tab pin is best-effort */ }\n }\n }\n return { id, name: opts.name, status: \"running\" };\n },\n\n async send(ref: string, message: string): Promise<void> {\n // Route to the tab named after the workspace (e.g. \":captain\" tab) so\n // messages don't land on a focused crew tab by mistake. Fall back to\n // workspace-level send when no matching tab is found.\n const allRefs = await this.list();\n const ws = allRefs.find((r) => r.id === ref);\n if (ws) {\n try {\n const surfaces = await this.listSurfaces(ws.id);\n const target = surfaces.find((s) => s.title === ws.name);\n if (target) {\n await cmux([\"send\", \"--workspace\", ws.id, \"--surface\", target.surfaceId, sanitizeForCmuxSend(message)]);\n await cmux([\"send-key\", \"--workspace\", ws.id, \"--surface\", target.surfaceId, \"Enter\"]);\n return;\n }\n } catch { /* fall through to default */ }\n }\n await cmux([\"send\", \"--workspace\", ref, sanitizeForCmuxSend(message)]);\n await cmux([\"send-key\", \"--workspace\", ref, \"Enter\"]);\n },\n\n async sendKey(ref: string, key: string): Promise<void> {\n await cmux([\"send-key\", \"--workspace\", ref, key]);\n },\n\n async readScreen(ref: string): Promise<string> {\n try {\n return await cmux([\"read-screen\", \"--workspace\", ref]);\n } catch {\n return \"\";\n }\n },\n\n async stop(ref: string): Promise<void> {\n // cmux 0.64.16 refuses to close a pinned workspace. Unpin first so that\n // squadrant launch --fresh works even when the captain workspace is pinned.\n try {\n await cmux([\"workspace-action\", \"--workspace\", ref, \"--action\", \"unpin\"]);\n } catch { /* workspace may not be pinned — proceed to close regardless */ }\n try {\n await cmux([\"workspace\", \"close\", ref]);\n } catch { /* may already be closed */ }\n },\n\n async newPane(opts: RuntimePaneOptions): Promise<PaneRef> {\n // #295 / audit A1+B3: a crew tab must never steal focus from the captain.\n // cmux 0.64.16's new-surface and new-pane both DEFAULT to --focus false,\n // so we pass it explicitly (intent + resilience if the default changes)\n // and create the surface focus-neutrally. This REPLACES the old\n // snapshot-then-move-surface refocus dance, which depended on the fragile\n // \"tree order == array index\" invariant that the 0.64 freeform canvas +\n // staggered restore broke — risking a focus-steal regression.\n const cmd = opts.direction === \"tab\"\n ? [\"new-surface\", \"--type\", \"terminal\", \"--workspace\", opts.workspaceId, \"--focus\", \"false\"]\n : [\"new-pane\", \"--type\", \"terminal\", \"--direction\", opts.direction, \"--workspace\", opts.workspaceId, \"--focus\", \"false\"];\n const output = await cmux(cmd);\n const surfaceId = output.match(/surface:\\d+/)?.[0];\n if (!surfaceId) {\n const verb = opts.direction === \"tab\" ? \"new-surface\" : \"new-pane\";\n throw new Error(`cmux ${verb} did not return a surface id: ${output}`);\n }\n if (opts.title) {\n try {\n await cmux([\"rename-tab\", \"--workspace\", opts.workspaceId, \"--surface\", surfaceId, \"--title\", opts.title]);\n } catch { /* rename is best-effort */ }\n }\n return { workspaceId: opts.workspaceId, surfaceId };\n },\n\n async closePane(pane: PaneRef): Promise<void> {\n try {\n await cmux([\"close-surface\", \"--workspace\", pane.workspaceId, \"--surface\", pane.surfaceId]);\n } catch { /* may already be closed */ }\n },\n\n async sendToPane(pane: PaneRef, message: string): Promise<void> {\n await this.pasteToPane(pane, message);\n await this.sendKeyToPane(pane, \"Enter\");\n },\n\n async pasteToPane(pane: PaneRef, text: string): Promise<void> {\n await cmux([\"send\", \"--workspace\", pane.workspaceId, \"--surface\", pane.surfaceId, sanitizeForCmuxSend(text)]);\n },\n\n async sendKeyToPane(pane: PaneRef, key: string): Promise<void> {\n await cmux([\"send-key\", \"--workspace\", pane.workspaceId, \"--surface\", pane.surfaceId, key]);\n },\n\n async readPaneScreen(pane: PaneRef): Promise<string> {\n try {\n return await cmux([\"read-screen\", \"--workspace\", pane.workspaceId, \"--surface\", pane.surfaceId]);\n } catch {\n return \"\";\n }\n },\n\n async spawnInjector(opts: {\n captainWorkspace: WorkspaceRef;\n command: string;\n title?: string;\n placement: \"background\" | \"visible\";\n }): Promise<PaneRef> {\n // Both placements use a background tab (new-surface) in the captain's\n // existing pane — full-height, NO split. A split-pane is wrong here:\n // cmux 0.62.2 has no resize/hide verb, so a `new-pane` split can never be\n // shrunk and stays an ugly full-height 50/50 split forever (#117). The\n // relay still runs as a cmux descendant in the same workspace, preserving\n // the in-cmux delivery requirement (#112).\n //\n // cmux 0.64.16's new-surface DEFAULTS to --focus false, so \"background\"\n // passes --focus false and the relay tab is created without ever stealing\n // focus from the captain — no snapshot-then-move-surface refocus dance\n // (audit A1+B3; the 0.64 freeform canvas broke the old tree-order==index\n // assumption it relied on). \"visible\" passes --focus true to leave the\n // debug tab focused for ergonomics.\n const wsId = opts.captainWorkspace.id;\n const focus = opts.placement === \"visible\" ? \"true\" : \"false\";\n const output = await cmux([\"new-surface\", \"--type\", \"terminal\", \"--workspace\", wsId, \"--focus\", focus]);\n const surfaceId = output.match(/surface:\\d+/)?.[0];\n if (!surfaceId) {\n throw new Error(`cmux spawnInjector did not return a surface id: ${output}`);\n }\n if (opts.title) {\n try {\n await cmux([\"rename-tab\", \"--workspace\", wsId, \"--surface\", surfaceId, \"--title\", opts.title]);\n } catch { /* rename is best-effort */ }\n }\n await cmux([\"send\", \"--workspace\", wsId, \"--surface\", surfaceId, opts.command]);\n await cmux([\"send-key\", \"--workspace\", wsId, \"--surface\", surfaceId, \"Enter\"]);\n return { workspaceId: wsId, surfaceId, title: opts.title };\n },\n\n async sendToSurface(surface: PaneRef, text: string, opts?: { probe?: boolean }): Promise<void> {\n const ws = surface.workspaceId;\n const sf = surface.surfaceId;\n const deliver = async () => {\n // #339 debug-gated instrumentation. When OFF this is the exact two-write\n // submit it always was (no extra reads, no latency). When ON we capture\n // one real frame: the input box BEFORE the send, the payload, and the box\n // AFTER the Enter — so a stranded submit can be told apart from a clean one.\n const dbg = sendDebugEnabled();\n let preBox: string | null = null;\n if (dbg) {\n try {\n preBox = readInputBoxRaw(await cmux([\"read-screen\", \"--workspace\", ws, \"--surface\", sf]));\n } catch { /* unreadable — leave preBox null, logged as such */ }\n }\n const payload = sanitizeForCmuxSend(text);\n await cmux([\"send\", \"--workspace\", ws, \"--surface\", sf, payload]);\n await cmux([\"send-key\", \"--workspace\", ws, \"--surface\", sf, \"Enter\"]);\n // Post-send read-back is READ-ONLY — never a re-send — so it can NEVER\n // double-submit (the #339 constraint). It only observes whether the box\n // still holds the payload (Enter mis-landed) or is empty (submit took).\n if (dbg) {\n let postBox: string | null = null;\n try {\n postBox = readInputBoxRaw(await cmux([\"read-screen\", \"--workspace\", ws, \"--surface\", sf]));\n } catch { /* unreadable — leave postBox null, classified box-gone */ }\n const verdict = classifySendOutcome(payload, postBox);\n process.stderr.write(`[squadrant] send-debug ${JSON.stringify({ surface: sf, verdict, payload, preBox, postBox })}\\n`);\n }\n };\n\n // #258/#268 Approach B: deliver only when the captain's input is positively\n // confirmed empty. null = box not visible (overlay/menu/scroll) → always defer.\n let screen = \"\";\n try {\n screen = await cmux([\"read-screen\", \"--workspace\", ws, \"--surface\", sf]);\n } catch { /* screen unreadable — parseDraftFromScreen(\"\") → null → defer below */ }\n const draft = parseDraftFromScreen(screen);\n\n // null = box not confirmed visible → never keystroke into an overlay (#268).\n if (draft === null) throw new DeferDelivery(null, \"no-box\");\n\n // #484: an AskUserQuestion / permission-approval SELECTION MODAL — never\n // deliver into it, regardless of what parseDraftFromScreen returned or\n // whether this call is probe-escalated. Checked before the probe branch\n // below because the probe's backspace-no-op check can't tell \"ghost\n // placeholder\" apart from \"selection list\" (backspace is a no-op\n // against both) and would otherwise call deliver(), typing the message\n // and pressing Enter into the picker — auto-confirming whichever option\n // is highlighted.\n if (hasModalOptionList(screen)) throw new DeferDelivery(null, \"modal\");\n\n // Empty input — nothing to protect, deliver directly.\n if (draft === \"\") { await deliver(); return; }\n\n // A draft is present. On the hot path (no probe) we NEVER keystroke — we\n // defer and carry the content so the relay can track stability (#302).\n if (!opts?.probe) throw new DeferDelivery(draft);\n\n // #302 buffer-liveness probe. classifyDraftLiveness decides from the\n // before/after box readings whether a real draft is present (#258 fix).\n // Capture both trimmed (for classification) and untrimmed (for no-op\n // detection in the inconclusive branch) before sending the backspace.\n const before = readInputBoxRaw(screen);\n const rawBefore = readInputBoxRaw(screen, { trim: false });\n await cmux([\"send-key\", \"--workspace\", ws, \"--surface\", sf, \"backspace\"]);\n // 50ms settle: give the TUI time to re-render before reading back the\n // result. Without this, a too-fast read may still show the pre-backspace\n // content, producing a false after===before (timing-race #258).\n await new Promise<void>((r) => setTimeout(r, 50));\n let afterScreen = \"\";\n try {\n afterScreen = await cmux([\"read-screen\", \"--workspace\", ws, \"--surface\", sf]);\n } catch { /* unreadable — after stays \"\", readInputBoxRaw → null → inconclusive → defer */ }\n const after = readInputBoxRaw(afterScreen);\n const rawAfter = readInputBoxRaw(afterScreen, { trim: false });\n\n const liveness = classifyDraftLiveness(before, after);\n if (liveness === \"real-draft\") {\n // Confirmed real draft. Restore the last grapheme our probe removed\n // (grapheme-aware — not slice(-1) which breaks emoji, #258), then defer.\n const segs = before ? [...new Intl.Segmenter().segment(before)] : [];\n const lastGrapheme =\n segs.length > 0 ? segs[segs.length - 1].segment : before!.slice(-1);\n await cmux([\"send\", \"--workspace\", ws, \"--surface\", sf, lastGrapheme]);\n throw new DeferDelivery(draft);\n }\n if (liveness === \"no-draft\") {\n // Ghost positively dismissed to empty — safe to deliver.\n await deliver(); return;\n }\n // 'inconclusive': could be ghost-invariant (true no-op) or trailing-space\n // draft (backspace removed the space but trim masked it). Distinguish by\n // comparing the UNTRIMMED raw content.\n if (rawBefore !== null && rawAfter !== null) {\n if (rawBefore !== rawAfter) {\n // Raw changed: real trailing-space (or similar) draft — backspace consumed\n // a real character. Restore the removed grapheme then defer (#258).\n const segs = [...new Intl.Segmenter().segment(rawBefore)];\n const lastGrapheme =\n segs.length > 0 ? segs[segs.length - 1].segment : rawBefore.slice(-1);\n await cmux([\"send\", \"--workspace\", ws, \"--surface\", sf, lastGrapheme]);\n throw new DeferDelivery(draft);\n }\n // rawBefore === rawAfter: backspace was a true no-op — the box holds ghost/hint\n // text (non-editable). A real draft ALWAYS changes under backspace. Deliver.\n await deliver(); return;\n }\n // Null raw reads: can't distinguish ghost from draft → defer (bias: protect human).\n throw new DeferDelivery(draft);\n },\n\n async showDiff(opts: {\n workspaceId: string;\n cwd: string;\n base: string;\n title?: string;\n layout?: \"split\" | \"unified\";\n focus?: boolean;\n lastTurn?: boolean;\n source?: \"branch\" | \"staged\" | \"unstaged\";\n }): Promise<void> {\n const source = opts.source ?? \"branch\";\n const args = [\"diff\"];\n if (source === \"staged\") {\n args.push(\"--staged\");\n } else if (source === \"unstaged\") {\n args.push(\"--unstaged\");\n } else {\n args.push(\"--branch\", \"--base\", opts.base);\n // --last-turn refines the branch-vs-base surface (#596); it has no\n // meaning against the staged/unstaged working-tree sources.\n if (opts.lastTurn) args.push(\"--last-turn\");\n }\n args.push(\"--cwd\", opts.cwd, \"--workspace\", opts.workspaceId, \"--layout\", opts.layout ?? \"split\");\n if (opts.title) args.push(\"--title\", opts.title);\n // cmux's diff subcommand defines --focus <true|false> (value required);\n // only --no-focus is bare.\n if (opts.focus === false) args.push(\"--no-focus\");\n else args.push(\"--focus\", \"true\");\n await cmux(args);\n },\n\n async showPatch(opts: {\n workspaceId: string;\n patch: string;\n title?: string;\n layout?: \"split\" | \"unified\";\n focus?: boolean;\n }): Promise<void> {\n const args = [\"diff\", \"-\", \"--workspace\", opts.workspaceId, \"--layout\", opts.layout ?? \"split\"];\n if (opts.title) args.push(\"--title\", opts.title);\n // Same --focus <true|false> contract as showDiff (#603): only --no-focus is bare.\n if (opts.focus === false) args.push(\"--no-focus\");\n else args.push(\"--focus\", \"true\");\n await cmuxStdin(args, opts.patch);\n },\n\n async listSurfaces(workspaceId: string): Promise<PaneRef[]> {\n let output: string;\n try {\n // --json: structured output (B2); --id-format refs: surface ids as\n // surface:N refs, not numeric (from #325). Both are required.\n output = await cmux([\"tree\", \"--workspace\", workspaceId, \"--json\", \"--id-format\", \"refs\"]);\n } catch {\n return [];\n }\n let parsed: CmuxTreeJson;\n try {\n parsed = JSON.parse(output) as CmuxTreeJson;\n } catch {\n return [];\n }\n // Navigate windows[].workspaces[].panes[].surfaces[], collecting every\n // surface that belongs to the requested workspace. Replaces the old regex\n // over `cmux tree` text (audit B2). Surface refs are globally unique, so\n // filtering by the parent workspace ref is sufficient.\n const surfaces: PaneRef[] = [];\n for (const win of parsed.windows ?? []) {\n for (const ws of win.workspaces ?? []) {\n if (ws.ref !== workspaceId) continue;\n for (const pane of ws.panes ?? []) {\n for (const sf of pane.surfaces ?? []) {\n const ref = sf.ref ?? sf.surface_ref;\n if (ref) surfaces.push({ workspaceId, surfaceId: ref, title: sf.title ?? \"\" });\n }\n }\n }\n }\n return surfaces;\n },\n };\n}\n","import type { SquadrantConfig } from \"@squadrant/shared\";\nimport type { RuntimeDriver, RuntimeProbeResult } from \"./types.js\";\n\nconst DEFAULT_RUNTIME = \"cmux\";\n\nexport class RuntimeRegistry {\n constructor(private drivers: Record<string, RuntimeDriver>) {}\n\n forProject(projectName: string, config: SquadrantConfig): RuntimeDriver {\n const projectRuntime = config.projects[projectName]?.runtime;\n const runtimeName = projectRuntime ?? config.runtime ?? DEFAULT_RUNTIME;\n return this.get(runtimeName);\n }\n\n global(config: SquadrantConfig): RuntimeDriver {\n const runtimeName = config.runtime ?? DEFAULT_RUNTIME;\n return this.get(runtimeName);\n }\n\n get(name: string): RuntimeDriver {\n const driver = this.drivers[name];\n if (!driver) {\n throw new Error(`Unknown runtime '${name}' — no driver registered`);\n }\n return driver;\n }\n\n async probeAll(): Promise<Record<string, RuntimeProbeResult>> {\n const results: Record<string, RuntimeProbeResult> = {};\n for (const [name, driver] of Object.entries(this.drivers)) {\n results[name] = await driver.probe();\n }\n return results;\n }\n}\n","import { execFile as execFileCb, execSync } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport type {\n NotifierDriver,\n NotifierProbeResult,\n NotifierScope,\n} from \"./types.js\";\nimport { CMUX_TIMEOUT } from \"../runtimes/cmux.js\";\n\nconst execFile = promisify(execFileCb);\n\nexport function createCmuxNotifier(_scope: NotifierScope): NotifierDriver {\n return {\n name: \"cmux\",\n\n async probe(): Promise<NotifierProbeResult> {\n try {\n execSync(\"squadrant runtime status --command\", { encoding: \"utf-8\", stdio: \"pipe\" });\n return { installed: true, reachable: true };\n } catch (err) {\n const code = (err as { code?: string }).code;\n if (code === \"ENOENT\") {\n return { installed: false, reachable: false };\n }\n // Any non-ENOENT error: squadrant shim crashed, workspace down, or\n // config unreadable all collapse to \"installed but not reachable\".\n return { installed: true, reachable: false };\n }\n },\n\n async notify(message: string): Promise<void> {\n // execFile (async, NOT execFileSync) with an argv array and NO shell: the\n // message is one literal argv element, so backticks / $() in notification\n // text are never parsed by a shell (#120, same class as #118/#119). Async\n // is required, not stylistic — a caller running inside the daemon's own\n // event loop (the #579/#484 DELIVERY STUCK fault alert) would otherwise\n // block ALL projects' delivery/health/socket serving for up to\n // CMUX_TIMEOUT on every call.\n await execFile(\"squadrant\", [\"runtime\", \"send\", \"--command\", message], { encoding: \"utf-8\", timeout: CMUX_TIMEOUT });\n },\n };\n}\n","import type { SquadrantConfig } from \"@squadrant/shared\";\nimport type {\n NotifierDriver,\n NotifierFactory,\n NotifierProbeResult,\n} from \"./types.js\";\n\nconst DEFAULT_NOTIFIER = \"cmux\";\n\nexport class NotifierRegistry {\n constructor(private factories: Record<string, NotifierFactory>) {}\n\n get(config: SquadrantConfig): NotifierDriver {\n const name = config.notifier ?? DEFAULT_NOTIFIER;\n return this.getFactory(name)({});\n }\n\n getFactory(name: string): NotifierFactory {\n const factory = this.factories[name];\n if (!factory) {\n throw new Error(`Unknown notifier provider '${name}' — no factory registered`);\n }\n return factory;\n }\n\n async probeAll(): Promise<Record<string, NotifierProbeResult>> {\n const results: Record<string, NotifierProbeResult> = {};\n for (const [name, factory] of Object.entries(this.factories)) {\n try {\n results[name] = await factory({}).probe();\n } catch {\n results[name] = { installed: false, reachable: false };\n }\n }\n return results;\n }\n}\n","import fs from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport type {\n WorkspaceDriver,\n WorkspaceProbeResult,\n WorkspaceScope,\n} from \"@squadrant/shared\";\n\n// Rejects `../` escapes and absolute paths via lexical containment check.\n// Does NOT resolve symlinks — a symlink inside the vault pointing outside\n// will be followed by fs.* calls. Vault contents are trusted in the squadrant\n// threat model (user-owned, not untrusted input). Tracked in issue #25.\nfunction resolveInRoot(root: string, relative: string): string {\n const joined = path.resolve(root, relative);\n const normalized = path.resolve(root) + path.sep;\n if (joined !== path.resolve(root) && !joined.startsWith(normalized)) {\n throw new Error(`Path '${relative}' escapes workspace root`);\n }\n return joined;\n}\n\nexport function createObsidianDriver(scope: WorkspaceScope): WorkspaceDriver {\n const root = scope.root;\n if (typeof root !== \"string\" || root === \"\") {\n throw new Error(\"ObsidianDriver requires scope.root (string)\");\n }\n\n return {\n name: \"obsidian\",\n\n async probe(): Promise<WorkspaceProbeResult> {\n return {\n installed: true,\n rootExists: existsSync(root),\n };\n },\n\n async read(rel: string): Promise<string> {\n return fs.readFile(resolveInRoot(root, rel), \"utf-8\");\n },\n\n async write(rel: string, content: string): Promise<void> {\n const abs = resolveInRoot(root, rel);\n await fs.mkdir(path.dirname(abs), { recursive: true });\n await fs.writeFile(abs, content);\n },\n\n async exists(rel: string): Promise<boolean> {\n try {\n await fs.access(resolveInRoot(root, rel));\n return true;\n } catch {\n return false;\n }\n },\n\n async list(rel: string): Promise<string[]> {\n try {\n return await fs.readdir(resolveInRoot(root, rel));\n } catch {\n return [];\n }\n },\n\n async mkdir(rel: string): Promise<void> {\n await fs.mkdir(resolveInRoot(root, rel), { recursive: true });\n },\n };\n}\n","// src/control/cmux/events-bridge.ts\n// Daemon-side bridge from cmux's native event stream to squadrant ControlEvents\n// (audit item B1 — reduce fragile screen-scraping).\n//\n// Unlike the per-crew OpencodeSseBridge, `cmux events` is a SINGLE global stream\n// for the whole cmux app: one newline-delimited JSON frame per cmux event,\n// carrying every agent's hook events. So this bridge is ONE long-lived\n// subscription owned by the daemon. Each `agent` frame is correlated back to a\n// crew TaskRecord by cwd (each interactive crew runs in a unique worktree path)\n// and classified into a run-state (deriveRunState):\n// - `agent.hook.Stop` — the \"turn ended / crew idle\" signal the pane reader\n// infers by scraping — → `task.turn.completed`.\n// - `agent.hook.PreToolUse` / `UserPromptSubmit` (a turn is live) →\n// `task.progress` (B4/A3): a real-activity signal that refreshes the crew's\n// liveness clock so the watchdog does not false-stall a crew mid long,\n// screen-quiet tool call (#292).\n//\n// ADDITIVE & SAFE: this runs ALONGSIDE the existing relay-proxy/pane-reader path,\n// which stays as the fallback. Both emissions are liveness, NOT completion\n// (anti-#2576): terminal state still comes from the explicit `squadrant crew signal\n// done`. The state-machine reducer already absorbs duplicate/late\n// task.turn.completed and task.progress (a blocked crew stays blocked), so\n// feeding them from BOTH paths is harmless.\nimport type { ChildProcess } from \"node:child_process\";\nimport { spawn as nodeSpawn } from \"node:child_process\";\nimport { resolveCmuxBin } from \"@squadrant/shared\";\nimport type { ControlEvent } from \"@squadrant/shared\";\n\n/** Minimal subset of ChildProcess this bridge needs (injectable for tests). */\nexport interface CmuxEventsChild {\n stdout: NodeJS.ReadableStream | null;\n kill(signal?: NodeJS.Signals): boolean | void;\n on(event: \"exit\", cb: (code: number | null) => void): unknown;\n on(event: \"error\", cb: (err: Error) => void): unknown;\n}\n\n/** Per-surface agent run-state derived from the hook stream (B4/A3). */\nexport type RunState = \"working\" | \"idle\";\n\n/**\n * Pure. Classify an `agent.hook.*` event name into the crew's run-state, or\n * null for hooks that carry no run-state signal.\n *\n * PreToolUse / UserPromptSubmit → \"working\" (a turn is live)\n * Stop → \"idle\" (turn ended)\n * SubagentStop / anything else → null (subagent end ≠ turn end)\n *\n * `Stop` is the existing turn-end signal (→ task.turn.completed). The \"working\"\n * hooks are the B4/A3 addition: they let the daemon keep a crew's liveness clock\n * fresh while it is mid (possibly long, screen-quiet) tool call, so the watchdog\n * does not false-stall it (#292). Only `PreToolUse` is live-confirmed in cmux\n * 0.64.16; `UserPromptSubmit` is mapped opportunistically (harmless if absent).\n */\nexport function deriveRunState(eventName: string): RunState | null {\n switch (eventName) {\n case \"agent.hook.PreToolUse\":\n case \"agent.hook.UserPromptSubmit\":\n return \"working\";\n case \"agent.hook.Stop\":\n return \"idle\";\n default:\n return null;\n }\n}\n\n/** A correlated hook frame, passed to the caller's record resolver. */\nexport interface CmuxAgentHook {\n cwd?: string;\n /** The emitting agent kind (`payload._source`, e.g. \"claude\"). */\n source?: string;\n /** The agent session id (`payload.session_id`). */\n sessionId?: string;\n}\n\nexport interface CmuxEventsBridgeDeps {\n /** Ingress into the daemon's event pipeline (resolves project + handles). */\n emit: (ev: ControlEvent) => void;\n /**\n * Map an agent hook frame to its owning crew record, or undefined if none.\n * The daemon supplies this from the store (non-terminal interactive records\n * matched by cwd). Keeping it injected keeps the bridge pure and testable.\n */\n resolve: (hook: CmuxAgentHook) => { id: string } | undefined;\n /** Durable resume cursor passed to `cmux events --cursor-file`. */\n cursorFile: string;\n /** Injectable spawn for tests; defaults to spawning the real cmux binary. */\n spawnImpl?: (bin: string, args: string[]) => CmuxEventsChild;\n /** Injectable cmux binary path; defaults to resolveCmuxBin(). */\n cmuxBin?: string;\n /** Injectable backoff for tests; defaults to setTimeout. */\n sleep?: (ms: number) => Promise<void>;\n /** Backoff between respawn attempts after the child exits (ms, default 1000). */\n reconnectMs?: number;\n log?: (msg: string) => void;\n /** Test-only: stop after the first child exits (don't respawn). */\n stopAfterFirstRun?: boolean;\n}\n\n/**\n * One long-lived `cmux events` subscription for the whole daemon. The CLI's\n * `--reconnect` resumes the socket in-process; `--cursor-file` makes resume\n * durable across daemon (and child) restarts. If the child process itself dies,\n * we respawn with backoff so the consumer self-heals.\n */\nexport class CmuxEventsBridge {\n private child: CmuxEventsChild | null = null;\n private stopped = false;\n private buf = \"\";\n private deps: CmuxEventsBridgeDeps;\n\n constructor(deps: CmuxEventsBridgeDeps) {\n this.deps = deps;\n }\n\n /** Begin the subscription. Idempotent. */\n start(): void {\n if (this.child || this.stopped) return;\n void this.run();\n }\n\n /** Stop the subscription and kill the child (daemon shutdown). */\n stop(): void {\n this.stopped = true;\n const c = this.child;\n this.child = null;\n if (c) {\n try { c.kill(); } catch { /* already gone */ }\n }\n }\n\n private async run(): Promise<void> {\n const spawnImpl =\n this.deps.spawnImpl ??\n ((bin, args) => nodeSpawn(bin, args, { stdio: [\"ignore\", \"pipe\", \"ignore\"] }) as ChildProcess as unknown as CmuxEventsChild);\n const bin = this.deps.cmuxBin ?? resolveCmuxBin();\n const sleep = this.deps.sleep ?? ((ms: number) => new Promise((r) => setTimeout(r, ms)));\n const reconnectMs = this.deps.reconnectMs ?? 1000;\n const args = [\n \"events\",\n \"--reconnect\",\n \"--cursor-file\", this.deps.cursorFile,\n \"--category\", \"agent\",\n \"--no-heartbeat\",\n ];\n\n while (!this.stopped) {\n this.buf = \"\";\n let child: CmuxEventsChild;\n try {\n child = spawnImpl(bin, args);\n } catch (e) {\n this.deps.log?.(`cmux events spawn failed: ${(e as Error).message}`);\n if (this.deps.stopAfterFirstRun) return;\n await sleep(reconnectMs);\n continue;\n }\n this.child = child;\n await new Promise<void>((resolve) => {\n let settled = false;\n const done = () => { if (!settled) { settled = true; resolve(); } };\n child.stdout?.on(\"data\", (b: Buffer | string) => this.onData(b));\n child.stdout?.on(\"end\", done);\n child.on(\"exit\", done);\n child.on(\"error\", (err) => {\n this.deps.log?.(`cmux events child error: ${err.message}`);\n done();\n });\n });\n this.child = null;\n if (this.stopped || this.deps.stopAfterFirstRun) break;\n // Child died (cmux app restart, binary error): resume from the cursor.\n await sleep(reconnectMs);\n }\n }\n\n private onData(chunk: Buffer | string): void {\n this.buf += typeof chunk === \"string\" ? chunk : chunk.toString(\"utf-8\");\n let nl: number;\n while ((nl = this.buf.indexOf(\"\\n\")) >= 0) {\n const line = this.buf.slice(0, nl);\n this.buf = this.buf.slice(nl + 1);\n this.handleLine(line);\n }\n }\n\n private handleLine(rawLine: string): void {\n const line = rawLine.trim();\n if (!line || line[0] !== \"{\") return;\n let f:\n | {\n type?: string;\n category?: string;\n name?: string;\n source?: string;\n payload?: { _source?: string; session_id?: string; cwd?: string; phase?: string; tool_name?: string };\n }\n | undefined;\n try {\n f = JSON.parse(line);\n } catch {\n return; // partial/non-JSON keepalive or ack we don't parse\n }\n // Only agent hook events; ignore ack/heartbeat and other categories.\n if (f?.type !== \"event\" || f.category !== \"agent\") return;\n // Classify the hook into a run-state. `Stop` is the main-session turn-end;\n // PreToolUse/UserPromptSubmit mean a turn is live; SubagentStop and any\n // other hook carry no turn-level run-state and are ignored.\n const runState = f.name ? deriveRunState(f.name) : null;\n if (!runState) return;\n const p = f.payload ?? {};\n // Each hook fires a \"received\" then \"completed\" phase frame; act on the\n // settled one so we emit exactly once per hook.\n if (p.phase === \"received\") return;\n const rec = this.deps.resolve({\n cwd: p.cwd,\n source: p._source ?? f.source,\n sessionId: p.session_id,\n });\n if (!rec) return;\n if (runState === \"idle\") {\n // Turn-end / idle — the signal the pane reader infers by scraping.\n this.deps.emit({\n type: \"task.turn.completed\",\n id: rec.id,\n turnId: p.session_id ?? \"cmux\",\n });\n return;\n }\n // working: feed a real-activity signal into the liveness path. task.progress\n // refreshes lastHeartbeatAt (the clock evaluateStall keys off), so a crew\n // that is mid long tool-call but screen-quiet is NOT false-stalled (#292),\n // and a crew the scrape path wrongly idled resumes to 'working'. ADDITIVE:\n // the reducer absorbs this idempotently, and a blocked crew stays blocked.\n // #354: carry the tool name on PreToolUse so the reducer can open a\n // tool-in-flight window (pendingTool) — the discriminator the watchdog uses\n // to tell a hung tool call apart from a quiet thinking turn.\n this.deps.emit({ type: \"task.progress\", id: rec.id, note: f.name, tool: p.tool_name });\n }\n}\n","import { readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport type { RuntimeDriver, PaneRef } from \"../runtimes/types.js\";\nimport { DeferDelivery } from \"@squadrant/core\";\nimport { loadConfig } from \"@squadrant/shared\";\nimport type { RuntimeLivenessRecord } from \"@squadrant/shared\";\nimport { readLivenessSnapshot } from \"./store-fingerprint.js\";\n\n/**\n * #332: daemon-side cmux access. The daemon (a launchd process, NOT a cmux\n * descendant) can now drive cmux directly because the CLI auto-discovers its\n * canonical socket (~/.local/state/cmux/cmux.sock) from any process.\n *\n * Every method is FAIL-SOFT: a cmux/socket error degrades to a safe sentinel\n * ([] / null / no-op) so a transient failure NEVER false-reaps a live crew.\n * Exceptions: DeferDelivery, which `send` re-throws so the delivery loop can\n * defer-while-typing (#258/#302); and `liveness()`, which THROWS when it\n * cannot get a good read of the store (readdir failure, or every store file\n * unreadable/corrupt) instead of returning [] — a locked/mid-write store must\n * never look like \"read succeeded, zero captains\" (that would false-close\n * every known captain via runLivenessTick's markEnded path). runLivenessTick\n * already treats a thrown liveness() as \"leave the registry untouched\".\n *\n * This is the seam #333's LifecycleSource port sits beside.\n */\nexport class DaemonCmux {\n constructor(private readonly driver: RuntimeDriver) {}\n\n async send(surface: PaneRef, text: string, opts?: { probe?: boolean }): Promise<void> {\n try {\n await this.driver.sendToSurface(surface, text, opts);\n } catch (e) {\n if (e instanceof DeferDelivery) throw e;\n }\n }\n\n async listSurfaces(workspaceId: string): Promise<PaneRef[]> {\n try { return await this.driver.listSurfaces(workspaceId); }\n catch { return []; }\n }\n\n async readScreen(ref: string): Promise<string | null> {\n try { return await this.driver.readScreen(ref); }\n catch { return null; }\n }\n\n async readPaneScreen(pane: PaneRef): Promise<string | null> {\n try { return await this.driver.readPaneScreen(pane); }\n catch { return null; }\n }\n\n async findWorkspaceId(name: string): Promise<string | null> {\n try {\n const ref = await this.driver.status(name);\n return ref?.id ?? null;\n } catch {\n return null;\n }\n }\n\n async isAvailable(): Promise<boolean> {\n try { await this.driver.listSurfaces(\"\"); return true; }\n catch { return false; }\n }\n\n /**\n * Ground-truth liveness from cmux's own hook-sessions store (§5.4).\n * THROWS (does not return []) when the dir can't be listed, or every store\n * file failed to read/parse — see the class doc above.\n */\n async liveness(): Promise<RuntimeLivenessRecord[]> {\n const dir = process.env.CMUX_AGENT_HOOK_STATE_DIR ?? join(homedir(), \".cmuxterm\");\n const projects = loadConfig().projects as Record<string, { path: string }>;\n let files: string[];\n try { files = readdirSync(dir).filter((f) => f.endsWith(\"-hook-sessions.json\") && !f.endsWith(\".lock\")); }\n catch (e) { throw new Error(`liveness: could not read cmux state dir ${dir}: ${(e as Error).message}`); }\n return readLivenessSnapshot(files, (f) => readFileSync(join(dir, f), \"utf-8\"), projects);\n }\n}\n","import { resolveHome } from \"@squadrant/shared\";\nimport type { RuntimeLivenessRecord, Role } from \"@squadrant/shared\";\n\ninterface RawSession {\n sessionId?: string; pid?: number | null; cwd?: string; isRestorable?: boolean;\n launchCommand?: { arguments?: string[]; workingDirectory?: string };\n}\n\n/** template basename → role (captain.claude.md → captain, crew.claude.md → crew, …). */\nfunction roleFromTemplate(args: string[] | undefined): Role | \"unknown\" {\n const i = args?.indexOf(\"--append-system-prompt-file\") ?? -1;\n const tmpl = i >= 0 && args ? (args[i + 1] ?? \"\").split(\"/\").pop() ?? \"\" : \"\";\n if (tmpl.startsWith(\"captain\")) return \"captain\";\n if (tmpl.startsWith(\"crew\")) return \"crew\";\n if (tmpl.startsWith(\"command\")) return \"command\";\n return \"unknown\"; // side.research.* etc. — not a captain\n}\n\nfunction projectFromCwd(cwd: string, projects: Record<string, { path: string }>): string | undefined {\n for (const [name, p] of Object.entries(projects)) {\n const projPath = resolveHome(p.path);\n if (cwd === projPath || cwd.startsWith(`${projPath}/`)) return name;\n }\n return undefined;\n}\n\n/**\n * Parse one store file's content. Throws on invalid JSON — a corrupt/mid-write\n * file is a failed read, NOT a valid file with zero sessions; callers (see\n * `readLivenessSnapshot`) must be able to tell the two apart so a locked file\n * never false-reads as \"no captains\".\n */\nexport function parseStoreRecords(\n fileContent: string,\n projects: Record<string, { path: string }>,\n): RuntimeLivenessRecord[] {\n let parsed: { sessions?: Record<string, RawSession> };\n try { parsed = JSON.parse(fileContent); }\n catch (e) { throw new Error(`parseStoreRecords: invalid JSON: ${(e as Error).message}`); }\n const out: RuntimeLivenessRecord[] = [];\n for (const s of Object.values(parsed.sessions ?? {})) {\n const cwd = s.cwd ?? s.launchCommand?.workingDirectory ?? \"\";\n const project = projectFromCwd(cwd, projects);\n if (!project || !s.sessionId) continue;\n out.push({\n role: roleFromTemplate(s.launchCommand?.arguments),\n project,\n pid: typeof s.pid === \"number\" ? s.pid : null,\n sessionId: s.sessionId,\n present: true,\n isRestorable: s.isRestorable,\n });\n }\n return out;\n}\n\n/**\n * Read+parse every given store file, tolerating individual bad files (locked\n * mid-write, corrupt) as long as at least one yields a good read. Only throws\n * when EVERY file failed — a locked/corrupt store must never look like \"read\n * succeeded, zero captains present\" (that would false-close every known\n * captain this tick). Genuinely zero files (none present) is a valid empty read.\n */\nexport function readLivenessSnapshot(\n files: string[],\n readFile: (filename: string) => string,\n projects: Record<string, { path: string }>,\n): RuntimeLivenessRecord[] {\n const out: RuntimeLivenessRecord[] = [];\n let successes = 0;\n for (const f of files) {\n try {\n out.push(...parseStoreRecords(readFile(f), projects));\n successes++;\n } catch { /* this file unreadable/corrupt — other files may still be good */ }\n }\n if (files.length > 0 && successes === 0) {\n throw new Error(`readLivenessSnapshot: all ${files.length} store file(s) unreadable/corrupt this tick`);\n }\n return out;\n}\n","// cmux-store-source.ts — LifecycleSource adapter for ~/.cmuxterm/*-hook-sessions.json\n//\n// Implements the backup LifecycleSource (D1: A-backup) from the #333 design.\n// Watches the cmux state directory, reads each agent's hook-sessions.json, and\n// feeds LifecycleSnapshots into the reduceLifecycle pipeline via deps.report().\n//\n// NOT wired into the live daemon path in Phase 1 (additive per D3/D7).\n//\n// CORRELATION CONSTRAINT (research §2.2): launchCommand in the store has no\n// environment vars — SQUADRANT_CREW_TASK_ID is not available. Correlate by:\n// 1. cwd (primary for interactive crews — match against TaskRecord.cwd)\n// 2. pid (passed in hint; daemon can try KERN_PROCARGS2 lookup later)\n// 3. sessionId (cmux UUID; may match TaskRecord.sessionId if crew populates it)\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { watch, readdirSync, readFileSync, existsSync } from \"node:fs\";\nimport type { LifecycleSource, LifecycleSourceDeps, LifecycleSnapshot, CorrelationHint, LifecycleState } from \"@squadrant/core\";\n\n// ── store file schema (version:1, live schema from research report §2.2) ─────\n\ninterface StoreSession {\n sessionId: string;\n agentLifecycle: string;\n pid: number;\n cwd: string;\n lastBody?: string;\n isRestorable?: boolean;\n updatedAt: number; // Unix float (seconds)\n}\n\ninterface StoreFile {\n sessions?: Record<string, StoreSession>;\n}\n\n// ── injectable deps ──────────────────────────────────────────────────────────\n\nexport interface CmuxStoreSourceOpts {\n /** Directory to watch. Defaults to CMUX_AGENT_HOOK_STATE_DIR or ~/.cmuxterm. */\n stateDir?: string;\n /** Debounce delay between a watch event and the next scan (ms). Default 50. */\n debounceMs?: number;\n /** Returns true if the given pid is alive. Default: process.kill(pid, 0). */\n isPidAlive?: (pid: number) => boolean;\n /**\n * Lists store files in the given directory.\n * Default: readdirSync filtered to *-hook-sessions.json.\n */\n listFiles?: (dir: string) => string[];\n /**\n * Reads a file's content, returns undefined on any read error.\n * Default: readFileSync.\n */\n readFile?: (path: string) => string | undefined;\n /**\n * Returns true if the given path exists (lock-file check).\n * Default: existsSync.\n */\n fileExists?: (path: string) => boolean;\n /**\n * Starts a directory watcher. Calls cb on relevant file changes.\n * Returns a stop function. Default: fs.watch.\n */\n watchDir?: (dir: string, cb: () => void) => () => void;\n /** Injectable setTimeout for debouncing. Default: global setTimeout. */\n scheduleTimer?: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;\n /** Injectable clearTimeout for debouncing. Default: global clearTimeout. */\n cancelTimer?: (id: ReturnType<typeof setTimeout>) => void;\n log?: (msg: string) => void;\n}\n\n// ── CmuxStoreSource ──────────────────────────────────────────────────────────\n\n/**\n * LifecycleSource that watches ~/.cmuxterm/*-hook-sessions.json.\n *\n * cmux writes the hook-sessions file on every lifecycle-changing hook event\n * (SessionStart, UserPromptSubmit, PreToolUse, Stop, Notification, AskUserQuestion).\n * Each session record carries `agentLifecycle` in the 4-state vocabulary that\n * exactly matches LifecycleState, so no re-mapping is needed.\n *\n * Events carry origin:\"agent\" because the store is the agent's own reported\n * lifecycle state — not inferred from a process scan.\n */\nexport class CmuxStoreSource implements LifecycleSource {\n readonly name = \"cmux-store\";\n\n private readonly stateDir: string;\n private readonly debounceMs: number;\n private readonly isPidAlive: (pid: number) => boolean;\n private readonly listFiles: (dir: string) => string[];\n private readonly readFile: (path: string) => string | undefined;\n private readonly fileExists: (path: string) => boolean;\n private readonly watchDir: (dir: string, cb: () => void) => () => void;\n private readonly scheduleTimer: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;\n private readonly cancelTimer: (id: ReturnType<typeof setTimeout>) => void;\n private readonly log: (msg: string) => void;\n\n private deps?: LifecycleSourceDeps;\n private stopWatcher?: () => void;\n private debounceTimer?: ReturnType<typeof setTimeout>;\n /** taskId → last reported snapshot (for snapshot() liveness floor). */\n private cache = new Map<string, LifecycleSnapshot>();\n private active = false;\n private lastError: string | null = null;\n\n constructor(opts: CmuxStoreSourceOpts = {}) {\n this.stateDir =\n opts.stateDir ??\n process.env.CMUX_AGENT_HOOK_STATE_DIR ??\n join(homedir(), \".cmuxterm\");\n this.debounceMs = opts.debounceMs ?? 50;\n this.isPidAlive = opts.isPidAlive ?? defaultIsPidAlive;\n this.listFiles = opts.listFiles ?? defaultListFiles;\n this.readFile = opts.readFile ?? defaultReadFile;\n this.fileExists = opts.fileExists ?? existsSync;\n this.watchDir = opts.watchDir ?? defaultWatchDir;\n this.scheduleTimer = opts.scheduleTimer ?? (setTimeout as NonNullable<CmuxStoreSourceOpts[\"scheduleTimer\"]>);\n this.cancelTimer = opts.cancelTimer ?? (clearTimeout as NonNullable<CmuxStoreSourceOpts[\"cancelTimer\"]>);\n this.log = opts.log ?? (() => {});\n }\n\n start(deps: LifecycleSourceDeps): void {\n this.deps = deps;\n this.active = true;\n this.lastError = null;\n // Initial scan before any watch events fire.\n this.scan();\n // Watch for subsequent changes, debounced.\n try {\n this.stopWatcher = this.watchDir(this.stateDir, () => this.scheduleDebounced());\n } catch (e) {\n this.lastError = (e as Error).message;\n this.log(`cmux-store: failed to watch ${this.stateDir}: ${(e as Error).message}`);\n }\n }\n\n stop(): void {\n if (this.debounceTimer !== undefined) {\n this.cancelTimer(this.debounceTimer);\n this.debounceTimer = undefined;\n }\n this.stopWatcher?.();\n this.stopWatcher = undefined;\n this.deps = undefined;\n this.cache.clear();\n this.active = false;\n }\n\n /** Returns the last-reported snapshot for a known crew (liveness floor). */\n snapshot(taskId: string): LifecycleSnapshot | undefined {\n return this.cache.get(taskId);\n }\n\n /** Read-only source health (B4 — dashboard visibility into which sources are up). */\n health(): { active: boolean; error: string | null } {\n return { active: this.active, error: this.lastError };\n }\n\n // ── private ─────────────────────────────────────────────────────────────────\n\n private scheduleDebounced(): void {\n if (this.debounceTimer !== undefined) {\n this.cancelTimer(this.debounceTimer);\n }\n this.debounceTimer = this.scheduleTimer(() => {\n this.debounceTimer = undefined;\n this.scan();\n }, this.debounceMs);\n }\n\n private scan(): void {\n if (!this.deps) return;\n for (const filename of this.listFiles(this.stateDir)) {\n this.scanFile(filename);\n }\n }\n\n private scanFile(filename: string): void {\n const deps = this.deps!;\n const filePath = join(this.stateDir, filename);\n const lockPath = `${filePath}.lock`;\n\n // Skip files that cmux is currently writing.\n if (this.fileExists(lockPath)) {\n this.log(`cmux-store: skipping ${filename} (locked)`);\n return;\n }\n\n const raw = this.readFile(filePath);\n if (!raw) return;\n\n let parsed: StoreFile;\n try {\n parsed = JSON.parse(raw) as StoreFile;\n } catch {\n this.log(`cmux-store: failed to parse ${filename}`);\n return;\n }\n\n for (const session of Object.values(parsed.sessions ?? {})) {\n this.processSession(session, deps);\n }\n }\n\n private processSession(session: StoreSession, deps: LifecycleSourceDeps): void {\n if (!session.sessionId || !session.cwd || typeof session.pid !== \"number\") return;\n\n const hint: CorrelationHint = {\n cwd: session.cwd,\n pid: session.pid,\n sessionId: session.sessionId,\n };\n const resolved = deps.resolve(hint);\n if (!resolved) return;\n\n // Pid-verify liveness.\n let alive = this.isPidAlive(session.pid);\n\n // Hibernation guard (research §194): cmux reclaims RAM from idle crews by\n // suspending or reaping the pid. Only treat a dead pid as logically alive\n // when the session is restorable AND idle — a running/needsInput session\n // with a dead pid is genuinely gone, not hibernated.\n if (!alive && session.isRestorable === true && session.agentLifecycle === \"idle\") {\n alive = true;\n }\n\n const snap: LifecycleSnapshot = {\n taskId: resolved.id,\n state: parseLifecycleState(session.agentLifecycle),\n alive,\n // \"agent\": the store carries the agent's own reported lifecycle state,\n // not a scan inference. needsInput from the store is authoritative.\n origin: \"agent\",\n at: Math.floor((session.updatedAt ?? 0) * 1000),\n pid: session.pid,\n ...(session.lastBody ? { detail: { note: session.lastBody } } : {}),\n };\n\n this.cache.set(resolved.id, snap);\n deps.report(snap);\n }\n}\n\n// ── private helpers ──────────────────────────────────────────────────────────\n\nfunction parseLifecycleState(s: string | undefined): LifecycleState {\n if (s === \"running\" || s === \"idle\" || s === \"needsInput\" || s === \"unknown\") {\n return s;\n }\n return \"unknown\";\n}\n\nfunction defaultIsPidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction defaultListFiles(dir: string): string[] {\n try {\n return readdirSync(dir).filter(\n (f) => f.endsWith(\"-hook-sessions.json\") && !f.endsWith(\".lock\"),\n );\n } catch {\n return [];\n }\n}\n\nfunction defaultReadFile(path: string): string | undefined {\n try {\n return readFileSync(path, \"utf-8\");\n } catch {\n return undefined;\n }\n}\n\nfunction defaultWatchDir(dir: string, cb: () => void): () => void {\n const w = watch(dir, (_event, filename) => {\n if (typeof filename === \"string\" && filename.endsWith(\"-hook-sessions.json\")) {\n cb();\n }\n });\n return () => w.close();\n}\n","// native-hook-source.ts — LifecycleSource C: squadrant-owned claude hooks\n//\n// PRIMARY LifecycleSource (#333 Phase 1, D1). Installs namespaced hooks into\n// claude's native config and receives hook events pushed by the daemon.\n//\n// NOT wired into the live daemon in Phase 1 (additive per D3/D7).\n// The sibling wiring crew adds 'squadrant hooks claude <sub>' to the CLI,\n// reads SQUADRANT_CREW_TASK_ID from the hook process env, and calls handleHook().\n\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport type { LifecycleSource, LifecycleSourceDeps, LifecycleSnapshot, LifecycleState } from \"@squadrant/core\";\n\n// ── Hook event matrix ─────────────────────────────────────────────────────────\n\n// Claude hook event name → sub-command alias → optional tool matcher (blueprint §9).\n// Non-lifecycle hooks (PostToolUse, SubagentStop) are intentionally excluded;\n// they feed the existing crew._hook bridge and are not part of the 4-state model.\n//\n// Third element (matcher) is passed as the hook entry's \"matcher\" field.\n// AskUserQuestion is a TOOL, not an event — hook it via PreToolUse with a tool matcher.\nconst CLAUDE_HOOK_EVENTS: ReadonlyArray<readonly [string, string, string?]> = [\n [\"SessionStart\", \"session-start\"],\n [\"UserPromptSubmit\", \"prompt-submit\"],\n [\"PreToolUse\", \"pre-tool-use\"],\n [\"Stop\", \"stop\"],\n [\"Notification\", \"notification\"],\n [\"PreToolUse\", \"ask-question\", \"AskUserQuestion\"],\n [\"SessionEnd\", \"session-end\"],\n];\n\nconst DEFAULT_HOOK_CMD = \"squadrant hooks\";\n\n// ── Hook installer ────────────────────────────────────────────────────────────\n\nexport interface ClaudeHooksInstallOpts {\n /** Path to ~/.claude/settings.json. Injectable for tests. */\n settingsPath?: string;\n /**\n * Base hook command — final command is '<hookCmd> claude <sub>'.\n * Default: 'squadrant hooks' (the CLI subcommand wired by the daemon crew).\n */\n hookCmd?: string;\n /** Injectable: read file content, undefined on any read error. */\n readFile?: (path: string) => string | undefined;\n /** Injectable: write file (caller responsible for creating parent dirs). */\n writeFile?: (path: string, content: string) => void;\n log?: (msg: string) => void;\n /**\n * #615 opt-in: deep-merged into settings.json's 'env' block, non-clobbering —\n * a key already present in the user's settings is never overwritten (logged\n * instead). Absent or empty ⇒ nothing written to env. Sourced from\n * squadrant config's defaults.claudeEnv.\n */\n claudeEnv?: Record<string, string>;\n}\n\n/**\n * Idempotent, non-clobbering installer for squadrant-owned hooks in ~/.claude/settings.json.\n *\n * Installs one hook entry per lifecycle-relevant Claude hook event (D4: namespaced,\n * re-run-safe). Hooks from cmux, the user, or other tools with different commands\n * are left untouched. A second call with the same hookCmd is a complete no-op.\n * Returns the path to the settings file (which may or may not have been written).\n */\nexport function installClaudeHooks(opts: ClaudeHooksInstallOpts = {}): string {\n const settingsPath = opts.settingsPath ?? join(homedir(), \".claude\", \"settings.json\");\n const hookCmd = opts.hookCmd ?? DEFAULT_HOOK_CMD;\n const readFile = opts.readFile ?? defaultReadFile;\n const writeFile = opts.writeFile ?? defaultWriteFile;\n const log = opts.log ?? (() => {});\n\n // Parse existing settings (start fresh if absent or malformed).\n let settings: Record<string, unknown> = {};\n const raw = readFile(settingsPath);\n const hadExistingSettings = raw !== undefined;\n if (raw) {\n try {\n settings = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n log(`native-hook: failed to parse ${settingsPath} — hooks section will be reset`);\n }\n }\n\n // Ensure hooks is a plain object.\n if (typeof settings.hooks !== \"object\" || settings.hooks === null || Array.isArray(settings.hooks)) {\n settings.hooks = {};\n }\n const hooks = settings.hooks as Record<string, unknown>;\n\n let changed = false;\n const repaired: string[] = [];\n for (const [eventName, sub, matcher] of CLAUDE_HOOK_EVENTS) {\n if (!Array.isArray(hooks[eventName])) {\n hooks[eventName] = [];\n }\n const entries = hooks[eventName] as unknown[];\n const command = `${hookCmd} claude ${sub}`;\n const hookMatcher = matcher ?? \"\";\n\n // Idempotency check: skip if our exact command is already registered.\n const alreadyPresent = entries.some(\n (m) =>\n Array.isArray((m as Record<string, unknown>).hooks) &&\n ((m as Record<string, unknown>).hooks as unknown[]).some(\n (h) =>\n typeof (h as Record<string, unknown>).command === \"string\" &&\n (h as Record<string, unknown>).command === command,\n ),\n );\n if (!alreadyPresent) {\n entries.push({ matcher: hookMatcher, hooks: [{ type: \"command\", command, timeout: 10 }] });\n changed = true;\n repaired.push(`${eventName}/${sub}`);\n }\n }\n\n // #615: a hook missing from an already-existing settings file is drift (e.g. the\n // file was hand-edited or clobbered) — surface it, since a missing AskUserQuestion\n // hook silently kills crew-blocked signalling (#560). A fresh install where nothing\n // existed yet is not drift, so it stays quiet.\n if (repaired.length > 0 && hadExistingSettings) {\n log(\n `native-hook: repaired ${repaired.length} missing squadrant hook(s) in ${settingsPath} [${repaired.join(\", \")}] — WARNING: blocked-signalling or lifecycle tracking may have been broken until this run`,\n );\n }\n\n // #615 opt-in: deep-merge defaults.claudeEnv into settings.json 'env', non-clobbering.\n if (opts.claudeEnv && Object.keys(opts.claudeEnv).length > 0) {\n if (typeof settings.env !== \"object\" || settings.env === null || Array.isArray(settings.env)) {\n settings.env = {};\n }\n const env = settings.env as Record<string, unknown>;\n for (const [key, value] of Object.entries(opts.claudeEnv)) {\n if (key in env) {\n if (env[key] !== value) {\n log(\n `native-hook: claudeEnv key '${key}' already set to '${String(env[key])}' in ${settingsPath} — not overwriting with '${value}'`,\n );\n }\n continue;\n }\n env[key] = value;\n changed = true;\n }\n }\n\n if (changed) {\n writeFile(settingsPath, JSON.stringify(settings, null, 2));\n }\n return settingsPath;\n}\n\n// ── Sub-event → lifecycle state mapping ──────────────────────────────────────\n\n/**\n * Pure: map a sub-event alias to its LifecycleState.\n * Returns \"session-end\" for the teardown alias (not a LifecycleState value — the\n * caller emits alive:false + state:\"unknown\" and the daemon wiring translates to\n * task.session.ended). Returns null for unknown subs (caller no-ops).\n */\nexport function mapSubToLifecycle(sub: string): LifecycleState | \"session-end\" | null {\n switch (sub) {\n case \"session-start\": return \"running\";\n case \"prompt-submit\": return \"running\";\n case \"pre-tool-use\": return \"running\";\n case \"stop\": return \"idle\";\n case \"notification\": return \"needsInput\";\n case \"ask-question\": return \"needsInput\";\n case \"session-end\": return \"session-end\";\n default: return null;\n }\n}\n\n// ── NativeHookSource ─────────────────────────────────────────────────────────\n\nexport interface NativeHookSourceOpts {\n /** Options forwarded to installClaudeHooks(). Useful for testing. */\n hookInstall?: ClaudeHooksInstallOpts;\n log?: (msg: string) => void;\n}\n\n/**\n * LifecycleSource C — primary, driver-agnostic (#333 D1).\n *\n * Two seams:\n * 1. install() — writes squadrant-owned hooks into ~/.claude/settings.json\n * (idempotent, namespaced, non-clobbering per D4).\n * 2. handleHook(sub, taskId, pid?, payload?) — called by the daemon when a\n * claude hook fires; maps the sub-event to a LifecycleSnapshot and feeds\n * it into deps.report().\n *\n * Unlike CmuxStoreSource (file-watcher), NativeHookSource is purely push-driven:\n * every snapshot arrives via handleHook() from the daemon's 'squadrant hooks'\n * CLI subcommand. The snapshot() method serves the liveness floor from the cache.\n */\nexport class NativeHookSource implements LifecycleSource {\n readonly name = \"native-hook\";\n\n private readonly hookInstall: ClaudeHooksInstallOpts;\n private readonly log: (msg: string) => void;\n\n private deps?: LifecycleSourceDeps;\n /** taskId → last-reported snapshot, for snapshot() liveness floor. */\n private cache = new Map<string, LifecycleSnapshot>();\n private active = false;\n\n constructor(opts: NativeHookSourceOpts = {}) {\n this.log = opts.log ?? (() => {});\n // Forward the source-level log into installClaudeHooks by default so #615\n // repair/non-clobber warnings surface — an explicit hookInstall.log still wins.\n this.hookInstall = { log: this.log, ...opts.hookInstall };\n }\n\n start(deps: LifecycleSourceDeps): void {\n this.deps = deps;\n this.active = true;\n }\n\n stop(): void {\n this.deps = undefined;\n this.cache.clear();\n this.active = false;\n }\n\n /** Returns the last-reported snapshot for a known crew (liveness floor poll). */\n snapshot(taskId: string): LifecycleSnapshot | undefined {\n return this.cache.get(taskId);\n }\n\n /** Read-only source health (B4). Purely push-driven — never errors on its own. */\n health(): { active: boolean; error: string | null } {\n return { active: this.active, error: null };\n }\n\n /**\n * Install squadrant-owned hooks into ~/.claude/settings.json.\n * Idempotent — safe to call on every project init or crew spawn.\n * Returns the path to the settings file.\n */\n install(): string {\n return installClaudeHooks(this.hookInstall);\n }\n\n /**\n * Receive a lifecycle hook event from the daemon and report a LifecycleSnapshot.\n *\n * The daemon's 'squadrant hooks claude <sub>' CLI subcommand calls this after\n * reading SQUADRANT_CREW_TASK_ID from the hook's process environment — the only\n * collision-proof correlation key (blueprint §2.2 priority 1).\n *\n * @param sub Sub-event alias: \"session-start\" | \"prompt-submit\" | \"stop\" | …\n * @param taskId SQUADRANT_CREW_TASK_ID extracted from the hook process env.\n * @param pid Optional: OS pid from the hook's process env or argv.\n * @param payload Optional: parsed JSON payload from hook stdin (best-effort detail).\n */\n handleHook(sub: string, taskId: string, pid?: number, payload?: unknown): void {\n if (!this.deps) return;\n\n const mapped = mapSubToLifecycle(sub);\n if (mapped === null) {\n this.log(`native-hook: unknown sub '${sub}' for task ${taskId} — ignored`);\n return;\n }\n\n // session-end signals teardown: alive:false lets the daemon wiring emit\n // task.session.ended (anti-#2576: never task.done from a lifecycle hook).\n const isSessionEnd = mapped === \"session-end\";\n const state: LifecycleState = isSessionEnd ? \"unknown\" : mapped;\n\n const detail = extractDetail(sub, payload);\n const snap: LifecycleSnapshot = {\n taskId,\n state,\n alive: !isSessionEnd,\n origin: \"agent\",\n at: Date.now(),\n ...(pid !== undefined ? { pid } : {}),\n ...(detail ? { detail } : {}),\n };\n\n this.cache.set(taskId, snap);\n this.deps.report(snap);\n }\n}\n\n// ── Private helpers ───────────────────────────────────────────────────────────\n\nfunction extractDetail(sub: string, payload: unknown): LifecycleSnapshot[\"detail\"] | undefined {\n if (!payload || typeof payload !== \"object\") return undefined;\n const p = payload as Record<string, unknown>;\n if (sub === \"notification\") {\n const note = typeof p.message === \"string\" ? p.message : undefined;\n return note ? { note } : undefined;\n }\n if (sub === \"pre-tool-use\") {\n const tool = typeof p.tool_name === \"string\" ? p.tool_name : undefined;\n return tool ? { tool } : undefined;\n }\n return undefined;\n}\n\nfunction defaultReadFile(path: string): string | undefined {\n try {\n return readFileSync(path, \"utf-8\");\n } catch {\n return undefined;\n }\n}\n\nfunction defaultWriteFile(path: string, content: string): void {\n mkdirSync(path.replace(/\\/[^/]+$/, \"\"), { recursive: true });\n writeFileSync(path, content, \"utf-8\");\n}\n","// Runtime-bound crew-pane helpers — discovery, first-turn delivery, captain\n// workspace resolution. Extracted from packages/cli/src/commands/crew.ts so\n// they are unit-testable with a mock RuntimeDriver.\n\nimport net from \"node:net\";\nimport { loadConfig } from \"@squadrant/shared\";\nimport type { PaneRef, RuntimeDriver } from \"@squadrant/shared\";\nimport { RuntimeRegistry } from \"./runtimes/registry.js\";\nimport { createCmuxDriver, parseDraftFromScreen, hasCCInputBox, hasModalOptionList, classifyStartupSurface } from \"./runtimes/cmux.js\";\nimport { titleFor, isCrewTitle, screenHasSplashMarker } from \"@squadrant/core\";\nimport type { TurnAcceptanceConfig } from \"@squadrant/core\";\n\n// Poll-based first-turn delivery timing constants.\nconst SEND_FIRST_TURN_FLOOR_MS = 1500;\nconst POLL_INTERVAL_MS = 750;\n// #466 residual: 90s readiness cap. Captains boot UNLOADED (5–15s, hence their\n// 30s readyTimeoutMs), but crews cold-init UNDER LOAD in a fresh worktree with\n// claude-mem's MCP server loading — that can take 30–60s to reach input-ready\n// (pact-network's actual case). A captain-parity 30s budget timed out into the\n// still-cold box and the confirmedSendToPane fallback blind-fired keystrokes that\n// were dropped. The strong CC-initialized gate below makes a generous cap safe: it\n// only ever waits as long as CC actually needs (delivery fires the instant the\n// surface is ready), and a crashed/never-ready CC still caps out here. 90s sits\n// well under the daemon's 5min CREW UNDELIVERED watchdog, so a genuine failure is\n// still surfaced.\nconst SEND_FIRST_TURN_TIMEOUT_MS = 90000;\nconst POST_SEND_CHECK_MS = 750;\n\n// #235 Confirm-on-delivery constants for splash-gated agents (opencode).\n// We poll every POST_SEND_CHECK_MS but only re-send every SPLASH_RESEND_EVERY_N\n// checks — a 3s de-dup guard that prevents double-execution when the TUI is\n// slow to redraw after accepting.\nconst SPLASH_MAX_CHECKS = 20; // 20 × 750ms ≈ 15s confirmation window\nconst SPLASH_RESEND_EVERY_N = 4; // re-send every 4 checks ≈ every 3s\n\n// #339 paste-then-submit constants for the claude/codex first-turn path.\n// SETTLE polls the input box after the paste until its content stops changing —\n// i.e. Claude Code's paste-accumulation window has closed — so the submit CR is a\n// separate keystroke that lands AFTER the [Pasted text] placeholder is final and\n// is therefore treated as a submit, not a literal newline inside the paste.\nconst SETTLE_POLL_MS = 400;\nconst SETTLE_MAX_POLLS = 8; // up to 3.2s for a very large paste to render\nconst SUBMIT_RETRY_LIMIT = 4; // Enter-only re-issues if the box stays stranded\n\n/** Poll the pane until its input box stops changing across two consecutive reads\n * (paste fully rendered / accumulation window closed), or the cap is hit.\n * Returns true if the box was observed with content at any point — the caller\n * uses this to distinguish \"paste rendered then submitted\" from \"paste never\n * rendered, empty box is NOT a confirmation of submit\" (#455). */\nasync function settleInputBox(\n runtime: Pick<RuntimeDriver, \"readPaneScreen\">,\n pane: PaneRef,\n): Promise<boolean> {\n let prev = (await runtime.readPaneScreen(pane)) ?? \"\";\n let sawContent = parseDraftFromScreen(prev) !== \"\" && parseDraftFromScreen(prev) !== null;\n for (let i = 0; i < SETTLE_MAX_POLLS; i++) {\n await new Promise((r) => setTimeout(r, SETTLE_POLL_MS));\n const cur = (await runtime.readPaneScreen(pane)) ?? \"\";\n const draft = parseDraftFromScreen(cur);\n if (draft !== \"\" && draft !== null) sawContent = true;\n if (cur === prev) return sawContent;\n prev = cur;\n }\n return sawContent;\n}\n\n/** Reserve an ephemeral TCP port for a crew's embedded HTTP server. Binds :0,\n * reads the OS-assigned port, then releases it. A small TOCTOU window exists\n * between release and the crew binding the port; acceptable for local\n * single-user spawns. */\nexport function getFreePort(): Promise<number> {\n return new Promise((resolve, reject) => {\n const srv = net.createServer();\n srv.once(\"error\", reject);\n srv.listen(0, \"127.0.0.1\", () => {\n const addr = srv.address();\n const port = typeof addr === \"object\" && addr ? addr.port : 0;\n srv.close(() => (port ? resolve(port) : reject(new Error(\"no free port assigned\"))));\n });\n });\n}\n\nexport async function listProjectCrews(\n runtime: RuntimeDriver,\n workspaceId: string,\n project: string,\n): Promise<PaneRef[]> {\n const surfaces = await runtime.listSurfaces(workspaceId);\n return surfaces.filter((s) => s.title && isCrewTitle(project, s.title));\n}\n\nexport async function findCrew(\n runtime: RuntimeDriver,\n workspaceId: string,\n project: string,\n name: string,\n): Promise<PaneRef | null> {\n const want = titleFor(project, name);\n const surfaces = await runtime.listSurfaces(workspaceId);\n return surfaces.find((s) => s.title === want) ?? null;\n}\n\nexport async function resolveCaptainWorkspace(project: string): Promise<{\n runtime: RuntimeDriver;\n workspaceId: string;\n}> {\n const config = loadConfig();\n const proj = config.projects[project];\n if (!proj) {\n throw new Error(`Project '${project}' not found. Run 'squadrant projects list'.`);\n }\n const runtime = new RuntimeRegistry({ cmux: createCmuxDriver() }).forProject(project, config);\n const captain = await runtime.status(proj.captainName);\n if (!captain) {\n throw new Error(\n `Captain workspace '${proj.captainName}' is not running. Run 'squadrant launch ${project}' first.`,\n );\n }\n return { runtime, workspaceId: captain.id };\n}\n\n/**\n * #516: cheap, side-effect-free check for an open AskUserQuestion/permission\n * SELECTION MODAL — a single screen read, no paste, no keystroke. Lets\n * runCrewSend skip its daemon-state emit (task.reopened/task.started) AND the\n * pane touch entirely when the crew can't actually receive the message right\n * now, so a modal-blocked send is a true no-op rather than just skipping the\n * pane write. confirmedSendToPane keeps its own copy of this check as the\n * pane-touch backstop for the TOCTOU window between this precheck and delivery.\n */\nexport async function paneHasOpenModal(\n runtime: Pick<RuntimeDriver, \"readPaneScreen\">,\n pane: PaneRef,\n): Promise<boolean> {\n const screen = (await runtime.readPaneScreen(pane)) ?? \"\";\n return hasModalOptionList(screen);\n}\n\n/**\n * Deliver a message to a crew pane with the paste-settle-Enter confirmation\n * sequence from #447. Shared by the follow-up `crew send` path (#448) and\n * available for first-turn use — both call the same submit hardening:\n * 1. paste only (no bundled CR)\n * 2. settle until the input box content stops changing (accumulation closed)\n * 3. separate Enter keystroke\n * 4. confirm box empty; re-issue ONLY Enter if stranded (never re-paste)\n *\n * Returns `{ delivered: true }` when the box empties after the draft was seen\n * (positive submit confirmation), or `{ delivered: false }` if the retry loop\n * exhausts without confirmation (#466: callers surface non-delivery explicitly).\n * Returns `{ delivered: false, blockedByModal: true }` without touching the\n * pane at all when an AskUserQuestion/permission SELECTION MODAL is open (#516).\n */\nexport async function confirmedSendToPane(\n runtime: Pick<RuntimeDriver, \"readPaneScreen\" | \"pasteToPane\" | \"sendKeyToPane\">,\n pane: PaneRef,\n message: string,\n): Promise<{ delivered: boolean; blockedByModal?: boolean }> {\n const preSendScreen = (await runtime.readPaneScreen(pane)) ?? \"\";\n // #516: a selection modal renders its highlighted default option (\"❯ 1. Red\")\n // in the same HR-bounded region a real draft would occupy, so the settle\n // loop below can't tell \"modal open\" apart from \"draft present\" — sending\n // Enter here would CONFIRM the modal's default instead of delivering the\n // captain's message (mirrors the #484 guard on the sendToSurface delivery\n // path, which has no equivalent here). Never keystroke into it.\n if (hasModalOptionList(preSendScreen)) {\n return { delivered: false, blockedByModal: true };\n }\n await runtime.pasteToPane(pane, message);\n // #455: track whether the paste ever rendered so we don't treat an empty box\n // that was NEVER populated as a successful submit (race: paste still in flight\n // when settle fires, stable-empty → Enter into nothing → false \"submitted\").\n let sawDraft = await settleInputBox(runtime, pane);\n await runtime.sendKeyToPane(pane, \"Enter\");\n\n let repasted = false;\n for (let attempt = 0; attempt < SUBMIT_RETRY_LIMIT; attempt++) {\n await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));\n const afterScreen = (await runtime.readPaneScreen(pane)) ?? \"\";\n const draft = parseDraftFromScreen(afterScreen);\n if (draft !== \"\" && draft !== null) sawDraft = true;\n // Box confirmed empty AND we observed the paste rendered first → submitted.\n if (draft === \"\" && sawDraft) return { delivered: true };\n if (draft === null && afterScreen !== preSendScreen && sawDraft) return { delivered: true };\n const settled = await settleInputBox(runtime, pane);\n if (settled) sawDraft = true;\n // #455: paste never rendered — re-paste once rather than issuing Enter into emptiness.\n if (!sawDraft && !repasted) {\n repasted = true;\n await runtime.pasteToPane(pane, message);\n }\n await runtime.sendKeyToPane(pane, \"Enter\");\n }\n return { delivered: false };\n}\n\n/**\n * #466 daemon self-heal: the pane-touching primitive behind the daemon's\n * sweep-loop first-turn resend hook. Finds the crew's own pane by title (never\n * blind-sends anywhere), RE-CHECKS TUI readiness itself (never blind-pastes into\n * a still-booting box — CRITICAL SAFETY), and only then submits via the same\n * paste-settle-Enter path a manual `crew send` uses. Returns { delivered: false }\n * without touching the pane when the crew can't be found or isn't ready yet —\n * the caller (the daemon sweep loop) retries on a later tick.\n */\nexport async function resendCrewFirstTurn(\n runtime: Pick<RuntimeDriver, \"status\" | \"listSurfaces\" | \"readPaneScreen\" | \"pasteToPane\" | \"sendKeyToPane\">,\n captainName: string,\n project: string,\n name: string,\n message: string,\n): Promise<{ delivered: boolean }> {\n const captain = await runtime.status(captainName);\n if (!captain) return { delivered: false };\n const surfaces = await runtime.listSurfaces(captain.id);\n const want = titleFor(project, name);\n const pane = surfaces.find((s) => s.title === want);\n if (!pane) return { delivered: false };\n const screen = (await runtime.readPaneScreen(pane)) ?? \"\";\n if (!hasCCInputBox(screen) || classifyStartupSurface(screen) !== \"idle\") {\n return { delivered: false }; // still not ready — caller retries on a later tick\n }\n return confirmedSendToPane(runtime, pane, message);\n}\n\nexport async function sendFirstTurnWhenReady(\n runtime: Pick<RuntimeDriver, \"readPaneScreen\" | \"sendToPane\" | \"pasteToPane\" | \"sendKeyToPane\">,\n pane: PaneRef,\n task: string,\n preLaunchScreen: string,\n acceptanceConfig?: TurnAcceptanceConfig,\n): Promise<{ delivered: boolean }> {\n await new Promise((r) => setTimeout(r, SEND_FIRST_TURN_FLOOR_MS));\n\n const maxPolls = Math.floor(\n (SEND_FIRST_TURN_TIMEOUT_MS - SEND_FIRST_TURN_FLOOR_MS) / POLL_INTERVAL_MS,\n );\n let previousScreen = \"\";\n let stable = false;\n\n for (let i = 0; i < maxPolls && !stable; i++) {\n const screen = (await runtime.readPaneScreen(pane)) ?? \"\";\n // Ready = the agent prompt is actually up: screen is non-empty, settled\n // (unchanged between two consecutive reads), has advanced past the un-entered\n // launch command line, AND (for the claude/codex path) the CC input box is\n // rendered with its ❯ prompt glyph. hasCCInputBox is stricter than the old\n // parseDraftFromScreen(screen)!==null check: the claude-mem startup banner can\n // produce HR-bounded regions without a ❯ inside — parseDraftFromScreen returns\n // \"\" (≠ null) for those, falsely satisfying the old gate. hasCCInputBox\n // requires the ❯ to be present, so banners do not trigger a premature paste\n // (#466-single root cause). For the opencode splash path the splashMarker\n // short-circuits before this check, preserving existing behaviour.\n // #466 residual: the ❯ input box renders during cold-init while claude-mem's\n // MCP server is still loading — a window where keystrokes are silently dropped\n // (#235/#292). hasCCInputBox alone (just a ❯ between two HRs) is satisfied in\n // that window, so the old gate pasted into a box that dropped the keystrokes and\n // the first turn never landed. Adopt the captain startup contract: require the\n // surface to be CC-INITIALIZED (classifyStartupSurface === \"idle\", i.e. the\n // persistent bottom status block — Ctx Used / ⏵⏵ / shortcuts / accept edits — is\n // present and no turn is in flight), in addition to the ❯ box (keeps #469's\n // banner rejection). For the opencode splash path (#499), readiness is a\n // POSITIVE signal too: the marker must actually be visible on screen (not\n // hardcoded true) — otherwise a booting/mid-transition screen could be\n // declared \"stable\" before the TUI has rendered its idle splash at all.\n const ready = acceptanceConfig?.splashMarker\n ? screenHasSplashMarker(screen, acceptanceConfig.splashMarker)\n : hasCCInputBox(screen) && classifyStartupSurface(screen) === \"idle\";\n if (screen.length > 0 && screen === previousScreen && screen !== preLaunchScreen && ready) {\n stable = true;\n } else {\n previousScreen = screen;\n await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));\n }\n }\n\n // Snapshot the screen immediately before sending so the post-send check can\n // tell whether the keystrokes were received. Comparing against the raw task\n // text is unreliable: sendToPane collapses newlines to spaces (#136), so a\n // multi-line task never appears verbatim in the single-line pane render and\n // the check would always re-send a duplicate first turn (#168).\n const preSendScreen = (await runtime.readPaneScreen(pane)) ?? \"\";\n\n // Confirm-on-delivery (#235): poll until the TUI confirms it accepted the turn.\n if (acceptanceConfig?.splashMarker) {\n // Splash path (opencode): the \"Ask anything…\" splash clears once the TUI\n // consumes the message. We check every POST_SEND_CHECK_MS but re-send only\n // every SPLASH_RESEND_EVERY_N checks — a 3s de-dup guard that prevents\n // duplicate task execution when the TUI is slow to redraw after accepting.\n // opencode's TUI does not collapse pastes into placeholders, so the atomic\n // send+Enter (sendToPane) is correct here and must stay (it was just fixed\n // and live-verified in #235). The #339 paste race is claude-specific.\n //\n // #499: sawSplash latches once the marker has actually been observed on\n // screen. Only THEN does the marker's absence count as acceptance — this\n // mirrors the claude path's sawDraft gate. Without the latch, a marker that\n // never matches (drift, misconfiguration) makes isTurnAccepted return true\n // on the very first check, before any keystroke lands, and silently\n // confirms delivery of a turn that was never sent. With the latch, the same\n // situation exhausts the confirm window and fails closed (delivered:false),\n // surfacing the non-delivery warning instead.\n let sawSplash = screenHasSplashMarker(preSendScreen, acceptanceConfig.splashMarker);\n await runtime.sendToPane(pane, task);\n for (let check = 0; check < SPLASH_MAX_CHECKS; check++) {\n await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));\n const afterScreen = (await runtime.readPaneScreen(pane)) ?? \"\";\n if (screenHasSplashMarker(afterScreen, acceptanceConfig.splashMarker)) {\n sawSplash = true;\n } else if (sawSplash) {\n return { delivered: true };\n }\n if ((check + 1) % SPLASH_RESEND_EVERY_N === 0 && check < SPLASH_MAX_CHECKS - 1) {\n await runtime.sendToPane(pane, task);\n }\n }\n return { delivered: false };\n }\n\n // #466: if the box never appeared in the boot window, skip the paste path and\n // go directly to the settled-box fallback (confirmedSendToPane). By the time we\n // get here, more time has passed and the box is likely ready.\n if (!stable) {\n return confirmedSendToPane(runtime, pane, task);\n }\n\n // Claude/codex path (#339): paste the task, let the [Pasted text] placeholder\n // settle, THEN submit with a separate Enter. Bundling the CR with the paste\n // (the old sendToPane) lets Claude Code absorb it as a literal newline inside\n // the placeholder under load, stranding the whole turn unsubmitted. We confirm\n // the submit by the input box going empty — NOT by \"screen changed\", because\n // the paste itself changes the screen. If the box is still holding the draft we\n // re-issue ONLY the Enter (after re-settling) and NEVER re-paste — re-pasting is\n // exactly what stacks [Pasted text #1][#2][#3] and never submits.\n await runtime.pasteToPane(pane, task);\n // #455: track whether the paste ever rendered so we don't treat an empty box\n // that was NEVER populated as a successful submit (race: paste still in flight\n // when settle fires, stable-empty → Enter into nothing → false \"submitted\").\n let sawDraft = await settleInputBox(runtime, pane);\n await runtime.sendKeyToPane(pane, \"Enter\");\n\n const retryLimit = acceptanceConfig?.retryLimit ?? SUBMIT_RETRY_LIMIT;\n let repasted = false;\n for (let attempt = 0; attempt < retryLimit; attempt++) {\n await new Promise((r) => setTimeout(r, POST_SEND_CHECK_MS));\n const afterScreen = (await runtime.readPaneScreen(pane)) ?? \"\";\n const draft = parseDraftFromScreen(afterScreen);\n if (draft !== \"\" && draft !== null) sawDraft = true;\n // Box confirmed empty AND we observed the paste rendered first → submitted.\n if (draft === \"\" && sawDraft) return { delivered: true };\n // Box not parseable (e.g. an agent TUI without the HR-bounded box, or a\n // transient overlay): fall back to the screen-changed signal so non-claude\n // TUIs aren't worse off than before.\n if (draft === null && afterScreen !== preSendScreen && sawDraft) return { delivered: true };\n const settled = await settleInputBox(runtime, pane);\n if (settled) sawDraft = true;\n // #455: paste never rendered — re-paste once rather than issuing Enter into emptiness.\n if (!sawDraft && !repasted) {\n repasted = true;\n await runtime.pasteToPane(pane, task);\n }\n // Still stranded — re-issue ONLY the Enter (re-paste only when never rendered).\n await runtime.sendKeyToPane(pane, \"Enter\");\n }\n\n // #466: retry loop exhausted — if the paste never rendered (sawDraft=false),\n // the box was likely not ready when we pasted (the #466 timing race). Fall back\n // once to confirmedSendToPane which starts fresh on a now-settled box.\n // When sawDraft=true (paste rendered, Enter repeatedly failed), re-pasting would\n // stack [Pasted text] entries — do not retry, just report non-delivery.\n if (!sawDraft) {\n return confirmedSendToPane(runtime, pane, task);\n }\n return { delivered: false };\n}\n","// Best-effort \"daemon restarted\" broadcast to all running captains, fired on\n// daemon boot — but only when the running build actually changed (version or\n// local rebuild), not on a same-build launchd crash-restart. Routes through the\n// mailbox (appendCaptainMessage) so the daemon's delivery-loop drains it with\n// draft protection, instead of raw driver.send which clobbers the user's draft\n// (#529).\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { SquadrantConfig } from \"@squadrant/shared\";\n\n/** Minimal slice of RuntimeDriver used to resolve captain status. */\nexport interface DaemonRestartNotifyDriver {\n status(nameOrId: string): Promise<{ id: string } | null>;\n}\n\n/** Matches the appendCaptainMessage signature from @squadrant/core/mailbox.\n * The caller provides the closure with stateRoot already bound. */\nexport type AppendCaptainMessageFn = (project: string, text: string) => Promise<void | number>;\n\nfunction statePath(stateRoot: string): string {\n return path.join(stateRoot, \"daemon-restart-state.json\");\n}\n\n/** version + build-file mtime — differs on a version bump AND on a local\n * rebuild of the same version (mtime moves), but not on a plain restart. */\nexport function computeRestartSignature(version: string, buildMtimeMs: number): string {\n return `${version}::${buildMtimeMs}`;\n}\n\nexport function readPersistedRestartSignature(stateRoot: string): string | null {\n try {\n const raw = fs.readFileSync(statePath(stateRoot), \"utf-8\");\n const data = JSON.parse(raw) as { signature?: string };\n return typeof data.signature === \"string\" ? data.signature : null;\n } catch {\n return null;\n }\n}\n\nexport function writePersistedRestartSignature(stateRoot: string, signature: string): void {\n fs.mkdirSync(stateRoot, { recursive: true });\n fs.writeFileSync(statePath(stateRoot), JSON.stringify({ signature }, null, 2) + \"\\n\");\n}\n\nfunction restartNotice(version: string, isDevRebuild: boolean): string {\n const suffix = isDevRebuild ? \" (dev build)\" : \"\";\n return `⚠️ Daemon restarted → v${version}${suffix} (control-plane bounced). Re-verify in-flight crews — a crew mid-first-turn may need a crew send.`;\n}\n\n/**\n * Send the daemon-restart notice to every running captain via the mailbox.\n * Unlike notifyCaptainsOfEffort there is no initiating cwd captain to exclude —\n * the daemon boots independently of any captain — so this reaches ALL of them.\n * The appendCaptainMessage callback is a closure that captures stateRoot from\n * the caller (squadrantd.ts), so it takes (projectName, text).\n */\nexport async function notifyCaptainsOfDaemonRestart(\n version: string,\n config: SquadrantConfig,\n driver: DaemonRestartNotifyDriver,\n isDevRebuild = false,\n appendCaptainMessage: AppendCaptainMessageFn,\n): Promise<void> {\n const notice = restartNotice(version, isDevRebuild);\n for (const [projName] of Object.entries(config.projects)) {\n try {\n const proj = config.projects[projName];\n const ref = await driver.status(proj.captainName);\n if (ref) {\n await appendCaptainMessage(projName, notice);\n }\n } catch {\n // individual project captain unreachable — skip\n }\n }\n}\n\nexport interface MaybeBroadcastDaemonRestartOpts {\n version: string;\n buildMtimeMs: number;\n stateRoot: string;\n config: SquadrantConfig;\n driver: DaemonRestartNotifyDriver;\n appendCaptainMessage: AppendCaptainMessageFn;\n}\n\n/**\n * Boot-time entry point: compare this boot's (version, buildMtime) signature\n * against the last persisted one. Differs → broadcast + persist. Same → stay\n * silent (e.g. launchd crash-restart of an identical build). Fully\n * best-effort — never throws, so it can never block or crash daemon boot.\n */\nexport async function maybeBroadcastDaemonRestart(opts: MaybeBroadcastDaemonRestartOpts): Promise<void> {\n try {\n const { version, buildMtimeMs, stateRoot, config, driver, appendCaptainMessage } = opts;\n const signature = computeRestartSignature(version, buildMtimeMs);\n const previous = readPersistedRestartSignature(stateRoot);\n if (previous === signature) return;\n const isDevRebuild = previous !== null && previous.split(\"::\")[0] === version;\n await notifyCaptainsOfDaemonRestart(version, config, driver, isDevRebuild, appendCaptainMessage);\n writePersistedRestartSignature(stateRoot, signature);\n } catch {\n // best-effort — never let a broadcast failure block or crash daemon boot\n }\n}\n"],"mappings":"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","names":["distBuiltAt","join","dirname","homedir","fileURLToPath","readFileSync","statSync","fs","os","path","existsSync","readFileSync","writeFileSync","mkdirSync","rmSync","homedir","dirname","join","path","existsSync","readFileSync","writeFileSync","join","existsSync","readFileSync","homedir","join","join","writeFileSync","existsSync","readFileSync","join","homedir","path","readFileSync","statePath","mkdirSync","dirname","writeFileSync","existsSync","rmSync","fs","path","os","execFileSync","fs","path","fs","fs","path","fs","path","fs","path","fs","join","fs","existsSync","resolve","mkdirSync","readFileSync","writeFileSync","existsSync","rmSync","join","execFileSync","mkdirSync","writeFileSync","readFileSync","existsSync","unlinkSync","homedir","dirname","join","cmux","homedir","join","writeFileSync","mkdirSync","writeFileSync","readFileSync","renameSync","readFileSync","writeFileSync","renameSync","join","homedir","spawn","mkdirSync","writeFileSync","randomUUID","randomUUID","join","dirname","cmux","createServer","assembleDaemonSnapshot","fileURLToPath","join","statSync","openSync","closeSync","readdirSync","readFileSync","path","CURSOR_SUBSCRIBER","logPath","join","dirname","createServer","resolve","fs","path","resolve","sleep","fs","path","sep","os","path","sleep","appendCaptainMessage","path","os","project","fs","execFileSync","existsSync","homedir","join","join","homedir","randomUUID","homedir","join","DEFAULT_SOCK_PATH","join","homedir","fs","fs","os","path","path","os","execSync","execSync","execSync","execSync","fs","path","path","os","mkdir","readFile","writeFile","path","os","mkdir","readFile","writeFile","path","os","mkdir","readFile","writeFile","path","os","resolve","readFile","homedir","join","resolve","sleep","execSync","readFileSync","homedir","join","resolve","execFile","execFileSync","resolve","execFile","execSync","promisify","execFile","promisify","execSync","fs","existsSync","path","nodeSpawn","bin","args","nodeSpawn","sleep","resolve","readdirSync","readFileSync","join","homedir","readFile","join","homedir","readdirSync","readFileSync","join","homedir","readdirSync","readFileSync","existsSync","defaultIsPidAlive","path","join","homedir","mkdirSync","readFileSync","writeFileSync","readFile","defaultReadFile","writeFile","path","fs","path","statePath","appendCaptainMessage","SELF_PATH","fileURLToPath","join","dirname","homedir","readFileSync","spawn","statSync"]}
|