@sideboard-ai/core 0.1.57 → 0.1.60

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 (39) hide show
  1. package/dist/{agents-2YYWW723.js → agents-B2723EOF.js} +4 -4
  2. package/dist/{agents-AQFEFKBL.js → agents-EADRAAPG.js} +4 -4
  3. package/dist/{app-settings-2LQNRTBE.js → app-settings-TBHTJWLQ.js} +1 -1
  4. package/dist/{app-settings-73DI4B6T.js → app-settings-YBNZ4XSR.js} +1 -1
  5. package/dist/{chunk-5DMYULLC.js → chunk-2P2UK7E7.js} +2 -2
  6. package/dist/{chunk-7LNGIP3X.js → chunk-73AT5K4A.js} +15 -3
  7. package/dist/{chunk-DIOF73S2.js → chunk-75QR74KM.js} +7 -2
  8. package/dist/{chunk-EBWEKG52.js → chunk-CHGOMFTA.js} +3 -3
  9. package/dist/{chunk-C4BCC5X5.js → chunk-FGNBTFRD.js} +7 -2
  10. package/dist/{chunk-H6GGDLYS.js → chunk-JB2H7E7V.js} +1 -1
  11. package/dist/{chunk-BXJ76RHF.js → chunk-MBAL5FE6.js} +4 -4
  12. package/dist/{chunk-XOU6HNQJ.js → chunk-MFGGIRUA.js} +39 -13
  13. package/dist/{chunk-S42XDFKF.js → chunk-MLAST5BB.js} +3 -3
  14. package/dist/{chunk-C3J4GDW4.js → chunk-MX7UH5AT.js} +76 -60
  15. package/dist/{chunk-DQJ5D42H.js → chunk-NTYRBRE7.js} +44 -13
  16. package/dist/{chunk-DZFH2KLT.js → chunk-P7EQUFB6.js} +39 -13
  17. package/dist/{chunk-V2DUTUNC.js → chunk-QJBC4BRK.js} +2 -2
  18. package/dist/{chunk-ZNM4MAN4.js → chunk-RIWUU7FO.js} +15 -3
  19. package/dist/{chunk-UA5NDAHL.js → chunk-XWPKLRB3.js} +44 -13
  20. package/dist/{chunk-7F454EE2.js → chunk-ZXDOI6IH.js} +76 -53
  21. package/dist/{connected-teams-ONC4666A.js → connected-teams-MKIYKOYA.js} +2 -2
  22. package/dist/{connected-teams-2ANNZCP5.js → connected-teams-RSL4VB3X.js} +2 -2
  23. package/dist/{coordinator-prompt-RBKUNRPR.js → coordinator-prompt-4P23VVFC.js} +4 -4
  24. package/dist/{coordinator-prompt-ICF36NUQ.js → coordinator-prompt-LNWHZ64D.js} +4 -4
  25. package/dist/{global-workspace-667XLBW6.js → global-workspace-BHT4XUAA.js} +5 -5
  26. package/dist/{global-workspace-S7VLWPWG.js → global-workspace-TMMAVRVW.js} +5 -5
  27. package/dist/index.cjs +303 -115
  28. package/dist/index.d.cts +15 -6
  29. package/dist/index.d.ts +15 -6
  30. package/dist/index.js +157 -64
  31. package/dist/mcp/run-stdio.cjs +303 -126
  32. package/dist/mcp/run-stdio.js +156 -68
  33. package/dist/{run-ZJKYHXGN.js → run-GPSVV5OH.js} +1 -1
  34. package/dist/{run-H3IMNOYN.js → run-HZTBWGQR.js} +1 -1
  35. package/dist/{workspaces-FPJJXXAE.js → workspaces-3GO3EDQG.js} +6 -6
  36. package/dist/{workspaces-LYIDE2VR.js → workspaces-GFPGDGCS.js} +6 -6
  37. package/dist/{worktree-5KEQWSAF.js → worktree-3L2G6PCM.js} +2 -2
  38. package/dist/{worktree-RWGL7FUV.js → worktree-GBLHEFHR.js} +2 -2
  39. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -712,9 +712,14 @@ function resolveThreadDefaults(settings = loadAppSettings()) {
712
712
  function resolveNewThreadOptions(overrides = {}, settings = loadAppSettings()) {
713
713
  const defaults = resolveThreadDefaults(settings);
714
714
  const effort = overrides.effort === void 0 || overrides.effort === null ? defaults.effort : normalizeThinkingEffort(overrides.effort) ?? defaults.effort;
715
+ const agent = overrides.agent ?? defaults.agent;
716
+ let model = overrides.model === void 0 ? defaults.model : overrides.model?.trim() || null;
717
+ if (model && agent !== "cursor" && /^(default|auto)$/i.test(model.trim())) {
718
+ model = null;
719
+ }
715
720
  return {
716
- agent: overrides.agent ?? defaults.agent,
717
- model: overrides.model === void 0 ? defaults.model : overrides.model?.trim() || null,
721
+ agent,
722
+ model,
718
723
  effort,
719
724
  fast: overrides.fast === void 0 || overrides.fast === null ? defaults.fast : Boolean(overrides.fast)
720
725
  };
@@ -2073,7 +2078,8 @@ async function run(file, args, opts) {
2073
2078
  const result = await (0, import_execa.execa)(file, args, {
2074
2079
  cwd: opts?.cwd,
2075
2080
  env: { ...process.env, ...opts?.env },
2076
- reject: opts?.reject ?? true
2081
+ reject: opts?.reject ?? true,
2082
+ ...opts?.timeoutMs != null ? { timeout: opts.timeoutMs } : {}
2077
2083
  });
2078
2084
  return {
2079
2085
  stdout: result.stdout ?? "",
@@ -2103,11 +2109,22 @@ async function git(args, cwd, opts) {
2103
2109
  return run("git", [...prefix, ...args], {
2104
2110
  cwd,
2105
2111
  reject: opts?.reject,
2106
- env: opts?.env
2112
+ timeoutMs: opts?.timeoutMs,
2113
+ // Never block forever on a credential/SSH prompt inside MCP / Electron.
2114
+ env: {
2115
+ GIT_TERMINAL_PROMPT: "0",
2116
+ GIT_ASKPASS: process.env.GIT_ASKPASS || "echo",
2117
+ GIT_SSH_COMMAND: process.env.GIT_SSH_COMMAND || "ssh -o BatchMode=yes -o ConnectTimeout=15",
2118
+ ...opts?.env
2119
+ }
2107
2120
  });
2108
2121
  }
2109
2122
  async function gh(args, cwd, opts) {
2110
- return run("gh", args, { cwd, reject: opts?.reject });
2123
+ return run("gh", args, {
2124
+ cwd,
2125
+ reject: opts?.reject,
2126
+ timeoutMs: opts?.timeoutMs
2127
+ });
2111
2128
  }
2112
2129
  async function resolveGhAuthToken(cwd) {
2113
2130
  const result = await gh(["auth", "token"], cwd, { reject: false });
@@ -2576,12 +2593,16 @@ async function ensureGhPreferOrigin(cwd) {
2576
2593
  const hasAlt = Boolean(await slugFromGitRemote(cwd, "upstream")) || Boolean(await slugFromGitRemote(cwd, "github"));
2577
2594
  if (!hasAlt) return;
2578
2595
  const view = await gh(["repo", "set-default", "--view"], cwd, {
2579
- reject: false
2596
+ reject: false,
2597
+ timeoutMs: 15e3
2580
2598
  });
2581
2599
  const current = `${view.stdout}
2582
2600
  ${view.stderr}`;
2583
2601
  if (view.exitCode === 0 && current.includes(originSlug)) return;
2584
- await gh(["repo", "set-default", "origin"], cwd, { reject: false });
2602
+ await gh(["repo", "set-default", "origin"], cwd, {
2603
+ reject: false,
2604
+ timeoutMs: 15e3
2605
+ });
2585
2606
  }
2586
2607
  async function originGhRepoEnv(cwd) {
2587
2608
  await ensureGhPreferOrigin(cwd);
@@ -3127,20 +3148,42 @@ async function createThreadWorktree(opts) {
3127
3148
  throw new Error(`Worktree already exists at ${worktreePath}`);
3128
3149
  }
3129
3150
  await ensureGhPreferOrigin(opts.repoPath);
3130
- await git(["fetch", "origin", "--prune"], opts.repoPath, { reject: false });
3131
- if (!isLocalPrFetchBranch(opts.sourceRef) && !opts.sourceRef.startsWith("origin/") && !opts.sourceRef.startsWith("refs/")) {
3132
- await git(["fetch", "origin", opts.sourceRef], opts.repoPath, {
3133
- reject: false
3151
+ let startPoint = null;
3152
+ try {
3153
+ startPoint = await resolveWorktreeStartPoint(opts.repoPath, opts.sourceRef);
3154
+ } catch {
3155
+ startPoint = null;
3156
+ }
3157
+ if (!isLocalPrFetchBranch(opts.sourceRef)) {
3158
+ const fetchTimeoutMs = startPoint ? 12e3 : 45e3;
3159
+ await git(["fetch", "origin", "--prune"], opts.repoPath, {
3160
+ reject: false,
3161
+ timeoutMs: fetchTimeoutMs
3134
3162
  });
3163
+ if (!opts.sourceRef.startsWith("origin/") && !opts.sourceRef.startsWith("refs/")) {
3164
+ await git(["fetch", "origin", opts.sourceRef], opts.repoPath, {
3165
+ reject: false,
3166
+ timeoutMs: fetchTimeoutMs
3167
+ });
3168
+ }
3169
+ try {
3170
+ startPoint = await resolveWorktreeStartPoint(
3171
+ opts.repoPath,
3172
+ opts.sourceRef
3173
+ );
3174
+ } catch (err) {
3175
+ if (!startPoint) throw err;
3176
+ }
3177
+ }
3178
+ if (!startPoint) {
3179
+ throw new Error(
3180
+ `Invalid git reference: ${opts.sourceRef} (and fetch did not resolve it)`
3181
+ );
3135
3182
  }
3136
- const startPoint = await resolveWorktreeStartPoint(
3137
- opts.repoPath,
3138
- opts.sourceRef
3139
- );
3140
3183
  const add = await git(
3141
3184
  ["worktree", "add", "-b", branchName, worktreePath, startPoint],
3142
3185
  opts.repoPath,
3143
- { reject: false }
3186
+ { reject: false, timeoutMs: 6e4 }
3144
3187
  );
3145
3188
  if (add.exitCode !== 0) {
3146
3189
  const err = `${add.stderr}
@@ -3151,7 +3194,7 @@ ${add.stdout}`;
3151
3194
  const retry = await git(
3152
3195
  ["worktree", "add", "-b", alt, worktreePath, startPoint],
3153
3196
  opts.repoPath,
3154
- { reject: false }
3197
+ { reject: false, timeoutMs: 6e4 }
3155
3198
  );
3156
3199
  if (retry.exitCode === 0) {
3157
3200
  branchName = alt;
@@ -3540,7 +3583,9 @@ function coordinatorTurnReminder(opts) {
3540
3583
  "- You oversee Sideboard worktree agents. You are not yourself checked out in a project worktree.",
3541
3584
  "- This cwd is a synthetic empty home (not a git repo). Emptiness here is expected \u2014 it is not a problem to fix.",
3542
3585
  "- Registered workspaces / child threads are the fleet you manage via Sideboard MCP.",
3543
- `- Parent thread id (pass as parentThreadId when creating children): ${opts.parentId}`,
3586
+ `- YOUR orchestration thread id is ${opts.parentId}`,
3587
+ `- Always pass parentThreadId="${opts.parentId}" on create_thread (never invent or reuse another uuid).`,
3588
+ `- Or omit parentThreadId \u2014 Sideboard MCP already binds children to ${opts.parentId}.`,
3544
3589
  goal ? `- Goal / title: ${goal}` : null,
3545
3590
  accountDefaultsPlaybookLine(),
3546
3591
  `- For "what's going on": call list_threads (and list_workspaces if needed). Summarize fleet status \u2014 do not ls/git-status this synthetic home.`,
@@ -3549,10 +3594,33 @@ function coordinatorTurnReminder(opts) {
3549
3594
  "- When naming threads for the user, link them as `[Title](sideboard://thread/<id>)`."
3550
3595
  ].filter(Boolean).join("\n");
3551
3596
  }
3552
- function ensureGlobalCoordinatorCwd() {
3597
+ function ensureGlobalCoordinatorCwd(opts) {
3553
3598
  const dir = globalAgentCwd();
3554
- (0, import_node_fs6.mkdirSync)(dir, { recursive: true });
3599
+ try {
3600
+ (0, import_node_fs6.mkdirSync)(dir, { recursive: true });
3601
+ } catch {
3602
+ return dir;
3603
+ }
3555
3604
  const reposDir = sideboardReposDir();
3605
+ let orchId = opts?.orchestratorThreadId?.trim() || "";
3606
+ if (!orchId) {
3607
+ try {
3608
+ const existing = (0, import_node_fs6.readFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), "utf8");
3609
+ const m = existing.match(
3610
+ /YOUR orchestration thread id is `([0-9a-f-]{36})`/i
3611
+ );
3612
+ if (m?.[1]) orchId = m[1];
3613
+ } catch {
3614
+ }
3615
+ }
3616
+ const parentBlock = orchId ? [
3617
+ "",
3618
+ `## This chat's id`,
3619
+ "",
3620
+ `YOUR orchestration thread id is \`${orchId}\`.`,
3621
+ `On every create_thread, pass parentThreadId="${orchId}" \u2014 or omit parentThreadId (Sideboard binds it automatically).`,
3622
+ "Never invent a uuid and never reuse an id from an earlier conversation."
3623
+ ] : [];
3556
3624
  const body = [
3557
3625
  "# Sideboard Orchestration",
3558
3626
  "",
@@ -3562,6 +3630,7 @@ function ensureGlobalCoordinatorCwd() {
3562
3630
  "Repos from `list_workspaces` and threads from `list_threads` are the fleet you orchestrate.",
3563
3631
  'For status questions ("what\'s going on?"), use `list_threads` / `list_workspaces` \u2014 never diagnose this synthetic home as a broken worktree.',
3564
3632
  "Bash is fine for inspecting **child worktree** / registered-repo paths, and for greenfield repo setup under the Sideboard repos directory \u2014 not for treating this home as the project.",
3633
+ ...parentBlock,
3565
3634
  "",
3566
3635
  COORDINATOR_TOOL_PLAYBOOK,
3567
3636
  "",
@@ -3569,16 +3638,21 @@ function ensureGlobalCoordinatorCwd() {
3569
3638
  "",
3570
3639
  coordinatorGreenfieldPlaybook(reposDir),
3571
3640
  "",
3572
- "When creating threads, pass `repoPath` from `list_workspaces` (or the path you just registered) and `parentThreadId` for children.",
3641
+ "When creating threads, pass `repoPath` from `list_workspaces` (or the path you just registered).",
3642
+ orchId ? `Pass parentThreadId="${orchId}" (or omit it). Never invent another parentThreadId.` : "Pass `parentThreadId` for children (this chat's id from the turn reminder).",
3573
3643
  "Omit `agent` / `model` on `create_thread` unless you have a reason to override Account defaults.",
3644
+ "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.",
3574
3645
  "Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn.",
3575
3646
  "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 \u2192 draft PR via worktree agent.",
3576
3647
  "Always ask worktree agents to open draft PRs (`send_to_thread` + `gh pr create --draft -R <origin>`); never open PRs from the orchestrator."
3577
3648
  ].join("\n");
3578
- (0, import_node_fs6.writeFileSync)((0, import_node_path7.join)(dir, "CLAUDE.md"), `${body}
3649
+ try {
3650
+ (0, import_node_fs6.writeFileSync)((0, import_node_path7.join)(dir, "CLAUDE.md"), `${body}
3579
3651
  `, "utf8");
3580
- (0, import_node_fs6.writeFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), `${body}
3652
+ (0, import_node_fs6.writeFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), `${body}
3581
3653
  `, "utf8");
3654
+ } catch {
3655
+ }
3582
3656
  return dir;
3583
3657
  }
3584
3658
  function coordinatorSystemPrompt(opts) {
@@ -3598,12 +3672,12 @@ function coordinatorSystemPrompt(opts) {
3598
3672
  COORDINATOR_TOOL_PLAYBOOK,
3599
3673
  accountDefaultsPlaybookLine(),
3600
3674
  coordinatorGreenfieldPlaybook(reposDir),
3601
- "When creating threads, pass the correct repoPath for the target workspace and parentThreadId for children.",
3675
+ "When creating threads, pass the correct repoPath for the target workspace.",
3676
+ `YOUR orchestration thread id is ${opts.parentId} \u2014 pass parentThreadId="${opts.parentId}" on create_thread, or omit parentThreadId (Sideboard binds it). Never invent a uuid.`,
3602
3677
  "Omit agent/model on create_thread unless you need to override Account defaults.",
3603
3678
  "Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread (implement) \u2192 wait_for_turn \u2192 send_to_thread (ask for `gh pr create --draft -R <origin-owner/name>` using the workspace github slug) \u2192 wait_for_turn. Never target upstream. Never open PRs from the orchestrator.",
3604
3679
  "Typical flow (new app): Bash under repos dir (clone or gh repo create) \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 draft PR via worktree agent.",
3605
3680
  `Goal: ${opts.goal}`,
3606
- `Parent thread id (pass as parentThreadId when creating children): ${opts.parentId}`,
3607
3681
  "Registered workspaces:",
3608
3682
  formatWorkspaceInventory(opts.workspaces)
3609
3683
  ].join("\n");
@@ -3629,7 +3703,7 @@ var init_coordinator_prompt = __esm({
3629
3703
  "Workspaces:",
3630
3704
  "- add_workspace / remove_workspace \u2014 register or unregister a git repo",
3631
3705
  "Worktree threads (chats):",
3632
- "- create_thread \u2014 create a worktree + chat from branch | pr | ticket; pass repoPath + parentThreadId; omit agent and model to use Sideboard Account defaults (Settings \u2192 Default agent, model & effort)",
3706
+ "- create_thread \u2014 create a worktree + chat from branch | pr | ticket; pass repoPath + parentThreadId; omit agent and model to use Sideboard Account defaults (Settings \u2192 Default agent, model & effort). If you are Codex, do not set agent=codex (nested Codex deadlocks)",
3633
3707
  "- fork_worktree \u2014 fork a worktree chat into a NEW git worktree + chat (transcript attached); optional agent; leave model unset (Auto) unless you have a reason. Not for orchestration chats.",
3634
3708
  "- fork_chat \u2014 fork a worktree chat (same worktree tab) OR a Global orchestration chat (new orchestration tab); optional agent; leave model unset (Auto) unless you have a reason. Remote coordinators: use this to continue another orchestration chat on a different agent after session limits.",
3635
3709
  "- send_to_thread \u2014 queue a prompt (start/continue a chat turn); pass force_stop: true to interrupt mid-turn / clear stale queued prompts before replacing with a new request",
@@ -3641,7 +3715,7 @@ var init_coordinator_prompt = __esm({
3641
3715
  "- list_run_scripts / run_dev_script / stop_dev_script \u2014 start/stop named run scripts",
3642
3716
  "Inspect / review / PRs:",
3643
3717
  "- get_diff \u2014 compact diff summary",
3644
- '- request_review \u2014 open a Review chat tab on a worktree thread (attaches .sideboard/review.md when present, else local guidelines; sends "Review."); then wait_for_turn / get_turn_result on the returned id',
3718
+ '- request_review \u2014 open a Review chat tab on a worktree thread (attaches .sideboard/review.md when present, else local guidelines; sends "Review changes in this workspace."); then wait_for_turn / get_turn_result on the returned id',
3645
3719
  "- Ask the worktree agent via send_to_thread to open a draft PR with `gh pr create --draft -R <origin-owner/name>` (workspace `github:` slug / that worktree's origin \u2014 never upstream). Do not open PRs from the orchestrator yourself.",
3646
3720
  "Human-only (do not attempt): merge, ready-for-review land, purge_thread.",
3647
3721
  "Thread links in replies: when mentioning a chat/thread for the user, include a markdown link `[Title](sideboard://thread/<id>)` using the full id (or the link field from create_thread / list_threads). Sideboard renders these as clickable opens.",
@@ -4965,7 +5039,16 @@ async function resolveSideboardMcpServer() {
4965
5039
  async function buildInjectedMcpServers(opts) {
4966
5040
  const servers = [];
4967
5041
  if (opts.includeSideboard) {
4968
- servers.push(await resolveSideboardMcpServer());
5042
+ const sideboard = await resolveSideboardMcpServer();
5043
+ sideboard.env = {
5044
+ ...sideboard.env ?? {},
5045
+ SIDEBOARD_APP_DATA: appDataDir()
5046
+ };
5047
+ const orchId = opts.orchestratorThreadId?.trim();
5048
+ if (orchId) {
5049
+ sideboard.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID = orchId;
5050
+ }
5051
+ servers.push(sideboard);
4969
5052
  }
4970
5053
  if (opts.includeBrightsy && isBrightsyConnected()) {
4971
5054
  const cmd = await resolveBrightsyMcpCommand();
@@ -5009,6 +5092,8 @@ function toCodexMcpConfigArgs(servers) {
5009
5092
  }
5010
5093
  }
5011
5094
  args.push("-c", `${prefix}.default_tools_approval_mode=${JSON.stringify("approve")}`);
5095
+ args.push("-c", `${prefix}.tool_timeout_sec=300`);
5096
+ args.push("-c", `${prefix}.startup_timeout_sec=30`);
5012
5097
  }
5013
5098
  return args;
5014
5099
  }
@@ -5054,6 +5139,7 @@ var init_injected_mcp = __esm({
5054
5139
  init_run();
5055
5140
  init_config();
5056
5141
  init_connected_teams();
5142
+ init_paths();
5057
5143
  init_node_launch();
5058
5144
  import_meta = {};
5059
5145
  SIDEBOARD_MCP_ALLOWED_TOOLS = [
@@ -5077,6 +5163,16 @@ var init_injected_mcp = __esm({
5077
5163
 
5078
5164
  // src/agents/types.ts
5079
5165
  function permissionMode(thread) {
5166
+ if (thread.sourceType === "orchestration") {
5167
+ return {
5168
+ claude: "acceptEdits",
5169
+ opencodePermission: JSON.stringify({
5170
+ edit: "allow",
5171
+ bash: { "*": "allow", "rm -rf *": "deny" }
5172
+ }),
5173
+ codexSandbox: "danger-full-access"
5174
+ };
5175
+ }
5080
5176
  if (thread.planMode) {
5081
5177
  return {
5082
5178
  claude: "plan",
@@ -5272,18 +5368,17 @@ var init_claude = __esm({
5272
5368
  };
5273
5369
  }
5274
5370
  }
5275
- const auth = await run(claude, ["auth", "status"], { reject: false });
5371
+ const auth = await run(claude, ["auth", "status"], {
5372
+ reject: false,
5373
+ timeoutMs: 5e3
5374
+ });
5276
5375
  const authenticated = auth.exitCode === 0;
5277
- const servers = await loadMcpServers();
5278
- const linearMcp = servers.some(
5279
- (s) => s.connected && /linear/i.test(s.name)
5280
- );
5281
5376
  return {
5282
5377
  agent: "claude",
5283
5378
  installed: true,
5284
5379
  authenticated,
5285
- linearMcp,
5286
- warnings: mcpAuthWarnings(servers),
5380
+ linearMcp: false,
5381
+ warnings: [],
5287
5382
  reason: authenticated ? void 0 : "claude auth status failed \u2014 run `claude auth login`"
5288
5383
  };
5289
5384
  },
@@ -5304,7 +5399,8 @@ var init_claude = __esm({
5304
5399
  const isOrchestrator = isOrchestratorThread2(thread);
5305
5400
  const injectedServers = await buildInjectedMcpServers({
5306
5401
  includeSideboard: true,
5307
- includeBrightsy: isBrightsyConnected()
5402
+ includeBrightsy: isBrightsyConnected(),
5403
+ orchestratorThreadId: isOrchestrator ? thread.id : null
5308
5404
  });
5309
5405
  const injectedBrightsyNames = injectedServers.filter((s) => s.name === "brightsy" || s.name.startsWith("brightsy_")).map((s) => s.name);
5310
5406
  const chromeOn = claudeChromeEnabled();
@@ -5550,6 +5646,15 @@ function asRecord(value) {
5550
5646
  }
5551
5647
  return void 0;
5552
5648
  }
5649
+ function codexLooksAuthenticated() {
5650
+ const authPath = (0, import_node_path12.join)((0, import_node_os7.homedir)(), ".codex", "auth.json");
5651
+ if (!(0, import_node_fs12.existsSync)(authPath)) return false;
5652
+ try {
5653
+ return (0, import_node_fs12.statSync)(authPath).size > 2;
5654
+ } catch {
5655
+ return false;
5656
+ }
5657
+ }
5553
5658
  var import_node_fs12, import_node_os7, import_node_path12, CODEX_PROMPT_ARG_MAX, FALLBACK_CODEX_MODELS, cachedCodexModels, CODEX_MODEL_CACHE_MS, codexAdapter;
5554
5659
  var init_codex = __esm({
5555
5660
  "src/agents/codex.ts"() {
@@ -5575,7 +5680,7 @@ var init_codex = __esm({
5575
5680
  codexAdapter = {
5576
5681
  kind: "codex",
5577
5682
  async detect() {
5578
- const which = await run("which", ["codex"], { reject: false });
5683
+ const which = await run("which", ["codex"], { reject: false, timeoutMs: 3e3 });
5579
5684
  if (which.exitCode !== 0) {
5580
5685
  return {
5581
5686
  agent: "codex",
@@ -5586,10 +5691,8 @@ var init_codex = __esm({
5586
5691
  reason: "codex CLI not found on PATH"
5587
5692
  };
5588
5693
  }
5589
- const auth = await run("codex", ["login", "status"], { reject: false });
5590
- const authenticated = auth.exitCode === 0;
5591
- const mcp = await run("codex", ["mcp", "list", "--json"], { reject: false });
5592
- const linearMcp = /linear/i.test(mcp.stdout + mcp.stderr);
5694
+ const authenticated = codexLooksAuthenticated();
5695
+ const linearMcp = false;
5593
5696
  const warnings = [];
5594
5697
  if (!codexConfigHasNetworkAccess()) {
5595
5698
  warnings.push(
@@ -5602,7 +5705,7 @@ var init_codex = __esm({
5602
5705
  authenticated,
5603
5706
  linearMcp,
5604
5707
  warnings,
5605
- reason: authenticated ? void 0 : "codex login status failed \u2014 run `codex login`"
5708
+ reason: authenticated ? void 0 : "Codex auth not found \u2014 run `codex login` (expected ~/.codex/auth.json)"
5606
5709
  };
5607
5710
  },
5608
5711
  async buildTurn(thread, input) {
@@ -5620,23 +5723,26 @@ var init_codex = __esm({
5620
5723
  const model = thread.model?.trim();
5621
5724
  const injected = await buildInjectedMcpServers({
5622
5725
  includeSideboard: true,
5623
- includeBrightsy: isBrightsyConnected()
5726
+ includeBrightsy: isBrightsyConnected(),
5727
+ orchestratorThreadId: thread.sourceType === "orchestration" ? thread.id : null
5624
5728
  });
5625
5729
  const mcpOverrides = toCodexMcpConfigArgs(injected);
5626
- const args = [
5627
- "exec",
5628
- ...sessionId ? ["resume", sessionId] : [],
5629
- promptArg,
5730
+ const execOpts = [
5731
+ // Global orchestration cwd is not a git repo; without this Codex ≥0.147
5732
+ // refuses to start ("Not inside a trusted directory").
5733
+ "--skip-git-repo-check",
5630
5734
  "--cd",
5631
5735
  thread.worktreePath,
5632
5736
  "--json",
5633
5737
  "--sandbox",
5634
5738
  mode.codexSandbox,
5635
- "--ask-for-approval",
5636
- "never",
5739
+ // `codex exec` rejects `--ask-for-approval` (global-only on newer CLIs).
5740
+ "-c",
5741
+ 'approval_policy="never"',
5637
5742
  ...model ? ["--model", model] : [],
5638
5743
  ...mcpOverrides
5639
5744
  ];
5745
+ const args = sessionId ? ["exec", ...execOpts, "resume", sessionId, promptArg] : ["exec", ...execOpts, promptArg];
5640
5746
  return {
5641
5747
  file: "codex",
5642
5748
  args,
@@ -5738,7 +5844,7 @@ var init_codex = __esm({
5738
5844
  if (sessionId) {
5739
5845
  return {
5740
5846
  file: "codex",
5741
- args: ["exec", "resume", sessionId, "--cd", thread.worktreePath],
5847
+ args: ["exec", "--cd", thread.worktreePath, "resume", sessionId],
5742
5848
  cwd: thread.worktreePath
5743
5849
  };
5744
5850
  }
@@ -5754,12 +5860,12 @@ var init_codex = __esm({
5754
5860
  "codex",
5755
5861
  [
5756
5862
  "exec",
5757
- prompt,
5758
5863
  "--json",
5759
5864
  "--sandbox",
5760
5865
  "read-only",
5761
- "--ask-for-approval",
5762
- "never"
5866
+ "-c",
5867
+ 'approval_policy="never"',
5868
+ prompt
5763
5869
  ],
5764
5870
  { reject: false }
5765
5871
  );
@@ -6056,26 +6162,13 @@ var init_cursor = __esm({
6056
6162
  reason: "CURSOR_API_KEY not set \u2014 add it in Settings \u2192 Agents \u2192 Cursor, or Settings \u2192 Environment"
6057
6163
  };
6058
6164
  }
6059
- try {
6060
- await import_sdk.Cursor.models.list({ apiKey });
6061
- return {
6062
- agent: "cursor",
6063
- installed: true,
6064
- authenticated: true,
6065
- linearMcp: false,
6066
- warnings: []
6067
- };
6068
- } catch (err) {
6069
- const message = err instanceof Error ? err.message : String(err);
6070
- return {
6071
- agent: "cursor",
6072
- installed: true,
6073
- authenticated: false,
6074
- linearMcp: false,
6075
- warnings: [],
6076
- reason: `Cursor API auth failed: ${message}`
6077
- };
6078
- }
6165
+ return {
6166
+ agent: "cursor",
6167
+ installed: true,
6168
+ authenticated: true,
6169
+ linearMcp: false,
6170
+ warnings: []
6171
+ };
6079
6172
  },
6080
6173
  async buildTurn(thread, input) {
6081
6174
  const prompt = flattenTurnInput(input);
@@ -6083,7 +6176,8 @@ var init_cursor = __esm({
6083
6176
  const apiKey = resolveCursorApiKey() || void 0;
6084
6177
  const injected = await buildInjectedMcpServers({
6085
6178
  includeSideboard: true,
6086
- includeBrightsy: isBrightsyConnected()
6179
+ includeBrightsy: isBrightsyConnected(),
6180
+ orchestratorThreadId: thread.sourceType === "orchestration" ? thread.id : null
6087
6181
  });
6088
6182
  const mcpServers = toCursorMcpServers(injected);
6089
6183
  const req = {
@@ -6252,7 +6346,8 @@ var init_opencode = __esm({
6252
6346
  }
6253
6347
  const injected = await buildInjectedMcpServers({
6254
6348
  includeSideboard: true,
6255
- includeBrightsy: isBrightsyConnected()
6349
+ includeBrightsy: isBrightsyConnected(),
6350
+ orchestratorThreadId: thread.sourceType === "orchestration" ? thread.id : null
6256
6351
  });
6257
6352
  const mcpContent = injected.length > 0 ? toOpencodeMcpConfigContent(injected) : null;
6258
6353
  return {
@@ -7886,7 +7981,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
7886
7981
  const { isGlobalThread: isGlobalThread2 } = await Promise.resolve().then(() => (init_global_workspace(), global_workspace_exports));
7887
7982
  if (isGlobalThread2(thread)) {
7888
7983
  const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await Promise.resolve().then(() => (init_coordinator_prompt(), coordinator_prompt_exports));
7889
- ensureGlobalCoordinatorCwd2();
7984
+ ensureGlobalCoordinatorCwd2(
7985
+ isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
7986
+ );
7890
7987
  }
7891
7988
  if (isOrchestratorThread(thread)) {
7892
7989
  assertOrchestratorCapableAgent(thread.agent);
@@ -8180,9 +8277,21 @@ async function detectAgents() {
8180
8277
  ensureAgentPath();
8181
8278
  return Promise.all(allAdapters().map((a) => a.detect()));
8182
8279
  }
8280
+ var REQUIRE_AGENT_TIMEOUT_MS = 8e3;
8183
8281
  async function requireAgent(agent, opts) {
8184
- const status = (await detectAgents()).find((s) => s.agent === agent);
8185
- if (!status) throw new Error(`Unknown agent: ${agent}`);
8282
+ ensureAgentPath();
8283
+ const status = await Promise.race([
8284
+ getAdapter(agent).detect(),
8285
+ new Promise((_, reject) => {
8286
+ setTimeout(() => {
8287
+ reject(
8288
+ new Error(
8289
+ `${agent} detect timed out after ${REQUIRE_AGENT_TIMEOUT_MS / 1e3}s`
8290
+ )
8291
+ );
8292
+ }, REQUIRE_AGENT_TIMEOUT_MS);
8293
+ })
8294
+ ]);
8186
8295
  if (!status.installed || !status.authenticated) {
8187
8296
  throw new Error(status.reason ?? `${agent} is not available`);
8188
8297
  }
@@ -11290,7 +11399,7 @@ var REPO_REVIEW_NAME = "review.md";
11290
11399
  var REVIEW_REQUEST_PATH = `${ATTACHMENTS_DIR}/Review request.md`;
11291
11400
  var LEGACY_REVIEW_REQUEST_PATH = `${LEGACY_ATTACHMENTS_DIR}/Review request.md`;
11292
11401
  var REVIEW_REQUEST_NAME = "Review request.md";
11293
- var REVIEW_REQUEST_PREFILL = "Review.";
11402
+ var REVIEW_REQUEST_PREFILL = "Review changes in this workspace.";
11294
11403
  var LEGACY_REVIEW_TEMPLATE_MARKERS = [
11295
11404
  "You are acting as a reviewer for a proposed code change made by another engineer.",
11296
11405
  "HOW MANY FINDINGS TO RETURN:"
@@ -12121,7 +12230,9 @@ var Orchestrator = class {
12121
12230
  }
12122
12231
  let coordinatorDirective = null;
12123
12232
  if (isOrchestration) {
12124
- if (isGlobalThread(fresh)) ensureGlobalCoordinatorCwd();
12233
+ if (isGlobalThread(fresh)) {
12234
+ ensureGlobalCoordinatorCwd({ orchestratorThreadId: fresh.id });
12235
+ }
12125
12236
  if (!fresh.sessionId) {
12126
12237
  const inventory = await enrichWorkspacesWithGithub(this.listWorkspaces());
12127
12238
  coordinatorDirective = coordinatorSystemPrompt({
@@ -13250,6 +13361,24 @@ function mcpArchiveBlockedReason(thread) {
13250
13361
 
13251
13362
  // src/mcp/server.ts
13252
13363
  var MAX_ORCH_THREADS = 5;
13364
+ var CREATE_THREAD_TIMEOUT_MS = 9e4;
13365
+ function withTimeout(promise, ms, label) {
13366
+ return new Promise((resolve, reject) => {
13367
+ const timer = setTimeout(() => {
13368
+ reject(new Error(`${label} timed out after ${Math.round(ms / 1e3)}s`));
13369
+ }, ms);
13370
+ promise.then(
13371
+ (value) => {
13372
+ clearTimeout(timer);
13373
+ resolve(value);
13374
+ },
13375
+ (err) => {
13376
+ clearTimeout(timer);
13377
+ reject(err);
13378
+ }
13379
+ );
13380
+ });
13381
+ }
13253
13382
  async function startMcpServer() {
13254
13383
  const orch = getOrchestrator();
13255
13384
  try {
@@ -13257,7 +13386,14 @@ async function startMcpServer() {
13257
13386
  orch.setMaxConcurrent(maxConcurrentAgents2());
13258
13387
  } catch {
13259
13388
  }
13260
- await orch.reconcile(void 0, { reclaimStaleTurns: false, drainQueues: false });
13389
+ try {
13390
+ await orch.reconcile(void 0, { reclaimStaleTurns: false, drainQueues: false });
13391
+ } catch (err) {
13392
+ console.error(
13393
+ "[sideboard-mcp] reconcile on boot failed (continuing):",
13394
+ err instanceof Error ? err.message : err
13395
+ );
13396
+ }
13261
13397
  const server = new import_mcp.McpServer({
13262
13398
  name: "sideboard",
13263
13399
  version: "0.1.0"
@@ -13462,7 +13598,7 @@ async function startMcpServer() {
13462
13598
  const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
13463
13599
  server.tool(
13464
13600
  "create_thread",
13465
- `Create a new worktree thread (chat) from branch, pr, or ticket. Pass repoPath from list_workspaces and parentThreadId when spawning from an orchestrator. Prefer omitting agent/model so Sideboard applies ${accountDefaultsHint}. Then use send_to_thread to chat.`,
13601
+ `Create a new worktree thread (chat) from branch, pr, or ticket. Pass repoPath from list_workspaces. 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}. Then use send_to_thread to chat.`,
13466
13602
  {
13467
13603
  sourceType: import_zod.z.enum(["branch", "pr", "ticket"]),
13468
13604
  sourceRef: import_zod.z.string(),
@@ -13472,11 +13608,31 @@ async function startMcpServer() {
13472
13608
  ),
13473
13609
  repoPath: import_zod.z.string(),
13474
13610
  title: import_zod.z.string().optional(),
13475
- parentThreadId: import_zod.z.string().optional()
13611
+ parentThreadId: import_zod.z.string().optional().describe(
13612
+ "Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
13613
+ )
13476
13614
  },
13477
13615
  async (args) => {
13478
- if (args.parentThreadId) {
13479
- const children = orch.getThreads(true).filter((t) => t.parentThreadId === args.parentThreadId);
13616
+ const envParentId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || "";
13617
+ let parentId = args.parentThreadId?.trim() || "";
13618
+ let parent = parentId ? orch.getThread(parentId) : null;
13619
+ let parentCorrectedFrom;
13620
+ if (envParentId) {
13621
+ const envParent = orch.getThread(envParentId);
13622
+ if (envParent) {
13623
+ if (parentId && parentId !== envParentId) parentCorrectedFrom = parentId;
13624
+ else if (!parentId) parentCorrectedFrom = void 0;
13625
+ parentId = envParentId;
13626
+ parent = envParent;
13627
+ }
13628
+ }
13629
+ if (parentId && !parent) {
13630
+ parentCorrectedFrom = parentId;
13631
+ parentId = "";
13632
+ parent = null;
13633
+ }
13634
+ if (parentId) {
13635
+ const children = orch.getThreads(true).filter((t) => t.parentThreadId === parentId);
13480
13636
  if (children.length >= MAX_ORCH_THREADS) {
13481
13637
  return {
13482
13638
  content: [
@@ -13489,38 +13645,70 @@ async function startMcpServer() {
13489
13645
  };
13490
13646
  }
13491
13647
  }
13648
+ let agentArg = args.agent;
13649
+ let agentCoercedFrom;
13650
+ const resolvedProbe = resolveNewThreadOptions2({ agent: agentArg }).agent;
13651
+ if (resolvedProbe === "codex") {
13652
+ agentCoercedFrom = agentArg ?? "codex";
13653
+ const accountAgent = resolveNewThreadOptions2({}).agent;
13654
+ agentArg = accountAgent !== "codex" ? accountAgent : "cursor";
13655
+ }
13492
13656
  const opts = resolveNewThreadOptions2({
13493
- agent: args.agent,
13657
+ agent: agentArg,
13494
13658
  model: args.model
13495
13659
  });
13496
- const thread = await orch.createThread({
13497
- sourceType: args.sourceType,
13498
- sourceRef: args.sourceRef,
13499
- agent: opts.agent,
13500
- model: opts.model,
13501
- effort: opts.effort,
13502
- fast: opts.fast,
13503
- repoPath: args.repoPath,
13504
- title: args.title,
13505
- parentThreadId: args.parentThreadId ?? null
13506
- });
13507
- return {
13508
- content: [
13509
- {
13510
- type: "text",
13511
- text: JSON.stringify({
13512
- id: thread.id,
13513
- title: thread.title,
13514
- branchName: thread.branchName,
13515
- worktreePath: thread.worktreePath,
13516
- agent: thread.agent,
13517
- model: thread.model,
13518
- status: thread.status,
13519
- link: `sideboard://thread/${thread.id}`
13520
- })
13521
- }
13522
- ]
13523
- };
13660
+ try {
13661
+ const thread = await withTimeout(
13662
+ orch.createThread({
13663
+ sourceType: args.sourceType,
13664
+ sourceRef: args.sourceRef,
13665
+ agent: opts.agent,
13666
+ model: opts.model,
13667
+ effort: opts.effort,
13668
+ fast: opts.fast,
13669
+ repoPath: args.repoPath,
13670
+ title: args.title,
13671
+ parentThreadId: parentId || null
13672
+ }),
13673
+ CREATE_THREAD_TIMEOUT_MS,
13674
+ "create_thread"
13675
+ );
13676
+ const parentNote = parentCorrectedFrom ? parentId ? `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; nested under ${parentId}` : `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; created without parent` : void 0;
13677
+ return {
13678
+ content: [
13679
+ {
13680
+ type: "text",
13681
+ text: JSON.stringify({
13682
+ id: thread.id,
13683
+ title: thread.title,
13684
+ branchName: thread.branchName,
13685
+ worktreePath: thread.worktreePath,
13686
+ agent: thread.agent,
13687
+ model: thread.model,
13688
+ status: thread.status,
13689
+ link: `sideboard://thread/${thread.id}`,
13690
+ parentThreadId: thread.parentThreadId,
13691
+ ...agentCoercedFrom ? {
13692
+ agentCoercedFrom,
13693
+ note: `Avoid nested Codex under a Codex orchestrator \u2014 used Account default agent=${thread.agent}`
13694
+ } : {},
13695
+ ...parentCorrectedFrom ? { parentCorrectedFrom, parentNote } : {}
13696
+ })
13697
+ }
13698
+ ]
13699
+ };
13700
+ } catch (err) {
13701
+ const message = err instanceof Error ? err.message : String(err);
13702
+ return {
13703
+ content: [
13704
+ {
13705
+ type: "text",
13706
+ text: `create_thread failed: ${message}`
13707
+ }
13708
+ ],
13709
+ isError: true
13710
+ };
13711
+ }
13524
13712
  }
13525
13713
  );
13526
13714
  server.tool(
@@ -13701,7 +13889,7 @@ async function startMcpServer() {
13701
13889
  );
13702
13890
  server.tool(
13703
13891
  "request_review",
13704
- 'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, attaches .sideboard/review.md when present (else local Review request.md / stock template), and sends "Review." Expect Approve / Approve with nits / Request changes / Needs more information. Pass a worktree thread ref \u2014 not the orchestrator. Then wait_for_turn / get_turn_result on the returned review tab id.',
13892
+ 'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, attaches .sideboard/review.md when present (else local Review request.md / 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 / get_turn_result on the returned review tab id.',
13705
13893
  { ref: import_zod.z.string().describe("Worktree thread id/ref to review") },
13706
13894
  async ({ ref }) => {
13707
13895
  try {