@sideboard-ai/core 0.1.216 → 0.1.218

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-WBDDLRBY.js → agents-NWJ7DFJD.js} +8 -7
  9. package/dist/{agents-HETHRRZJ.js → agents-YABJUV3V.js} +9 -9
  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-7MTVRNP4.js → chunk-FSIJGLSV.js} +1173 -1186
  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-SALM35KD.js → chunk-IILG7DPX.js} +5 -5
  20. package/dist/{chunk-D2IKXMF3.js → chunk-JWBPSD7Q.js} +453 -289
  21. package/dist/{chunk-NI243N27.js → chunk-KTZESHYH.js} +15 -7
  22. package/dist/{chunk-KFD2OXCE.js → chunk-MW57SKKR.js} +19 -6
  23. package/dist/{chunk-XYKVRRC2.js → chunk-NFWPDEN4.js} +15 -7
  24. package/dist/{chunk-FNGXKY5W.js → chunk-OPYBLX4V.js} +19 -6
  25. package/dist/{chunk-QQKDGPUI.js → chunk-PO4M4JAJ.js} +1 -1
  26. package/dist/{chunk-2M5B7YNY.js → chunk-RRW2XWAY.js} +2 -2
  27. package/dist/{chunk-REZRGCKV.js → chunk-SOGGKYFV.js} +3 -3
  28. package/dist/{chunk-C32SBNA3.js → chunk-SRFFIFW6.js} +457 -294
  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-DFTVZEZY.js → chunk-XRHTRXZN.js} +1051 -1050
  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-5V7GEPBG.js} +6 -6
  39. package/dist/{coordinator-prompt-LE2GQZ2X.js → coordinator-prompt-J4QSNTKE.js} +6 -5
  40. package/dist/{global-workspace-6P236KKG.js → global-workspace-4TEMLESD.js} +6 -6
  41. package/dist/{global-workspace-KT3FRHZL.js → global-workspace-XPTFHMKL.js} +6 -5
  42. package/dist/index.cjs +1027 -821
  43. package/dist/index.d.cts +10 -1
  44. package/dist/index.d.ts +10 -1
  45. package/dist/index.js +22 -18
  46. package/dist/mcp/run-stdio.cjs +1838 -1651
  47. package/dist/mcp/run-stdio.js +16 -14
  48. package/dist/{orchestrator-WVZPTHBV.js → orchestrator-HKW3VLGX.js} +10 -10
  49. package/dist/{orchestrator-MI7A5DQZ.js → orchestrator-SWRTYFHX.js} +9 -8
  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-KM7KTLVO.js → workspaces-2HQP654Q.js} +6 -5
  53. package/dist/{workspaces-R5LJCJRB.js → workspaces-BCBBMMXB.js} +6 -6
  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-OPYBLX4V.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,1142 @@ 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";
791
+ applyAgentRunnerHeapEnv(sideboard.env);
792
+ servers.push(sideboard);
648
793
  }
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";
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
+ }
658
808
  }
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";
809
+ return servers;
810
+ }
811
+ function cursorSafeMcpLaunch(command, args) {
812
+ if (process.platform === "win32") {
813
+ return args && args.length > 0 ? { command, args } : { command };
662
814
  }
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";
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
+ };
666
838
  }
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";
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`);
672
858
  }
673
- if (/^taskstop$/i.test(compact)) {
674
- return str(input.task_id) ? `Stop ${str(input.task_id)}` : "Stop background task";
859
+ return args;
860
+ }
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
+ };
675
871
  }
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";
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 };
679
876
  }
680
- return n;
877
+ return JSON.stringify({ mcp });
681
878
  }
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`);
879
+ function writeMcpServersConfig(servers) {
880
+ if (servers.length === 0) return null;
881
+ const mcpServers = {};
882
+ for (const s of servers) {
883
+ const env = mcpSpawnEnv(s.env);
884
+ mcpServers[s.name] = {
885
+ command: s.command,
886
+ ...s.args ? { args: s.args } : {},
887
+ ...env ? { env } : {}
888
+ };
690
889
  }
691
- const last = str(input.live_last_tool);
692
- if (last) bits.push(last);
693
- return bits.length ? ` \xB7 ${bits.join(" \xB7 ")}` : "";
890
+ const dir = mkdtempSync(join(tmpdir(), "sideboard-mcp-"));
891
+ const cfgPath = join(dir, "mcp.json");
892
+ writeFileSync(cfgPath, JSON.stringify({ mcpServers }, null, 2));
893
+ return cfgPath;
694
894
  }
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);
895
+ async function writeInjectedMcpConfig(opts) {
896
+ return writeMcpServersConfig(await buildInjectedMcpServers(opts));
699
897
  }
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;
898
+
899
+ // src/agents/user-mcp-config.ts
900
+ function userCursorMcpConfigPath() {
901
+ return join2(homedir(), ".cursor", "mcp.json");
706
902
  }
707
- function isPollWrapperToolName(name) {
708
- const n = (name ?? "").replace(/[_-]/g, "");
709
- return /^(taskoutput|taskstop|sleep)$/i.test(n);
903
+ function userClaudeMcpConfigPath() {
904
+ return join2(homedir(), ".claude.json");
710
905
  }
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 "";
906
+ function asObject(value) {
907
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
908
+ return { ...value };
720
909
  }
721
- function toolLabel(tool) {
722
- return tool.description || toolDescription(tool.name, tool.input) || tool.name;
910
+ function stripElectronSpawnEnv(env) {
911
+ if (!env) return void 0;
912
+ const next = { ...env };
913
+ delete next.ELECTRON_RUN_AS_NODE;
914
+ delete next.ELECTRON_RUN_AS_NODE;
915
+ return Object.keys(next).length > 0 ? next : void 0;
723
916
  }
724
- function fileBasename(path) {
725
- const parts = path.replace(/\/$/, "").split(/[/\\]/);
726
- return parts[parts.length - 1] || path;
917
+ function mergeSideboardIntoMcpServersJson(existing, sideboard) {
918
+ const root = asObject(existing);
919
+ const servers = asObject(root.mcpServers);
920
+ const env = stripElectronSpawnEnv(sideboard.env);
921
+ servers.sideboard = {
922
+ type: "stdio",
923
+ command: sideboard.command,
924
+ ...sideboard.args && sideboard.args.length > 0 ? { args: sideboard.args } : {},
925
+ ...env ? { env } : {}
926
+ };
927
+ return { ...root, mcpServers: servers };
727
928
  }
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";
929
+ function writeMergedMcpServersJson(configPath, sideboard) {
930
+ let existing = {};
931
+ if (existsSync3(configPath)) {
932
+ try {
933
+ existing = JSON.parse(readFileSync(configPath, "utf8"));
934
+ } catch {
935
+ existing = {};
936
+ }
735
937
  }
736
- return "other";
938
+ const next = mergeSideboardIntoMcpServersJson(existing, sideboard);
939
+ mkdirSync(dirname2(configPath), { recursive: true });
940
+ writeFileSync2(configPath, `${JSON.stringify(next, null, 2)}
941
+ `);
737
942
  }
738
- function isShellToolName(name) {
739
- return classifyTool(name ?? "") === "shell";
943
+ function launchFromResolved(server) {
944
+ return {
945
+ command: server.command,
946
+ args: server.args,
947
+ env: {
948
+ ...server.env ?? {},
949
+ SIDEBOARD_APP_DATA: appDataDir()
950
+ }
951
+ };
740
952
  }
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;
953
+ async function registerPackagedUserMcpClients() {
954
+ const launch = launchFromResolved(await resolveSideboardMcpServer());
955
+ const cursor = userCursorMcpConfigPath();
956
+ writeMergedMcpServersJson(cursor, launch);
957
+ const claude = userClaudeMcpConfigPath();
958
+ if (existsSync3(claude)) {
959
+ writeMergedMcpServersJson(claude, launch);
960
+ return { cursor, claude };
771
961
  }
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`);
962
+ return { cursor };
963
+ }
964
+
965
+ // src/agents/orch-mcp-isolation.ts
966
+ function isInjectedOrchMcpName(name) {
967
+ const n = name.trim().toLowerCase();
968
+ return n === "sideboard" || n === "brightsy" || n.startsWith("brightsy_");
969
+ }
970
+ function asObject2(value) {
971
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
972
+ return value;
973
+ }
974
+ function listMcpNamesFromJsonMap(raw, key) {
975
+ return Object.keys(asObject2(asObject2(raw)[key])).filter((n) => n.trim());
976
+ }
977
+ function listMcpNamesFromCodexToml(text) {
978
+ try {
979
+ const parsed = parseToml(text);
980
+ return Object.keys(asObject2(parsed.mcp_servers)).filter((n) => n.trim());
981
+ } catch {
982
+ return [];
780
983
  }
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 };
791
984
  }
792
- function liveActivitySummary(parts, opts) {
793
- if (opts?.queued && parts.length === 0) {
794
- return "Queued \u2014 waiting for a slot";
985
+ function readJsonObject(path) {
986
+ try {
987
+ return JSON.parse(readFileSync2(path, "utf8"));
988
+ } catch {
989
+ return {};
795
990
  }
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)
991
+ }
992
+ function readText(path) {
993
+ try {
994
+ return readFileSync2(path, "utf8");
995
+ } catch {
996
+ return "";
997
+ }
998
+ }
999
+ function userCodexConfigPaths(home = homedir2()) {
1000
+ return [join3(home, ".codex", "config.toml"), join3(home, ".config", "codex", "config.toml")];
1001
+ }
1002
+ function userOpencodeConfigPaths(home = homedir2()) {
1003
+ return [
1004
+ join3(home, ".config", "opencode", "opencode.json"),
1005
+ join3(home, ".config", "opencode", "opencode.jsonc")
1006
+ ];
1007
+ }
1008
+ function userMcpNamesToDisable(opts) {
1009
+ const keep = new Set(
1010
+ [...opts.injectedNames ?? []].map((n) => n.trim().toLowerCase()).filter(Boolean)
803
1011
  );
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;
1012
+ const out = [];
1013
+ const seen = /* @__PURE__ */ new Set();
1014
+ for (const raw of opts.names) {
1015
+ const name = raw.trim();
1016
+ if (!name) continue;
1017
+ const key = name.toLowerCase();
1018
+ if (seen.has(key) || keep.has(key) || isInjectedOrchMcpName(name)) continue;
1019
+ seen.add(key);
1020
+ out.push(name);
812
1021
  }
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}`;
1022
+ return out;
1023
+ }
1024
+ function listUserCodexMcpNames(home = homedir2()) {
1025
+ const names = [];
1026
+ for (const path of userCodexConfigPaths(home)) {
1027
+ if (!existsSync4(path)) continue;
1028
+ names.push(...listMcpNamesFromCodexToml(readText(path)));
821
1029
  }
822
- return "Working\u2026";
1030
+ return names;
823
1031
  }
824
- function messagePartParentId(part) {
825
- if ("parentId" in part && typeof part.parentId === "string" && part.parentId.trim()) {
826
- return part.parentId;
1032
+ function listUserOpencodeMcpNames(home = homedir2()) {
1033
+ const names = [];
1034
+ for (const path of userOpencodeConfigPaths(home)) {
1035
+ if (!existsSync4(path)) continue;
1036
+ names.push(...listMcpNamesFromJsonMap(readJsonObject(path), "mcp"));
827
1037
  }
828
- return void 0;
1038
+ return names;
829
1039
  }
830
- function sameParentId(a, b) {
831
- return (a ?? "") === (b ?? "");
1040
+ function listUserClaudeMcpNames(home = homedir2()) {
1041
+ const claudePath = home === homedir2() ? userClaudeMcpConfigPath() : join3(home, ".claude.json");
1042
+ if (!existsSync4(claudePath)) return [];
1043
+ return listMcpNamesFromJsonMap(readJsonObject(claudePath), "mcpServers");
832
1044
  }
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 };
1045
+ function toCodexDisableUserMcpArgs(names) {
1046
+ const args = [];
1047
+ for (const name of names) {
1048
+ if (!/^[A-Za-z0-9_-]+$/.test(name)) continue;
1049
+ args.push("-c", `mcp_servers.${name}.enabled=false`);
837
1050
  }
838
- return event;
1051
+ return args;
839
1052
  }
840
- function withEventsParentId(events, parentId) {
841
- if (!parentId) return events;
842
- return events.map((event) => withEventParentId(event, parentId));
1053
+ function toCodexUnattendedAppsArgs() {
1054
+ return [
1055
+ "-c",
1056
+ "features.apps=false",
1057
+ "-c",
1058
+ "apps._default.enabled=false",
1059
+ "-c",
1060
+ "apps.posthog.enabled=false",
1061
+ "-c",
1062
+ "features.plugins=false"
1063
+ ];
843
1064
  }
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);
1065
+
1066
+ // src/agents/message-parts.ts
1067
+ function asRecord(input) {
1068
+ if (input && typeof input === "object" && !Array.isArray(input)) {
1069
+ return input;
1070
+ }
1071
+ return void 0;
847
1072
  }
848
- function countLines(text) {
849
- if (!text) return 0;
850
- return text.split("\n").length;
1073
+ function str(v) {
1074
+ return typeof v === "string" && v.trim() ? v : void 0;
851
1075
  }
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
- };
861
- }
862
- return {};
1076
+ var CHAT_DETAIL_MAX = 72;
1077
+ function clipChatLine(value, max = CHAT_DETAIL_MAX) {
1078
+ const line = value.trim().split(/\r?\n/, 1)[0] ?? "";
1079
+ if (line.length <= max) return line;
1080
+ return `${line.slice(0, Math.max(1, max - 1))}\u2026`;
863
1081
  }
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
- };
881
- }
882
- return {};
1082
+ var SHELL_BIN_DIR = /^(?:\/usr\/local\/bin|\/usr\/bin|\/bin|\/opt\/homebrew\/bin|\/sbin|\/usr\/sbin)\//;
1083
+ function rootedPathWithSpaces(trimmed) {
1084
+ const [head = "", ...rest] = trimmed.split(/\s+/);
1085
+ if (SHELL_BIN_DIR.test(head)) return false;
1086
+ return rest.every((tok) => !/^[-/~$]/.test(tok) && !tok.includes("="));
883
1087
  }
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));
1088
+ function looksLikeFilePath(value) {
1089
+ const trimmed = value.trim();
1090
+ if (!trimmed || /[\r\n;|&<>]/.test(trimmed)) return false;
1091
+ const rooted = trimmed.startsWith("/") || trimmed.startsWith("~/") || /^[A-Za-z]:[\\/]/.test(trimmed);
1092
+ if (/\s/.test(trimmed)) return rooted && rootedPathWithSpaces(trimmed);
1093
+ if (rooted) return true;
1094
+ return trimmed.includes("/") || trimmed.includes("\\");
889
1095
  }
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));
1096
+ function stripWorktreePrefix(path, worktreePath) {
1097
+ if (!worktreePath) return path;
1098
+ const prefix = worktreePath.replace(/[/\\]+$/, "");
1099
+ if (path === prefix || path === `${prefix}/`) return "";
1100
+ if (path.startsWith(`${prefix}/`)) return path.slice(prefix.length + 1);
1101
+ return path;
896
1102
  }
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) };
1103
+ function visibleToolRowDetail(detail, description, worktreePath) {
1104
+ if (!detail?.trim()) return void 0;
1105
+ const raw = detail.trim();
1106
+ const desc = (description ?? "").trim();
1107
+ if (!looksLikeFilePath(raw)) {
1108
+ const clipped = clipChatLine(raw);
1109
+ if (!clipped || desc === clipped || desc === raw) return void 0;
1110
+ return clipped;
905
1111
  }
906
- if (event.type === "stdout" && event.data.length > STDOUT_EVENT_MAX_CHARS) {
907
- return { ...event, data: clipEventTail(event.data, STDOUT_EVENT_MAX_CHARS) };
1112
+ const rel = stripWorktreePrefix(raw, worktreePath);
1113
+ if (!rel) return void 0;
1114
+ const base = fileBasename(rel);
1115
+ if (desc && (desc === base || desc.endsWith(` ${base}`))) return void 0;
1116
+ if (rel.length <= 42) return rel;
1117
+ const parts = rel.split(/[/\\]/).filter(Boolean);
1118
+ if (parts.length >= 2) return `\u2026/${parts.slice(-2).join("/")}`;
1119
+ return base;
1120
+ }
1121
+ function toolDetail(name, input) {
1122
+ if (!input) return void 0;
1123
+ const command = str(input.command) ?? str(input.cmd);
1124
+ if (command) return clipChatLine(command);
1125
+ const pattern = str(input.pattern) ?? str(input.glob) ?? str(input.glob_pattern);
1126
+ const isSearch = /grep|glob|search|ripgrep|findfiles|semsearch/i.test(name);
1127
+ if (isSearch && pattern) {
1128
+ return pattern.length > 80 ? `${pattern.slice(0, 77)}\u2026` : pattern;
908
1129
  }
909
- if (event.type === "stderr" && event.data.length > STDERR_EVENT_MAX_CHARS) {
910
- return { ...event, data: clipEventTail(event.data, STDERR_EVENT_MAX_CHARS) };
1130
+ const path = str(input.file_path) ?? str(input.path) ?? str(input.filePath) ?? str(input.filename);
1131
+ if (path) return path;
1132
+ if (pattern) return pattern.length > 80 ? `${pattern.slice(0, 77)}\u2026` : pattern;
1133
+ const query = str(input.query) ?? str(input.prompt);
1134
+ if (query) return query.length > 80 ? `${query.slice(0, 77)}\u2026` : query;
1135
+ try {
1136
+ const raw = JSON.stringify(input);
1137
+ return raw.length > 80 ? `${raw.slice(0, 77)}\u2026` : raw;
1138
+ } catch {
1139
+ return name;
911
1140
  }
912
- return event;
913
1141
  }
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 };
918
- }
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;
1142
+ function toolDescription(name, input) {
1143
+ const n = name.replace(/^mcp__/, "").replace(/__/g, " \xB7 ");
1144
+ if (/^get_record_types$|recordTypes/i.test(name)) return "List record types";
1145
+ if (/connectedAgentRequest/i.test(name)) {
1146
+ return str(input?.agent_id) ? `Ask connected agent` : "Ask connected agent";
944
1147
  }
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;
1148
+ if (/present_artifact$/i.test(name)) {
1149
+ return str(input?.title) ? `Present ${str(input?.title)}` : "Present artifact";
993
1150
  }
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
- ];
1151
+ if (/present_plan$/i.test(name)) {
1152
+ return str(input?.title) ? `Plan ${str(input?.title)}` : "Present plan";
1037
1153
  }
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
- });
1154
+ if (/present_schema$/i.test(name)) {
1155
+ return str(input?.title) ? `Schema ${str(input?.title)}` : "Present schema";
1156
+ }
1157
+ if (/present_files$/i.test(name)) {
1158
+ return str(input?.title) ? `Files ${str(input?.title)}` : "Present files";
1159
+ }
1160
+ if (/wait_for_job$/i.test(name)) {
1161
+ return str(input?.id) ? `Wait for ${str(input?.id)}` : "Wait for job";
1162
+ }
1163
+ if (/stop_job$/i.test(name)) {
1164
+ return str(input?.id) ? `Stop ${str(input?.id)}` : "Stop job";
1165
+ }
1166
+ if (isSubagentToolName(name)) {
1167
+ const desc = str(input?.description);
1168
+ const sub = asRecord(input?.subagentType);
1169
+ const kind = str(sub?.name) ?? str(sub?.kind) ?? str(input?.subagent_type) ?? str(input?.subagentType);
1170
+ if (/^spawn_agent$/i.test(name)) {
1171
+ return str(input?.prompt) ?? desc ?? "Subagent";
1067
1172
  }
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
- ];
1173
+ if (/connectedAgentRequest/i.test(name)) {
1174
+ return str(input?.agent_id) ? `Ask connected agent` : "Ask connected agent";
1083
1175
  }
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
- });
1176
+ if (desc && kind) return `${kind}: ${desc}${subagentLiveSuffix(input)}`;
1177
+ if (desc) return `${desc}${subagentLiveSuffix(input)}`;
1178
+ return `Subagent${subagentLiveSuffix(input)}`;
1097
1179
  }
1098
- return parts;
1180
+ if (/^(create|update)_artifact$/i.test(name.replace(/^mcp__[^_]+__/, ""))) {
1181
+ return str(input?.title) ? `Artifact ${str(input?.title)}` : "Artifact";
1182
+ }
1183
+ if (!input) return n;
1184
+ if (/bash|shell|terminal/i.test(name) && str(input.command)) {
1185
+ const cmd = str(input.command);
1186
+ if (/git\s+fetch/i.test(cmd)) return "Fetch latest from origin and check status";
1187
+ if (/git\s+status/i.test(cmd)) return "Check git status";
1188
+ if (/git\s+log/i.test(cmd)) return "Inspect recent commits";
1189
+ if (/git\s+branch/i.test(cmd)) return "List branches";
1190
+ if (/grep|rg\b/i.test(cmd)) return "Search repository";
1191
+ return "Run shell command";
1192
+ }
1193
+ if (/edit|write|apply/i.test(name)) {
1194
+ const path = str(input.file_path) ?? str(input.path);
1195
+ return path ? `Edit ${path.split("/").pop()}` : "Edit file";
1196
+ }
1197
+ if (/read/i.test(name)) {
1198
+ const path = str(input.file_path) ?? str(input.path);
1199
+ return path ? `Read ${path.split("/").pop()}` : "Read file";
1200
+ }
1201
+ if (/grep/i.test(name)) return "Search files";
1202
+ if (/glob/i.test(name)) return "Find files";
1203
+ const compact = n.replace(/[_-]/g, "");
1204
+ if (/^taskoutput$/i.test(compact)) {
1205
+ return str(input.task_id) ? `Wait for ${str(input.task_id)}` : "Wait for background task";
1206
+ }
1207
+ if (/^taskstop$/i.test(compact)) {
1208
+ return str(input.task_id) ? `Stop ${str(input.task_id)}` : "Stop background task";
1209
+ }
1210
+ if (/^monitor$/i.test(compact)) {
1211
+ const command = str(input.command);
1212
+ return command ? `Watch ${command.length > 48 ? `${command.slice(0, 45)}\u2026` : command}` : "Watch command";
1213
+ }
1214
+ return n;
1099
1215
  }
1100
- function partsToAssistantText(parts) {
1101
- return parts.filter(
1102
- (p) => p.type === "text" && !messagePartParentId(p)
1103
- ).map((p) => p.text).join("").trim();
1216
+ function subagentLiveSuffix(input) {
1217
+ if (!input) return "";
1218
+ const bits = [];
1219
+ const status = str(input.live_status);
1220
+ if (status && !/^runn(ing)?$|^working$/i.test(status)) bits.push(status);
1221
+ if (typeof input.live_tool_uses === "number") bits.push(`${input.live_tool_uses} tools`);
1222
+ if (typeof input.live_duration_ms === "number") {
1223
+ bits.push(`${Math.max(0, Math.round(Number(input.live_duration_ms) / 1e3))}s`);
1224
+ }
1225
+ const last = str(input.live_last_tool);
1226
+ if (last) bits.push(last);
1227
+ return bits.length ? ` \xB7 ${bits.join(" \xB7 ")}` : "";
1104
1228
  }
1105
- function lastAssistantMessageText(parts) {
1106
- if (!parts?.length) return "";
1229
+ function isSubagentToolName(name) {
1230
+ const n = (name ?? "").trim();
1231
+ if (/^(task|agent|spawn_agent)$/i.test(n)) return true;
1232
+ return /connectedAgentRequest/i.test(n);
1233
+ }
1234
+ function isInternalAgentStatusText(text) {
1235
+ const t = text.trim();
1236
+ if (!t) return false;
1237
+ if (/waiting for gate to pass/i.test(t)) return true;
1238
+ if (/^agent is running\b/i.test(t) && t.length < 160) return true;
1239
+ return false;
1240
+ }
1241
+ function isPollWrapperToolName(name) {
1242
+ const n = (name ?? "").replace(/[_-]/g, "");
1243
+ return /^(taskoutput|taskstop|sleep)$/i.test(n);
1244
+ }
1245
+ function lastTextPart(parts, type) {
1107
1246
  for (let i = parts.length - 1; i >= 0; i--) {
1108
1247
  const p = parts[i];
1109
- if (!p || p.type !== "text" || messagePartParentId(p)) continue;
1248
+ if (p.type !== type) continue;
1110
1249
  const text = p.text.trim();
1111
1250
  if (!text || isInternalAgentStatusText(text)) continue;
1112
1251
  return text;
1113
1252
  }
1114
1253
  return "";
1115
1254
  }
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";
1255
+ function toolLabel(tool) {
1256
+ return tool.description || toolDescription(tool.name, tool.input) || tool.name;
1189
1257
  }
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;
1258
+ function fileBasename(path) {
1259
+ const parts = path.replace(/\/$/, "").split(/[/\\]/);
1260
+ return parts[parts.length - 1] || path;
1216
1261
  }
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;
1262
+ function classifyTool(name) {
1263
+ const compact = name.replace(/^mcp__[^_]+__/, "").replace(/[_-]/g, "");
1264
+ if (/bash|shell|terminal|^zsh$|^sh$/i.test(compact)) return "shell";
1265
+ if (/grep|glob|search|ripgrep|findfiles|semsearch/i.test(compact)) return "search";
1266
+ if (/^(read|cat)$/i.test(compact) || /^read/i.test(compact)) return "read";
1267
+ if (/edit|write|apply|strreplace|multiedit|updatefile|createfile/i.test(compact)) {
1268
+ return "edit";
1226
1269
  }
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;
1270
+ return "other";
1231
1271
  }
1232
- function isBrightsyConnected() {
1233
- try {
1234
- loadBrightsyConfig();
1272
+ function isShellToolName(name) {
1273
+ return classifyTool(name ?? "") === "shell";
1274
+ }
1275
+ function toolActivityLine(parts) {
1276
+ const tools = parts.filter((p) => {
1277
+ if (p.type !== "tool" || p.parentId) return false;
1278
+ if (isPollWrapperToolName(p.name)) return false;
1279
+ if (/present_plan$/i.test(p.name ?? "")) return false;
1280
+ if (/ask_user|AskUserQuestion/i.test(p.name ?? "")) return false;
1235
1281
  return true;
1236
- } catch {
1237
- return false;
1282
+ });
1283
+ if (tools.length === 0) return null;
1284
+ const edited = [];
1285
+ let reads = 0;
1286
+ let searches = 0;
1287
+ let shells = 0;
1288
+ let others = 0;
1289
+ let additions = 0;
1290
+ let deletions = 0;
1291
+ for (const tool of tools) {
1292
+ const kind = classifyTool(tool.name);
1293
+ if (kind === "edit") {
1294
+ const path = tool.filePath ?? toolFilePath(tool.input);
1295
+ edited.push({
1296
+ name: path ? fileBasename(path) : tool.name,
1297
+ running: tool.status === "running"
1298
+ });
1299
+ } else if (kind === "read") reads += 1;
1300
+ else if (kind === "search") searches += 1;
1301
+ else if (kind === "shell") shells += 1;
1302
+ else others += 1;
1303
+ if (typeof tool.additions === "number") additions += tool.additions;
1304
+ if (typeof tool.deletions === "number") deletions += tool.deletions;
1305
+ }
1306
+ const bits = [];
1307
+ const editing = edited.filter((e) => e.running);
1308
+ const editedDone = edited.filter((e) => !e.running);
1309
+ if (editing.length === 1) bits.push(`Editing ${editing[0].name}`);
1310
+ else if (editing.length > 1) bits.push(`Editing ${editing.length} files`);
1311
+ if (editedDone.length === 1) bits.push(`${editing.length ? "edited" : "Edited"} ${editedDone[0].name}`);
1312
+ else if (editedDone.length > 1) {
1313
+ bits.push(`${editing.length ? "edited" : "Edited"} ${editedDone.length} files`);
1238
1314
  }
1315
+ if (reads === 1) bits.push("explored 1 file");
1316
+ else if (reads > 1) bits.push(`explored ${reads} files`);
1317
+ if (searches === 1) bits.push("1 search");
1318
+ else if (searches > 1) bits.push(`${searches} searches`);
1319
+ if (shells === 1) bits.push("ran 1 command");
1320
+ else if (shells > 1) bits.push(`ran ${shells} commands`);
1321
+ if (others === 1) bits.push("1 tool");
1322
+ else if (others > 1) bits.push(`${others} tools`);
1323
+ if (bits.length === 0) return null;
1324
+ return { text: bits.join(", "), additions, deletions };
1239
1325
  }
1240
- var BRIGHTSY_WORD = /(?<![a-z0-9_-])brightsy(?![a-z0-9_-])/i;
1241
- function promptMentionsBrightsy(text) {
1242
- return BRIGHTSY_WORD.test(text ?? "");
1326
+ function liveActivitySummary(parts, opts) {
1327
+ if (opts?.queued && parts.length === 0) {
1328
+ return "Queued \u2014 waiting for a slot";
1329
+ }
1330
+ const tools = parts.filter((p) => p.type === "tool");
1331
+ const runningSubs = tools.filter(
1332
+ (t) => t.status === "running" && !t.parentId && isSubagentToolName(t.name)
1333
+ );
1334
+ const runningNested = [...tools].reverse().find((t) => t.status === "running" && t.parentId && !isPollWrapperToolName(t.name));
1335
+ const runningTop = [...tools].reverse().find(
1336
+ (t) => t.status === "running" && !t.parentId && !isPollWrapperToolName(t.name) && !isSubagentToolName(t.name)
1337
+ );
1338
+ const runningPoll = [...tools].reverse().find((t) => t.status === "running" && isPollWrapperToolName(t.name));
1339
+ const thinking = lastTextPart(parts, "thinking");
1340
+ const text = lastTextPart(parts, "text");
1341
+ if (runningSubs.length > 0) {
1342
+ const heads = runningSubs.map(toolLabel);
1343
+ const head = runningSubs.length === 1 ? heads[0] : `${runningSubs.length} subagents \xB7 ${heads.slice(0, 2).join(" \xB7 ")}`;
1344
+ if (runningNested) return `${head} \xB7 ${toolLabel(runningNested)}`;
1345
+ return head;
1346
+ }
1347
+ if (runningTop) return toolLabel(runningTop);
1348
+ if (runningPoll) return toolLabel(runningPoll);
1349
+ if (thinking) return thinking.length > 96 ? `\u2026${thinking.slice(-96)}` : thinking;
1350
+ if (text) return "Writing reply\u2026";
1351
+ const last = [...tools].reverse().find((t) => !isPollWrapperToolName(t.name)) ?? tools.at(-1);
1352
+ if (last) {
1353
+ const label = toolLabel(last);
1354
+ return last.status === "running" ? label : `Finished ${label}`;
1355
+ }
1356
+ return "Working\u2026";
1243
1357
  }
1244
- function isBrightsyMcpToolName(name) {
1245
- const n = name.toLowerCase();
1246
- return n === "brightsy" || n.startsWith("mcp__brightsy") || n.startsWith("brightsy_");
1358
+ function messagePartParentId(part) {
1359
+ if ("parentId" in part && typeof part.parentId === "string" && part.parentId.trim()) {
1360
+ return part.parentId;
1361
+ }
1362
+ return void 0;
1247
1363
  }
1248
- function toolInputUsesBrightsyDatasource(input) {
1249
- const ds = input?.datasource;
1250
- return typeof ds === "string" && ds.toLowerCase() === "brightsy";
1364
+ function sameParentId(a, b) {
1365
+ return (a ?? "") === (b ?? "");
1251
1366
  }
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
- }
1367
+ function withEventParentId(event, parentId) {
1368
+ if (!parentId) return event;
1369
+ if (event.type === "stdout" || event.type === "thinking" || event.type === "tool_use" || event.type === "tool_result") {
1370
+ return { ...event, parentId: event.parentId ?? parentId };
1260
1371
  }
1261
- return false;
1262
- }
1263
- function threadRequestsBrightsyMcp(thread) {
1264
- return (thread?.messages ?? []).some(messageUsedBrightsyMcp);
1372
+ return event;
1265
1373
  }
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);
1374
+ function withEventsParentId(events, parentId) {
1375
+ if (!parentId) return events;
1376
+ return events.map((event) => withEventParentId(event, parentId));
1272
1377
  }
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
- };
1378
+ function toolFilePath(input) {
1379
+ if (!input) return void 0;
1380
+ return str(input.file_path) ?? str(input.path) ?? str(input.filePath) ?? str(input.filename);
1283
1381
  }
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
- };
1382
+ function countLines(text) {
1383
+ if (!text) return 0;
1384
+ return text.split("\n").length;
1292
1385
  }
1293
- function brightsyMcpAllowedTools(serverNames) {
1294
- const out = [];
1295
- for (const name of serverNames) {
1296
- out.push(`mcp__${name}`, `mcp__${name}__*`);
1386
+ function diffFromInput(input) {
1387
+ if (!input) return {};
1388
+ const oldS = str(input.old_string) ?? str(input.oldString);
1389
+ const newS = str(input.new_string) ?? str(input.newString) ?? str(input.content);
1390
+ if (oldS != null || newS != null) {
1391
+ return {
1392
+ additions: countLines(newS),
1393
+ deletions: countLines(oldS)
1394
+ };
1297
1395
  }
1298
- return out;
1396
+ return {};
1299
1397
  }
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 {
1398
+ function parseDiffStat(result) {
1399
+ if (!result) return {};
1400
+ const paired = result.match(/\+(\d+)\s+-(\d+)/);
1401
+ if (paired) {
1402
+ return {
1403
+ additions: Number(paired[1]),
1404
+ deletions: Number(paired[2])
1405
+ };
1309
1406
  }
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();
1407
+ const verbose = result.match(
1408
+ /(\d+)\s+insertions?(?:,\s*(\d+)\s+deletions?)?/i
1409
+ );
1410
+ if (verbose) {
1411
+ return {
1412
+ additions: Number(verbose[1]),
1413
+ deletions: verbose[2] != null ? Number(verbose[2]) : void 0
1414
+ };
1315
1415
  }
1416
+ return {};
1316
1417
  }
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;
1418
+ var THINKING_PART_MAX_CHARS = 64e3;
1419
+ function clipThinkingForStore(text, max = THINKING_PART_MAX_CHARS) {
1420
+ if (text.length <= max) return text;
1421
+ const marker = "\u2026(earlier thinking trimmed)\n";
1422
+ return marker + text.slice(text.length - Math.max(1, max - marker.length));
1341
1423
  }
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
- }
1424
+ var STDOUT_EVENT_MAX_CHARS = 16e3;
1425
+ var STDERR_EVENT_MAX_CHARS = 4e3;
1426
+ function clipEventTail(text, max) {
1427
+ if (text.length <= max) return text;
1428
+ const marker = "\u2026(truncated)\n";
1429
+ return marker + text.slice(text.length - Math.max(1, max - marker.length));
1430
+ }
1431
+ function clipAgentEventForPaint(event) {
1432
+ if (event.type === "tool_result" && event.content) {
1433
+ const content = clipToolResultForStore(event.content);
1434
+ if (content === event.content) return event;
1435
+ return { ...event, content };
1436
+ }
1437
+ if (event.type === "thinking" && event.data.length > THINKING_PART_MAX_CHARS) {
1438
+ return { ...event, data: clipThinkingForStore(event.data) };
1356
1439
  }
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"] };
1440
+ if (event.type === "stdout" && event.data.length > STDOUT_EVENT_MAX_CHARS) {
1441
+ return { ...event, data: clipEventTail(event.data, STDOUT_EVENT_MAX_CHARS) };
1360
1442
  }
1361
- return { name: "sideboard", command: "sideboard", args: ["mcp"] };
1443
+ if (event.type === "stderr" && event.data.length > STDERR_EVENT_MAX_CHARS) {
1444
+ return { ...event, data: clipEventTail(event.data, STDERR_EVENT_MAX_CHARS) };
1445
+ }
1446
+ return event;
1362
1447
  }
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)
1448
+ function withPartTimes(next, prev) {
1449
+ const now = Date.now();
1450
+ const startedAt = prev && typeof prev.startedAt === "number" ? prev.startedAt : now;
1451
+ return { ...next, startedAt, updatedAt: now };
1452
+ }
1453
+ function applyAgentEvent(parts, event) {
1454
+ if (event.type === "stdout") {
1455
+ const data = event.data;
1456
+ if (!data) return parts;
1457
+ const next = [...parts];
1458
+ const last = next[next.length - 1];
1459
+ if (last?.type === "text" && sameParentId(last.parentId, event.parentId)) {
1460
+ next[next.length - 1] = withPartTimes(
1461
+ {
1462
+ type: "text",
1463
+ text: last.text + data,
1464
+ ...event.parentId ? { parentId: event.parentId } : {}
1465
+ },
1466
+ last
1467
+ );
1468
+ } else {
1469
+ next.push(
1470
+ withPartTimes({
1471
+ type: "text",
1472
+ text: data,
1473
+ ...event.parentId ? { parentId: event.parentId } : {}
1474
+ })
1381
1475
  );
1382
- } catch {
1383
1476
  }
1384
- applyAgentRunnerHeapEnv(sideboard.env);
1385
- servers.push(sideboard);
1477
+ return next;
1386
1478
  }
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)));
1479
+ if (event.type === "thinking") {
1480
+ const data = event.data;
1481
+ if (!data) return parts;
1482
+ const next = [...parts];
1483
+ if (event.replace) {
1484
+ for (let i = next.length - 1; i >= 0; i--) {
1485
+ const prev = next[i];
1486
+ if (prev?.type === "thinking" && sameParentId(prev.parentId, event.parentId)) {
1487
+ next[i] = withPartTimes(
1488
+ {
1489
+ type: "thinking",
1490
+ text: clipThinkingForStore(data),
1491
+ ...event.parentId ? { parentId: event.parentId } : {}
1492
+ },
1493
+ prev
1494
+ );
1495
+ return next;
1496
+ }
1397
1497
  }
1498
+ next.push(
1499
+ withPartTimes({
1500
+ type: "thinking",
1501
+ text: clipThinkingForStore(data),
1502
+ ...event.parentId ? { parentId: event.parentId } : {}
1503
+ })
1504
+ );
1505
+ return next;
1506
+ }
1507
+ const last = next[next.length - 1];
1508
+ if (last?.type === "thinking" && sameParentId(last.parentId, event.parentId)) {
1509
+ next[next.length - 1] = withPartTimes(
1510
+ {
1511
+ type: "thinking",
1512
+ text: clipThinkingForStore(last.text + data),
1513
+ ...event.parentId ? { parentId: event.parentId } : {}
1514
+ },
1515
+ last
1516
+ );
1398
1517
  } else {
1399
- servers.push(mcpLaunch(cmd, "brightsy"));
1518
+ next.push(
1519
+ withPartTimes({
1520
+ type: "thinking",
1521
+ text: clipThinkingForStore(data),
1522
+ ...event.parentId ? { parentId: event.parentId } : {}
1523
+ })
1524
+ );
1400
1525
  }
1526
+ return next;
1401
1527
  }
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)}`);
1528
+ if (event.type === "tool_use") {
1529
+ const input = asRecord(event.input);
1530
+ const diff = diffFromInput(input);
1531
+ const existing = parts.findIndex((p) => p.type === "tool" && p.id === event.id);
1532
+ if (existing >= 0) {
1533
+ const prev = parts[existing];
1534
+ const next = [...parts];
1535
+ const mergedInput = {
1536
+ ...prev.input ?? {},
1537
+ ...input ?? {}
1538
+ };
1539
+ const mergedRecord = Object.keys(mergedInput).length > 0 ? mergedInput : void 0;
1540
+ next[existing] = withPartTimes(
1541
+ {
1542
+ ...prev,
1543
+ name: event.name || prev.name,
1544
+ input: mergedRecord,
1545
+ description: toolDescription(event.name || prev.name, mergedRecord),
1546
+ detail: toolDetail(event.name || prev.name, mergedRecord) ?? prev.detail,
1547
+ filePath: toolFilePath(mergedRecord) ?? prev.filePath,
1548
+ additions: diff.additions ?? prev.additions,
1549
+ deletions: diff.deletions ?? prev.deletions,
1550
+ parentId: event.parentId ?? prev.parentId
1551
+ },
1552
+ prev
1553
+ );
1554
+ return next;
1441
1555
  }
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)}`);
1556
+ return [
1557
+ ...parts,
1558
+ withPartTimes({
1559
+ type: "tool",
1560
+ id: event.id,
1561
+ name: event.name,
1562
+ input,
1563
+ description: toolDescription(event.name, input),
1564
+ detail: toolDetail(event.name, input),
1565
+ status: "running",
1566
+ filePath: toolFilePath(input),
1567
+ ...event.parentId ? { parentId: event.parentId } : {},
1568
+ ...diff
1569
+ })
1570
+ ];
1571
+ }
1572
+ if (event.type === "tool_result") {
1573
+ const existing = parts.findIndex((p) => p.type === "tool" && p.id === event.id);
1574
+ const fromResult = parseDiffStat(event.content);
1575
+ if (event.partial) {
1576
+ if (existing < 0) {
1577
+ return [
1578
+ ...parts,
1579
+ withPartTimes({
1580
+ type: "tool",
1581
+ id: event.id,
1582
+ name: "tool",
1583
+ description: "tool",
1584
+ status: event.isError ? "error" : "running",
1585
+ result: clipToolResultForStore(event.content),
1586
+ ...event.parentId ? { parentId: event.parentId } : {}
1587
+ })
1588
+ ];
1446
1589
  }
1590
+ return parts.map((p, i) => {
1591
+ if (i !== existing || p.type !== "tool") return p;
1592
+ if (p.status === "done" || p.status === "error") return p;
1593
+ return withPartTimes(
1594
+ {
1595
+ ...p,
1596
+ result: clipToolResultForStore(event.content) ?? p.result
1597
+ },
1598
+ p
1599
+ );
1600
+ });
1447
1601
  }
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`);
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" : "done",
1611
+ result: clipToolResultForStore(event.content),
1612
+ ...event.parentId ? { parentId: event.parentId } : {},
1613
+ ...fromResult.additions != null ? { additions: fromResult.additions } : {},
1614
+ ...fromResult.deletions != null ? { deletions: fromResult.deletions } : {}
1615
+ })
1616
+ ];
1617
+ }
1618
+ return parts.map((p, i) => {
1619
+ if (i !== existing || p.type !== "tool") return p;
1620
+ return withPartTimes(
1621
+ {
1622
+ ...p,
1623
+ status: event.isError ? "error" : "done",
1624
+ result: clipToolResultForStore(event.content),
1625
+ ...fromResult.additions != null ? { additions: fromResult.additions } : {},
1626
+ ...fromResult.deletions != null ? { deletions: fromResult.deletions } : {}
1627
+ },
1628
+ p
1629
+ );
1630
+ });
1451
1631
  }
1452
- return args;
1632
+ return parts;
1453
1633
  }
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 });
1634
+ function partsToAssistantText(parts) {
1635
+ return parts.filter(
1636
+ (p) => p.type === "text" && !messagePartParentId(p)
1637
+ ).map((p) => p.text).join("").trim();
1471
1638
  }
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
- };
1639
+ function lastAssistantMessageText(parts) {
1640
+ if (!parts?.length) return "";
1641
+ for (let i = parts.length - 1; i >= 0; i--) {
1642
+ const p = parts[i];
1643
+ if (!p || p.type !== "text" || messagePartParentId(p)) continue;
1644
+ const text = p.text.trim();
1645
+ if (!text || isInternalAgentStatusText(text)) continue;
1646
+ return text;
1482
1647
  }
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;
1648
+ return "";
1487
1649
  }
1488
- async function writeInjectedMcpConfig(opts) {
1489
- return writeMcpServersConfig(await buildInjectedMcpServers(opts));
1650
+ function stripBrightsyNdjsonNoise(text) {
1651
+ if (!text || !text.includes('"type"')) return text;
1652
+ let out = text;
1653
+ out = out.replace(
1654
+ /\{"type":"(?:tool_use|tool_result|tool|thinking|usage|done|error)"[\s\S]*?\}\s*(?=\{"type":"|$|(?=[A-Za-z*#]))/g,
1655
+ ""
1656
+ );
1657
+ out = out.replace(/\{"type":"(?:tool_use|tool_result|tool)"[\s\S]*$/g, "");
1658
+ return out.trim();
1659
+ }
1660
+ function isBrightsyNdjsonLine(line) {
1661
+ const t = line.trim();
1662
+ if (!t.startsWith("{")) return false;
1663
+ return /"type"\s*:\s*"(tool_use|tool_result|tool|thinking|usage|done|error|text)"/.test(t);
1664
+ }
1665
+ function finalizeParts(parts) {
1666
+ return parts.map(
1667
+ (p) => p.type === "tool" && p.status === "running" ? { ...p, status: "done" } : p
1668
+ );
1669
+ }
1670
+ function normalizeParseResult(parsed) {
1671
+ if (!parsed) return [];
1672
+ return Array.isArray(parsed) ? parsed : [parsed];
1490
1673
  }
1491
1674
 
1492
1675
  // src/agents/types.ts
@@ -1550,24 +1733,15 @@ var BASE_ALLOWED_TOOLS = [
1550
1733
  "Skill"
1551
1734
  ];
1552
1735
  var CLAUDE_PRINT_BG_WAIT_CEILING_MS = 72e5;
1736
+ function isLocalDevAppDataDir(dir = process.env.SIDEBOARD_APP_DATA) {
1737
+ return /[/\\]\.sideboard[/\\]dev-app-data(?:[/\\]|$)/.test(dir ?? "");
1738
+ }
1553
1739
  var CLAUDE_CHROME_ALLOWED_TOOLS = [
1554
1740
  "mcp__claude-in-chrome",
1555
1741
  "mcp__claude-in-chrome__*",
1556
1742
  "Skill(claude-in-chrome)"
1557
1743
  ];
1558
1744
  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
1745
  function usageFromClaude(usage) {
1572
1746
  if (!usage) return null;
1573
1747
  const inputTokens = Number(usage.input_tokens ?? 0);
@@ -1854,7 +2028,7 @@ var claudeAdapter = {
1854
2028
  async detect() {
1855
2029
  const claude = resolveClaudeExecutable();
1856
2030
  if (claude !== "claude") {
1857
- if (!existsSync3(claude)) {
2031
+ if (!existsSync5(claude)) {
1858
2032
  return {
1859
2033
  agent: "claude",
1860
2034
  installed: false,
@@ -1903,7 +2077,7 @@ var claudeAdapter = {
1903
2077
  );
1904
2078
  }
1905
2079
  const mode = permissionMode(thread);
1906
- const { isOrchestratorThread: isOrchestratorThread2 } = await import("./global-workspace-6P236KKG.js");
2080
+ const { isOrchestratorThread: isOrchestratorThread2 } = await import("./global-workspace-4TEMLESD.js");
1907
2081
  const isOrchestrator = isOrchestratorThread2(thread);
1908
2082
  const injectedServers = await buildInjectedMcpServers({
1909
2083
  includeSideboard: true,
@@ -1922,10 +2096,9 @@ var claudeAdapter = {
1922
2096
  ...brightsyMcpAllowedTools(injectedBrightsyNames)
1923
2097
  ];
1924
2098
  } else {
1925
- const servers = await loadMcpServers();
1926
2099
  allowedTools = [
1927
2100
  ...BASE_ALLOWED_TOOLS,
1928
- ...mcpAllowTools(servers),
2101
+ ...mcpAllowToolsFromNames(userMcpNamesToDisable({ names: listUserClaudeMcpNames() })),
1929
2102
  ...sideboardWorktreeAllowedTools({
1930
2103
  github: true,
1931
2104
  linear: isLinearConnected(),
@@ -1953,9 +2126,7 @@ var claudeAdapter = {
1953
2126
  const mcpConfigPath = writeMcpServersConfig(injectedServers);
1954
2127
  if (mcpConfigPath) {
1955
2128
  args.push("--mcp-config", mcpConfigPath);
1956
- if (isOrchestrator) {
1957
- args.push("--strict-mcp-config");
1958
- }
2129
+ args.push("--strict-mcp-config");
1959
2130
  }
1960
2131
  if (chromeOn) {
1961
2132
  args.push("--chrome");
@@ -1987,8 +2158,10 @@ var claudeAdapter = {
1987
2158
  CLAUDE_CODE_FORWARD_SUBAGENT_TEXT: "1",
1988
2159
  CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS: process.env.CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS ?? String(CLAUDE_PRINT_BG_WAIT_CEILING_MS),
1989
2160
  // `--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" } : {}
2161
+ // builds (anthropics/claude-code#60252). Coordinators never need them.
2162
+ // Local `pnpm dev` also disables them: those HTTP connectors hang the
2163
+ // first worktree turn when Electron's cwd is this Sideboard repo.
2164
+ ...isOrchestrator || isLocalDevAppDataDir() ? { ENABLE_CLAUDEAI_MCP_SERVERS: "false" } : {}
1992
2165
  }
1993
2166
  };
1994
2167
  },
@@ -2139,179 +2312,6 @@ function parseIssuesJson(raw) {
2139
2312
  import { existsSync as existsSync6, readFileSync as readFileSync3, statSync } from "fs";
2140
2313
  import { homedir as homedir3 } from "os";
2141
2314
  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
2315
  var CODEX_PROMPT_ARG_MAX = 2e5;
2316
2316
  var FALLBACK_CODEX_MODELS = [
2317
2317
  { id: "gpt-5.6-sol", displayName: "GPT-5.6 Sol" },
@@ -4056,6 +4056,7 @@ export {
4056
4056
  parseMcpList,
4057
4057
  sanitizeMcpServerName,
4058
4058
  mcpAllowTools,
4059
+ mcpAllowToolsFromNames,
4059
4060
  mcpAuthWarnings,
4060
4061
  SIDEBOARD_MCP_PROFILE_ENV,
4061
4062
  WORKTREE_MCP_TOOLS,
@@ -4071,16 +4072,16 @@ export {
4071
4072
  shouldInjectBrightsyMcp,
4072
4073
  brightsyMcpAllowedTools,
4073
4074
  writeInjectedMcpConfig,
4074
- PLAN_MODE_INSTRUCTION,
4075
- permissionMode,
4076
- mcpStatusThinkingFromClaudeInit,
4077
- claudeAdapter,
4078
4075
  userCursorMcpConfigPath,
4079
4076
  userClaudeMcpConfigPath,
4080
4077
  mergeSideboardIntoMcpServersJson,
4081
4078
  registerPackagedUserMcpClients,
4082
4079
  isInjectedOrchMcpName,
4083
4080
  userMcpNamesToDisable,
4081
+ PLAN_MODE_INSTRUCTION,
4082
+ permissionMode,
4083
+ mcpStatusThinkingFromClaudeInit,
4084
+ claudeAdapter,
4084
4085
  listCodexModels,
4085
4086
  codexAdapter,
4086
4087
  isCursorApiKeyConfigured,