@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
@@ -1554,7 +1554,8 @@ async function run(file, args, opts) {
1554
1554
  const result = await (0, import_execa.execa)(file, args, {
1555
1555
  cwd: opts?.cwd,
1556
1556
  env: { ...process.env, ...opts?.env },
1557
- reject: opts?.reject ?? true
1557
+ reject: opts?.reject ?? true,
1558
+ ...opts?.timeoutMs != null ? { timeout: opts.timeoutMs } : {}
1558
1559
  });
1559
1560
  return {
1560
1561
  stdout: result.stdout ?? "",
@@ -1584,11 +1585,22 @@ async function git(args, cwd, opts) {
1584
1585
  return run("git", [...prefix, ...args], {
1585
1586
  cwd,
1586
1587
  reject: opts?.reject,
1587
- env: opts?.env
1588
+ timeoutMs: opts?.timeoutMs,
1589
+ // Never block forever on a credential/SSH prompt inside MCP / Electron.
1590
+ env: {
1591
+ GIT_TERMINAL_PROMPT: "0",
1592
+ GIT_ASKPASS: process.env.GIT_ASKPASS || "echo",
1593
+ GIT_SSH_COMMAND: process.env.GIT_SSH_COMMAND || "ssh -o BatchMode=yes -o ConnectTimeout=15",
1594
+ ...opts?.env
1595
+ }
1588
1596
  });
1589
1597
  }
1590
1598
  async function gh(args, cwd, opts) {
1591
- return run("gh", args, { cwd, reject: opts?.reject });
1599
+ return run("gh", args, {
1600
+ cwd,
1601
+ reject: opts?.reject,
1602
+ timeoutMs: opts?.timeoutMs
1603
+ });
1592
1604
  }
1593
1605
  async function resolveGhAuthToken(cwd) {
1594
1606
  const result = await gh(["auth", "token"], cwd, { reject: false });
@@ -2029,12 +2041,16 @@ async function ensureGhPreferOrigin(cwd) {
2029
2041
  const hasAlt = Boolean(await slugFromGitRemote(cwd, "upstream")) || Boolean(await slugFromGitRemote(cwd, "github"));
2030
2042
  if (!hasAlt) return;
2031
2043
  const view = await gh(["repo", "set-default", "--view"], cwd, {
2032
- reject: false
2044
+ reject: false,
2045
+ timeoutMs: 15e3
2033
2046
  });
2034
2047
  const current = `${view.stdout}
2035
2048
  ${view.stderr}`;
2036
2049
  if (view.exitCode === 0 && current.includes(originSlug)) return;
2037
- await gh(["repo", "set-default", "origin"], cwd, { reject: false });
2050
+ await gh(["repo", "set-default", "origin"], cwd, {
2051
+ reject: false,
2052
+ timeoutMs: 15e3
2053
+ });
2038
2054
  }
2039
2055
  async function originGhRepoEnv(cwd) {
2040
2056
  await ensureGhPreferOrigin(cwd);
@@ -2580,20 +2596,42 @@ async function createThreadWorktree(opts) {
2580
2596
  throw new Error(`Worktree already exists at ${worktreePath}`);
2581
2597
  }
2582
2598
  await ensureGhPreferOrigin(opts.repoPath);
2583
- await git(["fetch", "origin", "--prune"], opts.repoPath, { reject: false });
2584
- if (!isLocalPrFetchBranch(opts.sourceRef) && !opts.sourceRef.startsWith("origin/") && !opts.sourceRef.startsWith("refs/")) {
2585
- await git(["fetch", "origin", opts.sourceRef], opts.repoPath, {
2586
- reject: false
2599
+ let startPoint = null;
2600
+ try {
2601
+ startPoint = await resolveWorktreeStartPoint(opts.repoPath, opts.sourceRef);
2602
+ } catch {
2603
+ startPoint = null;
2604
+ }
2605
+ if (!isLocalPrFetchBranch(opts.sourceRef)) {
2606
+ const fetchTimeoutMs = startPoint ? 12e3 : 45e3;
2607
+ await git(["fetch", "origin", "--prune"], opts.repoPath, {
2608
+ reject: false,
2609
+ timeoutMs: fetchTimeoutMs
2587
2610
  });
2611
+ if (!opts.sourceRef.startsWith("origin/") && !opts.sourceRef.startsWith("refs/")) {
2612
+ await git(["fetch", "origin", opts.sourceRef], opts.repoPath, {
2613
+ reject: false,
2614
+ timeoutMs: fetchTimeoutMs
2615
+ });
2616
+ }
2617
+ try {
2618
+ startPoint = await resolveWorktreeStartPoint(
2619
+ opts.repoPath,
2620
+ opts.sourceRef
2621
+ );
2622
+ } catch (err) {
2623
+ if (!startPoint) throw err;
2624
+ }
2625
+ }
2626
+ if (!startPoint) {
2627
+ throw new Error(
2628
+ `Invalid git reference: ${opts.sourceRef} (and fetch did not resolve it)`
2629
+ );
2588
2630
  }
2589
- const startPoint = await resolveWorktreeStartPoint(
2590
- opts.repoPath,
2591
- opts.sourceRef
2592
- );
2593
2631
  const add = await git(
2594
2632
  ["worktree", "add", "-b", branchName, worktreePath, startPoint],
2595
2633
  opts.repoPath,
2596
- { reject: false }
2634
+ { reject: false, timeoutMs: 6e4 }
2597
2635
  );
2598
2636
  if (add.exitCode !== 0) {
2599
2637
  const err = `${add.stderr}
@@ -2604,7 +2642,7 @@ ${add.stdout}`;
2604
2642
  const retry = await git(
2605
2643
  ["worktree", "add", "-b", alt, worktreePath, startPoint],
2606
2644
  opts.repoPath,
2607
- { reject: false }
2645
+ { reject: false, timeoutMs: 6e4 }
2608
2646
  );
2609
2647
  if (retry.exitCode === 0) {
2610
2648
  branchName = alt;
@@ -3270,9 +3308,14 @@ function resolveThreadDefaults(settings = loadAppSettings()) {
3270
3308
  function resolveNewThreadOptions(overrides = {}, settings = loadAppSettings()) {
3271
3309
  const defaults = resolveThreadDefaults(settings);
3272
3310
  const effort = overrides.effort === void 0 || overrides.effort === null ? defaults.effort : normalizeThinkingEffort(overrides.effort) ?? defaults.effort;
3311
+ const agent = overrides.agent ?? defaults.agent;
3312
+ let model = overrides.model === void 0 ? defaults.model : overrides.model?.trim() || null;
3313
+ if (model && agent !== "cursor" && /^(default|auto)$/i.test(model.trim())) {
3314
+ model = null;
3315
+ }
3273
3316
  return {
3274
- agent: overrides.agent ?? defaults.agent,
3275
- model: overrides.model === void 0 ? defaults.model : overrides.model?.trim() || null,
3317
+ agent,
3318
+ model,
3276
3319
  effort,
3277
3320
  fast: overrides.fast === void 0 || overrides.fast === null ? defaults.fast : Boolean(overrides.fast)
3278
3321
  };
@@ -3561,7 +3604,9 @@ function coordinatorTurnReminder(opts) {
3561
3604
  "- You oversee Sideboard worktree agents. You are not yourself checked out in a project worktree.",
3562
3605
  "- This cwd is a synthetic empty home (not a git repo). Emptiness here is expected \u2014 it is not a problem to fix.",
3563
3606
  "- Registered workspaces / child threads are the fleet you manage via Sideboard MCP.",
3564
- `- Parent thread id (pass as parentThreadId when creating children): ${opts.parentId}`,
3607
+ `- YOUR orchestration thread id is ${opts.parentId}`,
3608
+ `- Always pass parentThreadId="${opts.parentId}" on create_thread (never invent or reuse another uuid).`,
3609
+ `- Or omit parentThreadId \u2014 Sideboard MCP already binds children to ${opts.parentId}.`,
3565
3610
  goal ? `- Goal / title: ${goal}` : null,
3566
3611
  accountDefaultsPlaybookLine(),
3567
3612
  `- 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.`,
@@ -3570,10 +3615,33 @@ function coordinatorTurnReminder(opts) {
3570
3615
  "- When naming threads for the user, link them as `[Title](sideboard://thread/<id>)`."
3571
3616
  ].filter(Boolean).join("\n");
3572
3617
  }
3573
- function ensureGlobalCoordinatorCwd() {
3618
+ function ensureGlobalCoordinatorCwd(opts) {
3574
3619
  const dir = globalAgentCwd();
3575
- (0, import_node_fs6.mkdirSync)(dir, { recursive: true });
3620
+ try {
3621
+ (0, import_node_fs6.mkdirSync)(dir, { recursive: true });
3622
+ } catch {
3623
+ return dir;
3624
+ }
3576
3625
  const reposDir = sideboardReposDir();
3626
+ let orchId = opts?.orchestratorThreadId?.trim() || "";
3627
+ if (!orchId) {
3628
+ try {
3629
+ const existing = (0, import_node_fs6.readFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), "utf8");
3630
+ const m = existing.match(
3631
+ /YOUR orchestration thread id is `([0-9a-f-]{36})`/i
3632
+ );
3633
+ if (m?.[1]) orchId = m[1];
3634
+ } catch {
3635
+ }
3636
+ }
3637
+ const parentBlock = orchId ? [
3638
+ "",
3639
+ `## This chat's id`,
3640
+ "",
3641
+ `YOUR orchestration thread id is \`${orchId}\`.`,
3642
+ `On every create_thread, pass parentThreadId="${orchId}" \u2014 or omit parentThreadId (Sideboard binds it automatically).`,
3643
+ "Never invent a uuid and never reuse an id from an earlier conversation."
3644
+ ] : [];
3577
3645
  const body = [
3578
3646
  "# Sideboard Orchestration",
3579
3647
  "",
@@ -3583,6 +3651,7 @@ function ensureGlobalCoordinatorCwd() {
3583
3651
  "Repos from `list_workspaces` and threads from `list_threads` are the fleet you orchestrate.",
3584
3652
  'For status questions ("what\'s going on?"), use `list_threads` / `list_workspaces` \u2014 never diagnose this synthetic home as a broken worktree.',
3585
3653
  "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.",
3654
+ ...parentBlock,
3586
3655
  "",
3587
3656
  COORDINATOR_TOOL_PLAYBOOK,
3588
3657
  "",
@@ -3590,16 +3659,21 @@ function ensureGlobalCoordinatorCwd() {
3590
3659
  "",
3591
3660
  coordinatorGreenfieldPlaybook(reposDir),
3592
3661
  "",
3593
- "When creating threads, pass `repoPath` from `list_workspaces` (or the path you just registered) and `parentThreadId` for children.",
3662
+ "When creating threads, pass `repoPath` from `list_workspaces` (or the path you just registered).",
3663
+ orchId ? `Pass parentThreadId="${orchId}" (or omit it). Never invent another parentThreadId.` : "Pass `parentThreadId` for children (this chat's id from the turn reminder).",
3594
3664
  "Omit `agent` / `model` on `create_thread` unless you have a reason to override Account defaults.",
3665
+ "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.",
3595
3666
  "Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn.",
3596
3667
  "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.",
3597
3668
  "Always ask worktree agents to open draft PRs (`send_to_thread` + `gh pr create --draft -R <origin>`); never open PRs from the orchestrator."
3598
3669
  ].join("\n");
3599
- (0, import_node_fs6.writeFileSync)((0, import_node_path7.join)(dir, "CLAUDE.md"), `${body}
3670
+ try {
3671
+ (0, import_node_fs6.writeFileSync)((0, import_node_path7.join)(dir, "CLAUDE.md"), `${body}
3600
3672
  `, "utf8");
3601
- (0, import_node_fs6.writeFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), `${body}
3673
+ (0, import_node_fs6.writeFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), `${body}
3602
3674
  `, "utf8");
3675
+ } catch {
3676
+ }
3603
3677
  return dir;
3604
3678
  }
3605
3679
  function coordinatorSystemPrompt(opts) {
@@ -3619,12 +3693,12 @@ function coordinatorSystemPrompt(opts) {
3619
3693
  COORDINATOR_TOOL_PLAYBOOK,
3620
3694
  accountDefaultsPlaybookLine(),
3621
3695
  coordinatorGreenfieldPlaybook(reposDir),
3622
- "When creating threads, pass the correct repoPath for the target workspace and parentThreadId for children.",
3696
+ "When creating threads, pass the correct repoPath for the target workspace.",
3697
+ `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.`,
3623
3698
  "Omit agent/model on create_thread unless you need to override Account defaults.",
3624
3699
  "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.",
3625
3700
  "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.",
3626
3701
  `Goal: ${opts.goal}`,
3627
- `Parent thread id (pass as parentThreadId when creating children): ${opts.parentId}`,
3628
3702
  "Registered workspaces:",
3629
3703
  formatWorkspaceInventory(opts.workspaces)
3630
3704
  ].join("\n");
@@ -3650,7 +3724,7 @@ var init_coordinator_prompt = __esm({
3650
3724
  "Workspaces:",
3651
3725
  "- add_workspace / remove_workspace \u2014 register or unregister a git repo",
3652
3726
  "Worktree threads (chats):",
3653
- "- 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)",
3727
+ "- 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)",
3654
3728
  "- 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.",
3655
3729
  "- 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.",
3656
3730
  "- 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",
@@ -3662,7 +3736,7 @@ var init_coordinator_prompt = __esm({
3662
3736
  "- list_run_scripts / run_dev_script / stop_dev_script \u2014 start/stop named run scripts",
3663
3737
  "Inspect / review / PRs:",
3664
3738
  "- get_diff \u2014 compact diff summary",
3665
- '- 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',
3739
+ '- 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',
3666
3740
  "- 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.",
3667
3741
  "Human-only (do not attempt): merge, ready-for-review land, purge_thread.",
3668
3742
  "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.",
@@ -4410,13 +4484,6 @@ function mcpAllowTools(servers) {
4410
4484
  }
4411
4485
  return out;
4412
4486
  }
4413
- function mcpAuthWarnings(servers) {
4414
- const needing = servers.filter((s) => s.needsAuth).map((s) => s.name);
4415
- if (needing.length === 0) return [];
4416
- return [
4417
- `MCP needs login: ${needing.join(", ")}. Run: claude mcp login "<name>"`
4418
- ];
4419
- }
4420
4487
  var init_claude_mcp = __esm({
4421
4488
  "src/agents/claude-mcp.ts"() {
4422
4489
  "use strict";
@@ -4558,7 +4625,16 @@ async function resolveSideboardMcpServer() {
4558
4625
  async function buildInjectedMcpServers(opts) {
4559
4626
  const servers = [];
4560
4627
  if (opts.includeSideboard) {
4561
- servers.push(await resolveSideboardMcpServer());
4628
+ const sideboard = await resolveSideboardMcpServer();
4629
+ sideboard.env = {
4630
+ ...sideboard.env ?? {},
4631
+ SIDEBOARD_APP_DATA: appDataDir()
4632
+ };
4633
+ const orchId = opts.orchestratorThreadId?.trim();
4634
+ if (orchId) {
4635
+ sideboard.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID = orchId;
4636
+ }
4637
+ servers.push(sideboard);
4562
4638
  }
4563
4639
  if (opts.includeBrightsy && isBrightsyConnected()) {
4564
4640
  const cmd = await resolveBrightsyMcpCommand();
@@ -4602,6 +4678,8 @@ function toCodexMcpConfigArgs(servers) {
4602
4678
  }
4603
4679
  }
4604
4680
  args.push("-c", `${prefix}.default_tools_approval_mode=${JSON.stringify("approve")}`);
4681
+ args.push("-c", `${prefix}.tool_timeout_sec=300`);
4682
+ args.push("-c", `${prefix}.startup_timeout_sec=30`);
4605
4683
  }
4606
4684
  return args;
4607
4685
  }
@@ -4644,6 +4722,7 @@ var init_injected_mcp = __esm({
4644
4722
  init_run();
4645
4723
  init_config();
4646
4724
  init_connected_teams();
4725
+ init_paths();
4647
4726
  init_node_launch();
4648
4727
  import_meta = {};
4649
4728
  SIDEBOARD_MCP_ALLOWED_TOOLS = [
@@ -4663,6 +4742,16 @@ var init_injected_mcp = __esm({
4663
4742
 
4664
4743
  // src/agents/types.ts
4665
4744
  function permissionMode(thread) {
4745
+ if (thread.sourceType === "orchestration") {
4746
+ return {
4747
+ claude: "acceptEdits",
4748
+ opencodePermission: JSON.stringify({
4749
+ edit: "allow",
4750
+ bash: { "*": "allow", "rm -rf *": "deny" }
4751
+ }),
4752
+ codexSandbox: "danger-full-access"
4753
+ };
4754
+ }
4666
4755
  if (thread.planMode) {
4667
4756
  return {
4668
4757
  claude: "plan",
@@ -4858,18 +4947,17 @@ var init_claude = __esm({
4858
4947
  };
4859
4948
  }
4860
4949
  }
4861
- const auth = await run(claude, ["auth", "status"], { reject: false });
4950
+ const auth = await run(claude, ["auth", "status"], {
4951
+ reject: false,
4952
+ timeoutMs: 5e3
4953
+ });
4862
4954
  const authenticated = auth.exitCode === 0;
4863
- const servers = await loadMcpServers();
4864
- const linearMcp = servers.some(
4865
- (s) => s.connected && /linear/i.test(s.name)
4866
- );
4867
4955
  return {
4868
4956
  agent: "claude",
4869
4957
  installed: true,
4870
4958
  authenticated,
4871
- linearMcp,
4872
- warnings: mcpAuthWarnings(servers),
4959
+ linearMcp: false,
4960
+ warnings: [],
4873
4961
  reason: authenticated ? void 0 : "claude auth status failed \u2014 run `claude auth login`"
4874
4962
  };
4875
4963
  },
@@ -4890,7 +4978,8 @@ var init_claude = __esm({
4890
4978
  const isOrchestrator = isOrchestratorThread2(thread);
4891
4979
  const injectedServers = await buildInjectedMcpServers({
4892
4980
  includeSideboard: true,
4893
- includeBrightsy: isBrightsyConnected()
4981
+ includeBrightsy: isBrightsyConnected(),
4982
+ orchestratorThreadId: isOrchestrator ? thread.id : null
4894
4983
  });
4895
4984
  const injectedBrightsyNames = injectedServers.filter((s) => s.name === "brightsy" || s.name.startsWith("brightsy_")).map((s) => s.name);
4896
4985
  const chromeOn = claudeChromeEnabled();
@@ -5136,6 +5225,15 @@ function asRecord(value) {
5136
5225
  }
5137
5226
  return void 0;
5138
5227
  }
5228
+ function codexLooksAuthenticated() {
5229
+ const authPath = (0, import_node_path11.join)((0, import_node_os7.homedir)(), ".codex", "auth.json");
5230
+ if (!(0, import_node_fs11.existsSync)(authPath)) return false;
5231
+ try {
5232
+ return (0, import_node_fs11.statSync)(authPath).size > 2;
5233
+ } catch {
5234
+ return false;
5235
+ }
5236
+ }
5139
5237
  var import_node_fs11, import_node_os7, import_node_path11, CODEX_PROMPT_ARG_MAX, FALLBACK_CODEX_MODELS, cachedCodexModels, CODEX_MODEL_CACHE_MS, codexAdapter;
5140
5238
  var init_codex = __esm({
5141
5239
  "src/agents/codex.ts"() {
@@ -5161,7 +5259,7 @@ var init_codex = __esm({
5161
5259
  codexAdapter = {
5162
5260
  kind: "codex",
5163
5261
  async detect() {
5164
- const which = await run("which", ["codex"], { reject: false });
5262
+ const which = await run("which", ["codex"], { reject: false, timeoutMs: 3e3 });
5165
5263
  if (which.exitCode !== 0) {
5166
5264
  return {
5167
5265
  agent: "codex",
@@ -5172,10 +5270,8 @@ var init_codex = __esm({
5172
5270
  reason: "codex CLI not found on PATH"
5173
5271
  };
5174
5272
  }
5175
- const auth = await run("codex", ["login", "status"], { reject: false });
5176
- const authenticated = auth.exitCode === 0;
5177
- const mcp = await run("codex", ["mcp", "list", "--json"], { reject: false });
5178
- const linearMcp = /linear/i.test(mcp.stdout + mcp.stderr);
5273
+ const authenticated = codexLooksAuthenticated();
5274
+ const linearMcp = false;
5179
5275
  const warnings = [];
5180
5276
  if (!codexConfigHasNetworkAccess()) {
5181
5277
  warnings.push(
@@ -5188,7 +5284,7 @@ var init_codex = __esm({
5188
5284
  authenticated,
5189
5285
  linearMcp,
5190
5286
  warnings,
5191
- reason: authenticated ? void 0 : "codex login status failed \u2014 run `codex login`"
5287
+ reason: authenticated ? void 0 : "Codex auth not found \u2014 run `codex login` (expected ~/.codex/auth.json)"
5192
5288
  };
5193
5289
  },
5194
5290
  async buildTurn(thread, input) {
@@ -5206,23 +5302,26 @@ var init_codex = __esm({
5206
5302
  const model = thread.model?.trim();
5207
5303
  const injected = await buildInjectedMcpServers({
5208
5304
  includeSideboard: true,
5209
- includeBrightsy: isBrightsyConnected()
5305
+ includeBrightsy: isBrightsyConnected(),
5306
+ orchestratorThreadId: thread.sourceType === "orchestration" ? thread.id : null
5210
5307
  });
5211
5308
  const mcpOverrides = toCodexMcpConfigArgs(injected);
5212
- const args = [
5213
- "exec",
5214
- ...sessionId ? ["resume", sessionId] : [],
5215
- promptArg,
5309
+ const execOpts = [
5310
+ // Global orchestration cwd is not a git repo; without this Codex ≥0.147
5311
+ // refuses to start ("Not inside a trusted directory").
5312
+ "--skip-git-repo-check",
5216
5313
  "--cd",
5217
5314
  thread.worktreePath,
5218
5315
  "--json",
5219
5316
  "--sandbox",
5220
5317
  mode.codexSandbox,
5221
- "--ask-for-approval",
5222
- "never",
5318
+ // `codex exec` rejects `--ask-for-approval` (global-only on newer CLIs).
5319
+ "-c",
5320
+ 'approval_policy="never"',
5223
5321
  ...model ? ["--model", model] : [],
5224
5322
  ...mcpOverrides
5225
5323
  ];
5324
+ const args = sessionId ? ["exec", ...execOpts, "resume", sessionId, promptArg] : ["exec", ...execOpts, promptArg];
5226
5325
  return {
5227
5326
  file: "codex",
5228
5327
  args,
@@ -5324,7 +5423,7 @@ var init_codex = __esm({
5324
5423
  if (sessionId) {
5325
5424
  return {
5326
5425
  file: "codex",
5327
- args: ["exec", "resume", sessionId, "--cd", thread.worktreePath],
5426
+ args: ["exec", "--cd", thread.worktreePath, "resume", sessionId],
5328
5427
  cwd: thread.worktreePath
5329
5428
  };
5330
5429
  }
@@ -5340,12 +5439,12 @@ var init_codex = __esm({
5340
5439
  "codex",
5341
5440
  [
5342
5441
  "exec",
5343
- prompt,
5344
5442
  "--json",
5345
5443
  "--sandbox",
5346
5444
  "read-only",
5347
- "--ask-for-approval",
5348
- "never"
5445
+ "-c",
5446
+ 'approval_policy="never"',
5447
+ prompt
5349
5448
  ],
5350
5449
  { reject: false }
5351
5450
  );
@@ -5642,26 +5741,13 @@ var init_cursor = __esm({
5642
5741
  reason: "CURSOR_API_KEY not set \u2014 add it in Settings \u2192 Agents \u2192 Cursor, or Settings \u2192 Environment"
5643
5742
  };
5644
5743
  }
5645
- try {
5646
- await import_sdk.Cursor.models.list({ apiKey });
5647
- return {
5648
- agent: "cursor",
5649
- installed: true,
5650
- authenticated: true,
5651
- linearMcp: false,
5652
- warnings: []
5653
- };
5654
- } catch (err) {
5655
- const message = err instanceof Error ? err.message : String(err);
5656
- return {
5657
- agent: "cursor",
5658
- installed: true,
5659
- authenticated: false,
5660
- linearMcp: false,
5661
- warnings: [],
5662
- reason: `Cursor API auth failed: ${message}`
5663
- };
5664
- }
5744
+ return {
5745
+ agent: "cursor",
5746
+ installed: true,
5747
+ authenticated: true,
5748
+ linearMcp: false,
5749
+ warnings: []
5750
+ };
5665
5751
  },
5666
5752
  async buildTurn(thread, input) {
5667
5753
  const prompt = flattenTurnInput(input);
@@ -5669,7 +5755,8 @@ var init_cursor = __esm({
5669
5755
  const apiKey = resolveCursorApiKey() || void 0;
5670
5756
  const injected = await buildInjectedMcpServers({
5671
5757
  includeSideboard: true,
5672
- includeBrightsy: isBrightsyConnected()
5758
+ includeBrightsy: isBrightsyConnected(),
5759
+ orchestratorThreadId: thread.sourceType === "orchestration" ? thread.id : null
5673
5760
  });
5674
5761
  const mcpServers = toCursorMcpServers(injected);
5675
5762
  const req = {
@@ -5838,7 +5925,8 @@ var init_opencode = __esm({
5838
5925
  }
5839
5926
  const injected = await buildInjectedMcpServers({
5840
5927
  includeSideboard: true,
5841
- includeBrightsy: isBrightsyConnected()
5928
+ includeBrightsy: isBrightsyConnected(),
5929
+ orchestratorThreadId: thread.sourceType === "orchestration" ? thread.id : null
5842
5930
  });
5843
5931
  const mcpContent = injected.length > 0 ? toOpencodeMcpConfigContent(injected) : null;
5844
5932
  return {
@@ -6993,7 +7081,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
6993
7081
  const { isGlobalThread: isGlobalThread2 } = await Promise.resolve().then(() => (init_global_workspace(), global_workspace_exports));
6994
7082
  if (isGlobalThread2(thread)) {
6995
7083
  const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await Promise.resolve().then(() => (init_coordinator_prompt(), coordinator_prompt_exports));
6996
- ensureGlobalCoordinatorCwd2();
7084
+ ensureGlobalCoordinatorCwd2(
7085
+ isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
7086
+ );
6997
7087
  }
6998
7088
  if (isOrchestratorThread(thread)) {
6999
7089
  assertOrchestratorCapableAgent(thread.agent);
@@ -7709,13 +7799,21 @@ var import_node_fs18 = require("fs");
7709
7799
 
7710
7800
  // src/detect/detect.ts
7711
7801
  init_agents();
7712
- async function detectAgents() {
7713
- ensureAgentPath();
7714
- return Promise.all(allAdapters().map((a) => a.detect()));
7715
- }
7802
+ var REQUIRE_AGENT_TIMEOUT_MS = 8e3;
7716
7803
  async function requireAgent(agent, opts) {
7717
- const status = (await detectAgents()).find((s) => s.agent === agent);
7718
- if (!status) throw new Error(`Unknown agent: ${agent}`);
7804
+ ensureAgentPath();
7805
+ const status = await Promise.race([
7806
+ getAdapter(agent).detect(),
7807
+ new Promise((_, reject) => {
7808
+ setTimeout(() => {
7809
+ reject(
7810
+ new Error(
7811
+ `${agent} detect timed out after ${REQUIRE_AGENT_TIMEOUT_MS / 1e3}s`
7812
+ )
7813
+ );
7814
+ }, REQUIRE_AGENT_TIMEOUT_MS);
7815
+ })
7816
+ ]);
7719
7817
  if (!status.installed || !status.authenticated) {
7720
7818
  throw new Error(status.reason ?? `${agent} is not available`);
7721
7819
  }
@@ -8333,7 +8431,7 @@ var REPO_REVIEW_NAME = "review.md";
8333
8431
  var REVIEW_REQUEST_PATH = `${ATTACHMENTS_DIR}/Review request.md`;
8334
8432
  var LEGACY_REVIEW_REQUEST_PATH = `${LEGACY_ATTACHMENTS_DIR}/Review request.md`;
8335
8433
  var REVIEW_REQUEST_NAME = "Review request.md";
8336
- var REVIEW_REQUEST_PREFILL = "Review.";
8434
+ var REVIEW_REQUEST_PREFILL = "Review changes in this workspace.";
8337
8435
  var LEGACY_REVIEW_TEMPLATE_MARKERS = [
8338
8436
  "You are acting as a reviewer for a proposed code change made by another engineer.",
8339
8437
  "HOW MANY FINDINGS TO RETURN:"
@@ -11029,7 +11127,9 @@ var Orchestrator = class {
11029
11127
  }
11030
11128
  let coordinatorDirective = null;
11031
11129
  if (isOrchestration) {
11032
- if (isGlobalThread(fresh)) ensureGlobalCoordinatorCwd();
11130
+ if (isGlobalThread(fresh)) {
11131
+ ensureGlobalCoordinatorCwd({ orchestratorThreadId: fresh.id });
11132
+ }
11033
11133
  if (!fresh.sessionId) {
11034
11134
  const inventory = await enrichWorkspacesWithGithub(this.listWorkspaces());
11035
11135
  coordinatorDirective = coordinatorSystemPrompt({
@@ -12111,6 +12211,24 @@ function mcpArchiveBlockedReason(thread) {
12111
12211
 
12112
12212
  // src/mcp/server.ts
12113
12213
  var MAX_ORCH_THREADS = 5;
12214
+ var CREATE_THREAD_TIMEOUT_MS = 9e4;
12215
+ function withTimeout(promise, ms, label) {
12216
+ return new Promise((resolve, reject) => {
12217
+ const timer = setTimeout(() => {
12218
+ reject(new Error(`${label} timed out after ${Math.round(ms / 1e3)}s`));
12219
+ }, ms);
12220
+ promise.then(
12221
+ (value) => {
12222
+ clearTimeout(timer);
12223
+ resolve(value);
12224
+ },
12225
+ (err) => {
12226
+ clearTimeout(timer);
12227
+ reject(err);
12228
+ }
12229
+ );
12230
+ });
12231
+ }
12114
12232
  async function startMcpServer() {
12115
12233
  const orch = getOrchestrator();
12116
12234
  try {
@@ -12118,7 +12236,14 @@ async function startMcpServer() {
12118
12236
  orch.setMaxConcurrent(maxConcurrentAgents2());
12119
12237
  } catch {
12120
12238
  }
12121
- await orch.reconcile(void 0, { reclaimStaleTurns: false, drainQueues: false });
12239
+ try {
12240
+ await orch.reconcile(void 0, { reclaimStaleTurns: false, drainQueues: false });
12241
+ } catch (err) {
12242
+ console.error(
12243
+ "[sideboard-mcp] reconcile on boot failed (continuing):",
12244
+ err instanceof Error ? err.message : err
12245
+ );
12246
+ }
12122
12247
  const server = new import_mcp.McpServer({
12123
12248
  name: "sideboard",
12124
12249
  version: "0.1.0"
@@ -12323,7 +12448,7 @@ async function startMcpServer() {
12323
12448
  const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
12324
12449
  server.tool(
12325
12450
  "create_thread",
12326
- `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.`,
12451
+ `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.`,
12327
12452
  {
12328
12453
  sourceType: import_zod.z.enum(["branch", "pr", "ticket"]),
12329
12454
  sourceRef: import_zod.z.string(),
@@ -12333,11 +12458,31 @@ async function startMcpServer() {
12333
12458
  ),
12334
12459
  repoPath: import_zod.z.string(),
12335
12460
  title: import_zod.z.string().optional(),
12336
- parentThreadId: import_zod.z.string().optional()
12461
+ parentThreadId: import_zod.z.string().optional().describe(
12462
+ "Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
12463
+ )
12337
12464
  },
12338
12465
  async (args) => {
12339
- if (args.parentThreadId) {
12340
- const children = orch.getThreads(true).filter((t) => t.parentThreadId === args.parentThreadId);
12466
+ const envParentId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || "";
12467
+ let parentId = args.parentThreadId?.trim() || "";
12468
+ let parent = parentId ? orch.getThread(parentId) : null;
12469
+ let parentCorrectedFrom;
12470
+ if (envParentId) {
12471
+ const envParent = orch.getThread(envParentId);
12472
+ if (envParent) {
12473
+ if (parentId && parentId !== envParentId) parentCorrectedFrom = parentId;
12474
+ else if (!parentId) parentCorrectedFrom = void 0;
12475
+ parentId = envParentId;
12476
+ parent = envParent;
12477
+ }
12478
+ }
12479
+ if (parentId && !parent) {
12480
+ parentCorrectedFrom = parentId;
12481
+ parentId = "";
12482
+ parent = null;
12483
+ }
12484
+ if (parentId) {
12485
+ const children = orch.getThreads(true).filter((t) => t.parentThreadId === parentId);
12341
12486
  if (children.length >= MAX_ORCH_THREADS) {
12342
12487
  return {
12343
12488
  content: [
@@ -12350,38 +12495,70 @@ async function startMcpServer() {
12350
12495
  };
12351
12496
  }
12352
12497
  }
12498
+ let agentArg = args.agent;
12499
+ let agentCoercedFrom;
12500
+ const resolvedProbe = resolveNewThreadOptions2({ agent: agentArg }).agent;
12501
+ if (resolvedProbe === "codex") {
12502
+ agentCoercedFrom = agentArg ?? "codex";
12503
+ const accountAgent = resolveNewThreadOptions2({}).agent;
12504
+ agentArg = accountAgent !== "codex" ? accountAgent : "cursor";
12505
+ }
12353
12506
  const opts = resolveNewThreadOptions2({
12354
- agent: args.agent,
12507
+ agent: agentArg,
12355
12508
  model: args.model
12356
12509
  });
12357
- const thread = await orch.createThread({
12358
- sourceType: args.sourceType,
12359
- sourceRef: args.sourceRef,
12360
- agent: opts.agent,
12361
- model: opts.model,
12362
- effort: opts.effort,
12363
- fast: opts.fast,
12364
- repoPath: args.repoPath,
12365
- title: args.title,
12366
- parentThreadId: args.parentThreadId ?? null
12367
- });
12368
- return {
12369
- content: [
12370
- {
12371
- type: "text",
12372
- text: JSON.stringify({
12373
- id: thread.id,
12374
- title: thread.title,
12375
- branchName: thread.branchName,
12376
- worktreePath: thread.worktreePath,
12377
- agent: thread.agent,
12378
- model: thread.model,
12379
- status: thread.status,
12380
- link: `sideboard://thread/${thread.id}`
12381
- })
12382
- }
12383
- ]
12384
- };
12510
+ try {
12511
+ const thread = await withTimeout(
12512
+ orch.createThread({
12513
+ sourceType: args.sourceType,
12514
+ sourceRef: args.sourceRef,
12515
+ agent: opts.agent,
12516
+ model: opts.model,
12517
+ effort: opts.effort,
12518
+ fast: opts.fast,
12519
+ repoPath: args.repoPath,
12520
+ title: args.title,
12521
+ parentThreadId: parentId || null
12522
+ }),
12523
+ CREATE_THREAD_TIMEOUT_MS,
12524
+ "create_thread"
12525
+ );
12526
+ const parentNote = parentCorrectedFrom ? parentId ? `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; nested under ${parentId}` : `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; created without parent` : void 0;
12527
+ return {
12528
+ content: [
12529
+ {
12530
+ type: "text",
12531
+ text: JSON.stringify({
12532
+ id: thread.id,
12533
+ title: thread.title,
12534
+ branchName: thread.branchName,
12535
+ worktreePath: thread.worktreePath,
12536
+ agent: thread.agent,
12537
+ model: thread.model,
12538
+ status: thread.status,
12539
+ link: `sideboard://thread/${thread.id}`,
12540
+ parentThreadId: thread.parentThreadId,
12541
+ ...agentCoercedFrom ? {
12542
+ agentCoercedFrom,
12543
+ note: `Avoid nested Codex under a Codex orchestrator \u2014 used Account default agent=${thread.agent}`
12544
+ } : {},
12545
+ ...parentCorrectedFrom ? { parentCorrectedFrom, parentNote } : {}
12546
+ })
12547
+ }
12548
+ ]
12549
+ };
12550
+ } catch (err) {
12551
+ const message = err instanceof Error ? err.message : String(err);
12552
+ return {
12553
+ content: [
12554
+ {
12555
+ type: "text",
12556
+ text: `create_thread failed: ${message}`
12557
+ }
12558
+ ],
12559
+ isError: true
12560
+ };
12561
+ }
12385
12562
  }
12386
12563
  );
12387
12564
  server.tool(
@@ -12562,7 +12739,7 @@ async function startMcpServer() {
12562
12739
  );
12563
12740
  server.tool(
12564
12741
  "request_review",
12565
- '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.',
12742
+ '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.',
12566
12743
  { ref: import_zod.z.string().describe("Worktree thread id/ref to review") },
12567
12744
  async ({ ref }) => {
12568
12745
  try {