@phnx-labs/agents-cli 1.20.76 → 1.20.77
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 +162 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/exec.js +58 -16
- package/dist/commands/routines.js +271 -13
- package/dist/commands/secrets.js +59 -17
- package/dist/commands/sessions-browser.js +11 -1
- package/dist/commands/sessions-picker.d.ts +14 -1
- package/dist/commands/sessions-picker.js +168 -15
- package/dist/commands/sessions.d.ts +23 -4
- package/dist/commands/sessions.js +128 -38
- package/dist/commands/ssh.d.ts +13 -0
- package/dist/commands/ssh.js +39 -2
- package/dist/index.js +2 -1
- package/dist/lib/activity.d.ts +5 -4
- package/dist/lib/activity.js +450 -36
- package/dist/lib/cloud/session-index.js +2 -2
- package/dist/lib/daemon.d.ts +10 -1
- package/dist/lib/daemon.js +99 -12
- package/dist/lib/devices/fleet.d.ts +16 -1
- package/dist/lib/devices/fleet.js +10 -2
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +16 -0
- package/dist/lib/exec.js +27 -4
- package/dist/lib/feed.d.ts +7 -1
- package/dist/lib/feed.js +96 -6
- package/dist/lib/heal.d.ts +7 -4
- package/dist/lib/heal.js +10 -22
- package/dist/lib/hosts/dispatch.d.ts +9 -0
- package/dist/lib/hosts/dispatch.js +24 -4
- package/dist/lib/hosts/passthrough.js +7 -2
- package/dist/lib/hosts/remote-cmd.d.ts +11 -0
- package/dist/lib/hosts/remote-cmd.js +31 -8
- package/dist/lib/hosts/remote-session-id.d.ts +45 -0
- package/dist/lib/hosts/remote-session-id.js +84 -0
- package/dist/lib/hosts/run-target.d.ts +4 -0
- package/dist/lib/hosts/run-target.js +5 -1
- package/dist/lib/hosts/session-index.js +3 -3
- package/dist/lib/menubar/install-menubar.d.ts +9 -0
- package/dist/lib/menubar/install-menubar.js +14 -0
- package/dist/lib/menubar/notify-desktop.d.ts +44 -0
- package/dist/lib/menubar/notify-desktop.js +78 -0
- package/dist/lib/overdue.d.ts +4 -5
- package/dist/lib/overdue.js +8 -41
- package/dist/lib/routine-notify.d.ts +76 -0
- package/dist/lib/routine-notify.js +190 -0
- package/dist/lib/routines-placement.d.ts +38 -0
- package/dist/lib/routines-placement.js +79 -0
- package/dist/lib/routines-project.d.ts +97 -0
- package/dist/lib/routines-project.js +349 -0
- package/dist/lib/routines.d.ts +68 -0
- package/dist/lib/routines.js +74 -7
- package/dist/lib/runner.d.ts +12 -2
- package/dist/lib/runner.js +150 -25
- package/dist/lib/sandbox.js +10 -0
- package/dist/lib/secrets/account-token.d.ts +20 -0
- package/dist/lib/secrets/account-token.js +64 -0
- package/dist/lib/secrets/agent.d.ts +9 -4
- package/dist/lib/secrets/agent.js +30 -20
- package/dist/lib/secrets/bundles.d.ts +20 -6
- package/dist/lib/secrets/bundles.js +70 -34
- package/dist/lib/secrets/index.d.ts +11 -2
- package/dist/lib/secrets/index.js +21 -9
- package/dist/lib/secrets/session-store.d.ts +6 -2
- package/dist/lib/secrets/session-store.js +65 -15
- package/dist/lib/secrets/unlock-hints.d.ts +27 -0
- package/dist/lib/secrets/unlock-hints.js +36 -0
- package/dist/lib/session/active.d.ts +10 -0
- package/dist/lib/session/active.js +67 -4
- package/dist/lib/session/db.d.ts +6 -0
- package/dist/lib/session/db.js +83 -6
- package/dist/lib/session/discover.d.ts +13 -1
- package/dist/lib/session/discover.js +91 -3
- package/dist/lib/session/parse.js +11 -2
- package/dist/lib/session/prompt.d.ts +7 -0
- package/dist/lib/session/prompt.js +37 -0
- package/dist/lib/session/provenance.d.ts +7 -0
- package/dist/lib/session/render.js +18 -16
- package/dist/lib/session/state.d.ts +4 -0
- package/dist/lib/session/state.js +93 -11
- package/dist/lib/session/types.d.ts +25 -1
- package/dist/lib/session/types.js +8 -0
- package/dist/lib/state.d.ts +7 -3
- package/dist/lib/state.js +20 -5
- package/dist/lib/types.d.ts +10 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,167 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.20.77
|
|
4
|
+
|
|
5
|
+
- **Interactive `agents run --host` now tracks the real session for every agent,
|
|
6
|
+
not just Claude.** Codex, Kimi, Grok, and Gemini coin their own session id and
|
|
7
|
+
reject a caller-supplied one, so an interactive host run of any of them showed a
|
|
8
|
+
stale/absent id locally — `agents sessions` couldn't surface it and a dropped
|
|
9
|
+
link couldn't auto-reconnect it (RUSH-2033 fixed only the Claude `--session-id`
|
|
10
|
+
path). The launcher now forwards one correlation key it controls
|
|
11
|
+
(`AGENT_LAUNCH_ID`); the remote `agents run` adopts that key
|
|
12
|
+
(`resolveLaunchId`), so its SessionStart hook records the agent's real session id
|
|
13
|
+
under it. After the stream the launcher does one ssh read of the remote hook
|
|
14
|
+
record, resolves the real id by launch id (`resolveRemoteSessionId` /
|
|
15
|
+
`pickRemoteSessionId`), registers it in the local session index, and reconnects
|
|
16
|
+
against it on a dropped link. Claude still forces its own id up front and is
|
|
17
|
+
unchanged. Source: `apps/cli/src/lib/hosts/remote-session-id.ts`,
|
|
18
|
+
`resolveLaunchId` in `apps/cli/src/lib/exec.ts`, and the interactive `--host`
|
|
19
|
+
branch in `apps/cli/src/commands/exec.ts`. (RUSH-2034)
|
|
20
|
+
|
|
21
|
+
- **Project routines can opt into daemon firing with source tracking, sync, and
|
|
22
|
+
host placement (RUSH-2035).** Project YAML under `<project>/.agents/routines/*.yml`
|
|
23
|
+
stays inspection-only until `agents routines enable-project` (with interactive /
|
|
24
|
+
`--yes` approval) records the project on `meta.routines.projects` and materialises
|
|
25
|
+
copies into `~/.agents/routines/` with a `source:` block (`projectPath`, git
|
|
26
|
+
`repo`/`branch`/`commit`). `agents routines sync` (and daemon start/SIGHUP reload)
|
|
27
|
+
refreshes those copies when project YAML changes; `disable-project` / `projects`
|
|
28
|
+
manage the allowlist. New `hostStrategy: local|host|fleet|cloud` (CLI `--placement`)
|
|
29
|
+
chooses where the job body runs: local, a named `--run-on` host, one online fleet
|
|
30
|
+
device per fire (no cross-device double-fire — off-box strategies auto-pin
|
|
31
|
+
`devices`), or the agent's native cloud provider. `--host` remains the remote-
|
|
32
|
+
management passthrough. List/JSON surfaces source repo/branch and strategy. Source:
|
|
33
|
+
`apps/cli/src/lib/routines.ts`, `routines-project.ts`, `routines-placement.ts`,
|
|
34
|
+
`runner.ts`, `daemon.ts`, `commands/routines.ts`, `docs/03-routines.md`.
|
|
35
|
+
|
|
36
|
+
- **Factory interactive launches default to `--mode auto` (RUSH-2038).** The Factory VS Code extension no longer inherits the CLI's `plan` default for interactive terminal launches. Codex, Claude, Gemini, Cursor, OpenCode, and Antigravity now start in `auto` (writable-but-gated) when opened from Factory without an explicit mode, so the agent can edit files instead of stalling in a read-only sandbox. Source: `apps/factory/src/core/agents.ts`.
|
|
37
|
+
|
|
38
|
+
- **Codex approval blocks now notify you.** A headless or terminal Codex agent
|
|
39
|
+
blocked on an approval prompt used to stall silently — the feed/notification path
|
|
40
|
+
only fired for Claude. Codex emits `PermissionRequest` (not Claude's
|
|
41
|
+
`Notification`), which the `feed-publish` hook now handles: it publishes an
|
|
42
|
+
approval-class block with a high cost-of-delay and a `deny` safe-default, so the
|
|
43
|
+
blocked agent surfaces on `agents feed` and `agents feed --dispatch` pages the
|
|
44
|
+
phone as urgent. A Codex approval card clears once the approved tool runs, via a
|
|
45
|
+
matcher-less `PostToolUse` clear hook registered **for Codex only** — so Claude's
|
|
46
|
+
card lifetime (its `permission_prompt`/`idle_prompt`/`elicitation_dialog`
|
|
47
|
+
notification blocks persist until `Stop`/`SessionEnd`) and per-tool overhead are
|
|
48
|
+
exactly as before. The other feed hooks are now registered for Codex too, not
|
|
49
|
+
Claude only. The Factory extension bridges the same waiting state to an
|
|
50
|
+
edge-triggered VS Code notification with a "Focus terminal" action. Claude's path
|
|
51
|
+
is unchanged. Source: `FEED_PUBLISH_HOOK_SCRIPT` / `ensureFeedPublishHook` in
|
|
52
|
+
`apps/cli/src/lib/feed.ts`, `apps/factory/src/core/waitingNotifier.ts`.
|
|
53
|
+
(RUSH-2039)
|
|
54
|
+
|
|
55
|
+
- **`agents fleet ping` now completes within ~15 s per device and ~30 s total, even when several fleet devices are offline or slow (RUSH-2041).** The per-device remote auth probe timeout was lowered from 60 s to 15 s (matching the `fleet status` version-probe budget, which is enough for the ~8 s provider-fetch inside the local auth probe). `fanOutDevices` gained an optional `perDeviceTimeoutMs` that races each probe against a deadline and records it as `failed: timed out` instead of hanging. `runFleetPing` now also wraps the entire fan-out in a 30 s hard cap so the command can never outlast a reasonable budget. Offline devices are now reported promptly as failed/timed-out rather than left hanging in the spinner. Source: `apps/cli/src/lib/devices/fleet.ts` (`fanOutDevices`, `FanOutDeviceOptions`), `apps/cli/src/commands/ssh.ts` (`probeRemoteAuth`, `runFleetPing`).
|
|
56
|
+
|
|
57
|
+
- **`agents sessions` surfaces checklist progress in every list/preview (RUSH-2045).** The picker preview, `--active` rows (local + cross-machine), flat `doing` cell, and metadata-only previews now show compact `✓done/total · current step` from `SessionMeta.todos` / `ActiveSession.todos`, plus the originating prompt and a directories-touched activity line. Active/cross-machine rows also show label + clickable project/ticket alongside the agent short id. Covers interactive, headless, teams, and sub-agent sessions that share the preview infra. Source: `apps/cli/src/commands/sessions-picker.ts`, `apps/cli/src/commands/sessions.ts`.
|
|
58
|
+
|
|
59
|
+
- **Checklist completions emit a feed event (RUSH-2046).** When an agent marks a
|
|
60
|
+
task-checklist item done, the `11-activity-log.py` hook now appends a
|
|
61
|
+
`task.completed` milestone to the session activity log (and `checklist.created`
|
|
62
|
+
the first time a checklist appears), so completions show in `agents feed` and the
|
|
63
|
+
unified `agents events` stream with the item subject and running `N/M`. Detection
|
|
64
|
+
folds the transcript across harnesses — Claude `TaskUpdate`/`TodoWrite`, Grok
|
|
65
|
+
`todo_write`, Codex `update_plan` — so a completion is recognized regardless of
|
|
66
|
+
which agent produced it. Source: `apps/cli/src/lib/activity.ts` (incl. the embedded
|
|
67
|
+
hook), `apps/cli/src/lib/events.ts`, `apps/cli/src/commands/feed.ts`.
|
|
68
|
+
|
|
69
|
+
- **Actor provenance now survives the SSH hop.** A run dispatched to another host
|
|
70
|
+
(`agents run --host`, a remote `agents teams` supervisor, or any `--host`
|
|
71
|
+
passthrough) used to drop the resolved actor at the SSH boundary, so the remote
|
|
72
|
+
re-resolved it from the *originating* box's `SSH_CONNECTION` and mis-credited the
|
|
73
|
+
work to the shared machine or `UNRESOLVED@<host>`. The dispatch layer now forwards
|
|
74
|
+
`AGENTS_ACTOR*` / `GIT_*` across the wire (POSIX `export` and Windows `$env:`
|
|
75
|
+
alike), so the remote inherits the origin identity instead of re-resolving. A
|
|
76
|
+
caller-supplied env value still wins on collision (mirrors `buildExecEnv`).
|
|
77
|
+
Source: `withActorEnv` in `apps/cli/src/lib/hosts/dispatch.ts`, wired into
|
|
78
|
+
`launchDetached` / `runInteractiveOnHost` and the `--host` passthrough. (RUSH-2028)
|
|
79
|
+
|
|
80
|
+
- **A Linux-driven release now auto-discovers its macOS sign host instead of
|
|
81
|
+
hardcoding `mac-mini`.** `scripts/remote-sign-mac.sh` previously defaulted
|
|
82
|
+
`SIGN_HOST` to `mac-mini`, so a release from a Linux box failed outright whenever
|
|
83
|
+
that one appliance was offline — the recurring reason a release stalled and a
|
|
84
|
+
human had to finish it by hand. With `SIGN_HOST` unset the script now reads
|
|
85
|
+
`agents devices list --json`, keeps the reachable/online macOS devices, and picks
|
|
86
|
+
the first that answers `ssh` in preference order `mac-mini` → `zion` → any other
|
|
87
|
+
online Mac. `mac-mini` stays first because it signs headlessly (no Touch ID);
|
|
88
|
+
`zion` (the interactive Mac) is the fallback. An explicit `SIGN_HOST=<host>` still
|
|
89
|
+
pins one and skips discovery, and when no reachable Mac qualifies the script fails
|
|
90
|
+
with the ordered list it tried rather than hanging on a dead host. Source:
|
|
91
|
+
`apps/cli/scripts/remote-sign-mac.sh`.
|
|
92
|
+
|
|
93
|
+
- **An agent launch never raises a Touch ID sheet.** On macOS, starting an agent
|
|
94
|
+
terminal or firing a routine could pop several biometric prompts in a row, because
|
|
95
|
+
each keychain read runs in its own helper process and the biometric assertion never
|
|
96
|
+
reuses across processes. Two causes: `interactiveUnlock` defaulted to true whenever
|
|
97
|
+
an agent name was present, which let an agent-initiated read fall through the
|
|
98
|
+
`agentOnly` guard; and `isHeadlessSecretsContext` recognized the `headless` and
|
|
99
|
+
`teams` runtimes but not `terminal`, which is what an interactive run sets. Agent
|
|
100
|
+
launches now resolve broker-only and a locked bundle fails fast naming
|
|
101
|
+
`agents secrets unlock <bundle>`; `AGENTS_SECRETS_NO_PROMPT=1` is no longer needed
|
|
102
|
+
as a workaround. `agents secrets get/export/exec` typed in a **plain shell** still
|
|
103
|
+
prompts — it carries no `AGENTS_RUNTIME`, so the guard does not apply. Run beneath
|
|
104
|
+
an agent it refuses, because there the agent is the caller. This narrows the
|
|
105
|
+
agent-triggered approval added in RUSH-2032, which is unreleased.
|
|
106
|
+
|
|
107
|
+
- **`release.sh` now borrows the npm token from a primary device when the local box
|
|
108
|
+
has none, so a Linux-driven release stops asking a human to approve a token.**
|
|
109
|
+
Token resolution was env → local `npmjs.com` bundle → *die*. On a fleet box whose
|
|
110
|
+
own keychain holds no npm token, that dead end pushed agents to hand-move a
|
|
111
|
+
credential between machines (and correctly get gated on it). A third step now
|
|
112
|
+
resolves the bundle **ephemerally from a primary device over SSH** —
|
|
113
|
+
`agents secrets exec npmjs.com --host <host>`, which resolves on the remote and
|
|
114
|
+
injects into the run only, never storing the token locally. It tries `SECRET_HOST`
|
|
115
|
+
first, then `zion`, then `mac-mini`, and fails with the list it tried if none
|
|
116
|
+
answer. Combined with the sign-host auto-discovery, a Linux box can now cut a full
|
|
117
|
+
release end-to-end given a reachable Mac for signing and any reachable device that
|
|
118
|
+
holds the npm token. Source: `apps/cli/scripts/release.sh`.
|
|
119
|
+
|
|
120
|
+
- **Branded, actionable daemon notifications on the routine lifecycle (RUSH-2030).**
|
|
121
|
+
Daemon desktop notifications (overdue routines, config heal, the no-credential
|
|
122
|
+
warning) now route through the `MenubarHelper.app` companion instead of raw
|
|
123
|
+
AppleScript, so they carry the agents-cli mark rather than the generic Script
|
|
124
|
+
Editor icon; they degrade to `osascript`/`notify-send` only when the helper is
|
|
125
|
+
not installed. The daemon also notifies when a routine **starts** and
|
|
126
|
+
**finishes** (success/failure, with the report's first line or the error reason
|
|
127
|
+
folded in), suppressing command-housekeeping start/success pings to avoid spam.
|
|
128
|
+
Clicking a finish notification opens the run report/log; start/overdue open the
|
|
129
|
+
runs folder. Source: `apps/cli/src/lib/menubar/notify-desktop.ts`,
|
|
130
|
+
`apps/cli/src/lib/routine-notify.ts`, `apps/cli/src/lib/daemon.ts`,
|
|
131
|
+
`apps/cli/menubar/Sources/MenubarHelper/PromptPanel.swift`.
|
|
132
|
+
|
|
133
|
+
- **`agents secrets status` now suggests which bundles to unlock.** It reads the existing `secrets.get` audit events and surfaces bundles you keep getting a Touch ID prompt for — read from the keychain (not served silently by the broker/session) 3+ times in the last 7 days and not currently held — with a ready `agents secrets unlock <name>` command. `never`/no-ACL bundles (which never prompt) are excluded, and the hint is best-effort so it never breaks `status`. Source: `apps/cli/src/lib/secrets/unlock-hints.ts`, `apps/cli/src/commands/secrets.ts`.
|
|
134
|
+
|
|
135
|
+
- **`agents sessions <id>` with a short/partial id resolves by id only — no more
|
|
136
|
+
"Multiple sessions match" from fuzzy content.** A complete UUID already resolved
|
|
137
|
+
by id, but a bare hex short-id like `d3470b57` was not caught by
|
|
138
|
+
`isCompleteSessionId`, so it fell through to the ranked content search and
|
|
139
|
+
surfaced every transcript that merely MENTIONED the string (a resume prompt
|
|
140
|
+
echoes the parent id into the body of many later sessions) — a real view id
|
|
141
|
+
returned a list of unrelated sessions. Any id-shaped query — complete id OR hex
|
|
142
|
+
short-id/prefix (`looksLikeSessionId`) — now resolves through the index by id in
|
|
143
|
+
both `resolveSessionQuery` and the `renderOneSession` content-widen gate, and
|
|
144
|
+
reports "no session found" when nothing matches instead of content-searching.
|
|
145
|
+
Free-text phrases keep the ranked search path. Source:
|
|
146
|
+
`apps/cli/src/commands/sessions.ts`.
|
|
147
|
+
|
|
148
|
+
- **`agents sessions --active` attributes the initiating device for SSH-launched
|
|
149
|
+
sessions.** A session started by ssh'ing into a box (common for tmux-hosted
|
|
150
|
+
runs) used to render as `local` with no origin, because the tmux discovery path
|
|
151
|
+
stamped a `transport:'local'` placeholder that made provenance enrichment skip
|
|
152
|
+
it. Enrichment now probes the pane process's env and upgrades the row to `ssh`
|
|
153
|
+
with the real origin, then resolves the SSH client IP against the device
|
|
154
|
+
registry into `provenance.origin` (`{ device, user? }`). Both the flat listing
|
|
155
|
+
and the interactive browser read `ssh←<device>` (e.g. `ssh←zion`); an
|
|
156
|
+
unregistered IP stays bare `ssh`. Answers "which box launched this session"
|
|
157
|
+
without scraping `ps`/`who`/`tailscale`. Source:
|
|
158
|
+
`apps/cli/src/lib/session/active.ts`, `apps/cli/src/lib/session/provenance.ts`,
|
|
159
|
+
`apps/cli/src/commands/sessions.ts`, `apps/cli/src/commands/sessions-browser.ts`.
|
|
160
|
+
|
|
161
|
+
- **`agents sessions --include user` / `--first` / `--last` now count genuine user turns, not harness-injected scaffolding (#1550).** A Claude session opened with a `!`-prefix command (e.g. `j <dir>`) stores `<bash-input>`/`<bash-stdout>` as `role=user` records, and `<system-reminder>`/`<task-notification>`/`<command-*>`/hook-feedback/skill bodies land the same way — so `--include user --first 3` returned the jump command and its shell output before the real ask, and every consumer of `--include user` (the `verify-work-complete` Stop hook's "original request" self-audit, `session-recall`) inherited the noise. `parseSession` now flags these injected `role=user` events `_synthetic` at its central post-parse chokepoint via one shared classifier (`isSyntheticUserMessage`), so turn slicing (`applyTurnSlice`) and role filtering (`roleOfEvent`) skip them; they stay in the default/`--markdown` stream for full fidelity. Claude-specific in practice — Codex/Gemini/OpenCode/Grok/Kimi/Rush route shell output to `tool_result`, never to `role=user`, and Droid's `<system-reminder>` was already dropped — but the classifier is cross-harness by construction. Source: `apps/cli/src/lib/session/{prompt,parse,render,types}.ts`.
|
|
162
|
+
|
|
163
|
+
- **Routine/daemon Claude runs authenticate the rotation-pinned account via its own long-lived setup-token — fixes fleet-wide daily logout.** Claude Code's interactive OAuth session uses single-use *rotating* refresh tokens: when one fleet machine refreshes, the server invalidates that account's token on every other machine, so unattended boxes 401 and drop (Claude Code #25609/#56339). A `claude setup-token` is a 1-year, non-rotating token that sidesteps this. The daemon now injects every `CLAUDE_CODE_OAUTH_TOKEN_<account>` present in the `claude` bundle (not just the one ambient token), and a routine spawn selects the token matching the account its version-home is pinned to (`runner.ts` `buildRoutineSpawnEnv` → `resolveAccountSetupToken`), so each unattended account authenticates with its own setup-token instead of the rotating interactive session. Works on macOS too, where the prior drop-based path was inert. Inert (no behavior change) until per-account setup-tokens are stored in the no-ACL `claude` bundle. Interactive `agents run` and remote `--host` dispatch are unchanged (out of scope; noted for follow-up). Source: `apps/cli/src/lib/secrets/account-token.ts`, `apps/cli/src/lib/runner.ts`, `apps/cli/src/lib/daemon.ts`, `apps/cli/src/lib/sandbox.ts`.
|
|
164
|
+
|
|
3
165
|
## 1.20.76
|
|
4
166
|
|
|
5
167
|
- **The routines daemon can read a `never`/no-ACL secrets bundle headlessly again — fixes a false "no Claude credential" alert.** The headless secrets guard (`readAndResolveBundleEnv`'s `agentOnly` branch) threw for every keychain-backed bundle absent from the broker, but a `never`/no-ACL bundle carries no biometry ACL — its reads raise no Touch ID sheet, so blocking it served no purpose. That wrongly blocked the automation-only `claude` bundle the routines daemon reads at startup (`readDaemonClaudeOAuthToken`), leaving every scheduled Claude routine token-less, and — on the new auth-failure alert path — firing "no Claude credential" on each daemon start even when the bundle was configured correctly. The guard now exempts `never`/no-ACL bundles (policy learned via a prompt-less metadata read), matching the existing file-backend exemption. Source: `apps/cli/src/lib/secrets/bundles.ts`.
|
package/dist/bin/agents
CHANGED
|
Binary file
|
package/dist/commands/exec.js
CHANGED
|
@@ -20,8 +20,9 @@ import { sshResolve } from '../lib/hosts/ssh-config.js';
|
|
|
20
20
|
import * as fs from 'fs';
|
|
21
21
|
import * as path from 'path';
|
|
22
22
|
import * as os from 'os';
|
|
23
|
-
import { randomUUID } from 'crypto';
|
|
24
23
|
import { spawnSync } from 'child_process';
|
|
24
|
+
import { randomUUID } from 'crypto';
|
|
25
|
+
import { isSessionTrackedAgent } from '../lib/session/types.js';
|
|
25
26
|
/** Distinguish a terminal account-picker marker from an explicit @version pin. */
|
|
26
27
|
export function parseRunAccountPickerRequest(agentSpec) {
|
|
27
28
|
const requested = agentSpec.endsWith('@');
|
|
@@ -877,7 +878,7 @@ export function registerRunCommand(program) {
|
|
|
877
878
|
process.exit(1);
|
|
878
879
|
}
|
|
879
880
|
const hostName = hostGiven[0];
|
|
880
|
-
const { resolveHostRunTarget, dispatchPromptToHost, HostResolutionError } = await import('../lib/hosts/run-target.js');
|
|
881
|
+
const { resolveHostRunTarget, resolveHostSessionId, dispatchPromptToHost, HostResolutionError } = await import('../lib/hosts/run-target.js');
|
|
881
882
|
const { runInteractiveOnHost } = await import('../lib/hosts/dispatch.js');
|
|
882
883
|
const { registerInteractiveHostSession } = await import('../lib/hosts/session-index.js');
|
|
883
884
|
const { RUN_OPTION_REJECT_MESSAGES } = await import('../lib/hosts/remote-cmd.js');
|
|
@@ -1035,10 +1036,21 @@ export function registerRunCommand(program) {
|
|
|
1035
1036
|
process.exit(1);
|
|
1036
1037
|
}
|
|
1037
1038
|
// Mirror the local path (lib/exec.ts): only Claude accepts a forced
|
|
1038
|
-
// `--session-id`.
|
|
1039
|
-
//
|
|
1040
|
-
//
|
|
1041
|
-
const hostSessionId = runAgent
|
|
1039
|
+
// `--session-id`. Adopt the caller's id when present; otherwise mint
|
|
1040
|
+
// one here. Registering that same id keeps the local index aligned
|
|
1041
|
+
// with the remote agent. On resume, don't mint a new one.
|
|
1042
|
+
const hostSessionId = resolveHostSessionId(runAgent, resumeId, options.sessionId);
|
|
1043
|
+
// For every OTHER agent the remote coins its own id, which we can't
|
|
1044
|
+
// know up front. Forward a launch id we control as AGENT_LAUNCH_ID:
|
|
1045
|
+
// the remote `agents run` adopts it (exec.ts resolveLaunchId) and its
|
|
1046
|
+
// SessionStart hook records the real id under that exact key, so after
|
|
1047
|
+
// the stream we resolve the id by one ssh read of the remote hook
|
|
1048
|
+
// record — the same launch-id join used locally (RUSH-2034). Not
|
|
1049
|
+
// needed for Claude (id forced) or resume (id already known).
|
|
1050
|
+
const correlationLaunchId = !hostSessionId && !resumeId && isSessionTrackedAgent(runAgent) ? randomUUID() : undefined;
|
|
1051
|
+
const hostEnv = correlationLaunchId
|
|
1052
|
+
? [...options.env, `AGENT_LAUNCH_ID=${correlationLaunchId}`]
|
|
1053
|
+
: options.env;
|
|
1042
1054
|
if (hostSessionId) {
|
|
1043
1055
|
registerInteractiveHostSession({
|
|
1044
1056
|
cwd: process.cwd(),
|
|
@@ -1058,7 +1070,7 @@ export function registerRunCommand(program) {
|
|
|
1058
1070
|
mode: options.mode,
|
|
1059
1071
|
model: options.model,
|
|
1060
1072
|
effort: options.effort,
|
|
1061
|
-
env:
|
|
1073
|
+
env: hostEnv,
|
|
1062
1074
|
addDir: hostAddDirs,
|
|
1063
1075
|
json: options.json,
|
|
1064
1076
|
verbose: options.verbose,
|
|
@@ -1074,14 +1086,41 @@ export function registerRunCommand(program) {
|
|
|
1074
1086
|
forceInteractive: options.interactive,
|
|
1075
1087
|
copyCreds: hostCopyCreds,
|
|
1076
1088
|
});
|
|
1089
|
+
// Resolve a non-Claude agent's REAL remote session id now the run has
|
|
1090
|
+
// booted (its hook has fired on the peer): one ssh read of the remote
|
|
1091
|
+
// hook record, keyed by the launch id we forwarded. Register it so the
|
|
1092
|
+
// run shows in `agents sessions` and can be reconnected/focused —
|
|
1093
|
+
// closing the non-Claude gap RUSH-2033 left. Best-effort: an
|
|
1094
|
+
// unreachable host or a not-yet-landed record leaves the run un-mapped
|
|
1095
|
+
// rather than mis-mapped.
|
|
1096
|
+
let resolvedRemoteId;
|
|
1097
|
+
if (correlationLaunchId) {
|
|
1098
|
+
const { resolveRemoteSessionId } = await import('../lib/hosts/remote-session-id.js');
|
|
1099
|
+
const { sshTargetFor } = await import('../lib/hosts/types.js');
|
|
1100
|
+
try {
|
|
1101
|
+
resolvedRemoteId = resolveRemoteSessionId(sshTargetFor(host), correlationLaunchId);
|
|
1102
|
+
}
|
|
1103
|
+
catch {
|
|
1104
|
+
/* ssh read is best-effort — keep the run un-mapped, never guess */
|
|
1105
|
+
}
|
|
1106
|
+
if (resolvedRemoteId) {
|
|
1107
|
+
registerInteractiveHostSession({
|
|
1108
|
+
cwd: process.cwd(),
|
|
1109
|
+
host: host.name,
|
|
1110
|
+
agent: runAgent,
|
|
1111
|
+
sessionId: resolvedRemoteId,
|
|
1112
|
+
name: options.name,
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1077
1116
|
// A network drop kills the local ssh client (exit 255) but the remote
|
|
1078
1117
|
// agent survives in its detached tmux session. With a known session id
|
|
1079
|
-
// (Claude,
|
|
1080
|
-
//
|
|
1081
|
-
// the
|
|
1082
|
-
//
|
|
1083
|
-
//
|
|
1084
|
-
const reconnectId = hostSessionId ?? resumeId;
|
|
1118
|
+
// (Claude's forced id, a resumed run, or a non-Claude id we just
|
|
1119
|
+
// resolved from the remote hook record) and a tmux-hosted run,
|
|
1120
|
+
// re-attach the live pane automatically instead of exiting — the user
|
|
1121
|
+
// never has to notice the drop and `agents sessions focus` by hand.
|
|
1122
|
+
// `raw` runs aren't tmux wrapped, so there is nothing to reconnect to.
|
|
1123
|
+
const reconnectId = hostSessionId ?? resolvedRemoteId ?? resumeId;
|
|
1085
1124
|
if (reconnectId && !isRaw) {
|
|
1086
1125
|
const { reconnectInteractiveSession, SSH_CONN_FAILURE } = await import('../lib/hosts/reconnect.js');
|
|
1087
1126
|
if (exitCode === SSH_CONN_FAILURE) {
|
|
@@ -1127,6 +1166,7 @@ export function registerRunCommand(program) {
|
|
|
1127
1166
|
remoteCwd: hostCwd,
|
|
1128
1167
|
name: options.name,
|
|
1129
1168
|
resume: resumeId,
|
|
1169
|
+
sessionId: options.sessionId,
|
|
1130
1170
|
follow: options.follow !== false,
|
|
1131
1171
|
passthroughArgs,
|
|
1132
1172
|
copyCreds: hostCopyCreds,
|
|
@@ -1883,11 +1923,13 @@ export function registerRunCommand(program) {
|
|
|
1883
1923
|
else {
|
|
1884
1924
|
const { bundle, env: bundleEnv } = readAndResolveBundleEnv(bundleName, {
|
|
1885
1925
|
caller: `agent ${agent}`,
|
|
1926
|
+
agent,
|
|
1886
1927
|
keys: secretsKeysSubset,
|
|
1887
1928
|
allowExpired: options.allowExpired,
|
|
1888
|
-
//
|
|
1889
|
-
//
|
|
1890
|
-
//
|
|
1929
|
+
// The harness identity scopes any cached grant. It no longer lets the
|
|
1930
|
+
// requesting agent wait for interactive approval — an agent launch
|
|
1931
|
+
// resolves broker-only and fails fast naming
|
|
1932
|
+
// `agents secrets unlock <bundle>` (bundles.ts:interactiveUnlock).
|
|
1891
1933
|
agentOnly: isHeadlessSecretsContext(),
|
|
1892
1934
|
});
|
|
1893
1935
|
const entries = describeBundle(bundle);
|