@mclawnet/agent 0.6.34 → 0.6.36
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/cli.js +75 -7
- package/dist/__tests__/bootstrap-deps.test.d.ts +2 -0
- package/dist/__tests__/bootstrap-deps.test.d.ts.map +1 -0
- package/dist/__tests__/collect-manifest.test.d.ts +2 -0
- package/dist/__tests__/collect-manifest.test.d.ts.map +1 -0
- package/dist/__tests__/hub-connection-on-activity.test.d.ts +2 -0
- package/dist/__tests__/hub-connection-on-activity.test.d.ts.map +1 -0
- package/dist/__tests__/hub-connection-wake-watch.test.d.ts +2 -0
- package/dist/__tests__/hub-connection-wake-watch.test.d.ts.map +1 -0
- package/dist/__tests__/ideas-rest-client.test.d.ts +2 -0
- package/dist/__tests__/ideas-rest-client.test.d.ts.map +1 -0
- package/dist/__tests__/legacy-claude-execute-compat.test.d.ts +2 -0
- package/dist/__tests__/legacy-claude-execute-compat.test.d.ts.map +1 -0
- package/dist/__tests__/no-adapter-cycle.test.d.ts +2 -0
- package/dist/__tests__/no-adapter-cycle.test.d.ts.map +1 -0
- package/dist/__tests__/runtime-env-defaults.test.d.ts +2 -0
- package/dist/__tests__/runtime-env-defaults.test.d.ts.map +1 -0
- package/dist/__tests__/session-manager-exit-reason.test.d.ts +2 -0
- package/dist/__tests__/session-manager-exit-reason.test.d.ts.map +1 -0
- package/dist/__tests__/session-manager-merge.test.d.ts +2 -0
- package/dist/__tests__/session-manager-merge.test.d.ts.map +1 -0
- package/dist/__tests__/session-manager-sticky.test.d.ts +2 -0
- package/dist/__tests__/session-manager-sticky.test.d.ts.map +1 -0
- package/dist/__tests__/session-protocol-dispatch.test.d.ts +2 -0
- package/dist/__tests__/session-protocol-dispatch.test.d.ts.map +1 -0
- package/dist/__tests__/worktree-bridge.test.d.ts +2 -0
- package/dist/__tests__/worktree-bridge.test.d.ts.map +1 -0
- package/dist/backend-adapter.d.ts +6 -232
- package/dist/backend-adapter.d.ts.map +1 -1
- package/dist/backend-factory-AFF6I7YF.js +11 -0
- package/dist/backend-factory.d.ts +23 -1
- package/dist/backend-factory.d.ts.map +1 -1
- package/dist/bootstrap-deps.d.ts +84 -0
- package/dist/bootstrap-deps.d.ts.map +1 -0
- package/dist/bootstrap-deps.js +173 -0
- package/dist/bootstrap-deps.js.map +1 -0
- package/dist/{chunk-PJ5M6Q36.js → chunk-376QZ7JB.js} +2 -2
- package/dist/chunk-376QZ7JB.js.map +1 -0
- package/dist/{chunk-2JDX6XFD.js → chunk-GOCWMRBB.js} +1817 -298
- package/dist/chunk-GOCWMRBB.js.map +1 -0
- package/dist/{chunk-M2CDVPQF.js → chunk-JH6RGJBQ.js} +2 -2
- package/dist/{chunk-MFXF77LG.js → chunk-VAEFJLPL.js} +25 -3
- package/dist/chunk-VAEFJLPL.js.map +1 -0
- package/dist/{dist-VLBO5CT3.js → dist-NWVHAP5R.js} +330 -23
- package/dist/dist-NWVHAP5R.js.map +1 -0
- package/dist/errors.d.ts +20 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/hub-connection.d.ts +25 -1
- package/dist/hub-connection.d.ts.map +1 -1
- package/dist/ideas-rest-client.d.ts +25 -0
- package/dist/ideas-rest-client.d.ts.map +1 -0
- package/dist/index.js +3 -3
- package/dist/{linux-IHA4O633.js → linux-MBU6ERXL.js} +3 -3
- package/dist/{macos-G4VK2253.js → macos-I2DUWFUH.js} +3 -3
- package/dist/projects-handler.d.ts +146 -1
- package/dist/projects-handler.d.ts.map +1 -1
- package/dist/runtime-env-defaults.d.ts +18 -0
- package/dist/runtime-env-defaults.d.ts.map +1 -0
- package/dist/service/index.js +5 -5
- package/dist/session-manager.d.ts +59 -0
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/start.d.ts.map +1 -1
- package/dist/start.js +3 -2
- package/dist/{windows-P6U3JLUZ.js → windows-PEJ3KOLC.js} +3 -3
- package/dist/worktree-bridge.d.ts +51 -0
- package/dist/worktree-bridge.d.ts.map +1 -0
- package/package.json +10 -8
- package/dist/backend-factory-RUYUBJVF.js +0 -9
- package/dist/chunk-2JDX6XFD.js.map +0 -1
- package/dist/chunk-MFXF77LG.js.map +0 -1
- package/dist/chunk-PJ5M6Q36.js.map +0 -1
- package/dist/dist-VLBO5CT3.js.map +0 -1
- /package/dist/{backend-factory-RUYUBJVF.js.map → backend-factory-AFF6I7YF.js.map} +0 -0
- /package/dist/{chunk-M2CDVPQF.js.map → chunk-JH6RGJBQ.js.map} +0 -0
- /package/dist/{linux-IHA4O633.js.map → linux-MBU6ERXL.js.map} +0 -0
- /package/dist/{macos-G4VK2253.js.map → macos-I2DUWFUH.js.map} +0 -0
- /package/dist/{windows-P6U3JLUZ.js.map → windows-PEJ3KOLC.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../codex-adapter/src/codex-adapter.ts","../../codex-adapter/src/codex-spawn-args.ts","../../codex-adapter/src/json-rpc-client.ts","../../codex-adapter/src/permission-mapper.ts","../../codex-adapter/src/output-mapper.ts","../../codex-adapter/src/catalog.ts","../../codex-adapter/src/detect.ts"],"sourcesContent":["import { execSync, spawn, type ChildProcess } from \"node:child_process\";\nimport { EventEmitter } from \"node:events\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir, platform } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { buildCodexSpawnArgs, cleanupBriefingFile, type McpServerConfig } from \"./codex-spawn-args.js\";\nimport type {\n BackendAdapter,\n BackendProcess,\n BackendOutput,\n PermissionDecision,\n PermissionRequest,\n SpawnOptions,\n} from \"@mclawnet/backend-types\";\nimport type { BackendManifestEntry } from \"@mclawnet/shared\";\nimport { createLogger, preview } from \"@mclawnet/logger\";\nimport { JsonRpcClient } from \"./json-rpc-client.js\";\nimport {\n buildApprovalReply,\n parseApprovalRequest,\n type ApprovalWireFamily,\n} from \"./permission-mapper.js\";\nimport { mapCodexFrame } from \"./output-mapper.js\";\nimport { CODEX_MODELS, CODEX_MODES } from \"./catalog.js\";\nimport { detectCodexInstall, type DetectResult } from \"./detect.js\";\n\nconst log = createLogger({ module: \"codex-adapter\" });\n\nconst isWin = platform() === \"win32\";\n\n/**\n * Resolve the codex CLI binary the same way claude-adapter resolves claude:\n * use the OS lookup tool (where/which), fall back to known install paths.\n *\n * Without this, Windows users hit two failure modes:\n * 1. `spawn(\"codex\", ...)` fails because Windows `CreateProcess` does NOT\n * consult PATHEXT — only cmd.exe does — so a bare \"codex\" never finds\n * the npm-installed `codex.cmd` / `codex.exe` shim.\n * 2. Even if cmd.exe is on the way, post-CVE-2024-27980 Node refuses to\n * spawn `.cmd` / `.bat` without `shell: true`.\n */\nfunction resolveCodexBin(explicit?: string): string {\n if (explicit && explicit !== \"codex\") return explicit;\n\n const names = isWin ? [\"codex.exe\", \"codex.cmd\", \"codex.bat\"] : [\"codex\"];\n const whichCmd = isWin ? \"where\" : \"which\";\n for (const name of names) {\n try {\n const found = execSync(`${whichCmd} ${name}`, {\n encoding: \"utf-8\",\n // 1.5s is plenty for `where`/`which` on a healthy machine; the old\n // 5s × 3 worst-case (15s blocking event loop) showed up on slow\n // domain-joined Windows hosts / network-PATH setups.\n timeout: 1500,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim().split(/\\r?\\n/)[0];\n if (found && existsSync(found)) return found;\n } catch {\n /* not in PATH */\n }\n }\n\n const home = homedir();\n const fallbacks = isWin\n ? [\n join(home, \"AppData\", \"Roaming\", \"npm\", \"codex.cmd\"),\n join(home, \"AppData\", \"Roaming\", \"npm\", \"codex.exe\"),\n join(home, \".cargo\", \"bin\", \"codex.exe\"),\n join(home, \".local\", \"bin\", \"codex.exe\"),\n ]\n : [\n join(home, \".cargo\", \"bin\", \"codex\"),\n join(home, \".local\", \"bin\", \"codex\"),\n \"/opt/homebrew/bin/codex\",\n \"/usr/local/bin/codex\",\n ];\n for (const p of fallbacks) {\n if (existsSync(p)) return p;\n }\n\n return isWin ? \"codex.cmd\" : \"codex\";\n}\n\n/**\n * Module-level cache for the resolved codex binary. Without this, every\n * CodexAdapter instance (created per-session by the agent's adapter factory)\n * pays the `where`/`which` cost again. On a Windows host where `where`\n * times out (slow PATH, network drive, AV scan), 3 lookups × 1.5s would\n * stall agent startup for every spawn.\n *\n * Cache is keyed on the override string so explicit per-instance overrides\n * (e.g. tests) bypass the shared cache.\n */\nconst resolvedCodexBinCache = new Map<string, string>();\nlet resolveCodexBinCallCount = 0;\nfunction getResolvedCodexBin(explicit?: string): string {\n const key = explicit ?? \"\";\n const hit = resolvedCodexBinCache.get(key);\n if (hit) return hit;\n resolveCodexBinCallCount++;\n const resolved = resolveCodexBin(explicit);\n resolvedCodexBinCache.set(key, resolved);\n return resolved;\n}\n\n/** Test-only: clear the resolved-bin cache between cases. */\nexport function __resetResolvedCodexBinCache(): void {\n resolvedCodexBinCache.clear();\n resolveCodexBinCallCount = 0;\n}\n\n/** Test-only: how many times resolveCodexBin actually ran (i.e. cache misses). */\nexport function __getResolveCodexBinCallCount(): number {\n return resolveCodexBinCallCount;\n}\n\n/**\n * Resolve how to spawn codex without invoking cmd.exe on Windows.\n *\n * codex.cmd is an npm shim that dispatches to the real Rust .exe (or in\n * some installs, a node-shimmed cli.js). Spawning the .cmd directly:\n * - requires `shell: true` post-CVE-2024-27980, which would also have to\n * deal with cmd.exe's 8191-char command-line cap.\n * - drops the child's stdio attachment in subtle ways through cmd.exe.\n *\n * So we parse the shim and spawn the underlying .exe (or `node cli.js`)\n * directly. Mirrors claude-adapter's resolveSpawnTarget pattern.\n */\nexport function resolveSpawnTarget(codexBin: string): {\n command: string;\n prefixArgs: string[];\n} {\n // Re-read platform per call (instead of using the module-level `isWin`\n // constant) so tests can flip `process.platform` to exercise Windows\n // paths from a macOS / Linux CI. Mirrors claude-adapter's pattern.\n const isWindows = platform() === \"win32\";\n if (!isWindows || !codexBin.toLowerCase().endsWith(\".cmd\")) {\n return { command: codexBin, prefixArgs: [] };\n }\n try {\n const cmdContent = readFileSync(codexBin, \"utf-8\");\n // codex shim typically points at a sibling .exe: e.g.\n // \"%~dp0\\node_modules\\@openai\\codex\\bin\\codex.exe\" %*\n const exeMatch = cmdContent.match(/(?:%~?dp0%?)[\\\\/](.+?\\.exe)/i);\n if (exeMatch) {\n const relParts = exeMatch[1].split(/[\\\\/]/);\n const exePath = join(dirname(codexBin), ...relParts);\n if (existsSync(exePath)) return { command: exePath, prefixArgs: [] };\n }\n // Fallback: npm-style `node cli.js` shim.\n const jsMatch = cmdContent.match(/(?:%~?dp0%?)[\\\\/](.+?\\.js)/i);\n if (jsMatch) {\n const relParts = jsMatch[1].split(/[\\\\/]/);\n const cliJsPath = join(dirname(codexBin), ...relParts);\n if (existsSync(cliJsPath)) {\n return { command: process.execPath, prefixArgs: [cliJsPath] };\n }\n }\n } catch {\n /* fall through */\n }\n // Last resort: invoke through cmd.exe. Loses the 8191-char cap protection\n // but at least the process gets spawned. Args are short for codex so this\n // is unlikely to bite.\n return { command: codexBin, prefixArgs: [] };\n}\n\nconst CLAWNET_CLIENT_INFO = {\n name: \"clawnet\",\n version: \"0.1.0\",\n title: \"ClawNet Agent\",\n};\n\n/**\n * Cap on the per-process `assistantTextSeen` map. Bounds memory growth\n * from interrupted agentMessage items that never fire `item/completed`\n * (a single very long session would otherwise leak forever now that\n * `turn/completed` no longer wholesale-clears — see the comment on that\n * branch in `handleNotification`).\n *\n * A single codex turn typically produces 1-2 agentMessage items; 64\n * comfortably covers extreme cases without making eviction reachable\n * during normal use. If the cap is exceeded, the OLDEST untouched entry\n * is dropped (FIFO); a subsequent matching `item/completed` for that\n * itemId would then see an empty accumulator and emit the full text,\n * producing at most one duplicated message instead of an unbounded leak.\n */\nconst ASSISTANT_TEXT_SEEN_MAX = 64;\n\n/**\n * Per-process state wrapper. Holds the JSON-RPC client + pending approval\n * resolvers so `respondToPermission` can find the right server-initiated\n * request id to reply to.\n */\nexport class CodexProcess extends EventEmitter implements BackendProcess {\n readonly id: string;\n readonly workDir: string;\n pid?: number;\n proc?: ChildProcess;\n private killed = false;\n /** callId → JSON-RPC server-request id we still owe a result to. */\n pendingApprovals = new Map<string, number>();\n /** callId → originating wire family (drives v2/legacy/mcp reply shape). */\n approvalMethods = new Map<string, ApprovalWireFamily>();\n /** Monotonic counter for approval resolver keys. */\n private nextResolverKey = 1;\n /** Resolver registered by the rpc client for each pending approval. */\n approvalResolvers = new Map<number, (reply: Record<string, string>) => void>();\n rpc?: JsonRpcClient;\n backendSessionId?: string;\n /**\n * Thread id to resume on agent restart. When set, handshake() sends\n * `thread/resume { threadId }` instead of `thread/start { cwd }`.\n * Set from SpawnOptions.resumeId in CodexAdapter.spawn(). Must NOT be\n * passed as `--resume` CLI flag — app-server rejects it.\n */\n resumeId?: string;\n /** True once `initialize` round-trip + `thread/start` (or `thread/resume`) have completed. */\n handshakeComplete = false;\n /** Inputs queued by send() while the handshake is still in flight. */\n pendingInputs: string[] = [];\n /**\n * Per-itemId accumulator of assistant text already emitted via\n * `item/agentMessage/delta` chunks. Consulted on `item/completed` with\n * `item.type === \"agentMessage\"` to decide what (if anything) of the\n * completed payload still needs to be emitted. Without this, codex\n * delivers the same assistant text twice (deltas + final) and the UI\n * renders \"Hello worldHello world\".\n *\n * Entries are cleared per item when the matching `item/completed` fires.\n * We do NOT clear the whole map on `turn/completed`: codex's wire\n * doesn't strictly guarantee that every item/completed precedes its\n * parent turn/completed, and a trailing item/completed against an empty\n * accumulator would re-emit the full text (reactivating the bug). To\n * cap unbounded growth from interrupted turns that never fire\n * completed, the map is size-bounded with FIFO eviction; in practice a\n * single turn produces 1-2 agentMessage items so the cap is generous.\n */\n assistantTextSeen = new Map<string, string>();\n /** Temp file for briefing injection; cleaned up on kill(). */\n briefingFile?: string;\n /**\n * Set by CodexAdapter.spawn() so handshake() can surface real process-exit\n * info if the codex CLI died (e.g. arg parse error). Without this, a dead\n * process leads to a generic \"handshake timeout\" instead of the actual\n * \"error: unexpected argument …\" stderr message.\n *\n * Default returns (null, \"\") for tests that construct CodexProcess\n * directly without going through spawn() (e.g. attachRpc mock pipes).\n * spawn() overrides these with real getters wired to the child process.\n */\n getExitInfo: () => { code: number | null; signal: string | null } | null = () => null;\n getStderr: () => string = () => \"\";\n\n /** Allocate a resolver key. Monotonic; never reused. */\n allocResolverKey(): number {\n return this.nextResolverKey++;\n }\n\n constructor(sessionId: string, workDir: string, proc?: ChildProcess) {\n super();\n this.id = sessionId;\n this.workDir = workDir;\n this.proc = proc;\n this.pid = proc?.pid;\n }\n\n async kill(): Promise<void> {\n this.killed = true;\n // Drain any pending approval bookkeeping so long-running hubs don't leak\n // entries on aborted sessions. Resolve any outstanding RPC promise with a\n // \"cancel\"-equivalent so callers awaiting `respondToPermission` never hang.\n for (const [, resolver] of this.approvalResolvers) {\n try {\n resolver({ decision: \"abort\" });\n } catch {\n // ignore\n }\n }\n this.pendingApprovals.clear();\n this.approvalMethods.clear();\n this.approvalResolvers.clear();\n this.pendingInputs.length = 0;\n this.assistantTextSeen.clear();\n if (this.briefingFile) {\n cleanupBriefingFile(this.briefingFile);\n this.briefingFile = undefined;\n }\n if (this.proc && !this.proc.killed) {\n try {\n this.proc.kill(\"SIGTERM\");\n } catch {\n // ignore\n }\n }\n }\n\n isAlive(): boolean {\n if (this.killed) return false;\n if (!this.proc) return true;\n return !this.proc.killed && this.proc.exitCode === null;\n }\n}\n\nexport interface CodexAdapterOptions {\n /** Override path to the codex binary. Default: lookup in PATH. */\n codexBin?: string;\n /**\n * Max time to wait for codex handshake (initialize + thread/start|resume)\n * to complete before emitting an error. Default: 15s. Used to detect the\n * case where the codex process dies mid-handshake (CLI parse error,\n * crash) — without this, pendingInputs stack up forever and the swarm\n * silently stalls.\n */\n handshakeTimeoutMs?: number;\n /**\n * Override the CLI detection probe. Defaults to `detectCodexInstall`.\n * Exposed for tests (ESM module exports aren't configurable, so we\n * inject the dependency instead of spying on the module).\n */\n detect?: () => Promise<DetectResult>;\n}\n\n/**\n * CodexAdapter — BackendAdapter wrapping `codex app-server --listen stdio://`.\n *\n * M3.S3 scope: spawn/stop/send/onOutput plus the permission flow\n * (`onPermissionRequest` / `respondToPermission`). Resume + token-budget +\n * MCP plumbing land in follow-up slices.\n */\nexport class CodexAdapter implements BackendAdapter {\n readonly type = \"codex\";\n private codexBin: string;\n private handshakeTimeoutMs: number;\n private detect: () => Promise<DetectResult>;\n\n constructor(options?: CodexAdapterOptions) {\n this.codexBin = getResolvedCodexBin(options?.codexBin);\n // Precedence: explicit constructor option > env var > 15s default. The\n // env var (CLAWNET_CODEX_HANDSHAKE_TIMEOUT_MS) is the escape hatch for\n // slow CI / cold-start machines where 15s isn't enough and the user\n // can't easily inject a CodexAdapterOptions override.\n const envTimeout = Number(process.env.CLAWNET_CODEX_HANDSHAKE_TIMEOUT_MS);\n this.handshakeTimeoutMs =\n options?.handshakeTimeoutMs ??\n (Number.isFinite(envTimeout) && envTimeout > 0 ? envTimeout : 15000);\n this.detect = options?.detect ?? detectCodexInstall;\n }\n\n async spawn(options: SpawnOptions): Promise<CodexProcess> {\n let cwd = options.workDir || process.cwd();\n if (!existsSync(cwd)) cwd = homedir();\n\n // Build args with MCP + briefing injection\n const mcpServer = this.buildMcpServerConfig(options);\n const { args, briefingFile, modeSource } = buildCodexSpawnArgs({\n sessionId: options.sessionId,\n resumeId: options.resumeId,\n systemPrompt: options.systemPrompt,\n mcpServer,\n sandbox: options.sandbox,\n model: options.model,\n mode: options.mode,\n });\n\n if (options.mode && modeSource !== \"options.mode\") {\n log.warn(\n { sessionId: options.sessionId, requestedMode: options.mode, modeSource },\n \"codex: invalid options.mode ignored, falling back\",\n );\n }\n\n log.info(\n { sessionId: options.sessionId, bin: this.codexBin, args, cwd, resumeId: options.resumeId },\n \"codex spawn: forking app-server\",\n );\n const { command, prefixArgs } = resolveSpawnTarget(this.codexBin);\n if (command !== this.codexBin) {\n log.info(\n { sessionId: options.sessionId, bin: this.codexBin, spawnCmd: command, prefixArgs },\n \"codex spawn: using resolved shim target (windows .cmd bypass)\",\n );\n }\n const proc = spawn(command, [...prefixArgs, ...args], {\n cwd,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: { ...process.env, ...(options.env ?? {}) },\n windowsHide: true,\n });\n // Capture exit state into a closure so handshake() can surface it if\n // initialize / thread RPC times out. Without this guard a dead codex\n // (e.g. CLI arg parse error → ~600ms after spawn) would leave the\n // adapter waiting handshakeTimeoutMs (default 15s) with a generic\n // \"handshake timeout\" message. With it, we report exit code + stderr.\n const stderrChunks: string[] = [];\n proc.stderr?.on(\"data\", (chunk: Buffer) => {\n const text = chunk.toString(\"utf8\").trim();\n if (text) {\n stderrChunks.push(text);\n log.warn({ sessionId: options.sessionId, stderr: text }, \"codex stderr\");\n }\n });\n let exitInfo: { code: number | null; signal: string | null } | null = null;\n // cp is built AFTER this Promise resolves, so capture a thunk and resolve\n // it lazily. proc.on(\"exit\") needs to re-emit on cp so the BackendAdapter\n // contract's onExit/onError subscribers (session-manager) actually fire —\n // without this re-emit, codex crashes silently never reach the swarm\n // coordinator's handleRoleCrashed path and the queen stays unaware.\n let createdCp: CodexProcess | undefined;\n // Shared emit helper: invoked both from the proc.on(\"exit\") handler and\n // the late-fire safety branch below. Centralizes the \"emit exit + maybe\n // emit error with stderr\" contract so future changes can't drift.\n const emitProcessExit = (cp: CodexProcess, code: number | null, signal: string | null) => {\n try { cp.emit(\"exit\", code); } catch { /* listener threw */ }\n const isClean = code === 0 || signal === \"SIGTERM\";\n if (isClean) return;\n // Surface stderr via \"error\" so the agent's onSessionError handler\n // can include the real failure cause (e.g. \"error: unexpected\n // argument\", codex auth missing) instead of a bare \"exit code=1\".\n const stderr = stderrChunks.join(\"\\n\").trim().slice(0, 800);\n const detail = stderr ? ` — stderr: ${stderr}` : \"\";\n try {\n cp.emit(\n \"error\",\n new Error(`codex process exited (code=${code ?? \"null\"}, signal=${signal ?? \"null\"})${detail}`),\n );\n } catch { /* listener threw */ }\n };\n proc.on(\"exit\", (code, signal) => {\n const isClean = code === 0 || signal === \"SIGTERM\";\n const level = isClean ? \"debug\" : \"warn\";\n log[level]({ sessionId: options.sessionId, code, signal }, \"codex process exited\");\n if (!exitInfo) exitInfo = { code, signal };\n const cpRef = createdCp;\n if (!cpRef) return;\n emitProcessExit(cpRef, code, signal);\n });\n\n await new Promise<void>((resolve, reject) => {\n const t = setTimeout(() => resolve(), 2000);\n proc.on(\"error\", (err) => {\n clearTimeout(t);\n reject(new Error(`Failed to spawn codex CLI: ${err.message}`));\n });\n proc.on(\"spawn\", () => {\n clearTimeout(t);\n log.debug({ sessionId: options.sessionId, pid: proc.pid }, \"codex spawn: process started\");\n resolve();\n });\n });\n\n const cp = new CodexProcess(options.sessionId, cwd, proc);\n cp.briefingFile = briefingFile;\n cp.resumeId = options.resumeId;\n // Expose process-exit signal to handshake so a timeout can report the\n // real cause instead of a generic \"timeout\".\n cp.getExitInfo = () => exitInfo;\n cp.getStderr = () => stderrChunks.join(\"\\n\").slice(0, 800);\n createdCp = cp;\n // Late-fire safety: if the process exited BEFORE we got here (cp wasn't\n // assigned yet so the proc.on(\"exit\") path skipped the re-emit), surface\n // it now via the same channel. setImmediate defers until session-manager\n // has had a chance to subscribe (which happens synchronously after our\n // returned cp lands).\n if (exitInfo) {\n const { code, signal } = exitInfo;\n setImmediate(() => emitProcessExit(cp, code, signal));\n }\n this.wireRpc(cp);\n return cp;\n }\n\n private buildMcpServerConfig(options: SpawnOptions): McpServerConfig | undefined {\n // Resolve clawnet-mcp-server path. The package's \"exports\" map is a\n // closed whitelist (only \".\" and \"./package.json\" are exported, plus\n // \"./server\"). Resolving `@mclawnet/mcp-server/dist/server.js` directly\n // throws ERR_PACKAGE_PATH_NOT_EXPORTED on modern Node, which is why\n // packaged-release Windows hosts saw \"not resolvable\" while the dev\n // monorepo path silently worked.\n //\n // Strategy: try the supported subpath export first (\"./server\"), then\n // resolve via package.json + join, then fall back to the legacy direct\n // path (still useful when \"./server\" isn't published yet but a hub\n // installs an older mcp-server release), then dev-monorepo fallback.\n let serverPath: string | undefined;\n const require = createRequire(import.meta.url);\n const tryResolve = (specifier: string): string | undefined => {\n try {\n const p = require.resolve(specifier);\n return p && existsSync(p) ? p : undefined;\n } catch {\n return undefined;\n }\n };\n serverPath = tryResolve(\"@mclawnet/mcp-server/server\");\n if (!serverPath) {\n try {\n const pkgPath = require.resolve(\"@mclawnet/mcp-server/package.json\");\n const candidate = join(dirname(pkgPath), \"dist\", \"server.js\");\n if (existsSync(candidate)) serverPath = candidate;\n } catch {\n /* not installed */\n }\n }\n if (!serverPath) {\n serverPath = tryResolve(\"@mclawnet/mcp-server/dist/server.js\");\n }\n if (!serverPath) {\n const devPath = join(\n import.meta.dirname ?? __dirname,\n \"../../mcp-server/dist/server.js\",\n );\n if (existsSync(devPath)) serverPath = devPath;\n }\n if (!serverPath) {\n log.warn(\n { pkg: \"@mclawnet/mcp-server\" },\n \"codex: clawnet-mcp-server not resolvable, codex role will lack MCP tools\",\n );\n return undefined;\n }\n log.debug({ serverPath }, \"codex: resolved clawnet-mcp-server path\");\n const env: Record<string, string> = {};\n if (options.workDir) env.CLAWNET_WORK_DIR = options.workDir;\n // mcp-server's server.ts:28 explicitly documents: CLAWNET_HOME is the\n // user home (NO `.clawnet` suffix) — the server appends `.clawnet`\n // itself via projectRoot(). Passing `~/.clawnet` here would yield\n // `~/.clawnet/.clawnet/...` (double append) and break inbox/task layout.\n const home = process.env.CLAWNET_HOME ?? homedir();\n env.CLAWNET_HOME = home;\n return { command: \"node\", args: [serverPath], env };\n }\n\n /**\n * Bind a CodexProcess to a JSON-RPC duplex. Public so tests can inject a\n * mocked stdin/stdout pair without spawning a real subprocess.\n *\n * Drives the v2 handshake automatically: `initialize` → `thread/start`.\n * Once `thread/start` resolves, `cp.backendSessionId` is set and a\n * `session_started` event fires so the agent's hub bridge can persist it.\n */\n attachRpc(\n cp: CodexProcess,\n stdin: NodeJS.WritableStream,\n stdout: NodeJS.ReadableStream,\n ): void {\n const rpc = new JsonRpcClient({\n stdin: stdin as never,\n stdout: stdout as never,\n onRequest: (method, params) => this.handleServerRequest(cp, method, params),\n onNotification: (method, params) => this.handleNotification(cp, method, params),\n onMalformedLine: (line, err) =>\n log.warn(\n { sessionId: cp.id, err: err.message, linePreview: line.slice(0, 200) },\n \"codex stdout: non-JSON line ignored\",\n ),\n });\n cp.rpc = rpc;\n void this.handshake(cp);\n }\n\n private async handshake(cp: CodexProcess): Promise<void> {\n if (!cp.rpc) return;\n const rpc = cp.rpc;\n const resumeId = cp.resumeId;\n log.info(\n { sessionId: cp.id, workDir: cp.workDir, resumeId },\n \"codex handshake: start\",\n );\n\n // Wrap the whole handshake in a timeout so a dead codex process (e.g.\n // killed by `--resume` CLI parse error, segfault, etc.) doesn't leave\n // pendingInputs queued forever. Without this guard the swarm silently\n // stalls — the user sees no error, just nothing happens.\n //\n // raceSettled is declared up here (before the IIFE) so the handshake\n // success path can also check it. If timeout/death wins the race,\n // handshakeAttempt may still complete later — without this guard it\n // would mutate cp.backendSessionId, emit session_started, and flush\n // pendingInputs against a process the caller already considers dead.\n let raceSettled = false;\n const handshakeAttempt = (async () => {\n await rpc.request(\"initialize\", { clientInfo: CLAWNET_CLIENT_INFO });\n log.info({ sessionId: cp.id }, \"codex handshake: initialize ok\");\n\n // Branch on resumeId — `codex app-server` rejects `--resume` CLI flag,\n // so resume MUST happen at the JSON-RPC layer via `thread/resume`.\n // `thread/start` on a resumed codex returns a NEW threadId (losing\n // conversation continuity), which is why we have to branch here.\n let threadId: string | undefined;\n if (resumeId) {\n const resumed = (await rpc.request(\"thread/resume\", { threadId: resumeId })) as\n | { thread?: { id?: string }; threadId?: string }\n | undefined;\n // Don't silently fall back to the input resumeId — if codex ever\n // migrates the thread id (the protocol allows it) we'd persist a\n // stale id and break the next restart. Treat absent thread id as a\n // protocol error so the caller sees it and can re-spawn fresh.\n threadId = resumed?.thread?.id ?? resumed?.threadId;\n } else {\n // v2 `thread/start` returns { thread: { id, sessionId, ... }, ... }.\n // The resumable identifier is `thread.id` — `sessionId` here is the\n // tree-id shared by forked threads, not what `thread/resume` wants.\n const started = (await rpc.request(\"thread/start\", { cwd: cp.workDir })) as\n | { thread?: { id?: string }; threadId?: string }\n | undefined;\n threadId = started?.thread?.id ?? started?.threadId;\n }\n\n if (!threadId) {\n throw new Error(\n `codex handshake: ${resumeId ? \"thread/resume\" : \"thread/start\"} returned no threadId`,\n );\n }\n // Race lost (timeout/death already won) → don't mutate state, don't\n // emit session_started, don't flush queued inputs. The handshake\n // error has already been reported via cp.emit(\"error\") in the\n // catch-block below.\n if (raceSettled) {\n log.warn(\n { sessionId: cp.id, threadId },\n \"codex handshake resolved AFTER race already lost — discarding result\",\n );\n return;\n }\n cp.backendSessionId = threadId;\n cp.handshakeComplete = true;\n log.info(\n { sessionId: cp.id, threadId, queuedInputs: cp.pendingInputs.length },\n \"codex handshake: complete, flushing queued inputs\",\n );\n cp.emit(\"session_started\", { backendSessionId: threadId });\n // Flush any inputs that arrived while the handshake was in flight.\n const queued = cp.pendingInputs.splice(0);\n for (const input of queued) {\n this.dispatchTurn(cp, input);\n }\n })();\n // Attach a no-op catch so handshakeAttempt rejecting AFTER losing the\n // race (timeout or death already won) doesn't bubble as an unhandled\n // promise rejection — node --unhandled-rejections=strict would crash.\n // The real error already surfaced via cp.emit(\"error\") above.\n handshakeAttempt.catch(() => {});\n\n let timeoutHandle: NodeJS.Timeout | undefined;\n let exitPollHandle: NodeJS.Timeout | undefined;\n // raceSettled is checked by the death-poll loop and the handshake\n // success path. clearTimeout in finally{} alone is insufficient:\n // poll() may be mid-execution when finally runs, scheduling the next\n // setTimeout before clear; the success path could likewise resolve a\n // late RPC after the race has been decided.\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutHandle = setTimeout(() => {\n reject(\n new Error(\n `codex handshake timeout after ${this.handshakeTimeoutMs}ms (process may have died)`,\n ),\n );\n }, this.handshakeTimeoutMs);\n });\n\n // Process-death detector: poll exitInfo every 100ms; if codex exits\n // mid-handshake (e.g. CLI parse error), reject early with the real\n // stderr instead of waiting handshakeTimeoutMs (15s default) for the\n // generic timeout message.\n const deathPromise = new Promise<never>((_, reject) => {\n const poll = () => {\n if (raceSettled) return; // race already over — stop recursing\n const exit = cp.getExitInfo();\n if (exit) {\n const stderr = cp.getStderr();\n reject(\n new Error(\n `codex process died during handshake ` +\n `(code=${exit.code}, signal=${exit.signal}). ` +\n `stderr: ${stderr || \"(empty)\"}`,\n ),\n );\n return;\n }\n exitPollHandle = setTimeout(poll, 100);\n };\n poll();\n });\n\n try {\n await Promise.race([handshakeAttempt, timeoutPromise, deathPromise]);\n } catch (err) {\n log.error({ err, sessionId: cp.id }, \"codex v2 handshake failed\");\n cp.emit(\"error\", err);\n } finally {\n raceSettled = true;\n if (timeoutHandle) clearTimeout(timeoutHandle);\n if (exitPollHandle) clearTimeout(exitPollHandle);\n }\n }\n\n private wireRpc(cp: CodexProcess) {\n const proc = cp.proc;\n if (proc?.stdin && proc?.stdout) {\n this.attachRpc(cp, proc.stdin, proc.stdout);\n }\n }\n\n private handleServerRequest(\n cp: CodexProcess,\n method: string,\n params: unknown,\n ): Promise<unknown> {\n const parsed = parseApprovalRequest(method, params);\n if (!parsed) {\n log.warn({ method }, \"codex server-request: unrecognized method\");\n return Promise.resolve({ decision: \"denied\" });\n }\n const { req, wireFamily } = parsed;\n return new Promise((resolve) => {\n const resolverKey = cp.allocResolverKey();\n cp.pendingApprovals.set(req.callId, resolverKey);\n cp.approvalMethods.set(req.callId, wireFamily);\n cp.approvalResolvers.set(resolverKey, (reply) => {\n resolve(reply);\n });\n cp.emit(\"permission_request\", req);\n });\n }\n\n private handleNotification(cp: CodexProcess, method: string, params: unknown) {\n // v2 thread lifecycle\n if (method === \"thread/started\") {\n // codex emits this as a *notification* during normal turns too; the\n // primary session_started signal comes from thread/start's RPC result\n // (see handshake()). Suppress the duplicate here so onSessionStarted\n // listeners only fire once.\n return;\n }\n if (method === \"error\") {\n // codex `error` notifications carry the real failure cause (e.g.\n // \"Missing environment variable: COPILOT_API_KEY\", upstream provider\n // failure). Before this branch existed they degraded to {kind:\"raw\"} →\n // normalize-backend-output dropped them silently → swarm coordinator\n // never learned the role had failed → queen kept nudging a dead\n // worker. Promote to cp.emit(\"error\") so session-manager.onError →\n // onSessionError fires.\n const p = (params ?? {}) as {\n error?: { message?: string; codexErrorInfo?: string };\n willRetry?: boolean;\n };\n const message = p.error?.message ?? \"codex emitted unspecified error\";\n log.warn({ sessionId: cp.id, params }, \"codex error notification\");\n cp.emit(\"error\", new Error(`codex: ${message}`));\n // willRetry:false is codex saying \"this turn is dead, no retry\". Kill\n // the process so proc.on(\"exit\") fires → session-manager.onSessionExit\n // → swarmCoordinator.handleRoleCrashed flips the role to crashed and\n // wakes the queen. Leaving it alive would burn an idle process and\n // continue queen nudge loops.\n if (p.willRetry === false) {\n void cp.kill().catch((err) => {\n log.warn({ err, sessionId: cp.id }, \"kill after fatal codex error failed\");\n });\n }\n return;\n }\n if (method === \"thread/status/changed\") {\n // Codex pairs systemError status with an `error` notification that\n // carries willRetry. We forward the status as onError so the swarm\n // sees the failure signal immediately, but defer the kill decision to\n // the paired `error` frame (it has the willRetry bit). Other status\n // transitions (active, idle, running, …) are benign lifecycle events\n // — silently consume them, otherwise mapCodexFrame would degrade them\n // to {kind:\"raw\"} and spam log.warn for every normal turn.\n const p = (params ?? {}) as { status?: { type?: string } };\n if (p.status?.type === \"systemError\") {\n log.warn({ sessionId: cp.id, params }, \"codex thread systemError\");\n cp.emit(\"error\", new Error(\"codex: thread entered systemError\"));\n }\n return;\n }\n if (method === \"turn/completed\" || method === \"thread/turnComplete\" || method === \"turn/complete\") {\n log.info({ sessionId: cp.id, method }, \"codex turn complete\");\n // Token usage is delivered via the separate\n // `thread/tokenUsageUpdated` notification — the `turn` payload here\n // does NOT carry a `usage` field per the v2 schema.\n cp.emit(\"turn_complete\", {\n backendSessionId: cp.backendSessionId,\n });\n // Intentionally do NOT clear cp.assistantTextSeen here. A trailing\n // item/completed (channel reorder, codex internal buffering) against\n // an empty accumulator would re-emit the full agentMessage text and\n // bring back the original duplication bug. Unbounded growth from\n // interrupted turns that never fire item/completed is bounded by\n // ASSISTANT_TEXT_SEEN_MAX + FIFO eviction in the delta branch below.\n return;\n }\n if (method === \"turn/started\") {\n log.info({ sessionId: cp.id }, \"codex turn started\");\n return;\n }\n // Dedupe assistant text: codex streams chunks via\n // `item/agentMessage/delta` AND then re-sends the same full text via\n // `item/completed{ item.type: \"agentMessage\" }`. Without dedupe the UI\n // events-reducer (which appends every `text` event to the open message)\n // renders the message twice.\n //\n // Strategy: accumulate per-itemId delta text; on completed-agentMessage\n // emit only the suffix the deltas didn't already cover. delta is treated\n // as an optional streaming hint; completed is the source of truth.\n if (method === \"item/agentMessage/delta\") {\n const p = (params ?? {}) as { itemId?: unknown; delta?: unknown };\n if (typeof p.itemId === \"string\" && typeof p.delta === \"string\") {\n const prev = cp.assistantTextSeen.get(p.itemId) ?? \"\";\n // FIFO eviction guard: cap the map so an interrupted turn that\n // never fires item/completed for an open agentMessage can't leak\n // unbounded entries over a long-lived session. Map iteration is\n // insertion order, so deleting the first key drops the oldest.\n // Only check size when inserting a NEW key — updates of an\n // existing key don't grow the map.\n if (\n !cp.assistantTextSeen.has(p.itemId) &&\n cp.assistantTextSeen.size >= ASSISTANT_TEXT_SEEN_MAX\n ) {\n const oldest = cp.assistantTextSeen.keys().next().value;\n if (oldest !== undefined) cp.assistantTextSeen.delete(oldest);\n }\n cp.assistantTextSeen.set(p.itemId, prev + p.delta);\n } else {\n // Telemetry: surface protocol drift instead of silently degrading\n // to \"no dedupe → full text duplicate\" on the matching completed.\n log.warn(\n { sessionId: cp.id, params: p },\n \"codex agentMessage delta: missing itemId/delta — dedupe will not apply to this item\",\n );\n }\n // Fall through to mapper, which emits {kind:\"assistant_text\", text:delta}.\n } else if (method === \"item/completed\") {\n const p = (params ?? {}) as { item?: { id?: unknown; type?: unknown; text?: unknown } };\n const item = p.item;\n if (item?.type === \"agentMessage\") {\n // Always short-circuit for agentMessage — never fall through to\n // mapper. Mapper has no per-itemId delta history and would re-emit\n // the full text against an empty accumulator, reactivating the\n // duplication bug under protocol drift (e.g. codex changes itemId\n // type).\n if (typeof item.id !== \"string\" || typeof item.text !== \"string\") {\n log.warn(\n { sessionId: cp.id, item },\n \"codex agentMessage completed: malformed item.id/text — dropping to avoid duplicate emission\",\n );\n return;\n }\n const seen = cp.assistantTextSeen.get(item.id) ?? \"\";\n cp.assistantTextSeen.delete(item.id);\n const full = item.text;\n let suffix: string;\n if (full === seen) {\n // Deltas already covered the full text — drop the redundant frame.\n return;\n } else if (seen.length > 0 && full.startsWith(seen)) {\n // Deltas covered a prefix; emit only what's left.\n suffix = full.slice(seen.length);\n } else {\n // No deltas seen (resume / cached / short-circuit) OR the final\n // text diverges from the streamed prefix (codex revised the\n // response). In both cases the full completed text is the source\n // of truth; emitting it preserves correctness even if the UI ends\n // up showing both the partial draft and the final answer.\n suffix = full;\n if (seen.length > 0) {\n log.warn(\n { sessionId: cp.id, itemId: item.id, seenLen: seen.length, fullLen: full.length },\n \"codex agentMessage: completed text diverges from accumulated deltas — emitting full\",\n );\n }\n }\n if (suffix.length === 0) return;\n const out: BackendOutput = { kind: \"assistant_text\", text: suffix };\n this.logBackendOutput(cp.id, method, out);\n cp.emit(\"output\", out);\n return;\n }\n // Non-agentMessage item/completed (commandExecution, fileChange,\n // mcpToolCall, …) — fall through to mapper.\n }\n const out: BackendOutput = mapCodexFrame({ method, params });\n this.logBackendOutput(cp.id, method, out);\n cp.emit(\"output\", out);\n }\n\n /**\n * Selective notification logging. The previous catch-all `log.debug(\"codex\n * notification\")` fired for every streaming `item/agentMessage/delta` chunk\n * (many per turn), drowning the actually useful events. Instead, log only\n * structural events at INFO and surface unrecognised methods as WARN so the\n * `{kind:\"raw\"}` degradation isn't silent.\n */\n private logBackendOutput(sessionId: string, method: string, out: BackendOutput): void {\n // Streaming text deltas — too high-frequency to log per-chunk.\n if (method === \"item/agentMessage/delta\") return;\n // Other token-usage / progress notifications without a structural meaning.\n if (method === \"thread/tokenUsageUpdated\") return;\n\n switch (out.kind) {\n case \"tool_use\":\n log.info(\n { sessionId, callId: out.callId, tool: out.toolName, input: preview(out.input, 120) },\n \"codex tool_use\",\n );\n return;\n case \"tool_result\":\n log.info(\n { sessionId, callId: out.callId, isError: out.isError, output: preview(out.output, 120) },\n \"codex tool_result\",\n );\n return;\n case \"assistant_text\":\n log.debug(\n { sessionId, len: out.text.length, text: preview(out.text, 120) },\n \"codex assistant_text\",\n );\n return;\n case \"raw\":\n log.warn({ sessionId, method, payload: preview(out.payload, 200) }, \"codex unhandled frame\");\n return;\n }\n }\n\n async stop(process: BackendProcess): Promise<void> {\n await process.kill();\n }\n\n send(process: BackendProcess, input: string): void {\n if (!(process instanceof CodexProcess)) {\n log.warn(\n { sessionId: (process as { id?: string })?.id, len: input.length },\n \"codex send: not a CodexProcess — input dropped\",\n );\n return;\n }\n if (!process.rpc) {\n log.warn({ sessionId: process.id, len: input.length }, \"codex send: no rpc — input dropped\");\n return;\n }\n if (!process.backendSessionId) {\n // Handshake still in flight: queue the input and let `handshake()`\n // flush it once `thread/start` resolves. This avoids silent drops when\n // a hub author moves from claude-adapter (no handshake) to codex.\n process.pendingInputs.push(input);\n log.info(\n { sessionId: process.id, queueDepth: process.pendingInputs.length, len: input.length },\n \"codex send: handshake pending, queued\",\n );\n return;\n }\n log.info(\n { sessionId: process.id, threadId: process.backendSessionId, len: input.length },\n \"codex send: dispatching turn/start\",\n );\n this.dispatchTurn(process, input);\n }\n\n private dispatchTurn(process: CodexProcess, input: string): void {\n if (!process.rpc || !process.backendSessionId) return;\n // codex v2: turn/start is a *request* (server returns a result when the\n // turn is queued). We fire-and-forget here because the agent loop reacts\n // to async `turn/completed` / `item/...` notifications, not to the\n // request's result envelope.\n void process.rpc\n .request(\"turn/start\", {\n threadId: process.backendSessionId,\n input: [{ type: \"text\", text: input }],\n })\n .then(() => {\n log.info(\n { sessionId: process.id, threadId: process.backendSessionId },\n \"codex turn/start: accepted by server\",\n );\n })\n .catch((err: Error) => {\n log.warn({ err, sessionId: process.id }, \"turn/start failed\");\n process.emit(\"error\", err);\n });\n }\n\n onOutput(process: BackendProcess, handler: (msg: unknown) => void): void {\n if (process instanceof CodexProcess) process.on(\"output\", handler);\n }\n\n onPermissionRequest(\n process: BackendProcess,\n handler: (req: PermissionRequest) => void,\n ): void {\n if (process instanceof CodexProcess) process.on(\"permission_request\", handler);\n }\n\n async respondToPermission(\n process: BackendProcess,\n decision: PermissionDecision,\n ): Promise<void> {\n if (!(process instanceof CodexProcess)) return;\n const key = process.pendingApprovals.get(decision.callId);\n if (key === undefined) {\n log.warn({ callId: decision.callId }, \"respondToPermission: no pending approval\");\n return;\n }\n const family = process.approvalMethods.get(decision.callId) ?? \"legacy\";\n const resolver = process.approvalResolvers.get(key);\n process.pendingApprovals.delete(decision.callId);\n process.approvalMethods.delete(decision.callId);\n process.approvalResolvers.delete(key);\n const reply = buildApprovalReply(family, decision.decision);\n resolver?.(reply);\n }\n\n onTurnComplete(\n process: BackendProcess,\n handler: (info: { backendSessionId?: string }) => void,\n ): void {\n if (process instanceof CodexProcess) process.on(\"turn_complete\", handler);\n }\n\n onSessionStarted(\n process: BackendProcess,\n handler: (info: { backendSessionId: string }) => void,\n ): void {\n if (process instanceof CodexProcess) process.on(\"session_started\", handler);\n }\n\n onError(process: BackendProcess, handler: (err: Error) => void): void {\n if (process instanceof CodexProcess) process.on(\"error\", handler);\n }\n\n onExit(process: BackendProcess, handler: (code: number | null) => void): void {\n if (process instanceof CodexProcess) process.on(\"exit\", handler);\n }\n\n async getManifest(): Promise<BackendManifestEntry> {\n const det = await this.detect();\n return {\n kind: \"codex\",\n installed: det.installed,\n binaryPath: det.binaryPath,\n version: det.version,\n unavailableReason: det.reason,\n models: det.installed ? CODEX_MODELS : [],\n modes: det.installed ? CODEX_MODES : [],\n };\n }\n}\n","/**\n * codex-spawn-args — construct CLI args for `codex app-server` with\n * per-spawn MCP server + briefing injection.\n *\n * Extracted from CodexAdapter.spawn() so it can be unit-tested without\n * spawning a real child process.\n */\nimport { writeFileSync, unlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { randomUUID } from \"node:crypto\";\nimport { DEFAULT_SANDBOX, SANDBOX_LEVELS, type SandboxLevel } from \"@mclawnet/shared\";\n\nexport interface McpServerConfig {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n}\n\nexport interface CodexSpawnArgsOptions {\n sessionId: string;\n resumeId?: string;\n systemPrompt?: string;\n mcpServer?: McpServerConfig;\n /**\n * Per-role sandbox level. Maps to codex's `sandbox_mode` config key:\n * - \"read-only\" → `read-only`\n * - \"workspace-write\" → `workspace-write` (default)\n * - \"full-access\" → `danger-full-access` (codex's internal name; we\n * expose the friendlier label upstream)\n * Undefined falls back to \"workspace-write\" so existing callers see no\n * behavior change.\n */\n sandbox?: SandboxLevel;\n /**\n * (N3) Sticky model code from SessionManager. Mapped to codex's\n * `-c model=\"...\"`. Codex passes this directly to OpenAI's API.\n * Omitted = codex CLI uses its own default.\n */\n model?: string;\n /**\n * (N3) Sticky mode code from SessionManager. For codex this is alias for\n * sandbox (catalog uses same axis). Resolution: options.mode wins over\n * options.sandbox only if it's a valid codex sandbox value; otherwise\n * options.sandbox path is used.\n */\n mode?: string;\n}\n\nexport interface CodexSpawnArgsResult {\n args: string[];\n briefingFile?: string;\n /** (N3) Which precedence layer supplied the final sandbox_mode. */\n modeSource?: \"options.mode\" | \"options.sandbox\" | \"default\";\n}\n\nfunction escapeToml(value: string): string {\n return value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n}\n\n// N3: module-level Set reusing shared SANDBOX_LEVELS so adding a new\n// sandbox level there auto-propagates here. Allocated once per process,\n// not per buildCodexSpawnArgs call.\nconst VALID_CODEX_SANDBOX: ReadonlySet<SandboxLevel> = new Set(SANDBOX_LEVELS);\n\nexport function buildCodexSpawnArgs(options: CodexSpawnArgsOptions): CodexSpawnArgsResult {\n const args: string[] = [\"app-server\"];\n let briefingFile: string | undefined;\n\n // NOTE: `--resume <id>` is NOT a valid `codex app-server` flag. It only\n // exists on `codex` interactive / `codex exec`. Passing it to app-server\n // makes the CLI exit with \"error: unexpected argument '--resume' found\",\n // and the calling spawn() doesn't notice the immediate exit, so all\n // JSON-RPC traffic hangs. Resume is implemented at the protocol layer\n // by sending `thread/resume { threadId }` from CodexAdapter.handshake()\n // when CodexProcess.resumeId is set. `options.resumeId` is kept on the\n // CodexProcess for the handshake to read; we do NOT add it to args.\n void options.resumeId;\n\n // Non-interactive runtime policy: swarm roles run unattended, so MCP tool\n // calls must not be gated on a user-facing approval prompt. Without\n // `approval_policy=\"never\"`, codex defaults to `on-request` and rejects\n // every MCP tool call with \"user rejected MCP tool call\" because there's\n // no elicitation handler wired on the client side. `workspace-write`\n // matches the role's workDir as the writable root (analogous to claude-\n // adapter's `--permission-mode bypassPermissions` + workspace boundary).\n args.push(\"-c\", 'approval_policy=\"never\"');\n // Map ClawNet's abstract sandbox level to codex's mode name. We expose\n // \"full-access\" to users for readability; codex's internal mode is\n // named \"danger-full-access\". Default (undefined) → DEFAULT_SANDBOX\n // from @mclawnet/shared so existing swarms keep current behavior.\n // N3: Resolve sandbox from options.mode > options.sandbox > DEFAULT_SANDBOX.\n let sandboxLevel: SandboxLevel;\n let modeSource: \"options.mode\" | \"options.sandbox\" | \"default\";\n if (options.mode && VALID_CODEX_SANDBOX.has(options.mode as SandboxLevel)) {\n sandboxLevel = options.mode as SandboxLevel;\n modeSource = \"options.mode\";\n } else if (options.sandbox) {\n sandboxLevel = options.sandbox;\n modeSource = \"options.sandbox\";\n } else {\n sandboxLevel = DEFAULT_SANDBOX;\n modeSource = \"default\";\n }\n const codexSandboxMode =\n sandboxLevel === \"read-only\"\n ? \"read-only\"\n : sandboxLevel === \"full-access\"\n ? \"danger-full-access\"\n : \"workspace-write\";\n args.push(\"-c\", `sandbox_mode=\"${codexSandboxMode}\"`);\n\n // N3: model\n if (options.model) {\n args.push(\"-c\", `model=\"${escapeToml(options.model)}\"`);\n }\n\n // Layer 1: MCP server injection via -c flags\n if (options.mcpServer) {\n const { command, args: serverArgs, env } = options.mcpServer;\n args.push(\"-c\", `mcp_servers.clawnet-mcp.command=\"${escapeToml(command)}\"`);\n if (serverArgs && serverArgs.length > 0) {\n const argsToml = \"[\" + serverArgs.map((a) => `\"${escapeToml(a)}\"`).join(\", \") + \"]\";\n args.push(\"-c\", `mcp_servers.clawnet-mcp.args=${argsToml}`);\n }\n if (env) {\n for (const [key, value] of Object.entries(env)) {\n args.push(\"-c\", `mcp_servers.clawnet-mcp.env.${key}=\"${escapeToml(value)}\"`);\n }\n }\n // CRITICAL: per-server tool approval mode. Without this codex still\n // elicits each MCP tool call (even with global approval_policy=\"never\"\n // which only covers shell exec). The elicitation has no client-side\n // handler, so every MCP tool call returns \"user rejected MCP tool call\"\n // in 0s. \"approve\" silently approves all tool calls — required for\n // unattended swarm-role execution. (Valid values are \"prompt\" and\n // \"approve\"; \"auto\" is NOT accepted by codex 0.134.)\n args.push(\"-c\", 'mcp_servers.clawnet-mcp.default_tools_approval_mode=\"approve\"');\n }\n\n // Layer 2: Briefing injection via temp file + model_instructions_file\n if (options.systemPrompt) {\n // Sanitize sessionId for use in a filename: swarm role sessionIds look like\n // \"<swarmId>::<instanceId>\" (e.g. \"abc123::dev-0\"). Windows forbids `:`\n // (and `\\\\/*?\"<>|`, control chars 0x00-0x1F, plus trailing `.`/space) in\n // filenames, so writeFileSync blows up with ENOENT and the entire codex\n // role fails to spawn. Replace all illegal chars with `_` — the file is\n // throwaway, only the .md suffix matters.\n const safeId = options.sessionId\n // eslint-disable-next-line no-control-regex\n .replace(/[\\\\/:*?\"<>|\\x00-\\x1F]/g, \"_\")\n .replace(/[. ]+$/g, \"_\");\n briefingFile = join(tmpdir(), `clawnet-briefing-${safeId}-${randomUUID().slice(0, 8)}.md`);\n writeFileSync(briefingFile, options.systemPrompt, { encoding: \"utf-8\", mode: 0o600 });\n args.push(\"-c\", `model_instructions_file=\"${escapeToml(briefingFile)}\"`);\n }\n\n return { args, briefingFile, modeSource };\n}\n\nexport function cleanupBriefingFile(filePath: string): void {\n try {\n unlinkSync(filePath);\n } catch {\n // ignore — file may already be gone\n }\n}\n","import type { Writable, Readable } from \"node:stream\";\n\nexport interface JsonRpcClientOptions {\n stdin: Writable;\n stdout: Readable;\n onRequest?: (method: string, params: unknown) => Promise<unknown> | unknown;\n onNotification?: (method: string, params: unknown) => void;\n onMalformedLine?: (line: string, err: Error) => void;\n}\n\ninterface JsonRpcPending {\n resolve: (v: unknown) => void;\n reject: (e: Error) => void;\n}\n\n/**\n * Minimal JSON-RPC 2.0 client over NDJSON stdio.\n *\n * Half of the duplex: client-initiated `request()` returns a Promise tied to\n * the response by id. Server-initiated requests (codex approval callbacks)\n * are dispatched to `onRequest` and the resolved value is written back as a\n * `result` envelope.\n */\nexport class JsonRpcClient {\n private stdin: Writable;\n private buffer = \"\";\n private nextId = 1;\n private pending = new Map<number, JsonRpcPending>();\n private onRequest?: JsonRpcClientOptions[\"onRequest\"];\n private onNotification?: JsonRpcClientOptions[\"onNotification\"];\n private onMalformedLine?: JsonRpcClientOptions[\"onMalformedLine\"];\n\n constructor(opts: JsonRpcClientOptions) {\n this.stdin = opts.stdin;\n this.onRequest = opts.onRequest;\n this.onNotification = opts.onNotification;\n this.onMalformedLine = opts.onMalformedLine;\n opts.stdout.on(\"data\", (chunk: Buffer) => this.feed(chunk.toString(\"utf-8\")));\n // Without this listener, an EPIPE on stdin (codex dies mid-handshake,\n // user closes the pipe, OS sends SIGPIPE) bubbles as an unhandled\n // 'error' event and crashes the host process. We surface it as a\n // structured rejection on every in-flight request instead.\n this.stdin.on(\"error\", (err) => this.failAllPending(err));\n }\n\n /**\n * Reject every in-flight request with the given error. Called when stdin\n * dies so callers get a clean rejection instead of hanging until the\n * handshake death-poll catches up.\n */\n private failAllPending(err: Error): void {\n for (const [, p] of this.pending) {\n try { p.reject(err); } catch { /* ignore listener throw */ }\n }\n this.pending.clear();\n }\n\n request(method: string, params: unknown): Promise<unknown> {\n const id = this.nextId++;\n const frame = { jsonrpc: \"2.0\", id, method, params };\n return new Promise((resolve, reject) => {\n this.pending.set(id, { resolve, reject });\n // Pass an error callback so a write failure (EPIPE on dead child)\n // rejects this specific request instead of triggering an unhandled\n // 'error' on the stream. The stdin 'error' handler above is the\n // backstop for failures that arrive out-of-band of any write.\n this.stdin.write(JSON.stringify(frame) + \"\\n\", (err) => {\n if (err) {\n this.pending.delete(id);\n reject(err);\n }\n });\n });\n }\n\n notify(method: string, params: unknown): void {\n // Same swallow-rather-than-crash policy for notifications. There's no\n // pending promise to reject; the caller fired-and-forgot.\n this.stdin.write(JSON.stringify({ jsonrpc: \"2.0\", method, params }) + \"\\n\", () => {\n /* errors absorbed by the 'error' handler in the constructor */\n });\n }\n\n private feed(s: string) {\n this.buffer += s;\n let nl: number;\n while ((nl = this.buffer.indexOf(\"\\n\")) >= 0) {\n const line = this.buffer.slice(0, nl).trim();\n this.buffer = this.buffer.slice(nl + 1);\n if (!line) continue;\n this.dispatch(line);\n }\n }\n\n private dispatch(line: string) {\n let msg: {\n id?: number;\n method?: string;\n params?: unknown;\n result?: unknown;\n error?: { code: number; message: string };\n };\n try {\n msg = JSON.parse(line);\n } catch (err) {\n this.onMalformedLine?.(line, err as Error);\n return;\n }\n if (typeof msg.id === \"number\" && (msg.result !== undefined || msg.error !== undefined)) {\n const pending = this.pending.get(msg.id);\n if (!pending) return;\n this.pending.delete(msg.id);\n if (msg.error) pending.reject(new Error(msg.error.message));\n else pending.resolve(msg.result);\n return;\n }\n if (msg.method && typeof msg.id === \"number\") {\n // server-initiated request\n const id = msg.id;\n const method = msg.method;\n const params = msg.params;\n void Promise.resolve(this.onRequest?.(method, params))\n .then((result) => {\n this.stdin.write(JSON.stringify({ jsonrpc: \"2.0\", id, result }) + \"\\n\");\n })\n .catch((err: Error) => {\n this.stdin.write(\n JSON.stringify({\n jsonrpc: \"2.0\",\n id,\n error: { code: -32000, message: err.message },\n }) + \"\\n\",\n );\n });\n return;\n }\n if (msg.method) {\n this.onNotification?.(msg.method, msg.params);\n }\n }\n}\n","import type { PermissionDecision, PermissionRequest } from \"@mclawnet/backend-types\";\n\n/**\n * Three coexisting decision-wire families codex app-server speaks:\n * - \"legacy\" → ReviewDecision (approved/approved_for_session/denied/abort)\n * - \"v2\" → CommandExecutionApprovalDecision / FileChangeApprovalDecision\n * (accept/acceptForSession/decline/cancel)\n * - \"mcp\" → McpServerElicitationAction\n * ({ action: \"accept\" | \"decline\" | \"cancel\" })\n *\n * `parseApprovalRequest` returns the family so `respondToPermission` can pick\n * the right wire without re-deriving it from a brittle method-name prefix.\n */\nexport type ApprovalWireFamily = \"legacy\" | \"v2\" | \"mcp\";\n\nexport type ReviewDecisionWire =\n | \"approved\"\n | \"approved_for_session\"\n | \"denied\"\n | \"abort\";\n\nexport function decisionToWire(d: PermissionDecision[\"decision\"]): ReviewDecisionWire {\n switch (d) {\n case \"allow\":\n return \"approved\";\n case \"allow_session\":\n return \"approved_for_session\";\n case \"deny\":\n return \"denied\";\n case \"abort\":\n return \"abort\";\n default: {\n const _exhaustive: never = d;\n return _exhaustive;\n }\n }\n}\n\n/**\n * v2 CommandExecutionApprovalDecision / FileChangeApprovalDecision wire enum.\n * Used by codex app-server >= 0.134 for `item/.../requestApproval` flows.\n */\nexport type V2DecisionWire = \"accept\" | \"acceptForSession\" | \"decline\" | \"cancel\";\n\nexport function decisionToV2Wire(d: PermissionDecision[\"decision\"]): V2DecisionWire {\n switch (d) {\n case \"allow\":\n return \"accept\";\n case \"allow_session\":\n return \"acceptForSession\";\n case \"deny\":\n return \"decline\";\n case \"abort\":\n return \"cancel\";\n default: {\n const _exhaustive: never = d;\n return _exhaustive;\n }\n }\n}\n\n/**\n * MCP elicitation action enum (RMCP CreateElicitationResult). Note: \"session\"\n * scope isn't part of the MCP spec — `allow_session` degrades to \"accept\".\n */\nexport type McpElicitationAction = \"accept\" | \"decline\" | \"cancel\";\n\nexport function decisionToMcpAction(d: PermissionDecision[\"decision\"]): McpElicitationAction {\n switch (d) {\n case \"allow\":\n case \"allow_session\":\n return \"accept\";\n case \"deny\":\n return \"decline\";\n case \"abort\":\n return \"cancel\";\n default: {\n const _exhaustive: never = d;\n return _exhaustive;\n }\n }\n}\n\n/** @deprecated kept only for back-compat with M3.S5 test seam; prefer the\n * wire family returned by `parseApprovalRequest`. */\nexport function isV2ApprovalMethod(method: string): boolean {\n return method.startsWith(\"item/\");\n}\n\n/**\n * Build the wire-shaped reply body for a given decision + family.\n * - legacy → `{ decision: \"approved\" | ... }`\n * - v2 → `{ decision: \"accept\" | ... }`\n * - mcp → `{ action: \"accept\" | ... }`\n */\nexport function buildApprovalReply(\n family: ApprovalWireFamily,\n decision: PermissionDecision[\"decision\"],\n): Record<string, string> {\n switch (family) {\n case \"legacy\":\n return { decision: decisionToWire(decision) };\n case \"v2\":\n return { decision: decisionToV2Wire(decision) };\n case \"mcp\":\n return { action: decisionToMcpAction(decision) };\n default: {\n const _exhaustive: never = family;\n return _exhaustive;\n }\n }\n}\n\nexport interface ParsedApprovalRequest {\n req: PermissionRequest;\n wireFamily: ApprovalWireFamily;\n}\n\n/**\n * Map a codex JSON-RPC approval request to our backend-neutral\n * PermissionRequest. Covers both legacy (`execCommandApproval`,\n * `applyPatchApproval`) and v2 (`item/commandExecution/requestApproval`,\n * `item/fileChange/requestApproval`) shapes, plus MCP elicitation.\n *\n * Returns null when the method is not a recognized approval channel — the\n * adapter forwards such frames as raw output instead.\n */\nexport function parseApprovalRequest(\n method: string,\n params: unknown,\n): ParsedApprovalRequest | null {\n const p = (params ?? {}) as Record<string, unknown>;\n const callId = (p.callId as string | undefined) ?? (p.itemId as string | undefined);\n if (!callId) return null;\n const reason = p.reason as string | undefined;\n const cwd = p.cwd as string | undefined;\n\n if (method === \"execCommandApproval\") {\n return {\n req: {\n callId,\n toolName: \"shell\",\n input: { command: p.command, cwd },\n meta: { backend: \"codex\", reason, cwd },\n },\n wireFamily: \"legacy\",\n };\n }\n if (method === \"item/commandExecution/requestApproval\") {\n return {\n req: {\n callId,\n toolName: \"shell\",\n input: { command: p.command, cwd },\n meta: { backend: \"codex\", reason, cwd },\n },\n wireFamily: \"v2\",\n };\n }\n if (method === \"applyPatchApproval\") {\n return {\n req: {\n callId,\n toolName: \"apply_patch\",\n input: { fileChanges: p.fileChanges },\n meta: { backend: \"codex\", reason, cwd },\n },\n wireFamily: \"legacy\",\n };\n }\n if (method === \"item/fileChange/requestApproval\") {\n return {\n req: {\n callId,\n toolName: \"apply_patch\",\n input: { fileChanges: p.fileChanges },\n meta: { backend: \"codex\", reason, cwd },\n },\n wireFamily: \"v2\",\n };\n }\n if (method === \"mcpServer/elicitation/request\") {\n return {\n req: {\n callId,\n toolName: (p.serverName as string | undefined) ?? \"mcp\",\n input: { message: p.message, meta: p._meta },\n meta: { backend: \"codex\", reason },\n },\n wireFamily: \"mcp\",\n };\n }\n return null;\n}\n","import type { BackendOutput } from \"@mclawnet/backend-types\";\n\n/**\n * Map a single codex JSON-RPC notification frame to a backend-neutral\n * BackendOutput envelope. Unknown methods degrade to `{ kind: \"raw\" }` so\n * the agent main loop can still see them (e.g. for debug logging) without\n * the adapter pretending to understand every codex frame.\n *\n * Covers both v2 (`item/agentMessage/delta`, `item/started`, `item/completed`)\n * and legacy (`thread/message`, `thread/toolUse`) frame names.\n */\nexport function mapCodexFrame(frame: {\n method: string;\n params?: unknown;\n}): BackendOutput {\n const params = (frame.params ?? {}) as Record<string, unknown>;\n switch (frame.method) {\n // ── v2 frames (real codex app-server v2 protocol) ─────────────────\n case \"item/agentMessage/delta\": {\n const delta = params.delta;\n if (typeof delta === \"string\") {\n return { kind: \"assistant_text\", text: delta };\n }\n break;\n }\n case \"item/completed\": {\n const item = params.item as\n | {\n type?: string;\n text?: string;\n id?: string;\n // commandExecution\n command?: string;\n aggregatedOutput?: string | null;\n exitCode?: number | null;\n status?: string;\n // fileChange\n changes?: unknown;\n // mcpToolCall\n server?: string;\n tool?: string;\n arguments?: unknown;\n result?: unknown;\n error?: unknown;\n }\n | undefined;\n // NOTE: `item.type === \"agentMessage\"` is intentionally NOT handled\n // here. It's intercepted earlier in `CodexAdapter.handleNotification`,\n // which needs per-itemId delta state to avoid double-emitting (deltas\n // already streamed the same text). Re-adding a branch here would\n // bypass that dedupe and reintroduce the \"Hello worldHello world\" bug.\n if (item?.type === \"commandExecution\") {\n // CommandExecutionStatus = \"inProgress\" | \"completed\" | \"failed\" | \"declined\"\n const failed = item.status === \"failed\" || item.status === \"declined\";\n const badExit = typeof item.exitCode === \"number\" && item.exitCode !== 0;\n return {\n kind: \"tool_result\",\n callId: String(item.id ?? \"\"),\n output: item.aggregatedOutput ?? null,\n isError: failed || badExit,\n };\n }\n if (item?.type === \"fileChange\") {\n // PatchApplyStatus shares the CommandExecutionStatus enum shape.\n const failed = item.status === \"failed\" || item.status === \"declined\";\n return {\n kind: \"tool_result\",\n callId: String(item.id ?? \"\"),\n output: { changes: item.changes },\n isError: failed,\n };\n }\n if (item?.type === \"mcpToolCall\") {\n // McpToolCallStatus = \"inProgress\" | \"completed\" | \"failed\"\n const failed = item.status === \"failed\" || item.error != null;\n return {\n kind: \"tool_result\",\n callId: String(item.id ?? \"\"),\n output: failed ? item.error : item.result,\n isError: failed,\n };\n }\n break;\n }\n case \"item/started\": {\n const item = params.item as\n | { type?: string; id?: string; command?: string; name?: string; input?: unknown }\n | undefined;\n if (item?.type === \"commandExecution\") {\n return {\n kind: \"tool_use\",\n callId: String(item.id ?? \"\"),\n toolName: \"shell\",\n input: { command: item.command },\n };\n }\n if (item?.type === \"mcpToolCall\") {\n return {\n kind: \"tool_use\",\n callId: String(item.id ?? \"\"),\n toolName: String(item.name ?? \"mcp\"),\n input: item.input,\n };\n }\n break;\n }\n\n // ── legacy frame names (kept for back-compat with older codex / tests) ──\n case \"thread/message\": {\n const role = params.role as string | undefined;\n const content = params.content as Array<{ type: string; text?: string }> | undefined;\n if (role === \"assistant\" && content) {\n const text = content\n .filter((c) => c.type === \"text\" && typeof c.text === \"string\")\n .map((c) => c.text!)\n .join(\"\");\n return { kind: \"assistant_text\", text };\n }\n break;\n }\n case \"thread/toolUse\":\n return {\n kind: \"tool_use\",\n callId: String(params.callId ?? \"\"),\n toolName: String(params.name ?? \"\"),\n input: params.input,\n };\n case \"thread/toolResult\":\n return {\n kind: \"tool_result\",\n callId: String(params.callId ?? \"\"),\n output: params.output,\n isError: Boolean(params.isError),\n };\n }\n return { kind: \"raw\", backend: \"codex\", payload: frame };\n}\n","import type { ModelDescriptor, ModeDescriptor } from \"@mclawnet/shared\";\n\n// PROVISIONAL — not verified against `codex --help` on a real install.\n// Update once we have a way to query codex's accepted model list at runtime.\n// `code` values must be ones OpenAI's API accepts (codex CLI passes via -c model=\"...\").\nexport const CODEX_MODELS: ModelDescriptor[] = [\n {\n code: \"gpt-4o\",\n label: \"GPT-4o\",\n tier: \"premium\",\n contextWindow: 128_000,\n supportsVision: true,\n supportsToolUse: true,\n default: true,\n },\n {\n code: \"gpt-4o-mini\",\n label: \"GPT-4o mini\",\n tier: \"standard\",\n contextWindow: 128_000,\n supportsVision: true,\n supportsToolUse: true,\n },\n];\n\n// codex 的 mode = sandbox_mode\nexport const CODEX_MODES: ModeDescriptor[] = [\n { code: \"read-only\", label: \"Read Only\", description: \"No FS writes\" },\n { code: \"workspace-write\", label: \"Workspace Write\", description: \"Write inside cwd only\", default: true },\n { code: \"full-access\", label: \"Full Access\", description: \"No sandbox\" },\n];\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { platform } from \"node:os\";\n\nconst exec = promisify(execFile);\n\n// TODO: extract `detectCliBinary(name)` into a shared backend-utils package\n// (NOT into @mclawnet/agent — that would re-introduce the build-time cycle\n// the @mclawnet/backend-types extraction was created to break).\n// Removes ~90% duplication between this file and the sibling adapter.\n\nexport interface DetectResult {\n installed: boolean;\n binaryPath?: string;\n version?: string;\n reason?: string;\n}\n\n/**\n * Detect a codex CLI install on this machine. Must never throw — all\n * errors (missing binary, EACCES, timeout, hung --version) are absorbed\n * and surfaced via `reason` with `installed: false`.\n *\n * NOTE: Keep the entire body inside the single try block. Any code path\n * added outside the try would break the no-throw contract that\n * collectAgentManifest() relies on.\n */\nexport async function detectCodexInstall(): Promise<DetectResult> {\n try {\n const isWin = platform() === \"win32\";\n const lookup = isWin ? \"where\" : \"which\";\n const { stdout: lookupOut } = await exec(lookup, [\"codex\"], { timeout: 5000 });\n // `where` / `which` may return multiple shims (homebrew + nvm-global, etc.) — take first line.\n const binaryPath = lookupOut.trim().split(/\\r?\\n/)[0]?.trim();\n if (!binaryPath) {\n return { installed: false, reason: `codex CLI not detected: empty ${lookup} output` };\n }\n const { stdout: verOut } = await exec(binaryPath, [\"--version\"], { timeout: 5000 });\n return { installed: true, binaryPath, version: verOut.trim() };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { installed: false, reason: `codex CLI not detected: ${message}` };\n }\n}\n"],"mappings":"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","names":["platform","join","isWin","platform","join","platform","join","cp","require","out","process"]}
|
package/dist/errors.d.ts
CHANGED
|
@@ -37,4 +37,24 @@ export declare class SessionLimitReachedError extends Error {
|
|
|
37
37
|
readonly code = "SESSION_LIMIT_REACHED";
|
|
38
38
|
constructor(details: SessionPoolSnapshot);
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* (N3) Thrown by SessionManager.sendUserInput when the pending per-turn
|
|
42
|
+
* model override differs from the model the current child process was
|
|
43
|
+
* spawned with. The caller (hub-connection.ts session.input handler) is
|
|
44
|
+
* expected to catch this, emit session.died, then re-spawn via
|
|
45
|
+
* session.create with --resume.
|
|
46
|
+
*/
|
|
47
|
+
export declare class RespawnRequiredError extends Error {
|
|
48
|
+
readonly code: "RESPAWN_REQUIRED";
|
|
49
|
+
readonly sessionId: string;
|
|
50
|
+
readonly newModel: string;
|
|
51
|
+
readonly currentModel: string | undefined;
|
|
52
|
+
readonly backendSessionId: string | undefined;
|
|
53
|
+
constructor(args: {
|
|
54
|
+
sessionId: string;
|
|
55
|
+
newModel: string;
|
|
56
|
+
currentModel: string | undefined;
|
|
57
|
+
backendSessionId: string | undefined;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
40
60
|
//# sourceMappingURL=errors.d.ts.map
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,YAAY,CAAC;AAEhD;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACpC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,cAAc,EAAE,WAAW,GAAG,IAAI,CAAC;CACpC;AAED;;;;;;GAMG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;aAGrB,OAAO,EAAE,mBAAmB;IAFxD,QAAQ,CAAC,IAAI,2BAA2B;gBAEZ,OAAO,EAAE,mBAAmB;CAIzD"}
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,YAAY,CAAC;AAEhD;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACpC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,cAAc,EAAE,WAAW,GAAG,IAAI,CAAC;CACpC;AAED;;;;;;GAMG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;aAGrB,OAAO,EAAE,mBAAmB;IAFxD,QAAQ,CAAC,IAAI,2BAA2B;gBAEZ,OAAO,EAAE,mBAAmB;CAIzD;AAED;;;;;;GAMG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAG,kBAAkB,CAAU;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;gBAElC,IAAI,EAAE;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;QACjC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;KACtC;CAWF"}
|
package/dist/hub-connection.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { AgentClientMessage, AgentGenericRequest } from "@mclawnet/shared";
|
|
2
2
|
import type { SessionManager } from "./session-manager.js";
|
|
3
|
-
import type { SwarmCoordinator } from "@mclawnet/swarm";
|
|
3
|
+
import type { SwarmCoordinator, WorkspaceManager } from "@mclawnet/swarm";
|
|
4
|
+
/**
|
|
5
|
+
* Test-only re-exports. Production code MUST NOT import these — they exist
|
|
6
|
+
* to let the wake-watch regression test pin the values without widening the
|
|
7
|
+
* public module surface.
|
|
8
|
+
*/
|
|
9
|
+
export declare const __TEST_ONLY_WAKE_WATCH_INTERVAL_MS = 1000;
|
|
10
|
+
export declare const __TEST_ONLY_WAKE_WATCH_JUMP_THRESHOLD_MS = 15000;
|
|
4
11
|
/**
|
|
5
12
|
* Structural type for the schedule runtime; declared locally so hub-connection
|
|
6
13
|
* does not import from `./schedule-runtime.js` (which itself imports
|
|
@@ -23,6 +30,20 @@ export interface HubConnectionOptions {
|
|
|
23
30
|
onConnect?: (agentId: string) => void;
|
|
24
31
|
onDisconnect?: (code: number, reason: string) => void;
|
|
25
32
|
onError?: (err: Error) => void;
|
|
33
|
+
/**
|
|
34
|
+
* M7.3: invoked on every inbound authenticated message (after `registered`,
|
|
35
|
+
* before dispatch) so the AlwaysOnManager can defer ticks while the user
|
|
36
|
+
* is in the loop. Pure side-effect; throws are swallowed.
|
|
37
|
+
*/
|
|
38
|
+
onActivity?: () => void;
|
|
39
|
+
/**
|
|
40
|
+
* (N5) Shared WorkspaceManager — used by session.create when `useWorktree`
|
|
41
|
+
* is set, to prepare an isolated workspace before SessionManager.createSession
|
|
42
|
+
* runs. Optional so that test harnesses can omit it (sessions without
|
|
43
|
+
* `useWorktree` never touch it). When omitted, useWorktree:true silently
|
|
44
|
+
* degrades to direct workDir.
|
|
45
|
+
*/
|
|
46
|
+
workspaceManager?: WorkspaceManager;
|
|
26
47
|
}
|
|
27
48
|
export declare class HubConnection {
|
|
28
49
|
private ws;
|
|
@@ -63,6 +84,9 @@ export declare class HubConnection {
|
|
|
63
84
|
private onConnectCb?;
|
|
64
85
|
private onDisconnect?;
|
|
65
86
|
private onError?;
|
|
87
|
+
private onActivity?;
|
|
88
|
+
/** (N5) Shared WorkspaceManager for chat `useWorktree` flow. */
|
|
89
|
+
private workspaceManager?;
|
|
66
90
|
constructor(opts: HubConnectionOptions);
|
|
67
91
|
setSessionManager(manager: SessionManager): void;
|
|
68
92
|
setSwarmCoordinator(coordinator: SwarmCoordinator): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hub-connection.d.ts","sourceRoot":"","sources":["../src/hub-connection.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAEV,kBAAkB,EAClB,mBAAmB,EAEpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"hub-connection.d.ts","sourceRoot":"","sources":["../src/hub-connection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { IdeasClient } from "@mclawnet/swarm";
|
|
2
|
+
export interface IdeasRestClientOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Hub URL. Accepts either ws(s):// (the agent's primary connection URL,
|
|
5
|
+
* /ws/agent suffix is stripped automatically) or http(s):// — the client
|
|
6
|
+
* normalises to http(s):// for fetch.
|
|
7
|
+
*/
|
|
8
|
+
hubUrl: string;
|
|
9
|
+
/**
|
|
10
|
+
* Returns the token to send as `Authorization: Bearer <token>`. Sync or
|
|
11
|
+
* async. Returning null/undefined makes the client throw — IdeaTodoSource
|
|
12
|
+
* treats that as "no ideas" and the cascade falls through.
|
|
13
|
+
*/
|
|
14
|
+
getAuthToken: () => string | null | undefined | Promise<string | null | undefined>;
|
|
15
|
+
/** Optional fetch override (tests). Defaults to globalThis.fetch. */
|
|
16
|
+
fetchImpl?: typeof fetch;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Convert an agent hubUrl into the REST base. The agent typically holds a
|
|
20
|
+
* ws(s):// URL ending in /ws/agent; we strip that suffix and switch protocol.
|
|
21
|
+
* Plain http(s):// URLs pass through unchanged.
|
|
22
|
+
*/
|
|
23
|
+
export declare function normalizeHubUrlToHttp(hubUrl: string): string;
|
|
24
|
+
export declare function createIdeasRestClient(opts: IdeasRestClientOptions): IdeasClient;
|
|
25
|
+
//# sourceMappingURL=ideas-rest-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ideas-rest-client.d.ts","sourceRoot":"","sources":["../src/ideas-rest-client.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAyB,WAAW,EAA+B,MAAM,iBAAiB,CAAC;AAEvG,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,YAAY,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACnF,qEAAqE;IACrE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAWD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAQ5D;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,sBAAsB,GAAG,WAAW,CA8E/E"}
|
package/dist/index.js
CHANGED
|
@@ -4,15 +4,15 @@ import {
|
|
|
4
4
|
HubConnection,
|
|
5
5
|
SessionManager,
|
|
6
6
|
startAgent
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-GOCWMRBB.js";
|
|
8
8
|
import {
|
|
9
9
|
__resetBackendFactoryCache,
|
|
10
10
|
createBackendAdapter
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-VAEFJLPL.js";
|
|
12
12
|
import {
|
|
13
13
|
loadConfig,
|
|
14
14
|
saveConfig
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-376QZ7JB.js";
|
|
16
16
|
|
|
17
17
|
// src/index.ts
|
|
18
18
|
import {
|
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
getLogDir,
|
|
5
5
|
getNodePath,
|
|
6
6
|
loadServiceConfig
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-JH6RGJBQ.js";
|
|
8
|
+
import "./chunk-376QZ7JB.js";
|
|
9
9
|
|
|
10
10
|
// src/service/linux.ts
|
|
11
11
|
import { execSync, spawn } from "child_process";
|
|
@@ -173,4 +173,4 @@ export {
|
|
|
173
173
|
generateUnit,
|
|
174
174
|
getUnitPath
|
|
175
175
|
};
|
|
176
|
-
//# sourceMappingURL=linux-
|
|
176
|
+
//# sourceMappingURL=linux-MBU6ERXL.js.map
|
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
getLogDir,
|
|
5
5
|
getNodePath,
|
|
6
6
|
loadServiceConfig
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-JH6RGJBQ.js";
|
|
8
|
+
import "./chunk-376QZ7JB.js";
|
|
9
9
|
|
|
10
10
|
// src/service/macos.ts
|
|
11
11
|
import { execSync, spawn } from "child_process";
|
|
@@ -194,4 +194,4 @@ export {
|
|
|
194
194
|
generatePlist,
|
|
195
195
|
getPlistPath
|
|
196
196
|
};
|
|
197
|
-
//# sourceMappingURL=macos-
|
|
197
|
+
//# sourceMappingURL=macos-I2DUWFUH.js.map
|
|
@@ -1,5 +1,20 @@
|
|
|
1
|
+
import type { ShipmentListEntry, AlwaysOnConfig as WireAlwaysOnConfig, AlwaysOnSnapshot, AlwaysOnTickRecord, ProjectsEnsureProjectResult } from "@mclawnet/shared";
|
|
2
|
+
import { type ShipmentActionKind, type ProjectSummary, type AlwaysOnConfig, type AlwaysOnPersistedConfig } from "@mclawnet/swarm";
|
|
3
|
+
type ProjectWorkdirErrorCode = NonNullable<NonNullable<ProjectsEnsureProjectResult["error"]>["code"]>;
|
|
4
|
+
type ProjectEnsureError = {
|
|
5
|
+
status: number;
|
|
6
|
+
message: string;
|
|
7
|
+
code?: ProjectWorkdirErrorCode;
|
|
8
|
+
details?: Record<string, unknown>;
|
|
9
|
+
};
|
|
10
|
+
export declare function handleWorkdirMkdir(workDirInput: string): Promise<{
|
|
11
|
+
ok: boolean;
|
|
12
|
+
workDir?: string;
|
|
13
|
+
created?: boolean;
|
|
14
|
+
error: ProjectEnsureError | null;
|
|
15
|
+
}>;
|
|
1
16
|
export declare function handleProjectsList(): Promise<{
|
|
2
|
-
projects:
|
|
17
|
+
projects: ProjectSummary[];
|
|
3
18
|
}>;
|
|
4
19
|
export declare function handleProjectsGet(encodedCwd: string): Promise<{
|
|
5
20
|
project: null;
|
|
@@ -14,6 +29,16 @@ export declare function handleProjectsGet(encodedCwd: string): Promise<{
|
|
|
14
29
|
taskCounts: import("@mclawnet/swarm").TaskCounts;
|
|
15
30
|
};
|
|
16
31
|
}>;
|
|
32
|
+
export declare function handleProjectsEnsureProject(workDirInput: string, opts?: {
|
|
33
|
+
createDir?: boolean;
|
|
34
|
+
}): Promise<{
|
|
35
|
+
ok: boolean;
|
|
36
|
+
project: ProjectSummary | null;
|
|
37
|
+
encodedCwd?: string;
|
|
38
|
+
workDir?: string;
|
|
39
|
+
createdMeta?: boolean;
|
|
40
|
+
error: ProjectEnsureError | null;
|
|
41
|
+
}>;
|
|
17
42
|
export declare function handleProjectsTasks(encodedCwd: string, filters?: {
|
|
18
43
|
status?: string;
|
|
19
44
|
swarmId?: string;
|
|
@@ -123,4 +148,124 @@ export declare function handleProjectsFileWrite(encodedCwd: string, relPath: str
|
|
|
123
148
|
details?: undefined;
|
|
124
149
|
};
|
|
125
150
|
}>;
|
|
151
|
+
export declare function handleProjectsShipmentGet(encodedCwd: string, swarmId: string): Promise<{
|
|
152
|
+
projectFound: boolean;
|
|
153
|
+
shipment: import("@mclawnet/swarm").ShipmentResult | null;
|
|
154
|
+
diffContent?: string;
|
|
155
|
+
reportContent?: string;
|
|
156
|
+
}>;
|
|
157
|
+
/**
|
|
158
|
+
* M6.2 hotfix S4: return the FULL diff.patch (no 1 MiB inline cap). Used by
|
|
159
|
+
* the hub's `GET .../shipment/diff` route to serve `text/plain` so the user
|
|
160
|
+
* can download the patch when the inline DiffViewer truncates at 5000 lines.
|
|
161
|
+
*
|
|
162
|
+
* Returns `content: null` (with `projectFound: true`) when the swarm exists
|
|
163
|
+
* but never shipped a diff (strategy=pr only, skipped, etc.) — hub maps to
|
|
164
|
+
* 404. `projectFound: false` is the missing-project 404.
|
|
165
|
+
*/
|
|
166
|
+
export declare function handleProjectsShipmentDiffFull(encodedCwd: string, swarmId: string): Promise<{
|
|
167
|
+
projectFound: boolean;
|
|
168
|
+
content: string | null;
|
|
169
|
+
}>;
|
|
170
|
+
export declare function handleProjectsShipmentAction(encodedCwd: string, swarmId: string, action: ShipmentActionKind, force?: boolean): Promise<{
|
|
171
|
+
projectFound: boolean;
|
|
172
|
+
ok: boolean;
|
|
173
|
+
message?: string;
|
|
174
|
+
newBranchName?: string;
|
|
175
|
+
error?: string;
|
|
176
|
+
}>;
|
|
177
|
+
/**
|
|
178
|
+
* M6.3: cross-swarm shipment history listing. Scans every
|
|
179
|
+
* `<projectsDir>/<encoded>/swarms/<id>/shipment.json` and returns the parsed
|
|
180
|
+
* snapshot per swarm so the UI can render a "Shipments" tab in MyProjectPage
|
|
181
|
+
* without N round-trips. Corrupt JSON files are skipped with a warn so one
|
|
182
|
+
* bad swarm directory can't break the whole list.
|
|
183
|
+
*
|
|
184
|
+
* Sort order: `createdAt` desc (newest first). When `createdAt` is missing
|
|
185
|
+
* (legacy M6.1 shipments wrote without the field) the entry sinks to the
|
|
186
|
+
* bottom — fine for back-compat, the UI shows "—" for the time column.
|
|
187
|
+
*
|
|
188
|
+
* `projectFound: false` distinguishes "project doesn't exist" (404) from
|
|
189
|
+
* "no swarms / no shipments yet" (200 + empty list). Missing `swarms/` dir
|
|
190
|
+
* is the latter — a freshly opened project before its first run.
|
|
191
|
+
*/
|
|
192
|
+
export declare function handleProjectsShipmentsList(encodedCwd: string): Promise<{
|
|
193
|
+
projectFound: boolean;
|
|
194
|
+
shipments: ShipmentListEntry[];
|
|
195
|
+
}>;
|
|
196
|
+
interface AlwaysOnHooks {
|
|
197
|
+
/** Returns the live snapshot for the project, or null when no scheduler is wired. */
|
|
198
|
+
getSnapshot(workDir: string): AlwaysOnSnapshot | null;
|
|
199
|
+
/** Called after a config write so the runner can resize timers / re-arm pause. */
|
|
200
|
+
applyConfig(workDir: string, config: AlwaysOnPersistedConfig): void;
|
|
201
|
+
/**
|
|
202
|
+
* M7.3: called on every inbound hub message so the userActive gate can
|
|
203
|
+
* defer ticks while the user is still in the loop. Pure side-effect — no
|
|
204
|
+
* return value, never throws (manager swallows on its end).
|
|
205
|
+
*/
|
|
206
|
+
recordActivity(): void;
|
|
207
|
+
/**
|
|
208
|
+
* B6 — dev-only force-tick. Returns the TickRecord + post-tick snapshot
|
|
209
|
+
* for the project, or null when no scheduler is booted (the handler will
|
|
210
|
+
* surface this as projectFound=false). Optional so the no-op fallback can
|
|
211
|
+
* leave it undefined.
|
|
212
|
+
*/
|
|
213
|
+
forceTick?(workDir: string): Promise<{
|
|
214
|
+
record: AlwaysOnTickRecord;
|
|
215
|
+
snapshot: AlwaysOnSnapshot;
|
|
216
|
+
} | null>;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Wire a real DiscoveryScheduler / WorkCycleRunner into the projects-handler.
|
|
220
|
+
* Intended for M7.3's `start.ts` to call once at agent boot. Passing partial
|
|
221
|
+
* hooks is allowed; missing hooks fall back to the no-op defaults so the
|
|
222
|
+
* caller never has to provide all of them.
|
|
223
|
+
*/
|
|
224
|
+
export declare function _registerAlwaysOnHooks(hooks: Partial<AlwaysOnHooks>): void;
|
|
225
|
+
/** Test-only reset; M7.3 won't need this in production. */
|
|
226
|
+
export declare function _resetAlwaysOnHooks(): void;
|
|
227
|
+
/** Test/agent helper: forward an activity ping to whichever manager is wired. */
|
|
228
|
+
export declare function _recordAlwaysOnActivity(): void;
|
|
229
|
+
export declare function handleProjectsAlwaysOnGet(encodedCwd: string): Promise<{
|
|
230
|
+
projectFound: boolean;
|
|
231
|
+
config: WireAlwaysOnConfig;
|
|
232
|
+
snapshot: AlwaysOnSnapshot;
|
|
233
|
+
}>;
|
|
234
|
+
export declare function handleProjectsAlwaysOnSet(encodedCwd: string, patch: Omit<Partial<AlwaysOnConfig>, "pausedUntil"> & {
|
|
235
|
+
pausedUntil?: string | null;
|
|
236
|
+
}): Promise<{
|
|
237
|
+
projectFound: boolean;
|
|
238
|
+
config: WireAlwaysOnConfig;
|
|
239
|
+
snapshot: AlwaysOnSnapshot;
|
|
240
|
+
error?: string;
|
|
241
|
+
}>;
|
|
242
|
+
/**
|
|
243
|
+
* B6 — dev-only force-tick handler. Hub gates the corresponding HTTP route
|
|
244
|
+
* on `NODE_ENV !== "production"` so prod never reaches here. Defense in
|
|
245
|
+
* depth: if no forceTick hook is wired we still return a safe shape rather
|
|
246
|
+
* than crash.
|
|
247
|
+
*
|
|
248
|
+
* Error classification (PR-A2 follow-up):
|
|
249
|
+
* - `workDir` unresolvable → projectFound:false (hub → 404)
|
|
250
|
+
* - forceTick hook absent → projectFound:true + "manager not wired"
|
|
251
|
+
* - forceTick throws "no scheduler for ..."
|
|
252
|
+
* → projectFound:true + "scheduler not booted ..."
|
|
253
|
+
* (actionable: tells dev to PATCH /always-on first)
|
|
254
|
+
* - forceTick throws "tick is already in progress"
|
|
255
|
+
* → projectFound:true + "another tick in flight ..."
|
|
256
|
+
* (actionable: tells dev to wait or check the runner)
|
|
257
|
+
* - any other throw → projectFound:true + "force-tick failed: <msg>"
|
|
258
|
+
*
|
|
259
|
+
* The previous version caught everything as "scheduler not booted", which
|
|
260
|
+
* actively misled dev tools (e.g. when the real cause was an in-flight
|
|
261
|
+
* tick, the error said "enable via PATCH" which was already done).
|
|
262
|
+
*/
|
|
263
|
+
export declare function handleProjectsAlwaysOnTickNow(encodedCwd: string): Promise<{
|
|
264
|
+
projectFound: boolean;
|
|
265
|
+
decision?: string;
|
|
266
|
+
gateBreakdown?: AlwaysOnTickRecord["gateBreakdown"];
|
|
267
|
+
snapshot?: AlwaysOnSnapshot;
|
|
268
|
+
error?: string;
|
|
269
|
+
}>;
|
|
270
|
+
export {};
|
|
126
271
|
//# sourceMappingURL=projects-handler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projects-handler.d.ts","sourceRoot":"","sources":["../src/projects-handler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"projects-handler.d.ts","sourceRoot":"","sources":["../src/projects-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runtime-env-defaults — populate env vars the agent's spawned backends
|
|
3
|
+
* implicitly assume, when the launching shell didn't provide them.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists in addition to `clawnet-agent init`:
|
|
6
|
+
* init writes the persistence advice (`export COPILOT_API_KEY=dummy` in
|
|
7
|
+
* ~/.zshrc, defaults in ~/.codex/config.toml). But agents launched by
|
|
8
|
+
* non-shell processes (macOS LaunchAgent, systemd, IDE debugger) inherit
|
|
9
|
+
* an env that never sourced ~/.zshrc, so the persisted line doesn't help.
|
|
10
|
+
* This runtime-side defaulter closes that hole without monkey-patching
|
|
11
|
+
* dotfiles or asking the user to also configure launchd plists.
|
|
12
|
+
*
|
|
13
|
+
* Only fills truly safe defaults: values that are accepted as opaque
|
|
14
|
+
* passthrough by the downstream tool (copilot-api proxy doesn't validate
|
|
15
|
+
* the bearer it forwards). Never override an env the user actually set.
|
|
16
|
+
*/
|
|
17
|
+
export declare function ensureRuntimeEnvDefaults(): void;
|
|
18
|
+
//# sourceMappingURL=runtime-env-defaults.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-env-defaults.d.ts","sourceRoot":"","sources":["../src/runtime-env-defaults.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAI/C"}
|