@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
@@ -5,6 +5,7 @@ import {
5
5
  } from "./chunk-LX5R5JOR.js";
6
6
  import {
7
7
  extractPresentedPlan,
8
+ isPresentPlanToolName,
8
9
  readPlanFile,
9
10
  writePlanFile
10
11
  } from "./chunk-4GK32AYX.js";
@@ -12,6 +13,7 @@ import {
12
13
  releaseCaffeinateHoldForThread
13
14
  } from "./chunk-5DZUWGKV.js";
14
15
  import {
16
+ SIDEBOARD_THREAD_ID_ENV,
15
17
  adjustBrightsyTurnExit,
16
18
  applyAgentRunnerHeapEnv,
17
19
  getAdapter,
@@ -21,7 +23,7 @@ import {
21
23
  parseSessionQuotaResetAt,
22
24
  planModeTurnInstruction,
23
25
  resolveQuotaFallbackAgent
24
- } from "./chunk-NT7RIWRR.js";
26
+ } from "./chunk-RK5PIWCW.js";
25
27
  import {
26
28
  REVIEW_TAB_TITLE,
27
29
  applyWorktreePkgCacheEnv,
@@ -31,12 +33,14 @@ import {
31
33
  copyConfiguredFiles,
32
34
  createChatTab,
33
35
  deriveTaskState,
36
+ extractPendingPlanQuestions,
34
37
  forkChatTab,
35
38
  forkMessageSlice,
36
39
  formatReviewWriteGateDirective,
37
40
  formatReviewWriteGateReminder,
38
41
  getDefaultRunScript,
39
42
  getRunMode,
43
+ isAskUserToolName,
40
44
  isJobContinuePrompt,
41
45
  isReviewWriteGatedThread,
42
46
  isWorktreeRunProcessKey,
@@ -58,7 +62,7 @@ import {
58
62
  worktreeDevProcessKey,
59
63
  worktreeRunProcessKey,
60
64
  worktreeSetupProcessKey
61
- } from "./chunk-HOMZKYHF.js";
65
+ } from "./chunk-YDXNCBA2.js";
62
66
  import {
63
67
  applyAgentEvent,
64
68
  clipAgentEventForPaint,
@@ -84,7 +88,7 @@ import {
84
88
  summarizeTurnStderr,
85
89
  toolDescription,
86
90
  turnFailChatText
87
- } from "./chunk-UKSJO36E.js";
91
+ } from "./chunk-CPA43FBK.js";
88
92
  import {
89
93
  SLACK_REPLY_FORMATTING,
90
94
  addWorkspace,
@@ -105,7 +109,7 @@ import {
105
109
  stageAbsolutePathsAsAttachments,
106
110
  stageBuffersAsAttachments,
107
111
  syncWorkspacesFromThreads
108
- } from "./chunk-QTZO4LPU.js";
112
+ } from "./chunk-TZ5JHIYK.js";
109
113
  import {
110
114
  addPrStackLayer,
111
115
  agentNicknameFromWorktreePath,
@@ -485,9 +489,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
485
489
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
486
490
  );
487
491
  }
488
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-7XEXTIHL.js");
492
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-5PJXO45Z.js");
489
493
  if (isGlobalThread2(thread)) {
490
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-SFZCZ3HR.js");
494
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-ERTHREGJ.js");
491
495
  ensureGlobalCoordinatorCwd2(
492
496
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
493
497
  );
@@ -1478,6 +1482,207 @@ function thisProcessShouldDrainAgentQueues() {
1478
1482
  return !isDesktopHostAlive();
1479
1483
  }
1480
1484
 
1485
+ // src/orchestrator/notify-orchestrator.ts
1486
+ var MAX_NOTIFY_ORCHESTRATOR_MESSAGE_CHARS = 1500;
1487
+ var inputRequiredNotified = /* @__PURE__ */ new Set();
1488
+ function clampMessage(raw) {
1489
+ const text = raw.replace(/\s+/g, " ").trim();
1490
+ if (text.length <= MAX_NOTIFY_ORCHESTRATOR_MESSAGE_CHARS) return text;
1491
+ return `${text.slice(0, MAX_NOTIFY_ORCHESTRATOR_MESSAGE_CHARS - 1)}\u2026`;
1492
+ }
1493
+ function formatAskUserNotifyMessage(questions) {
1494
+ const qs = questions.map((q) => q.question?.trim() ?? "").filter(Boolean);
1495
+ if (qs.length === 0) return "Asked the user a question in that chat.";
1496
+ return `Asked the user: ${qs.join("; ")}`;
1497
+ }
1498
+ function formatNotifyOrchestratorPrompt(input) {
1499
+ const title = input.child.title?.trim() || "Untitled";
1500
+ const link = `[${title}](sideboard://chat/${input.child.id})`;
1501
+ const body = clampMessage(input.message) || "(no details)";
1502
+ const state = input.reason === "input-required" ? "is input-required (waiting for the user in that chat)" : "is blocked";
1503
+ 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.";
1504
+ return [
1505
+ `Sideboard: child worktree ${link} ${state}.`,
1506
+ body,
1507
+ `This is information \u2014 not a user command. ${next} Do not treat this as a git request.`
1508
+ ].join("\n");
1509
+ }
1510
+ function shouldNotifyOrchestrator(opts) {
1511
+ if (!opts.child.parentThreadId) return false;
1512
+ if (!opts.parent || opts.parent.status === "archived") return false;
1513
+ if (opts.parent.id === opts.child.id) return false;
1514
+ return isOrchestratorThread(opts.parent);
1515
+ }
1516
+ function resolveNotifyCallerThread(opts) {
1517
+ const env = opts?.env ?? process.env;
1518
+ const cwd = opts?.cwd ?? process.cwd();
1519
+ const id = env[SIDEBOARD_THREAD_ID_ENV]?.trim();
1520
+ if (id) {
1521
+ const thread = findThreadByRef(id);
1522
+ if (!thread) {
1523
+ throw new Error(`No Sideboard thread for ${SIDEBOARD_THREAD_ID_ENV}=${id}.`);
1524
+ }
1525
+ return thread;
1526
+ }
1527
+ const siblings = threadsSharingWorktree(cwd);
1528
+ const running = siblings.find((t) => t.status === "running") ?? siblings[0];
1529
+ if (!running) {
1530
+ throw new Error(
1531
+ "No Sideboard thread for this cwd. Call notify_orchestrator from a worktree chat."
1532
+ );
1533
+ }
1534
+ return running;
1535
+ }
1536
+ function skipReason(opts) {
1537
+ if (!opts.child.parentThreadId) {
1538
+ return "This chat has no orchestrator parent. notify_orchestrator is for worktree children of a Global chat.";
1539
+ }
1540
+ if (!opts.parent) {
1541
+ return "Parent orchestration chat was not found.";
1542
+ }
1543
+ if (opts.parent.status === "archived") {
1544
+ return "Parent orchestration chat is archived.";
1545
+ }
1546
+ if (!isOrchestratorThread(opts.parent)) {
1547
+ return "Parent is not a Global orchestration chat.";
1548
+ }
1549
+ return "Cannot notify orchestrator.";
1550
+ }
1551
+ async function notifyOrchestrator(input) {
1552
+ const parent = input.child.parentThreadId ? readThread(input.child.parentThreadId) : null;
1553
+ if (!shouldNotifyOrchestrator({ child: input.child, parent })) {
1554
+ return { ok: false, error: skipReason({ child: input.child, parent }) };
1555
+ }
1556
+ const parentId = parent.id;
1557
+ if (input.reason === "input-required") {
1558
+ if (inputRequiredNotified.has(input.child.id)) {
1559
+ return { ok: true, parentThreadId: parentId, deduped: true };
1560
+ }
1561
+ inputRequiredNotified.add(input.child.id);
1562
+ }
1563
+ const prompt = formatNotifyOrchestratorPrompt({
1564
+ child: input.child,
1565
+ reason: input.reason,
1566
+ message: input.message
1567
+ });
1568
+ try {
1569
+ await input.send(parentId, prompt, { followUp: "queue" });
1570
+ } catch (err) {
1571
+ if (input.reason === "input-required") {
1572
+ inputRequiredNotified.delete(input.child.id);
1573
+ }
1574
+ const message = err instanceof Error ? err.message : String(err);
1575
+ return { ok: false, error: message };
1576
+ }
1577
+ return { ok: true, parentThreadId: parentId };
1578
+ }
1579
+
1580
+ // src/orchestrator/agent-block.ts
1581
+ var MAX_AGENT_BLOCK_REASON = 240;
1582
+ function clampBlockReason(raw) {
1583
+ const text = raw.replace(/\s+/g, " ").trim();
1584
+ if (!text) return "Waiting on a decision";
1585
+ if (text.length <= MAX_AGENT_BLOCK_REASON) return text;
1586
+ return `${text.slice(0, MAX_AGENT_BLOCK_REASON - 1)}\u2026`;
1587
+ }
1588
+ function makeAgentBlock(source, reason, at = (/* @__PURE__ */ new Date()).toISOString()) {
1589
+ return { source, reason: clampBlockReason(reason), at };
1590
+ }
1591
+ function lastTopLevelTool(parts) {
1592
+ if (!parts?.length) return null;
1593
+ for (let i = parts.length - 1; i >= 0; i--) {
1594
+ const part = parts[i];
1595
+ if (part.type === "tool" && !part.parentId) return part;
1596
+ }
1597
+ return null;
1598
+ }
1599
+ function decisionBlockFromParts(parts, planMode, at = (/* @__PURE__ */ new Date()).toISOString()) {
1600
+ const last = lastTopLevelTool(parts);
1601
+ if (last?.type === "tool" && isAskUserToolName(last.name)) {
1602
+ const pending = extractPendingPlanQuestions(parts);
1603
+ const reason = pending ? formatAskUserNotifyMessage(pending.questions) : "Waiting for an answer in that chat.";
1604
+ return makeAgentBlock("ask_user", reason, at);
1605
+ }
1606
+ if (!planMode || last?.type !== "tool") return null;
1607
+ const exited = /exitplanmode/i.test(last.name ?? "");
1608
+ if (!isPresentPlanToolName(last.name) && !exited) return null;
1609
+ const presented = extractPresentedPlan(parts);
1610
+ if (!presented && !exited) return null;
1611
+ const title = presented?.title && presented.title !== "Plan" ? presented.title : "";
1612
+ return makeAgentBlock(
1613
+ "plan",
1614
+ title ? `Waiting for plan approval: ${title}` : "Waiting for plan approval",
1615
+ at
1616
+ );
1617
+ }
1618
+ function agentBlockAfterTurn(input) {
1619
+ if (input.failed) return null;
1620
+ const decision = decisionBlockFromParts(input.parts, input.planMode);
1621
+ if (decision) return decision;
1622
+ if (input.existing && input.existing.source !== "reported" && lastTopLevelTool(input.parts)) {
1623
+ return null;
1624
+ }
1625
+ return input.existing ?? null;
1626
+ }
1627
+ function clearAgentBlock(threadId) {
1628
+ const thread = readThread(threadId);
1629
+ if (!thread?.agentBlock) return;
1630
+ updateThread(threadId, { agentBlock: null });
1631
+ }
1632
+ function writeAgentBlock(threadId, block) {
1633
+ const thread = readThread(threadId);
1634
+ if (!thread || thread.status === "archived") return;
1635
+ updateThread(threadId, { agentBlock: block });
1636
+ }
1637
+ function settleAgentBlock(threadId, parts, failed) {
1638
+ const thread = readThread(threadId);
1639
+ if (!thread || thread.status === "archived") return;
1640
+ const next = agentBlockAfterTurn({
1641
+ existing: thread.agentBlock,
1642
+ parts,
1643
+ planMode: Boolean(thread.planMode),
1644
+ failed
1645
+ });
1646
+ const prev = thread.agentBlock ?? null;
1647
+ if (prev === next) return;
1648
+ if (prev && next && prev.source === next.source && prev.reason === next.reason && prev.at === next.at) {
1649
+ return;
1650
+ }
1651
+ updateThread(threadId, { agentBlock: next });
1652
+ }
1653
+ function chatBlockedSuffix(thread) {
1654
+ const raw = thread.agentBlock?.reason;
1655
+ if (!raw?.trim()) return "";
1656
+ return ` blocked:${raw.replace(/\s+/g, " ").slice(0, 80)}`;
1657
+ }
1658
+ function agentBlockFields(thread) {
1659
+ return {
1660
+ blockedReason: thread.agentBlock?.reason ?? null,
1661
+ blockedSource: thread.agentBlock?.source ?? null
1662
+ };
1663
+ }
1664
+ function stampAskUserBlock(resolveCaller, questions) {
1665
+ const asked = formatAskUserNotifyMessage(questions);
1666
+ try {
1667
+ writeAgentBlock(resolveCaller().id, makeAgentBlock("ask_user", asked));
1668
+ } catch {
1669
+ }
1670
+ return asked;
1671
+ }
1672
+ function stampPlanBlock(threadId, title) {
1673
+ const planTitle = title?.trim() || "Plan";
1674
+ if (threadId) {
1675
+ const reason = planTitle === "Plan" ? "Waiting for plan approval" : `Waiting for plan approval: ${planTitle}`;
1676
+ writeAgentBlock(threadId, makeAgentBlock("plan", reason));
1677
+ }
1678
+ return planTitle;
1679
+ }
1680
+ function stampNotifyBlock(threadId, reason, message) {
1681
+ const resolved = reason ?? "blocked";
1682
+ writeAgentBlock(threadId, makeAgentBlock(resolved === "input-required" ? "ask_user" : "reported", message));
1683
+ return resolved;
1684
+ }
1685
+
1481
1686
  // src/orchestrator/child-halt.ts
1482
1687
  var HALT_STATUSES = /* @__PURE__ */ new Set(["stopped", "error", "broken"]);
1483
1688
  function isIncompleteChildStatus(status) {
@@ -1577,6 +1782,36 @@ async function requireAgent(agent, opts) {
1577
1782
  return status;
1578
1783
  }
1579
1784
 
1785
+ // src/board/agent-block-view.ts
1786
+ function worktreeBoardStatus(group) {
1787
+ const order = ["running", "queued", "error", "broken"];
1788
+ for (const status of order) {
1789
+ if (group.some((t) => t.status === status)) return status;
1790
+ }
1791
+ return group[0]?.status ?? "idle";
1792
+ }
1793
+ function visibleAgentBlock(thread) {
1794
+ if (thread.status === "archived" || thread.status === "error" || thread.status === "broken") {
1795
+ return null;
1796
+ }
1797
+ const block = thread.agentBlock;
1798
+ if (!block?.reason?.trim()) return null;
1799
+ return block;
1800
+ }
1801
+ function worktreeAgentBlock(group) {
1802
+ let best = null;
1803
+ for (const thread of group) {
1804
+ const block = visibleAgentBlock(thread);
1805
+ if (!block) continue;
1806
+ if (!best || block.at > best.at) best = block;
1807
+ }
1808
+ return best;
1809
+ }
1810
+ function threadCardBlockFields(group) {
1811
+ const block = worktreeAgentBlock(group);
1812
+ return block ? { blockedReason: block.reason, blockedSource: block.source } : null;
1813
+ }
1814
+
1580
1815
  // src/board/home-board.ts
1581
1816
  var GLOBAL_WORKSPACE_ID = "__global__";
1582
1817
  function isHomeBoardThread(thread) {
@@ -1692,13 +1927,6 @@ function classifyWorktreeColumn(group) {
1692
1927
  }
1693
1928
  return "new";
1694
1929
  }
1695
- function worktreeBoardStatus(group) {
1696
- const order = ["running", "queued", "error", "broken"];
1697
- for (const status of order) {
1698
- if (group.some((t) => t.status === status)) return status;
1699
- }
1700
- return group[0]?.status ?? "idle";
1701
- }
1702
1930
  function normalizeIssueKey(value) {
1703
1931
  return value.trim().toLowerCase().replace(/^#/, "");
1704
1932
  }
@@ -1988,7 +2216,8 @@ function toThreadCard(group) {
1988
2216
  repoPath: thread.repoPath,
1989
2217
  prUrl: withPr.prUrl,
1990
2218
  link: `sideboard://chat/${thread.id}`,
1991
- ...group.length > 1 ? { chatCount: group.length } : {}
2219
+ ...group.length > 1 ? { chatCount: group.length } : {},
2220
+ ...threadCardBlockFields(group) ?? {}
1992
2221
  };
1993
2222
  }
1994
2223
  function threadMatchesKind(thread, kind) {
@@ -2062,7 +2291,7 @@ function assembleHomeBoard(input) {
2062
2291
  }
2063
2292
  return { columns, hidden, totals };
2064
2293
  }
2065
- 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.";
2294
+ 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.";
2066
2295
 
2067
2296
  // src/threads/create.ts
2068
2297
  function persistCreateAttachments(worktreePath, attachments) {
@@ -2780,7 +3009,7 @@ async function adoptThread(input) {
2780
3009
  messages: input.messages ?? []
2781
3010
  });
2782
3011
  writeThread(thread);
2783
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-BJCHMGVG.js");
3012
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-WU2RNLDV.js");
2784
3013
  await ensureWorkspace2(repoPath);
2785
3014
  const { ensureWorktreeSideboardIgnored } = await import("./worktree-exclude-5N2KTYIT.js");
2786
3015
  await ensureWorktreeSideboardIgnored(input.worktreePath);
@@ -4214,10 +4443,49 @@ async function confirmLand(thread, opts) {
4214
4443
  }
4215
4444
  }
4216
4445
 
4446
+ // src/diff/read-worktree-tail.ts
4447
+ import { closeSync, fstatSync, openSync, readSync } from "fs";
4448
+ import { join as join9 } from "path";
4449
+ var WORKTREE_TAIL_BYTES = 2e5;
4450
+ function readWorktreeFileTail(worktreePath, relativePath, maxBytes = WORKTREE_TAIL_BYTES) {
4451
+ if (!relativePath || relativePath.includes("..") || relativePath.startsWith("/")) {
4452
+ throw new Error("Invalid path");
4453
+ }
4454
+ const abs = join9(worktreePath, relativePath);
4455
+ const fd = openSync(abs, "r");
4456
+ try {
4457
+ const st = fstatSync(fd);
4458
+ if (!st.isFile()) throw new Error(`Not a file: ${relativePath}`);
4459
+ const size = st.size;
4460
+ const start = Math.max(0, size - maxBytes);
4461
+ const length = size - start;
4462
+ const buf = Buffer.alloc(length);
4463
+ if (length > 0) readSync(fd, buf, 0, length, start);
4464
+ if (buf.subarray(0, Math.min(buf.length, 8e3)).includes(0)) {
4465
+ return {
4466
+ path: relativePath,
4467
+ content: `(binary file, ${size} bytes)`,
4468
+ truncated: false,
4469
+ binary: true,
4470
+ encoding: "utf8"
4471
+ };
4472
+ }
4473
+ let content = buf.toString("utf8");
4474
+ const truncated = start > 0;
4475
+ if (truncated) {
4476
+ const nl = content.indexOf("\n");
4477
+ if (nl >= 0) content = content.slice(nl + 1);
4478
+ }
4479
+ return { path: relativePath, content, truncated, binary: false, encoding: "utf8" };
4480
+ } finally {
4481
+ closeSync(fd);
4482
+ }
4483
+ }
4484
+
4217
4485
  // src/skills/discover.ts
4218
4486
  import { existsSync as existsSync10, readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
4219
4487
  import { homedir } from "os";
4220
- import { join as join9 } from "path";
4488
+ import { join as join10 } from "path";
4221
4489
 
4222
4490
  // src/skills/bundled/long-running.ts
4223
4491
  var LONG_RUNNING_SKILL_COMMAND = "long-running";
@@ -4386,7 +4654,7 @@ function scanSkillsDir(dir, source, out) {
4386
4654
  }
4387
4655
  for (const entry of entries) {
4388
4656
  if (entry.startsWith(".")) continue;
4389
- const skillMd = join9(dir, entry, "SKILL.md");
4657
+ const skillMd = join10(dir, entry, "SKILL.md");
4390
4658
  if (!existsSync10(skillMd)) continue;
4391
4659
  try {
4392
4660
  if (!statSync3(skillMd).isFile()) continue;
@@ -4408,12 +4676,12 @@ function scanClaudePluginSkills(pluginsRoot, out) {
4408
4676
  return;
4409
4677
  }
4410
4678
  if (lookingForSkillsDir && entries.includes("SKILL.md")) {
4411
- const skill = readSkill(join9(dir, "SKILL.md"), "cli");
4679
+ const skill = readSkill(join10(dir, "SKILL.md"), "cli");
4412
4680
  if (skill) out.push(skill);
4413
4681
  }
4414
4682
  for (const entry of entries) {
4415
4683
  if (entry === "node_modules" || entry === ".git") continue;
4416
- const full = join9(dir, entry);
4684
+ const full = join10(dir, entry);
4417
4685
  try {
4418
4686
  if (!statSync3(full).isDirectory()) continue;
4419
4687
  } catch {
@@ -4433,17 +4701,17 @@ function discoverSkills(worktreePath) {
4433
4701
  const home = homedir();
4434
4702
  const collected = [];
4435
4703
  for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
4436
- scanSkillsDir(join9(worktreePath, rel), "workspace", collected);
4704
+ scanSkillsDir(join10(worktreePath, rel), "workspace", collected);
4437
4705
  }
4438
4706
  for (const abs of [
4439
- join9(home, ".claude/skills"),
4440
- join9(home, ".cursor/skills"),
4441
- join9(home, ".sideboard/skills"),
4442
- join9(home, ".brightsy/skills")
4707
+ join10(home, ".claude/skills"),
4708
+ join10(home, ".cursor/skills"),
4709
+ join10(home, ".sideboard/skills"),
4710
+ join10(home, ".brightsy/skills")
4443
4711
  ]) {
4444
4712
  scanSkillsDir(abs, "user", collected);
4445
4713
  }
4446
- scanClaudePluginSkills(join9(home, ".claude/plugins"), collected);
4714
+ scanClaudePluginSkills(join10(home, ".claude/plugins"), collected);
4447
4715
  collected.push(...bundledSkills());
4448
4716
  const rank = {
4449
4717
  workspace: 0,
@@ -4615,11 +4883,11 @@ function shiftQueuedItem(queue, queueAttachments) {
4615
4883
 
4616
4884
  // src/agents/instructions.ts
4617
4885
  import { existsSync as existsSync12 } from "fs";
4618
- import { join as join11 } from "path";
4886
+ import { join as join12 } from "path";
4619
4887
 
4620
4888
  // src/skills/detached-job-path.ts
4621
4889
  import { existsSync as existsSync11 } from "fs";
4622
- import { dirname as dirname2, join as join10 } from "path";
4890
+ import { dirname as dirname2, join as join11 } from "path";
4623
4891
  import { fileURLToPath } from "url";
4624
4892
  var DETACHED_JOB_FILENAMES = ["detached-job.cjs", "detached-job.js"];
4625
4893
  function compiledModuleDir() {
@@ -4636,7 +4904,7 @@ function compiledModuleDir() {
4636
4904
  }
4637
4905
  function detachedJobScriptIn(dir) {
4638
4906
  for (const name of DETACHED_JOB_FILENAMES) {
4639
- const script = join10(dir, "scripts", name);
4907
+ const script = join11(dir, "scripts", name);
4640
4908
  if (existsSync11(script)) return script;
4641
4909
  }
4642
4910
  return null;
@@ -4855,9 +5123,9 @@ function formatPrGateDirective() {
4855
5123
  }
4856
5124
  function formatProcessGuideDirective(opts) {
4857
5125
  const worktree = opts?.worktreePath?.trim();
4858
- const hasGraphSkill = !!worktree && existsSync12(join11(worktree, ".claude/skills/graph-engineering/SKILL.md"));
4859
- const hasReviewSkill = !!worktree && existsSync12(join11(worktree, ".claude/skills/review/SKILL.md"));
4860
- const hasInstallSkill = !!worktree && existsSync12(join11(worktree, ".claude/skills/worktree-install/SKILL.md"));
5126
+ const hasGraphSkill = !!worktree && existsSync12(join12(worktree, ".claude/skills/graph-engineering/SKILL.md"));
5127
+ const hasReviewSkill = !!worktree && existsSync12(join12(worktree, ".claude/skills/review/SKILL.md"));
5128
+ const hasInstallSkill = !!worktree && existsSync12(join12(worktree, ".claude/skills/worktree-install/SKILL.md"));
4861
5129
  return [
4862
5130
  "Process guides (recurring work only):",
4863
5131
  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,
@@ -5023,7 +5291,7 @@ async function syncThreadBranchFromGit(threadId) {
5023
5291
  // src/store/schedules.ts
5024
5292
  import { randomUUID as randomUUID2 } from "crypto";
5025
5293
  import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
5026
- import { join as join12 } from "path";
5294
+ import { join as join13 } from "path";
5027
5295
  import { Cron } from "croner";
5028
5296
  var DURATION_RE = /^(\d+)(s|m|h|d)$/i;
5029
5297
  var UNIT_MS = {
@@ -5033,7 +5301,7 @@ var UNIT_MS = {
5033
5301
  d: 864e5
5034
5302
  };
5035
5303
  function schedulesPath() {
5036
- return join12(appDataDir(), "schedules.json");
5304
+ return join13(appDataDir(), "schedules.json");
5037
5305
  }
5038
5306
  function parseDurationMs(every) {
5039
5307
  const m = every.trim().match(DURATION_RE);
@@ -5271,7 +5539,7 @@ function formatScheduledPrompt(name, prompt) {
5271
5539
  ${prompt}`;
5272
5540
  }
5273
5541
  async function defaultDeps() {
5274
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-6QJLM6Y4.js");
5542
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-K66OMTQO.js");
5275
5543
  const orch = getOrchestrator2();
5276
5544
  return {
5277
5545
  findThread: (id) => findThreadByRef(id),
@@ -6249,6 +6517,7 @@ var Orchestrator = class {
6249
6517
  const autoContinue = isJobContinuePrompt(promptText) || promptText.startsWith("The previous agent process ended before it finished.");
6250
6518
  const parked = takePendingTurnAttachments(thread);
6251
6519
  const sentAttachments = !autoContinue && parked.length > 0 ? parked : void 0;
6520
+ if (!autoContinue) clearAgentBlock(threadId);
6252
6521
  appendMessage(threadId, {
6253
6522
  role: "user",
6254
6523
  text: promptText,
@@ -6654,6 +6923,7 @@ var Orchestrator = class {
6654
6923
  } else {
6655
6924
  await syncThreadBranchFromGit(threadId);
6656
6925
  }
6926
+ settleAgentBlock(threadId, parts, exitCode !== 0 && !this.stoppedTurns.has(threadId));
6657
6927
  if (this.stoppedTurns.has(threadId)) {
6658
6928
  const stopped = writeLiveStatus(threadId, "stopped");
6659
6929
  if (stopped?.status === "stopped") {
@@ -6704,6 +6974,7 @@ var Orchestrator = class {
6704
6974
  } catch (err) {
6705
6975
  const message = err instanceof Error ? err.message : String(err);
6706
6976
  await syncThreadBranchFromGit(threadId).catch(() => void 0);
6977
+ settleAgentBlock(threadId, void 0, !this.stoppedTurns.has(threadId));
6707
6978
  if (this.stoppedTurns.has(threadId)) {
6708
6979
  const stopped = writeLiveStatus(threadId, "stopped");
6709
6980
  if (stopped?.status === "stopped") {
@@ -7419,6 +7690,7 @@ var Orchestrator = class {
7419
7690
  stillRunning,
7420
7691
  lastAgentParts: lastAgent?.parts
7421
7692
  });
7693
+ const block = thread.agentBlock?.reason?.trim() ? thread.agentBlock : null;
7422
7694
  return {
7423
7695
  text,
7424
7696
  status: thread.status,
@@ -7432,7 +7704,9 @@ var Orchestrator = class {
7432
7704
  lastAgent?.ts,
7433
7705
  thread.updatedAt
7434
7706
  ]),
7435
- usage: stillRunning ? null : lastAgent?.usage ?? null
7707
+ usage: stillRunning ? null : lastAgent?.usage ?? null,
7708
+ blockedReason: block?.reason ?? null,
7709
+ blockedSource: block?.source ?? null
7436
7710
  };
7437
7711
  }
7438
7712
  assertNotGlobal(thread, action) {
@@ -7493,12 +7767,12 @@ var Orchestrator = class {
7493
7767
  return "missing";
7494
7768
  }
7495
7769
  }
7496
- async readFile(threadRef, relativePath) {
7770
+ async readFile(threadRef, relativePath, tail) {
7497
7771
  const thread = this.requireThread(threadRef);
7498
7772
  if (relativePath.includes("..") || relativePath.startsWith("/")) {
7499
7773
  throw new Error("Invalid path");
7500
7774
  }
7501
- return readWorktreeFile(thread.worktreePath, relativePath);
7775
+ return (tail ? readWorktreeFileTail : readWorktreeFile)(thread.worktreePath, relativePath);
7502
7776
  }
7503
7777
  async readFileForUpload(threadRef, relativePath) {
7504
7778
  const thread = this.requireThread(threadRef);
@@ -7959,7 +8233,7 @@ var Orchestrator = class {
7959
8233
  this.enforceHistoryRetention();
7960
8234
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
7961
8235
  try {
7962
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-BJCHMGVG.js");
8236
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-WU2RNLDV.js");
7963
8237
  await ensureWorkspace2(thread.repoPath);
7964
8238
  } catch {
7965
8239
  }
@@ -8150,6 +8424,13 @@ export {
8150
8424
  MAX_RUN_LOG_CHARS,
8151
8425
  DEFAULT_RUN_LOG_TAIL_CHARS,
8152
8426
  sliceRunLogOutput,
8427
+ resolveNotifyCallerThread,
8428
+ notifyOrchestrator,
8429
+ chatBlockedSuffix,
8430
+ agentBlockFields,
8431
+ stampAskUserBlock,
8432
+ stampPlanBlock,
8433
+ stampNotifyBlock,
8153
8434
  BOARD_COLUMN_DEFS,
8154
8435
  HOME_BOARD_CACHE_TTL_MS,
8155
8436
  dedupeBoardIssues,
@@ -199,8 +199,8 @@ function looksLikeInvalidAgentSession(text) {
199
199
  if (!lower) return false;
200
200
  return /session not found/.test(lower) || /no conversation found/.test(lower) || /conversation .+ not found/.test(lower) || /thread .+ not found/.test(lower) || /unknown session/.test(lower) || /invalid session/.test(lower) || /session .+ (missing|expired|deleted|gone)/.test(lower) || /cannot resume/.test(lower) || /failed to (load|resume|open) session/.test(lower) || /corrupt local agent checkpoint/.test(lower) || /missing root blob/.test(lower) || /\bagent\b.{0,120}\bnot found\b/.test(lower) || /\brun\b.{0,80}\bnot found for agent\b/.test(lower);
201
201
  }
202
- function looksLikeCursorHttp2StreamClose(text) {
203
- return /nghttp2/i.test(text.trim());
202
+ function looksLikeCursorStreamCutOff(text) {
203
+ return /nghttp2|premature close|\[unavailable\]\s+error\b/i.test(text.trim());
204
204
  }
205
205
  function looksLikeBrightsyEmptyCompletion(text) {
206
206
  const core = text.trim().replace(/^exit\s*\d+:\s*/i, "").replace(/^error:\s*/i, "").trim();
@@ -211,7 +211,7 @@ function looksLikeRetryableRunnerCrash(text) {
211
211
  if (looksLikeInvalidAgentSession(text)) return false;
212
212
  if (looksLikeV8Oom(text)) return false;
213
213
  if (looksLikeHugeToolResultDump(text)) return true;
214
- if (looksLikeCursorHttp2StreamClose(text)) return true;
214
+ if (looksLikeCursorStreamCutOff(text)) return true;
215
215
  if (looksLikeBrightsyEmptyCompletion(text)) return true;
216
216
  const lower = text.trim().toLowerCase();
217
217
  if (/cannot find (?:package|module)|err_module_not_found/.test(lower)) return false;
@@ -288,7 +288,7 @@ function humanizeAgentFailDetail(detail) {
288
288
  if (/corrupt local agent checkpoint|missing root blob|truncated crash dump/.test(lower)) {
289
289
  return `${raw} \u2014 retry the turn (Sideboard will start a fresh Cursor session).`;
290
290
  }
291
- if (/connection stalled/.test(lower) || looksLikeCursorHttp2StreamClose(raw)) {
291
+ if (/connection stalled/.test(lower) || looksLikeCursorStreamCutOff(raw)) {
292
292
  return /retry the turn/i.test(raw) ? raw : `${raw} \u2014 retry the turn (Sideboard will start a fresh Cursor session).`;
293
293
  }
294
294
  if (looksLikeBrightsyEmptyCompletion(raw)) {