@phnx-labs/agents-cli 1.22.117 → 1.22.119
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +301 -0
- package/dist/bootstrap.js +4 -11
- package/dist/commands/accounts.js +7 -4
- package/dist/commands/browser.d.ts +52 -70
- package/dist/commands/browser.js +192 -3562
- package/dist/commands/daemon.d.ts +39 -2
- package/dist/commands/daemon.js +72 -73
- package/dist/commands/events.js +1 -1
- package/dist/commands/exec.js +53 -1
- package/dist/commands/projects.js +92 -7
- package/dist/commands/repo.js +13 -49
- package/dist/commands/route.js +5 -5
- package/dist/commands/routines.js +39 -12
- package/dist/commands/sessions-render.d.ts +1 -1
- package/dist/commands/sessions-render.js +0 -1
- package/dist/commands/sessions.test-fixture.d.ts +0 -1
- package/dist/commands/sessions.test-fixture.js +0 -37
- package/dist/commands/setup-browser.d.ts +17 -12
- package/dist/commands/setup-browser.js +52 -87
- package/dist/commands/setup-preferences.d.ts +0 -17
- package/dist/commands/setup-preferences.js +18 -49
- package/dist/commands/setup.js +9 -9
- package/dist/commands/ssh.js +10 -1
- package/dist/commands/sync.js +6 -44
- package/dist/commands/teams.js +0 -1
- package/dist/commands/update.d.ts +19 -0
- package/dist/commands/update.js +34 -6
- package/dist/commands/view.d.ts +12 -1
- package/dist/commands/view.js +47 -2
- package/dist/index.js +107 -26
- package/dist/lib/account-capabilities.js +0 -2
- package/dist/lib/account-catalog.d.ts +38 -0
- package/dist/lib/account-catalog.js +110 -26
- package/dist/lib/account-provider-registry.js +1 -1
- package/dist/lib/account-registry.d.ts +1 -1
- package/dist/lib/account-registry.js +36 -2
- package/dist/lib/accounting/rotate.js +22 -8
- package/dist/lib/accounting/usage-ingest.d.ts +6 -1
- package/dist/lib/accounting/usage-ingest.js +115 -45
- package/dist/lib/accounting/usage-sync.d.ts +134 -31
- package/dist/lib/accounting/usage-sync.js +227 -66
- package/dist/lib/accounting/usage.d.ts +16 -3
- package/dist/lib/accounting/usage.js +26 -8
- package/dist/lib/accounts/add.d.ts +3 -1
- package/dist/lib/accounts/add.js +24 -13
- package/dist/lib/accounts/slots.d.ts +8 -0
- package/dist/lib/accounts/slots.js +17 -0
- package/dist/lib/add-dir.js +0 -1
- package/dist/lib/agent-cli-commands.js +0 -1
- package/dist/lib/agent-spec/agents.d.ts +6 -7
- package/dist/lib/agent-spec/agents.js +7 -60
- package/dist/lib/auth-health.d.ts +69 -1
- package/dist/lib/auth-health.js +133 -4
- package/dist/lib/browser/context.d.ts +82 -0
- package/dist/lib/browser/context.js +88 -0
- package/dist/lib/browser/paths.d.ts +25 -0
- package/dist/lib/browser/paths.js +51 -0
- package/dist/lib/browser/record.d.ts +38 -0
- package/dist/lib/browser/record.js +57 -0
- package/dist/lib/browser/sessions-list.js +1 -2
- package/dist/lib/browser-client.d.ts +158 -0
- package/dist/lib/browser-client.js +234 -0
- package/dist/lib/channels/owner-sink.d.ts +1 -18
- package/dist/lib/channels/owner-sink.js +29 -82
- package/dist/lib/channels/providers/rush.d.ts +7 -7
- package/dist/lib/channels/providers/rush.js +127 -57
- package/dist/lib/claude-account-token.d.ts +7 -1
- package/dist/lib/claude-account-token.js +26 -4
- package/dist/lib/claude-statusline.d.ts +1 -1
- package/dist/lib/claude-statusline.js +15 -3
- package/dist/lib/commands.js +1 -1
- package/dist/lib/crabbox/lease.js +1 -1
- package/dist/lib/crabbox/runtimes.js +1 -3
- package/dist/lib/daemon/account-state-daemon-service.js +1 -0
- package/dist/lib/daemon/auth-sync-service.d.ts +21 -18
- package/dist/lib/daemon/auth-sync-service.js +37 -43
- package/dist/lib/daemon/daemon.d.ts +25 -14
- package/dist/lib/daemon/daemon.js +92 -154
- package/dist/lib/daemon/runner.d.ts +2 -2
- package/dist/lib/daemon/runner.js +32 -9
- package/dist/lib/daemon/self-update-service.d.ts +35 -34
- package/dist/lib/daemon/self-update-service.js +40 -36
- package/dist/lib/daemon/service.d.ts +12 -8
- package/dist/lib/daemon/supervisor.d.ts +65 -54
- package/dist/lib/daemon/supervisor.js +136 -171
- package/dist/lib/daemon/testdata/process-view-start.js +5 -2
- package/dist/lib/daemon/usage-sync-service.d.ts +21 -30
- package/dist/lib/daemon/usage-sync-service.js +46 -78
- package/dist/lib/daemon-health.d.ts +22 -5
- package/dist/lib/daemon-health.js +47 -3
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +0 -10
- package/dist/lib/daemon-ticks.js +4 -2
- package/dist/lib/devices/doctor-findings.js +8 -11
- package/dist/lib/devices/harness-inventory.js +14 -5
- package/dist/lib/exec-account-home.js +5 -0
- package/dist/lib/exec.js +10 -13
- package/dist/lib/feed/events.js +0 -1
- package/dist/lib/feed/tool-activity.js +5 -2
- package/dist/lib/fleet-shared-state.d.ts +45 -0
- package/dist/lib/fleet-shared-state.js +78 -9
- package/dist/lib/fs-atomic.js +25 -0
- package/dist/lib/github/pr-mergeable.d.ts +4 -2
- package/dist/lib/github/pr-mergeable.js +5 -3
- package/dist/lib/github/project-prs.d.ts +58 -3
- package/dist/lib/github/project-prs.js +205 -7
- package/dist/lib/harness/adapters/cursor.js +14 -4
- package/dist/lib/harness-auth-capabilities.d.ts +4 -3
- package/dist/lib/harness-auth-capabilities.js +1 -2
- package/dist/lib/helper-versions.js +1 -1
- package/dist/lib/hooks/cache.d.ts +7 -0
- package/dist/lib/hooks/cache.js +27 -2
- package/dist/lib/hooks/install.js +7 -1
- package/dist/lib/hosts/ready.d.ts +38 -8
- package/dist/lib/hosts/ready.js +62 -14
- package/dist/lib/installations/active-check.d.ts +27 -0
- package/dist/lib/installations/active-check.js +61 -0
- package/dist/lib/installations/migrate.js +6 -3
- package/dist/lib/installations/shims.d.ts +1 -1
- package/dist/lib/installations/shims.js +32 -12
- package/dist/lib/installations/store.d.ts +9 -0
- package/dist/lib/installations/store.js +24 -0
- package/dist/lib/installations/update.js +21 -15
- package/dist/lib/installations/versions.js +15 -3
- package/dist/lib/manifest.js +1 -1
- package/dist/lib/models.js +0 -2
- package/dist/lib/open-url.js +28 -54
- package/dist/lib/profiles.js +0 -2
- package/dist/lib/projects.d.ts +16 -0
- package/dist/lib/projects.js +31 -0
- package/dist/lib/refresh.js +1 -1
- package/dist/lib/routine-readiness.d.ts +35 -0
- package/dist/lib/routine-readiness.js +74 -16
- package/dist/lib/rules/compile.js +6 -0
- package/dist/lib/sandbox.js +0 -2
- package/dist/lib/secrets-policy.d.ts +89 -11
- package/dist/lib/secrets-policy.js +175 -28
- package/dist/lib/session/active.d.ts +17 -6
- package/dist/lib/session/active.js +105 -49
- package/dist/lib/session/db.d.ts +1 -1
- package/dist/lib/session/db.js +65 -18
- package/dist/lib/session/discover.js +1 -283
- package/dist/lib/session/mirror.d.ts +4 -3
- package/dist/lib/session/mirror.js +4 -3
- package/dist/lib/session/parse.d.ts +0 -2
- package/dist/lib/session/parse.js +1 -138
- package/dist/lib/session/pid-registry.d.ts +6 -3
- package/dist/lib/session/pid-registry.js +18 -8
- package/dist/lib/session/remote/watch.d.ts +1 -1
- package/dist/lib/session/remote/watch.js +7 -3
- package/dist/lib/session/throughput.d.ts +1 -3
- package/dist/lib/session/throughput.js +0 -20
- package/dist/lib/session/types.d.ts +2 -2
- package/dist/lib/session/types.js +2 -2
- package/dist/lib/sessions-client.d.ts +60 -1
- package/dist/lib/sessions-client.js +227 -4
- package/dist/lib/setup-tool-status.js +5 -10
- package/dist/lib/signin-badge.d.ts +3 -2
- package/dist/lib/signin-badge.js +6 -3
- package/dist/lib/smart-launch.js +4 -1
- package/dist/lib/staleness/detectors/commands.d.ts +1 -1
- package/dist/lib/staleness/detectors/hooks.d.ts +1 -1
- package/dist/lib/staleness/detectors/mcp.d.ts +1 -1
- package/dist/lib/staleness/detectors/permissions.d.ts +1 -1
- package/dist/lib/staleness/detectors/plugins.d.ts +1 -1
- package/dist/lib/staleness/detectors/rules.d.ts +1 -1
- package/dist/lib/staleness/detectors/skills.d.ts +1 -1
- package/dist/lib/staleness/detectors/subagents.d.ts +1 -1
- package/dist/lib/staleness/detectors/workflows.d.ts +1 -1
- package/dist/lib/staleness/writers/commands.d.ts +1 -1
- package/dist/lib/staleness/writers/hooks.d.ts +1 -1
- package/dist/lib/staleness/writers/mcp.d.ts +1 -1
- package/dist/lib/staleness/writers/permissions.d.ts +1 -1
- package/dist/lib/staleness/writers/plugins.d.ts +1 -1
- package/dist/lib/staleness/writers/rules.d.ts +1 -1
- package/dist/lib/staleness/writers/skills.d.ts +1 -1
- package/dist/lib/staleness/writers/sources.js +5 -0
- package/dist/lib/staleness/writers/subagents.d.ts +1 -1
- package/dist/lib/staleness/writers/workflows.d.ts +1 -1
- package/dist/lib/state.d.ts +6 -4
- package/dist/lib/state.js +7 -17
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/teams/parsers.js +1 -136
- package/dist/lib/teams/placement-probe.js +50 -15
- package/dist/lib/teams/scheduler.d.ts +7 -0
- package/dist/lib/types.d.ts +16 -1
- package/dist/lib/types.js +1 -1
- package/dist/lib/view-types.d.ts +32 -0
- package/dist/lib/watchdog/read.js +0 -1
- package/dist/lib/watchdog/rotate.js +1 -2
- package/dist/lib/watchdog/runner.js +2 -2
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdogTail.js +0 -31
- package/package.json +1 -1
- package/scripts/postinstall.js +8 -8
- package/dist/browser.d.ts +0 -2
- package/dist/browser.js +0 -17
- package/dist/commands/browser-picker.d.ts +0 -18
- package/dist/commands/browser-picker.js +0 -97
- package/dist/lib/browser/arc-discovery.d.ts +0 -52
- package/dist/lib/browser/arc-discovery.js +0 -197
- package/dist/lib/browser/arc-dom.d.ts +0 -14
- package/dist/lib/browser/arc-dom.js +0 -121
- package/dist/lib/browser/caller-identity.d.ts +0 -40
- package/dist/lib/browser/caller-identity.js +0 -175
- package/dist/lib/browser/cdp.d.ts +0 -51
- package/dist/lib/browser/cdp.js +0 -254
- package/dist/lib/browser/chrome.d.ts +0 -141
- package/dist/lib/browser/chrome.js +0 -704
- package/dist/lib/browser/chromium-discovery.d.ts +0 -28
- package/dist/lib/browser/chromium-discovery.js +0 -96
- package/dist/lib/browser/devices.d.ts +0 -34
- package/dist/lib/browser/devices.js +0 -61
- package/dist/lib/browser/domain-skills.d.ts +0 -71
- package/dist/lib/browser/domain-skills.js +0 -195
- package/dist/lib/browser/drivers/arc.d.ts +0 -40
- package/dist/lib/browser/drivers/arc.js +0 -276
- package/dist/lib/browser/drivers/firefox.d.ts +0 -99
- package/dist/lib/browser/drivers/firefox.js +0 -377
- package/dist/lib/browser/drivers/local.d.ts +0 -55
- package/dist/lib/browser/drivers/local.js +0 -266
- package/dist/lib/browser/drivers/ssh.d.ts +0 -120
- package/dist/lib/browser/drivers/ssh.js +0 -467
- package/dist/lib/browser/editor.d.ts +0 -3
- package/dist/lib/browser/editor.js +0 -50
- package/dist/lib/browser/ffmpeg.d.ts +0 -12
- package/dist/lib/browser/ffmpeg.js +0 -184
- package/dist/lib/browser/firefox-discovery.d.ts +0 -69
- package/dist/lib/browser/firefox-discovery.js +0 -162
- package/dist/lib/browser/har.d.ts +0 -85
- package/dist/lib/browser/har.js +0 -77
- package/dist/lib/browser/hygiene.d.ts +0 -97
- package/dist/lib/browser/hygiene.js +0 -153
- package/dist/lib/browser/index.d.ts +0 -5
- package/dist/lib/browser/index.js +0 -5
- package/dist/lib/browser/input.d.ts +0 -7
- package/dist/lib/browser/input.js +0 -92
- package/dist/lib/browser/ipc.d.ts +0 -204
- package/dist/lib/browser/ipc.js +0 -1453
- package/dist/lib/browser/login-detection.d.ts +0 -87
- package/dist/lib/browser/login-detection.js +0 -274
- package/dist/lib/browser/profiles.d.ts +0 -422
- package/dist/lib/browser/profiles.js +0 -1157
- package/dist/lib/browser/refs.d.ts +0 -89
- package/dist/lib/browser/refs.js +0 -191
- package/dist/lib/browser/registry.d.ts +0 -70
- package/dist/lib/browser/registry.js +0 -252
- package/dist/lib/browser/remote-control.d.ts +0 -59
- package/dist/lib/browser/remote-control.js +0 -85
- package/dist/lib/browser/resolve-target.d.ts +0 -66
- package/dist/lib/browser/resolve-target.js +0 -264
- package/dist/lib/browser/runtime-state.d.ts +0 -291
- package/dist/lib/browser/runtime-state.js +0 -584
- package/dist/lib/browser/secret-ref.d.ts +0 -10
- package/dist/lib/browser/secret-ref.js +0 -14
- package/dist/lib/browser/service.d.ts +0 -812
- package/dist/lib/browser/service.js +0 -4693
- package/dist/lib/browser/stream.d.ts +0 -17
- package/dist/lib/browser/stream.js +0 -72
- package/dist/lib/browser/task-index.d.ts +0 -78
- package/dist/lib/browser/task-index.js +0 -199
- package/dist/lib/browser/types.d.ts +0 -647
- package/dist/lib/browser/types.js +0 -61
- package/dist/lib/browser/upload.d.ts +0 -29
- package/dist/lib/browser/upload.js +0 -298
- package/dist/lib/daemon/browser-ipc-service.d.ts +0 -20
- package/dist/lib/daemon/browser-ipc-service.js +0 -46
- package/dist/lib/daemon/browser-task-reap-service.d.ts +0 -14
- package/dist/lib/daemon/browser-task-reap-service.js +0 -26
- package/dist/lib/fleet-shared-repo-sync.d.ts +0 -55
- package/dist/lib/fleet-shared-repo-sync.js +0 -490
|
@@ -13,9 +13,8 @@
|
|
|
13
13
|
* partial state.
|
|
14
14
|
*/
|
|
15
15
|
import chalk from 'chalk';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import { DEFAULT_VIEWPORT } from '../lib/browser/devices.js';
|
|
16
|
+
import { browserInstalled, runBrowser } from '../lib/browser-client.js';
|
|
17
|
+
import { buildBrowserContext } from '../lib/browser/context.js';
|
|
19
18
|
import { getConfigValue, setConfigValue } from '../lib/device-config.js';
|
|
20
19
|
import { loadDevices } from '../lib/devices/registry.js';
|
|
21
20
|
import { machineId } from '../lib/machine-id.js';
|
|
@@ -38,14 +37,6 @@ export function defaultInteractiveHostChoice(candidates, self = machineId()) {
|
|
|
38
37
|
return null;
|
|
39
38
|
return candidates.includes(self) ? self : candidates[0];
|
|
40
39
|
}
|
|
41
|
-
/**
|
|
42
|
-
* The browser picker's highlighted default — the same browser auto-detect
|
|
43
|
-
* would win, since `listInstalledBrowsers` returns platform priority order
|
|
44
|
-
* (macOS: chrome first). null when nothing is installed.
|
|
45
|
-
*/
|
|
46
|
-
export function defaultBrowserChoice(installed) {
|
|
47
|
-
return installed.length > 0 ? installed[0].browserType : null;
|
|
48
|
-
}
|
|
49
40
|
/**
|
|
50
41
|
* Offer to set the interactive host when none is configured and the registry
|
|
51
42
|
* has more than one macOS device. Returns true when a host was set. Silent
|
|
@@ -94,48 +85,26 @@ export async function maybePickBrowserProfile(deps = {}) {
|
|
|
94
85
|
const interactive = deps.interactive ?? isInteractiveTerminal();
|
|
95
86
|
if (!interactive)
|
|
96
87
|
return false;
|
|
97
|
-
|
|
88
|
+
// Already chosen (by an earlier setup, `agents browser use`, or browser-cli
|
|
89
|
+
// directly) — never re-pin behind the user's back.
|
|
90
|
+
if (getConfigValue('browser.profile').value)
|
|
98
91
|
return false;
|
|
99
|
-
|
|
92
|
+
// The engine owns detection and profile creation (PHNX-4101); it is optional,
|
|
93
|
+
// so skip the pick quietly when it is not installed rather than nagging.
|
|
94
|
+
if (!browserInstalled())
|
|
100
95
|
return false;
|
|
101
|
-
|
|
102
|
-
|
|
96
|
+
// Let the engine detect installed browsers and create a machine-local profile
|
|
97
|
+
// for each (idempotent), then open its own picker to set this machine's default.
|
|
98
|
+
const context = await buildBrowserContext();
|
|
99
|
+
const seed = await runBrowser({ argv: ['profiles', 'seed'], context });
|
|
100
|
+
if (seed.exitCode !== 0)
|
|
103
101
|
return false;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
choices: [
|
|
108
|
-
...installed.map((b) => ({ name: `${b.browserType} ${chalk.dim(b.binary)}`, value: b.browserType })),
|
|
109
|
-
{ name: `None ${chalk.dim('— this box uses the fleet hub (agents config set browser.device <host>)')}`, value: SKIP },
|
|
110
|
-
],
|
|
111
|
-
};
|
|
112
|
-
// The prompt is the only non-testable boundary here, so it is injectable —
|
|
113
|
-
// everything after (createProfile, setConfigValue) runs for real.
|
|
114
|
-
let picked;
|
|
115
|
-
if (deps.select) {
|
|
116
|
-
picked = await deps.select(config);
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
const { select } = await import('@inquirer/prompts');
|
|
120
|
-
picked = await select(config);
|
|
121
|
-
}
|
|
122
|
-
if (picked === SKIP)
|
|
102
|
+
await runBrowser({ argv: ['use'], context });
|
|
103
|
+
const chosen = getConfigValue('browser.profile').value;
|
|
104
|
+
if (!chosen)
|
|
123
105
|
return false;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const profile = {
|
|
127
|
-
name: DEFAULT_BROWSER_PROFILE_NAME,
|
|
128
|
-
description: `${chosen.browserType} profile (chosen during setup)`,
|
|
129
|
-
browser: chosen.browserType,
|
|
130
|
-
binary: chosen.binary,
|
|
131
|
-
endpoints: [`cdp://127.0.0.1:${freePort}`],
|
|
132
|
-
viewport: { width: DEFAULT_VIEWPORT.width, height: DEFAULT_VIEWPORT.height },
|
|
133
|
-
};
|
|
134
|
-
await createProfile(profile);
|
|
135
|
-
// The same key `agents browser use` writes (device-local).
|
|
136
|
-
setConfigValue('browser.profile', profile.name);
|
|
137
|
-
console.log(chalk.green(`Browser: '${chosen.browserType}'`) +
|
|
138
|
-
chalk.dim(` — profile "${profile.name}" is this machine's default (agents browser use to change).`));
|
|
106
|
+
console.log(chalk.green(`Browser: '${chosen}'`) +
|
|
107
|
+
chalk.dim(' — this machine\'s default (agents browser use to change).'));
|
|
139
108
|
return true;
|
|
140
109
|
}
|
|
141
110
|
/**
|
package/dist/commands/setup.js
CHANGED
|
@@ -31,8 +31,7 @@ import { registerAliasCommand } from './alias.js';
|
|
|
31
31
|
import { registerBetaCommands } from './beta.js';
|
|
32
32
|
import { addUrlSchemeSubcommands } from './open.js';
|
|
33
33
|
import { runPreferencesStep } from './setup-preferences.js';
|
|
34
|
-
import {
|
|
35
|
-
import { listInstalledBrowsers } from '../lib/browser/chrome.js';
|
|
34
|
+
import { browserInstalled } from '../lib/browser-client.js';
|
|
36
35
|
import { probeComputerTrust } from './computer.js';
|
|
37
36
|
import { loadDevices } from '../lib/devices/registry.js';
|
|
38
37
|
import { getConfigValue } from '../lib/device-config.js';
|
|
@@ -269,12 +268,13 @@ export async function ensureInitialized(program) {
|
|
|
269
268
|
await runSetup(program, { suppressFooter: true });
|
|
270
269
|
}
|
|
271
270
|
export async function getSetupStatus() {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const
|
|
277
|
-
const
|
|
271
|
+
// Browser readiness is now config + standalone presence (PHNX-4101): the engine
|
|
272
|
+
// (@phnx-labs/browser-cli) owns profile declarations and launchability, so
|
|
273
|
+
// agents-cli reads the shared default-profile key it and browser-cli both write
|
|
274
|
+
// rather than resolving a profile through a deleted in-repo engine.
|
|
275
|
+
const browserCliInstalled = browserInstalled();
|
|
276
|
+
const configuredBrowserProfile = getConfigValue('browser.profile').value;
|
|
277
|
+
const browserReady = browserCliInstalled && !!configuredBrowserProfile;
|
|
278
278
|
const computerState = process.platform === 'darwin' ? (await probeComputerTrust() ? 'ready' : 'missing') : 'n/a';
|
|
279
279
|
const devices = await loadDevices();
|
|
280
280
|
const coreReady = isGitRepo(getAgentsDir());
|
|
@@ -286,7 +286,7 @@ export async function getSetupStatus() {
|
|
|
286
286
|
const defaultBrowser = getConfigValue('browser.profile').value;
|
|
287
287
|
return [
|
|
288
288
|
{ phase: 'core', state: coreReady ? 'ready' : 'missing', detail: coreReady ? 'system repo ready' : 'system repo missing' },
|
|
289
|
-
{ phase: 'browser', state: browserReady ? 'ready' : 'missing', detail: browserReady ? `profile ${
|
|
289
|
+
{ phase: 'browser', state: browserReady ? 'ready' : 'missing', detail: browserReady ? `profile ${configuredBrowserProfile}` : !browserCliInstalled ? 'browser CLI not installed (npm i -g @phnx-labs/browser-cli)' : 'no default profile (agents setup browser)' },
|
|
290
290
|
{ phase: 'computer', state: computerState, detail: computerState === 'ready' ? 'helper trusted' : computerState === 'n/a' ? 'macOS local setup only' : 'helper not running or not trusted' },
|
|
291
291
|
{ phase: 'secrets', state: secretsReady ? 'ready' : 'missing', detail: secretsReady ? 'defaults chosen' : 'defaults not chosen' },
|
|
292
292
|
{ phase: 'term', state: termReady ? 'ready' : 'missing', detail: termReady ? 'installed' : 'not installed (accounts add/login spawn it)' },
|
package/dist/commands/ssh.js
CHANGED
|
@@ -789,7 +789,16 @@ async function runFleetPing(opts) {
|
|
|
789
789
|
if (opts.local) {
|
|
790
790
|
const { authRows: rows } = await refreshLocalFleetAuthState({ force: true });
|
|
791
791
|
if (opts.json) {
|
|
792
|
-
|
|
792
|
+
// Per-agent launchability from the SAME run-router path the local readiness
|
|
793
|
+
// check uses (`collectRunCandidates`/`isLaunchableSignedIn`), so a consumer
|
|
794
|
+
// can tell a box that simply could not probe — a worker's setup-token box,
|
|
795
|
+
// or a headed box that spent its usage-endpoint budget, both DROP their
|
|
796
|
+
// `no_evidence` row — from a box with no credential at all. From `rows`
|
|
797
|
+
// alone the two are indistinguishable, which broke `--host` routine
|
|
798
|
+
// readiness on every worker (PHNX-4116).
|
|
799
|
+
const { collectRunCandidates } = await import('../lib/accounting/rotate.js');
|
|
800
|
+
const launchable = (await Promise.all(ALL_AGENT_IDS.map(async (agent) => (await collectRunCandidates(agent)).some((candidate) => candidate.signedIn) ? agent : null))).filter((agent) => agent !== null);
|
|
801
|
+
console.log(JSON.stringify({ host: self, rows, launchable }));
|
|
793
802
|
}
|
|
794
803
|
else {
|
|
795
804
|
for (const line of renderAuthMatrix([{ host: self, rows }], { verbose: opts.verbose }))
|
package/dist/commands/sync.js
CHANGED
|
@@ -35,8 +35,6 @@ import { Option } from 'commander';
|
|
|
35
35
|
import chalk from 'chalk';
|
|
36
36
|
import { resolveSyncPassphraseFromEnv } from '../lib/sync-passphrase.js';
|
|
37
37
|
import { agentLabel, resolveAgentName, MANAGED_AGENT_IDS, isAgentHardDeprecated, hardDeprecationError } from '../lib/agents.js';
|
|
38
|
-
import { autoEvictCentralBrowserProfiles } from '../lib/browser/registry.js';
|
|
39
|
-
import { shouldAutoClaimCentralProfile } from '../lib/browser/profiles.js';
|
|
40
38
|
import { isVersionInstalled, syncResourcesToVersion, parseAgentSpec, resolveVersion, resolveVersionAlias, listInstalledVersions, healDanglingVersionPointers, getAvailableResources, getActuallySyncedResources, getProjectOnlyResources, getNewResources, hasNewResources, promptResourceSelection, promptNewResourceSelection, buildSelection, mergeRepoScopedSelections, listRepoNames, getVersionHomePath, } from '../lib/installations/versions.js';
|
|
41
39
|
import { capableAgents } from '../lib/capabilities.js';
|
|
42
40
|
import { parseHookManifest, registerHooksToSettings } from '../lib/hooks/install.js';
|
|
@@ -397,12 +395,9 @@ async function runInteractiveReconcile(opts, outLog, errLog) {
|
|
|
397
395
|
else
|
|
398
396
|
outLog(chalk.yellow(` ! ${repo}: ${(res.error ?? 'pull failed').split('\n')[0]}`));
|
|
399
397
|
}
|
|
400
|
-
//
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
// already migrated (which would flip that profile's kind identity->fungible).
|
|
404
|
-
// The interactive path is always non-quiet, non-json.
|
|
405
|
-
evictCentralBrowserProfilesForSync(false, false, outLog, errLog);
|
|
398
|
+
// Central browser-profile claiming is the standalone `browser` CLI's now
|
|
399
|
+
// (PHNX-4101): `browser profiles claim` / `browser profiles prune` own it.
|
|
400
|
+
// agents-cli no longer evicts central `browser:` tombstones on sync.
|
|
406
401
|
// 2. One selection spanning the chosen repos.
|
|
407
402
|
const selection = mergeRepoScopedSelections(repos, cwd);
|
|
408
403
|
const hasResources = selection.memory === 'all' || Object.entries(selection).some(([kind, v]) => kind !== 'memory' && Array.isArray(v) && v.length > 0);
|
|
@@ -443,36 +438,6 @@ async function runInteractiveReconcile(opts, outLog, errLog) {
|
|
|
443
438
|
if (repairHadFailures(umbrellaRepair))
|
|
444
439
|
process.exitCode = 1;
|
|
445
440
|
}
|
|
446
|
-
/**
|
|
447
|
-
* Drain the legacy central `browser:` tombstone during `agents sync` (PHNX-3315).
|
|
448
|
-
* New profiles write the per-device doc, but profiles created before the
|
|
449
|
-
* device-scoped store lingered in the shared top-level `agents.yaml` and churned
|
|
450
|
-
* every fleet pull until someone ran `agents browser profiles claim` by hand.
|
|
451
|
-
* Fold the ones THIS box can host into its device doc — host-gated, and the
|
|
452
|
-
* selection is computed under the meta lock (see autoEvictCentralBrowserProfiles)
|
|
453
|
-
* so it never races itself. Callers MUST invoke this AFTER the repo pull: acting
|
|
454
|
-
* on a pre-pull view of central risks re-claiming a profile a peer already
|
|
455
|
-
* drained, which would flip its kind identity->fungible. Non-fatal so a hiccup
|
|
456
|
-
* can never wedge the sync.
|
|
457
|
-
*/
|
|
458
|
-
function evictCentralBrowserProfilesForSync(quiet, json, outLog, errLog) {
|
|
459
|
-
try {
|
|
460
|
-
// Auto-claim ONLY remote (ssh://) tombstones — they are fungible by design,
|
|
461
|
-
// so a concurrent cross-machine double-claim is harmless. Local/cdp profiles
|
|
462
|
-
// have no per-machine ownership signal and are left central for an explicit
|
|
463
|
-
// `agents browser profiles claim` (PHNX-3315 review). See
|
|
464
|
-
// shouldAutoClaimCentralProfile.
|
|
465
|
-
const result = autoEvictCentralBrowserProfiles(shouldAutoClaimCentralProfile);
|
|
466
|
-
if (!quiet && !json && result.claimed.length > 0) {
|
|
467
|
-
outLog(chalk.gray(` Claimed ${result.claimed.length} central browser profile(s) into this device: ${result.claimed.join(', ')}`));
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
catch (err) {
|
|
471
|
-
if (!quiet && !json) {
|
|
472
|
-
errLog(chalk.yellow(` ! browser profile eviction skipped: ${err.message}`));
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
441
|
/**
|
|
477
442
|
* The umbrella verb: bare `agents sync` (no agent) makes this machine current.
|
|
478
443
|
* Resolves the flags + a secrets passphrase (env-only for now; tokenized auth
|
|
@@ -550,12 +515,9 @@ async function runUmbrella(opts, quiet, outLog, errLog, json = false) {
|
|
|
550
515
|
log: (msg) => { if (!quiet && !json)
|
|
551
516
|
outLog(chalk.gray(` ${msg}`)); },
|
|
552
517
|
});
|
|
553
|
-
//
|
|
554
|
-
//
|
|
555
|
-
//
|
|
556
|
-
// --cloud (fetch-only, no local reconcile).
|
|
557
|
-
if (!opts.cloud && !kindSelection)
|
|
558
|
-
evictCentralBrowserProfilesForSync(quiet, json, outLog, errLog);
|
|
518
|
+
// Central browser-profile claiming moved to the standalone `browser` CLI
|
|
519
|
+
// (PHNX-4101: `browser profiles claim` / `prune`), so `agents sync` no longer
|
|
520
|
+
// drains the central `browser:` tombstone.
|
|
559
521
|
// Post-reconcile verification (PHNX-3186): re-read every version the reconcile
|
|
560
522
|
// wrote into and confirm it now matches source. Without this the umbrella
|
|
561
523
|
// printed `✓ sync: reconciled` unconditionally, the exact false-success the
|
package/dist/commands/teams.js
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import type { AgentId } from '../lib/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Split `<agent>[@<selector>]`. The selector names an INSTALLATION — its frozen
|
|
5
|
+
* label, or the release it currently carries — never a release to install; that
|
|
6
|
+
* is `--to`. Keeping them separate is what lets `agents update claude@2.0.65
|
|
7
|
+
* --to 2.0.71` read unambiguously.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* `<agent>[@<installed-version>][#<account>]`. The `#<account>` form is the
|
|
11
|
+
* same selector `agents run claude#work` takes: accounts run on the harness's
|
|
12
|
+
* one managed installation (PHNX-3940), so it names WHICH login the user means
|
|
13
|
+
* and resolves to that installation. Refusing it as "Unknown agent
|
|
14
|
+
* 'claude#work'" sent operators hunting for a per-account install that does
|
|
15
|
+
* not exist.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseTarget(raw: string): {
|
|
18
|
+
agent: AgentId;
|
|
19
|
+
selector?: string;
|
|
20
|
+
account?: string;
|
|
21
|
+
};
|
|
3
22
|
/**
|
|
4
23
|
* One line naming the installations a bare `agents update <agent>` did NOT
|
|
5
24
|
* touch — isolated (`--isolated`) copies and legacy pre-v2 per-version homes —
|
package/dist/commands/update.js
CHANGED
|
@@ -3,23 +3,48 @@ import { AGENTS, formatAgentError, resolveAgentName } from '../lib/agents.js';
|
|
|
3
3
|
import { setHelpSections } from '../lib/help.js';
|
|
4
4
|
import { describeInstallation, listInstallations, planAutoUpdates, resolveInstallation, runAutoUpdatePass, selectUpdateStrategy, supportsPinnedUpdate, updateInstallation, } from '../lib/installations/index.js';
|
|
5
5
|
import { resolveManagedInstallation } from '../lib/installations/index.js';
|
|
6
|
+
import { findUnifiedAccount } from '../lib/account-registry.js';
|
|
7
|
+
import { readMeta } from '../lib/state.js';
|
|
6
8
|
/**
|
|
7
9
|
* Split `<agent>[@<selector>]`. The selector names an INSTALLATION — its frozen
|
|
8
10
|
* label, or the release it currently carries — never a release to install; that
|
|
9
11
|
* is `--to`. Keeping them separate is what lets `agents update claude@2.0.65
|
|
10
12
|
* --to 2.0.71` read unambiguously.
|
|
11
13
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* `<agent>[@<installed-version>][#<account>]`. The `#<account>` form is the
|
|
16
|
+
* same selector `agents run claude#work` takes: accounts run on the harness's
|
|
17
|
+
* one managed installation (PHNX-3940), so it names WHICH login the user means
|
|
18
|
+
* and resolves to that installation. Refusing it as "Unknown agent
|
|
19
|
+
* 'claude#work'" sent operators hunting for a per-account install that does
|
|
20
|
+
* not exist.
|
|
21
|
+
*/
|
|
22
|
+
export function parseTarget(raw) {
|
|
23
|
+
const hash = raw.indexOf('#');
|
|
24
|
+
const spec = hash === -1 ? raw : raw.slice(0, hash);
|
|
25
|
+
const account = hash === -1 ? undefined : raw.slice(hash + 1).trim();
|
|
26
|
+
if (hash !== -1 && !account) {
|
|
27
|
+
throw new Error(`Select an account after # in '${raw}', e.g. claude#work — or just <agent>.`);
|
|
28
|
+
}
|
|
29
|
+
if (account?.includes('@')) {
|
|
30
|
+
throw new Error(`Put the installation before the account: <agent>@<installed-version>#<account>, not '${raw}'.`);
|
|
31
|
+
}
|
|
32
|
+
const at = spec.indexOf('@');
|
|
33
|
+
const name = at === -1 ? spec : spec.slice(0, at);
|
|
34
|
+
const selector = at === -1 ? undefined : spec.slice(at + 1).trim();
|
|
16
35
|
if (at !== -1 && !selector) {
|
|
17
36
|
throw new Error(`Missing installation in '${raw}'. Use <agent>@<installed-version>, or just <agent>.`);
|
|
18
37
|
}
|
|
19
38
|
const agent = resolveAgentName(name);
|
|
20
39
|
if (!agent)
|
|
21
40
|
throw new Error(formatAgentError(name));
|
|
22
|
-
|
|
41
|
+
if (account) {
|
|
42
|
+
const known = findUnifiedAccount(account, readMeta(), undefined, agent);
|
|
43
|
+
if (!known) {
|
|
44
|
+
throw new Error(`Unknown ${AGENTS[agent].name} account '${account}'. See: agents accounts list ${agent}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { agent, selector, account };
|
|
23
48
|
}
|
|
24
49
|
/**
|
|
25
50
|
* One line naming the installations a bare `agents update <agent>` did NOT
|
|
@@ -219,7 +244,10 @@ export function registerUpdateCommand(program) {
|
|
|
219
244
|
if (!target) {
|
|
220
245
|
throw new Error('Which agent? Use: agents update <agent>[@<installed-version>], or agents update --auto.');
|
|
221
246
|
}
|
|
222
|
-
const { agent, selector } = parseTarget(target);
|
|
247
|
+
const { agent, selector, account } = parseTarget(target);
|
|
248
|
+
if (account && !options.json) {
|
|
249
|
+
console.log(chalk.gray(` account '${account}' runs on the managed ${AGENTS[agent].name} installation; updating that.`));
|
|
250
|
+
}
|
|
223
251
|
if (options.check) {
|
|
224
252
|
const plan = (await planAutoUpdates({ agents: [agent] })).filter((entry) => !selector || entry.installation.label === selector || entry.installation.releaseVersion === selector);
|
|
225
253
|
if (selector && plan.length === 0) {
|
package/dist/commands/view.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { AccountInfo } from '../lib/agents.js';
|
|
|
4
4
|
import type { AgentId } from '../lib/types.js';
|
|
5
5
|
import { type ConfiguredDeviceRole } from '../lib/device-config.js';
|
|
6
6
|
import { type NativeAccountCatalogRow } from '../lib/account-catalog.js';
|
|
7
|
-
import type { ResourceSection, ViewJsonAgent } from '../lib/view-types.js';
|
|
7
|
+
import type { ResourceSection, ViewJsonAgent, ViewJsonRunReady } from '../lib/view-types.js';
|
|
8
8
|
export type { ResourceItemJson, ResourceSection, SyncState, VersionResourcesJson, ViewJsonAgent, ViewJsonVersion } from '../lib/view-types.js';
|
|
9
9
|
import { type ProfileSummary } from '../lib/profiles.js';
|
|
10
10
|
import { type ByokUsageResult } from '../lib/byok-usage.js';
|
|
@@ -76,6 +76,17 @@ export declare function parseResourceSections(options: {
|
|
|
76
76
|
resources?: string | boolean;
|
|
77
77
|
detailed?: boolean;
|
|
78
78
|
} & ViewSectionFilter, jsonMode: boolean): Set<ResourceSection>;
|
|
79
|
+
/**
|
|
80
|
+
* The one run-readiness gate for one agent on THIS box (PHNX-4116). Runs the
|
|
81
|
+
* SAME enumeration the local router picks from — `collectRunCandidates` (native
|
|
82
|
+
* slots + version homes) through `readinessFromCandidate` — so the answer
|
|
83
|
+
* `agents view --json` publishes is exactly what a `run` here would find, and a
|
|
84
|
+
* remote `--device auto` dispatcher can read it instead of re-deriving freshness
|
|
85
|
+
* on its own box (which drifted: view.ts lists version homes, the router lists
|
|
86
|
+
* slots). Returns undefined when the agent has no candidates at all (nothing
|
|
87
|
+
* installed), so the field is simply absent rather than a misleading verdict.
|
|
88
|
+
*/
|
|
89
|
+
export declare function computeAgentRunReady(agentId: AgentId): Promise<ViewJsonRunReady | undefined>;
|
|
79
90
|
/**
|
|
80
91
|
* Collect structured info for one or more agents without rendering to the
|
|
81
92
|
* terminal. Used by `--json` output and any programmatic consumer (e.g. the
|
package/dist/commands/view.js
CHANGED
|
@@ -39,7 +39,7 @@ import { selectUpdateStrategy } from '../lib/installations/strategies.js';
|
|
|
39
39
|
import { isGitRepo, getGitSyncStatus } from '../lib/git.js';
|
|
40
40
|
import { getCentralRulesFileName } from '../lib/rules/rules.js';
|
|
41
41
|
import { composeRulesFromState } from '../lib/rules/compose.js';
|
|
42
|
-
import { getConfiguredRunStrategy, isLaunchableSignedIn } from '../lib/accounting/rotate.js';
|
|
42
|
+
import { getConfiguredRunStrategy, isLaunchableSignedIn, collectRunCandidates, readinessFromCandidate } from '../lib/accounting/rotate.js';
|
|
43
43
|
import { resolveRunDefaults } from '../lib/run-defaults.js';
|
|
44
44
|
import { resolveConfiguredModel } from '../lib/models.js';
|
|
45
45
|
import { listProfiles, profileExists, profileSummary, readProfile } from '../lib/profiles.js';
|
|
@@ -1272,6 +1272,36 @@ export function parseResourceSections(options, jsonMode) {
|
|
|
1272
1272
|
}
|
|
1273
1273
|
return set;
|
|
1274
1274
|
}
|
|
1275
|
+
/**
|
|
1276
|
+
* The one run-readiness gate for one agent on THIS box (PHNX-4116). Runs the
|
|
1277
|
+
* SAME enumeration the local router picks from — `collectRunCandidates` (native
|
|
1278
|
+
* slots + version homes) through `readinessFromCandidate` — so the answer
|
|
1279
|
+
* `agents view --json` publishes is exactly what a `run` here would find, and a
|
|
1280
|
+
* remote `--device auto` dispatcher can read it instead of re-deriving freshness
|
|
1281
|
+
* on its own box (which drifted: view.ts lists version homes, the router lists
|
|
1282
|
+
* slots). Returns undefined when the agent has no candidates at all (nothing
|
|
1283
|
+
* installed), so the field is simply absent rather than a misleading verdict.
|
|
1284
|
+
*/
|
|
1285
|
+
export async function computeAgentRunReady(agentId) {
|
|
1286
|
+
const candidates = await collectRunCandidates(agentId);
|
|
1287
|
+
if (candidates.length === 0)
|
|
1288
|
+
return undefined;
|
|
1289
|
+
const accounts = candidates.map((c) => {
|
|
1290
|
+
const readiness = readinessFromCandidate(c);
|
|
1291
|
+
const name = c.nativeAccount || c.accountLabel || c.email || c.version;
|
|
1292
|
+
return { name, ready: readiness.ready, reason: readiness.ready ? 'ready' : readiness.reason };
|
|
1293
|
+
});
|
|
1294
|
+
const readyAccount = accounts.find((a) => a.ready);
|
|
1295
|
+
let reason;
|
|
1296
|
+
if (readyAccount) {
|
|
1297
|
+
reason = `ready (${readyAccount.name})`;
|
|
1298
|
+
}
|
|
1299
|
+
else {
|
|
1300
|
+
const reasons = [...new Set(accounts.map((a) => a.reason))];
|
|
1301
|
+
reason = reasons.length === 1 ? `all ${reasons[0]}` : reasons.join(', ');
|
|
1302
|
+
}
|
|
1303
|
+
return { ready: !!readyAccount, reason, accounts };
|
|
1304
|
+
}
|
|
1275
1305
|
/**
|
|
1276
1306
|
* Collect structured info for one or more agents without rendering to the
|
|
1277
1307
|
* terminal. Used by `--json` output and any programmatic consumer (e.g. the
|
|
@@ -1395,6 +1425,15 @@ export async function collectAgentsJson(filterAgentId, resourceSections, opts) {
|
|
|
1395
1425
|
const note = secretsUnavailableNote(catalog);
|
|
1396
1426
|
if (note)
|
|
1397
1427
|
console.error(chalk.yellow(note)); // stderr — never corrupts --json stdout
|
|
1428
|
+
// The one run-readiness gate per agent, computed off the router's own
|
|
1429
|
+
// enumeration (PHNX-4116). Runs in parallel across agents; a harness with
|
|
1430
|
+
// nothing installed yields undefined and the field is omitted.
|
|
1431
|
+
const runReadyByAgent = new Map();
|
|
1432
|
+
await Promise.all(agentsToShow.map(async (agentId) => {
|
|
1433
|
+
const runReady = await computeAgentRunReady(agentId);
|
|
1434
|
+
if (runReady)
|
|
1435
|
+
runReadyByAgent.set(agentId, runReady);
|
|
1436
|
+
}));
|
|
1398
1437
|
const out = [];
|
|
1399
1438
|
for (const agentId of agentsToShow) {
|
|
1400
1439
|
const versions = byAgent.get(agentId) ?? [];
|
|
@@ -1406,7 +1445,13 @@ export async function collectAgentsJson(filterAgentId, resourceSections, opts) {
|
|
|
1406
1445
|
// Project through the public JSON v2 serializer — the internal catalog row
|
|
1407
1446
|
// (identityKey, home, installations) is not the machine contract.
|
|
1408
1447
|
const accounts = accountListJson(catalog.native.filter((row) => row.agent === agentId), catalog.provider.filter((row) => row.harnesses.includes(agentId)), agentId).accounts;
|
|
1409
|
-
out.push({
|
|
1448
|
+
out.push({
|
|
1449
|
+
agent: agentId,
|
|
1450
|
+
versions,
|
|
1451
|
+
accounts,
|
|
1452
|
+
runReady: runReadyByAgent.get(agentId),
|
|
1453
|
+
harnesses: harnesses.filter((h) => h.agent === agentId),
|
|
1454
|
+
});
|
|
1410
1455
|
}
|
|
1411
1456
|
return out;
|
|
1412
1457
|
}
|
package/dist/index.js
CHANGED
|
@@ -78,10 +78,12 @@ if (process.argv[2] === '__claude-statusline') {
|
|
|
78
78
|
const { runClaudeStatusLine } = await import('./lib/claude-statusline.js');
|
|
79
79
|
process.exit(await runClaudeStatusLine());
|
|
80
80
|
}
|
|
81
|
-
// Fleet usage-sync receiver: a headed peer pipes its
|
|
82
|
-
// stdin
|
|
83
|
-
//
|
|
84
|
-
//
|
|
81
|
+
// Fleet usage-sync receiver: a headed peer pipes its daemon-state envelope to
|
|
82
|
+
// stdin; we store it as that peer's file, merge its usage rows newest-wins into
|
|
83
|
+
// this box's cache, and with `--reply` print our own envelope back (PHNX-3392,
|
|
84
|
+
// PHNX-4116). Above the bootstrap line for the same reason as
|
|
85
|
+
// __claude-statusline — no update check, no detached sync fork, and without
|
|
86
|
+
// `--reply` nothing writes to stdout to corrupt the ready probe's view.
|
|
85
87
|
if (process.argv[2] === '__usage-ingest') {
|
|
86
88
|
const { runUsageIngest } = await import('./lib/accounting/usage-ingest.js');
|
|
87
89
|
process.exit(await runUsageIngest());
|
|
@@ -110,9 +112,10 @@ if (process.argv[2] === '__daemon-run') {
|
|
|
110
112
|
// stack to whatever the service manager had wired to stdout and never reaching
|
|
111
113
|
// the daemon's own structured log. Route both into log() so the failure is in
|
|
112
114
|
// logs.jsonl where `agents daemon logs` reads it, then exit non-zero and
|
|
113
|
-
// DELIBERATELY let the supervisor restart us —
|
|
114
|
-
// ThrottleInterval / the unit's
|
|
115
|
-
//
|
|
115
|
+
// DELIBERATELY let the supervisor restart us — paced by the plist's
|
|
116
|
+
// ThrottleInterval / the unit's RestartSec, and never abandoned
|
|
117
|
+
// (StartLimitIntervalSec=0, PHNX-4116). Swallowing here would be the worse
|
|
118
|
+
// failure: a daemon left alive with a dead subsystem.
|
|
116
119
|
const crash = (kind) => (err) => {
|
|
117
120
|
const detail = err instanceof Error ? (err.stack ?? err.message) : String(err);
|
|
118
121
|
try {
|
|
@@ -137,28 +140,102 @@ if (process.argv[2] === '__daemon-run') {
|
|
|
137
140
|
// Lifecycle verbs (resume/stop/inject/watch/--active/--markdown) fall through.
|
|
138
141
|
if (process.argv[2] === 'sessions') {
|
|
139
142
|
const forwarded = process.argv.slice(3);
|
|
140
|
-
const { isReadQuery, resolveSessionsBin, invocation, SessionsClientError, SESSIONS_INSTALL_HINT, } = await import('./lib/sessions-client.js');
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
143
|
+
const { isReadQuery, usesFilterFlags, usesHostFlag, sessionsBinSupportsFilters, sessionsBinSupportsHost, planDeviceHostRead, resolveSessionsBin, invocation, SessionsClientError, SESSIONS_INSTALL_HINT, } = await import('./lib/sessions-client.js');
|
|
144
|
+
// Resolve the bin up front (a cheap PATH lookup): its presence decides whether
|
|
145
|
+
// we can route at all, and its VERSION decides whether the 0.2.0 filter flags
|
|
146
|
+
// (`--project`/`--since`/`--until`/`--sort`, `@version`, the shorthands) and the
|
|
147
|
+
// 0.2.1 `--host <target>` point-to-one remote read flag are recognized — an
|
|
148
|
+
// older `sessions` would mis-read them as FTS tokens, so those queries stay on
|
|
149
|
+
// the in-repo engine (which implements the same filters and the `--device`
|
|
150
|
+
// fan-out). The two floors are checked independently: a 0.2.0 binary takes the
|
|
151
|
+
// filters but not `--host`.
|
|
152
|
+
let bin = null;
|
|
153
|
+
try {
|
|
154
|
+
bin = resolveSessionsBin();
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
// No standalone on this box: fall through to the in-repo engine below rather
|
|
158
|
+
// than refusing the query. The fast path is an optimization for a box that
|
|
159
|
+
// has `sessions` installed (skips bootstrap and the sessions module); a box
|
|
160
|
+
// without it must still answer `agents sessions --json` exactly as before.
|
|
161
|
+
if (!(err instanceof SessionsClientError && err.code === 'SESSIONS_BIN_MISSING'))
|
|
162
|
+
throw err;
|
|
163
|
+
}
|
|
164
|
+
const filters = bin !== null && usesFilterFlags(forwarded) ? sessionsBinSupportsFilters(bin) : false;
|
|
165
|
+
const host = bin !== null && usesHostFlag(forwarded) ? sessionsBinSupportsHost(bin) : false;
|
|
166
|
+
// PHNX-4012 local-orchestration collapse: a read query carrying `--device
|
|
167
|
+
// <name>` (and no explicit `--host`) routes through the standalone's
|
|
168
|
+
// point-to-one remote read — `sessions <read args> --host ssh://<resolved>` —
|
|
169
|
+
// instead of the in-repo `--device` peer fan-out, WHEN this box's standalone
|
|
170
|
+
// supports `--host` (>=0.2.1). `planDeviceHostRead` is pure and probes nothing,
|
|
171
|
+
// so a non-device read never pays a `sessions --version` spawn (the probe fires
|
|
172
|
+
// only after a device read is detected). If the PEER lacks the standalone the
|
|
173
|
+
// ssh `bash -lc` command-not-found surfaces as exit 127 — a CAPABILITY gate
|
|
174
|
+
// (same spirit as #3687's version gate, keyed on a specific documented signal),
|
|
175
|
+
// on which we FALL THROUGH to the in-repo `--device` fan-out below so the read
|
|
176
|
+
// still succeeds. Everything else (e.g. 255 unreachable) propagates verbatim.
|
|
177
|
+
// The in-repo fan-out is deliberately KEPT here as the path for un-upgraded
|
|
178
|
+
// peers; its eventual removal is a follow-up gated on fleet-wide 0.2.1 (a
|
|
179
|
+
// separate PHNX-4012 follow-up, see the PR body), not this PR.
|
|
180
|
+
if (bin !== null) {
|
|
181
|
+
const deviceRead = planDeviceHostRead(forwarded, { filters });
|
|
182
|
+
if (deviceRead && sessionsBinSupportsHost(bin)) {
|
|
183
|
+
let target = null;
|
|
184
|
+
try {
|
|
185
|
+
const { resolveRemoteDevice } = await import('./lib/ssh-tunnel.js');
|
|
186
|
+
target = (await resolveRemoteDevice(deviceRead.device, {})).target;
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
// Unknown/unresolvable device — fall through to the in-repo path, which
|
|
190
|
+
// resolves `--device` against the same fleet registry and emits the
|
|
191
|
+
// canonical error; do not duplicate that error here.
|
|
192
|
+
target = null;
|
|
193
|
+
}
|
|
194
|
+
if (target !== null) {
|
|
195
|
+
const { spawnSync } = await import('node:child_process');
|
|
196
|
+
const { command, prefix } = invocation(bin);
|
|
197
|
+
const hostArgs = [...deviceRead.readArgs, '--host', `ssh://${target}`];
|
|
198
|
+
// Capture (not `stdio: 'inherit'`) so a 127 capability miss can fall
|
|
199
|
+
// through silently instead of leaking the peer's command-not-found before
|
|
200
|
+
// the in-repo read re-answers. Reads are bounded, so buffering is fine;
|
|
201
|
+
// force color + geometry through the capture when the caller is at a real
|
|
202
|
+
// terminal and not asking for `--json`, matching the passthrough renderer.
|
|
203
|
+
const childEnv = { ...process.env };
|
|
204
|
+
if (process.stdout.isTTY && !hostArgs.includes('--json')) {
|
|
205
|
+
if (childEnv.FORCE_COLOR === undefined)
|
|
206
|
+
childEnv.FORCE_COLOR = '1';
|
|
207
|
+
if (process.stdout.columns)
|
|
208
|
+
childEnv.COLUMNS = String(process.stdout.columns);
|
|
209
|
+
if (process.stdout.rows)
|
|
210
|
+
childEnv.LINES = String(process.stdout.rows);
|
|
211
|
+
}
|
|
212
|
+
const res = spawnSync(command, [...prefix, ...hostArgs], {
|
|
213
|
+
encoding: 'utf8',
|
|
214
|
+
env: childEnv,
|
|
215
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
216
|
+
});
|
|
217
|
+
if (res.error) {
|
|
218
|
+
process.stderr.write(`Failed to run \`sessions\`: ${res.error.message}\n`);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
// Exit 127 = the peer has no standalone `sessions` on PATH (ssh/`bash
|
|
222
|
+
// -lc` command-not-found). Fall through to the in-repo fan-out rather
|
|
223
|
+
// than surfacing a broken read.
|
|
224
|
+
if (res.status !== 127) {
|
|
225
|
+
if (res.stdout)
|
|
226
|
+
process.stdout.write(res.stdout);
|
|
227
|
+
if (res.stderr)
|
|
228
|
+
process.stderr.write(res.stderr);
|
|
229
|
+
process.exit(res.status ?? 1);
|
|
230
|
+
}
|
|
231
|
+
// 127: fall through. `forwarded` still carries `--device`, so the
|
|
232
|
+
// `isReadQuery` check below is false and bootstrap's in-repo path answers.
|
|
159
233
|
}
|
|
160
234
|
}
|
|
235
|
+
}
|
|
236
|
+
if (isReadQuery(forwarded, { filters, host })) {
|
|
161
237
|
if (bin) {
|
|
238
|
+
const { spawnSync } = await import('node:child_process');
|
|
162
239
|
const { command, prefix } = invocation(bin);
|
|
163
240
|
const res = spawnSync(command, [...prefix, ...forwarded], { stdio: 'inherit' });
|
|
164
241
|
if (res.error) {
|
|
@@ -167,6 +244,10 @@ if (process.argv[2] === 'sessions') {
|
|
|
167
244
|
}
|
|
168
245
|
process.exit(res.status ?? 1);
|
|
169
246
|
}
|
|
247
|
+
// A read query on a box with no standalone: hint once, then fall through.
|
|
248
|
+
if (process.env.AGENTS_SESSIONS_FASTPATH_HINT !== '0') {
|
|
249
|
+
process.stderr.write(`agents: standalone \`sessions\` not installed; using the in-process engine (${SESSIONS_INSTALL_HINT})\n`);
|
|
250
|
+
}
|
|
170
251
|
}
|
|
171
252
|
}
|
|
172
253
|
// Full CLI: commander tree, update checks, migrations, parse. Static imports
|
|
@@ -30,8 +30,6 @@ export const NATIVE_ACCOUNT_CAPABILITIES = {
|
|
|
30
30
|
antigravity: { inspection: 'opaque', scope: 'device', status: 'unsupported' },
|
|
31
31
|
droid: { inspection: 'opaque', scope: 'device', status: 'unsupported' },
|
|
32
32
|
opencode: { inspection: 'opaque', scope: 'device', status: 'unsupported' },
|
|
33
|
-
// Discoverable in the list, but not nameable.
|
|
34
|
-
gemini: { inspection: 'email', scope: 'unsupported', status: 'discovery-only' },
|
|
35
33
|
copilot: { inspection: 'none', scope: 'unsupported', status: 'unsupported' },
|
|
36
34
|
openclaw: { inspection: 'none', scope: 'unsupported', status: 'unsupported' },
|
|
37
35
|
amp: { inspection: 'none', scope: 'unsupported', status: 'unsupported' },
|