@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.
- package/CHANGELOG.md +27 -0
- package/README.md +1 -1
- package/dist/commands/accounts.js +2 -2
- package/dist/commands/computer.d.ts +100 -79
- package/dist/commands/computer.js +290 -830
- package/dist/commands/setup-computer.js +20 -1
- package/dist/commands/setup-secrets.d.ts +2 -2
- package/dist/commands/setup-secrets.js +1 -1
- package/dist/commands/view.js +4 -6
- package/dist/lib/account-catalog.d.ts +22 -1
- package/dist/lib/account-catalog.js +72 -38
- package/dist/lib/accounting/usage.js +18 -14
- package/dist/lib/agent-spec/agents.d.ts +1 -0
- package/dist/lib/agent-spec/agents.js +1 -1
- package/dist/lib/browser/drivers/ssh.js +1 -1
- package/dist/lib/computer/context.d.ts +83 -0
- package/dist/lib/computer/context.js +91 -0
- package/dist/lib/computer/policy.d.ts +46 -0
- package/dist/lib/computer/policy.js +160 -0
- package/dist/lib/computer/record.d.ts +38 -0
- package/dist/lib/computer/record.js +86 -0
- package/dist/lib/computer/sessions-list.js +6 -6
- package/dist/lib/computer-client.d.ts +150 -0
- package/dist/lib/computer-client.js +222 -0
- package/dist/lib/exec.js +35 -1
- package/dist/lib/harness/adapters/claude.d.ts +37 -0
- package/dist/lib/harness/adapters/claude.js +69 -0
- package/dist/lib/helper-download.d.ts +1 -1
- package/dist/lib/helper-download.js +1 -1
- package/dist/lib/helper-versions.d.ts +9 -4
- package/dist/lib/helper-versions.js +8 -8
- package/dist/lib/installations/shims.js +8 -4
- package/dist/lib/menubar/download-menubar.d.ts +2 -1
- package/dist/lib/menubar/download-menubar.js +2 -1
- package/dist/lib/secrets-client.d.ts +3 -3
- package/dist/lib/secrets-client.js +15 -38
- package/dist/lib/session/db.js +1 -1
- package/dist/lib/sha256-asset.d.ts +2 -1
- package/dist/lib/sha256-asset.js +2 -1
- package/dist/lib/ssh-tunnel.d.ts +61 -0
- package/dist/lib/ssh-tunnel.js +105 -0
- package/dist/lib/summarizer/summarize.d.ts +2 -2
- package/dist/lib/summarizer/summarize.js +10 -3
- package/package.json +2 -3
- package/dist/commands/computer-actions.d.ts +0 -55
- package/dist/commands/computer-actions.js +0 -594
- package/dist/computer.d.ts +0 -2
- package/dist/computer.js +0 -7
- package/dist/lib/computer/actions.d.ts +0 -36
- package/dist/lib/computer/actions.js +0 -162
- package/dist/lib/computer/computer-rpc.d.ts +0 -39
- package/dist/lib/computer/computer-rpc.js +0 -447
- package/dist/lib/computer/des.d.ts +0 -1
- package/dist/lib/computer/des.js +0 -114
- package/dist/lib/computer/dispatch.d.ts +0 -10
- package/dist/lib/computer/dispatch.js +0 -133
- package/dist/lib/computer/download.d.ts +0 -54
- package/dist/lib/computer/download.js +0 -83
- package/dist/lib/computer/loop.d.ts +0 -62
- package/dist/lib/computer/loop.js +0 -98
- package/dist/lib/computer/model.d.ts +0 -44
- package/dist/lib/computer/model.js +0 -157
- package/dist/lib/computer/rfb-client.d.ts +0 -53
- package/dist/lib/computer/rfb-client.js +0 -562
- package/dist/lib/computer/ssh-tunnel.d.ts +0 -189
- package/dist/lib/computer/ssh-tunnel.js +0 -584
package/dist/lib/computer/des.js
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
// Minimal, dependency-free single-block DES (ECB) — used only for VNC
|
|
2
|
-
// authentication's challenge/response. Node 24 ships OpenSSL 3, whose DES lives
|
|
3
|
-
// in the legacy provider that is off by default, so `createCipheriv('des-ecb')`
|
|
4
|
-
// throws `digital envelope routines::unsupported`. VNC clients have always
|
|
5
|
-
// bundled their own DES for exactly this reason. This is a textbook FIPS-46 DES
|
|
6
|
-
// implementation over bit arrays — verified against the standard test vector in
|
|
7
|
-
// des.test.ts. It encrypts one 8-byte block; VNC auth encrypts the two halves
|
|
8
|
-
// of the 16-byte challenge independently (see rfb-client.ts).
|
|
9
|
-
const IP = [
|
|
10
|
-
58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4,
|
|
11
|
-
62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8,
|
|
12
|
-
57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3,
|
|
13
|
-
61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7,
|
|
14
|
-
];
|
|
15
|
-
const FP = [
|
|
16
|
-
40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31,
|
|
17
|
-
38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29,
|
|
18
|
-
36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27,
|
|
19
|
-
34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25,
|
|
20
|
-
];
|
|
21
|
-
const E = [
|
|
22
|
-
32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17,
|
|
23
|
-
16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1,
|
|
24
|
-
];
|
|
25
|
-
const P = [
|
|
26
|
-
16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10,
|
|
27
|
-
2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25,
|
|
28
|
-
];
|
|
29
|
-
const PC1 = [
|
|
30
|
-
57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36,
|
|
31
|
-
63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4,
|
|
32
|
-
];
|
|
33
|
-
const PC2 = [
|
|
34
|
-
14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2,
|
|
35
|
-
41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32,
|
|
36
|
-
];
|
|
37
|
-
const SHIFTS = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1];
|
|
38
|
-
const SBOX = [
|
|
39
|
-
[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
|
|
40
|
-
[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
|
|
41
|
-
[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
|
|
42
|
-
[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
|
|
43
|
-
[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
|
|
44
|
-
[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
|
|
45
|
-
[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
|
|
46
|
-
[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],
|
|
47
|
-
];
|
|
48
|
-
function bytesToBits(buf) {
|
|
49
|
-
const bits = [];
|
|
50
|
-
for (let i = 0; i < buf.length; i++) {
|
|
51
|
-
for (let b = 7; b >= 0; b--)
|
|
52
|
-
bits.push((buf[i] >> b) & 1);
|
|
53
|
-
}
|
|
54
|
-
return bits;
|
|
55
|
-
}
|
|
56
|
-
function bitsToBytes(bits) {
|
|
57
|
-
const out = Buffer.alloc(bits.length / 8);
|
|
58
|
-
for (let i = 0; i < out.length; i++) {
|
|
59
|
-
let byte = 0;
|
|
60
|
-
for (let b = 0; b < 8; b++)
|
|
61
|
-
byte = (byte << 1) | bits[i * 8 + b];
|
|
62
|
-
out[i] = byte;
|
|
63
|
-
}
|
|
64
|
-
return out;
|
|
65
|
-
}
|
|
66
|
-
function permute(bits, table) {
|
|
67
|
-
return table.map((pos) => bits[pos - 1]);
|
|
68
|
-
}
|
|
69
|
-
function keySchedule(key8) {
|
|
70
|
-
const keyBits = bytesToBits(key8);
|
|
71
|
-
const pc1 = permute(keyBits, PC1);
|
|
72
|
-
let c = pc1.slice(0, 28);
|
|
73
|
-
let d = pc1.slice(28, 56);
|
|
74
|
-
const subkeys = [];
|
|
75
|
-
for (let round = 0; round < 16; round++) {
|
|
76
|
-
const s = SHIFTS[round];
|
|
77
|
-
c = c.slice(s).concat(c.slice(0, s));
|
|
78
|
-
d = d.slice(s).concat(d.slice(0, s));
|
|
79
|
-
subkeys.push(permute(c.concat(d), PC2));
|
|
80
|
-
}
|
|
81
|
-
return subkeys;
|
|
82
|
-
}
|
|
83
|
-
function feistel(r, subkey) {
|
|
84
|
-
const expanded = permute(r, E);
|
|
85
|
-
const xored = expanded.map((bit, i) => bit ^ subkey[i]);
|
|
86
|
-
const out = [];
|
|
87
|
-
for (let box = 0; box < 8; box++) {
|
|
88
|
-
const chunk = xored.slice(box * 6, box * 6 + 6);
|
|
89
|
-
const row = (chunk[0] << 1) | chunk[5];
|
|
90
|
-
const col = (chunk[1] << 3) | (chunk[2] << 2) | (chunk[3] << 1) | chunk[4];
|
|
91
|
-
const val = SBOX[box][row * 16 + col];
|
|
92
|
-
for (let b = 3; b >= 0; b--)
|
|
93
|
-
out.push((val >> b) & 1);
|
|
94
|
-
}
|
|
95
|
-
return permute(out, P);
|
|
96
|
-
}
|
|
97
|
-
// Encrypt one 8-byte block with DES-ECB under an 8-byte key.
|
|
98
|
-
export function desEncryptBlock(key8, block8) {
|
|
99
|
-
if (key8.length !== 8)
|
|
100
|
-
throw new Error(`des key must be 8 bytes, got ${key8.length}`);
|
|
101
|
-
if (block8.length !== 8)
|
|
102
|
-
throw new Error(`des block must be 8 bytes, got ${block8.length}`);
|
|
103
|
-
const subkeys = keySchedule(key8);
|
|
104
|
-
const bits = permute(bytesToBits(block8), IP);
|
|
105
|
-
let l = bits.slice(0, 32);
|
|
106
|
-
let r = bits.slice(32, 64);
|
|
107
|
-
for (let round = 0; round < 16; round++) {
|
|
108
|
-
const f = feistel(r, subkeys[round]);
|
|
109
|
-
const next = l.map((bit, i) => bit ^ f[i]);
|
|
110
|
-
l = r;
|
|
111
|
-
r = next;
|
|
112
|
-
}
|
|
113
|
-
return bitsToBytes(permute(r.concat(l), FP));
|
|
114
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { ComputerClient } from './computer-rpc.js';
|
|
2
|
-
import { type AppInfo } from './actions.js';
|
|
3
|
-
import type { VerbDispatcher } from './loop.js';
|
|
4
|
-
declare function rpcMethodFor(verb: string): string;
|
|
5
|
-
declare function toRpcParams(verb: string, input: Record<string, unknown>, pid?: number): Record<string, unknown>;
|
|
6
|
-
export declare function makeVerbDispatcher(client: ComputerClient, context?: {
|
|
7
|
-
device?: string;
|
|
8
|
-
}): VerbDispatcher;
|
|
9
|
-
export { rpcMethodFor, toRpcParams };
|
|
10
|
-
export type { AppInfo };
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
// Verb dispatcher for the `computer run` loop.
|
|
2
|
-
//
|
|
3
|
-
// Maps a loop VerbCall (CLI-verb name + friendly input) onto the EXISTING
|
|
4
|
-
// computer-helper RPC methods over the daemon socket. It reimplements no verb —
|
|
5
|
-
// it names the same RPC methods the explicit `agents computer <verb>` commands
|
|
6
|
-
// call, so the external-agent verb surface is untouched. The daemon stays the
|
|
7
|
-
// single authority on permissions and targeting.
|
|
8
|
-
import { resolveTargetPidDecision, emitComputerAction } from './actions.js';
|
|
9
|
-
// CLI verb -> daemon RPC method. Only the rename cases need listing; the rest
|
|
10
|
-
// pass through by name.
|
|
11
|
-
const RPC_METHOD = {
|
|
12
|
-
apps: 'list_apps',
|
|
13
|
-
'get-text': 'get_text',
|
|
14
|
-
'type-text': 'type_text',
|
|
15
|
-
'ax-action': 'ax_action',
|
|
16
|
-
'right-click': 'right_click',
|
|
17
|
-
launch: 'launch_app',
|
|
18
|
-
focus: 'set_focus',
|
|
19
|
-
raise: 'focus_window',
|
|
20
|
-
};
|
|
21
|
-
// Verbs that do not need a resolved target pid.
|
|
22
|
-
const NO_TARGET = new Set(['apps', 'launch']);
|
|
23
|
-
function rpcMethodFor(verb) {
|
|
24
|
-
return RPC_METHOD[verb] ?? verb;
|
|
25
|
-
}
|
|
26
|
-
// Translate the model's friendly input keys into the daemon's param names.
|
|
27
|
-
// pid is injected by the caller after target resolution.
|
|
28
|
-
function toRpcParams(verb, input, pid) {
|
|
29
|
-
const p = {};
|
|
30
|
-
if (pid != null)
|
|
31
|
-
p.pid = pid;
|
|
32
|
-
const copyIf = (from, to) => {
|
|
33
|
-
if (input[from] != null)
|
|
34
|
-
p[to] = input[from];
|
|
35
|
-
};
|
|
36
|
-
switch (verb) {
|
|
37
|
-
case 'describe':
|
|
38
|
-
copyIf('depth', 'max_depth');
|
|
39
|
-
break;
|
|
40
|
-
case 'screenshot':
|
|
41
|
-
copyIf('quality', 'quality');
|
|
42
|
-
break;
|
|
43
|
-
case 'get-text':
|
|
44
|
-
copyIf('id', 'element_id');
|
|
45
|
-
copyIf('maxChars', 'max_chars');
|
|
46
|
-
break;
|
|
47
|
-
case 'click':
|
|
48
|
-
case 'right-click':
|
|
49
|
-
case 'scroll':
|
|
50
|
-
copyIf('id', 'element_id');
|
|
51
|
-
copyIf('x', 'x');
|
|
52
|
-
copyIf('y', 'y');
|
|
53
|
-
copyIf('count', 'count');
|
|
54
|
-
copyIf('dx', 'dx');
|
|
55
|
-
copyIf('dy', 'dy');
|
|
56
|
-
break;
|
|
57
|
-
case 'type':
|
|
58
|
-
copyIf('id', 'element_id');
|
|
59
|
-
copyIf('x', 'x');
|
|
60
|
-
copyIf('y', 'y');
|
|
61
|
-
copyIf('text', 'text');
|
|
62
|
-
copyIf('commit', 'commit');
|
|
63
|
-
break;
|
|
64
|
-
case 'type-text':
|
|
65
|
-
copyIf('text', 'text');
|
|
66
|
-
copyIf('commit', 'commit');
|
|
67
|
-
break;
|
|
68
|
-
case 'key':
|
|
69
|
-
copyIf('keys', 'keys');
|
|
70
|
-
break;
|
|
71
|
-
case 'ax-action':
|
|
72
|
-
copyIf('id', 'element_id');
|
|
73
|
-
copyIf('action', 'action');
|
|
74
|
-
break;
|
|
75
|
-
case 'focus':
|
|
76
|
-
copyIf('id', 'element_id');
|
|
77
|
-
break;
|
|
78
|
-
case 'launch':
|
|
79
|
-
copyIf('bundle', 'bundle_id');
|
|
80
|
-
copyIf('path', 'path');
|
|
81
|
-
copyIf('name', 'name');
|
|
82
|
-
break;
|
|
83
|
-
case 'wait': {
|
|
84
|
-
if (input.duration != null)
|
|
85
|
-
p.duration_ms = input.duration;
|
|
86
|
-
copyIf('id', 'element_id');
|
|
87
|
-
copyIf('until', 'until');
|
|
88
|
-
const locator = {};
|
|
89
|
-
for (const k of ['role', 'label', 'identifier']) {
|
|
90
|
-
if (input[k] != null)
|
|
91
|
-
locator[k] = input[k];
|
|
92
|
-
}
|
|
93
|
-
if (Object.keys(locator).length > 0)
|
|
94
|
-
p.locator = locator;
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
default:
|
|
98
|
-
break;
|
|
99
|
-
}
|
|
100
|
-
return p;
|
|
101
|
-
}
|
|
102
|
-
// Build a dispatcher bound to a live client. Every call is a single daemon
|
|
103
|
-
// round-trip; errors are returned (never thrown) so the loop can feed them
|
|
104
|
-
// back to the model as a tool_result.
|
|
105
|
-
//
|
|
106
|
-
// `computer run`'s embedded model loop drives every verb (click, type,
|
|
107
|
-
// screenshot, ...) through THIS single seam rather than the explicit
|
|
108
|
-
// `agents computer <verb>` commands in computer-actions.ts, so it must emit
|
|
109
|
-
// the same `computer.action` event those commands emit — otherwise a session
|
|
110
|
-
// that only used the loop reads back usedComputer=false even though it drove
|
|
111
|
-
// real actions (reviewer-flagged regression on #1864).
|
|
112
|
-
export function makeVerbDispatcher(client, context = {}) {
|
|
113
|
-
return async (call) => {
|
|
114
|
-
const verb = call.name;
|
|
115
|
-
const input = call.input ?? {};
|
|
116
|
-
let pid;
|
|
117
|
-
if (!NO_TARGET.has(verb) && !(verb === 'wait' && input.duration != null)) {
|
|
118
|
-
const resolved = await resolveTargetPidDecision(client, { pid: input.pid, bundle: input.bundle });
|
|
119
|
-
if (!resolved.ok)
|
|
120
|
-
return { ok: false, error: resolved.error };
|
|
121
|
-
pid = resolved.pid;
|
|
122
|
-
}
|
|
123
|
-
const method = rpcMethodFor(verb);
|
|
124
|
-
const params = toRpcParams(verb, input, pid);
|
|
125
|
-
const res = await client.call(method, params);
|
|
126
|
-
if (res.error)
|
|
127
|
-
return { ok: false, error: `${res.error.code}: ${res.error.message}` };
|
|
128
|
-
emitComputerAction(verb, pid, { bundle: input.bundle, device: context.device });
|
|
129
|
-
return { ok: true, result: res.result ?? {} };
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
// Exposed for tests/diagnostics.
|
|
133
|
-
export { rpcMethodFor, toRpcParams };
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* On-demand download + verification of the macOS `agents computer` helper
|
|
3
|
-
* ("ComputerHelper.app").
|
|
4
|
-
*
|
|
5
|
-
* The helper is a signed + notarized universal `.app` bundle published as a
|
|
6
|
-
* GitHub release asset on its own `computer-mac/v<x.y.z>` tag (see
|
|
7
|
-
* `helper-versions.ts`). The Windows helper (`lib/computer/ssh-tunnel.ts`) does
|
|
8
|
-
* NOT yet follow this model — it still keys its exe to the CLI's own tag.
|
|
9
|
-
*
|
|
10
|
-
* A fresh `npm i -g` machine has no local build, so `agents computer setup` / `agents setup computer` fetch the
|
|
11
|
-
* asset for the pinned helper version, verify its sha256 against the published
|
|
12
|
-
* `.sha256`, then verify the code signature (Developer ID Team + notarization)
|
|
13
|
-
* before it is ever copied to /Applications.
|
|
14
|
-
*
|
|
15
|
-
* The download + verify machinery is shared with the menu-bar helper in
|
|
16
|
-
* `../helper-download.ts`; this file is the ComputerHelper spec + its wrappers,
|
|
17
|
-
* keeping the historical export surface (`verifyMacHelper`, `downloadMacHelperApp`,
|
|
18
|
-
* `ensureMacHelperApp`, …) stable for existing callers. ComputerHelper does NOT
|
|
19
|
-
* pin a designated requirement (no `expectedBundleId`), matching its verification
|
|
20
|
-
* strength before the shared primitive existed.
|
|
21
|
-
*/
|
|
22
|
-
import { EXPECTED_TEAM_ID, HELPER_RELEASE_REPO, parseTeamId } from '../helper-download.js';
|
|
23
|
-
export { EXPECTED_TEAM_ID, HELPER_RELEASE_REPO, parseTeamId };
|
|
24
|
-
/** The zipped `.app` release asset name. */
|
|
25
|
-
export declare const MAC_HELPER_ASSET = "ComputerHelper.app.zip";
|
|
26
|
-
/** The bundle directory name once extracted. */
|
|
27
|
-
export declare const MAC_HELPER_APP_NAME = "ComputerHelper.app";
|
|
28
|
-
/** Cache dir for the downloaded helper, one subdir per release tag. */
|
|
29
|
-
export declare function macHelperCacheDir(version: string): string;
|
|
30
|
-
/** Release-asset URLs for the helper zip + its checksum at one `v<version>` tag. */
|
|
31
|
-
export declare function macHelperAssetUrls(version: string): {
|
|
32
|
-
zip: string;
|
|
33
|
-
sha256: string;
|
|
34
|
-
};
|
|
35
|
-
/**
|
|
36
|
-
* Verify a helper `.app` bundle is intact, signed by the expected Developer ID
|
|
37
|
-
* Team, and notarized (Gatekeeper-accepted). Throws with an actionable message
|
|
38
|
-
* on any failure — a downloaded bundle is never trusted without this.
|
|
39
|
-
*/
|
|
40
|
-
export declare function verifyMacHelper(appPath: string): void;
|
|
41
|
-
/**
|
|
42
|
-
* Download the helper release asset for `version`, verify sha256, extract the
|
|
43
|
-
* `.app`, and verify its signature. Returns the path to the extracted
|
|
44
|
-
* `ComputerHelper.app`. A missing asset is a hard error naming the exact tag —
|
|
45
|
-
* never a silent fallback to another release.
|
|
46
|
-
*/
|
|
47
|
-
export declare function downloadMacHelperApp(version: string): Promise<string>;
|
|
48
|
-
/**
|
|
49
|
-
* Resolve the helper `.app` to install from: a local build / bundled copy first
|
|
50
|
-
* (repo checkout), else the checksum + signature-verified release-asset download
|
|
51
|
-
* for the pinned helper version. Throws with the tag it checked when neither
|
|
52
|
-
* exists. macOS only.
|
|
53
|
-
*/
|
|
54
|
-
export declare function ensureMacHelperApp(version?: string): Promise<string>;
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* On-demand download + verification of the macOS `agents computer` helper
|
|
3
|
-
* ("ComputerHelper.app").
|
|
4
|
-
*
|
|
5
|
-
* The helper is a signed + notarized universal `.app` bundle published as a
|
|
6
|
-
* GitHub release asset on its own `computer-mac/v<x.y.z>` tag (see
|
|
7
|
-
* `helper-versions.ts`). The Windows helper (`lib/computer/ssh-tunnel.ts`) does
|
|
8
|
-
* NOT yet follow this model — it still keys its exe to the CLI's own tag.
|
|
9
|
-
*
|
|
10
|
-
* A fresh `npm i -g` machine has no local build, so `agents computer setup` / `agents setup computer` fetch the
|
|
11
|
-
* asset for the pinned helper version, verify its sha256 against the published
|
|
12
|
-
* `.sha256`, then verify the code signature (Developer ID Team + notarization)
|
|
13
|
-
* before it is ever copied to /Applications.
|
|
14
|
-
*
|
|
15
|
-
* The download + verify machinery is shared with the menu-bar helper in
|
|
16
|
-
* `../helper-download.ts`; this file is the ComputerHelper spec + its wrappers,
|
|
17
|
-
* keeping the historical export surface (`verifyMacHelper`, `downloadMacHelperApp`,
|
|
18
|
-
* `ensureMacHelperApp`, …) stable for existing callers. ComputerHelper does NOT
|
|
19
|
-
* pin a designated requirement (no `expectedBundleId`), matching its verification
|
|
20
|
-
* strength before the shared primitive existed.
|
|
21
|
-
*/
|
|
22
|
-
import * as os from 'node:os';
|
|
23
|
-
import { resolveHelperApp } from './computer-rpc.js';
|
|
24
|
-
import { EXPECTED_TEAM_ID, HELPER_RELEASE_REPO, downloadHelperApp, helperAssetUrls, helperCacheDir, parseTeamId, verifyHelperApp, } from '../helper-download.js';
|
|
25
|
-
import { helperFloor } from '../helper-versions.js';
|
|
26
|
-
// Re-exported so existing importers (and download.test.ts) keep resolving these
|
|
27
|
-
// names from './download.js'.
|
|
28
|
-
export { EXPECTED_TEAM_ID, HELPER_RELEASE_REPO, parseTeamId };
|
|
29
|
-
/** The zipped `.app` release asset name. */
|
|
30
|
-
export const MAC_HELPER_ASSET = 'ComputerHelper.app.zip';
|
|
31
|
-
/** The bundle directory name once extracted. */
|
|
32
|
-
export const MAC_HELPER_APP_NAME = 'ComputerHelper.app';
|
|
33
|
-
/** ComputerHelper identity + verification policy. No `expectedBundleId`: this
|
|
34
|
-
* helper has no upgrade-stable Accessibility grant to protect, so its
|
|
35
|
-
* verification is codesign + Team + notarization exactly as before. */
|
|
36
|
-
const COMPUTER_HELPER_SPEC = {
|
|
37
|
-
helper: 'computer-mac',
|
|
38
|
-
assetName: MAC_HELPER_ASSET,
|
|
39
|
-
appName: MAC_HELPER_APP_NAME,
|
|
40
|
-
cacheSubdir: ['computer', 'mac-helper'],
|
|
41
|
-
expectedTeamId: EXPECTED_TEAM_ID,
|
|
42
|
-
localBuildHint: 'bash native/computer-mac/scripts/build.sh release',
|
|
43
|
-
};
|
|
44
|
-
/** Cache dir for the downloaded helper, one subdir per release tag. */
|
|
45
|
-
export function macHelperCacheDir(version) {
|
|
46
|
-
return helperCacheDir(COMPUTER_HELPER_SPEC, version);
|
|
47
|
-
}
|
|
48
|
-
/** Release-asset URLs for the helper zip + its checksum at one `v<version>` tag. */
|
|
49
|
-
export function macHelperAssetUrls(version) {
|
|
50
|
-
return helperAssetUrls(COMPUTER_HELPER_SPEC, version);
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Verify a helper `.app` bundle is intact, signed by the expected Developer ID
|
|
54
|
-
* Team, and notarized (Gatekeeper-accepted). Throws with an actionable message
|
|
55
|
-
* on any failure — a downloaded bundle is never trusted without this.
|
|
56
|
-
*/
|
|
57
|
-
export function verifyMacHelper(appPath) {
|
|
58
|
-
verifyHelperApp(appPath, COMPUTER_HELPER_SPEC);
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Download the helper release asset for `version`, verify sha256, extract the
|
|
62
|
-
* `.app`, and verify its signature. Returns the path to the extracted
|
|
63
|
-
* `ComputerHelper.app`. A missing asset is a hard error naming the exact tag —
|
|
64
|
-
* never a silent fallback to another release.
|
|
65
|
-
*/
|
|
66
|
-
export function downloadMacHelperApp(version) {
|
|
67
|
-
return downloadHelperApp(COMPUTER_HELPER_SPEC, version);
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Resolve the helper `.app` to install from: a local build / bundled copy first
|
|
71
|
-
* (repo checkout), else the checksum + signature-verified release-asset download
|
|
72
|
-
* for the pinned helper version. Throws with the tag it checked when neither
|
|
73
|
-
* exists. macOS only.
|
|
74
|
-
*/
|
|
75
|
-
export async function ensureMacHelperApp(version = helperFloor('computer-mac')) {
|
|
76
|
-
if (os.platform() !== 'darwin') {
|
|
77
|
-
throw new Error('The macOS computer helper is only available on macOS.');
|
|
78
|
-
}
|
|
79
|
-
const local = resolveHelperApp();
|
|
80
|
-
if (local)
|
|
81
|
-
return local;
|
|
82
|
-
return downloadMacHelperApp(version);
|
|
83
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
export interface VerbCall {
|
|
2
|
-
name: string;
|
|
3
|
-
input: Record<string, unknown>;
|
|
4
|
-
}
|
|
5
|
-
export interface VerbResult {
|
|
6
|
-
ok: boolean;
|
|
7
|
-
result?: Record<string, unknown>;
|
|
8
|
-
error?: string;
|
|
9
|
-
}
|
|
10
|
-
export interface LoopStep {
|
|
11
|
-
call: VerbCall;
|
|
12
|
-
result: VerbResult;
|
|
13
|
-
visionFallback?: boolean;
|
|
14
|
-
}
|
|
15
|
-
export interface LoopState {
|
|
16
|
-
task: string;
|
|
17
|
-
steps: LoopStep[];
|
|
18
|
-
visionMode: boolean;
|
|
19
|
-
}
|
|
20
|
-
export interface ModelDecision {
|
|
21
|
-
toolCalls: VerbCall[];
|
|
22
|
-
done?: {
|
|
23
|
-
text: string;
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
export type ModelResponder = (state: LoopState) => ModelDecision | Promise<ModelDecision>;
|
|
27
|
-
export type VerbDispatcher = (call: VerbCall) => VerbResult | Promise<VerbResult>;
|
|
28
|
-
export interface LoopConfig {
|
|
29
|
-
task: string;
|
|
30
|
-
responder: ModelResponder;
|
|
31
|
-
dispatch: VerbDispatcher;
|
|
32
|
-
maxSteps: number;
|
|
33
|
-
onEvent?: (event: LoopEvent) => void;
|
|
34
|
-
}
|
|
35
|
-
export type LoopEvent = {
|
|
36
|
-
kind: 'turn';
|
|
37
|
-
index: number;
|
|
38
|
-
} | {
|
|
39
|
-
kind: 'dispatch';
|
|
40
|
-
call: VerbCall;
|
|
41
|
-
result: VerbResult;
|
|
42
|
-
visionFallback?: boolean;
|
|
43
|
-
} | {
|
|
44
|
-
kind: 'vision_switch';
|
|
45
|
-
describe: VerbResult;
|
|
46
|
-
} | {
|
|
47
|
-
kind: 'done';
|
|
48
|
-
text: string;
|
|
49
|
-
} | {
|
|
50
|
-
kind: 'max_steps';
|
|
51
|
-
};
|
|
52
|
-
export interface LoopResult {
|
|
53
|
-
status: 'done' | 'max_steps';
|
|
54
|
-
finalText?: string;
|
|
55
|
-
steps: LoopStep[];
|
|
56
|
-
dispatched: string[];
|
|
57
|
-
visionMode: boolean;
|
|
58
|
-
turns: number;
|
|
59
|
-
}
|
|
60
|
-
export declare const AX_OPAQUE_ELEMENT_THRESHOLD = 3;
|
|
61
|
-
export declare function isAxOpaque(result: VerbResult): boolean;
|
|
62
|
-
export declare function runComputerLoop(config: LoopConfig): Promise<LoopResult>;
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
// The embedded observe -> act -> verify loop behind `agents computer run`.
|
|
2
|
-
//
|
|
3
|
-
// This module owns ONLY the decision logic: given a reasoning model (a
|
|
4
|
-
// boundary, injected as `responder`) and a verb dispatcher (the existing
|
|
5
|
-
// computer-helper RPC surface, injected as `dispatch`), it drives the model's
|
|
6
|
-
// tool calls against the daemon, step by step, until the model declares the
|
|
7
|
-
// task done or `maxSteps` is hit.
|
|
8
|
-
//
|
|
9
|
-
// The model and the daemon are boundaries — neither is code under test. The
|
|
10
|
-
// loop itself (verb ordering, max-steps, the AX -> vision fallback) is the
|
|
11
|
-
// unit under test. See loop.test.ts, which injects a scripted fake responder
|
|
12
|
-
// and a fake dispatcher.
|
|
13
|
-
// Below this many AX elements the tree is treated as opaque — a WebView, a
|
|
14
|
-
// Qt/Electron surface that never populated its AX tree, or a canvas app. The
|
|
15
|
-
// model then works from pixels + coordinate clicks instead of element ids.
|
|
16
|
-
export const AX_OPAQUE_ELEMENT_THRESHOLD = 3;
|
|
17
|
-
// Describe-time heuristic. An explicit `ax_opaque: true` from the daemon wins.
|
|
18
|
-
// Otherwise: a failed describe is NOT opaque (it's an error, surface it), and a
|
|
19
|
-
// successful one is opaque when its element_count is at/under the threshold or
|
|
20
|
-
// its tree has no children. Pure so the rule is unit-testable in isolation.
|
|
21
|
-
export function isAxOpaque(result) {
|
|
22
|
-
const r = result.result ?? {};
|
|
23
|
-
if (r.ax_opaque === true)
|
|
24
|
-
return true;
|
|
25
|
-
if (!result.ok)
|
|
26
|
-
return false;
|
|
27
|
-
const count = typeof r.element_count === 'number' ? r.element_count : undefined;
|
|
28
|
-
if (count !== undefined)
|
|
29
|
-
return count <= AX_OPAQUE_ELEMENT_THRESHOLD;
|
|
30
|
-
const tree = r.tree;
|
|
31
|
-
const children = tree && Array.isArray(tree.children) ? tree.children : [];
|
|
32
|
-
return children.length === 0;
|
|
33
|
-
}
|
|
34
|
-
// Drive the loop to completion. Returns the transcript, the ordered verb names
|
|
35
|
-
// dispatched, and whether the vision fallback ever fired.
|
|
36
|
-
export async function runComputerLoop(config) {
|
|
37
|
-
const state = { task: config.task, steps: [], visionMode: false };
|
|
38
|
-
const dispatched = [];
|
|
39
|
-
const emit = config.onEvent ?? (() => { });
|
|
40
|
-
let turns = 0;
|
|
41
|
-
while (turns < config.maxSteps) {
|
|
42
|
-
emit({ kind: 'turn', index: turns });
|
|
43
|
-
turns++;
|
|
44
|
-
const decision = await config.responder(state);
|
|
45
|
-
if (decision.done) {
|
|
46
|
-
emit({ kind: 'done', text: decision.done.text });
|
|
47
|
-
return {
|
|
48
|
-
status: 'done',
|
|
49
|
-
finalText: decision.done.text,
|
|
50
|
-
steps: state.steps,
|
|
51
|
-
dispatched,
|
|
52
|
-
visionMode: state.visionMode,
|
|
53
|
-
turns,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
for (const call of decision.toolCalls) {
|
|
57
|
-
const result = await config.dispatch(call);
|
|
58
|
-
dispatched.push(call.name);
|
|
59
|
-
// AX -> vision fallback. When a describe comes back opaque, the element
|
|
60
|
-
// ids the model would click are useless, so the loop flips visionMode and
|
|
61
|
-
// immediately hands the model pixels: it auto-dispatches a screenshot of
|
|
62
|
-
// the same target. The explicit verbs stay untouched for external callers
|
|
63
|
-
// — this switch lives entirely in the loop.
|
|
64
|
-
if (call.name === 'describe' && isAxOpaque(result)) {
|
|
65
|
-
state.visionMode = true;
|
|
66
|
-
state.steps.push({ call, result, visionFallback: true });
|
|
67
|
-
emit({ kind: 'dispatch', call, result, visionFallback: true });
|
|
68
|
-
emit({ kind: 'vision_switch', describe: result });
|
|
69
|
-
const shotCall = { name: 'screenshot', input: pickTargetInput(call.input) };
|
|
70
|
-
const shot = await config.dispatch(shotCall);
|
|
71
|
-
dispatched.push(shotCall.name);
|
|
72
|
-
state.steps.push({ call: shotCall, result: shot });
|
|
73
|
-
emit({ kind: 'dispatch', call: shotCall, result: shot });
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
state.steps.push({ call, result });
|
|
77
|
-
emit({ kind: 'dispatch', call, result });
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
emit({ kind: 'max_steps' });
|
|
81
|
-
return {
|
|
82
|
-
status: 'max_steps',
|
|
83
|
-
steps: state.steps,
|
|
84
|
-
dispatched,
|
|
85
|
-
visionMode: state.visionMode,
|
|
86
|
-
turns,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
// Carry the target selector (pid/bundle) from the describe call onto the
|
|
90
|
-
// auto-injected screenshot so the vision fallback captures the same surface.
|
|
91
|
-
function pickTargetInput(input) {
|
|
92
|
-
const out = {};
|
|
93
|
-
if (input.pid != null)
|
|
94
|
-
out.pid = input.pid;
|
|
95
|
-
if (input.bundle != null)
|
|
96
|
-
out.bundle = input.bundle;
|
|
97
|
-
return out;
|
|
98
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { LoopState, ModelDecision, ModelResponder } from './loop.js';
|
|
2
|
-
interface ToolSpec {
|
|
3
|
-
name: string;
|
|
4
|
-
description: string;
|
|
5
|
-
input_schema: {
|
|
6
|
-
type: 'object';
|
|
7
|
-
properties: Record<string, unknown>;
|
|
8
|
-
required?: string[];
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
export declare const COMPUTER_TOOLS: ToolSpec[];
|
|
12
|
-
export interface ClaudeResponderConfig {
|
|
13
|
-
baseUrl?: string;
|
|
14
|
-
apiKey?: string;
|
|
15
|
-
model?: string;
|
|
16
|
-
maxTokens?: number;
|
|
17
|
-
fetchImpl?: typeof fetch;
|
|
18
|
-
}
|
|
19
|
-
export declare const DEFAULT_CLAUDE_BASE_URL = "https://api.anthropic.com";
|
|
20
|
-
export declare const DEFAULT_CLAUDE_MODEL = "claude-sonnet-4-6";
|
|
21
|
-
export declare const ANTHROPIC_VERSION = "2023-06-01";
|
|
22
|
-
export declare function resolveApiKey(config: ClaudeResponderConfig, env?: NodeJS.ProcessEnv): string;
|
|
23
|
-
export declare function buildSystemPrompt(state: LoopState): string;
|
|
24
|
-
type AnthropicBlock = {
|
|
25
|
-
type: 'text';
|
|
26
|
-
text: string;
|
|
27
|
-
} | {
|
|
28
|
-
type: 'tool_use';
|
|
29
|
-
id: string;
|
|
30
|
-
name: string;
|
|
31
|
-
input: Record<string, unknown>;
|
|
32
|
-
} | {
|
|
33
|
-
type: 'tool_result';
|
|
34
|
-
tool_use_id: string;
|
|
35
|
-
content: string;
|
|
36
|
-
};
|
|
37
|
-
interface AnthropicMessage {
|
|
38
|
-
role: 'user' | 'assistant';
|
|
39
|
-
content: AnthropicBlock[];
|
|
40
|
-
}
|
|
41
|
-
export declare function buildMessages(state: LoopState): AnthropicMessage[];
|
|
42
|
-
export declare function parseDecision(body: unknown): ModelDecision;
|
|
43
|
-
export declare function makeClaudeResponder(config?: ClaudeResponderConfig): ModelResponder;
|
|
44
|
-
export {};
|