@schoolai/shipyard 3.24.0 → 3.25.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.
- package/dist/capability-detector-worker.js +3 -3
- package/dist/{chunk-NG355H2L.js → chunk-BBKZIEKF.js} +68 -1
- package/dist/chunk-BBKZIEKF.js.map +1 -0
- package/dist/{chunk-BGJ6QJNS.js → chunk-FC3HWVYQ.js} +46 -1
- package/dist/{chunk-BGJ6QJNS.js.map → chunk-FC3HWVYQ.js.map} +1 -1
- package/dist/{chunk-JZTUQVHQ.js → chunk-IRS6HYMU.js} +2 -2
- package/dist/{chunk-UK3IMOSP.js → chunk-VQCBF7X5.js} +71 -7
- package/dist/chunk-VQCBF7X5.js.map +1 -0
- package/dist/{chunk-TU3MSY2I.js → chunk-X4IWMBWY.js} +2 -2
- package/dist/{chunk-DDC5OQOC.js → chunk-XNH6FVIX.js} +3 -3
- package/dist/{chunk-BN54GQR3.js → chunk-YTYPMVS4.js} +3 -53
- package/dist/chunk-YTYPMVS4.js.map +1 -0
- package/dist/cursor-runner.js +8 -4
- package/dist/cursor-runner.js.map +1 -1
- package/dist/electron-utility.js +2 -2
- package/dist/index.js +3 -3
- package/dist/{plan-backfill-XYOUK2DY.js → plan-backfill-SDYNTKUX.js} +3 -3
- package/dist/{serve-RWFBSQIE.js → serve-HTQMCHRP.js} +334 -188
- package/dist/{serve-RWFBSQIE.js.map → serve-HTQMCHRP.js.map} +1 -1
- package/dist/{start-ZV22UPMC.js → start-HOICUTYV.js} +4 -4
- package/package.json +1 -1
- package/dist/chunk-BN54GQR3.js.map +0 -1
- package/dist/chunk-NG355H2L.js.map +0 -1
- package/dist/chunk-UK3IMOSP.js.map +0 -1
- /package/dist/{chunk-JZTUQVHQ.js.map → chunk-IRS6HYMU.js.map} +0 -0
- /package/dist/{chunk-TU3MSY2I.js.map → chunk-X4IWMBWY.js.map} +0 -0
- /package/dist/{chunk-DDC5OQOC.js.map → chunk-XNH6FVIX.js.map} +0 -0
- /package/dist/{plan-backfill-XYOUK2DY.js.map → plan-backfill-SDYNTKUX.js.map} +0 -0
- /package/dist/{start-ZV22UPMC.js.map → start-HOICUTYV.js.map} +0 -0
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
detectCapabilities,
|
|
4
4
|
refreshMcpServers,
|
|
5
5
|
registerBuiltInProfiles
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-YTYPMVS4.js";
|
|
7
7
|
import "./chunk-VSRWL5SP.js";
|
|
8
8
|
import {
|
|
9
9
|
getRepoMetadata
|
|
@@ -15,8 +15,8 @@ import {
|
|
|
15
15
|
import "./chunk-RR6V6SNM.js";
|
|
16
16
|
import "./chunk-ROEOOZOW.js";
|
|
17
17
|
import "./chunk-ZFKJAYAN.js";
|
|
18
|
-
import "./chunk-
|
|
19
|
-
import "./chunk-
|
|
18
|
+
import "./chunk-X4IWMBWY.js";
|
|
19
|
+
import "./chunk-FC3HWVYQ.js";
|
|
20
20
|
import "./chunk-RL25HGER.js";
|
|
21
21
|
import "./chunk-EHQITHQX.js";
|
|
22
22
|
import "./chunk-X3MULCV5.js";
|
|
@@ -3,6 +3,71 @@ import {
|
|
|
3
3
|
external_exports
|
|
4
4
|
} from "./chunk-JCEWPG3R.js";
|
|
5
5
|
|
|
6
|
+
// src/services/mcp/cursor-mcp-config.ts
|
|
7
|
+
function isRecord(value) {
|
|
8
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
function copyNonEmptyRecord(target, key, value) {
|
|
11
|
+
if (isRecord(value) && Object.keys(value).length > 0) target[key] = value;
|
|
12
|
+
}
|
|
13
|
+
function buildCursorStdioEntry(value) {
|
|
14
|
+
if (typeof value.command !== "string" || value.command.length === 0) return null;
|
|
15
|
+
const entry = { type: "stdio", command: value.command };
|
|
16
|
+
if (Array.isArray(value.args) && value.args.length > 0) entry.args = value.args;
|
|
17
|
+
copyNonEmptyRecord(entry, "env", value.env);
|
|
18
|
+
if (typeof value.cwd === "string" && value.cwd.length > 0) entry.cwd = value.cwd;
|
|
19
|
+
return entry;
|
|
20
|
+
}
|
|
21
|
+
function buildCursorHttpEntry(value) {
|
|
22
|
+
if (typeof value.url !== "string" || value.url.length === 0) return null;
|
|
23
|
+
const transport = value.type === "sse" ? "sse" : value.type === "http" ? "http" : "http";
|
|
24
|
+
const entry = { type: transport, url: value.url };
|
|
25
|
+
const headers = value.headers ?? value.http_headers;
|
|
26
|
+
copyNonEmptyRecord(entry, "headers", headers);
|
|
27
|
+
return entry;
|
|
28
|
+
}
|
|
29
|
+
function getUnsupportedCursorMcpReason(value) {
|
|
30
|
+
if (value.type === "sdk" || "instance" in value) return "unsupported_sdk_instance";
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
function buildCursorMcpEntry(value) {
|
|
34
|
+
if (!isRecord(value)) return { kind: "skip", reason: "not_object" };
|
|
35
|
+
const unsupportedReason = getUnsupportedCursorMcpReason(value);
|
|
36
|
+
if (unsupportedReason) return { kind: "skip", reason: unsupportedReason };
|
|
37
|
+
if (typeof value.command === "string") {
|
|
38
|
+
const stdio = buildCursorStdioEntry(value);
|
|
39
|
+
return stdio ? { kind: "ok", config: stdio } : { kind: "skip", reason: "missing_command" };
|
|
40
|
+
}
|
|
41
|
+
if (typeof value.url === "string") {
|
|
42
|
+
const http = buildCursorHttpEntry(value);
|
|
43
|
+
return http ? { kind: "ok", config: http } : { kind: "skip", reason: "missing_url" };
|
|
44
|
+
}
|
|
45
|
+
return { kind: "skip", reason: "missing_command_or_url" };
|
|
46
|
+
}
|
|
47
|
+
function countLoopbackHttpEntries(servers) {
|
|
48
|
+
let count = 0;
|
|
49
|
+
for (const value of Object.values(servers)) {
|
|
50
|
+
if (!isRecord(value)) continue;
|
|
51
|
+
if (value.type !== "http" && value.type !== "sse") continue;
|
|
52
|
+
if (typeof value.url !== "string") continue;
|
|
53
|
+
if (value.url.startsWith("http://127.0.0.1:")) count += 1;
|
|
54
|
+
}
|
|
55
|
+
return count;
|
|
56
|
+
}
|
|
57
|
+
function buildCursorMcpConfigFromSet(servers) {
|
|
58
|
+
const result = {};
|
|
59
|
+
const skipped = [];
|
|
60
|
+
for (const [name, config] of Object.entries(servers)) {
|
|
61
|
+
const entry = buildCursorMcpEntry(config);
|
|
62
|
+
if (entry.kind === "ok") {
|
|
63
|
+
result[name] = entry.config;
|
|
64
|
+
} else {
|
|
65
|
+
skipped.push({ name, reason: entry.reason });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { servers: result, skipped };
|
|
69
|
+
}
|
|
70
|
+
|
|
6
71
|
// src/services/session/cursor-fast-mode-send.ts
|
|
7
72
|
function buildCursorFastModeSendParam(fastMode, fastModeParam) {
|
|
8
73
|
if (fastModeParam === null) return null;
|
|
@@ -522,9 +587,11 @@ function parseRunnerToDaemon(raw) {
|
|
|
522
587
|
}
|
|
523
588
|
|
|
524
589
|
export {
|
|
590
|
+
countLoopbackHttpEntries,
|
|
591
|
+
buildCursorMcpConfigFromSet,
|
|
525
592
|
buildCursorFastModeModelOption,
|
|
526
593
|
shouldUseCursorFastModePricing,
|
|
527
594
|
parseDaemonToRunner,
|
|
528
595
|
parseRunnerToDaemon
|
|
529
596
|
};
|
|
530
|
-
//# sourceMappingURL=chunk-
|
|
597
|
+
//# sourceMappingURL=chunk-BBKZIEKF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/mcp/cursor-mcp-config.ts","../src/services/session/cursor-fast-mode-send.ts","../src/services/session/cursor-runner-protocol.ts"],"sourcesContent":["/**\n * Convert the McpCoordinator's SDK-oriented server map into Cursor's\n * `McpServerConfig` shape for `Agent.create` / per-send `mcpServersOverride`.\n *\n * Cursor cannot consume Claude's `{type:'sdk', instance}` entries; stdio pool\n * servers must be normalized to `{type:'stdio', command, args?, env?, cwd?}`.\n * HTTP proxy swaps already emit Cursor-shaped `{type:'http', url, headers}`.\n */\n\nexport interface CursorMcpConfigFromSetResult {\n servers: Record<string, Record<string, unknown>>;\n skipped: Array<{ name: string; reason: string }>;\n}\n\ntype CursorMcpEntryResult =\n | { kind: 'ok'; config: Record<string, unknown> }\n | { kind: 'skip'; reason: string };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction copyNonEmptyRecord(target: Record<string, unknown>, key: string, value: unknown): void {\n if (isRecord(value) && Object.keys(value).length > 0) target[key] = value;\n}\n\n/**\n * Mirror of Codex `buildStdioEntry` (MCPServerInfo wire) for coordinator records.\n * Cursor's union accepts optional `type: 'stdio'`; we set it explicitly so pool\n * stdio servers are never ambiguous with the http/sse arm.\n */\nexport function buildCursorStdioEntry(\n value: Record<string, unknown>\n): Record<string, unknown> | null {\n if (typeof value.command !== 'string' || value.command.length === 0) return null;\n const entry: Record<string, unknown> = { type: 'stdio', command: value.command };\n if (Array.isArray(value.args) && value.args.length > 0) entry.args = value.args;\n copyNonEmptyRecord(entry, 'env', value.env);\n if (typeof value.cwd === 'string' && value.cwd.length > 0) entry.cwd = value.cwd;\n return entry;\n}\n\nfunction buildCursorHttpEntry(value: Record<string, unknown>): Record<string, unknown> | null {\n if (typeof value.url !== 'string' || value.url.length === 0) return null;\n const transport =\n value.type === 'sse' ? 'sse' : value.type === 'http' ? 'http' : ('http' as const);\n const entry: Record<string, unknown> = { type: transport, url: value.url };\n const headers = value.headers ?? value.http_headers;\n copyNonEmptyRecord(entry, 'headers', headers);\n return entry;\n}\n\nfunction getUnsupportedCursorMcpReason(value: Record<string, unknown>): string | null {\n if (value.type === 'sdk' || 'instance' in value) return 'unsupported_sdk_instance';\n return null;\n}\n\nfunction buildCursorMcpEntry(value: unknown): CursorMcpEntryResult {\n if (!isRecord(value)) return { kind: 'skip', reason: 'not_object' };\n const unsupportedReason = getUnsupportedCursorMcpReason(value);\n if (unsupportedReason) return { kind: 'skip', reason: unsupportedReason };\n\n if (typeof value.command === 'string') {\n const stdio = buildCursorStdioEntry(value);\n return stdio ? { kind: 'ok', config: stdio } : { kind: 'skip', reason: 'missing_command' };\n }\n\n if (typeof value.url === 'string') {\n const http = buildCursorHttpEntry(value);\n return http ? { kind: 'ok', config: http } : { kind: 'skip', reason: 'missing_url' };\n }\n\n return { kind: 'skip', reason: 'missing_command_or_url' };\n}\n\n/**\n * Count entries pointing at the daemon's loopback MCP proxy — i.e. the\n * proxy-managed OAuth servers (`linear`, `Gmail`, …) after `#applyCursorProxyHttpSwap`.\n *\n * This subset silently dropping to zero IS the #5265 failure: the agent still\n * sees the servers in its status list while `CallMcpTool` reports they do not\n * exist. Logged alongside the raw count so a push that lost every proxy server\n * is one grep away instead of a hand-count across interleaved runners.\n */\nexport function countLoopbackHttpEntries(servers: Record<string, unknown>): number {\n let count = 0;\n for (const value of Object.values(servers)) {\n if (!isRecord(value)) continue;\n if (value.type !== 'http' && value.type !== 'sse') continue;\n if (typeof value.url !== 'string') continue;\n if (value.url.startsWith('http://127.0.0.1:')) count += 1;\n }\n return count;\n}\n\nexport function buildCursorMcpConfigFromSet(\n servers: Record<string, unknown>\n): CursorMcpConfigFromSetResult {\n const result: Record<string, Record<string, unknown>> = {};\n const skipped: Array<{ name: string; reason: string }> = [];\n\n for (const [name, config] of Object.entries(servers)) {\n const entry = buildCursorMcpEntry(config);\n if (entry.kind === 'ok') {\n result[name] = entry.config;\n } else {\n skipped.push({ name, reason: entry.reason });\n }\n }\n\n return { servers: result, skipped };\n}\n","/**\n * cursor-fast-mode-send — pure helpers for Cursor per-turn model params\n * (Fast Mode today; effort and friends later) and Fast Mode pricing.\n *\n * The discovery cache stores the ON binding `{ id: \"fast\", value: \"true\" }`\n * (or null when the model exposes no Fast Mode knob). At send time we must\n * always be explicit when the model supports the knob:\n *\n * - toggle ON → `{ id, value: \"true\" }`\n * - toggle OFF → `{ id, value: \"false\" }`\n *\n * Omitting `model.params` when the toggle is off cedes the decision to\n * Cursor's invisible server default, which (per usage-dashboard evidence)\n * bills Fast rates. Models with no Fast Mode param must still omit\n * `model.params` entirely — never send `fast:\"false\"` to a model that did\n * not declare the knob.\n *\n * `buildCursorModelSendParams` returns a LIST so a future effort (or other)\n * binding can push another entry without reworking the send path. Today\n * the list is 0 or 1 elements (Fast Mode only).\n */\n\nexport interface ModelParamBinding {\n id: string;\n value: string;\n}\n\n/** @deprecated Prefer {@link ModelParamBinding}; kept as an alias for call sites. */\nexport type FastModeParamBinding = ModelParamBinding;\n\n/**\n * Build the Fast Mode `{id, value}` for one turn, or null when the model\n * has no Fast Mode binding (caller must not include it in the params list).\n */\nexport function buildCursorFastModeSendParam(\n fastMode: boolean,\n fastModeParam: ModelParamBinding | null\n): ModelParamBinding | null {\n if (fastModeParam === null) return null;\n return { id: fastModeParam.id, value: fastMode ? 'true' : 'false' };\n}\n\n/**\n * Assemble every resolved model param for a turn. Fast Mode is the only\n * contributor today; add future bindings (effort, etc.) as extra pushes.\n */\nexport function buildCursorModelSendParams(args: {\n fastMode: boolean;\n fastModeParam: ModelParamBinding | null;\n}): ModelParamBinding[] {\n const params: ModelParamBinding[] = [];\n const fast = buildCursorFastModeSendParam(args.fastMode, args.fastModeParam);\n if (fast !== null) params.push(fast);\n return params;\n}\n\n/**\n * `SendOptions.model` fragment for a turn. Null means omit `model` entirely\n * (no resolved params for this model).\n */\nexport function buildCursorSendModelOption(\n modelId: string,\n params: readonly ModelParamBinding[]\n): { id: string; params: ModelParamBinding[] } | null {\n if (params.length === 0) return null;\n return { id: modelId, params: [...params] };\n}\n\n/**\n * Convenience: Fast Mode toggle + binding → `SendOptions.model` or null.\n */\nexport function buildCursorFastModeModelOption(\n modelId: string,\n fastMode: boolean,\n fastModeParam: ModelParamBinding | null\n): { id: string; params: ModelParamBinding[] } | null {\n return buildCursorSendModelOption(\n modelId,\n buildCursorModelSendParams({ fastMode, fastModeParam })\n );\n}\n\n/**\n * Whether cost estimation should use `fastModePricing`. Follows the value\n * we actually send — not merely whether the model is Fast-capable.\n */\nexport function shouldUseCursorFastModePricing(\n fastMode: boolean,\n fastModeParam: ModelParamBinding | null\n): boolean {\n const sent = buildCursorFastModeSendParam(fastMode, fastModeParam);\n return sent?.value === 'true';\n}\n","import { z } from 'zod';\n\n/**\n * IPC protocol between the daemon (parent) and the `cursor-runner` forked\n * child subprocess.\n *\n * Wire format: structured-clone over the Node IPC channel\n * (`child.send(msg)` / `process.on('message', msg => ...)`). The schemas\n * below are Zod-parsed on receive so a corrupt or version-skewed peer\n * cannot drive the recipient into an undefined state.\n *\n * Stdout/stderr of the runner are inherited from the daemon (see S2b's\n * `child_process.fork({ stdio: 'inherit', silent: false })`) — those are\n * NOT carrying IPC traffic, they are for human-readable logs and native\n * panic stack traces.\n *\n * `generation` is a per-turn monotonic counter minted by the daemon-side\n * adapter. Every per-turn message in either direction is stamped with the\n * value live at the time of mint; the daemon drops inbound events whose\n * generation does not match the current run (closes Invariant #16 — the\n * transport discriminator must be present at every layer). The runner\n * does NOT drop on its own — it echoes whatever generation the daemon\n * stamped, so the daemon can correlate.\n *\n * Lifecycle events that are not per-turn (`init`, `init_ok`,\n * `init_error`, `log`, `close`) carry no generation.\n *\n * MCP server config shape: `Record<string, unknown>` here is intentional.\n * We keep the wire schema decoupled from `@cursor/sdk`'s `McpServerConfig`\n * to avoid coupling the daemon and runner to the same exact SDK version\n * at the type level. The runner re-validates against the SDK shape at\n * the call site.\n */\n\nconst McpServersSchema = z.record(z.string(), z.unknown());\nconst CursorResolvedSkillBodySchema = z.object({\n name: z.string(),\n namespace: z.string().optional(),\n body: z.string(),\n});\n\n/**\n * DaemonToRunner — outbound from the daemon-side `CursorAgentSubprocess`\n * adapter to the forked runner child.\n */\nexport const DaemonToRunnerSchema = z.discriminatedUnion('kind', [\n z.object({\n kind: z.literal('init'),\n taskId: z.string(),\n cwd: z.string(),\n apiKey: z.string(),\n modelId: z.string(),\n mcpServers: McpServersSchema,\n harnessUrl: z.string(),\n harnessToken: z.string(),\n /**\n * Stable per-runner identity for the runner-scoped harness path (C8/C9\n * warm pool). The runner puts this in the `x-shipyard-runner-id` header\n * (instead of the per-task `x-shipyard-task-id`) so the harness MCP-server\n * config — and therefore the SDK's process-scoped executor cache key — is\n * byte-identical across every task the runner serves. Optional/additive:\n * a pre-pool daemon omits it, and the runner falls back to the legacy\n * `x-shipyard-task-id` header keyed on `taskId`.\n */\n harnessRunnerId: z.string().optional(),\n /**\n * When true, the runner is pool-managed: on `close` it disposes the Agent\n * but does NOT `process.exit(0)` — it resets to a quiescent state and acks\n * `pooled_idle` so the daemon can keep the warm process (and its\n * `localExecutorCache`) alive for the next matching task. Optional/additive\n * — absent means \"throwaway runner, exit on close\" (legacy behavior).\n */\n pooled: z.boolean().optional(),\n /**\n * Absolute filesystem path where skill files for this task are\n * written (see plan v2 E#V7). The runner does not enumerate the\n * directory — it just forwards the path to the SDK so any future\n * skills-loading code has the location. May be empty string if\n * skills are disabled for this run.\n */\n skillsDir: z.string(),\n sessionId: z.string(),\n generation: z.number().int().nonnegative(),\n /**\n * Cross-runtime structured handoff (Wave 7.5). When set, the daemon\n * already wrote a fabricated `ConversationStateStructure` Merkle tree\n * to the SDK's SQLite stores during the pre-fork exclusive write\n * window. The runner MUST call `Agent.resume(injectedAgentId, ...)`\n * instead of `Agent.create(...)` so the SDK reads back the prior\n * conversation as context. Same-id `Agent.create()` would\n * PRIMARY-KEY-conflict on the agents row.\n *\n * Daemon↔forked-child IPC, NOT WebRTC — no PROTOCOL_VERSION bump.\n */\n injectedAgentId: z.string().optional(),\n /**\n * Persisted Cursor agentId from a prior `init_ok`. When set, the runner\n * calls `Agent.resume(resumeAgentId, ...)` instead of `Agent.create()` —\n * SDK reads its SQLite checkpoint store keyed by agentId and brings up\n * the prior conversation without rebuilding from scratch. Cold-build\n * cost (Statsig bootstrap + workspace runtime + MCP startup) is still\n * paid; the win is preserving conversation context across daemon\n * restarts and not depending on the JSONL replay path for that.\n *\n * Distinct from `injectedAgentId`: that one is for cross-runtime\n * structured handoff where the daemon fabricates a fake conversation;\n * this one is just rejoining a previously-real conversation. Resume\n * failures (agentId not in the SDK's local store, e.g. user wiped\n * `~/.cursor/`) fall back to `Agent.create` with the same options so\n * the spawn succeeds — just without prior context.\n */\n resumeAgentId: z.string().optional(),\n /**\n * Initial Fast Mode flag from `composerSettings.fastMode`. When\n * `fastModeParam` is non-null the runner always attaches\n * `SendOptions.model.params` with value `\"true\"` (ON) or `\"false\"`\n * (OFF). When `fastModeParam` is null the runner omits `model.params`.\n * Optional/additive — pre-Fast-Mode daemons omit the field and the\n * runner treats it as false.\n */\n fastMode: z.boolean().optional(),\n /**\n * Resolved Fast Mode ON-binding for `modelId`, sourced from the\n * daemon's `cursor-models-cache`. `null` means the model exposes no\n * Fast Mode knob (or discovery has not resolved one). The runner\n * treats both `null` and `undefined` as \"omit model.params\".\n */\n fastModeParam: z.object({ id: z.string(), value: z.string() }).nullable().optional(),\n /**\n * Named subagent catalog rendered for the Cursor runtime. When present,\n * the runner passes this to `Agent.create({agents,...})` and\n * `Agent.resume({agents,...})` so Cursor's built-in `task` tool can\n * spawn named subagents from Shipyard's catalog. Optional/additive —\n * pre-catalog daemons omit the field; the runner treats absence as an\n * empty catalog (no named subagents declared to the SDK).\n *\n * Daemon↔forked-child IPC only — NOT the browser wire. No\n * PROTOCOL_VERSION bump.\n */\n agents: z\n .record(\n z.string(),\n z.object({\n description: z.string(),\n prompt: z.string(),\n model: z.union([z.object({ id: z.string() }), z.literal('inherit')]).optional(),\n mcpServers: z.array(z.string()).optional(),\n })\n )\n .optional(),\n /**\n * Native Cursor SDK conversation mode for this agent. When `'plan'`, the\n * SDK tells composer-2.5 to call its built-in `createPlan` tool instead of\n * writing files. This is the SOFT signal complementing the hard-enforcement\n * layer in `.cursor/hooks.json` deny-writes. Optional/additive — pre-1.0.17\n * runners omit it and the SDK defaults to `'agent'` mode.\n *\n * Daemon↔forked-child IPC only — NOT the browser wire. No\n * PROTOCOL_VERSION bump required.\n */\n mode: z.enum(['agent', 'plan']).optional(),\n }),\n z.object({\n kind: z.literal('push_message'),\n generation: z.number().int().nonnegative(),\n /**\n * Content blocks the daemon wants to send as a user turn. Shape is\n * Shipyard's internal `ContentBlock[]` — the runner translates to\n * Cursor's `SDKUserMessage` at the boundary (see cursor-content-builder.ts).\n *\n * Image attachments ride as `resource` blocks with `BlobResourceContents`\n * (mime + base64 blob). The runner extracts these into `SDKImage[]` and\n * attaches them to the `SDKUserMessage` it sends to `Agent.send()`.\n */\n content: z.array(z.unknown()),\n /**\n * SKILL.md bodies resolved by the daemon from the trusted capabilities\n * skill index for this turn. The runner must use these bodies instead of\n * any path carried on browser-authored `skill_invocation` content blocks.\n *\n * Daemon↔forked-child IPC only — NOT the browser wire. No\n * PROTOCOL_VERSION bump required.\n */\n skillBodies: z.array(CursorResolvedSkillBodySchema).optional(),\n /**\n * Cursor SDK local recovery flag. Only the daemon-side AgentBusyError\n * retry path sets this after confirming the errored generation is still\n * current; stale-generation retries are dropped before reaching the\n * runner, so this must never expire a legitimately live successor run.\n */\n forceLocalRun: z.boolean().optional(),\n }),\n z.object({\n kind: z.literal('interrupt'),\n generation: z.number().int().nonnegative(),\n }),\n z.object({\n kind: z.literal('set_model'),\n generation: z.number().int().nonnegative(),\n modelId: z.string(),\n }),\n z.object({\n kind: z.literal('set_mcp_servers'),\n generation: z.number().int().nonnegative(),\n servers: McpServersSchema,\n }),\n z.object({\n kind: z.literal('set_harness_task_id'),\n generation: z.number().int().nonnegative(),\n taskId: z.string(),\n token: z.string(),\n }),\n /**\n * Warm-pool reinit (C9). A pooled runner whose prior task closed is being\n * claimed for a NEW task. The runner cancels any active run, disposes the\n * current Agent, updates its state to the new task, and creates a fresh\n * Agent (`Agent.resume(resumeAgentId)` if present, else `Agent.create`) —\n * reusing the warm process-scoped executor cache (cache HIT on the next\n * send, because the harness MCP entry uses the STABLE `harnessRunnerId`\n * header so `mcpServers` is byte-identical to the prior task's).\n *\n * On success → `reinit_ok { sessionId }`; on ANY throw → `reinit_error`\n * and the runner does NOT exit (the daemon kills + respawns fresh). This\n * is the fail-closed contract: a broken reinit becomes a cold spawn, never\n * a stale/cross-task state leak.\n *\n * Carries the full per-task spawn surface (everything the cache key and the\n * Agent depend on). `mcpServers` here MUST NOT include the harness entry —\n * the runner re-adds it from `harnessRunnerId`/`harnessToken` so the entry\n * is byte-identical across init→reinit.\n */\n z.object({\n kind: z.literal('reinit'),\n taskId: z.string(),\n modelId: z.string(),\n mcpServers: McpServersSchema,\n harnessRunnerId: z.string(),\n harnessToken: z.string(),\n skillsDir: z.string(),\n generation: z.number().int().nonnegative(),\n resumeAgentId: z.string().optional(),\n fastMode: z.boolean().optional(),\n fastModeParam: z.object({ id: z.string(), value: z.string() }).nullable().optional(),\n agents: z\n .record(\n z.string(),\n z.object({\n description: z.string(),\n prompt: z.string(),\n model: z.union([z.object({ id: z.string() }), z.literal('inherit')]).optional(),\n mcpServers: z.array(z.string()).optional(),\n })\n )\n .optional(),\n /**\n * Native Cursor SDK conversation mode for the task this warm runner is\n * being claimed for. Threaded on `reinit` (not just `init`) because the\n * Agent is recreated on adoption — without this, a warm-pool-adopted\n * plan-mode task would create its Agent in the SDK's default `'agent'`\n * mode and silently lose the soft plan signal. See the `init` variant's\n * `mode` doc. Optional/additive — same backwards-compat posture.\n */\n mode: z.enum(['agent', 'plan']).optional(),\n }),\n /**\n * Flip the runner's pooled flag mid-life (C9). The daemon sends this just\n * before `close` when it intends to keep the warm process: the runner's\n * `close` handler then disposes the Agent and acks `pooled_idle` instead of\n * `process.exit(0)`. Sending `pooled: false` reverts to throwaway behavior.\n */\n z.object({\n kind: z.literal('set_pooled'),\n generation: z.number().int().nonnegative(),\n pooled: z.boolean(),\n }),\n /**\n * Daemon→runner notification that the permission mode changed. The runner\n * logs this for diagnostic observability; it takes no action (mode-file\n * is the authoritative source the hook script reads on each tool call).\n */\n z.object({\n kind: z.literal('set_permission_mode'),\n generation: z.number().int().nonnegative(),\n mode: z.enum(['default', 'accept-edits', 'plan', 'bypass', 'auto']),\n }),\n /**\n * Daemon→runner notification that Fast Mode was toggled mid-session.\n * Carries the current generation (not bumped — this is per-state, not\n * per-turn) so the runner can correlate the update. Effective on the\n * NEXT `push_message`: the runner records the flag and rebuilds\n * `SendOptions` on each `agent.send()`.\n */\n z.object({\n kind: z.literal('set_fast_mode'),\n generation: z.number().int().nonnegative(),\n fastMode: z.boolean(),\n }),\n z.object({ kind: z.literal('close') }),\n]);\nexport type DaemonToRunner = z.infer<typeof DaemonToRunnerSchema>;\n\n/**\n * The agents map as it travels the wire — the `init`/`reinit` `agents` field.\n * Narrower than `@cursor/sdk`'s `AgentDefinition` (this `mcpServers` is\n * `string[]`); the runner reconstructs the full `AgentDefinition` on receipt.\n * Daemon-side code that builds agents for the wire should use THIS type so the\n * IPC payload is type-correct by construction (no boundary cast).\n */\nexport type DaemonToRunnerAgents = NonNullable<Extract<DaemonToRunner, { kind: 'init' }>['agents']>;\n\n/**\n * RunnerToDaemon — inbound to the daemon-side adapter from the runner.\n *\n * `sdk_message.payload` is typed as `unknown` on the wire and narrowed\n * to `SDKMessage` (from `@cursor/sdk`) by the daemon-side translator\n * (`cursor-event-translator.ts`, S2b). Keeping it `unknown` at the\n * protocol boundary insulates the daemon from new SDK message variants\n * — the translator decides what to drop versus translate.\n */\nexport const RunnerToDaemonSchema = z.discriminatedUnion('kind', [\n z.object({\n kind: z.literal('init_ok'),\n sessionId: z.string(),\n tools: z.array(z.string()),\n model: z.string(),\n mcpStatus: z.array(z.object({ name: z.string(), status: z.string() })),\n }),\n z.object({\n kind: z.literal('init_error'),\n error: z.string(),\n /** SDK error class name (e.g. AuthenticationError) for daemon-side classification. */\n errorName: z.string().optional(),\n /** Stable SDK/backend error code, when available. */\n errorCode: z.string().optional(),\n statusCode: z.number().int().optional(),\n /**\n * `true` for transient failures (network, rate limit) where the\n * daemon should consider a retry; `false` for configuration errors\n * (bad API key, unknown model) where retry will not help.\n */\n retryable: z.boolean(),\n }),\n z.object({\n kind: z.literal('sdk_message'),\n generation: z.number().int().nonnegative(),\n payload: z.unknown(),\n }),\n z.object({\n kind: z.literal('run_complete'),\n generation: z.number().int().nonnegative(),\n /**\n * Cursor's `RunResult` — kept as `unknown` on the wire (see\n * `sdk_message.payload` rationale). The daemon-side adapter\n * narrows this against `RunResult` from `@cursor/sdk` at the\n * call site.\n */\n result: z.unknown(),\n /**\n * Real token usage from the `turn-ended` interaction update emitted on\n * `SendOptions.onDelta`. Optional — absent when the SDK does not surface\n * usage for this run (e.g. local composer, older SDK versions). When\n * present the daemon uses real counts instead of estimated ones.\n *\n * Daemon↔forked-child IPC only — NOT the browser wire. No PROTOCOL_VERSION bump.\n */\n usage: z\n .object({\n inputTokens: z.number().int().nonnegative(),\n outputTokens: z.number().int().nonnegative(),\n cacheReadTokens: z.number().int().nonnegative(),\n cacheWriteTokens: z.number().int().nonnegative(),\n reasoningTokens: z.number().int().nonnegative().optional(),\n })\n .optional(),\n /**\n * `Run.createdAt` from `@cursor/sdk` — authoritative SDK-side run-start\n * epoch ms (run.d.ts:42). Forwarded so the daemon's per-run cost / latency\n * accounting uses the SDK clock instead of `Date.now()` at the IPC\n * boundary (which drifts by IPC + cold-build latency). Optional —\n * older SDK builds may not emit it.\n */\n createdAt: z.number().nonnegative().optional(),\n /**\n * Total tokens reported via `token-delta` InteractionUpdate during the\n * run. Complements `usage.outputTokens` — `token-delta` carries no\n * input/cache breakdown, so use this only as a fallback when `usage` is\n * absent. Optional, omitted when zero.\n */\n tokenDeltaTotal: z.number().int().nonnegative().optional(),\n }),\n z.object({\n kind: z.literal('run_error'),\n generation: z.number().int().nonnegative(),\n error: z.string(),\n /** SDK error class name (e.g. RateLimitError) for daemon-side classification. */\n errorName: z.string().optional(),\n errorCode: z.string().optional(),\n statusCode: z.number().int().optional(),\n isRetryable: z.boolean(),\n }),\n z.object({\n kind: z.literal('log'),\n level: z.enum(['info', 'warn', 'error']),\n message: z.string(),\n data: z.unknown().optional(),\n }),\n /**\n * Runner→daemon notification that the agent proposed a plan. The runner\n * sends this when the agent calls the SDK's native `createPlan` tool via\n * `onStep` in plan mode (requires `mode:'plan'` on the `init`/`reinit`\n * IPC). The subprocess (`cursor-subprocess.ts`) turns it into a\n * `plan_content_ready` SubprocessEvent, which `task-event-dispatch.ts`\n * routes to `plan-handler.ts` (writes `~/.shipyard/plans/{taskId}.md` and\n * broadcasts `plan_detected`). Supersedes the old turn-complete plan\n * accumulator that lived in `cursor-event-translator.ts`.\n */\n z.object({\n kind: z.literal('plan_proposed'),\n generation: z.number().int().nonnegative(),\n /** Markdown plan content emitted by the agent via createPlan. */\n plan: z.string(),\n }),\n /**\n * Runner→daemon notification that Cursor's server-managed conversation\n * summarization (its native \"compaction\") started / completed (B2). These\n * arrive on the `SendOptions.onDelta` interaction-update stream as\n * `SummaryStartedUpdate` / `SummaryCompletedUpdate`, NOT on the public\n * `Run.stream()` `SDKMessage` union (verified against @cursor/sdk@1.0.22 —\n * `run.d.ts` exposes only `stream()`/`conversation()`/`wait()`, and the\n * bundle routes `summary-*` updates exclusively through `onDelta`). The\n * daemon translates these into normalized `compaction_started` /\n * `compaction_completed` SubprocessEvents so the Compaction SM resets the\n * (estimated) context fill-bar after Cursor's native summarization.\n *\n * Cursor's `summary-completed` event carries no token counts, but the runner\n * has already captured the pre-compaction usage from the preceding\n * `turn-ended` interaction update (stored in `latestUsage.ref`). The runner\n * forwards that as the optional `preTokens` field so the daemon can emit a\n * real `compaction_completed.preTokens` instead of falling back to the\n * estimated `planPostCompactTurnStats` baseline.\n *\n * Daemon↔forked-child IPC only — NOT the browser wire. No PROTOCOL_VERSION bump.\n */\n z.object({\n kind: z.literal('summary_started'),\n generation: z.number().int().nonnegative(),\n }),\n z.object({\n kind: z.literal('summary_completed'),\n generation: z.number().int().nonnegative(),\n /** Total input-equivalent tokens at the moment summarization completed (inputTokens + cacheRead + cacheWrite). Absent when no turn-ended usage was captured before summary-started. */\n preTokens: z.number().int().nonnegative().optional(),\n }),\n /**\n * Runner→daemon heartbeat emitted on every `SendOptions.onStep` callback\n * (each `ConversationStep` boundary from the SDK). The daemon resets its\n * per-run stall timer on receipt so an active but slow run does not trip\n * the watchdog. Stale-generation filtering applies (same as sdk_message).\n */\n z.object({\n kind: z.literal('step_progress'),\n generation: z.number().int().nonnegative(),\n }),\n /**\n * Runner→daemon liveness heartbeat emitted (throttled in the runner) on any\n * `SendOptions.onDelta` interaction-update — `text-delta`, `thinking-delta`,\n * `shell-output-delta`, `step-started`, `tool-call-started`, etc. Unlike\n * `step_progress` (which fires only at completed `ConversationStep`\n * boundaries via `onStep`), this fires DURING a step while output streams.\n * The daemon resets the stall watchdog WITHOUT retiring the cold-start\n * window, so a long-but-active step (a 2-min shell/test run, a slow MCP\n * tool, a big read) no longer trips the mid-run watchdog while it is plainly\n * still working. Stale-generation filtering applies (same as sdk_message).\n *\n * Daemon↔forked-child IPC only — NOT the browser wire. No PROTOCOL_VERSION bump.\n */\n z.object({\n kind: z.literal('stream_activity'),\n generation: z.number().int().nonnegative(),\n }),\n /**\n * Runner→daemon notification that the SDK persisted the user turn to its\n * local store (`user-message-appended` interaction update on `onDelta`).\n * Useful as a write-confirmed ack for cross-runtime handoff durability\n * checks — the daemon knows the user message landed in the SDK's SQLite\n * checkpoint store and is recoverable via `Agent.resume`. Daemon-side\n * handler logs only for now.\n *\n * Stale-generation filtering applies.\n *\n * Daemon↔forked-child IPC only — NOT the browser wire. No\n * PROTOCOL_VERSION bump.\n */\n z.object({\n kind: z.literal('user_message_persisted'),\n generation: z.number().int().nonnegative(),\n sessionId: z.string(),\n }),\n /**\n * Runner→daemon liveness edge for in-flight tool execution. Emitted only\n * on the 0→1 transition (first tool call started, phase `'started'`) and\n * the 1→0 transition (last tool call completed, phase `'settled'`). Drives\n * the daemon's `toolExecutionStallTimeoutMs` window: when a tool is\n * in-flight the stall watchdog uses the extended per-tool budget so a\n * legitimate long-running tool (e.g. `git commit` with a full `pnpm check`\n * pre-commit hook, 3–10 min) is not false-killed. On settle the watchdog\n * reverts to the shorter mid-run window. Stale-generation filtering applies.\n *\n * Daemon↔forked-child IPC only — NOT the browser wire. No PROTOCOL_VERSION bump.\n */\n z.object({\n kind: z.literal('tool_execution'),\n generation: z.number().int().nonnegative(),\n phase: z.enum(['started', 'settled']),\n }),\n /**\n * Warm-pool reinit succeeded (C9). The runner created a fresh Agent for the\n * claimed task on the warm executor. `sessionId` is the new `agent.agentId`\n * — the daemon persists it for the next resume. No generation: reinit is a\n * session-lifecycle event, not a per-turn one.\n */\n z.object({\n kind: z.literal('reinit_ok'),\n sessionId: z.string(),\n }),\n /**\n * Warm-pool reinit failed (C9). The runner could not bring up the new\n * Agent. `retryable` is informational only — the daemon's fail-closed\n * contract is to KILL this runner and spawn a fresh cold one regardless,\n * so a broken warm process can never serve the task. The runner does NOT\n * exit on its own (it has no clean state); the daemon owns the kill.\n */\n z.object({\n kind: z.literal('reinit_error'),\n error: z.string(),\n retryable: z.boolean(),\n }),\n /**\n * Pooled-close ack (C9). A pool-managed runner received `close`, disposed\n * its per-task Agent, and reset to a quiescent state WITHOUT exiting. The\n * daemon keeps the warm process (and its executor cache) for the next\n * claim. No generation — this is a session-lifecycle event.\n */\n z.object({\n kind: z.literal('pooled_idle'),\n }),\n]);\nexport type RunnerToDaemon = z.infer<typeof RunnerToDaemonSchema>;\n\n/**\n * Parse an inbound IPC message. Returns the parsed message or `null`\n * if the payload does not match the expected schema (matching the\n * watcher-worker pattern: log and continue, never throw out of the\n * IPC handler).\n */\nexport function parseDaemonToRunner(raw: unknown): DaemonToRunner | null {\n const result = DaemonToRunnerSchema.safeParse(raw);\n return result.success ? result.data : null;\n}\n\nexport function parseRunnerToDaemon(raw: unknown): RunnerToDaemon | null {\n const result = RunnerToDaemonSchema.safeParse(raw);\n return result.success ? result.data : null;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -18864,6 +18864,23 @@ var PlanRepository = class {
|
|
|
18864
18864
|
});
|
|
18865
18865
|
});
|
|
18866
18866
|
}
|
|
18867
|
+
/**
|
|
18868
|
+
* Has a bookmark for exactly this `(timestamp, toolUseId)` already been
|
|
18869
|
+
* recorded? Same key derivation as {@link recordVersionBookmark}, so this
|
|
18870
|
+
* answers "have we already replayed this source state into this doc?".
|
|
18871
|
+
*
|
|
18872
|
+
* The backfill paths use it to stay idempotent against a doc the user
|
|
18873
|
+
* deliberately emptied. A cleared plan and a peer-wiped plan are the same CRDT
|
|
18874
|
+
* state, so the rebuild cannot tell them apart — but it CAN refuse to replay
|
|
18875
|
+
* the same source twice, which turns an unbounded resurrection loop into a
|
|
18876
|
+
* single recovery attempt. Durable by construction: bookmarks live in the
|
|
18877
|
+
* `versions` container, which the observed wipe (#5272 — a root re-init of the
|
|
18878
|
+
* `doc` container) leaves untouched.
|
|
18879
|
+
*/
|
|
18880
|
+
hasVersionBookmark(taskId, timestamp, toolUseId) {
|
|
18881
|
+
const doc2 = this.getOrCreatePlanDoc(taskId);
|
|
18882
|
+
return Object.hasOwn(doc2.toJSON().versions, `${timestamp}-${toolUseId}`);
|
|
18883
|
+
}
|
|
18867
18884
|
/**
|
|
18868
18885
|
* Markdown of the latest recorded version (any source), reconstructed from the
|
|
18869
18886
|
* frontier bookmark via `forkAt`. Returns `''` when no bookmark exists.
|
|
@@ -19045,6 +19062,34 @@ var PlanRepository = class {
|
|
|
19045
19062
|
if (!this.#storageBoundary) return null;
|
|
19046
19063
|
return this.#storageBoundary.hasDoc(buildPlanDocId(taskId, this.#epoch));
|
|
19047
19064
|
}
|
|
19065
|
+
/**
|
|
19066
|
+
* What the filesystem actually holds for this task's plan doc. `null` when no
|
|
19067
|
+
* storage boundary is injected (browser repos, in-memory tests) — the caller
|
|
19068
|
+
* then has no observation to reason from and must fall back to its
|
|
19069
|
+
* confirmed-absent behavior. Prefer this over {@link hasOnDiskDoc} whenever the
|
|
19070
|
+
* question is "why did the replica read empty?": a directory-existence boolean
|
|
19071
|
+
* cannot answer it, and guessing costs a user-visible false failure (#5266).
|
|
19072
|
+
*/
|
|
19073
|
+
async probeDocChunks(taskId) {
|
|
19074
|
+
if (!this.#storageBoundary) return null;
|
|
19075
|
+
return this.#storageBoundary.probeDocChunks(buildPlanDocId(taskId, this.#epoch));
|
|
19076
|
+
}
|
|
19077
|
+
/**
|
|
19078
|
+
* Whether this task's plan doc has imported ANY ops.
|
|
19079
|
+
*
|
|
19080
|
+
* The discriminator a content read cannot provide: an empty markdown read
|
|
19081
|
+
* collapses two opposite states into `''`. A doc whose on-disk chunks HAVE
|
|
19082
|
+
* loaded carries their whole history in its oplog even when the current content
|
|
19083
|
+
* is empty (a peer deleted the body — the #5266 doc held 6,021 ops behind an
|
|
19084
|
+
* empty paragraph), whereas a doc whose chunks have NOT loaded yet has an empty
|
|
19085
|
+
* oplog. So `chunks on disk + ops imported + no content` means durably empty and
|
|
19086
|
+
* safe to rebuild, while `chunks on disk + no ops` means the load has not landed
|
|
19087
|
+
* and rebuilding would clobber real content.
|
|
19088
|
+
*/
|
|
19089
|
+
docHasImportedOps(taskId) {
|
|
19090
|
+
const doc2 = this.getOrCreatePlanDoc(taskId);
|
|
19091
|
+
return loro(doc2).oplogFrontiers().length > 0;
|
|
19092
|
+
}
|
|
19048
19093
|
dispose() {
|
|
19049
19094
|
this.#planDocs.clear();
|
|
19050
19095
|
this.#subscriberCounts.clear();
|
|
@@ -30062,4 +30107,4 @@ export {
|
|
|
30062
30107
|
encodeDrain,
|
|
30063
30108
|
decodeFrame
|
|
30064
30109
|
};
|
|
30065
|
-
//# sourceMappingURL=chunk-
|
|
30110
|
+
//# sourceMappingURL=chunk-FC3HWVYQ.js.map
|