@phnx-labs/agents-cli 1.22.10 → 1.22.11
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 +26 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/beta.js +21 -9
- package/dist/commands/events.d.ts +1 -1
- package/dist/commands/events.js +1 -1
- package/dist/commands/projects.d.ts +1 -2
- package/dist/commands/projects.js +39 -47
- package/dist/index.js +1 -1
- package/dist/lib/beta.d.ts +1 -1
- package/dist/lib/beta.js +1 -1
- package/dist/lib/event-stream.d.ts +1 -1
- package/dist/lib/event-stream.js +1 -1
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/events.js +3 -3
- package/dist/lib/feed-broadcast.d.ts +19 -5
- package/dist/lib/feed-broadcast.js +41 -8
- 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.js +61 -0
- package/dist/lib/project-probe.d.ts +1 -1
- package/dist/lib/project-probe.js +1 -1
- package/dist/lib/projects.d.ts +1 -1
- 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/audit.js +1 -1
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +1 -1
- package/dist/lib/types.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.22.11
|
|
4
|
+
|
|
5
|
+
- **`--blocked` iMessage notifications are now phone-actionable.** The forwarded message dropped the block's `--option`s, `--default`, and timeout and instead showed `agents focus <id>` — a CLI command that is useless on a phone. It now shows the choices (`Options: publish / wait`) and the safe-default fallback (`Default in 15 min: wait`) and omits the `agents focus` line, so a `--blocked` post that carries a `--default` self-resolves when the owner can't reply. Source: `apps/cli/src/lib/feed-broadcast.ts`.
|
|
6
|
+
|
|
7
|
+
- Move operational event logs from the git-backed `~/.agents/` root into `~/.agents/.history/events/`, including existing numbered gzip archives.
|
|
8
|
+
|
|
9
|
+
- **`agents projects` is out of beta — no `agents beta enable projects` needed.**
|
|
10
|
+
The command tree (list / add / import / status / link / …) is always registered
|
|
11
|
+
now; `projects` is dropped from the beta registry (`ALL_BETA_FEATURES`,
|
|
12
|
+
`BetaFeatureName`) and the `preAction` beta gate is removed. Any lingering
|
|
13
|
+
`beta.enabled: [projects]` entry is harmlessly ignored, and `agents beta
|
|
14
|
+
enable/disable projects` prints a friendly "graduated out of beta" note and
|
|
15
|
+
no-ops instead of erroring (so old scripts survive). Source:
|
|
16
|
+
`apps/cli/src/lib/beta.ts`, `apps/cli/src/lib/types.ts`,
|
|
17
|
+
`apps/cli/src/commands/beta.ts`, `apps/cli/src/commands/projects.ts`.
|
|
18
|
+
|
|
19
|
+
- **`agents projects status` shows every project across the whole fleet by default;
|
|
20
|
+
scope it with `--device`/`--devices`.** The old `--fleet` flag is gone — status
|
|
21
|
+
now dials every registered device's workspace (presence, branch, drift) in one
|
|
22
|
+
parallel SSH round without being asked. `--device <name...>` (repeatable) or
|
|
23
|
+
`--devices a,b,c` narrows the fan-out to a subset; with no filter the whole fleet
|
|
24
|
+
is dialled. Reuses the shared `--host`/`--device` target resolution. Source:
|
|
25
|
+
`apps/cli/src/commands/projects.ts`.
|
|
26
|
+
|
|
27
|
+
- **`scripts/release.sh` home-base hop: pass a single remote argv to `agents ssh`.** Multi-arg forms (`bash -lc '…'`) are joined without re-quoting by `wrapRemoteCommand`, so the remote `cd` never ran and publish failed with `fatal: not a git repository`. One shell string keeps the command intact. Source: `apps/cli/scripts/release.sh`.
|
|
28
|
+
|
|
3
29
|
## 1.22.10
|
|
4
30
|
|
|
5
31
|
- **Plugins package workflows (Phase 5 packaging slice).** A plugin’s `workflows/<name>/WORKFLOW.md` is discovered and resolved by `agents run <name>` with precedence project > user > plugin > extra > system — no separate install into `~/.agents/workflows/` required. Plugin inventory / resource groups list `workflows`. Source: `apps/cli/src/lib/workflows.ts`, `apps/cli/src/lib/plugins.ts`, `apps/cli/src/lib/resources/workflows.ts`.
|
package/dist/bin/agents
CHANGED
|
Binary file
|
package/dist/commands/beta.js
CHANGED
|
@@ -2,17 +2,23 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { ALL_BETA_FEATURES, getBetaConfigLocation, getEnabledBetaFeatures, setBetaEnabled, } from '../lib/beta.js';
|
|
3
3
|
const BETA_DESCRIPTIONS = {
|
|
4
4
|
factory: 'Cloud-based agent dispatch via Rush Factory',
|
|
5
|
-
projects: 'Named multi-repo projects with a progress rollup (agents projects)',
|
|
6
5
|
};
|
|
6
|
+
// Features that used to be beta and are now always-on. `beta enable/disable` on
|
|
7
|
+
// one of these is a friendly no-op, not an "Unknown beta feature" error — so
|
|
8
|
+
// muscle memory and old bootstrap scripts survive the graduation.
|
|
9
|
+
const GRADUATED_FEATURES = new Set(['projects']);
|
|
7
10
|
function parseFeatures(values) {
|
|
8
11
|
const valid = new Set(ALL_BETA_FEATURES);
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
for (const g of values.filter((v) => GRADUATED_FEATURES.has(v))) {
|
|
13
|
+
console.error(chalk.gray(`'${g}' has graduated out of beta — it is on by default; no action needed.`));
|
|
14
|
+
}
|
|
15
|
+
const unknown = values.filter((v) => !valid.has(v) && !GRADUATED_FEATURES.has(v));
|
|
16
|
+
if (unknown.length > 0) {
|
|
17
|
+
console.error(chalk.red(`Unknown beta feature: ${unknown.join(', ')}`));
|
|
12
18
|
console.error(chalk.gray(`Valid features: ${ALL_BETA_FEATURES.join(', ')}`));
|
|
13
19
|
process.exit(1);
|
|
14
20
|
}
|
|
15
|
-
return values;
|
|
21
|
+
return values.filter((v) => valid.has(v));
|
|
16
22
|
}
|
|
17
23
|
export function registerBetaCommands(program) {
|
|
18
24
|
const beta = program
|
|
@@ -43,16 +49,22 @@ Examples:
|
|
|
43
49
|
.command('enable <features...>')
|
|
44
50
|
.description('Enable one or more beta features.')
|
|
45
51
|
.action((features) => {
|
|
46
|
-
const
|
|
47
|
-
|
|
52
|
+
const parsed = parseFeatures(features);
|
|
53
|
+
if (parsed.length === 0)
|
|
54
|
+
return; // only graduated/no-op names
|
|
55
|
+
const result = setBetaEnabled(parsed, true);
|
|
56
|
+
console.log(chalk.green(`Enabled: ${parsed.join(', ')}`));
|
|
48
57
|
console.log(chalk.gray(`Saved to ${result.path}`));
|
|
49
58
|
});
|
|
50
59
|
beta
|
|
51
60
|
.command('disable <features...>')
|
|
52
61
|
.description('Disable one or more beta features.')
|
|
53
62
|
.action((features) => {
|
|
54
|
-
const
|
|
55
|
-
|
|
63
|
+
const parsed = parseFeatures(features);
|
|
64
|
+
if (parsed.length === 0)
|
|
65
|
+
return; // only graduated/no-op names
|
|
66
|
+
const result = setBetaEnabled(parsed, false);
|
|
67
|
+
console.log(chalk.green(`Disabled: ${parsed.join(', ')}`));
|
|
56
68
|
console.log(chalk.gray(`Saved to ${result.path}`));
|
|
57
69
|
});
|
|
58
70
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `agents events` — read the unified event stream.
|
|
3
3
|
*
|
|
4
|
-
* One stream over BOTH operational events (`~/.agents/events.jsonl`: every
|
|
4
|
+
* One stream over BOTH operational events (`~/.agents/.history/events/events.jsonl`: every
|
|
5
5
|
* `agents <module> <cmd>` invocation plus typed events like secrets access,
|
|
6
6
|
* version installs) AND agent-semantic events (the per-session activity logs:
|
|
7
7
|
* plans, PRs, worktrees, sub-agents, artifacts). Each is stamped with who ran
|
package/dist/commands/events.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `agents events` — read the unified event stream.
|
|
3
3
|
*
|
|
4
|
-
* One stream over BOTH operational events (`~/.agents/events.jsonl`: every
|
|
4
|
+
* One stream over BOTH operational events (`~/.agents/.history/events/events.jsonl`: every
|
|
5
5
|
* `agents <module> <cmd>` invocation plus typed events like secrets access,
|
|
6
6
|
* version installs) AND agent-semantic events (the per-session activity logs:
|
|
7
7
|
* plans, PRs, worktrees, sub-agents, artifacts). Each is stamped with who ran
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `agents projects` — named, multi-repo projects and the progress
|
|
3
3
|
* rollup. Definitions live in `~/.agents/projects/<name>.yaml` (see
|
|
4
|
-
* `lib/projects.ts`); this registers the command tree over them.
|
|
5
|
-
* `isBetaEnabled('projects')`, mirroring `agents factory`.
|
|
4
|
+
* `lib/projects.ts`); this registers the command tree over them.
|
|
6
5
|
*
|
|
7
6
|
* The headline is `status`: instead of the vague per-agent activity line, it
|
|
8
7
|
* rolls every session up by project (matched on cwd) into one card — agents by
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `agents projects` — named, multi-repo projects and the progress
|
|
3
3
|
* rollup. Definitions live in `~/.agents/projects/<name>.yaml` (see
|
|
4
|
-
* `lib/projects.ts`); this registers the command tree over them.
|
|
5
|
-
* `isBetaEnabled('projects')`, mirroring `agents factory`.
|
|
4
|
+
* `lib/projects.ts`); this registers the command tree over them.
|
|
6
5
|
*
|
|
7
6
|
* The headline is `status`: instead of the vague per-agent activity line, it
|
|
8
7
|
* rolls every session up by project (matched on cwd) into one card — agents by
|
|
@@ -12,7 +11,6 @@ import chalk from 'chalk';
|
|
|
12
11
|
import * as fs from 'fs';
|
|
13
12
|
import * as path from 'path';
|
|
14
13
|
import { execFileSync, spawnSync } from 'child_process';
|
|
15
|
-
import { betaEnableHint, isBetaEnabled } from '../lib/beta.js';
|
|
16
14
|
import { setHelpSections } from '../lib/help.js';
|
|
17
15
|
import { getMainRepoRoot } from '../lib/git.js';
|
|
18
16
|
import { parseOwnerRepoFromRemote } from '../lib/registry.js';
|
|
@@ -437,25 +435,24 @@ warnWorkspaces = []) {
|
|
|
437
435
|
console.log('');
|
|
438
436
|
}
|
|
439
437
|
export function registerProjectsCommands(program) {
|
|
440
|
-
const enabled = isBetaEnabled('projects');
|
|
441
438
|
const projects = program
|
|
442
|
-
.command('projects'
|
|
439
|
+
.command('projects')
|
|
443
440
|
.description('Named multi-repo projects with a progress rollup.');
|
|
444
441
|
setHelpSections(projects, {
|
|
445
442
|
examples: `
|
|
446
443
|
agents projects import --from-linear # the projects you actually track
|
|
447
444
|
agents projects add rush --repo phnx-labs/rush --path apps/web
|
|
448
445
|
agents projects list
|
|
449
|
-
agents projects status #
|
|
446
|
+
agents projects status # every project, across the whole fleet
|
|
450
447
|
agents projects status rush # one project (same body as view/show)
|
|
451
448
|
agents projects view rush # alias of status <name>
|
|
452
|
-
agents projects status --
|
|
449
|
+
agents projects status --device s0 # scope to one device (or --devices a,b,c)
|
|
453
450
|
agents projects link rush --linear # bind the Linear project (auto-suggest)
|
|
454
451
|
agents run --project rush # land an agent in the project
|
|
455
452
|
`,
|
|
456
453
|
notes: `
|
|
457
454
|
Definitions are hand-editable YAML in ~/.agents/projects/ and sync across
|
|
458
|
-
machines with 'agents push/pull'.
|
|
455
|
+
machines with 'agents push/pull'.
|
|
459
456
|
|
|
460
457
|
'import --from-factory' reads Factory's auto-detected registry, which
|
|
461
458
|
guesses from checkouts on disk — it imports only 'high' confidence rows by
|
|
@@ -463,18 +460,6 @@ export function registerProjectsCommands(program) {
|
|
|
463
460
|
with 'agents projects rm <name>'.
|
|
464
461
|
`,
|
|
465
462
|
});
|
|
466
|
-
projects.hook('preAction', (_thisCommand, actionCommand) => {
|
|
467
|
-
if (enabled)
|
|
468
|
-
return;
|
|
469
|
-
// `probe` is the peer half of `status --fleet`: it must answer whenever the
|
|
470
|
-
// binary carries it, even where the beta flag is off — a gated peer would
|
|
471
|
-
// look unreachable to every fleet member on a newer CLI.
|
|
472
|
-
if (actionCommand.name() === 'probe')
|
|
473
|
-
return;
|
|
474
|
-
console.error(chalk.red('agents projects is in beta.'));
|
|
475
|
-
console.error(chalk.gray(betaEnableHint('projects')));
|
|
476
|
-
process.exit(1);
|
|
477
|
-
});
|
|
478
463
|
// ---- list ----
|
|
479
464
|
projects
|
|
480
465
|
.command('list')
|
|
@@ -553,6 +538,15 @@ export function registerProjectsCommands(program) {
|
|
|
553
538
|
console.log(chalk.gray(` ${target}`));
|
|
554
539
|
console.log(chalk.gray(` root ${def.root}${def.repo ? ` · repo ${def.repo}` : ''}`));
|
|
555
540
|
});
|
|
541
|
+
/** Merge `--device a b` (variadic) and `--devices a,b,c` (comma list) into one
|
|
542
|
+
* deduped device filter; undefined when neither was given (= whole fleet). */
|
|
543
|
+
function resolveDeviceFilter(device, devices) {
|
|
544
|
+
const merged = [
|
|
545
|
+
...(device ?? []),
|
|
546
|
+
...(devices ? devices.split(',').map((s) => s.trim()).filter(Boolean) : []),
|
|
547
|
+
];
|
|
548
|
+
return merged.length ? [...new Set(merged)] : undefined;
|
|
549
|
+
}
|
|
556
550
|
/** Print the YAML-side fields that sit under the shared card in `view` mode. */
|
|
557
551
|
function printProjectDefinition(def, name) {
|
|
558
552
|
console.log();
|
|
@@ -598,16 +592,16 @@ export function registerProjectsCommands(program) {
|
|
|
598
592
|
}
|
|
599
593
|
const windowDays = Math.max(1, Number.parseInt(opts.window ?? '7', 10) || 7);
|
|
600
594
|
const nowMs = Date.now();
|
|
601
|
-
//
|
|
602
|
-
// locally
|
|
603
|
-
// live-session rollup to the
|
|
604
|
-
//
|
|
605
|
-
//
|
|
606
|
-
const fleetTargets =
|
|
595
|
+
// Fleet is the default: probe each shown def's workspace paths (root +
|
|
596
|
+
// repos[].path) locally AND on every peer in one parallel SSH round, and
|
|
597
|
+
// widen the live-session rollup to the fleet via the sessions fan-out.
|
|
598
|
+
// `--device`/`--devices` scopes the remote fan-out to a subset; with no
|
|
599
|
+
// filter every registered device is dialled.
|
|
600
|
+
const fleetTargets = [...new Set(defs.flatMap(workspaceTargetsForDef))];
|
|
607
601
|
let fleetWs = [];
|
|
608
602
|
let fleetSkipped = [];
|
|
609
603
|
let fleetSessions = [];
|
|
610
|
-
|
|
604
|
+
{
|
|
611
605
|
const self = machineId();
|
|
612
606
|
fleetWs.push(...probeProjectWorkspaces(fleetTargets).map((s) => ({ ...s, host: self })));
|
|
613
607
|
const [probeRes, activeRes] = await Promise.all([
|
|
@@ -615,11 +609,12 @@ export function registerProjectsCommands(program) {
|
|
|
615
609
|
? gatherRemoteAgentsJson({
|
|
616
610
|
args: ['projects', 'probe', '--json', ...fleetTargets],
|
|
617
611
|
noFanoutEnv: PROJECTS_NO_FANOUT_ENV,
|
|
612
|
+
hosts: opts.deviceFilter,
|
|
618
613
|
parse: parseRemoteProbe,
|
|
619
614
|
quiet: true,
|
|
620
615
|
})
|
|
621
616
|
: Promise.resolve({ items: [], deviceCount: 0, skipped: [] }),
|
|
622
|
-
gatherRemoteActive(
|
|
617
|
+
gatherRemoteActive(opts.deviceFilter, { quiet: true }),
|
|
623
618
|
]);
|
|
624
619
|
fleetWs.push(...probeRes.items);
|
|
625
620
|
fleetSkipped = probeRes.skipped;
|
|
@@ -636,17 +631,6 @@ export function registerProjectsCommands(program) {
|
|
|
636
631
|
const targets = new Set(workspaceTargetsForDef(d));
|
|
637
632
|
return fleetWs.filter((s) => targets.has(s.path));
|
|
638
633
|
};
|
|
639
|
-
/** Local-only workspace probe for the warnings footer when --fleet is off. */
|
|
640
|
-
const localWsCache = new Map();
|
|
641
|
-
const localWsFor = (d) => {
|
|
642
|
-
const cached = localWsCache.get(d.name);
|
|
643
|
-
if (cached)
|
|
644
|
-
return cached;
|
|
645
|
-
const self = machineId();
|
|
646
|
-
const rows = probeProjectWorkspaces(workspaceTargetsForDef(d)).map((s) => ({ ...s, host: self }));
|
|
647
|
-
localWsCache.set(d.name, rows);
|
|
648
|
-
return rows;
|
|
649
|
-
};
|
|
650
634
|
if (opts.json) {
|
|
651
635
|
if (fleetSkipped.length > 0)
|
|
652
636
|
process.stderr.write(formatFleetSkippedNote(fleetSkipped));
|
|
@@ -677,7 +661,7 @@ export function registerProjectsCommands(program) {
|
|
|
677
661
|
lastArtifact: rem?.lastArtifact ?? null,
|
|
678
662
|
windowDays,
|
|
679
663
|
repos: [d.repo, ...(d.repos ?? []).map((r2) => r2.slug)].filter(Boolean),
|
|
680
|
-
|
|
664
|
+
workspaces: fleetFor(d),
|
|
681
665
|
};
|
|
682
666
|
}), null, 2));
|
|
683
667
|
return;
|
|
@@ -685,10 +669,10 @@ export function registerProjectsCommands(program) {
|
|
|
685
669
|
// Compact rollup shows the next milestone; `view` shows every declared one.
|
|
686
670
|
const milestoneLimit = detail ? Number.POSITIVE_INFINITY : 1;
|
|
687
671
|
for (const d of defs) {
|
|
688
|
-
renderCard(d, roll.get(d.name), remote.get(d.name),
|
|
689
|
-
// Always feed workspace rows into the warnings footer
|
|
690
|
-
//
|
|
691
|
-
|
|
672
|
+
renderCard(d, roll.get(d.name), remote.get(d.name), fleetFor(d), linear.get(d.name), nowMs, milestoneLimit, focus.get(d.name) ?? [], detail,
|
|
673
|
+
// Always feed workspace rows into the warnings footer so behind/dirty
|
|
674
|
+
// is never silent.
|
|
675
|
+
fleetFor(d));
|
|
692
676
|
if (detail)
|
|
693
677
|
printProjectDefinition(d, d.name);
|
|
694
678
|
}
|
|
@@ -699,15 +683,23 @@ export function registerProjectsCommands(program) {
|
|
|
699
683
|
.command('status [name]')
|
|
700
684
|
.alias('view')
|
|
701
685
|
.alias('show')
|
|
702
|
-
.description('Progress card for every project, or one named project (aliases: view, show). Named form also prints every milestone and the stored definition.')
|
|
686
|
+
.description('Progress card for every project across the whole fleet, or one named project (aliases: view, show). Named form also prints every milestone and the stored definition.')
|
|
703
687
|
.option('--json', 'Machine-readable output')
|
|
704
688
|
.option('--window <days>', 'Window for merged PRs, artifacts, and focus areas', '7')
|
|
705
689
|
.option('--no-remote', 'Skip the GitHub and Linear lookups; faster, offline')
|
|
706
|
-
.option('--
|
|
707
|
-
.
|
|
690
|
+
.option('--device <name...>', 'Scope fleet status to one or more devices (repeatable)')
|
|
691
|
+
.option('--devices <names>', 'Scope fleet status to a comma-separated list of devices')
|
|
692
|
+
.action(async (name, rawOpts) => {
|
|
708
693
|
// Named invocation = `view` depth (all milestones + definition). Unnamed
|
|
709
694
|
// stays the scannable multi-project rollup. `view`/`show` are commander
|
|
710
695
|
// aliases of this same command, so there is only one implementation.
|
|
696
|
+
// Fleet is dialled by default; --device/--devices narrows it to a subset.
|
|
697
|
+
const opts = {
|
|
698
|
+
json: rawOpts.json,
|
|
699
|
+
window: rawOpts.window,
|
|
700
|
+
remote: rawOpts.remote,
|
|
701
|
+
deviceFilter: resolveDeviceFilter(rawOpts.device, rawOpts.devices),
|
|
702
|
+
};
|
|
711
703
|
const mode = name ? 'view' : 'status';
|
|
712
704
|
await runProjectCard(name, opts, mode);
|
|
713
705
|
});
|
package/dist/index.js
CHANGED
|
@@ -1177,7 +1177,7 @@ if (process.env.AGENTS_SKIP_MIGRATION !== '1') {
|
|
|
1177
1177
|
// Bumping the suffix re-runs migrations for every user; binary releases that
|
|
1178
1178
|
// don't change the schema must NOT re-run (they would destroy user content
|
|
1179
1179
|
// when migration steps overlap with user-authored paths). See issue #20.
|
|
1180
|
-
const sentinelValue = '
|
|
1180
|
+
const sentinelValue = 'v14';
|
|
1181
1181
|
let needRun = true;
|
|
1182
1182
|
try {
|
|
1183
1183
|
if (fs.existsSync(sentinel) && fs.readFileSync(sentinel, 'utf-8').trim() === sentinelValue) {
|
package/dist/lib/beta.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* checks.
|
|
9
9
|
*/
|
|
10
10
|
import type { BetaFeatureName } from './types.js';
|
|
11
|
-
export declare const ALL_BETA_FEATURES: readonly ["factory"
|
|
11
|
+
export declare const ALL_BETA_FEATURES: readonly ["factory"];
|
|
12
12
|
export declare function getEnabledBetaFeatures(): BetaFeatureName[];
|
|
13
13
|
export declare function isBetaEnabled(feature: BetaFeatureName): boolean;
|
|
14
14
|
export declare function getBetaConfigLocation(): {
|
package/dist/lib/beta.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import * as path from 'path';
|
|
11
11
|
import { getAgentsDir, getOptionalUserAgentsDir, readMeta, writeMeta } from './state.js';
|
|
12
12
|
import { readManifest, writeManifest } from './manifest.js';
|
|
13
|
-
export const ALL_BETA_FEATURES = ['factory'
|
|
13
|
+
export const ALL_BETA_FEATURES = ['factory'];
|
|
14
14
|
function isBetaFeatureName(value) {
|
|
15
15
|
return typeof value === 'string' && ALL_BETA_FEATURES.includes(value);
|
|
16
16
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The unified event reader -- one stream over BOTH operational events
|
|
3
|
-
* (`~/.agents/events.jsonl` via events.ts: secrets, commands, teams, ...) and
|
|
3
|
+
* (`~/.agents/.history/events/events.jsonl` via events.ts: secrets, commands, teams, ...) and
|
|
4
4
|
* agent-semantic events (the per-session activity logs via activity.ts: plans,
|
|
5
5
|
* PRs, worktrees, sub-agents, artifacts). They share one {@link EventType}
|
|
6
6
|
* vocabulary and one {@link EventRecord} shape, so `agents events` and any
|
package/dist/lib/event-stream.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The unified event reader -- one stream over BOTH operational events
|
|
3
|
-
* (`~/.agents/events.jsonl` via events.ts: secrets, commands, teams, ...) and
|
|
3
|
+
* (`~/.agents/.history/events/events.jsonl` via events.ts: secrets, commands, teams, ...) and
|
|
4
4
|
* agent-semantic events (the per-session activity logs via activity.ts: plans,
|
|
5
5
|
* PRs, worktrees, sub-agents, artifacts). They share one {@link EventType}
|
|
6
6
|
* vocabulary and one {@link EventRecord} shape, so `agents events` and any
|
package/dist/lib/events.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Centralized event logging for agents-cli.
|
|
3
3
|
*
|
|
4
|
-
* Structured JSONL audit log at ~/.agents/events.jsonl with lossless numbered
|
|
4
|
+
* Structured JSONL audit log at ~/.agents/.history/events/events.jsonl with lossless numbered
|
|
5
5
|
* gzip rotation at 10 MB and rich metadata for debugging/auditing.
|
|
6
6
|
*
|
|
7
7
|
* Features:
|
package/dist/lib/events.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Centralized event logging for agents-cli.
|
|
3
3
|
*
|
|
4
|
-
* Structured JSONL audit log at ~/.agents/events.jsonl with lossless numbered
|
|
4
|
+
* Structured JSONL audit log at ~/.agents/.history/events/events.jsonl with lossless numbered
|
|
5
5
|
* gzip rotation at 10 MB and rich metadata for debugging/auditing.
|
|
6
6
|
*
|
|
7
7
|
* Features:
|
|
@@ -17,7 +17,7 @@ import * as os from 'os';
|
|
|
17
17
|
import { createHash } from 'node:crypto';
|
|
18
18
|
import { gzipSync, gunzipSync } from 'node:zlib';
|
|
19
19
|
import { ensureLockTarget, withFileLock } from './fs-atomic.js';
|
|
20
|
-
import {
|
|
20
|
+
import { getHistoryDir } from './state.js';
|
|
21
21
|
import { stampProvenance, resetEventProvenanceForTest } from './event-provenance.js';
|
|
22
22
|
/** Lazy perf warehouse write — avoids a hard cycle at module load. */
|
|
23
23
|
function recordPerfTiming(payload) {
|
|
@@ -48,7 +48,7 @@ function recordPerfTiming(payload) {
|
|
|
48
48
|
// a test spawns, so fixture events can never land in the user's real log (#910).
|
|
49
49
|
let _eventsPath;
|
|
50
50
|
function eventsPath() {
|
|
51
|
-
return (_eventsPath ??= process.env.AGENTS_EVENTS_PATH || path.join(
|
|
51
|
+
return (_eventsPath ??= process.env.AGENTS_EVENTS_PATH || path.join(getHistoryDir(), 'events', 'events.jsonl'));
|
|
52
52
|
}
|
|
53
53
|
function eventsDir() {
|
|
54
54
|
return path.dirname(eventsPath());
|
|
@@ -48,8 +48,14 @@ export interface FeedBroadcastContext {
|
|
|
48
48
|
class?: string;
|
|
49
49
|
/** Block-only: cost-of-delay tag used by the urgency filter. */
|
|
50
50
|
cost?: string;
|
|
51
|
-
/** Block-only: the literal `agents focus <id>` command
|
|
51
|
+
/** Block-only: the literal `agents focus <id>` command (for a `{focus}` sink). */
|
|
52
52
|
focus?: string;
|
|
53
|
+
/** Block-only: the answer choices the operator can pick, in order. */
|
|
54
|
+
options?: string[];
|
|
55
|
+
/** Block-only: the fallback applied if nobody answers in time. */
|
|
56
|
+
safeDefault?: string;
|
|
57
|
+
/** Block-only: minutes before `safeDefault` applies. */
|
|
58
|
+
timeoutMinutes?: number;
|
|
53
59
|
}
|
|
54
60
|
/**
|
|
55
61
|
* Map an open block onto the broadcast context, so a block reaches the same sinks
|
|
@@ -69,9 +75,14 @@ export declare function blockBroadcastContext(block: {
|
|
|
69
75
|
host?: string;
|
|
70
76
|
questions?: Array<{
|
|
71
77
|
text?: string;
|
|
78
|
+
options?: Array<{
|
|
79
|
+
label?: string;
|
|
80
|
+
}>;
|
|
72
81
|
}>;
|
|
73
82
|
blockClass?: string;
|
|
74
83
|
costOfDelay?: string;
|
|
84
|
+
safeDefault?: string;
|
|
85
|
+
timeoutMinutes?: number;
|
|
75
86
|
ticket?: string;
|
|
76
87
|
pr?: string;
|
|
77
88
|
}, extras?: {
|
|
@@ -137,14 +148,17 @@ export declare function composeBroadcastFooter(ctx: FeedBroadcastContext): strin
|
|
|
137
148
|
* Title in a few words
|
|
138
149
|
*
|
|
139
150
|
* Body of what happened or the ask.
|
|
151
|
+
* Options: publish / wait (blocks with choices)
|
|
152
|
+
* Default in 15 min: wait (blocks with a safe default)
|
|
140
153
|
*
|
|
141
154
|
* Sent from grok/a02da0e2 on mac-mini
|
|
142
|
-
*
|
|
143
|
-
* https://… (optional attach URL)
|
|
155
|
+
* https://… (optional attach URL)
|
|
144
156
|
* ```
|
|
145
157
|
*
|
|
146
|
-
* Title first (scannable subject). Blank line. Body.
|
|
147
|
-
*
|
|
158
|
+
* Title first (scannable subject). Blank line. Body. Then the phone-actionable
|
|
159
|
+
* choices + default (a block that has stopped for the human), then footer
|
|
160
|
+
* provenance. No `agents focus <id>` line: a CLI command is unusable from a phone,
|
|
161
|
+
* so the safe default is the fallback and the message carries it. Prefer `{message}`
|
|
148
162
|
* over bare `{text}` in messaging sinks.
|
|
149
163
|
*/
|
|
150
164
|
export declare function composeBroadcastMessage(ctx: FeedBroadcastContext): string;
|
|
@@ -62,6 +62,12 @@ export function parseFeedPostLevel(raw) {
|
|
|
62
62
|
export function blockBroadcastContext(block, extras = {}) {
|
|
63
63
|
const ask = block.questions?.[0]?.text?.trim() || 'agent is blocked';
|
|
64
64
|
const links = [block.pr].filter((l) => !!l && /^https?:\/\//i.test(l));
|
|
65
|
+
// The answer choices + the safe default are what make the phone message
|
|
66
|
+
// actionable: the operator sees the options and what happens if they do not
|
|
67
|
+
// reply, instead of a `agents focus <id>` CLI command they cannot run from a phone.
|
|
68
|
+
const options = (block.questions?.[0]?.options ?? [])
|
|
69
|
+
.map((o) => o?.label?.trim())
|
|
70
|
+
.filter((l) => !!l);
|
|
65
71
|
// Prefer explicit title/body from the feed post; fall back to the ask as body.
|
|
66
72
|
const title = extras.title?.trim() || undefined;
|
|
67
73
|
const text = extras.body?.trim() || ask;
|
|
@@ -78,8 +84,12 @@ export function blockBroadcastContext(block, extras = {}) {
|
|
|
78
84
|
class: block.blockClass,
|
|
79
85
|
cost: block.costOfDelay,
|
|
80
86
|
// Short id: `agents focus` matches on a prefix, and a full uuid in a phone
|
|
81
|
-
// message is noise
|
|
87
|
+
// message is noise. Kept for a `{focus}` sink; the human message no longer
|
|
88
|
+
// shows it (a CLI command is unusable from a phone).
|
|
82
89
|
focus: `agents focus ${block.sessionId.slice(0, 8)}`,
|
|
90
|
+
...(options.length ? { options } : {}),
|
|
91
|
+
...(block.safeDefault ? { safeDefault: block.safeDefault } : {}),
|
|
92
|
+
...(block.timeoutMinutes ? { timeoutMinutes: block.timeoutMinutes } : {}),
|
|
83
93
|
...(links.length ? { links } : {}),
|
|
84
94
|
};
|
|
85
95
|
}
|
|
@@ -179,14 +189,17 @@ export function composeBroadcastFooter(ctx) {
|
|
|
179
189
|
* Title in a few words
|
|
180
190
|
*
|
|
181
191
|
* Body of what happened or the ask.
|
|
192
|
+
* Options: publish / wait (blocks with choices)
|
|
193
|
+
* Default in 15 min: wait (blocks with a safe default)
|
|
182
194
|
*
|
|
183
195
|
* Sent from grok/a02da0e2 on mac-mini
|
|
184
|
-
*
|
|
185
|
-
* https://… (optional attach URL)
|
|
196
|
+
* https://… (optional attach URL)
|
|
186
197
|
* ```
|
|
187
198
|
*
|
|
188
|
-
* Title first (scannable subject). Blank line. Body.
|
|
189
|
-
*
|
|
199
|
+
* Title first (scannable subject). Blank line. Body. Then the phone-actionable
|
|
200
|
+
* choices + default (a block that has stopped for the human), then footer
|
|
201
|
+
* provenance. No `agents focus <id>` line: a CLI command is unusable from a phone,
|
|
202
|
+
* so the safe default is the fallback and the message carries it. Prefer `{message}`
|
|
190
203
|
* over bare `{text}` in messaging sinks.
|
|
191
204
|
*/
|
|
192
205
|
export function composeBroadcastMessage(ctx) {
|
|
@@ -197,9 +210,21 @@ export function composeBroadcastMessage(ctx) {
|
|
|
197
210
|
const mid = title && body && title !== body ? body : undefined;
|
|
198
211
|
const footer = composeBroadcastFooter(ctx);
|
|
199
212
|
const link = ctx.links?.find((l) => /^https?:\/\//i.test(l));
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
|
|
213
|
+
// The action block: the one thing the operator can act on from a phone. Show the
|
|
214
|
+
// choices, then what happens if they do not answer. Deliberately NOT a CLI command
|
|
215
|
+
// (`agents focus <id>` is unusable from a phone) -- the safe default is the real
|
|
216
|
+
// fallback, so a block meant for a phone should always carry one.
|
|
217
|
+
const choices = ctx.options?.length
|
|
218
|
+
? `Options: ${ctx.options.map((o) => scrubOutboundDashes(o)).join(' / ')}`
|
|
219
|
+
: undefined;
|
|
220
|
+
const fallback = ctx.safeDefault
|
|
221
|
+
? (ctx.timeoutMinutes && ctx.timeoutMinutes > 0
|
|
222
|
+
? `Default in ${ctx.timeoutMinutes} min: ${scrubOutboundDashes(ctx.safeDefault)}`
|
|
223
|
+
: `Default: ${scrubOutboundDashes(ctx.safeDefault)}`)
|
|
224
|
+
: undefined;
|
|
225
|
+
const action = [choices, fallback].filter(Boolean).join('\n') || undefined;
|
|
226
|
+
// Link trail after the "Sent from" footer so the human sentence stays at the top.
|
|
227
|
+
const trail = [footer, link].filter(Boolean);
|
|
203
228
|
const parts = [];
|
|
204
229
|
if (head)
|
|
205
230
|
parts.push(head);
|
|
@@ -208,6 +233,12 @@ export function composeBroadcastMessage(ctx) {
|
|
|
208
233
|
parts.push('');
|
|
209
234
|
parts.push(mid);
|
|
210
235
|
}
|
|
236
|
+
if (action) {
|
|
237
|
+
// The choices/default hug the ask under a blank line so they read as the reply.
|
|
238
|
+
if (parts.length)
|
|
239
|
+
parts.push('');
|
|
240
|
+
parts.push(action);
|
|
241
|
+
}
|
|
211
242
|
if (trail.length) {
|
|
212
243
|
// Blank line before the footer block (iPhone "Sent from my iPhone" spacing).
|
|
213
244
|
if (parts.length)
|
|
@@ -233,6 +264,8 @@ function templateVars(ctx) {
|
|
|
233
264
|
class: ctx.class,
|
|
234
265
|
cost: ctx.cost,
|
|
235
266
|
focus: ctx.focus,
|
|
267
|
+
options: ctx.options?.length ? ctx.options.join(' / ') : undefined,
|
|
268
|
+
default: ctx.safeDefault,
|
|
236
269
|
};
|
|
237
270
|
}
|
|
238
271
|
/**
|
|
Binary file
|
|
Binary file
|
package/dist/lib/migrate.js
CHANGED
|
@@ -998,6 +998,7 @@ function migrateRuntimeToHistory() {
|
|
|
998
998
|
moveDirOnce(path.join(USER_DIR, '.backups'), path.join(HISTORY_DIR, 'backups'));
|
|
999
999
|
moveDirOnce(path.join(USER_DIR, 'routines', 'runs'), path.join(HISTORY_DIR, 'runs'));
|
|
1000
1000
|
moveDirOnce(path.join(USER_DIR, 'teams', 'agents'), path.join(HISTORY_DIR, 'teams', 'agents'));
|
|
1001
|
+
migrateEventLogsToHistory();
|
|
1001
1002
|
// Drop any empty leftover skeletons created mid-rename (e.g. `versions/<agent>/<v>/home/`
|
|
1002
1003
|
// recreated by a concurrent process). The real data is already under .history/.
|
|
1003
1004
|
rmEmptyDirTree(path.join(USER_DIR, 'versions'));
|
|
@@ -1013,6 +1014,66 @@ function migrateRuntimeToHistory() {
|
|
|
1013
1014
|
catch { /* best-effort */ }
|
|
1014
1015
|
}
|
|
1015
1016
|
}
|
|
1017
|
+
/** Move the operational event stream out of the git-backed user-repo root. */
|
|
1018
|
+
function migrateEventLogsToHistory() {
|
|
1019
|
+
const destination = path.join(HISTORY_DIR, 'events');
|
|
1020
|
+
let files = [];
|
|
1021
|
+
try {
|
|
1022
|
+
files = fs.readdirSync(USER_DIR).filter((file) => file === 'events.jsonl' || /^events\.\d+\.jsonl\.gz$/.test(file));
|
|
1023
|
+
}
|
|
1024
|
+
catch {
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
try {
|
|
1028
|
+
fs.mkdirSync(destination, { recursive: true, mode: 0o700 });
|
|
1029
|
+
}
|
|
1030
|
+
catch {
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
const active = files.find((file) => file === 'events.jsonl');
|
|
1034
|
+
if (active) {
|
|
1035
|
+
const src = path.join(USER_DIR, active);
|
|
1036
|
+
const dest = path.join(destination, active);
|
|
1037
|
+
if (!fs.existsSync(dest)) {
|
|
1038
|
+
moveFileOnce(src, dest);
|
|
1039
|
+
}
|
|
1040
|
+
else {
|
|
1041
|
+
try {
|
|
1042
|
+
const lines = [fs.readFileSync(src, 'utf-8'), fs.readFileSync(dest, 'utf-8')]
|
|
1043
|
+
.flatMap((content) => content.split('\n').filter(Boolean))
|
|
1044
|
+
.map((line, index) => {
|
|
1045
|
+
try {
|
|
1046
|
+
const timestamp = Date.parse(JSON.parse(line).ts ?? '');
|
|
1047
|
+
return { line, index, timestamp: Number.isNaN(timestamp) ? Number.MAX_SAFE_INTEGER : timestamp };
|
|
1048
|
+
}
|
|
1049
|
+
catch {
|
|
1050
|
+
return { line, index, timestamp: Number.MAX_SAFE_INTEGER };
|
|
1051
|
+
}
|
|
1052
|
+
})
|
|
1053
|
+
.sort((a, b) => a.timestamp - b.timestamp || a.index - b.index)
|
|
1054
|
+
.map(({ line }) => line);
|
|
1055
|
+
atomicWriteFileSync(dest, `${lines.join('\n')}\n`, { mode: 0o600 });
|
|
1056
|
+
fs.unlinkSync(src);
|
|
1057
|
+
}
|
|
1058
|
+
catch { /* preserve the source for a later retry */ }
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
const archives = files
|
|
1062
|
+
.map((file) => ({ file, match: file.match(/^events\.(\d+)\.jsonl\.gz$/) }))
|
|
1063
|
+
.filter((entry) => entry.match !== null)
|
|
1064
|
+
.sort((a, b) => Number(a.match[1]) - Number(b.match[1]));
|
|
1065
|
+
let nextArchive = fs.readdirSync(destination).reduce((max, file) => {
|
|
1066
|
+
const match = file.match(/^events\.(\d+)\.jsonl\.gz$/);
|
|
1067
|
+
return match ? Math.max(max, Number(match[1])) : max;
|
|
1068
|
+
}, 0) + 1;
|
|
1069
|
+
for (const archive of archives) {
|
|
1070
|
+
const preferred = path.join(destination, archive.file);
|
|
1071
|
+
const dest = fs.existsSync(preferred)
|
|
1072
|
+
? path.join(destination, `events.${nextArchive++}.jsonl.gz`)
|
|
1073
|
+
: preferred;
|
|
1074
|
+
moveFileOnce(path.join(USER_DIR, archive.file), dest);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1016
1077
|
/**
|
|
1017
1078
|
* Restore plugins from the cache bucket back to the user-root.
|
|
1018
1079
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Project workspace probing — the drift signal behind `projects status
|
|
2
|
+
* Project workspace probing — the drift signal behind `projects status`.
|
|
3
3
|
*
|
|
4
4
|
* Projects are natively multi-device: the same definition (home-relative paths)
|
|
5
5
|
* re-roots on every fleet machine, and the question is whether the project's
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Project workspace probing — the drift signal behind `projects status
|
|
2
|
+
* Project workspace probing — the drift signal behind `projects status`.
|
|
3
3
|
*
|
|
4
4
|
* Projects are natively multi-device: the same definition (home-relative paths)
|
|
5
5
|
* re-roots on every fleet machine, and the question is whether the project's
|
package/dist/lib/projects.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export interface ProjectRepo {
|
|
|
36
36
|
/**
|
|
37
37
|
* Optional home-relative local checkout of this repo. The def's `root` only
|
|
38
38
|
* knows the primary repo on disk; `path` opts an additional repo into
|
|
39
|
-
* workspace probing (`projects status
|
|
39
|
+
* workspace probing (`projects status`).
|
|
40
40
|
*/
|
|
41
41
|
path?: string;
|
|
42
42
|
}
|
|
Binary file
|
|
Binary file
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* This is the ONE write path for secret events. Every path that creates,
|
|
5
5
|
* imports, exports, views, reads a VALUE from, or unlocks a bundle funnels its
|
|
6
6
|
* audit through here, so the operational event stream — `agents events`, backed
|
|
7
|
-
* by the append-only `~/.agents/events.jsonl` audit log — carries a uniform,
|
|
7
|
+
* by the append-only `~/.agents/.history/events/events.jsonl` audit log — carries a uniform,
|
|
8
8
|
* value-free provenance record: bundle, key NAMES, the resolving agent/harness
|
|
9
9
|
* identity, operation, source, status. The ts / host / session / caller fields
|
|
10
10
|
* are filled in by `emit()` itself. The secret VALUE is never part of the
|
package/dist/lib/state.d.ts
CHANGED
|
@@ -113,7 +113,7 @@ export declare function getUserSecretsDir(): string;
|
|
|
113
113
|
* value-free usage telemetry (which bundle was created/imported/exported/viewed/
|
|
114
114
|
* accessed/unlocked, when, by whom), never a secret value. It is a derived index
|
|
115
115
|
* fed FROM the emitSecretAudit chokepoint alongside the append-only
|
|
116
|
-
* ~/.agents/events.jsonl audit log — the same way sessions.db indexes session
|
|
116
|
+
* ~/.agents/.history/events/events.jsonl audit log — the same way sessions.db indexes session
|
|
117
117
|
* metadata off the real session flow — not a second write path.
|
|
118
118
|
*/
|
|
119
119
|
export declare function getSecretsDbPath(): string;
|
package/dist/lib/state.js
CHANGED
|
@@ -372,7 +372,7 @@ export function getUserSecretsDir() { return USER_SECRETS_DIR; }
|
|
|
372
372
|
* value-free usage telemetry (which bundle was created/imported/exported/viewed/
|
|
373
373
|
* accessed/unlocked, when, by whom), never a secret value. It is a derived index
|
|
374
374
|
* fed FROM the emitSecretAudit chokepoint alongside the append-only
|
|
375
|
-
* ~/.agents/events.jsonl audit log — the same way sessions.db indexes session
|
|
375
|
+
* ~/.agents/.history/events/events.jsonl audit log — the same way sessions.db indexes session
|
|
376
376
|
* metadata off the real session flow — not a second write path.
|
|
377
377
|
*/
|
|
378
378
|
export function getSecretsDbPath() {
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -79,7 +79,7 @@ export interface BudgetConfig {
|
|
|
79
79
|
require_confirm_over?: number;
|
|
80
80
|
}
|
|
81
81
|
/** Preview features that users can opt into via `agents beta`. */
|
|
82
|
-
export type BetaFeatureName = 'factory'
|
|
82
|
+
export type BetaFeatureName = 'factory';
|
|
83
83
|
/** Subset of chalk color names used for agent-specific terminal output. */
|
|
84
84
|
export type ChalkColor = 'magenta' | 'green' | 'blue' | 'cyan' | 'yellowBright' | 'redBright' | 'whiteBright' | 'blueBright' | 'greenBright' | 'magentaBright' | 'cyanBright';
|
|
85
85
|
/** Static configuration for a single agent -- paths, capabilities, and format conventions. */
|
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.11",
|
|
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",
|