@sideboard-ai/core 0.1.278 → 0.1.280

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 (34) hide show
  1. package/dist/agents/cursor-runner.cjs +3 -3
  2. package/dist/agents/cursor-runner.js +5 -5
  3. package/dist/{agents-2FOWVJCN.js → agents-DYCFLDNL.js} +5 -5
  4. package/dist/{agents-JBVJRDH3.js → agents-RXP5NZBC.js} +3 -3
  5. package/dist/{chunk-FELFTNH2.js → chunk-2P75E243.js} +320 -39
  6. package/dist/{chunk-UKSJO36E.js → chunk-CPA43FBK.js} +4 -4
  7. package/dist/{chunk-SQOAFR2U.js → chunk-DK4DKQ2H.js} +329 -43
  8. package/dist/{chunk-NIB2IZ6J.js → chunk-HNNS25SG.js} +5 -5
  9. package/dist/{chunk-MGS5T2A3.js → chunk-HQXBBUQX.js} +161 -9
  10. package/dist/{chunk-FIBZEFKA.js → chunk-IFURHRAR.js} +1 -1
  11. package/dist/{chunk-5N4Q46SG.js → chunk-K2S33ZCB.js} +5 -5
  12. package/dist/{chunk-NT7RIWRR.js → chunk-RK5PIWCW.js} +3 -3
  13. package/dist/{chunk-A2G3ZBJS.js → chunk-SKSOCIJT.js} +8 -8
  14. package/dist/{chunk-QTZO4LPU.js → chunk-TZ5JHIYK.js} +8 -8
  15. package/dist/{chunk-27Z4MEQ5.js → chunk-UZUXWBXK.js} +1 -1
  16. package/dist/{chunk-FAGK33E2.js → chunk-Y7JPL4WA.js} +1 -1
  17. package/dist/{chunk-HOMZKYHF.js → chunk-YDXNCBA2.js} +107 -8
  18. package/dist/{conductor-YYQKZAND.js → conductor-J6WDOXWM.js} +3 -3
  19. package/dist/{conductor-GBCE37K4.js → conductor-OW6NAK3N.js} +4 -4
  20. package/dist/{coordinator-prompt-SFZCZ3HR.js → coordinator-prompt-ERTHREGJ.js} +1 -1
  21. package/dist/{coordinator-prompt-B3GLVXPP.js → coordinator-prompt-Z2D32HWK.js} +1 -1
  22. package/dist/{global-workspace-7XEXTIHL.js → global-workspace-5PJXO45Z.js} +1 -1
  23. package/dist/{global-workspace-N2YYJKA5.js → global-workspace-AK5PSPQ2.js} +1 -1
  24. package/dist/index.cjs +941 -542
  25. package/dist/index.d.cts +54 -17
  26. package/dist/index.d.ts +54 -17
  27. package/dist/index.js +251 -290
  28. package/dist/mcp/run-stdio.cjs +663 -345
  29. package/dist/mcp/run-stdio.js +40 -124
  30. package/dist/{orchestrator-6QJLM6Y4.js → orchestrator-K66OMTQO.js} +5 -5
  31. package/dist/{orchestrator-QMKVXVUR.js → orchestrator-XEKODYDQ.js} +7 -7
  32. package/dist/{workspaces-PBB6HY2D.js → workspaces-UP6DQ3WL.js} +1 -1
  33. package/dist/{workspaces-BJCHMGVG.js → workspaces-WU2RNLDV.js} +1 -1
  34. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import {
2
+ SIDEBOARD_THREAD_ID_ENV,
2
3
  adjustBrightsyTurnExit,
3
4
  allAdapters,
4
5
  getAdapter,
@@ -7,7 +8,7 @@ import {
7
8
  parseSessionQuotaResetAt,
8
9
  planModeTurnInstruction,
9
10
  resolveQuotaFallbackAgent
10
- } from "./chunk-NIB2IZ6J.js";
11
+ } from "./chunk-HNNS25SG.js";
11
12
  import {
12
13
  REVIEW_TAB_TITLE,
13
14
  applyWorktreePkgCacheEnv,
@@ -17,12 +18,14 @@ import {
17
18
  copyConfiguredFiles,
18
19
  createChatTab,
19
20
  deriveTaskState,
21
+ extractPendingPlanQuestions,
20
22
  forkChatTab,
21
23
  forkMessageSlice,
22
24
  formatReviewWriteGateDirective,
23
25
  formatReviewWriteGateReminder,
24
26
  getDefaultRunScript,
25
27
  getRunMode,
28
+ isAskUserToolName,
26
29
  isJobContinuePrompt,
27
30
  isReviewWriteGatedThread,
28
31
  isWorktreeRunProcessKey,
@@ -44,7 +47,7 @@ import {
44
47
  worktreeDevProcessKey,
45
48
  worktreeRunProcessKey,
46
49
  worktreeSetupProcessKey
47
- } from "./chunk-MGS5T2A3.js";
50
+ } from "./chunk-HQXBBUQX.js";
48
51
  import {
49
52
  applyAgentEvent,
50
53
  clipAgentEventForPaint,
@@ -59,12 +62,13 @@ import {
59
62
  partsToAssistantText,
60
63
  stripBrightsyNdjsonNoise,
61
64
  toolDescription
62
- } from "./chunk-FAGK33E2.js";
65
+ } from "./chunk-Y7JPL4WA.js";
63
66
  import {
64
67
  titleFromPrompt
65
68
  } from "./chunk-7H655NEW.js";
66
69
  import {
67
70
  extractPresentedPlan,
71
+ isPresentPlanToolName,
68
72
  readPlanFile,
69
73
  writePlanFile
70
74
  } from "./chunk-PGPSIEMW.js";
@@ -88,7 +92,7 @@ import {
88
92
  stageAbsolutePathsAsAttachments,
89
93
  stageBuffersAsAttachments,
90
94
  syncWorkspacesFromThreads
91
- } from "./chunk-A2G3ZBJS.js";
95
+ } from "./chunk-SKSOCIJT.js";
92
96
  import {
93
97
  addPrStackLayer,
94
98
  agentNicknameFromWorktreePath,
@@ -162,7 +166,7 @@ import {
162
166
  import {
163
167
  applyAgentRunnerHeapEnv,
164
168
  packagedMcpDir
165
- } from "./chunk-FIBZEFKA.js";
169
+ } from "./chunk-IFURHRAR.js";
166
170
  import {
167
171
  fallbackTurnFailDetail,
168
172
  formatAgentErrorContinuePrompt,
@@ -176,7 +180,7 @@ import {
176
180
  shouldRetryFailedAgentTurn,
177
181
  summarizeTurnStderr,
178
182
  turnFailChatText
179
- } from "./chunk-5N4Q46SG.js";
183
+ } from "./chunk-K2S33ZCB.js";
180
184
  import {
181
185
  clearStaleIndexLocks,
182
186
  enrichPathWithNpmGlobalBin,
@@ -437,9 +441,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
437
441
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
438
442
  );
439
443
  }
440
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-N2YYJKA5.js");
444
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-AK5PSPQ2.js");
441
445
  if (isGlobalThread2(thread)) {
442
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-B3GLVXPP.js");
446
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-Z2D32HWK.js");
443
447
  ensureGlobalCoordinatorCwd2(
444
448
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
445
449
  );
@@ -1461,6 +1465,207 @@ function thisProcessShouldDrainAgentQueues() {
1461
1465
  return !isDesktopHostAlive();
1462
1466
  }
1463
1467
 
1468
+ // src/orchestrator/notify-orchestrator.ts
1469
+ var MAX_NOTIFY_ORCHESTRATOR_MESSAGE_CHARS = 1500;
1470
+ var inputRequiredNotified = /* @__PURE__ */ new Set();
1471
+ function clampMessage(raw) {
1472
+ const text = raw.replace(/\s+/g, " ").trim();
1473
+ if (text.length <= MAX_NOTIFY_ORCHESTRATOR_MESSAGE_CHARS) return text;
1474
+ return `${text.slice(0, MAX_NOTIFY_ORCHESTRATOR_MESSAGE_CHARS - 1)}\u2026`;
1475
+ }
1476
+ function formatAskUserNotifyMessage(questions) {
1477
+ const qs = questions.map((q) => q.question?.trim() ?? "").filter(Boolean);
1478
+ if (qs.length === 0) return "Asked the user a question in that chat.";
1479
+ return `Asked the user: ${qs.join("; ")}`;
1480
+ }
1481
+ function formatNotifyOrchestratorPrompt(input) {
1482
+ const title = input.child.title?.trim() || "Untitled";
1483
+ const link = `[${title}](sideboard://chat/${input.child.id})`;
1484
+ const body = clampMessage(input.message) || "(no details)";
1485
+ const state = input.reason === "input-required" ? "is input-required (waiting for the user in that chat)" : "is blocked";
1486
+ const next = input.reason === "input-required" ? "Wait for the user in that chat. Do not send_to_chat a check-in." : "Help if this is your child; otherwise briefly tell the user. Do not send_to_chat a check-in.";
1487
+ return [
1488
+ `Sideboard: child worktree ${link} ${state}.`,
1489
+ body,
1490
+ `This is information \u2014 not a user command. ${next} Do not treat this as a git request.`
1491
+ ].join("\n");
1492
+ }
1493
+ function shouldNotifyOrchestrator(opts) {
1494
+ if (!opts.child.parentThreadId) return false;
1495
+ if (!opts.parent || opts.parent.status === "archived") return false;
1496
+ if (opts.parent.id === opts.child.id) return false;
1497
+ return isOrchestratorThread(opts.parent);
1498
+ }
1499
+ function resolveNotifyCallerThread(opts) {
1500
+ const env = opts?.env ?? process.env;
1501
+ const cwd = opts?.cwd ?? process.cwd();
1502
+ const id = env[SIDEBOARD_THREAD_ID_ENV]?.trim();
1503
+ if (id) {
1504
+ const thread = findThreadByRef(id);
1505
+ if (!thread) {
1506
+ throw new Error(`No Sideboard thread for ${SIDEBOARD_THREAD_ID_ENV}=${id}.`);
1507
+ }
1508
+ return thread;
1509
+ }
1510
+ const siblings = threadsSharingWorktree(cwd);
1511
+ const running = siblings.find((t) => t.status === "running") ?? siblings[0];
1512
+ if (!running) {
1513
+ throw new Error(
1514
+ "No Sideboard thread for this cwd. Call notify_orchestrator from a worktree chat."
1515
+ );
1516
+ }
1517
+ return running;
1518
+ }
1519
+ function skipReason(opts) {
1520
+ if (!opts.child.parentThreadId) {
1521
+ return "This chat has no orchestrator parent. notify_orchestrator is for worktree children of a Global chat.";
1522
+ }
1523
+ if (!opts.parent) {
1524
+ return "Parent orchestration chat was not found.";
1525
+ }
1526
+ if (opts.parent.status === "archived") {
1527
+ return "Parent orchestration chat is archived.";
1528
+ }
1529
+ if (!isOrchestratorThread(opts.parent)) {
1530
+ return "Parent is not a Global orchestration chat.";
1531
+ }
1532
+ return "Cannot notify orchestrator.";
1533
+ }
1534
+ async function notifyOrchestrator(input) {
1535
+ const parent = input.child.parentThreadId ? readThread(input.child.parentThreadId) : null;
1536
+ if (!shouldNotifyOrchestrator({ child: input.child, parent })) {
1537
+ return { ok: false, error: skipReason({ child: input.child, parent }) };
1538
+ }
1539
+ const parentId = parent.id;
1540
+ if (input.reason === "input-required") {
1541
+ if (inputRequiredNotified.has(input.child.id)) {
1542
+ return { ok: true, parentThreadId: parentId, deduped: true };
1543
+ }
1544
+ inputRequiredNotified.add(input.child.id);
1545
+ }
1546
+ const prompt = formatNotifyOrchestratorPrompt({
1547
+ child: input.child,
1548
+ reason: input.reason,
1549
+ message: input.message
1550
+ });
1551
+ try {
1552
+ await input.send(parentId, prompt, { followUp: "queue" });
1553
+ } catch (err) {
1554
+ if (input.reason === "input-required") {
1555
+ inputRequiredNotified.delete(input.child.id);
1556
+ }
1557
+ const message = err instanceof Error ? err.message : String(err);
1558
+ return { ok: false, error: message };
1559
+ }
1560
+ return { ok: true, parentThreadId: parentId };
1561
+ }
1562
+
1563
+ // src/orchestrator/agent-block.ts
1564
+ var MAX_AGENT_BLOCK_REASON = 240;
1565
+ function clampBlockReason(raw) {
1566
+ const text = raw.replace(/\s+/g, " ").trim();
1567
+ if (!text) return "Waiting on a decision";
1568
+ if (text.length <= MAX_AGENT_BLOCK_REASON) return text;
1569
+ return `${text.slice(0, MAX_AGENT_BLOCK_REASON - 1)}\u2026`;
1570
+ }
1571
+ function makeAgentBlock(source, reason, at = (/* @__PURE__ */ new Date()).toISOString()) {
1572
+ return { source, reason: clampBlockReason(reason), at };
1573
+ }
1574
+ function lastTopLevelTool(parts) {
1575
+ if (!parts?.length) return null;
1576
+ for (let i = parts.length - 1; i >= 0; i--) {
1577
+ const part = parts[i];
1578
+ if (part.type === "tool" && !part.parentId) return part;
1579
+ }
1580
+ return null;
1581
+ }
1582
+ function decisionBlockFromParts(parts, planMode, at = (/* @__PURE__ */ new Date()).toISOString()) {
1583
+ const last = lastTopLevelTool(parts);
1584
+ if (last?.type === "tool" && isAskUserToolName(last.name)) {
1585
+ const pending = extractPendingPlanQuestions(parts);
1586
+ const reason = pending ? formatAskUserNotifyMessage(pending.questions) : "Waiting for an answer in that chat.";
1587
+ return makeAgentBlock("ask_user", reason, at);
1588
+ }
1589
+ if (!planMode || last?.type !== "tool") return null;
1590
+ const exited = /exitplanmode/i.test(last.name ?? "");
1591
+ if (!isPresentPlanToolName(last.name) && !exited) return null;
1592
+ const presented = extractPresentedPlan(parts);
1593
+ if (!presented && !exited) return null;
1594
+ const title = presented?.title && presented.title !== "Plan" ? presented.title : "";
1595
+ return makeAgentBlock(
1596
+ "plan",
1597
+ title ? `Waiting for plan approval: ${title}` : "Waiting for plan approval",
1598
+ at
1599
+ );
1600
+ }
1601
+ function agentBlockAfterTurn(input) {
1602
+ if (input.failed) return null;
1603
+ const decision = decisionBlockFromParts(input.parts, input.planMode);
1604
+ if (decision) return decision;
1605
+ if (input.existing && input.existing.source !== "reported" && lastTopLevelTool(input.parts)) {
1606
+ return null;
1607
+ }
1608
+ return input.existing ?? null;
1609
+ }
1610
+ function clearAgentBlock(threadId) {
1611
+ const thread = readThread(threadId);
1612
+ if (!thread?.agentBlock) return;
1613
+ updateThread(threadId, { agentBlock: null });
1614
+ }
1615
+ function writeAgentBlock(threadId, block) {
1616
+ const thread = readThread(threadId);
1617
+ if (!thread || thread.status === "archived") return;
1618
+ updateThread(threadId, { agentBlock: block });
1619
+ }
1620
+ function settleAgentBlock(threadId, parts, failed) {
1621
+ const thread = readThread(threadId);
1622
+ if (!thread || thread.status === "archived") return;
1623
+ const next = agentBlockAfterTurn({
1624
+ existing: thread.agentBlock,
1625
+ parts,
1626
+ planMode: Boolean(thread.planMode),
1627
+ failed
1628
+ });
1629
+ const prev = thread.agentBlock ?? null;
1630
+ if (prev === next) return;
1631
+ if (prev && next && prev.source === next.source && prev.reason === next.reason && prev.at === next.at) {
1632
+ return;
1633
+ }
1634
+ updateThread(threadId, { agentBlock: next });
1635
+ }
1636
+ function chatBlockedSuffix(thread) {
1637
+ const raw = thread.agentBlock?.reason;
1638
+ if (!raw?.trim()) return "";
1639
+ return ` blocked:${raw.replace(/\s+/g, " ").slice(0, 80)}`;
1640
+ }
1641
+ function agentBlockFields(thread) {
1642
+ return {
1643
+ blockedReason: thread.agentBlock?.reason ?? null,
1644
+ blockedSource: thread.agentBlock?.source ?? null
1645
+ };
1646
+ }
1647
+ function stampAskUserBlock(resolveCaller, questions) {
1648
+ const asked = formatAskUserNotifyMessage(questions);
1649
+ try {
1650
+ writeAgentBlock(resolveCaller().id, makeAgentBlock("ask_user", asked));
1651
+ } catch {
1652
+ }
1653
+ return asked;
1654
+ }
1655
+ function stampPlanBlock(threadId, title) {
1656
+ const planTitle = title?.trim() || "Plan";
1657
+ if (threadId) {
1658
+ const reason = planTitle === "Plan" ? "Waiting for plan approval" : `Waiting for plan approval: ${planTitle}`;
1659
+ writeAgentBlock(threadId, makeAgentBlock("plan", reason));
1660
+ }
1661
+ return planTitle;
1662
+ }
1663
+ function stampNotifyBlock(threadId, reason, message) {
1664
+ const resolved = reason ?? "blocked";
1665
+ writeAgentBlock(threadId, makeAgentBlock(resolved === "input-required" ? "ask_user" : "reported", message));
1666
+ return resolved;
1667
+ }
1668
+
1464
1669
  // src/orchestrator/child-halt.ts
1465
1670
  var HALT_STATUSES = /* @__PURE__ */ new Set(["stopped", "error", "broken"]);
1466
1671
  function isIncompleteChildStatus(status) {
@@ -1565,6 +1770,36 @@ async function requireAgent(agent, opts) {
1565
1770
  return status;
1566
1771
  }
1567
1772
 
1773
+ // src/board/agent-block-view.ts
1774
+ function worktreeBoardStatus(group) {
1775
+ const order = ["running", "queued", "error", "broken"];
1776
+ for (const status of order) {
1777
+ if (group.some((t) => t.status === status)) return status;
1778
+ }
1779
+ return group[0]?.status ?? "idle";
1780
+ }
1781
+ function visibleAgentBlock(thread) {
1782
+ if (thread.status === "archived" || thread.status === "error" || thread.status === "broken") {
1783
+ return null;
1784
+ }
1785
+ const block = thread.agentBlock;
1786
+ if (!block?.reason?.trim()) return null;
1787
+ return block;
1788
+ }
1789
+ function worktreeAgentBlock(group) {
1790
+ let best = null;
1791
+ for (const thread of group) {
1792
+ const block = visibleAgentBlock(thread);
1793
+ if (!block) continue;
1794
+ if (!best || block.at > best.at) best = block;
1795
+ }
1796
+ return best;
1797
+ }
1798
+ function threadCardBlockFields(group) {
1799
+ const block = worktreeAgentBlock(group);
1800
+ return block ? { blockedReason: block.reason, blockedSource: block.source } : null;
1801
+ }
1802
+
1568
1803
  // src/board/home-board.ts
1569
1804
  var GLOBAL_WORKSPACE_ID = "__global__";
1570
1805
  function isHomeBoardThread(thread) {
@@ -1747,13 +1982,6 @@ function classifyWorktreeListBadges(group, viewerLogin = "", reviewLabel = "") {
1747
1982
  }
1748
1983
  ];
1749
1984
  }
1750
- function worktreeBoardStatus(group) {
1751
- const order = ["running", "queued", "error", "broken"];
1752
- for (const status of order) {
1753
- if (group.some((t) => t.status === status)) return status;
1754
- }
1755
- return group[0]?.status ?? "idle";
1756
- }
1757
1985
  function normalizeIssueKey(value) {
1758
1986
  return value.trim().toLowerCase().replace(/^#/, "");
1759
1987
  }
@@ -2109,7 +2337,8 @@ function toThreadCard(group) {
2109
2337
  repoPath: thread.repoPath,
2110
2338
  prUrl: withPr.prUrl,
2111
2339
  link: `sideboard://chat/${thread.id}`,
2112
- ...group.length > 1 ? { chatCount: group.length } : {}
2340
+ ...group.length > 1 ? { chatCount: group.length } : {},
2341
+ ...threadCardBlockFields(group) ?? {}
2113
2342
  };
2114
2343
  }
2115
2344
  function threadMatchesKind(thread, kind) {
@@ -2183,7 +2412,7 @@ function assembleHomeBoard(input) {
2183
2412
  }
2184
2413
  return { columns, hidden, totals };
2185
2414
  }
2186
- var HOME_BOARD_AGENT_HINT = "Home is a Kanban of workspaces (one card per checkout; sibling chat tabs nest as inner cards). Do not create a second workspace for a ticket, PR, or named branch that already has a live checkout \u2014 create_workspace / start_board_card return that chat (alreadyStarted). Creating from the default branch still opens a new isolated workspace. Columns are the path to merge: New (no PR) \u2192 Draft (draft PR) \u2192 Review (open PR) \u2192 Merged. ownership=mine is PRs you authored (or WIP with no PR); ownership=reviewing is someone else's PR (or a review-requested checkout). Viewer login comes from gh, not a setting. 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.";
2415
+ var HOME_BOARD_AGENT_HINT = "Home is a Kanban of workspaces (one card per checkout; sibling chats on a card are separate agents). Do not create a second workspace for a ticket, PR, or named branch that already has a live checkout \u2014 create_workspace / start_board_card return that checkout (alreadyStarted) and its chats. Add another agent with fork_chat. Creating from the default branch still opens a new isolated workspace. Columns are the path to merge: New (no PR) \u2192 Draft (draft PR) \u2192 Review (open PR) \u2192 Merged. ownership=mine is PRs you authored (or WIP with no PR); ownership=reviewing is someone else's PR (or a review-requested checkout). Viewer login comes from gh, not a setting. Archive removes the card to Settings \u2192 History. Queued/running are activity on the card, not columns. blockedReason means an agent on that card is waiting on a person (the question, plan approval, or a reported block) \u2014 read it and do not send_to_chat a check-in. Orchestration chats stay in the sidebar. Do not invent status.";
2187
2416
 
2188
2417
  // src/threads/create.ts
2189
2418
  function persistCreateAttachments(worktreePath, attachments) {
@@ -2406,7 +2635,7 @@ async function createThread(input, _onSetupLine) {
2406
2635
  return readThread(thread.id) ?? thread;
2407
2636
  }
2408
2637
  async function listLinearIssues(agent, repoPath) {
2409
- const { getAdapter: getAdapter2 } = await import("./agents-2FOWVJCN.js");
2638
+ const { getAdapter: getAdapter2 } = await import("./agents-DYCFLDNL.js");
2410
2639
  await requireAgent(agent, { requireLinear: true });
2411
2640
  const adapter = getAdapter2(agent);
2412
2641
  if (!adapter.listLinearIssues) {
@@ -2913,7 +3142,7 @@ async function adoptThread(input) {
2913
3142
  messages: input.messages ?? []
2914
3143
  });
2915
3144
  writeThread(thread);
2916
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-PBB6HY2D.js");
3145
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-UP6DQ3WL.js");
2917
3146
  await ensureWorkspace2(repoPath);
2918
3147
  const { ensureWorktreeSideboardIgnored } = await import("./worktree-exclude-RXZPTCNH.js");
2919
3148
  await ensureWorktreeSideboardIgnored(input.worktreePath);
@@ -4360,10 +4589,49 @@ async function confirmLand(thread, opts) {
4360
4589
  }
4361
4590
  }
4362
4591
 
4592
+ // src/diff/read-worktree-tail.ts
4593
+ import { closeSync, fstatSync, openSync, readSync } from "fs";
4594
+ import { join as join9 } from "path";
4595
+ var WORKTREE_TAIL_BYTES = 2e5;
4596
+ function readWorktreeFileTail(worktreePath, relativePath, maxBytes = WORKTREE_TAIL_BYTES) {
4597
+ if (!relativePath || relativePath.includes("..") || relativePath.startsWith("/")) {
4598
+ throw new Error("Invalid path");
4599
+ }
4600
+ const abs = join9(worktreePath, relativePath);
4601
+ const fd = openSync(abs, "r");
4602
+ try {
4603
+ const st = fstatSync(fd);
4604
+ if (!st.isFile()) throw new Error(`Not a file: ${relativePath}`);
4605
+ const size = st.size;
4606
+ const start = Math.max(0, size - maxBytes);
4607
+ const length = size - start;
4608
+ const buf = Buffer.alloc(length);
4609
+ if (length > 0) readSync(fd, buf, 0, length, start);
4610
+ if (buf.subarray(0, Math.min(buf.length, 8e3)).includes(0)) {
4611
+ return {
4612
+ path: relativePath,
4613
+ content: `(binary file, ${size} bytes)`,
4614
+ truncated: false,
4615
+ binary: true,
4616
+ encoding: "utf8"
4617
+ };
4618
+ }
4619
+ let content = buf.toString("utf8");
4620
+ const truncated = start > 0;
4621
+ if (truncated) {
4622
+ const nl = content.indexOf("\n");
4623
+ if (nl >= 0) content = content.slice(nl + 1);
4624
+ }
4625
+ return { path: relativePath, content, truncated, binary: false, encoding: "utf8" };
4626
+ } finally {
4627
+ closeSync(fd);
4628
+ }
4629
+ }
4630
+
4363
4631
  // src/skills/discover.ts
4364
4632
  import { existsSync as existsSync10, readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
4365
4633
  import { homedir } from "os";
4366
- import { join as join9 } from "path";
4634
+ import { join as join10 } from "path";
4367
4635
 
4368
4636
  // src/skills/bundled/long-running.ts
4369
4637
  var LONG_RUNNING_SKILL_COMMAND = "long-running";
@@ -4532,7 +4800,7 @@ function scanSkillsDir(dir, source, out) {
4532
4800
  }
4533
4801
  for (const entry of entries) {
4534
4802
  if (entry.startsWith(".")) continue;
4535
- const skillMd = join9(dir, entry, "SKILL.md");
4803
+ const skillMd = join10(dir, entry, "SKILL.md");
4536
4804
  if (!existsSync10(skillMd)) continue;
4537
4805
  try {
4538
4806
  if (!statSync3(skillMd).isFile()) continue;
@@ -4554,12 +4822,12 @@ function scanClaudePluginSkills(pluginsRoot, out) {
4554
4822
  return;
4555
4823
  }
4556
4824
  if (lookingForSkillsDir && entries.includes("SKILL.md")) {
4557
- const skill = readSkill(join9(dir, "SKILL.md"), "cli");
4825
+ const skill = readSkill(join10(dir, "SKILL.md"), "cli");
4558
4826
  if (skill) out.push(skill);
4559
4827
  }
4560
4828
  for (const entry of entries) {
4561
4829
  if (entry === "node_modules" || entry === ".git") continue;
4562
- const full = join9(dir, entry);
4830
+ const full = join10(dir, entry);
4563
4831
  try {
4564
4832
  if (!statSync3(full).isDirectory()) continue;
4565
4833
  } catch {
@@ -4579,17 +4847,17 @@ function discoverSkills(worktreePath) {
4579
4847
  const home = homedir();
4580
4848
  const collected = [];
4581
4849
  for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
4582
- scanSkillsDir(join9(worktreePath, rel), "workspace", collected);
4850
+ scanSkillsDir(join10(worktreePath, rel), "workspace", collected);
4583
4851
  }
4584
4852
  for (const abs of [
4585
- join9(home, ".claude/skills"),
4586
- join9(home, ".cursor/skills"),
4587
- join9(home, ".sideboard/skills"),
4588
- join9(home, ".brightsy/skills")
4853
+ join10(home, ".claude/skills"),
4854
+ join10(home, ".cursor/skills"),
4855
+ join10(home, ".sideboard/skills"),
4856
+ join10(home, ".brightsy/skills")
4589
4857
  ]) {
4590
4858
  scanSkillsDir(abs, "user", collected);
4591
4859
  }
4592
- scanClaudePluginSkills(join9(home, ".claude/plugins"), collected);
4860
+ scanClaudePluginSkills(join10(home, ".claude/plugins"), collected);
4593
4861
  collected.push(...bundledSkills());
4594
4862
  const rank = {
4595
4863
  workspace: 0,
@@ -4761,11 +5029,11 @@ function shiftQueuedItem(queue, queueAttachments) {
4761
5029
 
4762
5030
  // src/agents/instructions.ts
4763
5031
  import { existsSync as existsSync12 } from "fs";
4764
- import { join as join11 } from "path";
5032
+ import { join as join12 } from "path";
4765
5033
 
4766
5034
  // src/skills/detached-job-path.ts
4767
5035
  import { existsSync as existsSync11 } from "fs";
4768
- import { dirname as dirname2, join as join10 } from "path";
5036
+ import { dirname as dirname2, join as join11 } from "path";
4769
5037
  import { fileURLToPath } from "url";
4770
5038
  var DETACHED_JOB_FILENAMES = ["detached-job.cjs", "detached-job.js"];
4771
5039
  function compiledModuleDir() {
@@ -4782,7 +5050,7 @@ function compiledModuleDir() {
4782
5050
  }
4783
5051
  function detachedJobScriptIn(dir) {
4784
5052
  for (const name of DETACHED_JOB_FILENAMES) {
4785
- const script = join10(dir, "scripts", name);
5053
+ const script = join11(dir, "scripts", name);
4786
5054
  if (existsSync11(script)) return script;
4787
5055
  }
4788
5056
  return null;
@@ -5023,9 +5291,9 @@ function formatPrGateDirective() {
5023
5291
  }
5024
5292
  function formatProcessGuideDirective(opts) {
5025
5293
  const worktree = opts?.worktreePath?.trim();
5026
- const hasGraphSkill = !!worktree && existsSync12(join11(worktree, ".claude/skills/graph-engineering/SKILL.md"));
5027
- const hasReviewSkill = !!worktree && existsSync12(join11(worktree, ".claude/skills/review/SKILL.md"));
5028
- const hasInstallSkill = !!worktree && existsSync12(join11(worktree, ".claude/skills/worktree-install/SKILL.md"));
5294
+ const hasGraphSkill = !!worktree && existsSync12(join12(worktree, ".claude/skills/graph-engineering/SKILL.md"));
5295
+ const hasReviewSkill = !!worktree && existsSync12(join12(worktree, ".claude/skills/review/SKILL.md"));
5296
+ const hasInstallSkill = !!worktree && existsSync12(join12(worktree, ".claude/skills/worktree-install/SKILL.md"));
5029
5297
  return [
5030
5298
  "Process guides (recurring work only):",
5031
5299
  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,
@@ -5353,7 +5621,7 @@ async function syncThreadBranchFromGit(threadId) {
5353
5621
  // src/store/schedules.ts
5354
5622
  import { randomUUID as randomUUID2 } from "crypto";
5355
5623
  import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
5356
- import { join as join12 } from "path";
5624
+ import { join as join13 } from "path";
5357
5625
  import { Cron } from "croner";
5358
5626
  var DURATION_RE = /^(\d+)(s|m|h|d)$/i;
5359
5627
  var UNIT_MS = {
@@ -5363,7 +5631,7 @@ var UNIT_MS = {
5363
5631
  d: 864e5
5364
5632
  };
5365
5633
  function schedulesPath() {
5366
- return join12(appDataDir(), "schedules.json");
5634
+ return join13(appDataDir(), "schedules.json");
5367
5635
  }
5368
5636
  function parseDurationMs(every) {
5369
5637
  const m = every.trim().match(DURATION_RE);
@@ -5610,7 +5878,7 @@ function formatScheduledPrompt(name, prompt) {
5610
5878
  ${prompt}`;
5611
5879
  }
5612
5880
  async function defaultDeps() {
5613
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-QMKVXVUR.js");
5881
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-XEKODYDQ.js");
5614
5882
  const orch = getOrchestrator2();
5615
5883
  return {
5616
5884
  findThread: (id) => findThreadByRef(id),
@@ -6588,6 +6856,7 @@ var Orchestrator = class {
6588
6856
  const autoContinue = isJobContinuePrompt(promptText) || promptText.startsWith("The previous agent process ended before it finished.");
6589
6857
  const parked = takePendingTurnAttachments(thread);
6590
6858
  const sentAttachments = !autoContinue && parked.length > 0 ? parked : void 0;
6859
+ if (!autoContinue) clearAgentBlock(threadId);
6591
6860
  appendMessage(threadId, {
6592
6861
  role: "user",
6593
6862
  text: promptText,
@@ -6993,6 +7262,7 @@ var Orchestrator = class {
6993
7262
  } else {
6994
7263
  await syncThreadBranchFromGit(threadId);
6995
7264
  }
7265
+ settleAgentBlock(threadId, parts, exitCode !== 0 && !this.stoppedTurns.has(threadId));
6996
7266
  if (this.stoppedTurns.has(threadId)) {
6997
7267
  const stopped = writeLiveStatus(threadId, "stopped");
6998
7268
  if (stopped?.status === "stopped") {
@@ -7043,6 +7313,7 @@ var Orchestrator = class {
7043
7313
  } catch (err) {
7044
7314
  const message = err instanceof Error ? err.message : String(err);
7045
7315
  await syncThreadBranchFromGit(threadId).catch(() => void 0);
7316
+ settleAgentBlock(threadId, void 0, !this.stoppedTurns.has(threadId));
7046
7317
  if (this.stoppedTurns.has(threadId)) {
7047
7318
  const stopped = writeLiveStatus(threadId, "stopped");
7048
7319
  if (stopped?.status === "stopped") {
@@ -7758,6 +8029,7 @@ var Orchestrator = class {
7758
8029
  stillRunning,
7759
8030
  lastAgentParts: lastAgent?.parts
7760
8031
  });
8032
+ const block = thread.agentBlock?.reason?.trim() ? thread.agentBlock : null;
7761
8033
  return {
7762
8034
  text,
7763
8035
  status: thread.status,
@@ -7771,7 +8043,9 @@ var Orchestrator = class {
7771
8043
  lastAgent?.ts,
7772
8044
  thread.updatedAt
7773
8045
  ]),
7774
- usage: stillRunning ? null : lastAgent?.usage ?? null
8046
+ usage: stillRunning ? null : lastAgent?.usage ?? null,
8047
+ blockedReason: block?.reason ?? null,
8048
+ blockedSource: block?.source ?? null
7775
8049
  };
7776
8050
  }
7777
8051
  assertNotGlobal(thread, action) {
@@ -7832,12 +8106,12 @@ var Orchestrator = class {
7832
8106
  return "missing";
7833
8107
  }
7834
8108
  }
7835
- async readFile(threadRef, relativePath) {
8109
+ async readFile(threadRef, relativePath, tail) {
7836
8110
  const thread = this.requireThread(threadRef);
7837
8111
  if (relativePath.includes("..") || relativePath.startsWith("/")) {
7838
8112
  throw new Error("Invalid path");
7839
8113
  }
7840
- return readWorktreeFile(thread.worktreePath, relativePath);
8114
+ return (tail ? readWorktreeFileTail : readWorktreeFile)(thread.worktreePath, relativePath);
7841
8115
  }
7842
8116
  async readFileForUpload(threadRef, relativePath) {
7843
8117
  const thread = this.requireThread(threadRef);
@@ -8298,7 +8572,7 @@ var Orchestrator = class {
8298
8572
  this.enforceHistoryRetention();
8299
8573
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
8300
8574
  try {
8301
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-PBB6HY2D.js");
8575
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-UP6DQ3WL.js");
8302
8576
  await ensureWorkspace2(thread.repoPath);
8303
8577
  } catch {
8304
8578
  }
@@ -8486,6 +8760,9 @@ async function startOrchestration(opts) {
8486
8760
  export {
8487
8761
  isUnclaimedRunScriptRequest,
8488
8762
  isAdoptableRunScriptRequest,
8763
+ worktreeBoardStatus,
8764
+ visibleAgentBlock,
8765
+ worktreeAgentBlock,
8489
8766
  isHomeBoardThread,
8490
8767
  BOARD_COLUMN_DEFS,
8491
8768
  HOME_BOARD_CACHE_TTL_MS,
@@ -8494,7 +8771,6 @@ export {
8494
8771
  classifyWorktreeColumn,
8495
8772
  reviewLabelForRepo,
8496
8773
  classifyWorktreeListBadges,
8497
- worktreeBoardStatus,
8498
8774
  dedupeBoardIssues,
8499
8775
  issueNeedsWorkspacePick,
8500
8776
  isAccountWideIssueSource,
@@ -8556,6 +8832,16 @@ export {
8556
8832
  shouldRunHistoryCleanup,
8557
8833
  applyThreadIntoMain,
8558
8834
  cloneRepoIntoSideboard,
8835
+ formatAskUserNotifyMessage,
8836
+ formatNotifyOrchestratorPrompt,
8837
+ shouldNotifyOrchestrator,
8838
+ resolveNotifyCallerThread,
8839
+ notifyOrchestrator,
8840
+ chatBlockedSuffix,
8841
+ agentBlockFields,
8842
+ stampAskUserBlock,
8843
+ stampPlanBlock,
8844
+ stampNotifyBlock,
8559
8845
  detectAgents,
8560
8846
  requireAgent,
8561
8847
  createThread,
@@ -9,10 +9,10 @@ import {
9
9
  looksLikeSkillBody,
10
10
  skillCommandFromBody,
11
11
  withEventParentId
12
- } from "./chunk-FAGK33E2.js";
12
+ } from "./chunk-Y7JPL4WA.js";
13
13
  import {
14
14
  isOrchestratorThread
15
- } from "./chunk-A2G3ZBJS.js";
15
+ } from "./chunk-SKSOCIJT.js";
16
16
  import {
17
17
  codexUnattendedGitConfigArgs,
18
18
  mergeAgentGitAuthEnv,
@@ -38,13 +38,13 @@ import {
38
38
  packagedMcpStdioPath,
39
39
  parseCursorRunnerLine,
40
40
  resolveNodeLaunch
41
- } from "./chunk-FIBZEFKA.js";
41
+ } from "./chunk-IFURHRAR.js";
42
42
  import {
43
43
  extractJsonErrorMessage,
44
44
  formatUnknownDetail,
45
45
  looksLikeAgentFailureMessage,
46
46
  looksLikeBrightsyEmptyCompletion
47
- } from "./chunk-5N4Q46SG.js";
47
+ } from "./chunk-K2S33ZCB.js";
48
48
  import {
49
49
  enrichPathWithNpmGlobalBin,
50
50
  isConductorBundledCli,
@@ -1745,7 +1745,7 @@ var claudeAdapter = {
1745
1745
  );
1746
1746
  }
1747
1747
  const mode = permissionMode(thread);
1748
- const { isOrchestratorThread: isOrchestratorThread2 } = await import("./global-workspace-N2YYJKA5.js");
1748
+ const { isOrchestratorThread: isOrchestratorThread2 } = await import("./global-workspace-AK5PSPQ2.js");
1749
1749
  const isOrchestrator = isOrchestratorThread2(thread);
1750
1750
  const rpc = worktreeAgentRpc(isOrchestrator);
1751
1751
  const injectedServers = await buildInjectedMcpServers({