@schoolai/shipyard 3.17.0 → 3.18.0-nightly.20260617.0

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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getDaemonVersion
4
- } from "./chunk-RP4DQQXY.js";
4
+ } from "./chunk-RM4WIKPZ.js";
5
5
  import {
6
6
  WorkerCommandCodec,
7
7
  WorkerReplyCodec
@@ -1116,4 +1116,4 @@ export {
1116
1116
  shutdownFileWatcherGuard,
1117
1117
  guardedSubscribe
1118
1118
  };
1119
- //# sourceMappingURL=chunk-J274FTGE.js.map
1119
+ //# sourceMappingURL=chunk-CTCRVL3I.js.map
@@ -18,8 +18,8 @@ function getDaemonVersion() {
18
18
  return cached;
19
19
  }
20
20
  function readDaemonVersion() {
21
- if ("3.17.0".length > 0) {
22
- return "3.17.0";
21
+ if ("3.18.0".length > 0) {
22
+ return "3.18.0";
23
23
  }
24
24
  try {
25
25
  const here = dirname(fileURLToPath(import.meta.url));
@@ -48,4 +48,4 @@ function readDaemonVersion() {
48
48
  export {
49
49
  getDaemonVersion
50
50
  };
51
- //# sourceMappingURL=chunk-RP4DQQXY.js.map
51
+ //# sourceMappingURL=chunk-RM4WIKPZ.js.map
@@ -56,9 +56,30 @@ function resolveCursorBundledRipgrepPath(deps = {}) {
56
56
  return exists(rg) ? rg : null;
57
57
  }
58
58
 
59
- // src/services/session/cursor-init-error-formatter.ts
59
+ // src/services/session/cursor-init-error-reporter.ts
60
60
  import { Cursor } from "@cursor/sdk";
61
61
 
62
+ // src/services/session/cursor-init-error-formatter.ts
63
+ function formatCursorInitError(input) {
64
+ const { requestedModelId, originalError, availableModels, listError } = input;
65
+ if (availableModels === null) {
66
+ const listPart = listError ? ` (Cursor.models.list() also failed: ${listError})` : " (Cursor.models.list() unavailable)";
67
+ return `Agent.create failed for model '${requestedModelId}'. ${originalError}${listPart}`;
68
+ }
69
+ if (availableModels.length === 0) {
70
+ return `Agent.create failed for model '${requestedModelId}'. Cursor.models.list() returned no models. ${originalError}`;
71
+ }
72
+ const requested = requestedModelId.toLowerCase();
73
+ const exactMatch = availableModels.find(
74
+ (m) => m.id.toLowerCase() === requested || (m.aliases ?? []).some((a) => a.toLowerCase() === requested)
75
+ );
76
+ const idsList = availableModels.map((m) => m.id).join(", ");
77
+ const aliasParts = availableModels.filter((m) => (m.aliases?.length ?? 0) > 0).map((m) => `${m.id}=[${(m.aliases ?? []).join("|")}]`);
78
+ const aliasesList = aliasParts.length > 0 ? aliasParts.join(", ") : "(none)";
79
+ const matchPart = exactMatch ? ` The id IS in the available set (${exactMatch.id}) \u2014 failure is unrelated to model resolution.` : ` The id is NOT in the available set \u2014 likely renamed or deprecated server-side.`;
80
+ return `Agent.create failed for model '${requestedModelId}'.${matchPart} Available ids: ${idsList}. Aliases: ${aliasesList}. Original: ${originalError}`;
81
+ }
82
+
62
83
  // src/services/session/cursor-runner-error.ts
63
84
  function readStringField(rec, key) {
64
85
  const value = rec[key];
@@ -91,26 +112,7 @@ function serializeCursorRunnerError(err) {
91
112
  return { error: String(err), isRetryable: false };
92
113
  }
93
114
 
94
- // src/services/session/cursor-init-error-formatter.ts
95
- function formatCursorInitError(input) {
96
- const { requestedModelId, originalError, availableModels, listError } = input;
97
- if (availableModels === null) {
98
- const listPart = listError ? ` (Cursor.models.list() also failed: ${listError})` : " (Cursor.models.list() unavailable)";
99
- return `Agent.create failed for model '${requestedModelId}'. ${originalError}${listPart}`;
100
- }
101
- if (availableModels.length === 0) {
102
- return `Agent.create failed for model '${requestedModelId}'. Cursor.models.list() returned no models. ${originalError}`;
103
- }
104
- const requested = requestedModelId.toLowerCase();
105
- const exactMatch = availableModels.find(
106
- (m) => m.id.toLowerCase() === requested || (m.aliases ?? []).some((a) => a.toLowerCase() === requested)
107
- );
108
- const idsList = availableModels.map((m) => m.id).join(", ");
109
- const aliasParts = availableModels.filter((m) => (m.aliases?.length ?? 0) > 0).map((m) => `${m.id}=[${(m.aliases ?? []).join("|")}]`);
110
- const aliasesList = aliasParts.length > 0 ? aliasParts.join(", ") : "(none)";
111
- const matchPart = exactMatch ? ` The id IS in the available set (${exactMatch.id}) \u2014 failure is unrelated to model resolution.` : ` The id is NOT in the available set \u2014 likely renamed or deprecated server-side.`;
112
- return `Agent.create failed for model '${requestedModelId}'.${matchPart} Available ids: ${idsList}. Aliases: ${aliasesList}. Original: ${originalError}`;
113
- }
115
+ // src/services/session/cursor-init-error-reporter.ts
114
116
  var INIT_MODEL_LIST_TIMEOUT_MS = 5e3;
115
117
  async function sendInitErrorFromAgentCreateFailure(args) {
116
118
  const originalError = args.err instanceof Error ? args.err.message : String(args.err);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/services/session/cursor-runner.ts","../src/shared/capabilities/runtime/cursor-ripgrep.ts","../src/services/session/cursor-init-error-formatter.ts","../src/services/session/cursor-runner-error.ts","../src/services/session/cursor-tool-execution-edge.ts"],"sourcesContent":["/**\n * cursor-runner — forked Node child that owns the `@cursor/sdk` lifecycle\n * outside the daemon's V8 isolate (plan v2 §2).\n *\n * The daemon spawns this entry via `child_process.fork(require.resolve(\n * './cursor-runner.js'))` once per Cursor task. The child:\n * 1. waits for an `init` IPC message,\n * 2. calls `Agent.create()` from `@cursor/sdk`,\n * 3. on each `push_message`, calls `agent.send()` and iterates\n * `Run.stream()`, forwarding every `SDKMessage` back to the daemon,\n * 4. on `interrupt`, cancels the in-flight run,\n * 5. on `close`, disposes the agent and exits 0.\n *\n * Native panics (sqlite, FFI) crash THIS child only. The daemon-side\n * supervisor (S2b) observes the child exit, emits `subprocess_died`, and\n * the session FSM tears down cleanly.\n *\n * Per-tool gating uses the `.cursor/hooks.json` `preToolUse` hook script +\n * cursor-hook-socket (cursor-hook-socket.ts) — @cursor/sdk@1.0.13 has no\n * public respond-to-request API.\n */\n\nimport {\n Agent,\n type InteractionUpdate,\n type Run,\n type SDKAgent,\n type SDKMessage,\n type SDKUserMessage,\n type SendOptions,\n type SettingSource,\n} from '@cursor/sdk';\nimport { ContentBlockSchema, HARNESS_SERVER_NAME } from '@shipyard/loro-schema';\n\nimport { configureCursorRipgrepPath } from '../../shared/capabilities/runtime/cursor-ripgrep.js';\nimport {\n buildCursorUserPrompt,\n type CursorResolvedSkillBody,\n type CursorUserInput,\n} from './cursor-content-builder.js';\nimport { sendInitErrorFromAgentCreateFailure } from './cursor-init-error-formatter.js';\nimport { serializeCursorRunnerError } from './cursor-runner-error.js';\nimport {\n type DaemonToRunner,\n parseDaemonToRunner,\n type RunnerToDaemon,\n} from './cursor-runner-protocol.js';\nimport { decideToolExecutionEdge } from './cursor-tool-execution-edge.js';\n\nconfigureCursorRipgrepPath();\n\n/**\n * Real token usage from a `turn-ended` interaction update. Captured during\n * `onDelta` and forwarded on `run_complete` so the daemon can map real counts\n * onto the `TurnResult` instead of marking everything as estimated.\n */\ninterface TurnUsage {\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n}\n\n/** Named subagent catalog shape as it arrives over IPC (mirrors the protocol). */\ntype RunnerAgents = Record<\n string,\n {\n description: string;\n prompt: string;\n model?: { id: string } | 'inherit';\n mcpServers?: string[];\n }\n>;\n\ninterface RunnerState {\n agent: SDKAgent | null;\n currentRun: Run | null;\n currentGeneration: number;\n taskId: string;\n harnessToken: string;\n /**\n * Stable per-runner identity for the runner-scoped harness path. When set,\n * the harness MCP entry uses `x-shipyard-runner-id: harnessRunnerId` instead\n * of `x-shipyard-task-id: taskId`, keeping the `mcpServers` map (and thus the\n * SDK executor cache key) byte-identical across task switches. Empty string\n * = legacy per-task harness path.\n */\n harnessRunnerId: string;\n /** Harness MCP server URL, retained so reinit can rebuild the entry. */\n harnessUrl: string;\n /** Workspace cwd, retained so reinit can rebuild `local.cwd`. */\n cwd: string;\n /** Cursor API key, retained so reinit can recreate the Agent. */\n apiKey: string;\n /**\n * Pool-managed flag. When true, `close` disposes the Agent but does NOT\n * `process.exit(0)` — the runner acks `pooled_idle` and the daemon keeps the\n * warm process alive for the next claim.\n */\n pooled: boolean;\n /**\n * Cursor model id supplied at init. Threaded into every\n * `SendOptions.model.id` when Fast Mode is on so the runtime knows which\n * model the params belong to.\n */\n modelId: string;\n /**\n * Live Fast Mode flag. Initial value from `init`; updated by\n * `set_fast_mode` IPC. The runner attaches Fast Mode params only when\n * both this is true AND `fastModeParam` is non-null.\n */\n fastMode: boolean;\n /**\n * Resolved Fast Mode `{id, value}` binding for `modelId`, sourced from\n * the daemon's discovery cache. Null = no binding known; the runner\n * sends the plain `agent.send(message)` shape regardless of `fastMode`.\n */\n fastModeParam: { id: string; value: string } | null;\n /**\n * Live MCP server set, seeded at init/reinit and updated by `set_mcp_servers`\n * IPC. `@cursor/sdk@1.0.17` takes the MCP set as a per-RUN parameter\n * (`SendOptions.mcpServers`, applied via the local `RunExecutor`'s\n * `mcpServersOverride` on every `agent.send()`); there is NO\n * `agent.setMcpServers`. So we store the current set here and thread it into\n * each send — visibility changes apply live on the next turn, no restart. The\n * harness MCP entry is composed into this at init/reinit and preserved on\n * every `set_mcp_servers` update.\n */\n mcpServers: Record<string, unknown>;\n /**\n * In-flight `handleInit`/`handleReinit` promise — resolves when init has\n * settled (agent created, or init_error/reinit_error sent; never rejects).\n * `handlePushMessage` awaits it so a `push_message` that races ahead of\n * `Agent.create` waits for the agent instead of erroring \"not initialized.\"\n */\n initSettled: Promise<void> | null;\n /**\n * Native Cursor SDK conversation mode. Sourced from the `init` IPC; persists\n * for the agent's lifetime. `'plan'` tells composer-2.5 to use its built-in\n * `createPlan` tool instead of writing files (soft signal — hooks.json is the\n * hard enforcement layer). `undefined` = SDK default (`'agent'` mode).\n */\n mode?: 'agent' | 'plan';\n}\n\nconst state: RunnerState = {\n agent: null,\n currentRun: null,\n currentGeneration: 0,\n taskId: '',\n harnessToken: '',\n harnessRunnerId: '',\n harnessUrl: '',\n cwd: '',\n apiKey: '',\n pooled: false,\n modelId: '',\n fastMode: false,\n fastModeParam: null,\n mcpServers: {},\n initSettled: null,\n mode: undefined,\n};\n\nfunction send(msg: RunnerToDaemon): void {\n /**\n * `process.send` is only defined when this module is started via\n * `fork()` with an IPC channel. If it is undefined (e.g. the file is\n * accidentally executed directly), there is no way to reach the\n * parent — the runner is unusable; log to stderr and drop.\n */\n if (typeof process.send !== 'function') {\n process.stderr.write(\n `${JSON.stringify({ level: 'error', event: 'runner_no_ipc_channel', dropped: msg.kind })}\\n`\n );\n return;\n }\n process.send(msg);\n}\n\nfunction log(level: 'info' | 'warn' | 'error', message: string, data?: unknown): void {\n send({ kind: 'log', level, message, data });\n}\n\n/**\n * Runner-side throttle for `stream_activity` heartbeats. `onDelta` fires per\n * output chunk (text/thinking/shell-output deltas) — far too often to forward\n * each as IPC. Coalesce to at most one heartbeat per this interval. Kept well\n * below the daemon's stall window (180s) so the daemon reliably sees liveness\n * within it; the daemon applies its own coarser throttle on receipt.\n */\nconst STREAM_ACTIVITY_IPC_THROTTLE_MS = 2_000;\n/** Wall-clock of the last forwarded `stream_activity`. Reset per turn in `handlePushMessage`. */\nlet lastStreamActivitySentAt = 0;\n/**\n * Count of `tool-call-started` updates minus `tool-call-completed` updates for\n * the current turn. Used to emit `tool_execution` IPC edges only on the 0→1\n * (started) and 1→0 (settled) transitions — intermediate count changes produce\n * no IPC traffic. Reset at the start of every turn in `handlePushMessage`\n * and on pooled release (`handleClose`) so a stale count can never survive\n * into a different turn or task.\n */\nlet inFlightToolCount = 0;\n\n/**\n * Forward a throttled liveness heartbeat for the active generation. Called from\n * `onDelta` — ANY interaction update means the run is producing output, so the\n * daemon's stall watchdog should measure true silence rather than step cadence.\n * Drops stale-generation calls (the daemon would filter them anyway).\n */\nfunction maybeSendStreamActivity(generation: number): void {\n if (state.currentGeneration !== generation) return;\n const now = Date.now();\n if (now - lastStreamActivitySentAt < STREAM_ACTIVITY_IPC_THROTTLE_MS) return;\n lastStreamActivitySentAt = now;\n send({ kind: 'stream_activity', generation });\n}\n\n/**\n * Build the harness MCP entry. Uses the runner-scoped header\n * (`x-shipyard-runner-id`) when `harnessRunnerId` is set — that keeps the\n * entry byte-identical across the runner's whole life (init→reinit) so the\n * SDK executor cache key never changes on task switch. Falls back to the\n * legacy per-task header (`x-shipyard-task-id`) for non-pooled spawns.\n */\nfunction buildHarnessServerEntry(args: {\n harnessUrl: string;\n harnessToken: string;\n harnessRunnerId: string;\n taskId: string;\n}): {\n type: 'http';\n url: string;\n headers: Record<string, string>;\n} {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${args.harnessToken}`,\n };\n if (args.harnessRunnerId.length > 0) {\n headers['x-shipyard-runner-id'] = args.harnessRunnerId;\n } else {\n headers['x-shipyard-task-id'] = args.taskId;\n }\n return {\n type: 'http',\n url: args.harnessUrl,\n headers,\n };\n}\n\n/**\n * Compose the SDK `local` option. `settingSources: ['user', 'project']` is\n * parity with Claude/Codex — those runtimes auto-discover the user's installed\n * skills and the repo's project-level rules; leaving settingSources at the\n * default (`[]`) silently disabled Cursor's equivalent. User-authored markdown\n * on the user's own machine; same trust posture as the other agents.\n * Cross-runtime *portability* of those skills is tracked separately in #3955.\n *\n * `cwd: [cwd, skillsDir]` — `cwd[0]` is the repo workspace; `skillsDir` (cwd[1])\n * is Shipyard's task-scoped `~/.shipyard/cursor-skills/{taskId}` dir, which hosts\n * the permission-hook substrate (`.cursor/hooks.json` + hmac + allow). The SDK\n * resolves the WORKSPACE (rules/skills discovery) from `cwd[0]` ONLY\n * (`getExplicitLocalWorkspaceRef` → `n[0]`), so `cwd[1]` is NOT a prompt or\n * rules channel — the Shipyard prompt is delivered via the harness MCP server's\n * `initialize.instructions` instead. `cwd[1]` is retained because it is how the\n * SDK discovers the per-task hook substrate; dropping it would silently disable\n * Cursor permission gating.\n */\nfunction buildLocalOption(\n cwd: string,\n skillsDir: string\n): {\n cwd: string | string[];\n settingSources: SettingSource[];\n} {\n const settingSources: SettingSource[] = ['user', 'project'];\n return {\n ...(skillsDir ? { cwd: [cwd, skillsDir] } : { cwd }),\n settingSources,\n };\n}\n\n/**\n * Named subagent catalog as an SDK option. Omitted when the catalog is empty —\n * passing `agents: {}` would register an empty map and override any agents the\n * SDK might discover itself.\n */\nfunction buildAgentsOption(agents: RunnerAgents | undefined): Record<string, never> {\n return agents !== undefined && Object.keys(agents).length > 0\n ? // eslint-disable-next-line no-restricted-syntax -- AgentDefinition union (model field) requires boundary cast\n { agents: agents as never }\n : {};\n}\n\n/**\n * Acquire the Agent for a task. Priority order:\n * 1. `injectedAgentId` — cross-runtime structured handoff (Wave 7.5). The\n * daemon pre-wrote a fabricated conversation to SQLite; we MUST resume\n * that id or the conversation never surfaces. A same-id `Agent.create()`\n * would PRIMARY-KEY-conflict on the agents row.\n * 2. `resumeAgentId` — persisted from a prior `init_ok`. SDK reads the prior\n * conversation from its SQLite checkpoint store. If the store entry is\n * gone (user wiped ~/.cursor/, etc.) the resume throws; we catch and fall\n * through to `Agent.create` so the spawn succeeds without prior context.\n * 3. `Agent.create` — fresh agent. Default path for never-spawned tasks.\n *\n * Conversation history not preserved on path 3 is acceptable — Shipyard's\n * JSONL is the authoritative log; the SDK's checkpoint store is a latency /\n * context win, not the source of truth.\n */\nasync function acquireAgent(args: {\n apiKey: string;\n taskId: string;\n modelId: string;\n local: ReturnType<typeof buildLocalOption>;\n mcpServers: Record<string, unknown>;\n agentsOption: Record<string, never>;\n injectedAgentId?: string;\n resumeAgentId?: string;\n mode?: 'agent' | 'plan';\n}): Promise<SDKAgent> {\n const baseOptions = {\n apiKey: args.apiKey,\n model: { id: args.modelId },\n local: args.local,\n // eslint-disable-next-line no-restricted-syntax -- McpServerConfig union types require boundary cast\n mcpServers: args.mcpServers as never,\n ...args.agentsOption,\n ...(args.mode !== undefined ? { mode: args.mode } : {}),\n };\n async function createFreshAgent(): Promise<SDKAgent> {\n return Agent.create({ ...baseOptions, name: `shipyard-task-${args.taskId}` });\n }\n if (args.injectedAgentId !== undefined) {\n return Agent.resume(args.injectedAgentId, baseOptions);\n }\n if (args.resumeAgentId !== undefined) {\n try {\n const resumed = await Agent.resume(args.resumeAgentId, baseOptions);\n log('info', 'runner_resume_agent_succeeded', { agentId: args.resumeAgentId });\n return resumed;\n } catch (resumeErr) {\n log('warn', 'runner_resume_agent_failed_falling_back_to_create', {\n agentId: args.resumeAgentId,\n error: resumeErr instanceof Error ? resumeErr.message : String(resumeErr),\n });\n return createFreshAgent();\n }\n }\n return createFreshAgent();\n}\n\n/** Snapshot MCP status for init/reinit ok messages (Cursor has no live stream). */\nfunction buildMcpStatusSnapshot(mcpServers: Record<string, unknown>): Array<{\n name: string;\n status: string;\n}> {\n return Object.keys(mcpServers).map((name) => ({\n name,\n /**\n * Cursor local runtime does not expose a stable MCP-status stream during\n * init. Emit a non-empty snapshot so daemon-side init metadata never\n * races with an empty MCP list.\n */\n status: 'connected',\n }));\n}\n\nasync function handleInit(args: {\n taskId: string;\n cwd: string;\n apiKey: string;\n modelId: string;\n mcpServers: Record<string, unknown>;\n harnessUrl: string;\n harnessToken: string;\n harnessRunnerId: string;\n pooled: boolean;\n skillsDir: string;\n generation: number;\n injectedAgentId?: string;\n resumeAgentId?: string;\n fastMode: boolean;\n fastModeParam: { id: string; value: string } | null;\n agents?: RunnerAgents;\n mode?: 'agent' | 'plan';\n}): Promise<void> {\n state.currentGeneration = args.generation;\n state.taskId = args.taskId;\n state.harnessToken = args.harnessToken;\n state.harnessRunnerId = args.harnessRunnerId;\n state.harnessUrl = args.harnessUrl;\n state.cwd = args.cwd;\n state.apiKey = args.apiKey;\n state.pooled = args.pooled;\n state.modelId = args.modelId;\n state.fastMode = args.fastMode;\n state.fastModeParam = args.fastModeParam;\n state.mode = args.mode;\n try {\n /**\n * The daemon validates `mcpServers` shape before sending. We pass\n * through as-is; a malformed entry will surface as an\n * `Agent.create()` rejection rather than a runner crash.\n */\n const mcpServers: Record<string, unknown> = {\n ...args.mcpServers,\n [HARNESS_SERVER_NAME]: buildHarnessServerEntry({\n harnessUrl: args.harnessUrl,\n harnessToken: args.harnessToken,\n harnessRunnerId: args.harnessRunnerId,\n taskId: args.taskId,\n }),\n };\n state.mcpServers = mcpServers;\n const agent = await acquireAgent({\n apiKey: args.apiKey,\n taskId: args.taskId,\n modelId: args.modelId,\n local: buildLocalOption(args.cwd, args.skillsDir),\n mcpServers,\n agentsOption: buildAgentsOption(args.agents),\n ...(args.injectedAgentId !== undefined && { injectedAgentId: args.injectedAgentId }),\n ...(args.resumeAgentId !== undefined && { resumeAgentId: args.resumeAgentId }),\n ...(args.mode !== undefined && { mode: args.mode }),\n });\n state.agent = agent;\n send({\n kind: 'init_ok',\n sessionId: agent.agentId,\n tools: [],\n model: args.modelId,\n mcpStatus: buildMcpStatusSnapshot(mcpServers),\n });\n } catch (err) {\n await sendInitErrorFromAgentCreateFailure({\n apiKey: args.apiKey,\n modelId: args.modelId,\n err,\n send,\n });\n }\n}\n\n/**\n * Warm-pool reinit (C9). The runner was kept alive after its prior task\n * closed; the daemon is now claiming it for a NEW task. We cancel any active\n * run, dispose the prior Agent, update state, and create a fresh Agent on the\n * SAME warm process — so its `localExecutorCache` is reused (cache HIT on the\n * next send) because the harness entry uses the stable `harnessRunnerId`.\n *\n * FAIL CLOSED: on ANY throw we emit `reinit_error` and do NOT exit. The daemon\n * kills + respawns a fresh cold runner, so a broken warm process can never\n * serve the task — worst case is \"no speedup,\" never breakage or a cross-task\n * leak.\n */\nasync function handleReinit(args: {\n taskId: string;\n modelId: string;\n mcpServers: Record<string, unknown>;\n harnessRunnerId: string;\n harnessToken: string;\n skillsDir: string;\n generation: number;\n resumeAgentId?: string;\n fastMode: boolean;\n fastModeParam: { id: string; value: string } | null;\n agents?: RunnerAgents;\n mode?: 'agent' | 'plan';\n}): Promise<void> {\n try {\n /** Cancel any straggler run from the prior task before swapping Agents. */\n if (state.currentRun) {\n await state.currentRun.cancel().catch(() => {});\n state.currentRun = null;\n }\n /** Dispose the prior Agent; the warm executor cache stays in the process. */\n if (state.agent) {\n await state.agent[Symbol.asyncDispose]().catch(() => {});\n state.agent = null;\n }\n state.currentGeneration = args.generation;\n state.taskId = args.taskId;\n state.modelId = args.modelId;\n state.harnessToken = args.harnessToken;\n state.harnessRunnerId = args.harnessRunnerId;\n state.fastMode = args.fastMode;\n state.fastModeParam = args.fastModeParam;\n state.mode = args.mode;\n const mcpServers: Record<string, unknown> = {\n ...args.mcpServers,\n [HARNESS_SERVER_NAME]: buildHarnessServerEntry({\n harnessUrl: state.harnessUrl,\n harnessToken: args.harnessToken,\n harnessRunnerId: args.harnessRunnerId,\n taskId: args.taskId,\n }),\n };\n state.mcpServers = mcpServers;\n const agent = await acquireAgent({\n apiKey: state.apiKey,\n taskId: args.taskId,\n modelId: args.modelId,\n local: buildLocalOption(state.cwd, args.skillsDir),\n mcpServers,\n agentsOption: buildAgentsOption(args.agents),\n ...(args.resumeAgentId !== undefined && { resumeAgentId: args.resumeAgentId }),\n ...(args.mode !== undefined && { mode: args.mode }),\n });\n state.agent = agent;\n send({ kind: 'reinit_ok', sessionId: agent.agentId });\n } catch (err) {\n send({\n kind: 'reinit_error',\n error: err instanceof Error ? err.message : String(err),\n retryable: false,\n });\n }\n}\n\n/**\n * Parse a `turn-ended` update's `usage` block into `TurnUsage`, or null when\n * absent/invalid. As of @cursor/sdk 1.0.17 `turn-ended` is a first-class member\n * of the typed `InteractionUpdate` union with `usage?: { inputTokens,\n * outputTokens, cacheReadTokens, cacheWriteTokens }` (each a plain `z.number()`),\n * so we narrow via the discriminated `type` rather than widening to `unknown`.\n *\n * The integer/non-negative guard is a DOMAIN constraint, not structural: the\n * SDK schema's `z.number()` admits Infinity / fractional / negative, but our IPC\n * schema requires `int().nonnegative()`. A schema-invalid value would get the\n * whole `summary_completed` frame rejected at parse and wedge the compaction SM,\n * so we reject it here — preTokens is then omitted and the estimated-baseline\n * fallback applies. `Number.isInteger` also rejects NaN/Infinity/non-numbers.\n */\nfunction parseTurnEndedUsage(update: InteractionUpdate): TurnUsage | null {\n /*\n * `== null` (not `=== undefined`): the schema types `usage` as optional-not-null,\n * but this is a forked-child boundary parsing external SDK data — tolerate a\n * future `usage: null` rather than throwing on destructure and crashing the runner.\n */\n if (update.type !== 'turn-ended' || update.usage == null) return null;\n const { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens } = update.usage;\n if (\n Number.isInteger(inputTokens) &&\n inputTokens >= 0 &&\n Number.isInteger(outputTokens) &&\n outputTokens >= 0 &&\n Number.isInteger(cacheReadTokens) &&\n cacheReadTokens >= 0 &&\n Number.isInteger(cacheWriteTokens) &&\n cacheWriteTokens >= 0\n ) {\n return { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens };\n }\n return null;\n}\n\n/**\n * B2: translate a Cursor `onDelta` interaction-update into a runner→daemon IPC\n * signal. Cursor's native summarization emits `summary-started` /\n * `summary-completed` (both bare `{ type }` — no token counts), so the daemon\n * derives `compaction_completed.preTokens` from the last captured `turn-ended`\n * usage instead. Text/thinking/tool deltas are the public `Run.stream()` path's\n * responsibility and are ignored here. All update kinds below are members of the\n * typed `InteractionUpdate` union (@cursor/sdk 1.0.17), so each narrows by `type`.\n */\nfunction forwardSummaryUpdate(\n update: InteractionUpdate,\n generation: number,\n latestUsage: { ref: TurnUsage | null },\n tokenDeltaAccumulator: { count: number }\n): void {\n if (update.type === 'summary-started') {\n send({ kind: 'summary_started', generation });\n return;\n }\n if (update.type === 'summary-completed') {\n const raw = latestUsage.ref;\n /*\n * inputTokensRaw equivalent: input + cache reads + cache writes = total context fill,\n * the quantity the fill-bar renders. Absent when no turn-ended fired yet this turn.\n */\n const preTokens =\n raw !== null ? raw.inputTokens + raw.cacheReadTokens + raw.cacheWriteTokens : undefined;\n send({\n kind: 'summary_completed',\n generation,\n ...(preTokens !== undefined ? { preTokens } : {}),\n });\n return;\n }\n /**\n * `user-message-appended` fires once per `agent.send()` when the SDK has\n * durably written the user turn to its local SQLite checkpoint store. The\n * daemon logs the ack for durability triage; no state mutation needed yet.\n */\n if (update.type === 'user-message-appended') {\n send({ kind: 'user_message_persisted', generation, sessionId: update.userMessage.session_id });\n return;\n }\n /**\n * `token-delta` carries the SDK's running token count during streaming.\n * Accumulated as a SECOND signal — NOT a replacement for `turn-ended.usage`,\n * which carries the input/output/cache breakdown needed for pricing.\n */\n if (update.type === 'token-delta') {\n tokenDeltaAccumulator.count += update.tokens;\n return;\n }\n const usage = parseTurnEndedUsage(update);\n if (usage !== null) {\n latestUsage.ref = usage;\n log('info', 'cursor_turn_usage_received', { generation, ...usage });\n }\n}\n\nasync function streamRun(\n run: Run,\n generation: number,\n latestUsage: { ref: TurnUsage | null },\n tokenDeltaAccumulator: { count: number }\n): Promise<void> {\n try {\n for await (const event of run.stream()) {\n /**\n * If the generation rolled forward (interrupt + new push), stop\n * forwarding events from the stale run. The daemon will already\n * be ignoring them, but stopping here saves IPC traffic.\n */\n if (state.currentGeneration !== generation) {\n log('info', 'runner_stream_aborted_stale_generation', {\n generation,\n currentGeneration: state.currentGeneration,\n });\n return;\n }\n const message: SDKMessage = event;\n send({ kind: 'sdk_message', generation, payload: message });\n }\n const result = await run.wait();\n /**\n * `Run.createdAt` (run.d.ts:42) is the authoritative SDK-side run-start\n * timestamp. The daemon currently synthesizes one from `Date.now()` at\n * the IPC boundary, which can drift by the IPC + cold-build latency\n * (sometimes seconds). Forwarding the SDK's value gives accurate\n * per-run timing for the cost/latency dashboard.\n */\n const createdAt = run.createdAt;\n send({\n kind: 'run_complete',\n generation,\n result,\n ...(latestUsage.ref !== null ? { usage: latestUsage.ref } : {}),\n ...(createdAt !== undefined ? { createdAt } : {}),\n ...(tokenDeltaAccumulator.count > 0 ? { tokenDeltaTotal: tokenDeltaAccumulator.count } : {}),\n });\n } catch (err) {\n const serialized = serializeCursorRunnerError(err);\n send({\n kind: 'run_error',\n generation,\n ...serialized,\n });\n } finally {\n if (state.currentRun === run) {\n state.currentRun = null;\n }\n }\n}\n\n/**\n * Upper bound on how long `agent.send()` may take to return a `Run`. The SDK\n * does not resolve `send()` until it has flipped the run from QUEUED to RUNNING,\n * which it gates on an un-abortable model-validation round-trip to api2.cursor.sh\n * (`resolveLocalModelSelection` runs BEFORE `markRunStarting`). If that round-trip\n * wedges (TLS up, no response) the run sits in QUEUED until undici's 300s\n * headersTimeout — a silent 5-minute stall. `SendOptions` exposes no `signal`/\n * timeout, so we race the call and surface a fast, retryable error instead.\n */\nconst SEND_START_TIMEOUT_MS = 30_000;\n\n/**\n * `agent.send()` bounded by SEND_START_TIMEOUT_MS. On timeout, rejects with a\n * retryable error (duck-typed `isRetryable` is read by serializeCursorRunnerError)\n * so the daemon surfaces \"send a message to retry\" rather than a 5-min hang.\n */\nasync function sendWithStartTimeout(\n agent: SDKAgent,\n message: string | SDKUserMessage,\n sendOptions: SendOptions\n): Promise<Run> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n agent.send(message, sendOptions),\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n reject(\n Object.assign(\n new Error(\n `Cursor did not start the run within ${SEND_START_TIMEOUT_MS / 1000}s (model validation / handshake stalled). Send a message to try again.`\n ),\n { name: 'CursorSendStartTimeout', isRetryable: true }\n )\n );\n }, SEND_START_TIMEOUT_MS);\n }),\n ]);\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n}\n\nasync function handlePushMessage(args: {\n generation: number;\n content: unknown[];\n skillBodies?: CursorResolvedSkillBody[];\n forceLocalRun: boolean;\n}): Promise<void> {\n /**\n * A push_message can arrive while Agent.create is still in flight: the daemon\n * sends `init` then the first turn back-to-back, and the runner's IPC dispatch\n * is NOT serialized (process.on('message') fires `void dispatch(...)` per\n * message). Without this wait, the first turn hits the guard below before\n * `Agent.create` resolves and fails with \"cursor agent not initialized.\"\n * `initSettled` resolves when init/reinit finishes (success or a sent\n * init_error — it never rejects; the catch is belt-and-suspenders).\n */\n if (!state.agent && state.initSettled) {\n await state.initSettled.catch(() => undefined);\n }\n const agent = state.agent;\n if (!agent) {\n send({\n kind: 'run_error',\n generation: args.generation,\n error: 'cursor agent not initialized',\n errorName: 'ConfigurationError',\n isRetryable: false,\n });\n return;\n }\n state.currentGeneration = args.generation;\n /*\n * New turn: clear the heartbeat throttle so the first streamed delta of this\n * turn forwards a stream_activity immediately instead of being coalesced\n * against the previous turn's last heartbeat. Also reset the in-flight tool\n * counter so tool-execution liveness edges start fresh from 0 each turn.\n */\n lastStreamActivitySentAt = 0;\n inFlightToolCount = 0;\n /**\n * `args.content` arrives as `unknown[]` over IPC (the wire schema uses\n * `z.array(z.unknown())` for forward-compat — see comment in\n * cursor-runner-protocol.ts). Validate at the boundary via Zod rather\n * than cast: lint:extra forbids type assertions outside `as const`/`as never`,\n * and Zod gives us a runtime guarantee that matches the type.\n */\n const parsedContent = ContentBlockSchema.array().safeParse(args.content);\n if (!parsedContent.success) {\n log('warn', 'runner_push_message_invalid_content', {\n errorCount: parsedContent.error.issues.length,\n firstIssue: parsedContent.error.issues[0],\n });\n return;\n }\n const userInput: CursorUserInput = buildCursorUserPrompt(\n parsedContent.data,\n (entry) => log(entry.event.includes('error') ? 'warn' : 'info', entry.event, entry),\n { skillBodies: args.skillBodies }\n );\n if (userInput.text === '' && userInput.images.length === 0) {\n log('warn', 'runner_push_message_empty_after_build');\n return;\n }\n if (userInput.images.length > 0) {\n log('info', 'runner_push_message_with_images', {\n count: userInput.images.length,\n totalBytes: userInput.images.reduce((acc, img) => {\n const data = 'data' in img ? img.data : '';\n return acc + Math.floor((data.length * 3) / 4);\n }, 0),\n });\n }\n try {\n const message: string | SDKUserMessage =\n userInput.images.length > 0\n ? { text: userInput.text, images: userInput.images }\n : userInput.text;\n /**\n * Two send-option layers combine here:\n *\n * B2 — subscribe to the interaction-update stream so Cursor's native\n * server-managed summarization surfaces as `summary_started` /\n * `summary_completed` IPC messages. These updates flow ONLY through\n * `onDelta` — the public `Run.stream()` `SDKMessage` union has no summary\n * variant (verified against @cursor/sdk@1.0.13). Without this the daemon's\n * estimated context fill-bar never resets after Cursor compacts. `onDelta`\n * is unconditional.\n *\n * Fast Mode — attaches `SendOptions.model.params = [{id, value}]`, but only\n * when both knobs line up (the runner must NOT send an empty `params: []`).\n * Busy recovery may also attach `local.force`, but only when the daemon has\n * generation-guarded the retry.\n */\n /**\n * Mutable ref shared between the `onDelta` closure and `streamRun` so\n * the latest `turn-ended` usage can be forwarded on `run_complete`\n * without a module-level variable (each push_message gets its own ref,\n * avoiding any cross-turn contamination).\n */\n const latestUsage: { ref: TurnUsage | null } = { ref: null };\n const tokenDeltaAccumulator = { count: 0 };\n /**\n * `idempotencyKey` prevents the SDK from creating a duplicate run if the\n * runner crashes mid-`agent.send()` and we re-attempt with the same\n * generation after `Agent.resume()`. `${taskId}-${generation}` is unique\n * per turn (generation is bumped every turn or interrupt), so a clean\n * retry under the same key collapses server-side. Without this, an\n * Agent.resume-then-resend window can produce a duplicate run that the\n * SDK queues behind the original and we see two responses for one turn.\n */\n const sendOptions: SendOptions = {\n idempotencyKey: `${state.taskId}-${args.generation}`,\n /**\n * Per-RUN MCP set (live visibility). `@cursor/sdk@1.0.17` resolves\n * `opts.mcpServers ?? this.options.mcpServers` per send and forwards it to\n * the local `RunExecutor` as `mcpServersOverride`, so the set applied on\n * THIS turn reflects the latest `set_mcp_servers` IPC. Boundary cast: same\n * `McpServerConfig`-union reason as `Agent.create({ mcpServers: ... })`.\n */\n // eslint-disable-next-line no-restricted-syntax -- McpServerConfig union types require boundary cast\n mcpServers: state.mcpServers as never,\n onDelta: ({ update }) => {\n /*\n * Any interaction update means the run is producing output mid-step, so\n * feed the stall watchdog — it must measure true silence, not the\n * cadence of completed steps.\n */\n maybeSendStreamActivity(args.generation);\n /*\n * Track tool-call-started / tool-call-completed boundaries so the\n * daemon can arm an extended stall window while a tool is in flight.\n * Only emit IPC on the 0→1 (started) and 1→0 (settled) transitions —\n * intermediate count changes (2nd parallel tool start, etc.) produce\n * no IPC traffic. Stale-generation guard mirrors maybeSendStreamActivity.\n */\n if (state.currentGeneration === args.generation) {\n const edge = decideToolExecutionEdge(inFlightToolCount, update.type);\n inFlightToolCount = edge.count;\n if (edge.edge !== null) {\n send({ kind: 'tool_execution', generation: args.generation, phase: edge.edge });\n }\n }\n forwardSummaryUpdate(update, args.generation, latestUsage, tokenDeltaAccumulator);\n },\n onStep: ({ step }) => {\n /*\n * Guard against a stale generation completing after the daemon\n * incremented #generation for a new run (same pattern as streamRun's\n * stale-gen guard).\n */\n if (state.currentGeneration !== args.generation) return;\n send({ kind: 'step_progress', generation: args.generation });\n /*\n * Capture the native plan here rather than accumulating prose at\n * turn-complete (the codex pattern): a createPlan toolCall carries its\n * complete `args.plan` atomically, so there's no half-formed-content\n * risk that would need a turn-completion gate. No mode gate needed —\n * the SDK only invokes createPlan under `mode:'plan'`. The subprocess\n * drops empty plans before emitting plan_content_ready.\n */\n if (step.type === 'toolCall' && step.message.type === 'createPlan') {\n send({\n kind: 'plan_proposed',\n generation: args.generation,\n plan: step.message.args.plan,\n });\n }\n },\n ...(state.fastMode && state.fastModeParam !== null\n ? { model: { id: state.modelId, params: [state.fastModeParam] } }\n : {}),\n ...(args.forceLocalRun ? { local: { force: true } } : {}),\n };\n const run = await sendWithStartTimeout(agent, message, sendOptions);\n state.currentRun = run;\n await streamRun(run, args.generation, latestUsage, tokenDeltaAccumulator);\n } catch (err) {\n const serialized = serializeCursorRunnerError(err);\n send({\n kind: 'run_error',\n generation: args.generation,\n ...serialized,\n });\n }\n}\n\nasync function handleInterrupt(generation: number): Promise<void> {\n if (!state.currentRun) {\n log('info', 'runner_interrupt_no_active_run', { generation });\n return;\n }\n try {\n await state.currentRun.cancel();\n } catch (err) {\n log('warn', 'runner_interrupt_failed', {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n}\n\nasync function handleClose(): Promise<void> {\n try {\n if (state.currentRun) {\n await state.currentRun.cancel();\n }\n } catch {\n /**\n * Closing — already on the exit path, swallow.\n */\n }\n try {\n if (state.agent) {\n await state.agent[Symbol.asyncDispose]();\n }\n } catch {\n /** swallow — same reason */\n }\n state.agent = null;\n state.currentRun = null;\n /**\n * Pool-managed runner: keep the process (and its warm `localExecutorCache`)\n * alive. Reset to a quiescent state and ack `pooled_idle` so the daemon can\n * reuse us for the next matching task via `reinit`. The Agent is disposed\n * above; only the heavyweight executor cache survives.\n */\n if (state.pooled) {\n state.taskId = '';\n state.fastMode = false;\n state.fastModeParam = null;\n state.mode = undefined;\n /**\n * Defensive: a turn whose final tool never emitted `tool-call-completed`\n * (killed mid-tool) must not leak its in-flight count into the next\n * pooled task's first turn.\n */\n inFlightToolCount = 0;\n send({ kind: 'pooled_idle' });\n return;\n }\n process.exit(0);\n}\n\n/**\n * Translate an `init` IPC message into `handleInit` args. Pure — extracted from\n * `dispatch` so the conditional-spread branches for optional fields don't load\n * the switch's cognitive complexity past budget.\n */\nfunction buildHandleInitArgs(\n msg: Extract<DaemonToRunner, { kind: 'init' }>\n): Parameters<typeof handleInit>[0] {\n return {\n taskId: msg.taskId,\n cwd: msg.cwd,\n apiKey: msg.apiKey,\n modelId: msg.modelId,\n mcpServers: msg.mcpServers,\n harnessUrl: msg.harnessUrl,\n harnessToken: msg.harnessToken,\n harnessRunnerId: msg.harnessRunnerId ?? '',\n pooled: msg.pooled ?? false,\n skillsDir: msg.skillsDir,\n generation: msg.generation,\n ...(msg.injectedAgentId !== undefined && { injectedAgentId: msg.injectedAgentId }),\n ...(msg.resumeAgentId !== undefined && { resumeAgentId: msg.resumeAgentId }),\n fastMode: msg.fastMode ?? false,\n fastModeParam: msg.fastModeParam ?? null,\n ...(msg.agents !== undefined && { agents: msg.agents }),\n ...(msg.mode !== undefined && { mode: msg.mode }),\n };\n}\n\n/**\n * Translate a `reinit` IPC message into `handleReinit` args. Pure — extracted\n * from `dispatch` for the same reason as `buildHandleInitArgs`: the\n * conditional-spread branches for optional fields would otherwise load the\n * switch's cognitive complexity past budget.\n */\nfunction buildHandleReinitArgs(\n msg: Extract<DaemonToRunner, { kind: 'reinit' }>\n): Parameters<typeof handleReinit>[0] {\n return {\n taskId: msg.taskId,\n modelId: msg.modelId,\n mcpServers: msg.mcpServers,\n harnessRunnerId: msg.harnessRunnerId,\n harnessToken: msg.harnessToken,\n skillsDir: msg.skillsDir,\n generation: msg.generation,\n ...(msg.resumeAgentId !== undefined && { resumeAgentId: msg.resumeAgentId }),\n fastMode: msg.fastMode ?? false,\n fastModeParam: msg.fastModeParam ?? null,\n ...(msg.agents !== undefined && { agents: msg.agents }),\n ...(msg.mode !== undefined && { mode: msg.mode }),\n };\n}\n\nasync function dispatch(raw: unknown): Promise<void> {\n const msg = parseDaemonToRunner(raw);\n if (msg === null) {\n log('warn', 'runner_invalid_ipc_message', { raw });\n return;\n }\n switch (msg.kind) {\n case 'init':\n state.initSettled = handleInit(buildHandleInitArgs(msg));\n return;\n case 'reinit':\n state.initSettled = handleReinit(buildHandleReinitArgs(msg));\n return;\n case 'set_pooled':\n /**\n * Flip the pooled flag so the next `close` keeps (pooled=true) or exits\n * (pooled=false) the process. Per-state, not per-turn.\n */\n state.currentGeneration = msg.generation;\n state.pooled = msg.pooled;\n log('info', 'runner_set_pooled_recorded', { pooled: msg.pooled });\n return;\n case 'push_message':\n await handlePushMessage({\n generation: msg.generation,\n content: msg.content,\n skillBodies: msg.skillBodies,\n forceLocalRun: msg.forceLocalRun ?? false,\n });\n return;\n case 'interrupt':\n await handleInterrupt(msg.generation);\n return;\n case 'set_model':\n /**\n * `@cursor/sdk` accepts `model` via `SendOptions` on each\n * `send()` call; there is no `agent.setModel()`. The runner\n * stores the new id and applies it on the next push. (Wired\n * in S2b — for S2a we just acknowledge.)\n */\n state.currentGeneration = msg.generation;\n log('info', 'runner_set_model_recorded', { modelId: msg.modelId });\n return;\n case 'set_mcp_servers':\n /**\n * Live per-send MCP visibility. `@cursor/sdk@1.0.17` has no\n * `agent.setMcpServers` — the MCP set is a per-RUN parameter applied via\n * `SendOptions.mcpServers` on every `agent.send()`. So we store the new\n * set (re-composing the harness MCP entry, which the daemon's\n * `resolveServersForTask` payload omits) and the NEXT `push_message`\n * carries it. No agent recreate, no restart banner — the Cursor\n * subprocess no longer raises `mcp_mid_thread_reload_needed`.\n */\n state.currentGeneration = msg.generation;\n state.mcpServers = {\n ...msg.servers,\n [HARNESS_SERVER_NAME]: buildHarnessServerEntry({\n harnessUrl: state.harnessUrl,\n harnessToken: state.harnessToken,\n harnessRunnerId: state.harnessRunnerId,\n taskId: state.taskId,\n }),\n };\n log('info', 'runner_set_mcp_servers_applied_live', {\n count: Object.keys(state.mcpServers).length,\n });\n return;\n case 'set_harness_task_id':\n state.taskId = msg.taskId;\n state.harnessToken = msg.token;\n state.currentGeneration = msg.generation;\n log('info', 'runner_harness_task_id_recorded', { taskId: msg.taskId });\n return;\n case 'set_permission_mode':\n /**\n * R1 plan mode: the daemon rewrote the mode file and hooks.json before\n * sending this. The runner records the generation and logs for triage.\n * No action needed — the next hook invocation reads the new mode file.\n */\n state.currentGeneration = msg.generation;\n log('info', 'runner_set_permission_mode_recorded', { mode: msg.mode });\n return;\n case 'set_fast_mode':\n /**\n * Fast Mode is per-state, not per-turn — record the new flag without\n * bumping the generation. The next `push_message` will read\n * `state.fastMode` when building `SendOptions`.\n */\n state.currentGeneration = msg.generation;\n state.fastMode = msg.fastMode;\n log('info', 'runner_set_fast_mode_recorded', { fastMode: msg.fastMode });\n return;\n case 'close':\n await handleClose();\n return;\n default: {\n /**\n * Compile-time exhaustiveness — adding a new variant to the\n * protocol forces this switch to be updated.\n */\n const _exhaustive: never = msg;\n log('error', 'runner_unhandled_message_kind', {\n msg: _exhaustive,\n });\n return;\n }\n }\n}\n\nprocess.on('message', (raw) => {\n /**\n * `dispatch` is async but the IPC handler is sync. Swallow any\n * promise rejection that escapes dispatch's own try/catch — those\n * are programmer errors, and crashing the runner over them just\n * trades a logged warning for a process exit + supervisor respawn\n * cycle.\n */\n void dispatch(raw).catch((err: unknown) => {\n log('error', 'runner_dispatch_unhandled_rejection', {\n error: err instanceof Error ? err.message : String(err),\n });\n });\n});\n\nprocess.on('disconnect', () => {\n /**\n * Parent went away (daemon crash, fork channel torn down). Exit\n * with a non-zero code so the supervisor records the abnormal\n * termination.\n */\n process.exit(1);\n});\n\n/**\n * Crash-on-uncaught: any uncaught error or unhandled rejection in\n * native code (sqlite, FFI) or SDK internals SHOULD bring this child\n * down — the daemon-side supervisor records the exit and the session\n * FSM tears down. Silent recovery here would mask real bugs.\n */\nprocess.on('uncaughtException', (err) => {\n process.stderr.write(\n `${JSON.stringify({\n level: 'error',\n event: 'runner_uncaught_exception',\n error: err.message,\n stack: err.stack,\n })}\\n`\n );\n process.exit(2);\n});\n\nprocess.on('unhandledRejection', (reason) => {\n process.stderr.write(\n `${JSON.stringify({\n level: 'error',\n event: 'runner_unhandled_rejection',\n reason: reason instanceof Error ? reason.message : String(reason),\n })}\\n`\n );\n process.exit(3);\n});\n","import { existsSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { basename, dirname, join } from 'node:path';\n\nexport interface CursorRipgrepDeps {\n env?: NodeJS.ProcessEnv;\n resolve?: (id: string) => string;\n exists?: (path: string) => boolean;\n platform?: NodeJS.Platform;\n arch?: string;\n}\n\nexport type CursorRipgrepConfigureResult =\n | { status: 'already-configured'; path: string }\n | { status: 'configured'; path: string }\n | { status: 'missing'; path: null }\n | { status: 'resolve-error'; path: null; error: unknown };\n\n/**\n * Point @cursor/sdk at the ripgrep binary bundled in its platform package.\n * The SDK currently has no public configureRipgrepPath export, so this is the\n * narrow chokepoint for the documented env override it already honors.\n */\nexport function configureCursorRipgrepPath(\n deps: CursorRipgrepDeps = {}\n): CursorRipgrepConfigureResult {\n const env = deps.env ?? process.env;\n if (env.CURSOR_RIPGREP_PATH) {\n return { status: 'already-configured', path: env.CURSOR_RIPGREP_PATH };\n }\n\n try {\n const rg = resolveCursorBundledRipgrepPath(deps);\n if (rg === null) return { status: 'missing', path: null };\n env.CURSOR_RIPGREP_PATH = rg;\n return { status: 'configured', path: rg };\n } catch (error) {\n return { status: 'resolve-error', path: null, error };\n }\n}\n\nexport function resolveCursorBundledRipgrepPath(deps: CursorRipgrepDeps = {}): string | null {\n const resolve = deps.resolve ?? createRequire(import.meta.url).resolve;\n const exists = deps.exists ?? existsSync;\n const platform = deps.platform ?? process.platform;\n const arch = deps.arch ?? process.arch;\n\n const sdkMain = resolve('@cursor/sdk');\n let sdkRoot = dirname(sdkMain);\n while (sdkRoot !== dirname(sdkRoot) && basename(sdkRoot) !== 'sdk') {\n sdkRoot = dirname(sdkRoot);\n }\n if (basename(sdkRoot) !== 'sdk') return null;\n\n const bin = platform === 'win32' ? 'rg.exe' : 'rg';\n const rg = join(sdkRoot, '..', `sdk-${platform}-${arch}`, 'bin', bin);\n return exists(rg) ? rg : null;\n}\n","/**\n * Formats and reports `init_error` messages for the Cursor runner.\n *\n * `formatCursorInitError` is the pure formatter: given a requested model id,\n * the original SDK error, and the result of `Cursor.models.list()`, it builds\n * the human-readable diagnostic string. No I/O, no side effects.\n *\n * `sendInitErrorFromAgentCreateFailure` is the impure shell: on\n * `Agent.create()` failure it probes `Cursor.models.list()` (5 s cap), feeds\n * the result to `formatCursorInitError`, and sends the `init_error` IPC\n * message via the injected `send` callback.\n *\n * The Cursor SDK delegates model-id resolution to the backend, so an unknown\n * `composer-X.Y` id surfaces as an opaque `ModelNotFound` (or similar)\n * server error. Shipyard's catalog (`cursor-model-catalog.ts`) hardcodes\n * `composer-2.5` based on Cursor's docs; if Cursor renames the model\n * server-side, every spawn fails and our only signal is the opaque SDK\n * message. We've shipped 5 Cursor fixes without ever exercising the literal\n * model string — see PR history in #3680, #3744, #3752, #3758, #3787.\n *\n * Lives alongside the runner per the FC/IS pattern in `engineering-standards.md`.\n */\n\nimport { Cursor } from '@cursor/sdk';\nimport { serializeCursorRunnerError } from './cursor-runner-error.js';\nimport type { RunnerToDaemon } from './cursor-runner-protocol.js';\n\nexport interface ModelInfo {\n id: string;\n aliases?: readonly string[];\n}\n\nexport interface CursorInitErrorInput {\n /** The model id Shipyard sent to `Agent.create()`. */\n requestedModelId: string;\n /** The SDK's original failure message (preserved verbatim at the end). */\n originalError: string;\n /**\n * Result of `Cursor.models.list()`. `null` means the list call itself\n * failed (offline, auth invalid, rate-limited) — formatter falls back\n * to the original error without speculating about available models.\n */\n availableModels: readonly ModelInfo[] | null;\n /** Optional: the error string from the failed list call, for triage. */\n listError?: string;\n}\n\nexport function formatCursorInitError(input: CursorInitErrorInput): string {\n const { requestedModelId, originalError, availableModels, listError } = input;\n if (availableModels === null) {\n const listPart = listError\n ? ` (Cursor.models.list() also failed: ${listError})`\n : ' (Cursor.models.list() unavailable)';\n return `Agent.create failed for model '${requestedModelId}'. ${originalError}${listPart}`;\n }\n if (availableModels.length === 0) {\n return `Agent.create failed for model '${requestedModelId}'. Cursor.models.list() returned no models. ${originalError}`;\n }\n const requested = requestedModelId.toLowerCase();\n const exactMatch = availableModels.find(\n (m) =>\n m.id.toLowerCase() === requested ||\n (m.aliases ?? []).some((a) => a.toLowerCase() === requested)\n );\n const idsList = availableModels.map((m) => m.id).join(', ');\n const aliasParts = availableModels\n .filter((m) => (m.aliases?.length ?? 0) > 0)\n .map((m) => `${m.id}=[${(m.aliases ?? []).join('|')}]`);\n const aliasesList = aliasParts.length > 0 ? aliasParts.join(', ') : '(none)';\n const matchPart = exactMatch\n ? ` The id IS in the available set (${exactMatch.id}) — failure is unrelated to model resolution.`\n : ` The id is NOT in the available set — likely renamed or deprecated server-side.`;\n return `Agent.create failed for model '${requestedModelId}'.${matchPart} Available ids: ${idsList}. Aliases: ${aliasesList}. Original: ${originalError}`;\n}\n\nconst INIT_MODEL_LIST_TIMEOUT_MS = 5000;\n\nexport async function sendInitErrorFromAgentCreateFailure(args: {\n apiKey: string;\n modelId: string;\n err: unknown;\n send: (msg: RunnerToDaemon) => void;\n}): Promise<void> {\n const originalError = args.err instanceof Error ? args.err.message : String(args.err);\n /**\n * On Agent.create failure, probe Cursor.models.list() once to capture\n * the authoritative {id, aliases} set. If the requested model is in\n * that set, the failure is unrelated to model resolution; if not, the\n * id is stale and the log line names the replacement immediately.\n *\n * Best-effort: the list call may itself fail (offline, auth, rate-\n * limited). The formatter handles a null `availableModels` by falling\n * back to the original error verbatim — no information loss vs the\n * pre-Item-1 behavior.\n */\n let availableModels: ModelInfo[] | null = null;\n let listError: string | undefined;\n /**\n * @cursor/sdk@1.0.13's CursorRequestOptions has no AbortSignal or timeout\n * field — a hung backend would block init_error indefinitely and wedge\n * the session FSM in \"initializing\". 5s cap restores the pre-Item-1\n * upper bound (the original code sent init_error immediately) at the\n * cost of degrading the diagnostic when Cursor's API is slow.\n */\n let timeoutHandle: ReturnType<typeof setTimeout> | undefined;\n try {\n const listPromise = Cursor.models.list({ apiKey: args.apiKey });\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutHandle = setTimeout(\n () => reject(new Error(`timed out after ${INIT_MODEL_LIST_TIMEOUT_MS}ms`)),\n INIT_MODEL_LIST_TIMEOUT_MS\n );\n });\n const models = await Promise.race([listPromise, timeoutPromise]);\n availableModels = models.map((m) => ({ id: m.id, aliases: m.aliases }));\n } catch (listErr) {\n listError = listErr instanceof Error ? listErr.message : String(listErr);\n } finally {\n if (timeoutHandle) clearTimeout(timeoutHandle);\n }\n const serialized = serializeCursorRunnerError(args.err);\n args.send({\n kind: 'init_error',\n error: formatCursorInitError({\n requestedModelId: args.modelId,\n originalError,\n availableModels,\n listError,\n }),\n ...(serialized.errorName !== undefined ? { errorName: serialized.errorName } : {}),\n ...(serialized.errorCode !== undefined ? { errorCode: serialized.errorCode } : {}),\n ...(serialized.statusCode !== undefined ? { statusCode: serialized.statusCode } : {}),\n retryable: serialized.isRetryable,\n });\n}\n","/**\n * Serialize thrown values from `@cursor/sdk` into the wire shape the daemon\n * expects on `run_error` / `init_error` IPC messages.\n *\n * Pure FC/IS helper — no I/O. Duck-types SDK error fields because errors\n * arrive as live class instances inside the runner process.\n */\n\nexport interface CursorRunnerErrorWire {\n error: string;\n errorName?: string;\n errorCode?: string;\n statusCode?: number;\n isRetryable: boolean;\n}\n\nfunction readStringField(rec: Record<string, unknown>, key: string): string | undefined {\n const value = rec[key];\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction readNumberField(rec: Record<string, unknown>, key: string): number | undefined {\n const value = rec[key];\n return typeof value === 'number' ? value : undefined;\n}\n\nfunction readBooleanField(rec: Record<string, unknown>, key: string): boolean | undefined {\n const value = rec[key];\n return typeof value === 'boolean' ? value : undefined;\n}\n\n/**\n * Extract message, SDK error `name`, `code`, `status`, and `isRetryable`\n * from a thrown value for IPC transport to the daemon-side classifier.\n */\nexport function serializeCursorRunnerError(err: unknown): CursorRunnerErrorWire {\n if (typeof err === 'object' && err !== null) {\n // eslint-disable-next-line no-restricted-syntax -- unknown thrown value narrowed by typeof; Record probe for duck-typed SDK errors\n const rec = err as Record<string, unknown>;\n const message =\n typeof rec.message === 'string' && rec.message.length > 0 ? rec.message : String(err);\n const wire: CursorRunnerErrorWire = {\n error: message,\n isRetryable: readBooleanField(rec, 'isRetryable') ?? false,\n };\n const errorName = readStringField(rec, 'name');\n const errorCode = readStringField(rec, 'code');\n const statusCode = readNumberField(rec, 'status');\n if (errorName !== undefined) wire.errorName = errorName;\n if (errorCode !== undefined) wire.errorCode = errorCode;\n if (statusCode !== undefined) wire.statusCode = statusCode;\n return wire;\n }\n return { error: String(err), isRetryable: false };\n}\n","/**\n * cursor-tool-execution-edge — pure helper for tracking in-flight Cursor\n * tool calls and emitting IPC liveness edges.\n *\n * The @cursor/sdk's `InteractionUpdate` union includes `'tool-call-started'`\n * and `'tool-call-completed'` variants in the `onDelta` callback. Tools like\n * `git commit` (whose pre-commit hooks run `pnpm check` for 3–10 min) emit\n * no `stream_activity` deltas during execution — the SDK is truly silent\n * while the shell/tool subprocess runs. Without this edge the stall watchdog\n * (180s mid-run) false-kills a healthy runner executing a long tool.\n *\n * Solution: track in-flight count; emit an IPC `tool_execution` message only\n * on the 0→1 (started) and 1→0 (settled) transitions so the daemon can arm\n * an extended per-tool stall window on `started` and revert on `settled`.\n *\n * No I/O, no side effects. All exports are deterministic pure functions.\n */\n\nexport interface ToolExecutionEdgeResult {\n /** Updated in-flight tool count after applying the update type. */\n count: number;\n /**\n * Non-null only on a 0→1 (`'started'`) or 1→0 (`'settled'`) transition.\n * Null for any intermediate count change (1→2, 3→2, etc.) — only the\n * boundary edges matter for the daemon's window-switching decision.\n */\n edge: 'started' | 'settled' | null;\n}\n\n/**\n * Given the current in-flight tool count and an `InteractionUpdate` type\n * string, return the new count and (if a liveness edge occurred) the edge\n * direction.\n *\n * - `'tool-call-started'` increments; edge is `'started'` only on 0→1.\n * - `'tool-call-completed'` decrements (clamped at 0); edge is `'settled'`\n * only on 1→0.\n * - Any other type is a no-op: count unchanged, edge null.\n */\nexport function decideToolExecutionEdge(\n inFlightCount: number,\n updateType: string\n): ToolExecutionEdgeResult {\n if (updateType === 'tool-call-started') {\n const newCount = inFlightCount + 1;\n return { count: newCount, edge: newCount === 1 ? 'started' : null };\n }\n if (updateType === 'tool-call-completed') {\n const newCount = Math.max(0, inFlightCount - 1);\n return { count: newCount, edge: inFlightCount === 1 ? 'settled' : null };\n }\n return { count: inFlightCount, edge: null };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/services/session/cursor-runner.ts","../src/shared/capabilities/runtime/cursor-ripgrep.ts","../src/services/session/cursor-init-error-reporter.ts","../src/services/session/cursor-init-error-formatter.ts","../src/services/session/cursor-runner-error.ts","../src/services/session/cursor-tool-execution-edge.ts"],"sourcesContent":["/**\n * cursor-runner — forked Node child that owns the `@cursor/sdk` lifecycle\n * outside the daemon's V8 isolate (plan v2 §2).\n *\n * The daemon spawns this entry via `child_process.fork(require.resolve(\n * './cursor-runner.js'))` once per Cursor task. The child:\n * 1. waits for an `init` IPC message,\n * 2. calls `Agent.create()` from `@cursor/sdk`,\n * 3. on each `push_message`, calls `agent.send()` and iterates\n * `Run.stream()`, forwarding every `SDKMessage` back to the daemon,\n * 4. on `interrupt`, cancels the in-flight run,\n * 5. on `close`, disposes the agent and exits 0.\n *\n * Native panics (sqlite, FFI) crash THIS child only. The daemon-side\n * supervisor (S2b) observes the child exit, emits `subprocess_died`, and\n * the session FSM tears down cleanly.\n *\n * Per-tool gating uses the `.cursor/hooks.json` `preToolUse` hook script +\n * cursor-hook-socket (cursor-hook-socket.ts) — @cursor/sdk@1.0.13 has no\n * public respond-to-request API.\n */\n\nimport {\n Agent,\n type InteractionUpdate,\n type Run,\n type SDKAgent,\n type SDKMessage,\n type SDKUserMessage,\n type SendOptions,\n type SettingSource,\n} from '@cursor/sdk';\nimport { ContentBlockSchema, HARNESS_SERVER_NAME } from '@shipyard/loro-schema';\n\nimport { configureCursorRipgrepPath } from '../../shared/capabilities/runtime/cursor-ripgrep.js';\nimport {\n buildCursorUserPrompt,\n type CursorResolvedSkillBody,\n type CursorUserInput,\n} from './cursor-content-builder.js';\nimport { sendInitErrorFromAgentCreateFailure } from './cursor-init-error-reporter.js';\nimport { serializeCursorRunnerError } from './cursor-runner-error.js';\nimport {\n type DaemonToRunner,\n parseDaemonToRunner,\n type RunnerToDaemon,\n} from './cursor-runner-protocol.js';\nimport { decideToolExecutionEdge } from './cursor-tool-execution-edge.js';\n\nconfigureCursorRipgrepPath();\n\n/**\n * Real token usage from a `turn-ended` interaction update. Captured during\n * `onDelta` and forwarded on `run_complete` so the daemon can map real counts\n * onto the `TurnResult` instead of marking everything as estimated.\n */\ninterface TurnUsage {\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n}\n\n/** Named subagent catalog shape as it arrives over IPC (mirrors the protocol). */\ntype RunnerAgents = Record<\n string,\n {\n description: string;\n prompt: string;\n model?: { id: string } | 'inherit';\n mcpServers?: string[];\n }\n>;\n\ninterface RunnerState {\n agent: SDKAgent | null;\n currentRun: Run | null;\n currentGeneration: number;\n taskId: string;\n harnessToken: string;\n /**\n * Stable per-runner identity for the runner-scoped harness path. When set,\n * the harness MCP entry uses `x-shipyard-runner-id: harnessRunnerId` instead\n * of `x-shipyard-task-id: taskId`, keeping the `mcpServers` map (and thus the\n * SDK executor cache key) byte-identical across task switches. Empty string\n * = legacy per-task harness path.\n */\n harnessRunnerId: string;\n /** Harness MCP server URL, retained so reinit can rebuild the entry. */\n harnessUrl: string;\n /** Workspace cwd, retained so reinit can rebuild `local.cwd`. */\n cwd: string;\n /** Cursor API key, retained so reinit can recreate the Agent. */\n apiKey: string;\n /**\n * Pool-managed flag. When true, `close` disposes the Agent but does NOT\n * `process.exit(0)` — the runner acks `pooled_idle` and the daemon keeps the\n * warm process alive for the next claim.\n */\n pooled: boolean;\n /**\n * Cursor model id supplied at init. Threaded into every\n * `SendOptions.model.id` when Fast Mode is on so the runtime knows which\n * model the params belong to.\n */\n modelId: string;\n /**\n * Live Fast Mode flag. Initial value from `init`; updated by\n * `set_fast_mode` IPC. The runner attaches Fast Mode params only when\n * both this is true AND `fastModeParam` is non-null.\n */\n fastMode: boolean;\n /**\n * Resolved Fast Mode `{id, value}` binding for `modelId`, sourced from\n * the daemon's discovery cache. Null = no binding known; the runner\n * sends the plain `agent.send(message)` shape regardless of `fastMode`.\n */\n fastModeParam: { id: string; value: string } | null;\n /**\n * Live MCP server set, seeded at init/reinit and updated by `set_mcp_servers`\n * IPC. `@cursor/sdk@1.0.17` takes the MCP set as a per-RUN parameter\n * (`SendOptions.mcpServers`, applied via the local `RunExecutor`'s\n * `mcpServersOverride` on every `agent.send()`); there is NO\n * `agent.setMcpServers`. So we store the current set here and thread it into\n * each send — visibility changes apply live on the next turn, no restart. The\n * harness MCP entry is composed into this at init/reinit and preserved on\n * every `set_mcp_servers` update.\n */\n mcpServers: Record<string, unknown>;\n /**\n * In-flight `handleInit`/`handleReinit` promise — resolves when init has\n * settled (agent created, or init_error/reinit_error sent; never rejects).\n * `handlePushMessage` awaits it so a `push_message` that races ahead of\n * `Agent.create` waits for the agent instead of erroring \"not initialized.\"\n */\n initSettled: Promise<void> | null;\n /**\n * Native Cursor SDK conversation mode. Sourced from the `init` IPC; persists\n * for the agent's lifetime. `'plan'` tells composer-2.5 to use its built-in\n * `createPlan` tool instead of writing files (soft signal — hooks.json is the\n * hard enforcement layer). `undefined` = SDK default (`'agent'` mode).\n */\n mode?: 'agent' | 'plan';\n}\n\nconst state: RunnerState = {\n agent: null,\n currentRun: null,\n currentGeneration: 0,\n taskId: '',\n harnessToken: '',\n harnessRunnerId: '',\n harnessUrl: '',\n cwd: '',\n apiKey: '',\n pooled: false,\n modelId: '',\n fastMode: false,\n fastModeParam: null,\n mcpServers: {},\n initSettled: null,\n mode: undefined,\n};\n\nfunction send(msg: RunnerToDaemon): void {\n /**\n * `process.send` is only defined when this module is started via\n * `fork()` with an IPC channel. If it is undefined (e.g. the file is\n * accidentally executed directly), there is no way to reach the\n * parent — the runner is unusable; log to stderr and drop.\n */\n if (typeof process.send !== 'function') {\n process.stderr.write(\n `${JSON.stringify({ level: 'error', event: 'runner_no_ipc_channel', dropped: msg.kind })}\\n`\n );\n return;\n }\n process.send(msg);\n}\n\nfunction log(level: 'info' | 'warn' | 'error', message: string, data?: unknown): void {\n send({ kind: 'log', level, message, data });\n}\n\n/**\n * Runner-side throttle for `stream_activity` heartbeats. `onDelta` fires per\n * output chunk (text/thinking/shell-output deltas) — far too often to forward\n * each as IPC. Coalesce to at most one heartbeat per this interval. Kept well\n * below the daemon's stall window (180s) so the daemon reliably sees liveness\n * within it; the daemon applies its own coarser throttle on receipt.\n */\nconst STREAM_ACTIVITY_IPC_THROTTLE_MS = 2_000;\n/** Wall-clock of the last forwarded `stream_activity`. Reset per turn in `handlePushMessage`. */\nlet lastStreamActivitySentAt = 0;\n/**\n * Count of `tool-call-started` updates minus `tool-call-completed` updates for\n * the current turn. Used to emit `tool_execution` IPC edges only on the 0→1\n * (started) and 1→0 (settled) transitions — intermediate count changes produce\n * no IPC traffic. Reset at the start of every turn in `handlePushMessage`\n * and on pooled release (`handleClose`) so a stale count can never survive\n * into a different turn or task.\n */\nlet inFlightToolCount = 0;\n\n/**\n * Forward a throttled liveness heartbeat for the active generation. Called from\n * `onDelta` — ANY interaction update means the run is producing output, so the\n * daemon's stall watchdog should measure true silence rather than step cadence.\n * Drops stale-generation calls (the daemon would filter them anyway).\n */\nfunction maybeSendStreamActivity(generation: number): void {\n if (state.currentGeneration !== generation) return;\n const now = Date.now();\n if (now - lastStreamActivitySentAt < STREAM_ACTIVITY_IPC_THROTTLE_MS) return;\n lastStreamActivitySentAt = now;\n send({ kind: 'stream_activity', generation });\n}\n\n/**\n * Build the harness MCP entry. Uses the runner-scoped header\n * (`x-shipyard-runner-id`) when `harnessRunnerId` is set — that keeps the\n * entry byte-identical across the runner's whole life (init→reinit) so the\n * SDK executor cache key never changes on task switch. Falls back to the\n * legacy per-task header (`x-shipyard-task-id`) for non-pooled spawns.\n */\nfunction buildHarnessServerEntry(args: {\n harnessUrl: string;\n harnessToken: string;\n harnessRunnerId: string;\n taskId: string;\n}): {\n type: 'http';\n url: string;\n headers: Record<string, string>;\n} {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${args.harnessToken}`,\n };\n if (args.harnessRunnerId.length > 0) {\n headers['x-shipyard-runner-id'] = args.harnessRunnerId;\n } else {\n headers['x-shipyard-task-id'] = args.taskId;\n }\n return {\n type: 'http',\n url: args.harnessUrl,\n headers,\n };\n}\n\n/**\n * Compose the SDK `local` option. `settingSources: ['user', 'project']` is\n * parity with Claude/Codex — those runtimes auto-discover the user's installed\n * skills and the repo's project-level rules; leaving settingSources at the\n * default (`[]`) silently disabled Cursor's equivalent. User-authored markdown\n * on the user's own machine; same trust posture as the other agents.\n * Cross-runtime *portability* of those skills is tracked separately in #3955.\n *\n * `cwd: [cwd, skillsDir]` — `cwd[0]` is the repo workspace; `skillsDir` (cwd[1])\n * is Shipyard's task-scoped `~/.shipyard/cursor-skills/{taskId}` dir, which hosts\n * the permission-hook substrate (`.cursor/hooks.json` + hmac + allow). The SDK\n * resolves the WORKSPACE (rules/skills discovery) from `cwd[0]` ONLY\n * (`getExplicitLocalWorkspaceRef` → `n[0]`), so `cwd[1]` is NOT a prompt or\n * rules channel — the Shipyard prompt is delivered via the harness MCP server's\n * `initialize.instructions` instead. `cwd[1]` is retained because it is how the\n * SDK discovers the per-task hook substrate; dropping it would silently disable\n * Cursor permission gating.\n */\nfunction buildLocalOption(\n cwd: string,\n skillsDir: string\n): {\n cwd: string | string[];\n settingSources: SettingSource[];\n} {\n const settingSources: SettingSource[] = ['user', 'project'];\n return {\n ...(skillsDir ? { cwd: [cwd, skillsDir] } : { cwd }),\n settingSources,\n };\n}\n\n/**\n * Named subagent catalog as an SDK option. Omitted when the catalog is empty —\n * passing `agents: {}` would register an empty map and override any agents the\n * SDK might discover itself.\n */\nfunction buildAgentsOption(agents: RunnerAgents | undefined): Record<string, never> {\n return agents !== undefined && Object.keys(agents).length > 0\n ? // eslint-disable-next-line no-restricted-syntax -- AgentDefinition union (model field) requires boundary cast\n { agents: agents as never }\n : {};\n}\n\n/**\n * Acquire the Agent for a task. Priority order:\n * 1. `injectedAgentId` — cross-runtime structured handoff (Wave 7.5). The\n * daemon pre-wrote a fabricated conversation to SQLite; we MUST resume\n * that id or the conversation never surfaces. A same-id `Agent.create()`\n * would PRIMARY-KEY-conflict on the agents row.\n * 2. `resumeAgentId` — persisted from a prior `init_ok`. SDK reads the prior\n * conversation from its SQLite checkpoint store. If the store entry is\n * gone (user wiped ~/.cursor/, etc.) the resume throws; we catch and fall\n * through to `Agent.create` so the spawn succeeds without prior context.\n * 3. `Agent.create` — fresh agent. Default path for never-spawned tasks.\n *\n * Conversation history not preserved on path 3 is acceptable — Shipyard's\n * JSONL is the authoritative log; the SDK's checkpoint store is a latency /\n * context win, not the source of truth.\n */\nasync function acquireAgent(args: {\n apiKey: string;\n taskId: string;\n modelId: string;\n local: ReturnType<typeof buildLocalOption>;\n mcpServers: Record<string, unknown>;\n agentsOption: Record<string, never>;\n injectedAgentId?: string;\n resumeAgentId?: string;\n mode?: 'agent' | 'plan';\n}): Promise<SDKAgent> {\n const baseOptions = {\n apiKey: args.apiKey,\n model: { id: args.modelId },\n local: args.local,\n // eslint-disable-next-line no-restricted-syntax -- McpServerConfig union types require boundary cast\n mcpServers: args.mcpServers as never,\n ...args.agentsOption,\n ...(args.mode !== undefined ? { mode: args.mode } : {}),\n };\n async function createFreshAgent(): Promise<SDKAgent> {\n return Agent.create({ ...baseOptions, name: `shipyard-task-${args.taskId}` });\n }\n if (args.injectedAgentId !== undefined) {\n return Agent.resume(args.injectedAgentId, baseOptions);\n }\n if (args.resumeAgentId !== undefined) {\n try {\n const resumed = await Agent.resume(args.resumeAgentId, baseOptions);\n log('info', 'runner_resume_agent_succeeded', { agentId: args.resumeAgentId });\n return resumed;\n } catch (resumeErr) {\n log('warn', 'runner_resume_agent_failed_falling_back_to_create', {\n agentId: args.resumeAgentId,\n error: resumeErr instanceof Error ? resumeErr.message : String(resumeErr),\n });\n return createFreshAgent();\n }\n }\n return createFreshAgent();\n}\n\n/** Snapshot MCP status for init/reinit ok messages (Cursor has no live stream). */\nfunction buildMcpStatusSnapshot(mcpServers: Record<string, unknown>): Array<{\n name: string;\n status: string;\n}> {\n return Object.keys(mcpServers).map((name) => ({\n name,\n /**\n * Cursor local runtime does not expose a stable MCP-status stream during\n * init. Emit a non-empty snapshot so daemon-side init metadata never\n * races with an empty MCP list.\n */\n status: 'connected',\n }));\n}\n\nasync function handleInit(args: {\n taskId: string;\n cwd: string;\n apiKey: string;\n modelId: string;\n mcpServers: Record<string, unknown>;\n harnessUrl: string;\n harnessToken: string;\n harnessRunnerId: string;\n pooled: boolean;\n skillsDir: string;\n generation: number;\n injectedAgentId?: string;\n resumeAgentId?: string;\n fastMode: boolean;\n fastModeParam: { id: string; value: string } | null;\n agents?: RunnerAgents;\n mode?: 'agent' | 'plan';\n}): Promise<void> {\n state.currentGeneration = args.generation;\n state.taskId = args.taskId;\n state.harnessToken = args.harnessToken;\n state.harnessRunnerId = args.harnessRunnerId;\n state.harnessUrl = args.harnessUrl;\n state.cwd = args.cwd;\n state.apiKey = args.apiKey;\n state.pooled = args.pooled;\n state.modelId = args.modelId;\n state.fastMode = args.fastMode;\n state.fastModeParam = args.fastModeParam;\n state.mode = args.mode;\n try {\n /**\n * The daemon validates `mcpServers` shape before sending. We pass\n * through as-is; a malformed entry will surface as an\n * `Agent.create()` rejection rather than a runner crash.\n */\n const mcpServers: Record<string, unknown> = {\n ...args.mcpServers,\n [HARNESS_SERVER_NAME]: buildHarnessServerEntry({\n harnessUrl: args.harnessUrl,\n harnessToken: args.harnessToken,\n harnessRunnerId: args.harnessRunnerId,\n taskId: args.taskId,\n }),\n };\n state.mcpServers = mcpServers;\n const agent = await acquireAgent({\n apiKey: args.apiKey,\n taskId: args.taskId,\n modelId: args.modelId,\n local: buildLocalOption(args.cwd, args.skillsDir),\n mcpServers,\n agentsOption: buildAgentsOption(args.agents),\n ...(args.injectedAgentId !== undefined && { injectedAgentId: args.injectedAgentId }),\n ...(args.resumeAgentId !== undefined && { resumeAgentId: args.resumeAgentId }),\n ...(args.mode !== undefined && { mode: args.mode }),\n });\n state.agent = agent;\n send({\n kind: 'init_ok',\n sessionId: agent.agentId,\n tools: [],\n model: args.modelId,\n mcpStatus: buildMcpStatusSnapshot(mcpServers),\n });\n } catch (err) {\n await sendInitErrorFromAgentCreateFailure({\n apiKey: args.apiKey,\n modelId: args.modelId,\n err,\n send,\n });\n }\n}\n\n/**\n * Warm-pool reinit (C9). The runner was kept alive after its prior task\n * closed; the daemon is now claiming it for a NEW task. We cancel any active\n * run, dispose the prior Agent, update state, and create a fresh Agent on the\n * SAME warm process — so its `localExecutorCache` is reused (cache HIT on the\n * next send) because the harness entry uses the stable `harnessRunnerId`.\n *\n * FAIL CLOSED: on ANY throw we emit `reinit_error` and do NOT exit. The daemon\n * kills + respawns a fresh cold runner, so a broken warm process can never\n * serve the task — worst case is \"no speedup,\" never breakage or a cross-task\n * leak.\n */\nasync function handleReinit(args: {\n taskId: string;\n modelId: string;\n mcpServers: Record<string, unknown>;\n harnessRunnerId: string;\n harnessToken: string;\n skillsDir: string;\n generation: number;\n resumeAgentId?: string;\n fastMode: boolean;\n fastModeParam: { id: string; value: string } | null;\n agents?: RunnerAgents;\n mode?: 'agent' | 'plan';\n}): Promise<void> {\n try {\n /** Cancel any straggler run from the prior task before swapping Agents. */\n if (state.currentRun) {\n await state.currentRun.cancel().catch(() => {});\n state.currentRun = null;\n }\n /** Dispose the prior Agent; the warm executor cache stays in the process. */\n if (state.agent) {\n await state.agent[Symbol.asyncDispose]().catch(() => {});\n state.agent = null;\n }\n state.currentGeneration = args.generation;\n state.taskId = args.taskId;\n state.modelId = args.modelId;\n state.harnessToken = args.harnessToken;\n state.harnessRunnerId = args.harnessRunnerId;\n state.fastMode = args.fastMode;\n state.fastModeParam = args.fastModeParam;\n state.mode = args.mode;\n const mcpServers: Record<string, unknown> = {\n ...args.mcpServers,\n [HARNESS_SERVER_NAME]: buildHarnessServerEntry({\n harnessUrl: state.harnessUrl,\n harnessToken: args.harnessToken,\n harnessRunnerId: args.harnessRunnerId,\n taskId: args.taskId,\n }),\n };\n state.mcpServers = mcpServers;\n const agent = await acquireAgent({\n apiKey: state.apiKey,\n taskId: args.taskId,\n modelId: args.modelId,\n local: buildLocalOption(state.cwd, args.skillsDir),\n mcpServers,\n agentsOption: buildAgentsOption(args.agents),\n ...(args.resumeAgentId !== undefined && { resumeAgentId: args.resumeAgentId }),\n ...(args.mode !== undefined && { mode: args.mode }),\n });\n state.agent = agent;\n send({ kind: 'reinit_ok', sessionId: agent.agentId });\n } catch (err) {\n send({\n kind: 'reinit_error',\n error: err instanceof Error ? err.message : String(err),\n retryable: false,\n });\n }\n}\n\n/**\n * Parse a `turn-ended` update's `usage` block into `TurnUsage`, or null when\n * absent/invalid. As of @cursor/sdk 1.0.17 `turn-ended` is a first-class member\n * of the typed `InteractionUpdate` union with `usage?: { inputTokens,\n * outputTokens, cacheReadTokens, cacheWriteTokens }` (each a plain `z.number()`),\n * so we narrow via the discriminated `type` rather than widening to `unknown`.\n *\n * The integer/non-negative guard is a DOMAIN constraint, not structural: the\n * SDK schema's `z.number()` admits Infinity / fractional / negative, but our IPC\n * schema requires `int().nonnegative()`. A schema-invalid value would get the\n * whole `summary_completed` frame rejected at parse and wedge the compaction SM,\n * so we reject it here — preTokens is then omitted and the estimated-baseline\n * fallback applies. `Number.isInteger` also rejects NaN/Infinity/non-numbers.\n */\nfunction parseTurnEndedUsage(update: InteractionUpdate): TurnUsage | null {\n /*\n * `== null` (not `=== undefined`): the schema types `usage` as optional-not-null,\n * but this is a forked-child boundary parsing external SDK data — tolerate a\n * future `usage: null` rather than throwing on destructure and crashing the runner.\n */\n if (update.type !== 'turn-ended' || update.usage == null) return null;\n const { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens } = update.usage;\n if (\n Number.isInteger(inputTokens) &&\n inputTokens >= 0 &&\n Number.isInteger(outputTokens) &&\n outputTokens >= 0 &&\n Number.isInteger(cacheReadTokens) &&\n cacheReadTokens >= 0 &&\n Number.isInteger(cacheWriteTokens) &&\n cacheWriteTokens >= 0\n ) {\n return { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens };\n }\n return null;\n}\n\n/**\n * B2: translate a Cursor `onDelta` interaction-update into a runner→daemon IPC\n * signal. Cursor's native summarization emits `summary-started` /\n * `summary-completed` (both bare `{ type }` — no token counts), so the daemon\n * derives `compaction_completed.preTokens` from the last captured `turn-ended`\n * usage instead. Text/thinking/tool deltas are the public `Run.stream()` path's\n * responsibility and are ignored here. All update kinds below are members of the\n * typed `InteractionUpdate` union (@cursor/sdk 1.0.17), so each narrows by `type`.\n */\nfunction forwardSummaryUpdate(\n update: InteractionUpdate,\n generation: number,\n latestUsage: { ref: TurnUsage | null },\n tokenDeltaAccumulator: { count: number }\n): void {\n if (update.type === 'summary-started') {\n send({ kind: 'summary_started', generation });\n return;\n }\n if (update.type === 'summary-completed') {\n const raw = latestUsage.ref;\n /*\n * inputTokensRaw equivalent: input + cache reads + cache writes = total context fill,\n * the quantity the fill-bar renders. Absent when no turn-ended fired yet this turn.\n */\n const preTokens =\n raw !== null ? raw.inputTokens + raw.cacheReadTokens + raw.cacheWriteTokens : undefined;\n send({\n kind: 'summary_completed',\n generation,\n ...(preTokens !== undefined ? { preTokens } : {}),\n });\n return;\n }\n /**\n * `user-message-appended` fires once per `agent.send()` when the SDK has\n * durably written the user turn to its local SQLite checkpoint store. The\n * daemon logs the ack for durability triage; no state mutation needed yet.\n */\n if (update.type === 'user-message-appended') {\n send({ kind: 'user_message_persisted', generation, sessionId: update.userMessage.session_id });\n return;\n }\n /**\n * `token-delta` carries the SDK's running token count during streaming.\n * Accumulated as a SECOND signal — NOT a replacement for `turn-ended.usage`,\n * which carries the input/output/cache breakdown needed for pricing.\n */\n if (update.type === 'token-delta') {\n tokenDeltaAccumulator.count += update.tokens;\n return;\n }\n const usage = parseTurnEndedUsage(update);\n if (usage !== null) {\n latestUsage.ref = usage;\n log('info', 'cursor_turn_usage_received', { generation, ...usage });\n }\n}\n\nasync function streamRun(\n run: Run,\n generation: number,\n latestUsage: { ref: TurnUsage | null },\n tokenDeltaAccumulator: { count: number }\n): Promise<void> {\n try {\n for await (const event of run.stream()) {\n /**\n * If the generation rolled forward (interrupt + new push), stop\n * forwarding events from the stale run. The daemon will already\n * be ignoring them, but stopping here saves IPC traffic.\n */\n if (state.currentGeneration !== generation) {\n log('info', 'runner_stream_aborted_stale_generation', {\n generation,\n currentGeneration: state.currentGeneration,\n });\n return;\n }\n const message: SDKMessage = event;\n send({ kind: 'sdk_message', generation, payload: message });\n }\n const result = await run.wait();\n /**\n * `Run.createdAt` (run.d.ts:42) is the authoritative SDK-side run-start\n * timestamp. The daemon currently synthesizes one from `Date.now()` at\n * the IPC boundary, which can drift by the IPC + cold-build latency\n * (sometimes seconds). Forwarding the SDK's value gives accurate\n * per-run timing for the cost/latency dashboard.\n */\n const createdAt = run.createdAt;\n send({\n kind: 'run_complete',\n generation,\n result,\n ...(latestUsage.ref !== null ? { usage: latestUsage.ref } : {}),\n ...(createdAt !== undefined ? { createdAt } : {}),\n ...(tokenDeltaAccumulator.count > 0 ? { tokenDeltaTotal: tokenDeltaAccumulator.count } : {}),\n });\n } catch (err) {\n const serialized = serializeCursorRunnerError(err);\n send({\n kind: 'run_error',\n generation,\n ...serialized,\n });\n } finally {\n if (state.currentRun === run) {\n state.currentRun = null;\n }\n }\n}\n\n/**\n * Upper bound on how long `agent.send()` may take to return a `Run`. The SDK\n * does not resolve `send()` until it has flipped the run from QUEUED to RUNNING,\n * which it gates on an un-abortable model-validation round-trip to api2.cursor.sh\n * (`resolveLocalModelSelection` runs BEFORE `markRunStarting`). If that round-trip\n * wedges (TLS up, no response) the run sits in QUEUED until undici's 300s\n * headersTimeout — a silent 5-minute stall. `SendOptions` exposes no `signal`/\n * timeout, so we race the call and surface a fast, retryable error instead.\n */\nconst SEND_START_TIMEOUT_MS = 30_000;\n\n/**\n * `agent.send()` bounded by SEND_START_TIMEOUT_MS. On timeout, rejects with a\n * retryable error (duck-typed `isRetryable` is read by serializeCursorRunnerError)\n * so the daemon surfaces \"send a message to retry\" rather than a 5-min hang.\n */\nasync function sendWithStartTimeout(\n agent: SDKAgent,\n message: string | SDKUserMessage,\n sendOptions: SendOptions\n): Promise<Run> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n agent.send(message, sendOptions),\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n reject(\n Object.assign(\n new Error(\n `Cursor did not start the run within ${SEND_START_TIMEOUT_MS / 1000}s (model validation / handshake stalled). Send a message to try again.`\n ),\n { name: 'CursorSendStartTimeout', isRetryable: true }\n )\n );\n }, SEND_START_TIMEOUT_MS);\n }),\n ]);\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n}\n\nasync function handlePushMessage(args: {\n generation: number;\n content: unknown[];\n skillBodies?: CursorResolvedSkillBody[];\n forceLocalRun: boolean;\n}): Promise<void> {\n /**\n * A push_message can arrive while Agent.create is still in flight: the daemon\n * sends `init` then the first turn back-to-back, and the runner's IPC dispatch\n * is NOT serialized (process.on('message') fires `void dispatch(...)` per\n * message). Without this wait, the first turn hits the guard below before\n * `Agent.create` resolves and fails with \"cursor agent not initialized.\"\n * `initSettled` resolves when init/reinit finishes (success or a sent\n * init_error — it never rejects; the catch is belt-and-suspenders).\n */\n if (!state.agent && state.initSettled) {\n await state.initSettled.catch(() => undefined);\n }\n const agent = state.agent;\n if (!agent) {\n send({\n kind: 'run_error',\n generation: args.generation,\n error: 'cursor agent not initialized',\n errorName: 'ConfigurationError',\n isRetryable: false,\n });\n return;\n }\n state.currentGeneration = args.generation;\n /*\n * New turn: clear the heartbeat throttle so the first streamed delta of this\n * turn forwards a stream_activity immediately instead of being coalesced\n * against the previous turn's last heartbeat. Also reset the in-flight tool\n * counter so tool-execution liveness edges start fresh from 0 each turn.\n */\n lastStreamActivitySentAt = 0;\n inFlightToolCount = 0;\n /**\n * `args.content` arrives as `unknown[]` over IPC (the wire schema uses\n * `z.array(z.unknown())` for forward-compat — see comment in\n * cursor-runner-protocol.ts). Validate at the boundary via Zod rather\n * than cast: lint:extra forbids type assertions outside `as const`/`as never`,\n * and Zod gives us a runtime guarantee that matches the type.\n */\n const parsedContent = ContentBlockSchema.array().safeParse(args.content);\n if (!parsedContent.success) {\n log('warn', 'runner_push_message_invalid_content', {\n errorCount: parsedContent.error.issues.length,\n firstIssue: parsedContent.error.issues[0],\n });\n return;\n }\n const userInput: CursorUserInput = buildCursorUserPrompt(\n parsedContent.data,\n (entry) => log(entry.event.includes('error') ? 'warn' : 'info', entry.event, entry),\n { skillBodies: args.skillBodies }\n );\n if (userInput.text === '' && userInput.images.length === 0) {\n log('warn', 'runner_push_message_empty_after_build');\n return;\n }\n if (userInput.images.length > 0) {\n log('info', 'runner_push_message_with_images', {\n count: userInput.images.length,\n totalBytes: userInput.images.reduce((acc, img) => {\n const data = 'data' in img ? img.data : '';\n return acc + Math.floor((data.length * 3) / 4);\n }, 0),\n });\n }\n try {\n const message: string | SDKUserMessage =\n userInput.images.length > 0\n ? { text: userInput.text, images: userInput.images }\n : userInput.text;\n /**\n * Two send-option layers combine here:\n *\n * B2 — subscribe to the interaction-update stream so Cursor's native\n * server-managed summarization surfaces as `summary_started` /\n * `summary_completed` IPC messages. These updates flow ONLY through\n * `onDelta` — the public `Run.stream()` `SDKMessage` union has no summary\n * variant (verified against @cursor/sdk@1.0.13). Without this the daemon's\n * estimated context fill-bar never resets after Cursor compacts. `onDelta`\n * is unconditional.\n *\n * Fast Mode — attaches `SendOptions.model.params = [{id, value}]`, but only\n * when both knobs line up (the runner must NOT send an empty `params: []`).\n * Busy recovery may also attach `local.force`, but only when the daemon has\n * generation-guarded the retry.\n */\n /**\n * Mutable ref shared between the `onDelta` closure and `streamRun` so\n * the latest `turn-ended` usage can be forwarded on `run_complete`\n * without a module-level variable (each push_message gets its own ref,\n * avoiding any cross-turn contamination).\n */\n const latestUsage: { ref: TurnUsage | null } = { ref: null };\n const tokenDeltaAccumulator = { count: 0 };\n /**\n * `idempotencyKey` prevents the SDK from creating a duplicate run if the\n * runner crashes mid-`agent.send()` and we re-attempt with the same\n * generation after `Agent.resume()`. `${taskId}-${generation}` is unique\n * per turn (generation is bumped every turn or interrupt), so a clean\n * retry under the same key collapses server-side. Without this, an\n * Agent.resume-then-resend window can produce a duplicate run that the\n * SDK queues behind the original and we see two responses for one turn.\n */\n const sendOptions: SendOptions = {\n idempotencyKey: `${state.taskId}-${args.generation}`,\n /**\n * Per-RUN MCP set (live visibility). `@cursor/sdk@1.0.17` resolves\n * `opts.mcpServers ?? this.options.mcpServers` per send and forwards it to\n * the local `RunExecutor` as `mcpServersOverride`, so the set applied on\n * THIS turn reflects the latest `set_mcp_servers` IPC. Boundary cast: same\n * `McpServerConfig`-union reason as `Agent.create({ mcpServers: ... })`.\n */\n // eslint-disable-next-line no-restricted-syntax -- McpServerConfig union types require boundary cast\n mcpServers: state.mcpServers as never,\n onDelta: ({ update }) => {\n /*\n * Any interaction update means the run is producing output mid-step, so\n * feed the stall watchdog — it must measure true silence, not the\n * cadence of completed steps.\n */\n maybeSendStreamActivity(args.generation);\n /*\n * Track tool-call-started / tool-call-completed boundaries so the\n * daemon can arm an extended stall window while a tool is in flight.\n * Only emit IPC on the 0→1 (started) and 1→0 (settled) transitions —\n * intermediate count changes (2nd parallel tool start, etc.) produce\n * no IPC traffic. Stale-generation guard mirrors maybeSendStreamActivity.\n */\n if (state.currentGeneration === args.generation) {\n const edge = decideToolExecutionEdge(inFlightToolCount, update.type);\n inFlightToolCount = edge.count;\n if (edge.edge !== null) {\n send({ kind: 'tool_execution', generation: args.generation, phase: edge.edge });\n }\n }\n forwardSummaryUpdate(update, args.generation, latestUsage, tokenDeltaAccumulator);\n },\n onStep: ({ step }) => {\n /*\n * Guard against a stale generation completing after the daemon\n * incremented #generation for a new run (same pattern as streamRun's\n * stale-gen guard).\n */\n if (state.currentGeneration !== args.generation) return;\n send({ kind: 'step_progress', generation: args.generation });\n /*\n * Capture the native plan here rather than accumulating prose at\n * turn-complete (the codex pattern): a createPlan toolCall carries its\n * complete `args.plan` atomically, so there's no half-formed-content\n * risk that would need a turn-completion gate. No mode gate needed —\n * the SDK only invokes createPlan under `mode:'plan'`. The subprocess\n * drops empty plans before emitting plan_content_ready.\n */\n if (step.type === 'toolCall' && step.message.type === 'createPlan') {\n send({\n kind: 'plan_proposed',\n generation: args.generation,\n plan: step.message.args.plan,\n });\n }\n },\n ...(state.fastMode && state.fastModeParam !== null\n ? { model: { id: state.modelId, params: [state.fastModeParam] } }\n : {}),\n ...(args.forceLocalRun ? { local: { force: true } } : {}),\n };\n const run = await sendWithStartTimeout(agent, message, sendOptions);\n state.currentRun = run;\n await streamRun(run, args.generation, latestUsage, tokenDeltaAccumulator);\n } catch (err) {\n const serialized = serializeCursorRunnerError(err);\n send({\n kind: 'run_error',\n generation: args.generation,\n ...serialized,\n });\n }\n}\n\nasync function handleInterrupt(generation: number): Promise<void> {\n if (!state.currentRun) {\n log('info', 'runner_interrupt_no_active_run', { generation });\n return;\n }\n try {\n await state.currentRun.cancel();\n } catch (err) {\n log('warn', 'runner_interrupt_failed', {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n}\n\nasync function handleClose(): Promise<void> {\n try {\n if (state.currentRun) {\n await state.currentRun.cancel();\n }\n } catch {\n /**\n * Closing — already on the exit path, swallow.\n */\n }\n try {\n if (state.agent) {\n await state.agent[Symbol.asyncDispose]();\n }\n } catch {\n /** swallow — same reason */\n }\n state.agent = null;\n state.currentRun = null;\n /**\n * Pool-managed runner: keep the process (and its warm `localExecutorCache`)\n * alive. Reset to a quiescent state and ack `pooled_idle` so the daemon can\n * reuse us for the next matching task via `reinit`. The Agent is disposed\n * above; only the heavyweight executor cache survives.\n */\n if (state.pooled) {\n state.taskId = '';\n state.fastMode = false;\n state.fastModeParam = null;\n state.mode = undefined;\n /**\n * Defensive: a turn whose final tool never emitted `tool-call-completed`\n * (killed mid-tool) must not leak its in-flight count into the next\n * pooled task's first turn.\n */\n inFlightToolCount = 0;\n send({ kind: 'pooled_idle' });\n return;\n }\n process.exit(0);\n}\n\n/**\n * Translate an `init` IPC message into `handleInit` args. Pure — extracted from\n * `dispatch` so the conditional-spread branches for optional fields don't load\n * the switch's cognitive complexity past budget.\n */\nfunction buildHandleInitArgs(\n msg: Extract<DaemonToRunner, { kind: 'init' }>\n): Parameters<typeof handleInit>[0] {\n return {\n taskId: msg.taskId,\n cwd: msg.cwd,\n apiKey: msg.apiKey,\n modelId: msg.modelId,\n mcpServers: msg.mcpServers,\n harnessUrl: msg.harnessUrl,\n harnessToken: msg.harnessToken,\n harnessRunnerId: msg.harnessRunnerId ?? '',\n pooled: msg.pooled ?? false,\n skillsDir: msg.skillsDir,\n generation: msg.generation,\n ...(msg.injectedAgentId !== undefined && { injectedAgentId: msg.injectedAgentId }),\n ...(msg.resumeAgentId !== undefined && { resumeAgentId: msg.resumeAgentId }),\n fastMode: msg.fastMode ?? false,\n fastModeParam: msg.fastModeParam ?? null,\n ...(msg.agents !== undefined && { agents: msg.agents }),\n ...(msg.mode !== undefined && { mode: msg.mode }),\n };\n}\n\n/**\n * Translate a `reinit` IPC message into `handleReinit` args. Pure — extracted\n * from `dispatch` for the same reason as `buildHandleInitArgs`: the\n * conditional-spread branches for optional fields would otherwise load the\n * switch's cognitive complexity past budget.\n */\nfunction buildHandleReinitArgs(\n msg: Extract<DaemonToRunner, { kind: 'reinit' }>\n): Parameters<typeof handleReinit>[0] {\n return {\n taskId: msg.taskId,\n modelId: msg.modelId,\n mcpServers: msg.mcpServers,\n harnessRunnerId: msg.harnessRunnerId,\n harnessToken: msg.harnessToken,\n skillsDir: msg.skillsDir,\n generation: msg.generation,\n ...(msg.resumeAgentId !== undefined && { resumeAgentId: msg.resumeAgentId }),\n fastMode: msg.fastMode ?? false,\n fastModeParam: msg.fastModeParam ?? null,\n ...(msg.agents !== undefined && { agents: msg.agents }),\n ...(msg.mode !== undefined && { mode: msg.mode }),\n };\n}\n\nasync function dispatch(raw: unknown): Promise<void> {\n const msg = parseDaemonToRunner(raw);\n if (msg === null) {\n log('warn', 'runner_invalid_ipc_message', { raw });\n return;\n }\n switch (msg.kind) {\n case 'init':\n state.initSettled = handleInit(buildHandleInitArgs(msg));\n return;\n case 'reinit':\n state.initSettled = handleReinit(buildHandleReinitArgs(msg));\n return;\n case 'set_pooled':\n /**\n * Flip the pooled flag so the next `close` keeps (pooled=true) or exits\n * (pooled=false) the process. Per-state, not per-turn.\n */\n state.currentGeneration = msg.generation;\n state.pooled = msg.pooled;\n log('info', 'runner_set_pooled_recorded', { pooled: msg.pooled });\n return;\n case 'push_message':\n await handlePushMessage({\n generation: msg.generation,\n content: msg.content,\n skillBodies: msg.skillBodies,\n forceLocalRun: msg.forceLocalRun ?? false,\n });\n return;\n case 'interrupt':\n await handleInterrupt(msg.generation);\n return;\n case 'set_model':\n /**\n * `@cursor/sdk` accepts `model` via `SendOptions` on each\n * `send()` call; there is no `agent.setModel()`. The runner\n * stores the new id and applies it on the next push. (Wired\n * in S2b — for S2a we just acknowledge.)\n */\n state.currentGeneration = msg.generation;\n log('info', 'runner_set_model_recorded', { modelId: msg.modelId });\n return;\n case 'set_mcp_servers':\n /**\n * Live per-send MCP visibility. `@cursor/sdk@1.0.17` has no\n * `agent.setMcpServers` — the MCP set is a per-RUN parameter applied via\n * `SendOptions.mcpServers` on every `agent.send()`. So we store the new\n * set (re-composing the harness MCP entry, which the daemon's\n * `resolveServersForTask` payload omits) and the NEXT `push_message`\n * carries it. No agent recreate, no restart banner — the Cursor\n * subprocess no longer raises `mcp_mid_thread_reload_needed`.\n */\n state.currentGeneration = msg.generation;\n state.mcpServers = {\n ...msg.servers,\n [HARNESS_SERVER_NAME]: buildHarnessServerEntry({\n harnessUrl: state.harnessUrl,\n harnessToken: state.harnessToken,\n harnessRunnerId: state.harnessRunnerId,\n taskId: state.taskId,\n }),\n };\n log('info', 'runner_set_mcp_servers_applied_live', {\n count: Object.keys(state.mcpServers).length,\n });\n return;\n case 'set_harness_task_id':\n state.taskId = msg.taskId;\n state.harnessToken = msg.token;\n state.currentGeneration = msg.generation;\n log('info', 'runner_harness_task_id_recorded', { taskId: msg.taskId });\n return;\n case 'set_permission_mode':\n /**\n * R1 plan mode: the daemon rewrote the mode file and hooks.json before\n * sending this. The runner records the generation and logs for triage.\n * No action needed — the next hook invocation reads the new mode file.\n */\n state.currentGeneration = msg.generation;\n log('info', 'runner_set_permission_mode_recorded', { mode: msg.mode });\n return;\n case 'set_fast_mode':\n /**\n * Fast Mode is per-state, not per-turn — record the new flag without\n * bumping the generation. The next `push_message` will read\n * `state.fastMode` when building `SendOptions`.\n */\n state.currentGeneration = msg.generation;\n state.fastMode = msg.fastMode;\n log('info', 'runner_set_fast_mode_recorded', { fastMode: msg.fastMode });\n return;\n case 'close':\n await handleClose();\n return;\n default: {\n /**\n * Compile-time exhaustiveness — adding a new variant to the\n * protocol forces this switch to be updated.\n */\n const _exhaustive: never = msg;\n log('error', 'runner_unhandled_message_kind', {\n msg: _exhaustive,\n });\n return;\n }\n }\n}\n\nprocess.on('message', (raw) => {\n /**\n * `dispatch` is async but the IPC handler is sync. Swallow any\n * promise rejection that escapes dispatch's own try/catch — those\n * are programmer errors, and crashing the runner over them just\n * trades a logged warning for a process exit + supervisor respawn\n * cycle.\n */\n void dispatch(raw).catch((err: unknown) => {\n log('error', 'runner_dispatch_unhandled_rejection', {\n error: err instanceof Error ? err.message : String(err),\n });\n });\n});\n\nprocess.on('disconnect', () => {\n /**\n * Parent went away (daemon crash, fork channel torn down). Exit\n * with a non-zero code so the supervisor records the abnormal\n * termination.\n */\n process.exit(1);\n});\n\n/**\n * Crash-on-uncaught: any uncaught error or unhandled rejection in\n * native code (sqlite, FFI) or SDK internals SHOULD bring this child\n * down — the daemon-side supervisor records the exit and the session\n * FSM tears down. Silent recovery here would mask real bugs.\n */\nprocess.on('uncaughtException', (err) => {\n process.stderr.write(\n `${JSON.stringify({\n level: 'error',\n event: 'runner_uncaught_exception',\n error: err.message,\n stack: err.stack,\n })}\\n`\n );\n process.exit(2);\n});\n\nprocess.on('unhandledRejection', (reason) => {\n process.stderr.write(\n `${JSON.stringify({\n level: 'error',\n event: 'runner_unhandled_rejection',\n reason: reason instanceof Error ? reason.message : String(reason),\n })}\\n`\n );\n process.exit(3);\n});\n","import { existsSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { basename, dirname, join } from 'node:path';\n\nexport interface CursorRipgrepDeps {\n env?: NodeJS.ProcessEnv;\n resolve?: (id: string) => string;\n exists?: (path: string) => boolean;\n platform?: NodeJS.Platform;\n arch?: string;\n}\n\nexport type CursorRipgrepConfigureResult =\n | { status: 'already-configured'; path: string }\n | { status: 'configured'; path: string }\n | { status: 'missing'; path: null }\n | { status: 'resolve-error'; path: null; error: unknown };\n\n/**\n * Point @cursor/sdk at the ripgrep binary bundled in its platform package.\n * The SDK currently has no public configureRipgrepPath export, so this is the\n * narrow chokepoint for the documented env override it already honors.\n */\nexport function configureCursorRipgrepPath(\n deps: CursorRipgrepDeps = {}\n): CursorRipgrepConfigureResult {\n const env = deps.env ?? process.env;\n if (env.CURSOR_RIPGREP_PATH) {\n return { status: 'already-configured', path: env.CURSOR_RIPGREP_PATH };\n }\n\n try {\n const rg = resolveCursorBundledRipgrepPath(deps);\n if (rg === null) return { status: 'missing', path: null };\n env.CURSOR_RIPGREP_PATH = rg;\n return { status: 'configured', path: rg };\n } catch (error) {\n return { status: 'resolve-error', path: null, error };\n }\n}\n\nexport function resolveCursorBundledRipgrepPath(deps: CursorRipgrepDeps = {}): string | null {\n const resolve = deps.resolve ?? createRequire(import.meta.url).resolve;\n const exists = deps.exists ?? existsSync;\n const platform = deps.platform ?? process.platform;\n const arch = deps.arch ?? process.arch;\n\n const sdkMain = resolve('@cursor/sdk');\n let sdkRoot = dirname(sdkMain);\n while (sdkRoot !== dirname(sdkRoot) && basename(sdkRoot) !== 'sdk') {\n sdkRoot = dirname(sdkRoot);\n }\n if (basename(sdkRoot) !== 'sdk') return null;\n\n const bin = platform === 'win32' ? 'rg.exe' : 'rg';\n const rg = join(sdkRoot, '..', `sdk-${platform}-${arch}`, 'bin', bin);\n return exists(rg) ? rg : null;\n}\n","/**\n * Imperative shell for Cursor `init_error` reporting (FC/IS pair with the pure\n * `cursor-init-error-formatter.ts`).\n *\n * On `Agent.create()` failure this probes `Cursor.models.list()` (5 s cap),\n * feeds the result to the pure `formatCursorInitError`, and sends the\n * `init_error` IPC message via the injected `send` callback.\n *\n * This module — not the formatter — is the sole owner of the `@cursor/sdk`\n * import (`Cursor.models.list()`). Keeping it here means the pure formatter (and\n * its test) can load without booting the Cursor SDK and its transitive `sqlite3`\n * native binding.\n *\n * The Cursor SDK delegates model-id resolution to the backend, so an unknown\n * `composer-X.Y` id surfaces as an opaque `ModelNotFound` (or similar) server\n * error. Shipyard's catalog (`cursor-model-catalog.ts`) hardcodes `composer-2.5`\n * based on Cursor's docs; if Cursor renames the model server-side, every spawn\n * fails and our only signal is the opaque SDK message — see PR history in #3680,\n * #3744, #3752, #3758, #3787.\n */\n\nimport { Cursor } from '@cursor/sdk';\n\nimport { formatCursorInitError, type ModelInfo } from './cursor-init-error-formatter.js';\nimport { serializeCursorRunnerError } from './cursor-runner-error.js';\nimport type { RunnerToDaemon } from './cursor-runner-protocol.js';\n\nconst INIT_MODEL_LIST_TIMEOUT_MS = 5000;\n\nexport async function sendInitErrorFromAgentCreateFailure(args: {\n apiKey: string;\n modelId: string;\n err: unknown;\n send: (msg: RunnerToDaemon) => void;\n}): Promise<void> {\n const originalError = args.err instanceof Error ? args.err.message : String(args.err);\n /**\n * On Agent.create failure, probe Cursor.models.list() once to capture\n * the authoritative {id, aliases} set. If the requested model is in\n * that set, the failure is unrelated to model resolution; if not, the\n * id is stale and the log line names the replacement immediately.\n *\n * Best-effort: the list call may itself fail (offline, auth, rate-\n * limited). The formatter handles a null `availableModels` by falling\n * back to the original error verbatim — no information loss vs the\n * pre-Item-1 behavior.\n */\n let availableModels: ModelInfo[] | null = null;\n let listError: string | undefined;\n /**\n * @cursor/sdk@1.0.13's CursorRequestOptions has no AbortSignal or timeout\n * field — a hung backend would block init_error indefinitely and wedge\n * the session FSM in \"initializing\". 5s cap restores the pre-Item-1\n * upper bound (the original code sent init_error immediately) at the\n * cost of degrading the diagnostic when Cursor's API is slow.\n */\n let timeoutHandle: ReturnType<typeof setTimeout> | undefined;\n try {\n const listPromise = Cursor.models.list({ apiKey: args.apiKey });\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutHandle = setTimeout(\n () => reject(new Error(`timed out after ${INIT_MODEL_LIST_TIMEOUT_MS}ms`)),\n INIT_MODEL_LIST_TIMEOUT_MS\n );\n });\n const models = await Promise.race([listPromise, timeoutPromise]);\n availableModels = models.map((m) => ({ id: m.id, aliases: m.aliases }));\n } catch (listErr) {\n listError = listErr instanceof Error ? listErr.message : String(listErr);\n } finally {\n if (timeoutHandle) clearTimeout(timeoutHandle);\n }\n const serialized = serializeCursorRunnerError(args.err);\n args.send({\n kind: 'init_error',\n error: formatCursorInitError({\n requestedModelId: args.modelId,\n originalError,\n availableModels,\n listError,\n }),\n ...(serialized.errorName !== undefined ? { errorName: serialized.errorName } : {}),\n ...(serialized.errorCode !== undefined ? { errorCode: serialized.errorCode } : {}),\n ...(serialized.statusCode !== undefined ? { statusCode: serialized.statusCode } : {}),\n retryable: serialized.isRetryable,\n });\n}\n","/**\n * Pure formatter for Cursor `init_error` diagnostic messages.\n *\n * Given a requested model id, the original SDK error, and the result of\n * `Cursor.models.list()` (as plain data), `formatCursorInitError` builds the\n * human-readable diagnostic string. No I/O, no side effects, and — deliberately\n * — NO `@cursor/sdk` import: the impure probe that actually calls\n * `Cursor.models.list()` lives in `cursor-init-error-reporter.ts`. Keeping this\n * module SDK-free means it (and its test) load without booting the Cursor SDK\n * and its transitive `sqlite3` native binding.\n *\n * The Cursor SDK delegates model-id resolution to the backend, so an unknown\n * `composer-X.Y` id surfaces as an opaque `ModelNotFound` (or similar) server\n * error. Shipyard's catalog (`cursor-model-catalog.ts`) hardcodes `composer-2.5`\n * based on Cursor's docs; if Cursor renames the model server-side, every spawn\n * fails and our only signal is the opaque SDK message — see PR history in #3680,\n * #3744, #3752, #3758, #3787.\n */\n\nexport interface ModelInfo {\n id: string;\n aliases?: readonly string[];\n}\n\nexport interface CursorInitErrorInput {\n /** The model id Shipyard sent to `Agent.create()`. */\n requestedModelId: string;\n /** The SDK's original failure message (preserved verbatim at the end). */\n originalError: string;\n /**\n * Result of `Cursor.models.list()`. `null` means the list call itself\n * failed (offline, auth invalid, rate-limited) — formatter falls back\n * to the original error without speculating about available models.\n */\n availableModels: readonly ModelInfo[] | null;\n /** Optional: the error string from the failed list call, for triage. */\n listError?: string;\n}\n\nexport function formatCursorInitError(input: CursorInitErrorInput): string {\n const { requestedModelId, originalError, availableModels, listError } = input;\n if (availableModels === null) {\n const listPart = listError\n ? ` (Cursor.models.list() also failed: ${listError})`\n : ' (Cursor.models.list() unavailable)';\n return `Agent.create failed for model '${requestedModelId}'. ${originalError}${listPart}`;\n }\n if (availableModels.length === 0) {\n return `Agent.create failed for model '${requestedModelId}'. Cursor.models.list() returned no models. ${originalError}`;\n }\n const requested = requestedModelId.toLowerCase();\n const exactMatch = availableModels.find(\n (m) =>\n m.id.toLowerCase() === requested ||\n (m.aliases ?? []).some((a) => a.toLowerCase() === requested)\n );\n const idsList = availableModels.map((m) => m.id).join(', ');\n const aliasParts = availableModels\n .filter((m) => (m.aliases?.length ?? 0) > 0)\n .map((m) => `${m.id}=[${(m.aliases ?? []).join('|')}]`);\n const aliasesList = aliasParts.length > 0 ? aliasParts.join(', ') : '(none)';\n const matchPart = exactMatch\n ? ` The id IS in the available set (${exactMatch.id}) — failure is unrelated to model resolution.`\n : ` The id is NOT in the available set — likely renamed or deprecated server-side.`;\n return `Agent.create failed for model '${requestedModelId}'.${matchPart} Available ids: ${idsList}. Aliases: ${aliasesList}. Original: ${originalError}`;\n}\n","/**\n * Serialize thrown values from `@cursor/sdk` into the wire shape the daemon\n * expects on `run_error` / `init_error` IPC messages.\n *\n * Pure FC/IS helper — no I/O. Duck-types SDK error fields because errors\n * arrive as live class instances inside the runner process.\n */\n\nexport interface CursorRunnerErrorWire {\n error: string;\n errorName?: string;\n errorCode?: string;\n statusCode?: number;\n isRetryable: boolean;\n}\n\nfunction readStringField(rec: Record<string, unknown>, key: string): string | undefined {\n const value = rec[key];\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction readNumberField(rec: Record<string, unknown>, key: string): number | undefined {\n const value = rec[key];\n return typeof value === 'number' ? value : undefined;\n}\n\nfunction readBooleanField(rec: Record<string, unknown>, key: string): boolean | undefined {\n const value = rec[key];\n return typeof value === 'boolean' ? value : undefined;\n}\n\n/**\n * Extract message, SDK error `name`, `code`, `status`, and `isRetryable`\n * from a thrown value for IPC transport to the daemon-side classifier.\n */\nexport function serializeCursorRunnerError(err: unknown): CursorRunnerErrorWire {\n if (typeof err === 'object' && err !== null) {\n // eslint-disable-next-line no-restricted-syntax -- unknown thrown value narrowed by typeof; Record probe for duck-typed SDK errors\n const rec = err as Record<string, unknown>;\n const message =\n typeof rec.message === 'string' && rec.message.length > 0 ? rec.message : String(err);\n const wire: CursorRunnerErrorWire = {\n error: message,\n isRetryable: readBooleanField(rec, 'isRetryable') ?? false,\n };\n const errorName = readStringField(rec, 'name');\n const errorCode = readStringField(rec, 'code');\n const statusCode = readNumberField(rec, 'status');\n if (errorName !== undefined) wire.errorName = errorName;\n if (errorCode !== undefined) wire.errorCode = errorCode;\n if (statusCode !== undefined) wire.statusCode = statusCode;\n return wire;\n }\n return { error: String(err), isRetryable: false };\n}\n","/**\n * cursor-tool-execution-edge — pure helper for tracking in-flight Cursor\n * tool calls and emitting IPC liveness edges.\n *\n * The @cursor/sdk's `InteractionUpdate` union includes `'tool-call-started'`\n * and `'tool-call-completed'` variants in the `onDelta` callback. Tools like\n * `git commit` (whose pre-commit hooks run `pnpm check` for 3–10 min) emit\n * no `stream_activity` deltas during execution — the SDK is truly silent\n * while the shell/tool subprocess runs. Without this edge the stall watchdog\n * (180s mid-run) false-kills a healthy runner executing a long tool.\n *\n * Solution: track in-flight count; emit an IPC `tool_execution` message only\n * on the 0→1 (started) and 1→0 (settled) transitions so the daemon can arm\n * an extended per-tool stall window on `started` and revert on `settled`.\n *\n * No I/O, no side effects. All exports are deterministic pure functions.\n */\n\nexport interface ToolExecutionEdgeResult {\n /** Updated in-flight tool count after applying the update type. */\n count: number;\n /**\n * Non-null only on a 0→1 (`'started'`) or 1→0 (`'settled'`) transition.\n * Null for any intermediate count change (1→2, 3→2, etc.) — only the\n * boundary edges matter for the daemon's window-switching decision.\n */\n edge: 'started' | 'settled' | null;\n}\n\n/**\n * Given the current in-flight tool count and an `InteractionUpdate` type\n * string, return the new count and (if a liveness edge occurred) the edge\n * direction.\n *\n * - `'tool-call-started'` increments; edge is `'started'` only on 0→1.\n * - `'tool-call-completed'` decrements (clamped at 0); edge is `'settled'`\n * only on 1→0.\n * - Any other type is a no-op: count unchanged, edge null.\n */\nexport function decideToolExecutionEdge(\n inFlightCount: number,\n updateType: string\n): ToolExecutionEdgeResult {\n if (updateType === 'tool-call-started') {\n const newCount = inFlightCount + 1;\n return { count: newCount, edge: newCount === 1 ? 'started' : null };\n }\n if (updateType === 'tool-call-completed') {\n const newCount = Math.max(0, inFlightCount - 1);\n return { count: newCount, edge: inFlightCount === 1 ? 'settled' : null };\n }\n return { count: inFlightCount, edge: null };\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getDaemonVersion
4
- } from "./chunk-RP4DQQXY.js";
4
+ } from "./chunk-RM4WIKPZ.js";
5
5
  import {
6
6
  enableNativeCrashReports
7
7
  } from "./chunk-TSJOSKN3.js";
@@ -71,7 +71,7 @@ async function main() {
71
71
  await loadAuthFromConfig(env);
72
72
  crumb("post-auth-load");
73
73
  crumb("pre-serve-import");
74
- const { serve } = await import("./serve-PVAHED3I.js");
74
+ const { serve } = await import("./serve-NIZWPABR.js");
75
75
  crumb("post-serve-import");
76
76
  const portQueue = [];
77
77
  let acceptor = null;
package/dist/index.js CHANGED
@@ -25,6 +25,11 @@ import { parseArgs } from "util";
25
25
  var TAGLINE_H1 = "Ship together with AI";
26
26
  var TAGLINE_H2 = "Ship fast without shipping blind";
27
27
  var OG_IMAGE_ALT = `Shipyard \u2014 ${TAGLINE_H1}. ${TAGLINE_H2}.`;
28
+ var FRONT_DOOR_URL = "https://get.shipyard.computer";
29
+ var OG_IMAGE_URL = `${FRONT_DOOR_URL}/og-image.png`;
30
+ var FAVICON_ICO_URL = `${FRONT_DOOR_URL}/favicon.ico`;
31
+ var FAVICON_SVG_URL = `${FRONT_DOOR_URL}/icon.svg`;
32
+ var INSTALL_CMD = `curl -fsSL ${FRONT_DOOR_URL}/install.sh | bash`;
28
33
 
29
34
  // src/index.ts
30
35
  Error.stackTraceLimit = 50;
@@ -124,7 +129,7 @@ async function handleSubcommand() {
124
129
  return true;
125
130
  }
126
131
  if (subcommand === "start") {
127
- const { startCommand } = await import("./start-STKDMHUG.js");
132
+ const { startCommand } = await import("./start-FCLXCIBQ.js");
128
133
  await startCommand();
129
134
  return true;
130
135
  }
@@ -146,7 +151,7 @@ async function main() {
146
151
  const args = parseCliArgs();
147
152
  if (args.serve) {
148
153
  await loadAuthFromConfig(env);
149
- const { serve } = await import("./serve-PVAHED3I.js");
154
+ const { serve } = await import("./serve-NIZWPABR.js");
150
155
  return serve({ isDev: env.SHIPYARD_DEV });
151
156
  }
152
157
  logger.error("Use `shipyard start` to run the daemon. Use --help for usage.");
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../../../packages/brand/src/index.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { parseArgs } from 'node:util';\nimport { TAGLINE_H1, TAGLINE_H2 } from '@shipyard/brand';\nimport { enableNativeCrashReports } from './services/bootstrap/native-crash-report.js';\nimport { type Env, validateEnv } from './shared/env.js';\nimport { logger } from './shared/logger.js';\nimport { installWasmPanicBuffer } from './shared/wasm-panic-buffer.js';\n\n/**\n * Node defaults to `Error.stackTraceLimit = 10`. Loro wasm panics produce\n * exactly 10-frame wasm chains, which means any JS frames *above* the wasm\n * boundary are silently truncated and never reach `panic-watch.log`.\n * Raising this lets the JS-stack capture (issue #2354) actually see\n * caller-side frames so we can distinguish JS-triggered re-entry from\n * pure-wasm cascade poison.\n */\nError.stackTraceLimit = 50;\n\n/**\n * Install the `console.error` ring buffer BEFORE any `loro-crdt` import\n * resolves. The vendored Loro fork has `console_error_panic_hook` already\n * installed in Rust, which writes the actual panic site (file:line) to\n * `console.error` before the wasm trap fires. Without this Node-side\n * capture, that message is lost and we are stuck guessing among the\n * multiple Rust panic classes that all surface as bare `unreachable`\n * (Invariant #14). The dynamic `await import('./services/serve.js')`\n * below is the first thing that pulls in `@loro-extended/repo` which in\n * turn loads the wasm module — so installing here is well-ordered.\n */\ninstallWasmPanicBuffer();\n\n/**\n * Enable native-crash diagnostic reports (V8 fatal / OOM) next to the boot\n * breadcrumb, so the NEXT boot can root-cause a native death the JS handlers\n * never saw. Best-effort and gated by SHIPYARD_CRASH_EVIDENCE.\n */\nenableNativeCrashReports((entry) => logger.info(entry, entry.event));\n\nfunction getVersion(): string {\n try {\n const thisFile = fileURLToPath(import.meta.url);\n const pkgPath = resolve(thisFile, '../../package.json');\n const pkg: { version?: string } = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n return pkg.version ?? 'unknown';\n } catch {\n return 'unknown';\n }\n}\n\ninterface CliArgs {\n serve?: boolean;\n}\n\nfunction parseCliArgs(): CliArgs {\n const { values } = parseArgs({\n options: {\n serve: { type: 'boolean', short: 's' },\n version: { type: 'boolean', short: 'v' },\n help: { type: 'boolean', short: 'h' },\n },\n strict: true,\n });\n\n if (values.version) {\n process.stdout.write(`${getVersion()}\\n`);\n process.exit(0);\n }\n\n if (values.help) {\n logger.info(\n [\n `shipyard v${getVersion()} - ${TAGLINE_H1} — ${TAGLINE_H2.toLowerCase()}`,\n '',\n 'Usage:',\n ' shipyard start [--code CODE] Start daemon (authenticates if needed, opens browser)',\n ' shipyard login Authenticate with Shipyard',\n ' shipyard login --check Check current auth status',\n ' shipyard logout Clear stored credentials',\n '',\n 'Options:',\n ' -v, --version Show version',\n ' -h, --help Show this help',\n '',\n 'Authentication:',\n ' Run `claude auth login` or `codex auth` to authenticate your agent.',\n ' For CI/headless environments, set the agent-specific API key env var.',\n '',\n 'Environment:',\n ' ANTHROPIC_API_KEY API key for Claude Code (optional, overrides OAuth)',\n ' SHIPYARD_DEV Set to 1 for dev mode (uses ~/.shipyard-dev/)',\n ' SHIPYARD_WEB_URL Override browser URL for auto-open',\n ' LOG_LEVEL Log level: debug, info, warn, error (default: info)',\n ' SHIPYARD_SIGNALING_URL Signaling server WebSocket URL (optional)',\n ' SHIPYARD_USER_TOKEN JWT for signaling auth (optional)',\n ' SHIPYARD_USER_ID User ID for signaling path (optional, from login)',\n ' SHIPYARD_MACHINE_ID Machine identifier (default: os.hostname())',\n ' SHIPYARD_MACHINE_NAME Human-readable machine name (default: os.hostname())',\n ].join('\\n')\n );\n process.exit(0);\n }\n\n return {\n serve: values.serve,\n };\n}\n\nasync function loadAuthFromConfig(env: Env): Promise<void> {\n if (env.SHIPYARD_USER_TOKEN) return;\n\n const { loadAuthToken } = await import('./services/bootstrap/auth.js');\n const auth = await loadAuthToken();\n\n if (auth.status === 'ok') {\n env.SHIPYARD_USER_TOKEN = auth.token;\n env.SHIPYARD_USER_ID = auth.userId;\n env.SHIPYARD_USER_DISPLAY_NAME = auth.displayName;\n if (auth.signalingUrl) {\n env.SHIPYARD_SIGNALING_URL = auth.signalingUrl;\n }\n return;\n }\n\n if (auth.status === 'expired') {\n logger.warn('Auth token expired. Run `shipyard login` to re-authenticate.');\n return;\n }\n\n logger.warn('No auth token found. Run `shipyard login` to authenticate.');\n}\n\nasync function handleSubcommand(): Promise<boolean> {\n const subcommand = process.argv[2];\n\n if (subcommand === 'login') {\n const { loginCommand } = await import('./shared/commands/login.js');\n const hasCheck = process.argv.includes('--check');\n await loginCommand({ check: hasCheck });\n return true;\n }\n\n if (subcommand === 'logout') {\n const { logoutCommand } = await import('./shared/commands/logout.js');\n await logoutCommand();\n return true;\n }\n\n if (subcommand === 'start') {\n const { startCommand } = await import('./shared/commands/start.js');\n await startCommand();\n return true;\n }\n\n if (subcommand === 'roi') {\n const { roiCommand } = await import('./shared/commands/roi.js');\n await roiCommand();\n return true;\n }\n\n if (subcommand === 'plan-backfill') {\n const { planBackfillCommand } = await import('./shared/commands/plan-backfill.js');\n await planBackfillCommand();\n return true;\n }\n\n return false;\n}\n\nasync function main(): Promise<void> {\n if (await handleSubcommand()) return;\n\n const env = validateEnv();\n const args = parseCliArgs();\n\n if (args.serve) {\n await loadAuthFromConfig(env);\n const { serve } = await import('./services/serve.js');\n return serve({ isDev: env.SHIPYARD_DEV });\n }\n\n logger.error('Use `shipyard start` to run the daemon. Use --help for usage.');\n process.exit(1);\n}\n\nmain().catch((error: unknown) => {\n const errMsg = error instanceof Error ? error.message : String(error);\n const errStack = error instanceof Error ? error.stack : undefined;\n logger.error({ err: errMsg, stack: errStack }, 'Fatal error');\n process.exit(1);\n});\n","/**\n * Shipyard brand copy — the single source of truth for the tagline lockup,\n * category line, browser title, and SEO description. Every display surface\n * (static site hero, web app auth pages, daemon CLI banner, electron About\n * panel, index.html metas) derives from these constants.\n *\n * Lockup rule: TAGLINE_H1 never ships without TAGLINE_H2 beneath it on any\n * owned surface (site, OG image, app splash). Display punctuation: H1 bare,\n * H2 with a period.\n *\n * The two `index.html` files cannot import TypeScript, so they inline this\n * copy; `tests/brand-tagline-guard.test.ts` (run via `pnpm test:meta`)\n * asserts they stay in sync. If you change a constant here, that guard tells\n * you every static file to update.\n */\n\n/**\n * Primary headline. No trailing punctuation — display layers add the period\n * where design calls for it.\n */\nexport const TAGLINE_H1 = 'Ship together with AI';\n\n/** Secondary headline / promise line. No trailing punctuation. */\nexport const TAGLINE_H2 = 'Ship fast without shipping blind';\n\n/**\n * Category line — the positioning statement. Gets its own full-width beat at\n * the top of the underwater scroll on the landing page.\n */\nexport const CATEGORY_LINE =\n 'The local-first agentic workspace for engineers who refuse to ship what they don’t understand.';\n\n/** Browser title for the public landing page (title tag + meta name=title). */\nexport const BRAND_TITLE = 'Shipyard — the local-first agentic workspace';\n\n/** Long-form description for SEO metas (description / og / twitter). */\nexport const META_DESCRIPTION =\n 'Ship fast without shipping blind. Shipyard is the local-first agentic workspace where AI agents build on your machine and you decide what ships. Works with Claude Code, Codex, Cursor, and Antigravity.';\n\n/** Full-lockup alt text for the OG/twitter share image. */\nexport const OG_IMAGE_ALT = `Shipyard — ${TAGLINE_H1}. ${TAGLINE_H2}.`;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../../../packages/brand/src/index.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { parseArgs } from 'node:util';\nimport { TAGLINE_H1, TAGLINE_H2 } from '@shipyard/brand';\nimport { enableNativeCrashReports } from './services/bootstrap/native-crash-report.js';\nimport { type Env, validateEnv } from './shared/env.js';\nimport { logger } from './shared/logger.js';\nimport { installWasmPanicBuffer } from './shared/wasm-panic-buffer.js';\n\n/**\n * Node defaults to `Error.stackTraceLimit = 10`. Loro wasm panics produce\n * exactly 10-frame wasm chains, which means any JS frames *above* the wasm\n * boundary are silently truncated and never reach `panic-watch.log`.\n * Raising this lets the JS-stack capture (issue #2354) actually see\n * caller-side frames so we can distinguish JS-triggered re-entry from\n * pure-wasm cascade poison.\n */\nError.stackTraceLimit = 50;\n\n/**\n * Install the `console.error` ring buffer BEFORE any `loro-crdt` import\n * resolves. The vendored Loro fork has `console_error_panic_hook` already\n * installed in Rust, which writes the actual panic site (file:line) to\n * `console.error` before the wasm trap fires. Without this Node-side\n * capture, that message is lost and we are stuck guessing among the\n * multiple Rust panic classes that all surface as bare `unreachable`\n * (Invariant #14). The dynamic `await import('./services/serve.js')`\n * below is the first thing that pulls in `@loro-extended/repo` which in\n * turn loads the wasm module — so installing here is well-ordered.\n */\ninstallWasmPanicBuffer();\n\n/**\n * Enable native-crash diagnostic reports (V8 fatal / OOM) next to the boot\n * breadcrumb, so the NEXT boot can root-cause a native death the JS handlers\n * never saw. Best-effort and gated by SHIPYARD_CRASH_EVIDENCE.\n */\nenableNativeCrashReports((entry) => logger.info(entry, entry.event));\n\nfunction getVersion(): string {\n try {\n const thisFile = fileURLToPath(import.meta.url);\n const pkgPath = resolve(thisFile, '../../package.json');\n const pkg: { version?: string } = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n return pkg.version ?? 'unknown';\n } catch {\n return 'unknown';\n }\n}\n\ninterface CliArgs {\n serve?: boolean;\n}\n\nfunction parseCliArgs(): CliArgs {\n const { values } = parseArgs({\n options: {\n serve: { type: 'boolean', short: 's' },\n version: { type: 'boolean', short: 'v' },\n help: { type: 'boolean', short: 'h' },\n },\n strict: true,\n });\n\n if (values.version) {\n process.stdout.write(`${getVersion()}\\n`);\n process.exit(0);\n }\n\n if (values.help) {\n logger.info(\n [\n `shipyard v${getVersion()} - ${TAGLINE_H1} — ${TAGLINE_H2.toLowerCase()}`,\n '',\n 'Usage:',\n ' shipyard start [--code CODE] Start daemon (authenticates if needed, opens browser)',\n ' shipyard login Authenticate with Shipyard',\n ' shipyard login --check Check current auth status',\n ' shipyard logout Clear stored credentials',\n '',\n 'Options:',\n ' -v, --version Show version',\n ' -h, --help Show this help',\n '',\n 'Authentication:',\n ' Run `claude auth login` or `codex auth` to authenticate your agent.',\n ' For CI/headless environments, set the agent-specific API key env var.',\n '',\n 'Environment:',\n ' ANTHROPIC_API_KEY API key for Claude Code (optional, overrides OAuth)',\n ' SHIPYARD_DEV Set to 1 for dev mode (uses ~/.shipyard-dev/)',\n ' SHIPYARD_WEB_URL Override browser URL for auto-open',\n ' LOG_LEVEL Log level: debug, info, warn, error (default: info)',\n ' SHIPYARD_SIGNALING_URL Signaling server WebSocket URL (optional)',\n ' SHIPYARD_USER_TOKEN JWT for signaling auth (optional)',\n ' SHIPYARD_USER_ID User ID for signaling path (optional, from login)',\n ' SHIPYARD_MACHINE_ID Machine identifier (default: os.hostname())',\n ' SHIPYARD_MACHINE_NAME Human-readable machine name (default: os.hostname())',\n ].join('\\n')\n );\n process.exit(0);\n }\n\n return {\n serve: values.serve,\n };\n}\n\nasync function loadAuthFromConfig(env: Env): Promise<void> {\n if (env.SHIPYARD_USER_TOKEN) return;\n\n const { loadAuthToken } = await import('./services/bootstrap/auth.js');\n const auth = await loadAuthToken();\n\n if (auth.status === 'ok') {\n env.SHIPYARD_USER_TOKEN = auth.token;\n env.SHIPYARD_USER_ID = auth.userId;\n env.SHIPYARD_USER_DISPLAY_NAME = auth.displayName;\n if (auth.signalingUrl) {\n env.SHIPYARD_SIGNALING_URL = auth.signalingUrl;\n }\n return;\n }\n\n if (auth.status === 'expired') {\n logger.warn('Auth token expired. Run `shipyard login` to re-authenticate.');\n return;\n }\n\n logger.warn('No auth token found. Run `shipyard login` to authenticate.');\n}\n\nasync function handleSubcommand(): Promise<boolean> {\n const subcommand = process.argv[2];\n\n if (subcommand === 'login') {\n const { loginCommand } = await import('./shared/commands/login.js');\n const hasCheck = process.argv.includes('--check');\n await loginCommand({ check: hasCheck });\n return true;\n }\n\n if (subcommand === 'logout') {\n const { logoutCommand } = await import('./shared/commands/logout.js');\n await logoutCommand();\n return true;\n }\n\n if (subcommand === 'start') {\n const { startCommand } = await import('./shared/commands/start.js');\n await startCommand();\n return true;\n }\n\n if (subcommand === 'roi') {\n const { roiCommand } = await import('./shared/commands/roi.js');\n await roiCommand();\n return true;\n }\n\n if (subcommand === 'plan-backfill') {\n const { planBackfillCommand } = await import('./shared/commands/plan-backfill.js');\n await planBackfillCommand();\n return true;\n }\n\n return false;\n}\n\nasync function main(): Promise<void> {\n if (await handleSubcommand()) return;\n\n const env = validateEnv();\n const args = parseCliArgs();\n\n if (args.serve) {\n await loadAuthFromConfig(env);\n const { serve } = await import('./services/serve.js');\n return serve({ isDev: env.SHIPYARD_DEV });\n }\n\n logger.error('Use `shipyard start` to run the daemon. Use --help for usage.');\n process.exit(1);\n}\n\nmain().catch((error: unknown) => {\n const errMsg = error instanceof Error ? error.message : String(error);\n const errStack = error instanceof Error ? error.stack : undefined;\n logger.error({ err: errMsg, stack: errStack }, 'Fatal error');\n process.exit(1);\n});\n","/**\n * Shipyard brand copy — the single source of truth for the tagline lockup,\n * category line, browser title, and SEO description. Every display surface\n * (static site hero, web app auth pages, daemon CLI banner, electron About\n * panel, index.html metas) derives from these constants.\n *\n * Lockup rule: TAGLINE_H1 never ships without TAGLINE_H2 beneath it on any\n * owned surface (site, OG image, app splash). Display punctuation: H1 bare,\n * H2 with a period.\n *\n * The two `index.html` files cannot import TypeScript, so they inline this\n * copy; `tests/brand-tagline-guard.test.ts` (run via `pnpm test:meta`)\n * asserts they stay in sync. If you change a constant here, that guard tells\n * you every static file to update.\n */\n\n/**\n * Primary headline. No trailing punctuation — display layers add the period\n * where design calls for it.\n */\nexport const TAGLINE_H1 = 'Ship together with AI';\n\n/** Secondary headline / promise line. No trailing punctuation. */\nexport const TAGLINE_H2 = 'Ship fast without shipping blind';\n\n/**\n * Category line — the positioning statement. Gets its own full-width beat at\n * the top of the underwater scroll on the landing page.\n */\nexport const CATEGORY_LINE =\n 'The local-first agentic workspace for engineers who refuse to ship what they don’t understand.';\n\n/** Browser title for the public landing page (title tag + meta name=title). */\nexport const BRAND_TITLE = 'Shipyard — the local-first agentic workspace';\n\n/** Long-form description for SEO metas (description / og / twitter). */\nexport const META_DESCRIPTION =\n 'Ship fast without shipping blind. Shipyard is the local-first agentic workspace where AI agents build on your machine and you decide what ships. Works with Claude Code, Codex, Cursor, and Antigravity.';\n\n/** Full-lockup alt text for the OG/twitter share image. */\nexport const OG_IMAGE_ALT = `Shipyard — ${TAGLINE_H1}. ${TAGLINE_H2}.`;\n\n/**\n * Canonical public FRONT DOOR — the static-site hero plus the install/download\n * surface. This is the one host for user-facing brand ASSETS (OG image,\n * favicons) and the install/CTA URL. The `get.` host and the marketing host are\n * the same surface on purpose: the curl-this URL == the pitch URL.\n *\n * NOT the app apex `shipyard.computer` (which serves the web app and\n * plan-snapshot share URLs) and NOT `pub.shipyard.computer` (published\n * artifacts). Do not point app/share/publish URLs here. Topology lives in\n * `apps/frontdoor-worker/src/index.ts`.\n */\nexport const FRONT_DOOR_URL = 'https://get.shipyard.computer';\n\n/** Open Graph / Twitter share-card image (1200×630), served from the front door. */\nexport const OG_IMAGE_URL = `${FRONT_DOOR_URL}/og-image.png`;\n\n/** Classic 32×32 `.ico` favicon, served from the front door. */\nexport const FAVICON_ICO_URL = `${FRONT_DOOR_URL}/favicon.ico`;\n\n/** Scalable SVG favicon, served from the front door (`apps/static-site/public/icon.svg`). */\nexport const FAVICON_SVG_URL = `${FRONT_DOOR_URL}/icon.svg`;\n\n/**\n * Canonical one-line install command for the macOS desktop app (stable\n * channel). The `install.sh` path is routed by the front-door worker to the\n * session server's install surface (`isInstallSurface` in\n * `apps/frontdoor-worker/src/index.ts`).\n */\nexport const INSTALL_CMD = `curl -fsSL ${FRONT_DOOR_URL}/install.sh | bash`;\n\n/**\n * Attribution voice for PUBLISHED artifacts — a TOP-OF-FUNNEL CONVERSION\n * surface. A stranger who's never heard of Shipyard sees this because a user\n * shared something they built, so the copy ladders to the hero promise in\n * broad, inviting language — NOT the engineer-insider CATEGORY_LINE (wrong\n * audience). Human-approved wording; kept a single constant so it's a one-line\n * change if tuned later.\n */\nexport const ATTRIBUTION_LINE =\n 'Made with Shipyard — the local-first agentic workspace. Ship fast with AI, without shipping blind.';\n"],"mappings":"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","names":[]}