@phnx-labs/agents-cli 1.20.34 → 1.20.36
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 +113 -0
- package/LICENSE +185 -21
- package/README.md +13 -5
- package/dist/commands/audit.d.ts +14 -0
- package/dist/commands/audit.js +68 -0
- package/dist/commands/browser.js +82 -8
- package/dist/commands/check.d.ts +15 -0
- package/dist/commands/check.js +84 -0
- package/dist/commands/cloud.js +143 -3
- package/dist/commands/computer-actions.d.ts +15 -0
- package/dist/commands/computer-actions.js +120 -22
- package/dist/commands/computer.js +81 -0
- package/dist/commands/cost.js +17 -12
- package/dist/commands/daemon.js +4 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +54 -97
- package/dist/commands/events.d.ts +16 -0
- package/dist/commands/events.js +173 -0
- package/dist/commands/exec.d.ts +19 -0
- package/dist/commands/exec.js +283 -12
- package/dist/commands/hosts.js +37 -29
- package/dist/commands/inspect.d.ts +2 -0
- package/dist/commands/inspect.js +62 -16
- package/dist/commands/lock.d.ts +12 -0
- package/dist/commands/lock.js +70 -0
- package/dist/commands/logs.d.ts +17 -0
- package/dist/commands/logs.js +139 -0
- package/dist/commands/message.d.ts +15 -0
- package/dist/commands/message.js +56 -0
- package/dist/commands/models.d.ts +3 -0
- package/dist/commands/models.js +27 -8
- package/dist/commands/permissions.js +9 -2
- package/dist/commands/repo.d.ts +34 -0
- package/dist/commands/repo.js +243 -65
- package/dist/commands/resource-view.d.ts +20 -0
- package/dist/commands/resource-view.js +90 -28
- package/dist/commands/routines.js +101 -5
- package/dist/commands/secrets-migrate.js +106 -57
- package/dist/commands/secrets.d.ts +31 -18
- package/dist/commands/secrets.js +196 -91
- package/dist/commands/serve.d.ts +10 -0
- package/dist/commands/serve.js +37 -0
- package/dist/commands/sessions-inject.d.ts +14 -0
- package/dist/commands/sessions-inject.js +111 -0
- package/dist/commands/sessions-picker.d.ts +2 -0
- package/dist/commands/sessions-picker.js +24 -3
- package/dist/commands/sessions-resume.d.ts +2 -0
- package/dist/commands/sessions-resume.js +221 -0
- package/dist/commands/sessions-tail.d.ts +10 -0
- package/dist/commands/sessions-tail.js +11 -0
- package/dist/commands/sessions.d.ts +143 -1
- package/dist/commands/sessions.js +628 -80
- package/dist/commands/setup.js +4 -2
- package/dist/commands/sync.d.ts +3 -1
- package/dist/commands/sync.js +156 -4
- package/dist/commands/teams.js +217 -0
- package/dist/commands/versions.js +2 -4
- package/dist/commands/view.d.ts +3 -0
- package/dist/commands/view.js +19 -8
- package/dist/commands/wallet.d.ts +6 -0
- package/dist/commands/wallet.js +22 -5
- package/dist/commands/watchdog.d.ts +18 -0
- package/dist/commands/watchdog.js +238 -0
- package/dist/index.js +82 -32
- package/dist/lib/agents.js +30 -9
- package/dist/lib/audit/log.d.ts +92 -0
- package/dist/lib/audit/log.js +177 -0
- package/dist/lib/auto-pull.js +2 -1
- package/dist/lib/browser/cdp.js +5 -1
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +18 -7
- package/dist/lib/browser/drivers/ssh.d.ts +8 -0
- package/dist/lib/browser/drivers/ssh.js +77 -16
- package/dist/lib/browser/har.d.ts +84 -0
- package/dist/lib/browser/har.js +77 -0
- package/dist/lib/browser/ipc.d.ts +3 -0
- package/dist/lib/browser/ipc.js +37 -12
- package/dist/lib/browser/profiles.d.ts +1 -1
- package/dist/lib/browser/profiles.js +8 -10
- package/dist/lib/browser/refs.d.ts +65 -0
- package/dist/lib/browser/refs.js +73 -1
- package/dist/lib/browser/runtime-state.js +1 -0
- package/dist/lib/browser/service.d.ts +38 -2
- package/dist/lib/browser/service.js +183 -23
- package/dist/lib/browser/types.d.ts +14 -1
- package/dist/lib/budget/live-cloud.d.ts +42 -0
- package/dist/lib/budget/live-cloud.js +79 -0
- package/dist/lib/budget/live-team.d.ts +31 -0
- package/dist/lib/budget/live-team.js +115 -0
- package/dist/lib/cloud/codex.js +4 -0
- package/dist/lib/cloud/rush.d.ts +12 -1
- package/dist/lib/cloud/rush.js +13 -3
- package/dist/lib/cloud/types.d.ts +9 -0
- package/dist/lib/computer/dispatch.d.ts +8 -0
- package/dist/lib/computer/dispatch.js +125 -0
- package/dist/lib/computer/loop.d.ts +62 -0
- package/dist/lib/computer/loop.js +98 -0
- package/dist/lib/computer/model.d.ts +44 -0
- package/dist/lib/computer/model.js +157 -0
- package/dist/lib/concurrency.d.ts +19 -0
- package/dist/lib/concurrency.js +33 -0
- package/dist/lib/daemon.d.ts +92 -0
- package/dist/lib/daemon.js +222 -18
- package/dist/lib/devices/connect.d.ts +3 -2
- package/dist/lib/devices/connect.js +5 -3
- package/dist/lib/devices/registry.d.ts +7 -0
- package/dist/lib/devices/registry.js +24 -0
- package/dist/lib/devices/tailscale.js +1 -1
- package/dist/lib/drift.d.ts +52 -0
- package/dist/lib/drift.js +112 -0
- package/dist/lib/events.d.ts +10 -3
- package/dist/lib/events.js +101 -24
- package/dist/lib/exec.d.ts +17 -0
- package/dist/lib/exec.js +100 -12
- package/dist/lib/git.d.ts +27 -0
- package/dist/lib/git.js +56 -1
- package/dist/lib/hooks/cache.d.ts +6 -0
- package/dist/lib/hooks/cache.js +54 -12
- package/dist/lib/hooks.d.ts +27 -0
- package/dist/lib/hooks.js +135 -10
- package/dist/lib/hosts/dispatch.d.ts +15 -0
- package/dist/lib/hosts/dispatch.js +45 -13
- package/dist/lib/hosts/logs.d.ts +16 -0
- package/dist/lib/hosts/logs.js +74 -0
- package/dist/lib/hosts/option.js +1 -1
- package/dist/lib/hosts/passthrough.js +3 -1
- package/dist/lib/hosts/progress.d.ts +66 -0
- package/dist/lib/hosts/progress.js +125 -17
- package/dist/lib/hosts/ready.d.ts +51 -7
- package/dist/lib/hosts/ready.js +98 -25
- package/dist/lib/hosts/reconcile.d.ts +53 -0
- package/dist/lib/hosts/reconcile.js +81 -0
- package/dist/lib/hosts/registry.d.ts +19 -2
- package/dist/lib/hosts/registry.js +58 -2
- package/dist/lib/hosts/remote-cmd.d.ts +62 -1
- package/dist/lib/hosts/remote-cmd.js +70 -1
- package/dist/lib/hosts/remote-os.d.ts +17 -0
- package/dist/lib/hosts/remote-os.js +30 -0
- package/dist/lib/hosts/session-index.d.ts +34 -0
- package/dist/lib/hosts/session-index.js +56 -0
- package/dist/lib/hosts/tasks.d.ts +22 -0
- package/dist/lib/hosts/tasks.js +29 -0
- package/dist/lib/lock.d.ts +93 -0
- package/dist/lib/lock.js +207 -0
- package/dist/lib/loop.js +16 -1
- package/dist/lib/machine-id.d.ts +21 -0
- package/dist/lib/machine-id.js +26 -0
- package/dist/lib/mailbox-target.d.ts +36 -0
- package/dist/lib/mailbox-target.js +45 -0
- package/dist/lib/mailbox.d.ts +47 -0
- package/dist/lib/mailbox.js +194 -0
- package/dist/lib/mcp.d.ts +5 -0
- package/dist/lib/mcp.js +24 -8
- package/dist/lib/migrate.d.ts +19 -0
- package/dist/lib/migrate.js +134 -26
- package/dist/lib/overdue.js +3 -0
- package/dist/lib/permissions.d.ts +4 -0
- package/dist/lib/permissions.js +35 -0
- package/dist/lib/picker.d.ts +26 -0
- package/dist/lib/picker.js +127 -0
- package/dist/lib/platform/exec.d.ts +46 -0
- package/dist/lib/platform/exec.js +74 -0
- package/dist/lib/platform/process.d.ts +31 -0
- package/dist/lib/platform/process.js +34 -1
- package/dist/lib/platform/winpath.js +2 -0
- package/dist/lib/plugin-marketplace.d.ts +30 -0
- package/dist/lib/plugin-marketplace.js +215 -2
- package/dist/lib/plugins.d.ts +5 -0
- package/dist/lib/plugins.js +61 -10
- package/dist/lib/profiles.d.ts +25 -0
- package/dist/lib/profiles.js +22 -6
- package/dist/lib/pty-client.js +2 -1
- package/dist/lib/rotate.d.ts +61 -0
- package/dist/lib/rotate.js +52 -0
- package/dist/lib/routines.d.ts +40 -2
- package/dist/lib/routines.js +66 -8
- package/dist/lib/runner.d.ts +11 -2
- package/dist/lib/runner.js +49 -7
- package/dist/lib/scheduler.js +6 -1
- package/dist/lib/secrets/bundles.d.ts +60 -4
- package/dist/lib/secrets/bundles.js +131 -12
- package/dist/lib/secrets/filestore.d.ts +3 -0
- package/dist/lib/secrets/filestore.js +42 -16
- package/dist/lib/secrets/index.d.ts +43 -2
- package/dist/lib/secrets/index.js +102 -3
- package/dist/lib/secrets/mcp.d.ts +93 -0
- package/dist/lib/secrets/mcp.js +205 -0
- package/dist/lib/secrets/remote.js +12 -5
- package/dist/lib/secrets/sync.js +83 -4
- package/dist/lib/secrets/windows.js +14 -3
- package/dist/lib/serve/data.d.ts +81 -0
- package/dist/lib/serve/data.js +91 -0
- package/dist/lib/serve/page.d.ts +7 -0
- package/dist/lib/serve/page.js +140 -0
- package/dist/lib/serve/server.d.ts +46 -0
- package/dist/lib/serve/server.js +115 -0
- package/dist/lib/session/active.d.ts +61 -0
- package/dist/lib/session/active.js +196 -17
- package/dist/lib/session/discover.d.ts +37 -0
- package/dist/lib/session/discover.js +204 -27
- package/dist/lib/session/inject.d.ts +18 -0
- package/dist/lib/session/inject.js +21 -0
- package/dist/lib/session/parse.d.ts +6 -0
- package/dist/lib/session/parse.js +286 -2
- package/dist/lib/session/pid-registry.d.ts +22 -0
- package/dist/lib/session/pid-registry.js +104 -0
- package/dist/lib/session/provenance.d.ts +14 -2
- package/dist/lib/session/provenance.js +39 -8
- package/dist/lib/session/remote-active.d.ts +26 -0
- package/dist/lib/session/remote-active.js +153 -0
- package/dist/lib/session/remote-list.d.ts +51 -0
- package/dist/lib/session/remote-list.js +213 -0
- package/dist/lib/session/remote.d.ts +7 -1
- package/dist/lib/session/remote.js +18 -8
- package/dist/lib/session/render.js +1 -1
- package/dist/lib/session/sync/config.d.ts +1 -7
- package/dist/lib/session/sync/config.js +4 -11
- package/dist/lib/session/types.d.ts +18 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +44 -6
- package/dist/lib/shims.js +143 -57
- package/dist/lib/ssh-exec.d.ts +26 -3
- package/dist/lib/ssh-exec.js +47 -3
- package/dist/lib/ssh-tunnel.d.ts +24 -5
- package/dist/lib/ssh-tunnel.js +62 -63
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +8 -0
- package/dist/lib/startup/command-registry.js +17 -1
- package/dist/lib/state.d.ts +13 -0
- package/dist/lib/state.js +103 -9
- package/dist/lib/sync-umbrella.d.ts +14 -7
- package/dist/lib/sync-umbrella.js +17 -9
- package/dist/lib/teams/forEach.d.ts +110 -0
- package/dist/lib/teams/forEach.js +186 -0
- package/dist/lib/teams/index.d.ts +1 -0
- package/dist/lib/teams/index.js +1 -0
- package/dist/lib/teams/pr-watch.d.ts +226 -0
- package/dist/lib/teams/pr-watch.js +371 -0
- package/dist/lib/teams/registry.js +25 -9
- package/dist/lib/teams/supervisor.d.ts +14 -1
- package/dist/lib/teams/supervisor.js +19 -0
- package/dist/lib/teams/worktree.d.ts +9 -0
- package/dist/lib/teams/worktree.js +32 -0
- package/dist/lib/terminal/backends/ghostty.d.ts +6 -0
- package/dist/lib/terminal/backends/ghostty.js +69 -0
- package/dist/lib/terminal/backends/index.d.ts +19 -0
- package/dist/lib/terminal/backends/index.js +31 -0
- package/dist/lib/terminal/backends/iterm.d.ts +6 -0
- package/dist/lib/terminal/backends/iterm.js +62 -0
- package/dist/lib/terminal/backends/tmux.d.ts +14 -0
- package/dist/lib/terminal/backends/tmux.js +23 -0
- package/dist/lib/terminal/backends/vscodium-agent.d.ts +31 -0
- package/dist/lib/terminal/backends/vscodium-agent.js +72 -0
- package/dist/lib/terminal/engine.d.ts +39 -0
- package/dist/lib/terminal/engine.js +54 -0
- package/dist/lib/terminal/index.d.ts +17 -0
- package/dist/lib/terminal/index.js +8 -0
- package/dist/lib/terminal/inject.d.ts +204 -0
- package/dist/lib/terminal/inject.js +247 -0
- package/dist/lib/terminal/policy.d.ts +11 -0
- package/dist/lib/terminal/policy.js +11 -0
- package/dist/lib/terminal/quote.d.ts +11 -0
- package/dist/lib/terminal/quote.js +13 -0
- package/dist/lib/terminal/resolve.d.ts +64 -0
- package/dist/lib/terminal/resolve.js +90 -0
- package/dist/lib/terminal/shell.d.ts +6 -0
- package/dist/lib/terminal/shell.js +23 -0
- package/dist/lib/terminal/transport.d.ts +15 -0
- package/dist/lib/terminal/transport.js +40 -0
- package/dist/lib/terminal/types.d.ts +59 -0
- package/dist/lib/terminal/types.js +13 -0
- package/dist/lib/triggers/webhook.d.ts +85 -0
- package/dist/lib/triggers/webhook.js +141 -0
- package/dist/lib/types.d.ts +7 -0
- package/dist/lib/versions.d.ts +30 -3
- package/dist/lib/versions.js +129 -18
- package/dist/lib/watchdog/index.d.ts +3 -0
- package/dist/lib/watchdog/index.js +5 -0
- package/dist/lib/watchdog/read.d.ts +35 -0
- package/dist/lib/watchdog/read.js +149 -0
- package/dist/lib/watchdog/runner.d.ts +127 -0
- package/dist/lib/watchdog/runner.js +322 -0
- package/dist/lib/watchdog/watchdog.d.ts +40 -0
- package/dist/lib/watchdog/watchdog.js +166 -0
- package/dist/lib/watchdog/watchdogTail.d.ts +5 -0
- package/dist/lib/watchdog/watchdogTail.js +154 -0
- package/dist/lib/whats-new.d.ts +9 -0
- package/dist/lib/whats-new.js +35 -0
- package/dist/lib/workflows.d.ts +166 -0
- package/dist/lib/workflows.js +193 -0
- package/package.json +5 -4
package/dist/lib/browser/ipc.js
CHANGED
|
@@ -32,8 +32,12 @@ export function getSocketPath() {
|
|
|
32
32
|
function getIpcEndpoint() {
|
|
33
33
|
return ipcEndpoint(getSocketPath());
|
|
34
34
|
}
|
|
35
|
-
/** Can we open a connection to the daemon right now?
|
|
36
|
-
*
|
|
35
|
+
/** Can we open a connection to the daemon right now? Resolves false on any
|
|
36
|
+
* error. This is the authoritative liveness check on every platform: a live
|
|
37
|
+
* daemon accepts the connection, while a stale POSIX socket file left behind by
|
|
38
|
+
* a crashed daemon (or one that "appears and is immediately destroyed", #556)
|
|
39
|
+
* rejects with ECONNREFUSED and is correctly reported as not reachable —
|
|
40
|
+
* something fs.existsSync can't distinguish. */
|
|
37
41
|
function probeDaemon(endpoint, timeoutMs = 500) {
|
|
38
42
|
return new Promise((resolve) => {
|
|
39
43
|
const sock = net.createConnection(endpoint);
|
|
@@ -45,16 +49,16 @@ function probeDaemon(endpoint, timeoutMs = 500) {
|
|
|
45
49
|
sock.on('error', () => { clearTimeout(timer); finish(false); });
|
|
46
50
|
});
|
|
47
51
|
}
|
|
48
|
-
/** Is the daemon reachable?
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return fs.existsSync(getSocketPath());
|
|
52
|
+
/** Is the daemon reachable? A real connect probe on every platform — a socket
|
|
53
|
+
* file existing on disk is not proof a daemon is listening on it. */
|
|
54
|
+
export async function isDaemonReachable() {
|
|
55
|
+
return probeDaemon(getIpcEndpoint());
|
|
53
56
|
}
|
|
54
|
-
async function waitForSocket(
|
|
57
|
+
async function waitForSocket(_socketPath, timeoutMs) {
|
|
58
|
+
const endpoint = getIpcEndpoint();
|
|
55
59
|
const deadline = Date.now() + timeoutMs;
|
|
56
60
|
while (Date.now() < deadline) {
|
|
57
|
-
if (
|
|
61
|
+
if (await probeDaemon(endpoint))
|
|
58
62
|
return;
|
|
59
63
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
60
64
|
}
|
|
@@ -284,6 +288,13 @@ export class BrowserIPCServer {
|
|
|
284
288
|
const shot = await this.service.screenshot(request.task, request.tabId, request.path, request.quality);
|
|
285
289
|
return { ok: true, path: shot.path, bytes: shot.bytes, width: shot.width, height: shot.height };
|
|
286
290
|
}
|
|
291
|
+
case 'pdf': {
|
|
292
|
+
if (!request.task) {
|
|
293
|
+
return { ok: false, error: 'Task required' };
|
|
294
|
+
}
|
|
295
|
+
const doc = await this.service.printToPdf(request.task, request.tabId, request.path);
|
|
296
|
+
return { ok: true, path: doc.path, bytes: doc.bytes };
|
|
297
|
+
}
|
|
287
298
|
case 'refs': {
|
|
288
299
|
if (!request.task) {
|
|
289
300
|
return { ok: false, error: 'Task required' };
|
|
@@ -301,10 +312,24 @@ export class BrowserIPCServer {
|
|
|
301
312
|
return { ok: true, refs, nodes };
|
|
302
313
|
}
|
|
303
314
|
case 'click': {
|
|
304
|
-
if (!request.task
|
|
305
|
-
return { ok: false, error: 'Task
|
|
315
|
+
if (!request.task) {
|
|
316
|
+
return { ok: false, error: 'Task required' };
|
|
317
|
+
}
|
|
318
|
+
// Coordinate click (`--at X,Y`) bypasses ref resolution entirely.
|
|
319
|
+
if (request.atX !== undefined && request.atY !== undefined) {
|
|
320
|
+
await this.service.clickAt(request.task, request.atX, request.atY, request.tabId);
|
|
321
|
+
return { ok: true };
|
|
322
|
+
}
|
|
323
|
+
if (request.ref === undefined) {
|
|
324
|
+
return { ok: false, error: 'Task and ref (or --at X,Y) required' };
|
|
325
|
+
}
|
|
326
|
+
const { healed } = await this.service.click(request.task, request.ref, request.tabId);
|
|
327
|
+
if (healed) {
|
|
328
|
+
return {
|
|
329
|
+
ok: true,
|
|
330
|
+
message: `self-healed ref ${healed.from} -> ${healed.to} (${healed.role} "${healed.name}")`,
|
|
331
|
+
};
|
|
306
332
|
}
|
|
307
|
-
await this.service.click(request.task, request.ref, request.tabId);
|
|
308
333
|
return { ok: true };
|
|
309
334
|
}
|
|
310
335
|
case 'type': {
|
|
@@ -12,7 +12,7 @@ export declare function getProfile(name: string): Promise<BrowserProfile | null>
|
|
|
12
12
|
* Re-uses an existing `default` profile as-is (we don't second-guess the user
|
|
13
13
|
* if they've already customized it). On first run we walk the priority list
|
|
14
14
|
* (macOS: chrome > brave > edge > chromium > comet; Linux: chrome > chromium >
|
|
15
|
-
* brave > edge; Windows: edge > chrome > brave) and pin the profile to the
|
|
15
|
+
* brave > edge; Windows: edge > chrome > brave > comet) and pin the profile to the
|
|
16
16
|
* first match. Throws an actionable error if none of those binaries are
|
|
17
17
|
* installed so the user knows exactly which browsers we'd accept.
|
|
18
18
|
*/
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
|
-
import { execFileSync } from 'child_process';
|
|
3
2
|
import { getBrowserRuntimeDir as getBrowserRuntimeDirRoot, readMeta, writeMeta, } from '../state.js';
|
|
4
|
-
import { findBrowserPath, findFirstInstalledBrowser } from './chrome.js';
|
|
3
|
+
import { findBrowserPath, findFirstInstalledBrowser, isPortInUse } from './chrome.js';
|
|
5
4
|
import { DEFAULT_VIEWPORT } from './devices.js';
|
|
6
5
|
export const DEFAULT_BROWSER_PROFILE_NAME = 'default';
|
|
7
6
|
export function getBrowserRuntimeDir() {
|
|
@@ -76,7 +75,7 @@ export async function getProfile(name) {
|
|
|
76
75
|
* Re-uses an existing `default` profile as-is (we don't second-guess the user
|
|
77
76
|
* if they've already customized it). On first run we walk the priority list
|
|
78
77
|
* (macOS: chrome > brave > edge > chromium > comet; Linux: chrome > chromium >
|
|
79
|
-
* brave > edge; Windows: edge > chrome > brave) and pin the profile to the
|
|
78
|
+
* brave > edge; Windows: edge > chrome > brave > comet) and pin the profile to the
|
|
80
79
|
* first match. Throws an actionable error if none of those binaries are
|
|
81
80
|
* installed so the user knows exactly which browsers we'd accept.
|
|
82
81
|
*/
|
|
@@ -153,14 +152,13 @@ export async function findFreeProfilePort() {
|
|
|
153
152
|
for (let port = 9222; port <= 9399; port++) {
|
|
154
153
|
if (usedByProfile.has(port))
|
|
155
154
|
continue;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
155
|
+
// Platform-aware bound-port probe (lsof on POSIX, netstat on Windows).
|
|
156
|
+
// The old inline lsof call threw ENOENT on Windows — where lsof doesn't
|
|
157
|
+
// exist — so EVERY port scanned as "free", including one an already-
|
|
158
|
+
// running browser was listening on (typically 9222), and the new profile
|
|
159
|
+
// would silently attach to that browser instead of launching its own.
|
|
160
|
+
if (!isPortInUse(port))
|
|
162
161
|
return port;
|
|
163
|
-
}
|
|
164
162
|
}
|
|
165
163
|
throw new Error('No available ports in range 9222-9399');
|
|
166
164
|
}
|
|
@@ -12,9 +12,74 @@ export interface RefNode {
|
|
|
12
12
|
backendNodeId?: number;
|
|
13
13
|
editor?: string;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Stable, navigation-surviving identity for a ref. Unlike {@link RefNode}, this
|
|
17
|
+
* carries no `backendNodeId` (which goes stale the moment the DOM re-renders) —
|
|
18
|
+
* only the accessible descriptor a fresh `getRefs` can be re-matched against.
|
|
19
|
+
* Cached in per-task state so a later `click <ref>` can self-heal when the
|
|
20
|
+
* integer ref has drifted (see {@link healRef}).
|
|
21
|
+
*/
|
|
22
|
+
export interface RefDescriptor {
|
|
23
|
+
ref: number;
|
|
24
|
+
role: string;
|
|
25
|
+
name: string;
|
|
26
|
+
attrs: string[];
|
|
27
|
+
/**
|
|
28
|
+
* Best-effort CSS selector captured via DOM. Reserved for future
|
|
29
|
+
* selector-based healing; matching today is role+name+attrs+position.
|
|
30
|
+
*/
|
|
31
|
+
selector?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A cached ref listing for one tab: the stable descriptors plus the resolved
|
|
35
|
+
* {@link RefOpts} the listing was built with. A later `click`/`type` MUST
|
|
36
|
+
* rebuild its node map with these same opts, or the integer refs it looks up
|
|
37
|
+
* are numbered against a *different* accessibility filter than the one the
|
|
38
|
+
* user saw in `browser refs` — the drift that makes self-heal silently click
|
|
39
|
+
* the wrong element on the second click.
|
|
40
|
+
*/
|
|
41
|
+
export interface RefSnapshot {
|
|
42
|
+
descriptors: RefDescriptor[];
|
|
43
|
+
opts: {
|
|
44
|
+
interactive: boolean;
|
|
45
|
+
limit: number;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Snapshot the stable descriptor for every ref in a freshly-built node map. */
|
|
49
|
+
export declare function describeRefs(nodeMap: Map<number, RefNode>): RefDescriptor[];
|
|
50
|
+
/**
|
|
51
|
+
* Re-resolve a stale ref against a freshly-built node map by matching the
|
|
52
|
+
* cached descriptor. Pure — no CDP, no DOM.
|
|
53
|
+
*
|
|
54
|
+
* The integer ref assigned by {@link getRefs} is positional: it shifts whenever
|
|
55
|
+
* the accessibility tree changes (navigation, re-render, or even a different
|
|
56
|
+
* `interactive` filter). The descriptor's (role, name) pair is the coarse
|
|
57
|
+
* identity, but a page can carry duplicate (role, name) elements — two "Submit"
|
|
58
|
+
* buttons, one action button per repeated list row. Matching on (role, name)
|
|
59
|
+
* alone would heal to whichever duplicate the fresh scan happens to hit first,
|
|
60
|
+
* which can be the wrong one. So among (role, name) matches we tie-break, in
|
|
61
|
+
* priority order:
|
|
62
|
+
*
|
|
63
|
+
* 1. **DOM-backed** — a candidate with a `backendNodeId` is resolvable to
|
|
64
|
+
* coordinates; one without is unclickable, so healing to it is useless.
|
|
65
|
+
* 2. **Attribute similarity** — the cached `attrs` (disabled / checked /
|
|
66
|
+
* selected / …) disambiguate otherwise-identical twins (the *disabled*
|
|
67
|
+
* Submit vs the enabled one).
|
|
68
|
+
* 3. **Positional proximity** — the fresh ref closest to the original cached
|
|
69
|
+
* ref index wins, so a repeated list-row action heals to its nearest
|
|
70
|
+
* positional twin rather than jumping across the page.
|
|
71
|
+
*
|
|
72
|
+
* Returns the new integer ref, or `null` when no node with the same role+name
|
|
73
|
+
* exists in the fresh map (unhealable — the element is genuinely gone).
|
|
74
|
+
*/
|
|
75
|
+
export declare function healRef(descriptor: Pick<RefDescriptor, 'ref' | 'role' | 'name' | 'attrs'>, freshNodeMap: Map<number, RefNode>): number | null;
|
|
15
76
|
export declare function getRefs(cdp: CDPClient, sessionId: string, opts?: RefOpts): Promise<{
|
|
16
77
|
refs: string;
|
|
17
78
|
nodeMap: Map<number, RefNode>;
|
|
79
|
+
opts: {
|
|
80
|
+
interactive: boolean;
|
|
81
|
+
limit: number;
|
|
82
|
+
};
|
|
18
83
|
}>;
|
|
19
84
|
export declare function resolveRefToCoords(cdp: CDPClient, sessionId: string, nodeMap: Map<number, RefNode>, ref: number): Promise<{
|
|
20
85
|
x: number;
|
package/dist/lib/browser/refs.js
CHANGED
|
@@ -1,3 +1,75 @@
|
|
|
1
|
+
/** Snapshot the stable descriptor for every ref in a freshly-built node map. */
|
|
2
|
+
export function describeRefs(nodeMap) {
|
|
3
|
+
return Array.from(nodeMap.values()).map((n) => ({
|
|
4
|
+
ref: n.ref,
|
|
5
|
+
role: n.role,
|
|
6
|
+
name: n.name,
|
|
7
|
+
attrs: n.attrs.slice(),
|
|
8
|
+
}));
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Re-resolve a stale ref against a freshly-built node map by matching the
|
|
12
|
+
* cached descriptor. Pure — no CDP, no DOM.
|
|
13
|
+
*
|
|
14
|
+
* The integer ref assigned by {@link getRefs} is positional: it shifts whenever
|
|
15
|
+
* the accessibility tree changes (navigation, re-render, or even a different
|
|
16
|
+
* `interactive` filter). The descriptor's (role, name) pair is the coarse
|
|
17
|
+
* identity, but a page can carry duplicate (role, name) elements — two "Submit"
|
|
18
|
+
* buttons, one action button per repeated list row. Matching on (role, name)
|
|
19
|
+
* alone would heal to whichever duplicate the fresh scan happens to hit first,
|
|
20
|
+
* which can be the wrong one. So among (role, name) matches we tie-break, in
|
|
21
|
+
* priority order:
|
|
22
|
+
*
|
|
23
|
+
* 1. **DOM-backed** — a candidate with a `backendNodeId` is resolvable to
|
|
24
|
+
* coordinates; one without is unclickable, so healing to it is useless.
|
|
25
|
+
* 2. **Attribute similarity** — the cached `attrs` (disabled / checked /
|
|
26
|
+
* selected / …) disambiguate otherwise-identical twins (the *disabled*
|
|
27
|
+
* Submit vs the enabled one).
|
|
28
|
+
* 3. **Positional proximity** — the fresh ref closest to the original cached
|
|
29
|
+
* ref index wins, so a repeated list-row action heals to its nearest
|
|
30
|
+
* positional twin rather than jumping across the page.
|
|
31
|
+
*
|
|
32
|
+
* Returns the new integer ref, or `null` when no node with the same role+name
|
|
33
|
+
* exists in the fresh map (unhealable — the element is genuinely gone).
|
|
34
|
+
*/
|
|
35
|
+
export function healRef(descriptor, freshNodeMap) {
|
|
36
|
+
const { ref, role, name, attrs } = descriptor;
|
|
37
|
+
const cachedAttrs = new Set(attrs);
|
|
38
|
+
// How well a candidate's attribute set matches the cached one: reward shared
|
|
39
|
+
// attrs, penalize both extras the candidate carries and cached attrs it is
|
|
40
|
+
// missing. Exact set equality scores highest; an all-attrs-empty comparison
|
|
41
|
+
// scores 0 for every candidate (so the tie-break falls through to proximity).
|
|
42
|
+
const attrScore = (node) => {
|
|
43
|
+
let shared = 0;
|
|
44
|
+
for (const a of node.attrs)
|
|
45
|
+
if (cachedAttrs.has(a))
|
|
46
|
+
shared += 1;
|
|
47
|
+
const extra = node.attrs.length - shared;
|
|
48
|
+
const missing = cachedAttrs.size - shared;
|
|
49
|
+
return shared * 2 - extra - missing;
|
|
50
|
+
};
|
|
51
|
+
let best = null;
|
|
52
|
+
let bestKey = null;
|
|
53
|
+
for (const node of freshNodeMap.values()) {
|
|
54
|
+
if (node.role !== role || node.name !== name)
|
|
55
|
+
continue;
|
|
56
|
+
// Lexicographic key, all "higher is better":
|
|
57
|
+
// [ DOM-backed, attribute similarity, -distance-to-original-ref ].
|
|
58
|
+
const key = [
|
|
59
|
+
node.backendNodeId !== undefined ? 1 : 0,
|
|
60
|
+
attrScore(node),
|
|
61
|
+
-Math.abs(node.ref - ref),
|
|
62
|
+
];
|
|
63
|
+
if (bestKey === null ||
|
|
64
|
+
key[0] > bestKey[0] ||
|
|
65
|
+
(key[0] === bestKey[0] && key[1] > bestKey[1]) ||
|
|
66
|
+
(key[0] === bestKey[0] && key[1] === bestKey[1] && key[2] > bestKey[2])) {
|
|
67
|
+
best = node;
|
|
68
|
+
bestKey = key;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return best ? best.ref : null;
|
|
72
|
+
}
|
|
1
73
|
const EDITOR_DETECT_FN = `(function() {
|
|
2
74
|
let el = this;
|
|
3
75
|
for (let i = 0; i < 5; i++) {
|
|
@@ -101,7 +173,7 @@ export async function getRefs(cdp, sessionId, opts = {}) {
|
|
|
101
173
|
: `- ${role}${nameStr} [ref=${ref}]${attrStr}${editorStr}`;
|
|
102
174
|
lines.push(line);
|
|
103
175
|
}
|
|
104
|
-
return { refs: lines.join('\n'), nodeMap };
|
|
176
|
+
return { refs: lines.join('\n'), nodeMap, opts: { interactive, limit } };
|
|
105
177
|
}
|
|
106
178
|
function truncate(s, max) {
|
|
107
179
|
if (s.length <= max)
|
|
@@ -269,6 +269,7 @@ function liveProcessCommand(pid) {
|
|
|
269
269
|
const out = execFileSync('tasklist', ['/FI', `PID eq ${pid}`, '/NH', '/FO', 'CSV'], {
|
|
270
270
|
encoding: 'utf-8',
|
|
271
271
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
272
|
+
windowsHide: true,
|
|
272
273
|
}).trim();
|
|
273
274
|
// Rows look like: "node.exe","1234","Console","1","12,345 K"
|
|
274
275
|
// A no-match prints an "INFO: No tasks..." line that won't match the regex.
|
|
@@ -43,6 +43,13 @@ export declare function pickWindowTarget<T extends {
|
|
|
43
43
|
export declare function parseSinceUntil(s: string): Date;
|
|
44
44
|
export declare function readNewestMatchingRemoteFileCommand(dir: string, prefix: string, tailLines: number): string;
|
|
45
45
|
export declare function readNewestMatchingFile(dir: string, prefix: string, tailLines: number): string;
|
|
46
|
+
/** Describes a ref that was re-resolved from a drifted integer via its cached descriptor. */
|
|
47
|
+
export interface HealInfo {
|
|
48
|
+
from: number;
|
|
49
|
+
to: number;
|
|
50
|
+
role: string;
|
|
51
|
+
name: string;
|
|
52
|
+
}
|
|
46
53
|
export declare class BrowserService {
|
|
47
54
|
private static readonly SOURCE_PREFIX;
|
|
48
55
|
private connections;
|
|
@@ -104,6 +111,17 @@ export declare class BrowserService {
|
|
|
104
111
|
width: number;
|
|
105
112
|
height: number;
|
|
106
113
|
}>;
|
|
114
|
+
/**
|
|
115
|
+
* Export the current tab as a PDF via CDP `Page.printToPDF`. Reuses the
|
|
116
|
+
* screenshot session + tab resolution so `--tab`, path sandboxing, and the
|
|
117
|
+
* auto-path (`sessions/<task>/<ts>.pdf`) all behave identically to
|
|
118
|
+
* `screenshot`. `printBackground: true` matches Chrome's default print flow
|
|
119
|
+
* — without it, dark-mode pages render on a blank sheet.
|
|
120
|
+
*/
|
|
121
|
+
printToPdf(taskId: string, tabHint?: string, outputPath?: string): Promise<{
|
|
122
|
+
path: string;
|
|
123
|
+
bytes: number;
|
|
124
|
+
}>;
|
|
107
125
|
private recordings;
|
|
108
126
|
recordStart(taskId: string, tabHint?: string, opts?: {
|
|
109
127
|
fps?: number;
|
|
@@ -126,12 +144,30 @@ export declare class BrowserService {
|
|
|
126
144
|
path?: string;
|
|
127
145
|
elapsedMs?: number;
|
|
128
146
|
}>;
|
|
129
|
-
private refsCache;
|
|
130
147
|
refs(taskId: string, tabHint?: string, opts?: RefOpts): Promise<{
|
|
131
148
|
refs: string;
|
|
132
149
|
nodeMap: Map<number, RefNode>;
|
|
133
150
|
}>;
|
|
134
|
-
|
|
151
|
+
/** Record the last ref listing (descriptors + opts) for a tab into state. */
|
|
152
|
+
private cacheRefDescriptors;
|
|
153
|
+
/**
|
|
154
|
+
* Re-resolve a caller-supplied ref against a freshly-built node map, healing
|
|
155
|
+
* it back to the right element when the integer ref has drifted since the
|
|
156
|
+
* cached `refs` listing. Shared by `click` and `type` so both self-heal
|
|
157
|
+
* identically. Returns the ref to act on plus, when a heal occurred, the
|
|
158
|
+
* {@link HealInfo} to surface. Throws when the cached element is gone.
|
|
159
|
+
*/
|
|
160
|
+
private resolveHealedRef;
|
|
161
|
+
click(taskId: string, ref: number, tabHint?: string): Promise<{
|
|
162
|
+
healed?: HealInfo;
|
|
163
|
+
}>;
|
|
164
|
+
/**
|
|
165
|
+
* Click raw viewport coordinates, bypassing ref resolution entirely. Backs
|
|
166
|
+
* `browser click --at X,Y` — the escape hatch when the accessibility tree
|
|
167
|
+
* exposes no usable ref (canvas apps, custom-drawn UI) and the caller has
|
|
168
|
+
* located the target from a screenshot.
|
|
169
|
+
*/
|
|
170
|
+
clickAt(taskId: string, x: number, y: number, tabHint?: string): Promise<void>;
|
|
135
171
|
type(taskId: string, ref: number, text: string, tabHint?: string, clear?: boolean): Promise<void>;
|
|
136
172
|
press(taskId: string, key: string, tabHint?: string): Promise<void>;
|
|
137
173
|
hover(taskId: string, ref: number, tabHint?: string): Promise<void>;
|