@sideboard-ai/core 0.1.280 → 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.
Files changed (52) hide show
  1. package/dist/{abletime-VCVPSLSU.js → abletime-WLDBJJUI.js} +3 -3
  2. package/dist/{abletime-VRATUMDO.js → abletime-X64E3GUJ.js} +3 -3
  3. package/dist/{abletime-oauth-4K7MICAD.js → abletime-oauth-AXR676W5.js} +2 -2
  4. package/dist/{abletime-oauth-4C4G2J3R.js → abletime-oauth-UWOCEICP.js} +2 -2
  5. package/dist/{abletime-rest-PIBCNHF3.js → abletime-rest-LPUDJUEP.js} +2 -2
  6. package/dist/{abletime-rest-6N63ZVQ3.js → abletime-rest-S4XWEXV2.js} +2 -2
  7. package/dist/{agents-DYCFLDNL.js → agents-2IE4HKHA.js} +5 -5
  8. package/dist/{agents-RXP5NZBC.js → agents-XA2E64AW.js} +5 -5
  9. package/dist/{chunk-DK4DKQ2H.js → chunk-2KNO2GJQ.js} +722 -139
  10. package/dist/{chunk-TZ5JHIYK.js → chunk-2SJXQ4UX.js} +68 -21
  11. package/dist/{chunk-K7MWJLR7.js → chunk-5MGLNVNK.js} +6 -906
  12. package/dist/{chunk-RK5PIWCW.js → chunk-ADXVTKLC.js} +12 -4
  13. package/dist/{chunk-Y7JPL4WA.js → chunk-BDAUPNHL.js} +3 -4
  14. package/dist/{chunk-R7HLXD4I.js → chunk-BS3QLVPL.js} +93 -57
  15. package/dist/{chunk-2P75E243.js → chunk-BYZ7F2BQ.js} +707 -128
  16. package/dist/{chunk-2J34G3WJ.js → chunk-DGPROCE5.js} +93 -57
  17. package/dist/{chunk-HQXBBUQX.js → chunk-DQZPF6WL.js} +12 -11
  18. package/dist/{chunk-YDXNCBA2.js → chunk-EIEAR62M.js} +12 -11
  19. package/dist/{chunk-XKZAVGTT.js → chunk-I6HSGKMZ.js} +1 -1
  20. package/dist/chunk-JPH7PYSK.js +1241 -0
  21. package/dist/{chunk-IPPY3FG5.js → chunk-K5XSTHHP.js} +6 -906
  22. package/dist/chunk-N5UGX3JL.js +1239 -0
  23. package/dist/{chunk-HNNS25SG.js → chunk-OCUIZD4Y.js} +14 -4
  24. package/dist/{chunk-IJDQKC5A.js → chunk-PY4UCMRI.js} +4 -4
  25. package/dist/{chunk-NQ7JMVSU.js → chunk-U7HAVAGJ.js} +1 -1
  26. package/dist/{chunk-PZURG3XN.js → chunk-VA55JG2I.js} +4 -4
  27. package/dist/{chunk-CPA43FBK.js → chunk-W7HIK2A6.js} +3 -4
  28. package/dist/{chunk-SKSOCIJT.js → chunk-WY2IAB7J.js} +68 -21
  29. package/dist/{chunk-QINJKNLR.js → chunk-XTPQEZZA.js} +2 -2
  30. package/dist/{conductor-J6WDOXWM.js → conductor-CNSCOC54.js} +5 -5
  31. package/dist/{conductor-OW6NAK3N.js → conductor-U776JSXQ.js} +5 -5
  32. package/dist/{coordinator-prompt-Z2D32HWK.js → coordinator-prompt-4DJIHSE6.js} +3 -3
  33. package/dist/{coordinator-prompt-ERTHREGJ.js → coordinator-prompt-SVVGU6AZ.js} +3 -3
  34. package/dist/{global-workspace-5PJXO45Z.js → global-workspace-UPUDWIS6.js} +3 -3
  35. package/dist/{global-workspace-AK5PSPQ2.js → global-workspace-XON4YZXE.js} +3 -3
  36. package/dist/index.cjs +3690 -2912
  37. package/dist/index.d.cts +101 -28
  38. package/dist/index.d.ts +101 -28
  39. package/dist/index.js +1013 -1001
  40. package/dist/mcp/run-stdio.cjs +1975 -1259
  41. package/dist/mcp/run-stdio.js +743 -785
  42. package/dist/{orchestrator-XEKODYDQ.js → orchestrator-FPGVKEID.js} +7 -7
  43. package/dist/{orchestrator-K66OMTQO.js → orchestrator-HWJHK5NL.js} +7 -7
  44. package/dist/{thread-store-B3ZM4QEZ.js → thread-store-7XBOUQ5K.js} +1 -1
  45. package/dist/{thread-store-SHCUBWB3.js → thread-store-ZQZDREVH.js} +1 -1
  46. package/dist/{workspaces-UP6DQ3WL.js → workspaces-7AJ5RZ3P.js} +7 -3
  47. package/dist/{workspaces-WU2RNLDV.js → workspaces-F76BWCSI.js} +7 -3
  48. package/dist/{worktree-L57T3KH7.js → worktree-NKLNVNXX.js} +20 -15
  49. package/dist/{worktree-Q6UJHG25.js → worktree-YTZC3UQN.js} +14 -9
  50. package/package.json +1 -1
  51. package/dist/chunk-FWNL62VA.js +0 -308
  52. package/dist/chunk-JANCPF7Z.js +0 -310
@@ -23,7 +23,7 @@ import {
23
23
  parseSessionQuotaResetAt,
24
24
  planModeTurnInstruction,
25
25
  resolveQuotaFallbackAgent
26
- } from "./chunk-RK5PIWCW.js";
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-YDXNCBA2.js";
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-CPA43FBK.js";
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-TZ5JHIYK.js";
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
- teamSlugFromName,
158
- ticketSlugForBranch,
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-JANCPF7Z.js";
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(text) {
244
- return text.startsWith("Slack reply from ") && text.includes("not a command");
251
+ function isSlackExternalReplyText(text3) {
252
+ return text3.startsWith("Slack reply from ") && text3.includes("not a command");
245
253
  }
246
- function isInjectedNoticeText(text) {
247
- return isSlackExternalReplyText(text);
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 randomUUID3 } from "crypto";
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(text) {
329
- return text.trim().length >= REPLAY_MIN_CHARS;
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-5PJXO45Z.js");
500
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-UPUDWIS6.js");
493
501
  if (isGlobalThread2(thread)) {
494
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-ERTHREGJ.js");
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 text = thread.agent === "brightsy" ? stripBrightsyNdjsonNoise(rawText) : rawText;
637
- const exitCode = thread.agent === "brightsy" ? adjustBrightsyTurnExit(result.exitCode ?? null, text) : 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: text, parts: finalized, usage };
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 text = nl >= 0 && nl < cut.length - 1 ? cut.slice(nl + 1) : cut;
1070
- return { output: text, truncated: true, outputChars };
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 ensureWorkspace(repoPath2);
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 ensureWorkspace(repoPath);
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 text = raw.replace(/\s+/g, " ").trim();
1490
- if (text.length <= MAX_NOTIFY_ORCHESTRATOR_MESSAGE_CHARS) return text;
1491
- return `${text.slice(0, MAX_NOTIFY_ORCHESTRATOR_MESSAGE_CHARS - 1)}\u2026`;
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 text = raw.replace(/\s+/g, " ").trim();
1584
- if (!text) return "Waiting on a decision";
1585
- if (text.length <= MAX_AGENT_BLOCK_REASON) return text;
1586
- return `${text.slice(0, MAX_AGENT_BLOCK_REASON - 1)}\u2026`;
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
- const thread = readThread(existing.id) ?? existing;
2322
- if (!input.attachments?.length) return thread;
2323
- return updateThread(thread.id, {
2324
- attachments: persistCreateAttachments(thread.worktreePath, [
2325
- ...thread.attachments,
2326
- ...input.attachments
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-VCVPSLSU.js");
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 text = lastText(parts, "text");
2598
- const excerptRaw = thinking || text;
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 text = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
2839
- return text ? `- ${role}: ${text}` : null;
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: randomUUID(),
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 text;
3071
+ let text3;
2968
3072
  try {
2969
- text = readFileSync5(agentsFile, "utf8");
3073
+ text3 = readFileSync5(agentsFile, "utf8");
2970
3074
  } catch {
2971
3075
  continue;
2972
3076
  }
2973
- for (const line of text.split("\n")) {
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 { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-WU2RNLDV.js");
3013
- await ensureWorkspace2(repoPath);
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 z = emptyStat();
3843
+ const z4 = emptyStat();
3740
3844
  return {
3741
- commits: z,
3742
- uncommitted: z,
3743
- staged: z,
3744
- unstaged: z,
3745
- last_turn: z
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) {
@@ -5041,12 +5145,15 @@ function formatWorktreeDirective(thread, opts) {
5041
5145
  lines.push(
5042
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."
5043
5147
  );
5148
+ lines.push(
5149
+ "`set_workspace_tags` labels this checkout (omit ref; replace, add, or remove). Not another checkout."
5150
+ );
5044
5151
  lines.push("");
5045
5152
  lines.push(formatProcessGuideDirective({ worktreePath: thread.worktreePath }));
5046
5153
  return lines.join("\n");
5047
5154
  }
5048
5155
  function formatWorktreeReminder() {
5049
- 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).";
5050
5157
  }
5051
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;
5052
5159
  function mentionsPrGoal(prompt) {
@@ -5075,17 +5182,17 @@ function formatIssueToolsDirective(opts) {
5075
5182
  ];
5076
5183
  if (opts.linear) {
5077
5184
  lines.push(
5078
- "- 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."
5079
5186
  );
5080
5187
  }
5081
5188
  if (github) {
5082
5189
  lines.push(
5083
- "- 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`."
5084
5191
  );
5085
5192
  }
5086
5193
  if (opts.abletime) {
5087
5194
  lines.push(
5088
- "- 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)."
5089
5196
  );
5090
5197
  }
5091
5198
  lines.push(
@@ -5240,6 +5347,9 @@ function formatOptionalServicesDirective(integrations) {
5240
5347
  );
5241
5348
  }
5242
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
+ );
5243
5353
  lines.push(
5244
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."
5245
5355
  );
@@ -5265,7 +5375,451 @@ function formatOptionalServicesDirective(integrations) {
5265
5375
  }
5266
5376
  function formatOptionalServicesReminder(integrations) {
5267
5377
  if (connectedOptionalServices(integrations).length === 0) return null;
5268
- 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.`;
5269
5823
  }
5270
5824
 
5271
5825
  // src/threads/sync-branch.ts
@@ -5289,7 +5843,7 @@ async function syncThreadBranchFromGit(threadId) {
5289
5843
  }
5290
5844
 
5291
5845
  // src/store/schedules.ts
5292
- import { randomUUID as randomUUID2 } from "crypto";
5846
+ import { randomUUID as randomUUID3 } from "crypto";
5293
5847
  import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
5294
5848
  import { join as join13 } from "path";
5295
5849
  import { Cron } from "croner";
@@ -5428,7 +5982,7 @@ function buildTask(input, now) {
5428
5982
  const ts = nowIso(now);
5429
5983
  const agent = input.agent ? coerceOrchestratorAgent(input.agent) : null;
5430
5984
  return {
5431
- id: randomUUID2(),
5985
+ id: randomUUID3(),
5432
5986
  name: input.name?.trim() || defaultScheduleName(prompt),
5433
5987
  prompt,
5434
5988
  enabled: input.enabled !== false,
@@ -5539,7 +6093,7 @@ function formatScheduledPrompt(name, prompt) {
5539
6093
  ${prompt}`;
5540
6094
  }
5541
6095
  async function defaultDeps() {
5542
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-K66OMTQO.js");
6096
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-HWJHK5NL.js");
5543
6097
  const orch = getOrchestrator2();
5544
6098
  return {
5545
6099
  findThread: (id) => findThreadByRef(id),
@@ -5887,6 +6441,10 @@ var Orchestrator = class {
5887
6441
  }
5888
6442
  continue;
5889
6443
  }
6444
+ if (thisProcessShouldDrainAgentQueues() && isUnclaimedSteerRequest(thread.steerRequest) && thread.queue.length > 0) {
6445
+ this.finishSteer(thread.id, false);
6446
+ continue;
6447
+ }
5890
6448
  if (thread.queue.length > 0) {
5891
6449
  this.haltDrain.delete(thread.id);
5892
6450
  this.armDrain(thread.id);
@@ -6271,8 +6829,12 @@ var Orchestrator = class {
6271
6829
  async addWorkspace(repoPath) {
6272
6830
  return addWorkspace(repoPath);
6273
6831
  }
6274
- removeWorkspace(repoPath) {
6832
+ async removeWorkspace(repoPath) {
6275
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
+ }
6276
6838
  }
6277
6839
  async adopt(input) {
6278
6840
  const thread = await adoptThread(input);
@@ -6345,11 +6907,11 @@ var Orchestrator = class {
6345
6907
  return results;
6346
6908
  }
6347
6909
  /** Edit the text of a not-yet-started queued message. */
6348
- async editQueuedMessage(threadRef, index, text) {
6910
+ async editQueuedMessage(threadRef, index, text3) {
6349
6911
  const thread = this.requireThread(threadRef);
6350
6912
  return withThreadLock(thread.id, async () => {
6351
6913
  const current = this.requireThread(thread.id);
6352
- const trimmed = text.trim();
6914
+ const trimmed = text3.trim();
6353
6915
  if (!trimmed || index < 0 || index >= current.queue.length) {
6354
6916
  return current;
6355
6917
  }
@@ -6383,33 +6945,37 @@ var Orchestrator = class {
6383
6945
  return next;
6384
6946
  });
6385
6947
  }
6386
- /**
6387
- * Promote a queued message to run next, interrupting the in-flight turn (if any).
6388
- * The current turn is stopped without clearing the rest of the queue — drainQueue
6389
- * picks the promoted message up as soon as the interrupted turn unwinds.
6390
- */
6948
+ /** Promote a queued prompt to run next, interrupting the in-flight turn. */
6391
6949
  async sendQueuedMessageNow(threadRef, index) {
6392
6950
  const thread = this.requireThread(threadRef);
6951
+ const handoff = !thisProcessShouldDrainAgentQueues();
6393
6952
  const promoted = await withThreadLock(thread.id, async () => {
6394
- const current2 = this.requireThread(thread.id);
6395
- if (index < 0 || index >= current2.queue.length) return false;
6396
- const next = moveQueuedItemToFront(current2.queue, current2.queueAttachments, index);
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);
6397
6956
  this.haltDrain.delete(thread.id);
6398
- updateThread(thread.id, next);
6957
+ updateThread(thread.id, {
6958
+ ...next,
6959
+ ...handoff ? { steerRequest: nextSteerRequest(current.steerRequest) } : {}
6960
+ });
6399
6961
  this.emit({ type: "queue_changed", threadId: thread.id, queue: next.queue });
6400
6962
  return true;
6401
6963
  });
6402
- if (!promoted) return this.requireThread(thread.id);
6403
- if (!thisProcessShouldDrainAgentQueues()) return this.requireThread(thread.id);
6404
- const current = this.requireThread(thread.id);
6405
- const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id);
6406
- const livePid = current.agentPid;
6407
- const foreignLive = !inFlight && typeof livePid === "number" && livePid > 0 && isPidAlive(livePid);
6408
- if (inFlight || foreignLive) {
6409
- this.stop(thread.id, { clearQueue: false, continueQueue: true });
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 });
6410
6976
  }
6411
- this.armDrain(thread.id);
6412
- return this.requireThread(thread.id);
6977
+ this.armDrain(threadId);
6978
+ return this.requireThread(threadId);
6413
6979
  }
6414
6980
  armDrain(threadId) {
6415
6981
  void this.drainQueue(threadId).catch((err) => {
@@ -6540,7 +7106,7 @@ var Orchestrator = class {
6540
7106
  orch: isOrchestratorThread(thread)
6541
7107
  });
6542
7108
  if (this.stoppedTurns.has(threadId)) {
6543
- const stopped = writeLiveStatus(threadId, "stopped");
7109
+ const stopped = writeStoppedStatus(threadId);
6544
7110
  if (stopped?.status === "stopped") {
6545
7111
  this.emit({ type: "status_changed", threadId, status: "stopped" });
6546
7112
  }
@@ -6586,7 +7152,9 @@ var Orchestrator = class {
6586
7152
  ticketId: issueTicket?.id,
6587
7153
  ticketProvider: issueTicket?.provider
6588
7154
  }) : null;
6589
- const viewerContextReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatViewerContextReminder() : null;
7155
+ const viewerContextReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? `${formatViewerContextReminder()}
7156
+
7157
+ ${formatEnvReminder(projectNameForRepoPath(thread.repoPath || thread.worktreePath))}` : null;
6590
7158
  const reviewWriteGateReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) && isReviewWriteGatedThread(
6591
7159
  thread,
6592
7160
  thread.worktreePath ? threadsSharingWorktree(thread.worktreePath) : []
@@ -6660,16 +7228,15 @@ var Orchestrator = class {
6660
7228
  fresh.worktreePath ? threadsSharingWorktree(fresh.worktreePath) : []
6661
7229
  ) ? null : formatReviewWriteGateDirective();
6662
7230
  let viewerContextDirective = null;
7231
+ let envDirective = null;
6663
7232
  if (!isBrightsy && !isOrchestration) {
7233
+ const repo = fresh.repoPath || fresh.worktreePath;
6664
7234
  try {
6665
- viewerContextDirective = formatViewerContextDirective(
6666
- resolveViewerProfileForRepo(fresh.repoPath || fresh.worktreePath)
6667
- );
7235
+ viewerContextDirective = formatViewerContextDirective(resolveViewerProfileForRepo(repo));
6668
7236
  } catch {
6669
- viewerContextDirective = formatViewerContextDirective(
6670
- resolveViewerProfileForRepo()
6671
- );
7237
+ viewerContextDirective = formatViewerContextDirective(resolveViewerProfileForRepo());
6672
7238
  }
7239
+ envDirective = formatEnvDirective(projectNameForRepoPath(repo));
6673
7240
  }
6674
7241
  const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
6675
7242
  let renameBranchDirective = null;
@@ -6711,6 +7278,7 @@ var Orchestrator = class {
6711
7278
  issueToolsDirective,
6712
7279
  reviewWriteGateDirective,
6713
7280
  viewerContextDirective,
7281
+ envDirective,
6714
7282
  artifactDirective,
6715
7283
  longRunningDirective,
6716
7284
  renameBranchDirective,
@@ -6840,6 +7408,7 @@ var Orchestrator = class {
6840
7408
  issueToolsDirective,
6841
7409
  reviewWriteGateDirective,
6842
7410
  viewerContextDirective,
7411
+ envDirective,
6843
7412
  artifactDirective,
6844
7413
  longRunningDirective,
6845
7414
  renameBranchDirective,
@@ -6925,7 +7494,7 @@ var Orchestrator = class {
6925
7494
  }
6926
7495
  settleAgentBlock(threadId, parts, exitCode !== 0 && !this.stoppedTurns.has(threadId));
6927
7496
  if (this.stoppedTurns.has(threadId)) {
6928
- const stopped = writeLiveStatus(threadId, "stopped");
7497
+ const stopped = writeStoppedStatus(threadId);
6929
7498
  if (stopped?.status === "stopped") {
6930
7499
  this.emit({ type: "status_changed", threadId, status: "stopped" });
6931
7500
  }
@@ -6976,7 +7545,7 @@ var Orchestrator = class {
6976
7545
  await syncThreadBranchFromGit(threadId).catch(() => void 0);
6977
7546
  settleAgentBlock(threadId, void 0, !this.stoppedTurns.has(threadId));
6978
7547
  if (this.stoppedTurns.has(threadId)) {
6979
- const stopped = writeLiveStatus(threadId, "stopped");
7548
+ const stopped = writeStoppedStatus(threadId);
6980
7549
  if (stopped?.status === "stopped") {
6981
7550
  this.emit({ type: "status_changed", threadId, status: "stopped" });
6982
7551
  }
@@ -7057,7 +7626,7 @@ var Orchestrator = class {
7057
7626
  } catch {
7058
7627
  }
7059
7628
  }
7060
- const stopped = writeLiveStatus(thread.id, "stopped") ?? readThread(thread.id) ?? thread;
7629
+ const stopped = writeStoppedStatus(thread.id, resolveStopReason(opts)) ?? readThread(thread.id) ?? thread;
7061
7630
  if (stopped.status === "stopped") {
7062
7631
  this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
7063
7632
  if (inFlight && opts?.notifyParent !== false) {
@@ -7236,7 +7805,7 @@ var Orchestrator = class {
7236
7805
  const request = sameIntent ? pending : {
7237
7806
  op,
7238
7807
  scriptName: scriptName ?? null,
7239
- requestId: randomUUID3(),
7808
+ requestId: randomUUID4(),
7240
7809
  requestedAt: (/* @__PURE__ */ new Date()).toISOString()
7241
7810
  };
7242
7811
  if (!sameIntent) {
@@ -7681,7 +8250,7 @@ var Orchestrator = class {
7681
8250
  const stillRunning = this.threadLooksLive(thread);
7682
8251
  const rawText = (lastAgent?.text ?? "").trim();
7683
8252
  const finishedText = (rawText && !isInternalAgentStatusText(rawText) ? rawText : "") || (thread.status === "error" || thread.status === "stopped" || thread.status === "broken" ? lastError ?? "" : "");
7684
- const text = stillRunning ? "" : finishedText;
8253
+ const text3 = stillRunning ? "" : finishedText;
7685
8254
  const live = stillRunning ? readTurnLive(thread.id) : null;
7686
8255
  const liveSummary = live?.summary && !isInternalAgentStatusText(live.summary) ? live.summary : null;
7687
8256
  const queuedHint = stillRunning && thread.status === "queued" && !liveSummary ? "Queued \u2014 waiting for a concurrency slot" : null;
@@ -7692,7 +8261,7 @@ var Orchestrator = class {
7692
8261
  });
7693
8262
  const block = thread.agentBlock?.reason?.trim() ? thread.agentBlock : null;
7694
8263
  return {
7695
- text,
8264
+ text: text3,
7696
8265
  status: thread.status,
7697
8266
  taskState,
7698
8267
  sessionId: thread.sessionId,
@@ -8067,7 +8636,7 @@ var Orchestrator = class {
8067
8636
  return tab;
8068
8637
  }
8069
8638
  /**
8070
- * Desktop git buttons + MCP `ask_git`. Always queues the worktree agent
8639
+ * Desktop git buttons + MCP `ask_git`. Always steers the worktree agent
8071
8640
  * with the action prompt (same path as Resolve). Repository `[prompts]`
8072
8641
  * overrides apply when set.
8073
8642
  */
@@ -8151,6 +8720,9 @@ var Orchestrator = class {
8151
8720
  if (!next) throw new Error("Title cannot be empty");
8152
8721
  return updateThread(thread.id, { title: next, userSetTitle: true });
8153
8722
  }
8723
+ setWorkspaceTags(threadRef, tags) {
8724
+ return applyWorkspaceTags(threadRef, tags, "replace");
8725
+ }
8154
8726
  async switchThreadBranch(threadRef, branchName) {
8155
8727
  const thread = this.requireThread(threadRef);
8156
8728
  this.assertNotGlobal(thread, "Switch branch");
@@ -8232,11 +8804,7 @@ var Orchestrator = class {
8232
8804
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
8233
8805
  this.enforceHistoryRetention();
8234
8806
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
8235
- try {
8236
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-WU2RNLDV.js");
8237
- await ensureWorkspace2(thread.repoPath);
8238
- } catch {
8239
- }
8807
+ await ensureWorkspace(thread.repoPath).catch(() => void 0);
8240
8808
  }
8241
8809
  return archived;
8242
8810
  }
@@ -8287,16 +8855,12 @@ var Orchestrator = class {
8287
8855
  `Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
8288
8856
  );
8289
8857
  }
8290
- const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-L57T3KH7.js");
8291
- const slug = thread.worktreePath.split("/").pop();
8292
8858
  const dest = thread.worktreePath;
8293
8859
  await withRepoGitLock(thread.repoPath, async () => {
8294
8860
  await git(["worktree", "add", dest, thread.branchName], thread.repoPath);
8295
8861
  });
8296
8862
  const { ensureWorktreeSideboardIgnored } = await import("./worktree-exclude-5N2KTYIT.js");
8297
8863
  await ensureWorktreeSideboardIgnored(dest);
8298
- void createThreadWorktree2;
8299
- void slug;
8300
8864
  }
8301
8865
  const restorePatch = {};
8302
8866
  let alreadyMerged = normalizePrState(thread.prState) === "MERGED";
@@ -8320,6 +8884,9 @@ var Orchestrator = class {
8320
8884
  if (Object.keys(restorePatch).length > 0) {
8321
8885
  updateThread(thread.id, restorePatch);
8322
8886
  }
8887
+ if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
8888
+ await addWorkspace(thread.repoPath).catch(() => void 0);
8889
+ }
8323
8890
  const restored = setStatus(thread.id, "idle");
8324
8891
  this.emit({ type: "status_changed", threadId: restored.id, status: restored.status });
8325
8892
  return restored;
@@ -8397,7 +8964,7 @@ async function startOrchestration(opts) {
8397
8964
  sourceRef: "default",
8398
8965
  ...createOpts
8399
8966
  }).catch(async () => {
8400
- const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-L57T3KH7.js");
8967
+ const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-NKLNVNXX.js");
8401
8968
  const repo = await resolveRepoRoot2(repoPath);
8402
8969
  const def = await resolveDefaultBranch2(repo);
8403
8970
  return orch.createThread({
@@ -8407,7 +8974,7 @@ async function startOrchestration(opts) {
8407
8974
  repoPath: repo
8408
8975
  });
8409
8976
  });
8410
- const { updateThread: upd } = await import("./thread-store-B3ZM4QEZ.js");
8977
+ const { updateThread: upd } = await import("./thread-store-7XBOUQ5K.js");
8411
8978
  const updated = upd(thread.id, {
8412
8979
  sourceType: "orchestration",
8413
8980
  sourceRef: goal
@@ -8444,8 +9011,20 @@ export {
8444
9011
  findBoardPr,
8445
9012
  assembleHomeBoard,
8446
9013
  HOME_BOARD_AGENT_HINT,
9014
+ workspaceTagTargetRef,
9015
+ applyWorkspaceTags,
8447
9016
  readTurnLive,
8448
9017
  localhostPreviewUrl,
9018
+ parseIssueSince,
9019
+ formatGitHubSearchUpdatedSince,
9020
+ issueMatchesUpdatedSince,
9021
+ previewIssueCommentBody,
9022
+ clampMcpIssueLimit,
9023
+ applyIssueListWindow,
9024
+ mcpIssueUpdatedSinceSchema,
9025
+ formatListedIssuesForMcp,
9026
+ mcpJson,
9027
+ registerViewerContextTools,
8449
9028
  resolveScheduleThreadId,
8450
9029
  listSchedules,
8451
9030
  scheduleNeedsCaffeinate,