@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/mcp/run-stdio.cjs
CHANGED
|
@@ -1660,7 +1660,8 @@ function normalizeThread(raw) {
|
|
|
1660
1660
|
runScriptRequest: normalizeRunScriptRequest(raw.runScriptRequest),
|
|
1661
1661
|
quotaResumeAt: raw.quotaResumeAt ?? null,
|
|
1662
1662
|
quotaContinuedFromId: raw.quotaContinuedFromId ?? null,
|
|
1663
|
-
archivedAt: typeof raw.archivedAt === "string" && raw.archivedAt.trim() ? raw.archivedAt.trim() : void 0
|
|
1663
|
+
archivedAt: typeof raw.archivedAt === "string" && raw.archivedAt.trim() ? raw.archivedAt.trim() : void 0,
|
|
1664
|
+
compactedMessages: Array.isArray(raw.compactedMessages) ? raw.compactedMessages : void 0
|
|
1664
1665
|
};
|
|
1665
1666
|
}
|
|
1666
1667
|
function createEmptyThread(partial) {
|
|
@@ -3400,6 +3401,7 @@ function archivedUpdatedMs(thread) {
|
|
|
3400
3401
|
}
|
|
3401
3402
|
function isHistoryRetentionStub(thread) {
|
|
3402
3403
|
if ((thread.attachments?.length ?? 0) > 0) return false;
|
|
3404
|
+
if ((thread.compactedMessages?.length ?? 0) > 0) return false;
|
|
3403
3405
|
const messages = thread.messages ?? [];
|
|
3404
3406
|
if (messages.length === 0) return true;
|
|
3405
3407
|
if (messages.length > 1) return false;
|
|
@@ -9704,6 +9706,10 @@ var init_node_launch = __esm({
|
|
|
9704
9706
|
// src/agents/injected-mcp.ts
|
|
9705
9707
|
function sideboardWorktreeAllowedTools(opts) {
|
|
9706
9708
|
const out = [...SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS];
|
|
9709
|
+
if (!opts?.planMode) {
|
|
9710
|
+
const idx = out.indexOf("mcp__sideboard__present_plan");
|
|
9711
|
+
if (idx >= 0) out.splice(idx, 1);
|
|
9712
|
+
}
|
|
9707
9713
|
if (opts?.github !== false) out.push(...SIDEBOARD_GITHUB_MCP_ALLOWED_TOOLS);
|
|
9708
9714
|
if (opts?.linear) out.push(...SIDEBOARD_LINEAR_MCP_ALLOWED_TOOLS);
|
|
9709
9715
|
if (opts?.abletime) out.push(...SIDEBOARD_ABLETIME_MCP_ALLOWED_TOOLS);
|
|
@@ -10564,6 +10570,9 @@ function skillToolFromTextPart(part, id) {
|
|
|
10564
10570
|
updatedAt: Date.now()
|
|
10565
10571
|
};
|
|
10566
10572
|
}
|
|
10573
|
+
function compactSummaryPart(text7, id = "compact-summary") {
|
|
10574
|
+
return compactToolPart(text7, id, { status: "done" });
|
|
10575
|
+
}
|
|
10567
10576
|
function compactToolPart(text7, id, opts) {
|
|
10568
10577
|
const input = { trigger: opts?.trigger ?? "auto" };
|
|
10569
10578
|
return withPartTimes(
|
|
@@ -10841,6 +10850,17 @@ function partsToAssistantText(parts) {
|
|
|
10841
10850
|
(p) => p.type === "text" && !messagePartParentId(p)
|
|
10842
10851
|
).map((p) => p.text).join("").trim();
|
|
10843
10852
|
}
|
|
10853
|
+
function lastAssistantMessageText(parts) {
|
|
10854
|
+
if (!parts?.length) return "";
|
|
10855
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
10856
|
+
const p = parts[i];
|
|
10857
|
+
if (!p || p.type !== "text" || messagePartParentId(p)) continue;
|
|
10858
|
+
const text7 = p.text.trim();
|
|
10859
|
+
if (!text7 || isInternalAgentStatusText(text7)) continue;
|
|
10860
|
+
return text7;
|
|
10861
|
+
}
|
|
10862
|
+
return "";
|
|
10863
|
+
}
|
|
10844
10864
|
function stripBrightsyNdjsonNoise(text7) {
|
|
10845
10865
|
if (!text7 || !text7.includes('"type"')) return text7;
|
|
10846
10866
|
let out = text7;
|
|
@@ -10878,6 +10898,9 @@ var init_message_parts = __esm({
|
|
|
10878
10898
|
});
|
|
10879
10899
|
|
|
10880
10900
|
// src/agents/types.ts
|
|
10901
|
+
function planModeTurnInstruction(planMode) {
|
|
10902
|
+
return planMode ? PLAN_MODE_INSTRUCTION : PLAN_MODE_OFF_INSTRUCTION;
|
|
10903
|
+
}
|
|
10881
10904
|
function permissionMode(thread) {
|
|
10882
10905
|
if (thread.sourceType === "orchestration") {
|
|
10883
10906
|
return {
|
|
@@ -10916,11 +10939,12 @@ function permissionMode(thread) {
|
|
|
10916
10939
|
codexSandbox: "workspace-write"
|
|
10917
10940
|
};
|
|
10918
10941
|
}
|
|
10919
|
-
var PLAN_MODE_INSTRUCTION;
|
|
10942
|
+
var PLAN_MODE_INSTRUCTION, PLAN_MODE_OFF_INSTRUCTION;
|
|
10920
10943
|
var init_types = __esm({
|
|
10921
10944
|
"src/agents/types.ts"() {
|
|
10922
10945
|
"use strict";
|
|
10923
10946
|
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.";
|
|
10947
|
+
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.";
|
|
10924
10948
|
}
|
|
10925
10949
|
});
|
|
10926
10950
|
|
|
@@ -11433,7 +11457,8 @@ var init_claude = __esm({
|
|
|
11433
11457
|
...sideboardWorktreeAllowedTools({
|
|
11434
11458
|
github: true,
|
|
11435
11459
|
linear: isLinearConnected(),
|
|
11436
|
-
abletime: isAbleTimeConnected()
|
|
11460
|
+
abletime: isAbleTimeConnected(),
|
|
11461
|
+
planMode: thread.planMode
|
|
11437
11462
|
}),
|
|
11438
11463
|
...brightsyMcpAllowedTools(injectedBrightsyNames)
|
|
11439
11464
|
];
|
|
@@ -13983,12 +14008,38 @@ ${match.text}`, "");
|
|
|
13983
14008
|
blocks.push("Continue from this context. Do not repeat the summary unless asked.");
|
|
13984
14009
|
return blocks.join("\n");
|
|
13985
14010
|
}
|
|
14011
|
+
function compactArchiveProse(message) {
|
|
14012
|
+
if (message.origin === "continue") return null;
|
|
14013
|
+
if (message.role === "summary") return null;
|
|
14014
|
+
let text7 = (message.text ?? "").trim();
|
|
14015
|
+
if (!text7 && message.role === "agent") {
|
|
14016
|
+
text7 = lastAssistantMessageText(message.parts).trim();
|
|
14017
|
+
}
|
|
14018
|
+
if (!text7 || isInternalAgentStatusText(text7)) return null;
|
|
14019
|
+
return { role: message.role, text: text7, ts: message.ts };
|
|
14020
|
+
}
|
|
14021
|
+
function messagesForCompactArchive(older) {
|
|
14022
|
+
const out = [];
|
|
14023
|
+
for (const message of older) {
|
|
14024
|
+
const prose = compactArchiveProse(message);
|
|
14025
|
+
if (prose) out.push(prose);
|
|
14026
|
+
}
|
|
14027
|
+
return out;
|
|
14028
|
+
}
|
|
14029
|
+
function appendCompactedMessages(existing, older) {
|
|
14030
|
+
const next = messagesForCompactArchive(older);
|
|
14031
|
+
if (!existing?.length) return next;
|
|
14032
|
+
if (!next.length) return existing;
|
|
14033
|
+
return [...existing, ...next];
|
|
14034
|
+
}
|
|
13986
14035
|
function applyCompaction(messages, summaryText, thresholds = {}) {
|
|
13987
14036
|
const { older, recent } = splitForCompaction(messages, thresholds);
|
|
13988
14037
|
if (older.length === 0) return messages;
|
|
14038
|
+
const text7 = summaryText.trim();
|
|
13989
14039
|
const summary = {
|
|
13990
14040
|
role: "summary",
|
|
13991
|
-
text:
|
|
14041
|
+
text: text7,
|
|
14042
|
+
parts: [compactSummaryPart(text7)],
|
|
13992
14043
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
13993
14044
|
};
|
|
13994
14045
|
return [summary, ...recent];
|
|
@@ -14016,6 +14067,7 @@ async function maybeCompactContext(thread, thresholds = {}, summarize = summariz
|
|
|
14016
14067
|
cwd: thread.worktreePath
|
|
14017
14068
|
});
|
|
14018
14069
|
let messages = applyCompaction(thread.messages, summary, thresholds);
|
|
14070
|
+
const compactedMessages = appendCompactedMessages(thread.compactedMessages, older);
|
|
14019
14071
|
const resetSession = shouldResetSessionForOccupancy({ messages: thread.messages });
|
|
14020
14072
|
if (resetSession) {
|
|
14021
14073
|
messages = applyForwardOccupancy(messages);
|
|
@@ -14023,6 +14075,7 @@ async function maybeCompactContext(thread, thresholds = {}, summarize = summariz
|
|
|
14023
14075
|
const next = {
|
|
14024
14076
|
...thread,
|
|
14025
14077
|
messages,
|
|
14078
|
+
...compactedMessages.length ? { compactedMessages } : { compactedMessages: void 0 },
|
|
14026
14079
|
sessionId: resetSession ? null : thread.sessionId,
|
|
14027
14080
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
14028
14081
|
};
|
|
@@ -14038,6 +14091,7 @@ var CONTEXT_COMPACT_CHARS, CONTEXT_KEEP_RECENT_CHARS, CONTEXT_KEEP_RECENT_MESSAG
|
|
|
14038
14091
|
var init_context_compact = __esm({
|
|
14039
14092
|
"src/composer/context-compact.ts"() {
|
|
14040
14093
|
"use strict";
|
|
14094
|
+
init_message_parts();
|
|
14041
14095
|
init_error_detail();
|
|
14042
14096
|
init_usage();
|
|
14043
14097
|
init_context_estimate();
|
|
@@ -14596,390 +14650,797 @@ var init_worktree_run_ports = __esm({
|
|
|
14596
14650
|
}
|
|
14597
14651
|
});
|
|
14598
14652
|
|
|
14599
|
-
// src/
|
|
14600
|
-
function
|
|
14601
|
-
const
|
|
14602
|
-
|
|
14603
|
-
|
|
14604
|
-
function readWorktreeInclude(repoPath) {
|
|
14605
|
-
const path = (0, import_node_path39.join)(repoPath, ".worktreeinclude");
|
|
14606
|
-
if (!(0, import_node_fs39.existsSync)(path)) return [];
|
|
14607
|
-
return (0, import_node_fs39.readFileSync)(path, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
14653
|
+
// src/mcp/wait-for-turn.ts
|
|
14654
|
+
function mcpWaitForTurnTimeoutMs(requested) {
|
|
14655
|
+
const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
|
|
14656
|
+
if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
|
|
14657
|
+
return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
|
|
14608
14658
|
}
|
|
14609
|
-
function
|
|
14610
|
-
|
|
14611
|
-
|
|
14612
|
-
|
|
14613
|
-
|
|
14614
|
-
|
|
14615
|
-
|
|
14616
|
-
|
|
14617
|
-
|
|
14618
|
-
|
|
14619
|
-
|
|
14620
|
-
|
|
14621
|
-
|
|
14622
|
-
|
|
14623
|
-
}
|
|
14624
|
-
}
|
|
14625
|
-
}
|
|
14626
|
-
} catch {
|
|
14627
|
-
}
|
|
14628
|
-
if (matched.length) return [...new Set(matched)];
|
|
14659
|
+
function mcpWaitTaskHint(taskState, status) {
|
|
14660
|
+
switch (taskState) {
|
|
14661
|
+
case "submitted":
|
|
14662
|
+
return MCP_WAIT_QUEUED_HINT;
|
|
14663
|
+
case "working":
|
|
14664
|
+
return MCP_WAIT_STILL_RUNNING_HINT;
|
|
14665
|
+
case "input-required":
|
|
14666
|
+
return MCP_WAIT_INPUT_REQUIRED_HINT;
|
|
14667
|
+
case "canceled":
|
|
14668
|
+
return MCP_WAIT_STOPPED_HINT;
|
|
14669
|
+
case "failed":
|
|
14670
|
+
return status === "broken" ? MCP_WAIT_BROKEN_HINT : MCP_WAIT_ERROR_HINT;
|
|
14671
|
+
default:
|
|
14672
|
+
return void 0;
|
|
14629
14673
|
}
|
|
14630
|
-
|
|
14631
|
-
|
|
14632
|
-
|
|
14633
|
-
|
|
14634
|
-
|
|
14635
|
-
|
|
14636
|
-
|
|
14637
|
-
|
|
14674
|
+
}
|
|
14675
|
+
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;
|
|
14676
|
+
var init_wait_for_turn = __esm({
|
|
14677
|
+
"src/mcp/wait-for-turn.ts"() {
|
|
14678
|
+
"use strict";
|
|
14679
|
+
MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
14680
|
+
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.";
|
|
14681
|
+
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.";
|
|
14682
|
+
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.";
|
|
14683
|
+
MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
|
|
14684
|
+
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.";
|
|
14685
|
+
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.";
|
|
14638
14686
|
}
|
|
14639
|
-
|
|
14640
|
-
|
|
14687
|
+
});
|
|
14688
|
+
|
|
14689
|
+
// src/mcp/wait-for-job.ts
|
|
14690
|
+
function mcpWaitForJobTimeoutMs(requested) {
|
|
14691
|
+
return mcpWaitForTurnTimeoutMs(requested);
|
|
14641
14692
|
}
|
|
14642
|
-
function
|
|
14643
|
-
const
|
|
14644
|
-
|
|
14645
|
-
|
|
14646
|
-
const src = (0, import_node_path39.join)(repoPath, rel);
|
|
14647
|
-
if (!(0, import_node_fs39.existsSync)(src)) continue;
|
|
14648
|
-
const dest = (0, import_node_path39.join)(worktreePath, rel);
|
|
14649
|
-
(0, import_node_fs39.mkdirSync)((0, import_node_path39.dirname)(dest), { recursive: true });
|
|
14650
|
-
(0, import_node_fs39.copyFileSync)(src, dest);
|
|
14651
|
-
copied.push(rel);
|
|
14693
|
+
function sanitizeDetachedJobId(id) {
|
|
14694
|
+
const s = id.trim();
|
|
14695
|
+
if (!JOB_ID_RE.test(s)) {
|
|
14696
|
+
throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
|
|
14652
14697
|
}
|
|
14653
|
-
return
|
|
14698
|
+
return s;
|
|
14654
14699
|
}
|
|
14655
|
-
|
|
14656
|
-
if (
|
|
14657
|
-
const shell = process.env.SHELL?.trim() || (process.platform === "darwin" ? "/bin/zsh" : "/bin/bash");
|
|
14700
|
+
function jobAlive(pid) {
|
|
14701
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
14658
14702
|
try {
|
|
14659
|
-
|
|
14660
|
-
|
|
14661
|
-
timeout: 5e3
|
|
14662
|
-
});
|
|
14663
|
-
if (result.exitCode === 0 && result.stdout) {
|
|
14664
|
-
const env = { ...process.env };
|
|
14665
|
-
for (const entry of result.stdout.split("\0")) {
|
|
14666
|
-
if (!entry) continue;
|
|
14667
|
-
const eq = entry.indexOf("=");
|
|
14668
|
-
if (eq <= 0) continue;
|
|
14669
|
-
const key = entry.slice(0, eq);
|
|
14670
|
-
const value = entry.slice(eq + 1);
|
|
14671
|
-
if (key) env[key] = value;
|
|
14672
|
-
}
|
|
14673
|
-
cachedLoginEnv = env;
|
|
14674
|
-
return { ...env };
|
|
14675
|
-
}
|
|
14703
|
+
process.kill(pid, 0);
|
|
14704
|
+
return true;
|
|
14676
14705
|
} catch {
|
|
14706
|
+
return false;
|
|
14677
14707
|
}
|
|
14678
|
-
cachedLoginEnv = { ...process.env };
|
|
14679
|
-
return { ...cachedLoginEnv };
|
|
14680
14708
|
}
|
|
14681
|
-
function
|
|
14682
|
-
|
|
14683
|
-
|
|
14684
|
-
|
|
14685
|
-
|
|
14686
|
-
|
|
14687
|
-
|
|
14688
|
-
|
|
14689
|
-
env.SIDEBOARD_WORKSPACE_PATH = opts.worktreePath;
|
|
14690
|
-
env.SIDEBOARD_ROOT_PATH = opts.repoPath;
|
|
14691
|
-
env.SIDEBOARD_IS_LOCAL = "1";
|
|
14692
|
-
if (opts.defaultBranch) env.SIDEBOARD_DEFAULT_BRANCH = opts.defaultBranch;
|
|
14693
|
-
env.CONDUCTOR_WORKSPACE_NAME = name;
|
|
14694
|
-
env.CONDUCTOR_WORKSPACE_PATH = opts.worktreePath;
|
|
14695
|
-
env.CONDUCTOR_ROOT_PATH = opts.repoPath;
|
|
14696
|
-
env.CONDUCTOR_IS_LOCAL = "1";
|
|
14697
|
-
if (opts.defaultBranch) env.CONDUCTOR_DEFAULT_BRANCH = opts.defaultBranch;
|
|
14698
|
-
if (primary != null) {
|
|
14699
|
-
env.SIDEBOARD_PORT = String(primary);
|
|
14700
|
-
env.CONDUCTOR_PORT = String(primary);
|
|
14701
|
-
env.PORT = String(primary);
|
|
14702
|
-
for (let i = 1; i < ports.length; i++) {
|
|
14703
|
-
const p = ports[i];
|
|
14704
|
-
env[`SIDEBOARD_PORT_${i}`] = String(p);
|
|
14705
|
-
env[`CONDUCTOR_PORT_${i}`] = String(p);
|
|
14706
|
-
}
|
|
14709
|
+
function processGroupAlive(pgid) {
|
|
14710
|
+
if (process.platform === "win32") return false;
|
|
14711
|
+
if (!Number.isInteger(pgid) || pgid <= 1) return false;
|
|
14712
|
+
try {
|
|
14713
|
+
process.kill(-pgid, 0);
|
|
14714
|
+
return true;
|
|
14715
|
+
} catch (err) {
|
|
14716
|
+
return err && typeof err === "object" && "code" in err && err.code === "EPERM";
|
|
14707
14717
|
}
|
|
14708
|
-
applyWorktreePkgCacheEnv(env, opts.worktreePath);
|
|
14709
|
-
return env;
|
|
14710
14718
|
}
|
|
14711
|
-
function
|
|
14712
|
-
if (
|
|
14713
|
-
|
|
14714
|
-
rl.on("line", onLine);
|
|
14719
|
+
function jobTreeAlive(pid) {
|
|
14720
|
+
if (pid == null) return false;
|
|
14721
|
+
return jobAlive(pid) || processGroupAlive(pid);
|
|
14715
14722
|
}
|
|
14716
|
-
function
|
|
14717
|
-
|
|
14718
|
-
|
|
14719
|
-
|
|
14720
|
-
if (!Number.isFinite(pid) || pid <= 0 || pid === selfPid) continue;
|
|
14721
|
-
pids.push(pid);
|
|
14722
|
-
}
|
|
14723
|
-
return pids;
|
|
14723
|
+
function readIntFile(file) {
|
|
14724
|
+
if (!(0, import_node_fs39.existsSync)(file)) return null;
|
|
14725
|
+
const n = Number.parseInt((0, import_node_fs39.readFileSync)(file, "utf8").trim(), 10);
|
|
14726
|
+
return Number.isInteger(n) ? n : null;
|
|
14724
14727
|
}
|
|
14725
|
-
function
|
|
14726
|
-
|
|
14727
|
-
const raw = (0, import_node_child_process4.execFileSync)("lsof", [`-tiTCP:${port}`, "-sTCP:LISTEN"], {
|
|
14728
|
-
encoding: "utf8",
|
|
14729
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
14730
|
-
timeout: 2e3
|
|
14731
|
-
});
|
|
14732
|
-
return killableListenerPids(raw, selfPid);
|
|
14733
|
-
} catch {
|
|
14734
|
-
return [];
|
|
14735
|
-
}
|
|
14728
|
+
function jobDir(root, id, legacy = false) {
|
|
14729
|
+
return (0, import_node_path39.join)(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
|
|
14736
14730
|
}
|
|
14737
|
-
function
|
|
14738
|
-
|
|
14739
|
-
|
|
14740
|
-
|
|
14741
|
-
|
|
14742
|
-
|
|
14743
|
-
timeout: 2e3
|
|
14744
|
-
});
|
|
14745
|
-
for (const line of raw.split("\n")) {
|
|
14746
|
-
if (line.startsWith("n") && line.length > 1) return line.slice(1);
|
|
14747
|
-
}
|
|
14748
|
-
} catch {
|
|
14749
|
-
}
|
|
14750
|
-
return null;
|
|
14731
|
+
function resolveJobDir(root, id) {
|
|
14732
|
+
const modern = jobDir(root, id, false);
|
|
14733
|
+
if ((0, import_node_fs39.existsSync)(modern)) return modern;
|
|
14734
|
+
const legacy = jobDir(root, id, true);
|
|
14735
|
+
if ((0, import_node_fs39.existsSync)(legacy)) return legacy;
|
|
14736
|
+
return modern;
|
|
14751
14737
|
}
|
|
14752
|
-
function
|
|
14753
|
-
|
|
14754
|
-
|
|
14755
|
-
}
|
|
14756
|
-
|
|
14738
|
+
function listJobIdsIn(root, rel) {
|
|
14739
|
+
const dir = (0, import_node_path39.join)(root, rel);
|
|
14740
|
+
if (!(0, import_node_fs39.existsSync)(dir)) return [];
|
|
14741
|
+
return (0, import_node_fs39.readdirSync)(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
|
|
14742
|
+
}
|
|
14743
|
+
function listRunningDetachedJobs(worktreePath) {
|
|
14744
|
+
const root = worktreePath.trim();
|
|
14745
|
+
if (!root) return [];
|
|
14746
|
+
const ids = /* @__PURE__ */ new Set([
|
|
14747
|
+
...listJobIdsIn(root, DETACHED_JOBS_DIR),
|
|
14748
|
+
...listJobIdsIn(root, LEGACY_DETACHED_JOBS_DIR)
|
|
14749
|
+
]);
|
|
14750
|
+
const running = [];
|
|
14751
|
+
for (const id of ids) {
|
|
14752
|
+
const dir = resolveJobDir(root, id);
|
|
14753
|
+
const pid = readIntFile((0, import_node_path39.join)(dir, "pid"));
|
|
14754
|
+
if (jobTreeAlive(pid)) running.push(id);
|
|
14757
14755
|
}
|
|
14756
|
+
return running.sort();
|
|
14758
14757
|
}
|
|
14759
|
-
function
|
|
14760
|
-
const
|
|
14761
|
-
|
|
14762
|
-
|
|
14763
|
-
|
|
14764
|
-
|
|
14765
|
-
if (!thisName || c === parent) return false;
|
|
14766
|
-
if (!c.startsWith(parent + import_node_path39.sep)) return false;
|
|
14767
|
-
const first = c.slice(parent.length + 1).split(import_node_path39.sep)[0];
|
|
14768
|
-
return Boolean(first) && first !== thisName;
|
|
14758
|
+
function looksLikeDeferredDonePromise(text7) {
|
|
14759
|
+
const t = (text7 ?? "").trim();
|
|
14760
|
+
if (!t || t.length > DEFERRED_DONE_MAX_CHARS) return false;
|
|
14761
|
+
const unquoted = t.replace(/`[^`]*`/g, " ").replace(/["“”][^"“”]*["“”]/g, " ").replace(/\s+/g, " ").trim();
|
|
14762
|
+
if (!unquoted) return false;
|
|
14763
|
+
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);
|
|
14769
14764
|
}
|
|
14770
|
-
function
|
|
14771
|
-
|
|
14772
|
-
|
|
14773
|
-
|
|
14774
|
-
}
|
|
14765
|
+
function isJobContinuePrompt(text7) {
|
|
14766
|
+
if (typeof text7 !== "string") return false;
|
|
14767
|
+
const t = text7.trim();
|
|
14768
|
+
return t.startsWith("Detached job still running:") || t.startsWith("You ended the turn after promising to report later");
|
|
14775
14769
|
}
|
|
14776
|
-
function
|
|
14777
|
-
|
|
14778
|
-
|
|
14779
|
-
|
|
14780
|
-
|
|
14781
|
-
|
|
14782
|
-
|
|
14783
|
-
|
|
14784
|
-
|
|
14785
|
-
|
|
14786
|
-
|
|
14787
|
-
|
|
14788
|
-
|
|
14789
|
-
|
|
14790
|
-
|
|
14791
|
-
|
|
14792
|
-
|
|
14793
|
-
|
|
14794
|
-
|
|
14795
|
-
|
|
14796
|
-
} catch {
|
|
14797
|
-
}
|
|
14770
|
+
function collapseGhRunWatchLog(text7) {
|
|
14771
|
+
if (!text7.includes(GH_WATCH_REFRESH)) return text7;
|
|
14772
|
+
const frames = text7.split(/(?=Refreshing run status every 3 seconds\.)/);
|
|
14773
|
+
const watchFrames = frames.filter((f) => f.includes(GH_WATCH_REFRESH));
|
|
14774
|
+
if (watchFrames.length <= 1) return text7;
|
|
14775
|
+
const prefix = frames[0]?.includes(GH_WATCH_REFRESH) ? "" : frames[0] ?? "";
|
|
14776
|
+
const last = watchFrames[watchFrames.length - 1] ?? "";
|
|
14777
|
+
const omitted = watchFrames.length - 1;
|
|
14778
|
+
const note = `(${omitted} identical gh run watch refresh${omitted === 1 ? "" : "es"} omitted)
|
|
14779
|
+
`;
|
|
14780
|
+
return [prefix.trimEnd(), note, last.trimStart()].filter(Boolean).join("\n");
|
|
14781
|
+
}
|
|
14782
|
+
function capJobLogDelta(text7) {
|
|
14783
|
+
const collapsed = collapseGhRunWatchLog(text7);
|
|
14784
|
+
const lines = collapsed.split("\n");
|
|
14785
|
+
let out = lines.length > MAX_JOB_DELTA_LINES ? `\u2026(${lines.length - MAX_JOB_DELTA_LINES} earlier lines omitted)
|
|
14786
|
+
${lines.slice(-MAX_JOB_DELTA_LINES).join("\n")}` : collapsed;
|
|
14787
|
+
if (out.length > MAX_JOB_DELTA_CHARS) {
|
|
14788
|
+
out = `\u2026(truncated ${out.length} chars)
|
|
14789
|
+
${out.slice(-MAX_JOB_DELTA_CHARS)}`;
|
|
14798
14790
|
}
|
|
14791
|
+
return out;
|
|
14799
14792
|
}
|
|
14800
|
-
function
|
|
14801
|
-
|
|
14802
|
-
|
|
14803
|
-
|
|
14793
|
+
function formatJobStillRunningContinuePrompt(jobIds) {
|
|
14794
|
+
const ids = jobIds.join(", ");
|
|
14795
|
+
return [
|
|
14796
|
+
`Detached job still running: ${ids}.`,
|
|
14797
|
+
"Do not end this turn. Loop wait_for_job (same id) until stillRunning is false. The log pane updates from each wait result.",
|
|
14798
|
+
"If the job is hanging or doing the wrong thing, call stop_job (same id) instead of looping forever.",
|
|
14799
|
+
"Then report the result. Do not tell the user you will let them know later."
|
|
14800
|
+
].join(" ");
|
|
14801
|
+
}
|
|
14802
|
+
function formatDeferredDoneContinuePrompt() {
|
|
14803
|
+
return [
|
|
14804
|
+
"You ended the turn after promising to report later, but no detached job is running.",
|
|
14805
|
+
"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).",
|
|
14806
|
+
"Do not say you will let the user know later."
|
|
14807
|
+
].join(" ");
|
|
14808
|
+
}
|
|
14809
|
+
function turnWatchedDetachedJob(parts) {
|
|
14810
|
+
return parts.some((p) => {
|
|
14811
|
+
if (p.type !== "tool") return false;
|
|
14812
|
+
if (/wait_for_job$/i.test(p.name ?? "") || /stop_job$/i.test(p.name ?? "")) return true;
|
|
14813
|
+
const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
|
|
14814
|
+
return /detached-job\.(?:js|cjs)\b/i.test(blob);
|
|
14815
|
+
});
|
|
14816
|
+
}
|
|
14817
|
+
function planJobContinue(opts) {
|
|
14818
|
+
if (opts.isOrchestrator) return { action: "none" };
|
|
14819
|
+
if (opts.agent === "brightsy") return { action: "none" };
|
|
14820
|
+
if (opts.queueLength > 0) return { action: "none" };
|
|
14821
|
+
if (opts.continueCount >= MAX_JOB_CONTINUES) return { action: "none" };
|
|
14822
|
+
const farewell = looksLikeDeferredDonePromise(opts.chatText);
|
|
14823
|
+
if (opts.runningJobIds.length > 0 && (farewell || opts.watchedJob)) {
|
|
14824
|
+
return {
|
|
14825
|
+
action: "wait",
|
|
14826
|
+
jobIds: opts.runningJobIds,
|
|
14827
|
+
prompt: formatJobStillRunningContinuePrompt(opts.runningJobIds)
|
|
14828
|
+
};
|
|
14804
14829
|
}
|
|
14805
|
-
if (
|
|
14806
|
-
|
|
14807
|
-
return;
|
|
14830
|
+
if (looksLikeDeferredDonePromise(opts.chatText) && !opts.alreadyNudged) {
|
|
14831
|
+
return { action: "nudge", prompt: formatDeferredDoneContinuePrompt() };
|
|
14808
14832
|
}
|
|
14809
|
-
|
|
14810
|
-
|
|
14811
|
-
|
|
14812
|
-
|
|
14813
|
-
|
|
14814
|
-
|
|
14815
|
-
|
|
14816
|
-
|
|
14833
|
+
return { action: "none" };
|
|
14834
|
+
}
|
|
14835
|
+
function tailProgress(logFile, maxLines = 12) {
|
|
14836
|
+
if (!(0, import_node_fs39.existsSync)(logFile)) return "(no log yet)";
|
|
14837
|
+
const lines = (0, import_node_fs39.readFileSync)(logFile, "utf8").split("\n");
|
|
14838
|
+
return lines.slice(-maxLines).join("\n");
|
|
14839
|
+
}
|
|
14840
|
+
function lastProgressLine(progress) {
|
|
14841
|
+
const last = progress.split("\n").map((l) => l.trim()).filter((l) => l && l !== "(no log yet)").at(-1);
|
|
14842
|
+
return last ? last.slice(0, 72) : void 0;
|
|
14843
|
+
}
|
|
14844
|
+
function readLogLines(file) {
|
|
14845
|
+
if (!(0, import_node_fs39.existsSync)(file)) return [];
|
|
14846
|
+
const lines = (0, import_node_fs39.readFileSync)(file, "utf8").split("\n");
|
|
14847
|
+
if (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
14848
|
+
return lines;
|
|
14849
|
+
}
|
|
14850
|
+
function takeDelta(logFile, cursorFile) {
|
|
14851
|
+
const lines = readLogLines(logFile);
|
|
14852
|
+
const cursor = readIntFile(cursorFile) ?? 0;
|
|
14853
|
+
const start = Math.min(Math.max(0, cursor), lines.length);
|
|
14854
|
+
return { delta: capJobLogDelta(lines.slice(start).join("\n")), nextCursor: lines.length };
|
|
14855
|
+
}
|
|
14856
|
+
function snapshotJob(dir) {
|
|
14857
|
+
const pid = readIntFile((0, import_node_path39.join)(dir, "pid"));
|
|
14858
|
+
const running = jobTreeAlive(pid);
|
|
14859
|
+
return {
|
|
14860
|
+
pid,
|
|
14861
|
+
running,
|
|
14862
|
+
exitCode: readIntFile((0, import_node_path39.join)(dir, "exit")),
|
|
14863
|
+
log: (0, import_node_path39.join)(dir, "log"),
|
|
14864
|
+
cursor: (0, import_node_path39.join)(dir, "present.cursor"),
|
|
14865
|
+
progress: tailProgress((0, import_node_path39.join)(dir, "log"))
|
|
14866
|
+
};
|
|
14867
|
+
}
|
|
14868
|
+
function toResult(id, snap, extra) {
|
|
14869
|
+
const failed = extra?.failed === true || !snap.running && snap.exitCode != null && snap.exitCode !== 0;
|
|
14870
|
+
const ok = !snap.running && snap.exitCode === 0;
|
|
14871
|
+
const stillRunning = snap.running && !ok;
|
|
14872
|
+
const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
|
|
14873
|
+
try {
|
|
14874
|
+
(0, import_node_fs39.mkdirSync)((0, import_node_path39.dirname)(snap.cursor), { recursive: true });
|
|
14875
|
+
(0, import_node_fs39.writeFileSync)(snap.cursor, `${nextCursor}
|
|
14876
|
+
`);
|
|
14877
|
+
} catch {
|
|
14878
|
+
}
|
|
14879
|
+
const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
|
|
14880
|
+
const progress = extra?.progress ?? snap.progress;
|
|
14881
|
+
return {
|
|
14882
|
+
stillRunning,
|
|
14883
|
+
ok,
|
|
14884
|
+
failed: Boolean(failed && !stillRunning && !ok),
|
|
14885
|
+
status,
|
|
14886
|
+
id,
|
|
14887
|
+
pid: snap.pid,
|
|
14888
|
+
exitCode: snap.exitCode ?? void 0,
|
|
14889
|
+
phase: lastProgressLine(progress),
|
|
14890
|
+
delta,
|
|
14891
|
+
progress,
|
|
14892
|
+
hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
|
|
14893
|
+
};
|
|
14894
|
+
}
|
|
14895
|
+
async function sleepMs(ms) {
|
|
14896
|
+
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
14897
|
+
}
|
|
14898
|
+
async function waitForDetachedJob(cwd, id, opts) {
|
|
14899
|
+
const jobId = sanitizeDetachedJobId(id);
|
|
14900
|
+
const root = cwd.trim() || process.cwd();
|
|
14901
|
+
const dir = resolveJobDir(root, jobId);
|
|
14902
|
+
const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
|
|
14903
|
+
const sleep = opts?.sleep ?? sleepMs;
|
|
14904
|
+
if (!(0, import_node_fs39.existsSync)(dir)) {
|
|
14905
|
+
return {
|
|
14906
|
+
stillRunning: false,
|
|
14907
|
+
ok: false,
|
|
14908
|
+
failed: true,
|
|
14909
|
+
status: "failed",
|
|
14910
|
+
id: jobId,
|
|
14911
|
+
delta: "",
|
|
14912
|
+
progress: "No detached job. Start one first.",
|
|
14913
|
+
hint: "Start with detached-job.cjs start <id> -- <command>, then call wait_for_job again."
|
|
14914
|
+
};
|
|
14915
|
+
}
|
|
14916
|
+
const deadline = Date.now() + timeoutMs;
|
|
14917
|
+
let snap = snapshotJob(dir);
|
|
14918
|
+
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
14919
|
+
if (!snap.running && snap.pid == null && snap.progress === "(no log yet)") {
|
|
14920
|
+
return toResult(jobId, snap, {
|
|
14921
|
+
failed: true,
|
|
14922
|
+
progress: "No detached job. Start one first."
|
|
14923
|
+
});
|
|
14924
|
+
}
|
|
14925
|
+
while (Date.now() < deadline) {
|
|
14926
|
+
snap = snapshotJob(dir);
|
|
14927
|
+
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
14928
|
+
if (!snap.running) return toResult(jobId, snap, { failed: true });
|
|
14929
|
+
await sleep(Math.min(2e3, Math.max(50, deadline - Date.now())));
|
|
14817
14930
|
}
|
|
14931
|
+
snap = snapshotJob(dir);
|
|
14932
|
+
return toResult(jobId, snap);
|
|
14818
14933
|
}
|
|
14819
|
-
function
|
|
14820
|
-
|
|
14821
|
-
if (
|
|
14934
|
+
function killProcessTree(pid, signal) {
|
|
14935
|
+
if (!Number.isInteger(pid) || pid <= 0) return;
|
|
14936
|
+
if (process.platform !== "win32") {
|
|
14822
14937
|
try {
|
|
14823
|
-
|
|
14824
|
-
|
|
14825
|
-
} else {
|
|
14826
|
-
process.kill(-pid, "SIGTERM");
|
|
14827
|
-
setTimeout(() => {
|
|
14828
|
-
try {
|
|
14829
|
-
process.kill(-pid, "SIGKILL");
|
|
14830
|
-
} catch {
|
|
14831
|
-
}
|
|
14832
|
-
}, 2500).unref?.();
|
|
14833
|
-
}
|
|
14938
|
+
process.kill(-pid, signal);
|
|
14939
|
+
return;
|
|
14834
14940
|
} catch {
|
|
14835
|
-
try {
|
|
14836
|
-
child.kill("SIGTERM");
|
|
14837
|
-
} catch {
|
|
14838
|
-
}
|
|
14839
14941
|
}
|
|
14840
14942
|
}
|
|
14841
|
-
killListenersOnPorts(ports);
|
|
14842
|
-
}
|
|
14843
|
-
async function spawnWorkspaceScript(command, opts) {
|
|
14844
|
-
const loginEnv = await captureLoginEnv();
|
|
14845
|
-
const env = buildWorkspaceScriptEnv(
|
|
14846
|
-
{
|
|
14847
|
-
worktreePath: opts.worktreePath,
|
|
14848
|
-
repoPath: opts.repoPath,
|
|
14849
|
-
workspaceName: opts.workspaceName,
|
|
14850
|
-
defaultBranch: opts.defaultBranch,
|
|
14851
|
-
ports: opts.ports
|
|
14852
|
-
},
|
|
14853
|
-
loginEnv
|
|
14854
|
-
);
|
|
14855
14943
|
try {
|
|
14856
|
-
|
|
14857
|
-
env,
|
|
14858
|
-
await resolveAgentGitAuthEnv(env, { cwd: opts.worktreePath })
|
|
14859
|
-
);
|
|
14944
|
+
process.kill(pid, signal);
|
|
14860
14945
|
} catch {
|
|
14861
14946
|
}
|
|
14862
|
-
if (opts.heldPorts?.length) {
|
|
14863
|
-
await Promise.all(opts.heldPorts.map((h) => h.release()));
|
|
14864
|
-
}
|
|
14865
|
-
const shell = process.platform === "darwin" ? "zsh" : "bash";
|
|
14866
|
-
const child = (0, import_execa4.execa)(shell, ["-lc", command], {
|
|
14867
|
-
cwd: opts.worktreePath,
|
|
14868
|
-
reject: false,
|
|
14869
|
-
env,
|
|
14870
|
-
// Own process group so Stop can tear down the whole tree (not just the shell).
|
|
14871
|
-
// There is no settings.toml `stop=` / teardown hook for run scripts.
|
|
14872
|
-
...process.platform === "win32" ? {} : { detached: true }
|
|
14873
|
-
});
|
|
14874
|
-
pipeLines(child.stdout, opts.onLine);
|
|
14875
|
-
pipeLines(child.stderr, opts.onLine);
|
|
14876
|
-
const ports = opts.ports ?? [];
|
|
14877
|
-
return {
|
|
14878
|
-
pid: child.pid,
|
|
14879
|
-
kill: () => killScriptTree(child, ports),
|
|
14880
|
-
done: child.then((r) => r.exitCode ?? null),
|
|
14881
|
-
child
|
|
14882
|
-
};
|
|
14883
14947
|
}
|
|
14884
|
-
|
|
14885
|
-
|
|
14886
|
-
|
|
14887
|
-
|
|
14888
|
-
|
|
14948
|
+
function appendJobLog(logFile, line) {
|
|
14949
|
+
try {
|
|
14950
|
+
(0, import_node_fs39.mkdirSync)((0, import_node_path39.dirname)(logFile), { recursive: true });
|
|
14951
|
+
const body = line.endsWith("\n") ? line : `${line}
|
|
14952
|
+
`;
|
|
14953
|
+
(0, import_node_fs39.appendFileSync)(logFile, body);
|
|
14954
|
+
} catch {
|
|
14889
14955
|
}
|
|
14890
|
-
return handle.done;
|
|
14891
14956
|
}
|
|
14892
|
-
async function
|
|
14893
|
-
const
|
|
14894
|
-
|
|
14895
|
-
const
|
|
14896
|
-
|
|
14897
|
-
const
|
|
14898
|
-
|
|
14899
|
-
|
|
14900
|
-
|
|
14901
|
-
|
|
14902
|
-
|
|
14903
|
-
|
|
14957
|
+
async function stopDetachedJob(cwd, id, opts) {
|
|
14958
|
+
const jobId = sanitizeDetachedJobId(id);
|
|
14959
|
+
const root = cwd.trim() || process.cwd();
|
|
14960
|
+
const dir = resolveJobDir(root, jobId);
|
|
14961
|
+
const sleep = opts?.sleep ?? sleepMs;
|
|
14962
|
+
const why = opts?.reason?.trim() ?? "";
|
|
14963
|
+
if (!(0, import_node_fs39.existsSync)(dir)) {
|
|
14964
|
+
return {
|
|
14965
|
+
stillRunning: false,
|
|
14966
|
+
ok: false,
|
|
14967
|
+
failed: true,
|
|
14968
|
+
status: "failed",
|
|
14969
|
+
id: jobId,
|
|
14970
|
+
delta: "",
|
|
14971
|
+
progress: "No detached job. Start one first.",
|
|
14972
|
+
hint: "Start with detached-job.cjs start <id> -- <command>, then wait_for_job or stop_job.",
|
|
14973
|
+
stopped: false,
|
|
14974
|
+
reason: "not-found"
|
|
14975
|
+
};
|
|
14976
|
+
}
|
|
14977
|
+
let snap = snapshotJob(dir);
|
|
14978
|
+
if (!snap.running) {
|
|
14979
|
+
const result2 = toResult(jobId, snap);
|
|
14980
|
+
return {
|
|
14981
|
+
...result2,
|
|
14982
|
+
stopped: false,
|
|
14983
|
+
reason: "not-running",
|
|
14984
|
+
hint: "That job is already finished. Read the log; do not wait again unless you start a new command."
|
|
14985
|
+
};
|
|
14986
|
+
}
|
|
14987
|
+
appendJobLog(snap.log, `$ stop${why ? `: ${why}` : ""}`);
|
|
14988
|
+
const targetPid = snap.pid;
|
|
14989
|
+
if (targetPid != null) killProcessTree(targetPid, "SIGTERM");
|
|
14990
|
+
const graceMs = Number.isFinite(opts?.graceMs) ? Math.max(50, opts.graceMs) : 2e3;
|
|
14991
|
+
const deadline = Date.now() + graceMs;
|
|
14992
|
+
while (Date.now() < deadline && jobTreeAlive(targetPid)) {
|
|
14993
|
+
await sleep(Math.min(100, Math.max(20, deadline - Date.now())));
|
|
14994
|
+
}
|
|
14995
|
+
if (targetPid != null) {
|
|
14996
|
+
killProcessTree(targetPid, "SIGKILL");
|
|
14997
|
+
await sleep(100);
|
|
14998
|
+
}
|
|
14999
|
+
const exitFile = (0, import_node_path39.join)(dir, "exit");
|
|
15000
|
+
if (!(0, import_node_fs39.existsSync)(exitFile)) {
|
|
15001
|
+
try {
|
|
15002
|
+
(0, import_node_fs39.writeFileSync)(exitFile, "1\n");
|
|
15003
|
+
} catch {
|
|
15004
|
+
}
|
|
15005
|
+
}
|
|
15006
|
+
snap = snapshotJob(dir);
|
|
15007
|
+
const stillRunning = jobTreeAlive(targetPid) || snap.running;
|
|
15008
|
+
const result = toResult(jobId, snap, { failed: true });
|
|
15009
|
+
const stopped = !stillRunning;
|
|
14904
15010
|
return {
|
|
14905
|
-
|
|
14906
|
-
|
|
14907
|
-
|
|
14908
|
-
|
|
15011
|
+
...result,
|
|
15012
|
+
stillRunning,
|
|
15013
|
+
ok: false,
|
|
15014
|
+
failed: true,
|
|
15015
|
+
status: stillRunning ? "running" : "failed",
|
|
15016
|
+
stopped,
|
|
15017
|
+
reason: stopped ? "stopped" : "still-running",
|
|
15018
|
+
stopReason: why || void 0,
|
|
15019
|
+
hint: stopped ? MCP_STOP_JOB_HINT : MCP_WAIT_JOB_STILL_RUNNING_HINT
|
|
14909
15020
|
};
|
|
14910
15021
|
}
|
|
14911
|
-
|
|
14912
|
-
|
|
14913
|
-
|
|
14914
|
-
|
|
14915
|
-
|
|
14916
|
-
|
|
14917
|
-
|
|
14918
|
-
|
|
14919
|
-
|
|
14920
|
-
|
|
14921
|
-
|
|
14922
|
-
|
|
14923
|
-
|
|
14924
|
-
|
|
14925
|
-
|
|
14926
|
-
|
|
14927
|
-
}
|
|
15022
|
+
var import_node_fs39, import_node_path39, 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;
|
|
15023
|
+
var init_wait_for_job = __esm({
|
|
15024
|
+
"src/mcp/wait-for-job.ts"() {
|
|
15025
|
+
"use strict";
|
|
15026
|
+
import_node_fs39 = require("fs");
|
|
15027
|
+
import_node_path39 = require("path");
|
|
15028
|
+
init_workspace_scratch();
|
|
15029
|
+
init_wait_for_turn();
|
|
15030
|
+
MAX_JOB_CONTINUES = 8;
|
|
15031
|
+
MAX_JOB_DELTA_LINES = 80;
|
|
15032
|
+
MAX_JOB_DELTA_CHARS = 4e3;
|
|
15033
|
+
DEFERRED_DONE_MAX_CHARS = 480;
|
|
15034
|
+
GH_WATCH_REFRESH = "Refreshing run status every 3 seconds.";
|
|
15035
|
+
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.";
|
|
15036
|
+
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.";
|
|
15037
|
+
JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
15038
|
+
}
|
|
15039
|
+
});
|
|
15040
|
+
|
|
15041
|
+
// src/hook/conductor.ts
|
|
15042
|
+
function matchSimpleGlob(pattern, name) {
|
|
15043
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
15044
|
+
return new RegExp(`^${escaped}$`).test(name);
|
|
14928
15045
|
}
|
|
14929
|
-
|
|
14930
|
-
|
|
14931
|
-
|
|
14932
|
-
|
|
14933
|
-
|
|
15046
|
+
function readWorktreeInclude(repoPath) {
|
|
15047
|
+
const path = (0, import_node_path40.join)(repoPath, ".worktreeinclude");
|
|
15048
|
+
if (!(0, import_node_fs40.existsSync)(path)) return [];
|
|
15049
|
+
return (0, import_node_fs40.readFileSync)(path, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
15050
|
+
}
|
|
15051
|
+
function resolveFilesToCopy(repoPath) {
|
|
15052
|
+
const fromInclude = readWorktreeInclude(repoPath);
|
|
15053
|
+
if (fromInclude.length) return fromInclude;
|
|
15054
|
+
const settings = loadRepoSettings(repoPath);
|
|
15055
|
+
if (settings?.filesToCopy?.length) return settings.filesToCopy;
|
|
15056
|
+
if (settings?.fileIncludeGlobs?.length) {
|
|
15057
|
+
const matched = [];
|
|
15058
|
+
try {
|
|
15059
|
+
for (const entry of (0, import_node_fs40.readdirSync)(repoPath, { withFileTypes: true })) {
|
|
15060
|
+
if (!entry.isFile()) continue;
|
|
15061
|
+
for (const glob of settings.fileIncludeGlobs) {
|
|
15062
|
+
if (matchSimpleGlob(glob, entry.name) || matchSimpleGlob((0, import_node_path40.basename)(glob), entry.name)) {
|
|
15063
|
+
matched.push(entry.name);
|
|
15064
|
+
break;
|
|
15065
|
+
}
|
|
15066
|
+
}
|
|
15067
|
+
}
|
|
15068
|
+
} catch {
|
|
15069
|
+
}
|
|
15070
|
+
if (matched.length) return [...new Set(matched)];
|
|
14934
15071
|
}
|
|
14935
|
-
|
|
14936
|
-
|
|
14937
|
-
|
|
15072
|
+
const defaults = [];
|
|
15073
|
+
try {
|
|
15074
|
+
for (const entry of (0, import_node_fs40.readdirSync)(repoPath, { withFileTypes: true })) {
|
|
15075
|
+
if (entry.isFile() && entry.name.startsWith(".env")) {
|
|
15076
|
+
defaults.push(entry.name);
|
|
15077
|
+
}
|
|
15078
|
+
}
|
|
15079
|
+
} catch {
|
|
14938
15080
|
}
|
|
14939
|
-
if (
|
|
14940
|
-
|
|
15081
|
+
if (defaults.length) return defaults;
|
|
15082
|
+
return [".env.local", ".env"];
|
|
15083
|
+
}
|
|
15084
|
+
function copyConfiguredFiles(repoPath, worktreePath) {
|
|
15085
|
+
const patterns = resolveFilesToCopy(repoPath);
|
|
15086
|
+
const copied = [];
|
|
15087
|
+
for (const rel of patterns) {
|
|
15088
|
+
const src = (0, import_node_path40.join)(repoPath, rel);
|
|
15089
|
+
if (!(0, import_node_fs40.existsSync)(src)) continue;
|
|
15090
|
+
const dest = (0, import_node_path40.join)(worktreePath, rel);
|
|
15091
|
+
(0, import_node_fs40.mkdirSync)((0, import_node_path40.dirname)(dest), { recursive: true });
|
|
15092
|
+
(0, import_node_fs40.copyFileSync)(src, dest);
|
|
15093
|
+
copied.push(rel);
|
|
14941
15094
|
}
|
|
14942
|
-
return
|
|
15095
|
+
return copied;
|
|
14943
15096
|
}
|
|
14944
|
-
async function
|
|
14945
|
-
|
|
14946
|
-
|
|
14947
|
-
|
|
14948
|
-
|
|
14949
|
-
|
|
14950
|
-
|
|
14951
|
-
|
|
14952
|
-
|
|
14953
|
-
|
|
15097
|
+
async function captureLoginEnv() {
|
|
15098
|
+
if (cachedLoginEnv) return { ...cachedLoginEnv };
|
|
15099
|
+
const shell = process.env.SHELL?.trim() || (process.platform === "darwin" ? "/bin/zsh" : "/bin/bash");
|
|
15100
|
+
try {
|
|
15101
|
+
const result = await (0, import_execa4.execa)(shell, ["-l", "-c", "env -0"], {
|
|
15102
|
+
reject: false,
|
|
15103
|
+
timeout: 5e3
|
|
15104
|
+
});
|
|
15105
|
+
if (result.exitCode === 0 && result.stdout) {
|
|
15106
|
+
const env = { ...process.env };
|
|
15107
|
+
for (const entry of result.stdout.split("\0")) {
|
|
15108
|
+
if (!entry) continue;
|
|
15109
|
+
const eq = entry.indexOf("=");
|
|
15110
|
+
if (eq <= 0) continue;
|
|
15111
|
+
const key = entry.slice(0, eq);
|
|
15112
|
+
const value = entry.slice(eq + 1);
|
|
15113
|
+
if (key) env[key] = value;
|
|
15114
|
+
}
|
|
15115
|
+
cachedLoginEnv = env;
|
|
15116
|
+
return { ...env };
|
|
15117
|
+
}
|
|
15118
|
+
} catch {
|
|
15119
|
+
}
|
|
15120
|
+
cachedLoginEnv = { ...process.env };
|
|
15121
|
+
return { ...cachedLoginEnv };
|
|
14954
15122
|
}
|
|
14955
|
-
function
|
|
14956
|
-
const
|
|
14957
|
-
|
|
14958
|
-
return settings.runScripts.filter((s) => {
|
|
14959
|
-
if (!s.availableIn?.length) return true;
|
|
14960
|
-
return s.availableIn.includes("local");
|
|
15123
|
+
function buildWorkspaceScriptEnv(opts, baseEnv) {
|
|
15124
|
+
const env = stripNestedElectronEnv({
|
|
15125
|
+
...baseEnv ?? process.env
|
|
14961
15126
|
});
|
|
15127
|
+
const name = opts.workspaceName ?? (0, import_node_path40.basename)(opts.worktreePath);
|
|
15128
|
+
const ports = opts.ports ?? [];
|
|
15129
|
+
const primary = ports[0];
|
|
15130
|
+
env.SIDEBOARD_WORKSPACE_NAME = name;
|
|
15131
|
+
env.SIDEBOARD_WORKSPACE_PATH = opts.worktreePath;
|
|
15132
|
+
env.SIDEBOARD_ROOT_PATH = opts.repoPath;
|
|
15133
|
+
env.SIDEBOARD_IS_LOCAL = "1";
|
|
15134
|
+
if (opts.defaultBranch) env.SIDEBOARD_DEFAULT_BRANCH = opts.defaultBranch;
|
|
15135
|
+
env.CONDUCTOR_WORKSPACE_NAME = name;
|
|
15136
|
+
env.CONDUCTOR_WORKSPACE_PATH = opts.worktreePath;
|
|
15137
|
+
env.CONDUCTOR_ROOT_PATH = opts.repoPath;
|
|
15138
|
+
env.CONDUCTOR_IS_LOCAL = "1";
|
|
15139
|
+
if (opts.defaultBranch) env.CONDUCTOR_DEFAULT_BRANCH = opts.defaultBranch;
|
|
15140
|
+
if (primary != null) {
|
|
15141
|
+
env.SIDEBOARD_PORT = String(primary);
|
|
15142
|
+
env.CONDUCTOR_PORT = String(primary);
|
|
15143
|
+
env.PORT = String(primary);
|
|
15144
|
+
for (let i = 1; i < ports.length; i++) {
|
|
15145
|
+
const p = ports[i];
|
|
15146
|
+
env[`SIDEBOARD_PORT_${i}`] = String(p);
|
|
15147
|
+
env[`CONDUCTOR_PORT_${i}`] = String(p);
|
|
15148
|
+
}
|
|
15149
|
+
}
|
|
15150
|
+
applyWorktreePkgCacheEnv(env, opts.worktreePath);
|
|
15151
|
+
return env;
|
|
14962
15152
|
}
|
|
14963
|
-
function
|
|
14964
|
-
|
|
14965
|
-
|
|
14966
|
-
|
|
15153
|
+
function pipeLines(stream, onLine) {
|
|
15154
|
+
if (!stream || !onLine) return;
|
|
15155
|
+
const rl = (0, import_node_readline3.createInterface)({ input: stream });
|
|
15156
|
+
rl.on("line", onLine);
|
|
14967
15157
|
}
|
|
14968
|
-
function
|
|
14969
|
-
const
|
|
14970
|
-
|
|
14971
|
-
|
|
14972
|
-
|
|
15158
|
+
function killableListenerPids(raw, selfPid = process.pid) {
|
|
15159
|
+
const pids = [];
|
|
15160
|
+
for (const token of raw.trim().split(/\s+/)) {
|
|
15161
|
+
const pid = Number(token);
|
|
15162
|
+
if (!Number.isFinite(pid) || pid <= 0 || pid === selfPid) continue;
|
|
15163
|
+
pids.push(pid);
|
|
14973
15164
|
}
|
|
14974
|
-
return
|
|
15165
|
+
return pids;
|
|
14975
15166
|
}
|
|
14976
|
-
function
|
|
14977
|
-
|
|
15167
|
+
function listUnixListenerPids(port, selfPid) {
|
|
15168
|
+
try {
|
|
15169
|
+
const raw = (0, import_node_child_process4.execFileSync)("lsof", [`-tiTCP:${port}`, "-sTCP:LISTEN"], {
|
|
15170
|
+
encoding: "utf8",
|
|
15171
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
15172
|
+
timeout: 2e3
|
|
15173
|
+
});
|
|
15174
|
+
return killableListenerPids(raw, selfPid);
|
|
15175
|
+
} catch {
|
|
15176
|
+
return [];
|
|
15177
|
+
}
|
|
14978
15178
|
}
|
|
14979
|
-
|
|
14980
|
-
|
|
14981
|
-
|
|
14982
|
-
|
|
15179
|
+
function listenerCwd(pid) {
|
|
15180
|
+
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
15181
|
+
try {
|
|
15182
|
+
const raw = (0, import_node_child_process4.execFileSync)("lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], {
|
|
15183
|
+
encoding: "utf8",
|
|
15184
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
15185
|
+
timeout: 2e3
|
|
15186
|
+
});
|
|
15187
|
+
for (const line of raw.split("\n")) {
|
|
15188
|
+
if (line.startsWith("n") && line.length > 1) return line.slice(1);
|
|
15189
|
+
}
|
|
15190
|
+
} catch {
|
|
15191
|
+
}
|
|
15192
|
+
return null;
|
|
15193
|
+
}
|
|
15194
|
+
function realpathOrResolve(path) {
|
|
15195
|
+
try {
|
|
15196
|
+
return (0, import_node_fs40.realpathSync)(path);
|
|
15197
|
+
} catch {
|
|
15198
|
+
return (0, import_node_path40.resolve)(path);
|
|
15199
|
+
}
|
|
15200
|
+
}
|
|
15201
|
+
function isOtherWorktreeListenerCwd(cwd, worktreePath) {
|
|
15202
|
+
const wt = realpathOrResolve(worktreePath);
|
|
15203
|
+
const c = realpathOrResolve(cwd);
|
|
15204
|
+
if (c === wt || c.startsWith(wt + import_node_path40.sep)) return false;
|
|
15205
|
+
const parent = (0, import_node_path40.dirname)(wt);
|
|
15206
|
+
const thisName = (0, import_node_path40.basename)(wt);
|
|
15207
|
+
if (!thisName || c === parent) return false;
|
|
15208
|
+
if (!c.startsWith(parent + import_node_path40.sep)) return false;
|
|
15209
|
+
const first = c.slice(parent.length + 1).split(import_node_path40.sep)[0];
|
|
15210
|
+
return Boolean(first) && first !== thisName;
|
|
15211
|
+
}
|
|
15212
|
+
function signalPid(pid, signal) {
|
|
15213
|
+
try {
|
|
15214
|
+
process.kill(pid, signal);
|
|
15215
|
+
} catch {
|
|
15216
|
+
}
|
|
15217
|
+
}
|
|
15218
|
+
function isThisWorktreeListenerCwd(cwd, worktreePath) {
|
|
15219
|
+
const wt = realpathOrResolve(worktreePath);
|
|
15220
|
+
const c = realpathOrResolve(cwd);
|
|
15221
|
+
return c === wt || c.startsWith(wt + import_node_path40.sep);
|
|
15222
|
+
}
|
|
15223
|
+
function killDetachedRunTree(pid, worktreePath) {
|
|
15224
|
+
if (!Number.isFinite(pid) || pid <= 1 || pid === process.pid) return;
|
|
15225
|
+
const cwd = listenerCwd(pid);
|
|
15226
|
+
if (cwd) {
|
|
15227
|
+
if (!isThisWorktreeListenerCwd(cwd, worktreePath)) return;
|
|
15228
|
+
} else if (!processGroupAlive(pid)) {
|
|
15229
|
+
return;
|
|
15230
|
+
}
|
|
15231
|
+
try {
|
|
15232
|
+
process.kill(-pid, "SIGTERM");
|
|
15233
|
+
} catch {
|
|
15234
|
+
signalPid(pid, "SIGTERM");
|
|
15235
|
+
}
|
|
15236
|
+
}
|
|
15237
|
+
function killListenersOnPorts(ports) {
|
|
15238
|
+
const selfPid = process.pid;
|
|
15239
|
+
for (const port of ports) {
|
|
15240
|
+
if (!Number.isFinite(port) || port <= 0) continue;
|
|
15241
|
+
try {
|
|
15242
|
+
if (process.platform === "win32") {
|
|
15243
|
+
(0, import_node_child_process4.execFileSync)(
|
|
15244
|
+
"powershell",
|
|
15245
|
+
[
|
|
15246
|
+
"-NoProfile",
|
|
15247
|
+
"-Command",
|
|
15248
|
+
`Get-NetTCPConnection -LocalPort ${port} -ErrorAction SilentlyContinue | Where-Object { $_.OwningProcess -ne ${selfPid} } | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }`
|
|
15249
|
+
],
|
|
15250
|
+
{ stdio: "ignore", timeout: 3e3 }
|
|
15251
|
+
);
|
|
15252
|
+
continue;
|
|
15253
|
+
}
|
|
15254
|
+
for (const pid of listUnixListenerPids(port, selfPid)) {
|
|
15255
|
+
signalPid(pid, "SIGTERM");
|
|
15256
|
+
}
|
|
15257
|
+
} catch {
|
|
15258
|
+
}
|
|
15259
|
+
}
|
|
15260
|
+
}
|
|
15261
|
+
function killStaleWorktreeListeners(ports, worktreePath, signal = "SIGTERM") {
|
|
15262
|
+
if (!worktreePath.trim()) {
|
|
15263
|
+
killListenersOnPorts(ports);
|
|
15264
|
+
return;
|
|
15265
|
+
}
|
|
15266
|
+
if (process.platform === "win32") {
|
|
15267
|
+
killListenersOnPorts(ports);
|
|
15268
|
+
return;
|
|
15269
|
+
}
|
|
15270
|
+
const selfPid = process.pid;
|
|
15271
|
+
for (const port of ports) {
|
|
15272
|
+
if (!Number.isFinite(port) || port <= 0) continue;
|
|
15273
|
+
for (const pid of listUnixListenerPids(port, selfPid)) {
|
|
15274
|
+
const cwd = listenerCwd(pid);
|
|
15275
|
+
if (cwd && isOtherWorktreeListenerCwd(cwd, worktreePath)) continue;
|
|
15276
|
+
signalPid(pid, signal);
|
|
15277
|
+
}
|
|
15278
|
+
}
|
|
15279
|
+
}
|
|
15280
|
+
function killScriptTree(child, ports = []) {
|
|
15281
|
+
const pid = child.pid;
|
|
15282
|
+
if (pid) {
|
|
15283
|
+
try {
|
|
15284
|
+
if (process.platform === "win32") {
|
|
15285
|
+
void (0, import_execa4.execa)("taskkill", ["/pid", String(pid), "/T", "/F"], { reject: false });
|
|
15286
|
+
} else {
|
|
15287
|
+
process.kill(-pid, "SIGTERM");
|
|
15288
|
+
setTimeout(() => {
|
|
15289
|
+
try {
|
|
15290
|
+
process.kill(-pid, "SIGKILL");
|
|
15291
|
+
} catch {
|
|
15292
|
+
}
|
|
15293
|
+
}, 2500).unref?.();
|
|
15294
|
+
}
|
|
15295
|
+
} catch {
|
|
15296
|
+
try {
|
|
15297
|
+
child.kill("SIGTERM");
|
|
15298
|
+
} catch {
|
|
15299
|
+
}
|
|
15300
|
+
}
|
|
15301
|
+
}
|
|
15302
|
+
killListenersOnPorts(ports);
|
|
15303
|
+
}
|
|
15304
|
+
async function spawnWorkspaceScript(command, opts) {
|
|
15305
|
+
const loginEnv = await captureLoginEnv();
|
|
15306
|
+
const env = buildWorkspaceScriptEnv(
|
|
15307
|
+
{
|
|
15308
|
+
worktreePath: opts.worktreePath,
|
|
15309
|
+
repoPath: opts.repoPath,
|
|
15310
|
+
workspaceName: opts.workspaceName,
|
|
15311
|
+
defaultBranch: opts.defaultBranch,
|
|
15312
|
+
ports: opts.ports
|
|
15313
|
+
},
|
|
15314
|
+
loginEnv
|
|
15315
|
+
);
|
|
15316
|
+
try {
|
|
15317
|
+
mergeAgentGitAuthEnv(
|
|
15318
|
+
env,
|
|
15319
|
+
await resolveAgentGitAuthEnv(env, { cwd: opts.worktreePath })
|
|
15320
|
+
);
|
|
15321
|
+
} catch {
|
|
15322
|
+
}
|
|
15323
|
+
if (opts.heldPorts?.length) {
|
|
15324
|
+
await Promise.all(opts.heldPorts.map((h) => h.release()));
|
|
15325
|
+
}
|
|
15326
|
+
const shell = process.platform === "darwin" ? "zsh" : "bash";
|
|
15327
|
+
const child = (0, import_execa4.execa)(shell, ["-lc", command], {
|
|
15328
|
+
cwd: opts.worktreePath,
|
|
15329
|
+
reject: false,
|
|
15330
|
+
env,
|
|
15331
|
+
// Own process group so Stop can tear down the whole tree (not just the shell).
|
|
15332
|
+
// There is no settings.toml `stop=` / teardown hook for run scripts.
|
|
15333
|
+
...process.platform === "win32" ? {} : { detached: true }
|
|
15334
|
+
});
|
|
15335
|
+
pipeLines(child.stdout, opts.onLine);
|
|
15336
|
+
pipeLines(child.stderr, opts.onLine);
|
|
15337
|
+
const ports = opts.ports ?? [];
|
|
15338
|
+
return {
|
|
15339
|
+
pid: child.pid,
|
|
15340
|
+
kill: () => killScriptTree(child, ports),
|
|
15341
|
+
done: child.then((r) => r.exitCode ?? null),
|
|
15342
|
+
child
|
|
15343
|
+
};
|
|
15344
|
+
}
|
|
15345
|
+
async function attachAbort(handle, signal) {
|
|
15346
|
+
if (signal) {
|
|
15347
|
+
const onAbort = () => handle.kill();
|
|
15348
|
+
if (signal.aborted) onAbort();
|
|
15349
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
15350
|
+
}
|
|
15351
|
+
return handle.done;
|
|
15352
|
+
}
|
|
15353
|
+
async function runSetupScript(repoPath, worktreePath, onLine, opts) {
|
|
15354
|
+
const settings = loadWorkspaceSettings(worktreePath, repoPath);
|
|
15355
|
+
if (!settings?.setup) return { ran: false, exitCode: null, source: null };
|
|
15356
|
+
const source = workspaceSettingsSourceLabel(worktreePath, repoPath);
|
|
15357
|
+
onLine?.(`[setup] ${source ?? "settings.toml"}`);
|
|
15358
|
+
const handle = await spawnWorkspaceScript(settings.setup, {
|
|
15359
|
+
worktreePath,
|
|
15360
|
+
repoPath,
|
|
15361
|
+
defaultBranch: opts?.defaultBranch,
|
|
15362
|
+
onLine
|
|
15363
|
+
});
|
|
15364
|
+
const exitCode = await attachAbort(handle, opts?.signal);
|
|
15365
|
+
return {
|
|
15366
|
+
ran: true,
|
|
15367
|
+
exitCode,
|
|
15368
|
+
source,
|
|
15369
|
+
kill: handle.kill
|
|
15370
|
+
};
|
|
15371
|
+
}
|
|
15372
|
+
async function runConventionSetup(repoPath, worktreePath, onLine, opts) {
|
|
15373
|
+
const found = findConventionSetup(worktreePath, repoPath);
|
|
15374
|
+
if (!found) return { ran: false, exitCode: null, source: null };
|
|
15375
|
+
onLine?.(`[setup] ${found.source}`);
|
|
15376
|
+
const handle = await spawnWorkspaceScript(found.command, {
|
|
15377
|
+
worktreePath,
|
|
15378
|
+
repoPath,
|
|
15379
|
+
defaultBranch: opts?.defaultBranch,
|
|
15380
|
+
onLine
|
|
15381
|
+
});
|
|
15382
|
+
const exitCode = await attachAbort(handle, opts?.signal);
|
|
15383
|
+
return {
|
|
15384
|
+
ran: true,
|
|
15385
|
+
exitCode,
|
|
15386
|
+
source: found.source,
|
|
15387
|
+
kill: handle.kill
|
|
15388
|
+
};
|
|
15389
|
+
}
|
|
15390
|
+
async function runWorkspaceSetup(repoPath, worktreePath, onLine, opts) {
|
|
15391
|
+
await ensureWorktreeSideboardIgnored(worktreePath);
|
|
15392
|
+
const review = ensureReviewGuidelinesFile(worktreePath, repoPath);
|
|
15393
|
+
if (review.wrote) {
|
|
15394
|
+
onLine?.(`[setup] wrote ${review.path} \u2014 local Review guidelines`);
|
|
15395
|
+
}
|
|
15396
|
+
let setup = await runSetupScript(repoPath, worktreePath, onLine, opts);
|
|
15397
|
+
if (!setup.ran) {
|
|
15398
|
+
setup = await runCursorWorktreeSetup(repoPath, worktreePath, onLine);
|
|
15399
|
+
}
|
|
15400
|
+
if (!setup.ran) {
|
|
15401
|
+
setup = await runConventionSetup(repoPath, worktreePath, onLine, opts);
|
|
15402
|
+
}
|
|
15403
|
+
return setup;
|
|
15404
|
+
}
|
|
15405
|
+
async function runArchiveScript(repoPath, worktreePath, onLine) {
|
|
15406
|
+
const settings = loadWorkspaceSettings(worktreePath, repoPath);
|
|
15407
|
+
if (!settings?.archive) return { ran: false, exitCode: null };
|
|
15408
|
+
const handle = await spawnWorkspaceScript(settings.archive, {
|
|
15409
|
+
worktreePath,
|
|
15410
|
+
repoPath,
|
|
15411
|
+
onLine
|
|
15412
|
+
});
|
|
15413
|
+
const exitCode = await handle.done;
|
|
15414
|
+
return { ran: true, exitCode };
|
|
15415
|
+
}
|
|
15416
|
+
function listRunScripts(worktreePath, repoPath) {
|
|
15417
|
+
const settings = loadWorkspaceSettings(worktreePath, repoPath);
|
|
15418
|
+
if (!settings?.runScripts.length) return [];
|
|
15419
|
+
return settings.runScripts.filter((s) => {
|
|
15420
|
+
if (!s.availableIn?.length) return true;
|
|
15421
|
+
return s.availableIn.includes("local");
|
|
15422
|
+
});
|
|
15423
|
+
}
|
|
15424
|
+
function getDefaultRunScript(worktreePath, repoPath) {
|
|
15425
|
+
const scripts = listRunScripts(worktreePath, repoPath);
|
|
15426
|
+
if (!scripts.length) return null;
|
|
15427
|
+
return scripts.find((s) => s.default === true) ?? scripts.find((s) => s.name === "dev") ?? scripts.find((s) => s.name !== "all") ?? scripts[0] ?? null;
|
|
15428
|
+
}
|
|
15429
|
+
function getRunScript(worktreePath, repoPath, name) {
|
|
15430
|
+
const scripts = listRunScripts(worktreePath, repoPath);
|
|
15431
|
+
if (!scripts.length) return null;
|
|
15432
|
+
if (name) {
|
|
15433
|
+
return scripts.find((s) => s.name === name) ?? null;
|
|
15434
|
+
}
|
|
15435
|
+
return getDefaultRunScript(worktreePath, repoPath);
|
|
15436
|
+
}
|
|
15437
|
+
function getRunMode(worktreePath, repoPath) {
|
|
15438
|
+
return loadWorkspaceSettings(worktreePath, repoPath)?.runMode ?? "concurrent";
|
|
15439
|
+
}
|
|
15440
|
+
async function reservePort() {
|
|
15441
|
+
return new Promise((resolve2, reject) => {
|
|
15442
|
+
const server = (0, import_node_net.createServer)();
|
|
15443
|
+
let released = false;
|
|
14983
15444
|
const release = () => new Promise((res) => {
|
|
14984
15445
|
if (released) {
|
|
14985
15446
|
res();
|
|
@@ -15140,14 +15601,14 @@ async function startDevServer(repoPath, worktreePath, onLine, opts) {
|
|
|
15140
15601
|
throw err;
|
|
15141
15602
|
}
|
|
15142
15603
|
}
|
|
15143
|
-
var
|
|
15604
|
+
var import_node_fs40, import_node_child_process4, import_node_net, import_node_path40, import_execa4, import_node_readline3, PORT_RANGE_SIZE, RECLAIM_RETRY_DELAY_MS, RECLAIM_TRIES, cachedLoginEnv;
|
|
15144
15605
|
var init_conductor = __esm({
|
|
15145
15606
|
"src/hook/conductor.ts"() {
|
|
15146
15607
|
"use strict";
|
|
15147
|
-
|
|
15608
|
+
import_node_fs40 = require("fs");
|
|
15148
15609
|
import_node_child_process4 = require("child_process");
|
|
15149
15610
|
import_node_net = require("net");
|
|
15150
|
-
|
|
15611
|
+
import_node_path40 = require("path");
|
|
15151
15612
|
import_execa4 = require("execa");
|
|
15152
15613
|
import_node_readline3 = require("readline");
|
|
15153
15614
|
init_settings();
|
|
@@ -15159,6 +15620,7 @@ var init_conductor = __esm({
|
|
|
15159
15620
|
init_request_review();
|
|
15160
15621
|
init_worktree_exclude();
|
|
15161
15622
|
init_worktree_run_ports();
|
|
15623
|
+
init_wait_for_job();
|
|
15162
15624
|
init_worktree_run_ports();
|
|
15163
15625
|
init_nested_electron_env();
|
|
15164
15626
|
PORT_RANGE_SIZE = 10;
|
|
@@ -15201,7 +15663,7 @@ function materialize(mem) {
|
|
|
15201
15663
|
};
|
|
15202
15664
|
}
|
|
15203
15665
|
function setupLogPath(threadId) {
|
|
15204
|
-
return (0,
|
|
15666
|
+
return (0, import_node_path41.join)(threadsDir(), `${threadId}.setup.log.json`);
|
|
15205
15667
|
}
|
|
15206
15668
|
function setupLogKeyForWorktree(worktreePath) {
|
|
15207
15669
|
const norm = normalizeWorktreePath(worktreePath);
|
|
@@ -15219,7 +15681,7 @@ function persistNow(threadId) {
|
|
|
15219
15681
|
const mem = memory.get(threadId);
|
|
15220
15682
|
if (!mem) return;
|
|
15221
15683
|
try {
|
|
15222
|
-
(0,
|
|
15684
|
+
(0, import_node_fs41.writeFileSync)(
|
|
15223
15685
|
setupLogPath(threadId),
|
|
15224
15686
|
`${JSON.stringify(materialize(mem))}
|
|
15225
15687
|
`,
|
|
@@ -15240,9 +15702,9 @@ function schedulePersist(threadId) {
|
|
|
15240
15702
|
}
|
|
15241
15703
|
function loadFromDisk(threadId) {
|
|
15242
15704
|
const path = setupLogPath(threadId);
|
|
15243
|
-
if (!(0,
|
|
15705
|
+
if (!(0, import_node_fs41.existsSync)(path)) return null;
|
|
15244
15706
|
try {
|
|
15245
|
-
const raw = JSON.parse((0,
|
|
15707
|
+
const raw = JSON.parse((0, import_node_fs41.readFileSync)(path, "utf8"));
|
|
15246
15708
|
return {
|
|
15247
15709
|
output: typeof raw.output === "string" ? raw.output : "",
|
|
15248
15710
|
running: Boolean(raw.running),
|
|
@@ -15298,13 +15760,13 @@ function finishSetupLog(threadId, exitCode, source) {
|
|
|
15298
15760
|
persistNow(threadId);
|
|
15299
15761
|
return materialize(mem);
|
|
15300
15762
|
}
|
|
15301
|
-
var import_node_crypto8,
|
|
15763
|
+
var import_node_crypto8, import_node_fs41, import_node_path41, EMPTY, MAX_SETUP_LOG_CHARS, memory, persistTimers, PERSIST_DEBOUNCE_MS;
|
|
15302
15764
|
var init_setup_log = __esm({
|
|
15303
15765
|
"src/store/setup-log.ts"() {
|
|
15304
15766
|
"use strict";
|
|
15305
15767
|
import_node_crypto8 = require("crypto");
|
|
15306
|
-
|
|
15307
|
-
|
|
15768
|
+
import_node_fs41 = require("fs");
|
|
15769
|
+
import_node_path41 = require("path");
|
|
15308
15770
|
init_worktree_labels();
|
|
15309
15771
|
init_paths();
|
|
15310
15772
|
EMPTY = {
|
|
@@ -15349,7 +15811,7 @@ function safeScriptToken(scriptName) {
|
|
|
15349
15811
|
return token || "dev";
|
|
15350
15812
|
}
|
|
15351
15813
|
function runLogPath(key) {
|
|
15352
|
-
return (0,
|
|
15814
|
+
return (0, import_node_path42.join)(threadsDir(), `${key}.run.log.json`);
|
|
15353
15815
|
}
|
|
15354
15816
|
function runLogKeyForWorktree(worktreePath, scriptName) {
|
|
15355
15817
|
const norm = normalizeWorktreePath(worktreePath);
|
|
@@ -15377,7 +15839,7 @@ function persistNow2(key) {
|
|
|
15377
15839
|
const mem = memory2.get(key);
|
|
15378
15840
|
if (!mem) return;
|
|
15379
15841
|
try {
|
|
15380
|
-
(0,
|
|
15842
|
+
(0, import_node_fs42.writeFileSync)(runLogPath(key), `${JSON.stringify(materialize2(mem))}
|
|
15381
15843
|
`, "utf8");
|
|
15382
15844
|
} catch {
|
|
15383
15845
|
}
|
|
@@ -15394,9 +15856,9 @@ function schedulePersist2(key) {
|
|
|
15394
15856
|
}
|
|
15395
15857
|
function loadFromDisk2(key) {
|
|
15396
15858
|
const path = runLogPath(key);
|
|
15397
|
-
if (!(0,
|
|
15859
|
+
if (!(0, import_node_fs42.existsSync)(path)) return null;
|
|
15398
15860
|
try {
|
|
15399
|
-
const raw = JSON.parse((0,
|
|
15861
|
+
const raw = JSON.parse((0, import_node_fs42.readFileSync)(path, "utf8"));
|
|
15400
15862
|
return {
|
|
15401
15863
|
output: typeof raw.output === "string" ? raw.output : "",
|
|
15402
15864
|
running: Boolean(raw.running),
|
|
@@ -15454,13 +15916,13 @@ function finishRunLog(key, exitCode) {
|
|
|
15454
15916
|
function flushRunLog(key) {
|
|
15455
15917
|
persistNow2(key);
|
|
15456
15918
|
}
|
|
15457
|
-
var import_node_crypto9,
|
|
15919
|
+
var import_node_crypto9, import_node_fs42, import_node_path42, EMPTY2, MAX_RUN_LOG_CHARS, DEFAULT_RUN_LOG_TAIL_CHARS, memory2, persistTimers2, PERSIST_DEBOUNCE_MS2;
|
|
15458
15920
|
var init_run_log = __esm({
|
|
15459
15921
|
"src/store/run-log.ts"() {
|
|
15460
15922
|
"use strict";
|
|
15461
15923
|
import_node_crypto9 = require("crypto");
|
|
15462
|
-
|
|
15463
|
-
|
|
15924
|
+
import_node_fs42 = require("fs");
|
|
15925
|
+
import_node_path42 = require("path");
|
|
15464
15926
|
init_worktree_labels();
|
|
15465
15927
|
init_setup_log();
|
|
15466
15928
|
init_paths();
|
|
@@ -15489,9 +15951,9 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
15489
15951
|
repoPaths?.length ? repoPaths : threads.map((t) => t.repoPath).filter(Boolean)
|
|
15490
15952
|
);
|
|
15491
15953
|
const homeRoot = sideboardWorkspacesDir();
|
|
15492
|
-
if ((0,
|
|
15954
|
+
if ((0, import_node_fs43.existsSync)(homeRoot)) {
|
|
15493
15955
|
try {
|
|
15494
|
-
for (const entry of (0,
|
|
15956
|
+
for (const entry of (0, import_node_fs43.readdirSync)(homeRoot, { withFileTypes: true })) {
|
|
15495
15957
|
if (!entry.isDirectory()) continue;
|
|
15496
15958
|
void entry;
|
|
15497
15959
|
}
|
|
@@ -15501,7 +15963,7 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
15501
15963
|
const orphans = [];
|
|
15502
15964
|
const seen = /* @__PURE__ */ new Set();
|
|
15503
15965
|
for (const repoPath of repos) {
|
|
15504
|
-
if (!repoPath || !(0,
|
|
15966
|
+
if (!repoPath || !(0, import_node_fs43.existsSync)(repoPath)) continue;
|
|
15505
15967
|
try {
|
|
15506
15968
|
const wts = await listWorktrees(repoPath);
|
|
15507
15969
|
for (const wt of wts) {
|
|
@@ -15512,7 +15974,7 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
15512
15974
|
seen.add(path);
|
|
15513
15975
|
let mtimeMs = 0;
|
|
15514
15976
|
try {
|
|
15515
|
-
mtimeMs = (0,
|
|
15977
|
+
mtimeMs = (0, import_node_fs43.statSync)(path).mtimeMs;
|
|
15516
15978
|
} catch {
|
|
15517
15979
|
mtimeMs = 0;
|
|
15518
15980
|
}
|
|
@@ -15522,16 +15984,16 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
15522
15984
|
}
|
|
15523
15985
|
try {
|
|
15524
15986
|
const root = worktreesRoot(repoPath);
|
|
15525
|
-
if ((0,
|
|
15526
|
-
for (const entry of (0,
|
|
15987
|
+
if ((0, import_node_fs43.existsSync)(root)) {
|
|
15988
|
+
for (const entry of (0, import_node_fs43.readdirSync)(root, { withFileTypes: true })) {
|
|
15527
15989
|
if (!entry.isDirectory()) continue;
|
|
15528
|
-
const path = (0,
|
|
15990
|
+
const path = (0, import_node_path43.join)(root, entry.name).replace(/\/$/, "");
|
|
15529
15991
|
if (known.has(path) || seen.has(path)) continue;
|
|
15530
|
-
if (!(0,
|
|
15992
|
+
if (!(0, import_node_fs43.existsSync)((0, import_node_path43.join)(path, ".git"))) continue;
|
|
15531
15993
|
seen.add(path);
|
|
15532
15994
|
let mtimeMs = 0;
|
|
15533
15995
|
try {
|
|
15534
|
-
mtimeMs = (0,
|
|
15996
|
+
mtimeMs = (0, import_node_fs43.statSync)(path).mtimeMs;
|
|
15535
15997
|
} catch {
|
|
15536
15998
|
mtimeMs = Date.now();
|
|
15537
15999
|
}
|
|
@@ -15583,12 +16045,12 @@ function shouldRunWorktreeCleanup(settings = loadAppSettings()) {
|
|
|
15583
16045
|
const elapsed = Date.now() - Date.parse(last);
|
|
15584
16046
|
return elapsed >= intervalHours * 36e5;
|
|
15585
16047
|
}
|
|
15586
|
-
var
|
|
16048
|
+
var import_node_fs43, import_node_path43;
|
|
15587
16049
|
var init_orphan_cleanup = __esm({
|
|
15588
16050
|
"src/git/orphan-cleanup.ts"() {
|
|
15589
16051
|
"use strict";
|
|
15590
|
-
|
|
15591
|
-
|
|
16052
|
+
import_node_fs43 = require("fs");
|
|
16053
|
+
import_node_path43 = require("path");
|
|
15592
16054
|
init_worktree();
|
|
15593
16055
|
init_thread_store();
|
|
15594
16056
|
init_paths();
|
|
@@ -15604,6 +16066,7 @@ function stripArchivedTranscript(thread) {
|
|
|
15604
16066
|
attachments: [],
|
|
15605
16067
|
pendingTurnAttachments: [],
|
|
15606
16068
|
queueAttachments: [],
|
|
16069
|
+
compactedMessages: void 0,
|
|
15607
16070
|
messages: [historyRetentionStubMessage(thread)]
|
|
15608
16071
|
},
|
|
15609
16072
|
{ touch: false }
|
|
@@ -15755,534 +16218,146 @@ var init_apply_into_main = __esm({
|
|
|
15755
16218
|
import_execa5 = require("execa");
|
|
15756
16219
|
init_worktree();
|
|
15757
16220
|
}
|
|
15758
|
-
});
|
|
15759
|
-
|
|
15760
|
-
// src/git/clone-repo.ts
|
|
15761
|
-
async function cloneRepoIntoSideboard(opts) {
|
|
15762
|
-
const url = opts.url.trim();
|
|
15763
|
-
if (!url) throw new Error("Clone URL is required");
|
|
15764
|
-
let name = opts.name?.trim();
|
|
15765
|
-
if (!name) {
|
|
15766
|
-
const leaf = (0,
|
|
15767
|
-
name = leaf || "repo";
|
|
15768
|
-
}
|
|
15769
|
-
name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
|
|
15770
|
-
const dest = (0, import_node_path43.join)(sideboardReposDir(), name);
|
|
15771
|
-
if ((0, import_node_fs43.existsSync)(dest)) {
|
|
15772
|
-
const repoPath2 = await resolveRepoRoot(dest);
|
|
15773
|
-
const workspace2 = await ensureWorkspace(repoPath2);
|
|
15774
|
-
return { repoPath: repoPath2, workspace: workspace2 };
|
|
15775
|
-
}
|
|
15776
|
-
const result = await (0, import_execa6.execa)("git", ["clone", url, dest], { reject: false });
|
|
15777
|
-
if (result.exitCode !== 0) {
|
|
15778
|
-
throw new Error(
|
|
15779
|
-
`git clone failed: ${result.stderr?.trim() || result.stdout?.trim() || `exit ${result.exitCode}`}`
|
|
15780
|
-
);
|
|
15781
|
-
}
|
|
15782
|
-
const repoPath = await resolveRepoRoot(dest);
|
|
15783
|
-
const workspace = await ensureWorkspace(repoPath);
|
|
15784
|
-
return { repoPath, workspace };
|
|
15785
|
-
}
|
|
15786
|
-
var import_node_fs43, import_node_path43, import_execa6;
|
|
15787
|
-
var init_clone_repo = __esm({
|
|
15788
|
-
"src/git/clone-repo.ts"() {
|
|
15789
|
-
"use strict";
|
|
15790
|
-
import_node_fs43 = require("fs");
|
|
15791
|
-
import_node_path43 = require("path");
|
|
15792
|
-
import_execa6 = require("execa");
|
|
15793
|
-
init_paths();
|
|
15794
|
-
init_workspaces();
|
|
15795
|
-
init_worktree();
|
|
15796
|
-
}
|
|
15797
|
-
});
|
|
15798
|
-
|
|
15799
|
-
// src/types/thread.ts
|
|
15800
|
-
function isAdoptableRunScriptRequest(req) {
|
|
15801
|
-
return Boolean(req && !req.fulfilledAt && !req.error);
|
|
15802
|
-
}
|
|
15803
|
-
var init_thread = __esm({
|
|
15804
|
-
"src/types/thread.ts"() {
|
|
15805
|
-
"use strict";
|
|
15806
|
-
}
|
|
15807
|
-
});
|
|
15808
|
-
|
|
15809
|
-
// src/store/desktop-host.ts
|
|
15810
|
-
function desktopHostPidPath() {
|
|
15811
|
-
return (0, import_node_path44.join)(appDataDir(), "desktop-host.pid");
|
|
15812
|
-
}
|
|
15813
|
-
function readDesktopHostPid() {
|
|
15814
|
-
try {
|
|
15815
|
-
const pid = Number.parseInt((0, import_node_fs44.readFileSync)(desktopHostPidPath(), "utf8").trim(), 10);
|
|
15816
|
-
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
15817
|
-
return pid;
|
|
15818
|
-
} catch {
|
|
15819
|
-
return null;
|
|
15820
|
-
}
|
|
15821
|
-
}
|
|
15822
|
-
function pidAlive(pid) {
|
|
15823
|
-
try {
|
|
15824
|
-
process.kill(pid, 0);
|
|
15825
|
-
return true;
|
|
15826
|
-
} catch {
|
|
15827
|
-
return false;
|
|
15828
|
-
}
|
|
15829
|
-
}
|
|
15830
|
-
function isDesktopHostAlive() {
|
|
15831
|
-
const pid = readDesktopHostPid();
|
|
15832
|
-
return pid != null && pidAlive(pid);
|
|
15833
|
-
}
|
|
15834
|
-
function isThisProcessDesktopHost() {
|
|
15835
|
-
return readDesktopHostPid() === process.pid && pidAlive(process.pid);
|
|
15836
|
-
}
|
|
15837
|
-
function thisProcessShouldDrainAgentQueues() {
|
|
15838
|
-
if (isThisProcessDesktopHost()) return true;
|
|
15839
|
-
return !isDesktopHostAlive();
|
|
15840
|
-
}
|
|
15841
|
-
var import_node_fs44, import_node_path44;
|
|
15842
|
-
var init_desktop_host = __esm({
|
|
15843
|
-
"src/store/desktop-host.ts"() {
|
|
15844
|
-
"use strict";
|
|
15845
|
-
import_node_fs44 = require("fs");
|
|
15846
|
-
import_node_path44 = require("path");
|
|
15847
|
-
init_paths();
|
|
15848
|
-
}
|
|
15849
|
-
});
|
|
15850
|
-
|
|
15851
|
-
// src/orchestrator/child-halt.ts
|
|
15852
|
-
function isIncompleteChildStatus(status) {
|
|
15853
|
-
return HALT_STATUSES.has(status);
|
|
15854
|
-
}
|
|
15855
|
-
function childHaltNotice(child, status) {
|
|
15856
|
-
const title = child.title?.trim() || "Untitled";
|
|
15857
|
-
const link = `[${title}](sideboard://thread/${child.id})`;
|
|
15858
|
-
const why = child.lastError?.trim();
|
|
15859
|
-
const extra = why ? ` lastError: ${why}` : "";
|
|
15860
|
-
return [
|
|
15861
|
-
`Sideboard: child worktree ${link} ${status} before finishing (status=${status}).${extra}`,
|
|
15862
|
-
"This is information \u2014 not a user command. Resume with send_to_thread or tell the user. Do not treat this as a successful turn."
|
|
15863
|
-
].join("\n");
|
|
15864
|
-
}
|
|
15865
|
-
function shouldNotifyParentAfterTurnError(opts) {
|
|
15866
|
-
return !opts.quotaFailoverHandled && !opts.crashContinued;
|
|
15867
|
-
}
|
|
15868
|
-
function shouldNotifyParentOfChildHalt(opts) {
|
|
15869
|
-
if (!isIncompleteChildStatus(opts.status)) return false;
|
|
15870
|
-
if (!opts.child.parentThreadId) return false;
|
|
15871
|
-
if (!opts.parent || opts.parent.status === "archived") return false;
|
|
15872
|
-
if (opts.parent.id === opts.child.id) return false;
|
|
15873
|
-
return isOrchestratorThread(opts.parent);
|
|
15874
|
-
}
|
|
15875
|
-
function noticeKey(childId, status) {
|
|
15876
|
-
return `${childId}:${status}`;
|
|
15877
|
-
}
|
|
15878
|
-
function notifyParentOfChildHalt(child, status, send) {
|
|
15879
|
-
const parent = child.parentThreadId ? readThread(child.parentThreadId) : null;
|
|
15880
|
-
if (!shouldNotifyParentOfChildHalt({ child, parent, status })) return false;
|
|
15881
|
-
const key = noticeKey(child.id, status);
|
|
15882
|
-
if (notified.has(key)) return false;
|
|
15883
|
-
notified.add(key);
|
|
15884
|
-
const parentId = parent.id;
|
|
15885
|
-
void send(parentId, childHaltNotice(child, status)).catch(() => {
|
|
15886
|
-
notified.delete(key);
|
|
15887
|
-
});
|
|
15888
|
-
return true;
|
|
15889
|
-
}
|
|
15890
|
-
var HALT_STATUSES, notified;
|
|
15891
|
-
var init_child_halt = __esm({
|
|
15892
|
-
"src/orchestrator/child-halt.ts"() {
|
|
15893
|
-
"use strict";
|
|
15894
|
-
init_global_workspace();
|
|
15895
|
-
init_thread_store();
|
|
15896
|
-
HALT_STATUSES = /* @__PURE__ */ new Set(["stopped", "error", "broken"]);
|
|
15897
|
-
notified = /* @__PURE__ */ new Set();
|
|
15898
|
-
}
|
|
15899
|
-
});
|
|
15900
|
-
|
|
15901
|
-
// src/mcp/wait-for-turn.ts
|
|
15902
|
-
function mcpWaitForTurnTimeoutMs(requested) {
|
|
15903
|
-
const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
|
|
15904
|
-
if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
|
|
15905
|
-
return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
|
|
15906
|
-
}
|
|
15907
|
-
function mcpWaitTaskHint(taskState, status) {
|
|
15908
|
-
switch (taskState) {
|
|
15909
|
-
case "submitted":
|
|
15910
|
-
return MCP_WAIT_QUEUED_HINT;
|
|
15911
|
-
case "working":
|
|
15912
|
-
return MCP_WAIT_STILL_RUNNING_HINT;
|
|
15913
|
-
case "input-required":
|
|
15914
|
-
return MCP_WAIT_INPUT_REQUIRED_HINT;
|
|
15915
|
-
case "canceled":
|
|
15916
|
-
return MCP_WAIT_STOPPED_HINT;
|
|
15917
|
-
case "failed":
|
|
15918
|
-
return status === "broken" ? MCP_WAIT_BROKEN_HINT : MCP_WAIT_ERROR_HINT;
|
|
15919
|
-
default:
|
|
15920
|
-
return void 0;
|
|
15921
|
-
}
|
|
15922
|
-
}
|
|
15923
|
-
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;
|
|
15924
|
-
var init_wait_for_turn = __esm({
|
|
15925
|
-
"src/mcp/wait-for-turn.ts"() {
|
|
15926
|
-
"use strict";
|
|
15927
|
-
MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
15928
|
-
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.";
|
|
15929
|
-
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.";
|
|
15930
|
-
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.";
|
|
15931
|
-
MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
|
|
15932
|
-
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.";
|
|
15933
|
-
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.";
|
|
15934
|
-
}
|
|
15935
|
-
});
|
|
15936
|
-
|
|
15937
|
-
// src/mcp/wait-for-job.ts
|
|
15938
|
-
function mcpWaitForJobTimeoutMs(requested) {
|
|
15939
|
-
return mcpWaitForTurnTimeoutMs(requested);
|
|
15940
|
-
}
|
|
15941
|
-
function sanitizeDetachedJobId(id) {
|
|
15942
|
-
const s = id.trim();
|
|
15943
|
-
if (!JOB_ID_RE.test(s)) {
|
|
15944
|
-
throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
|
|
15945
|
-
}
|
|
15946
|
-
return s;
|
|
15947
|
-
}
|
|
15948
|
-
function jobAlive(pid) {
|
|
15949
|
-
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
15950
|
-
try {
|
|
15951
|
-
process.kill(pid, 0);
|
|
15952
|
-
return true;
|
|
15953
|
-
} catch {
|
|
15954
|
-
return false;
|
|
15955
|
-
}
|
|
15956
|
-
}
|
|
15957
|
-
function processGroupAlive(pgid) {
|
|
15958
|
-
if (process.platform === "win32") return false;
|
|
15959
|
-
if (!Number.isInteger(pgid) || pgid <= 1) return false;
|
|
15960
|
-
try {
|
|
15961
|
-
process.kill(-pgid, 0);
|
|
15962
|
-
return true;
|
|
15963
|
-
} catch (err) {
|
|
15964
|
-
return err && typeof err === "object" && "code" in err && err.code === "EPERM";
|
|
15965
|
-
}
|
|
15966
|
-
}
|
|
15967
|
-
function jobTreeAlive(pid) {
|
|
15968
|
-
if (pid == null) return false;
|
|
15969
|
-
return jobAlive(pid) || processGroupAlive(pid);
|
|
15970
|
-
}
|
|
15971
|
-
function readIntFile(file) {
|
|
15972
|
-
if (!(0, import_node_fs45.existsSync)(file)) return null;
|
|
15973
|
-
const n = Number.parseInt((0, import_node_fs45.readFileSync)(file, "utf8").trim(), 10);
|
|
15974
|
-
return Number.isInteger(n) ? n : null;
|
|
15975
|
-
}
|
|
15976
|
-
function jobDir(root, id, legacy = false) {
|
|
15977
|
-
return (0, import_node_path45.join)(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
|
|
15978
|
-
}
|
|
15979
|
-
function resolveJobDir(root, id) {
|
|
15980
|
-
const modern = jobDir(root, id, false);
|
|
15981
|
-
if ((0, import_node_fs45.existsSync)(modern)) return modern;
|
|
15982
|
-
const legacy = jobDir(root, id, true);
|
|
15983
|
-
if ((0, import_node_fs45.existsSync)(legacy)) return legacy;
|
|
15984
|
-
return modern;
|
|
15985
|
-
}
|
|
15986
|
-
function listJobIdsIn(root, rel) {
|
|
15987
|
-
const dir = (0, import_node_path45.join)(root, rel);
|
|
15988
|
-
if (!(0, import_node_fs45.existsSync)(dir)) return [];
|
|
15989
|
-
return (0, import_node_fs45.readdirSync)(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
|
|
15990
|
-
}
|
|
15991
|
-
function listRunningDetachedJobs(worktreePath) {
|
|
15992
|
-
const root = worktreePath.trim();
|
|
15993
|
-
if (!root) return [];
|
|
15994
|
-
const ids = /* @__PURE__ */ new Set([
|
|
15995
|
-
...listJobIdsIn(root, DETACHED_JOBS_DIR),
|
|
15996
|
-
...listJobIdsIn(root, LEGACY_DETACHED_JOBS_DIR)
|
|
15997
|
-
]);
|
|
15998
|
-
const running = [];
|
|
15999
|
-
for (const id of ids) {
|
|
16000
|
-
const dir = resolveJobDir(root, id);
|
|
16001
|
-
const pid = readIntFile((0, import_node_path45.join)(dir, "pid"));
|
|
16002
|
-
if (jobTreeAlive(pid)) running.push(id);
|
|
16003
|
-
}
|
|
16004
|
-
return running.sort();
|
|
16005
|
-
}
|
|
16006
|
-
function looksLikeDeferredDonePromise(text7) {
|
|
16007
|
-
const t = (text7 ?? "").trim();
|
|
16008
|
-
if (!t || t.length > DEFERRED_DONE_MAX_CHARS) return false;
|
|
16009
|
-
const unquoted = t.replace(/`[^`]*`/g, " ").replace(/["“”][^"“”]*["“”]/g, " ").replace(/\s+/g, " ").trim();
|
|
16010
|
-
if (!unquoted) return false;
|
|
16011
|
-
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);
|
|
16012
|
-
}
|
|
16013
|
-
function isJobContinuePrompt(text7) {
|
|
16014
|
-
if (typeof text7 !== "string") return false;
|
|
16015
|
-
const t = text7.trim();
|
|
16016
|
-
return t.startsWith("Detached job still running:") || t.startsWith("You ended the turn after promising to report later");
|
|
16017
|
-
}
|
|
16018
|
-
function collapseGhRunWatchLog(text7) {
|
|
16019
|
-
if (!text7.includes(GH_WATCH_REFRESH)) return text7;
|
|
16020
|
-
const frames = text7.split(/(?=Refreshing run status every 3 seconds\.)/);
|
|
16021
|
-
const watchFrames = frames.filter((f) => f.includes(GH_WATCH_REFRESH));
|
|
16022
|
-
if (watchFrames.length <= 1) return text7;
|
|
16023
|
-
const prefix = frames[0]?.includes(GH_WATCH_REFRESH) ? "" : frames[0] ?? "";
|
|
16024
|
-
const last = watchFrames[watchFrames.length - 1] ?? "";
|
|
16025
|
-
const omitted = watchFrames.length - 1;
|
|
16026
|
-
const note = `(${omitted} identical gh run watch refresh${omitted === 1 ? "" : "es"} omitted)
|
|
16027
|
-
`;
|
|
16028
|
-
return [prefix.trimEnd(), note, last.trimStart()].filter(Boolean).join("\n");
|
|
16029
|
-
}
|
|
16030
|
-
function capJobLogDelta(text7) {
|
|
16031
|
-
const collapsed = collapseGhRunWatchLog(text7);
|
|
16032
|
-
const lines = collapsed.split("\n");
|
|
16033
|
-
let out = lines.length > MAX_JOB_DELTA_LINES ? `\u2026(${lines.length - MAX_JOB_DELTA_LINES} earlier lines omitted)
|
|
16034
|
-
${lines.slice(-MAX_JOB_DELTA_LINES).join("\n")}` : collapsed;
|
|
16035
|
-
if (out.length > MAX_JOB_DELTA_CHARS) {
|
|
16036
|
-
out = `\u2026(truncated ${out.length} chars)
|
|
16037
|
-
${out.slice(-MAX_JOB_DELTA_CHARS)}`;
|
|
16038
|
-
}
|
|
16039
|
-
return out;
|
|
16040
|
-
}
|
|
16041
|
-
function formatJobStillRunningContinuePrompt(jobIds) {
|
|
16042
|
-
const ids = jobIds.join(", ");
|
|
16043
|
-
return [
|
|
16044
|
-
`Detached job still running: ${ids}.`,
|
|
16045
|
-
"Do not end this turn. Loop wait_for_job (same id) until stillRunning is false. The log pane updates from each wait result.",
|
|
16046
|
-
"If the job is hanging or doing the wrong thing, call stop_job (same id) instead of looping forever.",
|
|
16047
|
-
"Then report the result. Do not tell the user you will let them know later."
|
|
16048
|
-
].join(" ");
|
|
16049
|
-
}
|
|
16050
|
-
function formatDeferredDoneContinuePrompt() {
|
|
16051
|
-
return [
|
|
16052
|
-
"You ended the turn after promising to report later, but no detached job is running.",
|
|
16053
|
-
"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).",
|
|
16054
|
-
"Do not say you will let the user know later."
|
|
16055
|
-
].join(" ");
|
|
16056
|
-
}
|
|
16057
|
-
function turnWatchedDetachedJob(parts) {
|
|
16058
|
-
return parts.some((p) => {
|
|
16059
|
-
if (p.type !== "tool") return false;
|
|
16060
|
-
if (/wait_for_job$/i.test(p.name ?? "") || /stop_job$/i.test(p.name ?? "")) return true;
|
|
16061
|
-
const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
|
|
16062
|
-
return /detached-job\.(?:js|cjs)\b/i.test(blob);
|
|
16063
|
-
});
|
|
16064
|
-
}
|
|
16065
|
-
function planJobContinue(opts) {
|
|
16066
|
-
if (opts.isOrchestrator) return { action: "none" };
|
|
16067
|
-
if (opts.agent === "brightsy") return { action: "none" };
|
|
16068
|
-
if (opts.queueLength > 0) return { action: "none" };
|
|
16069
|
-
if (opts.continueCount >= MAX_JOB_CONTINUES) return { action: "none" };
|
|
16070
|
-
const farewell = looksLikeDeferredDonePromise(opts.chatText);
|
|
16071
|
-
if (opts.runningJobIds.length > 0 && (farewell || opts.watchedJob)) {
|
|
16072
|
-
return {
|
|
16073
|
-
action: "wait",
|
|
16074
|
-
jobIds: opts.runningJobIds,
|
|
16075
|
-
prompt: formatJobStillRunningContinuePrompt(opts.runningJobIds)
|
|
16076
|
-
};
|
|
16077
|
-
}
|
|
16078
|
-
if (looksLikeDeferredDonePromise(opts.chatText) && !opts.alreadyNudged) {
|
|
16079
|
-
return { action: "nudge", prompt: formatDeferredDoneContinuePrompt() };
|
|
16080
|
-
}
|
|
16081
|
-
return { action: "none" };
|
|
16082
|
-
}
|
|
16083
|
-
function tailProgress(logFile, maxLines = 12) {
|
|
16084
|
-
if (!(0, import_node_fs45.existsSync)(logFile)) return "(no log yet)";
|
|
16085
|
-
const lines = (0, import_node_fs45.readFileSync)(logFile, "utf8").split("\n");
|
|
16086
|
-
return lines.slice(-maxLines).join("\n");
|
|
16087
|
-
}
|
|
16088
|
-
function lastProgressLine(progress) {
|
|
16089
|
-
const last = progress.split("\n").map((l) => l.trim()).filter((l) => l && l !== "(no log yet)").at(-1);
|
|
16090
|
-
return last ? last.slice(0, 72) : void 0;
|
|
16091
|
-
}
|
|
16092
|
-
function readLogLines(file) {
|
|
16093
|
-
if (!(0, import_node_fs45.existsSync)(file)) return [];
|
|
16094
|
-
const lines = (0, import_node_fs45.readFileSync)(file, "utf8").split("\n");
|
|
16095
|
-
if (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
16096
|
-
return lines;
|
|
16097
|
-
}
|
|
16098
|
-
function takeDelta(logFile, cursorFile) {
|
|
16099
|
-
const lines = readLogLines(logFile);
|
|
16100
|
-
const cursor = readIntFile(cursorFile) ?? 0;
|
|
16101
|
-
const start = Math.min(Math.max(0, cursor), lines.length);
|
|
16102
|
-
return { delta: capJobLogDelta(lines.slice(start).join("\n")), nextCursor: lines.length };
|
|
16103
|
-
}
|
|
16104
|
-
function snapshotJob(dir) {
|
|
16105
|
-
const pid = readIntFile((0, import_node_path45.join)(dir, "pid"));
|
|
16106
|
-
const running = jobTreeAlive(pid);
|
|
16107
|
-
return {
|
|
16108
|
-
pid,
|
|
16109
|
-
running,
|
|
16110
|
-
exitCode: readIntFile((0, import_node_path45.join)(dir, "exit")),
|
|
16111
|
-
log: (0, import_node_path45.join)(dir, "log"),
|
|
16112
|
-
cursor: (0, import_node_path45.join)(dir, "present.cursor"),
|
|
16113
|
-
progress: tailProgress((0, import_node_path45.join)(dir, "log"))
|
|
16114
|
-
};
|
|
16115
|
-
}
|
|
16116
|
-
function toResult(id, snap, extra) {
|
|
16117
|
-
const failed = extra?.failed === true || !snap.running && snap.exitCode != null && snap.exitCode !== 0;
|
|
16118
|
-
const ok = !snap.running && snap.exitCode === 0;
|
|
16119
|
-
const stillRunning = snap.running && !ok;
|
|
16120
|
-
const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
|
|
16121
|
-
try {
|
|
16122
|
-
(0, import_node_fs45.mkdirSync)((0, import_node_path45.dirname)(snap.cursor), { recursive: true });
|
|
16123
|
-
(0, import_node_fs45.writeFileSync)(snap.cursor, `${nextCursor}
|
|
16124
|
-
`);
|
|
16125
|
-
} catch {
|
|
16126
|
-
}
|
|
16127
|
-
const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
|
|
16128
|
-
const progress = extra?.progress ?? snap.progress;
|
|
16129
|
-
return {
|
|
16130
|
-
stillRunning,
|
|
16131
|
-
ok,
|
|
16132
|
-
failed: Boolean(failed && !stillRunning && !ok),
|
|
16133
|
-
status,
|
|
16134
|
-
id,
|
|
16135
|
-
pid: snap.pid,
|
|
16136
|
-
exitCode: snap.exitCode ?? void 0,
|
|
16137
|
-
phase: lastProgressLine(progress),
|
|
16138
|
-
delta,
|
|
16139
|
-
progress,
|
|
16140
|
-
hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
|
|
16141
|
-
};
|
|
16142
|
-
}
|
|
16143
|
-
async function sleepMs(ms) {
|
|
16144
|
-
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
16145
|
-
}
|
|
16146
|
-
async function waitForDetachedJob(cwd, id, opts) {
|
|
16147
|
-
const jobId = sanitizeDetachedJobId(id);
|
|
16148
|
-
const root = cwd.trim() || process.cwd();
|
|
16149
|
-
const dir = resolveJobDir(root, jobId);
|
|
16150
|
-
const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
|
|
16151
|
-
const sleep = opts?.sleep ?? sleepMs;
|
|
16152
|
-
if (!(0, import_node_fs45.existsSync)(dir)) {
|
|
16153
|
-
return {
|
|
16154
|
-
stillRunning: false,
|
|
16155
|
-
ok: false,
|
|
16156
|
-
failed: true,
|
|
16157
|
-
status: "failed",
|
|
16158
|
-
id: jobId,
|
|
16159
|
-
delta: "",
|
|
16160
|
-
progress: "No detached job. Start one first.",
|
|
16161
|
-
hint: "Start with detached-job.cjs start <id> -- <command>, then call wait_for_job again."
|
|
16162
|
-
};
|
|
16163
|
-
}
|
|
16164
|
-
const deadline = Date.now() + timeoutMs;
|
|
16165
|
-
let snap = snapshotJob(dir);
|
|
16166
|
-
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
16167
|
-
if (!snap.running && snap.pid == null && snap.progress === "(no log yet)") {
|
|
16168
|
-
return toResult(jobId, snap, {
|
|
16169
|
-
failed: true,
|
|
16170
|
-
progress: "No detached job. Start one first."
|
|
16171
|
-
});
|
|
16221
|
+
});
|
|
16222
|
+
|
|
16223
|
+
// src/git/clone-repo.ts
|
|
16224
|
+
async function cloneRepoIntoSideboard(opts) {
|
|
16225
|
+
const url = opts.url.trim();
|
|
16226
|
+
if (!url) throw new Error("Clone URL is required");
|
|
16227
|
+
let name = opts.name?.trim();
|
|
16228
|
+
if (!name) {
|
|
16229
|
+
const leaf = (0, import_node_path44.basename)(url.replace(/\/$/, "").replace(/\.git$/, ""));
|
|
16230
|
+
name = leaf || "repo";
|
|
16172
16231
|
}
|
|
16173
|
-
|
|
16174
|
-
|
|
16175
|
-
|
|
16176
|
-
|
|
16177
|
-
|
|
16232
|
+
name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
|
|
16233
|
+
const dest = (0, import_node_path44.join)(sideboardReposDir(), name);
|
|
16234
|
+
if ((0, import_node_fs44.existsSync)(dest)) {
|
|
16235
|
+
const repoPath2 = await resolveRepoRoot(dest);
|
|
16236
|
+
const workspace2 = await ensureWorkspace(repoPath2);
|
|
16237
|
+
return { repoPath: repoPath2, workspace: workspace2 };
|
|
16178
16238
|
}
|
|
16179
|
-
|
|
16180
|
-
|
|
16239
|
+
const result = await (0, import_execa6.execa)("git", ["clone", url, dest], { reject: false });
|
|
16240
|
+
if (result.exitCode !== 0) {
|
|
16241
|
+
throw new Error(
|
|
16242
|
+
`git clone failed: ${result.stderr?.trim() || result.stdout?.trim() || `exit ${result.exitCode}`}`
|
|
16243
|
+
);
|
|
16244
|
+
}
|
|
16245
|
+
const repoPath = await resolveRepoRoot(dest);
|
|
16246
|
+
const workspace = await ensureWorkspace(repoPath);
|
|
16247
|
+
return { repoPath, workspace };
|
|
16181
16248
|
}
|
|
16182
|
-
|
|
16183
|
-
|
|
16184
|
-
|
|
16185
|
-
|
|
16186
|
-
|
|
16187
|
-
|
|
16188
|
-
|
|
16189
|
-
|
|
16249
|
+
var import_node_fs44, import_node_path44, import_execa6;
|
|
16250
|
+
var init_clone_repo = __esm({
|
|
16251
|
+
"src/git/clone-repo.ts"() {
|
|
16252
|
+
"use strict";
|
|
16253
|
+
import_node_fs44 = require("fs");
|
|
16254
|
+
import_node_path44 = require("path");
|
|
16255
|
+
import_execa6 = require("execa");
|
|
16256
|
+
init_paths();
|
|
16257
|
+
init_workspaces();
|
|
16258
|
+
init_worktree();
|
|
16259
|
+
}
|
|
16260
|
+
});
|
|
16261
|
+
|
|
16262
|
+
// src/types/thread.ts
|
|
16263
|
+
function isAdoptableRunScriptRequest(req) {
|
|
16264
|
+
return Boolean(req && !req.fulfilledAt && !req.error);
|
|
16265
|
+
}
|
|
16266
|
+
var init_thread = __esm({
|
|
16267
|
+
"src/types/thread.ts"() {
|
|
16268
|
+
"use strict";
|
|
16190
16269
|
}
|
|
16270
|
+
});
|
|
16271
|
+
|
|
16272
|
+
// src/store/desktop-host.ts
|
|
16273
|
+
function desktopHostPidPath() {
|
|
16274
|
+
return (0, import_node_path45.join)(appDataDir(), "desktop-host.pid");
|
|
16275
|
+
}
|
|
16276
|
+
function readDesktopHostPid() {
|
|
16191
16277
|
try {
|
|
16192
|
-
|
|
16278
|
+
const pid = Number.parseInt((0, import_node_fs45.readFileSync)(desktopHostPidPath(), "utf8").trim(), 10);
|
|
16279
|
+
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
16280
|
+
return pid;
|
|
16193
16281
|
} catch {
|
|
16282
|
+
return null;
|
|
16194
16283
|
}
|
|
16195
16284
|
}
|
|
16196
|
-
function
|
|
16285
|
+
function pidAlive(pid) {
|
|
16197
16286
|
try {
|
|
16198
|
-
(
|
|
16199
|
-
|
|
16200
|
-
`;
|
|
16201
|
-
(0, import_node_fs45.appendFileSync)(logFile, body);
|
|
16287
|
+
process.kill(pid, 0);
|
|
16288
|
+
return true;
|
|
16202
16289
|
} catch {
|
|
16290
|
+
return false;
|
|
16203
16291
|
}
|
|
16204
16292
|
}
|
|
16205
|
-
|
|
16206
|
-
const
|
|
16207
|
-
|
|
16208
|
-
const dir = resolveJobDir(root, jobId);
|
|
16209
|
-
const sleep = opts?.sleep ?? sleepMs;
|
|
16210
|
-
const why = opts?.reason?.trim() ?? "";
|
|
16211
|
-
if (!(0, import_node_fs45.existsSync)(dir)) {
|
|
16212
|
-
return {
|
|
16213
|
-
stillRunning: false,
|
|
16214
|
-
ok: false,
|
|
16215
|
-
failed: true,
|
|
16216
|
-
status: "failed",
|
|
16217
|
-
id: jobId,
|
|
16218
|
-
delta: "",
|
|
16219
|
-
progress: "No detached job. Start one first.",
|
|
16220
|
-
hint: "Start with detached-job.cjs start <id> -- <command>, then wait_for_job or stop_job.",
|
|
16221
|
-
stopped: false,
|
|
16222
|
-
reason: "not-found"
|
|
16223
|
-
};
|
|
16224
|
-
}
|
|
16225
|
-
let snap = snapshotJob(dir);
|
|
16226
|
-
if (!snap.running) {
|
|
16227
|
-
const result2 = toResult(jobId, snap);
|
|
16228
|
-
return {
|
|
16229
|
-
...result2,
|
|
16230
|
-
stopped: false,
|
|
16231
|
-
reason: "not-running",
|
|
16232
|
-
hint: "That job is already finished. Read the log; do not wait again unless you start a new command."
|
|
16233
|
-
};
|
|
16234
|
-
}
|
|
16235
|
-
appendJobLog(snap.log, `$ stop${why ? `: ${why}` : ""}`);
|
|
16236
|
-
const targetPid = snap.pid;
|
|
16237
|
-
if (targetPid != null) killProcessTree(targetPid, "SIGTERM");
|
|
16238
|
-
const graceMs = Number.isFinite(opts?.graceMs) ? Math.max(50, opts.graceMs) : 2e3;
|
|
16239
|
-
const deadline = Date.now() + graceMs;
|
|
16240
|
-
while (Date.now() < deadline && jobTreeAlive(targetPid)) {
|
|
16241
|
-
await sleep(Math.min(100, Math.max(20, deadline - Date.now())));
|
|
16242
|
-
}
|
|
16243
|
-
if (targetPid != null) {
|
|
16244
|
-
killProcessTree(targetPid, "SIGKILL");
|
|
16245
|
-
await sleep(100);
|
|
16246
|
-
}
|
|
16247
|
-
const exitFile = (0, import_node_path45.join)(dir, "exit");
|
|
16248
|
-
if (!(0, import_node_fs45.existsSync)(exitFile)) {
|
|
16249
|
-
try {
|
|
16250
|
-
(0, import_node_fs45.writeFileSync)(exitFile, "1\n");
|
|
16251
|
-
} catch {
|
|
16252
|
-
}
|
|
16253
|
-
}
|
|
16254
|
-
snap = snapshotJob(dir);
|
|
16255
|
-
const stillRunning = jobTreeAlive(targetPid) || snap.running;
|
|
16256
|
-
const result = toResult(jobId, snap, { failed: true });
|
|
16257
|
-
const stopped = !stillRunning;
|
|
16258
|
-
return {
|
|
16259
|
-
...result,
|
|
16260
|
-
stillRunning,
|
|
16261
|
-
ok: false,
|
|
16262
|
-
failed: true,
|
|
16263
|
-
status: stillRunning ? "running" : "failed",
|
|
16264
|
-
stopped,
|
|
16265
|
-
reason: stopped ? "stopped" : "still-running",
|
|
16266
|
-
stopReason: why || void 0,
|
|
16267
|
-
hint: stopped ? MCP_STOP_JOB_HINT : MCP_WAIT_JOB_STILL_RUNNING_HINT
|
|
16268
|
-
};
|
|
16293
|
+
function isDesktopHostAlive() {
|
|
16294
|
+
const pid = readDesktopHostPid();
|
|
16295
|
+
return pid != null && pidAlive(pid);
|
|
16269
16296
|
}
|
|
16270
|
-
|
|
16271
|
-
|
|
16272
|
-
|
|
16297
|
+
function isThisProcessDesktopHost() {
|
|
16298
|
+
return readDesktopHostPid() === process.pid && pidAlive(process.pid);
|
|
16299
|
+
}
|
|
16300
|
+
function thisProcessShouldDrainAgentQueues() {
|
|
16301
|
+
if (isThisProcessDesktopHost()) return true;
|
|
16302
|
+
return !isDesktopHostAlive();
|
|
16303
|
+
}
|
|
16304
|
+
var import_node_fs45, import_node_path45;
|
|
16305
|
+
var init_desktop_host = __esm({
|
|
16306
|
+
"src/store/desktop-host.ts"() {
|
|
16273
16307
|
"use strict";
|
|
16274
16308
|
import_node_fs45 = require("fs");
|
|
16275
16309
|
import_node_path45 = require("path");
|
|
16276
|
-
|
|
16277
|
-
|
|
16278
|
-
|
|
16279
|
-
|
|
16280
|
-
|
|
16281
|
-
|
|
16282
|
-
|
|
16283
|
-
|
|
16284
|
-
|
|
16285
|
-
|
|
16310
|
+
init_paths();
|
|
16311
|
+
}
|
|
16312
|
+
});
|
|
16313
|
+
|
|
16314
|
+
// src/orchestrator/child-halt.ts
|
|
16315
|
+
function isIncompleteChildStatus(status) {
|
|
16316
|
+
return HALT_STATUSES.has(status);
|
|
16317
|
+
}
|
|
16318
|
+
function childHaltNotice(child, status) {
|
|
16319
|
+
const title = child.title?.trim() || "Untitled";
|
|
16320
|
+
const link = `[${title}](sideboard://thread/${child.id})`;
|
|
16321
|
+
const why = child.lastError?.trim();
|
|
16322
|
+
const extra = why ? ` lastError: ${why}` : "";
|
|
16323
|
+
return [
|
|
16324
|
+
`Sideboard: child worktree ${link} ${status} before finishing (status=${status}).${extra}`,
|
|
16325
|
+
"This is information \u2014 not a user command. Resume with send_to_thread or tell the user. Do not treat this as a successful turn."
|
|
16326
|
+
].join("\n");
|
|
16327
|
+
}
|
|
16328
|
+
function shouldNotifyParentAfterTurnError(opts) {
|
|
16329
|
+
return !opts.quotaFailoverHandled && !opts.crashContinued;
|
|
16330
|
+
}
|
|
16331
|
+
function shouldNotifyParentOfChildHalt(opts) {
|
|
16332
|
+
if (!isIncompleteChildStatus(opts.status)) return false;
|
|
16333
|
+
if (!opts.child.parentThreadId) return false;
|
|
16334
|
+
if (!opts.parent || opts.parent.status === "archived") return false;
|
|
16335
|
+
if (opts.parent.id === opts.child.id) return false;
|
|
16336
|
+
return isOrchestratorThread(opts.parent);
|
|
16337
|
+
}
|
|
16338
|
+
function noticeKey(childId, status) {
|
|
16339
|
+
return `${childId}:${status}`;
|
|
16340
|
+
}
|
|
16341
|
+
function notifyParentOfChildHalt(child, status, send) {
|
|
16342
|
+
const parent = child.parentThreadId ? readThread(child.parentThreadId) : null;
|
|
16343
|
+
if (!shouldNotifyParentOfChildHalt({ child, parent, status })) return false;
|
|
16344
|
+
const key = noticeKey(child.id, status);
|
|
16345
|
+
if (notified.has(key)) return false;
|
|
16346
|
+
notified.add(key);
|
|
16347
|
+
const parentId = parent.id;
|
|
16348
|
+
void send(parentId, childHaltNotice(child, status)).catch(() => {
|
|
16349
|
+
notified.delete(key);
|
|
16350
|
+
});
|
|
16351
|
+
return true;
|
|
16352
|
+
}
|
|
16353
|
+
var HALT_STATUSES, notified;
|
|
16354
|
+
var init_child_halt = __esm({
|
|
16355
|
+
"src/orchestrator/child-halt.ts"() {
|
|
16356
|
+
"use strict";
|
|
16357
|
+
init_global_workspace();
|
|
16358
|
+
init_thread_store();
|
|
16359
|
+
HALT_STATUSES = /* @__PURE__ */ new Set(["stopped", "error", "broken"]);
|
|
16360
|
+
notified = /* @__PURE__ */ new Set();
|
|
16286
16361
|
}
|
|
16287
16362
|
});
|
|
16288
16363
|
|
|
@@ -21349,6 +21424,13 @@ function isPresentPlanToolName(name) {
|
|
|
21349
21424
|
if (!name) return false;
|
|
21350
21425
|
return /present_plan$/i.test(name) || /^mcp__sideboard__present_plan$/i.test(name);
|
|
21351
21426
|
}
|
|
21427
|
+
function threadMayPresentPlan(thread) {
|
|
21428
|
+
return Boolean(thread?.planMode);
|
|
21429
|
+
}
|
|
21430
|
+
function shouldRegisterPresentPlan(thread) {
|
|
21431
|
+
if (thread == null) return true;
|
|
21432
|
+
return threadMayPresentPlan(thread);
|
|
21433
|
+
}
|
|
21352
21434
|
function extractPresentedPlan(parts) {
|
|
21353
21435
|
if (!parts?.length) return null;
|
|
21354
21436
|
for (let i = parts.length - 1; i >= 0; i--) {
|
|
@@ -21386,7 +21468,7 @@ function resolvePlanMarkdown(opts) {
|
|
|
21386
21468
|
}
|
|
21387
21469
|
return null;
|
|
21388
21470
|
}
|
|
21389
|
-
var PLAN_FILE_REL, PLAN_FILE_NAME, LEGACY_PLAN_FILE_REL;
|
|
21471
|
+
var PLAN_FILE_REL, PLAN_FILE_NAME, LEGACY_PLAN_FILE_REL, PRESENT_PLAN_TOOL_DESCRIPTION, PRESENT_PLAN_REQUIRES_PLAN_MODE;
|
|
21390
21472
|
var init_plan_present = __esm({
|
|
21391
21473
|
"src/plan/plan-present.ts"() {
|
|
21392
21474
|
"use strict";
|
|
@@ -21394,6 +21476,8 @@ var init_plan_present = __esm({
|
|
|
21394
21476
|
PLAN_FILE_REL = `${ATTACHMENTS_DIR}/plan.md`;
|
|
21395
21477
|
PLAN_FILE_NAME = "plan.md";
|
|
21396
21478
|
LEGACY_PLAN_FILE_REL = ".sideboard/plan.md";
|
|
21479
|
+
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.";
|
|
21480
|
+
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.";
|
|
21397
21481
|
}
|
|
21398
21482
|
});
|
|
21399
21483
|
|
|
@@ -21403,11 +21487,15 @@ __export(plan_file_exports, {
|
|
|
21403
21487
|
LEGACY_PLAN_FILE_REL: () => LEGACY_PLAN_FILE_REL,
|
|
21404
21488
|
PLAN_FILE_NAME: () => PLAN_FILE_NAME,
|
|
21405
21489
|
PLAN_FILE_REL: () => PLAN_FILE_REL,
|
|
21490
|
+
PRESENT_PLAN_REQUIRES_PLAN_MODE: () => PRESENT_PLAN_REQUIRES_PLAN_MODE,
|
|
21491
|
+
PRESENT_PLAN_TOOL_DESCRIPTION: () => PRESENT_PLAN_TOOL_DESCRIPTION,
|
|
21406
21492
|
extractPresentedPlan: () => extractPresentedPlan,
|
|
21407
21493
|
isPresentPlanToolName: () => isPresentPlanToolName,
|
|
21408
21494
|
planFileAbs: () => planFileAbs,
|
|
21409
21495
|
readPlanFile: () => readPlanFile,
|
|
21410
21496
|
resolvePlanMarkdown: () => resolvePlanMarkdown,
|
|
21497
|
+
shouldRegisterPresentPlan: () => shouldRegisterPresentPlan,
|
|
21498
|
+
threadMayPresentPlan: () => threadMayPresentPlan,
|
|
21411
21499
|
writePlanFile: () => writePlanFile
|
|
21412
21500
|
});
|
|
21413
21501
|
function ensureAttachmentsGitignore(worktreePath) {
|
|
@@ -23075,7 +23163,8 @@ var init_orchestrator = __esm({
|
|
|
23075
23163
|
if (compact.didCompact) {
|
|
23076
23164
|
thread = updateThread(threadId, {
|
|
23077
23165
|
messages: compact.thread.messages,
|
|
23078
|
-
sessionId: compact.thread.sessionId
|
|
23166
|
+
sessionId: compact.thread.sessionId,
|
|
23167
|
+
compactedMessages: compact.thread.compactedMessages
|
|
23079
23168
|
});
|
|
23080
23169
|
this.emit({
|
|
23081
23170
|
type: "context_compacted",
|
|
@@ -23089,9 +23178,6 @@ var init_orchestrator = __esm({
|
|
|
23089
23178
|
this.emit({ type: "error", threadId, message: `context compact failed: ${message}` });
|
|
23090
23179
|
thread = this.requireThread(threadId);
|
|
23091
23180
|
}
|
|
23092
|
-
if (this.stoppedTurns.has(threadId)) {
|
|
23093
|
-
return;
|
|
23094
|
-
}
|
|
23095
23181
|
const promptText = typeof prompt === "string" ? prompt : "";
|
|
23096
23182
|
const autoContinue = isJobContinuePrompt(promptText) || promptText.startsWith("The previous agent process ended before it finished.");
|
|
23097
23183
|
const parked = takePendingTurnAttachments(thread);
|
|
@@ -23108,6 +23194,14 @@ var init_orchestrator = __esm({
|
|
|
23108
23194
|
agent: thread.agent,
|
|
23109
23195
|
orch: isOrchestratorThread(thread)
|
|
23110
23196
|
});
|
|
23197
|
+
if (this.stoppedTurns.has(threadId)) {
|
|
23198
|
+
const stopped = writeLiveStatus(threadId, "stopped");
|
|
23199
|
+
if (stopped?.status === "stopped") {
|
|
23200
|
+
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
23201
|
+
}
|
|
23202
|
+
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
23203
|
+
return;
|
|
23204
|
+
}
|
|
23111
23205
|
thread = this.requireThread(threadId);
|
|
23112
23206
|
const gitPrompt = expandCanonicalGitRequest(promptText);
|
|
23113
23207
|
const { agentPrompt: expandedPrompt } = expandComposerPrompt(
|
|
@@ -23166,7 +23260,7 @@ var init_orchestrator = __esm({
|
|
|
23166
23260
|
].filter(Boolean).join("\n\n") || void 0;
|
|
23167
23261
|
const prGateDirective = thread.agent !== "brightsy" && !isOrchestratorThread(thread) && mentionsPrGoal(promptText) ? formatPrGateDirective() : null;
|
|
23168
23262
|
const agentPrompt = [
|
|
23169
|
-
thread.planMode
|
|
23263
|
+
planModeTurnInstruction(thread.planMode),
|
|
23170
23264
|
orchestrationReminder,
|
|
23171
23265
|
prGateDirective,
|
|
23172
23266
|
injectedNoticeContext,
|
|
@@ -23669,6 +23763,9 @@ var init_orchestrator = __esm({
|
|
|
23669
23763
|
}
|
|
23670
23764
|
const preferredPorts = active?.ports?.length ? active.ports : active?.port != null ? [active.port] : void 0;
|
|
23671
23765
|
if (active && !existing) {
|
|
23766
|
+
if (typeof active.pid === "number") {
|
|
23767
|
+
killDetachedRunTree(active.pid, thread.worktreePath);
|
|
23768
|
+
}
|
|
23672
23769
|
killListenersOnPorts(collectActiveRunPorts([active]));
|
|
23673
23770
|
this.syncWorktreeRuns(
|
|
23674
23771
|
thread.worktreePath,
|
|
@@ -23741,7 +23838,8 @@ var init_orchestrator = __esm({
|
|
|
23741
23838
|
scriptName: resolvedName,
|
|
23742
23839
|
port: handle.port,
|
|
23743
23840
|
ports: handle.ports,
|
|
23744
|
-
startedAt
|
|
23841
|
+
startedAt,
|
|
23842
|
+
...typeof handle.pid === "number" && handle.pid > 1 ? { pid: handle.pid } : {}
|
|
23745
23843
|
};
|
|
23746
23844
|
const nextRuns = [
|
|
23747
23845
|
...latestShared.activeRuns.filter((r) => r.scriptName !== resolvedName),
|
|
@@ -23887,6 +23985,9 @@ var init_orchestrator = __esm({
|
|
|
23887
23985
|
}
|
|
23888
23986
|
const run2 = shared.activeRuns.find((r) => r.scriptName === scriptName);
|
|
23889
23987
|
if (!hadHandle2 && run2) {
|
|
23988
|
+
if (typeof run2.pid === "number") {
|
|
23989
|
+
killDetachedRunTree(run2.pid, thread.worktreePath);
|
|
23990
|
+
}
|
|
23890
23991
|
killListenersOnPorts(collectActiveRunPorts([run2]));
|
|
23891
23992
|
}
|
|
23892
23993
|
const remaining = shared.activeRuns.filter((r) => r.scriptName !== scriptName);
|
|
@@ -23908,6 +24009,11 @@ var init_orchestrator = __esm({
|
|
|
23908
24009
|
}
|
|
23909
24010
|
}
|
|
23910
24011
|
if (!hadHandle && shared.activeRuns.length > 0) {
|
|
24012
|
+
for (const run2 of shared.activeRuns) {
|
|
24013
|
+
if (typeof run2.pid === "number") {
|
|
24014
|
+
killDetachedRunTree(run2.pid, thread.worktreePath);
|
|
24015
|
+
}
|
|
24016
|
+
}
|
|
23911
24017
|
killListenersOnPorts(collectActiveRunPorts(shared.activeRuns));
|
|
23912
24018
|
}
|
|
23913
24019
|
this.syncWorktreeRuns(thread.worktreePath, [], null);
|
|
@@ -23949,6 +24055,11 @@ var init_orchestrator = __esm({
|
|
|
23949
24055
|
(k) => isWorktreeRunProcessKey(k, thread.worktreePath) || siblings.some((t) => k.startsWith(`${t.id}:run:`) || k === `${t.id}:dev`)
|
|
23950
24056
|
);
|
|
23951
24057
|
if (!force && hasLiveHandle) continue;
|
|
24058
|
+
for (const run2 of shared.activeRuns) {
|
|
24059
|
+
if (typeof run2.pid === "number") {
|
|
24060
|
+
killDetachedRunTree(run2.pid, thread.worktreePath);
|
|
24061
|
+
}
|
|
24062
|
+
}
|
|
23952
24063
|
killListenersOnPorts(collectActiveRunPorts(shared.activeRuns));
|
|
23953
24064
|
if (shared.devPort != null) {
|
|
23954
24065
|
killListenersOnPorts([shared.devPort]);
|
|
@@ -26577,6 +26688,7 @@ function mcpArchiveBlockedReason(thread) {
|
|
|
26577
26688
|
|
|
26578
26689
|
// src/mcp/server.ts
|
|
26579
26690
|
init_profile();
|
|
26691
|
+
init_plan_present();
|
|
26580
26692
|
|
|
26581
26693
|
// src/mcp/run-script-ref.ts
|
|
26582
26694
|
init_chat_tabs();
|
|
@@ -29507,31 +29619,40 @@ async function startMcpServer() {
|
|
|
29507
29619
|
return mcpJson(payload);
|
|
29508
29620
|
}
|
|
29509
29621
|
);
|
|
29510
|
-
|
|
29511
|
-
|
|
29512
|
-
|
|
29513
|
-
|
|
29514
|
-
|
|
29515
|
-
|
|
29516
|
-
|
|
29517
|
-
|
|
29518
|
-
|
|
29519
|
-
|
|
29520
|
-
|
|
29521
|
-
|
|
29522
|
-
const
|
|
29622
|
+
const callingThreadId = process.env[SIDEBOARD_THREAD_ID_ENV]?.trim() || "";
|
|
29623
|
+
const callingThread = callingThreadId ? orch.getThread(callingThreadId) : null;
|
|
29624
|
+
if (shouldRegisterPresentPlan(callingThread)) {
|
|
29625
|
+
server.tool(
|
|
29626
|
+
"present_plan",
|
|
29627
|
+
PRESENT_PLAN_TOOL_DESCRIPTION,
|
|
29628
|
+
{
|
|
29629
|
+
title: import_zod9.z.string().optional().describe("Short plan title (defaults to Plan)"),
|
|
29630
|
+
content: import_zod9.z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
|
|
29631
|
+
thread_id: import_zod9.z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
|
|
29632
|
+
},
|
|
29633
|
+
async ({ title, content, thread_id }) => {
|
|
29634
|
+
const { writePlanFile: writePlanFile2 } = await Promise.resolve().then(() => (init_plan_file(), plan_file_exports));
|
|
29635
|
+
const ref = thread_id?.trim() || callingThreadId;
|
|
29636
|
+
const t = ref ? orch.getThread(ref) : null;
|
|
29637
|
+
if (t && !threadMayPresentPlan(t)) {
|
|
29638
|
+
return mcpJson(
|
|
29639
|
+
{ ok: false, error: PRESENT_PLAN_REQUIRES_PLAN_MODE },
|
|
29640
|
+
true
|
|
29641
|
+
);
|
|
29642
|
+
}
|
|
29643
|
+
let root = process.cwd();
|
|
29523
29644
|
if (t?.worktreePath?.trim()) root = t.worktreePath;
|
|
29645
|
+
const path = writePlanFile2(root, content);
|
|
29646
|
+
const payload = {
|
|
29647
|
+
ok: true,
|
|
29648
|
+
path,
|
|
29649
|
+
title: title?.trim() || "Plan",
|
|
29650
|
+
message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
|
|
29651
|
+
};
|
|
29652
|
+
return mcpJson(payload);
|
|
29524
29653
|
}
|
|
29525
|
-
|
|
29526
|
-
|
|
29527
|
-
ok: true,
|
|
29528
|
-
path,
|
|
29529
|
-
title: title?.trim() || "Plan",
|
|
29530
|
-
message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
|
|
29531
|
-
};
|
|
29532
|
-
return mcpJson(payload);
|
|
29533
|
-
}
|
|
29534
|
-
);
|
|
29654
|
+
);
|
|
29655
|
+
}
|
|
29535
29656
|
server.tool(
|
|
29536
29657
|
"present_schema",
|
|
29537
29658
|
"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.",
|