@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.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
listWorkspaces,
|
|
5
5
|
removeWorkspace,
|
|
6
6
|
syncWorkspacesFromThreads
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-27MCESES.js";
|
|
8
8
|
import {
|
|
9
9
|
BRIGHTSY_MCP_ALLOWED_TOOLS,
|
|
10
10
|
CLAUDE_MODEL_CATALOG,
|
|
@@ -65,7 +65,7 @@ import {
|
|
|
65
65
|
threadRequestsBrightsyMcp,
|
|
66
66
|
totalTokens,
|
|
67
67
|
writeInjectedMcpConfig
|
|
68
|
-
} from "./chunk-
|
|
68
|
+
} from "./chunk-H6LP57EF.js";
|
|
69
69
|
import {
|
|
70
70
|
CLOUD_COORDINATOR_BUSY_REPLY,
|
|
71
71
|
CLOUD_COORDINATOR_STOPPED_REPLY,
|
|
@@ -93,7 +93,7 @@ import {
|
|
|
93
93
|
parseForceStopMessage,
|
|
94
94
|
slackCoordinatorSourceRef,
|
|
95
95
|
takenTeamSlugsForOrchestration
|
|
96
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-HHLZWO7U.js";
|
|
97
97
|
import {
|
|
98
98
|
COORDINATOR_TOOL_PLAYBOOK,
|
|
99
99
|
SLACK_REPLY_FORMATTING,
|
|
@@ -102,7 +102,7 @@ import {
|
|
|
102
102
|
enrichWorkspacesWithGithub,
|
|
103
103
|
ensureGlobalCoordinatorCwd,
|
|
104
104
|
formatWorkspaceInventory
|
|
105
|
-
} from "./chunk-
|
|
105
|
+
} from "./chunk-ETMRBJKA.js";
|
|
106
106
|
import {
|
|
107
107
|
brightsyConfigPath,
|
|
108
108
|
brightsyMcpServerName,
|
|
@@ -114,7 +114,7 @@ import {
|
|
|
114
114
|
loadBrightsyConfig,
|
|
115
115
|
saveBrightsyConfig,
|
|
116
116
|
switchBrightsyAccount
|
|
117
|
-
} from "./chunk-
|
|
117
|
+
} from "./chunk-2GO3YKBA.js";
|
|
118
118
|
import {
|
|
119
119
|
LEGACY_PLAN_FILE_REL,
|
|
120
120
|
PLAN_FILE_NAME,
|
|
@@ -147,7 +147,7 @@ import {
|
|
|
147
147
|
isThreadCaffeinated,
|
|
148
148
|
releaseCaffeinateHoldForThread,
|
|
149
149
|
setCaffeinateHold
|
|
150
|
-
} from "./chunk-
|
|
150
|
+
} from "./chunk-DCOZABNT.js";
|
|
151
151
|
import {
|
|
152
152
|
FAMOUS_SOCCER_TEAMS,
|
|
153
153
|
addPrStackLayer,
|
|
@@ -167,6 +167,7 @@ import {
|
|
|
167
167
|
currentBranch,
|
|
168
168
|
detectGhStack,
|
|
169
169
|
detectLocalMergeConflicts,
|
|
170
|
+
enqueueByKey,
|
|
170
171
|
ensureGhPreferOrigin,
|
|
171
172
|
extractGhErrorDetail,
|
|
172
173
|
fetchPrHead,
|
|
@@ -226,10 +227,11 @@ import {
|
|
|
226
227
|
takenSlugsFromThread,
|
|
227
228
|
threadDisplayLabel,
|
|
228
229
|
warmGithubAgentAuth,
|
|
230
|
+
withRepoGitLock,
|
|
229
231
|
worktreeDisplayLabel,
|
|
230
232
|
worktreeDisplayLabelForGroup,
|
|
231
233
|
worktreeNameFromPath
|
|
232
|
-
} from "./chunk-
|
|
234
|
+
} from "./chunk-DHAGMJIK.js";
|
|
233
235
|
import {
|
|
234
236
|
ATTACHMENTS_DIR,
|
|
235
237
|
LEGACY_ATTACHMENTS_DIR,
|
|
@@ -298,7 +300,7 @@ import {
|
|
|
298
300
|
updateIntegrationsSettings,
|
|
299
301
|
updateOpencodeSettings,
|
|
300
302
|
writeSecureJson
|
|
301
|
-
} from "./chunk-
|
|
303
|
+
} from "./chunk-XHZJJWLI.js";
|
|
302
304
|
import {
|
|
303
305
|
stripNestedElectronEnv
|
|
304
306
|
} from "./chunk-TLPJHLLM.js";
|
|
@@ -318,7 +320,7 @@ import {
|
|
|
318
320
|
updateThread,
|
|
319
321
|
withThreadLock,
|
|
320
322
|
writeThread
|
|
321
|
-
} from "./chunk-
|
|
323
|
+
} from "./chunk-KGSVLZV6.js";
|
|
322
324
|
import {
|
|
323
325
|
THINKING_EFFORTS,
|
|
324
326
|
isThinkingEffort,
|
|
@@ -349,11 +351,12 @@ import {
|
|
|
349
351
|
sideboardReposDir,
|
|
350
352
|
sideboardWorkspacesDir,
|
|
351
353
|
threadFilePath,
|
|
354
|
+
threadLivePath,
|
|
352
355
|
threadLockPath,
|
|
353
356
|
threadsDir,
|
|
354
357
|
workspaceSettingsSourceLabel,
|
|
355
358
|
worktreesRoot
|
|
356
|
-
} from "./chunk-
|
|
359
|
+
} from "./chunk-QZQEXME2.js";
|
|
357
360
|
import {
|
|
358
361
|
conductorBundledBinDir,
|
|
359
362
|
enrichPathWithNpmGlobalBin,
|
|
@@ -1386,9 +1389,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
1386
1389
|
`Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
|
|
1387
1390
|
);
|
|
1388
1391
|
}
|
|
1389
|
-
const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-
|
|
1392
|
+
const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-UR4E3CCG.js");
|
|
1390
1393
|
if (isGlobalThread2(thread)) {
|
|
1391
|
-
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-
|
|
1394
|
+
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-UWHSD65V.js");
|
|
1392
1395
|
ensureGlobalCoordinatorCwd2(
|
|
1393
1396
|
isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
|
|
1394
1397
|
);
|
|
@@ -3267,11 +3270,11 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
|
|
|
3267
3270
|
}
|
|
3268
3271
|
return out;
|
|
3269
3272
|
}
|
|
3270
|
-
function stageBuffersAsAttachments(worktreePath,
|
|
3271
|
-
if (
|
|
3273
|
+
function stageBuffersAsAttachments(worktreePath, buffers2) {
|
|
3274
|
+
if (buffers2.length === 0) return [];
|
|
3272
3275
|
const dir = ensureAttachmentsDir(worktreePath);
|
|
3273
3276
|
const out = [];
|
|
3274
|
-
for (const item of
|
|
3277
|
+
for (const item of buffers2) {
|
|
3275
3278
|
const originalName = (item.name || "file").replace(/[/\\]/g, "_") || "file";
|
|
3276
3279
|
try {
|
|
3277
3280
|
const buf = Buffer.from(item.dataBase64, "base64");
|
|
@@ -3291,8 +3294,8 @@ function stageBuffersAsAttachments(worktreePath, buffers) {
|
|
|
3291
3294
|
}
|
|
3292
3295
|
return out;
|
|
3293
3296
|
}
|
|
3294
|
-
function attachmentsFromBuffers(
|
|
3295
|
-
return
|
|
3297
|
+
function attachmentsFromBuffers(buffers2) {
|
|
3298
|
+
return buffers2.map((item) => {
|
|
3296
3299
|
const name = (item.name || "file").replace(/[/\\]/g, "_") || "file";
|
|
3297
3300
|
try {
|
|
3298
3301
|
const buf = Buffer.from(item.dataBase64, "base64");
|
|
@@ -3881,7 +3884,7 @@ async function createThread(input, _onSetupLine) {
|
|
|
3881
3884
|
return readThread(thread.id) ?? thread;
|
|
3882
3885
|
}
|
|
3883
3886
|
async function listLinearIssues(agent, repoPath) {
|
|
3884
|
-
const { getAdapter: getAdapter2 } = await import("./agents-
|
|
3887
|
+
const { getAdapter: getAdapter2 } = await import("./agents-XZBAKBO2.js");
|
|
3885
3888
|
await requireAgent(agent, { requireLinear: true });
|
|
3886
3889
|
const adapter = getAdapter2(agent);
|
|
3887
3890
|
if (!adapter.listLinearIssues) {
|
|
@@ -3932,8 +3935,11 @@ function worktreeBindingFrom(from) {
|
|
|
3932
3935
|
};
|
|
3933
3936
|
}
|
|
3934
3937
|
function threadsSharingWorktree(worktreePath) {
|
|
3938
|
+
if (!worktreePath) return [];
|
|
3935
3939
|
const key = normalizeWorktreePath(worktreePath);
|
|
3936
|
-
return listThreads({ includeArchived: true }).filter(
|
|
3940
|
+
return listThreads({ includeArchived: true }).filter(
|
|
3941
|
+
(t) => Boolean(t.worktreePath) && sameWorktreePath(t.worktreePath, key) && t.status !== "archived"
|
|
3942
|
+
).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
3937
3943
|
}
|
|
3938
3944
|
function formatTranscriptMarkdown(title, messages) {
|
|
3939
3945
|
const body = formatMessagesAsTranscript(messages, { tools: "full" });
|
|
@@ -4434,7 +4440,7 @@ async function adoptThread(input) {
|
|
|
4434
4440
|
messages: input.messages ?? []
|
|
4435
4441
|
});
|
|
4436
4442
|
writeThread(thread);
|
|
4437
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-
|
|
4443
|
+
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-TKJXPNV4.js");
|
|
4438
4444
|
await ensureWorkspace2(repoPath);
|
|
4439
4445
|
return thread;
|
|
4440
4446
|
}
|
|
@@ -5200,7 +5206,7 @@ async function refreshSlackReplyBadges(opts) {
|
|
|
5200
5206
|
}
|
|
5201
5207
|
|
|
5202
5208
|
// src/orchestrator/orchestrator.ts
|
|
5203
|
-
import { existsSync as
|
|
5209
|
+
import { existsSync as existsSync15 } from "fs";
|
|
5204
5210
|
|
|
5205
5211
|
// src/git/pr-merge-archive.ts
|
|
5206
5212
|
function normalizePrState(state) {
|
|
@@ -5445,9 +5451,132 @@ async function cloneRepoIntoSideboard(opts) {
|
|
|
5445
5451
|
return { repoPath, workspace };
|
|
5446
5452
|
}
|
|
5447
5453
|
|
|
5454
|
+
// src/store/turn-live.ts
|
|
5455
|
+
import {
|
|
5456
|
+
existsSync as existsSync13,
|
|
5457
|
+
renameSync,
|
|
5458
|
+
unlinkSync as unlinkSync2,
|
|
5459
|
+
writeFileSync as writeFileSync4,
|
|
5460
|
+
readFileSync as readFileSync10
|
|
5461
|
+
} from "fs";
|
|
5462
|
+
var buffers = /* @__PURE__ */ new Map();
|
|
5463
|
+
var FLUSH_MS = 800;
|
|
5464
|
+
var MAX_PARTS = 80;
|
|
5465
|
+
function trimParts(parts) {
|
|
5466
|
+
if (parts.length <= MAX_PARTS) return parts;
|
|
5467
|
+
return parts.slice(-Math.floor(MAX_PARTS / 2));
|
|
5468
|
+
}
|
|
5469
|
+
function lastText(parts, type) {
|
|
5470
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
5471
|
+
const p = parts[i];
|
|
5472
|
+
if (p.type === type && p.text.trim()) return p.text.trim();
|
|
5473
|
+
}
|
|
5474
|
+
return "";
|
|
5475
|
+
}
|
|
5476
|
+
function summarizeTurnLive(parts) {
|
|
5477
|
+
const tools = parts.filter(
|
|
5478
|
+
(p) => p.type === "tool"
|
|
5479
|
+
);
|
|
5480
|
+
const lastTool = tools[tools.length - 1];
|
|
5481
|
+
const lastToolLabel = lastTool ? lastTool.description || toolDescription(lastTool.name, lastTool.input) || lastTool.name : void 0;
|
|
5482
|
+
const running = lastTool?.status === "running";
|
|
5483
|
+
const thinking = lastText(parts, "thinking");
|
|
5484
|
+
const text3 = lastText(parts, "text");
|
|
5485
|
+
const excerptRaw = thinking || text3;
|
|
5486
|
+
const excerpt = excerptRaw.length > 280 ? `${excerptRaw.slice(-280)}` : excerptRaw || void 0;
|
|
5487
|
+
let summary = "Working\u2026";
|
|
5488
|
+
if (lastToolLabel) {
|
|
5489
|
+
const verb = running ? lastToolLabel : `Finished ${lastToolLabel}`;
|
|
5490
|
+
summary = tools.length > 1 ? `${verb} (${tools.length} tools)` : verb;
|
|
5491
|
+
} else if (thinking) {
|
|
5492
|
+
summary = "Thinking\u2026";
|
|
5493
|
+
} else if (text3) {
|
|
5494
|
+
summary = "Writing reply\u2026";
|
|
5495
|
+
}
|
|
5496
|
+
return {
|
|
5497
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5498
|
+
summary,
|
|
5499
|
+
lastTool: lastToolLabel,
|
|
5500
|
+
toolCount: tools.length,
|
|
5501
|
+
excerpt
|
|
5502
|
+
};
|
|
5503
|
+
}
|
|
5504
|
+
function flush(threadId) {
|
|
5505
|
+
const buf = buffers.get(threadId);
|
|
5506
|
+
if (!buf) return;
|
|
5507
|
+
if (buf.timer) {
|
|
5508
|
+
clearTimeout(buf.timer);
|
|
5509
|
+
buf.timer = null;
|
|
5510
|
+
}
|
|
5511
|
+
writeTurnLive(threadId, summarizeTurnLive(buf.parts));
|
|
5512
|
+
}
|
|
5513
|
+
function scheduleFlush(threadId) {
|
|
5514
|
+
const buf = buffers.get(threadId);
|
|
5515
|
+
if (!buf) return;
|
|
5516
|
+
if (buf.timer) return;
|
|
5517
|
+
buf.timer = setTimeout(() => {
|
|
5518
|
+
const live = buffers.get(threadId);
|
|
5519
|
+
if (live) live.timer = null;
|
|
5520
|
+
flush(threadId);
|
|
5521
|
+
}, FLUSH_MS);
|
|
5522
|
+
}
|
|
5523
|
+
function noteTurnLiveEvent(threadId, event) {
|
|
5524
|
+
if (event.type === "session_id" || event.type === "usage" || event.type === "stderr") {
|
|
5525
|
+
return;
|
|
5526
|
+
}
|
|
5527
|
+
const prev = buffers.get(threadId) ?? { parts: [], timer: null };
|
|
5528
|
+
prev.parts = trimParts(applyAgentEvent(prev.parts, event));
|
|
5529
|
+
buffers.set(threadId, prev);
|
|
5530
|
+
if (event.type === "exit") {
|
|
5531
|
+
flush(threadId);
|
|
5532
|
+
const cur = buffers.get(threadId);
|
|
5533
|
+
if (cur?.timer) clearTimeout(cur.timer);
|
|
5534
|
+
buffers.delete(threadId);
|
|
5535
|
+
return;
|
|
5536
|
+
}
|
|
5537
|
+
const immediate = event.type === "tool_use" || event.type === "tool_result";
|
|
5538
|
+
if (immediate) flush(threadId);
|
|
5539
|
+
else scheduleFlush(threadId);
|
|
5540
|
+
}
|
|
5541
|
+
function writeTurnLive(threadId, progress) {
|
|
5542
|
+
const path2 = threadLivePath(threadId);
|
|
5543
|
+
const tmp = `${path2}.${process.pid}.tmp`;
|
|
5544
|
+
try {
|
|
5545
|
+
writeFileSync4(tmp, JSON.stringify(progress), "utf8");
|
|
5546
|
+
renameSync(tmp, path2);
|
|
5547
|
+
} catch {
|
|
5548
|
+
try {
|
|
5549
|
+
unlinkSync2(tmp);
|
|
5550
|
+
} catch {
|
|
5551
|
+
}
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
function readTurnLive(threadId) {
|
|
5555
|
+
const path2 = threadLivePath(threadId);
|
|
5556
|
+
if (!existsSync13(path2)) return null;
|
|
5557
|
+
try {
|
|
5558
|
+
const raw = JSON.parse(readFileSync10(path2, "utf8"));
|
|
5559
|
+
if (!raw || typeof raw.summary !== "string") return null;
|
|
5560
|
+
return raw;
|
|
5561
|
+
} catch {
|
|
5562
|
+
return null;
|
|
5563
|
+
}
|
|
5564
|
+
}
|
|
5565
|
+
function clearTurnLive(threadId) {
|
|
5566
|
+
const buf = buffers.get(threadId);
|
|
5567
|
+
if (buf?.timer) clearTimeout(buf.timer);
|
|
5568
|
+
buffers.delete(threadId);
|
|
5569
|
+
const path2 = threadLivePath(threadId);
|
|
5570
|
+
if (!existsSync13(path2)) return;
|
|
5571
|
+
try {
|
|
5572
|
+
unlinkSync2(path2);
|
|
5573
|
+
} catch {
|
|
5574
|
+
}
|
|
5575
|
+
}
|
|
5576
|
+
|
|
5448
5577
|
// src/review/request-review.ts
|
|
5449
5578
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
5450
|
-
import { existsSync as
|
|
5579
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
|
|
5451
5580
|
import { dirname as dirname3, join as join13 } from "path";
|
|
5452
5581
|
|
|
5453
5582
|
// src/review/review-request-template.ts
|
|
@@ -5593,9 +5722,9 @@ function shouldRefreshReviewRequestTemplate(content) {
|
|
|
5593
5722
|
return LEGACY_REVIEW_TEMPLATE_MARKERS.every((m) => trimmed.includes(m));
|
|
5594
5723
|
}
|
|
5595
5724
|
function readTextIfPresent(abs) {
|
|
5596
|
-
if (!
|
|
5725
|
+
if (!existsSync14(abs)) return null;
|
|
5597
5726
|
try {
|
|
5598
|
-
const content =
|
|
5727
|
+
const content = readFileSync11(abs, "utf8");
|
|
5599
5728
|
return content.trim() ? content : null;
|
|
5600
5729
|
} catch {
|
|
5601
5730
|
return null;
|
|
@@ -5603,9 +5732,9 @@ function readTextIfPresent(abs) {
|
|
|
5603
5732
|
}
|
|
5604
5733
|
function ensureAttachmentsGitignore(worktreePath) {
|
|
5605
5734
|
const gitignoreAbs = join13(worktreePath, ATTACHMENTS_DIR, ".gitignore");
|
|
5606
|
-
if (
|
|
5735
|
+
if (existsSync14(gitignoreAbs)) return;
|
|
5607
5736
|
mkdirSync4(dirname3(gitignoreAbs), { recursive: true });
|
|
5608
|
-
|
|
5737
|
+
writeFileSync5(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
|
|
5609
5738
|
}
|
|
5610
5739
|
function resolveReviewGuidelines(worktreePath) {
|
|
5611
5740
|
const repoAbs = join13(worktreePath, REPO_REVIEW_PATH);
|
|
@@ -5640,7 +5769,7 @@ function resolveReviewGuidelines(worktreePath) {
|
|
|
5640
5769
|
}
|
|
5641
5770
|
ensureAttachmentsGitignore(worktreePath);
|
|
5642
5771
|
mkdirSync4(dirname3(localAbs), { recursive: true });
|
|
5643
|
-
|
|
5772
|
+
writeFileSync5(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
|
|
5644
5773
|
return {
|
|
5645
5774
|
path: REVIEW_REQUEST_PATH,
|
|
5646
5775
|
name: REVIEW_REQUEST_NAME,
|
|
@@ -5662,7 +5791,7 @@ function ensureReviewRequestFile(worktreePath) {
|
|
|
5662
5791
|
const localAbs = join13(worktreePath, REVIEW_REQUEST_PATH);
|
|
5663
5792
|
const localContent = readTextIfPresent(localAbs) ?? readTextIfPresent(join13(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
|
|
5664
5793
|
if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
|
|
5665
|
-
const path2 =
|
|
5794
|
+
const path2 = existsSync14(localAbs) ? REVIEW_REQUEST_PATH : LEGACY_REVIEW_REQUEST_PATH;
|
|
5666
5795
|
return {
|
|
5667
5796
|
path: path2,
|
|
5668
5797
|
name: REVIEW_REQUEST_NAME,
|
|
@@ -5671,7 +5800,7 @@ function ensureReviewRequestFile(worktreePath) {
|
|
|
5671
5800
|
};
|
|
5672
5801
|
}
|
|
5673
5802
|
mkdirSync4(dirname3(repoAbs), { recursive: true });
|
|
5674
|
-
|
|
5803
|
+
writeFileSync5(repoAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
|
|
5675
5804
|
return {
|
|
5676
5805
|
path: REPO_REVIEW_PATH,
|
|
5677
5806
|
name: REPO_REVIEW_NAME,
|
|
@@ -5864,6 +5993,15 @@ function isPidAlive(pid) {
|
|
|
5864
5993
|
return false;
|
|
5865
5994
|
}
|
|
5866
5995
|
}
|
|
5996
|
+
async function waitForPidExit(pid, timeoutMs) {
|
|
5997
|
+
const start = Date.now();
|
|
5998
|
+
while (isPidAlive(pid)) {
|
|
5999
|
+
if (Date.now() - start >= timeoutMs) return false;
|
|
6000
|
+
await new Promise((r) => setTimeout(r, 80));
|
|
6001
|
+
}
|
|
6002
|
+
return true;
|
|
6003
|
+
}
|
|
6004
|
+
var STALE_AGENT_PID_WAIT_MS = 2500;
|
|
5867
6005
|
function writeLiveStatus(threadId, status, lastError) {
|
|
5868
6006
|
const latest = readThread(threadId);
|
|
5869
6007
|
if (!latest || latest.status === "archived") return latest;
|
|
@@ -5941,7 +6079,7 @@ var Orchestrator = class {
|
|
|
5941
6079
|
}
|
|
5942
6080
|
continue;
|
|
5943
6081
|
}
|
|
5944
|
-
if (!
|
|
6082
|
+
if (!existsSync15(thread.worktreePath)) {
|
|
5945
6083
|
setStatus(thread.id, "broken", "Worktree missing on disk");
|
|
5946
6084
|
this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
|
|
5947
6085
|
continue;
|
|
@@ -5960,7 +6098,7 @@ var Orchestrator = class {
|
|
|
5960
6098
|
orphans: orphans.map((o) => ({ path: o.path, repoPath: o.repoPath }))
|
|
5961
6099
|
});
|
|
5962
6100
|
}
|
|
5963
|
-
const { autoCleanupOrphansEnabled: autoCleanupOrphansEnabled2 } = await import("./app-settings-
|
|
6101
|
+
const { autoCleanupOrphansEnabled: autoCleanupOrphansEnabled2 } = await import("./app-settings-EKJM2344.js");
|
|
5964
6102
|
if (autoCleanupOrphansEnabled2() && shouldRunWorktreeCleanup() && orphans.length > 0) {
|
|
5965
6103
|
await cleanupOrphanWorktrees({ repoPaths });
|
|
5966
6104
|
}
|
|
@@ -6128,7 +6266,7 @@ var Orchestrator = class {
|
|
|
6128
6266
|
}
|
|
6129
6267
|
}
|
|
6130
6268
|
await setup;
|
|
6131
|
-
const { autoRunAfterSetupEnabled: autoRunAfterSetupEnabled2 } = await import("./app-settings-
|
|
6269
|
+
const { autoRunAfterSetupEnabled: autoRunAfterSetupEnabled2 } = await import("./app-settings-EKJM2344.js");
|
|
6132
6270
|
if (autoRunAfterSetupEnabled2()) {
|
|
6133
6271
|
try {
|
|
6134
6272
|
await this.startDev(threadId);
|
|
@@ -6262,14 +6400,10 @@ var Orchestrator = class {
|
|
|
6262
6400
|
const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id);
|
|
6263
6401
|
const livePid = current.agentPid;
|
|
6264
6402
|
const foreignLive = !inFlight && typeof livePid === "number" && livePid > 0 && isPidAlive(livePid);
|
|
6265
|
-
if (inFlight) {
|
|
6403
|
+
if (inFlight || foreignLive) {
|
|
6266
6404
|
this.stop(thread.id, { clearQueue: false, continueQueue: true });
|
|
6267
|
-
} else {
|
|
6268
|
-
if (foreignLive) {
|
|
6269
|
-
this.stop(thread.id, { clearQueue: false, continueQueue: true });
|
|
6270
|
-
}
|
|
6271
|
-
void this.drainQueue(thread.id);
|
|
6272
6405
|
}
|
|
6406
|
+
void this.drainQueue(thread.id);
|
|
6273
6407
|
return this.requireThread(thread.id);
|
|
6274
6408
|
}
|
|
6275
6409
|
async drainQueue(threadId) {
|
|
@@ -6298,7 +6432,18 @@ var Orchestrator = class {
|
|
|
6298
6432
|
}
|
|
6299
6433
|
const livePid = thread.agentPid;
|
|
6300
6434
|
if (typeof livePid === "number" && livePid > 0 && isPidAlive(livePid)) {
|
|
6301
|
-
|
|
6435
|
+
const exited = await waitForPidExit(livePid, STALE_AGENT_PID_WAIT_MS);
|
|
6436
|
+
if (!exited && isPidAlive(livePid)) {
|
|
6437
|
+
try {
|
|
6438
|
+
process.kill(livePid, "SIGKILL");
|
|
6439
|
+
} catch {
|
|
6440
|
+
}
|
|
6441
|
+
await waitForPidExit(livePid, 400);
|
|
6442
|
+
try {
|
|
6443
|
+
updateThread(threadId, { agentPid: null });
|
|
6444
|
+
} catch {
|
|
6445
|
+
}
|
|
6446
|
+
}
|
|
6302
6447
|
continue;
|
|
6303
6448
|
}
|
|
6304
6449
|
const prompt = thread.queue[0];
|
|
@@ -6406,7 +6551,7 @@ var Orchestrator = class {
|
|
|
6406
6551
|
}
|
|
6407
6552
|
const isBrightsy = fresh.agent === "brightsy";
|
|
6408
6553
|
const isOrchestration = isOrchestratorThread(fresh);
|
|
6409
|
-
const { autoRenameBranchEnabled: autoRenameBranchEnabled2, getGithubGitAuthMode: getGithubGitAuthMode2 } = await import("./app-settings-
|
|
6554
|
+
const { autoRenameBranchEnabled: autoRenameBranchEnabled2, getGithubGitAuthMode: getGithubGitAuthMode2 } = await import("./app-settings-EKJM2344.js");
|
|
6410
6555
|
const gitAuthMode = getGithubGitAuthMode2();
|
|
6411
6556
|
const worktreeDirective = isBrightsy || isOrchestration ? null : formatWorktreeDirective(fresh, {
|
|
6412
6557
|
githubSlug: await resolveGithubRepoSlug(fresh.worktreePath).catch(
|
|
@@ -6455,6 +6600,7 @@ var Orchestrator = class {
|
|
|
6455
6600
|
{ cachedPrefix, prompt: agentPrompt },
|
|
6456
6601
|
(event) => {
|
|
6457
6602
|
this.emit({ type: "turn_output", threadId, event });
|
|
6603
|
+
noteTurnLiveEvent(threadId, event);
|
|
6458
6604
|
if (event.type === "session_id") {
|
|
6459
6605
|
updateThread(threadId, { sessionId: event.data });
|
|
6460
6606
|
}
|
|
@@ -6501,7 +6647,7 @@ var Orchestrator = class {
|
|
|
6501
6647
|
if (this.requireThread(threadId).agent === "cursor" && exitCode !== 0 && !assistantText && parts.length === 0) {
|
|
6502
6648
|
const sessionId = result.sessionId || this.requireThread(threadId).sessionId || "";
|
|
6503
6649
|
if (sessionId) {
|
|
6504
|
-
const { recoverFinishedCursorRun } = await import("./cursor-recover-
|
|
6650
|
+
const { recoverFinishedCursorRun } = await import("./cursor-recover-ZUJG6XRQ.js");
|
|
6505
6651
|
for (let i = 0; i < 8; i++) {
|
|
6506
6652
|
const recovered = recoverFinishedCursorRun({
|
|
6507
6653
|
agentId: sessionId,
|
|
@@ -6659,6 +6805,7 @@ var Orchestrator = class {
|
|
|
6659
6805
|
this.processes.delete(`${threadId}:agent`);
|
|
6660
6806
|
this.stoppedTurns.delete(threadId);
|
|
6661
6807
|
this.runningCount = Math.max(0, this.runningCount - 1);
|
|
6808
|
+
clearTurnLive(threadId);
|
|
6662
6809
|
try {
|
|
6663
6810
|
updateThread(threadId, { agentPid: null });
|
|
6664
6811
|
} catch {
|
|
@@ -6932,51 +7079,56 @@ var Orchestrator = class {
|
|
|
6932
7079
|
}
|
|
6933
7080
|
return created;
|
|
6934
7081
|
}
|
|
6935
|
-
async waitForTurn(threadRef, timeoutMs = 6e5) {
|
|
7082
|
+
async waitForTurn(threadRef, timeoutMs = 6e5, opts) {
|
|
6936
7083
|
const thread = this.requireThread(threadRef);
|
|
7084
|
+
if (!["running", "queued"].includes(thread.status)) {
|
|
7085
|
+
return thread;
|
|
7086
|
+
}
|
|
6937
7087
|
const start = Date.now();
|
|
6938
7088
|
return new Promise((resolve, reject) => {
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
7089
|
+
let settled = false;
|
|
7090
|
+
let timer;
|
|
7091
|
+
const finish = (fn) => {
|
|
7092
|
+
if (settled) return;
|
|
7093
|
+
settled = true;
|
|
7094
|
+
if (timer) clearInterval(timer);
|
|
7095
|
+
off();
|
|
7096
|
+
fn();
|
|
7097
|
+
};
|
|
6943
7098
|
const off = this.on((event) => {
|
|
6944
7099
|
if (event.type === "turn_finished" && event.threadId === thread.id) {
|
|
6945
|
-
off();
|
|
6946
7100
|
const latest = readThread(thread.id);
|
|
6947
7101
|
if (!latest) {
|
|
6948
|
-
reject(new Error(`Thread not found: ${thread.id}`));
|
|
7102
|
+
finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
|
|
6949
7103
|
return;
|
|
6950
7104
|
}
|
|
6951
|
-
resolve(latest);
|
|
7105
|
+
finish(() => resolve(latest));
|
|
6952
7106
|
}
|
|
6953
7107
|
if (event.type === "error" && event.threadId === thread.id) {
|
|
6954
|
-
off();
|
|
6955
7108
|
const latest = readThread(thread.id);
|
|
6956
7109
|
if (!latest) {
|
|
6957
|
-
reject(new Error(`Thread not found: ${thread.id}`));
|
|
7110
|
+
finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
|
|
6958
7111
|
return;
|
|
6959
7112
|
}
|
|
6960
|
-
resolve(latest);
|
|
7113
|
+
finish(() => resolve(latest));
|
|
6961
7114
|
}
|
|
6962
7115
|
});
|
|
6963
|
-
|
|
6964
|
-
if (Date.now() - start > timeoutMs) {
|
|
6965
|
-
clearInterval(timer);
|
|
6966
|
-
off();
|
|
6967
|
-
reject(new Error("wait_for_turn timed out"));
|
|
6968
|
-
}
|
|
7116
|
+
timer = setInterval(() => {
|
|
6969
7117
|
const current = readThread(thread.id);
|
|
6970
7118
|
if (!current) {
|
|
6971
|
-
|
|
6972
|
-
off();
|
|
6973
|
-
reject(new Error(`Thread not found: ${thread.id}`));
|
|
7119
|
+
finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
|
|
6974
7120
|
return;
|
|
6975
7121
|
}
|
|
6976
7122
|
if (!["running", "queued"].includes(current.status)) {
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
7123
|
+
finish(() => resolve(current));
|
|
7124
|
+
return;
|
|
7125
|
+
}
|
|
7126
|
+
if (Date.now() - start > timeoutMs) {
|
|
7127
|
+
if (opts?.resolveIfStillRunning) {
|
|
7128
|
+
finish(() => resolve(current));
|
|
7129
|
+
} else {
|
|
7130
|
+
finish(() => reject(new Error("wait_for_turn timed out")));
|
|
7131
|
+
}
|
|
6980
7132
|
}
|
|
6981
7133
|
}, 200);
|
|
6982
7134
|
});
|
|
@@ -6986,11 +7138,16 @@ var Orchestrator = class {
|
|
|
6986
7138
|
const lastAgent = [...thread.messages].reverse().find((m) => m.role === "agent");
|
|
6987
7139
|
const lastError = thread.lastError ?? null;
|
|
6988
7140
|
const text3 = (lastAgent?.text ?? "").trim() || (thread.status === "error" ? lastError ?? "" : "");
|
|
7141
|
+
const stillRunning = thread.status === "running" || thread.status === "queued";
|
|
7142
|
+
const live = stillRunning ? readTurnLive(thread.id) : null;
|
|
6989
7143
|
return {
|
|
6990
7144
|
text: text3,
|
|
6991
7145
|
status: thread.status,
|
|
6992
7146
|
sessionId: thread.sessionId,
|
|
6993
|
-
lastError
|
|
7147
|
+
lastError,
|
|
7148
|
+
stillRunning,
|
|
7149
|
+
progress: live?.summary ?? null,
|
|
7150
|
+
lastActivityAt: live?.updatedAt ?? null
|
|
6994
7151
|
};
|
|
6995
7152
|
}
|
|
6996
7153
|
assertNotGlobal(thread, action) {
|
|
@@ -7153,7 +7310,7 @@ var Orchestrator = class {
|
|
|
7153
7310
|
updateThread(thread.id, { title: meta.title });
|
|
7154
7311
|
}
|
|
7155
7312
|
}
|
|
7156
|
-
const { autoArchiveOnMergeEnabled: autoArchiveOnMergeEnabled2 } = await import("./app-settings-
|
|
7313
|
+
const { autoArchiveOnMergeEnabled: autoArchiveOnMergeEnabled2 } = await import("./app-settings-EKJM2344.js");
|
|
7157
7314
|
const latest = this.requireThread(thread.id);
|
|
7158
7315
|
if (!shouldAutoArchiveOnPrMerge({
|
|
7159
7316
|
previousPrState: prevState || null,
|
|
@@ -7416,7 +7573,19 @@ var Orchestrator = class {
|
|
|
7416
7573
|
const thread = this.requireThread(threadRef);
|
|
7417
7574
|
return threadsSharingWorktree(thread.worktreePath);
|
|
7418
7575
|
}
|
|
7576
|
+
/**
|
|
7577
|
+
* Last-tab teardown must see siblings already archived. Serialize per
|
|
7578
|
+
* worktree so parallel archive/purge of sibling tabs still removes once.
|
|
7579
|
+
*/
|
|
7580
|
+
enqueueWorktreeTeardown(thread, fn) {
|
|
7581
|
+
const key = isGlobalThread(thread) ? `archive:global:${thread.id}` : `archive:wt:${normalizeWorktreePath(thread.worktreePath)}`;
|
|
7582
|
+
return enqueueByKey(key, fn);
|
|
7583
|
+
}
|
|
7419
7584
|
async archive(threadRef) {
|
|
7585
|
+
const thread = this.requireThread(threadRef);
|
|
7586
|
+
return this.enqueueWorktreeTeardown(thread, () => this.archiveUnlocked(threadRef));
|
|
7587
|
+
}
|
|
7588
|
+
async archiveUnlocked(threadRef) {
|
|
7420
7589
|
const thread = this.requireThread(threadRef);
|
|
7421
7590
|
this.stop(thread.id);
|
|
7422
7591
|
this.releaseOrchestratorCaffeinate(thread);
|
|
@@ -7444,7 +7613,7 @@ var Orchestrator = class {
|
|
|
7444
7613
|
this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
|
|
7445
7614
|
if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
|
|
7446
7615
|
try {
|
|
7447
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-
|
|
7616
|
+
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-TKJXPNV4.js");
|
|
7448
7617
|
await ensureWorkspace2(thread.repoPath);
|
|
7449
7618
|
} catch {
|
|
7450
7619
|
}
|
|
@@ -7452,6 +7621,10 @@ var Orchestrator = class {
|
|
|
7452
7621
|
return archived;
|
|
7453
7622
|
}
|
|
7454
7623
|
async purge(threadRef, opts) {
|
|
7624
|
+
const thread = this.requireThread(threadRef);
|
|
7625
|
+
await this.enqueueWorktreeTeardown(thread, () => this.purgeUnlocked(threadRef, opts));
|
|
7626
|
+
}
|
|
7627
|
+
async purgeUnlocked(threadRef, opts) {
|
|
7455
7628
|
const thread = this.requireThread(threadRef);
|
|
7456
7629
|
this.stop(thread.id);
|
|
7457
7630
|
this.releaseOrchestratorCaffeinate(thread);
|
|
@@ -7466,7 +7639,7 @@ var Orchestrator = class {
|
|
|
7466
7639
|
await runArchiveScript(thread.repoPath, thread.worktreePath);
|
|
7467
7640
|
} catch {
|
|
7468
7641
|
}
|
|
7469
|
-
const { deleteBranchOnPurgeEnabled: deleteBranchOnPurgeEnabled2 } = await import("./app-settings-
|
|
7642
|
+
const { deleteBranchOnPurgeEnabled: deleteBranchOnPurgeEnabled2 } = await import("./app-settings-EKJM2344.js");
|
|
7470
7643
|
const deleteBranch = opts?.deleteBranch ?? deleteBranchOnPurgeEnabled2();
|
|
7471
7644
|
await removeWorktree(thread.repoPath, thread.worktreePath, {
|
|
7472
7645
|
deleteBranch: deleteBranch ? thread.branchName : void 0
|
|
@@ -7480,19 +7653,18 @@ var Orchestrator = class {
|
|
|
7480
7653
|
throw new Error("Thread is not archived");
|
|
7481
7654
|
}
|
|
7482
7655
|
if (isGlobalThread(thread)) {
|
|
7483
|
-
const { globalAgentCwd: globalAgentCwd2 } = await import("./paths-
|
|
7656
|
+
const { globalAgentCwd: globalAgentCwd2 } = await import("./paths-UMEDHOS5.js");
|
|
7484
7657
|
updateThread(thread.id, { worktreePath: globalAgentCwd2() });
|
|
7485
7658
|
const restored2 = setStatus(thread.id, "idle");
|
|
7486
7659
|
this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
|
|
7487
7660
|
return restored2;
|
|
7488
7661
|
}
|
|
7489
|
-
if (!
|
|
7490
|
-
const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-
|
|
7491
|
-
const { execa: execa5 } = await import("execa");
|
|
7662
|
+
if (!existsSync15(thread.worktreePath)) {
|
|
7663
|
+
const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-PEUPFAKL.js");
|
|
7492
7664
|
const slug = thread.worktreePath.split("/").pop();
|
|
7493
7665
|
const dest = thread.worktreePath;
|
|
7494
|
-
await
|
|
7495
|
-
|
|
7666
|
+
await withRepoGitLock(thread.repoPath, async () => {
|
|
7667
|
+
await git(["worktree", "add", dest, thread.branchName], thread.repoPath);
|
|
7496
7668
|
});
|
|
7497
7669
|
void createThreadWorktree2;
|
|
7498
7670
|
void slug;
|
|
@@ -7596,7 +7768,7 @@ async function startOrchestration(opts) {
|
|
|
7596
7768
|
sourceRef: "default",
|
|
7597
7769
|
...createOpts
|
|
7598
7770
|
}).catch(async () => {
|
|
7599
|
-
const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-
|
|
7771
|
+
const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-PEUPFAKL.js");
|
|
7600
7772
|
const repo = await resolveRepoRoot2(repoPath);
|
|
7601
7773
|
const def = await resolveDefaultBranch2(repo);
|
|
7602
7774
|
return orch.createThread({
|
|
@@ -7606,7 +7778,7 @@ async function startOrchestration(opts) {
|
|
|
7606
7778
|
repoPath: repo
|
|
7607
7779
|
});
|
|
7608
7780
|
});
|
|
7609
|
-
const { updateThread: upd } = await import("./thread-store-
|
|
7781
|
+
const { updateThread: upd } = await import("./thread-store-4OUEQ2DB.js");
|
|
7610
7782
|
const updated = upd(thread.id, {
|
|
7611
7783
|
sourceType: "orchestration",
|
|
7612
7784
|
sourceRef: goal
|
|
@@ -7729,6 +7901,15 @@ function mcpArchiveBlockedReason(thread) {
|
|
|
7729
7901
|
return null;
|
|
7730
7902
|
}
|
|
7731
7903
|
|
|
7904
|
+
// src/mcp/wait-for-turn.ts
|
|
7905
|
+
var MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
7906
|
+
function mcpWaitForTurnTimeoutMs(requested) {
|
|
7907
|
+
const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
|
|
7908
|
+
if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
|
|
7909
|
+
return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
|
|
7910
|
+
}
|
|
7911
|
+
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.";
|
|
7912
|
+
|
|
7732
7913
|
// src/mcp/slack-tools.ts
|
|
7733
7914
|
import { z } from "zod";
|
|
7734
7915
|
|
|
@@ -8356,7 +8537,7 @@ async function startMcpServer() {
|
|
|
8356
8537
|
);
|
|
8357
8538
|
}
|
|
8358
8539
|
try {
|
|
8359
|
-
const { maxConcurrentAgents: maxConcurrentAgents2 } = await import("./app-settings-
|
|
8540
|
+
const { maxConcurrentAgents: maxConcurrentAgents2 } = await import("./app-settings-EKJM2344.js");
|
|
8360
8541
|
orch.setMaxConcurrent(maxConcurrentAgents2());
|
|
8361
8542
|
} catch {
|
|
8362
8543
|
}
|
|
@@ -8399,7 +8580,9 @@ async function startMcpServer() {
|
|
|
8399
8580
|
const threads = orch.getThreads(true);
|
|
8400
8581
|
const lines = threads.map((t) => {
|
|
8401
8582
|
const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : basename4(t.repoPath) || t.repoPath;
|
|
8402
|
-
|
|
8583
|
+
const live = t.status === "running" || t.status === "queued" ? readTurnLive(t.id) : null;
|
|
8584
|
+
const progress = live?.summary ? ` ${live.summary}` : "";
|
|
8585
|
+
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}`;
|
|
8403
8586
|
});
|
|
8404
8587
|
return {
|
|
8405
8588
|
content: [{ type: "text", text: lines.join("\n") || "(no threads)" }]
|
|
@@ -8408,13 +8591,14 @@ async function startMcpServer() {
|
|
|
8408
8591
|
);
|
|
8409
8592
|
server.tool(
|
|
8410
8593
|
"get_thread",
|
|
8411
|
-
"Get a compact thread summary by id/ref",
|
|
8594
|
+
"Get a compact thread summary by id/ref. While running, includes progress (last tool/thinking) and lastActivityAt.",
|
|
8412
8595
|
{ ref: z3.string() },
|
|
8413
8596
|
async ({ ref }) => {
|
|
8414
8597
|
const t = orch.getThread(ref);
|
|
8415
8598
|
if (!t) {
|
|
8416
8599
|
return { content: [{ type: "text", text: `Thread not found: ${ref}` }], isError: true };
|
|
8417
8600
|
}
|
|
8601
|
+
const live = t.status === "running" || t.status === "queued" ? readTurnLive(t.id) : null;
|
|
8418
8602
|
const summary = {
|
|
8419
8603
|
id: t.id,
|
|
8420
8604
|
title: t.title,
|
|
@@ -8429,7 +8613,10 @@ async function startMcpServer() {
|
|
|
8429
8613
|
messageCount: t.messages.length,
|
|
8430
8614
|
devPort: t.devPort,
|
|
8431
8615
|
prUrl: t.prUrl,
|
|
8432
|
-
lastError: t.lastError ?? null
|
|
8616
|
+
lastError: t.lastError ?? null,
|
|
8617
|
+
stillRunning: t.status === "running" || t.status === "queued",
|
|
8618
|
+
progress: live?.summary ?? null,
|
|
8619
|
+
lastActivityAt: live?.updatedAt ?? null
|
|
8433
8620
|
};
|
|
8434
8621
|
return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
|
|
8435
8622
|
}
|
|
@@ -8565,8 +8752,8 @@ async function startMcpServer() {
|
|
|
8565
8752
|
registerSlackTools(server);
|
|
8566
8753
|
registerLinearTools(server);
|
|
8567
8754
|
if (sideboardMcpProfile() !== "worktree") {
|
|
8568
|
-
const { getCaffeinateHold: getCaffeinateHold2, setCaffeinateHold: setCaffeinateHold2 } = await import("./caffeinate-hold-
|
|
8569
|
-
const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await import("./app-settings-
|
|
8755
|
+
const { getCaffeinateHold: getCaffeinateHold2, setCaffeinateHold: setCaffeinateHold2 } = await import("./caffeinate-hold-X3JNPR42.js");
|
|
8756
|
+
const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await import("./app-settings-EKJM2344.js");
|
|
8570
8757
|
const accountDefaults = resolveThreadDefaults2();
|
|
8571
8758
|
const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
|
|
8572
8759
|
server.tool(
|
|
@@ -8755,13 +8942,15 @@ async function startMcpServer() {
|
|
|
8755
8942
|
);
|
|
8756
8943
|
server.tool(
|
|
8757
8944
|
"wait_for_turn",
|
|
8758
|
-
"
|
|
8945
|
+
"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.",
|
|
8759
8946
|
{
|
|
8760
8947
|
ref: z3.string(),
|
|
8761
8948
|
timeoutMs: z3.number().optional()
|
|
8762
8949
|
},
|
|
8763
8950
|
async ({ ref, timeoutMs }) => {
|
|
8764
|
-
const thread = await orch.waitForTurn(ref, timeoutMs
|
|
8951
|
+
const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
|
|
8952
|
+
resolveIfStillRunning: true
|
|
8953
|
+
});
|
|
8765
8954
|
const result = orch.getTurnResult(thread.id);
|
|
8766
8955
|
return {
|
|
8767
8956
|
content: [
|
|
@@ -8771,7 +8960,11 @@ async function startMcpServer() {
|
|
|
8771
8960
|
id: thread.id,
|
|
8772
8961
|
status: result.status,
|
|
8773
8962
|
text: result.text,
|
|
8774
|
-
lastError: result.lastError
|
|
8963
|
+
lastError: result.lastError,
|
|
8964
|
+
stillRunning: result.stillRunning,
|
|
8965
|
+
progress: result.progress,
|
|
8966
|
+
lastActivityAt: result.lastActivityAt,
|
|
8967
|
+
hint: result.stillRunning ? MCP_WAIT_STILL_RUNNING_HINT : void 0
|
|
8775
8968
|
})
|
|
8776
8969
|
}
|
|
8777
8970
|
]
|
|
@@ -8780,11 +8973,21 @@ async function startMcpServer() {
|
|
|
8780
8973
|
);
|
|
8781
8974
|
server.tool(
|
|
8782
8975
|
"get_turn_result",
|
|
8783
|
-
"
|
|
8976
|
+
"Assistant message when the turn finished, or live progress while stillRunning. Not the full transcript.",
|
|
8784
8977
|
{ ref: z3.string() },
|
|
8785
8978
|
async ({ ref }) => {
|
|
8786
8979
|
const result = orch.getTurnResult(ref);
|
|
8787
|
-
return {
|
|
8980
|
+
return {
|
|
8981
|
+
content: [
|
|
8982
|
+
{
|
|
8983
|
+
type: "text",
|
|
8984
|
+
text: JSON.stringify({
|
|
8985
|
+
...result,
|
|
8986
|
+
hint: result.stillRunning ? MCP_WAIT_STILL_RUNNING_HINT : void 0
|
|
8987
|
+
})
|
|
8988
|
+
}
|
|
8989
|
+
]
|
|
8990
|
+
};
|
|
8788
8991
|
}
|
|
8789
8992
|
);
|
|
8790
8993
|
server.tool(
|
|
@@ -8901,7 +9104,7 @@ async function startMcpServer() {
|
|
|
8901
9104
|
);
|
|
8902
9105
|
server.tool(
|
|
8903
9106
|
"request_review",
|
|
8904
|
-
'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.',
|
|
9107
|
+
'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.',
|
|
8905
9108
|
{ ref: z3.string().describe("Worktree thread id/ref to review") },
|
|
8906
9109
|
async ({ ref }) => {
|
|
8907
9110
|
try {
|
|
@@ -8929,7 +9132,7 @@ async function startMcpServer() {
|
|
|
8929
9132
|
);
|
|
8930
9133
|
server.tool(
|
|
8931
9134
|
"ask_git",
|
|
8932
|
-
"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.",
|
|
9135
|
+
"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.",
|
|
8933
9136
|
{
|
|
8934
9137
|
ref: z3.string().describe("Worktree thread id/ref"),
|
|
8935
9138
|
action: z3.enum(AGENT_GIT_ACTIONS).describe(
|
|
@@ -8980,7 +9183,7 @@ async function startMcpServer() {
|
|
|
8980
9183
|
);
|
|
8981
9184
|
server.tool(
|
|
8982
9185
|
"fork_worktree",
|
|
8983
|
-
"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.",
|
|
9186
|
+
"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.",
|
|
8984
9187
|
{
|
|
8985
9188
|
ref: z3.string().describe("Worktree thread id/ref to fork"),
|
|
8986
9189
|
through_index: z3.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
@@ -9025,7 +9228,7 @@ async function startMcpServer() {
|
|
|
9025
9228
|
);
|
|
9026
9229
|
server.tool(
|
|
9027
9230
|
"fork_chat",
|
|
9028
|
-
"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.",
|
|
9231
|
+
"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.",
|
|
9029
9232
|
{
|
|
9030
9233
|
ref: z3.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
9031
9234
|
through_index: z3.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
@@ -9692,7 +9895,7 @@ async function runCloudConnect(opts) {
|
|
|
9692
9895
|
}
|
|
9693
9896
|
|
|
9694
9897
|
// src/agents/user-mcp-config.ts
|
|
9695
|
-
import { existsSync as
|
|
9898
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
|
|
9696
9899
|
import { homedir as homedir2 } from "os";
|
|
9697
9900
|
import { dirname as dirname4, join as join14 } from "path";
|
|
9698
9901
|
function userCursorMcpConfigPath() {
|
|
@@ -9726,16 +9929,16 @@ function mergeSideboardIntoMcpServersJson(existing, sideboard) {
|
|
|
9726
9929
|
}
|
|
9727
9930
|
function writeMergedMcpServersJson(configPath, sideboard) {
|
|
9728
9931
|
let existing = {};
|
|
9729
|
-
if (
|
|
9932
|
+
if (existsSync16(configPath)) {
|
|
9730
9933
|
try {
|
|
9731
|
-
existing = JSON.parse(
|
|
9934
|
+
existing = JSON.parse(readFileSync12(configPath, "utf8"));
|
|
9732
9935
|
} catch {
|
|
9733
9936
|
existing = {};
|
|
9734
9937
|
}
|
|
9735
9938
|
}
|
|
9736
9939
|
const next = mergeSideboardIntoMcpServersJson(existing, sideboard);
|
|
9737
9940
|
mkdirSync5(dirname4(configPath), { recursive: true });
|
|
9738
|
-
|
|
9941
|
+
writeFileSync6(configPath, `${JSON.stringify(next, null, 2)}
|
|
9739
9942
|
`);
|
|
9740
9943
|
}
|
|
9741
9944
|
function launchFromResolved(server) {
|
|
@@ -9753,7 +9956,7 @@ async function registerPackagedUserMcpClients() {
|
|
|
9753
9956
|
const cursor = userCursorMcpConfigPath();
|
|
9754
9957
|
writeMergedMcpServersJson(cursor, launch);
|
|
9755
9958
|
const claude = userClaudeMcpConfigPath();
|
|
9756
|
-
if (
|
|
9959
|
+
if (existsSync16(claude)) {
|
|
9757
9960
|
writeMergedMcpServersJson(claude, launch);
|
|
9758
9961
|
return { cursor, claude };
|
|
9759
9962
|
}
|
|
@@ -10251,6 +10454,8 @@ function connectSession(ws, opts, log) {
|
|
|
10251
10454
|
|
|
10252
10455
|
// src/slack/relay-protocol.ts
|
|
10253
10456
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
10457
|
+
var SLACK_RELAY_PING_INTERVAL_MS = 2e4;
|
|
10458
|
+
var SLACK_RELAY_PONG_TIMEOUT_MS = 15e3;
|
|
10254
10459
|
function slackRelaySessionKey(teamId, userId, deviceId) {
|
|
10255
10460
|
return `${teamId.trim()}:${userId.trim()}:${deviceId.trim()}`;
|
|
10256
10461
|
}
|
|
@@ -10436,10 +10641,21 @@ function connectSession2(ws, opts, log) {
|
|
|
10436
10641
|
const allowedUsers = new Set(
|
|
10437
10642
|
opts.workspaces.map((w) => `${w.teamId.trim()}:${w.userId.trim()}`)
|
|
10438
10643
|
);
|
|
10644
|
+
const pingMs = opts.pingIntervalMs ?? SLACK_RELAY_PING_INTERVAL_MS;
|
|
10645
|
+
const pongMs = opts.pongTimeoutMs ?? SLACK_RELAY_PONG_TIMEOUT_MS;
|
|
10646
|
+
let pingTimer = null;
|
|
10647
|
+
let pongTimer = null;
|
|
10648
|
+
const stopKeepalive = () => {
|
|
10649
|
+
if (pingTimer) clearInterval(pingTimer);
|
|
10650
|
+
if (pongTimer) clearTimeout(pongTimer);
|
|
10651
|
+
pingTimer = null;
|
|
10652
|
+
pongTimer = null;
|
|
10653
|
+
};
|
|
10439
10654
|
const finish = (err) => {
|
|
10440
10655
|
if (settled) return;
|
|
10441
10656
|
settled = true;
|
|
10442
10657
|
pending.clear();
|
|
10658
|
+
stopKeepalive();
|
|
10443
10659
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
10444
10660
|
if (err) reject(err);
|
|
10445
10661
|
else resolve();
|
|
@@ -10452,6 +10668,25 @@ function connectSession2(ws, opts, log) {
|
|
|
10452
10668
|
finish();
|
|
10453
10669
|
};
|
|
10454
10670
|
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
10671
|
+
const armPongTimeout = () => {
|
|
10672
|
+
if (pongTimer) clearTimeout(pongTimer);
|
|
10673
|
+
pongTimer = setTimeout(() => {
|
|
10674
|
+
log("relay error: ping timeout");
|
|
10675
|
+
try {
|
|
10676
|
+
ws.close();
|
|
10677
|
+
} catch {
|
|
10678
|
+
}
|
|
10679
|
+
finish(new Error("relay ping timeout"));
|
|
10680
|
+
}, pongMs);
|
|
10681
|
+
};
|
|
10682
|
+
const pingOnce = () => {
|
|
10683
|
+
try {
|
|
10684
|
+
send(ws, { type: "ping" });
|
|
10685
|
+
armPongTimeout();
|
|
10686
|
+
} catch (err) {
|
|
10687
|
+
finish(err instanceof Error ? err : new Error("relay ping failed"));
|
|
10688
|
+
}
|
|
10689
|
+
};
|
|
10455
10690
|
ws.addEventListener("open", () => {
|
|
10456
10691
|
const label = opts.deviceLabel?.trim() || opts.deviceId.trim();
|
|
10457
10692
|
log(`Relay connected \xB7 ${label}`);
|
|
@@ -10466,6 +10701,8 @@ function connectSession2(ws, opts, log) {
|
|
|
10466
10701
|
userToken: wsInfo.userToken
|
|
10467
10702
|
});
|
|
10468
10703
|
}
|
|
10704
|
+
pingOnce();
|
|
10705
|
+
pingTimer = setInterval(pingOnce, pingMs);
|
|
10469
10706
|
});
|
|
10470
10707
|
ws.addEventListener("error", () => {
|
|
10471
10708
|
finish(new Error("WebSocket error"));
|
|
@@ -10477,7 +10714,13 @@ function connectSession2(ws, opts, log) {
|
|
|
10477
10714
|
const raw = typeof ev.data === "string" ? ev.data : String(ev.data ?? "");
|
|
10478
10715
|
const msg = parseSlackRelayServerMessage(raw);
|
|
10479
10716
|
if (!msg) return;
|
|
10480
|
-
if (msg.type === "pong")
|
|
10717
|
+
if (msg.type === "pong") {
|
|
10718
|
+
if (pongTimer) {
|
|
10719
|
+
clearTimeout(pongTimer);
|
|
10720
|
+
pongTimer = null;
|
|
10721
|
+
}
|
|
10722
|
+
return;
|
|
10723
|
+
}
|
|
10481
10724
|
if (msg.type === "error") {
|
|
10482
10725
|
log(`relay error: ${msg.message}`);
|
|
10483
10726
|
return;
|
|
@@ -10600,6 +10843,13 @@ function slackReplyThreadTs(msg) {
|
|
|
10600
10843
|
if (msg.kind === "mention") return msg.ts;
|
|
10601
10844
|
return void 0;
|
|
10602
10845
|
}
|
|
10846
|
+
var SLACK_PROGRESS_DELAY_MS = 2e4;
|
|
10847
|
+
var SLACK_PROGRESS_EDIT_MS = 15e3;
|
|
10848
|
+
function formatSlackWorkingText(summary) {
|
|
10849
|
+
const line = (summary ?? "").trim();
|
|
10850
|
+
if (!line || /^working/i.test(line)) return "Working\u2026";
|
|
10851
|
+
return `Working\u2026 ${line}`;
|
|
10852
|
+
}
|
|
10603
10853
|
var SLACK_SEEN_REACTION = "eyes";
|
|
10604
10854
|
function writeTokenForTeam(teamId) {
|
|
10605
10855
|
const workspaces = listSlackWorkspacesRaw();
|
|
@@ -10636,8 +10886,8 @@ async function ackSlackInboundSeen(msg, opts) {
|
|
|
10636
10886
|
log(`react error: ${errMsg}${hint}`);
|
|
10637
10887
|
}
|
|
10638
10888
|
}
|
|
10639
|
-
|
|
10640
|
-
|
|
10889
|
+
function replyStub(target) {
|
|
10890
|
+
return {
|
|
10641
10891
|
teamId: target.teamId,
|
|
10642
10892
|
channelId: target.channelId,
|
|
10643
10893
|
ts: target.threadTs ?? "",
|
|
@@ -10645,12 +10895,15 @@ async function postSlackText(target, text3, opts) {
|
|
|
10645
10895
|
text: "",
|
|
10646
10896
|
kind: "dm"
|
|
10647
10897
|
};
|
|
10898
|
+
}
|
|
10899
|
+
async function postSlackText(target, text3, opts) {
|
|
10648
10900
|
if (opts.postReply) {
|
|
10649
|
-
await opts.postReply(
|
|
10650
|
-
|
|
10901
|
+
const result = await opts.postReply(replyStub(target), text3);
|
|
10902
|
+
const ts2 = result && typeof result === "object" && typeof result.ts === "string" ? result.ts.trim() : "";
|
|
10903
|
+
return ts2 ? { ts: ts2 } : null;
|
|
10651
10904
|
}
|
|
10652
10905
|
const token = writeTokenForTeam(target.teamId);
|
|
10653
|
-
await slackApi(
|
|
10906
|
+
const json = await slackApi(
|
|
10654
10907
|
token,
|
|
10655
10908
|
"chat.postMessage",
|
|
10656
10909
|
{
|
|
@@ -10660,18 +10913,163 @@ async function postSlackText(target, text3, opts) {
|
|
|
10660
10913
|
},
|
|
10661
10914
|
opts.fetchImpl
|
|
10662
10915
|
);
|
|
10916
|
+
const ts = json.ts?.trim();
|
|
10917
|
+
return ts ? { ts } : null;
|
|
10663
10918
|
}
|
|
10664
|
-
async function
|
|
10665
|
-
|
|
10919
|
+
async function updateSlackText(target, ts, text3, opts) {
|
|
10920
|
+
if (opts.updateReply) {
|
|
10921
|
+
await opts.updateReply(replyStub(target), ts, text3);
|
|
10922
|
+
return;
|
|
10923
|
+
}
|
|
10924
|
+
const token = writeTokenForTeam(target.teamId);
|
|
10925
|
+
await slackApi(
|
|
10926
|
+
token,
|
|
10927
|
+
"chat.update",
|
|
10666
10928
|
{
|
|
10667
|
-
|
|
10668
|
-
|
|
10669
|
-
|
|
10929
|
+
channel: target.channelId,
|
|
10930
|
+
ts,
|
|
10931
|
+
text: text3
|
|
10670
10932
|
},
|
|
10671
|
-
|
|
10672
|
-
opts
|
|
10933
|
+
opts.fetchImpl
|
|
10673
10934
|
);
|
|
10674
10935
|
}
|
|
10936
|
+
async function deleteSlackText(target, ts, opts) {
|
|
10937
|
+
if (opts.deleteReply) {
|
|
10938
|
+
await opts.deleteReply(replyStub(target), ts);
|
|
10939
|
+
return;
|
|
10940
|
+
}
|
|
10941
|
+
const token = writeTokenForTeam(target.teamId);
|
|
10942
|
+
await slackApi(
|
|
10943
|
+
token,
|
|
10944
|
+
"chat.delete",
|
|
10945
|
+
{
|
|
10946
|
+
channel: target.channelId,
|
|
10947
|
+
ts
|
|
10948
|
+
},
|
|
10949
|
+
opts.fetchImpl
|
|
10950
|
+
);
|
|
10951
|
+
}
|
|
10952
|
+
function replyTargetOf(msg) {
|
|
10953
|
+
return {
|
|
10954
|
+
teamId: msg.teamId,
|
|
10955
|
+
channelId: msg.channelId,
|
|
10956
|
+
threadTs: slackReplyThreadTs(msg)
|
|
10957
|
+
};
|
|
10958
|
+
}
|
|
10959
|
+
async function postSlackReply(msg, text3, opts) {
|
|
10960
|
+
await postSlackText(replyTargetOf(msg), signForThisMac(text3), opts);
|
|
10961
|
+
}
|
|
10962
|
+
function startSlackTurnProgress(msg, threadId, opts) {
|
|
10963
|
+
const log = opts.onLog ?? (() => void 0);
|
|
10964
|
+
const target = replyTargetOf(msg);
|
|
10965
|
+
const delayMs = opts.progressDelayMs ?? SLACK_PROGRESS_DELAY_MS;
|
|
10966
|
+
const editMs = opts.progressEditMs ?? SLACK_PROGRESS_EDIT_MS;
|
|
10967
|
+
let stopped = false;
|
|
10968
|
+
let postedTs = null;
|
|
10969
|
+
let lastBody = null;
|
|
10970
|
+
let chain = Promise.resolve();
|
|
10971
|
+
let editTimer = null;
|
|
10972
|
+
const run2 = (fn) => {
|
|
10973
|
+
const next = chain.then(fn, fn);
|
|
10974
|
+
chain = next.then(
|
|
10975
|
+
() => void 0,
|
|
10976
|
+
() => void 0
|
|
10977
|
+
);
|
|
10978
|
+
return next;
|
|
10979
|
+
};
|
|
10980
|
+
const stillLive = () => {
|
|
10981
|
+
if (stopped || slackInboundSuperseded(opts)) return false;
|
|
10982
|
+
const thread = readThread(threadId());
|
|
10983
|
+
return Boolean(thread && (thread.status === "running" || thread.status === "queued"));
|
|
10984
|
+
};
|
|
10985
|
+
const postWorking = async () => {
|
|
10986
|
+
if (!stillLive()) return;
|
|
10987
|
+
const summary = readTurnLive(threadId())?.summary ?? "Working\u2026";
|
|
10988
|
+
const body = formatSlackWorkingText(summary);
|
|
10989
|
+
try {
|
|
10990
|
+
const posted = await postSlackText(target, signForThisMac(body), opts);
|
|
10991
|
+
postedTs = posted?.ts ?? postedTs;
|
|
10992
|
+
lastBody = body;
|
|
10993
|
+
if (postedTs && !stopped && !editTimer) {
|
|
10994
|
+
editTimer = setInterval(() => {
|
|
10995
|
+
void run2(editWorking);
|
|
10996
|
+
}, editMs);
|
|
10997
|
+
}
|
|
10998
|
+
} catch (err) {
|
|
10999
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
11000
|
+
log(`progress post error: ${errMsg}`);
|
|
11001
|
+
}
|
|
11002
|
+
};
|
|
11003
|
+
const editWorking = async () => {
|
|
11004
|
+
if (!postedTs || !stillLive()) return;
|
|
11005
|
+
const summary = readTurnLive(threadId())?.summary ?? "Working\u2026";
|
|
11006
|
+
const body = formatSlackWorkingText(summary);
|
|
11007
|
+
if (body === lastBody) return;
|
|
11008
|
+
lastBody = body;
|
|
11009
|
+
try {
|
|
11010
|
+
await updateSlackText(target, postedTs, signForThisMac(body), opts);
|
|
11011
|
+
} catch (err) {
|
|
11012
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
11013
|
+
log(`progress update error: ${errMsg}`);
|
|
11014
|
+
}
|
|
11015
|
+
};
|
|
11016
|
+
const delayTimer = setTimeout(() => {
|
|
11017
|
+
void run2(postWorking);
|
|
11018
|
+
}, delayMs);
|
|
11019
|
+
const stop = () => {
|
|
11020
|
+
stopped = true;
|
|
11021
|
+
clearTimeout(delayTimer);
|
|
11022
|
+
if (editTimer) {
|
|
11023
|
+
clearInterval(editTimer);
|
|
11024
|
+
editTimer = null;
|
|
11025
|
+
}
|
|
11026
|
+
};
|
|
11027
|
+
return {
|
|
11028
|
+
stop,
|
|
11029
|
+
discard: () => {
|
|
11030
|
+
stop();
|
|
11031
|
+
return run2(async () => {
|
|
11032
|
+
if (!postedTs) return;
|
|
11033
|
+
const ts = postedTs;
|
|
11034
|
+
postedTs = null;
|
|
11035
|
+
try {
|
|
11036
|
+
await deleteSlackText(target, ts, opts);
|
|
11037
|
+
} catch (err) {
|
|
11038
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
11039
|
+
log(`progress delete error: ${errMsg}`);
|
|
11040
|
+
}
|
|
11041
|
+
});
|
|
11042
|
+
},
|
|
11043
|
+
finishWith: (text3) => {
|
|
11044
|
+
stop();
|
|
11045
|
+
return run2(async () => {
|
|
11046
|
+
const signed = signForThisMac(text3.trim());
|
|
11047
|
+
if (!signed) {
|
|
11048
|
+
if (!postedTs) return;
|
|
11049
|
+
const ts = postedTs;
|
|
11050
|
+
postedTs = null;
|
|
11051
|
+
try {
|
|
11052
|
+
await deleteSlackText(target, ts, opts);
|
|
11053
|
+
} catch (err) {
|
|
11054
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
11055
|
+
log(`progress delete error: ${errMsg}`);
|
|
11056
|
+
}
|
|
11057
|
+
return;
|
|
11058
|
+
}
|
|
11059
|
+
if (postedTs) {
|
|
11060
|
+
try {
|
|
11061
|
+
await updateSlackText(target, postedTs, signed, opts);
|
|
11062
|
+
return;
|
|
11063
|
+
} catch (err) {
|
|
11064
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
11065
|
+
log(`progress update error: ${errMsg}`);
|
|
11066
|
+
}
|
|
11067
|
+
}
|
|
11068
|
+
await postSlackText(target, signed, opts);
|
|
11069
|
+
});
|
|
11070
|
+
}
|
|
11071
|
+
};
|
|
11072
|
+
}
|
|
10675
11073
|
var lastRelayed = /* @__PURE__ */ new Map();
|
|
10676
11074
|
function markRelayed(threadId, text3) {
|
|
10677
11075
|
lastRelayed.set(threadId, `${threadId}:${text3}`);
|
|
@@ -10763,10 +11161,15 @@ async function handleSlackInbound(msg, opts) {
|
|
|
10763
11161
|
});
|
|
10764
11162
|
};
|
|
10765
11163
|
bindReplyTarget(fresh.id);
|
|
11164
|
+
const progress = startSlackTurnProgress(msg, () => fresh.id, opts);
|
|
10766
11165
|
const runTurn = async (threadId) => {
|
|
10767
11166
|
await orch.send(threadId, prompt);
|
|
10768
11167
|
await orch.waitForTurn(threadId, 14 * 60 * 1e3);
|
|
10769
11168
|
};
|
|
11169
|
+
const dropProgress = async (reason) => {
|
|
11170
|
+
log(reason);
|
|
11171
|
+
await progress.discard();
|
|
11172
|
+
};
|
|
10770
11173
|
let reply;
|
|
10771
11174
|
try {
|
|
10772
11175
|
try {
|
|
@@ -10779,12 +11182,12 @@ async function handleSlackInbound(msg, opts) {
|
|
|
10779
11182
|
await runTurn(fresh.id);
|
|
10780
11183
|
}
|
|
10781
11184
|
if (slackInboundSuperseded(opts)) {
|
|
10782
|
-
|
|
11185
|
+
await dropProgress(`turn finished ${msg.ts} (superseded)`);
|
|
10783
11186
|
return;
|
|
10784
11187
|
}
|
|
10785
11188
|
let after = readThread(fresh.id);
|
|
10786
11189
|
if (after?.status === "stopped") {
|
|
10787
|
-
|
|
11190
|
+
await dropProgress(`turn finished ${msg.ts} (interrupted, skip post)`);
|
|
10788
11191
|
return;
|
|
10789
11192
|
}
|
|
10790
11193
|
if (!after || after.status === "archived") {
|
|
@@ -10793,46 +11196,48 @@ async function handleSlackInbound(msg, opts) {
|
|
|
10793
11196
|
bindReplyTarget(fresh.id);
|
|
10794
11197
|
await runTurn(fresh.id);
|
|
10795
11198
|
if (slackInboundSuperseded(opts)) {
|
|
10796
|
-
|
|
11199
|
+
await dropProgress(`turn finished ${msg.ts} (superseded)`);
|
|
10797
11200
|
return;
|
|
10798
11201
|
}
|
|
10799
11202
|
after = readThread(fresh.id);
|
|
10800
11203
|
if (!after || after.status === "stopped" || after.status === "archived") {
|
|
10801
|
-
|
|
11204
|
+
await dropProgress(`turn finished ${msg.ts} (interrupted, skip post)`);
|
|
10802
11205
|
return;
|
|
10803
11206
|
}
|
|
10804
11207
|
}
|
|
10805
11208
|
reply = orch.getTurnResult(fresh.id).text.trim();
|
|
10806
11209
|
} catch (err) {
|
|
10807
11210
|
if (slackInboundSuperseded(opts)) {
|
|
10808
|
-
|
|
11211
|
+
await dropProgress(`turn error ${msg.ts} (superseded)`);
|
|
10809
11212
|
return;
|
|
10810
11213
|
}
|
|
10811
11214
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
10812
11215
|
if (slackCoordinatorGone(err)) {
|
|
10813
|
-
|
|
11216
|
+
await dropProgress(`turn finished ${msg.ts} (thread gone, skip post)`);
|
|
10814
11217
|
return;
|
|
10815
11218
|
}
|
|
10816
11219
|
const timedOut = /timed out|timeout/i.test(errMsg);
|
|
10817
11220
|
const errorReply = timedOut ? SLACK_LISTEN_TIMEOUT_REPLY : `Sideboard coordinator error: ${errMsg}`;
|
|
10818
|
-
|
|
11221
|
+
try {
|
|
11222
|
+
await progress.finishWith(errorReply);
|
|
11223
|
+
log(`replied error ${msg.ts}: ${errMsg}`);
|
|
11224
|
+
} catch (postErr) {
|
|
10819
11225
|
const postMsg = postErr instanceof Error ? postErr.message : String(postErr);
|
|
10820
11226
|
log(`post error: ${postMsg}`);
|
|
10821
|
-
}
|
|
10822
|
-
log(`replied error ${msg.ts}: ${errMsg}`);
|
|
11227
|
+
}
|
|
10823
11228
|
if (!timedOut) throw err;
|
|
10824
11229
|
return;
|
|
10825
11230
|
}
|
|
10826
11231
|
if (!reply) {
|
|
10827
|
-
|
|
11232
|
+
await dropProgress(`post error: no agent text to send for ${msg.ts}`);
|
|
10828
11233
|
return;
|
|
10829
11234
|
}
|
|
10830
11235
|
if (alreadyRelayed(fresh.id, reply)) {
|
|
10831
|
-
|
|
11236
|
+
await dropProgress(`turn finished ${msg.ts} (already posted)`);
|
|
10832
11237
|
return;
|
|
10833
11238
|
}
|
|
10834
11239
|
try {
|
|
10835
|
-
await
|
|
11240
|
+
await progress.finishWith(reply);
|
|
10836
11241
|
markRelayed(fresh.id, reply);
|
|
10837
11242
|
log(`replied ${msg.ts} (${reply.length} chars)`);
|
|
10838
11243
|
} catch (err) {
|
|
@@ -11383,6 +11788,20 @@ async function startSlackRelayServer(opts) {
|
|
|
11383
11788
|
}
|
|
11384
11789
|
}
|
|
11385
11790
|
};
|
|
11791
|
+
const ping = setInterval(() => {
|
|
11792
|
+
if (ws.readyState !== ws.OPEN) {
|
|
11793
|
+
clearInterval(ping);
|
|
11794
|
+
return;
|
|
11795
|
+
}
|
|
11796
|
+
try {
|
|
11797
|
+
ws.ping();
|
|
11798
|
+
} catch {
|
|
11799
|
+
clearInterval(ping);
|
|
11800
|
+
}
|
|
11801
|
+
}, SLACK_RELAY_PING_INTERVAL_MS);
|
|
11802
|
+
const stopPing = () => clearInterval(ping);
|
|
11803
|
+
ws.on("close", stopPing);
|
|
11804
|
+
ws.on("error", stopPing);
|
|
11386
11805
|
log("desktop connected");
|
|
11387
11806
|
ws.on("message", (data) => {
|
|
11388
11807
|
const raw = typeof data === "string" ? data : data.toString("utf8");
|
|
@@ -11493,6 +11912,8 @@ export {
|
|
|
11493
11912
|
SLACK_LISTEN_TIMEOUT_REPLY,
|
|
11494
11913
|
SLACK_OAUTH_CANCELLED,
|
|
11495
11914
|
SLACK_OAUTH_REDIRECT,
|
|
11915
|
+
SLACK_PROGRESS_DELAY_MS,
|
|
11916
|
+
SLACK_PROGRESS_EDIT_MS,
|
|
11496
11917
|
SLACK_REPLY_FORMATTING,
|
|
11497
11918
|
SLACK_SEEN_REACTION,
|
|
11498
11919
|
SlackOAuthCancelledError,
|
|
@@ -11650,6 +12071,7 @@ export {
|
|
|
11650
12071
|
formatSlackInboundPrompt,
|
|
11651
12072
|
formatSlackRepliesForTurn,
|
|
11652
12073
|
formatSlackSignedReply,
|
|
12074
|
+
formatSlackWorkingText,
|
|
11653
12075
|
formatTranscriptMarkdown,
|
|
11654
12076
|
formatUiReminder,
|
|
11655
12077
|
formatWorkspaceInventory,
|
|
@@ -11945,6 +12367,7 @@ export {
|
|
|
11945
12367
|
thisProcessShouldDrainAgentQueues,
|
|
11946
12368
|
threadDisplayLabel,
|
|
11947
12369
|
threadFilePath,
|
|
12370
|
+
threadLivePath,
|
|
11948
12371
|
threadLockPath,
|
|
11949
12372
|
threadRequestsBrightsyMcp,
|
|
11950
12373
|
threadsDir,
|
|
@@ -11968,6 +12391,7 @@ export {
|
|
|
11968
12391
|
userClaudeMcpConfigPath,
|
|
11969
12392
|
userCursorMcpConfigPath,
|
|
11970
12393
|
validateLinearApiKey,
|
|
12394
|
+
waitForPidExit,
|
|
11971
12395
|
warmGithubAgentAuth,
|
|
11972
12396
|
withAgentInstructions,
|
|
11973
12397
|
withExportedPath,
|