@phnx-labs/agents-cli 1.22.57 → 1.22.59
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 +294 -0
- package/README.md +29 -0
- package/dist/bootstrap.js +39 -1
- package/dist/commands/accounts.js +7 -3
- package/dist/commands/apply.js +10 -2
- package/dist/commands/fork.d.ts +23 -10
- package/dist/commands/fork.js +115 -58
- package/dist/commands/monitors.js +198 -23
- package/dist/commands/prune.js +5 -3
- package/dist/commands/routines.d.ts +8 -0
- package/dist/commands/routines.js +57 -3
- package/dist/commands/routines.test-fixture.js +5 -0
- package/dist/commands/send.d.ts +2 -1
- package/dist/commands/send.js +7 -5
- package/dist/commands/sessions-picker.d.ts +11 -0
- package/dist/commands/sessions-picker.js +16 -0
- package/dist/commands/sessions-stats.js +37 -5
- package/dist/commands/sessions.js +40 -5
- package/dist/commands/share.d.ts +14 -0
- package/dist/commands/share.js +43 -2
- package/dist/commands/ssh.js +12 -1
- package/dist/commands/status.js +1 -1
- package/dist/commands/sync.js +83 -7
- package/dist/commands/traces.js +7 -0
- package/dist/commands/versions.js +12 -4
- package/dist/commands/view.js +7 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -1
- package/dist/lib/account-registry.d.ts +5 -1
- package/dist/lib/account-registry.js +47 -14
- package/dist/lib/accounting/capacity.d.ts +18 -7
- package/dist/lib/accounting/capacity.js +19 -8
- package/dist/lib/accounting/usage-sync.d.ts +29 -1
- package/dist/lib/accounting/usage-sync.js +76 -2
- package/dist/lib/accounting/usage.js +7 -1
- package/dist/lib/auth-mint.d.ts +11 -1
- package/dist/lib/auth-mint.js +21 -6
- package/dist/lib/auto-pull-worker.js +7 -2
- package/dist/lib/browser/ipc.d.ts +8 -0
- package/dist/lib/browser/ipc.js +87 -0
- package/dist/lib/browser/service.d.ts +19 -0
- package/dist/lib/browser/service.js +96 -11
- package/dist/lib/browser/sessions-list.js +10 -1
- package/dist/lib/cloud/rush.d.ts +7 -0
- package/dist/lib/cloud/rush.js +29 -1
- package/dist/lib/daemon/daemon.d.ts +22 -0
- package/dist/lib/daemon/daemon.js +39 -0
- package/dist/lib/daemon/runner.d.ts +3 -0
- package/dist/lib/daemon/runner.js +86 -45
- package/dist/lib/daemon/session-index-service.js +9 -1
- package/dist/lib/daemon/usage-sync-service.d.ts +3 -3
- package/dist/lib/daemon/usage-sync-service.js +14 -8
- package/dist/lib/daemon-services.js +1 -1
- package/dist/lib/daemon-ticks.d.ts +15 -0
- package/dist/lib/daemon-ticks.js +26 -0
- package/dist/lib/device-config.d.ts +5 -1
- package/dist/lib/device-config.js +2 -2
- package/dist/lib/devices/connect.d.ts +17 -8
- package/dist/lib/devices/connect.js +31 -14
- package/dist/lib/devices/health.js +5 -1
- package/dist/lib/devices/pool.d.ts +25 -2
- package/dist/lib/devices/pool.js +32 -2
- package/dist/lib/devices/stats-cache.d.ts +0 -6
- package/dist/lib/devices/stats-cache.js +2 -9
- package/dist/lib/doctor-diff.d.ts +14 -0
- package/dist/lib/doctor-diff.js +120 -9
- package/dist/lib/fleet/manifest.d.ts +17 -0
- package/dist/lib/fleet/manifest.js +26 -0
- package/dist/lib/git.d.ts +38 -0
- package/dist/lib/git.js +58 -0
- package/dist/lib/hooks/install.d.ts +27 -11
- package/dist/lib/hooks/install.js +42 -17
- package/dist/lib/hosts/ready.d.ts +8 -0
- package/dist/lib/hosts/ready.js +13 -2
- package/dist/lib/hosts/reconnect.d.ts +52 -203
- package/dist/lib/hosts/reconnect.js +64 -284
- package/dist/lib/installations/migrate.d.ts +6 -120
- package/dist/lib/installations/migrate.js +27 -259
- package/dist/lib/installations/shims.d.ts +13 -95
- package/dist/lib/installations/shims.js +22 -139
- package/dist/lib/installations/store.js +1 -1
- package/dist/lib/installations/versions.d.ts +43 -133
- package/dist/lib/installations/versions.js +94 -206
- package/dist/lib/monitors/config.d.ts +71 -3
- package/dist/lib/monitors/config.js +100 -12
- package/dist/lib/monitors/pid-watch.d.ts +35 -0
- package/dist/lib/monitors/pid-watch.js +45 -0
- package/dist/lib/monitors/remote.d.ts +18 -0
- package/dist/lib/monitors/remote.js +11 -0
- package/dist/lib/permissions.js +7 -2
- package/dist/lib/plugins/plugins.d.ts +17 -3
- package/dist/lib/plugins/plugins.js +84 -9
- package/dist/lib/plugins/skills.d.ts +8 -1
- package/dist/lib/plugins/skills.js +18 -2
- package/dist/lib/pty-server.d.ts +14 -0
- package/dist/lib/pty-server.js +49 -5
- package/dist/lib/refresh.d.ts +9 -0
- package/dist/lib/refresh.js +3 -1
- package/dist/lib/routine-readiness.d.ts +15 -1
- package/dist/lib/routine-readiness.js +41 -0
- package/dist/lib/sandbox.d.ts +4 -1
- package/dist/lib/sandbox.js +30 -1
- package/dist/lib/secrets/agent.d.ts +80 -225
- package/dist/lib/secrets/agent.js +139 -401
- package/dist/lib/secrets/bundles.d.ts +73 -222
- package/dist/lib/secrets/bundles.js +168 -467
- package/dist/lib/secrets/drivers/rush.js +5 -0
- package/dist/lib/secrets/reaper.d.ts +28 -70
- package/dist/lib/secrets/reaper.js +30 -85
- package/dist/lib/secrets/remote.d.ts +42 -129
- package/dist/lib/secrets/remote.js +55 -173
- package/dist/lib/self-heal/checks/install-staging.d.ts +4 -0
- package/dist/lib/self-heal/checks/install-staging.js +96 -0
- package/dist/lib/self-heal/registry.js +2 -0
- package/dist/lib/self-heal/types.d.ts +1 -1
- package/dist/lib/self-update.d.ts +65 -0
- package/dist/lib/self-update.js +138 -0
- package/dist/lib/session/active.d.ts +13 -1
- package/dist/lib/session/active.js +2 -0
- package/dist/lib/session/cloud.js +5 -0
- package/dist/lib/session/db.d.ts +51 -6
- package/dist/lib/session/db.js +266 -20
- package/dist/lib/session/fork.d.ts +45 -26
- package/dist/lib/session/fork.js +32 -95
- package/dist/lib/session/tool-calls.d.ts +43 -1
- package/dist/lib/session/tool-calls.js +74 -44
- package/dist/lib/session/tool-store.d.ts +33 -2
- package/dist/lib/session/tool-store.js +56 -3
- package/dist/lib/smart-launch.d.ts +6 -0
- package/dist/lib/smart-launch.js +5 -2
- package/dist/lib/staleness/writers/plugins.js +5 -2
- package/dist/lib/staleness/writers/sources.d.ts +5 -0
- package/dist/lib/staleness/writers/sources.js +2 -1
- package/dist/lib/staleness/writers/subagents.js +13 -3
- package/dist/lib/state.d.ts +7 -4
- package/dist/lib/state.js +7 -4
- package/dist/lib/subagents.js +8 -2
- package/dist/lib/sync-status.d.ts +22 -0
- package/dist/lib/sync-status.js +27 -0
- package/dist/lib/sync-umbrella.d.ts +9 -0
- package/dist/lib/sync-umbrella.js +21 -2
- package/dist/lib/teams/scheduler.d.ts +10 -0
- package/dist/lib/teams/scheduler.js +8 -0
- package/dist/lib/traces/insights.d.ts +47 -14
- package/dist/lib/traces/insights.js +92 -21
- package/dist/lib/traces/phenotype.d.ts +23 -3
- package/dist/lib/traces/phenotype.js +72 -24
- package/dist/lib/traces/sync.d.ts +128 -6
- package/dist/lib/traces/sync.js +294 -35
- package/dist/lib/traces/worker-template.js +154 -1
- package/dist/lib/view-types.d.ts +12 -0
- package/package.json +2 -2
|
@@ -16,6 +16,7 @@ import { capableAgents, isCapable } from '../capabilities.js';
|
|
|
16
16
|
import { getUserSkillsDir, getSkillsDir as getSystemSkillsDir, getProjectAgentsDir, getEnabledExtraRepos, getTrashSkillsDir } from '../state.js';
|
|
17
17
|
import { getEffectiveHome, getVersionHomePath, listInstalledVersions } from '../installations/store.js';
|
|
18
18
|
import { listCommandSkillsInVersion } from '../command-skills.js';
|
|
19
|
+
import { pluginSkillDirs, listPluginSkillNames } from '../staleness/writers/sources.js';
|
|
19
20
|
import { emit } from '../feed/events.js';
|
|
20
21
|
const HOME = os.homedir();
|
|
21
22
|
/** User-scoped skills dir (~/.agents/skills/). Used for installs. */
|
|
@@ -352,7 +353,14 @@ export function listCentralSkills() {
|
|
|
352
353
|
}
|
|
353
354
|
/**
|
|
354
355
|
* Resolve a skill name to its source directory. Searches user dir first,
|
|
355
|
-
* then system dir, then extra repos
|
|
356
|
+
* then system dir, then extra repos, then plugin-bundled skills
|
|
357
|
+
* (`plugins/<plugin>/skills/<name>` under any trusted base). Returns null if no
|
|
358
|
+
* source has a SKILL.md.
|
|
359
|
+
*
|
|
360
|
+
* Crediting plugins here is load-bearing (PHNX-3185): a skill materialized from
|
|
361
|
+
* `plugins/design/skills/design` has a real source, so it must resolve — else
|
|
362
|
+
* `versionSkillMatches` finds no source and the skill reads as drifted, and
|
|
363
|
+
* {@link diffVersionSkills} classes it an orphan `prune cleanup` would delete.
|
|
356
364
|
*/
|
|
357
365
|
export function resolveSkillSourcePath(skillName) {
|
|
358
366
|
for (const dir of [getUserSkillsDir(), getSystemSkillsDir()]) {
|
|
@@ -365,6 +373,11 @@ export function resolveSkillSourcePath(skillName) {
|
|
|
365
373
|
if (fs.existsSync(path.join(candidate, 'SKILL.md')))
|
|
366
374
|
return candidate;
|
|
367
375
|
}
|
|
376
|
+
for (const skillsDir of pluginSkillDirs()) {
|
|
377
|
+
const candidate = path.join(skillsDir, skillName);
|
|
378
|
+
if (fs.existsSync(path.join(candidate, 'SKILL.md')))
|
|
379
|
+
return candidate;
|
|
380
|
+
}
|
|
368
381
|
return null;
|
|
369
382
|
}
|
|
370
383
|
/**
|
|
@@ -443,7 +456,10 @@ function versionSkillMatches(agent, version, skillName) {
|
|
|
443
456
|
* Compare a version home's skills against central. Returns the reconciliation diff.
|
|
444
457
|
*/
|
|
445
458
|
export function diffVersionSkills(agent, version) {
|
|
446
|
-
|
|
459
|
+
// Plugin-provided skills (`plugins/<plugin>/skills`) are legitimate sources too, so a
|
|
460
|
+
// skill materialized from a plugin is NOT an orphan (PHNX-3185). Without this
|
|
461
|
+
// the detector offered to delete the entire plugin-installed skill library.
|
|
462
|
+
const available = new Set([...listAllSkills(), ...listPluginSkillNames({ agent })]);
|
|
447
463
|
// Goose and other native ~/.agents/skills consumers read central storage
|
|
448
464
|
// directly. They intentionally have no per-version copy to diff, so every
|
|
449
465
|
// available central skill is already current for every supported version.
|
package/dist/lib/pty-server.d.ts
CHANGED
|
@@ -21,6 +21,20 @@ export { captureProcessStartTime };
|
|
|
21
21
|
* (the authoritative code comes from node-pty's onExit).
|
|
22
22
|
*/
|
|
23
23
|
export declare function buildSentinelCommand(shell: string, command: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Turn a native-binding load failure into an actionable, platform-aware message.
|
|
26
|
+
*
|
|
27
|
+
* The binding is `@homebridge/node-pty-prebuilt-multiarch`'s `pty.node`. On Linux
|
|
28
|
+
* it is baked into the npm tarball and always present; on macOS/Windows it is
|
|
29
|
+
* fetched per host + Node ABI at install time by the package's `prebuild-install`
|
|
30
|
+
* postinstall. The common failure is a Node runtime whose ABI has no published
|
|
31
|
+
* prebuild (the PHNX-2740 darwin-arm64 case): `prebuild-install` finds nothing,
|
|
32
|
+
* the build-from-source fallback also fails, and the loader throws a bare
|
|
33
|
+
* `Cannot find module '.../pty.node'` / MODULE_NOT_FOUND with no clue what to do.
|
|
34
|
+
*
|
|
35
|
+
* Pure (returns the lines) so it can be unit-tested without spawning the server.
|
|
36
|
+
*/
|
|
37
|
+
export declare function describeNativePtyLoadFailure(err: unknown): string[];
|
|
24
38
|
/**
|
|
25
39
|
* Resolve the IPC endpoint for a given platform + PTY scratch dir. Pure so both
|
|
26
40
|
* branches are testable without stubbing process.platform.
|
package/dist/lib/pty-server.js
CHANGED
|
@@ -88,6 +88,43 @@ export function buildSentinelCommand(shell, command) {
|
|
|
88
88
|
}
|
|
89
89
|
return `${command}; echo "${SENTINEL}:$?"`;
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Turn a native-binding load failure into an actionable, platform-aware message.
|
|
93
|
+
*
|
|
94
|
+
* The binding is `@homebridge/node-pty-prebuilt-multiarch`'s `pty.node`. On Linux
|
|
95
|
+
* it is baked into the npm tarball and always present; on macOS/Windows it is
|
|
96
|
+
* fetched per host + Node ABI at install time by the package's `prebuild-install`
|
|
97
|
+
* postinstall. The common failure is a Node runtime whose ABI has no published
|
|
98
|
+
* prebuild (the PHNX-2740 darwin-arm64 case): `prebuild-install` finds nothing,
|
|
99
|
+
* the build-from-source fallback also fails, and the loader throws a bare
|
|
100
|
+
* `Cannot find module '.../pty.node'` / MODULE_NOT_FOUND with no clue what to do.
|
|
101
|
+
*
|
|
102
|
+
* Pure (returns the lines) so it can be unit-tested without spawning the server.
|
|
103
|
+
*/
|
|
104
|
+
export function describeNativePtyLoadFailure(err) {
|
|
105
|
+
const detail = err instanceof Error ? (err.stack || err.message) : String(err);
|
|
106
|
+
const abi = process.versions.modules;
|
|
107
|
+
const nodeVersion = process.version;
|
|
108
|
+
const platform = `${process.platform}-${process.arch}`;
|
|
109
|
+
const lines = [
|
|
110
|
+
`agents pty could not load its native terminal binding (@homebridge/node-pty-prebuilt-multiarch).`,
|
|
111
|
+
` platform: ${platform} node: ${nodeVersion} (ABI ${abi})`,
|
|
112
|
+
``,
|
|
113
|
+
`The prebuilt binary for this platform + Node ABI is missing. On macOS/Windows`,
|
|
114
|
+
`it is downloaded at install time; a Node version newer than any published`,
|
|
115
|
+
`prebuild, or an install that skipped the postinstall, leaves it absent.`,
|
|
116
|
+
``,
|
|
117
|
+
`Fix it by reinstalling so the native binding is fetched or built:`,
|
|
118
|
+
` npm rebuild @homebridge/node-pty-prebuilt-multiarch # rebuild for this Node`,
|
|
119
|
+
` # or reinstall the CLI: npm i -g @phnx-labs/agents-cli`,
|
|
120
|
+
`If your Node (ABI ${abi}) is newer than the shipped prebuilds, install an LTS`,
|
|
121
|
+
`Node and retry, or ensure a C++ toolchain is present for the source build.`,
|
|
122
|
+
``,
|
|
123
|
+
`Underlying error:`,
|
|
124
|
+
...detail.split('\n').map(l => ` ${l}`),
|
|
125
|
+
];
|
|
126
|
+
return lines;
|
|
127
|
+
}
|
|
91
128
|
/** Get the PTY helper directory, creating it if needed. */
|
|
92
129
|
function getPtyDir() {
|
|
93
130
|
const dir = getPtyDirRoot();
|
|
@@ -181,9 +218,16 @@ export async function runPtyServer() {
|
|
|
181
218
|
let nodePty;
|
|
182
219
|
let XtermTerminal;
|
|
183
220
|
try {
|
|
184
|
-
// The Homebridge multiarch fork of node-pty
|
|
185
|
-
//
|
|
186
|
-
//
|
|
221
|
+
// The Homebridge multiarch fork of node-pty (API-identical to the 1.x N-API
|
|
222
|
+
// upstream). Its npm tarball BAKES IN the Linux prebuilds (glibc + musl, every
|
|
223
|
+
// arch + Node ABI), so Linux never needs a compiler or a network fetch. The
|
|
224
|
+
// darwin-arm64 / win32 binaries are NOT in the tarball — they are downloaded
|
|
225
|
+
// per host+Node-ABI at install time by the package's own `prebuild-install`
|
|
226
|
+
// postinstall (gated by bun's `trustedDependencies`). That fetch fails when the
|
|
227
|
+
// running Node ABI is newer than any published prebuild (e.g. 0.13.1 shipped no
|
|
228
|
+
// darwin-arm64 above Node 24 / ABI 137, so Node 25/26 fell through to a
|
|
229
|
+
// build-from-source that also failed — PHNX-2740), leaving a raw MODULE_NOT_FOUND.
|
|
230
|
+
// Fail loud with the platform/ABI and a concrete remediation instead.
|
|
187
231
|
nodePty = await import('@homebridge/node-pty-prebuilt-multiarch');
|
|
188
232
|
// Handle ESM default export
|
|
189
233
|
if (nodePty.default?.spawn)
|
|
@@ -205,8 +249,8 @@ export async function runPtyServer() {
|
|
|
205
249
|
catch { }
|
|
206
250
|
}
|
|
207
251
|
catch (err) {
|
|
208
|
-
|
|
209
|
-
|
|
252
|
+
for (const line of describeNativePtyLoadFailure(err))
|
|
253
|
+
console.error(line);
|
|
210
254
|
process.exit(1);
|
|
211
255
|
}
|
|
212
256
|
try {
|
package/dist/lib/refresh.d.ts
CHANGED
|
@@ -40,5 +40,14 @@ export interface RefreshOptions {
|
|
|
40
40
|
export interface RefreshResult {
|
|
41
41
|
/** User-facing sentences, one per refused resource, prefixed with the agent. */
|
|
42
42
|
declined: string[];
|
|
43
|
+
/**
|
|
44
|
+
* The exact (agent, version) pairs this refresh reconciled — the set a
|
|
45
|
+
* post-reconcile verification must re-check for residual drift, so it never
|
|
46
|
+
* flags a version the reconcile never targeted (PHNX-3186).
|
|
47
|
+
*/
|
|
48
|
+
reconciled: Array<{
|
|
49
|
+
agent: AgentId;
|
|
50
|
+
version: string;
|
|
51
|
+
}>;
|
|
43
52
|
}
|
|
44
53
|
export declare function refresh(options?: RefreshOptions): Promise<RefreshResult>;
|
package/dist/lib/refresh.js
CHANGED
|
@@ -58,6 +58,7 @@ export async function refresh(options = {}) {
|
|
|
58
58
|
// Resources this pass refused to write, surfaced by the caller. An empty
|
|
59
59
|
// synced list cannot also mean "declined and here is why" (RUSH-2700).
|
|
60
60
|
const declined = [];
|
|
61
|
+
const reconciled = [];
|
|
61
62
|
migratePromptcutsToRoot(agentsDir, quiet);
|
|
62
63
|
const manifest = readManifest(agentsDir);
|
|
63
64
|
if (!manifest) {
|
|
@@ -176,6 +177,7 @@ export async function refresh(options = {}) {
|
|
|
176
177
|
// Pass the already-built `available` so each version does not re-scan
|
|
177
178
|
// resource trees (RUSH-2320 #5).
|
|
178
179
|
const syncResult = syncResourcesToVersion(agentId, ver, selection, { available, ...(forceFullSync ? { force: true } : {}) });
|
|
180
|
+
reconciled.push({ agent: agentId, version: ver });
|
|
179
181
|
if (syncResult.commands)
|
|
180
182
|
kinds.add('commands');
|
|
181
183
|
if (syncResult.skills)
|
|
@@ -353,5 +355,5 @@ export async function refresh(options = {}) {
|
|
|
353
355
|
for (const reason of declined)
|
|
354
356
|
log(` ${chalk.yellow(reason)}`);
|
|
355
357
|
}
|
|
356
|
-
return { declined };
|
|
358
|
+
return { declined, reconciled };
|
|
357
359
|
}
|
|
@@ -10,7 +10,21 @@
|
|
|
10
10
|
* authentication and Codex's native workspace-trust record before activation.
|
|
11
11
|
*/
|
|
12
12
|
import type { JobConfig } from './scheduling/routines.js';
|
|
13
|
-
import { type RoutineReadinessResult } from './routine-context.js';
|
|
13
|
+
import { type RoutineReadinessResult, type RoutineReadiness } from './routine-context.js';
|
|
14
|
+
/**
|
|
15
|
+
* Fire-time auth preflight: read the daemon-warmed auth-health cache for the
|
|
16
|
+
* exact (agent, version) a routine has resolved to run, and return an
|
|
17
|
+
* `agent_auth_failed` blocker when that account is provably signed out — so the
|
|
18
|
+
* daemon records a terminal `blocked` run (with the re-login repair) instead of
|
|
19
|
+
* spawning a doomed run that 401s and burns a session (PHNX-3415).
|
|
20
|
+
*
|
|
21
|
+
* Cache-only (no network, no prompt — the daemon refreshes the cache
|
|
22
|
+
* periodically and `fleet ping` writes it), and fails OPEN on a missing or
|
|
23
|
+
* non-blocking verdict so a stale/absent probe never wedges a routine. It is
|
|
24
|
+
* checked AFTER version rotation resolves the account (`launch.chain[0]`), so it
|
|
25
|
+
* judges the identity the run will actually use, never a rotated-past dead pin.
|
|
26
|
+
*/
|
|
27
|
+
export declare function fireTimeAuthReadiness(agent: string, version: string): RoutineReadiness | null;
|
|
14
28
|
/**
|
|
15
29
|
* Evaluate whether a routine is ready to activate on this box. `probeAgent`
|
|
16
30
|
* defaults to "is a version of the routine's agent resolvable" via
|
|
@@ -14,6 +14,8 @@ import * as path from 'path';
|
|
|
14
14
|
import * as TOML from 'smol-toml';
|
|
15
15
|
import { resolveJobExecutionContext, resolveHostStrategy } from './scheduling/routines.js';
|
|
16
16
|
import { evaluateRoutineReadiness } from './routine-context.js';
|
|
17
|
+
import { readAuthHealth } from './auth-health.js';
|
|
18
|
+
import { machineId } from './machine-id.js';
|
|
17
19
|
import { getVersionHomePath, isVersionInstalled, resolveVersion } from './installations/versions.js';
|
|
18
20
|
import { probeLocalFleetAuth } from './auth-health.js';
|
|
19
21
|
import { resolveHostRunTarget } from './hosts/run-target.js';
|
|
@@ -21,6 +23,45 @@ import { hostIdentityArgs, sshTargetFor } from './hosts/types.js';
|
|
|
21
23
|
import { probeHost } from './hosts/ready.js';
|
|
22
24
|
import { sshExec, shellQuote } from './ssh-exec.js';
|
|
23
25
|
import { encodePowershell, powershellQuote, POWERSHELL_PROGRESS_SILENCE } from './hosts/remote-cmd.js';
|
|
26
|
+
/**
|
|
27
|
+
* Verdicts that make a FIRE-TIME auth preflight block the run: the last live
|
|
28
|
+
* probe found the account either server-rejected (`revoked`) or with no
|
|
29
|
+
* credential at all (`unconfigured` — the "Please run /login" / "no account
|
|
30
|
+
* signed in" case, which is the most common way a routine's dispatch account
|
|
31
|
+
* goes dead). Everything else fails OPEN: `rate_limited` is still authenticated,
|
|
32
|
+
* `expired` self-heals on the next refresh, `unverified` means signed-in but no
|
|
33
|
+
* live probe endpoint (codex/grok), and `error` is indeterminate — none of those
|
|
34
|
+
* should stop a fire. This is deliberately BROADER than {@link isDeadVerdict}
|
|
35
|
+
* (display-only, `revoked` alone): a signed-out account must block a fire, not
|
|
36
|
+
* just paint a red cell.
|
|
37
|
+
*/
|
|
38
|
+
function fireBlockingAuthVerdict(verdict) {
|
|
39
|
+
return verdict === 'revoked' || verdict === 'unconfigured';
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Fire-time auth preflight: read the daemon-warmed auth-health cache for the
|
|
43
|
+
* exact (agent, version) a routine has resolved to run, and return an
|
|
44
|
+
* `agent_auth_failed` blocker when that account is provably signed out — so the
|
|
45
|
+
* daemon records a terminal `blocked` run (with the re-login repair) instead of
|
|
46
|
+
* spawning a doomed run that 401s and burns a session (PHNX-3415).
|
|
47
|
+
*
|
|
48
|
+
* Cache-only (no network, no prompt — the daemon refreshes the cache
|
|
49
|
+
* periodically and `fleet ping` writes it), and fails OPEN on a missing or
|
|
50
|
+
* non-blocking verdict so a stale/absent probe never wedges a routine. It is
|
|
51
|
+
* checked AFTER version rotation resolves the account (`launch.chain[0]`), so it
|
|
52
|
+
* judges the identity the run will actually use, never a rotated-past dead pin.
|
|
53
|
+
*/
|
|
54
|
+
export function fireTimeAuthReadiness(agent, version) {
|
|
55
|
+
const health = readAuthHealth(machineId(), agent, version);
|
|
56
|
+
if (!health || !fireBlockingAuthVerdict(health.verdict))
|
|
57
|
+
return null;
|
|
58
|
+
const who = health.account ? ` (${health.account})` : '';
|
|
59
|
+
return {
|
|
60
|
+
code: 'agent_auth_failed',
|
|
61
|
+
message: `the ${agent}${who} account is signed out — the last auth probe returned '${health.verdict}', so this run would fail authentication`,
|
|
62
|
+
repair: `agents run ${agent}@${version} -- login`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
24
65
|
/**
|
|
25
66
|
* Evaluate whether a routine is ready to activate on this box. `probeAgent`
|
|
26
67
|
* defaults to "is a version of the routine's agent resolvable" via
|
package/dist/lib/sandbox.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* access to explicitly allowed paths.
|
|
8
8
|
*/
|
|
9
9
|
import type { JobConfig } from './scheduling/routines.js';
|
|
10
|
+
import type { AgentId } from './types.js';
|
|
10
11
|
/**
|
|
11
12
|
* Absolute path to this host's `gh` config directory (`hosts.yml` + `config.yml`),
|
|
12
13
|
* or null when the host has never run `gh auth login`. Prefer `GH_CONFIG_DIR` when
|
|
@@ -28,7 +29,9 @@ export declare function buildSpawnEnv(overlayHome: string, extraEnv?: Record<str
|
|
|
28
29
|
*/
|
|
29
30
|
export declare function getJobHomePath(name: string): string;
|
|
30
31
|
/** Create a fresh overlay HOME for a job, including agent config and allowed-dir symlinks. */
|
|
31
|
-
export declare function prepareJobHome(config: JobConfig): string;
|
|
32
|
+
export declare function prepareJobHome(config: JobConfig, version?: string): string;
|
|
33
|
+
/** Link only the selected harness login into the disposable routine HOME. */
|
|
34
|
+
export declare function linkVersionAuth(overlayHome: string, agent: AgentId, version?: string): void;
|
|
32
35
|
/**
|
|
33
36
|
* Link this host's `gh` config directory into the disposable overlay so
|
|
34
37
|
* `$HOME/.config/gh` resolves even when `GH_CONFIG_DIR` is unset. Mirrors
|
package/dist/lib/sandbox.js
CHANGED
|
@@ -12,6 +12,7 @@ import * as os from 'os';
|
|
|
12
12
|
import { getRoutinesDir, getUserAgentsDir } from './state.js';
|
|
13
13
|
import { safeJoin } from './paths.js';
|
|
14
14
|
import { createLink } from './platform/index.js';
|
|
15
|
+
import { getVersionHomePath } from './installations/versions.js';
|
|
15
16
|
function resolveRealHome() {
|
|
16
17
|
const home = os.homedir();
|
|
17
18
|
try {
|
|
@@ -132,7 +133,7 @@ export function getJobHomePath(name) {
|
|
|
132
133
|
return path.join(safeJoin(getRoutinesDir(), name), 'home');
|
|
133
134
|
}
|
|
134
135
|
/** Create a fresh overlay HOME for a job, including agent config and allowed-dir symlinks. */
|
|
135
|
-
export function prepareJobHome(config) {
|
|
136
|
+
export function prepareJobHome(config, version) {
|
|
136
137
|
const overlayHome = getJobHomePath(config.name);
|
|
137
138
|
cleanJobHome(config.name);
|
|
138
139
|
fs.mkdirSync(overlayHome, { recursive: true });
|
|
@@ -143,6 +144,7 @@ export function prepareJobHome(config) {
|
|
|
143
144
|
}
|
|
144
145
|
else if (config.agent === 'codex') {
|
|
145
146
|
generateCodexConfig(overlayHome, config);
|
|
147
|
+
linkVersionAuth(overlayHome, 'codex', version);
|
|
146
148
|
}
|
|
147
149
|
else if (config.agent === 'cursor') {
|
|
148
150
|
generateCursorConfig(overlayHome);
|
|
@@ -165,6 +167,33 @@ export function prepareJobHome(config) {
|
|
|
165
167
|
}
|
|
166
168
|
return overlayHome;
|
|
167
169
|
}
|
|
170
|
+
/** Link only the selected harness login into the disposable routine HOME. */
|
|
171
|
+
export function linkVersionAuth(overlayHome, agent, version) {
|
|
172
|
+
if (!version)
|
|
173
|
+
return;
|
|
174
|
+
const versionHome = getVersionHomePath(agent, version);
|
|
175
|
+
const pairs = agent === 'claude'
|
|
176
|
+
? [
|
|
177
|
+
[path.join(versionHome, '.claude', '.claude.json'), path.join(overlayHome, '.claude', '.claude.json')],
|
|
178
|
+
[path.join(versionHome, '.claude', '.credentials.json'), path.join(overlayHome, '.claude', '.credentials.json')],
|
|
179
|
+
]
|
|
180
|
+
: agent === 'codex'
|
|
181
|
+
? [[path.join(versionHome, '.codex', 'auth.json'), path.join(overlayHome, '.codex', 'auth.json')]]
|
|
182
|
+
: [];
|
|
183
|
+
for (const [source, target] of pairs) {
|
|
184
|
+
if (!fs.existsSync(source))
|
|
185
|
+
continue;
|
|
186
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
187
|
+
try {
|
|
188
|
+
fs.rmSync(target, { force: true });
|
|
189
|
+
}
|
|
190
|
+
catch { /* absent */ }
|
|
191
|
+
try {
|
|
192
|
+
createLink(source, target);
|
|
193
|
+
}
|
|
194
|
+
catch { /* the harness fails auth loudly if linking is unavailable */ }
|
|
195
|
+
}
|
|
196
|
+
}
|
|
168
197
|
/**
|
|
169
198
|
* Link this host's `gh` config directory into the disposable overlay so
|
|
170
199
|
* `$HOME/.config/gh` resolves even when `GH_CONFIG_DIR` is unset. Mirrors
|