@phnx-labs/agents-cli 1.22.117 → 1.22.119
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 +301 -0
- package/dist/bootstrap.js +4 -11
- package/dist/commands/accounts.js +7 -4
- 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.js +53 -1
- package/dist/commands/projects.js +92 -7
- 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.js +9 -9
- 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/add.d.ts +3 -1
- package/dist/lib/accounts/add.js +24 -13
- 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 +7 -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-account-token.d.ts +7 -1
- package/dist/lib/claude-account-token.js +26 -4
- 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 +10 -13
- 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/github/pr-mergeable.d.ts +4 -2
- package/dist/lib/github/pr-mergeable.js +5 -3
- package/dist/lib/github/project-prs.d.ts +58 -3
- package/dist/lib/github/project-prs.js +205 -7
- package/dist/lib/harness/adapters/cursor.js +14 -4
- package/dist/lib/harness-auth-capabilities.d.ts +4 -3
- package/dist/lib/harness-auth-capabilities.js +1 -2
- 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/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.d.ts +1 -1
- package/dist/lib/installations/shims.js +32 -12
- package/dist/lib/installations/store.d.ts +9 -0
- package/dist/lib/installations/store.js +24 -0
- package/dist/lib/installations/update.js +21 -15
- package/dist/lib/installations/versions.js +15 -3
- 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/profiles.js +0 -2
- package/dist/lib/projects.d.ts +16 -0
- package/dist/lib/projects.js +31 -0
- 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/active.d.ts +17 -6
- package/dist/lib/session/active.js +105 -49
- 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/pid-registry.d.ts +6 -3
- package/dist/lib/session/pid-registry.js +18 -8
- package/dist/lib/session/remote/watch.d.ts +1 -1
- package/dist/lib/session/remote/watch.js +7 -3
- 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 +5 -10
- 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 +16 -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
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Abandoned browser-task reaper (RUSH-2622).
|
|
3
|
-
*
|
|
4
|
-
* `agents browser done` / `stop` already close a task's tabs, but agents
|
|
5
|
-
* routinely never call them — the run ends, the process exits, and the task's
|
|
6
|
-
* tabs stay open in the profile window forever. Over a day of fleet activity
|
|
7
|
-
* that is dozens of leftover tabs in Comet/Chrome.
|
|
8
|
-
*
|
|
9
|
-
* This closes the loop from the other end: a periodic pass that finds tasks
|
|
10
|
-
* nobody is driving any more and stops them.
|
|
11
|
-
*
|
|
12
|
-
* Two independent reasons, both conservative:
|
|
13
|
-
*
|
|
14
|
-
* - `session-dead` — the task recorded WHICH agent session (`sessionId`) or
|
|
15
|
-
* WHICH run (`launchId`) started it, and neither is alive on this host.
|
|
16
|
-
* That is the honest end of the task: the thing that would have called
|
|
17
|
-
* `done` no longer exists.
|
|
18
|
-
* - `idle` — nothing has touched the task for `idleMs` (default 30 minutes).
|
|
19
|
-
* The catch-all for a task with no recorded identity (a human running
|
|
20
|
-
* `agents browser start` by hand) or an agent that stalled without exiting.
|
|
21
|
-
*
|
|
22
|
-
* Closing always goes through `BrowserService.stop`, never a direct
|
|
23
|
-
* `Target.closeTarget`, so history, the session cache, the target cache, and
|
|
24
|
-
* forked-profile teardown all stay on the one code path that already handles
|
|
25
|
-
* them. Two things this deliberately never does: touch a tab that is not in
|
|
26
|
-
* `task.tabs` (a tab the user opened themselves is not ours to close), and kill
|
|
27
|
-
* the profile window or the browser process because one task went idle.
|
|
28
|
-
*/
|
|
29
|
-
import { isPidAlive, isSessionIdLiveOnProcessTable } from '../session/active.js';
|
|
30
|
-
import { listPidSessionEntries, sessionIdFromLivePid } from '../session/pid-registry.js';
|
|
31
|
-
import { parseConnectionKey } from './types.js';
|
|
32
|
-
/** Default idle window before an untouched task is reaped. */
|
|
33
|
-
const DEFAULT_IDLE_MS = 30 * 60_000;
|
|
34
|
-
/**
|
|
35
|
-
* Session and launch ids belonging to a process that is alive right now.
|
|
36
|
-
*
|
|
37
|
-
* Built from the per-pid launch registry, filtered to live pids —
|
|
38
|
-
* `isPidAlive(pid, startedAtMs)` rather than a bare existence check, so a pid
|
|
39
|
-
* the OS recycled onto an unrelated process does not read as a live agent.
|
|
40
|
-
* An entry with no recorded `sessionId` still contributes one when the live
|
|
41
|
-
* process carries `--session-id` on its argv, which is the RUSH-2384 recovery
|
|
42
|
-
* path the registry itself documents (pid-registry.ts:102-107).
|
|
43
|
-
*/
|
|
44
|
-
export function resolveLiveIdentities(deps = {}) {
|
|
45
|
-
const listEntries = deps.listEntries ?? listPidSessionEntries;
|
|
46
|
-
const alive = deps.pidAlive ?? isPidAlive;
|
|
47
|
-
const sessionIdOf = deps.sessionIdOfPid ?? sessionIdFromLivePid;
|
|
48
|
-
const sessions = new Set();
|
|
49
|
-
const launches = new Set();
|
|
50
|
-
for (const entry of listEntries()) {
|
|
51
|
-
if (!alive(entry.pid, entry.startedAtMs))
|
|
52
|
-
continue;
|
|
53
|
-
const sessionId = entry.sessionId ?? sessionIdOf(entry.pid);
|
|
54
|
-
if (sessionId)
|
|
55
|
-
sessions.add(sessionId);
|
|
56
|
-
if (entry.launchId)
|
|
57
|
-
launches.add(entry.launchId);
|
|
58
|
-
}
|
|
59
|
-
return { sessions, launches };
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* True when the task's owner is PROVABLY gone. The bar is proof, not absence of
|
|
63
|
-
* evidence, because being wrong here closes a working agent's tabs.
|
|
64
|
-
*
|
|
65
|
-
* Only a `sessionId` can carry that proof. It has two independent sources — the
|
|
66
|
-
* per-pid launch registry, and a live process carrying `--session-id <id>` in
|
|
67
|
-
* its argv — so a session the registry missed is still caught by the process
|
|
68
|
-
* table. The registry misses constantly: a wrapper pid exits, a prune sweeps
|
|
69
|
-
* the entry, or the agent was never launched via `agents run`
|
|
70
|
-
* (pid-registry.ts:102-107, RUSH-2384).
|
|
71
|
-
*
|
|
72
|
-
* A `launchId` has NO second source — the registry is its only witness, and
|
|
73
|
-
* that witness is the unreliable one. So a task carrying only a `launchId` is
|
|
74
|
-
* never session-reaped, exactly like a task carrying no identity at all; both
|
|
75
|
-
* fall through to the idle rule. This is not a corner case: `launchId` is
|
|
76
|
-
* minted for every run (`exec.ts` `resolveLaunchId`) while `AGENT_SESSION_ID`
|
|
77
|
-
* is Claude-only and skipped on resume, so treating a missing registry entry as
|
|
78
|
-
* proof of death would close the tabs of every live codex/droid/grok run whose
|
|
79
|
-
* launch pid had already exited.
|
|
80
|
-
*
|
|
81
|
-
* A live `launchId` still RESCUES a task whose `sessionId` looks dead — proof of
|
|
82
|
-
* life needs only one witness, unlike proof of death.
|
|
83
|
-
*/
|
|
84
|
-
export async function taskOwnerIsGone(task, live, deps = {}) {
|
|
85
|
-
if (!task.sessionId)
|
|
86
|
-
return false;
|
|
87
|
-
if (task.launchId && live.launches.has(task.launchId))
|
|
88
|
-
return false;
|
|
89
|
-
if (live.sessions.has(task.sessionId))
|
|
90
|
-
return false;
|
|
91
|
-
const onProcessTable = deps.sessionLiveOnProcessTable ?? ((id) => isSessionIdLiveOnProcessTable(id));
|
|
92
|
-
return !(await onProcessTable(task.sessionId));
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Stop every task whose owner is gone or that has sat untouched past `idleMs`.
|
|
96
|
-
*
|
|
97
|
-
* Returns what it closed and how many it left alone. A task that is mid-
|
|
98
|
-
* recording is always left alone: reaping it would truncate a capture the user
|
|
99
|
-
* asked for, and an in-flight recording is itself proof the task is in use.
|
|
100
|
-
*/
|
|
101
|
-
export async function reapAbandonedTasks(service, opts = {}) {
|
|
102
|
-
const idleDisabled = opts.idleMs === null;
|
|
103
|
-
const idleMs = idleDisabled ? undefined : (opts.idleMs ?? DEFAULT_IDLE_MS);
|
|
104
|
-
// Fail loud rather than reap everything. A caller-supplied `0` survives `??`
|
|
105
|
-
// and would close every task including one created a millisecond ago; a
|
|
106
|
-
// non-numeric value makes every `>=` comparison false and silently disables
|
|
107
|
-
// idle reaping. Both are worse than an error. `null` (idleDisabled) skips
|
|
108
|
-
// this check entirely — that is the one deliberate way to turn idle
|
|
109
|
-
// reaping off, distinct from an accidental `0`.
|
|
110
|
-
if (!idleDisabled && (!Number.isFinite(idleMs) || idleMs <= 0)) {
|
|
111
|
-
throw new Error(`idleMs must be a positive number of milliseconds, got ${String(idleMs)}`);
|
|
112
|
-
}
|
|
113
|
-
const now = opts.now ?? Date.now();
|
|
114
|
-
const deps = opts.deps ?? {};
|
|
115
|
-
const live = resolveLiveIdentities(deps);
|
|
116
|
-
const closed = [];
|
|
117
|
-
let skipped = 0;
|
|
118
|
-
for (const { profile, task } of service.listTasks()) {
|
|
119
|
-
if ((await service.recordStatus(task.name)).recording) {
|
|
120
|
-
skipped++;
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
let reason;
|
|
124
|
-
if (await taskOwnerIsGone(task, live, deps)) {
|
|
125
|
-
reason = 'session-dead';
|
|
126
|
-
}
|
|
127
|
-
else if (!idleDisabled && now - (task.lastActionAt ?? task.createdAt) >= idleMs) {
|
|
128
|
-
reason = 'idle';
|
|
129
|
-
}
|
|
130
|
-
if (!reason) {
|
|
131
|
-
skipped++;
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
if (opts.dryRun) {
|
|
135
|
-
// Report the BARE profile, exactly as the real run does below — `profile`
|
|
136
|
-
// here is the runtime key from `listTasks()`, and printing the two shapes
|
|
137
|
-
// in one column made `gc --dry-run` and `gc` disagree (RUSH-2709).
|
|
138
|
-
closed.push({ task: task.name, profile: parseConnectionKey(profile).profile, reason });
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
const result = await service.stop(task.name);
|
|
142
|
-
if (result.ok) {
|
|
143
|
-
closed.push({ task: task.name, profile: result.profile ?? profile, reason });
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
// `stop` reports not-found only when the task already left the map — a
|
|
147
|
-
// concurrent `done`, or a profile torn down mid-pass. Nothing was closed,
|
|
148
|
-
// so it is not reported as closed.
|
|
149
|
-
skipped++;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return { closed, skipped };
|
|
153
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { CDPClient } from './cdp.js';
|
|
2
|
-
export declare function clickAtCoords(cdp: CDPClient, sessionId: string, x: number, y: number): Promise<void>;
|
|
3
|
-
export declare function hoverAtCoords(cdp: CDPClient, sessionId: string, x: number, y: number): Promise<void>;
|
|
4
|
-
export declare function scrollAtCoords(cdp: CDPClient, sessionId: string, x: number, y: number, deltaX: number, deltaY: number): Promise<void>;
|
|
5
|
-
export declare function typeText(cdp: CDPClient, sessionId: string, text: string): Promise<void>;
|
|
6
|
-
export declare function pressKey(cdp: CDPClient, sessionId: string, keyName: string): Promise<void>;
|
|
7
|
-
export declare function focusNode(cdp: CDPClient, sessionId: string, backendNodeId: number): Promise<void>;
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
export async function clickAtCoords(cdp, sessionId, x, y) {
|
|
2
|
-
await cdp.send('Input.dispatchMouseEvent', { type: 'mousePressed', x, y, button: 'left', clickCount: 1 }, sessionId);
|
|
3
|
-
await cdp.send('Input.dispatchMouseEvent', { type: 'mouseReleased', x, y, button: 'left', clickCount: 1 }, sessionId);
|
|
4
|
-
}
|
|
5
|
-
export async function hoverAtCoords(cdp, sessionId, x, y) {
|
|
6
|
-
await cdp.send('Input.dispatchMouseEvent', { type: 'mouseMoved', x, y }, sessionId);
|
|
7
|
-
}
|
|
8
|
-
export async function scrollAtCoords(cdp, sessionId, x, y, deltaX, deltaY) {
|
|
9
|
-
await cdp.send('Input.dispatchMouseEvent', { type: 'mouseWheel', x, y, deltaX, deltaY }, sessionId);
|
|
10
|
-
}
|
|
11
|
-
// `Input.insertText` is the CDP native text-insertion method. It dispatches a
|
|
12
|
-
// real `beforeinput`/`input`/`textInput` sequence on the focused element, which
|
|
13
|
-
// is what framework-controlled inputs (React, Vue, Solid, contenteditable
|
|
14
|
-
// editors) actually listen for. Per-character `dispatchKeyEvent` only fires
|
|
15
|
-
// `keydown`/`keyup` with no input event, so controlled inputs ignore it.
|
|
16
|
-
export async function typeText(cdp, sessionId, text) {
|
|
17
|
-
await cdp.send('Input.insertText', { text }, sessionId);
|
|
18
|
-
}
|
|
19
|
-
const KEY_CODES = {
|
|
20
|
-
Enter: { key: 'Enter', code: 'Enter', keyCode: 13 },
|
|
21
|
-
Tab: { key: 'Tab', code: 'Tab', keyCode: 9 },
|
|
22
|
-
Escape: { key: 'Escape', code: 'Escape', keyCode: 27 },
|
|
23
|
-
Backspace: { key: 'Backspace', code: 'Backspace', keyCode: 8 },
|
|
24
|
-
Delete: { key: 'Delete', code: 'Delete', keyCode: 46 },
|
|
25
|
-
ArrowUp: { key: 'ArrowUp', code: 'ArrowUp', keyCode: 38 },
|
|
26
|
-
ArrowDown: { key: 'ArrowDown', code: 'ArrowDown', keyCode: 40 },
|
|
27
|
-
ArrowLeft: { key: 'ArrowLeft', code: 'ArrowLeft', keyCode: 37 },
|
|
28
|
-
ArrowRight: { key: 'ArrowRight', code: 'ArrowRight', keyCode: 39 },
|
|
29
|
-
Home: { key: 'Home', code: 'Home', keyCode: 36 },
|
|
30
|
-
End: { key: 'End', code: 'End', keyCode: 35 },
|
|
31
|
-
PageUp: { key: 'PageUp', code: 'PageUp', keyCode: 33 },
|
|
32
|
-
PageDown: { key: 'PageDown', code: 'PageDown', keyCode: 34 },
|
|
33
|
-
Space: { key: ' ', code: 'Space', keyCode: 32 },
|
|
34
|
-
};
|
|
35
|
-
export async function pressKey(cdp, sessionId, keyName) {
|
|
36
|
-
const keyInfo = KEY_CODES[keyName];
|
|
37
|
-
if (!keyInfo) {
|
|
38
|
-
throw new Error(`Unknown key: ${keyName}. Valid: ${Object.keys(KEY_CODES).join(', ')}`);
|
|
39
|
-
}
|
|
40
|
-
await cdp.send('Input.dispatchKeyEvent', {
|
|
41
|
-
type: 'keyDown',
|
|
42
|
-
key: keyInfo.key,
|
|
43
|
-
code: keyInfo.code,
|
|
44
|
-
windowsVirtualKeyCode: keyInfo.keyCode,
|
|
45
|
-
nativeVirtualKeyCode: keyInfo.keyCode,
|
|
46
|
-
}, sessionId);
|
|
47
|
-
await cdp.send('Input.dispatchKeyEvent', {
|
|
48
|
-
type: 'keyUp',
|
|
49
|
-
key: keyInfo.key,
|
|
50
|
-
code: keyInfo.code,
|
|
51
|
-
windowsVirtualKeyCode: keyInfo.keyCode,
|
|
52
|
-
nativeVirtualKeyCode: keyInfo.keyCode,
|
|
53
|
-
}, sessionId);
|
|
54
|
-
}
|
|
55
|
-
const FOCUS_DESCENDANT_FN = `(function() {
|
|
56
|
-
const selector = 'input:not([disabled]):not([type=hidden]),textarea:not([disabled]),select:not([disabled]),[contenteditable=""],[contenteditable=true],[tabindex]:not([tabindex="-1"])';
|
|
57
|
-
const candidates = this.querySelectorAll(selector);
|
|
58
|
-
for (const el of candidates) {
|
|
59
|
-
el.focus();
|
|
60
|
-
if (document.activeElement === el) return true;
|
|
61
|
-
}
|
|
62
|
-
return false;
|
|
63
|
-
})`;
|
|
64
|
-
// `DOM.focus` only works on natively focusable elements. UIs that wrap real
|
|
65
|
-
// inputs in styled containers (Slack composer, Linear comments, Notion blocks,
|
|
66
|
-
// Canva pickers, MUI/Chakra/Mantine TextField) often expose the wrapper as the
|
|
67
|
-
// accessible "ref" — focusing it throws "Element is not focusable". When that
|
|
68
|
-
// happens, walk the subtree for the first focusable descendant.
|
|
69
|
-
export async function focusNode(cdp, sessionId, backendNodeId) {
|
|
70
|
-
try {
|
|
71
|
-
await cdp.send('DOM.focus', { backendNodeId }, sessionId);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
catch (err) {
|
|
75
|
-
const focused = await focusFirstFocusableDescendant(cdp, sessionId, backendNodeId);
|
|
76
|
-
if (!focused)
|
|
77
|
-
throw err;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
async function focusFirstFocusableDescendant(cdp, sessionId, backendNodeId) {
|
|
81
|
-
const { object } = await cdp.send('DOM.resolveNode', { backendNodeId }, sessionId);
|
|
82
|
-
if (!object.objectId)
|
|
83
|
-
return false;
|
|
84
|
-
const objectId = object.objectId;
|
|
85
|
-
try {
|
|
86
|
-
const { result } = await cdp.send('Runtime.callFunctionOn', { objectId, functionDeclaration: FOCUS_DESCENDANT_FN, returnByValue: true }, sessionId);
|
|
87
|
-
return result.value === true;
|
|
88
|
-
}
|
|
89
|
-
finally {
|
|
90
|
-
await cdp.send('Runtime.releaseObject', { objectId }, sessionId);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import * as net from 'net';
|
|
2
|
-
import { BrowserService } from './service.js';
|
|
3
|
-
import type { IPCRequest, IPCResponse } from './types.js';
|
|
4
|
-
interface IPCRequestOptions {
|
|
5
|
-
autoStartDaemon?: boolean;
|
|
6
|
-
/**
|
|
7
|
-
* Opt-in client-side deadline in ms — most IPC actions (browser automation,
|
|
8
|
-
* long-running recordings) have no fixed budget and must NOT get one by
|
|
9
|
-
* default. Set explicitly for a request that genuinely has one, e.g.
|
|
10
|
-
* `request-self-update`, whose handler answers immediately (it kicks the
|
|
11
|
-
* install off in the background — see `triggerSelfUpdateInBackground`), so its
|
|
12
|
-
* client deadline (`DAEMON_SELF_UPDATE_TRIGGER_TIMEOUT_MS`) is a short
|
|
13
|
-
* "did the daemon accept the trigger?" bound, not a wait on the install.
|
|
14
|
-
*/
|
|
15
|
-
timeoutMs?: number;
|
|
16
|
-
}
|
|
17
|
-
export declare class BrowserServiceNotRunningError extends Error {
|
|
18
|
-
constructor();
|
|
19
|
-
}
|
|
20
|
-
export declare function formatBrowserServiceNotRunningError(daemonRunning?: boolean): string;
|
|
21
|
-
export declare function getSocketPath(): string;
|
|
22
|
-
/** Is the daemon reachable? A real connect probe on every platform — a socket
|
|
23
|
-
* file existing on disk is not proof a daemon is listening on it. */
|
|
24
|
-
export declare function isBrowserServiceReachable(): Promise<boolean>;
|
|
25
|
-
/**
|
|
26
|
-
* Is a *reachable* daemon actually responsive, or is its event loop wedged
|
|
27
|
-
* (PHNX-3411)? Retries {@link probeBrowserServiceResponsive} up to
|
|
28
|
-
* {@link RESPONSIVENESS_PROBE_ATTEMPTS} times so a single transient miss (a GC
|
|
29
|
-
* pause) never condemns a healthy daemon; returns true as soon as any attempt
|
|
30
|
-
* gets a reply, and false only when every attempt fails.
|
|
31
|
-
*/
|
|
32
|
-
export declare function isBrowserServiceResponsive(endpoint?: string, attempts?: number): Promise<boolean>;
|
|
33
|
-
/**
|
|
34
|
-
* Wait until the browser service is genuinely reachable, or throw.
|
|
35
|
-
*
|
|
36
|
-
* Re-probes across the whole window rather than latching on the first accept, so
|
|
37
|
-
* it survives an IPC-server restart that happens mid-wait (PHNX-3289): a restart
|
|
38
|
-
* just resets the consecutive-accept counter, and the loop keeps going until the
|
|
39
|
-
* daemon is *stably* up or the deadline passes. Bounded and fail-loud — a daemon
|
|
40
|
-
* that never comes up throws a message naming the endpoint and the budget, never
|
|
41
|
-
* a silent hang.
|
|
42
|
-
*/
|
|
43
|
-
export declare function waitForBrowserService(_socketPath: string, timeoutMs?: number): Promise<void>;
|
|
44
|
-
/** Outcome of {@link stopBrowserService} — what the service-scoped stop did. */
|
|
45
|
-
interface BrowserServiceStopResult {
|
|
46
|
-
/** The browser IPC service was reachable before the stop was requested. */
|
|
47
|
-
wasRunning: boolean;
|
|
48
|
-
/** The shared daemon stayed running while only browser IPC was stopped. */
|
|
49
|
-
daemonRunning: boolean;
|
|
50
|
-
/** A leftover `browser.sock` file was unlinked (POSIX only). */
|
|
51
|
-
socketCleared: boolean;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Stop only the daemon-hosted browser IPC service so a subsequent `start` comes
|
|
55
|
-
* up clean (PHNX-3605). The short-lived browser client persists the
|
|
56
|
-
* `browser-ipc` service off and asks the daemon to apply that transition over
|
|
57
|
-
* its existing SIGHUP control path. It never stops or restarts the shared
|
|
58
|
-
* supervisor, so routines, usage sync, secrets, and every sibling service keep
|
|
59
|
-
* their process and in-flight work.
|
|
60
|
-
*
|
|
61
|
-
* Once the endpoint is quiet, a stale socket left by a hard-crashed browser
|
|
62
|
-
* service is removed. If the daemon event loop itself is wedged and cannot
|
|
63
|
-
* consume SIGHUP, the endpoint remains reachable and this fails loud with the
|
|
64
|
-
* deliberate operator-owned whole-daemon restart command; the client never
|
|
65
|
-
* escalates into that process-wide action itself.
|
|
66
|
-
*/
|
|
67
|
-
export declare function stopBrowserService(): Promise<BrowserServiceStopResult>;
|
|
68
|
-
/**
|
|
69
|
-
* Remove a leftover browser socket FILE, but only when nothing is listening on
|
|
70
|
-
* it — re-probing liveness IMMEDIATELY before the unlink to close the TOCTOU
|
|
71
|
-
* window (PHNX-3289 review). Between {@link stopBrowserService}'s quiesce loop
|
|
72
|
-
* deciding the endpoint was unreachable and this unlink, a concurrent
|
|
73
|
-
* `browser start` could bind a NEW listener on the same path; an unconditional
|
|
74
|
-
* unlink would then delete a LIVE daemon's socket — the exact two-servers orphan
|
|
75
|
-
* this function exists to prevent. A listener seen here means the wedge is already
|
|
76
|
-
* resolved (a fresh daemon owns the path), so its socket is left intact. Returns
|
|
77
|
-
* true only when a genuinely dead file was removed.
|
|
78
|
-
*/
|
|
79
|
-
export declare function clearDeadSocketFile(endpoint: string, socketPath: string): Promise<boolean>;
|
|
80
|
-
/**
|
|
81
|
-
* One long-lived connection to the existing browser service. Requests are
|
|
82
|
-
* serialized so the daemon's newline-delimited responses always map to the
|
|
83
|
-
* caller that produced them, while the process and socket stay warm between
|
|
84
|
-
* actions.
|
|
85
|
-
*/
|
|
86
|
-
declare class BrowserIPCConnection {
|
|
87
|
-
private readonly socket;
|
|
88
|
-
private buffer;
|
|
89
|
-
private pending;
|
|
90
|
-
private tail;
|
|
91
|
-
private closed;
|
|
92
|
-
constructor(socket: net.Socket);
|
|
93
|
-
request(request: IPCRequest): Promise<IPCResponse>;
|
|
94
|
-
close(): Promise<void>;
|
|
95
|
-
private requestOnce;
|
|
96
|
-
private handleData;
|
|
97
|
-
private fail;
|
|
98
|
-
}
|
|
99
|
-
export declare class BrowserIPCServer {
|
|
100
|
-
private server;
|
|
101
|
-
/** Filesystem identity of the POSIX binding this server created. */
|
|
102
|
-
private socketIdentity;
|
|
103
|
-
private service;
|
|
104
|
-
/** Live client connections, so {@link stop} can end them rather than wait. */
|
|
105
|
-
private connections;
|
|
106
|
-
/** In-flight stop, so a second SIGTERM awaits the same close, never skips it. */
|
|
107
|
-
private stopping;
|
|
108
|
-
/**
|
|
109
|
-
* Optional explicit socket path for TEST ISOLATION only (POSIX). When set, the
|
|
110
|
-
* server binds (and unlinks a stale copy of) THIS path instead of the shared
|
|
111
|
-
* production `browser.sock`, so a test never unlinks the user's live daemon
|
|
112
|
-
* socket. Default (undefined) is unchanged. Not for production use.
|
|
113
|
-
*/
|
|
114
|
-
private readonly socketPathOverride?;
|
|
115
|
-
constructor(service: BrowserService, socketPathOverride?: string);
|
|
116
|
-
start(): Promise<void>;
|
|
117
|
-
/**
|
|
118
|
-
* Stop accepting, release the binding, and only then resolve.
|
|
119
|
-
*
|
|
120
|
-
* RUSH-2421: this used to call `server.close()` and move on. `close()` is
|
|
121
|
-
* asynchronous — it stops accepting immediately but the binding is not
|
|
122
|
-
* actually released until the `'close'` event fires — so `stop()` resolved
|
|
123
|
-
* while the socket (or named pipe) was still held. The daemon's
|
|
124
|
-
* `handleShutdown` awaits this before it exits, and a successor's
|
|
125
|
-
* `claimDaemonInstance` waits for that exit as its proof the predecessor's
|
|
126
|
-
* resources are free (SING-11). Resolving early made that proof false: the
|
|
127
|
-
* newcomer could bind before the incumbent's binding was gone, which is the
|
|
128
|
-
* two-servers-on-one-socket orphan the eviction protocol exists to prevent.
|
|
129
|
-
*
|
|
130
|
-
* The first version of this waited out a timeout instead, and that made every
|
|
131
|
-
* graceful daemon stop escalate to `killTree`: `close()` does not complete
|
|
132
|
-
* while a connection is open, browser clients hold one open on purpose, and
|
|
133
|
-
* the timeout was set equal to the daemon's own SIGTERM grace window
|
|
134
|
-
* (`STOP_GRACE_MS`) — so `handleShutdown` was still inside `stop()` when
|
|
135
|
-
* `stopDaemon` gave up waiting and killed it. So we END the connections
|
|
136
|
-
* rather than wait for them: `close()` stops accepting, destroying the
|
|
137
|
-
* tracked sockets lets it complete, and the timeout is only a backstop for a
|
|
138
|
-
* socket that will not die, deliberately far below `STOP_GRACE_MS`.
|
|
139
|
-
*/
|
|
140
|
-
stop(): Promise<void>;
|
|
141
|
-
private doStop;
|
|
142
|
-
/** Release the listening binding, resolving only once it is genuinely gone. */
|
|
143
|
-
private closeServer;
|
|
144
|
-
/**
|
|
145
|
-
* Resolve `request.task` from caller identity when omitted. Page verbs may
|
|
146
|
-
* create; done/stop never create. Returns an error response to short-circuit
|
|
147
|
-
* the action, or undefined when the request is ready (task filled in).
|
|
148
|
-
*/
|
|
149
|
-
private bindTask;
|
|
150
|
-
private handleRequest;
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Decide whether a running daemon is stale enough to name an operator refresh.
|
|
154
|
-
*
|
|
155
|
-
* FORWARD ONLY: recommend a refresh when the daemon is *older* than this CLI.
|
|
156
|
-
* An older CLI rides a newer daemon without noise. A client never performs the
|
|
157
|
-
* refresh itself — two installs sharing one daemon dir (keyed off $HOME) must
|
|
158
|
-
* not flap the daemon or interrupt one another's hosted services.
|
|
159
|
-
*
|
|
160
|
-
* `undefined`/`'unknown'` means the daemon is too old to answer the `version`
|
|
161
|
-
* action reliably — don't recommend an action on that ambiguous signal.
|
|
162
|
-
*
|
|
163
|
-
* When numeric compare cannot order two distinct strings (e.g. two
|
|
164
|
-
* `0.0.0-dev.*` builds), surface the mismatch without deciding either client
|
|
165
|
-
* owns the shared process.
|
|
166
|
-
*/
|
|
167
|
-
export declare function shouldRecommendDaemonRefresh(daemonVersion: string | undefined, clientVersion: string): boolean;
|
|
168
|
-
/**
|
|
169
|
-
* Reconcile the running daemon's version with ours without evicting it. If the
|
|
170
|
-
* daemon is serving stale code, ask the daemon to run its OWN self-update path
|
|
171
|
-
* (`request-self-update`, same fail-closed check/install/verify/exit
|
|
172
|
-
* SelfUpdateService's periodic tick runs, self-update-service.ts) rather than
|
|
173
|
-
* telling a human to run `agents daemon restart` by hand. A browser client
|
|
174
|
-
* still owns neither the shared supervisor nor its sibling services, so
|
|
175
|
-
* version skew can never be permission for THIS client to stop or restart
|
|
176
|
-
* that process directly (PHNX-3605 holds) — it only asks the daemon to run a
|
|
177
|
-
* path the daemon already runs on its own schedule, which only exits once the
|
|
178
|
-
* daemon itself has verified the new version is good. If self-update declines
|
|
179
|
-
* (dev build, shadowed install, already current, or a real failure) this
|
|
180
|
-
* degrades to the same "nothing changed" outcome PHNX-3605 shipped.
|
|
181
|
-
*/
|
|
182
|
-
export declare function reconcileDaemonVersion(): Promise<void>;
|
|
183
|
-
/** Test-only: `reconcileDaemonVersion` runs at most once per real process — this clears that latch so a test file can drive more than one branch. */
|
|
184
|
-
export declare function resetVersionReconciliationForTest(): void;
|
|
185
|
-
export declare function sendIPCRequest(request: IPCRequest, opts?: IPCRequestOptions): Promise<IPCResponse>;
|
|
186
|
-
/**
|
|
187
|
-
* Fill actor / launchId / sessionId from the calling process when the request
|
|
188
|
-
* left them blank. Explicit values on the request always win.
|
|
189
|
-
*/
|
|
190
|
-
export declare function stampCallerIdentity(request: IPCRequest, env?: NodeJS.ProcessEnv): IPCRequest;
|
|
191
|
-
/**
|
|
192
|
-
* Open one connection that can serve many browser operations. This uses the
|
|
193
|
-
* same daemon readiness and version-reconciliation path as sendIPCRequest;
|
|
194
|
-
* only the client process and IPC socket lifetime differ.
|
|
195
|
-
*/
|
|
196
|
-
export declare function connectBrowserIPC(opts?: IPCRequestOptions): Promise<BrowserIPCConnection>;
|
|
197
|
-
/**
|
|
198
|
-
* Exported (rather than kept module-private) so `ipc.test.ts` can drive the
|
|
199
|
-
* `opts.timeoutMs` client-side deadline directly against a raw `net.Server`
|
|
200
|
-
* test double that never responds — the hermetic way to prove the timeout
|
|
201
|
-
* actually fires without needing a slow real IPC action to provoke it.
|
|
202
|
-
*/
|
|
203
|
-
export declare function sendRawIPCRequest(request: IPCRequest, opts?: IPCRequestOptions): Promise<IPCResponse>;
|
|
204
|
-
export {};
|