@sideboard-ai/core 0.1.100 → 0.1.103
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/cursor-runner.js +1 -1
- package/dist/{agents-CLP5BG4O.js → agents-UT5GCJH3.js} +8 -8
- package/dist/{agents-OXZDMPSE.js → agents-XZBAKBO2.js} +8 -8
- package/dist/{app-settings-BRF7VKTQ.js → app-settings-EKJM2344.js} +2 -2
- package/dist/{app-settings-ZXPAB3QN.js → app-settings-VDJK2BZ2.js} +2 -2
- package/dist/{caffeinate-hold-U3QJBV67.js → caffeinate-hold-NBRUWYXO.js} +2 -2
- package/dist/{caffeinate-hold-I7SG6UQR.js → caffeinate-hold-X3JNPR42.js} +2 -2
- package/dist/{chunk-MHJV4WS3.js → chunk-27MCESES.js} +3 -3
- package/dist/{chunk-QSLE4VEM.js → chunk-2GO3YKBA.js} +4 -4
- package/dist/{chunk-DKHGWYWR.js → chunk-7YKXHBIW.js} +1 -1
- package/dist/{chunk-JL4SJ6V7.js → chunk-BD2ICC4D.js} +4 -0
- package/dist/{chunk-NZPGMNFC.js → chunk-DCOZABNT.js} +1 -1
- package/dist/{chunk-CIRXAYWS.js → chunk-DHAGMJIK.js} +55 -28
- package/dist/{chunk-HYKZEP5A.js → chunk-ETMRBJKA.js} +11 -11
- package/dist/{chunk-OXC3MEFI.js → chunk-H6LP57EF.js} +6 -6
- package/dist/{chunk-GR4JKWR4.js → chunk-HHLZWO7U.js} +5 -5
- package/dist/{chunk-3WA37BRI.js → chunk-KGSVLZV6.js} +11 -4
- package/dist/{chunk-7KDBDMNC.js → chunk-KLBOWJ74.js} +1 -1
- package/dist/{chunk-EQPQTLW6.js → chunk-LANDJIAI.js} +6 -6
- package/dist/{chunk-MRBKGFTL.js → chunk-ND6AO7C5.js} +3 -3
- package/dist/{chunk-WQTTUC4N.js → chunk-OW34MVDN.js} +5 -5
- package/dist/{chunk-JOF3XIEM.js → chunk-P3BQ5DOL.js} +11 -4
- package/dist/{chunk-B4R2VB2A.js → chunk-QZQEXME2.js} +4 -0
- package/dist/{chunk-R7BQBSDT.js → chunk-WA3DVVWG.js} +55 -28
- package/dist/{chunk-6LFV4VFI.js → chunk-WSYCND2U.js} +1 -1
- package/dist/{chunk-FT2SQOL4.js → chunk-XHZJJWLI.js} +1 -1
- package/dist/{chunk-IFHKPZHA.js → chunk-YJADJH5U.js} +11 -11
- package/dist/{connected-teams-SWHJWK2A.js → connected-teams-J4I5AKMR.js} +2 -2
- package/dist/{connected-teams-TLVKZPJP.js → connected-teams-M5XNXRI5.js} +2 -2
- package/dist/{coordinator-prompt-OOBSQCWQ.js → coordinator-prompt-3DFZRMXE.js} +5 -5
- package/dist/{coordinator-prompt-HHRKQWCX.js → coordinator-prompt-UWHSD65V.js} +5 -5
- package/dist/{cursor-recover-S5ORPW34.js → cursor-recover-JBTIDARH.js} +1 -1
- package/dist/{cursor-recover-G34BYKL2.js → cursor-recover-ZUJG6XRQ.js} +1 -1
- package/dist/{global-workspace-YZWYHLQY.js → global-workspace-KJQS5S2C.js} +6 -6
- package/dist/{global-workspace-KYEBFWKB.js → global-workspace-UR4E3CCG.js} +6 -6
- package/dist/index.cjs +626 -153
- package/dist/index.d.cts +37 -5
- package/dist/index.d.ts +37 -5
- package/dist/index.js +545 -121
- package/dist/mcp/run-stdio.cjs +426 -177
- package/dist/mcp/run-stdio.js +330 -127
- package/dist/{paths-TZX4L5X7.js → paths-QCI3MTO5.js} +3 -1
- package/dist/{paths-JQM2XRAD.js → paths-UMEDHOS5.js} +3 -1
- package/dist/{thread-store-XAKWCV2E.js → thread-store-4OUEQ2DB.js} +2 -2
- package/dist/{thread-store-V7NOOXKE.js → thread-store-DQJGDKIJ.js} +2 -2
- package/dist/{workspaces-ENVUDK6C.js → workspaces-NTIVI6DI.js} +7 -7
- package/dist/{workspaces-NNPD7QVV.js → workspaces-TKJXPNV4.js} +7 -7
- package/dist/{worktree-EO5QAGJU.js → worktree-NKWN5ZTS.js} +4 -4
- package/dist/{worktree-DA4BOV7G.js → worktree-PEUPFAKL.js} +4 -4
- package/package.json +1 -1
package/dist/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. 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.",
|
|
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. On status error, lastError (and text) is the failure \u2014 switch agent, tell the user, or retry; do not treat empty text as success.",
|
|
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:",
|
|
@@ -9308,17 +9357,17 @@ __export(plan_file_exports, {
|
|
|
9308
9357
|
});
|
|
9309
9358
|
function ensureAttachmentsGitignore2(worktreePath) {
|
|
9310
9359
|
const gitignoreAbs = (0, import_node_path37.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
|
|
9311
|
-
if ((0,
|
|
9312
|
-
(0,
|
|
9313
|
-
(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");
|
|
9314
9363
|
}
|
|
9315
9364
|
function planFileAbs(worktreePath) {
|
|
9316
9365
|
return (0, import_node_path37.join)(worktreePath, PLAN_FILE_REL);
|
|
9317
9366
|
}
|
|
9318
9367
|
function readTextIfPresent2(abs) {
|
|
9319
|
-
if (!(0,
|
|
9368
|
+
if (!(0, import_node_fs41.existsSync)(abs)) return null;
|
|
9320
9369
|
try {
|
|
9321
|
-
const content = (0,
|
|
9370
|
+
const content = (0, import_node_fs41.readFileSync)(abs, "utf8");
|
|
9322
9371
|
return content.trim() ? content : null;
|
|
9323
9372
|
} catch {
|
|
9324
9373
|
return null;
|
|
@@ -9330,16 +9379,16 @@ function readPlanFile(worktreePath) {
|
|
|
9330
9379
|
function writePlanFile(worktreePath, content) {
|
|
9331
9380
|
ensureAttachmentsGitignore2(worktreePath);
|
|
9332
9381
|
const abs = planFileAbs(worktreePath);
|
|
9333
|
-
(0,
|
|
9382
|
+
(0, import_node_fs41.mkdirSync)((0, import_node_path37.dirname)(abs), { recursive: true });
|
|
9334
9383
|
const body = content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
|
|
9335
|
-
(0,
|
|
9384
|
+
(0, import_node_fs41.writeFileSync)(abs, body, "utf8");
|
|
9336
9385
|
return PLAN_FILE_REL;
|
|
9337
9386
|
}
|
|
9338
|
-
var
|
|
9387
|
+
var import_node_fs41, import_node_path37;
|
|
9339
9388
|
var init_plan_file = __esm({
|
|
9340
9389
|
"src/plan/plan-file.ts"() {
|
|
9341
9390
|
"use strict";
|
|
9342
|
-
|
|
9391
|
+
import_node_fs41 = require("fs");
|
|
9343
9392
|
import_node_path37 = require("path");
|
|
9344
9393
|
init_workspace_scratch();
|
|
9345
9394
|
init_plan_present();
|
|
@@ -9356,9 +9405,9 @@ function recoverFinishedCursorRun(opts) {
|
|
|
9356
9405
|
const agentId = opts.agentId.trim();
|
|
9357
9406
|
if (!agentId) return null;
|
|
9358
9407
|
const runsPath = (0, import_node_path38.join)(appDataDir(), "cursor-sdk-store", "runs.ndjson");
|
|
9359
|
-
if (!(0,
|
|
9408
|
+
if (!(0, import_node_fs42.existsSync)(runsPath)) return null;
|
|
9360
9409
|
try {
|
|
9361
|
-
const lines = (0,
|
|
9410
|
+
const lines = (0, import_node_fs42.readFileSync)(runsPath, "utf8").split("\n");
|
|
9362
9411
|
let best = null;
|
|
9363
9412
|
for (const line of lines) {
|
|
9364
9413
|
const trimmed = line.trim();
|
|
@@ -9384,11 +9433,11 @@ function recoverFinishedCursorRun(opts) {
|
|
|
9384
9433
|
return null;
|
|
9385
9434
|
}
|
|
9386
9435
|
}
|
|
9387
|
-
var
|
|
9436
|
+
var import_node_fs42, import_node_path38;
|
|
9388
9437
|
var init_cursor_recover = __esm({
|
|
9389
9438
|
"src/agents/cursor-recover.ts"() {
|
|
9390
9439
|
"use strict";
|
|
9391
|
-
|
|
9440
|
+
import_node_fs42 = require("fs");
|
|
9392
9441
|
import_node_path38 = require("path");
|
|
9393
9442
|
init_paths();
|
|
9394
9443
|
}
|
|
@@ -9477,6 +9526,8 @@ __export(index_exports, {
|
|
|
9477
9526
|
SLACK_LISTEN_TIMEOUT_REPLY: () => SLACK_LISTEN_TIMEOUT_REPLY,
|
|
9478
9527
|
SLACK_OAUTH_CANCELLED: () => SLACK_OAUTH_CANCELLED,
|
|
9479
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,
|
|
9480
9531
|
SLACK_REPLY_FORMATTING: () => SLACK_REPLY_FORMATTING,
|
|
9481
9532
|
SLACK_SEEN_REACTION: () => SLACK_SEEN_REACTION,
|
|
9482
9533
|
SlackOAuthCancelledError: () => SlackOAuthCancelledError,
|
|
@@ -9634,6 +9685,7 @@ __export(index_exports, {
|
|
|
9634
9685
|
formatSlackInboundPrompt: () => formatSlackInboundPrompt,
|
|
9635
9686
|
formatSlackRepliesForTurn: () => formatSlackRepliesForTurn,
|
|
9636
9687
|
formatSlackSignedReply: () => formatSlackSignedReply,
|
|
9688
|
+
formatSlackWorkingText: () => formatSlackWorkingText,
|
|
9637
9689
|
formatTranscriptMarkdown: () => formatTranscriptMarkdown,
|
|
9638
9690
|
formatUiReminder: () => formatUiReminder,
|
|
9639
9691
|
formatWorkspaceInventory: () => formatWorkspaceInventory,
|
|
@@ -9929,6 +9981,7 @@ __export(index_exports, {
|
|
|
9929
9981
|
thisProcessShouldDrainAgentQueues: () => thisProcessShouldDrainAgentQueues,
|
|
9930
9982
|
threadDisplayLabel: () => threadDisplayLabel,
|
|
9931
9983
|
threadFilePath: () => threadFilePath,
|
|
9984
|
+
threadLivePath: () => threadLivePath,
|
|
9932
9985
|
threadLockPath: () => threadLockPath,
|
|
9933
9986
|
threadRequestsBrightsyMcp: () => threadRequestsBrightsyMcp,
|
|
9934
9987
|
threadsDir: () => threadsDir,
|
|
@@ -9952,6 +10005,7 @@ __export(index_exports, {
|
|
|
9952
10005
|
userClaudeMcpConfigPath: () => userClaudeMcpConfigPath,
|
|
9953
10006
|
userCursorMcpConfigPath: () => userCursorMcpConfigPath,
|
|
9954
10007
|
validateLinearApiKey: () => validateLinearApiKey,
|
|
10008
|
+
waitForPidExit: () => waitForPidExit,
|
|
9955
10009
|
warmGithubAgentAuth: () => warmGithubAgentAuth,
|
|
9956
10010
|
withAgentInstructions: () => withAgentInstructions,
|
|
9957
10011
|
withExportedPath: () => withExportedPath,
|
|
@@ -11022,7 +11076,7 @@ function createAgentStreamCoalescer(emit, opts) {
|
|
|
11022
11076
|
const intervalMs = opts?.intervalMs ?? 32;
|
|
11023
11077
|
let pending = null;
|
|
11024
11078
|
let timer = null;
|
|
11025
|
-
const
|
|
11079
|
+
const flush2 = () => {
|
|
11026
11080
|
if (timer) {
|
|
11027
11081
|
clearTimeout(timer);
|
|
11028
11082
|
timer = null;
|
|
@@ -11034,13 +11088,13 @@ function createAgentStreamCoalescer(emit, opts) {
|
|
|
11034
11088
|
};
|
|
11035
11089
|
const schedule = () => {
|
|
11036
11090
|
if (timer) return;
|
|
11037
|
-
timer = setTimeout(
|
|
11091
|
+
timer = setTimeout(flush2, intervalMs);
|
|
11038
11092
|
timer.unref?.();
|
|
11039
11093
|
};
|
|
11040
11094
|
return {
|
|
11041
11095
|
push(event) {
|
|
11042
11096
|
if (!isTextEvent(event)) {
|
|
11043
|
-
|
|
11097
|
+
flush2();
|
|
11044
11098
|
emit(event);
|
|
11045
11099
|
return;
|
|
11046
11100
|
}
|
|
@@ -11048,12 +11102,12 @@ function createAgentStreamCoalescer(emit, opts) {
|
|
|
11048
11102
|
if (pending && pending.type === event.type) {
|
|
11049
11103
|
pending.data += event.data;
|
|
11050
11104
|
} else {
|
|
11051
|
-
|
|
11105
|
+
flush2();
|
|
11052
11106
|
pending = { type: event.type, data: event.data };
|
|
11053
11107
|
}
|
|
11054
11108
|
schedule();
|
|
11055
11109
|
},
|
|
11056
|
-
flush
|
|
11110
|
+
flush: flush2
|
|
11057
11111
|
};
|
|
11058
11112
|
}
|
|
11059
11113
|
|
|
@@ -12966,11 +13020,11 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
|
|
|
12966
13020
|
}
|
|
12967
13021
|
return out;
|
|
12968
13022
|
}
|
|
12969
|
-
function stageBuffersAsAttachments(worktreePath,
|
|
12970
|
-
if (
|
|
13023
|
+
function stageBuffersAsAttachments(worktreePath, buffers2) {
|
|
13024
|
+
if (buffers2.length === 0) return [];
|
|
12971
13025
|
const dir = ensureAttachmentsDir(worktreePath);
|
|
12972
13026
|
const out = [];
|
|
12973
|
-
for (const item of
|
|
13027
|
+
for (const item of buffers2) {
|
|
12974
13028
|
const originalName = (item.name || "file").replace(/[/\\]/g, "_") || "file";
|
|
12975
13029
|
try {
|
|
12976
13030
|
const buf = Buffer.from(item.dataBase64, "base64");
|
|
@@ -12990,8 +13044,8 @@ function stageBuffersAsAttachments(worktreePath, buffers) {
|
|
|
12990
13044
|
}
|
|
12991
13045
|
return out;
|
|
12992
13046
|
}
|
|
12993
|
-
function attachmentsFromBuffers(
|
|
12994
|
-
return
|
|
13047
|
+
function attachmentsFromBuffers(buffers2) {
|
|
13048
|
+
return buffers2.map((item) => {
|
|
12995
13049
|
const name = (item.name || "file").replace(/[/\\]/g, "_") || "file";
|
|
12996
13050
|
try {
|
|
12997
13051
|
const buf = Buffer.from(item.dataBase64, "base64");
|
|
@@ -13648,8 +13702,11 @@ function worktreeBindingFrom(from) {
|
|
|
13648
13702
|
};
|
|
13649
13703
|
}
|
|
13650
13704
|
function threadsSharingWorktree(worktreePath) {
|
|
13705
|
+
if (!worktreePath) return [];
|
|
13651
13706
|
const key = normalizeWorktreePath(worktreePath);
|
|
13652
|
-
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));
|
|
13653
13710
|
}
|
|
13654
13711
|
function formatTranscriptMarkdown(title, messages) {
|
|
13655
13712
|
const body = formatMessagesAsTranscript(messages, { tools: "full" });
|
|
@@ -14928,7 +14985,7 @@ async function refreshSlackReplyBadges(opts) {
|
|
|
14928
14985
|
}
|
|
14929
14986
|
|
|
14930
14987
|
// src/orchestrator/orchestrator.ts
|
|
14931
|
-
var
|
|
14988
|
+
var import_node_fs43 = require("fs");
|
|
14932
14989
|
init_error_detail();
|
|
14933
14990
|
init_agents();
|
|
14934
14991
|
init_worktree();
|
|
@@ -15188,10 +15245,131 @@ async function cloneRepoIntoSideboard(opts) {
|
|
|
15188
15245
|
// src/orchestrator/orchestrator.ts
|
|
15189
15246
|
init_thread_store();
|
|
15190
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
|
+
}
|
|
15191
15369
|
|
|
15192
15370
|
// src/review/request-review.ts
|
|
15193
15371
|
var import_node_crypto8 = require("crypto");
|
|
15194
|
-
var
|
|
15372
|
+
var import_node_fs40 = require("fs");
|
|
15195
15373
|
var import_node_path36 = require("path");
|
|
15196
15374
|
init_global_workspace();
|
|
15197
15375
|
init_thread_store();
|
|
@@ -15340,9 +15518,9 @@ function shouldRefreshReviewRequestTemplate(content) {
|
|
|
15340
15518
|
return LEGACY_REVIEW_TEMPLATE_MARKERS.every((m) => trimmed.includes(m));
|
|
15341
15519
|
}
|
|
15342
15520
|
function readTextIfPresent(abs) {
|
|
15343
|
-
if (!(0,
|
|
15521
|
+
if (!(0, import_node_fs40.existsSync)(abs)) return null;
|
|
15344
15522
|
try {
|
|
15345
|
-
const content = (0,
|
|
15523
|
+
const content = (0, import_node_fs40.readFileSync)(abs, "utf8");
|
|
15346
15524
|
return content.trim() ? content : null;
|
|
15347
15525
|
} catch {
|
|
15348
15526
|
return null;
|
|
@@ -15350,9 +15528,9 @@ function readTextIfPresent(abs) {
|
|
|
15350
15528
|
}
|
|
15351
15529
|
function ensureAttachmentsGitignore(worktreePath) {
|
|
15352
15530
|
const gitignoreAbs = (0, import_node_path36.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
|
|
15353
|
-
if ((0,
|
|
15354
|
-
(0,
|
|
15355
|
-
(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");
|
|
15356
15534
|
}
|
|
15357
15535
|
function resolveReviewGuidelines(worktreePath) {
|
|
15358
15536
|
const repoAbs = (0, import_node_path36.join)(worktreePath, REPO_REVIEW_PATH);
|
|
@@ -15386,8 +15564,8 @@ function resolveReviewGuidelines(worktreePath) {
|
|
|
15386
15564
|
};
|
|
15387
15565
|
}
|
|
15388
15566
|
ensureAttachmentsGitignore(worktreePath);
|
|
15389
|
-
(0,
|
|
15390
|
-
(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");
|
|
15391
15569
|
return {
|
|
15392
15570
|
path: REVIEW_REQUEST_PATH,
|
|
15393
15571
|
name: REVIEW_REQUEST_NAME,
|
|
@@ -15409,7 +15587,7 @@ function ensureReviewRequestFile(worktreePath) {
|
|
|
15409
15587
|
const localAbs = (0, import_node_path36.join)(worktreePath, REVIEW_REQUEST_PATH);
|
|
15410
15588
|
const localContent = readTextIfPresent(localAbs) ?? readTextIfPresent((0, import_node_path36.join)(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
|
|
15411
15589
|
if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
|
|
15412
|
-
const path2 = (0,
|
|
15590
|
+
const path2 = (0, import_node_fs40.existsSync)(localAbs) ? REVIEW_REQUEST_PATH : LEGACY_REVIEW_REQUEST_PATH;
|
|
15413
15591
|
return {
|
|
15414
15592
|
path: path2,
|
|
15415
15593
|
name: REVIEW_REQUEST_NAME,
|
|
@@ -15417,8 +15595,8 @@ function ensureReviewRequestFile(worktreePath) {
|
|
|
15417
15595
|
source: "local"
|
|
15418
15596
|
};
|
|
15419
15597
|
}
|
|
15420
|
-
(0,
|
|
15421
|
-
(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");
|
|
15422
15600
|
return {
|
|
15423
15601
|
path: REPO_REVIEW_PATH,
|
|
15424
15602
|
name: REPO_REVIEW_NAME,
|
|
@@ -15627,6 +15805,15 @@ function isPidAlive(pid) {
|
|
|
15627
15805
|
return false;
|
|
15628
15806
|
}
|
|
15629
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;
|
|
15630
15817
|
function writeLiveStatus(threadId, status, lastError) {
|
|
15631
15818
|
const latest = readThread(threadId);
|
|
15632
15819
|
if (!latest || latest.status === "archived") return latest;
|
|
@@ -15704,7 +15891,7 @@ var Orchestrator = class {
|
|
|
15704
15891
|
}
|
|
15705
15892
|
continue;
|
|
15706
15893
|
}
|
|
15707
|
-
if (!(0,
|
|
15894
|
+
if (!(0, import_node_fs43.existsSync)(thread.worktreePath)) {
|
|
15708
15895
|
setStatus(thread.id, "broken", "Worktree missing on disk");
|
|
15709
15896
|
this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
|
|
15710
15897
|
continue;
|
|
@@ -16025,14 +16212,10 @@ var Orchestrator = class {
|
|
|
16025
16212
|
const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id);
|
|
16026
16213
|
const livePid = current.agentPid;
|
|
16027
16214
|
const foreignLive = !inFlight && typeof livePid === "number" && livePid > 0 && isPidAlive(livePid);
|
|
16028
|
-
if (inFlight) {
|
|
16215
|
+
if (inFlight || foreignLive) {
|
|
16029
16216
|
this.stop(thread.id, { clearQueue: false, continueQueue: true });
|
|
16030
|
-
} else {
|
|
16031
|
-
if (foreignLive) {
|
|
16032
|
-
this.stop(thread.id, { clearQueue: false, continueQueue: true });
|
|
16033
|
-
}
|
|
16034
|
-
void this.drainQueue(thread.id);
|
|
16035
16217
|
}
|
|
16218
|
+
void this.drainQueue(thread.id);
|
|
16036
16219
|
return this.requireThread(thread.id);
|
|
16037
16220
|
}
|
|
16038
16221
|
async drainQueue(threadId) {
|
|
@@ -16061,7 +16244,18 @@ var Orchestrator = class {
|
|
|
16061
16244
|
}
|
|
16062
16245
|
const livePid = thread.agentPid;
|
|
16063
16246
|
if (typeof livePid === "number" && livePid > 0 && isPidAlive(livePid)) {
|
|
16064
|
-
|
|
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
|
+
}
|
|
16065
16259
|
continue;
|
|
16066
16260
|
}
|
|
16067
16261
|
const prompt = thread.queue[0];
|
|
@@ -16218,6 +16412,7 @@ var Orchestrator = class {
|
|
|
16218
16412
|
{ cachedPrefix, prompt: agentPrompt },
|
|
16219
16413
|
(event) => {
|
|
16220
16414
|
this.emit({ type: "turn_output", threadId, event });
|
|
16415
|
+
noteTurnLiveEvent(threadId, event);
|
|
16221
16416
|
if (event.type === "session_id") {
|
|
16222
16417
|
updateThread(threadId, { sessionId: event.data });
|
|
16223
16418
|
}
|
|
@@ -16422,6 +16617,7 @@ var Orchestrator = class {
|
|
|
16422
16617
|
this.processes.delete(`${threadId}:agent`);
|
|
16423
16618
|
this.stoppedTurns.delete(threadId);
|
|
16424
16619
|
this.runningCount = Math.max(0, this.runningCount - 1);
|
|
16620
|
+
clearTurnLive(threadId);
|
|
16425
16621
|
try {
|
|
16426
16622
|
updateThread(threadId, { agentPid: null });
|
|
16427
16623
|
} catch {
|
|
@@ -16695,51 +16891,56 @@ var Orchestrator = class {
|
|
|
16695
16891
|
}
|
|
16696
16892
|
return created;
|
|
16697
16893
|
}
|
|
16698
|
-
async waitForTurn(threadRef, timeoutMs = 6e5) {
|
|
16894
|
+
async waitForTurn(threadRef, timeoutMs = 6e5, opts) {
|
|
16699
16895
|
const thread = this.requireThread(threadRef);
|
|
16896
|
+
if (!["running", "queued"].includes(thread.status)) {
|
|
16897
|
+
return thread;
|
|
16898
|
+
}
|
|
16700
16899
|
const start = Date.now();
|
|
16701
16900
|
return new Promise((resolve, reject) => {
|
|
16702
|
-
|
|
16703
|
-
|
|
16704
|
-
|
|
16705
|
-
|
|
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
|
+
};
|
|
16706
16910
|
const off = this.on((event) => {
|
|
16707
16911
|
if (event.type === "turn_finished" && event.threadId === thread.id) {
|
|
16708
|
-
off();
|
|
16709
16912
|
const latest = readThread(thread.id);
|
|
16710
16913
|
if (!latest) {
|
|
16711
|
-
reject(new Error(`Thread not found: ${thread.id}`));
|
|
16914
|
+
finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
|
|
16712
16915
|
return;
|
|
16713
16916
|
}
|
|
16714
|
-
resolve(latest);
|
|
16917
|
+
finish(() => resolve(latest));
|
|
16715
16918
|
}
|
|
16716
16919
|
if (event.type === "error" && event.threadId === thread.id) {
|
|
16717
|
-
off();
|
|
16718
16920
|
const latest = readThread(thread.id);
|
|
16719
16921
|
if (!latest) {
|
|
16720
|
-
reject(new Error(`Thread not found: ${thread.id}`));
|
|
16922
|
+
finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
|
|
16721
16923
|
return;
|
|
16722
16924
|
}
|
|
16723
|
-
resolve(latest);
|
|
16925
|
+
finish(() => resolve(latest));
|
|
16724
16926
|
}
|
|
16725
16927
|
});
|
|
16726
|
-
|
|
16727
|
-
if (Date.now() - start > timeoutMs) {
|
|
16728
|
-
clearInterval(timer);
|
|
16729
|
-
off();
|
|
16730
|
-
reject(new Error("wait_for_turn timed out"));
|
|
16731
|
-
}
|
|
16928
|
+
timer = setInterval(() => {
|
|
16732
16929
|
const current = readThread(thread.id);
|
|
16733
16930
|
if (!current) {
|
|
16734
|
-
|
|
16735
|
-
off();
|
|
16736
|
-
reject(new Error(`Thread not found: ${thread.id}`));
|
|
16931
|
+
finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
|
|
16737
16932
|
return;
|
|
16738
16933
|
}
|
|
16739
16934
|
if (!["running", "queued"].includes(current.status)) {
|
|
16740
|
-
|
|
16741
|
-
|
|
16742
|
-
|
|
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
|
+
}
|
|
16743
16944
|
}
|
|
16744
16945
|
}, 200);
|
|
16745
16946
|
});
|
|
@@ -16749,11 +16950,16 @@ var Orchestrator = class {
|
|
|
16749
16950
|
const lastAgent = [...thread.messages].reverse().find((m) => m.role === "agent");
|
|
16750
16951
|
const lastError = thread.lastError ?? null;
|
|
16751
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;
|
|
16752
16955
|
return {
|
|
16753
16956
|
text: text3,
|
|
16754
16957
|
status: thread.status,
|
|
16755
16958
|
sessionId: thread.sessionId,
|
|
16756
|
-
lastError
|
|
16959
|
+
lastError,
|
|
16960
|
+
stillRunning,
|
|
16961
|
+
progress: live?.summary ?? null,
|
|
16962
|
+
lastActivityAt: live?.updatedAt ?? null
|
|
16757
16963
|
};
|
|
16758
16964
|
}
|
|
16759
16965
|
assertNotGlobal(thread, action) {
|
|
@@ -17179,7 +17385,19 @@ var Orchestrator = class {
|
|
|
17179
17385
|
const thread = this.requireThread(threadRef);
|
|
17180
17386
|
return threadsSharingWorktree(thread.worktreePath);
|
|
17181
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
|
+
}
|
|
17182
17396
|
async archive(threadRef) {
|
|
17397
|
+
const thread = this.requireThread(threadRef);
|
|
17398
|
+
return this.enqueueWorktreeTeardown(thread, () => this.archiveUnlocked(threadRef));
|
|
17399
|
+
}
|
|
17400
|
+
async archiveUnlocked(threadRef) {
|
|
17183
17401
|
const thread = this.requireThread(threadRef);
|
|
17184
17402
|
this.stop(thread.id);
|
|
17185
17403
|
this.releaseOrchestratorCaffeinate(thread);
|
|
@@ -17215,6 +17433,10 @@ var Orchestrator = class {
|
|
|
17215
17433
|
return archived;
|
|
17216
17434
|
}
|
|
17217
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) {
|
|
17218
17440
|
const thread = this.requireThread(threadRef);
|
|
17219
17441
|
this.stop(thread.id);
|
|
17220
17442
|
this.releaseOrchestratorCaffeinate(thread);
|
|
@@ -17249,13 +17471,12 @@ var Orchestrator = class {
|
|
|
17249
17471
|
this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
|
|
17250
17472
|
return restored2;
|
|
17251
17473
|
}
|
|
17252
|
-
if (!(0,
|
|
17474
|
+
if (!(0, import_node_fs43.existsSync)(thread.worktreePath)) {
|
|
17253
17475
|
const { createThreadWorktree: createThreadWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
|
|
17254
|
-
const { execa: execa7 } = await import("execa");
|
|
17255
17476
|
const slug = thread.worktreePath.split("/").pop();
|
|
17256
17477
|
const dest = thread.worktreePath;
|
|
17257
|
-
await
|
|
17258
|
-
|
|
17478
|
+
await withRepoGitLock(thread.repoPath, async () => {
|
|
17479
|
+
await git(["worktree", "add", dest, thread.branchName], thread.repoPath);
|
|
17259
17480
|
});
|
|
17260
17481
|
void createThreadWorktree2;
|
|
17261
17482
|
void slug;
|
|
@@ -17507,6 +17728,15 @@ function mcpArchiveBlockedReason(thread) {
|
|
|
17507
17728
|
// src/mcp/server.ts
|
|
17508
17729
|
init_profile();
|
|
17509
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
|
+
|
|
17510
17740
|
// src/mcp/slack-tools.ts
|
|
17511
17741
|
var import_zod = require("zod");
|
|
17512
17742
|
|
|
@@ -18178,7 +18408,9 @@ async function startMcpServer() {
|
|
|
18178
18408
|
const threads = orch.getThreads(true);
|
|
18179
18409
|
const lines = threads.map((t) => {
|
|
18180
18410
|
const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path39.basename)(t.repoPath) || t.repoPath;
|
|
18181
|
-
|
|
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}`;
|
|
18182
18414
|
});
|
|
18183
18415
|
return {
|
|
18184
18416
|
content: [{ type: "text", text: lines.join("\n") || "(no threads)" }]
|
|
@@ -18187,13 +18419,14 @@ async function startMcpServer() {
|
|
|
18187
18419
|
);
|
|
18188
18420
|
server.tool(
|
|
18189
18421
|
"get_thread",
|
|
18190
|
-
"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.",
|
|
18191
18423
|
{ ref: import_zod3.z.string() },
|
|
18192
18424
|
async ({ ref }) => {
|
|
18193
18425
|
const t = orch.getThread(ref);
|
|
18194
18426
|
if (!t) {
|
|
18195
18427
|
return { content: [{ type: "text", text: `Thread not found: ${ref}` }], isError: true };
|
|
18196
18428
|
}
|
|
18429
|
+
const live = t.status === "running" || t.status === "queued" ? readTurnLive(t.id) : null;
|
|
18197
18430
|
const summary = {
|
|
18198
18431
|
id: t.id,
|
|
18199
18432
|
title: t.title,
|
|
@@ -18208,7 +18441,10 @@ async function startMcpServer() {
|
|
|
18208
18441
|
messageCount: t.messages.length,
|
|
18209
18442
|
devPort: t.devPort,
|
|
18210
18443
|
prUrl: t.prUrl,
|
|
18211
|
-
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
|
|
18212
18448
|
};
|
|
18213
18449
|
return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
|
|
18214
18450
|
}
|
|
@@ -18534,13 +18770,15 @@ async function startMcpServer() {
|
|
|
18534
18770
|
);
|
|
18535
18771
|
server.tool(
|
|
18536
18772
|
"wait_for_turn",
|
|
18537
|
-
"
|
|
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.",
|
|
18538
18774
|
{
|
|
18539
18775
|
ref: import_zod3.z.string(),
|
|
18540
18776
|
timeoutMs: import_zod3.z.number().optional()
|
|
18541
18777
|
},
|
|
18542
18778
|
async ({ ref, timeoutMs }) => {
|
|
18543
|
-
const thread = await orch.waitForTurn(ref, timeoutMs
|
|
18779
|
+
const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
|
|
18780
|
+
resolveIfStillRunning: true
|
|
18781
|
+
});
|
|
18544
18782
|
const result = orch.getTurnResult(thread.id);
|
|
18545
18783
|
return {
|
|
18546
18784
|
content: [
|
|
@@ -18550,7 +18788,11 @@ async function startMcpServer() {
|
|
|
18550
18788
|
id: thread.id,
|
|
18551
18789
|
status: result.status,
|
|
18552
18790
|
text: result.text,
|
|
18553
|
-
lastError: result.lastError
|
|
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
|
|
18554
18796
|
})
|
|
18555
18797
|
}
|
|
18556
18798
|
]
|
|
@@ -18559,11 +18801,21 @@ async function startMcpServer() {
|
|
|
18559
18801
|
);
|
|
18560
18802
|
server.tool(
|
|
18561
18803
|
"get_turn_result",
|
|
18562
|
-
"
|
|
18804
|
+
"Assistant message when the turn finished, or live progress while stillRunning. Not the full transcript.",
|
|
18563
18805
|
{ ref: import_zod3.z.string() },
|
|
18564
18806
|
async ({ ref }) => {
|
|
18565
18807
|
const result = orch.getTurnResult(ref);
|
|
18566
|
-
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
|
+
};
|
|
18567
18819
|
}
|
|
18568
18820
|
);
|
|
18569
18821
|
server.tool(
|
|
@@ -18680,7 +18932,7 @@ async function startMcpServer() {
|
|
|
18680
18932
|
);
|
|
18681
18933
|
server.tool(
|
|
18682
18934
|
"request_review",
|
|
18683
|
-
'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.',
|
|
18684
18936
|
{ ref: import_zod3.z.string().describe("Worktree thread id/ref to review") },
|
|
18685
18937
|
async ({ ref }) => {
|
|
18686
18938
|
try {
|
|
@@ -18708,7 +18960,7 @@ async function startMcpServer() {
|
|
|
18708
18960
|
);
|
|
18709
18961
|
server.tool(
|
|
18710
18962
|
"ask_git",
|
|
18711
|
-
"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.",
|
|
18712
18964
|
{
|
|
18713
18965
|
ref: import_zod3.z.string().describe("Worktree thread id/ref"),
|
|
18714
18966
|
action: import_zod3.z.enum(AGENT_GIT_ACTIONS).describe(
|
|
@@ -18759,7 +19011,7 @@ async function startMcpServer() {
|
|
|
18759
19011
|
);
|
|
18760
19012
|
server.tool(
|
|
18761
19013
|
"fork_worktree",
|
|
18762
|
-
"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.",
|
|
18763
19015
|
{
|
|
18764
19016
|
ref: import_zod3.z.string().describe("Worktree thread id/ref to fork"),
|
|
18765
19017
|
through_index: import_zod3.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
@@ -18804,7 +19056,7 @@ async function startMcpServer() {
|
|
|
18804
19056
|
);
|
|
18805
19057
|
server.tool(
|
|
18806
19058
|
"fork_chat",
|
|
18807
|
-
"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.",
|
|
18808
19060
|
{
|
|
18809
19061
|
ref: import_zod3.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
18810
19062
|
through_index: import_zod3.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
@@ -19487,7 +19739,7 @@ init_connected_teams();
|
|
|
19487
19739
|
init_injected_mcp();
|
|
19488
19740
|
|
|
19489
19741
|
// src/agents/user-mcp-config.ts
|
|
19490
|
-
var
|
|
19742
|
+
var import_node_fs44 = require("fs");
|
|
19491
19743
|
var import_node_os12 = require("os");
|
|
19492
19744
|
var import_node_path40 = require("path");
|
|
19493
19745
|
init_paths();
|
|
@@ -19523,16 +19775,16 @@ function mergeSideboardIntoMcpServersJson(existing, sideboard) {
|
|
|
19523
19775
|
}
|
|
19524
19776
|
function writeMergedMcpServersJson(configPath, sideboard) {
|
|
19525
19777
|
let existing = {};
|
|
19526
|
-
if ((0,
|
|
19778
|
+
if ((0, import_node_fs44.existsSync)(configPath)) {
|
|
19527
19779
|
try {
|
|
19528
|
-
existing = JSON.parse((0,
|
|
19780
|
+
existing = JSON.parse((0, import_node_fs44.readFileSync)(configPath, "utf8"));
|
|
19529
19781
|
} catch {
|
|
19530
19782
|
existing = {};
|
|
19531
19783
|
}
|
|
19532
19784
|
}
|
|
19533
19785
|
const next = mergeSideboardIntoMcpServersJson(existing, sideboard);
|
|
19534
|
-
(0,
|
|
19535
|
-
(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)}
|
|
19536
19788
|
`);
|
|
19537
19789
|
}
|
|
19538
19790
|
function launchFromResolved(server) {
|
|
@@ -19550,7 +19802,7 @@ async function registerPackagedUserMcpClients() {
|
|
|
19550
19802
|
const cursor = userCursorMcpConfigPath();
|
|
19551
19803
|
writeMergedMcpServersJson(cursor, launch);
|
|
19552
19804
|
const claude = userClaudeMcpConfigPath();
|
|
19553
|
-
if ((0,
|
|
19805
|
+
if ((0, import_node_fs44.existsSync)(claude)) {
|
|
19554
19806
|
writeMergedMcpServersJson(claude, launch);
|
|
19555
19807
|
return { cursor, claude };
|
|
19556
19808
|
}
|
|
@@ -20056,6 +20308,8 @@ function connectSession(ws, opts, log) {
|
|
|
20056
20308
|
|
|
20057
20309
|
// src/slack/relay-protocol.ts
|
|
20058
20310
|
var import_node_crypto11 = require("crypto");
|
|
20311
|
+
var SLACK_RELAY_PING_INTERVAL_MS = 2e4;
|
|
20312
|
+
var SLACK_RELAY_PONG_TIMEOUT_MS = 15e3;
|
|
20059
20313
|
function slackRelaySessionKey(teamId, userId, deviceId) {
|
|
20060
20314
|
return `${teamId.trim()}:${userId.trim()}:${deviceId.trim()}`;
|
|
20061
20315
|
}
|
|
@@ -20241,10 +20495,21 @@ function connectSession2(ws, opts, log) {
|
|
|
20241
20495
|
const allowedUsers = new Set(
|
|
20242
20496
|
opts.workspaces.map((w) => `${w.teamId.trim()}:${w.userId.trim()}`)
|
|
20243
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
|
+
};
|
|
20244
20508
|
const finish = (err) => {
|
|
20245
20509
|
if (settled) return;
|
|
20246
20510
|
settled = true;
|
|
20247
20511
|
pending.clear();
|
|
20512
|
+
stopKeepalive();
|
|
20248
20513
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
20249
20514
|
if (err) reject(err);
|
|
20250
20515
|
else resolve();
|
|
@@ -20257,6 +20522,25 @@ function connectSession2(ws, opts, log) {
|
|
|
20257
20522
|
finish();
|
|
20258
20523
|
};
|
|
20259
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
|
+
};
|
|
20260
20544
|
ws.addEventListener("open", () => {
|
|
20261
20545
|
const label = opts.deviceLabel?.trim() || opts.deviceId.trim();
|
|
20262
20546
|
log(`Relay connected \xB7 ${label}`);
|
|
@@ -20271,6 +20555,8 @@ function connectSession2(ws, opts, log) {
|
|
|
20271
20555
|
userToken: wsInfo.userToken
|
|
20272
20556
|
});
|
|
20273
20557
|
}
|
|
20558
|
+
pingOnce();
|
|
20559
|
+
pingTimer = setInterval(pingOnce, pingMs);
|
|
20274
20560
|
});
|
|
20275
20561
|
ws.addEventListener("error", () => {
|
|
20276
20562
|
finish(new Error("WebSocket error"));
|
|
@@ -20282,7 +20568,13 @@ function connectSession2(ws, opts, log) {
|
|
|
20282
20568
|
const raw = typeof ev.data === "string" ? ev.data : String(ev.data ?? "");
|
|
20283
20569
|
const msg = parseSlackRelayServerMessage(raw);
|
|
20284
20570
|
if (!msg) return;
|
|
20285
|
-
if (msg.type === "pong")
|
|
20571
|
+
if (msg.type === "pong") {
|
|
20572
|
+
if (pongTimer) {
|
|
20573
|
+
clearTimeout(pongTimer);
|
|
20574
|
+
pongTimer = null;
|
|
20575
|
+
}
|
|
20576
|
+
return;
|
|
20577
|
+
}
|
|
20286
20578
|
if (msg.type === "error") {
|
|
20287
20579
|
log(`relay error: ${msg.message}`);
|
|
20288
20580
|
return;
|
|
@@ -20405,6 +20697,13 @@ function slackReplyThreadTs(msg) {
|
|
|
20405
20697
|
if (msg.kind === "mention") return msg.ts;
|
|
20406
20698
|
return void 0;
|
|
20407
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
|
+
}
|
|
20408
20707
|
var SLACK_SEEN_REACTION = "eyes";
|
|
20409
20708
|
function writeTokenForTeam(teamId) {
|
|
20410
20709
|
const workspaces = listSlackWorkspacesRaw();
|
|
@@ -20441,8 +20740,8 @@ async function ackSlackInboundSeen(msg, opts) {
|
|
|
20441
20740
|
log(`react error: ${errMsg}${hint}`);
|
|
20442
20741
|
}
|
|
20443
20742
|
}
|
|
20444
|
-
|
|
20445
|
-
|
|
20743
|
+
function replyStub(target) {
|
|
20744
|
+
return {
|
|
20446
20745
|
teamId: target.teamId,
|
|
20447
20746
|
channelId: target.channelId,
|
|
20448
20747
|
ts: target.threadTs ?? "",
|
|
@@ -20450,12 +20749,15 @@ async function postSlackText(target, text3, opts) {
|
|
|
20450
20749
|
text: "",
|
|
20451
20750
|
kind: "dm"
|
|
20452
20751
|
};
|
|
20752
|
+
}
|
|
20753
|
+
async function postSlackText(target, text3, opts) {
|
|
20453
20754
|
if (opts.postReply) {
|
|
20454
|
-
await opts.postReply(
|
|
20455
|
-
|
|
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;
|
|
20456
20758
|
}
|
|
20457
20759
|
const token = writeTokenForTeam(target.teamId);
|
|
20458
|
-
await slackApi(
|
|
20760
|
+
const json = await slackApi(
|
|
20459
20761
|
token,
|
|
20460
20762
|
"chat.postMessage",
|
|
20461
20763
|
{
|
|
@@ -20465,18 +20767,163 @@ async function postSlackText(target, text3, opts) {
|
|
|
20465
20767
|
},
|
|
20466
20768
|
opts.fetchImpl
|
|
20467
20769
|
);
|
|
20770
|
+
const ts = json.ts?.trim();
|
|
20771
|
+
return ts ? { ts } : null;
|
|
20468
20772
|
}
|
|
20469
|
-
async function
|
|
20470
|
-
|
|
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",
|
|
20471
20782
|
{
|
|
20472
|
-
|
|
20473
|
-
|
|
20474
|
-
|
|
20783
|
+
channel: target.channelId,
|
|
20784
|
+
ts,
|
|
20785
|
+
text: text3
|
|
20475
20786
|
},
|
|
20476
|
-
|
|
20477
|
-
|
|
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
|
|
20478
20804
|
);
|
|
20479
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
|
+
}
|
|
20480
20927
|
var lastRelayed = /* @__PURE__ */ new Map();
|
|
20481
20928
|
function markRelayed(threadId, text3) {
|
|
20482
20929
|
lastRelayed.set(threadId, `${threadId}:${text3}`);
|
|
@@ -20568,10 +21015,15 @@ async function handleSlackInbound(msg, opts) {
|
|
|
20568
21015
|
});
|
|
20569
21016
|
};
|
|
20570
21017
|
bindReplyTarget(fresh.id);
|
|
21018
|
+
const progress = startSlackTurnProgress(msg, () => fresh.id, opts);
|
|
20571
21019
|
const runTurn = async (threadId) => {
|
|
20572
21020
|
await orch.send(threadId, prompt);
|
|
20573
21021
|
await orch.waitForTurn(threadId, 14 * 60 * 1e3);
|
|
20574
21022
|
};
|
|
21023
|
+
const dropProgress = async (reason) => {
|
|
21024
|
+
log(reason);
|
|
21025
|
+
await progress.discard();
|
|
21026
|
+
};
|
|
20575
21027
|
let reply;
|
|
20576
21028
|
try {
|
|
20577
21029
|
try {
|
|
@@ -20584,12 +21036,12 @@ async function handleSlackInbound(msg, opts) {
|
|
|
20584
21036
|
await runTurn(fresh.id);
|
|
20585
21037
|
}
|
|
20586
21038
|
if (slackInboundSuperseded(opts)) {
|
|
20587
|
-
|
|
21039
|
+
await dropProgress(`turn finished ${msg.ts} (superseded)`);
|
|
20588
21040
|
return;
|
|
20589
21041
|
}
|
|
20590
21042
|
let after = readThread(fresh.id);
|
|
20591
21043
|
if (after?.status === "stopped") {
|
|
20592
|
-
|
|
21044
|
+
await dropProgress(`turn finished ${msg.ts} (interrupted, skip post)`);
|
|
20593
21045
|
return;
|
|
20594
21046
|
}
|
|
20595
21047
|
if (!after || after.status === "archived") {
|
|
@@ -20598,46 +21050,48 @@ async function handleSlackInbound(msg, opts) {
|
|
|
20598
21050
|
bindReplyTarget(fresh.id);
|
|
20599
21051
|
await runTurn(fresh.id);
|
|
20600
21052
|
if (slackInboundSuperseded(opts)) {
|
|
20601
|
-
|
|
21053
|
+
await dropProgress(`turn finished ${msg.ts} (superseded)`);
|
|
20602
21054
|
return;
|
|
20603
21055
|
}
|
|
20604
21056
|
after = readThread(fresh.id);
|
|
20605
21057
|
if (!after || after.status === "stopped" || after.status === "archived") {
|
|
20606
|
-
|
|
21058
|
+
await dropProgress(`turn finished ${msg.ts} (interrupted, skip post)`);
|
|
20607
21059
|
return;
|
|
20608
21060
|
}
|
|
20609
21061
|
}
|
|
20610
21062
|
reply = orch.getTurnResult(fresh.id).text.trim();
|
|
20611
21063
|
} catch (err) {
|
|
20612
21064
|
if (slackInboundSuperseded(opts)) {
|
|
20613
|
-
|
|
21065
|
+
await dropProgress(`turn error ${msg.ts} (superseded)`);
|
|
20614
21066
|
return;
|
|
20615
21067
|
}
|
|
20616
21068
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
20617
21069
|
if (slackCoordinatorGone(err)) {
|
|
20618
|
-
|
|
21070
|
+
await dropProgress(`turn finished ${msg.ts} (thread gone, skip post)`);
|
|
20619
21071
|
return;
|
|
20620
21072
|
}
|
|
20621
21073
|
const timedOut = /timed out|timeout/i.test(errMsg);
|
|
20622
21074
|
const errorReply = timedOut ? SLACK_LISTEN_TIMEOUT_REPLY : `Sideboard coordinator error: ${errMsg}`;
|
|
20623
|
-
|
|
21075
|
+
try {
|
|
21076
|
+
await progress.finishWith(errorReply);
|
|
21077
|
+
log(`replied error ${msg.ts}: ${errMsg}`);
|
|
21078
|
+
} catch (postErr) {
|
|
20624
21079
|
const postMsg = postErr instanceof Error ? postErr.message : String(postErr);
|
|
20625
21080
|
log(`post error: ${postMsg}`);
|
|
20626
|
-
}
|
|
20627
|
-
log(`replied error ${msg.ts}: ${errMsg}`);
|
|
21081
|
+
}
|
|
20628
21082
|
if (!timedOut) throw err;
|
|
20629
21083
|
return;
|
|
20630
21084
|
}
|
|
20631
21085
|
if (!reply) {
|
|
20632
|
-
|
|
21086
|
+
await dropProgress(`post error: no agent text to send for ${msg.ts}`);
|
|
20633
21087
|
return;
|
|
20634
21088
|
}
|
|
20635
21089
|
if (alreadyRelayed(fresh.id, reply)) {
|
|
20636
|
-
|
|
21090
|
+
await dropProgress(`turn finished ${msg.ts} (already posted)`);
|
|
20637
21091
|
return;
|
|
20638
21092
|
}
|
|
20639
21093
|
try {
|
|
20640
|
-
await
|
|
21094
|
+
await progress.finishWith(reply);
|
|
20641
21095
|
markRelayed(fresh.id, reply);
|
|
20642
21096
|
log(`replied ${msg.ts} (${reply.length} chars)`);
|
|
20643
21097
|
} catch (err) {
|
|
@@ -20964,7 +21418,7 @@ var import_node_http3 = require("http");
|
|
|
20964
21418
|
var import_ws3 = require("ws");
|
|
20965
21419
|
|
|
20966
21420
|
// src/slack/relay-static.ts
|
|
20967
|
-
var
|
|
21421
|
+
var import_node_fs45 = require("fs");
|
|
20968
21422
|
var import_promises = require("fs/promises");
|
|
20969
21423
|
var import_node_path41 = __toESM(require("path"), 1);
|
|
20970
21424
|
var TYPES = {
|
|
@@ -21023,7 +21477,7 @@ function sendFile(req, res, file, size) {
|
|
|
21023
21477
|
res.end();
|
|
21024
21478
|
return true;
|
|
21025
21479
|
}
|
|
21026
|
-
(0,
|
|
21480
|
+
(0, import_node_fs45.createReadStream)(file).pipe(res);
|
|
21027
21481
|
return true;
|
|
21028
21482
|
}
|
|
21029
21483
|
async function tryServeStatic(req, res, root) {
|
|
@@ -21188,6 +21642,20 @@ async function startSlackRelayServer(opts) {
|
|
|
21188
21642
|
}
|
|
21189
21643
|
}
|
|
21190
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);
|
|
21191
21659
|
log("desktop connected");
|
|
21192
21660
|
ws.on("message", (data) => {
|
|
21193
21661
|
const raw = typeof data === "string" ? data : data.toString("utf8");
|
|
@@ -21299,6 +21767,8 @@ async function startSlackRelayServer(opts) {
|
|
|
21299
21767
|
SLACK_LISTEN_TIMEOUT_REPLY,
|
|
21300
21768
|
SLACK_OAUTH_CANCELLED,
|
|
21301
21769
|
SLACK_OAUTH_REDIRECT,
|
|
21770
|
+
SLACK_PROGRESS_DELAY_MS,
|
|
21771
|
+
SLACK_PROGRESS_EDIT_MS,
|
|
21302
21772
|
SLACK_REPLY_FORMATTING,
|
|
21303
21773
|
SLACK_SEEN_REACTION,
|
|
21304
21774
|
SlackOAuthCancelledError,
|
|
@@ -21456,6 +21926,7 @@ async function startSlackRelayServer(opts) {
|
|
|
21456
21926
|
formatSlackInboundPrompt,
|
|
21457
21927
|
formatSlackRepliesForTurn,
|
|
21458
21928
|
formatSlackSignedReply,
|
|
21929
|
+
formatSlackWorkingText,
|
|
21459
21930
|
formatTranscriptMarkdown,
|
|
21460
21931
|
formatUiReminder,
|
|
21461
21932
|
formatWorkspaceInventory,
|
|
@@ -21751,6 +22222,7 @@ async function startSlackRelayServer(opts) {
|
|
|
21751
22222
|
thisProcessShouldDrainAgentQueues,
|
|
21752
22223
|
threadDisplayLabel,
|
|
21753
22224
|
threadFilePath,
|
|
22225
|
+
threadLivePath,
|
|
21754
22226
|
threadLockPath,
|
|
21755
22227
|
threadRequestsBrightsyMcp,
|
|
21756
22228
|
threadsDir,
|
|
@@ -21774,6 +22246,7 @@ async function startSlackRelayServer(opts) {
|
|
|
21774
22246
|
userClaudeMcpConfigPath,
|
|
21775
22247
|
userCursorMcpConfigPath,
|
|
21776
22248
|
validateLinearApiKey,
|
|
22249
|
+
waitForPidExit,
|
|
21777
22250
|
warmGithubAgentAuth,
|
|
21778
22251
|
withAgentInstructions,
|
|
21779
22252
|
withExportedPath,
|