@sideboard-ai/core 0.1.241 → 0.1.243
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/{abletime-VSV6AUBV.js → abletime-CWIJ7INC.js} +4 -4
- package/dist/{abletime-UX4VUENQ.js → abletime-OI6FNSXX.js} +4 -4
- package/dist/{abletime-oauth-DKBUZV2C.js → abletime-oauth-CHVTUZBR.js} +3 -3
- package/dist/{abletime-oauth-QMDV45JC.js → abletime-oauth-PUIPZTA4.js} +3 -3
- package/dist/{abletime-rest-NL56UVK5.js → abletime-rest-32NIVXAY.js} +3 -3
- package/dist/{abletime-rest-VPBC2XS5.js → abletime-rest-APJUKFKL.js} +3 -3
- package/dist/{agents-CDQCQVWU.js → agents-CPME6O2H.js} +9 -5
- package/dist/{agents-PBRIHUYM.js → agents-VKO5UZO6.js} +9 -5
- package/dist/{app-settings-AGDLF75Z.js → app-settings-652J57CX.js} +1 -1
- package/dist/{app-settings-I27UBVI4.js → app-settings-VOPXJMGV.js} +1 -1
- package/dist/{chunk-BILEJG3V.js → chunk-3P5OCI2X.js} +3 -3
- package/dist/{chunk-3PGFAVFL.js → chunk-43F44EKX.js} +2 -2
- package/dist/{chunk-3AJUH66R.js → chunk-6I5YQTNK.js} +2 -1
- package/dist/{chunk-NJ5OXOTN.js → chunk-7HOKP4GR.js} +2 -1
- package/dist/{chunk-TB6UMHIF.js → chunk-7UCZ4DRF.js} +2 -2
- package/dist/{chunk-DXY4K5MP.js → chunk-BL2UGKWU.js} +3 -3
- package/dist/{chunk-CWHMLCLN.js → chunk-C64YQAA6.js} +3 -3
- package/dist/{chunk-O3UAQAYA.js → chunk-CCD2QIFK.js} +2 -2
- package/dist/{chunk-63UHRQ64.js → chunk-DARSRQUE.js} +1523 -1450
- package/dist/{chunk-OKHOYI53.js → chunk-ECZTXNYR.js} +3 -3
- package/dist/{chunk-CRAXJAYJ.js → chunk-G45QP2ZD.js} +3 -3
- package/dist/{chunk-3Q4PVZ3U.js → chunk-HR4RNC73.js} +32 -5
- package/dist/{chunk-YXOOSFAF.js → chunk-IMO7AZI4.js} +2 -2
- package/dist/{chunk-QXYWLO6W.js → chunk-ISZERVDT.js} +28 -5
- package/dist/{chunk-EE3REM4B.js → chunk-NEOAHPMK.js} +1582 -1505
- package/dist/{chunk-DJU54RYW.js → chunk-PGPSIEMW.js} +13 -0
- package/dist/{chunk-P3DCZRER.js → chunk-PPJ5JTWA.js} +1 -0
- package/dist/{chunk-5ELC3KK3.js → chunk-QNJXJIWJ.js} +5 -5
- package/dist/{chunk-JFOPOMK7.js → chunk-WDDUIGFK.js} +13 -0
- package/dist/{chunk-IVXPXYH2.js → chunk-WURF67ID.js} +5 -5
- package/dist/{chunk-QLWMHQPH.js → chunk-YLGBQVRK.js} +1 -0
- package/dist/{coordinator-prompt-5UINTEA6.js → coordinator-prompt-AJ3E6LBX.js} +4 -4
- package/dist/{coordinator-prompt-ATK73HYU.js → coordinator-prompt-PYIRQGU7.js} +4 -4
- package/dist/{global-workspace-TD5SARNW.js → global-workspace-GIQ6F6IJ.js} +4 -4
- package/dist/{global-workspace-ZJBBICFJ.js → global-workspace-GQM653DP.js} +4 -4
- package/dist/index.cjs +609 -463
- package/dist/index.d.cts +56 -2
- package/dist/index.d.ts +56 -2
- package/dist/index.js +74 -38
- package/dist/mcp/run-stdio.cjs +1029 -908
- package/dist/mcp/run-stdio.js +50 -36
- package/dist/{orchestrator-ZUOEBJFN.js → orchestrator-3IOISDFS.js} +7 -7
- package/dist/{orchestrator-7AJEFWGQ.js → orchestrator-YDPDMXUY.js} +7 -7
- package/dist/{plan-file-UUKCZEYP.js → plan-file-7MMIPV7Y.js} +9 -1
- package/dist/{plan-file-2EPHYRX7.js → plan-file-CKXUFTQ2.js} +9 -1
- package/dist/{thread-store-YOPKY74K.js → thread-store-JQB7KZJR.js} +1 -1
- package/dist/{thread-store-AYPBEZT5.js → thread-store-SCV2IZYS.js} +1 -1
- package/dist/{workspaces-4SFSCYKT.js → workspaces-OIBSD5B6.js} +4 -4
- package/dist/{workspaces-JX5R4V24.js → workspaces-WYC5TLC3.js} +4 -4
- package/dist/{worktree-Y5FES5D6.js → worktree-5VRZVGP3.js} +3 -3
- package/dist/{worktree-AGVPKDBH.js → worktree-DGDM7LGV.js} +3 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1240,6 +1240,7 @@ function archivedUpdatedMs(thread) {
|
|
|
1240
1240
|
}
|
|
1241
1241
|
function isHistoryRetentionStub(thread) {
|
|
1242
1242
|
if ((thread.attachments?.length ?? 0) > 0) return false;
|
|
1243
|
+
if ((thread.compactedMessages?.length ?? 0) > 0) return false;
|
|
1243
1244
|
const messages = thread.messages ?? [];
|
|
1244
1245
|
if (messages.length === 0) return true;
|
|
1245
1246
|
if (messages.length > 1) return false;
|
|
@@ -3323,7 +3324,8 @@ function normalizeThread(raw) {
|
|
|
3323
3324
|
runScriptRequest: normalizeRunScriptRequest(raw.runScriptRequest),
|
|
3324
3325
|
quotaResumeAt: raw.quotaResumeAt ?? null,
|
|
3325
3326
|
quotaContinuedFromId: raw.quotaContinuedFromId ?? null,
|
|
3326
|
-
archivedAt: typeof raw.archivedAt === "string" && raw.archivedAt.trim() ? raw.archivedAt.trim() : void 0
|
|
3327
|
+
archivedAt: typeof raw.archivedAt === "string" && raw.archivedAt.trim() ? raw.archivedAt.trim() : void 0,
|
|
3328
|
+
compactedMessages: Array.isArray(raw.compactedMessages) ? raw.compactedMessages : void 0
|
|
3327
3329
|
};
|
|
3328
3330
|
}
|
|
3329
3331
|
function createEmptyThread(partial) {
|
|
@@ -4415,6 +4417,16 @@ function skillToolFromTextPart(part, id) {
|
|
|
4415
4417
|
updatedAt: Date.now()
|
|
4416
4418
|
};
|
|
4417
4419
|
}
|
|
4420
|
+
function compactSummaryPart(text7, id = "compact-summary") {
|
|
4421
|
+
return compactToolPart(text7, id, { status: "done" });
|
|
4422
|
+
}
|
|
4423
|
+
function compactPartsForSummaryMessage(message) {
|
|
4424
|
+
const existing = (message.parts ?? []).filter(
|
|
4425
|
+
(p) => p.type === "tool" && isCompactToolName(p.name)
|
|
4426
|
+
);
|
|
4427
|
+
if (existing.length) return existing;
|
|
4428
|
+
return [compactSummaryPart(message.text ?? "")];
|
|
4429
|
+
}
|
|
4418
4430
|
function compactToolPart(text7, id, opts) {
|
|
4419
4431
|
const input = { trigger: opts?.trigger ?? "auto" };
|
|
4420
4432
|
return withPartTimes(
|
|
@@ -12360,6 +12372,10 @@ var init_node_launch = __esm({
|
|
|
12360
12372
|
// src/agents/injected-mcp.ts
|
|
12361
12373
|
function sideboardWorktreeAllowedTools(opts) {
|
|
12362
12374
|
const out = [...SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS];
|
|
12375
|
+
if (!opts?.planMode) {
|
|
12376
|
+
const idx = out.indexOf("mcp__sideboard__present_plan");
|
|
12377
|
+
if (idx >= 0) out.splice(idx, 1);
|
|
12378
|
+
}
|
|
12363
12379
|
if (opts?.github !== false) out.push(...SIDEBOARD_GITHUB_MCP_ALLOWED_TOOLS);
|
|
12364
12380
|
if (opts?.linear) out.push(...SIDEBOARD_LINEAR_MCP_ALLOWED_TOOLS);
|
|
12365
12381
|
if (opts?.abletime) out.push(...SIDEBOARD_ABLETIME_MCP_ALLOWED_TOOLS);
|
|
@@ -12906,6 +12922,9 @@ var init_orch_mcp_isolation = __esm({
|
|
|
12906
12922
|
});
|
|
12907
12923
|
|
|
12908
12924
|
// src/agents/types.ts
|
|
12925
|
+
function planModeTurnInstruction(planMode) {
|
|
12926
|
+
return planMode ? PLAN_MODE_INSTRUCTION : PLAN_MODE_OFF_INSTRUCTION;
|
|
12927
|
+
}
|
|
12909
12928
|
function permissionMode(thread) {
|
|
12910
12929
|
if (thread.sourceType === "orchestration") {
|
|
12911
12930
|
return {
|
|
@@ -12944,11 +12963,12 @@ function permissionMode(thread) {
|
|
|
12944
12963
|
codexSandbox: "workspace-write"
|
|
12945
12964
|
};
|
|
12946
12965
|
}
|
|
12947
|
-
var PLAN_MODE_INSTRUCTION;
|
|
12966
|
+
var PLAN_MODE_INSTRUCTION, PLAN_MODE_OFF_INSTRUCTION;
|
|
12948
12967
|
var init_types = __esm({
|
|
12949
12968
|
"src/agents/types.ts"() {
|
|
12950
12969
|
"use strict";
|
|
12951
12970
|
PLAN_MODE_INSTRUCTION = "Plan mode is active and must remain active until the user turns Plan mode off in the UI (or Approves / Hands off the plan). Analyze the codebase, search and read files as needed, and produce or refine a clear implementation plan. Do not modify, create, or delete any project files except via Sideboard MCP present_plan (writes .context/attachments/plan.md). When you need a clarifying decision (approach forks, auth choice, scope) \u2014 not greetings, check-ins, or an invented task menu: (1) first write a short chat message that explains the decision and what each option means (tradeoffs, when to pick it) \u2014 do not leave the user staring at bare labels; (2) then call Sideboard MCP ask_user with the same options, including a description on every option. If one option is the obvious default, proceed without asking. Sideboard shows questions in the composer and mirrors them in chat. After ask_user, wait for the user's next message with their answers before finalizing the plan. When the plan is ready for approval: (1) call present_plan with the full markdown plan (title + content) so Sideboard saves .context/attachments/plan.md and shows it in chat for Approve / Hand off / Copy; (2) Claude should also call ExitPlanMode after present_plan. Do not skip present_plan \u2014 the plan must be a markdown file, not only chat prose.";
|
|
12971
|
+
PLAN_MODE_OFF_INSTRUCTION = "Plan mode is off. Do not call present_plan or ExitPlanMode \u2014 those only work when the user turns Plan on in the composer. If you need to propose an approach, write it in chat.";
|
|
12952
12972
|
}
|
|
12953
12973
|
});
|
|
12954
12974
|
|
|
@@ -13461,7 +13481,8 @@ var init_claude = __esm({
|
|
|
13461
13481
|
...sideboardWorktreeAllowedTools({
|
|
13462
13482
|
github: true,
|
|
13463
13483
|
linear: isLinearConnected(),
|
|
13464
|
-
abletime: isAbleTimeConnected()
|
|
13484
|
+
abletime: isAbleTimeConnected(),
|
|
13485
|
+
planMode: thread.planMode
|
|
13465
13486
|
}),
|
|
13466
13487
|
...brightsyMcpAllowedTools(injectedBrightsyNames)
|
|
13467
13488
|
];
|
|
@@ -15879,6 +15900,7 @@ __export(agents_exports, {
|
|
|
15879
15900
|
CURSOR_SDK_INSTALL_HINT: () => CURSOR_SDK_INSTALL_HINT,
|
|
15880
15901
|
ORCHESTRATOR_AGENT_KINDS: () => ORCHESTRATOR_AGENT_KINDS,
|
|
15881
15902
|
PLAN_MODE_INSTRUCTION: () => PLAN_MODE_INSTRUCTION,
|
|
15903
|
+
PLAN_MODE_OFF_INSTRUCTION: () => PLAN_MODE_OFF_INSTRUCTION,
|
|
15882
15904
|
allAdapters: () => allAdapters,
|
|
15883
15905
|
assertOrchestratorCapableAgent: () => assertOrchestratorCapableAgent,
|
|
15884
15906
|
brightsyAdapter: () => brightsyAdapter,
|
|
@@ -15929,6 +15951,7 @@ __export(agents_exports, {
|
|
|
15929
15951
|
parseCursorRunnerLine: () => parseCursorRunnerLine,
|
|
15930
15952
|
parseSessionQuotaResetAt: () => parseSessionQuotaResetAt,
|
|
15931
15953
|
permissionMode: () => permissionMode,
|
|
15954
|
+
planModeTurnInstruction: () => planModeTurnInstruction,
|
|
15932
15955
|
posixShellSingleQuote: () => posixShellSingleQuote,
|
|
15933
15956
|
preferredCursorCostCents: () => preferredCursorCostCents,
|
|
15934
15957
|
prepareTerminalCommand: () => prepareTerminalCommand,
|
|
@@ -16911,12 +16934,38 @@ ${match.text}`, "");
|
|
|
16911
16934
|
blocks.push("Continue from this context. Do not repeat the summary unless asked.");
|
|
16912
16935
|
return blocks.join("\n");
|
|
16913
16936
|
}
|
|
16937
|
+
function compactArchiveProse(message) {
|
|
16938
|
+
if (message.origin === "continue") return null;
|
|
16939
|
+
if (message.role === "summary") return null;
|
|
16940
|
+
let text7 = (message.text ?? "").trim();
|
|
16941
|
+
if (!text7 && message.role === "agent") {
|
|
16942
|
+
text7 = lastAssistantMessageText(message.parts).trim();
|
|
16943
|
+
}
|
|
16944
|
+
if (!text7 || isInternalAgentStatusText(text7)) return null;
|
|
16945
|
+
return { role: message.role, text: text7, ts: message.ts };
|
|
16946
|
+
}
|
|
16947
|
+
function messagesForCompactArchive(older) {
|
|
16948
|
+
const out = [];
|
|
16949
|
+
for (const message of older) {
|
|
16950
|
+
const prose = compactArchiveProse(message);
|
|
16951
|
+
if (prose) out.push(prose);
|
|
16952
|
+
}
|
|
16953
|
+
return out;
|
|
16954
|
+
}
|
|
16955
|
+
function appendCompactedMessages(existing, older) {
|
|
16956
|
+
const next = messagesForCompactArchive(older);
|
|
16957
|
+
if (!existing?.length) return next;
|
|
16958
|
+
if (!next.length) return existing;
|
|
16959
|
+
return [...existing, ...next];
|
|
16960
|
+
}
|
|
16914
16961
|
function applyCompaction(messages, summaryText, thresholds = {}) {
|
|
16915
16962
|
const { older, recent } = splitForCompaction(messages, thresholds);
|
|
16916
16963
|
if (older.length === 0) return messages;
|
|
16964
|
+
const text7 = summaryText.trim();
|
|
16917
16965
|
const summary = {
|
|
16918
16966
|
role: "summary",
|
|
16919
|
-
text:
|
|
16967
|
+
text: text7,
|
|
16968
|
+
parts: [compactSummaryPart(text7)],
|
|
16920
16969
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
16921
16970
|
};
|
|
16922
16971
|
return [summary, ...recent];
|
|
@@ -16944,6 +16993,7 @@ async function maybeCompactContext(thread, thresholds = {}, summarize = summariz
|
|
|
16944
16993
|
cwd: thread.worktreePath
|
|
16945
16994
|
});
|
|
16946
16995
|
let messages = applyCompaction(thread.messages, summary, thresholds);
|
|
16996
|
+
const compactedMessages = appendCompactedMessages(thread.compactedMessages, older);
|
|
16947
16997
|
const resetSession = shouldResetSessionForOccupancy({ messages: thread.messages });
|
|
16948
16998
|
if (resetSession) {
|
|
16949
16999
|
messages = applyForwardOccupancy(messages);
|
|
@@ -16951,6 +17001,7 @@ async function maybeCompactContext(thread, thresholds = {}, summarize = summariz
|
|
|
16951
17001
|
const next = {
|
|
16952
17002
|
...thread,
|
|
16953
17003
|
messages,
|
|
17004
|
+
...compactedMessages.length ? { compactedMessages } : { compactedMessages: void 0 },
|
|
16954
17005
|
sessionId: resetSession ? null : thread.sessionId,
|
|
16955
17006
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16956
17007
|
};
|
|
@@ -16966,6 +17017,7 @@ var CONTEXT_COMPACT_CHARS, CONTEXT_KEEP_RECENT_CHARS, CONTEXT_KEEP_RECENT_MESSAG
|
|
|
16966
17017
|
var init_context_compact = __esm({
|
|
16967
17018
|
"src/composer/context-compact.ts"() {
|
|
16968
17019
|
"use strict";
|
|
17020
|
+
init_message_parts();
|
|
16969
17021
|
init_error_detail();
|
|
16970
17022
|
init_usage();
|
|
16971
17023
|
init_context_estimate();
|
|
@@ -17552,15 +17604,403 @@ var init_worktree_run_ports = __esm({
|
|
|
17552
17604
|
}
|
|
17553
17605
|
});
|
|
17554
17606
|
|
|
17607
|
+
// src/mcp/wait-for-turn.ts
|
|
17608
|
+
function mcpWaitForTurnTimeoutMs(requested) {
|
|
17609
|
+
const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
|
|
17610
|
+
if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
|
|
17611
|
+
return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
|
|
17612
|
+
}
|
|
17613
|
+
function mcpWaitTaskHint(taskState, status) {
|
|
17614
|
+
switch (taskState) {
|
|
17615
|
+
case "submitted":
|
|
17616
|
+
return MCP_WAIT_QUEUED_HINT;
|
|
17617
|
+
case "working":
|
|
17618
|
+
return MCP_WAIT_STILL_RUNNING_HINT;
|
|
17619
|
+
case "input-required":
|
|
17620
|
+
return MCP_WAIT_INPUT_REQUIRED_HINT;
|
|
17621
|
+
case "canceled":
|
|
17622
|
+
return MCP_WAIT_STOPPED_HINT;
|
|
17623
|
+
case "failed":
|
|
17624
|
+
return status === "broken" ? MCP_WAIT_BROKEN_HINT : MCP_WAIT_ERROR_HINT;
|
|
17625
|
+
default:
|
|
17626
|
+
return void 0;
|
|
17627
|
+
}
|
|
17628
|
+
}
|
|
17629
|
+
var MCP_WAIT_FOR_TURN_MAX_MS, MCP_WAIT_STILL_RUNNING_HINT, MCP_WAIT_QUEUED_HINT, MCP_WAIT_STOPPED_HINT, MCP_WAIT_BROKEN_HINT, MCP_WAIT_ERROR_HINT, MCP_WAIT_INPUT_REQUIRED_HINT;
|
|
17630
|
+
var init_wait_for_turn = __esm({
|
|
17631
|
+
"src/mcp/wait-for-turn.ts"() {
|
|
17632
|
+
"use strict";
|
|
17633
|
+
MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
17634
|
+
MCP_WAIT_STILL_RUNNING_HINT = "Child is still working. Call wait_for_turn again. Do not send_to_thread a check-in (that steers / interrupts) or assume a hang while progress is updating.";
|
|
17635
|
+
MCP_WAIT_QUEUED_HINT = "Child is queued waiting for a concurrency slot \u2014 it has not started yet. Call wait_for_turn again. Do not send_to_thread a check-in (that steers / interrupts), force_stop, or assume it failed to start.";
|
|
17636
|
+
MCP_WAIT_STOPPED_HINT = "Child was stopped before the turn finished. Do not treat this as success. send_to_thread to resume, or tell the user.";
|
|
17637
|
+
MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
|
|
17638
|
+
MCP_WAIT_ERROR_HINT = "Child turn failed. lastError/text is the failure \u2014 switch agent, tell the user, or retry. Do not treat empty text as success.";
|
|
17639
|
+
MCP_WAIT_INPUT_REQUIRED_HINT = "Child asked the user a question (ask_user). taskState is input-required. Wait for the user to answer in that chat \u2014 do not send_to_thread a check-in.";
|
|
17640
|
+
}
|
|
17641
|
+
});
|
|
17642
|
+
|
|
17643
|
+
// src/mcp/wait-for-job.ts
|
|
17644
|
+
function mcpWaitForJobTimeoutMs(requested) {
|
|
17645
|
+
return mcpWaitForTurnTimeoutMs(requested);
|
|
17646
|
+
}
|
|
17647
|
+
function sanitizeDetachedJobId(id) {
|
|
17648
|
+
const s = id.trim();
|
|
17649
|
+
if (!JOB_ID_RE.test(s)) {
|
|
17650
|
+
throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
|
|
17651
|
+
}
|
|
17652
|
+
return s;
|
|
17653
|
+
}
|
|
17654
|
+
function jobAlive(pid) {
|
|
17655
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
17656
|
+
try {
|
|
17657
|
+
process.kill(pid, 0);
|
|
17658
|
+
return true;
|
|
17659
|
+
} catch {
|
|
17660
|
+
return false;
|
|
17661
|
+
}
|
|
17662
|
+
}
|
|
17663
|
+
function processGroupAlive(pgid) {
|
|
17664
|
+
if (process.platform === "win32") return false;
|
|
17665
|
+
if (!Number.isInteger(pgid) || pgid <= 1) return false;
|
|
17666
|
+
try {
|
|
17667
|
+
process.kill(-pgid, 0);
|
|
17668
|
+
return true;
|
|
17669
|
+
} catch (err) {
|
|
17670
|
+
return err && typeof err === "object" && "code" in err && err.code === "EPERM";
|
|
17671
|
+
}
|
|
17672
|
+
}
|
|
17673
|
+
function jobTreeAlive(pid) {
|
|
17674
|
+
if (pid == null) return false;
|
|
17675
|
+
return jobAlive(pid) || processGroupAlive(pid);
|
|
17676
|
+
}
|
|
17677
|
+
function readIntFile(file) {
|
|
17678
|
+
if (!(0, import_node_fs45.existsSync)(file)) return null;
|
|
17679
|
+
const n = Number.parseInt((0, import_node_fs45.readFileSync)(file, "utf8").trim(), 10);
|
|
17680
|
+
return Number.isInteger(n) ? n : null;
|
|
17681
|
+
}
|
|
17682
|
+
function jobDir(root, id, legacy = false) {
|
|
17683
|
+
return (0, import_node_path45.join)(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
|
|
17684
|
+
}
|
|
17685
|
+
function resolveJobDir(root, id) {
|
|
17686
|
+
const modern = jobDir(root, id, false);
|
|
17687
|
+
if ((0, import_node_fs45.existsSync)(modern)) return modern;
|
|
17688
|
+
const legacy = jobDir(root, id, true);
|
|
17689
|
+
if ((0, import_node_fs45.existsSync)(legacy)) return legacy;
|
|
17690
|
+
return modern;
|
|
17691
|
+
}
|
|
17692
|
+
function listJobIdsIn(root, rel) {
|
|
17693
|
+
const dir = (0, import_node_path45.join)(root, rel);
|
|
17694
|
+
if (!(0, import_node_fs45.existsSync)(dir)) return [];
|
|
17695
|
+
return (0, import_node_fs45.readdirSync)(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
|
|
17696
|
+
}
|
|
17697
|
+
function listRunningDetachedJobs(worktreePath) {
|
|
17698
|
+
const root = worktreePath.trim();
|
|
17699
|
+
if (!root) return [];
|
|
17700
|
+
const ids = /* @__PURE__ */ new Set([
|
|
17701
|
+
...listJobIdsIn(root, DETACHED_JOBS_DIR),
|
|
17702
|
+
...listJobIdsIn(root, LEGACY_DETACHED_JOBS_DIR)
|
|
17703
|
+
]);
|
|
17704
|
+
const running = [];
|
|
17705
|
+
for (const id of ids) {
|
|
17706
|
+
const dir = resolveJobDir(root, id);
|
|
17707
|
+
const pid = readIntFile((0, import_node_path45.join)(dir, "pid"));
|
|
17708
|
+
if (jobTreeAlive(pid)) running.push(id);
|
|
17709
|
+
}
|
|
17710
|
+
return running.sort();
|
|
17711
|
+
}
|
|
17712
|
+
function looksLikeDeferredDonePromise(text7) {
|
|
17713
|
+
const t = (text7 ?? "").trim();
|
|
17714
|
+
if (!t || t.length > DEFERRED_DONE_MAX_CHARS) return false;
|
|
17715
|
+
const unquoted = t.replace(/`[^`]*`/g, " ").replace(/["“”][^"“”]*["“”]/g, " ").replace(/\s+/g, " ").trim();
|
|
17716
|
+
if (!unquoted) return false;
|
|
17717
|
+
return /\b(i['’]?ll|i will)\s+let you know\b/i.test(unquoted) || /\blet you know when\b/i.test(unquoted) || /\b(check back|ping me)\s+when\b/i.test(unquoted) || /\bi(?:['’]ll| will)\s+(report|update you)\s+when\b/i.test(unquoted);
|
|
17718
|
+
}
|
|
17719
|
+
function isJobContinuePrompt(text7) {
|
|
17720
|
+
if (typeof text7 !== "string") return false;
|
|
17721
|
+
const t = text7.trim();
|
|
17722
|
+
return t.startsWith("Detached job still running:") || t.startsWith("You ended the turn after promising to report later");
|
|
17723
|
+
}
|
|
17724
|
+
function collapseGhRunWatchLog(text7) {
|
|
17725
|
+
if (!text7.includes(GH_WATCH_REFRESH)) return text7;
|
|
17726
|
+
const frames = text7.split(/(?=Refreshing run status every 3 seconds\.)/);
|
|
17727
|
+
const watchFrames = frames.filter((f) => f.includes(GH_WATCH_REFRESH));
|
|
17728
|
+
if (watchFrames.length <= 1) return text7;
|
|
17729
|
+
const prefix = frames[0]?.includes(GH_WATCH_REFRESH) ? "" : frames[0] ?? "";
|
|
17730
|
+
const last = watchFrames[watchFrames.length - 1] ?? "";
|
|
17731
|
+
const omitted = watchFrames.length - 1;
|
|
17732
|
+
const note = `(${omitted} identical gh run watch refresh${omitted === 1 ? "" : "es"} omitted)
|
|
17733
|
+
`;
|
|
17734
|
+
return [prefix.trimEnd(), note, last.trimStart()].filter(Boolean).join("\n");
|
|
17735
|
+
}
|
|
17736
|
+
function capJobLogDelta(text7) {
|
|
17737
|
+
const collapsed = collapseGhRunWatchLog(text7);
|
|
17738
|
+
const lines = collapsed.split("\n");
|
|
17739
|
+
let out = lines.length > MAX_JOB_DELTA_LINES ? `\u2026(${lines.length - MAX_JOB_DELTA_LINES} earlier lines omitted)
|
|
17740
|
+
${lines.slice(-MAX_JOB_DELTA_LINES).join("\n")}` : collapsed;
|
|
17741
|
+
if (out.length > MAX_JOB_DELTA_CHARS) {
|
|
17742
|
+
out = `\u2026(truncated ${out.length} chars)
|
|
17743
|
+
${out.slice(-MAX_JOB_DELTA_CHARS)}`;
|
|
17744
|
+
}
|
|
17745
|
+
return out;
|
|
17746
|
+
}
|
|
17747
|
+
function formatJobStillRunningContinuePrompt(jobIds) {
|
|
17748
|
+
const ids = jobIds.join(", ");
|
|
17749
|
+
return [
|
|
17750
|
+
`Detached job still running: ${ids}.`,
|
|
17751
|
+
"Do not end this turn. Loop wait_for_job (same id) until stillRunning is false. The log pane updates from each wait result.",
|
|
17752
|
+
"If the job is hanging or doing the wrong thing, call stop_job (same id) instead of looping forever.",
|
|
17753
|
+
"Then report the result. Do not tell the user you will let them know later."
|
|
17754
|
+
].join(" ");
|
|
17755
|
+
}
|
|
17756
|
+
function formatDeferredDoneContinuePrompt() {
|
|
17757
|
+
return [
|
|
17758
|
+
"You ended the turn after promising to report later, but no detached job is running.",
|
|
17759
|
+
"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).",
|
|
17760
|
+
"Do not say you will let the user know later."
|
|
17761
|
+
].join(" ");
|
|
17762
|
+
}
|
|
17763
|
+
function turnWatchedDetachedJob(parts) {
|
|
17764
|
+
return parts.some((p) => {
|
|
17765
|
+
if (p.type !== "tool") return false;
|
|
17766
|
+
if (/wait_for_job$/i.test(p.name ?? "") || /stop_job$/i.test(p.name ?? "")) return true;
|
|
17767
|
+
const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
|
|
17768
|
+
return /detached-job\.(?:js|cjs)\b/i.test(blob);
|
|
17769
|
+
});
|
|
17770
|
+
}
|
|
17771
|
+
function planJobContinue(opts) {
|
|
17772
|
+
if (opts.isOrchestrator) return { action: "none" };
|
|
17773
|
+
if (opts.agent === "brightsy") return { action: "none" };
|
|
17774
|
+
if (opts.queueLength > 0) return { action: "none" };
|
|
17775
|
+
if (opts.continueCount >= MAX_JOB_CONTINUES) return { action: "none" };
|
|
17776
|
+
const farewell = looksLikeDeferredDonePromise(opts.chatText);
|
|
17777
|
+
if (opts.runningJobIds.length > 0 && (farewell || opts.watchedJob)) {
|
|
17778
|
+
return {
|
|
17779
|
+
action: "wait",
|
|
17780
|
+
jobIds: opts.runningJobIds,
|
|
17781
|
+
prompt: formatJobStillRunningContinuePrompt(opts.runningJobIds)
|
|
17782
|
+
};
|
|
17783
|
+
}
|
|
17784
|
+
if (looksLikeDeferredDonePromise(opts.chatText) && !opts.alreadyNudged) {
|
|
17785
|
+
return { action: "nudge", prompt: formatDeferredDoneContinuePrompt() };
|
|
17786
|
+
}
|
|
17787
|
+
return { action: "none" };
|
|
17788
|
+
}
|
|
17789
|
+
function tailProgress(logFile, maxLines = 12) {
|
|
17790
|
+
if (!(0, import_node_fs45.existsSync)(logFile)) return "(no log yet)";
|
|
17791
|
+
const lines = (0, import_node_fs45.readFileSync)(logFile, "utf8").split("\n");
|
|
17792
|
+
return lines.slice(-maxLines).join("\n");
|
|
17793
|
+
}
|
|
17794
|
+
function lastProgressLine(progress) {
|
|
17795
|
+
const last = progress.split("\n").map((l) => l.trim()).filter((l) => l && l !== "(no log yet)").at(-1);
|
|
17796
|
+
return last ? last.slice(0, 72) : void 0;
|
|
17797
|
+
}
|
|
17798
|
+
function readLogLines(file) {
|
|
17799
|
+
if (!(0, import_node_fs45.existsSync)(file)) return [];
|
|
17800
|
+
const lines = (0, import_node_fs45.readFileSync)(file, "utf8").split("\n");
|
|
17801
|
+
if (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
17802
|
+
return lines;
|
|
17803
|
+
}
|
|
17804
|
+
function takeDelta(logFile, cursorFile) {
|
|
17805
|
+
const lines = readLogLines(logFile);
|
|
17806
|
+
const cursor = readIntFile(cursorFile) ?? 0;
|
|
17807
|
+
const start = Math.min(Math.max(0, cursor), lines.length);
|
|
17808
|
+
return { delta: capJobLogDelta(lines.slice(start).join("\n")), nextCursor: lines.length };
|
|
17809
|
+
}
|
|
17810
|
+
function snapshotJob(dir) {
|
|
17811
|
+
const pid = readIntFile((0, import_node_path45.join)(dir, "pid"));
|
|
17812
|
+
const running = jobTreeAlive(pid);
|
|
17813
|
+
return {
|
|
17814
|
+
pid,
|
|
17815
|
+
running,
|
|
17816
|
+
exitCode: readIntFile((0, import_node_path45.join)(dir, "exit")),
|
|
17817
|
+
log: (0, import_node_path45.join)(dir, "log"),
|
|
17818
|
+
cursor: (0, import_node_path45.join)(dir, "present.cursor"),
|
|
17819
|
+
progress: tailProgress((0, import_node_path45.join)(dir, "log"))
|
|
17820
|
+
};
|
|
17821
|
+
}
|
|
17822
|
+
function toResult(id, snap, extra) {
|
|
17823
|
+
const failed = extra?.failed === true || !snap.running && snap.exitCode != null && snap.exitCode !== 0;
|
|
17824
|
+
const ok = !snap.running && snap.exitCode === 0;
|
|
17825
|
+
const stillRunning = snap.running && !ok;
|
|
17826
|
+
const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
|
|
17827
|
+
try {
|
|
17828
|
+
(0, import_node_fs45.mkdirSync)((0, import_node_path45.dirname)(snap.cursor), { recursive: true });
|
|
17829
|
+
(0, import_node_fs45.writeFileSync)(snap.cursor, `${nextCursor}
|
|
17830
|
+
`);
|
|
17831
|
+
} catch {
|
|
17832
|
+
}
|
|
17833
|
+
const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
|
|
17834
|
+
const progress = extra?.progress ?? snap.progress;
|
|
17835
|
+
return {
|
|
17836
|
+
stillRunning,
|
|
17837
|
+
ok,
|
|
17838
|
+
failed: Boolean(failed && !stillRunning && !ok),
|
|
17839
|
+
status,
|
|
17840
|
+
id,
|
|
17841
|
+
pid: snap.pid,
|
|
17842
|
+
exitCode: snap.exitCode ?? void 0,
|
|
17843
|
+
phase: lastProgressLine(progress),
|
|
17844
|
+
delta,
|
|
17845
|
+
progress,
|
|
17846
|
+
hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
|
|
17847
|
+
};
|
|
17848
|
+
}
|
|
17849
|
+
async function sleepMs(ms) {
|
|
17850
|
+
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
17851
|
+
}
|
|
17852
|
+
async function waitForDetachedJob(cwd, id, opts) {
|
|
17853
|
+
const jobId = sanitizeDetachedJobId(id);
|
|
17854
|
+
const root = cwd.trim() || process.cwd();
|
|
17855
|
+
const dir = resolveJobDir(root, jobId);
|
|
17856
|
+
const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
|
|
17857
|
+
const sleep2 = opts?.sleep ?? sleepMs;
|
|
17858
|
+
if (!(0, import_node_fs45.existsSync)(dir)) {
|
|
17859
|
+
return {
|
|
17860
|
+
stillRunning: false,
|
|
17861
|
+
ok: false,
|
|
17862
|
+
failed: true,
|
|
17863
|
+
status: "failed",
|
|
17864
|
+
id: jobId,
|
|
17865
|
+
delta: "",
|
|
17866
|
+
progress: "No detached job. Start one first.",
|
|
17867
|
+
hint: "Start with detached-job.cjs start <id> -- <command>, then call wait_for_job again."
|
|
17868
|
+
};
|
|
17869
|
+
}
|
|
17870
|
+
const deadline = Date.now() + timeoutMs;
|
|
17871
|
+
let snap = snapshotJob(dir);
|
|
17872
|
+
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
17873
|
+
if (!snap.running && snap.pid == null && snap.progress === "(no log yet)") {
|
|
17874
|
+
return toResult(jobId, snap, {
|
|
17875
|
+
failed: true,
|
|
17876
|
+
progress: "No detached job. Start one first."
|
|
17877
|
+
});
|
|
17878
|
+
}
|
|
17879
|
+
while (Date.now() < deadline) {
|
|
17880
|
+
snap = snapshotJob(dir);
|
|
17881
|
+
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
17882
|
+
if (!snap.running) return toResult(jobId, snap, { failed: true });
|
|
17883
|
+
await sleep2(Math.min(2e3, Math.max(50, deadline - Date.now())));
|
|
17884
|
+
}
|
|
17885
|
+
snap = snapshotJob(dir);
|
|
17886
|
+
return toResult(jobId, snap);
|
|
17887
|
+
}
|
|
17888
|
+
function killProcessTree(pid, signal) {
|
|
17889
|
+
if (!Number.isInteger(pid) || pid <= 0) return;
|
|
17890
|
+
if (process.platform !== "win32") {
|
|
17891
|
+
try {
|
|
17892
|
+
process.kill(-pid, signal);
|
|
17893
|
+
return;
|
|
17894
|
+
} catch {
|
|
17895
|
+
}
|
|
17896
|
+
}
|
|
17897
|
+
try {
|
|
17898
|
+
process.kill(pid, signal);
|
|
17899
|
+
} catch {
|
|
17900
|
+
}
|
|
17901
|
+
}
|
|
17902
|
+
function appendJobLog(logFile, line) {
|
|
17903
|
+
try {
|
|
17904
|
+
(0, import_node_fs45.mkdirSync)((0, import_node_path45.dirname)(logFile), { recursive: true });
|
|
17905
|
+
const body = line.endsWith("\n") ? line : `${line}
|
|
17906
|
+
`;
|
|
17907
|
+
(0, import_node_fs45.appendFileSync)(logFile, body);
|
|
17908
|
+
} catch {
|
|
17909
|
+
}
|
|
17910
|
+
}
|
|
17911
|
+
async function stopDetachedJob(cwd, id, opts) {
|
|
17912
|
+
const jobId = sanitizeDetachedJobId(id);
|
|
17913
|
+
const root = cwd.trim() || process.cwd();
|
|
17914
|
+
const dir = resolveJobDir(root, jobId);
|
|
17915
|
+
const sleep2 = opts?.sleep ?? sleepMs;
|
|
17916
|
+
const why = opts?.reason?.trim() ?? "";
|
|
17917
|
+
if (!(0, import_node_fs45.existsSync)(dir)) {
|
|
17918
|
+
return {
|
|
17919
|
+
stillRunning: false,
|
|
17920
|
+
ok: false,
|
|
17921
|
+
failed: true,
|
|
17922
|
+
status: "failed",
|
|
17923
|
+
id: jobId,
|
|
17924
|
+
delta: "",
|
|
17925
|
+
progress: "No detached job. Start one first.",
|
|
17926
|
+
hint: "Start with detached-job.cjs start <id> -- <command>, then wait_for_job or stop_job.",
|
|
17927
|
+
stopped: false,
|
|
17928
|
+
reason: "not-found"
|
|
17929
|
+
};
|
|
17930
|
+
}
|
|
17931
|
+
let snap = snapshotJob(dir);
|
|
17932
|
+
if (!snap.running) {
|
|
17933
|
+
const result2 = toResult(jobId, snap);
|
|
17934
|
+
return {
|
|
17935
|
+
...result2,
|
|
17936
|
+
stopped: false,
|
|
17937
|
+
reason: "not-running",
|
|
17938
|
+
hint: "That job is already finished. Read the log; do not wait again unless you start a new command."
|
|
17939
|
+
};
|
|
17940
|
+
}
|
|
17941
|
+
appendJobLog(snap.log, `$ stop${why ? `: ${why}` : ""}`);
|
|
17942
|
+
const targetPid = snap.pid;
|
|
17943
|
+
if (targetPid != null) killProcessTree(targetPid, "SIGTERM");
|
|
17944
|
+
const graceMs = Number.isFinite(opts?.graceMs) ? Math.max(50, opts.graceMs) : 2e3;
|
|
17945
|
+
const deadline = Date.now() + graceMs;
|
|
17946
|
+
while (Date.now() < deadline && jobTreeAlive(targetPid)) {
|
|
17947
|
+
await sleep2(Math.min(100, Math.max(20, deadline - Date.now())));
|
|
17948
|
+
}
|
|
17949
|
+
if (targetPid != null) {
|
|
17950
|
+
killProcessTree(targetPid, "SIGKILL");
|
|
17951
|
+
await sleep2(100);
|
|
17952
|
+
}
|
|
17953
|
+
const exitFile = (0, import_node_path45.join)(dir, "exit");
|
|
17954
|
+
if (!(0, import_node_fs45.existsSync)(exitFile)) {
|
|
17955
|
+
try {
|
|
17956
|
+
(0, import_node_fs45.writeFileSync)(exitFile, "1\n");
|
|
17957
|
+
} catch {
|
|
17958
|
+
}
|
|
17959
|
+
}
|
|
17960
|
+
snap = snapshotJob(dir);
|
|
17961
|
+
const stillRunning = jobTreeAlive(targetPid) || snap.running;
|
|
17962
|
+
const result = toResult(jobId, snap, { failed: true });
|
|
17963
|
+
const stopped = !stillRunning;
|
|
17964
|
+
return {
|
|
17965
|
+
...result,
|
|
17966
|
+
stillRunning,
|
|
17967
|
+
ok: false,
|
|
17968
|
+
failed: true,
|
|
17969
|
+
status: stillRunning ? "running" : "failed",
|
|
17970
|
+
stopped,
|
|
17971
|
+
reason: stopped ? "stopped" : "still-running",
|
|
17972
|
+
stopReason: why || void 0,
|
|
17973
|
+
hint: stopped ? MCP_STOP_JOB_HINT : MCP_WAIT_JOB_STILL_RUNNING_HINT
|
|
17974
|
+
};
|
|
17975
|
+
}
|
|
17976
|
+
var import_node_fs45, import_node_path45, MAX_JOB_CONTINUES, MAX_JOB_DELTA_LINES, MAX_JOB_DELTA_CHARS, DEFERRED_DONE_MAX_CHARS, GH_WATCH_REFRESH, MCP_WAIT_JOB_STILL_RUNNING_HINT, MCP_STOP_JOB_HINT, JOB_ID_RE;
|
|
17977
|
+
var init_wait_for_job = __esm({
|
|
17978
|
+
"src/mcp/wait-for-job.ts"() {
|
|
17979
|
+
"use strict";
|
|
17980
|
+
import_node_fs45 = require("fs");
|
|
17981
|
+
import_node_path45 = require("path");
|
|
17982
|
+
init_workspace_scratch();
|
|
17983
|
+
init_wait_for_turn();
|
|
17984
|
+
MAX_JOB_CONTINUES = 8;
|
|
17985
|
+
MAX_JOB_DELTA_LINES = 80;
|
|
17986
|
+
MAX_JOB_DELTA_CHARS = 4e3;
|
|
17987
|
+
DEFERRED_DONE_MAX_CHARS = 480;
|
|
17988
|
+
GH_WATCH_REFRESH = "Refreshing run status every 3 seconds.";
|
|
17989
|
+
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.";
|
|
17990
|
+
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.";
|
|
17991
|
+
JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
17992
|
+
}
|
|
17993
|
+
});
|
|
17994
|
+
|
|
17555
17995
|
// src/hook/conductor.ts
|
|
17556
17996
|
function matchSimpleGlob(pattern, name) {
|
|
17557
17997
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
17558
17998
|
return new RegExp(`^${escaped}$`).test(name);
|
|
17559
17999
|
}
|
|
17560
18000
|
function readWorktreeInclude(repoPath) {
|
|
17561
|
-
const path2 = (0,
|
|
17562
|
-
if (!(0,
|
|
17563
|
-
return (0,
|
|
18001
|
+
const path2 = (0, import_node_path46.join)(repoPath, ".worktreeinclude");
|
|
18002
|
+
if (!(0, import_node_fs46.existsSync)(path2)) return [];
|
|
18003
|
+
return (0, import_node_fs46.readFileSync)(path2, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
17564
18004
|
}
|
|
17565
18005
|
function resolveFilesToCopy(repoPath) {
|
|
17566
18006
|
const fromInclude = readWorktreeInclude(repoPath);
|
|
@@ -17570,10 +18010,10 @@ function resolveFilesToCopy(repoPath) {
|
|
|
17570
18010
|
if (settings?.fileIncludeGlobs?.length) {
|
|
17571
18011
|
const matched = [];
|
|
17572
18012
|
try {
|
|
17573
|
-
for (const entry of (0,
|
|
18013
|
+
for (const entry of (0, import_node_fs46.readdirSync)(repoPath, { withFileTypes: true })) {
|
|
17574
18014
|
if (!entry.isFile()) continue;
|
|
17575
18015
|
for (const glob of settings.fileIncludeGlobs) {
|
|
17576
|
-
if (matchSimpleGlob(glob, entry.name) || matchSimpleGlob((0,
|
|
18016
|
+
if (matchSimpleGlob(glob, entry.name) || matchSimpleGlob((0, import_node_path46.basename)(glob), entry.name)) {
|
|
17577
18017
|
matched.push(entry.name);
|
|
17578
18018
|
break;
|
|
17579
18019
|
}
|
|
@@ -17585,7 +18025,7 @@ function resolveFilesToCopy(repoPath) {
|
|
|
17585
18025
|
}
|
|
17586
18026
|
const defaults = [];
|
|
17587
18027
|
try {
|
|
17588
|
-
for (const entry of (0,
|
|
18028
|
+
for (const entry of (0, import_node_fs46.readdirSync)(repoPath, { withFileTypes: true })) {
|
|
17589
18029
|
if (entry.isFile() && entry.name.startsWith(".env")) {
|
|
17590
18030
|
defaults.push(entry.name);
|
|
17591
18031
|
}
|
|
@@ -17599,11 +18039,11 @@ function copyConfiguredFiles(repoPath, worktreePath) {
|
|
|
17599
18039
|
const patterns = resolveFilesToCopy(repoPath);
|
|
17600
18040
|
const copied = [];
|
|
17601
18041
|
for (const rel of patterns) {
|
|
17602
|
-
const src = (0,
|
|
17603
|
-
if (!(0,
|
|
17604
|
-
const dest = (0,
|
|
17605
|
-
(0,
|
|
17606
|
-
(0,
|
|
18042
|
+
const src = (0, import_node_path46.join)(repoPath, rel);
|
|
18043
|
+
if (!(0, import_node_fs46.existsSync)(src)) continue;
|
|
18044
|
+
const dest = (0, import_node_path46.join)(worktreePath, rel);
|
|
18045
|
+
(0, import_node_fs46.mkdirSync)((0, import_node_path46.dirname)(dest), { recursive: true });
|
|
18046
|
+
(0, import_node_fs46.copyFileSync)(src, dest);
|
|
17607
18047
|
copied.push(rel);
|
|
17608
18048
|
}
|
|
17609
18049
|
return copied;
|
|
@@ -17638,7 +18078,7 @@ function buildWorkspaceScriptEnv(opts, baseEnv) {
|
|
|
17638
18078
|
const env = stripNestedElectronEnv({
|
|
17639
18079
|
...baseEnv ?? process.env
|
|
17640
18080
|
});
|
|
17641
|
-
const name = opts.workspaceName ?? (0,
|
|
18081
|
+
const name = opts.workspaceName ?? (0, import_node_path46.basename)(opts.worktreePath);
|
|
17642
18082
|
const ports = opts.ports ?? [];
|
|
17643
18083
|
const primary = ports[0];
|
|
17644
18084
|
env.SIDEBOARD_WORKSPACE_NAME = name;
|
|
@@ -17707,20 +18147,20 @@ function listenerCwd(pid) {
|
|
|
17707
18147
|
}
|
|
17708
18148
|
function realpathOrResolve(path2) {
|
|
17709
18149
|
try {
|
|
17710
|
-
return (0,
|
|
18150
|
+
return (0, import_node_fs46.realpathSync)(path2);
|
|
17711
18151
|
} catch {
|
|
17712
|
-
return (0,
|
|
18152
|
+
return (0, import_node_path46.resolve)(path2);
|
|
17713
18153
|
}
|
|
17714
18154
|
}
|
|
17715
18155
|
function isOtherWorktreeListenerCwd(cwd, worktreePath) {
|
|
17716
18156
|
const wt = realpathOrResolve(worktreePath);
|
|
17717
18157
|
const c = realpathOrResolve(cwd);
|
|
17718
|
-
if (c === wt || c.startsWith(wt +
|
|
17719
|
-
const parent = (0,
|
|
17720
|
-
const thisName = (0,
|
|
18158
|
+
if (c === wt || c.startsWith(wt + import_node_path46.sep)) return false;
|
|
18159
|
+
const parent = (0, import_node_path46.dirname)(wt);
|
|
18160
|
+
const thisName = (0, import_node_path46.basename)(wt);
|
|
17721
18161
|
if (!thisName || c === parent) return false;
|
|
17722
|
-
if (!c.startsWith(parent +
|
|
17723
|
-
const first = c.slice(parent.length + 1).split(
|
|
18162
|
+
if (!c.startsWith(parent + import_node_path46.sep)) return false;
|
|
18163
|
+
const first = c.slice(parent.length + 1).split(import_node_path46.sep)[0];
|
|
17724
18164
|
return Boolean(first) && first !== thisName;
|
|
17725
18165
|
}
|
|
17726
18166
|
function signalPid(pid, signal) {
|
|
@@ -17729,6 +18169,25 @@ function signalPid(pid, signal) {
|
|
|
17729
18169
|
} catch {
|
|
17730
18170
|
}
|
|
17731
18171
|
}
|
|
18172
|
+
function isThisWorktreeListenerCwd(cwd, worktreePath) {
|
|
18173
|
+
const wt = realpathOrResolve(worktreePath);
|
|
18174
|
+
const c = realpathOrResolve(cwd);
|
|
18175
|
+
return c === wt || c.startsWith(wt + import_node_path46.sep);
|
|
18176
|
+
}
|
|
18177
|
+
function killDetachedRunTree(pid, worktreePath) {
|
|
18178
|
+
if (!Number.isFinite(pid) || pid <= 1 || pid === process.pid) return;
|
|
18179
|
+
const cwd = listenerCwd(pid);
|
|
18180
|
+
if (cwd) {
|
|
18181
|
+
if (!isThisWorktreeListenerCwd(cwd, worktreePath)) return;
|
|
18182
|
+
} else if (!processGroupAlive(pid)) {
|
|
18183
|
+
return;
|
|
18184
|
+
}
|
|
18185
|
+
try {
|
|
18186
|
+
process.kill(-pid, "SIGTERM");
|
|
18187
|
+
} catch {
|
|
18188
|
+
signalPid(pid, "SIGTERM");
|
|
18189
|
+
}
|
|
18190
|
+
}
|
|
17732
18191
|
function killListenersOnPorts(ports) {
|
|
17733
18192
|
const selfPid = process.pid;
|
|
17734
18193
|
for (const port of ports) {
|
|
@@ -18119,14 +18578,14 @@ async function startDevServer(repoPath, worktreePath, onLine, opts) {
|
|
|
18119
18578
|
throw err;
|
|
18120
18579
|
}
|
|
18121
18580
|
}
|
|
18122
|
-
var
|
|
18581
|
+
var import_node_fs46, import_node_child_process5, import_node_net, import_node_path46, import_execa4, import_node_readline3, PORT_RANGE_SIZE, RECLAIM_RETRY_DELAY_MS, RECLAIM_TRIES, cachedLoginEnv;
|
|
18123
18582
|
var init_conductor = __esm({
|
|
18124
18583
|
"src/hook/conductor.ts"() {
|
|
18125
18584
|
"use strict";
|
|
18126
|
-
|
|
18585
|
+
import_node_fs46 = require("fs");
|
|
18127
18586
|
import_node_child_process5 = require("child_process");
|
|
18128
18587
|
import_node_net = require("net");
|
|
18129
|
-
|
|
18588
|
+
import_node_path46 = require("path");
|
|
18130
18589
|
import_execa4 = require("execa");
|
|
18131
18590
|
import_node_readline3 = require("readline");
|
|
18132
18591
|
init_settings();
|
|
@@ -18138,6 +18597,7 @@ var init_conductor = __esm({
|
|
|
18138
18597
|
init_request_review();
|
|
18139
18598
|
init_worktree_exclude();
|
|
18140
18599
|
init_worktree_run_ports();
|
|
18600
|
+
init_wait_for_job();
|
|
18141
18601
|
init_worktree_run_ports();
|
|
18142
18602
|
init_nested_electron_env();
|
|
18143
18603
|
PORT_RANGE_SIZE = 10;
|
|
@@ -18158,9 +18618,9 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
18158
18618
|
repoPaths?.length ? repoPaths : threads.map((t) => t.repoPath).filter(Boolean)
|
|
18159
18619
|
);
|
|
18160
18620
|
const homeRoot = sideboardWorkspacesDir();
|
|
18161
|
-
if ((0,
|
|
18621
|
+
if ((0, import_node_fs47.existsSync)(homeRoot)) {
|
|
18162
18622
|
try {
|
|
18163
|
-
for (const entry of (0,
|
|
18623
|
+
for (const entry of (0, import_node_fs47.readdirSync)(homeRoot, { withFileTypes: true })) {
|
|
18164
18624
|
if (!entry.isDirectory()) continue;
|
|
18165
18625
|
void entry;
|
|
18166
18626
|
}
|
|
@@ -18170,7 +18630,7 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
18170
18630
|
const orphans = [];
|
|
18171
18631
|
const seen = /* @__PURE__ */ new Set();
|
|
18172
18632
|
for (const repoPath of repos) {
|
|
18173
|
-
if (!repoPath || !(0,
|
|
18633
|
+
if (!repoPath || !(0, import_node_fs47.existsSync)(repoPath)) continue;
|
|
18174
18634
|
try {
|
|
18175
18635
|
const wts = await listWorktrees(repoPath);
|
|
18176
18636
|
for (const wt of wts) {
|
|
@@ -18181,7 +18641,7 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
18181
18641
|
seen.add(path2);
|
|
18182
18642
|
let mtimeMs = 0;
|
|
18183
18643
|
try {
|
|
18184
|
-
mtimeMs = (0,
|
|
18644
|
+
mtimeMs = (0, import_node_fs47.statSync)(path2).mtimeMs;
|
|
18185
18645
|
} catch {
|
|
18186
18646
|
mtimeMs = 0;
|
|
18187
18647
|
}
|
|
@@ -18191,16 +18651,16 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
18191
18651
|
}
|
|
18192
18652
|
try {
|
|
18193
18653
|
const root = worktreesRoot(repoPath);
|
|
18194
|
-
if ((0,
|
|
18195
|
-
for (const entry of (0,
|
|
18654
|
+
if ((0, import_node_fs47.existsSync)(root)) {
|
|
18655
|
+
for (const entry of (0, import_node_fs47.readdirSync)(root, { withFileTypes: true })) {
|
|
18196
18656
|
if (!entry.isDirectory()) continue;
|
|
18197
|
-
const path2 = (0,
|
|
18657
|
+
const path2 = (0, import_node_path47.join)(root, entry.name).replace(/\/$/, "");
|
|
18198
18658
|
if (known.has(path2) || seen.has(path2)) continue;
|
|
18199
|
-
if (!(0,
|
|
18659
|
+
if (!(0, import_node_fs47.existsSync)((0, import_node_path47.join)(path2, ".git"))) continue;
|
|
18200
18660
|
seen.add(path2);
|
|
18201
18661
|
let mtimeMs = 0;
|
|
18202
18662
|
try {
|
|
18203
|
-
mtimeMs = (0,
|
|
18663
|
+
mtimeMs = (0, import_node_fs47.statSync)(path2).mtimeMs;
|
|
18204
18664
|
} catch {
|
|
18205
18665
|
mtimeMs = Date.now();
|
|
18206
18666
|
}
|
|
@@ -18261,12 +18721,12 @@ function worktreeCleanupSettings() {
|
|
|
18261
18721
|
autoCleanupOrphans: a.autoCleanupOrphans
|
|
18262
18722
|
};
|
|
18263
18723
|
}
|
|
18264
|
-
var
|
|
18724
|
+
var import_node_fs47, import_node_path47;
|
|
18265
18725
|
var init_orphan_cleanup = __esm({
|
|
18266
18726
|
"src/git/orphan-cleanup.ts"() {
|
|
18267
18727
|
"use strict";
|
|
18268
|
-
|
|
18269
|
-
|
|
18728
|
+
import_node_fs47 = require("fs");
|
|
18729
|
+
import_node_path47 = require("path");
|
|
18270
18730
|
init_worktree();
|
|
18271
18731
|
init_thread_store();
|
|
18272
18732
|
init_paths();
|
|
@@ -18282,6 +18742,7 @@ function stripArchivedTranscript(thread) {
|
|
|
18282
18742
|
attachments: [],
|
|
18283
18743
|
pendingTurnAttachments: [],
|
|
18284
18744
|
queueAttachments: [],
|
|
18745
|
+
compactedMessages: void 0,
|
|
18285
18746
|
messages: [historyRetentionStubMessage(thread)]
|
|
18286
18747
|
},
|
|
18287
18748
|
{ touch: false }
|
|
@@ -18441,12 +18902,12 @@ async function cloneRepoIntoSideboard(opts) {
|
|
|
18441
18902
|
if (!url) throw new Error("Clone URL is required");
|
|
18442
18903
|
let name = opts.name?.trim();
|
|
18443
18904
|
if (!name) {
|
|
18444
|
-
const leaf = (0,
|
|
18905
|
+
const leaf = (0, import_node_path48.basename)(url.replace(/\/$/, "").replace(/\.git$/, ""));
|
|
18445
18906
|
name = leaf || "repo";
|
|
18446
18907
|
}
|
|
18447
18908
|
name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
|
|
18448
|
-
const dest = (0,
|
|
18449
|
-
if ((0,
|
|
18909
|
+
const dest = (0, import_node_path48.join)(sideboardReposDir(), name);
|
|
18910
|
+
if ((0, import_node_fs48.existsSync)(dest)) {
|
|
18450
18911
|
const repoPath2 = await resolveRepoRoot(dest);
|
|
18451
18912
|
const workspace2 = await ensureWorkspace(repoPath2);
|
|
18452
18913
|
return { repoPath: repoPath2, workspace: workspace2 };
|
|
@@ -18461,12 +18922,12 @@ async function cloneRepoIntoSideboard(opts) {
|
|
|
18461
18922
|
const workspace = await ensureWorkspace(repoPath);
|
|
18462
18923
|
return { repoPath, workspace };
|
|
18463
18924
|
}
|
|
18464
|
-
var
|
|
18925
|
+
var import_node_fs48, import_node_path48, import_execa6;
|
|
18465
18926
|
var init_clone_repo = __esm({
|
|
18466
18927
|
"src/git/clone-repo.ts"() {
|
|
18467
18928
|
"use strict";
|
|
18468
|
-
|
|
18469
|
-
|
|
18929
|
+
import_node_fs48 = require("fs");
|
|
18930
|
+
import_node_path48 = require("path");
|
|
18470
18931
|
import_execa6 = require("execa");
|
|
18471
18932
|
init_paths();
|
|
18472
18933
|
init_workspaces();
|
|
@@ -18524,394 +18985,6 @@ var init_child_halt = __esm({
|
|
|
18524
18985
|
}
|
|
18525
18986
|
});
|
|
18526
18987
|
|
|
18527
|
-
// src/mcp/wait-for-turn.ts
|
|
18528
|
-
function mcpWaitForTurnTimeoutMs(requested) {
|
|
18529
|
-
const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
|
|
18530
|
-
if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
|
|
18531
|
-
return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
|
|
18532
|
-
}
|
|
18533
|
-
function mcpWaitTaskHint(taskState, status) {
|
|
18534
|
-
switch (taskState) {
|
|
18535
|
-
case "submitted":
|
|
18536
|
-
return MCP_WAIT_QUEUED_HINT;
|
|
18537
|
-
case "working":
|
|
18538
|
-
return MCP_WAIT_STILL_RUNNING_HINT;
|
|
18539
|
-
case "input-required":
|
|
18540
|
-
return MCP_WAIT_INPUT_REQUIRED_HINT;
|
|
18541
|
-
case "canceled":
|
|
18542
|
-
return MCP_WAIT_STOPPED_HINT;
|
|
18543
|
-
case "failed":
|
|
18544
|
-
return status === "broken" ? MCP_WAIT_BROKEN_HINT : MCP_WAIT_ERROR_HINT;
|
|
18545
|
-
default:
|
|
18546
|
-
return void 0;
|
|
18547
|
-
}
|
|
18548
|
-
}
|
|
18549
|
-
var MCP_WAIT_FOR_TURN_MAX_MS, MCP_WAIT_STILL_RUNNING_HINT, MCP_WAIT_QUEUED_HINT, MCP_WAIT_STOPPED_HINT, MCP_WAIT_BROKEN_HINT, MCP_WAIT_ERROR_HINT, MCP_WAIT_INPUT_REQUIRED_HINT;
|
|
18550
|
-
var init_wait_for_turn = __esm({
|
|
18551
|
-
"src/mcp/wait-for-turn.ts"() {
|
|
18552
|
-
"use strict";
|
|
18553
|
-
MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
18554
|
-
MCP_WAIT_STILL_RUNNING_HINT = "Child is still working. Call wait_for_turn again. Do not send_to_thread a check-in (that steers / interrupts) or assume a hang while progress is updating.";
|
|
18555
|
-
MCP_WAIT_QUEUED_HINT = "Child is queued waiting for a concurrency slot \u2014 it has not started yet. Call wait_for_turn again. Do not send_to_thread a check-in (that steers / interrupts), force_stop, or assume it failed to start.";
|
|
18556
|
-
MCP_WAIT_STOPPED_HINT = "Child was stopped before the turn finished. Do not treat this as success. send_to_thread to resume, or tell the user.";
|
|
18557
|
-
MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
|
|
18558
|
-
MCP_WAIT_ERROR_HINT = "Child turn failed. lastError/text is the failure \u2014 switch agent, tell the user, or retry. Do not treat empty text as success.";
|
|
18559
|
-
MCP_WAIT_INPUT_REQUIRED_HINT = "Child asked the user a question (ask_user). taskState is input-required. Wait for the user to answer in that chat \u2014 do not send_to_thread a check-in.";
|
|
18560
|
-
}
|
|
18561
|
-
});
|
|
18562
|
-
|
|
18563
|
-
// src/mcp/wait-for-job.ts
|
|
18564
|
-
function mcpWaitForJobTimeoutMs(requested) {
|
|
18565
|
-
return mcpWaitForTurnTimeoutMs(requested);
|
|
18566
|
-
}
|
|
18567
|
-
function sanitizeDetachedJobId(id) {
|
|
18568
|
-
const s = id.trim();
|
|
18569
|
-
if (!JOB_ID_RE.test(s)) {
|
|
18570
|
-
throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
|
|
18571
|
-
}
|
|
18572
|
-
return s;
|
|
18573
|
-
}
|
|
18574
|
-
function jobAlive(pid) {
|
|
18575
|
-
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
18576
|
-
try {
|
|
18577
|
-
process.kill(pid, 0);
|
|
18578
|
-
return true;
|
|
18579
|
-
} catch {
|
|
18580
|
-
return false;
|
|
18581
|
-
}
|
|
18582
|
-
}
|
|
18583
|
-
function processGroupAlive(pgid) {
|
|
18584
|
-
if (process.platform === "win32") return false;
|
|
18585
|
-
if (!Number.isInteger(pgid) || pgid <= 1) return false;
|
|
18586
|
-
try {
|
|
18587
|
-
process.kill(-pgid, 0);
|
|
18588
|
-
return true;
|
|
18589
|
-
} catch (err) {
|
|
18590
|
-
return err && typeof err === "object" && "code" in err && err.code === "EPERM";
|
|
18591
|
-
}
|
|
18592
|
-
}
|
|
18593
|
-
function jobTreeAlive(pid) {
|
|
18594
|
-
if (pid == null) return false;
|
|
18595
|
-
return jobAlive(pid) || processGroupAlive(pid);
|
|
18596
|
-
}
|
|
18597
|
-
function readIntFile(file) {
|
|
18598
|
-
if (!(0, import_node_fs48.existsSync)(file)) return null;
|
|
18599
|
-
const n = Number.parseInt((0, import_node_fs48.readFileSync)(file, "utf8").trim(), 10);
|
|
18600
|
-
return Number.isInteger(n) ? n : null;
|
|
18601
|
-
}
|
|
18602
|
-
function jobDir(root, id, legacy = false) {
|
|
18603
|
-
return (0, import_node_path48.join)(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
|
|
18604
|
-
}
|
|
18605
|
-
function resolveJobDir(root, id) {
|
|
18606
|
-
const modern = jobDir(root, id, false);
|
|
18607
|
-
if ((0, import_node_fs48.existsSync)(modern)) return modern;
|
|
18608
|
-
const legacy = jobDir(root, id, true);
|
|
18609
|
-
if ((0, import_node_fs48.existsSync)(legacy)) return legacy;
|
|
18610
|
-
return modern;
|
|
18611
|
-
}
|
|
18612
|
-
function listJobIdsIn(root, rel) {
|
|
18613
|
-
const dir = (0, import_node_path48.join)(root, rel);
|
|
18614
|
-
if (!(0, import_node_fs48.existsSync)(dir)) return [];
|
|
18615
|
-
return (0, import_node_fs48.readdirSync)(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
|
|
18616
|
-
}
|
|
18617
|
-
function listRunningDetachedJobs(worktreePath) {
|
|
18618
|
-
const root = worktreePath.trim();
|
|
18619
|
-
if (!root) return [];
|
|
18620
|
-
const ids = /* @__PURE__ */ new Set([
|
|
18621
|
-
...listJobIdsIn(root, DETACHED_JOBS_DIR),
|
|
18622
|
-
...listJobIdsIn(root, LEGACY_DETACHED_JOBS_DIR)
|
|
18623
|
-
]);
|
|
18624
|
-
const running = [];
|
|
18625
|
-
for (const id of ids) {
|
|
18626
|
-
const dir = resolveJobDir(root, id);
|
|
18627
|
-
const pid = readIntFile((0, import_node_path48.join)(dir, "pid"));
|
|
18628
|
-
if (jobTreeAlive(pid)) running.push(id);
|
|
18629
|
-
}
|
|
18630
|
-
return running.sort();
|
|
18631
|
-
}
|
|
18632
|
-
function looksLikeDeferredDonePromise(text7) {
|
|
18633
|
-
const t = (text7 ?? "").trim();
|
|
18634
|
-
if (!t || t.length > DEFERRED_DONE_MAX_CHARS) return false;
|
|
18635
|
-
const unquoted = t.replace(/`[^`]*`/g, " ").replace(/["“”][^"“”]*["“”]/g, " ").replace(/\s+/g, " ").trim();
|
|
18636
|
-
if (!unquoted) return false;
|
|
18637
|
-
return /\b(i['’]?ll|i will)\s+let you know\b/i.test(unquoted) || /\blet you know when\b/i.test(unquoted) || /\b(check back|ping me)\s+when\b/i.test(unquoted) || /\bi(?:['’]ll| will)\s+(report|update you)\s+when\b/i.test(unquoted);
|
|
18638
|
-
}
|
|
18639
|
-
function isJobContinuePrompt(text7) {
|
|
18640
|
-
if (typeof text7 !== "string") return false;
|
|
18641
|
-
const t = text7.trim();
|
|
18642
|
-
return t.startsWith("Detached job still running:") || t.startsWith("You ended the turn after promising to report later");
|
|
18643
|
-
}
|
|
18644
|
-
function collapseGhRunWatchLog(text7) {
|
|
18645
|
-
if (!text7.includes(GH_WATCH_REFRESH)) return text7;
|
|
18646
|
-
const frames = text7.split(/(?=Refreshing run status every 3 seconds\.)/);
|
|
18647
|
-
const watchFrames = frames.filter((f) => f.includes(GH_WATCH_REFRESH));
|
|
18648
|
-
if (watchFrames.length <= 1) return text7;
|
|
18649
|
-
const prefix = frames[0]?.includes(GH_WATCH_REFRESH) ? "" : frames[0] ?? "";
|
|
18650
|
-
const last = watchFrames[watchFrames.length - 1] ?? "";
|
|
18651
|
-
const omitted = watchFrames.length - 1;
|
|
18652
|
-
const note = `(${omitted} identical gh run watch refresh${omitted === 1 ? "" : "es"} omitted)
|
|
18653
|
-
`;
|
|
18654
|
-
return [prefix.trimEnd(), note, last.trimStart()].filter(Boolean).join("\n");
|
|
18655
|
-
}
|
|
18656
|
-
function capJobLogDelta(text7) {
|
|
18657
|
-
const collapsed = collapseGhRunWatchLog(text7);
|
|
18658
|
-
const lines = collapsed.split("\n");
|
|
18659
|
-
let out = lines.length > MAX_JOB_DELTA_LINES ? `\u2026(${lines.length - MAX_JOB_DELTA_LINES} earlier lines omitted)
|
|
18660
|
-
${lines.slice(-MAX_JOB_DELTA_LINES).join("\n")}` : collapsed;
|
|
18661
|
-
if (out.length > MAX_JOB_DELTA_CHARS) {
|
|
18662
|
-
out = `\u2026(truncated ${out.length} chars)
|
|
18663
|
-
${out.slice(-MAX_JOB_DELTA_CHARS)}`;
|
|
18664
|
-
}
|
|
18665
|
-
return out;
|
|
18666
|
-
}
|
|
18667
|
-
function formatJobStillRunningContinuePrompt(jobIds) {
|
|
18668
|
-
const ids = jobIds.join(", ");
|
|
18669
|
-
return [
|
|
18670
|
-
`Detached job still running: ${ids}.`,
|
|
18671
|
-
"Do not end this turn. Loop wait_for_job (same id) until stillRunning is false. The log pane updates from each wait result.",
|
|
18672
|
-
"If the job is hanging or doing the wrong thing, call stop_job (same id) instead of looping forever.",
|
|
18673
|
-
"Then report the result. Do not tell the user you will let them know later."
|
|
18674
|
-
].join(" ");
|
|
18675
|
-
}
|
|
18676
|
-
function formatDeferredDoneContinuePrompt() {
|
|
18677
|
-
return [
|
|
18678
|
-
"You ended the turn after promising to report later, but no detached job is running.",
|
|
18679
|
-
"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).",
|
|
18680
|
-
"Do not say you will let the user know later."
|
|
18681
|
-
].join(" ");
|
|
18682
|
-
}
|
|
18683
|
-
function turnWatchedDetachedJob(parts) {
|
|
18684
|
-
return parts.some((p) => {
|
|
18685
|
-
if (p.type !== "tool") return false;
|
|
18686
|
-
if (/wait_for_job$/i.test(p.name ?? "") || /stop_job$/i.test(p.name ?? "")) return true;
|
|
18687
|
-
const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
|
|
18688
|
-
return /detached-job\.(?:js|cjs)\b/i.test(blob);
|
|
18689
|
-
});
|
|
18690
|
-
}
|
|
18691
|
-
function planJobContinue(opts) {
|
|
18692
|
-
if (opts.isOrchestrator) return { action: "none" };
|
|
18693
|
-
if (opts.agent === "brightsy") return { action: "none" };
|
|
18694
|
-
if (opts.queueLength > 0) return { action: "none" };
|
|
18695
|
-
if (opts.continueCount >= MAX_JOB_CONTINUES) return { action: "none" };
|
|
18696
|
-
const farewell = looksLikeDeferredDonePromise(opts.chatText);
|
|
18697
|
-
if (opts.runningJobIds.length > 0 && (farewell || opts.watchedJob)) {
|
|
18698
|
-
return {
|
|
18699
|
-
action: "wait",
|
|
18700
|
-
jobIds: opts.runningJobIds,
|
|
18701
|
-
prompt: formatJobStillRunningContinuePrompt(opts.runningJobIds)
|
|
18702
|
-
};
|
|
18703
|
-
}
|
|
18704
|
-
if (looksLikeDeferredDonePromise(opts.chatText) && !opts.alreadyNudged) {
|
|
18705
|
-
return { action: "nudge", prompt: formatDeferredDoneContinuePrompt() };
|
|
18706
|
-
}
|
|
18707
|
-
return { action: "none" };
|
|
18708
|
-
}
|
|
18709
|
-
function tailProgress(logFile, maxLines = 12) {
|
|
18710
|
-
if (!(0, import_node_fs48.existsSync)(logFile)) return "(no log yet)";
|
|
18711
|
-
const lines = (0, import_node_fs48.readFileSync)(logFile, "utf8").split("\n");
|
|
18712
|
-
return lines.slice(-maxLines).join("\n");
|
|
18713
|
-
}
|
|
18714
|
-
function lastProgressLine(progress) {
|
|
18715
|
-
const last = progress.split("\n").map((l) => l.trim()).filter((l) => l && l !== "(no log yet)").at(-1);
|
|
18716
|
-
return last ? last.slice(0, 72) : void 0;
|
|
18717
|
-
}
|
|
18718
|
-
function readLogLines(file) {
|
|
18719
|
-
if (!(0, import_node_fs48.existsSync)(file)) return [];
|
|
18720
|
-
const lines = (0, import_node_fs48.readFileSync)(file, "utf8").split("\n");
|
|
18721
|
-
if (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
18722
|
-
return lines;
|
|
18723
|
-
}
|
|
18724
|
-
function takeDelta(logFile, cursorFile) {
|
|
18725
|
-
const lines = readLogLines(logFile);
|
|
18726
|
-
const cursor = readIntFile(cursorFile) ?? 0;
|
|
18727
|
-
const start = Math.min(Math.max(0, cursor), lines.length);
|
|
18728
|
-
return { delta: capJobLogDelta(lines.slice(start).join("\n")), nextCursor: lines.length };
|
|
18729
|
-
}
|
|
18730
|
-
function snapshotJob(dir) {
|
|
18731
|
-
const pid = readIntFile((0, import_node_path48.join)(dir, "pid"));
|
|
18732
|
-
const running = jobTreeAlive(pid);
|
|
18733
|
-
return {
|
|
18734
|
-
pid,
|
|
18735
|
-
running,
|
|
18736
|
-
exitCode: readIntFile((0, import_node_path48.join)(dir, "exit")),
|
|
18737
|
-
log: (0, import_node_path48.join)(dir, "log"),
|
|
18738
|
-
cursor: (0, import_node_path48.join)(dir, "present.cursor"),
|
|
18739
|
-
progress: tailProgress((0, import_node_path48.join)(dir, "log"))
|
|
18740
|
-
};
|
|
18741
|
-
}
|
|
18742
|
-
function toResult(id, snap, extra) {
|
|
18743
|
-
const failed = extra?.failed === true || !snap.running && snap.exitCode != null && snap.exitCode !== 0;
|
|
18744
|
-
const ok = !snap.running && snap.exitCode === 0;
|
|
18745
|
-
const stillRunning = snap.running && !ok;
|
|
18746
|
-
const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
|
|
18747
|
-
try {
|
|
18748
|
-
(0, import_node_fs48.mkdirSync)((0, import_node_path48.dirname)(snap.cursor), { recursive: true });
|
|
18749
|
-
(0, import_node_fs48.writeFileSync)(snap.cursor, `${nextCursor}
|
|
18750
|
-
`);
|
|
18751
|
-
} catch {
|
|
18752
|
-
}
|
|
18753
|
-
const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
|
|
18754
|
-
const progress = extra?.progress ?? snap.progress;
|
|
18755
|
-
return {
|
|
18756
|
-
stillRunning,
|
|
18757
|
-
ok,
|
|
18758
|
-
failed: Boolean(failed && !stillRunning && !ok),
|
|
18759
|
-
status,
|
|
18760
|
-
id,
|
|
18761
|
-
pid: snap.pid,
|
|
18762
|
-
exitCode: snap.exitCode ?? void 0,
|
|
18763
|
-
phase: lastProgressLine(progress),
|
|
18764
|
-
delta,
|
|
18765
|
-
progress,
|
|
18766
|
-
hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
|
|
18767
|
-
};
|
|
18768
|
-
}
|
|
18769
|
-
async function sleepMs(ms) {
|
|
18770
|
-
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
18771
|
-
}
|
|
18772
|
-
async function waitForDetachedJob(cwd, id, opts) {
|
|
18773
|
-
const jobId = sanitizeDetachedJobId(id);
|
|
18774
|
-
const root = cwd.trim() || process.cwd();
|
|
18775
|
-
const dir = resolveJobDir(root, jobId);
|
|
18776
|
-
const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
|
|
18777
|
-
const sleep2 = opts?.sleep ?? sleepMs;
|
|
18778
|
-
if (!(0, import_node_fs48.existsSync)(dir)) {
|
|
18779
|
-
return {
|
|
18780
|
-
stillRunning: false,
|
|
18781
|
-
ok: false,
|
|
18782
|
-
failed: true,
|
|
18783
|
-
status: "failed",
|
|
18784
|
-
id: jobId,
|
|
18785
|
-
delta: "",
|
|
18786
|
-
progress: "No detached job. Start one first.",
|
|
18787
|
-
hint: "Start with detached-job.cjs start <id> -- <command>, then call wait_for_job again."
|
|
18788
|
-
};
|
|
18789
|
-
}
|
|
18790
|
-
const deadline = Date.now() + timeoutMs;
|
|
18791
|
-
let snap = snapshotJob(dir);
|
|
18792
|
-
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
18793
|
-
if (!snap.running && snap.pid == null && snap.progress === "(no log yet)") {
|
|
18794
|
-
return toResult(jobId, snap, {
|
|
18795
|
-
failed: true,
|
|
18796
|
-
progress: "No detached job. Start one first."
|
|
18797
|
-
});
|
|
18798
|
-
}
|
|
18799
|
-
while (Date.now() < deadline) {
|
|
18800
|
-
snap = snapshotJob(dir);
|
|
18801
|
-
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
18802
|
-
if (!snap.running) return toResult(jobId, snap, { failed: true });
|
|
18803
|
-
await sleep2(Math.min(2e3, Math.max(50, deadline - Date.now())));
|
|
18804
|
-
}
|
|
18805
|
-
snap = snapshotJob(dir);
|
|
18806
|
-
return toResult(jobId, snap);
|
|
18807
|
-
}
|
|
18808
|
-
function killProcessTree(pid, signal) {
|
|
18809
|
-
if (!Number.isInteger(pid) || pid <= 0) return;
|
|
18810
|
-
if (process.platform !== "win32") {
|
|
18811
|
-
try {
|
|
18812
|
-
process.kill(-pid, signal);
|
|
18813
|
-
return;
|
|
18814
|
-
} catch {
|
|
18815
|
-
}
|
|
18816
|
-
}
|
|
18817
|
-
try {
|
|
18818
|
-
process.kill(pid, signal);
|
|
18819
|
-
} catch {
|
|
18820
|
-
}
|
|
18821
|
-
}
|
|
18822
|
-
function appendJobLog(logFile, line) {
|
|
18823
|
-
try {
|
|
18824
|
-
(0, import_node_fs48.mkdirSync)((0, import_node_path48.dirname)(logFile), { recursive: true });
|
|
18825
|
-
const body = line.endsWith("\n") ? line : `${line}
|
|
18826
|
-
`;
|
|
18827
|
-
(0, import_node_fs48.appendFileSync)(logFile, body);
|
|
18828
|
-
} catch {
|
|
18829
|
-
}
|
|
18830
|
-
}
|
|
18831
|
-
async function stopDetachedJob(cwd, id, opts) {
|
|
18832
|
-
const jobId = sanitizeDetachedJobId(id);
|
|
18833
|
-
const root = cwd.trim() || process.cwd();
|
|
18834
|
-
const dir = resolveJobDir(root, jobId);
|
|
18835
|
-
const sleep2 = opts?.sleep ?? sleepMs;
|
|
18836
|
-
const why = opts?.reason?.trim() ?? "";
|
|
18837
|
-
if (!(0, import_node_fs48.existsSync)(dir)) {
|
|
18838
|
-
return {
|
|
18839
|
-
stillRunning: false,
|
|
18840
|
-
ok: false,
|
|
18841
|
-
failed: true,
|
|
18842
|
-
status: "failed",
|
|
18843
|
-
id: jobId,
|
|
18844
|
-
delta: "",
|
|
18845
|
-
progress: "No detached job. Start one first.",
|
|
18846
|
-
hint: "Start with detached-job.cjs start <id> -- <command>, then wait_for_job or stop_job.",
|
|
18847
|
-
stopped: false,
|
|
18848
|
-
reason: "not-found"
|
|
18849
|
-
};
|
|
18850
|
-
}
|
|
18851
|
-
let snap = snapshotJob(dir);
|
|
18852
|
-
if (!snap.running) {
|
|
18853
|
-
const result2 = toResult(jobId, snap);
|
|
18854
|
-
return {
|
|
18855
|
-
...result2,
|
|
18856
|
-
stopped: false,
|
|
18857
|
-
reason: "not-running",
|
|
18858
|
-
hint: "That job is already finished. Read the log; do not wait again unless you start a new command."
|
|
18859
|
-
};
|
|
18860
|
-
}
|
|
18861
|
-
appendJobLog(snap.log, `$ stop${why ? `: ${why}` : ""}`);
|
|
18862
|
-
const targetPid = snap.pid;
|
|
18863
|
-
if (targetPid != null) killProcessTree(targetPid, "SIGTERM");
|
|
18864
|
-
const graceMs = Number.isFinite(opts?.graceMs) ? Math.max(50, opts.graceMs) : 2e3;
|
|
18865
|
-
const deadline = Date.now() + graceMs;
|
|
18866
|
-
while (Date.now() < deadline && jobTreeAlive(targetPid)) {
|
|
18867
|
-
await sleep2(Math.min(100, Math.max(20, deadline - Date.now())));
|
|
18868
|
-
}
|
|
18869
|
-
if (targetPid != null) {
|
|
18870
|
-
killProcessTree(targetPid, "SIGKILL");
|
|
18871
|
-
await sleep2(100);
|
|
18872
|
-
}
|
|
18873
|
-
const exitFile = (0, import_node_path48.join)(dir, "exit");
|
|
18874
|
-
if (!(0, import_node_fs48.existsSync)(exitFile)) {
|
|
18875
|
-
try {
|
|
18876
|
-
(0, import_node_fs48.writeFileSync)(exitFile, "1\n");
|
|
18877
|
-
} catch {
|
|
18878
|
-
}
|
|
18879
|
-
}
|
|
18880
|
-
snap = snapshotJob(dir);
|
|
18881
|
-
const stillRunning = jobTreeAlive(targetPid) || snap.running;
|
|
18882
|
-
const result = toResult(jobId, snap, { failed: true });
|
|
18883
|
-
const stopped = !stillRunning;
|
|
18884
|
-
return {
|
|
18885
|
-
...result,
|
|
18886
|
-
stillRunning,
|
|
18887
|
-
ok: false,
|
|
18888
|
-
failed: true,
|
|
18889
|
-
status: stillRunning ? "running" : "failed",
|
|
18890
|
-
stopped,
|
|
18891
|
-
reason: stopped ? "stopped" : "still-running",
|
|
18892
|
-
stopReason: why || void 0,
|
|
18893
|
-
hint: stopped ? MCP_STOP_JOB_HINT : MCP_WAIT_JOB_STILL_RUNNING_HINT
|
|
18894
|
-
};
|
|
18895
|
-
}
|
|
18896
|
-
var import_node_fs48, import_node_path48, MAX_JOB_CONTINUES, MAX_JOB_DELTA_LINES, MAX_JOB_DELTA_CHARS, DEFERRED_DONE_MAX_CHARS, GH_WATCH_REFRESH, MCP_WAIT_JOB_STILL_RUNNING_HINT, MCP_STOP_JOB_HINT, JOB_ID_RE;
|
|
18897
|
-
var init_wait_for_job = __esm({
|
|
18898
|
-
"src/mcp/wait-for-job.ts"() {
|
|
18899
|
-
"use strict";
|
|
18900
|
-
import_node_fs48 = require("fs");
|
|
18901
|
-
import_node_path48 = require("path");
|
|
18902
|
-
init_workspace_scratch();
|
|
18903
|
-
init_wait_for_turn();
|
|
18904
|
-
MAX_JOB_CONTINUES = 8;
|
|
18905
|
-
MAX_JOB_DELTA_LINES = 80;
|
|
18906
|
-
MAX_JOB_DELTA_CHARS = 4e3;
|
|
18907
|
-
DEFERRED_DONE_MAX_CHARS = 480;
|
|
18908
|
-
GH_WATCH_REFRESH = "Refreshing run status every 3 seconds.";
|
|
18909
|
-
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.";
|
|
18910
|
-
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.";
|
|
18911
|
-
JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
18912
|
-
}
|
|
18913
|
-
});
|
|
18914
|
-
|
|
18915
18988
|
// src/detect/detect.ts
|
|
18916
18989
|
async function detectAgents() {
|
|
18917
18990
|
ensureAgentPath();
|
|
@@ -23694,6 +23767,13 @@ function isPresentPlanToolName(name) {
|
|
|
23694
23767
|
if (!name) return false;
|
|
23695
23768
|
return /present_plan$/i.test(name) || /^mcp__sideboard__present_plan$/i.test(name);
|
|
23696
23769
|
}
|
|
23770
|
+
function threadMayPresentPlan(thread) {
|
|
23771
|
+
return Boolean(thread?.planMode);
|
|
23772
|
+
}
|
|
23773
|
+
function shouldRegisterPresentPlan(thread) {
|
|
23774
|
+
if (thread == null) return true;
|
|
23775
|
+
return threadMayPresentPlan(thread);
|
|
23776
|
+
}
|
|
23697
23777
|
function extractPresentedPlan(parts) {
|
|
23698
23778
|
if (!parts?.length) return null;
|
|
23699
23779
|
for (let i = parts.length - 1; i >= 0; i--) {
|
|
@@ -23731,7 +23811,7 @@ function resolvePlanMarkdown(opts) {
|
|
|
23731
23811
|
}
|
|
23732
23812
|
return null;
|
|
23733
23813
|
}
|
|
23734
|
-
var PLAN_FILE_REL, PLAN_FILE_NAME, LEGACY_PLAN_FILE_REL;
|
|
23814
|
+
var PLAN_FILE_REL, PLAN_FILE_NAME, LEGACY_PLAN_FILE_REL, PRESENT_PLAN_TOOL_DESCRIPTION, PRESENT_PLAN_REQUIRES_PLAN_MODE;
|
|
23735
23815
|
var init_plan_present = __esm({
|
|
23736
23816
|
"src/plan/plan-present.ts"() {
|
|
23737
23817
|
"use strict";
|
|
@@ -23739,6 +23819,8 @@ var init_plan_present = __esm({
|
|
|
23739
23819
|
PLAN_FILE_REL = `${ATTACHMENTS_DIR}/plan.md`;
|
|
23740
23820
|
PLAN_FILE_NAME = "plan.md";
|
|
23741
23821
|
LEGACY_PLAN_FILE_REL = ".sideboard/plan.md";
|
|
23822
|
+
PRESENT_PLAN_TOOL_DESCRIPTION = "Only when Sideboard Plan mode is on (composer Plan chip). Saves the implementation plan to .context/attachments/plan.md and shows Approve / Hand off / Copy in chat. Pass the full plan body in content. Then Claude should call ExitPlanMode. If Plan mode is off, do not call this \u2014 write the plan in chat.";
|
|
23823
|
+
PRESENT_PLAN_REQUIRES_PLAN_MODE = "Plan mode is off. Do not call present_plan or ExitPlanMode. Write the plan in chat. The user can turn Plan on in the composer.";
|
|
23742
23824
|
}
|
|
23743
23825
|
});
|
|
23744
23826
|
|
|
@@ -23748,11 +23830,15 @@ __export(plan_file_exports, {
|
|
|
23748
23830
|
LEGACY_PLAN_FILE_REL: () => LEGACY_PLAN_FILE_REL,
|
|
23749
23831
|
PLAN_FILE_NAME: () => PLAN_FILE_NAME,
|
|
23750
23832
|
PLAN_FILE_REL: () => PLAN_FILE_REL,
|
|
23833
|
+
PRESENT_PLAN_REQUIRES_PLAN_MODE: () => PRESENT_PLAN_REQUIRES_PLAN_MODE,
|
|
23834
|
+
PRESENT_PLAN_TOOL_DESCRIPTION: () => PRESENT_PLAN_TOOL_DESCRIPTION,
|
|
23751
23835
|
extractPresentedPlan: () => extractPresentedPlan,
|
|
23752
23836
|
isPresentPlanToolName: () => isPresentPlanToolName,
|
|
23753
23837
|
planFileAbs: () => planFileAbs,
|
|
23754
23838
|
readPlanFile: () => readPlanFile,
|
|
23755
23839
|
resolvePlanMarkdown: () => resolvePlanMarkdown,
|
|
23840
|
+
shouldRegisterPresentPlan: () => shouldRegisterPresentPlan,
|
|
23841
|
+
threadMayPresentPlan: () => threadMayPresentPlan,
|
|
23756
23842
|
writePlanFile: () => writePlanFile
|
|
23757
23843
|
});
|
|
23758
23844
|
function ensureAttachmentsGitignore(worktreePath) {
|
|
@@ -24888,7 +24974,8 @@ var init_orchestrator = __esm({
|
|
|
24888
24974
|
if (compact.didCompact) {
|
|
24889
24975
|
thread = updateThread(threadId, {
|
|
24890
24976
|
messages: compact.thread.messages,
|
|
24891
|
-
sessionId: compact.thread.sessionId
|
|
24977
|
+
sessionId: compact.thread.sessionId,
|
|
24978
|
+
compactedMessages: compact.thread.compactedMessages
|
|
24892
24979
|
});
|
|
24893
24980
|
this.emit({
|
|
24894
24981
|
type: "context_compacted",
|
|
@@ -24902,9 +24989,6 @@ var init_orchestrator = __esm({
|
|
|
24902
24989
|
this.emit({ type: "error", threadId, message: `context compact failed: ${message}` });
|
|
24903
24990
|
thread = this.requireThread(threadId);
|
|
24904
24991
|
}
|
|
24905
|
-
if (this.stoppedTurns.has(threadId)) {
|
|
24906
|
-
return;
|
|
24907
|
-
}
|
|
24908
24992
|
const promptText = typeof prompt === "string" ? prompt : "";
|
|
24909
24993
|
const autoContinue = isJobContinuePrompt(promptText) || promptText.startsWith("The previous agent process ended before it finished.");
|
|
24910
24994
|
const parked = takePendingTurnAttachments(thread);
|
|
@@ -24921,6 +25005,14 @@ var init_orchestrator = __esm({
|
|
|
24921
25005
|
agent: thread.agent,
|
|
24922
25006
|
orch: isOrchestratorThread(thread)
|
|
24923
25007
|
});
|
|
25008
|
+
if (this.stoppedTurns.has(threadId)) {
|
|
25009
|
+
const stopped = writeLiveStatus(threadId, "stopped");
|
|
25010
|
+
if (stopped?.status === "stopped") {
|
|
25011
|
+
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
25012
|
+
}
|
|
25013
|
+
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
25014
|
+
return;
|
|
25015
|
+
}
|
|
24924
25016
|
thread = this.requireThread(threadId);
|
|
24925
25017
|
const gitPrompt = expandCanonicalGitRequest(promptText);
|
|
24926
25018
|
const { agentPrompt: expandedPrompt } = expandComposerPrompt(
|
|
@@ -24979,7 +25071,7 @@ var init_orchestrator = __esm({
|
|
|
24979
25071
|
].filter(Boolean).join("\n\n") || void 0;
|
|
24980
25072
|
const prGateDirective = thread.agent !== "brightsy" && !isOrchestratorThread(thread) && mentionsPrGoal(promptText) ? formatPrGateDirective() : null;
|
|
24981
25073
|
const agentPrompt = [
|
|
24982
|
-
thread.planMode
|
|
25074
|
+
planModeTurnInstruction(thread.planMode),
|
|
24983
25075
|
orchestrationReminder,
|
|
24984
25076
|
prGateDirective,
|
|
24985
25077
|
injectedNoticeContext,
|
|
@@ -25482,6 +25574,9 @@ var init_orchestrator = __esm({
|
|
|
25482
25574
|
}
|
|
25483
25575
|
const preferredPorts = active?.ports?.length ? active.ports : active?.port != null ? [active.port] : void 0;
|
|
25484
25576
|
if (active && !existing) {
|
|
25577
|
+
if (typeof active.pid === "number") {
|
|
25578
|
+
killDetachedRunTree(active.pid, thread.worktreePath);
|
|
25579
|
+
}
|
|
25485
25580
|
killListenersOnPorts(collectActiveRunPorts([active]));
|
|
25486
25581
|
this.syncWorktreeRuns(
|
|
25487
25582
|
thread.worktreePath,
|
|
@@ -25554,7 +25649,8 @@ var init_orchestrator = __esm({
|
|
|
25554
25649
|
scriptName: resolvedName,
|
|
25555
25650
|
port: handle.port,
|
|
25556
25651
|
ports: handle.ports,
|
|
25557
|
-
startedAt
|
|
25652
|
+
startedAt,
|
|
25653
|
+
...typeof handle.pid === "number" && handle.pid > 1 ? { pid: handle.pid } : {}
|
|
25558
25654
|
};
|
|
25559
25655
|
const nextRuns = [
|
|
25560
25656
|
...latestShared.activeRuns.filter((r) => r.scriptName !== resolvedName),
|
|
@@ -25700,6 +25796,9 @@ var init_orchestrator = __esm({
|
|
|
25700
25796
|
}
|
|
25701
25797
|
const run2 = shared.activeRuns.find((r) => r.scriptName === scriptName);
|
|
25702
25798
|
if (!hadHandle2 && run2) {
|
|
25799
|
+
if (typeof run2.pid === "number") {
|
|
25800
|
+
killDetachedRunTree(run2.pid, thread.worktreePath);
|
|
25801
|
+
}
|
|
25703
25802
|
killListenersOnPorts(collectActiveRunPorts([run2]));
|
|
25704
25803
|
}
|
|
25705
25804
|
const remaining = shared.activeRuns.filter((r) => r.scriptName !== scriptName);
|
|
@@ -25721,6 +25820,11 @@ var init_orchestrator = __esm({
|
|
|
25721
25820
|
}
|
|
25722
25821
|
}
|
|
25723
25822
|
if (!hadHandle && shared.activeRuns.length > 0) {
|
|
25823
|
+
for (const run2 of shared.activeRuns) {
|
|
25824
|
+
if (typeof run2.pid === "number") {
|
|
25825
|
+
killDetachedRunTree(run2.pid, thread.worktreePath);
|
|
25826
|
+
}
|
|
25827
|
+
}
|
|
25724
25828
|
killListenersOnPorts(collectActiveRunPorts(shared.activeRuns));
|
|
25725
25829
|
}
|
|
25726
25830
|
this.syncWorktreeRuns(thread.worktreePath, [], null);
|
|
@@ -25762,6 +25866,11 @@ var init_orchestrator = __esm({
|
|
|
25762
25866
|
(k) => isWorktreeRunProcessKey(k, thread.worktreePath) || siblings.some((t) => k.startsWith(`${t.id}:run:`) || k === `${t.id}:dev`)
|
|
25763
25867
|
);
|
|
25764
25868
|
if (!force && hasLiveHandle) continue;
|
|
25869
|
+
for (const run2 of shared.activeRuns) {
|
|
25870
|
+
if (typeof run2.pid === "number") {
|
|
25871
|
+
killDetachedRunTree(run2.pid, thread.worktreePath);
|
|
25872
|
+
}
|
|
25873
|
+
}
|
|
25765
25874
|
killListenersOnPorts(collectActiveRunPorts(shared.activeRuns));
|
|
25766
25875
|
if (shared.devPort != null) {
|
|
25767
25876
|
killListenersOnPorts([shared.devPort]);
|
|
@@ -26896,8 +27005,11 @@ __export(index_exports, {
|
|
|
26896
27005
|
PLAN_FILE_NAME: () => PLAN_FILE_NAME,
|
|
26897
27006
|
PLAN_FILE_REL: () => PLAN_FILE_REL,
|
|
26898
27007
|
PLAN_MODE_INSTRUCTION: () => PLAN_MODE_INSTRUCTION,
|
|
27008
|
+
PLAN_MODE_OFF_INSTRUCTION: () => PLAN_MODE_OFF_INSTRUCTION,
|
|
26899
27009
|
PLAN_QUESTION_ANSWERS_PREFIX: () => PLAN_QUESTION_ANSWERS_PREFIX,
|
|
26900
27010
|
PORT_RANGE_SIZE: () => PORT_RANGE_SIZE,
|
|
27011
|
+
PRESENT_PLAN_REQUIRES_PLAN_MODE: () => PRESENT_PLAN_REQUIRES_PLAN_MODE,
|
|
27012
|
+
PRESENT_PLAN_TOOL_DESCRIPTION: () => PRESENT_PLAN_TOOL_DESCRIPTION,
|
|
26901
27013
|
PROFILE_NOTES_MAX: () => PROFILE_NOTES_MAX,
|
|
26902
27014
|
PR_DETAILS_JSON_FIELDS: () => PR_DETAILS_JSON_FIELDS,
|
|
26903
27015
|
REPO_REVIEW_NAME: () => REPO_REVIEW_NAME,
|
|
@@ -26968,6 +27080,7 @@ __export(index_exports, {
|
|
|
26968
27080
|
annotateDevAccessRuns: () => annotateDevAccessRuns,
|
|
26969
27081
|
appDataDir: () => appDataDir,
|
|
26970
27082
|
appSettingsPath: () => appSettingsPath,
|
|
27083
|
+
appendCompactedMessages: () => appendCompactedMessages,
|
|
26971
27084
|
appendIndexedGitConfig: () => appendIndexedGitConfig,
|
|
26972
27085
|
appendMessage: () => appendMessage,
|
|
26973
27086
|
appendQueuedItem: () => appendQueuedItem,
|
|
@@ -27055,6 +27168,9 @@ __export(index_exports, {
|
|
|
27055
27168
|
commentGitHubIssue: () => commentGitHubIssue,
|
|
27056
27169
|
commentLinearIssue: () => commentLinearIssue,
|
|
27057
27170
|
commitAll: () => commitAll,
|
|
27171
|
+
compactArchiveProse: () => compactArchiveProse,
|
|
27172
|
+
compactPartsForSummaryMessage: () => compactPartsForSummaryMessage,
|
|
27173
|
+
compactSummaryPart: () => compactSummaryPart,
|
|
27058
27174
|
computeNextRunAt: () => computeNextRunAt,
|
|
27059
27175
|
conductorBundledBinDir: () => conductorBundledBinDir,
|
|
27060
27176
|
conductorDbPath: () => conductorDbPath,
|
|
@@ -27361,6 +27477,7 @@ __export(index_exports, {
|
|
|
27361
27477
|
isTerminalTaskState: () => isTerminalTaskState,
|
|
27362
27478
|
isThinkingEffort: () => isThinkingEffort,
|
|
27363
27479
|
isThisProcessDesktopHost: () => isThisProcessDesktopHost,
|
|
27480
|
+
isThisWorktreeListenerCwd: () => isThisWorktreeListenerCwd,
|
|
27364
27481
|
isThreadCaffeinated: () => isThreadCaffeinated,
|
|
27365
27482
|
isThreadRecordFile: () => isThreadRecordFile,
|
|
27366
27483
|
isUnclaimedRunScriptRequest: () => isUnclaimedRunScriptRequest,
|
|
@@ -27370,6 +27487,7 @@ __export(index_exports, {
|
|
|
27370
27487
|
issueMatchesAssignee: () => issueMatchesAssignee,
|
|
27371
27488
|
issueSourceLabel: () => issueSourceLabel,
|
|
27372
27489
|
issueTicketFromThread: () => issueTicketFromThread,
|
|
27490
|
+
killDetachedRunTree: () => killDetachedRunTree,
|
|
27373
27491
|
killListenersOnPorts: () => killListenersOnPorts,
|
|
27374
27492
|
killStaleWorktreeListeners: () => killStaleWorktreeListeners,
|
|
27375
27493
|
killableListenerPids: () => killableListenerPids,
|
|
@@ -27452,6 +27570,7 @@ __export(index_exports, {
|
|
|
27452
27570
|
mergeSideboardIntoMcpServersJson: () => mergeSideboardIntoMcpServersJson,
|
|
27453
27571
|
mergeUsage: () => mergeUsage,
|
|
27454
27572
|
messagePartParentId: () => messagePartParentId,
|
|
27573
|
+
messagesForCompactArchive: () => messagesForCompactArchive,
|
|
27455
27574
|
messagesSinceLastBrightsyContextSummary: () => messagesSinceLastBrightsyContextSummary,
|
|
27456
27575
|
moveQueuedItemToFront: () => moveQueuedItemToFront,
|
|
27457
27576
|
needsCoordinatorAction: () => needsCoordinatorAction,
|
|
@@ -27511,6 +27630,7 @@ __export(index_exports, {
|
|
|
27511
27630
|
planFileAbs: () => planFileAbs,
|
|
27512
27631
|
planHistoryAgePurge: () => planHistoryAgePurge,
|
|
27513
27632
|
planHistoryRetention: () => planHistoryRetention,
|
|
27633
|
+
planModeTurnInstruction: () => planModeTurnInstruction,
|
|
27514
27634
|
planQuestionsSignature: () => planQuestionsSignature,
|
|
27515
27635
|
pnpmFilterPackage: () => pnpmFilterPackage,
|
|
27516
27636
|
pollSlackOutboundWatches: () => pollSlackOutboundWatches,
|
|
@@ -27638,6 +27758,7 @@ __export(index_exports, {
|
|
|
27638
27758
|
shouldInjectBrightsyMcp: () => shouldInjectBrightsyMcp,
|
|
27639
27759
|
shouldNotifyOrchestrator: () => shouldNotifyOrchestrator,
|
|
27640
27760
|
shouldRefreshReviewRequestTemplate: () => shouldRefreshReviewRequestTemplate,
|
|
27761
|
+
shouldRegisterPresentPlan: () => shouldRegisterPresentPlan,
|
|
27641
27762
|
shouldRemoveWorktreeOnTeardown: () => shouldRemoveWorktreeOnTeardown,
|
|
27642
27763
|
shouldResetSessionForOccupancy: () => shouldResetSessionForOccupancy,
|
|
27643
27764
|
shouldRunHistoryCleanup: () => shouldRunHistoryCleanup,
|
|
@@ -27699,6 +27820,7 @@ __export(index_exports, {
|
|
|
27699
27820
|
threadHasCompactedContext: () => threadHasCompactedContext,
|
|
27700
27821
|
threadLivePath: () => threadLivePath,
|
|
27701
27822
|
threadLockPath: () => threadLockPath,
|
|
27823
|
+
threadMayPresentPlan: () => threadMayPresentPlan,
|
|
27702
27824
|
threadRequestsBrightsyMcp: () => threadRequestsBrightsyMcp,
|
|
27703
27825
|
threadsDir: () => threadsDir,
|
|
27704
27826
|
threadsSharingWorktree: () => threadsSharingWorktree,
|
|
@@ -27872,7 +27994,8 @@ function slimThreadForUiList(thread) {
|
|
|
27872
27994
|
attachments: [],
|
|
27873
27995
|
pendingTurnAttachments: [],
|
|
27874
27996
|
queueAttachments: [],
|
|
27875
|
-
messages: slimMessagesForUiList(messages, thread.updatedAt)
|
|
27997
|
+
messages: slimMessagesForUiList(messages, thread.updatedAt),
|
|
27998
|
+
compactedMessages: void 0
|
|
27876
27999
|
};
|
|
27877
28000
|
}
|
|
27878
28001
|
function slimMessagesForUiList(messages, fallbackTs) {
|
|
@@ -30426,6 +30549,7 @@ function mcpArchiveBlockedReason(thread) {
|
|
|
30426
30549
|
|
|
30427
30550
|
// src/mcp/server.ts
|
|
30428
30551
|
init_profile();
|
|
30552
|
+
init_plan_present();
|
|
30429
30553
|
|
|
30430
30554
|
// src/mcp/run-script-ref.ts
|
|
30431
30555
|
init_chat_tabs();
|
|
@@ -33338,31 +33462,40 @@ async function startMcpServer() {
|
|
|
33338
33462
|
return mcpJson(payload);
|
|
33339
33463
|
}
|
|
33340
33464
|
);
|
|
33341
|
-
|
|
33342
|
-
|
|
33343
|
-
|
|
33344
|
-
|
|
33345
|
-
|
|
33346
|
-
|
|
33347
|
-
|
|
33348
|
-
|
|
33349
|
-
|
|
33350
|
-
|
|
33351
|
-
|
|
33352
|
-
|
|
33353
|
-
const
|
|
33465
|
+
const callingThreadId = process.env[SIDEBOARD_THREAD_ID_ENV]?.trim() || "";
|
|
33466
|
+
const callingThread = callingThreadId ? orch.getThread(callingThreadId) : null;
|
|
33467
|
+
if (shouldRegisterPresentPlan(callingThread)) {
|
|
33468
|
+
server.tool(
|
|
33469
|
+
"present_plan",
|
|
33470
|
+
PRESENT_PLAN_TOOL_DESCRIPTION,
|
|
33471
|
+
{
|
|
33472
|
+
title: import_zod9.z.string().optional().describe("Short plan title (defaults to Plan)"),
|
|
33473
|
+
content: import_zod9.z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
|
|
33474
|
+
thread_id: import_zod9.z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
|
|
33475
|
+
},
|
|
33476
|
+
async ({ title, content, thread_id }) => {
|
|
33477
|
+
const { writePlanFile: writePlanFile2 } = await Promise.resolve().then(() => (init_plan_file(), plan_file_exports));
|
|
33478
|
+
const ref = thread_id?.trim() || callingThreadId;
|
|
33479
|
+
const t = ref ? orch.getThread(ref) : null;
|
|
33480
|
+
if (t && !threadMayPresentPlan(t)) {
|
|
33481
|
+
return mcpJson(
|
|
33482
|
+
{ ok: false, error: PRESENT_PLAN_REQUIRES_PLAN_MODE },
|
|
33483
|
+
true
|
|
33484
|
+
);
|
|
33485
|
+
}
|
|
33486
|
+
let root = process.cwd();
|
|
33354
33487
|
if (t?.worktreePath?.trim()) root = t.worktreePath;
|
|
33488
|
+
const path2 = writePlanFile2(root, content);
|
|
33489
|
+
const payload = {
|
|
33490
|
+
ok: true,
|
|
33491
|
+
path: path2,
|
|
33492
|
+
title: title?.trim() || "Plan",
|
|
33493
|
+
message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
|
|
33494
|
+
};
|
|
33495
|
+
return mcpJson(payload);
|
|
33355
33496
|
}
|
|
33356
|
-
|
|
33357
|
-
|
|
33358
|
-
ok: true,
|
|
33359
|
-
path: path2,
|
|
33360
|
-
title: title?.trim() || "Plan",
|
|
33361
|
-
message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
|
|
33362
|
-
};
|
|
33363
|
-
return mcpJson(payload);
|
|
33364
|
-
}
|
|
33365
|
-
);
|
|
33497
|
+
);
|
|
33498
|
+
}
|
|
33366
33499
|
server.tool(
|
|
33367
33500
|
"present_schema",
|
|
33368
33501
|
"Open Sideboard\u2019s schema-driven side column (filterable table and/or form) when the user needs to filter, edit, publish, or persist records. Do not call this just to re-display rows you already wrote as a markdown table. If the user asks for an editable / interactive table, call this even if chat already showed those rows. Pass JSON Schema + optional schemaUi. Prefer datasource=inline with embedded resource/records. Use datasource=brightsy with resource_id only when the user is logged into Brightsy.",
|
|
@@ -36699,8 +36832,11 @@ async function startSlackRelayServer(opts) {
|
|
|
36699
36832
|
PLAN_FILE_NAME,
|
|
36700
36833
|
PLAN_FILE_REL,
|
|
36701
36834
|
PLAN_MODE_INSTRUCTION,
|
|
36835
|
+
PLAN_MODE_OFF_INSTRUCTION,
|
|
36702
36836
|
PLAN_QUESTION_ANSWERS_PREFIX,
|
|
36703
36837
|
PORT_RANGE_SIZE,
|
|
36838
|
+
PRESENT_PLAN_REQUIRES_PLAN_MODE,
|
|
36839
|
+
PRESENT_PLAN_TOOL_DESCRIPTION,
|
|
36704
36840
|
PROFILE_NOTES_MAX,
|
|
36705
36841
|
PR_DETAILS_JSON_FIELDS,
|
|
36706
36842
|
REPO_REVIEW_NAME,
|
|
@@ -36771,6 +36907,7 @@ async function startSlackRelayServer(opts) {
|
|
|
36771
36907
|
annotateDevAccessRuns,
|
|
36772
36908
|
appDataDir,
|
|
36773
36909
|
appSettingsPath,
|
|
36910
|
+
appendCompactedMessages,
|
|
36774
36911
|
appendIndexedGitConfig,
|
|
36775
36912
|
appendMessage,
|
|
36776
36913
|
appendQueuedItem,
|
|
@@ -36858,6 +36995,9 @@ async function startSlackRelayServer(opts) {
|
|
|
36858
36995
|
commentGitHubIssue,
|
|
36859
36996
|
commentLinearIssue,
|
|
36860
36997
|
commitAll,
|
|
36998
|
+
compactArchiveProse,
|
|
36999
|
+
compactPartsForSummaryMessage,
|
|
37000
|
+
compactSummaryPart,
|
|
36861
37001
|
computeNextRunAt,
|
|
36862
37002
|
conductorBundledBinDir,
|
|
36863
37003
|
conductorDbPath,
|
|
@@ -37164,6 +37304,7 @@ async function startSlackRelayServer(opts) {
|
|
|
37164
37304
|
isTerminalTaskState,
|
|
37165
37305
|
isThinkingEffort,
|
|
37166
37306
|
isThisProcessDesktopHost,
|
|
37307
|
+
isThisWorktreeListenerCwd,
|
|
37167
37308
|
isThreadCaffeinated,
|
|
37168
37309
|
isThreadRecordFile,
|
|
37169
37310
|
isUnclaimedRunScriptRequest,
|
|
@@ -37173,6 +37314,7 @@ async function startSlackRelayServer(opts) {
|
|
|
37173
37314
|
issueMatchesAssignee,
|
|
37174
37315
|
issueSourceLabel,
|
|
37175
37316
|
issueTicketFromThread,
|
|
37317
|
+
killDetachedRunTree,
|
|
37176
37318
|
killListenersOnPorts,
|
|
37177
37319
|
killStaleWorktreeListeners,
|
|
37178
37320
|
killableListenerPids,
|
|
@@ -37255,6 +37397,7 @@ async function startSlackRelayServer(opts) {
|
|
|
37255
37397
|
mergeSideboardIntoMcpServersJson,
|
|
37256
37398
|
mergeUsage,
|
|
37257
37399
|
messagePartParentId,
|
|
37400
|
+
messagesForCompactArchive,
|
|
37258
37401
|
messagesSinceLastBrightsyContextSummary,
|
|
37259
37402
|
moveQueuedItemToFront,
|
|
37260
37403
|
needsCoordinatorAction,
|
|
@@ -37314,6 +37457,7 @@ async function startSlackRelayServer(opts) {
|
|
|
37314
37457
|
planFileAbs,
|
|
37315
37458
|
planHistoryAgePurge,
|
|
37316
37459
|
planHistoryRetention,
|
|
37460
|
+
planModeTurnInstruction,
|
|
37317
37461
|
planQuestionsSignature,
|
|
37318
37462
|
pnpmFilterPackage,
|
|
37319
37463
|
pollSlackOutboundWatches,
|
|
@@ -37441,6 +37585,7 @@ async function startSlackRelayServer(opts) {
|
|
|
37441
37585
|
shouldInjectBrightsyMcp,
|
|
37442
37586
|
shouldNotifyOrchestrator,
|
|
37443
37587
|
shouldRefreshReviewRequestTemplate,
|
|
37588
|
+
shouldRegisterPresentPlan,
|
|
37444
37589
|
shouldRemoveWorktreeOnTeardown,
|
|
37445
37590
|
shouldResetSessionForOccupancy,
|
|
37446
37591
|
shouldRunHistoryCleanup,
|
|
@@ -37502,6 +37647,7 @@ async function startSlackRelayServer(opts) {
|
|
|
37502
37647
|
threadHasCompactedContext,
|
|
37503
37648
|
threadLivePath,
|
|
37504
37649
|
threadLockPath,
|
|
37650
|
+
threadMayPresentPlan,
|
|
37505
37651
|
threadRequestsBrightsyMcp,
|
|
37506
37652
|
threadsDir,
|
|
37507
37653
|
threadsSharingWorktree,
|