@sideboard-ai/core 0.1.166 → 0.1.168
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/{chunk-D6YXN25M.js → chunk-NYMFNUT5.js} +30 -21
- package/dist/{chunk-U7NHWCPE.js → chunk-UVLPL2FJ.js} +30 -21
- package/dist/index.cjs +33 -24
- package/dist/index.d.cts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +5 -5
- package/dist/mcp/run-stdio.cjs +33 -24
- package/dist/mcp/run-stdio.js +5 -5
- package/dist/{orchestrator-SQ37MAS5.js → orchestrator-IIME6KXQ.js} +1 -1
- package/dist/{orchestrator-BJLZGOG5.js → orchestrator-XG3QCMOS.js} +1 -1
- package/package.json +2 -2
|
@@ -399,7 +399,7 @@ async function continueSourceThread(threadId, prompt) {
|
|
|
399
399
|
await continueOnReply(threadId, prompt);
|
|
400
400
|
return;
|
|
401
401
|
}
|
|
402
|
-
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-
|
|
402
|
+
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-XG3QCMOS.js");
|
|
403
403
|
await getOrchestrator2().send(threadId, prompt);
|
|
404
404
|
} catch {
|
|
405
405
|
}
|
|
@@ -728,6 +728,8 @@ function createAgentStreamCoalescer(emit, opts) {
|
|
|
728
728
|
function applyPromptCacheTtlEnv(agent, env) {
|
|
729
729
|
if (agent === "claude" && env.FORCE_PROMPT_CACHING_5M !== "1") {
|
|
730
730
|
env.ENABLE_PROMPT_CACHING_1H ??= "1";
|
|
731
|
+
env.CLAUDE_CODE_PROMPT_CACHE_TTL ??= "1h";
|
|
732
|
+
env.CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL ??= "1h";
|
|
731
733
|
}
|
|
732
734
|
if (agent === "opencode" && env.OPENCODE_ANTHROPIC_FORCE_PROMPT_CACHING_5M !== "1") {
|
|
733
735
|
env.OPENCODE_ANTHROPIC_PROMPT_CACHING_1H ??= "1";
|
|
@@ -2607,7 +2609,7 @@ function formatJobStillRunningContinuePrompt(jobIds) {
|
|
|
2607
2609
|
function formatDeferredDoneContinuePrompt() {
|
|
2608
2610
|
return [
|
|
2609
2611
|
"You ended the turn after promising to report later, but no detached job is running.",
|
|
2610
|
-
"If tests/pack/deploy still need to run: start once with detached-job.
|
|
2612
|
+
"If tests/pack/deploy still need to run: start once with detached-job.cjs, present_artifact type=log, then loop wait_for_job until stillRunning is false.",
|
|
2611
2613
|
"Do not say you will let the user know later."
|
|
2612
2614
|
].join(" ");
|
|
2613
2615
|
}
|
|
@@ -2616,7 +2618,7 @@ function turnWatchedDetachedJob(parts) {
|
|
|
2616
2618
|
if (p.type !== "tool") return false;
|
|
2617
2619
|
if (/wait_for_job$/i.test(p.name ?? "") || /stop_job$/i.test(p.name ?? "")) return true;
|
|
2618
2620
|
const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
|
|
2619
|
-
return /detached-job\.js\b/i.test(blob);
|
|
2621
|
+
return /detached-job\.(?:js|cjs)\b/i.test(blob);
|
|
2620
2622
|
});
|
|
2621
2623
|
}
|
|
2622
2624
|
function planJobContinue(opts) {
|
|
@@ -2709,7 +2711,7 @@ async function waitForDetachedJob(cwd, id, opts) {
|
|
|
2709
2711
|
id: jobId,
|
|
2710
2712
|
delta: "",
|
|
2711
2713
|
progress: "No detached job. Start one first.",
|
|
2712
|
-
hint: "Start with detached-job.
|
|
2714
|
+
hint: "Start with detached-job.cjs start <id> -- <command>, then call wait_for_job again."
|
|
2713
2715
|
};
|
|
2714
2716
|
}
|
|
2715
2717
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -2768,7 +2770,7 @@ async function stopDetachedJob(cwd, id, opts) {
|
|
|
2768
2770
|
id: jobId,
|
|
2769
2771
|
delta: "",
|
|
2770
2772
|
progress: "No detached job. Start one first.",
|
|
2771
|
-
hint: "Start with detached-job.
|
|
2773
|
+
hint: "Start with detached-job.cjs start <id> -- <command>, then wait_for_job or stop_job.",
|
|
2772
2774
|
stopped: false,
|
|
2773
2775
|
reason: "not-found"
|
|
2774
2776
|
};
|
|
@@ -5268,21 +5270,21 @@ Do **not** ask the human to check back. Detach, then **wait** in 45s slices (MCP
|
|
|
5268
5270
|
|
|
5269
5271
|
## Tool
|
|
5270
5272
|
|
|
5271
|
-
Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" start\` path injected each turn. If this worktree has \`scripts/detached-job.
|
|
5273
|
+
Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" start\` path injected each turn. If this worktree has \`scripts/detached-job.cjs\`, that is the same tool.
|
|
5272
5274
|
|
|
5273
5275
|
\`\`\`bash
|
|
5274
5276
|
# Start (exits in ~1s; job survives this turn)
|
|
5275
|
-
node <detached-job.
|
|
5277
|
+
node <detached-job.cjs> start <id> -- <command> [args...]
|
|
5276
5278
|
|
|
5277
5279
|
# Wait \u2014 prefer MCP wait_for_job (same 45s / stillRunning contract).
|
|
5278
5280
|
# Shell fallback:
|
|
5279
|
-
node <detached-job.
|
|
5281
|
+
node <detached-job.cjs> wait <id>
|
|
5280
5282
|
|
|
5281
5283
|
# Block until the process exits (humans / a turn that will not be interrupted)
|
|
5282
|
-
node <detached-job.
|
|
5284
|
+
node <detached-job.cjs> wait <id> --until-done
|
|
5283
5285
|
|
|
5284
|
-
node <detached-job.
|
|
5285
|
-
node <detached-job.
|
|
5286
|
+
node <detached-job.cjs> stop <id> [--reason TEXT]
|
|
5287
|
+
node <detached-job.cjs> status <id>
|
|
5286
5288
|
\`\`\`
|
|
5287
5289
|
|
|
5288
5290
|
\`<id>\` is kebab-case (\`mac-release\`, \`core-test\`, \`fly-deploy\`). State is \`.context/.sideboard/detached-jobs/<id>/\` (local scratch).
|
|
@@ -5313,7 +5315,7 @@ After **start**, present once (\`status=running\`, empty or first lines). After
|
|
|
5313
5315
|
Ad-hoc pid/log (legacy or another tool\u2019s files):
|
|
5314
5316
|
|
|
5315
5317
|
\`\`\`bash
|
|
5316
|
-
node <detached-job.
|
|
5318
|
+
node <detached-job.cjs> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
|
|
5317
5319
|
\`\`\`
|
|
5318
5320
|
|
|
5319
5321
|
## Agent loop
|
|
@@ -5322,7 +5324,7 @@ node <detached-job.js> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
|
|
|
5322
5324
|
2. Immediately \`present_artifact\` \`type=log\` (same \`artifact_id\`, \`status=running\`) \u2014 the human should see **working** in the side column, not a \u201Ccheck back later\u201D message.
|
|
5323
5325
|
3. Loop \`wait_for_job\` (or shell \`wait\`). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
|
|
5324
5326
|
4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
|
|
5325
|
-
5. **Stop** when the job is hanging, buffering forever, watching the wrong thing, or you already have the answer. MCP \`stop_job\` (same id, optional \`reason\`) or \`detached-job.
|
|
5327
|
+
5. **Stop** when the job is hanging, buffering forever, watching the wrong thing, or you already have the answer. MCP \`stop_job\` (same id, optional \`reason\`) or \`detached-job.cjs stop <id>\`. Do **not** stop a pack/test/deploy that is clearly making progress.
|
|
5326
5328
|
|
|
5327
5329
|
Never tell the user \u201Csay status when it\u2019s done.\u201D You wait \u2014 unless you decided to stop.
|
|
5328
5330
|
|
|
@@ -5335,7 +5337,7 @@ Huge \`--json\` / \`--expand\` dumps crash any worktree agent mid-turn (Claude /
|
|
|
5335
5337
|
Do **not** watch after every push. If the user gave a goal (Greptile 5/5, CI green, until checks pass), wait the same way:
|
|
5336
5338
|
|
|
5337
5339
|
\`\`\`bash
|
|
5338
|
-
node <detached-job.
|
|
5340
|
+
node <detached-job.cjs> start pr-checks -- gh pr checks --watch
|
|
5339
5341
|
\`\`\`
|
|
5340
5342
|
|
|
5341
5343
|
On miss, fix, commit, push, and watch again until the goal is met or you are blocked. Do not ask the human to poll. Do not merge unless asked.
|
|
@@ -5601,19 +5603,26 @@ import { join as join15 } from "path";
|
|
|
5601
5603
|
import { existsSync as existsSync15 } from "fs";
|
|
5602
5604
|
import { dirname as dirname5, join as join14 } from "path";
|
|
5603
5605
|
import { fileURLToPath } from "url";
|
|
5606
|
+
var DETACHED_JOB_FILENAMES = ["detached-job.cjs", "detached-job.js"];
|
|
5607
|
+
function detachedJobScriptIn(dir) {
|
|
5608
|
+
for (const name of DETACHED_JOB_FILENAMES) {
|
|
5609
|
+
const script = join14(dir, "scripts", name);
|
|
5610
|
+
if (existsSync15(script)) return script;
|
|
5611
|
+
}
|
|
5612
|
+
return null;
|
|
5613
|
+
}
|
|
5604
5614
|
function packagedDetachedJobPath() {
|
|
5605
5615
|
const dir = packagedMcpDir();
|
|
5606
5616
|
if (!dir) return null;
|
|
5607
|
-
|
|
5608
|
-
return existsSync15(script) ? script : null;
|
|
5617
|
+
return detachedJobScriptIn(dir);
|
|
5609
5618
|
}
|
|
5610
5619
|
function resolveDetachedJobScript() {
|
|
5611
5620
|
const packaged = packagedDetachedJobPath();
|
|
5612
5621
|
if (packaged) return packaged;
|
|
5613
5622
|
let dir = dirname5(fileURLToPath(import.meta.url));
|
|
5614
5623
|
for (let i = 0; i < 8; i++) {
|
|
5615
|
-
const candidate =
|
|
5616
|
-
if (
|
|
5624
|
+
const candidate = detachedJobScriptIn(dir);
|
|
5625
|
+
if (candidate) return candidate;
|
|
5617
5626
|
const parent = dirname5(dir);
|
|
5618
5627
|
if (parent === dir) break;
|
|
5619
5628
|
dir = parent;
|
|
@@ -5623,7 +5632,7 @@ function resolveDetachedJobScript() {
|
|
|
5623
5632
|
function formatDetachedJobInvoke(scriptPath) {
|
|
5624
5633
|
const resolved = scriptPath === void 0 ? resolveDetachedJobScript() : scriptPath;
|
|
5625
5634
|
if (resolved) return `node ${JSON.stringify(resolved)}`;
|
|
5626
|
-
return "node scripts/detached-job.
|
|
5635
|
+
return "node scripts/detached-job.cjs";
|
|
5627
5636
|
}
|
|
5628
5637
|
|
|
5629
5638
|
// src/agents/instructions.ts
|
|
@@ -5817,7 +5826,7 @@ function formatLongRunningDirective(opts) {
|
|
|
5817
5826
|
return [
|
|
5818
5827
|
"Long-running jobs (mandatory when a command may run more than ~30s):",
|
|
5819
5828
|
"A Sideboard worktree turn SIGTERMs the agent shell (and its process group) when the user sends another message or the turn is interrupted. `block_until_ms: 0` is not enough. Do not ask the human to poll.",
|
|
5820
|
-
`Helper (same tool as \`scripts/detached-job.
|
|
5829
|
+
`Helper (same tool as \`scripts/detached-job.cjs\` when that file exists in the worktree): \`${invoke}\``,
|
|
5821
5830
|
`- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree). If JSON says already-running, do not start again.`,
|
|
5822
5831
|
"- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running` \u2014 the side column is the live view.",
|
|
5823
5832
|
`- Loop Sideboard MCP \`wait_for_job\` with the same id (returns in ~45s). stillRunning \u2192 present the same id with \`content=delta\` only \u2192 wait_for_job again. Shell fallback: \`${invoke} wait <id>\`. Do not resend the full log or HTML.`,
|
|
@@ -6231,7 +6240,7 @@ function formatScheduledPrompt(name, prompt) {
|
|
|
6231
6240
|
${prompt}`;
|
|
6232
6241
|
}
|
|
6233
6242
|
async function defaultDeps() {
|
|
6234
|
-
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-
|
|
6243
|
+
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-XG3QCMOS.js");
|
|
6235
6244
|
const orch = getOrchestrator2();
|
|
6236
6245
|
return {
|
|
6237
6246
|
findThread: (id) => findThreadByRef(id),
|
|
@@ -464,7 +464,7 @@ async function continueSourceThread(threadId, prompt) {
|
|
|
464
464
|
await continueOnReply(threadId, prompt);
|
|
465
465
|
return;
|
|
466
466
|
}
|
|
467
|
-
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-
|
|
467
|
+
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-IIME6KXQ.js");
|
|
468
468
|
await getOrchestrator2().send(threadId, prompt);
|
|
469
469
|
} catch {
|
|
470
470
|
}
|
|
@@ -732,6 +732,8 @@ import { execa } from "execa";
|
|
|
732
732
|
function applyPromptCacheTtlEnv(agent, env) {
|
|
733
733
|
if (agent === "claude" && env.FORCE_PROMPT_CACHING_5M !== "1") {
|
|
734
734
|
env.ENABLE_PROMPT_CACHING_1H ??= "1";
|
|
735
|
+
env.CLAUDE_CODE_PROMPT_CACHE_TTL ??= "1h";
|
|
736
|
+
env.CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL ??= "1h";
|
|
735
737
|
}
|
|
736
738
|
if (agent === "opencode" && env.OPENCODE_ANTHROPIC_FORCE_PROMPT_CACHING_5M !== "1") {
|
|
737
739
|
env.OPENCODE_ANTHROPIC_PROMPT_CACHING_1H ??= "1";
|
|
@@ -2692,7 +2694,7 @@ function formatJobStillRunningContinuePrompt(jobIds) {
|
|
|
2692
2694
|
function formatDeferredDoneContinuePrompt() {
|
|
2693
2695
|
return [
|
|
2694
2696
|
"You ended the turn after promising to report later, but no detached job is running.",
|
|
2695
|
-
"If tests/pack/deploy still need to run: start once with detached-job.
|
|
2697
|
+
"If tests/pack/deploy still need to run: start once with detached-job.cjs, present_artifact type=log, then loop wait_for_job until stillRunning is false.",
|
|
2696
2698
|
"Do not say you will let the user know later."
|
|
2697
2699
|
].join(" ");
|
|
2698
2700
|
}
|
|
@@ -2701,7 +2703,7 @@ function turnWatchedDetachedJob(parts) {
|
|
|
2701
2703
|
if (p.type !== "tool") return false;
|
|
2702
2704
|
if (/wait_for_job$/i.test(p.name ?? "") || /stop_job$/i.test(p.name ?? "")) return true;
|
|
2703
2705
|
const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
|
|
2704
|
-
return /detached-job\.js\b/i.test(blob);
|
|
2706
|
+
return /detached-job\.(?:js|cjs)\b/i.test(blob);
|
|
2705
2707
|
});
|
|
2706
2708
|
}
|
|
2707
2709
|
function planJobContinue(opts) {
|
|
@@ -2794,7 +2796,7 @@ async function waitForDetachedJob(cwd, id, opts) {
|
|
|
2794
2796
|
id: jobId,
|
|
2795
2797
|
delta: "",
|
|
2796
2798
|
progress: "No detached job. Start one first.",
|
|
2797
|
-
hint: "Start with detached-job.
|
|
2799
|
+
hint: "Start with detached-job.cjs start <id> -- <command>, then call wait_for_job again."
|
|
2798
2800
|
};
|
|
2799
2801
|
}
|
|
2800
2802
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -2853,7 +2855,7 @@ async function stopDetachedJob(cwd, id, opts) {
|
|
|
2853
2855
|
id: jobId,
|
|
2854
2856
|
delta: "",
|
|
2855
2857
|
progress: "No detached job. Start one first.",
|
|
2856
|
-
hint: "Start with detached-job.
|
|
2858
|
+
hint: "Start with detached-job.cjs start <id> -- <command>, then wait_for_job or stop_job.",
|
|
2857
2859
|
stopped: false,
|
|
2858
2860
|
reason: "not-found"
|
|
2859
2861
|
};
|
|
@@ -5420,21 +5422,21 @@ Do **not** ask the human to check back. Detach, then **wait** in 45s slices (MCP
|
|
|
5420
5422
|
|
|
5421
5423
|
## Tool
|
|
5422
5424
|
|
|
5423
|
-
Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" start\` path injected each turn. If this worktree has \`scripts/detached-job.
|
|
5425
|
+
Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" start\` path injected each turn. If this worktree has \`scripts/detached-job.cjs\`, that is the same tool.
|
|
5424
5426
|
|
|
5425
5427
|
\`\`\`bash
|
|
5426
5428
|
# Start (exits in ~1s; job survives this turn)
|
|
5427
|
-
node <detached-job.
|
|
5429
|
+
node <detached-job.cjs> start <id> -- <command> [args...]
|
|
5428
5430
|
|
|
5429
5431
|
# Wait \u2014 prefer MCP wait_for_job (same 45s / stillRunning contract).
|
|
5430
5432
|
# Shell fallback:
|
|
5431
|
-
node <detached-job.
|
|
5433
|
+
node <detached-job.cjs> wait <id>
|
|
5432
5434
|
|
|
5433
5435
|
# Block until the process exits (humans / a turn that will not be interrupted)
|
|
5434
|
-
node <detached-job.
|
|
5436
|
+
node <detached-job.cjs> wait <id> --until-done
|
|
5435
5437
|
|
|
5436
|
-
node <detached-job.
|
|
5437
|
-
node <detached-job.
|
|
5438
|
+
node <detached-job.cjs> stop <id> [--reason TEXT]
|
|
5439
|
+
node <detached-job.cjs> status <id>
|
|
5438
5440
|
\`\`\`
|
|
5439
5441
|
|
|
5440
5442
|
\`<id>\` is kebab-case (\`mac-release\`, \`core-test\`, \`fly-deploy\`). State is \`.context/.sideboard/detached-jobs/<id>/\` (local scratch).
|
|
@@ -5465,7 +5467,7 @@ After **start**, present once (\`status=running\`, empty or first lines). After
|
|
|
5465
5467
|
Ad-hoc pid/log (legacy or another tool\u2019s files):
|
|
5466
5468
|
|
|
5467
5469
|
\`\`\`bash
|
|
5468
|
-
node <detached-job.
|
|
5470
|
+
node <detached-job.cjs> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
|
|
5469
5471
|
\`\`\`
|
|
5470
5472
|
|
|
5471
5473
|
## Agent loop
|
|
@@ -5474,7 +5476,7 @@ node <detached-job.js> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
|
|
|
5474
5476
|
2. Immediately \`present_artifact\` \`type=log\` (same \`artifact_id\`, \`status=running\`) \u2014 the human should see **working** in the side column, not a \u201Ccheck back later\u201D message.
|
|
5475
5477
|
3. Loop \`wait_for_job\` (or shell \`wait\`). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
|
|
5476
5478
|
4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
|
|
5477
|
-
5. **Stop** when the job is hanging, buffering forever, watching the wrong thing, or you already have the answer. MCP \`stop_job\` (same id, optional \`reason\`) or \`detached-job.
|
|
5479
|
+
5. **Stop** when the job is hanging, buffering forever, watching the wrong thing, or you already have the answer. MCP \`stop_job\` (same id, optional \`reason\`) or \`detached-job.cjs stop <id>\`. Do **not** stop a pack/test/deploy that is clearly making progress.
|
|
5478
5480
|
|
|
5479
5481
|
Never tell the user \u201Csay status when it\u2019s done.\u201D You wait \u2014 unless you decided to stop.
|
|
5480
5482
|
|
|
@@ -5487,7 +5489,7 @@ Huge \`--json\` / \`--expand\` dumps crash any worktree agent mid-turn (Claude /
|
|
|
5487
5489
|
Do **not** watch after every push. If the user gave a goal (Greptile 5/5, CI green, until checks pass), wait the same way:
|
|
5488
5490
|
|
|
5489
5491
|
\`\`\`bash
|
|
5490
|
-
node <detached-job.
|
|
5492
|
+
node <detached-job.cjs> start pr-checks -- gh pr checks --watch
|
|
5491
5493
|
\`\`\`
|
|
5492
5494
|
|
|
5493
5495
|
On miss, fix, commit, push, and watch again until the goal is met or you are blocked. Do not ask the human to poll. Do not merge unless asked.
|
|
@@ -5753,19 +5755,26 @@ import { join as join15 } from "path";
|
|
|
5753
5755
|
import { existsSync as existsSync15 } from "fs";
|
|
5754
5756
|
import { dirname as dirname5, join as join14 } from "path";
|
|
5755
5757
|
import { fileURLToPath } from "url";
|
|
5758
|
+
var DETACHED_JOB_FILENAMES = ["detached-job.cjs", "detached-job.js"];
|
|
5759
|
+
function detachedJobScriptIn(dir) {
|
|
5760
|
+
for (const name of DETACHED_JOB_FILENAMES) {
|
|
5761
|
+
const script = join14(dir, "scripts", name);
|
|
5762
|
+
if (existsSync15(script)) return script;
|
|
5763
|
+
}
|
|
5764
|
+
return null;
|
|
5765
|
+
}
|
|
5756
5766
|
function packagedDetachedJobPath() {
|
|
5757
5767
|
const dir = packagedMcpDir();
|
|
5758
5768
|
if (!dir) return null;
|
|
5759
|
-
|
|
5760
|
-
return existsSync15(script) ? script : null;
|
|
5769
|
+
return detachedJobScriptIn(dir);
|
|
5761
5770
|
}
|
|
5762
5771
|
function resolveDetachedJobScript() {
|
|
5763
5772
|
const packaged = packagedDetachedJobPath();
|
|
5764
5773
|
if (packaged) return packaged;
|
|
5765
5774
|
let dir = dirname5(fileURLToPath(import.meta.url));
|
|
5766
5775
|
for (let i = 0; i < 8; i++) {
|
|
5767
|
-
const candidate =
|
|
5768
|
-
if (
|
|
5776
|
+
const candidate = detachedJobScriptIn(dir);
|
|
5777
|
+
if (candidate) return candidate;
|
|
5769
5778
|
const parent = dirname5(dir);
|
|
5770
5779
|
if (parent === dir) break;
|
|
5771
5780
|
dir = parent;
|
|
@@ -5775,7 +5784,7 @@ function resolveDetachedJobScript() {
|
|
|
5775
5784
|
function formatDetachedJobInvoke(scriptPath) {
|
|
5776
5785
|
const resolved = scriptPath === void 0 ? resolveDetachedJobScript() : scriptPath;
|
|
5777
5786
|
if (resolved) return `node ${JSON.stringify(resolved)}`;
|
|
5778
|
-
return "node scripts/detached-job.
|
|
5787
|
+
return "node scripts/detached-job.cjs";
|
|
5779
5788
|
}
|
|
5780
5789
|
|
|
5781
5790
|
// src/agents/instructions.ts
|
|
@@ -5991,7 +6000,7 @@ function formatLongRunningDirective(opts) {
|
|
|
5991
6000
|
return [
|
|
5992
6001
|
"Long-running jobs (mandatory when a command may run more than ~30s):",
|
|
5993
6002
|
"A Sideboard worktree turn SIGTERMs the agent shell (and its process group) when the user sends another message or the turn is interrupted. `block_until_ms: 0` is not enough. Do not ask the human to poll.",
|
|
5994
|
-
`Helper (same tool as \`scripts/detached-job.
|
|
6003
|
+
`Helper (same tool as \`scripts/detached-job.cjs\` when that file exists in the worktree): \`${invoke}\``,
|
|
5995
6004
|
`- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree). If JSON says already-running, do not start again.`,
|
|
5996
6005
|
"- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running` \u2014 the side column is the live view.",
|
|
5997
6006
|
`- Loop Sideboard MCP \`wait_for_job\` with the same id (returns in ~45s). stillRunning \u2192 present the same id with \`content=delta\` only \u2192 wait_for_job again. Shell fallback: \`${invoke} wait <id>\`. Do not resend the full log or HTML.`,
|
|
@@ -6641,7 +6650,7 @@ function formatScheduledPrompt(name, prompt) {
|
|
|
6641
6650
|
${prompt}`;
|
|
6642
6651
|
}
|
|
6643
6652
|
async function defaultDeps() {
|
|
6644
|
-
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-
|
|
6653
|
+
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-IIME6KXQ.js");
|
|
6645
6654
|
const orch = getOrchestrator2();
|
|
6646
6655
|
return {
|
|
6647
6656
|
findThread: (id) => findThreadByRef(id),
|
package/dist/index.cjs
CHANGED
|
@@ -13584,6 +13584,8 @@ var init_cursor_stream_coalesce = __esm({
|
|
|
13584
13584
|
function applyPromptCacheTtlEnv(agent, env) {
|
|
13585
13585
|
if (agent === "claude" && env.FORCE_PROMPT_CACHING_5M !== "1") {
|
|
13586
13586
|
env.ENABLE_PROMPT_CACHING_1H ??= "1";
|
|
13587
|
+
env.CLAUDE_CODE_PROMPT_CACHE_TTL ??= "1h";
|
|
13588
|
+
env.CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL ??= "1h";
|
|
13587
13589
|
}
|
|
13588
13590
|
if (agent === "opencode" && env.OPENCODE_ANTHROPIC_FORCE_PROMPT_CACHING_5M !== "1") {
|
|
13589
13591
|
env.OPENCODE_ANTHROPIC_PROMPT_CACHING_1H ??= "1";
|
|
@@ -15506,7 +15508,7 @@ function formatJobStillRunningContinuePrompt(jobIds) {
|
|
|
15506
15508
|
function formatDeferredDoneContinuePrompt() {
|
|
15507
15509
|
return [
|
|
15508
15510
|
"You ended the turn after promising to report later, but no detached job is running.",
|
|
15509
|
-
"If tests/pack/deploy still need to run: start once with detached-job.
|
|
15511
|
+
"If tests/pack/deploy still need to run: start once with detached-job.cjs, present_artifact type=log, then loop wait_for_job until stillRunning is false.",
|
|
15510
15512
|
"Do not say you will let the user know later."
|
|
15511
15513
|
].join(" ");
|
|
15512
15514
|
}
|
|
@@ -15515,7 +15517,7 @@ function turnWatchedDetachedJob(parts) {
|
|
|
15515
15517
|
if (p.type !== "tool") return false;
|
|
15516
15518
|
if (/wait_for_job$/i.test(p.name ?? "") || /stop_job$/i.test(p.name ?? "")) return true;
|
|
15517
15519
|
const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
|
|
15518
|
-
return /detached-job\.js\b/i.test(blob);
|
|
15520
|
+
return /detached-job\.(?:js|cjs)\b/i.test(blob);
|
|
15519
15521
|
});
|
|
15520
15522
|
}
|
|
15521
15523
|
function planJobContinue(opts) {
|
|
@@ -15608,7 +15610,7 @@ async function waitForDetachedJob(cwd, id, opts) {
|
|
|
15608
15610
|
id: jobId,
|
|
15609
15611
|
delta: "",
|
|
15610
15612
|
progress: "No detached job. Start one first.",
|
|
15611
|
-
hint: "Start with detached-job.
|
|
15613
|
+
hint: "Start with detached-job.cjs start <id> -- <command>, then call wait_for_job again."
|
|
15612
15614
|
};
|
|
15613
15615
|
}
|
|
15614
15616
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -15667,7 +15669,7 @@ async function stopDetachedJob(cwd, id, opts) {
|
|
|
15667
15669
|
id: jobId,
|
|
15668
15670
|
delta: "",
|
|
15669
15671
|
progress: "No detached job. Start one first.",
|
|
15670
|
-
hint: "Start with detached-job.
|
|
15672
|
+
hint: "Start with detached-job.cjs start <id> -- <command>, then wait_for_job or stop_job.",
|
|
15671
15673
|
stopped: false,
|
|
15672
15674
|
reason: "not-found"
|
|
15673
15675
|
};
|
|
@@ -18903,21 +18905,21 @@ Do **not** ask the human to check back. Detach, then **wait** in 45s slices (MCP
|
|
|
18903
18905
|
|
|
18904
18906
|
## Tool
|
|
18905
18907
|
|
|
18906
|
-
Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" start\` path injected each turn. If this worktree has \`scripts/detached-job.
|
|
18908
|
+
Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" start\` path injected each turn. If this worktree has \`scripts/detached-job.cjs\`, that is the same tool.
|
|
18907
18909
|
|
|
18908
18910
|
\`\`\`bash
|
|
18909
18911
|
# Start (exits in ~1s; job survives this turn)
|
|
18910
|
-
node <detached-job.
|
|
18912
|
+
node <detached-job.cjs> start <id> -- <command> [args...]
|
|
18911
18913
|
|
|
18912
18914
|
# Wait \u2014 prefer MCP wait_for_job (same 45s / stillRunning contract).
|
|
18913
18915
|
# Shell fallback:
|
|
18914
|
-
node <detached-job.
|
|
18916
|
+
node <detached-job.cjs> wait <id>
|
|
18915
18917
|
|
|
18916
18918
|
# Block until the process exits (humans / a turn that will not be interrupted)
|
|
18917
|
-
node <detached-job.
|
|
18919
|
+
node <detached-job.cjs> wait <id> --until-done
|
|
18918
18920
|
|
|
18919
|
-
node <detached-job.
|
|
18920
|
-
node <detached-job.
|
|
18921
|
+
node <detached-job.cjs> stop <id> [--reason TEXT]
|
|
18922
|
+
node <detached-job.cjs> status <id>
|
|
18921
18923
|
\`\`\`
|
|
18922
18924
|
|
|
18923
18925
|
\`<id>\` is kebab-case (\`mac-release\`, \`core-test\`, \`fly-deploy\`). State is \`.context/.sideboard/detached-jobs/<id>/\` (local scratch).
|
|
@@ -18948,7 +18950,7 @@ After **start**, present once (\`status=running\`, empty or first lines). After
|
|
|
18948
18950
|
Ad-hoc pid/log (legacy or another tool\u2019s files):
|
|
18949
18951
|
|
|
18950
18952
|
\`\`\`bash
|
|
18951
|
-
node <detached-job.
|
|
18953
|
+
node <detached-job.cjs> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
|
|
18952
18954
|
\`\`\`
|
|
18953
18955
|
|
|
18954
18956
|
## Agent loop
|
|
@@ -18957,7 +18959,7 @@ node <detached-job.js> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
|
|
|
18957
18959
|
2. Immediately \`present_artifact\` \`type=log\` (same \`artifact_id\`, \`status=running\`) \u2014 the human should see **working** in the side column, not a \u201Ccheck back later\u201D message.
|
|
18958
18960
|
3. Loop \`wait_for_job\` (or shell \`wait\`). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
|
|
18959
18961
|
4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
|
|
18960
|
-
5. **Stop** when the job is hanging, buffering forever, watching the wrong thing, or you already have the answer. MCP \`stop_job\` (same id, optional \`reason\`) or \`detached-job.
|
|
18962
|
+
5. **Stop** when the job is hanging, buffering forever, watching the wrong thing, or you already have the answer. MCP \`stop_job\` (same id, optional \`reason\`) or \`detached-job.cjs stop <id>\`. Do **not** stop a pack/test/deploy that is clearly making progress.
|
|
18961
18963
|
|
|
18962
18964
|
Never tell the user \u201Csay status when it\u2019s done.\u201D You wait \u2014 unless you decided to stop.
|
|
18963
18965
|
|
|
@@ -18970,7 +18972,7 @@ Huge \`--json\` / \`--expand\` dumps crash any worktree agent mid-turn (Claude /
|
|
|
18970
18972
|
Do **not** watch after every push. If the user gave a goal (Greptile 5/5, CI green, until checks pass), wait the same way:
|
|
18971
18973
|
|
|
18972
18974
|
\`\`\`bash
|
|
18973
|
-
node <detached-job.
|
|
18975
|
+
node <detached-job.cjs> start pr-checks -- gh pr checks --watch
|
|
18974
18976
|
\`\`\`
|
|
18975
18977
|
|
|
18976
18978
|
On miss, fix, commit, push, and watch again until the goal is met or you are blocked. Do not ask the human to poll. Do not merge unless asked.
|
|
@@ -19249,19 +19251,25 @@ var init_expand = __esm({
|
|
|
19249
19251
|
});
|
|
19250
19252
|
|
|
19251
19253
|
// src/skills/detached-job-path.ts
|
|
19254
|
+
function detachedJobScriptIn(dir) {
|
|
19255
|
+
for (const name of DETACHED_JOB_FILENAMES) {
|
|
19256
|
+
const script = (0, import_node_path44.join)(dir, "scripts", name);
|
|
19257
|
+
if ((0, import_node_fs47.existsSync)(script)) return script;
|
|
19258
|
+
}
|
|
19259
|
+
return null;
|
|
19260
|
+
}
|
|
19252
19261
|
function packagedDetachedJobPath() {
|
|
19253
19262
|
const dir = packagedMcpDir();
|
|
19254
19263
|
if (!dir) return null;
|
|
19255
|
-
|
|
19256
|
-
return (0, import_node_fs47.existsSync)(script) ? script : null;
|
|
19264
|
+
return detachedJobScriptIn(dir);
|
|
19257
19265
|
}
|
|
19258
19266
|
function resolveDetachedJobScript() {
|
|
19259
19267
|
const packaged = packagedDetachedJobPath();
|
|
19260
19268
|
if (packaged) return packaged;
|
|
19261
19269
|
let dir = (0, import_node_path44.dirname)((0, import_node_url3.fileURLToPath)(import_meta3.url));
|
|
19262
19270
|
for (let i = 0; i < 8; i++) {
|
|
19263
|
-
const candidate = (
|
|
19264
|
-
if (
|
|
19271
|
+
const candidate = detachedJobScriptIn(dir);
|
|
19272
|
+
if (candidate) return candidate;
|
|
19265
19273
|
const parent = (0, import_node_path44.dirname)(dir);
|
|
19266
19274
|
if (parent === dir) break;
|
|
19267
19275
|
dir = parent;
|
|
@@ -19271,9 +19279,9 @@ function resolveDetachedJobScript() {
|
|
|
19271
19279
|
function formatDetachedJobInvoke(scriptPath) {
|
|
19272
19280
|
const resolved = scriptPath === void 0 ? resolveDetachedJobScript() : scriptPath;
|
|
19273
19281
|
if (resolved) return `node ${JSON.stringify(resolved)}`;
|
|
19274
|
-
return "node scripts/detached-job.
|
|
19282
|
+
return "node scripts/detached-job.cjs";
|
|
19275
19283
|
}
|
|
19276
|
-
var import_node_fs47, import_node_path44, import_node_url3, import_meta3;
|
|
19284
|
+
var import_node_fs47, import_node_path44, import_node_url3, import_meta3, DETACHED_JOB_FILENAMES;
|
|
19277
19285
|
var init_detached_job_path = __esm({
|
|
19278
19286
|
"src/skills/detached-job-path.ts"() {
|
|
19279
19287
|
"use strict";
|
|
@@ -19282,6 +19290,7 @@ var init_detached_job_path = __esm({
|
|
|
19282
19290
|
import_node_url3 = require("url");
|
|
19283
19291
|
init_packaged_runtime();
|
|
19284
19292
|
import_meta3 = {};
|
|
19293
|
+
DETACHED_JOB_FILENAMES = ["detached-job.cjs", "detached-job.js"];
|
|
19285
19294
|
}
|
|
19286
19295
|
});
|
|
19287
19296
|
|
|
@@ -19496,7 +19505,7 @@ function formatLongRunningDirective(opts) {
|
|
|
19496
19505
|
return [
|
|
19497
19506
|
"Long-running jobs (mandatory when a command may run more than ~30s):",
|
|
19498
19507
|
"A Sideboard worktree turn SIGTERMs the agent shell (and its process group) when the user sends another message or the turn is interrupted. `block_until_ms: 0` is not enough. Do not ask the human to poll.",
|
|
19499
|
-
`Helper (same tool as \`scripts/detached-job.
|
|
19508
|
+
`Helper (same tool as \`scripts/detached-job.cjs\` when that file exists in the worktree): \`${invoke}\``,
|
|
19500
19509
|
`- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree). If JSON says already-running, do not start again.`,
|
|
19501
19510
|
"- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running` \u2014 the side column is the live view.",
|
|
19502
19511
|
`- Loop Sideboard MCP \`wait_for_job\` with the same id (returns in ~45s). stillRunning \u2192 present the same id with \`content=delta\` only \u2192 wait_for_job again. Shell fallback: \`${invoke} wait <id>\`. Do not resend the full log or HTML.`,
|
|
@@ -27202,9 +27211,9 @@ async function startMcpServer() {
|
|
|
27202
27211
|
);
|
|
27203
27212
|
server.tool(
|
|
27204
27213
|
"wait_for_job",
|
|
27205
|
-
"Wait on a detached long job (tests, pack, deploy, connector CLI) started with detached-job.
|
|
27214
|
+
"Wait on a detached long job (tests, pack, deploy, connector CLI) started with detached-job.cjs. MCP clients kill tools around 60s, so this returns within 45s. stillRunning is the source of truth \u2014 if true, present_artifact type=log with content=delta (same artifact_id) and call wait_for_job again. If the job is hanging, producing no useful output, or doing the wrong thing, call stop_job instead of looping forever. Do not end the turn or tell the user you will let them know later. If false, ok/failed is the result.",
|
|
27206
27215
|
{
|
|
27207
|
-
id: import_zod8.z.string().describe("Detached job id (same kebab-case id passed to detached-job.
|
|
27216
|
+
id: import_zod8.z.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
|
|
27208
27217
|
timeoutMs: import_zod8.z.number().optional()
|
|
27209
27218
|
},
|
|
27210
27219
|
async ({ id, timeoutMs }) => {
|
|
@@ -27216,9 +27225,9 @@ async function startMcpServer() {
|
|
|
27216
27225
|
);
|
|
27217
27226
|
server.tool(
|
|
27218
27227
|
"stop_job",
|
|
27219
|
-
"Stop a detached job you started with detached-job.
|
|
27228
|
+
"Stop a detached job you started with detached-job.cjs when it is hanging, producing no useful output, buffering forever, or doing the wrong thing (wrong project, infinite watch, huge dump). Do not stop a pack/test/deploy that is clearly making progress. After stop, present_artifact type=log with status=failed and the last delta, then decide the next step.",
|
|
27220
27229
|
{
|
|
27221
|
-
id: import_zod8.z.string().describe("Detached job id (same kebab-case id passed to detached-job.
|
|
27230
|
+
id: import_zod8.z.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
|
|
27222
27231
|
reason: import_zod8.z.string().optional().describe("Why you are stopping (hanging, no progress, wrong output, already have the error)")
|
|
27223
27232
|
},
|
|
27224
27233
|
async ({ id, reason }) => {
|
package/dist/index.d.cts
CHANGED
|
@@ -2981,6 +2981,11 @@ interface SpawnTurnHandle {
|
|
|
2981
2981
|
/**
|
|
2982
2982
|
* Opt into 1h prompt-cache TTL for desktop gaps (read a diff, Slack, schedules).
|
|
2983
2983
|
* Claude Code / OpenCode default to 5m on API keys. Honor an explicit 5m force.
|
|
2984
|
+
*
|
|
2985
|
+
* Claude Code v2.1.242+ prefers `CLAUDE_CODE_PROMPT_CACHE_TTL` /
|
|
2986
|
+
* `CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL` over `ENABLE_PROMPT_CACHING_1H`
|
|
2987
|
+
* (and over `promptCacheTtl` in settings). Set both so a 5m settings default
|
|
2988
|
+
* does not silently win after a CLI upgrade. Older CLIs ignore the new keys.
|
|
2984
2989
|
*/
|
|
2985
2990
|
declare function applyPromptCacheTtlEnv(agent: AgentKind, env: NodeJS.ProcessEnv): void;
|
|
2986
2991
|
declare function spawnAgentTurn(thread: Thread, input: string | AgentTurnInput, onEvent: (event: AgentEvent) => void): Promise<SpawnTurnHandle>;
|
|
@@ -3495,14 +3500,14 @@ declare const BUNDLED_SKILL_PREFIX = "bundled:";
|
|
|
3495
3500
|
declare function discoverSkills(worktreePath: string): SkillInfo[];
|
|
3496
3501
|
declare function readSkillBody(skillPath: string, maxChars?: number): string;
|
|
3497
3502
|
|
|
3498
|
-
/** Packaged extraResources copy (`Contents/Resources/sideboard-mcp/scripts/detached-job.
|
|
3503
|
+
/** Packaged extraResources copy (`Contents/Resources/sideboard-mcp/scripts/detached-job.cjs`). */
|
|
3499
3504
|
declare function packagedDetachedJobPath(): string | null;
|
|
3500
3505
|
/**
|
|
3501
3506
|
* Absolute path to the detach helper agents should exec.
|
|
3502
3507
|
* Packaged app first, then walk up from this module to the repo `scripts/` copy.
|
|
3503
3508
|
*/
|
|
3504
3509
|
declare function resolveDetachedJobScript(): string | null;
|
|
3505
|
-
/** Shell invocation agents can copy (`node "/abs/path/detached-job.
|
|
3510
|
+
/** Shell invocation agents can copy (`node "/abs/path/detached-job.cjs"`). */
|
|
3506
3511
|
declare function formatDetachedJobInvoke(scriptPath?: string | null): string;
|
|
3507
3512
|
|
|
3508
3513
|
/** Sideboard product skill — available in every worktree, not only this repo. */
|
package/dist/index.d.ts
CHANGED
|
@@ -2981,6 +2981,11 @@ interface SpawnTurnHandle {
|
|
|
2981
2981
|
/**
|
|
2982
2982
|
* Opt into 1h prompt-cache TTL for desktop gaps (read a diff, Slack, schedules).
|
|
2983
2983
|
* Claude Code / OpenCode default to 5m on API keys. Honor an explicit 5m force.
|
|
2984
|
+
*
|
|
2985
|
+
* Claude Code v2.1.242+ prefers `CLAUDE_CODE_PROMPT_CACHE_TTL` /
|
|
2986
|
+
* `CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL` over `ENABLE_PROMPT_CACHING_1H`
|
|
2987
|
+
* (and over `promptCacheTtl` in settings). Set both so a 5m settings default
|
|
2988
|
+
* does not silently win after a CLI upgrade. Older CLIs ignore the new keys.
|
|
2984
2989
|
*/
|
|
2985
2990
|
declare function applyPromptCacheTtlEnv(agent: AgentKind, env: NodeJS.ProcessEnv): void;
|
|
2986
2991
|
declare function spawnAgentTurn(thread: Thread, input: string | AgentTurnInput, onEvent: (event: AgentEvent) => void): Promise<SpawnTurnHandle>;
|
|
@@ -3495,14 +3500,14 @@ declare const BUNDLED_SKILL_PREFIX = "bundled:";
|
|
|
3495
3500
|
declare function discoverSkills(worktreePath: string): SkillInfo[];
|
|
3496
3501
|
declare function readSkillBody(skillPath: string, maxChars?: number): string;
|
|
3497
3502
|
|
|
3498
|
-
/** Packaged extraResources copy (`Contents/Resources/sideboard-mcp/scripts/detached-job.
|
|
3503
|
+
/** Packaged extraResources copy (`Contents/Resources/sideboard-mcp/scripts/detached-job.cjs`). */
|
|
3499
3504
|
declare function packagedDetachedJobPath(): string | null;
|
|
3500
3505
|
/**
|
|
3501
3506
|
* Absolute path to the detach helper agents should exec.
|
|
3502
3507
|
* Packaged app first, then walk up from this module to the repo `scripts/` copy.
|
|
3503
3508
|
*/
|
|
3504
3509
|
declare function resolveDetachedJobScript(): string | null;
|
|
3505
|
-
/** Shell invocation agents can copy (`node "/abs/path/detached-job.
|
|
3510
|
+
/** Shell invocation agents can copy (`node "/abs/path/detached-job.cjs"`). */
|
|
3506
3511
|
declare function formatDetachedJobInvoke(scriptPath?: string | null): string;
|
|
3507
3512
|
|
|
3508
3513
|
/** Sideboard product skill — available in every worktree, not only this repo. */
|
package/dist/index.js
CHANGED
|
@@ -237,7 +237,7 @@ import {
|
|
|
237
237
|
worktreeCleanupSettings,
|
|
238
238
|
wrapReviewSkillMarkdown,
|
|
239
239
|
writeWorktreeFile
|
|
240
|
-
} from "./chunk-
|
|
240
|
+
} from "./chunk-UVLPL2FJ.js";
|
|
241
241
|
import {
|
|
242
242
|
LEGACY_PLAN_FILE_REL,
|
|
243
243
|
PLAN_FILE_NAME,
|
|
@@ -4512,9 +4512,9 @@ async function startMcpServer() {
|
|
|
4512
4512
|
);
|
|
4513
4513
|
server.tool(
|
|
4514
4514
|
"wait_for_job",
|
|
4515
|
-
"Wait on a detached long job (tests, pack, deploy, connector CLI) started with detached-job.
|
|
4515
|
+
"Wait on a detached long job (tests, pack, deploy, connector CLI) started with detached-job.cjs. MCP clients kill tools around 60s, so this returns within 45s. stillRunning is the source of truth \u2014 if true, present_artifact type=log with content=delta (same artifact_id) and call wait_for_job again. If the job is hanging, producing no useful output, or doing the wrong thing, call stop_job instead of looping forever. Do not end the turn or tell the user you will let them know later. If false, ok/failed is the result.",
|
|
4516
4516
|
{
|
|
4517
|
-
id: z8.string().describe("Detached job id (same kebab-case id passed to detached-job.
|
|
4517
|
+
id: z8.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
|
|
4518
4518
|
timeoutMs: z8.number().optional()
|
|
4519
4519
|
},
|
|
4520
4520
|
async ({ id, timeoutMs }) => {
|
|
@@ -4526,9 +4526,9 @@ async function startMcpServer() {
|
|
|
4526
4526
|
);
|
|
4527
4527
|
server.tool(
|
|
4528
4528
|
"stop_job",
|
|
4529
|
-
"Stop a detached job you started with detached-job.
|
|
4529
|
+
"Stop a detached job you started with detached-job.cjs when it is hanging, producing no useful output, buffering forever, or doing the wrong thing (wrong project, infinite watch, huge dump). Do not stop a pack/test/deploy that is clearly making progress. After stop, present_artifact type=log with status=failed and the last delta, then decide the next step.",
|
|
4530
4530
|
{
|
|
4531
|
-
id: z8.string().describe("Detached job id (same kebab-case id passed to detached-job.
|
|
4531
|
+
id: z8.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
|
|
4532
4532
|
reason: z8.string().optional().describe("Why you are stopping (hanging, no progress, wrong output, already have the error)")
|
|
4533
4533
|
},
|
|
4534
4534
|
async ({ id, reason }) => {
|
package/dist/mcp/run-stdio.cjs
CHANGED
|
@@ -11844,6 +11844,8 @@ var init_cursor_stream_coalesce = __esm({
|
|
|
11844
11844
|
function applyPromptCacheTtlEnv(agent, env) {
|
|
11845
11845
|
if (agent === "claude" && env.FORCE_PROMPT_CACHING_5M !== "1") {
|
|
11846
11846
|
env.ENABLE_PROMPT_CACHING_1H ??= "1";
|
|
11847
|
+
env.CLAUDE_CODE_PROMPT_CACHE_TTL ??= "1h";
|
|
11848
|
+
env.CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL ??= "1h";
|
|
11847
11849
|
}
|
|
11848
11850
|
if (agent === "opencode" && env.OPENCODE_ANTHROPIC_FORCE_PROMPT_CACHING_5M !== "1") {
|
|
11849
11851
|
env.OPENCODE_ANTHROPIC_PROMPT_CACHING_1H ??= "1";
|
|
@@ -13858,7 +13860,7 @@ function formatJobStillRunningContinuePrompt(jobIds) {
|
|
|
13858
13860
|
function formatDeferredDoneContinuePrompt() {
|
|
13859
13861
|
return [
|
|
13860
13862
|
"You ended the turn after promising to report later, but no detached job is running.",
|
|
13861
|
-
"If tests/pack/deploy still need to run: start once with detached-job.
|
|
13863
|
+
"If tests/pack/deploy still need to run: start once with detached-job.cjs, present_artifact type=log, then loop wait_for_job until stillRunning is false.",
|
|
13862
13864
|
"Do not say you will let the user know later."
|
|
13863
13865
|
].join(" ");
|
|
13864
13866
|
}
|
|
@@ -13867,7 +13869,7 @@ function turnWatchedDetachedJob(parts) {
|
|
|
13867
13869
|
if (p.type !== "tool") return false;
|
|
13868
13870
|
if (/wait_for_job$/i.test(p.name ?? "") || /stop_job$/i.test(p.name ?? "")) return true;
|
|
13869
13871
|
const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
|
|
13870
|
-
return /detached-job\.js\b/i.test(blob);
|
|
13872
|
+
return /detached-job\.(?:js|cjs)\b/i.test(blob);
|
|
13871
13873
|
});
|
|
13872
13874
|
}
|
|
13873
13875
|
function planJobContinue(opts) {
|
|
@@ -13960,7 +13962,7 @@ async function waitForDetachedJob(cwd, id, opts) {
|
|
|
13960
13962
|
id: jobId,
|
|
13961
13963
|
delta: "",
|
|
13962
13964
|
progress: "No detached job. Start one first.",
|
|
13963
|
-
hint: "Start with detached-job.
|
|
13965
|
+
hint: "Start with detached-job.cjs start <id> -- <command>, then call wait_for_job again."
|
|
13964
13966
|
};
|
|
13965
13967
|
}
|
|
13966
13968
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -14019,7 +14021,7 @@ async function stopDetachedJob(cwd, id, opts) {
|
|
|
14019
14021
|
id: jobId,
|
|
14020
14022
|
delta: "",
|
|
14021
14023
|
progress: "No detached job. Start one first.",
|
|
14022
|
-
hint: "Start with detached-job.
|
|
14024
|
+
hint: "Start with detached-job.cjs start <id> -- <command>, then wait_for_job or stop_job.",
|
|
14023
14025
|
stopped: false,
|
|
14024
14026
|
reason: "not-found"
|
|
14025
14027
|
};
|
|
@@ -17188,21 +17190,21 @@ Do **not** ask the human to check back. Detach, then **wait** in 45s slices (MCP
|
|
|
17188
17190
|
|
|
17189
17191
|
## Tool
|
|
17190
17192
|
|
|
17191
|
-
Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" start\` path injected each turn. If this worktree has \`scripts/detached-job.
|
|
17193
|
+
Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" start\` path injected each turn. If this worktree has \`scripts/detached-job.cjs\`, that is the same tool.
|
|
17192
17194
|
|
|
17193
17195
|
\`\`\`bash
|
|
17194
17196
|
# Start (exits in ~1s; job survives this turn)
|
|
17195
|
-
node <detached-job.
|
|
17197
|
+
node <detached-job.cjs> start <id> -- <command> [args...]
|
|
17196
17198
|
|
|
17197
17199
|
# Wait \u2014 prefer MCP wait_for_job (same 45s / stillRunning contract).
|
|
17198
17200
|
# Shell fallback:
|
|
17199
|
-
node <detached-job.
|
|
17201
|
+
node <detached-job.cjs> wait <id>
|
|
17200
17202
|
|
|
17201
17203
|
# Block until the process exits (humans / a turn that will not be interrupted)
|
|
17202
|
-
node <detached-job.
|
|
17204
|
+
node <detached-job.cjs> wait <id> --until-done
|
|
17203
17205
|
|
|
17204
|
-
node <detached-job.
|
|
17205
|
-
node <detached-job.
|
|
17206
|
+
node <detached-job.cjs> stop <id> [--reason TEXT]
|
|
17207
|
+
node <detached-job.cjs> status <id>
|
|
17206
17208
|
\`\`\`
|
|
17207
17209
|
|
|
17208
17210
|
\`<id>\` is kebab-case (\`mac-release\`, \`core-test\`, \`fly-deploy\`). State is \`.context/.sideboard/detached-jobs/<id>/\` (local scratch).
|
|
@@ -17233,7 +17235,7 @@ After **start**, present once (\`status=running\`, empty or first lines). After
|
|
|
17233
17235
|
Ad-hoc pid/log (legacy or another tool\u2019s files):
|
|
17234
17236
|
|
|
17235
17237
|
\`\`\`bash
|
|
17236
|
-
node <detached-job.
|
|
17238
|
+
node <detached-job.cjs> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
|
|
17237
17239
|
\`\`\`
|
|
17238
17240
|
|
|
17239
17241
|
## Agent loop
|
|
@@ -17242,7 +17244,7 @@ node <detached-job.js> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
|
|
|
17242
17244
|
2. Immediately \`present_artifact\` \`type=log\` (same \`artifact_id\`, \`status=running\`) \u2014 the human should see **working** in the side column, not a \u201Ccheck back later\u201D message.
|
|
17243
17245
|
3. Loop \`wait_for_job\` (or shell \`wait\`). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
|
|
17244
17246
|
4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
|
|
17245
|
-
5. **Stop** when the job is hanging, buffering forever, watching the wrong thing, or you already have the answer. MCP \`stop_job\` (same id, optional \`reason\`) or \`detached-job.
|
|
17247
|
+
5. **Stop** when the job is hanging, buffering forever, watching the wrong thing, or you already have the answer. MCP \`stop_job\` (same id, optional \`reason\`) or \`detached-job.cjs stop <id>\`. Do **not** stop a pack/test/deploy that is clearly making progress.
|
|
17246
17248
|
|
|
17247
17249
|
Never tell the user \u201Csay status when it\u2019s done.\u201D You wait \u2014 unless you decided to stop.
|
|
17248
17250
|
|
|
@@ -17255,7 +17257,7 @@ Huge \`--json\` / \`--expand\` dumps crash any worktree agent mid-turn (Claude /
|
|
|
17255
17257
|
Do **not** watch after every push. If the user gave a goal (Greptile 5/5, CI green, until checks pass), wait the same way:
|
|
17256
17258
|
|
|
17257
17259
|
\`\`\`bash
|
|
17258
|
-
node <detached-job.
|
|
17260
|
+
node <detached-job.cjs> start pr-checks -- gh pr checks --watch
|
|
17259
17261
|
\`\`\`
|
|
17260
17262
|
|
|
17261
17263
|
On miss, fix, commit, push, and watch again until the goal is met or you are blocked. Do not ask the human to poll. Do not merge unless asked.
|
|
@@ -17534,19 +17536,25 @@ var init_expand = __esm({
|
|
|
17534
17536
|
});
|
|
17535
17537
|
|
|
17536
17538
|
// src/skills/detached-job-path.ts
|
|
17539
|
+
function detachedJobScriptIn(dir) {
|
|
17540
|
+
for (const name of DETACHED_JOB_FILENAMES) {
|
|
17541
|
+
const script = (0, import_node_path42.join)(dir, "scripts", name);
|
|
17542
|
+
if ((0, import_node_fs45.existsSync)(script)) return script;
|
|
17543
|
+
}
|
|
17544
|
+
return null;
|
|
17545
|
+
}
|
|
17537
17546
|
function packagedDetachedJobPath() {
|
|
17538
17547
|
const dir = packagedMcpDir();
|
|
17539
17548
|
if (!dir) return null;
|
|
17540
|
-
|
|
17541
|
-
return (0, import_node_fs45.existsSync)(script) ? script : null;
|
|
17549
|
+
return detachedJobScriptIn(dir);
|
|
17542
17550
|
}
|
|
17543
17551
|
function resolveDetachedJobScript() {
|
|
17544
17552
|
const packaged = packagedDetachedJobPath();
|
|
17545
17553
|
if (packaged) return packaged;
|
|
17546
17554
|
let dir = (0, import_node_path42.dirname)((0, import_node_url3.fileURLToPath)(import_meta3.url));
|
|
17547
17555
|
for (let i = 0; i < 8; i++) {
|
|
17548
|
-
const candidate = (
|
|
17549
|
-
if (
|
|
17556
|
+
const candidate = detachedJobScriptIn(dir);
|
|
17557
|
+
if (candidate) return candidate;
|
|
17550
17558
|
const parent = (0, import_node_path42.dirname)(dir);
|
|
17551
17559
|
if (parent === dir) break;
|
|
17552
17560
|
dir = parent;
|
|
@@ -17556,9 +17564,9 @@ function resolveDetachedJobScript() {
|
|
|
17556
17564
|
function formatDetachedJobInvoke(scriptPath) {
|
|
17557
17565
|
const resolved = scriptPath === void 0 ? resolveDetachedJobScript() : scriptPath;
|
|
17558
17566
|
if (resolved) return `node ${JSON.stringify(resolved)}`;
|
|
17559
|
-
return "node scripts/detached-job.
|
|
17567
|
+
return "node scripts/detached-job.cjs";
|
|
17560
17568
|
}
|
|
17561
|
-
var import_node_fs45, import_node_path42, import_node_url3, import_meta3;
|
|
17569
|
+
var import_node_fs45, import_node_path42, import_node_url3, import_meta3, DETACHED_JOB_FILENAMES;
|
|
17562
17570
|
var init_detached_job_path = __esm({
|
|
17563
17571
|
"src/skills/detached-job-path.ts"() {
|
|
17564
17572
|
"use strict";
|
|
@@ -17567,6 +17575,7 @@ var init_detached_job_path = __esm({
|
|
|
17567
17575
|
import_node_url3 = require("url");
|
|
17568
17576
|
init_packaged_runtime();
|
|
17569
17577
|
import_meta3 = {};
|
|
17578
|
+
DETACHED_JOB_FILENAMES = ["detached-job.cjs", "detached-job.js"];
|
|
17570
17579
|
}
|
|
17571
17580
|
});
|
|
17572
17581
|
|
|
@@ -17759,7 +17768,7 @@ function formatLongRunningDirective(opts) {
|
|
|
17759
17768
|
return [
|
|
17760
17769
|
"Long-running jobs (mandatory when a command may run more than ~30s):",
|
|
17761
17770
|
"A Sideboard worktree turn SIGTERMs the agent shell (and its process group) when the user sends another message or the turn is interrupted. `block_until_ms: 0` is not enough. Do not ask the human to poll.",
|
|
17762
|
-
`Helper (same tool as \`scripts/detached-job.
|
|
17771
|
+
`Helper (same tool as \`scripts/detached-job.cjs\` when that file exists in the worktree): \`${invoke}\``,
|
|
17763
17772
|
`- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree). If JSON says already-running, do not start again.`,
|
|
17764
17773
|
"- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running` \u2014 the side column is the live view.",
|
|
17765
17774
|
`- Loop Sideboard MCP \`wait_for_job\` with the same id (returns in ~45s). stillRunning \u2192 present the same id with \`content=delta\` only \u2192 wait_for_job again. Shell fallback: \`${invoke} wait <id>\`. Do not resend the full log or HTML.`,
|
|
@@ -24176,9 +24185,9 @@ async function startMcpServer() {
|
|
|
24176
24185
|
);
|
|
24177
24186
|
server.tool(
|
|
24178
24187
|
"wait_for_job",
|
|
24179
|
-
"Wait on a detached long job (tests, pack, deploy, connector CLI) started with detached-job.
|
|
24188
|
+
"Wait on a detached long job (tests, pack, deploy, connector CLI) started with detached-job.cjs. MCP clients kill tools around 60s, so this returns within 45s. stillRunning is the source of truth \u2014 if true, present_artifact type=log with content=delta (same artifact_id) and call wait_for_job again. If the job is hanging, producing no useful output, or doing the wrong thing, call stop_job instead of looping forever. Do not end the turn or tell the user you will let them know later. If false, ok/failed is the result.",
|
|
24180
24189
|
{
|
|
24181
|
-
id: import_zod8.z.string().describe("Detached job id (same kebab-case id passed to detached-job.
|
|
24190
|
+
id: import_zod8.z.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
|
|
24182
24191
|
timeoutMs: import_zod8.z.number().optional()
|
|
24183
24192
|
},
|
|
24184
24193
|
async ({ id, timeoutMs }) => {
|
|
@@ -24190,9 +24199,9 @@ async function startMcpServer() {
|
|
|
24190
24199
|
);
|
|
24191
24200
|
server.tool(
|
|
24192
24201
|
"stop_job",
|
|
24193
|
-
"Stop a detached job you started with detached-job.
|
|
24202
|
+
"Stop a detached job you started with detached-job.cjs when it is hanging, producing no useful output, buffering forever, or doing the wrong thing (wrong project, infinite watch, huge dump). Do not stop a pack/test/deploy that is clearly making progress. After stop, present_artifact type=log with status=failed and the last delta, then decide the next step.",
|
|
24194
24203
|
{
|
|
24195
|
-
id: import_zod8.z.string().describe("Detached job id (same kebab-case id passed to detached-job.
|
|
24204
|
+
id: import_zod8.z.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
|
|
24196
24205
|
reason: import_zod8.z.string().optional().describe("Why you are stopping (hanging, no progress, wrong output, already have the error)")
|
|
24197
24206
|
},
|
|
24198
24207
|
async ({ id, reason }) => {
|
package/dist/mcp/run-stdio.js
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
syncBoardPins,
|
|
38
38
|
updateSchedule,
|
|
39
39
|
waitForDetachedJob
|
|
40
|
-
} from "../chunk-
|
|
40
|
+
} from "../chunk-NYMFNUT5.js";
|
|
41
41
|
import {
|
|
42
42
|
isInternalAgentStatusText,
|
|
43
43
|
listModelsForAgent,
|
|
@@ -3261,9 +3261,9 @@ async function startMcpServer() {
|
|
|
3261
3261
|
);
|
|
3262
3262
|
server.tool(
|
|
3263
3263
|
"wait_for_job",
|
|
3264
|
-
"Wait on a detached long job (tests, pack, deploy, connector CLI) started with detached-job.
|
|
3264
|
+
"Wait on a detached long job (tests, pack, deploy, connector CLI) started with detached-job.cjs. MCP clients kill tools around 60s, so this returns within 45s. stillRunning is the source of truth \u2014 if true, present_artifact type=log with content=delta (same artifact_id) and call wait_for_job again. If the job is hanging, producing no useful output, or doing the wrong thing, call stop_job instead of looping forever. Do not end the turn or tell the user you will let them know later. If false, ok/failed is the result.",
|
|
3265
3265
|
{
|
|
3266
|
-
id: z8.string().describe("Detached job id (same kebab-case id passed to detached-job.
|
|
3266
|
+
id: z8.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
|
|
3267
3267
|
timeoutMs: z8.number().optional()
|
|
3268
3268
|
},
|
|
3269
3269
|
async ({ id, timeoutMs }) => {
|
|
@@ -3275,9 +3275,9 @@ async function startMcpServer() {
|
|
|
3275
3275
|
);
|
|
3276
3276
|
server.tool(
|
|
3277
3277
|
"stop_job",
|
|
3278
|
-
"Stop a detached job you started with detached-job.
|
|
3278
|
+
"Stop a detached job you started with detached-job.cjs when it is hanging, producing no useful output, buffering forever, or doing the wrong thing (wrong project, infinite watch, huge dump). Do not stop a pack/test/deploy that is clearly making progress. After stop, present_artifact type=log with status=failed and the last delta, then decide the next step.",
|
|
3279
3279
|
{
|
|
3280
|
-
id: z8.string().describe("Detached job id (same kebab-case id passed to detached-job.
|
|
3280
|
+
id: z8.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
|
|
3281
3281
|
reason: z8.string().optional().describe("Why you are stopping (hanging, no progress, wrong output, already have the error)")
|
|
3282
3282
|
},
|
|
3283
3283
|
async ({ id, reason }) => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sideboard-ai/core",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Sideboard core
|
|
3
|
+
"version": "0.1.168",
|
|
4
|
+
"description": "Sideboard core — orchestration, agents, git worktrees, MCP server",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.cjs",
|