@phnx-labs/agents-cli 1.22.46 → 1.22.49
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 +663 -0
- package/LICENSE +102 -182
- package/README.md +96 -49
- package/dist/bootstrap.js +4 -2
- package/dist/cli/command-registry.d.ts +2 -1
- package/dist/cli/command-registry.js +6 -2
- package/dist/commands/accounts.d.ts +58 -0
- package/dist/commands/accounts.js +243 -25
- package/dist/commands/artifacts.js +7 -6
- package/dist/commands/attach.js +1 -1
- package/dist/commands/auth.js +7 -5
- package/dist/commands/browser-sessions-picker.js +9 -7
- package/dist/commands/browser.js +711 -62
- package/dist/commands/config.d.ts +0 -1
- package/dist/commands/config.js +26 -24
- package/dist/commands/cost.d.ts +1 -1
- package/dist/commands/daemon.d.ts +21 -0
- package/dist/commands/daemon.js +147 -12
- package/dist/commands/detach.d.ts +13 -0
- package/dist/commands/detach.js +11 -8
- package/dist/commands/exec.js +107 -53
- package/dist/commands/feed-watch.d.ts +2 -0
- package/dist/commands/feed-watch.js +28 -0
- package/dist/commands/feed.js +27 -0
- package/dist/commands/feedback.js +3 -19
- package/dist/commands/focus.d.ts +14 -0
- package/dist/commands/focus.js +48 -5
- package/dist/commands/go.d.ts +3 -0
- package/dist/commands/go.js +16 -3
- package/dist/commands/insights.d.ts +1 -1
- package/dist/commands/insights.js +2 -2
- package/dist/commands/inspect.js +2 -2
- package/dist/commands/lease.d.ts +1 -1
- package/dist/commands/lease.js +7 -6
- package/dist/commands/mailboxes.js +4 -3
- package/dist/commands/menubar.js +11 -4
- package/dist/commands/projects.d.ts +16 -1
- package/dist/commands/projects.js +58 -64
- package/dist/commands/resume.js +1 -1
- package/dist/commands/route.js +4 -4
- package/dist/commands/routines.js +4 -3
- package/dist/commands/run-broadcast.d.ts +1 -1
- package/dist/commands/run-broadcast.js +1 -1
- package/dist/commands/sessions-picker-factory.d.ts +2 -1
- package/dist/commands/sessions-picker-factory.js +1 -1
- package/dist/commands/sessions-picker.d.ts +22 -0
- package/dist/commands/sessions-picker.js +48 -9
- package/dist/commands/sessions-stop.d.ts +24 -0
- package/dist/commands/sessions-stop.js +66 -0
- package/dist/commands/sessions-trace.d.ts +82 -0
- package/dist/commands/sessions-trace.js +387 -0
- package/dist/commands/sessions.js +5 -0
- package/dist/commands/setup-secrets.js +23 -0
- package/dist/commands/share.d.ts +20 -5
- package/dist/commands/share.js +152 -52
- package/dist/commands/ssh.d.ts +15 -0
- package/dist/commands/ssh.js +243 -18
- package/dist/commands/teams.js +34 -2
- package/dist/commands/traces.d.ts +2 -0
- package/dist/commands/traces.js +184 -0
- package/dist/commands/view.d.ts +2 -0
- package/dist/commands/view.js +32 -62
- package/dist/commands/watchdog.js +9 -14
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/account-capabilities.d.ts +4 -0
- package/dist/lib/account-capabilities.js +13 -9
- package/dist/lib/account-registry.d.ts +2 -0
- package/dist/lib/account-registry.js +30 -2
- package/dist/lib/accounting/rotate.d.ts +9 -0
- package/dist/lib/accounting/rotate.js +12 -2
- package/dist/lib/accounting/usage.d.ts +84 -6
- package/dist/lib/accounting/usage.js +293 -75
- package/dist/lib/agent-modes.js +29 -1
- package/dist/lib/agent-spec/agents.d.ts +4 -3
- package/dist/lib/agent-spec/agents.js +25 -15
- package/dist/lib/analytics/mix-commands.d.ts +1 -1
- package/dist/lib/analytics/mix-commands.js +2 -2
- package/dist/lib/auth-health.d.ts +13 -3
- package/dist/lib/auth-health.js +50 -8
- package/dist/lib/browser/devices.d.ts +19 -0
- package/dist/lib/browser/devices.js +23 -0
- package/dist/lib/browser/drivers/ssh.d.ts +16 -2
- package/dist/lib/browser/drivers/ssh.js +36 -9
- package/dist/lib/browser/ipc.d.ts +1 -1
- package/dist/lib/browser/ipc.js +40 -9
- package/dist/lib/browser/profiles.d.ts +104 -65
- package/dist/lib/browser/profiles.js +277 -165
- package/dist/lib/browser/registry.d.ts +39 -0
- package/dist/lib/browser/registry.js +149 -0
- package/dist/lib/browser/remote-control.d.ts +28 -2
- package/dist/lib/browser/remote-control.js +35 -3
- package/dist/lib/browser/resolve-target.d.ts +68 -0
- package/dist/lib/browser/resolve-target.js +250 -0
- package/dist/lib/browser/runtime-state.d.ts +44 -11
- package/dist/lib/browser/runtime-state.js +98 -15
- package/dist/lib/browser/service.d.ts +65 -9
- package/dist/lib/browser/service.js +196 -61
- package/dist/lib/browser/sessions-list.d.ts +1 -3
- package/dist/lib/browser/sessions-list.js +3 -16
- package/dist/lib/browser/task-index.d.ts +70 -0
- package/dist/lib/browser/task-index.js +180 -0
- package/dist/lib/browser/types.d.ts +12 -1
- package/dist/lib/byok-usage.js +2 -2
- package/dist/lib/claude-statusline.d.ts +29 -0
- package/dist/lib/claude-statusline.js +139 -0
- package/dist/lib/cloud/host.d.ts +1 -1
- package/dist/lib/cloud/host.js +1 -1
- package/dist/lib/cloud/rush.js +1 -1
- package/dist/lib/codex-policy.d.ts +11 -1
- package/dist/lib/codex-policy.js +20 -5
- package/dist/lib/computer/computer-rpc.js +2 -2
- package/dist/lib/computer/download.d.ts +15 -15
- package/dist/lib/computer/download.js +35 -98
- package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
- package/dist/lib/computer/ssh-tunnel.js +12 -22
- package/dist/lib/config-keys.d.ts +6 -11
- package/dist/lib/config-keys.js +18 -19
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +2 -2
- package/dist/lib/crabbox/lease.d.ts +9 -0
- package/dist/lib/crabbox/lease.js +70 -15
- package/dist/lib/crabbox/runtimes.js +1 -1
- package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
- package/dist/lib/daemon/account-state-daemon-service.js +26 -0
- package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
- package/dist/lib/daemon/browser-ipc-service.js +46 -0
- package/dist/lib/daemon/daemon.d.ts +3 -0
- package/dist/lib/daemon/daemon.js +212 -444
- package/dist/lib/daemon/device-probe-service.d.ts +26 -0
- package/dist/lib/daemon/device-probe-service.js +47 -0
- package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
- package/dist/lib/daemon/keychain-reap-service.js +32 -0
- package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
- package/dist/lib/daemon/monitor-engine-service.js +29 -0
- package/dist/lib/daemon/runner.js +15 -2
- package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
- package/dist/lib/daemon/secrets-broker-service.js +66 -0
- package/dist/lib/daemon/self-heal-service.d.ts +27 -0
- package/dist/lib/daemon/self-heal-service.js +48 -0
- package/dist/lib/daemon/service.d.ts +102 -0
- package/dist/lib/daemon/service.js +98 -0
- package/dist/lib/daemon/session-index-service.d.ts +22 -0
- package/dist/lib/daemon/session-index-service.js +40 -0
- package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
- package/dist/lib/daemon/state-dir-check-service.js +63 -0
- package/dist/lib/daemon/supervisor.d.ts +87 -0
- package/dist/lib/daemon/supervisor.js +298 -0
- package/dist/lib/daemon/watchdog-service.d.ts +19 -0
- package/dist/lib/daemon/watchdog-service.js +40 -0
- package/dist/lib/daemon-health.d.ts +15 -0
- package/dist/lib/daemon-health.js +29 -5
- package/dist/lib/daemon-services.d.ts +8 -1
- package/dist/lib/daemon-services.js +55 -0
- package/dist/lib/daemon-ticks.d.ts +13 -2
- package/dist/lib/daemon-ticks.js +22 -20
- package/dist/lib/device-config.d.ts +25 -10
- package/dist/lib/device-config.js +90 -39
- package/dist/lib/devices/config-migration.d.ts +7 -0
- package/dist/lib/devices/config-migration.js +67 -3
- package/dist/lib/devices/health.d.ts +27 -2
- package/dist/lib/devices/health.js +39 -9
- package/dist/lib/devices/interactive-host.d.ts +15 -0
- package/dist/lib/devices/interactive-host.js +53 -0
- package/dist/lib/devices/registry.d.ts +61 -4
- package/dist/lib/devices/registry.js +94 -38
- package/dist/lib/devices/stats-cache.d.ts +26 -0
- package/dist/lib/devices/stats-cache.js +59 -7
- package/dist/lib/devices/worker-pick.d.ts +48 -0
- package/dist/lib/devices/worker-pick.js +113 -0
- package/dist/lib/exec.d.ts +70 -14
- package/dist/lib/exec.js +110 -38
- package/dist/lib/feed/answer.d.ts +29 -0
- package/dist/lib/feed/answer.js +145 -0
- package/dist/lib/feed/attention.d.ts +83 -0
- package/dist/lib/feed/attention.js +0 -0
- package/dist/lib/feed/feed.d.ts +109 -0
- package/dist/lib/feed/feed.js +154 -3
- package/dist/lib/feed/pr-status.d.ts +14 -0
- package/dist/lib/feed/pr-status.js +47 -0
- package/dist/lib/feed/watch.d.ts +59 -0
- package/dist/lib/feed/watch.js +180 -0
- package/dist/lib/fleet/capture.js +6 -0
- package/dist/lib/fleet/remote-login.js +8 -3
- package/dist/lib/fleet/types.d.ts +17 -0
- package/dist/lib/harness/adapter.d.ts +11 -0
- package/dist/lib/harness/adapters/claude.js +38 -24
- package/dist/lib/harness/adapters/codex.js +2 -4
- package/dist/lib/harness/adapters/cursor.js +8 -11
- package/dist/lib/helper-download.d.ts +108 -0
- package/dist/lib/helper-download.js +194 -0
- package/dist/lib/helper-versions.d.ts +52 -0
- package/dist/lib/helper-versions.js +58 -0
- package/dist/lib/hooks/cache.js +19 -0
- package/dist/lib/hooks/install.js +2 -2
- package/dist/lib/hooks/match.d.ts +3 -0
- package/dist/lib/hooks/match.js +25 -0
- package/dist/lib/hosts/credentials.d.ts +1 -1
- package/dist/lib/hosts/credentials.js +1 -1
- package/dist/lib/hosts/dispatch.d.ts +7 -1
- package/dist/lib/hosts/dispatch.js +24 -5
- package/dist/lib/hosts/passthrough.d.ts +8 -15
- package/dist/lib/hosts/passthrough.js +35 -16
- package/dist/lib/hosts/providers/devices.d.ts +1 -1
- package/dist/lib/hosts/providers/devices.js +1 -1
- package/dist/lib/hosts/reconnect.d.ts +179 -21
- package/dist/lib/hosts/reconnect.js +266 -45
- package/dist/lib/hosts/registry.js +7 -0
- package/dist/lib/installations/migrate.d.ts +4 -5
- package/dist/lib/installations/migrate.js +6 -7
- package/dist/lib/installations/shims.d.ts +1 -1
- package/dist/lib/installations/shims.js +23 -13
- package/dist/lib/installations/versions.js +25 -2
- package/dist/lib/linear-autoclose.d.ts +1 -1
- package/dist/lib/linear-autoclose.js +1 -1
- package/dist/lib/loop.d.ts +2 -2
- package/dist/lib/loop.js +1 -1
- package/dist/lib/menubar/download-menubar.d.ts +47 -0
- package/dist/lib/menubar/download-menubar.js +60 -0
- package/dist/lib/menubar/install-menubar.d.ts +44 -7
- package/dist/lib/menubar/install-menubar.js +105 -23
- package/dist/lib/menubar/notify-desktop.d.ts +3 -3
- package/dist/lib/menubar/notify-desktop.js +6 -6
- package/dist/lib/model-tiers.d.ts +1 -1
- package/dist/lib/open-url.d.ts +60 -2
- package/dist/lib/open-url.js +167 -12
- package/dist/lib/project-probe.d.ts +1 -1
- package/dist/lib/project-probe.js +1 -1
- package/dist/lib/project-pull.d.ts +1 -1
- package/dist/lib/project-pull.js +2 -2
- package/dist/lib/projects.d.ts +1 -1
- package/dist/lib/projects.js +1 -1
- package/dist/lib/refresh-coordinator.d.ts +1 -1
- package/dist/lib/refresh-coordinator.js +1 -1
- package/dist/lib/sandbox.js +6 -7
- package/dist/lib/secrets/download-keychain.d.ts +47 -0
- package/dist/lib/secrets/download-keychain.js +70 -0
- package/dist/lib/secrets/install-helper.d.ts +20 -1
- package/dist/lib/secrets/install-helper.js +44 -3
- package/dist/lib/session/active.d.ts +67 -0
- package/dist/lib/session/active.js +86 -2
- package/dist/lib/session/db.d.ts +11 -1
- package/dist/lib/session/db.js +142 -8
- package/dist/lib/session/discover.d.ts +23 -1
- package/dist/lib/session/discover.js +44 -10
- package/dist/lib/session/highlights.d.ts +49 -1
- package/dist/lib/session/highlights.js +95 -0
- package/dist/lib/session/host-link.d.ts +16 -2
- package/dist/lib/session/host-link.js +7 -1
- package/dist/lib/session/parse.js +33 -1
- package/dist/lib/session/prompt.d.ts +35 -0
- package/dist/lib/session/prompt.js +56 -0
- package/dist/lib/session/remote/remote-list.d.ts +9 -0
- package/dist/lib/session/remote/remote-list.js +19 -2
- package/dist/lib/session/remote/watch.d.ts +1 -1
- package/dist/lib/session/remote/watch.js +17 -3
- package/dist/lib/session/render.js +7 -3
- package/dist/lib/session/session-cache.d.ts +53 -0
- package/dist/lib/session/session-cache.js +93 -1
- package/dist/lib/session/state.js +1 -1
- package/dist/lib/session/tool-calls.d.ts +2 -0
- package/dist/lib/session/tool-calls.js +1 -1
- package/dist/lib/session/trajectory-compare.d.ts +47 -0
- package/dist/lib/session/trajectory-compare.js +116 -0
- package/dist/lib/session/trajectory-html.d.ts +19 -0
- package/dist/lib/session/trajectory-html.js +770 -0
- package/dist/lib/session/trajectory-lineage.d.ts +92 -0
- package/dist/lib/session/trajectory-lineage.js +195 -0
- package/dist/lib/session/trajectory-text.d.ts +37 -0
- package/dist/lib/session/trajectory-text.js +284 -0
- package/dist/lib/session/trajectory.d.ts +102 -0
- package/dist/lib/session/trajectory.js +384 -0
- package/dist/lib/session/types.d.ts +15 -0
- package/dist/lib/sha256-asset.d.ts +26 -0
- package/dist/lib/sha256-asset.js +41 -0
- package/dist/lib/share/backend.d.ts +95 -0
- package/dist/lib/share/backend.js +139 -0
- package/dist/lib/share/delete.d.ts +6 -2
- package/dist/lib/share/delete.js +8 -9
- package/dist/lib/share/html.d.ts +22 -0
- package/dist/lib/share/html.js +88 -0
- package/dist/lib/share/provision.d.ts +18 -4
- package/dist/lib/share/provision.js +38 -4
- package/dist/lib/share/publish.d.ts +39 -15
- package/dist/lib/share/publish.js +55 -26
- package/dist/lib/share/worker-template.js +171 -11
- package/dist/lib/sqlite.js +16 -3
- package/dist/lib/ssh-exec.d.ts +65 -0
- package/dist/lib/ssh-exec.js +120 -4
- package/dist/lib/startup/command-registry.d.ts +3 -1
- package/dist/lib/startup/command-registry.js +6 -3
- package/dist/lib/startup/dev-build.js +3 -3
- package/dist/lib/state.d.ts +2 -2
- package/dist/lib/state.js +11 -5
- package/dist/lib/subagents-registry.js +1 -1
- package/dist/lib/teams/agents.js +9 -0
- package/dist/lib/terminal/index.d.ts +1 -1
- package/dist/lib/terminal/inject.d.ts +19 -0
- package/dist/lib/terminal/inject.js +19 -9
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/terminal/types.js +1 -1
- package/dist/lib/tmux/orphan-reap.js +1 -1
- package/dist/lib/tmux/session.d.ts +46 -13
- package/dist/lib/tmux/session.js +204 -27
- package/dist/lib/traces/backend.d.ts +29 -0
- package/dist/lib/traces/backend.js +46 -0
- package/dist/lib/traces/classify.d.ts +52 -0
- package/dist/lib/traces/classify.js +72 -0
- package/dist/lib/traces/config.d.ts +10 -0
- package/dist/lib/traces/config.js +2 -0
- package/dist/lib/traces/provision.d.ts +19 -0
- package/dist/lib/traces/provision.js +30 -0
- package/dist/lib/traces/sync.d.ts +150 -0
- package/dist/lib/traces/sync.js +447 -0
- package/dist/lib/traces/worker-template.d.ts +4 -0
- package/dist/lib/traces/worker-template.js +159 -0
- package/dist/lib/types.d.ts +34 -19
- package/dist/lib/types.js +14 -0
- package/dist/lib/usage-backoff.d.ts +2 -1
- package/dist/lib/usage-backoff.js +60 -19
- package/dist/lib/usage-refresh.d.ts +18 -4
- package/dist/lib/usage-refresh.js +68 -19
- package/dist/lib/watchdog/log.d.ts +1 -1
- package/dist/lib/watchdog/log.js +1 -1
- package/dist/lib/watchdog/runner.d.ts +36 -45
- package/dist/lib/watchdog/runner.js +150 -119
- package/dist/lib/watchdog/service.d.ts +0 -1
- package/dist/lib/watchdog/service.js +0 -1
- package/dist/lib/watchdog/watchdog-agent.d.ts +38 -0
- package/dist/lib/watchdog/watchdog-agent.js +59 -0
- package/dist/lib/watchdog/watchdog.d.ts +11 -2
- package/dist/lib/watchdog/watchdog.js +37 -104
- package/dist/lib/workflows.d.ts +1 -1
- package/package.json +5 -7
- package/dist/commands/usage.d.ts +0 -27
- package/dist/commands/usage.js +0 -96
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -24
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -123
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
- package/dist/lib/usage-fleet.d.ts +0 -32
- package/dist/lib/usage-fleet.js +0 -125
package/dist/lib/agent-modes.js
CHANGED
|
@@ -14,9 +14,28 @@ import { resolveRunDefaults } from './run-defaults.js';
|
|
|
14
14
|
export const MODE_DESCRIPTIONS = {
|
|
15
15
|
plan: 'read-only investigation; no writes, no shell side-effects',
|
|
16
16
|
edit: 'may edit files; prompts for shell / risky operations',
|
|
17
|
-
auto: '
|
|
17
|
+
auto: 'more autonomy than edit; the exact policy is per-harness (see notes)',
|
|
18
18
|
skip: 'bypass every permission prompt (dangerously-skip-permissions)',
|
|
19
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* What `auto` actually does, per harness. Two genuinely different mechanisms
|
|
22
|
+
* wear the same mode name, and conflating them is a safety error in both
|
|
23
|
+
* directions: telling a claude operator that auto never prompts invites an
|
|
24
|
+
* unattended run that stalls on a risky-operation gate, and telling a codex
|
|
25
|
+
* operator that auto prompts for risky work asserts a gate codex does not have.
|
|
26
|
+
* Absent entry = no extra note; the row's own text suffices.
|
|
27
|
+
*/
|
|
28
|
+
const AUTO_SEMANTICS = {
|
|
29
|
+
// Classifier-style: still escalates. --permission-mode auto / --autopilot.
|
|
30
|
+
claude: 'a smart classifier auto-approves safe operations and still prompts for risky ones.',
|
|
31
|
+
copilot: 'a smart classifier auto-approves safe operations and still prompts for risky ones.',
|
|
32
|
+
// Approvals off, sandbox kept: never escalates. A denied command just fails.
|
|
33
|
+
codex: 'approval_policy=never over the same sandbox as edit — it never prompts, and a sandbox-denied command fails instead of raising an approval request.',
|
|
34
|
+
muse: 'runs --disable-approval, which turns approvals off while keeping the sandbox — like codex, it never prompts and a denied command fails.',
|
|
35
|
+
// Native autonomy dial at its top setting.
|
|
36
|
+
droid: 'runs --auto high, droid\'s full-autonomy setting.',
|
|
37
|
+
kimi: "runs kimi's native --auto.",
|
|
38
|
+
};
|
|
20
39
|
/**
|
|
21
40
|
* Build the permission-mode catalog for one harness.
|
|
22
41
|
* `version` only affects the configured run.defaults lookup (modes themselves
|
|
@@ -42,6 +61,15 @@ export function getAgentModesCatalog(agent, version, cwd = process.cwd()) {
|
|
|
42
61
|
if (unsupported.includes('auto')) {
|
|
43
62
|
notes.push(`--mode auto degrades to edit on ${agent} (no native auto classifier).`);
|
|
44
63
|
}
|
|
64
|
+
// MODE_DESCRIPTIONS is one flat Record<Mode, string> rendered for every agent,
|
|
65
|
+
// so it cannot name any single harness's mechanism without lying about the
|
|
66
|
+
// others: claude/copilot auto STILL PROMPTS for risky operations, while codex
|
|
67
|
+
// auto never prompts at all. The row states only what is true everywhere; the
|
|
68
|
+
// mechanism rides here, per harness.
|
|
69
|
+
const autoSemantics = AUTO_SEMANTICS[agent];
|
|
70
|
+
if (autoSemantics && supported.includes('auto')) {
|
|
71
|
+
notes.push(`${agent} --mode auto: ${autoSemantics}`);
|
|
72
|
+
}
|
|
45
73
|
if (unsupported.includes('plan')) {
|
|
46
74
|
notes.push(`--mode plan degrades to ${defaultMode} on ${agent} (no native read-only mode).`);
|
|
47
75
|
}
|
|
@@ -552,9 +552,9 @@ export declare function resolveAgentName(input: string): AgentId | null;
|
|
|
552
552
|
/** Check whether the input string matches any known agent name or alias. */
|
|
553
553
|
export declare function isAgentName(input: string): boolean;
|
|
554
554
|
/**
|
|
555
|
-
* Split a CLI-provided `<agent>[@<version>]` spec into its bare agent
|
|
556
|
-
* optional exact version token, the same way
|
|
557
|
-
*
|
|
555
|
+
* Split a CLI-provided `<agent>[@<version>][#<label>]` spec into its bare agent
|
|
556
|
+
* id, optional exact version token, and optional account label, the same way
|
|
557
|
+
* `agents run` does — NOT the
|
|
558
558
|
* agent-spec qualifier engine (`@latest`/`@all`), which enumerates installed
|
|
559
559
|
* versions for read/diagnostic commands, not a single launch target. Returns an
|
|
560
560
|
* error message rather than throwing so callers decide exit-vs-continue.
|
|
@@ -562,6 +562,7 @@ export declare function isAgentName(input: string): boolean;
|
|
|
562
562
|
export declare function parseAgentVersionSpec(raw: string): {
|
|
563
563
|
agent: AgentId;
|
|
564
564
|
version?: string;
|
|
565
|
+
label?: string;
|
|
565
566
|
} | {
|
|
566
567
|
error: string;
|
|
567
568
|
};
|
|
@@ -298,7 +298,7 @@ export const AGENTS = {
|
|
|
298
298
|
cloudProvider: 'codex',
|
|
299
299
|
// Subagents: multi-agent plumbing since 0.117.0; custom agents as
|
|
300
300
|
// ~/.codex/agents/*.toml (name, description, developer_instructions).
|
|
301
|
-
capabilities: { hooks: { since: '0.116.0' }, mcp: true, mcpHttp: true, mcpHeaders: false, allowlist: { since: '0.138.0' }, skills: true, commands: { until: '0.117.0' }, plugins: { since: '0.128.0' }, subagents: { since: '0.117.0' }, rules: { file: 'AGENTS.md' }, workflows: false, memory: true, modes: ['plan', 'edit', 'skip'], interactiveRepl: true },
|
|
301
|
+
capabilities: { hooks: { since: '0.116.0' }, mcp: true, mcpHttp: true, mcpHeaders: false, allowlist: { since: '0.138.0' }, skills: true, commands: { until: '0.117.0' }, plugins: { since: '0.128.0' }, subagents: { since: '0.117.0' }, rules: { file: 'AGENTS.md' }, workflows: false, memory: true, modes: ['plan', 'edit', 'auto', 'skip'], interactiveRepl: true },
|
|
302
302
|
},
|
|
303
303
|
gemini: {
|
|
304
304
|
id: 'gemini',
|
|
@@ -1504,12 +1504,9 @@ const CREDENTIAL_FILE_SEGMENTS = {
|
|
|
1504
1504
|
// Muse Code stores OAuth / API credentials at ~/.config/muse/auth.json
|
|
1505
1505
|
// (or META_API_KEY in the environment, which is not a file).
|
|
1506
1506
|
muse: [['.config', 'muse', 'auth.json']],
|
|
1507
|
-
//
|
|
1508
|
-
//
|
|
1509
|
-
|
|
1510
|
-
// version home with no token of its own is genuinely logged out once runs pin
|
|
1511
|
-
// XDG_CONFIG_HOME per home (see buildExecEnv).
|
|
1512
|
-
cursor: [['.config', 'cursor', 'auth.json']],
|
|
1507
|
+
// With AGENT_CLI_CREDENTIAL_STORE=file, Cursor stores both OAuth tokens at
|
|
1508
|
+
// ~/.cursor/auth.json. cli-config.json holds account metadata only.
|
|
1509
|
+
cursor: [['.cursor', 'auth.json']],
|
|
1513
1510
|
};
|
|
1514
1511
|
/** Whether an agent's credential file exists under a given home. */
|
|
1515
1512
|
function credentialFileExistsUnder(agentId, home) {
|
|
@@ -2112,7 +2109,7 @@ export async function getAccountInfo(agentId, home) {
|
|
|
2112
2109
|
case 'cursor': {
|
|
2113
2110
|
// Cursor CLI keeps account metadata in ~/.cursor/cli-config.json
|
|
2114
2111
|
// (authInfo: { email, userId, authId }) and its OAuth tokens SEPARATELY
|
|
2115
|
-
// in ~/.
|
|
2112
|
+
// in ~/.cursor/auth.json ({ accessToken, refreshToken }). Presence
|
|
2116
2113
|
// of an access token is the signed-in signal; email/ids come from
|
|
2117
2114
|
// cli-config. authId is the OAuth subject (e.g. "google-oauth2|<n>") — the
|
|
2118
2115
|
// same value the usage endpoint keys on (see getCursorUsageInfo).
|
|
@@ -2126,7 +2123,7 @@ export async function getAccountInfo(agentId, home) {
|
|
|
2126
2123
|
const accountId = normalizeIdentityPart(authInfo?.authId ?? authInfo?.userId);
|
|
2127
2124
|
if (!email && !accountId)
|
|
2128
2125
|
return { ...empty, lastActive };
|
|
2129
|
-
const authPath = resolveAccountCredentialPath(base, '.
|
|
2126
|
+
const authPath = resolveAccountCredentialPath(base, '.cursor', 'auth.json');
|
|
2130
2127
|
let hasToken = false;
|
|
2131
2128
|
if (authPath) {
|
|
2132
2129
|
try {
|
|
@@ -2135,8 +2132,13 @@ export async function getAccountInfo(agentId, home) {
|
|
|
2135
2132
|
}
|
|
2136
2133
|
catch { /* unreadable token file */ }
|
|
2137
2134
|
}
|
|
2135
|
+
// cli-config survives logout and may describe a different keychain
|
|
2136
|
+
// account. Without this version's file token, its identity is stale
|
|
2137
|
+
// metadata rather than a usable managed login.
|
|
2138
|
+
if (!hasToken)
|
|
2139
|
+
return { ...empty, lastActive };
|
|
2138
2140
|
const accountKey = buildIdentityKey(agentId, [['user', accountId]]);
|
|
2139
|
-
return { ...empty, email, accountId, accountKey, signedIn:
|
|
2141
|
+
return { ...empty, email, accountId, accountKey, signedIn: true, lastActive };
|
|
2140
2142
|
}
|
|
2141
2143
|
catch { }
|
|
2142
2144
|
return { ...empty, lastActive };
|
|
@@ -3050,15 +3052,23 @@ export function isAgentName(input) {
|
|
|
3050
3052
|
return resolveAgentName(input) !== null;
|
|
3051
3053
|
}
|
|
3052
3054
|
/**
|
|
3053
|
-
* Split a CLI-provided `<agent>[@<version>]` spec into its bare agent
|
|
3054
|
-
* optional exact version token, the same way
|
|
3055
|
-
*
|
|
3055
|
+
* Split a CLI-provided `<agent>[@<version>][#<label>]` spec into its bare agent
|
|
3056
|
+
* id, optional exact version token, and optional account label, the same way
|
|
3057
|
+
* `agents run` does — NOT the
|
|
3056
3058
|
* agent-spec qualifier engine (`@latest`/`@all`), which enumerates installed
|
|
3057
3059
|
* versions for read/diagnostic commands, not a single launch target. Returns an
|
|
3058
3060
|
* error message rather than throwing so callers decide exit-vs-continue.
|
|
3059
3061
|
*/
|
|
3060
3062
|
export function parseAgentVersionSpec(raw) {
|
|
3061
|
-
const
|
|
3063
|
+
const labelParts = raw.split('#');
|
|
3064
|
+
if (labelParts.length > 2) {
|
|
3065
|
+
return { error: `Invalid agent spec '${raw}': at most one '#label' is allowed` };
|
|
3066
|
+
}
|
|
3067
|
+
const [versionSpec, rawLabel] = labelParts;
|
|
3068
|
+
if (rawLabel !== undefined && (rawLabel === '' || !/^[a-zA-Z0-9][a-zA-Z0-9@._+-]*$/.test(rawLabel))) {
|
|
3069
|
+
return { error: `Invalid account label '${rawLabel}' in '${raw}'` };
|
|
3070
|
+
}
|
|
3071
|
+
const parts = versionSpec.split('@');
|
|
3062
3072
|
if (parts.length > 2) {
|
|
3063
3073
|
return { error: `Invalid agent spec '${raw}': at most one '@version' is allowed` };
|
|
3064
3074
|
}
|
|
@@ -3070,7 +3080,7 @@ export function parseAgentVersionSpec(raw) {
|
|
|
3070
3080
|
if (rawVersion !== undefined && (rawVersion === '' || !VERSION_RE.test(rawVersion))) {
|
|
3071
3081
|
return { error: `Invalid version '${rawVersion}' in '${raw}'` };
|
|
3072
3082
|
}
|
|
3073
|
-
return { agent, ...(rawVersion ? { version: rawVersion } : {}) };
|
|
3083
|
+
return { agent, ...(rawVersion ? { version: rawVersion } : {}), ...(rawLabel ? { label: rawLabel } : {}) };
|
|
3074
3084
|
}
|
|
3075
3085
|
/**
|
|
3076
3086
|
* Build the deprecation notice lines for an agent, or null if it isn't
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* peer of `agents insights` with overlapping "analytics" meaning, so agents and
|
|
6
6
|
* humans kept picking the wrong verb. The cheap counter path (sessions index +
|
|
7
7
|
* usage.db) still exists — it is now `agents insights mix` and the recipe
|
|
8
|
-
* subcommands below. Latency stays on `agents perf`; quota on `agents
|
|
8
|
+
* subcommands below. Latency stays on `agents perf`; quota on `agents view`.
|
|
9
9
|
*
|
|
10
10
|
* Former top-level `agents trends` is gone. The nested spelling
|
|
11
11
|
* `agents insights trends` is an alias of `agents insights mix`.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* peer of `agents insights` with overlapping "analytics" meaning, so agents and
|
|
6
6
|
* humans kept picking the wrong verb. The cheap counter path (sessions index +
|
|
7
7
|
* usage.db) still exists — it is now `agents insights mix` and the recipe
|
|
8
|
-
* subcommands below. Latency stays on `agents perf`; quota on `agents
|
|
8
|
+
* subcommands below. Latency stays on `agents perf`; quota on `agents view`.
|
|
9
9
|
*
|
|
10
10
|
* Former top-level `agents trends` is gone. The nested spelling
|
|
11
11
|
* `agents insights trends` is an alias of `agents insights mix`.
|
|
@@ -97,7 +97,7 @@ export function registerMixCommands(parent) {
|
|
|
97
97
|
Session recipes read sessions.db; resource recipes read ~/.agents/.history/analytics/usage.db.
|
|
98
98
|
Empty recipes are skipped on the default mix board.
|
|
99
99
|
This is the cheap counter path. Behavioural report (transcript content, account split)
|
|
100
|
-
is bare \`agents insights\`. Latency is \`agents perf\`; quota is \`agents
|
|
100
|
+
is bare \`agents insights\`. Latency is \`agents perf\`; quota is \`agents view\`.
|
|
101
101
|
Skill/slash-command popularity is \`agents sessions stats\`.
|
|
102
102
|
`,
|
|
103
103
|
});
|
|
@@ -148,13 +148,21 @@ export declare function mergeAuthHealthEntries(current: Record<string, AuthHealt
|
|
|
148
148
|
export declare function writeAuthHealthEntries(entries: Record<string, AuthHealth>): void;
|
|
149
149
|
/**
|
|
150
150
|
* Complete a live auth probe for one (agent, home). For claude/kimi/droid this
|
|
151
|
-
* hits the provider
|
|
152
|
-
*
|
|
153
|
-
*
|
|
151
|
+
* hits the provider — unless the account's usage cache already holds a fresh
|
|
152
|
+
* successful fetch, which is the same authenticated request and proves the
|
|
153
|
+
* token live without spending a second one (RUSH-3036). For everyone else it
|
|
154
|
+
* reports a best-effort local verdict (`unverified` when a credential is
|
|
155
|
+
* present, `unconfigured` otherwise) — never masquerading as `live`.
|
|
154
156
|
*/
|
|
155
157
|
export declare function probeAuthHealth(agent: AgentId, home: string | undefined, opts?: {
|
|
156
158
|
cliVersion?: string | null;
|
|
157
159
|
info?: AccountInfo | null;
|
|
160
|
+
/**
|
|
161
|
+
* Skip the derived-from-usage shortcut and fire a real network probe
|
|
162
|
+
* (RUSH-3036). Set by `agents devices ping [--strict]`, whose contract is
|
|
163
|
+
* a genuinely live request that surfaces `revoked` immediately.
|
|
164
|
+
*/
|
|
165
|
+
forceLive?: boolean;
|
|
158
166
|
}): Promise<AuthHealth>;
|
|
159
167
|
/** One probed install on a host. */
|
|
160
168
|
export interface AuthProbeRow {
|
|
@@ -215,6 +223,8 @@ export declare function groupFleetAuthInstalls<T extends FleetAuthInstall>(insta
|
|
|
215
223
|
export declare function probeLocalFleetAuth(opts?: {
|
|
216
224
|
cliVersion?: string | null;
|
|
217
225
|
agents?: readonly AgentId[];
|
|
226
|
+
/** Fire real network probes even when fresh usage evidence exists (RUSH-3036) — the `devices ping [--strict]` contract. */
|
|
227
|
+
forceLive?: boolean;
|
|
218
228
|
}): Promise<AuthProbeRow[]>;
|
|
219
229
|
/** Persist a host's probed rows into the cache (keyed by host+agent+version). */
|
|
220
230
|
export declare function writeFleetAuthRows(host: string, rows: AuthProbeRow[]): void;
|
package/dist/lib/auth-health.js
CHANGED
|
@@ -20,7 +20,7 @@ import * as fs from 'fs';
|
|
|
20
20
|
import * as path from 'path';
|
|
21
21
|
import { ALL_AGENT_IDS, getAccountInfo } from './agents.js';
|
|
22
22
|
import { getCacheDir } from './state.js';
|
|
23
|
-
import { probeClaudeStatus, probeDroidStatus, probeKimiStatus, USAGE_HEADLESS_SCOPE_MARKER, } from './accounting/usage.js';
|
|
23
|
+
import { probeClaudeStatus, probeDroidStatus, probeKimiStatus, USAGE_HEADLESS_SCOPE_MARKER, readClaudeUsageCache, } from './accounting/usage.js';
|
|
24
24
|
import { getVersionHomePath, listInstalledVersions } from './installations/versions.js';
|
|
25
25
|
import { atomicWriteFileSync, ensureLockTarget, withFileLock } from './fs-atomic.js';
|
|
26
26
|
/** Agents with a live network probe wired up today. The rest are best-effort. */
|
|
@@ -294,22 +294,64 @@ export function writeAuthHealthEntries(entries) {
|
|
|
294
294
|
// ---------------------------------------------------------------------------
|
|
295
295
|
// The probe (writer side)
|
|
296
296
|
// ---------------------------------------------------------------------------
|
|
297
|
+
/**
|
|
298
|
+
* A usage snapshot this recent is live proof the account's shared setup-token
|
|
299
|
+
* works: the snapshot only exists because an authenticated `/oauth/usage`
|
|
300
|
+
* request succeeded. Matches the periodic tick's own probe window
|
|
301
|
+
* (AUTH_PROBE_MAX_AGE_MS in daemon-ticks.ts — not imported to avoid a cycle;
|
|
302
|
+
* a drift here only widens/narrows evidence freshness, never correctness).
|
|
303
|
+
*/
|
|
304
|
+
const FRESH_USAGE_VERDICT_MAX_AGE_MS = 20 * 60_000;
|
|
305
|
+
/**
|
|
306
|
+
* A `live` verdict derived from the account's usage cache instead of a second
|
|
307
|
+
* network request (RUSH-3036). The auth probe and the usage fetch hit the SAME
|
|
308
|
+
* rate-limited endpoint with the SAME fleet-shared setup-token, so a fresh
|
|
309
|
+
* successful usage snapshot already proves everything the probe would: paying a
|
|
310
|
+
* second request per account per box was half the fleet's endpoint load.
|
|
311
|
+
*
|
|
312
|
+
* Two guards keep the evidence honest (both review findings on the first cut):
|
|
313
|
+
* the caller must assert this box holds a LOCAL credential for the account
|
|
314
|
+
* (`signedIn`) — a fleet-imported snapshot proves the shared token works, not
|
|
315
|
+
* that THIS box can authenticate, so an unsigned home never derives `live`;
|
|
316
|
+
* and a `forceLive` caller (`agents devices ping --strict`) skips derivation
|
|
317
|
+
* entirely, because its contract is a real request that can surface `revoked`
|
|
318
|
+
* within seconds, not minutes. Returns null when there is no admissible fresh
|
|
319
|
+
* evidence — the caller then live-probes as before.
|
|
320
|
+
*/
|
|
321
|
+
function verdictFromFreshUsage(usageKey, signedIn, now) {
|
|
322
|
+
if (!usageKey || !signedIn)
|
|
323
|
+
return null;
|
|
324
|
+
const snapshot = readClaudeUsageCache(usageKey);
|
|
325
|
+
const capturedAt = snapshot?.capturedAt?.getTime();
|
|
326
|
+
if (!capturedAt || now - capturedAt >= FRESH_USAGE_VERDICT_MAX_AGE_MS)
|
|
327
|
+
return null;
|
|
328
|
+
const ageMin = Math.max(1, Math.round((now - capturedAt) / 60_000));
|
|
329
|
+
return { verdict: 'live', checkedAt: now, detail: `token proven live by a usage fetch ${ageMin}m ago` };
|
|
330
|
+
}
|
|
297
331
|
/**
|
|
298
332
|
* Complete a live auth probe for one (agent, home). For claude/kimi/droid this
|
|
299
|
-
* hits the provider
|
|
300
|
-
*
|
|
301
|
-
*
|
|
333
|
+
* hits the provider — unless the account's usage cache already holds a fresh
|
|
334
|
+
* successful fetch, which is the same authenticated request and proves the
|
|
335
|
+
* token live without spending a second one (RUSH-3036). For everyone else it
|
|
336
|
+
* reports a best-effort local verdict (`unverified` when a credential is
|
|
337
|
+
* present, `unconfigured` otherwise) — never masquerading as `live`.
|
|
302
338
|
*/
|
|
303
339
|
export async function probeAuthHealth(agent, home, opts) {
|
|
304
340
|
const checkedAt = Date.now();
|
|
305
341
|
if (LIVE_PROBE_AGENTS.has(agent)) {
|
|
342
|
+
const usageScope = opts?.info?.usageKey ?? null;
|
|
343
|
+
if (opts?.forceLive !== true) {
|
|
344
|
+
const derived = verdictFromFreshUsage(usageScope, opts?.info?.signedIn === true, checkedAt);
|
|
345
|
+
if (derived)
|
|
346
|
+
return derived;
|
|
347
|
+
}
|
|
306
348
|
let probe;
|
|
307
349
|
if (agent === 'claude')
|
|
308
|
-
probe = await probeClaudeStatus(home, opts?.cliVersion);
|
|
350
|
+
probe = await probeClaudeStatus(home, opts?.cliVersion, usageScope);
|
|
309
351
|
else if (agent === 'kimi')
|
|
310
|
-
probe = await probeKimiStatus(home);
|
|
352
|
+
probe = await probeKimiStatus(home, usageScope);
|
|
311
353
|
else
|
|
312
|
-
probe = await probeDroidStatus(home);
|
|
354
|
+
probe = await probeDroidStatus(home, usageScope);
|
|
313
355
|
return { verdict: verdictFromProbe(probe), checkedAt, detail: probeDetail(probe) };
|
|
314
356
|
}
|
|
315
357
|
const info = opts?.info !== undefined ? opts.info : await getAccountInfo(agent, home).catch(() => null);
|
|
@@ -384,7 +426,7 @@ export async function probeLocalFleetAuth(opts) {
|
|
|
384
426
|
// accounts, so no same-account concurrency is left to trip the throttle.
|
|
385
427
|
const perGroup = await Promise.all(groupFleetAuthInstalls(installs, (inst) => LIVE_PROBE_AGENTS.has(inst.agent)).map(async (group) => {
|
|
386
428
|
const rep = group.probe;
|
|
387
|
-
const health = await probeAuthHealth(rep.agent, rep.home, { cliVersion: opts?.cliVersion, info: rep.info });
|
|
429
|
+
const health = await probeAuthHealth(rep.agent, rep.home, { cliVersion: opts?.cliVersion, info: rep.info, forceLive: opts?.forceLive });
|
|
388
430
|
health.account = authAccountLabel(rep.info);
|
|
389
431
|
if (health.verdict === 'unconfigured')
|
|
390
432
|
return [];
|
|
@@ -13,3 +13,22 @@ export declare const DEFAULT_VIEWPORT: {
|
|
|
13
13
|
export declare const DEVICES: Record<string, DeviceDescriptor>;
|
|
14
14
|
export declare function getDevice(name: string): DeviceDescriptor | undefined;
|
|
15
15
|
export declare function listDevices(): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Parse a `--window WxH` value. Returns null when the value is malformed, so
|
|
18
|
+
* callers emit their own actionable error.
|
|
19
|
+
*
|
|
20
|
+
* Shared by `profiles create` and `profiles edit` — one regex, one test, no
|
|
21
|
+
* drift between the two surfaces.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseWindowSize(raw: string): {
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
} | null;
|
|
27
|
+
/**
|
|
28
|
+
* Parse a `--position X,Y` value. Negative coordinates are valid — a window may
|
|
29
|
+
* sit on a display left of or above the primary one.
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseWindowPosition(raw: string): {
|
|
32
|
+
x: number;
|
|
33
|
+
y: number;
|
|
34
|
+
} | null;
|
|
@@ -36,3 +36,26 @@ export function getDevice(name) {
|
|
|
36
36
|
export function listDevices() {
|
|
37
37
|
return Object.keys(DEVICES);
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Parse a `--window WxH` value. Returns null when the value is malformed, so
|
|
41
|
+
* callers emit their own actionable error.
|
|
42
|
+
*
|
|
43
|
+
* Shared by `profiles create` and `profiles edit` — one regex, one test, no
|
|
44
|
+
* drift between the two surfaces.
|
|
45
|
+
*/
|
|
46
|
+
export function parseWindowSize(raw) {
|
|
47
|
+
const m = String(raw).match(/^(\d+)x(\d+)$/);
|
|
48
|
+
if (!m)
|
|
49
|
+
return null;
|
|
50
|
+
return { width: parseInt(m[1], 10), height: parseInt(m[2], 10) };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Parse a `--position X,Y` value. Negative coordinates are valid — a window may
|
|
54
|
+
* sit on a display left of or above the primary one.
|
|
55
|
+
*/
|
|
56
|
+
export function parseWindowPosition(raw) {
|
|
57
|
+
const m = String(raw).match(/^(-?\d+),(-?\d+)$/);
|
|
58
|
+
if (!m)
|
|
59
|
+
return null;
|
|
60
|
+
return { x: parseInt(m[1], 10), y: parseInt(m[2], 10) };
|
|
61
|
+
}
|
|
@@ -16,13 +16,27 @@ export interface SSHConnection {
|
|
|
16
16
|
* strings differ (no `&` backgrounding, no `lsof`, `.exe` instead of `.app`).
|
|
17
17
|
*/
|
|
18
18
|
export type RemoteOs = 'windows' | 'posix';
|
|
19
|
+
export interface SSHConnectOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Attach to a browser another device already declared (identity-bearing or
|
|
22
|
+
* a fungible name we don't declare here). Do not launch a fresh remote
|
|
23
|
+
* chrome-data, and do not kill that browser when the tunnel closes — it
|
|
24
|
+
* holds the logins the caller is trying to reach.
|
|
25
|
+
*/
|
|
26
|
+
persistRemote?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** Lifecycle for a registry-driven tunnel vs a profile the daemon launched. */
|
|
29
|
+
export declare function persistRemoteLifecycle(persistRemote: boolean): {
|
|
30
|
+
launchRemote: boolean;
|
|
31
|
+
killOnCleanup: boolean;
|
|
32
|
+
};
|
|
19
33
|
export declare function connectSSH(endpoint: string, profile: BrowserProfile,
|
|
20
34
|
/**
|
|
21
|
-
* Runtime key (`<profile>@<
|
|
35
|
+
* Runtime key (`<profile>@<device>`) this connection is stored under. It
|
|
22
36
|
* keys the tunnel's runtime record; `profile.name` is the bare, user-facing
|
|
23
37
|
* name and appears only in messages (RUSH-2709).
|
|
24
38
|
*/
|
|
25
|
-
key: ConnectionKey): Promise<SSHConnection>;
|
|
39
|
+
key: ConnectionKey, opts?: SSHConnectOptions): Promise<SSHConnection>;
|
|
26
40
|
/**
|
|
27
41
|
* Wrap a PowerShell script as a `-EncodedCommand` invocation. Base64 of the
|
|
28
42
|
* UTF-16LE bytes is a single quote-free token, so it rides through Node spawn
|
|
@@ -13,13 +13,19 @@ export { shellQuote };
|
|
|
13
13
|
// driver's original foreground, stderr-captured behavior exactly.
|
|
14
14
|
import { startSSHTunnel } from '../../computer/ssh-tunnel.js';
|
|
15
15
|
import { encodePwshBase64 } from '../../pwsh.js';
|
|
16
|
+
/** Lifecycle for a registry-driven tunnel vs a profile the daemon launched. */
|
|
17
|
+
export function persistRemoteLifecycle(persistRemote) {
|
|
18
|
+
return persistRemote
|
|
19
|
+
? { launchRemote: false, killOnCleanup: false }
|
|
20
|
+
: { launchRemote: true, killOnCleanup: true };
|
|
21
|
+
}
|
|
16
22
|
export async function connectSSH(endpoint, profile,
|
|
17
23
|
/**
|
|
18
|
-
* Runtime key (`<profile>@<
|
|
24
|
+
* Runtime key (`<profile>@<device>`) this connection is stored under. It
|
|
19
25
|
* keys the tunnel's runtime record; `profile.name` is the bare, user-facing
|
|
20
26
|
* name and appears only in messages (RUSH-2709).
|
|
21
27
|
*/
|
|
22
|
-
key) {
|
|
28
|
+
key, opts = {}) {
|
|
23
29
|
const url = new URL(endpoint);
|
|
24
30
|
if (url.protocol !== 'ssh:') {
|
|
25
31
|
throw new Error(`Invalid SSH endpoint: ${endpoint}`);
|
|
@@ -59,7 +65,14 @@ key) {
|
|
|
59
65
|
// (the launch backgrounds/WMI-spawns regardless), so this does not spuriously
|
|
60
66
|
// fail the reconnect path — only genuine launch failures reach the caller
|
|
61
67
|
// instead of being swallowed and mis-reported later as a tunnel timeout.
|
|
62
|
-
|
|
68
|
+
//
|
|
69
|
+
// persistRemote skips the launch: launching would mint a logged-out
|
|
70
|
+
// `--user-data-dir=/tmp/agents-browser-N` under a name that means a
|
|
71
|
+
// credentialed browser on the declaring device.
|
|
72
|
+
const lifecycle = persistRemoteLifecycle(Boolean(opts.persistRemote));
|
|
73
|
+
if (lifecycle.launchRemote) {
|
|
74
|
+
await ensureRemoteBrowser(user, host, profile.browser, remotePort, remoteOs, profile.binary);
|
|
75
|
+
}
|
|
63
76
|
let tunnel;
|
|
64
77
|
if (occupant) {
|
|
65
78
|
// Reuse the existing tunnel rather than spawning a duplicate.
|
|
@@ -78,7 +91,19 @@ key) {
|
|
|
78
91
|
tunnel.kill();
|
|
79
92
|
throw new Error(`SSH tunnel failed to establish to ${host}`);
|
|
80
93
|
}
|
|
81
|
-
|
|
94
|
+
let wsUrl;
|
|
95
|
+
let browser;
|
|
96
|
+
try {
|
|
97
|
+
({ wsUrl, browser } = await discoverBrowserWsUrl(localPort, 'localhost', profile.name));
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
tunnel.kill();
|
|
101
|
+
if (opts.persistRemote) {
|
|
102
|
+
const why = err instanceof Error ? err.message : String(err);
|
|
103
|
+
throw new Error(`Browser profile "${profile.name}" is declared on ${host}, but nothing is speaking CDP on port ${remotePort}: ${why}`);
|
|
104
|
+
}
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
82
107
|
try {
|
|
83
108
|
verifyBrowserIdentity(browser, profile.browser, remotePort, host);
|
|
84
109
|
}
|
|
@@ -108,11 +133,13 @@ key) {
|
|
|
108
133
|
pid: tunnelPid,
|
|
109
134
|
cleanup: () => {
|
|
110
135
|
cdp.close();
|
|
111
|
-
// Kill the remote browser BEFORE tearing down the tunnel
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
|
|
136
|
+
// Kill the remote browser BEFORE tearing down the tunnel — but only
|
|
137
|
+
// when we launched it. persistRemote is an attach to someone else's
|
|
138
|
+
// declared browser; killing it would drop the logins the caller came
|
|
139
|
+
// for. Fire-and-forget — cleanup stays synchronous.
|
|
140
|
+
if (lifecycle.killOnCleanup) {
|
|
141
|
+
killRemoteBrowser(user, host, remoteOs, remotePort).catch(() => { });
|
|
142
|
+
}
|
|
116
143
|
tunnel.kill();
|
|
117
144
|
clearProfileRuntime(key);
|
|
118
145
|
},
|
|
@@ -96,7 +96,7 @@ export declare function sendIPCRequest(request: IPCRequest, opts?: IPCRequestOpt
|
|
|
96
96
|
* Fill actor / launchId / sessionId from the calling process when the request
|
|
97
97
|
* left them blank. Explicit values on the request always win.
|
|
98
98
|
*/
|
|
99
|
-
export declare function stampCallerIdentity(request: IPCRequest): IPCRequest;
|
|
99
|
+
export declare function stampCallerIdentity(request: IPCRequest, env?: NodeJS.ProcessEnv): IPCRequest;
|
|
100
100
|
/**
|
|
101
101
|
* Open one connection that can serve many browser operations. This uses the
|
|
102
102
|
* same daemon readiness and version-reconciliation path as sendIPCRequest;
|
package/dist/lib/browser/ipc.js
CHANGED
|
@@ -8,6 +8,7 @@ import { startDaemon, stopDaemon } from '../daemon/daemon.js';
|
|
|
8
8
|
import { getCliVersion } from '../version.js';
|
|
9
9
|
import { compareVersions } from '../agent-spec/primitives.js';
|
|
10
10
|
import { getDaemonLogPath } from '../daemon/daemon.js';
|
|
11
|
+
import { isFleetRemoteInvocation } from './remote-control.js';
|
|
11
12
|
import { resolveCallerIdentity } from './caller-identity.js';
|
|
12
13
|
import { actionable } from './service.js';
|
|
13
14
|
/**
|
|
@@ -382,6 +383,7 @@ export class BrowserIPCServer {
|
|
|
382
383
|
actor: request.actor,
|
|
383
384
|
launchId: request.launchId,
|
|
384
385
|
sessionId: request.sessionId,
|
|
386
|
+
fleetRemote: request.fleetRemote,
|
|
385
387
|
createIfMissing,
|
|
386
388
|
title: request.title,
|
|
387
389
|
url: request.url,
|
|
@@ -396,6 +398,9 @@ export class BrowserIPCServer {
|
|
|
396
398
|
};
|
|
397
399
|
}
|
|
398
400
|
request.task = resolved.task.name;
|
|
401
|
+
if (resolved.created && resolved.picked) {
|
|
402
|
+
request.picked = resolved.picked;
|
|
403
|
+
}
|
|
399
404
|
return undefined;
|
|
400
405
|
}
|
|
401
406
|
catch (err) {
|
|
@@ -421,6 +426,22 @@ export class BrowserIPCServer {
|
|
|
421
426
|
case 'version': {
|
|
422
427
|
return { ok: true, version: getCliVersion() };
|
|
423
428
|
}
|
|
429
|
+
case 'show': {
|
|
430
|
+
// Task-less by design — see BrowserService.showUrl. Absent from both
|
|
431
|
+
// PAGE_CREATE_VERBS and PAGE_RESOLVE_VERBS, so bindTask never ran above
|
|
432
|
+
// and this request carries no task.
|
|
433
|
+
if (!request.url) {
|
|
434
|
+
return { ok: false, error: actionable('URL required.', 'Next: agents browser navigate <url>') };
|
|
435
|
+
}
|
|
436
|
+
if (!request.profile) {
|
|
437
|
+
return { ok: false, error: actionable('Profile required.', 'Next: agents browser profiles list') };
|
|
438
|
+
}
|
|
439
|
+
const shown = await this.service.showUrl(request.profile, request.url, {
|
|
440
|
+
fleetRemote: request.fleetRemote,
|
|
441
|
+
actor: request.actor,
|
|
442
|
+
});
|
|
443
|
+
return { ok: true, tabId: shown.tabId, message: shown.picked };
|
|
444
|
+
}
|
|
424
445
|
case 'start': {
|
|
425
446
|
if (!request.profile) {
|
|
426
447
|
return { ok: false, error: actionable('Profile required.', 'Next: agents browser profiles list') };
|
|
@@ -434,6 +455,7 @@ export class BrowserIPCServer {
|
|
|
434
455
|
actor: request.actor,
|
|
435
456
|
launchId: request.launchId,
|
|
436
457
|
sessionId: request.sessionId,
|
|
458
|
+
fleetRemote: request.fleetRemote,
|
|
437
459
|
title: request.title,
|
|
438
460
|
});
|
|
439
461
|
return {
|
|
@@ -442,10 +464,11 @@ export class BrowserIPCServer {
|
|
|
442
464
|
tabId: result.tabId,
|
|
443
465
|
windowTargetId: result.windowId,
|
|
444
466
|
skill: result.skill,
|
|
467
|
+
message: result.picked,
|
|
445
468
|
};
|
|
446
469
|
}
|
|
447
470
|
// The out-of-process seam onto the abandoned-task reaper: the daemon
|
|
448
|
-
// owns the live BrowserService, so a CLI verb (`agents browser
|
|
471
|
+
// owns the live BrowserService, so a CLI verb (`agents browser prune`) can
|
|
449
472
|
// only reach `reapAbandoned` through IPC. The daemon's own periodic tick
|
|
450
473
|
// calls the service method directly.
|
|
451
474
|
case 'gc': {
|
|
@@ -514,7 +537,8 @@ export class BrowserIPCServer {
|
|
|
514
537
|
};
|
|
515
538
|
}
|
|
516
539
|
const result = await this.service.navigate(request.task, request.url, request.profile);
|
|
517
|
-
|
|
540
|
+
const picked = request.picked;
|
|
541
|
+
return { ok: true, tabId: result.tabId, task: request.task, message: picked };
|
|
518
542
|
}
|
|
519
543
|
case 'tab-add': {
|
|
520
544
|
if (!request.task || !request.url) {
|
|
@@ -855,15 +879,22 @@ export async function sendIPCRequest(request, opts = {}) {
|
|
|
855
879
|
* Fill actor / launchId / sessionId from the calling process when the request
|
|
856
880
|
* left them blank. Explicit values on the request always win.
|
|
857
881
|
*/
|
|
858
|
-
export function stampCallerIdentity(request) {
|
|
859
|
-
|
|
860
|
-
|
|
882
|
+
export function stampCallerIdentity(request, env = process.env) {
|
|
883
|
+
// The consent marker is stamped FIRST, above the identity early-return: a
|
|
884
|
+
// request that already carries a full identity (every streamed request does)
|
|
885
|
+
// would otherwise skip stamping entirely and reach the daemon unmarked.
|
|
886
|
+
// OR-ed, never overwritten — a client may assert the marker but must not be
|
|
887
|
+
// able to clear one this environment sets.
|
|
888
|
+
const fleetRemote = isFleetRemoteInvocation(env) || request.fleetRemote === true;
|
|
889
|
+
const marked = request.fleetRemote === fleetRemote ? request : { ...request, fleetRemote };
|
|
890
|
+
if (marked.actor && marked.launchId && marked.sessionId)
|
|
891
|
+
return marked;
|
|
861
892
|
const id = resolveCallerIdentity();
|
|
862
893
|
return {
|
|
863
|
-
...
|
|
864
|
-
actor:
|
|
865
|
-
launchId:
|
|
866
|
-
sessionId:
|
|
894
|
+
...marked,
|
|
895
|
+
actor: marked.actor ?? id.actor,
|
|
896
|
+
launchId: marked.launchId ?? id.launchId,
|
|
897
|
+
sessionId: marked.sessionId ?? id.sessionId,
|
|
867
898
|
};
|
|
868
899
|
}
|
|
869
900
|
/**
|