@sideboard-ai/core 0.1.242 → 0.1.244
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-PBRIHUYM.js → agents-BQZ2CHXK.js} +9 -5
- package/dist/{agents-CDQCQVWU.js → agents-Q23R4WOR.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-3Q4PVZ3U.js → chunk-2QRU4IFI.js} +32 -5
- package/dist/{chunk-3PGFAVFL.js → chunk-43F44EKX.js} +2 -2
- package/dist/{chunk-LRMJ6V6U.js → chunk-4HMV53AB.js} +1506 -1465
- package/dist/{chunk-OKHOYI53.js → chunk-576S3HJ7.js} +5 -5
- package/dist/{chunk-TB6UMHIF.js → chunk-5ZFULOTC.js} +10 -2
- package/dist/{chunk-3AJUH66R.js → chunk-6I5YQTNK.js} +2 -1
- package/dist/{chunk-NJ5OXOTN.js → chunk-7HOKP4GR.js} +2 -1
- package/dist/{chunk-DXY4K5MP.js → chunk-BL2UGKWU.js} +3 -3
- package/dist/{chunk-CWHMLCLN.js → chunk-C64YQAA6.js} +3 -3
- package/dist/{chunk-QXYWLO6W.js → chunk-CYYQ6577.js} +28 -5
- package/dist/{chunk-O3UAQAYA.js → chunk-E6C5NR46.js} +10 -2
- package/dist/{chunk-CRAXJAYJ.js → chunk-G45QP2ZD.js} +3 -3
- package/dist/{chunk-BILEJG3V.js → chunk-GFRC6JQN.js} +5 -5
- package/dist/{chunk-YXOOSFAF.js → chunk-IMO7AZI4.js} +2 -2
- 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-R3YKDTVE.js → chunk-YJJ2A5CI.js} +1572 -1529
- package/dist/{chunk-QLWMHQPH.js → chunk-YLGBQVRK.js} +1 -0
- package/dist/{coordinator-prompt-5UINTEA6.js → coordinator-prompt-WQIICPT6.js} +4 -4
- package/dist/{coordinator-prompt-ATK73HYU.js → coordinator-prompt-ZBYRREHD.js} +4 -4
- package/dist/{global-workspace-ZJBBICFJ.js → global-workspace-2N6KISDO.js} +4 -4
- package/dist/{global-workspace-TD5SARNW.js → global-workspace-3SQU4GL4.js} +4 -4
- package/dist/index.cjs +590 -470
- package/dist/index.d.cts +50 -3
- package/dist/index.d.ts +50 -3
- package/dist/index.js +73 -39
- package/dist/mcp/run-stdio.cjs +1649 -1552
- package/dist/mcp/run-stdio.js +51 -37
- package/dist/{orchestrator-VYNPNHY4.js → orchestrator-ABF6QFE6.js} +7 -7
- package/dist/{orchestrator-66ZFQHW5.js → orchestrator-ZJ6SYGW7.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-JX5R4V24.js → workspaces-CFI7XVAE.js} +4 -4
- package/dist/{workspaces-4SFSCYKT.js → workspaces-QLKWW3O2.js} +4 -4
- package/dist/{worktree-AGVPKDBH.js → worktree-4UAOZUJ4.js} +5 -3
- package/dist/{worktree-Y5FES5D6.js → worktree-PK3QETXO.js} +5 -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(
|
|
@@ -8606,6 +8618,7 @@ __export(worktree_exports, {
|
|
|
8606
8618
|
getPrForHeadBranch: () => getPrForHeadBranch,
|
|
8607
8619
|
getPrMeta: () => getPrMeta,
|
|
8608
8620
|
ghHeadRef: () => ghHeadRef,
|
|
8621
|
+
ghPrCreateAssigneeArgs: () => ghPrCreateAssigneeArgs,
|
|
8609
8622
|
ghRepoSelectArgs: () => ghRepoSelectArgs,
|
|
8610
8623
|
githubAgentGitEnv: () => githubAgentGitEnv,
|
|
8611
8624
|
isDefaultishSourceRef: () => isDefaultishSourceRef,
|
|
@@ -9855,6 +9868,11 @@ async function mergePr(cwd, selector, opts) {
|
|
|
9855
9868
|
}
|
|
9856
9869
|
return { url, state: "MERGED" };
|
|
9857
9870
|
}
|
|
9871
|
+
function ghPrCreateAssigneeArgs(assignee) {
|
|
9872
|
+
const raw = assignee?.trim();
|
|
9873
|
+
if (raw && /^(none|unassigned)$/i.test(raw)) return [];
|
|
9874
|
+
return ["--assignee", raw || "@me"];
|
|
9875
|
+
}
|
|
9858
9876
|
async function createOrUpdatePr(worktreePath, opts) {
|
|
9859
9877
|
await ensureGhPreferOrigin(worktreePath);
|
|
9860
9878
|
const slug = await resolveGithubRepoSlug(worktreePath);
|
|
@@ -9929,6 +9947,7 @@ async function createOrUpdatePrWithBodyFile(worktreePath, opts) {
|
|
|
9929
9947
|
headRef
|
|
9930
9948
|
];
|
|
9931
9949
|
if (opts.draft) args2.push("--draft");
|
|
9950
|
+
args2.push(...ghPrCreateAssigneeArgs(opts.assignee));
|
|
9932
9951
|
await gh(args2, worktreePath, { reject: false });
|
|
9933
9952
|
const again = await gh(
|
|
9934
9953
|
[...repoArgs, "pr", "view", branchOnly, "--json", "url", "--jq", ".url"],
|
|
@@ -9950,6 +9969,7 @@ async function createOrUpdatePrWithBodyFile(worktreePath, opts) {
|
|
|
9950
9969
|
headRef
|
|
9951
9970
|
];
|
|
9952
9971
|
if (opts.draft) args.push("--draft");
|
|
9972
|
+
args.push(...ghPrCreateAssigneeArgs(opts.assignee));
|
|
9953
9973
|
const created = await gh(args, worktreePath, { reject: false });
|
|
9954
9974
|
if (created.exitCode !== 0) {
|
|
9955
9975
|
const raw = created.stderr.trim() || created.stdout.trim() || "gh pr create failed";
|
|
@@ -10272,7 +10292,7 @@ function coordinatorGreenfieldPlaybook(reposDir) {
|
|
|
10272
10292
|
"- Examples:",
|
|
10273
10293
|
` - Clone: \`git clone <url> ${reposDir}/<name>\``,
|
|
10274
10294
|
` - New GitHub repo: \`gh repo create <owner>/<name> --private --clone -- ${reposDir}/<name>\` (or mkdir + git init + gh repo create + remote add + push)`,
|
|
10275
|
-
"- Then: add_workspace with that absolute path \u2192 create_thread (repoPath + parentThreadId) \u2192 send_to_thread (build) \u2192 wait_for_turn (loop while stillRunning) \u2192 ask_git create-draft (or send_to_thread `gh pr create --draft -R <origin-owner/name>`).",
|
|
10295
|
+
"- Then: add_workspace with that absolute path \u2192 create_thread (repoPath + parentThreadId) \u2192 send_to_thread (build) \u2192 wait_for_turn (loop while stillRunning) \u2192 ask_git create-draft (or send_to_thread `gh pr create --draft --assignee @me -R <origin-owner/name>`).",
|
|
10276
10296
|
"- Always target the child worktree's **origin** (`github:` slug from list_workspaces / `git remote get-url origin` in that worktree). Never open PRs against `upstream`.",
|
|
10277
10297
|
"- Do coding work in the child worktree thread, not by editing files in this home cwd."
|
|
10278
10298
|
].join("\n");
|
|
@@ -10481,7 +10501,7 @@ var init_coordinator_prompt = __esm({
|
|
|
10481
10501
|
'- request_review \u2014 open a Review chat tab on a worktree thread (attaches .claude/skills/review/SKILL.md when present, else .context/review.md copied from .sideboard/review.md / stock; sends "Review changes in this workspace."); then wait_for_turn (loop while stillRunning) / get_turn_result on the returned id. The review stays in that chat so they can read it and type next steps \u2014 do not tell the child to comment or update the PR or ticket.',
|
|
10482
10502
|
"- ask_git \u2014 commit & push, open a draft PR, mark ready for review, resolve conflicts, or merge \u2014 same prompts as the desktop git buttons (Resolve, Create PR, \u2026). Always steers the worktree agent; then wait_for_turn (loop while stillRunning). Prefer this over paraphrasing. If the user gave a goal (Greptile 5/5, CI green), send_to_thread that goal so the worktree agent enters the watch-fix-push loop \u2014 do not tell the human to poll.",
|
|
10483
10503
|
'- Merge (`ask_git` action=merge / send_to_thread "Merge PR.") only when the user explicitly asked to merge that PR. Do not merge because the work looks done, CI is green, or a typical flow includes it.',
|
|
10484
|
-
'- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "Ready for review.", "Merge the remote branch (main) into your branch and resolve conflicts. Then, commit and push your changes.", "Merge PR." (draft PRs: `gh pr create --draft -R <origin-owner/name>` using the workspace `github:` slug \u2014 never upstream). Never run git/gh from this orchestration cwd, and never merge the PR yourself.',
|
|
10504
|
+
'- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "Ready for review.", "Merge the remote branch (main) into your branch and resolve conflicts. Then, commit and push your changes.", "Merge PR." (draft PRs: `gh pr create --draft --assignee @me -R <origin-owner/name>` using the workspace `github:` slug \u2014 never upstream). Never run git/gh from this orchestration cwd, and never merge the PR yourself.',
|
|
10485
10505
|
"Process guides:",
|
|
10486
10506
|
"- Long child jobs (pack, test, deploy, anything that may run more than ~30s): workers always have `/long-running` (Sideboard product skill). Tell them to detach and wait; they may `stop_job` if it hangs or is doing the wrong thing; do not ask the human to poll.",
|
|
10487
10507
|
"- Recurring multi-item / fan-out: if the child worktree has `.claude/skills/graph-engineering/SKILL.md`, tell the worker to follow it (`/graph-engineering`). Judge first; state on disk; grow the rulebook; do not patch three threads.",
|
|
@@ -12360,6 +12380,10 @@ var init_node_launch = __esm({
|
|
|
12360
12380
|
// src/agents/injected-mcp.ts
|
|
12361
12381
|
function sideboardWorktreeAllowedTools(opts) {
|
|
12362
12382
|
const out = [...SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS];
|
|
12383
|
+
if (!opts?.planMode) {
|
|
12384
|
+
const idx = out.indexOf("mcp__sideboard__present_plan");
|
|
12385
|
+
if (idx >= 0) out.splice(idx, 1);
|
|
12386
|
+
}
|
|
12363
12387
|
if (opts?.github !== false) out.push(...SIDEBOARD_GITHUB_MCP_ALLOWED_TOOLS);
|
|
12364
12388
|
if (opts?.linear) out.push(...SIDEBOARD_LINEAR_MCP_ALLOWED_TOOLS);
|
|
12365
12389
|
if (opts?.abletime) out.push(...SIDEBOARD_ABLETIME_MCP_ALLOWED_TOOLS);
|
|
@@ -12906,6 +12930,9 @@ var init_orch_mcp_isolation = __esm({
|
|
|
12906
12930
|
});
|
|
12907
12931
|
|
|
12908
12932
|
// src/agents/types.ts
|
|
12933
|
+
function planModeTurnInstruction(planMode) {
|
|
12934
|
+
return planMode ? PLAN_MODE_INSTRUCTION : PLAN_MODE_OFF_INSTRUCTION;
|
|
12935
|
+
}
|
|
12909
12936
|
function permissionMode(thread) {
|
|
12910
12937
|
if (thread.sourceType === "orchestration") {
|
|
12911
12938
|
return {
|
|
@@ -12944,11 +12971,12 @@ function permissionMode(thread) {
|
|
|
12944
12971
|
codexSandbox: "workspace-write"
|
|
12945
12972
|
};
|
|
12946
12973
|
}
|
|
12947
|
-
var PLAN_MODE_INSTRUCTION;
|
|
12974
|
+
var PLAN_MODE_INSTRUCTION, PLAN_MODE_OFF_INSTRUCTION;
|
|
12948
12975
|
var init_types = __esm({
|
|
12949
12976
|
"src/agents/types.ts"() {
|
|
12950
12977
|
"use strict";
|
|
12951
12978
|
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.";
|
|
12979
|
+
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
12980
|
}
|
|
12953
12981
|
});
|
|
12954
12982
|
|
|
@@ -13461,7 +13489,8 @@ var init_claude = __esm({
|
|
|
13461
13489
|
...sideboardWorktreeAllowedTools({
|
|
13462
13490
|
github: true,
|
|
13463
13491
|
linear: isLinearConnected(),
|
|
13464
|
-
abletime: isAbleTimeConnected()
|
|
13492
|
+
abletime: isAbleTimeConnected(),
|
|
13493
|
+
planMode: thread.planMode
|
|
13465
13494
|
}),
|
|
13466
13495
|
...brightsyMcpAllowedTools(injectedBrightsyNames)
|
|
13467
13496
|
];
|
|
@@ -15879,6 +15908,7 @@ __export(agents_exports, {
|
|
|
15879
15908
|
CURSOR_SDK_INSTALL_HINT: () => CURSOR_SDK_INSTALL_HINT,
|
|
15880
15909
|
ORCHESTRATOR_AGENT_KINDS: () => ORCHESTRATOR_AGENT_KINDS,
|
|
15881
15910
|
PLAN_MODE_INSTRUCTION: () => PLAN_MODE_INSTRUCTION,
|
|
15911
|
+
PLAN_MODE_OFF_INSTRUCTION: () => PLAN_MODE_OFF_INSTRUCTION,
|
|
15882
15912
|
allAdapters: () => allAdapters,
|
|
15883
15913
|
assertOrchestratorCapableAgent: () => assertOrchestratorCapableAgent,
|
|
15884
15914
|
brightsyAdapter: () => brightsyAdapter,
|
|
@@ -15929,6 +15959,7 @@ __export(agents_exports, {
|
|
|
15929
15959
|
parseCursorRunnerLine: () => parseCursorRunnerLine,
|
|
15930
15960
|
parseSessionQuotaResetAt: () => parseSessionQuotaResetAt,
|
|
15931
15961
|
permissionMode: () => permissionMode,
|
|
15962
|
+
planModeTurnInstruction: () => planModeTurnInstruction,
|
|
15932
15963
|
posixShellSingleQuote: () => posixShellSingleQuote,
|
|
15933
15964
|
preferredCursorCostCents: () => preferredCursorCostCents,
|
|
15934
15965
|
prepareTerminalCommand: () => prepareTerminalCommand,
|
|
@@ -16391,9 +16422,9 @@ function gitActionExpansion(action, base) {
|
|
|
16391
16422
|
case "commit-push":
|
|
16392
16423
|
return "Commit any uncommitted work with a purpose-stating message, then push to origin (`git push -u origin HEAD`; this updates an existing PR when one is linked). Do not start a checks loop unless a goal was given.";
|
|
16393
16424
|
case "create-draft":
|
|
16394
|
-
return "Commit, push, then create a draft PR with `gh pr create --draft -R <origin-owner/name>` (title/body from the change purpose). Update the existing PR instead if one is linked.";
|
|
16425
|
+
return "Commit, push, then create a draft PR with `gh pr create --draft --assignee @me -R <origin-owner/name>` (title/body from the change purpose). Update the existing PR instead if one is linked. Use a different `--assignee` only if the user named someone else.";
|
|
16395
16426
|
case "create-web":
|
|
16396
|
-
return "Commit, push, then `gh pr create --web -R <origin-owner/name>`.";
|
|
16427
|
+
return "Commit, push, then `gh pr create --web --assignee @me -R <origin-owner/name>`. Use a different `--assignee` only if the user named someone else.";
|
|
16397
16428
|
case "resolve-conflicts":
|
|
16398
16429
|
return `Fetch the PR base${base ? ` (\`${base}\`)` : ""}, merge it into this branch, resolve conflicts carefully, then commit and push until the PR is mergeable.`;
|
|
16399
16430
|
case "ready-for-review":
|
|
@@ -16911,12 +16942,38 @@ ${match.text}`, "");
|
|
|
16911
16942
|
blocks.push("Continue from this context. Do not repeat the summary unless asked.");
|
|
16912
16943
|
return blocks.join("\n");
|
|
16913
16944
|
}
|
|
16945
|
+
function compactArchiveProse(message) {
|
|
16946
|
+
if (message.origin === "continue") return null;
|
|
16947
|
+
if (message.role === "summary") return null;
|
|
16948
|
+
let text7 = (message.text ?? "").trim();
|
|
16949
|
+
if (!text7 && message.role === "agent") {
|
|
16950
|
+
text7 = lastAssistantMessageText(message.parts).trim();
|
|
16951
|
+
}
|
|
16952
|
+
if (!text7 || isInternalAgentStatusText(text7)) return null;
|
|
16953
|
+
return { role: message.role, text: text7, ts: message.ts };
|
|
16954
|
+
}
|
|
16955
|
+
function messagesForCompactArchive(older) {
|
|
16956
|
+
const out = [];
|
|
16957
|
+
for (const message of older) {
|
|
16958
|
+
const prose = compactArchiveProse(message);
|
|
16959
|
+
if (prose) out.push(prose);
|
|
16960
|
+
}
|
|
16961
|
+
return out;
|
|
16962
|
+
}
|
|
16963
|
+
function appendCompactedMessages(existing, older) {
|
|
16964
|
+
const next = messagesForCompactArchive(older);
|
|
16965
|
+
if (!existing?.length) return next;
|
|
16966
|
+
if (!next.length) return existing;
|
|
16967
|
+
return [...existing, ...next];
|
|
16968
|
+
}
|
|
16914
16969
|
function applyCompaction(messages, summaryText, thresholds = {}) {
|
|
16915
16970
|
const { older, recent } = splitForCompaction(messages, thresholds);
|
|
16916
16971
|
if (older.length === 0) return messages;
|
|
16972
|
+
const text7 = summaryText.trim();
|
|
16917
16973
|
const summary = {
|
|
16918
16974
|
role: "summary",
|
|
16919
|
-
text:
|
|
16975
|
+
text: text7,
|
|
16976
|
+
parts: [compactSummaryPart(text7)],
|
|
16920
16977
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
16921
16978
|
};
|
|
16922
16979
|
return [summary, ...recent];
|
|
@@ -16944,6 +17001,7 @@ async function maybeCompactContext(thread, thresholds = {}, summarize = summariz
|
|
|
16944
17001
|
cwd: thread.worktreePath
|
|
16945
17002
|
});
|
|
16946
17003
|
let messages = applyCompaction(thread.messages, summary, thresholds);
|
|
17004
|
+
const compactedMessages = appendCompactedMessages(thread.compactedMessages, older);
|
|
16947
17005
|
const resetSession = shouldResetSessionForOccupancy({ messages: thread.messages });
|
|
16948
17006
|
if (resetSession) {
|
|
16949
17007
|
messages = applyForwardOccupancy(messages);
|
|
@@ -16951,6 +17009,7 @@ async function maybeCompactContext(thread, thresholds = {}, summarize = summariz
|
|
|
16951
17009
|
const next = {
|
|
16952
17010
|
...thread,
|
|
16953
17011
|
messages,
|
|
17012
|
+
...compactedMessages.length ? { compactedMessages } : { compactedMessages: void 0 },
|
|
16954
17013
|
sessionId: resetSession ? null : thread.sessionId,
|
|
16955
17014
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16956
17015
|
};
|
|
@@ -16966,6 +17025,7 @@ var CONTEXT_COMPACT_CHARS, CONTEXT_KEEP_RECENT_CHARS, CONTEXT_KEEP_RECENT_MESSAG
|
|
|
16966
17025
|
var init_context_compact = __esm({
|
|
16967
17026
|
"src/composer/context-compact.ts"() {
|
|
16968
17027
|
"use strict";
|
|
17028
|
+
init_message_parts();
|
|
16969
17029
|
init_error_detail();
|
|
16970
17030
|
init_usage();
|
|
16971
17031
|
init_context_estimate();
|
|
@@ -17552,15 +17612,403 @@ var init_worktree_run_ports = __esm({
|
|
|
17552
17612
|
}
|
|
17553
17613
|
});
|
|
17554
17614
|
|
|
17615
|
+
// src/mcp/wait-for-turn.ts
|
|
17616
|
+
function mcpWaitForTurnTimeoutMs(requested) {
|
|
17617
|
+
const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
|
|
17618
|
+
if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
|
|
17619
|
+
return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
|
|
17620
|
+
}
|
|
17621
|
+
function mcpWaitTaskHint(taskState, status) {
|
|
17622
|
+
switch (taskState) {
|
|
17623
|
+
case "submitted":
|
|
17624
|
+
return MCP_WAIT_QUEUED_HINT;
|
|
17625
|
+
case "working":
|
|
17626
|
+
return MCP_WAIT_STILL_RUNNING_HINT;
|
|
17627
|
+
case "input-required":
|
|
17628
|
+
return MCP_WAIT_INPUT_REQUIRED_HINT;
|
|
17629
|
+
case "canceled":
|
|
17630
|
+
return MCP_WAIT_STOPPED_HINT;
|
|
17631
|
+
case "failed":
|
|
17632
|
+
return status === "broken" ? MCP_WAIT_BROKEN_HINT : MCP_WAIT_ERROR_HINT;
|
|
17633
|
+
default:
|
|
17634
|
+
return void 0;
|
|
17635
|
+
}
|
|
17636
|
+
}
|
|
17637
|
+
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;
|
|
17638
|
+
var init_wait_for_turn = __esm({
|
|
17639
|
+
"src/mcp/wait-for-turn.ts"() {
|
|
17640
|
+
"use strict";
|
|
17641
|
+
MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
17642
|
+
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.";
|
|
17643
|
+
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.";
|
|
17644
|
+
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.";
|
|
17645
|
+
MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
|
|
17646
|
+
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.";
|
|
17647
|
+
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.";
|
|
17648
|
+
}
|
|
17649
|
+
});
|
|
17650
|
+
|
|
17651
|
+
// src/mcp/wait-for-job.ts
|
|
17652
|
+
function mcpWaitForJobTimeoutMs(requested) {
|
|
17653
|
+
return mcpWaitForTurnTimeoutMs(requested);
|
|
17654
|
+
}
|
|
17655
|
+
function sanitizeDetachedJobId(id) {
|
|
17656
|
+
const s = id.trim();
|
|
17657
|
+
if (!JOB_ID_RE.test(s)) {
|
|
17658
|
+
throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
|
|
17659
|
+
}
|
|
17660
|
+
return s;
|
|
17661
|
+
}
|
|
17662
|
+
function jobAlive(pid) {
|
|
17663
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
17664
|
+
try {
|
|
17665
|
+
process.kill(pid, 0);
|
|
17666
|
+
return true;
|
|
17667
|
+
} catch {
|
|
17668
|
+
return false;
|
|
17669
|
+
}
|
|
17670
|
+
}
|
|
17671
|
+
function processGroupAlive(pgid) {
|
|
17672
|
+
if (process.platform === "win32") return false;
|
|
17673
|
+
if (!Number.isInteger(pgid) || pgid <= 1) return false;
|
|
17674
|
+
try {
|
|
17675
|
+
process.kill(-pgid, 0);
|
|
17676
|
+
return true;
|
|
17677
|
+
} catch (err) {
|
|
17678
|
+
return err && typeof err === "object" && "code" in err && err.code === "EPERM";
|
|
17679
|
+
}
|
|
17680
|
+
}
|
|
17681
|
+
function jobTreeAlive(pid) {
|
|
17682
|
+
if (pid == null) return false;
|
|
17683
|
+
return jobAlive(pid) || processGroupAlive(pid);
|
|
17684
|
+
}
|
|
17685
|
+
function readIntFile(file) {
|
|
17686
|
+
if (!(0, import_node_fs45.existsSync)(file)) return null;
|
|
17687
|
+
const n = Number.parseInt((0, import_node_fs45.readFileSync)(file, "utf8").trim(), 10);
|
|
17688
|
+
return Number.isInteger(n) ? n : null;
|
|
17689
|
+
}
|
|
17690
|
+
function jobDir(root, id, legacy = false) {
|
|
17691
|
+
return (0, import_node_path45.join)(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
|
|
17692
|
+
}
|
|
17693
|
+
function resolveJobDir(root, id) {
|
|
17694
|
+
const modern = jobDir(root, id, false);
|
|
17695
|
+
if ((0, import_node_fs45.existsSync)(modern)) return modern;
|
|
17696
|
+
const legacy = jobDir(root, id, true);
|
|
17697
|
+
if ((0, import_node_fs45.existsSync)(legacy)) return legacy;
|
|
17698
|
+
return modern;
|
|
17699
|
+
}
|
|
17700
|
+
function listJobIdsIn(root, rel) {
|
|
17701
|
+
const dir = (0, import_node_path45.join)(root, rel);
|
|
17702
|
+
if (!(0, import_node_fs45.existsSync)(dir)) return [];
|
|
17703
|
+
return (0, import_node_fs45.readdirSync)(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
|
|
17704
|
+
}
|
|
17705
|
+
function listRunningDetachedJobs(worktreePath) {
|
|
17706
|
+
const root = worktreePath.trim();
|
|
17707
|
+
if (!root) return [];
|
|
17708
|
+
const ids = /* @__PURE__ */ new Set([
|
|
17709
|
+
...listJobIdsIn(root, DETACHED_JOBS_DIR),
|
|
17710
|
+
...listJobIdsIn(root, LEGACY_DETACHED_JOBS_DIR)
|
|
17711
|
+
]);
|
|
17712
|
+
const running = [];
|
|
17713
|
+
for (const id of ids) {
|
|
17714
|
+
const dir = resolveJobDir(root, id);
|
|
17715
|
+
const pid = readIntFile((0, import_node_path45.join)(dir, "pid"));
|
|
17716
|
+
if (jobTreeAlive(pid)) running.push(id);
|
|
17717
|
+
}
|
|
17718
|
+
return running.sort();
|
|
17719
|
+
}
|
|
17720
|
+
function looksLikeDeferredDonePromise(text7) {
|
|
17721
|
+
const t = (text7 ?? "").trim();
|
|
17722
|
+
if (!t || t.length > DEFERRED_DONE_MAX_CHARS) return false;
|
|
17723
|
+
const unquoted = t.replace(/`[^`]*`/g, " ").replace(/["“”][^"“”]*["“”]/g, " ").replace(/\s+/g, " ").trim();
|
|
17724
|
+
if (!unquoted) return false;
|
|
17725
|
+
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);
|
|
17726
|
+
}
|
|
17727
|
+
function isJobContinuePrompt(text7) {
|
|
17728
|
+
if (typeof text7 !== "string") return false;
|
|
17729
|
+
const t = text7.trim();
|
|
17730
|
+
return t.startsWith("Detached job still running:") || t.startsWith("You ended the turn after promising to report later");
|
|
17731
|
+
}
|
|
17732
|
+
function collapseGhRunWatchLog(text7) {
|
|
17733
|
+
if (!text7.includes(GH_WATCH_REFRESH)) return text7;
|
|
17734
|
+
const frames = text7.split(/(?=Refreshing run status every 3 seconds\.)/);
|
|
17735
|
+
const watchFrames = frames.filter((f) => f.includes(GH_WATCH_REFRESH));
|
|
17736
|
+
if (watchFrames.length <= 1) return text7;
|
|
17737
|
+
const prefix = frames[0]?.includes(GH_WATCH_REFRESH) ? "" : frames[0] ?? "";
|
|
17738
|
+
const last = watchFrames[watchFrames.length - 1] ?? "";
|
|
17739
|
+
const omitted = watchFrames.length - 1;
|
|
17740
|
+
const note = `(${omitted} identical gh run watch refresh${omitted === 1 ? "" : "es"} omitted)
|
|
17741
|
+
`;
|
|
17742
|
+
return [prefix.trimEnd(), note, last.trimStart()].filter(Boolean).join("\n");
|
|
17743
|
+
}
|
|
17744
|
+
function capJobLogDelta(text7) {
|
|
17745
|
+
const collapsed = collapseGhRunWatchLog(text7);
|
|
17746
|
+
const lines = collapsed.split("\n");
|
|
17747
|
+
let out = lines.length > MAX_JOB_DELTA_LINES ? `\u2026(${lines.length - MAX_JOB_DELTA_LINES} earlier lines omitted)
|
|
17748
|
+
${lines.slice(-MAX_JOB_DELTA_LINES).join("\n")}` : collapsed;
|
|
17749
|
+
if (out.length > MAX_JOB_DELTA_CHARS) {
|
|
17750
|
+
out = `\u2026(truncated ${out.length} chars)
|
|
17751
|
+
${out.slice(-MAX_JOB_DELTA_CHARS)}`;
|
|
17752
|
+
}
|
|
17753
|
+
return out;
|
|
17754
|
+
}
|
|
17755
|
+
function formatJobStillRunningContinuePrompt(jobIds) {
|
|
17756
|
+
const ids = jobIds.join(", ");
|
|
17757
|
+
return [
|
|
17758
|
+
`Detached job still running: ${ids}.`,
|
|
17759
|
+
"Do not end this turn. Loop wait_for_job (same id) until stillRunning is false. The log pane updates from each wait result.",
|
|
17760
|
+
"If the job is hanging or doing the wrong thing, call stop_job (same id) instead of looping forever.",
|
|
17761
|
+
"Then report the result. Do not tell the user you will let them know later."
|
|
17762
|
+
].join(" ");
|
|
17763
|
+
}
|
|
17764
|
+
function formatDeferredDoneContinuePrompt() {
|
|
17765
|
+
return [
|
|
17766
|
+
"You ended the turn after promising to report later, but no detached job is running.",
|
|
17767
|
+
"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).",
|
|
17768
|
+
"Do not say you will let the user know later."
|
|
17769
|
+
].join(" ");
|
|
17770
|
+
}
|
|
17771
|
+
function turnWatchedDetachedJob(parts) {
|
|
17772
|
+
return parts.some((p) => {
|
|
17773
|
+
if (p.type !== "tool") return false;
|
|
17774
|
+
if (/wait_for_job$/i.test(p.name ?? "") || /stop_job$/i.test(p.name ?? "")) return true;
|
|
17775
|
+
const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
|
|
17776
|
+
return /detached-job\.(?:js|cjs)\b/i.test(blob);
|
|
17777
|
+
});
|
|
17778
|
+
}
|
|
17779
|
+
function planJobContinue(opts) {
|
|
17780
|
+
if (opts.isOrchestrator) return { action: "none" };
|
|
17781
|
+
if (opts.agent === "brightsy") return { action: "none" };
|
|
17782
|
+
if (opts.queueLength > 0) return { action: "none" };
|
|
17783
|
+
if (opts.continueCount >= MAX_JOB_CONTINUES) return { action: "none" };
|
|
17784
|
+
const farewell = looksLikeDeferredDonePromise(opts.chatText);
|
|
17785
|
+
if (opts.runningJobIds.length > 0 && (farewell || opts.watchedJob)) {
|
|
17786
|
+
return {
|
|
17787
|
+
action: "wait",
|
|
17788
|
+
jobIds: opts.runningJobIds,
|
|
17789
|
+
prompt: formatJobStillRunningContinuePrompt(opts.runningJobIds)
|
|
17790
|
+
};
|
|
17791
|
+
}
|
|
17792
|
+
if (looksLikeDeferredDonePromise(opts.chatText) && !opts.alreadyNudged) {
|
|
17793
|
+
return { action: "nudge", prompt: formatDeferredDoneContinuePrompt() };
|
|
17794
|
+
}
|
|
17795
|
+
return { action: "none" };
|
|
17796
|
+
}
|
|
17797
|
+
function tailProgress(logFile, maxLines = 12) {
|
|
17798
|
+
if (!(0, import_node_fs45.existsSync)(logFile)) return "(no log yet)";
|
|
17799
|
+
const lines = (0, import_node_fs45.readFileSync)(logFile, "utf8").split("\n");
|
|
17800
|
+
return lines.slice(-maxLines).join("\n");
|
|
17801
|
+
}
|
|
17802
|
+
function lastProgressLine(progress) {
|
|
17803
|
+
const last = progress.split("\n").map((l) => l.trim()).filter((l) => l && l !== "(no log yet)").at(-1);
|
|
17804
|
+
return last ? last.slice(0, 72) : void 0;
|
|
17805
|
+
}
|
|
17806
|
+
function readLogLines(file) {
|
|
17807
|
+
if (!(0, import_node_fs45.existsSync)(file)) return [];
|
|
17808
|
+
const lines = (0, import_node_fs45.readFileSync)(file, "utf8").split("\n");
|
|
17809
|
+
if (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
17810
|
+
return lines;
|
|
17811
|
+
}
|
|
17812
|
+
function takeDelta(logFile, cursorFile) {
|
|
17813
|
+
const lines = readLogLines(logFile);
|
|
17814
|
+
const cursor = readIntFile(cursorFile) ?? 0;
|
|
17815
|
+
const start = Math.min(Math.max(0, cursor), lines.length);
|
|
17816
|
+
return { delta: capJobLogDelta(lines.slice(start).join("\n")), nextCursor: lines.length };
|
|
17817
|
+
}
|
|
17818
|
+
function snapshotJob(dir) {
|
|
17819
|
+
const pid = readIntFile((0, import_node_path45.join)(dir, "pid"));
|
|
17820
|
+
const running = jobTreeAlive(pid);
|
|
17821
|
+
return {
|
|
17822
|
+
pid,
|
|
17823
|
+
running,
|
|
17824
|
+
exitCode: readIntFile((0, import_node_path45.join)(dir, "exit")),
|
|
17825
|
+
log: (0, import_node_path45.join)(dir, "log"),
|
|
17826
|
+
cursor: (0, import_node_path45.join)(dir, "present.cursor"),
|
|
17827
|
+
progress: tailProgress((0, import_node_path45.join)(dir, "log"))
|
|
17828
|
+
};
|
|
17829
|
+
}
|
|
17830
|
+
function toResult(id, snap, extra) {
|
|
17831
|
+
const failed = extra?.failed === true || !snap.running && snap.exitCode != null && snap.exitCode !== 0;
|
|
17832
|
+
const ok = !snap.running && snap.exitCode === 0;
|
|
17833
|
+
const stillRunning = snap.running && !ok;
|
|
17834
|
+
const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
|
|
17835
|
+
try {
|
|
17836
|
+
(0, import_node_fs45.mkdirSync)((0, import_node_path45.dirname)(snap.cursor), { recursive: true });
|
|
17837
|
+
(0, import_node_fs45.writeFileSync)(snap.cursor, `${nextCursor}
|
|
17838
|
+
`);
|
|
17839
|
+
} catch {
|
|
17840
|
+
}
|
|
17841
|
+
const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
|
|
17842
|
+
const progress = extra?.progress ?? snap.progress;
|
|
17843
|
+
return {
|
|
17844
|
+
stillRunning,
|
|
17845
|
+
ok,
|
|
17846
|
+
failed: Boolean(failed && !stillRunning && !ok),
|
|
17847
|
+
status,
|
|
17848
|
+
id,
|
|
17849
|
+
pid: snap.pid,
|
|
17850
|
+
exitCode: snap.exitCode ?? void 0,
|
|
17851
|
+
phase: lastProgressLine(progress),
|
|
17852
|
+
delta,
|
|
17853
|
+
progress,
|
|
17854
|
+
hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
|
|
17855
|
+
};
|
|
17856
|
+
}
|
|
17857
|
+
async function sleepMs(ms) {
|
|
17858
|
+
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
17859
|
+
}
|
|
17860
|
+
async function waitForDetachedJob(cwd, id, opts) {
|
|
17861
|
+
const jobId = sanitizeDetachedJobId(id);
|
|
17862
|
+
const root = cwd.trim() || process.cwd();
|
|
17863
|
+
const dir = resolveJobDir(root, jobId);
|
|
17864
|
+
const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
|
|
17865
|
+
const sleep2 = opts?.sleep ?? sleepMs;
|
|
17866
|
+
if (!(0, import_node_fs45.existsSync)(dir)) {
|
|
17867
|
+
return {
|
|
17868
|
+
stillRunning: false,
|
|
17869
|
+
ok: false,
|
|
17870
|
+
failed: true,
|
|
17871
|
+
status: "failed",
|
|
17872
|
+
id: jobId,
|
|
17873
|
+
delta: "",
|
|
17874
|
+
progress: "No detached job. Start one first.",
|
|
17875
|
+
hint: "Start with detached-job.cjs start <id> -- <command>, then call wait_for_job again."
|
|
17876
|
+
};
|
|
17877
|
+
}
|
|
17878
|
+
const deadline = Date.now() + timeoutMs;
|
|
17879
|
+
let snap = snapshotJob(dir);
|
|
17880
|
+
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
17881
|
+
if (!snap.running && snap.pid == null && snap.progress === "(no log yet)") {
|
|
17882
|
+
return toResult(jobId, snap, {
|
|
17883
|
+
failed: true,
|
|
17884
|
+
progress: "No detached job. Start one first."
|
|
17885
|
+
});
|
|
17886
|
+
}
|
|
17887
|
+
while (Date.now() < deadline) {
|
|
17888
|
+
snap = snapshotJob(dir);
|
|
17889
|
+
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
17890
|
+
if (!snap.running) return toResult(jobId, snap, { failed: true });
|
|
17891
|
+
await sleep2(Math.min(2e3, Math.max(50, deadline - Date.now())));
|
|
17892
|
+
}
|
|
17893
|
+
snap = snapshotJob(dir);
|
|
17894
|
+
return toResult(jobId, snap);
|
|
17895
|
+
}
|
|
17896
|
+
function killProcessTree(pid, signal) {
|
|
17897
|
+
if (!Number.isInteger(pid) || pid <= 0) return;
|
|
17898
|
+
if (process.platform !== "win32") {
|
|
17899
|
+
try {
|
|
17900
|
+
process.kill(-pid, signal);
|
|
17901
|
+
return;
|
|
17902
|
+
} catch {
|
|
17903
|
+
}
|
|
17904
|
+
}
|
|
17905
|
+
try {
|
|
17906
|
+
process.kill(pid, signal);
|
|
17907
|
+
} catch {
|
|
17908
|
+
}
|
|
17909
|
+
}
|
|
17910
|
+
function appendJobLog(logFile, line) {
|
|
17911
|
+
try {
|
|
17912
|
+
(0, import_node_fs45.mkdirSync)((0, import_node_path45.dirname)(logFile), { recursive: true });
|
|
17913
|
+
const body = line.endsWith("\n") ? line : `${line}
|
|
17914
|
+
`;
|
|
17915
|
+
(0, import_node_fs45.appendFileSync)(logFile, body);
|
|
17916
|
+
} catch {
|
|
17917
|
+
}
|
|
17918
|
+
}
|
|
17919
|
+
async function stopDetachedJob(cwd, id, opts) {
|
|
17920
|
+
const jobId = sanitizeDetachedJobId(id);
|
|
17921
|
+
const root = cwd.trim() || process.cwd();
|
|
17922
|
+
const dir = resolveJobDir(root, jobId);
|
|
17923
|
+
const sleep2 = opts?.sleep ?? sleepMs;
|
|
17924
|
+
const why = opts?.reason?.trim() ?? "";
|
|
17925
|
+
if (!(0, import_node_fs45.existsSync)(dir)) {
|
|
17926
|
+
return {
|
|
17927
|
+
stillRunning: false,
|
|
17928
|
+
ok: false,
|
|
17929
|
+
failed: true,
|
|
17930
|
+
status: "failed",
|
|
17931
|
+
id: jobId,
|
|
17932
|
+
delta: "",
|
|
17933
|
+
progress: "No detached job. Start one first.",
|
|
17934
|
+
hint: "Start with detached-job.cjs start <id> -- <command>, then wait_for_job or stop_job.",
|
|
17935
|
+
stopped: false,
|
|
17936
|
+
reason: "not-found"
|
|
17937
|
+
};
|
|
17938
|
+
}
|
|
17939
|
+
let snap = snapshotJob(dir);
|
|
17940
|
+
if (!snap.running) {
|
|
17941
|
+
const result2 = toResult(jobId, snap);
|
|
17942
|
+
return {
|
|
17943
|
+
...result2,
|
|
17944
|
+
stopped: false,
|
|
17945
|
+
reason: "not-running",
|
|
17946
|
+
hint: "That job is already finished. Read the log; do not wait again unless you start a new command."
|
|
17947
|
+
};
|
|
17948
|
+
}
|
|
17949
|
+
appendJobLog(snap.log, `$ stop${why ? `: ${why}` : ""}`);
|
|
17950
|
+
const targetPid = snap.pid;
|
|
17951
|
+
if (targetPid != null) killProcessTree(targetPid, "SIGTERM");
|
|
17952
|
+
const graceMs = Number.isFinite(opts?.graceMs) ? Math.max(50, opts.graceMs) : 2e3;
|
|
17953
|
+
const deadline = Date.now() + graceMs;
|
|
17954
|
+
while (Date.now() < deadline && jobTreeAlive(targetPid)) {
|
|
17955
|
+
await sleep2(Math.min(100, Math.max(20, deadline - Date.now())));
|
|
17956
|
+
}
|
|
17957
|
+
if (targetPid != null) {
|
|
17958
|
+
killProcessTree(targetPid, "SIGKILL");
|
|
17959
|
+
await sleep2(100);
|
|
17960
|
+
}
|
|
17961
|
+
const exitFile = (0, import_node_path45.join)(dir, "exit");
|
|
17962
|
+
if (!(0, import_node_fs45.existsSync)(exitFile)) {
|
|
17963
|
+
try {
|
|
17964
|
+
(0, import_node_fs45.writeFileSync)(exitFile, "1\n");
|
|
17965
|
+
} catch {
|
|
17966
|
+
}
|
|
17967
|
+
}
|
|
17968
|
+
snap = snapshotJob(dir);
|
|
17969
|
+
const stillRunning = jobTreeAlive(targetPid) || snap.running;
|
|
17970
|
+
const result = toResult(jobId, snap, { failed: true });
|
|
17971
|
+
const stopped = !stillRunning;
|
|
17972
|
+
return {
|
|
17973
|
+
...result,
|
|
17974
|
+
stillRunning,
|
|
17975
|
+
ok: false,
|
|
17976
|
+
failed: true,
|
|
17977
|
+
status: stillRunning ? "running" : "failed",
|
|
17978
|
+
stopped,
|
|
17979
|
+
reason: stopped ? "stopped" : "still-running",
|
|
17980
|
+
stopReason: why || void 0,
|
|
17981
|
+
hint: stopped ? MCP_STOP_JOB_HINT : MCP_WAIT_JOB_STILL_RUNNING_HINT
|
|
17982
|
+
};
|
|
17983
|
+
}
|
|
17984
|
+
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;
|
|
17985
|
+
var init_wait_for_job = __esm({
|
|
17986
|
+
"src/mcp/wait-for-job.ts"() {
|
|
17987
|
+
"use strict";
|
|
17988
|
+
import_node_fs45 = require("fs");
|
|
17989
|
+
import_node_path45 = require("path");
|
|
17990
|
+
init_workspace_scratch();
|
|
17991
|
+
init_wait_for_turn();
|
|
17992
|
+
MAX_JOB_CONTINUES = 8;
|
|
17993
|
+
MAX_JOB_DELTA_LINES = 80;
|
|
17994
|
+
MAX_JOB_DELTA_CHARS = 4e3;
|
|
17995
|
+
DEFERRED_DONE_MAX_CHARS = 480;
|
|
17996
|
+
GH_WATCH_REFRESH = "Refreshing run status every 3 seconds.";
|
|
17997
|
+
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.";
|
|
17998
|
+
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.";
|
|
17999
|
+
JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
18000
|
+
}
|
|
18001
|
+
});
|
|
18002
|
+
|
|
17555
18003
|
// src/hook/conductor.ts
|
|
17556
18004
|
function matchSimpleGlob(pattern, name) {
|
|
17557
18005
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
17558
18006
|
return new RegExp(`^${escaped}$`).test(name);
|
|
17559
18007
|
}
|
|
17560
18008
|
function readWorktreeInclude(repoPath) {
|
|
17561
|
-
const path2 = (0,
|
|
17562
|
-
if (!(0,
|
|
17563
|
-
return (0,
|
|
18009
|
+
const path2 = (0, import_node_path46.join)(repoPath, ".worktreeinclude");
|
|
18010
|
+
if (!(0, import_node_fs46.existsSync)(path2)) return [];
|
|
18011
|
+
return (0, import_node_fs46.readFileSync)(path2, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
17564
18012
|
}
|
|
17565
18013
|
function resolveFilesToCopy(repoPath) {
|
|
17566
18014
|
const fromInclude = readWorktreeInclude(repoPath);
|
|
@@ -17570,10 +18018,10 @@ function resolveFilesToCopy(repoPath) {
|
|
|
17570
18018
|
if (settings?.fileIncludeGlobs?.length) {
|
|
17571
18019
|
const matched = [];
|
|
17572
18020
|
try {
|
|
17573
|
-
for (const entry of (0,
|
|
18021
|
+
for (const entry of (0, import_node_fs46.readdirSync)(repoPath, { withFileTypes: true })) {
|
|
17574
18022
|
if (!entry.isFile()) continue;
|
|
17575
18023
|
for (const glob of settings.fileIncludeGlobs) {
|
|
17576
|
-
if (matchSimpleGlob(glob, entry.name) || matchSimpleGlob((0,
|
|
18024
|
+
if (matchSimpleGlob(glob, entry.name) || matchSimpleGlob((0, import_node_path46.basename)(glob), entry.name)) {
|
|
17577
18025
|
matched.push(entry.name);
|
|
17578
18026
|
break;
|
|
17579
18027
|
}
|
|
@@ -17585,7 +18033,7 @@ function resolveFilesToCopy(repoPath) {
|
|
|
17585
18033
|
}
|
|
17586
18034
|
const defaults = [];
|
|
17587
18035
|
try {
|
|
17588
|
-
for (const entry of (0,
|
|
18036
|
+
for (const entry of (0, import_node_fs46.readdirSync)(repoPath, { withFileTypes: true })) {
|
|
17589
18037
|
if (entry.isFile() && entry.name.startsWith(".env")) {
|
|
17590
18038
|
defaults.push(entry.name);
|
|
17591
18039
|
}
|
|
@@ -17599,11 +18047,11 @@ function copyConfiguredFiles(repoPath, worktreePath) {
|
|
|
17599
18047
|
const patterns = resolveFilesToCopy(repoPath);
|
|
17600
18048
|
const copied = [];
|
|
17601
18049
|
for (const rel of patterns) {
|
|
17602
|
-
const src = (0,
|
|
17603
|
-
if (!(0,
|
|
17604
|
-
const dest = (0,
|
|
17605
|
-
(0,
|
|
17606
|
-
(0,
|
|
18050
|
+
const src = (0, import_node_path46.join)(repoPath, rel);
|
|
18051
|
+
if (!(0, import_node_fs46.existsSync)(src)) continue;
|
|
18052
|
+
const dest = (0, import_node_path46.join)(worktreePath, rel);
|
|
18053
|
+
(0, import_node_fs46.mkdirSync)((0, import_node_path46.dirname)(dest), { recursive: true });
|
|
18054
|
+
(0, import_node_fs46.copyFileSync)(src, dest);
|
|
17607
18055
|
copied.push(rel);
|
|
17608
18056
|
}
|
|
17609
18057
|
return copied;
|
|
@@ -17638,7 +18086,7 @@ function buildWorkspaceScriptEnv(opts, baseEnv) {
|
|
|
17638
18086
|
const env = stripNestedElectronEnv({
|
|
17639
18087
|
...baseEnv ?? process.env
|
|
17640
18088
|
});
|
|
17641
|
-
const name = opts.workspaceName ?? (0,
|
|
18089
|
+
const name = opts.workspaceName ?? (0, import_node_path46.basename)(opts.worktreePath);
|
|
17642
18090
|
const ports = opts.ports ?? [];
|
|
17643
18091
|
const primary = ports[0];
|
|
17644
18092
|
env.SIDEBOARD_WORKSPACE_NAME = name;
|
|
@@ -17707,20 +18155,20 @@ function listenerCwd(pid) {
|
|
|
17707
18155
|
}
|
|
17708
18156
|
function realpathOrResolve(path2) {
|
|
17709
18157
|
try {
|
|
17710
|
-
return (0,
|
|
18158
|
+
return (0, import_node_fs46.realpathSync)(path2);
|
|
17711
18159
|
} catch {
|
|
17712
|
-
return (0,
|
|
18160
|
+
return (0, import_node_path46.resolve)(path2);
|
|
17713
18161
|
}
|
|
17714
18162
|
}
|
|
17715
18163
|
function isOtherWorktreeListenerCwd(cwd, worktreePath) {
|
|
17716
18164
|
const wt = realpathOrResolve(worktreePath);
|
|
17717
18165
|
const c = realpathOrResolve(cwd);
|
|
17718
|
-
if (c === wt || c.startsWith(wt +
|
|
17719
|
-
const parent = (0,
|
|
17720
|
-
const thisName = (0,
|
|
18166
|
+
if (c === wt || c.startsWith(wt + import_node_path46.sep)) return false;
|
|
18167
|
+
const parent = (0, import_node_path46.dirname)(wt);
|
|
18168
|
+
const thisName = (0, import_node_path46.basename)(wt);
|
|
17721
18169
|
if (!thisName || c === parent) return false;
|
|
17722
|
-
if (!c.startsWith(parent +
|
|
17723
|
-
const first = c.slice(parent.length + 1).split(
|
|
18170
|
+
if (!c.startsWith(parent + import_node_path46.sep)) return false;
|
|
18171
|
+
const first = c.slice(parent.length + 1).split(import_node_path46.sep)[0];
|
|
17724
18172
|
return Boolean(first) && first !== thisName;
|
|
17725
18173
|
}
|
|
17726
18174
|
function signalPid(pid, signal) {
|
|
@@ -17732,12 +18180,16 @@ function signalPid(pid, signal) {
|
|
|
17732
18180
|
function isThisWorktreeListenerCwd(cwd, worktreePath) {
|
|
17733
18181
|
const wt = realpathOrResolve(worktreePath);
|
|
17734
18182
|
const c = realpathOrResolve(cwd);
|
|
17735
|
-
return c === wt || c.startsWith(wt +
|
|
18183
|
+
return c === wt || c.startsWith(wt + import_node_path46.sep);
|
|
17736
18184
|
}
|
|
17737
18185
|
function killDetachedRunTree(pid, worktreePath) {
|
|
17738
18186
|
if (!Number.isFinite(pid) || pid <= 1 || pid === process.pid) return;
|
|
17739
18187
|
const cwd = listenerCwd(pid);
|
|
17740
|
-
if (
|
|
18188
|
+
if (cwd) {
|
|
18189
|
+
if (!isThisWorktreeListenerCwd(cwd, worktreePath)) return;
|
|
18190
|
+
} else if (!processGroupAlive(pid)) {
|
|
18191
|
+
return;
|
|
18192
|
+
}
|
|
17741
18193
|
try {
|
|
17742
18194
|
process.kill(-pid, "SIGTERM");
|
|
17743
18195
|
} catch {
|
|
@@ -18134,14 +18586,14 @@ async function startDevServer(repoPath, worktreePath, onLine, opts) {
|
|
|
18134
18586
|
throw err;
|
|
18135
18587
|
}
|
|
18136
18588
|
}
|
|
18137
|
-
var
|
|
18589
|
+
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
18590
|
var init_conductor = __esm({
|
|
18139
18591
|
"src/hook/conductor.ts"() {
|
|
18140
18592
|
"use strict";
|
|
18141
|
-
|
|
18593
|
+
import_node_fs46 = require("fs");
|
|
18142
18594
|
import_node_child_process5 = require("child_process");
|
|
18143
18595
|
import_node_net = require("net");
|
|
18144
|
-
|
|
18596
|
+
import_node_path46 = require("path");
|
|
18145
18597
|
import_execa4 = require("execa");
|
|
18146
18598
|
import_node_readline3 = require("readline");
|
|
18147
18599
|
init_settings();
|
|
@@ -18153,6 +18605,7 @@ var init_conductor = __esm({
|
|
|
18153
18605
|
init_request_review();
|
|
18154
18606
|
init_worktree_exclude();
|
|
18155
18607
|
init_worktree_run_ports();
|
|
18608
|
+
init_wait_for_job();
|
|
18156
18609
|
init_worktree_run_ports();
|
|
18157
18610
|
init_nested_electron_env();
|
|
18158
18611
|
PORT_RANGE_SIZE = 10;
|
|
@@ -18173,9 +18626,9 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
18173
18626
|
repoPaths?.length ? repoPaths : threads.map((t) => t.repoPath).filter(Boolean)
|
|
18174
18627
|
);
|
|
18175
18628
|
const homeRoot = sideboardWorkspacesDir();
|
|
18176
|
-
if ((0,
|
|
18629
|
+
if ((0, import_node_fs47.existsSync)(homeRoot)) {
|
|
18177
18630
|
try {
|
|
18178
|
-
for (const entry of (0,
|
|
18631
|
+
for (const entry of (0, import_node_fs47.readdirSync)(homeRoot, { withFileTypes: true })) {
|
|
18179
18632
|
if (!entry.isDirectory()) continue;
|
|
18180
18633
|
void entry;
|
|
18181
18634
|
}
|
|
@@ -18185,7 +18638,7 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
18185
18638
|
const orphans = [];
|
|
18186
18639
|
const seen = /* @__PURE__ */ new Set();
|
|
18187
18640
|
for (const repoPath of repos) {
|
|
18188
|
-
if (!repoPath || !(0,
|
|
18641
|
+
if (!repoPath || !(0, import_node_fs47.existsSync)(repoPath)) continue;
|
|
18189
18642
|
try {
|
|
18190
18643
|
const wts = await listWorktrees(repoPath);
|
|
18191
18644
|
for (const wt of wts) {
|
|
@@ -18196,7 +18649,7 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
18196
18649
|
seen.add(path2);
|
|
18197
18650
|
let mtimeMs = 0;
|
|
18198
18651
|
try {
|
|
18199
|
-
mtimeMs = (0,
|
|
18652
|
+
mtimeMs = (0, import_node_fs47.statSync)(path2).mtimeMs;
|
|
18200
18653
|
} catch {
|
|
18201
18654
|
mtimeMs = 0;
|
|
18202
18655
|
}
|
|
@@ -18206,16 +18659,16 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
18206
18659
|
}
|
|
18207
18660
|
try {
|
|
18208
18661
|
const root = worktreesRoot(repoPath);
|
|
18209
|
-
if ((0,
|
|
18210
|
-
for (const entry of (0,
|
|
18662
|
+
if ((0, import_node_fs47.existsSync)(root)) {
|
|
18663
|
+
for (const entry of (0, import_node_fs47.readdirSync)(root, { withFileTypes: true })) {
|
|
18211
18664
|
if (!entry.isDirectory()) continue;
|
|
18212
|
-
const path2 = (0,
|
|
18665
|
+
const path2 = (0, import_node_path47.join)(root, entry.name).replace(/\/$/, "");
|
|
18213
18666
|
if (known.has(path2) || seen.has(path2)) continue;
|
|
18214
|
-
if (!(0,
|
|
18667
|
+
if (!(0, import_node_fs47.existsSync)((0, import_node_path47.join)(path2, ".git"))) continue;
|
|
18215
18668
|
seen.add(path2);
|
|
18216
18669
|
let mtimeMs = 0;
|
|
18217
18670
|
try {
|
|
18218
|
-
mtimeMs = (0,
|
|
18671
|
+
mtimeMs = (0, import_node_fs47.statSync)(path2).mtimeMs;
|
|
18219
18672
|
} catch {
|
|
18220
18673
|
mtimeMs = Date.now();
|
|
18221
18674
|
}
|
|
@@ -18276,12 +18729,12 @@ function worktreeCleanupSettings() {
|
|
|
18276
18729
|
autoCleanupOrphans: a.autoCleanupOrphans
|
|
18277
18730
|
};
|
|
18278
18731
|
}
|
|
18279
|
-
var
|
|
18732
|
+
var import_node_fs47, import_node_path47;
|
|
18280
18733
|
var init_orphan_cleanup = __esm({
|
|
18281
18734
|
"src/git/orphan-cleanup.ts"() {
|
|
18282
18735
|
"use strict";
|
|
18283
|
-
|
|
18284
|
-
|
|
18736
|
+
import_node_fs47 = require("fs");
|
|
18737
|
+
import_node_path47 = require("path");
|
|
18285
18738
|
init_worktree();
|
|
18286
18739
|
init_thread_store();
|
|
18287
18740
|
init_paths();
|
|
@@ -18297,6 +18750,7 @@ function stripArchivedTranscript(thread) {
|
|
|
18297
18750
|
attachments: [],
|
|
18298
18751
|
pendingTurnAttachments: [],
|
|
18299
18752
|
queueAttachments: [],
|
|
18753
|
+
compactedMessages: void 0,
|
|
18300
18754
|
messages: [historyRetentionStubMessage(thread)]
|
|
18301
18755
|
},
|
|
18302
18756
|
{ touch: false }
|
|
@@ -18456,12 +18910,12 @@ async function cloneRepoIntoSideboard(opts) {
|
|
|
18456
18910
|
if (!url) throw new Error("Clone URL is required");
|
|
18457
18911
|
let name = opts.name?.trim();
|
|
18458
18912
|
if (!name) {
|
|
18459
|
-
const leaf = (0,
|
|
18913
|
+
const leaf = (0, import_node_path48.basename)(url.replace(/\/$/, "").replace(/\.git$/, ""));
|
|
18460
18914
|
name = leaf || "repo";
|
|
18461
18915
|
}
|
|
18462
18916
|
name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
|
|
18463
|
-
const dest = (0,
|
|
18464
|
-
if ((0,
|
|
18917
|
+
const dest = (0, import_node_path48.join)(sideboardReposDir(), name);
|
|
18918
|
+
if ((0, import_node_fs48.existsSync)(dest)) {
|
|
18465
18919
|
const repoPath2 = await resolveRepoRoot(dest);
|
|
18466
18920
|
const workspace2 = await ensureWorkspace(repoPath2);
|
|
18467
18921
|
return { repoPath: repoPath2, workspace: workspace2 };
|
|
@@ -18476,12 +18930,12 @@ async function cloneRepoIntoSideboard(opts) {
|
|
|
18476
18930
|
const workspace = await ensureWorkspace(repoPath);
|
|
18477
18931
|
return { repoPath, workspace };
|
|
18478
18932
|
}
|
|
18479
|
-
var
|
|
18933
|
+
var import_node_fs48, import_node_path48, import_execa6;
|
|
18480
18934
|
var init_clone_repo = __esm({
|
|
18481
18935
|
"src/git/clone-repo.ts"() {
|
|
18482
18936
|
"use strict";
|
|
18483
|
-
|
|
18484
|
-
|
|
18937
|
+
import_node_fs48 = require("fs");
|
|
18938
|
+
import_node_path48 = require("path");
|
|
18485
18939
|
import_execa6 = require("execa");
|
|
18486
18940
|
init_paths();
|
|
18487
18941
|
init_workspaces();
|
|
@@ -18539,394 +18993,6 @@ var init_child_halt = __esm({
|
|
|
18539
18993
|
}
|
|
18540
18994
|
});
|
|
18541
18995
|
|
|
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
18996
|
// src/detect/detect.ts
|
|
18931
18997
|
async function detectAgents() {
|
|
18932
18998
|
ensureAgentPath();
|
|
@@ -23230,7 +23296,7 @@ function formatWorktreeDirective(thread, opts) {
|
|
|
23230
23296
|
const slug = opts?.githubSlug ?? "<origin-owner/name>";
|
|
23231
23297
|
const resolve2 = opts?.githubSlug ? "" : " Resolve `<origin-owner/name>` with `git remote get-url origin` in this worktree.";
|
|
23232
23298
|
lines.push(
|
|
23233
|
-
`- Prefer a draft PR first: \`gh pr create --draft -R ${slug}\` (update via \`gh pr edit -R ${slug}\`). Always pass \`-R\` \u2014 bare \`gh pr create\` may target upstream on dual-remote checkouts.${resolve2} Mark ready for review only when asked. If GitHub rejects the body as too long, retry with a short \`--body-file\` \u2014 do not paste a changelog or diff into \`--body\`.`
|
|
23299
|
+
`- Prefer a draft PR first: \`gh pr create --draft --assignee @me -R ${slug}\` (update via \`gh pr edit -R ${slug}\`). Always pass \`-R\` \u2014 bare \`gh pr create\` may target upstream on dual-remote checkouts.${resolve2} Assign to the GitHub-connected user (\`--assignee @me\`) unless the user named someone else. Mark ready for review only when asked. If GitHub rejects the body as too long, retry with a short \`--body-file\` \u2014 do not paste a changelog or diff into \`--body\`.`
|
|
23234
23300
|
);
|
|
23235
23301
|
}
|
|
23236
23302
|
lines.push(
|
|
@@ -23709,6 +23775,13 @@ function isPresentPlanToolName(name) {
|
|
|
23709
23775
|
if (!name) return false;
|
|
23710
23776
|
return /present_plan$/i.test(name) || /^mcp__sideboard__present_plan$/i.test(name);
|
|
23711
23777
|
}
|
|
23778
|
+
function threadMayPresentPlan(thread) {
|
|
23779
|
+
return Boolean(thread?.planMode);
|
|
23780
|
+
}
|
|
23781
|
+
function shouldRegisterPresentPlan(thread) {
|
|
23782
|
+
if (thread == null) return true;
|
|
23783
|
+
return threadMayPresentPlan(thread);
|
|
23784
|
+
}
|
|
23712
23785
|
function extractPresentedPlan(parts) {
|
|
23713
23786
|
if (!parts?.length) return null;
|
|
23714
23787
|
for (let i = parts.length - 1; i >= 0; i--) {
|
|
@@ -23746,7 +23819,7 @@ function resolvePlanMarkdown(opts) {
|
|
|
23746
23819
|
}
|
|
23747
23820
|
return null;
|
|
23748
23821
|
}
|
|
23749
|
-
var PLAN_FILE_REL, PLAN_FILE_NAME, LEGACY_PLAN_FILE_REL;
|
|
23822
|
+
var PLAN_FILE_REL, PLAN_FILE_NAME, LEGACY_PLAN_FILE_REL, PRESENT_PLAN_TOOL_DESCRIPTION, PRESENT_PLAN_REQUIRES_PLAN_MODE;
|
|
23750
23823
|
var init_plan_present = __esm({
|
|
23751
23824
|
"src/plan/plan-present.ts"() {
|
|
23752
23825
|
"use strict";
|
|
@@ -23754,6 +23827,8 @@ var init_plan_present = __esm({
|
|
|
23754
23827
|
PLAN_FILE_REL = `${ATTACHMENTS_DIR}/plan.md`;
|
|
23755
23828
|
PLAN_FILE_NAME = "plan.md";
|
|
23756
23829
|
LEGACY_PLAN_FILE_REL = ".sideboard/plan.md";
|
|
23830
|
+
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.";
|
|
23831
|
+
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
23832
|
}
|
|
23758
23833
|
});
|
|
23759
23834
|
|
|
@@ -23763,11 +23838,15 @@ __export(plan_file_exports, {
|
|
|
23763
23838
|
LEGACY_PLAN_FILE_REL: () => LEGACY_PLAN_FILE_REL,
|
|
23764
23839
|
PLAN_FILE_NAME: () => PLAN_FILE_NAME,
|
|
23765
23840
|
PLAN_FILE_REL: () => PLAN_FILE_REL,
|
|
23841
|
+
PRESENT_PLAN_REQUIRES_PLAN_MODE: () => PRESENT_PLAN_REQUIRES_PLAN_MODE,
|
|
23842
|
+
PRESENT_PLAN_TOOL_DESCRIPTION: () => PRESENT_PLAN_TOOL_DESCRIPTION,
|
|
23766
23843
|
extractPresentedPlan: () => extractPresentedPlan,
|
|
23767
23844
|
isPresentPlanToolName: () => isPresentPlanToolName,
|
|
23768
23845
|
planFileAbs: () => planFileAbs,
|
|
23769
23846
|
readPlanFile: () => readPlanFile,
|
|
23770
23847
|
resolvePlanMarkdown: () => resolvePlanMarkdown,
|
|
23848
|
+
shouldRegisterPresentPlan: () => shouldRegisterPresentPlan,
|
|
23849
|
+
threadMayPresentPlan: () => threadMayPresentPlan,
|
|
23771
23850
|
writePlanFile: () => writePlanFile
|
|
23772
23851
|
});
|
|
23773
23852
|
function ensureAttachmentsGitignore(worktreePath) {
|
|
@@ -24903,7 +24982,8 @@ var init_orchestrator = __esm({
|
|
|
24903
24982
|
if (compact.didCompact) {
|
|
24904
24983
|
thread = updateThread(threadId, {
|
|
24905
24984
|
messages: compact.thread.messages,
|
|
24906
|
-
sessionId: compact.thread.sessionId
|
|
24985
|
+
sessionId: compact.thread.sessionId,
|
|
24986
|
+
compactedMessages: compact.thread.compactedMessages
|
|
24907
24987
|
});
|
|
24908
24988
|
this.emit({
|
|
24909
24989
|
type: "context_compacted",
|
|
@@ -24917,9 +24997,6 @@ var init_orchestrator = __esm({
|
|
|
24917
24997
|
this.emit({ type: "error", threadId, message: `context compact failed: ${message}` });
|
|
24918
24998
|
thread = this.requireThread(threadId);
|
|
24919
24999
|
}
|
|
24920
|
-
if (this.stoppedTurns.has(threadId)) {
|
|
24921
|
-
return;
|
|
24922
|
-
}
|
|
24923
25000
|
const promptText = typeof prompt === "string" ? prompt : "";
|
|
24924
25001
|
const autoContinue = isJobContinuePrompt(promptText) || promptText.startsWith("The previous agent process ended before it finished.");
|
|
24925
25002
|
const parked = takePendingTurnAttachments(thread);
|
|
@@ -24936,6 +25013,14 @@ var init_orchestrator = __esm({
|
|
|
24936
25013
|
agent: thread.agent,
|
|
24937
25014
|
orch: isOrchestratorThread(thread)
|
|
24938
25015
|
});
|
|
25016
|
+
if (this.stoppedTurns.has(threadId)) {
|
|
25017
|
+
const stopped = writeLiveStatus(threadId, "stopped");
|
|
25018
|
+
if (stopped?.status === "stopped") {
|
|
25019
|
+
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
25020
|
+
}
|
|
25021
|
+
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
25022
|
+
return;
|
|
25023
|
+
}
|
|
24939
25024
|
thread = this.requireThread(threadId);
|
|
24940
25025
|
const gitPrompt = expandCanonicalGitRequest(promptText);
|
|
24941
25026
|
const { agentPrompt: expandedPrompt } = expandComposerPrompt(
|
|
@@ -24994,7 +25079,7 @@ var init_orchestrator = __esm({
|
|
|
24994
25079
|
].filter(Boolean).join("\n\n") || void 0;
|
|
24995
25080
|
const prGateDirective = thread.agent !== "brightsy" && !isOrchestratorThread(thread) && mentionsPrGoal(promptText) ? formatPrGateDirective() : null;
|
|
24996
25081
|
const agentPrompt = [
|
|
24997
|
-
thread.planMode
|
|
25082
|
+
planModeTurnInstruction(thread.planMode),
|
|
24998
25083
|
orchestrationReminder,
|
|
24999
25084
|
prGateDirective,
|
|
25000
25085
|
injectedNoticeContext,
|
|
@@ -26928,8 +27013,11 @@ __export(index_exports, {
|
|
|
26928
27013
|
PLAN_FILE_NAME: () => PLAN_FILE_NAME,
|
|
26929
27014
|
PLAN_FILE_REL: () => PLAN_FILE_REL,
|
|
26930
27015
|
PLAN_MODE_INSTRUCTION: () => PLAN_MODE_INSTRUCTION,
|
|
27016
|
+
PLAN_MODE_OFF_INSTRUCTION: () => PLAN_MODE_OFF_INSTRUCTION,
|
|
26931
27017
|
PLAN_QUESTION_ANSWERS_PREFIX: () => PLAN_QUESTION_ANSWERS_PREFIX,
|
|
26932
27018
|
PORT_RANGE_SIZE: () => PORT_RANGE_SIZE,
|
|
27019
|
+
PRESENT_PLAN_REQUIRES_PLAN_MODE: () => PRESENT_PLAN_REQUIRES_PLAN_MODE,
|
|
27020
|
+
PRESENT_PLAN_TOOL_DESCRIPTION: () => PRESENT_PLAN_TOOL_DESCRIPTION,
|
|
26933
27021
|
PROFILE_NOTES_MAX: () => PROFILE_NOTES_MAX,
|
|
26934
27022
|
PR_DETAILS_JSON_FIELDS: () => PR_DETAILS_JSON_FIELDS,
|
|
26935
27023
|
REPO_REVIEW_NAME: () => REPO_REVIEW_NAME,
|
|
@@ -27000,6 +27088,7 @@ __export(index_exports, {
|
|
|
27000
27088
|
annotateDevAccessRuns: () => annotateDevAccessRuns,
|
|
27001
27089
|
appDataDir: () => appDataDir,
|
|
27002
27090
|
appSettingsPath: () => appSettingsPath,
|
|
27091
|
+
appendCompactedMessages: () => appendCompactedMessages,
|
|
27003
27092
|
appendIndexedGitConfig: () => appendIndexedGitConfig,
|
|
27004
27093
|
appendMessage: () => appendMessage,
|
|
27005
27094
|
appendQueuedItem: () => appendQueuedItem,
|
|
@@ -27087,6 +27176,9 @@ __export(index_exports, {
|
|
|
27087
27176
|
commentGitHubIssue: () => commentGitHubIssue,
|
|
27088
27177
|
commentLinearIssue: () => commentLinearIssue,
|
|
27089
27178
|
commitAll: () => commitAll,
|
|
27179
|
+
compactArchiveProse: () => compactArchiveProse,
|
|
27180
|
+
compactPartsForSummaryMessage: () => compactPartsForSummaryMessage,
|
|
27181
|
+
compactSummaryPart: () => compactSummaryPart,
|
|
27090
27182
|
computeNextRunAt: () => computeNextRunAt,
|
|
27091
27183
|
conductorBundledBinDir: () => conductorBundledBinDir,
|
|
27092
27184
|
conductorDbPath: () => conductorDbPath,
|
|
@@ -27294,6 +27386,7 @@ __export(index_exports, {
|
|
|
27294
27386
|
getWorktreeDirtyStat: () => getWorktreeDirtyStat,
|
|
27295
27387
|
gh: () => gh,
|
|
27296
27388
|
ghHeadRef: () => ghHeadRef,
|
|
27389
|
+
ghPrCreateAssigneeArgs: () => ghPrCreateAssigneeArgs,
|
|
27297
27390
|
ghRepoSelectArgs: () => ghRepoSelectArgs,
|
|
27298
27391
|
git: () => git,
|
|
27299
27392
|
gitBranchPrefixSetting: () => gitBranchPrefixSetting,
|
|
@@ -27486,6 +27579,7 @@ __export(index_exports, {
|
|
|
27486
27579
|
mergeSideboardIntoMcpServersJson: () => mergeSideboardIntoMcpServersJson,
|
|
27487
27580
|
mergeUsage: () => mergeUsage,
|
|
27488
27581
|
messagePartParentId: () => messagePartParentId,
|
|
27582
|
+
messagesForCompactArchive: () => messagesForCompactArchive,
|
|
27489
27583
|
messagesSinceLastBrightsyContextSummary: () => messagesSinceLastBrightsyContextSummary,
|
|
27490
27584
|
moveQueuedItemToFront: () => moveQueuedItemToFront,
|
|
27491
27585
|
needsCoordinatorAction: () => needsCoordinatorAction,
|
|
@@ -27545,6 +27639,7 @@ __export(index_exports, {
|
|
|
27545
27639
|
planFileAbs: () => planFileAbs,
|
|
27546
27640
|
planHistoryAgePurge: () => planHistoryAgePurge,
|
|
27547
27641
|
planHistoryRetention: () => planHistoryRetention,
|
|
27642
|
+
planModeTurnInstruction: () => planModeTurnInstruction,
|
|
27548
27643
|
planQuestionsSignature: () => planQuestionsSignature,
|
|
27549
27644
|
pnpmFilterPackage: () => pnpmFilterPackage,
|
|
27550
27645
|
pollSlackOutboundWatches: () => pollSlackOutboundWatches,
|
|
@@ -27672,6 +27767,7 @@ __export(index_exports, {
|
|
|
27672
27767
|
shouldInjectBrightsyMcp: () => shouldInjectBrightsyMcp,
|
|
27673
27768
|
shouldNotifyOrchestrator: () => shouldNotifyOrchestrator,
|
|
27674
27769
|
shouldRefreshReviewRequestTemplate: () => shouldRefreshReviewRequestTemplate,
|
|
27770
|
+
shouldRegisterPresentPlan: () => shouldRegisterPresentPlan,
|
|
27675
27771
|
shouldRemoveWorktreeOnTeardown: () => shouldRemoveWorktreeOnTeardown,
|
|
27676
27772
|
shouldResetSessionForOccupancy: () => shouldResetSessionForOccupancy,
|
|
27677
27773
|
shouldRunHistoryCleanup: () => shouldRunHistoryCleanup,
|
|
@@ -27733,6 +27829,7 @@ __export(index_exports, {
|
|
|
27733
27829
|
threadHasCompactedContext: () => threadHasCompactedContext,
|
|
27734
27830
|
threadLivePath: () => threadLivePath,
|
|
27735
27831
|
threadLockPath: () => threadLockPath,
|
|
27832
|
+
threadMayPresentPlan: () => threadMayPresentPlan,
|
|
27736
27833
|
threadRequestsBrightsyMcp: () => threadRequestsBrightsyMcp,
|
|
27737
27834
|
threadsDir: () => threadsDir,
|
|
27738
27835
|
threadsSharingWorktree: () => threadsSharingWorktree,
|
|
@@ -27906,7 +28003,8 @@ function slimThreadForUiList(thread) {
|
|
|
27906
28003
|
attachments: [],
|
|
27907
28004
|
pendingTurnAttachments: [],
|
|
27908
28005
|
queueAttachments: [],
|
|
27909
|
-
messages: slimMessagesForUiList(messages, thread.updatedAt)
|
|
28006
|
+
messages: slimMessagesForUiList(messages, thread.updatedAt),
|
|
28007
|
+
compactedMessages: void 0
|
|
27910
28008
|
};
|
|
27911
28009
|
}
|
|
27912
28010
|
function slimMessagesForUiList(messages, fallbackTs) {
|
|
@@ -30460,6 +30558,7 @@ function mcpArchiveBlockedReason(thread) {
|
|
|
30460
30558
|
|
|
30461
30559
|
// src/mcp/server.ts
|
|
30462
30560
|
init_profile();
|
|
30561
|
+
init_plan_present();
|
|
30463
30562
|
|
|
30464
30563
|
// src/mcp/run-script-ref.ts
|
|
30465
30564
|
init_chat_tabs();
|
|
@@ -33372,31 +33471,40 @@ async function startMcpServer() {
|
|
|
33372
33471
|
return mcpJson(payload);
|
|
33373
33472
|
}
|
|
33374
33473
|
);
|
|
33375
|
-
|
|
33376
|
-
|
|
33377
|
-
|
|
33378
|
-
|
|
33379
|
-
|
|
33380
|
-
|
|
33381
|
-
|
|
33382
|
-
|
|
33383
|
-
|
|
33384
|
-
|
|
33385
|
-
|
|
33386
|
-
|
|
33387
|
-
const
|
|
33474
|
+
const callingThreadId = process.env[SIDEBOARD_THREAD_ID_ENV]?.trim() || "";
|
|
33475
|
+
const callingThread = callingThreadId ? orch.getThread(callingThreadId) : null;
|
|
33476
|
+
if (shouldRegisterPresentPlan(callingThread)) {
|
|
33477
|
+
server.tool(
|
|
33478
|
+
"present_plan",
|
|
33479
|
+
PRESENT_PLAN_TOOL_DESCRIPTION,
|
|
33480
|
+
{
|
|
33481
|
+
title: import_zod9.z.string().optional().describe("Short plan title (defaults to Plan)"),
|
|
33482
|
+
content: import_zod9.z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
|
|
33483
|
+
thread_id: import_zod9.z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
|
|
33484
|
+
},
|
|
33485
|
+
async ({ title, content, thread_id }) => {
|
|
33486
|
+
const { writePlanFile: writePlanFile2 } = await Promise.resolve().then(() => (init_plan_file(), plan_file_exports));
|
|
33487
|
+
const ref = thread_id?.trim() || callingThreadId;
|
|
33488
|
+
const t = ref ? orch.getThread(ref) : null;
|
|
33489
|
+
if (t && !threadMayPresentPlan(t)) {
|
|
33490
|
+
return mcpJson(
|
|
33491
|
+
{ ok: false, error: PRESENT_PLAN_REQUIRES_PLAN_MODE },
|
|
33492
|
+
true
|
|
33493
|
+
);
|
|
33494
|
+
}
|
|
33495
|
+
let root = process.cwd();
|
|
33388
33496
|
if (t?.worktreePath?.trim()) root = t.worktreePath;
|
|
33497
|
+
const path2 = writePlanFile2(root, content);
|
|
33498
|
+
const payload = {
|
|
33499
|
+
ok: true,
|
|
33500
|
+
path: path2,
|
|
33501
|
+
title: title?.trim() || "Plan",
|
|
33502
|
+
message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
|
|
33503
|
+
};
|
|
33504
|
+
return mcpJson(payload);
|
|
33389
33505
|
}
|
|
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
|
-
);
|
|
33506
|
+
);
|
|
33507
|
+
}
|
|
33400
33508
|
server.tool(
|
|
33401
33509
|
"present_schema",
|
|
33402
33510
|
"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.",
|
|
@@ -34004,7 +34112,7 @@ async function startMcpServer() {
|
|
|
34004
34112
|
error: true,
|
|
34005
34113
|
action,
|
|
34006
34114
|
lastError: message,
|
|
34007
|
-
hint: /body is too long/i.test(message) ? "Branch is already pushed. send_to_thread so the worktree agent runs gh pr create --draft -R <origin> --body-file <short.md>. Keep the description short." : void 0
|
|
34115
|
+
hint: /body is too long/i.test(message) ? "Branch is already pushed. send_to_thread so the worktree agent runs gh pr create --draft --assignee @me -R <origin> --body-file <short.md>. Keep the description short." : void 0
|
|
34008
34116
|
},
|
|
34009
34117
|
true
|
|
34010
34118
|
);
|
|
@@ -36733,8 +36841,11 @@ async function startSlackRelayServer(opts) {
|
|
|
36733
36841
|
PLAN_FILE_NAME,
|
|
36734
36842
|
PLAN_FILE_REL,
|
|
36735
36843
|
PLAN_MODE_INSTRUCTION,
|
|
36844
|
+
PLAN_MODE_OFF_INSTRUCTION,
|
|
36736
36845
|
PLAN_QUESTION_ANSWERS_PREFIX,
|
|
36737
36846
|
PORT_RANGE_SIZE,
|
|
36847
|
+
PRESENT_PLAN_REQUIRES_PLAN_MODE,
|
|
36848
|
+
PRESENT_PLAN_TOOL_DESCRIPTION,
|
|
36738
36849
|
PROFILE_NOTES_MAX,
|
|
36739
36850
|
PR_DETAILS_JSON_FIELDS,
|
|
36740
36851
|
REPO_REVIEW_NAME,
|
|
@@ -36805,6 +36916,7 @@ async function startSlackRelayServer(opts) {
|
|
|
36805
36916
|
annotateDevAccessRuns,
|
|
36806
36917
|
appDataDir,
|
|
36807
36918
|
appSettingsPath,
|
|
36919
|
+
appendCompactedMessages,
|
|
36808
36920
|
appendIndexedGitConfig,
|
|
36809
36921
|
appendMessage,
|
|
36810
36922
|
appendQueuedItem,
|
|
@@ -36892,6 +37004,9 @@ async function startSlackRelayServer(opts) {
|
|
|
36892
37004
|
commentGitHubIssue,
|
|
36893
37005
|
commentLinearIssue,
|
|
36894
37006
|
commitAll,
|
|
37007
|
+
compactArchiveProse,
|
|
37008
|
+
compactPartsForSummaryMessage,
|
|
37009
|
+
compactSummaryPart,
|
|
36895
37010
|
computeNextRunAt,
|
|
36896
37011
|
conductorBundledBinDir,
|
|
36897
37012
|
conductorDbPath,
|
|
@@ -37099,6 +37214,7 @@ async function startSlackRelayServer(opts) {
|
|
|
37099
37214
|
getWorktreeDirtyStat,
|
|
37100
37215
|
gh,
|
|
37101
37216
|
ghHeadRef,
|
|
37217
|
+
ghPrCreateAssigneeArgs,
|
|
37102
37218
|
ghRepoSelectArgs,
|
|
37103
37219
|
git,
|
|
37104
37220
|
gitBranchPrefixSetting,
|
|
@@ -37291,6 +37407,7 @@ async function startSlackRelayServer(opts) {
|
|
|
37291
37407
|
mergeSideboardIntoMcpServersJson,
|
|
37292
37408
|
mergeUsage,
|
|
37293
37409
|
messagePartParentId,
|
|
37410
|
+
messagesForCompactArchive,
|
|
37294
37411
|
messagesSinceLastBrightsyContextSummary,
|
|
37295
37412
|
moveQueuedItemToFront,
|
|
37296
37413
|
needsCoordinatorAction,
|
|
@@ -37350,6 +37467,7 @@ async function startSlackRelayServer(opts) {
|
|
|
37350
37467
|
planFileAbs,
|
|
37351
37468
|
planHistoryAgePurge,
|
|
37352
37469
|
planHistoryRetention,
|
|
37470
|
+
planModeTurnInstruction,
|
|
37353
37471
|
planQuestionsSignature,
|
|
37354
37472
|
pnpmFilterPackage,
|
|
37355
37473
|
pollSlackOutboundWatches,
|
|
@@ -37477,6 +37595,7 @@ async function startSlackRelayServer(opts) {
|
|
|
37477
37595
|
shouldInjectBrightsyMcp,
|
|
37478
37596
|
shouldNotifyOrchestrator,
|
|
37479
37597
|
shouldRefreshReviewRequestTemplate,
|
|
37598
|
+
shouldRegisterPresentPlan,
|
|
37480
37599
|
shouldRemoveWorktreeOnTeardown,
|
|
37481
37600
|
shouldResetSessionForOccupancy,
|
|
37482
37601
|
shouldRunHistoryCleanup,
|
|
@@ -37538,6 +37657,7 @@ async function startSlackRelayServer(opts) {
|
|
|
37538
37657
|
threadHasCompactedContext,
|
|
37539
37658
|
threadLivePath,
|
|
37540
37659
|
threadLockPath,
|
|
37660
|
+
threadMayPresentPlan,
|
|
37541
37661
|
threadRequestsBrightsyMcp,
|
|
37542
37662
|
threadsDir,
|
|
37543
37663
|
threadsSharingWorktree,
|