@sideboard-ai/core 0.1.99 → 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.cjs +22 -22
- package/dist/agents/cursor-runner.js +2 -2
- package/dist/{agents-ESJKIQQA.js → agents-UT5GCJH3.js} +8 -8
- package/dist/{agents-3MWWWSMF.js → agents-XZBAKBO2.js} +9 -9
- 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-UYQYK2RY.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-XUWDLRAE.js → chunk-ETMRBJKA.js} +11 -11
- package/dist/{chunk-MBP3XG57.js → chunk-H6LP57EF.js} +7 -7
- package/dist/{chunk-FO67IJTY.js → chunk-HHLZWO7U.js} +5 -5
- package/dist/{chunk-GXSYI7FH.js → chunk-HUKCGRAT.js} +209 -59
- package/dist/{chunk-3WA37BRI.js → chunk-KGSVLZV6.js} +11 -4
- package/dist/{chunk-7KDBDMNC.js → chunk-KLBOWJ74.js} +1 -1
- package/dist/{chunk-FYS2BULQ.js → chunk-LANDJIAI.js} +213 -63
- package/dist/{chunk-OANJQTVG.js → chunk-ND6AO7C5.js} +3 -3
- package/dist/{chunk-HI2OTFFR.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-XH2GS2LO.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-OQOOD5ET.js → coordinator-prompt-3DFZRMXE.js} +5 -5
- package/dist/{coordinator-prompt-AKEY4WSO.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-WMF3BJP5.js → global-workspace-KJQS5S2C.js} +6 -6
- package/dist/{global-workspace-RSQXRLT7.js → global-workspace-UR4E3CCG.js} +6 -6
- package/dist/index.cjs +851 -232
- package/dist/index.d.cts +39 -6
- package/dist/index.d.ts +39 -6
- package/dist/index.js +561 -138
- package/dist/mcp/run-stdio.cjs +651 -256
- package/dist/mcp/run-stdio.js +345 -143
- 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-MZVQHRSJ.js → workspaces-NTIVI6DI.js} +7 -7
- package/dist/{workspaces-4ZY4QPWQ.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 +2 -2
package/dist/index.cjs
CHANGED
|
@@ -455,6 +455,7 @@ __export(paths_exports, {
|
|
|
455
455
|
sideboardReposDir: () => sideboardReposDir,
|
|
456
456
|
sideboardWorkspacesDir: () => sideboardWorkspacesDir,
|
|
457
457
|
threadFilePath: () => threadFilePath,
|
|
458
|
+
threadLivePath: () => threadLivePath,
|
|
458
459
|
threadLockPath: () => threadLockPath,
|
|
459
460
|
threadsDir: () => threadsDir,
|
|
460
461
|
worktreesRoot: () => worktreesRoot
|
|
@@ -506,6 +507,9 @@ function worktreesRoot(repoPath) {
|
|
|
506
507
|
function threadFilePath(id) {
|
|
507
508
|
return (0, import_node_path5.join)(threadsDir(), `${id}.json`);
|
|
508
509
|
}
|
|
510
|
+
function threadLivePath(id) {
|
|
511
|
+
return (0, import_node_path5.join)(threadsDir(), `${id}.live.json`);
|
|
512
|
+
}
|
|
509
513
|
function threadLockPath(id) {
|
|
510
514
|
return (0, import_node_path5.join)(locksDir(), `${id}.lock`);
|
|
511
515
|
}
|
|
@@ -2039,8 +2043,11 @@ function writeThread(thread) {
|
|
|
2039
2043
|
function idPath(id) {
|
|
2040
2044
|
return id;
|
|
2041
2045
|
}
|
|
2046
|
+
function isThreadRecordFile(name) {
|
|
2047
|
+
return name.endsWith(".json") && !name.endsWith(".live.json");
|
|
2048
|
+
}
|
|
2042
2049
|
function listThreads(opts) {
|
|
2043
|
-
const files = (0, import_node_fs9.readdirSync)(threadsDir()).filter(
|
|
2050
|
+
const files = (0, import_node_fs9.readdirSync)(threadsDir()).filter(isThreadRecordFile);
|
|
2044
2051
|
const threads = files.map((f) => {
|
|
2045
2052
|
try {
|
|
2046
2053
|
return normalizeThread(
|
|
@@ -2049,7 +2056,9 @@ function listThreads(opts) {
|
|
|
2049
2056
|
} catch {
|
|
2050
2057
|
return null;
|
|
2051
2058
|
}
|
|
2052
|
-
}).filter(
|
|
2059
|
+
}).filter(
|
|
2060
|
+
(t) => t !== null && typeof t.id === "string" && t.id.length > 0
|
|
2061
|
+
).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
2053
2062
|
if (opts?.includeArchived) return threads;
|
|
2054
2063
|
return threads.filter((t) => t.status !== "archived");
|
|
2055
2064
|
}
|
|
@@ -2081,7 +2090,9 @@ function setStatus(id, status, lastError) {
|
|
|
2081
2090
|
}
|
|
2082
2091
|
function findThreadByRef(ref) {
|
|
2083
2092
|
const all = listThreads({ includeArchived: true });
|
|
2084
|
-
return all.find(
|
|
2093
|
+
return all.find(
|
|
2094
|
+
(t) => t.id === ref || t.id.startsWith(ref) || t.branchName === ref || t.title === ref
|
|
2095
|
+
) ?? null;
|
|
2085
2096
|
}
|
|
2086
2097
|
var import_node_crypto3, import_node_fs9, import_proper_lockfile;
|
|
2087
2098
|
var init_thread_store = __esm({
|
|
@@ -2938,7 +2949,7 @@ var init_teams = __esm({
|
|
|
2938
2949
|
|
|
2939
2950
|
// src/git/worktree-labels.ts
|
|
2940
2951
|
function normalizeWorktreePath(worktreePath) {
|
|
2941
|
-
const trimmed = worktreePath.replace(/\/+$/, "") || "/";
|
|
2952
|
+
const trimmed = (worktreePath ?? "").replace(/\/+$/, "") || "/";
|
|
2942
2953
|
const absolute = trimmed.startsWith("/");
|
|
2943
2954
|
const parts = trimmed.split("/").filter((p) => p && p !== ".");
|
|
2944
2955
|
const out = [];
|
|
@@ -3525,6 +3536,42 @@ var init_git_auth_mode = __esm({
|
|
|
3525
3536
|
}
|
|
3526
3537
|
});
|
|
3527
3538
|
|
|
3539
|
+
// src/util/enqueue-by-key.ts
|
|
3540
|
+
function enqueueByKey(key, fn) {
|
|
3541
|
+
const prev = tails.get(key) ?? Promise.resolve();
|
|
3542
|
+
const next = prev.then(fn, fn);
|
|
3543
|
+
tails.set(
|
|
3544
|
+
key,
|
|
3545
|
+
next.then(
|
|
3546
|
+
() => void 0,
|
|
3547
|
+
() => void 0
|
|
3548
|
+
)
|
|
3549
|
+
);
|
|
3550
|
+
return next;
|
|
3551
|
+
}
|
|
3552
|
+
var tails;
|
|
3553
|
+
var init_enqueue_by_key = __esm({
|
|
3554
|
+
"src/util/enqueue-by-key.ts"() {
|
|
3555
|
+
"use strict";
|
|
3556
|
+
tails = /* @__PURE__ */ new Map();
|
|
3557
|
+
}
|
|
3558
|
+
});
|
|
3559
|
+
|
|
3560
|
+
// src/git/repo-git-lock.ts
|
|
3561
|
+
function repoLockKey(repoPath) {
|
|
3562
|
+
const trimmed = repoPath.replace(/\/+$/, "");
|
|
3563
|
+
return `git-worktree:${trimmed || "/"}`;
|
|
3564
|
+
}
|
|
3565
|
+
function withRepoGitLock(repoPath, fn) {
|
|
3566
|
+
return enqueueByKey(repoLockKey(repoPath), fn);
|
|
3567
|
+
}
|
|
3568
|
+
var init_repo_git_lock = __esm({
|
|
3569
|
+
"src/git/repo-git-lock.ts"() {
|
|
3570
|
+
"use strict";
|
|
3571
|
+
init_enqueue_by_key();
|
|
3572
|
+
}
|
|
3573
|
+
});
|
|
3574
|
+
|
|
3528
3575
|
// src/git/pr-gates.ts
|
|
3529
3576
|
function prIsInMergeQueue(gate) {
|
|
3530
3577
|
if (gate.isInMergeQueue) return true;
|
|
@@ -4706,12 +4753,13 @@ async function createThreadWorktree(opts) {
|
|
|
4706
4753
|
`Invalid git reference: ${opts.sourceRef} (and fetch did not resolve it)`
|
|
4707
4754
|
);
|
|
4708
4755
|
}
|
|
4709
|
-
const
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4756
|
+
const added = await withRepoGitLock(opts.repoPath, async () => {
|
|
4757
|
+
const add = await git(
|
|
4758
|
+
["worktree", "add", "-b", branchName, worktreePath, startPoint],
|
|
4759
|
+
opts.repoPath,
|
|
4760
|
+
{ reject: false, timeoutMs: 6e4 }
|
|
4761
|
+
);
|
|
4762
|
+
if (add.exitCode === 0) return branchName;
|
|
4715
4763
|
const err = `${add.stderr}
|
|
4716
4764
|
${add.stdout}`;
|
|
4717
4765
|
if (/already used by worktree|already exists|checked out/i.test(err)) {
|
|
@@ -4722,17 +4770,14 @@ ${add.stdout}`;
|
|
|
4722
4770
|
opts.repoPath,
|
|
4723
4771
|
{ reject: false, timeoutMs: 6e4 }
|
|
4724
4772
|
);
|
|
4725
|
-
if (retry.exitCode === 0)
|
|
4726
|
-
branchName = alt;
|
|
4727
|
-
await ensureGhPreferOrigin(worktreePath);
|
|
4728
|
-
return { branchName, worktreePath };
|
|
4729
|
-
}
|
|
4773
|
+
if (retry.exitCode === 0) return alt;
|
|
4730
4774
|
}
|
|
4731
4775
|
}
|
|
4732
4776
|
throw new Error(
|
|
4733
4777
|
`Failed to create worktree: ${add.stderr.trim() || add.stdout.trim() || `exit ${add.exitCode}`}`
|
|
4734
4778
|
);
|
|
4735
|
-
}
|
|
4779
|
+
});
|
|
4780
|
+
branchName = added;
|
|
4736
4781
|
await ensureGhPreferOrigin(worktreePath);
|
|
4737
4782
|
return { branchName, worktreePath };
|
|
4738
4783
|
}
|
|
@@ -4756,12 +4801,13 @@ async function createExistingBranchWorktree(opts) {
|
|
|
4756
4801
|
);
|
|
4757
4802
|
}
|
|
4758
4803
|
const startPoint = await resolveWorktreeStartPoint(opts.repoPath, branchName);
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4804
|
+
await withRepoGitLock(opts.repoPath, async () => {
|
|
4805
|
+
const add = await git(
|
|
4806
|
+
["worktree", "add", worktreePath, startPoint],
|
|
4807
|
+
opts.repoPath,
|
|
4808
|
+
{ reject: false }
|
|
4809
|
+
);
|
|
4810
|
+
if (add.exitCode === 0) return;
|
|
4765
4811
|
const retry = await git(
|
|
4766
4812
|
["worktree", "add", worktreePath, branchName],
|
|
4767
4813
|
opts.repoPath,
|
|
@@ -4772,7 +4818,7 @@ async function createExistingBranchWorktree(opts) {
|
|
|
4772
4818
|
`Failed to create worktree for ${branchName}: ${retry.stderr.trim() || add.stderr.trim() || retry.stdout.trim() || add.stdout.trim() || `exit ${add.exitCode}`}`
|
|
4773
4819
|
);
|
|
4774
4820
|
}
|
|
4775
|
-
}
|
|
4821
|
+
});
|
|
4776
4822
|
const head = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath, {
|
|
4777
4823
|
reject: false
|
|
4778
4824
|
});
|
|
@@ -4783,12 +4829,14 @@ async function createExistingBranchWorktree(opts) {
|
|
|
4783
4829
|
return { branchName, worktreePath };
|
|
4784
4830
|
}
|
|
4785
4831
|
async function removeWorktree(repoPath, worktreePath, opts) {
|
|
4786
|
-
await
|
|
4787
|
-
|
|
4832
|
+
await withRepoGitLock(repoPath, async () => {
|
|
4833
|
+
await git(["worktree", "remove", "--force", worktreePath], repoPath, {
|
|
4834
|
+
reject: false
|
|
4835
|
+
});
|
|
4836
|
+
if (opts?.deleteBranch) {
|
|
4837
|
+
await git(["branch", "-D", opts.deleteBranch], repoPath, { reject: false });
|
|
4838
|
+
}
|
|
4788
4839
|
});
|
|
4789
|
-
if (opts?.deleteBranch) {
|
|
4790
|
-
await git(["branch", "-D", opts.deleteBranch], repoPath, { reject: false });
|
|
4791
|
-
}
|
|
4792
4840
|
}
|
|
4793
4841
|
async function listWorktrees(repoPath) {
|
|
4794
4842
|
const { stdout } = await git(
|
|
@@ -5080,6 +5128,7 @@ var init_worktree = __esm({
|
|
|
5080
5128
|
init_gh_errors();
|
|
5081
5129
|
init_git_auth_mode();
|
|
5082
5130
|
init_run();
|
|
5131
|
+
init_repo_git_lock();
|
|
5083
5132
|
init_app_settings();
|
|
5084
5133
|
init_pr_gates();
|
|
5085
5134
|
init_stack();
|
|
@@ -5124,7 +5173,7 @@ function coordinatorGreenfieldPlaybook(reposDir) {
|
|
|
5124
5173
|
"- Examples:",
|
|
5125
5174
|
` - Clone: \`git clone <url> ${reposDir}/<name>\``,
|
|
5126
5175
|
` - New GitHub repo: \`gh repo create <owner>/<name> --private --clone -- ${reposDir}/<name>\` (or mkdir + git init + gh repo create + remote add + push)`,
|
|
5127
|
-
"- 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>`).",
|
|
5176
|
+
"- 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>`).",
|
|
5128
5177
|
"- 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`.",
|
|
5129
5178
|
"- Do coding work in the child worktree thread, not by editing files in this home cwd."
|
|
5130
5179
|
].join("\n");
|
|
@@ -5147,8 +5196,8 @@ function coordinatorTurnReminder(opts) {
|
|
|
5147
5196
|
goal ? `- Goal / title: ${goal}` : null,
|
|
5148
5197
|
accountDefaultsPlaybookLine(),
|
|
5149
5198
|
`- 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.`,
|
|
5150
|
-
"- Existing repo: create_thread on a repoPath \u2192 send_to_thread \u2192 wait_for_turn. 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.",
|
|
5151
|
-
"- 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.",
|
|
5199
|
+
"- 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.",
|
|
5200
|
+
"- 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.",
|
|
5152
5201
|
"- When naming threads for the user, link them as `[Title](sideboard://thread/<id>)`.",
|
|
5153
5202
|
"- 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."
|
|
5154
5203
|
].filter(Boolean).join("\n");
|
|
@@ -5201,8 +5250,8 @@ function ensureGlobalCoordinatorCwd(opts) {
|
|
|
5201
5250
|
orchId ? `Pass parentThreadId="${orchId}" (or omit it). Never invent another parentThreadId.` : "Pass `parentThreadId` for children (this chat's id from the turn reminder).",
|
|
5202
5251
|
"Omit `agent` / `model` on `create_thread` unless you have a reason to override Account defaults.",
|
|
5203
5252
|
"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.",
|
|
5204
|
-
"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).",
|
|
5205
|
-
"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.",
|
|
5253
|
+
"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).",
|
|
5254
|
+
"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.",
|
|
5206
5255
|
"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."
|
|
5207
5256
|
].join("\n");
|
|
5208
5257
|
try {
|
|
@@ -5270,7 +5319,7 @@ var init_coordinator_prompt = __esm({
|
|
|
5270
5319
|
"- 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.",
|
|
5271
5320
|
"- 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.",
|
|
5272
5321
|
"- 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",
|
|
5273
|
-
"- wait_for_turn / get_turn_result \u2014 wait for and read the agent reply",
|
|
5322
|
+
"- 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.",
|
|
5274
5323
|
"- stop_thread \u2014 force-stop: kill in-flight turn AND clear queued prompts (do not leave stale queue after an interrupt)",
|
|
5275
5324
|
"- archive_thread / restore_thread \u2014 archive (tears down worktree when last tab) or restore",
|
|
5276
5325
|
"Setup / run:",
|
|
@@ -5278,8 +5327,8 @@ var init_coordinator_prompt = __esm({
|
|
|
5278
5327
|
"- list_run_scripts / run_dev_script / stop_dev_script \u2014 start/stop named run scripts",
|
|
5279
5328
|
"Inspect / review / PRs:",
|
|
5280
5329
|
"- get_diff \u2014 compact diff summary",
|
|
5281
|
-
'- 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',
|
|
5282
|
-
"- 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.",
|
|
5330
|
+
'- 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',
|
|
5331
|
+
"- 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.",
|
|
5283
5332
|
'- 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.',
|
|
5284
5333
|
'- 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.',
|
|
5285
5334
|
"Process guides:",
|
|
@@ -6133,6 +6182,13 @@ function looksLikeMinifiedJsDump(line) {
|
|
|
6133
6182
|
function looksLikeNestedElectronCrash(line) {
|
|
6134
6183
|
return /HasCustomHostObject|ElectronInitializeICUandStartNode/i.test(line);
|
|
6135
6184
|
}
|
|
6185
|
+
function looksLikeHomebrewLibuvCrash(line) {
|
|
6186
|
+
const uvRun = /uv_run/i.test(line);
|
|
6187
|
+
const spin = /SpinEventLoopInternal/i.test(line);
|
|
6188
|
+
const homebrewUv = /Cellar\/libuv|libuv\.\d\.dylib/i.test(line);
|
|
6189
|
+
const homebrewNode = /Cellar\/node\//i.test(line);
|
|
6190
|
+
return uvRun && (homebrewUv || spin || homebrewNode) || spin && (homebrewUv || homebrewNode);
|
|
6191
|
+
}
|
|
6136
6192
|
function clipStderr(text3, maxChars) {
|
|
6137
6193
|
const trimmed = text3.trim();
|
|
6138
6194
|
if (trimmed.length <= maxChars) return trimmed;
|
|
@@ -6143,6 +6199,7 @@ function summarizeTurnStderr(tail, maxChars = 500) {
|
|
|
6143
6199
|
const cursorStartup = [...tail].reverse().find((line) => /cursor startup failed:/i.test(line));
|
|
6144
6200
|
if (cursorStartup) return clipStderr(cursorStartup, maxChars);
|
|
6145
6201
|
if (tail.some(looksLikeNestedElectronCrash)) return NESTED_ELECTRON_SUMMARY;
|
|
6202
|
+
if (tail.some(looksLikeHomebrewLibuvCrash)) return HOMEBREW_LIBUV_SUMMARY;
|
|
6146
6203
|
if (tail.some(
|
|
6147
6204
|
(line) => /\[resource_exhausted\]|resource_exhausted/i.test(line) || /findFilesWithRipgrep/.test(line)
|
|
6148
6205
|
)) {
|
|
@@ -6165,6 +6222,20 @@ function looksLikeInvalidAgentSession(text3) {
|
|
|
6165
6222
|
if (!lower) return false;
|
|
6166
6223
|
return /session not found/.test(lower) || /no conversation found/.test(lower) || /conversation .+ not found/.test(lower) || /thread .+ not found/.test(lower) || /unknown session/.test(lower) || /invalid session/.test(lower) || /session .+ (missing|expired|deleted|gone)/.test(lower) || /cannot resume/.test(lower) || /failed to (load|resume|open) session/.test(lower) || /corrupt local agent checkpoint/.test(lower) || /missing root blob/.test(lower) || /\bagent\b.{0,120}\bnot found\b/.test(lower);
|
|
6167
6224
|
}
|
|
6225
|
+
function looksLikeRetryableRunnerCrash(text3) {
|
|
6226
|
+
if (looksLikeAgentFailureMessage(text3)) return false;
|
|
6227
|
+
if (looksLikeInvalidAgentSession(text3)) return false;
|
|
6228
|
+
const lower = text3.trim().toLowerCase();
|
|
6229
|
+
if (/cannot find (?:package|module)|err_module_not_found/.test(lower)) return false;
|
|
6230
|
+
if (!lower) return true;
|
|
6231
|
+
return /uv_run|spineventloopinternal|libuv|homebrew node \+ shared libuv|hascustomhostobject|electroninitializeicuandstartnode|nested chromium|truncated crash dump|sig(?:segv|abrt|ill)|segmentation fault|illegal instruction|fatal error/.test(
|
|
6232
|
+
lower
|
|
6233
|
+
);
|
|
6234
|
+
}
|
|
6235
|
+
function shouldRetryFailedAgentTurn(detail, opts) {
|
|
6236
|
+
if (looksLikeInvalidAgentSession(detail) && opts.hasSession) return true;
|
|
6237
|
+
return looksLikeRetryableRunnerCrash(detail);
|
|
6238
|
+
}
|
|
6168
6239
|
function looksLikeAgentFailureMessage(text3) {
|
|
6169
6240
|
const lower = text3.trim().toLowerCase();
|
|
6170
6241
|
if (!lower) return false;
|
|
@@ -6209,11 +6280,22 @@ function humanizeAgentFailDetail(detail) {
|
|
|
6209
6280
|
if (/hascustomhostobject|electroninitializeicuandstartnode|nested chromium/i.test(lower)) {
|
|
6210
6281
|
return `${raw} \u2014 retry the turn; if it keeps failing, pick another agent.`;
|
|
6211
6282
|
}
|
|
6283
|
+
if (/homebrew node \+ shared libuv|uv_run|spineventloopinternal/i.test(lower)) {
|
|
6284
|
+
return /brew install node@22/i.test(raw) ? raw : `${raw} \u2014 install Node 22 LTS (\`brew install node@22\`) and retry.`;
|
|
6285
|
+
}
|
|
6212
6286
|
if (/corrupt local agent checkpoint|missing root blob|truncated crash dump/.test(lower)) {
|
|
6213
6287
|
return `${raw} \u2014 retry the turn (Sideboard will start a fresh Cursor session).`;
|
|
6214
6288
|
}
|
|
6215
6289
|
return raw;
|
|
6216
6290
|
}
|
|
6291
|
+
function turnFailChatText(opts) {
|
|
6292
|
+
const chat = opts.assistantText.trim();
|
|
6293
|
+
if (chat) return chat;
|
|
6294
|
+
if (opts.exitCode === 0) return "";
|
|
6295
|
+
const detail = opts.detail.trim();
|
|
6296
|
+
if (detail) return humanizeAgentFailDetail(detail);
|
|
6297
|
+
return formatTurnExitError(opts.exitCode ?? 1, "");
|
|
6298
|
+
}
|
|
6217
6299
|
function formatTurnExitError(exitCode, stderrSummary) {
|
|
6218
6300
|
const code = exitCode ?? 1;
|
|
6219
6301
|
const raw = stderrSummary.trim();
|
|
@@ -6227,12 +6309,13 @@ function formatTurnExitError(exitCode, stderrSummary) {
|
|
|
6227
6309
|
if (looksLikeAgentFailureMessage(raw)) return detail;
|
|
6228
6310
|
return `exit ${code}: ${detail}`;
|
|
6229
6311
|
}
|
|
6230
|
-
var NODE_VERSION_FOOTER, NESTED_ELECTRON_SUMMARY, MINIFIED_DUMP_SUMMARY;
|
|
6312
|
+
var NODE_VERSION_FOOTER, NESTED_ELECTRON_SUMMARY, HOMEBREW_LIBUV_SUMMARY, MINIFIED_DUMP_SUMMARY;
|
|
6231
6313
|
var init_error_detail = __esm({
|
|
6232
6314
|
"src/agents/error-detail.ts"() {
|
|
6233
6315
|
"use strict";
|
|
6234
6316
|
NODE_VERSION_FOOTER = /^Node\.js v\d+/i;
|
|
6235
6317
|
NESTED_ELECTRON_SUMMARY = "Cursor local agent crashed at Electron startup (nested Chromium / HasCustomHostObject)";
|
|
6318
|
+
HOMEBREW_LIBUV_SUMMARY = "Cursor runner crashed in Node (Homebrew Node + shared libuv). Install Node 22 LTS (`brew install node@22`) and retry.";
|
|
6236
6319
|
MINIFIED_DUMP_SUMMARY = "Cursor local agent crashed during startup (truncated crash dump)";
|
|
6237
6320
|
}
|
|
6238
6321
|
});
|
|
@@ -6673,6 +6756,55 @@ var init_profile = __esm({
|
|
|
6673
6756
|
}
|
|
6674
6757
|
});
|
|
6675
6758
|
|
|
6759
|
+
// src/agents/packaged-runtime.ts
|
|
6760
|
+
function electronResourcesPath() {
|
|
6761
|
+
const resources = process.resourcesPath;
|
|
6762
|
+
if (typeof resources !== "string" || !resources) return null;
|
|
6763
|
+
return resources;
|
|
6764
|
+
}
|
|
6765
|
+
function packagedCursorRuntimeDir() {
|
|
6766
|
+
const resources = electronResourcesPath();
|
|
6767
|
+
if (!resources) return null;
|
|
6768
|
+
const dir = (0, import_node_path19.join)(resources, "cursor-runtime");
|
|
6769
|
+
if (!(0, import_node_fs19.existsSync)((0, import_node_path19.join)(dir, "core-dist", "agents", "cursor-runner.js"))) return null;
|
|
6770
|
+
return dir;
|
|
6771
|
+
}
|
|
6772
|
+
function packagedCursorRunnerPath() {
|
|
6773
|
+
const dir = packagedCursorRuntimeDir();
|
|
6774
|
+
return dir ? (0, import_node_path19.join)(dir, "core-dist", "agents", "cursor-runner.js") : null;
|
|
6775
|
+
}
|
|
6776
|
+
function packagedMcpDir() {
|
|
6777
|
+
const resources = electronResourcesPath();
|
|
6778
|
+
if (!resources) return null;
|
|
6779
|
+
const dir = (0, import_node_path19.join)(resources, "sideboard-mcp");
|
|
6780
|
+
if (!(0, import_node_fs19.existsSync)((0, import_node_path19.join)(dir, "core-dist", "mcp", "run-stdio.js"))) return null;
|
|
6781
|
+
return dir;
|
|
6782
|
+
}
|
|
6783
|
+
function packagedMcpStdioPath() {
|
|
6784
|
+
const dir = packagedMcpDir();
|
|
6785
|
+
return dir ? (0, import_node_path19.join)(dir, "core-dist", "mcp", "run-stdio.js") : null;
|
|
6786
|
+
}
|
|
6787
|
+
function packagedBundledNodePath() {
|
|
6788
|
+
const resources = electronResourcesPath();
|
|
6789
|
+
if (!resources) return null;
|
|
6790
|
+
const bin = (0, import_node_path19.join)(resources, "node", "bin", "node");
|
|
6791
|
+
if (!(0, import_node_fs19.existsSync)(bin)) return null;
|
|
6792
|
+
return bin;
|
|
6793
|
+
}
|
|
6794
|
+
function packagedCursorRipgrepCandidate(platformPkg, binName) {
|
|
6795
|
+
const dir = packagedCursorRuntimeDir();
|
|
6796
|
+
if (!dir) return null;
|
|
6797
|
+
return (0, import_node_path19.join)(dir, "node_modules", platformPkg, "bin", binName);
|
|
6798
|
+
}
|
|
6799
|
+
var import_node_fs19, import_node_path19;
|
|
6800
|
+
var init_packaged_runtime = __esm({
|
|
6801
|
+
"src/agents/packaged-runtime.ts"() {
|
|
6802
|
+
"use strict";
|
|
6803
|
+
import_node_fs19 = require("fs");
|
|
6804
|
+
import_node_path19 = require("path");
|
|
6805
|
+
}
|
|
6806
|
+
});
|
|
6807
|
+
|
|
6676
6808
|
// src/agents/node-launch.ts
|
|
6677
6809
|
function isAsarPath(filePath) {
|
|
6678
6810
|
if (/\.asar\.unpacked([/\\]|$)/.test(filePath)) return false;
|
|
@@ -6682,24 +6814,129 @@ function unpackedAsarPath(filePath) {
|
|
|
6682
6814
|
if (!isAsarPath(filePath)) return null;
|
|
6683
6815
|
const unpacked = filePath.replace(/\.asar(?=[/\\])/, ".asar.unpacked");
|
|
6684
6816
|
if (unpacked === filePath) return null;
|
|
6685
|
-
return (0,
|
|
6817
|
+
return (0, import_node_fs20.existsSync)(unpacked) ? unpacked : null;
|
|
6686
6818
|
}
|
|
6687
6819
|
function nodeReadableScriptPath(scriptPath) {
|
|
6688
6820
|
return unpackedAsarPath(scriptPath) ?? scriptPath;
|
|
6689
6821
|
}
|
|
6690
|
-
|
|
6691
|
-
const
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6822
|
+
function parseNodeMajor(version) {
|
|
6823
|
+
const match = /^v?(\d+)/.exec(version.trim());
|
|
6824
|
+
if (!match) return null;
|
|
6825
|
+
const major = Number(match[1]);
|
|
6826
|
+
return Number.isInteger(major) ? major : null;
|
|
6827
|
+
}
|
|
6828
|
+
function scoreNodeForAgentRuntime(candidate) {
|
|
6829
|
+
const major = parseNodeMajor(candidate.version);
|
|
6830
|
+
if (major == null) return Number.NEGATIVE_INFINITY;
|
|
6831
|
+
if (major < 20) return major - 100;
|
|
6832
|
+
const posix = candidate.path.replace(/\\/g, "/");
|
|
6833
|
+
let score = 0;
|
|
6834
|
+
if (major % 2 === 0) {
|
|
6835
|
+
score += 1e3 + major * 10;
|
|
6836
|
+
} else {
|
|
6837
|
+
score += major;
|
|
6838
|
+
}
|
|
6839
|
+
if (/\/Cellar\/node\/\d/.test(posix) && !/\/Cellar\/node@\d+/.test(posix)) {
|
|
6840
|
+
score -= 50;
|
|
6841
|
+
}
|
|
6842
|
+
return score;
|
|
6843
|
+
}
|
|
6844
|
+
function pickPreferredNode(candidates) {
|
|
6845
|
+
let best = null;
|
|
6846
|
+
let bestScore = Number.NEGATIVE_INFINITY;
|
|
6847
|
+
for (const candidate of candidates) {
|
|
6848
|
+
const score = scoreNodeForAgentRuntime(candidate);
|
|
6849
|
+
if (score > bestScore) {
|
|
6850
|
+
bestScore = score;
|
|
6851
|
+
best = candidate;
|
|
6852
|
+
}
|
|
6853
|
+
}
|
|
6854
|
+
return best;
|
|
6855
|
+
}
|
|
6856
|
+
function versionDirNodeBins(root, toBin) {
|
|
6857
|
+
if (!(0, import_node_fs20.existsSync)(root)) return [];
|
|
6858
|
+
try {
|
|
6859
|
+
return (0, import_node_fs20.readdirSync)(root).map(toBin);
|
|
6860
|
+
} catch {
|
|
6861
|
+
return [];
|
|
6862
|
+
}
|
|
6863
|
+
}
|
|
6864
|
+
function defaultNodeBinCandidates(home = (0, import_node_os7.homedir)()) {
|
|
6865
|
+
const kegs = ["/opt/homebrew", "/usr/local"].flatMap(
|
|
6866
|
+
(prefix) => PREFERRED_LTS_MAJORS.map((major) => (0, import_node_path20.join)(prefix, "opt", `node@${major}`, "bin", "node"))
|
|
6867
|
+
);
|
|
6868
|
+
return [
|
|
6869
|
+
...kegs,
|
|
6870
|
+
"/opt/homebrew/bin/node",
|
|
6871
|
+
"/usr/local/bin/node",
|
|
6872
|
+
(0, import_node_path20.join)(home, ".local/share/fnm/aliases/default/bin/node"),
|
|
6873
|
+
(0, import_node_path20.join)(home, ".nvm/current/bin/node"),
|
|
6874
|
+
(0, import_node_path20.join)(home, ".volta/bin/node"),
|
|
6875
|
+
(0, import_node_path20.join)(home, ".asdf/shims/node"),
|
|
6876
|
+
(0, import_node_path20.join)(home, ".local/share/mise/shims/node"),
|
|
6877
|
+
...versionDirNodeBins(
|
|
6878
|
+
(0, import_node_path20.join)(home, ".nvm", "versions", "node"),
|
|
6879
|
+
(name) => (0, import_node_path20.join)(home, ".nvm", "versions", "node", name, "bin", "node")
|
|
6880
|
+
),
|
|
6881
|
+
...versionDirNodeBins(
|
|
6882
|
+
(0, import_node_path20.join)(home, ".local/share/fnm", "node-versions"),
|
|
6883
|
+
(name) => (0, import_node_path20.join)(home, ".local/share/fnm", "node-versions", name, "installation", "bin", "node")
|
|
6884
|
+
),
|
|
6885
|
+
...versionDirNodeBins(
|
|
6886
|
+
(0, import_node_path20.join)(home, ".volta", "tools", "image", "node"),
|
|
6887
|
+
(name) => (0, import_node_path20.join)(home, ".volta", "tools", "image", "node", name, "bin", "node")
|
|
6888
|
+
)
|
|
6698
6889
|
];
|
|
6699
|
-
|
|
6700
|
-
|
|
6890
|
+
}
|
|
6891
|
+
function uniqueExistingNodeBins(paths) {
|
|
6892
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6893
|
+
const out = [];
|
|
6894
|
+
for (const raw of paths) {
|
|
6895
|
+
const p = raw.trim();
|
|
6896
|
+
if (!p || !(0, import_node_fs20.existsSync)(p) || isElectronLikeCommand(p)) continue;
|
|
6897
|
+
let key = p;
|
|
6898
|
+
try {
|
|
6899
|
+
key = (0, import_node_fs20.realpathSync)(p);
|
|
6900
|
+
} catch {
|
|
6901
|
+
continue;
|
|
6902
|
+
}
|
|
6903
|
+
if (seen.has(key)) continue;
|
|
6904
|
+
seen.add(key);
|
|
6905
|
+
out.push(p);
|
|
6701
6906
|
}
|
|
6702
|
-
return
|
|
6907
|
+
return out;
|
|
6908
|
+
}
|
|
6909
|
+
async function whichAllNode() {
|
|
6910
|
+
if (process.platform === "win32") {
|
|
6911
|
+
const located = await run("where", ["node"], { reject: false, timeoutMs: 5e3 });
|
|
6912
|
+
if (located.exitCode !== 0) return [];
|
|
6913
|
+
return located.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
6914
|
+
}
|
|
6915
|
+
const all = await run("which", ["-a", "node"], { reject: false, timeoutMs: 5e3 });
|
|
6916
|
+
if (all.exitCode === 0 && all.stdout.trim()) {
|
|
6917
|
+
return all.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
6918
|
+
}
|
|
6919
|
+
const single = await run("which", ["node"], { reject: false, timeoutMs: 5e3 });
|
|
6920
|
+
if (single.exitCode !== 0 || !single.stdout.trim()) return [];
|
|
6921
|
+
return [single.stdout.trim()];
|
|
6922
|
+
}
|
|
6923
|
+
async function probeNodeVersion(bin) {
|
|
6924
|
+
const probed = await run(bin, ["-v"], { reject: false, timeoutMs: 4e3 });
|
|
6925
|
+
if (probed.exitCode !== 0) return null;
|
|
6926
|
+
const version = probed.stdout.trim().split(/\r?\n/).find(Boolean);
|
|
6927
|
+
return version || null;
|
|
6928
|
+
}
|
|
6929
|
+
async function findSystemNode() {
|
|
6930
|
+
const fromPath = await whichAllNode();
|
|
6931
|
+
const bins = uniqueExistingNodeBins([...fromPath, ...defaultNodeBinCandidates()]);
|
|
6932
|
+
if (bins.length === 0) return null;
|
|
6933
|
+
const probed = await Promise.all(
|
|
6934
|
+
bins.map(async (path2) => {
|
|
6935
|
+
const version = await probeNodeVersion(path2);
|
|
6936
|
+
return version ? { path: path2, version } : null;
|
|
6937
|
+
})
|
|
6938
|
+
);
|
|
6939
|
+
return pickPreferredNode(probed.filter((c) => c != null));
|
|
6703
6940
|
}
|
|
6704
6941
|
function applyNodeLaunch(launch, args) {
|
|
6705
6942
|
const readableArgs = args.map(nodeReadableScriptPath);
|
|
@@ -6717,9 +6954,13 @@ function applyNodeLaunch(launch, args) {
|
|
|
6717
6954
|
async function resolveNodeLaunch(scriptPath) {
|
|
6718
6955
|
const script = nodeReadableScriptPath(scriptPath);
|
|
6719
6956
|
if (!isAsarPath(script)) {
|
|
6720
|
-
const
|
|
6721
|
-
if (
|
|
6722
|
-
return { file:
|
|
6957
|
+
const bundled = packagedBundledNodePath();
|
|
6958
|
+
if (bundled && !isElectronLikeCommand(bundled)) {
|
|
6959
|
+
return { file: bundled, env: {} };
|
|
6960
|
+
}
|
|
6961
|
+
const node = await findSystemNode();
|
|
6962
|
+
if (node) {
|
|
6963
|
+
return { file: node.path, env: {}, nodeVersion: node.version };
|
|
6723
6964
|
}
|
|
6724
6965
|
}
|
|
6725
6966
|
return {
|
|
@@ -6727,61 +6968,17 @@ async function resolveNodeLaunch(scriptPath) {
|
|
|
6727
6968
|
env: { ELECTRON_RUN_AS_NODE: "1" }
|
|
6728
6969
|
};
|
|
6729
6970
|
}
|
|
6730
|
-
var
|
|
6971
|
+
var import_node_fs20, import_node_os7, import_node_path20, PREFERRED_LTS_MAJORS;
|
|
6731
6972
|
var init_node_launch = __esm({
|
|
6732
6973
|
"src/agents/node-launch.ts"() {
|
|
6733
6974
|
"use strict";
|
|
6734
|
-
|
|
6975
|
+
import_node_fs20 = require("fs");
|
|
6735
6976
|
import_node_os7 = require("os");
|
|
6736
|
-
|
|
6977
|
+
import_node_path20 = require("path");
|
|
6737
6978
|
init_nested_electron_env();
|
|
6738
6979
|
init_run();
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
"/usr/local/bin/node"
|
|
6742
|
-
];
|
|
6743
|
-
}
|
|
6744
|
-
});
|
|
6745
|
-
|
|
6746
|
-
// src/agents/packaged-runtime.ts
|
|
6747
|
-
function electronResourcesPath() {
|
|
6748
|
-
const resources = process.resourcesPath;
|
|
6749
|
-
if (typeof resources !== "string" || !resources) return null;
|
|
6750
|
-
return resources;
|
|
6751
|
-
}
|
|
6752
|
-
function packagedCursorRuntimeDir() {
|
|
6753
|
-
const resources = electronResourcesPath();
|
|
6754
|
-
if (!resources) return null;
|
|
6755
|
-
const dir = (0, import_node_path20.join)(resources, "cursor-runtime");
|
|
6756
|
-
if (!(0, import_node_fs20.existsSync)((0, import_node_path20.join)(dir, "core-dist", "agents", "cursor-runner.js"))) return null;
|
|
6757
|
-
return dir;
|
|
6758
|
-
}
|
|
6759
|
-
function packagedCursorRunnerPath() {
|
|
6760
|
-
const dir = packagedCursorRuntimeDir();
|
|
6761
|
-
return dir ? (0, import_node_path20.join)(dir, "core-dist", "agents", "cursor-runner.js") : null;
|
|
6762
|
-
}
|
|
6763
|
-
function packagedMcpDir() {
|
|
6764
|
-
const resources = electronResourcesPath();
|
|
6765
|
-
if (!resources) return null;
|
|
6766
|
-
const dir = (0, import_node_path20.join)(resources, "sideboard-mcp");
|
|
6767
|
-
if (!(0, import_node_fs20.existsSync)((0, import_node_path20.join)(dir, "core-dist", "mcp", "run-stdio.js"))) return null;
|
|
6768
|
-
return dir;
|
|
6769
|
-
}
|
|
6770
|
-
function packagedMcpStdioPath() {
|
|
6771
|
-
const dir = packagedMcpDir();
|
|
6772
|
-
return dir ? (0, import_node_path20.join)(dir, "core-dist", "mcp", "run-stdio.js") : null;
|
|
6773
|
-
}
|
|
6774
|
-
function packagedCursorRipgrepCandidate(platformPkg, binName) {
|
|
6775
|
-
const dir = packagedCursorRuntimeDir();
|
|
6776
|
-
if (!dir) return null;
|
|
6777
|
-
return (0, import_node_path20.join)(dir, "node_modules", platformPkg, "bin", binName);
|
|
6778
|
-
}
|
|
6779
|
-
var import_node_fs20, import_node_path20;
|
|
6780
|
-
var init_packaged_runtime = __esm({
|
|
6781
|
-
"src/agents/packaged-runtime.ts"() {
|
|
6782
|
-
"use strict";
|
|
6783
|
-
import_node_fs20 = require("fs");
|
|
6784
|
-
import_node_path20 = require("path");
|
|
6980
|
+
init_packaged_runtime();
|
|
6981
|
+
PREFERRED_LTS_MAJORS = [24, 22, 20];
|
|
6785
6982
|
}
|
|
6786
6983
|
});
|
|
6787
6984
|
|
|
@@ -9160,17 +9357,17 @@ __export(plan_file_exports, {
|
|
|
9160
9357
|
});
|
|
9161
9358
|
function ensureAttachmentsGitignore2(worktreePath) {
|
|
9162
9359
|
const gitignoreAbs = (0, import_node_path37.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
|
|
9163
|
-
if ((0,
|
|
9164
|
-
(0,
|
|
9165
|
-
(0,
|
|
9360
|
+
if ((0, import_node_fs41.existsSync)(gitignoreAbs)) return;
|
|
9361
|
+
(0, import_node_fs41.mkdirSync)((0, import_node_path37.dirname)(gitignoreAbs), { recursive: true });
|
|
9362
|
+
(0, import_node_fs41.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
|
|
9166
9363
|
}
|
|
9167
9364
|
function planFileAbs(worktreePath) {
|
|
9168
9365
|
return (0, import_node_path37.join)(worktreePath, PLAN_FILE_REL);
|
|
9169
9366
|
}
|
|
9170
9367
|
function readTextIfPresent2(abs) {
|
|
9171
|
-
if (!(0,
|
|
9368
|
+
if (!(0, import_node_fs41.existsSync)(abs)) return null;
|
|
9172
9369
|
try {
|
|
9173
|
-
const content = (0,
|
|
9370
|
+
const content = (0, import_node_fs41.readFileSync)(abs, "utf8");
|
|
9174
9371
|
return content.trim() ? content : null;
|
|
9175
9372
|
} catch {
|
|
9176
9373
|
return null;
|
|
@@ -9182,16 +9379,16 @@ function readPlanFile(worktreePath) {
|
|
|
9182
9379
|
function writePlanFile(worktreePath, content) {
|
|
9183
9380
|
ensureAttachmentsGitignore2(worktreePath);
|
|
9184
9381
|
const abs = planFileAbs(worktreePath);
|
|
9185
|
-
(0,
|
|
9382
|
+
(0, import_node_fs41.mkdirSync)((0, import_node_path37.dirname)(abs), { recursive: true });
|
|
9186
9383
|
const body = content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
|
|
9187
|
-
(0,
|
|
9384
|
+
(0, import_node_fs41.writeFileSync)(abs, body, "utf8");
|
|
9188
9385
|
return PLAN_FILE_REL;
|
|
9189
9386
|
}
|
|
9190
|
-
var
|
|
9387
|
+
var import_node_fs41, import_node_path37;
|
|
9191
9388
|
var init_plan_file = __esm({
|
|
9192
9389
|
"src/plan/plan-file.ts"() {
|
|
9193
9390
|
"use strict";
|
|
9194
|
-
|
|
9391
|
+
import_node_fs41 = require("fs");
|
|
9195
9392
|
import_node_path37 = require("path");
|
|
9196
9393
|
init_workspace_scratch();
|
|
9197
9394
|
init_plan_present();
|
|
@@ -9208,9 +9405,9 @@ function recoverFinishedCursorRun(opts) {
|
|
|
9208
9405
|
const agentId = opts.agentId.trim();
|
|
9209
9406
|
if (!agentId) return null;
|
|
9210
9407
|
const runsPath = (0, import_node_path38.join)(appDataDir(), "cursor-sdk-store", "runs.ndjson");
|
|
9211
|
-
if (!(0,
|
|
9408
|
+
if (!(0, import_node_fs42.existsSync)(runsPath)) return null;
|
|
9212
9409
|
try {
|
|
9213
|
-
const lines = (0,
|
|
9410
|
+
const lines = (0, import_node_fs42.readFileSync)(runsPath, "utf8").split("\n");
|
|
9214
9411
|
let best = null;
|
|
9215
9412
|
for (const line of lines) {
|
|
9216
9413
|
const trimmed = line.trim();
|
|
@@ -9236,11 +9433,11 @@ function recoverFinishedCursorRun(opts) {
|
|
|
9236
9433
|
return null;
|
|
9237
9434
|
}
|
|
9238
9435
|
}
|
|
9239
|
-
var
|
|
9436
|
+
var import_node_fs42, import_node_path38;
|
|
9240
9437
|
var init_cursor_recover = __esm({
|
|
9241
9438
|
"src/agents/cursor-recover.ts"() {
|
|
9242
9439
|
"use strict";
|
|
9243
|
-
|
|
9440
|
+
import_node_fs42 = require("fs");
|
|
9244
9441
|
import_node_path38 = require("path");
|
|
9245
9442
|
init_paths();
|
|
9246
9443
|
}
|
|
@@ -9329,6 +9526,8 @@ __export(index_exports, {
|
|
|
9329
9526
|
SLACK_LISTEN_TIMEOUT_REPLY: () => SLACK_LISTEN_TIMEOUT_REPLY,
|
|
9330
9527
|
SLACK_OAUTH_CANCELLED: () => SLACK_OAUTH_CANCELLED,
|
|
9331
9528
|
SLACK_OAUTH_REDIRECT: () => SLACK_OAUTH_REDIRECT,
|
|
9529
|
+
SLACK_PROGRESS_DELAY_MS: () => SLACK_PROGRESS_DELAY_MS,
|
|
9530
|
+
SLACK_PROGRESS_EDIT_MS: () => SLACK_PROGRESS_EDIT_MS,
|
|
9332
9531
|
SLACK_REPLY_FORMATTING: () => SLACK_REPLY_FORMATTING,
|
|
9333
9532
|
SLACK_SEEN_REACTION: () => SLACK_SEEN_REACTION,
|
|
9334
9533
|
SlackOAuthCancelledError: () => SlackOAuthCancelledError,
|
|
@@ -9486,6 +9685,7 @@ __export(index_exports, {
|
|
|
9486
9685
|
formatSlackInboundPrompt: () => formatSlackInboundPrompt,
|
|
9487
9686
|
formatSlackRepliesForTurn: () => formatSlackRepliesForTurn,
|
|
9488
9687
|
formatSlackSignedReply: () => formatSlackSignedReply,
|
|
9688
|
+
formatSlackWorkingText: () => formatSlackWorkingText,
|
|
9489
9689
|
formatTranscriptMarkdown: () => formatTranscriptMarkdown,
|
|
9490
9690
|
formatUiReminder: () => formatUiReminder,
|
|
9491
9691
|
formatWorkspaceInventory: () => formatWorkspaceInventory,
|
|
@@ -9781,6 +9981,7 @@ __export(index_exports, {
|
|
|
9781
9981
|
thisProcessShouldDrainAgentQueues: () => thisProcessShouldDrainAgentQueues,
|
|
9782
9982
|
threadDisplayLabel: () => threadDisplayLabel,
|
|
9783
9983
|
threadFilePath: () => threadFilePath,
|
|
9984
|
+
threadLivePath: () => threadLivePath,
|
|
9784
9985
|
threadLockPath: () => threadLockPath,
|
|
9785
9986
|
threadRequestsBrightsyMcp: () => threadRequestsBrightsyMcp,
|
|
9786
9987
|
threadsDir: () => threadsDir,
|
|
@@ -9804,6 +10005,7 @@ __export(index_exports, {
|
|
|
9804
10005
|
userClaudeMcpConfigPath: () => userClaudeMcpConfigPath,
|
|
9805
10006
|
userCursorMcpConfigPath: () => userCursorMcpConfigPath,
|
|
9806
10007
|
validateLinearApiKey: () => validateLinearApiKey,
|
|
10008
|
+
waitForPidExit: () => waitForPidExit,
|
|
9807
10009
|
warmGithubAgentAuth: () => warmGithubAgentAuth,
|
|
9808
10010
|
withAgentInstructions: () => withAgentInstructions,
|
|
9809
10011
|
withExportedPath: () => withExportedPath,
|
|
@@ -10874,7 +11076,7 @@ function createAgentStreamCoalescer(emit, opts) {
|
|
|
10874
11076
|
const intervalMs = opts?.intervalMs ?? 32;
|
|
10875
11077
|
let pending = null;
|
|
10876
11078
|
let timer = null;
|
|
10877
|
-
const
|
|
11079
|
+
const flush2 = () => {
|
|
10878
11080
|
if (timer) {
|
|
10879
11081
|
clearTimeout(timer);
|
|
10880
11082
|
timer = null;
|
|
@@ -10886,13 +11088,13 @@ function createAgentStreamCoalescer(emit, opts) {
|
|
|
10886
11088
|
};
|
|
10887
11089
|
const schedule = () => {
|
|
10888
11090
|
if (timer) return;
|
|
10889
|
-
timer = setTimeout(
|
|
11091
|
+
timer = setTimeout(flush2, intervalMs);
|
|
10890
11092
|
timer.unref?.();
|
|
10891
11093
|
};
|
|
10892
11094
|
return {
|
|
10893
11095
|
push(event) {
|
|
10894
11096
|
if (!isTextEvent(event)) {
|
|
10895
|
-
|
|
11097
|
+
flush2();
|
|
10896
11098
|
emit(event);
|
|
10897
11099
|
return;
|
|
10898
11100
|
}
|
|
@@ -10900,12 +11102,12 @@ function createAgentStreamCoalescer(emit, opts) {
|
|
|
10900
11102
|
if (pending && pending.type === event.type) {
|
|
10901
11103
|
pending.data += event.data;
|
|
10902
11104
|
} else {
|
|
10903
|
-
|
|
11105
|
+
flush2();
|
|
10904
11106
|
pending = { type: event.type, data: event.data };
|
|
10905
11107
|
}
|
|
10906
11108
|
schedule();
|
|
10907
11109
|
},
|
|
10908
|
-
flush
|
|
11110
|
+
flush: flush2
|
|
10909
11111
|
};
|
|
10910
11112
|
}
|
|
10911
11113
|
|
|
@@ -12818,11 +13020,11 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
|
|
|
12818
13020
|
}
|
|
12819
13021
|
return out;
|
|
12820
13022
|
}
|
|
12821
|
-
function stageBuffersAsAttachments(worktreePath,
|
|
12822
|
-
if (
|
|
13023
|
+
function stageBuffersAsAttachments(worktreePath, buffers2) {
|
|
13024
|
+
if (buffers2.length === 0) return [];
|
|
12823
13025
|
const dir = ensureAttachmentsDir(worktreePath);
|
|
12824
13026
|
const out = [];
|
|
12825
|
-
for (const item of
|
|
13027
|
+
for (const item of buffers2) {
|
|
12826
13028
|
const originalName = (item.name || "file").replace(/[/\\]/g, "_") || "file";
|
|
12827
13029
|
try {
|
|
12828
13030
|
const buf = Buffer.from(item.dataBase64, "base64");
|
|
@@ -12842,8 +13044,8 @@ function stageBuffersAsAttachments(worktreePath, buffers) {
|
|
|
12842
13044
|
}
|
|
12843
13045
|
return out;
|
|
12844
13046
|
}
|
|
12845
|
-
function attachmentsFromBuffers(
|
|
12846
|
-
return
|
|
13047
|
+
function attachmentsFromBuffers(buffers2) {
|
|
13048
|
+
return buffers2.map((item) => {
|
|
12847
13049
|
const name = (item.name || "file").replace(/[/\\]/g, "_") || "file";
|
|
12848
13050
|
try {
|
|
12849
13051
|
const buf = Buffer.from(item.dataBase64, "base64");
|
|
@@ -13500,8 +13702,11 @@ function worktreeBindingFrom(from) {
|
|
|
13500
13702
|
};
|
|
13501
13703
|
}
|
|
13502
13704
|
function threadsSharingWorktree(worktreePath) {
|
|
13705
|
+
if (!worktreePath) return [];
|
|
13503
13706
|
const key = normalizeWorktreePath(worktreePath);
|
|
13504
|
-
return listThreads({ includeArchived: true }).filter(
|
|
13707
|
+
return listThreads({ includeArchived: true }).filter(
|
|
13708
|
+
(t) => Boolean(t.worktreePath) && sameWorktreePath(t.worktreePath, key) && t.status !== "archived"
|
|
13709
|
+
).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
13505
13710
|
}
|
|
13506
13711
|
function formatTranscriptMarkdown(title, messages) {
|
|
13507
13712
|
const body = formatMessagesAsTranscript(messages, { tools: "full" });
|
|
@@ -14780,7 +14985,7 @@ async function refreshSlackReplyBadges(opts) {
|
|
|
14780
14985
|
}
|
|
14781
14986
|
|
|
14782
14987
|
// src/orchestrator/orchestrator.ts
|
|
14783
|
-
var
|
|
14988
|
+
var import_node_fs43 = require("fs");
|
|
14784
14989
|
init_error_detail();
|
|
14785
14990
|
init_agents();
|
|
14786
14991
|
init_worktree();
|
|
@@ -15040,10 +15245,131 @@ async function cloneRepoIntoSideboard(opts) {
|
|
|
15040
15245
|
// src/orchestrator/orchestrator.ts
|
|
15041
15246
|
init_thread_store();
|
|
15042
15247
|
init_orchestrator_capable();
|
|
15248
|
+
init_enqueue_by_key();
|
|
15249
|
+
init_run();
|
|
15250
|
+
init_repo_git_lock();
|
|
15251
|
+
|
|
15252
|
+
// src/store/turn-live.ts
|
|
15253
|
+
var import_node_fs39 = require("fs");
|
|
15254
|
+
init_paths();
|
|
15255
|
+
var buffers = /* @__PURE__ */ new Map();
|
|
15256
|
+
var FLUSH_MS = 800;
|
|
15257
|
+
var MAX_PARTS = 80;
|
|
15258
|
+
function trimParts(parts) {
|
|
15259
|
+
if (parts.length <= MAX_PARTS) return parts;
|
|
15260
|
+
return parts.slice(-Math.floor(MAX_PARTS / 2));
|
|
15261
|
+
}
|
|
15262
|
+
function lastText(parts, type) {
|
|
15263
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
15264
|
+
const p = parts[i];
|
|
15265
|
+
if (p.type === type && p.text.trim()) return p.text.trim();
|
|
15266
|
+
}
|
|
15267
|
+
return "";
|
|
15268
|
+
}
|
|
15269
|
+
function summarizeTurnLive(parts) {
|
|
15270
|
+
const tools = parts.filter(
|
|
15271
|
+
(p) => p.type === "tool"
|
|
15272
|
+
);
|
|
15273
|
+
const lastTool = tools[tools.length - 1];
|
|
15274
|
+
const lastToolLabel = lastTool ? lastTool.description || toolDescription(lastTool.name, lastTool.input) || lastTool.name : void 0;
|
|
15275
|
+
const running = lastTool?.status === "running";
|
|
15276
|
+
const thinking = lastText(parts, "thinking");
|
|
15277
|
+
const text3 = lastText(parts, "text");
|
|
15278
|
+
const excerptRaw = thinking || text3;
|
|
15279
|
+
const excerpt = excerptRaw.length > 280 ? `${excerptRaw.slice(-280)}` : excerptRaw || void 0;
|
|
15280
|
+
let summary = "Working\u2026";
|
|
15281
|
+
if (lastToolLabel) {
|
|
15282
|
+
const verb = running ? lastToolLabel : `Finished ${lastToolLabel}`;
|
|
15283
|
+
summary = tools.length > 1 ? `${verb} (${tools.length} tools)` : verb;
|
|
15284
|
+
} else if (thinking) {
|
|
15285
|
+
summary = "Thinking\u2026";
|
|
15286
|
+
} else if (text3) {
|
|
15287
|
+
summary = "Writing reply\u2026";
|
|
15288
|
+
}
|
|
15289
|
+
return {
|
|
15290
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15291
|
+
summary,
|
|
15292
|
+
lastTool: lastToolLabel,
|
|
15293
|
+
toolCount: tools.length,
|
|
15294
|
+
excerpt
|
|
15295
|
+
};
|
|
15296
|
+
}
|
|
15297
|
+
function flush(threadId) {
|
|
15298
|
+
const buf = buffers.get(threadId);
|
|
15299
|
+
if (!buf) return;
|
|
15300
|
+
if (buf.timer) {
|
|
15301
|
+
clearTimeout(buf.timer);
|
|
15302
|
+
buf.timer = null;
|
|
15303
|
+
}
|
|
15304
|
+
writeTurnLive(threadId, summarizeTurnLive(buf.parts));
|
|
15305
|
+
}
|
|
15306
|
+
function scheduleFlush(threadId) {
|
|
15307
|
+
const buf = buffers.get(threadId);
|
|
15308
|
+
if (!buf) return;
|
|
15309
|
+
if (buf.timer) return;
|
|
15310
|
+
buf.timer = setTimeout(() => {
|
|
15311
|
+
const live = buffers.get(threadId);
|
|
15312
|
+
if (live) live.timer = null;
|
|
15313
|
+
flush(threadId);
|
|
15314
|
+
}, FLUSH_MS);
|
|
15315
|
+
}
|
|
15316
|
+
function noteTurnLiveEvent(threadId, event) {
|
|
15317
|
+
if (event.type === "session_id" || event.type === "usage" || event.type === "stderr") {
|
|
15318
|
+
return;
|
|
15319
|
+
}
|
|
15320
|
+
const prev = buffers.get(threadId) ?? { parts: [], timer: null };
|
|
15321
|
+
prev.parts = trimParts(applyAgentEvent(prev.parts, event));
|
|
15322
|
+
buffers.set(threadId, prev);
|
|
15323
|
+
if (event.type === "exit") {
|
|
15324
|
+
flush(threadId);
|
|
15325
|
+
const cur = buffers.get(threadId);
|
|
15326
|
+
if (cur?.timer) clearTimeout(cur.timer);
|
|
15327
|
+
buffers.delete(threadId);
|
|
15328
|
+
return;
|
|
15329
|
+
}
|
|
15330
|
+
const immediate = event.type === "tool_use" || event.type === "tool_result";
|
|
15331
|
+
if (immediate) flush(threadId);
|
|
15332
|
+
else scheduleFlush(threadId);
|
|
15333
|
+
}
|
|
15334
|
+
function writeTurnLive(threadId, progress) {
|
|
15335
|
+
const path2 = threadLivePath(threadId);
|
|
15336
|
+
const tmp = `${path2}.${process.pid}.tmp`;
|
|
15337
|
+
try {
|
|
15338
|
+
(0, import_node_fs39.writeFileSync)(tmp, JSON.stringify(progress), "utf8");
|
|
15339
|
+
(0, import_node_fs39.renameSync)(tmp, path2);
|
|
15340
|
+
} catch {
|
|
15341
|
+
try {
|
|
15342
|
+
(0, import_node_fs39.unlinkSync)(tmp);
|
|
15343
|
+
} catch {
|
|
15344
|
+
}
|
|
15345
|
+
}
|
|
15346
|
+
}
|
|
15347
|
+
function readTurnLive(threadId) {
|
|
15348
|
+
const path2 = threadLivePath(threadId);
|
|
15349
|
+
if (!(0, import_node_fs39.existsSync)(path2)) return null;
|
|
15350
|
+
try {
|
|
15351
|
+
const raw = JSON.parse((0, import_node_fs39.readFileSync)(path2, "utf8"));
|
|
15352
|
+
if (!raw || typeof raw.summary !== "string") return null;
|
|
15353
|
+
return raw;
|
|
15354
|
+
} catch {
|
|
15355
|
+
return null;
|
|
15356
|
+
}
|
|
15357
|
+
}
|
|
15358
|
+
function clearTurnLive(threadId) {
|
|
15359
|
+
const buf = buffers.get(threadId);
|
|
15360
|
+
if (buf?.timer) clearTimeout(buf.timer);
|
|
15361
|
+
buffers.delete(threadId);
|
|
15362
|
+
const path2 = threadLivePath(threadId);
|
|
15363
|
+
if (!(0, import_node_fs39.existsSync)(path2)) return;
|
|
15364
|
+
try {
|
|
15365
|
+
(0, import_node_fs39.unlinkSync)(path2);
|
|
15366
|
+
} catch {
|
|
15367
|
+
}
|
|
15368
|
+
}
|
|
15043
15369
|
|
|
15044
15370
|
// src/review/request-review.ts
|
|
15045
15371
|
var import_node_crypto8 = require("crypto");
|
|
15046
|
-
var
|
|
15372
|
+
var import_node_fs40 = require("fs");
|
|
15047
15373
|
var import_node_path36 = require("path");
|
|
15048
15374
|
init_global_workspace();
|
|
15049
15375
|
init_thread_store();
|
|
@@ -15192,9 +15518,9 @@ function shouldRefreshReviewRequestTemplate(content) {
|
|
|
15192
15518
|
return LEGACY_REVIEW_TEMPLATE_MARKERS.every((m) => trimmed.includes(m));
|
|
15193
15519
|
}
|
|
15194
15520
|
function readTextIfPresent(abs) {
|
|
15195
|
-
if (!(0,
|
|
15521
|
+
if (!(0, import_node_fs40.existsSync)(abs)) return null;
|
|
15196
15522
|
try {
|
|
15197
|
-
const content = (0,
|
|
15523
|
+
const content = (0, import_node_fs40.readFileSync)(abs, "utf8");
|
|
15198
15524
|
return content.trim() ? content : null;
|
|
15199
15525
|
} catch {
|
|
15200
15526
|
return null;
|
|
@@ -15202,9 +15528,9 @@ function readTextIfPresent(abs) {
|
|
|
15202
15528
|
}
|
|
15203
15529
|
function ensureAttachmentsGitignore(worktreePath) {
|
|
15204
15530
|
const gitignoreAbs = (0, import_node_path36.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
|
|
15205
|
-
if ((0,
|
|
15206
|
-
(0,
|
|
15207
|
-
(0,
|
|
15531
|
+
if ((0, import_node_fs40.existsSync)(gitignoreAbs)) return;
|
|
15532
|
+
(0, import_node_fs40.mkdirSync)((0, import_node_path36.dirname)(gitignoreAbs), { recursive: true });
|
|
15533
|
+
(0, import_node_fs40.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
|
|
15208
15534
|
}
|
|
15209
15535
|
function resolveReviewGuidelines(worktreePath) {
|
|
15210
15536
|
const repoAbs = (0, import_node_path36.join)(worktreePath, REPO_REVIEW_PATH);
|
|
@@ -15238,8 +15564,8 @@ function resolveReviewGuidelines(worktreePath) {
|
|
|
15238
15564
|
};
|
|
15239
15565
|
}
|
|
15240
15566
|
ensureAttachmentsGitignore(worktreePath);
|
|
15241
|
-
(0,
|
|
15242
|
-
(0,
|
|
15567
|
+
(0, import_node_fs40.mkdirSync)((0, import_node_path36.dirname)(localAbs), { recursive: true });
|
|
15568
|
+
(0, import_node_fs40.writeFileSync)(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
|
|
15243
15569
|
return {
|
|
15244
15570
|
path: REVIEW_REQUEST_PATH,
|
|
15245
15571
|
name: REVIEW_REQUEST_NAME,
|
|
@@ -15261,7 +15587,7 @@ function ensureReviewRequestFile(worktreePath) {
|
|
|
15261
15587
|
const localAbs = (0, import_node_path36.join)(worktreePath, REVIEW_REQUEST_PATH);
|
|
15262
15588
|
const localContent = readTextIfPresent(localAbs) ?? readTextIfPresent((0, import_node_path36.join)(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
|
|
15263
15589
|
if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
|
|
15264
|
-
const path2 = (0,
|
|
15590
|
+
const path2 = (0, import_node_fs40.existsSync)(localAbs) ? REVIEW_REQUEST_PATH : LEGACY_REVIEW_REQUEST_PATH;
|
|
15265
15591
|
return {
|
|
15266
15592
|
path: path2,
|
|
15267
15593
|
name: REVIEW_REQUEST_NAME,
|
|
@@ -15269,8 +15595,8 @@ function ensureReviewRequestFile(worktreePath) {
|
|
|
15269
15595
|
source: "local"
|
|
15270
15596
|
};
|
|
15271
15597
|
}
|
|
15272
|
-
(0,
|
|
15273
|
-
(0,
|
|
15598
|
+
(0, import_node_fs40.mkdirSync)((0, import_node_path36.dirname)(repoAbs), { recursive: true });
|
|
15599
|
+
(0, import_node_fs40.writeFileSync)(repoAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
|
|
15274
15600
|
return {
|
|
15275
15601
|
path: REPO_REVIEW_PATH,
|
|
15276
15602
|
name: REPO_REVIEW_NAME,
|
|
@@ -15479,6 +15805,15 @@ function isPidAlive(pid) {
|
|
|
15479
15805
|
return false;
|
|
15480
15806
|
}
|
|
15481
15807
|
}
|
|
15808
|
+
async function waitForPidExit(pid, timeoutMs) {
|
|
15809
|
+
const start = Date.now();
|
|
15810
|
+
while (isPidAlive(pid)) {
|
|
15811
|
+
if (Date.now() - start >= timeoutMs) return false;
|
|
15812
|
+
await new Promise((r) => setTimeout(r, 80));
|
|
15813
|
+
}
|
|
15814
|
+
return true;
|
|
15815
|
+
}
|
|
15816
|
+
var STALE_AGENT_PID_WAIT_MS = 2500;
|
|
15482
15817
|
function writeLiveStatus(threadId, status, lastError) {
|
|
15483
15818
|
const latest = readThread(threadId);
|
|
15484
15819
|
if (!latest || latest.status === "archived") return latest;
|
|
@@ -15556,7 +15891,7 @@ var Orchestrator = class {
|
|
|
15556
15891
|
}
|
|
15557
15892
|
continue;
|
|
15558
15893
|
}
|
|
15559
|
-
if (!(0,
|
|
15894
|
+
if (!(0, import_node_fs43.existsSync)(thread.worktreePath)) {
|
|
15560
15895
|
setStatus(thread.id, "broken", "Worktree missing on disk");
|
|
15561
15896
|
this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
|
|
15562
15897
|
continue;
|
|
@@ -15877,14 +16212,10 @@ var Orchestrator = class {
|
|
|
15877
16212
|
const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id);
|
|
15878
16213
|
const livePid = current.agentPid;
|
|
15879
16214
|
const foreignLive = !inFlight && typeof livePid === "number" && livePid > 0 && isPidAlive(livePid);
|
|
15880
|
-
if (inFlight) {
|
|
16215
|
+
if (inFlight || foreignLive) {
|
|
15881
16216
|
this.stop(thread.id, { clearQueue: false, continueQueue: true });
|
|
15882
|
-
} else {
|
|
15883
|
-
if (foreignLive) {
|
|
15884
|
-
this.stop(thread.id, { clearQueue: false, continueQueue: true });
|
|
15885
|
-
}
|
|
15886
|
-
void this.drainQueue(thread.id);
|
|
15887
16217
|
}
|
|
16218
|
+
void this.drainQueue(thread.id);
|
|
15888
16219
|
return this.requireThread(thread.id);
|
|
15889
16220
|
}
|
|
15890
16221
|
async drainQueue(threadId) {
|
|
@@ -15913,7 +16244,18 @@ var Orchestrator = class {
|
|
|
15913
16244
|
}
|
|
15914
16245
|
const livePid = thread.agentPid;
|
|
15915
16246
|
if (typeof livePid === "number" && livePid > 0 && isPidAlive(livePid)) {
|
|
15916
|
-
|
|
16247
|
+
const exited = await waitForPidExit(livePid, STALE_AGENT_PID_WAIT_MS);
|
|
16248
|
+
if (!exited && isPidAlive(livePid)) {
|
|
16249
|
+
try {
|
|
16250
|
+
process.kill(livePid, "SIGKILL");
|
|
16251
|
+
} catch {
|
|
16252
|
+
}
|
|
16253
|
+
await waitForPidExit(livePid, 400);
|
|
16254
|
+
try {
|
|
16255
|
+
updateThread(threadId, { agentPid: null });
|
|
16256
|
+
} catch {
|
|
16257
|
+
}
|
|
16258
|
+
}
|
|
15917
16259
|
continue;
|
|
15918
16260
|
}
|
|
15919
16261
|
const prompt = thread.queue[0];
|
|
@@ -16070,6 +16412,7 @@ var Orchestrator = class {
|
|
|
16070
16412
|
{ cachedPrefix, prompt: agentPrompt },
|
|
16071
16413
|
(event) => {
|
|
16072
16414
|
this.emit({ type: "turn_output", threadId, event });
|
|
16415
|
+
noteTurnLiveEvent(threadId, event);
|
|
16073
16416
|
if (event.type === "session_id") {
|
|
16074
16417
|
updateThread(threadId, { sessionId: event.data });
|
|
16075
16418
|
}
|
|
@@ -16133,19 +16476,16 @@ var Orchestrator = class {
|
|
|
16133
16476
|
}
|
|
16134
16477
|
let lastStderr = summarizeTurnStderr(stderrTail);
|
|
16135
16478
|
let detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
|
|
16136
|
-
if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) &&
|
|
16479
|
+
if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && this.requireThread(threadId).agent !== "brightsy" && shouldRetryFailedAgentTurn(detail, {
|
|
16480
|
+
hasSession: Boolean(this.requireThread(threadId).sessionId)
|
|
16481
|
+
})) {
|
|
16137
16482
|
updateThread(threadId, { sessionId: null });
|
|
16138
|
-
|
|
16139
|
-
|
|
16140
|
-
"Agent session missing \u2014 starting a fresh session"
|
|
16141
|
-
);
|
|
16483
|
+
const retryNote = looksLikeInvalidAgentSession(detail) ? "Agent session missing \u2014 starting a fresh session" : "Agent runner crashed \u2014 restarting Node once";
|
|
16484
|
+
pushTurnStderr(stderrTail, retryNote);
|
|
16142
16485
|
this.emit({
|
|
16143
16486
|
type: "turn_output",
|
|
16144
16487
|
threadId,
|
|
16145
|
-
event: {
|
|
16146
|
-
type: "stderr",
|
|
16147
|
-
data: "Agent session missing \u2014 starting a fresh session"
|
|
16148
|
-
}
|
|
16488
|
+
event: { type: "stderr", data: retryNote }
|
|
16149
16489
|
});
|
|
16150
16490
|
const retryThread = this.requireThread(threadId);
|
|
16151
16491
|
const prior = retryThread.messages.slice(0, -1);
|
|
@@ -16194,10 +16534,7 @@ var Orchestrator = class {
|
|
|
16194
16534
|
lastStderr = summarizeTurnStderr(stderrTail);
|
|
16195
16535
|
detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
|
|
16196
16536
|
}
|
|
16197
|
-
let chatText = assistantText;
|
|
16198
|
-
if (exitCode !== 0 && !chatText && looksLikeAgentFailureMessage(detail)) {
|
|
16199
|
-
chatText = humanizeAgentFailDetail(detail);
|
|
16200
|
-
}
|
|
16537
|
+
let chatText = this.stoppedTurns.has(threadId) ? assistantText : turnFailChatText({ exitCode, assistantText, detail });
|
|
16201
16538
|
if (chatText || parts.length > 0) {
|
|
16202
16539
|
appendMessage(threadId, {
|
|
16203
16540
|
role: "agent",
|
|
@@ -16280,6 +16617,7 @@ var Orchestrator = class {
|
|
|
16280
16617
|
this.processes.delete(`${threadId}:agent`);
|
|
16281
16618
|
this.stoppedTurns.delete(threadId);
|
|
16282
16619
|
this.runningCount = Math.max(0, this.runningCount - 1);
|
|
16620
|
+
clearTurnLive(threadId);
|
|
16283
16621
|
try {
|
|
16284
16622
|
updateThread(threadId, { agentPid: null });
|
|
16285
16623
|
} catch {
|
|
@@ -16553,51 +16891,56 @@ var Orchestrator = class {
|
|
|
16553
16891
|
}
|
|
16554
16892
|
return created;
|
|
16555
16893
|
}
|
|
16556
|
-
async waitForTurn(threadRef, timeoutMs = 6e5) {
|
|
16894
|
+
async waitForTurn(threadRef, timeoutMs = 6e5, opts) {
|
|
16557
16895
|
const thread = this.requireThread(threadRef);
|
|
16896
|
+
if (!["running", "queued"].includes(thread.status)) {
|
|
16897
|
+
return thread;
|
|
16898
|
+
}
|
|
16558
16899
|
const start = Date.now();
|
|
16559
16900
|
return new Promise((resolve, reject) => {
|
|
16560
|
-
|
|
16561
|
-
|
|
16562
|
-
|
|
16563
|
-
|
|
16901
|
+
let settled = false;
|
|
16902
|
+
let timer;
|
|
16903
|
+
const finish = (fn) => {
|
|
16904
|
+
if (settled) return;
|
|
16905
|
+
settled = true;
|
|
16906
|
+
if (timer) clearInterval(timer);
|
|
16907
|
+
off();
|
|
16908
|
+
fn();
|
|
16909
|
+
};
|
|
16564
16910
|
const off = this.on((event) => {
|
|
16565
16911
|
if (event.type === "turn_finished" && event.threadId === thread.id) {
|
|
16566
|
-
off();
|
|
16567
16912
|
const latest = readThread(thread.id);
|
|
16568
16913
|
if (!latest) {
|
|
16569
|
-
reject(new Error(`Thread not found: ${thread.id}`));
|
|
16914
|
+
finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
|
|
16570
16915
|
return;
|
|
16571
16916
|
}
|
|
16572
|
-
resolve(latest);
|
|
16917
|
+
finish(() => resolve(latest));
|
|
16573
16918
|
}
|
|
16574
16919
|
if (event.type === "error" && event.threadId === thread.id) {
|
|
16575
|
-
off();
|
|
16576
16920
|
const latest = readThread(thread.id);
|
|
16577
16921
|
if (!latest) {
|
|
16578
|
-
reject(new Error(`Thread not found: ${thread.id}`));
|
|
16922
|
+
finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
|
|
16579
16923
|
return;
|
|
16580
16924
|
}
|
|
16581
|
-
resolve(latest);
|
|
16925
|
+
finish(() => resolve(latest));
|
|
16582
16926
|
}
|
|
16583
16927
|
});
|
|
16584
|
-
|
|
16585
|
-
if (Date.now() - start > timeoutMs) {
|
|
16586
|
-
clearInterval(timer);
|
|
16587
|
-
off();
|
|
16588
|
-
reject(new Error("wait_for_turn timed out"));
|
|
16589
|
-
}
|
|
16928
|
+
timer = setInterval(() => {
|
|
16590
16929
|
const current = readThread(thread.id);
|
|
16591
16930
|
if (!current) {
|
|
16592
|
-
|
|
16593
|
-
off();
|
|
16594
|
-
reject(new Error(`Thread not found: ${thread.id}`));
|
|
16931
|
+
finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
|
|
16595
16932
|
return;
|
|
16596
16933
|
}
|
|
16597
16934
|
if (!["running", "queued"].includes(current.status)) {
|
|
16598
|
-
|
|
16599
|
-
|
|
16600
|
-
|
|
16935
|
+
finish(() => resolve(current));
|
|
16936
|
+
return;
|
|
16937
|
+
}
|
|
16938
|
+
if (Date.now() - start > timeoutMs) {
|
|
16939
|
+
if (opts?.resolveIfStillRunning) {
|
|
16940
|
+
finish(() => resolve(current));
|
|
16941
|
+
} else {
|
|
16942
|
+
finish(() => reject(new Error("wait_for_turn timed out")));
|
|
16943
|
+
}
|
|
16601
16944
|
}
|
|
16602
16945
|
}, 200);
|
|
16603
16946
|
});
|
|
@@ -16605,10 +16948,18 @@ var Orchestrator = class {
|
|
|
16605
16948
|
getTurnResult(threadRef) {
|
|
16606
16949
|
const thread = this.requireThread(threadRef);
|
|
16607
16950
|
const lastAgent = [...thread.messages].reverse().find((m) => m.role === "agent");
|
|
16951
|
+
const lastError = thread.lastError ?? null;
|
|
16952
|
+
const text3 = (lastAgent?.text ?? "").trim() || (thread.status === "error" ? lastError ?? "" : "");
|
|
16953
|
+
const stillRunning = thread.status === "running" || thread.status === "queued";
|
|
16954
|
+
const live = stillRunning ? readTurnLive(thread.id) : null;
|
|
16608
16955
|
return {
|
|
16609
|
-
text:
|
|
16956
|
+
text: text3,
|
|
16610
16957
|
status: thread.status,
|
|
16611
|
-
sessionId: thread.sessionId
|
|
16958
|
+
sessionId: thread.sessionId,
|
|
16959
|
+
lastError,
|
|
16960
|
+
stillRunning,
|
|
16961
|
+
progress: live?.summary ?? null,
|
|
16962
|
+
lastActivityAt: live?.updatedAt ?? null
|
|
16612
16963
|
};
|
|
16613
16964
|
}
|
|
16614
16965
|
assertNotGlobal(thread, action) {
|
|
@@ -17034,7 +17385,19 @@ var Orchestrator = class {
|
|
|
17034
17385
|
const thread = this.requireThread(threadRef);
|
|
17035
17386
|
return threadsSharingWorktree(thread.worktreePath);
|
|
17036
17387
|
}
|
|
17388
|
+
/**
|
|
17389
|
+
* Last-tab teardown must see siblings already archived. Serialize per
|
|
17390
|
+
* worktree so parallel archive/purge of sibling tabs still removes once.
|
|
17391
|
+
*/
|
|
17392
|
+
enqueueWorktreeTeardown(thread, fn) {
|
|
17393
|
+
const key = isGlobalThread(thread) ? `archive:global:${thread.id}` : `archive:wt:${normalizeWorktreePath(thread.worktreePath)}`;
|
|
17394
|
+
return enqueueByKey(key, fn);
|
|
17395
|
+
}
|
|
17037
17396
|
async archive(threadRef) {
|
|
17397
|
+
const thread = this.requireThread(threadRef);
|
|
17398
|
+
return this.enqueueWorktreeTeardown(thread, () => this.archiveUnlocked(threadRef));
|
|
17399
|
+
}
|
|
17400
|
+
async archiveUnlocked(threadRef) {
|
|
17038
17401
|
const thread = this.requireThread(threadRef);
|
|
17039
17402
|
this.stop(thread.id);
|
|
17040
17403
|
this.releaseOrchestratorCaffeinate(thread);
|
|
@@ -17070,6 +17433,10 @@ var Orchestrator = class {
|
|
|
17070
17433
|
return archived;
|
|
17071
17434
|
}
|
|
17072
17435
|
async purge(threadRef, opts) {
|
|
17436
|
+
const thread = this.requireThread(threadRef);
|
|
17437
|
+
await this.enqueueWorktreeTeardown(thread, () => this.purgeUnlocked(threadRef, opts));
|
|
17438
|
+
}
|
|
17439
|
+
async purgeUnlocked(threadRef, opts) {
|
|
17073
17440
|
const thread = this.requireThread(threadRef);
|
|
17074
17441
|
this.stop(thread.id);
|
|
17075
17442
|
this.releaseOrchestratorCaffeinate(thread);
|
|
@@ -17104,13 +17471,12 @@ var Orchestrator = class {
|
|
|
17104
17471
|
this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
|
|
17105
17472
|
return restored2;
|
|
17106
17473
|
}
|
|
17107
|
-
if (!(0,
|
|
17474
|
+
if (!(0, import_node_fs43.existsSync)(thread.worktreePath)) {
|
|
17108
17475
|
const { createThreadWorktree: createThreadWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
|
|
17109
|
-
const { execa: execa7 } = await import("execa");
|
|
17110
17476
|
const slug = thread.worktreePath.split("/").pop();
|
|
17111
17477
|
const dest = thread.worktreePath;
|
|
17112
|
-
await
|
|
17113
|
-
|
|
17478
|
+
await withRepoGitLock(thread.repoPath, async () => {
|
|
17479
|
+
await git(["worktree", "add", dest, thread.branchName], thread.repoPath);
|
|
17114
17480
|
});
|
|
17115
17481
|
void createThreadWorktree2;
|
|
17116
17482
|
void slug;
|
|
@@ -17362,6 +17728,15 @@ function mcpArchiveBlockedReason(thread) {
|
|
|
17362
17728
|
// src/mcp/server.ts
|
|
17363
17729
|
init_profile();
|
|
17364
17730
|
|
|
17731
|
+
// src/mcp/wait-for-turn.ts
|
|
17732
|
+
var MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
17733
|
+
function mcpWaitForTurnTimeoutMs(requested) {
|
|
17734
|
+
const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
|
|
17735
|
+
if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
|
|
17736
|
+
return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
|
|
17737
|
+
}
|
|
17738
|
+
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.";
|
|
17739
|
+
|
|
17365
17740
|
// src/mcp/slack-tools.ts
|
|
17366
17741
|
var import_zod = require("zod");
|
|
17367
17742
|
|
|
@@ -18033,7 +18408,9 @@ async function startMcpServer() {
|
|
|
18033
18408
|
const threads = orch.getThreads(true);
|
|
18034
18409
|
const lines = threads.map((t) => {
|
|
18035
18410
|
const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path39.basename)(t.repoPath) || t.repoPath;
|
|
18036
|
-
|
|
18411
|
+
const live = t.status === "running" || t.status === "queued" ? readTurnLive(t.id) : null;
|
|
18412
|
+
const progress = live?.summary ? ` ${live.summary}` : "";
|
|
18413
|
+
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}`;
|
|
18037
18414
|
});
|
|
18038
18415
|
return {
|
|
18039
18416
|
content: [{ type: "text", text: lines.join("\n") || "(no threads)" }]
|
|
@@ -18042,13 +18419,14 @@ async function startMcpServer() {
|
|
|
18042
18419
|
);
|
|
18043
18420
|
server.tool(
|
|
18044
18421
|
"get_thread",
|
|
18045
|
-
"Get a compact thread summary by id/ref",
|
|
18422
|
+
"Get a compact thread summary by id/ref. While running, includes progress (last tool/thinking) and lastActivityAt.",
|
|
18046
18423
|
{ ref: import_zod3.z.string() },
|
|
18047
18424
|
async ({ ref }) => {
|
|
18048
18425
|
const t = orch.getThread(ref);
|
|
18049
18426
|
if (!t) {
|
|
18050
18427
|
return { content: [{ type: "text", text: `Thread not found: ${ref}` }], isError: true };
|
|
18051
18428
|
}
|
|
18429
|
+
const live = t.status === "running" || t.status === "queued" ? readTurnLive(t.id) : null;
|
|
18052
18430
|
const summary = {
|
|
18053
18431
|
id: t.id,
|
|
18054
18432
|
title: t.title,
|
|
@@ -18063,7 +18441,10 @@ async function startMcpServer() {
|
|
|
18063
18441
|
messageCount: t.messages.length,
|
|
18064
18442
|
devPort: t.devPort,
|
|
18065
18443
|
prUrl: t.prUrl,
|
|
18066
|
-
lastError: t.lastError ?? null
|
|
18444
|
+
lastError: t.lastError ?? null,
|
|
18445
|
+
stillRunning: t.status === "running" || t.status === "queued",
|
|
18446
|
+
progress: live?.summary ?? null,
|
|
18447
|
+
lastActivityAt: live?.updatedAt ?? null
|
|
18067
18448
|
};
|
|
18068
18449
|
return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
|
|
18069
18450
|
}
|
|
@@ -18389,13 +18770,15 @@ async function startMcpServer() {
|
|
|
18389
18770
|
);
|
|
18390
18771
|
server.tool(
|
|
18391
18772
|
"wait_for_turn",
|
|
18392
|
-
"
|
|
18773
|
+
"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.",
|
|
18393
18774
|
{
|
|
18394
18775
|
ref: import_zod3.z.string(),
|
|
18395
18776
|
timeoutMs: import_zod3.z.number().optional()
|
|
18396
18777
|
},
|
|
18397
18778
|
async ({ ref, timeoutMs }) => {
|
|
18398
|
-
const thread = await orch.waitForTurn(ref, timeoutMs
|
|
18779
|
+
const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
|
|
18780
|
+
resolveIfStillRunning: true
|
|
18781
|
+
});
|
|
18399
18782
|
const result = orch.getTurnResult(thread.id);
|
|
18400
18783
|
return {
|
|
18401
18784
|
content: [
|
|
@@ -18404,7 +18787,12 @@ async function startMcpServer() {
|
|
|
18404
18787
|
text: JSON.stringify({
|
|
18405
18788
|
id: thread.id,
|
|
18406
18789
|
status: result.status,
|
|
18407
|
-
text: result.text
|
|
18790
|
+
text: result.text,
|
|
18791
|
+
lastError: result.lastError,
|
|
18792
|
+
stillRunning: result.stillRunning,
|
|
18793
|
+
progress: result.progress,
|
|
18794
|
+
lastActivityAt: result.lastActivityAt,
|
|
18795
|
+
hint: result.stillRunning ? MCP_WAIT_STILL_RUNNING_HINT : void 0
|
|
18408
18796
|
})
|
|
18409
18797
|
}
|
|
18410
18798
|
]
|
|
@@ -18413,11 +18801,21 @@ async function startMcpServer() {
|
|
|
18413
18801
|
);
|
|
18414
18802
|
server.tool(
|
|
18415
18803
|
"get_turn_result",
|
|
18416
|
-
"
|
|
18804
|
+
"Assistant message when the turn finished, or live progress while stillRunning. Not the full transcript.",
|
|
18417
18805
|
{ ref: import_zod3.z.string() },
|
|
18418
18806
|
async ({ ref }) => {
|
|
18419
18807
|
const result = orch.getTurnResult(ref);
|
|
18420
|
-
return {
|
|
18808
|
+
return {
|
|
18809
|
+
content: [
|
|
18810
|
+
{
|
|
18811
|
+
type: "text",
|
|
18812
|
+
text: JSON.stringify({
|
|
18813
|
+
...result,
|
|
18814
|
+
hint: result.stillRunning ? MCP_WAIT_STILL_RUNNING_HINT : void 0
|
|
18815
|
+
})
|
|
18816
|
+
}
|
|
18817
|
+
]
|
|
18818
|
+
};
|
|
18421
18819
|
}
|
|
18422
18820
|
);
|
|
18423
18821
|
server.tool(
|
|
@@ -18534,7 +18932,7 @@ async function startMcpServer() {
|
|
|
18534
18932
|
);
|
|
18535
18933
|
server.tool(
|
|
18536
18934
|
"request_review",
|
|
18537
|
-
'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.',
|
|
18935
|
+
'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.',
|
|
18538
18936
|
{ ref: import_zod3.z.string().describe("Worktree thread id/ref to review") },
|
|
18539
18937
|
async ({ ref }) => {
|
|
18540
18938
|
try {
|
|
@@ -18562,7 +18960,7 @@ async function startMcpServer() {
|
|
|
18562
18960
|
);
|
|
18563
18961
|
server.tool(
|
|
18564
18962
|
"ask_git",
|
|
18565
|
-
"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.",
|
|
18963
|
+
"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.",
|
|
18566
18964
|
{
|
|
18567
18965
|
ref: import_zod3.z.string().describe("Worktree thread id/ref"),
|
|
18568
18966
|
action: import_zod3.z.enum(AGENT_GIT_ACTIONS).describe(
|
|
@@ -18613,7 +19011,7 @@ async function startMcpServer() {
|
|
|
18613
19011
|
);
|
|
18614
19012
|
server.tool(
|
|
18615
19013
|
"fork_worktree",
|
|
18616
|
-
"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.",
|
|
19014
|
+
"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.",
|
|
18617
19015
|
{
|
|
18618
19016
|
ref: import_zod3.z.string().describe("Worktree thread id/ref to fork"),
|
|
18619
19017
|
through_index: import_zod3.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
@@ -18658,7 +19056,7 @@ async function startMcpServer() {
|
|
|
18658
19056
|
);
|
|
18659
19057
|
server.tool(
|
|
18660
19058
|
"fork_chat",
|
|
18661
|
-
"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.",
|
|
19059
|
+
"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.",
|
|
18662
19060
|
{
|
|
18663
19061
|
ref: import_zod3.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
18664
19062
|
through_index: import_zod3.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
@@ -19341,7 +19739,7 @@ init_connected_teams();
|
|
|
19341
19739
|
init_injected_mcp();
|
|
19342
19740
|
|
|
19343
19741
|
// src/agents/user-mcp-config.ts
|
|
19344
|
-
var
|
|
19742
|
+
var import_node_fs44 = require("fs");
|
|
19345
19743
|
var import_node_os12 = require("os");
|
|
19346
19744
|
var import_node_path40 = require("path");
|
|
19347
19745
|
init_paths();
|
|
@@ -19377,16 +19775,16 @@ function mergeSideboardIntoMcpServersJson(existing, sideboard) {
|
|
|
19377
19775
|
}
|
|
19378
19776
|
function writeMergedMcpServersJson(configPath, sideboard) {
|
|
19379
19777
|
let existing = {};
|
|
19380
|
-
if ((0,
|
|
19778
|
+
if ((0, import_node_fs44.existsSync)(configPath)) {
|
|
19381
19779
|
try {
|
|
19382
|
-
existing = JSON.parse((0,
|
|
19780
|
+
existing = JSON.parse((0, import_node_fs44.readFileSync)(configPath, "utf8"));
|
|
19383
19781
|
} catch {
|
|
19384
19782
|
existing = {};
|
|
19385
19783
|
}
|
|
19386
19784
|
}
|
|
19387
19785
|
const next = mergeSideboardIntoMcpServersJson(existing, sideboard);
|
|
19388
|
-
(0,
|
|
19389
|
-
(0,
|
|
19786
|
+
(0, import_node_fs44.mkdirSync)((0, import_node_path40.dirname)(configPath), { recursive: true });
|
|
19787
|
+
(0, import_node_fs44.writeFileSync)(configPath, `${JSON.stringify(next, null, 2)}
|
|
19390
19788
|
`);
|
|
19391
19789
|
}
|
|
19392
19790
|
function launchFromResolved(server) {
|
|
@@ -19404,7 +19802,7 @@ async function registerPackagedUserMcpClients() {
|
|
|
19404
19802
|
const cursor = userCursorMcpConfigPath();
|
|
19405
19803
|
writeMergedMcpServersJson(cursor, launch);
|
|
19406
19804
|
const claude = userClaudeMcpConfigPath();
|
|
19407
|
-
if ((0,
|
|
19805
|
+
if ((0, import_node_fs44.existsSync)(claude)) {
|
|
19408
19806
|
writeMergedMcpServersJson(claude, launch);
|
|
19409
19807
|
return { cursor, claude };
|
|
19410
19808
|
}
|
|
@@ -19910,6 +20308,8 @@ function connectSession(ws, opts, log) {
|
|
|
19910
20308
|
|
|
19911
20309
|
// src/slack/relay-protocol.ts
|
|
19912
20310
|
var import_node_crypto11 = require("crypto");
|
|
20311
|
+
var SLACK_RELAY_PING_INTERVAL_MS = 2e4;
|
|
20312
|
+
var SLACK_RELAY_PONG_TIMEOUT_MS = 15e3;
|
|
19913
20313
|
function slackRelaySessionKey(teamId, userId, deviceId) {
|
|
19914
20314
|
return `${teamId.trim()}:${userId.trim()}:${deviceId.trim()}`;
|
|
19915
20315
|
}
|
|
@@ -20095,10 +20495,21 @@ function connectSession2(ws, opts, log) {
|
|
|
20095
20495
|
const allowedUsers = new Set(
|
|
20096
20496
|
opts.workspaces.map((w) => `${w.teamId.trim()}:${w.userId.trim()}`)
|
|
20097
20497
|
);
|
|
20498
|
+
const pingMs = opts.pingIntervalMs ?? SLACK_RELAY_PING_INTERVAL_MS;
|
|
20499
|
+
const pongMs = opts.pongTimeoutMs ?? SLACK_RELAY_PONG_TIMEOUT_MS;
|
|
20500
|
+
let pingTimer = null;
|
|
20501
|
+
let pongTimer = null;
|
|
20502
|
+
const stopKeepalive = () => {
|
|
20503
|
+
if (pingTimer) clearInterval(pingTimer);
|
|
20504
|
+
if (pongTimer) clearTimeout(pongTimer);
|
|
20505
|
+
pingTimer = null;
|
|
20506
|
+
pongTimer = null;
|
|
20507
|
+
};
|
|
20098
20508
|
const finish = (err) => {
|
|
20099
20509
|
if (settled) return;
|
|
20100
20510
|
settled = true;
|
|
20101
20511
|
pending.clear();
|
|
20512
|
+
stopKeepalive();
|
|
20102
20513
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
20103
20514
|
if (err) reject(err);
|
|
20104
20515
|
else resolve();
|
|
@@ -20111,6 +20522,25 @@ function connectSession2(ws, opts, log) {
|
|
|
20111
20522
|
finish();
|
|
20112
20523
|
};
|
|
20113
20524
|
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
20525
|
+
const armPongTimeout = () => {
|
|
20526
|
+
if (pongTimer) clearTimeout(pongTimer);
|
|
20527
|
+
pongTimer = setTimeout(() => {
|
|
20528
|
+
log("relay error: ping timeout");
|
|
20529
|
+
try {
|
|
20530
|
+
ws.close();
|
|
20531
|
+
} catch {
|
|
20532
|
+
}
|
|
20533
|
+
finish(new Error("relay ping timeout"));
|
|
20534
|
+
}, pongMs);
|
|
20535
|
+
};
|
|
20536
|
+
const pingOnce = () => {
|
|
20537
|
+
try {
|
|
20538
|
+
send(ws, { type: "ping" });
|
|
20539
|
+
armPongTimeout();
|
|
20540
|
+
} catch (err) {
|
|
20541
|
+
finish(err instanceof Error ? err : new Error("relay ping failed"));
|
|
20542
|
+
}
|
|
20543
|
+
};
|
|
20114
20544
|
ws.addEventListener("open", () => {
|
|
20115
20545
|
const label = opts.deviceLabel?.trim() || opts.deviceId.trim();
|
|
20116
20546
|
log(`Relay connected \xB7 ${label}`);
|
|
@@ -20125,6 +20555,8 @@ function connectSession2(ws, opts, log) {
|
|
|
20125
20555
|
userToken: wsInfo.userToken
|
|
20126
20556
|
});
|
|
20127
20557
|
}
|
|
20558
|
+
pingOnce();
|
|
20559
|
+
pingTimer = setInterval(pingOnce, pingMs);
|
|
20128
20560
|
});
|
|
20129
20561
|
ws.addEventListener("error", () => {
|
|
20130
20562
|
finish(new Error("WebSocket error"));
|
|
@@ -20136,7 +20568,13 @@ function connectSession2(ws, opts, log) {
|
|
|
20136
20568
|
const raw = typeof ev.data === "string" ? ev.data : String(ev.data ?? "");
|
|
20137
20569
|
const msg = parseSlackRelayServerMessage(raw);
|
|
20138
20570
|
if (!msg) return;
|
|
20139
|
-
if (msg.type === "pong")
|
|
20571
|
+
if (msg.type === "pong") {
|
|
20572
|
+
if (pongTimer) {
|
|
20573
|
+
clearTimeout(pongTimer);
|
|
20574
|
+
pongTimer = null;
|
|
20575
|
+
}
|
|
20576
|
+
return;
|
|
20577
|
+
}
|
|
20140
20578
|
if (msg.type === "error") {
|
|
20141
20579
|
log(`relay error: ${msg.message}`);
|
|
20142
20580
|
return;
|
|
@@ -20259,6 +20697,13 @@ function slackReplyThreadTs(msg) {
|
|
|
20259
20697
|
if (msg.kind === "mention") return msg.ts;
|
|
20260
20698
|
return void 0;
|
|
20261
20699
|
}
|
|
20700
|
+
var SLACK_PROGRESS_DELAY_MS = 2e4;
|
|
20701
|
+
var SLACK_PROGRESS_EDIT_MS = 15e3;
|
|
20702
|
+
function formatSlackWorkingText(summary) {
|
|
20703
|
+
const line = (summary ?? "").trim();
|
|
20704
|
+
if (!line || /^working/i.test(line)) return "Working\u2026";
|
|
20705
|
+
return `Working\u2026 ${line}`;
|
|
20706
|
+
}
|
|
20262
20707
|
var SLACK_SEEN_REACTION = "eyes";
|
|
20263
20708
|
function writeTokenForTeam(teamId) {
|
|
20264
20709
|
const workspaces = listSlackWorkspacesRaw();
|
|
@@ -20295,8 +20740,8 @@ async function ackSlackInboundSeen(msg, opts) {
|
|
|
20295
20740
|
log(`react error: ${errMsg}${hint}`);
|
|
20296
20741
|
}
|
|
20297
20742
|
}
|
|
20298
|
-
|
|
20299
|
-
|
|
20743
|
+
function replyStub(target) {
|
|
20744
|
+
return {
|
|
20300
20745
|
teamId: target.teamId,
|
|
20301
20746
|
channelId: target.channelId,
|
|
20302
20747
|
ts: target.threadTs ?? "",
|
|
@@ -20304,12 +20749,15 @@ async function postSlackText(target, text3, opts) {
|
|
|
20304
20749
|
text: "",
|
|
20305
20750
|
kind: "dm"
|
|
20306
20751
|
};
|
|
20752
|
+
}
|
|
20753
|
+
async function postSlackText(target, text3, opts) {
|
|
20307
20754
|
if (opts.postReply) {
|
|
20308
|
-
await opts.postReply(
|
|
20309
|
-
|
|
20755
|
+
const result = await opts.postReply(replyStub(target), text3);
|
|
20756
|
+
const ts2 = result && typeof result === "object" && typeof result.ts === "string" ? result.ts.trim() : "";
|
|
20757
|
+
return ts2 ? { ts: ts2 } : null;
|
|
20310
20758
|
}
|
|
20311
20759
|
const token = writeTokenForTeam(target.teamId);
|
|
20312
|
-
await slackApi(
|
|
20760
|
+
const json = await slackApi(
|
|
20313
20761
|
token,
|
|
20314
20762
|
"chat.postMessage",
|
|
20315
20763
|
{
|
|
@@ -20319,18 +20767,163 @@ async function postSlackText(target, text3, opts) {
|
|
|
20319
20767
|
},
|
|
20320
20768
|
opts.fetchImpl
|
|
20321
20769
|
);
|
|
20770
|
+
const ts = json.ts?.trim();
|
|
20771
|
+
return ts ? { ts } : null;
|
|
20322
20772
|
}
|
|
20323
|
-
async function
|
|
20324
|
-
|
|
20773
|
+
async function updateSlackText(target, ts, text3, opts) {
|
|
20774
|
+
if (opts.updateReply) {
|
|
20775
|
+
await opts.updateReply(replyStub(target), ts, text3);
|
|
20776
|
+
return;
|
|
20777
|
+
}
|
|
20778
|
+
const token = writeTokenForTeam(target.teamId);
|
|
20779
|
+
await slackApi(
|
|
20780
|
+
token,
|
|
20781
|
+
"chat.update",
|
|
20325
20782
|
{
|
|
20326
|
-
|
|
20327
|
-
|
|
20328
|
-
|
|
20783
|
+
channel: target.channelId,
|
|
20784
|
+
ts,
|
|
20785
|
+
text: text3
|
|
20329
20786
|
},
|
|
20330
|
-
|
|
20331
|
-
|
|
20787
|
+
opts.fetchImpl
|
|
20788
|
+
);
|
|
20789
|
+
}
|
|
20790
|
+
async function deleteSlackText(target, ts, opts) {
|
|
20791
|
+
if (opts.deleteReply) {
|
|
20792
|
+
await opts.deleteReply(replyStub(target), ts);
|
|
20793
|
+
return;
|
|
20794
|
+
}
|
|
20795
|
+
const token = writeTokenForTeam(target.teamId);
|
|
20796
|
+
await slackApi(
|
|
20797
|
+
token,
|
|
20798
|
+
"chat.delete",
|
|
20799
|
+
{
|
|
20800
|
+
channel: target.channelId,
|
|
20801
|
+
ts
|
|
20802
|
+
},
|
|
20803
|
+
opts.fetchImpl
|
|
20332
20804
|
);
|
|
20333
20805
|
}
|
|
20806
|
+
function replyTargetOf(msg) {
|
|
20807
|
+
return {
|
|
20808
|
+
teamId: msg.teamId,
|
|
20809
|
+
channelId: msg.channelId,
|
|
20810
|
+
threadTs: slackReplyThreadTs(msg)
|
|
20811
|
+
};
|
|
20812
|
+
}
|
|
20813
|
+
async function postSlackReply(msg, text3, opts) {
|
|
20814
|
+
await postSlackText(replyTargetOf(msg), signForThisMac(text3), opts);
|
|
20815
|
+
}
|
|
20816
|
+
function startSlackTurnProgress(msg, threadId, opts) {
|
|
20817
|
+
const log = opts.onLog ?? (() => void 0);
|
|
20818
|
+
const target = replyTargetOf(msg);
|
|
20819
|
+
const delayMs = opts.progressDelayMs ?? SLACK_PROGRESS_DELAY_MS;
|
|
20820
|
+
const editMs = opts.progressEditMs ?? SLACK_PROGRESS_EDIT_MS;
|
|
20821
|
+
let stopped = false;
|
|
20822
|
+
let postedTs = null;
|
|
20823
|
+
let lastBody = null;
|
|
20824
|
+
let chain = Promise.resolve();
|
|
20825
|
+
let editTimer = null;
|
|
20826
|
+
const run2 = (fn) => {
|
|
20827
|
+
const next = chain.then(fn, fn);
|
|
20828
|
+
chain = next.then(
|
|
20829
|
+
() => void 0,
|
|
20830
|
+
() => void 0
|
|
20831
|
+
);
|
|
20832
|
+
return next;
|
|
20833
|
+
};
|
|
20834
|
+
const stillLive = () => {
|
|
20835
|
+
if (stopped || slackInboundSuperseded(opts)) return false;
|
|
20836
|
+
const thread = readThread(threadId());
|
|
20837
|
+
return Boolean(thread && (thread.status === "running" || thread.status === "queued"));
|
|
20838
|
+
};
|
|
20839
|
+
const postWorking = async () => {
|
|
20840
|
+
if (!stillLive()) return;
|
|
20841
|
+
const summary = readTurnLive(threadId())?.summary ?? "Working\u2026";
|
|
20842
|
+
const body = formatSlackWorkingText(summary);
|
|
20843
|
+
try {
|
|
20844
|
+
const posted = await postSlackText(target, signForThisMac(body), opts);
|
|
20845
|
+
postedTs = posted?.ts ?? postedTs;
|
|
20846
|
+
lastBody = body;
|
|
20847
|
+
if (postedTs && !stopped && !editTimer) {
|
|
20848
|
+
editTimer = setInterval(() => {
|
|
20849
|
+
void run2(editWorking);
|
|
20850
|
+
}, editMs);
|
|
20851
|
+
}
|
|
20852
|
+
} catch (err) {
|
|
20853
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
20854
|
+
log(`progress post error: ${errMsg}`);
|
|
20855
|
+
}
|
|
20856
|
+
};
|
|
20857
|
+
const editWorking = async () => {
|
|
20858
|
+
if (!postedTs || !stillLive()) return;
|
|
20859
|
+
const summary = readTurnLive(threadId())?.summary ?? "Working\u2026";
|
|
20860
|
+
const body = formatSlackWorkingText(summary);
|
|
20861
|
+
if (body === lastBody) return;
|
|
20862
|
+
lastBody = body;
|
|
20863
|
+
try {
|
|
20864
|
+
await updateSlackText(target, postedTs, signForThisMac(body), opts);
|
|
20865
|
+
} catch (err) {
|
|
20866
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
20867
|
+
log(`progress update error: ${errMsg}`);
|
|
20868
|
+
}
|
|
20869
|
+
};
|
|
20870
|
+
const delayTimer = setTimeout(() => {
|
|
20871
|
+
void run2(postWorking);
|
|
20872
|
+
}, delayMs);
|
|
20873
|
+
const stop = () => {
|
|
20874
|
+
stopped = true;
|
|
20875
|
+
clearTimeout(delayTimer);
|
|
20876
|
+
if (editTimer) {
|
|
20877
|
+
clearInterval(editTimer);
|
|
20878
|
+
editTimer = null;
|
|
20879
|
+
}
|
|
20880
|
+
};
|
|
20881
|
+
return {
|
|
20882
|
+
stop,
|
|
20883
|
+
discard: () => {
|
|
20884
|
+
stop();
|
|
20885
|
+
return run2(async () => {
|
|
20886
|
+
if (!postedTs) return;
|
|
20887
|
+
const ts = postedTs;
|
|
20888
|
+
postedTs = null;
|
|
20889
|
+
try {
|
|
20890
|
+
await deleteSlackText(target, ts, opts);
|
|
20891
|
+
} catch (err) {
|
|
20892
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
20893
|
+
log(`progress delete error: ${errMsg}`);
|
|
20894
|
+
}
|
|
20895
|
+
});
|
|
20896
|
+
},
|
|
20897
|
+
finishWith: (text3) => {
|
|
20898
|
+
stop();
|
|
20899
|
+
return run2(async () => {
|
|
20900
|
+
const signed = signForThisMac(text3.trim());
|
|
20901
|
+
if (!signed) {
|
|
20902
|
+
if (!postedTs) return;
|
|
20903
|
+
const ts = postedTs;
|
|
20904
|
+
postedTs = null;
|
|
20905
|
+
try {
|
|
20906
|
+
await deleteSlackText(target, ts, opts);
|
|
20907
|
+
} catch (err) {
|
|
20908
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
20909
|
+
log(`progress delete error: ${errMsg}`);
|
|
20910
|
+
}
|
|
20911
|
+
return;
|
|
20912
|
+
}
|
|
20913
|
+
if (postedTs) {
|
|
20914
|
+
try {
|
|
20915
|
+
await updateSlackText(target, postedTs, signed, opts);
|
|
20916
|
+
return;
|
|
20917
|
+
} catch (err) {
|
|
20918
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
20919
|
+
log(`progress update error: ${errMsg}`);
|
|
20920
|
+
}
|
|
20921
|
+
}
|
|
20922
|
+
await postSlackText(target, signed, opts);
|
|
20923
|
+
});
|
|
20924
|
+
}
|
|
20925
|
+
};
|
|
20926
|
+
}
|
|
20334
20927
|
var lastRelayed = /* @__PURE__ */ new Map();
|
|
20335
20928
|
function markRelayed(threadId, text3) {
|
|
20336
20929
|
lastRelayed.set(threadId, `${threadId}:${text3}`);
|
|
@@ -20422,10 +21015,15 @@ async function handleSlackInbound(msg, opts) {
|
|
|
20422
21015
|
});
|
|
20423
21016
|
};
|
|
20424
21017
|
bindReplyTarget(fresh.id);
|
|
21018
|
+
const progress = startSlackTurnProgress(msg, () => fresh.id, opts);
|
|
20425
21019
|
const runTurn = async (threadId) => {
|
|
20426
21020
|
await orch.send(threadId, prompt);
|
|
20427
21021
|
await orch.waitForTurn(threadId, 14 * 60 * 1e3);
|
|
20428
21022
|
};
|
|
21023
|
+
const dropProgress = async (reason) => {
|
|
21024
|
+
log(reason);
|
|
21025
|
+
await progress.discard();
|
|
21026
|
+
};
|
|
20429
21027
|
let reply;
|
|
20430
21028
|
try {
|
|
20431
21029
|
try {
|
|
@@ -20438,12 +21036,12 @@ async function handleSlackInbound(msg, opts) {
|
|
|
20438
21036
|
await runTurn(fresh.id);
|
|
20439
21037
|
}
|
|
20440
21038
|
if (slackInboundSuperseded(opts)) {
|
|
20441
|
-
|
|
21039
|
+
await dropProgress(`turn finished ${msg.ts} (superseded)`);
|
|
20442
21040
|
return;
|
|
20443
21041
|
}
|
|
20444
21042
|
let after = readThread(fresh.id);
|
|
20445
21043
|
if (after?.status === "stopped") {
|
|
20446
|
-
|
|
21044
|
+
await dropProgress(`turn finished ${msg.ts} (interrupted, skip post)`);
|
|
20447
21045
|
return;
|
|
20448
21046
|
}
|
|
20449
21047
|
if (!after || after.status === "archived") {
|
|
@@ -20452,46 +21050,48 @@ async function handleSlackInbound(msg, opts) {
|
|
|
20452
21050
|
bindReplyTarget(fresh.id);
|
|
20453
21051
|
await runTurn(fresh.id);
|
|
20454
21052
|
if (slackInboundSuperseded(opts)) {
|
|
20455
|
-
|
|
21053
|
+
await dropProgress(`turn finished ${msg.ts} (superseded)`);
|
|
20456
21054
|
return;
|
|
20457
21055
|
}
|
|
20458
21056
|
after = readThread(fresh.id);
|
|
20459
21057
|
if (!after || after.status === "stopped" || after.status === "archived") {
|
|
20460
|
-
|
|
21058
|
+
await dropProgress(`turn finished ${msg.ts} (interrupted, skip post)`);
|
|
20461
21059
|
return;
|
|
20462
21060
|
}
|
|
20463
21061
|
}
|
|
20464
21062
|
reply = orch.getTurnResult(fresh.id).text.trim();
|
|
20465
21063
|
} catch (err) {
|
|
20466
21064
|
if (slackInboundSuperseded(opts)) {
|
|
20467
|
-
|
|
21065
|
+
await dropProgress(`turn error ${msg.ts} (superseded)`);
|
|
20468
21066
|
return;
|
|
20469
21067
|
}
|
|
20470
21068
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
20471
21069
|
if (slackCoordinatorGone(err)) {
|
|
20472
|
-
|
|
21070
|
+
await dropProgress(`turn finished ${msg.ts} (thread gone, skip post)`);
|
|
20473
21071
|
return;
|
|
20474
21072
|
}
|
|
20475
21073
|
const timedOut = /timed out|timeout/i.test(errMsg);
|
|
20476
21074
|
const errorReply = timedOut ? SLACK_LISTEN_TIMEOUT_REPLY : `Sideboard coordinator error: ${errMsg}`;
|
|
20477
|
-
|
|
21075
|
+
try {
|
|
21076
|
+
await progress.finishWith(errorReply);
|
|
21077
|
+
log(`replied error ${msg.ts}: ${errMsg}`);
|
|
21078
|
+
} catch (postErr) {
|
|
20478
21079
|
const postMsg = postErr instanceof Error ? postErr.message : String(postErr);
|
|
20479
21080
|
log(`post error: ${postMsg}`);
|
|
20480
|
-
}
|
|
20481
|
-
log(`replied error ${msg.ts}: ${errMsg}`);
|
|
21081
|
+
}
|
|
20482
21082
|
if (!timedOut) throw err;
|
|
20483
21083
|
return;
|
|
20484
21084
|
}
|
|
20485
21085
|
if (!reply) {
|
|
20486
|
-
|
|
21086
|
+
await dropProgress(`post error: no agent text to send for ${msg.ts}`);
|
|
20487
21087
|
return;
|
|
20488
21088
|
}
|
|
20489
21089
|
if (alreadyRelayed(fresh.id, reply)) {
|
|
20490
|
-
|
|
21090
|
+
await dropProgress(`turn finished ${msg.ts} (already posted)`);
|
|
20491
21091
|
return;
|
|
20492
21092
|
}
|
|
20493
21093
|
try {
|
|
20494
|
-
await
|
|
21094
|
+
await progress.finishWith(reply);
|
|
20495
21095
|
markRelayed(fresh.id, reply);
|
|
20496
21096
|
log(`replied ${msg.ts} (${reply.length} chars)`);
|
|
20497
21097
|
} catch (err) {
|
|
@@ -20818,7 +21418,7 @@ var import_node_http3 = require("http");
|
|
|
20818
21418
|
var import_ws3 = require("ws");
|
|
20819
21419
|
|
|
20820
21420
|
// src/slack/relay-static.ts
|
|
20821
|
-
var
|
|
21421
|
+
var import_node_fs45 = require("fs");
|
|
20822
21422
|
var import_promises = require("fs/promises");
|
|
20823
21423
|
var import_node_path41 = __toESM(require("path"), 1);
|
|
20824
21424
|
var TYPES = {
|
|
@@ -20877,7 +21477,7 @@ function sendFile(req, res, file, size) {
|
|
|
20877
21477
|
res.end();
|
|
20878
21478
|
return true;
|
|
20879
21479
|
}
|
|
20880
|
-
(0,
|
|
21480
|
+
(0, import_node_fs45.createReadStream)(file).pipe(res);
|
|
20881
21481
|
return true;
|
|
20882
21482
|
}
|
|
20883
21483
|
async function tryServeStatic(req, res, root) {
|
|
@@ -21042,6 +21642,20 @@ async function startSlackRelayServer(opts) {
|
|
|
21042
21642
|
}
|
|
21043
21643
|
}
|
|
21044
21644
|
};
|
|
21645
|
+
const ping = setInterval(() => {
|
|
21646
|
+
if (ws.readyState !== ws.OPEN) {
|
|
21647
|
+
clearInterval(ping);
|
|
21648
|
+
return;
|
|
21649
|
+
}
|
|
21650
|
+
try {
|
|
21651
|
+
ws.ping();
|
|
21652
|
+
} catch {
|
|
21653
|
+
clearInterval(ping);
|
|
21654
|
+
}
|
|
21655
|
+
}, SLACK_RELAY_PING_INTERVAL_MS);
|
|
21656
|
+
const stopPing = () => clearInterval(ping);
|
|
21657
|
+
ws.on("close", stopPing);
|
|
21658
|
+
ws.on("error", stopPing);
|
|
21045
21659
|
log("desktop connected");
|
|
21046
21660
|
ws.on("message", (data) => {
|
|
21047
21661
|
const raw = typeof data === "string" ? data : data.toString("utf8");
|
|
@@ -21153,6 +21767,8 @@ async function startSlackRelayServer(opts) {
|
|
|
21153
21767
|
SLACK_LISTEN_TIMEOUT_REPLY,
|
|
21154
21768
|
SLACK_OAUTH_CANCELLED,
|
|
21155
21769
|
SLACK_OAUTH_REDIRECT,
|
|
21770
|
+
SLACK_PROGRESS_DELAY_MS,
|
|
21771
|
+
SLACK_PROGRESS_EDIT_MS,
|
|
21156
21772
|
SLACK_REPLY_FORMATTING,
|
|
21157
21773
|
SLACK_SEEN_REACTION,
|
|
21158
21774
|
SlackOAuthCancelledError,
|
|
@@ -21310,6 +21926,7 @@ async function startSlackRelayServer(opts) {
|
|
|
21310
21926
|
formatSlackInboundPrompt,
|
|
21311
21927
|
formatSlackRepliesForTurn,
|
|
21312
21928
|
formatSlackSignedReply,
|
|
21929
|
+
formatSlackWorkingText,
|
|
21313
21930
|
formatTranscriptMarkdown,
|
|
21314
21931
|
formatUiReminder,
|
|
21315
21932
|
formatWorkspaceInventory,
|
|
@@ -21605,6 +22222,7 @@ async function startSlackRelayServer(opts) {
|
|
|
21605
22222
|
thisProcessShouldDrainAgentQueues,
|
|
21606
22223
|
threadDisplayLabel,
|
|
21607
22224
|
threadFilePath,
|
|
22225
|
+
threadLivePath,
|
|
21608
22226
|
threadLockPath,
|
|
21609
22227
|
threadRequestsBrightsyMcp,
|
|
21610
22228
|
threadsDir,
|
|
@@ -21628,6 +22246,7 @@ async function startSlackRelayServer(opts) {
|
|
|
21628
22246
|
userClaudeMcpConfigPath,
|
|
21629
22247
|
userCursorMcpConfigPath,
|
|
21630
22248
|
validateLinearApiKey,
|
|
22249
|
+
waitForPidExit,
|
|
21631
22250
|
warmGithubAgentAuth,
|
|
21632
22251
|
withAgentInstructions,
|
|
21633
22252
|
withExportedPath,
|