pikiloom 0.4.26 → 0.4.27

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 (155) hide show
  1. package/dashboard/dist/assets/{AgentTab-BNJ73P0m.js → AgentTab-DIP-y4dl.js} +1 -1
  2. package/dashboard/dist/assets/ConnectionModal-BCfvhkiI.js +1 -0
  3. package/dashboard/dist/assets/{DirBrowser-D-GT-G-w.js → DirBrowser-Dot7ua9-.js} +1 -1
  4. package/dashboard/dist/assets/{ExtensionsTab-BrJMulJR.js → ExtensionsTab-GioMx4S6.js} +1 -1
  5. package/dashboard/dist/assets/{IMAccessTab-auPnx468.js → IMAccessTab-C9_r8jJP.js} +1 -1
  6. package/dashboard/dist/assets/{Modal-lYfO9m-I.js → Modal-M1AP1sgR.js} +1 -1
  7. package/dashboard/dist/assets/{Modals-PRdWOIY9.js → Modals-CCm3UUfJ.js} +1 -1
  8. package/dashboard/dist/assets/{Select-BhbU-rll.js → Select-YubDM16P.js} +1 -1
  9. package/dashboard/dist/assets/{SessionPanel-_0dLWvEb.js → SessionPanel-Cnv9o96n.js} +1 -1
  10. package/dashboard/dist/assets/SystemTab-VgNivIe3.js +1 -0
  11. package/dashboard/dist/assets/index-BY7l20bi.js +23 -0
  12. package/dashboard/dist/assets/index-BlqvzDUL.js +3 -0
  13. package/dashboard/dist/assets/index-CXIN3nTr.css +1 -0
  14. package/dashboard/dist/assets/{shared-C5uXvhZJ.js → shared-C0SAInnT.js} +1 -1
  15. package/dashboard/dist/index.html +2 -2
  16. package/dist/agent/acp-client.js +0 -59
  17. package/dist/agent/artifacts.js +1 -38
  18. package/dist/agent/auto-update.js +2 -78
  19. package/dist/agent/await-resume.js +0 -20
  20. package/dist/agent/cli/auth.js +3 -51
  21. package/dist/agent/cli/catalog.js +0 -3
  22. package/dist/agent/cli/detector.js +1 -19
  23. package/dist/agent/cli/index.js +0 -3
  24. package/dist/agent/cli/registry.js +0 -25
  25. package/dist/agent/driver.js +0 -14
  26. package/dist/agent/drivers/claude-tui.js +2 -769
  27. package/dist/agent/drivers/claude.js +12 -631
  28. package/dist/agent/drivers/codex.js +11 -162
  29. package/dist/agent/drivers/gemini.js +6 -114
  30. package/dist/agent/drivers/hermes.js +0 -128
  31. package/dist/agent/goal.js +0 -45
  32. package/dist/agent/handover.js +1 -46
  33. package/dist/agent/images.js +7 -133
  34. package/dist/agent/index.js +1 -29
  35. package/dist/agent/mcp/bridge.js +1 -197
  36. package/dist/agent/mcp/extensions.js +5 -84
  37. package/dist/agent/mcp/oauth.js +2 -69
  38. package/dist/agent/mcp/registry.js +2 -33
  39. package/dist/agent/mcp/session-server.js +2 -45
  40. package/dist/agent/mcp/tools/ask-user.js +0 -6
  41. package/dist/agent/mcp/tools/await-resume.js +0 -15
  42. package/dist/agent/mcp/tools/goal.js +0 -25
  43. package/dist/agent/mcp/tools/types.js +0 -5
  44. package/dist/agent/mcp/tools/workspace.js +0 -23
  45. package/dist/agent/npm.js +0 -14
  46. package/dist/agent/session.js +86 -196
  47. package/dist/agent/skill-installer.js +3 -70
  48. package/dist/agent/skills.js +0 -43
  49. package/dist/agent/stream.js +5 -148
  50. package/dist/agent/types.js +0 -12
  51. package/dist/agent/utils.js +2 -120
  52. package/dist/bot/bot.js +3 -468
  53. package/dist/bot/command-ui.js +0 -45
  54. package/dist/bot/commands.js +0 -51
  55. package/dist/bot/headless-bot.js +0 -21
  56. package/dist/bot/host.js +1 -5
  57. package/dist/bot/human-loop.js +0 -8
  58. package/dist/bot/menu.js +0 -3
  59. package/dist/bot/orchestration.js +0 -3
  60. package/dist/bot/render-shared.js +0 -63
  61. package/dist/bot/session-hub.js +14 -92
  62. package/dist/bot/session-status.js +1 -5
  63. package/dist/bot/streaming.js +0 -53
  64. package/dist/browser-profile.js +0 -51
  65. package/dist/browser-supervisor.js +0 -75
  66. package/dist/catalog/cli-tools.js +0 -53
  67. package/dist/catalog/index.js +0 -18
  68. package/dist/catalog/local-models.js +0 -18
  69. package/dist/catalog/mcp-servers.js +0 -35
  70. package/dist/catalog/skill-repos.js +0 -32
  71. package/dist/channels/base.js +0 -19
  72. package/dist/channels/dingtalk/bot.js +0 -3
  73. package/dist/channels/dingtalk/channel.js +0 -33
  74. package/dist/channels/discord/bot.js +0 -7
  75. package/dist/channels/discord/channel.js +0 -22
  76. package/dist/channels/feishu/bot.js +0 -71
  77. package/dist/channels/feishu/channel.js +0 -78
  78. package/dist/channels/feishu/markdown.js +0 -5
  79. package/dist/channels/feishu/render.js +0 -45
  80. package/dist/channels/health.js +0 -46
  81. package/dist/channels/slack/bot.js +0 -7
  82. package/dist/channels/slack/channel.js +1 -27
  83. package/dist/channels/states.js +0 -3
  84. package/dist/channels/telegram/bot.js +0 -60
  85. package/dist/channels/telegram/channel.js +2 -99
  86. package/dist/channels/telegram/directory.js +0 -3
  87. package/dist/channels/telegram/live-preview.js +0 -8
  88. package/dist/channels/telegram/render.js +0 -30
  89. package/dist/channels/wecom/bot.js +0 -3
  90. package/dist/channels/wecom/channel.js +0 -58
  91. package/dist/channels/weixin/api.js +0 -9
  92. package/dist/channels/weixin/bot.js +0 -88
  93. package/dist/channels/weixin/channel.js +0 -6
  94. package/dist/cli/autostart.js +1 -44
  95. package/dist/cli/channel-supervisor.js +1 -59
  96. package/dist/cli/channels.js +0 -6
  97. package/dist/cli/main.js +1 -145
  98. package/dist/cli/onboarding.js +0 -6
  99. package/dist/cli/run.js +0 -11
  100. package/dist/cli/setup-wizard.js +0 -3
  101. package/dist/core/config/runtime-config.js +0 -38
  102. package/dist/core/config/user-config.js +0 -63
  103. package/dist/core/config/validation.js +0 -9
  104. package/dist/core/constants.js +0 -268
  105. package/dist/core/git.js +0 -31
  106. package/dist/core/legacy-compat.js +1 -28
  107. package/dist/core/logging.js +0 -3
  108. package/dist/core/platform.js +0 -28
  109. package/dist/core/process-control.js +0 -8
  110. package/dist/core/secrets/index.js +0 -17
  111. package/dist/core/secrets/inline-seal.js +0 -7
  112. package/dist/core/secrets/ref.js +0 -19
  113. package/dist/core/secrets/resolver.js +0 -8
  114. package/dist/core/secrets/store.js +1 -12
  115. package/dist/core/utils.js +1 -37
  116. package/dist/core/version.js +0 -3
  117. package/dist/dashboard/platform.js +1 -35
  118. package/dist/dashboard/routes/agents.js +2 -71
  119. package/dist/dashboard/routes/cli.js +0 -27
  120. package/dist/dashboard/routes/config.js +0 -54
  121. package/dist/dashboard/routes/extensions.js +0 -132
  122. package/dist/dashboard/routes/local-models.js +0 -68
  123. package/dist/dashboard/routes/models.js +0 -46
  124. package/dist/dashboard/routes/sessions.js +0 -100
  125. package/dist/dashboard/runtime.js +1 -58
  126. package/dist/dashboard/server.js +0 -49
  127. package/dist/dashboard/session-control.js +2 -69
  128. package/dist/model/catalog.js +1 -17
  129. package/dist/model/index.js +0 -15
  130. package/dist/model/injector.js +1 -181
  131. package/dist/model/provider-models.js +1 -48
  132. package/dist/model/responses-bridge.js +3 -45
  133. package/dist/model/store.js +0 -17
  134. package/dist/model/types.js +0 -12
  135. package/dist/model/validation.js +1 -18
  136. package/dist/pikichannel/adapter-pikiloom.js +0 -31
  137. package/dist/pikichannel/code.js +0 -12
  138. package/dist/pikichannel/codec.js +0 -14
  139. package/dist/pikichannel/host.js +4 -33
  140. package/dist/pikichannel/protocol.js +0 -52
  141. package/dist/pikichannel/rendezvous-broker.js +5 -26
  142. package/dist/pikichannel/rendezvous-host.js +3 -16
  143. package/dist/pikichannel/server.js +6 -55
  144. package/dist/pikichannel/transport.js +0 -25
  145. package/dist/pikichannel/transports/webrtc-host.js +0 -18
  146. package/dist/pikichannel/transports/webrtc-shared.js +4 -34
  147. package/dist/pikichannel/transports/websocket-host.js +1 -14
  148. package/dist/pikichannel/turn.js +2 -69
  149. package/dist/pikichannel/web/sdk.js +14 -71
  150. package/package.json +1 -1
  151. package/dashboard/dist/assets/ConnectionModal-Dt05H2f9.js +0 -1
  152. package/dashboard/dist/assets/SystemTab-CN_QrRKt.js +0 -1
  153. package/dashboard/dist/assets/index-CP3LYJR2.js +0 -23
  154. package/dashboard/dist/assets/index-C_neLnaO.js +0 -3
  155. package/dashboard/dist/assets/index-CyCspgtc.css +0 -1
@@ -1,15 +1,3 @@
1
- /**
2
- * mcp-bridge.ts — MCP session bridge orchestrator.
3
- *
4
- * Runs inside the main pikiloom process. For each agent stream:
5
- * 1. Starts a tiny HTTP callback server on localhost (random port).
6
- * 2. Writes an MCP config JSON pointing to `pikiloom --mcp-serve`.
7
- * 3. The agent CLI loads that config via its MCP registration mechanism.
8
- * 4. When the agent calls `send_file`, the MCP server POSTs to our callback.
9
- * 5. We forward the request to the IM channel and respond with success/failure.
10
- *
11
- * Lifecycle: one bridge per stream, created before spawn, stopped after stream ends.
12
- */
13
1
  import http from 'node:http';
14
2
  import fs from 'node:fs';
15
3
  import os from 'node:os';
@@ -45,18 +33,15 @@ export function resolveMcpServerCommand(runtime = {
45
33
  const currentProcess = resolveCurrentCliCommand(runtime, ['--mcp-serve']);
46
34
  if (currentProcess)
47
35
  return currentProcess;
48
- // Try to find the compiled JS file in the same directory as this module
49
36
  const thisDir = path.dirname(fileURLToPath(runtime.moduleUrl));
50
37
  const serverScript = path.join(thisDir, 'session-server.js');
51
38
  if (fs.existsSync(serverScript)) {
52
39
  return { command: 'node', args: [serverScript] };
53
40
  }
54
- // Fallback: use pikiloom CLI with --mcp-serve flag
55
41
  const cliScript = path.resolve(thisDir, '../../cli/main.js');
56
42
  if (fs.existsSync(cliScript)) {
57
43
  return { command: 'node', args: [cliScript, '--mcp-serve'] };
58
44
  }
59
- // Last resort: assume pikiloom is in PATH
60
45
  return { command: 'pikiloom', args: ['--mcp-serve'] };
61
46
  }
62
47
  function parseOptionalBool(value) {
@@ -81,11 +66,6 @@ function boolFromConfigEnv(configValue, envValue, fallback) {
81
66
  return fallback;
82
67
  }
83
68
  export function resolveGuiIntegrationConfig(config = loadUserConfig(), env = process.env) {
84
- // A configured remote CDP endpoint implies the user wants browser automation,
85
- // so it flips the *default* on. An explicit PIKILOOM_BROWSER_ENABLED / config
86
- // value still wins (so `=false` can disable even with a CDP URL set). This
87
- // removes the footgun where setting only PIKILOOM_BROWSER_CDP_URL silently
88
- // injected no browser server at all.
89
69
  const browserEnabled = boolFromConfigEnv(typeof config.browserEnabled === 'boolean' ? config.browserEnabled : config.browserUseProfile, env.PIKILOOM_BROWSER_ENABLED ?? env.PIKILOOM_BROWSER_USE_PROFILE, !!getConfiguredRemoteCdpUrl(env));
90
70
  const peekabooEnabled = boolFromConfigEnv(config.peekabooEnabled, env.PIKILOOM_PEEKABOO_ENABLED, false);
91
71
  return {
@@ -95,19 +75,8 @@ export function resolveGuiIntegrationConfig(config = loadUserConfig(), env = pro
95
75
  peekabooEnabled,
96
76
  };
97
77
  }
98
- // ---------------------------------------------------------------------------
99
- // Peekaboo (native macOS GUI control) — npx package warming
100
- // ---------------------------------------------------------------------------
101
- /** npx package spec for the multi-bin Peekaboo package. */
102
78
  export const PEEKABOO_NPX_PACKAGE = '@steipete/peekaboo';
103
- /** argv for the long-running MCP server written into the agent's mcp-config. */
104
79
  export const PEEKABOO_MCP_ARGV = ['-y', '-p', PEEKABOO_NPX_PACKAGE, 'peekaboo-mcp'];
105
- /**
106
- * argv for the cache warm: the quick `peekaboo` bin (prints its version and
107
- * exits) under the SAME `-p` package spec the server uses, so it populates the
108
- * exact npx cache entry `peekaboo-mcp` later reuses. Deliberately NOT the
109
- * `peekaboo-mcp` bin — that's a server that never exits.
110
- */
111
80
  export const PEEKABOO_WARM_ARGV = ['-y', '-p', PEEKABOO_NPX_PACKAGE, 'peekaboo', '--version'];
112
81
  let peekabooWarmStarted = false;
113
82
  const PEEKABOO_ENV_ALLOWLIST = [
@@ -132,12 +101,6 @@ function cleanEnvString(value) {
132
101
  return null;
133
102
  return trimmed;
134
103
  }
135
- /**
136
- * Peekaboo only needs a normal user shell environment so npx can resolve/cache
137
- * the package. Do not inherit the full pikiloom/agent environment here: it may
138
- * contain provider API keys, channel tokens, OAuth bearer values, or database
139
- * URLs that a GUI MCP server has no reason to see.
140
- */
141
104
  export function buildPeekabooChildEnv(env = process.env) {
142
105
  const safe = {};
143
106
  for (const key of PEEKABOO_ENV_ALLOWLIST) {
@@ -162,23 +125,6 @@ function buildPeekabooMcpServer() {
162
125
  args: ['-i', ...peekabooEnvArgs(safeEnv), 'npx', ...PEEKABOO_MCP_ARGV],
163
126
  };
164
127
  }
165
- /**
166
- * Pre-warm the Peekaboo npx package so the agent's MCP server connects instantly.
167
- *
168
- * `peekaboo-mcp` ships a native Swift binary, so a cold `npx -y -p
169
- * @steipete/peekaboo …` pays a large one-time download. The agent CLI launches
170
- * that server itself and abandons it when the MCP handshake doesn't arrive
171
- * within its startup window — killing the npx child mid-download. The cache
172
- * never finishes, the next session is cold too, and Peekaboo stays stuck at
173
- * "Still connecting" indefinitely.
174
- *
175
- * Warming from a detached process that outlives any single session breaks the
176
- * loop: the download completes once, independent of the agent's timeout, and
177
- * every later session finds the package cached. Idempotent + process-singleton
178
- * (repeated stream-start calls are no-ops; npx itself short-circuits once the
179
- * package is present) and strictly fire-and-forget — it never blocks or fails
180
- * a session.
181
- */
182
128
  export function ensurePeekabooWarm() {
183
129
  if (process.platform !== 'darwin' || peekabooWarmStarted)
184
130
  return;
@@ -189,7 +135,6 @@ export function ensurePeekabooWarm() {
189
135
  detached: true,
190
136
  env: buildPeekabooChildEnv(),
191
137
  });
192
- // npx missing / spawn failure: clear the latch so a later call can retry.
193
138
  child.on('error', () => { peekabooWarmStarted = false; });
194
139
  child.unref();
195
140
  }
@@ -213,8 +158,6 @@ export function buildSupplementalMcpServers(gui = resolveGuiIntegrationConfig(),
213
158
  });
214
159
  }
215
160
  if (gui.peekabooEnabled && process.platform === 'darwin') {
216
- // Peekaboo — native macOS GUI automation via Accessibility + ScreenCaptureKit.
217
- // Run the dedicated MCP bin from the multi-bin @steipete/peekaboo package.
218
161
  servers.push(buildPeekabooMcpServer());
219
162
  }
220
163
  return servers;
@@ -237,19 +180,6 @@ function buildClaudeMcpConfig(servers) {
237
180
  ])),
238
181
  };
239
182
  }
240
- /**
241
- * Build the `codex mcp add` argv for a single registered server. Returns
242
- * `null` when the descriptor lacks the fields needed for its transport
243
- * (treated as a no-op rather than throwing — keeps a malformed entry from
244
- * breaking the whole session).
245
- *
246
- * HTTP servers can't pass a literal bearer to codex; the CLI only accepts
247
- * `--bearer-token-env-var <NAME>`. We synthesize a deterministic env-var
248
- * name per server, stash the token in the supplied `tokenEnv` map, and the
249
- * caller threads that map into the codex child process via extraEnv.
250
- *
251
- * Exported for unit tests; not re-exported from the package surface.
252
- */
253
183
  export function buildCodexMcpAddArgs(server, tokenEnv) {
254
184
  if (server.type === 'http') {
255
185
  if (!server.url)
@@ -316,8 +246,6 @@ export function redactMcpConfigForLog(configPath) {
316
246
  }
317
247
  export function buildGeminiMcpConfig(servers) {
318
248
  return {
319
- // Session attachments live under .pikiloom/... and should remain readable to
320
- // Gemini's built-in file tools even when the project ignores that directory.
321
249
  fileFiltering: {
322
250
  respectGitIgnore: false,
323
251
  respectGeminiIgnore: false,
@@ -346,43 +274,6 @@ export function buildGeminiMcpConfig(servers) {
346
274
  })),
347
275
  };
348
276
  }
349
- // ---------------------------------------------------------------------------
350
- // Stale playwright-mcp reaper
351
- // ---------------------------------------------------------------------------
352
- /**
353
- * Find and SIGTERM playwright-mcp processes that attach to the same managed
354
- * Chrome CDP endpoint but are NOT descendants of the current pikiloom process.
355
- *
356
- * Background: playwright-mcp is spawned by the agent CLI (e.g. claude) as a
357
- * child via the mcp-config we write. When the agent CLI is killed ungracefully
358
- * — or worse, gets reparented to launchd/init and survives across pikiloom
359
- * restarts — its playwright-mcp child stays alive too. Multiple playwright-mcp
360
- * instances attached to the same `--cdp-endpoint` cause backend state
361
- * confusion (microsoft/playwright-mcp#1299, #893) and manifest as
362
- * `Connection closed` errors or 2+ minute hangs the next time a tool is
363
- * called. The community's recommended hygiene is one playwright-mcp per
364
- * agent instance; this sweeper enforces that by reaping orphans from prior
365
- * runs at the start of every new bridge.
366
- *
367
- * Safety: a candidate is only reaped if its `ppid` chain — walked entirely in
368
- * memory from a single `ps` snapshot — does NOT include the current pikiloom
369
- * process. In-flight playwright-mcp children of THIS pikiloom (sibling
370
- * streams) are always spared.
371
- */
372
- /**
373
- * Pure matcher for the reaper. Returns true when `command` looks like a
374
- * playwright-mcp process attached to the same CDP endpoint as ours.
375
- *
376
- * Accepts both invocation forms we have seen in the wild:
377
- * - `node <path>/@playwright/mcp/cli.js …` (direct, pikiloom's preferred)
378
- * - `node <path>/node_modules/.bin/playwright-mcp …` (npm bin symlink,
379
- * used by `npx @playwright/mcp` and any agent CLI that resolves via PATH)
380
- *
381
- * The CDP endpoint must also appear literally in the argv — without that
382
- * guard a stray `npm exec @playwright/mcp` with its own browser would be
383
- * killed, and unrelated `node -e <src>` processes whose inline source happens
384
- * to mention `@playwright/mcp` would also be misidentified.
385
- */
386
277
  export function _matchPlaywrightMcpProcessCommand(command, normalizedCdpEndpoint) {
387
278
  if (!command || !normalizedCdpEndpoint)
388
279
  return false;
@@ -399,12 +290,7 @@ export function _matchPlaywrightMcpProcessCommand(command, normalizedCdpEndpoint
399
290
  return false;
400
291
  return true;
401
292
  }
402
- // Promisified spawn for codex MCP registration — keeps the per-server add/remove
403
- // off the event loop (was execFileSync, which blocked per spawn at stream start).
404
293
  const execFileAsync = promisify(execFile);
405
- // Reaping shells out to a full `ps` table scan + per-line regex and only guards
406
- // against playwright-mcp orphans left by a previous run, so it need not run on
407
- // every browser-enabled stream start. Throttle it per CDP endpoint.
408
294
  const REAP_THROTTLE_MS = 30_000;
409
295
  const lastReapAt = new Map();
410
296
  function reapStalePlaywrightMcpProcesses(cdpEndpoint) {
@@ -457,21 +343,13 @@ function reapStalePlaywrightMcpProcesses(cdpEndpoint) {
457
343
  reaped.push(pid);
458
344
  }
459
345
  catch {
460
- // Already dead — no-op.
461
346
  }
462
347
  }
463
348
  return { reaped, spared };
464
349
  }
465
- // ---------------------------------------------------------------------------
466
- // Stale peekaboo-mcp reaper
467
- // ---------------------------------------------------------------------------
468
350
  function commandTokenBase(token) {
469
351
  return path.basename(token.replace(/^"+|"+$/g, '')).replace(/\.(?:cmd|exe)$/i, '').toLowerCase();
470
352
  }
471
- /**
472
- * Pure matcher for stale-process hygiene. It intentionally targets the
473
- * long-running MCP server only, not the quick cache warm (`peekaboo --version`).
474
- */
475
353
  export function _matchPeekabooMcpProcessCommand(command) {
476
354
  if (!command || !command.includes('peekaboo-mcp'))
477
355
  return false;
@@ -560,51 +438,28 @@ function reapStalePeekabooMcpProcesses() {
560
438
  process.kill(pid, 'SIGKILL');
561
439
  }
562
440
  catch {
563
- // Process already exited or cannot be signalled — no-op.
564
441
  }
565
442
  }, PEEKABOO_REAP_FORCE_AFTER_MS);
566
443
  forceTimer.unref?.();
567
444
  reaped.push(pid);
568
445
  }
569
446
  catch {
570
- // Already dead — no-op.
571
447
  }
572
448
  }
573
449
  return { reaped, spared };
574
450
  }
575
- /**
576
- * Decide which CDP endpoint the per-session playwright/mcp should attach to.
577
- *
578
- * When `PIKILOOM_BROWSER_CDP_URL` is set we return it UNCONDITIONALLY (mode
579
- * `remote`) — without probing it for reachability. This is deliberate: the
580
- * documented contract is that pikiloom never launches, probes, or kills a local
581
- * Chrome in remote mode (e.g. inside a headless container that has no Chrome at
582
- * all). Gating on a reachability ping would let a momentarily-unreachable
583
- * sidecar fall through to the local-launch branch and silently spawn a browser
584
- * — exactly the bug reported in #16. Handing `--cdp-endpoint <url>` to
585
- * playwright/mcp instead surfaces an honest connection error on the first
586
- * `browser_*` call if the sidecar is down.
587
- *
588
- * Without the override, fall back to probing the local managed Chrome via its
589
- * DevToolsActivePort file (cross-process attach); `none` means leave Chrome
590
- * unlaunched and let playwright/mcp cold-start one with `--user-data-dir`.
591
- */
592
451
  export async function resolveBridgeBrowserEndpoint(profileDir = getManagedBrowserProfileDir(), remoteCdpUrl = getConfiguredRemoteCdpUrl()) {
593
452
  if (remoteCdpUrl)
594
453
  return { endpoint: remoteCdpUrl, mode: 'remote' };
595
454
  const local = await resolveManagedBrowserCdpEndpoint(profileDir).catch(() => null);
596
455
  return { endpoint: local, mode: local ? 'local-attach' : 'none' };
597
456
  }
598
- // ---------------------------------------------------------------------------
599
- // Bridge implementation
600
- // ---------------------------------------------------------------------------
601
457
  const ARTIFACT_MAX_BYTES = MCP_ARTIFACT_MAX_BYTES;
602
458
  const SEND_FILE_TIMEOUT_MS = MCP_TIMEOUTS.sendFile;
603
459
  const PHOTO_EXTS = new Set(['.jpg', '.jpeg', '.png', '.webp']);
604
460
  function isPhotoFile(filePath) {
605
461
  return PHOTO_EXTS.has(path.extname(filePath).toLowerCase());
606
462
  }
607
- /** Check if realFile is inside any of the allowed root directories. */
608
463
  function isInsideAllowedRoot(realFile, allowedRoots) {
609
464
  for (const root of allowedRoots) {
610
465
  try {
@@ -613,7 +468,7 @@ function isInsideAllowedRoot(realFile, allowedRoots) {
613
468
  if (rel && !rel.startsWith('..') && !path.isAbsolute(rel))
614
469
  return true;
615
470
  }
616
- catch { /* root doesn't exist, skip */ }
471
+ catch { }
617
472
  }
618
473
  return false;
619
474
  }
@@ -656,7 +511,6 @@ export function resolveSendFilePath(inputPath, workspacePath, stagedFiles = [],
656
511
  return { path: candidate };
657
512
  }
658
513
  catch {
659
- // Try next candidate.
660
514
  }
661
515
  }
662
516
  if (!requested.includes('/') && !requested.includes(path.sep)) {
@@ -700,9 +554,6 @@ export async function startMcpBridge(opts) {
700
554
  const gui = resolveGuiIntegrationConfig();
701
555
  for (const hint of buildGuiSetupHints(gui))
702
556
  opts.onLog?.(hint);
703
- // Peekaboo ships a native binary behind npx; warm its cache out-of-band so the
704
- // agent's MCP server connects instantly instead of hanging at "Still
705
- // connecting" on a cold first-run download (see ensurePeekabooWarm).
706
557
  if (gui.peekabooEnabled) {
707
558
  ensurePeekabooWarm();
708
559
  const { reaped, spared } = reapStalePeekabooMcpProcesses();
@@ -710,16 +561,8 @@ export async function startMcpBridge(opts) {
710
561
  opts.onLog?.(`reaped ${reaped.length} stale peekaboo-mcp process(es): pid=${reaped.join(',')}${spared.length ? ` (spared active: ${spared.join(',')})` : ''}`);
711
562
  }
712
563
  }
713
- // Lazy browser lifecycle: probe an already-running managed Chrome via
714
- // <profileDir>/DevToolsActivePort and attach if reachable; otherwise leave
715
- // Chrome unlaunched and let playwright/mcp launch it with `--user-data-dir`
716
- // on the first browser_* tool call. Previously the bridge eagerly called
717
- // `ensureManagedBrowser`, which forced a Chrome window to open at every
718
- // stream start even when the agent never touched the browser.
719
564
  let browserCdpEndpoint = null;
720
565
  if (gui.browserEnabled) {
721
- // Write the playwright/mcp config file (referenced by --config in
722
- // getManagedBrowserMcpArgs) before the agent CLI spawns playwright/mcp.
723
566
  ensurePlaywrightMcpConfigFile();
724
567
  const { endpoint, mode } = await resolveBridgeBrowserEndpoint(gui.browserProfileDir);
725
568
  browserCdpEndpoint = endpoint;
@@ -727,10 +570,6 @@ export async function startMcpBridge(opts) {
727
570
  opts.onLog?.(mode === 'remote'
728
571
  ? `attaching to remote CDP endpoint ${endpoint} (PIKILOOM_BROWSER_CDP_URL); local Chrome launch disabled.`
729
572
  : `attaching to existing managed browser at ${endpoint}.`);
730
- // Clear stale playwright-mcp children still bound to this endpoint (one
731
- // playwright-mcp per browser, per microsoft/playwright-mcp#1299). Safe for
732
- // the remote sidecar too — it only ever SIGTERMs local playwright-mcp
733
- // processes, never the Chrome itself.
734
573
  const { reaped, spared } = reapStalePlaywrightMcpProcesses(endpoint);
735
574
  if (reaped.length) {
736
575
  opts.onLog?.(`reaped ${reaped.length} stale playwright-mcp process(es) attached to ${endpoint}: pid=${reaped.join(',')}${spared.length ? ` (spared in-tree: ${spared.join(',')})` : ''}`);
@@ -740,16 +579,10 @@ export async function startMcpBridge(opts) {
740
579
  opts.onLog?.('no managed browser running; playwright/mcp will launch one on first browser_* tool call.');
741
580
  }
742
581
  }
743
- // Build allowed roots: workspace + workdir + /tmp
744
582
  const allowedRoots = [workspacePath];
745
583
  if (opts.workdir)
746
584
  allowedRoots.push(opts.workdir);
747
585
  allowedRoots.push('/tmp', os.tmpdir());
748
- // ── HTTP callback server ──
749
- // Started only when an IM-side callback is wired up, to serve:
750
- // - `im_send_file` → /send-file
751
- // - `im_ask_user` → /ask-user
752
- // - structured tool-activity logging from the in-process MCP server → /log
753
586
  let callbackServer = null;
754
587
  let port = 0;
755
588
  const needsCallbackServer = !!sendFile || !!onInteraction;
@@ -762,14 +595,12 @@ export async function startMcpBridge(opts) {
762
595
  res.end();
763
596
  return;
764
597
  }
765
- // /ask-user blocks until the user replies; disable timeouts for it.
766
598
  if (endpoint === '/ask-user') {
767
599
  req.setTimeout(0);
768
600
  res.setTimeout(0);
769
601
  }
770
602
  let body = '';
771
603
  req.on('data', (chunk) => { body += chunk; });
772
- // Timeout for receiving the request body
773
604
  const bodyTimer = setTimeout(() => {
774
605
  req.destroy(new Error('request body timeout'));
775
606
  }, MCP_TIMEOUTS.requestBody);
@@ -845,7 +676,6 @@ export async function startMcpBridge(opts) {
845
676
  }
846
677
  return;
847
678
  }
848
- // endpoint === '/send-file'
849
679
  if (!sendFile) {
850
680
  res.writeHead(503, { 'Content-Type': 'application/json' });
851
681
  res.end(JSON.stringify({ ok: false, error: 'send-file is not available for this session' }));
@@ -858,7 +688,6 @@ export async function startMcpBridge(opts) {
858
688
  res.end(JSON.stringify({ ok: false, error: 'path is required' }));
859
689
  return;
860
690
  }
861
- // Resolve and validate path
862
691
  const resolved = resolveSendFilePath(relPath, workspacePath, stagedFiles, opts.workdir);
863
692
  const absPath = resolved.path;
864
693
  let realFile;
@@ -875,7 +704,6 @@ export async function startMcpBridge(opts) {
875
704
  res.end(JSON.stringify({ ok: false, error: 'file must be inside the workspace, workdir, or /tmp' }));
876
705
  return;
877
706
  }
878
- // Size check
879
707
  const stat = fs.statSync(realFile);
880
708
  if (!stat.isFile()) {
881
709
  res.writeHead(400, { 'Content-Type': 'application/json' });
@@ -887,7 +715,6 @@ export async function startMcpBridge(opts) {
887
715
  res.end(JSON.stringify({ ok: false, error: `file too large (${stat.size} bytes, max ${ARTIFACT_MAX_BYTES})` }));
888
716
  return;
889
717
  }
890
- // Auto-detect kind
891
718
  const kind = data.kind === 'photo' ? 'photo'
892
719
  : data.kind === 'document' ? 'document'
893
720
  : isPhotoFile(realFile) ? 'photo'
@@ -907,10 +734,7 @@ export async function startMcpBridge(opts) {
907
734
  }
908
735
  });
909
736
  });
910
- // Per-request body timers above guard against partial uploads.
911
737
  callbackServer.headersTimeout = MCP_TIMEOUTS.serverHeaders;
912
- // /ask-user can block indefinitely; drop the server-wide request timeout
913
- // when that endpoint is wired up.
914
738
  if (onInteraction)
915
739
  callbackServer.requestTimeout = 0;
916
740
  await new Promise((resolve, reject) => {
@@ -919,18 +743,13 @@ export async function startMcpBridge(opts) {
919
743
  });
920
744
  port = callbackServer.address().port;
921
745
  }
922
- // ── Register MCP server with the agent ──
923
746
  const supplementalServers = buildSupplementalMcpServers(gui, { cdpEndpoint: browserCdpEndpoint });
924
747
  const servers = [...supplementalServers];
925
- // Register the pikiloom stdio MCP server when any in-process tool needs the
926
- // callback channel. `MCP_TOOLS_AVAILABLE` tells the server which tool
927
- // families to advertise.
928
748
  if (port && (sendFile || onInteraction)) {
929
749
  const { command, args } = resolveMcpServerCommand();
930
750
  const enabledTools = [];
931
751
  if (sendFile)
932
752
  enabledTools.push('workspace');
933
- // Codex has native user-input via JSON-RPC; don't expose `im_ask_user`.
934
753
  if (onInteraction && opts.agent !== 'codex')
935
754
  enabledTools.push('ask-user');
936
755
  const envVars = {
@@ -944,7 +763,6 @@ export async function startMcpBridge(opts) {
944
763
  };
945
764
  servers.unshift({ name: 'pikiloom', command, args, env: envVars });
946
765
  }
947
- // Nothing to register — skip bridge entirely
948
766
  if (!servers.length) {
949
767
  if (callbackServer)
950
768
  await new Promise(resolve => callbackServer.close(() => resolve()));
@@ -955,16 +773,9 @@ export async function startMcpBridge(opts) {
955
773
  let mcpServers;
956
774
  const codexRegisteredNames = [];
957
775
  if (opts.agent === 'codex') {
958
- // Codex: register MCP servers via `codex mcp add/remove`
959
- // Include global + workspace extensions alongside built-in servers
960
776
  const extServers = getGlobalExtensionsAsServers(opts.workdir);
961
777
  const allServers = [...extServers, ...servers];
962
- // Bearer tokens for HTTP MCP servers are injected into codex's process env
963
- // via extraEnv — codex's `--bearer-token-env-var` only accepts an env name,
964
- // never a literal token, so the value MUST land in the child env.
965
778
  const codexBearerEnv = {};
966
- // Sequential (codex serializes its own config writes) but async, so the
967
- // per-server spawns don't block the event loop at stream start.
968
779
  for (const server of allServers) {
969
780
  const codexArgs = buildCodexMcpAddArgs(server, codexBearerEnv);
970
781
  if (!codexArgs)
@@ -987,8 +798,6 @@ export async function startMcpBridge(opts) {
987
798
  }
988
799
  }
989
800
  else if (opts.agent === 'gemini') {
990
- // Gemini CLI 0.32+ loads MCP servers from settings.json rather than --mcp-config.
991
- // Include global + workspace extensions alongside built-in servers
992
801
  const extServers = getGlobalExtensionsAsServers(opts.workdir);
993
802
  const allServers = [...extServers, ...servers];
994
803
  configPath = path.join(sessionDir, 'gemini-system-settings.json');
@@ -998,14 +807,9 @@ export async function startMcpBridge(opts) {
998
807
  extraEnv = { GEMINI_CLI_SYSTEM_SETTINGS_PATH: configPath };
999
808
  }
1000
809
  else if (opts.agent === 'hermes') {
1001
- // Hermes consumes structured MCP server objects via ACP `session/new`,
1002
- // not a config file path. Resolve the merged server list and expose it
1003
- // on the bridge handle so the driver can translate to ACP's wire format.
1004
810
  mcpServers = mergeExtensionsForSession(servers, opts.workdir);
1005
811
  }
1006
812
  else {
1007
- // Claude: write MCP config JSON for --mcp-config
1008
- // Uses centralized merge: global extensions → .mcp.json files → built-in servers
1009
813
  configPath = path.join(sessionDir, 'mcp-config.json');
1010
814
  mcpServers = mergeExtensionsForSession(servers, opts.workdir);
1011
815
  fs.mkdirSync(sessionDir, { recursive: true });