@sideboard-ai/core 0.1.158 → 0.1.159

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-BEIDJG7W.js → abletime-HOQBDQC4.js} +2 -2
  2. package/dist/{abletime-GWZE6OYM.js → abletime-LK7GV556.js} +2 -2
  3. package/dist/{agents-E3ZPWZRS.js → agents-5HY76BCR.js} +4 -4
  4. package/dist/{agents-RGE7PSUY.js → agents-67ZBH5OE.js} +4 -4
  5. package/dist/{app-settings-VRQI3FIE.js → app-settings-BJ5GFAE5.js} +13 -17
  6. package/dist/{app-settings-H3INSR67.js → app-settings-YWJ4MVYM.js} +13 -17
  7. package/dist/{chunk-DVJBO3M7.js → chunk-AGLPYXDT.js} +1 -1
  8. package/dist/{chunk-EPQIOAAY.js → chunk-DWUOKF3W.js} +7 -5
  9. package/dist/{chunk-D7EGJ3EI.js → chunk-EQIJ5MHR.js} +119 -110
  10. package/dist/{chunk-3BWVOUQG.js → chunk-FLG4A5FJ.js} +119 -110
  11. package/dist/{chunk-LQMFCS54.js → chunk-KEDHHXKW.js} +3 -13
  12. package/dist/{chunk-A7QXLYBN.js → chunk-MEZXWELT.js} +3 -13
  13. package/dist/{chunk-CVR4RJ6G.js → chunk-O7RWWPMR.js} +12 -13
  14. package/dist/{chunk-KYKNFJK5.js → chunk-OYOXLY6B.js} +39 -20
  15. package/dist/{chunk-3SUZMPGL.js → chunk-SIMFKJIB.js} +12 -13
  16. package/dist/{chunk-FBFIOVDH.js → chunk-TGU3LSDG.js} +38 -19
  17. package/dist/{chunk-N27GVFZY.js → chunk-UBMCEZRE.js} +1 -1
  18. package/dist/{chunk-BLRHO7XQ.js → chunk-V4R6L2ZL.js} +10 -6
  19. package/dist/{coordinator-prompt-GZESIQNH.js → coordinator-prompt-7NKY7QRK.js} +3 -3
  20. package/dist/{coordinator-prompt-26IJU2AV.js → coordinator-prompt-CNI6AK65.js} +3 -3
  21. package/dist/{global-workspace-REI55FZ6.js → global-workspace-FKAJZHO5.js} +3 -3
  22. package/dist/{global-workspace-5BIW26YR.js → global-workspace-IJYCUO4M.js} +3 -3
  23. package/dist/index.cjs +588 -472
  24. package/dist/index.d.cts +62 -57
  25. package/dist/index.d.ts +62 -57
  26. package/dist/index.js +322 -230
  27. package/dist/mcp/run-stdio.cjs +517 -399
  28. package/dist/mcp/run-stdio.js +262 -163
  29. package/dist/{orchestrator-ZVW2AC6B.js → orchestrator-KFLS5BTP.js} +5 -5
  30. package/dist/{orchestrator-J727WIJY.js → orchestrator-NSVP7DJH.js} +5 -5
  31. package/dist/{workspaces-NYSM2EPX.js → workspaces-D2M3AM2M.js} +3 -3
  32. package/dist/{workspaces-OCEPZXFD.js → workspaces-OFSDJ7PZ.js} +3 -3
  33. package/dist/{worktree-VTMBZKY6.js → worktree-BQE5KLRY.js} +2 -2
  34. package/dist/{worktree-T4SWFSTT.js → worktree-EA6AD6D6.js} +2 -2
  35. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -234,7 +234,7 @@ import {
234
234
  worktreeCleanupSettings,
235
235
  wrapReviewSkillMarkdown,
236
236
  writeWorktreeFile
237
- } from "./chunk-KYKNFJK5.js";
237
+ } from "./chunk-OYOXLY6B.js";
238
238
  import {
239
239
  LEGACY_PLAN_FILE_REL,
240
240
  PLAN_FILE_NAME,
@@ -337,7 +337,7 @@ import {
337
337
  withEventParentId,
338
338
  withEventsParentId,
339
339
  writeInjectedMcpConfig
340
- } from "./chunk-BLRHO7XQ.js";
340
+ } from "./chunk-V4R6L2ZL.js";
341
341
  import {
342
342
  CLOUD_COORDINATOR_BUSY_REPLY,
343
343
  CLOUD_COORDINATOR_STOPPED_REPLY,
@@ -384,7 +384,7 @@ import {
384
384
  stageBuffersAsAttachments,
385
385
  syncWorkspacesFromThreads,
386
386
  takenTeamSlugsForOrchestration
387
- } from "./chunk-CVR4RJ6G.js";
387
+ } from "./chunk-O7RWWPMR.js";
388
388
  import {
389
389
  FAMOUS_SOCCER_TEAMS,
390
390
  GITHUB_PR_BODY_MAX_CHARS,
@@ -477,7 +477,7 @@ import {
477
477
  worktreeDisplayLabel,
478
478
  worktreeDisplayLabelForGroup,
479
479
  worktreeNameFromPath
480
- } from "./chunk-LQMFCS54.js";
480
+ } from "./chunk-KEDHHXKW.js";
481
481
  import {
482
482
  ATTACHMENTS_DIR,
483
483
  DETACHED_JOBS_DIR,
@@ -527,7 +527,7 @@ import {
527
527
  toAbleTimeIssueInfo,
528
528
  updateAbleTimeTask,
529
529
  verifyAbleTimeConnection
530
- } from "./chunk-DVJBO3M7.js";
530
+ } from "./chunk-AGLPYXDT.js";
531
531
  import {
532
532
  formatFetchError,
533
533
  httpFetch,
@@ -544,16 +544,11 @@ import {
544
544
  withMaxOldSpaceSize
545
545
  } from "./chunk-HEII7W3Q.js";
546
546
  import {
547
- ACCOUNT_ROLES,
548
- ACCOUNT_ROLE_LABELS,
549
- ACCOUNT_ROLE_MAX,
550
- ACCOUNT_ROLE_PRESETS,
551
547
  FOLLOW_UP_BEHAVIORS,
552
548
  GITHUB_GIT_AUTH_MODES,
553
549
  HARNESS_ENV_KEYS,
554
550
  ISSUE_SOURCE_LABELS,
555
551
  PROFILE_NOTES_MAX,
556
- accountRoleLabel,
557
552
  appSettingsPath,
558
553
  applyAppEnvironment,
559
554
  autoArchiveOnMergeEnabled,
@@ -577,9 +572,12 @@ import {
577
572
  emptyPublicIntegrations,
578
573
  ensureSlackDeviceIdentity,
579
574
  findProjectProfileKey,
575
+ foldLegacyRolesIntoNotes,
580
576
  followUpBehavior,
581
577
  formatAccountProfilePlaybookLine,
582
578
  formatProjectProfilePlaybookLines,
579
+ formatViewerContextDirective,
580
+ formatViewerContextReminder,
583
581
  formatWorkspaceProfileSuffix,
584
582
  getAbleTimeAccessToken,
585
583
  getAbleTimeHost,
@@ -601,8 +599,8 @@ import {
601
599
  orchestrationQuotaFallbackAgent,
602
600
  orchestrationQuotaOnLimit,
603
601
  persistVaultKeyInKeychain,
604
- preferTeamsForRole,
605
602
  readKeychainVaultKey,
603
+ readViewerContext,
606
604
  resolveAccountProfile,
607
605
  resolveAccountProfileFromSettings,
608
606
  resolveAgentExecutable,
@@ -613,7 +611,6 @@ import {
613
611
  resolveVaultKey,
614
612
  resolveViewerProfile,
615
613
  resolveViewerProfileForRepo,
616
- reviewTeamHintsForRoles,
617
614
  saveAbleTimeConnection,
618
615
  saveAppSettings,
619
616
  saveLinearOAuth,
@@ -632,8 +629,9 @@ import {
632
629
  updateDefaultsSettings,
633
630
  updateIntegrationsSettings,
634
631
  updateOpencodeSettings,
635
- updateProjectProfileSettings
636
- } from "./chunk-3BWVOUQG.js";
632
+ updateProjectProfileSettings,
633
+ writeViewerContext
634
+ } from "./chunk-FLG4A5FJ.js";
637
635
  import {
638
636
  stripNestedElectronEnv
639
637
  } from "./chunk-4TR3HZFT.js";
@@ -738,9 +736,9 @@ async function getGitHubStatus() {
738
736
  };
739
737
  }
740
738
  const status = await run("gh", ["auth", "status"], { reject: false });
741
- const text6 = `${status.stdout}
739
+ const text7 = `${status.stdout}
742
740
  ${status.stderr}`;
743
- const loginMatch = text6.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text6.match(/Logged in to ([^\s]+) as (\S+)/i);
741
+ const loginMatch = text7.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text7.match(/Logged in to ([^\s]+) as (\S+)/i);
744
742
  if (loginMatch) {
745
743
  return {
746
744
  connected: true,
@@ -2065,11 +2063,11 @@ function fenceLanguage(path2, language) {
2065
2063
  }
2066
2064
  function buildCodeRefAttachment(input) {
2067
2065
  const path2 = input.path.trim();
2068
- const text6 = input.text.replace(/\n$/, "");
2066
+ const text7 = input.text.replace(/\n$/, "");
2069
2067
  if (!path2) {
2070
2068
  throw new Error("code reference requires a file path");
2071
2069
  }
2072
- if (!text6.trim()) {
2070
+ if (!text7.trim()) {
2073
2071
  throw new Error("code reference requires selected text");
2074
2072
  }
2075
2073
  if (input.startLine < 1 || input.endLine < 1 || input.endLine < input.startLine) {
@@ -2082,7 +2080,7 @@ function buildCodeRefAttachment(input) {
2082
2080
  `Referenced code from \`${path2}\` (${range}).`,
2083
2081
  "",
2084
2082
  fence,
2085
- text6,
2083
+ text7,
2086
2084
  "```"
2087
2085
  ].join("\n");
2088
2086
  return {
@@ -2136,16 +2134,16 @@ var PASTE_ATTACH_MIN_CHARS = 1200;
2136
2134
  var PASTE_ATTACH_MIN_LINES = 15;
2137
2135
  var PASTED_NAME_RE = /^Pasted text #(\d+)\.txt$/i;
2138
2136
  var PASTED_NAME_ALT_RE = /^pasted-(\d+)\.txt$/i;
2139
- function pastedTextStats(text6) {
2140
- const chars = text6.length;
2137
+ function pastedTextStats(text7) {
2138
+ const chars = text7.length;
2141
2139
  if (chars === 0) return { chars: 0, lines: 0 };
2142
- const lines = text6.split(/\r\n|\r|\n/).length;
2140
+ const lines = text7.split(/\r\n|\r|\n/).length;
2143
2141
  return { chars, lines };
2144
2142
  }
2145
- function shouldAttachPastedText(text6) {
2146
- const trimmed = text6.trim();
2143
+ function shouldAttachPastedText(text7) {
2144
+ const trimmed = text7.trim();
2147
2145
  if (!trimmed) return false;
2148
- const { chars, lines } = pastedTextStats(text6);
2146
+ const { chars, lines } = pastedTextStats(text7);
2149
2147
  return chars >= PASTE_ATTACH_MIN_CHARS || lines >= PASTE_ATTACH_MIN_LINES;
2150
2148
  }
2151
2149
  function nextPastedTextName(existing) {
@@ -2156,13 +2154,13 @@ function nextPastedTextName(existing) {
2156
2154
  }
2157
2155
  return `Pasted text #${max + 1}.txt`;
2158
2156
  }
2159
- function buildPastedTextAttachment(text6, opts) {
2157
+ function buildPastedTextAttachment(text7, opts) {
2160
2158
  return {
2161
2159
  id: opts?.id ?? randomUUID(),
2162
2160
  name: opts?.name ?? "Pasted text #1.txt",
2163
2161
  kind: "file",
2164
2162
  path: opts?.path,
2165
- content: text6
2163
+ content: text7
2166
2164
  };
2167
2165
  }
2168
2166
 
@@ -2247,8 +2245,8 @@ function latestPendingPlanQuestions(input) {
2247
2245
  return null;
2248
2246
  }
2249
2247
  var PLAN_QUESTION_ANSWERS_PREFIX = "Answers to your questions:";
2250
- function isPlanQuestionAnswersMessage(text6) {
2251
- return text6.startsWith(PLAN_QUESTION_ANSWERS_PREFIX);
2248
+ function isPlanQuestionAnswersMessage(text7) {
2249
+ return text7.startsWith(PLAN_QUESTION_ANSWERS_PREFIX);
2252
2250
  }
2253
2251
  function formatPlanQuestionAnswers(questions, answers) {
2254
2252
  const lines = [PLAN_QUESTION_ANSWERS_PREFIX, ""];
@@ -2287,7 +2285,7 @@ function formatPlanQuestionsForChat(questions) {
2287
2285
  // src/mcp/server.ts
2288
2286
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2289
2287
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2290
- import { z as z6 } from "zod";
2288
+ import { z as z7 } from "zod";
2291
2289
  import { randomUUID as randomUUID3 } from "crypto";
2292
2290
  import { basename } from "path";
2293
2291
 
@@ -2303,9 +2301,9 @@ function mcpArchiveBlockedReason(thread) {
2303
2301
  function lastMessagePreview(messages, max = 160) {
2304
2302
  if (!messages?.length) return null;
2305
2303
  for (let i = messages.length - 1; i >= 0; i--) {
2306
- const text6 = messages[i]?.text?.trim();
2307
- if (!text6 || isInternalAgentStatusText(text6)) continue;
2308
- const flat = text6.replace(/\s+/g, " ");
2304
+ const text7 = messages[i]?.text?.trim();
2305
+ if (!text7 || isInternalAgentStatusText(text7)) continue;
2306
+ const flat = text7.replace(/\s+/g, " ");
2309
2307
  return flat.length > max ? `${flat.slice(0, max)}\u2026` : flat;
2310
2308
  }
2311
2309
  return null;
@@ -2537,8 +2535,8 @@ function labelGithubUrl(url) {
2537
2535
  }
2538
2536
  return { kind: "other", label: "GitHub", url: raw };
2539
2537
  }
2540
- function appendGithubLink(text6, githubUrl) {
2541
- const body = text6.trimEnd();
2538
+ function appendGithubLink(text7, githubUrl) {
2539
+ const body = text7.trimEnd();
2542
2540
  if (!githubUrl?.trim()) return body;
2543
2541
  const labeled = labelGithubUrl(githubUrl);
2544
2542
  if (!labeled) {
@@ -3228,6 +3226,101 @@ function registerConnectedIssueVendorTools(server, settings = loadAppSettings())
3228
3226
  if (isAbleTimeConnected(settings)) registerAbleTimeTools(server);
3229
3227
  }
3230
3228
 
3229
+ // src/mcp/viewer-context-tools.ts
3230
+ import { z as z5 } from "zod";
3231
+ function text5(payload, isError = false) {
3232
+ return mcpJson(payload, isError);
3233
+ }
3234
+ function matchRegisteredWorkspace(repoPath) {
3235
+ const hint = (repoPath ?? "").trim().replace(/\/+$/, "");
3236
+ if (!hint) return null;
3237
+ const match = listWorkspaces().find((w) => {
3238
+ const stored = w.path.replace(/\/+$/, "");
3239
+ return stored === hint || hint.startsWith(`${stored}/`) || stored.startsWith(`${hint}/`);
3240
+ });
3241
+ return match?.path.replace(/\/+$/, "") ?? null;
3242
+ }
3243
+ async function resolveProjectRepoPath(repoPath) {
3244
+ const hint = repoPath?.trim() || process.cwd();
3245
+ let root = hint;
3246
+ try {
3247
+ root = await resolveRepoRoot(hint);
3248
+ } catch {
3249
+ root = hint.replace(/\/+$/, "");
3250
+ }
3251
+ return matchRegisteredWorkspace(root) ?? matchRegisteredWorkspace(hint);
3252
+ }
3253
+ var PROJECT_PATH_HINT = "Pass repoPath from list_workspaces. Orchestration cwd is synthetic and empty \u2014 do not omit repoPath for project context.";
3254
+ function registerViewerContextTools(server) {
3255
+ server.tool(
3256
+ "get_viewer_context",
3257
+ "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).",
3258
+ {
3259
+ repoPath: z5.string().optional().describe(
3260
+ "Registered workspace or worktree path. Required from orchestration for a project. Omit on a worktree turn (uses cwd)."
3261
+ )
3262
+ },
3263
+ async ({ repoPath }) => {
3264
+ try {
3265
+ const path2 = await resolveProjectRepoPath(repoPath);
3266
+ return text5(readViewerContext(path2 ?? void 0));
3267
+ } catch (err) {
3268
+ return text5(
3269
+ { error: err instanceof Error ? err.message : String(err) },
3270
+ true
3271
+ );
3272
+ }
3273
+ }
3274
+ );
3275
+ server.tool(
3276
+ "update_viewer_context",
3277
+ "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.",
3278
+ {
3279
+ scope: z5.enum(["account", "project"]).describe("account = Settings \u2192 Agents; project = Settings \u2192 Projects for one repo"),
3280
+ context: z5.string().describe("Full replacement text (roles, tickets, review queues). Empty clears."),
3281
+ confirmed: z5.boolean().optional().describe("Must be true after the user confirmed via ask_user. False/omit is rejected."),
3282
+ repoPath: z5.string().optional().describe(
3283
+ "Required for scope=project from orchestration (path from list_workspaces). Worktree turns may omit it (cwd)."
3284
+ )
3285
+ },
3286
+ async ({ scope, context, confirmed, repoPath }) => {
3287
+ try {
3288
+ if (scope === "project") {
3289
+ const path2 = await resolveProjectRepoPath(repoPath);
3290
+ if (!path2) {
3291
+ return text5(
3292
+ {
3293
+ ok: false,
3294
+ error: "repoPath required",
3295
+ message: PROJECT_PATH_HINT
3296
+ },
3297
+ true
3298
+ );
3299
+ }
3300
+ const result2 = writeViewerContext({
3301
+ scope,
3302
+ context,
3303
+ repoPath: path2,
3304
+ confirmed: confirmed === true
3305
+ });
3306
+ return text5(result2, !result2.ok);
3307
+ }
3308
+ const result = writeViewerContext({
3309
+ scope,
3310
+ context,
3311
+ confirmed: confirmed === true
3312
+ });
3313
+ return text5(result, !result.ok);
3314
+ } catch (err) {
3315
+ return text5(
3316
+ { error: err instanceof Error ? err.message : String(err) },
3317
+ true
3318
+ );
3319
+ }
3320
+ }
3321
+ );
3322
+ }
3323
+
3231
3324
  // src/mcp/pr-list.ts
3232
3325
  function compactPrRow(pr) {
3233
3326
  return {
@@ -3263,15 +3356,15 @@ function formatMcpPrList(input) {
3263
3356
  }
3264
3357
 
3265
3358
  // src/mcp/schedule-tools.ts
3266
- import { z as z5 } from "zod";
3267
- function text5(payload, isError = false) {
3359
+ import { z as z6 } from "zod";
3360
+ function text6(payload, isError = false) {
3268
3361
  return {
3269
3362
  content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
3270
3363
  ...isError ? { isError: true } : {}
3271
3364
  };
3272
3365
  }
3273
3366
  function fail5(err) {
3274
- return text5(
3367
+ return text6(
3275
3368
  { error: err instanceof Error ? err.message : String(err) },
3276
3369
  true
3277
3370
  );
@@ -3296,7 +3389,7 @@ function registerScheduleTools(server) {
3296
3389
  {},
3297
3390
  async () => {
3298
3391
  try {
3299
- return text5({ schedules: listSchedules() });
3392
+ return text6({ schedules: listSchedules() });
3300
3393
  } catch (err) {
3301
3394
  return fail5(err);
3302
3395
  }
@@ -3306,17 +3399,17 @@ function registerScheduleTools(server) {
3306
3399
  "create_schedule",
3307
3400
  "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.",
3308
3401
  {
3309
- prompt: z5.string().describe("User message / goal queued when the schedule fires"),
3310
- name: z5.string().optional(),
3311
- at: z5.string().optional().describe("ISO datetime for a one-shot"),
3312
- every: z5.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
3313
- cron: z5.string().optional().describe("5-field cron expression"),
3314
- tz: z5.string().optional().describe("IANA timezone for cron (default: system)"),
3315
- threadId: z5.string().optional().describe(
3402
+ prompt: z6.string().describe("User message / goal queued when the schedule fires"),
3403
+ name: z6.string().optional(),
3404
+ at: z6.string().optional().describe("ISO datetime for a one-shot"),
3405
+ every: z6.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
3406
+ cron: z6.string().optional().describe("5-field cron expression"),
3407
+ tz: z6.string().optional().describe("IANA timezone for cron (default: system)"),
3408
+ threadId: z6.string().optional().describe(
3316
3409
  'Existing orchestration chat id, or "self" for this coordinator. Omit to create a new Global chat on fire.'
3317
3410
  ),
3318
- agent: z5.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
3319
- model: z5.string().optional()
3411
+ agent: z6.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
3412
+ model: z6.string().optional()
3320
3413
  },
3321
3414
  async (args) => {
3322
3415
  try {
@@ -3336,7 +3429,7 @@ function registerScheduleTools(server) {
3336
3429
  model: args.model,
3337
3430
  createdBy: "mcp"
3338
3431
  });
3339
- return text5({
3432
+ return text6({
3340
3433
  schedule,
3341
3434
  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."
3342
3435
  });
@@ -3349,17 +3442,17 @@ function registerScheduleTools(server) {
3349
3442
  "update_schedule",
3350
3443
  "Update a local schedule (prompt, cadence, target thread, enabled). Pass id from list_schedules.",
3351
3444
  {
3352
- id: z5.string(),
3353
- prompt: z5.string().optional(),
3354
- name: z5.string().optional(),
3355
- at: z5.string().optional(),
3356
- every: z5.string().optional(),
3357
- cron: z5.string().optional(),
3358
- tz: z5.string().optional(),
3359
- threadId: z5.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
3360
- agent: z5.enum(["claude", "cursor", "codex", "opencode"]).optional(),
3361
- model: z5.string().optional(),
3362
- enabled: z5.boolean().optional()
3445
+ id: z6.string(),
3446
+ prompt: z6.string().optional(),
3447
+ name: z6.string().optional(),
3448
+ at: z6.string().optional(),
3449
+ every: z6.string().optional(),
3450
+ cron: z6.string().optional(),
3451
+ tz: z6.string().optional(),
3452
+ threadId: z6.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
3453
+ agent: z6.enum(["claude", "cursor", "codex", "opencode"]).optional(),
3454
+ model: z6.string().optional(),
3455
+ enabled: z6.boolean().optional()
3363
3456
  },
3364
3457
  async (args) => {
3365
3458
  try {
@@ -3388,7 +3481,7 @@ function registerScheduleTools(server) {
3388
3481
  enabled: args.enabled,
3389
3482
  model: args.model
3390
3483
  });
3391
- return text5({ schedule });
3484
+ return text6({ schedule });
3392
3485
  } catch (err) {
3393
3486
  return fail5(err);
3394
3487
  }
@@ -3397,11 +3490,11 @@ function registerScheduleTools(server) {
3397
3490
  server.tool(
3398
3491
  "delete_schedule",
3399
3492
  "Delete a local schedule. Pass id from list_schedules.",
3400
- { id: z5.string() },
3493
+ { id: z6.string() },
3401
3494
  async ({ id }) => {
3402
3495
  try {
3403
3496
  deleteSchedule(id);
3404
- return text5({ ok: true, id });
3497
+ return text6({ ok: true, id });
3405
3498
  } catch (err) {
3406
3499
  return fail5(err);
3407
3500
  }
@@ -3410,14 +3503,14 @@ function registerScheduleTools(server) {
3410
3503
  server.tool(
3411
3504
  "run_schedule",
3412
3505
  "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.",
3413
- { id: z5.string() },
3506
+ { id: z6.string() },
3414
3507
  async ({ id }) => {
3415
3508
  try {
3416
3509
  if (!getSchedule(id)) {
3417
3510
  return fail5(new Error(`Schedule not found: ${id}`));
3418
3511
  }
3419
3512
  const schedule = await fireSchedule(id);
3420
- return text5({ schedule });
3513
+ return text6({ schedule });
3421
3514
  } catch (err) {
3422
3515
  return fail5(err);
3423
3516
  }
@@ -3820,7 +3913,7 @@ async function startMcpServer() {
3820
3913
  );
3821
3914
  }
3822
3915
  try {
3823
- const { maxConcurrentAgents: maxConcurrentAgents2 } = await import("./app-settings-H3INSR67.js");
3916
+ const { maxConcurrentAgents: maxConcurrentAgents2 } = await import("./app-settings-YWJ4MVYM.js");
3824
3917
  orch.setMaxConcurrent(maxConcurrentAgents2());
3825
3918
  } catch {
3826
3919
  }
@@ -3837,13 +3930,14 @@ async function startMcpServer() {
3837
3930
  version: "0.1.0"
3838
3931
  });
3839
3932
  const worktreeProfile = sideboardMcpProfile() === "worktree";
3933
+ registerViewerContextTools(server);
3840
3934
  if (worktreeProfile) {
3841
3935
  registerConnectedIssueVendorTools(server);
3842
3936
  }
3843
3937
  if (!worktreeProfile) {
3844
3938
  server.tool(
3845
3939
  "list_workspaces",
3846
- "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.",
3940
+ "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.",
3847
3941
  {},
3848
3942
  async () => {
3849
3943
  const workspaces = orch.listWorkspaces();
@@ -3892,13 +3986,13 @@ async function startMcpServer() {
3892
3986
  "list_board",
3893
3987
  "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.",
3894
3988
  {
3895
- query: z6.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
3896
- repoPath: z6.string().optional().describe("Limit to one workspace path from list_workspaces"),
3897
- kind: z6.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
3898
- column: z6.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
3989
+ query: z7.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
3990
+ repoPath: z7.string().optional().describe("Limit to one workspace path from list_workspaces"),
3991
+ kind: z7.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
3992
+ column: z7.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
3899
3993
  "Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
3900
3994
  ),
3901
- limit: z6.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
3995
+ limit: z7.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
3902
3996
  },
3903
3997
  async ({ query, repoPath, kind, column, limit }) => {
3904
3998
  const workspaces = orch.listWorkspaces();
@@ -3936,7 +4030,7 @@ async function startMcpServer() {
3936
4030
  server.tool(
3937
4031
  "get_thread",
3938
4032
  "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.",
3939
- { ref: z6.string() },
4033
+ { ref: z7.string() },
3940
4034
  async ({ ref }) => {
3941
4035
  const t = orch.getThread(ref);
3942
4036
  if (!t) {
@@ -3978,17 +4072,17 @@ async function startMcpServer() {
3978
4072
  "present_artifact",
3979
4073
  "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.",
3980
4074
  {
3981
- title: z6.string().describe("Short title shown in the artifact pane header"),
3982
- type: z6.enum(["html", "svg", "markdown", "react", "log"]).describe(
4075
+ title: z7.string().describe("Short title shown in the artifact pane header"),
4076
+ type: z7.enum(["html", "svg", "markdown", "react", "log"]).describe(
3983
4077
  "html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
3984
4078
  ),
3985
- content: z6.string().describe(
4079
+ content: z7.string().describe(
3986
4080
  "html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
3987
4081
  ),
3988
- artifact_id: z6.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
3989
- status: z6.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
3990
- phase: z6.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
3991
- mode: z6.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
4082
+ artifact_id: z7.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
4083
+ status: z7.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
4084
+ phase: z7.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
4085
+ mode: z7.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
3992
4086
  },
3993
4087
  async ({ title, type, artifact_id, status, phase, mode }) => {
3994
4088
  const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -4009,15 +4103,15 @@ async function startMcpServer() {
4009
4103
  "ask_user",
4010
4104
  "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.",
4011
4105
  {
4012
- questions: z6.array(
4013
- z6.object({
4014
- question: z6.string().describe("Full question text ending with ?"),
4015
- header: z6.string().max(24).optional().describe("Short label shown above the question"),
4016
- multiSelect: z6.boolean().optional().describe("Allow selecting multiple options"),
4017
- options: z6.array(
4018
- z6.object({
4019
- label: z6.string(),
4020
- description: z6.string().optional().describe("What this option means / when to choose it (strongly preferred)")
4106
+ questions: z7.array(
4107
+ z7.object({
4108
+ question: z7.string().describe("Full question text ending with ?"),
4109
+ header: z7.string().max(24).optional().describe("Short label shown above the question"),
4110
+ multiSelect: z7.boolean().optional().describe("Allow selecting multiple options"),
4111
+ options: z7.array(
4112
+ z7.object({
4113
+ label: z7.string(),
4114
+ description: z7.string().optional().describe("What this option means / when to choose it (strongly preferred)")
4021
4115
  })
4022
4116
  ).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
4023
4117
  })
@@ -4036,9 +4130,9 @@ async function startMcpServer() {
4036
4130
  "present_plan",
4037
4131
  "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.",
4038
4132
  {
4039
- title: z6.string().optional().describe("Short plan title (defaults to Plan)"),
4040
- content: z6.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
4041
- thread_id: z6.string().optional().describe("Sideboard thread id when cwd is not the worktree")
4133
+ title: z7.string().optional().describe("Short plan title (defaults to Plan)"),
4134
+ content: z7.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
4135
+ thread_id: z7.string().optional().describe("Sideboard thread id when cwd is not the worktree")
4042
4136
  },
4043
4137
  async ({ title, content, thread_id }) => {
4044
4138
  const { writePlanFile: writePlanFile2 } = await import("./plan-file-LSSSQGTQ.js");
@@ -4061,15 +4155,15 @@ async function startMcpServer() {
4061
4155
  "present_schema",
4062
4156
  "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.",
4063
4157
  {
4064
- title: z6.string().describe("Pane title"),
4065
- mode: z6.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
4066
- datasource: z6.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
4067
- resource_id: z6.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
4068
- record_id: z6.string().optional().describe("Record id when opening form mode"),
4069
- resource: z6.record(z6.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
4070
- record: z6.record(z6.unknown()).optional().describe("Inline record { id, data, published_at? }"),
4071
- records: z6.array(z6.record(z6.unknown())).optional().describe("Inline records for table mode"),
4072
- pane_id: z6.string().optional().describe("Stable pane id across updates")
4158
+ title: z7.string().describe("Pane title"),
4159
+ mode: z7.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
4160
+ datasource: z7.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
4161
+ resource_id: z7.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
4162
+ record_id: z7.string().optional().describe("Record id when opening form mode"),
4163
+ resource: z7.record(z7.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
4164
+ record: z7.record(z7.unknown()).optional().describe("Inline record { id, data, published_at? }"),
4165
+ records: z7.array(z7.record(z7.unknown())).optional().describe("Inline records for table mode"),
4166
+ pane_id: z7.string().optional().describe("Stable pane id across updates")
4073
4167
  },
4074
4168
  async (args) => {
4075
4169
  const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -4090,10 +4184,10 @@ async function startMcpServer() {
4090
4184
  "present_files",
4091
4185
  "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.",
4092
4186
  {
4093
- title: z6.string().optional().describe("Pane title (default: Files)"),
4094
- datasource: z6.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
4095
- path: z6.string().optional().describe("Initial folder path (e.g. public)"),
4096
- pane_id: z6.string().optional().describe("Stable pane id across updates")
4187
+ title: z7.string().optional().describe("Pane title (default: Files)"),
4188
+ datasource: z7.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
4189
+ path: z7.string().optional().describe("Initial folder path (e.g. public)"),
4190
+ pane_id: z7.string().optional().describe("Stable pane id across updates")
4097
4191
  },
4098
4192
  async (args) => {
4099
4193
  const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -4112,8 +4206,8 @@ async function startMcpServer() {
4112
4206
  "wait_for_job",
4113
4207
  "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.",
4114
4208
  {
4115
- id: z6.string().describe("Detached job id (same kebab-case id passed to detached-job.js start)"),
4116
- timeoutMs: z6.number().optional()
4209
+ id: z7.string().describe("Detached job id (same kebab-case id passed to detached-job.js start)"),
4210
+ timeoutMs: z7.number().optional()
4117
4211
  },
4118
4212
  async ({ id, timeoutMs }) => {
4119
4213
  const result = await waitForDetachedJob(process.cwd(), id, {
@@ -4127,14 +4221,14 @@ async function startMcpServer() {
4127
4221
  registerConnectedIssueVendorTools(server);
4128
4222
  registerScheduleTools(server);
4129
4223
  const { getCaffeinateHold: getCaffeinateHold2, setCaffeinateHold: setCaffeinateHold2 } = await import("./caffeinate-hold-SK2QISPE.js");
4130
- const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await import("./app-settings-H3INSR67.js");
4224
+ const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await import("./app-settings-YWJ4MVYM.js");
4131
4225
  const accountDefaults = resolveThreadDefaults2();
4132
4226
  const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
4133
4227
  server.tool(
4134
4228
  "set_caffeinate",
4135
4229
  "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.",
4136
4230
  {
4137
- enabled: z6.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
4231
+ enabled: z7.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
4138
4232
  },
4139
4233
  async ({ enabled }) => {
4140
4234
  const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
@@ -4172,18 +4266,18 @@ async function startMcpServer() {
4172
4266
  "create_thread",
4173
4267
  `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.`,
4174
4268
  {
4175
- sourceType: z6.enum(["branch", "pr", "ticket"]),
4176
- sourceRef: z6.string(),
4177
- agent: z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
4178
- model: z6.string().nullable().optional().describe(
4269
+ sourceType: z7.enum(["branch", "pr", "ticket"]),
4270
+ sourceRef: z7.string(),
4271
+ agent: z7.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
4272
+ model: z7.string().nullable().optional().describe(
4179
4273
  `Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
4180
4274
  ),
4181
- repoPath: z6.string(),
4182
- title: z6.string().optional(),
4183
- cowboy: z6.boolean().optional().describe(
4275
+ repoPath: z7.string(),
4276
+ title: z7.string().optional(),
4277
+ cowboy: z7.boolean().optional().describe(
4184
4278
  "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."
4185
4279
  ),
4186
- parentThreadId: z6.string().optional().describe(
4280
+ parentThreadId: z7.string().optional().describe(
4187
4281
  "Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
4188
4282
  )
4189
4283
  },
@@ -4199,10 +4293,10 @@ async function startMcpServer() {
4199
4293
  "start_board_card",
4200
4294
  "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.",
4201
4295
  {
4202
- kind: z6.enum(["ticket", "pr", "branch"]),
4203
- ref: z6.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
4204
- repoPath: z6.string().describe("Workspace path from list_workspaces / list_board"),
4205
- title: z6.string().optional()
4296
+ kind: z7.enum(["ticket", "pr", "branch"]),
4297
+ ref: z7.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
4298
+ repoPath: z7.string().describe("Workspace path from list_workspaces / list_board"),
4299
+ title: z7.string().optional()
4206
4300
  },
4207
4301
  async ({ kind, ref, repoPath, title }) => {
4208
4302
  const root = await resolveRepoRoot(repoPath);
@@ -4306,9 +4400,9 @@ async function startMcpServer() {
4306
4400
  "send_to_thread",
4307
4401
  '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.',
4308
4402
  {
4309
- ref: z6.string(),
4310
- prompt: z6.string(),
4311
- force_stop: z6.boolean().optional()
4403
+ ref: z7.string(),
4404
+ prompt: z7.string(),
4405
+ force_stop: z7.boolean().optional()
4312
4406
  },
4313
4407
  async ({ ref, prompt, force_stop }) => {
4314
4408
  if (force_stop) {
@@ -4337,8 +4431,8 @@ async function startMcpServer() {
4337
4431
  "wait_for_turn",
4338
4432
  "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).",
4339
4433
  {
4340
- ref: z6.string(),
4341
- timeoutMs: z6.number().optional()
4434
+ ref: z7.string(),
4435
+ timeoutMs: z7.number().optional()
4342
4436
  },
4343
4437
  async ({ ref, timeoutMs }) => {
4344
4438
  const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
@@ -4368,7 +4462,7 @@ async function startMcpServer() {
4368
4462
  server.tool(
4369
4463
  "get_turn_result",
4370
4464
  "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).",
4371
- { ref: z6.string() },
4465
+ { ref: z7.string() },
4372
4466
  async ({ ref }) => {
4373
4467
  const result = orch.getTurnResult(ref);
4374
4468
  return {
@@ -4389,8 +4483,8 @@ async function startMcpServer() {
4389
4483
  "stop_thread",
4390
4484
  "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.",
4391
4485
  {
4392
- ref: z6.string(),
4393
- force: z6.boolean().optional()
4486
+ ref: z7.string(),
4487
+ force: z7.boolean().optional()
4394
4488
  },
4395
4489
  async ({ ref, force }) => {
4396
4490
  const t = orch.getThread(ref);
@@ -4420,7 +4514,7 @@ async function startMcpServer() {
4420
4514
  server.tool(
4421
4515
  "archive_thread",
4422
4516
  "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.",
4423
- { ref: z6.string() },
4517
+ { ref: z7.string() },
4424
4518
  async ({ ref }) => {
4425
4519
  const t = orch.getThread(ref);
4426
4520
  if (!t) {
@@ -4453,7 +4547,7 @@ async function startMcpServer() {
4453
4547
  server.tool(
4454
4548
  "restore_thread",
4455
4549
  "Restore an archived thread (recreates worktree from branch when needed)",
4456
- { ref: z6.string() },
4550
+ { ref: z7.string() },
4457
4551
  async ({ ref }) => {
4458
4552
  try {
4459
4553
  const restored = await orch.restore(ref);
@@ -4479,8 +4573,8 @@ async function startMcpServer() {
4479
4573
  "get_diff",
4480
4574
  "Compact diff summary (capped hunks, paginated)",
4481
4575
  {
4482
- ref: z6.string(),
4483
- maxFiles: z6.number().optional()
4576
+ ref: z7.string(),
4577
+ maxFiles: z7.number().optional()
4484
4578
  },
4485
4579
  async ({ ref, maxFiles }) => {
4486
4580
  const summary = await orch.diffSummary(ref);
@@ -4500,7 +4594,7 @@ async function startMcpServer() {
4500
4594
  server.tool(
4501
4595
  "get_pr_checks",
4502
4596
  "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.",
4503
- { ref: z6.string().describe("Worktree thread id/ref") },
4597
+ { ref: z7.string().describe("Worktree thread id/ref") },
4504
4598
  async ({ ref }) => {
4505
4599
  try {
4506
4600
  const checks = await orch.getPrChecks(ref);
@@ -4516,7 +4610,7 @@ async function startMcpServer() {
4516
4610
  server.tool(
4517
4611
  "request_review",
4518
4612
  '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.',
4519
- { ref: z6.string().describe("Worktree thread id/ref to review") },
4613
+ { ref: z7.string().describe("Worktree thread id/ref to review") },
4520
4614
  async ({ ref }) => {
4521
4615
  try {
4522
4616
  const tab = await orch.requestReview(ref);
@@ -4545,8 +4639,8 @@ async function startMcpServer() {
4545
4639
  "ask_git",
4546
4640
  "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.",
4547
4641
  {
4548
- ref: z6.string().describe("Worktree thread id/ref"),
4549
- action: z6.enum(AGENT_GIT_ACTIONS).describe(
4642
+ ref: z7.string().describe("Worktree thread id/ref"),
4643
+ action: z7.enum(AGENT_GIT_ACTIONS).describe(
4550
4644
  "commit-push | create-draft | create-web | resolve-conflicts | merge"
4551
4645
  )
4552
4646
  },
@@ -4591,7 +4685,7 @@ async function startMcpServer() {
4591
4685
  }
4592
4686
  }
4593
4687
  );
4594
- const agentEnum = z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
4688
+ const agentEnum = z7.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
4595
4689
  server.tool(
4596
4690
  "list_models",
4597
4691
  "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.",
@@ -4614,11 +4708,11 @@ async function startMcpServer() {
4614
4708
  "fork_worktree",
4615
4709
  "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.",
4616
4710
  {
4617
- ref: z6.string().describe("Worktree thread id/ref to fork"),
4618
- through_index: z6.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
4711
+ ref: z7.string().describe("Worktree thread id/ref to fork"),
4712
+ through_index: z7.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
4619
4713
  agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
4620
- model: z6.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
4621
- title: z6.string().optional()
4714
+ model: z7.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
4715
+ title: z7.string().optional()
4622
4716
  },
4623
4717
  async ({ ref, through_index, agent, model, title }) => {
4624
4718
  try {
@@ -4659,11 +4753,11 @@ async function startMcpServer() {
4659
4753
  "fork_chat",
4660
4754
  "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.",
4661
4755
  {
4662
- ref: z6.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
4663
- through_index: z6.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
4756
+ ref: z7.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
4757
+ through_index: z7.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
4664
4758
  agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
4665
- model: z6.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
4666
- title: z6.string().optional()
4759
+ model: z7.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
4760
+ title: z7.string().optional()
4667
4761
  },
4668
4762
  async ({ ref, through_index, agent, model, title }) => {
4669
4763
  try {
@@ -4709,8 +4803,8 @@ async function startMcpServer() {
4709
4803
  "run_dev_script",
4710
4804
  "Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
4711
4805
  {
4712
- ref: z6.string(),
4713
- name: z6.string().optional()
4806
+ ref: z7.string(),
4807
+ name: z7.string().optional()
4714
4808
  },
4715
4809
  async ({ ref, name }) => {
4716
4810
  const result = await orch.startDev(ref, name);
@@ -4732,7 +4826,7 @@ async function startMcpServer() {
4732
4826
  server.tool(
4733
4827
  "list_run_scripts",
4734
4828
  "List named run scripts available for a thread",
4735
- { ref: z6.string() },
4829
+ { ref: z7.string() },
4736
4830
  async ({ ref }) => {
4737
4831
  const scripts = orch.listThreadRunScripts(ref);
4738
4832
  const active = orch.getActiveRuns(ref);
@@ -4750,8 +4844,8 @@ async function startMcpServer() {
4750
4844
  "stop_dev_script",
4751
4845
  "Stop a running script for a thread (all scripts if name omitted)",
4752
4846
  {
4753
- ref: z6.string(),
4754
- name: z6.string().optional()
4847
+ ref: z7.string(),
4848
+ name: z7.string().optional()
4755
4849
  },
4756
4850
  async ({ ref, name }) => {
4757
4851
  orch.stopDev(ref, name);
@@ -4761,7 +4855,7 @@ async function startMcpServer() {
4761
4855
  server.tool(
4762
4856
  "run_setup",
4763
4857
  "Re-run workspace setup (Sideboard/Conductor settings, .cursor/worktrees.json, or script/setup). New worktrees already run this automatically.",
4764
- { ref: z6.string() },
4858
+ { ref: z7.string() },
4765
4859
  async ({ ref }) => {
4766
4860
  const result = await orch.runSetup(ref);
4767
4861
  return {
@@ -4772,7 +4866,7 @@ async function startMcpServer() {
4772
4866
  server.tool(
4773
4867
  "add_workspace",
4774
4868
  "Register a git repo as a Sideboard workspace",
4775
- { repoPath: z6.string() },
4869
+ { repoPath: z7.string() },
4776
4870
  async ({ repoPath }) => {
4777
4871
  const ws = await orch.addWorkspace(repoPath);
4778
4872
  return { content: [{ type: "text", text: JSON.stringify(ws) }] };
@@ -4781,7 +4875,7 @@ async function startMcpServer() {
4781
4875
  server.tool(
4782
4876
  "remove_workspace",
4783
4877
  "Unregister a Sideboard workspace (does not archive threads)",
4784
- { repoPath: z6.string() },
4878
+ { repoPath: z7.string() },
4785
4879
  async ({ repoPath }) => {
4786
4880
  orch.removeWorkspace(repoPath);
4787
4881
  return { content: [{ type: "text", text: "ok" }] };
@@ -4791,12 +4885,12 @@ async function startMcpServer() {
4791
4885
  "fanout",
4792
4886
  "Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
4793
4887
  {
4794
- prompt: z6.string(),
4795
- agents: z6.array(z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
4796
- repoPath: z6.string(),
4797
- sourceType: z6.enum(["branch", "pr", "ticket"]).optional(),
4798
- sourceRef: z6.string().optional(),
4799
- title: z6.string().optional()
4888
+ prompt: z7.string(),
4889
+ agents: z7.array(z7.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
4890
+ repoPath: z7.string(),
4891
+ sourceType: z7.enum(["branch", "pr", "ticket"]).optional(),
4892
+ sourceRef: z7.string().optional(),
4893
+ title: z7.string().optional()
4800
4894
  },
4801
4895
  async (args) => {
4802
4896
  const threads = await orch.bestOfN(args);
@@ -4823,8 +4917,8 @@ async function startMcpServer() {
4823
4917
  "list_branches",
4824
4918
  "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).",
4825
4919
  {
4826
- repoPath: z6.string(),
4827
- unmergedOnly: z6.boolean().optional()
4920
+ repoPath: z7.string(),
4921
+ unmergedOnly: z7.boolean().optional()
4828
4922
  },
4829
4923
  async ({ repoPath, unmergedOnly }) => {
4830
4924
  const root = await resolveRepoRoot(repoPath);
@@ -4845,19 +4939,19 @@ async function startMcpServer() {
4845
4939
  "list_prs",
4846
4940
  '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.',
4847
4941
  {
4848
- repoPath: z6.string(),
4849
- query: z6.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
4850
- queue: z6.enum(["review", "mine", "approved", "changes"]).optional().describe(
4942
+ repoPath: z7.string(),
4943
+ query: z7.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
4944
+ queue: z7.enum(["review", "mine", "approved", "changes"]).optional().describe(
4851
4945
  'review = unclaimed eng-review inbox (use for "get me N tickets to review"). mine = review-requested:@me. approved / changes = eng-approved / eng-requested-changes.'
4852
4946
  ),
4853
- state: z6.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
4854
- label: z6.string().optional().describe(
4947
+ state: z7.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
4948
+ label: z7.string().optional().describe(
4855
4949
  "GitHub label / workflow tag. Comma-separated AND. Examples: eng-review, eng-approved, eng-requested-changes"
4856
4950
  ),
4857
- reviewer: z6.string().optional().describe(
4951
+ reviewer: z7.string().optional().describe(
4858
4952
  "me (review requested of you), unassigned (no individual reviewer; team queues like engineering-team still count), all, or a GitHub login"
4859
4953
  ),
4860
- limit: z6.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
4954
+ limit: z7.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
4861
4955
  },
4862
4956
  async ({ repoPath, query, queue, state, label, reviewer, limit }) => {
4863
4957
  const root = await resolveRepoRoot(repoPath);
@@ -4889,7 +4983,7 @@ async function startMcpServer() {
4889
4983
  server.tool(
4890
4984
  "get_pr_stack",
4891
4985
  "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).",
4892
- { ref: z6.string() },
4986
+ { ref: z7.string() },
4893
4987
  async ({ ref }) => {
4894
4988
  const stack = await orch.getPrStack(ref);
4895
4989
  return {
@@ -4901,8 +4995,8 @@ async function startMcpServer() {
4901
4995
  "open_pr_stack_layers",
4902
4996
  "Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
4903
4997
  {
4904
- ref: z6.string(),
4905
- layer: z6.number().int().positive().optional()
4998
+ ref: z7.string(),
4999
+ layer: z7.number().int().positive().optional()
4906
5000
  },
4907
5001
  async ({ ref, layer }) => {
4908
5002
  const result = await orch.openPrStackLayers(ref, { layer });
@@ -4936,9 +5030,9 @@ async function startMcpServer() {
4936
5030
  "add_stack_layer",
4937
5031
  "Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
4938
5032
  {
4939
- ref: z6.string(),
4940
- branchName: z6.string(),
4941
- title: z6.string().optional()
5033
+ ref: z7.string(),
5034
+ branchName: z7.string(),
5035
+ title: z7.string().optional()
4942
5036
  },
4943
5037
  async ({ ref, branchName, title }) => {
4944
5038
  const result = await orch.addStackLayer(ref, branchName, { title });
@@ -4967,11 +5061,11 @@ async function startMcpServer() {
4967
5061
  "create_pr_stack",
4968
5062
  "Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
4969
5063
  {
4970
- repoPath: z6.string(),
4971
- branches: z6.array(z6.string()).min(1),
4972
- agent: z6.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
4973
- base: z6.string().optional(),
4974
- title: z6.string().optional()
5064
+ repoPath: z7.string(),
5065
+ branches: z7.array(z7.string()).min(1),
5066
+ agent: z7.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
5067
+ base: z7.string().optional(),
5068
+ title: z7.string().optional()
4975
5069
  },
4976
5070
  async (args) => {
4977
5071
  const result = await orch.createPrStack({
@@ -5010,10 +5104,10 @@ async function startMcpServer() {
5010
5104
  "list_issues",
5011
5105
  "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.",
5012
5106
  {
5013
- repoPath: z6.string(),
5014
- query: z6.string().optional().describe("Search title, identifier, or description"),
5015
- assignee: z6.string().optional().describe("me (Linear default), unassigned, all, a user id, or a GitHub login"),
5016
- limit: z6.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
5107
+ repoPath: z7.string(),
5108
+ query: z7.string().optional().describe("Search title, identifier, or description"),
5109
+ assignee: z7.string().optional().describe("me (Linear default), unassigned, all, a user id, or a GitHub login"),
5110
+ limit: z7.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
5017
5111
  },
5018
5112
  async ({ repoPath, query, assignee, limit }) => {
5019
5113
  const root = await resolveRepoRoot(repoPath);
@@ -5145,8 +5239,8 @@ var BrightsySideboardApi = class {
5145
5239
  };
5146
5240
  function taskMessageText(task) {
5147
5241
  const parts = task.message?.parts ?? [];
5148
- const text6 = parts.find((p) => p.kind === "text")?.text ?? "";
5149
- return text6.trim();
5242
+ const text7 = parts.find((p) => p.kind === "text")?.text ?? "";
5243
+ return text7.trim();
5150
5244
  }
5151
5245
 
5152
5246
  // src/brightsy/cloud-connect.ts
@@ -5619,11 +5713,11 @@ async function startSlackOAuth(opts) {
5619
5713
  import { WebSocket as WsWebSocket } from "ws";
5620
5714
  var BACKOFF_START_MS = 1e3;
5621
5715
  var BACKOFF_MAX_MS = 3e4;
5622
- function stripSlackMentions(text6) {
5623
- return text6.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
5716
+ function stripSlackMentions(text7) {
5717
+ return text7.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
5624
5718
  }
5625
- function isSlackStopCommand(text6) {
5626
- const t = stripSlackMentions(text6).toLowerCase();
5719
+ function isSlackStopCommand(text7) {
5720
+ const t = stripSlackMentions(text7).toLowerCase();
5627
5721
  return t === "stop" || t === "sideboard_force_stop";
5628
5722
  }
5629
5723
  function parseSlackSocketFrame(raw) {
@@ -5645,8 +5739,8 @@ function inboundFromSocketFrame(frame) {
5645
5739
  const ts = event.ts?.trim();
5646
5740
  if (!channelId || !ts) return null;
5647
5741
  const rawText = event.text?.trim() ?? "";
5648
- const text6 = stripSlackMentions(rawText);
5649
- if (!text6) return null;
5742
+ const text7 = stripSlackMentions(rawText);
5743
+ if (!text7) return null;
5650
5744
  const teamId = (frame.payload?.team_id || event.team || "").trim();
5651
5745
  if (!teamId) return null;
5652
5746
  const isDm = event.type === "message" && (event.channel_type === "im" || event.channel_type === "mpim" || !event.channel_type && channelId.startsWith("D"));
@@ -5658,7 +5752,7 @@ function inboundFromSocketFrame(frame) {
5658
5752
  ts,
5659
5753
  threadTs: event.thread_ts?.trim() || void 0,
5660
5754
  userId: event.user?.trim(),
5661
- text: text6,
5755
+ text: text7,
5662
5756
  kind: isMention ? "mention" : "dm"
5663
5757
  };
5664
5758
  }
@@ -6198,12 +6292,12 @@ function formatSlackInboundPrompt(msg) {
6198
6292
 
6199
6293
  ${msg.text}`;
6200
6294
  }
6201
- function isSlackInboundUserPrompt(text6) {
6202
- return text6.startsWith("Slack DM\n") || text6.startsWith("Slack @mention\n");
6295
+ function isSlackInboundUserPrompt(text7) {
6296
+ return text7.startsWith("Slack DM\n") || text7.startsWith("Slack @mention\n");
6203
6297
  }
6204
- function formatSlackSignedReply(deviceLabel, text6) {
6298
+ function formatSlackSignedReply(deviceLabel, text7) {
6205
6299
  const label = deviceLabel.trim();
6206
- const body = text6.trim();
6300
+ const body = text7.trim();
6207
6301
  if (!body) return body;
6208
6302
  if (!label) return body;
6209
6303
  const head = `${label}:`;
@@ -6212,8 +6306,8 @@ function formatSlackSignedReply(deviceLabel, text6) {
6212
6306
  }
6213
6307
  return `${label}: ${body}`;
6214
6308
  }
6215
- function signForThisMac(text6) {
6216
- return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel, text6);
6309
+ function signForThisMac(text7) {
6310
+ return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel, text7);
6217
6311
  }
6218
6312
  function slackReplyThreadTs(msg) {
6219
6313
  if (msg.threadTs && msg.threadTs !== msg.ts) return msg.threadTs;
@@ -6274,9 +6368,9 @@ function replyStub(target) {
6274
6368
  kind: "dm"
6275
6369
  };
6276
6370
  }
6277
- async function postSlackText(target, text6, opts) {
6371
+ async function postSlackText(target, text7, opts) {
6278
6372
  if (opts.postReply) {
6279
- const result = await opts.postReply(replyStub(target), text6);
6373
+ const result = await opts.postReply(replyStub(target), text7);
6280
6374
  const ts2 = result && typeof result === "object" && typeof result.ts === "string" ? result.ts.trim() : "";
6281
6375
  return ts2 ? { ts: ts2 } : null;
6282
6376
  }
@@ -6286,7 +6380,7 @@ async function postSlackText(target, text6, opts) {
6286
6380
  "chat.postMessage",
6287
6381
  {
6288
6382
  channel: target.channelId,
6289
- text: text6,
6383
+ text: text7,
6290
6384
  thread_ts: target.threadTs
6291
6385
  },
6292
6386
  opts.fetchImpl
@@ -6294,9 +6388,9 @@ async function postSlackText(target, text6, opts) {
6294
6388
  const ts = json.ts?.trim();
6295
6389
  return ts ? { ts } : null;
6296
6390
  }
6297
- async function updateSlackText(target, ts, text6, opts) {
6391
+ async function updateSlackText(target, ts, text7, opts) {
6298
6392
  if (opts.updateReply) {
6299
- await opts.updateReply(replyStub(target), ts, text6);
6393
+ await opts.updateReply(replyStub(target), ts, text7);
6300
6394
  return;
6301
6395
  }
6302
6396
  const token = writeTokenForTeam(target.teamId);
@@ -6306,7 +6400,7 @@ async function updateSlackText(target, ts, text6, opts) {
6306
6400
  {
6307
6401
  channel: target.channelId,
6308
6402
  ts,
6309
- text: text6
6403
+ text: text7
6310
6404
  },
6311
6405
  opts.fetchImpl
6312
6406
  );
@@ -6334,8 +6428,8 @@ function replyTargetOf(msg) {
6334
6428
  threadTs: slackReplyThreadTs(msg)
6335
6429
  };
6336
6430
  }
6337
- async function postSlackReply(msg, text6, opts) {
6338
- await postSlackText(replyTargetOf(msg), signForThisMac(text6), opts);
6431
+ async function postSlackReply(msg, text7, opts) {
6432
+ await postSlackText(replyTargetOf(msg), signForThisMac(text7), opts);
6339
6433
  }
6340
6434
  function startSlackTurnProgress(msg, threadId, opts) {
6341
6435
  const log = opts.onLog ?? (() => void 0);
@@ -6418,10 +6512,10 @@ function startSlackTurnProgress(msg, threadId, opts) {
6418
6512
  }
6419
6513
  });
6420
6514
  },
6421
- finishWith: (text6) => {
6515
+ finishWith: (text7) => {
6422
6516
  stop();
6423
6517
  return run2(async () => {
6424
- const signed = signForThisMac(text6.trim());
6518
+ const signed = signForThisMac(text7.trim());
6425
6519
  if (!signed) {
6426
6520
  if (!postedTs) return;
6427
6521
  const ts = postedTs;
@@ -6449,11 +6543,11 @@ function startSlackTurnProgress(msg, threadId, opts) {
6449
6543
  };
6450
6544
  }
6451
6545
  var lastRelayed = /* @__PURE__ */ new Map();
6452
- function markRelayed(threadId, text6) {
6453
- lastRelayed.set(threadId, `${threadId}:${text6}`);
6546
+ function markRelayed(threadId, text7) {
6547
+ lastRelayed.set(threadId, `${threadId}:${text7}`);
6454
6548
  }
6455
- function alreadyRelayed(threadId, text6) {
6456
- return lastRelayed.get(threadId) === `${threadId}:${text6}`;
6549
+ function alreadyRelayed(threadId, text7) {
6550
+ return lastRelayed.get(threadId) === `${threadId}:${text7}`;
6457
6551
  }
6458
6552
  async function relayCoordinatorReplyToSlack(threadId, opts) {
6459
6553
  const target = getSlackReplyTarget(threadId);
@@ -6462,14 +6556,14 @@ async function relayCoordinatorReplyToSlack(threadId, opts) {
6462
6556
  const lastUser = thread ? [...thread.messages].reverse().find((m) => m.role === "user") : void 0;
6463
6557
  if (lastUser && isSlackInboundUserPrompt(lastUser.text)) return;
6464
6558
  const result = getOrchestrator().getTurnResult(threadId);
6465
- const text6 = result.text.trim();
6466
- if (!text6) return;
6467
- if (alreadyRelayed(threadId, text6)) return;
6468
- markRelayed(threadId, text6);
6559
+ const text7 = result.text.trim();
6560
+ if (!text7) return;
6561
+ if (alreadyRelayed(threadId, text7)) return;
6562
+ markRelayed(threadId, text7);
6469
6563
  const log = opts.onLog ?? (() => void 0);
6470
6564
  try {
6471
- await postSlackText(target, signForThisMac(text6), opts);
6472
- log(`replied ${target.threadTs ?? target.channelId} (${text6.length} chars)`);
6565
+ await postSlackText(target, signForThisMac(text7), opts);
6566
+ log(`replied ${target.threadTs ?? target.channelId} (${text7.length} chars)`);
6473
6567
  } catch (err) {
6474
6568
  lastRelayed.delete(threadId);
6475
6569
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -6717,10 +6811,10 @@ function resolveSlackListenMode(opts) {
6717
6811
  }
6718
6812
 
6719
6813
  // src/slack/relay-hub.ts
6720
- function parseSlackDeviceDestination(text6) {
6721
- const m = text6.match(/^\s*(?:to\s+)?(?:@|#)?([A-Za-z][\w-]{0,63})\s*[::]\s*/);
6722
- if (!m) return { label: null, rest: text6 };
6723
- return { label: m[1], rest: text6.slice(m[0].length) };
6814
+ function parseSlackDeviceDestination(text7) {
6815
+ const m = text7.match(/^\s*(?:to\s+)?(?:@|#)?([A-Za-z][\w-]{0,63})\s*[::]\s*/);
6816
+ if (!m) return { label: null, rest: text7 };
6817
+ return { label: m[1], rest: text7.slice(m[0].length) };
6724
6818
  }
6725
6819
  var SlackRelayHub = class {
6726
6820
  sessions = /* @__PURE__ */ new Map();
@@ -7242,10 +7336,6 @@ async function startSlackRelayServer(opts) {
7242
7336
  }
7243
7337
  export {
7244
7338
  ABLETIME_MCP_PATH,
7245
- ACCOUNT_ROLES,
7246
- ACCOUNT_ROLE_LABELS,
7247
- ACCOUNT_ROLE_MAX,
7248
- ACCOUNT_ROLE_PRESETS,
7249
7339
  AGENT_GIT_ACTIONS,
7250
7340
  AGENT_RUNNER_MAX_OLD_SPACE_MB,
7251
7341
  ATTACHMENTS_DIR,
@@ -7332,7 +7422,6 @@ export {
7332
7422
  WORKTREE_MCP_TOOLS,
7333
7423
  abletimeMcpRequest,
7334
7424
  abletimeMcpUrl,
7335
- accountRoleLabel,
7336
7425
  ackSlackInboundSeen,
7337
7426
  addBoardPin,
7338
7427
  addPrStackLayer,
@@ -7513,6 +7602,7 @@ export {
7513
7602
  fireSchedule,
7514
7603
  flattenTurnInput,
7515
7604
  flipLinearRelationType,
7605
+ foldLegacyRolesIntoNotes,
7516
7606
  followUpBehavior,
7517
7607
  forkChatTab,
7518
7608
  forkMessageSlice,
@@ -7553,6 +7643,8 @@ export {
7553
7643
  formatSlackWorkingText,
7554
7644
  formatTranscriptMarkdown,
7555
7645
  formatUiReminder,
7646
+ formatViewerContextDirective,
7647
+ formatViewerContextReminder,
7556
7648
  formatWorkspaceInventory,
7557
7649
  formatWorkspaceProfileSuffix,
7558
7650
  formatWorktreeDirective,
@@ -7790,7 +7882,6 @@ export {
7790
7882
  planQuestionsSignature,
7791
7883
  pollSlackOutboundWatches,
7792
7884
  posixShellSingleQuote,
7793
- preferTeamsForRole,
7794
7885
  preferredCursorCostCents,
7795
7886
  prepareTerminalCommand,
7796
7887
  previewLand,
@@ -7802,6 +7893,7 @@ export {
7802
7893
  readSetupLog,
7803
7894
  readSkillBody,
7804
7895
  readThread,
7896
+ readViewerContext,
7805
7897
  readWorktreeFile,
7806
7898
  readWorktreeFileForUpload,
7807
7899
  readWorktreeInclude,
@@ -7858,7 +7950,6 @@ export {
7858
7950
  resolveViewerProfile,
7859
7951
  resolveViewerProfileForRepo,
7860
7952
  resolveWorktreeStartPoint,
7861
- reviewTeamHintsForRoles,
7862
7953
  rewriteAbleTimeError,
7863
7954
  rewriteLinearError,
7864
7955
  run,
@@ -7992,5 +8083,6 @@ export {
7992
8083
  writeInjectedMcpConfig,
7993
8084
  writePlanFile,
7994
8085
  writeThread,
8086
+ writeViewerContext,
7995
8087
  writeWorktreeFile
7996
8088
  };