@cydm/pie 2.5.1 → 2.6.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 (26) 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 +135 -27
  8. package/dist/builtin/extensions/plan-mode/index.js +3 -3
  9. package/dist/builtin/extensions/subagent/index.js +8 -5
  10. package/dist/builtin/extensions/todo/index.js +3 -3
  11. package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-bridge.js +199 -40
  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-GB37NZHQ.js} +78 -29
  15. package/dist/builtin/skills/tui-use/SKILL.md +44 -0
  16. package/dist/builtin/skills/tui-use/tui-use-cli.cjs +40 -16
  17. package/dist/builtin/skills/tui-use/tui-use-daemon.cjs +7 -1
  18. package/dist/chunks/{chunk-REP6QANY.js → chunk-AZ336HKF.js} +405 -70
  19. package/dist/chunks/{chunk-N2MK46AE.js → chunk-G6KCJA6B.js} +7 -2
  20. package/dist/chunks/{chunk-YRXXRLMQ.js → chunk-O2DKMLNX.js} +152 -8
  21. package/dist/chunks/{chunk-ZKHMNEXL.js → chunk-Y4CYXJR7.js} +27 -2
  22. package/dist/chunks/src-VCWCE7YV.js +20 -0
  23. package/dist/chunks/{test-stream-OD3B57AS.js → test-stream-GOSEWMJ3.js} +1 -1
  24. package/dist/cli.js +67 -17
  25. package/package.json +2 -2
  26. package/dist/chunks/src-SXUQ7OY6.js +0 -14
@@ -1845,8 +1845,8 @@ function isProcessAlive(pid) {
1845
1845
  try {
1846
1846
  process.kill(pid, 0);
1847
1847
  return true;
1848
- } catch {
1849
- return false;
1848
+ } catch (error) {
1849
+ return error?.code !== "ESRCH";
1850
1850
  }
1851
1851
  }
1852
1852
  function httpGet(port, path, timeoutMs = 2e3) {
@@ -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");
@@ -50,10 +50,54 @@ tui-use daemon stop
50
50
  - Send an explicit `press enter` after typing when reliable submission matters.
51
51
  - `wait --text` exits non-zero if the requested pattern is absent at the deadline.
52
52
  Read the returned screen and fix the input instead of assuming the wait succeeded.
53
+ Do not rerun the identical failing wait; poll with `--poll` (below) or change the input.
53
54
  - Use `wait --exit` to confirm process termination. Seeing the last output or
54
55
  leaving fullscreen does not by itself mean the process has exited.
55
56
  - Kill only sessions you created. Do not restart or stop a daemon that owns another task's live sessions.
56
57
 
58
+ ## Bounded Polling For Long Tasks
59
+
60
+ For work that legitimately takes longer than one wait, opt in to poll mode:
61
+
62
+ ```text
63
+ tui-use --session <id> wait 20000 --text "DONE_MARKER" --poll --format json
64
+ ```
65
+
66
+ - `wait_state: "satisfied"` (exit 0): the marker appeared, or `--exit` saw the exit.
67
+ With `--exit` this means termination was observed, not that the program
68
+ succeeded: read `exit_code`, which can be nonzero.
69
+ - `wait_state: "pending"` (exit 0): the deadline passed while the process is still
70
+ running. This only means the PTY is alive; it is not evidence of progress.
71
+ - `wait_state: "failed"` (exit 1): the session exited without the `--text` marker.
72
+ Daemon, session and argument errors also exit 1. Never treat these as pending.
73
+
74
+ Set a total budget before polling (for example at most 6 polls or 10 minutes).
75
+ After each pending poll compare the `screen` with the previous poll. If the
76
+ screen is unchanged across two or three polls, or the budget is spent, stop and
77
+ report the task as not progressing, with the last screen, instead of polling on.
78
+ For programs that can emit structured output, prefer that over viewport text.
79
+
80
+ ## Waiting On Pie Tasks
81
+
82
+ To run or supervise a Pie task, use the JSONL protocol over pipes instead of
83
+ driving the Pie TUI through this skill and reading its viewport:
84
+
85
+ ```text
86
+ pie chat --json-events # stdin: one JSON request per line; stdout: one JSON event per line
87
+ ```
88
+
89
+ - Wait for `agent.ready`, then send
90
+ `{"type":"turn.request","turnId":"t1","prompt":"..."}`.
91
+ - The turn outcome is exactly one of `turn.completed`, `turn.failed`,
92
+ `turn.cancelled` or `turn.stalled`, followed by `turn.settled`. Decide from
93
+ these events, not from screen text or the process still running.
94
+ - `tool.started`/`tool.completed` and `turn.retry.*` events are progress
95
+ evidence. Pie emits `turn.stalled` after `PIE_JSON_EVENTS_NO_PROGRESS_MS`
96
+ (default 10 minutes) without activity.
97
+ - `turn.failed` after `turn.retry.exhausted`, or an exhausted-quota/balance
98
+ error, is terminal: report it instead of resubmitting the same prompt.
99
+ - Use `{"type":"turn.interrupt"}` to cancel and `{"type":"shutdown"}` to exit.
100
+
57
101
  ## Host Behavior
58
102
 
59
103
  Windows defaults to native PowerShell with matching invocation arguments;
@@ -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);
@@ -10345,8 +10361,8 @@ program2.command("snapshot").description("Return the current rendered screen sna
10345
10361
  handleResponse(res, (r) => printScreen(r, opts.format));
10346
10362
  });
10347
10363
  program2.command("wait [duration]").description(
10348
- 'Wait for screen to change or timeout.\n wait \u2192 wait for screen to change (default 3000ms)\n wait 5000 \u2192 wait for 5000ms\n wait --text "pattern" \u2192 wait until screen contains text\n wait --debounce 300 \u2192 wait until screen is stable for 300ms (default: 100ms)'
10349
- ).option("--text <text>", "Wait until screen contains literal text").option("--regex", "Interpret --text as a regex in a bounded worker").option("--exit", "Wait for process exit instead of just a screen change").option("--debounce <ms>", "Idle time after last change before resolving, in ms (default: 100)", "100").option("--format <fmt>", "Output format: pretty, json (default: pretty)", "pretty").option("--color", "Preserve ANSI color/style escape sequences in output lines").action(async (duration, opts) => {
10364
+ 'Wait for screen to change or timeout.\n wait \u2192 wait for screen to change (default 3000ms)\n wait 5000 \u2192 wait for 5000ms\n wait --text "pattern" \u2192 wait until screen contains text\n wait --debounce 300 \u2192 wait until screen is stable for 300ms (default: 100ms)\n wait --text "pattern" --poll \u2192 exit 0 with wait_state "pending" if the deadline passes while the session is still running'
10365
+ ).option("--text <text>", "Wait until screen contains literal text").option("--regex", "Interpret --text as a regex in a bounded worker").option("--exit", "Wait for process exit instead of just a screen change").option("--poll", 'Bounded polling: a deadline on a still-running session reports wait_state "pending" and exits 0; an exited session that never matched --text, and errors, exit 1. With --exit, "satisfied" means termination was observed, not success: check exit_code').option("--debounce <ms>", "Idle time after last change before resolving, in ms (default: 100)", "100").option("--format <fmt>", "Output format: pretty, json (default: pretty)", "pretty").option("--color", "Preserve ANSI color/style escape sequences in output lines").action(async (duration, opts) => {
10350
10366
  let timeoutMs = 3e3;
10351
10367
  if (duration) {
10352
10368
  timeoutMs = Number(duration);
@@ -10364,8 +10380,15 @@ program2.command("wait [duration]").description(
10364
10380
  });
10365
10381
  handleResponse(res, (r) => {
10366
10382
  const screen = r;
10367
- printScreen(screen, opts.format);
10368
- if ((opts.text !== void 0 || opts.exit) && screen.wait_satisfied === false) {
10383
+ const conditional = opts.text !== void 0 || opts.exit;
10384
+ const unmet = conditional && screen.wait_satisfied === false;
10385
+ const pending = !!opts.poll && screen.timed_out === true && screen.status === "running" && (unmet || !conditional);
10386
+ const waitState = opts.poll ? pending ? "pending" : unmet ? "failed" : "satisfied" : void 0;
10387
+ printScreen(screen, opts.format, waitState);
10388
+ if (pending) {
10389
+ process.stderr.write(`Wait pending: ${opts.text ?? (opts.exit ? "process exit" : "screen change")} not seen before timeout; session is still running. Liveness is not evidence of progress: compare screens across polls and stop after a bounded number of unchanged polls.
10390
+ `);
10391
+ } else if (unmet) {
10369
10392
  process.stderr.write(`Wait condition was not met${screen.timed_out ? " before timeout" : " before the session exited"}: ${opts.text ?? "process exit"}
10370
10393
  `);
10371
10394
  process.exitCode = 1;
@@ -10570,7 +10593,7 @@ function inputText(parts, fromStdin) {
10570
10593
  if (parts.length === 0) throw new Error("Provide text arguments or --stdin.");
10571
10594
  return parts.join(" ");
10572
10595
  }
10573
- function printScreen(r, format = "pretty") {
10596
+ function printScreen(r, format = "pretty", waitState) {
10574
10597
  if (format === "json") {
10575
10598
  console.log(
10576
10599
  JSON.stringify(
@@ -10586,7 +10609,8 @@ function printScreen(r, format = "pretty") {
10586
10609
  cols: r.cols,
10587
10610
  rows: r.rows,
10588
10611
  highlights: r.highlights,
10589
- ...r.type === "wait" ? { wait_satisfied: r.wait_satisfied, timed_out: r.timed_out } : {}
10612
+ ...r.type === "wait" ? { wait_satisfied: r.wait_satisfied, timed_out: r.timed_out } : {},
10613
+ ...waitState ? { wait_state: waitState } : {}
10590
10614
  },
10591
10615
  null,
10592
10616
  2
@@ -10596,7 +10620,7 @@ function printScreen(r, format = "pretty") {
10596
10620
  }
10597
10621
  const width = r.cols;
10598
10622
  const header = `\u2500\u2500\u2500 ${r.session_id} ${"\u2500".repeat(Math.max(0, width - r.session_id.length - 5))}`;
10599
- const info = `${r.status} | cursor(${r.cursor.x},${r.cursor.y}) | fullscreen:${r.is_fullscreen} | title:"${r.title || ""}"`;
10623
+ const info = `${r.status} | cursor(${r.cursor.x},${r.cursor.y}) | fullscreen:${r.is_fullscreen} | title:"${r.title || ""}"${waitState ? ` | wait:${waitState}` : ""}`;
10600
10624
  const footer = `\u2500\u2500\u2500 ${info} ${"\u2500".repeat(Math.max(0, width - info.length - 5))}`;
10601
10625
  process.stdout.write(header + "\n");
10602
10626
  for (const line of r.lines) process.stdout.write(line + "\n");
@@ -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");