@sideboard-ai/core 0.1.158 → 0.1.160

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/{abletime-GWZE6OYM.js → abletime-AGF54TNA.js} +2 -2
  2. package/dist/{abletime-BEIDJG7W.js → abletime-BMS53RIZ.js} +2 -2
  3. package/dist/{agents-RGE7PSUY.js → agents-6UOXBORU.js} +4 -4
  4. package/dist/{agents-E3ZPWZRS.js → agents-RD774UEV.js} +4 -4
  5. package/dist/{app-settings-VRQI3FIE.js → app-settings-BRLZQA7N.js} +13 -17
  6. package/dist/{app-settings-H3INSR67.js → app-settings-JJQDWPEK.js} +13 -17
  7. package/dist/{chunk-EPQIOAAY.js → chunk-7OWRFJK6.js} +7 -5
  8. package/dist/{chunk-KYKNFJK5.js → chunk-BQ4PEXIF.js} +39 -20
  9. package/dist/{chunk-3BWVOUQG.js → chunk-CVZ4KXVP.js} +127 -113
  10. package/dist/{chunk-3SUZMPGL.js → chunk-HZROPNYL.js} +12 -13
  11. package/dist/{chunk-DVJBO3M7.js → chunk-ILXKJ4ZD.js} +1 -1
  12. package/dist/{chunk-A7QXLYBN.js → chunk-IO5WGZRR.js} +3 -13
  13. package/dist/{chunk-N27GVFZY.js → chunk-KJFFQAMA.js} +1 -1
  14. package/dist/{chunk-D7EGJ3EI.js → chunk-LTXS26K5.js} +127 -113
  15. package/dist/{chunk-CVR4RJ6G.js → chunk-QANLIT2E.js} +12 -13
  16. package/dist/{chunk-FBFIOVDH.js → chunk-QYKADGPP.js} +38 -19
  17. package/dist/{chunk-BLRHO7XQ.js → chunk-RQNXLIXO.js} +10 -6
  18. package/dist/{chunk-LQMFCS54.js → chunk-UUEBNUEG.js} +3 -13
  19. package/dist/{coordinator-prompt-26IJU2AV.js → coordinator-prompt-X3LXE5Q4.js} +3 -3
  20. package/dist/{coordinator-prompt-GZESIQNH.js → coordinator-prompt-YYUXC7EM.js} +3 -3
  21. package/dist/{global-workspace-REI55FZ6.js → global-workspace-E6S5XQZF.js} +3 -3
  22. package/dist/{global-workspace-5BIW26YR.js → global-workspace-SPR2Y4YQ.js} +3 -3
  23. package/dist/index.cjs +599 -475
  24. package/dist/index.d.cts +62 -57
  25. package/dist/index.d.ts +62 -57
  26. package/dist/index.js +325 -230
  27. package/dist/mcp/run-stdio.cjs +528 -402
  28. package/dist/mcp/run-stdio.js +265 -163
  29. package/dist/{orchestrator-ZVW2AC6B.js → orchestrator-BSVSOTBK.js} +5 -5
  30. package/dist/{orchestrator-J727WIJY.js → orchestrator-KVKPIUVU.js} +5 -5
  31. package/dist/{workspaces-NYSM2EPX.js → workspaces-MZELP4BI.js} +3 -3
  32. package/dist/{workspaces-OCEPZXFD.js → workspaces-OK2SNNQF.js} +3 -3
  33. package/dist/{worktree-VTMBZKY6.js → worktree-BX53JZHO.js} +2 -2
  34. package/dist/{worktree-T4SWFSTT.js → worktree-IY7YCEGH.js} +2 -2
  35. package/package.json +1 -1
@@ -36,12 +36,12 @@ import {
36
36
  syncBoardPins,
37
37
  updateSchedule,
38
38
  waitForDetachedJob
39
- } from "../chunk-FBFIOVDH.js";
39
+ } from "../chunk-QYKADGPP.js";
40
40
  import {
41
41
  isInternalAgentStatusText,
42
42
  listModelsForAgent,
43
43
  sideboardMcpProfile
44
- } from "../chunk-EPQIOAAY.js";
44
+ } from "../chunk-7OWRFJK6.js";
45
45
  import "../chunk-LZ6QMEQC.js";
46
46
  import "../chunk-S42XV45P.js";
47
47
  import {
@@ -56,7 +56,7 @@ import {
56
56
  searchAbleTimeTasks,
57
57
  toAbleTimeIssueInfo,
58
58
  updateAbleTimeTask
59
- } from "../chunk-N27GVFZY.js";
59
+ } from "../chunk-KJFFQAMA.js";
60
60
  import {
61
61
  httpFetch
62
62
  } from "../chunk-6GN5WPYZ.js";
@@ -64,8 +64,9 @@ import "../chunk-JV4VNFMS.js";
64
64
  import "../chunk-OCQPTUR7.js";
65
65
  import {
66
66
  GLOBAL_WORKSPACE_ID,
67
- isCloudCoordinatorThread
68
- } from "../chunk-3SUZMPGL.js";
67
+ isCloudCoordinatorThread,
68
+ listWorkspaces
69
+ } from "../chunk-HZROPNYL.js";
69
70
  import {
70
71
  canonicalizeRepoPath,
71
72
  formatGhLandError,
@@ -76,7 +77,7 @@ import {
76
77
  resolveListPrsOptions,
77
78
  resolveRepoRoot,
78
79
  warmGithubAgentAuth
79
- } from "../chunk-A7QXLYBN.js";
80
+ } from "../chunk-IO5WGZRR.js";
80
81
  import "../chunk-5U4NZLBH.js";
81
82
  import "../chunk-LZQULFIT.js";
82
83
  import "../chunk-SYFVUF2C.js";
@@ -90,11 +91,13 @@ import {
90
91
  isAbleTimeConnected,
91
92
  isLinearConnected,
92
93
  loadAppSettings,
94
+ readViewerContext,
93
95
  resolveEffectiveIssueSource,
94
96
  resolveViewerProfile,
95
97
  resolveViewerProfileForRepo,
96
- saveLinearOAuth
97
- } from "../chunk-D7EGJ3EI.js";
98
+ saveLinearOAuth,
99
+ writeViewerContext
100
+ } from "../chunk-LTXS26K5.js";
98
101
  import "../chunk-NSTQ6QKD.js";
99
102
  import "../chunk-KGZBYWZ3.js";
100
103
  import {
@@ -104,7 +107,7 @@ import {
104
107
  // src/mcp/server.ts
105
108
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
106
109
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
107
- import { z as z6 } from "zod";
110
+ import { z as z7 } from "zod";
108
111
  import { randomUUID as randomUUID2 } from "crypto";
109
112
  import { basename } from "path";
110
113
 
@@ -939,9 +942,9 @@ function mcpArchiveBlockedReason(thread) {
939
942
  function lastMessagePreview(messages, max = 160) {
940
943
  if (!messages?.length) return null;
941
944
  for (let i = messages.length - 1; i >= 0; i--) {
942
- const text6 = messages[i]?.text?.trim();
943
- if (!text6 || isInternalAgentStatusText(text6)) continue;
944
- const flat = text6.replace(/\s+/g, " ");
945
+ const text7 = messages[i]?.text?.trim();
946
+ if (!text7 || isInternalAgentStatusText(text7)) continue;
947
+ const flat = text7.replace(/\s+/g, " ");
945
948
  return flat.length > max ? `${flat.slice(0, max)}\u2026` : flat;
946
949
  }
947
950
  return null;
@@ -1173,8 +1176,8 @@ function labelGithubUrl(url) {
1173
1176
  }
1174
1177
  return { kind: "other", label: "GitHub", url: raw };
1175
1178
  }
1176
- function appendGithubLink(text6, githubUrl) {
1177
- const body = text6.trimEnd();
1179
+ function appendGithubLink(text7, githubUrl) {
1180
+ const body = text7.trimEnd();
1178
1181
  if (!githubUrl?.trim()) return body;
1179
1182
  const labeled = labelGithubUrl(githubUrl);
1180
1183
  if (!labeled) {
@@ -2038,6 +2041,104 @@ function registerConnectedIssueVendorTools(server, settings = loadAppSettings())
2038
2041
  if (isAbleTimeConnected(settings)) registerAbleTimeTools(server);
2039
2042
  }
2040
2043
 
2044
+ // src/mcp/viewer-context-tools.ts
2045
+ import { z as z5 } from "zod";
2046
+ function text5(payload, isError = false) {
2047
+ return mcpJson(payload, isError);
2048
+ }
2049
+ function matchRegisteredWorkspace(repoPath) {
2050
+ const hint = (repoPath ?? "").trim().replace(/\/+$/, "");
2051
+ if (!hint) return null;
2052
+ let best = null;
2053
+ for (const w of listWorkspaces()) {
2054
+ const stored = w.path.replace(/\/+$/, "");
2055
+ if (stored === hint || hint.startsWith(`${stored}/`)) {
2056
+ if (!best || stored.length > best.length) best = stored;
2057
+ }
2058
+ }
2059
+ return best;
2060
+ }
2061
+ async function resolveProjectRepoPath(repoPath) {
2062
+ const hint = repoPath?.trim() || process.cwd();
2063
+ let root = hint;
2064
+ try {
2065
+ root = await resolveRepoRoot(hint);
2066
+ } catch {
2067
+ root = hint.replace(/\/+$/, "");
2068
+ }
2069
+ return matchRegisteredWorkspace(root) ?? matchRegisteredWorkspace(hint);
2070
+ }
2071
+ var PROJECT_PATH_HINT = "Pass repoPath from list_workspaces. Orchestration cwd is synthetic and empty \u2014 do not omit repoPath for project context.";
2072
+ function registerViewerContextTools(server) {
2073
+ server.tool(
2074
+ "get_viewer_context",
2075
+ "Read account and project user context (Settings \u2192 Agents / Projects): roles, tickets, and review queues as freeform text. Orchestration must pass repoPath from list_workspaces for project context. Worktree turns may omit repoPath (uses cwd).",
2076
+ {
2077
+ repoPath: z5.string().optional().describe(
2078
+ "Registered workspace or worktree path. Required from orchestration for a project. Omit on a worktree turn (uses cwd)."
2079
+ )
2080
+ },
2081
+ async ({ repoPath }) => {
2082
+ try {
2083
+ const path = await resolveProjectRepoPath(repoPath);
2084
+ return text5(readViewerContext(path ?? void 0));
2085
+ } catch (err) {
2086
+ return text5(
2087
+ { error: err instanceof Error ? err.message : String(err) },
2088
+ true
2089
+ );
2090
+ }
2091
+ }
2092
+ );
2093
+ server.tool(
2094
+ "update_viewer_context",
2095
+ "Replace account or project user context (Settings \u2192 Agents / Projects). Never call until the user confirmed. Show the proposed text, ask_user (Save this context / Do not save), wait, then pass confirmed=true. Empty context clears that field.",
2096
+ {
2097
+ scope: z5.enum(["account", "project"]).describe("account = Settings \u2192 Agents; project = Settings \u2192 Projects for one repo"),
2098
+ context: z5.string().describe("Full replacement text (roles, tickets, review queues). Empty clears."),
2099
+ confirmed: z5.boolean().optional().describe("Must be true after the user confirmed via ask_user. False/omit is rejected."),
2100
+ repoPath: z5.string().optional().describe(
2101
+ "Required for scope=project from orchestration (path from list_workspaces). Worktree turns may omit it (cwd)."
2102
+ )
2103
+ },
2104
+ async ({ scope, context, confirmed, repoPath }) => {
2105
+ try {
2106
+ if (scope === "project") {
2107
+ const path = await resolveProjectRepoPath(repoPath);
2108
+ if (!path) {
2109
+ return text5(
2110
+ {
2111
+ ok: false,
2112
+ error: "repoPath required",
2113
+ message: PROJECT_PATH_HINT
2114
+ },
2115
+ true
2116
+ );
2117
+ }
2118
+ const result2 = writeViewerContext({
2119
+ scope,
2120
+ context,
2121
+ repoPath: path,
2122
+ confirmed: confirmed === true
2123
+ });
2124
+ return text5(result2, !result2.ok);
2125
+ }
2126
+ const result = writeViewerContext({
2127
+ scope,
2128
+ context,
2129
+ confirmed: confirmed === true
2130
+ });
2131
+ return text5(result, !result.ok);
2132
+ } catch (err) {
2133
+ return text5(
2134
+ { error: err instanceof Error ? err.message : String(err) },
2135
+ true
2136
+ );
2137
+ }
2138
+ }
2139
+ );
2140
+ }
2141
+
2041
2142
  // src/mcp/pr-list.ts
2042
2143
  function compactPrRow(pr) {
2043
2144
  return {
@@ -2073,15 +2174,15 @@ function formatMcpPrList(input) {
2073
2174
  }
2074
2175
 
2075
2176
  // src/mcp/schedule-tools.ts
2076
- import { z as z5 } from "zod";
2077
- function text5(payload, isError = false) {
2177
+ import { z as z6 } from "zod";
2178
+ function text6(payload, isError = false) {
2078
2179
  return {
2079
2180
  content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
2080
2181
  ...isError ? { isError: true } : {}
2081
2182
  };
2082
2183
  }
2083
2184
  function fail5(err) {
2084
- return text5(
2185
+ return text6(
2085
2186
  { error: err instanceof Error ? err.message : String(err) },
2086
2187
  true
2087
2188
  );
@@ -2106,7 +2207,7 @@ function registerScheduleTools(server) {
2106
2207
  {},
2107
2208
  async () => {
2108
2209
  try {
2109
- return text5({ schedules: listSchedules() });
2210
+ return text6({ schedules: listSchedules() });
2110
2211
  } catch (err) {
2111
2212
  return fail5(err);
2112
2213
  }
@@ -2116,17 +2217,17 @@ function registerScheduleTools(server) {
2116
2217
  "create_schedule",
2117
2218
  "Create a local schedule that, when due, sends a prompt to an orchestration chat (threadId) or starts a new Global orchestration chat (omit threadId). Pass threadId=self to continue this coordinator. Exactly one of at (ISO datetime), every (15m/1h/6h/1d), or cron (5-field). Recurring jobs without threadId open a new chat each run. Overnight/unattended runs need Settings \u2192 Advanced \u2192 Caffeinate while schedules are enabled, or set_caffeinate. Sideboard.app must be running for the job to fire.",
2118
2219
  {
2119
- prompt: z5.string().describe("User message / goal queued when the schedule fires"),
2120
- name: z5.string().optional(),
2121
- at: z5.string().optional().describe("ISO datetime for a one-shot"),
2122
- every: z5.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
2123
- cron: z5.string().optional().describe("5-field cron expression"),
2124
- tz: z5.string().optional().describe("IANA timezone for cron (default: system)"),
2125
- threadId: z5.string().optional().describe(
2220
+ prompt: z6.string().describe("User message / goal queued when the schedule fires"),
2221
+ name: z6.string().optional(),
2222
+ at: z6.string().optional().describe("ISO datetime for a one-shot"),
2223
+ every: z6.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
2224
+ cron: z6.string().optional().describe("5-field cron expression"),
2225
+ tz: z6.string().optional().describe("IANA timezone for cron (default: system)"),
2226
+ threadId: z6.string().optional().describe(
2126
2227
  'Existing orchestration chat id, or "self" for this coordinator. Omit to create a new Global chat on fire.'
2127
2228
  ),
2128
- agent: z5.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
2129
- model: z5.string().optional()
2229
+ agent: z6.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
2230
+ model: z6.string().optional()
2130
2231
  },
2131
2232
  async (args) => {
2132
2233
  try {
@@ -2146,7 +2247,7 @@ function registerScheduleTools(server) {
2146
2247
  model: args.model,
2147
2248
  createdBy: "mcp"
2148
2249
  });
2149
- return text5({
2250
+ return text6({
2150
2251
  schedule,
2151
2252
  hint: "Fires while Sideboard.app is running. Recurring jobs without threadId create a new orchestration chat each run. Overnight runs need Settings \u2192 Advanced \u2192 Caffeinate while schedules are enabled, or set_caffeinate."
2152
2253
  });
@@ -2159,17 +2260,17 @@ function registerScheduleTools(server) {
2159
2260
  "update_schedule",
2160
2261
  "Update a local schedule (prompt, cadence, target thread, enabled). Pass id from list_schedules.",
2161
2262
  {
2162
- id: z5.string(),
2163
- prompt: z5.string().optional(),
2164
- name: z5.string().optional(),
2165
- at: z5.string().optional(),
2166
- every: z5.string().optional(),
2167
- cron: z5.string().optional(),
2168
- tz: z5.string().optional(),
2169
- threadId: z5.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
2170
- agent: z5.enum(["claude", "cursor", "codex", "opencode"]).optional(),
2171
- model: z5.string().optional(),
2172
- enabled: z5.boolean().optional()
2263
+ id: z6.string(),
2264
+ prompt: z6.string().optional(),
2265
+ name: z6.string().optional(),
2266
+ at: z6.string().optional(),
2267
+ every: z6.string().optional(),
2268
+ cron: z6.string().optional(),
2269
+ tz: z6.string().optional(),
2270
+ threadId: z6.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
2271
+ agent: z6.enum(["claude", "cursor", "codex", "opencode"]).optional(),
2272
+ model: z6.string().optional(),
2273
+ enabled: z6.boolean().optional()
2173
2274
  },
2174
2275
  async (args) => {
2175
2276
  try {
@@ -2198,7 +2299,7 @@ function registerScheduleTools(server) {
2198
2299
  enabled: args.enabled,
2199
2300
  model: args.model
2200
2301
  });
2201
- return text5({ schedule });
2302
+ return text6({ schedule });
2202
2303
  } catch (err) {
2203
2304
  return fail5(err);
2204
2305
  }
@@ -2207,11 +2308,11 @@ function registerScheduleTools(server) {
2207
2308
  server.tool(
2208
2309
  "delete_schedule",
2209
2310
  "Delete a local schedule. Pass id from list_schedules.",
2210
- { id: z5.string() },
2311
+ { id: z6.string() },
2211
2312
  async ({ id }) => {
2212
2313
  try {
2213
2314
  deleteSchedule(id);
2214
- return text5({ ok: true, id });
2315
+ return text6({ ok: true, id });
2215
2316
  } catch (err) {
2216
2317
  return fail5(err);
2217
2318
  }
@@ -2220,14 +2321,14 @@ function registerScheduleTools(server) {
2220
2321
  server.tool(
2221
2322
  "run_schedule",
2222
2323
  "Fire a schedule now (does not wait for nextRunAt). Queues the prompt or starts a new Global chat. If Sideboard.app is running, the desktop drains the turn.",
2223
- { id: z5.string() },
2324
+ { id: z6.string() },
2224
2325
  async ({ id }) => {
2225
2326
  try {
2226
2327
  if (!getSchedule(id)) {
2227
2328
  return fail5(new Error(`Schedule not found: ${id}`));
2228
2329
  }
2229
2330
  const schedule = await fireSchedule(id);
2230
- return text5({ schedule });
2331
+ return text6({ schedule });
2231
2332
  } catch (err) {
2232
2333
  return fail5(err);
2233
2334
  }
@@ -2568,7 +2669,7 @@ async function startMcpServer() {
2568
2669
  );
2569
2670
  }
2570
2671
  try {
2571
- const { maxConcurrentAgents } = await import("../app-settings-VRQI3FIE.js");
2672
+ const { maxConcurrentAgents } = await import("../app-settings-BRLZQA7N.js");
2572
2673
  orch.setMaxConcurrent(maxConcurrentAgents());
2573
2674
  } catch {
2574
2675
  }
@@ -2585,13 +2686,14 @@ async function startMcpServer() {
2585
2686
  version: "0.1.0"
2586
2687
  });
2587
2688
  const worktreeProfile = sideboardMcpProfile() === "worktree";
2689
+ registerViewerContextTools(server);
2588
2690
  if (worktreeProfile) {
2589
2691
  registerConnectedIssueVendorTools(server);
2590
2692
  }
2591
2693
  if (!worktreeProfile) {
2592
2694
  server.tool(
2593
2695
  "list_workspaces",
2594
- "List registered Sideboard workspaces (repos). Each line is name, path, github:owner/repo when resolvable, and viewer roles/notes when set \u2014 use path as repoPath for list_board/list_branches/list_prs/list_issues/create_thread. Roles and notes (Settings \u2192 Agents / Projects) say which tickets and review PRs belong to the user.",
2696
+ "List registered Sideboard workspaces (repos). Each line is name, path, github:owner/repo when resolvable, and project context when set \u2014 use path as repoPath for list_board/list_branches/list_prs/list_issues/create_thread. Account / project context (Settings \u2192 Agents / Projects) says which tickets and review PRs belong to the user.",
2595
2697
  {},
2596
2698
  async () => {
2597
2699
  const workspaces = orch.listWorkspaces();
@@ -2640,13 +2742,13 @@ async function startMcpServer() {
2640
2742
  "list_board",
2641
2743
  "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), column, limit (default 40). create_thread adds a worktree (and a Home card), or returns the live one if that ticket/PR/named branch is already checked out.",
2642
2744
  {
2643
- query: z6.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
2644
- repoPath: z6.string().optional().describe("Limit to one workspace path from list_workspaces"),
2645
- kind: z6.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
2646
- column: z6.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
2745
+ query: z7.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
2746
+ repoPath: z7.string().optional().describe("Limit to one workspace path from list_workspaces"),
2747
+ kind: z7.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
2748
+ column: z7.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
2647
2749
  "Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
2648
2750
  ),
2649
- limit: z6.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
2751
+ limit: z7.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
2650
2752
  },
2651
2753
  async ({ query, repoPath, kind, column, limit }) => {
2652
2754
  const workspaces = orch.listWorkspaces();
@@ -2684,7 +2786,7 @@ async function startMcpServer() {
2684
2786
  server.tool(
2685
2787
  "get_thread",
2686
2788
  "Get a compact thread summary by id/ref. Includes last message preview, parentThreadId, and child worktree threads (status + lastText). While running, includes progress (last tool/thinking) and lastActivityAt. Includes usage (thread billed token + costUsd totals when providers reported cost) and lastTurnUsage.",
2687
- { ref: z6.string() },
2789
+ { ref: z7.string() },
2688
2790
  async ({ ref }) => {
2689
2791
  const t = orch.getThread(ref);
2690
2792
  if (!t) {
@@ -2726,17 +2828,17 @@ async function startMcpServer() {
2726
2828
  "present_artifact",
2727
2829
  "Show a document or live log in Sideboard\u2019s side column. For html/svg/markdown/react, pass the FULL document and do not also fence that same body in chat. For type=log, pass only NEW lines (same artifact_id appends). Prefer type=log for long-running job output \u2014 do not resend HTML. type=react is a single default-export component (JSX/TSX); only react/react-dom imports.",
2728
2830
  {
2729
- title: z6.string().describe("Short title shown in the artifact pane header"),
2730
- type: z6.enum(["html", "svg", "markdown", "react", "log"]).describe(
2831
+ title: z7.string().describe("Short title shown in the artifact pane header"),
2832
+ type: z7.enum(["html", "svg", "markdown", "react", "log"]).describe(
2731
2833
  "html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
2732
2834
  ),
2733
- content: z6.string().describe(
2835
+ content: z7.string().describe(
2734
2836
  "html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
2735
2837
  ),
2736
- artifact_id: z6.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
2737
- status: z6.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
2738
- phase: z6.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
2739
- mode: z6.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
2838
+ artifact_id: z7.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
2839
+ status: z7.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
2840
+ phase: z7.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
2841
+ mode: z7.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
2740
2842
  },
2741
2843
  async ({ title, type, artifact_id, status, phase, mode }) => {
2742
2844
  const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -2757,15 +2859,15 @@ async function startMcpServer() {
2757
2859
  "ask_user",
2758
2860
  "Ask the user a clarifying multiple-choice question in Sideboard\u2019s composer. Call only when work is blocked on choosing among a few concrete options (approach fork, which API, auth vs cookies). Do not call for greetings, check-ins, \u201Chello\u201D, open-ended how-can-I-help, or to invent a menu of possible next tasks \u2014 reply in chat instead. If one option is the obvious default, proceed without asking. Before calling, write a short chat message explaining the decision and what each option means. Include a description on every option. After calling, stop and wait for answers. Not for \u201Cis the plan ready?\u201D.",
2759
2861
  {
2760
- questions: z6.array(
2761
- z6.object({
2762
- question: z6.string().describe("Full question text ending with ?"),
2763
- header: z6.string().max(24).optional().describe("Short label shown above the question"),
2764
- multiSelect: z6.boolean().optional().describe("Allow selecting multiple options"),
2765
- options: z6.array(
2766
- z6.object({
2767
- label: z6.string(),
2768
- description: z6.string().optional().describe("What this option means / when to choose it (strongly preferred)")
2862
+ questions: z7.array(
2863
+ z7.object({
2864
+ question: z7.string().describe("Full question text ending with ?"),
2865
+ header: z7.string().max(24).optional().describe("Short label shown above the question"),
2866
+ multiSelect: z7.boolean().optional().describe("Allow selecting multiple options"),
2867
+ options: z7.array(
2868
+ z7.object({
2869
+ label: z7.string(),
2870
+ description: z7.string().optional().describe("What this option means / when to choose it (strongly preferred)")
2769
2871
  })
2770
2872
  ).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
2771
2873
  })
@@ -2784,9 +2886,9 @@ async function startMcpServer() {
2784
2886
  "present_plan",
2785
2887
  "Save the implementation plan as markdown to .context/attachments/plan.md and show it in Sideboard chat for user approval (Copy / Hand off / Approve). Call this when the plan is ready \u2014 required in plan mode. Pass the full plan body in content. Then Claude should call ExitPlanMode.",
2786
2888
  {
2787
- title: z6.string().optional().describe("Short plan title (defaults to Plan)"),
2788
- content: z6.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
2789
- thread_id: z6.string().optional().describe("Sideboard thread id when cwd is not the worktree")
2889
+ title: z7.string().optional().describe("Short plan title (defaults to Plan)"),
2890
+ content: z7.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
2891
+ thread_id: z7.string().optional().describe("Sideboard thread id when cwd is not the worktree")
2790
2892
  },
2791
2893
  async ({ title, content, thread_id }) => {
2792
2894
  const { writePlanFile } = await import("../plan-file-I24VJVP2.js");
@@ -2809,15 +2911,15 @@ async function startMcpServer() {
2809
2911
  "present_schema",
2810
2912
  "Open Sideboard\u2019s schema-driven side column (filterable table and/or form) when the user needs to filter, edit, publish, or persist records. Do not call this just to re-display rows you already wrote as a markdown table. If the user asks for an editable / interactive table, call this even if chat already showed those rows. Pass JSON Schema + optional schemaUi. Prefer datasource=inline with embedded resource/records. Use datasource=brightsy with resource_id only when the user is logged into Brightsy.",
2811
2913
  {
2812
- title: z6.string().describe("Pane title"),
2813
- mode: z6.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
2814
- datasource: z6.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
2815
- resource_id: z6.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
2816
- record_id: z6.string().optional().describe("Record id when opening form mode"),
2817
- resource: z6.record(z6.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
2818
- record: z6.record(z6.unknown()).optional().describe("Inline record { id, data, published_at? }"),
2819
- records: z6.array(z6.record(z6.unknown())).optional().describe("Inline records for table mode"),
2820
- pane_id: z6.string().optional().describe("Stable pane id across updates")
2914
+ title: z7.string().describe("Pane title"),
2915
+ mode: z7.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
2916
+ datasource: z7.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
2917
+ resource_id: z7.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
2918
+ record_id: z7.string().optional().describe("Record id when opening form mode"),
2919
+ resource: z7.record(z7.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
2920
+ record: z7.record(z7.unknown()).optional().describe("Inline record { id, data, published_at? }"),
2921
+ records: z7.array(z7.record(z7.unknown())).optional().describe("Inline records for table mode"),
2922
+ pane_id: z7.string().optional().describe("Stable pane id across updates")
2821
2923
  },
2822
2924
  async (args) => {
2823
2925
  const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -2838,10 +2940,10 @@ async function startMcpServer() {
2838
2940
  "present_files",
2839
2941
  "Open Sideboard\u2019s Files column (CMS-style file manager: browse, upload, pick). Use datasource=brightsy when the user is logged into Brightsy account storage; datasource=memory for a session-local demo store. Prefer this over claiming a file manager UI is unavailable. Pair with present_schema when editing records that need media.",
2840
2942
  {
2841
- title: z6.string().optional().describe("Pane title (default: Files)"),
2842
- datasource: z6.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
2843
- path: z6.string().optional().describe("Initial folder path (e.g. public)"),
2844
- pane_id: z6.string().optional().describe("Stable pane id across updates")
2943
+ title: z7.string().optional().describe("Pane title (default: Files)"),
2944
+ datasource: z7.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
2945
+ path: z7.string().optional().describe("Initial folder path (e.g. public)"),
2946
+ pane_id: z7.string().optional().describe("Stable pane id across updates")
2845
2947
  },
2846
2948
  async (args) => {
2847
2949
  const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -2860,8 +2962,8 @@ async function startMcpServer() {
2860
2962
  "wait_for_job",
2861
2963
  "Wait on a detached long job (tests, pack, deploy) started with detached-job.js. MCP clients kill tools around 60s, so this returns within 45s. stillRunning is the source of truth \u2014 if true, present_artifact type=log with content=delta (same artifact_id) and call wait_for_job again. Do not end the turn or tell the user you will let them know later. If false, ok/failed is the result.",
2862
2964
  {
2863
- id: z6.string().describe("Detached job id (same kebab-case id passed to detached-job.js start)"),
2864
- timeoutMs: z6.number().optional()
2965
+ id: z7.string().describe("Detached job id (same kebab-case id passed to detached-job.js start)"),
2966
+ timeoutMs: z7.number().optional()
2865
2967
  },
2866
2968
  async ({ id, timeoutMs }) => {
2867
2969
  const result = await waitForDetachedJob(process.cwd(), id, {
@@ -2875,14 +2977,14 @@ async function startMcpServer() {
2875
2977
  registerConnectedIssueVendorTools(server);
2876
2978
  registerScheduleTools(server);
2877
2979
  const { getCaffeinateHold, setCaffeinateHold } = await import("../caffeinate-hold-DBKMRZZO.js");
2878
- const { resolveNewThreadOptions, resolveThreadDefaults } = await import("../app-settings-VRQI3FIE.js");
2980
+ const { resolveNewThreadOptions, resolveThreadDefaults } = await import("../app-settings-BRLZQA7N.js");
2879
2981
  const accountDefaults = resolveThreadDefaults();
2880
2982
  const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
2881
2983
  server.tool(
2882
2984
  "set_caffeinate",
2883
2985
  "Keep this Mac awake with caffeinate (like Claude Code) across turns \u2014 independent of Settings toggles. Turn ON when the user will be away from the keyboard, is driving work from Slack, or asks you to keep the machine awake. Turn OFF when they say they are done, wrapping up, going to sleep, or no longer need the Mac awake. Closing or archiving this orchestration chat also releases the hold. macOS only.",
2884
2986
  {
2885
- enabled: z6.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
2987
+ enabled: z7.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
2886
2988
  },
2887
2989
  async ({ enabled }) => {
2888
2990
  const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
@@ -2920,18 +3022,18 @@ async function startMcpServer() {
2920
3022
  "create_thread",
2921
3023
  `Create a worktree thread (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_workspaces. cowboy=true uses the project folder on the default branch (no isolated worktree; land is commit+push). From an orchestration chat, omit parentThreadId (Sideboard binds the child to this chat) or pass the exact id from the turn reminder \u2014 never invent a uuid. Prefer omitting agent/model so Sideboard applies ${accountDefaultsHint}. 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_thread to chat.`,
2922
3024
  {
2923
- sourceType: z6.enum(["branch", "pr", "ticket"]),
2924
- sourceRef: z6.string(),
2925
- agent: z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
2926
- model: z6.string().nullable().optional().describe(
3025
+ sourceType: z7.enum(["branch", "pr", "ticket"]),
3026
+ sourceRef: z7.string(),
3027
+ agent: z7.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
3028
+ model: z7.string().nullable().optional().describe(
2927
3029
  `Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
2928
3030
  ),
2929
- repoPath: z6.string(),
2930
- title: z6.string().optional(),
2931
- cowboy: z6.boolean().optional().describe(
3031
+ repoPath: z7.string(),
3032
+ title: z7.string().optional(),
3033
+ cowboy: z7.boolean().optional().describe(
2932
3034
  "If true, work in the project folder on the default branch (no thread/* worktree). Requires Settings \u2192 Advanced \u2192 Cowboy mode. Land is commit+push to that branch. Archive does not delete the folder."
2933
3035
  ),
2934
- parentThreadId: z6.string().optional().describe(
3036
+ parentThreadId: z7.string().optional().describe(
2935
3037
  "Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
2936
3038
  )
2937
3039
  },
@@ -2947,10 +3049,10 @@ async function startMcpServer() {
2947
3049
  "start_board_card",
2948
3050
  "Same as create_thread 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_thread.",
2949
3051
  {
2950
- kind: z6.enum(["ticket", "pr", "branch"]),
2951
- ref: z6.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
2952
- repoPath: z6.string().describe("Workspace path from list_workspaces / list_board"),
2953
- title: z6.string().optional()
3052
+ kind: z7.enum(["ticket", "pr", "branch"]),
3053
+ ref: z7.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
3054
+ repoPath: z7.string().describe("Workspace path from list_workspaces / list_board"),
3055
+ title: z7.string().optional()
2954
3056
  },
2955
3057
  async ({ kind, ref, repoPath, title }) => {
2956
3058
  const root = await resolveRepoRoot(repoPath);
@@ -3054,9 +3156,9 @@ async function startMcpServer() {
3054
3156
  "send_to_thread",
3055
3157
  'Queue a prompt on a worktree thread chat (runs under concurrency cap). Use after create_thread to start or continue a conversation. For commit/push/PR, prefer ask_git (canonical desktop-button phrases). Send "Merge PR." / ask_git merge only when the user explicitly asked to merge. force_stop=true kills the in-flight turn and clears the queue before this prompt \u2014 only when the current request is wrong and must be replaced. Do not force_stop to check in, resume after a halt notice, or because wait_for_turn returned stillRunning; that stops the child mid-thought. Call wait_for_turn again instead.',
3056
3158
  {
3057
- ref: z6.string(),
3058
- prompt: z6.string(),
3059
- force_stop: z6.boolean().optional()
3159
+ ref: z7.string(),
3160
+ prompt: z7.string(),
3161
+ force_stop: z7.boolean().optional()
3060
3162
  },
3061
3163
  async ({ ref, prompt, force_stop }) => {
3062
3164
  if (force_stop) {
@@ -3085,8 +3187,8 @@ async function startMcpServer() {
3085
3187
  "wait_for_turn",
3086
3188
  "Wait until the thread 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. stillRunning is the source of truth \u2014 if false, the child is not working (do not say it is waiting for a gate). If stillRunning is true, progress is tools/thinking (or \u201Cqueued, waiting for a concurrency slot\u201D if it has not started). Call wait_for_turn again. Do not send a check-in prompt, force_stop, or assume a hang. On status error, lastError/text is the failure. On status stopped or broken, the child did not finish \u2014 resume with send_to_thread or tell the user; do not treat that as success. When finished, usage is the last agent turn\u2019s tokens + costUsd (when the provider reported cost).",
3087
3189
  {
3088
- ref: z6.string(),
3089
- timeoutMs: z6.number().optional()
3190
+ ref: z7.string(),
3191
+ timeoutMs: z7.number().optional()
3090
3192
  },
3091
3193
  async ({ ref, timeoutMs }) => {
3092
3194
  const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
@@ -3116,7 +3218,7 @@ async function startMcpServer() {
3116
3218
  server.tool(
3117
3219
  "get_turn_result",
3118
3220
  "Assistant message when the turn finished, or live progress while stillRunning. Not the full transcript. Includes usage for the last agent turn (tokens + costUsd when reported).",
3119
- { ref: z6.string() },
3221
+ { ref: z7.string() },
3120
3222
  async ({ ref }) => {
3121
3223
  const result = orch.getTurnResult(ref);
3122
3224
  return {
@@ -3137,8 +3239,8 @@ async function startMcpServer() {
3137
3239
  "stop_thread",
3138
3240
  "Force-stop a thread: kill any in-flight agent turn AND clear queued prompts so drainQueue cannot continue. Does not archive the worktree. Optional force defaults to true.",
3139
3241
  {
3140
- ref: z6.string(),
3141
- force: z6.boolean().optional()
3242
+ ref: z7.string(),
3243
+ force: z7.boolean().optional()
3142
3244
  },
3143
3245
  async ({ ref, force }) => {
3144
3246
  const t = orch.getThread(ref);
@@ -3168,7 +3270,7 @@ async function startMcpServer() {
3168
3270
  server.tool(
3169
3271
  "archive_thread",
3170
3272
  "Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators commit, push, and open PRs by asking the worktree agent (ask_git). Merge only when the user explicitly asked.",
3171
- { ref: z6.string() },
3273
+ { ref: z7.string() },
3172
3274
  async ({ ref }) => {
3173
3275
  const t = orch.getThread(ref);
3174
3276
  if (!t) {
@@ -3201,7 +3303,7 @@ async function startMcpServer() {
3201
3303
  server.tool(
3202
3304
  "restore_thread",
3203
3305
  "Restore an archived thread (recreates worktree from branch when needed)",
3204
- { ref: z6.string() },
3306
+ { ref: z7.string() },
3205
3307
  async ({ ref }) => {
3206
3308
  try {
3207
3309
  const restored = await orch.restore(ref);
@@ -3227,8 +3329,8 @@ async function startMcpServer() {
3227
3329
  "get_diff",
3228
3330
  "Compact diff summary (capped hunks, paginated)",
3229
3331
  {
3230
- ref: z6.string(),
3231
- maxFiles: z6.number().optional()
3332
+ ref: z7.string(),
3333
+ maxFiles: z7.number().optional()
3232
3334
  },
3233
3335
  async ({ ref, maxFiles }) => {
3234
3336
  const summary = await orch.diffSummary(ref);
@@ -3248,7 +3350,7 @@ async function startMcpServer() {
3248
3350
  server.tool(
3249
3351
  "get_pr_checks",
3250
3352
  "Snapshot of GitHub PR checks for a worktree thread (`gh pr checks` plus merge/review gates). null = no PR. If the user gave a goal (Greptile 5/5, CI green), the worktree agent watches with `gh pr checks --watch` via /long-running \u2014 this tool is a snapshot, not a waiter. Coordinators: do not run gh from the orchestration cwd.",
3251
- { ref: z6.string().describe("Worktree thread id/ref") },
3353
+ { ref: z7.string().describe("Worktree thread id/ref") },
3252
3354
  async ({ ref }) => {
3253
3355
  try {
3254
3356
  const checks = await orch.getPrChecks(ref);
@@ -3264,7 +3366,7 @@ async function startMcpServer() {
3264
3366
  server.tool(
3265
3367
  "request_review",
3266
3368
  'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, attaches .claude/skills/review/SKILL.md when present (else copies .sideboard/review.md into .context/review.md, or seeds that file from the stock template), and sends "Review changes in this workspace." Expect Approve / Approve with nits / Request changes / Needs more information. Pass a worktree thread ref \u2014 not the orchestrator. Then wait_for_turn (loop while stillRunning) / get_turn_result on the returned review tab id.',
3267
- { ref: z6.string().describe("Worktree thread id/ref to review") },
3369
+ { ref: z7.string().describe("Worktree thread id/ref to review") },
3268
3370
  async ({ ref }) => {
3269
3371
  try {
3270
3372
  const tab = await orch.requestReview(ref);
@@ -3293,8 +3395,8 @@ async function startMcpServer() {
3293
3395
  "ask_git",
3294
3396
  "Commit & push, open a draft PR, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` even when origin is SSH / Settings \u2192 Git is SSH). When dirty, queues the worktree agent to commit; then wait_for_turn (loop while stillRunning). Do not start a checks loop on a plain push \u2014 only if the user gave a goal (Greptile 5/5, CI green). Pass a worktree thread ref (not the orchestrator). action=merge only when the user explicitly asked to merge that PR. Do not run git or gh from the orchestration cwd. If this tool errors that the GraphQL/PR body is too long, the branch is already pushed \u2014 have the worktree agent retry `gh pr create --body-file` with a short description (GitHub limit 65,536 characters). Do not invent SSH/auth failures from that error.",
3295
3397
  {
3296
- ref: z6.string().describe("Worktree thread id/ref"),
3297
- action: z6.enum(AGENT_GIT_ACTIONS).describe(
3398
+ ref: z7.string().describe("Worktree thread id/ref"),
3399
+ action: z7.enum(AGENT_GIT_ACTIONS).describe(
3298
3400
  "commit-push | create-draft | create-web | resolve-conflicts | merge"
3299
3401
  )
3300
3402
  },
@@ -3339,7 +3441,7 @@ async function startMcpServer() {
3339
3441
  }
3340
3442
  }
3341
3443
  );
3342
- const agentEnum = z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
3444
+ const agentEnum = z7.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
3343
3445
  server.tool(
3344
3446
  "list_models",
3345
3447
  "List models for an agent. Prefer Auto: do not call this unless you have a reason to pin a specific model (user request, cost/latency, capability). Omit agent to list all.",
@@ -3362,11 +3464,11 @@ async function startMcpServer() {
3362
3464
  "fork_worktree",
3363
3465
  "Fork a worktree agent chat into a NEW git worktree + chat (desktop \u201CFork to new workspace\u201D). Seeds a transcript (through through_index, default all). Optional agent override. Leave model unset for Auto (default) \u2014 only pass model when you have a reason. Not for the orchestrator. Then send_to_thread / wait_for_turn (loop while stillRunning) on the returned id.",
3364
3466
  {
3365
- ref: z6.string().describe("Worktree thread id/ref to fork"),
3366
- through_index: z6.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
3467
+ ref: z7.string().describe("Worktree thread id/ref to fork"),
3468
+ through_index: z7.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
3367
3469
  agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
3368
- model: z6.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
3369
- title: z6.string().optional()
3470
+ model: z7.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
3471
+ title: z7.string().optional()
3370
3472
  },
3371
3473
  async ({ ref, through_index, agent, model, title }) => {
3372
3474
  try {
@@ -3407,11 +3509,11 @@ async function startMcpServer() {
3407
3509
  "fork_chat",
3408
3510
  "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_thread / wait_for_turn (loop while stillRunning) on the returned id. Use fork_worktree only for worktree agents that need a new git worktree.",
3409
3511
  {
3410
- ref: z6.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
3411
- through_index: z6.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
3512
+ ref: z7.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
3513
+ through_index: z7.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
3412
3514
  agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
3413
- model: z6.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
3414
- title: z6.string().optional()
3515
+ model: z7.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
3516
+ title: z7.string().optional()
3415
3517
  },
3416
3518
  async ({ ref, through_index, agent, model, title }) => {
3417
3519
  try {
@@ -3457,8 +3559,8 @@ async function startMcpServer() {
3457
3559
  "run_dev_script",
3458
3560
  "Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
3459
3561
  {
3460
- ref: z6.string(),
3461
- name: z6.string().optional()
3562
+ ref: z7.string(),
3563
+ name: z7.string().optional()
3462
3564
  },
3463
3565
  async ({ ref, name }) => {
3464
3566
  const result = await orch.startDev(ref, name);
@@ -3480,7 +3582,7 @@ async function startMcpServer() {
3480
3582
  server.tool(
3481
3583
  "list_run_scripts",
3482
3584
  "List named run scripts available for a thread",
3483
- { ref: z6.string() },
3585
+ { ref: z7.string() },
3484
3586
  async ({ ref }) => {
3485
3587
  const scripts = orch.listThreadRunScripts(ref);
3486
3588
  const active = orch.getActiveRuns(ref);
@@ -3498,8 +3600,8 @@ async function startMcpServer() {
3498
3600
  "stop_dev_script",
3499
3601
  "Stop a running script for a thread (all scripts if name omitted)",
3500
3602
  {
3501
- ref: z6.string(),
3502
- name: z6.string().optional()
3603
+ ref: z7.string(),
3604
+ name: z7.string().optional()
3503
3605
  },
3504
3606
  async ({ ref, name }) => {
3505
3607
  orch.stopDev(ref, name);
@@ -3509,7 +3611,7 @@ async function startMcpServer() {
3509
3611
  server.tool(
3510
3612
  "run_setup",
3511
3613
  "Re-run workspace setup (Sideboard/Conductor settings, .cursor/worktrees.json, or script/setup). New worktrees already run this automatically.",
3512
- { ref: z6.string() },
3614
+ { ref: z7.string() },
3513
3615
  async ({ ref }) => {
3514
3616
  const result = await orch.runSetup(ref);
3515
3617
  return {
@@ -3520,7 +3622,7 @@ async function startMcpServer() {
3520
3622
  server.tool(
3521
3623
  "add_workspace",
3522
3624
  "Register a git repo as a Sideboard workspace",
3523
- { repoPath: z6.string() },
3625
+ { repoPath: z7.string() },
3524
3626
  async ({ repoPath }) => {
3525
3627
  const ws = await orch.addWorkspace(repoPath);
3526
3628
  return { content: [{ type: "text", text: JSON.stringify(ws) }] };
@@ -3529,7 +3631,7 @@ async function startMcpServer() {
3529
3631
  server.tool(
3530
3632
  "remove_workspace",
3531
3633
  "Unregister a Sideboard workspace (does not archive threads)",
3532
- { repoPath: z6.string() },
3634
+ { repoPath: z7.string() },
3533
3635
  async ({ repoPath }) => {
3534
3636
  orch.removeWorkspace(repoPath);
3535
3637
  return { content: [{ type: "text", text: "ok" }] };
@@ -3539,12 +3641,12 @@ async function startMcpServer() {
3539
3641
  "fanout",
3540
3642
  "Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
3541
3643
  {
3542
- prompt: z6.string(),
3543
- agents: z6.array(z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
3544
- repoPath: z6.string(),
3545
- sourceType: z6.enum(["branch", "pr", "ticket"]).optional(),
3546
- sourceRef: z6.string().optional(),
3547
- title: z6.string().optional()
3644
+ prompt: z7.string(),
3645
+ agents: z7.array(z7.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
3646
+ repoPath: z7.string(),
3647
+ sourceType: z7.enum(["branch", "pr", "ticket"]).optional(),
3648
+ sourceRef: z7.string().optional(),
3649
+ title: z7.string().optional()
3548
3650
  },
3549
3651
  async (args) => {
3550
3652
  const threads = await orch.bestOfN(args);
@@ -3571,8 +3673,8 @@ async function startMcpServer() {
3571
3673
  "list_branches",
3572
3674
  "List git branches in a registered workspace. Pass repoPath from list_workspaces (unmerged into the default branch by default \u2014 for create_thread sourceType=branch).",
3573
3675
  {
3574
- repoPath: z6.string(),
3575
- unmergedOnly: z6.boolean().optional()
3676
+ repoPath: z7.string(),
3677
+ unmergedOnly: z7.boolean().optional()
3576
3678
  },
3577
3679
  async ({ repoPath, unmergedOnly }) => {
3578
3680
  const root = await resolveRepoRoot(repoPath);
@@ -3593,19 +3695,19 @@ async function startMcpServer() {
3593
3695
  "list_prs",
3594
3696
  'List GitHub PRs for a registered workspace (the review surface for assigned ticket work \u2014 not the tickets). Pass repoPath from list_workspaces. "Get me N tickets to review" \u2192 queue=review and limit=N: open non-draft PRs labeled eng-review with no individual user reviewer. Prefer teams that match Settings \u2192 Agents / Projects roles for this repo. A team like engineering-team is not a claim (you are on that team). Also: state (open|closed|merged|all|review), label, reviewer (me|unassigned|login), query, limit (default 40, max 250). Then create_thread with sourceType=pr.',
3595
3697
  {
3596
- repoPath: z6.string(),
3597
- query: z6.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
3598
- queue: z6.enum(["review", "mine", "approved", "changes"]).optional().describe(
3698
+ repoPath: z7.string(),
3699
+ query: z7.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
3700
+ queue: z7.enum(["review", "mine", "approved", "changes"]).optional().describe(
3599
3701
  'review = unclaimed eng-review inbox (use for "get me N tickets to review"). mine = review-requested:@me. approved / changes = eng-approved / eng-requested-changes.'
3600
3702
  ),
3601
- state: z6.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
3602
- label: z6.string().optional().describe(
3703
+ state: z7.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
3704
+ label: z7.string().optional().describe(
3603
3705
  "GitHub label / workflow tag. Comma-separated AND. Examples: eng-review, eng-approved, eng-requested-changes"
3604
3706
  ),
3605
- reviewer: z6.string().optional().describe(
3707
+ reviewer: z7.string().optional().describe(
3606
3708
  "me (review requested of you), unassigned (no individual reviewer; team queues like engineering-team still count), all, or a GitHub login"
3607
3709
  ),
3608
- limit: z6.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
3710
+ limit: z7.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
3609
3711
  },
3610
3712
  async ({ repoPath, query, queue, state, label, reviewer, limit }) => {
3611
3713
  const root = await resolveRepoRoot(repoPath);
@@ -3637,7 +3739,7 @@ async function startMcpServer() {
3637
3739
  server.tool(
3638
3740
  "get_pr_stack",
3639
3741
  "Load the GitHub PR stack for a thread worktree (`gh stack view --json`). Returns null JSON when the branch is not stacked. Prefer this before ask_git merge on stacked PRs (and only merge when the user explicitly asked).",
3640
- { ref: z6.string() },
3742
+ { ref: z7.string() },
3641
3743
  async ({ ref }) => {
3642
3744
  const stack = await orch.getPrStack(ref);
3643
3745
  return {
@@ -3649,8 +3751,8 @@ async function startMcpServer() {
3649
3751
  "open_pr_stack_layers",
3650
3752
  "Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
3651
3753
  {
3652
- ref: z6.string(),
3653
- layer: z6.number().int().positive().optional()
3754
+ ref: z7.string(),
3755
+ layer: z7.number().int().positive().optional()
3654
3756
  },
3655
3757
  async ({ ref, layer }) => {
3656
3758
  const result = await orch.openPrStackLayers(ref, { layer });
@@ -3684,9 +3786,9 @@ async function startMcpServer() {
3684
3786
  "add_stack_layer",
3685
3787
  "Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
3686
3788
  {
3687
- ref: z6.string(),
3688
- branchName: z6.string(),
3689
- title: z6.string().optional()
3789
+ ref: z7.string(),
3790
+ branchName: z7.string(),
3791
+ title: z7.string().optional()
3690
3792
  },
3691
3793
  async ({ ref, branchName, title }) => {
3692
3794
  const result = await orch.addStackLayer(ref, branchName, { title });
@@ -3715,11 +3817,11 @@ async function startMcpServer() {
3715
3817
  "create_pr_stack",
3716
3818
  "Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
3717
3819
  {
3718
- repoPath: z6.string(),
3719
- branches: z6.array(z6.string()).min(1),
3720
- agent: z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
3721
- base: z6.string().optional(),
3722
- title: z6.string().optional()
3820
+ repoPath: z7.string(),
3821
+ branches: z7.array(z7.string()).min(1),
3822
+ agent: z7.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
3823
+ base: z7.string().optional(),
3824
+ title: z7.string().optional()
3723
3825
  },
3724
3826
  async (args) => {
3725
3827
  const result = await orch.createPrStack({
@@ -3758,10 +3860,10 @@ async function startMcpServer() {
3758
3860
  "list_issues",
3759
3861
  "List or search issues (Linear, AbleTime, or GitHub; falls back to GitHub). Use Settings \u2192 Agents / Projects notes and roles to pick tickets relevant to the viewer (query + assignee=me or unassigned as the notes say). Default 40; pass query and/or limit (max 250) when truncated. assignee: me (Linear default), unassigned, all, or a user id. Then create_thread with sourceType=ticket.",
3760
3862
  {
3761
- repoPath: z6.string(),
3762
- query: z6.string().optional().describe("Search title, identifier, or description"),
3763
- assignee: z6.string().optional().describe("me (Linear default), unassigned, all, a user id, or a GitHub login"),
3764
- limit: z6.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
3863
+ repoPath: z7.string(),
3864
+ query: z7.string().optional().describe("Search title, identifier, or description"),
3865
+ assignee: z7.string().optional().describe("me (Linear default), unassigned, all, a user id, or a GitHub login"),
3866
+ limit: z7.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
3765
3867
  },
3766
3868
  async ({ repoPath, query, assignee, limit }) => {
3767
3869
  const root = await resolveRepoRoot(repoPath);