@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
@@ -64,42 +64,34 @@ function unwrapStrippedElectronLaunch(command, args) {
64
64
  }
65
65
 
66
66
  // src/store/account-profile.ts
67
- var ACCOUNT_ROLE_PRESETS = ["engineering", "design", "product"];
68
- var ACCOUNT_ROLES = ACCOUNT_ROLE_PRESETS;
69
- var ACCOUNT_ROLE_MAX = 16;
70
67
  var PROFILE_NOTES_MAX = 2e3;
71
- var ACCOUNT_ROLE_LABELS = {
68
+ var LEGACY_ROLE_LABELS = {
72
69
  engineering: "Engineering",
73
70
  design: "Design",
74
71
  product: "Product",
75
72
  qa: "QA"
76
73
  };
77
- var ROLE_TEAM_HINTS = {
78
- engineering: ["engineering-team", "engineering", "eng-team"],
79
- design: ["design-team", "design"],
80
- product: ["product-team", "product"]
81
- };
82
- function accountRoleLabel(role) {
83
- const known = ACCOUNT_ROLE_LABELS[role];
74
+ function legacyRoleLabel(role) {
75
+ const known = LEGACY_ROLE_LABELS[role];
84
76
  if (known) return known;
85
77
  return role.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
86
78
  }
87
- function normalizeAccountRole(value) {
79
+ function normalizeLegacyRole(value) {
88
80
  const key = (value ?? "").trim().toLowerCase().replace(/[_\s]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
89
81
  if (key === "both") return void 0;
90
82
  if (key.length < 2 || key.length > 40) return void 0;
91
83
  if (!/^[a-z][a-z0-9-]*$/.test(key)) return void 0;
92
84
  return key;
93
85
  }
94
- function normalizeAccountRoles(roles, legacyRole) {
86
+ function legacyRoleLabels(roles, legacyRole) {
95
87
  const raw = Array.isArray(roles) ? roles : [];
96
88
  const out = [];
97
89
  const seen = /* @__PURE__ */ new Set();
98
90
  const push = (value) => {
99
- const role = normalizeAccountRole(typeof value === "string" ? value : "");
100
- if (!role || seen.has(role) || out.length >= ACCOUNT_ROLE_MAX) return;
91
+ const role = normalizeLegacyRole(typeof value === "string" ? value : "");
92
+ if (!role || seen.has(role) || out.length >= 16) return;
101
93
  seen.add(role);
102
- out.push(role);
94
+ out.push(legacyRoleLabel(role));
103
95
  };
104
96
  for (const item of raw) push(item);
105
97
  if (out.length === 0) push(legacyRole);
@@ -109,97 +101,71 @@ function normalizeProfileNotes(value) {
109
101
  if (typeof value !== "string") return "";
110
102
  return value.replace(/\r\n/g, "\n").trim().slice(0, PROFILE_NOTES_MAX);
111
103
  }
112
- function reviewTeamHintsForRoles(roles) {
113
- const seen = /* @__PURE__ */ new Set();
114
- const out = [];
115
- for (const role of roles) {
116
- const hints = ROLE_TEAM_HINTS[role] ?? teamHintsForCustomRole(role);
117
- for (const hint of hints) {
118
- const key = hint.toLowerCase();
119
- if (seen.has(key)) continue;
120
- seen.add(key);
121
- out.push(hint);
122
- }
123
- }
124
- return out;
125
- }
126
- function teamHintsForCustomRole(role) {
127
- const slug = role.trim().toLowerCase();
128
- if (!slug) return [];
129
- if (slug.endsWith("-team") || slug.endsWith("-squad")) return [slug];
130
- return [`${slug}-team`, slug];
104
+ function foldLegacyRolesIntoNotes(roles, legacyRole, notes) {
105
+ const text = normalizeProfileNotes(notes);
106
+ const labels = legacyRoleLabels(roles, legacyRole);
107
+ if (!labels.length) return text;
108
+ if (text.toLowerCase().startsWith("roles:")) return text;
109
+ const prefix = `Roles: ${labels.join(", ")}`;
110
+ return text ? `${prefix}. ${text}` : prefix;
131
111
  }
132
112
  function resolveAccountProfile(input) {
133
113
  return resolveViewerProfile(input);
134
114
  }
135
115
  function resolveViewerProfile(account, project) {
136
- const accountRoles = normalizeAccountRoles(account?.roles, account?.role);
137
- const projectRoles = normalizeAccountRoles(project?.roles, project?.role);
138
- const rolesFromProject = projectRoles.length > 0;
139
- const roles = rolesFromProject ? projectRoles : accountRoles;
140
- const accountNotes = normalizeProfileNotes(account?.notes);
141
- const projectNotes = normalizeProfileNotes(project?.notes);
142
- const notes = [accountNotes, projectNotes].filter(Boolean).join("\n");
116
+ const accountNotes = foldLegacyRolesIntoNotes(
117
+ account?.roles,
118
+ account?.role,
119
+ account?.notes
120
+ );
121
+ const projectNotes = foldLegacyRolesIntoNotes(
122
+ project?.roles,
123
+ project?.role,
124
+ project?.notes
125
+ );
143
126
  return {
144
- roles,
145
- roleLabels: roles.map((role) => accountRoleLabel(role)),
146
- reviewTeamHints: reviewTeamHintsForRoles(roles),
147
- notes,
127
+ notes: [accountNotes, projectNotes].filter(Boolean).join("\n"),
148
128
  accountNotes,
149
- projectNotes,
150
- rolesFromProject
129
+ projectNotes
151
130
  };
152
131
  }
153
- function preferTeamsForRole(viewerTeams, hints) {
154
- const teams = viewerTeams.map((t) => t.trim()).filter(Boolean);
155
- const want = hints.map((t) => t.trim()).filter(Boolean);
156
- if (!want.length) return teams;
157
- if (!teams.length) return want;
158
- const hintKeys = want.map((t) => t.toLowerCase());
159
- const matched = teams.filter((team) => {
160
- const key = team.toLowerCase();
161
- return hintKeys.some((hint) => key === hint || key.includes(hint) || hint.includes(key));
162
- });
163
- return matched.length ? matched : teams;
164
- }
165
132
  function compactNotes(notes, max = 160) {
166
133
  const oneLine = notes.replace(/\s+/g, " ").trim();
167
134
  if (oneLine.length <= max) return oneLine;
168
135
  return `${oneLine.slice(0, max - 1).trimEnd()}\u2026`;
169
136
  }
170
137
  function formatAccountProfilePlaybookLine(profile) {
171
- if (profile.roleLabels.length === 0 && !profile.notes) return "";
172
- const parts = [];
173
- if (profile.roleLabels.length) {
174
- parts.push(
175
- `roles ${profile.roleLabels.join(", ")} (tickets to work on / PRs to review; prefer those teams)`
176
- );
177
- }
178
- if (profile.notes) {
179
- parts.push(`notes: ${compactNotes(profile.notes)}`);
180
- }
181
- 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.`;
138
+ if (!profile.accountNotes) return "";
139
+ 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).`;
182
140
  }
183
141
  function formatProjectProfilePlaybookLines(projects) {
184
142
  const lines = projects.map((project) => {
185
- const roles = (project.roleLabels ?? []).filter(Boolean);
186
143
  const notes = normalizeProfileNotes(project.notes);
187
- if (!roles.length && !notes) return "";
188
- const bits = [];
189
- if (roles.length) bits.push(`roles ${roles.join(", ")}`);
190
- if (notes) bits.push(compactNotes(notes, 120));
191
- return `- ${project.name}: ${bits.join(". ")}`;
144
+ if (!notes) return "";
145
+ return `- ${project.name}: ${compactNotes(notes, 120)}`;
192
146
  }).filter(Boolean);
193
147
  if (!lines.length) return "";
194
- return ["Project overrides (Settings \u2192 Projects; empty roles inherit account):", ...lines].join(
195
- "\n"
196
- );
148
+ return [
149
+ "Project context (Settings \u2192 Projects; adds to account context):",
150
+ ...lines
151
+ ].join("\n");
197
152
  }
198
153
  function formatWorkspaceProfileSuffix(profile) {
199
- const bits = [];
200
- if (profile.roles.length) bits.push(`roles:${profile.roles.join(",")}`);
201
- if (profile.projectNotes) bits.push(`notes:${compactNotes(profile.projectNotes, 80)}`);
202
- return bits.length ? ` ${bits.join(" ")}` : "";
154
+ if (!profile.projectNotes) return "";
155
+ return ` context:${compactNotes(profile.projectNotes, 80)}`;
156
+ }
157
+ function formatViewerContextDirective(profile) {
158
+ const account = profile.accountNotes || "(empty)";
159
+ const project = profile.projectNotes || "(empty \u2014 account context applies)";
160
+ return [
161
+ "Viewer context (Settings \u2192 Agents / Projects) \u2014 how to find this user\u2019s tickets and review PRs:",
162
+ `- Account: ${compactNotes(profile.accountNotes || account, 240)}`,
163
+ `- This project: ${compactNotes(profile.projectNotes || project, 240)}`,
164
+ "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."
165
+ ].join("\n");
166
+ }
167
+ function formatViewerContextReminder() {
168
+ return "Viewer context: get_viewer_context to read; update_viewer_context only after ask_user confirms (Save this context / Do not save).";
203
169
  }
204
170
 
205
171
  // src/store/secret-vault.ts
@@ -682,9 +648,7 @@ function normalizeDefaults(raw) {
682
648
  if (typeof source.fast === "boolean") {
683
649
  out.fast = source.fast;
684
650
  }
685
- const roles = normalizeAccountRoles(source.roles, source.role);
686
- if (roles.length) out.roles = roles;
687
- const notes = normalizeProfileNotes(source.notes);
651
+ const notes = foldLegacyRolesIntoNotes(source.roles, source.role, source.notes);
688
652
  if (notes) out.notes = notes;
689
653
  return out;
690
654
  }
@@ -695,11 +659,9 @@ function normalizeProjectProfile(raw) {
695
659
  if (!raw || typeof raw !== "object") return void 0;
696
660
  const source = raw;
697
661
  const out = {};
698
- const roles = normalizeAccountRoles(source.roles, source.role);
699
- if (roles.length) out.roles = roles;
700
- const notes = normalizeProfileNotes(source.notes);
662
+ const notes = foldLegacyRolesIntoNotes(source.roles, source.role, source.notes);
701
663
  if (notes) out.notes = notes;
702
- return out.roles || out.notes ? out : void 0;
664
+ return out.notes ? out : void 0;
703
665
  }
704
666
  function normalizeProjects(raw) {
705
667
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
@@ -716,14 +678,19 @@ function findProjectProfileKey(projects, repoPath) {
716
678
  const want = profileRepoKey(repoPath ?? "");
717
679
  if (!want) return void 0;
718
680
  if (projects[want]) return want;
681
+ let best;
682
+ let bestLen = 0;
719
683
  for (const key of Object.keys(projects)) {
720
684
  const stored = profileRepoKey(key);
721
685
  if (!stored) continue;
722
- if (stored === want || want.startsWith(`${stored}/`) || stored.startsWith(`${want}/`)) {
723
- return key;
686
+ if (stored === want || want.startsWith(`${stored}/`)) {
687
+ if (stored.length > bestLen) {
688
+ best = key;
689
+ bestLen = stored.length;
690
+ }
724
691
  }
725
692
  }
726
- return void 0;
693
+ return best;
727
694
  }
728
695
  function normalizeAdvanced(raw) {
729
696
  if (!raw || typeof raw !== "object") return {};
@@ -1269,17 +1236,13 @@ function updateDefaultsSettings(patch) {
1269
1236
  defaults.fast = Boolean(patch.fast);
1270
1237
  }
1271
1238
  }
1272
- if ("roles" in patch) {
1273
- const roles = normalizeAccountRoles(patch.roles);
1274
- if (!roles.length) delete defaults.roles;
1275
- else defaults.roles = roles;
1276
- delete defaults.role;
1277
- }
1278
1239
  if ("notes" in patch) {
1279
1240
  const notes = normalizeProfileNotes(patch.notes);
1280
1241
  if (!notes) delete defaults.notes;
1281
1242
  else defaults.notes = notes;
1282
1243
  }
1244
+ delete defaults.roles;
1245
+ delete defaults.role;
1283
1246
  return saveAppSettings({ ...current, defaults });
1284
1247
  }
1285
1248
  function updateProjectProfileSettings(repoPath, patch) {
@@ -1289,17 +1252,14 @@ function updateProjectProfileSettings(repoPath, patch) {
1289
1252
  const projects = { ...current.projects };
1290
1253
  const existing = projects[key] ?? {};
1291
1254
  const next = { ...existing };
1292
- if ("roles" in patch) {
1293
- const roles = normalizeAccountRoles(patch.roles);
1294
- if (!roles.length) delete next.roles;
1295
- else next.roles = roles;
1296
- }
1297
1255
  if ("notes" in patch) {
1298
1256
  const notes = normalizeProfileNotes(patch.notes);
1299
1257
  if (!notes) delete next.notes;
1300
1258
  else next.notes = notes;
1301
1259
  }
1302
- if (!next.roles?.length && !next.notes) delete projects[key];
1260
+ delete next.roles;
1261
+ delete next.role;
1262
+ if (!next.notes) delete projects[key];
1303
1263
  else projects[key] = next;
1304
1264
  return saveAppSettings({ ...current, projects });
1305
1265
  }
@@ -1324,6 +1284,62 @@ function resolveViewerProfileForRepo(repoPath, settings = loadAppSettings()) {
1324
1284
  const project = key ? settings.projects[key] : void 0;
1325
1285
  return resolveViewerProfile(settings.defaults, project);
1326
1286
  }
1287
+ function readViewerContext(repoPath) {
1288
+ const settings = loadAppSettings();
1289
+ const key = findProjectProfileKey(settings.projects, repoPath);
1290
+ const resolved = resolveViewerProfile(
1291
+ settings.defaults,
1292
+ key ? settings.projects[key] : void 0
1293
+ );
1294
+ return {
1295
+ account: resolved.accountNotes,
1296
+ project: resolved.projectNotes,
1297
+ combined: resolved.notes,
1298
+ repoPath: key ?? (repoPath?.trim() ? profileRepoKey(repoPath) : null)
1299
+ };
1300
+ }
1301
+ var 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.";
1302
+ var 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.";
1303
+ function writeViewerContext(input) {
1304
+ const proposed = normalizeProfileNotes(input.context);
1305
+ if (input.scope === "project") {
1306
+ const settings = loadAppSettings();
1307
+ const key = findProjectProfileKey(settings.projects, input.repoPath) ?? profileRepoKey(input.repoPath ?? "");
1308
+ if (!key) {
1309
+ return {
1310
+ ok: false,
1311
+ error: "repoPath required",
1312
+ message: "Pass repoPath (or run from a registered worktree) when updating project context.",
1313
+ current: "",
1314
+ proposed
1315
+ };
1316
+ }
1317
+ const current2 = resolveViewerProfileForRepo(key, settings).projectNotes;
1318
+ if (!input.confirmed) {
1319
+ return {
1320
+ ok: false,
1321
+ error: "Confirmation required",
1322
+ message: CONFIRM_PROJECT,
1323
+ current: current2,
1324
+ proposed
1325
+ };
1326
+ }
1327
+ updateProjectProfileSettings(key, { notes: proposed || null });
1328
+ return { ok: true, scope: "project", context: proposed, repoPath: key };
1329
+ }
1330
+ const current = resolveAccountProfileFromSettings().accountNotes;
1331
+ if (!input.confirmed) {
1332
+ return {
1333
+ ok: false,
1334
+ error: "Confirmation required",
1335
+ message: CONFIRM_ACCOUNT,
1336
+ current,
1337
+ proposed
1338
+ };
1339
+ }
1340
+ updateDefaultsSettings({ notes: proposed || null });
1341
+ return { ok: true, scope: "account", context: proposed };
1342
+ }
1327
1343
  function resolveThreadDefaults(settings = loadAppSettings()) {
1328
1344
  return {
1329
1345
  agent: getDefaultAgent(settings),
@@ -1687,19 +1703,15 @@ export {
1687
1703
  readSecureJson,
1688
1704
  writeSecureJson,
1689
1705
  isSecureFileEncrypted,
1690
- ACCOUNT_ROLE_PRESETS,
1691
- ACCOUNT_ROLES,
1692
- ACCOUNT_ROLE_MAX,
1693
1706
  PROFILE_NOTES_MAX,
1694
- ACCOUNT_ROLE_LABELS,
1695
- accountRoleLabel,
1696
- reviewTeamHintsForRoles,
1707
+ foldLegacyRolesIntoNotes,
1697
1708
  resolveAccountProfile,
1698
1709
  resolveViewerProfile,
1699
- preferTeamsForRole,
1700
1710
  formatAccountProfilePlaybookLine,
1701
1711
  formatProjectProfilePlaybookLines,
1702
1712
  formatWorkspaceProfileSuffix,
1713
+ formatViewerContextDirective,
1714
+ formatViewerContextReminder,
1703
1715
  ISSUE_SOURCE_LABELS,
1704
1716
  issueSourceLabel,
1705
1717
  HARNESS_ENV_KEYS,
@@ -1726,6 +1738,8 @@ export {
1726
1738
  getDefaultFast,
1727
1739
  resolveAccountProfileFromSettings,
1728
1740
  resolveViewerProfileForRepo,
1741
+ readViewerContext,
1742
+ writeViewerContext,
1729
1743
  resolveThreadDefaults,
1730
1744
  resolveNewThreadOptions,
1731
1745
  isLinearConnected,
@@ -5,7 +5,7 @@ import {
5
5
  resolveRepoRoot,
6
6
  takenSlugsFromThread,
7
7
  teamSlugFromName
8
- } from "./chunk-LQMFCS54.js";
8
+ } from "./chunk-UUEBNUEG.js";
9
9
  import {
10
10
  ATTACHMENTS_DIR,
11
11
  attachmentsGitignoreBody
@@ -20,7 +20,7 @@ import {
20
20
  resolveThreadDefaults,
21
21
  resolveViewerProfile,
22
22
  resolveViewerProfileForRepo
23
- } from "./chunk-3BWVOUQG.js";
23
+ } from "./chunk-CVZ4KXVP.js";
24
24
  import {
25
25
  createEmptyThread,
26
26
  listThreads,
@@ -392,10 +392,10 @@ var COORDINATOR_TOOL_PLAYBOOK = [
392
392
  "Role: you oversee Sideboard worktree agents across registered repos. You do not live inside one of those worktrees.",
393
393
  "Sideboard MCP (fleet control \u2014 prefer these for status and orchestration):",
394
394
  "Discover:",
395
- "- list_workspaces \u2014 registered repos (path + github slug + roles/notes when set). Use that profile to pick the right repo for tickets or reviews.",
395
+ "- 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.",
396
396
  "- 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.",
397
- '- 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.',
398
- "- 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.",
397
+ '- 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).',
398
+ "- 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.",
399
399
  "- 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.",
400
400
  "- 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.",
401
401
  "- 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.",
@@ -405,7 +405,8 @@ var COORDINATOR_TOOL_PLAYBOOK = [
405
405
  "- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
406
406
  "- list_models \u2014 only when you need a specific model (rare); otherwise omit model so Account defaults apply",
407
407
  "- 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.",
408
- "- 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.",
408
+ "- 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.",
409
+ "- 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.",
409
410
  "- 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.",
410
411
  "- 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.",
411
412
  "Workspaces:",
@@ -464,16 +465,15 @@ function accountRolePlaybookLine() {
464
465
  return "";
465
466
  }
466
467
  }
467
- function accountRoleReminderLine() {
468
+ function accountContextReminderLine() {
468
469
  let profile;
469
470
  try {
470
471
  profile = resolveAccountProfileFromSettings();
471
472
  } catch {
472
473
  return "";
473
474
  }
474
- if (profile.roleLabels.length === 0 && !profile.notes) return "";
475
- const roles = profile.roleLabels.length ? profile.roleLabels.join(", ") : "no roles";
476
- return `- Viewer profile: ${roles}. Use Settings notes with list_issues / list_prs when they ask to find work.`;
475
+ if (!profile.accountNotes) return "";
476
+ 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.";
477
477
  }
478
478
  function projectProfilePlaybookBlock() {
479
479
  let settings;
@@ -490,7 +490,6 @@ function projectProfilePlaybookBlock() {
490
490
  const key = path.replace(/\/+$/, "");
491
491
  return {
492
492
  name: names.get(key) ?? key.split("/").filter(Boolean).pop() ?? path,
493
- roleLabels: resolved.rolesFromProject ? resolved.roleLabels : [],
494
493
  notes: resolved.projectNotes
495
494
  };
496
495
  });
@@ -504,7 +503,7 @@ function coordinatorTurnReminder(opts) {
504
503
  `- YOUR orchestration thread id is ${opts.parentId} \u2014 pass parentThreadId="${opts.parentId}" on create_thread, or omit it.`,
505
504
  goal ? `- Goal / title: ${goal}` : null,
506
505
  accountDefaultsPlaybookLine(),
507
- accountRoleReminderLine() || null,
506
+ accountContextReminderLine() || null,
508
507
  "- 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."
509
508
  ].filter(Boolean).join("\n");
510
509
  }
@@ -558,7 +557,7 @@ function ensureGlobalCoordinatorCwd(opts) {
558
557
  "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.",
559
558
  "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).",
560
559
  "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.",
561
- "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.",
560
+ "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.",
562
561
  '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".',
563
562
  "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.",
564
563
  "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."