@sideboard-ai/core 0.1.257 → 0.1.261

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 (66) hide show
  1. package/dist/{abletime-IDQJI2KW.js → abletime-CDGINDXG.js} +6 -5
  2. package/dist/{abletime-OGQR4LIL.js → abletime-OQ4PC52L.js} +7 -6
  3. package/dist/{abletime-oauth-ZQUIY4EM.js → abletime-oauth-5A5EOH2A.js} +5 -4
  4. package/dist/{abletime-oauth-MBAG5MYU.js → abletime-oauth-KX2TDFTE.js} +6 -5
  5. package/dist/{abletime-rest-WEJERY4O.js → abletime-rest-PUGKYTRR.js} +6 -5
  6. package/dist/{abletime-rest-JZIIT5W3.js → abletime-rest-UEHOBLXD.js} +5 -4
  7. package/dist/agents/cursor-runner.cjs +274 -12
  8. package/dist/agents/cursor-runner.js +156 -13
  9. package/dist/{agents-HZP7YEKC.js → agents-IKVUGW5H.js} +12 -9
  10. package/dist/{agents-FSUI5MET.js → agents-J2P4KNEY.js} +21 -18
  11. package/dist/{app-settings-CLARFJUX.js → app-settings-OM7H64YM.js} +12 -3
  12. package/dist/{app-settings-ONYJ36XH.js → app-settings-VQEUCONM.js} +11 -2
  13. package/dist/chunk-3HO2OY5T.js +159 -0
  14. package/dist/{chunk-F7ANS66U.js → chunk-4F7J6UWD.js} +75 -33
  15. package/dist/{chunk-WDDUIGFK.js → chunk-4GK32AYX.js} +1 -1
  16. package/dist/{chunk-ZC3TVLY6.js → chunk-5OKJDCBK.js} +5 -5
  17. package/dist/{chunk-KJM3E3NF.js → chunk-6PFZ6XO7.js} +65 -6
  18. package/dist/{title-4A2ATYNY.js → chunk-7H655NEW.js} +1 -0
  19. package/dist/{chunk-R4DWUBOL.js → chunk-ABPNIYRS.js} +78 -37
  20. package/dist/{chunk-XIEDFRBP.js → chunk-B6XFBNYM.js} +2 -2
  21. package/dist/chunk-CEMNIGQH.js +161 -0
  22. package/dist/{chunk-66LAOKLK.js → chunk-D554YCHG.js} +2 -2
  23. package/dist/{chunk-HCNCZPDL.js → chunk-FSZ7YYFQ.js} +119 -63
  24. package/dist/{chunk-4723RAO3.js → chunk-FWNL62VA.js} +3 -1
  25. package/dist/{chunk-Y7CS2WGJ.js → chunk-GIZQAOHZ.js} +247 -109
  26. package/dist/chunk-HHEKVR6C.js +72 -0
  27. package/dist/{chunk-IFHHPPSL.js → chunk-J6ONZP74.js} +5 -5
  28. package/dist/{chunk-PJK24CN3.js → chunk-JTNY5CK4.js} +10 -10
  29. package/dist/{chunk-CPEIHTG7.js → chunk-KVQX62XI.js} +3 -3
  30. package/dist/{chunk-5F4YFGHL.js → chunk-LDXSAVWO.js} +7 -7
  31. package/dist/{title-4WAKWZRF.js → chunk-LX5R5JOR.js} +1 -0
  32. package/dist/{chunk-D6JWYJ4P.js → chunk-N2WW62LF.js} +1 -0
  33. package/dist/{chunk-RTQOCFIV.js → chunk-S7VDDJ34.js} +3 -3
  34. package/dist/{chunk-RJC2MKXN.js → chunk-TJ673EZT.js} +3 -1
  35. package/dist/{chunk-63YSFXLT.js → chunk-U3AOJLH3.js} +126 -70
  36. package/dist/{chunk-452Y24PW.js → chunk-VIC6TDYT.js} +68 -9
  37. package/dist/{chunk-553IEH7O.js → chunk-W5PWGEKS.js} +241 -104
  38. package/dist/{chunk-VVHHBOIA.js → chunk-WV7KJH2E.js} +5 -3
  39. package/dist/{chunk-BYRZS2S2.js → chunk-X2OTZ36G.js} +4 -4
  40. package/dist/{chunk-FGWUZOZN.js → chunk-YFTO7U2A.js} +3 -3
  41. package/dist/connected-teams-7CENZDWU.js +1 -1
  42. package/dist/{connected-teams-Z5GOIC5I.js → connected-teams-RU6VATCM.js} +2 -2
  43. package/dist/{coordinator-prompt-HZUWK3NP.js → coordinator-prompt-TU6656OK.js} +9 -8
  44. package/dist/{coordinator-prompt-Y6S6USNS.js → coordinator-prompt-VQHQ3RIX.js} +8 -7
  45. package/dist/{global-workspace-4JCITHB6.js → global-workspace-47NZVA44.js} +8 -7
  46. package/dist/{global-workspace-YA7U4M2N.js → global-workspace-PHF2MOGW.js} +9 -8
  47. package/dist/index.cjs +1443 -856
  48. package/dist/index.d.cts +118 -37
  49. package/dist/index.d.ts +118 -37
  50. package/dist/index.js +148 -64
  51. package/dist/managed-accounts-2AK5RIAF.js +39 -0
  52. package/dist/managed-accounts-4BLTJKAN.js +41 -0
  53. package/dist/mcp/run-stdio.cjs +2722 -2248
  54. package/dist/mcp/run-stdio.js +136 -50
  55. package/dist/{orchestrator-LWADJQTN.js → orchestrator-OCG4QVA5.js} +13 -11
  56. package/dist/{orchestrator-VHJZEVGA.js → orchestrator-VLTO5JBR.js} +13 -11
  57. package/dist/{plan-file-CKXUFTQ2.js → plan-file-VAHV5WBB.js} +2 -2
  58. package/dist/{thread-store-HDQ4CH54.js → thread-store-FJ5CDX2M.js} +1 -1
  59. package/dist/{thread-store-ZDR3Z5UG.js → thread-store-SHCUBWB3.js} +1 -1
  60. package/dist/title-ERD6D3C3.js +8 -0
  61. package/dist/title-Z7WAJN57.js +6 -0
  62. package/dist/{workspaces-IXZD7EC4.js → workspaces-2XTDS7N2.js} +9 -8
  63. package/dist/{workspaces-4KHEUPEX.js → workspaces-I7TWFTJM.js} +8 -7
  64. package/dist/{worktree-DAAF64UU.js → worktree-DDDF3WHV.js} +8 -7
  65. package/dist/{worktree-S3FOMEQ2.js → worktree-YASFE65H.js} +7 -6
  66. package/package.json +1 -1
@@ -1,8 +1,3 @@
1
- import {
2
- extractPresentedPlan,
3
- readPlanFile,
4
- writePlanFile
5
- } from "./chunk-PGPSIEMW.js";
6
1
  import {
7
2
  adjustBrightsyTurnExit,
8
3
  allAdapters,
@@ -25,7 +20,15 @@ import {
25
20
  resolveQuotaFallbackAgent,
26
21
  stripBrightsyNdjsonNoise,
27
22
  toolDescription
28
- } from "./chunk-R4DWUBOL.js";
23
+ } from "./chunk-ABPNIYRS.js";
24
+ import {
25
+ titleFromPrompt
26
+ } from "./chunk-7H655NEW.js";
27
+ import {
28
+ extractPresentedPlan,
29
+ readPlanFile,
30
+ writePlanFile
31
+ } from "./chunk-PGPSIEMW.js";
29
32
  import {
30
33
  SLACK_REPLY_FORMATTING,
31
34
  addWorkspace,
@@ -48,9 +51,10 @@ import {
48
51
  stageAbsolutePathsAsAttachments,
49
52
  stageBuffersAsAttachments,
50
53
  syncWorkspacesFromThreads
51
- } from "./chunk-BYRZS2S2.js";
54
+ } from "./chunk-X2OTZ36G.js";
52
55
  import {
53
56
  addPrStackLayer,
57
+ agentNicknameFromWorktreePath,
54
58
  allocateTeamName,
55
59
  allocateTeamSlug,
56
60
  canonicalizeRepoPath,
@@ -78,6 +82,7 @@ import {
78
82
  isDirty,
79
83
  isPlaceholderBranch,
80
84
  isSideboardScratchPath,
85
+ isWorktreeIdentityTitle,
81
86
  listWorktrees,
82
87
  markPrReady,
83
88
  mergeAgentGitAuthEnv,
@@ -93,19 +98,14 @@ import {
93
98
  resolveRepoRoot,
94
99
  switchWorktreeBranch,
95
100
  takenSlugsFromThread,
96
- threadDisplayLabel,
101
+ teamSlugFromName,
97
102
  ticketSlugForBranch,
98
103
  withRepoGitLock,
99
104
  worktreeDisplayLabelForGroup,
105
+ worktreeIdentityTitles,
100
106
  worktreeNameFromPath,
101
107
  worktreeSlugForTicket
102
- } from "./chunk-63YSFXLT.js";
103
- import {
104
- ATTACHMENTS_DIR,
105
- DETACHED_JOBS_DIR,
106
- LEGACY_ATTACHMENTS_DIR,
107
- LEGACY_DETACHED_JOBS_DIR
108
- } from "./chunk-6K64FJDD.js";
108
+ } from "./chunk-U3AOJLH3.js";
109
109
  import {
110
110
  ensureWorktreeSideboardIgnored
111
111
  } from "./chunk-2P4XKWV2.js";
@@ -131,7 +131,21 @@ import {
131
131
  shouldRetryFailedAgentTurn,
132
132
  summarizeTurnStderr,
133
133
  turnFailChatText
134
- } from "./chunk-VVHHBOIA.js";
134
+ } from "./chunk-WV7KJH2E.js";
135
+ import {
136
+ ATTACHMENTS_DIR,
137
+ DETACHED_JOBS_DIR,
138
+ LEGACY_ATTACHMENTS_DIR,
139
+ LEGACY_DETACHED_JOBS_DIR
140
+ } from "./chunk-6K64FJDD.js";
141
+ import {
142
+ clearStaleIndexLocks,
143
+ enrichPathWithNpmGlobalBin,
144
+ ensureAgentPath,
145
+ git,
146
+ resolveGitDirsForLockRecovery,
147
+ run
148
+ } from "./chunk-QVN2WOKJ.js";
135
149
  import {
136
150
  HISTORY_CLEANUP_INTERVAL_HOURS_DEFAULT,
137
151
  applyTurnUsage,
@@ -163,11 +177,20 @@ import {
163
177
  sumUsageList,
164
178
  updateAdvancedSettings,
165
179
  usageOnLimit
166
- } from "./chunk-KJM3E3NF.js";
180
+ } from "./chunk-6PFZ6XO7.js";
181
+ import {
182
+ stripNestedElectronEnv
183
+ } from "./chunk-4TR3HZFT.js";
167
184
  import {
168
185
  examplePrefixedBranch,
169
186
  sanitizeGitBranchPrefix
170
187
  } from "./chunk-WDRMZKVB.js";
188
+ import {
189
+ accountIdToPin,
190
+ applyManagedAccountEnv,
191
+ nextManagedAccount,
192
+ resolveAccountForThread
193
+ } from "./chunk-3HO2OY5T.js";
171
194
  import {
172
195
  releaseCaffeinateHoldForThread
173
196
  } from "./chunk-NOT4SEAM.js";
@@ -185,7 +208,7 @@ import {
185
208
  updateThread,
186
209
  withThreadLock,
187
210
  writeThread
188
- } from "./chunk-4723RAO3.js";
211
+ } from "./chunk-FWNL62VA.js";
189
212
  import {
190
213
  annotateDevAccessRuns,
191
214
  appDataDir,
@@ -200,17 +223,6 @@ import {
200
223
  workspaceSettingsSourceLabel,
201
224
  worktreesRoot
202
225
  } from "./chunk-EHT2DRT4.js";
203
- import {
204
- clearStaleIndexLocks,
205
- enrichPathWithNpmGlobalBin,
206
- ensureAgentPath,
207
- git,
208
- resolveGitDirsForLockRecovery,
209
- run
210
- } from "./chunk-QVN2WOKJ.js";
211
- import {
212
- stripNestedElectronEnv
213
- } from "./chunk-4TR3HZFT.js";
214
226
 
215
227
  // src/orchestrator/orchestrator.ts
216
228
  import { EventEmitter } from "events";
@@ -445,9 +457,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
445
457
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
446
458
  );
447
459
  }
448
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-4JCITHB6.js");
460
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-47NZVA44.js");
449
461
  if (isGlobalThread2(thread)) {
450
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-Y6S6USNS.js");
462
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-VQHQ3RIX.js");
451
463
  ensureGlobalCoordinatorCwd2(
452
464
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
453
465
  );
@@ -475,6 +487,17 @@ async function spawnAgentTurn(thread, input, onEvent) {
475
487
  );
476
488
  }
477
489
  const env = childEnvWithAppSettings(cmd.env);
490
+ const accounts = loadAppSettings().accounts;
491
+ const pin = accountIdToPin(thread, accounts);
492
+ if (pin !== void 0 && thread.accountId !== pin) {
493
+ try {
494
+ updateThread(thread.id, { accountId: pin });
495
+ thread = { ...thread, accountId: pin };
496
+ } catch {
497
+ thread = { ...thread, accountId: pin };
498
+ }
499
+ }
500
+ applyManagedAccountEnv(env, thread.agent, resolveAccountForThread(thread, accounts));
478
501
  sanitizeAgentHostEnv(env, cmd.cwd);
479
502
  ensureAgentPath(env);
480
503
  applyPromptCacheTtlEnv(thread.agent, env);
@@ -1420,6 +1443,7 @@ function createChatTab(input) {
1420
1443
  autonomy: input.autonomy ?? from.autonomy,
1421
1444
  attachments: input.attachments ?? [],
1422
1445
  status: "idle",
1446
+ ...input.accountId !== void 0 ? { accountId: input.accountId } : {},
1423
1447
  ...mergeWorktreeActiveRuns(threadsSharingWorktree(binding.worktreePath))
1424
1448
  });
1425
1449
  writeThread(thread);
@@ -1444,6 +1468,32 @@ function forkChatTab(input) {
1444
1468
  return tab;
1445
1469
  }
1446
1470
 
1471
+ // src/review/review-write-gate.ts
1472
+ var REVIEW_TAB_TITLE = "Review";
1473
+ function hasLiveReviewSibling(thread, siblings = []) {
1474
+ const selfId = thread.id;
1475
+ return siblings.some(
1476
+ (t) => t.id !== selfId && t.status !== "archived" && t.title.trim() === REVIEW_TAB_TITLE
1477
+ );
1478
+ }
1479
+ function isReviewWriteGatedThread(thread, siblings) {
1480
+ if (thread.sourceType === "pr" || thread.title.trim() === REVIEW_TAB_TITLE) {
1481
+ return true;
1482
+ }
1483
+ return hasLiveReviewSibling(thread, siblings);
1484
+ }
1485
+ function formatReviewWriteGateDirective() {
1486
+ return [
1487
+ "Review writes are public. The PR or ticket author sees GitHub/Linear/AbleTime comments, ticket updates, and submitted reviews immediately \u2014 a draft will confuse them.",
1488
+ "Write the recommendation in chat so the user can work through the feedback first. Do not call github_comment, linear_comment, abletime_comment, github_update_issue, linear_update_issue, abletime_update_task, or run `gh pr review` / `gh pr comment`, until they type a next step that asks you to post.",
1489
+ "After the review is in chat, stop. Do not call ask_user. The user needs time to read and will type the next steps. If they already asked you to post a specific comment or update in this turn, that is confirmation.",
1490
+ "Do not request reviewers, change labels, or assign yourself unless they asked \u2014 those also notify the author."
1491
+ ].join("\n");
1492
+ }
1493
+ function formatReviewWriteGateReminder() {
1494
+ return "Review writes: keep the review in chat. Do not ask_user after it \u2014 they read and type next steps. Do not github_comment / linear_comment / *_update_* / `gh pr review` on the PR or ticket until they ask. Authors see those immediately.";
1495
+ }
1496
+
1447
1497
  // src/review/review-request-template.ts
1448
1498
  var REVIEW_REQUEST_TEMPLATE = `# Review guidelines:
1449
1499
 
@@ -1714,6 +1764,22 @@ function buildReviewRequestAttachment(content, opts) {
1714
1764
  function readExistingReviewRequestFile(worktreePath) {
1715
1765
  return readTextIfPresent(join3(worktreePath, REVIEW_SKILL_PATH)) ?? readTextIfPresent(join3(worktreePath, CONTEXT_REVIEW_PATH)) ?? readTextIfPresent(join3(worktreePath, REPO_REVIEW_PATH)) ?? readTextIfPresent(join3(worktreePath, REVIEW_REQUEST_PATH)) ?? readTextIfPresent(join3(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
1716
1766
  }
1767
+ function canReuseIdleWorktreeAgentForReview(from, siblings) {
1768
+ const live = siblings.filter((t) => t.status !== "archived");
1769
+ if (live.length !== 1) return false;
1770
+ const only = live[0];
1771
+ if (only.id !== from.id) return false;
1772
+ if (from.status !== "idle") return false;
1773
+ if ((from.queue?.length ?? 0) > 0) return false;
1774
+ return (from.messages?.length ?? 0) === 0 && (only.messages?.length ?? 0) === 0;
1775
+ }
1776
+ function reviewGuidelinesAttachment(from) {
1777
+ const guidelines = resolveReviewGuidelines(from.worktreePath, from.repoPath);
1778
+ return buildReviewRequestAttachment(guidelines.content, {
1779
+ path: guidelines.path,
1780
+ name: guidelines.name
1781
+ });
1782
+ }
1717
1783
  async function requestReview(threadRef, send) {
1718
1784
  const from = findThreadByRef(threadRef);
1719
1785
  if (!from) throw new Error(`Thread not found: ${threadRef}`);
@@ -1725,16 +1791,21 @@ async function requestReview(threadRef, send) {
1725
1791
  if (from.status === "archived") {
1726
1792
  throw new Error(`Thread is archived: ${from.id}`);
1727
1793
  }
1728
- const guidelines = resolveReviewGuidelines(from.worktreePath, from.repoPath);
1794
+ const attachment = reviewGuidelinesAttachment(from);
1795
+ const siblings = threadsSharingWorktree(from.worktreePath);
1796
+ if (canReuseIdleWorktreeAgentForReview(from, siblings)) {
1797
+ const reused = updateThread(from.id, {
1798
+ title: REVIEW_TAB_TITLE,
1799
+ userSetTitle: true,
1800
+ attachments: [...from.attachments ?? [], attachment]
1801
+ });
1802
+ const started2 = await send(reused.id, REVIEW_REQUEST_PREFILL);
1803
+ return { tab: started2, from: reused };
1804
+ }
1729
1805
  const tab = createChatTab({
1730
1806
  fromThreadId: from.id,
1731
- title: "Review",
1732
- attachments: [
1733
- buildReviewRequestAttachment(guidelines.content, {
1734
- path: guidelines.path,
1735
- name: guidelines.name
1736
- })
1737
- ]
1807
+ title: REVIEW_TAB_TITLE,
1808
+ attachments: [attachment]
1738
1809
  });
1739
1810
  const started = await send(tab.id, REVIEW_REQUEST_PREFILL);
1740
1811
  return { tab: started, from };
@@ -3460,6 +3531,32 @@ function notifyParentOfChildHalt(child, status, send) {
3460
3531
  return true;
3461
3532
  }
3462
3533
 
3534
+ // src/threads/agent-purpose-title.ts
3535
+ function agentPurposeStamp(input) {
3536
+ if (input.autoContinue) return null;
3537
+ if (input.thread.sourceType === "orchestration") return null;
3538
+ if (input.thread.title.trim() === REVIEW_TAB_TITLE) return null;
3539
+ const identities = worktreeIdentityTitles(input.thread);
3540
+ const titled = input.thread.title?.trim() || "";
3541
+ const nickname = agentNicknameFromWorktreePath(input.thread.worktreePath);
3542
+ const soccerTitle = Boolean(teamSlugFromName(titled));
3543
+ const identityTitle = isWorktreeIdentityTitle(titled, identities);
3544
+ if (input.thread.userSetTitle && !soccerTitle && !identityTitle) return null;
3545
+ const userTurns = (input.thread.messages ?? []).filter(
3546
+ (m) => m.role === "user" && m.origin !== "continue" && m.text?.trim()
3547
+ ).length;
3548
+ if (userTurns <= 1) {
3549
+ const purpose = titleFromPrompt(input.prompt);
3550
+ if (purpose && !isWorktreeIdentityTitle(purpose, identities)) {
3551
+ return { title: purpose };
3552
+ }
3553
+ }
3554
+ if (isWorktreeIdentityTitle(titled, identities) && nickname && !isWorktreeIdentityTitle(nickname, identities)) {
3555
+ return { title: nickname };
3556
+ }
3557
+ return null;
3558
+ }
3559
+
3463
3560
  // src/threads/create.ts
3464
3561
  import { existsSync as existsSync9 } from "fs";
3465
3562
 
@@ -4169,7 +4266,7 @@ async function createThread(input, _onSetupLine) {
4169
4266
  });
4170
4267
  await requireAgent(resolved.agent);
4171
4268
  if (input.cowboy) {
4172
- const { cowboyModeEnabled } = await import("./app-settings-ONYJ36XH.js");
4269
+ const { cowboyModeEnabled } = await import("./app-settings-VQEUCONM.js");
4173
4270
  if (!cowboyModeEnabled()) {
4174
4271
  throw new Error(
4175
4272
  "Cowboy mode is off. Enable it in Settings \u2192 Advanced."
@@ -4227,7 +4324,7 @@ async function createThread(input, _onSetupLine) {
4227
4324
  let sourceIsFork = false;
4228
4325
  if (!input.cowboy && sourceType === "branch" && (!sourceRef || sourceRef === "HEAD" || sourceRef === "default")) {
4229
4326
  if (isAbleTimeConnected() && getIssueSource() === "abletime") {
4230
- const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-IDQJI2KW.js");
4327
+ const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-CDGINDXG.js");
4231
4328
  const task = await ensureAbleTimeTask({
4232
4329
  title: title?.trim() || "Untitled work",
4233
4330
  description: input.prompt?.trim() || void 0
@@ -4337,7 +4434,7 @@ async function createThread(input, _onSetupLine) {
4337
4434
  return readThread(thread.id) ?? thread;
4338
4435
  }
4339
4436
  async function listLinearIssues(agent, repoPath) {
4340
- const { getAdapter: getAdapter2 } = await import("./agents-FSUI5MET.js");
4437
+ const { getAdapter: getAdapter2 } = await import("./agents-J2P4KNEY.js");
4341
4438
  await requireAgent(agent, { requireLinear: true });
4342
4439
  const adapter = getAdapter2(agent);
4343
4440
  if (!adapter.listLinearIssues) {
@@ -4585,6 +4682,9 @@ async function forkThreadWorktree(input, onSetupLine) {
4585
4682
 
4586
4683
  // src/orchestrator/quota-failover.ts
4587
4684
  import { randomUUID as randomUUID3 } from "crypto";
4685
+ function managedKindForAgent(agent) {
4686
+ return agent === "claude" || agent === "codex" ? agent : null;
4687
+ }
4588
4688
  function planOrchestrationQuotaFailover(thread, limitText, opts) {
4589
4689
  if (!isSessionQuotaLimit(limitText)) return null;
4590
4690
  const onLimit = opts?.onLimit ?? usageOnLimit();
@@ -4597,7 +4697,7 @@ function planOrchestrationQuotaFailover(thread, limitText, opts) {
4597
4697
  };
4598
4698
  }
4599
4699
  if (thread.quotaContinuedFromId) {
4600
- if (onLimit === "switch_agent" && resumeAt) {
4700
+ if ((onLimit === "switch_agent" || onLimit === "switch_account") && resumeAt) {
4601
4701
  return {
4602
4702
  action: "wait_reset",
4603
4703
  reason: "Already continued once; waiting for quota reset instead.",
@@ -4626,6 +4726,32 @@ function planOrchestrationQuotaFailover(thread, limitText, opts) {
4626
4726
  resumeAt
4627
4727
  };
4628
4728
  }
4729
+ if (onLimit === "switch_account") {
4730
+ const kind = managedKindForAgent(thread.agent);
4731
+ if (!kind) {
4732
+ return {
4733
+ action: "none",
4734
+ reason: `switch_account configured but ${thread.agent} has no managed accounts.`,
4735
+ limitText
4736
+ };
4737
+ }
4738
+ const accounts = opts?.accounts ?? loadAppSettings().accounts;
4739
+ const next = nextManagedAccount(kind, thread.accountId ?? null, accounts);
4740
+ if (!next) {
4741
+ return {
4742
+ action: "none",
4743
+ reason: "switch_account configured but no other Claude/Codex account is available.",
4744
+ limitText
4745
+ };
4746
+ }
4747
+ return {
4748
+ action: "switch_account",
4749
+ reason: `Continue on ${kind} account \u201C${next.label}\u201D after session limit.`,
4750
+ limitText,
4751
+ fallbackAccountId: next.accountId,
4752
+ fallbackAccountLabel: next.label
4753
+ };
4754
+ }
4629
4755
  const preferred = opts?.fallbackAgent ?? orchestrationQuotaFallbackAgent();
4630
4756
  const fallbackAgent = resolveQuotaFallbackAgent(thread.agent, preferred);
4631
4757
  return {
@@ -4685,6 +4811,10 @@ var QUOTA_CONTINUE_PROMPT = (fromAgent, fallback) => [
4685
4811
  `${fromAgent} hit a session/usage limit. Continue this chat on ${fallback} using the attached handoff.`,
4686
4812
  "Proceed with the goal. Leave model Auto unless needed."
4687
4813
  ].join(" ");
4814
+ var QUOTA_CONTINUE_ACCOUNT_PROMPT = (fromAgent, accountLabel) => [
4815
+ `${fromAgent} hit a session/usage limit. Continue this chat on ${fromAgent} account \u201C${accountLabel}\u201D using the attached handoff.`,
4816
+ "Proceed with the goal. Leave model Auto unless needed."
4817
+ ].join(" ");
4688
4818
  var QUOTA_RESUME_PROMPT = "Session/usage limit window should have reset. Continue this chat from where you left off.";
4689
4819
  async function isolateQuotaFailover(run2) {
4690
4820
  try {
@@ -4693,13 +4823,14 @@ async function isolateQuotaFailover(run2) {
4693
4823
  return true;
4694
4824
  }
4695
4825
  }
4696
- function createQuotaFailoverChat(from, fallbackAgent, limitText) {
4826
+ function createQuotaFailoverChat(from, fallbackAgent, limitText, opts) {
4697
4827
  const handoff = buildQuotaHandoffAttachment(from, limitText, fallbackAgent);
4698
4828
  const tab = createChatTab({
4699
4829
  fromThreadId: from.id,
4700
4830
  agent: fallbackAgent,
4701
4831
  model: null,
4702
- attachments: [handoff]
4832
+ attachments: [handoff],
4833
+ ...opts && "accountId" in opts ? { accountId: opts.accountId } : {}
4703
4834
  });
4704
4835
  const patch = {
4705
4836
  quotaContinuedFromId: from.id,
@@ -4810,7 +4941,7 @@ async function adoptThread(input) {
4810
4941
  messages: input.messages ?? []
4811
4942
  });
4812
4943
  writeThread(thread);
4813
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-4KHEUPEX.js");
4944
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-I7TWFTJM.js");
4814
4945
  await ensureWorkspace2(repoPath);
4815
4946
  const { ensureWorktreeSideboardIgnored: ensureWorktreeSideboardIgnored2 } = await import("./worktree-exclude-RXZPTCNH.js");
4816
4947
  await ensureWorktreeSideboardIgnored2(input.worktreePath);
@@ -6958,32 +7089,6 @@ function formatUiReminder() {
6958
7089
  return "Sideboard UI: markdown table is enough to read data; present_schema if they ask to edit/filter (even after markdown); present_files for the file manager. html fence or present_artifact, not both for the same document. type=log appends (same artifact_id, new lines only). ask_user only for a real multiple-choice (not hellos, \u201Cwhat next?\u201D, or after a review) \u2014 reply in chat. ask_user wakes the parent orchestrator; notify_orchestrator if blocked without a picker. Do not say artifacts/CMS UI are unavailable. Dev / preview: run_dev_script (same as the Dev button) returns {url, port, preview} (SIDEBOARD_PORT \u2014 never guess :3000). preview=url \u2192 open url; preview=window \u2192 Electron window is the app, url is renderer/HMR only. list_run_scripts active[].url if already running; get_run_log for the Run-tab terminal; stop_dev_script to stop.";
6959
7090
  }
6960
7091
 
6961
- // src/review/review-write-gate.ts
6962
- var REVIEW_TAB_TITLE = "Review";
6963
- function hasLiveReviewSibling(thread, siblings = []) {
6964
- const selfId = thread.id;
6965
- return siblings.some(
6966
- (t) => t.id !== selfId && t.status !== "archived" && t.title.trim() === REVIEW_TAB_TITLE
6967
- );
6968
- }
6969
- function isReviewWriteGatedThread(thread, siblings) {
6970
- if (thread.sourceType === "pr" || thread.title.trim() === REVIEW_TAB_TITLE) {
6971
- return true;
6972
- }
6973
- return hasLiveReviewSibling(thread, siblings);
6974
- }
6975
- function formatReviewWriteGateDirective() {
6976
- return [
6977
- "Review writes are public. The PR or ticket author sees GitHub/Linear/AbleTime comments, ticket updates, and submitted reviews immediately \u2014 a draft will confuse them.",
6978
- "Write the recommendation in chat so the user can work through the feedback first. Do not call github_comment, linear_comment, abletime_comment, github_update_issue, linear_update_issue, abletime_update_task, or run `gh pr review` / `gh pr comment`, until they type a next step that asks you to post.",
6979
- "After the review is in chat, stop. Do not call ask_user. The user needs time to read and will type the next steps. If they already asked you to post a specific comment or update in this turn, that is confirmation.",
6980
- "Do not request reviewers, change labels, or assign yourself unless they asked \u2014 those also notify the author."
6981
- ].join("\n");
6982
- }
6983
- function formatReviewWriteGateReminder() {
6984
- return "Review writes: keep the review in chat. Do not ask_user after it \u2014 they read and type next steps. Do not github_comment / linear_comment / *_update_* / `gh pr review` on the PR or ticket until they ask. Authors see those immediately.";
6985
- }
6986
-
6987
7092
  // src/integrations/optional-services.ts
6988
7093
  var OPTIONAL_SERVICE_IDS = ["vercel", "supabase", "posthog", "sentry"];
6989
7094
  var OPTIONAL_SERVICES = [
@@ -7262,19 +7367,9 @@ async function syncThreadBranchFromGit(threadId) {
7262
7367
  const branch = stdout.trim();
7263
7368
  if (!branch || branch === "HEAD") return;
7264
7369
  const siblings = threadsSharingWorktree(thread.worktreePath);
7265
- const canonicalId = siblings[0]?.id ?? null;
7266
7370
  for (const sibling of siblings) {
7267
- const patch = {};
7268
- if (sibling.branchName !== branch) patch.branchName = branch;
7269
- if (sibling.id === canonicalId && !sibling.userSetTitle) {
7270
- const nextTitle = threadDisplayLabel({
7271
- ...sibling,
7272
- branchName: branch
7273
- });
7274
- if (sibling.title !== nextTitle) patch.title = nextTitle;
7275
- }
7276
- if (Object.keys(patch).length > 0) {
7277
- updateThread(sibling.id, patch);
7371
+ if (sibling.branchName !== branch) {
7372
+ updateThread(sibling.id, { branchName: branch });
7278
7373
  }
7279
7374
  }
7280
7375
  }
@@ -7539,7 +7634,7 @@ function formatScheduledPrompt(name, prompt) {
7539
7634
  ${prompt}`;
7540
7635
  }
7541
7636
  async function defaultDeps() {
7542
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-LWADJQTN.js");
7637
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-OCG4QVA5.js");
7543
7638
  const orch = getOrchestrator2();
7544
7639
  return {
7545
7640
  findThread: (id) => findThreadByRef(id),
@@ -7857,7 +7952,7 @@ var Orchestrator = class {
7857
7952
  orphans: orphans.map((o) => ({ path: o.path, repoPath: o.repoPath }))
7858
7953
  });
7859
7954
  }
7860
- const { autoCleanupOrphansEnabled } = await import("./app-settings-ONYJ36XH.js");
7955
+ const { autoCleanupOrphansEnabled } = await import("./app-settings-VQEUCONM.js");
7861
7956
  if (autoCleanupOrphansEnabled() && shouldRunWorktreeCleanup() && orphans.length > 0) {
7862
7957
  await cleanupOrphanWorktrees({ repoPaths });
7863
7958
  }
@@ -8053,6 +8148,48 @@ var Orchestrator = class {
8053
8148
  }
8054
8149
  return true;
8055
8150
  }
8151
+ if (plan.action === "switch_account" && plan.fallbackAccountLabel) {
8152
+ this.haltDrain.add(threadId);
8153
+ let next;
8154
+ try {
8155
+ next = createQuotaFailoverChat(thread, thread.agent, plan.limitText, {
8156
+ accountId: plan.fallbackAccountId ?? null,
8157
+ accountLabel: plan.fallbackAccountLabel
8158
+ });
8159
+ } catch {
8160
+ return false;
8161
+ }
8162
+ this.clearQuotaResumeTimer(threadId);
8163
+ try {
8164
+ updateThread(threadId, { quotaResumeAt: null });
8165
+ } catch {
8166
+ }
8167
+ try {
8168
+ appendMessage(threadId, {
8169
+ role: "agent",
8170
+ text: `Session limit on ${thread.agent}. Sideboard continued on account \u201C${plan.fallbackAccountLabel}\u201D in [${next.title}](sideboard://thread/${next.id}).`,
8171
+ ts: (/* @__PURE__ */ new Date()).toISOString()
8172
+ });
8173
+ this.emit({
8174
+ type: "quota_failover",
8175
+ threadId,
8176
+ action: "switch_account",
8177
+ toThreadId: next.id,
8178
+ message: plan.reason
8179
+ });
8180
+ this.emit({
8181
+ type: "status_changed",
8182
+ threadId: next.id,
8183
+ status: next.status
8184
+ });
8185
+ } catch {
8186
+ }
8187
+ void this.send(
8188
+ next.id,
8189
+ QUOTA_CONTINUE_ACCOUNT_PROMPT(thread.agent, plan.fallbackAccountLabel)
8190
+ );
8191
+ return true;
8192
+ }
8056
8193
  if (plan.action === "switch_agent" && plan.fallbackAgent) {
8057
8194
  this.haltDrain.add(threadId);
8058
8195
  let next;
@@ -8204,7 +8341,7 @@ var Orchestrator = class {
8204
8341
  }
8205
8342
  await setup;
8206
8343
  if (skipSetup) return;
8207
- const { autoRunAfterSetupEnabled } = await import("./app-settings-ONYJ36XH.js");
8344
+ const { autoRunAfterSetupEnabled } = await import("./app-settings-VQEUCONM.js");
8208
8345
  if (autoRunAfterSetupEnabled()) {
8209
8346
  try {
8210
8347
  await this.startDev(threadId);
@@ -8481,6 +8618,15 @@ var Orchestrator = class {
8481
8618
  ...autoContinue ? { origin: "continue" } : {},
8482
8619
  ts: (/* @__PURE__ */ new Date()).toISOString()
8483
8620
  });
8621
+ if (!autoContinue) {
8622
+ const live = this.requireThread(threadId);
8623
+ const stamp = agentPurposeStamp({
8624
+ thread: live,
8625
+ prompt: promptText,
8626
+ autoContinue: false
8627
+ });
8628
+ if (stamp) updateThread(threadId, stamp);
8629
+ }
8484
8630
  traceTurn("runTurn.afterUserMessage", {
8485
8631
  threadId,
8486
8632
  agent: thread.agent,
@@ -8574,7 +8720,7 @@ var Orchestrator = class {
8574
8720
  }
8575
8721
  const isBrightsy = fresh.agent === "brightsy";
8576
8722
  const isOrchestration = isOrchestratorThread(fresh);
8577
- const { autoRenameBranchEnabled, getGithubGitAuthMode, gitBranchPrefixSetting } = await import("./app-settings-ONYJ36XH.js");
8723
+ const { autoRenameBranchEnabled, getGithubGitAuthMode, gitBranchPrefixSetting } = await import("./app-settings-VQEUCONM.js");
8578
8724
  const gitAuthMode = getGithubGitAuthMode();
8579
8725
  const worktreeDirective = isBrightsy || isOrchestration ? null : formatWorktreeDirective(fresh, {
8580
8726
  githubSlug: await withTimeout(
@@ -9879,11 +10025,7 @@ var Orchestrator = class {
9879
10025
  if (Object.keys(patch).length === 0) continue;
9880
10026
  updateThread(t.id, patch);
9881
10027
  }
9882
- const titled = this.requireThread(thread.id);
9883
- if (!titled.userSetTitle && meta.title && titled.title !== meta.title) {
9884
- updateThread(thread.id, { title: meta.title });
9885
- }
9886
- const { autoArchiveOnMergeEnabled } = await import("./app-settings-ONYJ36XH.js");
10028
+ const { autoArchiveOnMergeEnabled } = await import("./app-settings-VQEUCONM.js");
9887
10029
  const latest = this.requireThread(thread.id);
9888
10030
  if (!shouldAutoArchiveOnPrMerge({
9889
10031
  previousPrState: prevState || null,
@@ -9990,13 +10132,7 @@ var Orchestrator = class {
9990
10132
  const patch = {};
9991
10133
  if (details.url && details.url !== thread.prUrl) patch.prUrl = details.url;
9992
10134
  if (details.title && details.title !== thread.prTitle) patch.prTitle = details.title;
9993
- if (Object.keys(patch).length > 0) {
9994
- updateThread(thread.id, patch);
9995
- const latest = this.requireThread(thread.id);
9996
- if (!latest.userSetTitle && details.title && latest.title !== details.title) {
9997
- updateThread(thread.id, { title: details.title });
9998
- }
9999
- }
10135
+ if (Object.keys(patch).length > 0) updateThread(thread.id, patch);
10000
10136
  }
10001
10137
  return details;
10002
10138
  }
@@ -10004,8 +10140,9 @@ var Orchestrator = class {
10004
10140
  return this.setThreadOptions(threadRef, { autonomy });
10005
10141
  }
10006
10142
  /**
10007
- * Open a Review chat tab on a worktree thread (same as the desktop Review button)
10008
- * and send the merge-readiness prefill.
10143
+ * Run a merge-readiness review on a worktree thread (same as the desktop
10144
+ * Review button). Reuses a single unused idle agent when that is the only
10145
+ * chat; otherwise opens a Review tab so existing work is not interrupted.
10009
10146
  */
10010
10147
  async requestReview(threadRef) {
10011
10148
  const { tab } = await requestReview(threadRef, (ref, prompt) => this.send(ref, prompt));
@@ -10179,7 +10316,7 @@ var Orchestrator = class {
10179
10316
  this.enforceHistoryRetention();
10180
10317
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
10181
10318
  try {
10182
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-4KHEUPEX.js");
10319
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-I7TWFTJM.js");
10183
10320
  await ensureWorkspace2(thread.repoPath);
10184
10321
  } catch {
10185
10322
  }
@@ -10206,7 +10343,7 @@ var Orchestrator = class {
10206
10343
  await runArchiveScript(thread.repoPath, thread.worktreePath);
10207
10344
  } catch {
10208
10345
  }
10209
- const { deleteBranchOnPurgeEnabled } = await import("./app-settings-ONYJ36XH.js");
10346
+ const { deleteBranchOnPurgeEnabled } = await import("./app-settings-VQEUCONM.js");
10210
10347
  const deleteBranch = opts?.deleteBranch ?? deleteBranchOnPurgeEnabled();
10211
10348
  await removeWorktree(thread.repoPath, thread.worktreePath, {
10212
10349
  deleteBranch: deleteBranch ? thread.branchName : void 0
@@ -10233,7 +10370,7 @@ var Orchestrator = class {
10233
10370
  `Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
10234
10371
  );
10235
10372
  }
10236
- const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-S3FOMEQ2.js");
10373
+ const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-YASFE65H.js");
10237
10374
  const slug = thread.worktreePath.split("/").pop();
10238
10375
  const dest = thread.worktreePath;
10239
10376
  await withRepoGitLock(thread.repoPath, async () => {
@@ -10325,8 +10462,8 @@ async function startOrchestration(opts) {
10325
10462
  }
10326
10463
  return thread2;
10327
10464
  }
10328
- const { titleFromPrompt } = await import("./title-4A2ATYNY.js");
10329
- const title = titleFromPrompt(goal) || "Orchestration";
10465
+ const { titleFromPrompt: titleFromPrompt2 } = await import("./title-Z7WAJN57.js");
10466
+ const title = titleFromPrompt2(goal) || "Orchestration";
10330
10467
  const createOpts = {
10331
10468
  agent: opts.agent,
10332
10469
  repoPath,
@@ -10343,7 +10480,7 @@ async function startOrchestration(opts) {
10343
10480
  sourceRef: "default",
10344
10481
  ...createOpts
10345
10482
  }).catch(async () => {
10346
- const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-S3FOMEQ2.js");
10483
+ const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-YASFE65H.js");
10347
10484
  const repo = await resolveRepoRoot2(repoPath);
10348
10485
  const def = await resolveDefaultBranch2(repo);
10349
10486
  return orch.createThread({
@@ -10353,7 +10490,7 @@ async function startOrchestration(opts) {
10353
10490
  repoPath: repo
10354
10491
  });
10355
10492
  });
10356
- const { updateThread: upd } = await import("./thread-store-ZDR3Z5UG.js");
10493
+ const { updateThread: upd } = await import("./thread-store-SHCUBWB3.js");
10357
10494
  const updated = upd(thread.id, {
10358
10495
  sourceType: "orchestration",
10359
10496
  sourceRef: goal
@@ -10494,6 +10631,7 @@ export {
10494
10631
  ensureReviewRequestFile,
10495
10632
  buildReviewRequestAttachment,
10496
10633
  readExistingReviewRequestFile,
10634
+ canReuseIdleWorktreeAgentForReview,
10497
10635
  requestReview,
10498
10636
  RUN_PORTS_REL,
10499
10637
  worktreeRunPortsPath,