@cydm/pie 2.5.0 → 2.5.2

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 (25) hide show
  1. package/dist/builtin/extensions/ask-user/index.js +3 -3
  2. package/dist/builtin/extensions/computer-use/index.js +17 -2
  3. package/dist/builtin/extensions/computer-use/prebuilt/macos/arm64/pie-computer-use +0 -0
  4. package/dist/builtin/extensions/computer-use/prebuilt/macos/universal/pie-computer-use.app/Contents/MacOS/pie-computer-use +0 -0
  5. package/dist/builtin/extensions/computer-use/prebuilt/macos/x64/pie-computer-use +0 -0
  6. package/dist/builtin/extensions/computer-use/scripts/setup-helper.mjs +90 -9
  7. package/dist/builtin/extensions/herdr-agent-state/index.js +39 -3
  8. package/dist/builtin/extensions/mcp-adapter/index.js +161 -35
  9. package/dist/builtin/extensions/plan-mode/index.js +3 -3
  10. package/dist/builtin/extensions/subagent/index.js +4 -4
  11. package/dist/builtin/extensions/todo/index.js +3 -3
  12. package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-bridge.js +5 -0
  13. package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-cli.js +5 -2
  14. package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-runtime.js +1 -1
  15. package/dist/builtin/skills/chrome-devtools-axi/{cli-P4VG6ZZG.js → cli-EJ5MQZNA.js} +76 -27
  16. package/dist/builtin/skills/tui-use/tui-use-cli.cjs +25 -9
  17. package/dist/builtin/skills/tui-use/tui-use-daemon.cjs +7 -1
  18. package/dist/chunks/{chunk-JXY3YLDS.js → chunk-LO2V5DZZ.js} +46 -9
  19. package/dist/chunks/{chunk-PU7IIEKH.js → chunk-M4OJARWJ.js} +25 -15
  20. package/dist/chunks/{chunk-ZKHMNEXL.js → chunk-RTTGO2A6.js} +1 -1
  21. package/dist/chunks/{chunk-YRXXRLMQ.js → chunk-UD7KYQ6J.js} +9 -0
  22. package/dist/chunks/{src-SXUQ7OY6.js → src-33WRCQWX.js} +2 -2
  23. package/dist/chunks/{test-stream-OD3B57AS.js → test-stream-LFR6GSKQ.js} +1 -1
  24. package/dist/cli.js +117 -20
  25. package/package.json +1 -1
@@ -1,9 +1,9 @@
1
1
  import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  createAskUserCapability
4
- } from "../../../chunks/chunk-JXY3YLDS.js";
5
- import "../../../chunks/chunk-ZKHMNEXL.js";
6
- import "../../../chunks/chunk-YRXXRLMQ.js";
4
+ } from "../../../chunks/chunk-LO2V5DZZ.js";
5
+ import "../../../chunks/chunk-RTTGO2A6.js";
6
+ import "../../../chunks/chunk-UD7KYQ6J.js";
7
7
  import "../../../chunks/chunk-XXGYUYXI.js";
8
8
  import "../../../chunks/chunk-LZQXQOPJ.js";
9
9
 
@@ -3787,8 +3787,23 @@ function wireableInputDescendants(node) {
3787
3787
  visit(node);
3788
3788
  return matches;
3789
3789
  }
3790
+ var FOCUS_CHANGING_KEYS = /* @__PURE__ */ new Set(["TAB", "SHIFT+TAB", "ENTER", "RETURN", "SPACE", "UP", "DOWN", "LEFT", "RIGHT", "UP_ARROW", "DOWN_ARROW", "LEFT_ARROW", "RIGHT_ARROW", "ESC", "ESCAPE", "HOME", "END", "PAGE_UP", "PAGE_DOWN", "PAGEDOWN", "PAGEUP"]);
3791
+ function isFocusChangingKey(keys2) {
3792
+ const list = typeof keys2 === "string" ? [keys2] : Array.isArray(keys2) ? keys2 : [];
3793
+ for (const raw of list) {
3794
+ if (typeof raw !== "string") continue;
3795
+ const token = raw.trim().toUpperCase().split("+").pop() ?? "";
3796
+ if (FOCUS_CHANGING_KEYS.has(token) || FOCUS_CHANGING_KEYS.has(raw.trim().toUpperCase())) return true;
3797
+ }
3798
+ return false;
3799
+ }
3790
3800
  function recordActionFocus(action, state, node, coordinateFocus = false) {
3791
- if (!["click", "press", "keypress", "typeText"].includes(action.action) || !action.ref && action.action !== "click") return;
3801
+ if (action.action === "keypress" && !action.ref && isFocusChangingKey(action.keys)) {
3802
+ state.currentFocus = true;
3803
+ state.focusRef = void 0;
3804
+ return;
3805
+ }
3806
+ if (!["click", "press", "keypress", "typeText"].includes(action.action) || !action.ref && action.action !== "click" && action.action !== "press" && !coordinateFocus) return;
3792
3807
  state.currentFocus = Boolean(node?.isTextInput && !node.pictureOnly) || coordinateFocus;
3793
3808
  state.focusRef = node?.isTextInput && node.wireRef && !node.pictureOnly ? node.ref : void 0;
3794
3809
  if (!state.currentFocus && node && !node.pictureOnly && action.ref && (action.action === "click" || action.action === "press") && containsEditable(node)) {
@@ -8546,7 +8561,7 @@ async function dispatchUiAction(action, target, look, headless, state, signal) {
8546
8561
  }
8547
8562
  const trace = await helperAct(target, prepared, headless, signal);
8548
8563
  if (!headless) {
8549
- const coordinateFocus = prepared.action === "click" && "x" in prepared.target;
8564
+ const coordinateFocus = (prepared.action === "click" || prepared.action === "press") && "x" in prepared.target;
8550
8565
  const node = action.ref ? outlineNodeByRef(action.ref.trim()) : coordinateFocus ? editorAtPoint(currentOutlineOrThrow().nodes, action.x, action.y) : void 0;
8551
8566
  recordActionFocus(action, state, node, coordinateFocus);
8552
8567
  }
@@ -313,19 +313,100 @@ async function installPrebuiltHelperApp(sourceAppPath) {
313
313
  const existingInfo = await fs.readFile(path.join(helperAppPath, "Contents", "Info.plist"), "utf8").catch(() => undefined);
314
314
  const sourceInfo = await fs.readFile(sourceInfoPath, "utf8");
315
315
  if (existingExecutable?.equals(sourceExecutable) && existingInfo === sourceInfo) {
316
- await registerHelperApp();
317
- return false;
316
+ // Byte equality alone is NOT sufficient for the no-change shortcut: a
317
+ // damaged/missing _CodeSignature (CodeResources tampered or deleted
318
+ // around an otherwise-identical executable) would otherwise survive
319
+ // every repair invocation. The existing bundle must still verify.
320
+ let existingVerifies = false;
321
+ try {
322
+ await run("codesign", ["--verify", "--strict", helperAppPath]);
323
+ existingVerifies = true;
324
+ } catch {
325
+ // Fall through to the replacement path below, which re-installs (and
326
+ // re-verifies) a complete bundle.
327
+ }
328
+ if (existingVerifies) {
329
+ await registerHelperApp();
330
+ return false;
331
+ }
318
332
  }
319
333
  // The sealed bundle must arrive intact — a broken signature would burn
320
334
  // the user's TCC grants on an identity that can never validate.
321
335
  await run("codesign", ["--verify", "--strict", sourceAppPath]);
322
- await fs.rm(helperAppPath, { force: true, recursive: true });
323
- // ditto preserves the bundle byte-for-byte (signature + stapled
324
- // notarization ticket). The sealed app is NEVER re-signed here: its
325
- // Developer ID designated requirement (identifier + team) is exactly
326
- // what gives release builds their best chance of retaining TCC grants.
327
- await run("/usr/bin/ditto", [sourceAppPath, helperAppPath]);
328
- await registerHelperApp();
336
+ // Sweep staging directories abandoned by crashed installers (the finally
337
+ // below only runs on ordinary unwinding). Reclaim is ownership-aware:
338
+ // the staging name embeds its creator's PID, and ONLY a demonstrably dead
339
+ // owner (ESRCH on signal-0) plus age beyond a generous slow-install window
340
+ // makes a stage garbage — mtime alone is not ownership evidence, so a
341
+ // live/slow installer's stage is never touched.
342
+ try {
343
+ const parent = path.dirname(helperAppPath);
344
+ const base = path.basename(helperAppPath);
345
+ const entries = await fs.readdir(parent);
346
+ const cutoff = Date.now() - 60 * 60 * 1000;
347
+ for (const entry of entries) {
348
+ // Exact staging format <app>.staged-<pid>-<ts36>: anything else that
349
+ // merely contains ".staged-" is not ours to touch.
350
+ const match = new RegExp(`^${base.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.staged-(\\d+)-[0-9a-z]+$`).exec(entry);
351
+ if (!match) continue;
352
+ const staged = path.join(parent, entry);
353
+ try {
354
+ const stat = await fs.stat(staged);
355
+ if (stat.mtimeMs >= cutoff) continue;
356
+ const ownerPid = Number(match[1]);
357
+ let ownerAlive = false;
358
+ try {
359
+ process.kill(ownerPid, 0);
360
+ ownerAlive = true;
361
+ } catch (error) {
362
+ // ESRCH: dead. EPERM (exists, other user): treat as alive —
363
+ // conservative, never delete on unknown liveness.
364
+ ownerAlive = error && error.code === "EPERM";
365
+ }
366
+ if (!ownerAlive) await fs.rm(staged, { force: true, recursive: true });
367
+ } catch { /* raced away */ }
368
+ }
369
+ } catch { /* no parent dir yet */ }
370
+ // Stage-then-swap: the staged copy is verified before publication and the
371
+ // previous installation survives every failure up to the final swap.
372
+ // NOTE: the swap itself (rm + rename) is NOT atomic — directory bundles
373
+ // have no atomic exchange on stock Node — but the failure window no longer
374
+ // starts before verification, and a crash mid-swap self-heals on the next
375
+ // run (which re-stages from the verified source).
376
+ const stagingPath = `${helperAppPath}.staged-${process.pid}-${Date.now().toString(36)}`;
377
+ await fs.rm(stagingPath, { force: true, recursive: true });
378
+ try {
379
+ // ditto preserves the bundle byte-for-byte (signature + stapled
380
+ // notarization ticket). The sealed app is NEVER re-signed here: its
381
+ // Developer ID designated requirement (identifier + team) is exactly
382
+ // what gives release builds their best chance of retaining TCC grants.
383
+ await run("/usr/bin/ditto", [sourceAppPath, stagingPath]);
384
+ // Verify the STAGED bundle (not just the source): source mutation
385
+ // between the verify above and the copy, or a torn copy, stops here.
386
+ await run("codesign", ["--verify", "--strict", stagingPath]);
387
+ let published = false;
388
+ try {
389
+ await fs.rm(helperAppPath, { force: true, recursive: true });
390
+ await fs.rename(stagingPath, helperAppPath);
391
+ published = true;
392
+ } catch (renameError) {
393
+ // Cross-device or otherwise unrenamable staging: fall back to a
394
+ // second ditto onto the final path, then verify the destination. A
395
+ // VERIFIED fallback is a successful installation — continue to
396
+ // registration instead of failing after doing the work.
397
+ try {
398
+ await run("/usr/bin/ditto", [stagingPath, helperAppPath]);
399
+ await run("codesign", ["--verify", "--strict", helperAppPath]);
400
+ published = true;
401
+ } catch (fallbackError) {
402
+ fallbackError.cause = renameError;
403
+ throw fallbackError;
404
+ }
405
+ }
406
+ if (published) await registerHelperApp();
407
+ } finally {
408
+ await fs.rm(stagingPath, { force: true, recursive: true }).catch(() => {});
409
+ }
329
410
  return true;
330
411
  }
331
412
 
@@ -136,7 +136,42 @@ function spawnHerdrReport(binPath, args, timeoutMs = REPORT_TIMEOUT_MS) {
136
136
  } catch {
137
137
  }
138
138
  }
139
- function attachHerdrReporter(host, herdr, transport = (args) => spawnHerdrReport(herdr.binPath, args), sequencer = createReportSequencer()) {
139
+ var KILL_GRACE_MS = 1e3;
140
+ async function runHerdrReportBounded(binPath, args, timeoutMs = REPORT_TIMEOUT_MS) {
141
+ let child;
142
+ try {
143
+ child = spawn(binPath, args, { detached: true, stdio: "ignore" });
144
+ } catch {
145
+ return { outcome: "spawn-error", exitCode: null };
146
+ }
147
+ return await new Promise((resolve) => {
148
+ let killTimer;
149
+ let settled = false;
150
+ const finish = (outcome, exitCode) => {
151
+ if (settled) return;
152
+ settled = true;
153
+ clearTimeout(timer);
154
+ if (killTimer) clearTimeout(killTimer);
155
+ resolve({ outcome, exitCode });
156
+ };
157
+ const timer = setTimeout(() => {
158
+ try {
159
+ child.kill("SIGTERM");
160
+ } catch {
161
+ }
162
+ killTimer = setTimeout(() => {
163
+ try {
164
+ if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
165
+ } catch {
166
+ }
167
+ killTimer = setTimeout(() => finish("escalated", child.exitCode), 250);
168
+ }, KILL_GRACE_MS);
169
+ }, timeoutMs);
170
+ child.on("error", () => finish("spawn-error", null));
171
+ child.on("exit", (code) => finish("exited", code));
172
+ });
173
+ }
174
+ function attachHerdrReporter(host, herdr, transport = (args) => spawnHerdrReport(herdr.binPath, args), sequencer = createReportSequencer(), shutdownTransport = (args) => runHerdrReportBounded(herdr.binPath, args)) {
140
175
  const nextSeq = sequencer;
141
176
  let agentActive = false;
142
177
  const blockingToolCalls = /* @__PURE__ */ new Map();
@@ -238,10 +273,10 @@ function attachHerdrReporter(host, herdr, transport = (args) => spawnHerdrReport
238
273
  }
239
274
  publishState();
240
275
  });
241
- host.registerCleanup?.((reason) => {
276
+ host.registerCleanup?.(async (reason) => {
242
277
  if (reason !== "shutdown") return;
243
278
  if (!heldPane) return;
244
- report(
279
+ await shutdownTransport(
245
280
  buildReleaseReportArgs({
246
281
  paneId: herdr.paneId,
247
282
  source: SOURCE_ID,
@@ -267,6 +302,7 @@ export {
267
302
  createReportSequencer,
268
303
  herdrAgentStateExtension as default,
269
304
  readHerdrEnvironment,
305
+ runHerdrReportBounded,
270
306
  spawnHerdrReport,
271
307
  summarizeAskUserQuestion
272
308
  };
@@ -24434,6 +24434,7 @@ var StdioClientTransport = class {
24434
24434
  };
24435
24435
 
24436
24436
  // builtin/extensions/mcp-adapter/src/server-manager.ts
24437
+ import { createHash } from "node:crypto";
24437
24438
  import * as fs2 from "node:fs";
24438
24439
  import * as path2 from "node:path";
24439
24440
  var CONNECT_TIMEOUT_MS = 3e4;
@@ -24442,6 +24443,41 @@ var PACKAGE_MANAGER_COMMANDS = /* @__PURE__ */ new Set(["npx", "npm", "bunx", "u
24442
24443
  var METADATA_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
24443
24444
  var CALL_TIMEOUT_MS = 12e4;
24444
24445
  var STDERR_TAIL_LIMIT = 4e3;
24446
+ var DIAGNOSTIC_TEXT_LIMIT = 1500;
24447
+ function definitionSecrets(definition) {
24448
+ const secrets = [];
24449
+ for (const value of Object.values(definition.env ?? {})) {
24450
+ if (typeof value === "string" && value.length > 0) secrets.push(value);
24451
+ }
24452
+ for (const value of Object.values(definition.headers ?? {})) {
24453
+ if (typeof value === "string" && value.length > 0) secrets.push(value);
24454
+ }
24455
+ return secrets.sort((a, b) => b.length - a.length);
24456
+ }
24457
+ function redactSecrets(text, secrets) {
24458
+ let sanitized = text;
24459
+ for (const secret of secrets) {
24460
+ sanitized = sanitized.split(secret).join("[redacted]");
24461
+ }
24462
+ return sanitized;
24463
+ }
24464
+ function sanitizeDiagnostic(text, secrets) {
24465
+ let sanitized = redactSecrets(text, secrets);
24466
+ if (secrets.length > 0 && sanitized.length > 0) {
24467
+ for (const secret of secrets) {
24468
+ for (let keep = Math.min(secret.length - 1, sanitized.length); keep >= 4; keep--) {
24469
+ if (sanitized.startsWith(secret.slice(secret.length - keep))) {
24470
+ sanitized = `[redacted]${sanitized.slice(keep)}`;
24471
+ break;
24472
+ }
24473
+ }
24474
+ }
24475
+ }
24476
+ if (sanitized.length > DIAGNOSTIC_TEXT_LIMIT) {
24477
+ sanitized = `${sanitized.slice(0, DIAGNOSTIC_TEXT_LIMIT)}\u2026[truncated ${sanitized.length - DIAGNOSTIC_TEXT_LIMIT} chars]`;
24478
+ }
24479
+ return sanitized;
24480
+ }
24445
24481
  function connectTimeoutFor(definition) {
24446
24482
  if (definition.connectTimeoutMs !== void 0) return definition.connectTimeoutMs;
24447
24483
  const base = path2.basename(definition.command ?? "");
@@ -24499,6 +24535,22 @@ function withTimeout(promise, timeoutMs, label, signal) {
24499
24535
  );
24500
24536
  });
24501
24537
  }
24538
+ function cacheFingerprint(definition) {
24539
+ return createHash("sha256").update(JSON.stringify({
24540
+ sourceScope: definition.sourceScope,
24541
+ sourcePath: definition.sourcePath,
24542
+ name: definition.name,
24543
+ command: definition.command,
24544
+ args: definition.args,
24545
+ // Canonicalized (key-sorted) so reordering equivalent env/header keys
24546
+ // does not invalidate cached metadata.
24547
+ env: Object.fromEntries(Object.entries(definition.env ?? {}).sort(([a], [b]) => a.localeCompare(b))),
24548
+ serverCwd: definition.cwd,
24549
+ url: definition.url,
24550
+ headers: Object.fromEntries(Object.entries(definition.headers ?? {}).sort(([a], [b]) => a.localeCompare(b))),
24551
+ inheritEnv: definition.inheritEnv
24552
+ })).digest("hex").slice(0, 24);
24553
+ }
24502
24554
  var McpServerManager = class {
24503
24555
  constructor(definitions, cacheDir, log, now = Date.now) {
24504
24556
  this.now = now;
@@ -24577,8 +24629,9 @@ var McpServerManager = class {
24577
24629
  stderr: "pipe"
24578
24630
  });
24579
24631
  if (transport2.stderr) {
24632
+ const captureSecrets = definitionSecrets(definition);
24580
24633
  transport2.stderr.on("data", (chunk) => {
24581
- runtime.stderrTail = appendTail(runtime.stderrTail, chunk);
24634
+ runtime.stderrTail = appendTail(runtime.stderrTail, redactSecrets(String(chunk), captureSecrets));
24582
24635
  });
24583
24636
  }
24584
24637
  return { client, transport: transport2 };
@@ -24669,7 +24722,7 @@ var McpServerManager = class {
24669
24722
  if (!runtime.tools) continue;
24670
24723
  const shardPath = path2.join(this.cacheDir, `${key}.json`);
24671
24724
  const tempPath = `${shardPath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
24672
- fs2.writeFileSync(tempPath, JSON.stringify({ updatedAt: this.now(), tools: runtime.tools }, null, 2), "utf8");
24725
+ fs2.writeFileSync(tempPath, JSON.stringify({ updatedAt: this.now(), fingerprint: cacheFingerprint(runtime.definition), tools: runtime.tools }, null, 2), "utf8");
24673
24726
  try {
24674
24727
  fs2.renameSync(tempPath, shardPath);
24675
24728
  } catch (error2) {
@@ -24704,6 +24757,7 @@ var McpServerManager = class {
24704
24757
  }
24705
24758
  }
24706
24759
  /** Cached descriptors for one server (from the metadata cache file). */
24760
+ /** Cached descriptors for one server (from the metadata cache file). */
24707
24761
  cachedToolsFor(server) {
24708
24762
  return this.loadCatalog().filter((tool) => tool.server === sanitizeName(server));
24709
24763
  }
@@ -24722,13 +24776,16 @@ var McpServerManager = class {
24722
24776
  catalog.set(tool.prefixedName, tool);
24723
24777
  ownerOf.set(tool.prefixedName, tool.server);
24724
24778
  };
24725
- for (const tools of Object.values(this.readCacheFile())) {
24726
- for (const tool of tools) {
24727
- if (!this.definitions.has(tool.server)) continue;
24779
+ for (const [key, shard] of Object.entries(this.readCacheFile())) {
24780
+ const definition = this.definitions.get(key);
24781
+ if (!definition || definition.disabled) continue;
24782
+ if (shard.fingerprint !== cacheFingerprint(definition)) continue;
24783
+ for (const tool of shard.tools) {
24728
24784
  claim2(tool, false);
24729
24785
  }
24730
24786
  }
24731
24787
  for (const runtime of this.runtimes.values()) {
24788
+ if (runtime.definition.disabled) continue;
24732
24789
  for (const tool of runtime.tools ?? []) claim2(tool, true);
24733
24790
  }
24734
24791
  for (const name of conflicts) {
@@ -24784,7 +24841,10 @@ var McpServerManager = class {
24784
24841
  if (typeof parsed.updatedAt !== "number" || this.now() - parsed.updatedAt > METADATA_TTL_MS) continue;
24785
24842
  if (!Array.isArray(parsed.tools)) continue;
24786
24843
  const key = name.slice(0, -".json".length);
24787
- snapshot[key] = parsed.tools;
24844
+ snapshot[key] = {
24845
+ tools: parsed.tools,
24846
+ ...typeof parsed.fingerprint === "string" ? { fingerprint: parsed.fingerprint } : {}
24847
+ };
24788
24848
  updatedAt[key] = parsed.updatedAt;
24789
24849
  } catch {
24790
24850
  }
@@ -24828,12 +24888,16 @@ var McpServerManager = class {
24828
24888
  } catch (error2) {
24829
24889
  const runtimeState = this.runtimes.get(server);
24830
24890
  const tail = runtimeState?.stderrTail.trim();
24891
+ const secrets = definitionSecrets(definition);
24831
24892
  return {
24832
24893
  delivery: "not_sent",
24833
- error: `MCP server "${server}" failed to start: ${error2 instanceof Error ? error2.message : String(error2)}` + (tail ? `
24894
+ error: sanitizeDiagnostic(
24895
+ `MCP server "${server}" failed to start: ${error2 instanceof Error ? error2.message : String(error2)}` + (tail ? `
24834
24896
 
24835
24897
  Server stderr tail:
24836
- ${tail}` : "")
24898
+ ${tail}` : ""),
24899
+ secrets
24900
+ )
24837
24901
  };
24838
24902
  }
24839
24903
  const client = runtime.client;
@@ -24849,6 +24913,20 @@ ${tail}` : "")
24849
24913
  error: `MCP call "${prefixedName}" was cancelled before the request was sent; nothing was executed.`
24850
24914
  };
24851
24915
  }
24916
+ const postConnectCatalog = this.loadCatalog();
24917
+ if (this.conflictedNames.has(prefixedName)) {
24918
+ return {
24919
+ delivery: "not_sent",
24920
+ error: `MCP tool name "${prefixedName}" became claimed by more than one server during connection. Rename one of the servers so every tool name is unambiguous; the call was NOT sent.`
24921
+ };
24922
+ }
24923
+ const postDescriptor = postConnectCatalog.find((tool) => tool.prefixedName === prefixedName);
24924
+ if (postDescriptor && (postDescriptor.server !== server || postDescriptor.tool !== toolName)) {
24925
+ return {
24926
+ delivery: "not_sent",
24927
+ error: `MCP tool "${prefixedName}" resolved to ${postDescriptor.server}/${postDescriptor.tool} after connecting; refusing to send the stale target ${server}/${toolName}. Retry the call.`
24928
+ };
24929
+ }
24852
24930
  try {
24853
24931
  const result = await withTimeout(
24854
24932
  client.callTool({ name: toolName, arguments: args }, void 0, signal !== void 0 ? { signal } : void 0),
@@ -24863,7 +24941,7 @@ ${tail}` : "")
24863
24941
  isError: result.isError === true
24864
24942
  };
24865
24943
  } catch (error2) {
24866
- const message = error2 instanceof Error ? error2.message : String(error2);
24944
+ const message = sanitizeDiagnostic(error2 instanceof Error ? error2.message : String(error2), definitionSecrets(definition));
24867
24945
  if (error2 instanceof ProtocolError && (error2.code === ProtocolErrorCode.InvalidRequest || error2.code === ProtocolErrorCode.InvalidParams) && /output schema|structured content/i.test(message)) {
24868
24946
  return {
24869
24947
  delivery: "invalid_result",
@@ -24898,7 +24976,7 @@ ${tail}` : "")
24898
24976
  };
24899
24977
 
24900
24978
  // builtin/extensions/mcp-adapter/src/trust.ts
24901
- import { createHash } from "node:crypto";
24979
+ import { createHash as createHash2 } from "node:crypto";
24902
24980
  import * as fs3 from "node:fs";
24903
24981
  import * as path3 from "node:path";
24904
24982
  function serverFingerprint(cwd, definition) {
@@ -24917,7 +24995,7 @@ function serverFingerprint(cwd, definition) {
24917
24995
  inheritEnv: definition.inheritEnv,
24918
24996
  headers: definition.headers
24919
24997
  });
24920
- return createHash("sha256").update(spec).digest("hex").slice(0, 24);
24998
+ return createHash2("sha256").update(spec).digest("hex").slice(0, 24);
24921
24999
  }
24922
25000
  var McpTrustStore = class {
24923
25001
  filePath;
@@ -25018,8 +25096,9 @@ function structuredContentIsMirroredInText(textBlocks, structured) {
25018
25096
  function mapMcpOutcome(outcome, maxBytes) {
25019
25097
  if (outcome.delivery !== "ok") {
25020
25098
  const prefix = outcome.delivery === "may_have_run" || outcome.delivery === "invalid_result" ? "[outcome unknown]" : "[not executed]";
25099
+ const errorText = truncate(`${outcome.error ?? "MCP call failed"}`, maxBytes);
25021
25100
  return {
25022
- content: [{ type: "text", text: `${prefix} ${outcome.error ?? "MCP call failed"}` }],
25101
+ content: [{ type: "text", text: `${prefix} ${errorText}` }],
25023
25102
  details: { mode: "mcp", delivery: outcome.delivery, ...outcome.protocolCode !== void 0 ? { protocolCode: outcome.protocolCode } : {} },
25024
25103
  isError: true
25025
25104
  };
@@ -25054,8 +25133,9 @@ ${JSON.stringify(outcome.structuredContent, null, 2)}` });
25054
25133
  isError: outcome.isError === true
25055
25134
  };
25056
25135
  }
25057
- function createMcpProxyTool(getManager, ensureTrusted, options = {}) {
25058
- const maxBytes = options.outputMaxBytes ?? DEFAULT_OUTPUT_MAX_BYTES;
25136
+ function createMcpProxyTool(getManager, ensureTrusted, optionsOrVerify = {}, options = {}) {
25137
+ const verifyTrusted = typeof optionsOrVerify === "function" ? optionsOrVerify : void 0;
25138
+ const maxBytes = typeof optionsOrVerify === "object" && optionsOrVerify.outputMaxBytes || options.outputMaxBytes || DEFAULT_OUTPUT_MAX_BYTES;
25059
25139
  return {
25060
25140
  name: "mcp",
25061
25141
  label: "MCP",
@@ -25107,7 +25187,8 @@ ${truncate(JSON.stringify(schema ?? {}, null, 2), maxBytes)}` : `unknown MCP too
25107
25187
  const resolved = getManager().resolvePrefixedName(params.tool);
25108
25188
  const server = descriptor?.server ?? resolved?.server ?? "";
25109
25189
  if (server) {
25110
- const trust = await ensureTrusted(server);
25190
+ const managerAtGate = getManager();
25191
+ const trust = await ensureTrusted(server, managerAtGate);
25111
25192
  if (!trust.trusted) {
25112
25193
  return {
25113
25194
  content: [{ type: "text", text: `[not executed] ${trust.reason ?? "MCP server not trusted"}` }],
@@ -25115,6 +25196,14 @@ ${truncate(JSON.stringify(schema ?? {}, null, 2), maxBytes)}` : `unknown MCP too
25115
25196
  isError: true
25116
25197
  };
25117
25198
  }
25199
+ const managerNow = getManager();
25200
+ if (managerNow !== managerAtGate || verifyTrusted !== void 0 && !verifyTrusted(server, managerNow)) {
25201
+ return {
25202
+ content: [{ type: "text", text: `[not executed] MCP config changed during approval for server "${server}" \u2014 the approved definition and the current one differ. Retry the call to approve the new definition.` }],
25203
+ details: { mode: "mcp", delivery: "not_sent" },
25204
+ isError: true
25205
+ };
25206
+ }
25118
25207
  }
25119
25208
  let args = {};
25120
25209
  if (typeof params.args === "string") {
@@ -25188,7 +25277,7 @@ function jsonSchemaToTypebox(schema) {
25188
25277
  // builtin/extensions/mcp-adapter/src/direct-tools.ts
25189
25278
  var FALLBACK_PARAMS = Type.Object({}, { description: "MCP tool arguments (schema unknown until first connect)" });
25190
25279
  var DEFAULT_DIRECT_OUTPUT_MAX_BYTES = 8192;
25191
- function registerDirectTools(ctx, getManager, ensureTrusted, outputMaxBytes) {
25280
+ function registerDirectTools(ctx, getManager, ensureTrusted, verifyTrusted, outputMaxBytes) {
25192
25281
  const registered = [];
25193
25282
  const seenNames = /* @__PURE__ */ new Set();
25194
25283
  for (const definition of getManager().listDefinitions()) {
@@ -25202,13 +25291,13 @@ function registerDirectTools(ctx, getManager, ensureTrusted, outputMaxBytes) {
25202
25291
  continue;
25203
25292
  }
25204
25293
  seenNames.add(descriptor.prefixedName);
25205
- registerDirectTool(ctx, getManager, ensureTrusted, definition, descriptor.prefixedName, descriptor.description, descriptor.inputSchema, outputMaxBytes);
25294
+ registerDirectTool(ctx, getManager, ensureTrusted, verifyTrusted, definition, descriptor.prefixedName, descriptor.description, descriptor.inputSchema, outputMaxBytes);
25206
25295
  registered.push(descriptor.prefixedName);
25207
25296
  }
25208
25297
  }
25209
25298
  return registered;
25210
25299
  }
25211
- function registerDirectTool(ctx, getManager, ensureTrusted, definition, prefixedName, description, inputSchema, outputMaxBytes) {
25300
+ function registerDirectTool(ctx, getManager, ensureTrusted, verifyTrusted, definition, prefixedName, description, inputSchema, outputMaxBytes) {
25212
25301
  const parameters = inputSchema ? jsonSchemaToTypebox(inputSchema) : FALLBACK_PARAMS;
25213
25302
  ctx.registerTool({
25214
25303
  name: prefixedName,
@@ -25216,7 +25305,8 @@ function registerDirectTool(ctx, getManager, ensureTrusted, definition, prefixed
25216
25305
  description: `${(description ?? prefixedName).trim()} (MCP server: ${definition.name})`,
25217
25306
  parameters,
25218
25307
  async execute(args, toolContext) {
25219
- const trust = await ensureTrusted(definition.name);
25308
+ const managerAtGate = getManager();
25309
+ const trust = await ensureTrusted(definition.name, managerAtGate);
25220
25310
  if (!trust.trusted) {
25221
25311
  return {
25222
25312
  content: [{ type: "text", text: `[not executed] ${trust.reason ?? "MCP server not trusted"}` }],
@@ -25224,7 +25314,15 @@ function registerDirectTool(ctx, getManager, ensureTrusted, definition, prefixed
25224
25314
  isError: true
25225
25315
  };
25226
25316
  }
25227
- const outcome = await getManager().callTool(
25317
+ const managerNow = getManager();
25318
+ if (managerNow !== managerAtGate || verifyTrusted !== void 0 && !verifyTrusted(definition.name, managerNow)) {
25319
+ return {
25320
+ content: [{ type: "text", text: `[not executed] MCP config changed during approval for server "${definition.name}" \u2014 retry the call to approve the new definition.` }],
25321
+ details: { delivery: "not_sent" },
25322
+ isError: true
25323
+ };
25324
+ }
25325
+ const outcome = await managerNow.callTool(
25228
25326
  prefixedName,
25229
25327
  args && typeof args === "object" && !Array.isArray(args) ? args : {},
25230
25328
  toolContext.signal
@@ -25266,19 +25364,26 @@ function mcpAdapter(ctx) {
25266
25364
  ctx.log
25267
25365
  );
25268
25366
  async function reloadManager() {
25269
- await manager.dispose().catch(() => void 0);
25367
+ let replacement;
25270
25368
  try {
25271
- manager = new McpServerManager(
25272
- resolveMcpAdapterConfig(ctx.cwd, configDir).servers,
25273
- metadataCacheDir,
25274
- ctx.log
25275
- );
25369
+ const resolved = resolveMcpAdapterConfig(ctx.cwd, configDir);
25370
+ if (resolved.problems.length > 0) {
25371
+ const message = `config layers failed to parse: ${resolved.problems.join("; ")}`;
25372
+ ctx.log(`mcp: config reload failed, keeping the previous manager: ${message}`);
25373
+ if (ctx.hasUI) ctx.ui.notify(`MCP adapter config error (previous servers kept): ${message}`, "error");
25374
+ return { ok: false, error: message };
25375
+ }
25376
+ replacement = new McpServerManager(resolved.servers, metadataCacheDir, ctx.log);
25276
25377
  } catch (error2) {
25277
25378
  const message = error2 instanceof Error ? error2.message : String(error2);
25278
- ctx.log(`mcp: config reload failed: ${message}`);
25279
- if (ctx.hasUI) ctx.ui.notify(`MCP adapter config error: ${message}`, "error");
25280
- manager = new McpServerManager([], metadataCacheDir, ctx.log);
25379
+ ctx.log(`mcp: config reload failed, keeping the previous manager: ${message}`);
25380
+ if (ctx.hasUI) ctx.ui.notify(`MCP adapter config error (previous servers kept): ${message}`, "error");
25381
+ return { ok: false, error: message };
25281
25382
  }
25383
+ const previous = manager;
25384
+ manager = replacement;
25385
+ await previous.dispose().catch(() => void 0);
25386
+ return { ok: true };
25282
25387
  }
25283
25388
  ctx.registerCleanup?.(() => manager.dispose().catch(() => void 0));
25284
25389
  function readProjectOverride() {
@@ -25312,8 +25417,8 @@ function mcpAdapter(ctx) {
25312
25417
  fs4.writeFileSync(filePath, JSON.stringify(parsed, null, 2) + "\n", "utf8");
25313
25418
  return filePath;
25314
25419
  }
25315
- async function ensureTrusted(serverName) {
25316
- const definition = manager.getDefinition(serverName);
25420
+ async function ensureTrusted(serverName, managerAtGate = manager) {
25421
+ const definition = managerAtGate.getDefinition(serverName);
25317
25422
  if (!definition) return { trusted: false, reason: `unknown MCP server "${serverName}"` };
25318
25423
  if (trustStore.isTrusted(ctx.cwd, definition)) return { trusted: true };
25319
25424
  if (!ctx.interactions.isAvailable()) {
@@ -25337,8 +25442,12 @@ function mcpAdapter(ctx) {
25337
25442
  reason: `MCP server "${definition.name}" was not approved. It was NOT started. Ask the user to approve it, or remove it from ${definition.sourcePath}.`
25338
25443
  };
25339
25444
  }
25340
- ctx.registerTool(createMcpProxyTool(() => manager, ensureTrusted));
25341
- const eagerRegistered = registerDirectTools(ctx, () => manager, ensureTrusted);
25445
+ function verifyTrusted(serverName, currentManager) {
25446
+ const definition = currentManager.getDefinition(serverName);
25447
+ return !!definition && trustStore.isTrusted(ctx.cwd, definition);
25448
+ }
25449
+ ctx.registerTool(createMcpProxyTool(() => manager, ensureTrusted, verifyTrusted));
25450
+ const eagerRegistered = registerDirectTools(ctx, () => manager, ensureTrusted, verifyTrusted);
25342
25451
  if (eagerRegistered.length > 0) {
25343
25452
  ctx.log(`mcp: registered ${eagerRegistered.length} direct tool(s) from metadata cache: ${eagerRegistered.join(", ")}`);
25344
25453
  }
@@ -25359,6 +25468,7 @@ function mcpAdapter(ctx) {
25359
25468
  }
25360
25469
  const overrides = readProjectOverride().mcpServers;
25361
25470
  const forceEnabled = overrides?.[definition.name]?.disabled === false;
25471
+ const previousOverride = overrides?.[definition.name]?.disabled;
25362
25472
  const filePath = writeProjectOverride(
25363
25473
  definition.name,
25364
25474
  // enable must lift a source-level disabled:true with an explicit
@@ -25367,8 +25477,24 @@ function mcpAdapter(ctx) {
25367
25477
  // existing force-enable on repeated enable commands too.
25368
25478
  sub === "disable" ? true : definition.disabled || forceEnabled ? false : null
25369
25479
  );
25370
- await reloadManager();
25371
- const effectiveDisabled = manager.getDefinition(definition.name)?.disabled ?? false;
25480
+ const reload = await reloadManager();
25481
+ if (!reload.ok) {
25482
+ writeProjectOverride(definition.name, previousOverride === void 0 ? null : previousOverride);
25483
+ commandCtx.ui.notify(
25484
+ `MCP server "${definition.name}" NOT ${sub === "disable" ? "disabled" : "enabled"}: config reload failed (${reload.error}); previous servers kept and the override was rolled back.`,
25485
+ "error"
25486
+ );
25487
+ return;
25488
+ }
25489
+ const reloaded = manager.getDefinition(definition.name);
25490
+ if (!reloaded) {
25491
+ commandCtx.ui.notify(
25492
+ `MCP server "${definition.name}" disappeared from the reloaded config (wrote ${filePath}); check project/global config merges.`,
25493
+ "error"
25494
+ );
25495
+ return;
25496
+ }
25497
+ const effectiveDisabled = reloaded.disabled;
25372
25498
  const stateLabel = effectiveDisabled ? "disabled" : sub === "disable" ? "still enabled" : "enabled";
25373
25499
  commandCtx.ui.notify(
25374
25500
  `MCP server "${definition.name}" ${stateLabel} (wrote ${filePath}). ` + (eagerRegistered.length > 0 ? "Restart or /reload to resync direct tools." : "Effective immediately."),
@@ -7,9 +7,9 @@ import {
7
7
  isPlanModeSafeCommand,
8
8
  markCompletedPlanSteps,
9
9
  restoreExecutionState
10
- } from "../../../chunks/chunk-JXY3YLDS.js";
11
- import "../../../chunks/chunk-ZKHMNEXL.js";
12
- import "../../../chunks/chunk-YRXXRLMQ.js";
10
+ } from "../../../chunks/chunk-LO2V5DZZ.js";
11
+ import "../../../chunks/chunk-RTTGO2A6.js";
12
+ import "../../../chunks/chunk-UD7KYQ6J.js";
13
13
  import {
14
14
  Type
15
15
  } from "../../../chunks/chunk-XXGYUYXI.js";
@@ -1,13 +1,13 @@
1
1
  import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  createCliHostCapabilities
4
- } from "../../../chunks/chunk-PU7IIEKH.js";
4
+ } from "../../../chunks/chunk-M4OJARWJ.js";
5
5
  import {
6
6
  createSharedFileSystemTools,
7
7
  createSubagentCapability
8
- } from "../../../chunks/chunk-JXY3YLDS.js";
9
- import "../../../chunks/chunk-ZKHMNEXL.js";
10
- import "../../../chunks/chunk-YRXXRLMQ.js";
8
+ } from "../../../chunks/chunk-LO2V5DZZ.js";
9
+ import "../../../chunks/chunk-RTTGO2A6.js";
10
+ import "../../../chunks/chunk-UD7KYQ6J.js";
11
11
  import "../../../chunks/chunk-5353DEEC.js";
12
12
  import "../../../chunks/chunk-XXGYUYXI.js";
13
13
  import "../../../chunks/chunk-LZQXQOPJ.js";