@sideboard-ai/core 0.1.158 → 0.1.160

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/{abletime-GWZE6OYM.js → abletime-AGF54TNA.js} +2 -2
  2. package/dist/{abletime-BEIDJG7W.js → abletime-BMS53RIZ.js} +2 -2
  3. package/dist/{agents-RGE7PSUY.js → agents-6UOXBORU.js} +4 -4
  4. package/dist/{agents-E3ZPWZRS.js → agents-RD774UEV.js} +4 -4
  5. package/dist/{app-settings-VRQI3FIE.js → app-settings-BRLZQA7N.js} +13 -17
  6. package/dist/{app-settings-H3INSR67.js → app-settings-JJQDWPEK.js} +13 -17
  7. package/dist/{chunk-EPQIOAAY.js → chunk-7OWRFJK6.js} +7 -5
  8. package/dist/{chunk-KYKNFJK5.js → chunk-BQ4PEXIF.js} +39 -20
  9. package/dist/{chunk-3BWVOUQG.js → chunk-CVZ4KXVP.js} +127 -113
  10. package/dist/{chunk-3SUZMPGL.js → chunk-HZROPNYL.js} +12 -13
  11. package/dist/{chunk-DVJBO3M7.js → chunk-ILXKJ4ZD.js} +1 -1
  12. package/dist/{chunk-A7QXLYBN.js → chunk-IO5WGZRR.js} +3 -13
  13. package/dist/{chunk-N27GVFZY.js → chunk-KJFFQAMA.js} +1 -1
  14. package/dist/{chunk-D7EGJ3EI.js → chunk-LTXS26K5.js} +127 -113
  15. package/dist/{chunk-CVR4RJ6G.js → chunk-QANLIT2E.js} +12 -13
  16. package/dist/{chunk-FBFIOVDH.js → chunk-QYKADGPP.js} +38 -19
  17. package/dist/{chunk-BLRHO7XQ.js → chunk-RQNXLIXO.js} +10 -6
  18. package/dist/{chunk-LQMFCS54.js → chunk-UUEBNUEG.js} +3 -13
  19. package/dist/{coordinator-prompt-26IJU2AV.js → coordinator-prompt-X3LXE5Q4.js} +3 -3
  20. package/dist/{coordinator-prompt-GZESIQNH.js → coordinator-prompt-YYUXC7EM.js} +3 -3
  21. package/dist/{global-workspace-REI55FZ6.js → global-workspace-E6S5XQZF.js} +3 -3
  22. package/dist/{global-workspace-5BIW26YR.js → global-workspace-SPR2Y4YQ.js} +3 -3
  23. package/dist/index.cjs +599 -475
  24. package/dist/index.d.cts +62 -57
  25. package/dist/index.d.ts +62 -57
  26. package/dist/index.js +325 -230
  27. package/dist/mcp/run-stdio.cjs +528 -402
  28. package/dist/mcp/run-stdio.js +265 -163
  29. package/dist/{orchestrator-ZVW2AC6B.js → orchestrator-BSVSOTBK.js} +5 -5
  30. package/dist/{orchestrator-J727WIJY.js → orchestrator-KVKPIUVU.js} +5 -5
  31. package/dist/{workspaces-NYSM2EPX.js → workspaces-MZELP4BI.js} +3 -3
  32. package/dist/{workspaces-OCEPZXFD.js → workspaces-OK2SNNQF.js} +3 -3
  33. package/dist/{worktree-VTMBZKY6.js → worktree-BX53JZHO.js} +2 -2
  34. package/dist/{worktree-T4SWFSTT.js → worktree-IY7YCEGH.js} +2 -2
  35. package/package.json +1 -1
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)}`;
663
+ }
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");
702
673
  }
703
- var ACCOUNT_ROLE_PRESETS, ACCOUNT_ROLES, ACCOUNT_ROLE_MAX, PROFILE_NOTES_MAX, ACCOUNT_ROLE_LABELS, ROLE_TEAM_HINTS;
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 {};
@@ -1357,14 +1317,19 @@ function findProjectProfileKey(projects, repoPath) {
1357
1317
  const want = profileRepoKey(repoPath ?? "");
1358
1318
  if (!want) return void 0;
1359
1319
  if (projects[want]) return want;
1320
+ let best;
1321
+ let bestLen = 0;
1360
1322
  for (const key of Object.keys(projects)) {
1361
1323
  const stored = profileRepoKey(key);
1362
1324
  if (!stored) continue;
1363
- if (stored === want || want.startsWith(`${stored}/`) || stored.startsWith(`${want}/`)) {
1364
- return key;
1325
+ if (stored === want || want.startsWith(`${stored}/`)) {
1326
+ if (stored.length > bestLen) {
1327
+ best = key;
1328
+ bestLen = stored.length;
1329
+ }
1365
1330
  }
1366
1331
  }
1367
- return void 0;
1332
+ return best;
1368
1333
  }
1369
1334
  function normalizeAdvanced(raw) {
1370
1335
  if (!raw || typeof raw !== "object") return {};
@@ -1899,17 +1864,13 @@ function updateDefaultsSettings(patch) {
1899
1864
  defaults.fast = Boolean(patch.fast);
1900
1865
  }
1901
1866
  }
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
1867
  if ("notes" in patch) {
1909
1868
  const notes = normalizeProfileNotes(patch.notes);
1910
1869
  if (!notes) delete defaults.notes;
1911
1870
  else defaults.notes = notes;
1912
1871
  }
1872
+ delete defaults.roles;
1873
+ delete defaults.role;
1913
1874
  return saveAppSettings({ ...current, defaults });
1914
1875
  }
1915
1876
  function updateProjectProfileSettings(repoPath, patch) {
@@ -1919,17 +1880,14 @@ function updateProjectProfileSettings(repoPath, patch) {
1919
1880
  const projects = { ...current.projects };
1920
1881
  const existing = projects[key] ?? {};
1921
1882
  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
1883
  if ("notes" in patch) {
1928
1884
  const notes = normalizeProfileNotes(patch.notes);
1929
1885
  if (!notes) delete next.notes;
1930
1886
  else next.notes = notes;
1931
1887
  }
1932
- if (!next.roles?.length && !next.notes) delete projects[key];
1888
+ delete next.roles;
1889
+ delete next.role;
1890
+ if (!next.notes) delete projects[key];
1933
1891
  else projects[key] = next;
1934
1892
  return saveAppSettings({ ...current, projects });
1935
1893
  }
@@ -1954,6 +1912,60 @@ function resolveViewerProfileForRepo(repoPath, settings = loadAppSettings()) {
1954
1912
  const project = key ? settings.projects[key] : void 0;
1955
1913
  return resolveViewerProfile(settings.defaults, project);
1956
1914
  }
1915
+ function readViewerContext(repoPath) {
1916
+ const settings = loadAppSettings();
1917
+ const key = findProjectProfileKey(settings.projects, repoPath);
1918
+ const resolved = resolveViewerProfile(
1919
+ settings.defaults,
1920
+ key ? settings.projects[key] : void 0
1921
+ );
1922
+ return {
1923
+ account: resolved.accountNotes,
1924
+ project: resolved.projectNotes,
1925
+ combined: resolved.notes,
1926
+ repoPath: key ?? (repoPath?.trim() ? profileRepoKey(repoPath) : null)
1927
+ };
1928
+ }
1929
+ function writeViewerContext(input) {
1930
+ const proposed = normalizeProfileNotes(input.context);
1931
+ if (input.scope === "project") {
1932
+ const settings = loadAppSettings();
1933
+ const key = findProjectProfileKey(settings.projects, input.repoPath) ?? profileRepoKey(input.repoPath ?? "");
1934
+ if (!key) {
1935
+ return {
1936
+ ok: false,
1937
+ error: "repoPath required",
1938
+ message: "Pass repoPath (or run from a registered worktree) when updating project context.",
1939
+ current: "",
1940
+ proposed
1941
+ };
1942
+ }
1943
+ const current2 = resolveViewerProfileForRepo(key, settings).projectNotes;
1944
+ if (!input.confirmed) {
1945
+ return {
1946
+ ok: false,
1947
+ error: "Confirmation required",
1948
+ message: CONFIRM_PROJECT,
1949
+ current: current2,
1950
+ proposed
1951
+ };
1952
+ }
1953
+ updateProjectProfileSettings(key, { notes: proposed || null });
1954
+ return { ok: true, scope: "project", context: proposed, repoPath: key };
1955
+ }
1956
+ const current = resolveAccountProfileFromSettings().accountNotes;
1957
+ if (!input.confirmed) {
1958
+ return {
1959
+ ok: false,
1960
+ error: "Confirmation required",
1961
+ message: CONFIRM_ACCOUNT,
1962
+ current,
1963
+ proposed
1964
+ };
1965
+ }
1966
+ updateDefaultsSettings({ notes: proposed || null });
1967
+ return { ok: true, scope: "account", context: proposed };
1968
+ }
1957
1969
  function resolveThreadDefaults(settings = loadAppSettings()) {
1958
1970
  return {
1959
1971
  agent: getDefaultAgent(settings),
@@ -2300,7 +2312,7 @@ function childEnvWithAppSettings(extra) {
2300
2312
  function harnessEnvKey(harness) {
2301
2313
  return HARNESS_ENV_KEYS[harness];
2302
2314
  }
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;
2315
+ 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
2316
  var init_app_settings = __esm({
2305
2317
  "src/store/app-settings.ts"() {
2306
2318
  "use strict";
@@ -2351,6 +2363,8 @@ var init_app_settings = __esm({
2351
2363
  projects: {},
2352
2364
  advanced: {}
2353
2365
  };
2366
+ 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.";
2367
+ 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
2368
  DEFAULT_CLI_BIN = {
2355
2369
  claude: "claude",
2356
2370
  codex: "codex",
@@ -4373,12 +4387,12 @@ var init_worktree_labels = __esm({
4373
4387
  });
4374
4388
 
4375
4389
  // 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);
4390
+ function isGhRateLimitError(text7) {
4391
+ return /API rate limit (already )?exceeded/i.test(text7) || /rate limit exceeded/i.test(text7);
4378
4392
  }
4379
- function isGhPrBodyTooLongError(text6) {
4393
+ function isGhPrBodyTooLongError(text7) {
4380
4394
  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
4395
+ text7
4382
4396
  );
4383
4397
  }
4384
4398
  function clampGithubPrBody(body, max = GITHUB_PR_BODY_SAFE_CHARS) {
@@ -4398,21 +4412,21 @@ function formatRateLimitResetHint(resetEpochSec, nowMs = Date.now()) {
4398
4412
  const hours = Math.ceil(mins / 60);
4399
4413
  return `in about ${hours} hour${hours === 1 ? "" : "s"}`;
4400
4414
  }
4401
- function omitGhBodyArg(text6) {
4402
- return text6.replace(
4415
+ function omitGhBodyArg(text7) {
4416
+ return text7.replace(
4403
4417
  /--body(?:-file)?(\s+|=)(?:'[^']*'|"[^"]*"|\S+)/g,
4404
4418
  "--body <omitted>"
4405
4419
  );
4406
4420
  }
4407
- function capGhErrorText(text6, max = 400) {
4408
- const t = text6.trim();
4421
+ function capGhErrorText(text7, max = 400) {
4422
+ const t = text7.trim();
4409
4423
  if (t.length <= max) return t;
4410
4424
  return `${t.slice(0, max - 1).trimEnd()}\u2026`;
4411
4425
  }
4412
- function extractGhErrorDetail(text6) {
4413
- const trimmed = omitGhBodyArg(text6.trim());
4426
+ function extractGhErrorDetail(text7) {
4427
+ const trimmed = omitGhBodyArg(text7.trim());
4414
4428
  if (!trimmed) return "";
4415
- if (isGhPrBodyTooLongError(text6) || isGhPrBodyTooLongError(trimmed)) {
4429
+ if (isGhPrBodyTooLongError(text7) || isGhPrBodyTooLongError(trimmed)) {
4416
4430
  return "GraphQL: body is too long (GitHub PR body / request size limit).";
4417
4431
  }
4418
4432
  const graphql = trimmed.match(/\bGraphQL:\s*(.+)$/im);
@@ -4452,9 +4466,9 @@ function formatGhLandError(raw, opts) {
4452
4466
  }
4453
4467
  return detail || "Failed to create or update pull request";
4454
4468
  }
4455
- function isPrNotMergeableError(text6) {
4469
+ function isPrNotMergeableError(text7) {
4456
4470
  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
4471
+ text7
4458
4472
  );
4459
4473
  }
4460
4474
  function formatMergePrError(raw) {
@@ -4666,8 +4680,8 @@ var init_github_agent_auth = __esm({
4666
4680
  });
4667
4681
 
4668
4682
  // src/git/stale-lock.ts
4669
- function isIndexLockError(text6) {
4670
- return /Unable to create ['"][^'"]*index\.lock['"]: File exists/i.test(text6);
4683
+ function isIndexLockError(text7) {
4684
+ return /Unable to create ['"][^'"]*index\.lock['"]: File exists/i.test(text7);
4671
4685
  }
4672
4686
  function clearStaleIndexLock(gitDir, maxAgeMs = STALE_INDEX_LOCK_MS, now = Date.now()) {
4673
4687
  const lockPath = (0, import_node_path17.join)(gitDir, "index.lock");
@@ -6090,14 +6104,7 @@ async function listPrs(repoPath, opts) {
6090
6104
  const limit = resolved.limit ?? 200;
6091
6105
  const reviewerKey = (resolved.reviewer ?? "").trim().toLowerCase();
6092
6106
  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);
6107
+ const viewerTeams = resolved.viewerTeams ?? (needsUnclaimed ? await listGithubViewerTeamSlugs(repoPath).catch(() => []) : void 0);
6101
6108
  const fetchLimit = prListFetchLimit(limit, resolved.reviewer);
6102
6109
  const args = buildGhPrListArgs({
6103
6110
  slug,
@@ -7430,16 +7437,15 @@ function accountRolePlaybookLine() {
7430
7437
  return "";
7431
7438
  }
7432
7439
  }
7433
- function accountRoleReminderLine() {
7440
+ function accountContextReminderLine() {
7434
7441
  let profile;
7435
7442
  try {
7436
7443
  profile = resolveAccountProfileFromSettings();
7437
7444
  } catch {
7438
7445
  return "";
7439
7446
  }
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.`;
7447
+ if (!profile.accountNotes) return "";
7448
+ 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
7449
  }
7444
7450
  function projectProfilePlaybookBlock() {
7445
7451
  let settings;
@@ -7456,7 +7462,6 @@ function projectProfilePlaybookBlock() {
7456
7462
  const key = path2.replace(/\/+$/, "");
7457
7463
  return {
7458
7464
  name: names.get(key) ?? key.split("/").filter(Boolean).pop() ?? path2,
7459
- roleLabels: resolved.rolesFromProject ? resolved.roleLabels : [],
7460
7465
  notes: resolved.projectNotes
7461
7466
  };
7462
7467
  });
@@ -7470,7 +7475,7 @@ function coordinatorTurnReminder(opts) {
7470
7475
  `- YOUR orchestration thread id is ${opts.parentId} \u2014 pass parentThreadId="${opts.parentId}" on create_thread, or omit it.`,
7471
7476
  goal ? `- Goal / title: ${goal}` : null,
7472
7477
  accountDefaultsPlaybookLine(),
7473
- accountRoleReminderLine() || null,
7478
+ accountContextReminderLine() || null,
7474
7479
  "- 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
7480
  ].filter(Boolean).join("\n");
7476
7481
  }
@@ -7524,7 +7529,7 @@ function ensureGlobalCoordinatorCwd(opts) {
7524
7529
  "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
7530
  "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
7531
  "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.",
7532
+ "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
7533
  '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
7534
  "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
7535
  "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 +7583,10 @@ var init_coordinator_prompt = __esm({
7578
7583
  "Role: you oversee Sideboard worktree agents across registered repos. You do not live inside one of those worktrees.",
7579
7584
  "Sideboard MCP (fleet control \u2014 prefer these for status and orchestration):",
7580
7585
  "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.",
7586
+ "- 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
7587
  "- 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.",
7588
+ '- 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).',
7589
+ "- 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
7590
  "- 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
7591
  "- 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
7592
  "- 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 +7596,8 @@ var init_coordinator_prompt = __esm({
7591
7596
  "- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
7592
7597
  "- list_models \u2014 only when you need a specific model (rare); otherwise omit model so Account defaults apply",
7593
7598
  "- 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.",
7599
+ "- 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.",
7600
+ "- 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
7601
  "- 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
7602
  "- 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
7603
  "Workspaces:",
@@ -8126,8 +8132,8 @@ ${input.permalink}` : "";
8126
8132
 
8127
8133
  ${body}${link}`;
8128
8134
  }
8129
- function isSlackExternalReplyPrompt(text6) {
8130
- return text6.startsWith("Slack reply from ") && text6.includes("not a command");
8135
+ function isSlackExternalReplyPrompt(text7) {
8136
+ return text7.startsWith("Slack reply from ") && text7.includes("not a command");
8131
8137
  }
8132
8138
  function pendingSlackExternalReplies(messages) {
8133
8139
  let i = messages.length - 1;
@@ -8168,9 +8174,9 @@ async function continueSourceThread(threadId, prompt) {
8168
8174
  function listSlackOutboundWatches() {
8169
8175
  return pruneWatches(readStore3());
8170
8176
  }
8171
- function formatOwnerSlackFyi(userName, text6) {
8177
+ function formatOwnerSlackFyi(userName, text7) {
8172
8178
  const who = userName.trim() || "Someone";
8173
- const body = text6.trim() || "(no text)";
8179
+ const body = text7.trim() || "(no text)";
8174
8180
  return `${who} replied in Slack:
8175
8181
  ${body}`;
8176
8182
  }
@@ -8185,7 +8191,7 @@ async function relayExternalReply(opts) {
8185
8191
  const thread = readThread(threadId);
8186
8192
  if (!thread || thread.status === "archived") return "ignored";
8187
8193
  try {
8188
- const text6 = formatSlackExternalReplyPrompt({
8194
+ const text7 = formatSlackExternalReplyPrompt({
8189
8195
  userName: opts.reply.userName,
8190
8196
  kind: opts.watch.kind,
8191
8197
  toLabel: opts.watch.toLabel,
@@ -8194,7 +8200,7 @@ async function relayExternalReply(opts) {
8194
8200
  });
8195
8201
  appendMessage(threadId, {
8196
8202
  role: "agent",
8197
- text: text6,
8203
+ text: text7,
8198
8204
  ts: (/* @__PURE__ */ new Date()).toISOString()
8199
8205
  });
8200
8206
  } catch {
@@ -8508,20 +8514,20 @@ function looksLikeMinifiedJsDump(line) {
8508
8514
  function looksLikeNestedElectronCrash(line) {
8509
8515
  return /HasCustomHostObject|ElectronInitializeICUandStartNode/i.test(line);
8510
8516
  }
8511
- function looksLikeNativeEventLoopCrash(text6) {
8512
- return /uv_run|uv__io_poll|SpinEventLoopInternal/i.test(text6);
8517
+ function looksLikeNativeEventLoopCrash(text7) {
8518
+ return /uv_run|uv__io_poll|SpinEventLoopInternal/i.test(text7);
8513
8519
  }
8514
- function looksLikeV8Oom(text6) {
8520
+ function looksLikeV8Oom(text7) {
8515
8521
  return /javascript heap|reached heap limit|OOMErrorHandler|FatalProcessOutOfMemory|Allocation failed - JavaScript heap/i.test(
8516
- text6
8522
+ text7
8517
8523
  );
8518
8524
  }
8519
- function looksLikeHomebrewLibuvCrash(text6) {
8520
- if (!looksLikeNativeEventLoopCrash(text6)) return false;
8521
- return /Cellar\/(?:libuv|node)|libuv\.\d+\.dylib/i.test(text6);
8525
+ function looksLikeHomebrewLibuvCrash(text7) {
8526
+ if (!looksLikeNativeEventLoopCrash(text7)) return false;
8527
+ return /Cellar\/(?:libuv|node)|libuv\.\d+\.dylib/i.test(text7);
8522
8528
  }
8523
- function clipStderr(text6, maxChars) {
8524
- const trimmed = text6.trim();
8529
+ function clipStderr(text7, maxChars) {
8530
+ const trimmed = text7.trim();
8525
8531
  if (trimmed.length <= maxChars) return trimmed;
8526
8532
  return trimmed.slice(0, maxChars);
8527
8533
  }
@@ -8555,16 +8561,16 @@ function summarizeTurnStderr(tail, maxChars = 500) {
8555
8561
  const joined = (useful.length ? useful : tail).slice(-6).join("\n").trim();
8556
8562
  return clipStderr(joined, maxChars);
8557
8563
  }
8558
- function looksLikeInvalidAgentSession(text6) {
8559
- const lower = text6.trim().toLowerCase();
8564
+ function looksLikeInvalidAgentSession(text7) {
8565
+ const lower = text7.trim().toLowerCase();
8560
8566
  if (!lower) return false;
8561
8567
  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
8568
  }
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();
8569
+ function looksLikeRetryableRunnerCrash(text7) {
8570
+ if (looksLikeAgentFailureMessage(text7)) return false;
8571
+ if (looksLikeInvalidAgentSession(text7)) return false;
8572
+ if (looksLikeV8Oom(text7)) return false;
8573
+ const lower = text7.trim().toLowerCase();
8568
8574
  if (/cannot find (?:package|module)|err_module_not_found/.test(lower)) return false;
8569
8575
  if (!lower) return true;
8570
8576
  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 +8582,12 @@ function shouldRetryFailedAgentTurn(detail, opts) {
8576
8582
  if (looksLikeV8Oom(detail) && opts.hasSession) return true;
8577
8583
  return looksLikeRetryableRunnerCrash(detail);
8578
8584
  }
8579
- function looksLikeAgentFailureMessage(text6) {
8580
- const lower = text6.trim().toLowerCase();
8585
+ function looksLikeAgentFailureMessage(text7) {
8586
+ const lower = text7.trim().toLowerCase();
8581
8587
  if (!lower) return false;
8582
8588
  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
8589
  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);
8590
+ ) || /\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
8591
  }
8586
8592
  function fallbackTurnFailDetail(assistantText) {
8587
8593
  const t = assistantText.trim();
@@ -9640,10 +9646,10 @@ var init_brightsy = __esm({
9640
9646
  });
9641
9647
 
9642
9648
  // src/agents/claude-mcp.ts
9643
- function parseMcpList(text6) {
9649
+ function parseMcpList(text7) {
9644
9650
  const servers = [];
9645
9651
  const seen = /* @__PURE__ */ new Set();
9646
- for (const raw of text6.split("\n")) {
9652
+ for (const raw of text7.split("\n")) {
9647
9653
  const line = raw.trim();
9648
9654
  if (!line || /^Checking MCP/i.test(line)) continue;
9649
9655
  const m = line.match(/^(.+?):\s+\S+/);
@@ -9834,8 +9840,8 @@ function isSubagentToolName(name) {
9834
9840
  if (/^(task|agent|spawn_agent)$/i.test(n)) return true;
9835
9841
  return /connectedAgentRequest/i.test(n);
9836
9842
  }
9837
- function isInternalAgentStatusText(text6) {
9838
- const t = text6.trim();
9843
+ function isInternalAgentStatusText(text7) {
9844
+ const t = text7.trim();
9839
9845
  if (!t) return false;
9840
9846
  if (/waiting for gate to pass/i.test(t)) return true;
9841
9847
  if (/^agent is running\b/i.test(t) && t.length < 160) return true;
@@ -9849,9 +9855,9 @@ function lastTextPart(parts, type) {
9849
9855
  for (let i = parts.length - 1; i >= 0; i--) {
9850
9856
  const p = parts[i];
9851
9857
  if (p.type !== type) continue;
9852
- const text6 = p.text.trim();
9853
- if (!text6 || isInternalAgentStatusText(text6)) continue;
9854
- return text6;
9858
+ const text7 = p.text.trim();
9859
+ if (!text7 || isInternalAgentStatusText(text7)) continue;
9860
+ return text7;
9855
9861
  }
9856
9862
  return "";
9857
9863
  }
@@ -9940,7 +9946,7 @@ function liveActivitySummary(parts, opts) {
9940
9946
  );
9941
9947
  const runningPoll = [...tools].reverse().find((t) => t.status === "running" && isPollWrapperToolName(t.name));
9942
9948
  const thinking = lastTextPart(parts, "thinking");
9943
- const text6 = lastTextPart(parts, "text");
9949
+ const text7 = lastTextPart(parts, "text");
9944
9950
  if (runningSubs.length > 0) {
9945
9951
  const heads = runningSubs.map(toolLabel);
9946
9952
  const head = runningSubs.length === 1 ? heads[0] : `${runningSubs.length} subagents \xB7 ${heads.slice(0, 2).join(" \xB7 ")}`;
@@ -9950,7 +9956,7 @@ function liveActivitySummary(parts, opts) {
9950
9956
  if (runningTop) return toolLabel(runningTop);
9951
9957
  if (runningPoll) return toolLabel(runningPoll);
9952
9958
  if (thinking) return thinking.length > 96 ? `\u2026${thinking.slice(-96)}` : thinking;
9953
- if (text6) return "Writing reply\u2026";
9959
+ if (text7) return "Writing reply\u2026";
9954
9960
  const last = [...tools].reverse().find((t) => !isPollWrapperToolName(t.name)) ?? tools.at(-1);
9955
9961
  if (last) {
9956
9962
  const label = toolLabel(last);
@@ -9982,9 +9988,9 @@ function toolFilePath(input) {
9982
9988
  if (!input) return void 0;
9983
9989
  return str2(input.file_path) ?? str2(input.path) ?? str2(input.filePath) ?? str2(input.filename);
9984
9990
  }
9985
- function countLines(text6) {
9986
- if (!text6) return 0;
9987
- return text6.split("\n").length;
9991
+ function countLines(text7) {
9992
+ if (!text7) return 0;
9993
+ return text7.split("\n").length;
9988
9994
  }
9989
9995
  function diffFromInput(input) {
9990
9996
  if (!input) return {};
@@ -10209,9 +10215,9 @@ function partsToAssistantText(parts) {
10209
10215
  (p) => p.type === "text" && !messagePartParentId(p)
10210
10216
  ).map((p) => p.text).join("").trim();
10211
10217
  }
10212
- function stripBrightsyNdjsonNoise(text6) {
10213
- if (!text6 || !text6.includes('"type"')) return text6;
10214
- let out = text6;
10218
+ function stripBrightsyNdjsonNoise(text7) {
10219
+ if (!text7 || !text7.includes('"type"')) return text7;
10220
+ let out = text7;
10215
10221
  out = out.replace(
10216
10222
  /\{"type":"(?:tool_use|tool_result|tool|thinking|usage|done|error)"[\s\S]*?\}\s*(?=\{"type":"|$|(?=[A-Za-z*#]))/g,
10217
10223
  ""
@@ -10254,7 +10260,9 @@ var init_profile = __esm({
10254
10260
  "present_plan",
10255
10261
  "present_schema",
10256
10262
  "present_files",
10257
- "wait_for_job"
10263
+ "wait_for_job",
10264
+ "get_viewer_context",
10265
+ "update_viewer_context"
10258
10266
  ];
10259
10267
  WORKTREE_GITHUB_MCP_TOOLS = [
10260
10268
  "github_get_issue",
@@ -10567,8 +10575,8 @@ function isBrightsyConnected() {
10567
10575
  return false;
10568
10576
  }
10569
10577
  }
10570
- function promptMentionsBrightsy(text6) {
10571
- return BRIGHTSY_WORD.test(text6 ?? "");
10578
+ function promptMentionsBrightsy(text7) {
10579
+ return BRIGHTSY_WORD.test(text7 ?? "");
10572
10580
  }
10573
10581
  function isBrightsyMcpToolName(name) {
10574
10582
  const n = name.toLowerCase();
@@ -10847,7 +10855,9 @@ var init_injected_mcp = __esm({
10847
10855
  "mcp__sideboard__present_files",
10848
10856
  "mcp__sideboard__ask_user",
10849
10857
  "mcp__sideboard__present_plan",
10850
- "mcp__sideboard__wait_for_job"
10858
+ "mcp__sideboard__wait_for_job",
10859
+ "mcp__sideboard__get_viewer_context",
10860
+ "mcp__sideboard__update_viewer_context"
10851
10861
  ];
10852
10862
  SIDEBOARD_GITHUB_MCP_ALLOWED_TOOLS = ["mcp__sideboard__github_*"];
10853
10863
  SIDEBOARD_LINEAR_MCP_ALLOWED_TOOLS = ["mcp__sideboard__linear_*"];
@@ -11184,9 +11194,9 @@ function compactMetadataFromClaude(obj) {
11184
11194
  return { trigger, postTokens };
11185
11195
  }
11186
11196
  function parseIssuesJson(raw) {
11187
- const text6 = raw.trim();
11188
- const candidates = [text6];
11189
- const match = text6.match(/\[[\s\S]*\]/);
11197
+ const text7 = raw.trim();
11198
+ const candidates = [text7];
11199
+ const match = text7.match(/\[[\s\S]*\]/);
11190
11200
  if (match) candidates.push(match[0]);
11191
11201
  for (const c of candidates) {
11192
11202
  try {
@@ -11454,8 +11464,8 @@ var init_claude = __esm({
11454
11464
  if (errorDetail) {
11455
11465
  events.push({ type: "stderr", data: errorDetail });
11456
11466
  } else {
11457
- const text6 = obj.result;
11458
- if (typeof text6 === "string" && text6) events.push({ type: "stdout", data: text6 });
11467
+ const text7 = obj.result;
11468
+ if (typeof text7 === "string" && text7) events.push({ type: "stdout", data: text7 });
11459
11469
  }
11460
11470
  const usage = usageFromClaude(obj.usage);
11461
11471
  if (usage) {
@@ -11596,9 +11606,9 @@ function asObject2(value) {
11596
11606
  function listMcpNamesFromJsonMap(raw, key) {
11597
11607
  return Object.keys(asObject2(asObject2(raw)[key])).filter((n) => n.trim());
11598
11608
  }
11599
- function listMcpNamesFromCodexToml(text6) {
11609
+ function listMcpNamesFromCodexToml(text7) {
11600
11610
  try {
11601
- const parsed = (0, import_smol_toml2.parse)(text6);
11611
+ const parsed = (0, import_smol_toml2.parse)(text7);
11602
11612
  return Object.keys(asObject2(parsed.mcp_servers)).filter((n) => n.trim());
11603
11613
  } catch {
11604
11614
  return [];
@@ -11729,8 +11739,8 @@ function codexConfigHasNetworkAccess() {
11729
11739
  ];
11730
11740
  for (const path2 of candidates) {
11731
11741
  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;
11742
+ const text7 = (0, import_node_fs32.readFileSync)(path2, "utf8");
11743
+ if (/network_access\s*=\s*true/.test(text7)) return true;
11734
11744
  }
11735
11745
  return false;
11736
11746
  }
@@ -11743,8 +11753,8 @@ function unwrapCodexMcpResult(result) {
11743
11753
  const texts = [];
11744
11754
  for (const item of rec.content) {
11745
11755
  if (!item || typeof item !== "object") continue;
11746
- const text6 = item.text;
11747
- if (typeof text6 === "string") texts.push(text6);
11756
+ const text7 = item.text;
11757
+ if (typeof text7 === "string") texts.push(text7);
11748
11758
  }
11749
11759
  if (texts.length) return texts.join("\n");
11750
11760
  }
@@ -11986,8 +11996,8 @@ var init_codex = __esm({
11986
11996
  if (msg) nested.push(msg);
11987
11997
  }
11988
11998
  }
11989
- for (const text6 of nested) {
11990
- events.push({ type: "stdout", data: text6, parentId: item.id });
11999
+ for (const text7 of nested) {
12000
+ events.push({ type: "stdout", data: text7, parentId: item.id });
11991
12001
  }
11992
12002
  events.push({
11993
12003
  type: "tool_result",
@@ -12738,8 +12748,8 @@ var init_opencode = __esm({
12738
12748
  return { type: "session_id", data: sid };
12739
12749
  }
12740
12750
  if (obj.type === "text") {
12741
- const text6 = obj.part?.text ?? obj.text;
12742
- if (text6) return { type: "stdout", data: text6 };
12751
+ const text7 = obj.part?.text ?? obj.text;
12752
+ if (text7) return { type: "stdout", data: text7 };
12743
12753
  }
12744
12754
  if (obj.type === "tool_use") {
12745
12755
  const part = obj.part;
@@ -13018,8 +13028,8 @@ var init_list_models = __esm({
13018
13028
  });
13019
13029
 
13020
13030
  // src/agents/session-quota.ts
13021
- function isSessionQuotaLimit(text6) {
13022
- const lower = text6.trim().toLowerCase();
13031
+ function isSessionQuotaLimit(text7) {
13032
+ const lower = text7.trim().toLowerCase();
13023
13033
  if (!lower) return false;
13024
13034
  if (/credit balance is too low|out of credits|insufficient.?quota|billing/.test(lower)) {
13025
13035
  return false;
@@ -13027,10 +13037,10 @@ function isSessionQuotaLimit(text6) {
13027
13037
  if (/prompt is too long|context.*(too long|exceed)|conversation too long/.test(lower)) {
13028
13038
  return false;
13029
13039
  }
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);
13040
+ 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
13041
  }
13032
- function parseSessionQuotaResetAt(text6, now = /* @__PURE__ */ new Date()) {
13033
- const absolute = text6.match(
13042
+ function parseSessionQuotaResetAt(text7, now = /* @__PURE__ */ new Date()) {
13043
+ const absolute = text7.match(
13034
13044
  /resets\s+(?:at\s+)?(\d{1,2}):(\d{2})\s*(am|pm)(?:\s*\(([^)]+)\))?/i
13035
13045
  );
13036
13046
  if (absolute) {
@@ -13048,7 +13058,7 @@ function parseSessionQuotaResetAt(text6, now = /* @__PURE__ */ new Date()) {
13048
13058
  }
13049
13059
  return at;
13050
13060
  }
13051
- const relative = text6.match(
13061
+ const relative = text7.match(
13052
13062
  /resets\s+in\s+(\d+)\s*(minutes?|hours?|days?)/i
13053
13063
  );
13054
13064
  if (relative) {
@@ -13629,8 +13639,8 @@ async function spawnAgentTurn(thread, input, onEvent) {
13629
13639
  onEvent({ type: "exit", data: exitCode });
13630
13640
  const finalized = finalizeParts(parts);
13631
13641
  const rawText = assistantText.trim() || partsToAssistantText(finalized);
13632
- const text6 = thread.agent === "brightsy" ? stripBrightsyNdjsonNoise(rawText) : rawText;
13633
- return { exitCode, sessionId, assistantText: text6, parts: finalized, usage };
13642
+ const text7 = thread.agent === "brightsy" ? stripBrightsyNdjsonNoise(rawText) : rawText;
13643
+ return { exitCode, sessionId, assistantText: text7, parts: finalized, usage };
13634
13644
  });
13635
13645
  return {
13636
13646
  pid: child.pid,
@@ -13909,9 +13919,9 @@ async function tryClaudeSummary(transcript, opts) {
13909
13919
  { cwd: opts?.cwd, reject: false }
13910
13920
  );
13911
13921
  if (exitCode !== 0) return null;
13912
- const text6 = stdout.trim();
13913
- if (text6.length < 40) return null;
13914
- return text6;
13922
+ const text7 = stdout.trim();
13923
+ if (text7.length < 40) return null;
13924
+ return text7;
13915
13925
  } catch {
13916
13926
  return null;
13917
13927
  }
@@ -13960,9 +13970,9 @@ function extractiveSummary(transcript) {
13960
13970
  );
13961
13971
  return parts.join("\n");
13962
13972
  }
13963
- function clipSummary(text6) {
13964
- if (text6.length <= MAX_SUMMARY_CHARS) return text6;
13965
- return `${text6.slice(0, MAX_SUMMARY_CHARS)}
13973
+ function clipSummary(text7) {
13974
+ if (text7.length <= MAX_SUMMARY_CHARS) return text7;
13975
+ return `${text7.slice(0, MAX_SUMMARY_CHARS)}
13966
13976
 
13967
13977
  [\u2026summary truncated\u2026]`;
13968
13978
  }
@@ -14116,8 +14126,8 @@ function findLastBrightsyContextSummary(messages) {
14116
14126
  continue;
14117
14127
  }
14118
14128
  if (part.status === "error") continue;
14119
- const text6 = extractBrightsyContextSummary(part.result);
14120
- if (text6) return { index: i, text: text6 };
14129
+ const text7 = extractBrightsyContextSummary(part.result);
14130
+ if (text7) return { index: i, text: text7 };
14121
14131
  }
14122
14132
  }
14123
14133
  return null;
@@ -15409,8 +15419,8 @@ function listRunningDetachedJobs(worktreePath) {
15409
15419
  }
15410
15420
  return running.sort();
15411
15421
  }
15412
- function looksLikeDeferredDonePromise(text6) {
15413
- const t = (text6 ?? "").trim();
15422
+ function looksLikeDeferredDonePromise(text7) {
15423
+ const t = (text7 ?? "").trim();
15414
15424
  if (!t) return false;
15415
15425
  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
15426
  }
@@ -16156,11 +16166,11 @@ function unwrapToolResult(result) {
16156
16166
  return typeof item.text === "string" ? item.text : "";
16157
16167
  }).filter(Boolean);
16158
16168
  if (texts.length === 1) {
16159
- const text6 = texts[0];
16169
+ const text7 = texts[0];
16160
16170
  try {
16161
- return JSON.parse(text6);
16171
+ return JSON.parse(text7);
16162
16172
  } catch {
16163
- return text6;
16173
+ return text7;
16164
16174
  }
16165
16175
  }
16166
16176
  if (texts.length > 1) return texts.join("\n");
@@ -16922,8 +16932,8 @@ function summarizeTurnLive(parts) {
16922
16932
  const interesting = [...tools].reverse().find((t) => !isPollWrapperToolName(t.name)) ?? lastTool;
16923
16933
  const lastToolLabel = interesting ? interesting.description || toolDescription(interesting.name, interesting.input) || interesting.name : void 0;
16924
16934
  const thinking = lastText(parts, "thinking");
16925
- const text6 = lastText(parts, "text");
16926
- const excerptRaw = thinking || text6;
16935
+ const text7 = lastText(parts, "text");
16936
+ const excerptRaw = thinking || text7;
16927
16937
  const excerpt = excerptRaw.length > 280 ? `${excerptRaw.slice(-280)}` : excerptRaw || void 0;
16928
16938
  const summary = liveActivitySummary(parts);
16929
16939
  return {
@@ -17162,8 +17172,8 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
17162
17172
  );
17163
17173
  const recent = from.messages.slice(-8).map((m) => {
17164
17174
  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;
17175
+ const text7 = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
17176
+ return text7 ? `- ${role}: ${text7}` : null;
17167
17177
  }).filter(Boolean);
17168
17178
  const body = [
17169
17179
  `# Orchestration handoff`,
@@ -17260,13 +17270,13 @@ function resolveConductorCursorAgentId(workspacePath) {
17260
17270
  for (const hash of hashes) {
17261
17271
  const agentsFile = (0, import_node_path41.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
17262
17272
  if (!(0, import_node_fs43.existsSync)(agentsFile)) continue;
17263
- let text6;
17273
+ let text7;
17264
17274
  try {
17265
- text6 = (0, import_node_fs43.readFileSync)(agentsFile, "utf8");
17275
+ text7 = (0, import_node_fs43.readFileSync)(agentsFile, "utf8");
17266
17276
  } catch {
17267
17277
  continue;
17268
17278
  }
17269
- for (const line of text6.split("\n")) {
17279
+ for (const line of text7.split("\n")) {
17270
17280
  const trimmed = line.trim();
17271
17281
  if (!trimmed) continue;
17272
17282
  try {
@@ -18075,13 +18085,13 @@ function formatStat(files) {
18075
18085
  return `${n} file${n === 1 ? "" : "s"} changed, ${additions} insertions(+), ${deletions} deletions(-)`;
18076
18086
  }
18077
18087
  function emptyScopeStats() {
18078
- const z7 = emptyStat();
18088
+ const z8 = emptyStat();
18079
18089
  return {
18080
- commits: z7,
18081
- uncommitted: z7,
18082
- staged: z7,
18083
- unstaged: z7,
18084
- last_turn: z7
18090
+ commits: z8,
18091
+ uncommitted: z8,
18092
+ staged: z8,
18093
+ unstaged: z8,
18094
+ last_turn: z8
18085
18095
  };
18086
18096
  }
18087
18097
  function filesFromDiff(nameStatus, numstat, combinedDiff, maxHunk) {
@@ -19455,12 +19465,12 @@ function firstString2(record, keys) {
19455
19465
  return "";
19456
19466
  }
19457
19467
  async function readJson(res) {
19458
- const text6 = await res.text();
19459
- if (!text6.trim()) return null;
19468
+ const text7 = await res.text();
19469
+ if (!text7.trim()) return null;
19460
19470
  try {
19461
- return JSON.parse(text6);
19471
+ return JSON.parse(text7);
19462
19472
  } catch {
19463
- return text6;
19473
+ return text7;
19464
19474
  }
19465
19475
  }
19466
19476
  async function authorizedGet(url, token) {
@@ -19710,11 +19720,11 @@ function resolvePlanMarkdown(opts) {
19710
19720
  source: "exit_plan"
19711
19721
  };
19712
19722
  }
19713
- const text6 = opts.text?.trim();
19714
- if (text6 && text6.length >= 80) {
19723
+ const text7 = opts.text?.trim();
19724
+ if (text7 && text7.length >= 80) {
19715
19725
  return {
19716
19726
  title: "Plan",
19717
- content: text6,
19727
+ content: text7,
19718
19728
  path: PLAN_FILE_REL,
19719
19729
  source: "text"
19720
19730
  };
@@ -20593,11 +20603,11 @@ var init_orchestrator = __esm({
20593
20603
  return results;
20594
20604
  }
20595
20605
  /** Edit the text of a not-yet-started queued message. */
20596
- async editQueuedMessage(threadRef, index, text6) {
20606
+ async editQueuedMessage(threadRef, index, text7) {
20597
20607
  const thread = this.requireThread(threadRef);
20598
20608
  return withThreadLock(thread.id, async () => {
20599
20609
  const current = this.requireThread(thread.id);
20600
- const trimmed = text6.trim();
20610
+ const trimmed = text7.trim();
20601
20611
  if (!trimmed || index < 0 || index >= current.queue.length) {
20602
20612
  return current;
20603
20613
  }
@@ -20783,6 +20793,7 @@ var init_orchestrator = __esm({
20783
20793
  ticketId: issueTicket?.id,
20784
20794
  ticketProvider: issueTicket?.provider
20785
20795
  }) : null;
20796
+ const viewerContextReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatViewerContextReminder() : null;
20786
20797
  const slackReplyContext = formatSlackRepliesForTurn(
20787
20798
  pendingSlackExternalReplies(thread.messages)
20788
20799
  );
@@ -20792,6 +20803,7 @@ var init_orchestrator = __esm({
20792
20803
  worktreeReminder,
20793
20804
  optionalServicesReminder,
20794
20805
  issueToolsReminder,
20806
+ viewerContextReminder,
20795
20807
  artifactReminder,
20796
20808
  longRunningReminder,
20797
20809
  slackReplyContext,
@@ -20832,6 +20844,18 @@ var init_orchestrator = __esm({
20832
20844
  ticketId: freshIssueTicket?.id,
20833
20845
  ticketProvider: freshIssueTicket?.provider
20834
20846
  });
20847
+ let viewerContextDirective = null;
20848
+ if (!isBrightsy && !isOrchestration) {
20849
+ try {
20850
+ viewerContextDirective = formatViewerContextDirective(
20851
+ resolveViewerProfileForRepo(fresh.repoPath || fresh.worktreePath)
20852
+ );
20853
+ } catch {
20854
+ viewerContextDirective = formatViewerContextDirective(
20855
+ resolveViewerProfileForRepo()
20856
+ );
20857
+ }
20858
+ }
20835
20859
  const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
20836
20860
  const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled2() ? formatRenameBranchDirective(fresh, {
20837
20861
  customPrompt: settings?.prompts?.renameBranch
@@ -20863,6 +20887,7 @@ var init_orchestrator = __esm({
20863
20887
  worktreeDirective,
20864
20888
  optionalServicesDirective,
20865
20889
  issueToolsDirective,
20890
+ viewerContextDirective,
20866
20891
  artifactDirective,
20867
20892
  longRunningDirective,
20868
20893
  renameBranchDirective,
@@ -20973,6 +20998,7 @@ var init_orchestrator = __esm({
20973
20998
  worktreeDirective,
20974
20999
  optionalServicesDirective,
20975
21000
  issueToolsDirective,
21001
+ viewerContextDirective,
20976
21002
  artifactDirective,
20977
21003
  longRunningDirective,
20978
21004
  renameBranchDirective,
@@ -21507,13 +21533,13 @@ var init_orchestrator = __esm({
21507
21533
  const lastAgent = [...thread.messages].reverse().find((m) => m.role === "agent");
21508
21534
  const lastError = thread.lastError ?? null;
21509
21535
  const rawText = (lastAgent?.text ?? "").trim();
21510
- const text6 = (rawText && !isInternalAgentStatusText(rawText) ? rawText : "") || (thread.status === "error" || thread.status === "stopped" || thread.status === "broken" ? lastError ?? "" : "");
21536
+ const text7 = (rawText && !isInternalAgentStatusText(rawText) ? rawText : "") || (thread.status === "error" || thread.status === "stopped" || thread.status === "broken" ? lastError ?? "" : "");
21511
21537
  const stillRunning = this.threadLooksLive(thread);
21512
21538
  const live = stillRunning ? readTurnLive(thread.id) : null;
21513
21539
  const liveSummary = live?.summary && !isInternalAgentStatusText(live.summary) ? live.summary : null;
21514
21540
  const queuedHint = stillRunning && thread.status === "queued" && !liveSummary ? "Queued \u2014 waiting for a concurrency slot" : null;
21515
21541
  return {
21516
- text: text6,
21542
+ text: text7,
21517
21543
  status: thread.status,
21518
21544
  sessionId: thread.sessionId,
21519
21545
  lastError,
@@ -22280,10 +22306,6 @@ var init_schedule_runner = __esm({
22280
22306
  var index_exports = {};
22281
22307
  __export(index_exports, {
22282
22308
  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
22309
  AGENT_GIT_ACTIONS: () => AGENT_GIT_ACTIONS,
22288
22310
  AGENT_RUNNER_MAX_OLD_SPACE_MB: () => AGENT_RUNNER_MAX_OLD_SPACE_MB,
22289
22311
  ATTACHMENTS_DIR: () => ATTACHMENTS_DIR,
@@ -22370,7 +22392,6 @@ __export(index_exports, {
22370
22392
  WORKTREE_MCP_TOOLS: () => WORKTREE_MCP_TOOLS,
22371
22393
  abletimeMcpRequest: () => abletimeMcpRequest,
22372
22394
  abletimeMcpUrl: () => abletimeMcpUrl,
22373
- accountRoleLabel: () => accountRoleLabel,
22374
22395
  ackSlackInboundSeen: () => ackSlackInboundSeen,
22375
22396
  addBoardPin: () => addBoardPin,
22376
22397
  addPrStackLayer: () => addPrStackLayer,
@@ -22551,6 +22572,7 @@ __export(index_exports, {
22551
22572
  fireSchedule: () => fireSchedule,
22552
22573
  flattenTurnInput: () => flattenTurnInput,
22553
22574
  flipLinearRelationType: () => flipLinearRelationType,
22575
+ foldLegacyRolesIntoNotes: () => foldLegacyRolesIntoNotes,
22554
22576
  followUpBehavior: () => followUpBehavior,
22555
22577
  forkChatTab: () => forkChatTab,
22556
22578
  forkMessageSlice: () => forkMessageSlice,
@@ -22591,6 +22613,8 @@ __export(index_exports, {
22591
22613
  formatSlackWorkingText: () => formatSlackWorkingText,
22592
22614
  formatTranscriptMarkdown: () => formatTranscriptMarkdown,
22593
22615
  formatUiReminder: () => formatUiReminder,
22616
+ formatViewerContextDirective: () => formatViewerContextDirective,
22617
+ formatViewerContextReminder: () => formatViewerContextReminder,
22594
22618
  formatWorkspaceInventory: () => formatWorkspaceInventory,
22595
22619
  formatWorkspaceProfileSuffix: () => formatWorkspaceProfileSuffix,
22596
22620
  formatWorktreeDirective: () => formatWorktreeDirective,
@@ -22828,7 +22852,6 @@ __export(index_exports, {
22828
22852
  planQuestionsSignature: () => planQuestionsSignature,
22829
22853
  pollSlackOutboundWatches: () => pollSlackOutboundWatches,
22830
22854
  posixShellSingleQuote: () => posixShellSingleQuote,
22831
- preferTeamsForRole: () => preferTeamsForRole,
22832
22855
  preferredCursorCostCents: () => preferredCursorCostCents,
22833
22856
  prepareTerminalCommand: () => prepareTerminalCommand,
22834
22857
  previewLand: () => previewLand,
@@ -22840,6 +22863,7 @@ __export(index_exports, {
22840
22863
  readSetupLog: () => readSetupLog,
22841
22864
  readSkillBody: () => readSkillBody,
22842
22865
  readThread: () => readThread,
22866
+ readViewerContext: () => readViewerContext,
22843
22867
  readWorktreeFile: () => readWorktreeFile,
22844
22868
  readWorktreeFileForUpload: () => readWorktreeFileForUpload,
22845
22869
  readWorktreeInclude: () => readWorktreeInclude,
@@ -22896,7 +22920,6 @@ __export(index_exports, {
22896
22920
  resolveViewerProfile: () => resolveViewerProfile,
22897
22921
  resolveViewerProfileForRepo: () => resolveViewerProfileForRepo,
22898
22922
  resolveWorktreeStartPoint: () => resolveWorktreeStartPoint,
22899
- reviewTeamHintsForRoles: () => reviewTeamHintsForRoles,
22900
22923
  rewriteAbleTimeError: () => rewriteAbleTimeError,
22901
22924
  rewriteLinearError: () => rewriteLinearError,
22902
22925
  run: () => run,
@@ -23030,6 +23053,7 @@ __export(index_exports, {
23030
23053
  writeInjectedMcpConfig: () => writeInjectedMcpConfig,
23031
23054
  writePlanFile: () => writePlanFile,
23032
23055
  writeThread: () => writeThread,
23056
+ writeViewerContext: () => writeViewerContext,
23033
23057
  writeWorktreeFile: () => writeWorktreeFile
23034
23058
  });
23035
23059
  module.exports = __toCommonJS(index_exports);
@@ -23078,9 +23102,9 @@ async function getGitHubStatus() {
23078
23102
  };
23079
23103
  }
23080
23104
  const status = await run("gh", ["auth", "status"], { reject: false });
23081
- const text6 = `${status.stdout}
23105
+ const text7 = `${status.stdout}
23082
23106
  ${status.stderr}`;
23083
- const loginMatch = text6.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text6.match(/Logged in to ([^\s]+) as (\S+)/i);
23107
+ const loginMatch = text7.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text7.match(/Logged in to ([^\s]+) as (\S+)/i);
23084
23108
  if (loginMatch) {
23085
23109
  return {
23086
23110
  connected: true,
@@ -24446,11 +24470,11 @@ function fenceLanguage(path2, language) {
24446
24470
  }
24447
24471
  function buildCodeRefAttachment(input) {
24448
24472
  const path2 = input.path.trim();
24449
- const text6 = input.text.replace(/\n$/, "");
24473
+ const text7 = input.text.replace(/\n$/, "");
24450
24474
  if (!path2) {
24451
24475
  throw new Error("code reference requires a file path");
24452
24476
  }
24453
- if (!text6.trim()) {
24477
+ if (!text7.trim()) {
24454
24478
  throw new Error("code reference requires selected text");
24455
24479
  }
24456
24480
  if (input.startLine < 1 || input.endLine < 1 || input.endLine < input.startLine) {
@@ -24463,7 +24487,7 @@ function buildCodeRefAttachment(input) {
24463
24487
  `Referenced code from \`${path2}\` (${range}).`,
24464
24488
  "",
24465
24489
  fence,
24466
- text6,
24490
+ text7,
24467
24491
  "```"
24468
24492
  ].join("\n");
24469
24493
  return {
@@ -24520,16 +24544,16 @@ var PASTE_ATTACH_MIN_CHARS = 1200;
24520
24544
  var PASTE_ATTACH_MIN_LINES = 15;
24521
24545
  var PASTED_NAME_RE = /^Pasted text #(\d+)\.txt$/i;
24522
24546
  var PASTED_NAME_ALT_RE = /^pasted-(\d+)\.txt$/i;
24523
- function pastedTextStats(text6) {
24524
- const chars = text6.length;
24547
+ function pastedTextStats(text7) {
24548
+ const chars = text7.length;
24525
24549
  if (chars === 0) return { chars: 0, lines: 0 };
24526
- const lines = text6.split(/\r\n|\r|\n/).length;
24550
+ const lines = text7.split(/\r\n|\r|\n/).length;
24527
24551
  return { chars, lines };
24528
24552
  }
24529
- function shouldAttachPastedText(text6) {
24530
- const trimmed = text6.trim();
24553
+ function shouldAttachPastedText(text7) {
24554
+ const trimmed = text7.trim();
24531
24555
  if (!trimmed) return false;
24532
- const { chars, lines } = pastedTextStats(text6);
24556
+ const { chars, lines } = pastedTextStats(text7);
24533
24557
  return chars >= PASTE_ATTACH_MIN_CHARS || lines >= PASTE_ATTACH_MIN_LINES;
24534
24558
  }
24535
24559
  function nextPastedTextName(existing) {
@@ -24540,13 +24564,13 @@ function nextPastedTextName(existing) {
24540
24564
  }
24541
24565
  return `Pasted text #${max + 1}.txt`;
24542
24566
  }
24543
- function buildPastedTextAttachment(text6, opts) {
24567
+ function buildPastedTextAttachment(text7, opts) {
24544
24568
  return {
24545
24569
  id: opts?.id ?? (0, import_node_crypto11.randomUUID)(),
24546
24570
  name: opts?.name ?? "Pasted text #1.txt",
24547
24571
  kind: "file",
24548
24572
  path: opts?.path,
24549
- content: text6
24573
+ content: text7
24550
24574
  };
24551
24575
  }
24552
24576
 
@@ -24646,8 +24670,8 @@ function latestPendingPlanQuestions(input) {
24646
24670
  return null;
24647
24671
  }
24648
24672
  var PLAN_QUESTION_ANSWERS_PREFIX = "Answers to your questions:";
24649
- function isPlanQuestionAnswersMessage(text6) {
24650
- return text6.startsWith(PLAN_QUESTION_ANSWERS_PREFIX);
24673
+ function isPlanQuestionAnswersMessage(text7) {
24674
+ return text7.startsWith(PLAN_QUESTION_ANSWERS_PREFIX);
24651
24675
  }
24652
24676
  function formatPlanQuestionAnswers(questions, answers) {
24653
24677
  const lines = [PLAN_QUESTION_ANSWERS_PREFIX, ""];
@@ -24694,7 +24718,7 @@ init_orphan_cleanup();
24694
24718
  // src/mcp/server.ts
24695
24719
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
24696
24720
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
24697
- var import_zod6 = require("zod");
24721
+ var import_zod7 = require("zod");
24698
24722
  var import_node_crypto13 = require("crypto");
24699
24723
  var import_node_path50 = require("path");
24700
24724
  init_orchestrator();
@@ -24723,9 +24747,9 @@ init_message_parts();
24723
24747
  function lastMessagePreview(messages, max = 160) {
24724
24748
  if (!messages?.length) return null;
24725
24749
  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, " ");
24750
+ const text7 = messages[i]?.text?.trim();
24751
+ if (!text7 || isInternalAgentStatusText(text7)) continue;
24752
+ const flat = text7.replace(/\s+/g, " ");
24729
24753
  return flat.length > max ? `${flat.slice(0, max)}\u2026` : flat;
24730
24754
  }
24731
24755
  return null;
@@ -24959,8 +24983,8 @@ function labelGithubUrl(url) {
24959
24983
  }
24960
24984
  return { kind: "other", label: "GitHub", url: raw };
24961
24985
  }
24962
- function appendGithubLink(text6, githubUrl) {
24963
- const body = text6.trimEnd();
24986
+ function appendGithubLink(text7, githubUrl) {
24987
+ const body = text7.trimEnd();
24964
24988
  if (!githubUrl?.trim()) return body;
24965
24989
  const labeled = labelGithubUrl(githubUrl);
24966
24990
  if (!labeled) {
@@ -25656,6 +25680,107 @@ function registerConnectedIssueVendorTools(server, settings = loadAppSettings())
25656
25680
  if (isAbleTimeConnected(settings)) registerAbleTimeTools(server);
25657
25681
  }
25658
25682
 
25683
+ // src/mcp/viewer-context-tools.ts
25684
+ var import_zod5 = require("zod");
25685
+ init_worktree();
25686
+ init_app_settings();
25687
+ init_workspaces();
25688
+ function text5(payload, isError = false) {
25689
+ return mcpJson(payload, isError);
25690
+ }
25691
+ function matchRegisteredWorkspace(repoPath) {
25692
+ const hint = (repoPath ?? "").trim().replace(/\/+$/, "");
25693
+ if (!hint) return null;
25694
+ let best = null;
25695
+ for (const w of listWorkspaces()) {
25696
+ const stored = w.path.replace(/\/+$/, "");
25697
+ if (stored === hint || hint.startsWith(`${stored}/`)) {
25698
+ if (!best || stored.length > best.length) best = stored;
25699
+ }
25700
+ }
25701
+ return best;
25702
+ }
25703
+ async function resolveProjectRepoPath(repoPath) {
25704
+ const hint = repoPath?.trim() || process.cwd();
25705
+ let root = hint;
25706
+ try {
25707
+ root = await resolveRepoRoot(hint);
25708
+ } catch {
25709
+ root = hint.replace(/\/+$/, "");
25710
+ }
25711
+ return matchRegisteredWorkspace(root) ?? matchRegisteredWorkspace(hint);
25712
+ }
25713
+ var PROJECT_PATH_HINT = "Pass repoPath from list_workspaces. Orchestration cwd is synthetic and empty \u2014 do not omit repoPath for project context.";
25714
+ function registerViewerContextTools(server) {
25715
+ server.tool(
25716
+ "get_viewer_context",
25717
+ "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).",
25718
+ {
25719
+ repoPath: import_zod5.z.string().optional().describe(
25720
+ "Registered workspace or worktree path. Required from orchestration for a project. Omit on a worktree turn (uses cwd)."
25721
+ )
25722
+ },
25723
+ async ({ repoPath }) => {
25724
+ try {
25725
+ const path2 = await resolveProjectRepoPath(repoPath);
25726
+ return text5(readViewerContext(path2 ?? void 0));
25727
+ } catch (err) {
25728
+ return text5(
25729
+ { error: err instanceof Error ? err.message : String(err) },
25730
+ true
25731
+ );
25732
+ }
25733
+ }
25734
+ );
25735
+ server.tool(
25736
+ "update_viewer_context",
25737
+ "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.",
25738
+ {
25739
+ scope: import_zod5.z.enum(["account", "project"]).describe("account = Settings \u2192 Agents; project = Settings \u2192 Projects for one repo"),
25740
+ context: import_zod5.z.string().describe("Full replacement text (roles, tickets, review queues). Empty clears."),
25741
+ confirmed: import_zod5.z.boolean().optional().describe("Must be true after the user confirmed via ask_user. False/omit is rejected."),
25742
+ repoPath: import_zod5.z.string().optional().describe(
25743
+ "Required for scope=project from orchestration (path from list_workspaces). Worktree turns may omit it (cwd)."
25744
+ )
25745
+ },
25746
+ async ({ scope, context, confirmed, repoPath }) => {
25747
+ try {
25748
+ if (scope === "project") {
25749
+ const path2 = await resolveProjectRepoPath(repoPath);
25750
+ if (!path2) {
25751
+ return text5(
25752
+ {
25753
+ ok: false,
25754
+ error: "repoPath required",
25755
+ message: PROJECT_PATH_HINT
25756
+ },
25757
+ true
25758
+ );
25759
+ }
25760
+ const result2 = writeViewerContext({
25761
+ scope,
25762
+ context,
25763
+ repoPath: path2,
25764
+ confirmed: confirmed === true
25765
+ });
25766
+ return text5(result2, !result2.ok);
25767
+ }
25768
+ const result = writeViewerContext({
25769
+ scope,
25770
+ context,
25771
+ confirmed: confirmed === true
25772
+ });
25773
+ return text5(result, !result.ok);
25774
+ } catch (err) {
25775
+ return text5(
25776
+ { error: err instanceof Error ? err.message : String(err) },
25777
+ true
25778
+ );
25779
+ }
25780
+ }
25781
+ );
25782
+ }
25783
+
25659
25784
  // src/mcp/pr-list.ts
25660
25785
  function compactPrRow(pr) {
25661
25786
  return {
@@ -25695,17 +25820,17 @@ init_list_prs();
25695
25820
  init_app_settings();
25696
25821
 
25697
25822
  // src/mcp/schedule-tools.ts
25698
- var import_zod5 = require("zod");
25823
+ var import_zod6 = require("zod");
25699
25824
  init_schedules();
25700
25825
  init_schedule_runner();
25701
- function text5(payload, isError = false) {
25826
+ function text6(payload, isError = false) {
25702
25827
  return {
25703
25828
  content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
25704
25829
  ...isError ? { isError: true } : {}
25705
25830
  };
25706
25831
  }
25707
25832
  function fail5(err) {
25708
- return text5(
25833
+ return text6(
25709
25834
  { error: err instanceof Error ? err.message : String(err) },
25710
25835
  true
25711
25836
  );
@@ -25730,7 +25855,7 @@ function registerScheduleTools(server) {
25730
25855
  {},
25731
25856
  async () => {
25732
25857
  try {
25733
- return text5({ schedules: listSchedules() });
25858
+ return text6({ schedules: listSchedules() });
25734
25859
  } catch (err) {
25735
25860
  return fail5(err);
25736
25861
  }
@@ -25740,17 +25865,17 @@ function registerScheduleTools(server) {
25740
25865
  "create_schedule",
25741
25866
  "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
25867
  {
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(
25868
+ prompt: import_zod6.z.string().describe("User message / goal queued when the schedule fires"),
25869
+ name: import_zod6.z.string().optional(),
25870
+ at: import_zod6.z.string().optional().describe("ISO datetime for a one-shot"),
25871
+ every: import_zod6.z.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
25872
+ cron: import_zod6.z.string().optional().describe("5-field cron expression"),
25873
+ tz: import_zod6.z.string().optional().describe("IANA timezone for cron (default: system)"),
25874
+ threadId: import_zod6.z.string().optional().describe(
25750
25875
  'Existing orchestration chat id, or "self" for this coordinator. Omit to create a new Global chat on fire.'
25751
25876
  ),
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()
25877
+ agent: import_zod6.z.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
25878
+ model: import_zod6.z.string().optional()
25754
25879
  },
25755
25880
  async (args) => {
25756
25881
  try {
@@ -25770,7 +25895,7 @@ function registerScheduleTools(server) {
25770
25895
  model: args.model,
25771
25896
  createdBy: "mcp"
25772
25897
  });
25773
- return text5({
25898
+ return text6({
25774
25899
  schedule,
25775
25900
  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
25901
  });
@@ -25783,17 +25908,17 @@ function registerScheduleTools(server) {
25783
25908
  "update_schedule",
25784
25909
  "Update a local schedule (prompt, cadence, target thread, enabled). Pass id from list_schedules.",
25785
25910
  {
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()
25911
+ id: import_zod6.z.string(),
25912
+ prompt: import_zod6.z.string().optional(),
25913
+ name: import_zod6.z.string().optional(),
25914
+ at: import_zod6.z.string().optional(),
25915
+ every: import_zod6.z.string().optional(),
25916
+ cron: import_zod6.z.string().optional(),
25917
+ tz: import_zod6.z.string().optional(),
25918
+ threadId: import_zod6.z.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
25919
+ agent: import_zod6.z.enum(["claude", "cursor", "codex", "opencode"]).optional(),
25920
+ model: import_zod6.z.string().optional(),
25921
+ enabled: import_zod6.z.boolean().optional()
25797
25922
  },
25798
25923
  async (args) => {
25799
25924
  try {
@@ -25822,7 +25947,7 @@ function registerScheduleTools(server) {
25822
25947
  enabled: args.enabled,
25823
25948
  model: args.model
25824
25949
  });
25825
- return text5({ schedule });
25950
+ return text6({ schedule });
25826
25951
  } catch (err) {
25827
25952
  return fail5(err);
25828
25953
  }
@@ -25831,11 +25956,11 @@ function registerScheduleTools(server) {
25831
25956
  server.tool(
25832
25957
  "delete_schedule",
25833
25958
  "Delete a local schedule. Pass id from list_schedules.",
25834
- { id: import_zod5.z.string() },
25959
+ { id: import_zod6.z.string() },
25835
25960
  async ({ id }) => {
25836
25961
  try {
25837
25962
  deleteSchedule(id);
25838
- return text5({ ok: true, id });
25963
+ return text6({ ok: true, id });
25839
25964
  } catch (err) {
25840
25965
  return fail5(err);
25841
25966
  }
@@ -25844,14 +25969,14 @@ function registerScheduleTools(server) {
25844
25969
  server.tool(
25845
25970
  "run_schedule",
25846
25971
  "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() },
25972
+ { id: import_zod6.z.string() },
25848
25973
  async ({ id }) => {
25849
25974
  try {
25850
25975
  if (!getSchedule(id)) {
25851
25976
  return fail5(new Error(`Schedule not found: ${id}`));
25852
25977
  }
25853
25978
  const schedule = await fireSchedule(id);
25854
- return text5({ schedule });
25979
+ return text6({ schedule });
25855
25980
  } catch (err) {
25856
25981
  return fail5(err);
25857
25982
  }
@@ -26282,13 +26407,14 @@ async function startMcpServer() {
26282
26407
  version: "0.1.0"
26283
26408
  });
26284
26409
  const worktreeProfile = sideboardMcpProfile() === "worktree";
26410
+ registerViewerContextTools(server);
26285
26411
  if (worktreeProfile) {
26286
26412
  registerConnectedIssueVendorTools(server);
26287
26413
  }
26288
26414
  if (!worktreeProfile) {
26289
26415
  server.tool(
26290
26416
  "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.",
26417
+ "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
26418
  {},
26293
26419
  async () => {
26294
26420
  const workspaces = orch.listWorkspaces();
@@ -26337,13 +26463,13 @@ async function startMcpServer() {
26337
26463
  "list_board",
26338
26464
  "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
26465
  {
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(
26466
+ query: import_zod7.z.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
26467
+ repoPath: import_zod7.z.string().optional().describe("Limit to one workspace path from list_workspaces"),
26468
+ kind: import_zod7.z.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
26469
+ column: import_zod7.z.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
26344
26470
  "Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
26345
26471
  ),
26346
- limit: import_zod6.z.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
26472
+ limit: import_zod7.z.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
26347
26473
  },
26348
26474
  async ({ query, repoPath, kind, column, limit }) => {
26349
26475
  const workspaces = orch.listWorkspaces();
@@ -26381,7 +26507,7 @@ async function startMcpServer() {
26381
26507
  server.tool(
26382
26508
  "get_thread",
26383
26509
  "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() },
26510
+ { ref: import_zod7.z.string() },
26385
26511
  async ({ ref }) => {
26386
26512
  const t = orch.getThread(ref);
26387
26513
  if (!t) {
@@ -26423,17 +26549,17 @@ async function startMcpServer() {
26423
26549
  "present_artifact",
26424
26550
  "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
26551
  {
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(
26552
+ title: import_zod7.z.string().describe("Short title shown in the artifact pane header"),
26553
+ type: import_zod7.z.enum(["html", "svg", "markdown", "react", "log"]).describe(
26428
26554
  "html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
26429
26555
  ),
26430
- content: import_zod6.z.string().describe(
26556
+ content: import_zod7.z.string().describe(
26431
26557
  "html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
26432
26558
  ),
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")
26559
+ artifact_id: import_zod7.z.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
26560
+ status: import_zod7.z.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
26561
+ phase: import_zod7.z.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
26562
+ mode: import_zod7.z.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
26437
26563
  },
26438
26564
  async ({ title, type, artifact_id, status, phase, mode }) => {
26439
26565
  const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -26454,15 +26580,15 @@ async function startMcpServer() {
26454
26580
  "ask_user",
26455
26581
  "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
26582
  {
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)")
26583
+ questions: import_zod7.z.array(
26584
+ import_zod7.z.object({
26585
+ question: import_zod7.z.string().describe("Full question text ending with ?"),
26586
+ header: import_zod7.z.string().max(24).optional().describe("Short label shown above the question"),
26587
+ multiSelect: import_zod7.z.boolean().optional().describe("Allow selecting multiple options"),
26588
+ options: import_zod7.z.array(
26589
+ import_zod7.z.object({
26590
+ label: import_zod7.z.string(),
26591
+ description: import_zod7.z.string().optional().describe("What this option means / when to choose it (strongly preferred)")
26466
26592
  })
26467
26593
  ).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
26468
26594
  })
@@ -26481,9 +26607,9 @@ async function startMcpServer() {
26481
26607
  "present_plan",
26482
26608
  "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
26609
  {
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")
26610
+ title: import_zod7.z.string().optional().describe("Short plan title (defaults to Plan)"),
26611
+ content: import_zod7.z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
26612
+ thread_id: import_zod7.z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
26487
26613
  },
26488
26614
  async ({ title, content, thread_id }) => {
26489
26615
  const { writePlanFile: writePlanFile2 } = await Promise.resolve().then(() => (init_plan_file(), plan_file_exports));
@@ -26506,15 +26632,15 @@ async function startMcpServer() {
26506
26632
  "present_schema",
26507
26633
  "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
26634
  {
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")
26635
+ title: import_zod7.z.string().describe("Pane title"),
26636
+ mode: import_zod7.z.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
26637
+ datasource: import_zod7.z.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
26638
+ resource_id: import_zod7.z.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
26639
+ record_id: import_zod7.z.string().optional().describe("Record id when opening form mode"),
26640
+ resource: import_zod7.z.record(import_zod7.z.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
26641
+ record: import_zod7.z.record(import_zod7.z.unknown()).optional().describe("Inline record { id, data, published_at? }"),
26642
+ records: import_zod7.z.array(import_zod7.z.record(import_zod7.z.unknown())).optional().describe("Inline records for table mode"),
26643
+ pane_id: import_zod7.z.string().optional().describe("Stable pane id across updates")
26518
26644
  },
26519
26645
  async (args) => {
26520
26646
  const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -26535,10 +26661,10 @@ async function startMcpServer() {
26535
26661
  "present_files",
26536
26662
  "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
26663
  {
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")
26664
+ title: import_zod7.z.string().optional().describe("Pane title (default: Files)"),
26665
+ datasource: import_zod7.z.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
26666
+ path: import_zod7.z.string().optional().describe("Initial folder path (e.g. public)"),
26667
+ pane_id: import_zod7.z.string().optional().describe("Stable pane id across updates")
26542
26668
  },
26543
26669
  async (args) => {
26544
26670
  const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -26557,8 +26683,8 @@ async function startMcpServer() {
26557
26683
  "wait_for_job",
26558
26684
  "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
26685
  {
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()
26686
+ id: import_zod7.z.string().describe("Detached job id (same kebab-case id passed to detached-job.js start)"),
26687
+ timeoutMs: import_zod7.z.number().optional()
26562
26688
  },
26563
26689
  async ({ id, timeoutMs }) => {
26564
26690
  const result = await waitForDetachedJob(process.cwd(), id, {
@@ -26579,7 +26705,7 @@ async function startMcpServer() {
26579
26705
  "set_caffeinate",
26580
26706
  "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
26707
  {
26582
- enabled: import_zod6.z.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
26708
+ enabled: import_zod7.z.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
26583
26709
  },
26584
26710
  async ({ enabled }) => {
26585
26711
  const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
@@ -26617,18 +26743,18 @@ async function startMcpServer() {
26617
26743
  "create_thread",
26618
26744
  `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
26745
  {
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(
26746
+ sourceType: import_zod7.z.enum(["branch", "pr", "ticket"]),
26747
+ sourceRef: import_zod7.z.string(),
26748
+ agent: import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
26749
+ model: import_zod7.z.string().nullable().optional().describe(
26624
26750
  `Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
26625
26751
  ),
26626
- repoPath: import_zod6.z.string(),
26627
- title: import_zod6.z.string().optional(),
26628
- cowboy: import_zod6.z.boolean().optional().describe(
26752
+ repoPath: import_zod7.z.string(),
26753
+ title: import_zod7.z.string().optional(),
26754
+ cowboy: import_zod7.z.boolean().optional().describe(
26629
26755
  "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
26756
  ),
26631
- parentThreadId: import_zod6.z.string().optional().describe(
26757
+ parentThreadId: import_zod7.z.string().optional().describe(
26632
26758
  "Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
26633
26759
  )
26634
26760
  },
@@ -26644,10 +26770,10 @@ async function startMcpServer() {
26644
26770
  "start_board_card",
26645
26771
  "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
26772
  {
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()
26773
+ kind: import_zod7.z.enum(["ticket", "pr", "branch"]),
26774
+ ref: import_zod7.z.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
26775
+ repoPath: import_zod7.z.string().describe("Workspace path from list_workspaces / list_board"),
26776
+ title: import_zod7.z.string().optional()
26651
26777
  },
26652
26778
  async ({ kind, ref, repoPath, title }) => {
26653
26779
  const root = await resolveRepoRoot(repoPath);
@@ -26751,9 +26877,9 @@ async function startMcpServer() {
26751
26877
  "send_to_thread",
26752
26878
  '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
26879
  {
26754
- ref: import_zod6.z.string(),
26755
- prompt: import_zod6.z.string(),
26756
- force_stop: import_zod6.z.boolean().optional()
26880
+ ref: import_zod7.z.string(),
26881
+ prompt: import_zod7.z.string(),
26882
+ force_stop: import_zod7.z.boolean().optional()
26757
26883
  },
26758
26884
  async ({ ref, prompt, force_stop }) => {
26759
26885
  if (force_stop) {
@@ -26782,8 +26908,8 @@ async function startMcpServer() {
26782
26908
  "wait_for_turn",
26783
26909
  "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
26910
  {
26785
- ref: import_zod6.z.string(),
26786
- timeoutMs: import_zod6.z.number().optional()
26911
+ ref: import_zod7.z.string(),
26912
+ timeoutMs: import_zod7.z.number().optional()
26787
26913
  },
26788
26914
  async ({ ref, timeoutMs }) => {
26789
26915
  const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
@@ -26813,7 +26939,7 @@ async function startMcpServer() {
26813
26939
  server.tool(
26814
26940
  "get_turn_result",
26815
26941
  "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() },
26942
+ { ref: import_zod7.z.string() },
26817
26943
  async ({ ref }) => {
26818
26944
  const result = orch.getTurnResult(ref);
26819
26945
  return {
@@ -26834,8 +26960,8 @@ async function startMcpServer() {
26834
26960
  "stop_thread",
26835
26961
  "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
26962
  {
26837
- ref: import_zod6.z.string(),
26838
- force: import_zod6.z.boolean().optional()
26963
+ ref: import_zod7.z.string(),
26964
+ force: import_zod7.z.boolean().optional()
26839
26965
  },
26840
26966
  async ({ ref, force }) => {
26841
26967
  const t = orch.getThread(ref);
@@ -26865,7 +26991,7 @@ async function startMcpServer() {
26865
26991
  server.tool(
26866
26992
  "archive_thread",
26867
26993
  "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() },
26994
+ { ref: import_zod7.z.string() },
26869
26995
  async ({ ref }) => {
26870
26996
  const t = orch.getThread(ref);
26871
26997
  if (!t) {
@@ -26898,7 +27024,7 @@ async function startMcpServer() {
26898
27024
  server.tool(
26899
27025
  "restore_thread",
26900
27026
  "Restore an archived thread (recreates worktree from branch when needed)",
26901
- { ref: import_zod6.z.string() },
27027
+ { ref: import_zod7.z.string() },
26902
27028
  async ({ ref }) => {
26903
27029
  try {
26904
27030
  const restored = await orch.restore(ref);
@@ -26924,8 +27050,8 @@ async function startMcpServer() {
26924
27050
  "get_diff",
26925
27051
  "Compact diff summary (capped hunks, paginated)",
26926
27052
  {
26927
- ref: import_zod6.z.string(),
26928
- maxFiles: import_zod6.z.number().optional()
27053
+ ref: import_zod7.z.string(),
27054
+ maxFiles: import_zod7.z.number().optional()
26929
27055
  },
26930
27056
  async ({ ref, maxFiles }) => {
26931
27057
  const summary = await orch.diffSummary(ref);
@@ -26945,7 +27071,7 @@ async function startMcpServer() {
26945
27071
  server.tool(
26946
27072
  "get_pr_checks",
26947
27073
  "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") },
27074
+ { ref: import_zod7.z.string().describe("Worktree thread id/ref") },
26949
27075
  async ({ ref }) => {
26950
27076
  try {
26951
27077
  const checks = await orch.getPrChecks(ref);
@@ -26961,7 +27087,7 @@ async function startMcpServer() {
26961
27087
  server.tool(
26962
27088
  "request_review",
26963
27089
  '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") },
27090
+ { ref: import_zod7.z.string().describe("Worktree thread id/ref to review") },
26965
27091
  async ({ ref }) => {
26966
27092
  try {
26967
27093
  const tab = await orch.requestReview(ref);
@@ -26990,8 +27116,8 @@ async function startMcpServer() {
26990
27116
  "ask_git",
26991
27117
  "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
27118
  {
26993
- ref: import_zod6.z.string().describe("Worktree thread id/ref"),
26994
- action: import_zod6.z.enum(AGENT_GIT_ACTIONS).describe(
27119
+ ref: import_zod7.z.string().describe("Worktree thread id/ref"),
27120
+ action: import_zod7.z.enum(AGENT_GIT_ACTIONS).describe(
26995
27121
  "commit-push | create-draft | create-web | resolve-conflicts | merge"
26996
27122
  )
26997
27123
  },
@@ -27036,7 +27162,7 @@ async function startMcpServer() {
27036
27162
  }
27037
27163
  }
27038
27164
  );
27039
- const agentEnum = import_zod6.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
27165
+ const agentEnum = import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
27040
27166
  server.tool(
27041
27167
  "list_models",
27042
27168
  "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 +27185,11 @@ async function startMcpServer() {
27059
27185
  "fork_worktree",
27060
27186
  "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
27187
  {
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)"),
27188
+ ref: import_zod7.z.string().describe("Worktree thread id/ref to fork"),
27189
+ through_index: import_zod7.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
27064
27190
  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()
27191
+ model: import_zod7.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
27192
+ title: import_zod7.z.string().optional()
27067
27193
  },
27068
27194
  async ({ ref, through_index, agent, model, title }) => {
27069
27195
  try {
@@ -27104,11 +27230,11 @@ async function startMcpServer() {
27104
27230
  "fork_chat",
27105
27231
  "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
27232
  {
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)"),
27233
+ ref: import_zod7.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
27234
+ through_index: import_zod7.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
27109
27235
  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()
27236
+ model: import_zod7.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
27237
+ title: import_zod7.z.string().optional()
27112
27238
  },
27113
27239
  async ({ ref, through_index, agent, model, title }) => {
27114
27240
  try {
@@ -27154,8 +27280,8 @@ async function startMcpServer() {
27154
27280
  "run_dev_script",
27155
27281
  "Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
27156
27282
  {
27157
- ref: import_zod6.z.string(),
27158
- name: import_zod6.z.string().optional()
27283
+ ref: import_zod7.z.string(),
27284
+ name: import_zod7.z.string().optional()
27159
27285
  },
27160
27286
  async ({ ref, name }) => {
27161
27287
  const result = await orch.startDev(ref, name);
@@ -27177,7 +27303,7 @@ async function startMcpServer() {
27177
27303
  server.tool(
27178
27304
  "list_run_scripts",
27179
27305
  "List named run scripts available for a thread",
27180
- { ref: import_zod6.z.string() },
27306
+ { ref: import_zod7.z.string() },
27181
27307
  async ({ ref }) => {
27182
27308
  const scripts = orch.listThreadRunScripts(ref);
27183
27309
  const active = orch.getActiveRuns(ref);
@@ -27195,8 +27321,8 @@ async function startMcpServer() {
27195
27321
  "stop_dev_script",
27196
27322
  "Stop a running script for a thread (all scripts if name omitted)",
27197
27323
  {
27198
- ref: import_zod6.z.string(),
27199
- name: import_zod6.z.string().optional()
27324
+ ref: import_zod7.z.string(),
27325
+ name: import_zod7.z.string().optional()
27200
27326
  },
27201
27327
  async ({ ref, name }) => {
27202
27328
  orch.stopDev(ref, name);
@@ -27206,7 +27332,7 @@ async function startMcpServer() {
27206
27332
  server.tool(
27207
27333
  "run_setup",
27208
27334
  "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() },
27335
+ { ref: import_zod7.z.string() },
27210
27336
  async ({ ref }) => {
27211
27337
  const result = await orch.runSetup(ref);
27212
27338
  return {
@@ -27217,7 +27343,7 @@ async function startMcpServer() {
27217
27343
  server.tool(
27218
27344
  "add_workspace",
27219
27345
  "Register a git repo as a Sideboard workspace",
27220
- { repoPath: import_zod6.z.string() },
27346
+ { repoPath: import_zod7.z.string() },
27221
27347
  async ({ repoPath }) => {
27222
27348
  const ws = await orch.addWorkspace(repoPath);
27223
27349
  return { content: [{ type: "text", text: JSON.stringify(ws) }] };
@@ -27226,7 +27352,7 @@ async function startMcpServer() {
27226
27352
  server.tool(
27227
27353
  "remove_workspace",
27228
27354
  "Unregister a Sideboard workspace (does not archive threads)",
27229
- { repoPath: import_zod6.z.string() },
27355
+ { repoPath: import_zod7.z.string() },
27230
27356
  async ({ repoPath }) => {
27231
27357
  orch.removeWorkspace(repoPath);
27232
27358
  return { content: [{ type: "text", text: "ok" }] };
@@ -27236,12 +27362,12 @@ async function startMcpServer() {
27236
27362
  "fanout",
27237
27363
  "Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
27238
27364
  {
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()
27365
+ prompt: import_zod7.z.string(),
27366
+ agents: import_zod7.z.array(import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
27367
+ repoPath: import_zod7.z.string(),
27368
+ sourceType: import_zod7.z.enum(["branch", "pr", "ticket"]).optional(),
27369
+ sourceRef: import_zod7.z.string().optional(),
27370
+ title: import_zod7.z.string().optional()
27245
27371
  },
27246
27372
  async (args) => {
27247
27373
  const threads = await orch.bestOfN(args);
@@ -27268,8 +27394,8 @@ async function startMcpServer() {
27268
27394
  "list_branches",
27269
27395
  "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
27396
  {
27271
- repoPath: import_zod6.z.string(),
27272
- unmergedOnly: import_zod6.z.boolean().optional()
27397
+ repoPath: import_zod7.z.string(),
27398
+ unmergedOnly: import_zod7.z.boolean().optional()
27273
27399
  },
27274
27400
  async ({ repoPath, unmergedOnly }) => {
27275
27401
  const root = await resolveRepoRoot(repoPath);
@@ -27290,19 +27416,19 @@ async function startMcpServer() {
27290
27416
  "list_prs",
27291
27417
  '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
27418
  {
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(
27419
+ repoPath: import_zod7.z.string(),
27420
+ query: import_zod7.z.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
27421
+ queue: import_zod7.z.enum(["review", "mine", "approved", "changes"]).optional().describe(
27296
27422
  '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
27423
  ),
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(
27424
+ state: import_zod7.z.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
27425
+ label: import_zod7.z.string().optional().describe(
27300
27426
  "GitHub label / workflow tag. Comma-separated AND. Examples: eng-review, eng-approved, eng-requested-changes"
27301
27427
  ),
27302
- reviewer: import_zod6.z.string().optional().describe(
27428
+ reviewer: import_zod7.z.string().optional().describe(
27303
27429
  "me (review requested of you), unassigned (no individual reviewer; team queues like engineering-team still count), all, or a GitHub login"
27304
27430
  ),
27305
- limit: import_zod6.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
27431
+ limit: import_zod7.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
27306
27432
  },
27307
27433
  async ({ repoPath, query, queue, state, label, reviewer, limit }) => {
27308
27434
  const root = await resolveRepoRoot(repoPath);
@@ -27334,7 +27460,7 @@ async function startMcpServer() {
27334
27460
  server.tool(
27335
27461
  "get_pr_stack",
27336
27462
  "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() },
27463
+ { ref: import_zod7.z.string() },
27338
27464
  async ({ ref }) => {
27339
27465
  const stack = await orch.getPrStack(ref);
27340
27466
  return {
@@ -27346,8 +27472,8 @@ async function startMcpServer() {
27346
27472
  "open_pr_stack_layers",
27347
27473
  "Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
27348
27474
  {
27349
- ref: import_zod6.z.string(),
27350
- layer: import_zod6.z.number().int().positive().optional()
27475
+ ref: import_zod7.z.string(),
27476
+ layer: import_zod7.z.number().int().positive().optional()
27351
27477
  },
27352
27478
  async ({ ref, layer }) => {
27353
27479
  const result = await orch.openPrStackLayers(ref, { layer });
@@ -27381,9 +27507,9 @@ async function startMcpServer() {
27381
27507
  "add_stack_layer",
27382
27508
  "Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
27383
27509
  {
27384
- ref: import_zod6.z.string(),
27385
- branchName: import_zod6.z.string(),
27386
- title: import_zod6.z.string().optional()
27510
+ ref: import_zod7.z.string(),
27511
+ branchName: import_zod7.z.string(),
27512
+ title: import_zod7.z.string().optional()
27387
27513
  },
27388
27514
  async ({ ref, branchName, title }) => {
27389
27515
  const result = await orch.addStackLayer(ref, branchName, { title });
@@ -27412,11 +27538,11 @@ async function startMcpServer() {
27412
27538
  "create_pr_stack",
27413
27539
  "Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
27414
27540
  {
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()
27541
+ repoPath: import_zod7.z.string(),
27542
+ branches: import_zod7.z.array(import_zod7.z.string()).min(1),
27543
+ agent: import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
27544
+ base: import_zod7.z.string().optional(),
27545
+ title: import_zod7.z.string().optional()
27420
27546
  },
27421
27547
  async (args) => {
27422
27548
  const result = await orch.createPrStack({
@@ -27455,10 +27581,10 @@ async function startMcpServer() {
27455
27581
  "list_issues",
27456
27582
  "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
27583
  {
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.")
27584
+ repoPath: import_zod7.z.string(),
27585
+ query: import_zod7.z.string().optional().describe("Search title, identifier, or description"),
27586
+ assignee: import_zod7.z.string().optional().describe("me (Linear default), unassigned, all, a user id, or a GitHub login"),
27587
+ limit: import_zod7.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
27462
27588
  },
27463
27589
  async ({ repoPath, query, assignee, limit }) => {
27464
27590
  const root = await resolveRepoRoot(repoPath);
@@ -27598,8 +27724,8 @@ var BrightsySideboardApi = class {
27598
27724
  };
27599
27725
  function taskMessageText(task) {
27600
27726
  const parts = task.message?.parts ?? [];
27601
- const text6 = parts.find((p) => p.kind === "text")?.text ?? "";
27602
- return text6.trim();
27727
+ const text7 = parts.find((p) => p.kind === "text")?.text ?? "";
27728
+ return text7.trim();
27603
27729
  }
27604
27730
 
27605
27731
  // src/brightsy/cloud-connect.ts
@@ -28101,11 +28227,11 @@ var import_ws = require("ws");
28101
28227
  init_api();
28102
28228
  var BACKOFF_START_MS = 1e3;
28103
28229
  var BACKOFF_MAX_MS = 3e4;
28104
- function stripSlackMentions(text6) {
28105
- return text6.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
28230
+ function stripSlackMentions(text7) {
28231
+ return text7.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
28106
28232
  }
28107
- function isSlackStopCommand(text6) {
28108
- const t = stripSlackMentions(text6).toLowerCase();
28233
+ function isSlackStopCommand(text7) {
28234
+ const t = stripSlackMentions(text7).toLowerCase();
28109
28235
  return t === "stop" || t === "sideboard_force_stop";
28110
28236
  }
28111
28237
  function parseSlackSocketFrame(raw) {
@@ -28127,8 +28253,8 @@ function inboundFromSocketFrame(frame) {
28127
28253
  const ts = event.ts?.trim();
28128
28254
  if (!channelId || !ts) return null;
28129
28255
  const rawText = event.text?.trim() ?? "";
28130
- const text6 = stripSlackMentions(rawText);
28131
- if (!text6) return null;
28256
+ const text7 = stripSlackMentions(rawText);
28257
+ if (!text7) return null;
28132
28258
  const teamId = (frame.payload?.team_id || event.team || "").trim();
28133
28259
  if (!teamId) return null;
28134
28260
  const isDm = event.type === "message" && (event.channel_type === "im" || event.channel_type === "mpim" || !event.channel_type && channelId.startsWith("D"));
@@ -28140,7 +28266,7 @@ function inboundFromSocketFrame(frame) {
28140
28266
  ts,
28141
28267
  threadTs: event.thread_ts?.trim() || void 0,
28142
28268
  userId: event.user?.trim(),
28143
- text: text6,
28269
+ text: text7,
28144
28270
  kind: isMention ? "mention" : "dm"
28145
28271
  };
28146
28272
  }
@@ -28684,12 +28810,12 @@ function formatSlackInboundPrompt(msg) {
28684
28810
 
28685
28811
  ${msg.text}`;
28686
28812
  }
28687
- function isSlackInboundUserPrompt(text6) {
28688
- return text6.startsWith("Slack DM\n") || text6.startsWith("Slack @mention\n");
28813
+ function isSlackInboundUserPrompt(text7) {
28814
+ return text7.startsWith("Slack DM\n") || text7.startsWith("Slack @mention\n");
28689
28815
  }
28690
- function formatSlackSignedReply(deviceLabel, text6) {
28816
+ function formatSlackSignedReply(deviceLabel, text7) {
28691
28817
  const label = deviceLabel.trim();
28692
- const body = text6.trim();
28818
+ const body = text7.trim();
28693
28819
  if (!body) return body;
28694
28820
  if (!label) return body;
28695
28821
  const head = `${label}:`;
@@ -28698,8 +28824,8 @@ function formatSlackSignedReply(deviceLabel, text6) {
28698
28824
  }
28699
28825
  return `${label}: ${body}`;
28700
28826
  }
28701
- function signForThisMac(text6) {
28702
- return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel, text6);
28827
+ function signForThisMac(text7) {
28828
+ return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel, text7);
28703
28829
  }
28704
28830
  function slackReplyThreadTs(msg) {
28705
28831
  if (msg.threadTs && msg.threadTs !== msg.ts) return msg.threadTs;
@@ -28760,9 +28886,9 @@ function replyStub(target) {
28760
28886
  kind: "dm"
28761
28887
  };
28762
28888
  }
28763
- async function postSlackText(target, text6, opts) {
28889
+ async function postSlackText(target, text7, opts) {
28764
28890
  if (opts.postReply) {
28765
- const result = await opts.postReply(replyStub(target), text6);
28891
+ const result = await opts.postReply(replyStub(target), text7);
28766
28892
  const ts2 = result && typeof result === "object" && typeof result.ts === "string" ? result.ts.trim() : "";
28767
28893
  return ts2 ? { ts: ts2 } : null;
28768
28894
  }
@@ -28772,7 +28898,7 @@ async function postSlackText(target, text6, opts) {
28772
28898
  "chat.postMessage",
28773
28899
  {
28774
28900
  channel: target.channelId,
28775
- text: text6,
28901
+ text: text7,
28776
28902
  thread_ts: target.threadTs
28777
28903
  },
28778
28904
  opts.fetchImpl
@@ -28780,9 +28906,9 @@ async function postSlackText(target, text6, opts) {
28780
28906
  const ts = json.ts?.trim();
28781
28907
  return ts ? { ts } : null;
28782
28908
  }
28783
- async function updateSlackText(target, ts, text6, opts) {
28909
+ async function updateSlackText(target, ts, text7, opts) {
28784
28910
  if (opts.updateReply) {
28785
- await opts.updateReply(replyStub(target), ts, text6);
28911
+ await opts.updateReply(replyStub(target), ts, text7);
28786
28912
  return;
28787
28913
  }
28788
28914
  const token = writeTokenForTeam(target.teamId);
@@ -28792,7 +28918,7 @@ async function updateSlackText(target, ts, text6, opts) {
28792
28918
  {
28793
28919
  channel: target.channelId,
28794
28920
  ts,
28795
- text: text6
28921
+ text: text7
28796
28922
  },
28797
28923
  opts.fetchImpl
28798
28924
  );
@@ -28820,8 +28946,8 @@ function replyTargetOf(msg) {
28820
28946
  threadTs: slackReplyThreadTs(msg)
28821
28947
  };
28822
28948
  }
28823
- async function postSlackReply(msg, text6, opts) {
28824
- await postSlackText(replyTargetOf(msg), signForThisMac(text6), opts);
28949
+ async function postSlackReply(msg, text7, opts) {
28950
+ await postSlackText(replyTargetOf(msg), signForThisMac(text7), opts);
28825
28951
  }
28826
28952
  function startSlackTurnProgress(msg, threadId, opts) {
28827
28953
  const log = opts.onLog ?? (() => void 0);
@@ -28904,10 +29030,10 @@ function startSlackTurnProgress(msg, threadId, opts) {
28904
29030
  }
28905
29031
  });
28906
29032
  },
28907
- finishWith: (text6) => {
29033
+ finishWith: (text7) => {
28908
29034
  stop();
28909
29035
  return run2(async () => {
28910
- const signed = signForThisMac(text6.trim());
29036
+ const signed = signForThisMac(text7.trim());
28911
29037
  if (!signed) {
28912
29038
  if (!postedTs) return;
28913
29039
  const ts = postedTs;
@@ -28935,11 +29061,11 @@ function startSlackTurnProgress(msg, threadId, opts) {
28935
29061
  };
28936
29062
  }
28937
29063
  var lastRelayed = /* @__PURE__ */ new Map();
28938
- function markRelayed(threadId, text6) {
28939
- lastRelayed.set(threadId, `${threadId}:${text6}`);
29064
+ function markRelayed(threadId, text7) {
29065
+ lastRelayed.set(threadId, `${threadId}:${text7}`);
28940
29066
  }
28941
- function alreadyRelayed(threadId, text6) {
28942
- return lastRelayed.get(threadId) === `${threadId}:${text6}`;
29067
+ function alreadyRelayed(threadId, text7) {
29068
+ return lastRelayed.get(threadId) === `${threadId}:${text7}`;
28943
29069
  }
28944
29070
  async function relayCoordinatorReplyToSlack(threadId, opts) {
28945
29071
  const target = getSlackReplyTarget(threadId);
@@ -28948,14 +29074,14 @@ async function relayCoordinatorReplyToSlack(threadId, opts) {
28948
29074
  const lastUser = thread ? [...thread.messages].reverse().find((m) => m.role === "user") : void 0;
28949
29075
  if (lastUser && isSlackInboundUserPrompt(lastUser.text)) return;
28950
29076
  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);
29077
+ const text7 = result.text.trim();
29078
+ if (!text7) return;
29079
+ if (alreadyRelayed(threadId, text7)) return;
29080
+ markRelayed(threadId, text7);
28955
29081
  const log = opts.onLog ?? (() => void 0);
28956
29082
  try {
28957
- await postSlackText(target, signForThisMac(text6), opts);
28958
- log(`replied ${target.threadTs ?? target.channelId} (${text6.length} chars)`);
29083
+ await postSlackText(target, signForThisMac(text7), opts);
29084
+ log(`replied ${target.threadTs ?? target.channelId} (${text7.length} chars)`);
28959
29085
  } catch (err) {
28960
29086
  lastRelayed.delete(threadId);
28961
29087
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -29204,10 +29330,10 @@ function resolveSlackListenMode(opts) {
29204
29330
 
29205
29331
  // src/slack/relay-hub.ts
29206
29332
  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) };
29333
+ function parseSlackDeviceDestination(text7) {
29334
+ const m = text7.match(/^\s*(?:to\s+)?(?:@|#)?([A-Za-z][\w-]{0,63})\s*[::]\s*/);
29335
+ if (!m) return { label: null, rest: text7 };
29336
+ return { label: m[1], rest: text7.slice(m[0].length) };
29211
29337
  }
29212
29338
  var SlackRelayHub = class {
29213
29339
  sessions = /* @__PURE__ */ new Map();
@@ -29733,10 +29859,6 @@ init_outbound_watch();
29733
29859
  // Annotate the CommonJS export names for ESM import in node:
29734
29860
  0 && (module.exports = {
29735
29861
  ABLETIME_MCP_PATH,
29736
- ACCOUNT_ROLES,
29737
- ACCOUNT_ROLE_LABELS,
29738
- ACCOUNT_ROLE_MAX,
29739
- ACCOUNT_ROLE_PRESETS,
29740
29862
  AGENT_GIT_ACTIONS,
29741
29863
  AGENT_RUNNER_MAX_OLD_SPACE_MB,
29742
29864
  ATTACHMENTS_DIR,
@@ -29823,7 +29945,6 @@ init_outbound_watch();
29823
29945
  WORKTREE_MCP_TOOLS,
29824
29946
  abletimeMcpRequest,
29825
29947
  abletimeMcpUrl,
29826
- accountRoleLabel,
29827
29948
  ackSlackInboundSeen,
29828
29949
  addBoardPin,
29829
29950
  addPrStackLayer,
@@ -30004,6 +30125,7 @@ init_outbound_watch();
30004
30125
  fireSchedule,
30005
30126
  flattenTurnInput,
30006
30127
  flipLinearRelationType,
30128
+ foldLegacyRolesIntoNotes,
30007
30129
  followUpBehavior,
30008
30130
  forkChatTab,
30009
30131
  forkMessageSlice,
@@ -30044,6 +30166,8 @@ init_outbound_watch();
30044
30166
  formatSlackWorkingText,
30045
30167
  formatTranscriptMarkdown,
30046
30168
  formatUiReminder,
30169
+ formatViewerContextDirective,
30170
+ formatViewerContextReminder,
30047
30171
  formatWorkspaceInventory,
30048
30172
  formatWorkspaceProfileSuffix,
30049
30173
  formatWorktreeDirective,
@@ -30281,7 +30405,6 @@ init_outbound_watch();
30281
30405
  planQuestionsSignature,
30282
30406
  pollSlackOutboundWatches,
30283
30407
  posixShellSingleQuote,
30284
- preferTeamsForRole,
30285
30408
  preferredCursorCostCents,
30286
30409
  prepareTerminalCommand,
30287
30410
  previewLand,
@@ -30293,6 +30416,7 @@ init_outbound_watch();
30293
30416
  readSetupLog,
30294
30417
  readSkillBody,
30295
30418
  readThread,
30419
+ readViewerContext,
30296
30420
  readWorktreeFile,
30297
30421
  readWorktreeFileForUpload,
30298
30422
  readWorktreeInclude,
@@ -30349,7 +30473,6 @@ init_outbound_watch();
30349
30473
  resolveViewerProfile,
30350
30474
  resolveViewerProfileForRepo,
30351
30475
  resolveWorktreeStartPoint,
30352
- reviewTeamHintsForRoles,
30353
30476
  rewriteAbleTimeError,
30354
30477
  rewriteLinearError,
30355
30478
  run,
@@ -30483,5 +30606,6 @@ init_outbound_watch();
30483
30606
  writeInjectedMcpConfig,
30484
30607
  writePlanFile,
30485
30608
  writeThread,
30609
+ writeViewerContext,
30486
30610
  writeWorktreeFile
30487
30611
  });