@sideboard-ai/core 0.1.99 → 0.1.103
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/cursor-runner.cjs +22 -22
- package/dist/agents/cursor-runner.js +2 -2
- package/dist/{agents-ESJKIQQA.js → agents-UT5GCJH3.js} +8 -8
- package/dist/{agents-3MWWWSMF.js → agents-XZBAKBO2.js} +9 -9
- package/dist/{app-settings-BRF7VKTQ.js → app-settings-EKJM2344.js} +2 -2
- package/dist/{app-settings-ZXPAB3QN.js → app-settings-VDJK2BZ2.js} +2 -2
- package/dist/{caffeinate-hold-U3QJBV67.js → caffeinate-hold-NBRUWYXO.js} +2 -2
- package/dist/{caffeinate-hold-I7SG6UQR.js → caffeinate-hold-X3JNPR42.js} +2 -2
- package/dist/{chunk-UYQYK2RY.js → chunk-27MCESES.js} +3 -3
- package/dist/{chunk-QSLE4VEM.js → chunk-2GO3YKBA.js} +4 -4
- package/dist/{chunk-DKHGWYWR.js → chunk-7YKXHBIW.js} +1 -1
- package/dist/{chunk-JL4SJ6V7.js → chunk-BD2ICC4D.js} +4 -0
- package/dist/{chunk-NZPGMNFC.js → chunk-DCOZABNT.js} +1 -1
- package/dist/{chunk-CIRXAYWS.js → chunk-DHAGMJIK.js} +55 -28
- package/dist/{chunk-XUWDLRAE.js → chunk-ETMRBJKA.js} +11 -11
- package/dist/{chunk-MBP3XG57.js → chunk-H6LP57EF.js} +7 -7
- package/dist/{chunk-FO67IJTY.js → chunk-HHLZWO7U.js} +5 -5
- package/dist/{chunk-GXSYI7FH.js → chunk-HUKCGRAT.js} +209 -59
- package/dist/{chunk-3WA37BRI.js → chunk-KGSVLZV6.js} +11 -4
- package/dist/{chunk-7KDBDMNC.js → chunk-KLBOWJ74.js} +1 -1
- package/dist/{chunk-FYS2BULQ.js → chunk-LANDJIAI.js} +213 -63
- package/dist/{chunk-OANJQTVG.js → chunk-ND6AO7C5.js} +3 -3
- package/dist/{chunk-HI2OTFFR.js → chunk-OW34MVDN.js} +5 -5
- package/dist/{chunk-JOF3XIEM.js → chunk-P3BQ5DOL.js} +11 -4
- package/dist/{chunk-B4R2VB2A.js → chunk-QZQEXME2.js} +4 -0
- package/dist/{chunk-R7BQBSDT.js → chunk-WA3DVVWG.js} +55 -28
- package/dist/{chunk-6LFV4VFI.js → chunk-WSYCND2U.js} +1 -1
- package/dist/{chunk-FT2SQOL4.js → chunk-XHZJJWLI.js} +1 -1
- package/dist/{chunk-XH2GS2LO.js → chunk-YJADJH5U.js} +11 -11
- package/dist/{connected-teams-SWHJWK2A.js → connected-teams-J4I5AKMR.js} +2 -2
- package/dist/{connected-teams-TLVKZPJP.js → connected-teams-M5XNXRI5.js} +2 -2
- package/dist/{coordinator-prompt-OQOOD5ET.js → coordinator-prompt-3DFZRMXE.js} +5 -5
- package/dist/{coordinator-prompt-AKEY4WSO.js → coordinator-prompt-UWHSD65V.js} +5 -5
- package/dist/{cursor-recover-S5ORPW34.js → cursor-recover-JBTIDARH.js} +1 -1
- package/dist/{cursor-recover-G34BYKL2.js → cursor-recover-ZUJG6XRQ.js} +1 -1
- package/dist/{global-workspace-WMF3BJP5.js → global-workspace-KJQS5S2C.js} +6 -6
- package/dist/{global-workspace-RSQXRLT7.js → global-workspace-UR4E3CCG.js} +6 -6
- package/dist/index.cjs +851 -232
- package/dist/index.d.cts +39 -6
- package/dist/index.d.ts +39 -6
- package/dist/index.js +561 -138
- package/dist/mcp/run-stdio.cjs +651 -256
- package/dist/mcp/run-stdio.js +345 -143
- package/dist/{paths-TZX4L5X7.js → paths-QCI3MTO5.js} +3 -1
- package/dist/{paths-JQM2XRAD.js → paths-UMEDHOS5.js} +3 -1
- package/dist/{thread-store-XAKWCV2E.js → thread-store-4OUEQ2DB.js} +2 -2
- package/dist/{thread-store-V7NOOXKE.js → thread-store-DQJGDKIJ.js} +2 -2
- package/dist/{workspaces-MZVQHRSJ.js → workspaces-NTIVI6DI.js} +7 -7
- package/dist/{workspaces-4ZY4QPWQ.js → workspaces-TKJXPNV4.js} +7 -7
- package/dist/{worktree-EO5QAGJU.js → worktree-NKWN5ZTS.js} +4 -4
- package/dist/{worktree-DA4BOV7G.js → worktree-PEUPFAKL.js} +4 -4
- package/package.json +2 -2
package/dist/index.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,
|
|
@@ -133,20 +133,21 @@ import {
|
|
|
133
133
|
cursorSdkMessageToEvents,
|
|
134
134
|
fallbackTurnFailDetail,
|
|
135
135
|
formatTurnExitError,
|
|
136
|
-
humanizeAgentFailDetail,
|
|
137
136
|
looksLikeAgentFailureMessage,
|
|
138
137
|
looksLikeInvalidAgentSession,
|
|
139
138
|
parseCursorRunnerLine,
|
|
140
139
|
pushTurnStderr,
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
shouldRetryFailedAgentTurn,
|
|
141
|
+
summarizeTurnStderr,
|
|
142
|
+
turnFailChatText
|
|
143
|
+
} from "./chunk-HUKCGRAT.js";
|
|
143
144
|
import {
|
|
144
145
|
caffeinateHoldPath,
|
|
145
146
|
getCaffeinateHold,
|
|
146
147
|
isThreadCaffeinated,
|
|
147
148
|
releaseCaffeinateHoldForThread,
|
|
148
149
|
setCaffeinateHold
|
|
149
|
-
} from "./chunk-
|
|
150
|
+
} from "./chunk-DCOZABNT.js";
|
|
150
151
|
import {
|
|
151
152
|
FAMOUS_SOCCER_TEAMS,
|
|
152
153
|
addPrStackLayer,
|
|
@@ -166,6 +167,7 @@ import {
|
|
|
166
167
|
currentBranch,
|
|
167
168
|
detectGhStack,
|
|
168
169
|
detectLocalMergeConflicts,
|
|
170
|
+
enqueueByKey,
|
|
169
171
|
ensureGhPreferOrigin,
|
|
170
172
|
extractGhErrorDetail,
|
|
171
173
|
fetchPrHead,
|
|
@@ -225,10 +227,11 @@ import {
|
|
|
225
227
|
takenSlugsFromThread,
|
|
226
228
|
threadDisplayLabel,
|
|
227
229
|
warmGithubAgentAuth,
|
|
230
|
+
withRepoGitLock,
|
|
228
231
|
worktreeDisplayLabel,
|
|
229
232
|
worktreeDisplayLabelForGroup,
|
|
230
233
|
worktreeNameFromPath
|
|
231
|
-
} from "./chunk-
|
|
234
|
+
} from "./chunk-DHAGMJIK.js";
|
|
232
235
|
import {
|
|
233
236
|
ATTACHMENTS_DIR,
|
|
234
237
|
LEGACY_ATTACHMENTS_DIR,
|
|
@@ -297,7 +300,7 @@ import {
|
|
|
297
300
|
updateIntegrationsSettings,
|
|
298
301
|
updateOpencodeSettings,
|
|
299
302
|
writeSecureJson
|
|
300
|
-
} from "./chunk-
|
|
303
|
+
} from "./chunk-XHZJJWLI.js";
|
|
301
304
|
import {
|
|
302
305
|
stripNestedElectronEnv
|
|
303
306
|
} from "./chunk-TLPJHLLM.js";
|
|
@@ -317,7 +320,7 @@ import {
|
|
|
317
320
|
updateThread,
|
|
318
321
|
withThreadLock,
|
|
319
322
|
writeThread
|
|
320
|
-
} from "./chunk-
|
|
323
|
+
} from "./chunk-KGSVLZV6.js";
|
|
321
324
|
import {
|
|
322
325
|
THINKING_EFFORTS,
|
|
323
326
|
isThinkingEffort,
|
|
@@ -348,11 +351,12 @@ import {
|
|
|
348
351
|
sideboardReposDir,
|
|
349
352
|
sideboardWorkspacesDir,
|
|
350
353
|
threadFilePath,
|
|
354
|
+
threadLivePath,
|
|
351
355
|
threadLockPath,
|
|
352
356
|
threadsDir,
|
|
353
357
|
workspaceSettingsSourceLabel,
|
|
354
358
|
worktreesRoot
|
|
355
|
-
} from "./chunk-
|
|
359
|
+
} from "./chunk-QZQEXME2.js";
|
|
356
360
|
import {
|
|
357
361
|
conductorBundledBinDir,
|
|
358
362
|
enrichPathWithNpmGlobalBin,
|
|
@@ -1385,9 +1389,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
1385
1389
|
`Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
|
|
1386
1390
|
);
|
|
1387
1391
|
}
|
|
1388
|
-
const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-
|
|
1392
|
+
const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-UR4E3CCG.js");
|
|
1389
1393
|
if (isGlobalThread2(thread)) {
|
|
1390
|
-
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-
|
|
1394
|
+
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-UWHSD65V.js");
|
|
1391
1395
|
ensureGlobalCoordinatorCwd2(
|
|
1392
1396
|
isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
|
|
1393
1397
|
);
|
|
@@ -3266,11 +3270,11 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
|
|
|
3266
3270
|
}
|
|
3267
3271
|
return out;
|
|
3268
3272
|
}
|
|
3269
|
-
function stageBuffersAsAttachments(worktreePath,
|
|
3270
|
-
if (
|
|
3273
|
+
function stageBuffersAsAttachments(worktreePath, buffers2) {
|
|
3274
|
+
if (buffers2.length === 0) return [];
|
|
3271
3275
|
const dir = ensureAttachmentsDir(worktreePath);
|
|
3272
3276
|
const out = [];
|
|
3273
|
-
for (const item of
|
|
3277
|
+
for (const item of buffers2) {
|
|
3274
3278
|
const originalName = (item.name || "file").replace(/[/\\]/g, "_") || "file";
|
|
3275
3279
|
try {
|
|
3276
3280
|
const buf = Buffer.from(item.dataBase64, "base64");
|
|
@@ -3290,8 +3294,8 @@ function stageBuffersAsAttachments(worktreePath, buffers) {
|
|
|
3290
3294
|
}
|
|
3291
3295
|
return out;
|
|
3292
3296
|
}
|
|
3293
|
-
function attachmentsFromBuffers(
|
|
3294
|
-
return
|
|
3297
|
+
function attachmentsFromBuffers(buffers2) {
|
|
3298
|
+
return buffers2.map((item) => {
|
|
3295
3299
|
const name = (item.name || "file").replace(/[/\\]/g, "_") || "file";
|
|
3296
3300
|
try {
|
|
3297
3301
|
const buf = Buffer.from(item.dataBase64, "base64");
|
|
@@ -3880,7 +3884,7 @@ async function createThread(input, _onSetupLine) {
|
|
|
3880
3884
|
return readThread(thread.id) ?? thread;
|
|
3881
3885
|
}
|
|
3882
3886
|
async function listLinearIssues(agent, repoPath) {
|
|
3883
|
-
const { getAdapter: getAdapter2 } = await import("./agents-
|
|
3887
|
+
const { getAdapter: getAdapter2 } = await import("./agents-XZBAKBO2.js");
|
|
3884
3888
|
await requireAgent(agent, { requireLinear: true });
|
|
3885
3889
|
const adapter = getAdapter2(agent);
|
|
3886
3890
|
if (!adapter.listLinearIssues) {
|
|
@@ -3931,8 +3935,11 @@ function worktreeBindingFrom(from) {
|
|
|
3931
3935
|
};
|
|
3932
3936
|
}
|
|
3933
3937
|
function threadsSharingWorktree(worktreePath) {
|
|
3938
|
+
if (!worktreePath) return [];
|
|
3934
3939
|
const key = normalizeWorktreePath(worktreePath);
|
|
3935
|
-
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));
|
|
3936
3943
|
}
|
|
3937
3944
|
function formatTranscriptMarkdown(title, messages) {
|
|
3938
3945
|
const body = formatMessagesAsTranscript(messages, { tools: "full" });
|
|
@@ -4433,7 +4440,7 @@ async function adoptThread(input) {
|
|
|
4433
4440
|
messages: input.messages ?? []
|
|
4434
4441
|
});
|
|
4435
4442
|
writeThread(thread);
|
|
4436
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-
|
|
4443
|
+
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-TKJXPNV4.js");
|
|
4437
4444
|
await ensureWorkspace2(repoPath);
|
|
4438
4445
|
return thread;
|
|
4439
4446
|
}
|
|
@@ -5199,7 +5206,7 @@ async function refreshSlackReplyBadges(opts) {
|
|
|
5199
5206
|
}
|
|
5200
5207
|
|
|
5201
5208
|
// src/orchestrator/orchestrator.ts
|
|
5202
|
-
import { existsSync as
|
|
5209
|
+
import { existsSync as existsSync15 } from "fs";
|
|
5203
5210
|
|
|
5204
5211
|
// src/git/pr-merge-archive.ts
|
|
5205
5212
|
function normalizePrState(state) {
|
|
@@ -5444,9 +5451,132 @@ async function cloneRepoIntoSideboard(opts) {
|
|
|
5444
5451
|
return { repoPath, workspace };
|
|
5445
5452
|
}
|
|
5446
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
|
+
|
|
5447
5577
|
// src/review/request-review.ts
|
|
5448
5578
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
5449
|
-
import { existsSync as
|
|
5579
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
|
|
5450
5580
|
import { dirname as dirname3, join as join13 } from "path";
|
|
5451
5581
|
|
|
5452
5582
|
// src/review/review-request-template.ts
|
|
@@ -5592,9 +5722,9 @@ function shouldRefreshReviewRequestTemplate(content) {
|
|
|
5592
5722
|
return LEGACY_REVIEW_TEMPLATE_MARKERS.every((m) => trimmed.includes(m));
|
|
5593
5723
|
}
|
|
5594
5724
|
function readTextIfPresent(abs) {
|
|
5595
|
-
if (!
|
|
5725
|
+
if (!existsSync14(abs)) return null;
|
|
5596
5726
|
try {
|
|
5597
|
-
const content =
|
|
5727
|
+
const content = readFileSync11(abs, "utf8");
|
|
5598
5728
|
return content.trim() ? content : null;
|
|
5599
5729
|
} catch {
|
|
5600
5730
|
return null;
|
|
@@ -5602,9 +5732,9 @@ function readTextIfPresent(abs) {
|
|
|
5602
5732
|
}
|
|
5603
5733
|
function ensureAttachmentsGitignore(worktreePath) {
|
|
5604
5734
|
const gitignoreAbs = join13(worktreePath, ATTACHMENTS_DIR, ".gitignore");
|
|
5605
|
-
if (
|
|
5735
|
+
if (existsSync14(gitignoreAbs)) return;
|
|
5606
5736
|
mkdirSync4(dirname3(gitignoreAbs), { recursive: true });
|
|
5607
|
-
|
|
5737
|
+
writeFileSync5(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
|
|
5608
5738
|
}
|
|
5609
5739
|
function resolveReviewGuidelines(worktreePath) {
|
|
5610
5740
|
const repoAbs = join13(worktreePath, REPO_REVIEW_PATH);
|
|
@@ -5639,7 +5769,7 @@ function resolveReviewGuidelines(worktreePath) {
|
|
|
5639
5769
|
}
|
|
5640
5770
|
ensureAttachmentsGitignore(worktreePath);
|
|
5641
5771
|
mkdirSync4(dirname3(localAbs), { recursive: true });
|
|
5642
|
-
|
|
5772
|
+
writeFileSync5(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
|
|
5643
5773
|
return {
|
|
5644
5774
|
path: REVIEW_REQUEST_PATH,
|
|
5645
5775
|
name: REVIEW_REQUEST_NAME,
|
|
@@ -5661,7 +5791,7 @@ function ensureReviewRequestFile(worktreePath) {
|
|
|
5661
5791
|
const localAbs = join13(worktreePath, REVIEW_REQUEST_PATH);
|
|
5662
5792
|
const localContent = readTextIfPresent(localAbs) ?? readTextIfPresent(join13(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
|
|
5663
5793
|
if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
|
|
5664
|
-
const path2 =
|
|
5794
|
+
const path2 = existsSync14(localAbs) ? REVIEW_REQUEST_PATH : LEGACY_REVIEW_REQUEST_PATH;
|
|
5665
5795
|
return {
|
|
5666
5796
|
path: path2,
|
|
5667
5797
|
name: REVIEW_REQUEST_NAME,
|
|
@@ -5670,7 +5800,7 @@ function ensureReviewRequestFile(worktreePath) {
|
|
|
5670
5800
|
};
|
|
5671
5801
|
}
|
|
5672
5802
|
mkdirSync4(dirname3(repoAbs), { recursive: true });
|
|
5673
|
-
|
|
5803
|
+
writeFileSync5(repoAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
|
|
5674
5804
|
return {
|
|
5675
5805
|
path: REPO_REVIEW_PATH,
|
|
5676
5806
|
name: REPO_REVIEW_NAME,
|
|
@@ -5863,6 +5993,15 @@ function isPidAlive(pid) {
|
|
|
5863
5993
|
return false;
|
|
5864
5994
|
}
|
|
5865
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;
|
|
5866
6005
|
function writeLiveStatus(threadId, status, lastError) {
|
|
5867
6006
|
const latest = readThread(threadId);
|
|
5868
6007
|
if (!latest || latest.status === "archived") return latest;
|
|
@@ -5940,7 +6079,7 @@ var Orchestrator = class {
|
|
|
5940
6079
|
}
|
|
5941
6080
|
continue;
|
|
5942
6081
|
}
|
|
5943
|
-
if (!
|
|
6082
|
+
if (!existsSync15(thread.worktreePath)) {
|
|
5944
6083
|
setStatus(thread.id, "broken", "Worktree missing on disk");
|
|
5945
6084
|
this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
|
|
5946
6085
|
continue;
|
|
@@ -5959,7 +6098,7 @@ var Orchestrator = class {
|
|
|
5959
6098
|
orphans: orphans.map((o) => ({ path: o.path, repoPath: o.repoPath }))
|
|
5960
6099
|
});
|
|
5961
6100
|
}
|
|
5962
|
-
const { autoCleanupOrphansEnabled: autoCleanupOrphansEnabled2 } = await import("./app-settings-
|
|
6101
|
+
const { autoCleanupOrphansEnabled: autoCleanupOrphansEnabled2 } = await import("./app-settings-EKJM2344.js");
|
|
5963
6102
|
if (autoCleanupOrphansEnabled2() && shouldRunWorktreeCleanup() && orphans.length > 0) {
|
|
5964
6103
|
await cleanupOrphanWorktrees({ repoPaths });
|
|
5965
6104
|
}
|
|
@@ -6127,7 +6266,7 @@ var Orchestrator = class {
|
|
|
6127
6266
|
}
|
|
6128
6267
|
}
|
|
6129
6268
|
await setup;
|
|
6130
|
-
const { autoRunAfterSetupEnabled: autoRunAfterSetupEnabled2 } = await import("./app-settings-
|
|
6269
|
+
const { autoRunAfterSetupEnabled: autoRunAfterSetupEnabled2 } = await import("./app-settings-EKJM2344.js");
|
|
6131
6270
|
if (autoRunAfterSetupEnabled2()) {
|
|
6132
6271
|
try {
|
|
6133
6272
|
await this.startDev(threadId);
|
|
@@ -6261,14 +6400,10 @@ var Orchestrator = class {
|
|
|
6261
6400
|
const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id);
|
|
6262
6401
|
const livePid = current.agentPid;
|
|
6263
6402
|
const foreignLive = !inFlight && typeof livePid === "number" && livePid > 0 && isPidAlive(livePid);
|
|
6264
|
-
if (inFlight) {
|
|
6403
|
+
if (inFlight || foreignLive) {
|
|
6265
6404
|
this.stop(thread.id, { clearQueue: false, continueQueue: true });
|
|
6266
|
-
} else {
|
|
6267
|
-
if (foreignLive) {
|
|
6268
|
-
this.stop(thread.id, { clearQueue: false, continueQueue: true });
|
|
6269
|
-
}
|
|
6270
|
-
void this.drainQueue(thread.id);
|
|
6271
6405
|
}
|
|
6406
|
+
void this.drainQueue(thread.id);
|
|
6272
6407
|
return this.requireThread(thread.id);
|
|
6273
6408
|
}
|
|
6274
6409
|
async drainQueue(threadId) {
|
|
@@ -6297,7 +6432,18 @@ var Orchestrator = class {
|
|
|
6297
6432
|
}
|
|
6298
6433
|
const livePid = thread.agentPid;
|
|
6299
6434
|
if (typeof livePid === "number" && livePid > 0 && isPidAlive(livePid)) {
|
|
6300
|
-
|
|
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
|
+
}
|
|
6301
6447
|
continue;
|
|
6302
6448
|
}
|
|
6303
6449
|
const prompt = thread.queue[0];
|
|
@@ -6405,7 +6551,7 @@ var Orchestrator = class {
|
|
|
6405
6551
|
}
|
|
6406
6552
|
const isBrightsy = fresh.agent === "brightsy";
|
|
6407
6553
|
const isOrchestration = isOrchestratorThread(fresh);
|
|
6408
|
-
const { autoRenameBranchEnabled: autoRenameBranchEnabled2, getGithubGitAuthMode: getGithubGitAuthMode2 } = await import("./app-settings-
|
|
6554
|
+
const { autoRenameBranchEnabled: autoRenameBranchEnabled2, getGithubGitAuthMode: getGithubGitAuthMode2 } = await import("./app-settings-EKJM2344.js");
|
|
6409
6555
|
const gitAuthMode = getGithubGitAuthMode2();
|
|
6410
6556
|
const worktreeDirective = isBrightsy || isOrchestration ? null : formatWorktreeDirective(fresh, {
|
|
6411
6557
|
githubSlug: await resolveGithubRepoSlug(fresh.worktreePath).catch(
|
|
@@ -6454,6 +6600,7 @@ var Orchestrator = class {
|
|
|
6454
6600
|
{ cachedPrefix, prompt: agentPrompt },
|
|
6455
6601
|
(event) => {
|
|
6456
6602
|
this.emit({ type: "turn_output", threadId, event });
|
|
6603
|
+
noteTurnLiveEvent(threadId, event);
|
|
6457
6604
|
if (event.type === "session_id") {
|
|
6458
6605
|
updateThread(threadId, { sessionId: event.data });
|
|
6459
6606
|
}
|
|
@@ -6500,7 +6647,7 @@ var Orchestrator = class {
|
|
|
6500
6647
|
if (this.requireThread(threadId).agent === "cursor" && exitCode !== 0 && !assistantText && parts.length === 0) {
|
|
6501
6648
|
const sessionId = result.sessionId || this.requireThread(threadId).sessionId || "";
|
|
6502
6649
|
if (sessionId) {
|
|
6503
|
-
const { recoverFinishedCursorRun } = await import("./cursor-recover-
|
|
6650
|
+
const { recoverFinishedCursorRun } = await import("./cursor-recover-ZUJG6XRQ.js");
|
|
6504
6651
|
for (let i = 0; i < 8; i++) {
|
|
6505
6652
|
const recovered = recoverFinishedCursorRun({
|
|
6506
6653
|
agentId: sessionId,
|
|
@@ -6517,19 +6664,16 @@ var Orchestrator = class {
|
|
|
6517
6664
|
}
|
|
6518
6665
|
let lastStderr = summarizeTurnStderr(stderrTail);
|
|
6519
6666
|
let detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
|
|
6520
|
-
if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) &&
|
|
6667
|
+
if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && this.requireThread(threadId).agent !== "brightsy" && shouldRetryFailedAgentTurn(detail, {
|
|
6668
|
+
hasSession: Boolean(this.requireThread(threadId).sessionId)
|
|
6669
|
+
})) {
|
|
6521
6670
|
updateThread(threadId, { sessionId: null });
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
"Agent session missing \u2014 starting a fresh session"
|
|
6525
|
-
);
|
|
6671
|
+
const retryNote = looksLikeInvalidAgentSession(detail) ? "Agent session missing \u2014 starting a fresh session" : "Agent runner crashed \u2014 restarting Node once";
|
|
6672
|
+
pushTurnStderr(stderrTail, retryNote);
|
|
6526
6673
|
this.emit({
|
|
6527
6674
|
type: "turn_output",
|
|
6528
6675
|
threadId,
|
|
6529
|
-
event: {
|
|
6530
|
-
type: "stderr",
|
|
6531
|
-
data: "Agent session missing \u2014 starting a fresh session"
|
|
6532
|
-
}
|
|
6676
|
+
event: { type: "stderr", data: retryNote }
|
|
6533
6677
|
});
|
|
6534
6678
|
const retryThread = this.requireThread(threadId);
|
|
6535
6679
|
const prior = retryThread.messages.slice(0, -1);
|
|
@@ -6578,10 +6722,7 @@ var Orchestrator = class {
|
|
|
6578
6722
|
lastStderr = summarizeTurnStderr(stderrTail);
|
|
6579
6723
|
detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
|
|
6580
6724
|
}
|
|
6581
|
-
let chatText = assistantText;
|
|
6582
|
-
if (exitCode !== 0 && !chatText && looksLikeAgentFailureMessage(detail)) {
|
|
6583
|
-
chatText = humanizeAgentFailDetail(detail);
|
|
6584
|
-
}
|
|
6725
|
+
let chatText = this.stoppedTurns.has(threadId) ? assistantText : turnFailChatText({ exitCode, assistantText, detail });
|
|
6585
6726
|
if (chatText || parts.length > 0) {
|
|
6586
6727
|
appendMessage(threadId, {
|
|
6587
6728
|
role: "agent",
|
|
@@ -6664,6 +6805,7 @@ var Orchestrator = class {
|
|
|
6664
6805
|
this.processes.delete(`${threadId}:agent`);
|
|
6665
6806
|
this.stoppedTurns.delete(threadId);
|
|
6666
6807
|
this.runningCount = Math.max(0, this.runningCount - 1);
|
|
6808
|
+
clearTurnLive(threadId);
|
|
6667
6809
|
try {
|
|
6668
6810
|
updateThread(threadId, { agentPid: null });
|
|
6669
6811
|
} catch {
|
|
@@ -6937,51 +7079,56 @@ var Orchestrator = class {
|
|
|
6937
7079
|
}
|
|
6938
7080
|
return created;
|
|
6939
7081
|
}
|
|
6940
|
-
async waitForTurn(threadRef, timeoutMs = 6e5) {
|
|
7082
|
+
async waitForTurn(threadRef, timeoutMs = 6e5, opts) {
|
|
6941
7083
|
const thread = this.requireThread(threadRef);
|
|
7084
|
+
if (!["running", "queued"].includes(thread.status)) {
|
|
7085
|
+
return thread;
|
|
7086
|
+
}
|
|
6942
7087
|
const start = Date.now();
|
|
6943
7088
|
return new Promise((resolve, reject) => {
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
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
|
+
};
|
|
6948
7098
|
const off = this.on((event) => {
|
|
6949
7099
|
if (event.type === "turn_finished" && event.threadId === thread.id) {
|
|
6950
|
-
off();
|
|
6951
7100
|
const latest = readThread(thread.id);
|
|
6952
7101
|
if (!latest) {
|
|
6953
|
-
reject(new Error(`Thread not found: ${thread.id}`));
|
|
7102
|
+
finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
|
|
6954
7103
|
return;
|
|
6955
7104
|
}
|
|
6956
|
-
resolve(latest);
|
|
7105
|
+
finish(() => resolve(latest));
|
|
6957
7106
|
}
|
|
6958
7107
|
if (event.type === "error" && event.threadId === thread.id) {
|
|
6959
|
-
off();
|
|
6960
7108
|
const latest = readThread(thread.id);
|
|
6961
7109
|
if (!latest) {
|
|
6962
|
-
reject(new Error(`Thread not found: ${thread.id}`));
|
|
7110
|
+
finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
|
|
6963
7111
|
return;
|
|
6964
7112
|
}
|
|
6965
|
-
resolve(latest);
|
|
7113
|
+
finish(() => resolve(latest));
|
|
6966
7114
|
}
|
|
6967
7115
|
});
|
|
6968
|
-
|
|
6969
|
-
if (Date.now() - start > timeoutMs) {
|
|
6970
|
-
clearInterval(timer);
|
|
6971
|
-
off();
|
|
6972
|
-
reject(new Error("wait_for_turn timed out"));
|
|
6973
|
-
}
|
|
7116
|
+
timer = setInterval(() => {
|
|
6974
7117
|
const current = readThread(thread.id);
|
|
6975
7118
|
if (!current) {
|
|
6976
|
-
|
|
6977
|
-
off();
|
|
6978
|
-
reject(new Error(`Thread not found: ${thread.id}`));
|
|
7119
|
+
finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
|
|
6979
7120
|
return;
|
|
6980
7121
|
}
|
|
6981
7122
|
if (!["running", "queued"].includes(current.status)) {
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
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
|
+
}
|
|
6985
7132
|
}
|
|
6986
7133
|
}, 200);
|
|
6987
7134
|
});
|
|
@@ -6989,10 +7136,18 @@ var Orchestrator = class {
|
|
|
6989
7136
|
getTurnResult(threadRef) {
|
|
6990
7137
|
const thread = this.requireThread(threadRef);
|
|
6991
7138
|
const lastAgent = [...thread.messages].reverse().find((m) => m.role === "agent");
|
|
7139
|
+
const lastError = thread.lastError ?? null;
|
|
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;
|
|
6992
7143
|
return {
|
|
6993
|
-
text:
|
|
7144
|
+
text: text3,
|
|
6994
7145
|
status: thread.status,
|
|
6995
|
-
sessionId: thread.sessionId
|
|
7146
|
+
sessionId: thread.sessionId,
|
|
7147
|
+
lastError,
|
|
7148
|
+
stillRunning,
|
|
7149
|
+
progress: live?.summary ?? null,
|
|
7150
|
+
lastActivityAt: live?.updatedAt ?? null
|
|
6996
7151
|
};
|
|
6997
7152
|
}
|
|
6998
7153
|
assertNotGlobal(thread, action) {
|
|
@@ -7155,7 +7310,7 @@ var Orchestrator = class {
|
|
|
7155
7310
|
updateThread(thread.id, { title: meta.title });
|
|
7156
7311
|
}
|
|
7157
7312
|
}
|
|
7158
|
-
const { autoArchiveOnMergeEnabled: autoArchiveOnMergeEnabled2 } = await import("./app-settings-
|
|
7313
|
+
const { autoArchiveOnMergeEnabled: autoArchiveOnMergeEnabled2 } = await import("./app-settings-EKJM2344.js");
|
|
7159
7314
|
const latest = this.requireThread(thread.id);
|
|
7160
7315
|
if (!shouldAutoArchiveOnPrMerge({
|
|
7161
7316
|
previousPrState: prevState || null,
|
|
@@ -7418,7 +7573,19 @@ var Orchestrator = class {
|
|
|
7418
7573
|
const thread = this.requireThread(threadRef);
|
|
7419
7574
|
return threadsSharingWorktree(thread.worktreePath);
|
|
7420
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
|
+
}
|
|
7421
7584
|
async archive(threadRef) {
|
|
7585
|
+
const thread = this.requireThread(threadRef);
|
|
7586
|
+
return this.enqueueWorktreeTeardown(thread, () => this.archiveUnlocked(threadRef));
|
|
7587
|
+
}
|
|
7588
|
+
async archiveUnlocked(threadRef) {
|
|
7422
7589
|
const thread = this.requireThread(threadRef);
|
|
7423
7590
|
this.stop(thread.id);
|
|
7424
7591
|
this.releaseOrchestratorCaffeinate(thread);
|
|
@@ -7446,7 +7613,7 @@ var Orchestrator = class {
|
|
|
7446
7613
|
this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
|
|
7447
7614
|
if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
|
|
7448
7615
|
try {
|
|
7449
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-
|
|
7616
|
+
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-TKJXPNV4.js");
|
|
7450
7617
|
await ensureWorkspace2(thread.repoPath);
|
|
7451
7618
|
} catch {
|
|
7452
7619
|
}
|
|
@@ -7454,6 +7621,10 @@ var Orchestrator = class {
|
|
|
7454
7621
|
return archived;
|
|
7455
7622
|
}
|
|
7456
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) {
|
|
7457
7628
|
const thread = this.requireThread(threadRef);
|
|
7458
7629
|
this.stop(thread.id);
|
|
7459
7630
|
this.releaseOrchestratorCaffeinate(thread);
|
|
@@ -7468,7 +7639,7 @@ var Orchestrator = class {
|
|
|
7468
7639
|
await runArchiveScript(thread.repoPath, thread.worktreePath);
|
|
7469
7640
|
} catch {
|
|
7470
7641
|
}
|
|
7471
|
-
const { deleteBranchOnPurgeEnabled: deleteBranchOnPurgeEnabled2 } = await import("./app-settings-
|
|
7642
|
+
const { deleteBranchOnPurgeEnabled: deleteBranchOnPurgeEnabled2 } = await import("./app-settings-EKJM2344.js");
|
|
7472
7643
|
const deleteBranch = opts?.deleteBranch ?? deleteBranchOnPurgeEnabled2();
|
|
7473
7644
|
await removeWorktree(thread.repoPath, thread.worktreePath, {
|
|
7474
7645
|
deleteBranch: deleteBranch ? thread.branchName : void 0
|
|
@@ -7482,19 +7653,18 @@ var Orchestrator = class {
|
|
|
7482
7653
|
throw new Error("Thread is not archived");
|
|
7483
7654
|
}
|
|
7484
7655
|
if (isGlobalThread(thread)) {
|
|
7485
|
-
const { globalAgentCwd: globalAgentCwd2 } = await import("./paths-
|
|
7656
|
+
const { globalAgentCwd: globalAgentCwd2 } = await import("./paths-UMEDHOS5.js");
|
|
7486
7657
|
updateThread(thread.id, { worktreePath: globalAgentCwd2() });
|
|
7487
7658
|
const restored2 = setStatus(thread.id, "idle");
|
|
7488
7659
|
this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
|
|
7489
7660
|
return restored2;
|
|
7490
7661
|
}
|
|
7491
|
-
if (!
|
|
7492
|
-
const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-
|
|
7493
|
-
const { execa: execa5 } = await import("execa");
|
|
7662
|
+
if (!existsSync15(thread.worktreePath)) {
|
|
7663
|
+
const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-PEUPFAKL.js");
|
|
7494
7664
|
const slug = thread.worktreePath.split("/").pop();
|
|
7495
7665
|
const dest = thread.worktreePath;
|
|
7496
|
-
await
|
|
7497
|
-
|
|
7666
|
+
await withRepoGitLock(thread.repoPath, async () => {
|
|
7667
|
+
await git(["worktree", "add", dest, thread.branchName], thread.repoPath);
|
|
7498
7668
|
});
|
|
7499
7669
|
void createThreadWorktree2;
|
|
7500
7670
|
void slug;
|
|
@@ -7598,7 +7768,7 @@ async function startOrchestration(opts) {
|
|
|
7598
7768
|
sourceRef: "default",
|
|
7599
7769
|
...createOpts
|
|
7600
7770
|
}).catch(async () => {
|
|
7601
|
-
const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-
|
|
7771
|
+
const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-PEUPFAKL.js");
|
|
7602
7772
|
const repo = await resolveRepoRoot2(repoPath);
|
|
7603
7773
|
const def = await resolveDefaultBranch2(repo);
|
|
7604
7774
|
return orch.createThread({
|
|
@@ -7608,7 +7778,7 @@ async function startOrchestration(opts) {
|
|
|
7608
7778
|
repoPath: repo
|
|
7609
7779
|
});
|
|
7610
7780
|
});
|
|
7611
|
-
const { updateThread: upd } = await import("./thread-store-
|
|
7781
|
+
const { updateThread: upd } = await import("./thread-store-4OUEQ2DB.js");
|
|
7612
7782
|
const updated = upd(thread.id, {
|
|
7613
7783
|
sourceType: "orchestration",
|
|
7614
7784
|
sourceRef: goal
|
|
@@ -7731,6 +7901,15 @@ function mcpArchiveBlockedReason(thread) {
|
|
|
7731
7901
|
return null;
|
|
7732
7902
|
}
|
|
7733
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
|
+
|
|
7734
7913
|
// src/mcp/slack-tools.ts
|
|
7735
7914
|
import { z } from "zod";
|
|
7736
7915
|
|
|
@@ -8358,7 +8537,7 @@ async function startMcpServer() {
|
|
|
8358
8537
|
);
|
|
8359
8538
|
}
|
|
8360
8539
|
try {
|
|
8361
|
-
const { maxConcurrentAgents: maxConcurrentAgents2 } = await import("./app-settings-
|
|
8540
|
+
const { maxConcurrentAgents: maxConcurrentAgents2 } = await import("./app-settings-EKJM2344.js");
|
|
8362
8541
|
orch.setMaxConcurrent(maxConcurrentAgents2());
|
|
8363
8542
|
} catch {
|
|
8364
8543
|
}
|
|
@@ -8401,7 +8580,9 @@ async function startMcpServer() {
|
|
|
8401
8580
|
const threads = orch.getThreads(true);
|
|
8402
8581
|
const lines = threads.map((t) => {
|
|
8403
8582
|
const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : basename4(t.repoPath) || t.repoPath;
|
|
8404
|
-
|
|
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}`;
|
|
8405
8586
|
});
|
|
8406
8587
|
return {
|
|
8407
8588
|
content: [{ type: "text", text: lines.join("\n") || "(no threads)" }]
|
|
@@ -8410,13 +8591,14 @@ async function startMcpServer() {
|
|
|
8410
8591
|
);
|
|
8411
8592
|
server.tool(
|
|
8412
8593
|
"get_thread",
|
|
8413
|
-
"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.",
|
|
8414
8595
|
{ ref: z3.string() },
|
|
8415
8596
|
async ({ ref }) => {
|
|
8416
8597
|
const t = orch.getThread(ref);
|
|
8417
8598
|
if (!t) {
|
|
8418
8599
|
return { content: [{ type: "text", text: `Thread not found: ${ref}` }], isError: true };
|
|
8419
8600
|
}
|
|
8601
|
+
const live = t.status === "running" || t.status === "queued" ? readTurnLive(t.id) : null;
|
|
8420
8602
|
const summary = {
|
|
8421
8603
|
id: t.id,
|
|
8422
8604
|
title: t.title,
|
|
@@ -8431,7 +8613,10 @@ async function startMcpServer() {
|
|
|
8431
8613
|
messageCount: t.messages.length,
|
|
8432
8614
|
devPort: t.devPort,
|
|
8433
8615
|
prUrl: t.prUrl,
|
|
8434
|
-
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
|
|
8435
8620
|
};
|
|
8436
8621
|
return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
|
|
8437
8622
|
}
|
|
@@ -8567,8 +8752,8 @@ async function startMcpServer() {
|
|
|
8567
8752
|
registerSlackTools(server);
|
|
8568
8753
|
registerLinearTools(server);
|
|
8569
8754
|
if (sideboardMcpProfile() !== "worktree") {
|
|
8570
|
-
const { getCaffeinateHold: getCaffeinateHold2, setCaffeinateHold: setCaffeinateHold2 } = await import("./caffeinate-hold-
|
|
8571
|
-
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");
|
|
8572
8757
|
const accountDefaults = resolveThreadDefaults2();
|
|
8573
8758
|
const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
|
|
8574
8759
|
server.tool(
|
|
@@ -8757,13 +8942,15 @@ async function startMcpServer() {
|
|
|
8757
8942
|
);
|
|
8758
8943
|
server.tool(
|
|
8759
8944
|
"wait_for_turn",
|
|
8760
|
-
"
|
|
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.",
|
|
8761
8946
|
{
|
|
8762
8947
|
ref: z3.string(),
|
|
8763
8948
|
timeoutMs: z3.number().optional()
|
|
8764
8949
|
},
|
|
8765
8950
|
async ({ ref, timeoutMs }) => {
|
|
8766
|
-
const thread = await orch.waitForTurn(ref, timeoutMs
|
|
8951
|
+
const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
|
|
8952
|
+
resolveIfStillRunning: true
|
|
8953
|
+
});
|
|
8767
8954
|
const result = orch.getTurnResult(thread.id);
|
|
8768
8955
|
return {
|
|
8769
8956
|
content: [
|
|
@@ -8772,7 +8959,12 @@ async function startMcpServer() {
|
|
|
8772
8959
|
text: JSON.stringify({
|
|
8773
8960
|
id: thread.id,
|
|
8774
8961
|
status: result.status,
|
|
8775
|
-
text: result.text
|
|
8962
|
+
text: result.text,
|
|
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
|
|
8776
8968
|
})
|
|
8777
8969
|
}
|
|
8778
8970
|
]
|
|
@@ -8781,11 +8973,21 @@ async function startMcpServer() {
|
|
|
8781
8973
|
);
|
|
8782
8974
|
server.tool(
|
|
8783
8975
|
"get_turn_result",
|
|
8784
|
-
"
|
|
8976
|
+
"Assistant message when the turn finished, or live progress while stillRunning. Not the full transcript.",
|
|
8785
8977
|
{ ref: z3.string() },
|
|
8786
8978
|
async ({ ref }) => {
|
|
8787
8979
|
const result = orch.getTurnResult(ref);
|
|
8788
|
-
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
|
+
};
|
|
8789
8991
|
}
|
|
8790
8992
|
);
|
|
8791
8993
|
server.tool(
|
|
@@ -8902,7 +9104,7 @@ async function startMcpServer() {
|
|
|
8902
9104
|
);
|
|
8903
9105
|
server.tool(
|
|
8904
9106
|
"request_review",
|
|
8905
|
-
'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.',
|
|
8906
9108
|
{ ref: z3.string().describe("Worktree thread id/ref to review") },
|
|
8907
9109
|
async ({ ref }) => {
|
|
8908
9110
|
try {
|
|
@@ -8930,7 +9132,7 @@ async function startMcpServer() {
|
|
|
8930
9132
|
);
|
|
8931
9133
|
server.tool(
|
|
8932
9134
|
"ask_git",
|
|
8933
|
-
"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.",
|
|
8934
9136
|
{
|
|
8935
9137
|
ref: z3.string().describe("Worktree thread id/ref"),
|
|
8936
9138
|
action: z3.enum(AGENT_GIT_ACTIONS).describe(
|
|
@@ -8981,7 +9183,7 @@ async function startMcpServer() {
|
|
|
8981
9183
|
);
|
|
8982
9184
|
server.tool(
|
|
8983
9185
|
"fork_worktree",
|
|
8984
|
-
"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.",
|
|
8985
9187
|
{
|
|
8986
9188
|
ref: z3.string().describe("Worktree thread id/ref to fork"),
|
|
8987
9189
|
through_index: z3.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
@@ -9026,7 +9228,7 @@ async function startMcpServer() {
|
|
|
9026
9228
|
);
|
|
9027
9229
|
server.tool(
|
|
9028
9230
|
"fork_chat",
|
|
9029
|
-
"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.",
|
|
9030
9232
|
{
|
|
9031
9233
|
ref: z3.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
9032
9234
|
through_index: z3.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
@@ -9693,7 +9895,7 @@ async function runCloudConnect(opts) {
|
|
|
9693
9895
|
}
|
|
9694
9896
|
|
|
9695
9897
|
// src/agents/user-mcp-config.ts
|
|
9696
|
-
import { existsSync as
|
|
9898
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
|
|
9697
9899
|
import { homedir as homedir2 } from "os";
|
|
9698
9900
|
import { dirname as dirname4, join as join14 } from "path";
|
|
9699
9901
|
function userCursorMcpConfigPath() {
|
|
@@ -9727,16 +9929,16 @@ function mergeSideboardIntoMcpServersJson(existing, sideboard) {
|
|
|
9727
9929
|
}
|
|
9728
9930
|
function writeMergedMcpServersJson(configPath, sideboard) {
|
|
9729
9931
|
let existing = {};
|
|
9730
|
-
if (
|
|
9932
|
+
if (existsSync16(configPath)) {
|
|
9731
9933
|
try {
|
|
9732
|
-
existing = JSON.parse(
|
|
9934
|
+
existing = JSON.parse(readFileSync12(configPath, "utf8"));
|
|
9733
9935
|
} catch {
|
|
9734
9936
|
existing = {};
|
|
9735
9937
|
}
|
|
9736
9938
|
}
|
|
9737
9939
|
const next = mergeSideboardIntoMcpServersJson(existing, sideboard);
|
|
9738
9940
|
mkdirSync5(dirname4(configPath), { recursive: true });
|
|
9739
|
-
|
|
9941
|
+
writeFileSync6(configPath, `${JSON.stringify(next, null, 2)}
|
|
9740
9942
|
`);
|
|
9741
9943
|
}
|
|
9742
9944
|
function launchFromResolved(server) {
|
|
@@ -9754,7 +9956,7 @@ async function registerPackagedUserMcpClients() {
|
|
|
9754
9956
|
const cursor = userCursorMcpConfigPath();
|
|
9755
9957
|
writeMergedMcpServersJson(cursor, launch);
|
|
9756
9958
|
const claude = userClaudeMcpConfigPath();
|
|
9757
|
-
if (
|
|
9959
|
+
if (existsSync16(claude)) {
|
|
9758
9960
|
writeMergedMcpServersJson(claude, launch);
|
|
9759
9961
|
return { cursor, claude };
|
|
9760
9962
|
}
|
|
@@ -10252,6 +10454,8 @@ function connectSession(ws, opts, log) {
|
|
|
10252
10454
|
|
|
10253
10455
|
// src/slack/relay-protocol.ts
|
|
10254
10456
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
10457
|
+
var SLACK_RELAY_PING_INTERVAL_MS = 2e4;
|
|
10458
|
+
var SLACK_RELAY_PONG_TIMEOUT_MS = 15e3;
|
|
10255
10459
|
function slackRelaySessionKey(teamId, userId, deviceId) {
|
|
10256
10460
|
return `${teamId.trim()}:${userId.trim()}:${deviceId.trim()}`;
|
|
10257
10461
|
}
|
|
@@ -10437,10 +10641,21 @@ function connectSession2(ws, opts, log) {
|
|
|
10437
10641
|
const allowedUsers = new Set(
|
|
10438
10642
|
opts.workspaces.map((w) => `${w.teamId.trim()}:${w.userId.trim()}`)
|
|
10439
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
|
+
};
|
|
10440
10654
|
const finish = (err) => {
|
|
10441
10655
|
if (settled) return;
|
|
10442
10656
|
settled = true;
|
|
10443
10657
|
pending.clear();
|
|
10658
|
+
stopKeepalive();
|
|
10444
10659
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
10445
10660
|
if (err) reject(err);
|
|
10446
10661
|
else resolve();
|
|
@@ -10453,6 +10668,25 @@ function connectSession2(ws, opts, log) {
|
|
|
10453
10668
|
finish();
|
|
10454
10669
|
};
|
|
10455
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
|
+
};
|
|
10456
10690
|
ws.addEventListener("open", () => {
|
|
10457
10691
|
const label = opts.deviceLabel?.trim() || opts.deviceId.trim();
|
|
10458
10692
|
log(`Relay connected \xB7 ${label}`);
|
|
@@ -10467,6 +10701,8 @@ function connectSession2(ws, opts, log) {
|
|
|
10467
10701
|
userToken: wsInfo.userToken
|
|
10468
10702
|
});
|
|
10469
10703
|
}
|
|
10704
|
+
pingOnce();
|
|
10705
|
+
pingTimer = setInterval(pingOnce, pingMs);
|
|
10470
10706
|
});
|
|
10471
10707
|
ws.addEventListener("error", () => {
|
|
10472
10708
|
finish(new Error("WebSocket error"));
|
|
@@ -10478,7 +10714,13 @@ function connectSession2(ws, opts, log) {
|
|
|
10478
10714
|
const raw = typeof ev.data === "string" ? ev.data : String(ev.data ?? "");
|
|
10479
10715
|
const msg = parseSlackRelayServerMessage(raw);
|
|
10480
10716
|
if (!msg) return;
|
|
10481
|
-
if (msg.type === "pong")
|
|
10717
|
+
if (msg.type === "pong") {
|
|
10718
|
+
if (pongTimer) {
|
|
10719
|
+
clearTimeout(pongTimer);
|
|
10720
|
+
pongTimer = null;
|
|
10721
|
+
}
|
|
10722
|
+
return;
|
|
10723
|
+
}
|
|
10482
10724
|
if (msg.type === "error") {
|
|
10483
10725
|
log(`relay error: ${msg.message}`);
|
|
10484
10726
|
return;
|
|
@@ -10601,6 +10843,13 @@ function slackReplyThreadTs(msg) {
|
|
|
10601
10843
|
if (msg.kind === "mention") return msg.ts;
|
|
10602
10844
|
return void 0;
|
|
10603
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
|
+
}
|
|
10604
10853
|
var SLACK_SEEN_REACTION = "eyes";
|
|
10605
10854
|
function writeTokenForTeam(teamId) {
|
|
10606
10855
|
const workspaces = listSlackWorkspacesRaw();
|
|
@@ -10637,8 +10886,8 @@ async function ackSlackInboundSeen(msg, opts) {
|
|
|
10637
10886
|
log(`react error: ${errMsg}${hint}`);
|
|
10638
10887
|
}
|
|
10639
10888
|
}
|
|
10640
|
-
|
|
10641
|
-
|
|
10889
|
+
function replyStub(target) {
|
|
10890
|
+
return {
|
|
10642
10891
|
teamId: target.teamId,
|
|
10643
10892
|
channelId: target.channelId,
|
|
10644
10893
|
ts: target.threadTs ?? "",
|
|
@@ -10646,12 +10895,15 @@ async function postSlackText(target, text3, opts) {
|
|
|
10646
10895
|
text: "",
|
|
10647
10896
|
kind: "dm"
|
|
10648
10897
|
};
|
|
10898
|
+
}
|
|
10899
|
+
async function postSlackText(target, text3, opts) {
|
|
10649
10900
|
if (opts.postReply) {
|
|
10650
|
-
await opts.postReply(
|
|
10651
|
-
|
|
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;
|
|
10652
10904
|
}
|
|
10653
10905
|
const token = writeTokenForTeam(target.teamId);
|
|
10654
|
-
await slackApi(
|
|
10906
|
+
const json = await slackApi(
|
|
10655
10907
|
token,
|
|
10656
10908
|
"chat.postMessage",
|
|
10657
10909
|
{
|
|
@@ -10661,18 +10913,163 @@ async function postSlackText(target, text3, opts) {
|
|
|
10661
10913
|
},
|
|
10662
10914
|
opts.fetchImpl
|
|
10663
10915
|
);
|
|
10916
|
+
const ts = json.ts?.trim();
|
|
10917
|
+
return ts ? { ts } : null;
|
|
10664
10918
|
}
|
|
10665
|
-
async function
|
|
10666
|
-
|
|
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",
|
|
10667
10928
|
{
|
|
10668
|
-
|
|
10669
|
-
|
|
10670
|
-
|
|
10929
|
+
channel: target.channelId,
|
|
10930
|
+
ts,
|
|
10931
|
+
text: text3
|
|
10671
10932
|
},
|
|
10672
|
-
|
|
10673
|
-
|
|
10933
|
+
opts.fetchImpl
|
|
10934
|
+
);
|
|
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
|
|
10674
10950
|
);
|
|
10675
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
|
+
}
|
|
10676
11073
|
var lastRelayed = /* @__PURE__ */ new Map();
|
|
10677
11074
|
function markRelayed(threadId, text3) {
|
|
10678
11075
|
lastRelayed.set(threadId, `${threadId}:${text3}`);
|
|
@@ -10764,10 +11161,15 @@ async function handleSlackInbound(msg, opts) {
|
|
|
10764
11161
|
});
|
|
10765
11162
|
};
|
|
10766
11163
|
bindReplyTarget(fresh.id);
|
|
11164
|
+
const progress = startSlackTurnProgress(msg, () => fresh.id, opts);
|
|
10767
11165
|
const runTurn = async (threadId) => {
|
|
10768
11166
|
await orch.send(threadId, prompt);
|
|
10769
11167
|
await orch.waitForTurn(threadId, 14 * 60 * 1e3);
|
|
10770
11168
|
};
|
|
11169
|
+
const dropProgress = async (reason) => {
|
|
11170
|
+
log(reason);
|
|
11171
|
+
await progress.discard();
|
|
11172
|
+
};
|
|
10771
11173
|
let reply;
|
|
10772
11174
|
try {
|
|
10773
11175
|
try {
|
|
@@ -10780,12 +11182,12 @@ async function handleSlackInbound(msg, opts) {
|
|
|
10780
11182
|
await runTurn(fresh.id);
|
|
10781
11183
|
}
|
|
10782
11184
|
if (slackInboundSuperseded(opts)) {
|
|
10783
|
-
|
|
11185
|
+
await dropProgress(`turn finished ${msg.ts} (superseded)`);
|
|
10784
11186
|
return;
|
|
10785
11187
|
}
|
|
10786
11188
|
let after = readThread(fresh.id);
|
|
10787
11189
|
if (after?.status === "stopped") {
|
|
10788
|
-
|
|
11190
|
+
await dropProgress(`turn finished ${msg.ts} (interrupted, skip post)`);
|
|
10789
11191
|
return;
|
|
10790
11192
|
}
|
|
10791
11193
|
if (!after || after.status === "archived") {
|
|
@@ -10794,46 +11196,48 @@ async function handleSlackInbound(msg, opts) {
|
|
|
10794
11196
|
bindReplyTarget(fresh.id);
|
|
10795
11197
|
await runTurn(fresh.id);
|
|
10796
11198
|
if (slackInboundSuperseded(opts)) {
|
|
10797
|
-
|
|
11199
|
+
await dropProgress(`turn finished ${msg.ts} (superseded)`);
|
|
10798
11200
|
return;
|
|
10799
11201
|
}
|
|
10800
11202
|
after = readThread(fresh.id);
|
|
10801
11203
|
if (!after || after.status === "stopped" || after.status === "archived") {
|
|
10802
|
-
|
|
11204
|
+
await dropProgress(`turn finished ${msg.ts} (interrupted, skip post)`);
|
|
10803
11205
|
return;
|
|
10804
11206
|
}
|
|
10805
11207
|
}
|
|
10806
11208
|
reply = orch.getTurnResult(fresh.id).text.trim();
|
|
10807
11209
|
} catch (err) {
|
|
10808
11210
|
if (slackInboundSuperseded(opts)) {
|
|
10809
|
-
|
|
11211
|
+
await dropProgress(`turn error ${msg.ts} (superseded)`);
|
|
10810
11212
|
return;
|
|
10811
11213
|
}
|
|
10812
11214
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
10813
11215
|
if (slackCoordinatorGone(err)) {
|
|
10814
|
-
|
|
11216
|
+
await dropProgress(`turn finished ${msg.ts} (thread gone, skip post)`);
|
|
10815
11217
|
return;
|
|
10816
11218
|
}
|
|
10817
11219
|
const timedOut = /timed out|timeout/i.test(errMsg);
|
|
10818
11220
|
const errorReply = timedOut ? SLACK_LISTEN_TIMEOUT_REPLY : `Sideboard coordinator error: ${errMsg}`;
|
|
10819
|
-
|
|
11221
|
+
try {
|
|
11222
|
+
await progress.finishWith(errorReply);
|
|
11223
|
+
log(`replied error ${msg.ts}: ${errMsg}`);
|
|
11224
|
+
} catch (postErr) {
|
|
10820
11225
|
const postMsg = postErr instanceof Error ? postErr.message : String(postErr);
|
|
10821
11226
|
log(`post error: ${postMsg}`);
|
|
10822
|
-
}
|
|
10823
|
-
log(`replied error ${msg.ts}: ${errMsg}`);
|
|
11227
|
+
}
|
|
10824
11228
|
if (!timedOut) throw err;
|
|
10825
11229
|
return;
|
|
10826
11230
|
}
|
|
10827
11231
|
if (!reply) {
|
|
10828
|
-
|
|
11232
|
+
await dropProgress(`post error: no agent text to send for ${msg.ts}`);
|
|
10829
11233
|
return;
|
|
10830
11234
|
}
|
|
10831
11235
|
if (alreadyRelayed(fresh.id, reply)) {
|
|
10832
|
-
|
|
11236
|
+
await dropProgress(`turn finished ${msg.ts} (already posted)`);
|
|
10833
11237
|
return;
|
|
10834
11238
|
}
|
|
10835
11239
|
try {
|
|
10836
|
-
await
|
|
11240
|
+
await progress.finishWith(reply);
|
|
10837
11241
|
markRelayed(fresh.id, reply);
|
|
10838
11242
|
log(`replied ${msg.ts} (${reply.length} chars)`);
|
|
10839
11243
|
} catch (err) {
|
|
@@ -11384,6 +11788,20 @@ async function startSlackRelayServer(opts) {
|
|
|
11384
11788
|
}
|
|
11385
11789
|
}
|
|
11386
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);
|
|
11387
11805
|
log("desktop connected");
|
|
11388
11806
|
ws.on("message", (data) => {
|
|
11389
11807
|
const raw = typeof data === "string" ? data : data.toString("utf8");
|
|
@@ -11494,6 +11912,8 @@ export {
|
|
|
11494
11912
|
SLACK_LISTEN_TIMEOUT_REPLY,
|
|
11495
11913
|
SLACK_OAUTH_CANCELLED,
|
|
11496
11914
|
SLACK_OAUTH_REDIRECT,
|
|
11915
|
+
SLACK_PROGRESS_DELAY_MS,
|
|
11916
|
+
SLACK_PROGRESS_EDIT_MS,
|
|
11497
11917
|
SLACK_REPLY_FORMATTING,
|
|
11498
11918
|
SLACK_SEEN_REACTION,
|
|
11499
11919
|
SlackOAuthCancelledError,
|
|
@@ -11651,6 +12071,7 @@ export {
|
|
|
11651
12071
|
formatSlackInboundPrompt,
|
|
11652
12072
|
formatSlackRepliesForTurn,
|
|
11653
12073
|
formatSlackSignedReply,
|
|
12074
|
+
formatSlackWorkingText,
|
|
11654
12075
|
formatTranscriptMarkdown,
|
|
11655
12076
|
formatUiReminder,
|
|
11656
12077
|
formatWorkspaceInventory,
|
|
@@ -11946,6 +12367,7 @@ export {
|
|
|
11946
12367
|
thisProcessShouldDrainAgentQueues,
|
|
11947
12368
|
threadDisplayLabel,
|
|
11948
12369
|
threadFilePath,
|
|
12370
|
+
threadLivePath,
|
|
11949
12371
|
threadLockPath,
|
|
11950
12372
|
threadRequestsBrightsyMcp,
|
|
11951
12373
|
threadsDir,
|
|
@@ -11969,6 +12391,7 @@ export {
|
|
|
11969
12391
|
userClaudeMcpConfigPath,
|
|
11970
12392
|
userCursorMcpConfigPath,
|
|
11971
12393
|
validateLinearApiKey,
|
|
12394
|
+
waitForPidExit,
|
|
11972
12395
|
warmGithubAgentAuth,
|
|
11973
12396
|
withAgentInstructions,
|
|
11974
12397
|
withExportedPath,
|