@schoolai/shipyard 3.29.0 → 3.30.0-rc.20260912.1
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 +5 -5
- package/dist/{chunk-HIGCNT53.js → chunk-FARMJBC7.js} +3 -3
- package/dist/chunk-FARMJBC7.js.map +1 -0
- package/dist/{chunk-AIN32UWK.js → chunk-FWL2ZIEL.js} +31 -2
- package/dist/chunk-FWL2ZIEL.js.map +1 -0
- package/dist/{chunk-IJGA4YA3.js → chunk-JQFAXWT5.js} +3 -3
- package/dist/chunk-JQFAXWT5.js.map +1 -0
- package/dist/{chunk-ASK7AIJM.js → chunk-KFCIZHQW.js} +15 -3
- package/dist/{chunk-ASK7AIJM.js.map → chunk-KFCIZHQW.js.map} +1 -1
- package/dist/{chunk-3NS2YSRP.js → chunk-NNNYGFSQ.js} +35 -8
- package/dist/{chunk-3NS2YSRP.js.map → chunk-NNNYGFSQ.js.map} +1 -1
- package/dist/{chunk-SR3T3EYE.js → chunk-RGVTFZDF.js} +3 -3
- package/dist/{chunk-SMGVXANE.js → chunk-SGFCXJJJ.js} +2 -2
- package/dist/{chunk-CDOIPKD6.js → chunk-VE2ZAUIM.js} +2 -2
- package/dist/{chunk-J5MLXWO5.js → chunk-WO5EM3BP.js} +2 -2
- package/dist/{chunk-CB35TLO4.js → chunk-ZDJEJI62.js} +2 -2
- package/dist/cursor-runner.js +81 -47
- package/dist/cursor-runner.js.map +1 -1
- package/dist/electron-utility.js +2 -2
- package/dist/index.js +4 -4
- package/dist/{login-D23INMXJ.js → login-6YTDDUP4.js} +3 -3
- package/dist/{plan-backfill-7OALHIQK.js → plan-backfill-5UGECNV6.js} +4 -4
- package/dist/{serve-VBMV5LEN.js → serve-ZMQ2HHQQ.js} +2433 -1210
- package/dist/{serve-VBMV5LEN.js.map → serve-ZMQ2HHQQ.js.map} +1 -1
- package/dist/{skills-FSYFQ6FY.js → skills-BX2NZKPN.js} +3 -3
- package/dist/{start-NNW7R4M2.js → start-E24NNOMX.js} +6 -6
- package/package.json +1 -1
- package/dist/chunk-AIN32UWK.js.map +0 -1
- package/dist/chunk-HIGCNT53.js.map +0 -1
- package/dist/chunk-IJGA4YA3.js.map +0 -1
- /package/dist/{chunk-SR3T3EYE.js.map → chunk-RGVTFZDF.js.map} +0 -0
- /package/dist/{chunk-SMGVXANE.js.map → chunk-SGFCXJJJ.js.map} +0 -0
- /package/dist/{chunk-CDOIPKD6.js.map → chunk-VE2ZAUIM.js.map} +0 -0
- /package/dist/{chunk-J5MLXWO5.js.map → chunk-WO5EM3BP.js.map} +0 -0
- /package/dist/{chunk-CB35TLO4.js.map → chunk-ZDJEJI62.js.map} +0 -0
- /package/dist/{login-D23INMXJ.js.map → login-6YTDDUP4.js.map} +0 -0
- /package/dist/{plan-backfill-7OALHIQK.js.map → plan-backfill-5UGECNV6.js.map} +0 -0
- /package/dist/{skills-FSYFQ6FY.js.map → skills-BX2NZKPN.js.map} +0 -0
- /package/dist/{start-NNW7R4M2.js.map → start-E24NNOMX.js.map} +0 -0
|
@@ -168,6 +168,8 @@ var DaemonToRunnerSchema = external_exports.discriminatedUnion("kind", [
|
|
|
168
168
|
* the spawn succeeds — just without prior context.
|
|
169
169
|
*/
|
|
170
170
|
resumeAgentId: external_exports.string().optional(),
|
|
171
|
+
/** Context omitted for a known resume, but required if resume falls back to create. */
|
|
172
|
+
resumeFallbackSystemContext: external_exports.string().optional(),
|
|
171
173
|
/**
|
|
172
174
|
* Initial Fast Mode flag from `composerSettings.fastMode`. When
|
|
173
175
|
* `fastModeParam` is non-null the runner always attaches
|
|
@@ -295,6 +297,8 @@ var DaemonToRunnerSchema = external_exports.discriminatedUnion("kind", [
|
|
|
295
297
|
skillsDir: external_exports.string(),
|
|
296
298
|
generation: external_exports.number().int().nonnegative(),
|
|
297
299
|
resumeAgentId: external_exports.string().optional(),
|
|
300
|
+
/** Context omitted for a known resume, but required if resume falls back to create. */
|
|
301
|
+
resumeFallbackSystemContext: external_exports.string().optional(),
|
|
298
302
|
fastMode: external_exports.boolean().optional(),
|
|
299
303
|
fastModeParam: external_exports.object({ id: external_exports.string(), value: external_exports.string() }).nullable().optional(),
|
|
300
304
|
agents: external_exports.record(
|
|
@@ -528,6 +532,10 @@ var RunnerToDaemonSchema = external_exports.discriminatedUnion("kind", [
|
|
|
528
532
|
generation: external_exports.number().int().nonnegative(),
|
|
529
533
|
sessionId: external_exports.string()
|
|
530
534
|
}),
|
|
535
|
+
external_exports.object({
|
|
536
|
+
kind: external_exports.literal("initial_system_context_attached"),
|
|
537
|
+
generation: external_exports.number().int().nonnegative()
|
|
538
|
+
}),
|
|
531
539
|
/**
|
|
532
540
|
* Runner→daemon liveness edge for in-flight tool execution. Emitted only
|
|
533
541
|
* on the 0→1 transition (first tool call started, phase `'started'`) and
|
|
@@ -586,12 +594,33 @@ function parseRunnerToDaemon(raw) {
|
|
|
586
594
|
return result.success ? result.data : null;
|
|
587
595
|
}
|
|
588
596
|
|
|
597
|
+
// src/services/session/cursor-system-context.ts
|
|
598
|
+
import { createHash } from "crypto";
|
|
599
|
+
var CURSOR_SYSTEM_CONTEXT_FORMAT_VERSION = 1;
|
|
600
|
+
function cursorSystemContextDigest(context) {
|
|
601
|
+
return createHash("sha256").update(`${CURSOR_SYSTEM_CONTEXT_FORMAT_VERSION}\0${context}`).digest("hex");
|
|
602
|
+
}
|
|
603
|
+
function prependCursorSystemContext(content, initialSystemContext) {
|
|
604
|
+
if (initialSystemContext === null) return content;
|
|
605
|
+
return [
|
|
606
|
+
{
|
|
607
|
+
type: "text",
|
|
608
|
+
text: `<shipyard-system-context>
|
|
609
|
+
${initialSystemContext}
|
|
610
|
+
</shipyard-system-context>`
|
|
611
|
+
},
|
|
612
|
+
...content
|
|
613
|
+
];
|
|
614
|
+
}
|
|
615
|
+
|
|
589
616
|
export {
|
|
590
617
|
countLoopbackHttpEntries,
|
|
591
618
|
buildCursorMcpConfigFromSet,
|
|
592
619
|
buildCursorFastModeModelOption,
|
|
593
620
|
shouldUseCursorFastModePricing,
|
|
594
621
|
parseDaemonToRunner,
|
|
595
|
-
parseRunnerToDaemon
|
|
622
|
+
parseRunnerToDaemon,
|
|
623
|
+
cursorSystemContextDigest,
|
|
624
|
+
prependCursorSystemContext
|
|
596
625
|
};
|
|
597
|
-
//# sourceMappingURL=chunk-
|
|
626
|
+
//# sourceMappingURL=chunk-FWL2ZIEL.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","../src/services/session/cursor-system-context.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 /** Context omitted for a known resume, but required if resume falls back to create. */\n resumeFallbackSystemContext: 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 /** Context omitted for a known resume, but required if resume falls back to create. */\n resumeFallbackSystemContext: 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 z.object({\n kind: z.literal('initial_system_context_attached'),\n generation: z.number().int().nonnegative(),\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","import { createHash } from 'node:crypto';\nimport type { ContentBlock } from '@shipyard/loro-schema';\n\nconst CURSOR_SYSTEM_CONTEXT_FORMAT_VERSION = 1;\n\n/** Versioned identity for the context Cursor accepted into a persistent agent. */\nexport function cursorSystemContextDigest(context: string): string {\n return createHash('sha256')\n .update(`${CURSOR_SYSTEM_CONTEXT_FORMAT_VERSION}\\0${context}`)\n .digest('hex');\n}\n\n/** Wrap task-scoped context as an explicit first-turn content block for Cursor. */\nexport function prependCursorSystemContext(\n content: ContentBlock[],\n initialSystemContext: string | null\n): ContentBlock[] {\n if (initialSystemContext === null) return content;\n return [\n {\n type: 'text',\n text: `<shipyard-system-context>\\n${initialSystemContext}\\n</shipyard-system-context>`,\n },\n ...content,\n ];\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
isImageMimeType,
|
|
4
4
|
parseAssetUri
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-KFCIZHQW.js";
|
|
6
6
|
import {
|
|
7
7
|
logger
|
|
8
8
|
} from "./chunk-FIE2FRIW.js";
|
|
@@ -334,7 +334,7 @@ function formatTaskFeedback(block) {
|
|
|
334
334
|
}
|
|
335
335
|
lines.push(`</${XML_TAGS.TASK_UPDATE}>`);
|
|
336
336
|
lines.push(
|
|
337
|
-
`${block.authorName} updated your shared work plan.
|
|
337
|
+
`${block.authorName} updated your shared work plan. Use Shipyard's task snapshot when you need the current task list.`
|
|
338
338
|
);
|
|
339
339
|
return lines.join("\n").trimEnd();
|
|
340
340
|
}
|
|
@@ -588,4 +588,4 @@ export {
|
|
|
588
588
|
toRecord,
|
|
589
589
|
buildCursorUserPrompt
|
|
590
590
|
};
|
|
591
|
-
//# sourceMappingURL=chunk-
|
|
591
|
+
//# sourceMappingURL=chunk-JQFAXWT5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/asset-resolver.ts","../src/services/session/feedback-formatters.ts","../src/shared/xml-tag-names.ts","../src/services/session/sdk-content-builder.ts","../src/services/session/cursor-content-builder.ts"],"sourcesContent":["export { ASSET_PATH_META_KEY, createAssetResolver };\n\nimport type { BlobResourceContents, TextResourceContents } from '@shipyard/loro-schema';\nimport { parseAssetUri } from '@shipyard/loro-schema';\n\nimport type { SubResolver } from './shipyard-resolver.js';\nimport type { AssetStore } from './storage/asset-store.js';\n\n/**\n * `_meta` key used to carry the asset's absolute on-disk path through the\n * resolution pipeline. The SDK content builder reads this to emit a sibling\n * `[Saved at <path>]` text block so the agent can Read/Edit the file via\n * its filesystem tools (#2943). Single source of truth — string typo on\n * either side silently breaks the path injection.\n */\nconst ASSET_PATH_META_KEY = 'shipyard.assetPath';\n\n/**\n * MIME-type prefixes/values that are safe to decode as UTF-8 text. Anything\n * not on this list (binaries, audio, video, font, etc.) is returned as a\n * BlobResourceContents so the existing image-or-binary fallback in\n * `convertResourceBlock` handles it. Kept narrow on purpose — the daemon\n * decodes the bytes only when it has high confidence the result will be\n * meaningful to a coding agent. PROTOCOL_VERSION 109.\n */\nconst TEXT_MIME_PREFIXES = ['text/', 'application/json', 'application/xml'];\nconst TEXT_MIME_EXACT = new Set([\n 'application/javascript',\n 'application/x-yaml',\n 'application/yaml',\n 'application/toml',\n 'application/x-toml',\n 'application/sql',\n 'application/x-sh',\n]);\n\nfunction isTextMime(mimeType: string): boolean {\n if (TEXT_MIME_EXACT.has(mimeType)) return true;\n for (const prefix of TEXT_MIME_PREFIXES) {\n if (mimeType.startsWith(prefix)) return true;\n }\n return false;\n}\n\nfunction createAssetResolver(assetStore: AssetStore): SubResolver {\n return {\n async resolve(uri: string): Promise<TextResourceContents | BlobResourceContents> {\n const assetId = parseAssetUri(uri);\n if (!assetId) {\n throw new Error(`Not an asset URI: ${uri}`);\n }\n\n /**\n * For text MIME types we return TextResourceContents so the SDK\n * content builder inlines the file content directly into the user\n * turn. For everything else we return BlobResourceContents and let\n * the existing image / binary fallback in `convertResourceBlock`\n * decide whether to inline (images) or emit a placeholder (other\n * binaries). This is the load-bearing change that makes PR-D3's\n * \"drop a 1MB .log file → agent sees the content\" path work.\n */\n const meta = await assetStore.getMeta(assetId);\n if (meta && isTextMime(meta.mimeType)) {\n const { data } = await assetStore.read(assetId);\n return {\n uri,\n mimeType: meta.mimeType,\n text: data.toString('utf-8'),\n _meta: { [ASSET_PATH_META_KEY]: assetStore.pathFor(assetId) },\n };\n }\n\n const { base64, meta: readMeta } = await assetStore.readBase64(assetId);\n return {\n uri,\n mimeType: readMeta.mimeType,\n blob: base64,\n _meta: { [ASSET_PATH_META_KEY]: assetStore.pathFor(assetId) },\n };\n },\n };\n}\n","import type { AnnotationZod } from '@shipyard/loro-schema';\n\nexport function formatDiffReviewFeedback(\n comments: Array<{\n filePath: string;\n lineNumber: number;\n lineContent: string;\n side: string;\n body: string;\n }>\n): string {\n const lines: string[] = ['## Code Review Feedback', ''];\n const byFile = new Map<string, typeof comments>();\n for (const c of comments) {\n const arr = byFile.get(c.filePath);\n if (arr) arr.push(c);\n else byFile.set(c.filePath, [c]);\n }\n for (const [filePath, fileComments] of byFile) {\n lines.push(`### \\`${filePath}\\``);\n for (const c of fileComments) {\n const sideLabel = c.side === 'original' ? ' (before change)' : '';\n lines.push(`**Line ${c.lineNumber}${sideLabel}:** \\`${c.lineContent}\\``);\n lines.push(`> ${c.body}`);\n lines.push('');\n }\n }\n return lines.join('\\n').trimEnd();\n}\n\nexport interface AnnotationComment {\n url: string;\n elementSelector: string;\n elementText: string;\n body: string;\n authorName: string;\n componentName?: string | null;\n sourceFile?: string | null;\n sourceLine?: number | null;\n componentTree?: string[] | null;\n framework?: string | null;\n computedStyles?: Record<string, string> | null;\n cssClasses?: string[] | null;\n}\n\nexport function formatAnnotationCommentHeading(c: AnnotationComment): string {\n if (c.componentName) {\n const loc = c.sourceFile ? ` (${c.sourceFile}${c.sourceLine ? `:${c.sourceLine}` : ''})` : '';\n return `**Component \\`<${c.componentName} />\\`${loc}:**`;\n }\n const textLabel = c.elementText ? ` \"${c.elementText.slice(0, 60)}\"` : '';\n return `**Element \\`${c.elementSelector}\\`${textLabel}:**`;\n}\n\nexport function formatAnnotationCommentMetadata(c: AnnotationComment): string[] {\n const lines: string[] = [];\n if (c.componentTree && c.componentTree.length > 1) {\n lines.push(`Component tree: ${c.componentTree.join(' → ')}`);\n }\n if (c.cssClasses && c.cssClasses.length > 0) {\n lines.push(`CSS classes: \\`${c.cssClasses.join(' ')}\\``);\n }\n if (c.computedStyles && Object.keys(c.computedStyles).length > 0) {\n const styleEntries = Object.entries(c.computedStyles)\n .map(([k, v]) => `${k}: ${v}`)\n .join('; ');\n lines.push(`Computed styles: \\`${styleEntries}\\``);\n }\n return lines;\n}\n\nexport function formatPreviewAnnotationFeedback(comments: AnnotationComment[]): string {\n const lines: string[] = ['## Preview Annotation Feedback', ''];\n const byUrl = new Map<string, AnnotationComment[]>();\n for (const c of comments) {\n const arr = byUrl.get(c.url);\n if (arr) arr.push(c);\n else byUrl.set(c.url, [c]);\n }\n for (const [url, urlComments] of byUrl) {\n lines.push(`### ${url}`);\n for (const c of urlComments) {\n lines.push(formatAnnotationCommentHeading(c));\n lines.push(...formatAnnotationCommentMetadata(c));\n lines.push(`> ${c.body} — _${c.authorName}_`);\n lines.push('');\n }\n }\n return lines.join('\\n').trimEnd();\n}\n\nexport function formatDiffHunkAnnotation(\n a: Extract<AnnotationZod, { annotationType: 'diff-hunk' }>\n): string[] {\n const sideLabel = a.side === 'original' ? ' (before change)' : '';\n return [\n `### \\`${a.filePath}\\``,\n `**Line ${a.lineNumber}${sideLabel}:** \\`${a.lineContent}\\``,\n `> ${a.body} — _${a.authorName}_`,\n '',\n ];\n}\n\nexport function formatPreviewAnnotation(\n a: Extract<AnnotationZod, { annotationType: 'preview-element' }>\n): string[] {\n const heading = a.componentName\n ? `**Component \\`<${a.componentName} />\\`${a.sourceFile ? ` (${a.sourceFile}${a.sourceLine ? `:${a.sourceLine}` : ''})` : ''}:**`\n : `**Element \\`${a.elementSelector}\\`${a.elementText ? ` \"${a.elementText.slice(0, 60)}\"` : ''}:**`;\n return [`### ${a.url}`, heading, `> ${a.body} — _${a.authorName}_`, ''];\n}\n\nexport function formatPlanTextAnnotation(\n a: Extract<AnnotationZod, { annotationType: 'plan-text' }>\n): string[] {\n return [`**Plan text:** \"${a.anchorText.slice(0, 80)}\"`, `> ${a.body} — _${a.authorName}_`, ''];\n}\n\nexport function formatCanvasElementAnnotation(\n a: Extract<AnnotationZod, { annotationType: 'canvas-element' }>\n): string[] {\n return [\n `**Canvas ${a.elementType} element (${a.canvasElementId}):**`,\n `> ${a.body} — _${a.authorName}_`,\n '',\n ];\n}\n\nexport function formatCanvasSubElementAnnotation(\n a: Extract<AnnotationZod, { annotationType: 'canvas-sub-element' }>\n): string[] {\n const lines = [\n `**Canvas ${a.elementType} sub-element <${a.tagName}> (${a.canvasElementId}):**`,\n `Selector: ${a.elementSelector}`,\n ];\n if (a.elementText) lines.push(`Text: ${a.elementText.slice(0, 120)}`);\n if (a.cssClasses) lines.push(`Classes: ${a.cssClasses}`);\n if (a.computedStyles) lines.push(`Styles: ${a.computedStyles}`);\n if (a.elementHtml) lines.push(`HTML: ${a.elementHtml.slice(0, 500)}`);\n lines.push(`> ${a.body} — _${a.authorName}_`, '');\n return lines;\n}\n\nexport function formatAnnotationFeedback(annotations: AnnotationZod[]): string {\n const lines: string[] = ['## Annotation Feedback', ''];\n for (const a of annotations) {\n /**\n * Thread replies carry their ids so the agent answers in-thread via\n * `add_comment` mode \"reply\" instead of opening a new root comment.\n * Root comments keep the id-less format the agent already navigates\n * via embedded `<comment>` children and `list_comments`.\n */\n if (a.parentCommentId !== null) {\n lines.push(\n `**Reply in an existing comment thread** (commentId: ${a.commentId}, parentCommentId: ${a.parentCommentId}). Read the thread with \\`read_comment_thread\\` and respond in-thread with \\`add_comment\\` mode \"reply\".`,\n ''\n );\n }\n switch (a.annotationType) {\n case 'diff-hunk':\n lines.push(...formatDiffHunkAnnotation(a));\n break;\n case 'preview-element':\n lines.push(...formatPreviewAnnotation(a));\n break;\n case 'plan-text':\n lines.push(...formatPlanTextAnnotation(a));\n break;\n case 'canvas-element':\n lines.push(...formatCanvasElementAnnotation(a));\n break;\n case 'canvas-sub-element':\n lines.push(...formatCanvasSubElementAnnotation(a));\n break;\n default: {\n const _exhaustive: never = a;\n void _exhaustive;\n }\n }\n }\n return lines.join('\\n').trimEnd();\n}\n","/**\n * Shared XML tag name constants.\n *\n * Both the system prompt and production code that generates/parses\n * these tags import from here -- single source of truth.\n */\n\nexport const XML_TAGS = {\n TASK_UPDATE: 'task-update',\n TASK_LIST: 'task-list',\n TASK_LIST_UPDATE: 'task-list-update',\n TEMPLATE: 'template',\n PLAN_UPDATE: 'plan-update',\n PLAN_REVIEW: 'plan-review',\n COLLAB_MESSAGES: 'collab-messages',\n COLLAB_MESSAGE: 'collab-message',\n THREAD: 'thread',\n THREAD_UPDATE: 'thread-update',\n THREADS: 'threads',\n THREAD_ENTRY: 'thread-entry',\n TASK_METADATA: 'task-metadata',\n TASK_METADATA_UPDATE: 'task-metadata-update',\n CURRENT_TIME: 'current-time',\n BRANCH: 'branch',\n BRANCH_UPDATE: 'branch-update',\n PR: 'pr',\n WORKING_TREE: 'working-tree',\n BRANCH_COMMENTS: 'branch-comments',\n STACK: 'stack',\n CANVAS_STATE: 'canvas-state',\n CANVAS_STATE_UPDATE: 'canvas-state-update',\n COMMENT: 'comment',\n CI: 'ci',\n REVIEWERS: 'reviewers',\n DEV_SERVERS: 'dev-servers',\n DEV_SERVER: 'dev-server',\n TERMINALS: 'terminals',\n TERMINAL: 'terminal',\n} as const;\n","import type { PermissionMode, SDKUserMessage } from '@anthropic-ai/claude-agent-sdk';\nimport {\n type ContentBlock,\n isImageMimeType,\n type PermissionMode as SchemaPermissionMode,\n type SkillInvocationBlock,\n type TaskFeedbackBlock,\n type TaskFeedbackChange,\n} from '@shipyard/loro-schema';\n\nimport { logger } from '../../shared/logger.js';\nimport { XML_TAGS } from '../../shared/xml-tag-names.js';\nimport { ASSET_PATH_META_KEY } from '../asset-resolver.js';\n\nimport {\n formatAnnotationFeedback,\n formatDiffReviewFeedback,\n formatPreviewAnnotationFeedback,\n} from './feedback-formatters.js';\n\n/**\n * Maps a permission mode string (kebab-case from loro-schema) to SDK PermissionMode (camelCase).\n * Unknown values fall through as 'default'.\n */\nexport function toSdkPermissionMode(mode: SchemaPermissionMode): PermissionMode {\n switch (mode) {\n case 'default':\n return 'default';\n case 'accept-edits':\n return 'acceptEdits';\n case 'plan':\n return 'plan';\n case 'bypass':\n return 'bypassPermissions';\n case 'auto':\n return 'auto';\n default: {\n const _exhaustive: never = mode;\n throw new Error(`Unhandled permission mode: ${_exhaustive}`);\n }\n }\n}\n\nexport type SdkImageMediaType = 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp';\n\nexport function isSdkImageMediaType(s: string): s is SdkImageMediaType {\n return isImageMimeType(s);\n}\n\n/**\n * Anthropic API image guards (#3188). Oversized images return a 400 that\n * bricks the conversation; we replace them with a text marker so the user\n * turn survives. Per Invariant #20, transform — never empty.\n * https://docs.anthropic.com/en/docs/build-with-claude/vision\n */\nexport const MAX_IMAGE_BYTES = 5 * 1024 * 1024;\nexport const MAX_IMAGE_DIMENSION = 8000;\n\nexport type ImageValidationResult = { ok: true } | { ok: false; reason: string };\n\nexport function validateImageBlock(input: {\n mediaType: SdkImageMediaType;\n base64Data: string;\n}): ImageValidationResult {\n const bytes = decodedByteLength(input.base64Data);\n if (bytes > MAX_IMAGE_BYTES) {\n const mb = (bytes / 1024 / 1024).toFixed(1);\n const limitMb = (MAX_IMAGE_BYTES / 1024 / 1024).toFixed(0);\n return { ok: false, reason: `${mb}MB exceeds ${limitMb}MB limit` };\n }\n const dims = readImageDimensions(input.mediaType, input.base64Data);\n if (dims && (dims.width > MAX_IMAGE_DIMENSION || dims.height > MAX_IMAGE_DIMENSION)) {\n return {\n ok: false,\n reason: `${dims.width}x${dims.height}px exceeds ${MAX_IMAGE_DIMENSION}x${MAX_IMAGE_DIMENSION}px limit`,\n };\n }\n return { ok: true };\n}\n\nfunction decodedByteLength(base64: string): number {\n const padding = base64.endsWith('==') ? 2 : base64.endsWith('=') ? 1 : 0;\n return Math.floor((base64.length * 3) / 4) - padding;\n}\n\nfunction readImageDimensions(\n mediaType: SdkImageMediaType,\n base64: string\n): { width: number; height: number } | null {\n /** PNG/GIF only — JPEG/WebP fall back to byte-size checks. */\n if (mediaType !== 'image/png' && mediaType !== 'image/gif') return null;\n const head = Buffer.from(base64.slice(0, 64), 'base64');\n if (mediaType === 'image/png') {\n if (head.length < 24) return null;\n return { width: head.readUInt32BE(16), height: head.readUInt32BE(20) };\n }\n if (head.length < 10) return null;\n return { width: head.readUInt16LE(6), height: head.readUInt16LE(8) };\n}\n\nexport type SdkContentItem =\n | { type: 'text'; text: string }\n | { type: 'image'; source: { type: 'base64'; media_type: SdkImageMediaType; data: string } };\n\n/**\n * Read the asset's absolute on-disk path from a resource block's `_meta`,\n * injected by `asset-resolver.ts` at resolve time. Returns null when the key\n * is absent (non-asset resources) or non-string (defensive — `_meta` values\n * are typed as `unknown`).\n */\nfunction readAssetPath(meta: Record<string, unknown> | undefined): string | null {\n if (!meta) return null;\n const value = meta[ASSET_PATH_META_KEY];\n return typeof value === 'string' ? value : null;\n}\n\nexport function convertResourceBlock(block: ContentBlock & { type: 'resource' }): SdkContentItem[] {\n if (\n 'blob' in block.resource &&\n block.resource.mimeType &&\n isSdkImageMediaType(block.resource.mimeType)\n ) {\n const mediaType = block.resource.mimeType;\n const data = block.resource.blob;\n /**\n * Drop oversized images with a text marker rather than letting them brick\n * the conversation with a 400 from the Anthropic API (#3188). Per Invariant\n * #20, we transform, never empty — the user turn must stay alive.\n */\n const validation = validateImageBlock({ mediaType, base64Data: data });\n if (!validation.ok) {\n logger.warn(\n { event: 'image_block_dropped', uri: block.resource.uri, reason: validation.reason },\n 'oversized image dropped before sending to Anthropic API'\n );\n return [{ type: 'text', text: `[image omitted: ${validation.reason}]` }];\n }\n const image: SdkContentItem = {\n type: 'image',\n source: { type: 'base64', media_type: mediaType, data },\n };\n /**\n * For Shipyard assets (URI scheme `shipyard://asset/`), append the\n * absolute on-disk path so the agent can Read/Edit/reference the file\n * via filesystem tools instead of being stuck with the inline base64\n * (#2943). Path is sourced from `_meta` populated by asset-resolver.ts.\n */\n const assetPath = readAssetPath(block.resource._meta);\n if (assetPath) {\n return [image, { type: 'text', text: `[Saved at ${assetPath}]` }];\n }\n return [image];\n }\n if ('text' in block.resource) {\n return [{ type: 'text', text: block.resource.text }];\n }\n return [{ type: 'text', text: `[Binary resource: ${block.resource.uri}]` }];\n}\n\nexport function formatTaskFeedbackChange(change: TaskFeedbackChange): string {\n switch (change.changeType) {\n case 'status_changed':\n return `- \\u2713 \"${change.subject}\" \\u2014 ${change.newStatus ?? 'updated'}`;\n case 'task_added':\n return `- + \"${change.subject}\" \\u2014 added`;\n case 'task_removed':\n return `- \\u2717 \"${change.subject}\" \\u2014 removed`;\n case 'dep_added':\n return `- \\uD83D\\uDD17 \"${change.subject}\" \\u2014 blocked by \"${change.depSubject ?? 'dependency'}\"`;\n case 'dep_removed':\n return `- \\uD83D\\uDD13 \"${change.subject}\" \\u2014 unblocked from \"${change.depSubject ?? 'dependency'}\"`;\n default: {\n const _exhaustive: never = change.changeType;\n return `- Unknown change: ${String(_exhaustive)}`;\n }\n }\n}\n\nexport function formatTaskFeedback(block: TaskFeedbackBlock): string {\n const lines = [\n `<${XML_TAGS.TASK_UPDATE} author=\"${block.authorName}\" count=\"${block.changes.length}\">`,\n ];\n for (const change of block.changes) {\n lines.push(formatTaskFeedbackChange(change));\n }\n lines.push(`</${XML_TAGS.TASK_UPDATE}>`);\n lines.push(\n `${block.authorName} updated your shared work plan. Use Shipyard's task snapshot when you need the current task list.`\n );\n return lines.join('\\n').trimEnd();\n}\n\n/** Format a file_attachment block as a text snippet for SDK context injection. */\nfunction formatFileAttachment(filename: string, preview: string | undefined): string {\n return preview ? `[File: ${filename}]\\n${preview}` : `[File attached: ${filename}]`;\n}\n\n/**\n * The slash token the user typed for a skill chip (`/qa`, `/plugin:qa`).\n * Claude Code resolves skills natively from this token; the sigil is always\n * `/` (claude-code-profile.ts `skillInvocationSigil`).\n */\nfunction skillToken(block: SkillInvocationBlock): string {\n return block.namespace ? `/${block.namespace}:${block.name}` : `/${block.name}`;\n}\n\n/**\n * #4197 rescue: surface skill chips' slash tokens when a turn would otherwise\n * be empty (e.g. a lone unreachable skill), so a skill-only send is never\n * dropped (Invariant #20). Fires only when nothing else was emitted, uniform\n * with the Cursor/Codex builders' fallbacks.\n */\nfunction skillTokenFallback(blocks: ContentBlock[]): SdkContentItem[] {\n const out: SdkContentItem[] = [];\n for (const block of blocks) {\n if (block.type === 'skill_invocation') out.push({ type: 'text', text: skillToken(block) });\n }\n return out;\n}\n\n/**\n * Emit a skill chip as its slash token so Claude Code resolves the skill\n * natively. The composer STRIPS the `/qa` sigil from the surrounding text\n * (interleaveSkillBlocks), so this chip is the ONLY carrier — skipping it\n * dropped skill-only sends (null short-circuit per Invariant #20) and lost the\n * invocation in paired sends too (#4197).\n *\n * The three builders share a CONCEPTUAL contract — not a literal one: carry a\n * reachable skill in the provider's native idiom (Claude: this slash token;\n * Cursor: the resolved SKILL.md body, else the token; Codex: a native `skill`\n * input), skip `unreachable`, and rescue an otherwise-empty turn with the bare\n * token (`skillTokenFallback`). Claude skips unreachable here because the\n * `skills` allowlist (sdk-filter) already excludes those skills, so a stray\n * token would never resolve.\n */\nfunction appendSkillToken(result: SdkContentItem[], block: SkillInvocationBlock): void {\n if (block.bodyResolution !== 'unreachable') {\n result.push({ type: 'text', text: skillToken(block) });\n }\n}\n\n/**\n * Collapse the converted items into the SDK content shape. Rescues an\n * otherwise-empty turn that carried skill chips (#4197), returns `null` for a\n * genuinely empty turn or a lone empty-text block (Invariant #20), and\n * collapses a single text item to the bare string the controller expects.\n * Pure — does not mutate the caller's `result`.\n */\nfunction finalizeSdkContent(\n result: SdkContentItem[],\n blocks: ContentBlock[]\n): SDKUserMessage['message']['content'] | null {\n const items = result.length === 0 ? skillTokenFallback(blocks) : result;\n if (items.length === 0) return null;\n if (items.length === 1 && items[0]?.type === 'text') {\n return items[0].text === '' ? null : items[0].text;\n }\n return items;\n}\n\n/**\n * Convert ContentBlock[] to the SDK's message content format.\n * Text and resolved image resources are meaningful as user input.\n *\n * INVARIANT — empty user content must never reach the SDK controller as\n * a single empty-text block. Returns `null` when blocks are entirely\n * silent-skip types (tool_use, thinking, review_decision,\n * compaction_boundary, ...), when the input is empty, or when the only\n * remaining block is a single empty-text block. The Anthropic API\n * rejects messages whose text content blocks are empty strings\n * (`messages: text content blocks must be non-empty`), so callers MUST\n * treat `null` as \"do not push\" and short-circuit. The paired guard\n * lives in `anthropic-adapter.ts` at both the init `controller.push`\n * site and `pushMessage`. Guard first added in #2935 and reinforced in\n * #2954 after the same bug fired on a different code path. The structural\n * answer: signal \"empty\" distinctly (null/[]) from \"non-empty result\" at\n * the conversion function, AND guard at every wire-emit chokepoint.\n */\nexport function toSdkContent(blocks: ContentBlock[]): SDKUserMessage['message']['content'] | null {\n const result: SdkContentItem[] = [];\n\n for (const block of blocks) {\n switch (block.type) {\n case 'text':\n if (block.text !== '') result.push({ type: 'text', text: block.text });\n break;\n case 'diff_review_feedback':\n result.push({ type: 'text', text: formatDiffReviewFeedback(block.comments) });\n break;\n case 'preview_annotation_feedback':\n result.push({ type: 'text', text: formatPreviewAnnotationFeedback(block.comments) });\n break;\n case 'annotation_feedback':\n result.push({ type: 'text', text: formatAnnotationFeedback(block.annotations) });\n break;\n case 'task_feedback':\n result.push({ type: 'text', text: formatTaskFeedback(block) });\n break;\n case 'tool_use':\n case 'tool_result':\n case 'thinking':\n case 'review_decision':\n /** Legacy tombstone — historical JSONL may carry it; skip rendering. */\n case 'review_comment':\n case 'compaction_boundary':\n break;\n /** Skill chip → slash token text (see `appendSkillToken`, #4197). */\n case 'skill_invocation':\n appendSkillToken(result, block);\n break;\n /** File attachment — preview as text; full asset-transfer wiring is follow-up work. */\n case 'file_attachment':\n result.push({ type: 'text', text: formatFileAttachment(block.filename, block.preview) });\n break;\n case 'resource_link': {\n const desc = block.description ? ` - ${block.description}` : '';\n result.push({ type: 'text', text: `[Resource: ${block.name}${desc}](${block.uri})` });\n break;\n }\n case 'resource': {\n result.push(...convertResourceBlock(block));\n break;\n }\n default: {\n const _exhaustive: never = block;\n throw new Error(`Unhandled content block type: ${JSON.stringify(_exhaustive)}`);\n }\n }\n }\n\n return finalizeSdkContent(result, blocks);\n}\n\nexport function toRecord(value: unknown): Record<string, unknown> {\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n return value as never;\n }\n return {};\n}\n","/**\n * Cursor content builder.\n *\n * Pure FC/IS module: translates Shipyard's canonical `ContentBlock[]` into\n * Cursor SDK's native user-input shape, and parses Cursor's native assistant\n * content back into `ContentBlock[]`.\n *\n * Design invariants:\n * - No I/O. No async. No side effects beyond logging unsupported block types.\n * - Exhaustive switch over all `ContentBlock` variants.\n * - Image blocks are inlined as `SDKImage[]` via base64 (Cursor SDK 1.0.13\n * URL variant is rejected by `getLocalRunImages` for local agents — must\n * use data+mimeType form).\n *\n * Cursor SDK user-input surface:\n * `agent.send(prompt: string | SDKUserMessage)`\n * For text-only turns, we pass a plain string (documented hot path).\n * For turns with images, we pass `{ text, images: SDKImage[] }`.\n * `SDKUserMessage.text` is required-non-empty; empty text with images\n * falls back to `'(image only)'`.\n *\n * Feedback blocks (diff/preview/annotation/task) are formatted with the same\n * formatters the Claude path uses so the agent sees identical surface text.\n */\n\nimport type { SDKImage } from '@cursor/sdk';\nimport type { ContentBlock, SkillInvocationBlock } from '@shipyard/loro-schema';\nimport { assertNever } from '../../shared/assert-never.js';\nimport { ASSET_PATH_META_KEY } from '../asset-resolver.js';\nimport type { SDKContentBlock } from './cursor-event-translator.js';\nimport {\n formatAnnotationFeedback,\n formatDiffReviewFeedback,\n formatPreviewAnnotationFeedback,\n} from './feedback-formatters.js';\nimport {\n formatTaskFeedback,\n isSdkImageMediaType,\n validateImageBlock,\n} from './sdk-content-builder.js';\n\nexport type CursorContentBuilderLogger = (entry: { event: string; [key: string]: unknown }) => void;\n\n/**\n * The output shape for `buildCursorUserPrompt`. Runners with images use\n * `SDKUserMessage`; runners without use the string shorthand.\n */\nexport interface CursorUserInput {\n text: string;\n images: SDKImage[];\n}\n\nexport interface CursorResolvedSkillBody {\n name: string;\n namespace?: string;\n body: string;\n}\n\nexport interface CursorPromptBuildOptions {\n skillBodies?: readonly CursorResolvedSkillBody[];\n}\n\n/**\n * The display token for a skill chip. Cursor uses the `/` sigil (same as\n * Claude; distinct from Codex's `$` sigil).\n */\nfunction skillToken(block: SkillInvocationBlock): string {\n return block.namespace ? `/${block.namespace}:${block.name}` : `/${block.name}`;\n}\n\nfunction skillBodyKey(name: string, namespace: string | undefined): string {\n return namespace ? `${namespace}:${name}` : name;\n}\n\nfunction buildSkillBodyMap(\n skillBodies: readonly CursorResolvedSkillBody[] | undefined\n): Map<string, string> {\n const bodies = new Map<string, string>();\n if (skillBodies === undefined) return bodies;\n for (const skillBody of skillBodies) {\n bodies.set(skillBodyKey(skillBody.name, skillBody.namespace), skillBody.body);\n }\n return bodies;\n}\n\nfunction formatResolvedSkillBody(block: SkillInvocationBlock, body: string): string {\n const token = skillToken(block);\n return [`The user invoked the Cursor skill ${token}. Follow this SKILL.md body:`, '', body].join(\n '\\n'\n );\n}\n\nfunction processSkillInvocationBlock(\n block: SkillInvocationBlock,\n parts: string[],\n skillBodies: ReadonlyMap<string, string>\n): void {\n /**\n * Cursor composer-2.5 does not load Shipyard skill bodies from the\n * visual slash token. The daemon resolves SKILL.md from its trusted\n * capabilities index and passes only the body text here; never read\n * or trust block.path from the browser-authored content block.\n */\n const body = skillBodies.get(skillBodyKey(block.name, block.namespace));\n if (body === undefined) {\n if (block.bodyResolution === 'unreachable') return;\n parts.push(skillToken(block));\n return;\n }\n parts.push(formatResolvedSkillBody(block, body));\n}\n\nfunction readAssetPath(meta: Record<string, unknown> | undefined): string | null {\n if (!meta) return null;\n const value = meta[ASSET_PATH_META_KEY];\n return typeof value === 'string' ? value : null;\n}\n\n/**\n * Handle a `resource` block, appending to `parts` and `images` in place.\n *\n * Extracted from `buildCursorUserPrompt` to keep that function's switch\n * under the cognitive-complexity budget.\n */\n/** 10 MB decoded-size limit for base64 image payloads. */\nconst IMAGE_MAX_DECODED_BYTES = 10 * 1024 * 1024;\n\nfunction processResourceBlock(\n block: Extract<ContentBlock, { type: 'resource' }>,\n parts: string[],\n images: SDKImage[],\n log: CursorContentBuilderLogger\n): void {\n const r = block.resource;\n if ('blob' in r && r.mimeType && isSdkImageMediaType(r.mimeType)) {\n const mediaType = r.mimeType;\n const data = r.blob;\n /**\n * Guard against large images that would block the daemon event loop\n * during structured-clone serialization to the runner. Decoded size\n * ≈ base64-length × 3/4.\n */\n const approxDecodedBytes = Math.floor((data.length * 3) / 4);\n if (approxDecodedBytes > IMAGE_MAX_DECODED_BYTES) {\n const sizeMb = (approxDecodedBytes / (1024 * 1024)).toFixed(1);\n log({\n event: 'cursor_content_image_too_large',\n uri: r.uri,\n mimeType: r.mimeType,\n approxDecodedBytes,\n });\n parts.push(`[image omitted: too large (${sizeMb} MB)]`);\n return;\n }\n const validation = validateImageBlock({ mediaType, base64Data: data });\n if (!validation.ok) {\n log({\n event: 'cursor_content_image_validation_failed',\n reason: validation.reason,\n uri: r.uri,\n mimeType: r.mimeType,\n });\n parts.push(`[image omitted: ${validation.reason}]`);\n return;\n }\n images.push({ data, mimeType: mediaType });\n /**\n * Mirror Claude's [Saved at <path>] sibling text so the agent\n * can Read/Edit the file via filesystem tools (#2943 parity).\n */\n const assetPath = readAssetPath(r._meta);\n if (assetPath) parts.push(`[Saved at ${assetPath}]`);\n return;\n }\n if ('blob' in r) {\n /** Non-image blob → unchanged behavior (skip + log). */\n log({\n event: 'cursor_content_skip_binary_resource',\n uri: r.uri,\n mimeType: r.mimeType ?? '<none>',\n });\n return;\n }\n if ('text' in r) {\n if (r.text) parts.push(r.text);\n }\n}\n\n/**\n * #4197 fallback: the sigil tokens for every skill chip in a send. Used to\n * rescue a skill-only turn whose skill resolved to nothing (unreachable, no\n * body) so it is never dropped as empty input (Invariant #20).\n */\nfunction collectSkillTokens(blocks: ContentBlock[]): string[] {\n const tokens: string[] = [];\n for (const block of blocks) {\n if (block.type === 'skill_invocation') tokens.push(skillToken(block));\n }\n return tokens;\n}\n\n/**\n * Convert `ContentBlock[]` into a `CursorUserInput` suitable for\n * `agent.send(prompt | SDKUserMessage)`.\n *\n * Returns `{ text: '', images: [] }` when every block is silent-skip (tool_use,\n * tool_result, thinking, etc.). Callers MUST treat `text === '' && images.length === 0`\n * as \"do not push\" — sending empty input to the Cursor SDK produces a\n * server-side error.\n *\n * When images are present but text is empty, `text` is set to `'(image only)'`\n * because `SDKUserMessage.text` is required-non-empty in practice.\n */\nexport function buildCursorUserPrompt(\n blocks: ContentBlock[],\n log: CursorContentBuilderLogger,\n options: CursorPromptBuildOptions = {}\n): CursorUserInput {\n const parts: string[] = [];\n const images: SDKImage[] = [];\n const skillBodies = buildSkillBodyMap(options.skillBodies);\n\n for (const block of blocks) {\n switch (block.type) {\n case 'text': {\n if (block.text) parts.push(block.text);\n break;\n }\n\n case 'diff_review_feedback': {\n parts.push(formatDiffReviewFeedback(block.comments));\n break;\n }\n\n case 'preview_annotation_feedback': {\n parts.push(formatPreviewAnnotationFeedback(block.comments));\n break;\n }\n\n case 'annotation_feedback': {\n parts.push(formatAnnotationFeedback(block.annotations));\n break;\n }\n\n case 'task_feedback': {\n parts.push(formatTaskFeedback(block));\n break;\n }\n\n case 'resource_link': {\n const desc = block.description ? ` - ${block.description}` : '';\n parts.push(`[Resource: ${block.name}${desc}](${block.uri})`);\n break;\n }\n\n case 'resource': {\n processResourceBlock(block, parts, images, log);\n break;\n }\n\n case 'skill_invocation': {\n processSkillInvocationBlock(block, parts, skillBodies);\n break;\n }\n\n /** Silent-skip: not part of user input on the Cursor protocol. */\n case 'tool_use':\n case 'tool_result':\n case 'thinking':\n case 'review_decision':\n case 'review_comment':\n case 'compaction_boundary':\n break;\n\n case 'file_attachment':\n /**\n * File-attachment blocks (PROTOCOL_VERSION 104) are emitted by the\n * browser when a user attaches a non-image file. Cursor SDK 1.0.13\n * has no file-attachment surface in the message prompt API yet, so\n * we emit a placeholder mentioning the filename + size so the agent\n * at least knows a file was attached. Future SDK versions can route\n * the assetId to a real attachment slot.\n */\n parts.push(`[file attached: ${block.filename} (${block.sizeBytes} bytes)]`);\n break;\n\n default: {\n return assertNever(block);\n }\n }\n }\n\n /**\n * Sends that carry any other deliverable content (text OR an image) keep\n * that content and never reach this fallback; only an otherwise-empty turn\n * is rescued. An unrunnable skill must not inject a stray token alongside\n * real content. See `collectSkillTokens`.\n */\n if (parts.length === 0 && images.length === 0) {\n parts.push(...collectSkillTokens(blocks));\n }\n\n let text = parts.join('\\n');\n if (text === '' && images.length > 0) text = '(image only)';\n return { text, images };\n}\n\n/**\n * Parse Cursor's native assistant content array back into Shipyard\n * `ContentBlock[]`.\n *\n * `SDKContentBlock` is `TextBlock | ToolUseBlock` (from messages.d.ts).\n * Maps 1:1 to our `text` / `tool_use` blocks respectively.\n */\nexport function parseCursorAssistantContent(\n native: SDKContentBlock[],\n _runId: string\n): ContentBlock[] {\n const blocks: ContentBlock[] = [];\n\n for (const block of native) {\n if (block.type === 'text') {\n if (block.text) {\n blocks.push({ type: 'text', text: block.text });\n }\n } else if (block.type === 'tool_use') {\n blocks.push({\n type: 'tool_use',\n toolUseId: block.id,\n toolName: block.name,\n // eslint-disable-next-line no-restricted-syntax -- Cursor SDK types block.input as unknown; narrowing to Record<string,unknown> for ContentBlock.input\n input: (block.input ?? {}) as Record<string, unknown>,\n parentToolUseId: null,\n });\n }\n /** No other block types are defined in the Cursor SDK v1 assistant content. */\n }\n\n return blocks;\n}\n"],"mappings":"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","names":["skillToken","readAssetPath"]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
PUBLISHED_PREVIEW_KINDS,
|
|
4
4
|
PUBLISH_TTL_CHOICES
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-FARMJBC7.js";
|
|
6
6
|
import {
|
|
7
7
|
external_exports
|
|
8
8
|
} from "./chunk-XVW6VCKW.js";
|
|
@@ -21042,6 +21042,10 @@ var PersistedTurnStatsSchema = external_exports.object({
|
|
|
21042
21042
|
* without the estimated-badge UX.
|
|
21043
21043
|
*/
|
|
21044
21044
|
costSource: external_exports.enum(["reported", "estimated", "unavailable"]).optional(),
|
|
21045
|
+
inputTokensSource: external_exports.enum(["reported", "estimated", "unavailable"]).optional(),
|
|
21046
|
+
outputTokensSource: external_exports.enum(["reported", "estimated", "unavailable"]).optional(),
|
|
21047
|
+
/** Monotonic coverage of the cumulative dollar total across all prior turns. */
|
|
21048
|
+
cumulativeCostCoverage: external_exports.enum(["complete", "partial", "unavailable"]).optional(),
|
|
21045
21049
|
/**
|
|
21046
21050
|
* True when the token count is an estimate rather than a provider-reported figure.
|
|
21047
21051
|
* Cursor path uses estimation; false for Claude/Codex (provider-reported).
|
|
@@ -23900,7 +23904,9 @@ var BrowserToDaemonControlMessageSchema = external_exports.discriminatedUnion("t
|
|
|
23900
23904
|
external_exports.object({
|
|
23901
23905
|
type: external_exports.literal("update_user_setting"),
|
|
23902
23906
|
key: external_exports.string(),
|
|
23903
|
-
value: external_exports.unknown()
|
|
23907
|
+
value: external_exports.unknown(),
|
|
23908
|
+
/** Correlates the durable browser journal entry with the daemon write. */
|
|
23909
|
+
requestId: external_exports.string().optional()
|
|
23904
23910
|
}),
|
|
23905
23911
|
external_exports.object({ type: external_exports.literal("request_user_settings") }),
|
|
23906
23912
|
/** Re-read Claude on-disk spinner-verb config and update UserSettings. Owner-only. */
|
|
@@ -25242,6 +25248,11 @@ var DaemonToBrowserControlMessageSchema = external_exports.discriminatedUnion("t
|
|
|
25242
25248
|
type: external_exports.literal("user_settings_updated"),
|
|
25243
25249
|
settings: UserSettingsRecordSchema
|
|
25244
25250
|
}),
|
|
25251
|
+
external_exports.object({
|
|
25252
|
+
type: external_exports.literal("user_setting_update_ack"),
|
|
25253
|
+
requestId: external_exports.string(),
|
|
25254
|
+
ok: external_exports.boolean()
|
|
25255
|
+
}),
|
|
25245
25256
|
/** Project structure (sidebar redesign) */
|
|
25246
25257
|
external_exports.object({
|
|
25247
25258
|
type: external_exports.literal("projects_snapshot"),
|
|
@@ -26315,6 +26326,7 @@ function classifyCriticality(msg) {
|
|
|
26315
26326
|
case "rate_limit_info":
|
|
26316
26327
|
case "user_settings_snapshot":
|
|
26317
26328
|
case "user_settings_updated":
|
|
26329
|
+
case "user_setting_update_ack":
|
|
26318
26330
|
case "projects_snapshot":
|
|
26319
26331
|
case "project_updated":
|
|
26320
26332
|
case "project_deleted":
|
|
@@ -30853,4 +30865,4 @@ export {
|
|
|
30853
30865
|
encodeDrain,
|
|
30854
30866
|
decodeFrame
|
|
30855
30867
|
};
|
|
30856
|
-
//# sourceMappingURL=chunk-
|
|
30868
|
+
//# sourceMappingURL=chunk-KFCIZHQW.js.map
|