@sideboard-ai/core 0.1.279 → 0.1.281
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/{abletime-VCVPSLSU.js → abletime-WLDBJJUI.js} +3 -3
- package/dist/{abletime-VRATUMDO.js → abletime-X64E3GUJ.js} +3 -3
- package/dist/{abletime-oauth-4K7MICAD.js → abletime-oauth-AXR676W5.js} +2 -2
- package/dist/{abletime-oauth-4C4G2J3R.js → abletime-oauth-UWOCEICP.js} +2 -2
- package/dist/{abletime-rest-PIBCNHF3.js → abletime-rest-LPUDJUEP.js} +2 -2
- package/dist/{abletime-rest-6N63ZVQ3.js → abletime-rest-S4XWEXV2.js} +2 -2
- package/dist/{agents-DYCFLDNL.js → agents-2IE4HKHA.js} +5 -5
- package/dist/{agents-RXP5NZBC.js → agents-XA2E64AW.js} +5 -5
- package/dist/{chunk-V5DMHB6Y.js → chunk-2KNO2GJQ.js} +781 -159
- package/dist/{chunk-TZ5JHIYK.js → chunk-2SJXQ4UX.js} +68 -21
- package/dist/{chunk-K7MWJLR7.js → chunk-5MGLNVNK.js} +6 -906
- package/dist/{chunk-RK5PIWCW.js → chunk-ADXVTKLC.js} +12 -4
- package/dist/{chunk-Y7JPL4WA.js → chunk-BDAUPNHL.js} +3 -4
- package/dist/{chunk-R7HLXD4I.js → chunk-BS3QLVPL.js} +93 -57
- package/dist/{chunk-CPGJ5ICZ.js → chunk-BYZ7F2BQ.js} +766 -148
- package/dist/{chunk-2J34G3WJ.js → chunk-DGPROCE5.js} +93 -57
- package/dist/{chunk-HQXBBUQX.js → chunk-DQZPF6WL.js} +12 -11
- package/dist/{chunk-YDXNCBA2.js → chunk-EIEAR62M.js} +12 -11
- package/dist/{chunk-XKZAVGTT.js → chunk-I6HSGKMZ.js} +1 -1
- package/dist/chunk-JPH7PYSK.js +1241 -0
- package/dist/{chunk-IPPY3FG5.js → chunk-K5XSTHHP.js} +6 -906
- package/dist/chunk-N5UGX3JL.js +1239 -0
- package/dist/{chunk-HNNS25SG.js → chunk-OCUIZD4Y.js} +14 -4
- package/dist/{chunk-IJDQKC5A.js → chunk-PY4UCMRI.js} +4 -4
- package/dist/{chunk-NQ7JMVSU.js → chunk-U7HAVAGJ.js} +1 -1
- package/dist/{chunk-PZURG3XN.js → chunk-VA55JG2I.js} +4 -4
- package/dist/{chunk-CPA43FBK.js → chunk-W7HIK2A6.js} +3 -4
- package/dist/{chunk-SKSOCIJT.js → chunk-WY2IAB7J.js} +68 -21
- package/dist/{chunk-QINJKNLR.js → chunk-XTPQEZZA.js} +2 -2
- package/dist/{conductor-J6WDOXWM.js → conductor-CNSCOC54.js} +5 -5
- package/dist/{conductor-OW6NAK3N.js → conductor-U776JSXQ.js} +5 -5
- package/dist/{coordinator-prompt-Z2D32HWK.js → coordinator-prompt-4DJIHSE6.js} +3 -3
- package/dist/{coordinator-prompt-ERTHREGJ.js → coordinator-prompt-SVVGU6AZ.js} +3 -3
- package/dist/{global-workspace-5PJXO45Z.js → global-workspace-UPUDWIS6.js} +3 -3
- package/dist/{global-workspace-AK5PSPQ2.js → global-workspace-XON4YZXE.js} +3 -3
- package/dist/index.cjs +3819 -2995
- package/dist/index.d.cts +103 -30
- package/dist/index.d.ts +103 -30
- package/dist/index.js +1013 -1001
- package/dist/mcp/run-stdio.cjs +2075 -1313
- package/dist/mcp/run-stdio.js +743 -785
- package/dist/{orchestrator-KY2KBBE2.js → orchestrator-FPGVKEID.js} +7 -7
- package/dist/{orchestrator-E5WK57DA.js → orchestrator-HWJHK5NL.js} +7 -7
- package/dist/{thread-store-B3ZM4QEZ.js → thread-store-7XBOUQ5K.js} +1 -1
- package/dist/{thread-store-SHCUBWB3.js → thread-store-ZQZDREVH.js} +1 -1
- package/dist/{workspaces-UP6DQ3WL.js → workspaces-7AJ5RZ3P.js} +7 -3
- package/dist/{workspaces-WU2RNLDV.js → workspaces-F76BWCSI.js} +7 -3
- package/dist/{worktree-L57T3KH7.js → worktree-NKLNVNXX.js} +20 -15
- package/dist/{worktree-Q6UJHG25.js → worktree-YTZC3UQN.js} +14 -9
- package/package.json +1 -1
- package/dist/chunk-FWNL62VA.js +0 -308
- package/dist/chunk-JANCPF7Z.js +0 -310
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
parseSessionQuotaResetAt,
|
|
24
24
|
planModeTurnInstruction,
|
|
25
25
|
resolveQuotaFallbackAgent
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-ADXVTKLC.js";
|
|
27
27
|
import {
|
|
28
28
|
REVIEW_TAB_TITLE,
|
|
29
29
|
applyWorktreePkgCacheEnv,
|
|
@@ -62,7 +62,7 @@ import {
|
|
|
62
62
|
worktreeDevProcessKey,
|
|
63
63
|
worktreeRunProcessKey,
|
|
64
64
|
worktreeSetupProcessKey
|
|
65
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-EIEAR62M.js";
|
|
66
66
|
import {
|
|
67
67
|
applyAgentEvent,
|
|
68
68
|
clipAgentEventForPaint,
|
|
@@ -88,7 +88,7 @@ import {
|
|
|
88
88
|
summarizeTurnStderr,
|
|
89
89
|
toolDescription,
|
|
90
90
|
turnFailChatText
|
|
91
|
-
} from "./chunk-
|
|
91
|
+
} from "./chunk-W7HIK2A6.js";
|
|
92
92
|
import {
|
|
93
93
|
SLACK_REPLY_FORMATTING,
|
|
94
94
|
addWorkspace,
|
|
@@ -103,16 +103,18 @@ import {
|
|
|
103
103
|
isGlobalRepoPath,
|
|
104
104
|
isGlobalThread,
|
|
105
105
|
isOrchestratorThread,
|
|
106
|
+
isRemovedWorkspace,
|
|
107
|
+
listWorkspaces,
|
|
106
108
|
orchestratorSessionPoisonedByBuiltins,
|
|
107
109
|
persistPendingFileAttachments,
|
|
108
110
|
removeWorkspace,
|
|
111
|
+
sameWorkspacePath,
|
|
109
112
|
stageAbsolutePathsAsAttachments,
|
|
110
113
|
stageBuffersAsAttachments,
|
|
111
114
|
syncWorkspacesFromThreads
|
|
112
|
-
} from "./chunk-
|
|
115
|
+
} from "./chunk-2SJXQ4UX.js";
|
|
113
116
|
import {
|
|
114
117
|
addPrStackLayer,
|
|
115
|
-
agentNicknameFromWorktreePath,
|
|
116
118
|
allocateTeamSlug,
|
|
117
119
|
canonicalizeRepoPath,
|
|
118
120
|
ciChecksFailed,
|
|
@@ -137,14 +139,11 @@ import {
|
|
|
137
139
|
getPrStack,
|
|
138
140
|
initPrStack,
|
|
139
141
|
isDirty,
|
|
140
|
-
isPlaceholderBranch,
|
|
141
142
|
isSideboardScratchPath,
|
|
142
|
-
isWorktreeIdentityTitle,
|
|
143
143
|
listWorktrees,
|
|
144
144
|
markPrReady,
|
|
145
145
|
mergeAgentGitAuthEnv,
|
|
146
146
|
mergePr,
|
|
147
|
-
normalizeWorktreePath,
|
|
148
147
|
pushBranch,
|
|
149
148
|
removeWorktree,
|
|
150
149
|
resolveAgentGitAuthEnv,
|
|
@@ -154,14 +153,8 @@ import {
|
|
|
154
153
|
resolvePrSelector,
|
|
155
154
|
resolveRepoRoot,
|
|
156
155
|
switchWorktreeBranch,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
withRepoGitLock,
|
|
160
|
-
worktreeDisplayLabelForGroup,
|
|
161
|
-
worktreeIdentityTitles,
|
|
162
|
-
worktreeNameFromPath,
|
|
163
|
-
worktreeSlugForTicket
|
|
164
|
-
} from "./chunk-K7MWJLR7.js";
|
|
156
|
+
withRepoGitLock
|
|
157
|
+
} from "./chunk-5MGLNVNK.js";
|
|
165
158
|
import {
|
|
166
159
|
clearStaleIndexLocks,
|
|
167
160
|
enrichPathWithNpmGlobalBin,
|
|
@@ -191,14 +184,17 @@ import {
|
|
|
191
184
|
orchestrationQuotaFallbackAgent,
|
|
192
185
|
planHistoryAgePurge,
|
|
193
186
|
planHistoryRetention,
|
|
187
|
+
readViewerContext,
|
|
194
188
|
resolveEffectiveIssueSource,
|
|
195
189
|
resolveNewThreadOptions,
|
|
196
190
|
resolveOrchestratorDefaults,
|
|
197
191
|
resolveViewerProfileForRepo,
|
|
198
192
|
sumUsageList,
|
|
199
193
|
updateAdvancedSettings,
|
|
194
|
+
updateAppEnvironment,
|
|
200
195
|
usageOnLimit,
|
|
201
|
-
withTurnScopedCost
|
|
196
|
+
withTurnScopedCost,
|
|
197
|
+
writeViewerContext
|
|
202
198
|
} from "./chunk-WRIMAEF2.js";
|
|
203
199
|
import {
|
|
204
200
|
writePrivateFile
|
|
@@ -214,17 +210,29 @@ import {
|
|
|
214
210
|
resolveAccountForThread
|
|
215
211
|
} from "./chunk-UN5Q7POQ.js";
|
|
216
212
|
import {
|
|
213
|
+
agentNicknameFromWorktreePath,
|
|
217
214
|
appendMessage,
|
|
218
215
|
createEmptyThread,
|
|
219
216
|
deleteThreadRecord,
|
|
220
217
|
findThreadByRef,
|
|
218
|
+
isPlaceholderBranch,
|
|
219
|
+
isWorktreeIdentityTitle,
|
|
221
220
|
listThreads,
|
|
221
|
+
normalizeWorkspaceTags,
|
|
222
|
+
normalizeWorktreePath,
|
|
222
223
|
readThread,
|
|
223
224
|
setStatus,
|
|
225
|
+
teamSlugFromName,
|
|
226
|
+
ticketSlugForBranch,
|
|
224
227
|
updateThread,
|
|
225
228
|
withThreadLock,
|
|
229
|
+
workspaceTagsFromGroup,
|
|
230
|
+
worktreeDisplayLabelForGroup,
|
|
231
|
+
worktreeIdentityTitles,
|
|
232
|
+
worktreeNameFromPath,
|
|
233
|
+
worktreeSlugForTicket,
|
|
226
234
|
writeThread
|
|
227
|
-
} from "./chunk-
|
|
235
|
+
} from "./chunk-JPH7PYSK.js";
|
|
228
236
|
import {
|
|
229
237
|
annotateDevAccessRuns,
|
|
230
238
|
appDataDir,
|
|
@@ -240,11 +248,11 @@ import {
|
|
|
240
248
|
import { EventEmitter } from "events";
|
|
241
249
|
|
|
242
250
|
// src/threads/injected-notices.ts
|
|
243
|
-
function isSlackExternalReplyText(
|
|
244
|
-
return
|
|
251
|
+
function isSlackExternalReplyText(text3) {
|
|
252
|
+
return text3.startsWith("Slack reply from ") && text3.includes("not a command");
|
|
245
253
|
}
|
|
246
|
-
function isInjectedNoticeText(
|
|
247
|
-
return isSlackExternalReplyText(
|
|
254
|
+
function isInjectedNoticeText(text3) {
|
|
255
|
+
return isSlackExternalReplyText(text3);
|
|
248
256
|
}
|
|
249
257
|
function pendingInjectedNotices(messages, match = isInjectedNoticeText) {
|
|
250
258
|
let i = messages.length - 1;
|
|
@@ -278,7 +286,7 @@ function formatInjectedNoticesForTurn(notices) {
|
|
|
278
286
|
}
|
|
279
287
|
|
|
280
288
|
// src/orchestrator/orchestrator.ts
|
|
281
|
-
import { randomUUID as
|
|
289
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
282
290
|
import { existsSync as existsSync14 } from "fs";
|
|
283
291
|
|
|
284
292
|
// src/agents/spawn.ts
|
|
@@ -325,8 +333,8 @@ function textSoFar(parts, parentId) {
|
|
|
325
333
|
}
|
|
326
334
|
return out;
|
|
327
335
|
}
|
|
328
|
-
function replaySized(
|
|
329
|
-
return
|
|
336
|
+
function replaySized(text3) {
|
|
337
|
+
return text3.trim().length >= REPLAY_MIN_CHARS;
|
|
330
338
|
}
|
|
331
339
|
function tailAfterAccumulated(accumulated, incoming) {
|
|
332
340
|
if (incoming.startsWith(accumulated)) return incoming.slice(accumulated.length);
|
|
@@ -489,9 +497,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
489
497
|
`Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
|
|
490
498
|
);
|
|
491
499
|
}
|
|
492
|
-
const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-
|
|
500
|
+
const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-UPUDWIS6.js");
|
|
493
501
|
if (isGlobalThread2(thread)) {
|
|
494
|
-
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-
|
|
502
|
+
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-SVVGU6AZ.js");
|
|
495
503
|
ensureGlobalCoordinatorCwd2(
|
|
496
504
|
isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
|
|
497
505
|
);
|
|
@@ -633,10 +641,10 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
633
641
|
outbound.flush();
|
|
634
642
|
const finalized = finalizeParts(parts);
|
|
635
643
|
const rawText = assistantText.trim() || partsToAssistantText(finalized);
|
|
636
|
-
const
|
|
637
|
-
const exitCode = thread.agent === "brightsy" ? adjustBrightsyTurnExit(result.exitCode ?? null,
|
|
644
|
+
const text3 = thread.agent === "brightsy" ? stripBrightsyNdjsonNoise(rawText) : rawText;
|
|
645
|
+
const exitCode = thread.agent === "brightsy" ? adjustBrightsyTurnExit(result.exitCode ?? null, text3) : result.exitCode ?? null;
|
|
638
646
|
onEvent({ type: "exit", data: exitCode });
|
|
639
|
-
return { exitCode, sessionId, assistantText:
|
|
647
|
+
return { exitCode, sessionId, assistantText: text3, parts: finalized, usage };
|
|
640
648
|
});
|
|
641
649
|
return {
|
|
642
650
|
pid: child.pid,
|
|
@@ -1066,8 +1074,8 @@ function sliceRunLogOutput(output, tailChars = DEFAULT_RUN_LOG_TAIL_CHARS) {
|
|
|
1066
1074
|
if (outputChars <= max) return { output, truncated: false, outputChars };
|
|
1067
1075
|
const cut = output.slice(outputChars - max);
|
|
1068
1076
|
const nl = cut.indexOf("\n");
|
|
1069
|
-
const
|
|
1070
|
-
return { output:
|
|
1077
|
+
const text3 = nl >= 0 && nl < cut.length - 1 ? cut.slice(nl + 1) : cut;
|
|
1078
|
+
return { output: text3, truncated: true, outputChars };
|
|
1071
1079
|
}
|
|
1072
1080
|
var PERSIST_DEBOUNCE_MS2 = 500;
|
|
1073
1081
|
function persistNow2(key) {
|
|
@@ -1428,7 +1436,7 @@ async function cloneRepoIntoSideboard(opts) {
|
|
|
1428
1436
|
const dest = join5(sideboardReposDir(), name);
|
|
1429
1437
|
if (existsSync4(dest)) {
|
|
1430
1438
|
const repoPath2 = await resolveRepoRoot(dest);
|
|
1431
|
-
const workspace2 = await
|
|
1439
|
+
const workspace2 = await addWorkspace(repoPath2);
|
|
1432
1440
|
return { repoPath: repoPath2, workspace: workspace2 };
|
|
1433
1441
|
}
|
|
1434
1442
|
const result = await execa3("git", ["clone", url, dest], { reject: false });
|
|
@@ -1438,7 +1446,7 @@ async function cloneRepoIntoSideboard(opts) {
|
|
|
1438
1446
|
);
|
|
1439
1447
|
}
|
|
1440
1448
|
const repoPath = await resolveRepoRoot(dest);
|
|
1441
|
-
const workspace = await
|
|
1449
|
+
const workspace = await addWorkspace(repoPath);
|
|
1442
1450
|
return { repoPath, workspace };
|
|
1443
1451
|
}
|
|
1444
1452
|
|
|
@@ -1486,9 +1494,9 @@ function thisProcessShouldDrainAgentQueues() {
|
|
|
1486
1494
|
var MAX_NOTIFY_ORCHESTRATOR_MESSAGE_CHARS = 1500;
|
|
1487
1495
|
var inputRequiredNotified = /* @__PURE__ */ new Set();
|
|
1488
1496
|
function clampMessage(raw) {
|
|
1489
|
-
const
|
|
1490
|
-
if (
|
|
1491
|
-
return `${
|
|
1497
|
+
const text3 = raw.replace(/\s+/g, " ").trim();
|
|
1498
|
+
if (text3.length <= MAX_NOTIFY_ORCHESTRATOR_MESSAGE_CHARS) return text3;
|
|
1499
|
+
return `${text3.slice(0, MAX_NOTIFY_ORCHESTRATOR_MESSAGE_CHARS - 1)}\u2026`;
|
|
1492
1500
|
}
|
|
1493
1501
|
function formatAskUserNotifyMessage(questions) {
|
|
1494
1502
|
const qs = questions.map((q) => q.question?.trim() ?? "").filter(Boolean);
|
|
@@ -1580,10 +1588,10 @@ async function notifyOrchestrator(input) {
|
|
|
1580
1588
|
// src/orchestrator/agent-block.ts
|
|
1581
1589
|
var MAX_AGENT_BLOCK_REASON = 240;
|
|
1582
1590
|
function clampBlockReason(raw) {
|
|
1583
|
-
const
|
|
1584
|
-
if (!
|
|
1585
|
-
if (
|
|
1586
|
-
return `${
|
|
1591
|
+
const text3 = raw.replace(/\s+/g, " ").trim();
|
|
1592
|
+
if (!text3) return "Waiting on a decision";
|
|
1593
|
+
if (text3.length <= MAX_AGENT_BLOCK_REASON) return text3;
|
|
1594
|
+
return `${text3.slice(0, MAX_AGENT_BLOCK_REASON - 1)}\u2026`;
|
|
1587
1595
|
}
|
|
1588
1596
|
function makeAgentBlock(source, reason, at = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1589
1597
|
return { source, reason: clampBlockReason(reason), at };
|
|
@@ -1725,6 +1733,45 @@ function notifyParentOfChildHalt(child, status, send) {
|
|
|
1725
1733
|
return true;
|
|
1726
1734
|
}
|
|
1727
1735
|
|
|
1736
|
+
// src/orchestrator/stop-reason.ts
|
|
1737
|
+
var STOP_REASON_MAX = 500;
|
|
1738
|
+
function resolveStopReason(opts) {
|
|
1739
|
+
const explicit = opts?.reason?.trim();
|
|
1740
|
+
if (explicit) return explicit.slice(0, STOP_REASON_MAX);
|
|
1741
|
+
if (opts?.continueQueue) return "Stopped to send the next prompt";
|
|
1742
|
+
if (opts?.clearQueue === false) return "Stopped by the user";
|
|
1743
|
+
return "Stopped (queue cleared)";
|
|
1744
|
+
}
|
|
1745
|
+
function reasonToKeepOnStop(existing, incoming) {
|
|
1746
|
+
const next = incoming?.trim();
|
|
1747
|
+
if (next) return next.slice(0, STOP_REASON_MAX);
|
|
1748
|
+
const prior = existing?.trim();
|
|
1749
|
+
if (prior) return prior.slice(0, STOP_REASON_MAX);
|
|
1750
|
+
return "Stopped";
|
|
1751
|
+
}
|
|
1752
|
+
function writeStoppedStatus(threadId, reason) {
|
|
1753
|
+
const latest = readThread(threadId);
|
|
1754
|
+
if (!latest || latest.status === "archived") return latest;
|
|
1755
|
+
return setStatus(threadId, "stopped", reasonToKeepOnStop(latest.lastError, reason));
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
// src/orchestrator/steer-request.ts
|
|
1759
|
+
import { randomUUID } from "crypto";
|
|
1760
|
+
function isUnclaimedSteerRequest(req) {
|
|
1761
|
+
return Boolean(req?.requestId && !req.claimedAt);
|
|
1762
|
+
}
|
|
1763
|
+
function nextSteerRequest(existing) {
|
|
1764
|
+
if (existing?.requestId && !existing.claimedAt) return existing;
|
|
1765
|
+
return { requestId: randomUUID(), requestedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1766
|
+
}
|
|
1767
|
+
function claimSteerRequest(threadId) {
|
|
1768
|
+
const pending = readThread(threadId)?.steerRequest;
|
|
1769
|
+
if (!pending?.requestId || pending.claimedAt) return;
|
|
1770
|
+
updateThread(threadId, {
|
|
1771
|
+
steerRequest: { ...pending, claimedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1728
1775
|
// src/threads/agent-purpose-title.ts
|
|
1729
1776
|
function agentPurposeStamp(input) {
|
|
1730
1777
|
if (input.autoContinue) return null;
|
|
@@ -2146,7 +2193,8 @@ function threadSearchText(thread, workspaceName = "") {
|
|
|
2146
2193
|
thread.branchName,
|
|
2147
2194
|
thread.prUrl ?? "",
|
|
2148
2195
|
workspaceName,
|
|
2149
|
-
thread.repoPath
|
|
2196
|
+
thread.repoPath,
|
|
2197
|
+
...thread.tags ?? []
|
|
2150
2198
|
].join(" ");
|
|
2151
2199
|
}
|
|
2152
2200
|
function inWorkspace(repoPath, filterRepoPath) {
|
|
@@ -2205,6 +2253,7 @@ function findBoardPr(prs, ref, repoPath = "") {
|
|
|
2205
2253
|
function toThreadCard(group) {
|
|
2206
2254
|
const thread = group[0];
|
|
2207
2255
|
const withPr = group.find((t) => t.prUrl?.trim()) ?? thread;
|
|
2256
|
+
const tags = workspaceTagsFromGroup(group);
|
|
2208
2257
|
return {
|
|
2209
2258
|
kind: "thread",
|
|
2210
2259
|
id: thread.id,
|
|
@@ -2217,7 +2266,8 @@ function toThreadCard(group) {
|
|
|
2217
2266
|
prUrl: withPr.prUrl,
|
|
2218
2267
|
link: `sideboard://chat/${thread.id}`,
|
|
2219
2268
|
...group.length > 1 ? { chatCount: group.length } : {},
|
|
2220
|
-
...threadCardBlockFields(group) ?? {}
|
|
2269
|
+
...threadCardBlockFields(group) ?? {},
|
|
2270
|
+
...tags.length > 0 ? { tags } : {}
|
|
2221
2271
|
};
|
|
2222
2272
|
}
|
|
2223
2273
|
function threadMatchesKind(thread, kind) {
|
|
@@ -2293,6 +2343,51 @@ function assembleHomeBoard(input) {
|
|
|
2293
2343
|
}
|
|
2294
2344
|
var HOME_BOARD_AGENT_HINT = "Home is a Kanban of workspaces (one card per checkout; sibling chats on a card are separate agents). Do not create a second workspace for a ticket, PR, or named branch that already has a live checkout \u2014 create_workspace / start_board_card return that checkout (alreadyStarted) and its chats. Add another agent with fork_chat. Creating from the default branch still opens a new isolated workspace. Columns are the path to merge: New (no PR) \u2192 Draft (draft PR) \u2192 Review (open PR) \u2192 Merged. ownership=mine is PRs you authored (or WIP with no PR); ownership=reviewing is someone else's PR (or a review-requested checkout). Viewer login comes from gh, not a setting. Archive removes the card to Settings \u2192 History. Queued/running are activity on the card, not columns. blockedReason means an agent on that card 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. Orchestration chats stay in the sidebar. Do not invent status.";
|
|
2295
2345
|
|
|
2346
|
+
// src/threads/workspace-tags.ts
|
|
2347
|
+
function workspaceTagTargetRef(opts) {
|
|
2348
|
+
const requested = opts.requestedRef?.trim() || "";
|
|
2349
|
+
if (!opts.ownWorktreeOnly) {
|
|
2350
|
+
if (!requested) throw new Error("Pass a chat id on the workspace to label.");
|
|
2351
|
+
return requested;
|
|
2352
|
+
}
|
|
2353
|
+
const caller = opts.caller;
|
|
2354
|
+
if (!caller || isGlobalThread(caller) || caller.sourceType === "orchestration") {
|
|
2355
|
+
throw new Error("Tags belong on a project workspace. This chat is not one.");
|
|
2356
|
+
}
|
|
2357
|
+
if (!requested || requested === caller.id) return caller.id;
|
|
2358
|
+
const other = opts.find(requested);
|
|
2359
|
+
if (!other || normalizeWorktreePath(other.worktreePath) !== normalizeWorktreePath(caller.worktreePath)) {
|
|
2360
|
+
throw new Error("You can only change tags on this workspace.");
|
|
2361
|
+
}
|
|
2362
|
+
return other.id;
|
|
2363
|
+
}
|
|
2364
|
+
function applyWorkspaceTags(threadRef, tags, mode = "replace") {
|
|
2365
|
+
const thread = findThreadByRef(threadRef);
|
|
2366
|
+
if (!thread) throw new Error(`Thread not found: ${threadRef}`);
|
|
2367
|
+
if (isGlobalThread(thread) || thread.sourceType === "orchestration") {
|
|
2368
|
+
throw new Error("Tags belong on a project workspace. Pass a worktree chat id.");
|
|
2369
|
+
}
|
|
2370
|
+
const incoming = normalizeWorkspaceTags(tags);
|
|
2371
|
+
const siblings = threadsSharingWorktree(thread.worktreePath);
|
|
2372
|
+
const current = workspaceTagsFromGroup(siblings.length > 0 ? siblings : [thread]);
|
|
2373
|
+
let next;
|
|
2374
|
+
if (mode === "add") {
|
|
2375
|
+
next = normalizeWorkspaceTags([...current, ...incoming]);
|
|
2376
|
+
} else if (mode === "remove") {
|
|
2377
|
+
const drop = new Set(incoming.map((tag) => tag.toLowerCase()));
|
|
2378
|
+
next = current.filter((tag) => !drop.has(tag.toLowerCase()));
|
|
2379
|
+
} else {
|
|
2380
|
+
next = incoming;
|
|
2381
|
+
}
|
|
2382
|
+
const targets = siblings.length > 0 ? siblings : [thread];
|
|
2383
|
+
let updated = thread;
|
|
2384
|
+
for (const row of targets) {
|
|
2385
|
+
const written = updateThread(row.id, { tags: next });
|
|
2386
|
+
if (written.id === thread.id) updated = written;
|
|
2387
|
+
}
|
|
2388
|
+
return updated;
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2296
2391
|
// src/threads/create.ts
|
|
2297
2392
|
function persistCreateAttachments(worktreePath, attachments) {
|
|
2298
2393
|
return persistPendingFileAttachments(worktreePath, attachments ?? []);
|
|
@@ -2318,20 +2413,26 @@ function reuseLiveThread(input, repoPath, match) {
|
|
|
2318
2413
|
liveThreadsForCreate()
|
|
2319
2414
|
);
|
|
2320
2415
|
if (!existing) return void 0;
|
|
2321
|
-
|
|
2322
|
-
if (
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2416
|
+
let thread = readThread(existing.id) ?? existing;
|
|
2417
|
+
if (input.attachments?.length) {
|
|
2418
|
+
thread = updateThread(thread.id, {
|
|
2419
|
+
attachments: persistCreateAttachments(thread.worktreePath, [
|
|
2420
|
+
...thread.attachments,
|
|
2421
|
+
...input.attachments
|
|
2422
|
+
])
|
|
2423
|
+
});
|
|
2424
|
+
}
|
|
2425
|
+
if (normalizeWorkspaceTags(input.tags).length > 0) {
|
|
2426
|
+
thread = applyWorkspaceTags(thread.id, input.tags, "add");
|
|
2427
|
+
}
|
|
2428
|
+
return thread;
|
|
2329
2429
|
}
|
|
2330
2430
|
async function createThread(input, _onSetupLine) {
|
|
2331
2431
|
const repoPath = await resolveRepoRoot(input.repoPath);
|
|
2332
2432
|
if (!existsSync5(repoPath)) {
|
|
2333
2433
|
throw new Error(`Repo not found: ${repoPath}`);
|
|
2334
2434
|
}
|
|
2435
|
+
if (isRemovedWorkspace(repoPath)) throw new Error(`Project was removed: ${repoPath}`);
|
|
2335
2436
|
const reused = reuseLiveThread(input, repoPath, {
|
|
2336
2437
|
sourceType: input.sourceType,
|
|
2337
2438
|
sourceRef: input.sourceRef,
|
|
@@ -2390,7 +2491,8 @@ async function createThread(input, _onSetupLine) {
|
|
|
2390
2491
|
sourceIsFork: false,
|
|
2391
2492
|
parentThreadId: input.parentThreadId ?? null,
|
|
2392
2493
|
status: "idle",
|
|
2393
|
-
cowboy: true
|
|
2494
|
+
cowboy: true,
|
|
2495
|
+
tags: input.tags
|
|
2394
2496
|
});
|
|
2395
2497
|
writeThread(thread2);
|
|
2396
2498
|
await ensureWorkspace(repoPath);
|
|
@@ -2404,7 +2506,7 @@ async function createThread(input, _onSetupLine) {
|
|
|
2404
2506
|
let sourceIsFork = false;
|
|
2405
2507
|
if (!input.cowboy && sourceType === "branch" && (!sourceRef || sourceRef === "HEAD" || sourceRef === "default")) {
|
|
2406
2508
|
if (isAbleTimeConnected() && getIssueSource() === "abletime") {
|
|
2407
|
-
const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-
|
|
2509
|
+
const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-WLDBJJUI.js");
|
|
2408
2510
|
const task = await ensureAbleTimeTask({
|
|
2409
2511
|
title: title?.trim() || "Untitled work",
|
|
2410
2512
|
description: input.prompt?.trim() || void 0
|
|
@@ -2507,7 +2609,8 @@ async function createThread(input, _onSetupLine) {
|
|
|
2507
2609
|
prTitle,
|
|
2508
2610
|
prAuthorLogin: prAuthorLogin2,
|
|
2509
2611
|
prLabels,
|
|
2510
|
-
prIsDraft
|
|
2612
|
+
prIsDraft,
|
|
2613
|
+
tags: input.tags
|
|
2511
2614
|
});
|
|
2512
2615
|
writeThread(thread);
|
|
2513
2616
|
await ensureWorkspace(repoPath);
|
|
@@ -2594,8 +2697,8 @@ function summarizeTurnLive(parts) {
|
|
|
2594
2697
|
const interesting = [...tools].reverse().find((t) => !isPollWrapperToolName(t.name)) ?? lastTool;
|
|
2595
2698
|
const lastToolLabel = interesting ? interesting.description || toolDescription(interesting.name, interesting.input) || interesting.name : void 0;
|
|
2596
2699
|
const thinking = lastText(parts, "thinking");
|
|
2597
|
-
const
|
|
2598
|
-
const excerptRaw = thinking ||
|
|
2700
|
+
const text3 = lastText(parts, "text");
|
|
2701
|
+
const excerptRaw = thinking || text3;
|
|
2599
2702
|
const excerpt = excerptRaw.length > 280 ? `${excerptRaw.slice(-280)}` : excerptRaw || void 0;
|
|
2600
2703
|
const summary = liveActivitySummary(parts);
|
|
2601
2704
|
return {
|
|
@@ -2741,7 +2844,8 @@ async function forkThreadWorktree(input, onSetupLine) {
|
|
|
2741
2844
|
title: input.title?.trim() || void 0,
|
|
2742
2845
|
parentThreadId: from.id,
|
|
2743
2846
|
attachments: [attachment],
|
|
2744
|
-
reuseExisting: false
|
|
2847
|
+
reuseExisting: false,
|
|
2848
|
+
tags: from.tags
|
|
2745
2849
|
},
|
|
2746
2850
|
onSetupLine
|
|
2747
2851
|
);
|
|
@@ -2749,7 +2853,7 @@ async function forkThreadWorktree(input, onSetupLine) {
|
|
|
2749
2853
|
}
|
|
2750
2854
|
|
|
2751
2855
|
// src/orchestrator/quota-failover.ts
|
|
2752
|
-
import { randomUUID } from "crypto";
|
|
2856
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2753
2857
|
function managedKindForAgent(agent) {
|
|
2754
2858
|
return agent === "claude" || agent === "codex" ? agent : null;
|
|
2755
2859
|
}
|
|
@@ -2835,8 +2939,8 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
|
|
|
2835
2939
|
);
|
|
2836
2940
|
const recent = from.messages.slice(-8).map((m) => {
|
|
2837
2941
|
const role = m.role === "user" ? "User" : m.role === "agent" ? "Agent" : "Summary";
|
|
2838
|
-
const
|
|
2839
|
-
return
|
|
2942
|
+
const text3 = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
|
|
2943
|
+
return text3 ? `- ${role}: ${text3}` : null;
|
|
2840
2944
|
}).filter(Boolean);
|
|
2841
2945
|
const orch = isOrchestratorThread(from);
|
|
2842
2946
|
const instructions = orch ? [
|
|
@@ -2869,7 +2973,7 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
|
|
|
2869
2973
|
...instructions
|
|
2870
2974
|
].join("\n");
|
|
2871
2975
|
return {
|
|
2872
|
-
id:
|
|
2976
|
+
id: randomUUID2(),
|
|
2873
2977
|
name: orch ? "Orchestration quota handoff.md" : "Quota handoff.md",
|
|
2874
2978
|
kind: "transcript",
|
|
2875
2979
|
content: body
|
|
@@ -2964,13 +3068,13 @@ function resolveConductorCursorAgentId(workspacePath) {
|
|
|
2964
3068
|
for (const hash of hashes) {
|
|
2965
3069
|
const agentsFile = join7(CURSOR_SDK_STORE, hash, "agents.ndjson");
|
|
2966
3070
|
if (!existsSync7(agentsFile)) continue;
|
|
2967
|
-
let
|
|
3071
|
+
let text3;
|
|
2968
3072
|
try {
|
|
2969
|
-
|
|
3073
|
+
text3 = readFileSync5(agentsFile, "utf8");
|
|
2970
3074
|
} catch {
|
|
2971
3075
|
continue;
|
|
2972
3076
|
}
|
|
2973
|
-
for (const line of
|
|
3077
|
+
for (const line of text3.split("\n")) {
|
|
2974
3078
|
const trimmed = line.trim();
|
|
2975
3079
|
if (!trimmed) continue;
|
|
2976
3080
|
try {
|
|
@@ -3009,8 +3113,8 @@ async function adoptThread(input) {
|
|
|
3009
3113
|
messages: input.messages ?? []
|
|
3010
3114
|
});
|
|
3011
3115
|
writeThread(thread);
|
|
3012
|
-
const {
|
|
3013
|
-
await
|
|
3116
|
+
const { addWorkspace: addWorkspace2 } = await import("./workspaces-F76BWCSI.js");
|
|
3117
|
+
await addWorkspace2(repoPath);
|
|
3014
3118
|
const { ensureWorktreeSideboardIgnored } = await import("./worktree-exclude-5N2KTYIT.js");
|
|
3015
3119
|
await ensureWorktreeSideboardIgnored(input.worktreePath);
|
|
3016
3120
|
return thread;
|
|
@@ -3736,13 +3840,13 @@ function formatStat(files) {
|
|
|
3736
3840
|
return `${n} file${n === 1 ? "" : "s"} changed, ${additions} insertions(+), ${deletions} deletions(-)`;
|
|
3737
3841
|
}
|
|
3738
3842
|
function emptyScopeStats() {
|
|
3739
|
-
const
|
|
3843
|
+
const z4 = emptyStat();
|
|
3740
3844
|
return {
|
|
3741
|
-
commits:
|
|
3742
|
-
uncommitted:
|
|
3743
|
-
staged:
|
|
3744
|
-
unstaged:
|
|
3745
|
-
last_turn:
|
|
3845
|
+
commits: z4,
|
|
3846
|
+
uncommitted: z4,
|
|
3847
|
+
staged: z4,
|
|
3848
|
+
unstaged: z4,
|
|
3849
|
+
last_turn: z4
|
|
3746
3850
|
};
|
|
3747
3851
|
}
|
|
3748
3852
|
function filesFromDiff(nameStatus, numstat, combinedDiff, maxHunk) {
|
|
@@ -4443,10 +4547,49 @@ async function confirmLand(thread, opts) {
|
|
|
4443
4547
|
}
|
|
4444
4548
|
}
|
|
4445
4549
|
|
|
4550
|
+
// src/diff/read-worktree-tail.ts
|
|
4551
|
+
import { closeSync, fstatSync, openSync, readSync } from "fs";
|
|
4552
|
+
import { join as join9 } from "path";
|
|
4553
|
+
var WORKTREE_TAIL_BYTES = 2e5;
|
|
4554
|
+
function readWorktreeFileTail(worktreePath, relativePath, maxBytes = WORKTREE_TAIL_BYTES) {
|
|
4555
|
+
if (!relativePath || relativePath.includes("..") || relativePath.startsWith("/")) {
|
|
4556
|
+
throw new Error("Invalid path");
|
|
4557
|
+
}
|
|
4558
|
+
const abs = join9(worktreePath, relativePath);
|
|
4559
|
+
const fd = openSync(abs, "r");
|
|
4560
|
+
try {
|
|
4561
|
+
const st = fstatSync(fd);
|
|
4562
|
+
if (!st.isFile()) throw new Error(`Not a file: ${relativePath}`);
|
|
4563
|
+
const size = st.size;
|
|
4564
|
+
const start = Math.max(0, size - maxBytes);
|
|
4565
|
+
const length = size - start;
|
|
4566
|
+
const buf = Buffer.alloc(length);
|
|
4567
|
+
if (length > 0) readSync(fd, buf, 0, length, start);
|
|
4568
|
+
if (buf.subarray(0, Math.min(buf.length, 8e3)).includes(0)) {
|
|
4569
|
+
return {
|
|
4570
|
+
path: relativePath,
|
|
4571
|
+
content: `(binary file, ${size} bytes)`,
|
|
4572
|
+
truncated: false,
|
|
4573
|
+
binary: true,
|
|
4574
|
+
encoding: "utf8"
|
|
4575
|
+
};
|
|
4576
|
+
}
|
|
4577
|
+
let content = buf.toString("utf8");
|
|
4578
|
+
const truncated = start > 0;
|
|
4579
|
+
if (truncated) {
|
|
4580
|
+
const nl = content.indexOf("\n");
|
|
4581
|
+
if (nl >= 0) content = content.slice(nl + 1);
|
|
4582
|
+
}
|
|
4583
|
+
return { path: relativePath, content, truncated, binary: false, encoding: "utf8" };
|
|
4584
|
+
} finally {
|
|
4585
|
+
closeSync(fd);
|
|
4586
|
+
}
|
|
4587
|
+
}
|
|
4588
|
+
|
|
4446
4589
|
// src/skills/discover.ts
|
|
4447
4590
|
import { existsSync as existsSync10, readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
|
|
4448
4591
|
import { homedir } from "os";
|
|
4449
|
-
import { join as
|
|
4592
|
+
import { join as join10 } from "path";
|
|
4450
4593
|
|
|
4451
4594
|
// src/skills/bundled/long-running.ts
|
|
4452
4595
|
var LONG_RUNNING_SKILL_COMMAND = "long-running";
|
|
@@ -4615,7 +4758,7 @@ function scanSkillsDir(dir, source, out) {
|
|
|
4615
4758
|
}
|
|
4616
4759
|
for (const entry of entries) {
|
|
4617
4760
|
if (entry.startsWith(".")) continue;
|
|
4618
|
-
const skillMd =
|
|
4761
|
+
const skillMd = join10(dir, entry, "SKILL.md");
|
|
4619
4762
|
if (!existsSync10(skillMd)) continue;
|
|
4620
4763
|
try {
|
|
4621
4764
|
if (!statSync3(skillMd).isFile()) continue;
|
|
@@ -4637,12 +4780,12 @@ function scanClaudePluginSkills(pluginsRoot, out) {
|
|
|
4637
4780
|
return;
|
|
4638
4781
|
}
|
|
4639
4782
|
if (lookingForSkillsDir && entries.includes("SKILL.md")) {
|
|
4640
|
-
const skill = readSkill(
|
|
4783
|
+
const skill = readSkill(join10(dir, "SKILL.md"), "cli");
|
|
4641
4784
|
if (skill) out.push(skill);
|
|
4642
4785
|
}
|
|
4643
4786
|
for (const entry of entries) {
|
|
4644
4787
|
if (entry === "node_modules" || entry === ".git") continue;
|
|
4645
|
-
const full =
|
|
4788
|
+
const full = join10(dir, entry);
|
|
4646
4789
|
try {
|
|
4647
4790
|
if (!statSync3(full).isDirectory()) continue;
|
|
4648
4791
|
} catch {
|
|
@@ -4662,17 +4805,17 @@ function discoverSkills(worktreePath) {
|
|
|
4662
4805
|
const home = homedir();
|
|
4663
4806
|
const collected = [];
|
|
4664
4807
|
for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
|
|
4665
|
-
scanSkillsDir(
|
|
4808
|
+
scanSkillsDir(join10(worktreePath, rel), "workspace", collected);
|
|
4666
4809
|
}
|
|
4667
4810
|
for (const abs of [
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4811
|
+
join10(home, ".claude/skills"),
|
|
4812
|
+
join10(home, ".cursor/skills"),
|
|
4813
|
+
join10(home, ".sideboard/skills"),
|
|
4814
|
+
join10(home, ".brightsy/skills")
|
|
4672
4815
|
]) {
|
|
4673
4816
|
scanSkillsDir(abs, "user", collected);
|
|
4674
4817
|
}
|
|
4675
|
-
scanClaudePluginSkills(
|
|
4818
|
+
scanClaudePluginSkills(join10(home, ".claude/plugins"), collected);
|
|
4676
4819
|
collected.push(...bundledSkills());
|
|
4677
4820
|
const rank = {
|
|
4678
4821
|
workspace: 0,
|
|
@@ -4844,11 +4987,11 @@ function shiftQueuedItem(queue, queueAttachments) {
|
|
|
4844
4987
|
|
|
4845
4988
|
// src/agents/instructions.ts
|
|
4846
4989
|
import { existsSync as existsSync12 } from "fs";
|
|
4847
|
-
import { join as
|
|
4990
|
+
import { join as join12 } from "path";
|
|
4848
4991
|
|
|
4849
4992
|
// src/skills/detached-job-path.ts
|
|
4850
4993
|
import { existsSync as existsSync11 } from "fs";
|
|
4851
|
-
import { dirname as dirname2, join as
|
|
4994
|
+
import { dirname as dirname2, join as join11 } from "path";
|
|
4852
4995
|
import { fileURLToPath } from "url";
|
|
4853
4996
|
var DETACHED_JOB_FILENAMES = ["detached-job.cjs", "detached-job.js"];
|
|
4854
4997
|
function compiledModuleDir() {
|
|
@@ -4865,7 +5008,7 @@ function compiledModuleDir() {
|
|
|
4865
5008
|
}
|
|
4866
5009
|
function detachedJobScriptIn(dir) {
|
|
4867
5010
|
for (const name of DETACHED_JOB_FILENAMES) {
|
|
4868
|
-
const script =
|
|
5011
|
+
const script = join11(dir, "scripts", name);
|
|
4869
5012
|
if (existsSync11(script)) return script;
|
|
4870
5013
|
}
|
|
4871
5014
|
return null;
|
|
@@ -5002,12 +5145,15 @@ function formatWorktreeDirective(thread, opts) {
|
|
|
5002
5145
|
lines.push(
|
|
5003
5146
|
"Schedules: `create_schedule` / `list_schedules` (`at` / `every` / `cron`). `chatId=self` continues this chat; omit chatId for a new Global chat. Fires only while Sideboard.app is running. A future job turns on Settings \u2192 Advanced \u2192 Caffeinate while schedules are enabled."
|
|
5004
5147
|
);
|
|
5148
|
+
lines.push(
|
|
5149
|
+
"`set_workspace_tags` labels this checkout (omit ref; replace, add, or remove). Not another checkout."
|
|
5150
|
+
);
|
|
5005
5151
|
lines.push("");
|
|
5006
5152
|
lines.push(formatProcessGuideDirective({ worktreePath: thread.worktreePath }));
|
|
5007
5153
|
return lines.join("\n");
|
|
5008
5154
|
}
|
|
5009
5155
|
function formatWorktreeReminder() {
|
|
5010
|
-
return "Sideboard workspace: stay in this cwd for all file and git work. Push and open PRs against origin, never upstream. Do not edit the main repo checkout. If a goal is given (Greptile 5/5, CI green), watch-fix-push until it lands \u2014 do not watch after every push. Schedules: create_schedule (at / every / cron; chatId=self for this chat).";
|
|
5156
|
+
return "Sideboard workspace: stay in this cwd for all file and git work. Push and open PRs against origin, never upstream. Do not edit the main repo checkout. If a goal is given (Greptile 5/5, CI green), watch-fix-push until it lands \u2014 do not watch after every push. Schedules: create_schedule (at / every / cron; chatId=self for this chat). set_workspace_tags updates labels on this workspace (omit ref).";
|
|
5011
5157
|
}
|
|
5012
5158
|
var PR_GOAL_RE = /greptile|\bci\b.*\b(green|pass)|checks?\s+(pass|green)|until\s+.*\b(pass|green|merge|clean)|\bfix ci\b|watch[- ]fix[- ]push/i;
|
|
5013
5159
|
function mentionsPrGoal(prompt) {
|
|
@@ -5036,17 +5182,17 @@ function formatIssueToolsDirective(opts) {
|
|
|
5036
5182
|
];
|
|
5037
5183
|
if (opts.linear) {
|
|
5038
5184
|
lines.push(
|
|
5039
|
-
"- Linear: `linear_search_issues` (assignee=me; pass `updatedSince` for \u201Cany updates since yesterday?\u201D \u2014 new tickets, edits, and comments), `linear_get_issue` (comments, attachments; pass `include=full` if crush.truncated), `linear_download_attachment` (saves to `.context/attachments/`), `linear_comment`, `linear_update_issue` (state, cycle, labels, project, parent, relations), `linear_create_issue` (pass `parent` for spin-offs; call `linear_list_teams` first). Scope errors: reconnect Linear in Settings \u2192 Issues."
|
|
5185
|
+
"- Linear: `linear_search_issues` (assignee=me; pass `updatedSince` for \u201Cany updates since yesterday?\u201D \u2014 new tickets, edits, and comments; rows include the project), `linear_list_projects` (id, name, slug \u2014 use on update), `linear_get_issue` (comments, project, attachments; pass `include=full` if crush.truncated), `linear_download_attachment` (saves to `.context/attachments/`), `linear_comment`, `linear_update_issue` (state, cycle, labels, project, parent, relations), `linear_create_issue` (pass `parent` for spin-offs; call `linear_list_teams` first). Scope errors: reconnect Linear in Settings \u2192 Issues."
|
|
5040
5186
|
);
|
|
5041
5187
|
}
|
|
5042
5188
|
if (github) {
|
|
5043
5189
|
lines.push(
|
|
5044
|
-
"- GitHub: `github_search_issues` (assignee=me; `updatedSince` for inbox updates + comments), `github_get_issue` (comments, attachments; pass `include=full` if crush.truncated), `github_download_attachment` (saves to `.context/attachments/`), `github_comment`, `github_update_issue` (state open|closed, labels, project, parent, relations), `github_create_issue` (pass `parent` for spin-offs). Uses Account `gh`."
|
|
5190
|
+
"- GitHub: `github_search_issues` (assignee=me; `updatedSince` for inbox updates + comments; rows include projects when `gh` can read them), `github_list_projects` (titles for update), `github_get_issue` (comments, projects, attachments; pass `include=full` if crush.truncated; `projectsError` means run `gh auth refresh -s read:project`), `github_download_attachment` (saves to `.context/attachments/`), `github_comment`, `github_update_issue` (state open|closed, labels, project, parent, relations), `github_create_issue` (pass `parent` for spin-offs). Uses Account `gh`."
|
|
5045
5191
|
);
|
|
5046
5192
|
}
|
|
5047
5193
|
if (opts.abletime) {
|
|
5048
5194
|
lines.push(
|
|
5049
|
-
"- AbleTime: `abletime_list_tasks` / `abletime_search_tasks` (`updatedSince` for inbox updates + comments), `abletime_get_task` (comments, attachments; pass `include=full` if crush.truncated), `abletime_download_attachment` (saves to `.context/attachments/`), `abletime_comment`, `abletime_update_task` (state, labels, project, parent, relations), `abletime_create_task` (pass `parent` for spin-offs; `abletime_list_projects` if needed)."
|
|
5195
|
+
"- AbleTime: `abletime_list_tasks` / `abletime_search_tasks` (`updatedSince` for inbox updates + comments; rows include the project), `abletime_get_task` (comments, project, attachments; pass `include=full` if crush.truncated), `abletime_download_attachment` (saves to `.context/attachments/`), `abletime_comment`, `abletime_update_task` (state, labels, project, parent, relations), `abletime_create_task` (pass `parent` for spin-offs; `abletime_list_projects` if needed)."
|
|
5050
5196
|
);
|
|
5051
5197
|
}
|
|
5052
5198
|
lines.push(
|
|
@@ -5084,9 +5230,9 @@ function formatPrGateDirective() {
|
|
|
5084
5230
|
}
|
|
5085
5231
|
function formatProcessGuideDirective(opts) {
|
|
5086
5232
|
const worktree = opts?.worktreePath?.trim();
|
|
5087
|
-
const hasGraphSkill = !!worktree && existsSync12(
|
|
5088
|
-
const hasReviewSkill = !!worktree && existsSync12(
|
|
5089
|
-
const hasInstallSkill = !!worktree && existsSync12(
|
|
5233
|
+
const hasGraphSkill = !!worktree && existsSync12(join12(worktree, ".claude/skills/graph-engineering/SKILL.md"));
|
|
5234
|
+
const hasReviewSkill = !!worktree && existsSync12(join12(worktree, ".claude/skills/review/SKILL.md"));
|
|
5235
|
+
const hasInstallSkill = !!worktree && existsSync12(join12(worktree, ".claude/skills/worktree-install/SKILL.md"));
|
|
5090
5236
|
return [
|
|
5091
5237
|
"Process guides (recurring work only):",
|
|
5092
5238
|
hasGraphSkill ? "- Migrations, ports, batch fixes, and other fan-out: follow `.claude/skills/graph-engineering/SKILL.md` (`/graph-engineering`). Judge first; state on disk; grow the rulebook; do not patch around it." : null,
|
|
@@ -5201,6 +5347,9 @@ function formatOptionalServicesDirective(integrations) {
|
|
|
5201
5347
|
);
|
|
5202
5348
|
}
|
|
5203
5349
|
}
|
|
5350
|
+
lines.push(
|
|
5351
|
+
"If a connector MCP is listed but needsAuth or returns unauthorized, do not sign in to that MCP. Check the native CLI (or the HTTP API when there is no CLI) \u2014 the token is already in this process."
|
|
5352
|
+
);
|
|
5204
5353
|
lines.push(
|
|
5205
5354
|
"If a CLI is missing, say so or use the HTTP API with the same token. The user can Install CLI from Settings \u2192 Connectors. Do not ask the user to install a vendor MCP."
|
|
5206
5355
|
);
|
|
@@ -5226,7 +5375,451 @@ function formatOptionalServicesDirective(integrations) {
|
|
|
5226
5375
|
}
|
|
5227
5376
|
function formatOptionalServicesReminder(integrations) {
|
|
5228
5377
|
if (connectedOptionalServices(integrations).length === 0) return null;
|
|
5229
|
-
return "Settings \u2192 Connectors: official CLIs / PostHog HTTP with injected env. Write output to `.context/cli/` (not `.context/attachments/`) and read a slice \u2014 never dump raw --json/--expand into the tool result (any agent). stop_job if a fetch hangs. Do not add vendor MCPs.";
|
|
5378
|
+
return "Settings \u2192 Connectors: official CLIs / PostHog HTTP with injected env. If a connector MCP needsAuth or is unauthorized, check the native CLI or HTTP API instead of signing in. Write output to `.context/cli/` (not `.context/attachments/`) and read a slice \u2014 never dump raw --json/--expand into the tool result (any agent). stop_job if a fetch hangs. Do not add vendor MCPs.";
|
|
5379
|
+
}
|
|
5380
|
+
|
|
5381
|
+
// src/mcp/viewer-context-tools.ts
|
|
5382
|
+
import { z as z3 } from "zod";
|
|
5383
|
+
|
|
5384
|
+
// src/mcp/env-tools.ts
|
|
5385
|
+
import { z as z2 } from "zod";
|
|
5386
|
+
|
|
5387
|
+
// src/mcp/issue-list.ts
|
|
5388
|
+
import { z } from "zod";
|
|
5389
|
+
|
|
5390
|
+
// src/integrations/issue-since.ts
|
|
5391
|
+
var ISSUE_SINCE_ERROR = "updatedSince must be an ISO datetime, YYYY-MM-DD, or relative time (yesterday, 2d, 3 hours ago)";
|
|
5392
|
+
var DATE_ONLY_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
5393
|
+
var DATE_TIME_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/;
|
|
5394
|
+
var RELATIVE_RE = /^(\d+)\s*(hours?|hrs?|h|days?|d|weeks?|wks?|w)(?:\s+ago)?$/i;
|
|
5395
|
+
function startOfLocalDay(date) {
|
|
5396
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
5397
|
+
}
|
|
5398
|
+
function addLocalDays(date, days) {
|
|
5399
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + days);
|
|
5400
|
+
}
|
|
5401
|
+
function parseIssueSince(input, now = /* @__PURE__ */ new Date()) {
|
|
5402
|
+
const raw = input.trim();
|
|
5403
|
+
if (!raw) throw new Error(ISSUE_SINCE_ERROR);
|
|
5404
|
+
const dateOnly = raw.match(DATE_ONLY_RE);
|
|
5405
|
+
if (dateOnly) {
|
|
5406
|
+
const year = Number(dateOnly[1]);
|
|
5407
|
+
const month = Number(dateOnly[2]);
|
|
5408
|
+
const day = Number(dateOnly[3]);
|
|
5409
|
+
const local = new Date(year, month - 1, day);
|
|
5410
|
+
if (local.getFullYear() !== year || local.getMonth() !== month - 1 || local.getDate() !== day) {
|
|
5411
|
+
throw new Error(ISSUE_SINCE_ERROR);
|
|
5412
|
+
}
|
|
5413
|
+
return startOfLocalDay(local).toISOString();
|
|
5414
|
+
}
|
|
5415
|
+
if (DATE_TIME_RE.test(raw)) {
|
|
5416
|
+
const ms = Date.parse(raw);
|
|
5417
|
+
if (!Number.isFinite(ms)) throw new Error(ISSUE_SINCE_ERROR);
|
|
5418
|
+
return new Date(ms).toISOString();
|
|
5419
|
+
}
|
|
5420
|
+
const lower = raw.toLowerCase();
|
|
5421
|
+
if (lower === "today") return startOfLocalDay(now).toISOString();
|
|
5422
|
+
if (lower === "yesterday") return addLocalDays(startOfLocalDay(now), -1).toISOString();
|
|
5423
|
+
if (lower === "last week" || lower === "lastweek") {
|
|
5424
|
+
return addLocalDays(startOfLocalDay(now), -7).toISOString();
|
|
5425
|
+
}
|
|
5426
|
+
const relative = lower.match(RELATIVE_RE);
|
|
5427
|
+
if (relative) {
|
|
5428
|
+
const n = Number(relative[1]);
|
|
5429
|
+
if (!Number.isInteger(n) || n < 1) throw new Error(ISSUE_SINCE_ERROR);
|
|
5430
|
+
const unit = relative[2];
|
|
5431
|
+
if (unit.startsWith("h")) return new Date(now.getTime() - n * 36e5).toISOString();
|
|
5432
|
+
if (unit.startsWith("w")) return addLocalDays(startOfLocalDay(now), -7 * n).toISOString();
|
|
5433
|
+
return addLocalDays(startOfLocalDay(now), -n).toISOString();
|
|
5434
|
+
}
|
|
5435
|
+
throw new Error(ISSUE_SINCE_ERROR);
|
|
5436
|
+
}
|
|
5437
|
+
function formatGitHubSearchUpdatedSince(iso) {
|
|
5438
|
+
return iso.replace(/\.\d{3}Z$/, "Z");
|
|
5439
|
+
}
|
|
5440
|
+
function issueMatchesUpdatedSince(issue, sinceIso) {
|
|
5441
|
+
const ts = issue.updatedAt || issue.createdAt;
|
|
5442
|
+
if (!ts) return false;
|
|
5443
|
+
const value = Date.parse(ts);
|
|
5444
|
+
const since = Date.parse(sinceIso);
|
|
5445
|
+
return Number.isFinite(value) && Number.isFinite(since) && value >= since;
|
|
5446
|
+
}
|
|
5447
|
+
var ISSUE_COMMENT_PREVIEW_CHARS = 240;
|
|
5448
|
+
function previewIssueCommentBody(body) {
|
|
5449
|
+
const flat = body.replace(/\s+/g, " ").trim();
|
|
5450
|
+
if (flat.length <= ISSUE_COMMENT_PREVIEW_CHARS) return flat;
|
|
5451
|
+
return `${flat.slice(0, ISSUE_COMMENT_PREVIEW_CHARS - 1).trimEnd()}\u2026`;
|
|
5452
|
+
}
|
|
5453
|
+
function issueActivityKind(issue, sinceIso) {
|
|
5454
|
+
const created = issue.createdAt ? Date.parse(issue.createdAt) : Number.NaN;
|
|
5455
|
+
const since = Date.parse(sinceIso);
|
|
5456
|
+
if (Number.isFinite(created) && Number.isFinite(since) && created >= since) return "created";
|
|
5457
|
+
return "updated";
|
|
5458
|
+
}
|
|
5459
|
+
|
|
5460
|
+
// src/mcp/issue-list.ts
|
|
5461
|
+
var MCP_ISSUE_LIST_DEFAULT_LIMIT = 40;
|
|
5462
|
+
var MCP_ISSUE_LIST_MAX_LIMIT = 250;
|
|
5463
|
+
function clampMcpIssueLimit(limit) {
|
|
5464
|
+
if (limit == null || !Number.isFinite(limit)) return MCP_ISSUE_LIST_DEFAULT_LIMIT;
|
|
5465
|
+
return Math.max(1, Math.min(MCP_ISSUE_LIST_MAX_LIMIT, Math.floor(limit)));
|
|
5466
|
+
}
|
|
5467
|
+
function applyIssueListWindow(items, limit) {
|
|
5468
|
+
const truncated = items.length > limit;
|
|
5469
|
+
return {
|
|
5470
|
+
items: truncated ? items.slice(0, limit) : items,
|
|
5471
|
+
truncated
|
|
5472
|
+
};
|
|
5473
|
+
}
|
|
5474
|
+
var MCP_ISSUE_UPDATED_SINCE_DESCRIPTION = "ISO datetime, YYYY-MM-DD, or relative (yesterday, 2d, 3 hours ago). Tickets updated since then plus new comments. Use for \u201Cany updates since yesterday?\u201D";
|
|
5475
|
+
var mcpIssueUpdatedSinceSchema = z.string().min(1).optional().describe(MCP_ISSUE_UPDATED_SINCE_DESCRIPTION);
|
|
5476
|
+
function compactIssueRow(issue, opts) {
|
|
5477
|
+
const projectNames = (issue.projects ?? []).map((project) => (project.name || project.id || "").trim()).filter(Boolean);
|
|
5478
|
+
return {
|
|
5479
|
+
identifier: issue.identifier,
|
|
5480
|
+
title: issue.title,
|
|
5481
|
+
...issue.assignee ? { assignee: issue.assignee } : {},
|
|
5482
|
+
...issue.labels.length > 0 ? { labels: issue.labels } : {},
|
|
5483
|
+
...issue.teamKey ? { team: issue.teamKey } : {},
|
|
5484
|
+
...issue.cycle?.name ? { cycle: issue.cycle.name } : {},
|
|
5485
|
+
...projectNames.length ? { projects: projectNames } : {},
|
|
5486
|
+
...issue.createdAt ? { createdAt: issue.createdAt } : {},
|
|
5487
|
+
...issue.updatedAt ? { updatedAt: issue.updatedAt } : {},
|
|
5488
|
+
...opts?.since ? { kind: issueActivityKind(issue, opts.since) } : {}
|
|
5489
|
+
};
|
|
5490
|
+
}
|
|
5491
|
+
function formatMcpIssueList(input) {
|
|
5492
|
+
const truncated = input.truncated || Boolean(input.commentsTruncated);
|
|
5493
|
+
const comments = input.comments ?? [];
|
|
5494
|
+
return {
|
|
5495
|
+
...input.source ? { source: input.source } : {},
|
|
5496
|
+
...input.viewer ? { viewer: input.viewer } : {},
|
|
5497
|
+
limit: input.limit,
|
|
5498
|
+
returned: input.issues.length,
|
|
5499
|
+
truncated,
|
|
5500
|
+
...input.since ? { since: input.since } : {},
|
|
5501
|
+
...truncated ? {
|
|
5502
|
+
hint: input.since ? "More issues or comments match. Pass a higher limit (max 250) or a tighter query." : "More issues match. Pass a higher limit (max 250) or a tighter query."
|
|
5503
|
+
} : {},
|
|
5504
|
+
issues: input.issues.map((issue) => compactIssueRow(issue, { since: input.since })),
|
|
5505
|
+
...input.since || comments.length > 0 ? { comments } : {}
|
|
5506
|
+
};
|
|
5507
|
+
}
|
|
5508
|
+
function formatListedIssuesForMcp(result, page) {
|
|
5509
|
+
const windowed = applyIssueListWindow(result.issues, page);
|
|
5510
|
+
const comments = result.comments ?? [];
|
|
5511
|
+
const commentWindow = applyIssueListWindow(comments, page);
|
|
5512
|
+
return formatMcpIssueList({
|
|
5513
|
+
source: result.source,
|
|
5514
|
+
viewer: result.viewer?.name || result.viewer?.login,
|
|
5515
|
+
limit: page,
|
|
5516
|
+
issues: windowed.items,
|
|
5517
|
+
truncated: windowed.truncated,
|
|
5518
|
+
since: result.since,
|
|
5519
|
+
comments: commentWindow.items,
|
|
5520
|
+
commentsTruncated: commentWindow.truncated
|
|
5521
|
+
});
|
|
5522
|
+
}
|
|
5523
|
+
function mcpJson(payload, isError = false) {
|
|
5524
|
+
return {
|
|
5525
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
5526
|
+
...isError ? { isError: true } : {}
|
|
5527
|
+
};
|
|
5528
|
+
}
|
|
5529
|
+
|
|
5530
|
+
// src/mcp/env-tools.ts
|
|
5531
|
+
function text(payload, isError = false) {
|
|
5532
|
+
return mcpJson(payload, isError);
|
|
5533
|
+
}
|
|
5534
|
+
var PROJECT_PATH_HINT = "Pass repoPath from list_projects. Orchestration cwd is synthetic \u2014 do not omit repoPath for scope=project. Worktree turns may omit it (uses cwd).";
|
|
5535
|
+
async function projectFor(repoPath) {
|
|
5536
|
+
const hint = repoPath?.trim() || process.cwd();
|
|
5537
|
+
const name = projectNameForRepoPath(hint);
|
|
5538
|
+
if (!name) return null;
|
|
5539
|
+
return { name, prefix: projectEnvPrefix(name) };
|
|
5540
|
+
}
|
|
5541
|
+
function registerEnvTools(server) {
|
|
5542
|
+
server.tool(
|
|
5543
|
+
"list_env",
|
|
5544
|
+
"List Settings \u2192 Environment variable names (not values). scope=project keys use the project prefix (project name, uppercased, plus _). scope=account keys are shared by every project. Pass repoPath from list_projects when calling from orchestration so the reply includes this project prefix.",
|
|
5545
|
+
{
|
|
5546
|
+
repoPath: z2.string().optional().describe(
|
|
5547
|
+
"Registered workspace or worktree path. Required from orchestration. Omit on a worktree turn (uses cwd)."
|
|
5548
|
+
)
|
|
5549
|
+
},
|
|
5550
|
+
async ({ repoPath }) => {
|
|
5551
|
+
try {
|
|
5552
|
+
const project = await projectFor(repoPath);
|
|
5553
|
+
return text({
|
|
5554
|
+
project: project?.name ?? null,
|
|
5555
|
+
prefix: project?.prefix ?? null,
|
|
5556
|
+
keys: listEnvKeys()
|
|
5557
|
+
});
|
|
5558
|
+
} catch (err) {
|
|
5559
|
+
return text({ error: err instanceof Error ? err.message : String(err) }, true);
|
|
5560
|
+
}
|
|
5561
|
+
}
|
|
5562
|
+
);
|
|
5563
|
+
server.tool(
|
|
5564
|
+
"set_env",
|
|
5565
|
+
"Set a Settings \u2192 Environment variable. scope=project prefixes the key with the project name (APPLE_ID on project sideboard is stored as SIDEBOARD_APPLE_ID). scope=account stores the key unchanged and is shared by every project. Values are injected into later agent turns. list_env does not return values.",
|
|
5566
|
+
{
|
|
5567
|
+
key: z2.string().describe("Env name. For scope=project, pass the bare name or the already-prefixed name."),
|
|
5568
|
+
value: z2.string().describe("Value to store. Empty deletes the key."),
|
|
5569
|
+
scope: z2.enum(["account", "project"]).describe("project = prefixed for one repo; account = shared by every project"),
|
|
5570
|
+
repoPath: z2.string().optional().describe(
|
|
5571
|
+
"Required for scope=project from orchestration (path from list_projects). Worktree turns may omit it (cwd)."
|
|
5572
|
+
)
|
|
5573
|
+
},
|
|
5574
|
+
async ({ key, value, scope, repoPath }) => {
|
|
5575
|
+
try {
|
|
5576
|
+
const envScope = scope;
|
|
5577
|
+
if (envScope === "project") {
|
|
5578
|
+
const project = await projectFor(repoPath);
|
|
5579
|
+
if (!project) return text({ ok: false, error: "repoPath required", message: PROJECT_PATH_HINT }, true);
|
|
5580
|
+
const saved2 = setEnvVar({
|
|
5581
|
+
key,
|
|
5582
|
+
value,
|
|
5583
|
+
scope: "project",
|
|
5584
|
+
projectName: project.name
|
|
5585
|
+
});
|
|
5586
|
+
return text({ ok: true, ...saved2 });
|
|
5587
|
+
}
|
|
5588
|
+
const saved = setEnvVar({ key, value, scope: "account" });
|
|
5589
|
+
return text({ ok: true, ...saved });
|
|
5590
|
+
} catch (err) {
|
|
5591
|
+
return text({ error: err instanceof Error ? err.message : String(err) }, true);
|
|
5592
|
+
}
|
|
5593
|
+
}
|
|
5594
|
+
);
|
|
5595
|
+
server.tool(
|
|
5596
|
+
"delete_env",
|
|
5597
|
+
"Delete a Settings \u2192 Environment variable. scope=project uses the project prefix (same rule as set_env). Does not print the stored value.",
|
|
5598
|
+
{
|
|
5599
|
+
key: z2.string().describe("Env name. For scope=project, pass the bare name or the already-prefixed name."),
|
|
5600
|
+
scope: z2.enum(["account", "project"]),
|
|
5601
|
+
repoPath: z2.string().optional().describe(
|
|
5602
|
+
"Required for scope=project from orchestration (path from list_projects). Worktree turns may omit it (cwd)."
|
|
5603
|
+
)
|
|
5604
|
+
},
|
|
5605
|
+
async ({ key, scope, repoPath }) => {
|
|
5606
|
+
try {
|
|
5607
|
+
const envScope = scope;
|
|
5608
|
+
if (envScope === "project") {
|
|
5609
|
+
const project = await projectFor(repoPath);
|
|
5610
|
+
if (!project) return text({ ok: false, error: "repoPath required", message: PROJECT_PATH_HINT }, true);
|
|
5611
|
+
const removed2 = deleteEnvVar({
|
|
5612
|
+
key,
|
|
5613
|
+
scope: "project",
|
|
5614
|
+
projectName: project.name
|
|
5615
|
+
});
|
|
5616
|
+
return text({ ok: true, ...removed2 });
|
|
5617
|
+
}
|
|
5618
|
+
const removed = deleteEnvVar({ key, scope: "account" });
|
|
5619
|
+
return text({ ok: true, ...removed });
|
|
5620
|
+
} catch (err) {
|
|
5621
|
+
return text({ error: err instanceof Error ? err.message : String(err) }, true);
|
|
5622
|
+
}
|
|
5623
|
+
}
|
|
5624
|
+
);
|
|
5625
|
+
}
|
|
5626
|
+
|
|
5627
|
+
// src/mcp/viewer-context-tools.ts
|
|
5628
|
+
function text2(payload, isError = false) {
|
|
5629
|
+
return mcpJson(payload, isError);
|
|
5630
|
+
}
|
|
5631
|
+
function matchRegisteredWorkspace(repoPath) {
|
|
5632
|
+
const hint = (repoPath ?? "").trim().replace(/\/+$/, "");
|
|
5633
|
+
if (!hint) return null;
|
|
5634
|
+
let best = null;
|
|
5635
|
+
for (const w of listWorkspaces()) {
|
|
5636
|
+
const stored = w.path.replace(/\/+$/, "");
|
|
5637
|
+
if (stored === hint || hint.startsWith(`${stored}/`)) {
|
|
5638
|
+
if (!best || stored.length > best.length) best = stored;
|
|
5639
|
+
}
|
|
5640
|
+
}
|
|
5641
|
+
return best;
|
|
5642
|
+
}
|
|
5643
|
+
async function resolveProjectRepoPath(repoPath) {
|
|
5644
|
+
const hint = repoPath?.trim() || process.cwd();
|
|
5645
|
+
let root = hint;
|
|
5646
|
+
try {
|
|
5647
|
+
root = await resolveRepoRoot(hint);
|
|
5648
|
+
} catch {
|
|
5649
|
+
root = hint.replace(/\/+$/, "");
|
|
5650
|
+
}
|
|
5651
|
+
return matchRegisteredWorkspace(root) ?? matchRegisteredWorkspace(hint);
|
|
5652
|
+
}
|
|
5653
|
+
var PROJECT_PATH_HINT2 = "Pass repoPath from list_projects. Orchestration cwd is synthetic and empty \u2014 do not omit repoPath for project context.";
|
|
5654
|
+
function registerViewerContextTools(server) {
|
|
5655
|
+
server.tool(
|
|
5656
|
+
"get_viewer_context",
|
|
5657
|
+
"Read account and project user context (Settings \u2192 Agents / Projects): roles, tickets, and review queues as freeform text. Orchestration must pass repoPath from list_projects for project context. Worktree turns may omit repoPath (uses cwd).",
|
|
5658
|
+
{
|
|
5659
|
+
repoPath: z3.string().optional().describe(
|
|
5660
|
+
"Registered workspace or worktree path. Required from orchestration for a project. Omit on a worktree turn (uses cwd)."
|
|
5661
|
+
)
|
|
5662
|
+
},
|
|
5663
|
+
async ({ repoPath }) => {
|
|
5664
|
+
try {
|
|
5665
|
+
const path = await resolveProjectRepoPath(repoPath);
|
|
5666
|
+
return text2(readViewerContext(path ?? void 0));
|
|
5667
|
+
} catch (err) {
|
|
5668
|
+
return text2(
|
|
5669
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
5670
|
+
true
|
|
5671
|
+
);
|
|
5672
|
+
}
|
|
5673
|
+
}
|
|
5674
|
+
);
|
|
5675
|
+
server.tool(
|
|
5676
|
+
"update_viewer_context",
|
|
5677
|
+
"Replace account or project user context (Settings \u2192 Agents / Projects). Never call until the user confirmed. Show the proposed text, ask_user (Save this context / Do not save), wait, then pass confirmed=true. Empty context clears that field.",
|
|
5678
|
+
{
|
|
5679
|
+
scope: z3.enum(["account", "project"]).describe("account = Settings \u2192 Agents; project = Settings \u2192 Projects for one repo"),
|
|
5680
|
+
context: z3.string().describe("Full replacement text (roles, tickets, review queues). Empty clears."),
|
|
5681
|
+
confirmed: z3.boolean().optional().describe("Must be true after the user confirmed via ask_user. False/omit is rejected."),
|
|
5682
|
+
repoPath: z3.string().optional().describe(
|
|
5683
|
+
"Required for scope=project from orchestration (path from list_projects). Worktree turns may omit it (cwd)."
|
|
5684
|
+
)
|
|
5685
|
+
},
|
|
5686
|
+
async ({ scope, context, confirmed, repoPath }) => {
|
|
5687
|
+
try {
|
|
5688
|
+
if (scope === "project") {
|
|
5689
|
+
const path = await resolveProjectRepoPath(repoPath);
|
|
5690
|
+
if (!path) {
|
|
5691
|
+
return text2(
|
|
5692
|
+
{
|
|
5693
|
+
ok: false,
|
|
5694
|
+
error: "repoPath required",
|
|
5695
|
+
message: PROJECT_PATH_HINT2
|
|
5696
|
+
},
|
|
5697
|
+
true
|
|
5698
|
+
);
|
|
5699
|
+
}
|
|
5700
|
+
const result2 = writeViewerContext({
|
|
5701
|
+
scope,
|
|
5702
|
+
context,
|
|
5703
|
+
repoPath: path,
|
|
5704
|
+
confirmed: confirmed === true
|
|
5705
|
+
});
|
|
5706
|
+
return text2(result2, !result2.ok);
|
|
5707
|
+
}
|
|
5708
|
+
const result = writeViewerContext({
|
|
5709
|
+
scope,
|
|
5710
|
+
context,
|
|
5711
|
+
confirmed: confirmed === true
|
|
5712
|
+
});
|
|
5713
|
+
return text2(result, !result.ok);
|
|
5714
|
+
} catch (err) {
|
|
5715
|
+
return text2(
|
|
5716
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
5717
|
+
true
|
|
5718
|
+
);
|
|
5719
|
+
}
|
|
5720
|
+
}
|
|
5721
|
+
);
|
|
5722
|
+
registerEnvTools(server);
|
|
5723
|
+
}
|
|
5724
|
+
|
|
5725
|
+
// src/store/env-vars.ts
|
|
5726
|
+
var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
5727
|
+
function projectEnvPrefix(projectName) {
|
|
5728
|
+
const slug = projectName.normalize("NFKD").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").toUpperCase().slice(0, 40);
|
|
5729
|
+
return `${slug || "PROJECT"}_`;
|
|
5730
|
+
}
|
|
5731
|
+
function assertEnvKey(key) {
|
|
5732
|
+
const k = key.trim();
|
|
5733
|
+
if (!ENV_KEY.test(k)) {
|
|
5734
|
+
throw new Error(
|
|
5735
|
+
`Invalid env key "${key}". Use letters, digits, and underscores, starting with a letter or underscore.`
|
|
5736
|
+
);
|
|
5737
|
+
}
|
|
5738
|
+
return k;
|
|
5739
|
+
}
|
|
5740
|
+
function qualifyProjectEnvKey(prefix, key) {
|
|
5741
|
+
const k = assertEnvKey(key);
|
|
5742
|
+
if (k.toUpperCase().startsWith(prefix)) return prefix + k.slice(prefix.length);
|
|
5743
|
+
return prefix + k;
|
|
5744
|
+
}
|
|
5745
|
+
function projectNameForRepoPath(repoPath) {
|
|
5746
|
+
const matched = matchRegisteredWorkspace(repoPath);
|
|
5747
|
+
if (!matched) return null;
|
|
5748
|
+
const found = listWorkspaces().find((w) => w.path.replace(/\/+$/, "") === matched);
|
|
5749
|
+
return found?.name?.trim() || null;
|
|
5750
|
+
}
|
|
5751
|
+
function projectPrefixes() {
|
|
5752
|
+
return listWorkspaces().map((w) => ({ name: w.name.trim(), prefix: projectEnvPrefix(w.name) })).filter((p) => p.name && p.prefix !== "PROJECT_").sort((a, b) => b.prefix.length - a.prefix.length);
|
|
5753
|
+
}
|
|
5754
|
+
function classifyEnvKey(key) {
|
|
5755
|
+
const hit = projectPrefixes().find((p) => key.startsWith(p.prefix));
|
|
5756
|
+
if (!hit) return { scope: "account" };
|
|
5757
|
+
return { scope: "project", project: hit.name, prefix: hit.prefix };
|
|
5758
|
+
}
|
|
5759
|
+
function listEnvKeys() {
|
|
5760
|
+
return Object.keys(loadAppSettings().environment).sort((a, b) => a.localeCompare(b)).map((key) => ({ key, ...classifyEnvKey(key) }));
|
|
5761
|
+
}
|
|
5762
|
+
function setEnvVar(input) {
|
|
5763
|
+
const value = input.value;
|
|
5764
|
+
if (input.scope === "account") {
|
|
5765
|
+
const key2 = assertEnvKey(input.key);
|
|
5766
|
+
if (value.trim() === "") {
|
|
5767
|
+
updateAppEnvironment({ [key2]: null });
|
|
5768
|
+
return { key: key2, scope: "account" };
|
|
5769
|
+
}
|
|
5770
|
+
updateAppEnvironment({ [key2]: value });
|
|
5771
|
+
return { key: key2, scope: "account" };
|
|
5772
|
+
}
|
|
5773
|
+
const project = input.projectName?.trim();
|
|
5774
|
+
if (!project) throw new Error("Project name required for scope=project.");
|
|
5775
|
+
const prefix = projectEnvPrefix(project);
|
|
5776
|
+
const key = qualifyProjectEnvKey(prefix, input.key);
|
|
5777
|
+
if (value.trim() === "") {
|
|
5778
|
+
updateAppEnvironment({ [key]: null });
|
|
5779
|
+
} else {
|
|
5780
|
+
updateAppEnvironment({ [key]: value });
|
|
5781
|
+
}
|
|
5782
|
+
return { key, scope: "project", prefix, project };
|
|
5783
|
+
}
|
|
5784
|
+
function deleteEnvVar(input) {
|
|
5785
|
+
const current = loadAppSettings().environment;
|
|
5786
|
+
if (input.scope === "account") {
|
|
5787
|
+
const key2 = assertEnvKey(input.key);
|
|
5788
|
+
const deleted2 = Object.prototype.hasOwnProperty.call(current, key2);
|
|
5789
|
+
updateAppEnvironment({ [key2]: null });
|
|
5790
|
+
return { key: key2, scope: "account", deleted: deleted2 };
|
|
5791
|
+
}
|
|
5792
|
+
const project = input.projectName?.trim();
|
|
5793
|
+
if (!project) throw new Error("Project name required for scope=project.");
|
|
5794
|
+
const prefix = projectEnvPrefix(project);
|
|
5795
|
+
const key = qualifyProjectEnvKey(prefix, input.key);
|
|
5796
|
+
const deleted = Object.prototype.hasOwnProperty.call(current, key);
|
|
5797
|
+
updateAppEnvironment({ [key]: null });
|
|
5798
|
+
return { key, scope: "project", deleted, prefix, project };
|
|
5799
|
+
}
|
|
5800
|
+
function formatEnvDirective(projectName) {
|
|
5801
|
+
if (!projectName) {
|
|
5802
|
+
return [
|
|
5803
|
+
"Sideboard environment (Settings \u2192 Environment): list_env, set_env, delete_env.",
|
|
5804
|
+
"scope=project prefixes the key with the registered project name (uppercased, then _). APPLE_ID for a project named sideboard is stored as SIDEBOARD_APPLE_ID.",
|
|
5805
|
+
"scope=account is shared by every project and is not prefixed.",
|
|
5806
|
+
"list_env returns names only. A value is injected into later agent turns."
|
|
5807
|
+
].join(" ");
|
|
5808
|
+
}
|
|
5809
|
+
const prefix = projectEnvPrefix(projectName);
|
|
5810
|
+
return [
|
|
5811
|
+
"Sideboard environment (Settings \u2192 Environment): list_env, set_env, delete_env.",
|
|
5812
|
+
`This project's prefix is ${prefix}. Project secrets must use that prefix \u2014 set_env scope=project applies it (APPLE_ID becomes ${prefix}APPLE_ID).`,
|
|
5813
|
+
"scope=account is only for variables shared by every project.",
|
|
5814
|
+
"list_env returns names only. A value set here is injected into later agent turns."
|
|
5815
|
+
].join(" ");
|
|
5816
|
+
}
|
|
5817
|
+
function formatEnvReminder(projectName) {
|
|
5818
|
+
if (!projectName) {
|
|
5819
|
+
return "Sideboard env: list_env / set_env / delete_env. scope=project prefixes the key with the project name (SIDEBOARD_APPLE_ID). scope=account is shared by every project.";
|
|
5820
|
+
}
|
|
5821
|
+
const prefix = projectEnvPrefix(projectName);
|
|
5822
|
+
return `Sideboard env: list_env / set_env / delete_env. This project's prefix is ${prefix}. scope=project stores ${prefix}<NAME>. scope=account is shared by every project.`;
|
|
5230
5823
|
}
|
|
5231
5824
|
|
|
5232
5825
|
// src/threads/sync-branch.ts
|
|
@@ -5250,9 +5843,9 @@ async function syncThreadBranchFromGit(threadId) {
|
|
|
5250
5843
|
}
|
|
5251
5844
|
|
|
5252
5845
|
// src/store/schedules.ts
|
|
5253
|
-
import { randomUUID as
|
|
5846
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
5254
5847
|
import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
|
|
5255
|
-
import { join as
|
|
5848
|
+
import { join as join13 } from "path";
|
|
5256
5849
|
import { Cron } from "croner";
|
|
5257
5850
|
var DURATION_RE = /^(\d+)(s|m|h|d)$/i;
|
|
5258
5851
|
var UNIT_MS = {
|
|
@@ -5262,7 +5855,7 @@ var UNIT_MS = {
|
|
|
5262
5855
|
d: 864e5
|
|
5263
5856
|
};
|
|
5264
5857
|
function schedulesPath() {
|
|
5265
|
-
return
|
|
5858
|
+
return join13(appDataDir(), "schedules.json");
|
|
5266
5859
|
}
|
|
5267
5860
|
function parseDurationMs(every) {
|
|
5268
5861
|
const m = every.trim().match(DURATION_RE);
|
|
@@ -5389,7 +5982,7 @@ function buildTask(input, now) {
|
|
|
5389
5982
|
const ts = nowIso(now);
|
|
5390
5983
|
const agent = input.agent ? coerceOrchestratorAgent(input.agent) : null;
|
|
5391
5984
|
return {
|
|
5392
|
-
id:
|
|
5985
|
+
id: randomUUID3(),
|
|
5393
5986
|
name: input.name?.trim() || defaultScheduleName(prompt),
|
|
5394
5987
|
prompt,
|
|
5395
5988
|
enabled: input.enabled !== false,
|
|
@@ -5500,7 +6093,7 @@ function formatScheduledPrompt(name, prompt) {
|
|
|
5500
6093
|
${prompt}`;
|
|
5501
6094
|
}
|
|
5502
6095
|
async function defaultDeps() {
|
|
5503
|
-
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-
|
|
6096
|
+
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-HWJHK5NL.js");
|
|
5504
6097
|
const orch = getOrchestrator2();
|
|
5505
6098
|
return {
|
|
5506
6099
|
findThread: (id) => findThreadByRef(id),
|
|
@@ -5848,6 +6441,10 @@ var Orchestrator = class {
|
|
|
5848
6441
|
}
|
|
5849
6442
|
continue;
|
|
5850
6443
|
}
|
|
6444
|
+
if (thisProcessShouldDrainAgentQueues() && isUnclaimedSteerRequest(thread.steerRequest) && thread.queue.length > 0) {
|
|
6445
|
+
this.finishSteer(thread.id, false);
|
|
6446
|
+
continue;
|
|
6447
|
+
}
|
|
5851
6448
|
if (thread.queue.length > 0) {
|
|
5852
6449
|
this.haltDrain.delete(thread.id);
|
|
5853
6450
|
this.armDrain(thread.id);
|
|
@@ -6232,8 +6829,12 @@ var Orchestrator = class {
|
|
|
6232
6829
|
async addWorkspace(repoPath) {
|
|
6233
6830
|
return addWorkspace(repoPath);
|
|
6234
6831
|
}
|
|
6235
|
-
removeWorkspace(repoPath) {
|
|
6832
|
+
async removeWorkspace(repoPath) {
|
|
6236
6833
|
removeWorkspace(repoPath);
|
|
6834
|
+
for (const thread of listThreads()) {
|
|
6835
|
+
if (!sameWorkspacePath(thread.repoPath, repoPath)) continue;
|
|
6836
|
+
await this.archive(thread.id).catch(() => void 0);
|
|
6837
|
+
}
|
|
6237
6838
|
}
|
|
6238
6839
|
async adopt(input) {
|
|
6239
6840
|
const thread = await adoptThread(input);
|
|
@@ -6306,11 +6907,11 @@ var Orchestrator = class {
|
|
|
6306
6907
|
return results;
|
|
6307
6908
|
}
|
|
6308
6909
|
/** Edit the text of a not-yet-started queued message. */
|
|
6309
|
-
async editQueuedMessage(threadRef, index,
|
|
6910
|
+
async editQueuedMessage(threadRef, index, text3) {
|
|
6310
6911
|
const thread = this.requireThread(threadRef);
|
|
6311
6912
|
return withThreadLock(thread.id, async () => {
|
|
6312
6913
|
const current = this.requireThread(thread.id);
|
|
6313
|
-
const trimmed =
|
|
6914
|
+
const trimmed = text3.trim();
|
|
6314
6915
|
if (!trimmed || index < 0 || index >= current.queue.length) {
|
|
6315
6916
|
return current;
|
|
6316
6917
|
}
|
|
@@ -6344,33 +6945,37 @@ var Orchestrator = class {
|
|
|
6344
6945
|
return next;
|
|
6345
6946
|
});
|
|
6346
6947
|
}
|
|
6347
|
-
/**
|
|
6348
|
-
* Promote a queued message to run next, interrupting the in-flight turn (if any).
|
|
6349
|
-
* The current turn is stopped without clearing the rest of the queue — drainQueue
|
|
6350
|
-
* picks the promoted message up as soon as the interrupted turn unwinds.
|
|
6351
|
-
*/
|
|
6948
|
+
/** Promote a queued prompt to run next, interrupting the in-flight turn. */
|
|
6352
6949
|
async sendQueuedMessageNow(threadRef, index) {
|
|
6353
6950
|
const thread = this.requireThread(threadRef);
|
|
6951
|
+
const handoff = !thisProcessShouldDrainAgentQueues();
|
|
6354
6952
|
const promoted = await withThreadLock(thread.id, async () => {
|
|
6355
|
-
const
|
|
6356
|
-
if (index < 0 || index >=
|
|
6357
|
-
const next = moveQueuedItemToFront(
|
|
6953
|
+
const current = this.requireThread(thread.id);
|
|
6954
|
+
if (index < 0 || index >= current.queue.length) return false;
|
|
6955
|
+
const next = moveQueuedItemToFront(current.queue, current.queueAttachments, index);
|
|
6358
6956
|
this.haltDrain.delete(thread.id);
|
|
6359
|
-
updateThread(thread.id,
|
|
6957
|
+
updateThread(thread.id, {
|
|
6958
|
+
...next,
|
|
6959
|
+
...handoff ? { steerRequest: nextSteerRequest(current.steerRequest) } : {}
|
|
6960
|
+
});
|
|
6360
6961
|
this.emit({ type: "queue_changed", threadId: thread.id, queue: next.queue });
|
|
6361
6962
|
return true;
|
|
6362
6963
|
});
|
|
6363
|
-
if (!promoted) return this.requireThread(thread.id);
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6964
|
+
if (!promoted || handoff) return this.requireThread(thread.id);
|
|
6965
|
+
return this.finishSteer(thread.id);
|
|
6966
|
+
}
|
|
6967
|
+
/** Desktop host: interrupt the live turn and start the front-of-queue prompt. */
|
|
6968
|
+
finishSteer(threadId, notifyParent = true) {
|
|
6969
|
+
claimSteerRequest(threadId);
|
|
6970
|
+
this.haltDrain.delete(threadId);
|
|
6971
|
+
const current = this.requireThread(threadId);
|
|
6972
|
+
const inFlight = this.activeTurns.has(threadId) || this.startingTurns.has(threadId);
|
|
6973
|
+
const pid = current.agentPid;
|
|
6974
|
+
if (inFlight || typeof pid === "number" && pid > 0 && isPidAlive(pid)) {
|
|
6975
|
+
this.stop(threadId, { clearQueue: false, continueQueue: true, notifyParent });
|
|
6371
6976
|
}
|
|
6372
|
-
this.armDrain(
|
|
6373
|
-
return this.requireThread(
|
|
6977
|
+
this.armDrain(threadId);
|
|
6978
|
+
return this.requireThread(threadId);
|
|
6374
6979
|
}
|
|
6375
6980
|
armDrain(threadId) {
|
|
6376
6981
|
void this.drainQueue(threadId).catch((err) => {
|
|
@@ -6501,7 +7106,7 @@ var Orchestrator = class {
|
|
|
6501
7106
|
orch: isOrchestratorThread(thread)
|
|
6502
7107
|
});
|
|
6503
7108
|
if (this.stoppedTurns.has(threadId)) {
|
|
6504
|
-
const stopped =
|
|
7109
|
+
const stopped = writeStoppedStatus(threadId);
|
|
6505
7110
|
if (stopped?.status === "stopped") {
|
|
6506
7111
|
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
6507
7112
|
}
|
|
@@ -6547,7 +7152,9 @@ var Orchestrator = class {
|
|
|
6547
7152
|
ticketId: issueTicket?.id,
|
|
6548
7153
|
ticketProvider: issueTicket?.provider
|
|
6549
7154
|
}) : null;
|
|
6550
|
-
const viewerContextReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatViewerContextReminder()
|
|
7155
|
+
const viewerContextReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? `${formatViewerContextReminder()}
|
|
7156
|
+
|
|
7157
|
+
${formatEnvReminder(projectNameForRepoPath(thread.repoPath || thread.worktreePath))}` : null;
|
|
6551
7158
|
const reviewWriteGateReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) && isReviewWriteGatedThread(
|
|
6552
7159
|
thread,
|
|
6553
7160
|
thread.worktreePath ? threadsSharingWorktree(thread.worktreePath) : []
|
|
@@ -6621,16 +7228,15 @@ var Orchestrator = class {
|
|
|
6621
7228
|
fresh.worktreePath ? threadsSharingWorktree(fresh.worktreePath) : []
|
|
6622
7229
|
) ? null : formatReviewWriteGateDirective();
|
|
6623
7230
|
let viewerContextDirective = null;
|
|
7231
|
+
let envDirective = null;
|
|
6624
7232
|
if (!isBrightsy && !isOrchestration) {
|
|
7233
|
+
const repo = fresh.repoPath || fresh.worktreePath;
|
|
6625
7234
|
try {
|
|
6626
|
-
viewerContextDirective = formatViewerContextDirective(
|
|
6627
|
-
resolveViewerProfileForRepo(fresh.repoPath || fresh.worktreePath)
|
|
6628
|
-
);
|
|
7235
|
+
viewerContextDirective = formatViewerContextDirective(resolveViewerProfileForRepo(repo));
|
|
6629
7236
|
} catch {
|
|
6630
|
-
viewerContextDirective = formatViewerContextDirective(
|
|
6631
|
-
resolveViewerProfileForRepo()
|
|
6632
|
-
);
|
|
7237
|
+
viewerContextDirective = formatViewerContextDirective(resolveViewerProfileForRepo());
|
|
6633
7238
|
}
|
|
7239
|
+
envDirective = formatEnvDirective(projectNameForRepoPath(repo));
|
|
6634
7240
|
}
|
|
6635
7241
|
const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
|
|
6636
7242
|
let renameBranchDirective = null;
|
|
@@ -6672,6 +7278,7 @@ var Orchestrator = class {
|
|
|
6672
7278
|
issueToolsDirective,
|
|
6673
7279
|
reviewWriteGateDirective,
|
|
6674
7280
|
viewerContextDirective,
|
|
7281
|
+
envDirective,
|
|
6675
7282
|
artifactDirective,
|
|
6676
7283
|
longRunningDirective,
|
|
6677
7284
|
renameBranchDirective,
|
|
@@ -6801,6 +7408,7 @@ var Orchestrator = class {
|
|
|
6801
7408
|
issueToolsDirective,
|
|
6802
7409
|
reviewWriteGateDirective,
|
|
6803
7410
|
viewerContextDirective,
|
|
7411
|
+
envDirective,
|
|
6804
7412
|
artifactDirective,
|
|
6805
7413
|
longRunningDirective,
|
|
6806
7414
|
renameBranchDirective,
|
|
@@ -6886,7 +7494,7 @@ var Orchestrator = class {
|
|
|
6886
7494
|
}
|
|
6887
7495
|
settleAgentBlock(threadId, parts, exitCode !== 0 && !this.stoppedTurns.has(threadId));
|
|
6888
7496
|
if (this.stoppedTurns.has(threadId)) {
|
|
6889
|
-
const stopped =
|
|
7497
|
+
const stopped = writeStoppedStatus(threadId);
|
|
6890
7498
|
if (stopped?.status === "stopped") {
|
|
6891
7499
|
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
6892
7500
|
}
|
|
@@ -6937,7 +7545,7 @@ var Orchestrator = class {
|
|
|
6937
7545
|
await syncThreadBranchFromGit(threadId).catch(() => void 0);
|
|
6938
7546
|
settleAgentBlock(threadId, void 0, !this.stoppedTurns.has(threadId));
|
|
6939
7547
|
if (this.stoppedTurns.has(threadId)) {
|
|
6940
|
-
const stopped =
|
|
7548
|
+
const stopped = writeStoppedStatus(threadId);
|
|
6941
7549
|
if (stopped?.status === "stopped") {
|
|
6942
7550
|
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
6943
7551
|
}
|
|
@@ -7018,7 +7626,7 @@ var Orchestrator = class {
|
|
|
7018
7626
|
} catch {
|
|
7019
7627
|
}
|
|
7020
7628
|
}
|
|
7021
|
-
const stopped =
|
|
7629
|
+
const stopped = writeStoppedStatus(thread.id, resolveStopReason(opts)) ?? readThread(thread.id) ?? thread;
|
|
7022
7630
|
if (stopped.status === "stopped") {
|
|
7023
7631
|
this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
|
|
7024
7632
|
if (inFlight && opts?.notifyParent !== false) {
|
|
@@ -7197,7 +7805,7 @@ var Orchestrator = class {
|
|
|
7197
7805
|
const request = sameIntent ? pending : {
|
|
7198
7806
|
op,
|
|
7199
7807
|
scriptName: scriptName ?? null,
|
|
7200
|
-
requestId:
|
|
7808
|
+
requestId: randomUUID4(),
|
|
7201
7809
|
requestedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7202
7810
|
};
|
|
7203
7811
|
if (!sameIntent) {
|
|
@@ -7642,7 +8250,7 @@ var Orchestrator = class {
|
|
|
7642
8250
|
const stillRunning = this.threadLooksLive(thread);
|
|
7643
8251
|
const rawText = (lastAgent?.text ?? "").trim();
|
|
7644
8252
|
const finishedText = (rawText && !isInternalAgentStatusText(rawText) ? rawText : "") || (thread.status === "error" || thread.status === "stopped" || thread.status === "broken" ? lastError ?? "" : "");
|
|
7645
|
-
const
|
|
8253
|
+
const text3 = stillRunning ? "" : finishedText;
|
|
7646
8254
|
const live = stillRunning ? readTurnLive(thread.id) : null;
|
|
7647
8255
|
const liveSummary = live?.summary && !isInternalAgentStatusText(live.summary) ? live.summary : null;
|
|
7648
8256
|
const queuedHint = stillRunning && thread.status === "queued" && !liveSummary ? "Queued \u2014 waiting for a concurrency slot" : null;
|
|
@@ -7653,7 +8261,7 @@ var Orchestrator = class {
|
|
|
7653
8261
|
});
|
|
7654
8262
|
const block = thread.agentBlock?.reason?.trim() ? thread.agentBlock : null;
|
|
7655
8263
|
return {
|
|
7656
|
-
text,
|
|
8264
|
+
text: text3,
|
|
7657
8265
|
status: thread.status,
|
|
7658
8266
|
taskState,
|
|
7659
8267
|
sessionId: thread.sessionId,
|
|
@@ -7728,12 +8336,12 @@ var Orchestrator = class {
|
|
|
7728
8336
|
return "missing";
|
|
7729
8337
|
}
|
|
7730
8338
|
}
|
|
7731
|
-
async readFile(threadRef, relativePath) {
|
|
8339
|
+
async readFile(threadRef, relativePath, tail) {
|
|
7732
8340
|
const thread = this.requireThread(threadRef);
|
|
7733
8341
|
if (relativePath.includes("..") || relativePath.startsWith("/")) {
|
|
7734
8342
|
throw new Error("Invalid path");
|
|
7735
8343
|
}
|
|
7736
|
-
return readWorktreeFile(thread.worktreePath, relativePath);
|
|
8344
|
+
return (tail ? readWorktreeFileTail : readWorktreeFile)(thread.worktreePath, relativePath);
|
|
7737
8345
|
}
|
|
7738
8346
|
async readFileForUpload(threadRef, relativePath) {
|
|
7739
8347
|
const thread = this.requireThread(threadRef);
|
|
@@ -8028,7 +8636,7 @@ var Orchestrator = class {
|
|
|
8028
8636
|
return tab;
|
|
8029
8637
|
}
|
|
8030
8638
|
/**
|
|
8031
|
-
* Desktop git buttons + MCP `ask_git`. Always
|
|
8639
|
+
* Desktop git buttons + MCP `ask_git`. Always steers the worktree agent
|
|
8032
8640
|
* with the action prompt (same path as Resolve). Repository `[prompts]`
|
|
8033
8641
|
* overrides apply when set.
|
|
8034
8642
|
*/
|
|
@@ -8112,6 +8720,9 @@ var Orchestrator = class {
|
|
|
8112
8720
|
if (!next) throw new Error("Title cannot be empty");
|
|
8113
8721
|
return updateThread(thread.id, { title: next, userSetTitle: true });
|
|
8114
8722
|
}
|
|
8723
|
+
setWorkspaceTags(threadRef, tags) {
|
|
8724
|
+
return applyWorkspaceTags(threadRef, tags, "replace");
|
|
8725
|
+
}
|
|
8115
8726
|
async switchThreadBranch(threadRef, branchName) {
|
|
8116
8727
|
const thread = this.requireThread(threadRef);
|
|
8117
8728
|
this.assertNotGlobal(thread, "Switch branch");
|
|
@@ -8193,11 +8804,7 @@ var Orchestrator = class {
|
|
|
8193
8804
|
this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
|
|
8194
8805
|
this.enforceHistoryRetention();
|
|
8195
8806
|
if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
|
|
8196
|
-
|
|
8197
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-WU2RNLDV.js");
|
|
8198
|
-
await ensureWorkspace2(thread.repoPath);
|
|
8199
|
-
} catch {
|
|
8200
|
-
}
|
|
8807
|
+
await ensureWorkspace(thread.repoPath).catch(() => void 0);
|
|
8201
8808
|
}
|
|
8202
8809
|
return archived;
|
|
8203
8810
|
}
|
|
@@ -8248,16 +8855,12 @@ var Orchestrator = class {
|
|
|
8248
8855
|
`Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
|
|
8249
8856
|
);
|
|
8250
8857
|
}
|
|
8251
|
-
const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-L57T3KH7.js");
|
|
8252
|
-
const slug = thread.worktreePath.split("/").pop();
|
|
8253
8858
|
const dest = thread.worktreePath;
|
|
8254
8859
|
await withRepoGitLock(thread.repoPath, async () => {
|
|
8255
8860
|
await git(["worktree", "add", dest, thread.branchName], thread.repoPath);
|
|
8256
8861
|
});
|
|
8257
8862
|
const { ensureWorktreeSideboardIgnored } = await import("./worktree-exclude-5N2KTYIT.js");
|
|
8258
8863
|
await ensureWorktreeSideboardIgnored(dest);
|
|
8259
|
-
void createThreadWorktree2;
|
|
8260
|
-
void slug;
|
|
8261
8864
|
}
|
|
8262
8865
|
const restorePatch = {};
|
|
8263
8866
|
let alreadyMerged = normalizePrState(thread.prState) === "MERGED";
|
|
@@ -8281,6 +8884,9 @@ var Orchestrator = class {
|
|
|
8281
8884
|
if (Object.keys(restorePatch).length > 0) {
|
|
8282
8885
|
updateThread(thread.id, restorePatch);
|
|
8283
8886
|
}
|
|
8887
|
+
if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
|
|
8888
|
+
await addWorkspace(thread.repoPath).catch(() => void 0);
|
|
8889
|
+
}
|
|
8284
8890
|
const restored = setStatus(thread.id, "idle");
|
|
8285
8891
|
this.emit({ type: "status_changed", threadId: restored.id, status: restored.status });
|
|
8286
8892
|
return restored;
|
|
@@ -8358,7 +8964,7 @@ async function startOrchestration(opts) {
|
|
|
8358
8964
|
sourceRef: "default",
|
|
8359
8965
|
...createOpts
|
|
8360
8966
|
}).catch(async () => {
|
|
8361
|
-
const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-
|
|
8967
|
+
const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-NKLNVNXX.js");
|
|
8362
8968
|
const repo = await resolveRepoRoot2(repoPath);
|
|
8363
8969
|
const def = await resolveDefaultBranch2(repo);
|
|
8364
8970
|
return orch.createThread({
|
|
@@ -8368,7 +8974,7 @@ async function startOrchestration(opts) {
|
|
|
8368
8974
|
repoPath: repo
|
|
8369
8975
|
});
|
|
8370
8976
|
});
|
|
8371
|
-
const { updateThread: upd } = await import("./thread-store-
|
|
8977
|
+
const { updateThread: upd } = await import("./thread-store-7XBOUQ5K.js");
|
|
8372
8978
|
const updated = upd(thread.id, {
|
|
8373
8979
|
sourceType: "orchestration",
|
|
8374
8980
|
sourceRef: goal
|
|
@@ -8405,8 +9011,20 @@ export {
|
|
|
8405
9011
|
findBoardPr,
|
|
8406
9012
|
assembleHomeBoard,
|
|
8407
9013
|
HOME_BOARD_AGENT_HINT,
|
|
9014
|
+
workspaceTagTargetRef,
|
|
9015
|
+
applyWorkspaceTags,
|
|
8408
9016
|
readTurnLive,
|
|
8409
9017
|
localhostPreviewUrl,
|
|
9018
|
+
parseIssueSince,
|
|
9019
|
+
formatGitHubSearchUpdatedSince,
|
|
9020
|
+
issueMatchesUpdatedSince,
|
|
9021
|
+
previewIssueCommentBody,
|
|
9022
|
+
clampMcpIssueLimit,
|
|
9023
|
+
applyIssueListWindow,
|
|
9024
|
+
mcpIssueUpdatedSinceSchema,
|
|
9025
|
+
formatListedIssuesForMcp,
|
|
9026
|
+
mcpJson,
|
|
9027
|
+
registerViewerContextTools,
|
|
8410
9028
|
resolveScheduleThreadId,
|
|
8411
9029
|
listSchedules,
|
|
8412
9030
|
scheduleNeedsCaffeinate,
|