@cydm/pie 2.5.0 → 2.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/builtin/extensions/ask-user/index.js +3 -3
  2. package/dist/builtin/extensions/computer-use/index.js +17 -2
  3. package/dist/builtin/extensions/computer-use/prebuilt/macos/arm64/pie-computer-use +0 -0
  4. package/dist/builtin/extensions/computer-use/prebuilt/macos/universal/pie-computer-use.app/Contents/MacOS/pie-computer-use +0 -0
  5. package/dist/builtin/extensions/computer-use/prebuilt/macos/x64/pie-computer-use +0 -0
  6. package/dist/builtin/extensions/computer-use/scripts/setup-helper.mjs +90 -9
  7. package/dist/builtin/extensions/herdr-agent-state/index.js +39 -3
  8. package/dist/builtin/extensions/mcp-adapter/index.js +161 -35
  9. package/dist/builtin/extensions/plan-mode/index.js +3 -3
  10. package/dist/builtin/extensions/subagent/index.js +4 -4
  11. package/dist/builtin/extensions/todo/index.js +3 -3
  12. package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-bridge.js +5 -0
  13. package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-cli.js +5 -2
  14. package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-runtime.js +1 -1
  15. package/dist/builtin/skills/chrome-devtools-axi/{cli-P4VG6ZZG.js → cli-EJ5MQZNA.js} +76 -27
  16. package/dist/builtin/skills/tui-use/tui-use-cli.cjs +25 -9
  17. package/dist/builtin/skills/tui-use/tui-use-daemon.cjs +7 -1
  18. package/dist/chunks/{chunk-JXY3YLDS.js → chunk-LO2V5DZZ.js} +46 -9
  19. package/dist/chunks/{chunk-PU7IIEKH.js → chunk-M4OJARWJ.js} +25 -15
  20. package/dist/chunks/{chunk-ZKHMNEXL.js → chunk-RTTGO2A6.js} +1 -1
  21. package/dist/chunks/{chunk-YRXXRLMQ.js → chunk-UD7KYQ6J.js} +9 -0
  22. package/dist/chunks/{src-SXUQ7OY6.js → src-33WRCQWX.js} +2 -2
  23. package/dist/chunks/{test-stream-OD3B57AS.js → test-stream-LFR6GSKQ.js} +1 -1
  24. package/dist/cli.js +117 -20
  25. package/package.json +1 -1
@@ -8,9 +8,9 @@ import {
8
8
  executeManageTodoList,
9
9
  executionStateToTodos,
10
10
  restoreExecutionState
11
- } from "../../../chunks/chunk-JXY3YLDS.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";
@@ -1742,8 +1742,7 @@ function normalizeTaskDefs(args) {
1742
1742
  if (def.model !== void 0 && !modelOverride) {
1743
1743
  return { error: `Invalid task #${index + 1}: "model" must be a non-empty model reference such as "provider/model-id".` };
1744
1744
  }
1745
- const thinkingLevel = typeof def.thinkingLevel === "string" ? def.thinkingLevel.trim() : void 0;
1746
- if (def.thinkingLevel !== void 0 && (!thinkingLevel || !SUBAGENT_THINKING_LEVELS.includes(thinkingLevel))) {
1745
+ if (def.thinkingLevel !== void 0 && (typeof def.thinkingLevel !== "string" || !SUBAGENT_THINKING_LEVELS.includes(def.thinkingLevel))) {
1747
1746
  return { error: `Invalid task #${index + 1}: "thinkingLevel" must be one of ${SUBAGENT_THINKING_LEVELS.map((level) => `"${level}"`).join(", ")}.` };
1748
1747
  }
1749
1748
  normalized.push({
@@ -1752,7 +1751,7 @@ function normalizeTaskDefs(args) {
1752
1751
  priority,
1753
1752
  timeoutSeconds,
1754
1753
  modelOverride,
1755
- thinkingLevel
1754
+ thinkingLevel: def.thinkingLevel
1756
1755
  });
1757
1756
  }
1758
1757
  return normalized;
@@ -2968,6 +2967,9 @@ var FileSessionStore = class {
2968
2967
  throw new Error(`Session directory is a symbolic link; ${operation} refused: ${dir}`);
2969
2968
  }
2970
2969
  headStatus(sessionId) {
2970
+ if (sessionDirIsSymlink(this.pathsFor(sessionId).dir)) {
2971
+ return "missing";
2972
+ }
2971
2973
  const head = this.tryReadHeadFile(sessionId);
2972
2974
  if (head === "corrupt") {
2973
2975
  return "corrupt";
@@ -3023,6 +3025,9 @@ var FileSessionStore = class {
3023
3025
  * never reinitialize a damaged session to sequence 0.
3024
3026
  */
3025
3027
  tryReadHeadFile(sessionId) {
3028
+ if (sessionDirIsSymlink(this.pathsFor(sessionId).dir)) {
3029
+ return null;
3030
+ }
3026
3031
  const headPath = this.pathsFor(sessionId).headPath;
3027
3032
  const durable = readDurableFileSync(this.gateway, headPath, (content) => this.parseHeadContent(content));
3028
3033
  if (durable !== null) {
@@ -5239,6 +5244,16 @@ var AgentSessionQueue = class {
5239
5244
  }
5240
5245
  this.emitQueueChanged();
5241
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
+ }
5242
5257
  deliverToAgent(queuedInput) {
5243
5258
  if (queuedInput.deliveredToAgent) {
5244
5259
  return;
@@ -6399,6 +6414,12 @@ var AgentSessionController = class {
6399
6414
  drainQueuedInputsForEditor() {
6400
6415
  return this.queue.drainForEditor();
6401
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
+ }
6402
6423
  replaceQueuedInputText(search, replacement) {
6403
6424
  this.queue.replaceText(search, replacement);
6404
6425
  }
@@ -8770,7 +8791,7 @@ function createReadTool(cwd, options) {
8770
8791
  return {
8771
8792
  name: "read",
8772
8793
  label: "read",
8773
- 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.`,
8774
8795
  parameters: readSchema,
8775
8796
  execute: async (_toolCallId, params, signal) => {
8776
8797
  const { path: path4, root, offset, limit, question } = params;
@@ -8843,7 +8864,7 @@ function createReadTool(cwd, options) {
8843
8864
  });
8844
8865
  return;
8845
8866
  }
8846
- 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)) {
8847
8868
  resolve3(await options.understandFile({
8848
8869
  absolutePath,
8849
8870
  displayPath: path4,
@@ -8859,7 +8880,7 @@ function createReadTool(cwd, options) {
8859
8880
  }
8860
8881
  const imageInfo = options?.allowBinary === false || magicInfo?.kind !== "image" || !INLINE_IMAGE_MIME_TYPES.has(magicInfo.mimeType) ? void 0 : await readImageInfo(absolutePath, fs2);
8861
8882
  if (imageInfo) {
8862
- if (question && options?.understandFile) {
8883
+ if (options?.understandFile) {
8863
8884
  resolve3(await options.understandFile({
8864
8885
  absolutePath,
8865
8886
  displayPath: path4,
@@ -8874,7 +8895,8 @@ function createReadTool(cwd, options) {
8874
8895
  const dimensionText = imageInfo.width && imageInfo.height ? `, ${imageInfo.width}x${imageInfo.height}` : "";
8875
8896
  resolve3({
8876
8897
  content: [
8877
- { 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()}` : ""}` },
8878
8900
  { type: "image", data: imageInfo.base64, mimeType: imageInfo.mimeType }
8879
8901
  ],
8880
8902
  details: {
@@ -13446,6 +13468,21 @@ function createReadFileUnderstandingHandler(deps) {
13446
13468
  async function understandFileWithModel(deps, request) {
13447
13469
  const purpose = `read_file.${request.kind}`;
13448
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
+ }
13449
13486
  const singleDecision = deps.resolveToolModel(purpose);
13450
13487
  const candidateDecisions = deps.resolveToolModelCandidates?.(purpose) ?? [singleDecision];
13451
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-JXY3YLDS.js";
11
+ } from "./chunk-LO2V5DZZ.js";
12
12
  import {
13
13
  getBuiltinDir,
14
14
  getConfigDir
@@ -543,9 +543,24 @@ function createBashTool(defaultCwd, options) {
543
543
  totalNewlines += data.reduce((count, byte) => count + (byte === 10 ? 1 : 0), 0);
544
544
  if (headBytes >= maxHeadBytes) return;
545
545
  const prefix = data.subarray(0, maxHeadBytes - headBytes);
546
- headChunks.push(prefix);
546
+ headChunks.push(Buffer.from(prefix));
547
547
  headBytes += prefix.length;
548
548
  };
549
+ const appendRollingChunk = (data) => {
550
+ const owned = Buffer.from(data);
551
+ chunks.push(owned);
552
+ chunksBytes += owned.length;
553
+ while (chunksBytes > maxChunksBytes && chunks.length > 1) {
554
+ const removed = chunks.shift();
555
+ chunksBytes -= removed.length;
556
+ }
557
+ if (chunks.length === 1 && chunksBytes > maxChunksBytes) {
558
+ const only = chunks[0];
559
+ const tail = only.subarray(only.length - maxChunksBytes);
560
+ chunks[0] = Buffer.from(tail);
561
+ chunksBytes = tail.length;
562
+ }
563
+ };
549
564
  let timedOut = false;
550
565
  let processExited = false;
551
566
  let exitCode = null;
@@ -641,12 +656,7 @@ function createBashTool(defaultCwd, options) {
641
656
  return;
642
657
  }
643
658
  }
644
- chunks.push(data);
645
- chunksBytes += data.length;
646
- while (chunksBytes > maxChunksBytes && chunks.length > 1) {
647
- const removed = chunks.shift();
648
- chunksBytes -= removed.length;
649
- }
659
+ appendRollingChunk(data);
650
660
  if (onUpdate) {
651
661
  const fullBuffer = Buffer.concat(chunks);
652
662
  const fullText = fullBuffer.toString("utf-8");
@@ -700,12 +710,7 @@ function createBashTool(defaultCwd, options) {
700
710
  return;
701
711
  }
702
712
  }
703
- chunks.push(data);
704
- chunksBytes += data.length;
705
- while (chunksBytes > maxChunksBytes && chunks.length > 1) {
706
- const removed = chunks.shift();
707
- chunksBytes -= removed.length;
708
- }
713
+ appendRollingChunk(data);
709
714
  });
710
715
  const finishExecution = async (code, sig, stdioIncomplete = false) => {
711
716
  if (settled) return;
@@ -1171,7 +1176,12 @@ function createCliRenderedPageReader() {
1171
1176
  networkRequests: linesOf(stripHelpHints(networkOutput))
1172
1177
  };
1173
1178
  } finally {
1174
- 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
+ }
1175
1185
  }
1176
1186
  };
1177
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