@sideboard-ai/core 0.1.264 → 0.1.267

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 +363 -68
  53. package/dist/index.d.ts +363 -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,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import {
4
+ titleFromPrompt
5
+ } from "./chunk-LX5R5JOR.js";
3
6
  import {
4
7
  extractPresentedPlan,
5
8
  readPlanFile,
@@ -7,36 +10,73 @@ import {
7
10
  } from "./chunk-4GK32AYX.js";
8
11
  import {
9
12
  releaseCaffeinateHoldForThread
10
- } from "./chunk-QN5DL2QO.js";
13
+ } from "./chunk-5DZUWGKV.js";
11
14
  import {
12
15
  adjustBrightsyTurnExit,
13
- applyAgentEvent,
14
16
  applyAgentRunnerHeapEnv,
17
+ getAdapter,
18
+ isSessionQuotaLimit,
19
+ packagedMcpDir,
20
+ parseBrightsyCliLine,
21
+ parseSessionQuotaResetAt,
22
+ planModeTurnInstruction,
23
+ resolveQuotaFallbackAgent
24
+ } from "./chunk-2XIRV7FS.js";
25
+ import {
26
+ REVIEW_TAB_TITLE,
27
+ applyWorktreePkgCacheEnv,
28
+ buildBrightsySessionSeed,
29
+ buildForkTranscriptAttachment,
30
+ buildSessionSeed,
31
+ copyConfiguredFiles,
32
+ createChatTab,
33
+ deriveTaskState,
34
+ forkChatTab,
35
+ forkMessageSlice,
36
+ formatReviewWriteGateDirective,
37
+ formatReviewWriteGateReminder,
38
+ getDefaultRunScript,
39
+ getRunMode,
40
+ isJobContinuePrompt,
41
+ isReviewWriteGatedThread,
42
+ isWorktreeRunProcessKey,
43
+ killDetachedRunTree,
44
+ killListenersOnPorts,
45
+ lastActivityAtForWait,
46
+ listRunScripts,
47
+ listRunningDetachedJobs,
48
+ maybeCompactContext,
49
+ mergeWorktreeActiveRuns,
50
+ planJobContinue,
51
+ requestReview,
52
+ resolveWorktreeSetupLog,
53
+ runArchiveScript,
54
+ runWorkspaceSetup,
55
+ startDevServer,
56
+ threadsSharingWorktree,
57
+ turnWatchedDetachedJob,
58
+ worktreeDevProcessKey,
59
+ worktreeRunProcessKey,
60
+ worktreeSetupProcessKey
61
+ } from "./chunk-4ECDIKAS.js";
62
+ import {
63
+ applyAgentEvent,
15
64
  clipAgentEventForPaint,
16
- clipToolResultForStore,
17
- compactSummaryPart,
18
65
  fallbackTurnFailDetail,
19
66
  finalizeParts,
20
67
  formatAgentErrorContinuePrompt,
21
68
  formatTurnExitError,
22
- getAdapter,
23
69
  isBrightsyNdjsonLine,
24
70
  isInternalAgentStatusText,
25
71
  isPollWrapperToolName,
26
- isSessionQuotaLimit,
27
- lastAssistantMessageText,
28
72
  liveActivitySummary,
29
73
  looksLikeAgentFailureMessage,
30
74
  looksLikeInvalidAgentSession,
31
75
  looksLikeV8Oom,
76
+ messagePartParentId,
32
77
  normalizeParseResult,
33
- packagedMcpDir,
34
- parseBrightsyCliLine,
35
- parseSessionQuotaResetAt,
36
78
  partsToAssistantText,
37
- planModeTurnInstruction,
38
79
  pushTurnStderr,
39
- resolveQuotaFallbackAgent,
40
80
  shouldFeedErrorBackToAgent,
41
81
  shouldRetryCodexPluginIsolate,
42
82
  shouldRetryFailedAgentTurn,
@@ -44,7 +84,7 @@ import {
44
84
  summarizeTurnStderr,
45
85
  toolDescription,
46
86
  turnFailChatText
47
- } from "./chunk-EK3KVT7I.js";
87
+ } from "./chunk-UKSJO36E.js";
48
88
  import {
49
89
  SLACK_REPLY_FORMATTING,
50
90
  addWorkspace,
@@ -56,22 +96,19 @@ import {
56
96
  ensureGlobalCoordinatorCwd,
57
97
  ensureWorkspace,
58
98
  healOrchestrationSoccerTitles,
59
- isCloudCoordinatorThread,
60
99
  isGlobalRepoPath,
61
100
  isGlobalThread,
62
101
  isOrchestratorThread,
63
- isSlackCoordinatorThread,
64
102
  orchestratorSessionPoisonedByBuiltins,
65
103
  persistPendingFileAttachments,
66
104
  removeWorkspace,
67
105
  stageAbsolutePathsAsAttachments,
68
106
  stageBuffersAsAttachments,
69
107
  syncWorkspacesFromThreads
70
- } from "./chunk-L5WGAIL2.js";
108
+ } from "./chunk-GZXKUUJO.js";
71
109
  import {
72
110
  addPrStackLayer,
73
111
  agentNicknameFromWorktreePath,
74
- allocateTeamName,
75
112
  allocateTeamSlug,
76
113
  canonicalizeRepoPath,
77
114
  ciChecksFailed,
@@ -113,7 +150,6 @@ import {
113
150
  resolvePrSelector,
114
151
  resolveRepoRoot,
115
152
  switchWorktreeBranch,
116
- takenSlugsFromThread,
117
153
  teamSlugFromName,
118
154
  ticketSlugForBranch,
119
155
  withRepoGitLock,
@@ -121,26 +157,13 @@ import {
121
157
  worktreeIdentityTitles,
122
158
  worktreeNameFromPath,
123
159
  worktreeSlugForTicket
124
- } from "./chunk-4ALDX5IO.js";
125
- import {
126
- ATTACHMENTS_DIR,
127
- DETACHED_JOBS_DIR,
128
- LEGACY_ATTACHMENTS_DIR,
129
- LEGACY_DETACHED_JOBS_DIR
130
- } from "./chunk-N2WW62LF.js";
131
- import {
132
- titleFromPrompt
133
- } from "./chunk-LX5R5JOR.js";
134
- import {
135
- ensureWorktreeSideboardIgnored
136
- } from "./chunk-DD67JWTN.js";
160
+ } from "./chunk-NWE3NOWQ.js";
137
161
  import {
138
162
  clearStaleIndexLocks,
139
163
  enrichPathWithNpmGlobalBin,
140
164
  ensureAgentPath,
141
165
  git,
142
- resolveGitDirsForLockRecovery,
143
- run
166
+ resolveGitDirsForLockRecovery
144
167
  } from "./chunk-2UG25RMK.js";
145
168
  import {
146
169
  HISTORY_CLEANUP_INTERVAL_HOURS_DEFAULT,
@@ -150,7 +173,6 @@ import {
150
173
  caffeinateWhileSchedulesEnabled,
151
174
  childEnvWithAppSettings,
152
175
  coerceOrchestratorAgent,
153
- contextTokens,
154
176
  followUpBehavior,
155
177
  formatViewerContextDirective,
156
178
  formatViewerContextReminder,
@@ -173,13 +195,10 @@ import {
173
195
  updateAdvancedSettings,
174
196
  usageOnLimit,
175
197
  withTurnScopedCost
176
- } from "./chunk-36FOBJBF.js";
198
+ } from "./chunk-OIENQEGG.js";
177
199
  import {
178
200
  writePrivateFile
179
201
  } from "./chunk-NSTQ6QKD.js";
180
- import {
181
- stripNestedElectronEnv
182
- } from "./chunk-WWUABK6B.js";
183
202
  import {
184
203
  examplePrefixedBranch,
185
204
  sanitizeGitBranchPrefix
@@ -189,7 +208,7 @@ import {
189
208
  applyManagedAccountEnv,
190
209
  nextManagedAccount,
191
210
  resolveAccountForThread
192
- } from "./chunk-CEMNIGQH.js";
211
+ } from "./chunk-UN5Q7POQ.js";
193
212
  import {
194
213
  appendMessage,
195
214
  createEmptyThread,
@@ -201,21 +220,17 @@ import {
201
220
  updateThread,
202
221
  withThreadLock,
203
222
  writeThread
204
- } from "./chunk-TJ673EZT.js";
223
+ } from "./chunk-JANCPF7Z.js";
205
224
  import {
206
225
  annotateDevAccessRuns,
207
226
  appDataDir,
208
- findConventionSetup,
209
- loadRepoSettings,
210
227
  loadWorkspaceSettings,
211
- runCursorWorktreeSetup,
212
228
  sideboardReposDir,
213
229
  sideboardWorkspacesDir,
214
230
  threadLivePath,
215
231
  threadsDir,
216
- workspaceSettingsSourceLabel,
217
232
  worktreesRoot
218
- } from "./chunk-Z2LGMV6C.js";
233
+ } from "./chunk-H5XL4KUS.js";
219
234
 
220
235
  // src/orchestrator/orchestrator.ts
221
236
  import { EventEmitter } from "events";
@@ -258,92 +273,9 @@ function formatInjectedNoticesForTurn(notices) {
258
273
  ].join("\n\n");
259
274
  }
260
275
 
261
- // src/orchestrator/task-state.ts
262
- function isIncompleteTaskState(state) {
263
- return state === "failed" || state === "canceled";
264
- }
265
- function needsCoordinatorAction(state) {
266
- return isIncompleteTaskState(state) || state === "input-required";
267
- }
268
- var ASK_USER_TOOL = /(^|[_.:/])ask_user$/i;
269
- function endedOnAskUser(parts) {
270
- if (!parts?.length) return false;
271
- for (let i = parts.length - 1; i >= 0; i--) {
272
- const p = parts[i];
273
- if (p.type === "tool" && !p.parentId) {
274
- return ASK_USER_TOOL.test(p.name);
275
- }
276
- }
277
- return false;
278
- }
279
- function deriveTaskState(input) {
280
- if (input.stillRunning) {
281
- return input.status === "queued" ? "submitted" : "working";
282
- }
283
- switch (input.status) {
284
- case "error":
285
- case "broken":
286
- return "failed";
287
- case "stopped":
288
- return "canceled";
289
- default:
290
- return endedOnAskUser(input.lastAgentParts) ? "input-required" : "completed";
291
- }
292
- }
293
-
294
- // src/mcp/wait-for-turn.ts
295
- var MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
296
- function mcpWaitForTurnTimeoutMs(requested) {
297
- const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
298
- if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
299
- return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
300
- }
301
- 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.";
302
- 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.";
303
- 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.";
304
- var MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
305
- 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.";
306
- 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.";
307
- function mcpWaitTaskHint(taskState, status) {
308
- switch (taskState) {
309
- case "submitted":
310
- return MCP_WAIT_QUEUED_HINT;
311
- case "working":
312
- return MCP_WAIT_STILL_RUNNING_HINT;
313
- case "input-required":
314
- return MCP_WAIT_INPUT_REQUIRED_HINT;
315
- case "canceled":
316
- return MCP_WAIT_STOPPED_HINT;
317
- case "failed":
318
- return status === "broken" ? MCP_WAIT_BROKEN_HINT : MCP_WAIT_ERROR_HINT;
319
- default:
320
- return void 0;
321
- }
322
- }
323
- function nonemptyIso(value) {
324
- const trimmed = value?.trim();
325
- return trimmed ? trimmed : null;
326
- }
327
- function lastActivityAtForWait(stillRunning, liveUpdatedAt, fallbacks) {
328
- if (stillRunning) return nonemptyIso(liveUpdatedAt);
329
- for (const value of fallbacks) {
330
- const iso = nonemptyIso(value);
331
- if (iso) return iso;
332
- }
333
- return null;
334
- }
335
- function waitForTurnToolResult(input) {
336
- return {
337
- ...input,
338
- usage: input.usage ?? null,
339
- hint: mcpWaitTaskHint(input.taskState, input.status),
340
- incomplete: needsCoordinatorAction(input.taskState)
341
- };
342
- }
343
-
344
276
  // src/orchestrator/orchestrator.ts
345
- import { randomUUID as randomUUID5 } from "crypto";
346
- import { existsSync as existsSync18 } from "fs";
277
+ import { randomUUID as randomUUID3 } from "crypto";
278
+ import { existsSync as existsSync14 } from "fs";
347
279
 
348
280
  // src/agents/spawn.ts
349
281
  import { createInterface } from "readline";
@@ -377,6 +309,49 @@ async function withTimeout(promise, ms, label) {
377
309
  }
378
310
  }
379
311
 
312
+ // src/agents/stdout-replay.ts
313
+ var REPLAY_MIN_CHARS = 80;
314
+ function textSoFar(parts, parentId) {
315
+ const parent = parentId?.trim() || "";
316
+ let out = "";
317
+ for (const part of parts) {
318
+ if (part.type !== "text") continue;
319
+ if ((messagePartParentId(part) ?? "") !== parent) continue;
320
+ out += part.text;
321
+ }
322
+ return out;
323
+ }
324
+ function replaySized(text) {
325
+ return text.trim().length >= REPLAY_MIN_CHARS;
326
+ }
327
+ function tailAfterAccumulated(accumulated, incoming) {
328
+ if (incoming.startsWith(accumulated)) return incoming.slice(accumulated.length);
329
+ const acc = accumulated.trim();
330
+ const inc = incoming.trim();
331
+ if (!acc || !inc.startsWith(acc)) return null;
332
+ const at = incoming.indexOf(acc);
333
+ if (at < 0) return null;
334
+ return incoming.slice(at + acc.length);
335
+ }
336
+ function foldDuplicateStdout(accumulated, incoming) {
337
+ if (!incoming) return null;
338
+ if (!accumulated) return incoming;
339
+ const accTrim = accumulated.trim();
340
+ const incTrim = incoming.trim();
341
+ if (!incTrim) return incoming;
342
+ if (incTrim === accTrim) return null;
343
+ if (!replaySized(incoming)) return incoming;
344
+ if (accTrim.includes(incTrim)) return null;
345
+ let tail = tailAfterAccumulated(accumulated, incoming);
346
+ if (tail == null) return incoming;
347
+ for (let i = 0; i < 3 && tail.trim(); i++) {
348
+ const next = tailAfterAccumulated(accumulated, tail);
349
+ if (next == null) break;
350
+ tail = next;
351
+ }
352
+ return tail.trim() ? tail : null;
353
+ }
354
+
380
355
  // src/hook/agent-host-env.ts
381
356
  import { delimiter } from "path";
382
357
  var DROP_EXACT = /* @__PURE__ */ new Set([
@@ -433,56 +408,6 @@ function sanitizeAgentHostEnv(env, cwd) {
433
408
  return env;
434
409
  }
435
410
 
436
- // src/hook/worktree-pkg-cache.ts
437
- import { existsSync, mkdirSync } from "fs";
438
- import { join as join2 } from "path";
439
- var WORKTREE_PKG_CACHE_REL = ".context/.sideboard/pkg-cache";
440
- function worktreePkgCacheRoot(worktreePath) {
441
- return join2(worktreePath, WORKTREE_PKG_CACHE_REL);
442
- }
443
- function worktreePkgCacheEnv(worktreePath) {
444
- const root = worktreePkgCacheRoot(worktreePath);
445
- const store = join2(root, "pnpm-store");
446
- const pnpmCache = join2(root, "pnpm");
447
- const npmCache = join2(root, "npm");
448
- const nodeGyp = join2(root, "node-gyp");
449
- const electron = join2(root, "electron");
450
- return {
451
- npm_config_store_dir: store,
452
- PNPM_STORE_DIR: store,
453
- npm_config_cache_dir: pnpmCache,
454
- npm_config_cache: npmCache,
455
- npm_config_devdir: nodeGyp,
456
- // @electron/get — do not set ELECTRON_CACHE (stripNestedElectronEnv).
457
- electron_config_cache: electron
458
- };
459
- }
460
- function isSet(value) {
461
- return value != null && value.trim() !== "";
462
- }
463
- function fillGroup(env, keys, values) {
464
- if (keys.some((key) => isSet(env[key]))) return;
465
- for (const key of keys) {
466
- const value = values[key];
467
- if (value) env[key] = value;
468
- }
469
- }
470
- function ensureWorktreePkgCacheDirs(worktreePath) {
471
- if (!existsSync(worktreePath)) return;
472
- mkdirSync(worktreePkgCacheRoot(worktreePath), { recursive: true });
473
- }
474
- function applyWorktreePkgCacheEnv(env, worktreePath) {
475
- const cwd = worktreePath.trim();
476
- if (!cwd) return;
477
- const next = worktreePkgCacheEnv(cwd);
478
- fillGroup(env, ["npm_config_store_dir", "PNPM_STORE_DIR"], next);
479
- fillGroup(env, ["npm_config_cache_dir"], next);
480
- fillGroup(env, ["npm_config_cache"], next);
481
- fillGroup(env, ["npm_config_devdir"], next);
482
- fillGroup(env, ["electron_config_cache"], next);
483
- ensureWorktreePkgCacheDirs(cwd);
484
- }
485
-
486
411
  // src/agents/cursor-stream-coalesce.ts
487
412
  function isTextEvent(event) {
488
413
  return event.type === "stdout" || event.type === "thinking";
@@ -560,9 +485,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
560
485
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
561
486
  );
562
487
  }
563
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-6LDUZ2NF.js");
488
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-EWFL4HPW.js");
564
489
  if (isGlobalThread2(thread)) {
565
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-J7DKHOCH.js");
490
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-4GHKCVRH.js");
566
491
  ensureGlobalCoordinatorCwd2(
567
492
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
568
493
  );
@@ -669,7 +594,8 @@ async function spawnAgentTurn(thread, input, onEvent) {
669
594
  events = normalizeParseResult(parseBrightsyCliLine(line));
670
595
  }
671
596
  }
672
- for (const parsed of events) {
597
+ for (const event of events) {
598
+ let parsed = event;
673
599
  if (parsed.type === "session_id") {
674
600
  sessionId = parsed.data;
675
601
  outbound.push(parsed);
@@ -685,9 +611,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
685
611
  if (thread.agent === "brightsy" && isBrightsyNdjsonLine(parsed.data)) {
686
612
  continue;
687
613
  }
688
- if (assistantText && (parsed.data === assistantText || assistantText.endsWith(parsed.data))) {
689
- continue;
690
- }
614
+ const folded = foldDuplicateStdout(textSoFar(parts, parsed.parentId), parsed.data);
615
+ if (folded == null) continue;
616
+ if (folded !== parsed.data) parsed = { ...parsed, data: folded };
691
617
  }
692
618
  parts = applyAgentEvent(parts, parsed);
693
619
  if (parsed.type === "stdout") {
@@ -729,2117 +655,185 @@ var AGENT_GIT_ACTIONS = [
729
655
  "ready-for-review",
730
656
  "merge"
731
657
  ];
732
- function agentGitPrompt(action, opts) {
733
- switch (action) {
734
- case "commit-push":
735
- return "Commit and push.";
736
- case "create-draft":
737
- return "Commit, push, and open a draft PR.";
738
- case "create-web":
739
- return "Commit, push, and open a PR in the browser.";
740
- case "resolve-conflicts": {
741
- const base = opts?.prBase?.trim().replace(/^refs\/heads\//, "");
742
- const named = base ? ` (${base})` : "";
743
- return `Merge the remote branch${named} into your branch and resolve conflicts. Then, commit and push your changes.`;
744
- }
745
- case "ready-for-review":
746
- return "Ready for review.";
747
- case "merge":
748
- return "Merge PR.";
749
- }
750
- }
751
- function resolveSidebarGitPrompt(action, opts) {
752
- if (action === "create-pr" || action === "create-draft" || action === "create-web") {
753
- const custom = opts?.createPr?.trim();
754
- if (custom) return custom;
755
- return agentGitPrompt(action === "create-pr" ? "create-draft" : action);
756
- }
757
- if (action === "resolve-conflicts") {
758
- const custom = opts?.resolveMergeConflicts?.trim();
759
- if (custom) return custom;
760
- return agentGitPrompt("resolve-conflicts", opts);
761
- }
762
- return agentGitPrompt(action, opts);
763
- }
764
- var RESOLVE_CONFLICTS_RE = /^Merge the remote branch(?: \(([^)]+)\))? into your branch and resolve conflicts\. Then, commit and push your changes\.$/;
765
- function gitActionExpansion(action, base) {
766
- switch (action) {
767
- case "commit-push":
768
- return "Commit any uncommitted work with a purpose-stating message, then push to origin (`git push -u origin HEAD`; this updates an existing PR when one is linked). Do not start a checks loop unless a goal was given.";
769
- case "create-draft":
770
- return "Commit, push, then create a draft PR with `gh pr create --draft --assignee @me -R <origin-owner/name>` (title/body from the change purpose). Update the existing PR instead if one is linked. Use a different `--assignee` only if the user named someone else.";
771
- case "create-web":
772
- return "Commit, push, then `gh pr create --web --assignee @me -R <origin-owner/name>`. Use a different `--assignee` only if the user named someone else.";
773
- case "resolve-conflicts":
774
- return `Fetch the PR base${base ? ` (\`${base}\`)` : ""}, merge it into this branch, resolve conflicts carefully, then commit and push until the PR is mergeable.`;
775
- case "ready-for-review":
776
- 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.";
777
- case "merge":
778
- 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.";
779
- }
780
- }
781
- function expandCanonicalGitRequest(prompt) {
782
- const trimmed = prompt.trim();
783
- let action = null;
784
- let base = null;
785
- for (const candidate of AGENT_GIT_ACTIONS) {
786
- if (candidate === "resolve-conflicts") continue;
787
- if (agentGitPrompt(candidate) === trimmed) {
788
- action = candidate;
789
- break;
790
- }
791
- }
792
- if (!action) {
793
- const m = RESOLVE_CONFLICTS_RE.exec(trimmed);
794
- if (m) {
795
- action = "resolve-conflicts";
796
- base = m[1] ?? null;
797
- }
798
- }
799
- let expansion = action ? gitActionExpansion(action, base) : null;
800
- if (!expansion && trimmed === "Address review comments.") {
801
- expansion = "Read the PR review feedback (`gh pr view --comments`, `gh api` review comments), make the requested changes, commit, and push.";
802
- } else if (!expansion && FIX_CI_RE.test(trimmed)) {
803
- 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.";
804
- }
805
- if (!expansion) return prompt;
806
- return `${trimmed}
807
-
808
- (Sideboard git request \u2014 do not ask for clarification: ${expansion})`;
809
- }
810
- var FIX_CI_RE = /^Fix CI: .+\.$/;
811
-
812
- // src/git/pr-merge-archive.ts
813
- function normalizePrState(state) {
814
- return (state ?? "").trim().toUpperCase();
815
- }
816
- function samePrIdentity(a, b) {
817
- const left = (a ?? "").trim().replace(/\/+$/, "");
818
- const right = (b ?? "").trim().replace(/\/+$/, "");
819
- if (!left || !right) return false;
820
- if (left === right) return true;
821
- const ln = left.match(/\/pull\/(\d+)/i)?.[1];
822
- const rn = right.match(/\/pull\/(\d+)/i)?.[1];
823
- return Boolean(ln && rn && ln === rn);
824
- }
825
- function shouldStopRunOnPrRetarget(previousPrUrl, nextPrUrl) {
826
- const prev = (previousPrUrl ?? "").trim();
827
- const next = (nextPrUrl ?? "").trim();
828
- if (!prev || !next) return false;
829
- return !samePrIdentity(prev, next);
830
- }
831
- function shouldPersistFetchedPrMeta(opts) {
832
- const meta = opts.metaUrl?.trim() || "";
833
- if (!meta) return false;
834
- const head = opts.headPrUrl?.trim() || "";
835
- if (head) return samePrIdentity(meta, head);
836
- const live = opts.livePrUrl?.trim() || "";
837
- if (live) return samePrIdentity(meta, live);
838
- return true;
839
- }
840
- function sameLoginList(a, b) {
841
- const left = [...a ?? []].map((s) => s.trim().toLowerCase()).filter(Boolean).sort();
842
- const right = [...b ?? []].map((s) => s.trim().toLowerCase()).filter(Boolean).sort();
843
- return left.length === right.length && left.every((login, i) => login === right[i]);
844
- }
845
- function threadPrMetaPatch(thread, meta) {
846
- const prevState = normalizePrState(thread.prState);
847
- const nextState = normalizePrState(meta.state);
848
- const patch = {};
849
- if (meta.url && meta.url !== thread.prUrl) patch.prUrl = meta.url;
850
- if (meta.title && meta.title !== thread.prTitle) patch.prTitle = meta.title;
851
- if (nextState && nextState !== prevState) patch.prState = nextState;
852
- const nextDraft = Boolean(meta.isDraft) && nextState !== "MERGED" && nextState !== "CLOSED";
853
- if (nextDraft !== Boolean(thread.prIsDraft)) patch.prIsDraft = nextDraft;
854
- const nextAuthor = meta.authorLogin?.trim() || "";
855
- if (nextAuthor && nextAuthor !== (thread.prAuthorLogin ?? "").trim()) {
856
- patch.prAuthorLogin = nextAuthor;
857
- }
858
- const nextReviewers = meta.reviewerLogins ?? [];
859
- if (!sameLoginList(nextReviewers, thread.prReviewerLogins)) {
860
- patch.prReviewerLogins = nextReviewers;
861
- }
862
- const nextDecision = meta.reviewDecision ?? null;
863
- if ((thread.prReviewDecision ?? null) !== nextDecision) {
864
- patch.prReviewDecision = nextDecision;
865
- }
866
- const nextMergeable = meta.mergeable ?? null;
867
- if ((thread.prMergeable ?? null) !== nextMergeable) {
868
- patch.prMergeable = nextMergeable;
869
- }
870
- const nextMergeState = meta.mergeStateStatus ?? null;
871
- if ((thread.prMergeStateStatus ?? null) !== nextMergeState) {
872
- patch.prMergeStateStatus = nextMergeState;
873
- }
874
- const nextQueued = Boolean(meta.isInMergeQueue);
875
- if (Boolean(thread.prIsInMergeQueue) !== nextQueued) {
876
- patch.prIsInMergeQueue = nextQueued;
877
- }
878
- const nextLabels = meta.labels ?? [];
879
- if (!sameLoginList(nextLabels, thread.prLabels)) {
880
- patch.prLabels = nextLabels;
881
- }
882
- const mergeState = (nextMergeState ?? "").toUpperCase();
883
- if (mergeState === "CLEAN" && thread.prChecksFailed) {
884
- patch.prChecksFailed = false;
885
- } else if ((mergeState === "UNSTABLE" || mergeState === "BLOCKED" && (nextDecision ?? "").toUpperCase() === "APPROVED") && !thread.prChecksFailed) {
886
- patch.prChecksFailed = true;
887
- }
888
- if (thread.skipAutoArchiveOnMerge && nextState && nextState !== "MERGED" && nextState !== "CLOSED") {
889
- patch.skipAutoArchiveOnMerge = false;
890
- }
891
- return patch;
892
- }
893
- function threadPrChecksFailedPatch(thread, failed) {
894
- if (thread.prChecksFailed === failed) return {};
895
- return { prChecksFailed: failed };
896
- }
897
- function shouldAutoArchiveOnPrMerge(opts) {
898
- if (!opts.autoArchiveEnabled) return false;
899
- if (opts.isGlobal) return false;
900
- if (opts.skipAutoArchiveOnMerge) return false;
901
- if (opts.threadStatus === "archived") return false;
902
- const next = normalizePrState(opts.nextPrState);
903
- if (next !== "MERGED") return false;
904
- return normalizePrState(opts.previousPrState) !== "MERGED";
905
- }
906
-
907
- // src/hook/conductor.ts
908
- import {
909
- copyFileSync,
910
- existsSync as existsSync4,
911
- mkdirSync as mkdirSync5,
912
- readdirSync as readdirSync2,
913
- readFileSync as readFileSync4,
914
- realpathSync
915
- } from "fs";
916
- import { execFileSync } from "child_process";
917
- import { createServer } from "net";
918
- import { basename, dirname as dirname4, join as join6, resolve, sep } from "path";
919
- import { execa as execa2 } from "execa";
920
- import { createInterface as createInterface2 } from "readline";
921
-
922
- // src/review/request-review.ts
923
- import { randomUUID as randomUUID2 } from "crypto";
924
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
925
- import { dirname, join as join3 } from "path";
926
-
927
- // src/threads/chat-tabs.ts
928
- import { randomUUID } from "crypto";
929
-
930
- // src/composer/context-estimate.ts
931
- var CHARS_PER_CONTEXT_TOKEN = 4;
932
- function estimateMessageChars(message) {
933
- let n = message.text.length + 16;
934
- for (const part of message.parts ?? []) {
935
- n += estimatePartChars(part);
936
- }
937
- return n;
938
- }
939
- function estimatePartChars(part) {
940
- switch (part.type) {
941
- case "text":
942
- case "thinking":
943
- return part.text.length;
944
- case "tool": {
945
- const input = part.input ? JSON.stringify(part.input) : "";
946
- return part.name.length + (part.description?.length ?? 0) + (part.detail?.length ?? 0) + (part.result?.length ?? 0) + input.length + 32;
947
- }
948
- default:
949
- return 0;
950
- }
951
- }
952
- function estimateThreadChars(messages) {
953
- return messages.reduce((sum, m) => sum + estimateMessageChars(m), 0);
954
- }
955
- function estimateOccupancyTokens(messages) {
956
- return Math.ceil(estimateThreadChars(messages) / CHARS_PER_CONTEXT_TOKEN);
957
- }
958
- function applyForwardOccupancy(messages) {
959
- const tokens = estimateOccupancyTokens(messages);
960
- if (tokens <= 0) return messages;
961
- for (let i = messages.length - 1; i >= 0; i--) {
962
- const m = messages[i];
963
- if (m?.role !== "agent" || !m.usage) continue;
964
- const current = contextTokens(m.usage);
965
- if (tokens >= current) return messages;
966
- const next = messages.slice();
967
- next[i] = { ...m, usage: { ...m.usage, lastRequestTokens: tokens } };
968
- return next;
969
- }
970
- return messages;
971
- }
972
-
973
- // src/composer/summarize.ts
974
- var MAX_TRANSCRIPT_CHARS = 12e4;
975
- var MAX_SUMMARY_CHARS = 6e3;
976
- var CLAUDE_SUMMARY_TIMEOUT_MS = 2e4;
977
- async function summarizeConversation(transcript, opts) {
978
- const clipped = transcript.length > MAX_TRANSCRIPT_CHARS ? `${transcript.slice(0, MAX_TRANSCRIPT_CHARS)}
979
-
980
- [\u2026truncated\u2026]` : transcript;
981
- const claudeSummary = await tryClaudeSummary(clipped, opts);
982
- if (claudeSummary) {
983
- return { summary: clipSummary(claudeSummary), method: "claude" };
984
- }
985
- return { summary: clipSummary(extractiveSummary(clipped)), method: "extractive" };
986
- }
987
- async function tryClaudeSummary(transcript, opts) {
988
- ensureAgentPath();
989
- const prompt = [
990
- "Summarize this coding-agent conversation for continuity in a fresh session.",
991
- "Use concise markdown bullets covering:",
992
- "- Goal / request",
993
- "- Key decisions and constraints",
994
- "- Files / areas touched",
995
- "- Current state and open tasks",
996
- "- Anything the next turn must remember",
997
- "Do not invent details. No preamble.",
998
- "",
999
- "Transcript:",
1000
- transcript
1001
- ].join("\n");
1002
- try {
1003
- const { stdout, exitCode } = await run(
1004
- "claude",
1005
- [
1006
- "-p",
1007
- prompt,
1008
- "--output-format",
1009
- "text",
1010
- "--permission-mode",
1011
- "bypassPermissions",
1012
- "--model",
1013
- "haiku"
1014
- ],
1015
- {
1016
- cwd: opts?.cwd,
1017
- reject: false,
1018
- timeoutMs: opts?.timeoutMs ?? CLAUDE_SUMMARY_TIMEOUT_MS
1019
- }
1020
- );
1021
- if (exitCode !== 0) return null;
1022
- const text = stdout.trim();
1023
- if (text.length < 40) return null;
1024
- return text;
1025
- } catch {
1026
- return null;
1027
- }
1028
- }
1029
- function extractiveSummary(transcript) {
1030
- const lines = transcript.split("\n").map((l) => l.trim()).filter(Boolean);
1031
- const users = [];
1032
- const tools = [];
1033
- const agents = [];
1034
- let mode = "none";
1035
- for (const line of lines) {
1036
- if (line.startsWith("### User")) {
1037
- mode = "user";
1038
- continue;
1039
- }
1040
- if (line.startsWith("### Agent") || line.startsWith("## Prior")) {
1041
- mode = "agent";
1042
- continue;
1043
- }
1044
- if (line.startsWith("- ") && line.includes(":")) {
1045
- tools.push(line.replace(/^- /, ""));
1046
- continue;
1047
- }
1048
- if (mode === "user" && users.length < 8) {
1049
- users.push(line.slice(0, 240));
1050
- } else if (mode === "agent" && agents.length < 6) {
1051
- agents.push(line.slice(0, 240));
1052
- }
1053
- }
1054
- const uniqueTools = [...new Set(tools)].slice(0, 20);
1055
- const parts = [
1056
- "## Compacted context (extractive)",
1057
- "",
1058
- "### User goals",
1059
- ...users.length ? users.map((u) => `- ${u}`) : ["- (none captured)"],
1060
- "",
1061
- "### Recent agent notes",
1062
- ...agents.length ? agents.map((a) => `- ${a}`) : ["- (none captured)"]
1063
- ];
1064
- if (uniqueTools.length) {
1065
- parts.push("", "### Tools / files", ...uniqueTools.map((t) => `- ${t}`));
1066
- }
1067
- parts.push(
1068
- "",
1069
- "_Automatic extractive summary \u2014 Claude summarizer was unavailable._"
1070
- );
1071
- return parts.join("\n");
1072
- }
1073
- function clipSummary(text) {
1074
- if (text.length <= MAX_SUMMARY_CHARS) return text;
1075
- return `${text.slice(0, MAX_SUMMARY_CHARS)}
1076
-
1077
- [\u2026summary truncated\u2026]`;
1078
- }
1079
-
1080
- // src/composer/context-compact.ts
1081
- var CONTEXT_COMPACT_CHARS = 4e5;
1082
- var CONTEXT_KEEP_RECENT_CHARS = 24e3;
1083
- var CONTEXT_KEEP_RECENT_MESSAGES = 12;
1084
- var CONTEXT_MIN_MESSAGES = 10;
1085
- var SESSION_RESET_OCCUPANCY_TOKENS = 75e4;
1086
- function shouldCompactContext(messages, thresholds = {}) {
1087
- const maxChars = thresholds.maxChars ?? CONTEXT_COMPACT_CHARS;
1088
- const minMessages = thresholds.minMessages ?? CONTEXT_MIN_MESSAGES;
1089
- if (messages.length < minMessages) return false;
1090
- return estimateThreadChars(messages) >= maxChars;
1091
- }
1092
- function splitForCompaction(messages, thresholds = {}) {
1093
- const keepChars = thresholds.keepRecentChars ?? CONTEXT_KEEP_RECENT_CHARS;
1094
- const keepCount = thresholds.keepRecentMessages ?? CONTEXT_KEEP_RECENT_MESSAGES;
1095
- if (messages.length === 0) return { older: [], recent: [] };
1096
- let recentChars = 0;
1097
- let cut = messages.length;
1098
- for (let i = messages.length - 1; i >= 0; i--) {
1099
- const next = recentChars + estimateMessageChars(messages[i]);
1100
- const kept = messages.length - i;
1101
- if (kept >= keepCount && next > keepChars) break;
1102
- recentChars = next;
1103
- cut = i;
1104
- }
1105
- const minRecent = Math.min(keepCount, messages.length);
1106
- cut = Math.min(cut, messages.length - minRecent);
1107
- if (cut <= 0) return { older: [], recent: messages };
1108
- return {
1109
- older: messages.slice(0, cut),
1110
- recent: messages.slice(cut)
1111
- };
1112
- }
1113
- function formatToolPart(t, detail) {
1114
- if (detail === "summary") {
1115
- const label = t.description || t.detail || t.name;
1116
- const path = t.filePath ? ` (${t.filePath})` : "";
1117
- return `- ${t.name}: ${label}${path}`;
1118
- }
1119
- const lines = [`#### Tool: ${t.name}`, `Status: ${t.status}`];
1120
- if (t.description) lines.push(`Description: ${t.description}`);
1121
- if (t.filePath) lines.push(`Path: ${t.filePath}`);
1122
- if (t.input && Object.keys(t.input).length > 0) {
1123
- lines.push("Input:");
1124
- lines.push("```json");
1125
- lines.push(clipToolResultForStore(JSON.stringify(t.input, null, 2)) ?? "");
1126
- lines.push("```");
1127
- } else if (t.detail) {
1128
- lines.push(`Detail: ${t.detail}`);
1129
- }
1130
- if (t.result != null && t.result !== "") {
1131
- lines.push("Result:");
1132
- lines.push("```");
1133
- lines.push(t.result);
1134
- lines.push("```");
1135
- }
1136
- return lines.join("\n");
1137
- }
1138
- function formatMessagesAsTranscript(messages, opts) {
1139
- const tools = opts?.tools ?? "full";
1140
- const thinking = opts?.thinking ?? true;
1141
- return messages.map((m) => formatMessageBlock(m, { tools, thinking })).join("\n\n");
1142
- }
1143
- function formatMessageBlock(m, opts) {
1144
- if (m.role === "summary") return `## Prior summary
1145
- ${m.text}`;
1146
- if (m.role === "user") return `### User
1147
- ${m.text}`;
1148
- const bits = [];
1149
- if (m.text?.trim()) {
1150
- bits.push(`### Agent
1151
- ${m.text}`);
1152
- } else {
1153
- bits.push("### Agent");
1154
- }
1155
- if (opts.thinking) {
1156
- for (const th of m.parts ?? []) {
1157
- if (th.type !== "thinking" || !th.text.trim()) continue;
1158
- bits.push("Thinking:");
1159
- bits.push(th.text);
1160
- }
1161
- }
1162
- const toolParts = (m.parts ?? []).filter(
1163
- (p) => p.type === "tool"
1164
- );
1165
- if (opts.tools !== "none" && toolParts.length > 0) {
1166
- if (opts.tools === "summary") bits.push("Tools:");
1167
- for (const t of toolParts) {
1168
- bits.push(formatToolPart(t, opts.tools));
1169
- }
1170
- }
1171
- return bits.join("\n");
1172
- }
1173
- var SEED_MAX_CHARS = 6e4;
1174
- var SEED_FULL_TOOL_MESSAGES = 6;
1175
- function budgetSeedBlocks(messages, blocks, maxChars) {
1176
- const keep = new Array(blocks.length).fill(false);
1177
- let used = 0;
1178
- for (let i = 0; i < blocks.length; i++) {
1179
- if (messages[i].role === "summary") {
1180
- keep[i] = true;
1181
- used += blocks[i].length + 2;
1182
- }
1183
- }
1184
- for (let i = blocks.length - 1; i >= 0; i--) {
1185
- if (keep[i]) continue;
1186
- const next = used + blocks[i].length + 2;
1187
- if (next > maxChars && used > 0) continue;
1188
- keep[i] = true;
1189
- used = next;
1190
- }
1191
- const dropped = keep.filter((k) => !k).length;
1192
- const marker = `_(${dropped} older message${dropped === 1 ? "" : "s"} omitted for length)_`;
1193
- const kept = [];
1194
- let markerPlaced = false;
1195
- for (let i = 0; i < blocks.length; i++) {
1196
- if (!keep[i]) continue;
1197
- if (dropped > 0 && !markerPlaced && messages[i].role !== "summary") {
1198
- kept.push(marker);
1199
- markerPlaced = true;
1200
- }
1201
- kept.push(blocks[i]);
1202
- }
1203
- if (dropped > 0 && !markerPlaced) kept.push(marker);
1204
- return kept.join("\n\n");
1205
- }
1206
- function buildSessionSeed(messages, opts) {
1207
- if (messages.length === 0) return null;
1208
- const tools = opts?.tools ?? "full";
1209
- const fullCount = opts?.fullToolMessages ?? SEED_FULL_TOOL_MESSAGES;
1210
- const maxChars = opts?.maxChars ?? SEED_MAX_CHARS;
1211
- const olderTools = tools === "none" ? "none" : "summary";
1212
- const fullFrom = Math.max(0, messages.length - fullCount);
1213
- const blocks = messages.map(
1214
- (m, i) => formatMessageBlock(m, {
1215
- tools: i >= fullFrom ? tools : olderTools,
1216
- thinking: false
1217
- })
1218
- );
1219
- const body = budgetSeedBlocks(messages, blocks, maxChars);
1220
- if (!body.trim()) return null;
1221
- return [
1222
- "Sideboard conversation context (restored after compaction or a new session):",
1223
- "",
1224
- body,
1225
- "",
1226
- "Continue from this context. Do not repeat the summary unless asked."
1227
- ].join("\n");
1228
- }
1229
- var BRIGHTSY_SUMMARIZE_CONTEXT_TOOL = "summarize_context";
1230
- function extractBrightsyContextSummary(result) {
1231
- if (!result?.trim()) return null;
1232
- const trimmed = result.trim();
1233
- const lower = trimmed.toLowerCase();
1234
- 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")) {
1235
- return null;
1236
- }
1237
- try {
1238
- const parsed = JSON.parse(trimmed);
1239
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1240
- if (parsed.error != null && typeof parsed.context_summary !== "string") {
1241
- return null;
1242
- }
1243
- if (typeof parsed.context_summary === "string" && parsed.context_summary.trim()) {
1244
- return parsed.context_summary.trim();
1245
- }
1246
- return null;
1247
- }
1248
- } catch {
1249
- }
1250
- if (trimmed.includes('"error"') && !trimmed.includes("context_summary")) {
1251
- return null;
1252
- }
1253
- return trimmed;
1254
- }
1255
- function findLastBrightsyContextSummary(messages) {
1256
- for (let i = messages.length - 1; i >= 0; i--) {
1257
- const message = messages[i];
1258
- if (message?.role !== "agent") continue;
1259
- for (const part of message.parts ?? []) {
1260
- if (part.type !== "tool" || part.name !== BRIGHTSY_SUMMARIZE_CONTEXT_TOOL) {
1261
- continue;
1262
- }
1263
- if (part.status === "error") continue;
1264
- const text = extractBrightsyContextSummary(part.result);
1265
- if (text) return { index: i, text };
1266
- }
1267
- }
1268
- return null;
1269
- }
1270
- function buildBrightsySessionSeed(messages, opts) {
1271
- const match = findLastBrightsyContextSummary(messages);
1272
- const tail = match ? messages.slice(match.index + 1) : messages;
1273
- const budget = Math.max(1e3, (opts?.maxChars ?? SEED_MAX_CHARS) - (match?.text.length ?? 0));
1274
- const tailBlocks = tail.map((m) => formatMessageBlock(m, { tools: "none", thinking: false }));
1275
- const body = budgetSeedBlocks(tail, tailBlocks, budget);
1276
- if (!match && !body.trim()) return null;
1277
- const blocks = [
1278
- "Sideboard conversation context (restored after compaction or a new session):",
1279
- ""
1280
- ];
1281
- if (match) {
1282
- blocks.push(`## Prior summary
1283
- ${match.text}`, "");
1284
- }
1285
- if (body.trim()) {
1286
- blocks.push(body, "");
1287
- }
1288
- blocks.push("Continue from this context. Do not repeat the summary unless asked.");
1289
- return blocks.join("\n");
1290
- }
1291
- function compactArchiveProse(message) {
1292
- if (message.origin === "continue") return null;
1293
- if (message.role === "summary") return null;
1294
- let text = (message.text ?? "").trim();
1295
- if (!text && message.role === "agent") {
1296
- text = lastAssistantMessageText(message.parts).trim();
1297
- }
1298
- if (!text || isInternalAgentStatusText(text)) return null;
1299
- return { role: message.role, text, ts: message.ts };
1300
- }
1301
- function messagesForCompactArchive(older) {
1302
- const out = [];
1303
- for (const message of older) {
1304
- const prose = compactArchiveProse(message);
1305
- if (prose) out.push(prose);
1306
- }
1307
- return out;
1308
- }
1309
- function appendCompactedMessages(existing, older) {
1310
- const next = messagesForCompactArchive(older);
1311
- if (!existing?.length) return next;
1312
- if (!next.length) return existing;
1313
- return [...existing, ...next];
1314
- }
1315
- function applyCompaction(messages, summaryText, thresholds = {}) {
1316
- const { older, recent } = splitForCompaction(messages, thresholds);
1317
- if (older.length === 0) return messages;
1318
- const text = summaryText.trim();
1319
- const summary = {
1320
- role: "summary",
1321
- text,
1322
- parts: [compactSummaryPart(text)],
1323
- ts: (/* @__PURE__ */ new Date()).toISOString()
1324
- };
1325
- return [summary, ...recent];
1326
- }
1327
- function lastRequestOccupancy(thread) {
1328
- for (let i = thread.messages.length - 1; i >= 0; i--) {
1329
- const usage = thread.messages[i]?.usage;
1330
- if (usage) return contextTokens(usage);
1331
- }
1332
- return 0;
1333
- }
1334
- function shouldResetSessionForOccupancy(thread, occupancyTokens = SESSION_RESET_OCCUPANCY_TOKENS) {
1335
- return lastRequestOccupancy(thread) >= occupancyTokens;
1336
- }
1337
- async function maybeCompactContext(thread, thresholds = {}, summarize = summarizeConversation) {
1338
- if (!shouldCompactContext(thread.messages, thresholds)) {
1339
- return { didCompact: false, thread };
1340
- }
1341
- const { older } = splitForCompaction(thread.messages, thresholds);
1342
- if (older.length === 0) {
1343
- return { didCompact: false, thread };
1344
- }
1345
- const transcript = formatMessagesAsTranscript(older, { tools: "summary" });
1346
- const { summary, method } = await summarize(transcript, {
1347
- cwd: thread.worktreePath
1348
- });
1349
- let messages = applyCompaction(thread.messages, summary, thresholds);
1350
- const compactedMessages = appendCompactedMessages(thread.compactedMessages, older);
1351
- const resetSession = shouldResetSessionForOccupancy({ messages: thread.messages });
1352
- if (resetSession) {
1353
- messages = applyForwardOccupancy(messages);
1354
- }
1355
- const next = {
1356
- ...thread,
1357
- messages,
1358
- ...compactedMessages.length ? { compactedMessages } : { compactedMessages: void 0 },
1359
- sessionId: resetSession ? null : thread.sessionId,
1360
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1361
- };
1362
- return {
1363
- didCompact: true,
1364
- thread: next,
1365
- summary,
1366
- method,
1367
- olderCount: older.length
1368
- };
1369
- }
1370
-
1371
- // src/threads/worktree-runtime.ts
1372
- function worktreeProcessPrefix(worktreePath) {
1373
- return `wt:${normalizeWorktreePath(worktreePath)}`;
1374
- }
1375
- function worktreeSetupProcessKey(worktreePath) {
1376
- return `${worktreeProcessPrefix(worktreePath)}:setup`;
1377
- }
1378
- function worktreeRunProcessKey(worktreePath, scriptName) {
1379
- return `${worktreeProcessPrefix(worktreePath)}:run:${scriptName}`;
1380
- }
1381
- function worktreeDevProcessKey(worktreePath) {
1382
- return `${worktreeProcessPrefix(worktreePath)}:dev`;
1383
- }
1384
- function isWorktreeRunProcessKey(key, worktreePath) {
1385
- const prefix = worktreeProcessPrefix(worktreePath);
1386
- return key.startsWith(`${prefix}:run:`) || key === `${prefix}:dev`;
1387
- }
1388
- function pickRichestSetupLog(snaps) {
1389
- let best;
1390
- for (const snap of snaps) {
1391
- if (!snap.output && !snap.running && snap.exitCode == null) continue;
1392
- if (!best) {
1393
- best = snap;
1394
- continue;
1395
- }
1396
- if (snap.running && !best.running) {
1397
- best = snap;
1398
- continue;
1399
- }
1400
- if (snap.running === best.running && snap.output.length > best.output.length) {
1401
- best = snap;
1402
- }
1403
- }
1404
- return best;
1405
- }
1406
- function resolveWorktreeSetupLog(shared, legacyChatLogs) {
1407
- if (shared.output || shared.running || shared.exitCode != null) return shared;
1408
- return pickRichestSetupLog(legacyChatLogs);
1409
- }
1410
- function mergeWorktreeActiveRuns(threads) {
1411
- const byName = /* @__PURE__ */ new Map();
1412
- let devPort = null;
1413
- for (const thread of threads) {
1414
- if (thread.devPort != null && devPort == null) devPort = thread.devPort;
1415
- for (const run2 of thread.activeRuns ?? []) {
1416
- if (!byName.has(run2.scriptName)) byName.set(run2.scriptName, run2);
1417
- }
1418
- }
1419
- return { activeRuns: [...byName.values()], devPort };
1420
- }
1421
-
1422
- // src/threads/chat-tabs.ts
1423
- function sameWorktreePath(a, b) {
1424
- return normalizeWorktreePath(a) === normalizeWorktreePath(b);
1425
- }
1426
- function takenTeamSlugsForChatTab(worktreePath) {
1427
- const key = normalizeWorktreePath(worktreePath);
1428
- const taken = /* @__PURE__ */ new Set();
1429
- const dir = worktreeNameFromPath(key);
1430
- if (dir) taken.add(dir.toLowerCase());
1431
- for (const sibling of threadsSharingWorktree(key)) {
1432
- for (const slug of takenSlugsFromThread(sibling)) {
1433
- taken.add(slug);
1434
- }
1435
- }
1436
- return [...taken];
1437
- }
1438
- function requireThread(idOrRef) {
1439
- const thread = findThreadByRef(idOrRef) ?? null;
1440
- if (!thread) throw new Error(`Thread not found: ${idOrRef}`);
1441
- return thread;
1442
- }
1443
- function worktreeBindingFrom(from) {
1444
- return {
1445
- worktreePath: normalizeWorktreePath(from.worktreePath),
1446
- repoPath: from.repoPath,
1447
- branchName: from.branchName,
1448
- sourceRef: from.sourceRef,
1449
- // Legacy pinned-repo orchestration: new tabs become normal worktree agents.
1450
- // Global: keep orchestration so chat tabs stay fleet orchestrators (MCP + Bash).
1451
- sourceType: isGlobalThread(from) ? "orchestration" : from.sourceType === "orchestration" ? "branch" : from.sourceType,
1452
- sourceIsFork: from.sourceIsFork,
1453
- parentThreadId: from.parentThreadId,
1454
- prUrl: from.prUrl,
1455
- prTitle: from.prTitle,
1456
- prState: from.prState,
1457
- prIsDraft: from.prIsDraft,
1458
- prAuthorLogin: from.prAuthorLogin,
1459
- prReviewerLogins: from.prReviewerLogins,
1460
- prReviewDecision: from.prReviewDecision,
1461
- prMergeable: from.prMergeable,
1462
- prMergeStateStatus: from.prMergeStateStatus,
1463
- prIsInMergeQueue: from.prIsInMergeQueue,
1464
- prLabels: from.prLabels,
1465
- prChecksFailed: from.prChecksFailed,
1466
- skipAutoArchiveOnMerge: from.skipAutoArchiveOnMerge,
1467
- stackId: from.stackId,
1468
- stackLayer: from.stackLayer,
1469
- cowboy: from.cowboy
1470
- };
1471
- }
1472
- function threadsSharingWorktree(worktreePath) {
1473
- if (!worktreePath) return [];
1474
- const key = normalizeWorktreePath(worktreePath);
1475
- return listThreads({ includeArchived: true }).filter(
1476
- (t) => Boolean(t.worktreePath) && sameWorktreePath(t.worktreePath, key) && t.status !== "archived"
1477
- ).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
1478
- }
1479
- function formatTranscriptMarkdown(title, messages) {
1480
- const body = formatMessagesAsTranscript(messages, { tools: "full" });
1481
- return [`# Transcript: ${title}`, "", `Forked ${(/* @__PURE__ */ new Date()).toISOString()}`, "", body].join("\n");
1482
- }
1483
- function forkMessageSlice(from, throughIndex) {
1484
- const through = throughIndex === void 0 ? from.messages.length - 1 : Math.min(throughIndex, from.messages.length - 1);
1485
- return through >= 0 ? from.messages.slice(0, through + 1) : [];
1486
- }
1487
- function buildForkTranscriptAttachment(baseTitle, messages) {
1488
- const title = baseTitle || "Chat";
1489
- return {
1490
- id: randomUUID(),
1491
- name: `Transcript of ${title}.md`,
1492
- kind: "transcript",
1493
- content: formatTranscriptMarkdown(title, messages)
1494
- };
1495
- }
1496
- function createChatTab(input) {
1497
- const from = requireThread(input.fromThreadId);
1498
- const binding = worktreeBindingFrom(from);
1499
- const explicitTitle = input.title?.trim();
1500
- const title = explicitTitle || allocateTeamName(takenTeamSlugsForChatTab(binding.worktreePath)).name;
1501
- const nextAgent = input.agent ?? from.agent;
1502
- if (isOrchestratorThread(from) || binding.sourceType === "orchestration") {
1503
- assertOrchestratorCapableAgent(nextAgent);
1504
- }
1505
- const singletonInbox = isSlackCoordinatorThread(from) || isCloudCoordinatorThread(from);
1506
- const thread = createEmptyThread({
1507
- title,
1508
- // Chat-tab nicknames (soccer team or explicit) must stick. Post-turn
1509
- // syncThreadBranchFromGit would otherwise rewrite every sibling to the
1510
- // shared worktree folder name (e.g. fork "Arsenal" → "Monaco").
1511
- userSetTitle: true,
1512
- ...binding,
1513
- // Slack / Brightsy cloud identity stays on the original chat. A + tab
1514
- // that copies `slack:team:user` would steal inbound DMs after you close
1515
- // the connected chat.
1516
- sourceRef: singletonInbox ? title : binding.sourceRef,
1517
- agent: nextAgent,
1518
- model: input.model !== void 0 ? input.model : input.agent && input.agent !== from.agent ? null : from.model,
1519
- effort: input.effort !== void 0 ? input.effort : from.effort,
1520
- fast: input.fast !== void 0 ? Boolean(input.fast) : from.fast,
1521
- planMode: from.planMode,
1522
- autonomy: input.autonomy ?? from.autonomy,
1523
- attachments: input.attachments ?? [],
1524
- status: "idle",
1525
- ...input.accountId !== void 0 ? { accountId: input.accountId } : {},
1526
- ...mergeWorktreeActiveRuns(threadsSharingWorktree(binding.worktreePath))
1527
- });
1528
- writeThread(thread);
1529
- return thread;
1530
- }
1531
- function forkChatTab(input) {
1532
- const from = requireThread(input.threadId);
1533
- const slice = forkMessageSlice(from, input.throughIndex);
1534
- const attachment = buildForkTranscriptAttachment(from.title || "Chat", slice);
1535
- const tab = createChatTab({
1536
- fromThreadId: input.threadId,
1537
- agent: input.agent ?? from.agent,
1538
- model: input.model,
1539
- title: input.title?.trim() || void 0,
1540
- attachments: [attachment]
1541
- });
1542
- if (isOrchestratorThread(from) && tab.parentThreadId !== from.id) {
1543
- const next = { ...tab, parentThreadId: from.id };
1544
- writeThread(next);
1545
- return next;
1546
- }
1547
- return tab;
1548
- }
1549
-
1550
- // src/review/review-write-gate.ts
1551
- var REVIEW_TAB_TITLE = "Review";
1552
- function hasLiveReviewSibling(thread, siblings = []) {
1553
- const selfId = thread.id;
1554
- return siblings.some(
1555
- (t) => t.id !== selfId && t.status !== "archived" && t.title.trim() === REVIEW_TAB_TITLE
1556
- );
1557
- }
1558
- function isReviewWriteGatedThread(thread, siblings) {
1559
- if (thread.sourceType === "pr" || thread.title.trim() === REVIEW_TAB_TITLE) {
1560
- return true;
1561
- }
1562
- return hasLiveReviewSibling(thread, siblings);
1563
- }
1564
- function formatReviewWriteGateDirective() {
1565
- return [
1566
- "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.",
1567
- "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.",
1568
- "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.",
1569
- "Do not request reviewers, change labels, or assign yourself unless they asked \u2014 those also notify the author."
1570
- ].join("\n");
1571
- }
1572
- function formatReviewWriteGateReminder() {
1573
- 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.";
1574
- }
1575
-
1576
- // src/review/review-request-template.ts
1577
- var REVIEW_REQUEST_TEMPLATE = `# Review guidelines:
1578
-
1579
- 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.
1580
-
1581
- ## Required outcome
1582
-
1583
- Start your reply with a **Recommendation** section using exactly one of:
1584
-
1585
- - **Approve** \u2014 ready to merge as-is (or with only trivial nits the author can ignore).
1586
- - **Approve with nits** \u2014 ready to merge; list only optional polish that should not block.
1587
- - **Request changes** \u2014 not ready; blocking issues must be fixed first.
1588
- - **Needs more information** \u2014 cannot judge readiness yet (missing context, incomplete diff, unclear intent).
1589
-
1590
- 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.
1591
-
1592
- People running this review are asking \u201Ccan we ship this?\u201D Treat that as the question you answer first.
1593
-
1594
- ## Confirm before posting
1595
-
1596
- 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.
1597
-
1598
- 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.
1599
-
1600
- 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.
1601
-
1602
- ## Findings
1603
-
1604
- Below are guidelines for determining whether an issue is worth flagging to the original author.
1605
-
1606
- These are not the final word. More specific guidelines elsewhere (developer message, user message, a file, etc.) override these.
1607
-
1608
- Flag something as a bug / blocking finding only when:
1609
-
1610
- 1. It meaningfully impacts the accuracy, performance, security, or maintainability of the code.
1611
- 2. The bug is discrete and actionable (not a vague codebase-wide complaint or a bundle of unrelated issues).
1612
- 3. Fixing it does not demand rigor absent from the rest of the codebase.
1613
- 4. The issue was introduced by this change (do not flag pre-existing bugs unless they are newly exposed by this PR).
1614
- 5. The author would likely fix it if made aware.
1615
- 6. It does not rely on unstated assumptions about the codebase or author intent.
1616
- 7. Speculative breakage is not enough \u2014 identify the other code that is provably affected.
1617
- 8. It is clearly not just an intentional change by the author.
1618
-
1619
- When flagging an issue, include a short accompanying comment:
1620
-
1621
- 1. Clear about why it is a problem.
1622
- 2. Severity must match reality \u2014 do not inflate.
1623
- 3. Brief: at most one paragraph; avoid unnecessary line breaks in prose.
1624
- 4. No code chunks longer than 3 lines; wrap code in inline ticks or a fenced block.
1625
- 5. Call out scenarios / environments / inputs needed to hit the bug when severity depends on them.
1626
- 6. Matter-of-fact tone \u2014 helpful assistant, not accusatory or effusive.
1627
- 7. Skimmable on first read.
1628
- 8. No empty flattery (\u201CGreat job\u2026\u201D, \u201CThanks for\u2026\u201D).
1629
-
1630
- HOW MANY FINDINGS TO RETURN:
1631
-
1632
- 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.
1633
-
1634
- GUIDELINES:
1635
-
1636
- - Ignore trivial style unless it obscures meaning or violates documented standards.
1637
- - One comment per distinct issue (or a short multi-line range if needed).
1638
- - Use \`\`\`suggestion blocks ONLY for concrete replacement code (minimal lines; no commentary inside the block).
1639
- - In every \`\`\`suggestion block, preserve the exact leading whitespace of the replaced lines (spaces vs tabs, number of spaces).
1640
- - Do NOT introduce or remove outer indentation levels unless that is the actual fix.
1641
- - Separate **blocking** findings from **nits**. Only blocking findings should drive Request changes.
1642
-
1643
- 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).
1644
-
1645
- ## Getting the diff
1646
-
1647
- 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.
1648
-
1649
- 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.
1650
-
1651
- ## Fallback: if you don't have access to the Sideboard diff tool
1652
-
1653
- If you don't have access to \`get_diff\`, use the following git commands to get the diff:
1654
-
1655
- \`\`\`bash
1656
- # Get the merge base between this branch and the target
1657
- MERGE_BASE=$(git merge-base origin/main HEAD)
1658
-
1659
- # Get the committed diff against the merge base
1660
- git diff $MERGE_BASE HEAD
1661
-
1662
- # Get any uncommitted changes (staged and unstaged)
1663
- git diff HEAD
1664
- \`\`\`
1665
-
1666
- 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.
1667
-
1668
- No need to mention in your report whether or not you used one of the fallback strategies; it's usually irrelevant.
1669
-
1670
- ## Output format
1671
-
1672
- **1. Recommendation first** (required), then **2. Findings** (may be empty).
1673
-
1674
- Only report ONE finding per unique issue.
1675
-
1676
- <example>
1677
- ## Recommendation
1678
-
1679
- **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.
1680
-
1681
- ## Findings
1682
-
1683
- ### **#1 Empty input causes crash** (blocking)
1684
-
1685
- If the input field is empty when the page loads, the app will crash.
1686
-
1687
- File: src/client/frontends/desktop/ui/Input.tsx
1688
-
1689
- ### **#2 Dead code** (nit)
1690
-
1691
- The getUserData function is now unused. It should be deleted.
1692
-
1693
- File: src/client/frontends/desktop/core/UserData.ts
1694
- </example>
1695
-
1696
- <example>
1697
- ## Recommendation
1698
-
1699
- **Approve** \u2014 Diff is scoped, behavior looks correct, and there are no blocking issues. Safe to merge.
1700
- </example>
1701
-
1702
- ## Growing the rules
1703
-
1704
- 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/\`.
1705
- `;
1706
- var REVIEW_SKILL_PATH = ".claude/skills/review/SKILL.md";
1707
- var REVIEW_SKILL_NAME = "review";
1708
-
1709
- // src/review/request-review.ts
1710
- var REPO_REVIEW_PATH = ".sideboard/review.md";
1711
- var REPO_REVIEW_NAME = "review.md";
1712
- var CONTEXT_REVIEW_PATH = ".context/review.md";
1713
- var REVIEW_REQUEST_PATH = `${ATTACHMENTS_DIR}/Review request.md`;
1714
- var LEGACY_REVIEW_REQUEST_PATH = `${LEGACY_ATTACHMENTS_DIR}/Review request.md`;
1715
- var REVIEW_REQUEST_NAME = "Review request.md";
1716
- var REVIEW_REQUEST_PREFILL = "Review changes in this workspace.";
1717
- var LEGACY_REVIEW_TEMPLATE_MARKERS = [
1718
- "You are acting as a reviewer for a proposed code change made by another engineer.",
1719
- "HOW MANY FINDINGS TO RETURN:"
1720
- ];
1721
- function shouldRefreshReviewRequestTemplate(content) {
1722
- const trimmed = content.trim();
1723
- if (!trimmed) return true;
1724
- if (/##\s*Recommendation|ready to merge|Approve with nits|Request changes/i.test(trimmed)) {
1725
- return false;
1726
- }
1727
- return LEGACY_REVIEW_TEMPLATE_MARKERS.every((m) => trimmed.includes(m));
1728
- }
1729
- function readTextIfPresent(abs) {
1730
- if (!existsSync2(abs)) return null;
1731
- try {
1732
- const content = readFileSync(abs, "utf8");
1733
- return content.trim() ? content : null;
1734
- } catch {
1735
- return null;
1736
- }
1737
- }
1738
- function readLocalGuidelines(worktreePath) {
1739
- const localAbs = join3(worktreePath, REVIEW_REQUEST_PATH);
1740
- const localContent = readTextIfPresent(localAbs);
1741
- if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
1742
- return { path: REVIEW_REQUEST_PATH, content: localContent };
1743
- }
1744
- const legacyAbs = join3(worktreePath, LEGACY_REVIEW_REQUEST_PATH);
1745
- const legacyContent = readTextIfPresent(legacyAbs);
1746
- if (legacyContent && !shouldRefreshReviewRequestTemplate(legacyContent)) {
1747
- return { path: LEGACY_REVIEW_REQUEST_PATH, content: legacyContent };
1748
- }
1749
- return null;
1750
- }
1751
- function skillGuidelines(content, source) {
1752
- return {
1753
- path: REVIEW_SKILL_PATH,
1754
- name: REVIEW_SKILL_NAME,
1755
- content,
1756
- source
1757
- };
1758
- }
1759
- function contextGuidelines(content, source) {
1760
- return {
1761
- path: CONTEXT_REVIEW_PATH,
1762
- name: REPO_REVIEW_NAME,
1763
- content,
1764
- source
1765
- };
1766
- }
1767
- function writeContextGuidelines(worktreePath, content) {
1768
- const abs = join3(worktreePath, CONTEXT_REVIEW_PATH);
1769
- mkdirSync2(dirname(abs), { recursive: true });
1770
- const body = content.endsWith("\n") ? content : `${content}
1771
- `;
1772
- writeFileSync(abs, body, "utf8");
1773
- return body;
1774
- }
1775
- function readSideboardReview(worktreePath, repoPath) {
1776
- const fromWorktree = readTextIfPresent(join3(worktreePath, REPO_REVIEW_PATH));
1777
- if (fromWorktree) return fromWorktree;
1778
- const repo = repoPath?.replace(/\/+$/, "");
1779
- const wt = worktreePath.replace(/\/+$/, "");
1780
- if (!repo || repo === wt) return null;
1781
- return readTextIfPresent(join3(repo, REPO_REVIEW_PATH));
1782
- }
1783
- function ensureReviewGuidelinesFile(worktreePath, repoPath) {
1784
- const skillContent = readTextIfPresent(join3(worktreePath, REVIEW_SKILL_PATH));
1785
- if (skillContent) {
1786
- return { ...skillGuidelines(skillContent, "skill"), wrote: false };
1787
- }
1788
- const contextContent = readTextIfPresent(join3(worktreePath, CONTEXT_REVIEW_PATH));
1789
- if (contextContent && !shouldRefreshReviewRequestTemplate(contextContent)) {
1790
- return { ...contextGuidelines(contextContent, "local"), wrote: false };
1791
- }
1792
- const fromSideboard = readSideboardReview(worktreePath, repoPath);
1793
- if (fromSideboard && !shouldRefreshReviewRequestTemplate(fromSideboard)) {
1794
- const body2 = writeContextGuidelines(worktreePath, fromSideboard);
1795
- return { ...contextGuidelines(body2, "repo"), wrote: true };
1796
- }
1797
- const local = readLocalGuidelines(worktreePath);
1798
- const body = writeContextGuidelines(worktreePath, local?.content ?? REVIEW_REQUEST_TEMPLATE);
1799
- return {
1800
- ...contextGuidelines(body, local ? "local" : "stock"),
1801
- wrote: true
1802
- };
1803
- }
1804
- function resolveReviewGuidelines(worktreePath, repoPath) {
1805
- return ensureReviewGuidelinesFile(worktreePath, repoPath);
1806
- }
1807
- function buildReviewRequestAttachment(content, opts) {
1808
- const path = opts?.path ?? REVIEW_SKILL_PATH;
1809
- 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);
1810
- return {
1811
- id: randomUUID2(),
1812
- name,
1813
- kind: "file",
1814
- path,
1815
- content
1816
- };
1817
- }
1818
- function canReuseIdleWorktreeAgentForReview(from, siblings) {
1819
- const live = siblings.filter((t) => t.status !== "archived");
1820
- if (live.length !== 1) return false;
1821
- const only = live[0];
1822
- if (only.id !== from.id) return false;
1823
- if (from.status !== "idle") return false;
1824
- if ((from.queue?.length ?? 0) > 0) return false;
1825
- return (from.messages?.length ?? 0) === 0 && (only.messages?.length ?? 0) === 0;
1826
- }
1827
- function reviewGuidelinesAttachment(from) {
1828
- const guidelines = resolveReviewGuidelines(from.worktreePath, from.repoPath);
1829
- return buildReviewRequestAttachment(guidelines.content, {
1830
- path: guidelines.path,
1831
- name: guidelines.name
1832
- });
1833
- }
1834
- async function requestReview(threadRef, send) {
1835
- const from = findThreadByRef(threadRef);
1836
- if (!from) throw new Error(`Thread not found: ${threadRef}`);
1837
- if (isOrchestratorThread(from)) {
1838
- throw new Error(
1839
- "request_review targets a worktree agent thread (not the orchestrator). Pass a child/worktree thread ref."
1840
- );
1841
- }
1842
- if (from.status === "archived") {
1843
- throw new Error(`Thread is archived: ${from.id}`);
1844
- }
1845
- const attachment = reviewGuidelinesAttachment(from);
1846
- const siblings = threadsSharingWorktree(from.worktreePath);
1847
- if (canReuseIdleWorktreeAgentForReview(from, siblings)) {
1848
- const reused = updateThread(from.id, {
1849
- title: REVIEW_TAB_TITLE,
1850
- userSetTitle: true,
1851
- attachments: [...from.attachments ?? [], attachment]
1852
- });
1853
- const started2 = await send(reused.id, REVIEW_REQUEST_PREFILL);
1854
- return { tab: started2, from: reused };
1855
- }
1856
- const tab = createChatTab({
1857
- fromThreadId: from.id,
1858
- title: REVIEW_TAB_TITLE,
1859
- attachments: [attachment]
1860
- });
1861
- const started = await send(tab.id, REVIEW_REQUEST_PREFILL);
1862
- return { tab: started, from };
1863
- }
1864
-
1865
- // src/hook/worktree-run-ports.ts
1866
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1867
- import { dirname as dirname2, join as join4 } from "path";
1868
- var RUN_PORTS_REL = ".context/.sideboard/run-ports.json";
1869
- function worktreeRunPortsPath(worktreePath) {
1870
- return join4(worktreePath, RUN_PORTS_REL);
1871
- }
1872
- function isPort(value) {
1873
- return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= 65535;
1874
- }
1875
- function parseFile(raw) {
1876
- try {
1877
- const parsed = JSON.parse(raw);
1878
- if (!parsed || typeof parsed !== "object") return {};
1879
- const scripts = parsed.scripts;
1880
- if (!scripts || typeof scripts !== "object") return {};
1881
- const next = {};
1882
- for (const [name, ports] of Object.entries(scripts)) {
1883
- if (!name || !Array.isArray(ports)) continue;
1884
- const cleaned = ports.filter(isPort);
1885
- if (cleaned.length) next[name] = cleaned;
1886
- }
1887
- return { scripts: next };
1888
- } catch {
1889
- return {};
1890
- }
1891
- }
1892
- function loadWorktreeRunPorts(worktreePath, scriptName) {
1893
- if (!worktreePath.trim() || !scriptName.trim()) return null;
1894
- try {
1895
- const raw = readFileSync2(worktreeRunPortsPath(worktreePath), "utf8");
1896
- const ports = parseFile(raw).scripts?.[scriptName];
1897
- return ports?.length ? ports : null;
1898
- } catch {
1899
- return null;
1900
- }
1901
- }
1902
- function saveWorktreeRunPorts(worktreePath, scriptName, ports) {
1903
- if (!worktreePath.trim() || !scriptName.trim()) return;
1904
- const cleaned = ports.filter(isPort);
1905
- if (!cleaned.length) return;
1906
- const path = worktreeRunPortsPath(worktreePath);
1907
- let current = {};
1908
- try {
1909
- current = parseFile(readFileSync2(path, "utf8"));
1910
- } catch {
1911
- current = {};
1912
- }
1913
- const scripts = { ...current.scripts ?? {} };
1914
- scripts[scriptName] = cleaned;
1915
- mkdirSync3(dirname2(path), { recursive: true });
1916
- writeFileSync2(path, `${JSON.stringify({ scripts }, null, 2)}
1917
- `, "utf8");
1918
- }
1919
-
1920
- // src/mcp/wait-for-job.ts
1921
- import {
1922
- appendFileSync as appendFileSync2,
1923
- existsSync as existsSync3,
1924
- mkdirSync as mkdirSync4,
1925
- readdirSync,
1926
- readFileSync as readFileSync3,
1927
- writeFileSync as writeFileSync3
1928
- } from "fs";
1929
- import { dirname as dirname3, join as join5 } from "path";
1930
- var MAX_JOB_CONTINUES = 8;
1931
- var MAX_JOB_DELTA_LINES = 80;
1932
- var MAX_JOB_DELTA_CHARS = 4e3;
1933
- var DEFERRED_DONE_MAX_CHARS = 480;
1934
- var GH_WATCH_REFRESH = "Refreshing run status every 3 seconds.";
1935
- 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.";
1936
- 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.";
1937
- var JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
1938
- function mcpWaitForJobTimeoutMs(requested) {
1939
- return mcpWaitForTurnTimeoutMs(requested);
1940
- }
1941
- function sanitizeDetachedJobId(id) {
1942
- const s = id.trim();
1943
- if (!JOB_ID_RE.test(s)) {
1944
- throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
1945
- }
1946
- return s;
1947
- }
1948
- function jobAlive(pid) {
1949
- if (!Number.isInteger(pid) || pid <= 0) return false;
1950
- try {
1951
- process.kill(pid, 0);
1952
- return true;
1953
- } catch {
1954
- return false;
1955
- }
1956
- }
1957
- function processGroupAlive(pgid) {
1958
- if (process.platform === "win32") return false;
1959
- if (!Number.isInteger(pgid) || pgid <= 1) return false;
1960
- try {
1961
- process.kill(-pgid, 0);
1962
- return true;
1963
- } catch (err) {
1964
- return Boolean(err && typeof err === "object" && "code" in err && err.code === "EPERM");
1965
- }
1966
- }
1967
- function jobTreeAlive(pid) {
1968
- if (pid == null) return false;
1969
- return jobAlive(pid) || processGroupAlive(pid);
1970
- }
1971
- function readIntFile(file) {
1972
- if (!existsSync3(file)) return null;
1973
- const n = Number.parseInt(readFileSync3(file, "utf8").trim(), 10);
1974
- return Number.isInteger(n) ? n : null;
1975
- }
1976
- function jobDir(root, id, legacy = false) {
1977
- return join5(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
1978
- }
1979
- function resolveJobDir(root, id) {
1980
- const modern = jobDir(root, id, false);
1981
- if (existsSync3(modern)) return modern;
1982
- const legacy = jobDir(root, id, true);
1983
- if (existsSync3(legacy)) return legacy;
1984
- return modern;
1985
- }
1986
- function listJobIdsIn(root, rel) {
1987
- const dir = join5(root, rel);
1988
- if (!existsSync3(dir)) return [];
1989
- return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
1990
- }
1991
- function listRunningDetachedJobs(worktreePath) {
1992
- const root = worktreePath.trim();
1993
- if (!root) return [];
1994
- const ids = /* @__PURE__ */ new Set([
1995
- ...listJobIdsIn(root, DETACHED_JOBS_DIR),
1996
- ...listJobIdsIn(root, LEGACY_DETACHED_JOBS_DIR)
1997
- ]);
1998
- const running = [];
1999
- for (const id of ids) {
2000
- const dir = resolveJobDir(root, id);
2001
- const pid = readIntFile(join5(dir, "pid"));
2002
- if (jobTreeAlive(pid)) running.push(id);
2003
- }
2004
- return running.sort();
2005
- }
2006
- function looksLikeDeferredDonePromise(text) {
2007
- const t = (text ?? "").trim();
2008
- if (!t || t.length > DEFERRED_DONE_MAX_CHARS) return false;
2009
- const unquoted = t.replace(/`[^`]*`/g, " ").replace(/["“”][^"“”]*["“”]/g, " ").replace(/\s+/g, " ").trim();
2010
- if (!unquoted) return false;
2011
- 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);
2012
- }
2013
- function isJobContinuePrompt(text) {
2014
- if (typeof text !== "string") return false;
2015
- const t = text.trim();
2016
- return t.startsWith("Detached job still running:") || t.startsWith("You ended the turn after promising to report later");
2017
- }
2018
- function collapseGhRunWatchLog(text) {
2019
- if (!text.includes(GH_WATCH_REFRESH)) return text;
2020
- const frames = text.split(/(?=Refreshing run status every 3 seconds\.)/);
2021
- const watchFrames = frames.filter((f) => f.includes(GH_WATCH_REFRESH));
2022
- if (watchFrames.length <= 1) return text;
2023
- const prefix = frames[0]?.includes(GH_WATCH_REFRESH) ? "" : frames[0] ?? "";
2024
- const last = watchFrames[watchFrames.length - 1] ?? "";
2025
- const omitted = watchFrames.length - 1;
2026
- const note = `(${omitted} identical gh run watch refresh${omitted === 1 ? "" : "es"} omitted)
2027
- `;
2028
- return [prefix.trimEnd(), note, last.trimStart()].filter(Boolean).join("\n");
2029
- }
2030
- function capJobLogDelta(text) {
2031
- const collapsed = collapseGhRunWatchLog(text);
2032
- const lines = collapsed.split("\n");
2033
- let out = lines.length > MAX_JOB_DELTA_LINES ? `\u2026(${lines.length - MAX_JOB_DELTA_LINES} earlier lines omitted)
2034
- ${lines.slice(-MAX_JOB_DELTA_LINES).join("\n")}` : collapsed;
2035
- if (out.length > MAX_JOB_DELTA_CHARS) {
2036
- out = `\u2026(truncated ${out.length} chars)
2037
- ${out.slice(-MAX_JOB_DELTA_CHARS)}`;
2038
- }
2039
- return out;
2040
- }
2041
- function formatJobStillRunningContinuePrompt(jobIds) {
2042
- const ids = jobIds.join(", ");
2043
- return [
2044
- `Detached job still running: ${ids}.`,
2045
- "Do not end this turn. Loop wait_for_job (same id) until stillRunning is false. The log pane updates from each wait result.",
2046
- "If the job is hanging or doing the wrong thing, call stop_job (same id) instead of looping forever.",
2047
- "Then report the result. Do not tell the user you will let them know later."
2048
- ].join(" ");
2049
- }
2050
- function formatDeferredDoneContinuePrompt() {
2051
- return [
2052
- "You ended the turn after promising to report later, but no detached job is running.",
2053
- "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).",
2054
- "Do not say you will let the user know later."
2055
- ].join(" ");
2056
- }
2057
- function turnWatchedDetachedJob(parts) {
2058
- return parts.some((p) => {
2059
- if (p.type !== "tool") return false;
2060
- if (/wait_for_job$/i.test(p.name ?? "") || /stop_job$/i.test(p.name ?? "")) return true;
2061
- const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
2062
- return /detached-job\.(?:js|cjs)\b/i.test(blob);
2063
- });
2064
- }
2065
- function planJobContinue(opts) {
2066
- if (opts.isOrchestrator) return { action: "none" };
2067
- if (opts.agent === "brightsy") return { action: "none" };
2068
- if (opts.queueLength > 0) return { action: "none" };
2069
- if (opts.continueCount >= MAX_JOB_CONTINUES) return { action: "none" };
2070
- const farewell = looksLikeDeferredDonePromise(opts.chatText);
2071
- if (opts.runningJobIds.length > 0 && (farewell || opts.watchedJob)) {
2072
- return {
2073
- action: "wait",
2074
- jobIds: opts.runningJobIds,
2075
- prompt: formatJobStillRunningContinuePrompt(opts.runningJobIds)
2076
- };
2077
- }
2078
- if (looksLikeDeferredDonePromise(opts.chatText) && !opts.alreadyNudged) {
2079
- return { action: "nudge", prompt: formatDeferredDoneContinuePrompt() };
2080
- }
2081
- return { action: "none" };
2082
- }
2083
- function tailProgress(logFile, maxLines = 12) {
2084
- if (!existsSync3(logFile)) return "(no log yet)";
2085
- const lines = readFileSync3(logFile, "utf8").split("\n");
2086
- return lines.slice(-maxLines).join("\n");
2087
- }
2088
- function lastProgressLine(progress) {
2089
- const last = progress.split("\n").map((l) => l.trim()).filter((l) => l && l !== "(no log yet)").at(-1);
2090
- return last ? last.slice(0, 72) : void 0;
2091
- }
2092
- function readLogLines(file) {
2093
- if (!existsSync3(file)) return [];
2094
- const lines = readFileSync3(file, "utf8").split("\n");
2095
- if (lines.length && lines[lines.length - 1] === "") lines.pop();
2096
- return lines;
2097
- }
2098
- function takeDelta(logFile, cursorFile) {
2099
- const lines = readLogLines(logFile);
2100
- const cursor = readIntFile(cursorFile) ?? 0;
2101
- const start = Math.min(Math.max(0, cursor), lines.length);
2102
- return { delta: capJobLogDelta(lines.slice(start).join("\n")), nextCursor: lines.length };
2103
- }
2104
- function snapshotJob(dir) {
2105
- const pid = readIntFile(join5(dir, "pid"));
2106
- const running = jobTreeAlive(pid);
2107
- return {
2108
- pid,
2109
- running,
2110
- exitCode: readIntFile(join5(dir, "exit")),
2111
- log: join5(dir, "log"),
2112
- cursor: join5(dir, "present.cursor"),
2113
- progress: tailProgress(join5(dir, "log"))
2114
- };
2115
- }
2116
- function toResult(id, snap, extra) {
2117
- const failed = extra?.failed === true || !snap.running && snap.exitCode != null && snap.exitCode !== 0;
2118
- const ok = !snap.running && snap.exitCode === 0;
2119
- const stillRunning = snap.running && !ok;
2120
- const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
2121
- try {
2122
- mkdirSync4(dirname3(snap.cursor), { recursive: true });
2123
- writeFileSync3(snap.cursor, `${nextCursor}
2124
- `);
2125
- } catch {
2126
- }
2127
- const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
2128
- const progress = extra?.progress ?? snap.progress;
2129
- return {
2130
- stillRunning,
2131
- ok,
2132
- failed: Boolean(failed && !stillRunning && !ok),
2133
- status,
2134
- id,
2135
- pid: snap.pid,
2136
- exitCode: snap.exitCode ?? void 0,
2137
- phase: lastProgressLine(progress),
2138
- delta,
2139
- progress,
2140
- hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
2141
- };
2142
- }
2143
- async function sleepMs(ms) {
2144
- await new Promise((resolve2) => setTimeout(resolve2, ms));
2145
- }
2146
- async function waitForDetachedJob(cwd, id, opts) {
2147
- const jobId = sanitizeDetachedJobId(id);
2148
- const root = cwd.trim() || process.cwd();
2149
- const dir = resolveJobDir(root, jobId);
2150
- const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
2151
- const sleep = opts?.sleep ?? sleepMs;
2152
- if (!existsSync3(dir)) {
2153
- return {
2154
- stillRunning: false,
2155
- ok: false,
2156
- failed: true,
2157
- status: "failed",
2158
- id: jobId,
2159
- delta: "",
2160
- progress: "No detached job. Start one first.",
2161
- hint: "Start with detached-job.cjs start <id> -- <command>, then call wait_for_job again."
2162
- };
2163
- }
2164
- const deadline = Date.now() + timeoutMs;
2165
- let snap = snapshotJob(dir);
2166
- if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
2167
- if (!snap.running && snap.pid == null && snap.progress === "(no log yet)") {
2168
- return toResult(jobId, snap, {
2169
- failed: true,
2170
- progress: "No detached job. Start one first."
2171
- });
2172
- }
2173
- while (Date.now() < deadline) {
2174
- snap = snapshotJob(dir);
2175
- if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
2176
- if (!snap.running) return toResult(jobId, snap, { failed: true });
2177
- await sleep(Math.min(2e3, Math.max(50, deadline - Date.now())));
2178
- }
2179
- snap = snapshotJob(dir);
2180
- return toResult(jobId, snap);
2181
- }
2182
- function killProcessTree(pid, signal) {
2183
- if (!Number.isInteger(pid) || pid <= 0) return;
2184
- if (process.platform !== "win32") {
2185
- try {
2186
- process.kill(-pid, signal);
2187
- return;
2188
- } catch {
2189
- }
2190
- }
2191
- try {
2192
- process.kill(pid, signal);
2193
- } catch {
2194
- }
2195
- }
2196
- function appendJobLog(logFile, line) {
2197
- try {
2198
- mkdirSync4(dirname3(logFile), { recursive: true });
2199
- const body = line.endsWith("\n") ? line : `${line}
2200
- `;
2201
- appendFileSync2(logFile, body);
2202
- } catch {
2203
- }
2204
- }
2205
- async function stopDetachedJob(cwd, id, opts) {
2206
- const jobId = sanitizeDetachedJobId(id);
2207
- const root = cwd.trim() || process.cwd();
2208
- const dir = resolveJobDir(root, jobId);
2209
- const sleep = opts?.sleep ?? sleepMs;
2210
- const why = opts?.reason?.trim() ?? "";
2211
- if (!existsSync3(dir)) {
2212
- return {
2213
- stillRunning: false,
2214
- ok: false,
2215
- failed: true,
2216
- status: "failed",
2217
- id: jobId,
2218
- delta: "",
2219
- progress: "No detached job. Start one first.",
2220
- hint: "Start with detached-job.cjs start <id> -- <command>, then wait_for_job or stop_job.",
2221
- stopped: false,
2222
- reason: "not-found"
2223
- };
2224
- }
2225
- let snap = snapshotJob(dir);
2226
- if (!snap.running) {
2227
- const result2 = toResult(jobId, snap);
2228
- return {
2229
- ...result2,
2230
- stopped: false,
2231
- reason: "not-running",
2232
- hint: "That job is already finished. Read the log; do not wait again unless you start a new command."
2233
- };
2234
- }
2235
- appendJobLog(snap.log, `$ stop${why ? `: ${why}` : ""}`);
2236
- const targetPid = snap.pid;
2237
- if (targetPid != null) killProcessTree(targetPid, "SIGTERM");
2238
- const graceMs = Number.isFinite(opts?.graceMs) ? Math.max(50, opts.graceMs) : 2e3;
2239
- const deadline = Date.now() + graceMs;
2240
- while (Date.now() < deadline && jobTreeAlive(targetPid)) {
2241
- await sleep(Math.min(100, Math.max(20, deadline - Date.now())));
2242
- }
2243
- if (targetPid != null) {
2244
- killProcessTree(targetPid, "SIGKILL");
2245
- await sleep(100);
2246
- }
2247
- const exitFile = join5(dir, "exit");
2248
- if (!existsSync3(exitFile)) {
2249
- try {
2250
- writeFileSync3(exitFile, "1\n");
2251
- } catch {
2252
- }
2253
- }
2254
- snap = snapshotJob(dir);
2255
- const stillRunning = jobTreeAlive(targetPid) || snap.running;
2256
- const result = toResult(jobId, snap, { failed: true });
2257
- const stopped = !stillRunning;
2258
- return {
2259
- ...result,
2260
- stillRunning,
2261
- ok: false,
2262
- failed: true,
2263
- status: stillRunning ? "running" : "failed",
2264
- stopped,
2265
- reason: stopped ? "stopped" : "still-running",
2266
- stopReason: why || void 0,
2267
- hint: stopped ? MCP_STOP_JOB_HINT : MCP_WAIT_JOB_STILL_RUNNING_HINT
2268
- };
2269
- }
2270
-
2271
- // src/hook/conductor.ts
2272
- var PORT_RANGE_SIZE = 10;
2273
- var RECLAIM_RETRY_DELAY_MS = 40;
2274
- var RECLAIM_TRIES = 8;
2275
- function matchSimpleGlob(pattern, name) {
2276
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
2277
- return new RegExp(`^${escaped}$`).test(name);
2278
- }
2279
- function readWorktreeInclude(repoPath) {
2280
- const path = join6(repoPath, ".worktreeinclude");
2281
- if (!existsSync4(path)) return [];
2282
- return readFileSync4(path, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
2283
- }
2284
- function resolveFilesToCopy(repoPath) {
2285
- const fromInclude = readWorktreeInclude(repoPath);
2286
- if (fromInclude.length) return fromInclude;
2287
- const settings = loadRepoSettings(repoPath);
2288
- if (settings?.filesToCopy?.length) return settings.filesToCopy;
2289
- if (settings?.fileIncludeGlobs?.length) {
2290
- const matched = [];
2291
- try {
2292
- for (const entry of readdirSync2(repoPath, { withFileTypes: true })) {
2293
- if (!entry.isFile()) continue;
2294
- for (const glob of settings.fileIncludeGlobs) {
2295
- if (matchSimpleGlob(glob, entry.name) || matchSimpleGlob(basename(glob), entry.name)) {
2296
- matched.push(entry.name);
2297
- break;
2298
- }
2299
- }
2300
- }
2301
- } catch {
2302
- }
2303
- if (matched.length) return [...new Set(matched)];
2304
- }
2305
- const defaults = [];
2306
- try {
2307
- for (const entry of readdirSync2(repoPath, { withFileTypes: true })) {
2308
- if (entry.isFile() && entry.name.startsWith(".env")) {
2309
- defaults.push(entry.name);
2310
- }
2311
- }
2312
- } catch {
2313
- }
2314
- if (defaults.length) return defaults;
2315
- return [".env.local", ".env"];
2316
- }
2317
- function copyConfiguredFiles(repoPath, worktreePath) {
2318
- const patterns = resolveFilesToCopy(repoPath);
2319
- const copied = [];
2320
- for (const rel of patterns) {
2321
- const src = join6(repoPath, rel);
2322
- if (!existsSync4(src)) continue;
2323
- const dest = join6(worktreePath, rel);
2324
- mkdirSync5(dirname4(dest), { recursive: true });
2325
- copyFileSync(src, dest);
2326
- copied.push(rel);
2327
- }
2328
- return copied;
2329
- }
2330
- var cachedLoginEnv = null;
2331
- async function captureLoginEnv() {
2332
- if (cachedLoginEnv) return { ...cachedLoginEnv };
2333
- const shell = process.env.SHELL?.trim() || (process.platform === "darwin" ? "/bin/zsh" : "/bin/bash");
2334
- try {
2335
- const result = await execa2(shell, ["-l", "-c", "env -0"], {
2336
- reject: false,
2337
- timeout: 5e3
2338
- });
2339
- if (result.exitCode === 0 && result.stdout) {
2340
- const env = { ...process.env };
2341
- for (const entry of result.stdout.split("\0")) {
2342
- if (!entry) continue;
2343
- const eq = entry.indexOf("=");
2344
- if (eq <= 0) continue;
2345
- const key = entry.slice(0, eq);
2346
- const value = entry.slice(eq + 1);
2347
- if (key) env[key] = value;
2348
- }
2349
- cachedLoginEnv = env;
2350
- return { ...env };
2351
- }
2352
- } catch {
2353
- }
2354
- cachedLoginEnv = { ...process.env };
2355
- return { ...cachedLoginEnv };
2356
- }
2357
- function buildWorkspaceScriptEnv(opts, baseEnv) {
2358
- const env = stripNestedElectronEnv({
2359
- ...baseEnv ?? process.env
2360
- });
2361
- const name = opts.workspaceName ?? basename(opts.worktreePath);
2362
- const ports = opts.ports ?? [];
2363
- const primary = ports[0];
2364
- env.SIDEBOARD_WORKSPACE_NAME = name;
2365
- env.SIDEBOARD_WORKSPACE_PATH = opts.worktreePath;
2366
- env.SIDEBOARD_ROOT_PATH = opts.repoPath;
2367
- env.SIDEBOARD_IS_LOCAL = "1";
2368
- if (opts.defaultBranch) env.SIDEBOARD_DEFAULT_BRANCH = opts.defaultBranch;
2369
- env.CONDUCTOR_WORKSPACE_NAME = name;
2370
- env.CONDUCTOR_WORKSPACE_PATH = opts.worktreePath;
2371
- env.CONDUCTOR_ROOT_PATH = opts.repoPath;
2372
- env.CONDUCTOR_IS_LOCAL = "1";
2373
- if (opts.defaultBranch) env.CONDUCTOR_DEFAULT_BRANCH = opts.defaultBranch;
2374
- if (primary != null) {
2375
- env.SIDEBOARD_PORT = String(primary);
2376
- env.CONDUCTOR_PORT = String(primary);
2377
- env.PORT = String(primary);
2378
- for (let i = 1; i < ports.length; i++) {
2379
- const p = ports[i];
2380
- env[`SIDEBOARD_PORT_${i}`] = String(p);
2381
- env[`CONDUCTOR_PORT_${i}`] = String(p);
2382
- }
2383
- }
2384
- applyWorktreePkgCacheEnv(env, opts.worktreePath);
2385
- return env;
2386
- }
2387
- function pipeLines(stream, onLine) {
2388
- if (!stream || !onLine) return;
2389
- const rl = createInterface2({ input: stream });
2390
- rl.on("line", onLine);
2391
- }
2392
- function killableListenerPids(raw, selfPid = process.pid) {
2393
- const pids = [];
2394
- for (const token of raw.trim().split(/\s+/)) {
2395
- const pid = Number(token);
2396
- if (!Number.isFinite(pid) || pid <= 0 || pid === selfPid) continue;
2397
- pids.push(pid);
2398
- }
2399
- return pids;
2400
- }
2401
- function listUnixListenerPids(port, selfPid) {
2402
- try {
2403
- const raw = execFileSync("lsof", [`-tiTCP:${port}`, "-sTCP:LISTEN"], {
2404
- encoding: "utf8",
2405
- stdio: ["ignore", "pipe", "ignore"],
2406
- timeout: 2e3
2407
- });
2408
- return killableListenerPids(raw, selfPid);
2409
- } catch {
2410
- return [];
2411
- }
2412
- }
2413
- function listenerCwd(pid) {
2414
- if (!Number.isFinite(pid) || pid <= 0) return null;
2415
- try {
2416
- const raw = execFileSync("lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], {
2417
- encoding: "utf8",
2418
- stdio: ["ignore", "pipe", "ignore"],
2419
- timeout: 2e3
2420
- });
2421
- for (const line of raw.split("\n")) {
2422
- if (line.startsWith("n") && line.length > 1) return line.slice(1);
2423
- }
2424
- } catch {
2425
- }
2426
- return null;
2427
- }
2428
- function realpathOrResolve(path) {
2429
- try {
2430
- return realpathSync(path);
2431
- } catch {
2432
- return resolve(path);
2433
- }
2434
- }
2435
- function isOtherWorktreeListenerCwd(cwd, worktreePath) {
2436
- const wt = realpathOrResolve(worktreePath);
2437
- const c = realpathOrResolve(cwd);
2438
- if (c === wt || c.startsWith(wt + sep)) return false;
2439
- const parent = dirname4(wt);
2440
- const thisName = basename(wt);
2441
- if (!thisName || c === parent) return false;
2442
- if (!c.startsWith(parent + sep)) return false;
2443
- const first = c.slice(parent.length + 1).split(sep)[0];
2444
- return Boolean(first) && first !== thisName;
2445
- }
2446
- function signalPid(pid, signal) {
2447
- try {
2448
- process.kill(pid, signal);
2449
- } catch {
2450
- }
2451
- }
2452
- function isThisWorktreeListenerCwd(cwd, worktreePath) {
2453
- const wt = realpathOrResolve(worktreePath);
2454
- const c = realpathOrResolve(cwd);
2455
- return c === wt || c.startsWith(wt + sep);
2456
- }
2457
- function killDetachedRunTree(pid, worktreePath) {
2458
- if (!Number.isFinite(pid) || pid <= 1 || pid === process.pid) return;
2459
- const cwd = listenerCwd(pid);
2460
- if (cwd) {
2461
- if (!isThisWorktreeListenerCwd(cwd, worktreePath)) return;
2462
- } else if (!processGroupAlive(pid)) {
2463
- return;
2464
- }
2465
- try {
2466
- process.kill(-pid, "SIGTERM");
2467
- } catch {
2468
- signalPid(pid, "SIGTERM");
2469
- }
2470
- }
2471
- function killListenersOnPorts(ports) {
2472
- const selfPid = process.pid;
2473
- for (const port of ports) {
2474
- if (!Number.isFinite(port) || port <= 0) continue;
2475
- try {
2476
- if (process.platform === "win32") {
2477
- execFileSync(
2478
- "powershell",
2479
- [
2480
- "-NoProfile",
2481
- "-Command",
2482
- `Get-NetTCPConnection -LocalPort ${port} -ErrorAction SilentlyContinue | Where-Object { $_.OwningProcess -ne ${selfPid} } | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }`
2483
- ],
2484
- { stdio: "ignore", timeout: 3e3 }
2485
- );
2486
- continue;
2487
- }
2488
- for (const pid of listUnixListenerPids(port, selfPid)) {
2489
- signalPid(pid, "SIGTERM");
2490
- }
2491
- } catch {
2492
- }
2493
- }
2494
- }
2495
- function killStaleWorktreeListeners(ports, worktreePath, signal = "SIGTERM") {
2496
- if (!worktreePath.trim()) {
2497
- killListenersOnPorts(ports);
2498
- return;
2499
- }
2500
- if (process.platform === "win32") {
2501
- killListenersOnPorts(ports);
2502
- return;
2503
- }
2504
- const selfPid = process.pid;
2505
- for (const port of ports) {
2506
- if (!Number.isFinite(port) || port <= 0) continue;
2507
- for (const pid of listUnixListenerPids(port, selfPid)) {
2508
- const cwd = listenerCwd(pid);
2509
- if (cwd && isOtherWorktreeListenerCwd(cwd, worktreePath)) continue;
2510
- signalPid(pid, signal);
2511
- }
2512
- }
2513
- }
2514
- function killScriptTree(child, ports = []) {
2515
- const pid = child.pid;
2516
- if (pid) {
2517
- try {
2518
- if (process.platform === "win32") {
2519
- void execa2("taskkill", ["/pid", String(pid), "/T", "/F"], { reject: false });
2520
- } else {
2521
- process.kill(-pid, "SIGTERM");
2522
- setTimeout(() => {
2523
- try {
2524
- process.kill(-pid, "SIGKILL");
2525
- } catch {
2526
- }
2527
- }, 2500).unref?.();
2528
- }
2529
- } catch {
2530
- try {
2531
- child.kill("SIGTERM");
2532
- } catch {
2533
- }
658
+ function agentGitPrompt(action, opts) {
659
+ switch (action) {
660
+ case "commit-push":
661
+ return "Commit and push.";
662
+ case "create-draft":
663
+ return "Commit, push, and open a draft PR.";
664
+ case "create-web":
665
+ return "Commit, push, and open a PR in the browser.";
666
+ case "resolve-conflicts": {
667
+ const base = opts?.prBase?.trim().replace(/^refs\/heads\//, "");
668
+ const named = base ? ` (${base})` : "";
669
+ return `Merge the remote branch${named} into your branch and resolve conflicts. Then, commit and push your changes.`;
2534
670
  }
671
+ case "ready-for-review":
672
+ return "Ready for review.";
673
+ case "merge":
674
+ return "Merge PR.";
2535
675
  }
2536
- killListenersOnPorts(ports);
2537
676
  }
2538
- async function spawnWorkspaceScript(command, opts) {
2539
- const loginEnv = await captureLoginEnv();
2540
- const env = buildWorkspaceScriptEnv(
2541
- {
2542
- worktreePath: opts.worktreePath,
2543
- repoPath: opts.repoPath,
2544
- workspaceName: opts.workspaceName,
2545
- defaultBranch: opts.defaultBranch,
2546
- ports: opts.ports
2547
- },
2548
- loginEnv
2549
- );
2550
- try {
2551
- mergeAgentGitAuthEnv(
2552
- env,
2553
- await resolveAgentGitAuthEnv(env, { cwd: opts.worktreePath })
2554
- );
2555
- } catch {
677
+ function resolveSidebarGitPrompt(action, opts) {
678
+ if (action === "create-pr" || action === "create-draft" || action === "create-web") {
679
+ const custom = opts?.createPr?.trim();
680
+ if (custom) return custom;
681
+ return agentGitPrompt(action === "create-pr" ? "create-draft" : action);
2556
682
  }
2557
- if (opts.heldPorts?.length) {
2558
- await Promise.all(opts.heldPorts.map((h) => h.release()));
683
+ if (action === "resolve-conflicts") {
684
+ const custom = opts?.resolveMergeConflicts?.trim();
685
+ if (custom) return custom;
686
+ return agentGitPrompt("resolve-conflicts", opts);
2559
687
  }
2560
- const shell = process.platform === "darwin" ? "zsh" : "bash";
2561
- const child = execa2(shell, ["-lc", command], {
2562
- cwd: opts.worktreePath,
2563
- reject: false,
2564
- env,
2565
- // Own process group so Stop can tear down the whole tree (not just the shell).
2566
- // There is no settings.toml `stop=` / teardown hook for run scripts.
2567
- ...process.platform === "win32" ? {} : { detached: true }
2568
- });
2569
- pipeLines(child.stdout, opts.onLine);
2570
- pipeLines(child.stderr, opts.onLine);
2571
- const ports = opts.ports ?? [];
2572
- return {
2573
- pid: child.pid,
2574
- kill: () => killScriptTree(child, ports),
2575
- done: child.then((r) => r.exitCode ?? null),
2576
- child
2577
- };
688
+ return agentGitPrompt(action, opts);
2578
689
  }
2579
- async function attachAbort(handle, signal) {
2580
- if (signal) {
2581
- const onAbort = () => handle.kill();
2582
- if (signal.aborted) onAbort();
2583
- else signal.addEventListener("abort", onAbort, { once: true });
690
+ var RESOLVE_CONFLICTS_RE = /^Merge the remote branch(?: \(([^)]+)\))? into your branch and resolve conflicts\. Then, commit and push your changes\.$/;
691
+ function gitActionExpansion(action, base) {
692
+ switch (action) {
693
+ case "commit-push":
694
+ return "Commit any uncommitted work with a purpose-stating message, then push to origin (`git push -u origin HEAD`; this updates an existing PR when one is linked). Do not start a checks loop unless a goal was given.";
695
+ case "create-draft":
696
+ return "Commit, push, then create a draft PR with `gh pr create --draft --assignee @me -R <origin-owner/name>` (title/body from the change purpose). Update the existing PR instead if one is linked. Use a different `--assignee` only if the user named someone else.";
697
+ case "create-web":
698
+ return "Commit, push, then `gh pr create --web --assignee @me -R <origin-owner/name>`. Use a different `--assignee` only if the user named someone else.";
699
+ case "resolve-conflicts":
700
+ return `Fetch the PR base${base ? ` (\`${base}\`)` : ""}, merge it into this branch, resolve conflicts carefully, then commit and push until the PR is mergeable.`;
701
+ case "ready-for-review":
702
+ 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.";
703
+ case "merge":
704
+ 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.";
2584
705
  }
2585
- return handle.done;
2586
- }
2587
- async function runSetupScript(repoPath, worktreePath, onLine, opts) {
2588
- const settings = loadWorkspaceSettings(worktreePath, repoPath);
2589
- if (!settings?.setup) return { ran: false, exitCode: null, source: null };
2590
- const source = workspaceSettingsSourceLabel(worktreePath, repoPath);
2591
- onLine?.(`[setup] ${source ?? "settings.toml"}`);
2592
- const handle = await spawnWorkspaceScript(settings.setup, {
2593
- worktreePath,
2594
- repoPath,
2595
- defaultBranch: opts?.defaultBranch,
2596
- onLine
2597
- });
2598
- const exitCode = await attachAbort(handle, opts?.signal);
2599
- return {
2600
- ran: true,
2601
- exitCode,
2602
- source,
2603
- kill: handle.kill
2604
- };
2605
- }
2606
- async function runConventionSetup(repoPath, worktreePath, onLine, opts) {
2607
- const found = findConventionSetup(worktreePath, repoPath);
2608
- if (!found) return { ran: false, exitCode: null, source: null };
2609
- onLine?.(`[setup] ${found.source}`);
2610
- const handle = await spawnWorkspaceScript(found.command, {
2611
- worktreePath,
2612
- repoPath,
2613
- defaultBranch: opts?.defaultBranch,
2614
- onLine
2615
- });
2616
- const exitCode = await attachAbort(handle, opts?.signal);
2617
- return {
2618
- ran: true,
2619
- exitCode,
2620
- source: found.source,
2621
- kill: handle.kill
2622
- };
2623
706
  }
2624
- async function runWorkspaceSetup(repoPath, worktreePath, onLine, opts) {
2625
- await ensureWorktreeSideboardIgnored(worktreePath);
2626
- const review = ensureReviewGuidelinesFile(worktreePath, repoPath);
2627
- if (review.wrote) {
2628
- onLine?.(`[setup] wrote ${review.path} \u2014 local Review guidelines`);
707
+ function expandCanonicalGitRequest(prompt) {
708
+ const trimmed = prompt.trim();
709
+ let action = null;
710
+ let base = null;
711
+ for (const candidate of AGENT_GIT_ACTIONS) {
712
+ if (candidate === "resolve-conflicts") continue;
713
+ if (agentGitPrompt(candidate) === trimmed) {
714
+ action = candidate;
715
+ break;
716
+ }
2629
717
  }
2630
- let setup = await runSetupScript(repoPath, worktreePath, onLine, opts);
2631
- if (!setup.ran) {
2632
- setup = await runCursorWorktreeSetup(repoPath, worktreePath, onLine);
718
+ if (!action) {
719
+ const m = RESOLVE_CONFLICTS_RE.exec(trimmed);
720
+ if (m) {
721
+ action = "resolve-conflicts";
722
+ base = m[1] ?? null;
723
+ }
2633
724
  }
2634
- if (!setup.ran) {
2635
- setup = await runConventionSetup(repoPath, worktreePath, onLine, opts);
725
+ let expansion = action ? gitActionExpansion(action, base) : null;
726
+ if (!expansion && trimmed === "Address review comments.") {
727
+ expansion = "Read the PR review feedback (`gh pr view --comments`, `gh api` review comments), make the requested changes, commit, and push.";
728
+ } else if (!expansion && FIX_CI_RE.test(trimmed)) {
729
+ 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.";
2636
730
  }
2637
- return setup;
731
+ if (!expansion) return prompt;
732
+ return `${trimmed}
733
+
734
+ (Sideboard git request \u2014 do not ask for clarification: ${expansion})`;
2638
735
  }
2639
- async function runArchiveScript(repoPath, worktreePath, onLine) {
2640
- const settings = loadWorkspaceSettings(worktreePath, repoPath);
2641
- if (!settings?.archive) return { ran: false, exitCode: null };
2642
- const handle = await spawnWorkspaceScript(settings.archive, {
2643
- worktreePath,
2644
- repoPath,
2645
- onLine
2646
- });
2647
- const exitCode = await handle.done;
2648
- return { ran: true, exitCode };
2649
- }
2650
- function listRunScripts(worktreePath, repoPath) {
2651
- const settings = loadWorkspaceSettings(worktreePath, repoPath);
2652
- if (!settings?.runScripts.length) return [];
2653
- return settings.runScripts.filter((s) => {
2654
- if (!s.availableIn?.length) return true;
2655
- return s.availableIn.includes("local");
2656
- });
736
+ var FIX_CI_RE = /^Fix CI: .+\.$/;
737
+
738
+ // src/git/pr-merge-archive.ts
739
+ function normalizePrState(state) {
740
+ return (state ?? "").trim().toUpperCase();
2657
741
  }
2658
- function getDefaultRunScript(worktreePath, repoPath) {
2659
- const scripts = listRunScripts(worktreePath, repoPath);
2660
- if (!scripts.length) return null;
2661
- return scripts.find((s) => s.default === true) ?? scripts.find((s) => s.name === "dev") ?? scripts.find((s) => s.name !== "all") ?? scripts[0] ?? null;
2662
- }
2663
- function getRunScript(worktreePath, repoPath, name) {
2664
- const scripts = listRunScripts(worktreePath, repoPath);
2665
- if (!scripts.length) return null;
2666
- if (name) {
2667
- return scripts.find((s) => s.name === name) ?? null;
2668
- }
2669
- return getDefaultRunScript(worktreePath, repoPath);
2670
- }
2671
- function getRunMode(worktreePath, repoPath) {
2672
- return loadWorkspaceSettings(worktreePath, repoPath)?.runMode ?? "concurrent";
2673
- }
2674
- async function reservePort() {
2675
- return new Promise((resolve2, reject) => {
2676
- const server = createServer();
2677
- let released = false;
2678
- const release = () => new Promise((res) => {
2679
- if (released) {
2680
- res();
2681
- return;
2682
- }
2683
- released = true;
2684
- server.close(() => res());
2685
- });
2686
- server.listen(0, "127.0.0.1", () => {
2687
- const addr = server.address();
2688
- if (!addr || typeof addr === "string") {
2689
- void release().then(() => reject(new Error("Failed to allocate port")));
2690
- return;
2691
- }
2692
- resolve2({ port: addr.port, release });
2693
- });
2694
- server.on("error", reject);
2695
- });
742
+ function samePrIdentity(a, b) {
743
+ const left = (a ?? "").trim().replace(/\/+$/, "");
744
+ const right = (b ?? "").trim().replace(/\/+$/, "");
745
+ if (!left || !right) return false;
746
+ if (left === right) return true;
747
+ const ln = left.match(/\/pull\/(\d+)/i)?.[1];
748
+ const rn = right.match(/\/pull\/(\d+)/i)?.[1];
749
+ return Boolean(ln && rn && ln === rn);
2696
750
  }
2697
- async function tryReservePort(port) {
2698
- if (!Number.isInteger(port) || port <= 0 || port > 65535) return null;
2699
- return new Promise((resolve2) => {
2700
- const server = createServer();
2701
- let released = false;
2702
- const release = () => new Promise((res) => {
2703
- if (released) {
2704
- res();
2705
- return;
2706
- }
2707
- released = true;
2708
- server.close(() => res());
2709
- });
2710
- server.once("error", () => {
2711
- try {
2712
- server.close();
2713
- } catch {
2714
- }
2715
- resolve2(null);
2716
- });
2717
- server.listen(port, "127.0.0.1", () => {
2718
- resolve2({ port, release });
2719
- });
2720
- });
751
+ function shouldStopRunOnPrRetarget(previousPrUrl, nextPrUrl) {
752
+ const prev = (previousPrUrl ?? "").trim();
753
+ const next = (nextPrUrl ?? "").trim();
754
+ if (!prev || !next) return false;
755
+ return !samePrIdentity(prev, next);
2721
756
  }
2722
- function normalizePreferredPorts(ports, size) {
2723
- if (!ports?.length) return [];
2724
- const seen = /* @__PURE__ */ new Set();
2725
- const out = [];
2726
- for (const p of ports) {
2727
- if (!Number.isInteger(p) || p <= 0 || p > 65535 || seen.has(p)) continue;
2728
- seen.add(p);
2729
- out.push(p);
2730
- if (out.length >= size) break;
2731
- }
2732
- return out;
757
+ function shouldPersistFetchedPrMeta(opts) {
758
+ const meta = opts.metaUrl?.trim() || "";
759
+ if (!meta) return false;
760
+ const head = opts.headPrUrl?.trim() || "";
761
+ if (head) return samePrIdentity(meta, head);
762
+ const live = opts.livePrUrl?.trim() || "";
763
+ if (live) return samePrIdentity(meta, live);
764
+ return true;
2733
765
  }
2734
- function delay(ms) {
2735
- return new Promise((resolve2) => setTimeout(resolve2, ms));
766
+ function sameLoginList(a, b) {
767
+ const left = [...a ?? []].map((s) => s.trim().toLowerCase()).filter(Boolean).sort();
768
+ const right = [...b ?? []].map((s) => s.trim().toLowerCase()).filter(Boolean).sort();
769
+ return left.length === right.length && left.every((login, i) => login === right[i]);
2736
770
  }
2737
- async function fillPortRange(held, size) {
2738
- if (held.length >= size) return held;
2739
- const base = held[0]?.port;
2740
- const taken = new Set(held.map((h) => h.port));
2741
- try {
2742
- let offset = 0;
2743
- while (held.length < size) {
2744
- const candidate = base != null ? base + offset : null;
2745
- offset += 1;
2746
- if (candidate != null && taken.has(candidate)) continue;
2747
- const next = candidate != null ? await tryReservePort(candidate) : null;
2748
- if (next) {
2749
- held.push(next);
2750
- taken.add(next.port);
2751
- } else {
2752
- const ephemeral = await reservePort();
2753
- held.push(ephemeral);
2754
- taken.add(ephemeral.port);
2755
- }
2756
- }
2757
- return held;
2758
- } catch (err) {
2759
- await Promise.all(held.map((h) => h.release()));
2760
- throw err;
771
+ function threadPrMetaPatch(thread, meta) {
772
+ const prevState = normalizePrState(thread.prState);
773
+ const nextState = normalizePrState(meta.state);
774
+ const patch = {};
775
+ if (meta.url && meta.url !== thread.prUrl) patch.prUrl = meta.url;
776
+ if (meta.title && meta.title !== thread.prTitle) patch.prTitle = meta.title;
777
+ if (nextState && nextState !== prevState) patch.prState = nextState;
778
+ const nextDraft = Boolean(meta.isDraft) && nextState !== "MERGED" && nextState !== "CLOSED";
779
+ if (nextDraft !== Boolean(thread.prIsDraft)) patch.prIsDraft = nextDraft;
780
+ const nextAuthor = meta.authorLogin?.trim() || "";
781
+ if (nextAuthor && nextAuthor !== (thread.prAuthorLogin ?? "").trim()) {
782
+ patch.prAuthorLogin = nextAuthor;
2761
783
  }
2762
- }
2763
- async function tryReserveAvailablePorts(ports) {
2764
- const held = [];
2765
- for (const port of ports) {
2766
- const next = await tryReservePort(port);
2767
- if (next) held.push(next);
784
+ const nextReviewers = meta.reviewerLogins ?? [];
785
+ if (!sameLoginList(nextReviewers, thread.prReviewerLogins)) {
786
+ patch.prReviewerLogins = nextReviewers;
2768
787
  }
2769
- return held;
2770
- }
2771
- async function reclaimPreferredPorts(ports, worktreePath) {
2772
- const primary = ports[0];
2773
- if (primary == null) return null;
2774
- const takePrimary = async () => {
2775
- const held2 = await tryReserveAvailablePorts(ports);
2776
- if (held2.some((h) => h.port === primary)) return held2;
2777
- await Promise.all(held2.map((h) => h.release()));
2778
- return null;
2779
- };
2780
- let held = await takePrimary();
2781
- if (held) return held;
2782
- if (worktreePath) {
2783
- killStaleWorktreeListeners(ports, worktreePath, "SIGTERM");
2784
- } else {
2785
- killListenersOnPorts(ports);
788
+ const nextDecision = meta.reviewDecision ?? null;
789
+ if ((thread.prReviewDecision ?? null) !== nextDecision) {
790
+ patch.prReviewDecision = nextDecision;
2786
791
  }
2787
- for (let i = 0; i < RECLAIM_TRIES; i++) {
2788
- await delay(RECLAIM_RETRY_DELAY_MS);
2789
- held = await takePrimary();
2790
- if (held) return held;
2791
- if (i === 2 && worktreePath) {
2792
- killStaleWorktreeListeners(ports, worktreePath, "SIGKILL");
2793
- }
792
+ const nextMergeable = meta.mergeable ?? null;
793
+ if ((thread.prMergeable ?? null) !== nextMergeable) {
794
+ patch.prMergeable = nextMergeable;
2794
795
  }
2795
- return null;
2796
- }
2797
- async function reservePortRange(size = PORT_RANGE_SIZE, opts) {
2798
- const preferred = normalizePreferredPorts(opts?.preferred, size);
2799
- if (preferred.length) {
2800
- const reused = await reclaimPreferredPorts(preferred, opts?.worktreePath);
2801
- if (reused) return fillPortRange(reused, size);
2802
- }
2803
- const held = [await reservePort()];
2804
- return fillPortRange(held, size);
2805
- }
2806
- async function startDevServer(repoPath, worktreePath, onLine, opts) {
2807
- const script = getRunScript(worktreePath, repoPath, opts?.scriptName);
2808
- if (!script) return null;
2809
- const preferred = loadWorktreeRunPorts(worktreePath, script.name) ?? opts?.preferredPorts;
2810
- const held = await reservePortRange(PORT_RANGE_SIZE, {
2811
- preferred: preferred ?? void 0,
2812
- worktreePath
2813
- });
2814
- const ports = held.map((h) => h.port);
2815
- saveWorktreeRunPorts(worktreePath, script.name, ports);
2816
- try {
2817
- const handle = await spawnWorkspaceScript(script.command, {
2818
- worktreePath,
2819
- repoPath,
2820
- ports,
2821
- heldPorts: held,
2822
- defaultBranch: opts?.defaultBranch,
2823
- onLine
2824
- });
2825
- return {
2826
- pid: handle.pid,
2827
- port: ports[0],
2828
- ports,
2829
- scriptName: script.name,
2830
- kill: handle.kill,
2831
- done: handle.done
2832
- };
2833
- } catch (err) {
2834
- await Promise.all(held.map((h) => h.release()));
2835
- throw err;
796
+ const nextMergeState = meta.mergeStateStatus ?? null;
797
+ if ((thread.prMergeStateStatus ?? null) !== nextMergeState) {
798
+ patch.prMergeStateStatus = nextMergeState;
799
+ }
800
+ const nextQueued = Boolean(meta.isInMergeQueue);
801
+ if (Boolean(thread.prIsInMergeQueue) !== nextQueued) {
802
+ patch.prIsInMergeQueue = nextQueued;
803
+ }
804
+ const nextLabels = meta.labels ?? [];
805
+ if (!sameLoginList(nextLabels, thread.prLabels)) {
806
+ patch.prLabels = nextLabels;
807
+ }
808
+ const mergeState = (nextMergeState ?? "").toUpperCase();
809
+ if (mergeState === "CLEAN" && thread.prChecksFailed) {
810
+ patch.prChecksFailed = false;
811
+ } else if ((mergeState === "UNSTABLE" || mergeState === "BLOCKED" && (nextDecision ?? "").toUpperCase() === "APPROVED") && !thread.prChecksFailed) {
812
+ patch.prChecksFailed = true;
2836
813
  }
814
+ if (thread.skipAutoArchiveOnMerge && nextState && nextState !== "MERGED" && nextState !== "CLOSED") {
815
+ patch.skipAutoArchiveOnMerge = false;
816
+ }
817
+ return patch;
818
+ }
819
+ function threadPrChecksFailedPatch(thread, failed) {
820
+ if (thread.prChecksFailed === failed) return {};
821
+ return { prChecksFailed: failed };
822
+ }
823
+ function shouldAutoArchiveOnPrMerge(opts) {
824
+ if (!opts.autoArchiveEnabled) return false;
825
+ if (opts.isGlobal) return false;
826
+ if (opts.skipAutoArchiveOnMerge) return false;
827
+ if (opts.threadStatus === "archived") return false;
828
+ const next = normalizePrState(opts.nextPrState);
829
+ if (next !== "MERGED") return false;
830
+ return normalizePrState(opts.previousPrState) !== "MERGED";
2837
831
  }
2838
832
 
2839
833
  // src/store/setup-log.ts
2840
834
  import { createHash } from "crypto";
2841
- import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
2842
- import { join as join7 } from "path";
835
+ import { existsSync, readFileSync, writeFileSync } from "fs";
836
+ import { join as join2 } from "path";
2843
837
  var EMPTY = {
2844
838
  output: "",
2845
839
  running: false,
@@ -2881,7 +875,7 @@ function materialize(mem) {
2881
875
  };
2882
876
  }
2883
877
  function setupLogPath(threadId) {
2884
- return join7(threadsDir(), `${threadId}.setup.log.json`);
878
+ return join2(threadsDir(), `${threadId}.setup.log.json`);
2885
879
  }
2886
880
  function setupLogKeyForWorktree(worktreePath) {
2887
881
  const norm = normalizeWorktreePath(worktreePath);
@@ -2900,7 +894,7 @@ function persistNow(threadId) {
2900
894
  const mem = memory.get(threadId);
2901
895
  if (!mem) return;
2902
896
  try {
2903
- writeFileSync4(
897
+ writeFileSync(
2904
898
  setupLogPath(threadId),
2905
899
  `${JSON.stringify(materialize(mem))}
2906
900
  `,
@@ -2921,9 +915,9 @@ function schedulePersist(threadId) {
2921
915
  }
2922
916
  function loadFromDisk(threadId) {
2923
917
  const path = setupLogPath(threadId);
2924
- if (!existsSync5(path)) return null;
918
+ if (!existsSync(path)) return null;
2925
919
  try {
2926
- const raw = JSON.parse(readFileSync5(path, "utf8"));
920
+ const raw = JSON.parse(readFileSync(path, "utf8"));
2927
921
  return {
2928
922
  output: typeof raw.output === "string" ? raw.output : "",
2929
923
  running: Boolean(raw.running),
@@ -2982,8 +976,8 @@ function finishSetupLog(threadId, exitCode, source) {
2982
976
 
2983
977
  // src/store/run-log.ts
2984
978
  import { createHash as createHash2 } from "crypto";
2985
- import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
2986
- import { join as join8 } from "path";
979
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
980
+ import { join as join3 } from "path";
2987
981
  var EMPTY2 = {
2988
982
  output: "",
2989
983
  running: false,
@@ -3022,7 +1016,7 @@ function safeScriptToken(scriptName) {
3022
1016
  return token || "dev";
3023
1017
  }
3024
1018
  function runLogPath(key) {
3025
- return join8(threadsDir(), `${key}.run.log.json`);
1019
+ return join3(threadsDir(), `${key}.run.log.json`);
3026
1020
  }
3027
1021
  function runLogKeyForWorktree(worktreePath, scriptName) {
3028
1022
  const norm = normalizeWorktreePath(worktreePath);
@@ -3051,7 +1045,7 @@ function persistNow2(key) {
3051
1045
  const mem = memory2.get(key);
3052
1046
  if (!mem) return;
3053
1047
  try {
3054
- writeFileSync5(runLogPath(key), `${JSON.stringify(materialize2(mem))}
1048
+ writeFileSync2(runLogPath(key), `${JSON.stringify(materialize2(mem))}
3055
1049
  `, "utf8");
3056
1050
  } catch {
3057
1051
  }
@@ -3068,9 +1062,9 @@ function schedulePersist2(key) {
3068
1062
  }
3069
1063
  function loadFromDisk2(key) {
3070
1064
  const path = runLogPath(key);
3071
- if (!existsSync6(path)) return null;
1065
+ if (!existsSync2(path)) return null;
3072
1066
  try {
3073
- const raw = JSON.parse(readFileSync6(path, "utf8"));
1067
+ const raw = JSON.parse(readFileSync2(path, "utf8"));
3074
1068
  return {
3075
1069
  output: typeof raw.output === "string" ? raw.output : "",
3076
1070
  running: Boolean(raw.running),
@@ -3130,8 +1124,8 @@ function flushRunLog(key) {
3130
1124
  }
3131
1125
 
3132
1126
  // src/git/orphan-cleanup.ts
3133
- import { existsSync as existsSync7, readdirSync as readdirSync3, statSync } from "fs";
3134
- import { join as join9 } from "path";
1127
+ import { existsSync as existsSync3, readdirSync, statSync } from "fs";
1128
+ import { join as join4 } from "path";
3135
1129
  function isSideboardWorktreePath(path) {
3136
1130
  return path.includes("/.sideboard/worktrees/") || path.includes("/sideboard/workspaces/");
3137
1131
  }
@@ -3142,9 +1136,9 @@ async function findOrphanWorktrees(repoPaths) {
3142
1136
  repoPaths?.length ? repoPaths : threads.map((t) => t.repoPath).filter(Boolean)
3143
1137
  );
3144
1138
  const homeRoot = sideboardWorkspacesDir();
3145
- if (existsSync7(homeRoot)) {
1139
+ if (existsSync3(homeRoot)) {
3146
1140
  try {
3147
- for (const entry of readdirSync3(homeRoot, { withFileTypes: true })) {
1141
+ for (const entry of readdirSync(homeRoot, { withFileTypes: true })) {
3148
1142
  if (!entry.isDirectory()) continue;
3149
1143
  void entry;
3150
1144
  }
@@ -3154,7 +1148,7 @@ async function findOrphanWorktrees(repoPaths) {
3154
1148
  const orphans = [];
3155
1149
  const seen = /* @__PURE__ */ new Set();
3156
1150
  for (const repoPath of repos) {
3157
- if (!repoPath || !existsSync7(repoPath)) continue;
1151
+ if (!repoPath || !existsSync3(repoPath)) continue;
3158
1152
  try {
3159
1153
  const wts = await listWorktrees(repoPath);
3160
1154
  for (const wt of wts) {
@@ -3175,12 +1169,12 @@ async function findOrphanWorktrees(repoPaths) {
3175
1169
  }
3176
1170
  try {
3177
1171
  const root = worktreesRoot(repoPath);
3178
- if (existsSync7(root)) {
3179
- for (const entry of readdirSync3(root, { withFileTypes: true })) {
1172
+ if (existsSync3(root)) {
1173
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
3180
1174
  if (!entry.isDirectory()) continue;
3181
- const path = join9(root, entry.name).replace(/\/$/, "");
1175
+ const path = join4(root, entry.name).replace(/\/$/, "");
3182
1176
  if (known.has(path) || seen.has(path)) continue;
3183
- if (!existsSync7(join9(path, ".git"))) continue;
1177
+ if (!existsSync3(join4(path, ".git"))) continue;
3184
1178
  seen.add(path);
3185
1179
  let mtimeMs = 0;
3186
1180
  try {
@@ -3303,11 +1297,11 @@ function shouldRunHistoryCleanup(settings = loadAppSettings()) {
3303
1297
  }
3304
1298
 
3305
1299
  // src/git/apply-into-main.ts
3306
- import { execa as execa3 } from "execa";
1300
+ import { execa as execa2 } from "execa";
3307
1301
  async function applyThreadIntoMain(thread, opts) {
3308
1302
  const method = opts?.method ?? "merge";
3309
1303
  const target = opts?.targetBranch?.trim() || await resolveDefaultBranch(thread.repoPath);
3310
- const status = await execa3("git", ["status", "--porcelain"], {
1304
+ const status = await execa2("git", ["status", "--porcelain"], {
3311
1305
  cwd: thread.repoPath,
3312
1306
  reject: false
3313
1307
  });
@@ -3316,7 +1310,7 @@ async function applyThreadIntoMain(thread, opts) {
3316
1310
  `Main checkout is dirty \u2014 commit or stash before apply-into-main (${thread.repoPath})`
3317
1311
  );
3318
1312
  }
3319
- const checkout = await execa3("git", ["checkout", target], {
1313
+ const checkout = await execa2("git", ["checkout", target], {
3320
1314
  cwd: thread.repoPath,
3321
1315
  reject: false
3322
1316
  });
@@ -3326,7 +1320,7 @@ async function applyThreadIntoMain(thread, opts) {
3326
1320
  );
3327
1321
  }
3328
1322
  if (method === "cherry-pick") {
3329
- const log = await execa3(
1323
+ const log = await execa2(
3330
1324
  "git",
3331
1325
  ["log", "--reverse", "--format=%H", `${target}..${thread.branchName}`],
3332
1326
  { cwd: thread.repoPath, reject: false }
@@ -3341,12 +1335,12 @@ async function applyThreadIntoMain(thread, opts) {
3341
1335
  };
3342
1336
  }
3343
1337
  for (const sha of shas) {
3344
- const cp = await execa3("git", ["cherry-pick", sha], {
1338
+ const cp = await execa2("git", ["cherry-pick", sha], {
3345
1339
  cwd: thread.repoPath,
3346
1340
  reject: false
3347
1341
  });
3348
1342
  if (cp.exitCode !== 0) {
3349
- await execa3("git", ["cherry-pick", "--abort"], {
1343
+ await execa2("git", ["cherry-pick", "--abort"], {
3350
1344
  cwd: thread.repoPath,
3351
1345
  reject: false
3352
1346
  });
@@ -3362,13 +1356,13 @@ async function applyThreadIntoMain(thread, opts) {
3362
1356
  message: `Cherry-picked ${shas.length} commit(s) from ${thread.branchName} into ${target}`
3363
1357
  };
3364
1358
  }
3365
- const merge = await execa3(
1359
+ const merge = await execa2(
3366
1360
  "git",
3367
1361
  ["merge", "--no-ff", "-m", `Apply Sideboard thread ${thread.branchName}`, thread.branchName],
3368
1362
  { cwd: thread.repoPath, reject: false }
3369
1363
  );
3370
1364
  if (merge.exitCode !== 0) {
3371
- await execa3("git", ["merge", "--abort"], {
1365
+ await execa2("git", ["merge", "--abort"], {
3372
1366
  cwd: thread.repoPath,
3373
1367
  reject: false
3374
1368
  });
@@ -3385,25 +1379,25 @@ async function applyThreadIntoMain(thread, opts) {
3385
1379
  }
3386
1380
 
3387
1381
  // src/git/clone-repo.ts
3388
- import { existsSync as existsSync8 } from "fs";
3389
- import { basename as basename2, join as join10 } from "path";
3390
- import { execa as execa4 } from "execa";
1382
+ import { existsSync as existsSync4 } from "fs";
1383
+ import { basename, join as join5 } from "path";
1384
+ import { execa as execa3 } from "execa";
3391
1385
  async function cloneRepoIntoSideboard(opts) {
3392
1386
  const url = opts.url.trim();
3393
1387
  if (!url) throw new Error("Clone URL is required");
3394
1388
  let name = opts.name?.trim();
3395
1389
  if (!name) {
3396
- const leaf = basename2(url.replace(/\/$/, "").replace(/\.git$/, ""));
1390
+ const leaf = basename(url.replace(/\/$/, "").replace(/\.git$/, ""));
3397
1391
  name = leaf || "repo";
3398
1392
  }
3399
1393
  name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
3400
- const dest = join10(sideboardReposDir(), name);
3401
- if (existsSync8(dest)) {
1394
+ const dest = join5(sideboardReposDir(), name);
1395
+ if (existsSync4(dest)) {
3402
1396
  const repoPath2 = await resolveRepoRoot(dest);
3403
1397
  const workspace2 = await ensureWorkspace(repoPath2);
3404
1398
  return { repoPath: repoPath2, workspace: workspace2 };
3405
1399
  }
3406
- const result = await execa4("git", ["clone", url, dest], { reject: false });
1400
+ const result = await execa3("git", ["clone", url, dest], { reject: false });
3407
1401
  if (result.exitCode !== 0) {
3408
1402
  throw new Error(
3409
1403
  `git clone failed: ${result.stderr?.trim() || result.stdout?.trim() || `exit ${result.exitCode}`}`
@@ -3420,14 +1414,14 @@ function isAdoptableRunScriptRequest(req) {
3420
1414
  }
3421
1415
 
3422
1416
  // src/store/desktop-host.ts
3423
- import { readFileSync as readFileSync7, unlinkSync, writeFileSync as writeFileSync6 } from "fs";
3424
- import { join as join11 } from "path";
1417
+ import { readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
1418
+ import { join as join6 } from "path";
3425
1419
  function desktopHostPidPath() {
3426
- return join11(appDataDir(), "desktop-host.pid");
1420
+ return join6(appDataDir(), "desktop-host.pid");
3427
1421
  }
3428
1422
  function readDesktopHostPid() {
3429
1423
  try {
3430
- const pid = Number.parseInt(readFileSync7(desktopHostPidPath(), "utf8").trim(), 10);
1424
+ const pid = Number.parseInt(readFileSync3(desktopHostPidPath(), "utf8").trim(), 10);
3431
1425
  if (!Number.isFinite(pid) || pid <= 0) return null;
3432
1426
  return pid;
3433
1427
  } catch {
@@ -3523,7 +1517,7 @@ function agentPurposeStamp(input) {
3523
1517
  }
3524
1518
 
3525
1519
  // src/threads/create.ts
3526
- import { existsSync as existsSync9 } from "fs";
1520
+ import { existsSync as existsSync5 } from "fs";
3527
1521
 
3528
1522
  // src/detect/detect.ts
3529
1523
  var REQUIRE_AGENT_TIMEOUT_MS = 8e3;
@@ -4076,7 +2070,7 @@ function reuseLiveThread(input, repoPath, match) {
4076
2070
  }
4077
2071
  async function createThread(input, _onSetupLine) {
4078
2072
  const repoPath = await resolveRepoRoot(input.repoPath);
4079
- if (!existsSync9(repoPath)) {
2073
+ if (!existsSync5(repoPath)) {
4080
2074
  throw new Error(`Repo not found: ${repoPath}`);
4081
2075
  }
4082
2076
  const reused = reuseLiveThread(input, repoPath, {
@@ -4093,7 +2087,7 @@ async function createThread(input, _onSetupLine) {
4093
2087
  });
4094
2088
  await requireAgent(resolved.agent);
4095
2089
  if (input.cowboy) {
4096
- const { cowboyModeEnabled } = await import("./app-settings-MVZPM6WW.js");
2090
+ const { cowboyModeEnabled } = await import("./app-settings-RAC4PSSP.js");
4097
2091
  if (!cowboyModeEnabled()) {
4098
2092
  throw new Error(
4099
2093
  "Cowboy mode is off. Enable it in Settings \u2192 Advanced."
@@ -4151,7 +2145,7 @@ async function createThread(input, _onSetupLine) {
4151
2145
  let sourceIsFork = false;
4152
2146
  if (!input.cowboy && sourceType === "branch" && (!sourceRef || sourceRef === "HEAD" || sourceRef === "default")) {
4153
2147
  if (isAbleTimeConnected() && getIssueSource() === "abletime") {
4154
- const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-6AOJI2YZ.js");
2148
+ const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-4ZSDI4YP.js");
4155
2149
  const task = await ensureAbleTimeTask({
4156
2150
  title: title?.trim() || "Untitled work",
4157
2151
  description: input.prompt?.trim() || void 0
@@ -4313,11 +2307,11 @@ function createLineCoalescer(onChunk, opts) {
4313
2307
 
4314
2308
  // src/store/turn-live.ts
4315
2309
  import {
4316
- existsSync as existsSync10,
2310
+ existsSync as existsSync6,
4317
2311
  renameSync,
4318
2312
  unlinkSync as unlinkSync2,
4319
- writeFileSync as writeFileSync7,
4320
- readFileSync as readFileSync8
2313
+ writeFileSync as writeFileSync4,
2314
+ readFileSync as readFileSync4
4321
2315
  } from "fs";
4322
2316
  var buffers = /* @__PURE__ */ new Map();
4323
2317
  var FLUSH_MS = 800;
@@ -4394,7 +2388,7 @@ function writeTurnLive(threadId, progress) {
4394
2388
  const path = threadLivePath(threadId);
4395
2389
  const tmp = `${path}.${process.pid}.tmp`;
4396
2390
  try {
4397
- writeFileSync7(tmp, JSON.stringify(progress), "utf8");
2391
+ writeFileSync4(tmp, JSON.stringify(progress), "utf8");
4398
2392
  renameSync(tmp, path);
4399
2393
  } catch {
4400
2394
  try {
@@ -4405,9 +2399,9 @@ function writeTurnLive(threadId, progress) {
4405
2399
  }
4406
2400
  function readTurnLive(threadId) {
4407
2401
  const path = threadLivePath(threadId);
4408
- if (!existsSync10(path)) return null;
2402
+ if (!existsSync6(path)) return null;
4409
2403
  try {
4410
- const raw = JSON.parse(readFileSync8(path, "utf8"));
2404
+ const raw = JSON.parse(readFileSync4(path, "utf8"));
4411
2405
  if (!raw || typeof raw.summary !== "string") return null;
4412
2406
  return raw;
4413
2407
  } catch {
@@ -4419,7 +2413,7 @@ function clearTurnLive(threadId) {
4419
2413
  if (buf?.timer) clearTimeout(buf.timer);
4420
2414
  buffers.delete(threadId);
4421
2415
  const path = threadLivePath(threadId);
4422
- if (!existsSync10(path)) return;
2416
+ if (!existsSync6(path)) return;
4423
2417
  try {
4424
2418
  unlinkSync2(path);
4425
2419
  } catch {
@@ -4453,13 +2447,13 @@ function isStaleLastErrorDuringTurn(err) {
4453
2447
  }
4454
2448
 
4455
2449
  // src/threads/fork-worktree.ts
4456
- function requireThread2(idOrRef) {
2450
+ function requireThread(idOrRef) {
4457
2451
  const thread = findThreadByRef(idOrRef) ?? null;
4458
2452
  if (!thread) throw new Error(`Thread not found: ${idOrRef}`);
4459
2453
  return thread;
4460
2454
  }
4461
2455
  async function forkThreadWorktree(input, onSetupLine) {
4462
- const from = requireThread2(input.threadId);
2456
+ const from = requireThread(input.threadId);
4463
2457
  if (isOrchestratorThread(from)) {
4464
2458
  throw new Error(
4465
2459
  "fork_workspace targets a worktree agent thread (not the orchestrator). Pass a child/worktree thread ref."
@@ -4496,7 +2490,7 @@ async function forkThreadWorktree(input, onSetupLine) {
4496
2490
  }
4497
2491
 
4498
2492
  // src/orchestrator/quota-failover.ts
4499
- import { randomUUID as randomUUID3 } from "crypto";
2493
+ import { randomUUID } from "crypto";
4500
2494
  function managedKindForAgent(agent) {
4501
2495
  return agent === "claude" || agent === "codex" ? agent : null;
4502
2496
  }
@@ -4616,7 +2610,7 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
4616
2610
  ...instructions
4617
2611
  ].join("\n");
4618
2612
  return {
4619
- id: randomUUID3(),
2613
+ id: randomUUID(),
4620
2614
  name: orch ? "Orchestration quota handoff.md" : "Quota handoff.md",
4621
2615
  kind: "transcript",
4622
2616
  content: body
@@ -4631,9 +2625,9 @@ var QUOTA_CONTINUE_ACCOUNT_PROMPT = (fromAgent, accountLabel) => [
4631
2625
  "Proceed with the goal. Leave model Auto unless needed."
4632
2626
  ].join(" ");
4633
2627
  var QUOTA_RESUME_PROMPT = "Session/usage limit window should have reset. Continue this chat from where you left off.";
4634
- async function isolateQuotaFailover(run2) {
2628
+ async function isolateQuotaFailover(run) {
4635
2629
  try {
4636
- return await run2();
2630
+ return await run();
4637
2631
  } catch {
4638
2632
  return true;
4639
2633
  }
@@ -4659,29 +2653,29 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText, opts) {
4659
2653
  }
4660
2654
 
4661
2655
  // src/threads/adopt.ts
4662
- import { execFileSync as execFileSync2 } from "child_process";
2656
+ import { execFileSync } from "child_process";
4663
2657
  import {
4664
- copyFileSync as copyFileSync2,
4665
- existsSync as existsSync11,
2658
+ copyFileSync,
2659
+ existsSync as existsSync7,
4666
2660
  mkdtempSync,
4667
- readdirSync as readdirSync4,
4668
- readFileSync as readFileSync9,
2661
+ readdirSync as readdirSync2,
2662
+ readFileSync as readFileSync5,
4669
2663
  rmSync
4670
2664
  } from "fs";
4671
2665
  import { tmpdir } from "os";
4672
- import { join as join12 } from "path";
2666
+ import { join as join7 } from "path";
4673
2667
  import { createRequire } from "module";
4674
- var CONDUCTOR_APP_SUPPORT = join12(
2668
+ var CONDUCTOR_APP_SUPPORT = join7(
4675
2669
  process.env.HOME ?? "",
4676
2670
  "Library",
4677
2671
  "Application Support",
4678
2672
  "com.conductor.app"
4679
2673
  );
4680
- var CONDUCTOR_DB = join12(CONDUCTOR_APP_SUPPORT, "conductor.db");
4681
- var CURSOR_SDK_STORE = join12(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
2674
+ var CONDUCTOR_DB = join7(CONDUCTOR_APP_SUPPORT, "conductor.db");
2675
+ var CURSOR_SDK_STORE = join7(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
4682
2676
  function thisModuleFile() {
4683
2677
  const cjsFile = typeof __filename !== "undefined" ? __filename : "";
4684
- return cjsFile || process.argv[1] || join12(process.cwd(), "package.json");
2678
+ return cjsFile || process.argv[1] || join7(process.cwd(), "package.json");
4685
2679
  }
4686
2680
  function openReadonlySqlite(file) {
4687
2681
  const req = createRequire(thisModuleFile());
@@ -4699,21 +2693,21 @@ function mapAgentType(raw) {
4699
2693
  return null;
4700
2694
  }
4701
2695
  function resolveConductorCursorAgentId(workspacePath) {
4702
- if (!workspacePath || !existsSync11(CURSOR_SDK_STORE)) return null;
2696
+ if (!workspacePath || !existsSync7(CURSOR_SDK_STORE)) return null;
4703
2697
  const normalized = workspacePath.replace(/\/$/, "");
4704
2698
  let best = null;
4705
2699
  let hashes;
4706
2700
  try {
4707
- hashes = readdirSync4(CURSOR_SDK_STORE);
2701
+ hashes = readdirSync2(CURSOR_SDK_STORE);
4708
2702
  } catch {
4709
2703
  return null;
4710
2704
  }
4711
2705
  for (const hash of hashes) {
4712
- const agentsFile = join12(CURSOR_SDK_STORE, hash, "agents.ndjson");
4713
- if (!existsSync11(agentsFile)) continue;
2706
+ const agentsFile = join7(CURSOR_SDK_STORE, hash, "agents.ndjson");
2707
+ if (!existsSync7(agentsFile)) continue;
4714
2708
  let text;
4715
2709
  try {
4716
- text = readFileSync9(agentsFile, "utf8");
2710
+ text = readFileSync5(agentsFile, "utf8");
4717
2711
  } catch {
4718
2712
  continue;
4719
2713
  }
@@ -4737,7 +2731,7 @@ function resolveConductorCursorAgentId(workspacePath) {
4737
2731
  return best?.agentId ?? null;
4738
2732
  }
4739
2733
  async function adoptThread(input) {
4740
- if (!existsSync11(input.worktreePath)) {
2734
+ if (!existsSync7(input.worktreePath)) {
4741
2735
  throw new Error(`Worktree not found: ${input.worktreePath}`);
4742
2736
  }
4743
2737
  const repoPath = await resolveRepoRoot(input.worktreePath);
@@ -4756,25 +2750,25 @@ async function adoptThread(input) {
4756
2750
  messages: input.messages ?? []
4757
2751
  });
4758
2752
  writeThread(thread);
4759
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-WOQHJFRQ.js");
2753
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-SLP24Z3S.js");
4760
2754
  await ensureWorkspace2(repoPath);
4761
- const { ensureWorktreeSideboardIgnored: ensureWorktreeSideboardIgnored2 } = await import("./worktree-exclude-5N2KTYIT.js");
4762
- await ensureWorktreeSideboardIgnored2(input.worktreePath);
2755
+ const { ensureWorktreeSideboardIgnored } = await import("./worktree-exclude-5N2KTYIT.js");
2756
+ await ensureWorktreeSideboardIgnored(input.worktreePath);
4763
2757
  return thread;
4764
2758
  }
4765
2759
  function listConductorWorkspaces() {
4766
- if (!existsSync11(CONDUCTOR_DB)) {
2760
+ if (!existsSync7(CONDUCTOR_DB)) {
4767
2761
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
4768
2762
  }
4769
- const tmp = mkdtempSync(join12(tmpdir(), "sideboard-conductor-"));
4770
- const snapshot = join12(tmp, "conductor.db");
2763
+ const tmp = mkdtempSync(join7(tmpdir(), "sideboard-conductor-"));
2764
+ const snapshot = join7(tmp, "conductor.db");
4771
2765
  try {
4772
- copyFileSync2(CONDUCTOR_DB, snapshot);
2766
+ copyFileSync(CONDUCTOR_DB, snapshot);
4773
2767
  for (const suffix of ["-wal", "-shm"]) {
4774
2768
  const src = `${CONDUCTOR_DB}${suffix}`;
4775
- if (existsSync11(src)) {
2769
+ if (existsSync7(src)) {
4776
2770
  try {
4777
- copyFileSync2(src, `${snapshot}${suffix}`);
2771
+ copyFileSync(src, `${snapshot}${suffix}`);
4778
2772
  } catch {
4779
2773
  }
4780
2774
  }
@@ -4854,18 +2848,18 @@ function listConductorWorkspaces() {
4854
2848
  }
4855
2849
  }
4856
2850
  function importConductorWorkspace(workspaceId) {
4857
- if (!existsSync11(CONDUCTOR_DB)) {
2851
+ if (!existsSync7(CONDUCTOR_DB)) {
4858
2852
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
4859
2853
  }
4860
- const tmp = mkdtempSync(join12(tmpdir(), "sideboard-conductor-"));
4861
- const snapshot = join12(tmp, "conductor.db");
2854
+ const tmp = mkdtempSync(join7(tmpdir(), "sideboard-conductor-"));
2855
+ const snapshot = join7(tmp, "conductor.db");
4862
2856
  try {
4863
- copyFileSync2(CONDUCTOR_DB, snapshot);
2857
+ copyFileSync(CONDUCTOR_DB, snapshot);
4864
2858
  for (const suffix of ["-wal", "-shm"]) {
4865
2859
  const src = `${CONDUCTOR_DB}${suffix}`;
4866
- if (existsSync11(src)) {
2860
+ if (existsSync7(src)) {
4867
2861
  try {
4868
- copyFileSync2(src, `${snapshot}${suffix}`);
2862
+ copyFileSync(src, `${snapshot}${suffix}`);
4869
2863
  } catch {
4870
2864
  }
4871
2865
  }
@@ -4883,7 +2877,7 @@ function importConductorWorkspace(workspaceId) {
4883
2877
  ).get(workspaceId);
4884
2878
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
4885
2879
  const worktreePath = String(row.workspacePath);
4886
- if (!existsSync11(worktreePath)) {
2880
+ if (!existsSync7(worktreePath)) {
4887
2881
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
4888
2882
  }
4889
2883
  let sessionId = null;
@@ -4923,7 +2917,7 @@ function importConductorWorkspace(workspaceId) {
4923
2917
  }
4924
2918
  let repoPath = worktreePath;
4925
2919
  try {
4926
- repoPath = execFileSync2("git", ["rev-parse", "--show-toplevel"], {
2920
+ repoPath = execFileSync("git", ["rev-parse", "--show-toplevel"], {
4927
2921
  cwd: worktreePath,
4928
2922
  encoding: "utf8"
4929
2923
  }).trim();
@@ -4954,7 +2948,7 @@ async function importConductorWorkspaceAsync(workspaceId) {
4954
2948
  }
4955
2949
 
4956
2950
  // src/threads/stack-layers.ts
4957
- import { existsSync as existsSync12 } from "fs";
2951
+ import { existsSync as existsSync8 } from "fs";
4958
2952
  function stackIdFrom(stack) {
4959
2953
  if (stack.stackNumber != null) return `gh-stack-${stack.stackNumber}`;
4960
2954
  const key = stack.layers.map((l) => l.branchName).join("|");
@@ -4966,7 +2960,7 @@ function hashShort(s) {
4966
2960
  for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) | 0;
4967
2961
  return Math.abs(h).toString(36);
4968
2962
  }
4969
- function requireThread3(idOrRef) {
2963
+ function requireThread2(idOrRef) {
4970
2964
  const thread = findThreadByRef(idOrRef) ?? readThread(idOrRef);
4971
2965
  if (!thread) throw new Error(`Thread not found: ${idOrRef}`);
4972
2966
  return thread;
@@ -5017,7 +3011,7 @@ async function openStackLayer(input, _onSetupLine) {
5017
3011
  let createdWorktree = false;
5018
3012
  const trees = await listWorktrees(repoPath);
5019
3013
  const checkedOut = trees.find((w) => w.branch === branchName);
5020
- if (checkedOut?.path && existsSync12(checkedOut.path)) {
3014
+ if (checkedOut?.path && existsSync8(checkedOut.path)) {
5021
3015
  if (input.reuseExistingWorktree !== false) {
5022
3016
  worktreePath = checkedOut.path;
5023
3017
  } else {
@@ -5065,7 +3059,7 @@ async function openStackLayer(input, _onSetupLine) {
5065
3059
  return { thread: readThread(thread.id) ?? thread, createdWorktree };
5066
3060
  }
5067
3061
  async function openPrStackLayers(input, onSetupLine) {
5068
- const from = requireThread3(input.threadRef);
3062
+ const from = requireThread2(input.threadRef);
5069
3063
  if (!from.worktreePath?.trim() || !from.repoPath?.trim()) {
5070
3064
  throw new Error("Thread has no worktree");
5071
3065
  }
@@ -5109,7 +3103,7 @@ async function openPrStackLayers(input, onSetupLine) {
5109
3103
  return { stack, threads, createdThreadIds };
5110
3104
  }
5111
3105
  async function addStackLayerFromThread(input, onSetupLine) {
5112
- const from = requireThread3(input.threadRef);
3106
+ const from = requireThread2(input.threadRef);
5113
3107
  if (!from.worktreePath?.trim() || !from.repoPath?.trim()) {
5114
3108
  throw new Error("Thread has no worktree");
5115
3109
  }
@@ -5141,7 +3135,7 @@ async function addStackLayerFromThread(input, onSetupLine) {
5141
3135
  return { stack, thread: thread.thread, createdWorktree: thread.createdWorktree };
5142
3136
  }
5143
3137
  async function initStackFromThread(input, onSetupLine) {
5144
- const from = requireThread3(input.threadRef);
3138
+ const from = requireThread2(input.threadRef);
5145
3139
  if (!from.worktreePath?.trim() || !from.branchName?.trim() || !from.repoPath?.trim()) {
5146
3140
  throw new Error("Thread has no worktree/branch");
5147
3141
  }
@@ -5159,7 +3153,7 @@ async function initStackFromThread(input, onSetupLine) {
5159
3153
  async function createPrStack(input, onSetupLine) {
5160
3154
  await requireAgent(input.agent);
5161
3155
  const repoPath = await resolveRepoRoot(input.repoPath);
5162
- if (!existsSync12(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
3156
+ if (!existsSync8(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
5163
3157
  if (!input.branches.length) throw new Error("At least one branch name required");
5164
3158
  const status = await detectGhStack(repoPath);
5165
3159
  if (!status.available) throw new Error(status.reason);
@@ -5226,7 +3220,7 @@ async function createPrStack(input, onSetupLine) {
5226
3220
  }
5227
3221
  }
5228
3222
  const claimed = new Set(threads.map((t) => t.worktreePath));
5229
- if (!claimed.has(bootstrap.worktreePath) && existsSync12(bootstrap.worktreePath)) {
3223
+ if (!claimed.has(bootstrap.worktreePath) && existsSync8(bootstrap.worktreePath)) {
5230
3224
  try {
5231
3225
  await removeWorktree(repoPath, bootstrap.worktreePath, {
5232
3226
  deleteBranch: bootstrap.branchName
@@ -5238,10 +3232,10 @@ async function createPrStack(input, onSetupLine) {
5238
3232
  }
5239
3233
 
5240
3234
  // src/diff/diff.ts
5241
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
5242
- import { dirname as dirname5, join as join13 } from "path";
3235
+ import { existsSync as existsSync9, mkdirSync, readFileSync as readFileSync6, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
3236
+ import { dirname, join as join8 } from "path";
5243
3237
  async function inspectGitWorktree(worktreePath) {
5244
- if (!worktreePath || !existsSync13(worktreePath)) return "missing_worktree";
3238
+ if (!worktreePath || !existsSync9(worktreePath)) return "missing_worktree";
5245
3239
  const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
5246
3240
  reject: false
5247
3241
  });
@@ -5249,7 +3243,7 @@ async function inspectGitWorktree(worktreePath) {
5249
3243
  return "ok";
5250
3244
  }
5251
3245
  async function initializeGitRepository(worktreePath) {
5252
- if (!worktreePath || !existsSync13(worktreePath)) {
3246
+ if (!worktreePath || !existsSync9(worktreePath)) {
5253
3247
  throw new Error("Worktree not found");
5254
3248
  }
5255
3249
  const status = await inspectGitWorktree(worktreePath);
@@ -5385,11 +3379,11 @@ new file mode 100644
5385
3379
  };
5386
3380
  }
5387
3381
  async function untrackedPatch(worktreePath, path, maxHunk) {
5388
- const abs = join13(worktreePath, path);
3382
+ const abs = join8(worktreePath, path);
5389
3383
  try {
5390
3384
  const st = statSync2(abs);
5391
3385
  if (st.isFile() && st.size > maxHunk) {
5392
- const buf = readFileSync10(abs).subarray(0, maxHunk);
3386
+ const buf = readFileSync6(abs).subarray(0, maxHunk);
5393
3387
  return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
5394
3388
  }
5395
3389
  } catch {
@@ -5857,8 +3851,8 @@ async function getDiff(worktreePath, repoPath, opts) {
5857
3851
  }
5858
3852
  function statWorktreePath(worktreePath, relativePath) {
5859
3853
  assertSafeRelativePath(relativePath);
5860
- const abs = join13(worktreePath, relativePath);
5861
- if (!existsSync13(abs)) return "missing";
3854
+ const abs = join8(worktreePath, relativePath);
3855
+ if (!existsSync9(abs)) return "missing";
5862
3856
  const st = statSync2(abs);
5863
3857
  if (st.isDirectory()) return "dir";
5864
3858
  if (st.isFile()) return "file";
@@ -5899,7 +3893,7 @@ var DEFAULT_UPLOAD_MAX_BYTES = 5e7;
5899
3893
  function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
5900
3894
  assertSafeRelativePath(relativePath);
5901
3895
  const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
5902
- const abs = join13(worktreePath, relativePath);
3896
+ const abs = join8(worktreePath, relativePath);
5903
3897
  const st = statSync2(abs);
5904
3898
  if (!st.isFile()) {
5905
3899
  throw new Error(`Not a file: ${relativePath}`);
@@ -5909,7 +3903,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
5909
3903
  `File too large to upload (${st.size} bytes; max ${maxBytes})`
5910
3904
  );
5911
3905
  }
5912
- const buf = readFileSync10(abs);
3906
+ const buf = readFileSync6(abs);
5913
3907
  return {
5914
3908
  path: relativePath,
5915
3909
  contentBase64: buf.toString("base64"),
@@ -5919,12 +3913,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
5919
3913
  function readWorktreeFile(worktreePath, relativePath, opts) {
5920
3914
  assertSafeRelativePath(relativePath);
5921
3915
  const maxBytes = opts?.maxBytes ?? 2e5;
5922
- const abs = join13(worktreePath, relativePath);
3916
+ const abs = join8(worktreePath, relativePath);
5923
3917
  const st = statSync2(abs);
5924
3918
  if (!st.isFile()) {
5925
3919
  throw new Error(`Not a file: ${relativePath}`);
5926
3920
  }
5927
- const buf = readFileSync10(abs);
3921
+ const buf = readFileSync6(abs);
5928
3922
  if (isImageRelativePath(relativePath)) {
5929
3923
  const maxImageBytes = Math.max(maxBytes, 15e6);
5930
3924
  const truncated2 = buf.length > maxImageBytes;
@@ -5967,9 +3961,9 @@ function assertSafeRelativePath(relativePath) {
5967
3961
  }
5968
3962
  function writeWorktreeFile(worktreePath, relativePath, content) {
5969
3963
  assertSafeRelativePath(relativePath);
5970
- const abs = join13(worktreePath, relativePath);
5971
- mkdirSync6(dirname5(abs), { recursive: true });
5972
- writeFileSync8(abs, content, "utf8");
3964
+ const abs = join8(worktreePath, relativePath);
3965
+ mkdirSync(dirname(abs), { recursive: true });
3966
+ writeFileSync5(abs, content, "utf8");
5973
3967
  return { path: relativePath };
5974
3968
  }
5975
3969
  async function getWorktreeDirtyStat(worktreePath) {
@@ -6191,9 +4185,9 @@ async function confirmLand(thread, opts) {
6191
4185
  }
6192
4186
 
6193
4187
  // src/skills/discover.ts
6194
- import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
4188
+ import { existsSync as existsSync10, readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
6195
4189
  import { homedir } from "os";
6196
- import { join as join14 } from "path";
4190
+ import { join as join9 } from "path";
6197
4191
 
6198
4192
  // src/skills/bundled/long-running.ts
6199
4193
  var LONG_RUNNING_SKILL_COMMAND = "long-running";
@@ -6330,7 +4324,7 @@ function parseFrontmatter(content) {
6330
4324
  }
6331
4325
  function readSkill(skillMd, source) {
6332
4326
  try {
6333
- const content = readFileSync11(skillMd, "utf8");
4327
+ const content = readFileSync7(skillMd, "utf8");
6334
4328
  const { name: fmName, description } = parseFrontmatter(content);
6335
4329
  const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
6336
4330
  const name = fmName || dirName;
@@ -6349,17 +4343,17 @@ function readSkill(skillMd, source) {
6349
4343
  }
6350
4344
  }
6351
4345
  function scanSkillsDir(dir, source, out) {
6352
- if (!existsSync14(dir)) return;
4346
+ if (!existsSync10(dir)) return;
6353
4347
  let entries;
6354
4348
  try {
6355
- entries = readdirSync5(dir);
4349
+ entries = readdirSync3(dir);
6356
4350
  } catch {
6357
4351
  return;
6358
4352
  }
6359
4353
  for (const entry of entries) {
6360
4354
  if (entry.startsWith(".")) continue;
6361
- const skillMd = join14(dir, entry, "SKILL.md");
6362
- if (!existsSync14(skillMd)) continue;
4355
+ const skillMd = join9(dir, entry, "SKILL.md");
4356
+ if (!existsSync10(skillMd)) continue;
6363
4357
  try {
6364
4358
  if (!statSync3(skillMd).isFile()) continue;
6365
4359
  } catch {
@@ -6370,22 +4364,22 @@ function scanSkillsDir(dir, source, out) {
6370
4364
  }
6371
4365
  }
6372
4366
  function scanClaudePluginSkills(pluginsRoot, out) {
6373
- if (!existsSync14(pluginsRoot)) return;
4367
+ if (!existsSync10(pluginsRoot)) return;
6374
4368
  const walk = (dir, depth, lookingForSkillsDir) => {
6375
4369
  if (depth > 7) return;
6376
4370
  let entries;
6377
4371
  try {
6378
- entries = readdirSync5(dir);
4372
+ entries = readdirSync3(dir);
6379
4373
  } catch {
6380
4374
  return;
6381
4375
  }
6382
4376
  if (lookingForSkillsDir && entries.includes("SKILL.md")) {
6383
- const skill = readSkill(join14(dir, "SKILL.md"), "cli");
4377
+ const skill = readSkill(join9(dir, "SKILL.md"), "cli");
6384
4378
  if (skill) out.push(skill);
6385
4379
  }
6386
4380
  for (const entry of entries) {
6387
4381
  if (entry === "node_modules" || entry === ".git") continue;
6388
- const full = join14(dir, entry);
4382
+ const full = join9(dir, entry);
6389
4383
  try {
6390
4384
  if (!statSync3(full).isDirectory()) continue;
6391
4385
  } catch {
@@ -6405,17 +4399,17 @@ function discoverSkills(worktreePath) {
6405
4399
  const home = homedir();
6406
4400
  const collected = [];
6407
4401
  for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
6408
- scanSkillsDir(join14(worktreePath, rel), "workspace", collected);
4402
+ scanSkillsDir(join9(worktreePath, rel), "workspace", collected);
6409
4403
  }
6410
4404
  for (const abs of [
6411
- join14(home, ".claude/skills"),
6412
- join14(home, ".cursor/skills"),
6413
- join14(home, ".sideboard/skills"),
6414
- join14(home, ".brightsy/skills")
4405
+ join9(home, ".claude/skills"),
4406
+ join9(home, ".cursor/skills"),
4407
+ join9(home, ".sideboard/skills"),
4408
+ join9(home, ".brightsy/skills")
6415
4409
  ]) {
6416
4410
  scanSkillsDir(abs, "user", collected);
6417
4411
  }
6418
- scanClaudePluginSkills(join14(home, ".claude/plugins"), collected);
4412
+ scanClaudePluginSkills(join9(home, ".claude/plugins"), collected);
6419
4413
  collected.push(...bundledSkills());
6420
4414
  const rank = {
6421
4415
  workspace: 0,
@@ -6441,7 +4435,7 @@ function readSkillBody(skillPath, maxChars = 12e3) {
6441
4435
  \u2026(truncated)` : body;
6442
4436
  }
6443
4437
  }
6444
- const raw = readFileSync11(skillPath, "utf8");
4438
+ const raw = readFileSync7(skillPath, "utf8");
6445
4439
  if (raw.startsWith("---")) {
6446
4440
  const end = raw.indexOf("\n---", 3);
6447
4441
  if (end >= 0) {
@@ -6586,12 +4580,12 @@ function shiftQueuedItem(queue, queueAttachments) {
6586
4580
  }
6587
4581
 
6588
4582
  // src/agents/instructions.ts
6589
- import { existsSync as existsSync16 } from "fs";
6590
- import { join as join16 } from "path";
4583
+ import { existsSync as existsSync12 } from "fs";
4584
+ import { join as join11 } from "path";
6591
4585
 
6592
4586
  // src/skills/detached-job-path.ts
6593
- import { existsSync as existsSync15 } from "fs";
6594
- import { dirname as dirname6, join as join15 } from "path";
4587
+ import { existsSync as existsSync11 } from "fs";
4588
+ import { dirname as dirname2, join as join10 } from "path";
6595
4589
  import { fileURLToPath } from "url";
6596
4590
  var DETACHED_JOB_FILENAMES = ["detached-job.cjs", "detached-job.js"];
6597
4591
  function compiledModuleDir() {
@@ -6600,7 +4594,7 @@ function compiledModuleDir() {
6600
4594
  try {
6601
4595
  const url = import.meta.url;
6602
4596
  if (typeof url === "string" && url.length > 0) {
6603
- return dirname6(fileURLToPath(url));
4597
+ return dirname2(fileURLToPath(url));
6604
4598
  }
6605
4599
  } catch {
6606
4600
  }
@@ -6608,8 +4602,8 @@ function compiledModuleDir() {
6608
4602
  }
6609
4603
  function detachedJobScriptIn(dir) {
6610
4604
  for (const name of DETACHED_JOB_FILENAMES) {
6611
- const script = join15(dir, "scripts", name);
6612
- if (existsSync15(script)) return script;
4605
+ const script = join10(dir, "scripts", name);
4606
+ if (existsSync11(script)) return script;
6613
4607
  }
6614
4608
  return null;
6615
4609
  }
@@ -6626,7 +4620,7 @@ function resolveDetachedJobScript() {
6626
4620
  for (let i = 0; i < 8; i++) {
6627
4621
  const candidate = detachedJobScriptIn(dir);
6628
4622
  if (candidate) return candidate;
6629
- const parent = dirname6(dir);
4623
+ const parent = dirname2(dir);
6630
4624
  if (parent === dir) break;
6631
4625
  dir = parent;
6632
4626
  }
@@ -6668,12 +4662,12 @@ function formatRenameBranchDirective(thread, opts) {
6668
4662
  function localhostPreviewUrl(port) {
6669
4663
  return `http://localhost:${port}`;
6670
4664
  }
6671
- function describeLiveDevRun(run2) {
6672
- const url = localhostPreviewUrl(run2.port);
6673
- if (run2.preview === "window") {
6674
- return `${run2.scriptName} Electron window (renderer ${url})`;
4665
+ function describeLiveDevRun(run) {
4666
+ const url = localhostPreviewUrl(run.port);
4667
+ if (run.preview === "window") {
4668
+ return `${run.scriptName} Electron window (renderer ${url})`;
6675
4669
  }
6676
- return `${run2.scriptName} ${url}`;
4670
+ return `${run.scriptName} ${url}`;
6677
4671
  }
6678
4672
  function formatDevAccessHint(runs) {
6679
4673
  const live = (runs ?? []).filter(
@@ -6691,7 +4685,7 @@ function formatDevAccessHint(runs) {
6691
4685
  const bits = live.map(describeLiveDevRun).join("; ");
6692
4686
  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.`;
6693
4687
  }
6694
- 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.";
4688
+ 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.";
6695
4689
  }
6696
4690
  function formatWorktreeDirective(thread, opts) {
6697
4691
  const worktree = normPath(thread.worktreePath);
@@ -6731,9 +4725,9 @@ function formatWorktreeDirective(thread, opts) {
6731
4725
  );
6732
4726
  } else {
6733
4727
  const slug = opts?.githubSlug ?? "<origin-owner/name>";
6734
- const resolve2 = opts?.githubSlug ? "" : " Resolve `<origin-owner/name>` with `git remote get-url origin` in this worktree.";
4728
+ const resolve = opts?.githubSlug ? "" : " Resolve `<origin-owner/name>` with `git remote get-url origin` in this worktree.";
6735
4729
  lines.push(
6736
- `- 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\`.`
4730
+ `- 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\`.`
6737
4731
  );
6738
4732
  }
6739
4733
  lines.push(
@@ -6779,17 +4773,17 @@ function formatIssueToolsDirective(opts) {
6779
4773
  ];
6780
4774
  if (opts.linear) {
6781
4775
  lines.push(
6782
- "- 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."
4776
+ "- 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."
6783
4777
  );
6784
4778
  }
6785
4779
  if (github) {
6786
4780
  lines.push(
6787
- "- 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`."
4781
+ "- 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`."
6788
4782
  );
6789
4783
  }
6790
4784
  if (opts.abletime) {
6791
4785
  lines.push(
6792
- "- 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)."
4786
+ "- 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)."
6793
4787
  );
6794
4788
  }
6795
4789
  lines.push(
@@ -6827,9 +4821,9 @@ function formatPrGateDirective() {
6827
4821
  }
6828
4822
  function formatProcessGuideDirective(opts) {
6829
4823
  const worktree = opts?.worktreePath?.trim();
6830
- const hasGraphSkill = !!worktree && existsSync16(join16(worktree, ".claude/skills/graph-engineering/SKILL.md"));
6831
- const hasReviewSkill = !!worktree && existsSync16(join16(worktree, ".claude/skills/review/SKILL.md"));
6832
- const hasInstallSkill = !!worktree && existsSync16(join16(worktree, ".claude/skills/worktree-install/SKILL.md"));
4824
+ const hasGraphSkill = !!worktree && existsSync12(join11(worktree, ".claude/skills/graph-engineering/SKILL.md"));
4825
+ const hasReviewSkill = !!worktree && existsSync12(join11(worktree, ".claude/skills/review/SKILL.md"));
4826
+ const hasInstallSkill = !!worktree && existsSync12(join11(worktree, ".claude/skills/worktree-install/SKILL.md"));
6833
4827
  return [
6834
4828
  "Process guides (recurring work only):",
6835
4829
  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,
@@ -6849,7 +4843,7 @@ function formatLongRunningDirective(opts) {
6849
4843
  "- 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.",
6850
4844
  "- 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.",
6851
4845
  "State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available).",
6852
- "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."
4846
+ "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."
6853
4847
  ].join("\n");
6854
4848
  }
6855
4849
  function formatLongRunningReminder(opts) {
@@ -6866,7 +4860,7 @@ function formatArtifactDirective() {
6866
4860
  ].join("\n");
6867
4861
  }
6868
4862
  function formatUiReminder() {
6869
- 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.";
4863
+ 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.";
6870
4864
  }
6871
4865
 
6872
4866
  // src/integrations/optional-services.ts
@@ -6993,9 +4987,9 @@ async function syncThreadBranchFromGit(threadId) {
6993
4987
  }
6994
4988
 
6995
4989
  // src/store/schedules.ts
6996
- import { randomUUID as randomUUID4 } from "crypto";
6997
- import { existsSync as existsSync17, readFileSync as readFileSync12 } from "fs";
6998
- import { join as join17 } from "path";
4990
+ import { randomUUID as randomUUID2 } from "crypto";
4991
+ import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
4992
+ import { join as join12 } from "path";
6999
4993
  import { Cron } from "croner";
7000
4994
  var DURATION_RE = /^(\d+)(s|m|h|d)$/i;
7001
4995
  var UNIT_MS = {
@@ -7005,7 +4999,7 @@ var UNIT_MS = {
7005
4999
  d: 864e5
7006
5000
  };
7007
5001
  function schedulesPath() {
7008
- return join17(appDataDir(), "schedules.json");
5002
+ return join12(appDataDir(), "schedules.json");
7009
5003
  }
7010
5004
  function parseDurationMs(every) {
7011
5005
  const m = every.trim().match(DURATION_RE);
@@ -7105,9 +5099,9 @@ function normalizeTask(raw) {
7105
5099
  }
7106
5100
  function readAll() {
7107
5101
  const path = schedulesPath();
7108
- if (!existsSync17(path)) return [];
5102
+ if (!existsSync13(path)) return [];
7109
5103
  try {
7110
- const parsed = JSON.parse(readFileSync12(path, "utf8"));
5104
+ const parsed = JSON.parse(readFileSync8(path, "utf8"));
7111
5105
  if (!Array.isArray(parsed)) return [];
7112
5106
  return parsed.map((row) => {
7113
5107
  try {
@@ -7132,7 +5126,7 @@ function buildTask(input, now) {
7132
5126
  const ts = nowIso(now);
7133
5127
  const agent = input.agent ? coerceOrchestratorAgent(input.agent) : null;
7134
5128
  return {
7135
- id: randomUUID4(),
5129
+ id: randomUUID2(),
7136
5130
  name: input.name?.trim() || defaultScheduleName(prompt),
7137
5131
  prompt,
7138
5132
  enabled: input.enabled !== false,
@@ -7243,7 +5237,7 @@ function formatScheduledPrompt(name, prompt) {
7243
5237
  ${prompt}`;
7244
5238
  }
7245
5239
  async function defaultDeps() {
7246
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-4JSG7BWC.js");
5240
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-MA3MHYIM.js");
7247
5241
  const orch = getOrchestrator2();
7248
5242
  return {
7249
5243
  findThread: (id) => findThreadByRef(id),
@@ -7301,14 +5295,14 @@ function armOne(schedule) {
7301
5295
  if (!schedule.enabled) return;
7302
5296
  const at = new Date(schedule.nextRunAt);
7303
5297
  if (Number.isNaN(at.getTime())) return;
7304
- const delay2 = Math.max(0, at.getTime() - Date.now());
7305
- if (delay2 <= 250) {
5298
+ const delay = Math.max(0, at.getTime() - Date.now());
5299
+ if (delay <= 250) {
7306
5300
  void fireSchedule(schedule.id).then(() => {
7307
5301
  if (isThisProcessDesktopHost()) armSchedules();
7308
5302
  });
7309
5303
  return;
7310
5304
  }
7311
- const capped = Math.min(delay2, SET_TIMEOUT_MAX);
5305
+ const capped = Math.min(delay, SET_TIMEOUT_MAX);
7312
5306
  const timer = setTimeout(() => {
7313
5307
  timers.delete(schedule.id);
7314
5308
  const live = getSchedule(schedule.id);
@@ -7376,8 +5370,8 @@ function writeLiveStatus(threadId, status, lastError) {
7376
5370
  }
7377
5371
  function collectActiveRunPorts(runs) {
7378
5372
  const ports = /* @__PURE__ */ new Set();
7379
- for (const run2 of runs) {
7380
- const list = run2.ports?.length ? run2.ports : run2.port != null ? [run2.port] : [];
5373
+ for (const run of runs) {
5374
+ const list = run.ports?.length ? run.ports : run.port != null ? [run.port] : [];
7381
5375
  for (const p of list) {
7382
5376
  if (Number.isFinite(p) && p > 0) ports.add(p);
7383
5377
  }
@@ -7495,7 +5489,7 @@ var Orchestrator = class {
7495
5489
  this.lightReconcileAt.set(repoPath, Date.now());
7496
5490
  for (const thread of listThreads()) {
7497
5491
  if (isGlobalThread(thread) || thread.repoPath !== repoPath) continue;
7498
- if (!existsSync18(thread.worktreePath)) {
5492
+ if (!existsSync14(thread.worktreePath)) {
7499
5493
  setStatus(thread.id, "broken", "Worktree missing on disk");
7500
5494
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
7501
5495
  }
@@ -7524,7 +5518,7 @@ var Orchestrator = class {
7524
5518
  }
7525
5519
  continue;
7526
5520
  }
7527
- if (!existsSync18(thread.worktreePath)) {
5521
+ if (!existsSync14(thread.worktreePath)) {
7528
5522
  setStatus(thread.id, "broken", "Worktree missing on disk");
7529
5523
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
7530
5524
  continue;
@@ -7561,7 +5555,7 @@ var Orchestrator = class {
7561
5555
  orphans: orphans.map((o) => ({ path: o.path, repoPath: o.repoPath }))
7562
5556
  });
7563
5557
  }
7564
- const { autoCleanupOrphansEnabled } = await import("./app-settings-MVZPM6WW.js");
5558
+ const { autoCleanupOrphansEnabled } = await import("./app-settings-RAC4PSSP.js");
7565
5559
  if (autoCleanupOrphansEnabled() && shouldRunWorktreeCleanup() && orphans.length > 0) {
7566
5560
  await cleanupOrphanWorktrees({ repoPaths });
7567
5561
  }
@@ -7692,8 +5686,8 @@ var Orchestrator = class {
7692
5686
  scheduleQuotaResume(threadId, resumeAt) {
7693
5687
  this.clearQuotaResumeTimer(threadId);
7694
5688
  updateThread(threadId, { quotaResumeAt: resumeAt.toISOString() });
7695
- const delay2 = Math.max(5e3, resumeAt.getTime() - Date.now());
7696
- const capped = Math.min(delay2, 2147483647);
5689
+ const delay = Math.max(5e3, resumeAt.getTime() - Date.now());
5690
+ const capped = Math.min(delay, 2147483647);
7697
5691
  const timer = setTimeout(() => {
7698
5692
  this.quotaResumeTimers.delete(threadId);
7699
5693
  void this.resumeAfterQuotaWait(threadId);
@@ -7950,7 +5944,7 @@ var Orchestrator = class {
7950
5944
  }
7951
5945
  await setup;
7952
5946
  if (skipSetup) return;
7953
- const { autoRunAfterSetupEnabled } = await import("./app-settings-MVZPM6WW.js");
5947
+ const { autoRunAfterSetupEnabled } = await import("./app-settings-RAC4PSSP.js");
7954
5948
  if (autoRunAfterSetupEnabled()) {
7955
5949
  try {
7956
5950
  await this.startDev(threadId);
@@ -8329,7 +6323,7 @@ var Orchestrator = class {
8329
6323
  }
8330
6324
  const isBrightsy = fresh.agent === "brightsy";
8331
6325
  const isOrchestration = isOrchestratorThread(fresh);
8332
- const { autoRenameBranchEnabled, getGithubGitAuthMode, gitBranchPrefixSetting } = await import("./app-settings-MVZPM6WW.js");
6326
+ const { autoRenameBranchEnabled, getGithubGitAuthMode, gitBranchPrefixSetting } = await import("./app-settings-RAC4PSSP.js");
8333
6327
  const gitAuthMode = getGithubGitAuthMode();
8334
6328
  const worktreeDirective = isBrightsy || isOrchestration ? null : formatWorktreeDirective(fresh, {
8335
6329
  githubSlug: await withTimeout(
@@ -8484,7 +6478,7 @@ var Orchestrator = class {
8484
6478
  if (!this.stoppedTurns.has(threadId) && this.requireThread(threadId).agent === "cursor" && exitCode !== 0 && !assistantText && parts.length === 0) {
8485
6479
  const sessionId = result.sessionId || this.requireThread(threadId).sessionId || "";
8486
6480
  if (sessionId) {
8487
- const { recoverFinishedCursorRun } = await import("./cursor-recover-FALSNEZN.js");
6481
+ const { recoverFinishedCursorRun } = await import("./cursor-recover-MB3T2K5T.js");
8488
6482
  for (let i = 0; i < 8; i++) {
8489
6483
  const recovered = recoverFinishedCursorRun({
8490
6484
  agentId: sessionId,
@@ -8881,7 +6875,7 @@ var Orchestrator = class {
8881
6875
  });
8882
6876
  }
8883
6877
  const latestShared = mergeWorktreeActiveRuns(threadsSharingWorktree(thread.worktreePath));
8884
- const run2 = {
6878
+ const run = {
8885
6879
  scriptName: resolvedName,
8886
6880
  port: handle.port,
8887
6881
  ports: handle.ports,
@@ -8890,7 +6884,7 @@ var Orchestrator = class {
8890
6884
  };
8891
6885
  const nextRuns = [
8892
6886
  ...latestShared.activeRuns.filter((r) => r.scriptName !== resolvedName),
8893
- run2
6887
+ run
8894
6888
  ];
8895
6889
  this.syncWorktreeRuns(
8896
6890
  thread.worktreePath,
@@ -8936,7 +6930,7 @@ var Orchestrator = class {
8936
6930
  const request = sameIntent ? pending : {
8937
6931
  op,
8938
6932
  scriptName: scriptName ?? null,
8939
- requestId: randomUUID5(),
6933
+ requestId: randomUUID3(),
8940
6934
  requestedAt: (/* @__PURE__ */ new Date()).toISOString()
8941
6935
  };
8942
6936
  if (!sameIntent) {
@@ -8957,14 +6951,14 @@ var Orchestrator = class {
8957
6951
  throw new Error(req.error);
8958
6952
  }
8959
6953
  const shared = mergeWorktreeActiveRuns(threadsSharingWorktree(latest.worktreePath));
8960
- const run2 = scriptName ? shared.activeRuns.find((r) => r.scriptName === scriptName) : shared.activeRuns[0];
6954
+ const run = scriptName ? shared.activeRuns.find((r) => r.scriptName === scriptName) : shared.activeRuns[0];
8961
6955
  const fulfilled = req?.requestId === request.requestId && Boolean(req.fulfilledAt);
8962
6956
  const requestGone = !req || req.requestId !== request.requestId;
8963
6957
  if (op === "start") {
8964
- if (run2 && (fulfilled || requestGone || run2.startedAt >= request.requestedAt)) {
8965
- return { port: run2.port, scriptName: run2.scriptName, ports: run2.ports };
6958
+ if (run && (fulfilled || requestGone || run.startedAt >= request.requestedAt)) {
6959
+ return { port: run.port, scriptName: run.scriptName, ports: run.ports };
8966
6960
  }
8967
- if (fulfilled && !run2) {
6961
+ if (fulfilled && !run) {
8968
6962
  throw new Error("Desktop reported the run script started, but it is not active.");
8969
6963
  }
8970
6964
  } else if (fulfilled) {
@@ -9030,15 +7024,15 @@ var Orchestrator = class {
9030
7024
  }
9031
7025
  this.processes.delete(legacyKey);
9032
7026
  }
9033
- const run2 = shared.activeRuns.find((r) => r.scriptName === scriptName);
9034
- if (!hadHandle2 && run2) {
9035
- if (typeof run2.pid === "number") {
9036
- killDetachedRunTree(run2.pid, thread.worktreePath);
7027
+ const run = shared.activeRuns.find((r) => r.scriptName === scriptName);
7028
+ if (!hadHandle2 && run) {
7029
+ if (typeof run.pid === "number") {
7030
+ killDetachedRunTree(run.pid, thread.worktreePath);
9037
7031
  }
9038
- killListenersOnPorts(collectActiveRunPorts([run2]));
7032
+ killListenersOnPorts(collectActiveRunPorts([run]));
9039
7033
  }
9040
7034
  const remaining = shared.activeRuns.filter((r) => r.scriptName !== scriptName);
9041
- const isPrimary = shared.devPort != null && run2?.port === shared.devPort;
7035
+ const isPrimary = shared.devPort != null && run?.port === shared.devPort;
9042
7036
  this.syncWorktreeRuns(
9043
7037
  thread.worktreePath,
9044
7038
  remaining,
@@ -9056,9 +7050,9 @@ var Orchestrator = class {
9056
7050
  }
9057
7051
  }
9058
7052
  if (!hadHandle && shared.activeRuns.length > 0) {
9059
- for (const run2 of shared.activeRuns) {
9060
- if (typeof run2.pid === "number") {
9061
- killDetachedRunTree(run2.pid, thread.worktreePath);
7053
+ for (const run of shared.activeRuns) {
7054
+ if (typeof run.pid === "number") {
7055
+ killDetachedRunTree(run.pid, thread.worktreePath);
9062
7056
  }
9063
7057
  }
9064
7058
  killListenersOnPorts(collectActiveRunPorts(shared.activeRuns));
@@ -9102,9 +7096,9 @@ var Orchestrator = class {
9102
7096
  (k) => isWorktreeRunProcessKey(k, thread.worktreePath) || siblings.some((t) => k.startsWith(`${t.id}:run:`) || k === `${t.id}:dev`)
9103
7097
  );
9104
7098
  if (!force && hasLiveHandle) continue;
9105
- for (const run2 of shared.activeRuns) {
9106
- if (typeof run2.pid === "number") {
9107
- killDetachedRunTree(run2.pid, thread.worktreePath);
7099
+ for (const run of shared.activeRuns) {
7100
+ if (typeof run.pid === "number") {
7101
+ killDetachedRunTree(run.pid, thread.worktreePath);
9108
7102
  }
9109
7103
  }
9110
7104
  killListenersOnPorts(collectActiveRunPorts(shared.activeRuns));
@@ -9313,7 +7307,7 @@ var Orchestrator = class {
9313
7307
  return thread;
9314
7308
  }
9315
7309
  const start = Date.now();
9316
- return new Promise((resolve2, reject) => {
7310
+ return new Promise((resolve, reject) => {
9317
7311
  let settled = false;
9318
7312
  let timer;
9319
7313
  const finish = (fn) => {
@@ -9331,7 +7325,7 @@ var Orchestrator = class {
9331
7325
  finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
9332
7326
  return;
9333
7327
  }
9334
- finish(() => resolve2(latest));
7328
+ finish(() => resolve(latest));
9335
7329
  return;
9336
7330
  }
9337
7331
  if (event.type === "status_changed") {
@@ -9341,7 +7335,7 @@ var Orchestrator = class {
9341
7335
  return;
9342
7336
  }
9343
7337
  if (!["running", "queued"].includes(latest.status)) {
9344
- finish(() => resolve2(latest));
7338
+ finish(() => resolve(latest));
9345
7339
  }
9346
7340
  }
9347
7341
  });
@@ -9353,12 +7347,12 @@ var Orchestrator = class {
9353
7347
  }
9354
7348
  const current = this.healStaleReportedActivity(raw);
9355
7349
  if (!this.threadLooksLive(current)) {
9356
- finish(() => resolve2(current));
7350
+ finish(() => resolve(current));
9357
7351
  return;
9358
7352
  }
9359
7353
  if (Date.now() - start > timeoutMs) {
9360
7354
  if (opts?.resolveIfStillRunning) {
9361
- finish(() => resolve2(current));
7355
+ finish(() => resolve(current));
9362
7356
  } else {
9363
7357
  finish(() => reject(new Error("wait_for_turn timed out")));
9364
7358
  }
@@ -9639,7 +7633,7 @@ var Orchestrator = class {
9639
7633
  if (Object.keys(patch).length === 0) continue;
9640
7634
  updateThread(t.id, patch);
9641
7635
  }
9642
- const { autoArchiveOnMergeEnabled } = await import("./app-settings-MVZPM6WW.js");
7636
+ const { autoArchiveOnMergeEnabled } = await import("./app-settings-RAC4PSSP.js");
9643
7637
  const latest = this.requireThread(thread.id);
9644
7638
  if (!shouldAutoArchiveOnPrMerge({
9645
7639
  previousPrState: prevState || null,
@@ -9930,7 +7924,7 @@ var Orchestrator = class {
9930
7924
  this.enforceHistoryRetention();
9931
7925
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
9932
7926
  try {
9933
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-WOQHJFRQ.js");
7927
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-SLP24Z3S.js");
9934
7928
  await ensureWorkspace2(thread.repoPath);
9935
7929
  } catch {
9936
7930
  }
@@ -9957,7 +7951,7 @@ var Orchestrator = class {
9957
7951
  await runArchiveScript(thread.repoPath, thread.worktreePath);
9958
7952
  } catch {
9959
7953
  }
9960
- const { deleteBranchOnPurgeEnabled } = await import("./app-settings-MVZPM6WW.js");
7954
+ const { deleteBranchOnPurgeEnabled } = await import("./app-settings-RAC4PSSP.js");
9961
7955
  const deleteBranch = opts?.deleteBranch ?? deleteBranchOnPurgeEnabled();
9962
7956
  await removeWorktree(thread.repoPath, thread.worktreePath, {
9963
7957
  deleteBranch: deleteBranch ? thread.branchName : void 0
@@ -9972,26 +7966,26 @@ var Orchestrator = class {
9972
7966
  throw new Error("Thread is not archived");
9973
7967
  }
9974
7968
  if (isGlobalThread(thread)) {
9975
- const { globalAgentCwd } = await import("./paths-IUR3ABNG.js");
7969
+ const { globalAgentCwd } = await import("./paths-HYT5WOOX.js");
9976
7970
  updateThread(thread.id, { worktreePath: globalAgentCwd() });
9977
7971
  const restored2 = setStatus(thread.id, "idle");
9978
7972
  this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
9979
7973
  return restored2;
9980
7974
  }
9981
- if (!existsSync18(thread.worktreePath)) {
7975
+ if (!existsSync14(thread.worktreePath)) {
9982
7976
  if (isCowboyThread(thread) || isPrimaryCheckoutThread(thread)) {
9983
7977
  throw new Error(
9984
7978
  `Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
9985
7979
  );
9986
7980
  }
9987
- const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-6U2FA5LO.js");
7981
+ const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-ACW23KJ5.js");
9988
7982
  const slug = thread.worktreePath.split("/").pop();
9989
7983
  const dest = thread.worktreePath;
9990
7984
  await withRepoGitLock(thread.repoPath, async () => {
9991
7985
  await git(["worktree", "add", dest, thread.branchName], thread.repoPath);
9992
7986
  });
9993
- const { ensureWorktreeSideboardIgnored: ensureWorktreeSideboardIgnored2 } = await import("./worktree-exclude-5N2KTYIT.js");
9994
- await ensureWorktreeSideboardIgnored2(dest);
7987
+ const { ensureWorktreeSideboardIgnored } = await import("./worktree-exclude-5N2KTYIT.js");
7988
+ await ensureWorktreeSideboardIgnored(dest);
9995
7989
  void createThreadWorktree2;
9996
7990
  void slug;
9997
7991
  }
@@ -10094,7 +8088,7 @@ async function startOrchestration(opts) {
10094
8088
  sourceRef: "default",
10095
8089
  ...createOpts
10096
8090
  }).catch(async () => {
10097
- const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-6U2FA5LO.js");
8091
+ const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-ACW23KJ5.js");
10098
8092
  const repo = await resolveRepoRoot2(repoPath);
10099
8093
  const def = await resolveDefaultBranch2(repo);
10100
8094
  return orch.createThread({
@@ -10104,7 +8098,7 @@ async function startOrchestration(opts) {
10104
8098
  repoPath: repo
10105
8099
  });
10106
8100
  });
10107
- const { updateThread: upd } = await import("./thread-store-FJ5CDX2M.js");
8101
+ const { updateThread: upd } = await import("./thread-store-B3ZM4QEZ.js");
10108
8102
  const updated = upd(thread.id, {
10109
8103
  sourceType: "orchestration",
10110
8104
  sourceRef: goal
@@ -10117,15 +8111,7 @@ async function startOrchestration(opts) {
10117
8111
 
10118
8112
  export {
10119
8113
  isInjectedNoticeText,
10120
- needsCoordinatorAction,
10121
- mcpWaitForTurnTimeoutMs,
10122
- mcpWaitTaskHint,
10123
- waitForTurnToolResult,
10124
8114
  AGENT_GIT_ACTIONS,
10125
- threadsSharingWorktree,
10126
- mcpWaitForJobTimeoutMs,
10127
- waitForDetachedJob,
10128
- stopDetachedJob,
10129
8115
  MAX_RUN_LOG_CHARS,
10130
8116
  DEFAULT_RUN_LOG_TAIL_CHARS,
10131
8117
  sliceRunLogOutput,