@phnx-labs/agents-cli 1.22.14 → 1.22.16
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 +53 -0
- package/README.md +7 -5
- package/dist/bin/agents +0 -0
- package/dist/commands/cloud.js +4 -6
- package/dist/commands/exec.js +96 -11
- package/dist/commands/humans.d.ts +10 -0
- package/dist/commands/humans.js +91 -0
- package/dist/commands/packages.js +90 -22
- package/dist/commands/projects.d.ts +2 -5
- package/dist/commands/projects.js +92 -65
- package/dist/commands/resume.d.ts +17 -0
- package/dist/commands/resume.js +80 -0
- package/dist/commands/routines.js +53 -71
- package/dist/commands/sessions.d.ts +13 -4
- package/dist/commands/sessions.js +37 -19
- package/dist/commands/setup-watchdog.d.ts +3 -0
- package/dist/commands/setup-watchdog.js +54 -0
- package/dist/commands/setup.js +8 -1
- package/dist/commands/ssh.d.ts +11 -0
- package/dist/commands/ssh.js +18 -4
- package/dist/commands/watchdog.d.ts +3 -4
- package/dist/commands/watchdog.js +40 -28
- package/dist/index.js +3 -2
- package/dist/lib/auto-dispatch.d.ts +8 -10
- package/dist/lib/auto-dispatch.js +20 -42
- package/dist/lib/channels/send.d.ts +4 -1
- package/dist/lib/channels/send.js +15 -7
- package/dist/lib/devices/doctor-findings.d.ts +14 -0
- package/dist/lib/devices/doctor-findings.js +92 -51
- package/dist/lib/exec.d.ts +6 -2
- package/dist/lib/exec.js +22 -4
- package/dist/lib/hooks.d.ts +4 -2
- package/dist/lib/hooks.js +173 -23
- package/dist/lib/humans.d.ts +25 -0
- package/dist/lib/humans.js +65 -0
- package/dist/lib/memory.js +2 -1
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/migrate.d.ts +1 -1
- package/dist/lib/migrate.js +116 -7
- package/dist/lib/notify.js +6 -2
- package/dist/lib/permissions.js +12 -10
- package/dist/lib/project-import.d.ts +7 -46
- package/dist/lib/project-import.js +9 -108
- package/dist/lib/projects.d.ts +20 -3
- package/dist/lib/projects.js +36 -18
- package/dist/lib/registry.d.ts +1 -1
- package/dist/lib/registry.js +11 -0
- package/dist/lib/routine-activation.d.ts +14 -0
- package/dist/lib/routine-activation.js +78 -0
- package/dist/lib/routines-project.d.ts +1 -1
- package/dist/lib/routines-project.js +3 -9
- package/dist/lib/routines.d.ts +3 -1
- package/dist/lib/routines.js +32 -19
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/bundles.d.ts +11 -0
- package/dist/lib/secrets/bundles.js +122 -5
- package/dist/lib/session/actor-sidecar.d.ts +5 -2
- package/dist/lib/session/actor-sidecar.js +4 -2
- package/dist/lib/session/db.d.ts +2 -1
- package/dist/lib/session/db.js +19 -3
- package/dist/lib/session/types.d.ts +4 -0
- package/dist/lib/staleness/writers/sources.d.ts +6 -1
- package/dist/lib/staleness/writers/sources.js +93 -7
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +5 -0
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +23 -10
- package/dist/lib/types.d.ts +64 -1
- package/dist/lib/versions.js +48 -14
- package/dist/lib/watchdog/rotate.d.ts +1 -1
- package/dist/lib/watchdog/rotate.js +1 -1
- package/package.json +1 -1
- package/dist/lib/watchdog/routine.d.ts +0 -44
- package/dist/lib/watchdog/routine.js +0 -69
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getOwnerNotifyFromHumans } from '../humans.js';
|
|
1
2
|
import { registerBuiltinProviders } from './providers/index.js';
|
|
2
3
|
import { resolveTransport } from './resolve.js';
|
|
3
4
|
const OWNER_ALIAS = 'owner';
|
|
@@ -16,8 +17,14 @@ export function composeSendText(text, urls) {
|
|
|
16
17
|
return body;
|
|
17
18
|
return body ? `${body}\n${extra.join('\n')}` : extra.join('\n');
|
|
18
19
|
}
|
|
19
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Read the owner destination; humans.yaml is the primary source, agents.yaml
|
|
22
|
+
* notify.owner is the fallback. Returns null when neither is set.
|
|
23
|
+
*/
|
|
20
24
|
export function readOwnerDest(meta) {
|
|
25
|
+
const humansOwner = getOwnerNotifyFromHumans();
|
|
26
|
+
if (humansOwner)
|
|
27
|
+
return humansOwner;
|
|
21
28
|
const owner = meta.notify?.owner;
|
|
22
29
|
const channel = owner?.channel?.trim();
|
|
23
30
|
const to = owner?.to?.trim();
|
|
@@ -48,15 +55,16 @@ export function resolveSendEnvelope(input, meta) {
|
|
|
48
55
|
};
|
|
49
56
|
}
|
|
50
57
|
// Owner defaults fill only missing fields (and expand the bare "owner" alias).
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
const ownerCfg = meta.notify?.owner;
|
|
54
|
-
const ownerChannel = ownerCfg?.channel?.trim() || '';
|
|
55
|
-
const ownerTo = ownerCfg?.to?.trim() || '';
|
|
58
|
+
// humans.yaml is the primary source; notify.owner in agents.yaml is the
|
|
59
|
+
// fallback for the migration window. Explicit --channel/--to always win.
|
|
56
60
|
let channel = (input.channel ?? '').trim();
|
|
57
61
|
let to = (input.to ?? '').trim();
|
|
58
62
|
const usedOwnerAlias = isOwnerAlias(to);
|
|
59
63
|
if (input.ownerMode || usedOwnerAlias) {
|
|
64
|
+
const humansOwner = getOwnerNotifyFromHumans();
|
|
65
|
+
const fallbackOwner = meta.notify?.owner;
|
|
66
|
+
const ownerChannel = humansOwner?.channel ?? fallbackOwner?.channel?.trim() ?? '';
|
|
67
|
+
const ownerTo = humansOwner?.to ?? fallbackOwner?.to?.trim() ?? '';
|
|
60
68
|
if (!channel)
|
|
61
69
|
channel = ownerChannel;
|
|
62
70
|
if (!to || usedOwnerAlias)
|
|
@@ -64,7 +72,7 @@ export function resolveSendEnvelope(input, meta) {
|
|
|
64
72
|
}
|
|
65
73
|
if (!channel || !to) {
|
|
66
74
|
const hint = input.ownerMode || usedOwnerAlias
|
|
67
|
-
? 'Set notify.
|
|
75
|
+
? 'Set notify.{channel,to} in humans.yaml (or notify.owner in agents.yaml), or pass --channel and --to explicitly.'
|
|
68
76
|
: 'Need --channel and --to (or --to owner with notify.owner configured). ' +
|
|
69
77
|
'Example: agents send --channel desktop --to local --text "hi"';
|
|
70
78
|
return { ok: false, error: hint };
|
|
@@ -8,7 +8,21 @@ import type { FleetDivergence, FleetVersionSignIn } from './fleet-divergence.js'
|
|
|
8
8
|
export type FindingSeverity = 'critical' | 'warning';
|
|
9
9
|
/** A machine-stable class for a finding — drives {@link remediationFor} and lets
|
|
10
10
|
* the JSON consumer group by kind. */
|
|
11
|
+
/** Every finding class. Severity is NOT annotated here — {@link FINDING_SEVERITY}
|
|
12
|
+
* below owns it, and a second copy in these comments is a fourth place to drift. */
|
|
11
13
|
export declare const ALL_FINDING_KINDS: readonly ["logged-out", "logout-unprovable", "missing-hook", "missing-plugin", "unwired-hook", "cli-missing", "missing-resource", "content-drift", "never-synced", "stale", "repo-behind", "repo-drift", "fleet-resource-gap", "host-cli-missing", "host-cli-invalid", "version-skew", "orphan", "duplicate-hook", "duplicate-hook-drift", "rc-secret-export", "exec-policy", "stale-cli"];
|
|
14
|
+
/**
|
|
15
|
+
* The severity each kind is emitted with - the SINGLE source of truth, read by
|
|
16
|
+
* the builders below and asserted against both prose rubrics by
|
|
17
|
+
* `doctor-findings.test.ts`.
|
|
18
|
+
*
|
|
19
|
+
* It exists because severity drifted: the rubrics claimed `never-synced` and
|
|
20
|
+
* `duplicate-hook-drift` were critical for three days after RUSH-2162 downgraded
|
|
21
|
+
* them, and the exhaustiveness test missed it because it only checked that a kind
|
|
22
|
+
* was *named* in the rubric, not which bucket it sat in. Change a severity HERE
|
|
23
|
+
* and the test names the docs that must move with it.
|
|
24
|
+
*/
|
|
25
|
+
export declare const FINDING_SEVERITY: Record<FindingKind, FindingSeverity>;
|
|
12
26
|
/** A machine-stable class for a finding. Derived from the runtime list above so
|
|
13
27
|
* the rubric test can enumerate every kind. */
|
|
14
28
|
export type FindingKind = typeof ALL_FINDING_KINDS[number];
|
|
@@ -18,13 +18,14 @@
|
|
|
18
18
|
* with. Keep this list exhaustive; a kind missing from it is a doc that lies.
|
|
19
19
|
* CRITICAL — logged-out (provable) · missing-hook · missing-plugin ·
|
|
20
20
|
* unwired-hook (a hook on disk that settings.json never fires) ·
|
|
21
|
-
*
|
|
22
|
-
* duplicate-hook-drift (copies that DIFFER) · cli-missing.
|
|
21
|
+
* cli-missing.
|
|
23
22
|
* WARNING — logout-unprovable (hedged) · missing-resource · content-drift ·
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
23
|
+
* never-synced · stale · repo-behind · repo-drift · version-skew ·
|
|
24
|
+
* fleet-resource-gap · orphan · duplicate-hook ·
|
|
25
|
+
* duplicate-hook-drift · host-cli-missing · host-cli-invalid ·
|
|
26
|
+
* rc-secret-export · exec-policy · stale-cli.
|
|
27
|
+
* (RUSH-2162 moved never-synced and duplicate-hook-drift to WARNING: both are
|
|
28
|
+
* stale-sync states one `agents sync` resolves, not "needs you now".)
|
|
28
29
|
*
|
|
29
30
|
* This module is pure: it maps already-collected signals (drift rows, orphan
|
|
30
31
|
* rows, repo-behind markers, per-version resource diffs, cross-device divergence,
|
|
@@ -70,31 +71,71 @@ function loginShape(agent) {
|
|
|
70
71
|
}
|
|
71
72
|
/** A machine-stable class for a finding — drives {@link remediationFor} and lets
|
|
72
73
|
* the JSON consumer group by kind. */
|
|
74
|
+
/** Every finding class. Severity is NOT annotated here — {@link FINDING_SEVERITY}
|
|
75
|
+
* below owns it, and a second copy in these comments is a fourth place to drift. */
|
|
73
76
|
export const ALL_FINDING_KINDS = [
|
|
74
|
-
'logged-out', // provable per-version logout
|
|
75
|
-
'logout-unprovable', // credential absent but not provable
|
|
76
|
-
'missing-hook', // a declared hook absent from a version home
|
|
77
|
-
'missing-plugin', // a declared plugin absent from a version home
|
|
78
|
-
'unwired-hook', // hook present on disk but not wired into settings.json
|
|
79
|
-
'cli-missing', // a managed agent whose binary won't resolve
|
|
80
|
-
'missing-resource', // a missing command/skill/rule/mcp/permission/subagent
|
|
81
|
-
'content-drift', // a resource diverged from source
|
|
82
|
-
'never-synced', // installed but never synced
|
|
83
|
-
//
|
|
84
|
-
'
|
|
85
|
-
'repo-
|
|
86
|
-
'
|
|
87
|
-
'
|
|
88
|
-
'host-cli-
|
|
89
|
-
'
|
|
90
|
-
'
|
|
91
|
-
'
|
|
92
|
-
'duplicate-hook', //
|
|
93
|
-
'
|
|
94
|
-
'
|
|
95
|
-
'exec-policy', // Windows execution policy blocks agents.ps1 (WARNING)
|
|
77
|
+
'logged-out', // provable per-version logout
|
|
78
|
+
'logout-unprovable', // credential absent but not provable
|
|
79
|
+
'missing-hook', // a declared hook absent from a version home
|
|
80
|
+
'missing-plugin', // a declared plugin absent from a version home
|
|
81
|
+
'unwired-hook', // hook present on disk but not wired into settings.json
|
|
82
|
+
'cli-missing', // a managed agent whose binary won't resolve
|
|
83
|
+
'missing-resource', // a missing command/skill/rule/mcp/permission/subagent
|
|
84
|
+
'content-drift', // a resource diverged from source
|
|
85
|
+
'never-synced', // installed but never synced
|
|
86
|
+
'stale', // sources changed since last sync
|
|
87
|
+
'repo-behind', // a config repo behind origin
|
|
88
|
+
'repo-drift', // a config repo diverged from the fleet baseline
|
|
89
|
+
'fleet-resource-gap', // a resource in another box's central repos, absent here
|
|
90
|
+
'host-cli-missing', // a declared host CLI not installed on this box
|
|
91
|
+
'host-cli-invalid', // a host-CLI manifest that failed to parse
|
|
92
|
+
'version-skew', // an agent version present elsewhere, absent here
|
|
93
|
+
'orphan', // orphan resources in a version home
|
|
94
|
+
'duplicate-hook', // one hook materialized in several version homes, byte-identical
|
|
95
|
+
'duplicate-hook-drift', // …with differing content, so a stale copy can disagree
|
|
96
|
+
'rc-secret-export', // credential-shaped export in a shell rc file
|
|
97
|
+
'exec-policy', // Windows execution policy blocks agents.ps1
|
|
96
98
|
'stale-cli',
|
|
97
99
|
];
|
|
100
|
+
/**
|
|
101
|
+
* The severity each kind is emitted with - the SINGLE source of truth, read by
|
|
102
|
+
* the builders below and asserted against both prose rubrics by
|
|
103
|
+
* `doctor-findings.test.ts`.
|
|
104
|
+
*
|
|
105
|
+
* It exists because severity drifted: the rubrics claimed `never-synced` and
|
|
106
|
+
* `duplicate-hook-drift` were critical for three days after RUSH-2162 downgraded
|
|
107
|
+
* them, and the exhaustiveness test missed it because it only checked that a kind
|
|
108
|
+
* was *named* in the rubric, not which bucket it sat in. Change a severity HERE
|
|
109
|
+
* and the test names the docs that must move with it.
|
|
110
|
+
*/
|
|
111
|
+
export const FINDING_SEVERITY = {
|
|
112
|
+
// Needs you now: the harness cannot do its job until this is fixed.
|
|
113
|
+
'logged-out': 'critical',
|
|
114
|
+
'missing-hook': 'critical',
|
|
115
|
+
'missing-plugin': 'critical',
|
|
116
|
+
'unwired-hook': 'critical',
|
|
117
|
+
'cli-missing': 'critical',
|
|
118
|
+
// Everything else is resolvable by a routine sync/cleanup and does not block
|
|
119
|
+
// the harness right now. RUSH-2162 moved never-synced and duplicate-hook-drift
|
|
120
|
+
// here: both are stale-sync states that one `agents sync` resolves.
|
|
121
|
+
'logout-unprovable': 'warning',
|
|
122
|
+
'missing-resource': 'warning',
|
|
123
|
+
'content-drift': 'warning',
|
|
124
|
+
'never-synced': 'warning',
|
|
125
|
+
'stale': 'warning',
|
|
126
|
+
'repo-behind': 'warning',
|
|
127
|
+
'repo-drift': 'warning',
|
|
128
|
+
'fleet-resource-gap': 'warning',
|
|
129
|
+
'version-skew': 'warning',
|
|
130
|
+
'orphan': 'warning',
|
|
131
|
+
'duplicate-hook': 'warning',
|
|
132
|
+
'duplicate-hook-drift': 'warning',
|
|
133
|
+
'host-cli-missing': 'warning',
|
|
134
|
+
'host-cli-invalid': 'warning',
|
|
135
|
+
'rc-secret-export': 'warning',
|
|
136
|
+
'exec-policy': 'warning',
|
|
137
|
+
'stale-cli': 'warning',
|
|
138
|
+
};
|
|
98
139
|
function agentName(agent) {
|
|
99
140
|
return AGENT_NAMES[agent] || agent;
|
|
100
141
|
}
|
|
@@ -241,7 +282,7 @@ export function buildLocalFindings(input) {
|
|
|
241
282
|
// cli-missing (managed agent, binary broken) — critical.
|
|
242
283
|
for (const agent of input.cliMissing ?? []) {
|
|
243
284
|
out.push(finding({
|
|
244
|
-
severity: '
|
|
285
|
+
severity: FINDING_SEVERITY['cli-missing'], kind: 'cli-missing', device, agent,
|
|
245
286
|
message: `${agentName(agent)} binary not found`,
|
|
246
287
|
}));
|
|
247
288
|
}
|
|
@@ -254,20 +295,20 @@ export function buildLocalFindings(input) {
|
|
|
254
295
|
if (w?.supported) {
|
|
255
296
|
if (w.settingsMissing) {
|
|
256
297
|
out.push(finding({
|
|
257
|
-
severity: '
|
|
298
|
+
severity: FINDING_SEVERITY['unwired-hook'], kind: 'unwired-hook', device, agent, version,
|
|
258
299
|
message: `settings.json missing — ${w.expected ?? 0} declared hook${(w.expected ?? 0) === 1 ? '' : 's'} never fire`,
|
|
259
300
|
}));
|
|
260
301
|
}
|
|
261
302
|
else if (w.settingsUnparseable) {
|
|
262
303
|
out.push(finding({
|
|
263
|
-
severity: '
|
|
304
|
+
severity: FINDING_SEVERITY['unwired-hook'], kind: 'unwired-hook', device, agent, version,
|
|
264
305
|
message: `settings.json unparseable — hook wiring can't be verified`,
|
|
265
306
|
}));
|
|
266
307
|
}
|
|
267
308
|
else {
|
|
268
309
|
for (const u of w.unwired) {
|
|
269
310
|
out.push(finding({
|
|
270
|
-
severity: '
|
|
311
|
+
severity: FINDING_SEVERITY['unwired-hook'], kind: 'unwired-hook', device, agent, version,
|
|
271
312
|
message: `hook '${u.name}' present on disk but not wired into settings.json`,
|
|
272
313
|
}));
|
|
273
314
|
}
|
|
@@ -317,17 +358,17 @@ export function buildLocalFindings(input) {
|
|
|
317
358
|
missingPlugins.length ? `${missingPlugins.length} plugin${missingPlugins.length === 1 ? '' : 's'}` : '',
|
|
318
359
|
].filter(Boolean).join(', ');
|
|
319
360
|
out.push(finding({
|
|
320
|
-
severity: '
|
|
361
|
+
severity: FINDING_SEVERITY['never-synced'], kind: 'never-synced', device, agent, version,
|
|
321
362
|
message: `never synced — ${total} resource${total === 1 ? '' : 's'}${breakdown ? ` (incl. ${breakdown})` : ''} not installed`,
|
|
322
363
|
}));
|
|
323
364
|
}
|
|
324
365
|
}
|
|
325
366
|
else {
|
|
326
367
|
// Synced-but-drifted: one line per kind of gap on this version.
|
|
327
|
-
emitGroup(out, missingHooks, '
|
|
328
|
-
emitGroup(out, missingPlugins, '
|
|
329
|
-
emitGroup(out, missingOther, '
|
|
330
|
-
emitGroup(out, drifted, '
|
|
368
|
+
emitGroup(out, missingHooks, FINDING_SEVERITY['missing-hook'], 'missing-hook', device, agent, version, 'hook', 'missing');
|
|
369
|
+
emitGroup(out, missingPlugins, FINDING_SEVERITY['missing-plugin'], 'missing-plugin', device, agent, version, 'plugin', 'missing');
|
|
370
|
+
emitGroup(out, missingOther, FINDING_SEVERITY['missing-resource'], 'missing-resource', device, agent, version, 'resource', 'missing');
|
|
371
|
+
emitGroup(out, drifted, FINDING_SEVERITY['content-drift'], 'content-drift', device, agent, version, 'resource', 'drifted');
|
|
331
372
|
if (missingHooks.length + missingPlugins.length + missingOther.length + drifted.length > 0) {
|
|
332
373
|
detailedVersions.add(`${agent}@${version}`);
|
|
333
374
|
}
|
|
@@ -343,7 +384,7 @@ export function buildLocalFindings(input) {
|
|
|
343
384
|
if (detailedVersions.has(`${row.agent}@${row.version}`))
|
|
344
385
|
continue;
|
|
345
386
|
out.push(finding({
|
|
346
|
-
severity: '
|
|
387
|
+
severity: FINDING_SEVERITY['stale'], kind: 'stale', device, agent: row.agent, version: row.version,
|
|
347
388
|
message: 'sources changed since last sync',
|
|
348
389
|
}));
|
|
349
390
|
}
|
|
@@ -355,7 +396,7 @@ export function buildLocalFindings(input) {
|
|
|
355
396
|
Object.values(rep.kinds).some((rows) => rows.some((r) => r.status === 'missing')));
|
|
356
397
|
if (!hadCritical) {
|
|
357
398
|
out.push(finding({
|
|
358
|
-
severity: '
|
|
399
|
+
severity: FINDING_SEVERITY['never-synced'], kind: 'never-synced', device, agent: row.agent, version: row.version,
|
|
359
400
|
message: 'installed but never synced',
|
|
360
401
|
}));
|
|
361
402
|
}
|
|
@@ -369,7 +410,7 @@ export function buildLocalFindings(input) {
|
|
|
369
410
|
const stales = input.syncRows.filter((r) => r.status === 'stale').length;
|
|
370
411
|
const staleNote = stales > 0 ? ` → stales ${stales} version${stales === 1 ? '' : 's'}` : '';
|
|
371
412
|
out.push(finding({
|
|
372
|
-
severity: '
|
|
413
|
+
severity: FINDING_SEVERITY['repo-behind'], kind: 'repo-behind', device, version: m.alias,
|
|
373
414
|
message: `${m.behind} behind ${m.branch}${staleNote}`,
|
|
374
415
|
}));
|
|
375
416
|
}
|
|
@@ -383,7 +424,7 @@ export function buildLocalFindings(input) {
|
|
|
383
424
|
const missingClis = (input.hostClis?.statuses ?? []).filter((c) => !c.installed).map((c) => c.name);
|
|
384
425
|
if (missingClis.length > 0) {
|
|
385
426
|
out.push({
|
|
386
|
-
severity: '
|
|
427
|
+
severity: FINDING_SEVERITY['host-cli-missing'], kind: 'host-cli-missing', device,
|
|
387
428
|
message: missingClis.length === 1
|
|
388
429
|
? `host CLI '${missingClis[0]}' declared but not installed`
|
|
389
430
|
: `${missingClis.length} declared host CLIs not installed (${missingClis.slice(0, 2).join(', ')}${missingClis.length > 2 ? ', …' : ''})`,
|
|
@@ -399,7 +440,7 @@ export function buildLocalFindings(input) {
|
|
|
399
440
|
// row per bad file, since each needs its own edit.
|
|
400
441
|
for (const e of input.hostClis?.errors ?? []) {
|
|
401
442
|
out.push(finding({
|
|
402
|
-
severity: '
|
|
443
|
+
severity: FINDING_SEVERITY['host-cli-invalid'], kind: 'host-cli-invalid', device,
|
|
403
444
|
message: `host-CLI manifest ${e.file} could not be read: ${e.reason}`,
|
|
404
445
|
}));
|
|
405
446
|
}
|
|
@@ -432,7 +473,7 @@ function orphanFinding(device, rows) {
|
|
|
432
473
|
? `${affected[0].agent}@${affected[0].version}`
|
|
433
474
|
: `${affected.length} versions`;
|
|
434
475
|
return [finding({
|
|
435
|
-
severity: '
|
|
476
|
+
severity: FINDING_SEVERITY['orphan'], kind: 'orphan', device,
|
|
436
477
|
message: `${total} orphaned resource${total === 1 ? '' : 's'} on ${where} (cleanup only)`,
|
|
437
478
|
})];
|
|
438
479
|
}
|
|
@@ -472,7 +513,7 @@ function duplicateHookFindings(device, dups) {
|
|
|
472
513
|
// stale/sync drift, not a missing hook. Resolvable by one sync; a WARNING,
|
|
473
514
|
// not a "needs you now" critical. (A genuinely MISSING hook stays critical
|
|
474
515
|
// via the missing-hook path.)
|
|
475
|
-
severity: '
|
|
516
|
+
severity: FINDING_SEVERITY[drift ? 'duplicate-hook-drift' : 'duplicate-hook'],
|
|
476
517
|
kind: drift ? 'duplicate-hook-drift' : 'duplicate-hook',
|
|
477
518
|
device, agent, versions, message,
|
|
478
519
|
remediation: `agents sync ${agent}@all --yes`,
|
|
@@ -516,7 +557,7 @@ function rcSecretFindings(device, rc) {
|
|
|
516
557
|
? 'agents secrets add, then delete the rc line'
|
|
517
558
|
: `agents secrets add once per export (${n}), then delete each rc line`;
|
|
518
559
|
out.push({
|
|
519
|
-
severity: '
|
|
560
|
+
severity: FINDING_SEVERITY['rc-secret-export'], kind: 'rc-secret-export', device,
|
|
520
561
|
message: `${what} (${examples}) — readable by any same-user process`,
|
|
521
562
|
remediation,
|
|
522
563
|
});
|
|
@@ -537,7 +578,7 @@ function execPolicyFinding(device, execPolicy) {
|
|
|
537
578
|
if (!blocksLocalScripts(execPolicy.policy))
|
|
538
579
|
return null;
|
|
539
580
|
return finding({
|
|
540
|
-
severity: '
|
|
581
|
+
severity: FINDING_SEVERITY['exec-policy'], kind: 'exec-policy', device,
|
|
541
582
|
message: `PowerShell execution policy is ${execPolicy.policy} — it blocks the generated agents.ps1 launcher (agents.cmd still works)`,
|
|
542
583
|
});
|
|
543
584
|
}
|
|
@@ -630,14 +671,14 @@ export function signInToFindings(device, signIn) {
|
|
|
630
671
|
continue;
|
|
631
672
|
if (row.provable) {
|
|
632
673
|
out.push(finding({
|
|
633
|
-
severity: '
|
|
674
|
+
severity: FINDING_SEVERITY['logged-out'], kind: 'logged-out', device, agent, version: row.version,
|
|
634
675
|
account: row.account ?? null,
|
|
635
676
|
message: 'logged out — no account signed in',
|
|
636
677
|
}));
|
|
637
678
|
}
|
|
638
679
|
else {
|
|
639
680
|
out.push(finding({
|
|
640
|
-
severity: '
|
|
681
|
+
severity: FINDING_SEVERITY['logout-unprovable'], kind: 'logout-unprovable', device, agent, version: row.version,
|
|
641
682
|
account: row.account ?? null,
|
|
642
683
|
message: 'could not verify sign-in',
|
|
643
684
|
}));
|
|
@@ -662,14 +703,14 @@ export function fleetDivergenceToFindings(divergences, baseline) {
|
|
|
662
703
|
case 'agent-version-missing-remote':
|
|
663
704
|
case 'agent-version-missing-local':
|
|
664
705
|
out.push(finding({
|
|
665
|
-
severity: '
|
|
706
|
+
severity: FINDING_SEVERITY['version-skew'], kind: 'version-skew', device: laggingDevice,
|
|
666
707
|
agent: d.category, version: d.name,
|
|
667
708
|
message: 'not installed (present elsewhere in the fleet)',
|
|
668
709
|
}));
|
|
669
710
|
break;
|
|
670
711
|
case 'repo-drift':
|
|
671
712
|
out.push(finding({
|
|
672
|
-
severity: '
|
|
713
|
+
severity: FINDING_SEVERITY['repo-drift'], kind: 'repo-drift', device: laggingDevice,
|
|
673
714
|
// `category` is the repo ('agents' | 'system'); carry it as the alias
|
|
674
715
|
// `agents repo pull` expects — ~/.agents is the `user` repo.
|
|
675
716
|
version: d.category === 'system' ? 'system' : 'user',
|
|
@@ -679,7 +720,7 @@ export function fleetDivergenceToFindings(divergences, baseline) {
|
|
|
679
720
|
case 'resource-missing-remote':
|
|
680
721
|
case 'resource-missing-local':
|
|
681
722
|
out.push(finding({
|
|
682
|
-
severity: '
|
|
723
|
+
severity: FINDING_SEVERITY['fleet-resource-gap'], kind: 'fleet-resource-gap', device: laggingDevice,
|
|
683
724
|
message: `${d.category.replace(/s$/, '')} '${d.name}' missing (present elsewhere)`,
|
|
684
725
|
}));
|
|
685
726
|
break;
|
package/dist/lib/exec.d.ts
CHANGED
|
@@ -254,10 +254,14 @@ export interface AgentCommandTemplate {
|
|
|
254
254
|
* { subcommand } — replace the headless base subcommand with `<subcommand> <id>`
|
|
255
255
|
* (codex: `codex exec` -> `codex exec resume <id>`)
|
|
256
256
|
*/
|
|
257
|
-
resume?: {
|
|
257
|
+
resume?: ({
|
|
258
258
|
flag: string;
|
|
259
|
+
interactiveFlag?: string;
|
|
260
|
+
headlessFlag?: string;
|
|
259
261
|
} | {
|
|
260
262
|
subcommand: string;
|
|
263
|
+
}) & {
|
|
264
|
+
since?: string;
|
|
261
265
|
};
|
|
262
266
|
}
|
|
263
267
|
/**
|
|
@@ -272,7 +276,7 @@ export declare const AGENT_COMMANDS: Record<AgentId, AgentCommandTemplate>;
|
|
|
272
276
|
* command template's `resume` field — the single source of truth. Agents that
|
|
273
277
|
* return false resume via the universal Tier-2 `/continue` replay instead.
|
|
274
278
|
*/
|
|
275
|
-
export declare function nativeResume(agent: AgentId): boolean;
|
|
279
|
+
export declare function nativeResume(agent: AgentId, version?: string): boolean;
|
|
276
280
|
/**
|
|
277
281
|
* Build the `-c` value that adds `dir` to codex's workspace-write writable
|
|
278
282
|
* roots. Codex parses the value as TOML, so it's a single-element TOML array of
|
package/dist/lib/exec.js
CHANGED
|
@@ -11,7 +11,7 @@ import * as path from 'path';
|
|
|
11
11
|
import { ALL_MODES } from './types.js';
|
|
12
12
|
import { AGENTS } from './agents.js';
|
|
13
13
|
import { parseTimeout } from './routines.js';
|
|
14
|
-
import { getBinaryPath, getVersionHomePath, isVersionInstalled, resolveVersion } from './versions.js';
|
|
14
|
+
import { compareVersions, getBinaryPath, getVersionHomePath, isVersionInstalled, resolveVersion } from './versions.js';
|
|
15
15
|
import { resolveModel, buildReasoningFlags } from './models.js';
|
|
16
16
|
import { isTierToken, resolveTier } from './model-tiers.js';
|
|
17
17
|
import { createTimer, redactPrompt, redactArgs } from './events.js';
|
|
@@ -385,6 +385,12 @@ export function buildExecEnv(options) {
|
|
|
385
385
|
result.AGENTS_PARENT_SESSION_ID = spawnerSessionId;
|
|
386
386
|
}
|
|
387
387
|
result.AGENTS_RUNTIME = resolveInteractive(options) ? 'terminal' : 'headless';
|
|
388
|
+
// Durable SessionStart metadata. The hook joins these launch facts to the
|
|
389
|
+
// harness-provided real session id and writes them under the shared history
|
|
390
|
+
// directory, so a later resume can restore the permission boundary without
|
|
391
|
+
// re-parsing harness-specific transcripts.
|
|
392
|
+
result.AGENTS_RUN_MODE = resolveHeadlessMode(options.agent, normalizeMode(options.mode), resolveInteractive(options), options.modeWarningContext, options.modeWarningState);
|
|
393
|
+
result.AGENTS_HISTORY_DIR = getHistoryDir();
|
|
388
394
|
// So activity / feed posts stamp the right harness without re-detecting.
|
|
389
395
|
if (options.agent) {
|
|
390
396
|
result.AGENTS_AGENT_NAME = options.agent;
|
|
@@ -466,6 +472,7 @@ export const AGENT_COMMANDS = {
|
|
|
466
472
|
},
|
|
467
473
|
jsonFlags: ['--output-format', 'stream-json'],
|
|
468
474
|
modelFlag: '--model',
|
|
475
|
+
resume: { flag: '--resume', since: '2026.7.23' },
|
|
469
476
|
},
|
|
470
477
|
opencode: {
|
|
471
478
|
base: ['opencode', 'run'],
|
|
@@ -589,6 +596,7 @@ export const AGENT_COMMANDS = {
|
|
|
589
596
|
},
|
|
590
597
|
jsonFlags: ['--output-format', 'streaming-json'],
|
|
591
598
|
modelFlag: '--model',
|
|
599
|
+
resume: { flag: '--resume', since: '0.2.91' },
|
|
592
600
|
},
|
|
593
601
|
kimi: {
|
|
594
602
|
base: ['kimi'],
|
|
@@ -601,6 +609,7 @@ export const AGENT_COMMANDS = {
|
|
|
601
609
|
},
|
|
602
610
|
jsonFlags: ['--output-format', 'stream-json'],
|
|
603
611
|
modelFlag: '--model',
|
|
612
|
+
resume: { flag: '--session', since: '0.19.2' },
|
|
604
613
|
},
|
|
605
614
|
// Factory AI Droid (`droid exec` for headless, `droid` for TUI). Flags from
|
|
606
615
|
// docs.factory.ai CLI reference: prompt is positional; --auto low|medium|high
|
|
@@ -618,6 +627,7 @@ export const AGENT_COMMANDS = {
|
|
|
618
627
|
},
|
|
619
628
|
jsonFlags: ['-o', 'stream-json'],
|
|
620
629
|
modelFlag: '-m',
|
|
630
|
+
resume: { flag: '--resume', headlessFlag: '--session-id', since: '0.186.0' },
|
|
621
631
|
},
|
|
622
632
|
hermes: {
|
|
623
633
|
base: ['hermes', 'chat'],
|
|
@@ -633,8 +643,13 @@ export const AGENT_COMMANDS = {
|
|
|
633
643
|
* command template's `resume` field — the single source of truth. Agents that
|
|
634
644
|
* return false resume via the universal Tier-2 `/continue` replay instead.
|
|
635
645
|
*/
|
|
636
|
-
export function nativeResume(agent) {
|
|
637
|
-
|
|
646
|
+
export function nativeResume(agent, version) {
|
|
647
|
+
const resume = AGENT_COMMANDS[agent]?.resume;
|
|
648
|
+
if (!resume)
|
|
649
|
+
return false;
|
|
650
|
+
if (!resume.since)
|
|
651
|
+
return true;
|
|
652
|
+
return !!version && compareVersions(version, resume.since) >= 0;
|
|
638
653
|
}
|
|
639
654
|
/**
|
|
640
655
|
* Build the `-c` value that adds `dir` to codex's workspace-write writable
|
|
@@ -822,7 +837,10 @@ export function buildExecCommand(options) {
|
|
|
822
837
|
// `resume`, the legacy claude-only `--session-id` CREATES a session with that id.
|
|
823
838
|
if (options.resume && options.sessionId && resumeSpec) {
|
|
824
839
|
if ('flag' in resumeSpec) {
|
|
825
|
-
|
|
840
|
+
const flag = interactive
|
|
841
|
+
? (resumeSpec.interactiveFlag ?? resumeSpec.flag)
|
|
842
|
+
: (resumeSpec.headlessFlag ?? resumeSpec.flag);
|
|
843
|
+
cmd.push(flag, options.sessionId);
|
|
826
844
|
}
|
|
827
845
|
else {
|
|
828
846
|
cmd.push(options.sessionId);
|
package/dist/lib/hooks.d.ts
CHANGED
|
@@ -58,8 +58,10 @@ export interface DuplicateVersionHook {
|
|
|
58
58
|
export declare function inspectDuplicateVersionHooks(cwd?: string): DuplicateVersionHook[];
|
|
59
59
|
/**
|
|
60
60
|
* List hook entries in a single directory, grouping script + data files by
|
|
61
|
-
* basename.
|
|
62
|
-
*
|
|
61
|
+
* basename. Also discovers scripts in one-level group subdirs
|
|
62
|
+
* (hooks/session-starts/). Exported so doctor-diff can reuse the same grouping
|
|
63
|
+
* the sync path applies; without this, doctor would double-count `foo.sh` and
|
|
64
|
+
* `foo.yaml`. On basename collision, the top-level file wins over a nested one.
|
|
63
65
|
*/
|
|
64
66
|
export declare function listHookEntriesFromDir(dir: string): HookEntry[];
|
|
65
67
|
/**
|