@phnx-labs/agents-cli 1.22.51 → 1.22.53
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 +238 -0
- package/README.md +1 -1
- package/dist/commands/accounts.js +1 -1
- package/dist/commands/attach.js +7 -0
- package/dist/commands/browser.js +118 -56
- package/dist/commands/daemon.d.ts +2 -0
- package/dist/commands/daemon.js +8 -4
- package/dist/commands/detach.js +1 -1
- package/dist/commands/exec.js +16 -9
- package/dist/commands/fleet-capture.js +7 -0
- package/dist/commands/focus.d.ts +1 -10
- package/dist/commands/focus.js +16 -79
- package/dist/commands/go.d.ts +26 -0
- package/dist/commands/go.js +65 -6
- package/dist/commands/monitors.js +1 -1
- package/dist/commands/repo.js +31 -3
- package/dist/commands/sessions-inject.js +8 -3
- package/dist/commands/sessions-picker.js +2 -1
- package/dist/commands/sessions-resume.d.ts +1 -0
- package/dist/commands/sessions-resume.js +13 -2
- package/dist/commands/sessions-stop.js +1 -1
- package/dist/commands/sessions.d.ts +23 -13
- package/dist/commands/sessions.js +69 -39
- package/dist/commands/setup-browser.d.ts +5 -2
- package/dist/commands/setup-browser.js +14 -29
- package/dist/commands/setup-preferences.d.ts +22 -3
- package/dist/commands/setup-preferences.js +25 -8
- package/dist/commands/share.js +12 -8
- package/dist/commands/ssh.js +35 -12
- package/dist/commands/status.js +5 -0
- package/dist/commands/sync.js +102 -2
- package/dist/commands/tmux.d.ts +8 -1
- package/dist/commands/tmux.js +167 -17
- package/dist/lib/account-registry.d.ts +15 -5
- package/dist/lib/account-registry.js +150 -50
- package/dist/lib/answer-router.js +2 -1
- package/dist/lib/browser/ipc.d.ts +44 -0
- package/dist/lib/browser/ipc.js +120 -8
- package/dist/lib/browser/profiles.d.ts +57 -17
- package/dist/lib/browser/profiles.js +77 -53
- package/dist/lib/browser/registry.d.ts +44 -14
- package/dist/lib/browser/registry.js +141 -45
- package/dist/lib/browser/runtime-state.d.ts +4 -2
- package/dist/lib/browser/runtime-state.js +4 -2
- package/dist/lib/browser/service.js +4 -3
- package/dist/lib/channels/owner-forward.d.ts +88 -0
- package/dist/lib/channels/owner-forward.js +116 -0
- package/dist/lib/channels/owner-sink.js +7 -0
- package/dist/lib/daemon/runner.js +10 -2
- package/dist/lib/device-config.js +3 -2
- package/dist/lib/devices/config-migration.js +147 -1
- package/dist/lib/devices/device-docs.d.ts +35 -0
- package/dist/lib/devices/device-docs.js +163 -0
- package/dist/lib/devices/discovery-policy.d.ts +14 -2
- package/dist/lib/devices/discovery-policy.js +31 -21
- package/dist/lib/devices/registry.d.ts +11 -5
- package/dist/lib/devices/registry.js +46 -18
- package/dist/lib/exec.d.ts +66 -28
- package/dist/lib/exec.js +71 -26
- package/dist/lib/feed/feed.d.ts +10 -2
- package/dist/lib/feed/feed.js +12 -1
- package/dist/lib/feed-broadcast.js +15 -1
- package/dist/lib/git.d.ts +93 -0
- package/dist/lib/git.js +232 -0
- package/dist/lib/hosts/dispatch.d.ts +4 -3
- package/dist/lib/hosts/dispatch.js +12 -8
- package/dist/lib/hosts/providers/local.d.ts +9 -3
- package/dist/lib/hosts/providers/local.js +23 -12
- package/dist/lib/hosts/reconnect.d.ts +7 -4
- package/dist/lib/hosts/reconnect.js +29 -25
- package/dist/lib/hosts/registry.js +4 -1
- package/dist/lib/hosts/remote-os.js +3 -1
- package/dist/lib/monitors/remote.d.ts +18 -1
- package/dist/lib/monitors/remote.js +15 -2
- package/dist/lib/notify.d.ts +7 -0
- package/dist/lib/notify.js +15 -1
- package/dist/lib/session/active.d.ts +10 -1
- package/dist/lib/session/active.js +7 -1
- package/dist/lib/session/actor-sidecar.d.ts +7 -0
- package/dist/lib/session/actor-sidecar.js +2 -0
- package/dist/lib/session/db.d.ts +1 -1
- package/dist/lib/session/db.js +39 -3
- package/dist/lib/session/discover.js +7 -12
- package/dist/lib/session/live-metadata.js +1 -0
- package/dist/lib/session/local-tmux-attach.d.ts +69 -0
- package/dist/lib/session/local-tmux-attach.js +164 -0
- package/dist/lib/session/pid-registry.d.ts +7 -0
- package/dist/lib/session/prompt.d.ts +15 -0
- package/dist/lib/session/prompt.js +21 -0
- package/dist/lib/session/remote-active.d.ts +8 -0
- package/dist/lib/session/remote-active.js +1 -0
- package/dist/lib/session/types.d.ts +17 -0
- package/dist/lib/session/types.js +10 -0
- package/dist/lib/share/publish.d.ts +8 -11
- package/dist/lib/share/publish.js +16 -20
- package/dist/lib/share/worker-template.js +104 -12
- package/dist/lib/state.d.ts +8 -0
- package/dist/lib/state.js +143 -11
- package/dist/lib/sync-status.d.ts +17 -0
- package/dist/lib/sync-status.js +21 -2
- package/dist/lib/terminal/resolve.d.ts +7 -0
- package/dist/lib/terminal/resolve.js +41 -2
- package/dist/lib/tmux/index.d.ts +1 -1
- package/dist/lib/tmux/index.js +1 -1
- package/dist/lib/tmux/session.d.ts +10 -0
- package/dist/lib/tmux/session.js +29 -0
- package/dist/lib/traces/insights.d.ts +67 -0
- package/dist/lib/traces/insights.js +178 -0
- package/dist/lib/traces/phenotype.d.ts +67 -0
- package/dist/lib/traces/phenotype.js +437 -0
- package/dist/lib/traces/segments.d.ts +133 -0
- package/dist/lib/traces/segments.js +301 -0
- package/dist/lib/traces/sync.d.ts +33 -0
- package/dist/lib/traces/sync.js +11 -2
- package/dist/lib/types.d.ts +47 -1
- package/dist/lib/watchdog/runner.js +18 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,243 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.22.53
|
|
4
|
+
|
|
5
|
+
- **Custom-harness runs launched via a profile now show their real harness name in agents sessions instead of 'claude' (PHNX-2935).**
|
|
6
|
+
|
|
7
|
+
- **Watchdog un-addressable stalls now include the same recovery command as `agents focus` (PHNX-3070).**
|
|
8
|
+
Rotate, needs-human declared-block, and nudge-refuse paths append `addressabilityRecoveryHint` so an un-addressable session prints `agents sessions resume <id>` / tmux wrap instead of a reason-only skip. Source: `cli/src/lib/watchdog/runner.ts`.
|
|
9
|
+
|
|
10
|
+
- **Un-addressable live sessions now surface a clear recovery hint at the point of use** — `agents focus`, `sessions inject`, and the session picker report when a bare interactive session has no addressable terminal rail (not tmux/iTerm/an IDE terminal/a pty sidecar) instead of failing silently, and the hint now names the session's real id in `agents sessions resume <id>` rather than a placeholder (PHNX-3070).
|
|
11
|
+
|
|
12
|
+
- **Vitest can no longer launch the developer's browser (PHNX-3072).** `tests/setup.ts`
|
|
13
|
+
sandboxed `HOME` but not desktop-opener binaries, so a test that reached a real
|
|
14
|
+
`spawn('open' | 'xdg-open')` opened the URL on the developer's machine — green on
|
|
15
|
+
Linux CI, where `xdg-open` is absent, and disruptive locally. The harness now
|
|
16
|
+
prepends stub `open` / `xdg-open` / `gnome-open` binaries to `PATH` so a spawn
|
|
17
|
+
cannot reach the real handler; an unauthorized spawn fails the file. Source:
|
|
18
|
+
`cli/tests/opener-sandbox.ts`.
|
|
19
|
+
|
|
20
|
+
- **Feed attention stamps `sourceCursor` at block-write time (PHNX-3073).**
|
|
21
|
+
`coveredByResolution` conservatively suppresses a candidate with no comparable
|
|
22
|
+
cursor when a tombstone exists, which is correct for a stale lifecycle re-read
|
|
23
|
+
but buried a genuinely new open block whenever `session.lastActivityMs` was
|
|
24
|
+
unresolvable (cloud / remote / index-lag) because neither writer stamped a
|
|
25
|
+
cursor. `buildDeclaredBlock` and the feed-publish hook now stamp
|
|
26
|
+
`sourceCursor.lastActivityMs` from the write instant, so a fresh generation
|
|
27
|
+
compares strictly newer than any prior tombstone. Source:
|
|
28
|
+
`cli/src/lib/feed/feed.ts`.
|
|
29
|
+
|
|
30
|
+
- **Cursor auto-titles go through the same skill-scaffolding cleaner as Claude (PHNX-3123).**
|
|
31
|
+
`readCursorMeta` took `chatMeta.title` verbatim, so a Cursor session whose
|
|
32
|
+
server-generated title echoed the injected `Base directory for this skill: …`
|
|
33
|
+
line kept that path as `SessionMeta.label` — the field that wins on every
|
|
34
|
+
surface for the session's whole life. Claude's `ai-title` was already collapsed
|
|
35
|
+
to `/<skill>` at label composition (PR #2995); that collapse now lives in one
|
|
36
|
+
shared helper (`cleanGeneratedSessionLabel`) and Cursor's title takes the same
|
|
37
|
+
path. An ordinary Cursor title, and one that merely names a `skills/…` path,
|
|
38
|
+
is unchanged. A Claude `/rename` (`custom-title`) is still never rewritten.
|
|
39
|
+
Codex / OpenCode / Kimi / Droid put auto-titles on `topic`, not `label`, so
|
|
40
|
+
they are out of this change. **Not retroactive:** already-indexed labels stay
|
|
41
|
+
until the transcript next changes (RUSH-3122). Source:
|
|
42
|
+
`cli/src/lib/session/prompt.ts`, `cli/src/lib/session/discover.ts`.
|
|
43
|
+
|
|
44
|
+
- **Traces insight engine — cross-session failure clustering + wasted time (PHNX-3141).** `agents traces sync`'s `index.json` now carries `failurePatterns[]` (top-K, ranked by wasted time, not raw occurrence count — a rare 8-hour retry loop still outranks a frequent 2-minute one), `wastedMsTotal`, and `latency` (time-to-first-tool p50/p90/p99/max). Failed `tool_calls` are grouped by `(tool, cause, normalized error)` so near-identical failures (a GitHub rate-limit message with a different countdown each time) fold into one pattern instead of a wall of near-duplicates. Per-session `SessionDetail` also gains `surfacedToolFailures` — every failed step, listed regardless of the run's overall outcome, so a session that ultimately succeeded still shows the tool failures it hit along the way. Source: `cli/src/lib/traces/insights.ts`, `cli/src/lib/traces/sync.ts`.
|
|
45
|
+
|
|
46
|
+
- **Traces insight group-by dimensions (PHNX-3141).** `cli/src/lib/traces/segments.ts` derives the console bar axes — agent (model × harness), task type, failure timing — and the time-to-first-tool latency insight (`firstToolMs` p50/p90/p99/max). Pure functions; the sync integrator wires them into the shard. Source: `cli/src/lib/traces/segments.ts`.
|
|
47
|
+
|
|
48
|
+
- **Native-account labels live on the central `accounts.native` rows in agents.yaml (already fleet-synced via `agents repo push/pull`). `remove`/`rename`/`label` operate on every row matching `(agent, identityKey)`, so a post-merge duplicate cannot silently survive (PHNX-3206).**
|
|
49
|
+
|
|
50
|
+
- **The top-level `agents.yaml` header heals itself instead of freezing (PHNX-3315).**
|
|
51
|
+
`serializeCentral` parses the committed file to preserve its hand-written body
|
|
52
|
+
comments, but that also preserved the leading metadata header verbatim — so a
|
|
53
|
+
top-level `agents.yaml` written before the `agi-cli` rename kept its stale
|
|
54
|
+
3-line header (repo `agents-cli`, no `$schema:` line) forever while every
|
|
55
|
+
freshly-written device doc got the current header. The header is now rewritten
|
|
56
|
+
to the current `META_HEADER` on the next central write, textually (the `yaml`
|
|
57
|
+
library folds the leading comment onto the first key when that key already
|
|
58
|
+
carries a comment, so `doc.commentBefore` is unreliable), leaving body comments
|
|
59
|
+
and every config key intact, and byte-stable once healed so it never re-churns.
|
|
60
|
+
Source: `cli/src/lib/state.ts`.
|
|
61
|
+
- **The legacy central `browser:` tombstone drains itself on `agents sync` (PHNX-3315).**
|
|
62
|
+
New browser profiles write the per-device doc, but profiles created before the
|
|
63
|
+
device-scoped store lingered in the shared top-level `agents.yaml` and churned
|
|
64
|
+
every fleet pull until someone ran `agents browser profiles claim` by hand.
|
|
65
|
+
`agents sync` now auto-evicts a lingering central profile into this device's
|
|
66
|
+
doc and clears it from central — but ONLY for remote (`ssh://`) profiles. An
|
|
67
|
+
`ssh://` endpoint names a specific host, so the profile is fungible by design:
|
|
68
|
+
any box resolves it to the same browser, so a concurrent cross-machine
|
|
69
|
+
double-claim is harmless. A local/`cdp://` profile has no per-machine ownership
|
|
70
|
+
signal ("that browser is installed here" is not "I hold this profile's
|
|
71
|
+
credentialed session"), so auto-claiming it would let two boxes with the same
|
|
72
|
+
common browser each claim the same tombstone and flip a credentialed profile
|
|
73
|
+
from identity-bearing to fungible fleet-wide — the exact logged-out-browser
|
|
74
|
+
failure this module exists to prevent. Local/cdp tombstones are left central for
|
|
75
|
+
an explicit `agents browser profiles claim`. The claim is selected and committed
|
|
76
|
+
inside a single meta-lock acquisition, and non-throwing (a config conflict or
|
|
77
|
+
unhostable profile is left central rather than wedging the sync). `profileRegistry` is left as the single source of truth: a
|
|
78
|
+
claimed profile lives in the device doc alone, never double-counted across two
|
|
79
|
+
stores. Source: `cli/src/lib/browser/registry.ts`, `cli/src/commands/sync.ts`,
|
|
80
|
+
`cli/src/lib/state.ts`.
|
|
81
|
+
|
|
82
|
+
- **`fleet.discovery` / `fleet.ignored` are device-scoped with a union-on-read view (PHNX-3315).**
|
|
83
|
+
Every box used to rewrite one shared `fleet.discovery`/`fleet.ignored` map in the
|
|
84
|
+
top-level `agents.yaml` (each dismissal stamped `ignoredOn:<host>`), so two boxes
|
|
85
|
+
dismissing different peers produced guaranteed git line-conflicts on the next
|
|
86
|
+
pull. Each box now records ONLY its own discovery decisions and dismissals in its
|
|
87
|
+
tracked device doc (`devices/<host>/agents.yaml` `fleet:` block), and the
|
|
88
|
+
effective fleet view is recomputed as a deterministic, order-independent UNION
|
|
89
|
+
across every device doc at read time — `ignored` beats `approved` for a
|
|
90
|
+
conflicting name, and the newest `ignoredAt` wins a shared dismissal — so every
|
|
91
|
+
box computes the identical view and no box ever rewrites another's file. A
|
|
92
|
+
fold-then-delete migration moves the existing central maps into the owning box's
|
|
93
|
+
device doc before the central keys are dropped (the device-doc write lands before
|
|
94
|
+
the central strip, so a crash re-folds idempotently). `agents devices
|
|
95
|
+
register/ignore/unignore/ignored`, discovery reconcile, the sync pending-diff, and
|
|
96
|
+
`agents fleet capture` all read the union. Source:
|
|
97
|
+
`cli/src/lib/devices/{discovery-policy,registry,device-docs,config-migration}.ts`,
|
|
98
|
+
`cli/src/commands/{ssh,fleet-capture}.ts`, `cli/src/lib/state.ts`.
|
|
99
|
+
- **Agent-host registrations are device-scoped (`deviceHosts`) with a merged read (PHNX-3315).**
|
|
100
|
+
Locally-discovered SSH hosts and inline registrations landed in one shared
|
|
101
|
+
`hosts:` map in the top-level `agents.yaml`, so every box that enrolled a host
|
|
102
|
+
rewrote the fleet-shared file. Registrations now write this box's device doc
|
|
103
|
+
`hosts:` block; the effective host directory is the cross-box union (newest
|
|
104
|
+
`addedAt` wins a name collision), and `remove` only edits this box's own doc. A
|
|
105
|
+
fold-then-delete migration relocates the existing central `hosts:` map into this
|
|
106
|
+
box's device doc and drops the central key. Source:
|
|
107
|
+
`cli/src/lib/hosts/{providers/local,registry,remote-os}.ts`,
|
|
108
|
+
`cli/src/lib/devices/{device-docs,config-migration}.ts`, `cli/src/lib/state.ts`.
|
|
109
|
+
- **`scope:'device'` native accounts move off the shared file into the device doc (PHNX-3315).**
|
|
110
|
+
`addNativeAccount` accepted `scope:'device'` but always wrote the central
|
|
111
|
+
`accounts.native` map, so a per-box login (and its email / `identityKey` PII)
|
|
112
|
+
landed in the git-tracked, fleet-synced `agents.yaml`. A native login is
|
|
113
|
+
machine-local, so its home now follows its scope: a `scope:'device'` identity
|
|
114
|
+
(and any binding that targets it) writes this box's device doc `accounts:` block,
|
|
115
|
+
keeping its identity PII off the shared file; genuinely fleet-shared account
|
|
116
|
+
`defaults` and `scope:'version'` identities stay central. Every account reader
|
|
117
|
+
(`listNativeAccounts`, `findUnifiedAccount`, binding resolution, rename/remove)
|
|
118
|
+
merges the fleet-shared central store with this box's device slice. A
|
|
119
|
+
fold-then-delete migration relocates existing `scope:'device'` natives and their
|
|
120
|
+
bindings out of central (forward-only; no history rewrite). Source:
|
|
121
|
+
`cli/src/lib/account-registry.ts`, `cli/src/lib/devices/config-migration.ts`,
|
|
122
|
+
`cli/src/lib/state.ts`.
|
|
123
|
+
|
|
124
|
+
- **`tmux.enabled` now gates remote `--device` runs too — no more forced tmux wrap (PHNX-3316).**
|
|
125
|
+
`resolveTmuxWrap` (`cli/src/lib/exec.ts`) no longer lets `remoteDispatch`
|
|
126
|
+
override the device's `tmux.enabled`: off (the default) means a followed
|
|
127
|
+
`agents run --device <host>` spawns the agent directly, with no tmux session
|
|
128
|
+
on the peer. Durability for a bare remote run comes from the existing
|
|
129
|
+
auto-reconnect in `cli/src/lib/hosts/reconnect.ts`, whose target
|
|
130
|
+
(`agents sessions focus <id> --local`) already resumes the harness session in
|
|
131
|
+
place when no pane exists — a dropped link costs the in-flight turn, not the
|
|
132
|
+
conversation. One case still wraps regardless: a followed remote run whose
|
|
133
|
+
launcher has no TTY (CI, scripts, another agent driving the CLI), where the
|
|
134
|
+
detached pane is the run's only interface.
|
|
135
|
+
`undurable` is now reached only when a wrap was actually wanted (opted-in
|
|
136
|
+
device, or the no-TTY case) but the peer has no tmux; its refusal message
|
|
137
|
+
names the `agents ssh <host> 'agents devices config <host> tmux.enabled off'`
|
|
138
|
+
alternative (the key is machine-local, so it must be set on the peer)
|
|
139
|
+
alongside `--raw`.
|
|
140
|
+
RUSH-3125's forced wrap conflated durability with an ergonomics preference
|
|
141
|
+
and wrapped boxes whose operator had explicitly left tmux off.
|
|
142
|
+
`hosts/reconnect.ts`'s header premise, the `hosts/dispatch.ts`
|
|
143
|
+
REMOTE_INTERACTIVE_ENV comments, and the `tmux.enabled` help text
|
|
144
|
+
(`device-config.ts`) are updated to match; wrap-matrix tests cover the new
|
|
145
|
+
gate.
|
|
146
|
+
|
|
147
|
+
- **Share attribution bar: full-width chrome, not a floating box.** The bar was injected `position:sticky` inside `<body>`, so on a page whose body is a narrow centered column (`max-width` + `margin:auto`) it inherited that width and rendered as a floating black rectangle. It's now `position:fixed`, full viewport width, with an injected `html{padding-top}` pushing the page content down beneath it — clean top chrome on any page layout. `flex-wrap:nowrap` + left-side ellipsis keep it one line so the push height stays correct. Source: `cli/src/lib/share/worker-template.ts`.
|
|
148
|
+
|
|
149
|
+
## 1.22.52
|
|
150
|
+
|
|
151
|
+
- **`agents artifacts share --visibility me|org` — identity-gated share pages (PHNX-3260).**
|
|
152
|
+
Extends the P1 `public|unlisted` tiers (RUSH-3135) with two Phoenix-gated GET
|
|
153
|
+
visibilities. `me` is visible only to the signed-in owner (the viewer's Phoenix
|
|
154
|
+
`userId` must equal the stamped `owner`); `org` is visible to any signed-in
|
|
155
|
+
Phoenix user whose **verified email domain** matches the owner's company domain
|
|
156
|
+
(`org_domain`, stamped from the owner's verified email on PUT). A mismatch on
|
|
157
|
+
either returns **404** — the same body as a missing object — so a wrong viewer
|
|
158
|
+
cannot even learn the page exists. Both are hidden from the public gallery and
|
|
159
|
+
`share list`, like `unlisted`. Publishing `org` from a public inbox
|
|
160
|
+
(gmail/googlemail/outlook/hotmail/live/icloud/me) is refused with **400**, and
|
|
161
|
+
`me`/`org` PUT without a Phoenix identity is a loud 400 (a BYO `WRITE_TOKEN`
|
|
162
|
+
alone cannot publish them). Browser GET of an unauthenticated `me`/`org` URL
|
|
163
|
+
**302**s to `${PHOENIX_ID_BASE}/login?return=<url>` (the same Google OAuth as
|
|
164
|
+
`/device`, no new IdP); the returned one-time `phoenix_ticket` is redeemed once
|
|
165
|
+
via `POST /api/v1/auth/ticket` and exchanged for an HttpOnly, HMAC-signed
|
|
166
|
+
`__Host-phoenix_share` cookie on the share host. An existing CLI
|
|
167
|
+
`Authorization: Bearer` still works with no redirect. `me`/`org` responses send
|
|
168
|
+
`Cache-Control: private, no-store` and `X-Robots-Tag: noindex`;
|
|
169
|
+
`public`/`unlisted` GET is unchanged (anonymous 200). Because the Worker template
|
|
170
|
+
changed, **already-provisioned BYO endpoints need `agents artifacts share update`**
|
|
171
|
+
to serve the new gate. Source: `cli/src/lib/share/worker-template.ts`,
|
|
172
|
+
`cli/src/lib/share/{backend,publish}.ts`, `cli/src/commands/share.ts`.
|
|
173
|
+
|
|
174
|
+
- **Harden the browser daemon against the socket-timeout wedge (PHNX-3289).** Four
|
|
175
|
+
fixes to the browser/daemon reliability surface. `agents daemon status` no longer
|
|
176
|
+
errors on macOS with `ps: etimes: keyword not found` — the process-uptime probe now
|
|
177
|
+
shells the portable `ps -o etime=` and parses it with the same helper the keychain
|
|
178
|
+
reaper uses. `waitForSocket` raises its ceiling from a flat 6s to 15s and re-probes
|
|
179
|
+
across an IPC-server restart (requiring two stable accepts), so a browser
|
|
180
|
+
start/navigate that lands in a restart window no longer intermittently throws
|
|
181
|
+
`Timeout waiting for browser daemon socket`. New `agents browser stop --daemon` stops
|
|
182
|
+
the browser daemon and clears a stale/wedged `browser.sock`, failing loud if a live
|
|
183
|
+
server still holds it, so the next `start` comes up clean. And `agents browser start
|
|
184
|
+
--device <host>` now resolves the browser/profile on the TARGET device instead of the
|
|
185
|
+
local one — a bare start from a browserless box forwards to `--device` instead of
|
|
186
|
+
failing with a misleading `No supported browser found`. Source:
|
|
187
|
+
`cli/src/commands/daemon.ts`, `cli/src/lib/browser/ipc.ts`, `cli/src/commands/browser.ts`.
|
|
188
|
+
|
|
189
|
+
- **Managed share: a BYO-published page no longer locks the rightful Phoenix owner out of their own handle (PHNX-3291).** The share Worker's handle-ownership check (`assertHandleOwner`) scanned every page under a namespace and 409'd `handle taken` when any page's stamped `owner` differed from the writer's Phoenix `userId`. But a BYO `WRITE_TOKEN` publish stamps `owner = SHARE_NAMESPACE` (the namespace string, e.g. `octocat`), not a userId — so once a namespace held any BYO page, its legitimate Phoenix handle owner got a 409 on every subsequent publish, and could neither publish nor change a page's `--visibility` (which blocked the me/org tiers from PHNX-3260 for that user entirely). The `__handles/<handle>` claim object is now authoritative when it exists: the recorded userId may write and anyone else is refused, regardless of a stray page's owner stamp. The page-owner scan remains as the pre-claim fallback but ignores BYO namespace stamps (`owner === handle`), so a first Phoenix publish can still claim a handle previously used only by BYO pages. Source: `cli/src/lib/share/worker-template.ts`. Requires `agents artifacts share update` on already-provisioned endpoints (the Worker template hash changed).
|
|
190
|
+
|
|
191
|
+
- **`sessions resume`/`sessions attach`/`sessions focus` no longer SSH-probe the fleet before attaching a live local tmux pane (PHNX-3292).** First unique match now wins: a selector naming a live pane on THIS box — the full `ag-<agent>-<8hex>` alias `agents tmux ls` prints, or its bare 8-hex suffix when it names exactly one live local pane — attaches with zero SSH, for bare `sessions resume <alias>` and deprecated `sessions attach <alias>` too, not just `--attach-only`. Previously `collectSessionCandidates` ran first — two fleet sweeps (transcript pool + live roster) that stall on offline devices (~2 minutes measured on yosemite-s0, two copies of `unreachable or no agents CLI — skipped`) — and bare `resume` of a live alias didn't even reach the tmux-attach path at all. A miss still races the reachable fleet in parallel: `isDefinitiveMatch`/`selectorAllowsEarlyExit` (RUSH-2203) now cover a tmux alias and an exact 8-hex short id, not just a full UUID, so the first peer to answer with that pane aborts the rest of the sweep instead of waiting out every offline box's timeout. Two LIVE panes (local or fleet-answered) sharing the same 8-hex suffix fail closed with both names printed, rather than guessing; a dead/retained pane is never attached. `--device` skips the local gate entirely and scopes the race to the named box(es). Source: `cli/src/lib/session/local-tmux-attach.ts` (new, `attachLocalLiveSelector`), `cli/src/commands/{sessions,sessions-resume,attach,focus}.ts`.
|
|
192
|
+
|
|
193
|
+
- **`agents tmux attach` tears the session down when the agent exits (PHNX-3293).**
|
|
194
|
+
Attaching to a wrapped pane and exiting the agent (`/exit`, Ctrl-c) used to
|
|
195
|
+
print `[detached]` and leave the tmux session in `tmux ls` — the v6 pane-died
|
|
196
|
+
hook only `detach-client`s when a client is attached, and the attach verbs
|
|
197
|
+
never killed the `remain-on-exit` husk. `tmux attach`, `sessions focus` /
|
|
198
|
+
`resume --attach-only`, and `go`'s local-tmux attach now destroy the session
|
|
199
|
+
when every pane is dead and still keep it on Ctrl-b d. A piped (no-TTY)
|
|
200
|
+
local `agents run --interactive` no longer wraps in tmux, so session-tracker
|
|
201
|
+
tests cannot leak live "trust this folder" panes. `agents tmux kill` with no
|
|
202
|
+
name opens a picker whose preview is the pane's last screen (so a leaked
|
|
203
|
+
first-run dialog is distinguishable from real work); `tmux ls` prints that
|
|
204
|
+
snippet on each row. Source:
|
|
205
|
+
`cli/src/lib/tmux/session.ts`, `cli/src/commands/tmux.ts`,
|
|
206
|
+
`cli/src/commands/focus.ts`, `cli/src/lib/exec.ts`.
|
|
207
|
+
|
|
208
|
+
- **`agents browser start` no longer silently mints a logged-out `auto-chrome` (PHNX-3296).** With no configured default and no existing launchable profile, start now errors and points at `agents setup` / `agents browser use <name>` / the `browser.device` fleet hub instead of popping a signed-out Chrome. A pre-existing `auto-chrome` or legacy `default` still resolves. `agents setup`'s browser pick replaces the misleading "auto-detect on first use" opt-out with an explicit "None — this box uses the fleet hub" choice. Source: `cli/src/lib/browser/profiles.ts`, `cli/src/commands/setup-preferences.ts`, `cli/src/commands/browser.ts`.
|
|
209
|
+
|
|
210
|
+
- **`sessions detach` / `sessions stop` of a unique live local session no longer wait for fleet SSH timeouts (PHNX-3298).** A live pane already visible locally is the hit: a full UUID or a unique 8-hex prefix skips `gatherRemoteActive` entirely, so a sleeping peer cannot print `unreachable or no agents CLI` or stall SIGTERM. Two local matches fail closed without waiting for unanswered boxes. A genuine miss still races the fleet and aborts remaining SSH on the first unique reachable live row; browse / `focus` with no id stays all-settle. Source: `cli/src/commands/go.ts`, `cli/src/commands/detach.ts`, `cli/src/commands/sessions-stop.ts`.
|
|
211
|
+
|
|
212
|
+
- **`agents monitors add` refuses immediately when a reachable peer already has the same watcher (PHNX-3299).** The fleet duplicate guard used to collect every peer before reporting a clash, so a single slow box forced the full 12-second timeout even when another peer had already answered with the same fingerprint. The fan-out now aborts remaining SSH captures the moment a peer reports a matching monitor, while the no-match path still waits for the whole fleet so uniqueness can be proved. Source: `cli/src/commands/monitors.ts`, `cli/src/lib/monitors/remote.ts`.
|
|
213
|
+
|
|
214
|
+
- **`agents repo sync user` self-heals a non-git or partial `~/.agents` by adopting it in place — no re-clone, no data loss (PHNX-3301).** When the user-layer checkout lost (or never had) its `.git`, sync hard-failed with `Not a git repo: ~/.agents` and the only fix was a destructive re-clone that wiped runtime state (`.cache` / `.history` / `scratch` / device config). It now git-backs the existing directory in place with plumbing only (`init` / `remote` / `fetch` / `update-ref` / `read-tree` / `checkout-index` / `restore`, so it never trips the fleet git-guard): materializes only the MISSING tracked files, restores a stale-stub top-level `agents.yaml` from origin (backing up the previous copy to `.history/agents.yaml.pre-adopt.bak` first), preserves gitignored runtime state, and surfaces (never clobbers) real local edits. The remote URL is resolved from an existing `origin`, `AGENTS_USER_REPO_URL`, or a device-local record left by a prior healthy sync — never hardcoded. `agents sync status` now flags a non-git `~/.agents` as a distinct drift state instead of burying it as "N missing". Source: `cli/src/lib/git.ts`, `cli/src/commands/sync.ts`, `cli/src/commands/repo.ts`, `cli/src/lib/sync-status.ts`, `cli/src/commands/status.ts`.
|
|
215
|
+
|
|
216
|
+
- **Owner notifications forward to a capable fleet peer when this box can't deliver (PHNX-3303).**
|
|
217
|
+
The owner's delivery provider for the rush-backed channels (imessage / telegram /
|
|
218
|
+
slack / discord via the `rush` CLI) is macOS-only, so `agents feed post --level
|
|
219
|
+
important` / `--blocked`, `agents notify`, and monitor `notify` actions run from a
|
|
220
|
+
headless Linux worker used to record the post but fail to reach the owner with
|
|
221
|
+
`owner failed: … rush CLI not found on PATH`. They now hand the delivery over SSH to
|
|
222
|
+
a reachable macOS peer that DOES have the provider — the same reroute
|
|
223
|
+
`agents message` already uses — instead of stranding the important post. It is
|
|
224
|
+
best-effort: it never throws or blocks the post, only the macOS-only rush family
|
|
225
|
+
triggers a forward (a Linux-capable transport like openclaw-telegram stays local),
|
|
226
|
+
and when no capable peer is reachable the existing clean local error stands. A box
|
|
227
|
+
that received a forward never forwards onward (`AGENTS_OWNER_NO_FORWARD` guard).
|
|
228
|
+
Source: `cli/src/lib/channels/owner-forward.ts`, `cli/src/lib/notify.ts`,
|
|
229
|
+
`cli/src/lib/feed-broadcast.ts`.
|
|
230
|
+
|
|
231
|
+
- **`agents artifacts share <file>` now derives a stable slug automatically (PHNX-3310).**
|
|
232
|
+
The default comes from the HTML `<title>` or Markdown frontmatter `title:`, then
|
|
233
|
+
the filename, so repeat publishes of a long-running artifact update the same URL;
|
|
234
|
+
`--slug` remains an optional explicit override. Source:
|
|
235
|
+
`cli/src/lib/share/publish.ts`, `cli/src/commands/share.ts`.
|
|
236
|
+
|
|
237
|
+
- **The release attestation now shards its test suite across the fleet instead of pinning one box.** `release-attestation-produce.sh` — the full-suite run that mints a release's exact-tree attestation — ran the ~13k-test suite on a single auto-picked box at `--maxWorkers=2` (~880s measured, ~14 min per attestation). It now fans the suite across the fleet via `test.sh --shard N` by default, resolving N from the eligible workers `agents devices pick` reports (capped) and falling back to a single box only when fewer than two are eligible. The suite is throughput-bound, so this is ~1/N the wall time (~269s on one box → ~31s on nine). Each shard still runs vitest at `--maxWorkers=2 --retry=2`, so the RUSH-3015 per-box flake mitigation is unchanged — sharding adds machines, not per-box concurrency. New `--test-shard <n>` / `--test-devices a,b,c` overrides mirror `test.sh`. Source: `cli/scripts/release-attestation-produce.sh`.
|
|
238
|
+
|
|
239
|
+
- **Shared pages now show who shared them and — the point — a visibility cue (PHNX-3260 follow-up).** The share Worker injects a slim attribution bar at the top of every served HTML page: `Shared by <handle>`, `Made with <agent>`, the date, and a colour-coded **visibility chip** — 🔒 `Only you` (me), 🏢 `Anyone at <org>` (org), 🌐 `Public`, 🔗 `Unlisted` — so a viewer can tell at a glance who can see the page, the thing the raw served HTML never surfaced. All values come from metadata the Worker already stamps (`visibility`, `owner`/handle, `org_domain`, `agent`, `date`); no new publish-time metadata and no CLI change, so it ships with `agents artifacts share update`. Non-HTML assets (images, JSON, the OG cover) are served byte-for-byte; metadata is HTML-escaped; the rewritten body drops the now-stale R2 etag. Source: `cli/src/lib/share/worker-template.ts`.
|
|
240
|
+
|
|
3
241
|
## 1.22.51
|
|
4
242
|
|
|
5
243
|
- **`publish-computer-helper-mac.sh` cuts the helper's own tag (PHNX-3228).**
|
package/README.md
CHANGED
|
@@ -1085,7 +1085,7 @@ agents harness add deepinfra --account deepinfra
|
|
|
1085
1085
|
|
|
1086
1086
|
One provider account **is** one `agents secrets` bundle -- `agents accounts add` creates it with secrets policy `never`, so a background agent launch on that account never raises Touch ID. `agents accounts` (no subcommand) lists provider bundles next to harness-native signed-in identities so you see both kinds of credential together; `accounts list` / `inspect <name>` / `set-key <name>` (rotate) / `rename` / `remove` manage a bundle by its stable id, independent of its current label. `accounts switch <harness>` is the fast picker over that default -- it writes the same binding as `set-default`, and balanced rotation already honors it.
|
|
1087
1087
|
|
|
1088
|
-
Harness-native OAuth logins (Claude Code's own `/login`, `codex login`, and so on) stay exactly where the harness put them -- agi-cli discovers and displays them but never copies, renames, or converts them into a provider bundle. Native `accounts name` / `attach` is only for harnesses agents-cli can isolate today (claude, codex, grok; muse when an email is present). A device-scoped login (kimi, droid, …) is refused with a named reason; provider `accounts add --provider` is unrestricted. `accounts sync <name> --device <device>` is the only way a provider account crosses machines, and it's explicit: nothing
|
|
1088
|
+
Harness-native OAuth logins (Claude Code's own `/login`, `codex login`, and so on) stay exactly where the harness put them -- agi-cli discovers and displays them but never copies, renames, or converts them into a provider bundle. Native `accounts name` / `attach` is only for harnesses agents-cli can isolate today (claude, codex, grok; muse when an email is present). A device-scoped login (kimi, droid, …) is refused with a named reason; provider `accounts add --provider` is unrestricted. Native **labels** (`agents accounts label codex personal`) bind to a stable identityKey on the central account row in `agents.yaml`, which `agents repo push/pull` already syncs fleet-wide, so `codex#personal` selects the same login on every box. `accounts sync <name> --device <device>` is the only way a **provider** account credential crosses machines, and it's explicit: nothing copies OAuth automatically. Selection order for a run is explicit `--account`, then `accounts set-default` / `switch` for that harness, then the harness's native/balanced account behavior.
|
|
1089
1089
|
|
|
1090
1090
|
---
|
|
1091
1091
|
|
|
@@ -464,7 +464,7 @@ export function registerAccountsCommand(program) {
|
|
|
464
464
|
agents accounts label codex@0.146.0 personal
|
|
465
465
|
agents accounts label codex work --account you@example.com
|
|
466
466
|
agents run codex#work`,
|
|
467
|
-
notes: 'The label binds to the signed-in account identity, not the version — codex#work keeps selecting that account after it moves to a newer install. One signed-in login needs no selector; with several, an interactive terminal opens a picker, while scripts pass --account or <harness>@<version>.',
|
|
467
|
+
notes: 'The label binds to the signed-in account identity, not the version — codex#work keeps selecting that account after it moves to a newer install. Labels live on the central account rows in agents.yaml, which `agents repo push/pull` already syncs fleet-wide, keyed by (agent, identityKey). One signed-in login needs no selector; with several, an interactive terminal opens a picker, while scripts pass --account or <harness>@<version>.',
|
|
468
468
|
});
|
|
469
469
|
accounts.command('attach <account> <target>')
|
|
470
470
|
.description('Attach a named account to a native installation or custom harness')
|
package/dist/commands/attach.js
CHANGED
|
@@ -3,6 +3,7 @@ import { runOnPeer } from '../lib/session/remote-list.js';
|
|
|
3
3
|
import { sessionRecoveryPeer } from '../lib/session/recovery.js';
|
|
4
4
|
import { resolveSessionMetadataValue, resumeSessionInPlace } from './sessions.js';
|
|
5
5
|
import { readDetachRecord, clearDetachRecord, isHeadlessAlive } from '../lib/session/detached.js';
|
|
6
|
+
import { attachLocalLiveSelector } from '../lib/session/local-tmux-attach.js';
|
|
6
7
|
export function registerAttachCommand(program) {
|
|
7
8
|
// Deprecated: superseded by `agents sessions resume`, which detects the state
|
|
8
9
|
// (live pane / headless / ended) instead of making the caller pick a verb.
|
|
@@ -19,6 +20,12 @@ export function registerAttachCommand(program) {
|
|
|
19
20
|
});
|
|
20
21
|
}
|
|
21
22
|
export async function attachAction(id) {
|
|
23
|
+
// PHNX-3292: this deprecated spelling still gets the local-pane fast path —
|
|
24
|
+
// a live tmux alias/short id on THIS box attaches with zero SSH, matching
|
|
25
|
+
// `sessions resume`. `attach` takes no `--device`, so hosts is always empty.
|
|
26
|
+
if (await attachLocalLiveSelector(id.trim(), [])) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
22
29
|
const outcome = await resolveSessionMetadataValue(id);
|
|
23
30
|
if (outcome.kind === 'partial') {
|
|
24
31
|
// RUSH-2492: an unreachable peer is a warning, not a hard failure. The
|
package/dist/commands/browser.js
CHANGED
|
@@ -14,7 +14,7 @@ import { DEFAULT_VIEWPORT, parseWindowSize, parseWindowPosition } from '../lib/b
|
|
|
14
14
|
import { runBrowserSessionsCommand } from './browser-sessions-picker.js';
|
|
15
15
|
import { discoverBrowserWsUrl, verifyBrowserIdentity } from '../lib/browser/cdp.js';
|
|
16
16
|
import { parseTargetFilter } from '../lib/browser/service.js';
|
|
17
|
-
import { BrowserDaemonNotRunningError, formatBrowserDaemonNotRunningError, sendIPCRequest as sendRawIPCRequest, } from '../lib/browser/ipc.js';
|
|
17
|
+
import { BrowserDaemonNotRunningError, formatBrowserDaemonNotRunningError, sendIPCRequest as sendRawIPCRequest, resetBrowserDaemon, } from '../lib/browser/ipc.js';
|
|
18
18
|
import { bindTask, getTaskBinding, honorScreenshotOutput, REJECT_DEVICE_MESSAGE, resolveTaskRoute, unbindTask, unbindTasksForProfile, updateTaskBinding, } from '../lib/browser/task-index.js';
|
|
19
19
|
import { isSelfHost } from '../lib/devices/self-host.js';
|
|
20
20
|
import { resolveHost } from '../lib/hosts/registry.js';
|
|
@@ -294,7 +294,7 @@ export function registerBrowserCommand(program) {
|
|
|
294
294
|
# Create a Chrome profile pointed at a CDP endpoint
|
|
295
295
|
agents browser profiles create work --browser chrome --endpoint http://localhost:9222
|
|
296
296
|
|
|
297
|
-
# Start a session
|
|
297
|
+
# Start a session on this machine's default browser (set it once with: agents browser use)
|
|
298
298
|
agents browser start
|
|
299
299
|
|
|
300
300
|
# Or pin to a specific profile
|
|
@@ -323,6 +323,11 @@ export function registerBrowserCommand(program) {
|
|
|
323
323
|
|
|
324
324
|
# Close tabs the daemon's own reaper would have caught on its next 5-min tick
|
|
325
325
|
agents browser prune --dry-run
|
|
326
|
+
|
|
327
|
+
# Recover a wedged daemon ("Timeout waiting for browser daemon socket"):
|
|
328
|
+
# stop it and clear a stale socket, then start clean
|
|
329
|
+
agents browser stop --daemon
|
|
330
|
+
agents browser start
|
|
326
331
|
`,
|
|
327
332
|
notes: `
|
|
328
333
|
Most agent workflows should use the 'browser' skill instead of raw subcommands.
|
|
@@ -363,7 +368,7 @@ export function buildBrowserUseChoices(profiles, installed, current) {
|
|
|
363
368
|
export async function runBrowserUse(name, opts, interactive = isInteractiveTerminal()) {
|
|
364
369
|
if (opts.unset || name === 'auto') {
|
|
365
370
|
unsetConfigValue('browser.profile');
|
|
366
|
-
console.log('Default browser profile cleared. `agents browser start`
|
|
371
|
+
console.log('Default browser profile cleared. Pick one with `agents browser use <name>` (or `agents setup`); until then `agents browser start` has no default to launch here.');
|
|
367
372
|
return true;
|
|
368
373
|
}
|
|
369
374
|
let selectedName = name;
|
|
@@ -372,7 +377,7 @@ export async function runBrowserUse(name, opts, interactive = isInteractiveTermi
|
|
|
372
377
|
if (!interactive) {
|
|
373
378
|
console.log(current
|
|
374
379
|
? `Default browser profile (this machine): ${current}`
|
|
375
|
-
: 'Default browser profile (this machine):
|
|
380
|
+
: 'Default browser profile (this machine): none — pick one with `agents browser use <name>`');
|
|
376
381
|
const hub = getConfigValue('browser.device').value;
|
|
377
382
|
if (hub) {
|
|
378
383
|
console.log(isSelfHost(hub)
|
|
@@ -421,7 +426,7 @@ export async function runBrowserUse(name, opts, interactive = isInteractiveTermi
|
|
|
421
426
|
function configureBrowserUseCommand(command, deprecated = false) {
|
|
422
427
|
command
|
|
423
428
|
.description('Pick the profile `agents browser start` uses when no --profile is passed. No name opens a picker on a TTY or prints the current default headlessly.')
|
|
424
|
-
.option('--unset', 'Clear the configured default (
|
|
429
|
+
.option('--unset', 'Clear the configured default (leaves this machine with no default until you pick one)')
|
|
425
430
|
.action(async (name, opts) => {
|
|
426
431
|
if (deprecated) {
|
|
427
432
|
console.warn(chalk.yellow('Deprecation: `agents browser profiles set-default` is replaced by `agents browser use`.'));
|
|
@@ -942,7 +947,7 @@ function registerProfilesCommands(browser) {
|
|
|
942
947
|
(removed > 0 ? ` (and ${removed} cache dir${removed === 1 ? '' : 's'})` : ''));
|
|
943
948
|
if (getConfiguredDefaultProfileName() === name) {
|
|
944
949
|
console.error(`warning: "${name}" was this machine's default browser profile; ` +
|
|
945
|
-
`\`agents browser start\`
|
|
950
|
+
`\`agents browser start\` has no default here until you run: agents browser use <name>`);
|
|
946
951
|
}
|
|
947
952
|
});
|
|
948
953
|
profiles
|
|
@@ -1025,13 +1030,13 @@ function registerProfilesCommands(browser) {
|
|
|
1025
1030
|
with \`agents browser profiles claim\` on the machine that hosts the browser.
|
|
1026
1031
|
|
|
1027
1032
|
In \`list\`, the \`*\` marker means "this machine's configured default"
|
|
1028
|
-
(\`agents browser start\` with no --profile).
|
|
1029
|
-
named \`auto-chrome\`; \`default\` is only an ALIAS for whichever profile
|
|
1033
|
+
(\`agents browser start\` with no --profile). A profile a setup wizard pins
|
|
1034
|
+
is named \`auto-chrome\`; \`default\` is only an ALIAS for whichever profile
|
|
1030
1035
|
this machine is configured to use, resolved the same way by every command.
|
|
1031
1036
|
|
|
1032
1037
|
\`prune\` only considers profiles this device declares. It never removes a
|
|
1033
|
-
profile that is in use, the configured default, or the auto-
|
|
1034
|
-
|
|
1038
|
+
profile that is in use, the configured default, or the \`auto-chrome\`
|
|
1039
|
+
profile a setup wizard created.
|
|
1035
1040
|
`,
|
|
1036
1041
|
});
|
|
1037
1042
|
profiles
|
|
@@ -1371,8 +1376,8 @@ function registerTaskCommands(browser) {
|
|
|
1371
1376
|
});
|
|
1372
1377
|
browser
|
|
1373
1378
|
.command('start')
|
|
1374
|
-
.description('Start a browser task. Pass --profile <name>; omit to use your configured default (`agents browser use <name>`
|
|
1375
|
-
.option('-p, --profile <name>', 'Browser profile to use (omit to use
|
|
1379
|
+
.description('Start a browser task. Pass --profile <name>; omit to use your configured default (set it with `agents browser use <name>` or `agents setup`). Page verbs (navigate/screenshot/…) create a task implicitly when none exists — start is for --profile/--url/--record/--title.')
|
|
1380
|
+
.option('-p, --profile <name>', 'Browser profile to use (omit to use this machine\'s configured default; set it with `agents browser use <name>`)')
|
|
1376
1381
|
.option(TASK_OPTION_FLAG, 'Task name (auto-generated short id if omitted)')
|
|
1377
1382
|
.option('--title <label>', 'Human label shown in `browser status` (defaults to first navigated host)')
|
|
1378
1383
|
.option('-e, --endpoint <name>', 'Endpoint preset (defaults to the profile\'s default)')
|
|
@@ -1445,13 +1450,85 @@ function registerTaskCommands(browser) {
|
|
|
1445
1450
|
return;
|
|
1446
1451
|
}
|
|
1447
1452
|
}
|
|
1453
|
+
// Resolve an EXPLICIT --device BEFORE any local profile/browser
|
|
1454
|
+
// resolution (PHNX-3289). The task lives on the TARGET device, so its
|
|
1455
|
+
// browser binary and profile must resolve THERE. Resolving locally first
|
|
1456
|
+
// made a browserless box fail with a misleading "No supported browser
|
|
1457
|
+
// found" (bare start) or "Profile <x> not found" (a profile that lives
|
|
1458
|
+
// only on the target) before the start ever routed to --device. This
|
|
1459
|
+
// mirrors the hub path above: the target is the authority on its own
|
|
1460
|
+
// profiles, so forward the whole start and let it validate. `local`/`self`
|
|
1461
|
+
// force a local run; a fleet-remote re-exec is already ON the target and
|
|
1462
|
+
// falls through to the local launch below.
|
|
1463
|
+
const deviceName = opts.device === 'local' || opts.device === 'self' ? undefined : opts.device;
|
|
1464
|
+
if (deviceName) {
|
|
1465
|
+
const lowered = deviceName.toLowerCase();
|
|
1466
|
+
if (lowered === 'all' || lowered === 'auto') {
|
|
1467
|
+
console.error(`--device ${deviceName} is not valid on \`agents browser start\`: a task lives on one device.\n` +
|
|
1468
|
+
`Next: agents browser start --task <name> --device <device>`);
|
|
1469
|
+
process.exit(1);
|
|
1470
|
+
}
|
|
1471
|
+
if (!isSelfHost(deviceName) && !isFleetRemoteInvocation()) {
|
|
1472
|
+
// Resolve the profile NAME (never a local browser auto-pick) so a
|
|
1473
|
+
// named profile can still be validated against the target's
|
|
1474
|
+
// declarations before we round-trip. A bare start carries no name to
|
|
1475
|
+
// validate — the target picks its own default — so it forwards
|
|
1476
|
+
// straight through, which is exactly what unblocks a browserless box.
|
|
1477
|
+
// Only resolve when the caller actually named a profile: passing an
|
|
1478
|
+
// undefined ref would let `resolveProfileRef` fall back to THIS box's
|
|
1479
|
+
// configured/auto-detected default (matching the ~4 other call sites
|
|
1480
|
+
// in this file), and validating that local name against the target
|
|
1481
|
+
// re-introduces the exact false failure this reorder fixes.
|
|
1482
|
+
const forwardProfile = opts.profile ? await resolveProfileRef(opts.profile) : undefined;
|
|
1483
|
+
if (forwardProfile) {
|
|
1484
|
+
try {
|
|
1485
|
+
assertDeviceDeclaresProfile(deviceName, forwardProfile);
|
|
1486
|
+
}
|
|
1487
|
+
catch (err) {
|
|
1488
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
1489
|
+
process.exit(1);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
try {
|
|
1493
|
+
const result = await dispatchBrowserToDevice(deviceName, browserForwardedArgv(), 'capture');
|
|
1494
|
+
process.stdout.write(result.stdout);
|
|
1495
|
+
process.stderr.write(result.stderr);
|
|
1496
|
+
if (result.code !== 0)
|
|
1497
|
+
process.exit(result.code);
|
|
1498
|
+
const taskName = opts.task || result.stdout.trim().split('\n').find((line) => line.length > 0);
|
|
1499
|
+
if (!taskName) {
|
|
1500
|
+
console.error(`Remote start on ${deviceName} produced no task name.`);
|
|
1501
|
+
process.exit(1);
|
|
1502
|
+
}
|
|
1503
|
+
bindTask(taskName, {
|
|
1504
|
+
device: deviceName,
|
|
1505
|
+
// Only the caller-named profile; the target's own default (bare
|
|
1506
|
+
// start) is opaque here, exactly as in the hub path.
|
|
1507
|
+
profile: forwardProfile ?? opts.profile,
|
|
1508
|
+
url: opts.url,
|
|
1509
|
+
sessionId: callerSessionId(),
|
|
1510
|
+
launchId: callerLaunchId(),
|
|
1511
|
+
createdAt: Date.now(),
|
|
1512
|
+
});
|
|
1513
|
+
if (!result.stderr.includes(`started on ${deviceName}`)) {
|
|
1514
|
+
console.error(`Task "${taskName}" started on ${deviceName}.`);
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
catch (err) {
|
|
1518
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
1519
|
+
process.exit(1);
|
|
1520
|
+
}
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1448
1524
|
// One resolution order for every command (RUSH-2709): `--profile default`
|
|
1449
1525
|
// means the same profile here as it does in stop / status / navigate.
|
|
1450
1526
|
// `start` is the one command that LAUNCHES, so its implicit path goes
|
|
1451
1527
|
// through ensureDefaultBrowserProfile, which additionally verifies the
|
|
1452
|
-
// resolved default can launch on THIS machine and
|
|
1453
|
-
//
|
|
1454
|
-
//
|
|
1528
|
+
// resolved default can launch on THIS machine and, when nothing launchable
|
|
1529
|
+
// resolves, throws an actionable "pick a browser in `agents setup`" error
|
|
1530
|
+
// (PHNX-3296 — it no longer silently mints an auto-chrome). A filter-only
|
|
1531
|
+
// command must not warn about the config, so that check lives only here.
|
|
1455
1532
|
let profileName;
|
|
1456
1533
|
try {
|
|
1457
1534
|
profileName = await resolveProfileRefForStart(opts.profile);
|
|
@@ -1481,17 +1558,12 @@ function registerTaskCommands(browser) {
|
|
|
1481
1558
|
process.exit(1);
|
|
1482
1559
|
}
|
|
1483
1560
|
}
|
|
1484
|
-
//
|
|
1485
|
-
//
|
|
1486
|
-
//
|
|
1487
|
-
|
|
1561
|
+
// Only the LOCAL launch reaches here — an explicit remote --device already
|
|
1562
|
+
// forwarded and returned above. A residual `deviceName` means an explicit
|
|
1563
|
+
// self target (or a fleet-remote re-exec on the target); its profile
|
|
1564
|
+
// resolved locally, so verify this box actually declares it (the
|
|
1565
|
+
// `profiles claim` hint lives in `assertDeviceDeclaresProfile`).
|
|
1488
1566
|
if (deviceName) {
|
|
1489
|
-
const lowered = deviceName.toLowerCase();
|
|
1490
|
-
if (lowered === 'all' || lowered === 'auto') {
|
|
1491
|
-
console.error(`--device ${deviceName} is not valid on \`agents browser start\`: a task lives on one device.\n` +
|
|
1492
|
-
`Next: agents browser start --task <name> --device <device>`);
|
|
1493
|
-
process.exit(1);
|
|
1494
|
-
}
|
|
1495
1567
|
try {
|
|
1496
1568
|
assertDeviceDeclaresProfile(deviceName, profileName);
|
|
1497
1569
|
}
|
|
@@ -1499,36 +1571,6 @@ function registerTaskCommands(browser) {
|
|
|
1499
1571
|
console.error(err instanceof Error ? err.message : String(err));
|
|
1500
1572
|
process.exit(1);
|
|
1501
1573
|
}
|
|
1502
|
-
if (!isSelfHost(deviceName) && !isFleetRemoteInvocation()) {
|
|
1503
|
-
try {
|
|
1504
|
-
const result = await dispatchBrowserToDevice(deviceName, browserForwardedArgv(), 'capture');
|
|
1505
|
-
process.stdout.write(result.stdout);
|
|
1506
|
-
process.stderr.write(result.stderr);
|
|
1507
|
-
if (result.code !== 0)
|
|
1508
|
-
process.exit(result.code);
|
|
1509
|
-
const taskName = opts.task || result.stdout.trim().split('\n').find((line) => line.length > 0);
|
|
1510
|
-
if (!taskName) {
|
|
1511
|
-
console.error(`Remote start on ${deviceName} produced no task name.`);
|
|
1512
|
-
process.exit(1);
|
|
1513
|
-
}
|
|
1514
|
-
bindTask(taskName, {
|
|
1515
|
-
device: deviceName,
|
|
1516
|
-
profile: profileName,
|
|
1517
|
-
url: opts.url,
|
|
1518
|
-
sessionId: callerSessionId(),
|
|
1519
|
-
launchId: callerLaunchId(),
|
|
1520
|
-
createdAt: Date.now(),
|
|
1521
|
-
});
|
|
1522
|
-
if (!result.stderr.includes(`started on ${deviceName}`)) {
|
|
1523
|
-
console.error(`Task "${taskName}" started on ${deviceName} (profile: ${profileName}).`);
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
catch (err) {
|
|
1527
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
1528
|
-
process.exit(1);
|
|
1529
|
-
}
|
|
1530
|
-
return;
|
|
1531
|
-
}
|
|
1532
1574
|
}
|
|
1533
1575
|
// Grounded login guardrail: if the opening URL is a known login-gated
|
|
1534
1576
|
// service and the resolved profile has no session for it, say so on stderr
|
|
@@ -1641,11 +1683,31 @@ function registerTaskCommands(browser) {
|
|
|
1641
1683
|
});
|
|
1642
1684
|
browser
|
|
1643
1685
|
.command('stop')
|
|
1644
|
-
.description('Stop a browser task and close its tabs; with --profile, detach the whole profile (close browser + drop cached connection)')
|
|
1686
|
+
.description('Stop a browser task and close its tabs; with --profile, detach the whole profile (close browser + drop cached connection); with --daemon, stop the browser daemon and clear a wedged socket')
|
|
1645
1687
|
.option(TASK_OPTION_FLAG, TASK_OPTION_DESC)
|
|
1646
1688
|
.option(DEVICE_ON_PAGE_VERB_FLAG, DEVICE_ON_PAGE_VERB_DESC)
|
|
1647
1689
|
.option('-p, --profile <name>', 'Detach the whole profile (incl. composite "name@endpoint") instead of stopping a single task')
|
|
1690
|
+
.option('--daemon', 'Stop the browser daemon entirely and clear a stale/wedged socket so the next start comes up clean (recovery for "Timeout waiting for browser daemon socket")')
|
|
1648
1691
|
.action(async (opts) => {
|
|
1692
|
+
if (opts.daemon) {
|
|
1693
|
+
if (opts.profile || opts.task) {
|
|
1694
|
+
console.error('--daemon stops the whole browser daemon; do not combine it with --profile or --task.');
|
|
1695
|
+
process.exit(1);
|
|
1696
|
+
}
|
|
1697
|
+
try {
|
|
1698
|
+
const result = await resetBrowserDaemon();
|
|
1699
|
+
const parts = [];
|
|
1700
|
+
parts.push(result.wasRunning ? 'Stopped browser daemon' : 'Browser daemon was not running');
|
|
1701
|
+
if (result.socketCleared)
|
|
1702
|
+
parts.push('cleared stale socket');
|
|
1703
|
+
console.log(`${parts.join('; ')}. It restarts on the next browser command.`);
|
|
1704
|
+
}
|
|
1705
|
+
catch (err) {
|
|
1706
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
1707
|
+
process.exit(1);
|
|
1708
|
+
}
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1649
1711
|
if (opts.profile) {
|
|
1650
1712
|
const profile = (await resolveProfileRef(opts.profile)) ?? opts.profile;
|
|
1651
1713
|
const response = await sendIPCRequest({
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { Command } from 'commander';
|
|
18
18
|
import { type DaemonServiceId } from '../lib/daemon-services.js';
|
|
19
|
+
/** Elapsed wall-clock seconds since `pid` started, or null if unavailable (best-effort, POSIX only). */
|
|
20
|
+
export declare function uptimeSeconds(pid: number): number | null;
|
|
19
21
|
/** One row of the roster every registered daemon service — supervisor-managed or legacy — renders in `agents daemon services`. */
|
|
20
22
|
export interface DaemonServiceRow {
|
|
21
23
|
id: DaemonServiceId;
|
package/dist/commands/daemon.js
CHANGED
|
@@ -30,6 +30,7 @@ import { parseDuration } from '../lib/hooks/cache.js';
|
|
|
30
30
|
import { registerFunnelCommand } from './funnel.js';
|
|
31
31
|
import { DEFAULT_WEBHOOK_PORT, DEFAULT_WEBHOOK_RATE_LIMIT, addHostedReceiver, getDaemonWebhooksConfigPath, hostedReceiverPort, readDaemonWebhooksConfig, removeHostedReceiver, } from '../lib/daemon-webhooks.js';
|
|
32
32
|
import { parseFunnelPort } from '../lib/funnel.js';
|
|
33
|
+
import { parseEtimeToSeconds } from '../lib/secrets/reaper.js';
|
|
33
34
|
/**
|
|
34
35
|
* Provably absent, or null when unknowable.
|
|
35
36
|
*
|
|
@@ -256,13 +257,16 @@ function registryScopedDuplicates(processes, ownerPid) {
|
|
|
256
257
|
return processes.filter((p) => registered.has(p.pid));
|
|
257
258
|
}
|
|
258
259
|
/** Elapsed wall-clock seconds since `pid` started, or null if unavailable (best-effort, POSIX only). */
|
|
259
|
-
function uptimeSeconds(pid) {
|
|
260
|
+
export function uptimeSeconds(pid) {
|
|
260
261
|
if (process.platform === 'win32')
|
|
261
262
|
return null;
|
|
262
263
|
try {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
264
|
+
// `-o etimes=` is a GNU/procps keyword macOS/BSD `ps` rejects with a
|
|
265
|
+
// non-zero exit (`ps: etimes: keyword not found`), so `agents daemon status`
|
|
266
|
+
// errored out entirely on macOS. `etime` (`[[dd-]hh:]mm:ss`) is the portable
|
|
267
|
+
// POSIX field; `parseEtimeToSeconds` (shared with the keychain reaper) parses it.
|
|
268
|
+
const out = execFileSync('ps', ['-o', 'etime=', '-p', String(pid)], { encoding: 'utf-8' }).trim();
|
|
269
|
+
return parseEtimeToSeconds(out);
|
|
266
270
|
}
|
|
267
271
|
catch {
|
|
268
272
|
return null;
|