@cydm/pie 2.5.1 → 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 (24) 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/mcp-adapter/index.js +111 -25
  8. package/dist/builtin/extensions/plan-mode/index.js +3 -3
  9. package/dist/builtin/extensions/subagent/index.js +4 -4
  10. package/dist/builtin/extensions/todo/index.js +3 -3
  11. package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-bridge.js +5 -0
  12. package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-cli.js +5 -2
  13. package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-runtime.js +1 -1
  14. package/dist/builtin/skills/chrome-devtools-axi/{cli-P4VG6ZZG.js → cli-EJ5MQZNA.js} +76 -27
  15. package/dist/builtin/skills/tui-use/tui-use-cli.cjs +25 -9
  16. package/dist/builtin/skills/tui-use/tui-use-daemon.cjs +7 -1
  17. package/dist/chunks/{chunk-REP6QANY.js → chunk-LO2V5DZZ.js} +38 -6
  18. package/dist/chunks/{chunk-N2MK46AE.js → chunk-M4OJARWJ.js} +7 -2
  19. package/dist/chunks/{chunk-ZKHMNEXL.js → chunk-RTTGO2A6.js} +1 -1
  20. package/dist/chunks/{chunk-YRXXRLMQ.js → chunk-UD7KYQ6J.js} +9 -0
  21. package/dist/chunks/{src-SXUQ7OY6.js → src-33WRCQWX.js} +2 -2
  22. package/dist/chunks/{test-stream-OD3B57AS.js → test-stream-LFR6GSKQ.js} +1 -1
  23. package/dist/cli.js +62 -14
  24. 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-REP6QANY.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
 
@@ -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,10 +24776,11 @@ 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
- const definition = this.definitions.get(tool.server);
24728
- if (!definition || definition.disabled) 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) {
24729
24784
  claim2(tool, false);
24730
24785
  }
24731
24786
  }
@@ -24786,7 +24841,10 @@ var McpServerManager = class {
24786
24841
  if (typeof parsed.updatedAt !== "number" || this.now() - parsed.updatedAt > METADATA_TTL_MS) continue;
24787
24842
  if (!Array.isArray(parsed.tools)) continue;
24788
24843
  const key = name.slice(0, -".json".length);
24789
- snapshot[key] = parsed.tools;
24844
+ snapshot[key] = {
24845
+ tools: parsed.tools,
24846
+ ...typeof parsed.fingerprint === "string" ? { fingerprint: parsed.fingerprint } : {}
24847
+ };
24790
24848
  updatedAt[key] = parsed.updatedAt;
24791
24849
  } catch {
24792
24850
  }
@@ -24830,12 +24888,16 @@ var McpServerManager = class {
24830
24888
  } catch (error2) {
24831
24889
  const runtimeState = this.runtimes.get(server);
24832
24890
  const tail = runtimeState?.stderrTail.trim();
24891
+ const secrets = definitionSecrets(definition);
24833
24892
  return {
24834
24893
  delivery: "not_sent",
24835
- 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 ? `
24836
24896
 
24837
24897
  Server stderr tail:
24838
- ${tail}` : "")
24898
+ ${tail}` : ""),
24899
+ secrets
24900
+ )
24839
24901
  };
24840
24902
  }
24841
24903
  const client = runtime.client;
@@ -24879,7 +24941,7 @@ ${tail}` : "")
24879
24941
  isError: result.isError === true
24880
24942
  };
24881
24943
  } catch (error2) {
24882
- const message = error2 instanceof Error ? error2.message : String(error2);
24944
+ const message = sanitizeDiagnostic(error2 instanceof Error ? error2.message : String(error2), definitionSecrets(definition));
24883
24945
  if (error2 instanceof ProtocolError && (error2.code === ProtocolErrorCode.InvalidRequest || error2.code === ProtocolErrorCode.InvalidParams) && /output schema|structured content/i.test(message)) {
24884
24946
  return {
24885
24947
  delivery: "invalid_result",
@@ -24914,7 +24976,7 @@ ${tail}` : "")
24914
24976
  };
24915
24977
 
24916
24978
  // builtin/extensions/mcp-adapter/src/trust.ts
24917
- import { createHash } from "node:crypto";
24979
+ import { createHash as createHash2 } from "node:crypto";
24918
24980
  import * as fs3 from "node:fs";
24919
24981
  import * as path3 from "node:path";
24920
24982
  function serverFingerprint(cwd, definition) {
@@ -24933,7 +24995,7 @@ function serverFingerprint(cwd, definition) {
24933
24995
  inheritEnv: definition.inheritEnv,
24934
24996
  headers: definition.headers
24935
24997
  });
24936
- return createHash("sha256").update(spec).digest("hex").slice(0, 24);
24998
+ return createHash2("sha256").update(spec).digest("hex").slice(0, 24);
24937
24999
  }
24938
25000
  var McpTrustStore = class {
24939
25001
  filePath;
@@ -25034,8 +25096,9 @@ function structuredContentIsMirroredInText(textBlocks, structured) {
25034
25096
  function mapMcpOutcome(outcome, maxBytes) {
25035
25097
  if (outcome.delivery !== "ok") {
25036
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);
25037
25100
  return {
25038
- content: [{ type: "text", text: `${prefix} ${outcome.error ?? "MCP call failed"}` }],
25101
+ content: [{ type: "text", text: `${prefix} ${errorText}` }],
25039
25102
  details: { mode: "mcp", delivery: outcome.delivery, ...outcome.protocolCode !== void 0 ? { protocolCode: outcome.protocolCode } : {} },
25040
25103
  isError: true
25041
25104
  };
@@ -25070,8 +25133,9 @@ ${JSON.stringify(outcome.structuredContent, null, 2)}` });
25070
25133
  isError: outcome.isError === true
25071
25134
  };
25072
25135
  }
25073
- function createMcpProxyTool(getManager, ensureTrusted, options = {}) {
25074
- 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;
25075
25139
  return {
25076
25140
  name: "mcp",
25077
25141
  label: "MCP",
@@ -25123,7 +25187,8 @@ ${truncate(JSON.stringify(schema ?? {}, null, 2), maxBytes)}` : `unknown MCP too
25123
25187
  const resolved = getManager().resolvePrefixedName(params.tool);
25124
25188
  const server = descriptor?.server ?? resolved?.server ?? "";
25125
25189
  if (server) {
25126
- const trust = await ensureTrusted(server);
25190
+ const managerAtGate = getManager();
25191
+ const trust = await ensureTrusted(server, managerAtGate);
25127
25192
  if (!trust.trusted) {
25128
25193
  return {
25129
25194
  content: [{ type: "text", text: `[not executed] ${trust.reason ?? "MCP server not trusted"}` }],
@@ -25131,6 +25196,14 @@ ${truncate(JSON.stringify(schema ?? {}, null, 2), maxBytes)}` : `unknown MCP too
25131
25196
  isError: true
25132
25197
  };
25133
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
+ }
25134
25207
  }
25135
25208
  let args = {};
25136
25209
  if (typeof params.args === "string") {
@@ -25204,7 +25277,7 @@ function jsonSchemaToTypebox(schema) {
25204
25277
  // builtin/extensions/mcp-adapter/src/direct-tools.ts
25205
25278
  var FALLBACK_PARAMS = Type.Object({}, { description: "MCP tool arguments (schema unknown until first connect)" });
25206
25279
  var DEFAULT_DIRECT_OUTPUT_MAX_BYTES = 8192;
25207
- function registerDirectTools(ctx, getManager, ensureTrusted, outputMaxBytes) {
25280
+ function registerDirectTools(ctx, getManager, ensureTrusted, verifyTrusted, outputMaxBytes) {
25208
25281
  const registered = [];
25209
25282
  const seenNames = /* @__PURE__ */ new Set();
25210
25283
  for (const definition of getManager().listDefinitions()) {
@@ -25218,13 +25291,13 @@ function registerDirectTools(ctx, getManager, ensureTrusted, outputMaxBytes) {
25218
25291
  continue;
25219
25292
  }
25220
25293
  seenNames.add(descriptor.prefixedName);
25221
- 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);
25222
25295
  registered.push(descriptor.prefixedName);
25223
25296
  }
25224
25297
  }
25225
25298
  return registered;
25226
25299
  }
25227
- function registerDirectTool(ctx, getManager, ensureTrusted, definition, prefixedName, description, inputSchema, outputMaxBytes) {
25300
+ function registerDirectTool(ctx, getManager, ensureTrusted, verifyTrusted, definition, prefixedName, description, inputSchema, outputMaxBytes) {
25228
25301
  const parameters = inputSchema ? jsonSchemaToTypebox(inputSchema) : FALLBACK_PARAMS;
25229
25302
  ctx.registerTool({
25230
25303
  name: prefixedName,
@@ -25232,7 +25305,8 @@ function registerDirectTool(ctx, getManager, ensureTrusted, definition, prefixed
25232
25305
  description: `${(description ?? prefixedName).trim()} (MCP server: ${definition.name})`,
25233
25306
  parameters,
25234
25307
  async execute(args, toolContext) {
25235
- const trust = await ensureTrusted(definition.name);
25308
+ const managerAtGate = getManager();
25309
+ const trust = await ensureTrusted(definition.name, managerAtGate);
25236
25310
  if (!trust.trusted) {
25237
25311
  return {
25238
25312
  content: [{ type: "text", text: `[not executed] ${trust.reason ?? "MCP server not trusted"}` }],
@@ -25240,7 +25314,15 @@ function registerDirectTool(ctx, getManager, ensureTrusted, definition, prefixed
25240
25314
  isError: true
25241
25315
  };
25242
25316
  }
25243
- 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(
25244
25326
  prefixedName,
25245
25327
  args && typeof args === "object" && !Array.isArray(args) ? args : {},
25246
25328
  toolContext.signal
@@ -25335,8 +25417,8 @@ function mcpAdapter(ctx) {
25335
25417
  fs4.writeFileSync(filePath, JSON.stringify(parsed, null, 2) + "\n", "utf8");
25336
25418
  return filePath;
25337
25419
  }
25338
- async function ensureTrusted(serverName) {
25339
- const definition = manager.getDefinition(serverName);
25420
+ async function ensureTrusted(serverName, managerAtGate = manager) {
25421
+ const definition = managerAtGate.getDefinition(serverName);
25340
25422
  if (!definition) return { trusted: false, reason: `unknown MCP server "${serverName}"` };
25341
25423
  if (trustStore.isTrusted(ctx.cwd, definition)) return { trusted: true };
25342
25424
  if (!ctx.interactions.isAvailable()) {
@@ -25360,8 +25442,12 @@ function mcpAdapter(ctx) {
25360
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}.`
25361
25443
  };
25362
25444
  }
25363
- ctx.registerTool(createMcpProxyTool(() => manager, ensureTrusted));
25364
- 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);
25365
25451
  if (eagerRegistered.length > 0) {
25366
25452
  ctx.log(`mcp: registered ${eagerRegistered.length} direct tool(s) from metadata cache: ${eagerRegistered.join(", ")}`);
25367
25453
  }
@@ -7,9 +7,9 @@ import {
7
7
  isPlanModeSafeCommand,
8
8
  markCompletedPlanSteps,
9
9
  restoreExecutionState
10
- } from "../../../chunks/chunk-REP6QANY.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-N2MK46AE.js";
4
+ } from "../../../chunks/chunk-M4OJARWJ.js";
5
5
  import {
6
6
  createSharedFileSystemTools,
7
7
  createSubagentCapability
8
- } from "../../../chunks/chunk-REP6QANY.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";
@@ -8,9 +8,9 @@ import {
8
8
  executeManageTodoList,
9
9
  executionStateToTodos,
10
10
  restoreExecutionState
11
- } from "../../../chunks/chunk-REP6QANY.js";
12
- import "../../../chunks/chunk-ZKHMNEXL.js";
13
- import "../../../chunks/chunk-YRXXRLMQ.js";
11
+ } from "../../../chunks/chunk-LO2V5DZZ.js";
12
+ import "../../../chunks/chunk-RTTGO2A6.js";
13
+ import "../../../chunks/chunk-UD7KYQ6J.js";
14
14
  import "../../../chunks/chunk-XXGYUYXI.js";
15
15
  import "../../../chunks/chunk-LZQXQOPJ.js";
16
16
 
@@ -19640,6 +19640,11 @@ async function handleBridgeRequest(client, req, res, sessionName, logForbidden,
19640
19640
  writeJson(res, 200, {
19641
19641
  status: "ok",
19642
19642
  session: sessionName,
19643
+ // The bridge's own PID so `stop` can verify that the process named
19644
+ // in a (possibly stale) pid file is really THIS bridge before any
19645
+ // signal is sent — a healthy session on the recorded port alone
19646
+ // must not authorize killing an unrelated reused PID.
19647
+ pid: process.pid,
19643
19648
  busy: client.isBusy?.() ?? false,
19644
19649
  ...droppedSelection ? { pageIdentityChanged: true } : {}
19645
19650
  });
@@ -46,8 +46,11 @@ export function runChromeDevtoolsAxiCli(args = process.argv.slice(2), options =
46
46
  ...process.env,
47
47
  CHROME_DEVTOOLS_AXI_MCP_PATH: process.env.CHROME_DEVTOOLS_AXI_MCP_PATH?.trim() || resolveChromeDevtoolsMcpScript(),
48
48
  // Pie pins chrome-devtools-mcp; its daily background update check
49
- // (network + ~/.cache write + "install @latest" nag) stays off unless
50
- // the environment or caller explicitly provides a value.
49
+ // (network + ~/.cache write + "install @latest" nag) is UNCONDITIONALLY
50
+ // disabled for the packaged CLI: the upstream treats any nonempty
51
+ // value — including "0" or "false" — as "disable", so this env var
52
+ // cannot re-enable the check. To run the check, invoke the vendored
53
+ // cli outside Pie with the variable unset.
51
54
  CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS: process.env.CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS || "1",
52
55
  ...options.env,
53
56
  },
@@ -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-P4VG6ZZG.js");
24
+ const { main } = await import("./cli-EJ5MQZNA.js");
25
25
  await main(process.argv.slice(2));
26
26
  }
@@ -1935,6 +1935,23 @@ async function waitForProcessExit(pid, timeoutMs) {
1935
1935
  return !isProcessAlive(pid);
1936
1936
  }
1937
1937
  function isBridgeProcess(pid) {
1938
+ if (process.platform === "win32") {
1939
+ try {
1940
+ const command = execFileSync(
1941
+ "powershell",
1942
+ [
1943
+ "-NoProfile",
1944
+ "-NonInteractive",
1945
+ "-Command",
1946
+ `(Get-CimInstance Win32_Process -Filter "ProcessId=${pid}").CommandLine`
1947
+ ],
1948
+ { encoding: "utf-8", timeout: 4e3 }
1949
+ );
1950
+ return command.includes("chrome-devtools-axi-bridge");
1951
+ } catch {
1952
+ return false;
1953
+ }
1954
+ }
1938
1955
  try {
1939
1956
  const command = execFileSync("ps", ["-p", String(pid), "-o", "command="], {
1940
1957
  encoding: "utf-8",
@@ -1949,28 +1966,43 @@ async function probeBridgeIdentity(port, sessionName) {
1949
1966
  try {
1950
1967
  const resp = await httpGet(port, "/health", HEALTH_TIMEOUT_MS);
1951
1968
  const data = JSON.parse(resp);
1952
- if (data.status !== "ok") return "unknown";
1969
+ if (data.status !== "ok") return { identity: "unknown" };
1953
1970
  if (typeof data.session === "string" && data.session !== sessionName) {
1954
- return "other";
1971
+ return { identity: "other" };
1955
1972
  }
1956
- return "own";
1973
+ return {
1974
+ identity: "own",
1975
+ reportedPid: typeof data.pid === "number" && data.pid > 0 ? data.pid : void 0
1976
+ };
1957
1977
  } catch {
1958
- return "unknown";
1978
+ return { identity: "unknown" };
1959
1979
  }
1960
1980
  }
1961
1981
  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
- };
1982
+ const probe = await probeBridgeIdentity(pidInfo.port, sessionName);
1983
+ if (probe.identity === "own") {
1984
+ if (probe.reportedPid !== void 0) {
1985
+ if (probe.reportedPid === pidInfo.pid) {
1986
+ return {
1987
+ terminate: true,
1988
+ killProcessGroup: isBridgeProcess(pidInfo.pid),
1989
+ targetPid: pidInfo.pid
1990
+ };
1991
+ }
1992
+ return {
1993
+ terminate: true,
1994
+ killProcessGroup: isBridgeProcess(probe.reportedPid),
1995
+ targetPid: probe.reportedPid
1996
+ };
1997
+ }
1998
+ const bridge2 = isBridgeProcess(pidInfo.pid);
1999
+ return { terminate: bridge2, killProcessGroup: bridge2, targetPid: pidInfo.pid };
1968
2000
  }
1969
- if (identity === "other") {
2001
+ if (probe.identity === "other") {
1970
2002
  return { terminate: false, killProcessGroup: false };
1971
2003
  }
1972
2004
  const bridge = isBridgeProcess(pidInfo.pid);
1973
- return { terminate: bridge, killProcessGroup: bridge };
2005
+ return { terminate: bridge, killProcessGroup: bridge, targetPid: pidInfo.pid };
1974
2006
  }
1975
2007
  function removePidFile(pidFile) {
1976
2008
  try {
@@ -1978,13 +2010,27 @@ function removePidFile(pidFile) {
1978
2010
  } catch {
1979
2011
  }
1980
2012
  }
2013
+ function taskkillTree(pid) {
2014
+ try {
2015
+ execFileSync("taskkill", ["/PID", String(pid), "/T", "/F"], {
2016
+ stdio: "ignore",
2017
+ timeout: 1e4
2018
+ });
2019
+ return true;
2020
+ } catch {
2021
+ return !isProcessAlive(pid);
2022
+ }
2023
+ }
1981
2024
  async function terminateBridgeProcess(pid, opts = {}) {
1982
- if (!isProcessAlive(pid)) return;
2025
+ if (!isProcessAlive(pid)) return true;
1983
2026
  const killProcessGroup = opts.killProcessGroup === true;
2027
+ if (process.platform === "win32") {
2028
+ return taskkillTree(pid) && !await isProcessAlive(pid);
2029
+ }
1984
2030
  try {
1985
2031
  process.kill(pid, "SIGTERM");
1986
- } catch {
1987
- return;
2032
+ } catch (error) {
2033
+ if (error.code === "ESRCH") return true;
1988
2034
  }
1989
2035
  if (await waitForProcessExit(pid, 2e3)) {
1990
2036
  if (killProcessGroup) {
@@ -1993,7 +2039,7 @@ async function terminateBridgeProcess(pid, opts = {}) {
1993
2039
  } catch {
1994
2040
  }
1995
2041
  }
1996
- return;
2042
+ return true;
1997
2043
  }
1998
2044
  if (killProcessGroup) {
1999
2045
  try {
@@ -2010,7 +2056,7 @@ async function terminateBridgeProcess(pid, opts = {}) {
2010
2056
  } catch {
2011
2057
  }
2012
2058
  }
2013
- await waitForProcessExit(pid, 1e3);
2059
+ return waitForProcessExit(pid, 1e3);
2014
2060
  }
2015
2061
  function spawnBridgeProcess(port, sessionName) {
2016
2062
  const bridgeScript = resolveBridgeScript(import.meta.dirname);
@@ -2100,7 +2146,7 @@ async function ensureBridge(spawnBridge = spawnBridgeProcess, notice) {
2100
2146
  }
2101
2147
  const verdict = await judgeRecordedBridge(pidInfo, sessionName);
2102
2148
  if (verdict.terminate) {
2103
- await terminateBridgeProcess(pidInfo.pid, {
2149
+ await terminateBridgeProcess(verdict.targetPid ?? pidInfo.pid, {
2104
2150
  killProcessGroup: verdict.killProcessGroup
2105
2151
  });
2106
2152
  } else {
@@ -2374,15 +2420,15 @@ async function getSessionSnapshotIfRunning() {
2374
2420
  }
2375
2421
  async function stopBridge() {
2376
2422
  const pidInfo = readPidFile();
2377
- if (!pidInfo) return false;
2378
- if (!isProcessAlive(pidInfo.pid)) return false;
2423
+ if (!pidInfo) return "noop";
2424
+ if (!isProcessAlive(pidInfo.pid)) return "noop";
2379
2425
  const verdict = await judgeRecordedBridge(pidInfo, resolveSessionName());
2380
- if (!verdict.terminate) return false;
2381
- await terminateBridgeProcess(pidInfo.pid, {
2426
+ if (!verdict.terminate) return "noop";
2427
+ const terminated = await terminateBridgeProcess(verdict.targetPid ?? pidInfo.pid, {
2382
2428
  killProcessGroup: verdict.killProcessGroup
2383
2429
  });
2384
2430
  clearSelectedPageId();
2385
- return true;
2431
+ return terminated ? "stopped" : "failed";
2386
2432
  }
2387
2433
 
2388
2434
  // vendor/chrome-devtools-axi/src/generation.ts
@@ -3931,12 +3977,15 @@ async function handleStart() {
3931
3977
  const port = await ensureBridge();
3932
3978
  return encode({ status: "ready", port });
3933
3979
  }
3934
- function formatStopOutput(wasStopped) {
3935
- return encode({ status: wasStopped ? "stopped" : "stopped (no-op)" });
3980
+ function formatStopOutput(outcome) {
3981
+ if (outcome === "failed") {
3982
+ throw new Error("bridge stop failed: the target survived SIGTERM and SIGKILL escalation");
3983
+ }
3984
+ return encode({ status: outcome === "stopped" ? "stopped" : "stopped (no-op)" });
3936
3985
  }
3937
3986
  async function handleStop() {
3938
- const wasStopped = await stopBridge();
3939
- return formatStopOutput(wasStopped);
3987
+ const outcome = await stopBridge();
3988
+ return formatStopOutput(outcome);
3940
3989
  }
3941
3990
  async function handlePages() {
3942
3991
  const result = await callTool("list_pages");
@@ -9950,7 +9950,13 @@ var import_node_child_process = require("node:child_process");
9950
9950
  var import_node_crypto = require("node:crypto");
9951
9951
  var import_node_os = __toESM(require("node:os"));
9952
9952
  var import_node_path = __toESM(require("node:path"));
9953
- var expandHome = (value) => value.replace(/^~(?=$|\/)/, () => import_node_os.default.homedir());
9953
+ var expandHome = (value) => {
9954
+ if (value === "~") return import_node_os.default.homedir();
9955
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
9956
+ return import_node_path.default.join(import_node_os.default.homedir(), value.slice(2).replace(/\\/g, "/"));
9957
+ }
9958
+ return value;
9959
+ };
9954
9960
  var STATE_DIR = import_node_path.default.resolve(process.env.PIE_TUI_USE_STATE_DIR ? expandHome(process.env.PIE_TUI_USE_STATE_DIR) : import_node_path.default.join(expandHome(process.env.PIE_CODING_AGENT_DIR || import_node_path.default.join(import_node_os.default.homedir(), ".pie")), "tui-use"));
9955
9961
  var LOG_PATH = import_node_path.default.join(STATE_DIR, "daemon.log");
9956
9962
  var PID_PATH = import_node_path.default.join(STATE_DIR, "daemon.pid");
@@ -10139,17 +10145,27 @@ async function startDaemon() {
10139
10145
  if (error.code !== "EEXIST") throw error;
10140
10146
  try {
10141
10147
  const contents = import_node_fs.default.readFileSync(LOCK_PATH, "utf8");
10142
- const lock = JSON.parse(contents);
10143
- let alive = true;
10144
- if (!Number.isInteger(lock.pid) || lock.pid <= 0) alive = false;
10145
- else {
10148
+ let reclaim = false;
10149
+ try {
10150
+ const lock = JSON.parse(contents);
10151
+ let alive = true;
10152
+ if (!Number.isInteger(lock.pid) || lock.pid <= 0) alive = false;
10153
+ else {
10154
+ try {
10155
+ process.kill(lock.pid, 0);
10156
+ } catch (e) {
10157
+ if (e.code === "ESRCH") alive = false;
10158
+ }
10159
+ }
10160
+ if (!alive) reclaim = true;
10161
+ } catch {
10146
10162
  try {
10147
- process.kill(lock.pid, 0);
10148
- } catch (e) {
10149
- if (e.code === "ESRCH") alive = false;
10163
+ const mtimeMs = import_node_fs.default.statSync(LOCK_PATH).mtimeMs;
10164
+ if (Date.now() - mtimeMs > 5e3) reclaim = true;
10165
+ } catch {
10150
10166
  }
10151
10167
  }
10152
- if (!alive && import_node_fs.default.readFileSync(LOCK_PATH, "utf8") === contents) import_node_fs.default.unlinkSync(LOCK_PATH);
10168
+ if (reclaim && import_node_fs.default.readFileSync(LOCK_PATH, "utf8") === contents) import_node_fs.default.unlinkSync(LOCK_PATH);
10153
10169
  } catch {
10154
10170
  }
10155
10171
  await sleep(100);
@@ -7661,7 +7661,13 @@ function validateRequest(value) {
7661
7661
  var import_node_crypto = require("node:crypto");
7662
7662
  var import_node_os = __toESM(require("node:os"));
7663
7663
  var import_node_path3 = __toESM(require("node:path"));
7664
- var expandHome = (value) => value.replace(/^~(?=$|\/)/, () => import_node_os.default.homedir());
7664
+ var expandHome = (value) => {
7665
+ if (value === "~") return import_node_os.default.homedir();
7666
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
7667
+ return import_node_path3.default.join(import_node_os.default.homedir(), value.slice(2).replace(/\\/g, "/"));
7668
+ }
7669
+ return value;
7670
+ };
7665
7671
  var STATE_DIR = import_node_path3.default.resolve(process.env.PIE_TUI_USE_STATE_DIR ? expandHome(process.env.PIE_TUI_USE_STATE_DIR) : import_node_path3.default.join(expandHome(process.env.PIE_CODING_AGENT_DIR || import_node_path3.default.join(import_node_os.default.homedir(), ".pie")), "tui-use"));
7666
7672
  var LOG_PATH = import_node_path3.default.join(STATE_DIR, "daemon.log");
7667
7673
  var PID_PATH = import_node_path3.default.join(STATE_DIR, "daemon.pid");
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  Agent
4
- } from "./chunk-ZKHMNEXL.js";
4
+ } from "./chunk-RTTGO2A6.js";
5
5
  import {
6
6
  FileSystemGateway,
7
7
  completeSimple,
@@ -10,7 +10,7 @@ import {
10
10
  getPlatformConfig,
11
11
  hashText,
12
12
  streamSimple
13
- } from "./chunk-YRXXRLMQ.js";
13
+ } from "./chunk-UD7KYQ6J.js";
14
14
  import {
15
15
  Type
16
16
  } from "./chunk-XXGYUYXI.js";
@@ -5244,6 +5244,16 @@ var AgentSessionQueue = class {
5244
5244
  }
5245
5245
  this.emitQueueChanged();
5246
5246
  }
5247
+ /** Promote only an input still owned by this queue, never re-send agent-owned inputs. */
5248
+ promoteLatestFollowUp() {
5249
+ const input = this.queuedInputs.slice().reverse().find((queued) => queued.visibility === "user" && queued.mode === "followUp");
5250
+ if (!input || input.deliveredToAgent) return void 0;
5251
+ input.mode = "steer";
5252
+ input.deliverWhileStreaming = true;
5253
+ if (this.getIsStreaming()) this.deliverToAgent(input);
5254
+ this.emitQueueChanged();
5255
+ return cloneQueuedInput(input);
5256
+ }
5247
5257
  deliverToAgent(queuedInput) {
5248
5258
  if (queuedInput.deliveredToAgent) {
5249
5259
  return;
@@ -6404,6 +6414,12 @@ var AgentSessionController = class {
6404
6414
  drainQueuedInputsForEditor() {
6405
6415
  return this.queue.drainForEditor();
6406
6416
  }
6417
+ /** Upgrade the latest visible follow-up that has not been delivered to the agent. */
6418
+ promoteLatestFollowUpToSteer() {
6419
+ const promoted = this.disposed ? void 0 : this.queue.promoteLatestFollowUp();
6420
+ if (promoted && !this.isStreaming) this.requestQueueDispatch();
6421
+ return promoted;
6422
+ }
6407
6423
  replaceQueuedInputText(search, replacement) {
6408
6424
  this.queue.replaceText(search, replacement);
6409
6425
  }
@@ -8775,7 +8791,7 @@ function createReadTool(cwd, options) {
8775
8791
  return {
8776
8792
  name: "read",
8777
8793
  label: "read",
8778
- description: `Read a file's contents. Use this before editing, and use offset/limit for large files. It can also directly understand image, video, audio, and document files when a file-understanding model is configured; pass question for a focused media/document question instead of preprocessing the file with shell tools. Example: read_file path='Scripts/PlayerController.cs' or read_file path='Data/config.json'. ${pathResolutionDescription} For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). When you need the full file, continue with a larger offset until complete.`,
8794
+ description: `Read a file's contents. Use this before editing, and use offset/limit for large files. Images are attached for the current model when it supports vision; otherwise a configured file-understanding service analyzes them. Video, audio, and documents require a configured understanding service. Pass question to specify what to focus on; it does not force a separate model request. Example: read_file path='Scripts/PlayerController.cs' or read_file path='Data/config.json'. ${pathResolutionDescription} For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). When you need the full file, continue with a larger offset until complete.`,
8779
8795
  parameters: readSchema,
8780
8796
  execute: async (_toolCallId, params, signal) => {
8781
8797
  const { path: path4, root, offset, limit, question } = params;
@@ -8848,7 +8864,7 @@ function createReadTool(cwd, options) {
8848
8864
  });
8849
8865
  return;
8850
8866
  }
8851
- if (magicInfo?.kind === "image" && question && options?.understandFile && (!INLINE_IMAGE_MIME_TYPES.has(magicInfo.mimeType) || stat.size > MAX_INLINE_IMAGE_BYTES)) {
8867
+ if (magicInfo?.kind === "image" && options?.understandFile && (!INLINE_IMAGE_MIME_TYPES.has(magicInfo.mimeType) || stat.size > MAX_INLINE_IMAGE_BYTES)) {
8852
8868
  resolve3(await options.understandFile({
8853
8869
  absolutePath,
8854
8870
  displayPath: path4,
@@ -8864,7 +8880,7 @@ function createReadTool(cwd, options) {
8864
8880
  }
8865
8881
  const imageInfo = options?.allowBinary === false || magicInfo?.kind !== "image" || !INLINE_IMAGE_MIME_TYPES.has(magicInfo.mimeType) ? void 0 : await readImageInfo(absolutePath, fs2);
8866
8882
  if (imageInfo) {
8867
- if (question && options?.understandFile) {
8883
+ if (options?.understandFile) {
8868
8884
  resolve3(await options.understandFile({
8869
8885
  absolutePath,
8870
8886
  displayPath: path4,
@@ -8879,7 +8895,8 @@ function createReadTool(cwd, options) {
8879
8895
  const dimensionText = imageInfo.width && imageInfo.height ? `, ${imageInfo.width}x${imageInfo.height}` : "";
8880
8896
  resolve3({
8881
8897
  content: [
8882
- { type: "text", text: `Read image file [${imageInfo.mimeType}${dimensionText}]. Original image attached as an image block; for very large images, prefer a smaller crop or resized copy before rereading.` },
8898
+ { type: "text", text: `Read image file [${imageInfo.mimeType}${dimensionText}]. Original image attached as an image block; for very large images, prefer a smaller crop or resized copy before rereading.${question?.trim() ? `
8899
+ Focus: ${question.trim()}` : ""}` },
8883
8900
  { type: "image", data: imageInfo.base64, mimeType: imageInfo.mimeType }
8884
8901
  ],
8885
8902
  details: {
@@ -13451,6 +13468,21 @@ function createReadFileUnderstandingHandler(deps) {
13451
13468
  async function understandFileWithModel(deps, request) {
13452
13469
  const purpose = `read_file.${request.kind}`;
13453
13470
  const agentModel = deps.getAgentModel();
13471
+ if (request.kind === "image" && request.imageInfo && agentModel?.input.includes("image")) {
13472
+ if (request.signal?.aborted) throw new Error("Operation aborted");
13473
+ return {
13474
+ content: [
13475
+ { type: "text", text: `Read image file ${request.displayPath} [${request.mimeType}]. Original image attached for the current model.${request.question?.trim() ? `
13476
+ Focus: ${request.question.trim()}` : ""}` },
13477
+ { type: "image", data: request.imageInfo.base64, mimeType: request.imageInfo.mimeType }
13478
+ ],
13479
+ details: {
13480
+ image: { mimeType: request.imageInfo.mimeType, width: request.imageInfo.width, height: request.imageInfo.height },
13481
+ understanding: { purpose, kind: request.kind, mimeType: request.mimeType, agentModel: getModelLabel(agentModel), routeSource: "current_model", inputMode: "nativeAttachment" }
13482
+ },
13483
+ isError: false
13484
+ };
13485
+ }
13454
13486
  const singleDecision = deps.resolveToolModel(purpose);
13455
13487
  const candidateDecisions = deps.resolveToolModelCandidates?.(purpose) ?? [singleDecision];
13456
13488
  const sessionId = deps.getSessionId() || `read-file-${Date.now()}`;
@@ -8,7 +8,7 @@ import {
8
8
  createSharedWebSearchTool,
9
9
  interpretShellExit,
10
10
  requestInteraction
11
- } from "./chunk-REP6QANY.js";
11
+ } from "./chunk-LO2V5DZZ.js";
12
12
  import {
13
13
  getBuiltinDir,
14
14
  getConfigDir
@@ -1176,7 +1176,12 @@ function createCliRenderedPageReader() {
1176
1176
  networkRequests: linesOf(stripHelpHints(networkOutput))
1177
1177
  };
1178
1178
  } finally {
1179
- runAxi(["stop"], { env, timeoutMs: 1e4 }).catch(() => void 0);
1179
+ try {
1180
+ await runAxi(["stop"], { env, timeoutMs: 15e3 });
1181
+ } catch (error) {
1182
+ process.stderr.write(`[rendered-page-reader] axi stop after read failed: ${error instanceof Error ? error.message : String(error)}
1183
+ `);
1184
+ }
1180
1185
  }
1181
1186
  };
1182
1187
  }
@@ -3,7 +3,7 @@ import {
3
3
  EventStream,
4
4
  streamSimple,
5
5
  validateToolArguments
6
- } from "./chunk-YRXXRLMQ.js";
6
+ } from "./chunk-UD7KYQ6J.js";
7
7
 
8
8
  // ../../packages/agent-core/src/agent-loop.ts
9
9
  function createLoopErrorMessage(error, config, signal) {
@@ -2960,6 +2960,7 @@ function convertMessages(model, context, compat) {
2960
2960
  const hasContent = content !== null && content !== void 0 && (typeof content === "string" ? content.length > 0 : Array.isArray(content) && content.length > 0);
2961
2961
  if (hasContent || assistantMsg.tool_calls) params.push(assistantMsg);
2962
2962
  } else if (msg.role === "toolResult") {
2963
+ const attachments = [];
2963
2964
  for (; i < transformedMessages.length && transformedMessages[i].role === "toolResult"; i++) {
2964
2965
  const toolMsg = transformedMessages[i];
2965
2966
  const textResult = toolMsg.content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
@@ -2970,7 +2971,15 @@ function convertMessages(model, context, compat) {
2970
2971
  };
2971
2972
  if (compat.requiresToolResultName && toolMsg.toolName) toolResultMsg.name = toolMsg.toolName;
2972
2973
  params.push(toolResultMsg);
2974
+ if (model.input.includes("image")) {
2975
+ const images = toolMsg.content.filter((c) => c.type === "image");
2976
+ if (images.length > 0) {
2977
+ attachments.push({ type: "text", text: `Images from tool ${toolMsg.toolName ?? "unknown"} (call ${toolMsg.toolCallId}):` });
2978
+ for (const image of images) attachments.push(...convertUserContentItem(image));
2979
+ }
2980
+ }
2973
2981
  }
2982
+ if (attachments.length > 0) params.push({ role: "user", content: attachments });
2974
2983
  i -= 1;
2975
2984
  }
2976
2985
  }
@@ -3,8 +3,8 @@ import {
3
3
  Agent,
4
4
  agentLoop,
5
5
  agentLoopContinue
6
- } from "./chunk-ZKHMNEXL.js";
7
- import "./chunk-YRXXRLMQ.js";
6
+ } from "./chunk-RTTGO2A6.js";
7
+ import "./chunk-UD7KYQ6J.js";
8
8
  import "./chunk-XXGYUYXI.js";
9
9
  import "./chunk-LZQXQOPJ.js";
10
10
  export {
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  EventStream
4
- } from "./chunk-YRXXRLMQ.js";
4
+ } from "./chunk-UD7KYQ6J.js";
5
5
  import "./chunk-XXGYUYXI.js";
6
6
  import "./chunk-LZQXQOPJ.js";
7
7
 
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  createCliWebResearchHealthStore,
8
8
  formatSize,
9
9
  truncateHeadTail
10
- } from "./chunks/chunk-N2MK46AE.js";
10
+ } from "./chunks/chunk-M4OJARWJ.js";
11
11
  import {
12
12
  AGENTS_CONTEXT_FILE_NAME,
13
13
  AgentSessionController,
@@ -60,8 +60,8 @@ import {
60
60
  setExtensionEnabledInSettings,
61
61
  shouldPreserveExecutionStateForUserText,
62
62
  supersedeExecutionState
63
- } from "./chunks/chunk-REP6QANY.js";
64
- import "./chunks/chunk-ZKHMNEXL.js";
63
+ } from "./chunks/chunk-LO2V5DZZ.js";
64
+ import "./chunks/chunk-RTTGO2A6.js";
65
65
  import {
66
66
  Deref,
67
67
  EMPTY_ASSISTANT_CONTENT_ERROR,
@@ -85,7 +85,7 @@ import {
85
85
  setLogger,
86
86
  sortToolModelCandidatesByCapability,
87
87
  type_exports
88
- } from "./chunks/chunk-YRXXRLMQ.js";
88
+ } from "./chunks/chunk-UD7KYQ6J.js";
89
89
  import {
90
90
  VERSION,
91
91
  getAgentDir,
@@ -55210,8 +55210,11 @@ function publishJournalPath(sessionsDir, safeTargetId) {
55210
55210
  return path7.join(sessionsDir, PUBLISH_JOURNAL_DIR, `${safeTargetId}.json`);
55211
55211
  }
55212
55212
  var FSYNC_UNSUPPORTED_CODES = /* @__PURE__ */ new Set(["EPERM", "EINVAL", "ENOTSUP", "EOPNOTSUPP", "EACCES", "EBADF"]);
55213
+ var fsyncDirectoryFaultForTests = {};
55213
55214
  function fsyncDirectory(dirPath) {
55214
55215
  try {
55216
+ const injected = fsyncDirectoryFaultForTests.fault?.(dirPath);
55217
+ if (injected) throw injected;
55215
55218
  const fd = fs7.openSync(dirPath, "r");
55216
55219
  try {
55217
55220
  fs7.fsyncSync(fd);
@@ -55430,11 +55433,21 @@ async function forkStoredSession(params) {
55430
55433
  if (hadPreviousTarget) {
55431
55434
  try {
55432
55435
  params.store.moveSession(params.targetSessionId, backupId);
55433
- if (params.sessionsDir) fsyncDirectory(params.sessionsDir);
55434
55436
  } catch (error) {
55435
55437
  clearPublishJournal(journalPath);
55436
55438
  throw error;
55437
55439
  }
55440
+ try {
55441
+ if (params.sessionsDir) fsyncDirectory(params.sessionsDir);
55442
+ } catch (error) {
55443
+ markPublishJournalFailed(journalPath);
55444
+ preserveStagingForRecovery = true;
55445
+ throw new SessionControlError(
55446
+ "session_io_error",
55447
+ `Fork publish durability failure after moving the previous session aside; it is preserved as ${backupId} and startup recovery will restore it: ${String(error)}`,
55448
+ false
55449
+ );
55450
+ }
55438
55451
  }
55439
55452
  try {
55440
55453
  params.store.moveSession(stagingId, params.targetSessionId);
@@ -70137,22 +70150,40 @@ var SettingsSelectorComponent = class extends Container {
70137
70150
  };
70138
70151
 
70139
70152
  // src/core/session-display.ts
70153
+ function looksAutoGeneratedSessionName(name) {
70154
+ return /^Session (\d|[a-z0-9]{6,8}$)/i.test(name.trim());
70155
+ }
70156
+ function persistedNameIsAuthoritative(metadata) {
70157
+ if (metadata.nameSource === "derived") return true;
70158
+ if (metadata.nameSource === void 0) return !looksAutoGeneratedSessionName(metadata.name);
70159
+ return false;
70160
+ }
70140
70161
  function deriveSessionTitle(options) {
70141
70162
  const { activeSession, messages } = options;
70142
70163
  if (!activeSession) {
70143
70164
  return "No session";
70144
70165
  }
70166
+ if (activeSession.metadata.nameSource === "manual") {
70167
+ const manual = normalizeSessionTitle(activeSession.metadata.name);
70168
+ if (manual) return manual;
70169
+ }
70170
+ if (persistedNameIsAuthoritative(activeSession.metadata)) {
70171
+ const persisted = normalizeSessionTitle(activeSession.metadata.name);
70172
+ if (persisted) return persisted;
70173
+ }
70145
70174
  for (const msg of messages) {
70146
70175
  if (msg.role !== "user") continue;
70147
70176
  const text = extractUserText(msg);
70148
70177
  if (isCompactSummaryMessage(text.trimStart())) continue;
70149
70178
  const normalized = normalizeSessionTitle(text);
70150
70179
  if (normalized) {
70180
+ activeSession.metadata.name = normalized;
70181
+ activeSession.metadata.nameSource = "derived";
70151
70182
  return normalized;
70152
70183
  }
70153
70184
  }
70154
70185
  const metadataName = normalizeSessionTitle(activeSession.metadata.name);
70155
- if (metadataName && !metadataName.startsWith("Session ")) {
70186
+ if (metadataName && persistedNameIsAuthoritative(activeSession.metadata)) {
70156
70187
  return metadataName;
70157
70188
  }
70158
70189
  return `S:${activeSession.id.slice(-6)}`;
@@ -77094,7 +77125,9 @@ function updatePendingMessagesDisplay(host) {
77094
77125
  host.pendingMessagesContainer.addChild(new Text(text, 1, 0));
77095
77126
  }
77096
77127
  const dequeueHint = host.keybindings.getKeys("dequeue")[0] || "alt+up";
77097
- const hintText = theme.fg("dim", `\u21B3 Press ${dequeueHint} to edit all queued messages`);
77128
+ const steerHint = host.keybindings.getKeys("steer")[0];
77129
+ const promotionHint = followUpMessages.length > 0 && !followUpMessages[followUpMessages.length - 1].deliveredToAgent && steerHint ? `${steerHint} with empty editor: steer latest follow-up \xB7 ` : "";
77130
+ const hintText = theme.fg("dim", `\u21B3 ${promotionHint}${dequeueHint}: edit all queued messages`);
77098
77131
  host.pendingMessagesContainer.addChild(new Text(hintText, 1, 0));
77099
77132
  }
77100
77133
  function dequeueMessages(host) {
@@ -77150,7 +77183,7 @@ function queueMessage(host, text, mode, options = {}) {
77150
77183
  }
77151
77184
  updatePendingMessagesDisplay(host);
77152
77185
  host.showStatus(
77153
- options.statusMessage || `Queued message for ${mode === "steer" ? "steering" : "follow-up"}`
77186
+ options.statusMessage || (mode === "steer" ? "Queued message for steering" : `Queued for follow-up after the current task.${host.keybindings.getKeys("steer")[0] ? ` With empty editor, press ${host.keybindings.getKeys("steer")[0]} to steer the current task instead.` : ""}`)
77154
77187
  );
77155
77188
  host.ui.requestRender();
77156
77189
  }
@@ -77169,7 +77202,22 @@ function handleFollowUp(host) {
77169
77202
  }
77170
77203
  function handleSteer(host) {
77171
77204
  const text = host.editor.getText().trim();
77172
- if (!text) return;
77205
+ if (!text) {
77206
+ const promoted = host.agentSessionController?.promoteLatestFollowUpToSteer();
77207
+ if (!promoted) {
77208
+ host.showStatus("No waiting follow-up to steer (it may already be processing)");
77209
+ return;
77210
+ }
77211
+ if (host.executionState.applyUserTextLifecycle(promoted.displayText)) {
77212
+ host.persistExecutionState();
77213
+ void host.refreshTodoWidget();
77214
+ }
77215
+ host.logDebug("queue:promote", { id: promoted.id, mode: promoted.mode });
77216
+ updatePendingMessagesDisplay(host);
77217
+ host.showStatus("Latest follow-up changed to steer; it will guide the current task at the next message boundary");
77218
+ host.ui.requestRender();
77219
+ return;
77220
+ }
77173
77221
  if (isCommandLikeInput(text)) {
77174
77222
  host.editor.onSubmit?.(text);
77175
77223
  return;
@@ -79986,7 +80034,7 @@ var InteractiveMode = class {
79986
80034
  async init() {
79987
80035
  const activeSession = this.sessionManager.getActiveSession();
79988
80036
  this.thinkingLevel = resolveInitialThinkingLevel({ session: activeSession, settingsManager: this.options.settingsManager });
79989
- const { Agent } = await import("./chunks/src-SXUQ7OY6.js");
80037
+ const { Agent } = await import("./chunks/src-33WRCQWX.js");
79990
80038
  this.agent = new Agent({
79991
80039
  initialState: {
79992
80040
  systemPrompt: this.buildSystemPrompt(),
@@ -80820,7 +80868,7 @@ async function runPrintMode(params) {
80820
80868
  `);
80821
80869
  console.log("Assistant: ");
80822
80870
  }
80823
- const { Agent: AgentClass } = await import("./chunks/src-SXUQ7OY6.js");
80871
+ const { Agent: AgentClass } = await import("./chunks/src-33WRCQWX.js");
80824
80872
  let agent;
80825
80873
  const printModeExtensions = await loadPrintModeExtensions({
80826
80874
  cwd: runtime.cwd,
@@ -81681,7 +81729,7 @@ async function runJsonEventsMode(params) {
81681
81729
  return;
81682
81730
  }
81683
81731
  const parent = agent;
81684
- const { Agent: AgentClass } = await import("./chunks/src-SXUQ7OY6.js");
81732
+ const { Agent: AgentClass } = await import("./chunks/src-33WRCQWX.js");
81685
81733
  const sideAgent = new AgentClass({
81686
81734
  initialState: {
81687
81735
  systemPrompt: parent.state.systemPrompt,
@@ -82025,7 +82073,7 @@ async function runJsonEventsMode(params) {
82025
82073
  if (runtime.initialModel.unconfigured || controller) {
82026
82074
  return;
82027
82075
  }
82028
- const { Agent: AgentClass } = await import("./chunks/src-SXUQ7OY6.js");
82076
+ const { Agent: AgentClass } = await import("./chunks/src-33WRCQWX.js");
82029
82077
  const jsonModeExtensions = await loadJsonModeExtensions({ runtime, getAgent: () => agent });
82030
82078
  extensionRunner = jsonModeExtensions.runner;
82031
82079
  const tools = [...runtime.tools, ...jsonModeExtensions.extensionTools];
@@ -82614,7 +82662,7 @@ async function runWithJsonEventsConsoleIsolation(fn) {
82614
82662
  }
82615
82663
  async function createCliTestStreamFnFromEnv() {
82616
82664
  if (!process.env.PIE_TEST_MOCK_STREAM_SEQUENCE) return void 0;
82617
- return (await import("./chunks/test-stream-OD3B57AS.js")).createTestStreamFnFromSequenceEnv();
82665
+ return (await import("./chunks/test-stream-LFR6GSKQ.js")).createTestStreamFnFromSequenceEnv();
82618
82666
  }
82619
82667
  function resolveJsonEventsDetailFlag() {
82620
82668
  for (let index = 0; index < process.argv.length; index += 1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/pie",
3
- "version": "2.5.1",
3
+ "version": "2.5.2",
4
4
  "description": "Pie AI Agent CLI",
5
5
  "type": "module",
6
6
  "bin": {