@sideboard-ai/core 0.1.158 → 0.1.159

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/{abletime-BEIDJG7W.js → abletime-HOQBDQC4.js} +2 -2
  2. package/dist/{abletime-GWZE6OYM.js → abletime-LK7GV556.js} +2 -2
  3. package/dist/{agents-E3ZPWZRS.js → agents-5HY76BCR.js} +4 -4
  4. package/dist/{agents-RGE7PSUY.js → agents-67ZBH5OE.js} +4 -4
  5. package/dist/{app-settings-VRQI3FIE.js → app-settings-BJ5GFAE5.js} +13 -17
  6. package/dist/{app-settings-H3INSR67.js → app-settings-YWJ4MVYM.js} +13 -17
  7. package/dist/{chunk-DVJBO3M7.js → chunk-AGLPYXDT.js} +1 -1
  8. package/dist/{chunk-EPQIOAAY.js → chunk-DWUOKF3W.js} +7 -5
  9. package/dist/{chunk-D7EGJ3EI.js → chunk-EQIJ5MHR.js} +119 -110
  10. package/dist/{chunk-3BWVOUQG.js → chunk-FLG4A5FJ.js} +119 -110
  11. package/dist/{chunk-LQMFCS54.js → chunk-KEDHHXKW.js} +3 -13
  12. package/dist/{chunk-A7QXLYBN.js → chunk-MEZXWELT.js} +3 -13
  13. package/dist/{chunk-CVR4RJ6G.js → chunk-O7RWWPMR.js} +12 -13
  14. package/dist/{chunk-KYKNFJK5.js → chunk-OYOXLY6B.js} +39 -20
  15. package/dist/{chunk-3SUZMPGL.js → chunk-SIMFKJIB.js} +12 -13
  16. package/dist/{chunk-FBFIOVDH.js → chunk-TGU3LSDG.js} +38 -19
  17. package/dist/{chunk-N27GVFZY.js → chunk-UBMCEZRE.js} +1 -1
  18. package/dist/{chunk-BLRHO7XQ.js → chunk-V4R6L2ZL.js} +10 -6
  19. package/dist/{coordinator-prompt-GZESIQNH.js → coordinator-prompt-7NKY7QRK.js} +3 -3
  20. package/dist/{coordinator-prompt-26IJU2AV.js → coordinator-prompt-CNI6AK65.js} +3 -3
  21. package/dist/{global-workspace-REI55FZ6.js → global-workspace-FKAJZHO5.js} +3 -3
  22. package/dist/{global-workspace-5BIW26YR.js → global-workspace-IJYCUO4M.js} +3 -3
  23. package/dist/index.cjs +588 -472
  24. package/dist/index.d.cts +62 -57
  25. package/dist/index.d.ts +62 -57
  26. package/dist/index.js +322 -230
  27. package/dist/mcp/run-stdio.cjs +517 -399
  28. package/dist/mcp/run-stdio.js +262 -163
  29. package/dist/{orchestrator-ZVW2AC6B.js → orchestrator-KFLS5BTP.js} +5 -5
  30. package/dist/{orchestrator-J727WIJY.js → orchestrator-NSVP7DJH.js} +5 -5
  31. package/dist/{workspaces-NYSM2EPX.js → workspaces-D2M3AM2M.js} +3 -3
  32. package/dist/{workspaces-OCEPZXFD.js → workspaces-OFSDJ7PZ.js} +3 -3
  33. package/dist/{worktree-VTMBZKY6.js → worktree-BQE5KLRY.js} +2 -2
  34. package/dist/{worktree-T4SWFSTT.js → worktree-EA6AD6D6.js} +2 -2
  35. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -578,27 +578,27 @@ var init_nested_electron_env = __esm({
578
578
  });
579
579
 
580
580
  // src/store/account-profile.ts
581
- function accountRoleLabel(role) {
582
- const known = ACCOUNT_ROLE_LABELS[role];
581
+ function legacyRoleLabel(role) {
582
+ const known = LEGACY_ROLE_LABELS[role];
583
583
  if (known) return known;
584
584
  return role.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
585
585
  }
586
- function normalizeAccountRole(value) {
586
+ function normalizeLegacyRole(value) {
587
587
  const key = (value ?? "").trim().toLowerCase().replace(/[_\s]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
588
588
  if (key === "both") return void 0;
589
589
  if (key.length < 2 || key.length > 40) return void 0;
590
590
  if (!/^[a-z][a-z0-9-]*$/.test(key)) return void 0;
591
591
  return key;
592
592
  }
593
- function normalizeAccountRoles(roles, legacyRole) {
593
+ function legacyRoleLabels(roles, legacyRole) {
594
594
  const raw = Array.isArray(roles) ? roles : [];
595
595
  const out = [];
596
596
  const seen = /* @__PURE__ */ new Set();
597
597
  const push = (value) => {
598
- const role = normalizeAccountRole(typeof value === "string" ? value : "");
599
- if (!role || seen.has(role) || out.length >= ACCOUNT_ROLE_MAX) return;
598
+ const role = normalizeLegacyRole(typeof value === "string" ? value : "");
599
+ if (!role || seen.has(role) || out.length >= 16) return;
600
600
  seen.add(role);
601
- out.push(role);
601
+ out.push(legacyRoleLabel(role));
602
602
  };
603
603
  for (const item of raw) push(item);
604
604
  if (out.length === 0) push(legacyRole);
@@ -608,117 +608,83 @@ function normalizeProfileNotes(value) {
608
608
  if (typeof value !== "string") return "";
609
609
  return value.replace(/\r\n/g, "\n").trim().slice(0, PROFILE_NOTES_MAX);
610
610
  }
611
- function reviewTeamHintsForRoles(roles) {
612
- const seen = /* @__PURE__ */ new Set();
613
- const out = [];
614
- for (const role of roles) {
615
- const hints = ROLE_TEAM_HINTS[role] ?? teamHintsForCustomRole(role);
616
- for (const hint of hints) {
617
- const key = hint.toLowerCase();
618
- if (seen.has(key)) continue;
619
- seen.add(key);
620
- out.push(hint);
621
- }
622
- }
623
- return out;
624
- }
625
- function teamHintsForCustomRole(role) {
626
- const slug = role.trim().toLowerCase();
627
- if (!slug) return [];
628
- if (slug.endsWith("-team") || slug.endsWith("-squad")) return [slug];
629
- return [`${slug}-team`, slug];
611
+ function foldLegacyRolesIntoNotes(roles, legacyRole, notes) {
612
+ const text7 = normalizeProfileNotes(notes);
613
+ const labels = legacyRoleLabels(roles, legacyRole);
614
+ if (!labels.length) return text7;
615
+ if (text7.toLowerCase().startsWith("roles:")) return text7;
616
+ const prefix = `Roles: ${labels.join(", ")}`;
617
+ return text7 ? `${prefix}. ${text7}` : prefix;
630
618
  }
631
619
  function resolveAccountProfile(input) {
632
620
  return resolveViewerProfile(input);
633
621
  }
634
622
  function resolveViewerProfile(account, project) {
635
- const accountRoles = normalizeAccountRoles(account?.roles, account?.role);
636
- const projectRoles = normalizeAccountRoles(project?.roles, project?.role);
637
- const rolesFromProject = projectRoles.length > 0;
638
- const roles = rolesFromProject ? projectRoles : accountRoles;
639
- const accountNotes = normalizeProfileNotes(account?.notes);
640
- const projectNotes = normalizeProfileNotes(project?.notes);
641
- const notes = [accountNotes, projectNotes].filter(Boolean).join("\n");
623
+ const accountNotes = foldLegacyRolesIntoNotes(
624
+ account?.roles,
625
+ account?.role,
626
+ account?.notes
627
+ );
628
+ const projectNotes = foldLegacyRolesIntoNotes(
629
+ project?.roles,
630
+ project?.role,
631
+ project?.notes
632
+ );
642
633
  return {
643
- roles,
644
- roleLabels: roles.map((role) => accountRoleLabel(role)),
645
- reviewTeamHints: reviewTeamHintsForRoles(roles),
646
- notes,
634
+ notes: [accountNotes, projectNotes].filter(Boolean).join("\n"),
647
635
  accountNotes,
648
- projectNotes,
649
- rolesFromProject
636
+ projectNotes
650
637
  };
651
638
  }
652
- function preferTeamsForRole(viewerTeams, hints) {
653
- const teams = viewerTeams.map((t) => t.trim()).filter(Boolean);
654
- const want = hints.map((t) => t.trim()).filter(Boolean);
655
- if (!want.length) return teams;
656
- if (!teams.length) return want;
657
- const hintKeys = want.map((t) => t.toLowerCase());
658
- const matched = teams.filter((team) => {
659
- const key = team.toLowerCase();
660
- return hintKeys.some((hint) => key === hint || key.includes(hint) || hint.includes(key));
661
- });
662
- return matched.length ? matched : teams;
663
- }
664
639
  function compactNotes(notes, max = 160) {
665
640
  const oneLine = notes.replace(/\s+/g, " ").trim();
666
641
  if (oneLine.length <= max) return oneLine;
667
642
  return `${oneLine.slice(0, max - 1).trimEnd()}\u2026`;
668
643
  }
669
644
  function formatAccountProfilePlaybookLine(profile) {
670
- if (profile.roleLabels.length === 0 && !profile.notes) return "";
671
- const parts = [];
672
- if (profile.roleLabels.length) {
673
- parts.push(
674
- `roles ${profile.roleLabels.join(", ")} (tickets to work on / PRs to review; prefer those teams)`
675
- );
676
- }
677
- if (profile.notes) {
678
- parts.push(`notes: ${compactNotes(profile.notes)}`);
679
- }
680
- 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.`;
645
+ if (!profile.accountNotes) return "";
646
+ 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).`;
681
647
  }
682
648
  function formatProjectProfilePlaybookLines(projects) {
683
649
  const lines = projects.map((project) => {
684
- const roles = (project.roleLabels ?? []).filter(Boolean);
685
650
  const notes = normalizeProfileNotes(project.notes);
686
- if (!roles.length && !notes) return "";
687
- const bits = [];
688
- if (roles.length) bits.push(`roles ${roles.join(", ")}`);
689
- if (notes) bits.push(compactNotes(notes, 120));
690
- return `- ${project.name}: ${bits.join(". ")}`;
651
+ if (!notes) return "";
652
+ return `- ${project.name}: ${compactNotes(notes, 120)}`;
691
653
  }).filter(Boolean);
692
654
  if (!lines.length) return "";
693
- return ["Project overrides (Settings \u2192 Projects; empty roles inherit account):", ...lines].join(
694
- "\n"
695
- );
655
+ return [
656
+ "Project context (Settings \u2192 Projects; adds to account context):",
657
+ ...lines
658
+ ].join("\n");
696
659
  }
697
660
  function formatWorkspaceProfileSuffix(profile) {
698
- const bits = [];
699
- if (profile.roles.length) bits.push(`roles:${profile.roles.join(",")}`);
700
- if (profile.projectNotes) bits.push(`notes:${compactNotes(profile.projectNotes, 80)}`);
701
- return bits.length ? ` ${bits.join(" ")}` : "";
661
+ if (!profile.projectNotes) return "";
662
+ return ` context:${compactNotes(profile.projectNotes, 80)}`;
702
663
  }
703
- var ACCOUNT_ROLE_PRESETS, ACCOUNT_ROLES, ACCOUNT_ROLE_MAX, PROFILE_NOTES_MAX, ACCOUNT_ROLE_LABELS, ROLE_TEAM_HINTS;
664
+ function formatViewerContextDirective(profile) {
665
+ const account = profile.accountNotes || "(empty)";
666
+ const project = profile.projectNotes || "(empty \u2014 account context applies)";
667
+ return [
668
+ "Viewer context (Settings \u2192 Agents / Projects) \u2014 how to find this user\u2019s tickets and review PRs:",
669
+ `- Account: ${compactNotes(profile.accountNotes || account, 240)}`,
670
+ `- This project: ${compactNotes(profile.projectNotes || project, 240)}`,
671
+ "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."
672
+ ].join("\n");
673
+ }
674
+ function formatViewerContextReminder() {
675
+ return "Viewer context: get_viewer_context to read; update_viewer_context only after ask_user confirms (Save this context / Do not save).";
676
+ }
677
+ var PROFILE_NOTES_MAX, LEGACY_ROLE_LABELS;
704
678
  var init_account_profile = __esm({
705
679
  "src/store/account-profile.ts"() {
706
680
  "use strict";
707
- ACCOUNT_ROLE_PRESETS = ["engineering", "design", "product"];
708
- ACCOUNT_ROLES = ACCOUNT_ROLE_PRESETS;
709
- ACCOUNT_ROLE_MAX = 16;
710
681
  PROFILE_NOTES_MAX = 2e3;
711
- ACCOUNT_ROLE_LABELS = {
682
+ LEGACY_ROLE_LABELS = {
712
683
  engineering: "Engineering",
713
684
  design: "Design",
714
685
  product: "Product",
715
686
  qa: "QA"
716
687
  };
717
- ROLE_TEAM_HINTS = {
718
- engineering: ["engineering-team", "engineering", "eng-team"],
719
- design: ["design-team", "design"],
720
- product: ["product-team", "product"]
721
- };
722
688
  }
723
689
  });
724
690
 
@@ -982,16 +948,11 @@ var init_issue_source_labels = __esm({
982
948
  // src/store/app-settings.ts
983
949
  var app_settings_exports = {};
984
950
  __export(app_settings_exports, {
985
- ACCOUNT_ROLES: () => ACCOUNT_ROLES,
986
- ACCOUNT_ROLE_LABELS: () => ACCOUNT_ROLE_LABELS,
987
- ACCOUNT_ROLE_MAX: () => ACCOUNT_ROLE_MAX,
988
- ACCOUNT_ROLE_PRESETS: () => ACCOUNT_ROLE_PRESETS,
989
951
  FOLLOW_UP_BEHAVIORS: () => FOLLOW_UP_BEHAVIORS,
990
952
  GITHUB_GIT_AUTH_MODES: () => GITHUB_GIT_AUTH_MODES,
991
953
  HARNESS_ENV_KEYS: () => HARNESS_ENV_KEYS,
992
954
  ISSUE_SOURCE_LABELS: () => ISSUE_SOURCE_LABELS,
993
955
  PROFILE_NOTES_MAX: () => PROFILE_NOTES_MAX,
994
- accountRoleLabel: () => accountRoleLabel,
995
956
  appSettingsPath: () => appSettingsPath,
996
957
  applyAppEnvironment: () => applyAppEnvironment,
997
958
  autoArchiveOnMergeEnabled: () => autoArchiveOnMergeEnabled,
@@ -1015,9 +976,12 @@ __export(app_settings_exports, {
1015
976
  emptyPublicIntegrations: () => emptyPublicIntegrations,
1016
977
  ensureSlackDeviceIdentity: () => ensureSlackDeviceIdentity,
1017
978
  findProjectProfileKey: () => findProjectProfileKey,
979
+ foldLegacyRolesIntoNotes: () => foldLegacyRolesIntoNotes,
1018
980
  followUpBehavior: () => followUpBehavior,
1019
981
  formatAccountProfilePlaybookLine: () => formatAccountProfilePlaybookLine,
1020
982
  formatProjectProfilePlaybookLines: () => formatProjectProfilePlaybookLines,
983
+ formatViewerContextDirective: () => formatViewerContextDirective,
984
+ formatViewerContextReminder: () => formatViewerContextReminder,
1021
985
  formatWorkspaceProfileSuffix: () => formatWorkspaceProfileSuffix,
1022
986
  getAbleTimeAccessToken: () => getAbleTimeAccessToken,
1023
987
  getAbleTimeHost: () => getAbleTimeHost,
@@ -1038,7 +1002,7 @@ __export(app_settings_exports, {
1038
1002
  maxConcurrentAgents: () => maxConcurrentAgents,
1039
1003
  orchestrationQuotaFallbackAgent: () => orchestrationQuotaFallbackAgent,
1040
1004
  orchestrationQuotaOnLimit: () => orchestrationQuotaOnLimit,
1041
- preferTeamsForRole: () => preferTeamsForRole,
1005
+ readViewerContext: () => readViewerContext,
1042
1006
  resolveAccountProfile: () => resolveAccountProfile,
1043
1007
  resolveAccountProfileFromSettings: () => resolveAccountProfileFromSettings,
1044
1008
  resolveAgentExecutable: () => resolveAgentExecutable,
@@ -1048,7 +1012,6 @@ __export(app_settings_exports, {
1048
1012
  resolveThreadDefaults: () => resolveThreadDefaults,
1049
1013
  resolveViewerProfile: () => resolveViewerProfile,
1050
1014
  resolveViewerProfileForRepo: () => resolveViewerProfileForRepo,
1051
- reviewTeamHintsForRoles: () => reviewTeamHintsForRoles,
1052
1015
  saveAbleTimeConnection: () => saveAbleTimeConnection,
1053
1016
  saveAppSettings: () => saveAppSettings,
1054
1017
  saveLinearOAuth: () => saveLinearOAuth,
@@ -1065,7 +1028,8 @@ __export(app_settings_exports, {
1065
1028
  updateDefaultsSettings: () => updateDefaultsSettings,
1066
1029
  updateIntegrationsSettings: () => updateIntegrationsSettings,
1067
1030
  updateOpencodeSettings: () => updateOpencodeSettings,
1068
- updateProjectProfileSettings: () => updateProjectProfileSettings
1031
+ updateProjectProfileSettings: () => updateProjectProfileSettings,
1032
+ writeViewerContext: () => writeViewerContext
1069
1033
  });
1070
1034
  function emptyPublicIntegrations() {
1071
1035
  return {
@@ -1323,9 +1287,7 @@ function normalizeDefaults(raw) {
1323
1287
  if (typeof source.fast === "boolean") {
1324
1288
  out.fast = source.fast;
1325
1289
  }
1326
- const roles = normalizeAccountRoles(source.roles, source.role);
1327
- if (roles.length) out.roles = roles;
1328
- const notes = normalizeProfileNotes(source.notes);
1290
+ const notes = foldLegacyRolesIntoNotes(source.roles, source.role, source.notes);
1329
1291
  if (notes) out.notes = notes;
1330
1292
  return out;
1331
1293
  }
@@ -1336,11 +1298,9 @@ function normalizeProjectProfile(raw) {
1336
1298
  if (!raw || typeof raw !== "object") return void 0;
1337
1299
  const source = raw;
1338
1300
  const out = {};
1339
- const roles = normalizeAccountRoles(source.roles, source.role);
1340
- if (roles.length) out.roles = roles;
1341
- const notes = normalizeProfileNotes(source.notes);
1301
+ const notes = foldLegacyRolesIntoNotes(source.roles, source.role, source.notes);
1342
1302
  if (notes) out.notes = notes;
1343
- return out.roles || out.notes ? out : void 0;
1303
+ return out.notes ? out : void 0;
1344
1304
  }
1345
1305
  function normalizeProjects(raw) {
1346
1306
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
@@ -1899,17 +1859,13 @@ function updateDefaultsSettings(patch) {
1899
1859
  defaults.fast = Boolean(patch.fast);
1900
1860
  }
1901
1861
  }
1902
- if ("roles" in patch) {
1903
- const roles = normalizeAccountRoles(patch.roles);
1904
- if (!roles.length) delete defaults.roles;
1905
- else defaults.roles = roles;
1906
- delete defaults.role;
1907
- }
1908
1862
  if ("notes" in patch) {
1909
1863
  const notes = normalizeProfileNotes(patch.notes);
1910
1864
  if (!notes) delete defaults.notes;
1911
1865
  else defaults.notes = notes;
1912
1866
  }
1867
+ delete defaults.roles;
1868
+ delete defaults.role;
1913
1869
  return saveAppSettings({ ...current, defaults });
1914
1870
  }
1915
1871
  function updateProjectProfileSettings(repoPath, patch) {
@@ -1919,17 +1875,14 @@ function updateProjectProfileSettings(repoPath, patch) {
1919
1875
  const projects = { ...current.projects };
1920
1876
  const existing = projects[key] ?? {};
1921
1877
  const next = { ...existing };
1922
- if ("roles" in patch) {
1923
- const roles = normalizeAccountRoles(patch.roles);
1924
- if (!roles.length) delete next.roles;
1925
- else next.roles = roles;
1926
- }
1927
1878
  if ("notes" in patch) {
1928
1879
  const notes = normalizeProfileNotes(patch.notes);
1929
1880
  if (!notes) delete next.notes;
1930
1881
  else next.notes = notes;
1931
1882
  }
1932
- if (!next.roles?.length && !next.notes) delete projects[key];
1883
+ delete next.roles;
1884
+ delete next.role;
1885
+ if (!next.notes) delete projects[key];
1933
1886
  else projects[key] = next;
1934
1887
  return saveAppSettings({ ...current, projects });
1935
1888
  }
@@ -1954,6 +1907,60 @@ function resolveViewerProfileForRepo(repoPath, settings = loadAppSettings()) {
1954
1907
  const project = key ? settings.projects[key] : void 0;
1955
1908
  return resolveViewerProfile(settings.defaults, project);
1956
1909
  }
1910
+ function readViewerContext(repoPath) {
1911
+ const settings = loadAppSettings();
1912
+ const key = findProjectProfileKey(settings.projects, repoPath);
1913
+ const resolved = resolveViewerProfile(
1914
+ settings.defaults,
1915
+ key ? settings.projects[key] : void 0
1916
+ );
1917
+ return {
1918
+ account: resolved.accountNotes,
1919
+ project: resolved.projectNotes,
1920
+ combined: resolved.notes,
1921
+ repoPath: key ?? (repoPath?.trim() ? profileRepoKey(repoPath) : null)
1922
+ };
1923
+ }
1924
+ function writeViewerContext(input) {
1925
+ const proposed = normalizeProfileNotes(input.context);
1926
+ if (input.scope === "project") {
1927
+ const settings = loadAppSettings();
1928
+ const key = findProjectProfileKey(settings.projects, input.repoPath) ?? profileRepoKey(input.repoPath ?? "");
1929
+ if (!key) {
1930
+ return {
1931
+ ok: false,
1932
+ error: "repoPath required",
1933
+ message: "Pass repoPath (or run from a registered worktree) when updating project context.",
1934
+ current: "",
1935
+ proposed
1936
+ };
1937
+ }
1938
+ const current2 = resolveViewerProfileForRepo(key, settings).projectNotes;
1939
+ if (!input.confirmed) {
1940
+ return {
1941
+ ok: false,
1942
+ error: "Confirmation required",
1943
+ message: CONFIRM_PROJECT,
1944
+ current: current2,
1945
+ proposed
1946
+ };
1947
+ }
1948
+ updateProjectProfileSettings(key, { notes: proposed || null });
1949
+ return { ok: true, scope: "project", context: proposed, repoPath: key };
1950
+ }
1951
+ const current = resolveAccountProfileFromSettings().accountNotes;
1952
+ if (!input.confirmed) {
1953
+ return {
1954
+ ok: false,
1955
+ error: "Confirmation required",
1956
+ message: CONFIRM_ACCOUNT,
1957
+ current,
1958
+ proposed
1959
+ };
1960
+ }
1961
+ updateDefaultsSettings({ notes: proposed || null });
1962
+ return { ok: true, scope: "account", context: proposed };
1963
+ }
1957
1964
  function resolveThreadDefaults(settings = loadAppSettings()) {
1958
1965
  return {
1959
1966
  agent: getDefaultAgent(settings),
@@ -2300,7 +2307,7 @@ function childEnvWithAppSettings(extra) {
2300
2307
  function harnessEnvKey(harness) {
2301
2308
  return HARNESS_ENV_KEYS[harness];
2302
2309
  }
2303
- var import_node_crypto2, import_node_fs7, import_node_os3, import_node_path8, 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;
2310
+ var import_node_crypto2, import_node_fs7, import_node_os3, import_node_path8, 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;
2304
2311
  var init_app_settings = __esm({
2305
2312
  "src/store/app-settings.ts"() {
2306
2313
  "use strict";
@@ -2351,6 +2358,8 @@ var init_app_settings = __esm({
2351
2358
  projects: {},
2352
2359
  advanced: {}
2353
2360
  };
2361
+ 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.";
2362
+ 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.";
2354
2363
  DEFAULT_CLI_BIN = {
2355
2364
  claude: "claude",
2356
2365
  codex: "codex",
@@ -4373,12 +4382,12 @@ var init_worktree_labels = __esm({
4373
4382
  });
4374
4383
 
4375
4384
  // src/git/gh-errors.ts
4376
- function isGhRateLimitError(text6) {
4377
- return /API rate limit (already )?exceeded/i.test(text6) || /rate limit exceeded/i.test(text6);
4385
+ function isGhRateLimitError(text7) {
4386
+ return /API rate limit (already )?exceeded/i.test(text7) || /rate limit exceeded/i.test(text7);
4378
4387
  }
4379
- function isGhPrBodyTooLongError(text6) {
4388
+ function isGhPrBodyTooLongError(text7) {
4380
4389
  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(
4381
- text6
4390
+ text7
4382
4391
  );
4383
4392
  }
4384
4393
  function clampGithubPrBody(body, max = GITHUB_PR_BODY_SAFE_CHARS) {
@@ -4398,21 +4407,21 @@ function formatRateLimitResetHint(resetEpochSec, nowMs = Date.now()) {
4398
4407
  const hours = Math.ceil(mins / 60);
4399
4408
  return `in about ${hours} hour${hours === 1 ? "" : "s"}`;
4400
4409
  }
4401
- function omitGhBodyArg(text6) {
4402
- return text6.replace(
4410
+ function omitGhBodyArg(text7) {
4411
+ return text7.replace(
4403
4412
  /--body(?:-file)?(\s+|=)(?:'[^']*'|"[^"]*"|\S+)/g,
4404
4413
  "--body <omitted>"
4405
4414
  );
4406
4415
  }
4407
- function capGhErrorText(text6, max = 400) {
4408
- const t = text6.trim();
4416
+ function capGhErrorText(text7, max = 400) {
4417
+ const t = text7.trim();
4409
4418
  if (t.length <= max) return t;
4410
4419
  return `${t.slice(0, max - 1).trimEnd()}\u2026`;
4411
4420
  }
4412
- function extractGhErrorDetail(text6) {
4413
- const trimmed = omitGhBodyArg(text6.trim());
4421
+ function extractGhErrorDetail(text7) {
4422
+ const trimmed = omitGhBodyArg(text7.trim());
4414
4423
  if (!trimmed) return "";
4415
- if (isGhPrBodyTooLongError(text6) || isGhPrBodyTooLongError(trimmed)) {
4424
+ if (isGhPrBodyTooLongError(text7) || isGhPrBodyTooLongError(trimmed)) {
4416
4425
  return "GraphQL: body is too long (GitHub PR body / request size limit).";
4417
4426
  }
4418
4427
  const graphql = trimmed.match(/\bGraphQL:\s*(.+)$/im);
@@ -4452,9 +4461,9 @@ function formatGhLandError(raw, opts) {
4452
4461
  }
4453
4462
  return detail || "Failed to create or update pull request";
4454
4463
  }
4455
- function isPrNotMergeableError(text6) {
4464
+ function isPrNotMergeableError(text7) {
4456
4465
  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(
4457
- text6
4466
+ text7
4458
4467
  );
4459
4468
  }
4460
4469
  function formatMergePrError(raw) {
@@ -4666,8 +4675,8 @@ var init_github_agent_auth = __esm({
4666
4675
  });
4667
4676
 
4668
4677
  // src/git/stale-lock.ts
4669
- function isIndexLockError(text6) {
4670
- return /Unable to create ['"][^'"]*index\.lock['"]: File exists/i.test(text6);
4678
+ function isIndexLockError(text7) {
4679
+ return /Unable to create ['"][^'"]*index\.lock['"]: File exists/i.test(text7);
4671
4680
  }
4672
4681
  function clearStaleIndexLock(gitDir, maxAgeMs = STALE_INDEX_LOCK_MS, now = Date.now()) {
4673
4682
  const lockPath = (0, import_node_path17.join)(gitDir, "index.lock");
@@ -6090,14 +6099,7 @@ async function listPrs(repoPath, opts) {
6090
6099
  const limit = resolved.limit ?? 200;
6091
6100
  const reviewerKey = (resolved.reviewer ?? "").trim().toLowerCase();
6092
6101
  const needsUnclaimed = reviewerKey === "unassigned" || reviewerKey === "none" || reviewerKey === "null";
6093
- let profile;
6094
- try {
6095
- profile = resolveViewerProfileForRepo(repoPath);
6096
- } catch {
6097
- profile = resolveViewerProfile();
6098
- }
6099
- const fetchedTeams = needsUnclaimed ? await listGithubViewerTeamSlugs(repoPath).catch(() => []) : [];
6100
- const viewerTeams = resolved.viewerTeams ?? (needsUnclaimed ? preferTeamsForRole(fetchedTeams, profile.reviewTeamHints) : void 0);
6102
+ const viewerTeams = resolved.viewerTeams ?? (needsUnclaimed ? await listGithubViewerTeamSlugs(repoPath).catch(() => []) : void 0);
6101
6103
  const fetchLimit = prListFetchLimit(limit, resolved.reviewer);
6102
6104
  const args = buildGhPrListArgs({
6103
6105
  slug,
@@ -7430,16 +7432,15 @@ function accountRolePlaybookLine() {
7430
7432
  return "";
7431
7433
  }
7432
7434
  }
7433
- function accountRoleReminderLine() {
7435
+ function accountContextReminderLine() {
7434
7436
  let profile;
7435
7437
  try {
7436
7438
  profile = resolveAccountProfileFromSettings();
7437
7439
  } catch {
7438
7440
  return "";
7439
7441
  }
7440
- if (profile.roleLabels.length === 0 && !profile.notes) return "";
7441
- const roles = profile.roleLabels.length ? profile.roleLabels.join(", ") : "no roles";
7442
- return `- Viewer profile: ${roles}. Use Settings notes with list_issues / list_prs when they ask to find work.`;
7442
+ if (!profile.accountNotes) return "";
7443
+ 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.";
7443
7444
  }
7444
7445
  function projectProfilePlaybookBlock() {
7445
7446
  let settings;
@@ -7456,7 +7457,6 @@ function projectProfilePlaybookBlock() {
7456
7457
  const key = path2.replace(/\/+$/, "");
7457
7458
  return {
7458
7459
  name: names.get(key) ?? key.split("/").filter(Boolean).pop() ?? path2,
7459
- roleLabels: resolved.rolesFromProject ? resolved.roleLabels : [],
7460
7460
  notes: resolved.projectNotes
7461
7461
  };
7462
7462
  });
@@ -7470,7 +7470,7 @@ function coordinatorTurnReminder(opts) {
7470
7470
  `- YOUR orchestration thread id is ${opts.parentId} \u2014 pass parentThreadId="${opts.parentId}" on create_thread, or omit it.`,
7471
7471
  goal ? `- Goal / title: ${goal}` : null,
7472
7472
  accountDefaultsPlaybookLine(),
7473
- accountRoleReminderLine() || null,
7473
+ accountContextReminderLine() || null,
7474
7474
  "- 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."
7475
7475
  ].filter(Boolean).join("\n");
7476
7476
  }
@@ -7524,7 +7524,7 @@ function ensureGlobalCoordinatorCwd(opts) {
7524
7524
  "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.",
7525
7525
  "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).",
7526
7526
  "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.",
7527
- "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.",
7527
+ "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.",
7528
7528
  '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".',
7529
7529
  "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.",
7530
7530
  "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."
@@ -7578,10 +7578,10 @@ var init_coordinator_prompt = __esm({
7578
7578
  "Role: you oversee Sideboard worktree agents across registered repos. You do not live inside one of those worktrees.",
7579
7579
  "Sideboard MCP (fleet control \u2014 prefer these for status and orchestration):",
7580
7580
  "Discover:",
7581
- "- list_workspaces \u2014 registered repos (path + github slug + roles/notes when set). Use that profile to pick the right repo for tickets or reviews.",
7581
+ "- 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.",
7582
7582
  "- 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.",
7583
- '- 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.',
7584
- "- 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.",
7583
+ '- 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).',
7584
+ "- 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.",
7585
7585
  "- 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.",
7586
7586
  "- 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.",
7587
7587
  "- 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.",
@@ -7591,7 +7591,8 @@ var init_coordinator_prompt = __esm({
7591
7591
  "- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
7592
7592
  "- list_models \u2014 only when you need a specific model (rare); otherwise omit model so Account defaults apply",
7593
7593
  "- 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.",
7594
- "- 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.",
7594
+ "- 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.",
7595
+ "- 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.",
7595
7596
  "- 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.",
7596
7597
  "- 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.",
7597
7598
  "Workspaces:",
@@ -8126,8 +8127,8 @@ ${input.permalink}` : "";
8126
8127
 
8127
8128
  ${body}${link}`;
8128
8129
  }
8129
- function isSlackExternalReplyPrompt(text6) {
8130
- return text6.startsWith("Slack reply from ") && text6.includes("not a command");
8130
+ function isSlackExternalReplyPrompt(text7) {
8131
+ return text7.startsWith("Slack reply from ") && text7.includes("not a command");
8131
8132
  }
8132
8133
  function pendingSlackExternalReplies(messages) {
8133
8134
  let i = messages.length - 1;
@@ -8168,9 +8169,9 @@ async function continueSourceThread(threadId, prompt) {
8168
8169
  function listSlackOutboundWatches() {
8169
8170
  return pruneWatches(readStore3());
8170
8171
  }
8171
- function formatOwnerSlackFyi(userName, text6) {
8172
+ function formatOwnerSlackFyi(userName, text7) {
8172
8173
  const who = userName.trim() || "Someone";
8173
- const body = text6.trim() || "(no text)";
8174
+ const body = text7.trim() || "(no text)";
8174
8175
  return `${who} replied in Slack:
8175
8176
  ${body}`;
8176
8177
  }
@@ -8185,7 +8186,7 @@ async function relayExternalReply(opts) {
8185
8186
  const thread = readThread(threadId);
8186
8187
  if (!thread || thread.status === "archived") return "ignored";
8187
8188
  try {
8188
- const text6 = formatSlackExternalReplyPrompt({
8189
+ const text7 = formatSlackExternalReplyPrompt({
8189
8190
  userName: opts.reply.userName,
8190
8191
  kind: opts.watch.kind,
8191
8192
  toLabel: opts.watch.toLabel,
@@ -8194,7 +8195,7 @@ async function relayExternalReply(opts) {
8194
8195
  });
8195
8196
  appendMessage(threadId, {
8196
8197
  role: "agent",
8197
- text: text6,
8198
+ text: text7,
8198
8199
  ts: (/* @__PURE__ */ new Date()).toISOString()
8199
8200
  });
8200
8201
  } catch {
@@ -8508,20 +8509,20 @@ function looksLikeMinifiedJsDump(line) {
8508
8509
  function looksLikeNestedElectronCrash(line) {
8509
8510
  return /HasCustomHostObject|ElectronInitializeICUandStartNode/i.test(line);
8510
8511
  }
8511
- function looksLikeNativeEventLoopCrash(text6) {
8512
- return /uv_run|uv__io_poll|SpinEventLoopInternal/i.test(text6);
8512
+ function looksLikeNativeEventLoopCrash(text7) {
8513
+ return /uv_run|uv__io_poll|SpinEventLoopInternal/i.test(text7);
8513
8514
  }
8514
- function looksLikeV8Oom(text6) {
8515
+ function looksLikeV8Oom(text7) {
8515
8516
  return /javascript heap|reached heap limit|OOMErrorHandler|FatalProcessOutOfMemory|Allocation failed - JavaScript heap/i.test(
8516
- text6
8517
+ text7
8517
8518
  );
8518
8519
  }
8519
- function looksLikeHomebrewLibuvCrash(text6) {
8520
- if (!looksLikeNativeEventLoopCrash(text6)) return false;
8521
- return /Cellar\/(?:libuv|node)|libuv\.\d+\.dylib/i.test(text6);
8520
+ function looksLikeHomebrewLibuvCrash(text7) {
8521
+ if (!looksLikeNativeEventLoopCrash(text7)) return false;
8522
+ return /Cellar\/(?:libuv|node)|libuv\.\d+\.dylib/i.test(text7);
8522
8523
  }
8523
- function clipStderr(text6, maxChars) {
8524
- const trimmed = text6.trim();
8524
+ function clipStderr(text7, maxChars) {
8525
+ const trimmed = text7.trim();
8525
8526
  if (trimmed.length <= maxChars) return trimmed;
8526
8527
  return trimmed.slice(0, maxChars);
8527
8528
  }
@@ -8555,16 +8556,16 @@ function summarizeTurnStderr(tail, maxChars = 500) {
8555
8556
  const joined = (useful.length ? useful : tail).slice(-6).join("\n").trim();
8556
8557
  return clipStderr(joined, maxChars);
8557
8558
  }
8558
- function looksLikeInvalidAgentSession(text6) {
8559
- const lower = text6.trim().toLowerCase();
8559
+ function looksLikeInvalidAgentSession(text7) {
8560
+ const lower = text7.trim().toLowerCase();
8560
8561
  if (!lower) return false;
8561
8562
  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);
8562
8563
  }
8563
- function looksLikeRetryableRunnerCrash(text6) {
8564
- if (looksLikeAgentFailureMessage(text6)) return false;
8565
- if (looksLikeInvalidAgentSession(text6)) return false;
8566
- if (looksLikeV8Oom(text6)) return false;
8567
- const lower = text6.trim().toLowerCase();
8564
+ function looksLikeRetryableRunnerCrash(text7) {
8565
+ if (looksLikeAgentFailureMessage(text7)) return false;
8566
+ if (looksLikeInvalidAgentSession(text7)) return false;
8567
+ if (looksLikeV8Oom(text7)) return false;
8568
+ const lower = text7.trim().toLowerCase();
8568
8569
  if (/cannot find (?:package|module)|err_module_not_found/.test(lower)) return false;
8569
8570
  if (!lower) return true;
8570
8571
  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(
@@ -8576,12 +8577,12 @@ function shouldRetryFailedAgentTurn(detail, opts) {
8576
8577
  if (looksLikeV8Oom(detail) && opts.hasSession) return true;
8577
8578
  return looksLikeRetryableRunnerCrash(detail);
8578
8579
  }
8579
- function looksLikeAgentFailureMessage(text6) {
8580
- const lower = text6.trim().toLowerCase();
8580
+ function looksLikeAgentFailureMessage(text7) {
8581
+ const lower = text7.trim().toLowerCase();
8581
8582
  if (!lower) return false;
8582
8583
  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(
8583
8584
  lower
8584
- ) || /\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);
8585
+ ) || /\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);
8585
8586
  }
8586
8587
  function fallbackTurnFailDetail(assistantText) {
8587
8588
  const t = assistantText.trim();
@@ -9640,10 +9641,10 @@ var init_brightsy = __esm({
9640
9641
  });
9641
9642
 
9642
9643
  // src/agents/claude-mcp.ts
9643
- function parseMcpList(text6) {
9644
+ function parseMcpList(text7) {
9644
9645
  const servers = [];
9645
9646
  const seen = /* @__PURE__ */ new Set();
9646
- for (const raw of text6.split("\n")) {
9647
+ for (const raw of text7.split("\n")) {
9647
9648
  const line = raw.trim();
9648
9649
  if (!line || /^Checking MCP/i.test(line)) continue;
9649
9650
  const m = line.match(/^(.+?):\s+\S+/);
@@ -9834,8 +9835,8 @@ function isSubagentToolName(name) {
9834
9835
  if (/^(task|agent|spawn_agent)$/i.test(n)) return true;
9835
9836
  return /connectedAgentRequest/i.test(n);
9836
9837
  }
9837
- function isInternalAgentStatusText(text6) {
9838
- const t = text6.trim();
9838
+ function isInternalAgentStatusText(text7) {
9839
+ const t = text7.trim();
9839
9840
  if (!t) return false;
9840
9841
  if (/waiting for gate to pass/i.test(t)) return true;
9841
9842
  if (/^agent is running\b/i.test(t) && t.length < 160) return true;
@@ -9849,9 +9850,9 @@ function lastTextPart(parts, type) {
9849
9850
  for (let i = parts.length - 1; i >= 0; i--) {
9850
9851
  const p = parts[i];
9851
9852
  if (p.type !== type) continue;
9852
- const text6 = p.text.trim();
9853
- if (!text6 || isInternalAgentStatusText(text6)) continue;
9854
- return text6;
9853
+ const text7 = p.text.trim();
9854
+ if (!text7 || isInternalAgentStatusText(text7)) continue;
9855
+ return text7;
9855
9856
  }
9856
9857
  return "";
9857
9858
  }
@@ -9940,7 +9941,7 @@ function liveActivitySummary(parts, opts) {
9940
9941
  );
9941
9942
  const runningPoll = [...tools].reverse().find((t) => t.status === "running" && isPollWrapperToolName(t.name));
9942
9943
  const thinking = lastTextPart(parts, "thinking");
9943
- const text6 = lastTextPart(parts, "text");
9944
+ const text7 = lastTextPart(parts, "text");
9944
9945
  if (runningSubs.length > 0) {
9945
9946
  const heads = runningSubs.map(toolLabel);
9946
9947
  const head = runningSubs.length === 1 ? heads[0] : `${runningSubs.length} subagents \xB7 ${heads.slice(0, 2).join(" \xB7 ")}`;
@@ -9950,7 +9951,7 @@ function liveActivitySummary(parts, opts) {
9950
9951
  if (runningTop) return toolLabel(runningTop);
9951
9952
  if (runningPoll) return toolLabel(runningPoll);
9952
9953
  if (thinking) return thinking.length > 96 ? `\u2026${thinking.slice(-96)}` : thinking;
9953
- if (text6) return "Writing reply\u2026";
9954
+ if (text7) return "Writing reply\u2026";
9954
9955
  const last = [...tools].reverse().find((t) => !isPollWrapperToolName(t.name)) ?? tools.at(-1);
9955
9956
  if (last) {
9956
9957
  const label = toolLabel(last);
@@ -9982,9 +9983,9 @@ function toolFilePath(input) {
9982
9983
  if (!input) return void 0;
9983
9984
  return str2(input.file_path) ?? str2(input.path) ?? str2(input.filePath) ?? str2(input.filename);
9984
9985
  }
9985
- function countLines(text6) {
9986
- if (!text6) return 0;
9987
- return text6.split("\n").length;
9986
+ function countLines(text7) {
9987
+ if (!text7) return 0;
9988
+ return text7.split("\n").length;
9988
9989
  }
9989
9990
  function diffFromInput(input) {
9990
9991
  if (!input) return {};
@@ -10209,9 +10210,9 @@ function partsToAssistantText(parts) {
10209
10210
  (p) => p.type === "text" && !messagePartParentId(p)
10210
10211
  ).map((p) => p.text).join("").trim();
10211
10212
  }
10212
- function stripBrightsyNdjsonNoise(text6) {
10213
- if (!text6 || !text6.includes('"type"')) return text6;
10214
- let out = text6;
10213
+ function stripBrightsyNdjsonNoise(text7) {
10214
+ if (!text7 || !text7.includes('"type"')) return text7;
10215
+ let out = text7;
10215
10216
  out = out.replace(
10216
10217
  /\{"type":"(?:tool_use|tool_result|tool|thinking|usage|done|error)"[\s\S]*?\}\s*(?=\{"type":"|$|(?=[A-Za-z*#]))/g,
10217
10218
  ""
@@ -10254,7 +10255,9 @@ var init_profile = __esm({
10254
10255
  "present_plan",
10255
10256
  "present_schema",
10256
10257
  "present_files",
10257
- "wait_for_job"
10258
+ "wait_for_job",
10259
+ "get_viewer_context",
10260
+ "update_viewer_context"
10258
10261
  ];
10259
10262
  WORKTREE_GITHUB_MCP_TOOLS = [
10260
10263
  "github_get_issue",
@@ -10567,8 +10570,8 @@ function isBrightsyConnected() {
10567
10570
  return false;
10568
10571
  }
10569
10572
  }
10570
- function promptMentionsBrightsy(text6) {
10571
- return BRIGHTSY_WORD.test(text6 ?? "");
10573
+ function promptMentionsBrightsy(text7) {
10574
+ return BRIGHTSY_WORD.test(text7 ?? "");
10572
10575
  }
10573
10576
  function isBrightsyMcpToolName(name) {
10574
10577
  const n = name.toLowerCase();
@@ -10847,7 +10850,9 @@ var init_injected_mcp = __esm({
10847
10850
  "mcp__sideboard__present_files",
10848
10851
  "mcp__sideboard__ask_user",
10849
10852
  "mcp__sideboard__present_plan",
10850
- "mcp__sideboard__wait_for_job"
10853
+ "mcp__sideboard__wait_for_job",
10854
+ "mcp__sideboard__get_viewer_context",
10855
+ "mcp__sideboard__update_viewer_context"
10851
10856
  ];
10852
10857
  SIDEBOARD_GITHUB_MCP_ALLOWED_TOOLS = ["mcp__sideboard__github_*"];
10853
10858
  SIDEBOARD_LINEAR_MCP_ALLOWED_TOOLS = ["mcp__sideboard__linear_*"];
@@ -11184,9 +11189,9 @@ function compactMetadataFromClaude(obj) {
11184
11189
  return { trigger, postTokens };
11185
11190
  }
11186
11191
  function parseIssuesJson(raw) {
11187
- const text6 = raw.trim();
11188
- const candidates = [text6];
11189
- const match = text6.match(/\[[\s\S]*\]/);
11192
+ const text7 = raw.trim();
11193
+ const candidates = [text7];
11194
+ const match = text7.match(/\[[\s\S]*\]/);
11190
11195
  if (match) candidates.push(match[0]);
11191
11196
  for (const c of candidates) {
11192
11197
  try {
@@ -11454,8 +11459,8 @@ var init_claude = __esm({
11454
11459
  if (errorDetail) {
11455
11460
  events.push({ type: "stderr", data: errorDetail });
11456
11461
  } else {
11457
- const text6 = obj.result;
11458
- if (typeof text6 === "string" && text6) events.push({ type: "stdout", data: text6 });
11462
+ const text7 = obj.result;
11463
+ if (typeof text7 === "string" && text7) events.push({ type: "stdout", data: text7 });
11459
11464
  }
11460
11465
  const usage = usageFromClaude(obj.usage);
11461
11466
  if (usage) {
@@ -11596,9 +11601,9 @@ function asObject2(value) {
11596
11601
  function listMcpNamesFromJsonMap(raw, key) {
11597
11602
  return Object.keys(asObject2(asObject2(raw)[key])).filter((n) => n.trim());
11598
11603
  }
11599
- function listMcpNamesFromCodexToml(text6) {
11604
+ function listMcpNamesFromCodexToml(text7) {
11600
11605
  try {
11601
- const parsed = (0, import_smol_toml2.parse)(text6);
11606
+ const parsed = (0, import_smol_toml2.parse)(text7);
11602
11607
  return Object.keys(asObject2(parsed.mcp_servers)).filter((n) => n.trim());
11603
11608
  } catch {
11604
11609
  return [];
@@ -11729,8 +11734,8 @@ function codexConfigHasNetworkAccess() {
11729
11734
  ];
11730
11735
  for (const path2 of candidates) {
11731
11736
  if (!(0, import_node_fs32.existsSync)(path2)) continue;
11732
- const text6 = (0, import_node_fs32.readFileSync)(path2, "utf8");
11733
- if (/network_access\s*=\s*true/.test(text6)) return true;
11737
+ const text7 = (0, import_node_fs32.readFileSync)(path2, "utf8");
11738
+ if (/network_access\s*=\s*true/.test(text7)) return true;
11734
11739
  }
11735
11740
  return false;
11736
11741
  }
@@ -11743,8 +11748,8 @@ function unwrapCodexMcpResult(result) {
11743
11748
  const texts = [];
11744
11749
  for (const item of rec.content) {
11745
11750
  if (!item || typeof item !== "object") continue;
11746
- const text6 = item.text;
11747
- if (typeof text6 === "string") texts.push(text6);
11751
+ const text7 = item.text;
11752
+ if (typeof text7 === "string") texts.push(text7);
11748
11753
  }
11749
11754
  if (texts.length) return texts.join("\n");
11750
11755
  }
@@ -11986,8 +11991,8 @@ var init_codex = __esm({
11986
11991
  if (msg) nested.push(msg);
11987
11992
  }
11988
11993
  }
11989
- for (const text6 of nested) {
11990
- events.push({ type: "stdout", data: text6, parentId: item.id });
11994
+ for (const text7 of nested) {
11995
+ events.push({ type: "stdout", data: text7, parentId: item.id });
11991
11996
  }
11992
11997
  events.push({
11993
11998
  type: "tool_result",
@@ -12738,8 +12743,8 @@ var init_opencode = __esm({
12738
12743
  return { type: "session_id", data: sid };
12739
12744
  }
12740
12745
  if (obj.type === "text") {
12741
- const text6 = obj.part?.text ?? obj.text;
12742
- if (text6) return { type: "stdout", data: text6 };
12746
+ const text7 = obj.part?.text ?? obj.text;
12747
+ if (text7) return { type: "stdout", data: text7 };
12743
12748
  }
12744
12749
  if (obj.type === "tool_use") {
12745
12750
  const part = obj.part;
@@ -13018,8 +13023,8 @@ var init_list_models = __esm({
13018
13023
  });
13019
13024
 
13020
13025
  // src/agents/session-quota.ts
13021
- function isSessionQuotaLimit(text6) {
13022
- const lower = text6.trim().toLowerCase();
13026
+ function isSessionQuotaLimit(text7) {
13027
+ const lower = text7.trim().toLowerCase();
13023
13028
  if (!lower) return false;
13024
13029
  if (/credit balance is too low|out of credits|insufficient.?quota|billing/.test(lower)) {
13025
13030
  return false;
@@ -13027,10 +13032,10 @@ function isSessionQuotaLimit(text6) {
13027
13032
  if (/prompt is too long|context.*(too long|exceed)|conversation too long/.test(lower)) {
13028
13033
  return false;
13029
13034
  }
13030
- 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);
13035
+ 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);
13031
13036
  }
13032
- function parseSessionQuotaResetAt(text6, now = /* @__PURE__ */ new Date()) {
13033
- const absolute = text6.match(
13037
+ function parseSessionQuotaResetAt(text7, now = /* @__PURE__ */ new Date()) {
13038
+ const absolute = text7.match(
13034
13039
  /resets\s+(?:at\s+)?(\d{1,2}):(\d{2})\s*(am|pm)(?:\s*\(([^)]+)\))?/i
13035
13040
  );
13036
13041
  if (absolute) {
@@ -13048,7 +13053,7 @@ function parseSessionQuotaResetAt(text6, now = /* @__PURE__ */ new Date()) {
13048
13053
  }
13049
13054
  return at;
13050
13055
  }
13051
- const relative = text6.match(
13056
+ const relative = text7.match(
13052
13057
  /resets\s+in\s+(\d+)\s*(minutes?|hours?|days?)/i
13053
13058
  );
13054
13059
  if (relative) {
@@ -13629,8 +13634,8 @@ async function spawnAgentTurn(thread, input, onEvent) {
13629
13634
  onEvent({ type: "exit", data: exitCode });
13630
13635
  const finalized = finalizeParts(parts);
13631
13636
  const rawText = assistantText.trim() || partsToAssistantText(finalized);
13632
- const text6 = thread.agent === "brightsy" ? stripBrightsyNdjsonNoise(rawText) : rawText;
13633
- return { exitCode, sessionId, assistantText: text6, parts: finalized, usage };
13637
+ const text7 = thread.agent === "brightsy" ? stripBrightsyNdjsonNoise(rawText) : rawText;
13638
+ return { exitCode, sessionId, assistantText: text7, parts: finalized, usage };
13634
13639
  });
13635
13640
  return {
13636
13641
  pid: child.pid,
@@ -13909,9 +13914,9 @@ async function tryClaudeSummary(transcript, opts) {
13909
13914
  { cwd: opts?.cwd, reject: false }
13910
13915
  );
13911
13916
  if (exitCode !== 0) return null;
13912
- const text6 = stdout.trim();
13913
- if (text6.length < 40) return null;
13914
- return text6;
13917
+ const text7 = stdout.trim();
13918
+ if (text7.length < 40) return null;
13919
+ return text7;
13915
13920
  } catch {
13916
13921
  return null;
13917
13922
  }
@@ -13960,9 +13965,9 @@ function extractiveSummary(transcript) {
13960
13965
  );
13961
13966
  return parts.join("\n");
13962
13967
  }
13963
- function clipSummary(text6) {
13964
- if (text6.length <= MAX_SUMMARY_CHARS) return text6;
13965
- return `${text6.slice(0, MAX_SUMMARY_CHARS)}
13968
+ function clipSummary(text7) {
13969
+ if (text7.length <= MAX_SUMMARY_CHARS) return text7;
13970
+ return `${text7.slice(0, MAX_SUMMARY_CHARS)}
13966
13971
 
13967
13972
  [\u2026summary truncated\u2026]`;
13968
13973
  }
@@ -14116,8 +14121,8 @@ function findLastBrightsyContextSummary(messages) {
14116
14121
  continue;
14117
14122
  }
14118
14123
  if (part.status === "error") continue;
14119
- const text6 = extractBrightsyContextSummary(part.result);
14120
- if (text6) return { index: i, text: text6 };
14124
+ const text7 = extractBrightsyContextSummary(part.result);
14125
+ if (text7) return { index: i, text: text7 };
14121
14126
  }
14122
14127
  }
14123
14128
  return null;
@@ -15409,8 +15414,8 @@ function listRunningDetachedJobs(worktreePath) {
15409
15414
  }
15410
15415
  return running.sort();
15411
15416
  }
15412
- function looksLikeDeferredDonePromise(text6) {
15413
- const t = (text6 ?? "").trim();
15417
+ function looksLikeDeferredDonePromise(text7) {
15418
+ const t = (text7 ?? "").trim();
15414
15419
  if (!t) return false;
15415
15420
  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);
15416
15421
  }
@@ -16156,11 +16161,11 @@ function unwrapToolResult(result) {
16156
16161
  return typeof item.text === "string" ? item.text : "";
16157
16162
  }).filter(Boolean);
16158
16163
  if (texts.length === 1) {
16159
- const text6 = texts[0];
16164
+ const text7 = texts[0];
16160
16165
  try {
16161
- return JSON.parse(text6);
16166
+ return JSON.parse(text7);
16162
16167
  } catch {
16163
- return text6;
16168
+ return text7;
16164
16169
  }
16165
16170
  }
16166
16171
  if (texts.length > 1) return texts.join("\n");
@@ -16922,8 +16927,8 @@ function summarizeTurnLive(parts) {
16922
16927
  const interesting = [...tools].reverse().find((t) => !isPollWrapperToolName(t.name)) ?? lastTool;
16923
16928
  const lastToolLabel = interesting ? interesting.description || toolDescription(interesting.name, interesting.input) || interesting.name : void 0;
16924
16929
  const thinking = lastText(parts, "thinking");
16925
- const text6 = lastText(parts, "text");
16926
- const excerptRaw = thinking || text6;
16930
+ const text7 = lastText(parts, "text");
16931
+ const excerptRaw = thinking || text7;
16927
16932
  const excerpt = excerptRaw.length > 280 ? `${excerptRaw.slice(-280)}` : excerptRaw || void 0;
16928
16933
  const summary = liveActivitySummary(parts);
16929
16934
  return {
@@ -17162,8 +17167,8 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
17162
17167
  );
17163
17168
  const recent = from.messages.slice(-8).map((m) => {
17164
17169
  const role = m.role === "user" ? "User" : m.role === "agent" ? "Agent" : "Summary";
17165
- const text6 = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
17166
- return text6 ? `- ${role}: ${text6}` : null;
17170
+ const text7 = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
17171
+ return text7 ? `- ${role}: ${text7}` : null;
17167
17172
  }).filter(Boolean);
17168
17173
  const body = [
17169
17174
  `# Orchestration handoff`,
@@ -17260,13 +17265,13 @@ function resolveConductorCursorAgentId(workspacePath) {
17260
17265
  for (const hash of hashes) {
17261
17266
  const agentsFile = (0, import_node_path41.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
17262
17267
  if (!(0, import_node_fs43.existsSync)(agentsFile)) continue;
17263
- let text6;
17268
+ let text7;
17264
17269
  try {
17265
- text6 = (0, import_node_fs43.readFileSync)(agentsFile, "utf8");
17270
+ text7 = (0, import_node_fs43.readFileSync)(agentsFile, "utf8");
17266
17271
  } catch {
17267
17272
  continue;
17268
17273
  }
17269
- for (const line of text6.split("\n")) {
17274
+ for (const line of text7.split("\n")) {
17270
17275
  const trimmed = line.trim();
17271
17276
  if (!trimmed) continue;
17272
17277
  try {
@@ -18075,13 +18080,13 @@ function formatStat(files) {
18075
18080
  return `${n} file${n === 1 ? "" : "s"} changed, ${additions} insertions(+), ${deletions} deletions(-)`;
18076
18081
  }
18077
18082
  function emptyScopeStats() {
18078
- const z7 = emptyStat();
18083
+ const z8 = emptyStat();
18079
18084
  return {
18080
- commits: z7,
18081
- uncommitted: z7,
18082
- staged: z7,
18083
- unstaged: z7,
18084
- last_turn: z7
18085
+ commits: z8,
18086
+ uncommitted: z8,
18087
+ staged: z8,
18088
+ unstaged: z8,
18089
+ last_turn: z8
18085
18090
  };
18086
18091
  }
18087
18092
  function filesFromDiff(nameStatus, numstat, combinedDiff, maxHunk) {
@@ -19455,12 +19460,12 @@ function firstString2(record, keys) {
19455
19460
  return "";
19456
19461
  }
19457
19462
  async function readJson(res) {
19458
- const text6 = await res.text();
19459
- if (!text6.trim()) return null;
19463
+ const text7 = await res.text();
19464
+ if (!text7.trim()) return null;
19460
19465
  try {
19461
- return JSON.parse(text6);
19466
+ return JSON.parse(text7);
19462
19467
  } catch {
19463
- return text6;
19468
+ return text7;
19464
19469
  }
19465
19470
  }
19466
19471
  async function authorizedGet(url, token) {
@@ -19710,11 +19715,11 @@ function resolvePlanMarkdown(opts) {
19710
19715
  source: "exit_plan"
19711
19716
  };
19712
19717
  }
19713
- const text6 = opts.text?.trim();
19714
- if (text6 && text6.length >= 80) {
19718
+ const text7 = opts.text?.trim();
19719
+ if (text7 && text7.length >= 80) {
19715
19720
  return {
19716
19721
  title: "Plan",
19717
- content: text6,
19722
+ content: text7,
19718
19723
  path: PLAN_FILE_REL,
19719
19724
  source: "text"
19720
19725
  };
@@ -20593,11 +20598,11 @@ var init_orchestrator = __esm({
20593
20598
  return results;
20594
20599
  }
20595
20600
  /** Edit the text of a not-yet-started queued message. */
20596
- async editQueuedMessage(threadRef, index, text6) {
20601
+ async editQueuedMessage(threadRef, index, text7) {
20597
20602
  const thread = this.requireThread(threadRef);
20598
20603
  return withThreadLock(thread.id, async () => {
20599
20604
  const current = this.requireThread(thread.id);
20600
- const trimmed = text6.trim();
20605
+ const trimmed = text7.trim();
20601
20606
  if (!trimmed || index < 0 || index >= current.queue.length) {
20602
20607
  return current;
20603
20608
  }
@@ -20783,6 +20788,7 @@ var init_orchestrator = __esm({
20783
20788
  ticketId: issueTicket?.id,
20784
20789
  ticketProvider: issueTicket?.provider
20785
20790
  }) : null;
20791
+ const viewerContextReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatViewerContextReminder() : null;
20786
20792
  const slackReplyContext = formatSlackRepliesForTurn(
20787
20793
  pendingSlackExternalReplies(thread.messages)
20788
20794
  );
@@ -20792,6 +20798,7 @@ var init_orchestrator = __esm({
20792
20798
  worktreeReminder,
20793
20799
  optionalServicesReminder,
20794
20800
  issueToolsReminder,
20801
+ viewerContextReminder,
20795
20802
  artifactReminder,
20796
20803
  longRunningReminder,
20797
20804
  slackReplyContext,
@@ -20832,6 +20839,18 @@ var init_orchestrator = __esm({
20832
20839
  ticketId: freshIssueTicket?.id,
20833
20840
  ticketProvider: freshIssueTicket?.provider
20834
20841
  });
20842
+ let viewerContextDirective = null;
20843
+ if (!isBrightsy && !isOrchestration) {
20844
+ try {
20845
+ viewerContextDirective = formatViewerContextDirective(
20846
+ resolveViewerProfileForRepo(fresh.repoPath || fresh.worktreePath)
20847
+ );
20848
+ } catch {
20849
+ viewerContextDirective = formatViewerContextDirective(
20850
+ resolveViewerProfileForRepo()
20851
+ );
20852
+ }
20853
+ }
20835
20854
  const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
20836
20855
  const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled2() ? formatRenameBranchDirective(fresh, {
20837
20856
  customPrompt: settings?.prompts?.renameBranch
@@ -20863,6 +20882,7 @@ var init_orchestrator = __esm({
20863
20882
  worktreeDirective,
20864
20883
  optionalServicesDirective,
20865
20884
  issueToolsDirective,
20885
+ viewerContextDirective,
20866
20886
  artifactDirective,
20867
20887
  longRunningDirective,
20868
20888
  renameBranchDirective,
@@ -20973,6 +20993,7 @@ var init_orchestrator = __esm({
20973
20993
  worktreeDirective,
20974
20994
  optionalServicesDirective,
20975
20995
  issueToolsDirective,
20996
+ viewerContextDirective,
20976
20997
  artifactDirective,
20977
20998
  longRunningDirective,
20978
20999
  renameBranchDirective,
@@ -21507,13 +21528,13 @@ var init_orchestrator = __esm({
21507
21528
  const lastAgent = [...thread.messages].reverse().find((m) => m.role === "agent");
21508
21529
  const lastError = thread.lastError ?? null;
21509
21530
  const rawText = (lastAgent?.text ?? "").trim();
21510
- const text6 = (rawText && !isInternalAgentStatusText(rawText) ? rawText : "") || (thread.status === "error" || thread.status === "stopped" || thread.status === "broken" ? lastError ?? "" : "");
21531
+ const text7 = (rawText && !isInternalAgentStatusText(rawText) ? rawText : "") || (thread.status === "error" || thread.status === "stopped" || thread.status === "broken" ? lastError ?? "" : "");
21511
21532
  const stillRunning = this.threadLooksLive(thread);
21512
21533
  const live = stillRunning ? readTurnLive(thread.id) : null;
21513
21534
  const liveSummary = live?.summary && !isInternalAgentStatusText(live.summary) ? live.summary : null;
21514
21535
  const queuedHint = stillRunning && thread.status === "queued" && !liveSummary ? "Queued \u2014 waiting for a concurrency slot" : null;
21515
21536
  return {
21516
- text: text6,
21537
+ text: text7,
21517
21538
  status: thread.status,
21518
21539
  sessionId: thread.sessionId,
21519
21540
  lastError,
@@ -22280,10 +22301,6 @@ var init_schedule_runner = __esm({
22280
22301
  var index_exports = {};
22281
22302
  __export(index_exports, {
22282
22303
  ABLETIME_MCP_PATH: () => ABLETIME_MCP_PATH,
22283
- ACCOUNT_ROLES: () => ACCOUNT_ROLES,
22284
- ACCOUNT_ROLE_LABELS: () => ACCOUNT_ROLE_LABELS,
22285
- ACCOUNT_ROLE_MAX: () => ACCOUNT_ROLE_MAX,
22286
- ACCOUNT_ROLE_PRESETS: () => ACCOUNT_ROLE_PRESETS,
22287
22304
  AGENT_GIT_ACTIONS: () => AGENT_GIT_ACTIONS,
22288
22305
  AGENT_RUNNER_MAX_OLD_SPACE_MB: () => AGENT_RUNNER_MAX_OLD_SPACE_MB,
22289
22306
  ATTACHMENTS_DIR: () => ATTACHMENTS_DIR,
@@ -22370,7 +22387,6 @@ __export(index_exports, {
22370
22387
  WORKTREE_MCP_TOOLS: () => WORKTREE_MCP_TOOLS,
22371
22388
  abletimeMcpRequest: () => abletimeMcpRequest,
22372
22389
  abletimeMcpUrl: () => abletimeMcpUrl,
22373
- accountRoleLabel: () => accountRoleLabel,
22374
22390
  ackSlackInboundSeen: () => ackSlackInboundSeen,
22375
22391
  addBoardPin: () => addBoardPin,
22376
22392
  addPrStackLayer: () => addPrStackLayer,
@@ -22551,6 +22567,7 @@ __export(index_exports, {
22551
22567
  fireSchedule: () => fireSchedule,
22552
22568
  flattenTurnInput: () => flattenTurnInput,
22553
22569
  flipLinearRelationType: () => flipLinearRelationType,
22570
+ foldLegacyRolesIntoNotes: () => foldLegacyRolesIntoNotes,
22554
22571
  followUpBehavior: () => followUpBehavior,
22555
22572
  forkChatTab: () => forkChatTab,
22556
22573
  forkMessageSlice: () => forkMessageSlice,
@@ -22591,6 +22608,8 @@ __export(index_exports, {
22591
22608
  formatSlackWorkingText: () => formatSlackWorkingText,
22592
22609
  formatTranscriptMarkdown: () => formatTranscriptMarkdown,
22593
22610
  formatUiReminder: () => formatUiReminder,
22611
+ formatViewerContextDirective: () => formatViewerContextDirective,
22612
+ formatViewerContextReminder: () => formatViewerContextReminder,
22594
22613
  formatWorkspaceInventory: () => formatWorkspaceInventory,
22595
22614
  formatWorkspaceProfileSuffix: () => formatWorkspaceProfileSuffix,
22596
22615
  formatWorktreeDirective: () => formatWorktreeDirective,
@@ -22828,7 +22847,6 @@ __export(index_exports, {
22828
22847
  planQuestionsSignature: () => planQuestionsSignature,
22829
22848
  pollSlackOutboundWatches: () => pollSlackOutboundWatches,
22830
22849
  posixShellSingleQuote: () => posixShellSingleQuote,
22831
- preferTeamsForRole: () => preferTeamsForRole,
22832
22850
  preferredCursorCostCents: () => preferredCursorCostCents,
22833
22851
  prepareTerminalCommand: () => prepareTerminalCommand,
22834
22852
  previewLand: () => previewLand,
@@ -22840,6 +22858,7 @@ __export(index_exports, {
22840
22858
  readSetupLog: () => readSetupLog,
22841
22859
  readSkillBody: () => readSkillBody,
22842
22860
  readThread: () => readThread,
22861
+ readViewerContext: () => readViewerContext,
22843
22862
  readWorktreeFile: () => readWorktreeFile,
22844
22863
  readWorktreeFileForUpload: () => readWorktreeFileForUpload,
22845
22864
  readWorktreeInclude: () => readWorktreeInclude,
@@ -22896,7 +22915,6 @@ __export(index_exports, {
22896
22915
  resolveViewerProfile: () => resolveViewerProfile,
22897
22916
  resolveViewerProfileForRepo: () => resolveViewerProfileForRepo,
22898
22917
  resolveWorktreeStartPoint: () => resolveWorktreeStartPoint,
22899
- reviewTeamHintsForRoles: () => reviewTeamHintsForRoles,
22900
22918
  rewriteAbleTimeError: () => rewriteAbleTimeError,
22901
22919
  rewriteLinearError: () => rewriteLinearError,
22902
22920
  run: () => run,
@@ -23030,6 +23048,7 @@ __export(index_exports, {
23030
23048
  writeInjectedMcpConfig: () => writeInjectedMcpConfig,
23031
23049
  writePlanFile: () => writePlanFile,
23032
23050
  writeThread: () => writeThread,
23051
+ writeViewerContext: () => writeViewerContext,
23033
23052
  writeWorktreeFile: () => writeWorktreeFile
23034
23053
  });
23035
23054
  module.exports = __toCommonJS(index_exports);
@@ -23078,9 +23097,9 @@ async function getGitHubStatus() {
23078
23097
  };
23079
23098
  }
23080
23099
  const status = await run("gh", ["auth", "status"], { reject: false });
23081
- const text6 = `${status.stdout}
23100
+ const text7 = `${status.stdout}
23082
23101
  ${status.stderr}`;
23083
- const loginMatch = text6.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text6.match(/Logged in to ([^\s]+) as (\S+)/i);
23102
+ const loginMatch = text7.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text7.match(/Logged in to ([^\s]+) as (\S+)/i);
23084
23103
  if (loginMatch) {
23085
23104
  return {
23086
23105
  connected: true,
@@ -24446,11 +24465,11 @@ function fenceLanguage(path2, language) {
24446
24465
  }
24447
24466
  function buildCodeRefAttachment(input) {
24448
24467
  const path2 = input.path.trim();
24449
- const text6 = input.text.replace(/\n$/, "");
24468
+ const text7 = input.text.replace(/\n$/, "");
24450
24469
  if (!path2) {
24451
24470
  throw new Error("code reference requires a file path");
24452
24471
  }
24453
- if (!text6.trim()) {
24472
+ if (!text7.trim()) {
24454
24473
  throw new Error("code reference requires selected text");
24455
24474
  }
24456
24475
  if (input.startLine < 1 || input.endLine < 1 || input.endLine < input.startLine) {
@@ -24463,7 +24482,7 @@ function buildCodeRefAttachment(input) {
24463
24482
  `Referenced code from \`${path2}\` (${range}).`,
24464
24483
  "",
24465
24484
  fence,
24466
- text6,
24485
+ text7,
24467
24486
  "```"
24468
24487
  ].join("\n");
24469
24488
  return {
@@ -24520,16 +24539,16 @@ var PASTE_ATTACH_MIN_CHARS = 1200;
24520
24539
  var PASTE_ATTACH_MIN_LINES = 15;
24521
24540
  var PASTED_NAME_RE = /^Pasted text #(\d+)\.txt$/i;
24522
24541
  var PASTED_NAME_ALT_RE = /^pasted-(\d+)\.txt$/i;
24523
- function pastedTextStats(text6) {
24524
- const chars = text6.length;
24542
+ function pastedTextStats(text7) {
24543
+ const chars = text7.length;
24525
24544
  if (chars === 0) return { chars: 0, lines: 0 };
24526
- const lines = text6.split(/\r\n|\r|\n/).length;
24545
+ const lines = text7.split(/\r\n|\r|\n/).length;
24527
24546
  return { chars, lines };
24528
24547
  }
24529
- function shouldAttachPastedText(text6) {
24530
- const trimmed = text6.trim();
24548
+ function shouldAttachPastedText(text7) {
24549
+ const trimmed = text7.trim();
24531
24550
  if (!trimmed) return false;
24532
- const { chars, lines } = pastedTextStats(text6);
24551
+ const { chars, lines } = pastedTextStats(text7);
24533
24552
  return chars >= PASTE_ATTACH_MIN_CHARS || lines >= PASTE_ATTACH_MIN_LINES;
24534
24553
  }
24535
24554
  function nextPastedTextName(existing) {
@@ -24540,13 +24559,13 @@ function nextPastedTextName(existing) {
24540
24559
  }
24541
24560
  return `Pasted text #${max + 1}.txt`;
24542
24561
  }
24543
- function buildPastedTextAttachment(text6, opts) {
24562
+ function buildPastedTextAttachment(text7, opts) {
24544
24563
  return {
24545
24564
  id: opts?.id ?? (0, import_node_crypto11.randomUUID)(),
24546
24565
  name: opts?.name ?? "Pasted text #1.txt",
24547
24566
  kind: "file",
24548
24567
  path: opts?.path,
24549
- content: text6
24568
+ content: text7
24550
24569
  };
24551
24570
  }
24552
24571
 
@@ -24646,8 +24665,8 @@ function latestPendingPlanQuestions(input) {
24646
24665
  return null;
24647
24666
  }
24648
24667
  var PLAN_QUESTION_ANSWERS_PREFIX = "Answers to your questions:";
24649
- function isPlanQuestionAnswersMessage(text6) {
24650
- return text6.startsWith(PLAN_QUESTION_ANSWERS_PREFIX);
24668
+ function isPlanQuestionAnswersMessage(text7) {
24669
+ return text7.startsWith(PLAN_QUESTION_ANSWERS_PREFIX);
24651
24670
  }
24652
24671
  function formatPlanQuestionAnswers(questions, answers) {
24653
24672
  const lines = [PLAN_QUESTION_ANSWERS_PREFIX, ""];
@@ -24694,7 +24713,7 @@ init_orphan_cleanup();
24694
24713
  // src/mcp/server.ts
24695
24714
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
24696
24715
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
24697
- var import_zod6 = require("zod");
24716
+ var import_zod7 = require("zod");
24698
24717
  var import_node_crypto13 = require("crypto");
24699
24718
  var import_node_path50 = require("path");
24700
24719
  init_orchestrator();
@@ -24723,9 +24742,9 @@ init_message_parts();
24723
24742
  function lastMessagePreview(messages, max = 160) {
24724
24743
  if (!messages?.length) return null;
24725
24744
  for (let i = messages.length - 1; i >= 0; i--) {
24726
- const text6 = messages[i]?.text?.trim();
24727
- if (!text6 || isInternalAgentStatusText(text6)) continue;
24728
- const flat = text6.replace(/\s+/g, " ");
24745
+ const text7 = messages[i]?.text?.trim();
24746
+ if (!text7 || isInternalAgentStatusText(text7)) continue;
24747
+ const flat = text7.replace(/\s+/g, " ");
24729
24748
  return flat.length > max ? `${flat.slice(0, max)}\u2026` : flat;
24730
24749
  }
24731
24750
  return null;
@@ -24959,8 +24978,8 @@ function labelGithubUrl(url) {
24959
24978
  }
24960
24979
  return { kind: "other", label: "GitHub", url: raw };
24961
24980
  }
24962
- function appendGithubLink(text6, githubUrl) {
24963
- const body = text6.trimEnd();
24981
+ function appendGithubLink(text7, githubUrl) {
24982
+ const body = text7.trimEnd();
24964
24983
  if (!githubUrl?.trim()) return body;
24965
24984
  const labeled = labelGithubUrl(githubUrl);
24966
24985
  if (!labeled) {
@@ -25656,6 +25675,104 @@ function registerConnectedIssueVendorTools(server, settings = loadAppSettings())
25656
25675
  if (isAbleTimeConnected(settings)) registerAbleTimeTools(server);
25657
25676
  }
25658
25677
 
25678
+ // src/mcp/viewer-context-tools.ts
25679
+ var import_zod5 = require("zod");
25680
+ init_worktree();
25681
+ init_app_settings();
25682
+ init_workspaces();
25683
+ function text5(payload, isError = false) {
25684
+ return mcpJson(payload, isError);
25685
+ }
25686
+ function matchRegisteredWorkspace(repoPath) {
25687
+ const hint = (repoPath ?? "").trim().replace(/\/+$/, "");
25688
+ if (!hint) return null;
25689
+ const match = listWorkspaces().find((w) => {
25690
+ const stored = w.path.replace(/\/+$/, "");
25691
+ return stored === hint || hint.startsWith(`${stored}/`) || stored.startsWith(`${hint}/`);
25692
+ });
25693
+ return match?.path.replace(/\/+$/, "") ?? null;
25694
+ }
25695
+ async function resolveProjectRepoPath(repoPath) {
25696
+ const hint = repoPath?.trim() || process.cwd();
25697
+ let root = hint;
25698
+ try {
25699
+ root = await resolveRepoRoot(hint);
25700
+ } catch {
25701
+ root = hint.replace(/\/+$/, "");
25702
+ }
25703
+ return matchRegisteredWorkspace(root) ?? matchRegisteredWorkspace(hint);
25704
+ }
25705
+ var PROJECT_PATH_HINT = "Pass repoPath from list_workspaces. Orchestration cwd is synthetic and empty \u2014 do not omit repoPath for project context.";
25706
+ function registerViewerContextTools(server) {
25707
+ server.tool(
25708
+ "get_viewer_context",
25709
+ "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).",
25710
+ {
25711
+ repoPath: import_zod5.z.string().optional().describe(
25712
+ "Registered workspace or worktree path. Required from orchestration for a project. Omit on a worktree turn (uses cwd)."
25713
+ )
25714
+ },
25715
+ async ({ repoPath }) => {
25716
+ try {
25717
+ const path2 = await resolveProjectRepoPath(repoPath);
25718
+ return text5(readViewerContext(path2 ?? void 0));
25719
+ } catch (err) {
25720
+ return text5(
25721
+ { error: err instanceof Error ? err.message : String(err) },
25722
+ true
25723
+ );
25724
+ }
25725
+ }
25726
+ );
25727
+ server.tool(
25728
+ "update_viewer_context",
25729
+ "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.",
25730
+ {
25731
+ scope: import_zod5.z.enum(["account", "project"]).describe("account = Settings \u2192 Agents; project = Settings \u2192 Projects for one repo"),
25732
+ context: import_zod5.z.string().describe("Full replacement text (roles, tickets, review queues). Empty clears."),
25733
+ confirmed: import_zod5.z.boolean().optional().describe("Must be true after the user confirmed via ask_user. False/omit is rejected."),
25734
+ repoPath: import_zod5.z.string().optional().describe(
25735
+ "Required for scope=project from orchestration (path from list_workspaces). Worktree turns may omit it (cwd)."
25736
+ )
25737
+ },
25738
+ async ({ scope, context, confirmed, repoPath }) => {
25739
+ try {
25740
+ if (scope === "project") {
25741
+ const path2 = await resolveProjectRepoPath(repoPath);
25742
+ if (!path2) {
25743
+ return text5(
25744
+ {
25745
+ ok: false,
25746
+ error: "repoPath required",
25747
+ message: PROJECT_PATH_HINT
25748
+ },
25749
+ true
25750
+ );
25751
+ }
25752
+ const result2 = writeViewerContext({
25753
+ scope,
25754
+ context,
25755
+ repoPath: path2,
25756
+ confirmed: confirmed === true
25757
+ });
25758
+ return text5(result2, !result2.ok);
25759
+ }
25760
+ const result = writeViewerContext({
25761
+ scope,
25762
+ context,
25763
+ confirmed: confirmed === true
25764
+ });
25765
+ return text5(result, !result.ok);
25766
+ } catch (err) {
25767
+ return text5(
25768
+ { error: err instanceof Error ? err.message : String(err) },
25769
+ true
25770
+ );
25771
+ }
25772
+ }
25773
+ );
25774
+ }
25775
+
25659
25776
  // src/mcp/pr-list.ts
25660
25777
  function compactPrRow(pr) {
25661
25778
  return {
@@ -25695,17 +25812,17 @@ init_list_prs();
25695
25812
  init_app_settings();
25696
25813
 
25697
25814
  // src/mcp/schedule-tools.ts
25698
- var import_zod5 = require("zod");
25815
+ var import_zod6 = require("zod");
25699
25816
  init_schedules();
25700
25817
  init_schedule_runner();
25701
- function text5(payload, isError = false) {
25818
+ function text6(payload, isError = false) {
25702
25819
  return {
25703
25820
  content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
25704
25821
  ...isError ? { isError: true } : {}
25705
25822
  };
25706
25823
  }
25707
25824
  function fail5(err) {
25708
- return text5(
25825
+ return text6(
25709
25826
  { error: err instanceof Error ? err.message : String(err) },
25710
25827
  true
25711
25828
  );
@@ -25730,7 +25847,7 @@ function registerScheduleTools(server) {
25730
25847
  {},
25731
25848
  async () => {
25732
25849
  try {
25733
- return text5({ schedules: listSchedules() });
25850
+ return text6({ schedules: listSchedules() });
25734
25851
  } catch (err) {
25735
25852
  return fail5(err);
25736
25853
  }
@@ -25740,17 +25857,17 @@ function registerScheduleTools(server) {
25740
25857
  "create_schedule",
25741
25858
  "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.",
25742
25859
  {
25743
- prompt: import_zod5.z.string().describe("User message / goal queued when the schedule fires"),
25744
- name: import_zod5.z.string().optional(),
25745
- at: import_zod5.z.string().optional().describe("ISO datetime for a one-shot"),
25746
- every: import_zod5.z.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
25747
- cron: import_zod5.z.string().optional().describe("5-field cron expression"),
25748
- tz: import_zod5.z.string().optional().describe("IANA timezone for cron (default: system)"),
25749
- threadId: import_zod5.z.string().optional().describe(
25860
+ prompt: import_zod6.z.string().describe("User message / goal queued when the schedule fires"),
25861
+ name: import_zod6.z.string().optional(),
25862
+ at: import_zod6.z.string().optional().describe("ISO datetime for a one-shot"),
25863
+ every: import_zod6.z.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
25864
+ cron: import_zod6.z.string().optional().describe("5-field cron expression"),
25865
+ tz: import_zod6.z.string().optional().describe("IANA timezone for cron (default: system)"),
25866
+ threadId: import_zod6.z.string().optional().describe(
25750
25867
  'Existing orchestration chat id, or "self" for this coordinator. Omit to create a new Global chat on fire.'
25751
25868
  ),
25752
- agent: import_zod5.z.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
25753
- model: import_zod5.z.string().optional()
25869
+ agent: import_zod6.z.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
25870
+ model: import_zod6.z.string().optional()
25754
25871
  },
25755
25872
  async (args) => {
25756
25873
  try {
@@ -25770,7 +25887,7 @@ function registerScheduleTools(server) {
25770
25887
  model: args.model,
25771
25888
  createdBy: "mcp"
25772
25889
  });
25773
- return text5({
25890
+ return text6({
25774
25891
  schedule,
25775
25892
  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."
25776
25893
  });
@@ -25783,17 +25900,17 @@ function registerScheduleTools(server) {
25783
25900
  "update_schedule",
25784
25901
  "Update a local schedule (prompt, cadence, target thread, enabled). Pass id from list_schedules.",
25785
25902
  {
25786
- id: import_zod5.z.string(),
25787
- prompt: import_zod5.z.string().optional(),
25788
- name: import_zod5.z.string().optional(),
25789
- at: import_zod5.z.string().optional(),
25790
- every: import_zod5.z.string().optional(),
25791
- cron: import_zod5.z.string().optional(),
25792
- tz: import_zod5.z.string().optional(),
25793
- threadId: import_zod5.z.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
25794
- agent: import_zod5.z.enum(["claude", "cursor", "codex", "opencode"]).optional(),
25795
- model: import_zod5.z.string().optional(),
25796
- enabled: import_zod5.z.boolean().optional()
25903
+ id: import_zod6.z.string(),
25904
+ prompt: import_zod6.z.string().optional(),
25905
+ name: import_zod6.z.string().optional(),
25906
+ at: import_zod6.z.string().optional(),
25907
+ every: import_zod6.z.string().optional(),
25908
+ cron: import_zod6.z.string().optional(),
25909
+ tz: import_zod6.z.string().optional(),
25910
+ threadId: import_zod6.z.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
25911
+ agent: import_zod6.z.enum(["claude", "cursor", "codex", "opencode"]).optional(),
25912
+ model: import_zod6.z.string().optional(),
25913
+ enabled: import_zod6.z.boolean().optional()
25797
25914
  },
25798
25915
  async (args) => {
25799
25916
  try {
@@ -25822,7 +25939,7 @@ function registerScheduleTools(server) {
25822
25939
  enabled: args.enabled,
25823
25940
  model: args.model
25824
25941
  });
25825
- return text5({ schedule });
25942
+ return text6({ schedule });
25826
25943
  } catch (err) {
25827
25944
  return fail5(err);
25828
25945
  }
@@ -25831,11 +25948,11 @@ function registerScheduleTools(server) {
25831
25948
  server.tool(
25832
25949
  "delete_schedule",
25833
25950
  "Delete a local schedule. Pass id from list_schedules.",
25834
- { id: import_zod5.z.string() },
25951
+ { id: import_zod6.z.string() },
25835
25952
  async ({ id }) => {
25836
25953
  try {
25837
25954
  deleteSchedule(id);
25838
- return text5({ ok: true, id });
25955
+ return text6({ ok: true, id });
25839
25956
  } catch (err) {
25840
25957
  return fail5(err);
25841
25958
  }
@@ -25844,14 +25961,14 @@ function registerScheduleTools(server) {
25844
25961
  server.tool(
25845
25962
  "run_schedule",
25846
25963
  "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.",
25847
- { id: import_zod5.z.string() },
25964
+ { id: import_zod6.z.string() },
25848
25965
  async ({ id }) => {
25849
25966
  try {
25850
25967
  if (!getSchedule(id)) {
25851
25968
  return fail5(new Error(`Schedule not found: ${id}`));
25852
25969
  }
25853
25970
  const schedule = await fireSchedule(id);
25854
- return text5({ schedule });
25971
+ return text6({ schedule });
25855
25972
  } catch (err) {
25856
25973
  return fail5(err);
25857
25974
  }
@@ -26282,13 +26399,14 @@ async function startMcpServer() {
26282
26399
  version: "0.1.0"
26283
26400
  });
26284
26401
  const worktreeProfile = sideboardMcpProfile() === "worktree";
26402
+ registerViewerContextTools(server);
26285
26403
  if (worktreeProfile) {
26286
26404
  registerConnectedIssueVendorTools(server);
26287
26405
  }
26288
26406
  if (!worktreeProfile) {
26289
26407
  server.tool(
26290
26408
  "list_workspaces",
26291
- "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.",
26409
+ "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.",
26292
26410
  {},
26293
26411
  async () => {
26294
26412
  const workspaces = orch.listWorkspaces();
@@ -26337,13 +26455,13 @@ async function startMcpServer() {
26337
26455
  "list_board",
26338
26456
  "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.",
26339
26457
  {
26340
- query: import_zod6.z.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
26341
- repoPath: import_zod6.z.string().optional().describe("Limit to one workspace path from list_workspaces"),
26342
- kind: import_zod6.z.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
26343
- column: import_zod6.z.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
26458
+ query: import_zod7.z.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
26459
+ repoPath: import_zod7.z.string().optional().describe("Limit to one workspace path from list_workspaces"),
26460
+ kind: import_zod7.z.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
26461
+ column: import_zod7.z.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
26344
26462
  "Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
26345
26463
  ),
26346
- limit: import_zod6.z.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
26464
+ limit: import_zod7.z.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
26347
26465
  },
26348
26466
  async ({ query, repoPath, kind, column, limit }) => {
26349
26467
  const workspaces = orch.listWorkspaces();
@@ -26381,7 +26499,7 @@ async function startMcpServer() {
26381
26499
  server.tool(
26382
26500
  "get_thread",
26383
26501
  "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.",
26384
- { ref: import_zod6.z.string() },
26502
+ { ref: import_zod7.z.string() },
26385
26503
  async ({ ref }) => {
26386
26504
  const t = orch.getThread(ref);
26387
26505
  if (!t) {
@@ -26423,17 +26541,17 @@ async function startMcpServer() {
26423
26541
  "present_artifact",
26424
26542
  "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.",
26425
26543
  {
26426
- title: import_zod6.z.string().describe("Short title shown in the artifact pane header"),
26427
- type: import_zod6.z.enum(["html", "svg", "markdown", "react", "log"]).describe(
26544
+ title: import_zod7.z.string().describe("Short title shown in the artifact pane header"),
26545
+ type: import_zod7.z.enum(["html", "svg", "markdown", "react", "log"]).describe(
26428
26546
  "html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
26429
26547
  ),
26430
- content: import_zod6.z.string().describe(
26548
+ content: import_zod7.z.string().describe(
26431
26549
  "html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
26432
26550
  ),
26433
- artifact_id: import_zod6.z.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
26434
- status: import_zod6.z.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
26435
- phase: import_zod6.z.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
26436
- mode: import_zod6.z.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
26551
+ artifact_id: import_zod7.z.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
26552
+ status: import_zod7.z.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
26553
+ phase: import_zod7.z.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
26554
+ mode: import_zod7.z.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
26437
26555
  },
26438
26556
  async ({ title, type, artifact_id, status, phase, mode }) => {
26439
26557
  const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -26454,15 +26572,15 @@ async function startMcpServer() {
26454
26572
  "ask_user",
26455
26573
  "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.",
26456
26574
  {
26457
- questions: import_zod6.z.array(
26458
- import_zod6.z.object({
26459
- question: import_zod6.z.string().describe("Full question text ending with ?"),
26460
- header: import_zod6.z.string().max(24).optional().describe("Short label shown above the question"),
26461
- multiSelect: import_zod6.z.boolean().optional().describe("Allow selecting multiple options"),
26462
- options: import_zod6.z.array(
26463
- import_zod6.z.object({
26464
- label: import_zod6.z.string(),
26465
- description: import_zod6.z.string().optional().describe("What this option means / when to choose it (strongly preferred)")
26575
+ questions: import_zod7.z.array(
26576
+ import_zod7.z.object({
26577
+ question: import_zod7.z.string().describe("Full question text ending with ?"),
26578
+ header: import_zod7.z.string().max(24).optional().describe("Short label shown above the question"),
26579
+ multiSelect: import_zod7.z.boolean().optional().describe("Allow selecting multiple options"),
26580
+ options: import_zod7.z.array(
26581
+ import_zod7.z.object({
26582
+ label: import_zod7.z.string(),
26583
+ description: import_zod7.z.string().optional().describe("What this option means / when to choose it (strongly preferred)")
26466
26584
  })
26467
26585
  ).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
26468
26586
  })
@@ -26481,9 +26599,9 @@ async function startMcpServer() {
26481
26599
  "present_plan",
26482
26600
  "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.",
26483
26601
  {
26484
- title: import_zod6.z.string().optional().describe("Short plan title (defaults to Plan)"),
26485
- content: import_zod6.z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
26486
- thread_id: import_zod6.z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
26602
+ title: import_zod7.z.string().optional().describe("Short plan title (defaults to Plan)"),
26603
+ content: import_zod7.z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
26604
+ thread_id: import_zod7.z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
26487
26605
  },
26488
26606
  async ({ title, content, thread_id }) => {
26489
26607
  const { writePlanFile: writePlanFile2 } = await Promise.resolve().then(() => (init_plan_file(), plan_file_exports));
@@ -26506,15 +26624,15 @@ async function startMcpServer() {
26506
26624
  "present_schema",
26507
26625
  "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.",
26508
26626
  {
26509
- title: import_zod6.z.string().describe("Pane title"),
26510
- mode: import_zod6.z.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
26511
- datasource: import_zod6.z.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
26512
- resource_id: import_zod6.z.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
26513
- record_id: import_zod6.z.string().optional().describe("Record id when opening form mode"),
26514
- resource: import_zod6.z.record(import_zod6.z.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
26515
- record: import_zod6.z.record(import_zod6.z.unknown()).optional().describe("Inline record { id, data, published_at? }"),
26516
- records: import_zod6.z.array(import_zod6.z.record(import_zod6.z.unknown())).optional().describe("Inline records for table mode"),
26517
- pane_id: import_zod6.z.string().optional().describe("Stable pane id across updates")
26627
+ title: import_zod7.z.string().describe("Pane title"),
26628
+ mode: import_zod7.z.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
26629
+ datasource: import_zod7.z.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
26630
+ resource_id: import_zod7.z.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
26631
+ record_id: import_zod7.z.string().optional().describe("Record id when opening form mode"),
26632
+ resource: import_zod7.z.record(import_zod7.z.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
26633
+ record: import_zod7.z.record(import_zod7.z.unknown()).optional().describe("Inline record { id, data, published_at? }"),
26634
+ records: import_zod7.z.array(import_zod7.z.record(import_zod7.z.unknown())).optional().describe("Inline records for table mode"),
26635
+ pane_id: import_zod7.z.string().optional().describe("Stable pane id across updates")
26518
26636
  },
26519
26637
  async (args) => {
26520
26638
  const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -26535,10 +26653,10 @@ async function startMcpServer() {
26535
26653
  "present_files",
26536
26654
  "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.",
26537
26655
  {
26538
- title: import_zod6.z.string().optional().describe("Pane title (default: Files)"),
26539
- datasource: import_zod6.z.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
26540
- path: import_zod6.z.string().optional().describe("Initial folder path (e.g. public)"),
26541
- pane_id: import_zod6.z.string().optional().describe("Stable pane id across updates")
26656
+ title: import_zod7.z.string().optional().describe("Pane title (default: Files)"),
26657
+ datasource: import_zod7.z.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
26658
+ path: import_zod7.z.string().optional().describe("Initial folder path (e.g. public)"),
26659
+ pane_id: import_zod7.z.string().optional().describe("Stable pane id across updates")
26542
26660
  },
26543
26661
  async (args) => {
26544
26662
  const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -26557,8 +26675,8 @@ async function startMcpServer() {
26557
26675
  "wait_for_job",
26558
26676
  "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.",
26559
26677
  {
26560
- id: import_zod6.z.string().describe("Detached job id (same kebab-case id passed to detached-job.js start)"),
26561
- timeoutMs: import_zod6.z.number().optional()
26678
+ id: import_zod7.z.string().describe("Detached job id (same kebab-case id passed to detached-job.js start)"),
26679
+ timeoutMs: import_zod7.z.number().optional()
26562
26680
  },
26563
26681
  async ({ id, timeoutMs }) => {
26564
26682
  const result = await waitForDetachedJob(process.cwd(), id, {
@@ -26579,7 +26697,7 @@ async function startMcpServer() {
26579
26697
  "set_caffeinate",
26580
26698
  "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.",
26581
26699
  {
26582
- enabled: import_zod6.z.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
26700
+ enabled: import_zod7.z.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
26583
26701
  },
26584
26702
  async ({ enabled }) => {
26585
26703
  const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
@@ -26617,18 +26735,18 @@ async function startMcpServer() {
26617
26735
  "create_thread",
26618
26736
  `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.`,
26619
26737
  {
26620
- sourceType: import_zod6.z.enum(["branch", "pr", "ticket"]),
26621
- sourceRef: import_zod6.z.string(),
26622
- agent: import_zod6.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
26623
- model: import_zod6.z.string().nullable().optional().describe(
26738
+ sourceType: import_zod7.z.enum(["branch", "pr", "ticket"]),
26739
+ sourceRef: import_zod7.z.string(),
26740
+ agent: import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
26741
+ model: import_zod7.z.string().nullable().optional().describe(
26624
26742
  `Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
26625
26743
  ),
26626
- repoPath: import_zod6.z.string(),
26627
- title: import_zod6.z.string().optional(),
26628
- cowboy: import_zod6.z.boolean().optional().describe(
26744
+ repoPath: import_zod7.z.string(),
26745
+ title: import_zod7.z.string().optional(),
26746
+ cowboy: import_zod7.z.boolean().optional().describe(
26629
26747
  "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."
26630
26748
  ),
26631
- parentThreadId: import_zod6.z.string().optional().describe(
26749
+ parentThreadId: import_zod7.z.string().optional().describe(
26632
26750
  "Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
26633
26751
  )
26634
26752
  },
@@ -26644,10 +26762,10 @@ async function startMcpServer() {
26644
26762
  "start_board_card",
26645
26763
  "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.",
26646
26764
  {
26647
- kind: import_zod6.z.enum(["ticket", "pr", "branch"]),
26648
- ref: import_zod6.z.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
26649
- repoPath: import_zod6.z.string().describe("Workspace path from list_workspaces / list_board"),
26650
- title: import_zod6.z.string().optional()
26765
+ kind: import_zod7.z.enum(["ticket", "pr", "branch"]),
26766
+ ref: import_zod7.z.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
26767
+ repoPath: import_zod7.z.string().describe("Workspace path from list_workspaces / list_board"),
26768
+ title: import_zod7.z.string().optional()
26651
26769
  },
26652
26770
  async ({ kind, ref, repoPath, title }) => {
26653
26771
  const root = await resolveRepoRoot(repoPath);
@@ -26751,9 +26869,9 @@ async function startMcpServer() {
26751
26869
  "send_to_thread",
26752
26870
  '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.',
26753
26871
  {
26754
- ref: import_zod6.z.string(),
26755
- prompt: import_zod6.z.string(),
26756
- force_stop: import_zod6.z.boolean().optional()
26872
+ ref: import_zod7.z.string(),
26873
+ prompt: import_zod7.z.string(),
26874
+ force_stop: import_zod7.z.boolean().optional()
26757
26875
  },
26758
26876
  async ({ ref, prompt, force_stop }) => {
26759
26877
  if (force_stop) {
@@ -26782,8 +26900,8 @@ async function startMcpServer() {
26782
26900
  "wait_for_turn",
26783
26901
  "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).",
26784
26902
  {
26785
- ref: import_zod6.z.string(),
26786
- timeoutMs: import_zod6.z.number().optional()
26903
+ ref: import_zod7.z.string(),
26904
+ timeoutMs: import_zod7.z.number().optional()
26787
26905
  },
26788
26906
  async ({ ref, timeoutMs }) => {
26789
26907
  const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
@@ -26813,7 +26931,7 @@ async function startMcpServer() {
26813
26931
  server.tool(
26814
26932
  "get_turn_result",
26815
26933
  "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).",
26816
- { ref: import_zod6.z.string() },
26934
+ { ref: import_zod7.z.string() },
26817
26935
  async ({ ref }) => {
26818
26936
  const result = orch.getTurnResult(ref);
26819
26937
  return {
@@ -26834,8 +26952,8 @@ async function startMcpServer() {
26834
26952
  "stop_thread",
26835
26953
  "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.",
26836
26954
  {
26837
- ref: import_zod6.z.string(),
26838
- force: import_zod6.z.boolean().optional()
26955
+ ref: import_zod7.z.string(),
26956
+ force: import_zod7.z.boolean().optional()
26839
26957
  },
26840
26958
  async ({ ref, force }) => {
26841
26959
  const t = orch.getThread(ref);
@@ -26865,7 +26983,7 @@ async function startMcpServer() {
26865
26983
  server.tool(
26866
26984
  "archive_thread",
26867
26985
  "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.",
26868
- { ref: import_zod6.z.string() },
26986
+ { ref: import_zod7.z.string() },
26869
26987
  async ({ ref }) => {
26870
26988
  const t = orch.getThread(ref);
26871
26989
  if (!t) {
@@ -26898,7 +27016,7 @@ async function startMcpServer() {
26898
27016
  server.tool(
26899
27017
  "restore_thread",
26900
27018
  "Restore an archived thread (recreates worktree from branch when needed)",
26901
- { ref: import_zod6.z.string() },
27019
+ { ref: import_zod7.z.string() },
26902
27020
  async ({ ref }) => {
26903
27021
  try {
26904
27022
  const restored = await orch.restore(ref);
@@ -26924,8 +27042,8 @@ async function startMcpServer() {
26924
27042
  "get_diff",
26925
27043
  "Compact diff summary (capped hunks, paginated)",
26926
27044
  {
26927
- ref: import_zod6.z.string(),
26928
- maxFiles: import_zod6.z.number().optional()
27045
+ ref: import_zod7.z.string(),
27046
+ maxFiles: import_zod7.z.number().optional()
26929
27047
  },
26930
27048
  async ({ ref, maxFiles }) => {
26931
27049
  const summary = await orch.diffSummary(ref);
@@ -26945,7 +27063,7 @@ async function startMcpServer() {
26945
27063
  server.tool(
26946
27064
  "get_pr_checks",
26947
27065
  "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.",
26948
- { ref: import_zod6.z.string().describe("Worktree thread id/ref") },
27066
+ { ref: import_zod7.z.string().describe("Worktree thread id/ref") },
26949
27067
  async ({ ref }) => {
26950
27068
  try {
26951
27069
  const checks = await orch.getPrChecks(ref);
@@ -26961,7 +27079,7 @@ async function startMcpServer() {
26961
27079
  server.tool(
26962
27080
  "request_review",
26963
27081
  '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.',
26964
- { ref: import_zod6.z.string().describe("Worktree thread id/ref to review") },
27082
+ { ref: import_zod7.z.string().describe("Worktree thread id/ref to review") },
26965
27083
  async ({ ref }) => {
26966
27084
  try {
26967
27085
  const tab = await orch.requestReview(ref);
@@ -26990,8 +27108,8 @@ async function startMcpServer() {
26990
27108
  "ask_git",
26991
27109
  "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.",
26992
27110
  {
26993
- ref: import_zod6.z.string().describe("Worktree thread id/ref"),
26994
- action: import_zod6.z.enum(AGENT_GIT_ACTIONS).describe(
27111
+ ref: import_zod7.z.string().describe("Worktree thread id/ref"),
27112
+ action: import_zod7.z.enum(AGENT_GIT_ACTIONS).describe(
26995
27113
  "commit-push | create-draft | create-web | resolve-conflicts | merge"
26996
27114
  )
26997
27115
  },
@@ -27036,7 +27154,7 @@ async function startMcpServer() {
27036
27154
  }
27037
27155
  }
27038
27156
  );
27039
- const agentEnum = import_zod6.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
27157
+ const agentEnum = import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
27040
27158
  server.tool(
27041
27159
  "list_models",
27042
27160
  "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.",
@@ -27059,11 +27177,11 @@ async function startMcpServer() {
27059
27177
  "fork_worktree",
27060
27178
  "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.",
27061
27179
  {
27062
- ref: import_zod6.z.string().describe("Worktree thread id/ref to fork"),
27063
- through_index: import_zod6.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
27180
+ ref: import_zod7.z.string().describe("Worktree thread id/ref to fork"),
27181
+ through_index: import_zod7.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
27064
27182
  agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
27065
- model: import_zod6.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
27066
- title: import_zod6.z.string().optional()
27183
+ model: import_zod7.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
27184
+ title: import_zod7.z.string().optional()
27067
27185
  },
27068
27186
  async ({ ref, through_index, agent, model, title }) => {
27069
27187
  try {
@@ -27104,11 +27222,11 @@ async function startMcpServer() {
27104
27222
  "fork_chat",
27105
27223
  "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.",
27106
27224
  {
27107
- ref: import_zod6.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
27108
- through_index: import_zod6.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
27225
+ ref: import_zod7.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
27226
+ through_index: import_zod7.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
27109
27227
  agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
27110
- model: import_zod6.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
27111
- title: import_zod6.z.string().optional()
27228
+ model: import_zod7.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
27229
+ title: import_zod7.z.string().optional()
27112
27230
  },
27113
27231
  async ({ ref, through_index, agent, model, title }) => {
27114
27232
  try {
@@ -27154,8 +27272,8 @@ async function startMcpServer() {
27154
27272
  "run_dev_script",
27155
27273
  "Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
27156
27274
  {
27157
- ref: import_zod6.z.string(),
27158
- name: import_zod6.z.string().optional()
27275
+ ref: import_zod7.z.string(),
27276
+ name: import_zod7.z.string().optional()
27159
27277
  },
27160
27278
  async ({ ref, name }) => {
27161
27279
  const result = await orch.startDev(ref, name);
@@ -27177,7 +27295,7 @@ async function startMcpServer() {
27177
27295
  server.tool(
27178
27296
  "list_run_scripts",
27179
27297
  "List named run scripts available for a thread",
27180
- { ref: import_zod6.z.string() },
27298
+ { ref: import_zod7.z.string() },
27181
27299
  async ({ ref }) => {
27182
27300
  const scripts = orch.listThreadRunScripts(ref);
27183
27301
  const active = orch.getActiveRuns(ref);
@@ -27195,8 +27313,8 @@ async function startMcpServer() {
27195
27313
  "stop_dev_script",
27196
27314
  "Stop a running script for a thread (all scripts if name omitted)",
27197
27315
  {
27198
- ref: import_zod6.z.string(),
27199
- name: import_zod6.z.string().optional()
27316
+ ref: import_zod7.z.string(),
27317
+ name: import_zod7.z.string().optional()
27200
27318
  },
27201
27319
  async ({ ref, name }) => {
27202
27320
  orch.stopDev(ref, name);
@@ -27206,7 +27324,7 @@ async function startMcpServer() {
27206
27324
  server.tool(
27207
27325
  "run_setup",
27208
27326
  "Re-run workspace setup (Sideboard/Conductor settings, .cursor/worktrees.json, or script/setup). New worktrees already run this automatically.",
27209
- { ref: import_zod6.z.string() },
27327
+ { ref: import_zod7.z.string() },
27210
27328
  async ({ ref }) => {
27211
27329
  const result = await orch.runSetup(ref);
27212
27330
  return {
@@ -27217,7 +27335,7 @@ async function startMcpServer() {
27217
27335
  server.tool(
27218
27336
  "add_workspace",
27219
27337
  "Register a git repo as a Sideboard workspace",
27220
- { repoPath: import_zod6.z.string() },
27338
+ { repoPath: import_zod7.z.string() },
27221
27339
  async ({ repoPath }) => {
27222
27340
  const ws = await orch.addWorkspace(repoPath);
27223
27341
  return { content: [{ type: "text", text: JSON.stringify(ws) }] };
@@ -27226,7 +27344,7 @@ async function startMcpServer() {
27226
27344
  server.tool(
27227
27345
  "remove_workspace",
27228
27346
  "Unregister a Sideboard workspace (does not archive threads)",
27229
- { repoPath: import_zod6.z.string() },
27347
+ { repoPath: import_zod7.z.string() },
27230
27348
  async ({ repoPath }) => {
27231
27349
  orch.removeWorkspace(repoPath);
27232
27350
  return { content: [{ type: "text", text: "ok" }] };
@@ -27236,12 +27354,12 @@ async function startMcpServer() {
27236
27354
  "fanout",
27237
27355
  "Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
27238
27356
  {
27239
- prompt: import_zod6.z.string(),
27240
- agents: import_zod6.z.array(import_zod6.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
27241
- repoPath: import_zod6.z.string(),
27242
- sourceType: import_zod6.z.enum(["branch", "pr", "ticket"]).optional(),
27243
- sourceRef: import_zod6.z.string().optional(),
27244
- title: import_zod6.z.string().optional()
27357
+ prompt: import_zod7.z.string(),
27358
+ agents: import_zod7.z.array(import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
27359
+ repoPath: import_zod7.z.string(),
27360
+ sourceType: import_zod7.z.enum(["branch", "pr", "ticket"]).optional(),
27361
+ sourceRef: import_zod7.z.string().optional(),
27362
+ title: import_zod7.z.string().optional()
27245
27363
  },
27246
27364
  async (args) => {
27247
27365
  const threads = await orch.bestOfN(args);
@@ -27268,8 +27386,8 @@ async function startMcpServer() {
27268
27386
  "list_branches",
27269
27387
  "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).",
27270
27388
  {
27271
- repoPath: import_zod6.z.string(),
27272
- unmergedOnly: import_zod6.z.boolean().optional()
27389
+ repoPath: import_zod7.z.string(),
27390
+ unmergedOnly: import_zod7.z.boolean().optional()
27273
27391
  },
27274
27392
  async ({ repoPath, unmergedOnly }) => {
27275
27393
  const root = await resolveRepoRoot(repoPath);
@@ -27290,19 +27408,19 @@ async function startMcpServer() {
27290
27408
  "list_prs",
27291
27409
  '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.',
27292
27410
  {
27293
- repoPath: import_zod6.z.string(),
27294
- query: import_zod6.z.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
27295
- queue: import_zod6.z.enum(["review", "mine", "approved", "changes"]).optional().describe(
27411
+ repoPath: import_zod7.z.string(),
27412
+ query: import_zod7.z.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
27413
+ queue: import_zod7.z.enum(["review", "mine", "approved", "changes"]).optional().describe(
27296
27414
  'review = unclaimed eng-review inbox (use for "get me N tickets to review"). mine = review-requested:@me. approved / changes = eng-approved / eng-requested-changes.'
27297
27415
  ),
27298
- state: import_zod6.z.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
27299
- label: import_zod6.z.string().optional().describe(
27416
+ state: import_zod7.z.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
27417
+ label: import_zod7.z.string().optional().describe(
27300
27418
  "GitHub label / workflow tag. Comma-separated AND. Examples: eng-review, eng-approved, eng-requested-changes"
27301
27419
  ),
27302
- reviewer: import_zod6.z.string().optional().describe(
27420
+ reviewer: import_zod7.z.string().optional().describe(
27303
27421
  "me (review requested of you), unassigned (no individual reviewer; team queues like engineering-team still count), all, or a GitHub login"
27304
27422
  ),
27305
- limit: import_zod6.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
27423
+ limit: import_zod7.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
27306
27424
  },
27307
27425
  async ({ repoPath, query, queue, state, label, reviewer, limit }) => {
27308
27426
  const root = await resolveRepoRoot(repoPath);
@@ -27334,7 +27452,7 @@ async function startMcpServer() {
27334
27452
  server.tool(
27335
27453
  "get_pr_stack",
27336
27454
  "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).",
27337
- { ref: import_zod6.z.string() },
27455
+ { ref: import_zod7.z.string() },
27338
27456
  async ({ ref }) => {
27339
27457
  const stack = await orch.getPrStack(ref);
27340
27458
  return {
@@ -27346,8 +27464,8 @@ async function startMcpServer() {
27346
27464
  "open_pr_stack_layers",
27347
27465
  "Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
27348
27466
  {
27349
- ref: import_zod6.z.string(),
27350
- layer: import_zod6.z.number().int().positive().optional()
27467
+ ref: import_zod7.z.string(),
27468
+ layer: import_zod7.z.number().int().positive().optional()
27351
27469
  },
27352
27470
  async ({ ref, layer }) => {
27353
27471
  const result = await orch.openPrStackLayers(ref, { layer });
@@ -27381,9 +27499,9 @@ async function startMcpServer() {
27381
27499
  "add_stack_layer",
27382
27500
  "Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
27383
27501
  {
27384
- ref: import_zod6.z.string(),
27385
- branchName: import_zod6.z.string(),
27386
- title: import_zod6.z.string().optional()
27502
+ ref: import_zod7.z.string(),
27503
+ branchName: import_zod7.z.string(),
27504
+ title: import_zod7.z.string().optional()
27387
27505
  },
27388
27506
  async ({ ref, branchName, title }) => {
27389
27507
  const result = await orch.addStackLayer(ref, branchName, { title });
@@ -27412,11 +27530,11 @@ async function startMcpServer() {
27412
27530
  "create_pr_stack",
27413
27531
  "Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
27414
27532
  {
27415
- repoPath: import_zod6.z.string(),
27416
- branches: import_zod6.z.array(import_zod6.z.string()).min(1),
27417
- agent: import_zod6.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
27418
- base: import_zod6.z.string().optional(),
27419
- title: import_zod6.z.string().optional()
27533
+ repoPath: import_zod7.z.string(),
27534
+ branches: import_zod7.z.array(import_zod7.z.string()).min(1),
27535
+ agent: import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
27536
+ base: import_zod7.z.string().optional(),
27537
+ title: import_zod7.z.string().optional()
27420
27538
  },
27421
27539
  async (args) => {
27422
27540
  const result = await orch.createPrStack({
@@ -27455,10 +27573,10 @@ async function startMcpServer() {
27455
27573
  "list_issues",
27456
27574
  "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.",
27457
27575
  {
27458
- repoPath: import_zod6.z.string(),
27459
- query: import_zod6.z.string().optional().describe("Search title, identifier, or description"),
27460
- assignee: import_zod6.z.string().optional().describe("me (Linear default), unassigned, all, a user id, or a GitHub login"),
27461
- limit: import_zod6.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
27576
+ repoPath: import_zod7.z.string(),
27577
+ query: import_zod7.z.string().optional().describe("Search title, identifier, or description"),
27578
+ assignee: import_zod7.z.string().optional().describe("me (Linear default), unassigned, all, a user id, or a GitHub login"),
27579
+ limit: import_zod7.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
27462
27580
  },
27463
27581
  async ({ repoPath, query, assignee, limit }) => {
27464
27582
  const root = await resolveRepoRoot(repoPath);
@@ -27598,8 +27716,8 @@ var BrightsySideboardApi = class {
27598
27716
  };
27599
27717
  function taskMessageText(task) {
27600
27718
  const parts = task.message?.parts ?? [];
27601
- const text6 = parts.find((p) => p.kind === "text")?.text ?? "";
27602
- return text6.trim();
27719
+ const text7 = parts.find((p) => p.kind === "text")?.text ?? "";
27720
+ return text7.trim();
27603
27721
  }
27604
27722
 
27605
27723
  // src/brightsy/cloud-connect.ts
@@ -28101,11 +28219,11 @@ var import_ws = require("ws");
28101
28219
  init_api();
28102
28220
  var BACKOFF_START_MS = 1e3;
28103
28221
  var BACKOFF_MAX_MS = 3e4;
28104
- function stripSlackMentions(text6) {
28105
- return text6.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
28222
+ function stripSlackMentions(text7) {
28223
+ return text7.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
28106
28224
  }
28107
- function isSlackStopCommand(text6) {
28108
- const t = stripSlackMentions(text6).toLowerCase();
28225
+ function isSlackStopCommand(text7) {
28226
+ const t = stripSlackMentions(text7).toLowerCase();
28109
28227
  return t === "stop" || t === "sideboard_force_stop";
28110
28228
  }
28111
28229
  function parseSlackSocketFrame(raw) {
@@ -28127,8 +28245,8 @@ function inboundFromSocketFrame(frame) {
28127
28245
  const ts = event.ts?.trim();
28128
28246
  if (!channelId || !ts) return null;
28129
28247
  const rawText = event.text?.trim() ?? "";
28130
- const text6 = stripSlackMentions(rawText);
28131
- if (!text6) return null;
28248
+ const text7 = stripSlackMentions(rawText);
28249
+ if (!text7) return null;
28132
28250
  const teamId = (frame.payload?.team_id || event.team || "").trim();
28133
28251
  if (!teamId) return null;
28134
28252
  const isDm = event.type === "message" && (event.channel_type === "im" || event.channel_type === "mpim" || !event.channel_type && channelId.startsWith("D"));
@@ -28140,7 +28258,7 @@ function inboundFromSocketFrame(frame) {
28140
28258
  ts,
28141
28259
  threadTs: event.thread_ts?.trim() || void 0,
28142
28260
  userId: event.user?.trim(),
28143
- text: text6,
28261
+ text: text7,
28144
28262
  kind: isMention ? "mention" : "dm"
28145
28263
  };
28146
28264
  }
@@ -28684,12 +28802,12 @@ function formatSlackInboundPrompt(msg) {
28684
28802
 
28685
28803
  ${msg.text}`;
28686
28804
  }
28687
- function isSlackInboundUserPrompt(text6) {
28688
- return text6.startsWith("Slack DM\n") || text6.startsWith("Slack @mention\n");
28805
+ function isSlackInboundUserPrompt(text7) {
28806
+ return text7.startsWith("Slack DM\n") || text7.startsWith("Slack @mention\n");
28689
28807
  }
28690
- function formatSlackSignedReply(deviceLabel, text6) {
28808
+ function formatSlackSignedReply(deviceLabel, text7) {
28691
28809
  const label = deviceLabel.trim();
28692
- const body = text6.trim();
28810
+ const body = text7.trim();
28693
28811
  if (!body) return body;
28694
28812
  if (!label) return body;
28695
28813
  const head = `${label}:`;
@@ -28698,8 +28816,8 @@ function formatSlackSignedReply(deviceLabel, text6) {
28698
28816
  }
28699
28817
  return `${label}: ${body}`;
28700
28818
  }
28701
- function signForThisMac(text6) {
28702
- return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel, text6);
28819
+ function signForThisMac(text7) {
28820
+ return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel, text7);
28703
28821
  }
28704
28822
  function slackReplyThreadTs(msg) {
28705
28823
  if (msg.threadTs && msg.threadTs !== msg.ts) return msg.threadTs;
@@ -28760,9 +28878,9 @@ function replyStub(target) {
28760
28878
  kind: "dm"
28761
28879
  };
28762
28880
  }
28763
- async function postSlackText(target, text6, opts) {
28881
+ async function postSlackText(target, text7, opts) {
28764
28882
  if (opts.postReply) {
28765
- const result = await opts.postReply(replyStub(target), text6);
28883
+ const result = await opts.postReply(replyStub(target), text7);
28766
28884
  const ts2 = result && typeof result === "object" && typeof result.ts === "string" ? result.ts.trim() : "";
28767
28885
  return ts2 ? { ts: ts2 } : null;
28768
28886
  }
@@ -28772,7 +28890,7 @@ async function postSlackText(target, text6, opts) {
28772
28890
  "chat.postMessage",
28773
28891
  {
28774
28892
  channel: target.channelId,
28775
- text: text6,
28893
+ text: text7,
28776
28894
  thread_ts: target.threadTs
28777
28895
  },
28778
28896
  opts.fetchImpl
@@ -28780,9 +28898,9 @@ async function postSlackText(target, text6, opts) {
28780
28898
  const ts = json.ts?.trim();
28781
28899
  return ts ? { ts } : null;
28782
28900
  }
28783
- async function updateSlackText(target, ts, text6, opts) {
28901
+ async function updateSlackText(target, ts, text7, opts) {
28784
28902
  if (opts.updateReply) {
28785
- await opts.updateReply(replyStub(target), ts, text6);
28903
+ await opts.updateReply(replyStub(target), ts, text7);
28786
28904
  return;
28787
28905
  }
28788
28906
  const token = writeTokenForTeam(target.teamId);
@@ -28792,7 +28910,7 @@ async function updateSlackText(target, ts, text6, opts) {
28792
28910
  {
28793
28911
  channel: target.channelId,
28794
28912
  ts,
28795
- text: text6
28913
+ text: text7
28796
28914
  },
28797
28915
  opts.fetchImpl
28798
28916
  );
@@ -28820,8 +28938,8 @@ function replyTargetOf(msg) {
28820
28938
  threadTs: slackReplyThreadTs(msg)
28821
28939
  };
28822
28940
  }
28823
- async function postSlackReply(msg, text6, opts) {
28824
- await postSlackText(replyTargetOf(msg), signForThisMac(text6), opts);
28941
+ async function postSlackReply(msg, text7, opts) {
28942
+ await postSlackText(replyTargetOf(msg), signForThisMac(text7), opts);
28825
28943
  }
28826
28944
  function startSlackTurnProgress(msg, threadId, opts) {
28827
28945
  const log = opts.onLog ?? (() => void 0);
@@ -28904,10 +29022,10 @@ function startSlackTurnProgress(msg, threadId, opts) {
28904
29022
  }
28905
29023
  });
28906
29024
  },
28907
- finishWith: (text6) => {
29025
+ finishWith: (text7) => {
28908
29026
  stop();
28909
29027
  return run2(async () => {
28910
- const signed = signForThisMac(text6.trim());
29028
+ const signed = signForThisMac(text7.trim());
28911
29029
  if (!signed) {
28912
29030
  if (!postedTs) return;
28913
29031
  const ts = postedTs;
@@ -28935,11 +29053,11 @@ function startSlackTurnProgress(msg, threadId, opts) {
28935
29053
  };
28936
29054
  }
28937
29055
  var lastRelayed = /* @__PURE__ */ new Map();
28938
- function markRelayed(threadId, text6) {
28939
- lastRelayed.set(threadId, `${threadId}:${text6}`);
29056
+ function markRelayed(threadId, text7) {
29057
+ lastRelayed.set(threadId, `${threadId}:${text7}`);
28940
29058
  }
28941
- function alreadyRelayed(threadId, text6) {
28942
- return lastRelayed.get(threadId) === `${threadId}:${text6}`;
29059
+ function alreadyRelayed(threadId, text7) {
29060
+ return lastRelayed.get(threadId) === `${threadId}:${text7}`;
28943
29061
  }
28944
29062
  async function relayCoordinatorReplyToSlack(threadId, opts) {
28945
29063
  const target = getSlackReplyTarget(threadId);
@@ -28948,14 +29066,14 @@ async function relayCoordinatorReplyToSlack(threadId, opts) {
28948
29066
  const lastUser = thread ? [...thread.messages].reverse().find((m) => m.role === "user") : void 0;
28949
29067
  if (lastUser && isSlackInboundUserPrompt(lastUser.text)) return;
28950
29068
  const result = getOrchestrator().getTurnResult(threadId);
28951
- const text6 = result.text.trim();
28952
- if (!text6) return;
28953
- if (alreadyRelayed(threadId, text6)) return;
28954
- markRelayed(threadId, text6);
29069
+ const text7 = result.text.trim();
29070
+ if (!text7) return;
29071
+ if (alreadyRelayed(threadId, text7)) return;
29072
+ markRelayed(threadId, text7);
28955
29073
  const log = opts.onLog ?? (() => void 0);
28956
29074
  try {
28957
- await postSlackText(target, signForThisMac(text6), opts);
28958
- log(`replied ${target.threadTs ?? target.channelId} (${text6.length} chars)`);
29075
+ await postSlackText(target, signForThisMac(text7), opts);
29076
+ log(`replied ${target.threadTs ?? target.channelId} (${text7.length} chars)`);
28959
29077
  } catch (err) {
28960
29078
  lastRelayed.delete(threadId);
28961
29079
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -29204,10 +29322,10 @@ function resolveSlackListenMode(opts) {
29204
29322
 
29205
29323
  // src/slack/relay-hub.ts
29206
29324
  init_api();
29207
- function parseSlackDeviceDestination(text6) {
29208
- const m = text6.match(/^\s*(?:to\s+)?(?:@|#)?([A-Za-z][\w-]{0,63})\s*[::]\s*/);
29209
- if (!m) return { label: null, rest: text6 };
29210
- return { label: m[1], rest: text6.slice(m[0].length) };
29325
+ function parseSlackDeviceDestination(text7) {
29326
+ const m = text7.match(/^\s*(?:to\s+)?(?:@|#)?([A-Za-z][\w-]{0,63})\s*[::]\s*/);
29327
+ if (!m) return { label: null, rest: text7 };
29328
+ return { label: m[1], rest: text7.slice(m[0].length) };
29211
29329
  }
29212
29330
  var SlackRelayHub = class {
29213
29331
  sessions = /* @__PURE__ */ new Map();
@@ -29733,10 +29851,6 @@ init_outbound_watch();
29733
29851
  // Annotate the CommonJS export names for ESM import in node:
29734
29852
  0 && (module.exports = {
29735
29853
  ABLETIME_MCP_PATH,
29736
- ACCOUNT_ROLES,
29737
- ACCOUNT_ROLE_LABELS,
29738
- ACCOUNT_ROLE_MAX,
29739
- ACCOUNT_ROLE_PRESETS,
29740
29854
  AGENT_GIT_ACTIONS,
29741
29855
  AGENT_RUNNER_MAX_OLD_SPACE_MB,
29742
29856
  ATTACHMENTS_DIR,
@@ -29823,7 +29937,6 @@ init_outbound_watch();
29823
29937
  WORKTREE_MCP_TOOLS,
29824
29938
  abletimeMcpRequest,
29825
29939
  abletimeMcpUrl,
29826
- accountRoleLabel,
29827
29940
  ackSlackInboundSeen,
29828
29941
  addBoardPin,
29829
29942
  addPrStackLayer,
@@ -30004,6 +30117,7 @@ init_outbound_watch();
30004
30117
  fireSchedule,
30005
30118
  flattenTurnInput,
30006
30119
  flipLinearRelationType,
30120
+ foldLegacyRolesIntoNotes,
30007
30121
  followUpBehavior,
30008
30122
  forkChatTab,
30009
30123
  forkMessageSlice,
@@ -30044,6 +30158,8 @@ init_outbound_watch();
30044
30158
  formatSlackWorkingText,
30045
30159
  formatTranscriptMarkdown,
30046
30160
  formatUiReminder,
30161
+ formatViewerContextDirective,
30162
+ formatViewerContextReminder,
30047
30163
  formatWorkspaceInventory,
30048
30164
  formatWorkspaceProfileSuffix,
30049
30165
  formatWorktreeDirective,
@@ -30281,7 +30397,6 @@ init_outbound_watch();
30281
30397
  planQuestionsSignature,
30282
30398
  pollSlackOutboundWatches,
30283
30399
  posixShellSingleQuote,
30284
- preferTeamsForRole,
30285
30400
  preferredCursorCostCents,
30286
30401
  prepareTerminalCommand,
30287
30402
  previewLand,
@@ -30293,6 +30408,7 @@ init_outbound_watch();
30293
30408
  readSetupLog,
30294
30409
  readSkillBody,
30295
30410
  readThread,
30411
+ readViewerContext,
30296
30412
  readWorktreeFile,
30297
30413
  readWorktreeFileForUpload,
30298
30414
  readWorktreeInclude,
@@ -30349,7 +30465,6 @@ init_outbound_watch();
30349
30465
  resolveViewerProfile,
30350
30466
  resolveViewerProfileForRepo,
30351
30467
  resolveWorktreeStartPoint,
30352
- reviewTeamHintsForRoles,
30353
30468
  rewriteAbleTimeError,
30354
30469
  rewriteLinearError,
30355
30470
  run,
@@ -30483,5 +30598,6 @@ init_outbound_watch();
30483
30598
  writeInjectedMcpConfig,
30484
30599
  writePlanFile,
30485
30600
  writeThread,
30601
+ writeViewerContext,
30486
30602
  writeWorktreeFile
30487
30603
  });