@sideboard-ai/core 0.1.135 → 0.1.139

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 (35) hide show
  1. package/dist/{agents-RTQFF7PY.js → agents-665S7Z3R.js} +5 -5
  2. package/dist/{agents-O3AJMI2Y.js → agents-OC3XM7UE.js} +5 -5
  3. package/dist/{chunk-KBBXNS2V.js → chunk-23KCPND2.js} +49 -6
  4. package/dist/{chunk-2ESCEK2Q.js → chunk-5XH5M6RA.js} +244 -5
  5. package/dist/{chunk-57GIFU3X.js → chunk-7SYFWPOZ.js} +5 -5
  6. package/dist/{chunk-N62K3KXX.js → chunk-CDJISVKN.js} +79 -6
  7. package/dist/{chunk-QAV3HGVS.js → chunk-DJJ3DTT4.js} +1 -1
  8. package/dist/{chunk-HQQNLDVC.js → chunk-DKXZCIB2.js} +1 -1
  9. package/dist/{chunk-XUEI4GCF.js → chunk-E2RE7P2S.js} +286 -5
  10. package/dist/{chunk-J5IBSVB3.js → chunk-EUXOHTUK.js} +42 -25
  11. package/dist/{chunk-RDULVW3E.js → chunk-HLIHBGVO.js} +2 -2
  12. package/dist/{chunk-K7EX47QG.js → chunk-RTX3AY42.js} +5 -5
  13. package/dist/{chunk-LOKXPQ4U.js → chunk-SSBM4GZX.js} +2 -2
  14. package/dist/{chunk-PM3C2J6K.js → chunk-TUGKX5BD.js} +42 -25
  15. package/dist/{chunk-Z5LYMW7M.js → chunk-YZQEJOAU.js} +189 -248
  16. package/dist/{chunk-XIKEUCNC.js → chunk-ZCLHAOFR.js} +211 -299
  17. package/dist/{coordinator-prompt-FYMWE33S.js → coordinator-prompt-43O6EPA2.js} +3 -3
  18. package/dist/{coordinator-prompt-Y737IIFR.js → coordinator-prompt-4TXU6Q3R.js} +3 -3
  19. package/dist/{global-workspace-6KH6BSKL.js → global-workspace-VIU57E3Y.js} +4 -4
  20. package/dist/{global-workspace-ZFKNLBZA.js → global-workspace-ZDYKHQ4O.js} +4 -4
  21. package/dist/index.cjs +1019 -670
  22. package/dist/index.d.cts +41 -3
  23. package/dist/index.d.ts +41 -3
  24. package/dist/index.js +83 -24
  25. package/dist/mcp/run-stdio.cjs +831 -544
  26. package/dist/mcp/run-stdio.js +55 -16
  27. package/dist/{orchestrator-3YDPPZHZ.js → orchestrator-WEFXUHFX.js} +7 -7
  28. package/dist/{orchestrator-2BRAPJ47.js → orchestrator-ZCMYSDTC.js} +7 -7
  29. package/dist/{thread-store-FADXSMEJ.js → thread-store-57ZLHR3A.js} +1 -1
  30. package/dist/{thread-store-CRLQJ2HM.js → thread-store-WGASXXXR.js} +1 -1
  31. package/dist/{workspaces-3RRF3LVF.js → workspaces-4HYGNH4B.js} +5 -5
  32. package/dist/{workspaces-AYI5DHK4.js → workspaces-ALCTB65T.js} +5 -5
  33. package/dist/{worktree-MX7XBX6Z.js → worktree-GXD2NGOZ.js} +2 -2
  34. package/dist/{worktree-BC6XDMQK.js → worktree-NSNZODAM.js} +2 -2
  35. package/package.json +1 -1
@@ -18,15 +18,16 @@ import {
18
18
  stripBrightsyNdjsonNoise,
19
19
  sumUsageList,
20
20
  toolDescription
21
- } from "./chunk-N62K3KXX.js";
21
+ } from "./chunk-CDJISVKN.js";
22
22
  import {
23
23
  addWorkspace,
24
24
  ensureWorkspace,
25
25
  removeWorkspace,
26
26
  syncWorkspacesFromThreads
27
- } from "./chunk-LOKXPQ4U.js";
27
+ } from "./chunk-SSBM4GZX.js";
28
28
  import {
29
29
  assertOrchestratorCapableAgent,
30
+ attachmentsFromWorktreePaths,
30
31
  coerceOrchestratorAgent,
31
32
  createGlobalChat,
32
33
  healOrchestrationSoccerTitles,
@@ -36,15 +37,18 @@ import {
36
37
  isOrchestratorCapableAgent,
37
38
  isOrchestratorThread,
38
39
  isSlackCoordinatorThread,
39
- orchestratorSessionPoisonedByBuiltins
40
- } from "./chunk-XUEI4GCF.js";
40
+ orchestratorSessionPoisonedByBuiltins,
41
+ persistPendingFileAttachments,
42
+ stageAbsolutePathsAsAttachments,
43
+ stageBuffersAsAttachments
44
+ } from "./chunk-E2RE7P2S.js";
41
45
  import {
42
46
  SLACK_REPLY_FORMATTING,
43
47
  coordinatorSystemPrompt,
44
48
  coordinatorTurnReminder,
45
49
  enrichWorkspacesWithGithub,
46
50
  ensureGlobalCoordinatorCwd
47
- } from "./chunk-57GIFU3X.js";
51
+ } from "./chunk-7SYFWPOZ.js";
48
52
  import {
49
53
  extractPresentedPlan,
50
54
  readPlanFile,
@@ -114,11 +118,10 @@ import {
114
118
  withRepoGitLock,
115
119
  worktreeDisplayLabelForGroup,
116
120
  worktreeNameFromPath
117
- } from "./chunk-HQQNLDVC.js";
121
+ } from "./chunk-DKXZCIB2.js";
118
122
  import {
119
123
  ATTACHMENTS_DIR,
120
- LEGACY_ATTACHMENTS_DIR,
121
- attachmentsGitignoreBody
124
+ LEGACY_ATTACHMENTS_DIR
122
125
  } from "./chunk-FKOIHGKV.js";
123
126
  import {
124
127
  childEnvWithAppSettings,
@@ -152,7 +155,7 @@ import {
152
155
  updateThread,
153
156
  withThreadLock,
154
157
  writeThread
155
- } from "./chunk-PM3C2J6K.js";
158
+ } from "./chunk-TUGKX5BD.js";
156
159
  import {
157
160
  appDataDir,
158
161
  findConventionSetup,
@@ -445,7 +448,7 @@ async function continueSourceThread(threadId, prompt) {
445
448
  await continueOnReply(threadId, prompt);
446
449
  return;
447
450
  }
448
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-3YDPPZHZ.js");
451
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-WEFXUHFX.js");
449
452
  await getOrchestrator2().send(threadId, prompt);
450
453
  } catch {
451
454
  }
@@ -705,7 +708,7 @@ async function pollSlackOutboundWatches(opts) {
705
708
  }
706
709
 
707
710
  // src/orchestrator/orchestrator.ts
708
- import { existsSync as existsSync16 } from "fs";
711
+ import { existsSync as existsSync15 } from "fs";
709
712
 
710
713
  // src/agents/spawn.ts
711
714
  import { createInterface } from "readline";
@@ -725,9 +728,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
725
728
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
726
729
  );
727
730
  }
728
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-ZFKNLBZA.js");
731
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-ZDYKHQ4O.js");
729
732
  if (isGlobalThread2(thread)) {
730
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-FYMWE33S.js");
733
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-43O6EPA2.js");
731
734
  ensureGlobalCoordinatorCwd2(
732
735
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
733
736
  );
@@ -974,6 +977,60 @@ import { dirname, join as join4 } from "path";
974
977
  // src/threads/chat-tabs.ts
975
978
  import { randomUUID } from "crypto";
976
979
 
980
+ // src/composer/context-estimate.ts
981
+ var CHARS_PER_CONTEXT_TOKEN = 4;
982
+ function estimateMessageChars(message) {
983
+ let n = message.text.length + 16;
984
+ for (const part of message.parts ?? []) {
985
+ n += estimatePartChars(part);
986
+ }
987
+ return n;
988
+ }
989
+ function estimatePartChars(part) {
990
+ switch (part.type) {
991
+ case "text":
992
+ case "thinking":
993
+ return part.text.length;
994
+ case "tool": {
995
+ const input = part.input ? JSON.stringify(part.input) : "";
996
+ return part.name.length + (part.description?.length ?? 0) + (part.detail?.length ?? 0) + (part.result?.length ?? 0) + input.length + 32;
997
+ }
998
+ default:
999
+ return 0;
1000
+ }
1001
+ }
1002
+ function estimateThreadChars(messages) {
1003
+ return messages.reduce((sum, m) => sum + estimateMessageChars(m), 0);
1004
+ }
1005
+ function estimateOccupancyTokens(messages) {
1006
+ return Math.ceil(estimateThreadChars(messages) / CHARS_PER_CONTEXT_TOKEN);
1007
+ }
1008
+ function threadHasCompactedContext(messages) {
1009
+ return messages.some((m) => m.role === "summary");
1010
+ }
1011
+ function forwardContextUsage(usage, messages) {
1012
+ if (!usage) return null;
1013
+ if (!threadHasCompactedContext(messages)) return usage;
1014
+ const remaining = estimateOccupancyTokens(messages);
1015
+ const current = contextTokens(usage);
1016
+ if (remaining <= 0 || remaining >= current) return usage;
1017
+ return { ...usage, lastRequestTokens: remaining };
1018
+ }
1019
+ function applyForwardOccupancy(messages) {
1020
+ const tokens = estimateOccupancyTokens(messages);
1021
+ if (tokens <= 0) return messages;
1022
+ for (let i = messages.length - 1; i >= 0; i--) {
1023
+ const m = messages[i];
1024
+ if (m?.role !== "agent" || !m.usage) continue;
1025
+ const current = contextTokens(m.usage);
1026
+ if (tokens >= current) return messages;
1027
+ const next = messages.slice();
1028
+ next[i] = { ...m, usage: { ...m.usage, lastRequestTokens: tokens } };
1029
+ return next;
1030
+ }
1031
+ return messages;
1032
+ }
1033
+
977
1034
  // src/composer/summarize.ts
978
1035
  var MAX_TRANSCRIPT_CHARS = 12e4;
979
1036
  var MAX_SUMMARY_CHARS = 6e3;
@@ -1082,29 +1139,6 @@ var CONTEXT_KEEP_RECENT_CHARS = 24e3;
1082
1139
  var CONTEXT_KEEP_RECENT_MESSAGES = 12;
1083
1140
  var CONTEXT_MIN_MESSAGES = 10;
1084
1141
  var SESSION_RESET_OCCUPANCY_TOKENS = 75e4;
1085
- function estimateMessageChars(message) {
1086
- let n = message.text.length + 16;
1087
- for (const part of message.parts ?? []) {
1088
- n += estimatePartChars(part);
1089
- }
1090
- return n;
1091
- }
1092
- function estimatePartChars(part) {
1093
- switch (part.type) {
1094
- case "text":
1095
- case "thinking":
1096
- return part.text.length;
1097
- case "tool": {
1098
- const input = part.input ? JSON.stringify(part.input) : "";
1099
- return part.name.length + (part.description?.length ?? 0) + (part.detail?.length ?? 0) + (part.result?.length ?? 0) + input.length + 32;
1100
- }
1101
- default:
1102
- return 0;
1103
- }
1104
- }
1105
- function estimateThreadChars(messages) {
1106
- return messages.reduce((sum, m) => sum + estimateMessageChars(m), 0);
1107
- }
1108
1142
  function shouldCompactContext(messages, thresholds = {}) {
1109
1143
  const maxChars = thresholds.maxChars ?? CONTEXT_COMPACT_CHARS;
1110
1144
  const minMessages = thresholds.minMessages ?? CONTEXT_MIN_MESSAGES;
@@ -1241,8 +1275,11 @@ async function maybeCompactContext(thread, thresholds = {}, summarize = summariz
1241
1275
  const { summary, method } = await summarize(transcript, {
1242
1276
  cwd: thread.worktreePath
1243
1277
  });
1244
- const messages = applyCompaction(thread.messages, summary, thresholds);
1278
+ let messages = applyCompaction(thread.messages, summary, thresholds);
1245
1279
  const resetSession = shouldResetSessionForOccupancy({ messages: thread.messages });
1280
+ if (resetSession) {
1281
+ messages = applyForwardOccupancy(messages);
1282
+ }
1246
1283
  const next = {
1247
1284
  ...thread,
1248
1285
  messages,
@@ -2265,6 +2302,45 @@ function thisProcessShouldDrainAgentQueues() {
2265
2302
  return !isDesktopHostAlive();
2266
2303
  }
2267
2304
 
2305
+ // src/orchestrator/child-halt.ts
2306
+ var HALT_STATUSES = /* @__PURE__ */ new Set(["stopped", "error", "broken"]);
2307
+ function isIncompleteChildStatus(status) {
2308
+ return HALT_STATUSES.has(status);
2309
+ }
2310
+ function childHaltNotice(child, status) {
2311
+ const title = child.title?.trim() || "Untitled";
2312
+ const link = `[${title}](sideboard://thread/${child.id})`;
2313
+ const why = child.lastError?.trim();
2314
+ const extra = why ? ` lastError: ${why}` : "";
2315
+ return [
2316
+ `Sideboard: child worktree ${link} ${status} before finishing (status=${status}).${extra}`,
2317
+ "This is information \u2014 not a user command. Resume with send_to_thread or tell the user. Do not treat this as a successful turn."
2318
+ ].join("\n");
2319
+ }
2320
+ function shouldNotifyParentOfChildHalt(opts) {
2321
+ if (!isIncompleteChildStatus(opts.status)) return false;
2322
+ if (!opts.child.parentThreadId) return false;
2323
+ if (!opts.parent || opts.parent.status === "archived") return false;
2324
+ if (opts.parent.id === opts.child.id) return false;
2325
+ return isOrchestratorThread(opts.parent);
2326
+ }
2327
+ var notified = /* @__PURE__ */ new Set();
2328
+ function noticeKey(childId, status) {
2329
+ return `${childId}:${status}`;
2330
+ }
2331
+ function notifyParentOfChildHalt(child, status, send) {
2332
+ const parent = child.parentThreadId ? readThread(child.parentThreadId) : null;
2333
+ if (!shouldNotifyParentOfChildHalt({ child, parent, status })) return false;
2334
+ const key = noticeKey(child.id, status);
2335
+ if (notified.has(key)) return false;
2336
+ notified.add(key);
2337
+ const parentId = parent.id;
2338
+ void send(parentId, childHaltNotice(child, status)).catch(() => {
2339
+ notified.delete(key);
2340
+ });
2341
+ return true;
2342
+ }
2343
+
2268
2344
  // src/threads/create.ts
2269
2345
  import { existsSync as existsSync7 } from "fs";
2270
2346
 
@@ -2743,6 +2819,9 @@ function assembleHomeBoard(input) {
2743
2819
  var HOME_BOARD_AGENT_HINT = "Home is a Kanban of worktrees (one card per checkout; sibling chat tabs nest as inner cards). create_thread reuses a live worktree for the same ticket, PR, or named branch \u2014 do not recreate it. Columns are the path to merge: New (no PR) \u2192 Draft (draft PR) \u2192 Review (open PR) \u2192 Merged. Archive removes the card to Settings \u2192 History. Queued/running are activity on the card, not columns. Orchestration chats stay in the sidebar. Do not invent status.";
2744
2820
 
2745
2821
  // src/threads/create.ts
2822
+ function persistCreateAttachments(worktreePath, attachments) {
2823
+ return persistPendingFileAttachments(worktreePath, attachments ?? []);
2824
+ }
2746
2825
  async function createThread(input, _onSetupLine) {
2747
2826
  const repoPath = await resolveRepoRoot(input.repoPath);
2748
2827
  if (!existsSync7(repoPath)) {
@@ -2762,7 +2841,16 @@ async function createThread(input, _onSetupLine) {
2762
2841
  repoPath: canonicalizeRepoPath(t.repoPath)
2763
2842
  }))
2764
2843
  );
2765
- if (existing) return readThread(existing.id) ?? existing;
2844
+ if (existing) {
2845
+ const thread2 = readThread(existing.id) ?? existing;
2846
+ if (!input.attachments?.length) return thread2;
2847
+ return updateThread(thread2.id, {
2848
+ attachments: persistCreateAttachments(thread2.worktreePath, [
2849
+ ...thread2.attachments,
2850
+ ...input.attachments
2851
+ ])
2852
+ });
2853
+ }
2766
2854
  }
2767
2855
  const resolved = resolveNewThreadOptions({
2768
2856
  agent: input.agent,
@@ -2810,7 +2898,7 @@ async function createThread(input, _onSetupLine) {
2810
2898
  effort: resolved.effort,
2811
2899
  fast: resolved.fast,
2812
2900
  planMode: Boolean(input.planMode),
2813
- attachments: input.attachments ?? [],
2901
+ attachments: persistCreateAttachments(repoPath, input.attachments),
2814
2902
  sourceIsFork: false,
2815
2903
  parentThreadId: input.parentThreadId ?? null,
2816
2904
  status: "idle",
@@ -2889,7 +2977,7 @@ async function createThread(input, _onSetupLine) {
2889
2977
  effort: resolved.effort,
2890
2978
  fast: resolved.fast,
2891
2979
  planMode: Boolean(input.planMode),
2892
- attachments,
2980
+ attachments: persistCreateAttachments(worktreePath, attachments),
2893
2981
  sourceIsFork,
2894
2982
  parentThreadId: input.parentThreadId ?? null,
2895
2983
  status: "idle",
@@ -2901,7 +2989,7 @@ async function createThread(input, _onSetupLine) {
2901
2989
  return readThread(thread.id) ?? thread;
2902
2990
  }
2903
2991
  async function listLinearIssues(agent, repoPath) {
2904
- const { getAdapter: getAdapter2 } = await import("./agents-RTQFF7PY.js");
2992
+ const { getAdapter: getAdapter2 } = await import("./agents-665S7Z3R.js");
2905
2993
  await requireAgent(agent, { requireLinear: true });
2906
2994
  const adapter = getAdapter2(agent);
2907
2995
  if (!adapter.listLinearIssues) {
@@ -3298,7 +3386,7 @@ async function adoptThread(input) {
3298
3386
  messages: input.messages ?? []
3299
3387
  });
3300
3388
  writeThread(thread);
3301
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-AYI5DHK4.js");
3389
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-ALCTB65T.js");
3302
3390
  await ensureWorkspace2(repoPath);
3303
3391
  return thread;
3304
3392
  }
@@ -4861,232 +4949,9 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
4861
4949
  };
4862
4950
  }
4863
4951
 
4864
- // src/composer/stage-files.ts
4865
- import { copyFileSync as copyFileSync3, existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync10, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
4866
- import { basename as basename3, extname, join as join12 } from "path";
4867
- import { randomUUID as randomUUID4 } from "crypto";
4868
- var IMAGE_EXTENSIONS2 = /* @__PURE__ */ new Set([
4869
- "png",
4870
- "jpg",
4871
- "jpeg",
4872
- "gif",
4873
- "webp",
4874
- "svg",
4875
- "bmp",
4876
- "ico"
4877
- ]);
4878
- var IMAGE_MIME_BY_EXT = {
4879
- png: "image/png",
4880
- jpg: "image/jpeg",
4881
- jpeg: "image/jpeg",
4882
- gif: "image/gif",
4883
- webp: "image/webp",
4884
- svg: "image/svg+xml",
4885
- bmp: "image/bmp",
4886
- ico: "image/x-icon"
4887
- };
4888
- var MAX_INLINE_BYTES = 4e5;
4889
- var MAX_PREVIEW_BYTES = 5e6;
4890
- function fileExtension(filePath) {
4891
- const base = basename3(filePath).toLowerCase();
4892
- return base.includes(".") ? base.split(".").pop() || "" : "";
4893
- }
4894
- function isImageFilePath(filePath) {
4895
- return IMAGE_EXTENSIONS2.has(fileExtension(filePath));
4896
- }
4897
- function imageMimeType(filePath) {
4898
- return IMAGE_MIME_BY_EXT[fileExtension(filePath)] || "image/png";
4899
- }
4900
- function ensureAttachmentsDir(worktreePath) {
4901
- const dir = join12(worktreePath, ATTACHMENTS_DIR);
4902
- mkdirSync4(dir, { recursive: true });
4903
- const gi = join12(dir, ".gitignore");
4904
- if (!existsSync13(gi)) {
4905
- writeFileSync5(gi, attachmentsGitignoreBody(), "utf8");
4906
- }
4907
- return dir;
4908
- }
4909
- function uniqueAttachmentName(dir, originalName) {
4910
- const safe = originalName.replace(/[/\\]/g, "_") || "file";
4911
- if (!existsSync13(join12(dir, safe))) return safe;
4912
- const ext = extname(safe);
4913
- const stem = ext ? safe.slice(0, -ext.length) : safe;
4914
- for (let i = 1; i < 1e4; i++) {
4915
- const candidate = `${stem}-${i}${ext}`;
4916
- if (!existsSync13(join12(dir, candidate))) return candidate;
4917
- }
4918
- return `${stem}-${randomUUID4()}${ext}`;
4919
- }
4920
- function previewDataUrlFromBuf(filePath, buf) {
4921
- if (!isImageFilePath(filePath)) return void 0;
4922
- if (buf.length > MAX_PREVIEW_BYTES) return void 0;
4923
- return `data:${imageMimeType(filePath)};base64,${buf.toString("base64")}`;
4924
- }
4925
- function attachmentFromBuffer(name, buf, opts) {
4926
- const previewDataUrl = previewDataUrlFromBuf(name, buf);
4927
- if (isImageFilePath(name)) {
4928
- const pathHint = opts.path ? `\`${opts.path}\`` : opts.sourceLabel || name;
4929
- return {
4930
- id: randomUUID4(),
4931
- name,
4932
- kind: "file",
4933
- path: opts.path,
4934
- previewDataUrl,
4935
- content: [
4936
- `Image attached: ${pathHint}`,
4937
- opts.path ? `Use the Read tool on \`${opts.path}\` to view this image.` : "The image is shown in the composer; copy it into the worktree if you need to inspect pixels."
4938
- ].join("\n")
4939
- };
4940
- }
4941
- if (buf.length > MAX_INLINE_BYTES) {
4942
- return {
4943
- id: randomUUID4(),
4944
- name,
4945
- kind: "file",
4946
- path: opts.path,
4947
- content: opts.path ? `(file too large to attach inline: \`${opts.path}\`, ${buf.length} bytes \u2014 use the Read tool)` : `(file too large to attach inline: ${opts.sourceLabel || name}, ${buf.length} bytes)`
4948
- };
4949
- }
4950
- if (buf.includes(0)) {
4951
- return {
4952
- id: randomUUID4(),
4953
- name,
4954
- kind: "file",
4955
- path: opts.path,
4956
- content: opts.path ? `(binary file at \`${opts.path}\` \u2014 use tools to inspect)` : `(binary file attached by path only: ${opts.sourceLabel || name})`
4957
- };
4958
- }
4959
- return {
4960
- id: randomUUID4(),
4961
- name,
4962
- kind: "file",
4963
- path: opts.path,
4964
- content: buf.toString("utf8")
4965
- };
4966
- }
4967
- function attachmentFromAbsolutePath(absolutePath) {
4968
- const name = basename3(absolutePath);
4969
- try {
4970
- const st = statSync4(absolutePath);
4971
- if (!st.isFile()) {
4972
- return {
4973
- id: randomUUID4(),
4974
- name,
4975
- kind: "file",
4976
- content: `(not a file: ${absolutePath})`
4977
- };
4978
- }
4979
- const buf = readFileSync10(absolutePath);
4980
- return attachmentFromBuffer(name, buf, { sourceLabel: absolutePath });
4981
- } catch (err) {
4982
- return {
4983
- id: randomUUID4(),
4984
- name,
4985
- kind: "file",
4986
- content: `(could not read ${absolutePath}: ${err instanceof Error ? err.message : String(err)})`
4987
- };
4988
- }
4989
- }
4990
- function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
4991
- if (absolutePaths.length === 0) return [];
4992
- const dir = ensureAttachmentsDir(worktreePath);
4993
- const out = [];
4994
- for (const abs of absolutePaths) {
4995
- const originalName = basename3(abs);
4996
- try {
4997
- const st = statSync4(abs);
4998
- if (!st.isFile()) continue;
4999
- const name = uniqueAttachmentName(dir, originalName);
5000
- const destAbs = join12(dir, name);
5001
- copyFileSync3(abs, destAbs);
5002
- const rel = `${ATTACHMENTS_DIR}/${name}`;
5003
- const buf = readFileSync10(destAbs);
5004
- out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
5005
- } catch (err) {
5006
- out.push({
5007
- id: randomUUID4(),
5008
- name: originalName,
5009
- kind: "file",
5010
- content: `(could not attach ${abs}: ${err instanceof Error ? err.message : String(err)})`
5011
- });
5012
- }
5013
- }
5014
- return out;
5015
- }
5016
- function stageBuffersAsAttachments(worktreePath, buffers2) {
5017
- if (buffers2.length === 0) return [];
5018
- const dir = ensureAttachmentsDir(worktreePath);
5019
- const out = [];
5020
- for (const item of buffers2) {
5021
- const originalName = (item.name || "file").replace(/[/\\]/g, "_") || "file";
5022
- try {
5023
- const buf = Buffer.from(item.dataBase64, "base64");
5024
- const name = uniqueAttachmentName(dir, originalName);
5025
- const destAbs = join12(dir, name);
5026
- writeFileSync5(destAbs, buf);
5027
- const rel = `${ATTACHMENTS_DIR}/${name}`;
5028
- out.push(attachmentFromBuffer(name, buf, { path: rel }));
5029
- } catch (err) {
5030
- out.push({
5031
- id: randomUUID4(),
5032
- name: originalName,
5033
- kind: "file",
5034
- content: `(could not attach ${originalName}: ${err instanceof Error ? err.message : String(err)})`
5035
- });
5036
- }
5037
- }
5038
- return out;
5039
- }
5040
- function attachmentsFromBuffers(buffers2) {
5041
- return buffers2.map((item) => {
5042
- const name = (item.name || "file").replace(/[/\\]/g, "_") || "file";
5043
- try {
5044
- const buf = Buffer.from(item.dataBase64, "base64");
5045
- return attachmentFromBuffer(name, buf, { sourceLabel: name });
5046
- } catch (err) {
5047
- return {
5048
- id: randomUUID4(),
5049
- name,
5050
- kind: "file",
5051
- content: `(could not attach ${name}: ${err instanceof Error ? err.message : String(err)})`
5052
- };
5053
- }
5054
- });
5055
- }
5056
- function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
5057
- const out = [];
5058
- for (const rel of relativePaths) {
5059
- if (!rel || rel.includes("..") || rel.startsWith("/")) {
5060
- out.push({
5061
- id: randomUUID4(),
5062
- name: basename3(rel) || "file",
5063
- kind: "file",
5064
- content: `(invalid path: ${rel})`
5065
- });
5066
- continue;
5067
- }
5068
- const name = basename3(rel);
5069
- try {
5070
- const abs = join12(worktreePath, rel);
5071
- const st = statSync4(abs);
5072
- if (!st.isFile()) continue;
5073
- const buf = readFileSync10(abs);
5074
- out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
5075
- } catch (err) {
5076
- out.push({
5077
- id: randomUUID4(),
5078
- name,
5079
- kind: "file",
5080
- content: `(could not read ${rel}: ${err instanceof Error ? err.message : String(err)})`
5081
- });
5082
- }
5083
- }
5084
- return out;
5085
- }
5086
-
5087
4952
  // src/agents/instructions.ts
5088
- import { existsSync as existsSync14, readFileSync as readFileSync11, statSync as statSync5 } from "fs";
5089
- import { join as join13 } from "path";
4953
+ import { existsSync as existsSync13, readFileSync as readFileSync10, statSync as statSync4 } from "fs";
4954
+ import { join as join12 } from "path";
5090
4955
  function normPath(p) {
5091
4956
  return p.replace(/\/+$/, "");
5092
4957
  }
@@ -5250,11 +5115,11 @@ function loadAgentInstructions(worktreePath, agent) {
5250
5115
  const out = [];
5251
5116
  for (const rel of candidates) {
5252
5117
  if (seenPaths.has(rel)) continue;
5253
- const abs = join13(worktreePath, rel);
5254
- if (!existsSync14(abs)) continue;
5118
+ const abs = join12(worktreePath, rel);
5119
+ if (!existsSync13(abs)) continue;
5255
5120
  try {
5256
- if (!statSync5(abs).isFile()) continue;
5257
- let content = readFileSync11(abs, "utf8");
5121
+ if (!statSync4(abs).isFile()) continue;
5122
+ let content = readFileSync10(abs, "utf8");
5258
5123
  if (!content.trim()) continue;
5259
5124
  if (content.length > MAX_CHARS_PER_FILE) {
5260
5125
  content = `${content.slice(0, MAX_CHARS_PER_FILE)}
@@ -5326,9 +5191,9 @@ async function syncThreadBranchFromGit(threadId) {
5326
5191
  }
5327
5192
 
5328
5193
  // src/store/schedules.ts
5329
- import { randomUUID as randomUUID5 } from "crypto";
5330
- import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
5331
- import { join as join14 } from "path";
5194
+ import { randomUUID as randomUUID4 } from "crypto";
5195
+ import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
5196
+ import { join as join13 } from "path";
5332
5197
  import { Cron } from "croner";
5333
5198
  var DURATION_RE = /^(\d+)(s|m|h|d)$/i;
5334
5199
  var UNIT_MS = {
@@ -5338,7 +5203,7 @@ var UNIT_MS = {
5338
5203
  d: 864e5
5339
5204
  };
5340
5205
  function schedulesPath() {
5341
- return join14(appDataDir(), "schedules.json");
5206
+ return join13(appDataDir(), "schedules.json");
5342
5207
  }
5343
5208
  function parseDurationMs(every) {
5344
5209
  const m = every.trim().match(DURATION_RE);
@@ -5444,9 +5309,9 @@ function normalizeTask(raw) {
5444
5309
  }
5445
5310
  function readAll() {
5446
5311
  const path = schedulesPath();
5447
- if (!existsSync15(path)) return [];
5312
+ if (!existsSync14(path)) return [];
5448
5313
  try {
5449
- const parsed = JSON.parse(readFileSync12(path, "utf8"));
5314
+ const parsed = JSON.parse(readFileSync11(path, "utf8"));
5450
5315
  if (!Array.isArray(parsed)) return [];
5451
5316
  return parsed.map((row) => {
5452
5317
  try {
@@ -5471,7 +5336,7 @@ function buildTask(input, now) {
5471
5336
  const ts = nowIso(now);
5472
5337
  const agent = input.agent ? coerceOrchestratorAgent(input.agent) : null;
5473
5338
  return {
5474
- id: randomUUID5(),
5339
+ id: randomUUID4(),
5475
5340
  name: input.name?.trim() || defaultScheduleName(prompt),
5476
5341
  prompt,
5477
5342
  enabled: input.enabled !== false,
@@ -5572,7 +5437,7 @@ function formatScheduledPrompt(name, prompt) {
5572
5437
  ${prompt}`;
5573
5438
  }
5574
5439
  async function defaultDeps() {
5575
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-3YDPPZHZ.js");
5440
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-WEFXUHFX.js");
5576
5441
  const orch = getOrchestrator2();
5577
5442
  return {
5578
5443
  findThread: (id) => findThreadByRef(id),
@@ -5778,7 +5643,7 @@ var Orchestrator = class {
5778
5643
  }
5779
5644
  continue;
5780
5645
  }
5781
- if (!existsSync16(thread.worktreePath)) {
5646
+ if (!existsSync15(thread.worktreePath)) {
5782
5647
  setStatus(thread.id, "broken", "Worktree missing on disk");
5783
5648
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
5784
5649
  continue;
@@ -5815,6 +5680,9 @@ var Orchestrator = class {
5815
5680
  * MCP-created review threads don't stay `queued` after the MCP child exits.
5816
5681
  */
5817
5682
  adoptPersistedQueues() {
5683
+ if (thisProcessShouldDrainAgentQueues()) {
5684
+ this.healStaleRunningTurns();
5685
+ }
5818
5686
  for (const thread of listThreads()) {
5819
5687
  if (thread.status === "stopped" || thread.status === "archived") continue;
5820
5688
  const pid = thread.agentPid;
@@ -5835,6 +5703,32 @@ var Orchestrator = class {
5835
5703
  }
5836
5704
  }
5837
5705
  }
5706
+ /**
5707
+ * Mid-session: a worktree can sit at `running` after the agent process dies
5708
+ * (Cursor/CLI crash, OOM) while wait_for_turn still reports stillRunning.
5709
+ * Reclaim those and wake the parent orchestration chat.
5710
+ */
5711
+ healStaleRunningTurns() {
5712
+ for (const thread of listThreads()) {
5713
+ if (thread.status === "archived") continue;
5714
+ const handle = this.activeTurns.get(thread.id);
5715
+ if (handle) {
5716
+ const pid = handle.pid;
5717
+ if (typeof pid === "number" && pid > 0 && !isPidAlive(pid)) {
5718
+ handle.kill();
5719
+ }
5720
+ continue;
5721
+ }
5722
+ if (!this.shouldReclaimRunningThread(thread)) continue;
5723
+ setStatus(thread.id, "stopped", "Process died (agent exited)");
5724
+ this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
5725
+ this.emit({ type: "turn_finished", threadId: thread.id, exitCode: 1 });
5726
+ const latest = readThread(thread.id);
5727
+ if (latest) {
5728
+ notifyParentOfChildHalt(latest, "stopped", (id, prompt) => this.send(id, prompt));
5729
+ }
5730
+ }
5731
+ }
5838
5732
  clearQuotaResumeTimer(threadId) {
5839
5733
  const timer = this.quotaResumeTimers.get(threadId);
5840
5734
  if (timer) clearTimeout(timer);
@@ -6552,6 +6446,12 @@ var Orchestrator = class {
6552
6446
  assistantText: chatText,
6553
6447
  partsCount: parts.length
6554
6448
  });
6449
+ if (!this.crashContinued.has(threadId)) {
6450
+ const failed = readThread(threadId);
6451
+ if (failed) {
6452
+ notifyParentOfChildHalt(failed, "error", (id, prompt2) => this.send(id, prompt2));
6453
+ }
6454
+ }
6555
6455
  }
6556
6456
  }
6557
6457
  } catch (err) {
@@ -6576,6 +6476,12 @@ var Orchestrator = class {
6576
6476
  assistantText: "",
6577
6477
  partsCount: 0
6578
6478
  });
6479
+ if (!this.crashContinued.has(threadId)) {
6480
+ const failed = readThread(threadId);
6481
+ if (failed) {
6482
+ notifyParentOfChildHalt(failed, "error", (id, prompt2) => this.send(id, prompt2));
6483
+ }
6484
+ }
6579
6485
  }
6580
6486
  } finally {
6581
6487
  this.startingTurns.delete(threadId);
@@ -6631,6 +6537,9 @@ var Orchestrator = class {
6631
6537
  const stopped = writeLiveStatus(thread.id, "stopped") ?? readThread(thread.id) ?? thread;
6632
6538
  if (stopped.status === "stopped") {
6633
6539
  this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
6540
+ if (inFlight && opts?.notifyParent !== false) {
6541
+ notifyParentOfChildHalt(stopped, "stopped", (id, prompt) => this.send(id, prompt));
6542
+ }
6634
6543
  }
6635
6544
  return stopped;
6636
6545
  }
@@ -6875,21 +6784,25 @@ var Orchestrator = class {
6875
6784
  fn();
6876
6785
  };
6877
6786
  const off = this.on((event) => {
6878
- if (event.type === "turn_finished" && event.threadId === thread.id) {
6787
+ if (!("threadId" in event) || event.threadId !== thread.id) return;
6788
+ if (event.type === "turn_finished" || event.type === "error") {
6879
6789
  const latest = readThread(thread.id);
6880
6790
  if (!latest) {
6881
6791
  finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
6882
6792
  return;
6883
6793
  }
6884
6794
  finish(() => resolve(latest));
6795
+ return;
6885
6796
  }
6886
- if (event.type === "error" && event.threadId === thread.id) {
6797
+ if (event.type === "status_changed") {
6887
6798
  const latest = readThread(thread.id);
6888
6799
  if (!latest) {
6889
6800
  finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
6890
6801
  return;
6891
6802
  }
6892
- finish(() => resolve(latest));
6803
+ if (!["running", "queued"].includes(latest.status)) {
6804
+ finish(() => resolve(latest));
6805
+ }
6893
6806
  }
6894
6807
  });
6895
6808
  timer = setInterval(() => {
@@ -6924,7 +6837,7 @@ var Orchestrator = class {
6924
6837
  const thread = this.requireThread(threadRef);
6925
6838
  const lastAgent = [...thread.messages].reverse().find((m) => m.role === "agent");
6926
6839
  const lastError = thread.lastError ?? null;
6927
- const text = (lastAgent?.text ?? "").trim() || (thread.status === "error" ? lastError ?? "" : "");
6840
+ const text = (lastAgent?.text ?? "").trim() || (thread.status === "error" || thread.status === "stopped" || thread.status === "broken" ? lastError ?? "" : "");
6928
6841
  const stillRunning = thread.status === "running" || thread.status === "queued";
6929
6842
  const live = stillRunning ? readTurnLive(thread.id) : null;
6930
6843
  const queuedHint = thread.status === "queued" && !live?.summary ? "Queued \u2014 waiting for a concurrency slot" : null;
@@ -7383,7 +7296,7 @@ var Orchestrator = class {
7383
7296
  }
7384
7297
  async archiveUnlocked(threadRef) {
7385
7298
  const thread = this.requireThread(threadRef);
7386
- this.stop(thread.id);
7299
+ this.stop(thread.id, { notifyParent: false });
7387
7300
  this.releaseOrchestratorCaffeinate(thread);
7388
7301
  if (isGlobalThread(thread)) {
7389
7302
  const archived2 = setStatus(thread.id, "archived");
@@ -7411,7 +7324,7 @@ var Orchestrator = class {
7411
7324
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
7412
7325
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
7413
7326
  try {
7414
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-AYI5DHK4.js");
7327
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-ALCTB65T.js");
7415
7328
  await ensureWorkspace2(thread.repoPath);
7416
7329
  } catch {
7417
7330
  }
@@ -7424,7 +7337,7 @@ var Orchestrator = class {
7424
7337
  }
7425
7338
  async purgeUnlocked(threadRef, opts) {
7426
7339
  const thread = this.requireThread(threadRef);
7427
- this.stop(thread.id);
7340
+ this.stop(thread.id, { notifyParent: false });
7428
7341
  this.releaseOrchestratorCaffeinate(thread);
7429
7342
  if (isGlobalThread(thread)) {
7430
7343
  deleteThreadRecord(thread.id);
@@ -7459,13 +7372,13 @@ var Orchestrator = class {
7459
7372
  this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
7460
7373
  return restored2;
7461
7374
  }
7462
- if (!existsSync16(thread.worktreePath)) {
7375
+ if (!existsSync15(thread.worktreePath)) {
7463
7376
  if (isCowboyThread(thread) || isPrimaryCheckoutThread(thread)) {
7464
7377
  throw new Error(
7465
7378
  `Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
7466
7379
  );
7467
7380
  }
7468
- const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-MX7XBX6Z.js");
7381
+ const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-GXD2NGOZ.js");
7469
7382
  const slug = thread.worktreePath.split("/").pop();
7470
7383
  const dest = thread.worktreePath;
7471
7384
  await withRepoGitLock(thread.repoPath, async () => {
@@ -7573,7 +7486,7 @@ async function startOrchestration(opts) {
7573
7486
  sourceRef: "default",
7574
7487
  ...createOpts
7575
7488
  }).catch(async () => {
7576
- const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-MX7XBX6Z.js");
7489
+ const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-GXD2NGOZ.js");
7577
7490
  const repo = await resolveRepoRoot2(repoPath);
7578
7491
  const def = await resolveDefaultBranch2(repo);
7579
7492
  return orch.createThread({
@@ -7583,7 +7496,7 @@ async function startOrchestration(opts) {
7583
7496
  repoPath: repo
7584
7497
  });
7585
7498
  });
7586
- const { updateThread: upd } = await import("./thread-store-CRLQJ2HM.js");
7499
+ const { updateThread: upd } = await import("./thread-store-WGASXXXR.js");
7587
7500
  const updated = upd(thread.id, {
7588
7501
  sourceType: "orchestration",
7589
7502
  sourceRef: goal
@@ -7639,6 +7552,13 @@ export {
7639
7552
  spawnAgentTurn,
7640
7553
  AGENT_GIT_ACTIONS,
7641
7554
  agentGitPrompt,
7555
+ CHARS_PER_CONTEXT_TOKEN,
7556
+ estimateMessageChars,
7557
+ estimateThreadChars,
7558
+ estimateOccupancyTokens,
7559
+ threadHasCompactedContext,
7560
+ forwardContextUsage,
7561
+ applyForwardOccupancy,
7642
7562
  summarizeConversation,
7643
7563
  extractiveSummary,
7644
7564
  CONTEXT_COMPACT_CHARS,
@@ -7646,8 +7566,6 @@ export {
7646
7566
  CONTEXT_KEEP_RECENT_MESSAGES,
7647
7567
  CONTEXT_MIN_MESSAGES,
7648
7568
  SESSION_RESET_OCCUPANCY_TOKENS,
7649
- estimateMessageChars,
7650
- estimateThreadChars,
7651
7569
  shouldCompactContext,
7652
7570
  splitForCompaction,
7653
7571
  formatMessagesAsTranscript,
@@ -7760,12 +7678,6 @@ export {
7760
7678
  discoverSkills,
7761
7679
  readSkillBody,
7762
7680
  expandComposerPrompt,
7763
- isImageFilePath,
7764
- attachmentFromAbsolutePath,
7765
- stageAbsolutePathsAsAttachments,
7766
- stageBuffersAsAttachments,
7767
- attachmentsFromBuffers,
7768
- attachmentsFromWorktreePaths,
7769
7681
  formatRenameBranchDirective,
7770
7682
  formatWorktreeDirective,
7771
7683
  formatWorktreeReminder,