@cydm/pie 2.2.0 → 2.4.0

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 (27) hide show
  1. package/dist/builtin/extensions/ask-user/index.js +3 -3
  2. package/dist/builtin/extensions/computer-use/index.js +49 -12
  3. package/dist/builtin/extensions/computer-use/package.json +3 -0
  4. package/dist/builtin/extensions/computer-use/prebuilt/macos/arm64/pie-computer-use +0 -0
  5. package/dist/builtin/extensions/computer-use/prebuilt/macos/universal/pie-computer-use.app/Contents/MacOS/pie-computer-use +0 -0
  6. package/dist/builtin/extensions/computer-use/prebuilt/macos/x64/pie-computer-use +0 -0
  7. package/dist/builtin/extensions/computer-use/scripts/build-native.mjs +2 -1
  8. package/dist/builtin/extensions/computer-use/scripts/setup-helper.mjs +2 -2
  9. package/dist/builtin/extensions/computer-use/scripts/test-native-retention.mjs +204 -0
  10. package/dist/builtin/extensions/herdr-agent-state/index.js +272 -0
  11. package/dist/builtin/extensions/herdr-agent-state/package.json +11 -0
  12. package/dist/builtin/extensions/mcp-adapter/index.js +188 -74
  13. package/dist/builtin/extensions/plan-mode/index.js +3 -3
  14. package/dist/builtin/extensions/subagent/index.js +5 -5
  15. package/dist/builtin/extensions/todo/index.js +3 -3
  16. package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-runtime.js +1 -1
  17. package/dist/builtin/skills/chrome-devtools-axi/{cli-F2VCSVL4.js → cli-P4VG6ZZG.js} +48 -8
  18. package/dist/builtin/skills/skill-creator/scripts/pie_runner.mjs +33 -12
  19. package/dist/builtin/skills/tui-use/tui-use-daemon.cjs +5 -7
  20. package/dist/chunks/{chunk-HH2Q2SNX.js → chunk-FKUEIYFQ.js} +8213 -7367
  21. package/dist/chunks/{chunk-G3SE3I2L.js → chunk-XVUV3IM2.js} +112 -40
  22. package/dist/chunks/{chunk-IM43UYVV.js → chunk-YRXXRLMQ.js} +75 -14
  23. package/dist/chunks/{chunk-QJH6YN2W.js → chunk-ZKHMNEXL.js} +1 -1
  24. package/dist/chunks/{src-QSP2OPHU.js → src-SXUQ7OY6.js} +2 -2
  25. package/dist/chunks/{test-stream-CVGWFLSX.js → test-stream-OD3B57AS.js} +1 -1
  26. package/dist/cli.js +1321 -894
  27. package/package.json +2 -2
@@ -544,6 +544,7 @@ function normalizeEntry(name, raw, sourcePath, sourceScope, homeDir) {
544
544
  const hasStdio = typeof raw.command === "string" && raw.command.length > 0;
545
545
  const hasHttp = typeof raw.url === "string" && raw.url.length > 0;
546
546
  if (!hasStdio && !hasHttp) return null;
547
+ const directTools = normalizeDirectTools(raw.directTools);
547
548
  return {
548
549
  name,
549
550
  command: hasStdio ? expandHome(raw.command, homeDir) : void 0,
@@ -554,8 +555,8 @@ function normalizeEntry(name, raw, sourcePath, sourceScope, homeDir) {
554
555
  headers: asStringRecord(raw.headers),
555
556
  inheritEnv: raw.inheritEnv !== false,
556
557
  connectTimeoutMs: typeof raw.connectTimeoutMs === "number" && Number.isFinite(raw.connectTimeoutMs) && raw.connectTimeoutMs > 0 ? raw.connectTimeoutMs : void 0,
557
- directTools: normalizeDirectTools(raw.directTools).mode,
558
- directToolsWhitelist: normalizeDirectTools(raw.directTools).whitelist,
558
+ directTools: directTools.mode,
559
+ directToolsWhitelist: directTools.whitelist,
559
560
  disabled: raw.disabled === true,
560
561
  sourcePath,
561
562
  sourceScope
@@ -615,8 +616,8 @@ function resolveMcpAdapterConfig(cwd, configDir, homeDir = os.homedir()) {
615
616
  continue;
616
617
  }
617
618
  const existing = merged.get(name);
618
- if (existing && raw.disabled === true) {
619
- merged.set(name, { ...existing, disabled: true });
619
+ if (existing && (raw.disabled === true || raw.disabled === false)) {
620
+ merged.set(name, { ...existing, disabled: raw.disabled });
620
621
  }
621
622
  }
622
623
  }
@@ -24438,6 +24439,7 @@ import * as path2 from "node:path";
24438
24439
  var CONNECT_TIMEOUT_MS = 3e4;
24439
24440
  var PACKAGE_MANAGER_CONNECT_TIMEOUT_MS = 12e4;
24440
24441
  var PACKAGE_MANAGER_COMMANDS = /* @__PURE__ */ new Set(["npx", "npm", "bunx", "uvx", "uv", "pnpm", "pnpmx"]);
24442
+ var METADATA_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
24441
24443
  var CALL_TIMEOUT_MS = 12e4;
24442
24444
  var STDERR_TAIL_LIMIT = 4e3;
24443
24445
  function connectTimeoutFor(definition) {
@@ -24445,6 +24447,17 @@ function connectTimeoutFor(definition) {
24445
24447
  const base = path2.basename(definition.command ?? "");
24446
24448
  return PACKAGE_MANAGER_COMMANDS.has(base) ? PACKAGE_MANAGER_CONNECT_TIMEOUT_MS : CONNECT_TIMEOUT_MS;
24447
24449
  }
24450
+ function unpinnedWarnings(definitions) {
24451
+ const commandPattern = new RegExp(`(^|\\s)(?:${[...PACKAGE_MANAGER_COMMANDS].join("|")})\\s`);
24452
+ const warnings = [];
24453
+ for (const definition of definitions) {
24454
+ const spec = [definition.command ?? "", ...definition.args].join(" ");
24455
+ if (commandPattern.test(spec) && /@latest\b/.test(spec)) {
24456
+ warnings.push(`${definition.name}: unpinned "@latest" fetch \u2014 pin an exact version (e.g. pkg@1.2.3).`);
24457
+ }
24458
+ }
24459
+ return warnings;
24460
+ }
24448
24461
  function appendTail(tail, chunk) {
24449
24462
  return (tail + String(chunk)).slice(-STDERR_TAIL_LIMIT);
24450
24463
  }
@@ -24487,15 +24500,8 @@ function withTimeout(promise, timeoutMs, label, signal) {
24487
24500
  });
24488
24501
  }
24489
24502
  var McpServerManager = class {
24490
- runtimes = /* @__PURE__ */ new Map();
24491
- definitions;
24492
- cachePath;
24493
- log;
24494
- /** Set by dispose(); after it, no new connects and no late attach. */
24495
- disposed = false;
24496
- /** Prefixed names claimed by more than one server in the last loadCatalog(); callTool fail-closes on them. */
24497
- conflictedNames = /* @__PURE__ */ new Set();
24498
- constructor(definitions, cachePath, log) {
24503
+ constructor(definitions, cacheDir, log, now = Date.now) {
24504
+ this.now = now;
24499
24505
  this.definitions = new Map(definitions.map((definition) => [sanitizeName(definition.name), definition]));
24500
24506
  if (this.definitions.size !== definitions.length) {
24501
24507
  const counts = /* @__PURE__ */ new Map();
@@ -24510,12 +24516,43 @@ var McpServerManager = class {
24510
24516
  );
24511
24517
  }
24512
24518
  }
24513
- this.cachePath = cachePath;
24519
+ this.cacheDir = cacheDir;
24514
24520
  this.log = log;
24515
24521
  }
24522
+ now;
24523
+ runtimes = /* @__PURE__ */ new Map();
24524
+ definitions;
24525
+ // Cache directory holding one <server>.json shard per connected server. A
24526
+ // legacy pre-sharding single file (<configDir>/mcp-metadata.json) is
24527
+ // deliberately ignored — the cache is disposable and shards rebuild on the
24528
+ // next connect.
24529
+ cacheDir;
24530
+ log;
24531
+ /** Set by dispose(); after it, no new connects and no late attach. */
24532
+ disposed = false;
24533
+ /** Prefixed names claimed by more than one server in the last loadCatalog(); callTool fail-closes on them. */
24534
+ conflictedNames = /* @__PURE__ */ new Set();
24516
24535
  getDefinition(server) {
24517
24536
  return this.definitions.get(sanitizeName(server));
24518
24537
  }
24538
+ /**
24539
+ * Resolve a `<server>_<tool>` prefixed name against CONFIGURED server names.
24540
+ * Splitting at the first underscore breaks servers whose own name contains
24541
+ * underscores (`my_server_echo` would resolve to server "my"), so match the
24542
+ * longest configured server name that prefixes the tool name instead. When
24543
+ * several configured names prefix (e.g. servers `my` and `my_server` both
24544
+ * exist), the longest match wins: that is how toPrefixedName() would have
24545
+ * constructed the deepest claim, and it is deterministic.
24546
+ */
24547
+ resolvePrefixedName(prefixedName) {
24548
+ let best = null;
24549
+ for (const key of this.definitions.keys()) {
24550
+ if (!prefixedName.startsWith(`${key}_`)) continue;
24551
+ if (best !== null && key.length <= best.server.length) continue;
24552
+ best = { server: key, tool: prefixedName.slice(key.length + 1) };
24553
+ }
24554
+ return best;
24555
+ }
24519
24556
  listDefinitions() {
24520
24557
  return [...this.definitions.values()];
24521
24558
  }
@@ -24614,13 +24651,11 @@ var McpServerManager = class {
24614
24651
  }
24615
24652
  seen.add(tool.prefixedName);
24616
24653
  }
24617
- for (const [otherKey, otherRuntime] of this.runtimes) {
24654
+ for (const otherRuntime of this.runtimes.values()) {
24618
24655
  if (otherRuntime === runtime || !otherRuntime.tools) continue;
24619
24656
  for (const otherTool of otherRuntime.tools) {
24620
24657
  if (seen.has(otherTool.prefixedName)) {
24621
- throw new Error(
24622
- `tool name collision between "${runtime.definition.name}" and "${otherKey}": ${otherTool.prefixedName}`
24623
- );
24658
+ this.log(`mcp: tool name "${otherTool.prefixedName}" is claimed by more than one server \u2014 disabled until the servers are renamed (fail-closed)`);
24624
24659
  }
24625
24660
  }
24626
24661
  }
@@ -24629,23 +24664,45 @@ var McpServerManager = class {
24629
24664
  }
24630
24665
  persistMetadata() {
24631
24666
  try {
24632
- const snapshot = { ...this.readCacheFile() };
24667
+ fs2.mkdirSync(this.cacheDir, { recursive: true });
24633
24668
  for (const [key, runtime] of this.runtimes) {
24634
- if (runtime.tools) snapshot[key] = runtime.tools;
24635
- }
24636
- fs2.mkdirSync(path2.dirname(this.cachePath), { recursive: true });
24637
- const tempPath = `${this.cachePath}.${process.pid}.tmp`;
24638
- fs2.writeFileSync(tempPath, JSON.stringify({ updatedAt: Date.now(), tools: snapshot }, null, 2), "utf8");
24639
- try {
24640
- fs2.renameSync(tempPath, this.cachePath);
24641
- } catch (error2) {
24642
- fs2.unlinkSync(tempPath);
24643
- throw error2;
24669
+ if (!runtime.tools) continue;
24670
+ const shardPath = path2.join(this.cacheDir, `${key}.json`);
24671
+ 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");
24673
+ try {
24674
+ fs2.renameSync(tempPath, shardPath);
24675
+ } catch (error2) {
24676
+ fs2.unlinkSync(tempPath);
24677
+ throw error2;
24678
+ }
24644
24679
  }
24680
+ this.deleteExpiredShards();
24645
24681
  } catch (error2) {
24646
24682
  this.log(`mcp: metadata cache write failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
24647
24683
  }
24648
24684
  }
24685
+ /** Delete shard files older than METADATA_TTL_MS (stale entries of offline servers age out). */
24686
+ deleteExpiredShards() {
24687
+ let names;
24688
+ try {
24689
+ names = fs2.readdirSync(this.cacheDir);
24690
+ } catch {
24691
+ return;
24692
+ }
24693
+ for (const name of names) {
24694
+ if (!name.endsWith(".json")) continue;
24695
+ const filePath = path2.join(this.cacheDir, name);
24696
+ try {
24697
+ const mtimeMs = fs2.statSync(filePath).mtimeMs;
24698
+ const parsed = JSON.parse(fs2.readFileSync(filePath, "utf8"));
24699
+ if (typeof parsed.updatedAt !== "number" || this.now() - parsed.updatedAt > METADATA_TTL_MS) {
24700
+ if (fs2.statSync(filePath).mtimeMs === mtimeMs) fs2.unlinkSync(filePath);
24701
+ }
24702
+ } catch {
24703
+ }
24704
+ }
24705
+ }
24649
24706
  /** Cached descriptors for one server (from the metadata cache file). */
24650
24707
  cachedToolsFor(server) {
24651
24708
  return this.loadCatalog().filter((tool) => tool.server === sanitizeName(server));
@@ -24681,27 +24738,60 @@ var McpServerManager = class {
24681
24738
  this.conflictedNames = conflicts;
24682
24739
  return [...catalog.values()].sort((a, b) => a.prefixedName.localeCompare(b.prefixedName));
24683
24740
  }
24684
- cacheMtime;
24741
+ cacheSignature;
24685
24742
  cacheSnapshot = {};
24686
- /** Read the metadata cache file, re-reading only when the mtime changed. */
24743
+ cacheUpdatedAt = {};
24744
+ pruneExpiredSnapshot() {
24745
+ for (const [key, updatedAt] of Object.entries(this.cacheUpdatedAt)) {
24746
+ if (this.now() - updatedAt > METADATA_TTL_MS) {
24747
+ delete this.cacheSnapshot[key];
24748
+ delete this.cacheUpdatedAt[key];
24749
+ }
24750
+ }
24751
+ return this.cacheSnapshot;
24752
+ }
24753
+ /**
24754
+ * Read the sharded metadata cache (<cacheDir>/<server>.json), merging every
24755
+ * shard's tools by server key (the file name is the sanitized server key).
24756
+ * Re-reads only when the signature changed; the signature must cover each
24757
+ * shard's name + mtime — the directory's own mtime is NOT enough, because a
24758
+ * rename replacing an existing shard leaves it unchanged.
24759
+ */
24687
24760
  readCacheFile() {
24688
- let mtime;
24761
+ let names;
24689
24762
  try {
24690
- mtime = fs2.statSync(this.cachePath).mtimeMs;
24763
+ names = fs2.readdirSync(this.cacheDir).sort();
24691
24764
  } catch {
24692
- this.cacheMtime = void 0;
24765
+ this.cacheSignature = void 0;
24693
24766
  this.cacheSnapshot = {};
24767
+ this.cacheUpdatedAt = {};
24694
24768
  return this.cacheSnapshot;
24695
24769
  }
24696
- if (mtime === this.cacheMtime) return this.cacheSnapshot;
24697
- try {
24698
- const parsed = JSON.parse(fs2.readFileSync(this.cachePath, "utf8"));
24699
- this.cacheSnapshot = parsed.tools ?? {};
24700
- this.cacheMtime = mtime;
24701
- } catch {
24702
- this.cacheSnapshot = {};
24703
- this.cacheMtime = mtime;
24770
+ let signature = "";
24771
+ for (const name of names) {
24772
+ try {
24773
+ signature += `${name}:${fs2.statSync(path2.join(this.cacheDir, name)).mtimeMs};`;
24774
+ } catch {
24775
+ }
24704
24776
  }
24777
+ if (signature === this.cacheSignature) return this.pruneExpiredSnapshot();
24778
+ const snapshot = {};
24779
+ const updatedAt = {};
24780
+ for (const name of names) {
24781
+ if (!name.endsWith(".json")) continue;
24782
+ try {
24783
+ const parsed = JSON.parse(fs2.readFileSync(path2.join(this.cacheDir, name), "utf8"));
24784
+ if (typeof parsed.updatedAt !== "number" || this.now() - parsed.updatedAt > METADATA_TTL_MS) continue;
24785
+ if (!Array.isArray(parsed.tools)) continue;
24786
+ const key = name.slice(0, -".json".length);
24787
+ snapshot[key] = parsed.tools;
24788
+ updatedAt[key] = parsed.updatedAt;
24789
+ } catch {
24790
+ }
24791
+ }
24792
+ this.cacheSnapshot = snapshot;
24793
+ this.cacheUpdatedAt = updatedAt;
24794
+ this.cacheSignature = signature;
24705
24795
  return this.cacheSnapshot;
24706
24796
  }
24707
24797
  async callTool(prefixedName, args, signal) {
@@ -24716,13 +24806,10 @@ var McpServerManager = class {
24716
24806
  let server = descriptor?.server;
24717
24807
  let toolName = descriptor?.tool;
24718
24808
  if (!server) {
24719
- const separator = prefixedName.indexOf("_");
24720
- if (separator > 0) {
24721
- const candidate = prefixedName.slice(0, separator);
24722
- if (this.definitions.has(candidate)) {
24723
- server = candidate;
24724
- toolName = prefixedName.slice(separator + 1);
24725
- }
24809
+ const resolved = this.resolvePrefixedName(prefixedName);
24810
+ if (resolved) {
24811
+ server = resolved.server;
24812
+ toolName = resolved.tool;
24726
24813
  }
24727
24814
  }
24728
24815
  if (!server || !toolName) {
@@ -24756,9 +24843,15 @@ ${tail}` : "")
24756
24843
  error: `MCP connection to "${server}" closed before the call could start.`
24757
24844
  };
24758
24845
  }
24846
+ if (signal?.aborted) {
24847
+ return {
24848
+ delivery: "not_sent",
24849
+ error: `MCP call "${prefixedName}" was cancelled before the request was sent; nothing was executed.`
24850
+ };
24851
+ }
24759
24852
  try {
24760
24853
  const result = await withTimeout(
24761
- client.callTool({ name: toolName, arguments: args }),
24854
+ client.callTool({ name: toolName, arguments: args }, void 0, signal !== void 0 ? { signal } : void 0),
24762
24855
  CALL_TIMEOUT_MS,
24763
24856
  `call "${prefixedName}"`,
24764
24857
  signal
@@ -24771,6 +24864,12 @@ ${tail}` : "")
24771
24864
  };
24772
24865
  } catch (error2) {
24773
24866
  const message = error2 instanceof Error ? error2.message : String(error2);
24867
+ if (error2 instanceof ProtocolError && (error2.code === ProtocolErrorCode.InvalidRequest || error2.code === ProtocolErrorCode.InvalidParams) && /output schema|structured content/i.test(message)) {
24868
+ return {
24869
+ delivery: "invalid_result",
24870
+ error: `MCP server "${server}" returned a result that fails its own outputSchema: ${message}. The call DID reach the server and the tool likely executed \u2014 do NOT retry automatically; verify state with a read-only tool first.`
24871
+ };
24872
+ }
24774
24873
  if (runtime.client === void 0 || /closed|aborted|timed out|socket|ECONNRESET/i.test(message)) {
24775
24874
  return {
24776
24875
  delivery: "may_have_run",
@@ -24918,7 +25017,7 @@ function structuredContentIsMirroredInText(textBlocks, structured) {
24918
25017
  }
24919
25018
  function mapMcpOutcome(outcome, maxBytes) {
24920
25019
  if (outcome.delivery !== "ok") {
24921
- const prefix = outcome.delivery === "may_have_run" ? "[outcome unknown]" : "[not executed]";
25020
+ const prefix = outcome.delivery === "may_have_run" || outcome.delivery === "invalid_result" ? "[outcome unknown]" : "[not executed]";
24922
25021
  return {
24923
25022
  content: [{ type: "text", text: `${prefix} ${outcome.error ?? "MCP call failed"}` }],
24924
25023
  details: { mode: "mcp", delivery: outcome.delivery, ...outcome.protocolCode !== void 0 ? { protocolCode: outcome.protocolCode } : {} },
@@ -24960,7 +25059,7 @@ function createMcpProxyTool(getManager, ensureTrusted, options = {}) {
24960
25059
  return {
24961
25060
  name: "mcp",
24962
25061
  label: "MCP",
24963
- description: 'Call tools on configured MCP servers without loading their definitions. Discover: mcp({ search: "screenshot" }) lists matching <server>_<tool> names with compact parameter hints (cached metadata; servers do not start for search). Inspect: mcp({ tool: "<name>", describe: true }) returns the full input schema. Call: mcp({ tool: "<name>", args: { ... } }) \u2014 the server starts lazily on first use. args may be a JSON object or JSON string. If a result is marked [outcome unknown], the tool may have executed: do not retry automatically \u2014 verify state with a read-only call first.',
25062
+ description: 'Call tools on configured MCP servers without loading their definitions. Discover: mcp({ search: "screenshot" }) lists matching <server>_<tool> names with compact parameter hints (cached metadata; servers do not start for search). Inspect: mcp({ tool: "<name>", describe: true }) returns the full input schema. Call: mcp({ tool: "<name>", args: { ... } }) \u2014 the server starts lazily on first use. args may be a JSON object or JSON string. Tool lists are a snapshot taken when a server connects: tools added or removed by a running server only appear after /reload refreshes the session. If a result is marked [outcome unknown], the tool may have executed: do not retry automatically \u2014 verify state with a read-only call first.',
24964
25063
  parameters: Type.Object({
24965
25064
  search: Type.Optional(Type.String({ description: "Query text to discover MCP tools by name/description" })),
24966
25065
  tool: Type.Optional(Type.String({ description: "Prefixed tool name <server>_<tool> to describe or call" })),
@@ -25005,8 +25104,8 @@ ${truncate(JSON.stringify(schema ?? {}, null, 2), maxBytes)}` : `unknown MCP too
25005
25104
  isError: descriptor === void 0
25006
25105
  };
25007
25106
  }
25008
- const separator = params.tool.indexOf("_");
25009
- const server = descriptor?.server ?? (separator > 0 ? params.tool.slice(0, separator) : "");
25107
+ const resolved = getManager().resolvePrefixedName(params.tool);
25108
+ const server = descriptor?.server ?? resolved?.server ?? "";
25010
25109
  if (server) {
25011
25110
  const trust = await ensureTrusted(server);
25012
25111
  if (!trust.trusted) {
@@ -25160,10 +25259,10 @@ function formatServerForTrustPrompt(definition) {
25160
25259
  function mcpAdapter(ctx) {
25161
25260
  const configDir = resolveConfigDir();
25162
25261
  const trustStore = new McpTrustStore(path4.join(configDir, "mcp-trust.json"));
25163
- const metadataCachePath = path4.join(configDir, "mcp-metadata.json");
25262
+ const metadataCacheDir = path4.join(configDir, "mcp-metadata");
25164
25263
  let manager = new McpServerManager(
25165
25264
  resolveMcpAdapterConfig(ctx.cwd, configDir).servers,
25166
- metadataCachePath,
25265
+ metadataCacheDir,
25167
25266
  ctx.log
25168
25267
  );
25169
25268
  async function reloadManager() {
@@ -25171,28 +25270,42 @@ function mcpAdapter(ctx) {
25171
25270
  try {
25172
25271
  manager = new McpServerManager(
25173
25272
  resolveMcpAdapterConfig(ctx.cwd, configDir).servers,
25174
- metadataCachePath,
25273
+ metadataCacheDir,
25175
25274
  ctx.log
25176
25275
  );
25177
25276
  } catch (error2) {
25178
25277
  const message = error2 instanceof Error ? error2.message : String(error2);
25179
25278
  ctx.log(`mcp: config reload failed: ${message}`);
25180
25279
  if (ctx.hasUI) ctx.ui.notify(`MCP adapter config error: ${message}`, "error");
25181
- manager = new McpServerManager([], metadataCachePath, ctx.log);
25280
+ manager = new McpServerManager([], metadataCacheDir, ctx.log);
25182
25281
  }
25183
25282
  }
25184
- function writeProjectOverride(serverName, disabled) {
25283
+ ctx.registerCleanup?.(() => manager.dispose().catch(() => void 0));
25284
+ function readProjectOverride() {
25185
25285
  const filePath = path4.join(ctx.cwd, ".pie", "mcp-adapter.json");
25186
25286
  let parsed = {};
25187
25287
  try {
25188
25288
  parsed = JSON.parse(fs4.readFileSync(filePath, "utf8"));
25189
25289
  } catch {
25190
25290
  }
25291
+ return parsed;
25292
+ }
25293
+ function writeProjectOverride(serverName, disabled) {
25294
+ const filePath = path4.join(ctx.cwd, ".pie", "mcp-adapter.json");
25295
+ const parsed = readProjectOverride();
25191
25296
  const servers = parsed.mcpServers && typeof parsed.mcpServers === "object" && !Array.isArray(parsed.mcpServers) ? { ...parsed.mcpServers } : {};
25297
+ const existing = servers[serverName];
25298
+ const existingObj = existing && typeof existing === "object" && !Array.isArray(existing) ? { ...existing } : {};
25192
25299
  if (disabled === null) {
25193
- delete servers[serverName];
25300
+ delete existingObj.disabled;
25301
+ if (Object.keys(existingObj).length === 0) {
25302
+ delete servers[serverName];
25303
+ } else {
25304
+ servers[serverName] = existingObj;
25305
+ }
25194
25306
  } else {
25195
- servers[serverName] = { disabled };
25307
+ existingObj.disabled = disabled;
25308
+ servers[serverName] = existingObj;
25196
25309
  }
25197
25310
  parsed.mcpServers = servers;
25198
25311
  fs4.mkdirSync(path4.dirname(filePath), { recursive: true });
@@ -25229,16 +25342,6 @@ function mcpAdapter(ctx) {
25229
25342
  if (eagerRegistered.length > 0) {
25230
25343
  ctx.log(`mcp: registered ${eagerRegistered.length} direct tool(s) from metadata cache: ${eagerRegistered.join(", ")}`);
25231
25344
  }
25232
- function unpinnedWarnings() {
25233
- const warnings = [];
25234
- for (const definition of manager.listDefinitions()) {
25235
- const spec = [definition.command ?? "", ...definition.args].join(" ");
25236
- if (/(^|\s)(npx|bunx|uvx)\s/.test(spec) && /@latest\b/.test(spec)) {
25237
- warnings.push(`${definition.name}: unpinned "@latest" fetch \u2014 pin an exact version (e.g. pkg@1.2.3).`);
25238
- }
25239
- }
25240
- return warnings;
25241
- }
25242
25345
  ctx.registerCommand({
25243
25346
  name: "mcp",
25244
25347
  description: "MCP adapter: status (default), enable/disable <server>",
@@ -25254,10 +25357,21 @@ function mcpAdapter(ctx) {
25254
25357
  commandCtx.ui.notify(`Unknown MCP server "${target}". Run /mcp for the configured list.`, "warning");
25255
25358
  return;
25256
25359
  }
25257
- const filePath = writeProjectOverride(definition.name, sub === "disable" ? true : null);
25360
+ const overrides = readProjectOverride().mcpServers;
25361
+ const forceEnabled = overrides?.[definition.name]?.disabled === false;
25362
+ const filePath = writeProjectOverride(
25363
+ definition.name,
25364
+ // enable must lift a source-level disabled:true with an explicit
25365
+ // {disabled:false} force-override: deleting the override alone
25366
+ // would leave the source-level disable in force. Preserve an
25367
+ // existing force-enable on repeated enable commands too.
25368
+ sub === "disable" ? true : definition.disabled || forceEnabled ? false : null
25369
+ );
25258
25370
  await reloadManager();
25371
+ const effectiveDisabled = manager.getDefinition(definition.name)?.disabled ?? false;
25372
+ const stateLabel = effectiveDisabled ? "disabled" : sub === "disable" ? "still enabled" : "enabled";
25259
25373
  commandCtx.ui.notify(
25260
- `MCP server "${definition.name}" ${sub}d (wrote ${filePath}). ` + (eagerRegistered.length > 0 ? "Restart or /reload to resync direct tools." : "Effective immediately."),
25374
+ `MCP server "${definition.name}" ${stateLabel} (wrote ${filePath}). ` + (eagerRegistered.length > 0 ? "Restart or /reload to resync direct tools." : "Effective immediately."),
25261
25375
  "info"
25262
25376
  );
25263
25377
  return;
@@ -25279,7 +25393,7 @@ function mcpAdapter(ctx) {
25279
25393
  lines.push(`- ${sanitizeName(definition.name)} [${manager.statusOf(definition.name)}, ${scopeLabel}] ${spec}`);
25280
25394
  }
25281
25395
  }
25282
- const warnings = unpinnedWarnings();
25396
+ const warnings = unpinnedWarnings(manager.listDefinitions());
25283
25397
  if (warnings.length > 0) lines.push("", "Warnings:", ...warnings.map((warning) => `- ${warning}`));
25284
25398
  const config2 = resolveMcpAdapterConfig(commandCtx.cwd, configDir);
25285
25399
  if (config2.problems.length > 0) lines.push("", "Config problems:", ...config2.problems.map((problem) => `- ${problem}`));
@@ -7,9 +7,9 @@ import {
7
7
  isPlanModeSafeCommand,
8
8
  markCompletedPlanSteps,
9
9
  restoreExecutionState
10
- } from "../../../chunks/chunk-HH2Q2SNX.js";
11
- import "../../../chunks/chunk-QJH6YN2W.js";
12
- import "../../../chunks/chunk-IM43UYVV.js";
10
+ } from "../../../chunks/chunk-FKUEIYFQ.js";
11
+ import "../../../chunks/chunk-ZKHMNEXL.js";
12
+ import "../../../chunks/chunk-YRXXRLMQ.js";
13
13
  import {
14
14
  Type
15
15
  } from "../../../chunks/chunk-XXGYUYXI.js";
@@ -1,14 +1,14 @@
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-G3SE3I2L.js";
5
- import "../../../chunks/chunk-5353DEEC.js";
4
+ } from "../../../chunks/chunk-XVUV3IM2.js";
6
5
  import {
7
6
  createSharedFileSystemTools,
8
7
  createSubagentCapability
9
- } from "../../../chunks/chunk-HH2Q2SNX.js";
10
- import "../../../chunks/chunk-QJH6YN2W.js";
11
- import "../../../chunks/chunk-IM43UYVV.js";
8
+ } from "../../../chunks/chunk-FKUEIYFQ.js";
9
+ import "../../../chunks/chunk-ZKHMNEXL.js";
10
+ import "../../../chunks/chunk-YRXXRLMQ.js";
11
+ import "../../../chunks/chunk-5353DEEC.js";
12
12
  import "../../../chunks/chunk-XXGYUYXI.js";
13
13
  import "../../../chunks/chunk-LZQXQOPJ.js";
14
14
 
@@ -8,9 +8,9 @@ import {
8
8
  executeManageTodoList,
9
9
  executionStateToTodos,
10
10
  restoreExecutionState
11
- } from "../../../chunks/chunk-HH2Q2SNX.js";
12
- import "../../../chunks/chunk-QJH6YN2W.js";
13
- import "../../../chunks/chunk-IM43UYVV.js";
11
+ } from "../../../chunks/chunk-FKUEIYFQ.js";
12
+ import "../../../chunks/chunk-ZKHMNEXL.js";
13
+ import "../../../chunks/chunk-YRXXRLMQ.js";
14
14
  import "../../../chunks/chunk-XXGYUYXI.js";
15
15
  import "../../../chunks/chunk-LZQXQOPJ.js";
16
16
 
@@ -21,6 +21,6 @@ function tryFastPath(argv, options) {
21
21
 
22
22
  // vendor/chrome-devtools-axi/bin/chrome-devtools-axi.ts
23
23
  if (!tryFastPath(process.argv.slice(2), { version: VERSION })) {
24
- const { main } = await import("./cli-F2VCSVL4.js");
24
+ const { main } = await import("./cli-P4VG6ZZG.js");
25
25
  await main(process.argv.slice(2));
26
26
  }
@@ -1612,7 +1612,7 @@ function installSessionStartHooks(options = {}) {
1612
1612
 
1613
1613
  // vendor/chrome-devtools-axi/src/client.ts
1614
1614
  import { execFileSync, spawn as spawn2 } from "node:child_process";
1615
- import { readFileSync as readFileSync3, existsSync as existsSync3 } from "node:fs";
1615
+ import { readFileSync as readFileSync3, existsSync as existsSync3, unlinkSync } from "node:fs";
1616
1616
  import { request } from "node:http";
1617
1617
  import { dirname as dirname3 } from "node:path";
1618
1618
 
@@ -1945,6 +1945,39 @@ function isBridgeProcess(pid) {
1945
1945
  return false;
1946
1946
  }
1947
1947
  }
1948
+ async function probeBridgeIdentity(port, sessionName) {
1949
+ try {
1950
+ const resp = await httpGet(port, "/health", HEALTH_TIMEOUT_MS);
1951
+ const data = JSON.parse(resp);
1952
+ if (data.status !== "ok") return "unknown";
1953
+ if (typeof data.session === "string" && data.session !== sessionName) {
1954
+ return "other";
1955
+ }
1956
+ return "own";
1957
+ } catch {
1958
+ return "unknown";
1959
+ }
1960
+ }
1961
+ async function judgeRecordedBridge(pidInfo, sessionName) {
1962
+ const identity = await probeBridgeIdentity(pidInfo.port, sessionName);
1963
+ if (identity === "own") {
1964
+ return {
1965
+ terminate: true,
1966
+ killProcessGroup: isBridgeProcess(pidInfo.pid)
1967
+ };
1968
+ }
1969
+ if (identity === "other") {
1970
+ return { terminate: false, killProcessGroup: false };
1971
+ }
1972
+ const bridge = isBridgeProcess(pidInfo.pid);
1973
+ return { terminate: bridge, killProcessGroup: bridge };
1974
+ }
1975
+ function removePidFile(pidFile) {
1976
+ try {
1977
+ unlinkSync(pidFile);
1978
+ } catch {
1979
+ }
1980
+ }
1948
1981
  async function terminateBridgeProcess(pid, opts = {}) {
1949
1982
  if (!isProcessAlive(pid)) return;
1950
1983
  const killProcessGroup = opts.killProcessGroup === true;
@@ -2065,9 +2098,14 @@ async function ensureBridge(spawnBridge = spawnBridgeProcess, notice) {
2065
2098
  })) {
2066
2099
  return pidInfo.port;
2067
2100
  }
2068
- await terminateBridgeProcess(pidInfo.pid, {
2069
- killProcessGroup: isBridgeProcess(pidInfo.pid)
2070
- });
2101
+ const verdict = await judgeRecordedBridge(pidInfo, sessionName);
2102
+ if (verdict.terminate) {
2103
+ await terminateBridgeProcess(pidInfo.pid, {
2104
+ killProcessGroup: verdict.killProcessGroup
2105
+ });
2106
+ } else {
2107
+ removePidFile(pidFile);
2108
+ }
2071
2109
  }
2072
2110
  clearSelectedPageId();
2073
2111
  const child = spawnBridge(port, sessionName);
@@ -2338,8 +2376,10 @@ async function stopBridge() {
2338
2376
  const pidInfo = readPidFile();
2339
2377
  if (!pidInfo) return false;
2340
2378
  if (!isProcessAlive(pidInfo.pid)) return false;
2379
+ const verdict = await judgeRecordedBridge(pidInfo, resolveSessionName());
2380
+ if (!verdict.terminate) return false;
2341
2381
  await terminateBridgeProcess(pidInfo.pid, {
2342
- killProcessGroup: isBridgeProcess(pidInfo.pid)
2382
+ killProcessGroup: verdict.killProcessGroup
2343
2383
  });
2344
2384
  clearSelectedPageId();
2345
2385
  return true;
@@ -2350,7 +2390,7 @@ import {
2350
2390
  existsSync as existsSync4,
2351
2391
  mkdirSync as mkdirSync2,
2352
2392
  readFileSync as readFileSync4,
2353
- unlinkSync,
2393
+ unlinkSync as unlinkSync2,
2354
2394
  writeFileSync as writeFileSync2
2355
2395
  } from "node:fs";
2356
2396
  import { dirname as dirname4, join as join3 } from "node:path";
@@ -2379,7 +2419,7 @@ function bumpGeneration() {
2379
2419
  }
2380
2420
 
2381
2421
  // vendor/chrome-devtools-axi/src/run.ts
2382
- import { mkdtempSync, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, rmdirSync } from "node:fs";
2422
+ import { mkdtempSync, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, rmdirSync } from "node:fs";
2383
2423
  import { join as join4 } from "node:path";
2384
2424
  import { tmpdir } from "node:os";
2385
2425
  import { pathToFileURL } from "node:url";
@@ -2786,7 +2826,7 @@ async function runScript(content, callTool2) {
2786
2826
  globalThis.page = prevPage;
2787
2827
  }
2788
2828
  try {
2789
- unlinkSync2(tmpFile);
2829
+ unlinkSync3(tmpFile);
2790
2830
  rmdirSync(tmpDir);
2791
2831
  } catch {
2792
2832
  }