@sideboard-ai/core 0.1.133 → 0.1.136

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-NHS6P25H.js → agents-ELWR7A2T.js} +5 -5
  2. package/dist/{agents-SOU5GPWI.js → agents-QNTTLMG2.js} +5 -5
  3. package/dist/{chunk-Y645NFYN.js → chunk-4XKUHP6G.js} +2 -2
  4. package/dist/{chunk-4YAVXWFR.js → chunk-CYM5DCHI.js} +39 -5
  5. package/dist/{chunk-LTP7GHIF.js → chunk-GSKRGF7B.js} +286 -5
  6. package/dist/{chunk-WEU5M7BW.js → chunk-HQQNLDVC.js} +1 -1
  7. package/dist/{chunk-IM36S4HZ.js → chunk-IFZ4MOTN.js} +3 -3
  8. package/dist/{chunk-G6X6UFJO.js → chunk-J5IBSVB3.js} +42 -12
  9. package/dist/{chunk-AK7SWE2U.js → chunk-JPBRMUM6.js} +9 -5
  10. package/dist/{chunk-YEIH7P7D.js → chunk-K5YT5GX2.js} +244 -5
  11. package/dist/{chunk-3UVVWNHF.js → chunk-MDCKV2NF.js} +2 -2
  12. package/dist/{chunk-HLJUNJBF.js → chunk-PM3C2J6K.js} +42 -12
  13. package/dist/{chunk-Q6XQXCOM.js → chunk-QAV3HGVS.js} +1 -1
  14. package/dist/{chunk-TG7YU2KG.js → chunk-TIGKDMIA.js} +144 -271
  15. package/dist/{chunk-6CRKPD4C.js → chunk-TQ4S5AGJ.js} +3 -3
  16. package/dist/{chunk-VSON7EJ6.js → chunk-WS5LFFU3.js} +140 -222
  17. package/dist/{coordinator-prompt-DXHEDRRN.js → coordinator-prompt-2OWSUAUR.js} +3 -3
  18. package/dist/{coordinator-prompt-XF7LVOUN.js → coordinator-prompt-IPL4Z6SL.js} +3 -3
  19. package/dist/{global-workspace-2LO5ATOH.js → global-workspace-JDUCUL7S.js} +4 -4
  20. package/dist/{global-workspace-MLQNULOU.js → global-workspace-NIKZAKOO.js} +4 -4
  21. package/dist/index.cjs +873 -628
  22. package/dist/index.d.cts +26 -1
  23. package/dist/index.d.ts +26 -1
  24. package/dist/index.js +42 -19
  25. package/dist/mcp/run-stdio.cjs +700 -498
  26. package/dist/mcp/run-stdio.js +22 -11
  27. package/dist/{orchestrator-P2SYHVLS.js → orchestrator-6I47JMU2.js} +7 -7
  28. package/dist/{orchestrator-KR5RA5B7.js → orchestrator-KK3CUW37.js} +7 -7
  29. package/dist/{thread-store-5MLYY35S.js → thread-store-CRLQJ2HM.js} +3 -1
  30. package/dist/{thread-store-F6BCARQG.js → thread-store-FADXSMEJ.js} +3 -1
  31. package/dist/{workspaces-MINB764G.js → workspaces-5EWNNALF.js} +5 -5
  32. package/dist/{workspaces-ARWPZG6F.js → workspaces-KZA3TCEE.js} +5 -5
  33. package/dist/{worktree-EBZMUUAL.js → worktree-BC6XDMQK.js} +2 -2
  34. package/dist/{worktree-S7MKMYAT.js → worktree-MX7XBX6Z.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-4YAVXWFR.js";
21
+ } from "./chunk-CYM5DCHI.js";
22
22
  import {
23
23
  addWorkspace,
24
24
  ensureWorkspace,
25
25
  removeWorkspace,
26
26
  syncWorkspacesFromThreads
27
- } from "./chunk-3UVVWNHF.js";
27
+ } from "./chunk-MDCKV2NF.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-LTP7GHIF.js";
40
+ orchestratorSessionPoisonedByBuiltins,
41
+ persistPendingFileAttachments,
42
+ stageAbsolutePathsAsAttachments,
43
+ stageBuffersAsAttachments
44
+ } from "./chunk-GSKRGF7B.js";
41
45
  import {
42
46
  SLACK_REPLY_FORMATTING,
43
47
  coordinatorSystemPrompt,
44
48
  coordinatorTurnReminder,
45
49
  enrichWorkspacesWithGithub,
46
50
  ensureGlobalCoordinatorCwd
47
- } from "./chunk-IM36S4HZ.js";
51
+ } from "./chunk-IFZ4MOTN.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-WEU5M7BW.js";
121
+ } from "./chunk-HQQNLDVC.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-HLJUNJBF.js";
158
+ } from "./chunk-PM3C2J6K.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-P2SYHVLS.js");
451
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-6I47JMU2.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-MLQNULOU.js");
731
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-NIKZAKOO.js");
729
732
  if (isGlobalThread2(thread)) {
730
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-XF7LVOUN.js");
733
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-IPL4Z6SL.js");
731
734
  ensureGlobalCoordinatorCwd2(
732
735
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
733
736
  );
@@ -2265,6 +2268,45 @@ function thisProcessShouldDrainAgentQueues() {
2265
2268
  return !isDesktopHostAlive();
2266
2269
  }
2267
2270
 
2271
+ // src/orchestrator/child-halt.ts
2272
+ var HALT_STATUSES = /* @__PURE__ */ new Set(["stopped", "error", "broken"]);
2273
+ function isIncompleteChildStatus(status) {
2274
+ return HALT_STATUSES.has(status);
2275
+ }
2276
+ function childHaltNotice(child, status) {
2277
+ const title = child.title?.trim() || "Untitled";
2278
+ const link = `[${title}](sideboard://thread/${child.id})`;
2279
+ const why = child.lastError?.trim();
2280
+ const extra = why ? ` lastError: ${why}` : "";
2281
+ return [
2282
+ `Sideboard: child worktree ${link} ${status} before finishing (status=${status}).${extra}`,
2283
+ "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."
2284
+ ].join("\n");
2285
+ }
2286
+ function shouldNotifyParentOfChildHalt(opts) {
2287
+ if (!isIncompleteChildStatus(opts.status)) return false;
2288
+ if (!opts.child.parentThreadId) return false;
2289
+ if (!opts.parent || opts.parent.status === "archived") return false;
2290
+ if (opts.parent.id === opts.child.id) return false;
2291
+ return isOrchestratorThread(opts.parent);
2292
+ }
2293
+ var notified = /* @__PURE__ */ new Set();
2294
+ function noticeKey(childId, status) {
2295
+ return `${childId}:${status}`;
2296
+ }
2297
+ function notifyParentOfChildHalt(child, status, send) {
2298
+ const parent = child.parentThreadId ? readThread(child.parentThreadId) : null;
2299
+ if (!shouldNotifyParentOfChildHalt({ child, parent, status })) return false;
2300
+ const key = noticeKey(child.id, status);
2301
+ if (notified.has(key)) return false;
2302
+ notified.add(key);
2303
+ const parentId = parent.id;
2304
+ void send(parentId, childHaltNotice(child, status)).catch(() => {
2305
+ notified.delete(key);
2306
+ });
2307
+ return true;
2308
+ }
2309
+
2268
2310
  // src/threads/create.ts
2269
2311
  import { existsSync as existsSync7 } from "fs";
2270
2312
 
@@ -2743,6 +2785,9 @@ function assembleHomeBoard(input) {
2743
2785
  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
2786
 
2745
2787
  // src/threads/create.ts
2788
+ function persistCreateAttachments(worktreePath, attachments) {
2789
+ return persistPendingFileAttachments(worktreePath, attachments ?? []);
2790
+ }
2746
2791
  async function createThread(input, _onSetupLine) {
2747
2792
  const repoPath = await resolveRepoRoot(input.repoPath);
2748
2793
  if (!existsSync7(repoPath)) {
@@ -2762,7 +2807,16 @@ async function createThread(input, _onSetupLine) {
2762
2807
  repoPath: canonicalizeRepoPath(t.repoPath)
2763
2808
  }))
2764
2809
  );
2765
- if (existing) return readThread(existing.id) ?? existing;
2810
+ if (existing) {
2811
+ const thread2 = readThread(existing.id) ?? existing;
2812
+ if (!input.attachments?.length) return thread2;
2813
+ return updateThread(thread2.id, {
2814
+ attachments: persistCreateAttachments(thread2.worktreePath, [
2815
+ ...thread2.attachments,
2816
+ ...input.attachments
2817
+ ])
2818
+ });
2819
+ }
2766
2820
  }
2767
2821
  const resolved = resolveNewThreadOptions({
2768
2822
  agent: input.agent,
@@ -2810,7 +2864,7 @@ async function createThread(input, _onSetupLine) {
2810
2864
  effort: resolved.effort,
2811
2865
  fast: resolved.fast,
2812
2866
  planMode: Boolean(input.planMode),
2813
- attachments: input.attachments ?? [],
2867
+ attachments: persistCreateAttachments(repoPath, input.attachments),
2814
2868
  sourceIsFork: false,
2815
2869
  parentThreadId: input.parentThreadId ?? null,
2816
2870
  status: "idle",
@@ -2889,7 +2943,7 @@ async function createThread(input, _onSetupLine) {
2889
2943
  effort: resolved.effort,
2890
2944
  fast: resolved.fast,
2891
2945
  planMode: Boolean(input.planMode),
2892
- attachments,
2946
+ attachments: persistCreateAttachments(worktreePath, attachments),
2893
2947
  sourceIsFork,
2894
2948
  parentThreadId: input.parentThreadId ?? null,
2895
2949
  status: "idle",
@@ -2901,7 +2955,7 @@ async function createThread(input, _onSetupLine) {
2901
2955
  return readThread(thread.id) ?? thread;
2902
2956
  }
2903
2957
  async function listLinearIssues(agent, repoPath) {
2904
- const { getAdapter: getAdapter2 } = await import("./agents-SOU5GPWI.js");
2958
+ const { getAdapter: getAdapter2 } = await import("./agents-QNTTLMG2.js");
2905
2959
  await requireAgent(agent, { requireLinear: true });
2906
2960
  const adapter = getAdapter2(agent);
2907
2961
  if (!adapter.listLinearIssues) {
@@ -3298,7 +3352,7 @@ async function adoptThread(input) {
3298
3352
  messages: input.messages ?? []
3299
3353
  });
3300
3354
  writeThread(thread);
3301
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-ARWPZG6F.js");
3355
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-KZA3TCEE.js");
3302
3356
  await ensureWorkspace2(repoPath);
3303
3357
  return thread;
3304
3358
  }
@@ -4861,232 +4915,9 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
4861
4915
  };
4862
4916
  }
4863
4917
 
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
4918
  // src/agents/instructions.ts
5088
- import { existsSync as existsSync14, readFileSync as readFileSync11, statSync as statSync5 } from "fs";
5089
- import { join as join13 } from "path";
4919
+ import { existsSync as existsSync13, readFileSync as readFileSync10, statSync as statSync4 } from "fs";
4920
+ import { join as join12 } from "path";
5090
4921
  function normPath(p) {
5091
4922
  return p.replace(/\/+$/, "");
5092
4923
  }
@@ -5250,11 +5081,11 @@ function loadAgentInstructions(worktreePath, agent) {
5250
5081
  const out = [];
5251
5082
  for (const rel of candidates) {
5252
5083
  if (seenPaths.has(rel)) continue;
5253
- const abs = join13(worktreePath, rel);
5254
- if (!existsSync14(abs)) continue;
5084
+ const abs = join12(worktreePath, rel);
5085
+ if (!existsSync13(abs)) continue;
5255
5086
  try {
5256
- if (!statSync5(abs).isFile()) continue;
5257
- let content = readFileSync11(abs, "utf8");
5087
+ if (!statSync4(abs).isFile()) continue;
5088
+ let content = readFileSync10(abs, "utf8");
5258
5089
  if (!content.trim()) continue;
5259
5090
  if (content.length > MAX_CHARS_PER_FILE) {
5260
5091
  content = `${content.slice(0, MAX_CHARS_PER_FILE)}
@@ -5326,9 +5157,9 @@ async function syncThreadBranchFromGit(threadId) {
5326
5157
  }
5327
5158
 
5328
5159
  // 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";
5160
+ import { randomUUID as randomUUID4 } from "crypto";
5161
+ import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
5162
+ import { join as join13 } from "path";
5332
5163
  import { Cron } from "croner";
5333
5164
  var DURATION_RE = /^(\d+)(s|m|h|d)$/i;
5334
5165
  var UNIT_MS = {
@@ -5338,7 +5169,7 @@ var UNIT_MS = {
5338
5169
  d: 864e5
5339
5170
  };
5340
5171
  function schedulesPath() {
5341
- return join14(appDataDir(), "schedules.json");
5172
+ return join13(appDataDir(), "schedules.json");
5342
5173
  }
5343
5174
  function parseDurationMs(every) {
5344
5175
  const m = every.trim().match(DURATION_RE);
@@ -5444,9 +5275,9 @@ function normalizeTask(raw) {
5444
5275
  }
5445
5276
  function readAll() {
5446
5277
  const path = schedulesPath();
5447
- if (!existsSync15(path)) return [];
5278
+ if (!existsSync14(path)) return [];
5448
5279
  try {
5449
- const parsed = JSON.parse(readFileSync12(path, "utf8"));
5280
+ const parsed = JSON.parse(readFileSync11(path, "utf8"));
5450
5281
  if (!Array.isArray(parsed)) return [];
5451
5282
  return parsed.map((row) => {
5452
5283
  try {
@@ -5471,7 +5302,7 @@ function buildTask(input, now) {
5471
5302
  const ts = nowIso(now);
5472
5303
  const agent = input.agent ? coerceOrchestratorAgent(input.agent) : null;
5473
5304
  return {
5474
- id: randomUUID5(),
5305
+ id: randomUUID4(),
5475
5306
  name: input.name?.trim() || defaultScheduleName(prompt),
5476
5307
  prompt,
5477
5308
  enabled: input.enabled !== false,
@@ -5572,7 +5403,7 @@ function formatScheduledPrompt(name, prompt) {
5572
5403
  ${prompt}`;
5573
5404
  }
5574
5405
  async function defaultDeps() {
5575
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-P2SYHVLS.js");
5406
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-6I47JMU2.js");
5576
5407
  const orch = getOrchestrator2();
5577
5408
  return {
5578
5409
  findThread: (id) => findThreadByRef(id),
@@ -5778,7 +5609,7 @@ var Orchestrator = class {
5778
5609
  }
5779
5610
  continue;
5780
5611
  }
5781
- if (!existsSync16(thread.worktreePath)) {
5612
+ if (!existsSync15(thread.worktreePath)) {
5782
5613
  setStatus(thread.id, "broken", "Worktree missing on disk");
5783
5614
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
5784
5615
  continue;
@@ -5815,6 +5646,9 @@ var Orchestrator = class {
5815
5646
  * MCP-created review threads don't stay `queued` after the MCP child exits.
5816
5647
  */
5817
5648
  adoptPersistedQueues() {
5649
+ if (thisProcessShouldDrainAgentQueues()) {
5650
+ this.healStaleRunningTurns();
5651
+ }
5818
5652
  for (const thread of listThreads()) {
5819
5653
  if (thread.status === "stopped" || thread.status === "archived") continue;
5820
5654
  const pid = thread.agentPid;
@@ -5835,6 +5669,32 @@ var Orchestrator = class {
5835
5669
  }
5836
5670
  }
5837
5671
  }
5672
+ /**
5673
+ * Mid-session: a worktree can sit at `running` after the agent process dies
5674
+ * (Cursor/CLI crash, OOM) while wait_for_turn still reports stillRunning.
5675
+ * Reclaim those and wake the parent orchestration chat.
5676
+ */
5677
+ healStaleRunningTurns() {
5678
+ for (const thread of listThreads()) {
5679
+ if (thread.status === "archived") continue;
5680
+ const handle = this.activeTurns.get(thread.id);
5681
+ if (handle) {
5682
+ const pid = thread.agentPid;
5683
+ if (typeof pid === "number" && pid > 0 && !isPidAlive(pid)) {
5684
+ handle.kill();
5685
+ }
5686
+ continue;
5687
+ }
5688
+ if (!this.shouldReclaimRunningThread(thread)) continue;
5689
+ setStatus(thread.id, "stopped", "Process died (agent exited)");
5690
+ this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
5691
+ this.emit({ type: "turn_finished", threadId: thread.id, exitCode: 1 });
5692
+ const latest = readThread(thread.id);
5693
+ if (latest) {
5694
+ notifyParentOfChildHalt(latest, "stopped", (id, prompt) => this.send(id, prompt));
5695
+ }
5696
+ }
5697
+ }
5838
5698
  clearQuotaResumeTimer(threadId) {
5839
5699
  const timer = this.quotaResumeTimers.get(threadId);
5840
5700
  if (timer) clearTimeout(timer);
@@ -6552,6 +6412,12 @@ var Orchestrator = class {
6552
6412
  assistantText: chatText,
6553
6413
  partsCount: parts.length
6554
6414
  });
6415
+ if (!this.crashContinued.has(threadId)) {
6416
+ const failed = readThread(threadId);
6417
+ if (failed) {
6418
+ notifyParentOfChildHalt(failed, "error", (id, prompt2) => this.send(id, prompt2));
6419
+ }
6420
+ }
6555
6421
  }
6556
6422
  }
6557
6423
  } catch (err) {
@@ -6576,6 +6442,12 @@ var Orchestrator = class {
6576
6442
  assistantText: "",
6577
6443
  partsCount: 0
6578
6444
  });
6445
+ if (!this.crashContinued.has(threadId)) {
6446
+ const failed = readThread(threadId);
6447
+ if (failed) {
6448
+ notifyParentOfChildHalt(failed, "error", (id, prompt2) => this.send(id, prompt2));
6449
+ }
6450
+ }
6579
6451
  }
6580
6452
  } finally {
6581
6453
  this.startingTurns.delete(threadId);
@@ -6631,6 +6503,9 @@ var Orchestrator = class {
6631
6503
  const stopped = writeLiveStatus(thread.id, "stopped") ?? readThread(thread.id) ?? thread;
6632
6504
  if (stopped.status === "stopped") {
6633
6505
  this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
6506
+ if (opts?.notifyParent !== false) {
6507
+ notifyParentOfChildHalt(stopped, "stopped", (id, prompt) => this.send(id, prompt));
6508
+ }
6634
6509
  }
6635
6510
  return stopped;
6636
6511
  }
@@ -6875,21 +6750,25 @@ var Orchestrator = class {
6875
6750
  fn();
6876
6751
  };
6877
6752
  const off = this.on((event) => {
6878
- if (event.type === "turn_finished" && event.threadId === thread.id) {
6753
+ if (!("threadId" in event) || event.threadId !== thread.id) return;
6754
+ if (event.type === "turn_finished" || event.type === "error") {
6879
6755
  const latest = readThread(thread.id);
6880
6756
  if (!latest) {
6881
6757
  finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
6882
6758
  return;
6883
6759
  }
6884
6760
  finish(() => resolve(latest));
6761
+ return;
6885
6762
  }
6886
- if (event.type === "error" && event.threadId === thread.id) {
6763
+ if (event.type === "status_changed") {
6887
6764
  const latest = readThread(thread.id);
6888
6765
  if (!latest) {
6889
6766
  finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
6890
6767
  return;
6891
6768
  }
6892
- finish(() => resolve(latest));
6769
+ if (!["running", "queued"].includes(latest.status)) {
6770
+ finish(() => resolve(latest));
6771
+ }
6893
6772
  }
6894
6773
  });
6895
6774
  timer = setInterval(() => {
@@ -6924,7 +6803,7 @@ var Orchestrator = class {
6924
6803
  const thread = this.requireThread(threadRef);
6925
6804
  const lastAgent = [...thread.messages].reverse().find((m) => m.role === "agent");
6926
6805
  const lastError = thread.lastError ?? null;
6927
- const text = (lastAgent?.text ?? "").trim() || (thread.status === "error" ? lastError ?? "" : "");
6806
+ const text = (lastAgent?.text ?? "").trim() || (thread.status === "error" || thread.status === "stopped" || thread.status === "broken" ? lastError ?? "" : "");
6928
6807
  const stillRunning = thread.status === "running" || thread.status === "queued";
6929
6808
  const live = stillRunning ? readTurnLive(thread.id) : null;
6930
6809
  const queuedHint = thread.status === "queued" && !live?.summary ? "Queued \u2014 waiting for a concurrency slot" : null;
@@ -7411,7 +7290,7 @@ var Orchestrator = class {
7411
7290
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
7412
7291
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
7413
7292
  try {
7414
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-ARWPZG6F.js");
7293
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-KZA3TCEE.js");
7415
7294
  await ensureWorkspace2(thread.repoPath);
7416
7295
  } catch {
7417
7296
  }
@@ -7459,13 +7338,13 @@ var Orchestrator = class {
7459
7338
  this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
7460
7339
  return restored2;
7461
7340
  }
7462
- if (!existsSync16(thread.worktreePath)) {
7341
+ if (!existsSync15(thread.worktreePath)) {
7463
7342
  if (isCowboyThread(thread) || isPrimaryCheckoutThread(thread)) {
7464
7343
  throw new Error(
7465
7344
  `Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
7466
7345
  );
7467
7346
  }
7468
- const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-S7MKMYAT.js");
7347
+ const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-MX7XBX6Z.js");
7469
7348
  const slug = thread.worktreePath.split("/").pop();
7470
7349
  const dest = thread.worktreePath;
7471
7350
  await withRepoGitLock(thread.repoPath, async () => {
@@ -7573,7 +7452,7 @@ async function startOrchestration(opts) {
7573
7452
  sourceRef: "default",
7574
7453
  ...createOpts
7575
7454
  }).catch(async () => {
7576
- const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-S7MKMYAT.js");
7455
+ const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-MX7XBX6Z.js");
7577
7456
  const repo = await resolveRepoRoot2(repoPath);
7578
7457
  const def = await resolveDefaultBranch2(repo);
7579
7458
  return orch.createThread({
@@ -7583,7 +7462,7 @@ async function startOrchestration(opts) {
7583
7462
  repoPath: repo
7584
7463
  });
7585
7464
  });
7586
- const { updateThread: upd } = await import("./thread-store-5MLYY35S.js");
7465
+ const { updateThread: upd } = await import("./thread-store-CRLQJ2HM.js");
7587
7466
  const updated = upd(thread.id, {
7588
7467
  sourceType: "orchestration",
7589
7468
  sourceRef: goal
@@ -7760,12 +7639,6 @@ export {
7760
7639
  discoverSkills,
7761
7640
  readSkillBody,
7762
7641
  expandComposerPrompt,
7763
- isImageFilePath,
7764
- attachmentFromAbsolutePath,
7765
- stageAbsolutePathsAsAttachments,
7766
- stageBuffersAsAttachments,
7767
- attachmentsFromBuffers,
7768
- attachmentsFromWorktreePaths,
7769
7642
  formatRenameBranchDirective,
7770
7643
  formatWorktreeDirective,
7771
7644
  formatWorktreeReminder,