@sideboard-ai/core 0.1.89 → 0.1.95

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 (30) hide show
  1. package/dist/agents/cursor-runner.cjs +129 -14
  2. package/dist/agents/cursor-runner.js +4 -1
  3. package/dist/{agents-LMUFTGKF.js → agents-HBLA6FEV.js} +4 -4
  4. package/dist/{agents-ODBP7J6E.js → agents-WS5QV6LE.js} +5 -5
  5. package/dist/{chunk-WANQFU3S.js → chunk-6XBXVXX2.js} +2 -2
  6. package/dist/{chunk-7D27DD2X.js → chunk-CIRXAYWS.js} +260 -61
  7. package/dist/{chunk-B2KIO2SD.js → chunk-CLGO7TLO.js} +2 -2
  8. package/dist/{chunk-CBJSPTBG.js → chunk-GXSYI7FH.js} +206 -5
  9. package/dist/{chunk-RJLBSYUO.js → chunk-KWNUZ4LR.js} +46 -77
  10. package/dist/{chunk-UHTNJKZX.js → chunk-NR6APJLD.js} +2 -2
  11. package/dist/{chunk-6EZRSCIT.js → chunk-QKYO6BHB.js} +2 -2
  12. package/dist/{chunk-ZXYWWSHZ.js → chunk-R7BQBSDT.js} +254 -61
  13. package/dist/{chunk-JE75QW2I.js → chunk-WBX46OPD.js} +237 -96
  14. package/dist/{chunk-OB6IRIFV.js → chunk-XH2GS2LO.js} +2 -2
  15. package/dist/{chunk-VZ2L4AEJ.js → chunk-XUWDLRAE.js} +2 -2
  16. package/dist/{coordinator-prompt-UK5LYFN5.js → coordinator-prompt-AKEY4WSO.js} +2 -2
  17. package/dist/{coordinator-prompt-CI5SHONJ.js → coordinator-prompt-OQOOD5ET.js} +2 -2
  18. package/dist/{global-workspace-2YZ2V4I5.js → global-workspace-3GNPQCLE.js} +3 -3
  19. package/dist/{global-workspace-JQQLPJM5.js → global-workspace-M3OMVDDH.js} +3 -3
  20. package/dist/index.cjs +1021 -473
  21. package/dist/index.d.cts +91 -20
  22. package/dist/index.d.ts +91 -20
  23. package/dist/index.js +244 -59
  24. package/dist/mcp/run-stdio.cjs +816 -428
  25. package/dist/mcp/run-stdio.js +77 -40
  26. package/dist/{workspaces-YWCC3WV4.js → workspaces-ERZC7ULY.js} +4 -4
  27. package/dist/{workspaces-FDO5L4NI.js → workspaces-J4WG6UFR.js} +4 -4
  28. package/dist/{worktree-7YNSJ224.js → worktree-DA4BOV7G.js} +7 -1
  29. package/dist/{worktree-4555QBQ7.js → worktree-EO5QAGJU.js} +7 -1
  30. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  listWorkspaces,
5
5
  removeWorkspace,
6
6
  syncWorkspacesFromThreads
7
- } from "./chunk-6EZRSCIT.js";
7
+ } from "./chunk-QKYO6BHB.js";
8
8
  import {
9
9
  BRIGHTSY_MCP_ALLOWED_TOOLS,
10
10
  CLAUDE_MODEL_CATALOG,
@@ -58,13 +58,14 @@ import {
58
58
  resolveCursorModelId,
59
59
  resolveLoginCommand,
60
60
  resolveQuotaFallbackAgent,
61
+ resolveSideboardMcpServer,
61
62
  sanitizeMcpServerName,
62
63
  shouldInjectBrightsyMcp,
63
64
  sideboardMcpProfile,
64
65
  threadRequestsBrightsyMcp,
65
66
  totalTokens,
66
67
  writeInjectedMcpConfig
67
- } from "./chunk-RJLBSYUO.js";
68
+ } from "./chunk-KWNUZ4LR.js";
68
69
  import {
69
70
  CLOUD_COORDINATOR_BUSY_REPLY,
70
71
  CLOUD_COORDINATOR_STOPPED_REPLY,
@@ -91,7 +92,7 @@ import {
91
92
  parseForceStopMessage,
92
93
  slackCoordinatorSourceRef,
93
94
  takenTeamSlugsForOrchestration
94
- } from "./chunk-B2KIO2SD.js";
95
+ } from "./chunk-CLGO7TLO.js";
95
96
  import {
96
97
  COORDINATOR_TOOL_PLAYBOOK,
97
98
  SLACK_REPLY_FORMATTING,
@@ -100,7 +101,7 @@ import {
100
101
  enrichWorkspacesWithGithub,
101
102
  ensureGlobalCoordinatorCwd,
102
103
  formatWorkspaceInventory
103
- } from "./chunk-VZ2L4AEJ.js";
104
+ } from "./chunk-XUWDLRAE.js";
104
105
  import {
105
106
  brightsyConfigPath,
106
107
  brightsyMcpServerName,
@@ -137,7 +138,7 @@ import {
137
138
  parseCursorRunnerLine,
138
139
  pushTurnStderr,
139
140
  summarizeTurnStderr
140
- } from "./chunk-CBJSPTBG.js";
141
+ } from "./chunk-GXSYI7FH.js";
141
142
  import {
142
143
  caffeinateHoldPath,
143
144
  getCaffeinateHold,
@@ -154,6 +155,7 @@ import {
154
155
  applyGithubGitAuthEnv,
155
156
  branchDisplayLabel,
156
157
  checkoutPrStackLayer,
158
+ codexSandboxWritableRootsArgs,
157
159
  codexUnattendedGitConfigArgs,
158
160
  collectTakenTeamSlugs,
159
161
  commitAll,
@@ -174,12 +176,14 @@ import {
174
176
  getPr,
175
177
  getPrChecks,
176
178
  getPrDetails,
179
+ getPrForHeadBranch,
177
180
  getPrMeta,
178
181
  getPrStack,
179
182
  ghHeadRef,
180
183
  ghRepoSelectArgs,
181
184
  githubAgentGitEnv,
182
185
  initPrStack,
186
+ isDefaultishSourceRef,
183
187
  isDirty,
184
188
  isGhRateLimitError,
185
189
  isInPrStack,
@@ -190,6 +194,7 @@ import {
190
194
  listPrs,
191
195
  listWorktrees,
192
196
  lookupSoccerTeam,
197
+ mergeAgentGitAuthEnv,
193
198
  mergePr,
194
199
  mergePrStack,
195
200
  nonInteractiveGitProcessEnv,
@@ -200,24 +205,29 @@ import {
200
205
  pushBranch,
201
206
  removeWorktree,
202
207
  resetGhStackDetectCache,
208
+ resetGithubAgentTokenMemo,
203
209
  resolveAgentGitAuthEnv,
210
+ resolveCodexGitWritableRoots,
204
211
  resolveDefaultBranch,
205
212
  resolveDiffBaseRef,
206
213
  resolveGithubAgentToken,
207
214
  resolveGithubRepoSlug,
208
215
  resolvePrSelector,
216
+ resolvePrSelectors,
209
217
  resolveRepoRoot,
210
218
  resolveWorktreeStartPoint,
219
+ scrubGithubTokensFromChildEnv,
211
220
  slugify,
212
221
  stackMergeReadiness,
213
222
  submitPrStack,
214
223
  suggestSlug,
215
224
  takenSlugsFromThread,
216
225
  threadDisplayLabel,
226
+ warmGithubAgentAuth,
217
227
  worktreeDisplayLabel,
218
228
  worktreeDisplayLabelForGroup,
219
229
  worktreeNameFromPath
220
- } from "./chunk-7D27DD2X.js";
230
+ } from "./chunk-CIRXAYWS.js";
221
231
  import {
222
232
  ATTACHMENTS_DIR,
223
233
  LEGACY_ATTACHMENTS_DIR,
@@ -1328,9 +1338,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
1328
1338
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
1329
1339
  );
1330
1340
  }
1331
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-JQQLPJM5.js");
1341
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-M3OMVDDH.js");
1332
1342
  if (isGlobalThread2(thread)) {
1333
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-UK5LYFN5.js");
1343
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-AKEY4WSO.js");
1334
1344
  ensureGlobalCoordinatorCwd2(
1335
1345
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
1336
1346
  );
@@ -1348,10 +1358,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
1348
1358
  const env = childEnvWithAppSettings(cmd.env);
1349
1359
  try {
1350
1360
  if (isOrchestratorThread(thread)) {
1351
- Object.assign(env, await resolveAgentGitAuthEnv(env));
1361
+ mergeAgentGitAuthEnv(env, await resolveAgentGitAuthEnv(env));
1352
1362
  } else {
1353
- const originEnv = await originGhRepoEnv(thread.worktreePath, { env });
1354
- Object.assign(env, originEnv);
1363
+ mergeAgentGitAuthEnv(env, await originGhRepoEnv(thread.worktreePath, { env }));
1355
1364
  }
1356
1365
  } catch (err) {
1357
1366
  const detail = err instanceof Error ? err.message : String(err);
@@ -1574,9 +1583,10 @@ function formatArtifactDirective() {
1574
1583
  "Files / media browser (CMS file manager column):",
1575
1584
  "4) Call Sideboard MCP `present_files` with optional title, path, and datasource (brightsy|memory).",
1576
1585
  " Opens the Files column for browse/upload/pick. Do NOT say a file manager UI is missing.",
1577
- "Multiple-choice questions (any mode \u2014 not only Plan):",
1578
- "5) Call Sideboard MCP `ask_user` when the user should pick from predefined options (approach forks, requirements, which API). First write a short chat message that explains the decision and what each option means (tradeoffs, when to pick it). Include a description on every option. After calling, stop and wait for their next message with answers. Do not ask multiple-choice questions as plain chat bullets \u2014 use ask_user so Sideboard shows the composer picker.",
1579
- "Never say artifacts, CMS UI, or the Files column are unavailable. Prefer present_schema for list/edit/publish; present_files for storage UI; html fences for standalone pages; ask_user for predefined-option questions."
1586
+ "Multiple-choice questions:",
1587
+ "5) Call Sideboard MCP `ask_user` only when work is blocked on choosing among a few concrete options (approach forks, which API, auth vs cookies). First write a short chat message that explains the decision and what each option means (tradeoffs, when to pick it). Include a description on every option. After calling, stop and wait for their next message with answers. If you are asking a real multiple-choice, use ask_user rather than chat bullets so Sideboard shows the composer picker.",
1588
+ "Do not call ask_user 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. If one option is the obvious default, proceed without asking.",
1589
+ "Never say artifacts, CMS UI, or the Files column are unavailable. Prefer present_schema for list/edit/publish; present_files for storage UI; html fences for standalone pages; ask_user only for those blocked predefined-option questions."
1580
1590
  ].join("\n");
1581
1591
  }
1582
1592
  function formatUiReminder() {
@@ -1587,7 +1597,7 @@ function formatUiReminder() {
1587
1597
  "CMS forms/tables: MCP present_schema (Brightsy resource_id or inline schema+schemaUi).",
1588
1598
  "Files column: MCP present_files (brightsy account storage or memory).",
1589
1599
  "Do not say artifacts/CMS UI are unavailable.",
1590
- "Multiple-choice questions: MCP ask_user (composer picker, any mode). Explain options in chat first, then wait for answers."
1600
+ "ask_user: only when blocked on a real multiple-choice (approach fork, which API). Never for hellos, check-ins, or \u201Cwhat next?\u201D menus \u2014 reply in chat."
1591
1601
  ].join(" ");
1592
1602
  }
1593
1603
  var FILES_BY_AGENT = {
@@ -1874,7 +1884,10 @@ async function spawnWorkspaceScript(command, opts) {
1874
1884
  loginEnv
1875
1885
  );
1876
1886
  try {
1877
- Object.assign(env, await resolveAgentGitAuthEnv(env, { cwd: opts.worktreePath }));
1887
+ mergeAgentGitAuthEnv(
1888
+ env,
1889
+ await resolveAgentGitAuthEnv(env, { cwd: opts.worktreePath })
1890
+ );
1878
1891
  } catch {
1879
1892
  }
1880
1893
  const shell = process.platform === "darwin" ? "zsh" : "bash";
@@ -3759,6 +3772,7 @@ async function createThread(input, _onSetupLine) {
3759
3772
  let sourceRef = input.sourceRef;
3760
3773
  let sourceIsFork = false;
3761
3774
  let prUrl = null;
3775
+ let prTitle = null;
3762
3776
  if (input.sourceType === "pr") {
3763
3777
  const num = Number(input.sourceRef.replace(/^#/, ""));
3764
3778
  if (!Number.isFinite(num)) throw new Error(`Invalid PR number: ${input.sourceRef}`);
@@ -3774,6 +3788,12 @@ async function createThread(input, _onSetupLine) {
3774
3788
  } else if (input.sourceType === "branch") {
3775
3789
  if (!sourceRef || sourceRef === "HEAD" || sourceRef === "default") {
3776
3790
  sourceRef = await resolveDefaultBranch(repoPath);
3791
+ } else {
3792
+ const existing = await getPrForHeadBranch(repoPath, sourceRef);
3793
+ if (existing?.url) {
3794
+ prUrl = existing.url;
3795
+ prTitle = existing.title;
3796
+ }
3777
3797
  }
3778
3798
  } else if (input.sourceType === "adopt") {
3779
3799
  throw new Error("Use adoptThread() for adopt sources");
@@ -3804,14 +3824,15 @@ async function createThread(input, _onSetupLine) {
3804
3824
  sourceIsFork,
3805
3825
  parentThreadId: input.parentThreadId ?? null,
3806
3826
  status: "idle",
3807
- prUrl
3827
+ prUrl,
3828
+ prTitle
3808
3829
  });
3809
3830
  writeThread(thread);
3810
3831
  await ensureWorkspace(repoPath);
3811
3832
  return readThread(thread.id) ?? thread;
3812
3833
  }
3813
3834
  async function listLinearIssues(agent, repoPath) {
3814
- const { getAdapter: getAdapter2 } = await import("./agents-ODBP7J6E.js");
3835
+ const { getAdapter: getAdapter2 } = await import("./agents-WS5QV6LE.js");
3815
3836
  await requireAgent(agent, { requireLinear: true });
3816
3837
  const adapter = getAdapter2(agent);
3817
3838
  if (!adapter.listLinearIssues) {
@@ -4277,7 +4298,7 @@ import {
4277
4298
  } from "fs";
4278
4299
  import { tmpdir } from "os";
4279
4300
  import { join as join6 } from "path";
4280
- import Database from "better-sqlite3";
4301
+ import { createRequire } from "module";
4281
4302
  var CONDUCTOR_APP_SUPPORT = join6(
4282
4303
  process.env.HOME ?? "",
4283
4304
  "Library",
@@ -4286,6 +4307,11 @@ var CONDUCTOR_APP_SUPPORT = join6(
4286
4307
  );
4287
4308
  var CONDUCTOR_DB = join6(CONDUCTOR_APP_SUPPORT, "conductor.db");
4288
4309
  var CURSOR_SDK_STORE = join6(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
4310
+ function openReadonlySqlite(file) {
4311
+ const req = createRequire(import.meta.url);
4312
+ const Database = req("better-sqlite3");
4313
+ return new Database(file, { readonly: true, fileMustExist: true });
4314
+ }
4289
4315
  function mapAgentType(raw) {
4290
4316
  if (!raw) return null;
4291
4317
  const v = raw.toLowerCase();
@@ -4354,7 +4380,7 @@ async function adoptThread(input) {
4354
4380
  messages: input.messages ?? []
4355
4381
  });
4356
4382
  writeThread(thread);
4357
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-YWCC3WV4.js");
4383
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-ERZC7ULY.js");
4358
4384
  await ensureWorkspace2(repoPath);
4359
4385
  return thread;
4360
4386
  }
@@ -4378,7 +4404,7 @@ function listConductorWorkspaces() {
4378
4404
  }
4379
4405
  }
4380
4406
  }
4381
- const db = new Database(snapshot, { readonly: true, fileMustExist: true });
4407
+ const db = openReadonlySqlite(snapshot);
4382
4408
  try {
4383
4409
  const tables = db.prepare(`SELECT name FROM sqlite_master WHERE type='table'`).all();
4384
4410
  const names = new Set(tables.map((t) => t.name));
@@ -4469,7 +4495,7 @@ function importConductorWorkspace(workspaceId) {
4469
4495
  }
4470
4496
  }
4471
4497
  }
4472
- const db = new Database(snapshot, { readonly: true, fileMustExist: true });
4498
+ const db = openReadonlySqlite(snapshot);
4473
4499
  try {
4474
4500
  const row = db.prepare(
4475
4501
  `SELECT
@@ -6948,8 +6974,9 @@ var Orchestrator = class {
6948
6974
  return result;
6949
6975
  }
6950
6976
  async mergePr(threadRef) {
6951
- const { thread, selector, cwd } = await this.withPrSelector(threadRef);
6977
+ const { thread, selectors, cwd } = await this.withPrSelector(threadRef);
6952
6978
  this.assertNotGlobal(thread, "Merge PR");
6979
+ const selector = selectors[0];
6953
6980
  if (!selector) throw new Error("No pull request linked to this thread");
6954
6981
  const result = await mergePr(cwd, selector);
6955
6982
  const state = normalizePrState(result.state) || "MERGED";
@@ -6969,29 +6996,34 @@ var Orchestrator = class {
6969
6996
  await this.persistPrMetaAndMaybeArchive(thread, metaLike);
6970
6997
  return { url: metaLike.url, state };
6971
6998
  }
6972
- /** Resolve PR selector and optionally persist `prUrl` when found. */
6999
+ /** Resolve PR selectors and optionally persist `prUrl` when found. */
6973
7000
  async withPrSelector(threadRef) {
6974
7001
  const thread = this.requireThread(threadRef);
6975
- const selector = resolvePrSelector(thread);
7002
+ const selectors = resolvePrSelectors(thread);
6976
7003
  const cwd = thread.worktreePath;
6977
7004
  if (!cwd?.trim()) {
6978
7005
  throw new Error(`Thread ${threadRef} has no worktreePath`);
6979
7006
  }
6980
- return { thread, selector, cwd };
7007
+ return { thread, selectors, cwd };
6981
7008
  }
6982
7009
  async getPrChecks(threadRef) {
6983
- const { selector, cwd } = await this.withPrSelector(threadRef);
6984
- if (!selector) return null;
6985
- return getPrChecks(cwd, selector);
7010
+ const { selectors, cwd } = await this.withPrSelector(threadRef);
7011
+ for (const selector of selectors) {
7012
+ const checks = await getPrChecks(cwd, selector);
7013
+ if (checks) return checks;
7014
+ }
7015
+ return null;
6986
7016
  }
6987
7017
  async getPrMeta(threadRef) {
6988
- const { thread, selector, cwd } = await this.withPrSelector(threadRef);
6989
- if (!selector) return null;
6990
- const meta = await getPrMeta(cwd, selector);
6991
- if (meta) {
6992
- await this.persistPrMetaAndMaybeArchive(thread, meta);
7018
+ const { thread, selectors, cwd } = await this.withPrSelector(threadRef);
7019
+ for (const selector of selectors) {
7020
+ const meta = await getPrMeta(cwd, selector);
7021
+ if (meta) {
7022
+ await this.persistPrMetaAndMaybeArchive(thread, meta);
7023
+ return meta;
7024
+ }
6993
7025
  }
6994
- return meta;
7026
+ return null;
6995
7027
  }
6996
7028
  /**
6997
7029
  * Persist PR URL/title/state and Conductor-style auto-archive when the PR
@@ -7115,9 +7147,12 @@ var Orchestrator = class {
7115
7147
  return { stack: result.stack, threads: result.threads };
7116
7148
  }
7117
7149
  async getPrDetails(threadRef) {
7118
- const { thread, selector, cwd } = await this.withPrSelector(threadRef);
7119
- if (!selector) return null;
7120
- const details = await getPrDetails(cwd, selector);
7150
+ const { thread, selectors, cwd } = await this.withPrSelector(threadRef);
7151
+ let details = null;
7152
+ for (const selector of selectors) {
7153
+ details = await getPrDetails(cwd, selector);
7154
+ if (details) break;
7155
+ }
7121
7156
  if (details) {
7122
7157
  const patch = {};
7123
7158
  if (details.url && details.url !== thread.prUrl) patch.prUrl = details.url;
@@ -7302,7 +7337,7 @@ var Orchestrator = class {
7302
7337
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
7303
7338
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
7304
7339
  try {
7305
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-YWCC3WV4.js");
7340
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-ERZC7ULY.js");
7306
7341
  await ensureWorkspace2(thread.repoPath);
7307
7342
  } catch {
7308
7343
  }
@@ -7345,7 +7380,7 @@ var Orchestrator = class {
7345
7380
  return restored2;
7346
7381
  }
7347
7382
  if (!existsSync14(thread.worktreePath)) {
7348
- const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-7YNSJ224.js");
7383
+ const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-DA4BOV7G.js");
7349
7384
  const { execa: execa5 } = await import("execa");
7350
7385
  const slug = thread.worktreePath.split("/").pop();
7351
7386
  const dest = thread.worktreePath;
@@ -7454,7 +7489,7 @@ async function startOrchestration(opts) {
7454
7489
  sourceRef: "default",
7455
7490
  ...createOpts
7456
7491
  }).catch(async () => {
7457
- const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-7YNSJ224.js");
7492
+ const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-DA4BOV7G.js");
7458
7493
  const repo = await resolveRepoRoot2(repoPath);
7459
7494
  const def = await resolveDefaultBranch2(repo);
7460
7495
  return orch.createThread({
@@ -8205,6 +8240,14 @@ function withTimeout(promise, ms, label) {
8205
8240
  }
8206
8241
  async function startMcpServer() {
8207
8242
  const orch = getOrchestrator();
8243
+ try {
8244
+ await warmGithubAgentAuth();
8245
+ } catch (err) {
8246
+ console.error(
8247
+ "[sideboard-mcp] GitHub agent auth warm skipped:",
8248
+ err instanceof Error ? err.message : err
8249
+ );
8250
+ }
8208
8251
  try {
8209
8252
  const { maxConcurrentAgents: maxConcurrentAgents2 } = await import("./app-settings-BRF7VKTQ.js");
8210
8253
  orch.setMaxConcurrent(maxConcurrentAgents2());
@@ -8311,7 +8354,7 @@ async function startMcpServer() {
8311
8354
  );
8312
8355
  server.tool(
8313
8356
  "ask_user",
8314
- "Ask the user clarifying multiple-choice questions in Sideboard\u2019s composer (any mode \u2014 not only Plan). Before calling, write a short chat message explaining the decision and what each option means. Include a description on every option. Use when the user should pick from predefined options (approach forks, requirements, which API) \u2014 not for \u201Cis the plan ready?\u201D. After calling, stop and wait for their next message with answers.",
8357
+ "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.",
8315
8358
  {
8316
8359
  questions: z3.array(
8317
8360
  z3.object({
@@ -9540,6 +9583,75 @@ async function runCloudConnect(opts) {
9540
9583
  }
9541
9584
  }
9542
9585
 
9586
+ // src/agents/user-mcp-config.ts
9587
+ import { existsSync as existsSync15, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
9588
+ import { homedir as homedir2 } from "os";
9589
+ import { dirname as dirname4, join as join13 } from "path";
9590
+ function userCursorMcpConfigPath() {
9591
+ return join13(homedir2(), ".cursor", "mcp.json");
9592
+ }
9593
+ function userClaudeMcpConfigPath() {
9594
+ return join13(homedir2(), ".claude.json");
9595
+ }
9596
+ function asObject(value) {
9597
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
9598
+ return { ...value };
9599
+ }
9600
+ function stripElectronSpawnEnv(env) {
9601
+ if (!env) return void 0;
9602
+ const next = { ...env };
9603
+ delete next.ELECTRON_RUN_AS_NODE;
9604
+ delete next.ELECTRON_RUN_AS_NODE;
9605
+ return Object.keys(next).length > 0 ? next : void 0;
9606
+ }
9607
+ function mergeSideboardIntoMcpServersJson(existing, sideboard) {
9608
+ const root = asObject(existing);
9609
+ const servers = asObject(root.mcpServers);
9610
+ const env = stripElectronSpawnEnv(sideboard.env);
9611
+ servers.sideboard = {
9612
+ type: "stdio",
9613
+ command: sideboard.command,
9614
+ ...sideboard.args && sideboard.args.length > 0 ? { args: sideboard.args } : {},
9615
+ ...env ? { env } : {}
9616
+ };
9617
+ return { ...root, mcpServers: servers };
9618
+ }
9619
+ function writeMergedMcpServersJson(configPath, sideboard) {
9620
+ let existing = {};
9621
+ if (existsSync15(configPath)) {
9622
+ try {
9623
+ existing = JSON.parse(readFileSync10(configPath, "utf8"));
9624
+ } catch {
9625
+ existing = {};
9626
+ }
9627
+ }
9628
+ const next = mergeSideboardIntoMcpServersJson(existing, sideboard);
9629
+ mkdirSync5(dirname4(configPath), { recursive: true });
9630
+ writeFileSync4(configPath, `${JSON.stringify(next, null, 2)}
9631
+ `);
9632
+ }
9633
+ function launchFromResolved(server) {
9634
+ return {
9635
+ command: server.command,
9636
+ args: server.args,
9637
+ env: {
9638
+ ...server.env ?? {},
9639
+ SIDEBOARD_APP_DATA: appDataDir()
9640
+ }
9641
+ };
9642
+ }
9643
+ async function registerPackagedUserMcpClients() {
9644
+ const launch = launchFromResolved(await resolveSideboardMcpServer());
9645
+ const cursor = userCursorMcpConfigPath();
9646
+ writeMergedMcpServersJson(cursor, launch);
9647
+ const claude = userClaudeMcpConfigPath();
9648
+ if (existsSync15(claude)) {
9649
+ writeMergedMcpServersJson(claude, launch);
9650
+ return { cursor, claude };
9651
+ }
9652
+ return { cursor };
9653
+ }
9654
+
9543
9655
  // src/slack/oauth.ts
9544
9656
  import { randomBytes as randomBytes2 } from "crypto";
9545
9657
 
@@ -10306,15 +10418,10 @@ function connectSession2(ws, opts, log) {
10306
10418
  }
10307
10419
 
10308
10420
  // src/slack/listen.ts
10309
- var SLACK_LISTEN_BUSY_REPLY = [
10310
- "Sideboard is busy with an in-progress turn.",
10311
- "I did not start a new request.",
10312
- 'Send "stop" to interrupt, or try again when idle.'
10313
- ].join(" ");
10314
10421
  var SLACK_LISTEN_STOPPED_REPLY = "Sideboard stopped the in-progress turn. Send another message when you want to continue.";
10315
10422
  var SLACK_LISTEN_TIMEOUT_REPLY = [
10316
10423
  "Sideboard timed out waiting for the local agent turn to finish.",
10317
- 'Send "stop" to interrupt, or try again.'
10424
+ 'Send another message to interrupt and retry, or send "stop".'
10318
10425
  ].join(" ");
10319
10426
  var handleChain = Promise.resolve();
10320
10427
  function enqueueHandle(fn) {
@@ -10328,6 +10435,30 @@ function enqueueHandle(fn) {
10328
10435
  function refreshWorkspaces2() {
10329
10436
  return getOrchestrator().listWorkspaces();
10330
10437
  }
10438
+ function slackInboundSuperseded(opts) {
10439
+ if (opts.inboundGeneration === void 0 || !opts.currentInboundGeneration) {
10440
+ return false;
10441
+ }
10442
+ return opts.currentInboundGeneration() !== opts.inboundGeneration;
10443
+ }
10444
+ function interruptSlackCoordinatorForInbound(msg, agent, log = () => void 0) {
10445
+ const userId = msg.userId?.trim();
10446
+ if (!userId) return false;
10447
+ const coordinator = ensureSlackCoordinator(msg.teamId, userId, agent);
10448
+ const fresh = readThread(coordinator.id) ?? coordinator;
10449
+ if (fresh.status !== "running" && fresh.status !== "queued") return false;
10450
+ try {
10451
+ getOrchestrator().stop(fresh.id, { clearQueue: true });
10452
+ log(
10453
+ `interrupt ${msg.kind} ${msg.ts} \u2192 coordinator ${fresh.id.slice(0, 8)} (${fresh.status})`
10454
+ );
10455
+ return true;
10456
+ } catch (err) {
10457
+ const errMsg = err instanceof Error ? err.message : String(err);
10458
+ log(`interrupt ${msg.ts}: ${errMsg}`);
10459
+ return false;
10460
+ }
10461
+ }
10331
10462
  function formatSlackInboundPrompt(msg) {
10332
10463
  const kind = msg.kind === "mention" ? "Slack @mention" : "Slack DM";
10333
10464
  return `${kind}
@@ -10458,7 +10589,13 @@ async function relayCoordinatorReplyToSlack(threadId, opts) {
10458
10589
  }
10459
10590
  async function handleSlackInbound(msg, opts) {
10460
10591
  const log = opts.onLog ?? (() => void 0);
10461
- await ackSlackInboundSeen(msg, opts);
10592
+ if (!opts.skipAck) {
10593
+ await ackSlackInboundSeen(msg, opts);
10594
+ }
10595
+ if (slackInboundSuperseded(opts)) {
10596
+ log(`skip superseded ${msg.kind} ${msg.ts}`);
10597
+ return;
10598
+ }
10462
10599
  const agent = coerceOrchestratorAgent(opts.agent ?? getDefaultAgent());
10463
10600
  const workspaces = refreshWorkspaces2();
10464
10601
  for (const ws of workspaces) {
@@ -10470,25 +10607,31 @@ async function handleSlackInbound(msg, opts) {
10470
10607
  log(`skip ${msg.kind} ${msg.ts}: missing Slack user id`);
10471
10608
  return;
10472
10609
  }
10610
+ if (slackInboundSuperseded(opts)) {
10611
+ log(`skip superseded ${msg.kind} ${msg.ts}`);
10612
+ return;
10613
+ }
10473
10614
  const coordinator = ensureSlackCoordinator(msg.teamId, userId, agent);
10474
- const fresh = readThread(coordinator.id) ?? coordinator;
10615
+ let fresh = readThread(coordinator.id) ?? coordinator;
10475
10616
  if (isSlackStopCommand(msg.text)) {
10476
10617
  try {
10477
- getOrchestrator().stop(fresh.id);
10618
+ getOrchestrator().stop(fresh.id, { clearQueue: true });
10478
10619
  log(`stop ${msg.kind} ${msg.ts} \u2192 coordinator ${fresh.id.slice(0, 8)}`);
10479
10620
  } catch (err) {
10480
10621
  const errMsg = err instanceof Error ? err.message : String(err);
10481
10622
  log(`stop ${msg.ts}: ${errMsg}`);
10482
10623
  }
10624
+ if (slackInboundSuperseded(opts)) {
10625
+ log(`skip superseded stop reply ${msg.ts}`);
10626
+ return;
10627
+ }
10483
10628
  await postSlackReply(msg, SLACK_LISTEN_STOPPED_REPLY, opts);
10484
10629
  log(`replied stopped ${msg.ts}`);
10485
10630
  return;
10486
10631
  }
10487
10632
  if (fresh.status === "running" || fresh.status === "queued") {
10488
- log(`busy ${msg.kind} ${msg.ts} \u2192 coordinator ${fresh.id.slice(0, 8)} (${fresh.status})`);
10489
- await postSlackReply(msg, SLACK_LISTEN_BUSY_REPLY, opts);
10490
- log(`replied busy ${msg.ts}`);
10491
- return;
10633
+ interruptSlackCoordinatorForInbound(msg, agent, log);
10634
+ fresh = readThread(coordinator.id) ?? coordinator;
10492
10635
  }
10493
10636
  log(
10494
10637
  `run ${msg.kind} ${msg.ts} user ${userId} \u2192 coordinator ${fresh.id.slice(0, 8)} (${inventory.length} workspace${inventory.length === 1 ? "" : "s"})`
@@ -10505,8 +10648,21 @@ async function handleSlackInbound(msg, opts) {
10505
10648
  try {
10506
10649
  await orch.send(fresh.id, prompt);
10507
10650
  await orch.waitForTurn(fresh.id, 14 * 60 * 1e3);
10651
+ if (slackInboundSuperseded(opts)) {
10652
+ log(`turn finished ${msg.ts} (superseded)`);
10653
+ return;
10654
+ }
10655
+ const after = readThread(fresh.id);
10656
+ if (after?.status === "stopped") {
10657
+ log(`turn finished ${msg.ts} (interrupted, skip post)`);
10658
+ return;
10659
+ }
10508
10660
  reply = orch.getTurnResult(fresh.id).text.trim();
10509
10661
  } catch (err) {
10662
+ if (slackInboundSuperseded(opts)) {
10663
+ log(`turn error ${msg.ts} (superseded)`);
10664
+ return;
10665
+ }
10510
10666
  const errMsg = err instanceof Error ? err.message : String(err);
10511
10667
  const timedOut = /timed out|timeout/i.test(errMsg);
10512
10668
  const errorReply = timedOut ? SLACK_LISTEN_TIMEOUT_REPLY : `Sideboard coordinator error: ${errMsg}`;
@@ -10554,19 +10710,35 @@ async function runSlackListen(opts = {}) {
10554
10710
  const log = opts.onLog ?? console.log;
10555
10711
  const connected = listSlackWorkspacesRaw();
10556
10712
  const orch = getOrchestrator();
10713
+ const agent = coerceOrchestratorAgent(opts.agent ?? getDefaultAgent());
10714
+ let inboundGeneration = 0;
10557
10715
  const off = orch.on((event) => {
10558
10716
  if (event.type !== "turn_finished") return;
10559
10717
  void relayCoordinatorReplyToSlack(event.threadId, opts);
10560
10718
  });
10561
- const onInbound = (msg) => enqueueHandle(async () => {
10719
+ const onInbound = (msg) => {
10562
10720
  if (!isInboundForThisDesktop(msg)) {
10563
10721
  log(
10564
10722
  `skip ${msg.kind} ${msg.ts}: Slack user ${msg.userId ?? "?"} is not connected on this Mac`
10565
10723
  );
10566
10724
  return;
10567
10725
  }
10568
- await handleSlackInbound(msg, opts);
10569
- });
10726
+ const generation = ++inboundGeneration;
10727
+ void ackSlackInboundSeen(msg, opts);
10728
+ interruptSlackCoordinatorForInbound(msg, agent, log);
10729
+ return enqueueHandle(async () => {
10730
+ if (generation !== inboundGeneration) {
10731
+ log(`skip superseded ${msg.kind} ${msg.ts}`);
10732
+ return;
10733
+ }
10734
+ await handleSlackInbound(msg, {
10735
+ ...opts,
10736
+ skipAck: true,
10737
+ inboundGeneration: generation,
10738
+ currentInboundGeneration: () => inboundGeneration
10739
+ });
10740
+ });
10741
+ };
10570
10742
  try {
10571
10743
  const relay = (opts.relayUrl ?? slackRelayUrl()).trim();
10572
10744
  if (!relay) {
@@ -11168,7 +11340,6 @@ export {
11168
11340
  SIDEBOARD_FORCE_STOP,
11169
11341
  SIDEBOARD_MCP_ALLOWED_TOOLS,
11170
11342
  SIDEBOARD_MCP_PROFILE_ENV,
11171
- SLACK_LISTEN_BUSY_REPLY,
11172
11343
  SLACK_LISTEN_STOPPED_REPLY,
11173
11344
  SLACK_LISTEN_TIMEOUT_REPLY,
11174
11345
  SLACK_OAUTH_CANCELLED,
@@ -11238,6 +11409,7 @@ export {
11238
11409
  cleanupOrphanWorktrees,
11239
11410
  cloneRepoIntoSideboard,
11240
11411
  codexAdapter,
11412
+ codexSandboxWritableRootsArgs,
11241
11413
  codexUnattendedGitConfigArgs,
11242
11414
  coerceOrchestratorAgent,
11243
11415
  collectTakenTeamSlugs,
@@ -11354,6 +11526,7 @@ export {
11354
11526
  getPr,
11355
11527
  getPrChecks,
11356
11528
  getPrDetails,
11529
+ getPrForHeadBranch,
11357
11530
  getPrMeta,
11358
11531
  getPrStack,
11359
11532
  getRepoSetupInfo,
@@ -11384,12 +11557,14 @@ export {
11384
11557
  initializeGitRepository,
11385
11558
  inspectGitWorktree,
11386
11559
  installAgent,
11560
+ interruptSlackCoordinatorForInbound,
11387
11561
  isAskUserToolName,
11388
11562
  isBrightsyConnected,
11389
11563
  isBrightsyNdjsonLine,
11390
11564
  isCloudCoordinatorThread,
11391
11565
  isConductorBundledCli,
11392
11566
  isCursorAutoModel,
11567
+ isDefaultishSourceRef,
11393
11568
  isDirty,
11394
11569
  isGhRateLimitError,
11395
11570
  isGlobalRepoPath,
@@ -11456,8 +11631,10 @@ export {
11456
11631
  maybeCompactContext,
11457
11632
  mcpAllowTools,
11458
11633
  mcpAuthWarnings,
11634
+ mergeAgentGitAuthEnv,
11459
11635
  mergePr,
11460
11636
  mergePrStack,
11637
+ mergeSideboardIntoMcpServersJson,
11461
11638
  mergeUsage,
11462
11639
  nextPastedTextName,
11463
11640
  nextThinkingEffort,
@@ -11508,6 +11685,7 @@ export {
11508
11685
  recordSlackOutboundWatch,
11509
11686
  refreshGitHubAuth,
11510
11687
  refreshSlackReplyBadges,
11688
+ registerPackagedUserMcpClients,
11511
11689
  releaseCaffeinateHoldForThread,
11512
11690
  removeWorkspace,
11513
11691
  removeWorktree,
@@ -11516,9 +11694,11 @@ export {
11516
11694
  requestReview,
11517
11695
  requireAgent,
11518
11696
  resetGhStackDetectCache,
11697
+ resetGithubAgentTokenMemo,
11519
11698
  resolveAgentExecutable,
11520
11699
  resolveAgentGitAuthEnv,
11521
11700
  resolveClaudeExecutable,
11701
+ resolveCodexGitWritableRoots,
11522
11702
  resolveCommandBinarySync,
11523
11703
  resolveConductorCursorAgentId,
11524
11704
  resolveCursorModelId,
@@ -11535,6 +11715,7 @@ export {
11535
11715
  resolveNewThreadOptions,
11536
11716
  resolvePlanMarkdown,
11537
11717
  resolvePrSelector,
11718
+ resolvePrSelectors,
11538
11719
  resolveQuotaFallbackAgent,
11539
11720
  resolveRepoRoot,
11540
11721
  resolveReviewGuidelines,
@@ -11557,6 +11738,7 @@ export {
11557
11738
  sanitizeMcpServerName,
11558
11739
  saveAppSettings,
11559
11740
  saveLinearOAuth,
11741
+ scrubGithubTokensFromChildEnv,
11560
11742
  secureFileUnlocksWith,
11561
11743
  setCaffeinateHold,
11562
11744
  setHttpFetchImpl,
@@ -11627,7 +11809,10 @@ export {
11627
11809
  updateLinearIssue,
11628
11810
  updateOpencodeSettings,
11629
11811
  updateThread,
11812
+ userClaudeMcpConfigPath,
11813
+ userCursorMcpConfigPath,
11630
11814
  validateLinearApiKey,
11815
+ warmGithubAgentAuth,
11631
11816
  withAgentInstructions,
11632
11817
  withExportedPath,
11633
11818
  withThreadLock,