@phnx-labs/agents-cli 1.22.116 → 1.22.118
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 +247 -0
- package/dist/bootstrap.js +4 -11
- package/dist/commands/browser.d.ts +52 -70
- package/dist/commands/browser.js +192 -3562
- package/dist/commands/daemon.d.ts +39 -2
- package/dist/commands/daemon.js +72 -73
- package/dist/commands/events.js +1 -1
- package/dist/commands/exec.d.ts +18 -0
- package/dist/commands/exec.js +104 -12
- package/dist/commands/repo.js +13 -49
- package/dist/commands/route.js +5 -5
- package/dist/commands/routines.js +39 -12
- package/dist/commands/sessions-render.d.ts +1 -1
- package/dist/commands/sessions-render.js +0 -1
- package/dist/commands/sessions.test-fixture.d.ts +0 -1
- package/dist/commands/sessions.test-fixture.js +0 -37
- package/dist/commands/setup-browser.d.ts +17 -12
- package/dist/commands/setup-browser.js +52 -87
- package/dist/commands/setup-preferences.d.ts +0 -17
- package/dist/commands/setup-preferences.js +18 -49
- package/dist/commands/setup-term.d.ts +4 -3
- package/dist/commands/setup-term.js +5 -4
- package/dist/commands/setup.js +11 -11
- package/dist/commands/ssh.js +10 -1
- package/dist/commands/sync.js +6 -44
- package/dist/commands/teams.js +0 -1
- package/dist/commands/update.d.ts +19 -0
- package/dist/commands/update.js +34 -6
- package/dist/commands/view.d.ts +12 -1
- package/dist/commands/view.js +47 -2
- package/dist/index.js +107 -26
- package/dist/lib/account-capabilities.js +0 -2
- package/dist/lib/account-catalog.d.ts +38 -0
- package/dist/lib/account-catalog.js +110 -26
- package/dist/lib/account-provider-registry.js +1 -1
- package/dist/lib/account-registry.d.ts +1 -1
- package/dist/lib/account-registry.js +36 -2
- package/dist/lib/accounting/rotate.js +22 -8
- package/dist/lib/accounting/usage-ingest.d.ts +6 -1
- package/dist/lib/accounting/usage-ingest.js +115 -45
- package/dist/lib/accounting/usage-sync.d.ts +134 -31
- package/dist/lib/accounting/usage-sync.js +227 -66
- package/dist/lib/accounting/usage.d.ts +16 -3
- package/dist/lib/accounting/usage.js +26 -8
- package/dist/lib/accounts/slots.d.ts +8 -0
- package/dist/lib/accounts/slots.js +17 -0
- package/dist/lib/add-dir.js +0 -1
- package/dist/lib/agent-cli-commands.js +0 -1
- package/dist/lib/agent-spec/agents.d.ts +6 -7
- package/dist/lib/agent-spec/agents.js +6 -60
- package/dist/lib/auth-health.d.ts +69 -1
- package/dist/lib/auth-health.js +133 -4
- package/dist/lib/browser/context.d.ts +82 -0
- package/dist/lib/browser/context.js +88 -0
- package/dist/lib/browser/paths.d.ts +25 -0
- package/dist/lib/browser/paths.js +51 -0
- package/dist/lib/browser/record.d.ts +38 -0
- package/dist/lib/browser/record.js +57 -0
- package/dist/lib/browser/sessions-list.js +1 -2
- package/dist/lib/browser-client.d.ts +158 -0
- package/dist/lib/browser-client.js +234 -0
- package/dist/lib/channels/owner-sink.d.ts +1 -18
- package/dist/lib/channels/owner-sink.js +29 -82
- package/dist/lib/channels/providers/rush.d.ts +7 -7
- package/dist/lib/channels/providers/rush.js +127 -57
- package/dist/lib/claude-statusline.d.ts +1 -1
- package/dist/lib/claude-statusline.js +15 -3
- package/dist/lib/commands.js +1 -1
- package/dist/lib/crabbox/lease.js +1 -1
- package/dist/lib/crabbox/runtimes.js +1 -3
- package/dist/lib/daemon/account-state-daemon-service.js +1 -0
- package/dist/lib/daemon/auth-sync-service.d.ts +21 -18
- package/dist/lib/daemon/auth-sync-service.js +37 -43
- package/dist/lib/daemon/daemon.d.ts +25 -14
- package/dist/lib/daemon/daemon.js +92 -154
- package/dist/lib/daemon/runner.d.ts +2 -2
- package/dist/lib/daemon/runner.js +32 -9
- package/dist/lib/daemon/self-update-service.d.ts +35 -34
- package/dist/lib/daemon/self-update-service.js +40 -36
- package/dist/lib/daemon/service.d.ts +12 -8
- package/dist/lib/daemon/supervisor.d.ts +65 -54
- package/dist/lib/daemon/supervisor.js +136 -171
- package/dist/lib/daemon/testdata/process-view-start.js +5 -2
- package/dist/lib/daemon/usage-sync-service.d.ts +21 -30
- package/dist/lib/daemon/usage-sync-service.js +46 -78
- package/dist/lib/daemon-health.d.ts +22 -5
- package/dist/lib/daemon-health.js +47 -3
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +0 -10
- package/dist/lib/daemon-ticks.js +4 -2
- package/dist/lib/devices/doctor-findings.js +8 -11
- package/dist/lib/devices/harness-inventory.js +14 -5
- package/dist/lib/exec-account-home.js +5 -0
- package/dist/lib/exec.js +0 -11
- package/dist/lib/feed/events.js +0 -1
- package/dist/lib/feed/tool-activity.js +5 -2
- package/dist/lib/fleet-shared-state.d.ts +45 -0
- package/dist/lib/fleet-shared-state.js +78 -9
- package/dist/lib/fs-atomic.js +25 -0
- package/dist/lib/harness-auth-capabilities.js +0 -1
- package/dist/lib/helper-versions.js +1 -1
- package/dist/lib/hooks/cache.d.ts +7 -0
- package/dist/lib/hooks/cache.js +27 -2
- package/dist/lib/hooks/install.js +7 -1
- package/dist/lib/hosts/ready.d.ts +38 -8
- package/dist/lib/hosts/ready.js +62 -14
- package/dist/lib/hosts/remote-cmd.js +1 -0
- package/dist/lib/installations/active-check.d.ts +27 -0
- package/dist/lib/installations/active-check.js +61 -0
- package/dist/lib/installations/migrate.js +6 -3
- package/dist/lib/installations/shims.js +9 -7
- package/dist/lib/installations/update.js +21 -15
- package/dist/lib/installations/versions.js +12 -1
- package/dist/lib/manifest.js +1 -1
- package/dist/lib/models.js +0 -2
- package/dist/lib/open-url.js +28 -54
- package/dist/lib/placement.d.ts +2 -0
- package/dist/lib/placement.js +5 -0
- package/dist/lib/profiles.js +0 -2
- package/dist/lib/refresh.js +1 -1
- package/dist/lib/routine-readiness.d.ts +35 -0
- package/dist/lib/routine-readiness.js +74 -16
- package/dist/lib/rules/compile.js +6 -0
- package/dist/lib/sandbox.js +0 -2
- package/dist/lib/secrets-policy.d.ts +89 -11
- package/dist/lib/secrets-policy.js +175 -28
- package/dist/lib/session/db.d.ts +1 -1
- package/dist/lib/session/db.js +65 -18
- package/dist/lib/session/discover.js +1 -283
- package/dist/lib/session/mirror.d.ts +4 -3
- package/dist/lib/session/mirror.js +4 -3
- package/dist/lib/session/parse.d.ts +0 -2
- package/dist/lib/session/parse.js +1 -138
- package/dist/lib/session/throughput.d.ts +1 -3
- package/dist/lib/session/throughput.js +0 -20
- package/dist/lib/session/types.d.ts +2 -2
- package/dist/lib/session/types.js +2 -2
- package/dist/lib/sessions-client.d.ts +60 -1
- package/dist/lib/sessions-client.js +227 -4
- package/dist/lib/setup-tool-status.js +8 -12
- package/dist/lib/signin-badge.d.ts +3 -2
- package/dist/lib/signin-badge.js +6 -3
- package/dist/lib/smart-launch.js +4 -1
- package/dist/lib/staleness/detectors/commands.d.ts +1 -1
- package/dist/lib/staleness/detectors/hooks.d.ts +1 -1
- package/dist/lib/staleness/detectors/mcp.d.ts +1 -1
- package/dist/lib/staleness/detectors/permissions.d.ts +1 -1
- package/dist/lib/staleness/detectors/plugins.d.ts +1 -1
- package/dist/lib/staleness/detectors/rules.d.ts +1 -1
- package/dist/lib/staleness/detectors/skills.d.ts +1 -1
- package/dist/lib/staleness/detectors/subagents.d.ts +1 -1
- package/dist/lib/staleness/detectors/workflows.d.ts +1 -1
- package/dist/lib/staleness/writers/commands.d.ts +1 -1
- package/dist/lib/staleness/writers/hooks.d.ts +1 -1
- package/dist/lib/staleness/writers/mcp.d.ts +1 -1
- package/dist/lib/staleness/writers/permissions.d.ts +1 -1
- package/dist/lib/staleness/writers/plugins.d.ts +1 -1
- package/dist/lib/staleness/writers/rules.d.ts +1 -1
- package/dist/lib/staleness/writers/skills.d.ts +1 -1
- package/dist/lib/staleness/writers/sources.js +5 -0
- package/dist/lib/staleness/writers/subagents.d.ts +1 -1
- package/dist/lib/staleness/writers/workflows.d.ts +1 -1
- package/dist/lib/state.d.ts +6 -4
- package/dist/lib/state.js +7 -17
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/teams/parsers.js +1 -136
- package/dist/lib/teams/placement-probe.js +50 -15
- package/dist/lib/teams/scheduler.d.ts +7 -0
- package/dist/lib/types.d.ts +10 -1
- package/dist/lib/types.js +1 -1
- package/dist/lib/view-types.d.ts +32 -0
- package/dist/lib/watchdog/read.js +0 -1
- package/dist/lib/watchdog/rotate.js +1 -2
- package/dist/lib/watchdog/runner.js +2 -2
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdogTail.js +0 -31
- package/package.json +1 -1
- package/scripts/postinstall.js +8 -8
- package/dist/browser.d.ts +0 -2
- package/dist/browser.js +0 -17
- package/dist/commands/browser-picker.d.ts +0 -18
- package/dist/commands/browser-picker.js +0 -97
- package/dist/lib/browser/arc-discovery.d.ts +0 -52
- package/dist/lib/browser/arc-discovery.js +0 -197
- package/dist/lib/browser/arc-dom.d.ts +0 -14
- package/dist/lib/browser/arc-dom.js +0 -121
- package/dist/lib/browser/caller-identity.d.ts +0 -40
- package/dist/lib/browser/caller-identity.js +0 -175
- package/dist/lib/browser/cdp.d.ts +0 -51
- package/dist/lib/browser/cdp.js +0 -254
- package/dist/lib/browser/chrome.d.ts +0 -141
- package/dist/lib/browser/chrome.js +0 -704
- package/dist/lib/browser/chromium-discovery.d.ts +0 -28
- package/dist/lib/browser/chromium-discovery.js +0 -96
- package/dist/lib/browser/devices.d.ts +0 -34
- package/dist/lib/browser/devices.js +0 -61
- package/dist/lib/browser/domain-skills.d.ts +0 -71
- package/dist/lib/browser/domain-skills.js +0 -195
- package/dist/lib/browser/drivers/arc.d.ts +0 -40
- package/dist/lib/browser/drivers/arc.js +0 -276
- package/dist/lib/browser/drivers/firefox.d.ts +0 -99
- package/dist/lib/browser/drivers/firefox.js +0 -377
- package/dist/lib/browser/drivers/local.d.ts +0 -55
- package/dist/lib/browser/drivers/local.js +0 -266
- package/dist/lib/browser/drivers/ssh.d.ts +0 -120
- package/dist/lib/browser/drivers/ssh.js +0 -467
- package/dist/lib/browser/editor.d.ts +0 -3
- package/dist/lib/browser/editor.js +0 -50
- package/dist/lib/browser/ffmpeg.d.ts +0 -12
- package/dist/lib/browser/ffmpeg.js +0 -184
- package/dist/lib/browser/firefox-discovery.d.ts +0 -69
- package/dist/lib/browser/firefox-discovery.js +0 -162
- package/dist/lib/browser/har.d.ts +0 -85
- package/dist/lib/browser/har.js +0 -77
- package/dist/lib/browser/hygiene.d.ts +0 -97
- package/dist/lib/browser/hygiene.js +0 -153
- package/dist/lib/browser/index.d.ts +0 -5
- package/dist/lib/browser/index.js +0 -5
- package/dist/lib/browser/input.d.ts +0 -7
- package/dist/lib/browser/input.js +0 -92
- package/dist/lib/browser/ipc.d.ts +0 -204
- package/dist/lib/browser/ipc.js +0 -1453
- package/dist/lib/browser/login-detection.d.ts +0 -87
- package/dist/lib/browser/login-detection.js +0 -274
- package/dist/lib/browser/profiles.d.ts +0 -422
- package/dist/lib/browser/profiles.js +0 -1157
- package/dist/lib/browser/refs.d.ts +0 -89
- package/dist/lib/browser/refs.js +0 -191
- package/dist/lib/browser/registry.d.ts +0 -70
- package/dist/lib/browser/registry.js +0 -252
- package/dist/lib/browser/remote-control.d.ts +0 -59
- package/dist/lib/browser/remote-control.js +0 -85
- package/dist/lib/browser/resolve-target.d.ts +0 -66
- package/dist/lib/browser/resolve-target.js +0 -264
- package/dist/lib/browser/runtime-state.d.ts +0 -291
- package/dist/lib/browser/runtime-state.js +0 -584
- package/dist/lib/browser/secret-ref.d.ts +0 -10
- package/dist/lib/browser/secret-ref.js +0 -14
- package/dist/lib/browser/service.d.ts +0 -812
- package/dist/lib/browser/service.js +0 -4693
- package/dist/lib/browser/stream.d.ts +0 -17
- package/dist/lib/browser/stream.js +0 -72
- package/dist/lib/browser/task-index.d.ts +0 -78
- package/dist/lib/browser/task-index.js +0 -199
- package/dist/lib/browser/types.d.ts +0 -647
- package/dist/lib/browser/types.js +0 -61
- package/dist/lib/browser/upload.d.ts +0 -29
- package/dist/lib/browser/upload.js +0 -298
- package/dist/lib/daemon/browser-ipc-service.d.ts +0 -20
- package/dist/lib/daemon/browser-ipc-service.js +0 -46
- package/dist/lib/daemon/browser-task-reap-service.d.ts +0 -14
- package/dist/lib/daemon/browser-task-reap-service.js +0 -26
- package/dist/lib/fleet-shared-repo-sync.d.ts +0 -55
- package/dist/lib/fleet-shared-repo-sync.js +0 -490
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* paths.ts — the on-disk browser layout the CONSUMER still reads.
|
|
3
|
+
*
|
|
4
|
+
* browser-cli owns the browser process, drivers and IPC, but it keeps every
|
|
5
|
+
* on-disk path the in-repo subsystem used (integration contract §4). So
|
|
6
|
+
* `agents browser sessions` (`sessions-list.ts`) and the feed's browser tool
|
|
7
|
+
* rows (`feed/tool-activity.ts`) read a profile's captures straight off disk —
|
|
8
|
+
* no daemon, no engine — and need just these path helpers. They were the pure
|
|
9
|
+
* path corner of the deleted `profiles.ts` / `runtime-state.ts` engine files.
|
|
10
|
+
*
|
|
11
|
+
* A profile's live tasks and captures may live under a COMPOSITE runtime dir
|
|
12
|
+
* (`<profile>@<endpoint>`, `<profile>@<endpoint>.<fork>`), not the bare
|
|
13
|
+
* `<profile>` dir, so `listProfileCacheDirs` matches every dir that belongs to a
|
|
14
|
+
* profile — the same last-`@` rule browser-cli names them with.
|
|
15
|
+
*/
|
|
16
|
+
import * as path from 'node:path';
|
|
17
|
+
import * as fs from 'node:fs';
|
|
18
|
+
import { getBrowserRuntimeDir as getBrowserRuntimeDirRoot } from '../state.js';
|
|
19
|
+
export { getBrowserDurableDir } from '../state.js';
|
|
20
|
+
/** Root of the browser runtime cache — `~/.agents/.cache/browser/`. */
|
|
21
|
+
export function getBrowserRuntimeDir() {
|
|
22
|
+
return getBrowserRuntimeDirRoot();
|
|
23
|
+
}
|
|
24
|
+
/** The runtime cache dir for one profile's bare name. */
|
|
25
|
+
export function getProfileRuntimeDir(name) {
|
|
26
|
+
return path.join(getBrowserRuntimeDir(), name);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The profile a runtime-cache dir key belongs to. browser-cli keys a dir as
|
|
30
|
+
* `<profile>`, `<profile>@<endpoint>`, or `<profile>@<endpoint>.<fork>`. The
|
|
31
|
+
* profile name may itself contain `@` (`me@work`), so split on the LAST one.
|
|
32
|
+
*/
|
|
33
|
+
function profileOfCacheKey(key) {
|
|
34
|
+
const at = key.lastIndexOf('@');
|
|
35
|
+
return at === -1 ? key : key.slice(0, at);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Every runtime-cache dir that belongs to a profile, composite forms included.
|
|
39
|
+
* Empty when the root does not exist yet.
|
|
40
|
+
*/
|
|
41
|
+
export function listProfileCacheDirs(profileName) {
|
|
42
|
+
const root = getBrowserRuntimeDir();
|
|
43
|
+
if (!fs.existsSync(root))
|
|
44
|
+
return [];
|
|
45
|
+
const matches = [];
|
|
46
|
+
for (const entry of fs.readdirSync(root)) {
|
|
47
|
+
if (profileOfCacheKey(entry) === profileName)
|
|
48
|
+
matches.push(path.join(root, entry));
|
|
49
|
+
}
|
|
50
|
+
return matches;
|
|
51
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* record.ts — turn the engine's NDJSON action events (fd 4) into the durable
|
|
3
|
+
* `browser_sessions` row that `agents browser sessions` and `agents sessions
|
|
4
|
+
* --browser` read to link a browser task back to the agent session that drove it.
|
|
5
|
+
*
|
|
6
|
+
* WHY THIS STAYS HERE. `sessions.db` is agents-cli state. Handing the standalone
|
|
7
|
+
* engine a writer for it would have made it a second author of the session index
|
|
8
|
+
* — precisely the "one engine, one executor" rule the repo holds elsewhere.
|
|
9
|
+
* Instead the engine reports what it did on fd 4 and agents-cli, which owns the
|
|
10
|
+
* store, records it.
|
|
11
|
+
*
|
|
12
|
+
* Before PHNX-4101 this upsert ran INSIDE the browser daemon at task start
|
|
13
|
+
* (`BrowserService.start` → `recordBrowserSession`), with the identity resolved
|
|
14
|
+
* in the calling CLI and forwarded over IPC. The engine now streams that identity
|
|
15
|
+
* back on fd 4 instead, and this CLI process — the one the user actually ran, so
|
|
16
|
+
* the actor is right — performs the upsert. The behavior is unchanged; only the
|
|
17
|
+
* trigger moved from an IPC call to a line on a pipe.
|
|
18
|
+
*
|
|
19
|
+
* The feed's browser tool rows are projected from the on-disk captures and the
|
|
20
|
+
* live `tasks.json` (`lib/feed/tool-activity.ts`), not from a per-action event
|
|
21
|
+
* ledger, so — unlike `lib/computer/record.ts` — this emits no feed event: the
|
|
22
|
+
* durable row is the whole job, and every consumer already reads it.
|
|
23
|
+
*/
|
|
24
|
+
import type { BrowserActionEvent } from '../browser-client.js';
|
|
25
|
+
/**
|
|
26
|
+
* Record one action the engine performed. Never throws: the action already
|
|
27
|
+
* happened and already reported its own success or failure on the engine's
|
|
28
|
+
* stderr, so a bookkeeping failure must not turn a successful navigate into a
|
|
29
|
+
* failed command.
|
|
30
|
+
*
|
|
31
|
+
* Only an event that names a `task` AND a `profile` can be recorded — the
|
|
32
|
+
* `browser_sessions` row is keyed on `(profile, task)`. A task-less verb
|
|
33
|
+
* (`profiles list`, `status`) carries neither and is skipped, exactly as the old
|
|
34
|
+
* inline recorder only ran for task-scoped verbs.
|
|
35
|
+
*/
|
|
36
|
+
export declare function recordBrowserAction(event: BrowserActionEvent, opts?: {
|
|
37
|
+
device?: string;
|
|
38
|
+
}): void;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* record.ts — turn the engine's NDJSON action events (fd 4) into the durable
|
|
3
|
+
* `browser_sessions` row that `agents browser sessions` and `agents sessions
|
|
4
|
+
* --browser` read to link a browser task back to the agent session that drove it.
|
|
5
|
+
*
|
|
6
|
+
* WHY THIS STAYS HERE. `sessions.db` is agents-cli state. Handing the standalone
|
|
7
|
+
* engine a writer for it would have made it a second author of the session index
|
|
8
|
+
* — precisely the "one engine, one executor" rule the repo holds elsewhere.
|
|
9
|
+
* Instead the engine reports what it did on fd 4 and agents-cli, which owns the
|
|
10
|
+
* store, records it.
|
|
11
|
+
*
|
|
12
|
+
* Before PHNX-4101 this upsert ran INSIDE the browser daemon at task start
|
|
13
|
+
* (`BrowserService.start` → `recordBrowserSession`), with the identity resolved
|
|
14
|
+
* in the calling CLI and forwarded over IPC. The engine now streams that identity
|
|
15
|
+
* back on fd 4 instead, and this CLI process — the one the user actually ran, so
|
|
16
|
+
* the actor is right — performs the upsert. The behavior is unchanged; only the
|
|
17
|
+
* trigger moved from an IPC call to a line on a pipe.
|
|
18
|
+
*
|
|
19
|
+
* The feed's browser tool rows are projected from the on-disk captures and the
|
|
20
|
+
* live `tasks.json` (`lib/feed/tool-activity.ts`), not from a per-action event
|
|
21
|
+
* ledger, so — unlike `lib/computer/record.ts` — this emits no feed event: the
|
|
22
|
+
* durable row is the whole job, and every consumer already reads it.
|
|
23
|
+
*/
|
|
24
|
+
import { recordBrowserSession } from '../session/db.js';
|
|
25
|
+
import { resolveActor } from '../actor.js';
|
|
26
|
+
/**
|
|
27
|
+
* Record one action the engine performed. Never throws: the action already
|
|
28
|
+
* happened and already reported its own success or failure on the engine's
|
|
29
|
+
* stderr, so a bookkeeping failure must not turn a successful navigate into a
|
|
30
|
+
* failed command.
|
|
31
|
+
*
|
|
32
|
+
* Only an event that names a `task` AND a `profile` can be recorded — the
|
|
33
|
+
* `browser_sessions` row is keyed on `(profile, task)`. A task-less verb
|
|
34
|
+
* (`profiles list`, `status`) carries neither and is skipped, exactly as the old
|
|
35
|
+
* inline recorder only ran for task-scoped verbs.
|
|
36
|
+
*/
|
|
37
|
+
export function recordBrowserAction(event, opts = {}) {
|
|
38
|
+
if (typeof event.task !== 'string' || typeof event.profile !== 'string')
|
|
39
|
+
return;
|
|
40
|
+
// The driven machine. `host` is what a `--device` invocation stamps; `opts.device`
|
|
41
|
+
// is the fallback for an engine that drove the device this CLI resolved but did
|
|
42
|
+
// not echo it back. Both absent → a local run, recorded under this machine.
|
|
43
|
+
const machine = event.host ?? opts.device;
|
|
44
|
+
try {
|
|
45
|
+
recordBrowserSession({
|
|
46
|
+
task: event.task,
|
|
47
|
+
profile: event.profile,
|
|
48
|
+
sessionId: event.sessionId ?? process.env.AGENT_SESSION_ID ?? process.env.AGENTS_SESSION_ID,
|
|
49
|
+
launchId: event.launchId ?? process.env.AGENT_LAUNCH_ID,
|
|
50
|
+
actor: event.actor ?? resolveActor().id,
|
|
51
|
+
...(machine ? { machine } : {}),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Recording is best-effort; the action is already done.
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -20,8 +20,7 @@ import { formatBytes } from '../format.js';
|
|
|
20
20
|
// Re-exported for the picker, which lists artifacts from this module's rows.
|
|
21
21
|
export { formatBytes };
|
|
22
22
|
import * as path from 'path';
|
|
23
|
-
import { getBrowserRuntimeDir, getProfileRuntimeDir } from './
|
|
24
|
-
import { listProfileCacheDirs } from './runtime-state.js';
|
|
23
|
+
import { getBrowserRuntimeDir, getProfileRuntimeDir, listProfileCacheDirs } from './paths.js';
|
|
25
24
|
import { formatRelativeTime } from '../session/relative-time.js';
|
|
26
25
|
import { getSessionById, listBrowserSessionRecords, pruneToolSessions } from '../session/db.js';
|
|
27
26
|
import { listPidSessionEntries } from '../session/pid-registry.js';
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browser-client.ts — the ONE process client through which agents-cli talks to
|
|
3
|
+
* the standalone `browser` CLI (@phnx-labs/browser-cli, PHNX-4101).
|
|
4
|
+
*
|
|
5
|
+
* This is the agents-owned half of the browser extraction, and it is
|
|
6
|
+
* deliberately small. agents-cli no longer carries the CDP/BiDi/Arc drivers, a
|
|
7
|
+
* browser IPC service, a chrome-data/profile store, a network-capture pipeline,
|
|
8
|
+
* or the remote SSH driving loop — the standalone engine owns all of it, exactly
|
|
9
|
+
* as `secrets` took the keychain engine (PHNX-3989), `computer` took the
|
|
10
|
+
* accessibility engine (PHNX-4075) and `sessions` took the transcript engine
|
|
11
|
+
* (PHNX-4012). What stays here is what only the fleet CLI can know: which device
|
|
12
|
+
* a `--device` name resolves to, whether this machine consents to being driven,
|
|
13
|
+
* who the acting session is, and where an action must be recorded.
|
|
14
|
+
*
|
|
15
|
+
* THERE IS NO FALLBACK. A missing executable throws `BROWSER_BIN_MISSING` with
|
|
16
|
+
* install guidance (DIST-1) rather than silently driving a bundled engine —
|
|
17
|
+
* agents-cli has none to drive, and a fallback would re-couple the two release
|
|
18
|
+
* trains this extraction exists to separate.
|
|
19
|
+
*
|
|
20
|
+
* Transport — inherited-fd passthrough, not request/response (matches
|
|
21
|
+
* browser-cli's `bin/browser.cjs` launcher, which forwards fds 3-16):
|
|
22
|
+
*
|
|
23
|
+
* - stdio 0/1/2 are INHERITED. The engine owns the user's terminal: its
|
|
24
|
+
* stdout is the command's stdout, its `--json` is the command's `--json`,
|
|
25
|
+
* its prompts reach a real tty. agents-cli never re-formats engine output,
|
|
26
|
+
* which is what keeps the surface honest as the engine evolves.
|
|
27
|
+
* - fd 3 (`BROWSER_CONTEXT_FD`) carries ONE JSON object — the consumer context
|
|
28
|
+
* built by `lib/browser/context.ts` — written and closed immediately, so the
|
|
29
|
+
* engine reads to EOF and proceeds. The document is read once, before command
|
|
30
|
+
* parsing; a malformed one is a hard error, an absent one the standalone case.
|
|
31
|
+
* - fd 4 (`BROWSER_EVENTS_FD`) carries NDJSON action events back: one JSON
|
|
32
|
+
* object per line, each an action the engine actually performed. agents-cli
|
|
33
|
+
* turns those into feed events and `sessions --browser` history
|
|
34
|
+
* (`lib/browser/record.ts`). The engine may emit none; it must never block
|
|
35
|
+
* on this pipe.
|
|
36
|
+
*
|
|
37
|
+
* Both fds are anonymous pipes on the child's side, the same shape
|
|
38
|
+
* `computer-client.ts` and `secrets-client.ts` settled on after a named FIFO
|
|
39
|
+
* wedged macOS reads. The context is pushed rather than pulled so the engine
|
|
40
|
+
* needs no callback into agents-cli — one direction each way, no reentrancy.
|
|
41
|
+
*/
|
|
42
|
+
/** fd the engine reads its one-shot JSON context from. */
|
|
43
|
+
export declare const BROWSER_CONTEXT_FD = 3;
|
|
44
|
+
/** fd the engine writes NDJSON action events to. */
|
|
45
|
+
export declare const BROWSER_EVENTS_FD = 4;
|
|
46
|
+
export declare class BrowserClientError extends Error {
|
|
47
|
+
code: string;
|
|
48
|
+
constructor(code: string, message: string);
|
|
49
|
+
}
|
|
50
|
+
export declare function isBrowserClientError(err: unknown): err is BrowserClientError;
|
|
51
|
+
/**
|
|
52
|
+
* Accept only the real standalone `browser` executable, never agents-cli's own
|
|
53
|
+
* `browser` command shim (`exec "$AGENTS_BIN" browser`, `installations/shims.ts`)
|
|
54
|
+
* — resolving that would recurse into this process. `findInPath` already skips
|
|
55
|
+
* `~/.agents/.cache/shims`; this guard is the second line, mirroring
|
|
56
|
+
* `isStandaloneComputer` (agents-cli never shipped a `dist/browser.js` bin, but
|
|
57
|
+
* the check stays symmetric and future-proof against one).
|
|
58
|
+
*/
|
|
59
|
+
export declare function isStandaloneBrowser(bin: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the standalone executable. `BROWSER_BIN` wins so a dev build can be
|
|
62
|
+
* driven without touching PATH.
|
|
63
|
+
*
|
|
64
|
+
* Resolution uses `findInPath`, which skips `~/.agents/.cache/shims` — see
|
|
65
|
+
* `isStandaloneBrowser` for why that skip is load-bearing.
|
|
66
|
+
*/
|
|
67
|
+
export declare function resolveBrowserBin(): string;
|
|
68
|
+
/** True when the standalone `browser` CLI is resolvable — the readiness signal
|
|
69
|
+
* for `agents setup` without a spawn. Never throws. */
|
|
70
|
+
export declare function browserInstalled(): boolean;
|
|
71
|
+
/** A `.js`/`.cjs`/`.mjs` bin is run through this runtime; a real executable is exec'd directly. */
|
|
72
|
+
export declare function invocation(bin: string): {
|
|
73
|
+
command: string;
|
|
74
|
+
prefix: string[];
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* One action the engine performed, as it appears on the NDJSON events fd.
|
|
78
|
+
*
|
|
79
|
+
* This is the engine's wire shape (browser-cli integration contract §2), not a
|
|
80
|
+
* translation of it: the engine emits `{event: "browser.action", ts, command,
|
|
81
|
+
* invocationId, pid, task, profile, url, host, sessionId, launchId, actor}`.
|
|
82
|
+
* `command` — not `verb` — is the field that names the action, and it is what
|
|
83
|
+
* marks a line as an action event.
|
|
84
|
+
*/
|
|
85
|
+
export interface BrowserActionEvent {
|
|
86
|
+
/** Always `browser.action` on this stream. */
|
|
87
|
+
event?: string;
|
|
88
|
+
/** ISO timestamp the engine stamped. */
|
|
89
|
+
ts?: string;
|
|
90
|
+
/** The verb the engine ran (`navigate`, `screenshot`, `click`, …). */
|
|
91
|
+
command: string;
|
|
92
|
+
/** The engine's own id for this run — the grouping key for a session row. */
|
|
93
|
+
invocationId?: string;
|
|
94
|
+
/** The engine process's pid. */
|
|
95
|
+
pid?: number;
|
|
96
|
+
/** The browser task the action targeted. */
|
|
97
|
+
task?: string;
|
|
98
|
+
/** The browser profile the task runs under. */
|
|
99
|
+
profile?: string;
|
|
100
|
+
/** The page URL at the time of the action, when the engine reported one. */
|
|
101
|
+
url?: string;
|
|
102
|
+
/** The driven device for a `--device` invocation; absent when local. */
|
|
103
|
+
host?: string;
|
|
104
|
+
/** Identity, echoed back from the context this CLI handed the engine. */
|
|
105
|
+
sessionId?: string;
|
|
106
|
+
launchId?: string;
|
|
107
|
+
actor?: string;
|
|
108
|
+
/** Free-form detail the engine attaches. */
|
|
109
|
+
[key: string]: unknown;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Split a growing buffer into complete NDJSON lines. Pure so the framing rules —
|
|
113
|
+
* blank lines skipped, a non-JSON line dropped rather than crashing the CLI, a
|
|
114
|
+
* trailing partial line carried forward — are unit-testable without a spawn.
|
|
115
|
+
*
|
|
116
|
+
* A malformed line is dropped, not thrown: these events are telemetry riding
|
|
117
|
+
* alongside a user-visible action that already happened. Failing the command
|
|
118
|
+
* because its receipt was unreadable would be strictly worse than losing the
|
|
119
|
+
* receipt. The action itself already failed loud on its own channel if it failed.
|
|
120
|
+
*/
|
|
121
|
+
export declare function parseEventLines(buffer: string): {
|
|
122
|
+
events: BrowserActionEvent[];
|
|
123
|
+
rest: string;
|
|
124
|
+
};
|
|
125
|
+
interface RunBrowserOptions {
|
|
126
|
+
/** argv handed to the standalone, after the program name. */
|
|
127
|
+
argv: string[];
|
|
128
|
+
/** The consumer context serialized onto fd 3. */
|
|
129
|
+
context: unknown;
|
|
130
|
+
/** Called once per action event the engine reports on fd 4. */
|
|
131
|
+
onEvent?: (event: BrowserActionEvent) => void;
|
|
132
|
+
/**
|
|
133
|
+
* Capture the engine's stdout instead of inheriting the terminal.
|
|
134
|
+
*
|
|
135
|
+
* Used only where agents-cli must READ an answer rather than show it (the
|
|
136
|
+
* setup readiness probe polling `status --json`). Verbs never capture:
|
|
137
|
+
* re-printing engine output would make agents-cli a formatter for a surface
|
|
138
|
+
* it no longer owns.
|
|
139
|
+
*/
|
|
140
|
+
capture?: boolean;
|
|
141
|
+
}
|
|
142
|
+
interface RunBrowserResult {
|
|
143
|
+
exitCode: number;
|
|
144
|
+
/** Engine stdout, only when `capture` was set. */
|
|
145
|
+
stdout: string;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Run the standalone engine with the consumer context on fd 3 and the action
|
|
149
|
+
* event stream on fd 4. Resolves with the engine's exit code; the caller
|
|
150
|
+
* propagates it so `agents browser` exits exactly as the engine did.
|
|
151
|
+
*
|
|
152
|
+
* Throws `BROWSER_BIN_MISSING` when the standalone is not installed. Every other
|
|
153
|
+
* failure is the engine's own, reported on the inherited stderr.
|
|
154
|
+
*/
|
|
155
|
+
export declare function runBrowser(opts: RunBrowserOptions): Promise<RunBrowserResult>;
|
|
156
|
+
/** Test seam: drop the memoized bin so PATH fixtures can re-resolve. */
|
|
157
|
+
export declare function _resetBrowserClientForTest(): void;
|
|
158
|
+
export {};
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browser-client.ts — the ONE process client through which agents-cli talks to
|
|
3
|
+
* the standalone `browser` CLI (@phnx-labs/browser-cli, PHNX-4101).
|
|
4
|
+
*
|
|
5
|
+
* This is the agents-owned half of the browser extraction, and it is
|
|
6
|
+
* deliberately small. agents-cli no longer carries the CDP/BiDi/Arc drivers, a
|
|
7
|
+
* browser IPC service, a chrome-data/profile store, a network-capture pipeline,
|
|
8
|
+
* or the remote SSH driving loop — the standalone engine owns all of it, exactly
|
|
9
|
+
* as `secrets` took the keychain engine (PHNX-3989), `computer` took the
|
|
10
|
+
* accessibility engine (PHNX-4075) and `sessions` took the transcript engine
|
|
11
|
+
* (PHNX-4012). What stays here is what only the fleet CLI can know: which device
|
|
12
|
+
* a `--device` name resolves to, whether this machine consents to being driven,
|
|
13
|
+
* who the acting session is, and where an action must be recorded.
|
|
14
|
+
*
|
|
15
|
+
* THERE IS NO FALLBACK. A missing executable throws `BROWSER_BIN_MISSING` with
|
|
16
|
+
* install guidance (DIST-1) rather than silently driving a bundled engine —
|
|
17
|
+
* agents-cli has none to drive, and a fallback would re-couple the two release
|
|
18
|
+
* trains this extraction exists to separate.
|
|
19
|
+
*
|
|
20
|
+
* Transport — inherited-fd passthrough, not request/response (matches
|
|
21
|
+
* browser-cli's `bin/browser.cjs` launcher, which forwards fds 3-16):
|
|
22
|
+
*
|
|
23
|
+
* - stdio 0/1/2 are INHERITED. The engine owns the user's terminal: its
|
|
24
|
+
* stdout is the command's stdout, its `--json` is the command's `--json`,
|
|
25
|
+
* its prompts reach a real tty. agents-cli never re-formats engine output,
|
|
26
|
+
* which is what keeps the surface honest as the engine evolves.
|
|
27
|
+
* - fd 3 (`BROWSER_CONTEXT_FD`) carries ONE JSON object — the consumer context
|
|
28
|
+
* built by `lib/browser/context.ts` — written and closed immediately, so the
|
|
29
|
+
* engine reads to EOF and proceeds. The document is read once, before command
|
|
30
|
+
* parsing; a malformed one is a hard error, an absent one the standalone case.
|
|
31
|
+
* - fd 4 (`BROWSER_EVENTS_FD`) carries NDJSON action events back: one JSON
|
|
32
|
+
* object per line, each an action the engine actually performed. agents-cli
|
|
33
|
+
* turns those into feed events and `sessions --browser` history
|
|
34
|
+
* (`lib/browser/record.ts`). The engine may emit none; it must never block
|
|
35
|
+
* on this pipe.
|
|
36
|
+
*
|
|
37
|
+
* Both fds are anonymous pipes on the child's side, the same shape
|
|
38
|
+
* `computer-client.ts` and `secrets-client.ts` settled on after a named FIFO
|
|
39
|
+
* wedged macOS reads. The context is pushed rather than pulled so the engine
|
|
40
|
+
* needs no callback into agents-cli — one direction each way, no reentrancy.
|
|
41
|
+
*/
|
|
42
|
+
import { spawn } from 'node:child_process';
|
|
43
|
+
import { realpathSync, existsSync } from 'node:fs';
|
|
44
|
+
import * as path from 'node:path';
|
|
45
|
+
import { findInPath } from './agent-spec/agents.js';
|
|
46
|
+
const INSTALL_HINT = 'npm i -g @phnx-labs/browser-cli';
|
|
47
|
+
/** fd the engine reads its one-shot JSON context from. */
|
|
48
|
+
export const BROWSER_CONTEXT_FD = 3;
|
|
49
|
+
/** fd the engine writes NDJSON action events to. */
|
|
50
|
+
export const BROWSER_EVENTS_FD = 4;
|
|
51
|
+
export class BrowserClientError extends Error {
|
|
52
|
+
code;
|
|
53
|
+
constructor(code, message) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.code = code;
|
|
56
|
+
this.name = 'BrowserClientError';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function isBrowserClientError(err) {
|
|
60
|
+
return err instanceof BrowserClientError;
|
|
61
|
+
}
|
|
62
|
+
let cachedBin;
|
|
63
|
+
function browserEntrypoint(bin) {
|
|
64
|
+
if (!/\.(cmd|ps1)$/i.test(bin))
|
|
65
|
+
return bin;
|
|
66
|
+
const launcher = path.join(path.dirname(bin), 'node_modules', '@phnx-labs', 'browser-cli', 'bin', 'browser.cjs');
|
|
67
|
+
return existsSync(launcher) ? launcher : bin;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Accept only the real standalone `browser` executable, never agents-cli's own
|
|
71
|
+
* `browser` command shim (`exec "$AGENTS_BIN" browser`, `installations/shims.ts`)
|
|
72
|
+
* — resolving that would recurse into this process. `findInPath` already skips
|
|
73
|
+
* `~/.agents/.cache/shims`; this guard is the second line, mirroring
|
|
74
|
+
* `isStandaloneComputer` (agents-cli never shipped a `dist/browser.js` bin, but
|
|
75
|
+
* the check stays symmetric and future-proof against one).
|
|
76
|
+
*/
|
|
77
|
+
export function isStandaloneBrowser(bin) {
|
|
78
|
+
let real = browserEntrypoint(bin);
|
|
79
|
+
try {
|
|
80
|
+
real = realpathSync(real);
|
|
81
|
+
}
|
|
82
|
+
catch { /* spawn reports missing explicit paths */ }
|
|
83
|
+
return !/\.(cmd|ps1)$/i.test(real) && !real.endsWith(path.join('dist', 'browser.js'));
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Resolve the standalone executable. `BROWSER_BIN` wins so a dev build can be
|
|
87
|
+
* driven without touching PATH.
|
|
88
|
+
*
|
|
89
|
+
* Resolution uses `findInPath`, which skips `~/.agents/.cache/shims` — see
|
|
90
|
+
* `isStandaloneBrowser` for why that skip is load-bearing.
|
|
91
|
+
*/
|
|
92
|
+
export function resolveBrowserBin() {
|
|
93
|
+
if (cachedBin)
|
|
94
|
+
return cachedBin;
|
|
95
|
+
const explicit = process.env.BROWSER_BIN?.trim();
|
|
96
|
+
const resolved = explicit && explicit.length > 0 ? (isStandaloneBrowser(explicit) ? explicit : null) : findInPath('browser', { accept: isStandaloneBrowser });
|
|
97
|
+
if (!resolved) {
|
|
98
|
+
throw new BrowserClientError('BROWSER_BIN_MISSING', 'The standalone `browser` CLI was not found. Install it with:\n' +
|
|
99
|
+
` ${INSTALL_HINT}\n` +
|
|
100
|
+
'or run `agents setup browser` / `agents clis install browser`, or point $BROWSER_BIN at its executable.');
|
|
101
|
+
}
|
|
102
|
+
cachedBin = browserEntrypoint(resolved);
|
|
103
|
+
return cachedBin;
|
|
104
|
+
}
|
|
105
|
+
/** True when the standalone `browser` CLI is resolvable — the readiness signal
|
|
106
|
+
* for `agents setup` without a spawn. Never throws. */
|
|
107
|
+
export function browserInstalled() {
|
|
108
|
+
try {
|
|
109
|
+
resolveBrowserBin();
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** A `.js`/`.cjs`/`.mjs` bin is run through this runtime; a real executable is exec'd directly. */
|
|
117
|
+
export function invocation(bin) {
|
|
118
|
+
if (/\.[mc]?js$/.test(bin))
|
|
119
|
+
return { command: process.execPath, prefix: [bin] };
|
|
120
|
+
return { command: bin, prefix: [] };
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Split a growing buffer into complete NDJSON lines. Pure so the framing rules —
|
|
124
|
+
* blank lines skipped, a non-JSON line dropped rather than crashing the CLI, a
|
|
125
|
+
* trailing partial line carried forward — are unit-testable without a spawn.
|
|
126
|
+
*
|
|
127
|
+
* A malformed line is dropped, not thrown: these events are telemetry riding
|
|
128
|
+
* alongside a user-visible action that already happened. Failing the command
|
|
129
|
+
* because its receipt was unreadable would be strictly worse than losing the
|
|
130
|
+
* receipt. The action itself already failed loud on its own channel if it failed.
|
|
131
|
+
*/
|
|
132
|
+
export function parseEventLines(buffer) {
|
|
133
|
+
const events = [];
|
|
134
|
+
const parts = buffer.split('\n');
|
|
135
|
+
const rest = parts.pop() ?? '';
|
|
136
|
+
for (const line of parts) {
|
|
137
|
+
const trimmed = line.trim();
|
|
138
|
+
if (!trimmed)
|
|
139
|
+
continue;
|
|
140
|
+
try {
|
|
141
|
+
const parsed = JSON.parse(trimmed);
|
|
142
|
+
if (parsed && typeof parsed === 'object' && typeof parsed.command === 'string') {
|
|
143
|
+
events.push(parsed);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// Unreadable receipt — see above.
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { events, rest };
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Run the standalone engine with the consumer context on fd 3 and the action
|
|
154
|
+
* event stream on fd 4. Resolves with the engine's exit code; the caller
|
|
155
|
+
* propagates it so `agents browser` exits exactly as the engine did.
|
|
156
|
+
*
|
|
157
|
+
* Throws `BROWSER_BIN_MISSING` when the standalone is not installed. Every other
|
|
158
|
+
* failure is the engine's own, reported on the inherited stderr.
|
|
159
|
+
*/
|
|
160
|
+
export async function runBrowser(opts) {
|
|
161
|
+
const bin = resolveBrowserBin();
|
|
162
|
+
const { command, prefix } = invocation(bin);
|
|
163
|
+
const child = spawn(command, [...prefix, ...opts.argv], {
|
|
164
|
+
stdio: ['inherit', opts.capture ? 'pipe' : 'inherit', 'inherit', 'pipe', 'pipe'],
|
|
165
|
+
env: {
|
|
166
|
+
...process.env,
|
|
167
|
+
BROWSER_CONTEXT_FD: String(BROWSER_CONTEXT_FD),
|
|
168
|
+
BROWSER_EVENTS_FD: String(BROWSER_EVENTS_FD),
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
const contextPipe = child.stdio[BROWSER_CONTEXT_FD];
|
|
172
|
+
const eventsPipe = child.stdio[BROWSER_EVENTS_FD];
|
|
173
|
+
if (contextPipe) {
|
|
174
|
+
// An engine that exits before reading the context (bad argv, `--help`)
|
|
175
|
+
// closes fd 3 and we get EPIPE. That is a normal race, not a failure.
|
|
176
|
+
contextPipe.on('error', () => { });
|
|
177
|
+
contextPipe.end(JSON.stringify(opts.context) + '\n');
|
|
178
|
+
}
|
|
179
|
+
let stdout = '';
|
|
180
|
+
if (opts.capture && child.stdout) {
|
|
181
|
+
child.stdout.setEncoding('utf-8');
|
|
182
|
+
child.stdout.on('data', (chunk) => {
|
|
183
|
+
stdout += chunk;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
let pending = '';
|
|
187
|
+
const drained = new Promise((resolve) => {
|
|
188
|
+
if (!eventsPipe)
|
|
189
|
+
return resolve();
|
|
190
|
+
eventsPipe.setEncoding('utf-8');
|
|
191
|
+
eventsPipe.on('data', (chunk) => {
|
|
192
|
+
const { events, rest } = parseEventLines(pending + chunk);
|
|
193
|
+
pending = rest;
|
|
194
|
+
for (const event of events)
|
|
195
|
+
opts.onEvent?.(event);
|
|
196
|
+
});
|
|
197
|
+
eventsPipe.on('error', () => resolve());
|
|
198
|
+
eventsPipe.on('end', () => {
|
|
199
|
+
// A final line with no trailing newline still counts.
|
|
200
|
+
const { events } = parseEventLines(pending.endsWith('\n') ? pending : pending + '\n');
|
|
201
|
+
pending = '';
|
|
202
|
+
for (const event of events)
|
|
203
|
+
opts.onEvent?.(event);
|
|
204
|
+
resolve();
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
const exitCode = await new Promise((resolve, reject) => {
|
|
208
|
+
child.on('error', (err) => {
|
|
209
|
+
reject(new BrowserClientError('BROWSER_SPAWN_FAILED', `Could not run \`${bin}\`: ${err.message}`));
|
|
210
|
+
});
|
|
211
|
+
child.on('close', (code, signal) => {
|
|
212
|
+
// A signalled child has no exit code; report the conventional 128+n so
|
|
213
|
+
// callers and shells see a non-zero status instead of a false success.
|
|
214
|
+
if (code == null)
|
|
215
|
+
return resolve(signal ? 128 + (osSignalNumber(signal) ?? 0) : 1);
|
|
216
|
+
resolve(code);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
await drained;
|
|
220
|
+
return { exitCode, stdout };
|
|
221
|
+
}
|
|
222
|
+
/** Signal name → number for the 128+n exit convention. Only the ones a tunnelled
|
|
223
|
+
* or interrupted engine realistically dies on; anything else contributes 0 and
|
|
224
|
+
* still yields a non-zero status. */
|
|
225
|
+
function osSignalNumber(signal) {
|
|
226
|
+
const table = {
|
|
227
|
+
SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGKILL: 9, SIGPIPE: 13, SIGTERM: 15,
|
|
228
|
+
};
|
|
229
|
+
return table[signal];
|
|
230
|
+
}
|
|
231
|
+
/** Test seam: drop the memoized bin so PATH fixtures can re-resolve. */
|
|
232
|
+
export function _resetBrowserClientForTest() {
|
|
233
|
+
cachedBin = undefined;
|
|
234
|
+
}
|
|
@@ -1,27 +1,10 @@
|
|
|
1
1
|
import type { Meta } from '../types.js';
|
|
2
|
-
|
|
3
|
-
export type OwnerSinkReason = 'rush-not-on-path' | 'rush-signed-out';
|
|
2
|
+
export type OwnerSinkReason = 'imessage-not-macos' | 'slack-no-token' | 'channel-unsupported';
|
|
4
3
|
export interface OwnerSinkStatus {
|
|
5
|
-
/** Owner delivery is configured for this fleet (humans.yaml / notify.owner).
|
|
6
|
-
* When false, no finding is emitted — an un-opted-in box is not "broken". */
|
|
7
4
|
configured: boolean;
|
|
8
|
-
/** This box can actually deliver an owner notification right now. */
|
|
9
5
|
reachable: boolean;
|
|
10
|
-
/** Resolved owner channel (e.g. `imessage`). */
|
|
11
6
|
channel?: string;
|
|
12
|
-
/** Resolved transport after `notify.transports` mapping (usually === channel). */
|
|
13
7
|
transport?: string;
|
|
14
|
-
/** Set only when `reachable` is false. */
|
|
15
8
|
reason?: OwnerSinkReason;
|
|
16
9
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Probe whether THIS box can deliver an owner notification right now. Returns
|
|
19
|
-
* `configured:false` (and the caller emits no finding) when the fleet has no
|
|
20
|
-
* owner channel configured — an un-opted-in box is not broken. When configured,
|
|
21
|
-
* reports whether the resolved transport can actually deliver from here.
|
|
22
|
-
*
|
|
23
|
-
* Only the concrete rush-backed failure the lane hits is reported as unreachable;
|
|
24
|
-
* non-rush transports (desktop / mailbox / …) deliver locally and are treated as
|
|
25
|
-
* reachable rather than probed, so this never invents a critical it cannot back.
|
|
26
|
-
*/
|
|
27
10
|
export declare function probeOwnerSink(meta: Meta): Promise<OwnerSinkStatus>;
|