@sideboard-ai/core 0.1.217 → 0.1.219

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 (59) hide show
  1. package/dist/{abletime-QGGYL2G4.js → abletime-BF253OZT.js} +5 -4
  2. package/dist/{abletime-GDZLZRMY.js → abletime-D6773P33.js} +5 -5
  3. package/dist/{abletime-oauth-F335TWKY.js → abletime-oauth-H4672PRH.js} +4 -3
  4. package/dist/{abletime-oauth-3X3BAPTF.js → abletime-oauth-KQPVVBKA.js} +4 -4
  5. package/dist/{abletime-rest-PLJVY3H3.js → abletime-rest-DAFPXZ7H.js} +4 -4
  6. package/dist/{abletime-rest-LAGTPAJL.js → abletime-rest-OPEVV3XC.js} +4 -3
  7. package/dist/agents/cursor-runner.js +5 -5
  8. package/dist/{agents-HETHRRZJ.js → agents-IFVGY77W.js} +9 -9
  9. package/dist/{agents-WBDDLRBY.js → agents-IK3U4Z2O.js} +8 -7
  10. package/dist/{app-settings-XCRNCEEE.js → app-settings-5RQMQKSB.js} +2 -1
  11. package/dist/{app-settings-2TN3WLZV.js → app-settings-V2BBCX2H.js} +2 -2
  12. package/dist/{chunk-MN6YUD4O.js → chunk-3NWUHXU3.js} +3 -3
  13. package/dist/{chunk-G4TANQC3.js → chunk-646I442S.js} +5 -5
  14. package/dist/{chunk-FNQNJOHY.js → chunk-ABOBTSO6.js} +4 -1
  15. package/dist/{chunk-YVE4PXCJ.js → chunk-CLI5DVU4.js} +2 -2
  16. package/dist/{chunk-C32SBNA3.js → chunk-EIICOB7P.js} +457 -294
  17. package/dist/{chunk-LS6TSOCE.js → chunk-HSYE5ID6.js} +5 -5
  18. package/dist/{chunk-UFHNSFAS.js → chunk-HVUVQXZ2.js} +5 -1
  19. package/dist/{chunk-7MTVRNP4.js → chunk-IAP5NXQ4.js} +1206 -1192
  20. package/dist/{chunk-SALM35KD.js → chunk-IILG7DPX.js} +5 -5
  21. package/dist/{chunk-DFTVZEZY.js → chunk-K642ORWF.js} +1085 -1057
  22. package/dist/{chunk-NI243N27.js → chunk-KTZESHYH.js} +15 -7
  23. package/dist/{chunk-FNGXKY5W.js → chunk-N2VST3NL.js} +68 -8
  24. package/dist/{chunk-XYKVRRC2.js → chunk-NFWPDEN4.js} +15 -7
  25. package/dist/{chunk-KFD2OXCE.js → chunk-P2EAAM3H.js} +68 -8
  26. package/dist/{chunk-QQKDGPUI.js → chunk-PO4M4JAJ.js} +1 -1
  27. package/dist/{chunk-2M5B7YNY.js → chunk-RRW2XWAY.js} +2 -2
  28. package/dist/{chunk-REZRGCKV.js → chunk-SOGGKYFV.js} +3 -3
  29. package/dist/{chunk-3OHR73VK.js → chunk-SVXIJ2IC.js} +1 -1
  30. package/dist/{chunk-SYFVUF2C.js → chunk-SZJZEFTY.js} +1 -1
  31. package/dist/{chunk-42IULNIY.js → chunk-THLVCXLF.js} +3 -3
  32. package/dist/{chunk-JZHRZ3DR.js → chunk-TQBUXKIB.js} +5 -5
  33. package/dist/chunk-WWUABK6B.js +56 -0
  34. package/dist/{chunk-D2IKXMF3.js → chunk-XGE6YUO6.js} +453 -289
  35. package/dist/{chunk-BTC2D6LS.js → chunk-Y5JES2VV.js} +3 -51
  36. package/dist/{connected-teams-WK47ATT7.js → connected-teams-LX5DLL2I.js} +3 -2
  37. package/dist/{connected-teams-PU4AMFNA.js → connected-teams-PQETXUWV.js} +3 -2
  38. package/dist/{coordinator-prompt-FQX6ALAI.js → coordinator-prompt-JLMQVORO.js} +6 -6
  39. package/dist/{coordinator-prompt-LE2GQZ2X.js → coordinator-prompt-VZ7OSRLD.js} +6 -5
  40. package/dist/{global-workspace-6P236KKG.js → global-workspace-3FDAUNN2.js} +6 -6
  41. package/dist/{global-workspace-KT3FRHZL.js → global-workspace-QPSJEXGR.js} +6 -5
  42. package/dist/index.cjs +1107 -826
  43. package/dist/index.d.cts +15 -1
  44. package/dist/index.d.ts +15 -1
  45. package/dist/index.js +24 -18
  46. package/dist/mcp/run-stdio.cjs +1912 -1652
  47. package/dist/mcp/run-stdio.js +16 -14
  48. package/dist/{orchestrator-MI7A5DQZ.js → orchestrator-5VVR7LOU.js} +9 -8
  49. package/dist/{orchestrator-WVZPTHBV.js → orchestrator-LFC3V3AX.js} +10 -10
  50. package/dist/{run-EBWL2Q4N.js → run-QDZCTP4Y.js} +2 -1
  51. package/dist/{run-CAGKZLHA.js → run-UEQM4JMZ.js} +2 -1
  52. package/dist/{workspaces-R5LJCJRB.js → workspaces-76W7IEKR.js} +8 -6
  53. package/dist/{workspaces-KM7KTLVO.js → workspaces-TDBA64II.js} +8 -5
  54. package/dist/{worktree-MDMTCAHX.js → worktree-7V67XTEO.js} +5 -5
  55. package/dist/{worktree-VXRI7LYM.js → worktree-I573UOPD.js} +5 -4
  56. package/dist/{worktree-exclude-N5X5I422.js → worktree-exclude-K7QK4RI5.js} +3 -2
  57. package/dist/{worktree-exclude-X54WSOXM.js → worktree-exclude-L32WEBX6.js} +3 -2
  58. package/package.json +1 -1
  59. package/dist/{chunk-L5CIEBLR.js → chunk-EWHJNJHV.js} +3 -3
@@ -1,18 +1,18 @@
1
1
  import {
2
2
  isOrchestratorThread
3
- } from "./chunk-FNGXKY5W.js";
3
+ } from "./chunk-N2VST3NL.js";
4
4
  import {
5
5
  codexUnattendedGitConfigArgs,
6
6
  mergeAgentGitAuthEnv,
7
7
  resolveAgentGitAuthEnv,
8
8
  resolveCodexGitWritableRoots
9
- } from "./chunk-NI243N27.js";
9
+ } from "./chunk-KTZESHYH.js";
10
10
  import {
11
11
  applyConnectedTeamToCli,
12
12
  brightsyMcpServerName,
13
13
  ensureCliTeamTracked,
14
14
  ensureConnectedBrightsyTeamTokens
15
- } from "./chunk-SALM35KD.js";
15
+ } from "./chunk-IILG7DPX.js";
16
16
  import {
17
17
  loadBrightsyConfig
18
18
  } from "./chunk-AN3THU7F.js";
@@ -33,7 +33,7 @@ import {
33
33
  packagedMcpStdioPath,
34
34
  parseCursorRunnerLine,
35
35
  resolveNodeLaunch
36
- } from "./chunk-JZHRZ3DR.js";
36
+ } from "./chunk-TQBUXKIB.js";
37
37
  import {
38
38
  claudeChromeEnabled,
39
39
  fromInclusiveInputUsage,
@@ -42,11 +42,7 @@ import {
42
42
  loadAppSettings,
43
43
  resolveAgentExecutable,
44
44
  resolveClaudeExecutable
45
- } from "./chunk-L5CIEBLR.js";
46
- import {
47
- isElectronLikeCommand,
48
- unwrapStrippedElectronLaunch
49
- } from "./chunk-4TR3HZFT.js";
45
+ } from "./chunk-EWHJNJHV.js";
50
46
  import {
51
47
  appDataDir
52
48
  } from "./chunk-WFUDY6PC.js";
@@ -56,7 +52,11 @@ import {
56
52
  resolveCommandBinarySync,
57
53
  run,
58
54
  withExportedPath
59
- } from "./chunk-UFHNSFAS.js";
55
+ } from "./chunk-HVUVQXZ2.js";
56
+ import {
57
+ isElectronLikeCommand,
58
+ unwrapStrippedElectronLaunch
59
+ } from "./chunk-4TR3HZFT.js";
60
60
 
61
61
  // src/agents/brightsy.ts
62
62
  import { existsSync } from "fs";
@@ -486,7 +486,7 @@ var brightsyAdapter = {
486
486
  };
487
487
 
488
488
  // src/agents/claude.ts
489
- import { existsSync as existsSync3 } from "fs";
489
+ import { existsSync as existsSync5 } from "fs";
490
490
 
491
491
  // src/agents/claude-mcp.ts
492
492
  function parseMcpList(text) {
@@ -521,6 +521,11 @@ function mcpAllowTools(servers) {
521
521
  }
522
522
  return out;
523
523
  }
524
+ function mcpAllowToolsFromNames(names) {
525
+ return mcpAllowTools(
526
+ [...names].map((name) => name.trim()).filter(Boolean).map((name) => ({ name, connected: true, needsAuth: false }))
527
+ );
528
+ }
524
529
  function mcpAuthWarnings(servers) {
525
530
  const needing = servers.filter((s) => s.needsAuth).map((s) => s.name);
526
531
  if (needing.length === 0) return [];
@@ -529,964 +534,1168 @@ function mcpAuthWarnings(servers) {
529
534
  ];
530
535
  }
531
536
 
532
- // src/agents/message-parts.ts
533
- function asRecord(input) {
534
- if (input && typeof input === "object" && !Array.isArray(input)) {
535
- return input;
537
+ // src/agents/orch-mcp-isolation.ts
538
+ import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
539
+ import { homedir as homedir2 } from "os";
540
+ import { join as join3 } from "path";
541
+ import { parse as parseToml } from "smol-toml";
542
+
543
+ // src/agents/user-mcp-config.ts
544
+ import { existsSync as existsSync3, mkdirSync, readFileSync, writeFileSync as writeFileSync2 } from "fs";
545
+ import { homedir } from "os";
546
+ import { dirname as dirname2, join as join2 } from "path";
547
+
548
+ // src/agents/injected-mcp.ts
549
+ import { existsSync as existsSync2, mkdtempSync, writeFileSync } from "fs";
550
+ import { createRequire } from "module";
551
+ import { tmpdir } from "os";
552
+ import { dirname, join } from "path";
553
+ import { fileURLToPath } from "url";
554
+
555
+ // src/mcp/profile.ts
556
+ var SIDEBOARD_MCP_PROFILE_ENV = "SIDEBOARD_MCP_PROFILE";
557
+ var WORKTREE_MCP_TOOLS = [
558
+ "present_artifact",
559
+ "ask_user",
560
+ "present_plan",
561
+ "present_schema",
562
+ "present_files",
563
+ "wait_for_job",
564
+ "stop_job",
565
+ "get_viewer_context",
566
+ "update_viewer_context"
567
+ ];
568
+ var WORKTREE_GITHUB_MCP_TOOLS = [
569
+ "github_search_issues",
570
+ "github_get_issue",
571
+ "github_comment",
572
+ "github_update_issue",
573
+ "github_create_issue"
574
+ ];
575
+ var WORKTREE_LINEAR_MCP_TOOLS = [
576
+ "linear_list_teams",
577
+ "linear_search_issues",
578
+ "linear_get_issue",
579
+ "linear_create_issue",
580
+ "linear_update_issue",
581
+ "linear_comment"
582
+ ];
583
+ var WORKTREE_ABLETIME_MCP_TOOLS = [
584
+ "abletime_orientation",
585
+ "abletime_list_projects",
586
+ "abletime_list_tasks",
587
+ "abletime_search_tasks",
588
+ "abletime_get_task",
589
+ "abletime_comment",
590
+ "abletime_update_task",
591
+ "abletime_create_task",
592
+ "abletime_ensure_task"
593
+ ];
594
+ function sideboardMcpProfile(env = process.env) {
595
+ return env[SIDEBOARD_MCP_PROFILE_ENV]?.trim().toLowerCase() === "worktree" ? "worktree" : "orchestration";
596
+ }
597
+
598
+ // src/agents/injected-mcp.ts
599
+ var SIDEBOARD_MCP_ALLOWED_TOOLS = [
600
+ "mcp__sideboard",
601
+ "mcp__sideboard__*"
602
+ ];
603
+ var SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS = [
604
+ "mcp__sideboard__present_artifact",
605
+ "mcp__sideboard__present_schema",
606
+ "mcp__sideboard__present_files",
607
+ "mcp__sideboard__ask_user",
608
+ "mcp__sideboard__present_plan",
609
+ "mcp__sideboard__wait_for_job",
610
+ "mcp__sideboard__stop_job",
611
+ "mcp__sideboard__get_viewer_context",
612
+ "mcp__sideboard__update_viewer_context"
613
+ ];
614
+ var SIDEBOARD_GITHUB_MCP_ALLOWED_TOOLS = ["mcp__sideboard__github_*"];
615
+ var SIDEBOARD_LINEAR_MCP_ALLOWED_TOOLS = ["mcp__sideboard__linear_*"];
616
+ var SIDEBOARD_ABLETIME_MCP_ALLOWED_TOOLS = ["mcp__sideboard__abletime_*"];
617
+ function sideboardWorktreeAllowedTools(opts) {
618
+ const out = [...SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS];
619
+ if (opts?.github !== false) out.push(...SIDEBOARD_GITHUB_MCP_ALLOWED_TOOLS);
620
+ if (opts?.linear) out.push(...SIDEBOARD_LINEAR_MCP_ALLOWED_TOOLS);
621
+ if (opts?.abletime) out.push(...SIDEBOARD_ABLETIME_MCP_ALLOWED_TOOLS);
622
+ return out;
623
+ }
624
+ var BRIGHTSY_MCP_ALLOWED_TOOLS = [
625
+ "mcp__brightsy",
626
+ "mcp__brightsy__*"
627
+ ];
628
+ var brightsyMcpCommandCache = null;
629
+ async function resolveBrightsyMcpCommand() {
630
+ const now = Date.now();
631
+ if (brightsyMcpCommandCache && now - brightsyMcpCommandCache.at < 6e4 && brightsyMcpCommandCache.command) {
632
+ return brightsyMcpCommandCache.command;
536
633
  }
537
- return void 0;
634
+ const which = await run("which", ["brightsy-mcp"], { reject: false });
635
+ const command = which.exitCode === 0 && which.stdout.trim() ? "brightsy-mcp" : "npx";
636
+ brightsyMcpCommandCache = { at: now, command };
637
+ return command;
538
638
  }
539
- function str(v) {
540
- return typeof v === "string" && v.trim() ? v : void 0;
639
+ function isBrightsyConnected() {
640
+ try {
641
+ loadBrightsyConfig();
642
+ return true;
643
+ } catch {
644
+ return false;
645
+ }
541
646
  }
542
- var CHAT_DETAIL_MAX = 72;
543
- function clipChatLine(value, max = CHAT_DETAIL_MAX) {
544
- const line = value.trim().split(/\r?\n/, 1)[0] ?? "";
545
- if (line.length <= max) return line;
546
- return `${line.slice(0, Math.max(1, max - 1))}\u2026`;
647
+ var BRIGHTSY_WORD = /(?<![a-z0-9_-])brightsy(?![a-z0-9_-])/i;
648
+ function promptMentionsBrightsy(text) {
649
+ return BRIGHTSY_WORD.test(text ?? "");
547
650
  }
548
- var SHELL_BIN_DIR = /^(?:\/usr\/local\/bin|\/usr\/bin|\/bin|\/opt\/homebrew\/bin|\/sbin|\/usr\/sbin)\//;
549
- function rootedPathWithSpaces(trimmed) {
550
- const [head = "", ...rest] = trimmed.split(/\s+/);
551
- if (SHELL_BIN_DIR.test(head)) return false;
552
- return rest.every((tok) => !/^[-/~$]/.test(tok) && !tok.includes("="));
651
+ function isBrightsyMcpToolName(name) {
652
+ const n = name.toLowerCase();
653
+ return n === "brightsy" || n.startsWith("mcp__brightsy") || n.startsWith("brightsy_");
553
654
  }
554
- function looksLikeFilePath(value) {
555
- const trimmed = value.trim();
556
- if (!trimmed || /[\r\n;|&<>]/.test(trimmed)) return false;
557
- const rooted = trimmed.startsWith("/") || trimmed.startsWith("~/") || /^[A-Za-z]:[\\/]/.test(trimmed);
558
- if (/\s/.test(trimmed)) return rooted && rootedPathWithSpaces(trimmed);
559
- if (rooted) return true;
560
- return trimmed.includes("/") || trimmed.includes("\\");
655
+ function toolInputUsesBrightsyDatasource(input) {
656
+ const ds = input?.datasource;
657
+ return typeof ds === "string" && ds.toLowerCase() === "brightsy";
561
658
  }
562
- function stripWorktreePrefix(path, worktreePath) {
563
- if (!worktreePath) return path;
564
- const prefix = worktreePath.replace(/[/\\]+$/, "");
565
- if (path === prefix || path === `${prefix}/`) return "";
566
- if (path.startsWith(`${prefix}/`)) return path.slice(prefix.length + 1);
567
- return path;
659
+ function messageUsedBrightsyMcp(msg) {
660
+ if (msg.role === "user" && promptMentionsBrightsy(msg.text)) return true;
661
+ for (const part of msg.parts ?? []) {
662
+ if (part.type !== "tool") continue;
663
+ if (isBrightsyMcpToolName(part.name)) return true;
664
+ if (/present_(schema|files)$/i.test(part.name) && toolInputUsesBrightsyDatasource(part.input)) {
665
+ return true;
666
+ }
667
+ }
668
+ return false;
568
669
  }
569
- function visibleToolRowDetail(detail, description, worktreePath) {
570
- if (!detail?.trim()) return void 0;
571
- const raw = detail.trim();
572
- const desc = (description ?? "").trim();
573
- if (!looksLikeFilePath(raw)) {
574
- const clipped = clipChatLine(raw);
575
- if (!clipped || desc === clipped || desc === raw) return void 0;
576
- return clipped;
670
+ function threadRequestsBrightsyMcp(thread) {
671
+ return (thread?.messages ?? []).some(messageUsedBrightsyMcp);
672
+ }
673
+ function shouldInjectBrightsyMcp(thread, opts) {
674
+ if (!(opts?.connected ?? isBrightsyConnected())) return false;
675
+ if (opts?.orchestrator) return true;
676
+ const alwaysOn = opts?.alwaysOnWorktree ?? Boolean(loadAppSettings().brightsy?.injectWorktreeMcp);
677
+ if (alwaysOn) return true;
678
+ return threadRequestsBrightsyMcp(thread);
679
+ }
680
+ function mcpLaunch(cmd, name, env) {
681
+ if (cmd === "brightsy-mcp") {
682
+ return { name, command: "brightsy-mcp", ...env ? { env } : {} };
577
683
  }
578
- const rel = stripWorktreePrefix(raw, worktreePath);
579
- if (!rel) return void 0;
580
- const base = fileBasename(rel);
581
- if (desc && (desc === base || desc.endsWith(` ${base}`))) return void 0;
582
- if (rel.length <= 42) return rel;
583
- const parts = rel.split(/[/\\]/).filter(Boolean);
584
- if (parts.length >= 2) return `\u2026/${parts.slice(-2).join("/")}`;
585
- return base;
684
+ return {
685
+ name,
686
+ command: "npx",
687
+ args: ["-y", "@brightsy/mcp-server"],
688
+ ...env ? { env } : {}
689
+ };
586
690
  }
587
- function toolDetail(name, input) {
588
- if (!input) return void 0;
589
- const command = str(input.command) ?? str(input.cmd);
590
- if (command) return clipChatLine(command);
591
- const pattern = str(input.pattern) ?? str(input.glob) ?? str(input.glob_pattern);
592
- const isSearch = /grep|glob|search|ripgrep|findfiles|semsearch/i.test(name);
593
- if (isSearch && pattern) {
594
- return pattern.length > 80 ? `${pattern.slice(0, 77)}\u2026` : pattern;
691
+ function teamEnv(team) {
692
+ const endpoint = (team.endpoint || "https://brightsy.ai").replace(/\/$/, "");
693
+ return {
694
+ BRIGHTSY_API_TOKEN: team.access_token,
695
+ ...team.refresh_token ? { BRIGHTSY_REFRESH_TOKEN: team.refresh_token } : {},
696
+ BRIGHTSY_ACCOUNT_ID: team.id,
697
+ BRIGHTSY_API_URL: endpoint
698
+ };
699
+ }
700
+ function brightsyMcpAllowedTools(serverNames) {
701
+ const out = [];
702
+ for (const name of serverNames) {
703
+ out.push(`mcp__${name}`, `mcp__${name}__*`);
595
704
  }
596
- const path = str(input.file_path) ?? str(input.path) ?? str(input.filePath) ?? str(input.filename);
597
- if (path) return path;
598
- if (pattern) return pattern.length > 80 ? `${pattern.slice(0, 77)}\u2026` : pattern;
599
- const query = str(input.query) ?? str(input.prompt);
600
- if (query) return query.length > 80 ? `${query.slice(0, 77)}\u2026` : query;
705
+ return out;
706
+ }
707
+ function corePackageDir() {
708
+ const cjsDir = typeof __dirname !== "undefined" ? __dirname : "";
709
+ if (cjsDir) return cjsDir;
601
710
  try {
602
- const raw = JSON.stringify(input);
603
- return raw.length > 80 ? `${raw.slice(0, 77)}\u2026` : raw;
711
+ const url = import.meta.url;
712
+ if (typeof url === "string" && url.length > 0) {
713
+ return dirname(fileURLToPath(url));
714
+ }
604
715
  } catch {
605
- return name;
606
- }
607
- }
608
- function toolDescription(name, input) {
609
- const n = name.replace(/^mcp__/, "").replace(/__/g, " \xB7 ");
610
- if (/^get_record_types$|recordTypes/i.test(name)) return "List record types";
611
- if (/connectedAgentRequest/i.test(name)) {
612
- return str(input?.agent_id) ? `Ask connected agent` : "Ask connected agent";
613
716
  }
614
- if (/present_artifact$/i.test(name)) {
615
- return str(input?.title) ? `Present ${str(input?.title)}` : "Present artifact";
616
- }
617
- if (/present_plan$/i.test(name)) {
618
- return str(input?.title) ? `Plan ${str(input?.title)}` : "Present plan";
717
+ try {
718
+ const req = createRequire(join(process.cwd(), "package.json"));
719
+ return dirname(req.resolve("@sideboard-ai/core"));
720
+ } catch {
721
+ return process.cwd();
619
722
  }
620
- if (/present_schema$/i.test(name)) {
621
- return str(input?.title) ? `Schema ${str(input?.title)}` : "Present schema";
622
- }
623
- if (/present_files$/i.test(name)) {
624
- return str(input?.title) ? `Files ${str(input?.title)}` : "Present files";
723
+ }
724
+ function findSideboardMcpJsEntry() {
725
+ const override = process.env.SIDEBOARD_MCP_ENTRY?.trim() || process.env.SIDEBOARD_CLI?.trim();
726
+ if (override && existsSync2(override)) return override;
727
+ const packaged = packagedMcpStdioPath();
728
+ if (packaged) return packaged;
729
+ let dir = corePackageDir();
730
+ for (let i = 0; i < 10; i++) {
731
+ const candidates = [
732
+ join(dir, "mcp/run-stdio.js"),
733
+ join(dir, "mcp/run-stdio.cjs"),
734
+ join(dir, "dist/mcp/run-stdio.js"),
735
+ join(dir, "dist/mcp/run-stdio.cjs"),
736
+ join(dir, "packages/core/dist/mcp/run-stdio.js"),
737
+ join(dir, "packages/cli/dist/index.js"),
738
+ join(dir, "cli/dist/index.js")
739
+ ];
740
+ for (const p of candidates) {
741
+ if (existsSync2(p) && !isAsarPath(p)) return p;
742
+ }
743
+ const parent = dirname(dir);
744
+ if (parent === dir) break;
745
+ dir = parent;
625
746
  }
626
- if (/wait_for_job$/i.test(name)) {
627
- return str(input?.id) ? `Wait for ${str(input?.id)}` : "Wait for job";
747
+ return null;
748
+ }
749
+ async function resolveSideboardMcpServer() {
750
+ const entry = findSideboardMcpJsEntry();
751
+ if (entry) {
752
+ const isCli = /[/\\]cli[/\\]dist[/\\]index\.js$/.test(entry);
753
+ const scriptArgs = isCli ? [entry, "mcp"] : [entry];
754
+ const launch = applyNodeLaunch(await resolveNodeLaunch(entry), scriptArgs);
755
+ if (launch.file !== "/bin/sh" && !isElectronLikeCommand(launch.file)) {
756
+ return {
757
+ name: "sideboard",
758
+ command: launch.file,
759
+ args: launch.args,
760
+ ...Object.keys(launch.env).length > 0 ? { env: launch.env } : {}
761
+ };
762
+ }
628
763
  }
629
- if (/stop_job$/i.test(name)) {
630
- return str(input?.id) ? `Stop ${str(input?.id)}` : "Stop job";
764
+ const which = await run("which", ["sideboard"], { reject: false });
765
+ if (which.exitCode === 0 && which.stdout.trim()) {
766
+ return { name: "sideboard", command: which.stdout.trim(), args: ["mcp"] };
631
767
  }
632
- if (isSubagentToolName(name)) {
633
- const desc = str(input?.description);
634
- const sub = asRecord(input?.subagentType);
635
- const kind = str(sub?.name) ?? str(sub?.kind) ?? str(input?.subagent_type) ?? str(input?.subagentType);
636
- if (/^spawn_agent$/i.test(name)) {
637
- return str(input?.prompt) ?? desc ?? "Subagent";
768
+ return { name: "sideboard", command: "sideboard", args: ["mcp"] };
769
+ }
770
+ async function buildInjectedMcpServers(opts) {
771
+ const servers = [];
772
+ if (opts.includeSideboard) {
773
+ const sideboard = await resolveSideboardMcpServer();
774
+ const orchId = opts.orchestratorThreadId?.trim();
775
+ const profile = orchId ? "orchestration" : "worktree";
776
+ sideboard.env = {
777
+ ...sideboard.env ?? {},
778
+ SIDEBOARD_APP_DATA: appDataDir(),
779
+ [SIDEBOARD_MCP_PROFILE_ENV]: profile
780
+ };
781
+ if (orchId) {
782
+ sideboard.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID = orchId;
638
783
  }
639
- if (/connectedAgentRequest/i.test(name)) {
640
- return str(input?.agent_id) ? `Ask connected agent` : "Ask connected agent";
784
+ try {
785
+ mergeAgentGitAuthEnv(
786
+ sideboard.env,
787
+ await resolveAgentGitAuthEnv(sideboard.env)
788
+ );
789
+ } catch {
641
790
  }
642
- if (desc && kind) return `${kind}: ${desc}${subagentLiveSuffix(input)}`;
643
- if (desc) return `${desc}${subagentLiveSuffix(input)}`;
644
- return `Subagent${subagentLiveSuffix(input)}`;
645
- }
646
- if (/^(create|update)_artifact$/i.test(name.replace(/^mcp__[^_]+__/, ""))) {
647
- return str(input?.title) ? `Artifact ${str(input?.title)}` : "Artifact";
648
- }
649
- if (!input) return n;
650
- if (/bash|shell|terminal/i.test(name) && str(input.command)) {
651
- const cmd = str(input.command);
652
- if (/git\s+fetch/i.test(cmd)) return "Fetch latest from origin and check status";
653
- if (/git\s+status/i.test(cmd)) return "Check git status";
654
- if (/git\s+log/i.test(cmd)) return "Inspect recent commits";
655
- if (/git\s+branch/i.test(cmd)) return "List branches";
656
- if (/grep|rg\b/i.test(cmd)) return "Search repository";
657
- return "Run shell command";
658
- }
659
- if (/edit|write|apply/i.test(name)) {
660
- const path = str(input.file_path) ?? str(input.path);
661
- return path ? `Edit ${path.split("/").pop()}` : "Edit file";
791
+ applyAgentRunnerHeapEnv(sideboard.env);
792
+ servers.push(sideboard);
662
793
  }
663
- if (/read/i.test(name)) {
664
- const path = str(input.file_path) ?? str(input.path);
665
- return path ? `Read ${path.split("/").pop()}` : "Read file";
794
+ if (opts.includeBrightsy && isBrightsyConnected()) {
795
+ const cmd = await resolveBrightsyMcpCommand();
796
+ const teams = await ensureConnectedBrightsyTeamTokens();
797
+ if (teams.length > 0) {
798
+ const used = /* @__PURE__ */ new Set();
799
+ for (const team of teams) {
800
+ let name = brightsyMcpServerName(team.slug);
801
+ if (used.has(name)) name = `${name}_${team.id.slice(0, 8)}`;
802
+ used.add(name);
803
+ servers.push(mcpLaunch(cmd, name, teamEnv(team)));
804
+ }
805
+ } else {
806
+ servers.push(mcpLaunch(cmd, "brightsy"));
807
+ }
666
808
  }
667
- if (/grep/i.test(name)) return "Search files";
668
- if (/glob/i.test(name)) return "Find files";
669
- const compact = n.replace(/[_-]/g, "");
670
- if (/^taskoutput$/i.test(compact)) {
671
- return str(input.task_id) ? `Wait for ${str(input.task_id)}` : "Wait for background task";
809
+ return servers;
810
+ }
811
+ function cursorSafeMcpLaunch(command, args) {
812
+ if (process.platform === "win32") {
813
+ return args && args.length > 0 ? { command, args } : { command };
672
814
  }
673
- if (/^taskstop$/i.test(compact)) {
674
- return str(input.task_id) ? `Stop ${str(input.task_id)}` : "Stop background task";
815
+ const unwrapped = unwrapStrippedElectronLaunch(command, args);
816
+ const file = unwrapped?.file ?? command;
817
+ const fileArgs = unwrapped?.args ?? args ?? [];
818
+ return fileArgs.length > 0 ? { command: file, args: fileArgs } : { command: file };
819
+ }
820
+ function mcpSpawnEnv(env) {
821
+ if (!env) return void 0;
822
+ const out = { ...env };
823
+ delete out.ELECTRON_RUN_AS_NODE;
824
+ delete out.ELECTRON_RUN_AS_NODE;
825
+ return Object.keys(out).length > 0 ? out : void 0;
826
+ }
827
+ function toCursorMcpServers(servers) {
828
+ const out = {};
829
+ for (const s of servers) {
830
+ const env = mcpSpawnEnv(s.env);
831
+ const launch = cursorSafeMcpLaunch(s.command, s.args);
832
+ out[s.name] = {
833
+ type: "stdio",
834
+ command: launch.command,
835
+ ...launch.args && launch.args.length > 0 ? { args: launch.args } : {},
836
+ ...env ? { env } : {}
837
+ };
675
838
  }
676
- if (/^monitor$/i.test(compact)) {
677
- const command = str(input.command);
678
- return command ? `Watch ${command.length > 48 ? `${command.slice(0, 45)}\u2026` : command}` : "Watch command";
839
+ return out;
840
+ }
841
+ function toCodexMcpConfigArgs(servers) {
842
+ const args = [];
843
+ for (const s of servers) {
844
+ const prefix = `mcp_servers.${s.name}`;
845
+ args.push("-c", `${prefix}.command=${JSON.stringify(s.command)}`);
846
+ if (s.args?.length) {
847
+ args.push("-c", `${prefix}.args=${JSON.stringify(s.args)}`);
848
+ }
849
+ const env = mcpSpawnEnv(s.env);
850
+ if (env) {
851
+ for (const [key, value] of Object.entries(env)) {
852
+ args.push("-c", `${prefix}.env.${key}=${JSON.stringify(value)}`);
853
+ }
854
+ }
855
+ args.push("-c", `${prefix}.default_tools_approval_mode=${JSON.stringify("approve")}`);
856
+ args.push("-c", `${prefix}.tool_timeout_sec=300`);
857
+ args.push("-c", `${prefix}.startup_timeout_sec=30`);
679
858
  }
680
- return n;
859
+ return args;
681
860
  }
682
- function subagentLiveSuffix(input) {
683
- if (!input) return "";
684
- const bits = [];
685
- const status = str(input.live_status);
686
- if (status && !/^runn(ing)?$|^working$/i.test(status)) bits.push(status);
687
- if (typeof input.live_tool_uses === "number") bits.push(`${input.live_tool_uses} tools`);
688
- if (typeof input.live_duration_ms === "number") {
689
- bits.push(`${Math.max(0, Math.round(Number(input.live_duration_ms) / 1e3))}s`);
861
+ function toOpencodeMcpConfigContent(servers, opts) {
862
+ const mcp = {};
863
+ for (const s of servers) {
864
+ const env = mcpSpawnEnv(s.env);
865
+ mcp[s.name] = {
866
+ type: "local",
867
+ command: [s.command, ...s.args ?? []],
868
+ enabled: true,
869
+ ...env ? { environment: env } : {}
870
+ };
690
871
  }
691
- const last = str(input.live_last_tool);
692
- if (last) bits.push(last);
693
- return bits.length ? ` \xB7 ${bits.join(" \xB7 ")}` : "";
872
+ for (const name of opts?.disableNames ?? []) {
873
+ const key = name.trim();
874
+ if (!key || mcp[key]?.enabled === true) continue;
875
+ mcp[key] = { ...mcp[key], enabled: false };
876
+ }
877
+ return JSON.stringify({ mcp });
694
878
  }
695
- function isSubagentToolName(name) {
696
- const n = (name ?? "").trim();
697
- if (/^(task|agent|spawn_agent)$/i.test(n)) return true;
698
- return /connectedAgentRequest/i.test(n);
879
+ function stripElectronFromUserMcpEntry(entry) {
880
+ const next = { ...entry };
881
+ const env = next.env;
882
+ if (!env || typeof env !== "object" || Array.isArray(env)) return next;
883
+ const cleaned = { ...env };
884
+ delete cleaned.ELECTRON_RUN_AS_NODE;
885
+ next.env = cleaned;
886
+ return next;
699
887
  }
700
- function isInternalAgentStatusText(text) {
701
- const t = text.trim();
702
- if (!t) return false;
703
- if (/waiting for gate to pass/i.test(t)) return true;
704
- if (/^agent is running\b/i.test(t) && t.length < 160) return true;
705
- return false;
888
+ function writeMcpServersConfig(servers, extraServers) {
889
+ const mcpServers = {};
890
+ for (const s of servers) {
891
+ const env = mcpSpawnEnv(s.env);
892
+ mcpServers[s.name] = {
893
+ command: s.command,
894
+ ...s.args ? { args: s.args } : {},
895
+ ...env ? { env } : {}
896
+ };
897
+ }
898
+ if (extraServers) {
899
+ for (const [rawName, entry] of Object.entries(extraServers)) {
900
+ const name = rawName.trim();
901
+ if (!name || mcpServers[name] || !entry) continue;
902
+ mcpServers[name] = stripElectronFromUserMcpEntry(entry);
903
+ }
904
+ }
905
+ if (Object.keys(mcpServers).length === 0) return null;
906
+ const dir = mkdtempSync(join(tmpdir(), "sideboard-mcp-"));
907
+ const cfgPath = join(dir, "mcp.json");
908
+ writeFileSync(cfgPath, JSON.stringify({ mcpServers }, null, 2));
909
+ return cfgPath;
706
910
  }
707
- function isPollWrapperToolName(name) {
708
- const n = (name ?? "").replace(/[_-]/g, "");
709
- return /^(taskoutput|taskstop|sleep)$/i.test(n);
911
+ async function writeInjectedMcpConfig(opts) {
912
+ return writeMcpServersConfig(await buildInjectedMcpServers(opts));
710
913
  }
711
- function lastTextPart(parts, type) {
712
- for (let i = parts.length - 1; i >= 0; i--) {
713
- const p = parts[i];
714
- if (p.type !== type) continue;
715
- const text = p.text.trim();
716
- if (!text || isInternalAgentStatusText(text)) continue;
717
- return text;
718
- }
719
- return "";
914
+
915
+ // src/agents/user-mcp-config.ts
916
+ function userCursorMcpConfigPath() {
917
+ return join2(homedir(), ".cursor", "mcp.json");
720
918
  }
721
- function toolLabel(tool) {
722
- return tool.description || toolDescription(tool.name, tool.input) || tool.name;
919
+ function userClaudeMcpConfigPath() {
920
+ return join2(homedir(), ".claude.json");
723
921
  }
724
- function fileBasename(path) {
725
- const parts = path.replace(/\/$/, "").split(/[/\\]/);
726
- return parts[parts.length - 1] || path;
922
+ function asObject(value) {
923
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
924
+ return { ...value };
727
925
  }
728
- function classifyTool(name) {
729
- const compact = name.replace(/^mcp__[^_]+__/, "").replace(/[_-]/g, "");
730
- if (/bash|shell|terminal|^zsh$|^sh$/i.test(compact)) return "shell";
731
- if (/grep|glob|search|ripgrep|findfiles|semsearch/i.test(compact)) return "search";
732
- if (/^(read|cat)$/i.test(compact) || /^read/i.test(compact)) return "read";
733
- if (/edit|write|apply|strreplace|multiedit|updatefile|createfile/i.test(compact)) {
734
- return "edit";
735
- }
736
- return "other";
926
+ function stripElectronSpawnEnv(env) {
927
+ if (!env) return void 0;
928
+ const next = { ...env };
929
+ delete next.ELECTRON_RUN_AS_NODE;
930
+ delete next.ELECTRON_RUN_AS_NODE;
931
+ return Object.keys(next).length > 0 ? next : void 0;
737
932
  }
738
- function isShellToolName(name) {
739
- return classifyTool(name ?? "") === "shell";
933
+ function mergeSideboardIntoMcpServersJson(existing, sideboard) {
934
+ const root = asObject(existing);
935
+ const servers = asObject(root.mcpServers);
936
+ const env = stripElectronSpawnEnv(sideboard.env);
937
+ servers.sideboard = {
938
+ type: "stdio",
939
+ command: sideboard.command,
940
+ ...sideboard.args && sideboard.args.length > 0 ? { args: sideboard.args } : {},
941
+ ...env ? { env } : {}
942
+ };
943
+ return { ...root, mcpServers: servers };
740
944
  }
741
- function toolActivityLine(parts) {
742
- const tools = parts.filter((p) => {
743
- if (p.type !== "tool" || p.parentId) return false;
744
- if (isPollWrapperToolName(p.name)) return false;
745
- if (/present_plan$/i.test(p.name ?? "")) return false;
746
- if (/ask_user|AskUserQuestion/i.test(p.name ?? "")) return false;
747
- return true;
748
- });
749
- if (tools.length === 0) return null;
750
- const edited = [];
751
- let reads = 0;
752
- let searches = 0;
753
- let shells = 0;
754
- let others = 0;
755
- let additions = 0;
756
- let deletions = 0;
757
- for (const tool of tools) {
758
- const kind = classifyTool(tool.name);
759
- if (kind === "edit") {
760
- const path = tool.filePath ?? toolFilePath(tool.input);
761
- edited.push({
762
- name: path ? fileBasename(path) : tool.name,
763
- running: tool.status === "running"
764
- });
765
- } else if (kind === "read") reads += 1;
766
- else if (kind === "search") searches += 1;
767
- else if (kind === "shell") shells += 1;
768
- else others += 1;
769
- if (typeof tool.additions === "number") additions += tool.additions;
770
- if (typeof tool.deletions === "number") deletions += tool.deletions;
771
- }
772
- const bits = [];
773
- const editing = edited.filter((e) => e.running);
774
- const editedDone = edited.filter((e) => !e.running);
775
- if (editing.length === 1) bits.push(`Editing ${editing[0].name}`);
776
- else if (editing.length > 1) bits.push(`Editing ${editing.length} files`);
777
- if (editedDone.length === 1) bits.push(`${editing.length ? "edited" : "Edited"} ${editedDone[0].name}`);
778
- else if (editedDone.length > 1) {
779
- bits.push(`${editing.length ? "edited" : "Edited"} ${editedDone.length} files`);
945
+ function writeMergedMcpServersJson(configPath, sideboard) {
946
+ let existing = {};
947
+ if (existsSync3(configPath)) {
948
+ try {
949
+ existing = JSON.parse(readFileSync(configPath, "utf8"));
950
+ } catch {
951
+ existing = {};
952
+ }
780
953
  }
781
- if (reads === 1) bits.push("explored 1 file");
782
- else if (reads > 1) bits.push(`explored ${reads} files`);
783
- if (searches === 1) bits.push("1 search");
784
- else if (searches > 1) bits.push(`${searches} searches`);
785
- if (shells === 1) bits.push("ran 1 command");
786
- else if (shells > 1) bits.push(`ran ${shells} commands`);
787
- if (others === 1) bits.push("1 tool");
788
- else if (others > 1) bits.push(`${others} tools`);
789
- if (bits.length === 0) return null;
790
- return { text: bits.join(", "), additions, deletions };
954
+ const next = mergeSideboardIntoMcpServersJson(existing, sideboard);
955
+ mkdirSync(dirname2(configPath), { recursive: true });
956
+ writeFileSync2(configPath, `${JSON.stringify(next, null, 2)}
957
+ `);
791
958
  }
792
- function liveActivitySummary(parts, opts) {
793
- if (opts?.queued && parts.length === 0) {
794
- return "Queued \u2014 waiting for a slot";
959
+ function launchFromResolved(server) {
960
+ return {
961
+ command: server.command,
962
+ args: server.args,
963
+ env: {
964
+ ...server.env ?? {},
965
+ SIDEBOARD_APP_DATA: appDataDir()
966
+ }
967
+ };
968
+ }
969
+ async function registerPackagedUserMcpClients() {
970
+ const launch = launchFromResolved(await resolveSideboardMcpServer());
971
+ const cursor = userCursorMcpConfigPath();
972
+ writeMergedMcpServersJson(cursor, launch);
973
+ const claude = userClaudeMcpConfigPath();
974
+ if (existsSync3(claude)) {
975
+ writeMergedMcpServersJson(claude, launch);
976
+ return { cursor, claude };
795
977
  }
796
- const tools = parts.filter((p) => p.type === "tool");
797
- const runningSubs = tools.filter(
798
- (t) => t.status === "running" && !t.parentId && isSubagentToolName(t.name)
799
- );
800
- const runningNested = [...tools].reverse().find((t) => t.status === "running" && t.parentId && !isPollWrapperToolName(t.name));
801
- const runningTop = [...tools].reverse().find(
802
- (t) => t.status === "running" && !t.parentId && !isPollWrapperToolName(t.name) && !isSubagentToolName(t.name)
803
- );
804
- const runningPoll = [...tools].reverse().find((t) => t.status === "running" && isPollWrapperToolName(t.name));
805
- const thinking = lastTextPart(parts, "thinking");
806
- const text = lastTextPart(parts, "text");
807
- if (runningSubs.length > 0) {
808
- const heads = runningSubs.map(toolLabel);
809
- const head = runningSubs.length === 1 ? heads[0] : `${runningSubs.length} subagents \xB7 ${heads.slice(0, 2).join(" \xB7 ")}`;
810
- if (runningNested) return `${head} \xB7 ${toolLabel(runningNested)}`;
811
- return head;
978
+ return { cursor };
979
+ }
980
+
981
+ // src/agents/orch-mcp-isolation.ts
982
+ function isInjectedOrchMcpName(name) {
983
+ const n = name.trim().toLowerCase();
984
+ return n === "sideboard" || n === "brightsy" || n.startsWith("brightsy_");
985
+ }
986
+ function asObject2(value) {
987
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
988
+ return value;
989
+ }
990
+ function listMcpNamesFromJsonMap(raw, key) {
991
+ return Object.keys(asObject2(asObject2(raw)[key])).filter((n) => n.trim());
992
+ }
993
+ function listMcpNamesFromCodexToml(text) {
994
+ try {
995
+ const parsed = parseToml(text);
996
+ return Object.keys(asObject2(parsed.mcp_servers)).filter((n) => n.trim());
997
+ } catch {
998
+ return [];
812
999
  }
813
- if (runningTop) return toolLabel(runningTop);
814
- if (runningPoll) return toolLabel(runningPoll);
815
- if (thinking) return thinking.length > 96 ? `\u2026${thinking.slice(-96)}` : thinking;
816
- if (text) return "Writing reply\u2026";
817
- const last = [...tools].reverse().find((t) => !isPollWrapperToolName(t.name)) ?? tools.at(-1);
818
- if (last) {
819
- const label = toolLabel(last);
820
- return last.status === "running" ? label : `Finished ${label}`;
1000
+ }
1001
+ function readJsonObject(path) {
1002
+ try {
1003
+ return JSON.parse(readFileSync2(path, "utf8"));
1004
+ } catch {
1005
+ return {};
821
1006
  }
822
- return "Working\u2026";
823
1007
  }
824
- function messagePartParentId(part) {
825
- if ("parentId" in part && typeof part.parentId === "string" && part.parentId.trim()) {
826
- return part.parentId;
1008
+ function readText(path) {
1009
+ try {
1010
+ return readFileSync2(path, "utf8");
1011
+ } catch {
1012
+ return "";
827
1013
  }
828
- return void 0;
829
1014
  }
830
- function sameParentId(a, b) {
831
- return (a ?? "") === (b ?? "");
1015
+ function userCodexConfigPaths(home = homedir2()) {
1016
+ return [join3(home, ".codex", "config.toml"), join3(home, ".config", "codex", "config.toml")];
832
1017
  }
833
- function withEventParentId(event, parentId) {
834
- if (!parentId) return event;
835
- if (event.type === "stdout" || event.type === "thinking" || event.type === "tool_use" || event.type === "tool_result") {
836
- return { ...event, parentId: event.parentId ?? parentId };
1018
+ function userOpencodeConfigPaths(home = homedir2()) {
1019
+ return [
1020
+ join3(home, ".config", "opencode", "opencode.json"),
1021
+ join3(home, ".config", "opencode", "opencode.jsonc")
1022
+ ];
1023
+ }
1024
+ function userMcpNamesToDisable(opts) {
1025
+ const keep = new Set(
1026
+ [...opts.injectedNames ?? []].map((n) => n.trim().toLowerCase()).filter(Boolean)
1027
+ );
1028
+ const out = [];
1029
+ const seen = /* @__PURE__ */ new Set();
1030
+ for (const raw of opts.names) {
1031
+ const name = raw.trim();
1032
+ if (!name) continue;
1033
+ const key = name.toLowerCase();
1034
+ if (seen.has(key) || keep.has(key) || isInjectedOrchMcpName(name)) continue;
1035
+ seen.add(key);
1036
+ out.push(name);
837
1037
  }
838
- return event;
1038
+ return out;
839
1039
  }
840
- function withEventsParentId(events, parentId) {
841
- if (!parentId) return events;
842
- return events.map((event) => withEventParentId(event, parentId));
1040
+ function listUserCodexMcpNames(home = homedir2()) {
1041
+ const names = [];
1042
+ for (const path of userCodexConfigPaths(home)) {
1043
+ if (!existsSync4(path)) continue;
1044
+ names.push(...listMcpNamesFromCodexToml(readText(path)));
1045
+ }
1046
+ return names;
843
1047
  }
844
- function toolFilePath(input) {
845
- if (!input) return void 0;
846
- return str(input.file_path) ?? str(input.path) ?? str(input.filePath) ?? str(input.filename);
1048
+ function listUserOpencodeMcpNames(home = homedir2()) {
1049
+ const names = [];
1050
+ for (const path of userOpencodeConfigPaths(home)) {
1051
+ if (!existsSync4(path)) continue;
1052
+ names.push(...listMcpNamesFromJsonMap(readJsonObject(path), "mcp"));
1053
+ }
1054
+ return names;
847
1055
  }
848
- function countLines(text) {
849
- if (!text) return 0;
850
- return text.split("\n").length;
1056
+ function userClaudeMcpConfigObject(home = homedir2()) {
1057
+ const claudePath = home === homedir2() ? userClaudeMcpConfigPath() : join3(home, ".claude.json");
1058
+ if (!existsSync4(claudePath)) return {};
1059
+ return asObject2(asObject2(readJsonObject(claudePath)).mcpServers);
851
1060
  }
852
- function diffFromInput(input) {
853
- if (!input) return {};
854
- const oldS = str(input.old_string) ?? str(input.oldString);
855
- const newS = str(input.new_string) ?? str(input.newString) ?? str(input.content);
856
- if (oldS != null || newS != null) {
857
- return {
858
- additions: countLines(newS),
859
- deletions: countLines(oldS)
860
- };
1061
+ function listUserClaudeMcpServerEntries(home = homedir2()) {
1062
+ const out = {};
1063
+ for (const [rawName, value] of Object.entries(userClaudeMcpConfigObject(home))) {
1064
+ const name = rawName.trim();
1065
+ if (!name || isInjectedOrchMcpName(name)) continue;
1066
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
1067
+ out[name] = value;
861
1068
  }
862
- return {};
1069
+ return out;
863
1070
  }
864
- function parseDiffStat(result) {
865
- if (!result) return {};
866
- const paired = result.match(/\+(\d+)\s+-(\d+)/);
867
- if (paired) {
868
- return {
869
- additions: Number(paired[1]),
870
- deletions: Number(paired[2])
871
- };
872
- }
873
- const verbose = result.match(
874
- /(\d+)\s+insertions?(?:,\s*(\d+)\s+deletions?)?/i
875
- );
876
- if (verbose) {
877
- return {
878
- additions: Number(verbose[1]),
879
- deletions: verbose[2] != null ? Number(verbose[2]) : void 0
880
- };
1071
+ function toCodexDisableUserMcpArgs(names) {
1072
+ const args = [];
1073
+ for (const name of names) {
1074
+ if (!/^[A-Za-z0-9_-]+$/.test(name)) continue;
1075
+ args.push("-c", `mcp_servers.${name}.enabled=false`);
881
1076
  }
882
- return {};
1077
+ return args;
883
1078
  }
884
- var THINKING_PART_MAX_CHARS = 64e3;
885
- function clipThinkingForStore(text, max = THINKING_PART_MAX_CHARS) {
886
- if (text.length <= max) return text;
887
- const marker = "\u2026(earlier thinking trimmed)\n";
888
- return marker + text.slice(text.length - Math.max(1, max - marker.length));
889
- }
890
- var STDOUT_EVENT_MAX_CHARS = 16e3;
891
- var STDERR_EVENT_MAX_CHARS = 4e3;
892
- function clipEventTail(text, max) {
893
- if (text.length <= max) return text;
894
- const marker = "\u2026(truncated)\n";
895
- return marker + text.slice(text.length - Math.max(1, max - marker.length));
1079
+ function toCodexUnattendedAppsArgs() {
1080
+ return [
1081
+ "-c",
1082
+ "features.apps=false",
1083
+ "-c",
1084
+ "apps._default.enabled=false",
1085
+ "-c",
1086
+ "apps.posthog.enabled=false",
1087
+ "-c",
1088
+ "features.plugins=false"
1089
+ ];
896
1090
  }
897
- function clipAgentEventForPaint(event) {
898
- if (event.type === "tool_result" && event.content) {
899
- const content = clipToolResultForStore(event.content);
900
- if (content === event.content) return event;
901
- return { ...event, content };
902
- }
903
- if (event.type === "thinking" && event.data.length > THINKING_PART_MAX_CHARS) {
904
- return { ...event, data: clipThinkingForStore(event.data) };
905
- }
906
- if (event.type === "stdout" && event.data.length > STDOUT_EVENT_MAX_CHARS) {
907
- return { ...event, data: clipEventTail(event.data, STDOUT_EVENT_MAX_CHARS) };
908
- }
909
- if (event.type === "stderr" && event.data.length > STDERR_EVENT_MAX_CHARS) {
910
- return { ...event, data: clipEventTail(event.data, STDERR_EVENT_MAX_CHARS) };
1091
+
1092
+ // src/agents/message-parts.ts
1093
+ function asRecord(input) {
1094
+ if (input && typeof input === "object" && !Array.isArray(input)) {
1095
+ return input;
911
1096
  }
912
- return event;
1097
+ return void 0;
913
1098
  }
914
- function withPartTimes(next, prev) {
915
- const now = Date.now();
916
- const startedAt = prev && typeof prev.startedAt === "number" ? prev.startedAt : now;
917
- return { ...next, startedAt, updatedAt: now };
1099
+ function str(v) {
1100
+ return typeof v === "string" && v.trim() ? v : void 0;
918
1101
  }
919
- function applyAgentEvent(parts, event) {
920
- if (event.type === "stdout") {
921
- const data = event.data;
922
- if (!data) return parts;
923
- const next = [...parts];
924
- const last = next[next.length - 1];
925
- if (last?.type === "text" && sameParentId(last.parentId, event.parentId)) {
926
- next[next.length - 1] = withPartTimes(
927
- {
928
- type: "text",
929
- text: last.text + data,
930
- ...event.parentId ? { parentId: event.parentId } : {}
931
- },
932
- last
933
- );
934
- } else {
935
- next.push(
936
- withPartTimes({
937
- type: "text",
938
- text: data,
939
- ...event.parentId ? { parentId: event.parentId } : {}
940
- })
941
- );
942
- }
943
- return next;
1102
+ var CHAT_DETAIL_MAX = 72;
1103
+ function clipChatLine(value, max = CHAT_DETAIL_MAX) {
1104
+ const line = value.trim().split(/\r?\n/, 1)[0] ?? "";
1105
+ if (line.length <= max) return line;
1106
+ return `${line.slice(0, Math.max(1, max - 1))}\u2026`;
1107
+ }
1108
+ var SHELL_BIN_DIR = /^(?:\/usr\/local\/bin|\/usr\/bin|\/bin|\/opt\/homebrew\/bin|\/sbin|\/usr\/sbin)\//;
1109
+ function rootedPathWithSpaces(trimmed) {
1110
+ const [head = "", ...rest] = trimmed.split(/\s+/);
1111
+ if (SHELL_BIN_DIR.test(head)) return false;
1112
+ return rest.every((tok) => !/^[-/~$]/.test(tok) && !tok.includes("="));
1113
+ }
1114
+ function looksLikeFilePath(value) {
1115
+ const trimmed = value.trim();
1116
+ if (!trimmed || /[\r\n;|&<>]/.test(trimmed)) return false;
1117
+ const rooted = trimmed.startsWith("/") || trimmed.startsWith("~/") || /^[A-Za-z]:[\\/]/.test(trimmed);
1118
+ if (/\s/.test(trimmed)) return rooted && rootedPathWithSpaces(trimmed);
1119
+ if (rooted) return true;
1120
+ return trimmed.includes("/") || trimmed.includes("\\");
1121
+ }
1122
+ function stripWorktreePrefix(path, worktreePath) {
1123
+ if (!worktreePath) return path;
1124
+ const prefix = worktreePath.replace(/[/\\]+$/, "");
1125
+ if (path === prefix || path === `${prefix}/`) return "";
1126
+ if (path.startsWith(`${prefix}/`)) return path.slice(prefix.length + 1);
1127
+ return path;
1128
+ }
1129
+ function visibleToolRowDetail(detail, description, worktreePath) {
1130
+ if (!detail?.trim()) return void 0;
1131
+ const raw = detail.trim();
1132
+ const desc = (description ?? "").trim();
1133
+ if (!looksLikeFilePath(raw)) {
1134
+ const clipped = clipChatLine(raw);
1135
+ if (!clipped || desc === clipped || desc === raw) return void 0;
1136
+ return clipped;
944
1137
  }
945
- if (event.type === "thinking") {
946
- const data = event.data;
947
- if (!data) return parts;
948
- const next = [...parts];
949
- if (event.replace) {
950
- for (let i = next.length - 1; i >= 0; i--) {
951
- const prev = next[i];
952
- if (prev?.type === "thinking" && sameParentId(prev.parentId, event.parentId)) {
953
- next[i] = withPartTimes(
954
- {
955
- type: "thinking",
956
- text: clipThinkingForStore(data),
957
- ...event.parentId ? { parentId: event.parentId } : {}
958
- },
959
- prev
960
- );
961
- return next;
962
- }
963
- }
964
- next.push(
965
- withPartTimes({
966
- type: "thinking",
967
- text: clipThinkingForStore(data),
968
- ...event.parentId ? { parentId: event.parentId } : {}
969
- })
970
- );
971
- return next;
972
- }
973
- const last = next[next.length - 1];
974
- if (last?.type === "thinking" && sameParentId(last.parentId, event.parentId)) {
975
- next[next.length - 1] = withPartTimes(
976
- {
977
- type: "thinking",
978
- text: clipThinkingForStore(last.text + data),
979
- ...event.parentId ? { parentId: event.parentId } : {}
980
- },
981
- last
982
- );
983
- } else {
984
- next.push(
985
- withPartTimes({
986
- type: "thinking",
987
- text: clipThinkingForStore(data),
988
- ...event.parentId ? { parentId: event.parentId } : {}
989
- })
990
- );
991
- }
992
- return next;
1138
+ const rel = stripWorktreePrefix(raw, worktreePath);
1139
+ if (!rel) return void 0;
1140
+ const base = fileBasename(rel);
1141
+ if (desc && (desc === base || desc.endsWith(` ${base}`))) return void 0;
1142
+ if (rel.length <= 42) return rel;
1143
+ const parts = rel.split(/[/\\]/).filter(Boolean);
1144
+ if (parts.length >= 2) return `\u2026/${parts.slice(-2).join("/")}`;
1145
+ return base;
1146
+ }
1147
+ function toolDetail(name, input) {
1148
+ if (!input) return void 0;
1149
+ const command = str(input.command) ?? str(input.cmd);
1150
+ if (command) return clipChatLine(command);
1151
+ const pattern = str(input.pattern) ?? str(input.glob) ?? str(input.glob_pattern);
1152
+ const isSearch = /grep|glob|search|ripgrep|findfiles|semsearch/i.test(name);
1153
+ if (isSearch && pattern) {
1154
+ return pattern.length > 80 ? `${pattern.slice(0, 77)}\u2026` : pattern;
993
1155
  }
994
- if (event.type === "tool_use") {
995
- const input = asRecord(event.input);
996
- const diff = diffFromInput(input);
997
- const existing = parts.findIndex((p) => p.type === "tool" && p.id === event.id);
998
- if (existing >= 0) {
999
- const prev = parts[existing];
1000
- const next = [...parts];
1001
- const mergedInput = {
1002
- ...prev.input ?? {},
1003
- ...input ?? {}
1004
- };
1005
- const mergedRecord = Object.keys(mergedInput).length > 0 ? mergedInput : void 0;
1006
- next[existing] = withPartTimes(
1007
- {
1008
- ...prev,
1009
- name: event.name || prev.name,
1010
- input: mergedRecord,
1011
- description: toolDescription(event.name || prev.name, mergedRecord),
1012
- detail: toolDetail(event.name || prev.name, mergedRecord) ?? prev.detail,
1013
- filePath: toolFilePath(mergedRecord) ?? prev.filePath,
1014
- additions: diff.additions ?? prev.additions,
1015
- deletions: diff.deletions ?? prev.deletions,
1016
- parentId: event.parentId ?? prev.parentId
1017
- },
1018
- prev
1019
- );
1020
- return next;
1021
- }
1022
- return [
1023
- ...parts,
1024
- withPartTimes({
1025
- type: "tool",
1026
- id: event.id,
1027
- name: event.name,
1028
- input,
1029
- description: toolDescription(event.name, input),
1030
- detail: toolDetail(event.name, input),
1031
- status: "running",
1032
- filePath: toolFilePath(input),
1033
- ...event.parentId ? { parentId: event.parentId } : {},
1034
- ...diff
1035
- })
1036
- ];
1156
+ const path = str(input.file_path) ?? str(input.path) ?? str(input.filePath) ?? str(input.filename);
1157
+ if (path) return path;
1158
+ if (pattern) return pattern.length > 80 ? `${pattern.slice(0, 77)}\u2026` : pattern;
1159
+ const query = str(input.query) ?? str(input.prompt);
1160
+ if (query) return query.length > 80 ? `${query.slice(0, 77)}\u2026` : query;
1161
+ try {
1162
+ const raw = JSON.stringify(input);
1163
+ return raw.length > 80 ? `${raw.slice(0, 77)}\u2026` : raw;
1164
+ } catch {
1165
+ return name;
1037
1166
  }
1038
- if (event.type === "tool_result") {
1039
- const existing = parts.findIndex((p) => p.type === "tool" && p.id === event.id);
1040
- const fromResult = parseDiffStat(event.content);
1041
- if (event.partial) {
1042
- if (existing < 0) {
1043
- return [
1044
- ...parts,
1045
- withPartTimes({
1046
- type: "tool",
1047
- id: event.id,
1048
- name: "tool",
1049
- description: "tool",
1050
- status: event.isError ? "error" : "running",
1051
- result: clipToolResultForStore(event.content),
1052
- ...event.parentId ? { parentId: event.parentId } : {}
1053
- })
1054
- ];
1055
- }
1056
- return parts.map((p, i) => {
1057
- if (i !== existing || p.type !== "tool") return p;
1058
- if (p.status === "done" || p.status === "error") return p;
1059
- return withPartTimes(
1060
- {
1061
- ...p,
1062
- result: clipToolResultForStore(event.content) ?? p.result
1063
- },
1064
- p
1065
- );
1066
- });
1167
+ }
1168
+ function toolDescription(name, input) {
1169
+ const n = name.replace(/^mcp__/, "").replace(/__/g, " \xB7 ");
1170
+ if (/^get_record_types$|recordTypes/i.test(name)) return "List record types";
1171
+ if (/connectedAgentRequest/i.test(name)) {
1172
+ return str(input?.agent_id) ? `Ask connected agent` : "Ask connected agent";
1173
+ }
1174
+ if (/present_artifact$/i.test(name)) {
1175
+ return str(input?.title) ? `Present ${str(input?.title)}` : "Present artifact";
1176
+ }
1177
+ if (/present_plan$/i.test(name)) {
1178
+ return str(input?.title) ? `Plan ${str(input?.title)}` : "Present plan";
1179
+ }
1180
+ if (/present_schema$/i.test(name)) {
1181
+ return str(input?.title) ? `Schema ${str(input?.title)}` : "Present schema";
1182
+ }
1183
+ if (/present_files$/i.test(name)) {
1184
+ return str(input?.title) ? `Files ${str(input?.title)}` : "Present files";
1185
+ }
1186
+ if (/wait_for_job$/i.test(name)) {
1187
+ return str(input?.id) ? `Wait for ${str(input?.id)}` : "Wait for job";
1188
+ }
1189
+ if (/stop_job$/i.test(name)) {
1190
+ return str(input?.id) ? `Stop ${str(input?.id)}` : "Stop job";
1191
+ }
1192
+ if (isSubagentToolName(name)) {
1193
+ const desc = str(input?.description);
1194
+ const sub = asRecord(input?.subagentType);
1195
+ const kind = str(sub?.name) ?? str(sub?.kind) ?? str(input?.subagent_type) ?? str(input?.subagentType);
1196
+ if (/^spawn_agent$/i.test(name)) {
1197
+ return str(input?.prompt) ?? desc ?? "Subagent";
1067
1198
  }
1068
- if (existing < 0) {
1069
- return [
1070
- ...parts,
1071
- withPartTimes({
1072
- type: "tool",
1073
- id: event.id,
1074
- name: "tool",
1075
- description: "tool",
1076
- status: event.isError ? "error" : "done",
1077
- result: clipToolResultForStore(event.content),
1078
- ...event.parentId ? { parentId: event.parentId } : {},
1079
- ...fromResult.additions != null ? { additions: fromResult.additions } : {},
1080
- ...fromResult.deletions != null ? { deletions: fromResult.deletions } : {}
1081
- })
1082
- ];
1199
+ if (/connectedAgentRequest/i.test(name)) {
1200
+ return str(input?.agent_id) ? `Ask connected agent` : "Ask connected agent";
1083
1201
  }
1084
- return parts.map((p, i) => {
1085
- if (i !== existing || p.type !== "tool") return p;
1086
- return withPartTimes(
1087
- {
1088
- ...p,
1089
- status: event.isError ? "error" : "done",
1090
- result: clipToolResultForStore(event.content),
1091
- ...fromResult.additions != null ? { additions: fromResult.additions } : {},
1092
- ...fromResult.deletions != null ? { deletions: fromResult.deletions } : {}
1093
- },
1094
- p
1095
- );
1096
- });
1202
+ if (desc && kind) return `${kind}: ${desc}${subagentLiveSuffix(input)}`;
1203
+ if (desc) return `${desc}${subagentLiveSuffix(input)}`;
1204
+ return `Subagent${subagentLiveSuffix(input)}`;
1097
1205
  }
1098
- return parts;
1206
+ if (/^(create|update)_artifact$/i.test(name.replace(/^mcp__[^_]+__/, ""))) {
1207
+ return str(input?.title) ? `Artifact ${str(input?.title)}` : "Artifact";
1208
+ }
1209
+ if (!input) return n;
1210
+ if (/bash|shell|terminal/i.test(name) && str(input.command)) {
1211
+ const cmd = str(input.command);
1212
+ if (/git\s+fetch/i.test(cmd)) return "Fetch latest from origin and check status";
1213
+ if (/git\s+status/i.test(cmd)) return "Check git status";
1214
+ if (/git\s+log/i.test(cmd)) return "Inspect recent commits";
1215
+ if (/git\s+branch/i.test(cmd)) return "List branches";
1216
+ if (/grep|rg\b/i.test(cmd)) return "Search repository";
1217
+ return "Run shell command";
1218
+ }
1219
+ if (/edit|write|apply/i.test(name)) {
1220
+ const path = str(input.file_path) ?? str(input.path);
1221
+ return path ? `Edit ${path.split("/").pop()}` : "Edit file";
1222
+ }
1223
+ if (/read/i.test(name)) {
1224
+ const path = str(input.file_path) ?? str(input.path);
1225
+ return path ? `Read ${path.split("/").pop()}` : "Read file";
1226
+ }
1227
+ if (/grep/i.test(name)) return "Search files";
1228
+ if (/glob/i.test(name)) return "Find files";
1229
+ const compact = n.replace(/[_-]/g, "");
1230
+ if (/^taskoutput$/i.test(compact)) {
1231
+ return str(input.task_id) ? `Wait for ${str(input.task_id)}` : "Wait for background task";
1232
+ }
1233
+ if (/^taskstop$/i.test(compact)) {
1234
+ return str(input.task_id) ? `Stop ${str(input.task_id)}` : "Stop background task";
1235
+ }
1236
+ if (/^monitor$/i.test(compact)) {
1237
+ const command = str(input.command);
1238
+ return command ? `Watch ${command.length > 48 ? `${command.slice(0, 45)}\u2026` : command}` : "Watch command";
1239
+ }
1240
+ return n;
1099
1241
  }
1100
- function partsToAssistantText(parts) {
1101
- return parts.filter(
1102
- (p) => p.type === "text" && !messagePartParentId(p)
1103
- ).map((p) => p.text).join("").trim();
1242
+ function subagentLiveSuffix(input) {
1243
+ if (!input) return "";
1244
+ const bits = [];
1245
+ const status = str(input.live_status);
1246
+ if (status && !/^runn(ing)?$|^working$/i.test(status)) bits.push(status);
1247
+ if (typeof input.live_tool_uses === "number") bits.push(`${input.live_tool_uses} tools`);
1248
+ if (typeof input.live_duration_ms === "number") {
1249
+ bits.push(`${Math.max(0, Math.round(Number(input.live_duration_ms) / 1e3))}s`);
1250
+ }
1251
+ const last = str(input.live_last_tool);
1252
+ if (last) bits.push(last);
1253
+ return bits.length ? ` \xB7 ${bits.join(" \xB7 ")}` : "";
1104
1254
  }
1105
- function lastAssistantMessageText(parts) {
1106
- if (!parts?.length) return "";
1255
+ function isSubagentToolName(name) {
1256
+ const n = (name ?? "").trim();
1257
+ if (/^(task|agent|spawn_agent)$/i.test(n)) return true;
1258
+ return /connectedAgentRequest/i.test(n);
1259
+ }
1260
+ function isInternalAgentStatusText(text) {
1261
+ const t = text.trim();
1262
+ if (!t) return false;
1263
+ if (/waiting for gate to pass/i.test(t)) return true;
1264
+ if (/^agent is running\b/i.test(t) && t.length < 160) return true;
1265
+ return false;
1266
+ }
1267
+ function isPollWrapperToolName(name) {
1268
+ const n = (name ?? "").replace(/[_-]/g, "");
1269
+ return /^(taskoutput|taskstop|sleep)$/i.test(n);
1270
+ }
1271
+ function lastTextPart(parts, type) {
1107
1272
  for (let i = parts.length - 1; i >= 0; i--) {
1108
1273
  const p = parts[i];
1109
- if (!p || p.type !== "text" || messagePartParentId(p)) continue;
1274
+ if (p.type !== type) continue;
1110
1275
  const text = p.text.trim();
1111
1276
  if (!text || isInternalAgentStatusText(text)) continue;
1112
1277
  return text;
1113
1278
  }
1114
1279
  return "";
1115
1280
  }
1116
- function stripBrightsyNdjsonNoise(text) {
1117
- if (!text || !text.includes('"type"')) return text;
1118
- let out = text;
1119
- out = out.replace(
1120
- /\{"type":"(?:tool_use|tool_result|tool|thinking|usage|done|error)"[\s\S]*?\}\s*(?=\{"type":"|$|(?=[A-Za-z*#]))/g,
1121
- ""
1122
- );
1123
- out = out.replace(/\{"type":"(?:tool_use|tool_result|tool)"[\s\S]*$/g, "");
1124
- return out.trim();
1125
- }
1126
- function isBrightsyNdjsonLine(line) {
1127
- const t = line.trim();
1128
- if (!t.startsWith("{")) return false;
1129
- return /"type"\s*:\s*"(tool_use|tool_result|tool|thinking|usage|done|error|text)"/.test(t);
1130
- }
1131
- function finalizeParts(parts) {
1132
- return parts.map(
1133
- (p) => p.type === "tool" && p.status === "running" ? { ...p, status: "done" } : p
1134
- );
1135
- }
1136
- function normalizeParseResult(parsed) {
1137
- if (!parsed) return [];
1138
- return Array.isArray(parsed) ? parsed : [parsed];
1139
- }
1140
-
1141
- // src/agents/injected-mcp.ts
1142
- import { existsSync as existsSync2, mkdtempSync, writeFileSync } from "fs";
1143
- import { createRequire } from "module";
1144
- import { tmpdir } from "os";
1145
- import { dirname, join } from "path";
1146
- import { fileURLToPath } from "url";
1147
-
1148
- // src/mcp/profile.ts
1149
- var SIDEBOARD_MCP_PROFILE_ENV = "SIDEBOARD_MCP_PROFILE";
1150
- var WORKTREE_MCP_TOOLS = [
1151
- "present_artifact",
1152
- "ask_user",
1153
- "present_plan",
1154
- "present_schema",
1155
- "present_files",
1156
- "wait_for_job",
1157
- "stop_job",
1158
- "get_viewer_context",
1159
- "update_viewer_context"
1160
- ];
1161
- var WORKTREE_GITHUB_MCP_TOOLS = [
1162
- "github_search_issues",
1163
- "github_get_issue",
1164
- "github_comment",
1165
- "github_update_issue",
1166
- "github_create_issue"
1167
- ];
1168
- var WORKTREE_LINEAR_MCP_TOOLS = [
1169
- "linear_list_teams",
1170
- "linear_search_issues",
1171
- "linear_get_issue",
1172
- "linear_create_issue",
1173
- "linear_update_issue",
1174
- "linear_comment"
1175
- ];
1176
- var WORKTREE_ABLETIME_MCP_TOOLS = [
1177
- "abletime_orientation",
1178
- "abletime_list_projects",
1179
- "abletime_list_tasks",
1180
- "abletime_search_tasks",
1181
- "abletime_get_task",
1182
- "abletime_comment",
1183
- "abletime_update_task",
1184
- "abletime_create_task",
1185
- "abletime_ensure_task"
1186
- ];
1187
- function sideboardMcpProfile(env = process.env) {
1188
- return env[SIDEBOARD_MCP_PROFILE_ENV]?.trim().toLowerCase() === "worktree" ? "worktree" : "orchestration";
1281
+ function toolLabel(tool) {
1282
+ return tool.description || toolDescription(tool.name, tool.input) || tool.name;
1189
1283
  }
1190
-
1191
- // src/agents/injected-mcp.ts
1192
- var SIDEBOARD_MCP_ALLOWED_TOOLS = [
1193
- "mcp__sideboard",
1194
- "mcp__sideboard__*"
1195
- ];
1196
- var SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS = [
1197
- "mcp__sideboard__present_artifact",
1198
- "mcp__sideboard__present_schema",
1199
- "mcp__sideboard__present_files",
1200
- "mcp__sideboard__ask_user",
1201
- "mcp__sideboard__present_plan",
1202
- "mcp__sideboard__wait_for_job",
1203
- "mcp__sideboard__stop_job",
1204
- "mcp__sideboard__get_viewer_context",
1205
- "mcp__sideboard__update_viewer_context"
1206
- ];
1207
- var SIDEBOARD_GITHUB_MCP_ALLOWED_TOOLS = ["mcp__sideboard__github_*"];
1208
- var SIDEBOARD_LINEAR_MCP_ALLOWED_TOOLS = ["mcp__sideboard__linear_*"];
1209
- var SIDEBOARD_ABLETIME_MCP_ALLOWED_TOOLS = ["mcp__sideboard__abletime_*"];
1210
- function sideboardWorktreeAllowedTools(opts) {
1211
- const out = [...SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS];
1212
- if (opts?.github !== false) out.push(...SIDEBOARD_GITHUB_MCP_ALLOWED_TOOLS);
1213
- if (opts?.linear) out.push(...SIDEBOARD_LINEAR_MCP_ALLOWED_TOOLS);
1214
- if (opts?.abletime) out.push(...SIDEBOARD_ABLETIME_MCP_ALLOWED_TOOLS);
1215
- return out;
1284
+ function fileBasename(path) {
1285
+ const parts = path.replace(/\/$/, "").split(/[/\\]/);
1286
+ return parts[parts.length - 1] || path;
1216
1287
  }
1217
- var BRIGHTSY_MCP_ALLOWED_TOOLS = [
1218
- "mcp__brightsy",
1219
- "mcp__brightsy__*"
1220
- ];
1221
- var brightsyMcpCommandCache = null;
1222
- async function resolveBrightsyMcpCommand() {
1223
- const now = Date.now();
1224
- if (brightsyMcpCommandCache && now - brightsyMcpCommandCache.at < 6e4 && brightsyMcpCommandCache.command) {
1225
- return brightsyMcpCommandCache.command;
1226
- }
1227
- const which = await run("which", ["brightsy-mcp"], { reject: false });
1228
- const command = which.exitCode === 0 && which.stdout.trim() ? "brightsy-mcp" : "npx";
1229
- brightsyMcpCommandCache = { at: now, command };
1230
- return command;
1288
+ function classifyTool(name) {
1289
+ const compact = name.replace(/^mcp__[^_]+__/, "").replace(/[_-]/g, "");
1290
+ if (/bash|shell|terminal|^zsh$|^sh$/i.test(compact)) return "shell";
1291
+ if (/grep|glob|search|ripgrep|findfiles|semsearch/i.test(compact)) return "search";
1292
+ if (/^(read|cat)$/i.test(compact) || /^read/i.test(compact)) return "read";
1293
+ if (/edit|write|apply|strreplace|multiedit|updatefile|createfile/i.test(compact)) {
1294
+ return "edit";
1295
+ }
1296
+ return "other";
1231
1297
  }
1232
- function isBrightsyConnected() {
1233
- try {
1234
- loadBrightsyConfig();
1298
+ function isShellToolName(name) {
1299
+ return classifyTool(name ?? "") === "shell";
1300
+ }
1301
+ function toolActivityLine(parts) {
1302
+ const tools = parts.filter((p) => {
1303
+ if (p.type !== "tool" || p.parentId) return false;
1304
+ if (isPollWrapperToolName(p.name)) return false;
1305
+ if (/present_plan$/i.test(p.name ?? "")) return false;
1306
+ if (/ask_user|AskUserQuestion/i.test(p.name ?? "")) return false;
1235
1307
  return true;
1236
- } catch {
1237
- return false;
1308
+ });
1309
+ if (tools.length === 0) return null;
1310
+ const edited = [];
1311
+ let reads = 0;
1312
+ let searches = 0;
1313
+ let shells = 0;
1314
+ let others = 0;
1315
+ let additions = 0;
1316
+ let deletions = 0;
1317
+ for (const tool of tools) {
1318
+ const kind = classifyTool(tool.name);
1319
+ if (kind === "edit") {
1320
+ const path = tool.filePath ?? toolFilePath(tool.input);
1321
+ edited.push({
1322
+ name: path ? fileBasename(path) : tool.name,
1323
+ running: tool.status === "running"
1324
+ });
1325
+ } else if (kind === "read") reads += 1;
1326
+ else if (kind === "search") searches += 1;
1327
+ else if (kind === "shell") shells += 1;
1328
+ else others += 1;
1329
+ if (typeof tool.additions === "number") additions += tool.additions;
1330
+ if (typeof tool.deletions === "number") deletions += tool.deletions;
1331
+ }
1332
+ const bits = [];
1333
+ const editing = edited.filter((e) => e.running);
1334
+ const editedDone = edited.filter((e) => !e.running);
1335
+ if (editing.length === 1) bits.push(`Editing ${editing[0].name}`);
1336
+ else if (editing.length > 1) bits.push(`Editing ${editing.length} files`);
1337
+ if (editedDone.length === 1) bits.push(`${editing.length ? "edited" : "Edited"} ${editedDone[0].name}`);
1338
+ else if (editedDone.length > 1) {
1339
+ bits.push(`${editing.length ? "edited" : "Edited"} ${editedDone.length} files`);
1238
1340
  }
1341
+ if (reads === 1) bits.push("explored 1 file");
1342
+ else if (reads > 1) bits.push(`explored ${reads} files`);
1343
+ if (searches === 1) bits.push("1 search");
1344
+ else if (searches > 1) bits.push(`${searches} searches`);
1345
+ if (shells === 1) bits.push("ran 1 command");
1346
+ else if (shells > 1) bits.push(`ran ${shells} commands`);
1347
+ if (others === 1) bits.push("1 tool");
1348
+ else if (others > 1) bits.push(`${others} tools`);
1349
+ if (bits.length === 0) return null;
1350
+ return { text: bits.join(", "), additions, deletions };
1239
1351
  }
1240
- var BRIGHTSY_WORD = /(?<![a-z0-9_-])brightsy(?![a-z0-9_-])/i;
1241
- function promptMentionsBrightsy(text) {
1242
- return BRIGHTSY_WORD.test(text ?? "");
1352
+ function liveActivitySummary(parts, opts) {
1353
+ if (opts?.queued && parts.length === 0) {
1354
+ return "Queued \u2014 waiting for a slot";
1355
+ }
1356
+ const tools = parts.filter((p) => p.type === "tool");
1357
+ const runningSubs = tools.filter(
1358
+ (t) => t.status === "running" && !t.parentId && isSubagentToolName(t.name)
1359
+ );
1360
+ const runningNested = [...tools].reverse().find((t) => t.status === "running" && t.parentId && !isPollWrapperToolName(t.name));
1361
+ const runningTop = [...tools].reverse().find(
1362
+ (t) => t.status === "running" && !t.parentId && !isPollWrapperToolName(t.name) && !isSubagentToolName(t.name)
1363
+ );
1364
+ const runningPoll = [...tools].reverse().find((t) => t.status === "running" && isPollWrapperToolName(t.name));
1365
+ const thinking = lastTextPart(parts, "thinking");
1366
+ const text = lastTextPart(parts, "text");
1367
+ if (runningSubs.length > 0) {
1368
+ const heads = runningSubs.map(toolLabel);
1369
+ const head = runningSubs.length === 1 ? heads[0] : `${runningSubs.length} subagents \xB7 ${heads.slice(0, 2).join(" \xB7 ")}`;
1370
+ if (runningNested) return `${head} \xB7 ${toolLabel(runningNested)}`;
1371
+ return head;
1372
+ }
1373
+ if (runningTop) return toolLabel(runningTop);
1374
+ if (runningPoll) return toolLabel(runningPoll);
1375
+ if (thinking) return thinking.length > 96 ? `\u2026${thinking.slice(-96)}` : thinking;
1376
+ if (text) return "Writing reply\u2026";
1377
+ const last = [...tools].reverse().find((t) => !isPollWrapperToolName(t.name)) ?? tools.at(-1);
1378
+ if (last) {
1379
+ const label = toolLabel(last);
1380
+ return last.status === "running" ? label : `Finished ${label}`;
1381
+ }
1382
+ return "Working\u2026";
1243
1383
  }
1244
- function isBrightsyMcpToolName(name) {
1245
- const n = name.toLowerCase();
1246
- return n === "brightsy" || n.startsWith("mcp__brightsy") || n.startsWith("brightsy_");
1384
+ function messagePartParentId(part) {
1385
+ if ("parentId" in part && typeof part.parentId === "string" && part.parentId.trim()) {
1386
+ return part.parentId;
1387
+ }
1388
+ return void 0;
1247
1389
  }
1248
- function toolInputUsesBrightsyDatasource(input) {
1249
- const ds = input?.datasource;
1250
- return typeof ds === "string" && ds.toLowerCase() === "brightsy";
1390
+ function sameParentId(a, b) {
1391
+ return (a ?? "") === (b ?? "");
1251
1392
  }
1252
- function messageUsedBrightsyMcp(msg) {
1253
- if (msg.role === "user" && promptMentionsBrightsy(msg.text)) return true;
1254
- for (const part of msg.parts ?? []) {
1255
- if (part.type !== "tool") continue;
1256
- if (isBrightsyMcpToolName(part.name)) return true;
1257
- if (/present_(schema|files)$/i.test(part.name) && toolInputUsesBrightsyDatasource(part.input)) {
1258
- return true;
1259
- }
1393
+ function withEventParentId(event, parentId) {
1394
+ if (!parentId) return event;
1395
+ if (event.type === "stdout" || event.type === "thinking" || event.type === "tool_use" || event.type === "tool_result") {
1396
+ return { ...event, parentId: event.parentId ?? parentId };
1260
1397
  }
1261
- return false;
1262
- }
1263
- function threadRequestsBrightsyMcp(thread) {
1264
- return (thread?.messages ?? []).some(messageUsedBrightsyMcp);
1398
+ return event;
1265
1399
  }
1266
- function shouldInjectBrightsyMcp(thread, opts) {
1267
- if (!(opts?.connected ?? isBrightsyConnected())) return false;
1268
- if (opts?.orchestrator) return true;
1269
- const alwaysOn = opts?.alwaysOnWorktree ?? Boolean(loadAppSettings().brightsy?.injectWorktreeMcp);
1270
- if (alwaysOn) return true;
1271
- return threadRequestsBrightsyMcp(thread);
1400
+ function withEventsParentId(events, parentId) {
1401
+ if (!parentId) return events;
1402
+ return events.map((event) => withEventParentId(event, parentId));
1272
1403
  }
1273
- function mcpLaunch(cmd, name, env) {
1274
- if (cmd === "brightsy-mcp") {
1275
- return { name, command: "brightsy-mcp", ...env ? { env } : {} };
1276
- }
1277
- return {
1278
- name,
1279
- command: "npx",
1280
- args: ["-y", "@brightsy/mcp-server"],
1281
- ...env ? { env } : {}
1282
- };
1404
+ function toolFilePath(input) {
1405
+ if (!input) return void 0;
1406
+ return str(input.file_path) ?? str(input.path) ?? str(input.filePath) ?? str(input.filename);
1283
1407
  }
1284
- function teamEnv(team) {
1285
- const endpoint = (team.endpoint || "https://brightsy.ai").replace(/\/$/, "");
1286
- return {
1287
- BRIGHTSY_API_TOKEN: team.access_token,
1288
- ...team.refresh_token ? { BRIGHTSY_REFRESH_TOKEN: team.refresh_token } : {},
1289
- BRIGHTSY_ACCOUNT_ID: team.id,
1290
- BRIGHTSY_API_URL: endpoint
1291
- };
1408
+ function countLines(text) {
1409
+ if (!text) return 0;
1410
+ return text.split("\n").length;
1292
1411
  }
1293
- function brightsyMcpAllowedTools(serverNames) {
1294
- const out = [];
1295
- for (const name of serverNames) {
1296
- out.push(`mcp__${name}`, `mcp__${name}__*`);
1412
+ function diffFromInput(input) {
1413
+ if (!input) return {};
1414
+ const oldS = str(input.old_string) ?? str(input.oldString);
1415
+ const newS = str(input.new_string) ?? str(input.newString) ?? str(input.content);
1416
+ if (oldS != null || newS != null) {
1417
+ return {
1418
+ additions: countLines(newS),
1419
+ deletions: countLines(oldS)
1420
+ };
1297
1421
  }
1298
- return out;
1422
+ return {};
1299
1423
  }
1300
- function corePackageDir() {
1301
- const cjsDir = typeof __dirname !== "undefined" ? __dirname : "";
1302
- if (cjsDir) return cjsDir;
1303
- try {
1304
- const url = import.meta.url;
1305
- if (typeof url === "string" && url.length > 0) {
1306
- return dirname(fileURLToPath(url));
1307
- }
1308
- } catch {
1424
+ function parseDiffStat(result) {
1425
+ if (!result) return {};
1426
+ const paired = result.match(/\+(\d+)\s+-(\d+)/);
1427
+ if (paired) {
1428
+ return {
1429
+ additions: Number(paired[1]),
1430
+ deletions: Number(paired[2])
1431
+ };
1309
1432
  }
1310
- try {
1311
- const req = createRequire(join(process.cwd(), "package.json"));
1312
- return dirname(req.resolve("@sideboard-ai/core"));
1313
- } catch {
1314
- return process.cwd();
1433
+ const verbose = result.match(
1434
+ /(\d+)\s+insertions?(?:,\s*(\d+)\s+deletions?)?/i
1435
+ );
1436
+ if (verbose) {
1437
+ return {
1438
+ additions: Number(verbose[1]),
1439
+ deletions: verbose[2] != null ? Number(verbose[2]) : void 0
1440
+ };
1315
1441
  }
1442
+ return {};
1316
1443
  }
1317
- function findSideboardMcpJsEntry() {
1318
- const override = process.env.SIDEBOARD_MCP_ENTRY?.trim() || process.env.SIDEBOARD_CLI?.trim();
1319
- if (override && existsSync2(override)) return override;
1320
- const packaged = packagedMcpStdioPath();
1321
- if (packaged) return packaged;
1322
- let dir = corePackageDir();
1323
- for (let i = 0; i < 10; i++) {
1324
- const candidates = [
1325
- join(dir, "mcp/run-stdio.js"),
1326
- join(dir, "mcp/run-stdio.cjs"),
1327
- join(dir, "dist/mcp/run-stdio.js"),
1328
- join(dir, "dist/mcp/run-stdio.cjs"),
1329
- join(dir, "packages/core/dist/mcp/run-stdio.js"),
1330
- join(dir, "packages/cli/dist/index.js"),
1331
- join(dir, "cli/dist/index.js")
1332
- ];
1333
- for (const p of candidates) {
1334
- if (existsSync2(p) && !isAsarPath(p)) return p;
1335
- }
1336
- const parent = dirname(dir);
1337
- if (parent === dir) break;
1338
- dir = parent;
1339
- }
1340
- return null;
1444
+ var THINKING_PART_MAX_CHARS = 64e3;
1445
+ function clipThinkingForStore(text, max = THINKING_PART_MAX_CHARS) {
1446
+ if (text.length <= max) return text;
1447
+ const marker = "\u2026(earlier thinking trimmed)\n";
1448
+ return marker + text.slice(text.length - Math.max(1, max - marker.length));
1341
1449
  }
1342
- async function resolveSideboardMcpServer() {
1343
- const entry = findSideboardMcpJsEntry();
1344
- if (entry) {
1345
- const isCli = /[/\\]cli[/\\]dist[/\\]index\.js$/.test(entry);
1346
- const scriptArgs = isCli ? [entry, "mcp"] : [entry];
1347
- const launch = applyNodeLaunch(await resolveNodeLaunch(entry), scriptArgs);
1348
- if (launch.file !== "/bin/sh" && !isElectronLikeCommand(launch.file)) {
1349
- return {
1350
- name: "sideboard",
1351
- command: launch.file,
1352
- args: launch.args,
1353
- ...Object.keys(launch.env).length > 0 ? { env: launch.env } : {}
1354
- };
1355
- }
1450
+ var STDOUT_EVENT_MAX_CHARS = 16e3;
1451
+ var STDERR_EVENT_MAX_CHARS = 4e3;
1452
+ function clipEventTail(text, max) {
1453
+ if (text.length <= max) return text;
1454
+ const marker = "\u2026(truncated)\n";
1455
+ return marker + text.slice(text.length - Math.max(1, max - marker.length));
1456
+ }
1457
+ function clipAgentEventForPaint(event) {
1458
+ if (event.type === "tool_result" && event.content) {
1459
+ const content = clipToolResultForStore(event.content);
1460
+ if (content === event.content) return event;
1461
+ return { ...event, content };
1356
1462
  }
1357
- const which = await run("which", ["sideboard"], { reject: false });
1358
- if (which.exitCode === 0 && which.stdout.trim()) {
1359
- return { name: "sideboard", command: which.stdout.trim(), args: ["mcp"] };
1463
+ if (event.type === "thinking" && event.data.length > THINKING_PART_MAX_CHARS) {
1464
+ return { ...event, data: clipThinkingForStore(event.data) };
1360
1465
  }
1361
- return { name: "sideboard", command: "sideboard", args: ["mcp"] };
1466
+ if (event.type === "stdout" && event.data.length > STDOUT_EVENT_MAX_CHARS) {
1467
+ return { ...event, data: clipEventTail(event.data, STDOUT_EVENT_MAX_CHARS) };
1468
+ }
1469
+ if (event.type === "stderr" && event.data.length > STDERR_EVENT_MAX_CHARS) {
1470
+ return { ...event, data: clipEventTail(event.data, STDERR_EVENT_MAX_CHARS) };
1471
+ }
1472
+ return event;
1362
1473
  }
1363
- async function buildInjectedMcpServers(opts) {
1364
- const servers = [];
1365
- if (opts.includeSideboard) {
1366
- const sideboard = await resolveSideboardMcpServer();
1367
- const orchId = opts.orchestratorThreadId?.trim();
1368
- const profile = orchId ? "orchestration" : "worktree";
1369
- sideboard.env = {
1370
- ...sideboard.env ?? {},
1371
- SIDEBOARD_APP_DATA: appDataDir(),
1372
- [SIDEBOARD_MCP_PROFILE_ENV]: profile
1373
- };
1374
- if (orchId) {
1375
- sideboard.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID = orchId;
1376
- }
1377
- try {
1378
- mergeAgentGitAuthEnv(
1379
- sideboard.env,
1380
- await resolveAgentGitAuthEnv(sideboard.env)
1474
+ function withPartTimes(next, prev) {
1475
+ const now = Date.now();
1476
+ const startedAt = prev && typeof prev.startedAt === "number" ? prev.startedAt : now;
1477
+ return { ...next, startedAt, updatedAt: now };
1478
+ }
1479
+ function applyAgentEvent(parts, event) {
1480
+ if (event.type === "stdout") {
1481
+ const data = event.data;
1482
+ if (!data) return parts;
1483
+ const next = [...parts];
1484
+ const last = next[next.length - 1];
1485
+ if (last?.type === "text" && sameParentId(last.parentId, event.parentId)) {
1486
+ next[next.length - 1] = withPartTimes(
1487
+ {
1488
+ type: "text",
1489
+ text: last.text + data,
1490
+ ...event.parentId ? { parentId: event.parentId } : {}
1491
+ },
1492
+ last
1493
+ );
1494
+ } else {
1495
+ next.push(
1496
+ withPartTimes({
1497
+ type: "text",
1498
+ text: data,
1499
+ ...event.parentId ? { parentId: event.parentId } : {}
1500
+ })
1381
1501
  );
1382
- } catch {
1383
1502
  }
1384
- applyAgentRunnerHeapEnv(sideboard.env);
1385
- servers.push(sideboard);
1503
+ return next;
1386
1504
  }
1387
- if (opts.includeBrightsy && isBrightsyConnected()) {
1388
- const cmd = await resolveBrightsyMcpCommand();
1389
- const teams = await ensureConnectedBrightsyTeamTokens();
1390
- if (teams.length > 0) {
1391
- const used = /* @__PURE__ */ new Set();
1392
- for (const team of teams) {
1393
- let name = brightsyMcpServerName(team.slug);
1394
- if (used.has(name)) name = `${name}_${team.id.slice(0, 8)}`;
1395
- used.add(name);
1396
- servers.push(mcpLaunch(cmd, name, teamEnv(team)));
1505
+ if (event.type === "thinking") {
1506
+ const data = event.data;
1507
+ if (!data) return parts;
1508
+ const next = [...parts];
1509
+ if (event.replace) {
1510
+ for (let i = next.length - 1; i >= 0; i--) {
1511
+ const prev = next[i];
1512
+ if (prev?.type === "thinking" && sameParentId(prev.parentId, event.parentId)) {
1513
+ next[i] = withPartTimes(
1514
+ {
1515
+ type: "thinking",
1516
+ text: clipThinkingForStore(data),
1517
+ ...event.parentId ? { parentId: event.parentId } : {}
1518
+ },
1519
+ prev
1520
+ );
1521
+ return next;
1522
+ }
1397
1523
  }
1524
+ next.push(
1525
+ withPartTimes({
1526
+ type: "thinking",
1527
+ text: clipThinkingForStore(data),
1528
+ ...event.parentId ? { parentId: event.parentId } : {}
1529
+ })
1530
+ );
1531
+ return next;
1532
+ }
1533
+ const last = next[next.length - 1];
1534
+ if (last?.type === "thinking" && sameParentId(last.parentId, event.parentId)) {
1535
+ next[next.length - 1] = withPartTimes(
1536
+ {
1537
+ type: "thinking",
1538
+ text: clipThinkingForStore(last.text + data),
1539
+ ...event.parentId ? { parentId: event.parentId } : {}
1540
+ },
1541
+ last
1542
+ );
1398
1543
  } else {
1399
- servers.push(mcpLaunch(cmd, "brightsy"));
1544
+ next.push(
1545
+ withPartTimes({
1546
+ type: "thinking",
1547
+ text: clipThinkingForStore(data),
1548
+ ...event.parentId ? { parentId: event.parentId } : {}
1549
+ })
1550
+ );
1400
1551
  }
1552
+ return next;
1401
1553
  }
1402
- return servers;
1403
- }
1404
- function cursorSafeMcpLaunch(command, args) {
1405
- if (process.platform === "win32") {
1406
- return args && args.length > 0 ? { command, args } : { command };
1407
- }
1408
- const unwrapped = unwrapStrippedElectronLaunch(command, args);
1409
- const file = unwrapped?.file ?? command;
1410
- const fileArgs = unwrapped?.args ?? args ?? [];
1411
- return fileArgs.length > 0 ? { command: file, args: fileArgs } : { command: file };
1412
- }
1413
- function mcpSpawnEnv(env) {
1414
- if (!env) return void 0;
1415
- const out = { ...env };
1416
- delete out.ELECTRON_RUN_AS_NODE;
1417
- delete out.ELECTRON_RUN_AS_NODE;
1418
- return Object.keys(out).length > 0 ? out : void 0;
1419
- }
1420
- function toCursorMcpServers(servers) {
1421
- const out = {};
1422
- for (const s of servers) {
1423
- const env = mcpSpawnEnv(s.env);
1424
- const launch = cursorSafeMcpLaunch(s.command, s.args);
1425
- out[s.name] = {
1426
- type: "stdio",
1427
- command: launch.command,
1428
- ...launch.args && launch.args.length > 0 ? { args: launch.args } : {},
1429
- ...env ? { env } : {}
1430
- };
1431
- }
1432
- return out;
1433
- }
1434
- function toCodexMcpConfigArgs(servers) {
1435
- const args = [];
1436
- for (const s of servers) {
1437
- const prefix = `mcp_servers.${s.name}`;
1438
- args.push("-c", `${prefix}.command=${JSON.stringify(s.command)}`);
1439
- if (s.args?.length) {
1440
- args.push("-c", `${prefix}.args=${JSON.stringify(s.args)}`);
1554
+ if (event.type === "tool_use") {
1555
+ const input = asRecord(event.input);
1556
+ const diff = diffFromInput(input);
1557
+ const existing = parts.findIndex((p) => p.type === "tool" && p.id === event.id);
1558
+ if (existing >= 0) {
1559
+ const prev = parts[existing];
1560
+ const next = [...parts];
1561
+ const mergedInput = {
1562
+ ...prev.input ?? {},
1563
+ ...input ?? {}
1564
+ };
1565
+ const mergedRecord = Object.keys(mergedInput).length > 0 ? mergedInput : void 0;
1566
+ next[existing] = withPartTimes(
1567
+ {
1568
+ ...prev,
1569
+ name: event.name || prev.name,
1570
+ input: mergedRecord,
1571
+ description: toolDescription(event.name || prev.name, mergedRecord),
1572
+ detail: toolDetail(event.name || prev.name, mergedRecord) ?? prev.detail,
1573
+ filePath: toolFilePath(mergedRecord) ?? prev.filePath,
1574
+ additions: diff.additions ?? prev.additions,
1575
+ deletions: diff.deletions ?? prev.deletions,
1576
+ parentId: event.parentId ?? prev.parentId
1577
+ },
1578
+ prev
1579
+ );
1580
+ return next;
1441
1581
  }
1442
- const env = mcpSpawnEnv(s.env);
1443
- if (env) {
1444
- for (const [key, value] of Object.entries(env)) {
1445
- args.push("-c", `${prefix}.env.${key}=${JSON.stringify(value)}`);
1582
+ return [
1583
+ ...parts,
1584
+ withPartTimes({
1585
+ type: "tool",
1586
+ id: event.id,
1587
+ name: event.name,
1588
+ input,
1589
+ description: toolDescription(event.name, input),
1590
+ detail: toolDetail(event.name, input),
1591
+ status: "running",
1592
+ filePath: toolFilePath(input),
1593
+ ...event.parentId ? { parentId: event.parentId } : {},
1594
+ ...diff
1595
+ })
1596
+ ];
1597
+ }
1598
+ if (event.type === "tool_result") {
1599
+ const existing = parts.findIndex((p) => p.type === "tool" && p.id === event.id);
1600
+ const fromResult = parseDiffStat(event.content);
1601
+ if (event.partial) {
1602
+ if (existing < 0) {
1603
+ return [
1604
+ ...parts,
1605
+ withPartTimes({
1606
+ type: "tool",
1607
+ id: event.id,
1608
+ name: "tool",
1609
+ description: "tool",
1610
+ status: event.isError ? "error" : "running",
1611
+ result: clipToolResultForStore(event.content),
1612
+ ...event.parentId ? { parentId: event.parentId } : {}
1613
+ })
1614
+ ];
1446
1615
  }
1616
+ return parts.map((p, i) => {
1617
+ if (i !== existing || p.type !== "tool") return p;
1618
+ if (p.status === "done" || p.status === "error") return p;
1619
+ return withPartTimes(
1620
+ {
1621
+ ...p,
1622
+ result: clipToolResultForStore(event.content) ?? p.result
1623
+ },
1624
+ p
1625
+ );
1626
+ });
1447
1627
  }
1448
- args.push("-c", `${prefix}.default_tools_approval_mode=${JSON.stringify("approve")}`);
1449
- args.push("-c", `${prefix}.tool_timeout_sec=300`);
1450
- args.push("-c", `${prefix}.startup_timeout_sec=30`);
1628
+ if (existing < 0) {
1629
+ return [
1630
+ ...parts,
1631
+ withPartTimes({
1632
+ type: "tool",
1633
+ id: event.id,
1634
+ name: "tool",
1635
+ description: "tool",
1636
+ status: event.isError ? "error" : "done",
1637
+ result: clipToolResultForStore(event.content),
1638
+ ...event.parentId ? { parentId: event.parentId } : {},
1639
+ ...fromResult.additions != null ? { additions: fromResult.additions } : {},
1640
+ ...fromResult.deletions != null ? { deletions: fromResult.deletions } : {}
1641
+ })
1642
+ ];
1643
+ }
1644
+ return parts.map((p, i) => {
1645
+ if (i !== existing || p.type !== "tool") return p;
1646
+ return withPartTimes(
1647
+ {
1648
+ ...p,
1649
+ status: event.isError ? "error" : "done",
1650
+ result: clipToolResultForStore(event.content),
1651
+ ...fromResult.additions != null ? { additions: fromResult.additions } : {},
1652
+ ...fromResult.deletions != null ? { deletions: fromResult.deletions } : {}
1653
+ },
1654
+ p
1655
+ );
1656
+ });
1451
1657
  }
1452
- return args;
1658
+ return parts;
1453
1659
  }
1454
- function toOpencodeMcpConfigContent(servers, opts) {
1455
- const mcp = {};
1456
- for (const s of servers) {
1457
- const env = mcpSpawnEnv(s.env);
1458
- mcp[s.name] = {
1459
- type: "local",
1460
- command: [s.command, ...s.args ?? []],
1461
- enabled: true,
1462
- ...env ? { environment: env } : {}
1463
- };
1464
- }
1465
- for (const name of opts?.disableNames ?? []) {
1466
- const key = name.trim();
1467
- if (!key || mcp[key]?.enabled === true) continue;
1468
- mcp[key] = { ...mcp[key], enabled: false };
1469
- }
1470
- return JSON.stringify({ mcp });
1660
+ function partsToAssistantText(parts) {
1661
+ return parts.filter(
1662
+ (p) => p.type === "text" && !messagePartParentId(p)
1663
+ ).map((p) => p.text).join("").trim();
1471
1664
  }
1472
- function writeMcpServersConfig(servers) {
1473
- if (servers.length === 0) return null;
1474
- const mcpServers = {};
1475
- for (const s of servers) {
1476
- const env = mcpSpawnEnv(s.env);
1477
- mcpServers[s.name] = {
1478
- command: s.command,
1479
- ...s.args ? { args: s.args } : {},
1480
- ...env ? { env } : {}
1481
- };
1665
+ function lastAssistantMessageText(parts) {
1666
+ if (!parts?.length) return "";
1667
+ for (let i = parts.length - 1; i >= 0; i--) {
1668
+ const p = parts[i];
1669
+ if (!p || p.type !== "text" || messagePartParentId(p)) continue;
1670
+ const text = p.text.trim();
1671
+ if (!text || isInternalAgentStatusText(text)) continue;
1672
+ return text;
1482
1673
  }
1483
- const dir = mkdtempSync(join(tmpdir(), "sideboard-mcp-"));
1484
- const cfgPath = join(dir, "mcp.json");
1485
- writeFileSync(cfgPath, JSON.stringify({ mcpServers }, null, 2));
1486
- return cfgPath;
1674
+ return "";
1487
1675
  }
1488
- async function writeInjectedMcpConfig(opts) {
1489
- return writeMcpServersConfig(await buildInjectedMcpServers(opts));
1676
+ function stripBrightsyNdjsonNoise(text) {
1677
+ if (!text || !text.includes('"type"')) return text;
1678
+ let out = text;
1679
+ out = out.replace(
1680
+ /\{"type":"(?:tool_use|tool_result|tool|thinking|usage|done|error)"[\s\S]*?\}\s*(?=\{"type":"|$|(?=[A-Za-z*#]))/g,
1681
+ ""
1682
+ );
1683
+ out = out.replace(/\{"type":"(?:tool_use|tool_result|tool)"[\s\S]*$/g, "");
1684
+ return out.trim();
1685
+ }
1686
+ function isBrightsyNdjsonLine(line) {
1687
+ const t = line.trim();
1688
+ if (!t.startsWith("{")) return false;
1689
+ return /"type"\s*:\s*"(tool_use|tool_result|tool|thinking|usage|done|error|text)"/.test(t);
1690
+ }
1691
+ function finalizeParts(parts) {
1692
+ return parts.map(
1693
+ (p) => p.type === "tool" && p.status === "running" ? { ...p, status: "done" } : p
1694
+ );
1695
+ }
1696
+ function normalizeParseResult(parsed) {
1697
+ if (!parsed) return [];
1698
+ return Array.isArray(parsed) ? parsed : [parsed];
1490
1699
  }
1491
1700
 
1492
1701
  // src/agents/types.ts
@@ -1550,24 +1759,15 @@ var BASE_ALLOWED_TOOLS = [
1550
1759
  "Skill"
1551
1760
  ];
1552
1761
  var CLAUDE_PRINT_BG_WAIT_CEILING_MS = 72e5;
1762
+ function isLocalDevAppDataDir(dir = process.env.SIDEBOARD_APP_DATA) {
1763
+ return /[/\\]\.sideboard[/\\]dev-app-data(?:[/\\]|$)/.test(dir ?? "");
1764
+ }
1553
1765
  var CLAUDE_CHROME_ALLOWED_TOOLS = [
1554
1766
  "mcp__claude-in-chrome",
1555
1767
  "mcp__claude-in-chrome__*",
1556
1768
  "Skill(claude-in-chrome)"
1557
1769
  ];
1558
1770
  var CLAUDE_PROMPT_ARG_MAX = 2e5;
1559
- var mcpListCache = null;
1560
- async function loadMcpServers() {
1561
- if (mcpListCache && Date.now() - mcpListCache.at < 3e4) {
1562
- return mcpListCache.servers;
1563
- }
1564
- const claude = resolveClaudeExecutable();
1565
- const mcpText = await run(claude, ["mcp", "list"], { reject: false });
1566
- const servers = parseMcpList(`${mcpText.stdout}
1567
- ${mcpText.stderr}`);
1568
- mcpListCache = { at: Date.now(), servers };
1569
- return servers;
1570
- }
1571
1771
  function usageFromClaude(usage) {
1572
1772
  if (!usage) return null;
1573
1773
  const inputTokens = Number(usage.input_tokens ?? 0);
@@ -1854,7 +2054,7 @@ var claudeAdapter = {
1854
2054
  async detect() {
1855
2055
  const claude = resolveClaudeExecutable();
1856
2056
  if (claude !== "claude") {
1857
- if (!existsSync3(claude)) {
2057
+ if (!existsSync5(claude)) {
1858
2058
  return {
1859
2059
  agent: "claude",
1860
2060
  installed: false,
@@ -1903,7 +2103,7 @@ var claudeAdapter = {
1903
2103
  );
1904
2104
  }
1905
2105
  const mode = permissionMode(thread);
1906
- const { isOrchestratorThread: isOrchestratorThread2 } = await import("./global-workspace-6P236KKG.js");
2106
+ const { isOrchestratorThread: isOrchestratorThread2 } = await import("./global-workspace-3FDAUNN2.js");
1907
2107
  const isOrchestrator = isOrchestratorThread2(thread);
1908
2108
  const injectedServers = await buildInjectedMcpServers({
1909
2109
  includeSideboard: true,
@@ -1914,6 +2114,7 @@ var claudeAdapter = {
1914
2114
  });
1915
2115
  const injectedBrightsyNames = injectedServers.filter((s) => s.name === "brightsy" || s.name.startsWith("brightsy_")).map((s) => s.name);
1916
2116
  const chromeOn = claudeChromeEnabled();
2117
+ const userMcpEntries = isOrchestrator ? {} : listUserClaudeMcpServerEntries();
1917
2118
  let allowedTools;
1918
2119
  if (isOrchestrator) {
1919
2120
  allowedTools = [
@@ -1922,10 +2123,9 @@ var claudeAdapter = {
1922
2123
  ...brightsyMcpAllowedTools(injectedBrightsyNames)
1923
2124
  ];
1924
2125
  } else {
1925
- const servers = await loadMcpServers();
1926
2126
  allowedTools = [
1927
2127
  ...BASE_ALLOWED_TOOLS,
1928
- ...mcpAllowTools(servers),
2128
+ ...mcpAllowToolsFromNames(Object.keys(userMcpEntries)),
1929
2129
  ...sideboardWorktreeAllowedTools({
1930
2130
  github: true,
1931
2131
  linear: isLinearConnected(),
@@ -1950,12 +2150,10 @@ var claudeAdapter = {
1950
2150
  if (systemPrompt) {
1951
2151
  args.push("--append-system-prompt", systemPrompt);
1952
2152
  }
1953
- const mcpConfigPath = writeMcpServersConfig(injectedServers);
2153
+ const mcpConfigPath = writeMcpServersConfig(injectedServers, userMcpEntries);
1954
2154
  if (mcpConfigPath) {
1955
2155
  args.push("--mcp-config", mcpConfigPath);
1956
- if (isOrchestrator) {
1957
- args.push("--strict-mcp-config");
1958
- }
2156
+ args.push("--strict-mcp-config");
1959
2157
  }
1960
2158
  if (chromeOn) {
1961
2159
  args.push("--chrome");
@@ -1987,8 +2185,10 @@ var claudeAdapter = {
1987
2185
  CLAUDE_CODE_FORWARD_SUBAGENT_TEXT: "1",
1988
2186
  CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS: process.env.CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS ?? String(CLAUDE_PRINT_BG_WAIT_CEILING_MS),
1989
2187
  // `--strict-mcp-config` still fetches claude.ai connectors on some CLI
1990
- // builds (anthropics/claude-code#60252). Coordinators do not need them.
1991
- ...isOrchestrator ? { ENABLE_CLAUDEAI_MCP_SERVERS: "false" } : {}
2188
+ // builds (anthropics/claude-code#60252). Coordinators never need them.
2189
+ // Local `pnpm dev` also disables them: those HTTP connectors hang the
2190
+ // first worktree turn when Electron's cwd is this Sideboard repo.
2191
+ ...isOrchestrator || isLocalDevAppDataDir() ? { ENABLE_CLAUDEAI_MCP_SERVERS: "false" } : {}
1992
2192
  }
1993
2193
  };
1994
2194
  },
@@ -2139,179 +2339,6 @@ function parseIssuesJson(raw) {
2139
2339
  import { existsSync as existsSync6, readFileSync as readFileSync3, statSync } from "fs";
2140
2340
  import { homedir as homedir3 } from "os";
2141
2341
  import { join as join4 } from "path";
2142
-
2143
- // src/agents/orch-mcp-isolation.ts
2144
- import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
2145
- import { homedir as homedir2 } from "os";
2146
- import { join as join3 } from "path";
2147
- import { parse as parseToml } from "smol-toml";
2148
-
2149
- // src/agents/user-mcp-config.ts
2150
- import { existsSync as existsSync4, mkdirSync, readFileSync, writeFileSync as writeFileSync2 } from "fs";
2151
- import { homedir } from "os";
2152
- import { dirname as dirname2, join as join2 } from "path";
2153
- function userCursorMcpConfigPath() {
2154
- return join2(homedir(), ".cursor", "mcp.json");
2155
- }
2156
- function userClaudeMcpConfigPath() {
2157
- return join2(homedir(), ".claude.json");
2158
- }
2159
- function asObject(value) {
2160
- if (!value || typeof value !== "object" || Array.isArray(value)) return {};
2161
- return { ...value };
2162
- }
2163
- function stripElectronSpawnEnv(env) {
2164
- if (!env) return void 0;
2165
- const next = { ...env };
2166
- delete next.ELECTRON_RUN_AS_NODE;
2167
- delete next.ELECTRON_RUN_AS_NODE;
2168
- return Object.keys(next).length > 0 ? next : void 0;
2169
- }
2170
- function mergeSideboardIntoMcpServersJson(existing, sideboard) {
2171
- const root = asObject(existing);
2172
- const servers = asObject(root.mcpServers);
2173
- const env = stripElectronSpawnEnv(sideboard.env);
2174
- servers.sideboard = {
2175
- type: "stdio",
2176
- command: sideboard.command,
2177
- ...sideboard.args && sideboard.args.length > 0 ? { args: sideboard.args } : {},
2178
- ...env ? { env } : {}
2179
- };
2180
- return { ...root, mcpServers: servers };
2181
- }
2182
- function writeMergedMcpServersJson(configPath, sideboard) {
2183
- let existing = {};
2184
- if (existsSync4(configPath)) {
2185
- try {
2186
- existing = JSON.parse(readFileSync(configPath, "utf8"));
2187
- } catch {
2188
- existing = {};
2189
- }
2190
- }
2191
- const next = mergeSideboardIntoMcpServersJson(existing, sideboard);
2192
- mkdirSync(dirname2(configPath), { recursive: true });
2193
- writeFileSync2(configPath, `${JSON.stringify(next, null, 2)}
2194
- `);
2195
- }
2196
- function launchFromResolved(server) {
2197
- return {
2198
- command: server.command,
2199
- args: server.args,
2200
- env: {
2201
- ...server.env ?? {},
2202
- SIDEBOARD_APP_DATA: appDataDir()
2203
- }
2204
- };
2205
- }
2206
- async function registerPackagedUserMcpClients() {
2207
- const launch = launchFromResolved(await resolveSideboardMcpServer());
2208
- const cursor = userCursorMcpConfigPath();
2209
- writeMergedMcpServersJson(cursor, launch);
2210
- const claude = userClaudeMcpConfigPath();
2211
- if (existsSync4(claude)) {
2212
- writeMergedMcpServersJson(claude, launch);
2213
- return { cursor, claude };
2214
- }
2215
- return { cursor };
2216
- }
2217
-
2218
- // src/agents/orch-mcp-isolation.ts
2219
- function isInjectedOrchMcpName(name) {
2220
- const n = name.trim().toLowerCase();
2221
- return n === "sideboard" || n === "brightsy" || n.startsWith("brightsy_");
2222
- }
2223
- function asObject2(value) {
2224
- if (!value || typeof value !== "object" || Array.isArray(value)) return {};
2225
- return value;
2226
- }
2227
- function listMcpNamesFromJsonMap(raw, key) {
2228
- return Object.keys(asObject2(asObject2(raw)[key])).filter((n) => n.trim());
2229
- }
2230
- function listMcpNamesFromCodexToml(text) {
2231
- try {
2232
- const parsed = parseToml(text);
2233
- return Object.keys(asObject2(parsed.mcp_servers)).filter((n) => n.trim());
2234
- } catch {
2235
- return [];
2236
- }
2237
- }
2238
- function readJsonObject(path) {
2239
- try {
2240
- return JSON.parse(readFileSync2(path, "utf8"));
2241
- } catch {
2242
- return {};
2243
- }
2244
- }
2245
- function readText(path) {
2246
- try {
2247
- return readFileSync2(path, "utf8");
2248
- } catch {
2249
- return "";
2250
- }
2251
- }
2252
- function userCodexConfigPaths(home = homedir2()) {
2253
- return [join3(home, ".codex", "config.toml"), join3(home, ".config", "codex", "config.toml")];
2254
- }
2255
- function userOpencodeConfigPaths(home = homedir2()) {
2256
- return [
2257
- join3(home, ".config", "opencode", "opencode.json"),
2258
- join3(home, ".config", "opencode", "opencode.jsonc")
2259
- ];
2260
- }
2261
- function userMcpNamesToDisable(opts) {
2262
- const keep = new Set(
2263
- [...opts.injectedNames ?? []].map((n) => n.trim().toLowerCase()).filter(Boolean)
2264
- );
2265
- const out = [];
2266
- const seen = /* @__PURE__ */ new Set();
2267
- for (const raw of opts.names) {
2268
- const name = raw.trim();
2269
- if (!name) continue;
2270
- const key = name.toLowerCase();
2271
- if (seen.has(key) || keep.has(key) || isInjectedOrchMcpName(name)) continue;
2272
- seen.add(key);
2273
- out.push(name);
2274
- }
2275
- return out;
2276
- }
2277
- function listUserCodexMcpNames(home = homedir2()) {
2278
- const names = [];
2279
- for (const path of userCodexConfigPaths(home)) {
2280
- if (!existsSync5(path)) continue;
2281
- names.push(...listMcpNamesFromCodexToml(readText(path)));
2282
- }
2283
- return names;
2284
- }
2285
- function listUserOpencodeMcpNames(home = homedir2()) {
2286
- const names = [];
2287
- for (const path of userOpencodeConfigPaths(home)) {
2288
- if (!existsSync5(path)) continue;
2289
- names.push(...listMcpNamesFromJsonMap(readJsonObject(path), "mcp"));
2290
- }
2291
- return names;
2292
- }
2293
- function toCodexDisableUserMcpArgs(names) {
2294
- const args = [];
2295
- for (const name of names) {
2296
- if (!/^[A-Za-z0-9_-]+$/.test(name)) continue;
2297
- args.push("-c", `mcp_servers.${name}.enabled=false`);
2298
- }
2299
- return args;
2300
- }
2301
- function toCodexUnattendedAppsArgs() {
2302
- return [
2303
- "-c",
2304
- "features.apps=false",
2305
- "-c",
2306
- "apps._default.enabled=false",
2307
- "-c",
2308
- "apps.posthog.enabled=false",
2309
- "-c",
2310
- "features.plugins=false"
2311
- ];
2312
- }
2313
-
2314
- // src/agents/codex.ts
2315
2342
  var CODEX_PROMPT_ARG_MAX = 2e5;
2316
2343
  var FALLBACK_CODEX_MODELS = [
2317
2344
  { id: "gpt-5.6-sol", displayName: "GPT-5.6 Sol" },
@@ -4056,6 +4083,7 @@ export {
4056
4083
  parseMcpList,
4057
4084
  sanitizeMcpServerName,
4058
4085
  mcpAllowTools,
4086
+ mcpAllowToolsFromNames,
4059
4087
  mcpAuthWarnings,
4060
4088
  SIDEBOARD_MCP_PROFILE_ENV,
4061
4089
  WORKTREE_MCP_TOOLS,
@@ -4071,16 +4099,16 @@ export {
4071
4099
  shouldInjectBrightsyMcp,
4072
4100
  brightsyMcpAllowedTools,
4073
4101
  writeInjectedMcpConfig,
4074
- PLAN_MODE_INSTRUCTION,
4075
- permissionMode,
4076
- mcpStatusThinkingFromClaudeInit,
4077
- claudeAdapter,
4078
4102
  userCursorMcpConfigPath,
4079
4103
  userClaudeMcpConfigPath,
4080
4104
  mergeSideboardIntoMcpServersJson,
4081
4105
  registerPackagedUserMcpClients,
4082
4106
  isInjectedOrchMcpName,
4083
4107
  userMcpNamesToDisable,
4108
+ PLAN_MODE_INSTRUCTION,
4109
+ permissionMode,
4110
+ mcpStatusThinkingFromClaudeInit,
4111
+ claudeAdapter,
4084
4112
  listCodexModels,
4085
4113
  codexAdapter,
4086
4114
  isCursorApiKeyConfigured,