@schoolai/shipyard 3.12.0 → 3.13.0-rc.20260615.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 +4 -4
- package/dist/{chunk-3NML6IJM.js → chunk-7MVFHLYV.js} +2 -2
- package/dist/{chunk-GMMRLPGI.js → chunk-BUAEJNUG.js} +78 -13
- package/dist/{chunk-GMMRLPGI.js.map → chunk-BUAEJNUG.js.map} +1 -1
- package/dist/{chunk-HUTIDJIT.js → chunk-IWBDVGD2.js} +634 -29
- package/dist/chunk-IWBDVGD2.js.map +1 -0
- package/dist/{chunk-OCCM6UXW.js → chunk-KUPHN3ZN.js} +2 -2
- package/dist/{chunk-LUKN75OT.js → chunk-M3WBYTB3.js} +4 -2
- package/dist/chunk-M3WBYTB3.js.map +1 -0
- package/dist/{chunk-KJAEED3H.js → chunk-MR6HRO7R.js} +3 -3
- package/dist/{chunk-Q6JQK2DR.js → chunk-OX3UY44R.js} +4 -4
- package/dist/{chunk-LXFMCDAZ.js → chunk-RCEAMZVG.js} +2 -2
- package/dist/cursor-runner.js +3 -3
- package/dist/electron-utility.js +2 -2
- package/dist/index.js +5 -5
- package/dist/{login-2DJVKACB.js → login-HRR3T4SZ.js} +3 -3
- package/dist/{plan-backfill-S7FKJVOP.js → plan-backfill-QNJUWOYP.js} +4 -4
- package/dist/{roi-PTTYX6XY.js → roi-HNJRISLW.js} +11 -12
- package/dist/roi-HNJRISLW.js.map +1 -0
- package/dist/{serve-2N7BIGRO.js → serve-3N7IHREI.js} +718 -268
- package/dist/{serve-2N7BIGRO.js.map → serve-3N7IHREI.js.map} +1 -1
- package/dist/{start-GKHRXDGA.js → start-UPNI6OOF.js} +6 -6
- package/package.json +1 -1
- package/dist/chunk-HUTIDJIT.js.map +0 -1
- package/dist/chunk-LUKN75OT.js.map +0 -1
- package/dist/roi-PTTYX6XY.js.map +0 -1
- /package/dist/{chunk-3NML6IJM.js.map → chunk-7MVFHLYV.js.map} +0 -0
- /package/dist/{chunk-OCCM6UXW.js.map → chunk-KUPHN3ZN.js.map} +0 -0
- /package/dist/{chunk-KJAEED3H.js.map → chunk-MR6HRO7R.js.map} +0 -0
- /package/dist/{chunk-Q6JQK2DR.js.map → chunk-OX3UY44R.js.map} +0 -0
- /package/dist/{chunk-LXFMCDAZ.js.map → chunk-RCEAMZVG.js.map} +0 -0
- /package/dist/{login-2DJVKACB.js.map → login-HRR3T4SZ.js.map} +0 -0
- /package/dist/{plan-backfill-S7FKJVOP.js.map → plan-backfill-QNJUWOYP.js.map} +0 -0
- /package/dist/{start-GKHRXDGA.js.map → start-UPNI6OOF.js.map} +0 -0
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
guardedSubscribe,
|
|
13
13
|
makeInitialAttemptState,
|
|
14
14
|
shutdownFileWatcherGuard
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-7MVFHLYV.js";
|
|
16
16
|
import {
|
|
17
17
|
TRAILER_SCHEMA_VERSION,
|
|
18
18
|
appendTrailerToMessage,
|
|
@@ -39,13 +39,13 @@ import {
|
|
|
39
39
|
startPlanBackfillForDaemon,
|
|
40
40
|
sync,
|
|
41
41
|
validatePeerId
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-BUAEJNUG.js";
|
|
43
43
|
import {
|
|
44
44
|
loadAuthToken
|
|
45
45
|
} from "./chunk-TFWDJCUJ.js";
|
|
46
46
|
import {
|
|
47
47
|
getDaemonVersion
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-MR6HRO7R.js";
|
|
49
49
|
import {
|
|
50
50
|
collectPriorCrashDetail,
|
|
51
51
|
getRecentWasmPanicMessages
|
|
@@ -167,7 +167,7 @@ import {
|
|
|
167
167
|
translateWarningNotification,
|
|
168
168
|
unresolveReviewThread,
|
|
169
169
|
writeCursorApiKeyToVault
|
|
170
|
-
} from "./chunk-
|
|
170
|
+
} from "./chunk-OX3UY44R.js";
|
|
171
171
|
import {
|
|
172
172
|
buildNodeSpawnEnv,
|
|
173
173
|
nodeSpawnEnvOverlay,
|
|
@@ -226,7 +226,7 @@ import {
|
|
|
226
226
|
createAssetResolver,
|
|
227
227
|
toSdkContent,
|
|
228
228
|
toSdkPermissionMode
|
|
229
|
-
} from "./chunk-
|
|
229
|
+
} from "./chunk-RCEAMZVG.js";
|
|
230
230
|
import {
|
|
231
231
|
AGENT_IDS,
|
|
232
232
|
AGENT_SYSTEM_CLAUDE_CODE,
|
|
@@ -369,6 +369,7 @@ import {
|
|
|
369
369
|
isDiffEmpty,
|
|
370
370
|
isPendingAgentParticipantId,
|
|
371
371
|
isResourceResolutionMeta,
|
|
372
|
+
maybeCompressFrame,
|
|
372
373
|
migrateAnnotationStore,
|
|
373
374
|
migrateCredentialsVaultStore,
|
|
374
375
|
migrateDiffCommentStore,
|
|
@@ -407,7 +408,7 @@ import {
|
|
|
407
408
|
unsafeAssertSourceId,
|
|
408
409
|
vizFileExtension,
|
|
409
410
|
withPreferredRuntimeAuthMethod
|
|
410
|
-
} from "./chunk-
|
|
411
|
+
} from "./chunk-IWBDVGD2.js";
|
|
411
412
|
import {
|
|
412
413
|
AuthGitHubCallbackRequestSchema,
|
|
413
414
|
AuthGitHubCallbackResponseSchema,
|
|
@@ -420,6 +421,7 @@ import {
|
|
|
420
421
|
BrowserCodeExchangeRequestSchema,
|
|
421
422
|
BrowserCodeExchangeResponseSchema,
|
|
422
423
|
BrowserCodeResponseSchema,
|
|
424
|
+
CONTROL_COMPRESSION_MIN_VERSION,
|
|
423
425
|
CollabCreateRequestSchema,
|
|
424
426
|
CollabCreateResponseSchema,
|
|
425
427
|
CollabRoomConnection,
|
|
@@ -452,7 +454,7 @@ import {
|
|
|
452
454
|
VaultKeyPutRequestSchema,
|
|
453
455
|
VaultKeyPutResponseSchema,
|
|
454
456
|
classifyClaudeCodeCompatibility
|
|
455
|
-
} from "./chunk-
|
|
457
|
+
} from "./chunk-M3WBYTB3.js";
|
|
456
458
|
import {
|
|
457
459
|
external_exports as external_exports2
|
|
458
460
|
} from "./chunk-EHQITHQX.js";
|
|
@@ -5762,7 +5764,7 @@ function nanoid(size = 21) {
|
|
|
5762
5764
|
}
|
|
5763
5765
|
|
|
5764
5766
|
// src/services/bootstrap/signaling.ts
|
|
5765
|
-
var DAEMON_NPM_VERSION = true ? "3.
|
|
5767
|
+
var DAEMON_NPM_VERSION = true ? "3.13.0" : "unknown";
|
|
5766
5768
|
function createDaemonSignaling(config) {
|
|
5767
5769
|
const agentId = config.agentId ?? nanoid();
|
|
5768
5770
|
function send(msg) {
|
|
@@ -13295,6 +13297,8 @@ var ROLE_PERMISSIONS = {
|
|
|
13295
13297
|
"rewind_preview_request",
|
|
13296
13298
|
"fetch_older",
|
|
13297
13299
|
"permission_response",
|
|
13300
|
+
"permission_response_codex",
|
|
13301
|
+
"permission_response_cursor",
|
|
13298
13302
|
"update_settings",
|
|
13299
13303
|
"request_mcp_toggle",
|
|
13300
13304
|
"request_skill_toggle",
|
|
@@ -13332,6 +13336,7 @@ var ROLE_PERMISSIONS = {
|
|
|
13332
13336
|
"delete_annotation",
|
|
13333
13337
|
"mark_annotation_delivered",
|
|
13334
13338
|
"request_annotation_snapshot",
|
|
13339
|
+
"request_viz_content_batch",
|
|
13335
13340
|
"request_deliverables_snapshot",
|
|
13336
13341
|
"request_task_state",
|
|
13337
13342
|
"create_thread",
|
|
@@ -13403,6 +13408,7 @@ var ROLE_PERMISSIONS = {
|
|
|
13403
13408
|
"delete_annotation",
|
|
13404
13409
|
"mark_annotation_delivered",
|
|
13405
13410
|
"request_annotation_snapshot",
|
|
13411
|
+
"request_viz_content_batch",
|
|
13406
13412
|
"request_deliverables_snapshot",
|
|
13407
13413
|
"request_task_state",
|
|
13408
13414
|
"create_thread",
|
|
@@ -13426,6 +13432,7 @@ var ROLE_PERMISSIONS = {
|
|
|
13426
13432
|
"join_collab_room",
|
|
13427
13433
|
"leave_collab_room",
|
|
13428
13434
|
"request_annotation_snapshot",
|
|
13435
|
+
"request_viz_content_batch",
|
|
13429
13436
|
"request_deliverables_snapshot",
|
|
13430
13437
|
"request_task_state",
|
|
13431
13438
|
"list_threads",
|
|
@@ -13437,6 +13444,7 @@ var ROLE_PERMISSIONS = {
|
|
|
13437
13444
|
"request_capabilities",
|
|
13438
13445
|
"request_pr_data",
|
|
13439
13446
|
"request_task_state",
|
|
13447
|
+
"request_viz_content_batch",
|
|
13440
13448
|
"join_collab_room",
|
|
13441
13449
|
"leave_collab_room",
|
|
13442
13450
|
"presence_update"
|
|
@@ -13474,6 +13482,7 @@ var TASK_SCOPED_CONTROL_TYPES = /* @__PURE__ */ new Set([
|
|
|
13474
13482
|
"delete_annotation",
|
|
13475
13483
|
"mark_annotation_delivered",
|
|
13476
13484
|
"request_annotation_snapshot",
|
|
13485
|
+
"request_viz_content_batch",
|
|
13477
13486
|
"request_deliverables_snapshot",
|
|
13478
13487
|
"request_task_state",
|
|
13479
13488
|
"create_thread",
|
|
@@ -14786,6 +14795,7 @@ function routeMessage(msg, callbacks, sendControl, log) {
|
|
|
14786
14795
|
callbacks.onUpdateThreadSettings(msg.taskId, msg.threadId, msg.settings);
|
|
14787
14796
|
break;
|
|
14788
14797
|
case "request_capabilities":
|
|
14798
|
+
callbacks.onPeerProtocolVersion(msg.protocolVersion ?? 0);
|
|
14789
14799
|
callbacks.onRequestCapabilities({ force: msg.force });
|
|
14790
14800
|
break;
|
|
14791
14801
|
case "request_skills":
|
|
@@ -14994,6 +15004,9 @@ function routeMessage(msg, callbacks, sendControl, log) {
|
|
|
14994
15004
|
case "request_annotation_snapshot":
|
|
14995
15005
|
callbacks.onRequestAnnotationSnapshot(msg.taskId);
|
|
14996
15006
|
break;
|
|
15007
|
+
case "request_viz_content_batch":
|
|
15008
|
+
callbacks.onRequestVizContentBatch(msg.taskId);
|
|
15009
|
+
break;
|
|
14997
15010
|
case "request_deliverables_snapshot":
|
|
14998
15011
|
callbacks.onRequestDeliverablesSnapshot(msg.taskId);
|
|
14999
15012
|
break;
|
|
@@ -16295,6 +16308,8 @@ function dedupKeyForMessage(msg) {
|
|
|
16295
16308
|
return JSON.stringify({ type: "plugin_data_response", requestId: msg.requestId });
|
|
16296
16309
|
case "presence_state":
|
|
16297
16310
|
return JSON.stringify({ type: "presence_state" });
|
|
16311
|
+
case "capabilities":
|
|
16312
|
+
return JSON.stringify({ type: "capabilities" });
|
|
16298
16313
|
default:
|
|
16299
16314
|
return void 0;
|
|
16300
16315
|
}
|
|
@@ -19332,8 +19347,9 @@ function createSkillsTools(ctx) {
|
|
|
19332
19347
|
"To use one, call load_skill with its name. Skills you already reach natively are omitted here",
|
|
19333
19348
|
"(invoke those the normal way). Returns each skill's name, one-line description, and origin.",
|
|
19334
19349
|
"",
|
|
19335
|
-
"WHEN: before starting work, to see
|
|
19336
|
-
"
|
|
19350
|
+
"WHEN: before starting domain-specific work, to see whether the team already wrote down the",
|
|
19351
|
+
"conventions for it (check skills first, then dive in); or when a native skill mention came",
|
|
19352
|
+
"back empty (the skill is likely library-only \u2014 look for it here)."
|
|
19337
19353
|
].join("\n"),
|
|
19338
19354
|
inputSchema: {},
|
|
19339
19355
|
handler: async () => {
|
|
@@ -19462,12 +19478,14 @@ var COMMENTS_PROMPT_DOC = [
|
|
|
19462
19478
|
var VISUALIZE_PROMPT_DOC = [
|
|
19463
19479
|
"Call `read_me` before your first visualization in a session. It returns the theme, color palette, and constraints. Skipping it leads to broken colors (e.g. invisible text on dark backgrounds). Pass the `modules` parameter for your use case: chart, interactive, diagram, art, slides, review, table, recap, or plan.",
|
|
19464
19480
|
"",
|
|
19465
|
-
"**When
|
|
19481
|
+
"**When a picture beats a paragraph** \u2014 don't wait to be asked, but produce one when it changes what a reviewer understands, not by reflex:",
|
|
19466
19482
|
"- Architecture or system design \u2192 Mermaid diagram",
|
|
19467
19483
|
"- UI work or layout discussion \u2192 HTML mockup",
|
|
19468
19484
|
"- Data modeling or performance \u2192 chart via Chart.js or D3 in HTML",
|
|
19469
19485
|
"- State machines or workflows \u2192 Mermaid stateDiagram",
|
|
19470
19486
|
"",
|
|
19487
|
+
"When prose already carries the idea clearly, skip the diagram \u2014 one sharp explanation beats a chart nobody needed.",
|
|
19488
|
+
"",
|
|
19471
19489
|
"**Two tools:** `visualize` creates a file-backed visualization that renders inline immediately (returns slug + file path). `present({source:'slug', slug, target})` shows an existing visualization: `target:'inline'` returns a snapshot in conversation, `target:'canvas'` places a live element that auto-updates when the file changes."
|
|
19472
19490
|
].join("\n");
|
|
19473
19491
|
var PRESENT_PROMPT_DOC = [
|
|
@@ -19516,16 +19534,16 @@ var TASK_TOOL_PROMPT_DOC = [
|
|
|
19516
19534
|
'The `task` MCP tool covers three actions: `send_message` and `cancel` for sibling tasks (by ID \u2014 drive a spawned task with new instructions or stop it), and `snapshot` for your CURRENT task. Call `task({action:"snapshot"})` to read a live markdown view of your own todos, branch, cost, PR state, and recent deliverables \u2014 the same shape `create_task` returns for spawned siblings.'
|
|
19517
19535
|
].join("\n");
|
|
19518
19536
|
var REGISTER_DELIVERABLE_PROMPT_DOC = [
|
|
19519
|
-
"Use `register_deliverable` to
|
|
19537
|
+
"Use `register_deliverable` to attach proof a reviewer can't get from the diff alone \u2014 a screenshot of the UI after your change, a green test-run log, a benchmark number, an AX-tree dump. Reviewers open these in the conversation dock's Deliverables tab. This is the \"don't ship blind\" half made concrete: the diff shows what changed; the deliverable shows it works.",
|
|
19520
19538
|
"",
|
|
19521
|
-
"**When to call:**
|
|
19539
|
+
"**When to call:** right after a change whose effect a reviewer can't verify by reading the diff \u2014 a UI tweak (attach the rendered screenshot), a fix you proved with a test (attach the passing run), a perf claim (attach the numbers). **When NOT to call:** when the diff *is* the proof. A pure refactor, a rename, a config edit \u2014 the change verifies itself; registering a deliverable just costs the reviewer attention.",
|
|
19522
19540
|
"",
|
|
19523
19541
|
"**Scoping:** when the task has structured todos (visible in `<task-list>`), scope the deliverable to the matching `todoItemId` (bare number, no `#`). For task-spanning artifacts, leave `todoItemId` null. For thread-scoped evidence, set `threadId`.",
|
|
19524
19542
|
"",
|
|
19525
19543
|
"**Required fields:** `kind` (one of `screenshot`, `video`, `log`, `markdown`, `html`, `json`, `ax-tree`, `dom-snapshot`, `other`), `title`, and exactly one of `assetId` or `filePath`."
|
|
19526
19544
|
].join("\n");
|
|
19527
19545
|
var SKILLS_PROMPT_DOC = [
|
|
19528
|
-
"Shipyard curates a set of skills for this task \u2014 reusable instructions for
|
|
19546
|
+
"Shipyard curates a set of skills for this task \u2014 reusable, expert instructions for specific workflows. **Before starting domain-specific work, check skills first:** call `list_skills` to see what's available, then `load_skill({ name })` to pull a skill's full instructions (and a directory path for any sibling files it references) before you dive in. A relevant skill saves you re-deriving conventions the team already wrote down. Some skills are also reachable through your own `/name` mentions; for anything in `list_skills`, `load_skill` always works."
|
|
19529
19547
|
].join("\n");
|
|
19530
19548
|
var HARNESS_TOOL_REGISTRY = [
|
|
19531
19549
|
/** Comments — gate: all. */
|
|
@@ -19700,17 +19718,51 @@ var CURSOR_TOOLS = buildToolsBlock(
|
|
|
19700
19718
|
function sharedIntro(shellWord) {
|
|
19701
19719
|
return `# Shipyard
|
|
19702
19720
|
|
|
19703
|
-
|
|
19721
|
+
You're a pair programmer on this team. Your job has two halves, and they matter equally:
|
|
19722
|
+
|
|
19723
|
+
- **Ship fast** \u2014 do the work: write the code, run it, fix it, keep moving.
|
|
19724
|
+
- **Don't ship blind** \u2014 make your work legible *as you go*, so the people reviewing it see not just *what* changed but *why*, and can accept it with confidence.
|
|
19725
|
+
|
|
19726
|
+
Everything Shipyard gives you \u2014 the diff, comments, visualizations, deliverables, the plan, the canvas \u2014 exists to serve the second half. You're not narrating for its own sake; you're keeping the people you work with in the loop at the speed you're working. **A change nobody can review isn't done.**
|
|
19727
|
+
|
|
19728
|
+
Your teammates see everything you produce in real time, and they can act on it \u2014 edit the plan alongside you, comment on a diff hunk, add a task to the list. Treat what they do as direct input.
|
|
19729
|
+
|
|
19730
|
+
Before anything else, the few rules that don't bend:
|
|
19731
|
+
|
|
19732
|
+
- **Never launch browser tabs.** Don't use \`open\`, \`xdg-open\`, or ${shellWord} commands to open a URL \u2014 Shipyard renders everything the user needs to see.
|
|
19733
|
+
- **Confirm before anything outward-facing, irreversible, or tied to an identity** \u2014 publishing, posting, anything that consumes a token or attributes work to an account. Ask first.
|
|
19734
|
+
- **Rejection does not revert your code.** When a reviewer rejects, your changes stay on disk. Wait for their feedback before reattempting \u2014 don't undo or redo on your own.
|
|
19735
|
+
|
|
19736
|
+
## Make your work legible
|
|
19737
|
+
|
|
19738
|
+
After you do something, close the gap between what you did and what a reviewer can see \u2014 and reach for the tool only when there's a real gap:
|
|
19739
|
+
|
|
19740
|
+
- **The reasoning behind a non-obvious change** belongs where they'll read it: a diff comment (\`add_comment\`) on the hunk. If the change speaks for itself, say nothing.
|
|
19741
|
+
- **A design, a data model, a flow, a comparison** is faster to grasp as a picture than a paragraph \u2014 a visualization (\`visualize\`, then \`present\`).
|
|
19742
|
+
- **Proof that something works that the diff can't show** \u2014 the UI after your change, a green test run, a performance number \u2014 is a deliverable (\`register_deliverable\`). When the diff *is* the proof, skip it.
|
|
19743
|
+
- **Multi-step work** becomes a task list the team watches and adds to \u2014 keep it current with your task-list tool (see Live State Resources for the exact verb your runtime uses).
|
|
19744
|
+
|
|
19745
|
+
You don't need to be asked, and you don't need permission \u2014 this is how the job gets done. But each artifact costs a reviewer a moment of attention, so produce the one that changes what they understand. One sharp comment beats three diagrams. When in doubt, don't.
|
|
19746
|
+
|
|
19747
|
+
You cannot preview your own visualizations \u2014 they render client-side only. Follow the constraints from \`read_me\` exactly.
|
|
19704
19748
|
|
|
19705
|
-
|
|
19749
|
+
## Choosing the right tool
|
|
19706
19750
|
|
|
19707
|
-
|
|
19751
|
+
A few boundaries are easy to get wrong \u2014 prefer the more specific option:
|
|
19708
19752
|
|
|
19709
|
-
|
|
19753
|
+
- **Isolated or parallel work on its own branch** \u2192 a sibling task (\`create_task\`, which gets its own git worktree). A subagent (your runtime's subagent tool \u2014 see the agent-specific notes) shares *this* worktree and returns findings here \u2014 it's for read/research fan-out, not isolated build work.
|
|
19754
|
+
- **Sharing a preview** \u2192 \`publish\` to send it outside the session; \`present\` to show it inside.
|
|
19755
|
+
- **A recurring dashboard the user will return to** \u2192 a plugin. A one-off chart \u2192 a visualization.
|
|
19710
19756
|
|
|
19711
|
-
|
|
19757
|
+
These are preferences, not commands \u2014 you decide.
|
|
19712
19758
|
|
|
19713
|
-
|
|
19759
|
+
## The plan is a shared editor, not a document you publish
|
|
19760
|
+
|
|
19761
|
+
When you propose a plan, it opens on the canvas as a **live collaborative editor** \u2014 rich text over a CRDT, with multiplayer cursors. Your teammates can **edit the plan text directly**, alongside your comments and theirs. So:
|
|
19762
|
+
|
|
19763
|
+
- Write the plan to be *edited*, not just approved \u2014 clear sections a human can revise in place.
|
|
19764
|
+
- Between turns, re-read the \`<plan>\` resource: human edits to the plan text are intent, the same as a task they added or a comment they left. Build on them; don't overwrite them.
|
|
19765
|
+
- Comments and direct edits are two channels into the same document \u2014 answer the comments, absorb the edits.`;
|
|
19714
19766
|
}
|
|
19715
19767
|
var CLAUDE_LIVE_STATE = `## Live State Resources
|
|
19716
19768
|
|
|
@@ -19777,9 +19829,11 @@ When liveness flips back to \`bound\`, the resource re-emits on your next turn.
|
|
|
19777
19829
|
|
|
19778
19830
|
When you call ExitPlanMode, your plan is published to the Shipyard canvas for collaborative review. You will receive a deny response \u2014 this is expected. Stay in plan mode and wait for reviewer feedback. The \`<plan>\` resource carries the current plan markdown, embedded \`<${XML_TAGS.COMMENT}>\` children for plan-anchored comments, and a \`<review>\` element when reviewers have approved or requested changes.
|
|
19779
19831
|
|
|
19832
|
+
**The plan is a live editor, not a static post.** It renders as a collaborative rich-text editor over a CRDT \u2014 your teammates can edit the plan text directly, not just comment on it. So the markdown in a later \`<plan>\` resource may differ from what you wrote: treat their edits as intent, the same as a task they added or a comment they left. Build on what they changed; don't silently revert it. Comments and direct edits are two channels into the same document.
|
|
19833
|
+
|
|
19780
19834
|
**While in plan mode** \u2014 place supporting visualizations on the canvas using \`present({source:'slug', slug, target:'canvas'})\`. Architecture diagrams, data models, and flow charts alongside the plan text help reviewers evaluate your design.
|
|
19781
19835
|
|
|
19782
|
-
Do not revise the plan unless reviewers request changes. When the \`<review decision="approve">\` element appears, call ExitPlanMode again to begin implementation.
|
|
19836
|
+
Do not revise the plan unless reviewers request changes \u2014 but absorbing a human's direct edit to the plan text is not "revising," it's accepting their contribution. When the \`<review decision="approve">\` element appears, call ExitPlanMode again to begin implementation.
|
|
19783
19837
|
|
|
19784
19838
|
### \`<${XML_TAGS.DEV_SERVERS}>\`
|
|
19785
19839
|
|
|
@@ -19807,11 +19861,19 @@ Prefer background execution to keep your main thread responsive to collaborators
|
|
|
19807
19861
|
**Bash tool** \u2014 Use \`run_in_background: true\` for commands that may take more than 10 seconds: builds, installs, test suites, large git operations, network requests. Use \`TaskOutput\` to read results when notified. Keep short commands (git status, ls, cat) in the foreground.
|
|
19808
19862
|
|
|
19809
19863
|
**Why this matters:** Foreground operations block everything. The only recovery from a stuck foreground operation is stopping the entire agent. Background-first execution avoids this and keeps you available for collaborator feedback.`;
|
|
19864
|
+
var SHARED_CLOSING = `## The rules that don't bend
|
|
19865
|
+
|
|
19866
|
+
A reminder, because these are the ones that cost the most when missed:
|
|
19867
|
+
|
|
19868
|
+
- **Never open browser tabs** \u2014 Shipyard renders everything; no \`open\` / \`xdg-open\`.
|
|
19869
|
+
- **Confirm before anything outward-facing, irreversible, or identity-tied** \u2014 publishing, posting, spending a token. Ask first.
|
|
19870
|
+
- **Rejection does not revert your code** \u2014 your changes stay on disk; wait for the reviewer's feedback before reattempting.`;
|
|
19810
19871
|
var SHIPYARD_SYSTEM_PROMPT = [
|
|
19811
19872
|
sharedIntro("shell"),
|
|
19812
19873
|
CLAUDE_TOOLS,
|
|
19813
19874
|
CLAUDE_LIVE_STATE,
|
|
19814
|
-
CLAUDE_BACKGROUND
|
|
19875
|
+
CLAUDE_BACKGROUND,
|
|
19876
|
+
SHARED_CLOSING
|
|
19815
19877
|
].join("\n\n").trim();
|
|
19816
19878
|
function buildCollabSystemPrompt(participants) {
|
|
19817
19879
|
const roster = participants.map((p2) => `${p2.name} (${p2.role})`).join(", ");
|
|
@@ -19865,6 +19927,8 @@ When you are in plan-collaboration mode, publish your plan by wrapping it in a \
|
|
|
19865
19927
|
|
|
19866
19928
|
The \`<plan>\` resource carries the current plan markdown, embedded \`<${XML_TAGS.COMMENT}>\` children for plan-anchored comments, and a \`<review>\` element when reviewers have approved or requested changes. Reviewer feedback arrives as \`<${XML_TAGS.COMMENT}>\` children of \`<plan>\` \u2014 reply to every one with \`add_comment\` \`mode: "reply"\` and \`parentCommentId\` set to the comment's \`id\`, same as on every other surface.
|
|
19867
19929
|
|
|
19930
|
+
**The plan is a live editor, not a static post.** Once published, it renders as a collaborative rich-text editor over a CRDT \u2014 your teammates can edit the plan text directly, not just comment. The markdown in a later \`<plan>\` resource may therefore differ from what you proposed: treat their edits as intent, the same as a comment they left. When you emit your next \`<proposed_plan>\` block, build on what they changed \u2014 carry their edits forward rather than reverting to your prior wording.
|
|
19931
|
+
|
|
19868
19932
|
**Iteration semantics** \u2014 to revise the plan, emit a fresh \`<proposed_plan>\` block in your next turn. Each new block is a complete replacement, not a patch \u2014 include everything the plan should contain. Emit at most one \`<proposed_plan>\` block per turn, and only when the plan is decision-complete.
|
|
19869
19933
|
|
|
19870
19934
|
**Refining with the reviewer** \u2014 use \`request_user_input\` to ask multiple-choice questions during refinement (preferences, tradeoffs, and locked-in assumptions that materially change the plan). Don't ask questions you can answer from the repo \u2014 explore first via \`shell\` reads, then ask only when ambiguity is genuinely about intent.
|
|
@@ -19901,7 +19965,8 @@ var SHIPYARD_CODEX_SYSTEM_PROMPT = [
|
|
|
19901
19965
|
sharedIntro("shell"),
|
|
19902
19966
|
CODEX_TOOLS,
|
|
19903
19967
|
CODEX_LIVE_STATE,
|
|
19904
|
-
CODEX_NOTES
|
|
19968
|
+
CODEX_NOTES,
|
|
19969
|
+
SHARED_CLOSING
|
|
19905
19970
|
].join("\n\n").trim();
|
|
19906
19971
|
var CURSOR_LIVE_STATE = `## Live State Resources
|
|
19907
19972
|
|
|
@@ -19937,6 +20002,8 @@ Authoritative snapshot of what's on the shared canvas. Each \`<element>\` carrie
|
|
|
19937
20002
|
|
|
19938
20003
|
The \`<plan>\` resource carries the current plan markdown, embedded \`<${XML_TAGS.COMMENT}>\` children for plan-anchored comments, and a \`<review>\` element when reviewers have approved or requested changes. Reviewer feedback arrives as \`<${XML_TAGS.COMMENT}>\` children of \`<plan>\` \u2014 reply to every one with \`add_comment\` \`mode: "reply"\` and \`parentCommentId\` set to the comment's \`id\`, same as on every other surface.
|
|
19939
20004
|
|
|
20005
|
+
The plan text is a live collaborative editor \u2014 your teammates can edit it directly, not just comment. If the plan markdown in a later \`<plan>\` resource differs from what you remember, that is a human edit: treat it as intent, the same as a comment they left, and build on it rather than reverting it.
|
|
20006
|
+
|
|
19940
20007
|
### \`<${XML_TAGS.DEV_SERVERS}>\` and \`<${XML_TAGS.TERMINALS}>\`
|
|
19941
20008
|
|
|
19942
20009
|
Task-scoped snapshots of running processes. Use \`<${XML_TAGS.DEV_SERVERS}>\` to check whether a dev server is running before suggesting starting one. Use \`<${XML_TAGS.TERMINALS}>\` to find log files for active PTY sessions \u2014 when you need current terminal state, read the file at \`log="..."\` directly via \`Shell\` (e.g. \`tail -n 100\`), do not infer from possibly-stale \`<recent-output>\`.
|
|
@@ -19960,7 +20027,8 @@ var SHIPYARD_CURSOR_SYSTEM_PROMPT = [
|
|
|
19960
20027
|
sharedIntro("`Shell`"),
|
|
19961
20028
|
CURSOR_TOOLS,
|
|
19962
20029
|
CURSOR_LIVE_STATE,
|
|
19963
|
-
CURSOR_NOTES
|
|
20030
|
+
CURSOR_NOTES,
|
|
20031
|
+
SHARED_CLOSING
|
|
19964
20032
|
].join("\n\n").trim();
|
|
19965
20033
|
|
|
19966
20034
|
// src/services/mcp/visibility-migrations.ts
|
|
@@ -22316,7 +22384,13 @@ function dispatchSpawn(decision, shell) {
|
|
|
22316
22384
|
return shell.spawnClaude();
|
|
22317
22385
|
case "spawn-cursor": {
|
|
22318
22386
|
const sub = shell.spawnCursor();
|
|
22319
|
-
|
|
22387
|
+
const firstTurnBlocks = shell.initialContent.length;
|
|
22388
|
+
if (firstTurnBlocks > 0) sub.pushMessage(shell.initialContent);
|
|
22389
|
+
shell.log({
|
|
22390
|
+
event: "cursor_spawn_first_turn",
|
|
22391
|
+
firstTurnPushed: firstTurnBlocks > 0,
|
|
22392
|
+
firstTurnBlocks
|
|
22393
|
+
});
|
|
22320
22394
|
return sub;
|
|
22321
22395
|
}
|
|
22322
22396
|
case "reject-not-installed":
|
|
@@ -23080,11 +23154,12 @@ var RATCHET_THRESHOLDS = [10, 100, 1e3];
|
|
|
23080
23154
|
var RATCHET_WINDOW_MS = 6e4;
|
|
23081
23155
|
function createBestEffortSender(deps) {
|
|
23082
23156
|
const dropsCounter = new RollingCounter(RATCHET_WINDOW_MS, RATCHET_THRESHOLDS);
|
|
23083
|
-
const { guardedChannel, logAdapter, controlPeerId } = deps;
|
|
23157
|
+
const { guardedChannel, logAdapter, controlPeerId, compress } = deps;
|
|
23084
23158
|
return {
|
|
23085
23159
|
send: (msg) => {
|
|
23086
23160
|
const json = JSON.stringify(msg);
|
|
23087
|
-
const
|
|
23161
|
+
const out = compress(json);
|
|
23162
|
+
const result = guardedChannel.send(out, "best-effort");
|
|
23088
23163
|
if (result !== "dropped" && result !== "drain-pending") return;
|
|
23089
23164
|
const now = Date.now();
|
|
23090
23165
|
const { thresholdCrossed } = dropsCounter.record(now);
|
|
@@ -26406,6 +26481,61 @@ function describeError(err3) {
|
|
|
26406
26481
|
// src/services/channels/control-channel/task-callbacks.ts
|
|
26407
26482
|
var TASK_INDEX_SNAPSHOT_MAX_BYTES = 9e5;
|
|
26408
26483
|
var TASK_INDEX_SNAPSHOT_OVERSIZE_BYTES = 1048576;
|
|
26484
|
+
function buildSlimTaskIndex(tasks) {
|
|
26485
|
+
const slimTasks = {};
|
|
26486
|
+
for (const [taskId, record] of Object.entries(tasks)) {
|
|
26487
|
+
slimTasks[taskId] = toTaskIndexEntry(record);
|
|
26488
|
+
}
|
|
26489
|
+
return slimTasks;
|
|
26490
|
+
}
|
|
26491
|
+
function measureJsonBytes(value) {
|
|
26492
|
+
try {
|
|
26493
|
+
return Buffer.byteLength(JSON.stringify(value), "utf-8");
|
|
26494
|
+
} catch {
|
|
26495
|
+
return 0;
|
|
26496
|
+
}
|
|
26497
|
+
}
|
|
26498
|
+
function reportOversizedTaskIndexSnapshot(slimTasks, bytes, logAdapter) {
|
|
26499
|
+
if (bytes <= TASK_INDEX_SNAPSHOT_OVERSIZE_BYTES) return;
|
|
26500
|
+
const taskCount = Object.keys(slimTasks).length;
|
|
26501
|
+
logAdapter({
|
|
26502
|
+
event: "task_index_snapshot_oversized",
|
|
26503
|
+
level: "error",
|
|
26504
|
+
bytes,
|
|
26505
|
+
taskCount,
|
|
26506
|
+
limitBytes: TASK_INDEX_SNAPSHOT_OVERSIZE_BYTES
|
|
26507
|
+
});
|
|
26508
|
+
telemeter("task_index_snapshot_oversized", {
|
|
26509
|
+
bytes,
|
|
26510
|
+
taskCount,
|
|
26511
|
+
limitBytes: TASK_INDEX_SNAPSHOT_OVERSIZE_BYTES
|
|
26512
|
+
});
|
|
26513
|
+
}
|
|
26514
|
+
function emitTaskIndexSnapshot(args) {
|
|
26515
|
+
const { tasks, version, partial, nextCursor, nextCursorId, totalCount } = args.result;
|
|
26516
|
+
const slimTasks = buildSlimTaskIndex(tasks);
|
|
26517
|
+
const message = {
|
|
26518
|
+
type: "task_index_snapshot",
|
|
26519
|
+
tasks: slimTasks,
|
|
26520
|
+
version,
|
|
26521
|
+
partial,
|
|
26522
|
+
nextCursor,
|
|
26523
|
+
nextCursorId,
|
|
26524
|
+
totalCount
|
|
26525
|
+
};
|
|
26526
|
+
const bytes = measureJsonBytes(message);
|
|
26527
|
+
args.controlHandler.sendControl(message);
|
|
26528
|
+
reportOversizedTaskIndexSnapshot(slimTasks, bytes, args.logAdapter);
|
|
26529
|
+
args.logAdapter({
|
|
26530
|
+
event: "perf_task_index_snapshot_emit_done",
|
|
26531
|
+
taskCount: Object.keys(slimTasks).length,
|
|
26532
|
+
totalCount,
|
|
26533
|
+
partial: partial === true,
|
|
26534
|
+
cursorRequested: args.cursorRequested,
|
|
26535
|
+
bytes,
|
|
26536
|
+
durationMs: Date.now() - args.startedAt
|
|
26537
|
+
});
|
|
26538
|
+
}
|
|
26409
26539
|
function buildTaskCallbacks(deps) {
|
|
26410
26540
|
const { daemon, getControlHandler, logAdapter, publishedArtifactStore, previewStateStore } = deps;
|
|
26411
26541
|
const taskIndexInflightTtlMs = 250;
|
|
@@ -26490,7 +26620,8 @@ function buildTaskCallbacks(deps) {
|
|
|
26490
26620
|
limit,
|
|
26491
26621
|
sinceLastActivityAt: opts.sinceLastActivityAt ?? null,
|
|
26492
26622
|
sinceLastId: opts.sinceLastId ?? null,
|
|
26493
|
-
maxBytes: TASK_INDEX_SNAPSHOT_MAX_BYTES
|
|
26623
|
+
maxBytes: TASK_INDEX_SNAPSHOT_MAX_BYTES,
|
|
26624
|
+
includeTaskIds: cursorRequested ? [] : "favoriteTasks"
|
|
26494
26625
|
});
|
|
26495
26626
|
const cached = taskIndexInflightCache.get(fingerprint);
|
|
26496
26627
|
const now = Date.now();
|
|
@@ -26502,80 +26633,54 @@ function buildTaskCallbacks(deps) {
|
|
|
26502
26633
|
});
|
|
26503
26634
|
return;
|
|
26504
26635
|
}
|
|
26505
|
-
|
|
26506
|
-
|
|
26507
|
-
|
|
26508
|
-
|
|
26509
|
-
|
|
26510
|
-
|
|
26511
|
-
activeOnly: opts.activeOnly,
|
|
26512
|
-
limit,
|
|
26513
|
-
sinceLastActivityAt: opts.sinceLastActivityAt,
|
|
26514
|
-
sinceLastId: opts.sinceLastId,
|
|
26515
|
-
maxBytes: TASK_INDEX_SNAPSHOT_MAX_BYTES
|
|
26516
|
-
});
|
|
26517
|
-
taskIndexInflightCache.set(fingerprint, {
|
|
26518
|
-
promise: resultPromise,
|
|
26519
|
-
expiresAt: now + taskIndexInflightTtlMs * 4
|
|
26636
|
+
const includeTaskIdsPromise = cursorRequested ? Promise.resolve(void 0) : daemon.userSettingsStore.getSettings().then((settings) => settings.favoriteTasks).catch((err3) => {
|
|
26637
|
+
logAdapter({
|
|
26638
|
+
event: "task_index_favorites_load_failed",
|
|
26639
|
+
error: err3 instanceof Error ? err3.message : String(err3)
|
|
26640
|
+
});
|
|
26641
|
+
return void 0;
|
|
26520
26642
|
});
|
|
26521
|
-
|
|
26522
|
-
const slimTasks = {};
|
|
26523
|
-
for (const [taskId, record] of Object.entries(tasks)) {
|
|
26524
|
-
slimTasks[taskId] = toTaskIndexEntry(record);
|
|
26525
|
-
}
|
|
26526
|
-
const message = {
|
|
26527
|
-
type: "task_index_snapshot",
|
|
26528
|
-
tasks: slimTasks,
|
|
26529
|
-
version,
|
|
26530
|
-
partial,
|
|
26531
|
-
nextCursor,
|
|
26532
|
-
nextCursorId,
|
|
26533
|
-
totalCount
|
|
26534
|
-
};
|
|
26535
|
-
let bytes = 0;
|
|
26536
|
-
try {
|
|
26537
|
-
bytes = Buffer.byteLength(JSON.stringify(message), "utf-8");
|
|
26538
|
-
} catch {
|
|
26539
|
-
}
|
|
26540
|
-
getControlHandler().sendControl(message);
|
|
26541
|
-
if (bytes > TASK_INDEX_SNAPSHOT_OVERSIZE_BYTES) {
|
|
26542
|
-
const taskCount = Object.keys(slimTasks).length;
|
|
26543
|
-
logAdapter({
|
|
26544
|
-
event: "task_index_snapshot_oversized",
|
|
26545
|
-
level: "error",
|
|
26546
|
-
bytes,
|
|
26547
|
-
taskCount,
|
|
26548
|
-
limitBytes: TASK_INDEX_SNAPSHOT_OVERSIZE_BYTES
|
|
26549
|
-
});
|
|
26550
|
-
telemeter("task_index_snapshot_oversized", {
|
|
26551
|
-
bytes,
|
|
26552
|
-
taskCount,
|
|
26553
|
-
limitBytes: TASK_INDEX_SNAPSHOT_OVERSIZE_BYTES
|
|
26554
|
-
});
|
|
26555
|
-
}
|
|
26643
|
+
const requestPromise = includeTaskIdsPromise.then((includeTaskIds) => {
|
|
26556
26644
|
logAdapter({
|
|
26557
|
-
event: "
|
|
26558
|
-
taskCount: Object.keys(slimTasks).length,
|
|
26559
|
-
totalCount,
|
|
26560
|
-
partial: partial === true,
|
|
26645
|
+
event: "perf_task_index_snapshot_emit_start",
|
|
26561
26646
|
cursorRequested,
|
|
26562
|
-
|
|
26563
|
-
|
|
26647
|
+
activeOnly: opts.activeOnly === true
|
|
26648
|
+
});
|
|
26649
|
+
return daemon.taskStateStore.listTasksPaginated({
|
|
26650
|
+
activeOnly: opts.activeOnly,
|
|
26651
|
+
limit,
|
|
26652
|
+
sinceLastActivityAt: opts.sinceLastActivityAt,
|
|
26653
|
+
sinceLastId: opts.sinceLastId,
|
|
26654
|
+
includeTaskIds,
|
|
26655
|
+
maxBytes: TASK_INDEX_SNAPSHOT_MAX_BYTES
|
|
26656
|
+
});
|
|
26657
|
+
}).then((result) => {
|
|
26658
|
+
emitTaskIndexSnapshot({
|
|
26659
|
+
result,
|
|
26660
|
+
controlHandler: getControlHandler(),
|
|
26661
|
+
logAdapter,
|
|
26662
|
+
cursorRequested,
|
|
26663
|
+
startedAt
|
|
26564
26664
|
});
|
|
26565
26665
|
const settled = taskIndexInflightCache.get(fingerprint);
|
|
26566
|
-
if (settled?.promise ===
|
|
26666
|
+
if (settled?.promise === requestPromise) {
|
|
26567
26667
|
settled.expiresAt = Date.now() + taskIndexInflightTtlMs;
|
|
26568
26668
|
}
|
|
26569
26669
|
}).catch((err3) => {
|
|
26570
26670
|
logAdapter({
|
|
26571
|
-
event: "
|
|
26671
|
+
event: "task_index_request_failed",
|
|
26572
26672
|
error: err3 instanceof Error ? err3.message : String(err3)
|
|
26573
26673
|
});
|
|
26574
26674
|
const failed = taskIndexInflightCache.get(fingerprint);
|
|
26575
|
-
if (failed?.promise ===
|
|
26675
|
+
if (failed?.promise === requestPromise) {
|
|
26576
26676
|
taskIndexInflightCache.delete(fingerprint);
|
|
26577
26677
|
}
|
|
26578
26678
|
});
|
|
26679
|
+
taskIndexInflightCache.set(fingerprint, {
|
|
26680
|
+
promise: requestPromise,
|
|
26681
|
+
expiresAt: now + taskIndexInflightTtlMs * 4
|
|
26682
|
+
});
|
|
26683
|
+
void requestPromise;
|
|
26579
26684
|
},
|
|
26580
26685
|
onRequestReviewTasks: () => {
|
|
26581
26686
|
daemon.taskStateStore.listTasks().then((all) => {
|
|
@@ -27042,6 +27147,10 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
27042
27147
|
chunk: true,
|
|
27043
27148
|
logAdapter
|
|
27044
27149
|
});
|
|
27150
|
+
let peerProtocolVersion = 0;
|
|
27151
|
+
function maybeCompress(json) {
|
|
27152
|
+
return maybeCompressFrame(json, peerProtocolVersion >= CONTROL_COMPRESSION_MIN_VERSION);
|
|
27153
|
+
}
|
|
27045
27154
|
function guardedSend(data) {
|
|
27046
27155
|
const result = guardedChannel.send(data);
|
|
27047
27156
|
if (result === "dropped" || result === "drain-pending") {
|
|
@@ -27240,6 +27349,9 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
27240
27349
|
daemon.taskManager.applyThreadSettings(taskId, threadId, applied);
|
|
27241
27350
|
logAdapter({ event: "thread_settings_updated", taskId, threadId, settings: applied });
|
|
27242
27351
|
},
|
|
27352
|
+
onPeerProtocolVersion: (version) => {
|
|
27353
|
+
peerProtocolVersion = version;
|
|
27354
|
+
},
|
|
27243
27355
|
onRequestCapabilities: ({ force } = {}) => {
|
|
27244
27356
|
if (force) {
|
|
27245
27357
|
void deps.capabilityRefresh.refreshFull("force_request");
|
|
@@ -27795,6 +27907,16 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
27795
27907
|
});
|
|
27796
27908
|
});
|
|
27797
27909
|
},
|
|
27910
|
+
onRequestVizContentBatch: (taskId) => {
|
|
27911
|
+
daemon.attachVizWatcher(taskId);
|
|
27912
|
+
daemon.pushTaskVizContent(taskId, (msg) => controlHandler.sendControl(msg)).catch((err3) => {
|
|
27913
|
+
logAdapter({
|
|
27914
|
+
event: "request_viz_content_batch_push_failed",
|
|
27915
|
+
taskId,
|
|
27916
|
+
error: err3 instanceof Error ? err3.message : String(err3)
|
|
27917
|
+
});
|
|
27918
|
+
});
|
|
27919
|
+
},
|
|
27798
27920
|
onCreateThread: (opts) => handleCreateThread({
|
|
27799
27921
|
opts,
|
|
27800
27922
|
daemon,
|
|
@@ -28176,7 +28298,8 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
28176
28298
|
receiveCursor: aloReceiveCursor,
|
|
28177
28299
|
rawSend: (json) => {
|
|
28178
28300
|
try {
|
|
28179
|
-
const
|
|
28301
|
+
const out = maybeCompress(json);
|
|
28302
|
+
const result = guardedChannel.send(out);
|
|
28180
28303
|
if (result === "dropped" || result === "drain-pending") {
|
|
28181
28304
|
const now = Date.now();
|
|
28182
28305
|
const { thresholdCrossed } = rawSendDropsCounter.record(now);
|
|
@@ -28228,7 +28351,16 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
28228
28351
|
error: err3 instanceof Error ? err3.message : String(err3)
|
|
28229
28352
|
});
|
|
28230
28353
|
});
|
|
28231
|
-
const bestEffort = createBestEffortSender({
|
|
28354
|
+
const bestEffort = createBestEffortSender({
|
|
28355
|
+
guardedChannel,
|
|
28356
|
+
logAdapter,
|
|
28357
|
+
controlPeerId,
|
|
28358
|
+
/**
|
|
28359
|
+
* Pass the compression gate so best-effort messages are also compressed for
|
|
28360
|
+
* v139+ peers. `maybeCompress` is a no-op for pre-v139 peers (safe default).
|
|
28361
|
+
*/
|
|
28362
|
+
compress: maybeCompress
|
|
28363
|
+
});
|
|
28232
28364
|
const aloSend = (msg) => {
|
|
28233
28365
|
if (classifyCriticality(msg) === "best-effort") {
|
|
28234
28366
|
bestEffort.send(msg);
|
|
@@ -28945,57 +29077,74 @@ function handleMessageChannel(opts) {
|
|
|
28945
29077
|
}).catch(() => {
|
|
28946
29078
|
});
|
|
28947
29079
|
}
|
|
28948
|
-
|
|
28949
|
-
const
|
|
28950
|
-
|
|
28951
|
-
|
|
28952
|
-
|
|
28953
|
-
|
|
28954
|
-
|
|
28955
|
-
|
|
28956
|
-
|
|
28957
|
-
|
|
28958
|
-
|
|
28959
|
-
|
|
28960
|
-
|
|
28961
|
-
|
|
28962
|
-
|
|
28963
|
-
|
|
28964
|
-
|
|
28965
|
-
|
|
28966
|
-
|
|
28967
|
-
|
|
28968
|
-
|
|
28969
|
-
|
|
28970
|
-
|
|
28971
|
-
|
|
28972
|
-
|
|
28973
|
-
|
|
28974
|
-
|
|
28975
|
-
|
|
28976
|
-
|
|
28977
|
-
|
|
28978
|
-
|
|
28979
|
-
|
|
28980
|
-
|
|
28981
|
-
|
|
28982
|
-
|
|
28983
|
-
|
|
28984
|
-
|
|
28985
|
-
|
|
28986
|
-
|
|
28987
|
-
|
|
28988
|
-
|
|
28989
|
-
|
|
28990
|
-
|
|
28991
|
-
|
|
29080
|
+
void taskManager.getTask(taskId).then((task) => {
|
|
29081
|
+
const clearedAtSeqNo = task?.clearedAtSeqNo;
|
|
29082
|
+
const sinceSeqNo = clearedAtSeqNo == null ? msg.sinceSeqNo : Math.max(msg.sinceSeqNo, clearedAtSeqNo + 1);
|
|
29083
|
+
if (msg.maxMessages && msg.sinceSeqNo === 0 && clearedAtSeqNo == null) {
|
|
29084
|
+
const limit = msg.maxMessages;
|
|
29085
|
+
store.getMessagesBefore(channelId, Number.POSITIVE_INFINITY, limit).then(({ messages: page }) => {
|
|
29086
|
+
const outgoing = {
|
|
29087
|
+
type: "catch_up_chunk",
|
|
29088
|
+
messages: page,
|
|
29089
|
+
cursor: null
|
|
29090
|
+
};
|
|
29091
|
+
send(JSON.stringify(outgoing));
|
|
29092
|
+
finalizeCatchUp();
|
|
29093
|
+
logSubscribeDone(page.length);
|
|
29094
|
+
}).catch((err3) => {
|
|
29095
|
+
flushBufferedMessages();
|
|
29096
|
+
sendError(formatError2(err3));
|
|
29097
|
+
logSubscribeDone(0);
|
|
29098
|
+
});
|
|
29099
|
+
} else if (msg.maxMessages) {
|
|
29100
|
+
store.getMessagesPaginated(channelId, sinceSeqNo, msg.maxMessages).then(({ messages, nextSeqNo }) => {
|
|
29101
|
+
const outgoing = {
|
|
29102
|
+
type: "catch_up_chunk",
|
|
29103
|
+
messages,
|
|
29104
|
+
cursor: nextSeqNo
|
|
29105
|
+
};
|
|
29106
|
+
send(JSON.stringify(outgoing));
|
|
29107
|
+
if (nextSeqNo === null) finalizeCatchUp();
|
|
29108
|
+
else flushBufferedMessages();
|
|
29109
|
+
logSubscribeDone(messages.length);
|
|
29110
|
+
}).catch((err3) => {
|
|
29111
|
+
flushBufferedMessages();
|
|
29112
|
+
sendError(formatError2(err3));
|
|
29113
|
+
logSubscribeDone(0);
|
|
29114
|
+
});
|
|
29115
|
+
} else {
|
|
29116
|
+
store.getMessagesSince(channelId, sinceSeqNo).then((messages) => {
|
|
29117
|
+
const outgoing = { type: "catch_up", messages };
|
|
29118
|
+
send(JSON.stringify(outgoing));
|
|
29119
|
+
finalizeCatchUp();
|
|
29120
|
+
logSubscribeDone(messages.length);
|
|
29121
|
+
}).catch((err3) => {
|
|
29122
|
+
flushBufferedMessages();
|
|
29123
|
+
sendError(formatError2(err3));
|
|
29124
|
+
logSubscribeDone(0);
|
|
29125
|
+
});
|
|
29126
|
+
}
|
|
29127
|
+
}).catch((err3) => {
|
|
29128
|
+
flushBufferedMessages();
|
|
29129
|
+
sendError(formatError2(err3));
|
|
29130
|
+
logSubscribeDone(0);
|
|
29131
|
+
});
|
|
28992
29132
|
}
|
|
28993
29133
|
function handleFetchOlder(msg) {
|
|
28994
|
-
|
|
29134
|
+
taskManager.getTask(taskId).then((task) => {
|
|
29135
|
+
const clearedAtSeqNo = task?.clearedAtSeqNo ?? -1;
|
|
29136
|
+
return store.getMessagesBefore(channelId, msg.beforeSeqNo, msg.limit).then((page) => ({
|
|
29137
|
+
...page,
|
|
29138
|
+
clearedAtSeqNo
|
|
29139
|
+
}));
|
|
29140
|
+
}).then(({ messages, hasMore, clearedAtSeqNo }) => {
|
|
29141
|
+
const visibleMessages = messages.filter((m2) => m2.seqNo > clearedAtSeqNo);
|
|
29142
|
+
const oldestVisibleSeqNo = visibleMessages[0]?.seqNo ?? null;
|
|
29143
|
+
const visibleHasMore = visibleMessages.length === messages.length && hasMore && oldestVisibleSeqNo !== null && oldestVisibleSeqNo > clearedAtSeqNo + 1;
|
|
28995
29144
|
const outgoing = {
|
|
28996
29145
|
type: "older_messages",
|
|
28997
|
-
messages,
|
|
28998
|
-
hasMore
|
|
29146
|
+
messages: visibleMessages,
|
|
29147
|
+
hasMore: visibleHasMore
|
|
28999
29148
|
};
|
|
29000
29149
|
send(JSON.stringify(outgoing));
|
|
29001
29150
|
}).catch((err3) => {
|
|
@@ -37508,9 +37657,14 @@ ${block.preview}` : `[File: ${block.filename}]`
|
|
|
37508
37657
|
}
|
|
37509
37658
|
}
|
|
37510
37659
|
}
|
|
37660
|
+
function resolveEntryRole(msg) {
|
|
37661
|
+
if (msg.content.some((block) => block.type === "tool_result")) return "user";
|
|
37662
|
+
return msg.senderKind === "human" ? "user" : "assistant";
|
|
37663
|
+
}
|
|
37511
37664
|
function portableMessageToEntry(msg, sessionId, parentUuid) {
|
|
37512
37665
|
const apiContent = msg.content.map(portableBlockToApiContent).filter((c) => c !== null);
|
|
37513
|
-
|
|
37666
|
+
const role = resolveEntryRole(msg);
|
|
37667
|
+
if (role === "user") {
|
|
37514
37668
|
const entry2 = {
|
|
37515
37669
|
type: "user",
|
|
37516
37670
|
uuid: msg.messageId,
|
|
@@ -38555,7 +38709,11 @@ import { randomUUID as randomUUID12 } from "crypto";
|
|
|
38555
38709
|
import { copyFile, rm as fsRm, stat as fsStat3, mkdir as mkdir17 } from "fs/promises";
|
|
38556
38710
|
import { basename as basename9, extname as extname2, join as join50, resolve as resolve8, sep as sep3 } from "path";
|
|
38557
38711
|
var TOOL_DESCRIPTION2 = [
|
|
38558
|
-
"Register a proof-of-work deliverable for this task
|
|
38712
|
+
"Register a proof-of-work deliverable for this task \u2014 proof a reviewer cannot get from the diff alone.",
|
|
38713
|
+
"",
|
|
38714
|
+
"WHEN: right after a change whose effect is not visible in the diff \u2014 a UI change (attach the rendered screenshot), a fix you proved by running tests (attach the passing log), a performance claim (attach the numbers), an accessibility audit (attach the AX tree).",
|
|
38715
|
+
"WHEN NOT: when the diff IS the proof \u2014 a refactor, a rename, a config edit verifies itself. Registering a deliverable then just costs the reviewer attention.",
|
|
38716
|
+
"EXAMPLE: after adding a PATCH route and confirming it returns 200, register the test-run log so the reviewer sees it passed without re-running it.",
|
|
38559
38717
|
"",
|
|
38560
38718
|
"SCOPING:",
|
|
38561
38719
|
" When the current task has a structured todo list (visible in the <task-list> resource), scope the deliverable to the specific todoItemId it proves. This gives reviewers granular evidence per task step.",
|
|
@@ -39742,11 +39900,12 @@ var VISUALIZE_DESCRIPTION = [
|
|
|
39742
39900
|
"",
|
|
39743
39901
|
"Provide content inline OR pass file_path to import from disk.",
|
|
39744
39902
|
"",
|
|
39745
|
-
"WHEN
|
|
39903
|
+
"WHEN a picture beats a paragraph (don't wait to be asked, but produce one when it changes what a reviewer understands \u2014 not by reflex):",
|
|
39746
39904
|
"- Architecture or system design -> mermaid",
|
|
39747
39905
|
"- UI work or layout -> html mockup",
|
|
39748
39906
|
"- Data or performance -> html with Chart.js/D3",
|
|
39749
|
-
"- State machines or workflows -> mermaid stateDiagram"
|
|
39907
|
+
"- State machines or workflows -> mermaid stateDiagram",
|
|
39908
|
+
"When prose already carries the idea, skip the diagram."
|
|
39750
39909
|
].join("\n");
|
|
39751
39910
|
var PRESENT_DESCRIPTION = [
|
|
39752
39911
|
"Display something somewhere.",
|
|
@@ -40139,7 +40298,21 @@ async function guardTaskTarget(args, callingTaskId, ctx) {
|
|
|
40139
40298
|
}
|
|
40140
40299
|
async function handleSendMessage(taskId, prompt, title, callingTaskId, ctx) {
|
|
40141
40300
|
try {
|
|
40142
|
-
|
|
40301
|
+
const callingSessionId = ctx.taskManager.getTaskState(callingTaskId)?.sessionId;
|
|
40302
|
+
if (!callingSessionId) {
|
|
40303
|
+
return errorResult("send_message failed: calling task has no active agent session.");
|
|
40304
|
+
}
|
|
40305
|
+
const agentParticipantId = buildAgentParticipantId(callingSessionId, ctx.agentSystem);
|
|
40306
|
+
await ctx.taskManager.handleUserMessage(
|
|
40307
|
+
taskId,
|
|
40308
|
+
[{ type: "text", text: `[From agent \xB7 task ${callingTaskId}]: ${prompt}` }],
|
|
40309
|
+
void 0,
|
|
40310
|
+
void 0,
|
|
40311
|
+
agentParticipantId,
|
|
40312
|
+
void 0,
|
|
40313
|
+
void 0,
|
|
40314
|
+
"agent"
|
|
40315
|
+
);
|
|
40143
40316
|
} catch (err3) {
|
|
40144
40317
|
const message = err3 instanceof Error ? err3.message : String(err3);
|
|
40145
40318
|
logger.error({
|
|
@@ -49800,6 +49973,7 @@ function buildHarnessTools(ctx) {
|
|
|
49800
49973
|
};
|
|
49801
49974
|
const taskControlCtx = {
|
|
49802
49975
|
callingTaskId: taskIdRef,
|
|
49976
|
+
agentSystem: ctx.agentSystem,
|
|
49803
49977
|
taskManager: ctx.taskManager,
|
|
49804
49978
|
taskStateStore: ctx.taskStateStore,
|
|
49805
49979
|
getWorktreeRunner: ctx.getWorktreeRunner,
|
|
@@ -52655,6 +52829,7 @@ var McpCoordinator = class {
|
|
|
52655
52829
|
}
|
|
52656
52830
|
this.#timer.cancelAll();
|
|
52657
52831
|
this.#deps.reauthOrchestrator.cancelAll();
|
|
52832
|
+
this.#deps.broadcaster.dispose();
|
|
52658
52833
|
await Promise.all([this.#deps.proxyTransport.dispose(), this.#deps.stdioTransport.dispose()]);
|
|
52659
52834
|
}
|
|
52660
52835
|
/** ===== private helpers ===== */
|
|
@@ -54728,22 +54903,165 @@ function defaultRequestId() {
|
|
|
54728
54903
|
return `reauth-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
54729
54904
|
}
|
|
54730
54905
|
|
|
54906
|
+
// src/services/event-batching.ts
|
|
54907
|
+
var EVENT_BATCHING = {
|
|
54908
|
+
/**
|
|
54909
|
+
* Comments: sliding window for review comment bursts.
|
|
54910
|
+
* 20 s idle matches observed user typing cadence in review sessions (#2218).
|
|
54911
|
+
*/
|
|
54912
|
+
comments: { idleMs: 2e4, maxWindowMs: 6e4 },
|
|
54913
|
+
/**
|
|
54914
|
+
* Resources: sliding window for file/plan/git/task pushes.
|
|
54915
|
+
*
|
|
54916
|
+
* Inverted in Wave 4 of the unified context-delivery refactor from 20s
|
|
54917
|
+
* to 100ms. The 20s window made agent-triggered updates (TaskCreate /
|
|
54918
|
+
* TaskUpdate, structured-task mutations, etc.) invisible to the agent
|
|
54919
|
+
* for up to 20s after the tool call returned — the agent's next turn
|
|
54920
|
+
* would be reasoning on stale state. 100ms still coalesces within-tick
|
|
54921
|
+
* bursts and absorbs CRDT keystroke storms via each resolver's existing
|
|
54922
|
+
* projection cache (task-metadata, branch, canvas all gate no-op
|
|
54923
|
+
* re-emissions before the timer ever sees them).
|
|
54924
|
+
*
|
|
54925
|
+
* `maxWindowMs` retains the 60s cap as a guardrail for pathological
|
|
54926
|
+
* never-quiet sources.
|
|
54927
|
+
*/
|
|
54928
|
+
resources: { idleMs: 100, maxWindowMs: 6e4 },
|
|
54929
|
+
/** Messages: sliding window for collab message micro-batching. */
|
|
54930
|
+
messages: { idleMs: 250, maxWindowMs: 3e4 },
|
|
54931
|
+
/**
|
|
54932
|
+
* MCP broadcaster: coalesce FSM-transition storms during MCP churn.
|
|
54933
|
+
*
|
|
54934
|
+
* Idle 150 ms, max 250 ms — the toggle-feel contract today is ~50 ms via
|
|
54935
|
+
* the direct `pushWorkspaceMcpStateSnapshot` path in `applyWorkspaceMcpToggle`
|
|
54936
|
+
* (which bypasses this debounce entirely). This window only applies to
|
|
54937
|
+
* daemon-internal FSM transitions that fire during background MCP churn
|
|
54938
|
+
* (reconnects, auth probes, plugin reloads). 250 ms max keeps the UI snappy
|
|
54939
|
+
* while collapsing the ~900 sends / 10s burst seen during MCP churn.
|
|
54940
|
+
*/
|
|
54941
|
+
mcp: { idleMs: 150, maxWindowMs: 250 }
|
|
54942
|
+
};
|
|
54943
|
+
var MAX_COLLAB_BATCH_SIZE = 4;
|
|
54944
|
+
var SlidingWindowTimer = class {
|
|
54945
|
+
#config;
|
|
54946
|
+
#onFlush;
|
|
54947
|
+
#timer = null;
|
|
54948
|
+
#windowStart = null;
|
|
54949
|
+
#lastScheduleAt = null;
|
|
54950
|
+
#disposed = false;
|
|
54951
|
+
constructor(config, onFlush) {
|
|
54952
|
+
this.#config = config;
|
|
54953
|
+
this.#onFlush = onFlush;
|
|
54954
|
+
}
|
|
54955
|
+
/**
|
|
54956
|
+
* Schedule or reschedule the flush timer.
|
|
54957
|
+
*
|
|
54958
|
+
* - First call in a window: records `windowStart`, starts idle timer.
|
|
54959
|
+
* - Subsequent calls: resets idle timer (sliding window).
|
|
54960
|
+
* - If `maxWindowMs` exceeded: fires flush immediately.
|
|
54961
|
+
*/
|
|
54962
|
+
schedule() {
|
|
54963
|
+
if (this.#disposed) return;
|
|
54964
|
+
if (this.#windowStart === null) {
|
|
54965
|
+
this.#windowStart = Date.now();
|
|
54966
|
+
} else if (Date.now() - this.#windowStart >= this.#config.maxWindowMs) {
|
|
54967
|
+
this.#clearTimer();
|
|
54968
|
+
this.#windowStart = null;
|
|
54969
|
+
this.#lastScheduleAt = null;
|
|
54970
|
+
this.#onFlush();
|
|
54971
|
+
return;
|
|
54972
|
+
}
|
|
54973
|
+
this.#clearTimer();
|
|
54974
|
+
this.#lastScheduleAt = Date.now();
|
|
54975
|
+
this.#timer = setTimeout(() => {
|
|
54976
|
+
this.#timer = null;
|
|
54977
|
+
this.#windowStart = null;
|
|
54978
|
+
this.#lastScheduleAt = null;
|
|
54979
|
+
this.#onFlush();
|
|
54980
|
+
}, this.#config.idleMs);
|
|
54981
|
+
}
|
|
54982
|
+
/** Cancel the pending timer without resetting the window. */
|
|
54983
|
+
cancel() {
|
|
54984
|
+
this.#clearTimer();
|
|
54985
|
+
}
|
|
54986
|
+
/** Reset the window start (call after draining the batch). */
|
|
54987
|
+
reset() {
|
|
54988
|
+
this.#windowStart = null;
|
|
54989
|
+
this.#lastScheduleAt = null;
|
|
54990
|
+
}
|
|
54991
|
+
/**
|
|
54992
|
+
* Epoch-ms at which the idle timer will fire, or null if no timer is
|
|
54993
|
+
* scheduled. Tracks the most recent `schedule()` call (sliding window),
|
|
54994
|
+
* not the original windowStart — used by ResourcePushManager to emit
|
|
54995
|
+
* accurate `flushAt` values for all dirty URIs after each timer slide.
|
|
54996
|
+
*/
|
|
54997
|
+
get nextFireAt() {
|
|
54998
|
+
if (this.#timer === null || this.#lastScheduleAt === null) return null;
|
|
54999
|
+
return this.#lastScheduleAt + this.#config.idleMs;
|
|
55000
|
+
}
|
|
55001
|
+
/** Permanently shut down. */
|
|
55002
|
+
dispose() {
|
|
55003
|
+
this.#clearTimer();
|
|
55004
|
+
this.#windowStart = null;
|
|
55005
|
+
this.#lastScheduleAt = null;
|
|
55006
|
+
this.#disposed = true;
|
|
55007
|
+
}
|
|
55008
|
+
#clearTimer() {
|
|
55009
|
+
if (this.#timer !== null) {
|
|
55010
|
+
clearTimeout(this.#timer);
|
|
55011
|
+
this.#timer = null;
|
|
55012
|
+
}
|
|
55013
|
+
}
|
|
55014
|
+
};
|
|
55015
|
+
|
|
54731
55016
|
// src/services/mcp/push/status-broadcaster.ts
|
|
54732
55017
|
var StatusBroadcaster = class {
|
|
54733
55018
|
#deps;
|
|
55019
|
+
/** Latest coalesced payload — replaced on every broadcast() call. */
|
|
55020
|
+
#pending = null;
|
|
55021
|
+
#timer;
|
|
55022
|
+
/** Serial flush promise — deferred per Invariant #11 (no sync re-entry). */
|
|
55023
|
+
#flushInProgress = Promise.resolve();
|
|
55024
|
+
#disposed = false;
|
|
54734
55025
|
constructor(deps) {
|
|
54735
55026
|
this.#deps = deps;
|
|
55027
|
+
this.#timer = new SlidingWindowTimer(EVENT_BATCHING.mcp, () => {
|
|
55028
|
+
this.#flushInProgress = this.#flushInProgress.then(() => this.#flush());
|
|
55029
|
+
});
|
|
54736
55030
|
}
|
|
54737
55031
|
/**
|
|
54738
|
-
*
|
|
55032
|
+
* Schedule a coalesced broadcast of the current snapshot to all peers.
|
|
54739
55033
|
*
|
|
54740
55034
|
* `disabledNames` is the live workspace VISIBILITY set from the coordinator.
|
|
54741
55035
|
* Required per-call — without it every FSM transition re-broadcasts
|
|
54742
55036
|
* `visible:true` and silently clobbers a persisted workspace-disable (the
|
|
54743
55037
|
* #4203 regression). It arrives per-call rather than at construction so the
|
|
54744
55038
|
* broadcaster stays a thin push adapter with no visibility-state of its own.
|
|
55039
|
+
*
|
|
55040
|
+
* Multiple calls within the 150ms idle / 250ms max window collapse to one
|
|
55041
|
+
* delivery of the LATEST pair.
|
|
54745
55042
|
*/
|
|
54746
55043
|
broadcast(servers, disabledNames) {
|
|
55044
|
+
if (this.#disposed) return;
|
|
55045
|
+
this.#pending = { servers, disabledNames };
|
|
55046
|
+
this.#timer.schedule();
|
|
55047
|
+
}
|
|
55048
|
+
/**
|
|
55049
|
+
* Permanently shut down. Cancels the pending timer and discards any
|
|
55050
|
+
* coalesced snapshot — the next channel attach re-pushes fresh MCP state
|
|
55051
|
+
* via `attach-snapshot.ts:78` (`sendMcpStatusSnapshot`), so a discarded
|
|
55052
|
+
* pending snapshot self-heals on reconnect.
|
|
55053
|
+
*/
|
|
55054
|
+
dispose() {
|
|
55055
|
+
this.#timer.dispose();
|
|
55056
|
+
this.#pending = null;
|
|
55057
|
+
this.#disposed = true;
|
|
55058
|
+
}
|
|
55059
|
+
#flush() {
|
|
55060
|
+
if (this.#disposed) return Promise.resolve();
|
|
55061
|
+
const snapshot = this.#pending;
|
|
55062
|
+
this.#pending = null;
|
|
55063
|
+
if (snapshot === null) return Promise.resolve();
|
|
55064
|
+
const { servers, disabledNames } = snapshot;
|
|
54747
55065
|
try {
|
|
54748
55066
|
this.#deps.send({ type: "mcp_server_status", servers });
|
|
54749
55067
|
} catch (err3) {
|
|
@@ -54762,6 +55080,7 @@ var StatusBroadcaster = class {
|
|
|
54762
55080
|
error: err3 instanceof Error ? err3.message : String(err3)
|
|
54763
55081
|
});
|
|
54764
55082
|
}
|
|
55083
|
+
return Promise.resolve();
|
|
54765
55084
|
}
|
|
54766
55085
|
};
|
|
54767
55086
|
|
|
@@ -66250,6 +66569,7 @@ var VisualizationFileWatcher = class {
|
|
|
66250
66569
|
title: effect.title,
|
|
66251
66570
|
content: effect.content
|
|
66252
66571
|
});
|
|
66572
|
+
this.#updateCanvasElementContent(effect);
|
|
66253
66573
|
break;
|
|
66254
66574
|
}
|
|
66255
66575
|
case "persist_registry":
|
|
@@ -66468,7 +66788,7 @@ var VisualizationFileWatcher = class {
|
|
|
66468
66788
|
title: effect.title,
|
|
66469
66789
|
content
|
|
66470
66790
|
});
|
|
66471
|
-
const data = { slug: effect.slug, title: effect.title };
|
|
66791
|
+
const data = { slug: effect.slug, title: effect.title, content };
|
|
66472
66792
|
const nodeId = this.#deps.canvasRepo.createElement(effect.taskId, {
|
|
66473
66793
|
type: effect.vizType,
|
|
66474
66794
|
x: position.x,
|
|
@@ -66481,6 +66801,23 @@ var VisualizationFileWatcher = class {
|
|
|
66481
66801
|
const followUp = this.registry.setCanvasElementId(effect.slug, `${nodeId}`);
|
|
66482
66802
|
await this.executeEffects(followUp);
|
|
66483
66803
|
}
|
|
66804
|
+
/**
|
|
66805
|
+
* Mirror the latest viz HTML into the canvas CRDT element's `data.content`
|
|
66806
|
+
* on hot-reload so the inline-content reload fallback (`html-element.tsx`)
|
|
66807
|
+
* stays current. Reads the existing element to preserve `slug`/`title`, then
|
|
66808
|
+
* writes the merged `data` blob. A missing element (evicted/raced) is a
|
|
66809
|
+
* no-op — the `viz_content` control push already covers the live path.
|
|
66810
|
+
*/
|
|
66811
|
+
#updateCanvasElementContent(effect) {
|
|
66812
|
+
const elements = this.#deps.canvasRepo.getElements(effect.taskId);
|
|
66813
|
+
const target = elements.find((el) => `${el.id}` === effect.elementId);
|
|
66814
|
+
if (!target) return;
|
|
66815
|
+
const existing = target.data.data;
|
|
66816
|
+
const existingData = typeof existing === "object" && existing !== null && !Array.isArray(existing) ? existing : {};
|
|
66817
|
+
this.#deps.canvasRepo.updateElement(effect.taskId, target.id, {
|
|
66818
|
+
data: { ...existingData, slug: effect.slug, title: effect.title, content: effect.content }
|
|
66819
|
+
});
|
|
66820
|
+
}
|
|
66484
66821
|
async #handleFileChange(filePath) {
|
|
66485
66822
|
let content;
|
|
66486
66823
|
try {
|
|
@@ -74257,105 +74594,6 @@ function drainPendingRuntimeSwitch(ctx, taskId) {
|
|
|
74257
74594
|
);
|
|
74258
74595
|
}
|
|
74259
74596
|
|
|
74260
|
-
// src/services/event-batching.ts
|
|
74261
|
-
var EVENT_BATCHING = {
|
|
74262
|
-
/**
|
|
74263
|
-
* Comments: sliding window for review comment bursts.
|
|
74264
|
-
* 20 s idle matches observed user typing cadence in review sessions (#2218).
|
|
74265
|
-
*/
|
|
74266
|
-
comments: { idleMs: 2e4, maxWindowMs: 6e4 },
|
|
74267
|
-
/**
|
|
74268
|
-
* Resources: sliding window for file/plan/git/task pushes.
|
|
74269
|
-
*
|
|
74270
|
-
* Inverted in Wave 4 of the unified context-delivery refactor from 20s
|
|
74271
|
-
* to 100ms. The 20s window made agent-triggered updates (TaskCreate /
|
|
74272
|
-
* TaskUpdate, structured-task mutations, etc.) invisible to the agent
|
|
74273
|
-
* for up to 20s after the tool call returned — the agent's next turn
|
|
74274
|
-
* would be reasoning on stale state. 100ms still coalesces within-tick
|
|
74275
|
-
* bursts and absorbs CRDT keystroke storms via each resolver's existing
|
|
74276
|
-
* projection cache (task-metadata, branch, canvas all gate no-op
|
|
74277
|
-
* re-emissions before the timer ever sees them).
|
|
74278
|
-
*
|
|
74279
|
-
* `maxWindowMs` retains the 60s cap as a guardrail for pathological
|
|
74280
|
-
* never-quiet sources.
|
|
74281
|
-
*/
|
|
74282
|
-
resources: { idleMs: 100, maxWindowMs: 6e4 },
|
|
74283
|
-
/** Messages: sliding window for collab message micro-batching. */
|
|
74284
|
-
messages: { idleMs: 250, maxWindowMs: 3e4 }
|
|
74285
|
-
};
|
|
74286
|
-
var MAX_COLLAB_BATCH_SIZE = 4;
|
|
74287
|
-
var SlidingWindowTimer = class {
|
|
74288
|
-
#config;
|
|
74289
|
-
#onFlush;
|
|
74290
|
-
#timer = null;
|
|
74291
|
-
#windowStart = null;
|
|
74292
|
-
#lastScheduleAt = null;
|
|
74293
|
-
#disposed = false;
|
|
74294
|
-
constructor(config, onFlush) {
|
|
74295
|
-
this.#config = config;
|
|
74296
|
-
this.#onFlush = onFlush;
|
|
74297
|
-
}
|
|
74298
|
-
/**
|
|
74299
|
-
* Schedule or reschedule the flush timer.
|
|
74300
|
-
*
|
|
74301
|
-
* - First call in a window: records `windowStart`, starts idle timer.
|
|
74302
|
-
* - Subsequent calls: resets idle timer (sliding window).
|
|
74303
|
-
* - If `maxWindowMs` exceeded: fires flush immediately.
|
|
74304
|
-
*/
|
|
74305
|
-
schedule() {
|
|
74306
|
-
if (this.#disposed) return;
|
|
74307
|
-
if (this.#windowStart === null) {
|
|
74308
|
-
this.#windowStart = Date.now();
|
|
74309
|
-
} else if (Date.now() - this.#windowStart >= this.#config.maxWindowMs) {
|
|
74310
|
-
this.#clearTimer();
|
|
74311
|
-
this.#windowStart = null;
|
|
74312
|
-
this.#lastScheduleAt = null;
|
|
74313
|
-
this.#onFlush();
|
|
74314
|
-
return;
|
|
74315
|
-
}
|
|
74316
|
-
this.#clearTimer();
|
|
74317
|
-
this.#lastScheduleAt = Date.now();
|
|
74318
|
-
this.#timer = setTimeout(() => {
|
|
74319
|
-
this.#timer = null;
|
|
74320
|
-
this.#windowStart = null;
|
|
74321
|
-
this.#lastScheduleAt = null;
|
|
74322
|
-
this.#onFlush();
|
|
74323
|
-
}, this.#config.idleMs);
|
|
74324
|
-
}
|
|
74325
|
-
/** Cancel the pending timer without resetting the window. */
|
|
74326
|
-
cancel() {
|
|
74327
|
-
this.#clearTimer();
|
|
74328
|
-
}
|
|
74329
|
-
/** Reset the window start (call after draining the batch). */
|
|
74330
|
-
reset() {
|
|
74331
|
-
this.#windowStart = null;
|
|
74332
|
-
this.#lastScheduleAt = null;
|
|
74333
|
-
}
|
|
74334
|
-
/**
|
|
74335
|
-
* Epoch-ms at which the idle timer will fire, or null if no timer is
|
|
74336
|
-
* scheduled. Tracks the most recent `schedule()` call (sliding window),
|
|
74337
|
-
* not the original windowStart — used by ResourcePushManager to emit
|
|
74338
|
-
* accurate `flushAt` values for all dirty URIs after each timer slide.
|
|
74339
|
-
*/
|
|
74340
|
-
get nextFireAt() {
|
|
74341
|
-
if (this.#timer === null || this.#lastScheduleAt === null) return null;
|
|
74342
|
-
return this.#lastScheduleAt + this.#config.idleMs;
|
|
74343
|
-
}
|
|
74344
|
-
/** Permanently shut down. */
|
|
74345
|
-
dispose() {
|
|
74346
|
-
this.#clearTimer();
|
|
74347
|
-
this.#windowStart = null;
|
|
74348
|
-
this.#lastScheduleAt = null;
|
|
74349
|
-
this.#disposed = true;
|
|
74350
|
-
}
|
|
74351
|
-
#clearTimer() {
|
|
74352
|
-
if (this.#timer !== null) {
|
|
74353
|
-
clearTimeout(this.#timer);
|
|
74354
|
-
this.#timer = null;
|
|
74355
|
-
}
|
|
74356
|
-
}
|
|
74357
|
-
};
|
|
74358
|
-
|
|
74359
74597
|
// src/services/collab/collab-message-queue.ts
|
|
74360
74598
|
var UNKNOWN_PARTICIPANT = "__unknown__";
|
|
74361
74599
|
function formatCollabBatch(batch) {
|
|
@@ -78346,6 +78584,8 @@ var Thread = class {
|
|
|
78346
78584
|
#sendControlMessage;
|
|
78347
78585
|
#streamDelta = new ThreadStreamDeltaForwarder();
|
|
78348
78586
|
#latestSettings = {};
|
|
78587
|
+
#lastApiUsage = null;
|
|
78588
|
+
#lastTurnResult = null;
|
|
78349
78589
|
/**
|
|
78350
78590
|
* Last values pushed to the live subprocess. Reset to undefined whenever
|
|
78351
78591
|
* a fresh subprocess is attached (settings get applied via spawn options
|
|
@@ -79227,6 +79467,7 @@ var Thread = class {
|
|
|
79227
79467
|
return;
|
|
79228
79468
|
}
|
|
79229
79469
|
if (event.type === "api_usage_snapshot") {
|
|
79470
|
+
this.#lastApiUsage = event;
|
|
79230
79471
|
this.#callbacks.onSubprocessEvent?.(event);
|
|
79231
79472
|
return;
|
|
79232
79473
|
}
|
|
@@ -79365,6 +79606,7 @@ var Thread = class {
|
|
|
79365
79606
|
this.#recovery.resetRetriesOnTurnComplete();
|
|
79366
79607
|
this.#cursorRecovery.reset();
|
|
79367
79608
|
this.#rateLimitFlap.noteRecoveredOnTurnComplete((event.result.outputTokens ?? 0) > 0);
|
|
79609
|
+
this.#emitThreadTurnStats(event.result);
|
|
79368
79610
|
this.#manager.notifyTurnComplete();
|
|
79369
79611
|
break;
|
|
79370
79612
|
case "sdk_error":
|
|
@@ -79498,6 +79740,27 @@ var Thread = class {
|
|
|
79498
79740
|
}
|
|
79499
79741
|
this.#callbacks.onSubprocessEvent?.(event);
|
|
79500
79742
|
}
|
|
79743
|
+
#emitThreadTurnStats(result) {
|
|
79744
|
+
if (!this.#config.permissionThreadId) return;
|
|
79745
|
+
const prevResult = this.#lastTurnResult;
|
|
79746
|
+
this.#lastTurnResult = result;
|
|
79747
|
+
const stats = buildTurnStatsMessage(
|
|
79748
|
+
this.#config.permissionTaskId ?? this.#config.threadId,
|
|
79749
|
+
result,
|
|
79750
|
+
null,
|
|
79751
|
+
this.#lastApiUsage,
|
|
79752
|
+
prevResult,
|
|
79753
|
+
void 0,
|
|
79754
|
+
this.#latestSettings.model ?? void 0,
|
|
79755
|
+
null
|
|
79756
|
+
);
|
|
79757
|
+
this.#lastApiUsage = null;
|
|
79758
|
+
if (stats.type !== "turn_stats") return;
|
|
79759
|
+
this.#sendControlMessage?.({
|
|
79760
|
+
...stats,
|
|
79761
|
+
threadId: this.#config.permissionThreadId
|
|
79762
|
+
});
|
|
79763
|
+
}
|
|
79501
79764
|
};
|
|
79502
79765
|
|
|
79503
79766
|
// src/services/plan/plan-handler.ts
|
|
@@ -79511,7 +79774,12 @@ import { dirname as dirname34, join as join72 } from "path";
|
|
|
79511
79774
|
var SETTLE_BUDGET_MS = 2e3;
|
|
79512
79775
|
async function ensurePlanDocRebuilt(deps) {
|
|
79513
79776
|
try {
|
|
79514
|
-
|
|
79777
|
+
const verdict = await settleAndCheckContent(deps);
|
|
79778
|
+
if (verdict === "has-content") return { kind: "has-content" };
|
|
79779
|
+
if (verdict === "storage-error") {
|
|
79780
|
+
deps.log({ event: "plan_lazy_rebuild_storage_error", taskId: deps.taskId });
|
|
79781
|
+
return { kind: "storage-error" };
|
|
79782
|
+
}
|
|
79515
79783
|
const sources = await gatherBackfillSources(
|
|
79516
79784
|
deps.taskRecord,
|
|
79517
79785
|
deps.taskId,
|
|
@@ -79553,7 +79821,9 @@ async function settleAndCheckContent(deps) {
|
|
|
79553
79821
|
await sync(doc).waitForSync({ kind: "storage", timeout: SETTLE_BUDGET_MS });
|
|
79554
79822
|
} catch {
|
|
79555
79823
|
}
|
|
79556
|
-
|
|
79824
|
+
if (deps.planRepo.getContent(deps.taskId).trim() !== "") return "has-content";
|
|
79825
|
+
const onDisk = await (deps.hasOnDiskDoc?.() ?? deps.planRepo.hasOnDiskDoc(deps.taskId));
|
|
79826
|
+
return onDisk === true ? "storage-error" : "absent";
|
|
79557
79827
|
}
|
|
79558
79828
|
|
|
79559
79829
|
// src/services/plan/plan-detection-policy.ts
|
|
@@ -79622,6 +79892,21 @@ var PlanFileBridge = class _PlanFileBridge {
|
|
|
79622
79892
|
#disposed = false;
|
|
79623
79893
|
#hasAttachedContent = false;
|
|
79624
79894
|
#writeBackInProgress = null;
|
|
79895
|
+
/**
|
|
79896
|
+
* Pending FM-8 re-attach scheduled after a recoverable wasm panic. Cancelled
|
|
79897
|
+
* on dispose so a torn-down bridge never re-attaches against a dead task.
|
|
79898
|
+
*/
|
|
79899
|
+
#reattachTimer = null;
|
|
79900
|
+
/**
|
|
79901
|
+
* One-shot loop guard: true while a panic-recovery re-attach is scheduled or
|
|
79902
|
+
* running. The re-attach itself calls `updateContent`, which can panic again
|
|
79903
|
+
* (on-disk chunks still poisoned). Without this gate that second panic would
|
|
79904
|
+
* schedule a third re-attach, and so on — a hot recovery loop. Reset only when
|
|
79905
|
+
* the re-attach finishes, so a single bounded retry fires per poisoning and a
|
|
79906
|
+
* persistently-poisoned doc falls back to the next real file-change event (the
|
|
79907
|
+
* prior recovery cadence), never a spin.
|
|
79908
|
+
*/
|
|
79909
|
+
#reattachAfterPanicInFlight = false;
|
|
79625
79910
|
static WRITE_BACK_DEBOUNCE_MS = 1e3;
|
|
79626
79911
|
static FILE_READ_DEBOUNCE_MS = 500;
|
|
79627
79912
|
#lastFileReadTime = 0;
|
|
@@ -79664,6 +79949,10 @@ var PlanFileBridge = class _PlanFileBridge {
|
|
|
79664
79949
|
*/
|
|
79665
79950
|
async attach(filePath) {
|
|
79666
79951
|
if (this.#disposed) return;
|
|
79952
|
+
if (this.#reattachTimer) {
|
|
79953
|
+
clearTimeout(this.#reattachTimer);
|
|
79954
|
+
this.#reattachTimer = null;
|
|
79955
|
+
}
|
|
79667
79956
|
void this.#dirWatcher?.unsubscribe();
|
|
79668
79957
|
this.#dirWatcher = null;
|
|
79669
79958
|
this.#crdtUnsub?.();
|
|
@@ -79905,10 +80194,46 @@ var PlanFileBridge = class _PlanFileBridge {
|
|
|
79905
80194
|
callSite: "plan-file-bridge.updateContent",
|
|
79906
80195
|
errorMessage: err3 instanceof Error ? err3.message : String(err3)
|
|
79907
80196
|
});
|
|
80197
|
+
this.#scheduleReattachAfterPanic();
|
|
79908
80198
|
}
|
|
79909
80199
|
return err3;
|
|
79910
80200
|
}
|
|
79911
80201
|
}
|
|
80202
|
+
/**
|
|
80203
|
+
* FM-8: re-attach to the current file after a recoverable wasm panic so the
|
|
80204
|
+
* fresh (post-eviction) CRDT is repopulated immediately. One-shot per
|
|
80205
|
+
* poisoning via `#reattachAfterPanicInFlight` — a re-attach that panics again
|
|
80206
|
+
* does NOT reschedule; recovery then falls back to the next real file-change
|
|
80207
|
+
* event. No-op when disposed, when there is no file to re-read, or when a
|
|
80208
|
+
* re-attach is already pending/running.
|
|
80209
|
+
*/
|
|
80210
|
+
#scheduleReattachAfterPanic() {
|
|
80211
|
+
if (this.#disposed || !this.#filePath || this.#reattachAfterPanicInFlight) return;
|
|
80212
|
+
this.#reattachAfterPanicInFlight = true;
|
|
80213
|
+
const filePath = this.#filePath;
|
|
80214
|
+
this.#reattachTimer = setTimeout(() => {
|
|
80215
|
+
this.#reattachTimer = null;
|
|
80216
|
+
if (this.#disposed) {
|
|
80217
|
+
this.#reattachAfterPanicInFlight = false;
|
|
80218
|
+
return;
|
|
80219
|
+
}
|
|
80220
|
+
this.#config.log({
|
|
80221
|
+
event: "plan_bridge_reattach_after_panic",
|
|
80222
|
+
taskId: this.#config.taskId,
|
|
80223
|
+
filePath
|
|
80224
|
+
});
|
|
80225
|
+
this.attach(filePath).catch((err3) => {
|
|
80226
|
+
this.#config.log({
|
|
80227
|
+
event: "plan_bridge_reattach_after_panic_failed",
|
|
80228
|
+
taskId: this.#config.taskId,
|
|
80229
|
+
filePath,
|
|
80230
|
+
error: err3 instanceof Error ? err3.message : String(err3)
|
|
80231
|
+
});
|
|
80232
|
+
}).finally(() => {
|
|
80233
|
+
this.#reattachAfterPanicInFlight = false;
|
|
80234
|
+
});
|
|
80235
|
+
}, 0);
|
|
80236
|
+
}
|
|
79912
80237
|
/**
|
|
79913
80238
|
* Subscribe to CRDT changes (from browser edits arriving via WebRTC).
|
|
79914
80239
|
* Debounced write-back to the plan file on disk.
|
|
@@ -80016,6 +80341,10 @@ var PlanFileBridge = class _PlanFileBridge {
|
|
|
80016
80341
|
clearTimeout(this.#writeBackTimer);
|
|
80017
80342
|
this.#writeBackTimer = null;
|
|
80018
80343
|
}
|
|
80344
|
+
if (this.#reattachTimer) {
|
|
80345
|
+
clearTimeout(this.#reattachTimer);
|
|
80346
|
+
this.#reattachTimer = null;
|
|
80347
|
+
}
|
|
80019
80348
|
}
|
|
80020
80349
|
/**
|
|
80021
80350
|
* Test-only: seed internal state so `flushWriteBack` can be exercised
|
|
@@ -84597,6 +84926,7 @@ function dispatchToCompactionSM(event, profile, now) {
|
|
|
84597
84926
|
// src/services/task/orchestrator/task-event-dispatch.ts
|
|
84598
84927
|
var AUTO_COMPACTION_CANCEL_DELAY_MS = 3e3;
|
|
84599
84928
|
var AUTO_COMPACTION_REARM_DELTA = 0.05;
|
|
84929
|
+
var TASK_SNAPSHOT_URI_PREFIX2 = "shipyard://task-snapshot/";
|
|
84600
84930
|
var AUTO_COMPACTION_MESSAGE_CEILING = 3e3;
|
|
84601
84931
|
function decideAutoCompaction(params) {
|
|
84602
84932
|
if (params.threshold === null) return { kind: "noop" };
|
|
@@ -84830,9 +85160,21 @@ function handleToolResultEcho(ctx, event) {
|
|
|
84830
85160
|
ctx.resolveStructuredTaskCreates(event.content);
|
|
84831
85161
|
ctx.handleWorktreeToolResults(event.content);
|
|
84832
85162
|
ctx.handleSubagentToolResults(event.content);
|
|
85163
|
+
trackTaskSnapshotLinksFromToolResults(ctx, event.content);
|
|
84833
85164
|
ctx.trackPlanFileCreation(event.content);
|
|
84834
85165
|
ctx.triggerCommitScan();
|
|
84835
85166
|
}
|
|
85167
|
+
function trackTaskSnapshotLinksFromToolResults(ctx, content) {
|
|
85168
|
+
for (const block of content) {
|
|
85169
|
+
if (block.type !== "tool_result" || typeof block.content !== "string") continue;
|
|
85170
|
+
if (!block.content.includes(TASK_SNAPSHOT_URI_PREFIX2)) continue;
|
|
85171
|
+
for (const uri of extractToolResultResourceUris(block.content)) {
|
|
85172
|
+
if (uri.startsWith(TASK_SNAPSHOT_URI_PREFIX2)) {
|
|
85173
|
+
ctx.trackTaskSnapshotResource(uri);
|
|
85174
|
+
}
|
|
85175
|
+
}
|
|
85176
|
+
}
|
|
85177
|
+
}
|
|
84836
85178
|
function resolveCompactionEvents(ctx) {
|
|
84837
85179
|
const parsed = AgentIdSchema.safeParse(ctx.getRuntimeId());
|
|
84838
85180
|
if (!parsed.success) return null;
|
|
@@ -87164,6 +87506,12 @@ function applyDepEdgeAdditions(merged, depEdges) {
|
|
|
87164
87506
|
}
|
|
87165
87507
|
}
|
|
87166
87508
|
}
|
|
87509
|
+
function findOverlayTask(overlay, taskId) {
|
|
87510
|
+
return overlay?.userTasks.find((task) => task.id === taskId);
|
|
87511
|
+
}
|
|
87512
|
+
function getStructuredTaskForUpdate(tasks, overlay, taskId) {
|
|
87513
|
+
return tasks.get(taskId) ?? findOverlayTask(overlay, taskId);
|
|
87514
|
+
}
|
|
87167
87515
|
function applyCapabilitiesOverrides(merged, overrides) {
|
|
87168
87516
|
if (!overrides) return;
|
|
87169
87517
|
for (const [id, caps] of Object.entries(overrides)) {
|
|
@@ -87487,22 +87835,16 @@ var StructuredTaskTracker = class {
|
|
|
87487
87835
|
}
|
|
87488
87836
|
return false;
|
|
87489
87837
|
}
|
|
87490
|
-
const existing =
|
|
87838
|
+
const existing = getStructuredTaskForUpdate(
|
|
87839
|
+
this.#structuredTasks,
|
|
87840
|
+
this.#currentOverlay,
|
|
87841
|
+
taskId
|
|
87842
|
+
);
|
|
87491
87843
|
if (existing) {
|
|
87492
87844
|
const prevStatus = existing.status;
|
|
87493
87845
|
const updated = applyStructuredTaskChanges(existing, changes);
|
|
87494
87846
|
this.#structuredTasks.set(taskId, updated);
|
|
87495
|
-
|
|
87496
|
-
const caps = this.#resolveEffectiveCaps(taskId, updated);
|
|
87497
|
-
this.#maybeFireItemActivated(taskId, caps);
|
|
87498
|
-
}
|
|
87499
|
-
return true;
|
|
87500
|
-
}
|
|
87501
|
-
const overlayTask = this.#currentOverlay?.userTasks.find((t) => t.id === taskId);
|
|
87502
|
-
if (overlayTask) {
|
|
87503
|
-
const prevStatus = overlayTask.status;
|
|
87504
|
-
const updated = applyStructuredTaskChanges(overlayTask, changes);
|
|
87505
|
-
this.#structuredTasks.set(taskId, updated);
|
|
87847
|
+
this.#applyReciprocalDependencyChanges(taskId, changes);
|
|
87506
87848
|
if (prevStatus !== "in_progress" && updated.status === "in_progress") {
|
|
87507
87849
|
const caps = this.#resolveEffectiveCaps(taskId, updated);
|
|
87508
87850
|
this.#maybeFireItemActivated(taskId, caps);
|
|
@@ -87516,6 +87858,36 @@ var StructuredTaskTracker = class {
|
|
|
87516
87858
|
});
|
|
87517
87859
|
return false;
|
|
87518
87860
|
}
|
|
87861
|
+
#applyReciprocalDependencyChanges(taskId, changes) {
|
|
87862
|
+
for (const blockedTaskId of changes.addBlocks ?? []) {
|
|
87863
|
+
const blockedTask = getStructuredTaskForUpdate(
|
|
87864
|
+
this.#structuredTasks,
|
|
87865
|
+
this.#currentOverlay,
|
|
87866
|
+
blockedTaskId
|
|
87867
|
+
);
|
|
87868
|
+
if (blockedTask && !blockedTask.blockedBy.includes(taskId)) {
|
|
87869
|
+
this.#structuredTasks.set(blockedTaskId, {
|
|
87870
|
+
...blockedTask,
|
|
87871
|
+
blockedBy: [...blockedTask.blockedBy, taskId],
|
|
87872
|
+
updatedAt: Date.now()
|
|
87873
|
+
});
|
|
87874
|
+
}
|
|
87875
|
+
}
|
|
87876
|
+
for (const prerequisiteTaskId of changes.addBlockedBy ?? []) {
|
|
87877
|
+
const prerequisiteTask = getStructuredTaskForUpdate(
|
|
87878
|
+
this.#structuredTasks,
|
|
87879
|
+
this.#currentOverlay,
|
|
87880
|
+
prerequisiteTaskId
|
|
87881
|
+
);
|
|
87882
|
+
if (prerequisiteTask && !prerequisiteTask.blocks.includes(taskId)) {
|
|
87883
|
+
this.#structuredTasks.set(prerequisiteTaskId, {
|
|
87884
|
+
...prerequisiteTask,
|
|
87885
|
+
blocks: [...prerequisiteTask.blocks, taskId],
|
|
87886
|
+
updatedAt: Date.now()
|
|
87887
|
+
});
|
|
87888
|
+
}
|
|
87889
|
+
}
|
|
87890
|
+
}
|
|
87519
87891
|
resolveStructuredTaskCreates(content) {
|
|
87520
87892
|
this.#deps.log({
|
|
87521
87893
|
event: "debug_resolve_creates",
|
|
@@ -88971,6 +89343,10 @@ var UsageTracker = class {
|
|
|
88971
89343
|
};
|
|
88972
89344
|
|
|
88973
89345
|
// src/services/task/orchestrator/task.ts
|
|
89346
|
+
var WORKTREE_ESCAPE_WARNING = "Working directory is now outside the Shipyard-managed worktree \u2014 the Changes tab will be empty until you return (cd back, or ExitWorktree).";
|
|
89347
|
+
function isOutsideTaskWorktreeRoot(newCwd, originalCwd) {
|
|
89348
|
+
return originalCwd != null && newCwd !== originalCwd && !newCwd.startsWith(`${originalCwd}/`);
|
|
89349
|
+
}
|
|
88974
89350
|
var Task = class {
|
|
88975
89351
|
#deps;
|
|
88976
89352
|
#threads = /* @__PURE__ */ new Map();
|
|
@@ -88984,6 +89360,7 @@ var Task = class {
|
|
|
88984
89360
|
/** Task-level state */
|
|
88985
89361
|
#cwd;
|
|
88986
89362
|
#originalCwd;
|
|
89363
|
+
#cwdEscapeWarningActive = false;
|
|
88987
89364
|
#latestSettings = {};
|
|
88988
89365
|
#pendingMessageMeta = [];
|
|
88989
89366
|
#pendingWorktreeToolUseIds = /* @__PURE__ */ new Set();
|
|
@@ -90469,8 +90846,9 @@ var Task = class {
|
|
|
90469
90846
|
return ack;
|
|
90470
90847
|
}
|
|
90471
90848
|
/**
|
|
90472
|
-
* Clear
|
|
90473
|
-
*
|
|
90849
|
+
* Clear visible session state and reset to cold_idle. The JSONL transcript
|
|
90850
|
+
* stays on disk; subscribers hide prior rows via TaskRecord.clearedAtSeqNo.
|
|
90851
|
+
* The next user message will start a completely fresh SDK session.
|
|
90474
90852
|
*/
|
|
90475
90853
|
clearSession() {
|
|
90476
90854
|
this.#deps.log({ event: "clear_session", taskId: this.#deps.taskId });
|
|
@@ -90499,11 +90877,18 @@ var Task = class {
|
|
|
90499
90877
|
this.#getPrimary().enqueueAsync(async () => {
|
|
90500
90878
|
this.#compaction.reset();
|
|
90501
90879
|
await this.#sideThreads.dispose();
|
|
90880
|
+
let clearedAtSeqNo = -1;
|
|
90881
|
+
let markerPersisted = false;
|
|
90502
90882
|
try {
|
|
90503
|
-
await this.#deps.store.
|
|
90883
|
+
const messages = await this.#deps.store.getMessages(this.#deps.channelId);
|
|
90884
|
+
clearedAtSeqNo = messages.at(-1)?.seqNo ?? -1;
|
|
90885
|
+
await this.#deps.taskStateStore.setClearedAtSeqNo(this.#deps.taskId, clearedAtSeqNo, {
|
|
90886
|
+
broadcast: false
|
|
90887
|
+
});
|
|
90888
|
+
markerPersisted = true;
|
|
90504
90889
|
} catch (err3) {
|
|
90505
90890
|
this.#deps.log({
|
|
90506
|
-
event: "
|
|
90891
|
+
event: "clear_session_marker_failed",
|
|
90507
90892
|
taskId: this.#deps.taskId,
|
|
90508
90893
|
error: err3 instanceof Error ? err3.message : String(err3)
|
|
90509
90894
|
});
|
|
@@ -90517,9 +90902,11 @@ var Task = class {
|
|
|
90517
90902
|
error: err3 instanceof Error ? err3.message : String(err3)
|
|
90518
90903
|
});
|
|
90519
90904
|
}
|
|
90905
|
+
if (!markerPersisted) return;
|
|
90520
90906
|
this.#broadcastToAllPeers?.({
|
|
90521
90907
|
type: "session_cleared",
|
|
90522
|
-
taskId: this.#deps.taskId
|
|
90908
|
+
taskId: this.#deps.taskId,
|
|
90909
|
+
clearedAtSeqNo
|
|
90523
90910
|
});
|
|
90524
90911
|
});
|
|
90525
90912
|
}
|
|
@@ -91009,6 +91396,26 @@ var Task = class {
|
|
|
91009
91396
|
prevCwd: prevCwd ?? null,
|
|
91010
91397
|
originalCwd: this.#originalCwd ?? null
|
|
91011
91398
|
});
|
|
91399
|
+
this.#applyCwdEscapeWarning(newCwd);
|
|
91400
|
+
}
|
|
91401
|
+
#applyCwdEscapeWarning(newCwd) {
|
|
91402
|
+
const originalCwd = this.#originalCwd;
|
|
91403
|
+
const escaped = isOutsideTaskWorktreeRoot(newCwd, originalCwd);
|
|
91404
|
+
if (escaped) {
|
|
91405
|
+
if (this.#cwdEscapeWarningActive) return;
|
|
91406
|
+
this.#cwdEscapeWarningActive = true;
|
|
91407
|
+
this.#deps.log({
|
|
91408
|
+
event: "worktree_escaped_task_root",
|
|
91409
|
+
taskId: this.#deps.taskId,
|
|
91410
|
+
newCwd,
|
|
91411
|
+
originalCwd: originalCwd ?? null
|
|
91412
|
+
});
|
|
91413
|
+
this.#handleTaskError(WORKTREE_ESCAPE_WARNING, "sdk_error");
|
|
91414
|
+
return;
|
|
91415
|
+
}
|
|
91416
|
+
if (!this.#cwdEscapeWarningActive) return;
|
|
91417
|
+
this.#cwdEscapeWarningActive = false;
|
|
91418
|
+
this.#broadcastToAllPeers?.({ type: "task_error_cleared", taskId: this.#deps.taskId });
|
|
91012
91419
|
}
|
|
91013
91420
|
#trackPlanFileFromToolUse(content) {
|
|
91014
91421
|
trackPlanFileFromToolUse(content, (path5) => this.#planHandler.trackCreatedFile(path5));
|
|
@@ -91211,6 +91618,7 @@ var Task = class {
|
|
|
91211
91618
|
this.#pushManager.track(buildDevServersUri(this.#deps.taskId));
|
|
91212
91619
|
this.#pushManager.track(buildTerminalsUri(this.#deps.taskId));
|
|
91213
91620
|
},
|
|
91621
|
+
trackTaskSnapshotResource: (uri) => this.#pushManager.track(uri),
|
|
91214
91622
|
pushManagerTurnStart: () => this.#pushManager.onTurnStart(),
|
|
91215
91623
|
pushManagerTurnEnd: () => this.#pushManager.onTurnEnd(),
|
|
91216
91624
|
broadcastSessionMcps: (mcpServers) => this.#broadcastSessionMcps(mcpServers),
|
|
@@ -91929,7 +92337,7 @@ async function persistUserMessageAtChokepoint(args) {
|
|
|
91929
92337
|
messageId: randomUUID24(),
|
|
91930
92338
|
channelId: args.channelId,
|
|
91931
92339
|
participantId: args.participantId ?? buildHumanParticipantId(args.userId),
|
|
91932
|
-
senderKind: "human",
|
|
92340
|
+
senderKind: args.senderKind ?? "human",
|
|
91933
92341
|
content: stripBlobResourcesForStorage(args.content),
|
|
91934
92342
|
timestamp: Date.now(),
|
|
91935
92343
|
...persistedCorrelationId !== void 0 && { correlationId: persistedCorrelationId },
|
|
@@ -93416,7 +93824,7 @@ var TaskManager = class {
|
|
|
93416
93824
|
});
|
|
93417
93825
|
return task;
|
|
93418
93826
|
}
|
|
93419
|
-
async handleUserMessage(taskId, content, settings, cwd, participantId, senderDisplayName, correlationId) {
|
|
93827
|
+
async handleUserMessage(taskId, content, settings, cwd, participantId, senderDisplayName, correlationId, senderKind = "human") {
|
|
93420
93828
|
const task = await this.#ensureTaskForUserMessage(taskId);
|
|
93421
93829
|
if (cwd && cwd !== task.cwd) {
|
|
93422
93830
|
if (task.mode === "review" && !task.cwd) {
|
|
@@ -93446,6 +93854,7 @@ var TaskManager = class {
|
|
|
93446
93854
|
settings: normalizedSettings,
|
|
93447
93855
|
latestSettings: task.orchestrator.latestSettings,
|
|
93448
93856
|
participantId,
|
|
93857
|
+
senderKind,
|
|
93449
93858
|
correlationId
|
|
93450
93859
|
});
|
|
93451
93860
|
task.orchestrator.handleUserMessage(
|
|
@@ -94061,20 +94470,21 @@ function fingerprintSkillRows(rows) {
|
|
|
94061
94470
|
}
|
|
94062
94471
|
|
|
94063
94472
|
// src/services/task-snapshot-resource-resolver.ts
|
|
94064
|
-
var
|
|
94473
|
+
var TASK_SNAPSHOT_URI_PREFIX3 = "shipyard://task-snapshot/";
|
|
94065
94474
|
var PREVIEW_CHARS = 240;
|
|
94475
|
+
var HUMAN_STEERING_LIMIT = 3;
|
|
94066
94476
|
var TASK_ID_SEGMENT_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
94067
94477
|
function buildTaskSnapshotResourceUri(taskId) {
|
|
94068
94478
|
if (!TASK_ID_SEGMENT_PATTERN.test(taskId)) {
|
|
94069
94479
|
throw new Error(`Invalid taskId for task-snapshot URI: ${taskId}`);
|
|
94070
94480
|
}
|
|
94071
|
-
return `${
|
|
94481
|
+
return `${TASK_SNAPSHOT_URI_PREFIX3}${taskId}`;
|
|
94072
94482
|
}
|
|
94073
94483
|
function parseTaskSnapshotResourceUri(uri) {
|
|
94074
|
-
if (!uri.startsWith(
|
|
94484
|
+
if (!uri.startsWith(TASK_SNAPSHOT_URI_PREFIX3)) {
|
|
94075
94485
|
throw new Error(`Invalid task-snapshot URI: ${uri}`);
|
|
94076
94486
|
}
|
|
94077
|
-
const taskId = uri.slice(
|
|
94487
|
+
const taskId = uri.slice(TASK_SNAPSHOT_URI_PREFIX3.length);
|
|
94078
94488
|
if (!TASK_ID_SEGMENT_PATTERN.test(taskId)) {
|
|
94079
94489
|
throw new Error(`Invalid task-snapshot URI: ${uri}`);
|
|
94080
94490
|
}
|
|
@@ -94112,9 +94522,23 @@ function renderTaskSnapshot(ctx) {
|
|
|
94112
94522
|
lines.push(`| planFilePath | ${ctx.planFilePath ?? "(none)"} |`);
|
|
94113
94523
|
lines.push(`| lastAssistantTurnPreview | ${ctx.lastAssistantTurnPreview ?? "(none)"} |`);
|
|
94114
94524
|
lines.push(`| deliverableCount | ${ctx.deliverables.length} |`);
|
|
94525
|
+
lines.push(...renderHumanSteeringSection(ctx.humanSteering));
|
|
94526
|
+
lines.push(...renderApprovedPlanSection(ctx.approvedPlanSummary));
|
|
94115
94527
|
lines.push(...renderDeliverablesSection(ctx.deliverables));
|
|
94116
94528
|
return lines.join("\n");
|
|
94117
94529
|
}
|
|
94530
|
+
function renderHumanSteeringSection(steering) {
|
|
94531
|
+
if (steering.length === 0) return [];
|
|
94532
|
+
const out = ["", "## Human steering", ""];
|
|
94533
|
+
for (const item of steering) {
|
|
94534
|
+
out.push(`- ${formatTimestamp(item.timestamp)}: ${escapeListItem(item.text)}`);
|
|
94535
|
+
}
|
|
94536
|
+
return out;
|
|
94537
|
+
}
|
|
94538
|
+
function renderApprovedPlanSection(summary) {
|
|
94539
|
+
if (!summary) return [];
|
|
94540
|
+
return ["", "## Approved plan", "", escapeListItem(summary)];
|
|
94541
|
+
}
|
|
94118
94542
|
function renderDeliverablesSection(deliverables) {
|
|
94119
94543
|
if (deliverables.length === 0) return [];
|
|
94120
94544
|
const out = [
|
|
@@ -94135,6 +94559,9 @@ function renderDeliverablesSection(deliverables) {
|
|
|
94135
94559
|
function escapeCell(text) {
|
|
94136
94560
|
return text.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
94137
94561
|
}
|
|
94562
|
+
function escapeListItem(text) {
|
|
94563
|
+
return text.replace(/\s+/g, " ").trim();
|
|
94564
|
+
}
|
|
94138
94565
|
function extractTaskSnapshotDigest(ctx) {
|
|
94139
94566
|
const r = ctx.record;
|
|
94140
94567
|
const ci = ctx.ci ? `${ctx.ci.passing}/${ctx.ci.failing}/${ctx.ci.pending}` : "";
|
|
@@ -94169,6 +94596,8 @@ function extractTaskSnapshotDigest(ctx) {
|
|
|
94169
94596
|
ctx.awaitingPlanApproval,
|
|
94170
94597
|
ctx.planFilePath,
|
|
94171
94598
|
ctx.lastAssistantTurnPreview,
|
|
94599
|
+
ctx.humanSteering.map((m2) => `${m2.seqNo}:${m2.text}`).join(","),
|
|
94600
|
+
ctx.approvedPlanSummary,
|
|
94172
94601
|
/**
|
|
94173
94602
|
* Deliverable digest: count + ordered (id, kind) tuples joined to a
|
|
94174
94603
|
* single string so the digest array stays primitive-typed. Registration
|
|
@@ -94248,7 +94677,25 @@ function findLastAssistantPreview(messages) {
|
|
|
94248
94677
|
}
|
|
94249
94678
|
return null;
|
|
94250
94679
|
}
|
|
94680
|
+
function findRecentHumanSteering(messages, opts) {
|
|
94681
|
+
const humanMessages = messages.filter((m2) => m2.senderKind === "human");
|
|
94682
|
+
const steering = opts.hasEarlierMessages ? humanMessages : humanMessages.slice(1);
|
|
94683
|
+
return steering.map((m2) => {
|
|
94684
|
+
const text = collectMessageText(m2);
|
|
94685
|
+
if (!text) return null;
|
|
94686
|
+
return { seqNo: m2.seqNo, timestamp: m2.timestamp, text: previewSlice(text) };
|
|
94687
|
+
}).filter((m2) => m2 !== null).slice(-HUMAN_STEERING_LIMIT);
|
|
94688
|
+
}
|
|
94689
|
+
function summarizeApprovedPlan(markdown) {
|
|
94690
|
+
if (!markdown) return null;
|
|
94691
|
+
const firstMeaningfulLine = markdown.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
|
|
94692
|
+
if (!firstMeaningfulLine) return null;
|
|
94693
|
+
return previewSlice(firstMeaningfulLine.replace(/^#{1,6}\s+/, ""));
|
|
94694
|
+
}
|
|
94251
94695
|
function collectAgentText(m2) {
|
|
94696
|
+
return collectMessageText(m2);
|
|
94697
|
+
}
|
|
94698
|
+
function collectMessageText(m2) {
|
|
94252
94699
|
const parts = [];
|
|
94253
94700
|
for (const block of m2.content) {
|
|
94254
94701
|
if (block.type === "text" && block.text) parts.push(block.text);
|
|
@@ -94396,6 +94843,8 @@ function wireTaskSnapshotResource(deps) {
|
|
|
94396
94843
|
mergeable: prSummary.mergeable,
|
|
94397
94844
|
branchName,
|
|
94398
94845
|
lastAssistantTurnPreview: findLastAssistantPreview(tail.messages),
|
|
94846
|
+
humanSteering: findRecentHumanSteering(tail.messages, { hasEarlierMessages: tail.hasMore }),
|
|
94847
|
+
approvedPlanSummary: planDetection?.approved === true ? summarizeApprovedPlan(planDetection.originalMarkdown) : null,
|
|
94399
94848
|
deliverables
|
|
94400
94849
|
};
|
|
94401
94850
|
}
|
|
@@ -95464,6 +95913,7 @@ async function createDaemon(deps) {
|
|
|
95464
95913
|
return dispatchSpawn(decision, {
|
|
95465
95914
|
onEvent,
|
|
95466
95915
|
initialContent: initialContent ?? [],
|
|
95916
|
+
log: deps.log,
|
|
95467
95917
|
spawnClaude,
|
|
95468
95918
|
spawnCursor,
|
|
95469
95919
|
spawnCodex: () => {
|
|
@@ -100175,4 +100625,4 @@ export {
|
|
|
100175
100625
|
decideWorkspaceScope,
|
|
100176
100626
|
serve
|
|
100177
100627
|
};
|
|
100178
|
-
//# sourceMappingURL=serve-
|
|
100628
|
+
//# sourceMappingURL=serve-3N7IHREI.js.map
|