@sideboard-ai/core 0.1.242 → 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-LRMJ6V6U.js → chunk-DARSRQUE.js} +1503 -1462
- 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-R3YKDTVE.js → chunk-NEOAHPMK.js} +1569 -1526
- 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 +574 -464
- package/dist/index.d.cts +43 -3
- package/dist/index.d.ts +43 -3
- package/dist/index.js +70 -38
- package/dist/mcp/run-stdio.cjs +1633 -1544
- package/dist/mcp/run-stdio.js +50 -36
- package/dist/{orchestrator-66ZFQHW5.js → orchestrator-3IOISDFS.js} +7 -7
- package/dist/{orchestrator-VYNPNHY4.js → orchestrator-YDPDMXUY.js} +7 -7
- package/dist/{plan-file-UUKCZEYP.js → plan-file-7MMIPV7Y.js} +9 -1
- package/dist/{plan-file-2EPHYRX7.js → plan-file-CKXUFTQ2.js} +9 -1
- package/dist/{thread-store-YOPKY74K.js → thread-store-JQB7KZJR.js} +1 -1
- package/dist/{thread-store-AYPBEZT5.js → thread-store-SCV2IZYS.js} +1 -1
- package/dist/{workspaces-4SFSCYKT.js → workspaces-OIBSD5B6.js} +4 -4
- package/dist/{workspaces-JX5R4V24.js → workspaces-WYC5TLC3.js} +4 -4
- package/dist/{worktree-Y5FES5D6.js → worktree-5VRZVGP3.js} +3 -3
- package/dist/{worktree-AGVPKDBH.js → worktree-DGDM7LGV.js} +3 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1240,6 +1240,7 @@ function archivedUpdatedMs(thread) {
|
|
|
1240
1240
|
}
|
|
1241
1241
|
function isHistoryRetentionStub(thread) {
|
|
1242
1242
|
if ((thread.attachments?.length ?? 0) > 0) return false;
|
|
1243
|
+
if ((thread.compactedMessages?.length ?? 0) > 0) return false;
|
|
1243
1244
|
const messages = thread.messages ?? [];
|
|
1244
1245
|
if (messages.length === 0) return true;
|
|
1245
1246
|
if (messages.length > 1) return false;
|
|
@@ -3323,7 +3324,8 @@ function normalizeThread(raw) {
|
|
|
3323
3324
|
runScriptRequest: normalizeRunScriptRequest(raw.runScriptRequest),
|
|
3324
3325
|
quotaResumeAt: raw.quotaResumeAt ?? null,
|
|
3325
3326
|
quotaContinuedFromId: raw.quotaContinuedFromId ?? null,
|
|
3326
|
-
archivedAt: typeof raw.archivedAt === "string" && raw.archivedAt.trim() ? raw.archivedAt.trim() : void 0
|
|
3327
|
+
archivedAt: typeof raw.archivedAt === "string" && raw.archivedAt.trim() ? raw.archivedAt.trim() : void 0,
|
|
3328
|
+
compactedMessages: Array.isArray(raw.compactedMessages) ? raw.compactedMessages : void 0
|
|
3327
3329
|
};
|
|
3328
3330
|
}
|
|
3329
3331
|
function createEmptyThread(partial) {
|
|
@@ -4415,6 +4417,16 @@ function skillToolFromTextPart(part, id) {
|
|
|
4415
4417
|
updatedAt: Date.now()
|
|
4416
4418
|
};
|
|
4417
4419
|
}
|
|
4420
|
+
function compactSummaryPart(text7, id = "compact-summary") {
|
|
4421
|
+
return compactToolPart(text7, id, { status: "done" });
|
|
4422
|
+
}
|
|
4423
|
+
function compactPartsForSummaryMessage(message) {
|
|
4424
|
+
const existing = (message.parts ?? []).filter(
|
|
4425
|
+
(p) => p.type === "tool" && isCompactToolName(p.name)
|
|
4426
|
+
);
|
|
4427
|
+
if (existing.length) return existing;
|
|
4428
|
+
return [compactSummaryPart(message.text ?? "")];
|
|
4429
|
+
}
|
|
4418
4430
|
function compactToolPart(text7, id, opts) {
|
|
4419
4431
|
const input = { trigger: opts?.trigger ?? "auto" };
|
|
4420
4432
|
return withPartTimes(
|
|
@@ -12360,6 +12372,10 @@ var init_node_launch = __esm({
|
|
|
12360
12372
|
// src/agents/injected-mcp.ts
|
|
12361
12373
|
function sideboardWorktreeAllowedTools(opts) {
|
|
12362
12374
|
const out = [...SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS];
|
|
12375
|
+
if (!opts?.planMode) {
|
|
12376
|
+
const idx = out.indexOf("mcp__sideboard__present_plan");
|
|
12377
|
+
if (idx >= 0) out.splice(idx, 1);
|
|
12378
|
+
}
|
|
12363
12379
|
if (opts?.github !== false) out.push(...SIDEBOARD_GITHUB_MCP_ALLOWED_TOOLS);
|
|
12364
12380
|
if (opts?.linear) out.push(...SIDEBOARD_LINEAR_MCP_ALLOWED_TOOLS);
|
|
12365
12381
|
if (opts?.abletime) out.push(...SIDEBOARD_ABLETIME_MCP_ALLOWED_TOOLS);
|
|
@@ -12906,6 +12922,9 @@ var init_orch_mcp_isolation = __esm({
|
|
|
12906
12922
|
});
|
|
12907
12923
|
|
|
12908
12924
|
// src/agents/types.ts
|
|
12925
|
+
function planModeTurnInstruction(planMode) {
|
|
12926
|
+
return planMode ? PLAN_MODE_INSTRUCTION : PLAN_MODE_OFF_INSTRUCTION;
|
|
12927
|
+
}
|
|
12909
12928
|
function permissionMode(thread) {
|
|
12910
12929
|
if (thread.sourceType === "orchestration") {
|
|
12911
12930
|
return {
|
|
@@ -12944,11 +12963,12 @@ function permissionMode(thread) {
|
|
|
12944
12963
|
codexSandbox: "workspace-write"
|
|
12945
12964
|
};
|
|
12946
12965
|
}
|
|
12947
|
-
var PLAN_MODE_INSTRUCTION;
|
|
12966
|
+
var PLAN_MODE_INSTRUCTION, PLAN_MODE_OFF_INSTRUCTION;
|
|
12948
12967
|
var init_types = __esm({
|
|
12949
12968
|
"src/agents/types.ts"() {
|
|
12950
12969
|
"use strict";
|
|
12951
12970
|
PLAN_MODE_INSTRUCTION = "Plan mode is active and must remain active until the user turns Plan mode off in the UI (or Approves / Hands off the plan). Analyze the codebase, search and read files as needed, and produce or refine a clear implementation plan. Do not modify, create, or delete any project files except via Sideboard MCP present_plan (writes .context/attachments/plan.md). When you need a clarifying decision (approach forks, auth choice, scope) \u2014 not greetings, check-ins, or an invented task menu: (1) first write a short chat message that explains the decision and what each option means (tradeoffs, when to pick it) \u2014 do not leave the user staring at bare labels; (2) then call Sideboard MCP ask_user with the same options, including a description on every option. If one option is the obvious default, proceed without asking. Sideboard shows questions in the composer and mirrors them in chat. After ask_user, wait for the user's next message with their answers before finalizing the plan. When the plan is ready for approval: (1) call present_plan with the full markdown plan (title + content) so Sideboard saves .context/attachments/plan.md and shows it in chat for Approve / Hand off / Copy; (2) Claude should also call ExitPlanMode after present_plan. Do not skip present_plan \u2014 the plan must be a markdown file, not only chat prose.";
|
|
12971
|
+
PLAN_MODE_OFF_INSTRUCTION = "Plan mode is off. Do not call present_plan or ExitPlanMode \u2014 those only work when the user turns Plan on in the composer. If you need to propose an approach, write it in chat.";
|
|
12952
12972
|
}
|
|
12953
12973
|
});
|
|
12954
12974
|
|
|
@@ -13461,7 +13481,8 @@ var init_claude = __esm({
|
|
|
13461
13481
|
...sideboardWorktreeAllowedTools({
|
|
13462
13482
|
github: true,
|
|
13463
13483
|
linear: isLinearConnected(),
|
|
13464
|
-
abletime: isAbleTimeConnected()
|
|
13484
|
+
abletime: isAbleTimeConnected(),
|
|
13485
|
+
planMode: thread.planMode
|
|
13465
13486
|
}),
|
|
13466
13487
|
...brightsyMcpAllowedTools(injectedBrightsyNames)
|
|
13467
13488
|
];
|
|
@@ -15879,6 +15900,7 @@ __export(agents_exports, {
|
|
|
15879
15900
|
CURSOR_SDK_INSTALL_HINT: () => CURSOR_SDK_INSTALL_HINT,
|
|
15880
15901
|
ORCHESTRATOR_AGENT_KINDS: () => ORCHESTRATOR_AGENT_KINDS,
|
|
15881
15902
|
PLAN_MODE_INSTRUCTION: () => PLAN_MODE_INSTRUCTION,
|
|
15903
|
+
PLAN_MODE_OFF_INSTRUCTION: () => PLAN_MODE_OFF_INSTRUCTION,
|
|
15882
15904
|
allAdapters: () => allAdapters,
|
|
15883
15905
|
assertOrchestratorCapableAgent: () => assertOrchestratorCapableAgent,
|
|
15884
15906
|
brightsyAdapter: () => brightsyAdapter,
|
|
@@ -15929,6 +15951,7 @@ __export(agents_exports, {
|
|
|
15929
15951
|
parseCursorRunnerLine: () => parseCursorRunnerLine,
|
|
15930
15952
|
parseSessionQuotaResetAt: () => parseSessionQuotaResetAt,
|
|
15931
15953
|
permissionMode: () => permissionMode,
|
|
15954
|
+
planModeTurnInstruction: () => planModeTurnInstruction,
|
|
15932
15955
|
posixShellSingleQuote: () => posixShellSingleQuote,
|
|
15933
15956
|
preferredCursorCostCents: () => preferredCursorCostCents,
|
|
15934
15957
|
prepareTerminalCommand: () => prepareTerminalCommand,
|
|
@@ -16911,12 +16934,38 @@ ${match.text}`, "");
|
|
|
16911
16934
|
blocks.push("Continue from this context. Do not repeat the summary unless asked.");
|
|
16912
16935
|
return blocks.join("\n");
|
|
16913
16936
|
}
|
|
16937
|
+
function compactArchiveProse(message) {
|
|
16938
|
+
if (message.origin === "continue") return null;
|
|
16939
|
+
if (message.role === "summary") return null;
|
|
16940
|
+
let text7 = (message.text ?? "").trim();
|
|
16941
|
+
if (!text7 && message.role === "agent") {
|
|
16942
|
+
text7 = lastAssistantMessageText(message.parts).trim();
|
|
16943
|
+
}
|
|
16944
|
+
if (!text7 || isInternalAgentStatusText(text7)) return null;
|
|
16945
|
+
return { role: message.role, text: text7, ts: message.ts };
|
|
16946
|
+
}
|
|
16947
|
+
function messagesForCompactArchive(older) {
|
|
16948
|
+
const out = [];
|
|
16949
|
+
for (const message of older) {
|
|
16950
|
+
const prose = compactArchiveProse(message);
|
|
16951
|
+
if (prose) out.push(prose);
|
|
16952
|
+
}
|
|
16953
|
+
return out;
|
|
16954
|
+
}
|
|
16955
|
+
function appendCompactedMessages(existing, older) {
|
|
16956
|
+
const next = messagesForCompactArchive(older);
|
|
16957
|
+
if (!existing?.length) return next;
|
|
16958
|
+
if (!next.length) return existing;
|
|
16959
|
+
return [...existing, ...next];
|
|
16960
|
+
}
|
|
16914
16961
|
function applyCompaction(messages, summaryText, thresholds = {}) {
|
|
16915
16962
|
const { older, recent } = splitForCompaction(messages, thresholds);
|
|
16916
16963
|
if (older.length === 0) return messages;
|
|
16964
|
+
const text7 = summaryText.trim();
|
|
16917
16965
|
const summary = {
|
|
16918
16966
|
role: "summary",
|
|
16919
|
-
text:
|
|
16967
|
+
text: text7,
|
|
16968
|
+
parts: [compactSummaryPart(text7)],
|
|
16920
16969
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
16921
16970
|
};
|
|
16922
16971
|
return [summary, ...recent];
|
|
@@ -16944,6 +16993,7 @@ async function maybeCompactContext(thread, thresholds = {}, summarize = summariz
|
|
|
16944
16993
|
cwd: thread.worktreePath
|
|
16945
16994
|
});
|
|
16946
16995
|
let messages = applyCompaction(thread.messages, summary, thresholds);
|
|
16996
|
+
const compactedMessages = appendCompactedMessages(thread.compactedMessages, older);
|
|
16947
16997
|
const resetSession = shouldResetSessionForOccupancy({ messages: thread.messages });
|
|
16948
16998
|
if (resetSession) {
|
|
16949
16999
|
messages = applyForwardOccupancy(messages);
|
|
@@ -16951,6 +17001,7 @@ async function maybeCompactContext(thread, thresholds = {}, summarize = summariz
|
|
|
16951
17001
|
const next = {
|
|
16952
17002
|
...thread,
|
|
16953
17003
|
messages,
|
|
17004
|
+
...compactedMessages.length ? { compactedMessages } : { compactedMessages: void 0 },
|
|
16954
17005
|
sessionId: resetSession ? null : thread.sessionId,
|
|
16955
17006
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16956
17007
|
};
|
|
@@ -16966,6 +17017,7 @@ var CONTEXT_COMPACT_CHARS, CONTEXT_KEEP_RECENT_CHARS, CONTEXT_KEEP_RECENT_MESSAG
|
|
|
16966
17017
|
var init_context_compact = __esm({
|
|
16967
17018
|
"src/composer/context-compact.ts"() {
|
|
16968
17019
|
"use strict";
|
|
17020
|
+
init_message_parts();
|
|
16969
17021
|
init_error_detail();
|
|
16970
17022
|
init_usage();
|
|
16971
17023
|
init_context_estimate();
|
|
@@ -17552,15 +17604,403 @@ var init_worktree_run_ports = __esm({
|
|
|
17552
17604
|
}
|
|
17553
17605
|
});
|
|
17554
17606
|
|
|
17607
|
+
// src/mcp/wait-for-turn.ts
|
|
17608
|
+
function mcpWaitForTurnTimeoutMs(requested) {
|
|
17609
|
+
const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
|
|
17610
|
+
if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
|
|
17611
|
+
return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
|
|
17612
|
+
}
|
|
17613
|
+
function mcpWaitTaskHint(taskState, status) {
|
|
17614
|
+
switch (taskState) {
|
|
17615
|
+
case "submitted":
|
|
17616
|
+
return MCP_WAIT_QUEUED_HINT;
|
|
17617
|
+
case "working":
|
|
17618
|
+
return MCP_WAIT_STILL_RUNNING_HINT;
|
|
17619
|
+
case "input-required":
|
|
17620
|
+
return MCP_WAIT_INPUT_REQUIRED_HINT;
|
|
17621
|
+
case "canceled":
|
|
17622
|
+
return MCP_WAIT_STOPPED_HINT;
|
|
17623
|
+
case "failed":
|
|
17624
|
+
return status === "broken" ? MCP_WAIT_BROKEN_HINT : MCP_WAIT_ERROR_HINT;
|
|
17625
|
+
default:
|
|
17626
|
+
return void 0;
|
|
17627
|
+
}
|
|
17628
|
+
}
|
|
17629
|
+
var MCP_WAIT_FOR_TURN_MAX_MS, MCP_WAIT_STILL_RUNNING_HINT, MCP_WAIT_QUEUED_HINT, MCP_WAIT_STOPPED_HINT, MCP_WAIT_BROKEN_HINT, MCP_WAIT_ERROR_HINT, MCP_WAIT_INPUT_REQUIRED_HINT;
|
|
17630
|
+
var init_wait_for_turn = __esm({
|
|
17631
|
+
"src/mcp/wait-for-turn.ts"() {
|
|
17632
|
+
"use strict";
|
|
17633
|
+
MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
17634
|
+
MCP_WAIT_STILL_RUNNING_HINT = "Child is still working. Call wait_for_turn again. Do not send_to_thread a check-in (that steers / interrupts) or assume a hang while progress is updating.";
|
|
17635
|
+
MCP_WAIT_QUEUED_HINT = "Child is queued waiting for a concurrency slot \u2014 it has not started yet. Call wait_for_turn again. Do not send_to_thread a check-in (that steers / interrupts), force_stop, or assume it failed to start.";
|
|
17636
|
+
MCP_WAIT_STOPPED_HINT = "Child was stopped before the turn finished. Do not treat this as success. send_to_thread to resume, or tell the user.";
|
|
17637
|
+
MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
|
|
17638
|
+
MCP_WAIT_ERROR_HINT = "Child turn failed. lastError/text is the failure \u2014 switch agent, tell the user, or retry. Do not treat empty text as success.";
|
|
17639
|
+
MCP_WAIT_INPUT_REQUIRED_HINT = "Child asked the user a question (ask_user). taskState is input-required. Wait for the user to answer in that chat \u2014 do not send_to_thread a check-in.";
|
|
17640
|
+
}
|
|
17641
|
+
});
|
|
17642
|
+
|
|
17643
|
+
// src/mcp/wait-for-job.ts
|
|
17644
|
+
function mcpWaitForJobTimeoutMs(requested) {
|
|
17645
|
+
return mcpWaitForTurnTimeoutMs(requested);
|
|
17646
|
+
}
|
|
17647
|
+
function sanitizeDetachedJobId(id) {
|
|
17648
|
+
const s = id.trim();
|
|
17649
|
+
if (!JOB_ID_RE.test(s)) {
|
|
17650
|
+
throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
|
|
17651
|
+
}
|
|
17652
|
+
return s;
|
|
17653
|
+
}
|
|
17654
|
+
function jobAlive(pid) {
|
|
17655
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
17656
|
+
try {
|
|
17657
|
+
process.kill(pid, 0);
|
|
17658
|
+
return true;
|
|
17659
|
+
} catch {
|
|
17660
|
+
return false;
|
|
17661
|
+
}
|
|
17662
|
+
}
|
|
17663
|
+
function processGroupAlive(pgid) {
|
|
17664
|
+
if (process.platform === "win32") return false;
|
|
17665
|
+
if (!Number.isInteger(pgid) || pgid <= 1) return false;
|
|
17666
|
+
try {
|
|
17667
|
+
process.kill(-pgid, 0);
|
|
17668
|
+
return true;
|
|
17669
|
+
} catch (err) {
|
|
17670
|
+
return err && typeof err === "object" && "code" in err && err.code === "EPERM";
|
|
17671
|
+
}
|
|
17672
|
+
}
|
|
17673
|
+
function jobTreeAlive(pid) {
|
|
17674
|
+
if (pid == null) return false;
|
|
17675
|
+
return jobAlive(pid) || processGroupAlive(pid);
|
|
17676
|
+
}
|
|
17677
|
+
function readIntFile(file) {
|
|
17678
|
+
if (!(0, import_node_fs45.existsSync)(file)) return null;
|
|
17679
|
+
const n = Number.parseInt((0, import_node_fs45.readFileSync)(file, "utf8").trim(), 10);
|
|
17680
|
+
return Number.isInteger(n) ? n : null;
|
|
17681
|
+
}
|
|
17682
|
+
function jobDir(root, id, legacy = false) {
|
|
17683
|
+
return (0, import_node_path45.join)(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
|
|
17684
|
+
}
|
|
17685
|
+
function resolveJobDir(root, id) {
|
|
17686
|
+
const modern = jobDir(root, id, false);
|
|
17687
|
+
if ((0, import_node_fs45.existsSync)(modern)) return modern;
|
|
17688
|
+
const legacy = jobDir(root, id, true);
|
|
17689
|
+
if ((0, import_node_fs45.existsSync)(legacy)) return legacy;
|
|
17690
|
+
return modern;
|
|
17691
|
+
}
|
|
17692
|
+
function listJobIdsIn(root, rel) {
|
|
17693
|
+
const dir = (0, import_node_path45.join)(root, rel);
|
|
17694
|
+
if (!(0, import_node_fs45.existsSync)(dir)) return [];
|
|
17695
|
+
return (0, import_node_fs45.readdirSync)(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
|
|
17696
|
+
}
|
|
17697
|
+
function listRunningDetachedJobs(worktreePath) {
|
|
17698
|
+
const root = worktreePath.trim();
|
|
17699
|
+
if (!root) return [];
|
|
17700
|
+
const ids = /* @__PURE__ */ new Set([
|
|
17701
|
+
...listJobIdsIn(root, DETACHED_JOBS_DIR),
|
|
17702
|
+
...listJobIdsIn(root, LEGACY_DETACHED_JOBS_DIR)
|
|
17703
|
+
]);
|
|
17704
|
+
const running = [];
|
|
17705
|
+
for (const id of ids) {
|
|
17706
|
+
const dir = resolveJobDir(root, id);
|
|
17707
|
+
const pid = readIntFile((0, import_node_path45.join)(dir, "pid"));
|
|
17708
|
+
if (jobTreeAlive(pid)) running.push(id);
|
|
17709
|
+
}
|
|
17710
|
+
return running.sort();
|
|
17711
|
+
}
|
|
17712
|
+
function looksLikeDeferredDonePromise(text7) {
|
|
17713
|
+
const t = (text7 ?? "").trim();
|
|
17714
|
+
if (!t || t.length > DEFERRED_DONE_MAX_CHARS) return false;
|
|
17715
|
+
const unquoted = t.replace(/`[^`]*`/g, " ").replace(/["“”][^"“”]*["“”]/g, " ").replace(/\s+/g, " ").trim();
|
|
17716
|
+
if (!unquoted) return false;
|
|
17717
|
+
return /\b(i['’]?ll|i will)\s+let you know\b/i.test(unquoted) || /\blet you know when\b/i.test(unquoted) || /\b(check back|ping me)\s+when\b/i.test(unquoted) || /\bi(?:['’]ll| will)\s+(report|update you)\s+when\b/i.test(unquoted);
|
|
17718
|
+
}
|
|
17719
|
+
function isJobContinuePrompt(text7) {
|
|
17720
|
+
if (typeof text7 !== "string") return false;
|
|
17721
|
+
const t = text7.trim();
|
|
17722
|
+
return t.startsWith("Detached job still running:") || t.startsWith("You ended the turn after promising to report later");
|
|
17723
|
+
}
|
|
17724
|
+
function collapseGhRunWatchLog(text7) {
|
|
17725
|
+
if (!text7.includes(GH_WATCH_REFRESH)) return text7;
|
|
17726
|
+
const frames = text7.split(/(?=Refreshing run status every 3 seconds\.)/);
|
|
17727
|
+
const watchFrames = frames.filter((f) => f.includes(GH_WATCH_REFRESH));
|
|
17728
|
+
if (watchFrames.length <= 1) return text7;
|
|
17729
|
+
const prefix = frames[0]?.includes(GH_WATCH_REFRESH) ? "" : frames[0] ?? "";
|
|
17730
|
+
const last = watchFrames[watchFrames.length - 1] ?? "";
|
|
17731
|
+
const omitted = watchFrames.length - 1;
|
|
17732
|
+
const note = `(${omitted} identical gh run watch refresh${omitted === 1 ? "" : "es"} omitted)
|
|
17733
|
+
`;
|
|
17734
|
+
return [prefix.trimEnd(), note, last.trimStart()].filter(Boolean).join("\n");
|
|
17735
|
+
}
|
|
17736
|
+
function capJobLogDelta(text7) {
|
|
17737
|
+
const collapsed = collapseGhRunWatchLog(text7);
|
|
17738
|
+
const lines = collapsed.split("\n");
|
|
17739
|
+
let out = lines.length > MAX_JOB_DELTA_LINES ? `\u2026(${lines.length - MAX_JOB_DELTA_LINES} earlier lines omitted)
|
|
17740
|
+
${lines.slice(-MAX_JOB_DELTA_LINES).join("\n")}` : collapsed;
|
|
17741
|
+
if (out.length > MAX_JOB_DELTA_CHARS) {
|
|
17742
|
+
out = `\u2026(truncated ${out.length} chars)
|
|
17743
|
+
${out.slice(-MAX_JOB_DELTA_CHARS)}`;
|
|
17744
|
+
}
|
|
17745
|
+
return out;
|
|
17746
|
+
}
|
|
17747
|
+
function formatJobStillRunningContinuePrompt(jobIds) {
|
|
17748
|
+
const ids = jobIds.join(", ");
|
|
17749
|
+
return [
|
|
17750
|
+
`Detached job still running: ${ids}.`,
|
|
17751
|
+
"Do not end this turn. Loop wait_for_job (same id) until stillRunning is false. The log pane updates from each wait result.",
|
|
17752
|
+
"If the job is hanging or doing the wrong thing, call stop_job (same id) instead of looping forever.",
|
|
17753
|
+
"Then report the result. Do not tell the user you will let them know later."
|
|
17754
|
+
].join(" ");
|
|
17755
|
+
}
|
|
17756
|
+
function formatDeferredDoneContinuePrompt() {
|
|
17757
|
+
return [
|
|
17758
|
+
"You ended the turn after promising to report later, but no detached job is running.",
|
|
17759
|
+
"If tests/pack/deploy still need to run: start once with detached-job.cjs, then loop wait_for_job until stillRunning is false (the log pane updates from wait JSON).",
|
|
17760
|
+
"Do not say you will let the user know later."
|
|
17761
|
+
].join(" ");
|
|
17762
|
+
}
|
|
17763
|
+
function turnWatchedDetachedJob(parts) {
|
|
17764
|
+
return parts.some((p) => {
|
|
17765
|
+
if (p.type !== "tool") return false;
|
|
17766
|
+
if (/wait_for_job$/i.test(p.name ?? "") || /stop_job$/i.test(p.name ?? "")) return true;
|
|
17767
|
+
const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
|
|
17768
|
+
return /detached-job\.(?:js|cjs)\b/i.test(blob);
|
|
17769
|
+
});
|
|
17770
|
+
}
|
|
17771
|
+
function planJobContinue(opts) {
|
|
17772
|
+
if (opts.isOrchestrator) return { action: "none" };
|
|
17773
|
+
if (opts.agent === "brightsy") return { action: "none" };
|
|
17774
|
+
if (opts.queueLength > 0) return { action: "none" };
|
|
17775
|
+
if (opts.continueCount >= MAX_JOB_CONTINUES) return { action: "none" };
|
|
17776
|
+
const farewell = looksLikeDeferredDonePromise(opts.chatText);
|
|
17777
|
+
if (opts.runningJobIds.length > 0 && (farewell || opts.watchedJob)) {
|
|
17778
|
+
return {
|
|
17779
|
+
action: "wait",
|
|
17780
|
+
jobIds: opts.runningJobIds,
|
|
17781
|
+
prompt: formatJobStillRunningContinuePrompt(opts.runningJobIds)
|
|
17782
|
+
};
|
|
17783
|
+
}
|
|
17784
|
+
if (looksLikeDeferredDonePromise(opts.chatText) && !opts.alreadyNudged) {
|
|
17785
|
+
return { action: "nudge", prompt: formatDeferredDoneContinuePrompt() };
|
|
17786
|
+
}
|
|
17787
|
+
return { action: "none" };
|
|
17788
|
+
}
|
|
17789
|
+
function tailProgress(logFile, maxLines = 12) {
|
|
17790
|
+
if (!(0, import_node_fs45.existsSync)(logFile)) return "(no log yet)";
|
|
17791
|
+
const lines = (0, import_node_fs45.readFileSync)(logFile, "utf8").split("\n");
|
|
17792
|
+
return lines.slice(-maxLines).join("\n");
|
|
17793
|
+
}
|
|
17794
|
+
function lastProgressLine(progress) {
|
|
17795
|
+
const last = progress.split("\n").map((l) => l.trim()).filter((l) => l && l !== "(no log yet)").at(-1);
|
|
17796
|
+
return last ? last.slice(0, 72) : void 0;
|
|
17797
|
+
}
|
|
17798
|
+
function readLogLines(file) {
|
|
17799
|
+
if (!(0, import_node_fs45.existsSync)(file)) return [];
|
|
17800
|
+
const lines = (0, import_node_fs45.readFileSync)(file, "utf8").split("\n");
|
|
17801
|
+
if (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
17802
|
+
return lines;
|
|
17803
|
+
}
|
|
17804
|
+
function takeDelta(logFile, cursorFile) {
|
|
17805
|
+
const lines = readLogLines(logFile);
|
|
17806
|
+
const cursor = readIntFile(cursorFile) ?? 0;
|
|
17807
|
+
const start = Math.min(Math.max(0, cursor), lines.length);
|
|
17808
|
+
return { delta: capJobLogDelta(lines.slice(start).join("\n")), nextCursor: lines.length };
|
|
17809
|
+
}
|
|
17810
|
+
function snapshotJob(dir) {
|
|
17811
|
+
const pid = readIntFile((0, import_node_path45.join)(dir, "pid"));
|
|
17812
|
+
const running = jobTreeAlive(pid);
|
|
17813
|
+
return {
|
|
17814
|
+
pid,
|
|
17815
|
+
running,
|
|
17816
|
+
exitCode: readIntFile((0, import_node_path45.join)(dir, "exit")),
|
|
17817
|
+
log: (0, import_node_path45.join)(dir, "log"),
|
|
17818
|
+
cursor: (0, import_node_path45.join)(dir, "present.cursor"),
|
|
17819
|
+
progress: tailProgress((0, import_node_path45.join)(dir, "log"))
|
|
17820
|
+
};
|
|
17821
|
+
}
|
|
17822
|
+
function toResult(id, snap, extra) {
|
|
17823
|
+
const failed = extra?.failed === true || !snap.running && snap.exitCode != null && snap.exitCode !== 0;
|
|
17824
|
+
const ok = !snap.running && snap.exitCode === 0;
|
|
17825
|
+
const stillRunning = snap.running && !ok;
|
|
17826
|
+
const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
|
|
17827
|
+
try {
|
|
17828
|
+
(0, import_node_fs45.mkdirSync)((0, import_node_path45.dirname)(snap.cursor), { recursive: true });
|
|
17829
|
+
(0, import_node_fs45.writeFileSync)(snap.cursor, `${nextCursor}
|
|
17830
|
+
`);
|
|
17831
|
+
} catch {
|
|
17832
|
+
}
|
|
17833
|
+
const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
|
|
17834
|
+
const progress = extra?.progress ?? snap.progress;
|
|
17835
|
+
return {
|
|
17836
|
+
stillRunning,
|
|
17837
|
+
ok,
|
|
17838
|
+
failed: Boolean(failed && !stillRunning && !ok),
|
|
17839
|
+
status,
|
|
17840
|
+
id,
|
|
17841
|
+
pid: snap.pid,
|
|
17842
|
+
exitCode: snap.exitCode ?? void 0,
|
|
17843
|
+
phase: lastProgressLine(progress),
|
|
17844
|
+
delta,
|
|
17845
|
+
progress,
|
|
17846
|
+
hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
|
|
17847
|
+
};
|
|
17848
|
+
}
|
|
17849
|
+
async function sleepMs(ms) {
|
|
17850
|
+
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
17851
|
+
}
|
|
17852
|
+
async function waitForDetachedJob(cwd, id, opts) {
|
|
17853
|
+
const jobId = sanitizeDetachedJobId(id);
|
|
17854
|
+
const root = cwd.trim() || process.cwd();
|
|
17855
|
+
const dir = resolveJobDir(root, jobId);
|
|
17856
|
+
const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
|
|
17857
|
+
const sleep2 = opts?.sleep ?? sleepMs;
|
|
17858
|
+
if (!(0, import_node_fs45.existsSync)(dir)) {
|
|
17859
|
+
return {
|
|
17860
|
+
stillRunning: false,
|
|
17861
|
+
ok: false,
|
|
17862
|
+
failed: true,
|
|
17863
|
+
status: "failed",
|
|
17864
|
+
id: jobId,
|
|
17865
|
+
delta: "",
|
|
17866
|
+
progress: "No detached job. Start one first.",
|
|
17867
|
+
hint: "Start with detached-job.cjs start <id> -- <command>, then call wait_for_job again."
|
|
17868
|
+
};
|
|
17869
|
+
}
|
|
17870
|
+
const deadline = Date.now() + timeoutMs;
|
|
17871
|
+
let snap = snapshotJob(dir);
|
|
17872
|
+
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
17873
|
+
if (!snap.running && snap.pid == null && snap.progress === "(no log yet)") {
|
|
17874
|
+
return toResult(jobId, snap, {
|
|
17875
|
+
failed: true,
|
|
17876
|
+
progress: "No detached job. Start one first."
|
|
17877
|
+
});
|
|
17878
|
+
}
|
|
17879
|
+
while (Date.now() < deadline) {
|
|
17880
|
+
snap = snapshotJob(dir);
|
|
17881
|
+
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
17882
|
+
if (!snap.running) return toResult(jobId, snap, { failed: true });
|
|
17883
|
+
await sleep2(Math.min(2e3, Math.max(50, deadline - Date.now())));
|
|
17884
|
+
}
|
|
17885
|
+
snap = snapshotJob(dir);
|
|
17886
|
+
return toResult(jobId, snap);
|
|
17887
|
+
}
|
|
17888
|
+
function killProcessTree(pid, signal) {
|
|
17889
|
+
if (!Number.isInteger(pid) || pid <= 0) return;
|
|
17890
|
+
if (process.platform !== "win32") {
|
|
17891
|
+
try {
|
|
17892
|
+
process.kill(-pid, signal);
|
|
17893
|
+
return;
|
|
17894
|
+
} catch {
|
|
17895
|
+
}
|
|
17896
|
+
}
|
|
17897
|
+
try {
|
|
17898
|
+
process.kill(pid, signal);
|
|
17899
|
+
} catch {
|
|
17900
|
+
}
|
|
17901
|
+
}
|
|
17902
|
+
function appendJobLog(logFile, line) {
|
|
17903
|
+
try {
|
|
17904
|
+
(0, import_node_fs45.mkdirSync)((0, import_node_path45.dirname)(logFile), { recursive: true });
|
|
17905
|
+
const body = line.endsWith("\n") ? line : `${line}
|
|
17906
|
+
`;
|
|
17907
|
+
(0, import_node_fs45.appendFileSync)(logFile, body);
|
|
17908
|
+
} catch {
|
|
17909
|
+
}
|
|
17910
|
+
}
|
|
17911
|
+
async function stopDetachedJob(cwd, id, opts) {
|
|
17912
|
+
const jobId = sanitizeDetachedJobId(id);
|
|
17913
|
+
const root = cwd.trim() || process.cwd();
|
|
17914
|
+
const dir = resolveJobDir(root, jobId);
|
|
17915
|
+
const sleep2 = opts?.sleep ?? sleepMs;
|
|
17916
|
+
const why = opts?.reason?.trim() ?? "";
|
|
17917
|
+
if (!(0, import_node_fs45.existsSync)(dir)) {
|
|
17918
|
+
return {
|
|
17919
|
+
stillRunning: false,
|
|
17920
|
+
ok: false,
|
|
17921
|
+
failed: true,
|
|
17922
|
+
status: "failed",
|
|
17923
|
+
id: jobId,
|
|
17924
|
+
delta: "",
|
|
17925
|
+
progress: "No detached job. Start one first.",
|
|
17926
|
+
hint: "Start with detached-job.cjs start <id> -- <command>, then wait_for_job or stop_job.",
|
|
17927
|
+
stopped: false,
|
|
17928
|
+
reason: "not-found"
|
|
17929
|
+
};
|
|
17930
|
+
}
|
|
17931
|
+
let snap = snapshotJob(dir);
|
|
17932
|
+
if (!snap.running) {
|
|
17933
|
+
const result2 = toResult(jobId, snap);
|
|
17934
|
+
return {
|
|
17935
|
+
...result2,
|
|
17936
|
+
stopped: false,
|
|
17937
|
+
reason: "not-running",
|
|
17938
|
+
hint: "That job is already finished. Read the log; do not wait again unless you start a new command."
|
|
17939
|
+
};
|
|
17940
|
+
}
|
|
17941
|
+
appendJobLog(snap.log, `$ stop${why ? `: ${why}` : ""}`);
|
|
17942
|
+
const targetPid = snap.pid;
|
|
17943
|
+
if (targetPid != null) killProcessTree(targetPid, "SIGTERM");
|
|
17944
|
+
const graceMs = Number.isFinite(opts?.graceMs) ? Math.max(50, opts.graceMs) : 2e3;
|
|
17945
|
+
const deadline = Date.now() + graceMs;
|
|
17946
|
+
while (Date.now() < deadline && jobTreeAlive(targetPid)) {
|
|
17947
|
+
await sleep2(Math.min(100, Math.max(20, deadline - Date.now())));
|
|
17948
|
+
}
|
|
17949
|
+
if (targetPid != null) {
|
|
17950
|
+
killProcessTree(targetPid, "SIGKILL");
|
|
17951
|
+
await sleep2(100);
|
|
17952
|
+
}
|
|
17953
|
+
const exitFile = (0, import_node_path45.join)(dir, "exit");
|
|
17954
|
+
if (!(0, import_node_fs45.existsSync)(exitFile)) {
|
|
17955
|
+
try {
|
|
17956
|
+
(0, import_node_fs45.writeFileSync)(exitFile, "1\n");
|
|
17957
|
+
} catch {
|
|
17958
|
+
}
|
|
17959
|
+
}
|
|
17960
|
+
snap = snapshotJob(dir);
|
|
17961
|
+
const stillRunning = jobTreeAlive(targetPid) || snap.running;
|
|
17962
|
+
const result = toResult(jobId, snap, { failed: true });
|
|
17963
|
+
const stopped = !stillRunning;
|
|
17964
|
+
return {
|
|
17965
|
+
...result,
|
|
17966
|
+
stillRunning,
|
|
17967
|
+
ok: false,
|
|
17968
|
+
failed: true,
|
|
17969
|
+
status: stillRunning ? "running" : "failed",
|
|
17970
|
+
stopped,
|
|
17971
|
+
reason: stopped ? "stopped" : "still-running",
|
|
17972
|
+
stopReason: why || void 0,
|
|
17973
|
+
hint: stopped ? MCP_STOP_JOB_HINT : MCP_WAIT_JOB_STILL_RUNNING_HINT
|
|
17974
|
+
};
|
|
17975
|
+
}
|
|
17976
|
+
var import_node_fs45, import_node_path45, MAX_JOB_CONTINUES, MAX_JOB_DELTA_LINES, MAX_JOB_DELTA_CHARS, DEFERRED_DONE_MAX_CHARS, GH_WATCH_REFRESH, MCP_WAIT_JOB_STILL_RUNNING_HINT, MCP_STOP_JOB_HINT, JOB_ID_RE;
|
|
17977
|
+
var init_wait_for_job = __esm({
|
|
17978
|
+
"src/mcp/wait-for-job.ts"() {
|
|
17979
|
+
"use strict";
|
|
17980
|
+
import_node_fs45 = require("fs");
|
|
17981
|
+
import_node_path45 = require("path");
|
|
17982
|
+
init_workspace_scratch();
|
|
17983
|
+
init_wait_for_turn();
|
|
17984
|
+
MAX_JOB_CONTINUES = 8;
|
|
17985
|
+
MAX_JOB_DELTA_LINES = 80;
|
|
17986
|
+
MAX_JOB_DELTA_CHARS = 4e3;
|
|
17987
|
+
DEFERRED_DONE_MAX_CHARS = 480;
|
|
17988
|
+
GH_WATCH_REFRESH = "Refreshing run status every 3 seconds.";
|
|
17989
|
+
MCP_WAIT_JOB_STILL_RUNNING_HINT = "Job is still running. The type=log pane already appended this delta. Call wait_for_job again. If it is hanging, producing no useful output, or doing the wrong thing, call stop_job (same id) instead of looping forever. Do not end the turn or tell the user you will let them know later.";
|
|
17990
|
+
MCP_STOP_JOB_HINT = "Stopped. The type=log pane is status=failed with the last delta. Do not wait again unless you start a new command.";
|
|
17991
|
+
JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
17992
|
+
}
|
|
17993
|
+
});
|
|
17994
|
+
|
|
17555
17995
|
// src/hook/conductor.ts
|
|
17556
17996
|
function matchSimpleGlob(pattern, name) {
|
|
17557
17997
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
17558
17998
|
return new RegExp(`^${escaped}$`).test(name);
|
|
17559
17999
|
}
|
|
17560
18000
|
function readWorktreeInclude(repoPath) {
|
|
17561
|
-
const path2 = (0,
|
|
17562
|
-
if (!(0,
|
|
17563
|
-
return (0,
|
|
18001
|
+
const path2 = (0, import_node_path46.join)(repoPath, ".worktreeinclude");
|
|
18002
|
+
if (!(0, import_node_fs46.existsSync)(path2)) return [];
|
|
18003
|
+
return (0, import_node_fs46.readFileSync)(path2, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
17564
18004
|
}
|
|
17565
18005
|
function resolveFilesToCopy(repoPath) {
|
|
17566
18006
|
const fromInclude = readWorktreeInclude(repoPath);
|
|
@@ -17570,10 +18010,10 @@ function resolveFilesToCopy(repoPath) {
|
|
|
17570
18010
|
if (settings?.fileIncludeGlobs?.length) {
|
|
17571
18011
|
const matched = [];
|
|
17572
18012
|
try {
|
|
17573
|
-
for (const entry of (0,
|
|
18013
|
+
for (const entry of (0, import_node_fs46.readdirSync)(repoPath, { withFileTypes: true })) {
|
|
17574
18014
|
if (!entry.isFile()) continue;
|
|
17575
18015
|
for (const glob of settings.fileIncludeGlobs) {
|
|
17576
|
-
if (matchSimpleGlob(glob, entry.name) || matchSimpleGlob((0,
|
|
18016
|
+
if (matchSimpleGlob(glob, entry.name) || matchSimpleGlob((0, import_node_path46.basename)(glob), entry.name)) {
|
|
17577
18017
|
matched.push(entry.name);
|
|
17578
18018
|
break;
|
|
17579
18019
|
}
|
|
@@ -17585,7 +18025,7 @@ function resolveFilesToCopy(repoPath) {
|
|
|
17585
18025
|
}
|
|
17586
18026
|
const defaults = [];
|
|
17587
18027
|
try {
|
|
17588
|
-
for (const entry of (0,
|
|
18028
|
+
for (const entry of (0, import_node_fs46.readdirSync)(repoPath, { withFileTypes: true })) {
|
|
17589
18029
|
if (entry.isFile() && entry.name.startsWith(".env")) {
|
|
17590
18030
|
defaults.push(entry.name);
|
|
17591
18031
|
}
|
|
@@ -17599,11 +18039,11 @@ function copyConfiguredFiles(repoPath, worktreePath) {
|
|
|
17599
18039
|
const patterns = resolveFilesToCopy(repoPath);
|
|
17600
18040
|
const copied = [];
|
|
17601
18041
|
for (const rel of patterns) {
|
|
17602
|
-
const src = (0,
|
|
17603
|
-
if (!(0,
|
|
17604
|
-
const dest = (0,
|
|
17605
|
-
(0,
|
|
17606
|
-
(0,
|
|
18042
|
+
const src = (0, import_node_path46.join)(repoPath, rel);
|
|
18043
|
+
if (!(0, import_node_fs46.existsSync)(src)) continue;
|
|
18044
|
+
const dest = (0, import_node_path46.join)(worktreePath, rel);
|
|
18045
|
+
(0, import_node_fs46.mkdirSync)((0, import_node_path46.dirname)(dest), { recursive: true });
|
|
18046
|
+
(0, import_node_fs46.copyFileSync)(src, dest);
|
|
17607
18047
|
copied.push(rel);
|
|
17608
18048
|
}
|
|
17609
18049
|
return copied;
|
|
@@ -17638,7 +18078,7 @@ function buildWorkspaceScriptEnv(opts, baseEnv) {
|
|
|
17638
18078
|
const env = stripNestedElectronEnv({
|
|
17639
18079
|
...baseEnv ?? process.env
|
|
17640
18080
|
});
|
|
17641
|
-
const name = opts.workspaceName ?? (0,
|
|
18081
|
+
const name = opts.workspaceName ?? (0, import_node_path46.basename)(opts.worktreePath);
|
|
17642
18082
|
const ports = opts.ports ?? [];
|
|
17643
18083
|
const primary = ports[0];
|
|
17644
18084
|
env.SIDEBOARD_WORKSPACE_NAME = name;
|
|
@@ -17707,20 +18147,20 @@ function listenerCwd(pid) {
|
|
|
17707
18147
|
}
|
|
17708
18148
|
function realpathOrResolve(path2) {
|
|
17709
18149
|
try {
|
|
17710
|
-
return (0,
|
|
18150
|
+
return (0, import_node_fs46.realpathSync)(path2);
|
|
17711
18151
|
} catch {
|
|
17712
|
-
return (0,
|
|
18152
|
+
return (0, import_node_path46.resolve)(path2);
|
|
17713
18153
|
}
|
|
17714
18154
|
}
|
|
17715
18155
|
function isOtherWorktreeListenerCwd(cwd, worktreePath) {
|
|
17716
18156
|
const wt = realpathOrResolve(worktreePath);
|
|
17717
18157
|
const c = realpathOrResolve(cwd);
|
|
17718
|
-
if (c === wt || c.startsWith(wt +
|
|
17719
|
-
const parent = (0,
|
|
17720
|
-
const thisName = (0,
|
|
18158
|
+
if (c === wt || c.startsWith(wt + import_node_path46.sep)) return false;
|
|
18159
|
+
const parent = (0, import_node_path46.dirname)(wt);
|
|
18160
|
+
const thisName = (0, import_node_path46.basename)(wt);
|
|
17721
18161
|
if (!thisName || c === parent) return false;
|
|
17722
|
-
if (!c.startsWith(parent +
|
|
17723
|
-
const first = c.slice(parent.length + 1).split(
|
|
18162
|
+
if (!c.startsWith(parent + import_node_path46.sep)) return false;
|
|
18163
|
+
const first = c.slice(parent.length + 1).split(import_node_path46.sep)[0];
|
|
17724
18164
|
return Boolean(first) && first !== thisName;
|
|
17725
18165
|
}
|
|
17726
18166
|
function signalPid(pid, signal) {
|
|
@@ -17732,12 +18172,16 @@ function signalPid(pid, signal) {
|
|
|
17732
18172
|
function isThisWorktreeListenerCwd(cwd, worktreePath) {
|
|
17733
18173
|
const wt = realpathOrResolve(worktreePath);
|
|
17734
18174
|
const c = realpathOrResolve(cwd);
|
|
17735
|
-
return c === wt || c.startsWith(wt +
|
|
18175
|
+
return c === wt || c.startsWith(wt + import_node_path46.sep);
|
|
17736
18176
|
}
|
|
17737
18177
|
function killDetachedRunTree(pid, worktreePath) {
|
|
17738
18178
|
if (!Number.isFinite(pid) || pid <= 1 || pid === process.pid) return;
|
|
17739
18179
|
const cwd = listenerCwd(pid);
|
|
17740
|
-
if (
|
|
18180
|
+
if (cwd) {
|
|
18181
|
+
if (!isThisWorktreeListenerCwd(cwd, worktreePath)) return;
|
|
18182
|
+
} else if (!processGroupAlive(pid)) {
|
|
18183
|
+
return;
|
|
18184
|
+
}
|
|
17741
18185
|
try {
|
|
17742
18186
|
process.kill(-pid, "SIGTERM");
|
|
17743
18187
|
} catch {
|
|
@@ -18134,14 +18578,14 @@ async function startDevServer(repoPath, worktreePath, onLine, opts) {
|
|
|
18134
18578
|
throw err;
|
|
18135
18579
|
}
|
|
18136
18580
|
}
|
|
18137
|
-
var
|
|
18581
|
+
var import_node_fs46, import_node_child_process5, import_node_net, import_node_path46, import_execa4, import_node_readline3, PORT_RANGE_SIZE, RECLAIM_RETRY_DELAY_MS, RECLAIM_TRIES, cachedLoginEnv;
|
|
18138
18582
|
var init_conductor = __esm({
|
|
18139
18583
|
"src/hook/conductor.ts"() {
|
|
18140
18584
|
"use strict";
|
|
18141
|
-
|
|
18585
|
+
import_node_fs46 = require("fs");
|
|
18142
18586
|
import_node_child_process5 = require("child_process");
|
|
18143
18587
|
import_node_net = require("net");
|
|
18144
|
-
|
|
18588
|
+
import_node_path46 = require("path");
|
|
18145
18589
|
import_execa4 = require("execa");
|
|
18146
18590
|
import_node_readline3 = require("readline");
|
|
18147
18591
|
init_settings();
|
|
@@ -18153,6 +18597,7 @@ var init_conductor = __esm({
|
|
|
18153
18597
|
init_request_review();
|
|
18154
18598
|
init_worktree_exclude();
|
|
18155
18599
|
init_worktree_run_ports();
|
|
18600
|
+
init_wait_for_job();
|
|
18156
18601
|
init_worktree_run_ports();
|
|
18157
18602
|
init_nested_electron_env();
|
|
18158
18603
|
PORT_RANGE_SIZE = 10;
|
|
@@ -18173,9 +18618,9 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
18173
18618
|
repoPaths?.length ? repoPaths : threads.map((t) => t.repoPath).filter(Boolean)
|
|
18174
18619
|
);
|
|
18175
18620
|
const homeRoot = sideboardWorkspacesDir();
|
|
18176
|
-
if ((0,
|
|
18621
|
+
if ((0, import_node_fs47.existsSync)(homeRoot)) {
|
|
18177
18622
|
try {
|
|
18178
|
-
for (const entry of (0,
|
|
18623
|
+
for (const entry of (0, import_node_fs47.readdirSync)(homeRoot, { withFileTypes: true })) {
|
|
18179
18624
|
if (!entry.isDirectory()) continue;
|
|
18180
18625
|
void entry;
|
|
18181
18626
|
}
|
|
@@ -18185,7 +18630,7 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
18185
18630
|
const orphans = [];
|
|
18186
18631
|
const seen = /* @__PURE__ */ new Set();
|
|
18187
18632
|
for (const repoPath of repos) {
|
|
18188
|
-
if (!repoPath || !(0,
|
|
18633
|
+
if (!repoPath || !(0, import_node_fs47.existsSync)(repoPath)) continue;
|
|
18189
18634
|
try {
|
|
18190
18635
|
const wts = await listWorktrees(repoPath);
|
|
18191
18636
|
for (const wt of wts) {
|
|
@@ -18196,7 +18641,7 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
18196
18641
|
seen.add(path2);
|
|
18197
18642
|
let mtimeMs = 0;
|
|
18198
18643
|
try {
|
|
18199
|
-
mtimeMs = (0,
|
|
18644
|
+
mtimeMs = (0, import_node_fs47.statSync)(path2).mtimeMs;
|
|
18200
18645
|
} catch {
|
|
18201
18646
|
mtimeMs = 0;
|
|
18202
18647
|
}
|
|
@@ -18206,16 +18651,16 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
18206
18651
|
}
|
|
18207
18652
|
try {
|
|
18208
18653
|
const root = worktreesRoot(repoPath);
|
|
18209
|
-
if ((0,
|
|
18210
|
-
for (const entry of (0,
|
|
18654
|
+
if ((0, import_node_fs47.existsSync)(root)) {
|
|
18655
|
+
for (const entry of (0, import_node_fs47.readdirSync)(root, { withFileTypes: true })) {
|
|
18211
18656
|
if (!entry.isDirectory()) continue;
|
|
18212
|
-
const path2 = (0,
|
|
18657
|
+
const path2 = (0, import_node_path47.join)(root, entry.name).replace(/\/$/, "");
|
|
18213
18658
|
if (known.has(path2) || seen.has(path2)) continue;
|
|
18214
|
-
if (!(0,
|
|
18659
|
+
if (!(0, import_node_fs47.existsSync)((0, import_node_path47.join)(path2, ".git"))) continue;
|
|
18215
18660
|
seen.add(path2);
|
|
18216
18661
|
let mtimeMs = 0;
|
|
18217
18662
|
try {
|
|
18218
|
-
mtimeMs = (0,
|
|
18663
|
+
mtimeMs = (0, import_node_fs47.statSync)(path2).mtimeMs;
|
|
18219
18664
|
} catch {
|
|
18220
18665
|
mtimeMs = Date.now();
|
|
18221
18666
|
}
|
|
@@ -18276,12 +18721,12 @@ function worktreeCleanupSettings() {
|
|
|
18276
18721
|
autoCleanupOrphans: a.autoCleanupOrphans
|
|
18277
18722
|
};
|
|
18278
18723
|
}
|
|
18279
|
-
var
|
|
18724
|
+
var import_node_fs47, import_node_path47;
|
|
18280
18725
|
var init_orphan_cleanup = __esm({
|
|
18281
18726
|
"src/git/orphan-cleanup.ts"() {
|
|
18282
18727
|
"use strict";
|
|
18283
|
-
|
|
18284
|
-
|
|
18728
|
+
import_node_fs47 = require("fs");
|
|
18729
|
+
import_node_path47 = require("path");
|
|
18285
18730
|
init_worktree();
|
|
18286
18731
|
init_thread_store();
|
|
18287
18732
|
init_paths();
|
|
@@ -18297,6 +18742,7 @@ function stripArchivedTranscript(thread) {
|
|
|
18297
18742
|
attachments: [],
|
|
18298
18743
|
pendingTurnAttachments: [],
|
|
18299
18744
|
queueAttachments: [],
|
|
18745
|
+
compactedMessages: void 0,
|
|
18300
18746
|
messages: [historyRetentionStubMessage(thread)]
|
|
18301
18747
|
},
|
|
18302
18748
|
{ touch: false }
|
|
@@ -18456,12 +18902,12 @@ async function cloneRepoIntoSideboard(opts) {
|
|
|
18456
18902
|
if (!url) throw new Error("Clone URL is required");
|
|
18457
18903
|
let name = opts.name?.trim();
|
|
18458
18904
|
if (!name) {
|
|
18459
|
-
const leaf = (0,
|
|
18905
|
+
const leaf = (0, import_node_path48.basename)(url.replace(/\/$/, "").replace(/\.git$/, ""));
|
|
18460
18906
|
name = leaf || "repo";
|
|
18461
18907
|
}
|
|
18462
18908
|
name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
|
|
18463
|
-
const dest = (0,
|
|
18464
|
-
if ((0,
|
|
18909
|
+
const dest = (0, import_node_path48.join)(sideboardReposDir(), name);
|
|
18910
|
+
if ((0, import_node_fs48.existsSync)(dest)) {
|
|
18465
18911
|
const repoPath2 = await resolveRepoRoot(dest);
|
|
18466
18912
|
const workspace2 = await ensureWorkspace(repoPath2);
|
|
18467
18913
|
return { repoPath: repoPath2, workspace: workspace2 };
|
|
@@ -18476,12 +18922,12 @@ async function cloneRepoIntoSideboard(opts) {
|
|
|
18476
18922
|
const workspace = await ensureWorkspace(repoPath);
|
|
18477
18923
|
return { repoPath, workspace };
|
|
18478
18924
|
}
|
|
18479
|
-
var
|
|
18925
|
+
var import_node_fs48, import_node_path48, import_execa6;
|
|
18480
18926
|
var init_clone_repo = __esm({
|
|
18481
18927
|
"src/git/clone-repo.ts"() {
|
|
18482
18928
|
"use strict";
|
|
18483
|
-
|
|
18484
|
-
|
|
18929
|
+
import_node_fs48 = require("fs");
|
|
18930
|
+
import_node_path48 = require("path");
|
|
18485
18931
|
import_execa6 = require("execa");
|
|
18486
18932
|
init_paths();
|
|
18487
18933
|
init_workspaces();
|
|
@@ -18539,394 +18985,6 @@ var init_child_halt = __esm({
|
|
|
18539
18985
|
}
|
|
18540
18986
|
});
|
|
18541
18987
|
|
|
18542
|
-
// src/mcp/wait-for-turn.ts
|
|
18543
|
-
function mcpWaitForTurnTimeoutMs(requested) {
|
|
18544
|
-
const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
|
|
18545
|
-
if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
|
|
18546
|
-
return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
|
|
18547
|
-
}
|
|
18548
|
-
function mcpWaitTaskHint(taskState, status) {
|
|
18549
|
-
switch (taskState) {
|
|
18550
|
-
case "submitted":
|
|
18551
|
-
return MCP_WAIT_QUEUED_HINT;
|
|
18552
|
-
case "working":
|
|
18553
|
-
return MCP_WAIT_STILL_RUNNING_HINT;
|
|
18554
|
-
case "input-required":
|
|
18555
|
-
return MCP_WAIT_INPUT_REQUIRED_HINT;
|
|
18556
|
-
case "canceled":
|
|
18557
|
-
return MCP_WAIT_STOPPED_HINT;
|
|
18558
|
-
case "failed":
|
|
18559
|
-
return status === "broken" ? MCP_WAIT_BROKEN_HINT : MCP_WAIT_ERROR_HINT;
|
|
18560
|
-
default:
|
|
18561
|
-
return void 0;
|
|
18562
|
-
}
|
|
18563
|
-
}
|
|
18564
|
-
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;
|
|
18565
|
-
var init_wait_for_turn = __esm({
|
|
18566
|
-
"src/mcp/wait-for-turn.ts"() {
|
|
18567
|
-
"use strict";
|
|
18568
|
-
MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
18569
|
-
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.";
|
|
18570
|
-
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.";
|
|
18571
|
-
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.";
|
|
18572
|
-
MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
|
|
18573
|
-
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.";
|
|
18574
|
-
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.";
|
|
18575
|
-
}
|
|
18576
|
-
});
|
|
18577
|
-
|
|
18578
|
-
// src/mcp/wait-for-job.ts
|
|
18579
|
-
function mcpWaitForJobTimeoutMs(requested) {
|
|
18580
|
-
return mcpWaitForTurnTimeoutMs(requested);
|
|
18581
|
-
}
|
|
18582
|
-
function sanitizeDetachedJobId(id) {
|
|
18583
|
-
const s = id.trim();
|
|
18584
|
-
if (!JOB_ID_RE.test(s)) {
|
|
18585
|
-
throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
|
|
18586
|
-
}
|
|
18587
|
-
return s;
|
|
18588
|
-
}
|
|
18589
|
-
function jobAlive(pid) {
|
|
18590
|
-
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
18591
|
-
try {
|
|
18592
|
-
process.kill(pid, 0);
|
|
18593
|
-
return true;
|
|
18594
|
-
} catch {
|
|
18595
|
-
return false;
|
|
18596
|
-
}
|
|
18597
|
-
}
|
|
18598
|
-
function processGroupAlive(pgid) {
|
|
18599
|
-
if (process.platform === "win32") return false;
|
|
18600
|
-
if (!Number.isInteger(pgid) || pgid <= 1) return false;
|
|
18601
|
-
try {
|
|
18602
|
-
process.kill(-pgid, 0);
|
|
18603
|
-
return true;
|
|
18604
|
-
} catch (err) {
|
|
18605
|
-
return err && typeof err === "object" && "code" in err && err.code === "EPERM";
|
|
18606
|
-
}
|
|
18607
|
-
}
|
|
18608
|
-
function jobTreeAlive(pid) {
|
|
18609
|
-
if (pid == null) return false;
|
|
18610
|
-
return jobAlive(pid) || processGroupAlive(pid);
|
|
18611
|
-
}
|
|
18612
|
-
function readIntFile(file) {
|
|
18613
|
-
if (!(0, import_node_fs48.existsSync)(file)) return null;
|
|
18614
|
-
const n = Number.parseInt((0, import_node_fs48.readFileSync)(file, "utf8").trim(), 10);
|
|
18615
|
-
return Number.isInteger(n) ? n : null;
|
|
18616
|
-
}
|
|
18617
|
-
function jobDir(root, id, legacy = false) {
|
|
18618
|
-
return (0, import_node_path48.join)(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
|
|
18619
|
-
}
|
|
18620
|
-
function resolveJobDir(root, id) {
|
|
18621
|
-
const modern = jobDir(root, id, false);
|
|
18622
|
-
if ((0, import_node_fs48.existsSync)(modern)) return modern;
|
|
18623
|
-
const legacy = jobDir(root, id, true);
|
|
18624
|
-
if ((0, import_node_fs48.existsSync)(legacy)) return legacy;
|
|
18625
|
-
return modern;
|
|
18626
|
-
}
|
|
18627
|
-
function listJobIdsIn(root, rel) {
|
|
18628
|
-
const dir = (0, import_node_path48.join)(root, rel);
|
|
18629
|
-
if (!(0, import_node_fs48.existsSync)(dir)) return [];
|
|
18630
|
-
return (0, import_node_fs48.readdirSync)(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
|
|
18631
|
-
}
|
|
18632
|
-
function listRunningDetachedJobs(worktreePath) {
|
|
18633
|
-
const root = worktreePath.trim();
|
|
18634
|
-
if (!root) return [];
|
|
18635
|
-
const ids = /* @__PURE__ */ new Set([
|
|
18636
|
-
...listJobIdsIn(root, DETACHED_JOBS_DIR),
|
|
18637
|
-
...listJobIdsIn(root, LEGACY_DETACHED_JOBS_DIR)
|
|
18638
|
-
]);
|
|
18639
|
-
const running = [];
|
|
18640
|
-
for (const id of ids) {
|
|
18641
|
-
const dir = resolveJobDir(root, id);
|
|
18642
|
-
const pid = readIntFile((0, import_node_path48.join)(dir, "pid"));
|
|
18643
|
-
if (jobTreeAlive(pid)) running.push(id);
|
|
18644
|
-
}
|
|
18645
|
-
return running.sort();
|
|
18646
|
-
}
|
|
18647
|
-
function looksLikeDeferredDonePromise(text7) {
|
|
18648
|
-
const t = (text7 ?? "").trim();
|
|
18649
|
-
if (!t || t.length > DEFERRED_DONE_MAX_CHARS) return false;
|
|
18650
|
-
const unquoted = t.replace(/`[^`]*`/g, " ").replace(/["“”][^"“”]*["“”]/g, " ").replace(/\s+/g, " ").trim();
|
|
18651
|
-
if (!unquoted) return false;
|
|
18652
|
-
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);
|
|
18653
|
-
}
|
|
18654
|
-
function isJobContinuePrompt(text7) {
|
|
18655
|
-
if (typeof text7 !== "string") return false;
|
|
18656
|
-
const t = text7.trim();
|
|
18657
|
-
return t.startsWith("Detached job still running:") || t.startsWith("You ended the turn after promising to report later");
|
|
18658
|
-
}
|
|
18659
|
-
function collapseGhRunWatchLog(text7) {
|
|
18660
|
-
if (!text7.includes(GH_WATCH_REFRESH)) return text7;
|
|
18661
|
-
const frames = text7.split(/(?=Refreshing run status every 3 seconds\.)/);
|
|
18662
|
-
const watchFrames = frames.filter((f) => f.includes(GH_WATCH_REFRESH));
|
|
18663
|
-
if (watchFrames.length <= 1) return text7;
|
|
18664
|
-
const prefix = frames[0]?.includes(GH_WATCH_REFRESH) ? "" : frames[0] ?? "";
|
|
18665
|
-
const last = watchFrames[watchFrames.length - 1] ?? "";
|
|
18666
|
-
const omitted = watchFrames.length - 1;
|
|
18667
|
-
const note = `(${omitted} identical gh run watch refresh${omitted === 1 ? "" : "es"} omitted)
|
|
18668
|
-
`;
|
|
18669
|
-
return [prefix.trimEnd(), note, last.trimStart()].filter(Boolean).join("\n");
|
|
18670
|
-
}
|
|
18671
|
-
function capJobLogDelta(text7) {
|
|
18672
|
-
const collapsed = collapseGhRunWatchLog(text7);
|
|
18673
|
-
const lines = collapsed.split("\n");
|
|
18674
|
-
let out = lines.length > MAX_JOB_DELTA_LINES ? `\u2026(${lines.length - MAX_JOB_DELTA_LINES} earlier lines omitted)
|
|
18675
|
-
${lines.slice(-MAX_JOB_DELTA_LINES).join("\n")}` : collapsed;
|
|
18676
|
-
if (out.length > MAX_JOB_DELTA_CHARS) {
|
|
18677
|
-
out = `\u2026(truncated ${out.length} chars)
|
|
18678
|
-
${out.slice(-MAX_JOB_DELTA_CHARS)}`;
|
|
18679
|
-
}
|
|
18680
|
-
return out;
|
|
18681
|
-
}
|
|
18682
|
-
function formatJobStillRunningContinuePrompt(jobIds) {
|
|
18683
|
-
const ids = jobIds.join(", ");
|
|
18684
|
-
return [
|
|
18685
|
-
`Detached job still running: ${ids}.`,
|
|
18686
|
-
"Do not end this turn. Loop wait_for_job (same id) until stillRunning is false. The log pane updates from each wait result.",
|
|
18687
|
-
"If the job is hanging or doing the wrong thing, call stop_job (same id) instead of looping forever.",
|
|
18688
|
-
"Then report the result. Do not tell the user you will let them know later."
|
|
18689
|
-
].join(" ");
|
|
18690
|
-
}
|
|
18691
|
-
function formatDeferredDoneContinuePrompt() {
|
|
18692
|
-
return [
|
|
18693
|
-
"You ended the turn after promising to report later, but no detached job is running.",
|
|
18694
|
-
"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).",
|
|
18695
|
-
"Do not say you will let the user know later."
|
|
18696
|
-
].join(" ");
|
|
18697
|
-
}
|
|
18698
|
-
function turnWatchedDetachedJob(parts) {
|
|
18699
|
-
return parts.some((p) => {
|
|
18700
|
-
if (p.type !== "tool") return false;
|
|
18701
|
-
if (/wait_for_job$/i.test(p.name ?? "") || /stop_job$/i.test(p.name ?? "")) return true;
|
|
18702
|
-
const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
|
|
18703
|
-
return /detached-job\.(?:js|cjs)\b/i.test(blob);
|
|
18704
|
-
});
|
|
18705
|
-
}
|
|
18706
|
-
function planJobContinue(opts) {
|
|
18707
|
-
if (opts.isOrchestrator) return { action: "none" };
|
|
18708
|
-
if (opts.agent === "brightsy") return { action: "none" };
|
|
18709
|
-
if (opts.queueLength > 0) return { action: "none" };
|
|
18710
|
-
if (opts.continueCount >= MAX_JOB_CONTINUES) return { action: "none" };
|
|
18711
|
-
const farewell = looksLikeDeferredDonePromise(opts.chatText);
|
|
18712
|
-
if (opts.runningJobIds.length > 0 && (farewell || opts.watchedJob)) {
|
|
18713
|
-
return {
|
|
18714
|
-
action: "wait",
|
|
18715
|
-
jobIds: opts.runningJobIds,
|
|
18716
|
-
prompt: formatJobStillRunningContinuePrompt(opts.runningJobIds)
|
|
18717
|
-
};
|
|
18718
|
-
}
|
|
18719
|
-
if (looksLikeDeferredDonePromise(opts.chatText) && !opts.alreadyNudged) {
|
|
18720
|
-
return { action: "nudge", prompt: formatDeferredDoneContinuePrompt() };
|
|
18721
|
-
}
|
|
18722
|
-
return { action: "none" };
|
|
18723
|
-
}
|
|
18724
|
-
function tailProgress(logFile, maxLines = 12) {
|
|
18725
|
-
if (!(0, import_node_fs48.existsSync)(logFile)) return "(no log yet)";
|
|
18726
|
-
const lines = (0, import_node_fs48.readFileSync)(logFile, "utf8").split("\n");
|
|
18727
|
-
return lines.slice(-maxLines).join("\n");
|
|
18728
|
-
}
|
|
18729
|
-
function lastProgressLine(progress) {
|
|
18730
|
-
const last = progress.split("\n").map((l) => l.trim()).filter((l) => l && l !== "(no log yet)").at(-1);
|
|
18731
|
-
return last ? last.slice(0, 72) : void 0;
|
|
18732
|
-
}
|
|
18733
|
-
function readLogLines(file) {
|
|
18734
|
-
if (!(0, import_node_fs48.existsSync)(file)) return [];
|
|
18735
|
-
const lines = (0, import_node_fs48.readFileSync)(file, "utf8").split("\n");
|
|
18736
|
-
if (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
18737
|
-
return lines;
|
|
18738
|
-
}
|
|
18739
|
-
function takeDelta(logFile, cursorFile) {
|
|
18740
|
-
const lines = readLogLines(logFile);
|
|
18741
|
-
const cursor = readIntFile(cursorFile) ?? 0;
|
|
18742
|
-
const start = Math.min(Math.max(0, cursor), lines.length);
|
|
18743
|
-
return { delta: capJobLogDelta(lines.slice(start).join("\n")), nextCursor: lines.length };
|
|
18744
|
-
}
|
|
18745
|
-
function snapshotJob(dir) {
|
|
18746
|
-
const pid = readIntFile((0, import_node_path48.join)(dir, "pid"));
|
|
18747
|
-
const running = jobTreeAlive(pid);
|
|
18748
|
-
return {
|
|
18749
|
-
pid,
|
|
18750
|
-
running,
|
|
18751
|
-
exitCode: readIntFile((0, import_node_path48.join)(dir, "exit")),
|
|
18752
|
-
log: (0, import_node_path48.join)(dir, "log"),
|
|
18753
|
-
cursor: (0, import_node_path48.join)(dir, "present.cursor"),
|
|
18754
|
-
progress: tailProgress((0, import_node_path48.join)(dir, "log"))
|
|
18755
|
-
};
|
|
18756
|
-
}
|
|
18757
|
-
function toResult(id, snap, extra) {
|
|
18758
|
-
const failed = extra?.failed === true || !snap.running && snap.exitCode != null && snap.exitCode !== 0;
|
|
18759
|
-
const ok = !snap.running && snap.exitCode === 0;
|
|
18760
|
-
const stillRunning = snap.running && !ok;
|
|
18761
|
-
const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
|
|
18762
|
-
try {
|
|
18763
|
-
(0, import_node_fs48.mkdirSync)((0, import_node_path48.dirname)(snap.cursor), { recursive: true });
|
|
18764
|
-
(0, import_node_fs48.writeFileSync)(snap.cursor, `${nextCursor}
|
|
18765
|
-
`);
|
|
18766
|
-
} catch {
|
|
18767
|
-
}
|
|
18768
|
-
const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
|
|
18769
|
-
const progress = extra?.progress ?? snap.progress;
|
|
18770
|
-
return {
|
|
18771
|
-
stillRunning,
|
|
18772
|
-
ok,
|
|
18773
|
-
failed: Boolean(failed && !stillRunning && !ok),
|
|
18774
|
-
status,
|
|
18775
|
-
id,
|
|
18776
|
-
pid: snap.pid,
|
|
18777
|
-
exitCode: snap.exitCode ?? void 0,
|
|
18778
|
-
phase: lastProgressLine(progress),
|
|
18779
|
-
delta,
|
|
18780
|
-
progress,
|
|
18781
|
-
hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
|
|
18782
|
-
};
|
|
18783
|
-
}
|
|
18784
|
-
async function sleepMs(ms) {
|
|
18785
|
-
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
18786
|
-
}
|
|
18787
|
-
async function waitForDetachedJob(cwd, id, opts) {
|
|
18788
|
-
const jobId = sanitizeDetachedJobId(id);
|
|
18789
|
-
const root = cwd.trim() || process.cwd();
|
|
18790
|
-
const dir = resolveJobDir(root, jobId);
|
|
18791
|
-
const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
|
|
18792
|
-
const sleep2 = opts?.sleep ?? sleepMs;
|
|
18793
|
-
if (!(0, import_node_fs48.existsSync)(dir)) {
|
|
18794
|
-
return {
|
|
18795
|
-
stillRunning: false,
|
|
18796
|
-
ok: false,
|
|
18797
|
-
failed: true,
|
|
18798
|
-
status: "failed",
|
|
18799
|
-
id: jobId,
|
|
18800
|
-
delta: "",
|
|
18801
|
-
progress: "No detached job. Start one first.",
|
|
18802
|
-
hint: "Start with detached-job.cjs start <id> -- <command>, then call wait_for_job again."
|
|
18803
|
-
};
|
|
18804
|
-
}
|
|
18805
|
-
const deadline = Date.now() + timeoutMs;
|
|
18806
|
-
let snap = snapshotJob(dir);
|
|
18807
|
-
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
18808
|
-
if (!snap.running && snap.pid == null && snap.progress === "(no log yet)") {
|
|
18809
|
-
return toResult(jobId, snap, {
|
|
18810
|
-
failed: true,
|
|
18811
|
-
progress: "No detached job. Start one first."
|
|
18812
|
-
});
|
|
18813
|
-
}
|
|
18814
|
-
while (Date.now() < deadline) {
|
|
18815
|
-
snap = snapshotJob(dir);
|
|
18816
|
-
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
18817
|
-
if (!snap.running) return toResult(jobId, snap, { failed: true });
|
|
18818
|
-
await sleep2(Math.min(2e3, Math.max(50, deadline - Date.now())));
|
|
18819
|
-
}
|
|
18820
|
-
snap = snapshotJob(dir);
|
|
18821
|
-
return toResult(jobId, snap);
|
|
18822
|
-
}
|
|
18823
|
-
function killProcessTree(pid, signal) {
|
|
18824
|
-
if (!Number.isInteger(pid) || pid <= 0) return;
|
|
18825
|
-
if (process.platform !== "win32") {
|
|
18826
|
-
try {
|
|
18827
|
-
process.kill(-pid, signal);
|
|
18828
|
-
return;
|
|
18829
|
-
} catch {
|
|
18830
|
-
}
|
|
18831
|
-
}
|
|
18832
|
-
try {
|
|
18833
|
-
process.kill(pid, signal);
|
|
18834
|
-
} catch {
|
|
18835
|
-
}
|
|
18836
|
-
}
|
|
18837
|
-
function appendJobLog(logFile, line) {
|
|
18838
|
-
try {
|
|
18839
|
-
(0, import_node_fs48.mkdirSync)((0, import_node_path48.dirname)(logFile), { recursive: true });
|
|
18840
|
-
const body = line.endsWith("\n") ? line : `${line}
|
|
18841
|
-
`;
|
|
18842
|
-
(0, import_node_fs48.appendFileSync)(logFile, body);
|
|
18843
|
-
} catch {
|
|
18844
|
-
}
|
|
18845
|
-
}
|
|
18846
|
-
async function stopDetachedJob(cwd, id, opts) {
|
|
18847
|
-
const jobId = sanitizeDetachedJobId(id);
|
|
18848
|
-
const root = cwd.trim() || process.cwd();
|
|
18849
|
-
const dir = resolveJobDir(root, jobId);
|
|
18850
|
-
const sleep2 = opts?.sleep ?? sleepMs;
|
|
18851
|
-
const why = opts?.reason?.trim() ?? "";
|
|
18852
|
-
if (!(0, import_node_fs48.existsSync)(dir)) {
|
|
18853
|
-
return {
|
|
18854
|
-
stillRunning: false,
|
|
18855
|
-
ok: false,
|
|
18856
|
-
failed: true,
|
|
18857
|
-
status: "failed",
|
|
18858
|
-
id: jobId,
|
|
18859
|
-
delta: "",
|
|
18860
|
-
progress: "No detached job. Start one first.",
|
|
18861
|
-
hint: "Start with detached-job.cjs start <id> -- <command>, then wait_for_job or stop_job.",
|
|
18862
|
-
stopped: false,
|
|
18863
|
-
reason: "not-found"
|
|
18864
|
-
};
|
|
18865
|
-
}
|
|
18866
|
-
let snap = snapshotJob(dir);
|
|
18867
|
-
if (!snap.running) {
|
|
18868
|
-
const result2 = toResult(jobId, snap);
|
|
18869
|
-
return {
|
|
18870
|
-
...result2,
|
|
18871
|
-
stopped: false,
|
|
18872
|
-
reason: "not-running",
|
|
18873
|
-
hint: "That job is already finished. Read the log; do not wait again unless you start a new command."
|
|
18874
|
-
};
|
|
18875
|
-
}
|
|
18876
|
-
appendJobLog(snap.log, `$ stop${why ? `: ${why}` : ""}`);
|
|
18877
|
-
const targetPid = snap.pid;
|
|
18878
|
-
if (targetPid != null) killProcessTree(targetPid, "SIGTERM");
|
|
18879
|
-
const graceMs = Number.isFinite(opts?.graceMs) ? Math.max(50, opts.graceMs) : 2e3;
|
|
18880
|
-
const deadline = Date.now() + graceMs;
|
|
18881
|
-
while (Date.now() < deadline && jobTreeAlive(targetPid)) {
|
|
18882
|
-
await sleep2(Math.min(100, Math.max(20, deadline - Date.now())));
|
|
18883
|
-
}
|
|
18884
|
-
if (targetPid != null) {
|
|
18885
|
-
killProcessTree(targetPid, "SIGKILL");
|
|
18886
|
-
await sleep2(100);
|
|
18887
|
-
}
|
|
18888
|
-
const exitFile = (0, import_node_path48.join)(dir, "exit");
|
|
18889
|
-
if (!(0, import_node_fs48.existsSync)(exitFile)) {
|
|
18890
|
-
try {
|
|
18891
|
-
(0, import_node_fs48.writeFileSync)(exitFile, "1\n");
|
|
18892
|
-
} catch {
|
|
18893
|
-
}
|
|
18894
|
-
}
|
|
18895
|
-
snap = snapshotJob(dir);
|
|
18896
|
-
const stillRunning = jobTreeAlive(targetPid) || snap.running;
|
|
18897
|
-
const result = toResult(jobId, snap, { failed: true });
|
|
18898
|
-
const stopped = !stillRunning;
|
|
18899
|
-
return {
|
|
18900
|
-
...result,
|
|
18901
|
-
stillRunning,
|
|
18902
|
-
ok: false,
|
|
18903
|
-
failed: true,
|
|
18904
|
-
status: stillRunning ? "running" : "failed",
|
|
18905
|
-
stopped,
|
|
18906
|
-
reason: stopped ? "stopped" : "still-running",
|
|
18907
|
-
stopReason: why || void 0,
|
|
18908
|
-
hint: stopped ? MCP_STOP_JOB_HINT : MCP_WAIT_JOB_STILL_RUNNING_HINT
|
|
18909
|
-
};
|
|
18910
|
-
}
|
|
18911
|
-
var import_node_fs48, import_node_path48, MAX_JOB_CONTINUES, MAX_JOB_DELTA_LINES, MAX_JOB_DELTA_CHARS, DEFERRED_DONE_MAX_CHARS, GH_WATCH_REFRESH, MCP_WAIT_JOB_STILL_RUNNING_HINT, MCP_STOP_JOB_HINT, JOB_ID_RE;
|
|
18912
|
-
var init_wait_for_job = __esm({
|
|
18913
|
-
"src/mcp/wait-for-job.ts"() {
|
|
18914
|
-
"use strict";
|
|
18915
|
-
import_node_fs48 = require("fs");
|
|
18916
|
-
import_node_path48 = require("path");
|
|
18917
|
-
init_workspace_scratch();
|
|
18918
|
-
init_wait_for_turn();
|
|
18919
|
-
MAX_JOB_CONTINUES = 8;
|
|
18920
|
-
MAX_JOB_DELTA_LINES = 80;
|
|
18921
|
-
MAX_JOB_DELTA_CHARS = 4e3;
|
|
18922
|
-
DEFERRED_DONE_MAX_CHARS = 480;
|
|
18923
|
-
GH_WATCH_REFRESH = "Refreshing run status every 3 seconds.";
|
|
18924
|
-
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.";
|
|
18925
|
-
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.";
|
|
18926
|
-
JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
18927
|
-
}
|
|
18928
|
-
});
|
|
18929
|
-
|
|
18930
18988
|
// src/detect/detect.ts
|
|
18931
18989
|
async function detectAgents() {
|
|
18932
18990
|
ensureAgentPath();
|
|
@@ -23709,6 +23767,13 @@ function isPresentPlanToolName(name) {
|
|
|
23709
23767
|
if (!name) return false;
|
|
23710
23768
|
return /present_plan$/i.test(name) || /^mcp__sideboard__present_plan$/i.test(name);
|
|
23711
23769
|
}
|
|
23770
|
+
function threadMayPresentPlan(thread) {
|
|
23771
|
+
return Boolean(thread?.planMode);
|
|
23772
|
+
}
|
|
23773
|
+
function shouldRegisterPresentPlan(thread) {
|
|
23774
|
+
if (thread == null) return true;
|
|
23775
|
+
return threadMayPresentPlan(thread);
|
|
23776
|
+
}
|
|
23712
23777
|
function extractPresentedPlan(parts) {
|
|
23713
23778
|
if (!parts?.length) return null;
|
|
23714
23779
|
for (let i = parts.length - 1; i >= 0; i--) {
|
|
@@ -23746,7 +23811,7 @@ function resolvePlanMarkdown(opts) {
|
|
|
23746
23811
|
}
|
|
23747
23812
|
return null;
|
|
23748
23813
|
}
|
|
23749
|
-
var PLAN_FILE_REL, PLAN_FILE_NAME, LEGACY_PLAN_FILE_REL;
|
|
23814
|
+
var PLAN_FILE_REL, PLAN_FILE_NAME, LEGACY_PLAN_FILE_REL, PRESENT_PLAN_TOOL_DESCRIPTION, PRESENT_PLAN_REQUIRES_PLAN_MODE;
|
|
23750
23815
|
var init_plan_present = __esm({
|
|
23751
23816
|
"src/plan/plan-present.ts"() {
|
|
23752
23817
|
"use strict";
|
|
@@ -23754,6 +23819,8 @@ var init_plan_present = __esm({
|
|
|
23754
23819
|
PLAN_FILE_REL = `${ATTACHMENTS_DIR}/plan.md`;
|
|
23755
23820
|
PLAN_FILE_NAME = "plan.md";
|
|
23756
23821
|
LEGACY_PLAN_FILE_REL = ".sideboard/plan.md";
|
|
23822
|
+
PRESENT_PLAN_TOOL_DESCRIPTION = "Only when Sideboard Plan mode is on (composer Plan chip). Saves the implementation plan to .context/attachments/plan.md and shows Approve / Hand off / Copy in chat. Pass the full plan body in content. Then Claude should call ExitPlanMode. If Plan mode is off, do not call this \u2014 write the plan in chat.";
|
|
23823
|
+
PRESENT_PLAN_REQUIRES_PLAN_MODE = "Plan mode is off. Do not call present_plan or ExitPlanMode. Write the plan in chat. The user can turn Plan on in the composer.";
|
|
23757
23824
|
}
|
|
23758
23825
|
});
|
|
23759
23826
|
|
|
@@ -23763,11 +23830,15 @@ __export(plan_file_exports, {
|
|
|
23763
23830
|
LEGACY_PLAN_FILE_REL: () => LEGACY_PLAN_FILE_REL,
|
|
23764
23831
|
PLAN_FILE_NAME: () => PLAN_FILE_NAME,
|
|
23765
23832
|
PLAN_FILE_REL: () => PLAN_FILE_REL,
|
|
23833
|
+
PRESENT_PLAN_REQUIRES_PLAN_MODE: () => PRESENT_PLAN_REQUIRES_PLAN_MODE,
|
|
23834
|
+
PRESENT_PLAN_TOOL_DESCRIPTION: () => PRESENT_PLAN_TOOL_DESCRIPTION,
|
|
23766
23835
|
extractPresentedPlan: () => extractPresentedPlan,
|
|
23767
23836
|
isPresentPlanToolName: () => isPresentPlanToolName,
|
|
23768
23837
|
planFileAbs: () => planFileAbs,
|
|
23769
23838
|
readPlanFile: () => readPlanFile,
|
|
23770
23839
|
resolvePlanMarkdown: () => resolvePlanMarkdown,
|
|
23840
|
+
shouldRegisterPresentPlan: () => shouldRegisterPresentPlan,
|
|
23841
|
+
threadMayPresentPlan: () => threadMayPresentPlan,
|
|
23771
23842
|
writePlanFile: () => writePlanFile
|
|
23772
23843
|
});
|
|
23773
23844
|
function ensureAttachmentsGitignore(worktreePath) {
|
|
@@ -24903,7 +24974,8 @@ var init_orchestrator = __esm({
|
|
|
24903
24974
|
if (compact.didCompact) {
|
|
24904
24975
|
thread = updateThread(threadId, {
|
|
24905
24976
|
messages: compact.thread.messages,
|
|
24906
|
-
sessionId: compact.thread.sessionId
|
|
24977
|
+
sessionId: compact.thread.sessionId,
|
|
24978
|
+
compactedMessages: compact.thread.compactedMessages
|
|
24907
24979
|
});
|
|
24908
24980
|
this.emit({
|
|
24909
24981
|
type: "context_compacted",
|
|
@@ -24917,9 +24989,6 @@ var init_orchestrator = __esm({
|
|
|
24917
24989
|
this.emit({ type: "error", threadId, message: `context compact failed: ${message}` });
|
|
24918
24990
|
thread = this.requireThread(threadId);
|
|
24919
24991
|
}
|
|
24920
|
-
if (this.stoppedTurns.has(threadId)) {
|
|
24921
|
-
return;
|
|
24922
|
-
}
|
|
24923
24992
|
const promptText = typeof prompt === "string" ? prompt : "";
|
|
24924
24993
|
const autoContinue = isJobContinuePrompt(promptText) || promptText.startsWith("The previous agent process ended before it finished.");
|
|
24925
24994
|
const parked = takePendingTurnAttachments(thread);
|
|
@@ -24936,6 +25005,14 @@ var init_orchestrator = __esm({
|
|
|
24936
25005
|
agent: thread.agent,
|
|
24937
25006
|
orch: isOrchestratorThread(thread)
|
|
24938
25007
|
});
|
|
25008
|
+
if (this.stoppedTurns.has(threadId)) {
|
|
25009
|
+
const stopped = writeLiveStatus(threadId, "stopped");
|
|
25010
|
+
if (stopped?.status === "stopped") {
|
|
25011
|
+
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
25012
|
+
}
|
|
25013
|
+
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
25014
|
+
return;
|
|
25015
|
+
}
|
|
24939
25016
|
thread = this.requireThread(threadId);
|
|
24940
25017
|
const gitPrompt = expandCanonicalGitRequest(promptText);
|
|
24941
25018
|
const { agentPrompt: expandedPrompt } = expandComposerPrompt(
|
|
@@ -24994,7 +25071,7 @@ var init_orchestrator = __esm({
|
|
|
24994
25071
|
].filter(Boolean).join("\n\n") || void 0;
|
|
24995
25072
|
const prGateDirective = thread.agent !== "brightsy" && !isOrchestratorThread(thread) && mentionsPrGoal(promptText) ? formatPrGateDirective() : null;
|
|
24996
25073
|
const agentPrompt = [
|
|
24997
|
-
thread.planMode
|
|
25074
|
+
planModeTurnInstruction(thread.planMode),
|
|
24998
25075
|
orchestrationReminder,
|
|
24999
25076
|
prGateDirective,
|
|
25000
25077
|
injectedNoticeContext,
|
|
@@ -26928,8 +27005,11 @@ __export(index_exports, {
|
|
|
26928
27005
|
PLAN_FILE_NAME: () => PLAN_FILE_NAME,
|
|
26929
27006
|
PLAN_FILE_REL: () => PLAN_FILE_REL,
|
|
26930
27007
|
PLAN_MODE_INSTRUCTION: () => PLAN_MODE_INSTRUCTION,
|
|
27008
|
+
PLAN_MODE_OFF_INSTRUCTION: () => PLAN_MODE_OFF_INSTRUCTION,
|
|
26931
27009
|
PLAN_QUESTION_ANSWERS_PREFIX: () => PLAN_QUESTION_ANSWERS_PREFIX,
|
|
26932
27010
|
PORT_RANGE_SIZE: () => PORT_RANGE_SIZE,
|
|
27011
|
+
PRESENT_PLAN_REQUIRES_PLAN_MODE: () => PRESENT_PLAN_REQUIRES_PLAN_MODE,
|
|
27012
|
+
PRESENT_PLAN_TOOL_DESCRIPTION: () => PRESENT_PLAN_TOOL_DESCRIPTION,
|
|
26933
27013
|
PROFILE_NOTES_MAX: () => PROFILE_NOTES_MAX,
|
|
26934
27014
|
PR_DETAILS_JSON_FIELDS: () => PR_DETAILS_JSON_FIELDS,
|
|
26935
27015
|
REPO_REVIEW_NAME: () => REPO_REVIEW_NAME,
|
|
@@ -27000,6 +27080,7 @@ __export(index_exports, {
|
|
|
27000
27080
|
annotateDevAccessRuns: () => annotateDevAccessRuns,
|
|
27001
27081
|
appDataDir: () => appDataDir,
|
|
27002
27082
|
appSettingsPath: () => appSettingsPath,
|
|
27083
|
+
appendCompactedMessages: () => appendCompactedMessages,
|
|
27003
27084
|
appendIndexedGitConfig: () => appendIndexedGitConfig,
|
|
27004
27085
|
appendMessage: () => appendMessage,
|
|
27005
27086
|
appendQueuedItem: () => appendQueuedItem,
|
|
@@ -27087,6 +27168,9 @@ __export(index_exports, {
|
|
|
27087
27168
|
commentGitHubIssue: () => commentGitHubIssue,
|
|
27088
27169
|
commentLinearIssue: () => commentLinearIssue,
|
|
27089
27170
|
commitAll: () => commitAll,
|
|
27171
|
+
compactArchiveProse: () => compactArchiveProse,
|
|
27172
|
+
compactPartsForSummaryMessage: () => compactPartsForSummaryMessage,
|
|
27173
|
+
compactSummaryPart: () => compactSummaryPart,
|
|
27090
27174
|
computeNextRunAt: () => computeNextRunAt,
|
|
27091
27175
|
conductorBundledBinDir: () => conductorBundledBinDir,
|
|
27092
27176
|
conductorDbPath: () => conductorDbPath,
|
|
@@ -27486,6 +27570,7 @@ __export(index_exports, {
|
|
|
27486
27570
|
mergeSideboardIntoMcpServersJson: () => mergeSideboardIntoMcpServersJson,
|
|
27487
27571
|
mergeUsage: () => mergeUsage,
|
|
27488
27572
|
messagePartParentId: () => messagePartParentId,
|
|
27573
|
+
messagesForCompactArchive: () => messagesForCompactArchive,
|
|
27489
27574
|
messagesSinceLastBrightsyContextSummary: () => messagesSinceLastBrightsyContextSummary,
|
|
27490
27575
|
moveQueuedItemToFront: () => moveQueuedItemToFront,
|
|
27491
27576
|
needsCoordinatorAction: () => needsCoordinatorAction,
|
|
@@ -27545,6 +27630,7 @@ __export(index_exports, {
|
|
|
27545
27630
|
planFileAbs: () => planFileAbs,
|
|
27546
27631
|
planHistoryAgePurge: () => planHistoryAgePurge,
|
|
27547
27632
|
planHistoryRetention: () => planHistoryRetention,
|
|
27633
|
+
planModeTurnInstruction: () => planModeTurnInstruction,
|
|
27548
27634
|
planQuestionsSignature: () => planQuestionsSignature,
|
|
27549
27635
|
pnpmFilterPackage: () => pnpmFilterPackage,
|
|
27550
27636
|
pollSlackOutboundWatches: () => pollSlackOutboundWatches,
|
|
@@ -27672,6 +27758,7 @@ __export(index_exports, {
|
|
|
27672
27758
|
shouldInjectBrightsyMcp: () => shouldInjectBrightsyMcp,
|
|
27673
27759
|
shouldNotifyOrchestrator: () => shouldNotifyOrchestrator,
|
|
27674
27760
|
shouldRefreshReviewRequestTemplate: () => shouldRefreshReviewRequestTemplate,
|
|
27761
|
+
shouldRegisterPresentPlan: () => shouldRegisterPresentPlan,
|
|
27675
27762
|
shouldRemoveWorktreeOnTeardown: () => shouldRemoveWorktreeOnTeardown,
|
|
27676
27763
|
shouldResetSessionForOccupancy: () => shouldResetSessionForOccupancy,
|
|
27677
27764
|
shouldRunHistoryCleanup: () => shouldRunHistoryCleanup,
|
|
@@ -27733,6 +27820,7 @@ __export(index_exports, {
|
|
|
27733
27820
|
threadHasCompactedContext: () => threadHasCompactedContext,
|
|
27734
27821
|
threadLivePath: () => threadLivePath,
|
|
27735
27822
|
threadLockPath: () => threadLockPath,
|
|
27823
|
+
threadMayPresentPlan: () => threadMayPresentPlan,
|
|
27736
27824
|
threadRequestsBrightsyMcp: () => threadRequestsBrightsyMcp,
|
|
27737
27825
|
threadsDir: () => threadsDir,
|
|
27738
27826
|
threadsSharingWorktree: () => threadsSharingWorktree,
|
|
@@ -27906,7 +27994,8 @@ function slimThreadForUiList(thread) {
|
|
|
27906
27994
|
attachments: [],
|
|
27907
27995
|
pendingTurnAttachments: [],
|
|
27908
27996
|
queueAttachments: [],
|
|
27909
|
-
messages: slimMessagesForUiList(messages, thread.updatedAt)
|
|
27997
|
+
messages: slimMessagesForUiList(messages, thread.updatedAt),
|
|
27998
|
+
compactedMessages: void 0
|
|
27910
27999
|
};
|
|
27911
28000
|
}
|
|
27912
28001
|
function slimMessagesForUiList(messages, fallbackTs) {
|
|
@@ -30460,6 +30549,7 @@ function mcpArchiveBlockedReason(thread) {
|
|
|
30460
30549
|
|
|
30461
30550
|
// src/mcp/server.ts
|
|
30462
30551
|
init_profile();
|
|
30552
|
+
init_plan_present();
|
|
30463
30553
|
|
|
30464
30554
|
// src/mcp/run-script-ref.ts
|
|
30465
30555
|
init_chat_tabs();
|
|
@@ -33372,31 +33462,40 @@ async function startMcpServer() {
|
|
|
33372
33462
|
return mcpJson(payload);
|
|
33373
33463
|
}
|
|
33374
33464
|
);
|
|
33375
|
-
|
|
33376
|
-
|
|
33377
|
-
|
|
33378
|
-
|
|
33379
|
-
|
|
33380
|
-
|
|
33381
|
-
|
|
33382
|
-
|
|
33383
|
-
|
|
33384
|
-
|
|
33385
|
-
|
|
33386
|
-
|
|
33387
|
-
const
|
|
33465
|
+
const callingThreadId = process.env[SIDEBOARD_THREAD_ID_ENV]?.trim() || "";
|
|
33466
|
+
const callingThread = callingThreadId ? orch.getThread(callingThreadId) : null;
|
|
33467
|
+
if (shouldRegisterPresentPlan(callingThread)) {
|
|
33468
|
+
server.tool(
|
|
33469
|
+
"present_plan",
|
|
33470
|
+
PRESENT_PLAN_TOOL_DESCRIPTION,
|
|
33471
|
+
{
|
|
33472
|
+
title: import_zod9.z.string().optional().describe("Short plan title (defaults to Plan)"),
|
|
33473
|
+
content: import_zod9.z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
|
|
33474
|
+
thread_id: import_zod9.z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
|
|
33475
|
+
},
|
|
33476
|
+
async ({ title, content, thread_id }) => {
|
|
33477
|
+
const { writePlanFile: writePlanFile2 } = await Promise.resolve().then(() => (init_plan_file(), plan_file_exports));
|
|
33478
|
+
const ref = thread_id?.trim() || callingThreadId;
|
|
33479
|
+
const t = ref ? orch.getThread(ref) : null;
|
|
33480
|
+
if (t && !threadMayPresentPlan(t)) {
|
|
33481
|
+
return mcpJson(
|
|
33482
|
+
{ ok: false, error: PRESENT_PLAN_REQUIRES_PLAN_MODE },
|
|
33483
|
+
true
|
|
33484
|
+
);
|
|
33485
|
+
}
|
|
33486
|
+
let root = process.cwd();
|
|
33388
33487
|
if (t?.worktreePath?.trim()) root = t.worktreePath;
|
|
33488
|
+
const path2 = writePlanFile2(root, content);
|
|
33489
|
+
const payload = {
|
|
33490
|
+
ok: true,
|
|
33491
|
+
path: path2,
|
|
33492
|
+
title: title?.trim() || "Plan",
|
|
33493
|
+
message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
|
|
33494
|
+
};
|
|
33495
|
+
return mcpJson(payload);
|
|
33389
33496
|
}
|
|
33390
|
-
|
|
33391
|
-
|
|
33392
|
-
ok: true,
|
|
33393
|
-
path: path2,
|
|
33394
|
-
title: title?.trim() || "Plan",
|
|
33395
|
-
message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
|
|
33396
|
-
};
|
|
33397
|
-
return mcpJson(payload);
|
|
33398
|
-
}
|
|
33399
|
-
);
|
|
33497
|
+
);
|
|
33498
|
+
}
|
|
33400
33499
|
server.tool(
|
|
33401
33500
|
"present_schema",
|
|
33402
33501
|
"Open Sideboard\u2019s schema-driven side column (filterable table and/or form) when the user needs to filter, edit, publish, or persist records. Do not call this just to re-display rows you already wrote as a markdown table. If the user asks for an editable / interactive table, call this even if chat already showed those rows. Pass JSON Schema + optional schemaUi. Prefer datasource=inline with embedded resource/records. Use datasource=brightsy with resource_id only when the user is logged into Brightsy.",
|
|
@@ -36733,8 +36832,11 @@ async function startSlackRelayServer(opts) {
|
|
|
36733
36832
|
PLAN_FILE_NAME,
|
|
36734
36833
|
PLAN_FILE_REL,
|
|
36735
36834
|
PLAN_MODE_INSTRUCTION,
|
|
36835
|
+
PLAN_MODE_OFF_INSTRUCTION,
|
|
36736
36836
|
PLAN_QUESTION_ANSWERS_PREFIX,
|
|
36737
36837
|
PORT_RANGE_SIZE,
|
|
36838
|
+
PRESENT_PLAN_REQUIRES_PLAN_MODE,
|
|
36839
|
+
PRESENT_PLAN_TOOL_DESCRIPTION,
|
|
36738
36840
|
PROFILE_NOTES_MAX,
|
|
36739
36841
|
PR_DETAILS_JSON_FIELDS,
|
|
36740
36842
|
REPO_REVIEW_NAME,
|
|
@@ -36805,6 +36907,7 @@ async function startSlackRelayServer(opts) {
|
|
|
36805
36907
|
annotateDevAccessRuns,
|
|
36806
36908
|
appDataDir,
|
|
36807
36909
|
appSettingsPath,
|
|
36910
|
+
appendCompactedMessages,
|
|
36808
36911
|
appendIndexedGitConfig,
|
|
36809
36912
|
appendMessage,
|
|
36810
36913
|
appendQueuedItem,
|
|
@@ -36892,6 +36995,9 @@ async function startSlackRelayServer(opts) {
|
|
|
36892
36995
|
commentGitHubIssue,
|
|
36893
36996
|
commentLinearIssue,
|
|
36894
36997
|
commitAll,
|
|
36998
|
+
compactArchiveProse,
|
|
36999
|
+
compactPartsForSummaryMessage,
|
|
37000
|
+
compactSummaryPart,
|
|
36895
37001
|
computeNextRunAt,
|
|
36896
37002
|
conductorBundledBinDir,
|
|
36897
37003
|
conductorDbPath,
|
|
@@ -37291,6 +37397,7 @@ async function startSlackRelayServer(opts) {
|
|
|
37291
37397
|
mergeSideboardIntoMcpServersJson,
|
|
37292
37398
|
mergeUsage,
|
|
37293
37399
|
messagePartParentId,
|
|
37400
|
+
messagesForCompactArchive,
|
|
37294
37401
|
messagesSinceLastBrightsyContextSummary,
|
|
37295
37402
|
moveQueuedItemToFront,
|
|
37296
37403
|
needsCoordinatorAction,
|
|
@@ -37350,6 +37457,7 @@ async function startSlackRelayServer(opts) {
|
|
|
37350
37457
|
planFileAbs,
|
|
37351
37458
|
planHistoryAgePurge,
|
|
37352
37459
|
planHistoryRetention,
|
|
37460
|
+
planModeTurnInstruction,
|
|
37353
37461
|
planQuestionsSignature,
|
|
37354
37462
|
pnpmFilterPackage,
|
|
37355
37463
|
pollSlackOutboundWatches,
|
|
@@ -37477,6 +37585,7 @@ async function startSlackRelayServer(opts) {
|
|
|
37477
37585
|
shouldInjectBrightsyMcp,
|
|
37478
37586
|
shouldNotifyOrchestrator,
|
|
37479
37587
|
shouldRefreshReviewRequestTemplate,
|
|
37588
|
+
shouldRegisterPresentPlan,
|
|
37480
37589
|
shouldRemoveWorktreeOnTeardown,
|
|
37481
37590
|
shouldResetSessionForOccupancy,
|
|
37482
37591
|
shouldRunHistoryCleanup,
|
|
@@ -37538,6 +37647,7 @@ async function startSlackRelayServer(opts) {
|
|
|
37538
37647
|
threadHasCompactedContext,
|
|
37539
37648
|
threadLivePath,
|
|
37540
37649
|
threadLockPath,
|
|
37650
|
+
threadMayPresentPlan,
|
|
37541
37651
|
threadRequestsBrightsyMcp,
|
|
37542
37652
|
threadsDir,
|
|
37543
37653
|
threadsSharingWorktree,
|