@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
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
* it would claim a distribution responsibility agents-cli no longer has.
|
|
42
42
|
*/
|
|
43
43
|
export const HELPER_RELEASES = {
|
|
44
|
-
menubar: { tagPrefix: 'menubar', floor: '1.
|
|
44
|
+
menubar: { tagPrefix: 'menubar', floor: '1.6.2' },
|
|
45
45
|
};
|
|
46
46
|
/** The release tag for one helper at one version, e.g. `menubar/v1.0.0`. */
|
|
47
47
|
export function helperTag(helper, version) {
|
|
@@ -45,6 +45,13 @@ export declare function generateHookShim(args: {
|
|
|
45
45
|
cache?: HookCacheConfig | null;
|
|
46
46
|
matches?: HookMatches;
|
|
47
47
|
paths?: HookShimPaths;
|
|
48
|
+
/**
|
|
49
|
+
* Deny the tool call (exit 2) when the source script is missing instead of
|
|
50
|
+
* failing with 127, which every harness reads as "allow". Set for
|
|
51
|
+
* PreToolUse hooks: a guard whose script vanished (version pruned, sync
|
|
52
|
+
* rewriting the hooks dir) must not silently wave tool calls through.
|
|
53
|
+
*/
|
|
54
|
+
failClosed?: boolean;
|
|
48
55
|
}): string;
|
|
49
56
|
/**
|
|
50
57
|
* Remove a hook's shim. Called by the registrar's garbage collection when a
|
package/dist/lib/hooks/cache.js
CHANGED
|
@@ -119,7 +119,7 @@ export function generateHookShim(args) {
|
|
|
119
119
|
const logsDir = args.paths?.logsDir ?? getLogsDir();
|
|
120
120
|
const perfDir = args.paths?.perfDir ?? getPerfDir();
|
|
121
121
|
const shimPath = resolveContainedHookShimPath(shimsDir, args.name);
|
|
122
|
-
const content = renderShim(args.name, args.scriptPath, args.cache ?? null, args.matches, { cacheDir, logsDir, perfDir });
|
|
122
|
+
const content = renderShim(args.name, args.scriptPath, args.cache ?? null, args.matches, { cacheDir, logsDir, perfDir }, args.failClosed === true);
|
|
123
123
|
fs.mkdirSync(shimsDir, { recursive: true });
|
|
124
124
|
let existing = null;
|
|
125
125
|
if (fs.existsSync(shimPath)) {
|
|
@@ -363,7 +363,7 @@ exit "$EXIT"`;
|
|
|
363
363
|
* a gate-only pass-through: it forwards stdin to the script and its stdout back,
|
|
364
364
|
* with no cache read/write.
|
|
365
365
|
*/
|
|
366
|
-
function renderShim(name, scriptPath, cache, matches, paths) {
|
|
366
|
+
function renderShim(name, scriptPath, cache, matches, paths, failClosed = false) {
|
|
367
367
|
const ttl = cache ? (typeof cache.ttl === 'number' ? cache.ttl : (parseDuration(cache.ttl) ?? 0)) : 0;
|
|
368
368
|
const key = cache?.key ?? 'global';
|
|
369
369
|
const prefetch = cache?.prefetch ?? 'none';
|
|
@@ -394,6 +394,7 @@ TTL=${ttl}
|
|
|
394
394
|
PREFETCH=${q(prefetch)}
|
|
395
395
|
KEY_MODE=${q(key)}
|
|
396
396
|
MATCHES_JSON=${q(matchesJson)}
|
|
397
|
+
FAIL_CLOSED=${failClosed ? 1 : 0}
|
|
397
398
|
|
|
398
399
|
mkdir -p "$CACHE_DIR" "$LOGS_DIR" "$PERF_DIR"
|
|
399
400
|
|
|
@@ -458,6 +459,30 @@ if [ -n "$MATCHES_JSON" ]; then
|
|
|
458
459
|
exit 0
|
|
459
460
|
fi
|
|
460
461
|
fi
|
|
462
|
+
# A source that is not on disk cannot be executed: bash exits 127, and the
|
|
463
|
+
# harnesses that share the exit-2 deny contract (Claude Code, Codex, Droid)
|
|
464
|
+
# read anything else as "allow" -- a guard silently disabled. Deny instead and
|
|
465
|
+
# name the repair, but only for a PreToolUse firing that the matches: gate
|
|
466
|
+
# above let through: one shim serves every event a hook declares, so a
|
|
467
|
+
# SessionStart or Stop leg of the same hook must not wedge a session, and a
|
|
468
|
+
# fire the predicates would have skipped must not turn into a denial. Other
|
|
469
|
+
# shims keep their existing path (a cached hook still serves its cache).
|
|
470
|
+
if [ "$FAIL_CLOSED" = 1 ] && [ ! -f "$SOURCE" ]; then
|
|
471
|
+
_EVT="$(printf '%s' "$STDIN_PAYLOAD" | "$PY" -c 'import json, sys
|
|
472
|
+
try:
|
|
473
|
+
d = json.load(sys.stdin)
|
|
474
|
+
except Exception:
|
|
475
|
+
d = {}
|
|
476
|
+
print(d.get("hook_event_name") or d.get("hookEventName") or "")' 2>/dev/null || true)"
|
|
477
|
+
if [ -z "$_EVT" ] || [ "$_EVT" = PreToolUse ]; then
|
|
478
|
+
_TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
479
|
+
printf '{"ts":"%s","event":"hook.fire","hook":"%s","ms":0,"cache":"missing-source","exit":2}\\n' \\
|
|
480
|
+
"$_TS" "$HOOK_NAME" >>"$LOGS_DIR/events-$(date -u +%Y-%m-%d).jsonl" 2>/dev/null || true
|
|
481
|
+
printf '%s: hook source is missing (%s); refusing the tool call unchecked (fail-closed). Run: agents hooks sync\\n' \\
|
|
482
|
+
"$HOOK_NAME" "$SOURCE" >&2
|
|
483
|
+
exit 2
|
|
484
|
+
fi
|
|
485
|
+
fi
|
|
461
486
|
${cache ? CACHE_TAIL : PASSTHROUGH_TAIL}
|
|
462
487
|
`;
|
|
463
488
|
}
|
|
@@ -363,7 +363,13 @@ function resolveHookCommand(name, hookDef, resolveScript) {
|
|
|
363
363
|
// script when predicates don't hold) and, when `cache:` is set, layers the
|
|
364
364
|
// cache/timing machinery on top; with neither, it is a pure pass-through
|
|
365
365
|
// timing wrapper.
|
|
366
|
-
return toPortableCommand(generateHookShim({
|
|
366
|
+
return toPortableCommand(generateHookShim({
|
|
367
|
+
name,
|
|
368
|
+
scriptPath,
|
|
369
|
+
cache,
|
|
370
|
+
matches,
|
|
371
|
+
failClosed: hookDef.events.includes('PreToolUse'),
|
|
372
|
+
}));
|
|
367
373
|
}
|
|
368
374
|
/**
|
|
369
375
|
* Extensions that are NEVER hooks — docs, configuration, plain data. A file
|
|
@@ -57,8 +57,19 @@ export interface ReadyProbe {
|
|
|
57
57
|
* `$LASTEXITCODE` (no `printf`/`||`); `parseReadyProbe` keys off the sentinel
|
|
58
58
|
* substring, so the missing leading newline vs the POSIX `printf '\n…'` form is
|
|
59
59
|
* absorbed by its `.trim()`. Pure/exported so both branches are unit-testable.
|
|
60
|
+
*
|
|
61
|
+
* With `ingestUsage`, the probe first runs `agents __usage-ingest` on the
|
|
62
|
+
* dispatcher's daemon-state envelope, which the caller pipes to stdin
|
|
63
|
+
* (PHNX-4116): the chosen worker then holds the dispatcher's current usage
|
|
64
|
+
* numbers at dispatch instead of waiting for the next 15-minute usage-sync
|
|
65
|
+
* tick. The ingest writes nothing to stdout (only `--reply` does), so the
|
|
66
|
+
* version/sentinel/listing shape `parseReadyProbe` reads is unchanged. On
|
|
67
|
+
* Windows the `agents.ps1` shim drops ssh-piped stdin, so PowerShell reads it
|
|
68
|
+
* into a temp file and hands the verb `--from <path>`.
|
|
60
69
|
*/
|
|
61
|
-
export declare function buildReadyProbeCommand(os?: string
|
|
70
|
+
export declare function buildReadyProbeCommand(os?: string, opts?: {
|
|
71
|
+
ingestUsage?: boolean;
|
|
72
|
+
}): string;
|
|
62
73
|
export declare function readyProbe(target: string, os?: string, extraSshArgs?: string[]): ReadyProbe;
|
|
63
74
|
/** Pure parser for `readyProbe` output (unit-tested without ssh). */
|
|
64
75
|
export declare function parseReadyProbe(stdout: string): ReadyProbe;
|
|
@@ -92,6 +103,12 @@ interface ViewAgentAccountEligibility {
|
|
|
92
103
|
signedIn: boolean | undefined;
|
|
93
104
|
/** At least one account can launch immediately or enter the harness login flow. */
|
|
94
105
|
pickerEligible: boolean | undefined;
|
|
106
|
+
/**
|
|
107
|
+
* The remote box's own aggregate exclusion reason when it is not signed in
|
|
108
|
+
* (`runReady.reason` — e.g. `all signed_out`), for the operator-facing
|
|
109
|
+
* placement error. Undefined when ready, absent, or on an older remote CLI.
|
|
110
|
+
*/
|
|
111
|
+
reason?: string;
|
|
95
112
|
}
|
|
96
113
|
/**
|
|
97
114
|
* Read the two account gates automatic placement needs from `agents view
|
|
@@ -99,13 +116,26 @@ interface ViewAgentAccountEligibility {
|
|
|
99
116
|
* the login flow, but it must not route to a device whose every signed-in
|
|
100
117
|
* account is throttled.
|
|
101
118
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
119
|
+
* ONE readiness gate, computed on the box that runs (PHNX-4116). The remote box
|
|
120
|
+
* publishes `runReady` — the router's own `collectRunCandidates` →
|
|
121
|
+
* `readinessFromCandidate` verdict over its native slots AND version homes — so
|
|
122
|
+
* this reads that answer rather than re-deriving freshness here. The
|
|
123
|
+
* dispatching box re-deriving from the per-version `versions[]` list was the bug:
|
|
124
|
+
* that list enumerates version homes, misses the account slots a run picks from,
|
|
125
|
+
* and applied a 40-minute usage-freshness refusal a synced-only worker could
|
|
126
|
+
* never satisfy, turning a usage-sync lag into "no ready device" while
|
|
127
|
+
* `--device <name>` on the same box launched fine.
|
|
128
|
+
*
|
|
129
|
+
* An older remote CLI omits `runReady`; the one-release fallback derives the
|
|
130
|
+
* verdict from the per-version list the same way the pre-PHNX-4116 dispatcher
|
|
131
|
+
* did: the strict `launchable` signal (`isLaunchableSignedIn`, PHNX-3466) for
|
|
132
|
+
* sign-in, MINUS a FRESH throttle (`rate_limited`/`out_of_credits` captured
|
|
133
|
+
* within {@link USAGE_STALE_REFUSAL_MAX_AGE_MS}) or a FRESH dead auth verdict
|
|
134
|
+
* (checked within {@link AUTH_PROBE_MAX_AGE_MS}, {@link isDeadVerdict}). A stale
|
|
135
|
+
* reading is UNVERIFIED, not disqualifying (PHNX-4116/#3700), so a usage-sync
|
|
136
|
+
* lag never bars an old-CLI worker `--device <name>` would launch fine — but a
|
|
137
|
+
* throttled-but-launchable worker no longer reads `signedIn: true` and slips
|
|
138
|
+
* into `--device auto`'s pick during a rolling upgrade.
|
|
109
139
|
*/
|
|
110
140
|
export declare function viewAgentAccountEligibility(view: string, agent: string, now?: number): ViewAgentAccountEligibility;
|
|
111
141
|
export declare function viewAgentSignedIn(view: string, agent: string): boolean | undefined;
|
package/dist/lib/hosts/ready.js
CHANGED
|
@@ -102,15 +102,31 @@ const READY_MARKER = '@@AGENTS_READY@@';
|
|
|
102
102
|
* `$LASTEXITCODE` (no `printf`/`||`); `parseReadyProbe` keys off the sentinel
|
|
103
103
|
* substring, so the missing leading newline vs the POSIX `printf '\n…'` form is
|
|
104
104
|
* absorbed by its `.trim()`. Pure/exported so both branches are unit-testable.
|
|
105
|
+
*
|
|
106
|
+
* With `ingestUsage`, the probe first runs `agents __usage-ingest` on the
|
|
107
|
+
* dispatcher's daemon-state envelope, which the caller pipes to stdin
|
|
108
|
+
* (PHNX-4116): the chosen worker then holds the dispatcher's current usage
|
|
109
|
+
* numbers at dispatch instead of waiting for the next 15-minute usage-sync
|
|
110
|
+
* tick. The ingest writes nothing to stdout (only `--reply` does), so the
|
|
111
|
+
* version/sentinel/listing shape `parseReadyProbe` reads is unchanged. On
|
|
112
|
+
* Windows the `agents.ps1` shim drops ssh-piped stdin, so PowerShell reads it
|
|
113
|
+
* into a temp file and hands the verb `--from <path>`.
|
|
105
114
|
*/
|
|
106
|
-
export function buildReadyProbeCommand(os) {
|
|
115
|
+
export function buildReadyProbeCommand(os, opts = {}) {
|
|
107
116
|
if (remoteShellFor(os) === 'powershell') {
|
|
108
|
-
const
|
|
117
|
+
const ingest = opts.ingestUsage
|
|
118
|
+
? '$in = [Console]::In.ReadToEnd(); $tmp = $null; ' +
|
|
119
|
+
'try { $tmp = [System.IO.Path]::GetTempFileName(); [System.IO.File]::WriteAllText($tmp, $in); ' +
|
|
120
|
+
'agents __usage-ingest --from $tmp 2>$null } ' +
|
|
121
|
+
'finally { if ($tmp) { Remove-Item -LiteralPath $tmp -Force -ErrorAction SilentlyContinue } }; '
|
|
122
|
+
: '';
|
|
123
|
+
const script = `${POWERSHELL_PROGRESS_SILENCE}; ${ingest}` +
|
|
109
124
|
`agents --version 2>$null; Write-Output "${READY_MARKER}"; ` +
|
|
110
125
|
`agents view --json 2>$null; if ($LASTEXITCODE -ne 0) { agents list 2>$null }`;
|
|
111
126
|
return `powershell -NoProfile -EncodedCommand ${encodePowershell(script)}`;
|
|
112
127
|
}
|
|
113
|
-
const script =
|
|
128
|
+
const script = `${opts.ingestUsage ? 'agents __usage-ingest 2>/dev/null; ' : ''}` +
|
|
129
|
+
`agents --version 2>/dev/null; printf '\\n${READY_MARKER}\\n'; ` +
|
|
114
130
|
`agents view --json 2>/dev/null || agents list 2>/dev/null`;
|
|
115
131
|
return `bash -lc ${shellQuote(script)}`;
|
|
116
132
|
}
|
|
@@ -207,13 +223,26 @@ export function missingPinnedVersionMessage(hostName, agent, version, installed)
|
|
|
207
223
|
* the login flow, but it must not route to a device whose every signed-in
|
|
208
224
|
* account is throttled.
|
|
209
225
|
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
226
|
+
* ONE readiness gate, computed on the box that runs (PHNX-4116). The remote box
|
|
227
|
+
* publishes `runReady` — the router's own `collectRunCandidates` →
|
|
228
|
+
* `readinessFromCandidate` verdict over its native slots AND version homes — so
|
|
229
|
+
* this reads that answer rather than re-deriving freshness here. The
|
|
230
|
+
* dispatching box re-deriving from the per-version `versions[]` list was the bug:
|
|
231
|
+
* that list enumerates version homes, misses the account slots a run picks from,
|
|
232
|
+
* and applied a 40-minute usage-freshness refusal a synced-only worker could
|
|
233
|
+
* never satisfy, turning a usage-sync lag into "no ready device" while
|
|
234
|
+
* `--device <name>` on the same box launched fine.
|
|
235
|
+
*
|
|
236
|
+
* An older remote CLI omits `runReady`; the one-release fallback derives the
|
|
237
|
+
* verdict from the per-version list the same way the pre-PHNX-4116 dispatcher
|
|
238
|
+
* did: the strict `launchable` signal (`isLaunchableSignedIn`, PHNX-3466) for
|
|
239
|
+
* sign-in, MINUS a FRESH throttle (`rate_limited`/`out_of_credits` captured
|
|
240
|
+
* within {@link USAGE_STALE_REFUSAL_MAX_AGE_MS}) or a FRESH dead auth verdict
|
|
241
|
+
* (checked within {@link AUTH_PROBE_MAX_AGE_MS}, {@link isDeadVerdict}). A stale
|
|
242
|
+
* reading is UNVERIFIED, not disqualifying (PHNX-4116/#3700), so a usage-sync
|
|
243
|
+
* lag never bars an old-CLI worker `--device <name>` would launch fine — but a
|
|
244
|
+
* throttled-but-launchable worker no longer reads `signedIn: true` and slips
|
|
245
|
+
* into `--device auto`'s pick during a rolling upgrade.
|
|
217
246
|
*/
|
|
218
247
|
export function viewAgentAccountEligibility(view, agent, now = Date.now()) {
|
|
219
248
|
try {
|
|
@@ -221,11 +250,25 @@ export function viewAgentAccountEligibility(view, agent, now = Date.now()) {
|
|
|
221
250
|
const row = rows.find((candidate) => candidate.agent?.toLowerCase() === agent.toLowerCase());
|
|
222
251
|
if (!row)
|
|
223
252
|
return { signedIn: undefined, pickerEligible: undefined };
|
|
253
|
+
const runReady = row.runReady;
|
|
254
|
+
if (runReady && typeof runReady.ready === 'boolean') {
|
|
255
|
+
// A signed-out / revoked account is picker-eligible even when not ready —
|
|
256
|
+
// launching it IS the login flow. A throttled account is not.
|
|
257
|
+
const accounts = runReady.accounts ?? [];
|
|
258
|
+
const pickerEligible = runReady.ready
|
|
259
|
+
|| accounts.some((a) => a.reason === 'signed_out' || a.reason === 'revoked');
|
|
260
|
+
return {
|
|
261
|
+
signedIn: runReady.ready,
|
|
262
|
+
pickerEligible,
|
|
263
|
+
reason: runReady.ready ? undefined : runReady.reason,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
// One-release fallback for an older remote CLI without `runReady`.
|
|
224
267
|
const verdicts = (row.versions ?? []).flatMap((version) => {
|
|
225
268
|
if (typeof version.signedIn !== 'boolean')
|
|
226
269
|
return [];
|
|
227
270
|
// Prefer the strict per-version launch signal; fall back to the display
|
|
228
|
-
// `signedIn` for
|
|
271
|
+
// `signedIn` for a CLI old enough to omit `launchable` too.
|
|
229
272
|
const launchable = typeof version.launchable === 'boolean' ? version.launchable : version.signedIn;
|
|
230
273
|
const usageCapturedAt = version.usageCapturedAt ? Date.parse(version.usageCapturedAt) : Number.NaN;
|
|
231
274
|
const usageFresh = Number.isFinite(usageCapturedAt)
|
|
@@ -240,9 +283,14 @@ export function viewAgentAccountEligibility(view, agent, now = Date.now()) {
|
|
|
240
283
|
&& version.authVerdict !== undefined
|
|
241
284
|
&& authFresh
|
|
242
285
|
&& isDeadVerdict(version.authVerdict);
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
286
|
+
// A stale usage/auth reading makes the account UNVERIFIED, not unusable:
|
|
287
|
+
// the number is ignored (`throttled`/`authBlocked` only trust a fresh one)
|
|
288
|
+
// and the remote `agents run` weights an unverified account at the floor.
|
|
289
|
+
// Refusing the whole device on a stale reading turned a fleet-wide
|
|
290
|
+
// usage-sync lag into "no healthy device can run claude" while
|
|
291
|
+
// `--device <name>` on the same box launched fine (PHNX-4116/#3700). Only a
|
|
292
|
+
// FRESH throttle or a FRESH dead auth verdict disqualifies.
|
|
293
|
+
const ready = launchable && !authBlocked && !throttled;
|
|
246
294
|
return [{ ready, pickerEligible: ready || !launchable || authBlocked }];
|
|
247
295
|
});
|
|
248
296
|
if (verdicts.length === 0)
|
|
@@ -25,6 +25,14 @@ import type { Installation } from './types.js';
|
|
|
25
25
|
*/
|
|
26
26
|
export interface ProcessSnapshot {
|
|
27
27
|
listCommandLines(): Promise<string[]>;
|
|
28
|
+
/** Optional richer listing (pid, elapsed, tty) for naming a blocking process. */
|
|
29
|
+
listProcessRows?(): Promise<ProcessRow[]>;
|
|
30
|
+
}
|
|
31
|
+
export interface ProcessRow {
|
|
32
|
+
pid?: number;
|
|
33
|
+
elapsed?: string;
|
|
34
|
+
tty?: string;
|
|
35
|
+
args: string;
|
|
28
36
|
}
|
|
29
37
|
export declare const realProcessSnapshot: ProcessSnapshot;
|
|
30
38
|
/**
|
|
@@ -46,3 +54,22 @@ export declare function installationLooksActive(installation: Pick<Installation,
|
|
|
46
54
|
* update to a harness this check simply failed to observe.
|
|
47
55
|
*/
|
|
48
56
|
export declare function isInstallationLikelyActive(installation: Pick<Installation, 'agent' | 'label'>, snapshot?: ProcessSnapshot): Promise<boolean>;
|
|
57
|
+
export interface InstallationActivity {
|
|
58
|
+
active: boolean;
|
|
59
|
+
/** A launch lease is held (a launch is starting and may not be in the process table yet). */
|
|
60
|
+
lease: boolean;
|
|
61
|
+
/** Live processes naming this installation's directory, one line each. */
|
|
62
|
+
processes: string[];
|
|
63
|
+
/** The process scan failed; `active` is the fail-closed default. */
|
|
64
|
+
scanError?: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Same verdict as {@link isInstallationLikelyActive}, plus WHAT is holding the
|
|
68
|
+
* installation, so a refused update can name the session to finish instead of
|
|
69
|
+
* "in use; retry later" (PHNX-4116 follow-up: an operator on yosemite-s0 had a
|
|
70
|
+
* one-hour-old resumed session on another tty and no way to see it from the
|
|
71
|
+
* refusal).
|
|
72
|
+
*/
|
|
73
|
+
export declare function describeInstallationActivity(installation: Pick<Installation, 'agent' | 'label'>, snapshot?: ProcessSnapshot): Promise<InstallationActivity>;
|
|
74
|
+
/** The one-line refusal an update prints for a busy installation. */
|
|
75
|
+
export declare function formatInUseDeferral(name: string, activity: InstallationActivity): string;
|
|
@@ -22,6 +22,16 @@ import { promisify } from 'util';
|
|
|
22
22
|
import { getVersionDir } from './store.js';
|
|
23
23
|
import { hasLiveLaunchLease } from './shims.js';
|
|
24
24
|
const execFileAsync = promisify(execFile);
|
|
25
|
+
async function listProcessRowsPosix() {
|
|
26
|
+
const { stdout } = await execFileAsync('ps', ['-Ao', 'pid=,etime=,tty=,args='], {
|
|
27
|
+
timeout: 5_000,
|
|
28
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
29
|
+
});
|
|
30
|
+
return stdout.split('\n').flatMap((line) => {
|
|
31
|
+
const m = /^\s*(\d+)\s+(\S+)\s+(\S+)\s+(.*)$/.exec(line);
|
|
32
|
+
return m ? [{ pid: Number(m[1]), elapsed: m[2], tty: m[3], args: m[4] }] : [];
|
|
33
|
+
});
|
|
34
|
+
}
|
|
25
35
|
async function listCommandLinesPosix() {
|
|
26
36
|
try {
|
|
27
37
|
const { stdout } = await execFileAsync('ps', ['-Ao', 'args'], {
|
|
@@ -49,6 +59,9 @@ async function listCommandLinesWindows() {
|
|
|
49
59
|
}
|
|
50
60
|
export const realProcessSnapshot = {
|
|
51
61
|
listCommandLines: () => (process.platform === 'win32' ? listCommandLinesWindows() : listCommandLinesPosix()),
|
|
62
|
+
listProcessRows: async () => (process.platform === 'win32'
|
|
63
|
+
? (await listCommandLinesWindows()).map((args) => ({ args }))
|
|
64
|
+
: listProcessRowsPosix()),
|
|
52
65
|
};
|
|
53
66
|
/**
|
|
54
67
|
* Does any live process reference this installation's own directory? Matches
|
|
@@ -82,3 +95,51 @@ export async function isInstallationLikelyActive(installation, snapshot = realPr
|
|
|
82
95
|
return true;
|
|
83
96
|
}
|
|
84
97
|
}
|
|
98
|
+
/** `ps` prints `?` (Linux), `??` (macOS) or `-` for a process with no controlling terminal. */
|
|
99
|
+
const NO_TTY_MARKERS = new Set(['?', '??', '-']);
|
|
100
|
+
function shortenArgs(args, versionDir) {
|
|
101
|
+
// Strip the long install path so the line reads as the command the user ran.
|
|
102
|
+
const trimmed = args.replace(versionDir, '…').replace(/\/node_modules\/\.bin\//, '/');
|
|
103
|
+
return trimmed.length > 110 ? `${trimmed.slice(0, 107)}...` : trimmed;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Same verdict as {@link isInstallationLikelyActive}, plus WHAT is holding the
|
|
107
|
+
* installation, so a refused update can name the session to finish instead of
|
|
108
|
+
* "in use; retry later" (PHNX-4116 follow-up: an operator on yosemite-s0 had a
|
|
109
|
+
* one-hour-old resumed session on another tty and no way to see it from the
|
|
110
|
+
* refusal).
|
|
111
|
+
*/
|
|
112
|
+
export async function describeInstallationActivity(installation, snapshot = realProcessSnapshot) {
|
|
113
|
+
const lease = hasLiveLaunchLease(installation.agent, installation.label);
|
|
114
|
+
const versionDir = getVersionDir(installation.agent, installation.label);
|
|
115
|
+
try {
|
|
116
|
+
const rows = snapshot.listProcessRows
|
|
117
|
+
? await snapshot.listProcessRows()
|
|
118
|
+
: (await snapshot.listCommandLines()).map((args) => ({ args }));
|
|
119
|
+
const processes = rows
|
|
120
|
+
.filter((row) => row.args.includes(versionDir))
|
|
121
|
+
.map((row) => {
|
|
122
|
+
const meta = [row.pid !== undefined ? `pid ${row.pid}` : null, row.elapsed ? `up ${row.elapsed}` : null, row.tty && !NO_TTY_MARKERS.has(row.tty) ? row.tty : null]
|
|
123
|
+
.filter((part) => part !== null)
|
|
124
|
+
.join(', ');
|
|
125
|
+
return meta ? `${meta}: ${shortenArgs(row.args, versionDir)}` : shortenArgs(row.args, versionDir);
|
|
126
|
+
});
|
|
127
|
+
return { active: lease || processes.length > 0, lease, processes };
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
return { active: true, lease, processes: [], scanError: err instanceof Error ? err.message : String(err) };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** The one-line refusal an update prints for a busy installation. */
|
|
134
|
+
export function formatInUseDeferral(name, activity) {
|
|
135
|
+
if (activity.scanError)
|
|
136
|
+
return `${name}: could not confirm nothing is running (${activity.scanError}); not updating.`;
|
|
137
|
+
if (activity.processes.length > 0) {
|
|
138
|
+
const list = activity.processes.length === 1
|
|
139
|
+
? activity.processes[0]
|
|
140
|
+
: activity.processes.map((p) => `\n ${p}`).join('');
|
|
141
|
+
return `${name} is in use by ${activity.processes.length === 1 ? 'a running process' : `${activity.processes.length} running processes`} — ${list}. `
|
|
142
|
+
+ 'Finish that session (agents sessions stop <id> for an agents session), then retry.';
|
|
143
|
+
}
|
|
144
|
+
return `${name} has a launch in flight; retry once it has started or exited.`;
|
|
145
|
+
}
|
|
@@ -17,10 +17,13 @@ import { enabledRoutineNames, replaceEnabledRoutines } from '../routine-activati
|
|
|
17
17
|
import { evaluateActivationReadiness } from '../routine-readiness.js';
|
|
18
18
|
import { migrateDeviceConfigStores } from '../devices/config-migration.js';
|
|
19
19
|
import { detrackViaGitExclude } from '../project-resources.js';
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
import { LEGACY_DEFAULT_BROWSER_PROFILE_NAME } from '../browser/profiles.js';
|
|
20
|
+
// Migrations operate on raw YAML so they never take the meta lock or prime the
|
|
21
|
+
// meta cache mid-migration.
|
|
23
22
|
import { META_HEADER as DEVICE_META_HEADER } from '../state.js';
|
|
23
|
+
/** The pre-device-scope central browser profile name this one-shot migration
|
|
24
|
+
* folds into the device doc. Inlined (the in-repo browser engine that defined it
|
|
25
|
+
* was removed with PHNX-4101); the legacy on-disk name is unchanged. */
|
|
26
|
+
const LEGACY_DEFAULT_BROWSER_PROFILE_NAME = 'default';
|
|
24
27
|
import { COMPILED_HEADER_PROJECT } from '../rules/compile.js';
|
|
25
28
|
const HOME = process.env.HOME ?? os.homedir();
|
|
26
29
|
const USER_DIR = path.join(HOME, '.agents');
|
|
@@ -64,7 +64,7 @@ export interface ConflictInfo {
|
|
|
64
64
|
* top-level entry add/remove — deep edits to plugin contents won't
|
|
65
65
|
* trigger auto-resync, run `agents sync` for that.
|
|
66
66
|
*/
|
|
67
|
-
export declare const SHIM_SCHEMA_VERSION =
|
|
67
|
+
export declare const SHIM_SCHEMA_VERSION = 33;
|
|
68
68
|
/**
|
|
69
69
|
* Generate the full bash shim script for the given agent. The returned string
|
|
70
70
|
* is written to ~/.agents/shims/{cliCommand} and made executable.
|
|
@@ -271,7 +271,7 @@ async function promptConflictStrategy(conflictInfos) {
|
|
|
271
271
|
// v32 — every config-dir pin (claude, grok, opencode, kimi, muse, copilot) yields to
|
|
272
272
|
// an account-slot launch (AGENTS_EXEC_HOME from `agents run`), so a slot run
|
|
273
273
|
// reads and writes its own slot home instead of the shared version home.
|
|
274
|
-
export const SHIM_SCHEMA_VERSION =
|
|
274
|
+
export const SHIM_SCHEMA_VERSION = 33;
|
|
275
275
|
/** Internal marker string used to embed the schema version in shim scripts. */
|
|
276
276
|
const SHIM_VERSION_MARKER = 'agents-shim-version:';
|
|
277
277
|
function shellQuote(value) {
|
|
@@ -306,7 +306,14 @@ function shellQuote(value) {
|
|
|
306
306
|
* versions.ts (which reads bare filenames via `fs.readdirSync` and so never
|
|
307
307
|
* had the full-path-match bug).
|
|
308
308
|
*/
|
|
309
|
-
const GROK_RESOLVE_BINARY_FN = `
|
|
309
|
+
const GROK_RESOLVE_BINARY_FN = `_resolve_grok_current() {
|
|
310
|
+
local target
|
|
311
|
+
target=$(readlink -f "$1/bin/grok" 2>/dev/null) || return 0
|
|
312
|
+
[ -f "$target" ] && [ -x "$target" ] || return 0
|
|
313
|
+
[ "$(wc -c < "$target" 2>/dev/null || echo 0)" -ge 1000000 ] || return 0
|
|
314
|
+
printf '%s\\n' "$target"
|
|
315
|
+
}
|
|
316
|
+
_resolve_grok_binary() {
|
|
310
317
|
local dir="$1" version_hint="$2"
|
|
311
318
|
local candidate base size mtime
|
|
312
319
|
for candidate in "$dir"/grok-*; do
|
|
@@ -569,12 +576,20 @@ ${GROK_RESOLVE_BINARY_FN}
|
|
|
569
576
|
# Check the versioned home first — this is where the binary lands when the
|
|
570
577
|
# installer runs with GROK_HOME set (i.e. via the shim or a correct
|
|
571
578
|
# \`agents add grok\`), or when grok self-updates from within the shim.
|
|
579
|
+
# \`bin/grok\` is grok's own current-release pointer, kept by its updater in
|
|
580
|
+
# both layouts (\`bin/grok -> ../downloads/grok-<v>-<arch>\` and the newer
|
|
581
|
+
# \`bin/grok -> grok-<v>\`); \`grok update\` never touches downloads/ in the
|
|
582
|
+
# newer one, so scanning downloads/ alone keeps exec'ing a dead release.
|
|
583
|
+
BINARY=$(_resolve_grok_current "$VERSION_DIR/home/.grok")
|
|
572
584
|
GROK_DOWNLOADS="$VERSION_DIR/home/.grok/downloads"
|
|
573
|
-
if [ -d "$GROK_DOWNLOADS" ]; then
|
|
585
|
+
if [ -z "$BINARY" ] && [ -d "$GROK_DOWNLOADS" ]; then
|
|
574
586
|
BINARY=$(_resolve_grok_binary "$GROK_DOWNLOADS" "$VERSION")
|
|
575
587
|
fi
|
|
576
588
|
# Fall back to the global grok home (binary installed without GROK_HOME set,
|
|
577
589
|
# e.g. an earlier \`agents add grok@latest\` before this resolution fix).
|
|
590
|
+
if [ -z "$BINARY" ] || [ ! -x "$BINARY" ]; then
|
|
591
|
+
BINARY=$(_resolve_grok_current "$HOME/.grok")
|
|
592
|
+
fi
|
|
578
593
|
if [ -z "$BINARY" ] || [ ! -x "$BINARY" ]; then
|
|
579
594
|
GROK_DOWNLOADS="$HOME/.grok/downloads"
|
|
580
595
|
if [ -d "$GROK_DOWNLOADS" ]; then
|
|
@@ -1300,12 +1315,15 @@ export AGENT_CLI_CREDENTIAL_STORE="file"
|
|
|
1300
1315
|
? `# Grok ships its native binary in the versioned home's .grok/downloads (or,
|
|
1301
1316
|
# for pre-fix installs, the global ~/.grok/downloads), not node_modules.
|
|
1302
1317
|
${GROK_RESOLVE_BINARY_FN}
|
|
1318
|
+
BINARY=$(_resolve_grok_current "${versionDir}/home/.grok")
|
|
1303
1319
|
GROK_DOWNLOADS="${versionDir}/home/.grok/downloads"
|
|
1304
|
-
BINARY
|
|
1305
|
-
if [ -d "$GROK_DOWNLOADS" ]; then
|
|
1320
|
+
if [ -z "$BINARY" ] && [ -d "$GROK_DOWNLOADS" ]; then
|
|
1306
1321
|
BINARY=$(_resolve_grok_binary "$GROK_DOWNLOADS" "${version}")
|
|
1307
1322
|
fi
|
|
1308
1323
|
# Fall back to the global grok home (binary installed without GROK_HOME set).
|
|
1324
|
+
if [ -z "$BINARY" ] || [ ! -x "$BINARY" ]; then
|
|
1325
|
+
BINARY=$(_resolve_grok_current "$AGENTS_REAL_HOME/.grok")
|
|
1326
|
+
fi
|
|
1309
1327
|
if [ -z "$BINARY" ] || [ ! -x "$BINARY" ]; then
|
|
1310
1328
|
GROK_GLOBAL_DOWNLOADS="$AGENTS_REAL_HOME/.grok/downloads"
|
|
1311
1329
|
if [ -d "$GROK_GLOBAL_DOWNLOADS" ]; then
|
|
@@ -2189,15 +2207,17 @@ export function listShimFileNames() {
|
|
|
2189
2207
|
}
|
|
2190
2208
|
/**
|
|
2191
2209
|
* Legacy command shims that are wrong even when their baked install is alive.
|
|
2192
|
-
* `secrets` (PHNX-3989), `sessions` (PHNX-4012)
|
|
2193
|
-
* shim `exec`s `agents <name>`, which is a passthrough
|
|
2194
|
-
* so the shim re-enters itself (the 1.22.85 fork
|
|
2210
|
+
* `secrets` (PHNX-3989), `sessions` (PHNX-4012), `computer` (PHNX-4075) and
|
|
2211
|
+
* `browser` (PHNX-4101): the shim `exec`s `agents <name>`, which is a passthrough
|
|
2212
|
+
* to the STANDALONE binary — so the shim re-enters itself (the 1.22.85 fork
|
|
2213
|
+
* bomb) and, sitting first on PATH, shadows the real `@phnx-labs/<name>-cli`
|
|
2214
|
+
* binary a user types directly. Pruned unconditionally.
|
|
2195
2215
|
*
|
|
2196
|
-
* `computer`
|
|
2197
|
-
* `computer` bin of its own: a machine that installed an
|
|
2198
|
-
* shim on PATH under the name the standalone engine now owns.
|
|
2216
|
+
* `computer` and `browser` are the sharpest, because agents-cli USED to publish
|
|
2217
|
+
* (`computer`) or shim (`browser`) a bin of its own: a machine that installed an
|
|
2218
|
+
* older CLI has a real shim on PATH under the name the standalone engine now owns.
|
|
2199
2219
|
*/
|
|
2200
|
-
const LEGACY_SHIMS_ALWAYS_PRUNED = new Set(['secrets', 'sessions', 'computer', 'pty']);
|
|
2220
|
+
const LEGACY_SHIMS_ALWAYS_PRUNED = new Set(['secrets', 'sessions', 'computer', 'pty', 'browser']);
|
|
2201
2221
|
/**
|
|
2202
2222
|
* Prune a stale, orphaned shim: one that is NOT a managed agent shim and NOT a user
|
|
2203
2223
|
* alias, whose baked `AGENTS_BIN` points at an install that no longer exists. These
|
|
@@ -118,6 +118,15 @@ export declare function getVersionDir(agent: AgentId, version: string): string;
|
|
|
118
118
|
* Returns null (fail loud, never guess) when nothing survives the size filter.
|
|
119
119
|
*/
|
|
120
120
|
export declare function resolveGrokFallbackBinary(downloadsDir: string): string | null;
|
|
121
|
+
/**
|
|
122
|
+
* Grok's own current-release pointer, `<grokHome>/bin/grok`, resolved to the
|
|
123
|
+
* real binary, or null when absent or not a real binary. Grok's updater keeps
|
|
124
|
+
* it current in both of its layouts (`bin/grok -> ../downloads/grok-<v>-<arch>`
|
|
125
|
+
* and the newer `bin/grok -> grok-<v>`, which never touches downloads/), so it
|
|
126
|
+
* is the only location that tracks `grok update`. Mirrors the shim's
|
|
127
|
+
* `_resolve_grok_current`.
|
|
128
|
+
*/
|
|
129
|
+
export declare function resolveGrokCurrentBinary(grokHome: string): string | null;
|
|
121
130
|
/**
|
|
122
131
|
* Get the binary path for a specific agent version.
|
|
123
132
|
*/
|
|
@@ -339,12 +339,36 @@ export function resolveGrokFallbackBinary(downloadsDir) {
|
|
|
339
339
|
}
|
|
340
340
|
return best ? path.join(downloadsDir, best.name) : null;
|
|
341
341
|
}
|
|
342
|
+
/**
|
|
343
|
+
* Grok's own current-release pointer, `<grokHome>/bin/grok`, resolved to the
|
|
344
|
+
* real binary, or null when absent or not a real binary. Grok's updater keeps
|
|
345
|
+
* it current in both of its layouts (`bin/grok -> ../downloads/grok-<v>-<arch>`
|
|
346
|
+
* and the newer `bin/grok -> grok-<v>`, which never touches downloads/), so it
|
|
347
|
+
* is the only location that tracks `grok update`. Mirrors the shim's
|
|
348
|
+
* `_resolve_grok_current`.
|
|
349
|
+
*/
|
|
350
|
+
export function resolveGrokCurrentBinary(grokHome) {
|
|
351
|
+
try {
|
|
352
|
+
const target = fs.realpathSync(path.join(grokHome, 'bin', 'grok'));
|
|
353
|
+
const stat = fs.statSync(target);
|
|
354
|
+
if (!stat.isFile() || stat.size < MIN_GROK_BINARY_BYTES)
|
|
355
|
+
return null;
|
|
356
|
+
fs.accessSync(target, fs.constants.X_OK);
|
|
357
|
+
return target;
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
342
363
|
/**
|
|
343
364
|
* Get the binary path for a specific agent version.
|
|
344
365
|
*/
|
|
345
366
|
export function getBinaryPath(agent, version) {
|
|
346
367
|
const agentConfig = AGENTS[agent];
|
|
347
368
|
if (agent === 'grok') {
|
|
369
|
+
const current = resolveGrokCurrentBinary(path.join(getVersionHomePath(agent, version), '.grok'));
|
|
370
|
+
if (current)
|
|
371
|
+
return current;
|
|
348
372
|
const grokDownloads = path.join(getVersionHomePath(agent, version), '.grok', 'downloads');
|
|
349
373
|
// The directory token is the stable installation/account label. A
|
|
350
374
|
// self-updating slot may carry a newer vendor release, whose binary keeps
|
|
@@ -6,7 +6,7 @@ import { getBinaryPath, invalidateInstalledVersionsCache, invalidateLiveVersionC
|
|
|
6
6
|
import { assertValidRelease, selectUpdateStrategy, } from './strategies.js';
|
|
7
7
|
import { listInstallations, readInstallation, recordRelease, writeInstallation } from './store.js';
|
|
8
8
|
import { effectiveUpdatePolicy, isAutoUpdateEnabledForAgent } from './update-policy.js';
|
|
9
|
-
import {
|
|
9
|
+
import { describeInstallationActivity, formatInUseDeferral } from './active-check.js';
|
|
10
10
|
import { installationLockTarget, INSTALLATION_LOCK_OPTIONS } from './installation-lock.js';
|
|
11
11
|
/**
|
|
12
12
|
* Move one frozen installation to a new vendor release, preserving its identity.
|
|
@@ -88,18 +88,21 @@ async function runUpdateInstallation(agent, requestedInstallation, options) {
|
|
|
88
88
|
return { installation, strategy: strategy.id, fromRelease: installation.releaseVersion, toRelease: target,
|
|
89
89
|
unchanged: true, deferred: 'Update cancelled or automatic update policy changed.', alsoUpdated: [] };
|
|
90
90
|
}
|
|
91
|
-
if (strategy.transactional
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
91
|
+
if (strategy.transactional) {
|
|
92
|
+
const activity = await describeInstallationActivity(installation);
|
|
93
|
+
if (activity.active) {
|
|
94
|
+
const name = `${AGENTS[agent].name}@${installation.label}`;
|
|
95
|
+
options.onProgress?.(`${name} looks active right now; not staging release ${target}.`);
|
|
96
|
+
return {
|
|
97
|
+
installation,
|
|
98
|
+
strategy: strategy.id,
|
|
99
|
+
fromRelease: installation.releaseVersion,
|
|
100
|
+
toRelease: target,
|
|
101
|
+
unchanged: true,
|
|
102
|
+
deferred: formatInUseDeferral(name, activity),
|
|
103
|
+
alsoUpdated: [],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
103
106
|
}
|
|
104
107
|
let staged = null;
|
|
105
108
|
try {
|
|
@@ -179,7 +182,8 @@ async function runUpdateInstallation(agent, requestedInstallation, options) {
|
|
|
179
182
|
// identical pre-stage check above for why this cannot be opt-in: a launch
|
|
180
183
|
// that starts AFTER that first check but before this swap is exactly the
|
|
181
184
|
// window this closes.
|
|
182
|
-
|
|
185
|
+
const lateActivity = strategy.transactional ? await describeInstallationActivity(installation) : null;
|
|
186
|
+
if (options.shouldCancel?.() || lateActivity?.active) {
|
|
183
187
|
options.onProgress?.(`${AGENTS[agent].name}@${installation.label} looks active now (a process or launch lease appeared while `
|
|
184
188
|
+ `${staged.release} was staging); not committing it.`);
|
|
185
189
|
return {
|
|
@@ -188,7 +192,9 @@ async function runUpdateInstallation(agent, requestedInstallation, options) {
|
|
|
188
192
|
fromRelease: installation.releaseVersion,
|
|
189
193
|
toRelease: staged.release,
|
|
190
194
|
unchanged: true,
|
|
191
|
-
deferred:
|
|
195
|
+
deferred: lateActivity?.active
|
|
196
|
+
? formatInUseDeferral(`${AGENTS[agent].name}@${installation.label}`, lateActivity)
|
|
197
|
+
: 'Update cancelled while the release was being prepared.',
|
|
192
198
|
alsoUpdated: [],
|
|
193
199
|
};
|
|
194
200
|
}
|