@phnx-labs/agents-cli 1.22.100 → 1.22.101
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 +4 -0
- package/dist/commands/exec.js +2 -0
- package/dist/commands/status.js +3 -1
- package/dist/lib/drift-sync.js +5 -2
- package/dist/lib/exec.js +2 -0
- package/dist/lib/sync-status.d.ts +6 -0
- package/dist/lib/sync-status.js +11 -0
- package/dist/lib/system-run-sync.d.ts +11 -0
- package/dist/lib/system-run-sync.js +114 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.22.101
|
|
4
|
+
|
|
5
|
+
- **`agents sync status` names what drifted, and a merged system-layer skill or subagent reaches the next `agents run` on every box (PHNX-4056).** The status readout said `11 drifted` per install and nothing more, although `AgentVersionStatus.resources` already carried the rows; both human renderers (`agents sync status`, the drift summary `agents sync` prints) now list each drifted or missing resource as `drifted skills/artifacts` under its install line, with the differ's detail when it has one. And when `~/.agents/.system/skills` or `subagents` changed since the last launch of an (agent, version), `agents run` rewrites those resources into the version home through the registered writers before spawning, filtered by the active resource profile like the full sync, with the same stat-tier sentinel shape as the rules run-sync: on 2026-09-10 a merged skill change left 31 of 33 installed copies stale until the 4-hour umbrella autosync, because the shim's `--launch` path mirrors project resources only. Source: `cli/src/lib/system-run-sync.ts`, `cli/src/lib/sync-status.ts`, `cli/src/commands/status.ts`, `cli/src/lib/drift-sync.ts`, `cli/src/lib/exec.ts`, `cli/src/commands/exec.ts`.
|
|
6
|
+
|
|
3
7
|
## 1.22.100
|
|
4
8
|
|
|
5
9
|
- **The daemon's 20-minute auth-probe reuse works again on a box that has uninstalled a version (PHNX-4051).** `isCachedFleetAuthProbeFresh` required EVERY cached auth row to be under 20 minutes old, including rows left behind for versions that are no longer installed. Nothing re-probes those — the probe only walks homes that exist — so their timestamp froze and the check was permanently false: the 3-minute tick live-probed the rate-limited `/api/oauth/usage` for every account on every tick, re-arming the per-account 429 backoff and keeping the usage refresher parked (the RUSH-2998 failure the reuse window exists to prevent; observed on a worker holding rows dated Sep 2 / Sep 6 for uninstalled Claude versions). Freshness now considers only rows for currently installed `(agent, version)` homes and account slots, and the tick prunes the orphan rows when it writes the cache, so `agents view` and fleet status stop reporting a verdict for a version that is gone. `agents devices ping [--strict]` still forces a genuinely live probe. Source: `cli/src/lib/daemon-ticks.ts` (`installedAuthRows`, `isCachedFleetAuthProbeFresh`, `refreshLocalFleetAuthState`), `cli/src/lib/auth-health.ts` (`authTargetKey`, `enumerateLocalAuthInstalls`, `localAuthTargetKeys`, `writeFleetAuthRows`).
|
package/dist/commands/exec.js
CHANGED
|
@@ -26,6 +26,7 @@ import { spawnSync } from 'child_process';
|
|
|
26
26
|
import { randomUUID } from 'crypto';
|
|
27
27
|
import { isSessionTrackedAgent } from '../lib/session/types.js';
|
|
28
28
|
import { applyActiveRulesPresetAtRun } from '../lib/rules/run-sync.js';
|
|
29
|
+
import { applySystemResourcesAtRun } from '../lib/system-run-sync.js';
|
|
29
30
|
import { handleBroadcast } from './run-broadcast.js';
|
|
30
31
|
import { bootMark } from '../lib/boot-profile.js';
|
|
31
32
|
/** Validate a caller-supplied session id before it reaches tmux, paths, indexes, or remote dispatch. */
|
|
@@ -2714,6 +2715,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
2714
2715
|
// path itself; there's no version home to sync into.
|
|
2715
2716
|
if (defaultVersion) {
|
|
2716
2717
|
applyActiveRulesPresetAtRun(agent, defaultVersion, getVersionHomePath(agent, defaultVersion));
|
|
2718
|
+
applySystemResourcesAtRun(agent, defaultVersion, getVersionHomePath(agent, defaultVersion));
|
|
2717
2719
|
}
|
|
2718
2720
|
bootMark('rules-sync:done');
|
|
2719
2721
|
// Login preflight (advisory, warn + continue). On a local INTERACTIVE
|
package/dist/commands/status.js
CHANGED
|
@@ -13,7 +13,7 @@ import chalk from 'chalk';
|
|
|
13
13
|
import { AGENTS } from '../lib/agents.js';
|
|
14
14
|
import { setHelpSections } from '../lib/help.js';
|
|
15
15
|
import { addHostOption } from '../lib/hosts/option.js';
|
|
16
|
-
import { computeSyncStatus } from '../lib/sync-status.js';
|
|
16
|
+
import { computeSyncStatus, formatDriftRows } from '../lib/sync-status.js';
|
|
17
17
|
import { promptDriftSync } from '../lib/drift-sync.js';
|
|
18
18
|
import { resolveConfiguredModel } from '../lib/models.js';
|
|
19
19
|
import { resolveSurface } from './utils.js';
|
|
@@ -93,6 +93,8 @@ export function registerStatusCommand(syncCmd) {
|
|
|
93
93
|
+ (v.isDefault ? chalk.gray(' (default)') : '');
|
|
94
94
|
const pad = ' '.repeat(Math.max(1, 34 - plain.length));
|
|
95
95
|
console.log(` ${shown}${pad}${versionSummary(v)}`);
|
|
96
|
+
for (const line of formatDriftRows(v))
|
|
97
|
+
console.log(chalk.gray(` ${line}`));
|
|
96
98
|
}
|
|
97
99
|
if (status.totals.orphan > 0) {
|
|
98
100
|
console.log(chalk.gray(` (${status.totals.orphan} orphan${status.totals.orphan === 1 ? '' : 's'} — run \`agents prune cleanup\`)`));
|
package/dist/lib/drift-sync.js
CHANGED
|
@@ -24,7 +24,7 @@ import { pullRepo } from './git.js';
|
|
|
24
24
|
import { repairAfterSync, renderRepairAfterSync } from './reconcile-and-repair.js';
|
|
25
25
|
import { promptAgentVersionSelection } from './installations/versions.js';
|
|
26
26
|
import { isInteractiveTerminal, isPromptCancelled } from './format.js';
|
|
27
|
-
import { computeSyncStatus, } from './sync-status.js';
|
|
27
|
+
import { computeSyncStatus, formatDriftRows } from './sync-status.js';
|
|
28
28
|
const agentName = (id) => AGENTS[id]?.name ?? id;
|
|
29
29
|
/** "claude@2.1.170 2 drifted · 1 missing" for one version. */
|
|
30
30
|
function versionLine(v) {
|
|
@@ -42,8 +42,11 @@ function renderSummary(status, needing) {
|
|
|
42
42
|
if (status.system.behind > 0) {
|
|
43
43
|
console.log(` ${'.system repo'.padEnd(28)} ${chalk.yellow(`${status.system.behind} commit${status.system.behind === 1 ? '' : 's'} behind`)} ${chalk.gray('— pull recommended')}`);
|
|
44
44
|
}
|
|
45
|
-
for (const v of needing)
|
|
45
|
+
for (const v of needing) {
|
|
46
46
|
console.log(versionLine(v));
|
|
47
|
+
for (const line of formatDriftRows(v))
|
|
48
|
+
console.log(chalk.gray(` ${line}`));
|
|
49
|
+
}
|
|
47
50
|
if (status.totals.orphan > 0) {
|
|
48
51
|
console.log(chalk.gray(` (${status.totals.orphan} orphan${status.totals.orphan === 1 ? '' : 's'} — run \`agents prune cleanup\`)`));
|
|
49
52
|
}
|
package/dist/lib/exec.js
CHANGED
|
@@ -39,6 +39,7 @@ import { probeUnprivilegedUserns } from './linux-userns.js';
|
|
|
39
39
|
import { resolveClaudeSetupToken } from './claude-account-token.js';
|
|
40
40
|
import { applyAddDirs } from './add-dir.js';
|
|
41
41
|
import { applyActiveRulesPresetAtRun } from './rules/run-sync.js';
|
|
42
|
+
import { applySystemResourcesAtRun } from './system-run-sync.js';
|
|
42
43
|
import { resolveHarnessAdapter, stripForeignConfigDir } from './harness/index.js';
|
|
43
44
|
import { resolveConfigVersion } from './harness/exec-config-version.js';
|
|
44
45
|
import { getAccountInfo } from './agents.js';
|
|
@@ -2406,6 +2407,7 @@ export async function runWithFallback(options) {
|
|
|
2406
2407
|
const rulesVersion = version ?? resolveVersion(agent);
|
|
2407
2408
|
if (rulesVersion) {
|
|
2408
2409
|
applyActiveRulesPresetAtRun(agent, rulesVersion, getVersionHomePath(agent, rulesVersion));
|
|
2410
|
+
applySystemResourcesAtRun(agent, rulesVersion, getVersionHomePath(agent, rulesVersion));
|
|
2409
2411
|
}
|
|
2410
2412
|
// Record the entry we're about to attempt so the caller (audit log) sees the
|
|
2411
2413
|
// agent+version that actually ran, even after a rate-limit handoff.
|
|
@@ -115,6 +115,12 @@ export interface ResidualDrift {
|
|
|
115
115
|
export declare function verifyVersionConverged(agent: AgentId, version: string, cwd?: string): ResidualDrift | null;
|
|
116
116
|
/** One-line-per-resource description of residual drift, for the sync exit line. */
|
|
117
117
|
export declare function formatResidualDrift(residual: ResidualDrift[]): string[];
|
|
118
|
+
/**
|
|
119
|
+
* The rows behind a "N drifted" count, one line each, for the human renderers.
|
|
120
|
+
* A count alone cannot be acted on; the name says which skill or hook to look
|
|
121
|
+
* at, and `detail` says what differs when the differ knows.
|
|
122
|
+
*/
|
|
123
|
+
export declare function formatDriftRows(v: AgentVersionStatus): string[];
|
|
118
124
|
export interface SyncStatusOptions {
|
|
119
125
|
cwd?: string;
|
|
120
126
|
/** Restrict to specific agent ids; undefined = every supported agent. */
|
package/dist/lib/sync-status.js
CHANGED
|
@@ -54,6 +54,17 @@ export function formatResidualDrift(residual) {
|
|
|
54
54
|
}
|
|
55
55
|
return lines;
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* The rows behind a "N drifted" count, one line each, for the human renderers.
|
|
59
|
+
* A count alone cannot be acted on; the name says which skill or hook to look
|
|
60
|
+
* at, and `detail` says what differs when the differ knows.
|
|
61
|
+
*/
|
|
62
|
+
export function formatDriftRows(v) {
|
|
63
|
+
return v.resources
|
|
64
|
+
.filter((r) => r.status === 'drifted' || r.status === 'missing')
|
|
65
|
+
.sort((a, b) => a.kind.localeCompare(b.kind) || a.name.localeCompare(b.name))
|
|
66
|
+
.map((r) => `${r.status.padEnd(7)} ${r.kind}/${r.name}${r.detail ? ` (${r.detail})` : ''}`);
|
|
67
|
+
}
|
|
57
68
|
const STATUS_MAP = {
|
|
58
69
|
ok: 'synced',
|
|
59
70
|
diff: 'drifted',
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AgentId } from './types.js';
|
|
2
|
+
export interface SystemRunSyncResult {
|
|
3
|
+
/** Names rewritten per kind; empty when the sentinel matched (skip-fast). */
|
|
4
|
+
skills: string[];
|
|
5
|
+
subagents: string[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Refresh the system layer's skills and subagents into the version home when
|
|
9
|
+
* their source directories changed since the last run for (agent, version).
|
|
10
|
+
*/
|
|
11
|
+
export declare function applySystemResourcesAtRun(agent: AgentId, version: string, versionHome: string): SystemRunSyncResult;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System-layer skills and subagents refresh at `agents run` launch time.
|
|
3
|
+
*
|
|
4
|
+
* `agents sync <harness>@all system` rewrites every version home, but nothing
|
|
5
|
+
* runs it when the `.system` mirror moves: the shim's `--launch` path compiles
|
|
6
|
+
* project rules and mirrors project resources only, and the SessionStart
|
|
7
|
+
* autosync fires the umbrella at most once per 4 h. Rules already close that
|
|
8
|
+
* gap per run (`rules/run-sync.ts`); skills and subagents did not, so a merged
|
|
9
|
+
* system-layer skill change left 31 of 33 installed copies on the old text
|
|
10
|
+
* (PHNX-4056). This is the same shape as the rules run-sync: a small per-
|
|
11
|
+
* (agent, version) sentinel of source fingerprints, stat-tier comparison, and
|
|
12
|
+
* a write through the registered writers only when a source file changed.
|
|
13
|
+
*
|
|
14
|
+
* Scope: the system layer's `skills/` and `subagents/` directories. The
|
|
15
|
+
* writers resolve each name through the normal precedence (a user-layer
|
|
16
|
+
* shadow still wins), and the active resource profile filters both kinds the
|
|
17
|
+
* same way `syncResourcesToVersion` does. Never blocks a launch: any failure
|
|
18
|
+
* is swallowed and leaves the sentinel stale, so the next run retries.
|
|
19
|
+
*/
|
|
20
|
+
import * as fs from 'fs';
|
|
21
|
+
import * as path from 'path';
|
|
22
|
+
import { getCacheDir, getSystemSkillsDir, getSystemSubagentsDir } from './state.js';
|
|
23
|
+
import { getWriter } from './staleness/registry.js';
|
|
24
|
+
import { fingerprintDir, isDirStale } from './staleness/fingerprint.js';
|
|
25
|
+
import { filterNamesForActiveResourceProfile } from './resource-profiles.js';
|
|
26
|
+
/** ~/.agents/.cache/system-run-sync/ — regenerable; a lost sentinel costs one extra write. */
|
|
27
|
+
function sentinelPath(agent, version) {
|
|
28
|
+
const key = `${agent}@${version}`.replace(/[^a-zA-Z0-9@._-]/g, '_');
|
|
29
|
+
return path.join(getCacheDir(), 'system-run-sync', `${key}.json`);
|
|
30
|
+
}
|
|
31
|
+
function loadSentinel(agent, version) {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(fs.readFileSync(sentinelPath(agent, version), 'utf-8'));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function saveSentinel(agent, version, sentinel) {
|
|
40
|
+
const p = sentinelPath(agent, version);
|
|
41
|
+
try {
|
|
42
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
43
|
+
fs.writeFileSync(p, JSON.stringify(sentinel));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Best-effort — a failed write just means the next run redoes the copy.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Subdirectories of `dir` that carry `marker` (SKILL.md / AGENT.md). */
|
|
50
|
+
function namesWithMarker(dir, marker) {
|
|
51
|
+
const out = new Map();
|
|
52
|
+
let entries;
|
|
53
|
+
try {
|
|
54
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
for (const e of entries) {
|
|
60
|
+
if (!e.isDirectory() || e.name.startsWith('.'))
|
|
61
|
+
continue;
|
|
62
|
+
const src = path.join(dir, e.name, marker);
|
|
63
|
+
if (fs.existsSync(src))
|
|
64
|
+
out.set(e.name, src);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Refresh the system layer's skills and subagents into the version home when
|
|
70
|
+
* their source directories changed since the last run for (agent, version).
|
|
71
|
+
*/
|
|
72
|
+
export function applySystemResourcesAtRun(agent, version, versionHome) {
|
|
73
|
+
const result = { skills: [], subagents: [] };
|
|
74
|
+
const stored = loadSentinel(agent, version);
|
|
75
|
+
const next = { ...stored };
|
|
76
|
+
const kinds = [
|
|
77
|
+
{ kind: 'skills', dir: getSystemSkillsDir(), marker: 'SKILL.md' },
|
|
78
|
+
{ kind: 'subagents', dir: getSystemSubagentsDir(), marker: 'AGENT.md' },
|
|
79
|
+
];
|
|
80
|
+
for (const { kind, dir, marker } of kinds) {
|
|
81
|
+
if (!fs.existsSync(dir))
|
|
82
|
+
continue;
|
|
83
|
+
const prev = stored[kind];
|
|
84
|
+
if (prev && prev.dir === dir && !isDirStale(prev.dir, prev.files, dir))
|
|
85
|
+
continue;
|
|
86
|
+
const writer = getWriter(kind, agent);
|
|
87
|
+
if (!writer)
|
|
88
|
+
continue;
|
|
89
|
+
const sources = namesWithMarker(dir, marker);
|
|
90
|
+
// The active resource profile is the per-client scoping seam (a brand pins
|
|
91
|
+
// one); it filters every kind here exactly as `syncResourcesToVersion` does.
|
|
92
|
+
const names = filterNamesForActiveResourceProfile(kind, Array.from(sources.keys()), sources);
|
|
93
|
+
if (names.length === 0) {
|
|
94
|
+
next[kind] = { dir, files: fingerprintDir(dir) };
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
let written;
|
|
98
|
+
try {
|
|
99
|
+
written = writer.write({ version, versionHome, selection: names, cwd: '' });
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
continue; // leave the sentinel stale so the next run retries
|
|
103
|
+
}
|
|
104
|
+
result[kind] = written.synced;
|
|
105
|
+
// A per-name failure (an AGENT.md that does not parse) also leaves the
|
|
106
|
+
// sentinel stale: the next run retries instead of never seeing it again.
|
|
107
|
+
if (written.errors?.length)
|
|
108
|
+
continue;
|
|
109
|
+
next[kind] = { dir, files: fingerprintDir(dir) };
|
|
110
|
+
}
|
|
111
|
+
if (JSON.stringify(next) !== JSON.stringify(stored))
|
|
112
|
+
saveSentinel(agent, version, next);
|
|
113
|
+
return result;
|
|
114
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.22.
|
|
3
|
+
"version": "1.22.101",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|