@phnx-labs/agents-cli 1.22.103 → 1.22.104

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 (66) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +1 -1
  3. package/dist/commands/accounts.js +2 -2
  4. package/dist/commands/computer.d.ts +100 -79
  5. package/dist/commands/computer.js +290 -830
  6. package/dist/commands/setup-computer.js +20 -1
  7. package/dist/commands/setup-secrets.d.ts +2 -2
  8. package/dist/commands/setup-secrets.js +1 -1
  9. package/dist/commands/view.js +4 -6
  10. package/dist/lib/account-catalog.d.ts +22 -1
  11. package/dist/lib/account-catalog.js +72 -38
  12. package/dist/lib/accounting/usage.js +18 -14
  13. package/dist/lib/agent-spec/agents.d.ts +1 -0
  14. package/dist/lib/agent-spec/agents.js +1 -1
  15. package/dist/lib/browser/drivers/ssh.js +1 -1
  16. package/dist/lib/computer/context.d.ts +83 -0
  17. package/dist/lib/computer/context.js +91 -0
  18. package/dist/lib/computer/policy.d.ts +46 -0
  19. package/dist/lib/computer/policy.js +160 -0
  20. package/dist/lib/computer/record.d.ts +38 -0
  21. package/dist/lib/computer/record.js +86 -0
  22. package/dist/lib/computer/sessions-list.js +6 -6
  23. package/dist/lib/computer-client.d.ts +150 -0
  24. package/dist/lib/computer-client.js +222 -0
  25. package/dist/lib/exec.js +35 -1
  26. package/dist/lib/harness/adapters/claude.d.ts +37 -0
  27. package/dist/lib/harness/adapters/claude.js +69 -0
  28. package/dist/lib/helper-download.d.ts +1 -1
  29. package/dist/lib/helper-download.js +1 -1
  30. package/dist/lib/helper-versions.d.ts +9 -4
  31. package/dist/lib/helper-versions.js +8 -8
  32. package/dist/lib/installations/shims.js +8 -4
  33. package/dist/lib/menubar/download-menubar.d.ts +2 -1
  34. package/dist/lib/menubar/download-menubar.js +2 -1
  35. package/dist/lib/secrets-client.d.ts +3 -3
  36. package/dist/lib/secrets-client.js +15 -38
  37. package/dist/lib/session/db.js +1 -1
  38. package/dist/lib/sha256-asset.d.ts +2 -1
  39. package/dist/lib/sha256-asset.js +2 -1
  40. package/dist/lib/ssh-tunnel.d.ts +61 -0
  41. package/dist/lib/ssh-tunnel.js +105 -0
  42. package/dist/lib/summarizer/summarize.d.ts +2 -2
  43. package/dist/lib/summarizer/summarize.js +10 -3
  44. package/package.json +2 -3
  45. package/dist/commands/computer-actions.d.ts +0 -55
  46. package/dist/commands/computer-actions.js +0 -594
  47. package/dist/computer.d.ts +0 -2
  48. package/dist/computer.js +0 -7
  49. package/dist/lib/computer/actions.d.ts +0 -36
  50. package/dist/lib/computer/actions.js +0 -162
  51. package/dist/lib/computer/computer-rpc.d.ts +0 -39
  52. package/dist/lib/computer/computer-rpc.js +0 -447
  53. package/dist/lib/computer/des.d.ts +0 -1
  54. package/dist/lib/computer/des.js +0 -114
  55. package/dist/lib/computer/dispatch.d.ts +0 -10
  56. package/dist/lib/computer/dispatch.js +0 -133
  57. package/dist/lib/computer/download.d.ts +0 -54
  58. package/dist/lib/computer/download.js +0 -83
  59. package/dist/lib/computer/loop.d.ts +0 -62
  60. package/dist/lib/computer/loop.js +0 -98
  61. package/dist/lib/computer/model.d.ts +0 -44
  62. package/dist/lib/computer/model.js +0 -157
  63. package/dist/lib/computer/rfb-client.d.ts +0 -53
  64. package/dist/lib/computer/rfb-client.js +0 -562
  65. package/dist/lib/computer/ssh-tunnel.d.ts +0 -189
  66. package/dist/lib/computer/ssh-tunnel.js +0 -584
@@ -0,0 +1,160 @@
1
+ /** Resolve Agents permission groups and caller identities for the standalone
2
+ * engine. The engine alone writes helper policy and peer files. */
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import { getUserPermissionsDir, getPermissionsDir } from '../state.js';
6
+ // Walk all permission group YAMLs (user dir wins on name collision) and
7
+ // collect Computer(<bundle-id>) patterns from each group's `allow:` list.
8
+ // Returns distinct bundle ids. Line-by-line regex extraction matches
9
+ // buildPermissionsFromGroups: YAML parsers stumble on the nested quotes in
10
+ // some rule values, but the strict pattern below catches our shape cleanly.
11
+ export function loadComputerAllowList() {
12
+ const seenFiles = new Set();
13
+ const allowed = new Set();
14
+ for (const baseDir of [getUserPermissionsDir(), getPermissionsDir()]) {
15
+ const groupsDir = path.join(baseDir, 'groups');
16
+ if (!fs.existsSync(groupsDir))
17
+ continue;
18
+ let entries;
19
+ try {
20
+ entries = fs.readdirSync(groupsDir, { withFileTypes: true });
21
+ }
22
+ catch {
23
+ continue;
24
+ }
25
+ for (const entry of entries) {
26
+ if (!entry.isFile())
27
+ continue;
28
+ if (!entry.name.endsWith('.yml') && !entry.name.endsWith('.yaml'))
29
+ continue;
30
+ // User dir wins on filename collision.
31
+ const stem = entry.name.replace(/\.(yaml|yml)$/, '');
32
+ if (seenFiles.has(stem))
33
+ continue;
34
+ seenFiles.add(stem);
35
+ const filePath = path.join(groupsDir, entry.name);
36
+ let content;
37
+ try {
38
+ content = fs.readFileSync(filePath, 'utf-8');
39
+ }
40
+ catch {
41
+ continue;
42
+ }
43
+ // Strict regex: optional whitespace, dash, quoted Computer(<id>).
44
+ // Only honors `allow:` lines — `deny:` Computer patterns would be a
45
+ // contradiction (everything is deny-by-default already).
46
+ let inAllow = false;
47
+ for (const rawLine of content.split('\n')) {
48
+ const line = rawLine.replace(/\r$/, '');
49
+ const sectionMatch = line.match(/^(\w+)\s*:\s*$/);
50
+ if (sectionMatch) {
51
+ inAllow = sectionMatch[1] === 'allow';
52
+ continue;
53
+ }
54
+ if (!inAllow)
55
+ continue;
56
+ const ruleMatch = line.match(/^\s*-\s*"Computer\(([^)]+)\)"\s*$/);
57
+ if (ruleMatch) {
58
+ const bundleId = ruleMatch[1].trim();
59
+ if (bundleId.length > 0)
60
+ allowed.add(bundleId);
61
+ }
62
+ }
63
+ }
64
+ }
65
+ return [...allowed].sort();
66
+ }
67
+ /**
68
+ * Default peer set: the standalone `computer` executable, this `agents` CLI's
69
+ * own runtime, plus Rush.app if it's installed. realpath() the symlink chain so
70
+ * we record the on-disk path the helper will see via proc_pidpath, not the shim
71
+ * path.
72
+ *
73
+ * The standalone's path is the one that changed with PHNX-4075: the daemon's
74
+ * caller is now the engine process, not this CLI. `agents`' own execPath stays
75
+ * on the list because the engine may be a `.js` bin run through this same
76
+ * runtime (`invocation()` in computer-client.ts), in which case proc_pidpath
77
+ * still reports the runtime.
78
+ *
79
+ * Why path-based instead of codesign-team-id? The agents CLI is unsigned
80
+ * today (npm distribution), and even if we sign Rush.app the team-id
81
+ * check would need a separate roundtrip. Path is concrete and fast; the
82
+ * daemon already runs as the user so anyone who can swap a binary at
83
+ * these paths can do worse via other means.
84
+ */
85
+ export function loadDefaultPeers(opts = {}) {
86
+ const out = new Set();
87
+ const add = (p) => {
88
+ try {
89
+ out.add(fs.realpathSync(p));
90
+ }
91
+ catch {
92
+ out.add(p);
93
+ }
94
+ };
95
+ // The standalone engine — the process that actually opens the socket now.
96
+ if (opts.computerBin)
97
+ add(opts.computerBin);
98
+ // The runtime currently running this CLI. Still a possible proc_pidpath when
99
+ // the engine is a .js bin executed through it.
100
+ if (process.execPath)
101
+ add(process.execPath);
102
+ // Rush.app — the consumer Electron client. Both the helper-binary and
103
+ // the main app binary are possible callers depending on how Rush wires
104
+ // the RPC client.
105
+ const rushCandidates = [
106
+ '/Applications/Rush.app/Contents/MacOS/Rush',
107
+ '/Applications/Rush.app/Contents/MacOS/Electron',
108
+ ];
109
+ for (const p of rushCandidates) {
110
+ if (fs.existsSync(p))
111
+ add(p);
112
+ }
113
+ return [...out].sort();
114
+ }
115
+ /**
116
+ * Parse a `host:port` VNC endpoint, defaulting the port to 5901. Pure.
117
+ *
118
+ * Kept on the consumer side because the `--vnc` FLAG is parsed here — the
119
+ * platform gate has to know whether a remote desktop was named before the
120
+ * engine is ever spawned (see `shouldBlockOffPlatform`). The RFB protocol
121
+ * implementation itself went to the engine.
122
+ */
123
+ export function parseVncEndpoint(raw) {
124
+ if (!raw || raw.length === 0)
125
+ return null;
126
+ const idx = raw.lastIndexOf(':');
127
+ const host = idx >= 0 ? raw.slice(0, idx) : raw;
128
+ const portStr = idx >= 0 ? raw.slice(idx + 1) : '5901';
129
+ const port = Number(portStr);
130
+ if (!Number.isInteger(port) || port <= 0 || port > 65535)
131
+ return null;
132
+ return { host: host || '127.0.0.1', port };
133
+ }
134
+ // Resolve the TCP endpoint for a remote daemon (the Windows helper), if
135
+ // configured. That helper binds loopback TCP and is reached over an `ssh -L`
136
+ // tunnel, so the endpoint is a local forwarded port. COMPUTER_HELPER_TCP is
137
+ // "host:port" (host defaults to 127.0.0.1); COMPUTER_HELPER_TOKEN is the shared
138
+ // secret sent in the first `auth` frame.
139
+ export function resolveTcpEndpoint() {
140
+ const raw = process.env.COMPUTER_HELPER_TCP;
141
+ if (!raw || raw.length === 0)
142
+ return null;
143
+ const [hostPart, portPart] = raw.includes(':') ? raw.split(':') : ['127.0.0.1', raw];
144
+ const port = Number(portPart);
145
+ if (!Number.isInteger(port) || port <= 0)
146
+ return null;
147
+ const token = process.env.COMPUTER_HELPER_TOKEN;
148
+ return { host: hostPart || '127.0.0.1', port, token: token && token.length > 0 ? token : null };
149
+ }
150
+ // Resolve the VNC/RFB endpoint for driving a remote GUI desktop over the RFB
151
+ // protocol (an x11vnc/Xvnc server — e.g. a headless Linux desktop or an LXD
152
+ // container exposing x11vnc on the host's Tailscale IP). COMPUTER_HELPER_VNC is
153
+ // "host:port" (port defaults to 5901); COMPUTER_HELPER_VNC_PASSWORD is the VNC
154
+ // password.
155
+ export function resolveVncEndpoint() {
156
+ const parsed = parseVncEndpoint(process.env.COMPUTER_HELPER_VNC);
157
+ if (!parsed)
158
+ return null;
159
+ return { ...parsed, password: process.env.COMPUTER_HELPER_VNC_PASSWORD ?? '' };
160
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * record.ts — turn the engine's NDJSON action events into agents-cli's own
3
+ * records: a feed event and a row in the computer-session history that
4
+ * `agents computer sessions` and `agents sessions --computer` read.
5
+ *
6
+ * WHY THIS STAYS HERE. The feed, the actor registry, and `sessions.db` are
7
+ * agents-cli state. Handing the standalone engine a writer for all three would
8
+ * have made it a second author of the session index — precisely the
9
+ * "one engine, one executor" rule the repo holds elsewhere. Instead the engine
10
+ * reports what it did on fd 4 and agents-cli, which owns those stores, records it.
11
+ *
12
+ * Before PHNX-4075 this was `emitComputerAction`, called inline by each verb in
13
+ * the same process. The behavior is unchanged; only the trigger moved from a
14
+ * function call to a line on a pipe.
15
+ *
16
+ * WHAT THE ENGINE OWNS, AND IS NOT REWRITTEN HERE: the action's identity. The
17
+ * engine mints the `invocationId` that groups a whole run into one session row,
18
+ * names the `host` it drove, and echoes back the session/launch/actor it was
19
+ * handed. Re-deriving any of those from this process would describe the CLI that
20
+ * spawned the engine rather than the run that happened — and for `--device` the
21
+ * two genuinely differ.
22
+ */
23
+ import type { ComputerActionEvent } from '../computer-client.js';
24
+ /**
25
+ * Fallback grouping id for an engine that reported no `invocationId` of its own.
26
+ * One per `agents computer` process, so such a run still collapses to a single
27
+ * session row instead of N unrelated ones.
28
+ */
29
+ export declare const COMPUTER_INVOCATION_ID: `${string}-${string}-${string}-${string}-${string}`;
30
+ /**
31
+ * Record one action the engine performed. Never throws: the action already
32
+ * happened and already reported its own success or failure on the engine's
33
+ * stderr, so a bookkeeping failure must not turn a successful click into a
34
+ * failed command.
35
+ */
36
+ export declare function recordComputerAction(event: ComputerActionEvent, opts?: {
37
+ device?: string;
38
+ }): void;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * record.ts — turn the engine's NDJSON action events into agents-cli's own
3
+ * records: a feed event and a row in the computer-session history that
4
+ * `agents computer sessions` and `agents sessions --computer` read.
5
+ *
6
+ * WHY THIS STAYS HERE. The feed, the actor registry, and `sessions.db` are
7
+ * agents-cli state. Handing the standalone engine a writer for all three would
8
+ * have made it a second author of the session index — precisely the
9
+ * "one engine, one executor" rule the repo holds elsewhere. Instead the engine
10
+ * reports what it did on fd 4 and agents-cli, which owns those stores, records it.
11
+ *
12
+ * Before PHNX-4075 this was `emitComputerAction`, called inline by each verb in
13
+ * the same process. The behavior is unchanged; only the trigger moved from a
14
+ * function call to a line on a pipe.
15
+ *
16
+ * WHAT THE ENGINE OWNS, AND IS NOT REWRITTEN HERE: the action's identity. The
17
+ * engine mints the `invocationId` that groups a whole run into one session row,
18
+ * names the `host` it drove, and echoes back the session/launch/actor it was
19
+ * handed. Re-deriving any of those from this process would describe the CLI that
20
+ * spawned the engine rather than the run that happened — and for `--device` the
21
+ * two genuinely differ.
22
+ */
23
+ import { randomUUID } from 'node:crypto';
24
+ import { emit as emitEvent } from '../feed/events.js';
25
+ import { recordComputerSession } from '../session/db.js';
26
+ import { resolveActor } from '../actor.js';
27
+ import { truncate } from '../feed/events.js';
28
+ import { TASK_PREVIEW_MAX_CHARS } from './sessions-list.js';
29
+ /**
30
+ * Fallback grouping id for an engine that reported no `invocationId` of its own.
31
+ * One per `agents computer` process, so such a run still collapses to a single
32
+ * session row instead of N unrelated ones.
33
+ */
34
+ export const COMPUTER_INVOCATION_ID = randomUUID();
35
+ /**
36
+ * Record one action the engine performed. Never throws: the action already
37
+ * happened and already reported its own success or failure on the engine's
38
+ * stderr, so a bookkeeping failure must not turn a successful click into a
39
+ * failed command.
40
+ */
41
+ export function recordComputerAction(event, opts = {}) {
42
+ const { event: _kind, command, invocationId,
43
+ // The ledger's `pid` is the EMITTING process's by construction (events.ts
44
+ // stamps `process.pid` over any payload value), so the engine's own pid
45
+ // cannot be carried in it. Dropped rather than passed in to be silently
46
+ // overwritten.
47
+ pid: _enginePid, host, sessionId, launchId, actor, ...rest } = event;
48
+ const runId = invocationId || COMPUTER_INVOCATION_ID;
49
+ // The driven machine. `host` is the field `sessions-list.ts` reads for a
50
+ // remote run; `opts.device` is the fallback for an engine that drove the
51
+ // device this CLI resolved but did not stamp it.
52
+ const drivenHost = host ?? opts.device;
53
+ // The task preview is bounded HERE, not upstream. agents-cli owns the ledger
54
+ // and therefore its retention/privacy rule (see sessions-list.ts): an engine
55
+ // that reported a full `--task` string must not be able to write an unbounded
56
+ // one into the session index.
57
+ const extra = typeof rest.task === 'string'
58
+ ? { ...rest, task: truncate(rest.task, TASK_PREVIEW_MAX_CHARS) }
59
+ : rest;
60
+ try {
61
+ emitEvent('computer.action', {
62
+ command,
63
+ invocationId: runId,
64
+ ...(drivenHost ? { host: drivenHost } : {}),
65
+ ...(sessionId ? { sessionId } : {}),
66
+ ...(launchId ? { launchId } : {}),
67
+ ...extra,
68
+ });
69
+ }
70
+ catch {
71
+ // Feed emission is best-effort; the action is already done.
72
+ }
73
+ try {
74
+ recordComputerSession({
75
+ invocationId: runId,
76
+ sessionId: sessionId ?? process.env.AGENT_SESSION_ID ?? process.env.AGENTS_SESSION_ID,
77
+ launchId: launchId ?? process.env.AGENT_LAUNCH_ID,
78
+ actor: actor ?? resolveActor().id,
79
+ actionCount: 1,
80
+ taskPreview: typeof extra.task === 'string' ? extra.task : undefined,
81
+ });
82
+ }
83
+ catch {
84
+ // Recording is best-effort; the action and its event are already done.
85
+ }
86
+ }
@@ -2,9 +2,9 @@
2
2
  * Read-only task/run history over the `computer.action` event ledger
3
3
  * (`~/.agents/.history/events/YYYY-MM-DD/events.jsonl`, see `../events.ts`) —
4
4
  * the durable, already-existing audit log every `agents computer <verb>`
5
- * invocation (the explicit CLI verbs in `commands/computer-actions.ts`, and
6
- * the embedded `computer run` loop in `computer/dispatch.ts`) writes through
7
- * `emitComputerAction()`. Backs both `agents computer sessions` and the
5
+ * invocation writes through `computer/record.ts`'s `recordComputerAction()`,
6
+ * fed by the action events the standalone engine streams back (PHNX-4075).
7
+ * Backs both `agents computer sessions` and the
8
8
  * `agents sessions --computer` alias.
9
9
  *
10
10
  * There is no separate capture directory the way browser tasks have
@@ -24,7 +24,7 @@
24
24
  * the ledger's 7 days, and one row per CLI process would otherwise grow without
25
25
  * limit. It is metadata only. Nothing sensitive is persisted: `type` /
26
26
  * `type-text` events already carry only `textLength`, never the typed text
27
- * (see `commands/computer-actions.ts` `emitComputerAction` call sites) — the
27
+ * (see `computer/record.ts` `recordComputerAction`) — the
28
28
  * mission this module fulfils changes NONE of that. A `run --task`
29
29
  * description is the agent's OWN instruction, not typed-into-a-target-app
30
30
  * content (the same class of thing `agents sessions` already stores
@@ -35,10 +35,10 @@
35
35
  * deliberate exception to (not a bypass of) the automatic prompt-redaction
36
36
  * path in `events.ts` `sanitizePayload`.
37
37
  *
38
- * Grouping key: `emitComputerAction()` stamps one random `invocationId` for
38
+ * Grouping key: `recordComputerAction()` stamps one random `invocationId` for
39
39
  * the lifetime of the emitting CLI process. The event's own `pid` field is the emitting
40
40
  * CLI PROCESS's pid, never the target app's (that's `targetPid` — see
41
- * `computer-actions.ts` `emitComputerAction`, and its `#11` test guarding
41
+ * `computer/record.ts` `recordComputerAction`, and its `#11` test guarding
42
42
  * this). One `agents computer <verb>` invocation is one process, and
43
43
  * `computer run`'s whole embedded observe/act/verify loop is ALSO one
44
44
  * process. Grouping by `invocationId` gives exactly one row per CLI invocation without
@@ -0,0 +1,150 @@
1
+ /**
2
+ * computer-client.ts — the ONE process client through which agents-cli talks to
3
+ * the standalone `computer` CLI (PHNX-4075).
4
+ *
5
+ * This is the agents-owned half of the computer extraction, and it is
6
+ * deliberately small. agents-cli no longer carries a helper daemon, an RPC
7
+ * transport, an element cache, an RFB client, or an autonomous loop — the
8
+ * standalone engine owns all of it, exactly as `secrets` took the keychain
9
+ * engine (PHNX-3989) and `sessions` took the transcript engine (PHNX-4012).
10
+ * What stays here is what only the fleet CLI can know: which apps the
11
+ * permissions layer allows, which device a `--device` name resolves to, who the
12
+ * acting session is, and where an action must be recorded.
13
+ *
14
+ * THERE IS NO FALLBACK. A missing executable throws `COMPUTER_BIN_MISSING` with
15
+ * install guidance (DIST-1) rather than silently driving a bundled engine —
16
+ * agents-cli has none to drive, and a fallback would re-couple the two release
17
+ * trains this extraction exists to separate.
18
+ *
19
+ * Transport — inherited-fd passthrough, not request/response:
20
+ *
21
+ * The engine's ENVIRONMENT is inherited verbatim — no overlay. Transport
22
+ * selection (`COMPUTER_HELPER_TCP`, `COMPUTER_HELPER_VNC`,
23
+ * `COMPUTER_HELPER_SOCKET`) is the engine's: it opens the `--device` tunnel and
24
+ * hydrates its own endpoint AND the auth token that goes with it. Publishing a
25
+ * bare endpoint from here would hand the daemon a connection it then rejects
26
+ * with `auth_failed`.
27
+ *
28
+ * - stdio 0/1/2 are INHERITED. The engine owns the user's terminal: its
29
+ * stdout is the command's stdout, its `--json` is the command's `--json`,
30
+ * its prompts reach a real tty. agents-cli never re-formats engine output,
31
+ * which is what keeps the surface honest as the engine evolves.
32
+ * - fd 3 (`COMPUTER_CONTEXT_FD`) carries ONE JSON object — the consumer
33
+ * context built by `lib/computer/context.ts` — written and closed
34
+ * immediately, so the engine reads to EOF and proceeds.
35
+ * - fd 4 (`COMPUTER_EVENTS_FD`) carries NDJSON action events back: one JSON
36
+ * object per line, each an action the engine actually performed. agents-cli
37
+ * turns those into feed events and `sessions --computer` history
38
+ * (`lib/computer/record.ts`). The engine may emit none; it must never block
39
+ * on this pipe.
40
+ *
41
+ * Both fds are anonymous pipes on the child's side, the same shape
42
+ * `secrets-client.ts` settled on after a named FIFO wedged macOS reads. The
43
+ * context is pushed rather than pulled so the engine needs no callback into
44
+ * agents-cli — one direction each way, no reentrancy.
45
+ */
46
+ /** fd the engine reads its one-shot JSON context from. */
47
+ export declare const COMPUTER_CONTEXT_FD = 3;
48
+ /** fd the engine writes NDJSON action events to. */
49
+ export declare const COMPUTER_EVENTS_FD = 4;
50
+ export declare class ComputerClientError extends Error {
51
+ code: string;
52
+ constructor(code: string, message: string);
53
+ }
54
+ export declare function isComputerClientError(err: unknown): err is ComputerClientError;
55
+ export declare function isStandaloneComputer(bin: string): boolean;
56
+ /**
57
+ * Resolve the standalone executable. `COMPUTER_BIN` wins so a dev build can be
58
+ * driven without touching PATH.
59
+ *
60
+ * Resolution uses `findInPath`, which skips `~/.agents/.cache/shims`. That skip
61
+ * is load-bearing here for the same reason it is in `sessions-client.ts`: a
62
+ * leftover `computer` alias shim execs `agents computer`, and resolving it would
63
+ * recurse into this process (the 1.22.85 secrets fork bomb, agi-cli#3532).
64
+ */
65
+ export declare function resolveComputerBin(): string;
66
+ /** A `.js` bin is run through this runtime; a real executable is exec'd directly. */
67
+ export declare function invocation(bin: string): {
68
+ command: string;
69
+ prefix: string[];
70
+ };
71
+ /**
72
+ * One action the engine performed, as it appears on the NDJSON events fd.
73
+ *
74
+ * This is the engine's wire shape, not a translation of it: the engine emits
75
+ * `{event: "computer.action", command, invocationId, pid, targetPid, bundle,
76
+ * host, task, sessionId, launchId, actor}`. `command` — not `verb` — is the
77
+ * field that names the action, and it is what marks a line as an action event.
78
+ */
79
+ export interface ComputerActionEvent {
80
+ /** Always `computer.action` on this stream. */
81
+ event?: string;
82
+ /** The verb the engine ran (`click`, `type`, `screenshot`, …). */
83
+ command: string;
84
+ /** The engine's own id for this run — the grouping key for a session row. */
85
+ invocationId?: string;
86
+ /** The engine process's pid. */
87
+ pid?: number;
88
+ /** pid of the app the action targeted, when the engine resolved one. */
89
+ targetPid?: number;
90
+ /** Bundle id / app identifier the action targeted. */
91
+ bundle?: string;
92
+ /** The driven device for a `--device` invocation; absent when local. */
93
+ host?: string;
94
+ /** `run --task` description, only on the task marker. */
95
+ task?: string;
96
+ /** Identity, echoed back from the context this CLI handed the engine. */
97
+ sessionId?: string;
98
+ launchId?: string;
99
+ actor?: string;
100
+ /** Free-form detail the engine attaches (coordinates, text length, …). */
101
+ [key: string]: unknown;
102
+ }
103
+ /**
104
+ * Split a growing buffer into complete NDJSON lines. Pure so the framing rules —
105
+ * blank lines skipped, a non-JSON line dropped rather than crashing the CLI, a
106
+ * trailing partial line carried forward — are unit-testable without a spawn.
107
+ *
108
+ * A malformed line is dropped, not thrown: these events are telemetry riding
109
+ * alongside a user-visible action that already happened. Failing the command
110
+ * because its receipt was unreadable would be strictly worse than losing the
111
+ * receipt. The action itself already failed loud on its own channel if it failed.
112
+ */
113
+ export declare function parseEventLines(buffer: string): {
114
+ events: ComputerActionEvent[];
115
+ rest: string;
116
+ };
117
+ export interface RunComputerOptions {
118
+ /** argv handed to the standalone, after the program name. */
119
+ argv: string[];
120
+ /** The consumer context serialized onto fd 3. */
121
+ context: unknown;
122
+ /** Called once per action event the engine reports on fd 4. */
123
+ onEvent?: (event: ComputerActionEvent) => void;
124
+ /**
125
+ * Capture the engine's stdout instead of inheriting the terminal.
126
+ *
127
+ * Used only where agents-cli must READ an answer rather than show it — the
128
+ * `agents setup computer` wizard polling `status --json` for trust. Verbs
129
+ * never capture: re-printing engine output would make agents-cli a formatter
130
+ * for a surface it no longer owns.
131
+ */
132
+ capture?: boolean;
133
+ }
134
+ export interface RunComputerResult {
135
+ exitCode: number;
136
+ /** Engine stdout, only when `capture` was set. */
137
+ stdout: string;
138
+ }
139
+ /**
140
+ * Run the standalone engine with the consumer context on fd 3 and the action
141
+ * event stream on fd 4. Resolves with the engine's exit code; the caller
142
+ * propagates it so `agents computer` exits exactly as the engine did.
143
+ *
144
+ * Throws `COMPUTER_BIN_MISSING` when the standalone is not installed. Every
145
+ * other failure is the engine's own, reported on the inherited stderr.
146
+ */
147
+ export declare function runComputer(opts: RunComputerOptions): Promise<RunComputerResult>;
148
+ /** Test seam: drop the memoized bin so PATH fixtures can re-resolve. */
149
+ export declare function _resetComputerClientForTest(): void;
150
+ export declare const COMPUTER_INSTALL_HINT = "npm i -g @phnx-labs/computer-cli";