@sideboard-ai/core 0.1.184 → 0.1.186
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-IMCIHSIF.js → chunk-3QTEXR7L.js} +20 -14
- package/dist/{chunk-4IQQEJ2C.js → chunk-VTIVVVEC.js} +20 -14
- package/dist/index.cjs +110 -72
- package/dist/index.d.cts +8 -7
- package/dist/index.d.ts +8 -7
- package/dist/index.js +93 -61
- package/dist/mcp/run-stdio.cjs +110 -72
- package/dist/mcp/run-stdio.js +93 -61
- package/dist/{orchestrator-4GLAG7YH.js → orchestrator-RJEULR5I.js} +1 -1
- package/dist/{orchestrator-DKVX67MH.js → orchestrator-RNDMJXAY.js} +1 -1
- package/package.json +1 -1
|
@@ -402,7 +402,7 @@ async function continueSourceThread(threadId, prompt) {
|
|
|
402
402
|
await continueOnReply(threadId, prompt);
|
|
403
403
|
return;
|
|
404
404
|
}
|
|
405
|
-
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-
|
|
405
|
+
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-RJEULR5I.js");
|
|
406
406
|
await getOrchestrator2().send(threadId, prompt);
|
|
407
407
|
} catch {
|
|
408
408
|
}
|
|
@@ -2675,8 +2675,8 @@ function mcpWaitFinishedHint(status) {
|
|
|
2675
2675
|
|
|
2676
2676
|
// src/mcp/wait-for-job.ts
|
|
2677
2677
|
var MAX_JOB_CONTINUES = 8;
|
|
2678
|
-
var MCP_WAIT_JOB_STILL_RUNNING_HINT = "Job is still running.
|
|
2679
|
-
var MCP_STOP_JOB_HINT = "Stopped.
|
|
2678
|
+
var MCP_WAIT_JOB_STILL_RUNNING_HINT = "Job is still running. The type=log pane already appended this delta. Call wait_for_job again. If it is hanging, producing no useful output, or doing the wrong thing, call stop_job (same id) instead of looping forever. Do not end the turn or tell the user you will let them know later.";
|
|
2679
|
+
var MCP_STOP_JOB_HINT = "Stopped. The type=log pane is status=failed with the last delta. Do not wait again unless you start a new command.";
|
|
2680
2680
|
var JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
2681
2681
|
function mcpWaitForJobTimeoutMs(requested) {
|
|
2682
2682
|
return mcpWaitForTurnTimeoutMs(requested);
|
|
@@ -2755,7 +2755,7 @@ function formatJobStillRunningContinuePrompt(jobIds) {
|
|
|
2755
2755
|
const ids = jobIds.join(", ");
|
|
2756
2756
|
return [
|
|
2757
2757
|
`Detached job still running: ${ids}.`,
|
|
2758
|
-
"Do not end this turn. Loop wait_for_job (same id)
|
|
2758
|
+
"Do not end this turn. Loop wait_for_job (same id) until stillRunning is false. The log pane updates from each wait result.",
|
|
2759
2759
|
"If the job is hanging or doing the wrong thing, call stop_job (same id) instead of looping forever.",
|
|
2760
2760
|
"Then report the result. Do not tell the user you will let them know later."
|
|
2761
2761
|
].join(" ");
|
|
@@ -2763,7 +2763,7 @@ function formatJobStillRunningContinuePrompt(jobIds) {
|
|
|
2763
2763
|
function formatDeferredDoneContinuePrompt() {
|
|
2764
2764
|
return [
|
|
2765
2765
|
"You ended the turn after promising to report later, but no detached job is running.",
|
|
2766
|
-
"If tests/pack/deploy still need to run: start once with detached-job.cjs,
|
|
2766
|
+
"If tests/pack/deploy still need to run: start once with detached-job.cjs, then loop wait_for_job until stillRunning is false (the log pane updates from wait JSON).",
|
|
2767
2767
|
"Do not say you will let the user know later."
|
|
2768
2768
|
].join(" ");
|
|
2769
2769
|
}
|
|
@@ -2798,6 +2798,10 @@ function tailProgress(logFile, maxLines = 12) {
|
|
|
2798
2798
|
const lines = readFileSync7(logFile, "utf8").split("\n");
|
|
2799
2799
|
return lines.slice(-maxLines).join("\n");
|
|
2800
2800
|
}
|
|
2801
|
+
function lastProgressLine(progress) {
|
|
2802
|
+
const last = progress.split("\n").map((l) => l.trim()).filter((l) => l && l !== "(no log yet)").at(-1);
|
|
2803
|
+
return last ? last.slice(0, 72) : void 0;
|
|
2804
|
+
}
|
|
2801
2805
|
function readLogLines(file) {
|
|
2802
2806
|
if (!existsSync9(file)) return [];
|
|
2803
2807
|
const lines = readFileSync7(file, "utf8").split("\n");
|
|
@@ -2834,6 +2838,7 @@ function toResult(id, snap, extra) {
|
|
|
2834
2838
|
} catch {
|
|
2835
2839
|
}
|
|
2836
2840
|
const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
|
|
2841
|
+
const progress = extra?.progress ?? snap.progress;
|
|
2837
2842
|
return {
|
|
2838
2843
|
stillRunning,
|
|
2839
2844
|
ok,
|
|
@@ -2842,8 +2847,9 @@ function toResult(id, snap, extra) {
|
|
|
2842
2847
|
id,
|
|
2843
2848
|
pid: snap.pid,
|
|
2844
2849
|
exitCode: snap.exitCode ?? void 0,
|
|
2850
|
+
phase: lastProgressLine(progress),
|
|
2845
2851
|
delta,
|
|
2846
|
-
progress
|
|
2852
|
+
progress,
|
|
2847
2853
|
hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
|
|
2848
2854
|
};
|
|
2849
2855
|
}
|
|
@@ -3101,7 +3107,7 @@ function classifyWorktreeOwnership(group, viewerLogin = "") {
|
|
|
3101
3107
|
const reviewing = group.some((t) => {
|
|
3102
3108
|
const author = normalizeViewerLogin(t.prAuthorLogin);
|
|
3103
3109
|
if (author && me && author !== me) return true;
|
|
3104
|
-
if (t.sourceType === "pr" && (!author ||
|
|
3110
|
+
if (t.sourceType === "pr" && me && (!author || author !== me)) return true;
|
|
3105
3111
|
if (me && loginsInclude(t.prReviewerLogins, me)) return true;
|
|
3106
3112
|
return false;
|
|
3107
3113
|
});
|
|
@@ -5490,7 +5496,7 @@ Wait JSON:
|
|
|
5490
5496
|
|
|
5491
5497
|
## Sideboard UI (append-only log)
|
|
5492
5498
|
|
|
5493
|
-
The side column **is** the live view.
|
|
5499
|
+
The side column **is** the live view. \`wait_for_job\` / \`stop_job\` (and shell \`detached-job\` wait JSON) open a \`type=log\` pane automatically (\`artifact_id\` = job id, content = \`delta\`). You may still call \`present_artifact\` **\`type=log\`** for a custom title. Each update **appends** \u2014 send only \`delta\`, not the full log and not HTML.
|
|
5494
5500
|
|
|
5495
5501
|
\`\`\`
|
|
5496
5502
|
present_artifact
|
|
@@ -5502,7 +5508,7 @@ present_artifact
|
|
|
5502
5508
|
phase: <wait.phase>
|
|
5503
5509
|
\`\`\`
|
|
5504
5510
|
|
|
5505
|
-
After **start**,
|
|
5511
|
+
After **start**, the first \`wait_for_job\` opens the pane (\`status=running\`). Do **not** dump the log in chat. Do **not** also fence HTML.
|
|
5506
5512
|
|
|
5507
5513
|
Ad-hoc pid/log (legacy or another tool\u2019s files):
|
|
5508
5514
|
|
|
@@ -5513,8 +5519,8 @@ node <detached-job.cjs> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
|
|
|
5513
5519
|
## Agent loop
|
|
5514
5520
|
|
|
5515
5521
|
1. \`start\` once. If JSON says \`already-running\`, do not start again.
|
|
5516
|
-
2.
|
|
5517
|
-
3.
|
|
5522
|
+
2. Loop \`wait_for_job\` (or shell \`wait\`). The human should see **working** in the side column from the wait JSON \u2014 do not also dump the log in chat.
|
|
5523
|
+
3. After each slice, the same log pane appends \`delta\`. Optional: \`present_artifact\` the same id for a custom title.
|
|
5518
5524
|
4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
|
|
5519
5525
|
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.
|
|
5520
5526
|
|
|
@@ -5999,7 +6005,7 @@ function formatLongRunningDirective(opts) {
|
|
|
5999
6005
|
"Long-running jobs (mandatory when a command may run more than ~30s \u2014 pack, test, deploy, `gh pr checks --watch`):",
|
|
6000
6006
|
"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. Detach instead, and never ask the human to poll.",
|
|
6001
6007
|
`- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree; same tool as \`scripts/detached-job.cjs\` when the worktree has it). If JSON says already-running, do not start again.`,
|
|
6002
|
-
"-
|
|
6008
|
+
"- Loop Sideboard MCP `wait_for_job` (returns in ~45s). The desktop opens a `type=log` pane from wait/stop (and shell detached-job JSON) automatically \u2014 you may still `present_artifact` with `artifact_id=<id>` for a custom title. stillRunning \u2192 wait again. Shell fallback: the same helper with `wait <id>`.",
|
|
6003
6009
|
"- Hanging, no useful output, or the wrong thing \u2192 `stop_job` (or the helper with `stop <id>`). Do not stop a pack/test/deploy that is clearly making progress.",
|
|
6004
6010
|
"- Stay in the loop until stillRunning is false (or you stopped it). ok \u2192 finish the task. failed / stopped \u2192 read the log, fix or narrow the command, start once.",
|
|
6005
6011
|
"State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
|
|
@@ -6007,7 +6013,7 @@ function formatLongRunningDirective(opts) {
|
|
|
6007
6013
|
}
|
|
6008
6014
|
function formatLongRunningReminder(opts) {
|
|
6009
6015
|
const invoke = formatDetachedJobInvoke(opts?.scriptPath);
|
|
6010
|
-
return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait_for_job (or detached-job wait)
|
|
6016
|
+
return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait_for_job (or detached-job wait). The log pane updates from wait JSON; present_artifact type=log is optional. stop_job if hanging or wrong. Do not say you will let the user know later \u2014 stay in the turn.`;
|
|
6011
6017
|
}
|
|
6012
6018
|
function formatArtifactDirective() {
|
|
6013
6019
|
return [
|
|
@@ -6419,7 +6425,7 @@ function formatScheduledPrompt(name, prompt) {
|
|
|
6419
6425
|
${prompt}`;
|
|
6420
6426
|
}
|
|
6421
6427
|
async function defaultDeps() {
|
|
6422
|
-
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-
|
|
6428
|
+
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-RJEULR5I.js");
|
|
6423
6429
|
const orch = getOrchestrator2();
|
|
6424
6430
|
return {
|
|
6425
6431
|
findThread: (id) => findThreadByRef(id),
|
|
@@ -467,7 +467,7 @@ async function continueSourceThread(threadId, prompt) {
|
|
|
467
467
|
await continueOnReply(threadId, prompt);
|
|
468
468
|
return;
|
|
469
469
|
}
|
|
470
|
-
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-
|
|
470
|
+
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-RNDMJXAY.js");
|
|
471
471
|
await getOrchestrator2().send(threadId, prompt);
|
|
472
472
|
} catch {
|
|
473
473
|
}
|
|
@@ -2762,8 +2762,8 @@ function mcpWaitFinishedHint(status) {
|
|
|
2762
2762
|
|
|
2763
2763
|
// src/mcp/wait-for-job.ts
|
|
2764
2764
|
var MAX_JOB_CONTINUES = 8;
|
|
2765
|
-
var MCP_WAIT_JOB_STILL_RUNNING_HINT = "Job is still running.
|
|
2766
|
-
var MCP_STOP_JOB_HINT = "Stopped.
|
|
2765
|
+
var MCP_WAIT_JOB_STILL_RUNNING_HINT = "Job is still running. The type=log pane already appended this delta. Call wait_for_job again. If it is hanging, producing no useful output, or doing the wrong thing, call stop_job (same id) instead of looping forever. Do not end the turn or tell the user you will let them know later.";
|
|
2766
|
+
var MCP_STOP_JOB_HINT = "Stopped. The type=log pane is status=failed with the last delta. Do not wait again unless you start a new command.";
|
|
2767
2767
|
var JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
2768
2768
|
function mcpWaitForJobTimeoutMs(requested) {
|
|
2769
2769
|
return mcpWaitForTurnTimeoutMs(requested);
|
|
@@ -2842,7 +2842,7 @@ function formatJobStillRunningContinuePrompt(jobIds) {
|
|
|
2842
2842
|
const ids = jobIds.join(", ");
|
|
2843
2843
|
return [
|
|
2844
2844
|
`Detached job still running: ${ids}.`,
|
|
2845
|
-
"Do not end this turn. Loop wait_for_job (same id)
|
|
2845
|
+
"Do not end this turn. Loop wait_for_job (same id) until stillRunning is false. The log pane updates from each wait result.",
|
|
2846
2846
|
"If the job is hanging or doing the wrong thing, call stop_job (same id) instead of looping forever.",
|
|
2847
2847
|
"Then report the result. Do not tell the user you will let them know later."
|
|
2848
2848
|
].join(" ");
|
|
@@ -2850,7 +2850,7 @@ function formatJobStillRunningContinuePrompt(jobIds) {
|
|
|
2850
2850
|
function formatDeferredDoneContinuePrompt() {
|
|
2851
2851
|
return [
|
|
2852
2852
|
"You ended the turn after promising to report later, but no detached job is running.",
|
|
2853
|
-
"If tests/pack/deploy still need to run: start once with detached-job.cjs,
|
|
2853
|
+
"If tests/pack/deploy still need to run: start once with detached-job.cjs, then loop wait_for_job until stillRunning is false (the log pane updates from wait JSON).",
|
|
2854
2854
|
"Do not say you will let the user know later."
|
|
2855
2855
|
].join(" ");
|
|
2856
2856
|
}
|
|
@@ -2885,6 +2885,10 @@ function tailProgress(logFile, maxLines = 12) {
|
|
|
2885
2885
|
const lines = readFileSync7(logFile, "utf8").split("\n");
|
|
2886
2886
|
return lines.slice(-maxLines).join("\n");
|
|
2887
2887
|
}
|
|
2888
|
+
function lastProgressLine(progress) {
|
|
2889
|
+
const last = progress.split("\n").map((l) => l.trim()).filter((l) => l && l !== "(no log yet)").at(-1);
|
|
2890
|
+
return last ? last.slice(0, 72) : void 0;
|
|
2891
|
+
}
|
|
2888
2892
|
function readLogLines(file) {
|
|
2889
2893
|
if (!existsSync9(file)) return [];
|
|
2890
2894
|
const lines = readFileSync7(file, "utf8").split("\n");
|
|
@@ -2921,6 +2925,7 @@ function toResult(id, snap, extra) {
|
|
|
2921
2925
|
} catch {
|
|
2922
2926
|
}
|
|
2923
2927
|
const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
|
|
2928
|
+
const progress = extra?.progress ?? snap.progress;
|
|
2924
2929
|
return {
|
|
2925
2930
|
stillRunning,
|
|
2926
2931
|
ok,
|
|
@@ -2929,8 +2934,9 @@ function toResult(id, snap, extra) {
|
|
|
2929
2934
|
id,
|
|
2930
2935
|
pid: snap.pid,
|
|
2931
2936
|
exitCode: snap.exitCode ?? void 0,
|
|
2937
|
+
phase: lastProgressLine(progress),
|
|
2932
2938
|
delta,
|
|
2933
|
-
progress
|
|
2939
|
+
progress,
|
|
2934
2940
|
hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
|
|
2935
2941
|
};
|
|
2936
2942
|
}
|
|
@@ -3193,7 +3199,7 @@ function classifyWorktreeOwnership(group, viewerLogin = "") {
|
|
|
3193
3199
|
const reviewing = group.some((t) => {
|
|
3194
3200
|
const author = normalizeViewerLogin(t.prAuthorLogin);
|
|
3195
3201
|
if (author && me && author !== me) return true;
|
|
3196
|
-
if (t.sourceType === "pr" && (!author ||
|
|
3202
|
+
if (t.sourceType === "pr" && me && (!author || author !== me)) return true;
|
|
3197
3203
|
if (me && loginsInclude(t.prReviewerLogins, me)) return true;
|
|
3198
3204
|
return false;
|
|
3199
3205
|
});
|
|
@@ -5644,7 +5650,7 @@ Wait JSON:
|
|
|
5644
5650
|
|
|
5645
5651
|
## Sideboard UI (append-only log)
|
|
5646
5652
|
|
|
5647
|
-
The side column **is** the live view.
|
|
5653
|
+
The side column **is** the live view. \`wait_for_job\` / \`stop_job\` (and shell \`detached-job\` wait JSON) open a \`type=log\` pane automatically (\`artifact_id\` = job id, content = \`delta\`). You may still call \`present_artifact\` **\`type=log\`** for a custom title. Each update **appends** \u2014 send only \`delta\`, not the full log and not HTML.
|
|
5648
5654
|
|
|
5649
5655
|
\`\`\`
|
|
5650
5656
|
present_artifact
|
|
@@ -5656,7 +5662,7 @@ present_artifact
|
|
|
5656
5662
|
phase: <wait.phase>
|
|
5657
5663
|
\`\`\`
|
|
5658
5664
|
|
|
5659
|
-
After **start**,
|
|
5665
|
+
After **start**, the first \`wait_for_job\` opens the pane (\`status=running\`). Do **not** dump the log in chat. Do **not** also fence HTML.
|
|
5660
5666
|
|
|
5661
5667
|
Ad-hoc pid/log (legacy or another tool\u2019s files):
|
|
5662
5668
|
|
|
@@ -5667,8 +5673,8 @@ node <detached-job.cjs> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
|
|
|
5667
5673
|
## Agent loop
|
|
5668
5674
|
|
|
5669
5675
|
1. \`start\` once. If JSON says \`already-running\`, do not start again.
|
|
5670
|
-
2.
|
|
5671
|
-
3.
|
|
5676
|
+
2. Loop \`wait_for_job\` (or shell \`wait\`). The human should see **working** in the side column from the wait JSON \u2014 do not also dump the log in chat.
|
|
5677
|
+
3. After each slice, the same log pane appends \`delta\`. Optional: \`present_artifact\` the same id for a custom title.
|
|
5672
5678
|
4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
|
|
5673
5679
|
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.
|
|
5674
5680
|
|
|
@@ -6175,7 +6181,7 @@ function formatLongRunningDirective(opts) {
|
|
|
6175
6181
|
"Long-running jobs (mandatory when a command may run more than ~30s \u2014 pack, test, deploy, `gh pr checks --watch`):",
|
|
6176
6182
|
"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. Detach instead, and never ask the human to poll.",
|
|
6177
6183
|
`- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree; same tool as \`scripts/detached-job.cjs\` when the worktree has it). If JSON says already-running, do not start again.`,
|
|
6178
|
-
"-
|
|
6184
|
+
"- Loop Sideboard MCP `wait_for_job` (returns in ~45s). The desktop opens a `type=log` pane from wait/stop (and shell detached-job JSON) automatically \u2014 you may still `present_artifact` with `artifact_id=<id>` for a custom title. stillRunning \u2192 wait again. Shell fallback: the same helper with `wait <id>`.",
|
|
6179
6185
|
"- Hanging, no useful output, or the wrong thing \u2192 `stop_job` (or the helper with `stop <id>`). Do not stop a pack/test/deploy that is clearly making progress.",
|
|
6180
6186
|
"- Stay in the loop until stillRunning is false (or you stopped it). ok \u2192 finish the task. failed / stopped \u2192 read the log, fix or narrow the command, start once.",
|
|
6181
6187
|
"State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
|
|
@@ -6183,7 +6189,7 @@ function formatLongRunningDirective(opts) {
|
|
|
6183
6189
|
}
|
|
6184
6190
|
function formatLongRunningReminder(opts) {
|
|
6185
6191
|
const invoke = formatDetachedJobInvoke(opts?.scriptPath);
|
|
6186
|
-
return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait_for_job (or detached-job wait)
|
|
6192
|
+
return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait_for_job (or detached-job wait). The log pane updates from wait JSON; present_artifact type=log is optional. stop_job if hanging or wrong. Do not say you will let the user know later \u2014 stay in the turn.`;
|
|
6187
6193
|
}
|
|
6188
6194
|
function formatArtifactDirective() {
|
|
6189
6195
|
return [
|
|
@@ -6766,7 +6772,7 @@ function formatScheduledPrompt(name, prompt) {
|
|
|
6766
6772
|
${prompt}`;
|
|
6767
6773
|
}
|
|
6768
6774
|
async function defaultDeps() {
|
|
6769
|
-
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-
|
|
6775
|
+
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-RNDMJXAY.js");
|
|
6770
6776
|
const orch = getOrchestrator2();
|
|
6771
6777
|
return {
|
|
6772
6778
|
findThread: (id) => findThreadByRef(id),
|
package/dist/index.cjs
CHANGED
|
@@ -15990,7 +15990,7 @@ function formatJobStillRunningContinuePrompt(jobIds) {
|
|
|
15990
15990
|
const ids = jobIds.join(", ");
|
|
15991
15991
|
return [
|
|
15992
15992
|
`Detached job still running: ${ids}.`,
|
|
15993
|
-
"Do not end this turn. Loop wait_for_job (same id)
|
|
15993
|
+
"Do not end this turn. Loop wait_for_job (same id) until stillRunning is false. The log pane updates from each wait result.",
|
|
15994
15994
|
"If the job is hanging or doing the wrong thing, call stop_job (same id) instead of looping forever.",
|
|
15995
15995
|
"Then report the result. Do not tell the user you will let them know later."
|
|
15996
15996
|
].join(" ");
|
|
@@ -15998,7 +15998,7 @@ function formatJobStillRunningContinuePrompt(jobIds) {
|
|
|
15998
15998
|
function formatDeferredDoneContinuePrompt() {
|
|
15999
15999
|
return [
|
|
16000
16000
|
"You ended the turn after promising to report later, but no detached job is running.",
|
|
16001
|
-
"If tests/pack/deploy still need to run: start once with detached-job.cjs,
|
|
16001
|
+
"If tests/pack/deploy still need to run: start once with detached-job.cjs, then loop wait_for_job until stillRunning is false (the log pane updates from wait JSON).",
|
|
16002
16002
|
"Do not say you will let the user know later."
|
|
16003
16003
|
].join(" ");
|
|
16004
16004
|
}
|
|
@@ -16033,6 +16033,10 @@ function tailProgress(logFile, maxLines = 12) {
|
|
|
16033
16033
|
const lines = (0, import_node_fs42.readFileSync)(logFile, "utf8").split("\n");
|
|
16034
16034
|
return lines.slice(-maxLines).join("\n");
|
|
16035
16035
|
}
|
|
16036
|
+
function lastProgressLine(progress) {
|
|
16037
|
+
const last = progress.split("\n").map((l) => l.trim()).filter((l) => l && l !== "(no log yet)").at(-1);
|
|
16038
|
+
return last ? last.slice(0, 72) : void 0;
|
|
16039
|
+
}
|
|
16036
16040
|
function readLogLines(file) {
|
|
16037
16041
|
if (!(0, import_node_fs42.existsSync)(file)) return [];
|
|
16038
16042
|
const lines = (0, import_node_fs42.readFileSync)(file, "utf8").split("\n");
|
|
@@ -16069,6 +16073,7 @@ function toResult(id, snap, extra) {
|
|
|
16069
16073
|
} catch {
|
|
16070
16074
|
}
|
|
16071
16075
|
const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
|
|
16076
|
+
const progress = extra?.progress ?? snap.progress;
|
|
16072
16077
|
return {
|
|
16073
16078
|
stillRunning,
|
|
16074
16079
|
ok,
|
|
@@ -16077,8 +16082,9 @@ function toResult(id, snap, extra) {
|
|
|
16077
16082
|
id,
|
|
16078
16083
|
pid: snap.pid,
|
|
16079
16084
|
exitCode: snap.exitCode ?? void 0,
|
|
16085
|
+
phase: lastProgressLine(progress),
|
|
16080
16086
|
delta,
|
|
16081
|
-
progress
|
|
16087
|
+
progress,
|
|
16082
16088
|
hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
|
|
16083
16089
|
};
|
|
16084
16090
|
}
|
|
@@ -16218,8 +16224,8 @@ var init_wait_for_job = __esm({
|
|
|
16218
16224
|
init_workspace_scratch();
|
|
16219
16225
|
init_wait_for_turn();
|
|
16220
16226
|
MAX_JOB_CONTINUES = 8;
|
|
16221
|
-
MCP_WAIT_JOB_STILL_RUNNING_HINT = "Job is still running.
|
|
16222
|
-
MCP_STOP_JOB_HINT = "Stopped.
|
|
16227
|
+
MCP_WAIT_JOB_STILL_RUNNING_HINT = "Job is still running. The type=log pane already appended this delta. Call wait_for_job again. If it is hanging, producing no useful output, or doing the wrong thing, call stop_job (same id) instead of looping forever. Do not end the turn or tell the user you will let them know later.";
|
|
16228
|
+
MCP_STOP_JOB_HINT = "Stopped. The type=log pane is status=failed with the last delta. Do not wait again unless you start a new command.";
|
|
16223
16229
|
JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
16224
16230
|
}
|
|
16225
16231
|
});
|
|
@@ -16349,7 +16355,7 @@ function classifyWorktreeOwnership(group, viewerLogin = "") {
|
|
|
16349
16355
|
const reviewing = group.some((t) => {
|
|
16350
16356
|
const author = normalizeViewerLogin(t.prAuthorLogin);
|
|
16351
16357
|
if (author && me && author !== me) return true;
|
|
16352
|
-
if (t.sourceType === "pr" && (!author ||
|
|
16358
|
+
if (t.sourceType === "pr" && me && (!author || author !== me)) return true;
|
|
16353
16359
|
if (me && loginsInclude(t.prReviewerLogins, me)) return true;
|
|
16354
16360
|
return false;
|
|
16355
16361
|
});
|
|
@@ -19472,7 +19478,7 @@ Wait JSON:
|
|
|
19472
19478
|
|
|
19473
19479
|
## Sideboard UI (append-only log)
|
|
19474
19480
|
|
|
19475
|
-
The side column **is** the live view.
|
|
19481
|
+
The side column **is** the live view. \`wait_for_job\` / \`stop_job\` (and shell \`detached-job\` wait JSON) open a \`type=log\` pane automatically (\`artifact_id\` = job id, content = \`delta\`). You may still call \`present_artifact\` **\`type=log\`** for a custom title. Each update **appends** \u2014 send only \`delta\`, not the full log and not HTML.
|
|
19476
19482
|
|
|
19477
19483
|
\`\`\`
|
|
19478
19484
|
present_artifact
|
|
@@ -19484,7 +19490,7 @@ present_artifact
|
|
|
19484
19490
|
phase: <wait.phase>
|
|
19485
19491
|
\`\`\`
|
|
19486
19492
|
|
|
19487
|
-
After **start**,
|
|
19493
|
+
After **start**, the first \`wait_for_job\` opens the pane (\`status=running\`). Do **not** dump the log in chat. Do **not** also fence HTML.
|
|
19488
19494
|
|
|
19489
19495
|
Ad-hoc pid/log (legacy or another tool\u2019s files):
|
|
19490
19496
|
|
|
@@ -19495,8 +19501,8 @@ node <detached-job.cjs> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
|
|
|
19495
19501
|
## Agent loop
|
|
19496
19502
|
|
|
19497
19503
|
1. \`start\` once. If JSON says \`already-running\`, do not start again.
|
|
19498
|
-
2.
|
|
19499
|
-
3.
|
|
19504
|
+
2. Loop \`wait_for_job\` (or shell \`wait\`). The human should see **working** in the side column from the wait JSON \u2014 do not also dump the log in chat.
|
|
19505
|
+
3. After each slice, the same log pane appends \`delta\`. Optional: \`present_artifact\` the same id for a custom title.
|
|
19500
19506
|
4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
|
|
19501
19507
|
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.
|
|
19502
19508
|
|
|
@@ -20024,7 +20030,7 @@ function formatLongRunningDirective(opts) {
|
|
|
20024
20030
|
"Long-running jobs (mandatory when a command may run more than ~30s \u2014 pack, test, deploy, `gh pr checks --watch`):",
|
|
20025
20031
|
"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. Detach instead, and never ask the human to poll.",
|
|
20026
20032
|
`- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree; same tool as \`scripts/detached-job.cjs\` when the worktree has it). If JSON says already-running, do not start again.`,
|
|
20027
|
-
"-
|
|
20033
|
+
"- Loop Sideboard MCP `wait_for_job` (returns in ~45s). The desktop opens a `type=log` pane from wait/stop (and shell detached-job JSON) automatically \u2014 you may still `present_artifact` with `artifact_id=<id>` for a custom title. stillRunning \u2192 wait again. Shell fallback: the same helper with `wait <id>`.",
|
|
20028
20034
|
"- Hanging, no useful output, or the wrong thing \u2192 `stop_job` (or the helper with `stop <id>`). Do not stop a pack/test/deploy that is clearly making progress.",
|
|
20029
20035
|
"- Stay in the loop until stillRunning is false (or you stopped it). ok \u2192 finish the task. failed / stopped \u2192 read the log, fix or narrow the command, start once.",
|
|
20030
20036
|
"State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
|
|
@@ -20032,7 +20038,7 @@ function formatLongRunningDirective(opts) {
|
|
|
20032
20038
|
}
|
|
20033
20039
|
function formatLongRunningReminder(opts) {
|
|
20034
20040
|
const invoke = formatDetachedJobInvoke(opts?.scriptPath);
|
|
20035
|
-
return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait_for_job (or detached-job wait)
|
|
20041
|
+
return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait_for_job (or detached-job wait). The log pane updates from wait JSON; present_artifact type=log is optional. stop_job if hanging or wrong. Do not say you will let the user know later \u2014 stay in the turn.`;
|
|
20036
20042
|
}
|
|
20037
20043
|
function formatArtifactDirective() {
|
|
20038
20044
|
return [
|
|
@@ -24622,12 +24628,27 @@ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
|
24622
24628
|
function assigneeFilterKey(assignee) {
|
|
24623
24629
|
return (assignee ?? "").trim();
|
|
24624
24630
|
}
|
|
24631
|
+
function applyLinearIssueQueryFilter(filter, query) {
|
|
24632
|
+
const term = query?.trim();
|
|
24633
|
+
if (!term) return;
|
|
24634
|
+
const or = [
|
|
24635
|
+
{ title: { containsIgnoreCase: term } },
|
|
24636
|
+
{ description: { containsIgnoreCase: term } }
|
|
24637
|
+
];
|
|
24638
|
+
const identifierMatch = term.match(/^[A-Za-z][\w]*-(\d+)$/);
|
|
24639
|
+
const asNumber = identifierMatch ? Number(identifierMatch[1]) : /^\d+$/.test(term) ? Number(term) : NaN;
|
|
24640
|
+
if (Number.isFinite(asNumber) && asNumber > 0) {
|
|
24641
|
+
or.push({ number: { eq: asNumber } });
|
|
24642
|
+
}
|
|
24643
|
+
filter.or = or;
|
|
24644
|
+
}
|
|
24625
24645
|
function buildLinearIssueFilter(input) {
|
|
24626
24646
|
const filter = {
|
|
24627
24647
|
state: { type: { nin: ["completed", "canceled"] } }
|
|
24628
24648
|
};
|
|
24629
24649
|
const since = input?.updatedSince?.trim();
|
|
24630
24650
|
if (since) filter.updatedAt = { gte: since };
|
|
24651
|
+
applyLinearIssueQueryFilter(filter, input?.query);
|
|
24631
24652
|
const raw = assigneeFilterKey(input?.assignee) || "me";
|
|
24632
24653
|
const key = raw.toLowerCase();
|
|
24633
24654
|
if (key === "all" || key === "*") return filter;
|
|
@@ -24693,6 +24714,11 @@ async function listLinearIssuesFiltered(opts) {
|
|
|
24693
24714
|
async function listLinearCommentsSince(opts) {
|
|
24694
24715
|
const first = Math.max(1, Math.min(250, opts.limit ?? 200));
|
|
24695
24716
|
const query = opts.query?.trim() ?? "";
|
|
24717
|
+
const restrictToIssues = opts.issueIdentifiers != null;
|
|
24718
|
+
const allowed = new Set(
|
|
24719
|
+
(opts.issueIdentifiers ?? []).map((id) => id.trim()).filter(Boolean)
|
|
24720
|
+
);
|
|
24721
|
+
if (restrictToIssues && allowed.size === 0) return [];
|
|
24696
24722
|
const assignee = assigneeFilterKey(opts.assignee) || (query ? "all" : "me");
|
|
24697
24723
|
const json = await linearGraphql(
|
|
24698
24724
|
COMMENTS_SINCE_QUERY,
|
|
@@ -24700,7 +24726,7 @@ async function listLinearCommentsSince(opts) {
|
|
|
24700
24726
|
first,
|
|
24701
24727
|
filter: {
|
|
24702
24728
|
createdAt: { gte: opts.since },
|
|
24703
|
-
issue: buildLinearIssueFilter({ assignee })
|
|
24729
|
+
issue: buildLinearIssueFilter({ assignee, query: query || void 0 })
|
|
24704
24730
|
}
|
|
24705
24731
|
},
|
|
24706
24732
|
opts
|
|
@@ -24708,6 +24734,7 @@ async function listLinearCommentsSince(opts) {
|
|
|
24708
24734
|
const out = [];
|
|
24709
24735
|
for (const node of json.comments?.nodes ?? []) {
|
|
24710
24736
|
const identifier = String(node.issue?.identifier ?? "").trim();
|
|
24737
|
+
if (restrictToIssues && !allowed.has(identifier)) continue;
|
|
24711
24738
|
const body = previewIssueCommentBody(String(node.body ?? ""));
|
|
24712
24739
|
if (!identifier && !body) continue;
|
|
24713
24740
|
out.push({
|
|
@@ -24939,10 +24966,32 @@ function issueNumberFromApiUrl(url) {
|
|
|
24939
24966
|
const number = Number(match[1]);
|
|
24940
24967
|
return Number.isFinite(number) && number > 0 ? number : null;
|
|
24941
24968
|
}
|
|
24969
|
+
var GITHUB_ISSUE_COMMENTS_PAGE_SIZE = 100;
|
|
24970
|
+
var GITHUB_ISSUE_COMMENTS_MAX_PAGES = 5;
|
|
24971
|
+
function parseGitHubIssueCommentNode(item, allowed) {
|
|
24972
|
+
const rec = item && typeof item === "object" ? item : null;
|
|
24973
|
+
if (!rec) return null;
|
|
24974
|
+
const issueUrl = typeof rec.issue_url === "string" ? rec.issue_url : "";
|
|
24975
|
+
const number = issueNumberFromApiUrl(issueUrl);
|
|
24976
|
+
if (number == null) return null;
|
|
24977
|
+
if (allowed && !allowed.has(number)) return null;
|
|
24978
|
+
const body = previewIssueCommentBody(typeof rec.body === "string" ? rec.body : "");
|
|
24979
|
+
const user = rec.user && typeof rec.user === "object" ? String(rec.user.login ?? "").trim() : "";
|
|
24980
|
+
const createdAt = typeof rec.created_at === "string" ? rec.created_at : typeof rec.createdAt === "string" ? rec.createdAt : void 0;
|
|
24981
|
+
const url = typeof rec.html_url === "string" ? rec.html_url : void 0;
|
|
24982
|
+
return {
|
|
24983
|
+
identifier: `#${number}`,
|
|
24984
|
+
...user ? { author: user } : {},
|
|
24985
|
+
...createdAt ? { createdAt } : {},
|
|
24986
|
+
body,
|
|
24987
|
+
...url ? { url } : {}
|
|
24988
|
+
};
|
|
24989
|
+
}
|
|
24942
24990
|
async function listGitHubIssueCommentsSince(opts) {
|
|
24943
24991
|
const limit = Math.max(1, Math.min(250, opts.limit ?? 200));
|
|
24944
24992
|
const { cwd, slug } = await resolveGitHubIssueRepo(opts.repoPath);
|
|
24945
24993
|
if (!slug) return [];
|
|
24994
|
+
const restrictToIssues = opts.issueIdentifiers != null;
|
|
24946
24995
|
const allowed = new Set(
|
|
24947
24996
|
(opts.issueIdentifiers ?? []).map((id) => {
|
|
24948
24997
|
try {
|
|
@@ -24952,36 +25001,27 @@ async function listGitHubIssueCommentsSince(opts) {
|
|
|
24952
25001
|
}
|
|
24953
25002
|
}).filter((n) => n != null)
|
|
24954
25003
|
);
|
|
24955
|
-
|
|
24956
|
-
const
|
|
24957
|
-
if (result.exitCode !== 0 || !result.stdout.trim()) return [];
|
|
24958
|
-
let parsed;
|
|
24959
|
-
try {
|
|
24960
|
-
parsed = JSON.parse(result.stdout);
|
|
24961
|
-
} catch {
|
|
24962
|
-
return [];
|
|
24963
|
-
}
|
|
24964
|
-
if (!Array.isArray(parsed)) return [];
|
|
25004
|
+
if (restrictToIssues && allowed.size === 0) return [];
|
|
25005
|
+
const pageSize = Math.min(GITHUB_ISSUE_COMMENTS_PAGE_SIZE, Math.max(1, limit));
|
|
24965
25006
|
const out = [];
|
|
24966
|
-
for (
|
|
24967
|
-
const
|
|
24968
|
-
|
|
24969
|
-
|
|
24970
|
-
|
|
24971
|
-
|
|
24972
|
-
|
|
24973
|
-
|
|
24974
|
-
|
|
24975
|
-
|
|
24976
|
-
|
|
24977
|
-
|
|
24978
|
-
|
|
24979
|
-
|
|
24980
|
-
|
|
24981
|
-
|
|
24982
|
-
|
|
24983
|
-
|
|
24984
|
-
if (out.length >= limit) break;
|
|
25007
|
+
for (let page = 1; page <= GITHUB_ISSUE_COMMENTS_MAX_PAGES; page++) {
|
|
25008
|
+
const path2 = `repos/${slug}/issues/comments?since=${encodeURIComponent(opts.since)}&sort=created&direction=desc&per_page=${pageSize}&page=${page}`;
|
|
25009
|
+
const result = await gh(["api", path2], cwd, { reject: false });
|
|
25010
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) break;
|
|
25011
|
+
let parsed;
|
|
25012
|
+
try {
|
|
25013
|
+
parsed = JSON.parse(result.stdout);
|
|
25014
|
+
} catch {
|
|
25015
|
+
break;
|
|
25016
|
+
}
|
|
25017
|
+
if (!Array.isArray(parsed) || parsed.length === 0) break;
|
|
25018
|
+
for (const item of parsed) {
|
|
25019
|
+
const comment = parseGitHubIssueCommentNode(item, restrictToIssues ? allowed : null);
|
|
25020
|
+
if (!comment) continue;
|
|
25021
|
+
out.push(comment);
|
|
25022
|
+
if (out.length >= limit) return out;
|
|
25023
|
+
}
|
|
25024
|
+
if (parsed.length < pageSize) break;
|
|
24985
25025
|
}
|
|
24986
25026
|
return out;
|
|
24987
25027
|
}
|
|
@@ -25195,20 +25235,19 @@ async function listIssues(repoPath, opts) {
|
|
|
25195
25235
|
const assignee = opts?.assignee?.trim() || void 0;
|
|
25196
25236
|
const since = opts?.updatedSince ? parseIssueSince(opts.updatedSince) : void 0;
|
|
25197
25237
|
if (source === "linear") {
|
|
25198
|
-
const
|
|
25199
|
-
|
|
25200
|
-
|
|
25201
|
-
|
|
25202
|
-
|
|
25203
|
-
|
|
25204
|
-
|
|
25205
|
-
since
|
|
25206
|
-
|
|
25207
|
-
|
|
25208
|
-
|
|
25209
|
-
|
|
25210
|
-
|
|
25211
|
-
]);
|
|
25238
|
+
const listed = await listLinearIssuesFiltered({
|
|
25239
|
+
assignee,
|
|
25240
|
+
query,
|
|
25241
|
+
limit: opts?.limit,
|
|
25242
|
+
updatedSince: since
|
|
25243
|
+
});
|
|
25244
|
+
const comments2 = since ? await listLinearCommentsSince({
|
|
25245
|
+
since,
|
|
25246
|
+
assignee,
|
|
25247
|
+
query,
|
|
25248
|
+
issueIdentifiers: query ? listed.issues.map((issue) => issue.identifier) : void 0,
|
|
25249
|
+
limit: opts?.limit
|
|
25250
|
+
}) : void 0;
|
|
25212
25251
|
return {
|
|
25213
25252
|
source,
|
|
25214
25253
|
preferredSource,
|
|
@@ -26955,20 +26994,19 @@ function registerLinearTools(server) {
|
|
|
26955
26994
|
try {
|
|
26956
26995
|
const page = clampMcpIssueLimit(limit);
|
|
26957
26996
|
const since = updatedSince ? parseIssueSince(updatedSince) : void 0;
|
|
26958
|
-
const
|
|
26959
|
-
|
|
26960
|
-
|
|
26961
|
-
|
|
26962
|
-
|
|
26963
|
-
|
|
26964
|
-
|
|
26965
|
-
since
|
|
26966
|
-
|
|
26967
|
-
|
|
26968
|
-
|
|
26969
|
-
|
|
26970
|
-
|
|
26971
|
-
]);
|
|
26997
|
+
const listed = await listLinearIssuesFiltered({
|
|
26998
|
+
query,
|
|
26999
|
+
assignee,
|
|
27000
|
+
limit: page + 1,
|
|
27001
|
+
updatedSince: since
|
|
27002
|
+
});
|
|
27003
|
+
const comments = since ? await listLinearCommentsSince({
|
|
27004
|
+
since,
|
|
27005
|
+
assignee,
|
|
27006
|
+
query,
|
|
27007
|
+
issueIdentifiers: query ? listed.issues.map((issue) => issue.identifier) : void 0,
|
|
27008
|
+
limit: page + 1
|
|
27009
|
+
}) : void 0;
|
|
26972
27010
|
return mcpJson(
|
|
26973
27011
|
formatListedIssuesForMcp(
|
|
26974
27012
|
{
|
|
@@ -28096,7 +28134,7 @@ async function startMcpServer() {
|
|
|
28096
28134
|
);
|
|
28097
28135
|
server.tool(
|
|
28098
28136
|
"wait_for_job",
|
|
28099
|
-
"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,
|
|
28137
|
+
"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. Sideboard opens a type=log pane from this result (artifact_id = job id, content = delta). stillRunning is the source of truth \u2014 if true, 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.",
|
|
28100
28138
|
{
|
|
28101
28139
|
id: import_zod9.z.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
|
|
28102
28140
|
timeoutMs: import_zod9.z.number().optional()
|
|
@@ -28110,7 +28148,7 @@ async function startMcpServer() {
|
|
|
28110
28148
|
);
|
|
28111
28149
|
server.tool(
|
|
28112
28150
|
"stop_job",
|
|
28113
|
-
"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.
|
|
28151
|
+
"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. The type=log pane updates to status=failed with the last delta. Then decide the next step.",
|
|
28114
28152
|
{
|
|
28115
28153
|
id: import_zod9.z.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
|
|
28116
28154
|
reason: import_zod9.z.string().optional().describe("Why you are stopping (hanging, no progress, wrong output, already have the error)")
|