@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
@@ -1086,8 +1086,8 @@ ${input.permalink}` : "";
1086
1086
 
1087
1087
  ${body}${link}`;
1088
1088
  }
1089
- function isSlackExternalReplyPrompt(text6) {
1090
- return text6.startsWith("Slack reply from ") && text6.includes("not a command");
1089
+ function isSlackExternalReplyPrompt(text7) {
1090
+ return text7.startsWith("Slack reply from ") && text7.includes("not a command");
1091
1091
  }
1092
1092
  function pendingSlackExternalReplies(messages) {
1093
1093
  let i = messages.length - 1;
@@ -1128,9 +1128,9 @@ async function continueSourceThread(threadId, prompt) {
1128
1128
  function listSlackOutboundWatches() {
1129
1129
  return pruneWatches(readStore3());
1130
1130
  }
1131
- function formatOwnerSlackFyi(userName, text6) {
1131
+ function formatOwnerSlackFyi(userName, text7) {
1132
1132
  const who = userName.trim() || "Someone";
1133
- const body = text6.trim() || "(no text)";
1133
+ const body = text7.trim() || "(no text)";
1134
1134
  return `${who} replied in Slack:
1135
1135
  ${body}`;
1136
1136
  }
@@ -1145,7 +1145,7 @@ async function relayExternalReply(opts) {
1145
1145
  const thread = readThread(threadId);
1146
1146
  if (!thread || thread.status === "archived") return "ignored";
1147
1147
  try {
1148
- const text6 = formatSlackExternalReplyPrompt({
1148
+ const text7 = formatSlackExternalReplyPrompt({
1149
1149
  userName: opts.reply.userName,
1150
1150
  kind: opts.watch.kind,
1151
1151
  toLabel: opts.watch.toLabel,
@@ -1154,7 +1154,7 @@ async function relayExternalReply(opts) {
1154
1154
  });
1155
1155
  appendMessage(threadId, {
1156
1156
  role: "agent",
1157
- text: text6,
1157
+ text: text7,
1158
1158
  ts: (/* @__PURE__ */ new Date()).toISOString()
1159
1159
  });
1160
1160
  } catch {
@@ -1468,20 +1468,20 @@ function looksLikeMinifiedJsDump(line) {
1468
1468
  function looksLikeNestedElectronCrash(line) {
1469
1469
  return /HasCustomHostObject|ElectronInitializeICUandStartNode/i.test(line);
1470
1470
  }
1471
- function looksLikeNativeEventLoopCrash(text6) {
1472
- return /uv_run|uv__io_poll|SpinEventLoopInternal/i.test(text6);
1471
+ function looksLikeNativeEventLoopCrash(text7) {
1472
+ return /uv_run|uv__io_poll|SpinEventLoopInternal/i.test(text7);
1473
1473
  }
1474
- function looksLikeV8Oom(text6) {
1474
+ function looksLikeV8Oom(text7) {
1475
1475
  return /javascript heap|reached heap limit|OOMErrorHandler|FatalProcessOutOfMemory|Allocation failed - JavaScript heap/i.test(
1476
- text6
1476
+ text7
1477
1477
  );
1478
1478
  }
1479
- function looksLikeHomebrewLibuvCrash(text6) {
1480
- if (!looksLikeNativeEventLoopCrash(text6)) return false;
1481
- return /Cellar\/(?:libuv|node)|libuv\.\d+\.dylib/i.test(text6);
1479
+ function looksLikeHomebrewLibuvCrash(text7) {
1480
+ if (!looksLikeNativeEventLoopCrash(text7)) return false;
1481
+ return /Cellar\/(?:libuv|node)|libuv\.\d+\.dylib/i.test(text7);
1482
1482
  }
1483
- function clipStderr(text6, maxChars) {
1484
- const trimmed = text6.trim();
1483
+ function clipStderr(text7, maxChars) {
1484
+ const trimmed = text7.trim();
1485
1485
  if (trimmed.length <= maxChars) return trimmed;
1486
1486
  return trimmed.slice(0, maxChars);
1487
1487
  }
@@ -1515,16 +1515,16 @@ function summarizeTurnStderr(tail, maxChars = 500) {
1515
1515
  const joined = (useful.length ? useful : tail).slice(-6).join("\n").trim();
1516
1516
  return clipStderr(joined, maxChars);
1517
1517
  }
1518
- function looksLikeInvalidAgentSession(text6) {
1519
- const lower = text6.trim().toLowerCase();
1518
+ function looksLikeInvalidAgentSession(text7) {
1519
+ const lower = text7.trim().toLowerCase();
1520
1520
  if (!lower) return false;
1521
1521
  return /session not found/.test(lower) || /no conversation found/.test(lower) || /conversation .+ not found/.test(lower) || /thread .+ not found/.test(lower) || /unknown session/.test(lower) || /invalid session/.test(lower) || /session .+ (missing|expired|deleted|gone)/.test(lower) || /cannot resume/.test(lower) || /failed to (load|resume|open) session/.test(lower) || /corrupt local agent checkpoint/.test(lower) || /missing root blob/.test(lower) || /\bagent\b.{0,120}\bnot found\b/.test(lower) || /\brun\b.{0,80}\bnot found for agent\b/.test(lower);
1522
1522
  }
1523
- function looksLikeRetryableRunnerCrash(text6) {
1524
- if (looksLikeAgentFailureMessage(text6)) return false;
1525
- if (looksLikeInvalidAgentSession(text6)) return false;
1526
- if (looksLikeV8Oom(text6)) return false;
1527
- const lower = text6.trim().toLowerCase();
1523
+ function looksLikeRetryableRunnerCrash(text7) {
1524
+ if (looksLikeAgentFailureMessage(text7)) return false;
1525
+ if (looksLikeInvalidAgentSession(text7)) return false;
1526
+ if (looksLikeV8Oom(text7)) return false;
1527
+ const lower = text7.trim().toLowerCase();
1528
1528
  if (/cannot find (?:package|module)|err_module_not_found/.test(lower)) return false;
1529
1529
  if (!lower) return true;
1530
1530
  return /uv_run|spineventloopinternal|libuv|homebrew node \+ shared libuv|cursor runner crashed in node|hascustomhostobject|electroninitializeicuandstartnode|nested chromium|truncated crash dump|connection stalled|network request failed|cursor startup failed:.+\(retryable\)|sig(?:segv|abrt|ill)|segmentation fault|illegal instruction|fatal error/.test(
@@ -1536,12 +1536,12 @@ function shouldRetryFailedAgentTurn(detail, opts) {
1536
1536
  if (looksLikeV8Oom(detail) && opts.hasSession) return true;
1537
1537
  return looksLikeRetryableRunnerCrash(detail);
1538
1538
  }
1539
- function looksLikeAgentFailureMessage(text6) {
1540
- const lower = text6.trim().toLowerCase();
1539
+ function looksLikeAgentFailureMessage(text7) {
1540
+ const lower = text7.trim().toLowerCase();
1541
1541
  if (!lower) return false;
1542
1542
  return /you've hit your|hit your (session|weekly|opus) limit|usage limit/.test(lower) || /credit balance is too low|out of credits|insufficient.?quota|quota.?exceeded/.test(lower) || /invalid user api key|invalid api key|not logged in|not authenticated|unauthorized/.test(
1543
1543
  lower
1544
- ) || /\b429\b|too many requests|rate.?limit/.test(lower) || /prompt is too long|context.*(too long|exceed)|conversation too long/.test(lower) || /\[resource_exhausted\]|resource_exhausted/.test(lower) || /findFilesWithRipgrep/.test(text6);
1544
+ ) || /\b429\b|too many requests|rate.?limit/.test(lower) || /prompt is too long|context.*(too long|exceed)|conversation too long/.test(lower) || /\[resource_exhausted\]|resource_exhausted/.test(lower) || /findFilesWithRipgrep/.test(text7);
1545
1545
  }
1546
1546
  function fallbackTurnFailDetail(assistantText) {
1547
1547
  const t = assistantText.trim();
@@ -1822,8 +1822,8 @@ var init_github_agent_auth = __esm({
1822
1822
  });
1823
1823
 
1824
1824
  // src/git/stale-lock.ts
1825
- function isIndexLockError(text6) {
1826
- return /Unable to create ['"][^'"]*index\.lock['"]: File exists/i.test(text6);
1825
+ function isIndexLockError(text7) {
1826
+ return /Unable to create ['"][^'"]*index\.lock['"]: File exists/i.test(text7);
1827
1827
  }
1828
1828
  function clearStaleIndexLock(gitDir, maxAgeMs = STALE_INDEX_LOCK_MS, now = Date.now()) {
1829
1829
  const lockPath = (0, import_node_path13.join)(gitDir, "index.lock");
@@ -2802,12 +2802,12 @@ var init_worktree_labels = __esm({
2802
2802
  });
2803
2803
 
2804
2804
  // src/git/gh-errors.ts
2805
- function isGhRateLimitError(text6) {
2806
- return /API rate limit (already )?exceeded/i.test(text6) || /rate limit exceeded/i.test(text6);
2805
+ function isGhRateLimitError(text7) {
2806
+ return /API rate limit (already )?exceeded/i.test(text7) || /rate limit exceeded/i.test(text7);
2807
2807
  }
2808
- function isGhPrBodyTooLongError(text6) {
2808
+ function isGhPrBodyTooLongError(text7) {
2809
2809
  return /body is too long|body.{0,20}too long|maximum is 65536|exceeds the maximum allowed size|request (entity |is )?too large|payload too large|413 Request Entity Too Large/i.test(
2810
- text6
2810
+ text7
2811
2811
  );
2812
2812
  }
2813
2813
  function clampGithubPrBody(body, max = GITHUB_PR_BODY_SAFE_CHARS) {
@@ -2827,21 +2827,21 @@ function formatRateLimitResetHint(resetEpochSec, nowMs = Date.now()) {
2827
2827
  const hours = Math.ceil(mins / 60);
2828
2828
  return `in about ${hours} hour${hours === 1 ? "" : "s"}`;
2829
2829
  }
2830
- function omitGhBodyArg(text6) {
2831
- return text6.replace(
2830
+ function omitGhBodyArg(text7) {
2831
+ return text7.replace(
2832
2832
  /--body(?:-file)?(\s+|=)(?:'[^']*'|"[^"]*"|\S+)/g,
2833
2833
  "--body <omitted>"
2834
2834
  );
2835
2835
  }
2836
- function capGhErrorText(text6, max = 400) {
2837
- const t = text6.trim();
2836
+ function capGhErrorText(text7, max = 400) {
2837
+ const t = text7.trim();
2838
2838
  if (t.length <= max) return t;
2839
2839
  return `${t.slice(0, max - 1).trimEnd()}\u2026`;
2840
2840
  }
2841
- function extractGhErrorDetail(text6) {
2842
- const trimmed = omitGhBodyArg(text6.trim());
2841
+ function extractGhErrorDetail(text7) {
2842
+ const trimmed = omitGhBodyArg(text7.trim());
2843
2843
  if (!trimmed) return "";
2844
- if (isGhPrBodyTooLongError(text6) || isGhPrBodyTooLongError(trimmed)) {
2844
+ if (isGhPrBodyTooLongError(text7) || isGhPrBodyTooLongError(trimmed)) {
2845
2845
  return "GraphQL: body is too long (GitHub PR body / request size limit).";
2846
2846
  }
2847
2847
  const graphql = trimmed.match(/\bGraphQL:\s*(.+)$/im);
@@ -2881,9 +2881,9 @@ function formatGhLandError(raw, opts) {
2881
2881
  }
2882
2882
  return detail || "Failed to create or update pull request";
2883
2883
  }
2884
- function isPrNotMergeableError(text6) {
2884
+ function isPrNotMergeableError(text7) {
2885
2885
  return /not mergeable|cannot be cleanly created|cannot merge cleanly|Merge conflict|\bCONFLICTING\b|must be (updated|rebased)|branch is out of date|needs? to be (updated|rebased)|Resolve conflicts or update the branch/i.test(
2886
- text6
2886
+ text7
2887
2887
  );
2888
2888
  }
2889
2889
  function formatMergePrError(raw) {
@@ -2971,27 +2971,27 @@ var init_worktree_exclude = __esm({
2971
2971
  });
2972
2972
 
2973
2973
  // src/store/account-profile.ts
2974
- function accountRoleLabel(role) {
2975
- const known = ACCOUNT_ROLE_LABELS[role];
2974
+ function legacyRoleLabel(role) {
2975
+ const known = LEGACY_ROLE_LABELS[role];
2976
2976
  if (known) return known;
2977
2977
  return role.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
2978
2978
  }
2979
- function normalizeAccountRole(value) {
2979
+ function normalizeLegacyRole(value) {
2980
2980
  const key = (value ?? "").trim().toLowerCase().replace(/[_\s]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
2981
2981
  if (key === "both") return void 0;
2982
2982
  if (key.length < 2 || key.length > 40) return void 0;
2983
2983
  if (!/^[a-z][a-z0-9-]*$/.test(key)) return void 0;
2984
2984
  return key;
2985
2985
  }
2986
- function normalizeAccountRoles(roles, legacyRole) {
2986
+ function legacyRoleLabels(roles, legacyRole) {
2987
2987
  const raw = Array.isArray(roles) ? roles : [];
2988
2988
  const out = [];
2989
2989
  const seen = /* @__PURE__ */ new Set();
2990
2990
  const push = (value) => {
2991
- const role = normalizeAccountRole(typeof value === "string" ? value : "");
2992
- if (!role || seen.has(role) || out.length >= ACCOUNT_ROLE_MAX) return;
2991
+ const role = normalizeLegacyRole(typeof value === "string" ? value : "");
2992
+ if (!role || seen.has(role) || out.length >= 16) return;
2993
2993
  seen.add(role);
2994
- out.push(role);
2994
+ out.push(legacyRoleLabel(role));
2995
2995
  };
2996
2996
  for (const item of raw) push(item);
2997
2997
  if (out.length === 0) push(legacyRole);
@@ -3001,117 +3001,83 @@ function normalizeProfileNotes(value) {
3001
3001
  if (typeof value !== "string") return "";
3002
3002
  return value.replace(/\r\n/g, "\n").trim().slice(0, PROFILE_NOTES_MAX);
3003
3003
  }
3004
- function reviewTeamHintsForRoles(roles) {
3005
- const seen = /* @__PURE__ */ new Set();
3006
- const out = [];
3007
- for (const role of roles) {
3008
- const hints = ROLE_TEAM_HINTS[role] ?? teamHintsForCustomRole(role);
3009
- for (const hint of hints) {
3010
- const key = hint.toLowerCase();
3011
- if (seen.has(key)) continue;
3012
- seen.add(key);
3013
- out.push(hint);
3014
- }
3015
- }
3016
- return out;
3017
- }
3018
- function teamHintsForCustomRole(role) {
3019
- const slug = role.trim().toLowerCase();
3020
- if (!slug) return [];
3021
- if (slug.endsWith("-team") || slug.endsWith("-squad")) return [slug];
3022
- return [`${slug}-team`, slug];
3004
+ function foldLegacyRolesIntoNotes(roles, legacyRole, notes) {
3005
+ const text7 = normalizeProfileNotes(notes);
3006
+ const labels = legacyRoleLabels(roles, legacyRole);
3007
+ if (!labels.length) return text7;
3008
+ if (text7.toLowerCase().startsWith("roles:")) return text7;
3009
+ const prefix = `Roles: ${labels.join(", ")}`;
3010
+ return text7 ? `${prefix}. ${text7}` : prefix;
3023
3011
  }
3024
3012
  function resolveAccountProfile(input) {
3025
3013
  return resolveViewerProfile(input);
3026
3014
  }
3027
3015
  function resolveViewerProfile(account, project) {
3028
- const accountRoles = normalizeAccountRoles(account?.roles, account?.role);
3029
- const projectRoles = normalizeAccountRoles(project?.roles, project?.role);
3030
- const rolesFromProject = projectRoles.length > 0;
3031
- const roles = rolesFromProject ? projectRoles : accountRoles;
3032
- const accountNotes = normalizeProfileNotes(account?.notes);
3033
- const projectNotes = normalizeProfileNotes(project?.notes);
3034
- const notes = [accountNotes, projectNotes].filter(Boolean).join("\n");
3016
+ const accountNotes = foldLegacyRolesIntoNotes(
3017
+ account?.roles,
3018
+ account?.role,
3019
+ account?.notes
3020
+ );
3021
+ const projectNotes = foldLegacyRolesIntoNotes(
3022
+ project?.roles,
3023
+ project?.role,
3024
+ project?.notes
3025
+ );
3035
3026
  return {
3036
- roles,
3037
- roleLabels: roles.map((role) => accountRoleLabel(role)),
3038
- reviewTeamHints: reviewTeamHintsForRoles(roles),
3039
- notes,
3027
+ notes: [accountNotes, projectNotes].filter(Boolean).join("\n"),
3040
3028
  accountNotes,
3041
- projectNotes,
3042
- rolesFromProject
3029
+ projectNotes
3043
3030
  };
3044
3031
  }
3045
- function preferTeamsForRole(viewerTeams, hints) {
3046
- const teams = viewerTeams.map((t) => t.trim()).filter(Boolean);
3047
- const want = hints.map((t) => t.trim()).filter(Boolean);
3048
- if (!want.length) return teams;
3049
- if (!teams.length) return want;
3050
- const hintKeys = want.map((t) => t.toLowerCase());
3051
- const matched = teams.filter((team) => {
3052
- const key = team.toLowerCase();
3053
- return hintKeys.some((hint) => key === hint || key.includes(hint) || hint.includes(key));
3054
- });
3055
- return matched.length ? matched : teams;
3056
- }
3057
3032
  function compactNotes(notes, max = 160) {
3058
3033
  const oneLine = notes.replace(/\s+/g, " ").trim();
3059
3034
  if (oneLine.length <= max) return oneLine;
3060
3035
  return `${oneLine.slice(0, max - 1).trimEnd()}\u2026`;
3061
3036
  }
3062
3037
  function formatAccountProfilePlaybookLine(profile) {
3063
- if (profile.roleLabels.length === 0 && !profile.notes) return "";
3064
- const parts = [];
3065
- if (profile.roleLabels.length) {
3066
- parts.push(
3067
- `roles ${profile.roleLabels.join(", ")} (tickets to work on / PRs to review; prefer those teams)`
3068
- );
3069
- }
3070
- if (profile.notes) {
3071
- parts.push(`notes: ${compactNotes(profile.notes)}`);
3072
- }
3073
- return `- Viewer profile: ${parts.join(". ")}. A team review request is not a claim \u2014 only an individual reviewer is. When they ask to find work, use these notes with list_issues (tickets) and list_prs(queue=review) (reviews) and show the options. Only create_thread and start when they also asked to start.`;
3038
+ if (!profile.accountNotes) return "";
3039
+ return `- Account context (Settings \u2192 Agents): ${compactNotes(profile.accountNotes)}. When they ask to find work, use this with list_issues (tickets) and list_prs(queue=review) (reviews) and show the options. Only create_thread and start when they also asked to start. To change it, show the proposed text, ask_user (Save this context / Do not save), wait, then update_viewer_context(scope=account, confirmed=true).`;
3074
3040
  }
3075
3041
  function formatProjectProfilePlaybookLines(projects) {
3076
3042
  const lines = projects.map((project) => {
3077
- const roles = (project.roleLabels ?? []).filter(Boolean);
3078
3043
  const notes = normalizeProfileNotes(project.notes);
3079
- if (!roles.length && !notes) return "";
3080
- const bits = [];
3081
- if (roles.length) bits.push(`roles ${roles.join(", ")}`);
3082
- if (notes) bits.push(compactNotes(notes, 120));
3083
- return `- ${project.name}: ${bits.join(". ")}`;
3044
+ if (!notes) return "";
3045
+ return `- ${project.name}: ${compactNotes(notes, 120)}`;
3084
3046
  }).filter(Boolean);
3085
3047
  if (!lines.length) return "";
3086
- return ["Project overrides (Settings \u2192 Projects; empty roles inherit account):", ...lines].join(
3087
- "\n"
3088
- );
3048
+ return [
3049
+ "Project context (Settings \u2192 Projects; adds to account context):",
3050
+ ...lines
3051
+ ].join("\n");
3089
3052
  }
3090
3053
  function formatWorkspaceProfileSuffix(profile) {
3091
- const bits = [];
3092
- if (profile.roles.length) bits.push(`roles:${profile.roles.join(",")}`);
3093
- if (profile.projectNotes) bits.push(`notes:${compactNotes(profile.projectNotes, 80)}`);
3094
- return bits.length ? ` ${bits.join(" ")}` : "";
3054
+ if (!profile.projectNotes) return "";
3055
+ return ` context:${compactNotes(profile.projectNotes, 80)}`;
3056
+ }
3057
+ function formatViewerContextDirective(profile) {
3058
+ const account = profile.accountNotes || "(empty)";
3059
+ const project = profile.projectNotes || "(empty \u2014 account context applies)";
3060
+ return [
3061
+ "Viewer context (Settings \u2192 Agents / Projects) \u2014 how to find this user\u2019s tickets and review PRs:",
3062
+ `- Account: ${compactNotes(profile.accountNotes || account, 240)}`,
3063
+ `- This project: ${compactNotes(profile.projectNotes || project, 240)}`,
3064
+ "To change these, show the proposed text in chat, call ask_user (Save this context / Do not save), wait for the answer, then update_viewer_context with confirmed=true. Never write context without confirmation. get_viewer_context reads the current values."
3065
+ ].join("\n");
3066
+ }
3067
+ function formatViewerContextReminder() {
3068
+ return "Viewer context: get_viewer_context to read; update_viewer_context only after ask_user confirms (Save this context / Do not save).";
3095
3069
  }
3096
- var ACCOUNT_ROLE_PRESETS, ACCOUNT_ROLES, ACCOUNT_ROLE_MAX, PROFILE_NOTES_MAX, ACCOUNT_ROLE_LABELS, ROLE_TEAM_HINTS;
3070
+ var PROFILE_NOTES_MAX, LEGACY_ROLE_LABELS;
3097
3071
  var init_account_profile = __esm({
3098
3072
  "src/store/account-profile.ts"() {
3099
3073
  "use strict";
3100
- ACCOUNT_ROLE_PRESETS = ["engineering", "design", "product"];
3101
- ACCOUNT_ROLES = ACCOUNT_ROLE_PRESETS;
3102
- ACCOUNT_ROLE_MAX = 16;
3103
3074
  PROFILE_NOTES_MAX = 2e3;
3104
- ACCOUNT_ROLE_LABELS = {
3075
+ LEGACY_ROLE_LABELS = {
3105
3076
  engineering: "Engineering",
3106
3077
  design: "Design",
3107
3078
  product: "Product",
3108
3079
  qa: "QA"
3109
3080
  };
3110
- ROLE_TEAM_HINTS = {
3111
- engineering: ["engineering-team", "engineering", "eng-team"],
3112
- design: ["design-team", "design"],
3113
- product: ["product-team", "product"]
3114
- };
3115
3081
  }
3116
3082
  });
3117
3083
 
@@ -3209,16 +3175,11 @@ var init_issue_source_labels = __esm({
3209
3175
  // src/store/app-settings.ts
3210
3176
  var app_settings_exports = {};
3211
3177
  __export(app_settings_exports, {
3212
- ACCOUNT_ROLES: () => ACCOUNT_ROLES,
3213
- ACCOUNT_ROLE_LABELS: () => ACCOUNT_ROLE_LABELS,
3214
- ACCOUNT_ROLE_MAX: () => ACCOUNT_ROLE_MAX,
3215
- ACCOUNT_ROLE_PRESETS: () => ACCOUNT_ROLE_PRESETS,
3216
3178
  FOLLOW_UP_BEHAVIORS: () => FOLLOW_UP_BEHAVIORS,
3217
3179
  GITHUB_GIT_AUTH_MODES: () => GITHUB_GIT_AUTH_MODES,
3218
3180
  HARNESS_ENV_KEYS: () => HARNESS_ENV_KEYS,
3219
3181
  ISSUE_SOURCE_LABELS: () => ISSUE_SOURCE_LABELS,
3220
3182
  PROFILE_NOTES_MAX: () => PROFILE_NOTES_MAX,
3221
- accountRoleLabel: () => accountRoleLabel,
3222
3183
  appSettingsPath: () => appSettingsPath,
3223
3184
  applyAppEnvironment: () => applyAppEnvironment,
3224
3185
  autoArchiveOnMergeEnabled: () => autoArchiveOnMergeEnabled,
@@ -3242,9 +3203,12 @@ __export(app_settings_exports, {
3242
3203
  emptyPublicIntegrations: () => emptyPublicIntegrations,
3243
3204
  ensureSlackDeviceIdentity: () => ensureSlackDeviceIdentity,
3244
3205
  findProjectProfileKey: () => findProjectProfileKey,
3206
+ foldLegacyRolesIntoNotes: () => foldLegacyRolesIntoNotes,
3245
3207
  followUpBehavior: () => followUpBehavior,
3246
3208
  formatAccountProfilePlaybookLine: () => formatAccountProfilePlaybookLine,
3247
3209
  formatProjectProfilePlaybookLines: () => formatProjectProfilePlaybookLines,
3210
+ formatViewerContextDirective: () => formatViewerContextDirective,
3211
+ formatViewerContextReminder: () => formatViewerContextReminder,
3248
3212
  formatWorkspaceProfileSuffix: () => formatWorkspaceProfileSuffix,
3249
3213
  getAbleTimeAccessToken: () => getAbleTimeAccessToken,
3250
3214
  getAbleTimeHost: () => getAbleTimeHost,
@@ -3265,7 +3229,7 @@ __export(app_settings_exports, {
3265
3229
  maxConcurrentAgents: () => maxConcurrentAgents,
3266
3230
  orchestrationQuotaFallbackAgent: () => orchestrationQuotaFallbackAgent,
3267
3231
  orchestrationQuotaOnLimit: () => orchestrationQuotaOnLimit,
3268
- preferTeamsForRole: () => preferTeamsForRole,
3232
+ readViewerContext: () => readViewerContext,
3269
3233
  resolveAccountProfile: () => resolveAccountProfile,
3270
3234
  resolveAccountProfileFromSettings: () => resolveAccountProfileFromSettings,
3271
3235
  resolveAgentExecutable: () => resolveAgentExecutable,
@@ -3275,7 +3239,6 @@ __export(app_settings_exports, {
3275
3239
  resolveThreadDefaults: () => resolveThreadDefaults,
3276
3240
  resolveViewerProfile: () => resolveViewerProfile,
3277
3241
  resolveViewerProfileForRepo: () => resolveViewerProfileForRepo,
3278
- reviewTeamHintsForRoles: () => reviewTeamHintsForRoles,
3279
3242
  saveAbleTimeConnection: () => saveAbleTimeConnection,
3280
3243
  saveAppSettings: () => saveAppSettings,
3281
3244
  saveLinearOAuth: () => saveLinearOAuth,
@@ -3292,7 +3255,8 @@ __export(app_settings_exports, {
3292
3255
  updateDefaultsSettings: () => updateDefaultsSettings,
3293
3256
  updateIntegrationsSettings: () => updateIntegrationsSettings,
3294
3257
  updateOpencodeSettings: () => updateOpencodeSettings,
3295
- updateProjectProfileSettings: () => updateProjectProfileSettings
3258
+ updateProjectProfileSettings: () => updateProjectProfileSettings,
3259
+ writeViewerContext: () => writeViewerContext
3296
3260
  });
3297
3261
  function emptyPublicIntegrations() {
3298
3262
  return {
@@ -3550,9 +3514,7 @@ function normalizeDefaults(raw) {
3550
3514
  if (typeof source.fast === "boolean") {
3551
3515
  out.fast = source.fast;
3552
3516
  }
3553
- const roles = normalizeAccountRoles(source.roles, source.role);
3554
- if (roles.length) out.roles = roles;
3555
- const notes = normalizeProfileNotes(source.notes);
3517
+ const notes = foldLegacyRolesIntoNotes(source.roles, source.role, source.notes);
3556
3518
  if (notes) out.notes = notes;
3557
3519
  return out;
3558
3520
  }
@@ -3563,11 +3525,9 @@ function normalizeProjectProfile(raw) {
3563
3525
  if (!raw || typeof raw !== "object") return void 0;
3564
3526
  const source = raw;
3565
3527
  const out = {};
3566
- const roles = normalizeAccountRoles(source.roles, source.role);
3567
- if (roles.length) out.roles = roles;
3568
- const notes = normalizeProfileNotes(source.notes);
3528
+ const notes = foldLegacyRolesIntoNotes(source.roles, source.role, source.notes);
3569
3529
  if (notes) out.notes = notes;
3570
- return out.roles || out.notes ? out : void 0;
3530
+ return out.notes ? out : void 0;
3571
3531
  }
3572
3532
  function normalizeProjects(raw) {
3573
3533
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
@@ -4126,17 +4086,13 @@ function updateDefaultsSettings(patch) {
4126
4086
  defaults.fast = Boolean(patch.fast);
4127
4087
  }
4128
4088
  }
4129
- if ("roles" in patch) {
4130
- const roles = normalizeAccountRoles(patch.roles);
4131
- if (!roles.length) delete defaults.roles;
4132
- else defaults.roles = roles;
4133
- delete defaults.role;
4134
- }
4135
4089
  if ("notes" in patch) {
4136
4090
  const notes = normalizeProfileNotes(patch.notes);
4137
4091
  if (!notes) delete defaults.notes;
4138
4092
  else defaults.notes = notes;
4139
4093
  }
4094
+ delete defaults.roles;
4095
+ delete defaults.role;
4140
4096
  return saveAppSettings({ ...current, defaults });
4141
4097
  }
4142
4098
  function updateProjectProfileSettings(repoPath, patch) {
@@ -4146,17 +4102,14 @@ function updateProjectProfileSettings(repoPath, patch) {
4146
4102
  const projects = { ...current.projects };
4147
4103
  const existing = projects[key] ?? {};
4148
4104
  const next = { ...existing };
4149
- if ("roles" in patch) {
4150
- const roles = normalizeAccountRoles(patch.roles);
4151
- if (!roles.length) delete next.roles;
4152
- else next.roles = roles;
4153
- }
4154
4105
  if ("notes" in patch) {
4155
4106
  const notes = normalizeProfileNotes(patch.notes);
4156
4107
  if (!notes) delete next.notes;
4157
4108
  else next.notes = notes;
4158
4109
  }
4159
- if (!next.roles?.length && !next.notes) delete projects[key];
4110
+ delete next.roles;
4111
+ delete next.role;
4112
+ if (!next.notes) delete projects[key];
4160
4113
  else projects[key] = next;
4161
4114
  return saveAppSettings({ ...current, projects });
4162
4115
  }
@@ -4181,6 +4134,60 @@ function resolveViewerProfileForRepo(repoPath, settings = loadAppSettings()) {
4181
4134
  const project = key ? settings.projects[key] : void 0;
4182
4135
  return resolveViewerProfile(settings.defaults, project);
4183
4136
  }
4137
+ function readViewerContext(repoPath) {
4138
+ const settings = loadAppSettings();
4139
+ const key = findProjectProfileKey(settings.projects, repoPath);
4140
+ const resolved = resolveViewerProfile(
4141
+ settings.defaults,
4142
+ key ? settings.projects[key] : void 0
4143
+ );
4144
+ return {
4145
+ account: resolved.accountNotes,
4146
+ project: resolved.projectNotes,
4147
+ combined: resolved.notes,
4148
+ repoPath: key ?? (repoPath?.trim() ? profileRepoKey(repoPath) : null)
4149
+ };
4150
+ }
4151
+ function writeViewerContext(input) {
4152
+ const proposed = normalizeProfileNotes(input.context);
4153
+ if (input.scope === "project") {
4154
+ const settings = loadAppSettings();
4155
+ const key = findProjectProfileKey(settings.projects, input.repoPath) ?? profileRepoKey(input.repoPath ?? "");
4156
+ if (!key) {
4157
+ return {
4158
+ ok: false,
4159
+ error: "repoPath required",
4160
+ message: "Pass repoPath (or run from a registered worktree) when updating project context.",
4161
+ current: "",
4162
+ proposed
4163
+ };
4164
+ }
4165
+ const current2 = resolveViewerProfileForRepo(key, settings).projectNotes;
4166
+ if (!input.confirmed) {
4167
+ return {
4168
+ ok: false,
4169
+ error: "Confirmation required",
4170
+ message: CONFIRM_PROJECT,
4171
+ current: current2,
4172
+ proposed
4173
+ };
4174
+ }
4175
+ updateProjectProfileSettings(key, { notes: proposed || null });
4176
+ return { ok: true, scope: "project", context: proposed, repoPath: key };
4177
+ }
4178
+ const current = resolveAccountProfileFromSettings().accountNotes;
4179
+ if (!input.confirmed) {
4180
+ return {
4181
+ ok: false,
4182
+ error: "Confirmation required",
4183
+ message: CONFIRM_ACCOUNT,
4184
+ current,
4185
+ proposed
4186
+ };
4187
+ }
4188
+ updateDefaultsSettings({ notes: proposed || null });
4189
+ return { ok: true, scope: "account", context: proposed };
4190
+ }
4184
4191
  function resolveThreadDefaults(settings = loadAppSettings()) {
4185
4192
  return {
4186
4193
  agent: getDefaultAgent(settings),
@@ -4527,7 +4534,7 @@ function childEnvWithAppSettings(extra) {
4527
4534
  function harnessEnvKey(harness) {
4528
4535
  return HARNESS_ENV_KEYS[harness];
4529
4536
  }
4530
- var import_node_crypto3, import_node_fs14, import_node_os5, import_node_path16, HARNESS_ENV_KEYS, DEFAULT_AGENTS, GITHUB_GIT_AUTH_MODES, FOLLOW_UP_BEHAVIORS, CLOUD_CONNECT_AGENTS, ISSUE_SOURCES, GIT_AUTH_MODES, EMPTY_SETTINGS, DEFAULT_CLI_BIN;
4537
+ var import_node_crypto3, import_node_fs14, import_node_os5, import_node_path16, HARNESS_ENV_KEYS, DEFAULT_AGENTS, GITHUB_GIT_AUTH_MODES, FOLLOW_UP_BEHAVIORS, CLOUD_CONNECT_AGENTS, ISSUE_SOURCES, GIT_AUTH_MODES, EMPTY_SETTINGS, CONFIRM_ACCOUNT, CONFIRM_PROJECT, DEFAULT_CLI_BIN;
4531
4538
  var init_app_settings = __esm({
4532
4539
  "src/store/app-settings.ts"() {
4533
4540
  "use strict";
@@ -4578,6 +4585,8 @@ var init_app_settings = __esm({
4578
4585
  projects: {},
4579
4586
  advanced: {}
4580
4587
  };
4588
+ CONFIRM_ACCOUNT = "Show the user the proposed account context, call ask_user (Save this context / Do not save), wait for their answer, then retry with confirmed=true.";
4589
+ CONFIRM_PROJECT = "Show the user the proposed project context, call ask_user (Save this context / Do not save), wait for their answer, then retry with confirmed=true.";
4581
4590
  DEFAULT_CLI_BIN = {
4582
4591
  claude: "claude",
4583
4592
  codex: "codex",
@@ -5787,14 +5796,7 @@ async function listPrs(repoPath, opts) {
5787
5796
  const limit = resolved.limit ?? 200;
5788
5797
  const reviewerKey = (resolved.reviewer ?? "").trim().toLowerCase();
5789
5798
  const needsUnclaimed = reviewerKey === "unassigned" || reviewerKey === "none" || reviewerKey === "null";
5790
- let profile;
5791
- try {
5792
- profile = resolveViewerProfileForRepo(repoPath);
5793
- } catch {
5794
- profile = resolveViewerProfile();
5795
- }
5796
- const fetchedTeams = needsUnclaimed ? await listGithubViewerTeamSlugs(repoPath).catch(() => []) : [];
5797
- const viewerTeams = resolved.viewerTeams ?? (needsUnclaimed ? preferTeamsForRole(fetchedTeams, profile.reviewTeamHints) : void 0);
5799
+ const viewerTeams = resolved.viewerTeams ?? (needsUnclaimed ? await listGithubViewerTeamSlugs(repoPath).catch(() => []) : void 0);
5798
5800
  const fetchLimit = prListFetchLimit(limit, resolved.reviewer);
5799
5801
  const args = buildGhPrListArgs({
5800
5802
  slug,
@@ -7419,16 +7421,15 @@ function accountRolePlaybookLine() {
7419
7421
  return "";
7420
7422
  }
7421
7423
  }
7422
- function accountRoleReminderLine() {
7424
+ function accountContextReminderLine() {
7423
7425
  let profile;
7424
7426
  try {
7425
7427
  profile = resolveAccountProfileFromSettings();
7426
7428
  } catch {
7427
7429
  return "";
7428
7430
  }
7429
- if (profile.roleLabels.length === 0 && !profile.notes) return "";
7430
- const roles = profile.roleLabels.length ? profile.roleLabels.join(", ") : "no roles";
7431
- return `- Viewer profile: ${roles}. Use Settings notes with list_issues / list_prs when they ask to find work.`;
7431
+ if (!profile.accountNotes) return "";
7432
+ return "- Account context is set. Use get_viewer_context / Settings notes with list_issues / list_prs when they ask to find work. Update only after ask_user confirms.";
7432
7433
  }
7433
7434
  function projectProfilePlaybookBlock() {
7434
7435
  let settings;
@@ -7445,7 +7446,6 @@ function projectProfilePlaybookBlock() {
7445
7446
  const key = path.replace(/\/+$/, "");
7446
7447
  return {
7447
7448
  name: names.get(key) ?? key.split("/").filter(Boolean).pop() ?? path,
7448
- roleLabels: resolved.rolesFromProject ? resolved.roleLabels : [],
7449
7449
  notes: resolved.projectNotes
7450
7450
  };
7451
7451
  });
@@ -7459,7 +7459,7 @@ function coordinatorTurnReminder(opts) {
7459
7459
  `- YOUR orchestration thread id is ${opts.parentId} \u2014 pass parentThreadId="${opts.parentId}" on create_thread, or omit it.`,
7460
7460
  goal ? `- Goal / title: ${goal}` : null,
7461
7461
  accountDefaultsPlaybookLine(),
7462
- accountRoleReminderLine() || null,
7462
+ accountContextReminderLine() || null,
7463
7463
  "- Status: list_board (worktree Kanban: New \u2192 Draft \u2192 Review \u2192 Merged) or list_threads. Link chats as `[Title](sideboard://thread/<id>)`. Merge only if the user asked. If a child is stopped/error/broken, it did not finish \u2014 resume or tell the user."
7464
7464
  ].filter(Boolean).join("\n");
7465
7465
  }
@@ -7513,7 +7513,7 @@ function ensureGlobalCoordinatorCwd(opts) {
7513
7513
  "Never pass `agent=codex` when you yourself are Codex \u2014 nested Codex deadlocks on shared ~/.codex locks. Omit agent (Account default) or use cursor/claude.",
7514
7514
  "Typical flow (Home board): list_board \u2192 create_thread (sourceType=ticket|pr|branch) \u2192 send_to_thread \u2192 wait_for_turn (loop while stillRunning) \u2192 ask_git create-draft \u2192 wait_for_turn. Merge only if the user explicitly asked (`ask_git` merge).",
7515
7515
  "Typical flow (branch / explicit source): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn (loop while stillRunning) \u2192 ask_git create-draft.",
7516
- "Typical flow (find work): list_workspaces \u2192 pick repo(s) matching viewer roles/notes \u2192 Sideboard list_issues / linear_* (tickets) or list_prs(queue=review) (reviews) \u2192 show the options. Never wait on Claude Linear MCP. Only create_thread + send_to_thread when they asked to start (e.g. \u201Cfind me work and start it\u201D) \u2014 then wait_for_turn (loop while stillRunning) \u2192 ask_git create-draft.",
7516
+ "Typical flow (find work): list_workspaces \u2192 pick repo(s) matching account / project context \u2192 Sideboard list_issues / linear_* (tickets) or list_prs(queue=review) (reviews) \u2192 show the options. Never wait on Claude Linear MCP. Only create_thread + send_to_thread when they asked to start (e.g. \u201Cfind me work and start it\u201D) \u2014 then wait_for_turn (loop while stillRunning) \u2192 ask_git create-draft.",
7517
7517
  'Typical flow (review inbox): list_workspaces \u2192 list_prs(queue=review, limit=N) \u2192 show those PRs (ticket ids in the title when present). create_thread sourceType=pr only when they asked to start / do the work. Do not list_issues for "tickets to review".',
7518
7518
  "Typical flow (new app): Bash create/clone under repos dir \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread (implement) \u2192 wait_for_turn (loop while stillRunning) \u2192 ask_git create-draft.",
7519
7519
  "Always ask worktree agents to commit, push, and open draft PRs (`ask_git` / `send_to_thread`). If the user gave a goal (Greptile 5/5, CI green), pass that goal through so they watch-fix-push until it lands \u2014 do not start that loop on a plain push, and do not tell the human to poll. Tell them to merge only when the user explicitly asked. The worktree agent runs git/gh; never merge from this orchestration cwd."
@@ -7567,10 +7567,10 @@ var init_coordinator_prompt = __esm({
7567
7567
  "Role: you oversee Sideboard worktree agents across registered repos. You do not live inside one of those worktrees.",
7568
7568
  "Sideboard MCP (fleet control \u2014 prefer these for status and orchestration):",
7569
7569
  "Discover:",
7570
- "- list_workspaces \u2014 registered repos (path + github slug + roles/notes when set). Use that profile to pick the right repo for tickets or reviews.",
7570
+ "- list_workspaces \u2014 registered repos (path + github slug + project context when set). Use account / project context to pick the right repo for tickets or reviews.",
7571
7571
  "- list_board \u2014 Home Kanban of worktrees (New / Draft / Review / Merged; one card per checkout). 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, column, limit.",
7572
- '- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces. Review is PRs (the surface for assigned ticket work), not the tickets: "Get me N tickets to review" \u2192 list_prs(queue=review, limit=N) then create_thread sourceType=pr. That is open non-draft PRs labeled eng-review with no individual user reviewer yet. A team request (engineering-team) is not a claim \u2014 the viewer is on that team and can pick it up; claimed means an individual account is the reviewer. Bots ignored. Prefer teams that match Settings \u2192 Agents / Projects roles (check one or more of Engineering, Design, Product, or extras they added \u2014 never a combined both value; project roles override account for that repo). queue=mine is review-requested:@me; queue=approved|changes uses those labels. Also: state, label, reviewer=me|unassigned|login, query, limit default 40 max 250; raise limit or tighten when truncated. list_issues (query, assignee=me|unassigned|all|user, limit default 40 max 250) lists Linear, AbleTime, or GitHub tickets \u2014 do not use it for that review-inbox ask. When they ask for tickets to work on, use their notes (assignee=me or unassigned as the notes say) and prefer ones that match their roles.',
7573
- "- Find work: when they ask to find work, pick up tickets, or list reviews, use Settings \u2192 Agents (account) plus Settings \u2192 Projects (per-repo) roles and notes. Tickets \u2192 Sideboard list_issues / linear_* (Account Linear). Reviews \u2192 list_prs(queue=review). Do not call Claude Linear MCP or any other vendor Linear MCP \u2014 those HTTP connectors hang or flap on the first turn. If a vendor MCP is down or reconnecting, ignore it and keep going with Sideboard tools. Show the options \u2014 do not create_thread or start unless they also asked to start (e.g. \u201Cfind me work and start it\u201D). Do not start this unprompted.",
7572
+ '- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces. Review is PRs (the surface for assigned ticket work), not the tickets: "Get me N tickets to review" \u2192 list_prs(queue=review, limit=N) then create_thread sourceType=pr. That is open non-draft PRs labeled eng-review with no individual user reviewer yet. A team request (engineering-team) is not a claim \u2014 the viewer is on that team and can pick it up; claimed means an individual account is the reviewer. Bots ignored. Use Settings \u2192 Agents / Projects context (roles, teams, labels \u2014 freeform text; project adds to account) to prefer the right queues. queue=mine is review-requested:@me; queue=approved|changes uses those labels. Also: state, label, reviewer=me|unassigned|login, query, limit default 40 max 250; raise limit or tighten when truncated. list_issues (query, assignee=me|unassigned|all|user, limit default 40 max 250) lists Linear, AbleTime, or GitHub tickets \u2014 do not use it for that review-inbox ask. When they ask for tickets to work on, follow their context (assignee=me or unassigned as it says).',
7573
+ "- Find work: when they ask to find work, pick up tickets, or list reviews, use Settings \u2192 Agents (account) plus Settings \u2192 Projects (per-repo) context. Tickets \u2192 Sideboard list_issues / linear_* (Account Linear). Reviews \u2192 list_prs(queue=review). Do not call Claude Linear MCP or any other vendor Linear MCP \u2014 those HTTP connectors hang or flap on the first turn. If a vendor MCP is down or reconnecting, ignore it and keep going with Sideboard tools. Show the options \u2014 do not create_thread or start unless they also asked to start (e.g. \u201Cfind me work and start it\u201D). Do not start this unprompted. To change that context, show the proposed text, ask_user (Save this context / Do not save), wait, then update_viewer_context with confirmed=true. Never write it without confirmation.",
7574
7574
  "- linear_* (when Linear is connected) \u2014 list_teams for team key/states; get/create/update/comment with ENG-123. Worktree agents have the same Account tools \u2014 prefer they comment, update status, and create spin-offs (parent=) on their own ticket. Scope errors: reconnect Linear in Account settings. Prefer these over any Linear MCP the CLI may still list.",
7575
7575
  "- github_* \u2014 get/comment/update/create GitHub issues via Account `gh` (#123). Worktree agents have these too. Prefer over any vendor GitHub MCP. Pass parent= for spin-offs.",
7576
7576
  "- abletime_* (when AbleTime is connected) \u2014 orientation first; get/comment/update/create (parent= for spin-offs); ensure_task when work has no ticket (or create_thread from the default branch auto-creates one). Worktree agents have the same Account tools.",
@@ -7580,7 +7580,8 @@ var init_coordinator_prompt = __esm({
7580
7580
  "- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
7581
7581
  "- list_models \u2014 only when you need a specific model (rare); otherwise omit model so Account defaults apply",
7582
7582
  "- list_threads / get_thread \u2014 live thread list (parent id + last message preview). get_thread on this orchestration chat lists child worktree agents (status + lastText). Also includes usage / lastTurnUsage.",
7583
- "- ask_user \u2014 composer multiple-choice only when blocked on a concrete choice (approach fork, which API). Never for hellos, check-ins, or invented \u201Cwhat should we do?\u201D menus \u2014 reply in chat. Explain options first, description on every option, then wait.",
7583
+ "- ask_user \u2014 composer multiple-choice only when blocked on a concrete choice (approach fork, which API, Save this context / Do not save). Never for hellos, check-ins, or invented \u201Cwhat should we do?\u201D menus \u2014 reply in chat. Explain options first, description on every option, then wait.",
7584
+ "- get_viewer_context / update_viewer_context \u2014 read or replace Settings \u2192 Agents (account) and Settings \u2192 Projects (per-repo) context. You can update a project: pass scope=project and repoPath from list_workspaces (this cwd is not a project). update_viewer_context requires confirmed=true after ask_user. Never write context unprompted.",
7584
7585
  "- set_caffeinate \u2014 keep this Mac awake across turns (macOS caffeinate). Turn on for Slack / away-from-keyboard work, overnight schedules, or when the user will be away. Turn OFF when they say they are done, wrapping up, going to sleep, or no longer need the machine awake. Closing this chat also releases it.",
7585
7586
  "- list_schedules / create_schedule / update_schedule / delete_schedule / run_schedule \u2014 local jobs that send a prompt to an orchestration chat (threadId or self) or start a new Global chat (omit threadId). One-shot `at`, interval `every` (15m/1h/6h/1d), or 5-field `cron`. Recurring jobs without threadId open a new chat each run. Jobs fire only while Sideboard.app is running; sleep skips until wake. Overnight/unattended runs: ask the user to enable Settings \u2192 Advanced \u2192 Caffeinate while schedules are enabled, or call set_caffeinate.",
7586
7587
  "Workspaces:",
@@ -8607,10 +8608,10 @@ var init_brightsy = __esm({
8607
8608
  });
8608
8609
 
8609
8610
  // src/agents/claude-mcp.ts
8610
- function parseMcpList(text6) {
8611
+ function parseMcpList(text7) {
8611
8612
  const servers = [];
8612
8613
  const seen = /* @__PURE__ */ new Set();
8613
- for (const raw of text6.split("\n")) {
8614
+ for (const raw of text7.split("\n")) {
8614
8615
  const line = raw.trim();
8615
8616
  if (!line || /^Checking MCP/i.test(line)) continue;
8616
8617
  const m = line.match(/^(.+?):\s+\S+/);
@@ -8765,8 +8766,8 @@ function isSubagentToolName(name) {
8765
8766
  if (/^(task|agent|spawn_agent)$/i.test(n)) return true;
8766
8767
  return /connectedAgentRequest/i.test(n);
8767
8768
  }
8768
- function isInternalAgentStatusText(text6) {
8769
- const t = text6.trim();
8769
+ function isInternalAgentStatusText(text7) {
8770
+ const t = text7.trim();
8770
8771
  if (!t) return false;
8771
8772
  if (/waiting for gate to pass/i.test(t)) return true;
8772
8773
  if (/^agent is running\b/i.test(t) && t.length < 160) return true;
@@ -8780,9 +8781,9 @@ function lastTextPart(parts, type) {
8780
8781
  for (let i = parts.length - 1; i >= 0; i--) {
8781
8782
  const p = parts[i];
8782
8783
  if (p.type !== type) continue;
8783
- const text6 = p.text.trim();
8784
- if (!text6 || isInternalAgentStatusText(text6)) continue;
8785
- return text6;
8784
+ const text7 = p.text.trim();
8785
+ if (!text7 || isInternalAgentStatusText(text7)) continue;
8786
+ return text7;
8786
8787
  }
8787
8788
  return "";
8788
8789
  }
@@ -8803,7 +8804,7 @@ function liveActivitySummary(parts, opts) {
8803
8804
  );
8804
8805
  const runningPoll = [...tools].reverse().find((t) => t.status === "running" && isPollWrapperToolName(t.name));
8805
8806
  const thinking = lastTextPart(parts, "thinking");
8806
- const text6 = lastTextPart(parts, "text");
8807
+ const text7 = lastTextPart(parts, "text");
8807
8808
  if (runningSubs.length > 0) {
8808
8809
  const heads = runningSubs.map(toolLabel);
8809
8810
  const head = runningSubs.length === 1 ? heads[0] : `${runningSubs.length} subagents \xB7 ${heads.slice(0, 2).join(" \xB7 ")}`;
@@ -8813,7 +8814,7 @@ function liveActivitySummary(parts, opts) {
8813
8814
  if (runningTop) return toolLabel(runningTop);
8814
8815
  if (runningPoll) return toolLabel(runningPoll);
8815
8816
  if (thinking) return thinking.length > 96 ? `\u2026${thinking.slice(-96)}` : thinking;
8816
- if (text6) return "Writing reply\u2026";
8817
+ if (text7) return "Writing reply\u2026";
8817
8818
  const last = [...tools].reverse().find((t) => !isPollWrapperToolName(t.name)) ?? tools.at(-1);
8818
8819
  if (last) {
8819
8820
  const label = toolLabel(last);
@@ -8841,9 +8842,9 @@ function toolFilePath(input) {
8841
8842
  if (!input) return void 0;
8842
8843
  return str2(input.file_path) ?? str2(input.path) ?? str2(input.filePath) ?? str2(input.filename);
8843
8844
  }
8844
- function countLines(text6) {
8845
- if (!text6) return 0;
8846
- return text6.split("\n").length;
8845
+ function countLines(text7) {
8846
+ if (!text7) return 0;
8847
+ return text7.split("\n").length;
8847
8848
  }
8848
8849
  function diffFromInput(input) {
8849
8850
  if (!input) return {};
@@ -9068,9 +9069,9 @@ function partsToAssistantText(parts) {
9068
9069
  (p) => p.type === "text" && !messagePartParentId(p)
9069
9070
  ).map((p) => p.text).join("").trim();
9070
9071
  }
9071
- function stripBrightsyNdjsonNoise(text6) {
9072
- if (!text6 || !text6.includes('"type"')) return text6;
9073
- let out = text6;
9072
+ function stripBrightsyNdjsonNoise(text7) {
9073
+ if (!text7 || !text7.includes('"type"')) return text7;
9074
+ let out = text7;
9074
9075
  out = out.replace(
9075
9076
  /\{"type":"(?:tool_use|tool_result|tool|thinking|usage|done|error)"[\s\S]*?\}\s*(?=\{"type":"|$|(?=[A-Za-z*#]))/g,
9076
9077
  ""
@@ -9393,8 +9394,8 @@ function isBrightsyConnected() {
9393
9394
  return false;
9394
9395
  }
9395
9396
  }
9396
- function promptMentionsBrightsy(text6) {
9397
- return BRIGHTSY_WORD.test(text6 ?? "");
9397
+ function promptMentionsBrightsy(text7) {
9398
+ return BRIGHTSY_WORD.test(text7 ?? "");
9398
9399
  }
9399
9400
  function isBrightsyMcpToolName(name) {
9400
9401
  const n = name.toLowerCase();
@@ -9670,7 +9671,9 @@ var init_injected_mcp = __esm({
9670
9671
  "mcp__sideboard__present_files",
9671
9672
  "mcp__sideboard__ask_user",
9672
9673
  "mcp__sideboard__present_plan",
9673
- "mcp__sideboard__wait_for_job"
9674
+ "mcp__sideboard__wait_for_job",
9675
+ "mcp__sideboard__get_viewer_context",
9676
+ "mcp__sideboard__update_viewer_context"
9674
9677
  ];
9675
9678
  SIDEBOARD_GITHUB_MCP_ALLOWED_TOOLS = ["mcp__sideboard__github_*"];
9676
9679
  SIDEBOARD_LINEAR_MCP_ALLOWED_TOOLS = ["mcp__sideboard__linear_*"];
@@ -10003,9 +10006,9 @@ function compactMetadataFromClaude(obj) {
10003
10006
  return { trigger, postTokens };
10004
10007
  }
10005
10008
  function parseIssuesJson(raw) {
10006
- const text6 = raw.trim();
10007
- const candidates = [text6];
10008
- const match = text6.match(/\[[\s\S]*\]/);
10009
+ const text7 = raw.trim();
10010
+ const candidates = [text7];
10011
+ const match = text7.match(/\[[\s\S]*\]/);
10009
10012
  if (match) candidates.push(match[0]);
10010
10013
  for (const c of candidates) {
10011
10014
  try {
@@ -10273,8 +10276,8 @@ var init_claude = __esm({
10273
10276
  if (errorDetail) {
10274
10277
  events.push({ type: "stderr", data: errorDetail });
10275
10278
  } else {
10276
- const text6 = obj.result;
10277
- if (typeof text6 === "string" && text6) events.push({ type: "stdout", data: text6 });
10279
+ const text7 = obj.result;
10280
+ if (typeof text7 === "string" && text7) events.push({ type: "stdout", data: text7 });
10278
10281
  }
10279
10282
  const usage = usageFromClaude(obj.usage);
10280
10283
  if (usage) {
@@ -10351,9 +10354,9 @@ function asObject(value) {
10351
10354
  function listMcpNamesFromJsonMap(raw, key) {
10352
10355
  return Object.keys(asObject(asObject(raw)[key])).filter((n) => n.trim());
10353
10356
  }
10354
- function listMcpNamesFromCodexToml(text6) {
10357
+ function listMcpNamesFromCodexToml(text7) {
10355
10358
  try {
10356
- const parsed = (0, import_smol_toml2.parse)(text6);
10359
+ const parsed = (0, import_smol_toml2.parse)(text7);
10357
10360
  return Object.keys(asObject(parsed.mcp_servers)).filter((n) => n.trim());
10358
10361
  } catch {
10359
10362
  return [];
@@ -10484,8 +10487,8 @@ function codexConfigHasNetworkAccess() {
10484
10487
  ];
10485
10488
  for (const path of candidates) {
10486
10489
  if (!(0, import_node_fs28.existsSync)(path)) continue;
10487
- const text6 = (0, import_node_fs28.readFileSync)(path, "utf8");
10488
- if (/network_access\s*=\s*true/.test(text6)) return true;
10490
+ const text7 = (0, import_node_fs28.readFileSync)(path, "utf8");
10491
+ if (/network_access\s*=\s*true/.test(text7)) return true;
10489
10492
  }
10490
10493
  return false;
10491
10494
  }
@@ -10498,8 +10501,8 @@ function unwrapCodexMcpResult(result) {
10498
10501
  const texts = [];
10499
10502
  for (const item of rec.content) {
10500
10503
  if (!item || typeof item !== "object") continue;
10501
- const text6 = item.text;
10502
- if (typeof text6 === "string") texts.push(text6);
10504
+ const text7 = item.text;
10505
+ if (typeof text7 === "string") texts.push(text7);
10503
10506
  }
10504
10507
  if (texts.length) return texts.join("\n");
10505
10508
  }
@@ -10741,8 +10744,8 @@ var init_codex = __esm({
10741
10744
  if (msg) nested.push(msg);
10742
10745
  }
10743
10746
  }
10744
- for (const text6 of nested) {
10745
- events.push({ type: "stdout", data: text6, parentId: item.id });
10747
+ for (const text7 of nested) {
10748
+ events.push({ type: "stdout", data: text7, parentId: item.id });
10746
10749
  }
10747
10750
  events.push({
10748
10751
  type: "tool_result",
@@ -11293,8 +11296,8 @@ var init_opencode = __esm({
11293
11296
  return { type: "session_id", data: sid };
11294
11297
  }
11295
11298
  if (obj.type === "text") {
11296
- const text6 = obj.part?.text ?? obj.text;
11297
- if (text6) return { type: "stdout", data: text6 };
11299
+ const text7 = obj.part?.text ?? obj.text;
11300
+ if (text7) return { type: "stdout", data: text7 };
11298
11301
  }
11299
11302
  if (obj.type === "tool_use") {
11300
11303
  const part = obj.part;
@@ -11573,8 +11576,8 @@ var init_list_models = __esm({
11573
11576
  });
11574
11577
 
11575
11578
  // src/agents/session-quota.ts
11576
- function isSessionQuotaLimit(text6) {
11577
- const lower = text6.trim().toLowerCase();
11579
+ function isSessionQuotaLimit(text7) {
11580
+ const lower = text7.trim().toLowerCase();
11578
11581
  if (!lower) return false;
11579
11582
  if (/credit balance is too low|out of credits|insufficient.?quota|billing/.test(lower)) {
11580
11583
  return false;
@@ -11582,10 +11585,10 @@ function isSessionQuotaLimit(text6) {
11582
11585
  if (/prompt is too long|context.*(too long|exceed)|conversation too long/.test(lower)) {
11583
11586
  return false;
11584
11587
  }
11585
- return /you've hit your/.test(lower) || /hit your (session|weekly|opus) limit/.test(lower) || /usage limit/.test(lower) || /rate.?limit|too many requests|\b429\b/.test(lower) && /reset/i.test(text6);
11588
+ return /you've hit your/.test(lower) || /hit your (session|weekly|opus) limit/.test(lower) || /usage limit/.test(lower) || /rate.?limit|too many requests|\b429\b/.test(lower) && /reset/i.test(text7);
11586
11589
  }
11587
- function parseSessionQuotaResetAt(text6, now = /* @__PURE__ */ new Date()) {
11588
- const absolute = text6.match(
11590
+ function parseSessionQuotaResetAt(text7, now = /* @__PURE__ */ new Date()) {
11591
+ const absolute = text7.match(
11589
11592
  /resets\s+(?:at\s+)?(\d{1,2}):(\d{2})\s*(am|pm)(?:\s*\(([^)]+)\))?/i
11590
11593
  );
11591
11594
  if (absolute) {
@@ -11603,7 +11606,7 @@ function parseSessionQuotaResetAt(text6, now = /* @__PURE__ */ new Date()) {
11603
11606
  }
11604
11607
  return at;
11605
11608
  }
11606
- const relative = text6.match(
11609
+ const relative = text7.match(
11607
11610
  /resets\s+in\s+(\d+)\s*(minutes?|hours?|days?)/i
11608
11611
  );
11609
11612
  if (relative) {
@@ -11903,8 +11906,8 @@ async function spawnAgentTurn(thread, input, onEvent) {
11903
11906
  onEvent({ type: "exit", data: exitCode });
11904
11907
  const finalized = finalizeParts(parts);
11905
11908
  const rawText = assistantText.trim() || partsToAssistantText(finalized);
11906
- const text6 = thread.agent === "brightsy" ? stripBrightsyNdjsonNoise(rawText) : rawText;
11907
- return { exitCode, sessionId, assistantText: text6, parts: finalized, usage };
11909
+ const text7 = thread.agent === "brightsy" ? stripBrightsyNdjsonNoise(rawText) : rawText;
11910
+ return { exitCode, sessionId, assistantText: text7, parts: finalized, usage };
11908
11911
  });
11909
11912
  return {
11910
11913
  pid: child.pid,
@@ -12163,9 +12166,9 @@ async function tryClaudeSummary(transcript, opts) {
12163
12166
  { cwd: opts?.cwd, reject: false }
12164
12167
  );
12165
12168
  if (exitCode !== 0) return null;
12166
- const text6 = stdout.trim();
12167
- if (text6.length < 40) return null;
12168
- return text6;
12169
+ const text7 = stdout.trim();
12170
+ if (text7.length < 40) return null;
12171
+ return text7;
12169
12172
  } catch {
12170
12173
  return null;
12171
12174
  }
@@ -12214,9 +12217,9 @@ function extractiveSummary(transcript) {
12214
12217
  );
12215
12218
  return parts.join("\n");
12216
12219
  }
12217
- function clipSummary(text6) {
12218
- if (text6.length <= MAX_SUMMARY_CHARS) return text6;
12219
- return `${text6.slice(0, MAX_SUMMARY_CHARS)}
12220
+ function clipSummary(text7) {
12221
+ if (text7.length <= MAX_SUMMARY_CHARS) return text7;
12222
+ return `${text7.slice(0, MAX_SUMMARY_CHARS)}
12220
12223
 
12221
12224
  [\u2026summary truncated\u2026]`;
12222
12225
  }
@@ -12370,8 +12373,8 @@ function findLastBrightsyContextSummary(messages) {
12370
12373
  continue;
12371
12374
  }
12372
12375
  if (part.status === "error") continue;
12373
- const text6 = extractBrightsyContextSummary(part.result);
12374
- if (text6) return { index: i, text: text6 };
12376
+ const text7 = extractBrightsyContextSummary(part.result);
12377
+ if (text7) return { index: i, text: text7 };
12375
12378
  }
12376
12379
  }
12377
12380
  return null;
@@ -13775,8 +13778,8 @@ function listRunningDetachedJobs(worktreePath) {
13775
13778
  }
13776
13779
  return running.sort();
13777
13780
  }
13778
- function looksLikeDeferredDonePromise(text6) {
13779
- const t = (text6 ?? "").trim();
13781
+ function looksLikeDeferredDonePromise(text7) {
13782
+ const t = (text7 ?? "").trim();
13780
13783
  if (!t) return false;
13781
13784
  return /\b(i['’]?ll|i will)\s+let you know\b/i.test(t) || /\blet you know when\b/i.test(t) || /\b(check back|ping me)\s+when\b/i.test(t) || /\bi(?:['’]ll| will)\s+(report|update you)\s+when\b/i.test(t);
13782
13785
  }
@@ -14480,11 +14483,11 @@ function unwrapToolResult(result) {
14480
14483
  return typeof item.text === "string" ? item.text : "";
14481
14484
  }).filter(Boolean);
14482
14485
  if (texts.length === 1) {
14483
- const text6 = texts[0];
14486
+ const text7 = texts[0];
14484
14487
  try {
14485
- return JSON.parse(text6);
14488
+ return JSON.parse(text7);
14486
14489
  } catch {
14487
- return text6;
14490
+ return text7;
14488
14491
  }
14489
14492
  }
14490
14493
  if (texts.length > 1) return texts.join("\n");
@@ -15237,8 +15240,8 @@ function summarizeTurnLive(parts) {
15237
15240
  const interesting = [...tools].reverse().find((t) => !isPollWrapperToolName(t.name)) ?? lastTool;
15238
15241
  const lastToolLabel = interesting ? interesting.description || toolDescription(interesting.name, interesting.input) || interesting.name : void 0;
15239
15242
  const thinking = lastText(parts, "thinking");
15240
- const text6 = lastText(parts, "text");
15241
- const excerptRaw = thinking || text6;
15243
+ const text7 = lastText(parts, "text");
15244
+ const excerptRaw = thinking || text7;
15242
15245
  const excerpt = excerptRaw.length > 280 ? `${excerptRaw.slice(-280)}` : excerptRaw || void 0;
15243
15246
  const summary = liveActivitySummary(parts);
15244
15247
  return {
@@ -15474,8 +15477,8 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
15474
15477
  );
15475
15478
  const recent = from.messages.slice(-8).map((m) => {
15476
15479
  const role = m.role === "user" ? "User" : m.role === "agent" ? "Agent" : "Summary";
15477
- const text6 = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
15478
- return text6 ? `- ${role}: ${text6}` : null;
15480
+ const text7 = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
15481
+ return text7 ? `- ${role}: ${text7}` : null;
15479
15482
  }).filter(Boolean);
15480
15483
  const body = [
15481
15484
  `# Orchestration handoff`,
@@ -15572,13 +15575,13 @@ function resolveConductorCursorAgentId(workspacePath) {
15572
15575
  for (const hash of hashes) {
15573
15576
  const agentsFile = (0, import_node_path39.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
15574
15577
  if (!(0, import_node_fs41.existsSync)(agentsFile)) continue;
15575
- let text6;
15578
+ let text7;
15576
15579
  try {
15577
- text6 = (0, import_node_fs41.readFileSync)(agentsFile, "utf8");
15580
+ text7 = (0, import_node_fs41.readFileSync)(agentsFile, "utf8");
15578
15581
  } catch {
15579
15582
  continue;
15580
15583
  }
15581
- for (const line of text6.split("\n")) {
15584
+ for (const line of text7.split("\n")) {
15582
15585
  const trimmed = line.trim();
15583
15586
  if (!trimmed) continue;
15584
15587
  try {
@@ -16374,13 +16377,13 @@ function formatStat(files) {
16374
16377
  return `${n} file${n === 1 ? "" : "s"} changed, ${additions} insertions(+), ${deletions} deletions(-)`;
16375
16378
  }
16376
16379
  function emptyScopeStats() {
16377
- const z7 = emptyStat();
16380
+ const z8 = emptyStat();
16378
16381
  return {
16379
- commits: z7,
16380
- uncommitted: z7,
16381
- staged: z7,
16382
- unstaged: z7,
16383
- last_turn: z7
16382
+ commits: z8,
16383
+ uncommitted: z8,
16384
+ staged: z8,
16385
+ unstaged: z8,
16386
+ last_turn: z8
16384
16387
  };
16385
16388
  }
16386
16389
  function filesFromDiff(nameStatus, numstat, combinedDiff, maxHunk) {
@@ -17760,11 +17763,11 @@ function resolvePlanMarkdown(opts) {
17760
17763
  source: "exit_plan"
17761
17764
  };
17762
17765
  }
17763
- const text6 = opts.text?.trim();
17764
- if (text6 && text6.length >= 80) {
17766
+ const text7 = opts.text?.trim();
17767
+ if (text7 && text7.length >= 80) {
17765
17768
  return {
17766
17769
  title: "Plan",
17767
- content: text6,
17770
+ content: text7,
17768
17771
  path: PLAN_FILE_REL,
17769
17772
  source: "text"
17770
17773
  };
@@ -19175,11 +19178,11 @@ var init_orchestrator = __esm({
19175
19178
  return results;
19176
19179
  }
19177
19180
  /** Edit the text of a not-yet-started queued message. */
19178
- async editQueuedMessage(threadRef, index, text6) {
19181
+ async editQueuedMessage(threadRef, index, text7) {
19179
19182
  const thread = this.requireThread(threadRef);
19180
19183
  return withThreadLock(thread.id, async () => {
19181
19184
  const current = this.requireThread(thread.id);
19182
- const trimmed = text6.trim();
19185
+ const trimmed = text7.trim();
19183
19186
  if (!trimmed || index < 0 || index >= current.queue.length) {
19184
19187
  return current;
19185
19188
  }
@@ -19365,6 +19368,7 @@ var init_orchestrator = __esm({
19365
19368
  ticketId: issueTicket?.id,
19366
19369
  ticketProvider: issueTicket?.provider
19367
19370
  }) : null;
19371
+ const viewerContextReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatViewerContextReminder() : null;
19368
19372
  const slackReplyContext = formatSlackRepliesForTurn(
19369
19373
  pendingSlackExternalReplies(thread.messages)
19370
19374
  );
@@ -19374,6 +19378,7 @@ var init_orchestrator = __esm({
19374
19378
  worktreeReminder,
19375
19379
  optionalServicesReminder,
19376
19380
  issueToolsReminder,
19381
+ viewerContextReminder,
19377
19382
  artifactReminder,
19378
19383
  longRunningReminder,
19379
19384
  slackReplyContext,
@@ -19414,6 +19419,18 @@ var init_orchestrator = __esm({
19414
19419
  ticketId: freshIssueTicket?.id,
19415
19420
  ticketProvider: freshIssueTicket?.provider
19416
19421
  });
19422
+ let viewerContextDirective = null;
19423
+ if (!isBrightsy && !isOrchestration) {
19424
+ try {
19425
+ viewerContextDirective = formatViewerContextDirective(
19426
+ resolveViewerProfileForRepo(fresh.repoPath || fresh.worktreePath)
19427
+ );
19428
+ } catch {
19429
+ viewerContextDirective = formatViewerContextDirective(
19430
+ resolveViewerProfileForRepo()
19431
+ );
19432
+ }
19433
+ }
19417
19434
  const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
19418
19435
  const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled2() ? formatRenameBranchDirective(fresh, {
19419
19436
  customPrompt: settings?.prompts?.renameBranch
@@ -19445,6 +19462,7 @@ var init_orchestrator = __esm({
19445
19462
  worktreeDirective,
19446
19463
  optionalServicesDirective,
19447
19464
  issueToolsDirective,
19465
+ viewerContextDirective,
19448
19466
  artifactDirective,
19449
19467
  longRunningDirective,
19450
19468
  renameBranchDirective,
@@ -19555,6 +19573,7 @@ var init_orchestrator = __esm({
19555
19573
  worktreeDirective,
19556
19574
  optionalServicesDirective,
19557
19575
  issueToolsDirective,
19576
+ viewerContextDirective,
19558
19577
  artifactDirective,
19559
19578
  longRunningDirective,
19560
19579
  renameBranchDirective,
@@ -20089,13 +20108,13 @@ var init_orchestrator = __esm({
20089
20108
  const lastAgent = [...thread.messages].reverse().find((m) => m.role === "agent");
20090
20109
  const lastError = thread.lastError ?? null;
20091
20110
  const rawText = (lastAgent?.text ?? "").trim();
20092
- const text6 = (rawText && !isInternalAgentStatusText(rawText) ? rawText : "") || (thread.status === "error" || thread.status === "stopped" || thread.status === "broken" ? lastError ?? "" : "");
20111
+ const text7 = (rawText && !isInternalAgentStatusText(rawText) ? rawText : "") || (thread.status === "error" || thread.status === "stopped" || thread.status === "broken" ? lastError ?? "" : "");
20093
20112
  const stillRunning = this.threadLooksLive(thread);
20094
20113
  const live = stillRunning ? readTurnLive(thread.id) : null;
20095
20114
  const liveSummary = live?.summary && !isInternalAgentStatusText(live.summary) ? live.summary : null;
20096
20115
  const queuedHint = stillRunning && thread.status === "queued" && !liveSummary ? "Queued \u2014 waiting for a concurrency slot" : null;
20097
20116
  return {
20098
- text: text6,
20117
+ text: text7,
20099
20118
  status: thread.status,
20100
20119
  sessionId: thread.sessionId,
20101
20120
  lastError,
@@ -20739,7 +20758,7 @@ init_nested_electron_env();
20739
20758
  // src/mcp/server.ts
20740
20759
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
20741
20760
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
20742
- var import_zod6 = require("zod");
20761
+ var import_zod7 = require("zod");
20743
20762
  var import_node_crypto12 = require("crypto");
20744
20763
  var import_node_path50 = require("path");
20745
20764
  init_orchestrator();
@@ -21600,9 +21619,9 @@ init_message_parts();
21600
21619
  function lastMessagePreview(messages, max = 160) {
21601
21620
  if (!messages?.length) return null;
21602
21621
  for (let i = messages.length - 1; i >= 0; i--) {
21603
- const text6 = messages[i]?.text?.trim();
21604
- if (!text6 || isInternalAgentStatusText(text6)) continue;
21605
- const flat = text6.replace(/\s+/g, " ");
21622
+ const text7 = messages[i]?.text?.trim();
21623
+ if (!text7 || isInternalAgentStatusText(text7)) continue;
21624
+ const flat = text7.replace(/\s+/g, " ");
21606
21625
  return flat.length > max ? `${flat.slice(0, max)}\u2026` : flat;
21607
21626
  }
21608
21627
  return null;
@@ -21836,8 +21855,8 @@ function labelGithubUrl(url) {
21836
21855
  }
21837
21856
  return { kind: "other", label: "GitHub", url: raw };
21838
21857
  }
21839
- function appendGithubLink(text6, githubUrl) {
21840
- const body = text6.trimEnd();
21858
+ function appendGithubLink(text7, githubUrl) {
21859
+ const body = text7.trimEnd();
21841
21860
  if (!githubUrl?.trim()) return body;
21842
21861
  const labeled = labelGithubUrl(githubUrl);
21843
21862
  if (!labeled) {
@@ -22709,6 +22728,104 @@ function registerConnectedIssueVendorTools(server, settings = loadAppSettings())
22709
22728
  if (isAbleTimeConnected(settings)) registerAbleTimeTools(server);
22710
22729
  }
22711
22730
 
22731
+ // src/mcp/viewer-context-tools.ts
22732
+ var import_zod5 = require("zod");
22733
+ init_worktree();
22734
+ init_app_settings();
22735
+ init_workspaces2();
22736
+ function text5(payload, isError = false) {
22737
+ return mcpJson(payload, isError);
22738
+ }
22739
+ function matchRegisteredWorkspace(repoPath) {
22740
+ const hint = (repoPath ?? "").trim().replace(/\/+$/, "");
22741
+ if (!hint) return null;
22742
+ const match = listWorkspaces().find((w) => {
22743
+ const stored = w.path.replace(/\/+$/, "");
22744
+ return stored === hint || hint.startsWith(`${stored}/`) || stored.startsWith(`${hint}/`);
22745
+ });
22746
+ return match?.path.replace(/\/+$/, "") ?? null;
22747
+ }
22748
+ async function resolveProjectRepoPath(repoPath) {
22749
+ const hint = repoPath?.trim() || process.cwd();
22750
+ let root = hint;
22751
+ try {
22752
+ root = await resolveRepoRoot(hint);
22753
+ } catch {
22754
+ root = hint.replace(/\/+$/, "");
22755
+ }
22756
+ return matchRegisteredWorkspace(root) ?? matchRegisteredWorkspace(hint);
22757
+ }
22758
+ var PROJECT_PATH_HINT = "Pass repoPath from list_workspaces. Orchestration cwd is synthetic and empty \u2014 do not omit repoPath for project context.";
22759
+ function registerViewerContextTools(server) {
22760
+ server.tool(
22761
+ "get_viewer_context",
22762
+ "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).",
22763
+ {
22764
+ repoPath: import_zod5.z.string().optional().describe(
22765
+ "Registered workspace or worktree path. Required from orchestration for a project. Omit on a worktree turn (uses cwd)."
22766
+ )
22767
+ },
22768
+ async ({ repoPath }) => {
22769
+ try {
22770
+ const path = await resolveProjectRepoPath(repoPath);
22771
+ return text5(readViewerContext(path ?? void 0));
22772
+ } catch (err) {
22773
+ return text5(
22774
+ { error: err instanceof Error ? err.message : String(err) },
22775
+ true
22776
+ );
22777
+ }
22778
+ }
22779
+ );
22780
+ server.tool(
22781
+ "update_viewer_context",
22782
+ "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.",
22783
+ {
22784
+ scope: import_zod5.z.enum(["account", "project"]).describe("account = Settings \u2192 Agents; project = Settings \u2192 Projects for one repo"),
22785
+ context: import_zod5.z.string().describe("Full replacement text (roles, tickets, review queues). Empty clears."),
22786
+ confirmed: import_zod5.z.boolean().optional().describe("Must be true after the user confirmed via ask_user. False/omit is rejected."),
22787
+ repoPath: import_zod5.z.string().optional().describe(
22788
+ "Required for scope=project from orchestration (path from list_workspaces). Worktree turns may omit it (cwd)."
22789
+ )
22790
+ },
22791
+ async ({ scope, context, confirmed, repoPath }) => {
22792
+ try {
22793
+ if (scope === "project") {
22794
+ const path = await resolveProjectRepoPath(repoPath);
22795
+ if (!path) {
22796
+ return text5(
22797
+ {
22798
+ ok: false,
22799
+ error: "repoPath required",
22800
+ message: PROJECT_PATH_HINT
22801
+ },
22802
+ true
22803
+ );
22804
+ }
22805
+ const result2 = writeViewerContext({
22806
+ scope,
22807
+ context,
22808
+ repoPath: path,
22809
+ confirmed: confirmed === true
22810
+ });
22811
+ return text5(result2, !result2.ok);
22812
+ }
22813
+ const result = writeViewerContext({
22814
+ scope,
22815
+ context,
22816
+ confirmed: confirmed === true
22817
+ });
22818
+ return text5(result, !result.ok);
22819
+ } catch (err) {
22820
+ return text5(
22821
+ { error: err instanceof Error ? err.message : String(err) },
22822
+ true
22823
+ );
22824
+ }
22825
+ }
22826
+ );
22827
+ }
22828
+
22712
22829
  // src/mcp/pr-list.ts
22713
22830
  function compactPrRow(pr) {
22714
22831
  return {
@@ -22748,17 +22865,17 @@ init_list_prs();
22748
22865
  init_app_settings();
22749
22866
 
22750
22867
  // src/mcp/schedule-tools.ts
22751
- var import_zod5 = require("zod");
22868
+ var import_zod6 = require("zod");
22752
22869
  init_schedules();
22753
22870
  init_schedule_runner();
22754
- function text5(payload, isError = false) {
22871
+ function text6(payload, isError = false) {
22755
22872
  return {
22756
22873
  content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
22757
22874
  ...isError ? { isError: true } : {}
22758
22875
  };
22759
22876
  }
22760
22877
  function fail5(err) {
22761
- return text5(
22878
+ return text6(
22762
22879
  { error: err instanceof Error ? err.message : String(err) },
22763
22880
  true
22764
22881
  );
@@ -22783,7 +22900,7 @@ function registerScheduleTools(server) {
22783
22900
  {},
22784
22901
  async () => {
22785
22902
  try {
22786
- return text5({ schedules: listSchedules() });
22903
+ return text6({ schedules: listSchedules() });
22787
22904
  } catch (err) {
22788
22905
  return fail5(err);
22789
22906
  }
@@ -22793,17 +22910,17 @@ function registerScheduleTools(server) {
22793
22910
  "create_schedule",
22794
22911
  "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.",
22795
22912
  {
22796
- prompt: import_zod5.z.string().describe("User message / goal queued when the schedule fires"),
22797
- name: import_zod5.z.string().optional(),
22798
- at: import_zod5.z.string().optional().describe("ISO datetime for a one-shot"),
22799
- every: import_zod5.z.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
22800
- cron: import_zod5.z.string().optional().describe("5-field cron expression"),
22801
- tz: import_zod5.z.string().optional().describe("IANA timezone for cron (default: system)"),
22802
- threadId: import_zod5.z.string().optional().describe(
22913
+ prompt: import_zod6.z.string().describe("User message / goal queued when the schedule fires"),
22914
+ name: import_zod6.z.string().optional(),
22915
+ at: import_zod6.z.string().optional().describe("ISO datetime for a one-shot"),
22916
+ every: import_zod6.z.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
22917
+ cron: import_zod6.z.string().optional().describe("5-field cron expression"),
22918
+ tz: import_zod6.z.string().optional().describe("IANA timezone for cron (default: system)"),
22919
+ threadId: import_zod6.z.string().optional().describe(
22803
22920
  'Existing orchestration chat id, or "self" for this coordinator. Omit to create a new Global chat on fire.'
22804
22921
  ),
22805
- agent: import_zod5.z.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
22806
- model: import_zod5.z.string().optional()
22922
+ agent: import_zod6.z.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
22923
+ model: import_zod6.z.string().optional()
22807
22924
  },
22808
22925
  async (args) => {
22809
22926
  try {
@@ -22823,7 +22940,7 @@ function registerScheduleTools(server) {
22823
22940
  model: args.model,
22824
22941
  createdBy: "mcp"
22825
22942
  });
22826
- return text5({
22943
+ return text6({
22827
22944
  schedule,
22828
22945
  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."
22829
22946
  });
@@ -22836,17 +22953,17 @@ function registerScheduleTools(server) {
22836
22953
  "update_schedule",
22837
22954
  "Update a local schedule (prompt, cadence, target thread, enabled). Pass id from list_schedules.",
22838
22955
  {
22839
- id: import_zod5.z.string(),
22840
- prompt: import_zod5.z.string().optional(),
22841
- name: import_zod5.z.string().optional(),
22842
- at: import_zod5.z.string().optional(),
22843
- every: import_zod5.z.string().optional(),
22844
- cron: import_zod5.z.string().optional(),
22845
- tz: import_zod5.z.string().optional(),
22846
- threadId: import_zod5.z.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
22847
- agent: import_zod5.z.enum(["claude", "cursor", "codex", "opencode"]).optional(),
22848
- model: import_zod5.z.string().optional(),
22849
- enabled: import_zod5.z.boolean().optional()
22956
+ id: import_zod6.z.string(),
22957
+ prompt: import_zod6.z.string().optional(),
22958
+ name: import_zod6.z.string().optional(),
22959
+ at: import_zod6.z.string().optional(),
22960
+ every: import_zod6.z.string().optional(),
22961
+ cron: import_zod6.z.string().optional(),
22962
+ tz: import_zod6.z.string().optional(),
22963
+ threadId: import_zod6.z.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
22964
+ agent: import_zod6.z.enum(["claude", "cursor", "codex", "opencode"]).optional(),
22965
+ model: import_zod6.z.string().optional(),
22966
+ enabled: import_zod6.z.boolean().optional()
22850
22967
  },
22851
22968
  async (args) => {
22852
22969
  try {
@@ -22875,7 +22992,7 @@ function registerScheduleTools(server) {
22875
22992
  enabled: args.enabled,
22876
22993
  model: args.model
22877
22994
  });
22878
- return text5({ schedule });
22995
+ return text6({ schedule });
22879
22996
  } catch (err) {
22880
22997
  return fail5(err);
22881
22998
  }
@@ -22884,11 +23001,11 @@ function registerScheduleTools(server) {
22884
23001
  server.tool(
22885
23002
  "delete_schedule",
22886
23003
  "Delete a local schedule. Pass id from list_schedules.",
22887
- { id: import_zod5.z.string() },
23004
+ { id: import_zod6.z.string() },
22888
23005
  async ({ id }) => {
22889
23006
  try {
22890
23007
  deleteSchedule(id);
22891
- return text5({ ok: true, id });
23008
+ return text6({ ok: true, id });
22892
23009
  } catch (err) {
22893
23010
  return fail5(err);
22894
23011
  }
@@ -22897,14 +23014,14 @@ function registerScheduleTools(server) {
22897
23014
  server.tool(
22898
23015
  "run_schedule",
22899
23016
  "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.",
22900
- { id: import_zod5.z.string() },
23017
+ { id: import_zod6.z.string() },
22901
23018
  async ({ id }) => {
22902
23019
  try {
22903
23020
  if (!getSchedule(id)) {
22904
23021
  return fail5(new Error(`Schedule not found: ${id}`));
22905
23022
  }
22906
23023
  const schedule = await fireSchedule(id);
22907
- return text5({ schedule });
23024
+ return text6({ schedule });
22908
23025
  } catch (err) {
22909
23026
  return fail5(err);
22910
23027
  }
@@ -23273,13 +23390,14 @@ async function startMcpServer() {
23273
23390
  version: "0.1.0"
23274
23391
  });
23275
23392
  const worktreeProfile = sideboardMcpProfile() === "worktree";
23393
+ registerViewerContextTools(server);
23276
23394
  if (worktreeProfile) {
23277
23395
  registerConnectedIssueVendorTools(server);
23278
23396
  }
23279
23397
  if (!worktreeProfile) {
23280
23398
  server.tool(
23281
23399
  "list_workspaces",
23282
- "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.",
23400
+ "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.",
23283
23401
  {},
23284
23402
  async () => {
23285
23403
  const workspaces = orch.listWorkspaces();
@@ -23328,13 +23446,13 @@ async function startMcpServer() {
23328
23446
  "list_board",
23329
23447
  "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.",
23330
23448
  {
23331
- query: import_zod6.z.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
23332
- repoPath: import_zod6.z.string().optional().describe("Limit to one workspace path from list_workspaces"),
23333
- kind: import_zod6.z.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
23334
- column: import_zod6.z.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
23449
+ query: import_zod7.z.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
23450
+ repoPath: import_zod7.z.string().optional().describe("Limit to one workspace path from list_workspaces"),
23451
+ kind: import_zod7.z.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
23452
+ column: import_zod7.z.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
23335
23453
  "Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
23336
23454
  ),
23337
- limit: import_zod6.z.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
23455
+ limit: import_zod7.z.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
23338
23456
  },
23339
23457
  async ({ query, repoPath, kind, column, limit }) => {
23340
23458
  const workspaces = orch.listWorkspaces();
@@ -23372,7 +23490,7 @@ async function startMcpServer() {
23372
23490
  server.tool(
23373
23491
  "get_thread",
23374
23492
  "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.",
23375
- { ref: import_zod6.z.string() },
23493
+ { ref: import_zod7.z.string() },
23376
23494
  async ({ ref }) => {
23377
23495
  const t = orch.getThread(ref);
23378
23496
  if (!t) {
@@ -23414,17 +23532,17 @@ async function startMcpServer() {
23414
23532
  "present_artifact",
23415
23533
  "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.",
23416
23534
  {
23417
- title: import_zod6.z.string().describe("Short title shown in the artifact pane header"),
23418
- type: import_zod6.z.enum(["html", "svg", "markdown", "react", "log"]).describe(
23535
+ title: import_zod7.z.string().describe("Short title shown in the artifact pane header"),
23536
+ type: import_zod7.z.enum(["html", "svg", "markdown", "react", "log"]).describe(
23419
23537
  "html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
23420
23538
  ),
23421
- content: import_zod6.z.string().describe(
23539
+ content: import_zod7.z.string().describe(
23422
23540
  "html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
23423
23541
  ),
23424
- artifact_id: import_zod6.z.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
23425
- status: import_zod6.z.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
23426
- phase: import_zod6.z.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
23427
- mode: import_zod6.z.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
23542
+ artifact_id: import_zod7.z.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
23543
+ status: import_zod7.z.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
23544
+ phase: import_zod7.z.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
23545
+ mode: import_zod7.z.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
23428
23546
  },
23429
23547
  async ({ title, type, artifact_id, status, phase, mode }) => {
23430
23548
  const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -23445,15 +23563,15 @@ async function startMcpServer() {
23445
23563
  "ask_user",
23446
23564
  "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.",
23447
23565
  {
23448
- questions: import_zod6.z.array(
23449
- import_zod6.z.object({
23450
- question: import_zod6.z.string().describe("Full question text ending with ?"),
23451
- header: import_zod6.z.string().max(24).optional().describe("Short label shown above the question"),
23452
- multiSelect: import_zod6.z.boolean().optional().describe("Allow selecting multiple options"),
23453
- options: import_zod6.z.array(
23454
- import_zod6.z.object({
23455
- label: import_zod6.z.string(),
23456
- description: import_zod6.z.string().optional().describe("What this option means / when to choose it (strongly preferred)")
23566
+ questions: import_zod7.z.array(
23567
+ import_zod7.z.object({
23568
+ question: import_zod7.z.string().describe("Full question text ending with ?"),
23569
+ header: import_zod7.z.string().max(24).optional().describe("Short label shown above the question"),
23570
+ multiSelect: import_zod7.z.boolean().optional().describe("Allow selecting multiple options"),
23571
+ options: import_zod7.z.array(
23572
+ import_zod7.z.object({
23573
+ label: import_zod7.z.string(),
23574
+ description: import_zod7.z.string().optional().describe("What this option means / when to choose it (strongly preferred)")
23457
23575
  })
23458
23576
  ).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
23459
23577
  })
@@ -23472,9 +23590,9 @@ async function startMcpServer() {
23472
23590
  "present_plan",
23473
23591
  "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.",
23474
23592
  {
23475
- title: import_zod6.z.string().optional().describe("Short plan title (defaults to Plan)"),
23476
- content: import_zod6.z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
23477
- thread_id: import_zod6.z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
23593
+ title: import_zod7.z.string().optional().describe("Short plan title (defaults to Plan)"),
23594
+ content: import_zod7.z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
23595
+ thread_id: import_zod7.z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
23478
23596
  },
23479
23597
  async ({ title, content, thread_id }) => {
23480
23598
  const { writePlanFile: writePlanFile2 } = await Promise.resolve().then(() => (init_plan_file(), plan_file_exports));
@@ -23497,15 +23615,15 @@ async function startMcpServer() {
23497
23615
  "present_schema",
23498
23616
  "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.",
23499
23617
  {
23500
- title: import_zod6.z.string().describe("Pane title"),
23501
- mode: import_zod6.z.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
23502
- datasource: import_zod6.z.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
23503
- resource_id: import_zod6.z.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
23504
- record_id: import_zod6.z.string().optional().describe("Record id when opening form mode"),
23505
- resource: import_zod6.z.record(import_zod6.z.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
23506
- record: import_zod6.z.record(import_zod6.z.unknown()).optional().describe("Inline record { id, data, published_at? }"),
23507
- records: import_zod6.z.array(import_zod6.z.record(import_zod6.z.unknown())).optional().describe("Inline records for table mode"),
23508
- pane_id: import_zod6.z.string().optional().describe("Stable pane id across updates")
23618
+ title: import_zod7.z.string().describe("Pane title"),
23619
+ mode: import_zod7.z.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
23620
+ datasource: import_zod7.z.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
23621
+ resource_id: import_zod7.z.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
23622
+ record_id: import_zod7.z.string().optional().describe("Record id when opening form mode"),
23623
+ resource: import_zod7.z.record(import_zod7.z.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
23624
+ record: import_zod7.z.record(import_zod7.z.unknown()).optional().describe("Inline record { id, data, published_at? }"),
23625
+ records: import_zod7.z.array(import_zod7.z.record(import_zod7.z.unknown())).optional().describe("Inline records for table mode"),
23626
+ pane_id: import_zod7.z.string().optional().describe("Stable pane id across updates")
23509
23627
  },
23510
23628
  async (args) => {
23511
23629
  const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -23526,10 +23644,10 @@ async function startMcpServer() {
23526
23644
  "present_files",
23527
23645
  "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.",
23528
23646
  {
23529
- title: import_zod6.z.string().optional().describe("Pane title (default: Files)"),
23530
- datasource: import_zod6.z.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
23531
- path: import_zod6.z.string().optional().describe("Initial folder path (e.g. public)"),
23532
- pane_id: import_zod6.z.string().optional().describe("Stable pane id across updates")
23647
+ title: import_zod7.z.string().optional().describe("Pane title (default: Files)"),
23648
+ datasource: import_zod7.z.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
23649
+ path: import_zod7.z.string().optional().describe("Initial folder path (e.g. public)"),
23650
+ pane_id: import_zod7.z.string().optional().describe("Stable pane id across updates")
23533
23651
  },
23534
23652
  async (args) => {
23535
23653
  const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -23548,8 +23666,8 @@ async function startMcpServer() {
23548
23666
  "wait_for_job",
23549
23667
  "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.",
23550
23668
  {
23551
- id: import_zod6.z.string().describe("Detached job id (same kebab-case id passed to detached-job.js start)"),
23552
- timeoutMs: import_zod6.z.number().optional()
23669
+ id: import_zod7.z.string().describe("Detached job id (same kebab-case id passed to detached-job.js start)"),
23670
+ timeoutMs: import_zod7.z.number().optional()
23553
23671
  },
23554
23672
  async ({ id, timeoutMs }) => {
23555
23673
  const result = await waitForDetachedJob(process.cwd(), id, {
@@ -23570,7 +23688,7 @@ async function startMcpServer() {
23570
23688
  "set_caffeinate",
23571
23689
  "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.",
23572
23690
  {
23573
- enabled: import_zod6.z.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
23691
+ enabled: import_zod7.z.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
23574
23692
  },
23575
23693
  async ({ enabled }) => {
23576
23694
  const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
@@ -23608,18 +23726,18 @@ async function startMcpServer() {
23608
23726
  "create_thread",
23609
23727
  `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.`,
23610
23728
  {
23611
- sourceType: import_zod6.z.enum(["branch", "pr", "ticket"]),
23612
- sourceRef: import_zod6.z.string(),
23613
- agent: import_zod6.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
23614
- model: import_zod6.z.string().nullable().optional().describe(
23729
+ sourceType: import_zod7.z.enum(["branch", "pr", "ticket"]),
23730
+ sourceRef: import_zod7.z.string(),
23731
+ agent: import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
23732
+ model: import_zod7.z.string().nullable().optional().describe(
23615
23733
  `Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
23616
23734
  ),
23617
- repoPath: import_zod6.z.string(),
23618
- title: import_zod6.z.string().optional(),
23619
- cowboy: import_zod6.z.boolean().optional().describe(
23735
+ repoPath: import_zod7.z.string(),
23736
+ title: import_zod7.z.string().optional(),
23737
+ cowboy: import_zod7.z.boolean().optional().describe(
23620
23738
  "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."
23621
23739
  ),
23622
- parentThreadId: import_zod6.z.string().optional().describe(
23740
+ parentThreadId: import_zod7.z.string().optional().describe(
23623
23741
  "Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
23624
23742
  )
23625
23743
  },
@@ -23635,10 +23753,10 @@ async function startMcpServer() {
23635
23753
  "start_board_card",
23636
23754
  "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.",
23637
23755
  {
23638
- kind: import_zod6.z.enum(["ticket", "pr", "branch"]),
23639
- ref: import_zod6.z.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
23640
- repoPath: import_zod6.z.string().describe("Workspace path from list_workspaces / list_board"),
23641
- title: import_zod6.z.string().optional()
23756
+ kind: import_zod7.z.enum(["ticket", "pr", "branch"]),
23757
+ ref: import_zod7.z.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
23758
+ repoPath: import_zod7.z.string().describe("Workspace path from list_workspaces / list_board"),
23759
+ title: import_zod7.z.string().optional()
23642
23760
  },
23643
23761
  async ({ kind, ref, repoPath, title }) => {
23644
23762
  const root = await resolveRepoRoot(repoPath);
@@ -23742,9 +23860,9 @@ async function startMcpServer() {
23742
23860
  "send_to_thread",
23743
23861
  '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.',
23744
23862
  {
23745
- ref: import_zod6.z.string(),
23746
- prompt: import_zod6.z.string(),
23747
- force_stop: import_zod6.z.boolean().optional()
23863
+ ref: import_zod7.z.string(),
23864
+ prompt: import_zod7.z.string(),
23865
+ force_stop: import_zod7.z.boolean().optional()
23748
23866
  },
23749
23867
  async ({ ref, prompt, force_stop }) => {
23750
23868
  if (force_stop) {
@@ -23773,8 +23891,8 @@ async function startMcpServer() {
23773
23891
  "wait_for_turn",
23774
23892
  "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).",
23775
23893
  {
23776
- ref: import_zod6.z.string(),
23777
- timeoutMs: import_zod6.z.number().optional()
23894
+ ref: import_zod7.z.string(),
23895
+ timeoutMs: import_zod7.z.number().optional()
23778
23896
  },
23779
23897
  async ({ ref, timeoutMs }) => {
23780
23898
  const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
@@ -23804,7 +23922,7 @@ async function startMcpServer() {
23804
23922
  server.tool(
23805
23923
  "get_turn_result",
23806
23924
  "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).",
23807
- { ref: import_zod6.z.string() },
23925
+ { ref: import_zod7.z.string() },
23808
23926
  async ({ ref }) => {
23809
23927
  const result = orch.getTurnResult(ref);
23810
23928
  return {
@@ -23825,8 +23943,8 @@ async function startMcpServer() {
23825
23943
  "stop_thread",
23826
23944
  "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.",
23827
23945
  {
23828
- ref: import_zod6.z.string(),
23829
- force: import_zod6.z.boolean().optional()
23946
+ ref: import_zod7.z.string(),
23947
+ force: import_zod7.z.boolean().optional()
23830
23948
  },
23831
23949
  async ({ ref, force }) => {
23832
23950
  const t = orch.getThread(ref);
@@ -23856,7 +23974,7 @@ async function startMcpServer() {
23856
23974
  server.tool(
23857
23975
  "archive_thread",
23858
23976
  "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.",
23859
- { ref: import_zod6.z.string() },
23977
+ { ref: import_zod7.z.string() },
23860
23978
  async ({ ref }) => {
23861
23979
  const t = orch.getThread(ref);
23862
23980
  if (!t) {
@@ -23889,7 +24007,7 @@ async function startMcpServer() {
23889
24007
  server.tool(
23890
24008
  "restore_thread",
23891
24009
  "Restore an archived thread (recreates worktree from branch when needed)",
23892
- { ref: import_zod6.z.string() },
24010
+ { ref: import_zod7.z.string() },
23893
24011
  async ({ ref }) => {
23894
24012
  try {
23895
24013
  const restored = await orch.restore(ref);
@@ -23915,8 +24033,8 @@ async function startMcpServer() {
23915
24033
  "get_diff",
23916
24034
  "Compact diff summary (capped hunks, paginated)",
23917
24035
  {
23918
- ref: import_zod6.z.string(),
23919
- maxFiles: import_zod6.z.number().optional()
24036
+ ref: import_zod7.z.string(),
24037
+ maxFiles: import_zod7.z.number().optional()
23920
24038
  },
23921
24039
  async ({ ref, maxFiles }) => {
23922
24040
  const summary = await orch.diffSummary(ref);
@@ -23936,7 +24054,7 @@ async function startMcpServer() {
23936
24054
  server.tool(
23937
24055
  "get_pr_checks",
23938
24056
  "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.",
23939
- { ref: import_zod6.z.string().describe("Worktree thread id/ref") },
24057
+ { ref: import_zod7.z.string().describe("Worktree thread id/ref") },
23940
24058
  async ({ ref }) => {
23941
24059
  try {
23942
24060
  const checks = await orch.getPrChecks(ref);
@@ -23952,7 +24070,7 @@ async function startMcpServer() {
23952
24070
  server.tool(
23953
24071
  "request_review",
23954
24072
  '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.',
23955
- { ref: import_zod6.z.string().describe("Worktree thread id/ref to review") },
24073
+ { ref: import_zod7.z.string().describe("Worktree thread id/ref to review") },
23956
24074
  async ({ ref }) => {
23957
24075
  try {
23958
24076
  const tab = await orch.requestReview(ref);
@@ -23981,8 +24099,8 @@ async function startMcpServer() {
23981
24099
  "ask_git",
23982
24100
  "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.",
23983
24101
  {
23984
- ref: import_zod6.z.string().describe("Worktree thread id/ref"),
23985
- action: import_zod6.z.enum(AGENT_GIT_ACTIONS).describe(
24102
+ ref: import_zod7.z.string().describe("Worktree thread id/ref"),
24103
+ action: import_zod7.z.enum(AGENT_GIT_ACTIONS).describe(
23986
24104
  "commit-push | create-draft | create-web | resolve-conflicts | merge"
23987
24105
  )
23988
24106
  },
@@ -24027,7 +24145,7 @@ async function startMcpServer() {
24027
24145
  }
24028
24146
  }
24029
24147
  );
24030
- const agentEnum = import_zod6.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
24148
+ const agentEnum = import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
24031
24149
  server.tool(
24032
24150
  "list_models",
24033
24151
  "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.",
@@ -24050,11 +24168,11 @@ async function startMcpServer() {
24050
24168
  "fork_worktree",
24051
24169
  "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.",
24052
24170
  {
24053
- ref: import_zod6.z.string().describe("Worktree thread id/ref to fork"),
24054
- through_index: import_zod6.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
24171
+ ref: import_zod7.z.string().describe("Worktree thread id/ref to fork"),
24172
+ through_index: import_zod7.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
24055
24173
  agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
24056
- model: import_zod6.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
24057
- title: import_zod6.z.string().optional()
24174
+ model: import_zod7.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
24175
+ title: import_zod7.z.string().optional()
24058
24176
  },
24059
24177
  async ({ ref, through_index, agent, model, title }) => {
24060
24178
  try {
@@ -24095,11 +24213,11 @@ async function startMcpServer() {
24095
24213
  "fork_chat",
24096
24214
  "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.",
24097
24215
  {
24098
- ref: import_zod6.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
24099
- through_index: import_zod6.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
24216
+ ref: import_zod7.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
24217
+ through_index: import_zod7.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
24100
24218
  agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
24101
- model: import_zod6.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
24102
- title: import_zod6.z.string().optional()
24219
+ model: import_zod7.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
24220
+ title: import_zod7.z.string().optional()
24103
24221
  },
24104
24222
  async ({ ref, through_index, agent, model, title }) => {
24105
24223
  try {
@@ -24145,8 +24263,8 @@ async function startMcpServer() {
24145
24263
  "run_dev_script",
24146
24264
  "Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
24147
24265
  {
24148
- ref: import_zod6.z.string(),
24149
- name: import_zod6.z.string().optional()
24266
+ ref: import_zod7.z.string(),
24267
+ name: import_zod7.z.string().optional()
24150
24268
  },
24151
24269
  async ({ ref, name }) => {
24152
24270
  const result = await orch.startDev(ref, name);
@@ -24168,7 +24286,7 @@ async function startMcpServer() {
24168
24286
  server.tool(
24169
24287
  "list_run_scripts",
24170
24288
  "List named run scripts available for a thread",
24171
- { ref: import_zod6.z.string() },
24289
+ { ref: import_zod7.z.string() },
24172
24290
  async ({ ref }) => {
24173
24291
  const scripts = orch.listThreadRunScripts(ref);
24174
24292
  const active = orch.getActiveRuns(ref);
@@ -24186,8 +24304,8 @@ async function startMcpServer() {
24186
24304
  "stop_dev_script",
24187
24305
  "Stop a running script for a thread (all scripts if name omitted)",
24188
24306
  {
24189
- ref: import_zod6.z.string(),
24190
- name: import_zod6.z.string().optional()
24307
+ ref: import_zod7.z.string(),
24308
+ name: import_zod7.z.string().optional()
24191
24309
  },
24192
24310
  async ({ ref, name }) => {
24193
24311
  orch.stopDev(ref, name);
@@ -24197,7 +24315,7 @@ async function startMcpServer() {
24197
24315
  server.tool(
24198
24316
  "run_setup",
24199
24317
  "Re-run workspace setup (Sideboard/Conductor settings, .cursor/worktrees.json, or script/setup). New worktrees already run this automatically.",
24200
- { ref: import_zod6.z.string() },
24318
+ { ref: import_zod7.z.string() },
24201
24319
  async ({ ref }) => {
24202
24320
  const result = await orch.runSetup(ref);
24203
24321
  return {
@@ -24208,7 +24326,7 @@ async function startMcpServer() {
24208
24326
  server.tool(
24209
24327
  "add_workspace",
24210
24328
  "Register a git repo as a Sideboard workspace",
24211
- { repoPath: import_zod6.z.string() },
24329
+ { repoPath: import_zod7.z.string() },
24212
24330
  async ({ repoPath }) => {
24213
24331
  const ws = await orch.addWorkspace(repoPath);
24214
24332
  return { content: [{ type: "text", text: JSON.stringify(ws) }] };
@@ -24217,7 +24335,7 @@ async function startMcpServer() {
24217
24335
  server.tool(
24218
24336
  "remove_workspace",
24219
24337
  "Unregister a Sideboard workspace (does not archive threads)",
24220
- { repoPath: import_zod6.z.string() },
24338
+ { repoPath: import_zod7.z.string() },
24221
24339
  async ({ repoPath }) => {
24222
24340
  orch.removeWorkspace(repoPath);
24223
24341
  return { content: [{ type: "text", text: "ok" }] };
@@ -24227,12 +24345,12 @@ async function startMcpServer() {
24227
24345
  "fanout",
24228
24346
  "Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
24229
24347
  {
24230
- prompt: import_zod6.z.string(),
24231
- agents: import_zod6.z.array(import_zod6.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
24232
- repoPath: import_zod6.z.string(),
24233
- sourceType: import_zod6.z.enum(["branch", "pr", "ticket"]).optional(),
24234
- sourceRef: import_zod6.z.string().optional(),
24235
- title: import_zod6.z.string().optional()
24348
+ prompt: import_zod7.z.string(),
24349
+ agents: import_zod7.z.array(import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
24350
+ repoPath: import_zod7.z.string(),
24351
+ sourceType: import_zod7.z.enum(["branch", "pr", "ticket"]).optional(),
24352
+ sourceRef: import_zod7.z.string().optional(),
24353
+ title: import_zod7.z.string().optional()
24236
24354
  },
24237
24355
  async (args) => {
24238
24356
  const threads = await orch.bestOfN(args);
@@ -24259,8 +24377,8 @@ async function startMcpServer() {
24259
24377
  "list_branches",
24260
24378
  "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).",
24261
24379
  {
24262
- repoPath: import_zod6.z.string(),
24263
- unmergedOnly: import_zod6.z.boolean().optional()
24380
+ repoPath: import_zod7.z.string(),
24381
+ unmergedOnly: import_zod7.z.boolean().optional()
24264
24382
  },
24265
24383
  async ({ repoPath, unmergedOnly }) => {
24266
24384
  const root = await resolveRepoRoot(repoPath);
@@ -24281,19 +24399,19 @@ async function startMcpServer() {
24281
24399
  "list_prs",
24282
24400
  '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.',
24283
24401
  {
24284
- repoPath: import_zod6.z.string(),
24285
- query: import_zod6.z.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
24286
- queue: import_zod6.z.enum(["review", "mine", "approved", "changes"]).optional().describe(
24402
+ repoPath: import_zod7.z.string(),
24403
+ query: import_zod7.z.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
24404
+ queue: import_zod7.z.enum(["review", "mine", "approved", "changes"]).optional().describe(
24287
24405
  'review = unclaimed eng-review inbox (use for "get me N tickets to review"). mine = review-requested:@me. approved / changes = eng-approved / eng-requested-changes.'
24288
24406
  ),
24289
- state: import_zod6.z.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
24290
- label: import_zod6.z.string().optional().describe(
24407
+ state: import_zod7.z.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
24408
+ label: import_zod7.z.string().optional().describe(
24291
24409
  "GitHub label / workflow tag. Comma-separated AND. Examples: eng-review, eng-approved, eng-requested-changes"
24292
24410
  ),
24293
- reviewer: import_zod6.z.string().optional().describe(
24411
+ reviewer: import_zod7.z.string().optional().describe(
24294
24412
  "me (review requested of you), unassigned (no individual reviewer; team queues like engineering-team still count), all, or a GitHub login"
24295
24413
  ),
24296
- limit: import_zod6.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
24414
+ limit: import_zod7.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
24297
24415
  },
24298
24416
  async ({ repoPath, query, queue, state, label, reviewer, limit }) => {
24299
24417
  const root = await resolveRepoRoot(repoPath);
@@ -24325,7 +24443,7 @@ async function startMcpServer() {
24325
24443
  server.tool(
24326
24444
  "get_pr_stack",
24327
24445
  "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).",
24328
- { ref: import_zod6.z.string() },
24446
+ { ref: import_zod7.z.string() },
24329
24447
  async ({ ref }) => {
24330
24448
  const stack = await orch.getPrStack(ref);
24331
24449
  return {
@@ -24337,8 +24455,8 @@ async function startMcpServer() {
24337
24455
  "open_pr_stack_layers",
24338
24456
  "Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
24339
24457
  {
24340
- ref: import_zod6.z.string(),
24341
- layer: import_zod6.z.number().int().positive().optional()
24458
+ ref: import_zod7.z.string(),
24459
+ layer: import_zod7.z.number().int().positive().optional()
24342
24460
  },
24343
24461
  async ({ ref, layer }) => {
24344
24462
  const result = await orch.openPrStackLayers(ref, { layer });
@@ -24372,9 +24490,9 @@ async function startMcpServer() {
24372
24490
  "add_stack_layer",
24373
24491
  "Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
24374
24492
  {
24375
- ref: import_zod6.z.string(),
24376
- branchName: import_zod6.z.string(),
24377
- title: import_zod6.z.string().optional()
24493
+ ref: import_zod7.z.string(),
24494
+ branchName: import_zod7.z.string(),
24495
+ title: import_zod7.z.string().optional()
24378
24496
  },
24379
24497
  async ({ ref, branchName, title }) => {
24380
24498
  const result = await orch.addStackLayer(ref, branchName, { title });
@@ -24403,11 +24521,11 @@ async function startMcpServer() {
24403
24521
  "create_pr_stack",
24404
24522
  "Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
24405
24523
  {
24406
- repoPath: import_zod6.z.string(),
24407
- branches: import_zod6.z.array(import_zod6.z.string()).min(1),
24408
- agent: import_zod6.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
24409
- base: import_zod6.z.string().optional(),
24410
- title: import_zod6.z.string().optional()
24524
+ repoPath: import_zod7.z.string(),
24525
+ branches: import_zod7.z.array(import_zod7.z.string()).min(1),
24526
+ agent: import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
24527
+ base: import_zod7.z.string().optional(),
24528
+ title: import_zod7.z.string().optional()
24411
24529
  },
24412
24530
  async (args) => {
24413
24531
  const result = await orch.createPrStack({
@@ -24446,10 +24564,10 @@ async function startMcpServer() {
24446
24564
  "list_issues",
24447
24565
  "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.",
24448
24566
  {
24449
- repoPath: import_zod6.z.string(),
24450
- query: import_zod6.z.string().optional().describe("Search title, identifier, or description"),
24451
- assignee: import_zod6.z.string().optional().describe("me (Linear default), unassigned, all, a user id, or a GitHub login"),
24452
- limit: import_zod6.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
24567
+ repoPath: import_zod7.z.string(),
24568
+ query: import_zod7.z.string().optional().describe("Search title, identifier, or description"),
24569
+ assignee: import_zod7.z.string().optional().describe("me (Linear default), unassigned, all, a user id, or a GitHub login"),
24570
+ limit: import_zod7.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
24453
24571
  },
24454
24572
  async ({ repoPath, query, assignee, limit }) => {
24455
24573
  const root = await resolveRepoRoot(repoPath);