@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
@@ -28,7 +28,9 @@ import {
28
28
  HOME_BOARD_AGENT_HINT,
29
29
  HOME_BOARD_CACHE_TTL_MS,
30
30
  MAX_RUN_LOG_CHARS,
31
+ agentBlockFields,
31
32
  assembleHomeBoard,
33
+ chatBlockedSuffix,
32
34
  createSchedule,
33
35
  dedupeBoardIssues,
34
36
  dedupeBoardPrs,
@@ -45,14 +47,19 @@ import {
45
47
  issueNeedsWorkspacePick,
46
48
  listSchedules,
47
49
  localhostPreviewUrl,
50
+ notifyOrchestrator,
48
51
  readTurnLive,
52
+ resolveNotifyCallerThread,
49
53
  resolveOrchChildFollowUp,
50
54
  resolveScheduleThreadId,
51
55
  scheduleNeedsCaffeinate,
52
56
  sliceRunLogOutput,
57
+ stampAskUserBlock,
58
+ stampNotifyBlock,
59
+ stampPlanBlock,
53
60
  syncBoardPins,
54
61
  updateSchedule
55
- } from "../chunk-FELFTNH2.js";
62
+ } from "../chunk-2P75E243.js";
56
63
  import "../chunk-LX5R5JOR.js";
57
64
  import {
58
65
  PRESENT_PLAN_REQUIRES_PLAN_MODE,
@@ -71,7 +78,7 @@ import {
71
78
  sideboardMcpProfile,
72
79
  trustSystemCertificates,
73
80
  worktreeMcpToolNames
74
- } from "../chunk-NT7RIWRR.js";
81
+ } from "../chunk-RK5PIWCW.js";
75
82
  import "../chunk-TQRSVE7D.js";
76
83
  import "../chunk-P2BSMNHB.js";
77
84
  import {
@@ -85,19 +92,19 @@ import {
85
92
  stopDetachedJob,
86
93
  threadsSharingWorktree,
87
94
  waitForDetachedJob,
88
- waitForTurnToolResult
89
- } from "../chunk-HOMZKYHF.js";
95
+ waitForTurnToolResult,
96
+ workspaceAgentChatSummaries
97
+ } from "../chunk-YDXNCBA2.js";
90
98
  import {
91
99
  clipToolResultForStore,
92
100
  isInternalAgentStatusText,
93
101
  looksLikeHugeToolResultDump
94
- } from "../chunk-UKSJO36E.js";
102
+ } from "../chunk-CPA43FBK.js";
95
103
  import {
96
104
  GLOBAL_WORKSPACE_ID,
97
105
  isCloudCoordinatorThread,
98
- isOrchestratorThread,
99
106
  listWorkspaces
100
- } from "../chunk-QTZO4LPU.js";
107
+ } from "../chunk-TZ5JHIYK.js";
101
108
  import {
102
109
  canonicalizeRepoPath,
103
110
  formatGhLandError,
@@ -138,10 +145,7 @@ import {
138
145
  } from "../chunk-WWUABK6B.js";
139
146
  import "../chunk-FJZWM2ON.js";
140
147
  import "../chunk-UN5Q7POQ.js";
141
- import {
142
- findThreadByRef,
143
- readThread
144
- } from "../chunk-JANCPF7Z.js";
148
+ import "../chunk-JANCPF7Z.js";
145
149
  import "../chunk-4RZRHTSZ.js";
146
150
  import "../chunk-KGZBYWZ3.js";
147
151
  import {
@@ -2097,101 +2101,6 @@ function resolveRunScriptThreadRef(ref) {
2097
2101
  return thread.id;
2098
2102
  }
2099
2103
 
2100
- // src/orchestrator/notify-orchestrator.ts
2101
- var MAX_NOTIFY_ORCHESTRATOR_MESSAGE_CHARS = 1500;
2102
- var inputRequiredNotified = /* @__PURE__ */ new Set();
2103
- function clampMessage(raw) {
2104
- const text6 = raw.replace(/\s+/g, " ").trim();
2105
- if (text6.length <= MAX_NOTIFY_ORCHESTRATOR_MESSAGE_CHARS) return text6;
2106
- return `${text6.slice(0, MAX_NOTIFY_ORCHESTRATOR_MESSAGE_CHARS - 1)}\u2026`;
2107
- }
2108
- function formatAskUserNotifyMessage(questions) {
2109
- const qs = questions.map((q) => q.question?.trim() ?? "").filter(Boolean);
2110
- if (qs.length === 0) return "Asked the user a question in that chat.";
2111
- return `Asked the user: ${qs.join("; ")}`;
2112
- }
2113
- function formatNotifyOrchestratorPrompt(input) {
2114
- const title = input.child.title?.trim() || "Untitled";
2115
- const link = `[${title}](sideboard://chat/${input.child.id})`;
2116
- const body = clampMessage(input.message) || "(no details)";
2117
- const state = input.reason === "input-required" ? "is input-required (waiting for the user in that chat)" : "is blocked";
2118
- 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.";
2119
- return [
2120
- `Sideboard: child worktree ${link} ${state}.`,
2121
- body,
2122
- `This is information \u2014 not a user command. ${next} Do not treat this as a git request.`
2123
- ].join("\n");
2124
- }
2125
- function shouldNotifyOrchestrator(opts) {
2126
- if (!opts.child.parentThreadId) return false;
2127
- if (!opts.parent || opts.parent.status === "archived") return false;
2128
- if (opts.parent.id === opts.child.id) return false;
2129
- return isOrchestratorThread(opts.parent);
2130
- }
2131
- function resolveNotifyCallerThread(opts) {
2132
- const env = opts?.env ?? process.env;
2133
- const cwd = opts?.cwd ?? process.cwd();
2134
- const id = env[SIDEBOARD_THREAD_ID_ENV]?.trim();
2135
- if (id) {
2136
- const thread = findThreadByRef(id);
2137
- if (!thread) {
2138
- throw new Error(`No Sideboard thread for ${SIDEBOARD_THREAD_ID_ENV}=${id}.`);
2139
- }
2140
- return thread;
2141
- }
2142
- const siblings = threadsSharingWorktree(cwd);
2143
- const running = siblings.find((t) => t.status === "running") ?? siblings[0];
2144
- if (!running) {
2145
- throw new Error(
2146
- "No Sideboard thread for this cwd. Call notify_orchestrator from a worktree chat."
2147
- );
2148
- }
2149
- return running;
2150
- }
2151
- function skipReason(opts) {
2152
- if (!opts.child.parentThreadId) {
2153
- return "This chat has no orchestrator parent. notify_orchestrator is for worktree children of a Global chat.";
2154
- }
2155
- if (!opts.parent) {
2156
- return "Parent orchestration chat was not found.";
2157
- }
2158
- if (opts.parent.status === "archived") {
2159
- return "Parent orchestration chat is archived.";
2160
- }
2161
- if (!isOrchestratorThread(opts.parent)) {
2162
- return "Parent is not a Global orchestration chat.";
2163
- }
2164
- return "Cannot notify orchestrator.";
2165
- }
2166
- async function notifyOrchestrator(input) {
2167
- const parent = input.child.parentThreadId ? readThread(input.child.parentThreadId) : null;
2168
- if (!shouldNotifyOrchestrator({ child: input.child, parent })) {
2169
- return { ok: false, error: skipReason({ child: input.child, parent }) };
2170
- }
2171
- const parentId = parent.id;
2172
- if (input.reason === "input-required") {
2173
- if (inputRequiredNotified.has(input.child.id)) {
2174
- return { ok: true, parentThreadId: parentId, deduped: true };
2175
- }
2176
- inputRequiredNotified.add(input.child.id);
2177
- }
2178
- const prompt = formatNotifyOrchestratorPrompt({
2179
- child: input.child,
2180
- reason: input.reason,
2181
- message: input.message
2182
- });
2183
- try {
2184
- await input.send(parentId, prompt, { followUp: "queue" });
2185
- } catch (err) {
2186
- if (input.reason === "input-required") {
2187
- inputRequiredNotified.delete(input.child.id);
2188
- }
2189
- const message = err instanceof Error ? err.message : String(err);
2190
- return { ok: false, error: message };
2191
- }
2192
- return { ok: true, parentThreadId: parentId };
2193
- }
2194
-
2195
2104
  // src/mcp/thread-visibility.ts
2196
2105
  function lastMessagePreview(messages, max = 160) {
2197
2106
  if (!messages?.length) return null;
@@ -2209,7 +2118,8 @@ function childThreadRefs(parentId, threads) {
2209
2118
  title: t.title,
2210
2119
  status: t.status,
2211
2120
  agent: t.agent,
2212
- lastText: lastMessagePreview(t.messages, 120)
2121
+ lastText: lastMessagePreview(t.messages, 120),
2122
+ ...t.agentBlock?.reason ? { blockedReason: t.agentBlock.reason, blockedSource: t.agentBlock.source } : {}
2213
2123
  }));
2214
2124
  }
2215
2125
 
@@ -3834,6 +3744,7 @@ async function createOrchChildThread(orch, args, resolveNewThreadOptions) {
3834
3744
  link: `sideboard://chat/${thread.id}`,
3835
3745
  parentChatId: thread.parentThreadId,
3836
3746
  parentThreadId: thread.parentThreadId,
3747
+ chats: workspaceAgentChatSummaries(thread.worktreePath),
3837
3748
  ...alreadyStarted ? { alreadyStarted: true } : {},
3838
3749
  ...opts.coercedFrom ? { agentCoercedFrom: opts.coercedFrom } : {},
3839
3750
  ...opts.ignoredAgent ? { agentIgnored: opts.ignoredAgent } : {},
@@ -3920,7 +3831,7 @@ async function startMcpServer() {
3920
3831
  );
3921
3832
  server.tool(
3922
3833
  "list_chats",
3923
- "List chats across all projects (one summary line each \u2014 token-frugal). Includes parent id, last message preview, and live progress so you can see workspace children. Each line ends with sideboard://chat/<id> \u2014 use that URL in markdown links so the UI can open the chat.",
3834
+ "List chats across all projects (one summary line each \u2014 token-frugal). Includes parent id, last message preview, blocked: when an agent is waiting on a person, and live progress so you can see workspace children. Each line ends with sideboard://chat/<id> \u2014 use that URL in markdown links so the UI can open the chat.",
3924
3835
  {},
3925
3836
  async () => {
3926
3837
  const threads = orch.getThreads(true);
@@ -3932,7 +3843,7 @@ async function startMcpServer() {
3932
3843
  const previewBit = preview ? ` ${preview}` : "";
3933
3844
  const err = t.lastError ? ` error:${t.lastError.replace(/\s+/g, " ").slice(0, 60)}` : "";
3934
3845
  const progress = live?.summary && !isInternalAgentStatusText(live.summary) ? ` ${live.summary}` : "";
3935
- return `${t.id.slice(0, 8)} ${t.status.padEnd(9)} ${t.agent.padEnd(8)} ${repo} ${t.sourceType}:${t.sourceRef} ${t.title}${parent}${previewBit}${err} sideboard://chat/${t.id}${t.devPort ? ` http://localhost:${t.devPort}` : ""}${progress}`;
3846
+ return `${t.id.slice(0, 8)} ${t.status.padEnd(9)} ${t.agent.padEnd(8)} ${repo} ${t.sourceType}:${t.sourceRef} ${t.title}${parent}${previewBit}${chatBlockedSuffix(t)}${err} sideboard://chat/${t.id}${t.devPort ? ` http://localhost:${t.devPort}` : ""}${progress}`;
3936
3847
  });
3937
3848
  return {
3938
3849
  content: [{ type: "text", text: lines.join("\n") || "(no chats)" }]
@@ -3941,7 +3852,7 @@ async function startMcpServer() {
3941
3852
  );
3942
3853
  server.tool(
3943
3854
  "list_board",
3944
- "Home Kanban of worktrees (New, Draft, Review, Merged) \u2014 one card per checkout; sibling chat tabs nest as inner cards. Same cards as desktop Home. Path to merge: no PR \u2192 draft PR \u2192 open PR \u2192 merged. Archive removes the card to Settings \u2192 History. Queued/running are activity on the card, not columns. Orchestration chats are not on the board. Filters: query, repoPath, kind (ticket/PR/branch source), ownership (mine = your PRs / WIP; reviewing = someone else's PR), column, limit (default 40). create_workspace adds a workspace (and a Home card), or returns the live one if that ticket/PR/named branch is already checked out.",
3855
+ "Home Kanban of workspaces (New, Draft, Review, Merged) \u2014 one card per checkout; sibling chats on that card are separate agents. Same cards as desktop Home. Path to merge: no PR \u2192 draft PR \u2192 open PR \u2192 merged. Archive removes the card to Settings \u2192 History. Queued/running are activity on the card, not columns. Orchestration chats are not on the board. Filters: query, repoPath, kind (ticket/PR/branch source), ownership (mine = your PRs / WIP; reviewing = someone else's PR), column, limit (default 40). create_workspace adds a workspace (and a Home card), or returns the live checkout if that ticket/PR/named branch is already checked out. That reuse is one checkout, not one agent \u2014 fork_chat to add another.",
3945
3856
  {
3946
3857
  query: z8.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
3947
3858
  repoPath: z8.string().optional().describe("Limit to one project path from list_projects"),
@@ -3981,7 +3892,7 @@ async function startMcpServer() {
3981
3892
  );
3982
3893
  server.tool(
3983
3894
  "get_chat",
3984
- "Get a compact chat summary by id/ref. Includes last message preview, parentChatId, and child workspace chats (status + lastText). While running, includes progress (last tool/thinking) and lastActivityAt. Includes usage (billed token + costUsd totals when providers reported cost) and lastTurnUsage.",
3895
+ "Get a compact chat summary by id/ref. Includes last message preview, parentChatId, and child workspace chats (status + lastText). While running, includes progress (last tool/thinking) and lastActivityAt. Includes usage (billed token + costUsd totals when providers reported cost) and lastTurnUsage. blockedReason is why that agent is waiting on a person (question, plan approval, or a reported block).",
3985
3896
  { ref: z8.string() },
3986
3897
  async ({ ref }) => {
3987
3898
  const t = orch.getThread(ref);
@@ -4011,6 +3922,7 @@ async function startMcpServer() {
4011
3922
  devPort: t.devPort,
4012
3923
  prUrl: t.prUrl,
4013
3924
  lastError: t.lastError ?? null,
3925
+ ...agentBlockFields(t),
4014
3926
  stillRunning,
4015
3927
  progress: liveSummary ?? (stillRunning && t.status === "queued" ? "Queued \u2014 waiting for a concurrency slot" : null),
4016
3928
  lastActivityAt: live?.updatedAt ?? null,
@@ -4072,7 +3984,7 @@ async function startMcpServer() {
4072
3984
  };
4073
3985
  await notifyParent({
4074
3986
  reason: "input-required",
4075
- message: formatAskUserNotifyMessage(questions)
3987
+ message: stampAskUserBlock(resolveNotifyCallerThread, questions)
4076
3988
  });
4077
3989
  return mcpJson(payload);
4078
3990
  }
@@ -4104,7 +4016,7 @@ async function startMcpServer() {
4104
4016
  const payload = {
4105
4017
  ok: true,
4106
4018
  path,
4107
- title: title?.trim() || "Plan",
4019
+ title: stampPlanBlock(t?.id, title),
4108
4020
  message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
4109
4021
  };
4110
4022
  return mcpJson(payload);
@@ -4309,7 +4221,7 @@ async function startMcpServer() {
4309
4221
  if (worktreeProfile) {
4310
4222
  server.tool(
4311
4223
  "notify_orchestrator",
4312
- "Wake the parent Global orchestrator with a short status so it does not have to poll wait_for_turn. Use when you are blocked and the parent may have moved on (missing access, waiting on something the coordinator must handle). ask_user already notifies for input-required \u2014 do not call this for the same question. Information only: never a git command. Do not use this to steer other worktrees.",
4224
+ "Wake the parent Global orchestrator with a short status so it does not have to poll wait_for_turn. Use when you are blocked and the parent may have moved on (missing access, waiting on something the coordinator must handle). The message is shown on the sidebar and board until the next user message in this chat. ask_user already notifies for input-required \u2014 do not call this for the same question. Information only: never a git command. Do not use this to steer other worktrees.",
4313
4225
  {
4314
4226
  message: z8.string().min(1).max(1500).describe("Why you are blocked / what the parent should know (short)."),
4315
4227
  reason: z8.enum(["input-required", "blocked"]).optional().describe(
@@ -4321,7 +4233,7 @@ async function startMcpServer() {
4321
4233
  const child = resolveNotifyCallerThread();
4322
4234
  const result = await notifyOrchestrator({
4323
4235
  child,
4324
- reason: reason ?? "blocked",
4236
+ reason: stampNotifyBlock(child.id, reason, message),
4325
4237
  message,
4326
4238
  send: (id, prompt, opts) => orch.send(id, prompt, opts)
4327
4239
  });
@@ -4379,7 +4291,7 @@ async function startMcpServer() {
4379
4291
  );
4380
4292
  server.tool(
4381
4293
  "create_workspace",
4382
- `Create a workspace (isolated git worktree + first chat) from branch, pr, or ticket. A ticket, PR, or named branch may have only one live worktree \u2014 if one already matches, returns it (alreadyStarted=true) instead of a second checkout. Creating from the default branch still opens a new isolated worktree. Pass repoPath from list_projects. cowboy=true uses the project folder on the default branch (no isolated worktree; land is commit+push). From an orchestration chat, omit parentChatId (Sideboard binds the child to this chat) or pass the exact id from the turn reminder \u2014 never invent a uuid. Omit agent/model \u2014 Sideboard applies ${accountDefaultsHint}. Do not pass your own agent or agent=cursor; those are ignored. Setup (settings.toml, .cursor/worktrees.json, or script/setup) runs in the background in parallel with the first turn (skipped for cowboy). Then use send_to_chat to chat.`,
4294
+ `Create a workspace (isolated git checkout + first chat) from branch, pr, or ticket. A ticket, PR, or named branch has one checkout \u2014 if one already matches, returns it (alreadyStarted=true) instead of a second checkout. That is not one agent: chats lists every agent on that checkout. Add another with fork_chat; send_to_chat only the chat that should do this job. Creating from the default branch still opens a new isolated checkout. Pass repoPath from list_projects. cowboy=true uses the project folder on the default branch (no isolated worktree; land is commit+push). From an orchestration chat, omit parentChatId (Sideboard binds the child to this chat) or pass the exact id from the turn reminder \u2014 never invent a uuid. Omit agent/model \u2014 Sideboard applies ${accountDefaultsHint}. Do not pass your own agent or agent=cursor; those are ignored. Setup (settings.toml, .cursor/worktrees.json, or script/setup) runs in the background in parallel with the first turn (skipped for cowboy).`,
4383
4295
  {
4384
4296
  sourceType: z8.enum(["branch", "pr", "ticket"]),
4385
4297
  sourceRef: z8.string(),
@@ -4409,7 +4321,7 @@ async function startMcpServer() {
4409
4321
  );
4410
4322
  server.tool(
4411
4323
  "start_board_card",
4412
- "Same as create_workspace for a ticket, PR, or named branch (attaches issue text when Sideboard can resolve it). Does not create a second worktree when one already matches \u2014 returns that thread (alreadyStarted). Then send_to_chat.",
4324
+ "Same as create_workspace for a ticket, PR, or named branch (attaches issue text when Sideboard can resolve it). Does not create a second checkout when one already matches \u2014 returns that workspace (alreadyStarted) and its chats. A workspace can have many agents. fork_chat to add one; send_to_chat only the chat that should do this job.",
4413
4325
  {
4414
4326
  kind: z8.enum(["ticket", "pr", "branch"]),
4415
4327
  ref: z8.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
@@ -4436,7 +4348,8 @@ async function startMcpServer() {
4436
4348
  id: existing.id,
4437
4349
  title: existing.title,
4438
4350
  status: existing.status,
4439
- link: `sideboard://chat/${existing.id}`
4351
+ link: `sideboard://chat/${existing.id}`,
4352
+ chats: workspaceAgentChatSummaries(orch.getThread(existing.id)?.worktreePath)
4440
4353
  });
4441
4354
  }
4442
4355
  const workspaces = orch.listWorkspaces();
@@ -4536,7 +4449,7 @@ async function startMcpServer() {
4536
4449
  if (shouldRegisterMcpWaitForTurn()) {
4537
4450
  server.tool(
4538
4451
  "wait_for_turn",
4539
- "Wait until the chat finishes its current/queued turn, or return early with a live progress snapshot. MCP clients often kill tools around 60s, so this returns within 45s even while the child is still working. taskState is the A2A-style lifecycle: submitted (queued, not started), working, input-required (ask_user), completed, failed, canceled. stillRunning is true only for submitted/working. If stillRunning, text and usage are empty (they would be the previous turn) \u2014 read progress and call wait_for_turn again. Do not send_to_chat a check-in (that steers / interrupts). On failed, lastError/text is the failure. On canceled, the child did not finish \u2014 resume with send_to_chat or tell the user. On input-required, wait for the user in that chat. When finished, text is this turn\u2019s assistant reply and usage is that turn\u2019s tokens + costUsd (this turn, not the Claude session total; sessionCostUsd is the provider session total when present).",
4452
+ "Wait until the chat finishes its current/queued turn, or return early with a live progress snapshot. MCP clients often kill tools around 60s, so this returns within 45s even while the child is still working. taskState is the A2A-style lifecycle: submitted (queued, not started), working, input-required (ask_user), completed, failed, canceled. stillRunning is true only for submitted/working. blockedReason is why the child 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. If stillRunning, text and usage are empty (they would be the previous turn) \u2014 read progress and call wait_for_turn again. Do not send_to_chat a check-in (that steers / interrupts). On failed, lastError/text is the failure. On canceled, the child did not finish \u2014 resume with send_to_chat or tell the user. On input-required, wait for the user in that chat. When finished, text is this turn\u2019s assistant reply and usage is that turn\u2019s tokens + costUsd (this turn, not the Claude session total; sessionCostUsd is the provider session total when present).",
4540
4453
  {
4541
4454
  ref: z8.string(),
4542
4455
  timeoutMs: z8.number().optional()
@@ -4556,7 +4469,9 @@ async function startMcpServer() {
4556
4469
  stillRunning: result.stillRunning,
4557
4470
  progress: result.progress,
4558
4471
  lastActivityAt: result.lastActivityAt,
4559
- usage: result.usage
4472
+ usage: result.usage,
4473
+ blockedReason: result.blockedReason,
4474
+ blockedSource: result.blockedSource
4560
4475
  })
4561
4476
  );
4562
4477
  }
@@ -4564,15 +4479,16 @@ async function startMcpServer() {
4564
4479
  }
4565
4480
  server.tool(
4566
4481
  "get_turn_result",
4567
- "Assistant message and last-turn usage when the turn finished. While stillRunning (submitted/working), text and usage are empty so a previous reply cannot be mistaken for this turn \u2014 use progress for tools/thinking. Not the full transcript. Includes taskState (A2A lifecycle).",
4482
+ "Assistant message and last-turn usage when the turn finished. While stillRunning (submitted/working), text and usage are empty so a previous reply cannot be mistaken for this turn \u2014 use progress for tools/thinking. Not the full transcript. Includes taskState (A2A lifecycle) and blockedReason (why the agent is waiting on a person).",
4568
4483
  { ref: z8.string() },
4569
4484
  async ({ ref }) => {
4570
4485
  const result = orch.getTurnResult(ref);
4571
- const hint = mcpWaitTaskHint(result.taskState, result.status);
4486
+ const hint = mcpWaitTaskHint(result.taskState, result.status, result.blockedReason);
4487
+ const waiting = Boolean(result.blockedReason) && !result.stillRunning && result.taskState !== "failed" && result.taskState !== "canceled";
4572
4488
  return mcpJson({
4573
4489
  ...result,
4574
4490
  hint,
4575
- incomplete: needsCoordinatorAction(result.taskState)
4491
+ incomplete: needsCoordinatorAction(result.taskState) || waiting
4576
4492
  });
4577
4493
  }
4578
4494
  );
@@ -4786,7 +4702,7 @@ async function startMcpServer() {
4786
4702
  );
4787
4703
  server.tool(
4788
4704
  "fork_chat",
4789
- "Fork a chat into a NEW tab on the SAME workspace: worktree agent \u2192 same worktree tab; Global orchestration chat \u2192 new orchestration chat (same synthetic home). Seeds a transcript; optional agent override. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). Slack / Global orchestrators use this to continue an orchestration chat on another agent after session limits. Then send_to_chat / wait_for_turn (loop while stillRunning) on the returned id. Use fork_workspace only for worktree agents that need a new git worktree.",
4705
+ "Add another agent chat on the SAME workspace (same files, Run, and git). Also forks a Global orchestration chat into a new orchestration tab. Pass agent when the new chat should use a different harness. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). Use this for a second agent on a checkout (review, another harness, parallel work on the same branch). Do not create_workspace again for that \u2014 it returns the existing checkout. Then send_to_chat / wait_for_turn (loop while stillRunning) on the returned id. Use fork_workspace only when the work needs its own git checkout.",
4790
4706
  {
4791
4707
  ref: z8.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
4792
4708
  through_index: z8.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
@@ -11,17 +11,17 @@ import {
11
11
  resolveSendFollowUp,
12
12
  startOrchestration,
13
13
  waitForPidExit
14
- } from "./chunk-FELFTNH2.js";
14
+ } from "./chunk-2P75E243.js";
15
15
  import "./chunk-LX5R5JOR.js";
16
16
  import "./chunk-4GK32AYX.js";
17
17
  import "./chunk-5DZUWGKV.js";
18
- import "./chunk-NT7RIWRR.js";
18
+ import "./chunk-RK5PIWCW.js";
19
19
  import "./chunk-TQRSVE7D.js";
20
20
  import "./chunk-P2BSMNHB.js";
21
21
  import "./chunk-VKQGAWHY.js";
22
- import "./chunk-HOMZKYHF.js";
23
- import "./chunk-UKSJO36E.js";
24
- import "./chunk-QTZO4LPU.js";
22
+ import "./chunk-YDXNCBA2.js";
23
+ import "./chunk-CPA43FBK.js";
24
+ import "./chunk-TZ5JHIYK.js";
25
25
  import "./chunk-K7MWJLR7.js";
26
26
  import "./chunk-N2WW62LF.js";
27
27
  import "./chunk-DD67JWTN.js";
@@ -9,22 +9,22 @@ import {
9
9
  resolveSendFollowUp,
10
10
  startOrchestration,
11
11
  waitForPidExit
12
- } from "./chunk-SQOAFR2U.js";
13
- import "./chunk-NIB2IZ6J.js";
12
+ } from "./chunk-DK4DKQ2H.js";
13
+ import "./chunk-HNNS25SG.js";
14
14
  import "./chunk-GWFVCCJZ.js";
15
- import "./chunk-MGS5T2A3.js";
16
- import "./chunk-FAGK33E2.js";
15
+ import "./chunk-HQXBBUQX.js";
16
+ import "./chunk-Y7JPL4WA.js";
17
17
  import "./chunk-7H655NEW.js";
18
18
  import "./chunk-PGPSIEMW.js";
19
- import "./chunk-A2G3ZBJS.js";
19
+ import "./chunk-SKSOCIJT.js";
20
20
  import "./chunk-IPPY3FG5.js";
21
21
  import "./chunk-FWNL62VA.js";
22
22
  import "./chunk-2P4XKWV2.js";
23
23
  import "./chunk-ROPGQRPU.js";
24
24
  import "./chunk-N7WZ6NIN.js";
25
25
  import "./chunk-AXQ4ZWHK.js";
26
- import "./chunk-FIBZEFKA.js";
27
- import "./chunk-5N4Q46SG.js";
26
+ import "./chunk-IFURHRAR.js";
27
+ import "./chunk-K2S33ZCB.js";
28
28
  import "./chunk-6K64FJDD.js";
29
29
  import "./chunk-UWQ6UCIB.js";
30
30
  import "./chunk-QVN2WOKJ.js";
@@ -5,7 +5,7 @@ import {
5
5
  primaryCheckoutFromLinkedWorktree,
6
6
  removeWorkspace,
7
7
  syncWorkspacesFromThreads
8
- } from "./chunk-A2G3ZBJS.js";
8
+ } from "./chunk-SKSOCIJT.js";
9
9
  import "./chunk-IPPY3FG5.js";
10
10
  import "./chunk-FWNL62VA.js";
11
11
  import "./chunk-2P4XKWV2.js";
@@ -7,7 +7,7 @@ import {
7
7
  primaryCheckoutFromLinkedWorktree,
8
8
  removeWorkspace,
9
9
  syncWorkspacesFromThreads
10
- } from "./chunk-QTZO4LPU.js";
10
+ } from "./chunk-TZ5JHIYK.js";
11
11
  import "./chunk-K7MWJLR7.js";
12
12
  import "./chunk-N2WW62LF.js";
13
13
  import "./chunk-DD67JWTN.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sideboard-ai/core",
3
- "version": "0.1.278",
3
+ "version": "0.1.280",
4
4
  "description": "Sideboard core — orchestration, agents, git worktrees, MCP server",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",