@sideboard-ai/core 0.1.100 → 0.1.103
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/agents/cursor-runner.js +1 -1
- package/dist/{agents-CLP5BG4O.js → agents-UT5GCJH3.js} +8 -8
- package/dist/{agents-OXZDMPSE.js → agents-XZBAKBO2.js} +8 -8
- package/dist/{app-settings-BRF7VKTQ.js → app-settings-EKJM2344.js} +2 -2
- package/dist/{app-settings-ZXPAB3QN.js → app-settings-VDJK2BZ2.js} +2 -2
- package/dist/{caffeinate-hold-U3QJBV67.js → caffeinate-hold-NBRUWYXO.js} +2 -2
- package/dist/{caffeinate-hold-I7SG6UQR.js → caffeinate-hold-X3JNPR42.js} +2 -2
- package/dist/{chunk-MHJV4WS3.js → chunk-27MCESES.js} +3 -3
- package/dist/{chunk-QSLE4VEM.js → chunk-2GO3YKBA.js} +4 -4
- package/dist/{chunk-DKHGWYWR.js → chunk-7YKXHBIW.js} +1 -1
- package/dist/{chunk-JL4SJ6V7.js → chunk-BD2ICC4D.js} +4 -0
- package/dist/{chunk-NZPGMNFC.js → chunk-DCOZABNT.js} +1 -1
- package/dist/{chunk-CIRXAYWS.js → chunk-DHAGMJIK.js} +55 -28
- package/dist/{chunk-HYKZEP5A.js → chunk-ETMRBJKA.js} +11 -11
- package/dist/{chunk-OXC3MEFI.js → chunk-H6LP57EF.js} +6 -6
- package/dist/{chunk-GR4JKWR4.js → chunk-HHLZWO7U.js} +5 -5
- package/dist/{chunk-3WA37BRI.js → chunk-KGSVLZV6.js} +11 -4
- package/dist/{chunk-7KDBDMNC.js → chunk-KLBOWJ74.js} +1 -1
- package/dist/{chunk-EQPQTLW6.js → chunk-LANDJIAI.js} +6 -6
- package/dist/{chunk-MRBKGFTL.js → chunk-ND6AO7C5.js} +3 -3
- package/dist/{chunk-WQTTUC4N.js → chunk-OW34MVDN.js} +5 -5
- package/dist/{chunk-JOF3XIEM.js → chunk-P3BQ5DOL.js} +11 -4
- package/dist/{chunk-B4R2VB2A.js → chunk-QZQEXME2.js} +4 -0
- package/dist/{chunk-R7BQBSDT.js → chunk-WA3DVVWG.js} +55 -28
- package/dist/{chunk-6LFV4VFI.js → chunk-WSYCND2U.js} +1 -1
- package/dist/{chunk-FT2SQOL4.js → chunk-XHZJJWLI.js} +1 -1
- package/dist/{chunk-IFHKPZHA.js → chunk-YJADJH5U.js} +11 -11
- package/dist/{connected-teams-SWHJWK2A.js → connected-teams-J4I5AKMR.js} +2 -2
- package/dist/{connected-teams-TLVKZPJP.js → connected-teams-M5XNXRI5.js} +2 -2
- package/dist/{coordinator-prompt-OOBSQCWQ.js → coordinator-prompt-3DFZRMXE.js} +5 -5
- package/dist/{coordinator-prompt-HHRKQWCX.js → coordinator-prompt-UWHSD65V.js} +5 -5
- package/dist/{cursor-recover-S5ORPW34.js → cursor-recover-JBTIDARH.js} +1 -1
- package/dist/{cursor-recover-G34BYKL2.js → cursor-recover-ZUJG6XRQ.js} +1 -1
- package/dist/{global-workspace-YZWYHLQY.js → global-workspace-KJQS5S2C.js} +6 -6
- package/dist/{global-workspace-KYEBFWKB.js → global-workspace-UR4E3CCG.js} +6 -6
- package/dist/index.cjs +626 -153
- package/dist/index.d.cts +37 -5
- package/dist/index.d.ts +37 -5
- package/dist/index.js +545 -121
- package/dist/mcp/run-stdio.cjs +426 -177
- package/dist/mcp/run-stdio.js +330 -127
- package/dist/{paths-TZX4L5X7.js → paths-QCI3MTO5.js} +3 -1
- package/dist/{paths-JQM2XRAD.js → paths-UMEDHOS5.js} +3 -1
- package/dist/{thread-store-XAKWCV2E.js → thread-store-4OUEQ2DB.js} +2 -2
- package/dist/{thread-store-V7NOOXKE.js → thread-store-DQJGDKIJ.js} +2 -2
- package/dist/{workspaces-ENVUDK6C.js → workspaces-NTIVI6DI.js} +7 -7
- package/dist/{workspaces-NNPD7QVV.js → workspaces-TKJXPNV4.js} +7 -7
- package/dist/{worktree-EO5QAGJU.js → worktree-NKWN5ZTS.js} +4 -4
- package/dist/{worktree-DA4BOV7G.js → worktree-PEUPFAKL.js} +4 -4
- package/package.json +1 -1
package/dist/mcp/run-stdio.cjs
CHANGED
|
@@ -416,6 +416,7 @@ __export(paths_exports, {
|
|
|
416
416
|
sideboardReposDir: () => sideboardReposDir,
|
|
417
417
|
sideboardWorkspacesDir: () => sideboardWorkspacesDir,
|
|
418
418
|
threadFilePath: () => threadFilePath,
|
|
419
|
+
threadLivePath: () => threadLivePath,
|
|
419
420
|
threadLockPath: () => threadLockPath,
|
|
420
421
|
threadsDir: () => threadsDir,
|
|
421
422
|
worktreesRoot: () => worktreesRoot
|
|
@@ -467,6 +468,9 @@ function worktreesRoot(repoPath) {
|
|
|
467
468
|
function threadFilePath(id) {
|
|
468
469
|
return (0, import_node_path5.join)(threadsDir(), `${id}.json`);
|
|
469
470
|
}
|
|
471
|
+
function threadLivePath(id) {
|
|
472
|
+
return (0, import_node_path5.join)(threadsDir(), `${id}.live.json`);
|
|
473
|
+
}
|
|
470
474
|
function threadLockPath(id) {
|
|
471
475
|
return (0, import_node_path5.join)(locksDir(), `${id}.lock`);
|
|
472
476
|
}
|
|
@@ -759,8 +763,11 @@ function writeThread(thread) {
|
|
|
759
763
|
function idPath(id) {
|
|
760
764
|
return id;
|
|
761
765
|
}
|
|
766
|
+
function isThreadRecordFile(name) {
|
|
767
|
+
return name.endsWith(".json") && !name.endsWith(".live.json");
|
|
768
|
+
}
|
|
762
769
|
function listThreads(opts) {
|
|
763
|
-
const files = (0, import_node_fs7.readdirSync)(threadsDir()).filter(
|
|
770
|
+
const files = (0, import_node_fs7.readdirSync)(threadsDir()).filter(isThreadRecordFile);
|
|
764
771
|
const threads = files.map((f) => {
|
|
765
772
|
try {
|
|
766
773
|
return normalizeThread(
|
|
@@ -769,7 +776,9 @@ function listThreads(opts) {
|
|
|
769
776
|
} catch {
|
|
770
777
|
return null;
|
|
771
778
|
}
|
|
772
|
-
}).filter(
|
|
779
|
+
}).filter(
|
|
780
|
+
(t) => t !== null && typeof t.id === "string" && t.id.length > 0
|
|
781
|
+
).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
773
782
|
if (opts?.includeArchived) return threads;
|
|
774
783
|
return threads.filter((t) => t.status !== "archived");
|
|
775
784
|
}
|
|
@@ -801,7 +810,9 @@ function setStatus(id, status, lastError) {
|
|
|
801
810
|
}
|
|
802
811
|
function findThreadByRef(ref) {
|
|
803
812
|
const all = listThreads({ includeArchived: true });
|
|
804
|
-
return all.find(
|
|
813
|
+
return all.find(
|
|
814
|
+
(t) => t.id === ref || t.id.startsWith(ref) || t.branchName === ref || t.title === ref
|
|
815
|
+
) ?? null;
|
|
805
816
|
}
|
|
806
817
|
var import_node_crypto2, import_node_fs7, import_proper_lockfile;
|
|
807
818
|
var init_thread_store = __esm({
|
|
@@ -1798,7 +1809,7 @@ var init_teams = __esm({
|
|
|
1798
1809
|
|
|
1799
1810
|
// src/git/worktree-labels.ts
|
|
1800
1811
|
function normalizeWorktreePath(worktreePath) {
|
|
1801
|
-
const trimmed = worktreePath.replace(/\/+$/, "") || "/";
|
|
1812
|
+
const trimmed = (worktreePath ?? "").replace(/\/+$/, "") || "/";
|
|
1802
1813
|
const absolute = trimmed.startsWith("/");
|
|
1803
1814
|
const parts = trimmed.split("/").filter((p) => p && p !== ".");
|
|
1804
1815
|
const out = [];
|
|
@@ -3389,6 +3400,42 @@ var init_git_auth_mode = __esm({
|
|
|
3389
3400
|
}
|
|
3390
3401
|
});
|
|
3391
3402
|
|
|
3403
|
+
// src/util/enqueue-by-key.ts
|
|
3404
|
+
function enqueueByKey(key, fn) {
|
|
3405
|
+
const prev = tails.get(key) ?? Promise.resolve();
|
|
3406
|
+
const next = prev.then(fn, fn);
|
|
3407
|
+
tails.set(
|
|
3408
|
+
key,
|
|
3409
|
+
next.then(
|
|
3410
|
+
() => void 0,
|
|
3411
|
+
() => void 0
|
|
3412
|
+
)
|
|
3413
|
+
);
|
|
3414
|
+
return next;
|
|
3415
|
+
}
|
|
3416
|
+
var tails;
|
|
3417
|
+
var init_enqueue_by_key = __esm({
|
|
3418
|
+
"src/util/enqueue-by-key.ts"() {
|
|
3419
|
+
"use strict";
|
|
3420
|
+
tails = /* @__PURE__ */ new Map();
|
|
3421
|
+
}
|
|
3422
|
+
});
|
|
3423
|
+
|
|
3424
|
+
// src/git/repo-git-lock.ts
|
|
3425
|
+
function repoLockKey(repoPath) {
|
|
3426
|
+
const trimmed = repoPath.replace(/\/+$/, "");
|
|
3427
|
+
return `git-worktree:${trimmed || "/"}`;
|
|
3428
|
+
}
|
|
3429
|
+
function withRepoGitLock(repoPath, fn) {
|
|
3430
|
+
return enqueueByKey(repoLockKey(repoPath), fn);
|
|
3431
|
+
}
|
|
3432
|
+
var init_repo_git_lock = __esm({
|
|
3433
|
+
"src/git/repo-git-lock.ts"() {
|
|
3434
|
+
"use strict";
|
|
3435
|
+
init_enqueue_by_key();
|
|
3436
|
+
}
|
|
3437
|
+
});
|
|
3438
|
+
|
|
3392
3439
|
// src/git/pr-gates.ts
|
|
3393
3440
|
function prIsInMergeQueue(gate) {
|
|
3394
3441
|
if (gate.isInMergeQueue) return true;
|
|
@@ -4542,12 +4589,13 @@ async function createThreadWorktree(opts) {
|
|
|
4542
4589
|
`Invalid git reference: ${opts.sourceRef} (and fetch did not resolve it)`
|
|
4543
4590
|
);
|
|
4544
4591
|
}
|
|
4545
|
-
const
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4592
|
+
const added = await withRepoGitLock(opts.repoPath, async () => {
|
|
4593
|
+
const add = await git(
|
|
4594
|
+
["worktree", "add", "-b", branchName, worktreePath, startPoint],
|
|
4595
|
+
opts.repoPath,
|
|
4596
|
+
{ reject: false, timeoutMs: 6e4 }
|
|
4597
|
+
);
|
|
4598
|
+
if (add.exitCode === 0) return branchName;
|
|
4551
4599
|
const err = `${add.stderr}
|
|
4552
4600
|
${add.stdout}`;
|
|
4553
4601
|
if (/already used by worktree|already exists|checked out/i.test(err)) {
|
|
@@ -4558,17 +4606,14 @@ ${add.stdout}`;
|
|
|
4558
4606
|
opts.repoPath,
|
|
4559
4607
|
{ reject: false, timeoutMs: 6e4 }
|
|
4560
4608
|
);
|
|
4561
|
-
if (retry.exitCode === 0)
|
|
4562
|
-
branchName = alt;
|
|
4563
|
-
await ensureGhPreferOrigin(worktreePath);
|
|
4564
|
-
return { branchName, worktreePath };
|
|
4565
|
-
}
|
|
4609
|
+
if (retry.exitCode === 0) return alt;
|
|
4566
4610
|
}
|
|
4567
4611
|
}
|
|
4568
4612
|
throw new Error(
|
|
4569
4613
|
`Failed to create worktree: ${add.stderr.trim() || add.stdout.trim() || `exit ${add.exitCode}`}`
|
|
4570
4614
|
);
|
|
4571
|
-
}
|
|
4615
|
+
});
|
|
4616
|
+
branchName = added;
|
|
4572
4617
|
await ensureGhPreferOrigin(worktreePath);
|
|
4573
4618
|
return { branchName, worktreePath };
|
|
4574
4619
|
}
|
|
@@ -4592,12 +4637,13 @@ async function createExistingBranchWorktree(opts) {
|
|
|
4592
4637
|
);
|
|
4593
4638
|
}
|
|
4594
4639
|
const startPoint = await resolveWorktreeStartPoint(opts.repoPath, branchName);
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4640
|
+
await withRepoGitLock(opts.repoPath, async () => {
|
|
4641
|
+
const add = await git(
|
|
4642
|
+
["worktree", "add", worktreePath, startPoint],
|
|
4643
|
+
opts.repoPath,
|
|
4644
|
+
{ reject: false }
|
|
4645
|
+
);
|
|
4646
|
+
if (add.exitCode === 0) return;
|
|
4601
4647
|
const retry = await git(
|
|
4602
4648
|
["worktree", "add", worktreePath, branchName],
|
|
4603
4649
|
opts.repoPath,
|
|
@@ -4608,7 +4654,7 @@ async function createExistingBranchWorktree(opts) {
|
|
|
4608
4654
|
`Failed to create worktree for ${branchName}: ${retry.stderr.trim() || add.stderr.trim() || retry.stdout.trim() || add.stdout.trim() || `exit ${add.exitCode}`}`
|
|
4609
4655
|
);
|
|
4610
4656
|
}
|
|
4611
|
-
}
|
|
4657
|
+
});
|
|
4612
4658
|
const head = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath, {
|
|
4613
4659
|
reject: false
|
|
4614
4660
|
});
|
|
@@ -4619,12 +4665,14 @@ async function createExistingBranchWorktree(opts) {
|
|
|
4619
4665
|
return { branchName, worktreePath };
|
|
4620
4666
|
}
|
|
4621
4667
|
async function removeWorktree(repoPath, worktreePath, opts) {
|
|
4622
|
-
await
|
|
4623
|
-
|
|
4668
|
+
await withRepoGitLock(repoPath, async () => {
|
|
4669
|
+
await git(["worktree", "remove", "--force", worktreePath], repoPath, {
|
|
4670
|
+
reject: false
|
|
4671
|
+
});
|
|
4672
|
+
if (opts?.deleteBranch) {
|
|
4673
|
+
await git(["branch", "-D", opts.deleteBranch], repoPath, { reject: false });
|
|
4674
|
+
}
|
|
4624
4675
|
});
|
|
4625
|
-
if (opts?.deleteBranch) {
|
|
4626
|
-
await git(["branch", "-D", opts.deleteBranch], repoPath, { reject: false });
|
|
4627
|
-
}
|
|
4628
4676
|
}
|
|
4629
4677
|
async function listWorktrees(repoPath) {
|
|
4630
4678
|
const { stdout } = await git(
|
|
@@ -4916,6 +4964,7 @@ var init_worktree = __esm({
|
|
|
4916
4964
|
init_gh_errors();
|
|
4917
4965
|
init_git_auth_mode();
|
|
4918
4966
|
init_run();
|
|
4967
|
+
init_repo_git_lock();
|
|
4919
4968
|
init_app_settings();
|
|
4920
4969
|
init_pr_gates();
|
|
4921
4970
|
init_stack();
|
|
@@ -5018,7 +5067,7 @@ function coordinatorGreenfieldPlaybook(reposDir) {
|
|
|
5018
5067
|
"- Examples:",
|
|
5019
5068
|
` - Clone: \`git clone <url> ${reposDir}/<name>\``,
|
|
5020
5069
|
` - New GitHub repo: \`gh repo create <owner>/<name> --private --clone -- ${reposDir}/<name>\` (or mkdir + git init + gh repo create + remote add + push)`,
|
|
5021
|
-
"- Then: add_workspace with that absolute path \u2192 create_thread (repoPath + parentThreadId) \u2192 send_to_thread (build) \u2192 wait_for_turn \u2192 ask_git create-draft (or send_to_thread `gh pr create --draft -R <origin-owner/name>`).",
|
|
5070
|
+
"- 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>`).",
|
|
5022
5071
|
"- 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`.",
|
|
5023
5072
|
"- Do coding work in the child worktree thread, not by editing files in this home cwd."
|
|
5024
5073
|
].join("\n");
|
|
@@ -5041,8 +5090,8 @@ function coordinatorTurnReminder(opts) {
|
|
|
5041
5090
|
goal ? `- Goal / title: ${goal}` : null,
|
|
5042
5091
|
accountDefaultsPlaybookLine(),
|
|
5043
5092
|
`- For "what's going on": call list_threads (and list_workspaces if needed). Summarize fleet status \u2014 do not ls/git-status this synthetic home.`,
|
|
5044
|
-
"- Existing repo: create_thread on a repoPath \u2192 send_to_thread \u2192 wait_for_turn. If status is error, lastError/text is the failure \u2014 adapt (switch agent, tell the user). Commit/push/draft PR with ask_git on the child, then wait_for_turn \u2014 never git/gh from this cwd. Call ask_git merge only if the user explicitly asked to merge.",
|
|
5045
|
-
"- New repo: Bash (clone or gh repo create under ~/sideboard/repos/<name>) \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft.",
|
|
5093
|
+
"- Existing repo: create_thread on a repoPath \u2192 send_to_thread \u2192 wait_for_turn. If stillRunning, the child is working (progress is tools/thinking) \u2014 wait_for_turn again; do not ping it. If status is error, lastError/text is the failure \u2014 adapt (switch agent, tell the user). Commit/push/draft PR with ask_git on the child, then wait_for_turn \u2014 never git/gh from this cwd. Call ask_git merge only if the user explicitly asked to merge.",
|
|
5094
|
+
"- New repo: Bash (clone or gh repo create under ~/sideboard/repos/<name>) \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn (loop while stillRunning) \u2192 ask_git create-draft.",
|
|
5046
5095
|
"- When naming threads for the user, link them as `[Title](sideboard://thread/<id>)`.",
|
|
5047
5096
|
"- If they will wait on Slack or leave the Mac, call set_caffeinate enabled=true. When they say they are done / wrapping up / going to sleep, call set_caffeinate enabled=false. Closing this chat also turns it off."
|
|
5048
5097
|
].filter(Boolean).join("\n");
|
|
@@ -5095,8 +5144,8 @@ function ensureGlobalCoordinatorCwd(opts) {
|
|
|
5095
5144
|
orchId ? `Pass parentThreadId="${orchId}" (or omit it). Never invent another parentThreadId.` : "Pass `parentThreadId` for children (this chat's id from the turn reminder).",
|
|
5096
5145
|
"Omit `agent` / `model` on `create_thread` unless you have a reason to override Account defaults.",
|
|
5097
5146
|
"Never pass `agent=codex` when you yourself are Codex \u2014 nested Codex deadlocks on shared ~/.codex locks. Omit agent (Account default) or use cursor/claude.",
|
|
5098
|
-
"Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft \u2192 wait_for_turn. Merge only if the user explicitly asked (`ask_git` merge).",
|
|
5099
|
-
"Typical flow (new app): Bash create/clone under repos dir \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread (implement) \u2192 wait_for_turn \u2192 ask_git create-draft.",
|
|
5147
|
+
"Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn (loop while stillRunning) \u2192 ask_git create-draft \u2192 wait_for_turn. Merge only if the user explicitly asked (`ask_git` merge).",
|
|
5148
|
+
"Typical flow (new app): Bash create/clone under repos dir \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread (implement) \u2192 wait_for_turn (loop while stillRunning) \u2192 ask_git create-draft.",
|
|
5100
5149
|
"Always ask worktree agents to commit, push, and open draft PRs (`ask_git` / `send_to_thread`). Tell them to merge only when the user explicitly asked. The worktree agent runs git/gh; never merge from this orchestration cwd."
|
|
5101
5150
|
].join("\n");
|
|
5102
5151
|
try {
|
|
@@ -5164,7 +5213,7 @@ var init_coordinator_prompt = __esm({
|
|
|
5164
5213
|
"- fork_worktree \u2014 fork a worktree chat into a NEW git worktree + chat (transcript attached); optional agent; leave model unset (Auto) unless you have a reason. Not for orchestration chats.",
|
|
5165
5214
|
"- fork_chat \u2014 fork a worktree chat (same worktree tab) OR a Global orchestration chat (new orchestration tab); optional agent; leave model unset (Auto) unless you have a reason. Remote coordinators: use this to continue another orchestration chat on a different agent after session limits.",
|
|
5166
5215
|
"- send_to_thread \u2014 queue a prompt (start/continue a chat turn); pass force_stop: true to interrupt mid-turn / clear stale queued prompts before replacing with a new request",
|
|
5167
|
-
"- wait_for_turn / get_turn_result \u2014 wait for and read the agent reply. On status error, lastError (and text) is the failure \u2014 switch agent, tell the user, or retry; do not treat empty text as success.",
|
|
5216
|
+
"- wait_for_turn / get_turn_result \u2014 wait for and read the agent reply. wait_for_turn returns within ~45s even if the child is still working (MCP clients kill longer tool calls). If stillRunning is true, progress is a live snapshot of tools/thinking \u2014 call wait_for_turn again. Do not send \u201Care you stuck?\u201D or assume a hang while lastActivityAt is recent. On status error, lastError (and text) is the failure \u2014 switch agent, tell the user, or retry; do not treat empty text as success.",
|
|
5168
5217
|
"- stop_thread \u2014 force-stop: kill in-flight turn AND clear queued prompts (do not leave stale queue after an interrupt)",
|
|
5169
5218
|
"- archive_thread / restore_thread \u2014 archive (tears down worktree when last tab) or restore",
|
|
5170
5219
|
"Setup / run:",
|
|
@@ -5172,8 +5221,8 @@ var init_coordinator_prompt = __esm({
|
|
|
5172
5221
|
"- list_run_scripts / run_dev_script / stop_dev_script \u2014 start/stop named run scripts",
|
|
5173
5222
|
"Inspect / review / PRs:",
|
|
5174
5223
|
"- get_diff \u2014 compact diff summary",
|
|
5175
|
-
'- request_review \u2014 open a Review chat tab on a worktree thread (attaches .sideboard/review.md when present, else local guidelines; sends "Review changes in this workspace."); then wait_for_turn / get_turn_result on the returned id',
|
|
5176
|
-
"- ask_git \u2014 commit & push, open a draft PR, resolve conflicts, or merge. When the worktree is clean, Sideboard pushes / opens the PR itself. When dirty, it queues the worktree agent \u2014 then wait_for_turn. Prefer this over paraphrasing.",
|
|
5224
|
+
'- request_review \u2014 open a Review chat tab on a worktree thread (attaches .sideboard/review.md when present, else local guidelines; sends "Review changes in this workspace."); then wait_for_turn (loop while stillRunning) / get_turn_result on the returned id',
|
|
5225
|
+
"- ask_git \u2014 commit & push, open a draft PR, resolve conflicts, or merge. When the worktree is clean, Sideboard pushes / opens the PR itself. When dirty, it queues the worktree agent \u2014 then wait_for_turn (loop while stillRunning). Prefer this over paraphrasing.",
|
|
5177
5226
|
'- 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.',
|
|
5178
5227
|
'- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "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.',
|
|
5179
5228
|
"Process guides:",
|
|
@@ -8793,17 +8842,17 @@ __export(plan_file_exports, {
|
|
|
8793
8842
|
});
|
|
8794
8843
|
function ensureAttachmentsGitignore2(worktreePath) {
|
|
8795
8844
|
const gitignoreAbs = (0, import_node_path36.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
|
|
8796
|
-
if ((0,
|
|
8797
|
-
(0,
|
|
8798
|
-
(0,
|
|
8845
|
+
if ((0, import_node_fs40.existsSync)(gitignoreAbs)) return;
|
|
8846
|
+
(0, import_node_fs40.mkdirSync)((0, import_node_path36.dirname)(gitignoreAbs), { recursive: true });
|
|
8847
|
+
(0, import_node_fs40.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
|
|
8799
8848
|
}
|
|
8800
8849
|
function planFileAbs(worktreePath) {
|
|
8801
8850
|
return (0, import_node_path36.join)(worktreePath, PLAN_FILE_REL);
|
|
8802
8851
|
}
|
|
8803
8852
|
function readTextIfPresent2(abs) {
|
|
8804
|
-
if (!(0,
|
|
8853
|
+
if (!(0, import_node_fs40.existsSync)(abs)) return null;
|
|
8805
8854
|
try {
|
|
8806
|
-
const content = (0,
|
|
8855
|
+
const content = (0, import_node_fs40.readFileSync)(abs, "utf8");
|
|
8807
8856
|
return content.trim() ? content : null;
|
|
8808
8857
|
} catch {
|
|
8809
8858
|
return null;
|
|
@@ -8815,16 +8864,16 @@ function readPlanFile(worktreePath) {
|
|
|
8815
8864
|
function writePlanFile(worktreePath, content) {
|
|
8816
8865
|
ensureAttachmentsGitignore2(worktreePath);
|
|
8817
8866
|
const abs = planFileAbs(worktreePath);
|
|
8818
|
-
(0,
|
|
8867
|
+
(0, import_node_fs40.mkdirSync)((0, import_node_path36.dirname)(abs), { recursive: true });
|
|
8819
8868
|
const body = content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
|
|
8820
|
-
(0,
|
|
8869
|
+
(0, import_node_fs40.writeFileSync)(abs, body, "utf8");
|
|
8821
8870
|
return PLAN_FILE_REL;
|
|
8822
8871
|
}
|
|
8823
|
-
var
|
|
8872
|
+
var import_node_fs40, import_node_path36;
|
|
8824
8873
|
var init_plan_file = __esm({
|
|
8825
8874
|
"src/plan/plan-file.ts"() {
|
|
8826
8875
|
"use strict";
|
|
8827
|
-
|
|
8876
|
+
import_node_fs40 = require("fs");
|
|
8828
8877
|
import_node_path36 = require("path");
|
|
8829
8878
|
init_workspace_scratch();
|
|
8830
8879
|
init_plan_present();
|
|
@@ -8880,9 +8929,9 @@ function uniqueIds(ids) {
|
|
|
8880
8929
|
}
|
|
8881
8930
|
function readHold() {
|
|
8882
8931
|
const path = caffeinateHoldPath();
|
|
8883
|
-
if (!(0,
|
|
8932
|
+
if (!(0, import_node_fs41.existsSync)(path)) return null;
|
|
8884
8933
|
try {
|
|
8885
|
-
const parsed = JSON.parse((0,
|
|
8934
|
+
const parsed = JSON.parse((0, import_node_fs41.readFileSync)(path, "utf8"));
|
|
8886
8935
|
if (typeof parsed?.pid === "number" && parsed.pid > 0) {
|
|
8887
8936
|
return {
|
|
8888
8937
|
pid: parsed.pid,
|
|
@@ -8904,7 +8953,7 @@ function writeHold(pid, threadIds) {
|
|
|
8904
8953
|
}
|
|
8905
8954
|
function clearHold() {
|
|
8906
8955
|
try {
|
|
8907
|
-
(0,
|
|
8956
|
+
(0, import_node_fs41.unlinkSync)(caffeinateHoldPath());
|
|
8908
8957
|
} catch {
|
|
8909
8958
|
}
|
|
8910
8959
|
}
|
|
@@ -8989,12 +9038,12 @@ function releaseCaffeinateHoldForThread(threadId) {
|
|
|
8989
9038
|
}
|
|
8990
9039
|
return setCaffeinateHold(false, { threadId: id });
|
|
8991
9040
|
}
|
|
8992
|
-
var import_node_child_process4,
|
|
9041
|
+
var import_node_child_process4, import_node_fs41, import_node_path37, hooks;
|
|
8993
9042
|
var init_caffeinate_hold = __esm({
|
|
8994
9043
|
"src/store/caffeinate-hold.ts"() {
|
|
8995
9044
|
"use strict";
|
|
8996
9045
|
import_node_child_process4 = require("child_process");
|
|
8997
|
-
|
|
9046
|
+
import_node_fs41 = require("fs");
|
|
8998
9047
|
import_node_path37 = require("path");
|
|
8999
9048
|
init_paths();
|
|
9000
9049
|
init_private_file();
|
|
@@ -9011,9 +9060,9 @@ function recoverFinishedCursorRun(opts) {
|
|
|
9011
9060
|
const agentId = opts.agentId.trim();
|
|
9012
9061
|
if (!agentId) return null;
|
|
9013
9062
|
const runsPath = (0, import_node_path38.join)(appDataDir(), "cursor-sdk-store", "runs.ndjson");
|
|
9014
|
-
if (!(0,
|
|
9063
|
+
if (!(0, import_node_fs42.existsSync)(runsPath)) return null;
|
|
9015
9064
|
try {
|
|
9016
|
-
const lines = (0,
|
|
9065
|
+
const lines = (0, import_node_fs42.readFileSync)(runsPath, "utf8").split("\n");
|
|
9017
9066
|
let best = null;
|
|
9018
9067
|
for (const line of lines) {
|
|
9019
9068
|
const trimmed = line.trim();
|
|
@@ -9039,11 +9088,11 @@ function recoverFinishedCursorRun(opts) {
|
|
|
9039
9088
|
return null;
|
|
9040
9089
|
}
|
|
9041
9090
|
}
|
|
9042
|
-
var
|
|
9091
|
+
var import_node_fs42, import_node_path38;
|
|
9043
9092
|
var init_cursor_recover = __esm({
|
|
9044
9093
|
"src/agents/cursor-recover.ts"() {
|
|
9045
9094
|
"use strict";
|
|
9046
|
-
|
|
9095
|
+
import_node_fs42 = require("fs");
|
|
9047
9096
|
import_node_path38 = require("path");
|
|
9048
9097
|
init_paths();
|
|
9049
9098
|
}
|
|
@@ -9567,7 +9616,7 @@ async function refreshSlackReplyBadges(opts) {
|
|
|
9567
9616
|
}
|
|
9568
9617
|
|
|
9569
9618
|
// src/orchestrator/orchestrator.ts
|
|
9570
|
-
var
|
|
9619
|
+
var import_node_fs43 = require("fs");
|
|
9571
9620
|
init_error_detail();
|
|
9572
9621
|
|
|
9573
9622
|
// src/agents/spawn.ts
|
|
@@ -9821,7 +9870,7 @@ function createAgentStreamCoalescer(emit, opts) {
|
|
|
9821
9870
|
const intervalMs = opts?.intervalMs ?? 32;
|
|
9822
9871
|
let pending = null;
|
|
9823
9872
|
let timer = null;
|
|
9824
|
-
const
|
|
9873
|
+
const flush2 = () => {
|
|
9825
9874
|
if (timer) {
|
|
9826
9875
|
clearTimeout(timer);
|
|
9827
9876
|
timer = null;
|
|
@@ -9833,13 +9882,13 @@ function createAgentStreamCoalescer(emit, opts) {
|
|
|
9833
9882
|
};
|
|
9834
9883
|
const schedule = () => {
|
|
9835
9884
|
if (timer) return;
|
|
9836
|
-
timer = setTimeout(
|
|
9885
|
+
timer = setTimeout(flush2, intervalMs);
|
|
9837
9886
|
timer.unref?.();
|
|
9838
9887
|
};
|
|
9839
9888
|
return {
|
|
9840
9889
|
push(event) {
|
|
9841
9890
|
if (!isTextEvent(event)) {
|
|
9842
|
-
|
|
9891
|
+
flush2();
|
|
9843
9892
|
emit(event);
|
|
9844
9893
|
return;
|
|
9845
9894
|
}
|
|
@@ -9847,12 +9896,12 @@ function createAgentStreamCoalescer(emit, opts) {
|
|
|
9847
9896
|
if (pending && pending.type === event.type) {
|
|
9848
9897
|
pending.data += event.data;
|
|
9849
9898
|
} else {
|
|
9850
|
-
|
|
9899
|
+
flush2();
|
|
9851
9900
|
pending = { type: event.type, data: event.data };
|
|
9852
9901
|
}
|
|
9853
9902
|
schedule();
|
|
9854
9903
|
},
|
|
9855
|
-
flush
|
|
9904
|
+
flush: flush2
|
|
9856
9905
|
};
|
|
9857
9906
|
}
|
|
9858
9907
|
|
|
@@ -11168,8 +11217,11 @@ function worktreeBindingFrom(from) {
|
|
|
11168
11217
|
};
|
|
11169
11218
|
}
|
|
11170
11219
|
function threadsSharingWorktree(worktreePath) {
|
|
11220
|
+
if (!worktreePath) return [];
|
|
11171
11221
|
const key = normalizeWorktreePath(worktreePath);
|
|
11172
|
-
return listThreads({ includeArchived: true }).filter(
|
|
11222
|
+
return listThreads({ includeArchived: true }).filter(
|
|
11223
|
+
(t) => Boolean(t.worktreePath) && sameWorktreePath(t.worktreePath, key) && t.status !== "archived"
|
|
11224
|
+
).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
11173
11225
|
}
|
|
11174
11226
|
function formatTranscriptMarkdown(title, messages) {
|
|
11175
11227
|
const body = formatMessagesAsTranscript(messages, { tools: "full" });
|
|
@@ -11240,9 +11292,132 @@ function forkChatTab(input) {
|
|
|
11240
11292
|
return tab;
|
|
11241
11293
|
}
|
|
11242
11294
|
|
|
11295
|
+
// src/orchestrator/orchestrator.ts
|
|
11296
|
+
init_enqueue_by_key();
|
|
11297
|
+
init_run();
|
|
11298
|
+
init_repo_git_lock();
|
|
11299
|
+
|
|
11300
|
+
// src/store/turn-live.ts
|
|
11301
|
+
var import_node_fs32 = require("fs");
|
|
11302
|
+
init_paths();
|
|
11303
|
+
var buffers = /* @__PURE__ */ new Map();
|
|
11304
|
+
var FLUSH_MS = 800;
|
|
11305
|
+
var MAX_PARTS = 80;
|
|
11306
|
+
function trimParts(parts) {
|
|
11307
|
+
if (parts.length <= MAX_PARTS) return parts;
|
|
11308
|
+
return parts.slice(-Math.floor(MAX_PARTS / 2));
|
|
11309
|
+
}
|
|
11310
|
+
function lastText(parts, type) {
|
|
11311
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
11312
|
+
const p = parts[i];
|
|
11313
|
+
if (p.type === type && p.text.trim()) return p.text.trim();
|
|
11314
|
+
}
|
|
11315
|
+
return "";
|
|
11316
|
+
}
|
|
11317
|
+
function summarizeTurnLive(parts) {
|
|
11318
|
+
const tools = parts.filter(
|
|
11319
|
+
(p) => p.type === "tool"
|
|
11320
|
+
);
|
|
11321
|
+
const lastTool = tools[tools.length - 1];
|
|
11322
|
+
const lastToolLabel = lastTool ? lastTool.description || toolDescription(lastTool.name, lastTool.input) || lastTool.name : void 0;
|
|
11323
|
+
const running = lastTool?.status === "running";
|
|
11324
|
+
const thinking = lastText(parts, "thinking");
|
|
11325
|
+
const text3 = lastText(parts, "text");
|
|
11326
|
+
const excerptRaw = thinking || text3;
|
|
11327
|
+
const excerpt = excerptRaw.length > 280 ? `${excerptRaw.slice(-280)}` : excerptRaw || void 0;
|
|
11328
|
+
let summary = "Working\u2026";
|
|
11329
|
+
if (lastToolLabel) {
|
|
11330
|
+
const verb = running ? lastToolLabel : `Finished ${lastToolLabel}`;
|
|
11331
|
+
summary = tools.length > 1 ? `${verb} (${tools.length} tools)` : verb;
|
|
11332
|
+
} else if (thinking) {
|
|
11333
|
+
summary = "Thinking\u2026";
|
|
11334
|
+
} else if (text3) {
|
|
11335
|
+
summary = "Writing reply\u2026";
|
|
11336
|
+
}
|
|
11337
|
+
return {
|
|
11338
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11339
|
+
summary,
|
|
11340
|
+
lastTool: lastToolLabel,
|
|
11341
|
+
toolCount: tools.length,
|
|
11342
|
+
excerpt
|
|
11343
|
+
};
|
|
11344
|
+
}
|
|
11345
|
+
function flush(threadId) {
|
|
11346
|
+
const buf = buffers.get(threadId);
|
|
11347
|
+
if (!buf) return;
|
|
11348
|
+
if (buf.timer) {
|
|
11349
|
+
clearTimeout(buf.timer);
|
|
11350
|
+
buf.timer = null;
|
|
11351
|
+
}
|
|
11352
|
+
writeTurnLive(threadId, summarizeTurnLive(buf.parts));
|
|
11353
|
+
}
|
|
11354
|
+
function scheduleFlush(threadId) {
|
|
11355
|
+
const buf = buffers.get(threadId);
|
|
11356
|
+
if (!buf) return;
|
|
11357
|
+
if (buf.timer) return;
|
|
11358
|
+
buf.timer = setTimeout(() => {
|
|
11359
|
+
const live = buffers.get(threadId);
|
|
11360
|
+
if (live) live.timer = null;
|
|
11361
|
+
flush(threadId);
|
|
11362
|
+
}, FLUSH_MS);
|
|
11363
|
+
}
|
|
11364
|
+
function noteTurnLiveEvent(threadId, event) {
|
|
11365
|
+
if (event.type === "session_id" || event.type === "usage" || event.type === "stderr") {
|
|
11366
|
+
return;
|
|
11367
|
+
}
|
|
11368
|
+
const prev = buffers.get(threadId) ?? { parts: [], timer: null };
|
|
11369
|
+
prev.parts = trimParts(applyAgentEvent(prev.parts, event));
|
|
11370
|
+
buffers.set(threadId, prev);
|
|
11371
|
+
if (event.type === "exit") {
|
|
11372
|
+
flush(threadId);
|
|
11373
|
+
const cur = buffers.get(threadId);
|
|
11374
|
+
if (cur?.timer) clearTimeout(cur.timer);
|
|
11375
|
+
buffers.delete(threadId);
|
|
11376
|
+
return;
|
|
11377
|
+
}
|
|
11378
|
+
const immediate = event.type === "tool_use" || event.type === "tool_result";
|
|
11379
|
+
if (immediate) flush(threadId);
|
|
11380
|
+
else scheduleFlush(threadId);
|
|
11381
|
+
}
|
|
11382
|
+
function writeTurnLive(threadId, progress) {
|
|
11383
|
+
const path = threadLivePath(threadId);
|
|
11384
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
11385
|
+
try {
|
|
11386
|
+
(0, import_node_fs32.writeFileSync)(tmp, JSON.stringify(progress), "utf8");
|
|
11387
|
+
(0, import_node_fs32.renameSync)(tmp, path);
|
|
11388
|
+
} catch {
|
|
11389
|
+
try {
|
|
11390
|
+
(0, import_node_fs32.unlinkSync)(tmp);
|
|
11391
|
+
} catch {
|
|
11392
|
+
}
|
|
11393
|
+
}
|
|
11394
|
+
}
|
|
11395
|
+
function readTurnLive(threadId) {
|
|
11396
|
+
const path = threadLivePath(threadId);
|
|
11397
|
+
if (!(0, import_node_fs32.existsSync)(path)) return null;
|
|
11398
|
+
try {
|
|
11399
|
+
const raw = JSON.parse((0, import_node_fs32.readFileSync)(path, "utf8"));
|
|
11400
|
+
if (!raw || typeof raw.summary !== "string") return null;
|
|
11401
|
+
return raw;
|
|
11402
|
+
} catch {
|
|
11403
|
+
return null;
|
|
11404
|
+
}
|
|
11405
|
+
}
|
|
11406
|
+
function clearTurnLive(threadId) {
|
|
11407
|
+
const buf = buffers.get(threadId);
|
|
11408
|
+
if (buf?.timer) clearTimeout(buf.timer);
|
|
11409
|
+
buffers.delete(threadId);
|
|
11410
|
+
const path = threadLivePath(threadId);
|
|
11411
|
+
if (!(0, import_node_fs32.existsSync)(path)) return;
|
|
11412
|
+
try {
|
|
11413
|
+
(0, import_node_fs32.unlinkSync)(path);
|
|
11414
|
+
} catch {
|
|
11415
|
+
}
|
|
11416
|
+
}
|
|
11417
|
+
|
|
11243
11418
|
// src/review/request-review.ts
|
|
11244
11419
|
var import_node_crypto5 = require("crypto");
|
|
11245
|
-
var
|
|
11420
|
+
var import_node_fs33 = require("fs");
|
|
11246
11421
|
var import_node_path30 = require("path");
|
|
11247
11422
|
init_global_workspace();
|
|
11248
11423
|
init_thread_store();
|
|
@@ -11391,9 +11566,9 @@ function shouldRefreshReviewRequestTemplate(content) {
|
|
|
11391
11566
|
return LEGACY_REVIEW_TEMPLATE_MARKERS.every((m) => trimmed.includes(m));
|
|
11392
11567
|
}
|
|
11393
11568
|
function readTextIfPresent(abs) {
|
|
11394
|
-
if (!(0,
|
|
11569
|
+
if (!(0, import_node_fs33.existsSync)(abs)) return null;
|
|
11395
11570
|
try {
|
|
11396
|
-
const content = (0,
|
|
11571
|
+
const content = (0, import_node_fs33.readFileSync)(abs, "utf8");
|
|
11397
11572
|
return content.trim() ? content : null;
|
|
11398
11573
|
} catch {
|
|
11399
11574
|
return null;
|
|
@@ -11401,9 +11576,9 @@ function readTextIfPresent(abs) {
|
|
|
11401
11576
|
}
|
|
11402
11577
|
function ensureAttachmentsGitignore(worktreePath) {
|
|
11403
11578
|
const gitignoreAbs = (0, import_node_path30.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
|
|
11404
|
-
if ((0,
|
|
11405
|
-
(0,
|
|
11406
|
-
(0,
|
|
11579
|
+
if ((0, import_node_fs33.existsSync)(gitignoreAbs)) return;
|
|
11580
|
+
(0, import_node_fs33.mkdirSync)((0, import_node_path30.dirname)(gitignoreAbs), { recursive: true });
|
|
11581
|
+
(0, import_node_fs33.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
|
|
11407
11582
|
}
|
|
11408
11583
|
function resolveReviewGuidelines(worktreePath) {
|
|
11409
11584
|
const repoAbs = (0, import_node_path30.join)(worktreePath, REPO_REVIEW_PATH);
|
|
@@ -11437,8 +11612,8 @@ function resolveReviewGuidelines(worktreePath) {
|
|
|
11437
11612
|
};
|
|
11438
11613
|
}
|
|
11439
11614
|
ensureAttachmentsGitignore(worktreePath);
|
|
11440
|
-
(0,
|
|
11441
|
-
(0,
|
|
11615
|
+
(0, import_node_fs33.mkdirSync)((0, import_node_path30.dirname)(localAbs), { recursive: true });
|
|
11616
|
+
(0, import_node_fs33.writeFileSync)(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
|
|
11442
11617
|
return {
|
|
11443
11618
|
path: REVIEW_REQUEST_PATH,
|
|
11444
11619
|
name: REVIEW_REQUEST_NAME,
|
|
@@ -11638,7 +11813,7 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
|
|
|
11638
11813
|
|
|
11639
11814
|
// src/threads/adopt.ts
|
|
11640
11815
|
var import_node_child_process3 = require("child_process");
|
|
11641
|
-
var
|
|
11816
|
+
var import_node_fs34 = require("fs");
|
|
11642
11817
|
var import_node_os10 = require("os");
|
|
11643
11818
|
var import_node_path31 = require("path");
|
|
11644
11819
|
var import_node_module4 = require("module");
|
|
@@ -11669,21 +11844,21 @@ function mapAgentType(raw) {
|
|
|
11669
11844
|
return null;
|
|
11670
11845
|
}
|
|
11671
11846
|
function resolveConductorCursorAgentId(workspacePath) {
|
|
11672
|
-
if (!workspacePath || !(0,
|
|
11847
|
+
if (!workspacePath || !(0, import_node_fs34.existsSync)(CURSOR_SDK_STORE)) return null;
|
|
11673
11848
|
const normalized = workspacePath.replace(/\/$/, "");
|
|
11674
11849
|
let best = null;
|
|
11675
11850
|
let hashes;
|
|
11676
11851
|
try {
|
|
11677
|
-
hashes = (0,
|
|
11852
|
+
hashes = (0, import_node_fs34.readdirSync)(CURSOR_SDK_STORE);
|
|
11678
11853
|
} catch {
|
|
11679
11854
|
return null;
|
|
11680
11855
|
}
|
|
11681
11856
|
for (const hash of hashes) {
|
|
11682
11857
|
const agentsFile = (0, import_node_path31.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
|
|
11683
|
-
if (!(0,
|
|
11858
|
+
if (!(0, import_node_fs34.existsSync)(agentsFile)) continue;
|
|
11684
11859
|
let text3;
|
|
11685
11860
|
try {
|
|
11686
|
-
text3 = (0,
|
|
11861
|
+
text3 = (0, import_node_fs34.readFileSync)(agentsFile, "utf8");
|
|
11687
11862
|
} catch {
|
|
11688
11863
|
continue;
|
|
11689
11864
|
}
|
|
@@ -11707,7 +11882,7 @@ function resolveConductorCursorAgentId(workspacePath) {
|
|
|
11707
11882
|
return best?.agentId ?? null;
|
|
11708
11883
|
}
|
|
11709
11884
|
async function adoptThread(input) {
|
|
11710
|
-
if (!(0,
|
|
11885
|
+
if (!(0, import_node_fs34.existsSync)(input.worktreePath)) {
|
|
11711
11886
|
throw new Error(`Worktree not found: ${input.worktreePath}`);
|
|
11712
11887
|
}
|
|
11713
11888
|
const repoPath = await resolveRepoRoot(input.worktreePath);
|
|
@@ -11731,18 +11906,18 @@ async function adoptThread(input) {
|
|
|
11731
11906
|
return thread;
|
|
11732
11907
|
}
|
|
11733
11908
|
function listConductorWorkspaces() {
|
|
11734
|
-
if (!(0,
|
|
11909
|
+
if (!(0, import_node_fs34.existsSync)(CONDUCTOR_DB)) {
|
|
11735
11910
|
throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
|
|
11736
11911
|
}
|
|
11737
|
-
const tmp = (0,
|
|
11912
|
+
const tmp = (0, import_node_fs34.mkdtempSync)((0, import_node_path31.join)((0, import_node_os10.tmpdir)(), "sideboard-conductor-"));
|
|
11738
11913
|
const snapshot = (0, import_node_path31.join)(tmp, "conductor.db");
|
|
11739
11914
|
try {
|
|
11740
|
-
(0,
|
|
11915
|
+
(0, import_node_fs34.copyFileSync)(CONDUCTOR_DB, snapshot);
|
|
11741
11916
|
for (const suffix of ["-wal", "-shm"]) {
|
|
11742
11917
|
const src = `${CONDUCTOR_DB}${suffix}`;
|
|
11743
|
-
if ((0,
|
|
11918
|
+
if ((0, import_node_fs34.existsSync)(src)) {
|
|
11744
11919
|
try {
|
|
11745
|
-
(0,
|
|
11920
|
+
(0, import_node_fs34.copyFileSync)(src, `${snapshot}${suffix}`);
|
|
11746
11921
|
} catch {
|
|
11747
11922
|
}
|
|
11748
11923
|
}
|
|
@@ -11818,22 +11993,22 @@ function listConductorWorkspaces() {
|
|
|
11818
11993
|
db.close();
|
|
11819
11994
|
}
|
|
11820
11995
|
} finally {
|
|
11821
|
-
(0,
|
|
11996
|
+
(0, import_node_fs34.rmSync)(tmp, { recursive: true, force: true });
|
|
11822
11997
|
}
|
|
11823
11998
|
}
|
|
11824
11999
|
function importConductorWorkspace(workspaceId) {
|
|
11825
|
-
if (!(0,
|
|
12000
|
+
if (!(0, import_node_fs34.existsSync)(CONDUCTOR_DB)) {
|
|
11826
12001
|
throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
|
|
11827
12002
|
}
|
|
11828
|
-
const tmp = (0,
|
|
12003
|
+
const tmp = (0, import_node_fs34.mkdtempSync)((0, import_node_path31.join)((0, import_node_os10.tmpdir)(), "sideboard-conductor-"));
|
|
11829
12004
|
const snapshot = (0, import_node_path31.join)(tmp, "conductor.db");
|
|
11830
12005
|
try {
|
|
11831
|
-
(0,
|
|
12006
|
+
(0, import_node_fs34.copyFileSync)(CONDUCTOR_DB, snapshot);
|
|
11832
12007
|
for (const suffix of ["-wal", "-shm"]) {
|
|
11833
12008
|
const src = `${CONDUCTOR_DB}${suffix}`;
|
|
11834
|
-
if ((0,
|
|
12009
|
+
if ((0, import_node_fs34.existsSync)(src)) {
|
|
11835
12010
|
try {
|
|
11836
|
-
(0,
|
|
12011
|
+
(0, import_node_fs34.copyFileSync)(src, `${snapshot}${suffix}`);
|
|
11837
12012
|
} catch {
|
|
11838
12013
|
}
|
|
11839
12014
|
}
|
|
@@ -11851,7 +12026,7 @@ function importConductorWorkspace(workspaceId) {
|
|
|
11851
12026
|
).get(workspaceId);
|
|
11852
12027
|
if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
|
|
11853
12028
|
const worktreePath = String(row.workspacePath);
|
|
11854
|
-
if (!(0,
|
|
12029
|
+
if (!(0, import_node_fs34.existsSync)(worktreePath)) {
|
|
11855
12030
|
throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
|
|
11856
12031
|
}
|
|
11857
12032
|
let sessionId = null;
|
|
@@ -11914,7 +12089,7 @@ function importConductorWorkspace(workspaceId) {
|
|
|
11914
12089
|
db.close();
|
|
11915
12090
|
}
|
|
11916
12091
|
} finally {
|
|
11917
|
-
(0,
|
|
12092
|
+
(0, import_node_fs34.rmSync)(tmp, { recursive: true, force: true });
|
|
11918
12093
|
}
|
|
11919
12094
|
}
|
|
11920
12095
|
async function importConductorWorkspaceAsync(workspaceId) {
|
|
@@ -11922,7 +12097,7 @@ async function importConductorWorkspaceAsync(workspaceId) {
|
|
|
11922
12097
|
}
|
|
11923
12098
|
|
|
11924
12099
|
// src/threads/stack-layers.ts
|
|
11925
|
-
var
|
|
12100
|
+
var import_node_fs35 = require("fs");
|
|
11926
12101
|
init_run();
|
|
11927
12102
|
init_stack();
|
|
11928
12103
|
init_worktree();
|
|
@@ -11990,7 +12165,7 @@ async function openStackLayer(input, _onSetupLine) {
|
|
|
11990
12165
|
let createdWorktree = false;
|
|
11991
12166
|
const trees = await listWorktrees(repoPath);
|
|
11992
12167
|
const checkedOut = trees.find((w) => w.branch === branchName);
|
|
11993
|
-
if (checkedOut?.path && (0,
|
|
12168
|
+
if (checkedOut?.path && (0, import_node_fs35.existsSync)(checkedOut.path)) {
|
|
11994
12169
|
if (input.reuseExistingWorktree !== false) {
|
|
11995
12170
|
worktreePath = checkedOut.path;
|
|
11996
12171
|
} else {
|
|
@@ -12132,7 +12307,7 @@ async function initStackFromThread(input, onSetupLine) {
|
|
|
12132
12307
|
async function createPrStack(input, onSetupLine) {
|
|
12133
12308
|
await requireAgent(input.agent);
|
|
12134
12309
|
const repoPath = await resolveRepoRoot(input.repoPath);
|
|
12135
|
-
if (!(0,
|
|
12310
|
+
if (!(0, import_node_fs35.existsSync)(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
|
|
12136
12311
|
if (!input.branches.length) throw new Error("At least one branch name required");
|
|
12137
12312
|
const status = await detectGhStack(repoPath);
|
|
12138
12313
|
if (!status.available) throw new Error(status.reason);
|
|
@@ -12199,7 +12374,7 @@ async function createPrStack(input, onSetupLine) {
|
|
|
12199
12374
|
}
|
|
12200
12375
|
}
|
|
12201
12376
|
const claimed = new Set(threads.map((t) => t.worktreePath));
|
|
12202
|
-
if (!claimed.has(bootstrap.worktreePath) && (0,
|
|
12377
|
+
if (!claimed.has(bootstrap.worktreePath) && (0, import_node_fs35.existsSync)(bootstrap.worktreePath)) {
|
|
12203
12378
|
try {
|
|
12204
12379
|
await removeWorktree(repoPath, bootstrap.worktreePath, {
|
|
12205
12380
|
deleteBranch: bootstrap.branchName
|
|
@@ -12214,12 +12389,12 @@ async function createPrStack(input, onSetupLine) {
|
|
|
12214
12389
|
init_worktree();
|
|
12215
12390
|
|
|
12216
12391
|
// src/diff/diff.ts
|
|
12217
|
-
var
|
|
12392
|
+
var import_node_fs36 = require("fs");
|
|
12218
12393
|
var import_node_path32 = require("path");
|
|
12219
12394
|
init_run();
|
|
12220
12395
|
init_worktree();
|
|
12221
12396
|
async function inspectGitWorktree(worktreePath) {
|
|
12222
|
-
if (!worktreePath || !(0,
|
|
12397
|
+
if (!worktreePath || !(0, import_node_fs36.existsSync)(worktreePath)) return "missing_worktree";
|
|
12223
12398
|
const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
|
|
12224
12399
|
reject: false
|
|
12225
12400
|
});
|
|
@@ -12227,7 +12402,7 @@ async function inspectGitWorktree(worktreePath) {
|
|
|
12227
12402
|
return "ok";
|
|
12228
12403
|
}
|
|
12229
12404
|
async function initializeGitRepository(worktreePath) {
|
|
12230
|
-
if (!worktreePath || !(0,
|
|
12405
|
+
if (!worktreePath || !(0, import_node_fs36.existsSync)(worktreePath)) {
|
|
12231
12406
|
throw new Error("Worktree not found");
|
|
12232
12407
|
}
|
|
12233
12408
|
const status = await inspectGitWorktree(worktreePath);
|
|
@@ -12365,9 +12540,9 @@ new file mode 100644
|
|
|
12365
12540
|
async function untrackedPatch(worktreePath, path, maxHunk) {
|
|
12366
12541
|
const abs = (0, import_node_path32.join)(worktreePath, path);
|
|
12367
12542
|
try {
|
|
12368
|
-
const st = (0,
|
|
12543
|
+
const st = (0, import_node_fs36.statSync)(abs);
|
|
12369
12544
|
if (st.isFile() && st.size > maxHunk) {
|
|
12370
|
-
const buf = (0,
|
|
12545
|
+
const buf = (0, import_node_fs36.readFileSync)(abs).subarray(0, maxHunk);
|
|
12371
12546
|
return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
|
|
12372
12547
|
}
|
|
12373
12548
|
} catch {
|
|
@@ -12869,7 +13044,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
|
12869
13044
|
assertSafeRelativePath(relativePath);
|
|
12870
13045
|
const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
|
|
12871
13046
|
const abs = (0, import_node_path32.join)(worktreePath, relativePath);
|
|
12872
|
-
const st = (0,
|
|
13047
|
+
const st = (0, import_node_fs36.statSync)(abs);
|
|
12873
13048
|
if (!st.isFile()) {
|
|
12874
13049
|
throw new Error(`Not a file: ${relativePath}`);
|
|
12875
13050
|
}
|
|
@@ -12878,7 +13053,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
|
12878
13053
|
`File too large to upload (${st.size} bytes; max ${maxBytes})`
|
|
12879
13054
|
);
|
|
12880
13055
|
}
|
|
12881
|
-
const buf = (0,
|
|
13056
|
+
const buf = (0, import_node_fs36.readFileSync)(abs);
|
|
12882
13057
|
return {
|
|
12883
13058
|
path: relativePath,
|
|
12884
13059
|
contentBase64: buf.toString("base64"),
|
|
@@ -12889,11 +13064,11 @@ function readWorktreeFile(worktreePath, relativePath, opts) {
|
|
|
12889
13064
|
assertSafeRelativePath(relativePath);
|
|
12890
13065
|
const maxBytes = opts?.maxBytes ?? 2e5;
|
|
12891
13066
|
const abs = (0, import_node_path32.join)(worktreePath, relativePath);
|
|
12892
|
-
const st = (0,
|
|
13067
|
+
const st = (0, import_node_fs36.statSync)(abs);
|
|
12893
13068
|
if (!st.isFile()) {
|
|
12894
13069
|
throw new Error(`Not a file: ${relativePath}`);
|
|
12895
13070
|
}
|
|
12896
|
-
const buf = (0,
|
|
13071
|
+
const buf = (0, import_node_fs36.readFileSync)(abs);
|
|
12897
13072
|
if (isImageRelativePath(relativePath)) {
|
|
12898
13073
|
const maxImageBytes = Math.max(maxBytes, 15e6);
|
|
12899
13074
|
const truncated2 = buf.length > maxImageBytes;
|
|
@@ -12937,8 +13112,8 @@ function assertSafeRelativePath(relativePath) {
|
|
|
12937
13112
|
function writeWorktreeFile(worktreePath, relativePath, content) {
|
|
12938
13113
|
assertSafeRelativePath(relativePath);
|
|
12939
13114
|
const abs = (0, import_node_path32.join)(worktreePath, relativePath);
|
|
12940
|
-
(0,
|
|
12941
|
-
(0,
|
|
13115
|
+
(0, import_node_fs36.mkdirSync)((0, import_node_path32.dirname)(abs), { recursive: true });
|
|
13116
|
+
(0, import_node_fs36.writeFileSync)(abs, content, "utf8");
|
|
12942
13117
|
return { path: relativePath };
|
|
12943
13118
|
}
|
|
12944
13119
|
async function getDiffSummary(worktreePath, repoPath, opts) {
|
|
@@ -13042,7 +13217,7 @@ async function confirmLand(thread, opts) {
|
|
|
13042
13217
|
}
|
|
13043
13218
|
|
|
13044
13219
|
// src/skills/discover.ts
|
|
13045
|
-
var
|
|
13220
|
+
var import_node_fs37 = require("fs");
|
|
13046
13221
|
var import_node_os11 = require("os");
|
|
13047
13222
|
var import_node_path33 = require("path");
|
|
13048
13223
|
function toCommand(name) {
|
|
@@ -13076,7 +13251,7 @@ function parseFrontmatter(content) {
|
|
|
13076
13251
|
}
|
|
13077
13252
|
function readSkill(skillMd, source) {
|
|
13078
13253
|
try {
|
|
13079
|
-
const content = (0,
|
|
13254
|
+
const content = (0, import_node_fs37.readFileSync)(skillMd, "utf8");
|
|
13080
13255
|
const { name: fmName, description } = parseFrontmatter(content);
|
|
13081
13256
|
const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
|
|
13082
13257
|
const name = fmName || dirName;
|
|
@@ -13095,19 +13270,19 @@ function readSkill(skillMd, source) {
|
|
|
13095
13270
|
}
|
|
13096
13271
|
}
|
|
13097
13272
|
function scanSkillsDir(dir, source, out) {
|
|
13098
|
-
if (!(0,
|
|
13273
|
+
if (!(0, import_node_fs37.existsSync)(dir)) return;
|
|
13099
13274
|
let entries;
|
|
13100
13275
|
try {
|
|
13101
|
-
entries = (0,
|
|
13276
|
+
entries = (0, import_node_fs37.readdirSync)(dir);
|
|
13102
13277
|
} catch {
|
|
13103
13278
|
return;
|
|
13104
13279
|
}
|
|
13105
13280
|
for (const entry of entries) {
|
|
13106
13281
|
if (entry.startsWith(".")) continue;
|
|
13107
13282
|
const skillMd = (0, import_node_path33.join)(dir, entry, "SKILL.md");
|
|
13108
|
-
if (!(0,
|
|
13283
|
+
if (!(0, import_node_fs37.existsSync)(skillMd)) continue;
|
|
13109
13284
|
try {
|
|
13110
|
-
if (!(0,
|
|
13285
|
+
if (!(0, import_node_fs37.statSync)(skillMd).isFile()) continue;
|
|
13111
13286
|
} catch {
|
|
13112
13287
|
continue;
|
|
13113
13288
|
}
|
|
@@ -13116,12 +13291,12 @@ function scanSkillsDir(dir, source, out) {
|
|
|
13116
13291
|
}
|
|
13117
13292
|
}
|
|
13118
13293
|
function scanClaudePluginSkills(pluginsRoot, out) {
|
|
13119
|
-
if (!(0,
|
|
13294
|
+
if (!(0, import_node_fs37.existsSync)(pluginsRoot)) return;
|
|
13120
13295
|
const walk = (dir, depth, lookingForSkillsDir) => {
|
|
13121
13296
|
if (depth > 7) return;
|
|
13122
13297
|
let entries;
|
|
13123
13298
|
try {
|
|
13124
|
-
entries = (0,
|
|
13299
|
+
entries = (0, import_node_fs37.readdirSync)(dir);
|
|
13125
13300
|
} catch {
|
|
13126
13301
|
return;
|
|
13127
13302
|
}
|
|
@@ -13133,7 +13308,7 @@ function scanClaudePluginSkills(pluginsRoot, out) {
|
|
|
13133
13308
|
if (entry === "node_modules" || entry === ".git") continue;
|
|
13134
13309
|
const full = (0, import_node_path33.join)(dir, entry);
|
|
13135
13310
|
try {
|
|
13136
|
-
if (!(0,
|
|
13311
|
+
if (!(0, import_node_fs37.statSync)(full).isDirectory()) continue;
|
|
13137
13312
|
} catch {
|
|
13138
13313
|
continue;
|
|
13139
13314
|
}
|
|
@@ -13173,7 +13348,7 @@ function discoverSkills(worktreePath) {
|
|
|
13173
13348
|
return [...byCommand.values()].sort((a, b) => a.command.localeCompare(b.command));
|
|
13174
13349
|
}
|
|
13175
13350
|
function readSkillBody(skillPath, maxChars = 12e3) {
|
|
13176
|
-
const raw = (0,
|
|
13351
|
+
const raw = (0, import_node_fs37.readFileSync)(skillPath, "utf8");
|
|
13177
13352
|
if (raw.startsWith("---")) {
|
|
13178
13353
|
const end = raw.indexOf("\n---", 3);
|
|
13179
13354
|
if (end >= 0) {
|
|
@@ -13266,7 +13441,7 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
|
|
|
13266
13441
|
}
|
|
13267
13442
|
|
|
13268
13443
|
// src/composer/stage-files.ts
|
|
13269
|
-
var
|
|
13444
|
+
var import_node_fs38 = require("fs");
|
|
13270
13445
|
var import_node_path34 = require("path");
|
|
13271
13446
|
var import_node_crypto7 = require("crypto");
|
|
13272
13447
|
init_workspace_scratch();
|
|
@@ -13304,21 +13479,21 @@ function imageMimeType(filePath) {
|
|
|
13304
13479
|
}
|
|
13305
13480
|
function ensureAttachmentsDir(worktreePath) {
|
|
13306
13481
|
const dir = (0, import_node_path34.join)(worktreePath, ATTACHMENTS_DIR);
|
|
13307
|
-
(0,
|
|
13482
|
+
(0, import_node_fs38.mkdirSync)(dir, { recursive: true });
|
|
13308
13483
|
const gi = (0, import_node_path34.join)(dir, ".gitignore");
|
|
13309
|
-
if (!(0,
|
|
13310
|
-
(0,
|
|
13484
|
+
if (!(0, import_node_fs38.existsSync)(gi)) {
|
|
13485
|
+
(0, import_node_fs38.writeFileSync)(gi, attachmentsGitignoreBody(), "utf8");
|
|
13311
13486
|
}
|
|
13312
13487
|
return dir;
|
|
13313
13488
|
}
|
|
13314
13489
|
function uniqueAttachmentName(dir, originalName) {
|
|
13315
13490
|
const safe = originalName.replace(/[/\\]/g, "_") || "file";
|
|
13316
|
-
if (!(0,
|
|
13491
|
+
if (!(0, import_node_fs38.existsSync)((0, import_node_path34.join)(dir, safe))) return safe;
|
|
13317
13492
|
const ext = (0, import_node_path34.extname)(safe);
|
|
13318
13493
|
const stem = ext ? safe.slice(0, -ext.length) : safe;
|
|
13319
13494
|
for (let i = 1; i < 1e4; i++) {
|
|
13320
13495
|
const candidate = `${stem}-${i}${ext}`;
|
|
13321
|
-
if (!(0,
|
|
13496
|
+
if (!(0, import_node_fs38.existsSync)((0, import_node_path34.join)(dir, candidate))) return candidate;
|
|
13322
13497
|
}
|
|
13323
13498
|
return `${stem}-${(0, import_node_crypto7.randomUUID)()}${ext}`;
|
|
13324
13499
|
}
|
|
@@ -13376,13 +13551,13 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
|
|
|
13376
13551
|
for (const abs of absolutePaths) {
|
|
13377
13552
|
const originalName = (0, import_node_path34.basename)(abs);
|
|
13378
13553
|
try {
|
|
13379
|
-
const st = (0,
|
|
13554
|
+
const st = (0, import_node_fs38.statSync)(abs);
|
|
13380
13555
|
if (!st.isFile()) continue;
|
|
13381
13556
|
const name = uniqueAttachmentName(dir, originalName);
|
|
13382
13557
|
const destAbs = (0, import_node_path34.join)(dir, name);
|
|
13383
|
-
(0,
|
|
13558
|
+
(0, import_node_fs38.copyFileSync)(abs, destAbs);
|
|
13384
13559
|
const rel = `${ATTACHMENTS_DIR}/${name}`;
|
|
13385
|
-
const buf = (0,
|
|
13560
|
+
const buf = (0, import_node_fs38.readFileSync)(destAbs);
|
|
13386
13561
|
out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
|
|
13387
13562
|
} catch (err) {
|
|
13388
13563
|
out.push({
|
|
@@ -13395,17 +13570,17 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
|
|
|
13395
13570
|
}
|
|
13396
13571
|
return out;
|
|
13397
13572
|
}
|
|
13398
|
-
function stageBuffersAsAttachments(worktreePath,
|
|
13399
|
-
if (
|
|
13573
|
+
function stageBuffersAsAttachments(worktreePath, buffers2) {
|
|
13574
|
+
if (buffers2.length === 0) return [];
|
|
13400
13575
|
const dir = ensureAttachmentsDir(worktreePath);
|
|
13401
13576
|
const out = [];
|
|
13402
|
-
for (const item of
|
|
13577
|
+
for (const item of buffers2) {
|
|
13403
13578
|
const originalName = (item.name || "file").replace(/[/\\]/g, "_") || "file";
|
|
13404
13579
|
try {
|
|
13405
13580
|
const buf = Buffer.from(item.dataBase64, "base64");
|
|
13406
13581
|
const name = uniqueAttachmentName(dir, originalName);
|
|
13407
13582
|
const destAbs = (0, import_node_path34.join)(dir, name);
|
|
13408
|
-
(0,
|
|
13583
|
+
(0, import_node_fs38.writeFileSync)(destAbs, buf);
|
|
13409
13584
|
const rel = `${ATTACHMENTS_DIR}/${name}`;
|
|
13410
13585
|
out.push(attachmentFromBuffer(name, buf, { path: rel }));
|
|
13411
13586
|
} catch (err) {
|
|
@@ -13434,9 +13609,9 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
|
|
|
13434
13609
|
const name = (0, import_node_path34.basename)(rel);
|
|
13435
13610
|
try {
|
|
13436
13611
|
const abs = (0, import_node_path34.join)(worktreePath, rel);
|
|
13437
|
-
const st = (0,
|
|
13612
|
+
const st = (0, import_node_fs38.statSync)(abs);
|
|
13438
13613
|
if (!st.isFile()) continue;
|
|
13439
|
-
const buf = (0,
|
|
13614
|
+
const buf = (0, import_node_fs38.readFileSync)(abs);
|
|
13440
13615
|
out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
|
|
13441
13616
|
} catch (err) {
|
|
13442
13617
|
out.push({
|
|
@@ -13451,7 +13626,7 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
|
|
|
13451
13626
|
}
|
|
13452
13627
|
|
|
13453
13628
|
// src/agents/instructions.ts
|
|
13454
|
-
var
|
|
13629
|
+
var import_node_fs39 = require("fs");
|
|
13455
13630
|
var import_node_path35 = require("path");
|
|
13456
13631
|
init_git_auth_mode();
|
|
13457
13632
|
init_worktree_labels();
|
|
@@ -13655,6 +13830,15 @@ function isPidAlive(pid) {
|
|
|
13655
13830
|
return false;
|
|
13656
13831
|
}
|
|
13657
13832
|
}
|
|
13833
|
+
async function waitForPidExit(pid, timeoutMs) {
|
|
13834
|
+
const start = Date.now();
|
|
13835
|
+
while (isPidAlive(pid)) {
|
|
13836
|
+
if (Date.now() - start >= timeoutMs) return false;
|
|
13837
|
+
await new Promise((r) => setTimeout(r, 80));
|
|
13838
|
+
}
|
|
13839
|
+
return true;
|
|
13840
|
+
}
|
|
13841
|
+
var STALE_AGENT_PID_WAIT_MS = 2500;
|
|
13658
13842
|
function writeLiveStatus(threadId, status, lastError) {
|
|
13659
13843
|
const latest = readThread(threadId);
|
|
13660
13844
|
if (!latest || latest.status === "archived") return latest;
|
|
@@ -13732,7 +13916,7 @@ var Orchestrator = class {
|
|
|
13732
13916
|
}
|
|
13733
13917
|
continue;
|
|
13734
13918
|
}
|
|
13735
|
-
if (!(0,
|
|
13919
|
+
if (!(0, import_node_fs43.existsSync)(thread.worktreePath)) {
|
|
13736
13920
|
setStatus(thread.id, "broken", "Worktree missing on disk");
|
|
13737
13921
|
this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
|
|
13738
13922
|
continue;
|
|
@@ -14053,14 +14237,10 @@ var Orchestrator = class {
|
|
|
14053
14237
|
const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id);
|
|
14054
14238
|
const livePid = current.agentPid;
|
|
14055
14239
|
const foreignLive = !inFlight && typeof livePid === "number" && livePid > 0 && isPidAlive(livePid);
|
|
14056
|
-
if (inFlight) {
|
|
14240
|
+
if (inFlight || foreignLive) {
|
|
14057
14241
|
this.stop(thread.id, { clearQueue: false, continueQueue: true });
|
|
14058
|
-
} else {
|
|
14059
|
-
if (foreignLive) {
|
|
14060
|
-
this.stop(thread.id, { clearQueue: false, continueQueue: true });
|
|
14061
|
-
}
|
|
14062
|
-
void this.drainQueue(thread.id);
|
|
14063
14242
|
}
|
|
14243
|
+
void this.drainQueue(thread.id);
|
|
14064
14244
|
return this.requireThread(thread.id);
|
|
14065
14245
|
}
|
|
14066
14246
|
async drainQueue(threadId) {
|
|
@@ -14089,7 +14269,18 @@ var Orchestrator = class {
|
|
|
14089
14269
|
}
|
|
14090
14270
|
const livePid = thread.agentPid;
|
|
14091
14271
|
if (typeof livePid === "number" && livePid > 0 && isPidAlive(livePid)) {
|
|
14092
|
-
|
|
14272
|
+
const exited = await waitForPidExit(livePid, STALE_AGENT_PID_WAIT_MS);
|
|
14273
|
+
if (!exited && isPidAlive(livePid)) {
|
|
14274
|
+
try {
|
|
14275
|
+
process.kill(livePid, "SIGKILL");
|
|
14276
|
+
} catch {
|
|
14277
|
+
}
|
|
14278
|
+
await waitForPidExit(livePid, 400);
|
|
14279
|
+
try {
|
|
14280
|
+
updateThread(threadId, { agentPid: null });
|
|
14281
|
+
} catch {
|
|
14282
|
+
}
|
|
14283
|
+
}
|
|
14093
14284
|
continue;
|
|
14094
14285
|
}
|
|
14095
14286
|
const prompt = thread.queue[0];
|
|
@@ -14246,6 +14437,7 @@ var Orchestrator = class {
|
|
|
14246
14437
|
{ cachedPrefix, prompt: agentPrompt },
|
|
14247
14438
|
(event) => {
|
|
14248
14439
|
this.emit({ type: "turn_output", threadId, event });
|
|
14440
|
+
noteTurnLiveEvent(threadId, event);
|
|
14249
14441
|
if (event.type === "session_id") {
|
|
14250
14442
|
updateThread(threadId, { sessionId: event.data });
|
|
14251
14443
|
}
|
|
@@ -14450,6 +14642,7 @@ var Orchestrator = class {
|
|
|
14450
14642
|
this.processes.delete(`${threadId}:agent`);
|
|
14451
14643
|
this.stoppedTurns.delete(threadId);
|
|
14452
14644
|
this.runningCount = Math.max(0, this.runningCount - 1);
|
|
14645
|
+
clearTurnLive(threadId);
|
|
14453
14646
|
try {
|
|
14454
14647
|
updateThread(threadId, { agentPid: null });
|
|
14455
14648
|
} catch {
|
|
@@ -14723,51 +14916,56 @@ var Orchestrator = class {
|
|
|
14723
14916
|
}
|
|
14724
14917
|
return created;
|
|
14725
14918
|
}
|
|
14726
|
-
async waitForTurn(threadRef, timeoutMs = 6e5) {
|
|
14919
|
+
async waitForTurn(threadRef, timeoutMs = 6e5, opts) {
|
|
14727
14920
|
const thread = this.requireThread(threadRef);
|
|
14921
|
+
if (!["running", "queued"].includes(thread.status)) {
|
|
14922
|
+
return thread;
|
|
14923
|
+
}
|
|
14728
14924
|
const start = Date.now();
|
|
14729
14925
|
return new Promise((resolve, reject) => {
|
|
14730
|
-
|
|
14731
|
-
|
|
14732
|
-
|
|
14733
|
-
|
|
14926
|
+
let settled = false;
|
|
14927
|
+
let timer;
|
|
14928
|
+
const finish = (fn) => {
|
|
14929
|
+
if (settled) return;
|
|
14930
|
+
settled = true;
|
|
14931
|
+
if (timer) clearInterval(timer);
|
|
14932
|
+
off();
|
|
14933
|
+
fn();
|
|
14934
|
+
};
|
|
14734
14935
|
const off = this.on((event) => {
|
|
14735
14936
|
if (event.type === "turn_finished" && event.threadId === thread.id) {
|
|
14736
|
-
off();
|
|
14737
14937
|
const latest = readThread(thread.id);
|
|
14738
14938
|
if (!latest) {
|
|
14739
|
-
reject(new Error(`Thread not found: ${thread.id}`));
|
|
14939
|
+
finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
|
|
14740
14940
|
return;
|
|
14741
14941
|
}
|
|
14742
|
-
resolve(latest);
|
|
14942
|
+
finish(() => resolve(latest));
|
|
14743
14943
|
}
|
|
14744
14944
|
if (event.type === "error" && event.threadId === thread.id) {
|
|
14745
|
-
off();
|
|
14746
14945
|
const latest = readThread(thread.id);
|
|
14747
14946
|
if (!latest) {
|
|
14748
|
-
reject(new Error(`Thread not found: ${thread.id}`));
|
|
14947
|
+
finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
|
|
14749
14948
|
return;
|
|
14750
14949
|
}
|
|
14751
|
-
resolve(latest);
|
|
14950
|
+
finish(() => resolve(latest));
|
|
14752
14951
|
}
|
|
14753
14952
|
});
|
|
14754
|
-
|
|
14755
|
-
if (Date.now() - start > timeoutMs) {
|
|
14756
|
-
clearInterval(timer);
|
|
14757
|
-
off();
|
|
14758
|
-
reject(new Error("wait_for_turn timed out"));
|
|
14759
|
-
}
|
|
14953
|
+
timer = setInterval(() => {
|
|
14760
14954
|
const current = readThread(thread.id);
|
|
14761
14955
|
if (!current) {
|
|
14762
|
-
|
|
14763
|
-
off();
|
|
14764
|
-
reject(new Error(`Thread not found: ${thread.id}`));
|
|
14956
|
+
finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
|
|
14765
14957
|
return;
|
|
14766
14958
|
}
|
|
14767
14959
|
if (!["running", "queued"].includes(current.status)) {
|
|
14768
|
-
|
|
14769
|
-
|
|
14770
|
-
|
|
14960
|
+
finish(() => resolve(current));
|
|
14961
|
+
return;
|
|
14962
|
+
}
|
|
14963
|
+
if (Date.now() - start > timeoutMs) {
|
|
14964
|
+
if (opts?.resolveIfStillRunning) {
|
|
14965
|
+
finish(() => resolve(current));
|
|
14966
|
+
} else {
|
|
14967
|
+
finish(() => reject(new Error("wait_for_turn timed out")));
|
|
14968
|
+
}
|
|
14771
14969
|
}
|
|
14772
14970
|
}, 200);
|
|
14773
14971
|
});
|
|
@@ -14777,11 +14975,16 @@ var Orchestrator = class {
|
|
|
14777
14975
|
const lastAgent = [...thread.messages].reverse().find((m) => m.role === "agent");
|
|
14778
14976
|
const lastError = thread.lastError ?? null;
|
|
14779
14977
|
const text3 = (lastAgent?.text ?? "").trim() || (thread.status === "error" ? lastError ?? "" : "");
|
|
14978
|
+
const stillRunning = thread.status === "running" || thread.status === "queued";
|
|
14979
|
+
const live = stillRunning ? readTurnLive(thread.id) : null;
|
|
14780
14980
|
return {
|
|
14781
14981
|
text: text3,
|
|
14782
14982
|
status: thread.status,
|
|
14783
14983
|
sessionId: thread.sessionId,
|
|
14784
|
-
lastError
|
|
14984
|
+
lastError,
|
|
14985
|
+
stillRunning,
|
|
14986
|
+
progress: live?.summary ?? null,
|
|
14987
|
+
lastActivityAt: live?.updatedAt ?? null
|
|
14785
14988
|
};
|
|
14786
14989
|
}
|
|
14787
14990
|
assertNotGlobal(thread, action) {
|
|
@@ -15207,7 +15410,19 @@ var Orchestrator = class {
|
|
|
15207
15410
|
const thread = this.requireThread(threadRef);
|
|
15208
15411
|
return threadsSharingWorktree(thread.worktreePath);
|
|
15209
15412
|
}
|
|
15413
|
+
/**
|
|
15414
|
+
* Last-tab teardown must see siblings already archived. Serialize per
|
|
15415
|
+
* worktree so parallel archive/purge of sibling tabs still removes once.
|
|
15416
|
+
*/
|
|
15417
|
+
enqueueWorktreeTeardown(thread, fn) {
|
|
15418
|
+
const key = isGlobalThread(thread) ? `archive:global:${thread.id}` : `archive:wt:${normalizeWorktreePath(thread.worktreePath)}`;
|
|
15419
|
+
return enqueueByKey(key, fn);
|
|
15420
|
+
}
|
|
15210
15421
|
async archive(threadRef) {
|
|
15422
|
+
const thread = this.requireThread(threadRef);
|
|
15423
|
+
return this.enqueueWorktreeTeardown(thread, () => this.archiveUnlocked(threadRef));
|
|
15424
|
+
}
|
|
15425
|
+
async archiveUnlocked(threadRef) {
|
|
15211
15426
|
const thread = this.requireThread(threadRef);
|
|
15212
15427
|
this.stop(thread.id);
|
|
15213
15428
|
this.releaseOrchestratorCaffeinate(thread);
|
|
@@ -15243,6 +15458,10 @@ var Orchestrator = class {
|
|
|
15243
15458
|
return archived;
|
|
15244
15459
|
}
|
|
15245
15460
|
async purge(threadRef, opts) {
|
|
15461
|
+
const thread = this.requireThread(threadRef);
|
|
15462
|
+
await this.enqueueWorktreeTeardown(thread, () => this.purgeUnlocked(threadRef, opts));
|
|
15463
|
+
}
|
|
15464
|
+
async purgeUnlocked(threadRef, opts) {
|
|
15246
15465
|
const thread = this.requireThread(threadRef);
|
|
15247
15466
|
this.stop(thread.id);
|
|
15248
15467
|
this.releaseOrchestratorCaffeinate(thread);
|
|
@@ -15277,13 +15496,12 @@ var Orchestrator = class {
|
|
|
15277
15496
|
this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
|
|
15278
15497
|
return restored2;
|
|
15279
15498
|
}
|
|
15280
|
-
if (!(0,
|
|
15499
|
+
if (!(0, import_node_fs43.existsSync)(thread.worktreePath)) {
|
|
15281
15500
|
const { createThreadWorktree: createThreadWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
|
|
15282
|
-
const { execa: execa7 } = await import("execa");
|
|
15283
15501
|
const slug = thread.worktreePath.split("/").pop();
|
|
15284
15502
|
const dest = thread.worktreePath;
|
|
15285
|
-
await
|
|
15286
|
-
|
|
15503
|
+
await withRepoGitLock(thread.repoPath, async () => {
|
|
15504
|
+
await git(["worktree", "add", dest, thread.branchName], thread.repoPath);
|
|
15287
15505
|
});
|
|
15288
15506
|
void createThreadWorktree2;
|
|
15289
15507
|
void slug;
|
|
@@ -15847,6 +16065,15 @@ function mcpArchiveBlockedReason(thread) {
|
|
|
15847
16065
|
// src/mcp/server.ts
|
|
15848
16066
|
init_profile();
|
|
15849
16067
|
|
|
16068
|
+
// src/mcp/wait-for-turn.ts
|
|
16069
|
+
var MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
16070
|
+
function mcpWaitForTurnTimeoutMs(requested) {
|
|
16071
|
+
const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
|
|
16072
|
+
if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
|
|
16073
|
+
return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
|
|
16074
|
+
}
|
|
16075
|
+
var MCP_WAIT_STILL_RUNNING_HINT = "Child is still working. Call wait_for_turn again. Do not send a check-in prompt or assume a hang while progress is updating.";
|
|
16076
|
+
|
|
15850
16077
|
// src/mcp/slack-tools.ts
|
|
15851
16078
|
var import_zod = require("zod");
|
|
15852
16079
|
|
|
@@ -16518,7 +16745,9 @@ async function startMcpServer() {
|
|
|
16518
16745
|
const threads = orch.getThreads(true);
|
|
16519
16746
|
const lines = threads.map((t) => {
|
|
16520
16747
|
const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path39.basename)(t.repoPath) || t.repoPath;
|
|
16521
|
-
|
|
16748
|
+
const live = t.status === "running" || t.status === "queued" ? readTurnLive(t.id) : null;
|
|
16749
|
+
const progress = live?.summary ? ` ${live.summary}` : "";
|
|
16750
|
+
return `${t.id.slice(0, 8)} ${t.status.padEnd(9)} ${t.agent.padEnd(8)} ${repo} ${t.sourceType}:${t.sourceRef} ${t.title} sideboard://thread/${t.id}${t.devPort ? ` http://localhost:${t.devPort}` : ""}${progress}`;
|
|
16522
16751
|
});
|
|
16523
16752
|
return {
|
|
16524
16753
|
content: [{ type: "text", text: lines.join("\n") || "(no threads)" }]
|
|
@@ -16527,13 +16756,14 @@ async function startMcpServer() {
|
|
|
16527
16756
|
);
|
|
16528
16757
|
server.tool(
|
|
16529
16758
|
"get_thread",
|
|
16530
|
-
"Get a compact thread summary by id/ref",
|
|
16759
|
+
"Get a compact thread summary by id/ref. While running, includes progress (last tool/thinking) and lastActivityAt.",
|
|
16531
16760
|
{ ref: import_zod3.z.string() },
|
|
16532
16761
|
async ({ ref }) => {
|
|
16533
16762
|
const t = orch.getThread(ref);
|
|
16534
16763
|
if (!t) {
|
|
16535
16764
|
return { content: [{ type: "text", text: `Thread not found: ${ref}` }], isError: true };
|
|
16536
16765
|
}
|
|
16766
|
+
const live = t.status === "running" || t.status === "queued" ? readTurnLive(t.id) : null;
|
|
16537
16767
|
const summary = {
|
|
16538
16768
|
id: t.id,
|
|
16539
16769
|
title: t.title,
|
|
@@ -16548,7 +16778,10 @@ async function startMcpServer() {
|
|
|
16548
16778
|
messageCount: t.messages.length,
|
|
16549
16779
|
devPort: t.devPort,
|
|
16550
16780
|
prUrl: t.prUrl,
|
|
16551
|
-
lastError: t.lastError ?? null
|
|
16781
|
+
lastError: t.lastError ?? null,
|
|
16782
|
+
stillRunning: t.status === "running" || t.status === "queued",
|
|
16783
|
+
progress: live?.summary ?? null,
|
|
16784
|
+
lastActivityAt: live?.updatedAt ?? null
|
|
16552
16785
|
};
|
|
16553
16786
|
return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
|
|
16554
16787
|
}
|
|
@@ -16874,13 +17107,15 @@ async function startMcpServer() {
|
|
|
16874
17107
|
);
|
|
16875
17108
|
server.tool(
|
|
16876
17109
|
"wait_for_turn",
|
|
16877
|
-
"
|
|
17110
|
+
"Wait until the thread finishes its current/queued turn, or return early with a live progress snapshot. MCP clients often kill tools around 60s, so this returns within 45s even while the child is still working. If stillRunning is true, progress is tools/thinking \u2014 call wait_for_turn again. Do not send a check-in prompt or assume a hang. On status error, lastError/text is the failure.",
|
|
16878
17111
|
{
|
|
16879
17112
|
ref: import_zod3.z.string(),
|
|
16880
17113
|
timeoutMs: import_zod3.z.number().optional()
|
|
16881
17114
|
},
|
|
16882
17115
|
async ({ ref, timeoutMs }) => {
|
|
16883
|
-
const thread = await orch.waitForTurn(ref, timeoutMs
|
|
17116
|
+
const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
|
|
17117
|
+
resolveIfStillRunning: true
|
|
17118
|
+
});
|
|
16884
17119
|
const result = orch.getTurnResult(thread.id);
|
|
16885
17120
|
return {
|
|
16886
17121
|
content: [
|
|
@@ -16890,7 +17125,11 @@ async function startMcpServer() {
|
|
|
16890
17125
|
id: thread.id,
|
|
16891
17126
|
status: result.status,
|
|
16892
17127
|
text: result.text,
|
|
16893
|
-
lastError: result.lastError
|
|
17128
|
+
lastError: result.lastError,
|
|
17129
|
+
stillRunning: result.stillRunning,
|
|
17130
|
+
progress: result.progress,
|
|
17131
|
+
lastActivityAt: result.lastActivityAt,
|
|
17132
|
+
hint: result.stillRunning ? MCP_WAIT_STILL_RUNNING_HINT : void 0
|
|
16894
17133
|
})
|
|
16895
17134
|
}
|
|
16896
17135
|
]
|
|
@@ -16899,11 +17138,21 @@ async function startMcpServer() {
|
|
|
16899
17138
|
);
|
|
16900
17139
|
server.tool(
|
|
16901
17140
|
"get_turn_result",
|
|
16902
|
-
"
|
|
17141
|
+
"Assistant message when the turn finished, or live progress while stillRunning. Not the full transcript.",
|
|
16903
17142
|
{ ref: import_zod3.z.string() },
|
|
16904
17143
|
async ({ ref }) => {
|
|
16905
17144
|
const result = orch.getTurnResult(ref);
|
|
16906
|
-
return {
|
|
17145
|
+
return {
|
|
17146
|
+
content: [
|
|
17147
|
+
{
|
|
17148
|
+
type: "text",
|
|
17149
|
+
text: JSON.stringify({
|
|
17150
|
+
...result,
|
|
17151
|
+
hint: result.stillRunning ? MCP_WAIT_STILL_RUNNING_HINT : void 0
|
|
17152
|
+
})
|
|
17153
|
+
}
|
|
17154
|
+
]
|
|
17155
|
+
};
|
|
16907
17156
|
}
|
|
16908
17157
|
);
|
|
16909
17158
|
server.tool(
|
|
@@ -17020,7 +17269,7 @@ async function startMcpServer() {
|
|
|
17020
17269
|
);
|
|
17021
17270
|
server.tool(
|
|
17022
17271
|
"request_review",
|
|
17023
|
-
'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, attaches .sideboard/review.md when present (else local Review request.md / stock template), and sends "Review changes in this workspace." Expect Approve / Approve with nits / Request changes / Needs more information. Pass a worktree thread ref \u2014 not the orchestrator. Then wait_for_turn / get_turn_result on the returned review tab id.',
|
|
17272
|
+
'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, attaches .sideboard/review.md when present (else local Review request.md / stock template), and sends "Review changes in this workspace." Expect Approve / Approve with nits / Request changes / Needs more information. Pass a worktree thread ref \u2014 not the orchestrator. Then wait_for_turn (loop while stillRunning) / get_turn_result on the returned review tab id.',
|
|
17024
17273
|
{ ref: import_zod3.z.string().describe("Worktree thread id/ref to review") },
|
|
17025
17274
|
async ({ ref }) => {
|
|
17026
17275
|
try {
|
|
@@ -17048,7 +17297,7 @@ async function startMcpServer() {
|
|
|
17048
17297
|
);
|
|
17049
17298
|
server.tool(
|
|
17050
17299
|
"ask_git",
|
|
17051
|
-
"Commit & push, open a draft PR, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` if SSH is missing). When dirty, queues the worktree agent to commit; then wait_for_turn. Pass a worktree thread ref (not the orchestrator). action=merge only when the user explicitly asked to merge that PR. Do not run git or gh from the orchestration cwd.",
|
|
17300
|
+
"Commit & push, open a draft PR, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` if SSH is missing). When dirty, queues the worktree agent to commit; then wait_for_turn (loop while stillRunning). Pass a worktree thread ref (not the orchestrator). action=merge only when the user explicitly asked to merge that PR. Do not run git or gh from the orchestration cwd.",
|
|
17052
17301
|
{
|
|
17053
17302
|
ref: import_zod3.z.string().describe("Worktree thread id/ref"),
|
|
17054
17303
|
action: import_zod3.z.enum(AGENT_GIT_ACTIONS).describe(
|
|
@@ -17099,7 +17348,7 @@ async function startMcpServer() {
|
|
|
17099
17348
|
);
|
|
17100
17349
|
server.tool(
|
|
17101
17350
|
"fork_worktree",
|
|
17102
|
-
"Fork a worktree agent chat into a NEW git worktree + chat (desktop \u201CFork to new workspace\u201D). Seeds a transcript (through through_index, default all). Optional agent override. Leave model unset for Auto (default) \u2014 only pass model when you have a reason. Not for the orchestrator. Then send_to_thread / wait_for_turn on the returned id.",
|
|
17351
|
+
"Fork a worktree agent chat into a NEW git worktree + chat (desktop \u201CFork to new workspace\u201D). Seeds a transcript (through through_index, default all). Optional agent override. Leave model unset for Auto (default) \u2014 only pass model when you have a reason. Not for the orchestrator. Then send_to_thread / wait_for_turn (loop while stillRunning) on the returned id.",
|
|
17103
17352
|
{
|
|
17104
17353
|
ref: import_zod3.z.string().describe("Worktree thread id/ref to fork"),
|
|
17105
17354
|
through_index: import_zod3.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
@@ -17144,7 +17393,7 @@ async function startMcpServer() {
|
|
|
17144
17393
|
);
|
|
17145
17394
|
server.tool(
|
|
17146
17395
|
"fork_chat",
|
|
17147
|
-
"Fork a chat into a NEW tab on the SAME workspace: worktree agent \u2192 same worktree tab; Global orchestration chat \u2192 new orchestration chat (same synthetic home). Seeds a transcript; optional agent override. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). Slack / Global orchestrators use this to continue an orchestration chat on another agent after session limits. Then send_to_thread / wait_for_turn on the returned id. Use fork_worktree only for worktree agents that need a new git worktree.",
|
|
17396
|
+
"Fork a chat into a NEW tab on the SAME workspace: worktree agent \u2192 same worktree tab; Global orchestration chat \u2192 new orchestration chat (same synthetic home). Seeds a transcript; optional agent override. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). Slack / Global orchestrators use this to continue an orchestration chat on another agent after session limits. Then send_to_thread / wait_for_turn (loop while stillRunning) on the returned id. Use fork_worktree only for worktree agents that need a new git worktree.",
|
|
17148
17397
|
{
|
|
17149
17398
|
ref: import_zod3.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
17150
17399
|
through_index: import_zod3.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|