@phnx-labs/agents-cli 1.22.52 → 1.22.54
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 +336 -0
- package/README.md +42 -9
- package/dist/bootstrap.js +55 -154
- package/dist/cli/command-registry.d.ts +5 -0
- package/dist/cli/command-registry.js +8 -1
- package/dist/commands/accounts.js +220 -174
- package/dist/commands/apply.js +6 -3
- package/dist/commands/auth-mint.d.ts +8 -0
- package/dist/commands/auth-mint.js +96 -0
- package/dist/commands/auth.js +5 -1
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cost.js +8 -2
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/doctor.js +6 -1
- package/dist/commands/exec.js +26 -17
- package/dist/commands/fleet-capture.js +7 -0
- package/dist/commands/focus.d.ts +1 -0
- package/dist/commands/focus.js +4 -2
- package/dist/commands/go.d.ts +5 -4
- package/dist/commands/go.js +8 -7
- package/dist/commands/insights.js +9 -0
- package/dist/commands/monitors.js +85 -30
- package/dist/commands/output.js +8 -2
- package/dist/commands/repo.js +18 -0
- package/dist/commands/secrets.js +33 -14
- package/dist/commands/sessions-inject.js +8 -3
- package/dist/commands/sessions-picker.js +2 -1
- package/dist/commands/sessions.d.ts +20 -12
- package/dist/commands/sessions.js +94 -40
- package/dist/commands/setup-accounts.d.ts +8 -0
- package/dist/commands/setup-accounts.js +47 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +11 -2
- package/dist/commands/share.d.ts +52 -3
- package/dist/commands/share.js +262 -18
- package/dist/commands/ssh.d.ts +7 -0
- package/dist/commands/ssh.js +53 -14
- package/dist/commands/status.js +14 -0
- package/dist/commands/sync.js +44 -0
- package/dist/commands/view.d.ts +3 -1
- package/dist/commands/view.js +5 -4
- package/dist/lib/account-registry.d.ts +15 -5
- package/dist/lib/account-registry.js +165 -53
- package/dist/lib/accounting/rotate.d.ts +20 -6
- package/dist/lib/accounting/rotate.js +38 -7
- package/dist/lib/accounting/usage.d.ts +37 -1
- package/dist/lib/accounting/usage.js +71 -6
- package/dist/lib/agent-spec/agents.d.ts +5 -2
- package/dist/lib/agent-spec/agents.js +25 -7
- package/dist/lib/analytics/mix-commands.js +12 -6
- package/dist/lib/answer-router.js +2 -1
- package/dist/lib/auth-mint.d.ts +150 -0
- package/dist/lib/auth-mint.js +434 -0
- package/dist/lib/browser/profiles.d.ts +18 -0
- package/dist/lib/browser/profiles.js +26 -1
- package/dist/lib/browser/registry.d.ts +44 -14
- package/dist/lib/browser/registry.js +141 -45
- package/dist/lib/browser/remote-control.d.ts +9 -7
- package/dist/lib/browser/remote-control.js +9 -7
- package/dist/lib/claude-account-token.d.ts +10 -0
- package/dist/lib/claude-account-token.js +14 -4
- package/dist/lib/config-drift.d.ts +37 -0
- package/dist/lib/config-drift.js +72 -0
- package/dist/lib/daemon/auth-sync-service.d.ts +19 -0
- package/dist/lib/daemon/auth-sync-service.js +34 -0
- package/dist/lib/daemon/daemon.js +30 -4
- package/dist/lib/daemon/runner.js +10 -2
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/device-config.d.ts +3 -3
- package/dist/lib/device-config.js +8 -7
- package/dist/lib/devices/config-migration.js +147 -1
- package/dist/lib/devices/connect.d.ts +26 -0
- package/dist/lib/devices/connect.js +48 -1
- package/dist/lib/devices/device-docs.d.ts +35 -0
- package/dist/lib/devices/device-docs.js +163 -0
- package/dist/lib/devices/discovery-policy.d.ts +14 -2
- package/dist/lib/devices/discovery-policy.js +31 -21
- package/dist/lib/devices/doctor-findings.d.ts +5 -1
- package/dist/lib/devices/doctor-findings.js +19 -1
- package/dist/lib/devices/registry.d.ts +11 -5
- package/dist/lib/devices/registry.js +46 -18
- package/dist/lib/exec.d.ts +88 -30
- package/dist/lib/exec.js +138 -34
- package/dist/lib/feed/feed.d.ts +10 -2
- package/dist/lib/feed/feed.js +35 -2
- package/dist/lib/feed-broadcast.js +1 -1
- package/dist/lib/fleet/apply.d.ts +11 -0
- package/dist/lib/fleet/apply.js +23 -3
- package/dist/lib/fleet/auth-sync.js +5 -3
- package/dist/lib/help.d.ts +9 -0
- package/dist/lib/help.js +29 -1
- package/dist/lib/hosts/dispatch.d.ts +4 -3
- package/dist/lib/hosts/dispatch.js +12 -8
- package/dist/lib/hosts/passthrough.d.ts +1 -10
- package/dist/lib/hosts/passthrough.js +1 -13
- package/dist/lib/hosts/providers/local.d.ts +9 -3
- package/dist/lib/hosts/providers/local.js +23 -12
- package/dist/lib/hosts/reconnect.d.ts +7 -4
- package/dist/lib/hosts/reconnect.js +29 -25
- package/dist/lib/hosts/registry.js +4 -1
- package/dist/lib/hosts/remote-os.js +3 -1
- package/dist/lib/installations/versions.js +9 -1
- package/dist/lib/linux-userns.d.ts +58 -0
- package/dist/lib/linux-userns.js +116 -0
- package/dist/lib/memory.d.ts +26 -0
- package/dist/lib/memory.js +80 -1
- package/dist/lib/monitors/config.d.ts +11 -0
- package/dist/lib/monitors/config.js +8 -0
- package/dist/lib/monitors/engine.js +8 -1
- package/dist/lib/monitors/state.d.ts +37 -1
- package/dist/lib/monitors/state.js +79 -4
- package/dist/lib/permissions-registry.d.ts +2 -0
- package/dist/lib/permissions-registry.js +116 -14
- package/dist/lib/permissions.d.ts +5 -3
- package/dist/lib/permissions.js +25 -27
- package/dist/lib/profiles.d.ts +8 -7
- package/dist/lib/profiles.js +12 -0
- package/dist/lib/project-key.d.ts +9 -0
- package/dist/lib/project-key.js +11 -0
- package/dist/lib/secrets/bundles.d.ts +35 -0
- package/dist/lib/secrets/bundles.js +78 -1
- package/dist/lib/secrets/push.d.ts +3 -8
- package/dist/lib/secrets/push.js +18 -14
- package/dist/lib/secrets/remote.d.ts +9 -18
- package/dist/lib/secrets/remote.js +11 -26
- package/dist/lib/secrets/reserved-sync.d.ts +65 -0
- package/dist/lib/secrets/reserved-sync.js +129 -0
- package/dist/lib/self-heal/checks/hook-manifest.d.ts +2 -0
- package/dist/lib/self-heal/checks/hook-manifest.js +56 -0
- package/dist/lib/self-heal/registry.js +4 -0
- package/dist/lib/self-heal/types.d.ts +1 -1
- package/dist/lib/session/active.d.ts +10 -1
- package/dist/lib/session/active.js +8 -5
- package/dist/lib/session/actor-sidecar.d.ts +7 -0
- package/dist/lib/session/actor-sidecar.js +2 -0
- package/dist/lib/session/db.d.ts +39 -4
- package/dist/lib/session/db.js +168 -31
- package/dist/lib/session/discover.d.ts +32 -4
- package/dist/lib/session/discover.js +126 -37
- package/dist/lib/session/insights.d.ts +14 -0
- package/dist/lib/session/insights.js +25 -2
- package/dist/lib/session/linear.js +1 -1
- package/dist/lib/session/live-metadata.js +1 -0
- package/dist/lib/session/pid-registry.d.ts +7 -0
- package/dist/lib/session/prompt.d.ts +15 -0
- package/dist/lib/session/prompt.js +21 -0
- package/dist/lib/session/shell-programs.d.ts +17 -0
- package/dist/lib/session/shell-programs.js +21 -0
- package/dist/lib/session/state.js +2 -1
- package/dist/lib/session/stream-render.js +2 -1
- package/dist/lib/session/tool-calls.js +2 -5
- package/dist/lib/session/trajectory-html.js +2 -1
- package/dist/lib/session/trajectory.js +3 -12
- package/dist/lib/session/types.d.ts +25 -0
- package/dist/lib/session/types.js +10 -0
- package/dist/lib/share/publish.d.ts +53 -5
- package/dist/lib/share/publish.js +99 -17
- package/dist/lib/share/worker-template.js +594 -64
- package/dist/lib/startup/root-command.js +2 -1
- package/dist/lib/state.d.ts +24 -0
- package/dist/lib/state.js +318 -54
- package/dist/lib/sync-status.d.ts +4 -0
- package/dist/lib/sync-status.js +3 -0
- package/dist/lib/terminal/resolve.d.ts +7 -0
- package/dist/lib/terminal/resolve.js +41 -2
- package/dist/lib/traces/classify.js +24 -19
- package/dist/lib/traces/insights.d.ts +67 -0
- package/dist/lib/traces/insights.js +178 -0
- package/dist/lib/traces/phenotype.d.ts +67 -0
- package/dist/lib/traces/phenotype.js +437 -0
- package/dist/lib/traces/segments.d.ts +133 -0
- package/dist/lib/traces/segments.js +301 -0
- package/dist/lib/traces/sync.d.ts +33 -0
- package/dist/lib/traces/sync.js +11 -2
- package/dist/lib/types.d.ts +47 -1
- package/dist/lib/usage-refresh.js +2 -1
- package/dist/lib/view-types.d.ts +2 -0
- package/dist/lib/watchdog/runner.js +18 -4
- package/package.json +2 -1
|
@@ -24,6 +24,7 @@ import { IS_WINDOWS } from '../lib/platform/index.js';
|
|
|
24
24
|
import { safeJoin } from '../lib/paths.js';
|
|
25
25
|
import { machineId, normalizeHost } from '../lib/machine-id.js';
|
|
26
26
|
import { loadDevices } from '../lib/devices/registry.js';
|
|
27
|
+
import { assertDaemonEnabled } from '../lib/device-config.js';
|
|
27
28
|
import { setHelpSections } from '../lib/help.js';
|
|
28
29
|
import { isInteractiveTerminal, requireInteractiveSelection } from './utils.js';
|
|
29
30
|
function stdoutJson(payload) {
|
|
@@ -52,6 +53,26 @@ function sourceLabel(source) {
|
|
|
52
53
|
return source.type;
|
|
53
54
|
}
|
|
54
55
|
}
|
|
56
|
+
/** Warn when a run/routine action has no postcondition — completed-exit-0 still records as ok (PHNX-2842). */
|
|
57
|
+
function warnMissingPostcondition(action) {
|
|
58
|
+
if ((action.type === 'run' || action.type === 'routine') && !action.postcondition) {
|
|
59
|
+
stderrLine(chalk.yellow(` Note: ${action.type} has no --postcondition — a completed agent that did nothing will still record as ok. Add --postcondition '<cmd>' that exits 0 when the intended effect happened.`));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Reconciled one-line outcome for a fire (ok / no effect / failed). */
|
|
63
|
+
function fireOutcomeDisplay(name, f) {
|
|
64
|
+
const rec = resolveFireOutcome(name, f);
|
|
65
|
+
if (rec.ok)
|
|
66
|
+
return { label: chalk.green('ok'), note: '' };
|
|
67
|
+
if (rec.effect === 'none') {
|
|
68
|
+
const note = rec.error ? chalk.gray(` ${rec.error}`) : '';
|
|
69
|
+
return { label: chalk.yellow('no effect'), note };
|
|
70
|
+
}
|
|
71
|
+
const corrected = f.ok !== false && rec.runStatus
|
|
72
|
+
? chalk.yellow(` (run ${rec.runStatus})`)
|
|
73
|
+
: '';
|
|
74
|
+
return { label: chalk.red('failed') + corrected, note: '' };
|
|
75
|
+
}
|
|
55
76
|
/** A one-line human label for a monitor's action. */
|
|
56
77
|
function actionLabel(action) {
|
|
57
78
|
switch (action.type) {
|
|
@@ -123,12 +144,26 @@ function livenessLabel(monitor, state, liveness) {
|
|
|
123
144
|
}
|
|
124
145
|
return chalk.gray(`checked ${liveness.checkCount}x · last ${checkedAgo} · no match yet`);
|
|
125
146
|
}
|
|
126
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* Start or reload the background daemon so a newly-added monitor is watched.
|
|
149
|
+
* Returns false when auto-start was refused (`daemon.enabled=false`); the add
|
|
150
|
+
* itself already succeeded — the monitor is config and stays valid fleet-wide.
|
|
151
|
+
* The refusal names the setting and the fix. Mirrors `ensureSchedulerRunning`
|
|
152
|
+
* in commands/routines.ts (PHNX-2637).
|
|
153
|
+
*/
|
|
127
154
|
function ensureDaemonRunning() {
|
|
155
|
+
try {
|
|
156
|
+
assertDaemonEnabled();
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
// Loud stated skip, on stderr so --json stdout stays clean.
|
|
160
|
+
stderrLine(chalk.yellow(err.message));
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
128
163
|
if (isDaemonRunning()) {
|
|
129
164
|
signalDaemonReload();
|
|
130
165
|
stderrLine(chalk.gray('Daemon reloaded'));
|
|
131
|
-
return;
|
|
166
|
+
return true;
|
|
132
167
|
}
|
|
133
168
|
const result = startDaemon();
|
|
134
169
|
if (result.pid) {
|
|
@@ -138,6 +173,7 @@ function ensureDaemonRunning() {
|
|
|
138
173
|
else {
|
|
139
174
|
stderrLine(chalk.yellow('Could not start the daemon. Start it manually with: agents routines start'));
|
|
140
175
|
}
|
|
176
|
+
return true;
|
|
141
177
|
}
|
|
142
178
|
/**
|
|
143
179
|
* Assert the postcondition of `add`: the engine actually picked the monitor up.
|
|
@@ -272,10 +308,16 @@ function buildAction(options) {
|
|
|
272
308
|
action.effort = options.effort;
|
|
273
309
|
if (options.actionTimeout)
|
|
274
310
|
action.timeout = options.actionTimeout;
|
|
311
|
+
if (options.postcondition)
|
|
312
|
+
action.postcondition = options.postcondition;
|
|
313
|
+
chosen.push(action);
|
|
314
|
+
}
|
|
315
|
+
if (options.routine) {
|
|
316
|
+
const action = { type: 'routine', routine: options.routine };
|
|
317
|
+
if (options.postcondition)
|
|
318
|
+
action.postcondition = options.postcondition;
|
|
275
319
|
chosen.push(action);
|
|
276
320
|
}
|
|
277
|
-
if (options.routine)
|
|
278
|
-
chosen.push({ type: 'routine', routine: options.routine });
|
|
279
321
|
if (options.notify !== undefined) {
|
|
280
322
|
// --notify may be a bare flag (notify the owner) or carry a channel that
|
|
281
323
|
// overrides notify.owner.channel. Left unset, the send resolves the owner
|
|
@@ -293,6 +335,10 @@ function buildAction(options) {
|
|
|
293
335
|
stderrLine(chalk.red(`Exactly one action is allowed; got ${chosen.map((c) => c.type).join(', ')}`));
|
|
294
336
|
process.exit(1);
|
|
295
337
|
}
|
|
338
|
+
if (options.postcondition && chosen[0].type !== 'run' && chosen[0].type !== 'routine') {
|
|
339
|
+
stderrLine(chalk.red('--postcondition only applies to --run or --routine'));
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
296
342
|
return chosen[0];
|
|
297
343
|
}
|
|
298
344
|
/** Interactive monitor picker. Returns the selected name or null on cancel/empty. */
|
|
@@ -379,7 +425,7 @@ async function guardAgainstDuplicateMonitor(config, force) {
|
|
|
379
425
|
export function registerMonitorsCommands(program) {
|
|
380
426
|
const monitorsCmd = program
|
|
381
427
|
.command('monitors')
|
|
382
|
-
.description('Durable event-triggered watchers: watch a source, detect a change, fire an action. The daemon auto-starts on first add.');
|
|
428
|
+
.description('Durable event-triggered watchers: watch a source, detect a change, fire an action. The daemon auto-starts on first add unless daemon.enabled is false.');
|
|
383
429
|
setHelpSections(monitorsCmd, {
|
|
384
430
|
examples: `
|
|
385
431
|
# CI went red → triage it (poll a command, diff, fire an agent)
|
|
@@ -388,6 +434,12 @@ export function registerMonitorsCommands(program) {
|
|
|
388
434
|
--run claude --prompt 'CI failed on #1119: {event}. Diagnose and fix.' \\
|
|
389
435
|
--device yosemite-s0
|
|
390
436
|
|
|
437
|
+
# Merge-on-green: the fire is ok only if the PR actually merged
|
|
438
|
+
agents monitors add merge-1682 \\
|
|
439
|
+
--poll 'gh pr view 1682 --json state --jq .state' 2m --match OPEN \\
|
|
440
|
+
--run claude --prompt 'Rebase-merge #1682: {event}' \\
|
|
441
|
+
--postcondition 'gh pr view 1682 --json state --jq .state | grep -qx MERGED'
|
|
442
|
+
|
|
391
443
|
# SSL cert issued → notify (poll an HTTPS endpoint every 8h)
|
|
392
444
|
agents monitors add cert-issued \\
|
|
393
445
|
--poll-http 'https://secure.ssl.com/team/.../co-ec1l5dgjofa' 8h \\
|
|
@@ -406,10 +458,14 @@ export function registerMonitorsCommands(program) {
|
|
|
406
458
|
- SOURCE (--watch, --poll, --poll-http, --ws, --watch-file, --watch-device, --on)
|
|
407
459
|
- CONDITION (--on-change [default], --match <re>, --every; --dedupe-key)
|
|
408
460
|
- ACTION (--run <agent> --prompt, --routine, --notify, --webhook-out)
|
|
461
|
+
--postcondition <cmd> on --run/--routine asserts the effect
|
|
462
|
+
happened (exit 0) after the agent settles; otherwise the
|
|
463
|
+
fire records as "no effect", not ok.
|
|
409
464
|
|
|
410
465
|
The fired event is injected into a run/routine prompt as {event}.
|
|
411
466
|
Pin the single OWNER device with --device (exactly-once). The daemon (shared
|
|
412
|
-
with routines) auto-starts on first add
|
|
467
|
+
with routines) auto-starts on first add unless daemon.enabled is false on this
|
|
468
|
+
device; manage it with 'agents daemon start|stop' (or 'agents routines start|stop').
|
|
413
469
|
|
|
414
470
|
v1 evaluates poll sources (command, poll, poll-http, file, device). Push
|
|
415
471
|
sources (ws, webhook) are accepted but delivered through a receiver wired in
|
|
@@ -419,7 +475,7 @@ export function registerMonitorsCommands(program) {
|
|
|
419
475
|
// ─── add ────────────────────────────────────────────────────────────────────
|
|
420
476
|
monitorsCmd
|
|
421
477
|
.command('add [nameOrPath]')
|
|
422
|
-
.description('Create a monitor from inline flags or a YAML file. Auto-starts the daemon.')
|
|
478
|
+
.description('Create a monitor from inline flags or a YAML file. Auto-starts the daemon unless daemon.enabled is false.')
|
|
423
479
|
// SOURCE
|
|
424
480
|
.option('--watch <cmd>', 'Run a shell command; its stdout is the observation')
|
|
425
481
|
.option('--poll <cmd...>', 'Re-run a command every interval: --poll "<cmd>" <interval> (e.g. 30s)')
|
|
@@ -445,6 +501,7 @@ export function registerMonitorsCommands(program) {
|
|
|
445
501
|
.option('--mode <mode>', 'Execution mode for --run: plan, edit, auto, or skip')
|
|
446
502
|
.option('--effort <effort>', 'Reasoning effort for --run: low | medium | high | xhigh | max | auto')
|
|
447
503
|
.option('--action-timeout <t>', 'Kill the --run action if it runs longer than this (e.g. 10m)')
|
|
504
|
+
.option('--postcondition <cmd>', 'Shell command that must exit 0 after a --run/--routine action settles; otherwise the fire records as no-effect, not ok. {event} is replaced with the fired event summary')
|
|
448
505
|
.option('--routine <name>', 'Fire an existing routine on change')
|
|
449
506
|
.option('--notify [channel]', 'Notify the owner (notify.owner); [channel] overrides the owner channel')
|
|
450
507
|
.option('--webhook-out <url>', 'POST the event to this URL')
|
|
@@ -481,8 +538,9 @@ export function registerMonitorsCommands(program) {
|
|
|
481
538
|
await guardAgainstDuplicateMonitor(config, options.force === true);
|
|
482
539
|
writeMonitor(config);
|
|
483
540
|
console.log(chalk.green(`Monitor '${name}' added`));
|
|
484
|
-
|
|
485
|
-
|
|
541
|
+
warnMissingPostcondition(config.action);
|
|
542
|
+
if (ensureDaemonRunning())
|
|
543
|
+
await assertEnginePickup(config);
|
|
486
544
|
return;
|
|
487
545
|
}
|
|
488
546
|
if (!nameOrPath) {
|
|
@@ -556,8 +614,9 @@ export function registerMonitorsCommands(program) {
|
|
|
556
614
|
writeMonitor(config);
|
|
557
615
|
console.log(chalk.green(`Monitor '${nameOrPath}' added`));
|
|
558
616
|
console.log(chalk.gray(` ${sourceLabel(source)} → [${condition.mode}] → ${actionLabel(action)} · owner: ${ownerLabel(config)}`));
|
|
559
|
-
|
|
560
|
-
|
|
617
|
+
warnMissingPostcondition(action);
|
|
618
|
+
if (ensureDaemonRunning())
|
|
619
|
+
await assertEnginePickup(config);
|
|
561
620
|
});
|
|
562
621
|
// ─── list ────────────────────────────────────────────────────────────────────
|
|
563
622
|
monitorsCmd
|
|
@@ -641,7 +700,7 @@ export function registerMonitorsCommands(program) {
|
|
|
641
700
|
state,
|
|
642
701
|
liveness,
|
|
643
702
|
stalled: isStalled(monitor, liveness),
|
|
644
|
-
recentFires,
|
|
703
|
+
recentFires: recentFires.map((f) => ({ ...f, reconciled: resolveFireOutcome(name, f) })),
|
|
645
704
|
});
|
|
646
705
|
return;
|
|
647
706
|
}
|
|
@@ -671,9 +730,11 @@ export function registerMonitorsCommands(program) {
|
|
|
671
730
|
if (recentFires.length > 0) {
|
|
672
731
|
console.log(chalk.bold('\nRecent fires'));
|
|
673
732
|
for (const f of recentFires) {
|
|
674
|
-
// Reconciled against the run's real status
|
|
675
|
-
const {
|
|
676
|
-
console.log(` ${chalk.gray(f.firedAt)} ${f.action ?? '?'} ${
|
|
733
|
+
// Reconciled against the run's real status + postcondition (RUSH-2690, PHNX-2842).
|
|
734
|
+
const { label, note } = fireOutcomeDisplay(name, f);
|
|
735
|
+
console.log(` ${chalk.gray(f.firedAt)} ${f.action ?? '?'} ${label}`);
|
|
736
|
+
if (note)
|
|
737
|
+
console.log(note);
|
|
677
738
|
}
|
|
678
739
|
}
|
|
679
740
|
});
|
|
@@ -847,23 +908,17 @@ export function registerMonitorsCommands(program) {
|
|
|
847
908
|
}
|
|
848
909
|
console.log(chalk.bold(`Fire history: ${name}\n`));
|
|
849
910
|
for (const f of fires.slice(-20)) {
|
|
850
|
-
// Reconcile against the run's REAL, current status (RUSH-2690)
|
|
851
|
-
// than trusting the frozen
|
|
852
|
-
//
|
|
853
|
-
//
|
|
854
|
-
//
|
|
855
|
-
|
|
856
|
-
const { ok, runStatus } = resolveFireOutcome(name, f);
|
|
857
|
-
const outcome = ok ? chalk.green('ok') : chalk.red('failed');
|
|
858
|
-
// Surface the divergence explicitly when the frozen write disagreed
|
|
859
|
-
// with the reconciled read — the exact "fire says ok, run says
|
|
860
|
-
// otherwise" symptom RUSH-2690 reported.
|
|
861
|
-
const corrected = f.ok !== false && !ok && runStatus
|
|
862
|
-
? chalk.yellow(` (run ${runStatus})`)
|
|
863
|
-
: '';
|
|
911
|
+
// Reconcile against the run's REAL, current status (RUSH-2690) and a
|
|
912
|
+
// declared postcondition (PHNX-2842) rather than trusting the frozen
|
|
913
|
+
// `ok` written at fire time — `dispatchAction` only sees a synchronous
|
|
914
|
+
// 'running' snapshot before the run has actually settled, and a
|
|
915
|
+
// `completed` agent that did nothing used to read `ok` here forever.
|
|
916
|
+
const { label, note } = fireOutcomeDisplay(name, f);
|
|
864
917
|
const runRef = f.runId ? chalk.gray(` run ${f.runId}`) : '';
|
|
865
|
-
console.log(` ${f.firedAt} ${(f.action ?? '?').padEnd(12)} ${
|
|
918
|
+
console.log(` ${f.firedAt} ${(f.action ?? '?').padEnd(12)} ${label}${runRef}`);
|
|
866
919
|
console.log(chalk.gray(` ${f.summary.slice(0, 100)}`));
|
|
920
|
+
if (note)
|
|
921
|
+
console.log(note);
|
|
867
922
|
}
|
|
868
923
|
});
|
|
869
924
|
// ─── pause / resume ───────────────────────────────────────────────────────────
|
package/dist/commands/output.js
CHANGED
|
@@ -42,8 +42,14 @@ The burn is split into input / cache-read / cache-write where the harness record
|
|
|
42
42
|
(Claude/Codex/Gemini/Droid). --pricing no-cache reprices cached tokens at the input rate,
|
|
43
43
|
so you can see what caching is saving. --json always carries both actual and no-cache costs.
|
|
44
44
|
`)
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
// Read opts via optsWithGlobals(): `--json`/`--since`/`--by` collide by name
|
|
46
|
+
// with the `insights` parent's own options, so commander binds them to the
|
|
47
|
+
// parent at parse time and the leaf's plain opts() never sees them. Merging
|
|
48
|
+
// ancestor opts is what the sibling `insights mix` recipes already do
|
|
49
|
+
// (mix-commands.ts) — without it every flag on this command is silently
|
|
50
|
+
// dropped (e.g. `agents insights output --json` printed the human table).
|
|
51
|
+
.action(async (_options, command) => {
|
|
52
|
+
await outputAction(command.optsWithGlobals());
|
|
47
53
|
});
|
|
48
54
|
}
|
|
49
55
|
function resolveGroup(by) {
|
package/dist/commands/repo.js
CHANGED
|
@@ -1052,6 +1052,14 @@ export function registerRepoCommands(program) {
|
|
|
1052
1052
|
].filter(Boolean);
|
|
1053
1053
|
if (parts.length > 0)
|
|
1054
1054
|
console.log(chalk.gray(`Device policy: ${parts.join(' · ')}`));
|
|
1055
|
+
const { syncReservedAuthBundle } = await import('../lib/secrets/reserved-sync.js');
|
|
1056
|
+
const auth = syncReservedAuthBundle();
|
|
1057
|
+
if (auth.pushed.length > 0) {
|
|
1058
|
+
console.log(chalk.gray(`Auth bundle: pushed to ${auth.pushed.join(', ')}`));
|
|
1059
|
+
}
|
|
1060
|
+
for (const err of auth.errors) {
|
|
1061
|
+
console.error(chalk.yellow(`Auth bundle: ${err.device}: ${err.message}`));
|
|
1062
|
+
}
|
|
1055
1063
|
}
|
|
1056
1064
|
});
|
|
1057
1065
|
repoCmd
|
|
@@ -1091,6 +1099,16 @@ export function registerRepoCommands(program) {
|
|
|
1091
1099
|
const result = await commitAndPush(t.dir, options.message);
|
|
1092
1100
|
if (result.success) {
|
|
1093
1101
|
spinner.succeed(`${formatRepoTarget(t.alias, t.dir, result.branch)}: ${result.detail ?? 'pushed'}`);
|
|
1102
|
+
if (t.alias === 'user') {
|
|
1103
|
+
const { syncReservedAuthBundle } = await import('../lib/secrets/reserved-sync.js');
|
|
1104
|
+
const auth = syncReservedAuthBundle();
|
|
1105
|
+
if (auth.pushed.length > 0) {
|
|
1106
|
+
console.log(chalk.gray(`Auth bundle: pushed to ${auth.pushed.join(', ')}`));
|
|
1107
|
+
}
|
|
1108
|
+
for (const err of auth.errors) {
|
|
1109
|
+
console.error(chalk.yellow(`Auth bundle: ${err.device}: ${err.message}`));
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1094
1112
|
}
|
|
1095
1113
|
else {
|
|
1096
1114
|
spinner.fail(`${formatRepoTarget(t.alias, t.dir)}: ${result.error}`);
|
package/dist/commands/secrets.js
CHANGED
|
@@ -18,7 +18,7 @@ import { ensureDaemonStarted, isDaemonRunning } from '../lib/daemon/daemon.js';
|
|
|
18
18
|
import { parseHostsOption, remoteResolveEnv, remoteSecretsRaw, remoteSecretsStream, resolveHostSshTarget, } from '../lib/secrets/remote.js';
|
|
19
19
|
import { resolveBundleForPush, pushResolvedBundleToHost, bundleEnvToDotenv } from '../lib/secrets/push.js';
|
|
20
20
|
export { bundleEnvToDotenv };
|
|
21
|
-
import { bundleBackend, bundleExists, bundleItemStore, bundlePolicy, deleteBundle, describeBundle, keychainItemsForBundle, keychainRef, listBundles, healKeychainBundleMetadataAclOnce, isHeadlessSecretsContext, isAgentInvocationContext, migrateLegacyBundles, parseDotenv, readAndResolveBundleEnv, readBundle, readBundleIfDecryptable, reAclBundleItems, renameBundle, rotateBundleSecret, sanitizeProcessEnv, validateBundleName, validateEnvKey, validateExpiresFutureDated, validateSecretType, writeBundle, writeBundleWithItems, SECRET_TYPES, } from '../lib/secrets/bundles.js';
|
|
21
|
+
import { bundleBackend, bundleExists, bundleItemStore, bundlePolicy, deleteBundle, describeBundle, keychainItemsForBundle, keychainRef, listBundles, healKeychainBundleMetadataAclOnce, isHeadlessSecretsContext, isAgentInvocationContext, migrateLegacyBundles, parseDotenv, readAndResolveBundleEnv, readBundle, readBundleIfDecryptable, reAclBundleItems, renameBundle, rotateBundleSecret, sanitizeProcessEnv, validateBundleName, validateEnvKey, validateExpiresFutureDated, validateSecretType, writeBundle, writeBundleWithItems, assertFileBundleDecryptable, isReservedBundleName, ReservedBundleWrongBackendError, SECRET_TYPES, } from '../lib/secrets/bundles.js';
|
|
22
22
|
import { parseListFilters, bundleMatchesFilter, bundleExpiry, filterIsActive, describeFilter, parseSortField, sortBundles, SORT_FIELDS, REF_KINDS, DEFAULT_EXPIRING_DAYS, } from '../lib/secrets/list-filter.js';
|
|
23
23
|
import { encryptForFallback, decryptForFallback } from '../lib/secrets/filestore.js';
|
|
24
24
|
import { getKeychainToken, hasKeychainToken, secretsKeychainItem, setKeychainToken, maybeAutoRekey, } from '../lib/secrets/index.js';
|
|
@@ -846,7 +846,12 @@ export function renderExpiringCol(b, now = Date.now()) {
|
|
|
846
846
|
* exists to describe. `--force` already means "overwrite what is there".
|
|
847
847
|
*/
|
|
848
848
|
export function resolveImportBundle(name, backendOpt, synced = false, force = false) {
|
|
849
|
-
|
|
849
|
+
if (isReservedBundleName(name) && (synced || (backendOpt && backendOpt !== 'file'))) {
|
|
850
|
+
throw new ReservedBundleWrongBackendError(name, synced ? 'vault' : resolveBackendOpt(backendOpt));
|
|
851
|
+
}
|
|
852
|
+
const requestedBackend = isReservedBundleName(name)
|
|
853
|
+
? 'file'
|
|
854
|
+
: (synced ? 'vault' : resolveBackendOpt(backendOpt));
|
|
850
855
|
if (bundleExists(name)) {
|
|
851
856
|
// readBundleIfDecryptable nulls ONLY on BundleUndecryptableError; a locked
|
|
852
857
|
// keychain or logged-out vault still throws, so a recoverable state can
|
|
@@ -892,6 +897,9 @@ function applyEnvToBundle(bundle, env, opts) {
|
|
|
892
897
|
else {
|
|
893
898
|
writeBundle(bundle);
|
|
894
899
|
}
|
|
900
|
+
if (added > 0) {
|
|
901
|
+
assertFileBundleDecryptable(bundle.name, Object.keys(env).filter((k) => k in bundle.vars));
|
|
902
|
+
}
|
|
895
903
|
return { added, skipped };
|
|
896
904
|
}
|
|
897
905
|
/** Register the `agents secrets` command tree. */
|
|
@@ -1581,7 +1589,16 @@ export function registerSecretsCommands(program) {
|
|
|
1581
1589
|
// inherits the configured default (`hold`) instead of being pinned.
|
|
1582
1590
|
const policyOpt = opts.policy ?? opts.tier;
|
|
1583
1591
|
const policy = policyOpt ? parsePolicyOpt(policyOpt) : undefined;
|
|
1584
|
-
|
|
1592
|
+
let backend = opts.synced ? 'vault' : resolveBackendOpt(opts.backend);
|
|
1593
|
+
if (isReservedBundleName(resolvedName)) {
|
|
1594
|
+
// Reserved `auth` is file-backed by construction. Omitting --backend
|
|
1595
|
+
// still forces file even when agents.yaml defaults to keychain; an
|
|
1596
|
+
// explicit non-file backend fails loud (SEC-GAP-3).
|
|
1597
|
+
if (opts.synced || (opts.backend && opts.backend !== 'file')) {
|
|
1598
|
+
throw new ReservedBundleWrongBackendError(resolvedName, backend);
|
|
1599
|
+
}
|
|
1600
|
+
backend = 'file';
|
|
1601
|
+
}
|
|
1585
1602
|
if (bundleExists(resolvedName) && !opts.force) {
|
|
1586
1603
|
console.error(chalk.red(`Bundle '${resolvedName}' already exists. Use --force to overwrite.`));
|
|
1587
1604
|
process.exit(1);
|
|
@@ -2134,7 +2151,7 @@ Examples:
|
|
|
2134
2151
|
.option('--to-1password', 'Push every key in the bundle as a PASSWORD item in a 1Password vault')
|
|
2135
2152
|
.option('--vault <name>', '1Password vault name (used with --to-1password)')
|
|
2136
2153
|
.option('--device <target...>', 'Push the bundle over SSH to this device (host alias or user@host); repeatable for multiple machines')
|
|
2137
|
-
.option('--remote-backend <backend>', 'Backend for the bundle on the remote device (with --device): keychain (default) or file. file is headless-readable via the remote\'s machine-local key
|
|
2154
|
+
.option('--remote-backend <backend>', 'Backend for the bundle on the remote device (with --device): keychain (default) or file. file is headless-readable via the remote\'s machine-local key and never forwards AGENTS_SECRETS_PASSPHRASE.', 'keychain')
|
|
2138
2155
|
.option('--force', 'Overwrite existing keys/items on the target (used with --to-1password and --device)')
|
|
2139
2156
|
.option('--to-file <path>', `Write the bundle as an AES-256-GCM encrypted offline file (needs ${SYNC_PASSPHRASE_ENV}; symmetric counterpart of import --from-file)`)
|
|
2140
2157
|
.action(async (bundleName, opts) => {
|
|
@@ -2170,15 +2187,18 @@ Examples:
|
|
|
2170
2187
|
console.error(chalk.red("--remote-backend vault is not supported; use 'keychain' or 'file'."));
|
|
2171
2188
|
process.exit(1);
|
|
2172
2189
|
}
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
//
|
|
2181
|
-
|
|
2190
|
+
let remoteBackend = parsedBackend;
|
|
2191
|
+
if (isReservedBundleName(resolvedBundleName)) {
|
|
2192
|
+
if (opts.remoteBackend && opts.remoteBackend !== 'file') {
|
|
2193
|
+
throw new ReservedBundleWrongBackendError(resolvedBundleName, parsedBackend);
|
|
2194
|
+
}
|
|
2195
|
+
remoteBackend = 'file';
|
|
2196
|
+
}
|
|
2197
|
+
// File-backend export NEVER forwards AGENTS_SECRETS_PASSPHRASE
|
|
2198
|
+
// (PHNX-2371). Forwarding it keys the remote ciphertext to a secret
|
|
2199
|
+
// the destination daemon does not hold, so later headless reads fail
|
|
2200
|
+
// while import still prints "Imported N key(s)". The remote
|
|
2201
|
+
// auto-provisions its own machine-local key instead.
|
|
2182
2202
|
// Resolve ONCE for N hosts — reading a bundle can prompt, and doing it
|
|
2183
2203
|
// per host would prompt per host.
|
|
2184
2204
|
//
|
|
@@ -2197,7 +2217,6 @@ Examples:
|
|
|
2197
2217
|
const out = pushResolvedBundleToHost(resolvedForPush, resolvedBundleName, host, {
|
|
2198
2218
|
remoteBackend,
|
|
2199
2219
|
force: opts.force,
|
|
2200
|
-
passphrase: remotePassphrase,
|
|
2201
2220
|
operation: 'ssh export',
|
|
2202
2221
|
});
|
|
2203
2222
|
if (!out.ok) {
|
|
@@ -26,7 +26,11 @@ async function resolveTarget(sessionId) {
|
|
|
26
26
|
const resolution = resolveInjectTargetForSession(match);
|
|
27
27
|
if (!resolution.addressable) {
|
|
28
28
|
// Surface the resolver's precise reason (host/rail), not a generic "not tmux".
|
|
29
|
-
return {
|
|
29
|
+
return {
|
|
30
|
+
target: null,
|
|
31
|
+
reason: `Session "${sessionId}": ${resolution.reason}`,
|
|
32
|
+
hint: resolution.hint,
|
|
33
|
+
};
|
|
30
34
|
}
|
|
31
35
|
return { target: resolution.target };
|
|
32
36
|
}
|
|
@@ -43,10 +47,11 @@ async function runInject(sessionId, text, options) {
|
|
|
43
47
|
else {
|
|
44
48
|
const resolved = await resolveTarget(sessionId);
|
|
45
49
|
if (!resolved.target) {
|
|
50
|
+
const message = resolved.hint ? `${resolved.reason}\n${resolved.hint}` : resolved.reason;
|
|
46
51
|
if (options.json)
|
|
47
|
-
console.log(JSON.stringify({ ok: false, error:
|
|
52
|
+
console.log(JSON.stringify({ ok: false, error: message }));
|
|
48
53
|
else
|
|
49
|
-
console.error(chalk.red(
|
|
54
|
+
console.error(chalk.red(message));
|
|
50
55
|
process.exit(1);
|
|
51
56
|
}
|
|
52
57
|
target = resolved.target;
|
|
@@ -9,6 +9,7 @@ import fs from 'node:fs';
|
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import chalk from 'chalk';
|
|
11
11
|
import { truncate, humanDuration } from '../lib/format.js';
|
|
12
|
+
import { sessionDisplayAgent } from '../lib/session/types.js';
|
|
12
13
|
import { fetchPeerPreviewDigest } from '../lib/session/remote-list.js';
|
|
13
14
|
import { parseSession, sanitizeForTerminal, SNAPSHOT_TODO_TOOLS } from '../lib/session/parse.js';
|
|
14
15
|
import { safeTeamText } from '../lib/session/team-filter.js';
|
|
@@ -404,7 +405,7 @@ function formatHeader(session, events) {
|
|
|
404
405
|
const { createdAgo, lastActiveAgo, duration } = extractTiming(session, events);
|
|
405
406
|
// Line 1: Agent v version · shortId · model · account
|
|
406
407
|
const line1 = [];
|
|
407
|
-
line1.push(chalk.gray(`${displayAgent(session
|
|
408
|
+
line1.push(chalk.gray(`${displayAgent(sessionDisplayAgent(session))}${session.version ? ` v${session.version}` : ''}`));
|
|
408
409
|
if (session.shortId)
|
|
409
410
|
line1.push(chalk.dim(session.shortId));
|
|
410
411
|
if (model)
|
|
@@ -676,7 +676,7 @@ export declare function formatPickerLabel(s: SessionMeta, query: string, cols?:
|
|
|
676
676
|
* so it stays fixed across the picker's re-renders within a single run.
|
|
677
677
|
*/
|
|
678
678
|
export declare function formatPickerTip(sessions: SessionMeta[]): string;
|
|
679
|
-
export declare function pickSessionInteractive(sessions: SessionMeta[], message?: string, initialSearch?: string, hiddenCount?: number, enterHint?: string): Promise<PickedSession | null>;
|
|
679
|
+
export declare function pickSessionInteractive(sessions: SessionMeta[], message?: string, initialSearch?: string, hiddenCount?: number, enterHint?: string, scope?: SessionSearchScope): Promise<PickedSession | null>;
|
|
680
680
|
/**
|
|
681
681
|
* Row-id prefix for a live session whose agent has not reported a session id yet
|
|
682
682
|
* (a booting harness, a queued teammate). The browser lists these so nothing live
|
|
@@ -736,6 +736,16 @@ interface AgentFilter {
|
|
|
736
736
|
* alias and single-typo rules. Kept pure so focus and the browser cannot drift. */
|
|
737
737
|
export declare function resolveSessionAgentName(name: string): SessionAgentId | null;
|
|
738
738
|
export declare function parseAgentFilter(agentName?: string): AgentFilter;
|
|
739
|
+
/**
|
|
740
|
+
* Explicit `--agent` / `--project` / `--routine` flags. Distinct from the
|
|
741
|
+
* listing pool (cwd-scoped, default-capped) that PHNX-2767 hydrates past:
|
|
742
|
+
* these flags MUST still exclude FTS hits after that union.
|
|
743
|
+
*/
|
|
744
|
+
export type SessionSearchScope = {
|
|
745
|
+
agent?: string;
|
|
746
|
+
project?: string;
|
|
747
|
+
routine?: boolean | string;
|
|
748
|
+
};
|
|
739
749
|
/**
|
|
740
750
|
* How a `sessions <query>` argument was resolved against the pool.
|
|
741
751
|
*
|
|
@@ -759,29 +769,27 @@ export interface SessionQueryResolution {
|
|
|
759
769
|
*/
|
|
760
770
|
export declare function resolveSessionQuery(pool: SessionMeta[], query: string, options?: {
|
|
761
771
|
indexFallback?: boolean;
|
|
772
|
+
scope?: SessionSearchScope;
|
|
762
773
|
}): SessionQueryResolution;
|
|
763
774
|
/** Honest result of a fleet sweep that found nothing: it says what the sweep
|
|
764
775
|
* actually did — how many peers answered, which were unreachable — instead of
|
|
765
776
|
* dead-ending on "search with --device <host>" after the search already ran. */
|
|
766
777
|
export declare function fleetNotFoundMessage(query: string, deviceCount: number, unreachable: string[]): string[];
|
|
767
778
|
/** Filter and rank sessions by a multi-term search query across metadata and content. */
|
|
768
|
-
export declare function filterSessionsByQuery(sessions: SessionMeta[], query: string | undefined): SessionMeta[];
|
|
779
|
+
export declare function filterSessionsByQuery(sessions: SessionMeta[], query: string | undefined, scope?: SessionSearchScope): SessionMeta[];
|
|
769
780
|
/**
|
|
770
781
|
* Narrow a session list by --project and --agent before search resolution.
|
|
771
782
|
* Without this, a query like "scoped search" could match sessions in BOTH
|
|
772
783
|
* the project you specified AND elsewhere, producing an ambiguity error
|
|
773
784
|
* even though the user already pointed at the correct scope.
|
|
785
|
+
*
|
|
786
|
+
* PHNX-2767 hydrates FTS hits missing from the listing pool *after* this
|
|
787
|
+
* runs, so callers that search must also pass the same scope into
|
|
788
|
+
* `filterSessionsByQuery` / `scopedContentIndex` or the union
|
|
789
|
+
* reintroduces the rows this function just dropped.
|
|
774
790
|
*/
|
|
775
|
-
export declare function applyScopeFilters(sessions: SessionMeta[], scope:
|
|
776
|
-
|
|
777
|
-
project?: string;
|
|
778
|
-
routine?: boolean | string;
|
|
779
|
-
}): SessionMeta[];
|
|
780
|
-
export declare function artifactLookupScope(agent?: string, project?: string, routine?: boolean | string): {
|
|
781
|
-
agent?: string;
|
|
782
|
-
project?: string;
|
|
783
|
-
routine?: boolean | string;
|
|
784
|
-
};
|
|
791
|
+
export declare function applyScopeFilters(sessions: SessionMeta[], scope: SessionSearchScope): SessionMeta[];
|
|
792
|
+
export declare function artifactLookupScope(agent?: string, project?: string, routine?: boolean | string): SessionSearchScope;
|
|
785
793
|
/**
|
|
786
794
|
* Whether a missed local id lookup should widen to a cross-machine sweep.
|
|
787
795
|
*
|