@sideboard-ai/core 0.1.264 → 0.1.266

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 (68) hide show
  1. package/dist/{abletime-6AOJI2YZ.js → abletime-4ZSDI4YP.js} +9 -6
  2. package/dist/{abletime-U75ZGDIX.js → abletime-JNQAHLHH.js} +7 -4
  3. package/dist/{abletime-oauth-TA2YDCYX.js → abletime-oauth-4RXZH2KU.js} +3 -3
  4. package/dist/{abletime-oauth-LOAHGG4X.js → abletime-oauth-DCEIINYT.js} +5 -5
  5. package/dist/{abletime-rest-CTA5WV26.js → abletime-rest-KVBJFVFD.js} +3 -3
  6. package/dist/{abletime-rest-YZPFM6IY.js → abletime-rest-VN6PZ6P2.js} +5 -5
  7. package/dist/agents/cursor-runner.js +7 -5
  8. package/dist/{agents-3TUMFSMH.js → agents-G3JTULM2.js} +7 -5
  9. package/dist/{agents-6FWDRVA2.js → agents-XRXJCAWJ.js} +11 -10
  10. package/dist/{app-settings-RKFFUIFQ.js → app-settings-L5HGYXWJ.js} +1 -3
  11. package/dist/{app-settings-MVZPM6WW.js → app-settings-RAC4PSSP.js} +3 -5
  12. package/dist/{caffeinate-hold-UAGLSGMM.js → caffeinate-hold-7JM5H5P3.js} +2 -2
  13. package/dist/{chunk-F5MFNUAA.js → chunk-27Z4MEQ5.js} +1 -1
  14. package/dist/{chunk-EK3KVT7I.js → chunk-2XIRV7FS.js} +53 -1254
  15. package/dist/{chunk-3H7O27X2.js → chunk-3VBS6MES.js} +39 -1017
  16. package/dist/chunk-4ECDIKAS.js +2273 -0
  17. package/dist/{chunk-H547WSSZ.js → chunk-4RZRHTSZ.js} +1 -1
  18. package/dist/{chunk-QN5DL2QO.js → chunk-5DZUWGKV.js} +1 -1
  19. package/dist/chunk-5N4Q46SG.js +392 -0
  20. package/dist/{chunk-YKYEHDMX.js → chunk-AGXZAMOF.js} +5 -5
  21. package/dist/{chunk-RKPBIB2U.js → chunk-DB7MVDYS.js} +33 -7
  22. package/dist/{chunk-BOMSJUXW.js → chunk-DBFAFI5A.js} +5 -5
  23. package/dist/chunk-FAGK33E2.js +994 -0
  24. package/dist/{chunk-A5SAYFJ4.js → chunk-FIBZEFKA.js} +4 -390
  25. package/dist/{chunk-5PK53SAP.js → chunk-GONPMH36.js} +5 -5
  26. package/dist/{chunk-L5WGAIL2.js → chunk-GZXKUUJO.js} +35 -9
  27. package/dist/{chunk-Z2LGMV6C.js → chunk-H5XL4KUS.js} +21 -0
  28. package/dist/{chunk-T3AYRQJZ.js → chunk-HSRN2CSL.js} +473 -2487
  29. package/dist/{chunk-TJ673EZT.js → chunk-JANCPF7Z.js} +2 -2
  30. package/dist/chunk-KZ2CDZBZ.js +828 -0
  31. package/dist/{chunk-6YY7XPPL.js → chunk-L6CYOON4.js} +5 -5
  32. package/dist/{chunk-6YOXQVJ7.js → chunk-LAIBYKZS.js} +3 -3
  33. package/dist/{chunk-4ALDX5IO.js → chunk-NWE3NOWQ.js} +3 -3
  34. package/dist/{chunk-36FOBJBF.js → chunk-OIENQEGG.js} +63 -38
  35. package/dist/{chunk-2QUQH2SZ.js → chunk-PAH7KDL2.js} +68 -24
  36. package/dist/{chunk-XAISOBBG.js → chunk-TQRSVE7D.js} +1 -1
  37. package/dist/chunk-UKSJO36E.js +1224 -0
  38. package/dist/{chunk-CEMNIGQH.js → chunk-UN5Q7POQ.js} +1 -1
  39. package/dist/{chunk-5ZPT5YAV.js → chunk-USBZ7PH4.js} +1 -1
  40. package/dist/chunk-UXI52MLJ.js +826 -0
  41. package/dist/chunk-X7RP3TQB.js +2385 -0
  42. package/dist/{chunk-QX6QUZVD.js → chunk-ZSGSVFWD.js} +424 -2611
  43. package/dist/conductor-MLM5NGTP.js +104 -0
  44. package/dist/conductor-NRKTEN5X.js +105 -0
  45. package/dist/{connected-teams-7CENZDWU.js → connected-teams-JIUOSZ6C.js} +2 -2
  46. package/dist/{coordinator-prompt-J7DKHOCH.js → coordinator-prompt-4GHKCVRH.js} +7 -7
  47. package/dist/{coordinator-prompt-HZCQZ6C5.js → coordinator-prompt-BKRQDZFT.js} +3 -3
  48. package/dist/{cursor-recover-FALSNEZN.js → cursor-recover-MB3T2K5T.js} +2 -2
  49. package/dist/{global-workspace-TI26K25J.js → global-workspace-EEWJOMED.js} +9 -3
  50. package/dist/{global-workspace-6LDUZ2NF.js → global-workspace-EWFL4HPW.js} +13 -7
  51. package/dist/index.cjs +3918 -2592
  52. package/dist/index.d.cts +350 -68
  53. package/dist/index.d.ts +350 -68
  54. package/dist/index.js +4181 -3462
  55. package/dist/{managed-accounts-4BLTJKAN.js → managed-accounts-46A4KRUE.js} +2 -2
  56. package/dist/mcp/run-stdio.cjs +1894 -2012
  57. package/dist/mcp/run-stdio.js +719 -1445
  58. package/dist/{orchestrator-5YU46YNS.js → orchestrator-6ZQ2CYS4.js} +9 -6
  59. package/dist/{orchestrator-4JSG7BWC.js → orchestrator-MA3MHYIM.js} +15 -13
  60. package/dist/{paths-IUR3ABNG.js → paths-HYT5WOOX.js} +1 -1
  61. package/dist/{thread-store-FJ5CDX2M.js → thread-store-B3ZM4QEZ.js} +3 -3
  62. package/dist/{workspaces-QJ3XIPUJ.js → workspaces-F33Z4CEY.js} +3 -3
  63. package/dist/{workspaces-WOQHJFRQ.js → workspaces-SLP24Z3S.js} +7 -7
  64. package/dist/{worktree-6U2FA5LO.js → worktree-ACW23KJ5.js} +6 -6
  65. package/dist/{worktree-KQA4ON2G.js → worktree-C7RPVHZ7.js} +2 -2
  66. package/package.json +1 -1
  67. package/dist/chunk-CEOTK5GZ.js +0 -479
  68. package/dist/chunk-K7B25HO4.js +0 -477
@@ -1,26 +1,65 @@
1
1
  import {
2
2
  adjustBrightsyTurnExit,
3
3
  allAdapters,
4
+ getAdapter,
5
+ isSessionQuotaLimit,
6
+ parseBrightsyCliLine,
7
+ parseSessionQuotaResetAt,
8
+ planModeTurnInstruction,
9
+ resolveQuotaFallbackAgent
10
+ } from "./chunk-3VBS6MES.js";
11
+ import {
12
+ REVIEW_TAB_TITLE,
13
+ applyWorktreePkgCacheEnv,
14
+ buildBrightsySessionSeed,
15
+ buildForkTranscriptAttachment,
16
+ buildSessionSeed,
17
+ copyConfiguredFiles,
18
+ createChatTab,
19
+ deriveTaskState,
20
+ forkChatTab,
21
+ forkMessageSlice,
22
+ formatReviewWriteGateDirective,
23
+ formatReviewWriteGateReminder,
24
+ getDefaultRunScript,
25
+ getRunMode,
26
+ isJobContinuePrompt,
27
+ isReviewWriteGatedThread,
28
+ isWorktreeRunProcessKey,
29
+ killDetachedRunTree,
30
+ killListenersOnPorts,
31
+ lastActivityAtForWait,
32
+ listRunScripts,
33
+ listRunningDetachedJobs,
34
+ maybeCompactContext,
35
+ mergeWorktreeActiveRuns,
36
+ planJobContinue,
37
+ requestReview,
38
+ resolveWorktreeSetupLog,
39
+ runArchiveScript,
40
+ runWorkspaceSetup,
41
+ startDevServer,
42
+ threadsSharingWorktree,
43
+ turnWatchedDetachedJob,
44
+ worktreeDevProcessKey,
45
+ worktreeRunProcessKey,
46
+ worktreeSetupProcessKey
47
+ } from "./chunk-X7RP3TQB.js";
48
+ import {
4
49
  applyAgentEvent,
5
50
  clipAgentEventForPaint,
6
- compactSummaryPart,
7
51
  finalizeParts,
8
- getAdapter,
9
52
  isBrightsyNdjsonLine,
10
53
  isInternalAgentStatusText,
11
54
  isPollWrapperToolName,
12
- isSessionQuotaLimit,
13
55
  lastAssistantMessageText,
14
56
  liveActivitySummary,
57
+ messagePartParentId,
15
58
  normalizeParseResult,
16
- parseBrightsyCliLine,
17
- parseSessionQuotaResetAt,
18
59
  partsToAssistantText,
19
- planModeTurnInstruction,
20
- resolveQuotaFallbackAgent,
21
60
  stripBrightsyNdjsonNoise,
22
61
  toolDescription
23
- } from "./chunk-3H7O27X2.js";
62
+ } from "./chunk-FAGK33E2.js";
24
63
  import {
25
64
  titleFromPrompt
26
65
  } from "./chunk-7H655NEW.js";
@@ -40,22 +79,19 @@ import {
40
79
  ensureGlobalCoordinatorCwd,
41
80
  ensureWorkspace,
42
81
  healOrchestrationSoccerTitles,
43
- isCloudCoordinatorThread,
44
82
  isGlobalRepoPath,
45
83
  isGlobalThread,
46
84
  isOrchestratorThread,
47
- isSlackCoordinatorThread,
48
85
  orchestratorSessionPoisonedByBuiltins,
49
86
  persistPendingFileAttachments,
50
87
  removeWorkspace,
51
88
  stageAbsolutePathsAsAttachments,
52
89
  stageBuffersAsAttachments,
53
90
  syncWorkspacesFromThreads
54
- } from "./chunk-RKPBIB2U.js";
91
+ } from "./chunk-DB7MVDYS.js";
55
92
  import {
56
93
  addPrStackLayer,
57
94
  agentNicknameFromWorktreePath,
58
- allocateTeamName,
59
95
  allocateTeamSlug,
60
96
  canonicalizeRepoPath,
61
97
  ciChecksFailed,
@@ -97,7 +133,6 @@ import {
97
133
  resolvePrSelector,
98
134
  resolveRepoRoot,
99
135
  switchWorktreeBranch,
100
- takenSlugsFromThread,
101
136
  teamSlugFromName,
102
137
  ticketSlugForBranch,
103
138
  withRepoGitLock,
@@ -105,7 +140,7 @@ import {
105
140
  worktreeIdentityTitles,
106
141
  worktreeNameFromPath,
107
142
  worktreeSlugForTicket
108
- } from "./chunk-5ZPT5YAV.js";
143
+ } from "./chunk-USBZ7PH4.js";
109
144
  import {
110
145
  appendMessage,
111
146
  createEmptyThread,
@@ -118,9 +153,6 @@ import {
118
153
  withThreadLock,
119
154
  writeThread
120
155
  } from "./chunk-FWNL62VA.js";
121
- import {
122
- ensureWorktreeSideboardIgnored
123
- } from "./chunk-2P4XKWV2.js";
124
156
  import {
125
157
  httpFetch
126
158
  } from "./chunk-N7WZ6NIN.js";
@@ -129,34 +161,28 @@ import {
129
161
  } from "./chunk-AXQ4ZWHK.js";
130
162
  import {
131
163
  applyAgentRunnerHeapEnv,
132
- clipToolResultForStore,
164
+ packagedMcpDir
165
+ } from "./chunk-FIBZEFKA.js";
166
+ import {
133
167
  fallbackTurnFailDetail,
134
168
  formatAgentErrorContinuePrompt,
135
169
  formatTurnExitError,
136
170
  looksLikeAgentFailureMessage,
137
171
  looksLikeInvalidAgentSession,
138
172
  looksLikeV8Oom,
139
- packagedMcpDir,
140
173
  pushTurnStderr,
141
174
  shouldFeedErrorBackToAgent,
142
175
  shouldRetryCodexPluginIsolate,
143
176
  shouldRetryFailedAgentTurn,
144
177
  summarizeTurnStderr,
145
178
  turnFailChatText
146
- } from "./chunk-A5SAYFJ4.js";
147
- import {
148
- ATTACHMENTS_DIR,
149
- DETACHED_JOBS_DIR,
150
- LEGACY_ATTACHMENTS_DIR,
151
- LEGACY_DETACHED_JOBS_DIR
152
- } from "./chunk-6K64FJDD.js";
179
+ } from "./chunk-5N4Q46SG.js";
153
180
  import {
154
181
  clearStaleIndexLocks,
155
182
  enrichPathWithNpmGlobalBin,
156
183
  ensureAgentPath,
157
184
  git,
158
- resolveGitDirsForLockRecovery,
159
- run
185
+ resolveGitDirsForLockRecovery
160
186
  } from "./chunk-QVN2WOKJ.js";
161
187
  import {
162
188
  HISTORY_CLEANUP_INTERVAL_HOURS_DEFAULT,
@@ -166,7 +192,6 @@ import {
166
192
  caffeinateWhileSchedulesEnabled,
167
193
  childEnvWithAppSettings,
168
194
  coerceOrchestratorAgent,
169
- contextTokens,
170
195
  followUpBehavior,
171
196
  formatViewerContextDirective,
172
197
  formatViewerContextReminder,
@@ -190,10 +215,7 @@ import {
190
215
  updateAdvancedSettings,
191
216
  usageOnLimit,
192
217
  withTurnScopedCost
193
- } from "./chunk-2QUQH2SZ.js";
194
- import {
195
- stripNestedElectronEnv
196
- } from "./chunk-4TR3HZFT.js";
218
+ } from "./chunk-PAH7KDL2.js";
197
219
  import {
198
220
  examplePrefixedBranch,
199
221
  sanitizeGitBranchPrefix
@@ -213,15 +235,11 @@ import {
213
235
  import {
214
236
  annotateDevAccessRuns,
215
237
  appDataDir,
216
- findConventionSetup,
217
- loadRepoSettings,
218
238
  loadWorkspaceSettings,
219
- runCursorWorktreeSetup,
220
239
  sideboardReposDir,
221
240
  sideboardWorkspacesDir,
222
241
  threadLivePath,
223
242
  threadsDir,
224
- workspaceSettingsSourceLabel,
225
243
  worktreesRoot
226
244
  } from "./chunk-EHT2DRT4.js";
227
245
 
@@ -266,95 +284,9 @@ function formatInjectedNoticesForTurn(notices) {
266
284
  ].join("\n\n");
267
285
  }
268
286
 
269
- // src/orchestrator/task-state.ts
270
- function isTerminalTaskState(state) {
271
- return state !== "submitted" && state !== "working";
272
- }
273
- function isIncompleteTaskState(state) {
274
- return state === "failed" || state === "canceled";
275
- }
276
- function needsCoordinatorAction(state) {
277
- return isIncompleteTaskState(state) || state === "input-required";
278
- }
279
- var ASK_USER_TOOL = /(^|[_.:/])ask_user$/i;
280
- function endedOnAskUser(parts) {
281
- if (!parts?.length) return false;
282
- for (let i = parts.length - 1; i >= 0; i--) {
283
- const p = parts[i];
284
- if (p.type === "tool" && !p.parentId) {
285
- return ASK_USER_TOOL.test(p.name);
286
- }
287
- }
288
- return false;
289
- }
290
- function deriveTaskState(input) {
291
- if (input.stillRunning) {
292
- return input.status === "queued" ? "submitted" : "working";
293
- }
294
- switch (input.status) {
295
- case "error":
296
- case "broken":
297
- return "failed";
298
- case "stopped":
299
- return "canceled";
300
- default:
301
- return endedOnAskUser(input.lastAgentParts) ? "input-required" : "completed";
302
- }
303
- }
304
-
305
- // src/mcp/wait-for-turn.ts
306
- var MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
307
- function mcpWaitForTurnTimeoutMs(requested) {
308
- const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
309
- if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
310
- return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
311
- }
312
- var MCP_WAIT_STILL_RUNNING_HINT = "Child is still working. Call wait_for_turn again. Do not send_to_chat a check-in (that steers / interrupts) or assume a hang while progress is updating.";
313
- var MCP_WAIT_QUEUED_HINT = "Child is queued waiting for a concurrency slot \u2014 it has not started yet. Call wait_for_turn again. Do not send_to_chat a check-in (that steers / interrupts), force_stop, or assume it failed to start.";
314
- var MCP_WAIT_STOPPED_HINT = "Child was stopped before the turn finished. Do not treat this as success. send_to_chat to resume, or tell the user.";
315
- var MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
316
- var MCP_WAIT_ERROR_HINT = "Child turn failed. lastError/text is the failure \u2014 switch agent, tell the user, or retry. Do not treat empty text as success.";
317
- var MCP_WAIT_INPUT_REQUIRED_HINT = "Child asked the user a question (ask_user). taskState is input-required. Wait for the user to answer in that chat \u2014 do not send_to_chat a check-in.";
318
- function mcpWaitTaskHint(taskState, status) {
319
- switch (taskState) {
320
- case "submitted":
321
- return MCP_WAIT_QUEUED_HINT;
322
- case "working":
323
- return MCP_WAIT_STILL_RUNNING_HINT;
324
- case "input-required":
325
- return MCP_WAIT_INPUT_REQUIRED_HINT;
326
- case "canceled":
327
- return MCP_WAIT_STOPPED_HINT;
328
- case "failed":
329
- return status === "broken" ? MCP_WAIT_BROKEN_HINT : MCP_WAIT_ERROR_HINT;
330
- default:
331
- return void 0;
332
- }
333
- }
334
- function nonemptyIso(value) {
335
- const trimmed = value?.trim();
336
- return trimmed ? trimmed : null;
337
- }
338
- function lastActivityAtForWait(stillRunning, liveUpdatedAt, fallbacks) {
339
- if (stillRunning) return nonemptyIso(liveUpdatedAt);
340
- for (const value of fallbacks) {
341
- const iso = nonemptyIso(value);
342
- if (iso) return iso;
343
- }
344
- return null;
345
- }
346
- function waitForTurnToolResult(input) {
347
- return {
348
- ...input,
349
- usage: input.usage ?? null,
350
- hint: mcpWaitTaskHint(input.taskState, input.status),
351
- incomplete: needsCoordinatorAction(input.taskState)
352
- };
353
- }
354
-
355
287
  // src/orchestrator/orchestrator.ts
356
- import { randomUUID as randomUUID5 } from "crypto";
357
- import { existsSync as existsSync18 } from "fs";
288
+ import { randomUUID as randomUUID3 } from "crypto";
289
+ import { existsSync as existsSync14 } from "fs";
358
290
 
359
291
  // src/agents/spawn.ts
360
292
  import { createInterface } from "readline";
@@ -388,6 +320,49 @@ async function withTimeout(promise, ms, label) {
388
320
  }
389
321
  }
390
322
 
323
+ // src/agents/stdout-replay.ts
324
+ var REPLAY_MIN_CHARS = 80;
325
+ function textSoFar(parts, parentId) {
326
+ const parent = parentId?.trim() || "";
327
+ let out = "";
328
+ for (const part of parts) {
329
+ if (part.type !== "text") continue;
330
+ if ((messagePartParentId(part) ?? "") !== parent) continue;
331
+ out += part.text;
332
+ }
333
+ return out;
334
+ }
335
+ function replaySized(text) {
336
+ return text.trim().length >= REPLAY_MIN_CHARS;
337
+ }
338
+ function tailAfterAccumulated(accumulated, incoming) {
339
+ if (incoming.startsWith(accumulated)) return incoming.slice(accumulated.length);
340
+ const acc = accumulated.trim();
341
+ const inc = incoming.trim();
342
+ if (!acc || !inc.startsWith(acc)) return null;
343
+ const at = incoming.indexOf(acc);
344
+ if (at < 0) return null;
345
+ return incoming.slice(at + acc.length);
346
+ }
347
+ function foldDuplicateStdout(accumulated, incoming) {
348
+ if (!incoming) return null;
349
+ if (!accumulated) return incoming;
350
+ const accTrim = accumulated.trim();
351
+ const incTrim = incoming.trim();
352
+ if (!incTrim) return incoming;
353
+ if (incTrim === accTrim) return null;
354
+ if (!replaySized(incoming)) return incoming;
355
+ if (accTrim.includes(incTrim)) return null;
356
+ let tail = tailAfterAccumulated(accumulated, incoming);
357
+ if (tail == null) return incoming;
358
+ for (let i = 0; i < 3 && tail.trim(); i++) {
359
+ const next = tailAfterAccumulated(accumulated, tail);
360
+ if (next == null) break;
361
+ tail = next;
362
+ }
363
+ return tail.trim() ? tail : null;
364
+ }
365
+
391
366
  // src/hook/agent-host-env.ts
392
367
  import { delimiter } from "path";
393
368
  var DROP_EXACT = /* @__PURE__ */ new Set([
@@ -444,56 +419,6 @@ function sanitizeAgentHostEnv(env, cwd) {
444
419
  return env;
445
420
  }
446
421
 
447
- // src/hook/worktree-pkg-cache.ts
448
- import { existsSync, mkdirSync } from "fs";
449
- import { join as join2 } from "path";
450
- var WORKTREE_PKG_CACHE_REL = ".context/.sideboard/pkg-cache";
451
- function worktreePkgCacheRoot(worktreePath) {
452
- return join2(worktreePath, WORKTREE_PKG_CACHE_REL);
453
- }
454
- function worktreePkgCacheEnv(worktreePath) {
455
- const root = worktreePkgCacheRoot(worktreePath);
456
- const store = join2(root, "pnpm-store");
457
- const pnpmCache = join2(root, "pnpm");
458
- const npmCache = join2(root, "npm");
459
- const nodeGyp = join2(root, "node-gyp");
460
- const electron = join2(root, "electron");
461
- return {
462
- npm_config_store_dir: store,
463
- PNPM_STORE_DIR: store,
464
- npm_config_cache_dir: pnpmCache,
465
- npm_config_cache: npmCache,
466
- npm_config_devdir: nodeGyp,
467
- // @electron/get — do not set ELECTRON_CACHE (stripNestedElectronEnv).
468
- electron_config_cache: electron
469
- };
470
- }
471
- function isSet(value) {
472
- return value != null && value.trim() !== "";
473
- }
474
- function fillGroup(env, keys, values) {
475
- if (keys.some((key) => isSet(env[key]))) return;
476
- for (const key of keys) {
477
- const value = values[key];
478
- if (value) env[key] = value;
479
- }
480
- }
481
- function ensureWorktreePkgCacheDirs(worktreePath) {
482
- if (!existsSync(worktreePath)) return;
483
- mkdirSync(worktreePkgCacheRoot(worktreePath), { recursive: true });
484
- }
485
- function applyWorktreePkgCacheEnv(env, worktreePath) {
486
- const cwd = worktreePath.trim();
487
- if (!cwd) return;
488
- const next = worktreePkgCacheEnv(cwd);
489
- fillGroup(env, ["npm_config_store_dir", "PNPM_STORE_DIR"], next);
490
- fillGroup(env, ["npm_config_cache_dir"], next);
491
- fillGroup(env, ["npm_config_cache"], next);
492
- fillGroup(env, ["npm_config_devdir"], next);
493
- fillGroup(env, ["electron_config_cache"], next);
494
- ensureWorktreePkgCacheDirs(cwd);
495
- }
496
-
497
422
  // src/agents/spawn.ts
498
423
  function applyPromptCacheTtlEnv(agent, env) {
499
424
  if (agent === "claude" && env.FORCE_PROMPT_CACHING_5M !== "1") {
@@ -512,9 +437,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
512
437
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
513
438
  );
514
439
  }
515
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-TI26K25J.js");
440
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-EEWJOMED.js");
516
441
  if (isGlobalThread2(thread)) {
517
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-HZCQZ6C5.js");
442
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-BKRQDZFT.js");
518
443
  ensureGlobalCoordinatorCwd2(
519
444
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
520
445
  );
@@ -621,7 +546,8 @@ async function spawnAgentTurn(thread, input, onEvent) {
621
546
  events = normalizeParseResult(parseBrightsyCliLine(line));
622
547
  }
623
548
  }
624
- for (const parsed of events) {
549
+ for (const event of events) {
550
+ let parsed = event;
625
551
  if (parsed.type === "session_id") {
626
552
  sessionId = parsed.data;
627
553
  outbound.push(parsed);
@@ -637,9 +563,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
637
563
  if (thread.agent === "brightsy" && isBrightsyNdjsonLine(parsed.data)) {
638
564
  continue;
639
565
  }
640
- if (assistantText && (parsed.data === assistantText || assistantText.endsWith(parsed.data))) {
641
- continue;
642
- }
566
+ const folded = foldDuplicateStdout(textSoFar(parts, parsed.parentId), parsed.data);
567
+ if (folded == null) continue;
568
+ if (folded !== parsed.data) parsed = { ...parsed, data: folded };
643
569
  }
644
570
  parts = applyAgentEvent(parts, parsed);
645
571
  if (parsed.type === "stdout") {
@@ -725,2149 +651,141 @@ function gitActionExpansion(action, base) {
725
651
  case "resolve-conflicts":
726
652
  return `Fetch the PR base${base ? ` (\`${base}\`)` : ""}, merge it into this branch, resolve conflicts carefully, then commit and push until the PR is mergeable.`;
727
653
  case "ready-for-review":
728
- return "This phrase is the explicit ask to mark this thread's draft pull request ready for review on GitHub (`gh pr ready -R <origin-owner/name>` or `gh pr ready`). Update title/body if the purpose drifted. Do not merge.";
729
- case "merge":
730
- return "This phrase is the explicit ask to merge this thread's open pull request on GitHub. If `gh stack view` shows a stack, use `gh stack merge`; otherwise `gh pr merge` (respect repo defaults / squash vs merge). Do not force-push main/master or merge locally into the main checkout.";
731
- }
732
- }
733
- function expandCanonicalGitRequest(prompt) {
734
- const trimmed = prompt.trim();
735
- let action = null;
736
- let base = null;
737
- for (const candidate of AGENT_GIT_ACTIONS) {
738
- if (candidate === "resolve-conflicts") continue;
739
- if (agentGitPrompt(candidate) === trimmed) {
740
- action = candidate;
741
- break;
742
- }
743
- }
744
- if (!action) {
745
- const m = RESOLVE_CONFLICTS_RE.exec(trimmed);
746
- if (m) {
747
- action = "resolve-conflicts";
748
- base = m[1] ?? null;
749
- }
750
- }
751
- let expansion = action ? gitActionExpansion(action, base) : null;
752
- if (!expansion && trimmed === "Address review comments.") {
753
- expansion = "Read the PR review feedback (`gh pr view --comments`, `gh api` review comments), make the requested changes, commit, and push.";
754
- } else if (!expansion && FIX_CI_RE.test(trimmed)) {
755
- expansion = "Investigate that failing check (`gh pr checks`, `gh run view --log-failed`), fix it, commit, and push. Loop only if a goal to keep going until it passes was given.";
756
- }
757
- if (!expansion) return prompt;
758
- return `${trimmed}
759
-
760
- (Sideboard git request \u2014 do not ask for clarification: ${expansion})`;
761
- }
762
- var FIX_CI_RE = /^Fix CI: .+\.$/;
763
-
764
- // src/git/pr-merge-archive.ts
765
- function normalizePrState(state) {
766
- return (state ?? "").trim().toUpperCase();
767
- }
768
- function samePrIdentity(a, b) {
769
- const left = (a ?? "").trim().replace(/\/+$/, "");
770
- const right = (b ?? "").trim().replace(/\/+$/, "");
771
- if (!left || !right) return false;
772
- if (left === right) return true;
773
- const ln = left.match(/\/pull\/(\d+)/i)?.[1];
774
- const rn = right.match(/\/pull\/(\d+)/i)?.[1];
775
- return Boolean(ln && rn && ln === rn);
776
- }
777
- function shouldStopRunOnPrRetarget(previousPrUrl, nextPrUrl) {
778
- const prev = (previousPrUrl ?? "").trim();
779
- const next = (nextPrUrl ?? "").trim();
780
- if (!prev || !next) return false;
781
- return !samePrIdentity(prev, next);
782
- }
783
- function shouldPersistFetchedPrMeta(opts) {
784
- const meta = opts.metaUrl?.trim() || "";
785
- if (!meta) return false;
786
- const head = opts.headPrUrl?.trim() || "";
787
- if (head) return samePrIdentity(meta, head);
788
- const live = opts.livePrUrl?.trim() || "";
789
- if (live) return samePrIdentity(meta, live);
790
- return true;
791
- }
792
- function sameLoginList(a, b) {
793
- const left = [...a ?? []].map((s) => s.trim().toLowerCase()).filter(Boolean).sort();
794
- const right = [...b ?? []].map((s) => s.trim().toLowerCase()).filter(Boolean).sort();
795
- return left.length === right.length && left.every((login, i) => login === right[i]);
796
- }
797
- function threadPrMetaPatch(thread, meta) {
798
- const prevState = normalizePrState(thread.prState);
799
- const nextState = normalizePrState(meta.state);
800
- const patch = {};
801
- if (meta.url && meta.url !== thread.prUrl) patch.prUrl = meta.url;
802
- if (meta.title && meta.title !== thread.prTitle) patch.prTitle = meta.title;
803
- if (nextState && nextState !== prevState) patch.prState = nextState;
804
- const nextDraft = Boolean(meta.isDraft) && nextState !== "MERGED" && nextState !== "CLOSED";
805
- if (nextDraft !== Boolean(thread.prIsDraft)) patch.prIsDraft = nextDraft;
806
- const nextAuthor = meta.authorLogin?.trim() || "";
807
- if (nextAuthor && nextAuthor !== (thread.prAuthorLogin ?? "").trim()) {
808
- patch.prAuthorLogin = nextAuthor;
809
- }
810
- const nextReviewers = meta.reviewerLogins ?? [];
811
- if (!sameLoginList(nextReviewers, thread.prReviewerLogins)) {
812
- patch.prReviewerLogins = nextReviewers;
813
- }
814
- const nextDecision = meta.reviewDecision ?? null;
815
- if ((thread.prReviewDecision ?? null) !== nextDecision) {
816
- patch.prReviewDecision = nextDecision;
817
- }
818
- const nextMergeable = meta.mergeable ?? null;
819
- if ((thread.prMergeable ?? null) !== nextMergeable) {
820
- patch.prMergeable = nextMergeable;
821
- }
822
- const nextMergeState = meta.mergeStateStatus ?? null;
823
- if ((thread.prMergeStateStatus ?? null) !== nextMergeState) {
824
- patch.prMergeStateStatus = nextMergeState;
825
- }
826
- const nextQueued = Boolean(meta.isInMergeQueue);
827
- if (Boolean(thread.prIsInMergeQueue) !== nextQueued) {
828
- patch.prIsInMergeQueue = nextQueued;
829
- }
830
- const nextLabels = meta.labels ?? [];
831
- if (!sameLoginList(nextLabels, thread.prLabels)) {
832
- patch.prLabels = nextLabels;
833
- }
834
- const mergeState = (nextMergeState ?? "").toUpperCase();
835
- if (mergeState === "CLEAN" && thread.prChecksFailed) {
836
- patch.prChecksFailed = false;
837
- } else if ((mergeState === "UNSTABLE" || mergeState === "BLOCKED" && (nextDecision ?? "").toUpperCase() === "APPROVED") && !thread.prChecksFailed) {
838
- patch.prChecksFailed = true;
839
- }
840
- if (thread.skipAutoArchiveOnMerge && nextState && nextState !== "MERGED" && nextState !== "CLOSED") {
841
- patch.skipAutoArchiveOnMerge = false;
842
- }
843
- return patch;
844
- }
845
- function threadPrChecksFailedPatch(thread, failed) {
846
- if (thread.prChecksFailed === failed) return {};
847
- return { prChecksFailed: failed };
848
- }
849
- function shouldAutoArchiveOnPrMerge(opts) {
850
- if (!opts.autoArchiveEnabled) return false;
851
- if (opts.isGlobal) return false;
852
- if (opts.skipAutoArchiveOnMerge) return false;
853
- if (opts.threadStatus === "archived") return false;
854
- const next = normalizePrState(opts.nextPrState);
855
- if (next !== "MERGED") return false;
856
- return normalizePrState(opts.previousPrState) !== "MERGED";
857
- }
858
-
859
- // src/hook/conductor.ts
860
- import {
861
- copyFileSync,
862
- existsSync as existsSync4,
863
- mkdirSync as mkdirSync5,
864
- readdirSync as readdirSync2,
865
- readFileSync as readFileSync4,
866
- realpathSync
867
- } from "fs";
868
- import { execFileSync } from "child_process";
869
- import { createServer } from "net";
870
- import { basename, dirname as dirname4, join as join6, resolve, sep } from "path";
871
- import { execa as execa2 } from "execa";
872
- import { createInterface as createInterface2 } from "readline";
873
-
874
- // src/review/request-review.ts
875
- import { randomUUID as randomUUID2 } from "crypto";
876
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
877
- import { dirname, join as join3 } from "path";
878
-
879
- // src/threads/chat-tabs.ts
880
- import { randomUUID } from "crypto";
881
-
882
- // src/composer/context-estimate.ts
883
- var CHARS_PER_CONTEXT_TOKEN = 4;
884
- function estimateMessageChars(message) {
885
- let n = message.text.length + 16;
886
- for (const part of message.parts ?? []) {
887
- n += estimatePartChars(part);
888
- }
889
- return n;
890
- }
891
- function estimatePartChars(part) {
892
- switch (part.type) {
893
- case "text":
894
- case "thinking":
895
- return part.text.length;
896
- case "tool": {
897
- const input = part.input ? JSON.stringify(part.input) : "";
898
- return part.name.length + (part.description?.length ?? 0) + (part.detail?.length ?? 0) + (part.result?.length ?? 0) + input.length + 32;
899
- }
900
- default:
901
- return 0;
902
- }
903
- }
904
- function estimateThreadChars(messages) {
905
- return messages.reduce((sum, m) => sum + estimateMessageChars(m), 0);
906
- }
907
- function estimateOccupancyTokens(messages) {
908
- return Math.ceil(estimateThreadChars(messages) / CHARS_PER_CONTEXT_TOKEN);
909
- }
910
- function threadHasCompactedContext(messages) {
911
- return messages.some((m) => m.role === "summary");
912
- }
913
- function forwardOccupancyTokens(messages, usage, windowTokens = 1e6) {
914
- const estimated = estimateOccupancyTokens(messages);
915
- if (threadHasCompactedContext(messages) && estimated > 0) return estimated;
916
- if (usage) {
917
- const last = contextTokens(usage);
918
- if (last > 0 && last <= windowTokens) return last;
919
- }
920
- return estimated;
921
- }
922
- function forwardContextUsage(usage, messages) {
923
- if (!usage) return null;
924
- if (!threadHasCompactedContext(messages)) return usage;
925
- const remaining = estimateOccupancyTokens(messages);
926
- const current = contextTokens(usage);
927
- if (remaining <= 0 || remaining >= current) return usage;
928
- return { ...usage, lastRequestTokens: remaining };
929
- }
930
- function applyForwardOccupancy(messages) {
931
- const tokens = estimateOccupancyTokens(messages);
932
- if (tokens <= 0) return messages;
933
- for (let i = messages.length - 1; i >= 0; i--) {
934
- const m = messages[i];
935
- if (m?.role !== "agent" || !m.usage) continue;
936
- const current = contextTokens(m.usage);
937
- if (tokens >= current) return messages;
938
- const next = messages.slice();
939
- next[i] = { ...m, usage: { ...m.usage, lastRequestTokens: tokens } };
940
- return next;
941
- }
942
- return messages;
943
- }
944
-
945
- // src/composer/summarize.ts
946
- var MAX_TRANSCRIPT_CHARS = 12e4;
947
- var MAX_SUMMARY_CHARS = 6e3;
948
- var CLAUDE_SUMMARY_TIMEOUT_MS = 2e4;
949
- async function summarizeConversation(transcript, opts) {
950
- const clipped = transcript.length > MAX_TRANSCRIPT_CHARS ? `${transcript.slice(0, MAX_TRANSCRIPT_CHARS)}
951
-
952
- [\u2026truncated\u2026]` : transcript;
953
- const claudeSummary = await tryClaudeSummary(clipped, opts);
954
- if (claudeSummary) {
955
- return { summary: clipSummary(claudeSummary), method: "claude" };
956
- }
957
- return { summary: clipSummary(extractiveSummary(clipped)), method: "extractive" };
958
- }
959
- async function tryClaudeSummary(transcript, opts) {
960
- ensureAgentPath();
961
- const prompt = [
962
- "Summarize this coding-agent conversation for continuity in a fresh session.",
963
- "Use concise markdown bullets covering:",
964
- "- Goal / request",
965
- "- Key decisions and constraints",
966
- "- Files / areas touched",
967
- "- Current state and open tasks",
968
- "- Anything the next turn must remember",
969
- "Do not invent details. No preamble.",
970
- "",
971
- "Transcript:",
972
- transcript
973
- ].join("\n");
974
- try {
975
- const { stdout, exitCode } = await run(
976
- "claude",
977
- [
978
- "-p",
979
- prompt,
980
- "--output-format",
981
- "text",
982
- "--permission-mode",
983
- "bypassPermissions",
984
- "--model",
985
- "haiku"
986
- ],
987
- {
988
- cwd: opts?.cwd,
989
- reject: false,
990
- timeoutMs: opts?.timeoutMs ?? CLAUDE_SUMMARY_TIMEOUT_MS
991
- }
992
- );
993
- if (exitCode !== 0) return null;
994
- const text = stdout.trim();
995
- if (text.length < 40) return null;
996
- return text;
997
- } catch {
998
- return null;
999
- }
1000
- }
1001
- function extractiveSummary(transcript) {
1002
- const lines = transcript.split("\n").map((l) => l.trim()).filter(Boolean);
1003
- const users = [];
1004
- const tools = [];
1005
- const agents = [];
1006
- let mode = "none";
1007
- for (const line of lines) {
1008
- if (line.startsWith("### User")) {
1009
- mode = "user";
1010
- continue;
1011
- }
1012
- if (line.startsWith("### Agent") || line.startsWith("## Prior")) {
1013
- mode = "agent";
1014
- continue;
1015
- }
1016
- if (line.startsWith("- ") && line.includes(":")) {
1017
- tools.push(line.replace(/^- /, ""));
1018
- continue;
1019
- }
1020
- if (mode === "user" && users.length < 8) {
1021
- users.push(line.slice(0, 240));
1022
- } else if (mode === "agent" && agents.length < 6) {
1023
- agents.push(line.slice(0, 240));
1024
- }
1025
- }
1026
- const uniqueTools = [...new Set(tools)].slice(0, 20);
1027
- const parts = [
1028
- "## Compacted context (extractive)",
1029
- "",
1030
- "### User goals",
1031
- ...users.length ? users.map((u) => `- ${u}`) : ["- (none captured)"],
1032
- "",
1033
- "### Recent agent notes",
1034
- ...agents.length ? agents.map((a) => `- ${a}`) : ["- (none captured)"]
1035
- ];
1036
- if (uniqueTools.length) {
1037
- parts.push("", "### Tools / files", ...uniqueTools.map((t) => `- ${t}`));
1038
- }
1039
- parts.push(
1040
- "",
1041
- "_Automatic extractive summary \u2014 Claude summarizer was unavailable._"
1042
- );
1043
- return parts.join("\n");
1044
- }
1045
- function clipSummary(text) {
1046
- if (text.length <= MAX_SUMMARY_CHARS) return text;
1047
- return `${text.slice(0, MAX_SUMMARY_CHARS)}
1048
-
1049
- [\u2026summary truncated\u2026]`;
1050
- }
1051
-
1052
- // src/composer/context-compact.ts
1053
- var CONTEXT_COMPACT_CHARS = 4e5;
1054
- var CONTEXT_KEEP_RECENT_CHARS = 24e3;
1055
- var CONTEXT_KEEP_RECENT_MESSAGES = 12;
1056
- var CONTEXT_MIN_MESSAGES = 10;
1057
- var SESSION_RESET_OCCUPANCY_TOKENS = 75e4;
1058
- function shouldCompactContext(messages, thresholds = {}) {
1059
- const maxChars = thresholds.maxChars ?? CONTEXT_COMPACT_CHARS;
1060
- const minMessages = thresholds.minMessages ?? CONTEXT_MIN_MESSAGES;
1061
- if (messages.length < minMessages) return false;
1062
- return estimateThreadChars(messages) >= maxChars;
1063
- }
1064
- function splitForCompaction(messages, thresholds = {}) {
1065
- const keepChars = thresholds.keepRecentChars ?? CONTEXT_KEEP_RECENT_CHARS;
1066
- const keepCount = thresholds.keepRecentMessages ?? CONTEXT_KEEP_RECENT_MESSAGES;
1067
- if (messages.length === 0) return { older: [], recent: [] };
1068
- let recentChars = 0;
1069
- let cut = messages.length;
1070
- for (let i = messages.length - 1; i >= 0; i--) {
1071
- const next = recentChars + estimateMessageChars(messages[i]);
1072
- const kept = messages.length - i;
1073
- if (kept >= keepCount && next > keepChars) break;
1074
- recentChars = next;
1075
- cut = i;
1076
- }
1077
- const minRecent = Math.min(keepCount, messages.length);
1078
- cut = Math.min(cut, messages.length - minRecent);
1079
- if (cut <= 0) return { older: [], recent: messages };
1080
- return {
1081
- older: messages.slice(0, cut),
1082
- recent: messages.slice(cut)
1083
- };
1084
- }
1085
- function formatToolPart(t, detail) {
1086
- if (detail === "summary") {
1087
- const label = t.description || t.detail || t.name;
1088
- const path = t.filePath ? ` (${t.filePath})` : "";
1089
- return `- ${t.name}: ${label}${path}`;
1090
- }
1091
- const lines = [`#### Tool: ${t.name}`, `Status: ${t.status}`];
1092
- if (t.description) lines.push(`Description: ${t.description}`);
1093
- if (t.filePath) lines.push(`Path: ${t.filePath}`);
1094
- if (t.input && Object.keys(t.input).length > 0) {
1095
- lines.push("Input:");
1096
- lines.push("```json");
1097
- lines.push(clipToolResultForStore(JSON.stringify(t.input, null, 2)) ?? "");
1098
- lines.push("```");
1099
- } else if (t.detail) {
1100
- lines.push(`Detail: ${t.detail}`);
1101
- }
1102
- if (t.result != null && t.result !== "") {
1103
- lines.push("Result:");
1104
- lines.push("```");
1105
- lines.push(t.result);
1106
- lines.push("```");
1107
- }
1108
- return lines.join("\n");
1109
- }
1110
- function formatMessagesAsTranscript(messages, opts) {
1111
- const tools = opts?.tools ?? "full";
1112
- const thinking = opts?.thinking ?? true;
1113
- return messages.map((m) => formatMessageBlock(m, { tools, thinking })).join("\n\n");
1114
- }
1115
- function formatMessageBlock(m, opts) {
1116
- if (m.role === "summary") return `## Prior summary
1117
- ${m.text}`;
1118
- if (m.role === "user") return `### User
1119
- ${m.text}`;
1120
- const bits = [];
1121
- if (m.text?.trim()) {
1122
- bits.push(`### Agent
1123
- ${m.text}`);
1124
- } else {
1125
- bits.push("### Agent");
1126
- }
1127
- if (opts.thinking) {
1128
- for (const th of m.parts ?? []) {
1129
- if (th.type !== "thinking" || !th.text.trim()) continue;
1130
- bits.push("Thinking:");
1131
- bits.push(th.text);
1132
- }
1133
- }
1134
- const toolParts = (m.parts ?? []).filter(
1135
- (p) => p.type === "tool"
1136
- );
1137
- if (opts.tools !== "none" && toolParts.length > 0) {
1138
- if (opts.tools === "summary") bits.push("Tools:");
1139
- for (const t of toolParts) {
1140
- bits.push(formatToolPart(t, opts.tools));
1141
- }
1142
- }
1143
- return bits.join("\n");
1144
- }
1145
- var SEED_MAX_CHARS = 6e4;
1146
- var SEED_FULL_TOOL_MESSAGES = 6;
1147
- function budgetSeedBlocks(messages, blocks, maxChars) {
1148
- const keep = new Array(blocks.length).fill(false);
1149
- let used = 0;
1150
- for (let i = 0; i < blocks.length; i++) {
1151
- if (messages[i].role === "summary") {
1152
- keep[i] = true;
1153
- used += blocks[i].length + 2;
1154
- }
1155
- }
1156
- for (let i = blocks.length - 1; i >= 0; i--) {
1157
- if (keep[i]) continue;
1158
- const next = used + blocks[i].length + 2;
1159
- if (next > maxChars && used > 0) continue;
1160
- keep[i] = true;
1161
- used = next;
1162
- }
1163
- const dropped = keep.filter((k) => !k).length;
1164
- const marker = `_(${dropped} older message${dropped === 1 ? "" : "s"} omitted for length)_`;
1165
- const kept = [];
1166
- let markerPlaced = false;
1167
- for (let i = 0; i < blocks.length; i++) {
1168
- if (!keep[i]) continue;
1169
- if (dropped > 0 && !markerPlaced && messages[i].role !== "summary") {
1170
- kept.push(marker);
1171
- markerPlaced = true;
1172
- }
1173
- kept.push(blocks[i]);
1174
- }
1175
- if (dropped > 0 && !markerPlaced) kept.push(marker);
1176
- return kept.join("\n\n");
1177
- }
1178
- function buildSessionSeed(messages, opts) {
1179
- if (messages.length === 0) return null;
1180
- const tools = opts?.tools ?? "full";
1181
- const fullCount = opts?.fullToolMessages ?? SEED_FULL_TOOL_MESSAGES;
1182
- const maxChars = opts?.maxChars ?? SEED_MAX_CHARS;
1183
- const olderTools = tools === "none" ? "none" : "summary";
1184
- const fullFrom = Math.max(0, messages.length - fullCount);
1185
- const blocks = messages.map(
1186
- (m, i) => formatMessageBlock(m, {
1187
- tools: i >= fullFrom ? tools : olderTools,
1188
- thinking: false
1189
- })
1190
- );
1191
- const body = budgetSeedBlocks(messages, blocks, maxChars);
1192
- if (!body.trim()) return null;
1193
- return [
1194
- "Sideboard conversation context (restored after compaction or a new session):",
1195
- "",
1196
- body,
1197
- "",
1198
- "Continue from this context. Do not repeat the summary unless asked."
1199
- ].join("\n");
1200
- }
1201
- var BRIGHTSY_SUMMARIZE_CONTEXT_TOOL = "summarize_context";
1202
- function extractBrightsyContextSummary(result) {
1203
- if (!result?.trim()) return null;
1204
- const trimmed = result.trim();
1205
- const lower = trimmed.toLowerCase();
1206
- if (lower.startsWith("context summarization failed") || lower.startsWith("nothing to summarize") || lower.startsWith("no messages found") || lower.startsWith("messages are required") || lower.startsWith("agent id is required")) {
1207
- return null;
1208
- }
1209
- try {
1210
- const parsed = JSON.parse(trimmed);
1211
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1212
- if (parsed.error != null && typeof parsed.context_summary !== "string") {
1213
- return null;
1214
- }
1215
- if (typeof parsed.context_summary === "string" && parsed.context_summary.trim()) {
1216
- return parsed.context_summary.trim();
1217
- }
1218
- return null;
1219
- }
1220
- } catch {
1221
- }
1222
- if (trimmed.includes('"error"') && !trimmed.includes("context_summary")) {
1223
- return null;
1224
- }
1225
- return trimmed;
1226
- }
1227
- function findLastBrightsyContextSummary(messages) {
1228
- for (let i = messages.length - 1; i >= 0; i--) {
1229
- const message = messages[i];
1230
- if (message?.role !== "agent") continue;
1231
- for (const part of message.parts ?? []) {
1232
- if (part.type !== "tool" || part.name !== BRIGHTSY_SUMMARIZE_CONTEXT_TOOL) {
1233
- continue;
1234
- }
1235
- if (part.status === "error") continue;
1236
- const text = extractBrightsyContextSummary(part.result);
1237
- if (text) return { index: i, text };
1238
- }
1239
- }
1240
- return null;
1241
- }
1242
- function messagesSinceLastBrightsyContextSummary(messages) {
1243
- const match = findLastBrightsyContextSummary(messages);
1244
- if (!match) return messages;
1245
- return messages.slice(match.index + 1);
1246
- }
1247
- function buildBrightsySessionSeed(messages, opts) {
1248
- const match = findLastBrightsyContextSummary(messages);
1249
- const tail = match ? messages.slice(match.index + 1) : messages;
1250
- const budget = Math.max(1e3, (opts?.maxChars ?? SEED_MAX_CHARS) - (match?.text.length ?? 0));
1251
- const tailBlocks = tail.map((m) => formatMessageBlock(m, { tools: "none", thinking: false }));
1252
- const body = budgetSeedBlocks(tail, tailBlocks, budget);
1253
- if (!match && !body.trim()) return null;
1254
- const blocks = [
1255
- "Sideboard conversation context (restored after compaction or a new session):",
1256
- ""
1257
- ];
1258
- if (match) {
1259
- blocks.push(`## Prior summary
1260
- ${match.text}`, "");
1261
- }
1262
- if (body.trim()) {
1263
- blocks.push(body, "");
1264
- }
1265
- blocks.push("Continue from this context. Do not repeat the summary unless asked.");
1266
- return blocks.join("\n");
1267
- }
1268
- function compactArchiveProse(message) {
1269
- if (message.origin === "continue") return null;
1270
- if (message.role === "summary") return null;
1271
- let text = (message.text ?? "").trim();
1272
- if (!text && message.role === "agent") {
1273
- text = lastAssistantMessageText(message.parts).trim();
1274
- }
1275
- if (!text || isInternalAgentStatusText(text)) return null;
1276
- return { role: message.role, text, ts: message.ts };
1277
- }
1278
- function messagesForCompactArchive(older) {
1279
- const out = [];
1280
- for (const message of older) {
1281
- const prose = compactArchiveProse(message);
1282
- if (prose) out.push(prose);
1283
- }
1284
- return out;
1285
- }
1286
- function appendCompactedMessages(existing, older) {
1287
- const next = messagesForCompactArchive(older);
1288
- if (!existing?.length) return next;
1289
- if (!next.length) return existing;
1290
- return [...existing, ...next];
1291
- }
1292
- function applyCompaction(messages, summaryText, thresholds = {}) {
1293
- const { older, recent } = splitForCompaction(messages, thresholds);
1294
- if (older.length === 0) return messages;
1295
- const text = summaryText.trim();
1296
- const summary = {
1297
- role: "summary",
1298
- text,
1299
- parts: [compactSummaryPart(text)],
1300
- ts: (/* @__PURE__ */ new Date()).toISOString()
1301
- };
1302
- return [summary, ...recent];
1303
- }
1304
- function lastRequestOccupancy(thread) {
1305
- for (let i = thread.messages.length - 1; i >= 0; i--) {
1306
- const usage = thread.messages[i]?.usage;
1307
- if (usage) return contextTokens(usage);
1308
- }
1309
- return 0;
1310
- }
1311
- function shouldResetSessionForOccupancy(thread, occupancyTokens = SESSION_RESET_OCCUPANCY_TOKENS) {
1312
- return lastRequestOccupancy(thread) >= occupancyTokens;
1313
- }
1314
- async function maybeCompactContext(thread, thresholds = {}, summarize = summarizeConversation) {
1315
- if (!shouldCompactContext(thread.messages, thresholds)) {
1316
- return { didCompact: false, thread };
1317
- }
1318
- const { older } = splitForCompaction(thread.messages, thresholds);
1319
- if (older.length === 0) {
1320
- return { didCompact: false, thread };
1321
- }
1322
- const transcript = formatMessagesAsTranscript(older, { tools: "summary" });
1323
- const { summary, method } = await summarize(transcript, {
1324
- cwd: thread.worktreePath
1325
- });
1326
- let messages = applyCompaction(thread.messages, summary, thresholds);
1327
- const compactedMessages = appendCompactedMessages(thread.compactedMessages, older);
1328
- const resetSession = shouldResetSessionForOccupancy({ messages: thread.messages });
1329
- if (resetSession) {
1330
- messages = applyForwardOccupancy(messages);
1331
- }
1332
- const next = {
1333
- ...thread,
1334
- messages,
1335
- ...compactedMessages.length ? { compactedMessages } : { compactedMessages: void 0 },
1336
- sessionId: resetSession ? null : thread.sessionId,
1337
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1338
- };
1339
- return {
1340
- didCompact: true,
1341
- thread: next,
1342
- summary,
1343
- method,
1344
- olderCount: older.length
1345
- };
1346
- }
1347
-
1348
- // src/threads/worktree-runtime.ts
1349
- function worktreeProcessPrefix(worktreePath) {
1350
- return `wt:${normalizeWorktreePath(worktreePath)}`;
1351
- }
1352
- function worktreeSetupProcessKey(worktreePath) {
1353
- return `${worktreeProcessPrefix(worktreePath)}:setup`;
1354
- }
1355
- function worktreeRunProcessKey(worktreePath, scriptName) {
1356
- return `${worktreeProcessPrefix(worktreePath)}:run:${scriptName}`;
1357
- }
1358
- function worktreeDevProcessKey(worktreePath) {
1359
- return `${worktreeProcessPrefix(worktreePath)}:dev`;
1360
- }
1361
- function isWorktreeRunProcessKey(key, worktreePath) {
1362
- const prefix = worktreeProcessPrefix(worktreePath);
1363
- return key.startsWith(`${prefix}:run:`) || key === `${prefix}:dev`;
1364
- }
1365
- function pickRichestSetupLog(snaps) {
1366
- let best;
1367
- for (const snap of snaps) {
1368
- if (!snap.output && !snap.running && snap.exitCode == null) continue;
1369
- if (!best) {
1370
- best = snap;
1371
- continue;
1372
- }
1373
- if (snap.running && !best.running) {
1374
- best = snap;
1375
- continue;
1376
- }
1377
- if (snap.running === best.running && snap.output.length > best.output.length) {
1378
- best = snap;
1379
- }
1380
- }
1381
- return best;
1382
- }
1383
- function resolveWorktreeSetupLog(shared, legacyChatLogs) {
1384
- if (shared.output || shared.running || shared.exitCode != null) return shared;
1385
- return pickRichestSetupLog(legacyChatLogs);
1386
- }
1387
- function mergeWorktreeActiveRuns(threads) {
1388
- const byName = /* @__PURE__ */ new Map();
1389
- let devPort = null;
1390
- for (const thread of threads) {
1391
- if (thread.devPort != null && devPort == null) devPort = thread.devPort;
1392
- for (const run2 of thread.activeRuns ?? []) {
1393
- if (!byName.has(run2.scriptName)) byName.set(run2.scriptName, run2);
1394
- }
1395
- }
1396
- return { activeRuns: [...byName.values()], devPort };
1397
- }
1398
-
1399
- // src/threads/chat-tabs.ts
1400
- function sameWorktreePath(a, b) {
1401
- return normalizeWorktreePath(a) === normalizeWorktreePath(b);
1402
- }
1403
- function takenTeamSlugsForChatTab(worktreePath) {
1404
- const key = normalizeWorktreePath(worktreePath);
1405
- const taken = /* @__PURE__ */ new Set();
1406
- const dir = worktreeNameFromPath(key);
1407
- if (dir) taken.add(dir.toLowerCase());
1408
- for (const sibling of threadsSharingWorktree(key)) {
1409
- for (const slug of takenSlugsFromThread(sibling)) {
1410
- taken.add(slug);
1411
- }
1412
- }
1413
- return [...taken];
1414
- }
1415
- function requireThread(idOrRef) {
1416
- const thread = findThreadByRef(idOrRef) ?? null;
1417
- if (!thread) throw new Error(`Thread not found: ${idOrRef}`);
1418
- return thread;
1419
- }
1420
- function worktreeBindingFrom(from) {
1421
- return {
1422
- worktreePath: normalizeWorktreePath(from.worktreePath),
1423
- repoPath: from.repoPath,
1424
- branchName: from.branchName,
1425
- sourceRef: from.sourceRef,
1426
- // Legacy pinned-repo orchestration: new tabs become normal worktree agents.
1427
- // Global: keep orchestration so chat tabs stay fleet orchestrators (MCP + Bash).
1428
- sourceType: isGlobalThread(from) ? "orchestration" : from.sourceType === "orchestration" ? "branch" : from.sourceType,
1429
- sourceIsFork: from.sourceIsFork,
1430
- parentThreadId: from.parentThreadId,
1431
- prUrl: from.prUrl,
1432
- prTitle: from.prTitle,
1433
- prState: from.prState,
1434
- prIsDraft: from.prIsDraft,
1435
- prAuthorLogin: from.prAuthorLogin,
1436
- prReviewerLogins: from.prReviewerLogins,
1437
- prReviewDecision: from.prReviewDecision,
1438
- prMergeable: from.prMergeable,
1439
- prMergeStateStatus: from.prMergeStateStatus,
1440
- prIsInMergeQueue: from.prIsInMergeQueue,
1441
- prLabels: from.prLabels,
1442
- prChecksFailed: from.prChecksFailed,
1443
- skipAutoArchiveOnMerge: from.skipAutoArchiveOnMerge,
1444
- stackId: from.stackId,
1445
- stackLayer: from.stackLayer,
1446
- cowboy: from.cowboy
1447
- };
1448
- }
1449
- function threadsSharingWorktree(worktreePath) {
1450
- if (!worktreePath) return [];
1451
- const key = normalizeWorktreePath(worktreePath);
1452
- return listThreads({ includeArchived: true }).filter(
1453
- (t) => Boolean(t.worktreePath) && sameWorktreePath(t.worktreePath, key) && t.status !== "archived"
1454
- ).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
1455
- }
1456
- function formatTranscriptMarkdown(title, messages) {
1457
- const body = formatMessagesAsTranscript(messages, { tools: "full" });
1458
- return [`# Transcript: ${title}`, "", `Forked ${(/* @__PURE__ */ new Date()).toISOString()}`, "", body].join("\n");
1459
- }
1460
- function forkMessageSlice(from, throughIndex) {
1461
- const through = throughIndex === void 0 ? from.messages.length - 1 : Math.min(throughIndex, from.messages.length - 1);
1462
- return through >= 0 ? from.messages.slice(0, through + 1) : [];
1463
- }
1464
- function buildForkTranscriptAttachment(baseTitle, messages) {
1465
- const title = baseTitle || "Chat";
1466
- return {
1467
- id: randomUUID(),
1468
- name: `Transcript of ${title}.md`,
1469
- kind: "transcript",
1470
- content: formatTranscriptMarkdown(title, messages)
1471
- };
1472
- }
1473
- function createChatTab(input) {
1474
- const from = requireThread(input.fromThreadId);
1475
- const binding = worktreeBindingFrom(from);
1476
- const explicitTitle = input.title?.trim();
1477
- const title = explicitTitle || allocateTeamName(takenTeamSlugsForChatTab(binding.worktreePath)).name;
1478
- const nextAgent = input.agent ?? from.agent;
1479
- if (isOrchestratorThread(from) || binding.sourceType === "orchestration") {
1480
- assertOrchestratorCapableAgent(nextAgent);
1481
- }
1482
- const singletonInbox = isSlackCoordinatorThread(from) || isCloudCoordinatorThread(from);
1483
- const thread = createEmptyThread({
1484
- title,
1485
- // Chat-tab nicknames (soccer team or explicit) must stick. Post-turn
1486
- // syncThreadBranchFromGit would otherwise rewrite every sibling to the
1487
- // shared worktree folder name (e.g. fork "Arsenal" → "Monaco").
1488
- userSetTitle: true,
1489
- ...binding,
1490
- // Slack / Brightsy cloud identity stays on the original chat. A + tab
1491
- // that copies `slack:team:user` would steal inbound DMs after you close
1492
- // the connected chat.
1493
- sourceRef: singletonInbox ? title : binding.sourceRef,
1494
- agent: nextAgent,
1495
- model: input.model !== void 0 ? input.model : input.agent && input.agent !== from.agent ? null : from.model,
1496
- effort: input.effort !== void 0 ? input.effort : from.effort,
1497
- fast: input.fast !== void 0 ? Boolean(input.fast) : from.fast,
1498
- planMode: from.planMode,
1499
- autonomy: input.autonomy ?? from.autonomy,
1500
- attachments: input.attachments ?? [],
1501
- status: "idle",
1502
- ...input.accountId !== void 0 ? { accountId: input.accountId } : {},
1503
- ...mergeWorktreeActiveRuns(threadsSharingWorktree(binding.worktreePath))
1504
- });
1505
- writeThread(thread);
1506
- return thread;
1507
- }
1508
- function forkChatTab(input) {
1509
- const from = requireThread(input.threadId);
1510
- const slice = forkMessageSlice(from, input.throughIndex);
1511
- const attachment = buildForkTranscriptAttachment(from.title || "Chat", slice);
1512
- const tab = createChatTab({
1513
- fromThreadId: input.threadId,
1514
- agent: input.agent ?? from.agent,
1515
- model: input.model,
1516
- title: input.title?.trim() || void 0,
1517
- attachments: [attachment]
1518
- });
1519
- if (isOrchestratorThread(from) && tab.parentThreadId !== from.id) {
1520
- const next = { ...tab, parentThreadId: from.id };
1521
- writeThread(next);
1522
- return next;
1523
- }
1524
- return tab;
1525
- }
1526
-
1527
- // src/review/review-write-gate.ts
1528
- var REVIEW_TAB_TITLE = "Review";
1529
- function hasLiveReviewSibling(thread, siblings = []) {
1530
- const selfId = thread.id;
1531
- return siblings.some(
1532
- (t) => t.id !== selfId && t.status !== "archived" && t.title.trim() === REVIEW_TAB_TITLE
1533
- );
1534
- }
1535
- function isReviewWriteGatedThread(thread, siblings) {
1536
- if (thread.sourceType === "pr" || thread.title.trim() === REVIEW_TAB_TITLE) {
1537
- return true;
1538
- }
1539
- return hasLiveReviewSibling(thread, siblings);
1540
- }
1541
- function formatReviewWriteGateDirective() {
1542
- return [
1543
- "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.",
1544
- "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.",
1545
- "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.",
1546
- "Do not request reviewers, change labels, or assign yourself unless they asked \u2014 those also notify the author."
1547
- ].join("\n");
1548
- }
1549
- function formatReviewWriteGateReminder() {
1550
- 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.";
1551
- }
1552
-
1553
- // src/review/review-request-template.ts
1554
- var REVIEW_REQUEST_TEMPLATE = `# Review guidelines:
1555
-
1556
- You are reviewing a proposed code change so a human can decide whether it is **ready to merge / land**. Findings matter, but the primary deliverable is a clear readiness recommendation \u2014 not a laundry list of style notes.
1557
-
1558
- ## Required outcome
1559
-
1560
- Start your reply with a **Recommendation** section using exactly one of:
1561
-
1562
- - **Approve** \u2014 ready to merge as-is (or with only trivial nits the author can ignore).
1563
- - **Approve with nits** \u2014 ready to merge; list only optional polish that should not block.
1564
- - **Request changes** \u2014 not ready; blocking issues must be fixed first.
1565
- - **Needs more information** \u2014 cannot judge readiness yet (missing context, incomplete diff, unclear intent).
1566
-
1567
- In 1\u20133 sentences, say **why** \u2014 grounded in correctness, risk, test coverage, and scope \u2014 not vibes. If you request changes, name the blockers explicitly.
1568
-
1569
- People running this review are asking \u201Ccan we ship this?\u201D Treat that as the question you answer first.
1570
-
1571
- ## Confirm before posting
1572
-
1573
- The report stays in this chat until the user posts it. 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.
1574
-
1575
- After the Recommendation is in chat, stop. Do not call ask_user. The user needs time to read the review and will type the next steps. If they already asked you to post a specific comment or update in this turn, that is confirmation.
1576
-
1577
- The PR or ticket author sees those writes immediately \u2014 a draft review will confuse them. Do not request reviewers, change labels, or assign yourself unless they asked.
1578
-
1579
- ## Findings
1580
-
1581
- Below are guidelines for determining whether an issue is worth flagging to the original author.
1582
-
1583
- These are not the final word. More specific guidelines elsewhere (developer message, user message, a file, etc.) override these.
1584
-
1585
- Flag something as a bug / blocking finding only when:
1586
-
1587
- 1. It meaningfully impacts the accuracy, performance, security, or maintainability of the code.
1588
- 2. The bug is discrete and actionable (not a vague codebase-wide complaint or a bundle of unrelated issues).
1589
- 3. Fixing it does not demand rigor absent from the rest of the codebase.
1590
- 4. The issue was introduced by this change (do not flag pre-existing bugs unless they are newly exposed by this PR).
1591
- 5. The author would likely fix it if made aware.
1592
- 6. It does not rely on unstated assumptions about the codebase or author intent.
1593
- 7. Speculative breakage is not enough \u2014 identify the other code that is provably affected.
1594
- 8. It is clearly not just an intentional change by the author.
1595
-
1596
- When flagging an issue, include a short accompanying comment:
1597
-
1598
- 1. Clear about why it is a problem.
1599
- 2. Severity must match reality \u2014 do not inflate.
1600
- 3. Brief: at most one paragraph; avoid unnecessary line breaks in prose.
1601
- 4. No code chunks longer than 3 lines; wrap code in inline ticks or a fenced block.
1602
- 5. Call out scenarios / environments / inputs needed to hit the bug when severity depends on them.
1603
- 6. Matter-of-fact tone \u2014 helpful assistant, not accusatory or effusive.
1604
- 7. Skimmable on first read.
1605
- 8. No empty flattery (\u201CGreat job\u2026\u201D, \u201CThanks for\u2026\u201D).
1606
-
1607
- HOW MANY FINDINGS TO RETURN:
1608
-
1609
- List every finding the author would fix if they knew about it. If nothing qualifies, say so and still give the Recommendation. Do not stop at the first finding.
1610
-
1611
- GUIDELINES:
1612
-
1613
- - Ignore trivial style unless it obscures meaning or violates documented standards.
1614
- - One comment per distinct issue (or a short multi-line range if needed).
1615
- - Use \`\`\`suggestion blocks ONLY for concrete replacement code (minimal lines; no commentary inside the block).
1616
- - In every \`\`\`suggestion block, preserve the exact leading whitespace of the replaced lines (spaces vs tabs, number of spaces).
1617
- - Do NOT introduce or remove outer indentation levels unless that is the actual fix.
1618
- - Separate **blocking** findings from **nits**. Only blocking findings should drive Request changes.
1619
-
1620
- The report appears in chat (and can become Sideboard diff comments). Avoid unnecessary location chatter in the body; keep line ranges as short as possible (prefer \u22645\u201310 lines).
1621
-
1622
- ## Getting the diff
1623
-
1624
- Use Sideboard's diff for this thread's worktree. Prefer the \`get_diff\` MCP tool (pass this thread's ref) for a compact summary, then read specific files with Read/Glob as needed. In the Sideboard desktop app, the Changes panel shows the same worktree diff.
1625
-
1626
- If the user asks you to address or read line comments they added in the Changes / file diff UI, those arrive as \`diff-comment\` attachments on the next turn \u2014 follow them precisely.
1627
-
1628
- ## Fallback: if you don't have access to the Sideboard diff tool
1629
-
1630
- If you don't have access to \`get_diff\`, use the following git commands to get the diff:
1631
-
1632
- \`\`\`bash
1633
- # Get the merge base between this branch and the target
1634
- MERGE_BASE=$(git merge-base origin/main HEAD)
1635
-
1636
- # Get the committed diff against the merge base
1637
- git diff $MERGE_BASE HEAD
1638
-
1639
- # Get any uncommitted changes (staged and unstaged)
1640
- git diff HEAD
1641
- \`\`\`
1642
-
1643
- Review the combination of both outputs: the first shows all committed changes on this branch relative to the target, and the second shows any uncommitted work in progress.
1644
-
1645
- No need to mention in your report whether or not you used one of the fallback strategies; it's usually irrelevant.
1646
-
1647
- ## Output format
1648
-
1649
- **1. Recommendation first** (required), then **2. Findings** (may be empty).
1650
-
1651
- Only report ONE finding per unique issue.
1652
-
1653
- <example>
1654
- ## Recommendation
1655
-
1656
- **Request changes** \u2014 The empty-input crash on load will break first-run users; fix that before merge. The unused helper is a nit and can wait.
1657
-
1658
- ## Findings
1659
-
1660
- ### **#1 Empty input causes crash** (blocking)
1661
-
1662
- If the input field is empty when the page loads, the app will crash.
1663
-
1664
- File: src/client/frontends/desktop/ui/Input.tsx
1665
-
1666
- ### **#2 Dead code** (nit)
1667
-
1668
- The getUserData function is now unused. It should be deleted.
1669
-
1670
- File: src/client/frontends/desktop/core/UserData.ts
1671
- </example>
1672
-
1673
- <example>
1674
- ## Recommendation
1675
-
1676
- **Approve** \u2014 Diff is scoped, behavior looks correct, and there are no blocking issues. Safe to merge.
1677
- </example>
1678
-
1679
- ## Growing the rules
1680
-
1681
- If a blocking issue is a missing or ambiguous repo rule that will recur, add one sentence to \`.claude/skills/review/SKILL.md\` when that skill already exists. Otherwise write it to \`.context/review.md\` (do not create a review skill). Do not only patch this diff when the same miss will happen again. Do not write new skills under \`.sideboard/skills/\`.
1682
- `;
1683
- var REVIEW_SKILL_PATH = ".claude/skills/review/SKILL.md";
1684
- var REVIEW_SKILL_NAME = "review";
1685
- var REVIEW_SKILL_FRONTMATTER = `---
1686
- name: review
1687
- description: >-
1688
- Merge-readiness review for this repository. Use when reviewing a PR, worktree
1689
- diff, or Sideboard Review (Approve / Approve with nits / Request changes).
1690
- ---
1691
-
1692
- `;
1693
- function wrapReviewSkillMarkdown(body) {
1694
- const trimmed = body.replace(/^\uFEFF/, "").trim();
1695
- const source = trimmed || REVIEW_REQUEST_TEMPLATE.trim();
1696
- if (/^---\s*\n[\s\S]*?\n---\s*\n/.test(source)) {
1697
- return source.endsWith("\n") ? source : `${source}
1698
- `;
1699
- }
1700
- return `${REVIEW_SKILL_FRONTMATTER}${source}
1701
- `;
1702
- }
1703
-
1704
- // src/review/request-review.ts
1705
- var REPO_REVIEW_PATH = ".sideboard/review.md";
1706
- var REPO_REVIEW_NAME = "review.md";
1707
- var CONTEXT_REVIEW_PATH = ".context/review.md";
1708
- var REVIEW_REQUEST_PATH = `${ATTACHMENTS_DIR}/Review request.md`;
1709
- var LEGACY_REVIEW_REQUEST_PATH = `${LEGACY_ATTACHMENTS_DIR}/Review request.md`;
1710
- var REVIEW_REQUEST_NAME = "Review request.md";
1711
- var REVIEW_REQUEST_PREFILL = "Review changes in this workspace.";
1712
- var LEGACY_REVIEW_TEMPLATE_MARKERS = [
1713
- "You are acting as a reviewer for a proposed code change made by another engineer.",
1714
- "HOW MANY FINDINGS TO RETURN:"
1715
- ];
1716
- function shouldRefreshReviewRequestTemplate(content) {
1717
- const trimmed = content.trim();
1718
- if (!trimmed) return true;
1719
- if (/##\s*Recommendation|ready to merge|Approve with nits|Request changes/i.test(trimmed)) {
1720
- return false;
1721
- }
1722
- return LEGACY_REVIEW_TEMPLATE_MARKERS.every((m) => trimmed.includes(m));
1723
- }
1724
- function readTextIfPresent(abs) {
1725
- if (!existsSync2(abs)) return null;
1726
- try {
1727
- const content = readFileSync(abs, "utf8");
1728
- return content.trim() ? content : null;
1729
- } catch {
1730
- return null;
1731
- }
1732
- }
1733
- function readLocalGuidelines(worktreePath) {
1734
- const localAbs = join3(worktreePath, REVIEW_REQUEST_PATH);
1735
- const localContent = readTextIfPresent(localAbs);
1736
- if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
1737
- return { path: REVIEW_REQUEST_PATH, content: localContent };
1738
- }
1739
- const legacyAbs = join3(worktreePath, LEGACY_REVIEW_REQUEST_PATH);
1740
- const legacyContent = readTextIfPresent(legacyAbs);
1741
- if (legacyContent && !shouldRefreshReviewRequestTemplate(legacyContent)) {
1742
- return { path: LEGACY_REVIEW_REQUEST_PATH, content: legacyContent };
1743
- }
1744
- return null;
1745
- }
1746
- function skillGuidelines(content, source) {
1747
- return {
1748
- path: REVIEW_SKILL_PATH,
1749
- name: REVIEW_SKILL_NAME,
1750
- content,
1751
- source
1752
- };
1753
- }
1754
- function contextGuidelines(content, source) {
1755
- return {
1756
- path: CONTEXT_REVIEW_PATH,
1757
- name: REPO_REVIEW_NAME,
1758
- content,
1759
- source
1760
- };
1761
- }
1762
- function writeContextGuidelines(worktreePath, content) {
1763
- const abs = join3(worktreePath, CONTEXT_REVIEW_PATH);
1764
- mkdirSync2(dirname(abs), { recursive: true });
1765
- const body = content.endsWith("\n") ? content : `${content}
1766
- `;
1767
- writeFileSync(abs, body, "utf8");
1768
- return body;
1769
- }
1770
- function readSideboardReview(worktreePath, repoPath) {
1771
- const fromWorktree = readTextIfPresent(join3(worktreePath, REPO_REVIEW_PATH));
1772
- if (fromWorktree) return fromWorktree;
1773
- const repo = repoPath?.replace(/\/+$/, "");
1774
- const wt = worktreePath.replace(/\/+$/, "");
1775
- if (!repo || repo === wt) return null;
1776
- return readTextIfPresent(join3(repo, REPO_REVIEW_PATH));
1777
- }
1778
- function ensureReviewGuidelinesFile(worktreePath, repoPath) {
1779
- const skillContent = readTextIfPresent(join3(worktreePath, REVIEW_SKILL_PATH));
1780
- if (skillContent) {
1781
- return { ...skillGuidelines(skillContent, "skill"), wrote: false };
1782
- }
1783
- const contextContent = readTextIfPresent(join3(worktreePath, CONTEXT_REVIEW_PATH));
1784
- if (contextContent && !shouldRefreshReviewRequestTemplate(contextContent)) {
1785
- return { ...contextGuidelines(contextContent, "local"), wrote: false };
1786
- }
1787
- const fromSideboard = readSideboardReview(worktreePath, repoPath);
1788
- if (fromSideboard && !shouldRefreshReviewRequestTemplate(fromSideboard)) {
1789
- const body2 = writeContextGuidelines(worktreePath, fromSideboard);
1790
- return { ...contextGuidelines(body2, "repo"), wrote: true };
1791
- }
1792
- const local = readLocalGuidelines(worktreePath);
1793
- const body = writeContextGuidelines(worktreePath, local?.content ?? REVIEW_REQUEST_TEMPLATE);
1794
- return {
1795
- ...contextGuidelines(body, local ? "local" : "stock"),
1796
- wrote: true
1797
- };
1798
- }
1799
- function ensureReviewSkillFile(worktreePath, repoPath) {
1800
- const g = ensureReviewGuidelinesFile(worktreePath, repoPath);
1801
- return { path: g.path, content: g.content, wrote: g.wrote };
1802
- }
1803
- function resolveReviewGuidelines(worktreePath, repoPath) {
1804
- return ensureReviewGuidelinesFile(worktreePath, repoPath);
1805
- }
1806
- function ensureReviewRequestFile(worktreePath, repoPath) {
1807
- return ensureReviewGuidelinesFile(worktreePath, repoPath);
1808
- }
1809
- function buildReviewRequestAttachment(content, opts) {
1810
- const path = opts?.path ?? REVIEW_SKILL_PATH;
1811
- const name = opts?.name ?? (path === REVIEW_SKILL_PATH ? REVIEW_SKILL_NAME : path === REPO_REVIEW_PATH || path === CONTEXT_REVIEW_PATH ? REPO_REVIEW_NAME : REVIEW_REQUEST_NAME);
1812
- return {
1813
- id: randomUUID2(),
1814
- name,
1815
- kind: "file",
1816
- path,
1817
- content
1818
- };
1819
- }
1820
- function readExistingReviewRequestFile(worktreePath) {
1821
- 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));
1822
- }
1823
- function canReuseIdleWorktreeAgentForReview(from, siblings) {
1824
- const live = siblings.filter((t) => t.status !== "archived");
1825
- if (live.length !== 1) return false;
1826
- const only = live[0];
1827
- if (only.id !== from.id) return false;
1828
- if (from.status !== "idle") return false;
1829
- if ((from.queue?.length ?? 0) > 0) return false;
1830
- return (from.messages?.length ?? 0) === 0 && (only.messages?.length ?? 0) === 0;
1831
- }
1832
- function reviewGuidelinesAttachment(from) {
1833
- const guidelines = resolveReviewGuidelines(from.worktreePath, from.repoPath);
1834
- return buildReviewRequestAttachment(guidelines.content, {
1835
- path: guidelines.path,
1836
- name: guidelines.name
1837
- });
1838
- }
1839
- async function requestReview(threadRef, send) {
1840
- const from = findThreadByRef(threadRef);
1841
- if (!from) throw new Error(`Thread not found: ${threadRef}`);
1842
- if (isOrchestratorThread(from)) {
1843
- throw new Error(
1844
- "request_review targets a worktree agent thread (not the orchestrator). Pass a child/worktree thread ref."
1845
- );
1846
- }
1847
- if (from.status === "archived") {
1848
- throw new Error(`Thread is archived: ${from.id}`);
1849
- }
1850
- const attachment = reviewGuidelinesAttachment(from);
1851
- const siblings = threadsSharingWorktree(from.worktreePath);
1852
- if (canReuseIdleWorktreeAgentForReview(from, siblings)) {
1853
- const reused = updateThread(from.id, {
1854
- title: REVIEW_TAB_TITLE,
1855
- userSetTitle: true,
1856
- attachments: [...from.attachments ?? [], attachment]
1857
- });
1858
- const started2 = await send(reused.id, REVIEW_REQUEST_PREFILL);
1859
- return { tab: started2, from: reused };
1860
- }
1861
- const tab = createChatTab({
1862
- fromThreadId: from.id,
1863
- title: REVIEW_TAB_TITLE,
1864
- attachments: [attachment]
1865
- });
1866
- const started = await send(tab.id, REVIEW_REQUEST_PREFILL);
1867
- return { tab: started, from };
1868
- }
1869
-
1870
- // src/hook/worktree-run-ports.ts
1871
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1872
- import { dirname as dirname2, join as join4 } from "path";
1873
- var RUN_PORTS_REL = ".context/.sideboard/run-ports.json";
1874
- function worktreeRunPortsPath(worktreePath) {
1875
- return join4(worktreePath, RUN_PORTS_REL);
1876
- }
1877
- function isPort(value) {
1878
- return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= 65535;
1879
- }
1880
- function parseFile(raw) {
1881
- try {
1882
- const parsed = JSON.parse(raw);
1883
- if (!parsed || typeof parsed !== "object") return {};
1884
- const scripts = parsed.scripts;
1885
- if (!scripts || typeof scripts !== "object") return {};
1886
- const next = {};
1887
- for (const [name, ports] of Object.entries(scripts)) {
1888
- if (!name || !Array.isArray(ports)) continue;
1889
- const cleaned = ports.filter(isPort);
1890
- if (cleaned.length) next[name] = cleaned;
1891
- }
1892
- return { scripts: next };
1893
- } catch {
1894
- return {};
1895
- }
1896
- }
1897
- function loadWorktreeRunPorts(worktreePath, scriptName) {
1898
- if (!worktreePath.trim() || !scriptName.trim()) return null;
1899
- try {
1900
- const raw = readFileSync2(worktreeRunPortsPath(worktreePath), "utf8");
1901
- const ports = parseFile(raw).scripts?.[scriptName];
1902
- return ports?.length ? ports : null;
1903
- } catch {
1904
- return null;
1905
- }
1906
- }
1907
- function saveWorktreeRunPorts(worktreePath, scriptName, ports) {
1908
- if (!worktreePath.trim() || !scriptName.trim()) return;
1909
- const cleaned = ports.filter(isPort);
1910
- if (!cleaned.length) return;
1911
- const path = worktreeRunPortsPath(worktreePath);
1912
- let current = {};
1913
- try {
1914
- current = parseFile(readFileSync2(path, "utf8"));
1915
- } catch {
1916
- current = {};
1917
- }
1918
- const scripts = { ...current.scripts ?? {} };
1919
- scripts[scriptName] = cleaned;
1920
- mkdirSync3(dirname2(path), { recursive: true });
1921
- writeFileSync2(path, `${JSON.stringify({ scripts }, null, 2)}
1922
- `, "utf8");
1923
- }
1924
-
1925
- // src/mcp/wait-for-job.ts
1926
- import {
1927
- appendFileSync as appendFileSync2,
1928
- existsSync as existsSync3,
1929
- mkdirSync as mkdirSync4,
1930
- readdirSync,
1931
- readFileSync as readFileSync3,
1932
- writeFileSync as writeFileSync3
1933
- } from "fs";
1934
- import { dirname as dirname3, join as join5 } from "path";
1935
- var MAX_JOB_CONTINUES = 8;
1936
- var MAX_JOB_DELTA_LINES = 80;
1937
- var MAX_JOB_DELTA_CHARS = 4e3;
1938
- var DEFERRED_DONE_MAX_CHARS = 480;
1939
- var GH_WATCH_REFRESH = "Refreshing run status every 3 seconds.";
1940
- var MCP_WAIT_JOB_STILL_RUNNING_HINT = "Job is still running. The type=log pane already appended this delta. Call wait_for_job again. If it is hanging, producing no useful output, or doing the wrong thing, call stop_job (same id) instead of looping forever. Do not end the turn or tell the user you will let them know later.";
1941
- var MCP_STOP_JOB_HINT = "Stopped. The type=log pane is status=failed with the last delta. Do not wait again unless you start a new command.";
1942
- var JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
1943
- function mcpWaitForJobTimeoutMs(requested) {
1944
- return mcpWaitForTurnTimeoutMs(requested);
1945
- }
1946
- function sanitizeDetachedJobId(id) {
1947
- const s = id.trim();
1948
- if (!JOB_ID_RE.test(s)) {
1949
- throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
1950
- }
1951
- return s;
1952
- }
1953
- function jobAlive(pid) {
1954
- if (!Number.isInteger(pid) || pid <= 0) return false;
1955
- try {
1956
- process.kill(pid, 0);
1957
- return true;
1958
- } catch {
1959
- return false;
1960
- }
1961
- }
1962
- function processGroupAlive(pgid) {
1963
- if (process.platform === "win32") return false;
1964
- if (!Number.isInteger(pgid) || pgid <= 1) return false;
1965
- try {
1966
- process.kill(-pgid, 0);
1967
- return true;
1968
- } catch (err) {
1969
- return Boolean(err && typeof err === "object" && "code" in err && err.code === "EPERM");
1970
- }
1971
- }
1972
- function jobTreeAlive(pid) {
1973
- if (pid == null) return false;
1974
- return jobAlive(pid) || processGroupAlive(pid);
1975
- }
1976
- function readIntFile(file) {
1977
- if (!existsSync3(file)) return null;
1978
- const n = Number.parseInt(readFileSync3(file, "utf8").trim(), 10);
1979
- return Number.isInteger(n) ? n : null;
1980
- }
1981
- function jobDir(root, id, legacy = false) {
1982
- return join5(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
1983
- }
1984
- function resolveJobDir(root, id) {
1985
- const modern = jobDir(root, id, false);
1986
- if (existsSync3(modern)) return modern;
1987
- const legacy = jobDir(root, id, true);
1988
- if (existsSync3(legacy)) return legacy;
1989
- return modern;
1990
- }
1991
- function listJobIdsIn(root, rel) {
1992
- const dir = join5(root, rel);
1993
- if (!existsSync3(dir)) return [];
1994
- return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
1995
- }
1996
- function listRunningDetachedJobs(worktreePath) {
1997
- const root = worktreePath.trim();
1998
- if (!root) return [];
1999
- const ids = /* @__PURE__ */ new Set([
2000
- ...listJobIdsIn(root, DETACHED_JOBS_DIR),
2001
- ...listJobIdsIn(root, LEGACY_DETACHED_JOBS_DIR)
2002
- ]);
2003
- const running = [];
2004
- for (const id of ids) {
2005
- const dir = resolveJobDir(root, id);
2006
- const pid = readIntFile(join5(dir, "pid"));
2007
- if (jobTreeAlive(pid)) running.push(id);
2008
- }
2009
- return running.sort();
2010
- }
2011
- function looksLikeDeferredDonePromise(text) {
2012
- const t = (text ?? "").trim();
2013
- if (!t || t.length > DEFERRED_DONE_MAX_CHARS) return false;
2014
- const unquoted = t.replace(/`[^`]*`/g, " ").replace(/["“”][^"“”]*["“”]/g, " ").replace(/\s+/g, " ").trim();
2015
- if (!unquoted) return false;
2016
- return /\b(i['’]?ll|i will)\s+let you know\b/i.test(unquoted) || /\blet you know when\b/i.test(unquoted) || /\b(check back|ping me)\s+when\b/i.test(unquoted) || /\bi(?:['’]ll| will)\s+(report|update you)\s+when\b/i.test(unquoted);
2017
- }
2018
- function isJobContinuePrompt(text) {
2019
- if (typeof text !== "string") return false;
2020
- const t = text.trim();
2021
- return t.startsWith("Detached job still running:") || t.startsWith("You ended the turn after promising to report later");
2022
- }
2023
- function collapseGhRunWatchLog(text) {
2024
- if (!text.includes(GH_WATCH_REFRESH)) return text;
2025
- const frames = text.split(/(?=Refreshing run status every 3 seconds\.)/);
2026
- const watchFrames = frames.filter((f) => f.includes(GH_WATCH_REFRESH));
2027
- if (watchFrames.length <= 1) return text;
2028
- const prefix = frames[0]?.includes(GH_WATCH_REFRESH) ? "" : frames[0] ?? "";
2029
- const last = watchFrames[watchFrames.length - 1] ?? "";
2030
- const omitted = watchFrames.length - 1;
2031
- const note = `(${omitted} identical gh run watch refresh${omitted === 1 ? "" : "es"} omitted)
2032
- `;
2033
- return [prefix.trimEnd(), note, last.trimStart()].filter(Boolean).join("\n");
2034
- }
2035
- function capJobLogDelta(text) {
2036
- const collapsed = collapseGhRunWatchLog(text);
2037
- const lines = collapsed.split("\n");
2038
- let out = lines.length > MAX_JOB_DELTA_LINES ? `\u2026(${lines.length - MAX_JOB_DELTA_LINES} earlier lines omitted)
2039
- ${lines.slice(-MAX_JOB_DELTA_LINES).join("\n")}` : collapsed;
2040
- if (out.length > MAX_JOB_DELTA_CHARS) {
2041
- out = `\u2026(truncated ${out.length} chars)
2042
- ${out.slice(-MAX_JOB_DELTA_CHARS)}`;
2043
- }
2044
- return out;
2045
- }
2046
- function formatJobStillRunningContinuePrompt(jobIds) {
2047
- const ids = jobIds.join(", ");
2048
- return [
2049
- `Detached job still running: ${ids}.`,
2050
- "Do not end this turn. Loop wait_for_job (same id) until stillRunning is false. The log pane updates from each wait result.",
2051
- "If the job is hanging or doing the wrong thing, call stop_job (same id) instead of looping forever.",
2052
- "Then report the result. Do not tell the user you will let them know later."
2053
- ].join(" ");
2054
- }
2055
- function formatDeferredDoneContinuePrompt() {
2056
- return [
2057
- "You ended the turn after promising to report later, but no detached job is running.",
2058
- "If tests/pack/deploy still need to run: start once with detached-job.cjs, then loop wait_for_job until stillRunning is false (the log pane updates from wait JSON).",
2059
- "Do not say you will let the user know later."
2060
- ].join(" ");
2061
- }
2062
- function turnWatchedDetachedJob(parts) {
2063
- return parts.some((p) => {
2064
- if (p.type !== "tool") return false;
2065
- if (/wait_for_job$/i.test(p.name ?? "") || /stop_job$/i.test(p.name ?? "")) return true;
2066
- const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
2067
- return /detached-job\.(?:js|cjs)\b/i.test(blob);
2068
- });
2069
- }
2070
- function planJobContinue(opts) {
2071
- if (opts.isOrchestrator) return { action: "none" };
2072
- if (opts.agent === "brightsy") return { action: "none" };
2073
- if (opts.queueLength > 0) return { action: "none" };
2074
- if (opts.continueCount >= MAX_JOB_CONTINUES) return { action: "none" };
2075
- const farewell = looksLikeDeferredDonePromise(opts.chatText);
2076
- if (opts.runningJobIds.length > 0 && (farewell || opts.watchedJob)) {
2077
- return {
2078
- action: "wait",
2079
- jobIds: opts.runningJobIds,
2080
- prompt: formatJobStillRunningContinuePrompt(opts.runningJobIds)
2081
- };
2082
- }
2083
- if (looksLikeDeferredDonePromise(opts.chatText) && !opts.alreadyNudged) {
2084
- return { action: "nudge", prompt: formatDeferredDoneContinuePrompt() };
2085
- }
2086
- return { action: "none" };
2087
- }
2088
- function tailProgress(logFile, maxLines = 12) {
2089
- if (!existsSync3(logFile)) return "(no log yet)";
2090
- const lines = readFileSync3(logFile, "utf8").split("\n");
2091
- return lines.slice(-maxLines).join("\n");
2092
- }
2093
- function lastProgressLine(progress) {
2094
- const last = progress.split("\n").map((l) => l.trim()).filter((l) => l && l !== "(no log yet)").at(-1);
2095
- return last ? last.slice(0, 72) : void 0;
2096
- }
2097
- function readLogLines(file) {
2098
- if (!existsSync3(file)) return [];
2099
- const lines = readFileSync3(file, "utf8").split("\n");
2100
- if (lines.length && lines[lines.length - 1] === "") lines.pop();
2101
- return lines;
2102
- }
2103
- function takeDelta(logFile, cursorFile) {
2104
- const lines = readLogLines(logFile);
2105
- const cursor = readIntFile(cursorFile) ?? 0;
2106
- const start = Math.min(Math.max(0, cursor), lines.length);
2107
- return { delta: capJobLogDelta(lines.slice(start).join("\n")), nextCursor: lines.length };
2108
- }
2109
- function snapshotJob(dir) {
2110
- const pid = readIntFile(join5(dir, "pid"));
2111
- const running = jobTreeAlive(pid);
2112
- return {
2113
- pid,
2114
- running,
2115
- exitCode: readIntFile(join5(dir, "exit")),
2116
- log: join5(dir, "log"),
2117
- cursor: join5(dir, "present.cursor"),
2118
- progress: tailProgress(join5(dir, "log"))
2119
- };
2120
- }
2121
- function toResult(id, snap, extra) {
2122
- const failed = extra?.failed === true || !snap.running && snap.exitCode != null && snap.exitCode !== 0;
2123
- const ok = !snap.running && snap.exitCode === 0;
2124
- const stillRunning = snap.running && !ok;
2125
- const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
2126
- try {
2127
- mkdirSync4(dirname3(snap.cursor), { recursive: true });
2128
- writeFileSync3(snap.cursor, `${nextCursor}
2129
- `);
2130
- } catch {
2131
- }
2132
- const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
2133
- const progress = extra?.progress ?? snap.progress;
2134
- return {
2135
- stillRunning,
2136
- ok,
2137
- failed: Boolean(failed && !stillRunning && !ok),
2138
- status,
2139
- id,
2140
- pid: snap.pid,
2141
- exitCode: snap.exitCode ?? void 0,
2142
- phase: lastProgressLine(progress),
2143
- delta,
2144
- progress,
2145
- hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
2146
- };
2147
- }
2148
- async function sleepMs(ms) {
2149
- await new Promise((resolve2) => setTimeout(resolve2, ms));
2150
- }
2151
- async function waitForDetachedJob(cwd, id, opts) {
2152
- const jobId = sanitizeDetachedJobId(id);
2153
- const root = cwd.trim() || process.cwd();
2154
- const dir = resolveJobDir(root, jobId);
2155
- const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
2156
- const sleep = opts?.sleep ?? sleepMs;
2157
- if (!existsSync3(dir)) {
2158
- return {
2159
- stillRunning: false,
2160
- ok: false,
2161
- failed: true,
2162
- status: "failed",
2163
- id: jobId,
2164
- delta: "",
2165
- progress: "No detached job. Start one first.",
2166
- hint: "Start with detached-job.cjs start <id> -- <command>, then call wait_for_job again."
2167
- };
2168
- }
2169
- const deadline = Date.now() + timeoutMs;
2170
- let snap = snapshotJob(dir);
2171
- if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
2172
- if (!snap.running && snap.pid == null && snap.progress === "(no log yet)") {
2173
- return toResult(jobId, snap, {
2174
- failed: true,
2175
- progress: "No detached job. Start one first."
2176
- });
2177
- }
2178
- while (Date.now() < deadline) {
2179
- snap = snapshotJob(dir);
2180
- if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
2181
- if (!snap.running) return toResult(jobId, snap, { failed: true });
2182
- await sleep(Math.min(2e3, Math.max(50, deadline - Date.now())));
2183
- }
2184
- snap = snapshotJob(dir);
2185
- return toResult(jobId, snap);
2186
- }
2187
- function killProcessTree(pid, signal) {
2188
- if (!Number.isInteger(pid) || pid <= 0) return;
2189
- if (process.platform !== "win32") {
2190
- try {
2191
- process.kill(-pid, signal);
2192
- return;
2193
- } catch {
2194
- }
2195
- }
2196
- try {
2197
- process.kill(pid, signal);
2198
- } catch {
2199
- }
2200
- }
2201
- function appendJobLog(logFile, line) {
2202
- try {
2203
- mkdirSync4(dirname3(logFile), { recursive: true });
2204
- const body = line.endsWith("\n") ? line : `${line}
2205
- `;
2206
- appendFileSync2(logFile, body);
2207
- } catch {
2208
- }
2209
- }
2210
- async function stopDetachedJob(cwd, id, opts) {
2211
- const jobId = sanitizeDetachedJobId(id);
2212
- const root = cwd.trim() || process.cwd();
2213
- const dir = resolveJobDir(root, jobId);
2214
- const sleep = opts?.sleep ?? sleepMs;
2215
- const why = opts?.reason?.trim() ?? "";
2216
- if (!existsSync3(dir)) {
2217
- return {
2218
- stillRunning: false,
2219
- ok: false,
2220
- failed: true,
2221
- status: "failed",
2222
- id: jobId,
2223
- delta: "",
2224
- progress: "No detached job. Start one first.",
2225
- hint: "Start with detached-job.cjs start <id> -- <command>, then wait_for_job or stop_job.",
2226
- stopped: false,
2227
- reason: "not-found"
2228
- };
2229
- }
2230
- let snap = snapshotJob(dir);
2231
- if (!snap.running) {
2232
- const result2 = toResult(jobId, snap);
2233
- return {
2234
- ...result2,
2235
- stopped: false,
2236
- reason: "not-running",
2237
- hint: "That job is already finished. Read the log; do not wait again unless you start a new command."
2238
- };
2239
- }
2240
- appendJobLog(snap.log, `$ stop${why ? `: ${why}` : ""}`);
2241
- const targetPid = snap.pid;
2242
- if (targetPid != null) killProcessTree(targetPid, "SIGTERM");
2243
- const graceMs = Number.isFinite(opts?.graceMs) ? Math.max(50, opts.graceMs) : 2e3;
2244
- const deadline = Date.now() + graceMs;
2245
- while (Date.now() < deadline && jobTreeAlive(targetPid)) {
2246
- await sleep(Math.min(100, Math.max(20, deadline - Date.now())));
2247
- }
2248
- if (targetPid != null) {
2249
- killProcessTree(targetPid, "SIGKILL");
2250
- await sleep(100);
2251
- }
2252
- const exitFile = join5(dir, "exit");
2253
- if (!existsSync3(exitFile)) {
2254
- try {
2255
- writeFileSync3(exitFile, "1\n");
2256
- } catch {
2257
- }
2258
- }
2259
- snap = snapshotJob(dir);
2260
- const stillRunning = jobTreeAlive(targetPid) || snap.running;
2261
- const result = toResult(jobId, snap, { failed: true });
2262
- const stopped = !stillRunning;
2263
- return {
2264
- ...result,
2265
- stillRunning,
2266
- ok: false,
2267
- failed: true,
2268
- status: stillRunning ? "running" : "failed",
2269
- stopped,
2270
- reason: stopped ? "stopped" : "still-running",
2271
- stopReason: why || void 0,
2272
- hint: stopped ? MCP_STOP_JOB_HINT : MCP_WAIT_JOB_STILL_RUNNING_HINT
2273
- };
2274
- }
2275
-
2276
- // src/hook/conductor.ts
2277
- var PORT_RANGE_SIZE = 10;
2278
- var RECLAIM_RETRY_DELAY_MS = 40;
2279
- var RECLAIM_TRIES = 8;
2280
- function matchSimpleGlob(pattern, name) {
2281
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
2282
- return new RegExp(`^${escaped}$`).test(name);
2283
- }
2284
- function readWorktreeInclude(repoPath) {
2285
- const path = join6(repoPath, ".worktreeinclude");
2286
- if (!existsSync4(path)) return [];
2287
- return readFileSync4(path, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
2288
- }
2289
- function resolveFilesToCopy(repoPath) {
2290
- const fromInclude = readWorktreeInclude(repoPath);
2291
- if (fromInclude.length) return fromInclude;
2292
- const settings = loadRepoSettings(repoPath);
2293
- if (settings?.filesToCopy?.length) return settings.filesToCopy;
2294
- if (settings?.fileIncludeGlobs?.length) {
2295
- const matched = [];
2296
- try {
2297
- for (const entry of readdirSync2(repoPath, { withFileTypes: true })) {
2298
- if (!entry.isFile()) continue;
2299
- for (const glob of settings.fileIncludeGlobs) {
2300
- if (matchSimpleGlob(glob, entry.name) || matchSimpleGlob(basename(glob), entry.name)) {
2301
- matched.push(entry.name);
2302
- break;
2303
- }
2304
- }
2305
- }
2306
- } catch {
2307
- }
2308
- if (matched.length) return [...new Set(matched)];
2309
- }
2310
- const defaults = [];
2311
- try {
2312
- for (const entry of readdirSync2(repoPath, { withFileTypes: true })) {
2313
- if (entry.isFile() && entry.name.startsWith(".env")) {
2314
- defaults.push(entry.name);
2315
- }
2316
- }
2317
- } catch {
2318
- }
2319
- if (defaults.length) return defaults;
2320
- return [".env.local", ".env"];
2321
- }
2322
- function copyConfiguredFiles(repoPath, worktreePath) {
2323
- const patterns = resolveFilesToCopy(repoPath);
2324
- const copied = [];
2325
- for (const rel of patterns) {
2326
- const src = join6(repoPath, rel);
2327
- if (!existsSync4(src)) continue;
2328
- const dest = join6(worktreePath, rel);
2329
- mkdirSync5(dirname4(dest), { recursive: true });
2330
- copyFileSync(src, dest);
2331
- copied.push(rel);
2332
- }
2333
- return copied;
2334
- }
2335
- var cachedLoginEnv = null;
2336
- async function captureLoginEnv() {
2337
- if (cachedLoginEnv) return { ...cachedLoginEnv };
2338
- const shell = process.env.SHELL?.trim() || (process.platform === "darwin" ? "/bin/zsh" : "/bin/bash");
2339
- try {
2340
- const result = await execa2(shell, ["-l", "-c", "env -0"], {
2341
- reject: false,
2342
- timeout: 5e3
2343
- });
2344
- if (result.exitCode === 0 && result.stdout) {
2345
- const env = { ...process.env };
2346
- for (const entry of result.stdout.split("\0")) {
2347
- if (!entry) continue;
2348
- const eq = entry.indexOf("=");
2349
- if (eq <= 0) continue;
2350
- const key = entry.slice(0, eq);
2351
- const value = entry.slice(eq + 1);
2352
- if (key) env[key] = value;
2353
- }
2354
- cachedLoginEnv = env;
2355
- return { ...env };
2356
- }
2357
- } catch {
2358
- }
2359
- cachedLoginEnv = { ...process.env };
2360
- return { ...cachedLoginEnv };
2361
- }
2362
- function buildWorkspaceScriptEnv(opts, baseEnv) {
2363
- const env = stripNestedElectronEnv({
2364
- ...baseEnv ?? process.env
2365
- });
2366
- const name = opts.workspaceName ?? basename(opts.worktreePath);
2367
- const ports = opts.ports ?? [];
2368
- const primary = ports[0];
2369
- env.SIDEBOARD_WORKSPACE_NAME = name;
2370
- env.SIDEBOARD_WORKSPACE_PATH = opts.worktreePath;
2371
- env.SIDEBOARD_ROOT_PATH = opts.repoPath;
2372
- env.SIDEBOARD_IS_LOCAL = "1";
2373
- if (opts.defaultBranch) env.SIDEBOARD_DEFAULT_BRANCH = opts.defaultBranch;
2374
- env.CONDUCTOR_WORKSPACE_NAME = name;
2375
- env.CONDUCTOR_WORKSPACE_PATH = opts.worktreePath;
2376
- env.CONDUCTOR_ROOT_PATH = opts.repoPath;
2377
- env.CONDUCTOR_IS_LOCAL = "1";
2378
- if (opts.defaultBranch) env.CONDUCTOR_DEFAULT_BRANCH = opts.defaultBranch;
2379
- if (primary != null) {
2380
- env.SIDEBOARD_PORT = String(primary);
2381
- env.CONDUCTOR_PORT = String(primary);
2382
- env.PORT = String(primary);
2383
- for (let i = 1; i < ports.length; i++) {
2384
- const p = ports[i];
2385
- env[`SIDEBOARD_PORT_${i}`] = String(p);
2386
- env[`CONDUCTOR_PORT_${i}`] = String(p);
2387
- }
2388
- }
2389
- applyWorktreePkgCacheEnv(env, opts.worktreePath);
2390
- return env;
2391
- }
2392
- function pipeLines(stream, onLine) {
2393
- if (!stream || !onLine) return;
2394
- const rl = createInterface2({ input: stream });
2395
- rl.on("line", onLine);
2396
- }
2397
- function killableListenerPids(raw, selfPid = process.pid) {
2398
- const pids = [];
2399
- for (const token of raw.trim().split(/\s+/)) {
2400
- const pid = Number(token);
2401
- if (!Number.isFinite(pid) || pid <= 0 || pid === selfPid) continue;
2402
- pids.push(pid);
2403
- }
2404
- return pids;
2405
- }
2406
- function listUnixListenerPids(port, selfPid) {
2407
- try {
2408
- const raw = execFileSync("lsof", [`-tiTCP:${port}`, "-sTCP:LISTEN"], {
2409
- encoding: "utf8",
2410
- stdio: ["ignore", "pipe", "ignore"],
2411
- timeout: 2e3
2412
- });
2413
- return killableListenerPids(raw, selfPid);
2414
- } catch {
2415
- return [];
2416
- }
2417
- }
2418
- function listenerCwd(pid) {
2419
- if (!Number.isFinite(pid) || pid <= 0) return null;
2420
- try {
2421
- const raw = execFileSync("lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], {
2422
- encoding: "utf8",
2423
- stdio: ["ignore", "pipe", "ignore"],
2424
- timeout: 2e3
2425
- });
2426
- for (const line of raw.split("\n")) {
2427
- if (line.startsWith("n") && line.length > 1) return line.slice(1);
2428
- }
2429
- } catch {
2430
- }
2431
- return null;
2432
- }
2433
- function realpathOrResolve(path) {
2434
- try {
2435
- return realpathSync(path);
2436
- } catch {
2437
- return resolve(path);
2438
- }
2439
- }
2440
- function isOtherWorktreeListenerCwd(cwd, worktreePath) {
2441
- const wt = realpathOrResolve(worktreePath);
2442
- const c = realpathOrResolve(cwd);
2443
- if (c === wt || c.startsWith(wt + sep)) return false;
2444
- const parent = dirname4(wt);
2445
- const thisName = basename(wt);
2446
- if (!thisName || c === parent) return false;
2447
- if (!c.startsWith(parent + sep)) return false;
2448
- const first = c.slice(parent.length + 1).split(sep)[0];
2449
- return Boolean(first) && first !== thisName;
2450
- }
2451
- function signalPid(pid, signal) {
2452
- try {
2453
- process.kill(pid, signal);
2454
- } catch {
2455
- }
2456
- }
2457
- function isThisWorktreeListenerCwd(cwd, worktreePath) {
2458
- const wt = realpathOrResolve(worktreePath);
2459
- const c = realpathOrResolve(cwd);
2460
- return c === wt || c.startsWith(wt + sep);
2461
- }
2462
- function killDetachedRunTree(pid, worktreePath) {
2463
- if (!Number.isFinite(pid) || pid <= 1 || pid === process.pid) return;
2464
- const cwd = listenerCwd(pid);
2465
- if (cwd) {
2466
- if (!isThisWorktreeListenerCwd(cwd, worktreePath)) return;
2467
- } else if (!processGroupAlive(pid)) {
2468
- return;
2469
- }
2470
- try {
2471
- process.kill(-pid, "SIGTERM");
2472
- } catch {
2473
- signalPid(pid, "SIGTERM");
2474
- }
2475
- }
2476
- function killListenersOnPorts(ports) {
2477
- const selfPid = process.pid;
2478
- for (const port of ports) {
2479
- if (!Number.isFinite(port) || port <= 0) continue;
2480
- try {
2481
- if (process.platform === "win32") {
2482
- execFileSync(
2483
- "powershell",
2484
- [
2485
- "-NoProfile",
2486
- "-Command",
2487
- `Get-NetTCPConnection -LocalPort ${port} -ErrorAction SilentlyContinue | Where-Object { $_.OwningProcess -ne ${selfPid} } | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }`
2488
- ],
2489
- { stdio: "ignore", timeout: 3e3 }
2490
- );
2491
- continue;
2492
- }
2493
- for (const pid of listUnixListenerPids(port, selfPid)) {
2494
- signalPid(pid, "SIGTERM");
2495
- }
2496
- } catch {
2497
- }
654
+ return "This phrase is the explicit ask to mark this thread's draft pull request ready for review on GitHub (`gh pr ready -R <origin-owner/name>` or `gh pr ready`). Update title/body if the purpose drifted. Do not merge.";
655
+ case "merge":
656
+ return "This phrase is the explicit ask to merge this thread's open pull request on GitHub. If `gh stack view` shows a stack, use `gh stack merge`; otherwise `gh pr merge` (respect repo defaults / squash vs merge). Do not force-push main/master or merge locally into the main checkout.";
2498
657
  }
2499
658
  }
2500
- function killStaleWorktreeListeners(ports, worktreePath, signal = "SIGTERM") {
2501
- if (!worktreePath.trim()) {
2502
- killListenersOnPorts(ports);
2503
- return;
2504
- }
2505
- if (process.platform === "win32") {
2506
- killListenersOnPorts(ports);
2507
- return;
2508
- }
2509
- const selfPid = process.pid;
2510
- for (const port of ports) {
2511
- if (!Number.isFinite(port) || port <= 0) continue;
2512
- for (const pid of listUnixListenerPids(port, selfPid)) {
2513
- const cwd = listenerCwd(pid);
2514
- if (cwd && isOtherWorktreeListenerCwd(cwd, worktreePath)) continue;
2515
- signalPid(pid, signal);
659
+ function expandCanonicalGitRequest(prompt) {
660
+ const trimmed = prompt.trim();
661
+ let action = null;
662
+ let base = null;
663
+ for (const candidate of AGENT_GIT_ACTIONS) {
664
+ if (candidate === "resolve-conflicts") continue;
665
+ if (agentGitPrompt(candidate) === trimmed) {
666
+ action = candidate;
667
+ break;
2516
668
  }
2517
669
  }
2518
- }
2519
- function killScriptTree(child, ports = []) {
2520
- const pid = child.pid;
2521
- if (pid) {
2522
- try {
2523
- if (process.platform === "win32") {
2524
- void execa2("taskkill", ["/pid", String(pid), "/T", "/F"], { reject: false });
2525
- } else {
2526
- process.kill(-pid, "SIGTERM");
2527
- setTimeout(() => {
2528
- try {
2529
- process.kill(-pid, "SIGKILL");
2530
- } catch {
2531
- }
2532
- }, 2500).unref?.();
2533
- }
2534
- } catch {
2535
- try {
2536
- child.kill("SIGTERM");
2537
- } catch {
2538
- }
670
+ if (!action) {
671
+ const m = RESOLVE_CONFLICTS_RE.exec(trimmed);
672
+ if (m) {
673
+ action = "resolve-conflicts";
674
+ base = m[1] ?? null;
2539
675
  }
2540
676
  }
2541
- killListenersOnPorts(ports);
2542
- }
2543
- async function spawnWorkspaceScript(command, opts) {
2544
- const loginEnv = await captureLoginEnv();
2545
- const env = buildWorkspaceScriptEnv(
2546
- {
2547
- worktreePath: opts.worktreePath,
2548
- repoPath: opts.repoPath,
2549
- workspaceName: opts.workspaceName,
2550
- defaultBranch: opts.defaultBranch,
2551
- ports: opts.ports
2552
- },
2553
- loginEnv
2554
- );
2555
- try {
2556
- mergeAgentGitAuthEnv(
2557
- env,
2558
- await resolveAgentGitAuthEnv(env, { cwd: opts.worktreePath })
2559
- );
2560
- } catch {
2561
- }
2562
- if (opts.heldPorts?.length) {
2563
- await Promise.all(opts.heldPorts.map((h) => h.release()));
677
+ let expansion = action ? gitActionExpansion(action, base) : null;
678
+ if (!expansion && trimmed === "Address review comments.") {
679
+ expansion = "Read the PR review feedback (`gh pr view --comments`, `gh api` review comments), make the requested changes, commit, and push.";
680
+ } else if (!expansion && FIX_CI_RE.test(trimmed)) {
681
+ expansion = "Investigate that failing check (`gh pr checks`, `gh run view --log-failed`), fix it, commit, and push. Loop only if a goal to keep going until it passes was given.";
2564
682
  }
2565
- const shell = process.platform === "darwin" ? "zsh" : "bash";
2566
- const child = execa2(shell, ["-lc", command], {
2567
- cwd: opts.worktreePath,
2568
- reject: false,
2569
- env,
2570
- // Own process group so Stop can tear down the whole tree (not just the shell).
2571
- // There is no settings.toml `stop=` / teardown hook for run scripts.
2572
- ...process.platform === "win32" ? {} : { detached: true }
2573
- });
2574
- pipeLines(child.stdout, opts.onLine);
2575
- pipeLines(child.stderr, opts.onLine);
2576
- const ports = opts.ports ?? [];
2577
- return {
2578
- pid: child.pid,
2579
- kill: () => killScriptTree(child, ports),
2580
- done: child.then((r) => r.exitCode ?? null),
2581
- child
2582
- };
683
+ if (!expansion) return prompt;
684
+ return `${trimmed}
685
+
686
+ (Sideboard git request \u2014 do not ask for clarification: ${expansion})`;
2583
687
  }
2584
- async function attachAbort(handle, signal) {
2585
- if (signal) {
2586
- const onAbort = () => handle.kill();
2587
- if (signal.aborted) onAbort();
2588
- else signal.addEventListener("abort", onAbort, { once: true });
2589
- }
2590
- return handle.done;
688
+ var FIX_CI_RE = /^Fix CI: .+\.$/;
689
+
690
+ // src/git/pr-merge-archive.ts
691
+ function normalizePrState(state) {
692
+ return (state ?? "").trim().toUpperCase();
2591
693
  }
2592
- async function runSetupScript(repoPath, worktreePath, onLine, opts) {
2593
- const settings = loadWorkspaceSettings(worktreePath, repoPath);
2594
- if (!settings?.setup) return { ran: false, exitCode: null, source: null };
2595
- const source = workspaceSettingsSourceLabel(worktreePath, repoPath);
2596
- onLine?.(`[setup] ${source ?? "settings.toml"}`);
2597
- const handle = await spawnWorkspaceScript(settings.setup, {
2598
- worktreePath,
2599
- repoPath,
2600
- defaultBranch: opts?.defaultBranch,
2601
- onLine
2602
- });
2603
- const exitCode = await attachAbort(handle, opts?.signal);
2604
- return {
2605
- ran: true,
2606
- exitCode,
2607
- source,
2608
- kill: handle.kill
2609
- };
694
+ function samePrIdentity(a, b) {
695
+ const left = (a ?? "").trim().replace(/\/+$/, "");
696
+ const right = (b ?? "").trim().replace(/\/+$/, "");
697
+ if (!left || !right) return false;
698
+ if (left === right) return true;
699
+ const ln = left.match(/\/pull\/(\d+)/i)?.[1];
700
+ const rn = right.match(/\/pull\/(\d+)/i)?.[1];
701
+ return Boolean(ln && rn && ln === rn);
2610
702
  }
2611
- async function runConventionSetup(repoPath, worktreePath, onLine, opts) {
2612
- const found = findConventionSetup(worktreePath, repoPath);
2613
- if (!found) return { ran: false, exitCode: null, source: null };
2614
- onLine?.(`[setup] ${found.source}`);
2615
- const handle = await spawnWorkspaceScript(found.command, {
2616
- worktreePath,
2617
- repoPath,
2618
- defaultBranch: opts?.defaultBranch,
2619
- onLine
2620
- });
2621
- const exitCode = await attachAbort(handle, opts?.signal);
2622
- return {
2623
- ran: true,
2624
- exitCode,
2625
- source: found.source,
2626
- kill: handle.kill
2627
- };
703
+ function shouldStopRunOnPrRetarget(previousPrUrl, nextPrUrl) {
704
+ const prev = (previousPrUrl ?? "").trim();
705
+ const next = (nextPrUrl ?? "").trim();
706
+ if (!prev || !next) return false;
707
+ return !samePrIdentity(prev, next);
708
+ }
709
+ function shouldPersistFetchedPrMeta(opts) {
710
+ const meta = opts.metaUrl?.trim() || "";
711
+ if (!meta) return false;
712
+ const head = opts.headPrUrl?.trim() || "";
713
+ if (head) return samePrIdentity(meta, head);
714
+ const live = opts.livePrUrl?.trim() || "";
715
+ if (live) return samePrIdentity(meta, live);
716
+ return true;
2628
717
  }
2629
- async function runWorkspaceSetup(repoPath, worktreePath, onLine, opts) {
2630
- await ensureWorktreeSideboardIgnored(worktreePath);
2631
- const review = ensureReviewGuidelinesFile(worktreePath, repoPath);
2632
- if (review.wrote) {
2633
- onLine?.(`[setup] wrote ${review.path} \u2014 local Review guidelines`);
718
+ function sameLoginList(a, b) {
719
+ const left = [...a ?? []].map((s) => s.trim().toLowerCase()).filter(Boolean).sort();
720
+ const right = [...b ?? []].map((s) => s.trim().toLowerCase()).filter(Boolean).sort();
721
+ return left.length === right.length && left.every((login, i) => login === right[i]);
722
+ }
723
+ function threadPrMetaPatch(thread, meta) {
724
+ const prevState = normalizePrState(thread.prState);
725
+ const nextState = normalizePrState(meta.state);
726
+ const patch = {};
727
+ if (meta.url && meta.url !== thread.prUrl) patch.prUrl = meta.url;
728
+ if (meta.title && meta.title !== thread.prTitle) patch.prTitle = meta.title;
729
+ if (nextState && nextState !== prevState) patch.prState = nextState;
730
+ const nextDraft = Boolean(meta.isDraft) && nextState !== "MERGED" && nextState !== "CLOSED";
731
+ if (nextDraft !== Boolean(thread.prIsDraft)) patch.prIsDraft = nextDraft;
732
+ const nextAuthor = meta.authorLogin?.trim() || "";
733
+ if (nextAuthor && nextAuthor !== (thread.prAuthorLogin ?? "").trim()) {
734
+ patch.prAuthorLogin = nextAuthor;
2634
735
  }
2635
- let setup = await runSetupScript(repoPath, worktreePath, onLine, opts);
2636
- if (!setup.ran) {
2637
- setup = await runCursorWorktreeSetup(repoPath, worktreePath, onLine);
736
+ const nextReviewers = meta.reviewerLogins ?? [];
737
+ if (!sameLoginList(nextReviewers, thread.prReviewerLogins)) {
738
+ patch.prReviewerLogins = nextReviewers;
2638
739
  }
2639
- if (!setup.ran) {
2640
- setup = await runConventionSetup(repoPath, worktreePath, onLine, opts);
740
+ const nextDecision = meta.reviewDecision ?? null;
741
+ if ((thread.prReviewDecision ?? null) !== nextDecision) {
742
+ patch.prReviewDecision = nextDecision;
2641
743
  }
2642
- return setup;
2643
- }
2644
- async function runArchiveScript(repoPath, worktreePath, onLine) {
2645
- const settings = loadWorkspaceSettings(worktreePath, repoPath);
2646
- if (!settings?.archive) return { ran: false, exitCode: null };
2647
- const handle = await spawnWorkspaceScript(settings.archive, {
2648
- worktreePath,
2649
- repoPath,
2650
- onLine
2651
- });
2652
- const exitCode = await handle.done;
2653
- return { ran: true, exitCode };
2654
- }
2655
- function listRunScripts(worktreePath, repoPath) {
2656
- const settings = loadWorkspaceSettings(worktreePath, repoPath);
2657
- if (!settings?.runScripts.length) return [];
2658
- return settings.runScripts.filter((s) => {
2659
- if (!s.availableIn?.length) return true;
2660
- return s.availableIn.includes("local");
2661
- });
2662
- }
2663
- function getDefaultRunScript(worktreePath, repoPath) {
2664
- const scripts = listRunScripts(worktreePath, repoPath);
2665
- if (!scripts.length) return null;
2666
- return scripts.find((s) => s.default === true) ?? scripts.find((s) => s.name === "dev") ?? scripts.find((s) => s.name !== "all") ?? scripts[0] ?? null;
2667
- }
2668
- function getRunScript(worktreePath, repoPath, name) {
2669
- const scripts = listRunScripts(worktreePath, repoPath);
2670
- if (!scripts.length) return null;
2671
- if (name) {
2672
- return scripts.find((s) => s.name === name) ?? null;
2673
- }
2674
- return getDefaultRunScript(worktreePath, repoPath);
2675
- }
2676
- function getRunMode(worktreePath, repoPath) {
2677
- return loadWorkspaceSettings(worktreePath, repoPath)?.runMode ?? "concurrent";
2678
- }
2679
- async function reservePort() {
2680
- return new Promise((resolve2, reject) => {
2681
- const server = createServer();
2682
- let released = false;
2683
- const release = () => new Promise((res) => {
2684
- if (released) {
2685
- res();
2686
- return;
2687
- }
2688
- released = true;
2689
- server.close(() => res());
2690
- });
2691
- server.listen(0, "127.0.0.1", () => {
2692
- const addr = server.address();
2693
- if (!addr || typeof addr === "string") {
2694
- void release().then(() => reject(new Error("Failed to allocate port")));
2695
- return;
2696
- }
2697
- resolve2({ port: addr.port, release });
2698
- });
2699
- server.on("error", reject);
2700
- });
2701
- }
2702
- async function allocatePort() {
2703
- const held = await reservePort();
2704
- await held.release();
2705
- return held.port;
2706
- }
2707
- async function tryReservePort(port) {
2708
- if (!Number.isInteger(port) || port <= 0 || port > 65535) return null;
2709
- return new Promise((resolve2) => {
2710
- const server = createServer();
2711
- let released = false;
2712
- const release = () => new Promise((res) => {
2713
- if (released) {
2714
- res();
2715
- return;
2716
- }
2717
- released = true;
2718
- server.close(() => res());
2719
- });
2720
- server.once("error", () => {
2721
- try {
2722
- server.close();
2723
- } catch {
2724
- }
2725
- resolve2(null);
2726
- });
2727
- server.listen(port, "127.0.0.1", () => {
2728
- resolve2({ port, release });
2729
- });
2730
- });
2731
- }
2732
- async function tryReservePorts(ports) {
2733
- const held = [];
2734
- for (const port of ports) {
2735
- const next = await tryReservePort(port);
2736
- if (!next) {
2737
- await Promise.all(held.map((h) => h.release()));
2738
- return null;
2739
- }
2740
- held.push(next);
744
+ const nextMergeable = meta.mergeable ?? null;
745
+ if ((thread.prMergeable ?? null) !== nextMergeable) {
746
+ patch.prMergeable = nextMergeable;
2741
747
  }
2742
- return held;
2743
- }
2744
- function normalizePreferredPorts(ports, size) {
2745
- if (!ports?.length) return [];
2746
- const seen = /* @__PURE__ */ new Set();
2747
- const out = [];
2748
- for (const p of ports) {
2749
- if (!Number.isInteger(p) || p <= 0 || p > 65535 || seen.has(p)) continue;
2750
- seen.add(p);
2751
- out.push(p);
2752
- if (out.length >= size) break;
748
+ const nextMergeState = meta.mergeStateStatus ?? null;
749
+ if ((thread.prMergeStateStatus ?? null) !== nextMergeState) {
750
+ patch.prMergeStateStatus = nextMergeState;
2753
751
  }
2754
- return out;
2755
- }
2756
- function delay(ms) {
2757
- return new Promise((resolve2) => setTimeout(resolve2, ms));
2758
- }
2759
- async function fillPortRange(held, size) {
2760
- if (held.length >= size) return held;
2761
- const base = held[0]?.port;
2762
- const taken = new Set(held.map((h) => h.port));
2763
- try {
2764
- let offset = 0;
2765
- while (held.length < size) {
2766
- const candidate = base != null ? base + offset : null;
2767
- offset += 1;
2768
- if (candidate != null && taken.has(candidate)) continue;
2769
- const next = candidate != null ? await tryReservePort(candidate) : null;
2770
- if (next) {
2771
- held.push(next);
2772
- taken.add(next.port);
2773
- } else {
2774
- const ephemeral = await reservePort();
2775
- held.push(ephemeral);
2776
- taken.add(ephemeral.port);
2777
- }
2778
- }
2779
- return held;
2780
- } catch (err) {
2781
- await Promise.all(held.map((h) => h.release()));
2782
- throw err;
752
+ const nextQueued = Boolean(meta.isInMergeQueue);
753
+ if (Boolean(thread.prIsInMergeQueue) !== nextQueued) {
754
+ patch.prIsInMergeQueue = nextQueued;
2783
755
  }
2784
- }
2785
- async function tryReserveAvailablePorts(ports) {
2786
- const held = [];
2787
- for (const port of ports) {
2788
- const next = await tryReservePort(port);
2789
- if (next) held.push(next);
756
+ const nextLabels = meta.labels ?? [];
757
+ if (!sameLoginList(nextLabels, thread.prLabels)) {
758
+ patch.prLabels = nextLabels;
2790
759
  }
2791
- return held;
2792
- }
2793
- async function reclaimPreferredPorts(ports, worktreePath) {
2794
- const primary = ports[0];
2795
- if (primary == null) return null;
2796
- const takePrimary = async () => {
2797
- const held2 = await tryReserveAvailablePorts(ports);
2798
- if (held2.some((h) => h.port === primary)) return held2;
2799
- await Promise.all(held2.map((h) => h.release()));
2800
- return null;
2801
- };
2802
- let held = await takePrimary();
2803
- if (held) return held;
2804
- if (worktreePath) {
2805
- killStaleWorktreeListeners(ports, worktreePath, "SIGTERM");
2806
- } else {
2807
- killListenersOnPorts(ports);
760
+ const mergeState = (nextMergeState ?? "").toUpperCase();
761
+ if (mergeState === "CLEAN" && thread.prChecksFailed) {
762
+ patch.prChecksFailed = false;
763
+ } else if ((mergeState === "UNSTABLE" || mergeState === "BLOCKED" && (nextDecision ?? "").toUpperCase() === "APPROVED") && !thread.prChecksFailed) {
764
+ patch.prChecksFailed = true;
2808
765
  }
2809
- for (let i = 0; i < RECLAIM_TRIES; i++) {
2810
- await delay(RECLAIM_RETRY_DELAY_MS);
2811
- held = await takePrimary();
2812
- if (held) return held;
2813
- if (i === 2 && worktreePath) {
2814
- killStaleWorktreeListeners(ports, worktreePath, "SIGKILL");
2815
- }
766
+ if (thread.skipAutoArchiveOnMerge && nextState && nextState !== "MERGED" && nextState !== "CLOSED") {
767
+ patch.skipAutoArchiveOnMerge = false;
2816
768
  }
2817
- return null;
769
+ return patch;
2818
770
  }
2819
- async function reservePortRange(size = PORT_RANGE_SIZE, opts) {
2820
- const preferred = normalizePreferredPorts(opts?.preferred, size);
2821
- if (preferred.length) {
2822
- const reused = await reclaimPreferredPorts(preferred, opts?.worktreePath);
2823
- if (reused) return fillPortRange(reused, size);
2824
- }
2825
- const held = [await reservePort()];
2826
- return fillPortRange(held, size);
2827
- }
2828
- async function allocatePortRange(size = PORT_RANGE_SIZE) {
2829
- const held = await reservePortRange(size);
2830
- const ports = held.map((h) => h.port);
2831
- await Promise.all(held.map((h) => h.release()));
2832
- return ports;
2833
- }
2834
- async function startDevServer(repoPath, worktreePath, onLine, opts) {
2835
- const script = getRunScript(worktreePath, repoPath, opts?.scriptName);
2836
- if (!script) return null;
2837
- const preferred = loadWorktreeRunPorts(worktreePath, script.name) ?? opts?.preferredPorts;
2838
- const held = await reservePortRange(PORT_RANGE_SIZE, {
2839
- preferred: preferred ?? void 0,
2840
- worktreePath
2841
- });
2842
- const ports = held.map((h) => h.port);
2843
- saveWorktreeRunPorts(worktreePath, script.name, ports);
2844
- try {
2845
- const handle = await spawnWorkspaceScript(script.command, {
2846
- worktreePath,
2847
- repoPath,
2848
- ports,
2849
- heldPorts: held,
2850
- defaultBranch: opts?.defaultBranch,
2851
- onLine
2852
- });
2853
- return {
2854
- pid: handle.pid,
2855
- port: ports[0],
2856
- ports,
2857
- scriptName: script.name,
2858
- kill: handle.kill,
2859
- done: handle.done
2860
- };
2861
- } catch (err) {
2862
- await Promise.all(held.map((h) => h.release()));
2863
- throw err;
2864
- }
771
+ function threadPrChecksFailedPatch(thread, failed) {
772
+ if (thread.prChecksFailed === failed) return {};
773
+ return { prChecksFailed: failed };
774
+ }
775
+ function shouldAutoArchiveOnPrMerge(opts) {
776
+ if (!opts.autoArchiveEnabled) return false;
777
+ if (opts.isGlobal) return false;
778
+ if (opts.skipAutoArchiveOnMerge) return false;
779
+ if (opts.threadStatus === "archived") return false;
780
+ const next = normalizePrState(opts.nextPrState);
781
+ if (next !== "MERGED") return false;
782
+ return normalizePrState(opts.previousPrState) !== "MERGED";
2865
783
  }
2866
784
 
2867
785
  // src/store/setup-log.ts
2868
786
  import { createHash } from "crypto";
2869
- import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
2870
- import { join as join7 } from "path";
787
+ import { existsSync, readFileSync, writeFileSync } from "fs";
788
+ import { join as join2 } from "path";
2871
789
  var EMPTY = {
2872
790
  output: "",
2873
791
  running: false,
@@ -2909,7 +827,7 @@ function materialize(mem) {
2909
827
  };
2910
828
  }
2911
829
  function setupLogPath(threadId) {
2912
- return join7(threadsDir(), `${threadId}.setup.log.json`);
830
+ return join2(threadsDir(), `${threadId}.setup.log.json`);
2913
831
  }
2914
832
  function setupLogKeyForWorktree(worktreePath) {
2915
833
  const norm = normalizeWorktreePath(worktreePath);
@@ -2936,7 +854,7 @@ function persistNow(threadId) {
2936
854
  const mem = memory.get(threadId);
2937
855
  if (!mem) return;
2938
856
  try {
2939
- writeFileSync4(
857
+ writeFileSync(
2940
858
  setupLogPath(threadId),
2941
859
  `${JSON.stringify(materialize(mem))}
2942
860
  `,
@@ -2957,9 +875,9 @@ function schedulePersist(threadId) {
2957
875
  }
2958
876
  function loadFromDisk(threadId) {
2959
877
  const path = setupLogPath(threadId);
2960
- if (!existsSync5(path)) return null;
878
+ if (!existsSync(path)) return null;
2961
879
  try {
2962
- const raw = JSON.parse(readFileSync5(path, "utf8"));
880
+ const raw = JSON.parse(readFileSync(path, "utf8"));
2963
881
  return {
2964
882
  output: typeof raw.output === "string" ? raw.output : "",
2965
883
  running: Boolean(raw.running),
@@ -3018,8 +936,8 @@ function finishSetupLog(threadId, exitCode, source) {
3018
936
 
3019
937
  // src/store/run-log.ts
3020
938
  import { createHash as createHash2 } from "crypto";
3021
- import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
3022
- import { join as join8 } from "path";
939
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
940
+ import { join as join3 } from "path";
3023
941
  var EMPTY2 = {
3024
942
  output: "",
3025
943
  running: false,
@@ -3058,7 +976,7 @@ function safeScriptToken(scriptName) {
3058
976
  return token || "dev";
3059
977
  }
3060
978
  function runLogPath(key) {
3061
- return join8(threadsDir(), `${key}.run.log.json`);
979
+ return join3(threadsDir(), `${key}.run.log.json`);
3062
980
  }
3063
981
  function runLogKeyForWorktree(worktreePath, scriptName) {
3064
982
  const norm = normalizeWorktreePath(worktreePath);
@@ -3087,7 +1005,7 @@ function persistNow2(key) {
3087
1005
  const mem = memory2.get(key);
3088
1006
  if (!mem) return;
3089
1007
  try {
3090
- writeFileSync5(runLogPath(key), `${JSON.stringify(materialize2(mem))}
1008
+ writeFileSync2(runLogPath(key), `${JSON.stringify(materialize2(mem))}
3091
1009
  `, "utf8");
3092
1010
  } catch {
3093
1011
  }
@@ -3104,9 +1022,9 @@ function schedulePersist2(key) {
3104
1022
  }
3105
1023
  function loadFromDisk2(key) {
3106
1024
  const path = runLogPath(key);
3107
- if (!existsSync6(path)) return null;
1025
+ if (!existsSync2(path)) return null;
3108
1026
  try {
3109
- const raw = JSON.parse(readFileSync6(path, "utf8"));
1027
+ const raw = JSON.parse(readFileSync2(path, "utf8"));
3110
1028
  return {
3111
1029
  output: typeof raw.output === "string" ? raw.output : "",
3112
1030
  running: Boolean(raw.running),
@@ -3166,8 +1084,8 @@ function flushRunLog(key) {
3166
1084
  }
3167
1085
 
3168
1086
  // src/git/orphan-cleanup.ts
3169
- import { existsSync as existsSync7, readdirSync as readdirSync3, statSync } from "fs";
3170
- import { join as join9 } from "path";
1087
+ import { existsSync as existsSync3, readdirSync, statSync } from "fs";
1088
+ import { join as join4 } from "path";
3171
1089
  function isSideboardWorktreePath(path) {
3172
1090
  return path.includes("/.sideboard/worktrees/") || path.includes("/sideboard/workspaces/");
3173
1091
  }
@@ -3178,9 +1096,9 @@ async function findOrphanWorktrees(repoPaths) {
3178
1096
  repoPaths?.length ? repoPaths : threads.map((t) => t.repoPath).filter(Boolean)
3179
1097
  );
3180
1098
  const homeRoot = sideboardWorkspacesDir();
3181
- if (existsSync7(homeRoot)) {
1099
+ if (existsSync3(homeRoot)) {
3182
1100
  try {
3183
- for (const entry of readdirSync3(homeRoot, { withFileTypes: true })) {
1101
+ for (const entry of readdirSync(homeRoot, { withFileTypes: true })) {
3184
1102
  if (!entry.isDirectory()) continue;
3185
1103
  void entry;
3186
1104
  }
@@ -3190,7 +1108,7 @@ async function findOrphanWorktrees(repoPaths) {
3190
1108
  const orphans = [];
3191
1109
  const seen = /* @__PURE__ */ new Set();
3192
1110
  for (const repoPath of repos) {
3193
- if (!repoPath || !existsSync7(repoPath)) continue;
1111
+ if (!repoPath || !existsSync3(repoPath)) continue;
3194
1112
  try {
3195
1113
  const wts = await listWorktrees(repoPath);
3196
1114
  for (const wt of wts) {
@@ -3211,12 +1129,12 @@ async function findOrphanWorktrees(repoPaths) {
3211
1129
  }
3212
1130
  try {
3213
1131
  const root = worktreesRoot(repoPath);
3214
- if (existsSync7(root)) {
3215
- for (const entry of readdirSync3(root, { withFileTypes: true })) {
1132
+ if (existsSync3(root)) {
1133
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
3216
1134
  if (!entry.isDirectory()) continue;
3217
- const path = join9(root, entry.name).replace(/\/$/, "");
1135
+ const path = join4(root, entry.name).replace(/\/$/, "");
3218
1136
  if (known.has(path) || seen.has(path)) continue;
3219
- if (!existsSync7(join9(path, ".git"))) continue;
1137
+ if (!existsSync3(join4(path, ".git"))) continue;
3220
1138
  seen.add(path);
3221
1139
  let mtimeMs = 0;
3222
1140
  try {
@@ -3348,11 +1266,11 @@ function shouldRunHistoryCleanup(settings = loadAppSettings()) {
3348
1266
  }
3349
1267
 
3350
1268
  // src/git/apply-into-main.ts
3351
- import { execa as execa3 } from "execa";
1269
+ import { execa as execa2 } from "execa";
3352
1270
  async function applyThreadIntoMain(thread, opts) {
3353
1271
  const method = opts?.method ?? "merge";
3354
1272
  const target = opts?.targetBranch?.trim() || await resolveDefaultBranch(thread.repoPath);
3355
- const status = await execa3("git", ["status", "--porcelain"], {
1273
+ const status = await execa2("git", ["status", "--porcelain"], {
3356
1274
  cwd: thread.repoPath,
3357
1275
  reject: false
3358
1276
  });
@@ -3361,7 +1279,7 @@ async function applyThreadIntoMain(thread, opts) {
3361
1279
  `Main checkout is dirty \u2014 commit or stash before apply-into-main (${thread.repoPath})`
3362
1280
  );
3363
1281
  }
3364
- const checkout = await execa3("git", ["checkout", target], {
1282
+ const checkout = await execa2("git", ["checkout", target], {
3365
1283
  cwd: thread.repoPath,
3366
1284
  reject: false
3367
1285
  });
@@ -3371,7 +1289,7 @@ async function applyThreadIntoMain(thread, opts) {
3371
1289
  );
3372
1290
  }
3373
1291
  if (method === "cherry-pick") {
3374
- const log = await execa3(
1292
+ const log = await execa2(
3375
1293
  "git",
3376
1294
  ["log", "--reverse", "--format=%H", `${target}..${thread.branchName}`],
3377
1295
  { cwd: thread.repoPath, reject: false }
@@ -3386,12 +1304,12 @@ async function applyThreadIntoMain(thread, opts) {
3386
1304
  };
3387
1305
  }
3388
1306
  for (const sha of shas) {
3389
- const cp = await execa3("git", ["cherry-pick", sha], {
1307
+ const cp = await execa2("git", ["cherry-pick", sha], {
3390
1308
  cwd: thread.repoPath,
3391
1309
  reject: false
3392
1310
  });
3393
1311
  if (cp.exitCode !== 0) {
3394
- await execa3("git", ["cherry-pick", "--abort"], {
1312
+ await execa2("git", ["cherry-pick", "--abort"], {
3395
1313
  cwd: thread.repoPath,
3396
1314
  reject: false
3397
1315
  });
@@ -3407,13 +1325,13 @@ async function applyThreadIntoMain(thread, opts) {
3407
1325
  message: `Cherry-picked ${shas.length} commit(s) from ${thread.branchName} into ${target}`
3408
1326
  };
3409
1327
  }
3410
- const merge = await execa3(
1328
+ const merge = await execa2(
3411
1329
  "git",
3412
1330
  ["merge", "--no-ff", "-m", `Apply Sideboard thread ${thread.branchName}`, thread.branchName],
3413
1331
  { cwd: thread.repoPath, reject: false }
3414
1332
  );
3415
1333
  if (merge.exitCode !== 0) {
3416
- await execa3("git", ["merge", "--abort"], {
1334
+ await execa2("git", ["merge", "--abort"], {
3417
1335
  cwd: thread.repoPath,
3418
1336
  reject: false
3419
1337
  });
@@ -3430,25 +1348,25 @@ async function applyThreadIntoMain(thread, opts) {
3430
1348
  }
3431
1349
 
3432
1350
  // src/git/clone-repo.ts
3433
- import { existsSync as existsSync8 } from "fs";
3434
- import { basename as basename2, join as join10 } from "path";
3435
- import { execa as execa4 } from "execa";
1351
+ import { existsSync as existsSync4 } from "fs";
1352
+ import { basename, join as join5 } from "path";
1353
+ import { execa as execa3 } from "execa";
3436
1354
  async function cloneRepoIntoSideboard(opts) {
3437
1355
  const url = opts.url.trim();
3438
1356
  if (!url) throw new Error("Clone URL is required");
3439
1357
  let name = opts.name?.trim();
3440
1358
  if (!name) {
3441
- const leaf = basename2(url.replace(/\/$/, "").replace(/\.git$/, ""));
1359
+ const leaf = basename(url.replace(/\/$/, "").replace(/\.git$/, ""));
3442
1360
  name = leaf || "repo";
3443
1361
  }
3444
1362
  name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
3445
- const dest = join10(sideboardReposDir(), name);
3446
- if (existsSync8(dest)) {
1363
+ const dest = join5(sideboardReposDir(), name);
1364
+ if (existsSync4(dest)) {
3447
1365
  const repoPath2 = await resolveRepoRoot(dest);
3448
1366
  const workspace2 = await ensureWorkspace(repoPath2);
3449
1367
  return { repoPath: repoPath2, workspace: workspace2 };
3450
1368
  }
3451
- const result = await execa4("git", ["clone", url, dest], { reject: false });
1369
+ const result = await execa3("git", ["clone", url, dest], { reject: false });
3452
1370
  if (result.exitCode !== 0) {
3453
1371
  throw new Error(
3454
1372
  `git clone failed: ${result.stderr?.trim() || result.stdout?.trim() || `exit ${result.exitCode}`}`
@@ -3468,13 +1386,13 @@ function isAdoptableRunScriptRequest(req) {
3468
1386
  }
3469
1387
 
3470
1388
  // src/store/desktop-host.ts
3471
- import { readFileSync as readFileSync7, unlinkSync, writeFileSync as writeFileSync6 } from "fs";
3472
- import { join as join11 } from "path";
1389
+ import { readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
1390
+ import { join as join6 } from "path";
3473
1391
  function desktopHostPidPath() {
3474
- return join11(appDataDir(), "desktop-host.pid");
1392
+ return join6(appDataDir(), "desktop-host.pid");
3475
1393
  }
3476
1394
  function claimDesktopHost(pid = process.pid) {
3477
- writeFileSync6(desktopHostPidPath(), `${pid}
1395
+ writeFileSync3(desktopHostPidPath(), `${pid}
3478
1396
  `, "utf8");
3479
1397
  }
3480
1398
  function releaseDesktopHost(pid = process.pid) {
@@ -3486,7 +1404,7 @@ function releaseDesktopHost(pid = process.pid) {
3486
1404
  }
3487
1405
  function readDesktopHostPid() {
3488
1406
  try {
3489
- const pid = Number.parseInt(readFileSync7(desktopHostPidPath(), "utf8").trim(), 10);
1407
+ const pid = Number.parseInt(readFileSync3(desktopHostPidPath(), "utf8").trim(), 10);
3490
1408
  if (!Number.isFinite(pid) || pid <= 0) return null;
3491
1409
  return pid;
3492
1410
  } catch {
@@ -3582,7 +1500,7 @@ function agentPurposeStamp(input) {
3582
1500
  }
3583
1501
 
3584
1502
  // src/threads/create.ts
3585
- import { existsSync as existsSync9 } from "fs";
1503
+ import { existsSync as existsSync5 } from "fs";
3586
1504
 
3587
1505
  // src/detect/detect.ts
3588
1506
  async function detectAgents() {
@@ -4273,7 +2191,7 @@ function reuseLiveThread(input, repoPath, match) {
4273
2191
  }
4274
2192
  async function createThread(input, _onSetupLine) {
4275
2193
  const repoPath = await resolveRepoRoot(input.repoPath);
4276
- if (!existsSync9(repoPath)) {
2194
+ if (!existsSync5(repoPath)) {
4277
2195
  throw new Error(`Repo not found: ${repoPath}`);
4278
2196
  }
4279
2197
  const reused = reuseLiveThread(input, repoPath, {
@@ -4290,7 +2208,7 @@ async function createThread(input, _onSetupLine) {
4290
2208
  });
4291
2209
  await requireAgent(resolved.agent);
4292
2210
  if (input.cowboy) {
4293
- const { cowboyModeEnabled } = await import("./app-settings-RKFFUIFQ.js");
2211
+ const { cowboyModeEnabled } = await import("./app-settings-L5HGYXWJ.js");
4294
2212
  if (!cowboyModeEnabled()) {
4295
2213
  throw new Error(
4296
2214
  "Cowboy mode is off. Enable it in Settings \u2192 Advanced."
@@ -4348,7 +2266,7 @@ async function createThread(input, _onSetupLine) {
4348
2266
  let sourceIsFork = false;
4349
2267
  if (!input.cowboy && sourceType === "branch" && (!sourceRef || sourceRef === "HEAD" || sourceRef === "default")) {
4350
2268
  if (isAbleTimeConnected() && getIssueSource() === "abletime") {
4351
- const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-U75ZGDIX.js");
2269
+ const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-JNQAHLHH.js");
4352
2270
  const task = await ensureAbleTimeTask({
4353
2271
  title: title?.trim() || "Untitled work",
4354
2272
  description: input.prompt?.trim() || void 0
@@ -4458,7 +2376,7 @@ async function createThread(input, _onSetupLine) {
4458
2376
  return readThread(thread.id) ?? thread;
4459
2377
  }
4460
2378
  async function listLinearIssues(agent, repoPath) {
4461
- const { getAdapter: getAdapter2 } = await import("./agents-3TUMFSMH.js");
2379
+ const { getAdapter: getAdapter2 } = await import("./agents-G3JTULM2.js");
4462
2380
  await requireAgent(agent, { requireLinear: true });
4463
2381
  const adapter = getAdapter2(agent);
4464
2382
  if (!adapter.listLinearIssues) {
@@ -4519,11 +2437,11 @@ function createLineCoalescer(onChunk, opts) {
4519
2437
 
4520
2438
  // src/store/turn-live.ts
4521
2439
  import {
4522
- existsSync as existsSync10,
2440
+ existsSync as existsSync6,
4523
2441
  renameSync,
4524
2442
  unlinkSync as unlinkSync2,
4525
- writeFileSync as writeFileSync7,
4526
- readFileSync as readFileSync8
2443
+ writeFileSync as writeFileSync4,
2444
+ readFileSync as readFileSync4
4527
2445
  } from "fs";
4528
2446
  var buffers = /* @__PURE__ */ new Map();
4529
2447
  var FLUSH_MS = 800;
@@ -4600,7 +2518,7 @@ function writeTurnLive(threadId, progress) {
4600
2518
  const path = threadLivePath(threadId);
4601
2519
  const tmp = `${path}.${process.pid}.tmp`;
4602
2520
  try {
4603
- writeFileSync7(tmp, JSON.stringify(progress), "utf8");
2521
+ writeFileSync4(tmp, JSON.stringify(progress), "utf8");
4604
2522
  renameSync(tmp, path);
4605
2523
  } catch {
4606
2524
  try {
@@ -4611,9 +2529,9 @@ function writeTurnLive(threadId, progress) {
4611
2529
  }
4612
2530
  function readTurnLive(threadId) {
4613
2531
  const path = threadLivePath(threadId);
4614
- if (!existsSync10(path)) return null;
2532
+ if (!existsSync6(path)) return null;
4615
2533
  try {
4616
- const raw = JSON.parse(readFileSync8(path, "utf8"));
2534
+ const raw = JSON.parse(readFileSync4(path, "utf8"));
4617
2535
  if (!raw || typeof raw.summary !== "string") return null;
4618
2536
  return raw;
4619
2537
  } catch {
@@ -4625,7 +2543,7 @@ function clearTurnLive(threadId) {
4625
2543
  if (buf?.timer) clearTimeout(buf.timer);
4626
2544
  buffers.delete(threadId);
4627
2545
  const path = threadLivePath(threadId);
4628
- if (!existsSync10(path)) return;
2546
+ if (!existsSync6(path)) return;
4629
2547
  try {
4630
2548
  unlinkSync2(path);
4631
2549
  } catch {
@@ -4662,13 +2580,13 @@ function isStaleLastErrorDuringTurn(err) {
4662
2580
  }
4663
2581
 
4664
2582
  // src/threads/fork-worktree.ts
4665
- function requireThread2(idOrRef) {
2583
+ function requireThread(idOrRef) {
4666
2584
  const thread = findThreadByRef(idOrRef) ?? null;
4667
2585
  if (!thread) throw new Error(`Thread not found: ${idOrRef}`);
4668
2586
  return thread;
4669
2587
  }
4670
2588
  async function forkThreadWorktree(input, onSetupLine) {
4671
- const from = requireThread2(input.threadId);
2589
+ const from = requireThread(input.threadId);
4672
2590
  if (isOrchestratorThread(from)) {
4673
2591
  throw new Error(
4674
2592
  "fork_workspace targets a worktree agent thread (not the orchestrator). Pass a child/worktree thread ref."
@@ -4705,7 +2623,7 @@ async function forkThreadWorktree(input, onSetupLine) {
4705
2623
  }
4706
2624
 
4707
2625
  // src/orchestrator/quota-failover.ts
4708
- import { randomUUID as randomUUID3 } from "crypto";
2626
+ import { randomUUID } from "crypto";
4709
2627
  function managedKindForAgent(agent) {
4710
2628
  return agent === "claude" || agent === "codex" ? agent : null;
4711
2629
  }
@@ -4825,7 +2743,7 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
4825
2743
  ...instructions
4826
2744
  ].join("\n");
4827
2745
  return {
4828
- id: randomUUID3(),
2746
+ id: randomUUID(),
4829
2747
  name: orch ? "Orchestration quota handoff.md" : "Quota handoff.md",
4830
2748
  kind: "transcript",
4831
2749
  content: body
@@ -4840,9 +2758,9 @@ var QUOTA_CONTINUE_ACCOUNT_PROMPT = (fromAgent, accountLabel) => [
4840
2758
  "Proceed with the goal. Leave model Auto unless needed."
4841
2759
  ].join(" ");
4842
2760
  var QUOTA_RESUME_PROMPT = "Session/usage limit window should have reset. Continue this chat from where you left off.";
4843
- async function isolateQuotaFailover(run2) {
2761
+ async function isolateQuotaFailover(run) {
4844
2762
  try {
4845
- return await run2();
2763
+ return await run();
4846
2764
  } catch {
4847
2765
  return true;
4848
2766
  }
@@ -4868,29 +2786,29 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText, opts) {
4868
2786
  }
4869
2787
 
4870
2788
  // src/threads/adopt.ts
4871
- import { execFileSync as execFileSync2 } from "child_process";
2789
+ import { execFileSync } from "child_process";
4872
2790
  import {
4873
- copyFileSync as copyFileSync2,
4874
- existsSync as existsSync11,
2791
+ copyFileSync,
2792
+ existsSync as existsSync7,
4875
2793
  mkdtempSync,
4876
- readdirSync as readdirSync4,
4877
- readFileSync as readFileSync9,
2794
+ readdirSync as readdirSync2,
2795
+ readFileSync as readFileSync5,
4878
2796
  rmSync
4879
2797
  } from "fs";
4880
2798
  import { tmpdir } from "os";
4881
- import { join as join12 } from "path";
2799
+ import { join as join7 } from "path";
4882
2800
  import { createRequire } from "module";
4883
- var CONDUCTOR_APP_SUPPORT = join12(
2801
+ var CONDUCTOR_APP_SUPPORT = join7(
4884
2802
  process.env.HOME ?? "",
4885
2803
  "Library",
4886
2804
  "Application Support",
4887
2805
  "com.conductor.app"
4888
2806
  );
4889
- var CONDUCTOR_DB = join12(CONDUCTOR_APP_SUPPORT, "conductor.db");
4890
- var CURSOR_SDK_STORE = join12(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
2807
+ var CONDUCTOR_DB = join7(CONDUCTOR_APP_SUPPORT, "conductor.db");
2808
+ var CURSOR_SDK_STORE = join7(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
4891
2809
  function thisModuleFile() {
4892
2810
  const cjsFile = typeof __filename !== "undefined" ? __filename : "";
4893
- return cjsFile || process.argv[1] || join12(process.cwd(), "package.json");
2811
+ return cjsFile || process.argv[1] || join7(process.cwd(), "package.json");
4894
2812
  }
4895
2813
  function openReadonlySqlite(file) {
4896
2814
  const req = createRequire(thisModuleFile());
@@ -4908,21 +2826,21 @@ function mapAgentType(raw) {
4908
2826
  return null;
4909
2827
  }
4910
2828
  function resolveConductorCursorAgentId(workspacePath) {
4911
- if (!workspacePath || !existsSync11(CURSOR_SDK_STORE)) return null;
2829
+ if (!workspacePath || !existsSync7(CURSOR_SDK_STORE)) return null;
4912
2830
  const normalized = workspacePath.replace(/\/$/, "");
4913
2831
  let best = null;
4914
2832
  let hashes;
4915
2833
  try {
4916
- hashes = readdirSync4(CURSOR_SDK_STORE);
2834
+ hashes = readdirSync2(CURSOR_SDK_STORE);
4917
2835
  } catch {
4918
2836
  return null;
4919
2837
  }
4920
2838
  for (const hash of hashes) {
4921
- const agentsFile = join12(CURSOR_SDK_STORE, hash, "agents.ndjson");
4922
- if (!existsSync11(agentsFile)) continue;
2839
+ const agentsFile = join7(CURSOR_SDK_STORE, hash, "agents.ndjson");
2840
+ if (!existsSync7(agentsFile)) continue;
4923
2841
  let text;
4924
2842
  try {
4925
- text = readFileSync9(agentsFile, "utf8");
2843
+ text = readFileSync5(agentsFile, "utf8");
4926
2844
  } catch {
4927
2845
  continue;
4928
2846
  }
@@ -4946,7 +2864,7 @@ function resolveConductorCursorAgentId(workspacePath) {
4946
2864
  return best?.agentId ?? null;
4947
2865
  }
4948
2866
  async function adoptThread(input) {
4949
- if (!existsSync11(input.worktreePath)) {
2867
+ if (!existsSync7(input.worktreePath)) {
4950
2868
  throw new Error(`Worktree not found: ${input.worktreePath}`);
4951
2869
  }
4952
2870
  const repoPath = await resolveRepoRoot(input.worktreePath);
@@ -4965,28 +2883,28 @@ async function adoptThread(input) {
4965
2883
  messages: input.messages ?? []
4966
2884
  });
4967
2885
  writeThread(thread);
4968
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-QJ3XIPUJ.js");
2886
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-F33Z4CEY.js");
4969
2887
  await ensureWorkspace2(repoPath);
4970
- const { ensureWorktreeSideboardIgnored: ensureWorktreeSideboardIgnored2 } = await import("./worktree-exclude-RXZPTCNH.js");
4971
- await ensureWorktreeSideboardIgnored2(input.worktreePath);
2888
+ const { ensureWorktreeSideboardIgnored } = await import("./worktree-exclude-RXZPTCNH.js");
2889
+ await ensureWorktreeSideboardIgnored(input.worktreePath);
4972
2890
  return thread;
4973
2891
  }
4974
2892
  function conductorDbPath() {
4975
2893
  return CONDUCTOR_DB;
4976
2894
  }
4977
2895
  function listConductorWorkspaces() {
4978
- if (!existsSync11(CONDUCTOR_DB)) {
2896
+ if (!existsSync7(CONDUCTOR_DB)) {
4979
2897
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
4980
2898
  }
4981
- const tmp = mkdtempSync(join12(tmpdir(), "sideboard-conductor-"));
4982
- const snapshot = join12(tmp, "conductor.db");
2899
+ const tmp = mkdtempSync(join7(tmpdir(), "sideboard-conductor-"));
2900
+ const snapshot = join7(tmp, "conductor.db");
4983
2901
  try {
4984
- copyFileSync2(CONDUCTOR_DB, snapshot);
2902
+ copyFileSync(CONDUCTOR_DB, snapshot);
4985
2903
  for (const suffix of ["-wal", "-shm"]) {
4986
2904
  const src = `${CONDUCTOR_DB}${suffix}`;
4987
- if (existsSync11(src)) {
2905
+ if (existsSync7(src)) {
4988
2906
  try {
4989
- copyFileSync2(src, `${snapshot}${suffix}`);
2907
+ copyFileSync(src, `${snapshot}${suffix}`);
4990
2908
  } catch {
4991
2909
  }
4992
2910
  }
@@ -5066,18 +2984,18 @@ function listConductorWorkspaces() {
5066
2984
  }
5067
2985
  }
5068
2986
  function importConductorWorkspace(workspaceId) {
5069
- if (!existsSync11(CONDUCTOR_DB)) {
2987
+ if (!existsSync7(CONDUCTOR_DB)) {
5070
2988
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
5071
2989
  }
5072
- const tmp = mkdtempSync(join12(tmpdir(), "sideboard-conductor-"));
5073
- const snapshot = join12(tmp, "conductor.db");
2990
+ const tmp = mkdtempSync(join7(tmpdir(), "sideboard-conductor-"));
2991
+ const snapshot = join7(tmp, "conductor.db");
5074
2992
  try {
5075
- copyFileSync2(CONDUCTOR_DB, snapshot);
2993
+ copyFileSync(CONDUCTOR_DB, snapshot);
5076
2994
  for (const suffix of ["-wal", "-shm"]) {
5077
2995
  const src = `${CONDUCTOR_DB}${suffix}`;
5078
- if (existsSync11(src)) {
2996
+ if (existsSync7(src)) {
5079
2997
  try {
5080
- copyFileSync2(src, `${snapshot}${suffix}`);
2998
+ copyFileSync(src, `${snapshot}${suffix}`);
5081
2999
  } catch {
5082
3000
  }
5083
3001
  }
@@ -5095,7 +3013,7 @@ function importConductorWorkspace(workspaceId) {
5095
3013
  ).get(workspaceId);
5096
3014
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
5097
3015
  const worktreePath = String(row.workspacePath);
5098
- if (!existsSync11(worktreePath)) {
3016
+ if (!existsSync7(worktreePath)) {
5099
3017
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
5100
3018
  }
5101
3019
  let sessionId = null;
@@ -5135,7 +3053,7 @@ function importConductorWorkspace(workspaceId) {
5135
3053
  }
5136
3054
  let repoPath = worktreePath;
5137
3055
  try {
5138
- repoPath = execFileSync2("git", ["rev-parse", "--show-toplevel"], {
3056
+ repoPath = execFileSync("git", ["rev-parse", "--show-toplevel"], {
5139
3057
  cwd: worktreePath,
5140
3058
  encoding: "utf8"
5141
3059
  }).trim();
@@ -5166,7 +3084,7 @@ async function importConductorWorkspaceAsync(workspaceId) {
5166
3084
  }
5167
3085
 
5168
3086
  // src/threads/stack-layers.ts
5169
- import { existsSync as existsSync12 } from "fs";
3087
+ import { existsSync as existsSync8 } from "fs";
5170
3088
  function stackIdFrom(stack) {
5171
3089
  if (stack.stackNumber != null) return `gh-stack-${stack.stackNumber}`;
5172
3090
  const key = stack.layers.map((l) => l.branchName).join("|");
@@ -5178,7 +3096,7 @@ function hashShort(s) {
5178
3096
  for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) | 0;
5179
3097
  return Math.abs(h).toString(36);
5180
3098
  }
5181
- function requireThread3(idOrRef) {
3099
+ function requireThread2(idOrRef) {
5182
3100
  const thread = findThreadByRef(idOrRef) ?? readThread(idOrRef);
5183
3101
  if (!thread) throw new Error(`Thread not found: ${idOrRef}`);
5184
3102
  return thread;
@@ -5229,7 +3147,7 @@ async function openStackLayer(input, _onSetupLine) {
5229
3147
  let createdWorktree = false;
5230
3148
  const trees = await listWorktrees(repoPath);
5231
3149
  const checkedOut = trees.find((w) => w.branch === branchName);
5232
- if (checkedOut?.path && existsSync12(checkedOut.path)) {
3150
+ if (checkedOut?.path && existsSync8(checkedOut.path)) {
5233
3151
  if (input.reuseExistingWorktree !== false) {
5234
3152
  worktreePath = checkedOut.path;
5235
3153
  } else {
@@ -5277,7 +3195,7 @@ async function openStackLayer(input, _onSetupLine) {
5277
3195
  return { thread: readThread(thread.id) ?? thread, createdWorktree };
5278
3196
  }
5279
3197
  async function openPrStackLayers(input, onSetupLine) {
5280
- const from = requireThread3(input.threadRef);
3198
+ const from = requireThread2(input.threadRef);
5281
3199
  if (!from.worktreePath?.trim() || !from.repoPath?.trim()) {
5282
3200
  throw new Error("Thread has no worktree");
5283
3201
  }
@@ -5321,7 +3239,7 @@ async function openPrStackLayers(input, onSetupLine) {
5321
3239
  return { stack, threads, createdThreadIds };
5322
3240
  }
5323
3241
  async function addStackLayerFromThread(input, onSetupLine) {
5324
- const from = requireThread3(input.threadRef);
3242
+ const from = requireThread2(input.threadRef);
5325
3243
  if (!from.worktreePath?.trim() || !from.repoPath?.trim()) {
5326
3244
  throw new Error("Thread has no worktree");
5327
3245
  }
@@ -5353,7 +3271,7 @@ async function addStackLayerFromThread(input, onSetupLine) {
5353
3271
  return { stack, thread: thread.thread, createdWorktree: thread.createdWorktree };
5354
3272
  }
5355
3273
  async function initStackFromThread(input, onSetupLine) {
5356
- const from = requireThread3(input.threadRef);
3274
+ const from = requireThread2(input.threadRef);
5357
3275
  if (!from.worktreePath?.trim() || !from.branchName?.trim() || !from.repoPath?.trim()) {
5358
3276
  throw new Error("Thread has no worktree/branch");
5359
3277
  }
@@ -5371,7 +3289,7 @@ async function initStackFromThread(input, onSetupLine) {
5371
3289
  async function createPrStack(input, onSetupLine) {
5372
3290
  await requireAgent(input.agent);
5373
3291
  const repoPath = await resolveRepoRoot(input.repoPath);
5374
- if (!existsSync12(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
3292
+ if (!existsSync8(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
5375
3293
  if (!input.branches.length) throw new Error("At least one branch name required");
5376
3294
  const status = await detectGhStack(repoPath);
5377
3295
  if (!status.available) throw new Error(status.reason);
@@ -5438,7 +3356,7 @@ async function createPrStack(input, onSetupLine) {
5438
3356
  }
5439
3357
  }
5440
3358
  const claimed = new Set(threads.map((t) => t.worktreePath));
5441
- if (!claimed.has(bootstrap.worktreePath) && existsSync12(bootstrap.worktreePath)) {
3359
+ if (!claimed.has(bootstrap.worktreePath) && existsSync8(bootstrap.worktreePath)) {
5442
3360
  try {
5443
3361
  await removeWorktree(repoPath, bootstrap.worktreePath, {
5444
3362
  deleteBranch: bootstrap.branchName
@@ -5460,10 +3378,10 @@ function stackAgentDefaultsFrom(input) {
5460
3378
  }
5461
3379
 
5462
3380
  // src/diff/diff.ts
5463
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
5464
- import { dirname as dirname5, join as join13 } from "path";
3381
+ import { existsSync as existsSync9, mkdirSync, readFileSync as readFileSync6, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
3382
+ import { dirname, join as join8 } from "path";
5465
3383
  async function inspectGitWorktree(worktreePath) {
5466
- if (!worktreePath || !existsSync13(worktreePath)) return "missing_worktree";
3384
+ if (!worktreePath || !existsSync9(worktreePath)) return "missing_worktree";
5467
3385
  const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
5468
3386
  reject: false
5469
3387
  });
@@ -5471,7 +3389,7 @@ async function inspectGitWorktree(worktreePath) {
5471
3389
  return "ok";
5472
3390
  }
5473
3391
  async function initializeGitRepository(worktreePath) {
5474
- if (!worktreePath || !existsSync13(worktreePath)) {
3392
+ if (!worktreePath || !existsSync9(worktreePath)) {
5475
3393
  throw new Error("Worktree not found");
5476
3394
  }
5477
3395
  const status = await inspectGitWorktree(worktreePath);
@@ -5607,11 +3525,11 @@ new file mode 100644
5607
3525
  };
5608
3526
  }
5609
3527
  async function untrackedPatch(worktreePath, path, maxHunk) {
5610
- const abs = join13(worktreePath, path);
3528
+ const abs = join8(worktreePath, path);
5611
3529
  try {
5612
3530
  const st = statSync2(abs);
5613
3531
  if (st.isFile() && st.size > maxHunk) {
5614
- const buf = readFileSync10(abs).subarray(0, maxHunk);
3532
+ const buf = readFileSync6(abs).subarray(0, maxHunk);
5615
3533
  return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
5616
3534
  }
5617
3535
  } catch {
@@ -6079,8 +3997,8 @@ async function getDiff(worktreePath, repoPath, opts) {
6079
3997
  }
6080
3998
  function statWorktreePath(worktreePath, relativePath) {
6081
3999
  assertSafeRelativePath(relativePath);
6082
- const abs = join13(worktreePath, relativePath);
6083
- if (!existsSync13(abs)) return "missing";
4000
+ const abs = join8(worktreePath, relativePath);
4001
+ if (!existsSync9(abs)) return "missing";
6084
4002
  const st = statSync2(abs);
6085
4003
  if (st.isDirectory()) return "dir";
6086
4004
  if (st.isFile()) return "file";
@@ -6121,7 +4039,7 @@ var DEFAULT_UPLOAD_MAX_BYTES = 5e7;
6121
4039
  function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
6122
4040
  assertSafeRelativePath(relativePath);
6123
4041
  const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
6124
- const abs = join13(worktreePath, relativePath);
4042
+ const abs = join8(worktreePath, relativePath);
6125
4043
  const st = statSync2(abs);
6126
4044
  if (!st.isFile()) {
6127
4045
  throw new Error(`Not a file: ${relativePath}`);
@@ -6131,7 +4049,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
6131
4049
  `File too large to upload (${st.size} bytes; max ${maxBytes})`
6132
4050
  );
6133
4051
  }
6134
- const buf = readFileSync10(abs);
4052
+ const buf = readFileSync6(abs);
6135
4053
  return {
6136
4054
  path: relativePath,
6137
4055
  contentBase64: buf.toString("base64"),
@@ -6141,12 +4059,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
6141
4059
  function readWorktreeFile(worktreePath, relativePath, opts) {
6142
4060
  assertSafeRelativePath(relativePath);
6143
4061
  const maxBytes = opts?.maxBytes ?? 2e5;
6144
- const abs = join13(worktreePath, relativePath);
4062
+ const abs = join8(worktreePath, relativePath);
6145
4063
  const st = statSync2(abs);
6146
4064
  if (!st.isFile()) {
6147
4065
  throw new Error(`Not a file: ${relativePath}`);
6148
4066
  }
6149
- const buf = readFileSync10(abs);
4067
+ const buf = readFileSync6(abs);
6150
4068
  if (isImageRelativePath(relativePath)) {
6151
4069
  const maxImageBytes = Math.max(maxBytes, 15e6);
6152
4070
  const truncated2 = buf.length > maxImageBytes;
@@ -6189,9 +4107,9 @@ function assertSafeRelativePath(relativePath) {
6189
4107
  }
6190
4108
  function writeWorktreeFile(worktreePath, relativePath, content) {
6191
4109
  assertSafeRelativePath(relativePath);
6192
- const abs = join13(worktreePath, relativePath);
6193
- mkdirSync6(dirname5(abs), { recursive: true });
6194
- writeFileSync8(abs, content, "utf8");
4110
+ const abs = join8(worktreePath, relativePath);
4111
+ mkdirSync(dirname(abs), { recursive: true });
4112
+ writeFileSync5(abs, content, "utf8");
6195
4113
  return { path: relativePath };
6196
4114
  }
6197
4115
  async function getWorktreeDirtyStat(worktreePath) {
@@ -6413,9 +4331,9 @@ async function confirmLand(thread, opts) {
6413
4331
  }
6414
4332
 
6415
4333
  // src/skills/discover.ts
6416
- import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
4334
+ import { existsSync as existsSync10, readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
6417
4335
  import { homedir } from "os";
6418
- import { join as join14 } from "path";
4336
+ import { join as join9 } from "path";
6419
4337
 
6420
4338
  // src/skills/bundled/long-running.ts
6421
4339
  var LONG_RUNNING_SKILL_COMMAND = "long-running";
@@ -6552,7 +4470,7 @@ function parseFrontmatter(content) {
6552
4470
  }
6553
4471
  function readSkill(skillMd, source) {
6554
4472
  try {
6555
- const content = readFileSync11(skillMd, "utf8");
4473
+ const content = readFileSync7(skillMd, "utf8");
6556
4474
  const { name: fmName, description } = parseFrontmatter(content);
6557
4475
  const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
6558
4476
  const name = fmName || dirName;
@@ -6571,17 +4489,17 @@ function readSkill(skillMd, source) {
6571
4489
  }
6572
4490
  }
6573
4491
  function scanSkillsDir(dir, source, out) {
6574
- if (!existsSync14(dir)) return;
4492
+ if (!existsSync10(dir)) return;
6575
4493
  let entries;
6576
4494
  try {
6577
- entries = readdirSync5(dir);
4495
+ entries = readdirSync3(dir);
6578
4496
  } catch {
6579
4497
  return;
6580
4498
  }
6581
4499
  for (const entry of entries) {
6582
4500
  if (entry.startsWith(".")) continue;
6583
- const skillMd = join14(dir, entry, "SKILL.md");
6584
- if (!existsSync14(skillMd)) continue;
4501
+ const skillMd = join9(dir, entry, "SKILL.md");
4502
+ if (!existsSync10(skillMd)) continue;
6585
4503
  try {
6586
4504
  if (!statSync3(skillMd).isFile()) continue;
6587
4505
  } catch {
@@ -6592,22 +4510,22 @@ function scanSkillsDir(dir, source, out) {
6592
4510
  }
6593
4511
  }
6594
4512
  function scanClaudePluginSkills(pluginsRoot, out) {
6595
- if (!existsSync14(pluginsRoot)) return;
4513
+ if (!existsSync10(pluginsRoot)) return;
6596
4514
  const walk = (dir, depth, lookingForSkillsDir) => {
6597
4515
  if (depth > 7) return;
6598
4516
  let entries;
6599
4517
  try {
6600
- entries = readdirSync5(dir);
4518
+ entries = readdirSync3(dir);
6601
4519
  } catch {
6602
4520
  return;
6603
4521
  }
6604
4522
  if (lookingForSkillsDir && entries.includes("SKILL.md")) {
6605
- const skill = readSkill(join14(dir, "SKILL.md"), "cli");
4523
+ const skill = readSkill(join9(dir, "SKILL.md"), "cli");
6606
4524
  if (skill) out.push(skill);
6607
4525
  }
6608
4526
  for (const entry of entries) {
6609
4527
  if (entry === "node_modules" || entry === ".git") continue;
6610
- const full = join14(dir, entry);
4528
+ const full = join9(dir, entry);
6611
4529
  try {
6612
4530
  if (!statSync3(full).isDirectory()) continue;
6613
4531
  } catch {
@@ -6627,17 +4545,17 @@ function discoverSkills(worktreePath) {
6627
4545
  const home = homedir();
6628
4546
  const collected = [];
6629
4547
  for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
6630
- scanSkillsDir(join14(worktreePath, rel), "workspace", collected);
4548
+ scanSkillsDir(join9(worktreePath, rel), "workspace", collected);
6631
4549
  }
6632
4550
  for (const abs of [
6633
- join14(home, ".claude/skills"),
6634
- join14(home, ".cursor/skills"),
6635
- join14(home, ".sideboard/skills"),
6636
- join14(home, ".brightsy/skills")
4551
+ join9(home, ".claude/skills"),
4552
+ join9(home, ".cursor/skills"),
4553
+ join9(home, ".sideboard/skills"),
4554
+ join9(home, ".brightsy/skills")
6637
4555
  ]) {
6638
4556
  scanSkillsDir(abs, "user", collected);
6639
4557
  }
6640
- scanClaudePluginSkills(join14(home, ".claude/plugins"), collected);
4558
+ scanClaudePluginSkills(join9(home, ".claude/plugins"), collected);
6641
4559
  collected.push(...bundledSkills());
6642
4560
  const rank = {
6643
4561
  workspace: 0,
@@ -6663,7 +4581,7 @@ function readSkillBody(skillPath, maxChars = 12e3) {
6663
4581
  \u2026(truncated)` : body;
6664
4582
  }
6665
4583
  }
6666
- const raw = readFileSync11(skillPath, "utf8");
4584
+ const raw = readFileSync7(skillPath, "utf8");
6667
4585
  if (raw.startsWith("---")) {
6668
4586
  const end = raw.indexOf("\n---", 3);
6669
4587
  if (end >= 0) {
@@ -6808,12 +4726,12 @@ function shiftQueuedItem(queue, queueAttachments) {
6808
4726
  }
6809
4727
 
6810
4728
  // src/agents/instructions.ts
6811
- import { existsSync as existsSync16 } from "fs";
6812
- import { join as join16 } from "path";
4729
+ import { existsSync as existsSync12 } from "fs";
4730
+ import { join as join11 } from "path";
6813
4731
 
6814
4732
  // src/skills/detached-job-path.ts
6815
- import { existsSync as existsSync15 } from "fs";
6816
- import { dirname as dirname6, join as join15 } from "path";
4733
+ import { existsSync as existsSync11 } from "fs";
4734
+ import { dirname as dirname2, join as join10 } from "path";
6817
4735
  import { fileURLToPath } from "url";
6818
4736
  var DETACHED_JOB_FILENAMES = ["detached-job.cjs", "detached-job.js"];
6819
4737
  function compiledModuleDir() {
@@ -6822,7 +4740,7 @@ function compiledModuleDir() {
6822
4740
  try {
6823
4741
  const url = import.meta.url;
6824
4742
  if (typeof url === "string" && url.length > 0) {
6825
- return dirname6(fileURLToPath(url));
4743
+ return dirname2(fileURLToPath(url));
6826
4744
  }
6827
4745
  } catch {
6828
4746
  }
@@ -6830,8 +4748,8 @@ function compiledModuleDir() {
6830
4748
  }
6831
4749
  function detachedJobScriptIn(dir) {
6832
4750
  for (const name of DETACHED_JOB_FILENAMES) {
6833
- const script = join15(dir, "scripts", name);
6834
- if (existsSync15(script)) return script;
4751
+ const script = join10(dir, "scripts", name);
4752
+ if (existsSync11(script)) return script;
6835
4753
  }
6836
4754
  return null;
6837
4755
  }
@@ -6848,7 +4766,7 @@ function resolveDetachedJobScript() {
6848
4766
  for (let i = 0; i < 8; i++) {
6849
4767
  const candidate = detachedJobScriptIn(dir);
6850
4768
  if (candidate) return candidate;
6851
- const parent = dirname6(dir);
4769
+ const parent = dirname2(dir);
6852
4770
  if (parent === dir) break;
6853
4771
  dir = parent;
6854
4772
  }
@@ -6890,12 +4808,12 @@ function formatRenameBranchDirective(thread, opts) {
6890
4808
  function localhostPreviewUrl(port) {
6891
4809
  return `http://localhost:${port}`;
6892
4810
  }
6893
- function describeLiveDevRun(run2) {
6894
- const url = localhostPreviewUrl(run2.port);
6895
- if (run2.preview === "window") {
6896
- return `${run2.scriptName} Electron window (renderer ${url})`;
4811
+ function describeLiveDevRun(run) {
4812
+ const url = localhostPreviewUrl(run.port);
4813
+ if (run.preview === "window") {
4814
+ return `${run.scriptName} Electron window (renderer ${url})`;
6897
4815
  }
6898
- return `${run2.scriptName} ${url}`;
4816
+ return `${run.scriptName} ${url}`;
6899
4817
  }
6900
4818
  function formatDevAccessHint(runs) {
6901
4819
  const live = (runs ?? []).filter(
@@ -6913,7 +4831,7 @@ function formatDevAccessHint(runs) {
6913
4831
  const bits = live.map(describeLiveDevRun).join("; ");
6914
4832
  return `This worktree's Dev apps: ${bits}. preview=url \u2192 open the URL; preview=window \u2192 Electron window is the app. MCP \`get_run_log\` reads the Run-tab terminal. Do not guess :3000.`;
6915
4833
  }
6916
- return "To open the project Dev app: MCP `run_dev_script` (same as the desktop Dev button) returns `{url, port, preview}` (`SIDEBOARD_PORT`, not 3000). preview=url \u2192 open that URL (browser, curl, Playwright). preview=window \u2192 Electron: the native window is the app; url is renderer/HMR only (no preload/IPC in a browser). If already running, `list_run_scripts` `active[].url`. `get_run_log` reads the Run-tab terminal (tail). `stop_dev_script` to stop.";
4834
+ return "To open a project Dev app: MCP `run_dev_script` (same as the desktop Dev button; pass `name` for one script \u2014 several can run at once, each with its own port) returns `{url, port, preview}` (`SIDEBOARD_PORT`, not 3000). preview=url \u2192 open that URL (browser, curl, Playwright). preview=window \u2192 Electron: the native window is the app; url is renderer/HMR only (no preload/IPC in a browser). If already running, `list_run_scripts` `active[].url`. `get_run_log` reads that script\u2019s Run-tab terminal (tail). `stop_dev_script` stops one script; omit name to stop all.";
6917
4835
  }
6918
4836
  function formatWorktreeDirective(thread, opts) {
6919
4837
  const worktree = normPath(thread.worktreePath);
@@ -6953,9 +4871,9 @@ function formatWorktreeDirective(thread, opts) {
6953
4871
  );
6954
4872
  } else {
6955
4873
  const slug = opts?.githubSlug ?? "<origin-owner/name>";
6956
- const resolve2 = opts?.githubSlug ? "" : " Resolve `<origin-owner/name>` with `git remote get-url origin` in this worktree.";
4874
+ const resolve = opts?.githubSlug ? "" : " Resolve `<origin-owner/name>` with `git remote get-url origin` in this worktree.";
6957
4875
  lines.push(
6958
- `- Prefer a draft PR first: \`gh pr create --draft --assignee @me -R ${slug}\` (update via \`gh pr edit -R ${slug}\`). Always pass \`-R\` \u2014 bare \`gh pr create\` may target upstream on dual-remote checkouts.${resolve2} Assign to the GitHub-connected user (\`--assignee @me\`) unless the user named someone else. Mark ready for review only when asked. If GitHub rejects the body as too long, retry with a short \`--body-file\` \u2014 do not paste a changelog or diff into \`--body\`.`
4876
+ `- Prefer a draft PR first: \`gh pr create --draft --assignee @me -R ${slug}\` (update via \`gh pr edit -R ${slug}\`). Always pass \`-R\` \u2014 bare \`gh pr create\` may target upstream on dual-remote checkouts.${resolve} Assign to the GitHub-connected user (\`--assignee @me\`) unless the user named someone else. Mark ready for review only when asked. If GitHub rejects the body as too long, retry with a short \`--body-file\` \u2014 do not paste a changelog or diff into \`--body\`.`
6959
4877
  );
6960
4878
  }
6961
4879
  lines.push(
@@ -7005,17 +4923,17 @@ function formatIssueToolsDirective(opts) {
7005
4923
  ];
7006
4924
  if (opts.linear) {
7007
4925
  lines.push(
7008
- "- Linear: `linear_search_issues` (assignee=me; pass `updatedSince` for \u201Cany updates since yesterday?\u201D \u2014 new tickets, edits, and comments), `linear_get_issue` (comments; pass `include=full` if crush.truncated), `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."
4926
+ "- 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."
7009
4927
  );
7010
4928
  }
7011
4929
  if (github) {
7012
4930
  lines.push(
7013
- "- GitHub: `github_search_issues` (assignee=me; `updatedSince` for inbox updates + comments), `github_get_issue` (comments; pass `include=full` if crush.truncated), `github_comment`, `github_update_issue` (state open|closed, labels, project, parent, relations), `github_create_issue` (pass `parent` for spin-offs). Uses Account `gh`."
4931
+ "- 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`."
7014
4932
  );
7015
4933
  }
7016
4934
  if (opts.abletime) {
7017
4935
  lines.push(
7018
- "- AbleTime: `abletime_list_tasks` / `abletime_search_tasks` (`updatedSince` for inbox updates + comments), `abletime_get_task` (comments; pass `include=full` if crush.truncated), `abletime_comment`, `abletime_update_task` (state, labels, project, parent, relations), `abletime_create_task` (pass `parent` for spin-offs; `abletime_list_projects` if needed)."
4936
+ "- 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)."
7019
4937
  );
7020
4938
  }
7021
4939
  lines.push(
@@ -7071,9 +4989,9 @@ function formatPrGateDirective() {
7071
4989
  }
7072
4990
  function formatProcessGuideDirective(opts) {
7073
4991
  const worktree = opts?.worktreePath?.trim();
7074
- const hasGraphSkill = !!worktree && existsSync16(join16(worktree, ".claude/skills/graph-engineering/SKILL.md"));
7075
- const hasReviewSkill = !!worktree && existsSync16(join16(worktree, ".claude/skills/review/SKILL.md"));
7076
- const hasInstallSkill = !!worktree && existsSync16(join16(worktree, ".claude/skills/worktree-install/SKILL.md"));
4992
+ const hasGraphSkill = !!worktree && existsSync12(join11(worktree, ".claude/skills/graph-engineering/SKILL.md"));
4993
+ const hasReviewSkill = !!worktree && existsSync12(join11(worktree, ".claude/skills/review/SKILL.md"));
4994
+ const hasInstallSkill = !!worktree && existsSync12(join11(worktree, ".claude/skills/worktree-install/SKILL.md"));
7077
4995
  return [
7078
4996
  "Process guides (recurring work only):",
7079
4997
  hasGraphSkill ? "- Migrations, ports, batch fixes, and other fan-out: follow `.claude/skills/graph-engineering/SKILL.md` (`/graph-engineering`). Judge first; state on disk; grow the rulebook; do not patch around it." : null,
@@ -7093,7 +5011,7 @@ function formatLongRunningDirective(opts) {
7093
5011
  "- Hanging, no useful output, or the wrong thing \u2192 `stop_job` (or the helper with `stop <id>`). Do not stop a pack/test/deploy that is clearly making progress.",
7094
5012
  "- Stay in the loop until stillRunning is false (or you stopped it). ok \u2192 finish the task. failed / stopped \u2192 read the log, fix or narrow the command, start once.",
7095
5013
  "State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available).",
7096
- "Project Dev / run scripts (`.sideboard` `[scripts.run.*]`, same as the desktop Dev button): use MCP `list_run_scripts` / `run_dev_script` / `stop_dev_script` / `get_run_log`. `get_run_log` is the Run-tab terminal (tail; raise `tail` only if truncated). Do not detached-job or shell-spawn that same command. `run_dev_script` returns `{url, port, preview}` (`SIDEBOARD_PORT`). preview=url \u2192 open that URL; preview=window \u2192 Electron window is the app (url is renderer/HMR only). Never guess :3000."
5014
+ "Project Dev / run scripts (`.sideboard` `[scripts.run.*]`, same as the desktop Dev button): use MCP `list_run_scripts` / `run_dev_script` / `stop_dev_script` / `get_run_log`. Pass `name` to start one script; several can run at once, each with its own port. `get_run_log` is that script\u2019s Run-tab terminal (tail; raise `tail` only if truncated). Do not detached-job or shell-spawn that same command. `run_dev_script` returns `{url, port, preview}` (`SIDEBOARD_PORT`). preview=url \u2192 open that URL; preview=window \u2192 Electron window is the app (url is renderer/HMR only). Never guess :3000."
7097
5015
  ].join("\n");
7098
5016
  }
7099
5017
  function formatLongRunningReminder(opts) {
@@ -7110,7 +5028,7 @@ function formatArtifactDirective() {
7110
5028
  ].join("\n");
7111
5029
  }
7112
5030
  function formatUiReminder() {
7113
- 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.";
5031
+ 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; pass name \u2014 several scripts can run at once) 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 that script\u2019s Run-tab terminal; stop_dev_script to stop one.";
7114
5032
  }
7115
5033
 
7116
5034
  // src/integrations/optional-services.ts
@@ -7399,9 +5317,9 @@ async function syncThreadBranchFromGit(threadId) {
7399
5317
  }
7400
5318
 
7401
5319
  // src/store/schedules.ts
7402
- import { randomUUID as randomUUID4 } from "crypto";
7403
- import { existsSync as existsSync17, readFileSync as readFileSync12 } from "fs";
7404
- import { join as join17 } from "path";
5320
+ import { randomUUID as randomUUID2 } from "crypto";
5321
+ import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
5322
+ import { join as join12 } from "path";
7405
5323
  import { Cron } from "croner";
7406
5324
  var DURATION_RE = /^(\d+)(s|m|h|d)$/i;
7407
5325
  var UNIT_MS = {
@@ -7411,7 +5329,7 @@ var UNIT_MS = {
7411
5329
  d: 864e5
7412
5330
  };
7413
5331
  function schedulesPath() {
7414
- return join17(appDataDir(), "schedules.json");
5332
+ return join12(appDataDir(), "schedules.json");
7415
5333
  }
7416
5334
  function parseDurationMs(every) {
7417
5335
  const m = every.trim().match(DURATION_RE);
@@ -7517,9 +5435,9 @@ function normalizeTask(raw) {
7517
5435
  }
7518
5436
  function readAll() {
7519
5437
  const path = schedulesPath();
7520
- if (!existsSync17(path)) return [];
5438
+ if (!existsSync13(path)) return [];
7521
5439
  try {
7522
- const parsed = JSON.parse(readFileSync12(path, "utf8"));
5440
+ const parsed = JSON.parse(readFileSync8(path, "utf8"));
7523
5441
  if (!Array.isArray(parsed)) return [];
7524
5442
  return parsed.map((row) => {
7525
5443
  try {
@@ -7544,7 +5462,7 @@ function buildTask(input, now) {
7544
5462
  const ts = nowIso(now);
7545
5463
  const agent = input.agent ? coerceOrchestratorAgent(input.agent) : null;
7546
5464
  return {
7547
- id: randomUUID4(),
5465
+ id: randomUUID2(),
7548
5466
  name: input.name?.trim() || defaultScheduleName(prompt),
7549
5467
  prompt,
7550
5468
  enabled: input.enabled !== false,
@@ -7658,7 +5576,7 @@ function formatScheduledPrompt(name, prompt) {
7658
5576
  ${prompt}`;
7659
5577
  }
7660
5578
  async function defaultDeps() {
7661
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-5YU46YNS.js");
5579
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-6ZQ2CYS4.js");
7662
5580
  const orch = getOrchestrator2();
7663
5581
  return {
7664
5582
  findThread: (id) => findThreadByRef(id),
@@ -7716,14 +5634,14 @@ function armOne(schedule) {
7716
5634
  if (!schedule.enabled) return;
7717
5635
  const at = new Date(schedule.nextRunAt);
7718
5636
  if (Number.isNaN(at.getTime())) return;
7719
- const delay2 = Math.max(0, at.getTime() - Date.now());
7720
- if (delay2 <= 250) {
5637
+ const delay = Math.max(0, at.getTime() - Date.now());
5638
+ if (delay <= 250) {
7721
5639
  void fireSchedule(schedule.id).then(() => {
7722
5640
  if (isThisProcessDesktopHost()) armSchedules();
7723
5641
  });
7724
5642
  return;
7725
5643
  }
7726
- const capped = Math.min(delay2, SET_TIMEOUT_MAX);
5644
+ const capped = Math.min(delay, SET_TIMEOUT_MAX);
7727
5645
  const timer = setTimeout(() => {
7728
5646
  timers.delete(schedule.id);
7729
5647
  const live = getSchedule(schedule.id);
@@ -7791,8 +5709,8 @@ function writeLiveStatus(threadId, status, lastError) {
7791
5709
  }
7792
5710
  function collectActiveRunPorts(runs) {
7793
5711
  const ports = /* @__PURE__ */ new Set();
7794
- for (const run2 of runs) {
7795
- const list = run2.ports?.length ? run2.ports : run2.port != null ? [run2.port] : [];
5712
+ for (const run of runs) {
5713
+ const list = run.ports?.length ? run.ports : run.port != null ? [run.port] : [];
7796
5714
  for (const p of list) {
7797
5715
  if (Number.isFinite(p) && p > 0) ports.add(p);
7798
5716
  }
@@ -7910,7 +5828,7 @@ var Orchestrator = class {
7910
5828
  this.lightReconcileAt.set(repoPath, Date.now());
7911
5829
  for (const thread of listThreads()) {
7912
5830
  if (isGlobalThread(thread) || thread.repoPath !== repoPath) continue;
7913
- if (!existsSync18(thread.worktreePath)) {
5831
+ if (!existsSync14(thread.worktreePath)) {
7914
5832
  setStatus(thread.id, "broken", "Worktree missing on disk");
7915
5833
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
7916
5834
  }
@@ -7939,7 +5857,7 @@ var Orchestrator = class {
7939
5857
  }
7940
5858
  continue;
7941
5859
  }
7942
- if (!existsSync18(thread.worktreePath)) {
5860
+ if (!existsSync14(thread.worktreePath)) {
7943
5861
  setStatus(thread.id, "broken", "Worktree missing on disk");
7944
5862
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
7945
5863
  continue;
@@ -7976,7 +5894,7 @@ var Orchestrator = class {
7976
5894
  orphans: orphans.map((o) => ({ path: o.path, repoPath: o.repoPath }))
7977
5895
  });
7978
5896
  }
7979
- const { autoCleanupOrphansEnabled } = await import("./app-settings-RKFFUIFQ.js");
5897
+ const { autoCleanupOrphansEnabled } = await import("./app-settings-L5HGYXWJ.js");
7980
5898
  if (autoCleanupOrphansEnabled() && shouldRunWorktreeCleanup() && orphans.length > 0) {
7981
5899
  await cleanupOrphanWorktrees({ repoPaths });
7982
5900
  }
@@ -8107,8 +6025,8 @@ var Orchestrator = class {
8107
6025
  scheduleQuotaResume(threadId, resumeAt) {
8108
6026
  this.clearQuotaResumeTimer(threadId);
8109
6027
  updateThread(threadId, { quotaResumeAt: resumeAt.toISOString() });
8110
- const delay2 = Math.max(5e3, resumeAt.getTime() - Date.now());
8111
- const capped = Math.min(delay2, 2147483647);
6028
+ const delay = Math.max(5e3, resumeAt.getTime() - Date.now());
6029
+ const capped = Math.min(delay, 2147483647);
8112
6030
  const timer = setTimeout(() => {
8113
6031
  this.quotaResumeTimers.delete(threadId);
8114
6032
  void this.resumeAfterQuotaWait(threadId);
@@ -8365,7 +6283,7 @@ var Orchestrator = class {
8365
6283
  }
8366
6284
  await setup;
8367
6285
  if (skipSetup) return;
8368
- const { autoRunAfterSetupEnabled } = await import("./app-settings-RKFFUIFQ.js");
6286
+ const { autoRunAfterSetupEnabled } = await import("./app-settings-L5HGYXWJ.js");
8369
6287
  if (autoRunAfterSetupEnabled()) {
8370
6288
  try {
8371
6289
  await this.startDev(threadId);
@@ -8744,7 +6662,7 @@ var Orchestrator = class {
8744
6662
  }
8745
6663
  const isBrightsy = fresh.agent === "brightsy";
8746
6664
  const isOrchestration = isOrchestratorThread(fresh);
8747
- const { autoRenameBranchEnabled, getGithubGitAuthMode, gitBranchPrefixSetting } = await import("./app-settings-RKFFUIFQ.js");
6665
+ const { autoRenameBranchEnabled, getGithubGitAuthMode, gitBranchPrefixSetting } = await import("./app-settings-L5HGYXWJ.js");
8748
6666
  const gitAuthMode = getGithubGitAuthMode();
8749
6667
  const worktreeDirective = isBrightsy || isOrchestration ? null : formatWorktreeDirective(fresh, {
8750
6668
  githubSlug: await withTimeout(
@@ -9296,7 +7214,7 @@ var Orchestrator = class {
9296
7214
  });
9297
7215
  }
9298
7216
  const latestShared = mergeWorktreeActiveRuns(threadsSharingWorktree(thread.worktreePath));
9299
- const run2 = {
7217
+ const run = {
9300
7218
  scriptName: resolvedName,
9301
7219
  port: handle.port,
9302
7220
  ports: handle.ports,
@@ -9305,7 +7223,7 @@ var Orchestrator = class {
9305
7223
  };
9306
7224
  const nextRuns = [
9307
7225
  ...latestShared.activeRuns.filter((r) => r.scriptName !== resolvedName),
9308
- run2
7226
+ run
9309
7227
  ];
9310
7228
  this.syncWorktreeRuns(
9311
7229
  thread.worktreePath,
@@ -9351,7 +7269,7 @@ var Orchestrator = class {
9351
7269
  const request = sameIntent ? pending : {
9352
7270
  op,
9353
7271
  scriptName: scriptName ?? null,
9354
- requestId: randomUUID5(),
7272
+ requestId: randomUUID3(),
9355
7273
  requestedAt: (/* @__PURE__ */ new Date()).toISOString()
9356
7274
  };
9357
7275
  if (!sameIntent) {
@@ -9372,14 +7290,14 @@ var Orchestrator = class {
9372
7290
  throw new Error(req.error);
9373
7291
  }
9374
7292
  const shared = mergeWorktreeActiveRuns(threadsSharingWorktree(latest.worktreePath));
9375
- const run2 = scriptName ? shared.activeRuns.find((r) => r.scriptName === scriptName) : shared.activeRuns[0];
7293
+ const run = scriptName ? shared.activeRuns.find((r) => r.scriptName === scriptName) : shared.activeRuns[0];
9376
7294
  const fulfilled = req?.requestId === request.requestId && Boolean(req.fulfilledAt);
9377
7295
  const requestGone = !req || req.requestId !== request.requestId;
9378
7296
  if (op === "start") {
9379
- if (run2 && (fulfilled || requestGone || run2.startedAt >= request.requestedAt)) {
9380
- return { port: run2.port, scriptName: run2.scriptName, ports: run2.ports };
7297
+ if (run && (fulfilled || requestGone || run.startedAt >= request.requestedAt)) {
7298
+ return { port: run.port, scriptName: run.scriptName, ports: run.ports };
9381
7299
  }
9382
- if (fulfilled && !run2) {
7300
+ if (fulfilled && !run) {
9383
7301
  throw new Error("Desktop reported the run script started, but it is not active.");
9384
7302
  }
9385
7303
  } else if (fulfilled) {
@@ -9445,15 +7363,15 @@ var Orchestrator = class {
9445
7363
  }
9446
7364
  this.processes.delete(legacyKey);
9447
7365
  }
9448
- const run2 = shared.activeRuns.find((r) => r.scriptName === scriptName);
9449
- if (!hadHandle2 && run2) {
9450
- if (typeof run2.pid === "number") {
9451
- killDetachedRunTree(run2.pid, thread.worktreePath);
7366
+ const run = shared.activeRuns.find((r) => r.scriptName === scriptName);
7367
+ if (!hadHandle2 && run) {
7368
+ if (typeof run.pid === "number") {
7369
+ killDetachedRunTree(run.pid, thread.worktreePath);
9452
7370
  }
9453
- killListenersOnPorts(collectActiveRunPorts([run2]));
7371
+ killListenersOnPorts(collectActiveRunPorts([run]));
9454
7372
  }
9455
7373
  const remaining = shared.activeRuns.filter((r) => r.scriptName !== scriptName);
9456
- const isPrimary = shared.devPort != null && run2?.port === shared.devPort;
7374
+ const isPrimary = shared.devPort != null && run?.port === shared.devPort;
9457
7375
  this.syncWorktreeRuns(
9458
7376
  thread.worktreePath,
9459
7377
  remaining,
@@ -9471,9 +7389,9 @@ var Orchestrator = class {
9471
7389
  }
9472
7390
  }
9473
7391
  if (!hadHandle && shared.activeRuns.length > 0) {
9474
- for (const run2 of shared.activeRuns) {
9475
- if (typeof run2.pid === "number") {
9476
- killDetachedRunTree(run2.pid, thread.worktreePath);
7392
+ for (const run of shared.activeRuns) {
7393
+ if (typeof run.pid === "number") {
7394
+ killDetachedRunTree(run.pid, thread.worktreePath);
9477
7395
  }
9478
7396
  }
9479
7397
  killListenersOnPorts(collectActiveRunPorts(shared.activeRuns));
@@ -9517,9 +7435,9 @@ var Orchestrator = class {
9517
7435
  (k) => isWorktreeRunProcessKey(k, thread.worktreePath) || siblings.some((t) => k.startsWith(`${t.id}:run:`) || k === `${t.id}:dev`)
9518
7436
  );
9519
7437
  if (!force && hasLiveHandle) continue;
9520
- for (const run2 of shared.activeRuns) {
9521
- if (typeof run2.pid === "number") {
9522
- killDetachedRunTree(run2.pid, thread.worktreePath);
7438
+ for (const run of shared.activeRuns) {
7439
+ if (typeof run.pid === "number") {
7440
+ killDetachedRunTree(run.pid, thread.worktreePath);
9523
7441
  }
9524
7442
  }
9525
7443
  killListenersOnPorts(collectActiveRunPorts(shared.activeRuns));
@@ -9728,7 +7646,7 @@ var Orchestrator = class {
9728
7646
  return thread;
9729
7647
  }
9730
7648
  const start = Date.now();
9731
- return new Promise((resolve2, reject) => {
7649
+ return new Promise((resolve, reject) => {
9732
7650
  let settled = false;
9733
7651
  let timer;
9734
7652
  const finish = (fn) => {
@@ -9746,7 +7664,7 @@ var Orchestrator = class {
9746
7664
  finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
9747
7665
  return;
9748
7666
  }
9749
- finish(() => resolve2(latest));
7667
+ finish(() => resolve(latest));
9750
7668
  return;
9751
7669
  }
9752
7670
  if (event.type === "status_changed") {
@@ -9756,7 +7674,7 @@ var Orchestrator = class {
9756
7674
  return;
9757
7675
  }
9758
7676
  if (!["running", "queued"].includes(latest.status)) {
9759
- finish(() => resolve2(latest));
7677
+ finish(() => resolve(latest));
9760
7678
  }
9761
7679
  }
9762
7680
  });
@@ -9768,12 +7686,12 @@ var Orchestrator = class {
9768
7686
  }
9769
7687
  const current = this.healStaleReportedActivity(raw);
9770
7688
  if (!this.threadLooksLive(current)) {
9771
- finish(() => resolve2(current));
7689
+ finish(() => resolve(current));
9772
7690
  return;
9773
7691
  }
9774
7692
  if (Date.now() - start > timeoutMs) {
9775
7693
  if (opts?.resolveIfStillRunning) {
9776
- finish(() => resolve2(current));
7694
+ finish(() => resolve(current));
9777
7695
  } else {
9778
7696
  finish(() => reject(new Error("wait_for_turn timed out")));
9779
7697
  }
@@ -10054,7 +7972,7 @@ var Orchestrator = class {
10054
7972
  if (Object.keys(patch).length === 0) continue;
10055
7973
  updateThread(t.id, patch);
10056
7974
  }
10057
- const { autoArchiveOnMergeEnabled } = await import("./app-settings-RKFFUIFQ.js");
7975
+ const { autoArchiveOnMergeEnabled } = await import("./app-settings-L5HGYXWJ.js");
10058
7976
  const latest = this.requireThread(thread.id);
10059
7977
  if (!shouldAutoArchiveOnPrMerge({
10060
7978
  previousPrState: prevState || null,
@@ -10345,7 +8263,7 @@ var Orchestrator = class {
10345
8263
  this.enforceHistoryRetention();
10346
8264
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
10347
8265
  try {
10348
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-QJ3XIPUJ.js");
8266
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-F33Z4CEY.js");
10349
8267
  await ensureWorkspace2(thread.repoPath);
10350
8268
  } catch {
10351
8269
  }
@@ -10372,7 +8290,7 @@ var Orchestrator = class {
10372
8290
  await runArchiveScript(thread.repoPath, thread.worktreePath);
10373
8291
  } catch {
10374
8292
  }
10375
- const { deleteBranchOnPurgeEnabled } = await import("./app-settings-RKFFUIFQ.js");
8293
+ const { deleteBranchOnPurgeEnabled } = await import("./app-settings-L5HGYXWJ.js");
10376
8294
  const deleteBranch = opts?.deleteBranch ?? deleteBranchOnPurgeEnabled();
10377
8295
  await removeWorktree(thread.repoPath, thread.worktreePath, {
10378
8296
  deleteBranch: deleteBranch ? thread.branchName : void 0
@@ -10393,20 +8311,20 @@ var Orchestrator = class {
10393
8311
  this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
10394
8312
  return restored2;
10395
8313
  }
10396
- if (!existsSync18(thread.worktreePath)) {
8314
+ if (!existsSync14(thread.worktreePath)) {
10397
8315
  if (isCowboyThread(thread) || isPrimaryCheckoutThread(thread)) {
10398
8316
  throw new Error(
10399
8317
  `Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
10400
8318
  );
10401
8319
  }
10402
- const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-KQA4ON2G.js");
8320
+ const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-C7RPVHZ7.js");
10403
8321
  const slug = thread.worktreePath.split("/").pop();
10404
8322
  const dest = thread.worktreePath;
10405
8323
  await withRepoGitLock(thread.repoPath, async () => {
10406
8324
  await git(["worktree", "add", dest, thread.branchName], thread.repoPath);
10407
8325
  });
10408
- const { ensureWorktreeSideboardIgnored: ensureWorktreeSideboardIgnored2 } = await import("./worktree-exclude-RXZPTCNH.js");
10409
- await ensureWorktreeSideboardIgnored2(dest);
8326
+ const { ensureWorktreeSideboardIgnored } = await import("./worktree-exclude-RXZPTCNH.js");
8327
+ await ensureWorktreeSideboardIgnored(dest);
10410
8328
  void createThreadWorktree2;
10411
8329
  void slug;
10412
8330
  }
@@ -10509,7 +8427,7 @@ async function startOrchestration(opts) {
10509
8427
  sourceRef: "default",
10510
8428
  ...createOpts
10511
8429
  }).catch(async () => {
10512
- const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-KQA4ON2G.js");
8430
+ const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-C7RPVHZ7.js");
10513
8431
  const repo = await resolveRepoRoot2(repoPath);
10514
8432
  const def = await resolveDefaultBranch2(repo);
10515
8433
  return orch.createThread({
@@ -10590,116 +8508,11 @@ export {
10590
8508
  pendingInjectedNotices,
10591
8509
  lastAgentReply,
10592
8510
  formatInjectedNoticesForTurn,
10593
- isTerminalTaskState,
10594
- isIncompleteTaskState,
10595
- needsCoordinatorAction,
10596
- endedOnAskUser,
10597
- deriveTaskState,
10598
- mcpWaitForTurnTimeoutMs,
10599
- mcpWaitTaskHint,
10600
- waitForTurnToolResult,
10601
8511
  applyPromptCacheTtlEnv,
10602
8512
  spawnAgentTurn,
10603
8513
  AGENT_GIT_ACTIONS,
10604
8514
  agentGitPrompt,
10605
8515
  resolveSidebarGitPrompt,
10606
- CHARS_PER_CONTEXT_TOKEN,
10607
- estimateMessageChars,
10608
- estimateThreadChars,
10609
- estimateOccupancyTokens,
10610
- threadHasCompactedContext,
10611
- forwardOccupancyTokens,
10612
- forwardContextUsage,
10613
- applyForwardOccupancy,
10614
- CLAUDE_SUMMARY_TIMEOUT_MS,
10615
- summarizeConversation,
10616
- extractiveSummary,
10617
- CONTEXT_COMPACT_CHARS,
10618
- CONTEXT_KEEP_RECENT_CHARS,
10619
- CONTEXT_KEEP_RECENT_MESSAGES,
10620
- CONTEXT_MIN_MESSAGES,
10621
- SESSION_RESET_OCCUPANCY_TOKENS,
10622
- shouldCompactContext,
10623
- splitForCompaction,
10624
- formatMessagesAsTranscript,
10625
- SEED_MAX_CHARS,
10626
- SEED_FULL_TOOL_MESSAGES,
10627
- buildSessionSeed,
10628
- BRIGHTSY_SUMMARIZE_CONTEXT_TOOL,
10629
- extractBrightsyContextSummary,
10630
- findLastBrightsyContextSummary,
10631
- messagesSinceLastBrightsyContextSummary,
10632
- buildBrightsySessionSeed,
10633
- compactArchiveProse,
10634
- messagesForCompactArchive,
10635
- appendCompactedMessages,
10636
- applyCompaction,
10637
- lastRequestOccupancy,
10638
- shouldResetSessionForOccupancy,
10639
- maybeCompactContext,
10640
- sameWorktreePath,
10641
- takenTeamSlugsForChatTab,
10642
- threadsSharingWorktree,
10643
- formatTranscriptMarkdown,
10644
- forkMessageSlice,
10645
- buildForkTranscriptAttachment,
10646
- createChatTab,
10647
- forkChatTab,
10648
- REVIEW_REQUEST_TEMPLATE,
10649
- REVIEW_SKILL_PATH,
10650
- REVIEW_SKILL_NAME,
10651
- wrapReviewSkillMarkdown,
10652
- REPO_REVIEW_PATH,
10653
- REPO_REVIEW_NAME,
10654
- CONTEXT_REVIEW_PATH,
10655
- REVIEW_REQUEST_PATH,
10656
- LEGACY_REVIEW_REQUEST_PATH,
10657
- REVIEW_REQUEST_NAME,
10658
- REVIEW_REQUEST_PREFILL,
10659
- shouldRefreshReviewRequestTemplate,
10660
- ensureReviewGuidelinesFile,
10661
- ensureReviewSkillFile,
10662
- resolveReviewGuidelines,
10663
- ensureReviewRequestFile,
10664
- buildReviewRequestAttachment,
10665
- readExistingReviewRequestFile,
10666
- canReuseIdleWorktreeAgentForReview,
10667
- requestReview,
10668
- RUN_PORTS_REL,
10669
- worktreeRunPortsPath,
10670
- loadWorktreeRunPorts,
10671
- saveWorktreeRunPorts,
10672
- mcpWaitForJobTimeoutMs,
10673
- waitForDetachedJob,
10674
- stopDetachedJob,
10675
- PORT_RANGE_SIZE,
10676
- readWorktreeInclude,
10677
- resolveFilesToCopy,
10678
- copyConfiguredFiles,
10679
- captureLoginEnv,
10680
- buildWorkspaceScriptEnv,
10681
- killableListenerPids,
10682
- listenerCwd,
10683
- isOtherWorktreeListenerCwd,
10684
- isThisWorktreeListenerCwd,
10685
- killDetachedRunTree,
10686
- killListenersOnPorts,
10687
- killStaleWorktreeListeners,
10688
- runSetupScript,
10689
- runConventionSetup,
10690
- runWorkspaceSetup,
10691
- runArchiveScript,
10692
- listRunScripts,
10693
- getDefaultRunScript,
10694
- getRunScript,
10695
- getRunMode,
10696
- reservePort,
10697
- allocatePort,
10698
- tryReservePort,
10699
- tryReservePorts,
10700
- reservePortRange,
10701
- allocatePortRange,
10702
- startDevServer,
10703
8516
  findOrphanWorktrees,
10704
8517
  cleanupOrphanWorktrees,
10705
8518
  shouldRunWorktreeCleanup,