@sideboard-ai/core 0.1.278 → 0.1.279
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 +3 -3
- package/dist/agents/cursor-runner.js +5 -5
- package/dist/{agents-2FOWVJCN.js → agents-DYCFLDNL.js} +5 -5
- package/dist/{agents-JBVJRDH3.js → agents-RXP5NZBC.js} +3 -3
- package/dist/{chunk-UKSJO36E.js → chunk-CPA43FBK.js} +4 -4
- package/dist/{chunk-FELFTNH2.js → chunk-CPGJ5ICZ.js} +261 -19
- package/dist/{chunk-NIB2IZ6J.js → chunk-HNNS25SG.js} +5 -5
- package/dist/{chunk-MGS5T2A3.js → chunk-HQXBBUQX.js} +161 -9
- package/dist/{chunk-FIBZEFKA.js → chunk-IFURHRAR.js} +1 -1
- package/dist/{chunk-5N4Q46SG.js → chunk-K2S33ZCB.js} +5 -5
- package/dist/{chunk-NT7RIWRR.js → chunk-RK5PIWCW.js} +3 -3
- package/dist/{chunk-A2G3ZBJS.js → chunk-SKSOCIJT.js} +8 -8
- package/dist/{chunk-QTZO4LPU.js → chunk-TZ5JHIYK.js} +8 -8
- package/dist/{chunk-27Z4MEQ5.js → chunk-UZUXWBXK.js} +1 -1
- package/dist/{chunk-SQOAFR2U.js → chunk-V5DMHB6Y.js} +270 -23
- package/dist/{chunk-FAGK33E2.js → chunk-Y7JPL4WA.js} +1 -1
- package/dist/{chunk-HOMZKYHF.js → chunk-YDXNCBA2.js} +107 -8
- package/dist/{conductor-YYQKZAND.js → conductor-J6WDOXWM.js} +3 -3
- package/dist/{conductor-GBCE37K4.js → conductor-OW6NAK3N.js} +4 -4
- package/dist/{coordinator-prompt-SFZCZ3HR.js → coordinator-prompt-ERTHREGJ.js} +1 -1
- package/dist/{coordinator-prompt-B3GLVXPP.js → coordinator-prompt-Z2D32HWK.js} +1 -1
- package/dist/{global-workspace-7XEXTIHL.js → global-workspace-5PJXO45Z.js} +1 -1
- package/dist/{global-workspace-N2YYJKA5.js → global-workspace-AK5PSPQ2.js} +1 -1
- package/dist/index.cjs +798 -445
- package/dist/index.d.cts +52 -15
- package/dist/index.d.ts +52 -15
- package/dist/index.js +251 -290
- package/dist/mcp/run-stdio.cjs +529 -257
- package/dist/mcp/run-stdio.js +40 -124
- package/dist/{orchestrator-6QJLM6Y4.js → orchestrator-E5WK57DA.js} +5 -5
- package/dist/{orchestrator-QMKVXVUR.js → orchestrator-KY2KBBE2.js} +7 -7
- package/dist/{workspaces-PBB6HY2D.js → workspaces-UP6DQ3WL.js} +1 -1
- package/dist/{workspaces-BJCHMGVG.js → workspaces-WU2RNLDV.js} +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
RUN_SCRIPT_DESKTOP_ADOPT_MS,
|
|
19
19
|
addStackLayerFromThread,
|
|
20
20
|
adoptThread,
|
|
21
|
+
agentBlockFields,
|
|
21
22
|
agentGitPrompt,
|
|
22
23
|
alignQueueAttachments,
|
|
23
24
|
appendQueuedItem,
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
assembleHomeBoard,
|
|
28
29
|
boardPinIdentity,
|
|
29
30
|
captureTurnBaseline,
|
|
31
|
+
chatBlockedSuffix,
|
|
30
32
|
claimDesktopHost,
|
|
31
33
|
classifyWorktreeColumn,
|
|
32
34
|
classifyWorktreeListBadges,
|
|
@@ -63,6 +65,7 @@ import {
|
|
|
63
65
|
fireSchedule,
|
|
64
66
|
forkThreadWorktree,
|
|
65
67
|
formatArtifactDirective,
|
|
68
|
+
formatAskUserNotifyMessage,
|
|
66
69
|
formatDetachedJobInvoke,
|
|
67
70
|
formatDevAccessHint,
|
|
68
71
|
formatInjectedNoticesForTurn,
|
|
@@ -72,6 +75,7 @@ import {
|
|
|
72
75
|
formatLinearReminder,
|
|
73
76
|
formatLongRunningDirective,
|
|
74
77
|
formatLongRunningReminder,
|
|
78
|
+
formatNotifyOrchestratorPrompt,
|
|
75
79
|
formatOptionalServicesDirective,
|
|
76
80
|
formatOptionalServicesReminder,
|
|
77
81
|
formatPrGateDirective,
|
|
@@ -124,6 +128,7 @@ import {
|
|
|
124
128
|
mergeSetupOutput,
|
|
125
129
|
moveQueuedItemToFront,
|
|
126
130
|
normalizeServiceOrigin,
|
|
131
|
+
notifyOrchestrator,
|
|
127
132
|
openPrStackLayers,
|
|
128
133
|
openStackLayer,
|
|
129
134
|
optionalServiceConnected,
|
|
@@ -146,6 +151,7 @@ import {
|
|
|
146
151
|
resolveConductorCursorAgentId,
|
|
147
152
|
resolveCreateFirstPrompt,
|
|
148
153
|
resolveDetachedJobScript,
|
|
154
|
+
resolveNotifyCallerThread,
|
|
149
155
|
resolveOrchChildFollowUp,
|
|
150
156
|
resolveScheduleThreadId,
|
|
151
157
|
resolveSendFollowUp,
|
|
@@ -154,6 +160,7 @@ import {
|
|
|
154
160
|
scheduleNeedsCaffeinate,
|
|
155
161
|
schedulesPath,
|
|
156
162
|
shiftQueuedItem,
|
|
163
|
+
shouldNotifyOrchestrator,
|
|
157
164
|
shouldRemoveWorktreeOnTeardown,
|
|
158
165
|
shouldRunHistoryCleanup,
|
|
159
166
|
shouldRunWorktreeCleanup,
|
|
@@ -162,6 +169,9 @@ import {
|
|
|
162
169
|
spawnAgentTurn,
|
|
163
170
|
stackAgentDefaultsFrom,
|
|
164
171
|
stackIdFrom,
|
|
172
|
+
stampAskUserBlock,
|
|
173
|
+
stampNotifyBlock,
|
|
174
|
+
stampPlanBlock,
|
|
165
175
|
startOrchestration,
|
|
166
176
|
statWorktreePath,
|
|
167
177
|
syncBoardPins,
|
|
@@ -169,11 +179,13 @@ import {
|
|
|
169
179
|
thisProcessShouldDrainAgentQueues,
|
|
170
180
|
updateSchedule,
|
|
171
181
|
verifyOptionalService,
|
|
182
|
+
visibleAgentBlock,
|
|
172
183
|
waitForPidExit,
|
|
184
|
+
worktreeAgentBlock,
|
|
173
185
|
worktreeBoardStatus,
|
|
174
186
|
worktreeCleanupSettings,
|
|
175
187
|
writeWorktreeFile
|
|
176
|
-
} from "./chunk-
|
|
188
|
+
} from "./chunk-V5DMHB6Y.js";
|
|
177
189
|
import {
|
|
178
190
|
BASE_ALLOWED_TOOLS,
|
|
179
191
|
BRIGHTSY_MCP_ALLOWED_TOOLS,
|
|
@@ -263,7 +275,7 @@ import {
|
|
|
263
275
|
whichOnPath,
|
|
264
276
|
worktreeMcpToolNames,
|
|
265
277
|
writeInjectedMcpConfig
|
|
266
|
-
} from "./chunk-
|
|
278
|
+
} from "./chunk-HNNS25SG.js";
|
|
267
279
|
import {
|
|
268
280
|
brightsyMcpServerName,
|
|
269
281
|
connectBrightsyTeam,
|
|
@@ -284,6 +296,7 @@ import {
|
|
|
284
296
|
CONTEXT_MIN_MESSAGES,
|
|
285
297
|
CONTEXT_REVIEW_PATH,
|
|
286
298
|
LEGACY_REVIEW_REQUEST_PATH,
|
|
299
|
+
PLAN_QUESTION_ANSWERS_PREFIX,
|
|
287
300
|
PORT_RANGE_SIZE,
|
|
288
301
|
REPO_REVIEW_NAME,
|
|
289
302
|
REPO_REVIEW_PATH,
|
|
@@ -321,19 +334,24 @@ import {
|
|
|
321
334
|
estimateOccupancyTokens,
|
|
322
335
|
estimateThreadChars,
|
|
323
336
|
extractBrightsyContextSummary,
|
|
337
|
+
extractPendingPlanQuestions,
|
|
324
338
|
extractiveSummary,
|
|
325
339
|
findLastBrightsyContextSummary,
|
|
326
340
|
forkChatTab,
|
|
327
341
|
forkMessageSlice,
|
|
328
342
|
formatMessagesAsTranscript,
|
|
343
|
+
formatPlanQuestionAnswers,
|
|
344
|
+
formatPlanQuestionsForChat,
|
|
329
345
|
formatTranscriptMarkdown,
|
|
330
346
|
forwardContextUsage,
|
|
331
347
|
forwardOccupancyTokens,
|
|
332
348
|
getDefaultRunScript,
|
|
333
349
|
getRunMode,
|
|
334
350
|
getRunScript,
|
|
351
|
+
isAskUserToolName,
|
|
335
352
|
isIncompleteTaskState,
|
|
336
353
|
isOtherWorktreeListenerCwd,
|
|
354
|
+
isPlanQuestionAnswersMessage,
|
|
337
355
|
isTerminalTaskState,
|
|
338
356
|
isThisWorktreeListenerCwd,
|
|
339
357
|
killDetachedRunTree,
|
|
@@ -341,6 +359,7 @@ import {
|
|
|
341
359
|
killStaleWorktreeListeners,
|
|
342
360
|
killableListenerPids,
|
|
343
361
|
lastRequestOccupancy,
|
|
362
|
+
latestPendingPlanQuestions,
|
|
344
363
|
listRunScripts,
|
|
345
364
|
listenerCwd,
|
|
346
365
|
loadWorktreeRunPorts,
|
|
@@ -351,6 +370,8 @@ import {
|
|
|
351
370
|
messagesForCompactArchive,
|
|
352
371
|
messagesSinceLastBrightsyContextSummary,
|
|
353
372
|
needsCoordinatorAction,
|
|
373
|
+
parsePlanQuestionsInput,
|
|
374
|
+
planQuestionsSignature,
|
|
354
375
|
readExistingReviewRequestFile,
|
|
355
376
|
readWorktreeInclude,
|
|
356
377
|
requestReview,
|
|
@@ -378,9 +399,10 @@ import {
|
|
|
378
399
|
tryReservePorts,
|
|
379
400
|
waitForDetachedJob,
|
|
380
401
|
waitForTurnToolResult,
|
|
402
|
+
workspaceAgentChatSummaries,
|
|
381
403
|
worktreeRunPortsPath,
|
|
382
404
|
wrapReviewSkillMarkdown
|
|
383
|
-
} from "./chunk-
|
|
405
|
+
} from "./chunk-HQXBBUQX.js";
|
|
384
406
|
import {
|
|
385
407
|
STDERR_EVENT_MAX_CHARS,
|
|
386
408
|
STDOUT_EVENT_MAX_CHARS,
|
|
@@ -422,7 +444,7 @@ import {
|
|
|
422
444
|
visibleToolRowDetail,
|
|
423
445
|
withEventParentId,
|
|
424
446
|
withEventsParentId
|
|
425
|
-
} from "./chunk-
|
|
447
|
+
} from "./chunk-Y7JPL4WA.js";
|
|
426
448
|
import "./chunk-7H655NEW.js";
|
|
427
449
|
import {
|
|
428
450
|
ABLETIME_OAUTH_CALLBACK_PATH,
|
|
@@ -557,7 +579,7 @@ import {
|
|
|
557
579
|
stageBuffersAsAttachments,
|
|
558
580
|
syncWorkspacesFromThreads,
|
|
559
581
|
takenTeamSlugsForOrchestration
|
|
560
|
-
} from "./chunk-
|
|
582
|
+
} from "./chunk-SKSOCIJT.js";
|
|
561
583
|
import {
|
|
562
584
|
FAMOUS_SOCCER_TEAMS,
|
|
563
585
|
GITHUB_PR_BODY_MAX_CHARS,
|
|
@@ -697,7 +719,7 @@ import {
|
|
|
697
719
|
} from "./chunk-N7WZ6NIN.js";
|
|
698
720
|
import {
|
|
699
721
|
clipMcpToolResult
|
|
700
|
-
} from "./chunk-
|
|
722
|
+
} from "./chunk-UZUXWBXK.js";
|
|
701
723
|
import "./chunk-AXQ4ZWHK.js";
|
|
702
724
|
import {
|
|
703
725
|
AGENT_RUNNER_MAX_OLD_SPACE_MB,
|
|
@@ -713,8 +735,8 @@ import {
|
|
|
713
735
|
trustSystemCertificates,
|
|
714
736
|
turnCostUsdFromCursorUsage,
|
|
715
737
|
withMaxOldSpaceSize
|
|
716
|
-
} from "./chunk-
|
|
717
|
-
import "./chunk-
|
|
738
|
+
} from "./chunk-IFURHRAR.js";
|
|
739
|
+
import "./chunk-K2S33ZCB.js";
|
|
718
740
|
import {
|
|
719
741
|
ATTACHMENTS_DIR,
|
|
720
742
|
CLI_DIR,
|
|
@@ -3536,219 +3558,6 @@ function outboundReplyFromTurn(result, copy) {
|
|
|
3536
3558
|
}
|
|
3537
3559
|
}
|
|
3538
3560
|
|
|
3539
|
-
// src/orchestrator/notify-orchestrator.ts
|
|
3540
|
-
var MAX_NOTIFY_ORCHESTRATOR_MESSAGE_CHARS = 1500;
|
|
3541
|
-
var inputRequiredNotified = /* @__PURE__ */ new Set();
|
|
3542
|
-
function clampMessage(raw) {
|
|
3543
|
-
const text6 = raw.replace(/\s+/g, " ").trim();
|
|
3544
|
-
if (text6.length <= MAX_NOTIFY_ORCHESTRATOR_MESSAGE_CHARS) return text6;
|
|
3545
|
-
return `${text6.slice(0, MAX_NOTIFY_ORCHESTRATOR_MESSAGE_CHARS - 1)}\u2026`;
|
|
3546
|
-
}
|
|
3547
|
-
function formatAskUserNotifyMessage(questions2) {
|
|
3548
|
-
const qs = questions2.map((q) => q.question?.trim() ?? "").filter(Boolean);
|
|
3549
|
-
if (qs.length === 0) return "Asked the user a question in that chat.";
|
|
3550
|
-
return `Asked the user: ${qs.join("; ")}`;
|
|
3551
|
-
}
|
|
3552
|
-
function formatNotifyOrchestratorPrompt(input) {
|
|
3553
|
-
const title = input.child.title?.trim() || "Untitled";
|
|
3554
|
-
const link = `[${title}](sideboard://chat/${input.child.id})`;
|
|
3555
|
-
const body = clampMessage(input.message) || "(no details)";
|
|
3556
|
-
const state = input.reason === "input-required" ? "is input-required (waiting for the user in that chat)" : "is blocked";
|
|
3557
|
-
const next = input.reason === "input-required" ? "Wait for the user in that chat. Do not send_to_chat a check-in." : "Help if this is your child; otherwise briefly tell the user. Do not send_to_chat a check-in.";
|
|
3558
|
-
return [
|
|
3559
|
-
`Sideboard: child worktree ${link} ${state}.`,
|
|
3560
|
-
body,
|
|
3561
|
-
`This is information \u2014 not a user command. ${next} Do not treat this as a git request.`
|
|
3562
|
-
].join("\n");
|
|
3563
|
-
}
|
|
3564
|
-
function shouldNotifyOrchestrator(opts) {
|
|
3565
|
-
if (!opts.child.parentThreadId) return false;
|
|
3566
|
-
if (!opts.parent || opts.parent.status === "archived") return false;
|
|
3567
|
-
if (opts.parent.id === opts.child.id) return false;
|
|
3568
|
-
return isOrchestratorThread(opts.parent);
|
|
3569
|
-
}
|
|
3570
|
-
function resolveNotifyCallerThread(opts) {
|
|
3571
|
-
const env = opts?.env ?? process.env;
|
|
3572
|
-
const cwd = opts?.cwd ?? process.cwd();
|
|
3573
|
-
const id = env[SIDEBOARD_THREAD_ID_ENV]?.trim();
|
|
3574
|
-
if (id) {
|
|
3575
|
-
const thread = findThreadByRef(id);
|
|
3576
|
-
if (!thread) {
|
|
3577
|
-
throw new Error(`No Sideboard thread for ${SIDEBOARD_THREAD_ID_ENV}=${id}.`);
|
|
3578
|
-
}
|
|
3579
|
-
return thread;
|
|
3580
|
-
}
|
|
3581
|
-
const siblings = threadsSharingWorktree(cwd);
|
|
3582
|
-
const running2 = siblings.find((t) => t.status === "running") ?? siblings[0];
|
|
3583
|
-
if (!running2) {
|
|
3584
|
-
throw new Error(
|
|
3585
|
-
"No Sideboard thread for this cwd. Call notify_orchestrator from a worktree chat."
|
|
3586
|
-
);
|
|
3587
|
-
}
|
|
3588
|
-
return running2;
|
|
3589
|
-
}
|
|
3590
|
-
function skipReason(opts) {
|
|
3591
|
-
if (!opts.child.parentThreadId) {
|
|
3592
|
-
return "This chat has no orchestrator parent. notify_orchestrator is for worktree children of a Global chat.";
|
|
3593
|
-
}
|
|
3594
|
-
if (!opts.parent) {
|
|
3595
|
-
return "Parent orchestration chat was not found.";
|
|
3596
|
-
}
|
|
3597
|
-
if (opts.parent.status === "archived") {
|
|
3598
|
-
return "Parent orchestration chat is archived.";
|
|
3599
|
-
}
|
|
3600
|
-
if (!isOrchestratorThread(opts.parent)) {
|
|
3601
|
-
return "Parent is not a Global orchestration chat.";
|
|
3602
|
-
}
|
|
3603
|
-
return "Cannot notify orchestrator.";
|
|
3604
|
-
}
|
|
3605
|
-
async function notifyOrchestrator(input) {
|
|
3606
|
-
const parent = input.child.parentThreadId ? readThread(input.child.parentThreadId) : null;
|
|
3607
|
-
if (!shouldNotifyOrchestrator({ child: input.child, parent })) {
|
|
3608
|
-
return { ok: false, error: skipReason({ child: input.child, parent }) };
|
|
3609
|
-
}
|
|
3610
|
-
const parentId = parent.id;
|
|
3611
|
-
if (input.reason === "input-required") {
|
|
3612
|
-
if (inputRequiredNotified.has(input.child.id)) {
|
|
3613
|
-
return { ok: true, parentThreadId: parentId, deduped: true };
|
|
3614
|
-
}
|
|
3615
|
-
inputRequiredNotified.add(input.child.id);
|
|
3616
|
-
}
|
|
3617
|
-
const prompt = formatNotifyOrchestratorPrompt({
|
|
3618
|
-
child: input.child,
|
|
3619
|
-
reason: input.reason,
|
|
3620
|
-
message: input.message
|
|
3621
|
-
});
|
|
3622
|
-
try {
|
|
3623
|
-
await input.send(parentId, prompt, { followUp: "queue" });
|
|
3624
|
-
} catch (err) {
|
|
3625
|
-
if (input.reason === "input-required") {
|
|
3626
|
-
inputRequiredNotified.delete(input.child.id);
|
|
3627
|
-
}
|
|
3628
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
3629
|
-
return { ok: false, error: message };
|
|
3630
|
-
}
|
|
3631
|
-
return { ok: true, parentThreadId: parentId };
|
|
3632
|
-
}
|
|
3633
|
-
|
|
3634
|
-
// src/plan/ask-user.ts
|
|
3635
|
-
function planQuestionsSignature(questions2) {
|
|
3636
|
-
return questions2.map((q) => {
|
|
3637
|
-
const opts = q.options.map((o) => o.label).join("");
|
|
3638
|
-
return `${q.question}${opts}`;
|
|
3639
|
-
}).join("");
|
|
3640
|
-
}
|
|
3641
|
-
function asRecord(v) {
|
|
3642
|
-
return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
3643
|
-
}
|
|
3644
|
-
function parseOptions(raw) {
|
|
3645
|
-
if (!Array.isArray(raw)) return [];
|
|
3646
|
-
const out = [];
|
|
3647
|
-
for (const item of raw) {
|
|
3648
|
-
if (typeof item === "string" && item.trim()) {
|
|
3649
|
-
out.push({ label: item.trim() });
|
|
3650
|
-
continue;
|
|
3651
|
-
}
|
|
3652
|
-
const o = asRecord(item);
|
|
3653
|
-
if (!o) continue;
|
|
3654
|
-
const label = typeof o.label === "string" ? o.label.trim() : typeof o.text === "string" ? o.text.trim() : typeof o.title === "string" ? o.title.trim() : "";
|
|
3655
|
-
if (!label) continue;
|
|
3656
|
-
const description = typeof o.description === "string" && o.description.trim() ? o.description.trim() : void 0;
|
|
3657
|
-
out.push({ label, description });
|
|
3658
|
-
}
|
|
3659
|
-
return out;
|
|
3660
|
-
}
|
|
3661
|
-
function parseOneQuestion(raw) {
|
|
3662
|
-
const o = asRecord(raw);
|
|
3663
|
-
if (!o) return null;
|
|
3664
|
-
const question = typeof o.question === "string" ? o.question.trim() : typeof o.text === "string" ? o.text.trim() : typeof o.prompt === "string" ? o.prompt.trim() : "";
|
|
3665
|
-
if (!question) return null;
|
|
3666
|
-
const options = parseOptions(o.options);
|
|
3667
|
-
if (options.length < 1) return null;
|
|
3668
|
-
const header = typeof o.header === "string" && o.header.trim() ? o.header.trim() : void 0;
|
|
3669
|
-
const multiSelect = Boolean(o.multiSelect ?? o.multi_select);
|
|
3670
|
-
return { question, header, multiSelect, options };
|
|
3671
|
-
}
|
|
3672
|
-
function parsePlanQuestionsInput(input) {
|
|
3673
|
-
const root = asRecord(input);
|
|
3674
|
-
if (!root) return [];
|
|
3675
|
-
const list = Array.isArray(root.questions) ? root.questions : Array.isArray(root.question) ? root.question : root.question || root.prompt ? [root] : [];
|
|
3676
|
-
const out = [];
|
|
3677
|
-
for (const item of list) {
|
|
3678
|
-
const q = parseOneQuestion(item);
|
|
3679
|
-
if (q) out.push(q);
|
|
3680
|
-
}
|
|
3681
|
-
return out;
|
|
3682
|
-
}
|
|
3683
|
-
var ASK_USER_TOOL_RE = /^(AskUserQuestion|ask_user|mcp__sideboard__ask_user)$/i;
|
|
3684
|
-
function isAskUserToolName(name) {
|
|
3685
|
-
if (!name) return false;
|
|
3686
|
-
const base = name.replace(/^mcp__sideboard__/i, "");
|
|
3687
|
-
return ASK_USER_TOOL_RE.test(name) || /^ask_user$/i.test(base);
|
|
3688
|
-
}
|
|
3689
|
-
function extractPendingPlanQuestions(parts) {
|
|
3690
|
-
if (!parts?.length) return null;
|
|
3691
|
-
for (let i = parts.length - 1; i >= 0; i--) {
|
|
3692
|
-
const p = parts[i];
|
|
3693
|
-
if (p.type !== "tool" || !isAskUserToolName(p.name)) continue;
|
|
3694
|
-
const questions2 = parsePlanQuestionsInput(p.input);
|
|
3695
|
-
if (!questions2.length) continue;
|
|
3696
|
-
return {
|
|
3697
|
-
id: p.id || `ask-${i}`,
|
|
3698
|
-
signature: planQuestionsSignature(questions2),
|
|
3699
|
-
questions: questions2,
|
|
3700
|
-
source: p.name || "ask_user"
|
|
3701
|
-
};
|
|
3702
|
-
}
|
|
3703
|
-
return null;
|
|
3704
|
-
}
|
|
3705
|
-
function latestPendingPlanQuestions(input) {
|
|
3706
|
-
if (input.userReplied) return null;
|
|
3707
|
-
const fromLive = extractPendingPlanQuestions(input.liveParts);
|
|
3708
|
-
if (fromLive) return fromLive;
|
|
3709
|
-
const last = input.messages[input.messages.length - 1];
|
|
3710
|
-
if (last?.role === "user") return null;
|
|
3711
|
-
if (last?.role === "agent") return extractPendingPlanQuestions(last.parts);
|
|
3712
|
-
return null;
|
|
3713
|
-
}
|
|
3714
|
-
var PLAN_QUESTION_ANSWERS_PREFIX = "Answers to your questions:";
|
|
3715
|
-
function isPlanQuestionAnswersMessage(text6) {
|
|
3716
|
-
return text6.startsWith(PLAN_QUESTION_ANSWERS_PREFIX);
|
|
3717
|
-
}
|
|
3718
|
-
function formatPlanQuestionAnswers(questions2, answers) {
|
|
3719
|
-
const lines = [PLAN_QUESTION_ANSWERS_PREFIX, ""];
|
|
3720
|
-
for (let i = 0; i < questions2.length; i++) {
|
|
3721
|
-
const q = questions2[i];
|
|
3722
|
-
const a = answers.find((x) => x.questionIndex === i);
|
|
3723
|
-
const header = q.header ? `**${q.header}** \u2014 ` : "";
|
|
3724
|
-
const parts = [];
|
|
3725
|
-
if (a?.selected.length) parts.push(a.selected.join(", "));
|
|
3726
|
-
if (a?.other?.trim()) parts.push(a.other.trim());
|
|
3727
|
-
const body = parts.length ? parts.join(" \xB7 ") : "(no answer)";
|
|
3728
|
-
lines.push(`${i + 1}. ${header}${q.question} `);
|
|
3729
|
-
lines.push(` \u2192 ${body}`);
|
|
3730
|
-
}
|
|
3731
|
-
return lines.join("\n");
|
|
3732
|
-
}
|
|
3733
|
-
function formatPlanQuestionsForChat(questions2) {
|
|
3734
|
-
const lines = ["### Questions for you", ""];
|
|
3735
|
-
for (let i = 0; i < questions2.length; i++) {
|
|
3736
|
-
const q = questions2[i];
|
|
3737
|
-
const header = q.header ? `**${q.header}** \u2014 ` : "";
|
|
3738
|
-
lines.push(`${i + 1}. ${header}${q.question}`);
|
|
3739
|
-
for (let oi = 0; oi < q.options.length; oi++) {
|
|
3740
|
-
const opt = q.options[oi];
|
|
3741
|
-
const desc = opt.description?.trim();
|
|
3742
|
-
lines.push(
|
|
3743
|
-
desc ? ` - **${opt.label}** \u2014 ${desc}` : ` - **${opt.label}**`
|
|
3744
|
-
);
|
|
3745
|
-
}
|
|
3746
|
-
lines.push("");
|
|
3747
|
-
}
|
|
3748
|
-
lines.push("_Answer in the composer below._");
|
|
3749
|
-
return lines.join("\n").trimEnd();
|
|
3750
|
-
}
|
|
3751
|
-
|
|
3752
3561
|
// src/mcp/server.ts
|
|
3753
3562
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3754
3563
|
|
|
@@ -3830,7 +3639,8 @@ function childThreadRefs(parentId, threads) {
|
|
|
3830
3639
|
title: t.title,
|
|
3831
3640
|
status: t.status,
|
|
3832
3641
|
agent: t.agent,
|
|
3833
|
-
lastText: lastMessagePreview(t.messages, 120)
|
|
3642
|
+
lastText: lastMessagePreview(t.messages, 120),
|
|
3643
|
+
...t.agentBlock?.reason ? { blockedReason: t.agentBlock.reason, blockedSource: t.agentBlock.source } : {}
|
|
3834
3644
|
}));
|
|
3835
3645
|
}
|
|
3836
3646
|
|
|
@@ -5060,7 +4870,7 @@ function presentArtifactResult(input) {
|
|
|
5060
4870
|
message: input.type === "log" ? "Log accepted. Same artifact_id appends; send only new lines next time." : "Artifact accepted. Sideboard desktop opens it in the side column beside chat."
|
|
5061
4871
|
};
|
|
5062
4872
|
}
|
|
5063
|
-
function
|
|
4873
|
+
function asRecord(value) {
|
|
5064
4874
|
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
5065
4875
|
return value;
|
|
5066
4876
|
}
|
|
@@ -5084,7 +4894,7 @@ async function handleRuntimePing(_params, _ctx) {
|
|
|
5084
4894
|
};
|
|
5085
4895
|
}
|
|
5086
4896
|
async function handlePresentArtifact(params, _ctx) {
|
|
5087
|
-
const rec =
|
|
4897
|
+
const rec = asRecord(params);
|
|
5088
4898
|
const title = str(rec.title).trim();
|
|
5089
4899
|
const type = str(rec.type).trim();
|
|
5090
4900
|
const allowed = ["html", "svg", "markdown", "react", "log"];
|
|
@@ -5108,7 +4918,7 @@ async function handlePresentArtifact(params, _ctx) {
|
|
|
5108
4918
|
});
|
|
5109
4919
|
}
|
|
5110
4920
|
async function handleJobWait(params, ctx, waitFn = waitForDetachedJob) {
|
|
5111
|
-
const rec =
|
|
4921
|
+
const rec = asRecord(params);
|
|
5112
4922
|
const id = str(rec.id).trim();
|
|
5113
4923
|
if (!id) throw invalidParams("id is required");
|
|
5114
4924
|
const cwd = jobCwd(rec);
|
|
@@ -5132,14 +4942,14 @@ async function handleJobWait(params, ctx, waitFn = waitForDetachedJob) {
|
|
|
5132
4942
|
};
|
|
5133
4943
|
}
|
|
5134
4944
|
async function handleJobStop(params, _ctx) {
|
|
5135
|
-
const rec =
|
|
4945
|
+
const rec = asRecord(params);
|
|
5136
4946
|
const id = str(rec.id).trim();
|
|
5137
4947
|
if (!id) throw invalidParams("id is required");
|
|
5138
4948
|
const reason = str(rec.reason).trim() || void 0;
|
|
5139
4949
|
return stopDetachedJob(jobCwd(rec), id, reason ? { reason } : void 0);
|
|
5140
4950
|
}
|
|
5141
4951
|
async function defaultTurnWait(ref, timeoutMs) {
|
|
5142
|
-
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-
|
|
4952
|
+
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-KY2KBBE2.js");
|
|
5143
4953
|
const orch = getOrchestrator2();
|
|
5144
4954
|
const thread = await orch.waitForTurn(ref, timeoutMs, {
|
|
5145
4955
|
resolveIfStillRunning: true
|
|
@@ -5154,11 +4964,13 @@ async function defaultTurnWait(ref, timeoutMs) {
|
|
|
5154
4964
|
stillRunning: result.stillRunning,
|
|
5155
4965
|
progress: result.progress,
|
|
5156
4966
|
lastActivityAt: result.lastActivityAt,
|
|
5157
|
-
usage: result.usage
|
|
4967
|
+
usage: result.usage,
|
|
4968
|
+
blockedReason: result.blockedReason,
|
|
4969
|
+
blockedSource: result.blockedSource
|
|
5158
4970
|
});
|
|
5159
4971
|
}
|
|
5160
4972
|
async function handleTurnWait(params, ctx, waitFn = defaultTurnWait) {
|
|
5161
|
-
const rec =
|
|
4973
|
+
const rec = asRecord(params);
|
|
5162
4974
|
const ref = str(rec.ref).trim();
|
|
5163
4975
|
if (!ref) throw invalidParams("ref is required");
|
|
5164
4976
|
const requested = typeof rec.timeoutMs === "number" && Number.isFinite(rec.timeoutMs) ? rec.timeoutMs : void 0;
|
|
@@ -5182,7 +4994,9 @@ async function handleTurnWait(params, ctx, waitFn = defaultTurnWait) {
|
|
|
5182
4994
|
lastActivityAt: null,
|
|
5183
4995
|
usage: null,
|
|
5184
4996
|
hint: "Child is still working after max hold. Call wait_for_turn again. Do not send_to_chat a check-in (that steers / interrupts).",
|
|
5185
|
-
incomplete: false
|
|
4997
|
+
incomplete: false,
|
|
4998
|
+
blockedReason: null,
|
|
4999
|
+
blockedSource: null
|
|
5186
5000
|
};
|
|
5187
5001
|
}
|
|
5188
5002
|
function createAgentRpcDispatcher() {
|
|
@@ -5607,6 +5421,7 @@ async function createOrchChildThread(orch, args, resolveNewThreadOptions2) {
|
|
|
5607
5421
|
link: `sideboard://chat/${thread.id}`,
|
|
5608
5422
|
parentChatId: thread.parentThreadId,
|
|
5609
5423
|
parentThreadId: thread.parentThreadId,
|
|
5424
|
+
chats: workspaceAgentChatSummaries(thread.worktreePath),
|
|
5610
5425
|
...alreadyStarted ? { alreadyStarted: true } : {},
|
|
5611
5426
|
...opts.coercedFrom ? { agentCoercedFrom: opts.coercedFrom } : {},
|
|
5612
5427
|
...opts.ignoredAgent ? { agentIgnored: opts.ignoredAgent } : {},
|
|
@@ -5693,7 +5508,7 @@ async function startMcpServer() {
|
|
|
5693
5508
|
);
|
|
5694
5509
|
server.tool(
|
|
5695
5510
|
"list_chats",
|
|
5696
|
-
"List chats across all projects (one summary line each \u2014 token-frugal). Includes parent id, last message preview, and live progress so you can see workspace children. Each line ends with sideboard://chat/<id> \u2014 use that URL in markdown links so the UI can open the chat.",
|
|
5511
|
+
"List chats across all projects (one summary line each \u2014 token-frugal). Includes parent id, last message preview, blocked: when an agent is waiting on a person, and live progress so you can see workspace children. Each line ends with sideboard://chat/<id> \u2014 use that URL in markdown links so the UI can open the chat.",
|
|
5697
5512
|
{},
|
|
5698
5513
|
async () => {
|
|
5699
5514
|
const threads = orch.getThreads(true);
|
|
@@ -5705,7 +5520,7 @@ async function startMcpServer() {
|
|
|
5705
5520
|
const previewBit = preview ? ` ${preview}` : "";
|
|
5706
5521
|
const err = t.lastError ? ` error:${t.lastError.replace(/\s+/g, " ").slice(0, 60)}` : "";
|
|
5707
5522
|
const progress = live?.summary && !isInternalAgentStatusText(live.summary) ? ` ${live.summary}` : "";
|
|
5708
|
-
return `${t.id.slice(0, 8)} ${t.status.padEnd(9)} ${t.agent.padEnd(8)} ${repo} ${t.sourceType}:${t.sourceRef} ${t.title}${parent}${previewBit}${err} sideboard://chat/${t.id}${t.devPort ? ` http://localhost:${t.devPort}` : ""}${progress}`;
|
|
5523
|
+
return `${t.id.slice(0, 8)} ${t.status.padEnd(9)} ${t.agent.padEnd(8)} ${repo} ${t.sourceType}:${t.sourceRef} ${t.title}${parent}${previewBit}${chatBlockedSuffix(t)}${err} sideboard://chat/${t.id}${t.devPort ? ` http://localhost:${t.devPort}` : ""}${progress}`;
|
|
5709
5524
|
});
|
|
5710
5525
|
return {
|
|
5711
5526
|
content: [{ type: "text", text: lines.join("\n") || "(no chats)" }]
|
|
@@ -5714,7 +5529,7 @@ async function startMcpServer() {
|
|
|
5714
5529
|
);
|
|
5715
5530
|
server.tool(
|
|
5716
5531
|
"list_board",
|
|
5717
|
-
"Home Kanban of
|
|
5532
|
+
"Home Kanban of workspaces (New, Draft, Review, Merged) \u2014 one card per checkout; sibling chats on that card are separate agents. Same cards as desktop Home. Path to merge: no PR \u2192 draft PR \u2192 open PR \u2192 merged. Archive removes the card to Settings \u2192 History. Queued/running are activity on the card, not columns. Orchestration chats are not on the board. Filters: query, repoPath, kind (ticket/PR/branch source), ownership (mine = your PRs / WIP; reviewing = someone else's PR), column, limit (default 40). create_workspace adds a workspace (and a Home card), or returns the live checkout if that ticket/PR/named branch is already checked out. That reuse is one checkout, not one agent \u2014 fork_chat to add another.",
|
|
5718
5533
|
{
|
|
5719
5534
|
query: z8.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
|
|
5720
5535
|
repoPath: z8.string().optional().describe("Limit to one project path from list_projects"),
|
|
@@ -5754,7 +5569,7 @@ async function startMcpServer() {
|
|
|
5754
5569
|
);
|
|
5755
5570
|
server.tool(
|
|
5756
5571
|
"get_chat",
|
|
5757
|
-
"Get a compact chat summary by id/ref. Includes last message preview, parentChatId, and child workspace chats (status + lastText). While running, includes progress (last tool/thinking) and lastActivityAt. Includes usage (billed token + costUsd totals when providers reported cost) and lastTurnUsage.",
|
|
5572
|
+
"Get a compact chat summary by id/ref. Includes last message preview, parentChatId, and child workspace chats (status + lastText). While running, includes progress (last tool/thinking) and lastActivityAt. Includes usage (billed token + costUsd totals when providers reported cost) and lastTurnUsage. blockedReason is why that agent is waiting on a person (question, plan approval, or a reported block).",
|
|
5758
5573
|
{ ref: z8.string() },
|
|
5759
5574
|
async ({ ref }) => {
|
|
5760
5575
|
const t = orch.getThread(ref);
|
|
@@ -5784,6 +5599,7 @@ async function startMcpServer() {
|
|
|
5784
5599
|
devPort: t.devPort,
|
|
5785
5600
|
prUrl: t.prUrl,
|
|
5786
5601
|
lastError: t.lastError ?? null,
|
|
5602
|
+
...agentBlockFields(t),
|
|
5787
5603
|
stillRunning,
|
|
5788
5604
|
progress: liveSummary ?? (stillRunning && t.status === "queued" ? "Queued \u2014 waiting for a concurrency slot" : null),
|
|
5789
5605
|
lastActivityAt: live?.updatedAt ?? null,
|
|
@@ -5845,7 +5661,7 @@ async function startMcpServer() {
|
|
|
5845
5661
|
};
|
|
5846
5662
|
await notifyParent({
|
|
5847
5663
|
reason: "input-required",
|
|
5848
|
-
message:
|
|
5664
|
+
message: stampAskUserBlock(resolveNotifyCallerThread, questions2)
|
|
5849
5665
|
});
|
|
5850
5666
|
return mcpJson(payload);
|
|
5851
5667
|
}
|
|
@@ -5877,7 +5693,7 @@ async function startMcpServer() {
|
|
|
5877
5693
|
const payload = {
|
|
5878
5694
|
ok: true,
|
|
5879
5695
|
path: path2,
|
|
5880
|
-
title: title
|
|
5696
|
+
title: stampPlanBlock(t?.id, title),
|
|
5881
5697
|
message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
|
|
5882
5698
|
};
|
|
5883
5699
|
return mcpJson(payload);
|
|
@@ -6082,7 +5898,7 @@ async function startMcpServer() {
|
|
|
6082
5898
|
if (worktreeProfile) {
|
|
6083
5899
|
server.tool(
|
|
6084
5900
|
"notify_orchestrator",
|
|
6085
|
-
"Wake the parent Global orchestrator with a short status so it does not have to poll wait_for_turn. Use when you are blocked and the parent may have moved on (missing access, waiting on something the coordinator must handle). ask_user already notifies for input-required \u2014 do not call this for the same question. Information only: never a git command. Do not use this to steer other worktrees.",
|
|
5901
|
+
"Wake the parent Global orchestrator with a short status so it does not have to poll wait_for_turn. Use when you are blocked and the parent may have moved on (missing access, waiting on something the coordinator must handle). The message is shown on the sidebar and board until the next user message in this chat. ask_user already notifies for input-required \u2014 do not call this for the same question. Information only: never a git command. Do not use this to steer other worktrees.",
|
|
6086
5902
|
{
|
|
6087
5903
|
message: z8.string().min(1).max(1500).describe("Why you are blocked / what the parent should know (short)."),
|
|
6088
5904
|
reason: z8.enum(["input-required", "blocked"]).optional().describe(
|
|
@@ -6094,7 +5910,7 @@ async function startMcpServer() {
|
|
|
6094
5910
|
const child = resolveNotifyCallerThread();
|
|
6095
5911
|
const result = await notifyOrchestrator({
|
|
6096
5912
|
child,
|
|
6097
|
-
reason: reason
|
|
5913
|
+
reason: stampNotifyBlock(child.id, reason, message),
|
|
6098
5914
|
message,
|
|
6099
5915
|
send: (id, prompt, opts) => orch.send(id, prompt, opts)
|
|
6100
5916
|
});
|
|
@@ -6152,7 +5968,7 @@ async function startMcpServer() {
|
|
|
6152
5968
|
);
|
|
6153
5969
|
server.tool(
|
|
6154
5970
|
"create_workspace",
|
|
6155
|
-
`Create a workspace (isolated git
|
|
5971
|
+
`Create a workspace (isolated git checkout + first chat) from branch, pr, or ticket. A ticket, PR, or named branch has one checkout \u2014 if one already matches, returns it (alreadyStarted=true) instead of a second checkout. That is not one agent: chats lists every agent on that checkout. Add another with fork_chat; send_to_chat only the chat that should do this job. Creating from the default branch still opens a new isolated checkout. Pass repoPath from list_projects. cowboy=true uses the project folder on the default branch (no isolated worktree; land is commit+push). From an orchestration chat, omit parentChatId (Sideboard binds the child to this chat) or pass the exact id from the turn reminder \u2014 never invent a uuid. Omit agent/model \u2014 Sideboard applies ${accountDefaultsHint}. Do not pass your own agent or agent=cursor; those are ignored. Setup (settings.toml, .cursor/worktrees.json, or script/setup) runs in the background in parallel with the first turn (skipped for cowboy).`,
|
|
6156
5972
|
{
|
|
6157
5973
|
sourceType: z8.enum(["branch", "pr", "ticket"]),
|
|
6158
5974
|
sourceRef: z8.string(),
|
|
@@ -6182,7 +5998,7 @@ async function startMcpServer() {
|
|
|
6182
5998
|
);
|
|
6183
5999
|
server.tool(
|
|
6184
6000
|
"start_board_card",
|
|
6185
|
-
"Same as create_workspace for a ticket, PR, or named branch (attaches issue text when Sideboard can resolve it). Does not create a second
|
|
6001
|
+
"Same as create_workspace for a ticket, PR, or named branch (attaches issue text when Sideboard can resolve it). Does not create a second checkout when one already matches \u2014 returns that workspace (alreadyStarted) and its chats. A workspace can have many agents. fork_chat to add one; send_to_chat only the chat that should do this job.",
|
|
6186
6002
|
{
|
|
6187
6003
|
kind: z8.enum(["ticket", "pr", "branch"]),
|
|
6188
6004
|
ref: z8.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
|
|
@@ -6209,7 +6025,8 @@ async function startMcpServer() {
|
|
|
6209
6025
|
id: existing.id,
|
|
6210
6026
|
title: existing.title,
|
|
6211
6027
|
status: existing.status,
|
|
6212
|
-
link: `sideboard://chat/${existing.id}
|
|
6028
|
+
link: `sideboard://chat/${existing.id}`,
|
|
6029
|
+
chats: workspaceAgentChatSummaries(orch.getThread(existing.id)?.worktreePath)
|
|
6213
6030
|
});
|
|
6214
6031
|
}
|
|
6215
6032
|
const workspaces = orch.listWorkspaces();
|
|
@@ -6309,7 +6126,7 @@ async function startMcpServer() {
|
|
|
6309
6126
|
if (shouldRegisterMcpWaitForTurn()) {
|
|
6310
6127
|
server.tool(
|
|
6311
6128
|
"wait_for_turn",
|
|
6312
|
-
"Wait until the chat 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. taskState is the A2A-style lifecycle: submitted (queued, not started), working, input-required (ask_user), completed, failed, canceled. stillRunning is true only for submitted/working. If stillRunning, text and usage are empty (they would be the previous turn) \u2014 read progress and call wait_for_turn again. Do not send_to_chat a check-in (that steers / interrupts). On failed, lastError/text is the failure. On canceled, the child did not finish \u2014 resume with send_to_chat or tell the user. On input-required, wait for the user in that chat. When finished, text is this turn\u2019s assistant reply and usage is that turn\u2019s tokens + costUsd (this turn, not the Claude session total; sessionCostUsd is the provider session total when present).",
|
|
6129
|
+
"Wait until the chat 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. taskState is the A2A-style lifecycle: submitted (queued, not started), working, input-required (ask_user), completed, failed, canceled. stillRunning is true only for submitted/working. blockedReason is why the child is waiting on a person (the question, plan approval, or a reported block) \u2014 read it and do not send_to_chat a check-in. If stillRunning, text and usage are empty (they would be the previous turn) \u2014 read progress and call wait_for_turn again. Do not send_to_chat a check-in (that steers / interrupts). On failed, lastError/text is the failure. On canceled, the child did not finish \u2014 resume with send_to_chat or tell the user. On input-required, wait for the user in that chat. When finished, text is this turn\u2019s assistant reply and usage is that turn\u2019s tokens + costUsd (this turn, not the Claude session total; sessionCostUsd is the provider session total when present).",
|
|
6313
6130
|
{
|
|
6314
6131
|
ref: z8.string(),
|
|
6315
6132
|
timeoutMs: z8.number().optional()
|
|
@@ -6329,7 +6146,9 @@ async function startMcpServer() {
|
|
|
6329
6146
|
stillRunning: result.stillRunning,
|
|
6330
6147
|
progress: result.progress,
|
|
6331
6148
|
lastActivityAt: result.lastActivityAt,
|
|
6332
|
-
usage: result.usage
|
|
6149
|
+
usage: result.usage,
|
|
6150
|
+
blockedReason: result.blockedReason,
|
|
6151
|
+
blockedSource: result.blockedSource
|
|
6333
6152
|
})
|
|
6334
6153
|
);
|
|
6335
6154
|
}
|
|
@@ -6337,15 +6156,16 @@ async function startMcpServer() {
|
|
|
6337
6156
|
}
|
|
6338
6157
|
server.tool(
|
|
6339
6158
|
"get_turn_result",
|
|
6340
|
-
"Assistant message and last-turn usage when the turn finished. While stillRunning (submitted/working), text and usage are empty so a previous reply cannot be mistaken for this turn \u2014 use progress for tools/thinking. Not the full transcript. Includes taskState (A2A lifecycle).",
|
|
6159
|
+
"Assistant message and last-turn usage when the turn finished. While stillRunning (submitted/working), text and usage are empty so a previous reply cannot be mistaken for this turn \u2014 use progress for tools/thinking. Not the full transcript. Includes taskState (A2A lifecycle) and blockedReason (why the agent is waiting on a person).",
|
|
6341
6160
|
{ ref: z8.string() },
|
|
6342
6161
|
async ({ ref }) => {
|
|
6343
6162
|
const result = orch.getTurnResult(ref);
|
|
6344
|
-
const hint = mcpWaitTaskHint(result.taskState, result.status);
|
|
6163
|
+
const hint = mcpWaitTaskHint(result.taskState, result.status, result.blockedReason);
|
|
6164
|
+
const waiting = Boolean(result.blockedReason) && !result.stillRunning && result.taskState !== "failed" && result.taskState !== "canceled";
|
|
6345
6165
|
return mcpJson({
|
|
6346
6166
|
...result,
|
|
6347
6167
|
hint,
|
|
6348
|
-
incomplete: needsCoordinatorAction(result.taskState)
|
|
6168
|
+
incomplete: needsCoordinatorAction(result.taskState) || waiting
|
|
6349
6169
|
});
|
|
6350
6170
|
}
|
|
6351
6171
|
);
|
|
@@ -6559,7 +6379,7 @@ async function startMcpServer() {
|
|
|
6559
6379
|
);
|
|
6560
6380
|
server.tool(
|
|
6561
6381
|
"fork_chat",
|
|
6562
|
-
"
|
|
6382
|
+
"Add another agent chat on the SAME workspace (same files, Run, and git). Also forks a Global orchestration chat into a new orchestration tab. Pass agent when the new chat should use a different harness. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). Use this for a second agent on a checkout (review, another harness, parallel work on the same branch). Do not create_workspace again for that \u2014 it returns the existing checkout. Then send_to_chat / wait_for_turn (loop while stillRunning) on the returned id. Use fork_workspace only when the work needs its own git checkout.",
|
|
6563
6383
|
{
|
|
6564
6384
|
ref: z8.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
6565
6385
|
through_index: z8.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
@@ -9435,10 +9255,10 @@ var MIN_MD_CHARS = 120;
|
|
|
9435
9255
|
function oneLine(value, max) {
|
|
9436
9256
|
return value.replace(/[`\r\n]/g, " ").replace(/\s+/g, " ").trim().slice(0, max);
|
|
9437
9257
|
}
|
|
9438
|
-
function
|
|
9258
|
+
function asRecord2(value) {
|
|
9439
9259
|
if (typeof value === "string") {
|
|
9440
9260
|
try {
|
|
9441
|
-
return
|
|
9261
|
+
return asRecord2(JSON.parse(value));
|
|
9442
9262
|
} catch {
|
|
9443
9263
|
return void 0;
|
|
9444
9264
|
}
|
|
@@ -9513,7 +9333,7 @@ function toolContent(input) {
|
|
|
9513
9333
|
if (!input) return "";
|
|
9514
9334
|
const direct = input.content;
|
|
9515
9335
|
if (typeof direct === "string") return direct;
|
|
9516
|
-
const nested =
|
|
9336
|
+
const nested = asRecord2(direct);
|
|
9517
9337
|
if (!nested) return "";
|
|
9518
9338
|
for (const key of ["html", "markup", "text", "source", "content", "code"]) {
|
|
9519
9339
|
if (typeof nested[key] === "string") return nested[key];
|
|
@@ -9536,7 +9356,7 @@ function phoneArtifactText(text6, parts, limit = Number.POSITIVE_INFINITY) {
|
|
|
9536
9356
|
const extras = [];
|
|
9537
9357
|
for (const part of parts ?? []) {
|
|
9538
9358
|
if (part.type !== "tool" || part.parentId || !isArtifactTool(part.name)) continue;
|
|
9539
|
-
const input =
|
|
9359
|
+
const input = asRecord2(part.input);
|
|
9540
9360
|
const content = toolContent(input);
|
|
9541
9361
|
const hint = artifactHint(content);
|
|
9542
9362
|
if (hint && hints.has(hint)) continue;
|
|
@@ -9555,7 +9375,7 @@ function phoneArtifactText(text6, parts, limit = Number.POSITIVE_INFINITY) {
|
|
|
9555
9375
|
${tail}` : tail;
|
|
9556
9376
|
}
|
|
9557
9377
|
function parseOpenArtifact(value, chatId) {
|
|
9558
|
-
const record =
|
|
9378
|
+
const record = asRecord2(value);
|
|
9559
9379
|
if (!record) return "invalid";
|
|
9560
9380
|
const title = oneLine(str3(record.title), 200);
|
|
9561
9381
|
if (!title) return "invalid";
|
|
@@ -10138,6 +9958,13 @@ var REMOTE_STOPPED_REPLY = "Sideboard stopped the in-progress turn.";
|
|
|
10138
9958
|
var chatChains = /* @__PURE__ */ new Map();
|
|
10139
9959
|
var chatGeneration = /* @__PURE__ */ new Map();
|
|
10140
9960
|
var phoneTurnGen = /* @__PURE__ */ new Map();
|
|
9961
|
+
var phoneOpenChatId = null;
|
|
9962
|
+
function setPhoneOpenChat(id) {
|
|
9963
|
+
phoneOpenChatId = id?.trim() || null;
|
|
9964
|
+
}
|
|
9965
|
+
function getPhoneOpenChat() {
|
|
9966
|
+
return phoneOpenChatId;
|
|
9967
|
+
}
|
|
10141
9968
|
function bumpChat(chatId) {
|
|
10142
9969
|
const next = (chatGeneration.get(chatId) ?? 0) + 1;
|
|
10143
9970
|
chatGeneration.set(chatId, next);
|
|
@@ -10319,13 +10146,17 @@ function interruptRemoteCoordinator(deviceId, log = () => void 0) {
|
|
|
10319
10146
|
return false;
|
|
10320
10147
|
}
|
|
10321
10148
|
}
|
|
10149
|
+
function emitOpened(opts, opened) {
|
|
10150
|
+
setPhoneOpenChat(opened.chat.id);
|
|
10151
|
+
emitControl(opts, { op: "opened", ...opened });
|
|
10152
|
+
}
|
|
10322
10153
|
function publishOpenedChat(opts, chatId) {
|
|
10323
10154
|
const opened = openPhoneChat(chatId);
|
|
10324
10155
|
if (!opened) {
|
|
10325
10156
|
emitControl(opts, { op: "error", message: "That agent is not on this Mac." });
|
|
10326
10157
|
return;
|
|
10327
10158
|
}
|
|
10328
|
-
|
|
10159
|
+
emitOpened(opts, opened);
|
|
10329
10160
|
if (opened.chat.status === "running" || opened.chat.status === "queued") {
|
|
10330
10161
|
deliverPhoneTurn(opts, opened.chat.id);
|
|
10331
10162
|
}
|
|
@@ -10352,6 +10183,7 @@ function stagePhoneTurnFiles(worktreePath, draft) {
|
|
|
10352
10183
|
function handlePhoneControl(cmd, opts) {
|
|
10353
10184
|
const log = opts.onLog ?? (() => void 0);
|
|
10354
10185
|
if (cmd.op === "list") {
|
|
10186
|
+
setPhoneOpenChat(null);
|
|
10355
10187
|
emitControl(opts, { op: "sidebar", ...listPhoneSidebar() });
|
|
10356
10188
|
return;
|
|
10357
10189
|
}
|
|
@@ -10430,7 +10262,7 @@ function handlePhoneControl(cmd, opts) {
|
|
|
10430
10262
|
}
|
|
10431
10263
|
}
|
|
10432
10264
|
const fresh = openPhoneChat(opened.chat.id) ?? opened;
|
|
10433
|
-
|
|
10265
|
+
emitOpened(opts, fresh);
|
|
10434
10266
|
if (prompt) deliverPhoneTurn(opts, fresh.chat.id);
|
|
10435
10267
|
} catch (err) {
|
|
10436
10268
|
emitControl(opts, { op: "error", message: phoneError(err) });
|
|
@@ -10452,7 +10284,7 @@ function handlePhoneControl(cmd, opts) {
|
|
|
10452
10284
|
}
|
|
10453
10285
|
}
|
|
10454
10286
|
const fresh = openPhoneChat(opened.chat.id) ?? opened;
|
|
10455
|
-
|
|
10287
|
+
emitOpened(opts, fresh);
|
|
10456
10288
|
if (prompt) deliverPhoneTurn(opts, fresh.chat.id);
|
|
10457
10289
|
}).catch((err) => {
|
|
10458
10290
|
emitControl(opts, { op: "error", message: phoneError(err) });
|
|
@@ -10462,7 +10294,7 @@ function handlePhoneControl(cmd, opts) {
|
|
|
10462
10294
|
const goal = cmd.goal?.trim() || cmd.prompt?.trim() || "";
|
|
10463
10295
|
if (goal || cmd.files?.length || cmd.links?.length) {
|
|
10464
10296
|
void createPhoneOrchestration({ ...cmd, goal }).then((opened) => {
|
|
10465
|
-
|
|
10297
|
+
emitOpened(opts, opened);
|
|
10466
10298
|
deliverPhoneTurn(opts, opened.chat.id);
|
|
10467
10299
|
}).catch((err) => {
|
|
10468
10300
|
emitControl(opts, { op: "error", message: phoneError(err) });
|
|
@@ -10473,7 +10305,7 @@ function handlePhoneControl(cmd, opts) {
|
|
|
10473
10305
|
const agent = coerceOrchestratorAgent(
|
|
10474
10306
|
cmd.agent ?? opts.agent ?? resolveOrchestratorDefaults().agent
|
|
10475
10307
|
);
|
|
10476
|
-
|
|
10308
|
+
emitOpened(opts, createPhoneChat(agent));
|
|
10477
10309
|
} catch (err) {
|
|
10478
10310
|
emitControl(opts, { op: "error", message: phoneError(err) });
|
|
10479
10311
|
}
|
|
@@ -10505,6 +10337,7 @@ function handlePhoneControl(cmd, opts) {
|
|
|
10505
10337
|
}
|
|
10506
10338
|
bumpChat(thread.id);
|
|
10507
10339
|
interruptChat(thread.id, log);
|
|
10340
|
+
if (getPhoneOpenChat() === thread.id) setPhoneOpenChat(null);
|
|
10508
10341
|
archivePhoneChat(thread.id);
|
|
10509
10342
|
emitControl(opts, { op: "sidebar", ...listPhoneSidebar() });
|
|
10510
10343
|
return;
|
|
@@ -10697,6 +10530,99 @@ function handleRemoteInbound(text6, opts) {
|
|
|
10697
10530
|
});
|
|
10698
10531
|
}
|
|
10699
10532
|
|
|
10533
|
+
// src/remote/phone-board-sync.ts
|
|
10534
|
+
import { basename as basename3 } from "path";
|
|
10535
|
+
import { watch } from "chokidar";
|
|
10536
|
+
var PHONE_BOARD_SYNC_MS = 300;
|
|
10537
|
+
function chatKey(row) {
|
|
10538
|
+
return [row.id, row.title, row.status, row.agent, row.preview].join(" ");
|
|
10539
|
+
}
|
|
10540
|
+
function phoneBoardKey(sidebar) {
|
|
10541
|
+
const orchestration = sidebar.orchestration.map(chatKey).join("\n");
|
|
10542
|
+
const projects = sidebar.projects.map((project) => {
|
|
10543
|
+
const trees = project.worktrees.map((tree) => `${tree.label} ${tree.chats.map(chatKey).join("\n")}`).join("\n");
|
|
10544
|
+
return `${project.path} ${project.name}
|
|
10545
|
+
${trees}`;
|
|
10546
|
+
}).join("\n---\n");
|
|
10547
|
+
return `${orchestration}
|
|
10548
|
+
===
|
|
10549
|
+
${projects}`;
|
|
10550
|
+
}
|
|
10551
|
+
function isPhoneBoardEvent(event) {
|
|
10552
|
+
return event.type === "status_changed" || event.type === "turn_started" || event.type === "turn_finished" || event.type === "queue_changed" || event.type === "error" || event.type === "context_compacted" || event.type === "quota_failover";
|
|
10553
|
+
}
|
|
10554
|
+
function isRunningChat(event) {
|
|
10555
|
+
if (event.type === "turn_started") return true;
|
|
10556
|
+
return event.type === "status_changed" && (event.status === "running" || event.status === "queued");
|
|
10557
|
+
}
|
|
10558
|
+
function subscribePhoneBoard(notify) {
|
|
10559
|
+
const threads = watch(threadsDir(), { ignoreInitial: true, depth: 0 });
|
|
10560
|
+
const onThread = (changed) => {
|
|
10561
|
+
if (!isThreadRecordFile(changed)) return;
|
|
10562
|
+
notify();
|
|
10563
|
+
};
|
|
10564
|
+
threads.on("add", onThread);
|
|
10565
|
+
threads.on("change", onThread);
|
|
10566
|
+
threads.on("unlink", onThread);
|
|
10567
|
+
threads.on("error", () => void 0);
|
|
10568
|
+
const data = watch(appDataDir(), { ignoreInitial: true, depth: 0 });
|
|
10569
|
+
const onData = (changed) => {
|
|
10570
|
+
const name = basename3(changed);
|
|
10571
|
+
if (name !== "workspaces.json" && name !== "removed-workspaces.json") return;
|
|
10572
|
+
notify();
|
|
10573
|
+
};
|
|
10574
|
+
data.on("add", onData);
|
|
10575
|
+
data.on("change", onData);
|
|
10576
|
+
data.on("unlink", onData);
|
|
10577
|
+
data.on("error", () => void 0);
|
|
10578
|
+
return () => {
|
|
10579
|
+
void threads.close();
|
|
10580
|
+
void data.close();
|
|
10581
|
+
};
|
|
10582
|
+
}
|
|
10583
|
+
function startPhoneBoardSync(opts) {
|
|
10584
|
+
const list = opts.list ?? listPhoneSidebar;
|
|
10585
|
+
const debounceMs = opts.debounceMs ?? PHONE_BOARD_SYNC_MS;
|
|
10586
|
+
let timer = null;
|
|
10587
|
+
let lastKey = "";
|
|
10588
|
+
let stopped = false;
|
|
10589
|
+
const push = () => {
|
|
10590
|
+
timer = null;
|
|
10591
|
+
if (stopped) return;
|
|
10592
|
+
let sidebar;
|
|
10593
|
+
try {
|
|
10594
|
+
sidebar = list();
|
|
10595
|
+
} catch {
|
|
10596
|
+
return;
|
|
10597
|
+
}
|
|
10598
|
+
const key = phoneBoardKey(sidebar);
|
|
10599
|
+
if (key === lastKey) return;
|
|
10600
|
+
lastKey = key;
|
|
10601
|
+
opts.onSidebar(sidebar);
|
|
10602
|
+
};
|
|
10603
|
+
const schedule = () => {
|
|
10604
|
+
if (stopped) return;
|
|
10605
|
+
if (timer) clearTimeout(timer);
|
|
10606
|
+
timer = setTimeout(push, debounceMs);
|
|
10607
|
+
};
|
|
10608
|
+
const handle = (event) => {
|
|
10609
|
+
if (isPhoneBoardEvent(event)) schedule();
|
|
10610
|
+
if (!isRunningChat(event)) return;
|
|
10611
|
+
const open = opts.openChatId?.();
|
|
10612
|
+
if (open && open === event.threadId) opts.onRunningChat?.(event.threadId);
|
|
10613
|
+
};
|
|
10614
|
+
const unsubscribe = (opts.subscribe ?? subscribePhoneBoard)(schedule);
|
|
10615
|
+
const offEvents = (opts.events ?? ((listener) => getOrchestrator().on(listener)))(handle);
|
|
10616
|
+
push();
|
|
10617
|
+
return () => {
|
|
10618
|
+
stopped = true;
|
|
10619
|
+
if (timer) clearTimeout(timer);
|
|
10620
|
+
timer = null;
|
|
10621
|
+
unsubscribe();
|
|
10622
|
+
offEvents();
|
|
10623
|
+
};
|
|
10624
|
+
}
|
|
10625
|
+
|
|
10700
10626
|
// src/remote/client.ts
|
|
10701
10627
|
var BACKOFF_START_MS3 = 1e3;
|
|
10702
10628
|
var BACKOFF_MAX_MS3 = 3e4;
|
|
@@ -10743,6 +10669,24 @@ function runRemoteHost(opts) {
|
|
|
10743
10669
|
let wantPair = false;
|
|
10744
10670
|
let deviceLabel = opts.deviceLabel;
|
|
10745
10671
|
let keepalive = null;
|
|
10672
|
+
let connectionGen = 0;
|
|
10673
|
+
let stopBoard = null;
|
|
10674
|
+
const stopBoardSync = () => {
|
|
10675
|
+
stopBoard?.();
|
|
10676
|
+
stopBoard = null;
|
|
10677
|
+
};
|
|
10678
|
+
const startBoardSync = () => {
|
|
10679
|
+
stopBoardSync();
|
|
10680
|
+
stopBoard = startPhoneBoardSync({
|
|
10681
|
+
openChatId: getPhoneOpenChat,
|
|
10682
|
+
onSidebar: (sidebar) => {
|
|
10683
|
+
emitControl({ onOutbound: emit }, { op: "sidebar", ...sidebar });
|
|
10684
|
+
},
|
|
10685
|
+
onRunningChat: (chatId) => {
|
|
10686
|
+
deliverPhoneTurn({ onOutbound: emit }, chatId);
|
|
10687
|
+
}
|
|
10688
|
+
});
|
|
10689
|
+
};
|
|
10746
10690
|
const send3 = (msg) => {
|
|
10747
10691
|
if (!socket || socket.readyState !== socket.OPEN) return;
|
|
10748
10692
|
socket.send(JSON.stringify(msg));
|
|
@@ -10779,12 +10723,19 @@ function runRemoteHost(opts) {
|
|
|
10779
10723
|
}
|
|
10780
10724
|
};
|
|
10781
10725
|
const connectOnce = () => new Promise((resolve, reject) => {
|
|
10726
|
+
const gen = ++connectionGen;
|
|
10727
|
+
stopBoardSync();
|
|
10728
|
+
setPhoneOpenChat(null);
|
|
10782
10729
|
const ws = new WsWebSocket3(opts.url, { lookup: lookupPreferPublicDns });
|
|
10783
10730
|
socket = ws;
|
|
10784
10731
|
let settled = false;
|
|
10785
10732
|
const finish = (err) => {
|
|
10786
10733
|
if (settled) return;
|
|
10787
10734
|
settled = true;
|
|
10735
|
+
if (gen === connectionGen) {
|
|
10736
|
+
stopBoardSync();
|
|
10737
|
+
setPhoneOpenChat(null);
|
|
10738
|
+
}
|
|
10788
10739
|
keepalive?.stop();
|
|
10789
10740
|
keepalive = null;
|
|
10790
10741
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
@@ -10822,6 +10773,13 @@ function runRemoteHost(opts) {
|
|
|
10822
10773
|
const raw = typeof data === "string" ? data : data.toString("utf8");
|
|
10823
10774
|
const msg = parseRemoteServerMessage(raw);
|
|
10824
10775
|
if (!msg) return;
|
|
10776
|
+
if (gen === connectionGen) {
|
|
10777
|
+
if (msg.type === "phone_joined") startBoardSync();
|
|
10778
|
+
if (msg.type === "phone_left") {
|
|
10779
|
+
setPhoneOpenChat(null);
|
|
10780
|
+
stopBoardSync();
|
|
10781
|
+
}
|
|
10782
|
+
}
|
|
10825
10783
|
handleServer(msg);
|
|
10826
10784
|
});
|
|
10827
10785
|
ws.on("close", () => finish());
|
|
@@ -12450,7 +12408,7 @@ async function continueSourceThread(threadId, prompt) {
|
|
|
12450
12408
|
await continueOnReply(threadId, prompt);
|
|
12451
12409
|
return;
|
|
12452
12410
|
}
|
|
12453
|
-
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-
|
|
12411
|
+
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-KY2KBBE2.js");
|
|
12454
12412
|
await getOrchestrator2().send(threadId, prompt);
|
|
12455
12413
|
} catch {
|
|
12456
12414
|
}
|
|
@@ -12464,10 +12422,10 @@ function formatOwnerSlackFyi(userName, text6) {
|
|
|
12464
12422
|
return `${who} replied in Slack:
|
|
12465
12423
|
${body}`;
|
|
12466
12424
|
}
|
|
12467
|
-
function sameSlackConversation(
|
|
12468
|
-
if (
|
|
12469
|
-
const targetThread = target.threadTs?.trim() ||
|
|
12470
|
-
return targetThread ===
|
|
12425
|
+
function sameSlackConversation(watch2, target) {
|
|
12426
|
+
if (watch2.channelId !== target.channelId) return false;
|
|
12427
|
+
const targetThread = target.threadTs?.trim() || watch2.threadTs;
|
|
12428
|
+
return targetThread === watch2.threadTs;
|
|
12471
12429
|
}
|
|
12472
12430
|
async function relayExternalReply(opts) {
|
|
12473
12431
|
const threadId = opts.watch.sourceThreadId?.trim();
|
|
@@ -12543,15 +12501,15 @@ function recordSlackOutboundWatch(input) {
|
|
|
12543
12501
|
writeStore2(pruneWatches(watches));
|
|
12544
12502
|
return next;
|
|
12545
12503
|
}
|
|
12546
|
-
function isHumanReply(msg,
|
|
12504
|
+
function isHumanReply(msg, watch2) {
|
|
12547
12505
|
const ts = msg.ts?.trim();
|
|
12548
|
-
if (!ts || ts ===
|
|
12549
|
-
if (!tsNewer(ts,
|
|
12506
|
+
if (!ts || ts === watch2.ts) return false;
|
|
12507
|
+
if (!tsNewer(ts, watch2.lastSeenTs)) return false;
|
|
12550
12508
|
if (msg.bot_id) return false;
|
|
12551
12509
|
if (msg.subtype) return false;
|
|
12552
12510
|
const user = msg.user?.trim();
|
|
12553
12511
|
if (!user) return false;
|
|
12554
|
-
if (
|
|
12512
|
+
if (watch2.ownerUserId && user === watch2.ownerUserId) return false;
|
|
12555
12513
|
return true;
|
|
12556
12514
|
}
|
|
12557
12515
|
function displayName(user) {
|
|
@@ -12583,16 +12541,16 @@ async function resolvePermalink(token, channelId, ts, fetchImpl) {
|
|
|
12583
12541
|
}
|
|
12584
12542
|
return slackArchiveUrl(channelId, ts);
|
|
12585
12543
|
}
|
|
12586
|
-
async function fetchMessages(token,
|
|
12544
|
+
async function fetchMessages(token, watch2, fetchImpl) {
|
|
12587
12545
|
const out = [];
|
|
12588
12546
|
try {
|
|
12589
12547
|
const data = await slackApi(
|
|
12590
12548
|
token,
|
|
12591
12549
|
"conversations.replies",
|
|
12592
12550
|
{
|
|
12593
|
-
channel:
|
|
12594
|
-
ts:
|
|
12595
|
-
oldest:
|
|
12551
|
+
channel: watch2.channelId,
|
|
12552
|
+
ts: watch2.threadTs,
|
|
12553
|
+
oldest: watch2.lastSeenTs,
|
|
12596
12554
|
inclusive: false,
|
|
12597
12555
|
limit: 50
|
|
12598
12556
|
},
|
|
@@ -12601,14 +12559,14 @@ async function fetchMessages(token, watch, fetchImpl) {
|
|
|
12601
12559
|
out.push(...data.messages ?? []);
|
|
12602
12560
|
} catch {
|
|
12603
12561
|
}
|
|
12604
|
-
if (
|
|
12562
|
+
if (watch2.kind === "dm" || watch2.channelId.startsWith("D")) {
|
|
12605
12563
|
try {
|
|
12606
12564
|
const data = await slackApi(
|
|
12607
12565
|
token,
|
|
12608
12566
|
"conversations.history",
|
|
12609
12567
|
{
|
|
12610
|
-
channel:
|
|
12611
|
-
oldest:
|
|
12568
|
+
channel: watch2.channelId,
|
|
12569
|
+
oldest: watch2.lastSeenTs,
|
|
12612
12570
|
inclusive: false,
|
|
12613
12571
|
limit: 50
|
|
12614
12572
|
},
|
|
@@ -12628,8 +12586,8 @@ async function pollSlackOutboundWatches(opts) {
|
|
|
12628
12586
|
let watches = pruneWatches(existing, now);
|
|
12629
12587
|
let changed = watches.length !== existing.length;
|
|
12630
12588
|
for (let i = 0; i < watches.length; i++) {
|
|
12631
|
-
const
|
|
12632
|
-
const ws = getSlackWorkspace(
|
|
12589
|
+
const watch2 = watches[i];
|
|
12590
|
+
const ws = getSlackWorkspace(watch2.teamId);
|
|
12633
12591
|
if (!ws) continue;
|
|
12634
12592
|
let token;
|
|
12635
12593
|
try {
|
|
@@ -12637,19 +12595,19 @@ async function pollSlackOutboundWatches(opts) {
|
|
|
12637
12595
|
} catch {
|
|
12638
12596
|
continue;
|
|
12639
12597
|
}
|
|
12640
|
-
const messages = await fetchMessages(token,
|
|
12641
|
-
const replies = messages.filter((m) => isHumanReply(m,
|
|
12598
|
+
const messages = await fetchMessages(token, watch2, opts?.fetchImpl);
|
|
12599
|
+
const replies = messages.filter((m) => isHumanReply(m, watch2)).sort((a, b) => Number(a.ts) - Number(b.ts));
|
|
12642
12600
|
if (replies.length === 0) continue;
|
|
12643
|
-
const injected = new Set(
|
|
12644
|
-
const collected = [...
|
|
12645
|
-
let lastSeenTs =
|
|
12601
|
+
const injected = new Set(watch2.injectedReplyTs ?? []);
|
|
12602
|
+
const collected = [...watch2.replies ?? []];
|
|
12603
|
+
let lastSeenTs = watch2.lastSeenTs;
|
|
12646
12604
|
let latestName;
|
|
12647
|
-
let latestPermalink =
|
|
12605
|
+
let latestPermalink = watch2.permalink;
|
|
12648
12606
|
let newlyInjected = 0;
|
|
12649
12607
|
for (const msg of replies) {
|
|
12650
12608
|
const ts = msg.ts.trim();
|
|
12651
12609
|
const user = msg.user.trim();
|
|
12652
|
-
const fallback =
|
|
12610
|
+
const fallback = watch2.toUserId === user ? watch2.toLabel : user;
|
|
12653
12611
|
const replyUserName = await resolveUserName(
|
|
12654
12612
|
token,
|
|
12655
12613
|
user,
|
|
@@ -12658,7 +12616,7 @@ async function pollSlackOutboundWatches(opts) {
|
|
|
12658
12616
|
);
|
|
12659
12617
|
const permalink = await resolvePermalink(
|
|
12660
12618
|
token,
|
|
12661
|
-
|
|
12619
|
+
watch2.channelId,
|
|
12662
12620
|
ts,
|
|
12663
12621
|
opts?.fetchImpl
|
|
12664
12622
|
);
|
|
@@ -12676,7 +12634,7 @@ async function pollSlackOutboundWatches(opts) {
|
|
|
12676
12634
|
continue;
|
|
12677
12635
|
}
|
|
12678
12636
|
const delivered = await relayExternalReply({
|
|
12679
|
-
watch,
|
|
12637
|
+
watch: watch2,
|
|
12680
12638
|
reply,
|
|
12681
12639
|
permalink,
|
|
12682
12640
|
fetchImpl: opts?.fetchImpl
|
|
@@ -12686,19 +12644,19 @@ async function pollSlackOutboundWatches(opts) {
|
|
|
12686
12644
|
injected.add(ts);
|
|
12687
12645
|
lastSeenTs = ts;
|
|
12688
12646
|
}
|
|
12689
|
-
if (newlyInjected > 0 &&
|
|
12647
|
+
if (newlyInjected > 0 && watch2.sourceThreadId?.trim()) {
|
|
12690
12648
|
await continueSourceThread(
|
|
12691
|
-
|
|
12649
|
+
watch2.sourceThreadId.trim(),
|
|
12692
12650
|
formatSlackReplyContinuePrompt({
|
|
12693
|
-
userName: latestName ||
|
|
12694
|
-
kind:
|
|
12695
|
-
toLabel:
|
|
12651
|
+
userName: latestName || watch2.toLabel,
|
|
12652
|
+
kind: watch2.kind,
|
|
12653
|
+
toLabel: watch2.toLabel,
|
|
12696
12654
|
count: newlyInjected
|
|
12697
12655
|
})
|
|
12698
12656
|
);
|
|
12699
12657
|
}
|
|
12700
12658
|
watches[i] = {
|
|
12701
|
-
...
|
|
12659
|
+
...watch2,
|
|
12702
12660
|
lastSeenTs,
|
|
12703
12661
|
permalink: latestPermalink,
|
|
12704
12662
|
injectedReplyTs: [...injected],
|
|
@@ -13728,6 +13686,7 @@ export {
|
|
|
13728
13686
|
validateLinearApiKey,
|
|
13729
13687
|
verifyAbleTimeConnection,
|
|
13730
13688
|
verifyOptionalService,
|
|
13689
|
+
visibleAgentBlock,
|
|
13731
13690
|
visibleAssistantText,
|
|
13732
13691
|
visibleToolRowDetail,
|
|
13733
13692
|
waitForPidExit,
|
|
@@ -13742,7 +13701,9 @@ export {
|
|
|
13742
13701
|
withSelectedAccount,
|
|
13743
13702
|
withThreadLock,
|
|
13744
13703
|
withTurnScopedCost,
|
|
13704
|
+
workspaceAgentChatSummaries,
|
|
13745
13705
|
workspaceSettingsSourceLabel,
|
|
13706
|
+
worktreeAgentBlock,
|
|
13746
13707
|
worktreeBoardStatus,
|
|
13747
13708
|
worktreeCleanupSettings,
|
|
13748
13709
|
worktreeDisplayLabel,
|