@phnx-labs/agents-cli 1.22.11 → 1.22.13

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +5 -1
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/cli.js +12 -12
  5. package/dist/commands/events.d.ts +20 -1
  6. package/dist/commands/events.js +71 -55
  7. package/dist/commands/logs.js +14 -138
  8. package/dist/commands/repo.d.ts +1 -1
  9. package/dist/commands/repo.js +3 -3
  10. package/dist/commands/sessions.d.ts +15 -0
  11. package/dist/commands/sessions.js +79 -10
  12. package/dist/commands/view.d.ts +1 -1
  13. package/dist/commands/view.js +7 -7
  14. package/dist/commands/workflows.js +1 -0
  15. package/dist/lib/cli-resources.js +2 -2
  16. package/dist/lib/event-stream.d.ts +1 -1
  17. package/dist/lib/event-stream.js +1 -1
  18. package/dist/lib/events.d.ts +21 -12
  19. package/dist/lib/events.js +312 -65
  20. package/dist/lib/exec.js +1 -2
  21. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  22. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  23. package/dist/lib/migrate.d.ts +8 -0
  24. package/dist/lib/migrate.js +26 -62
  25. package/dist/lib/resources/workflows.js +41 -21
  26. package/dist/lib/resources.d.ts +1 -1
  27. package/dist/lib/runner.js +1 -2
  28. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  29. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  30. package/dist/lib/secrets/audit.js +1 -1
  31. package/dist/lib/startup/command-registry.js +1 -1
  32. package/dist/lib/state.d.ts +1 -1
  33. package/dist/lib/state.js +60 -2
  34. package/dist/lib/watchdog/runner.d.ts +13 -0
  35. package/dist/lib/watchdog/runner.js +75 -6
  36. package/dist/lib/workflows.d.ts +22 -4
  37. package/dist/lib/workflows.js +79 -29
  38. package/package.json +1 -1
@@ -1091,9 +1091,12 @@ async function renderActiveSessions(asJson, waitingOnly = false, opts = {}) {
1091
1091
  const merged = opts.favoritesOnly
1092
1092
  ? gathered.sessions.filter((s) => !!s.sessionId && listFavorites().has(s.sessionId))
1093
1093
  : gathered.sessions;
1094
- // --waiting: only sessions blocked on the user. Exits non-zero when any are
1095
- // present so a supervising agent or hook can poll it as a gate.
1096
- const sessions = waitingOnly ? merged.filter(isAwaitingUser) : merged;
1094
+ // Status flags form a union. --waiting additionally retains its scriptable
1095
+ // gate: exit non-zero when the union contains a session awaiting the user.
1096
+ const statusFiltered = opts.statuses?.length
1097
+ ? merged.filter((session) => opts.statuses.some((status) => matchesLiveStatus(session, status)))
1098
+ : merged;
1099
+ const sessions = statusFiltered;
1097
1100
  if (asJson) {
1098
1101
  // Resolve who is watching each local tmux pane before serializing: `viewingIn`
1099
1102
  // is how a consumer distinguishes a session someone is looking at from one
@@ -1101,7 +1104,7 @@ async function renderActiveSessions(asJson, waitingOnly = false, opts = {}) {
1101
1104
  // scriptable path stays cheap — see enrichTmuxLocators.
1102
1105
  await enrichTmuxLocators(sessions.filter(s => !s.machine || s.machine === self));
1103
1106
  process.stdout.write(JSON.stringify(serializeActiveSessionsForJson(sessions), null, 2) + '\n');
1104
- if (waitingOnly && sessions.length > 0)
1107
+ if (waitingOnly && sessions.some(isAwaitingUser))
1105
1108
  process.exitCode = 1;
1106
1109
  return;
1107
1110
  }
@@ -1136,9 +1139,40 @@ async function renderActiveSessions(asJson, waitingOnly = false, opts = {}) {
1136
1139
  if (!opts.local && !opts.hosts?.length && remoteDeviceCount === 0)
1137
1140
  printCrossMachineTip();
1138
1141
  // Scriptable gate: a non-zero exit when anything is waiting on the user.
1139
- if (waitingOnly && sessions.length > 0)
1142
+ if (waitingOnly && sessions.some(isAwaitingUser))
1140
1143
  process.exitCode = 1;
1141
1144
  }
1145
+ /** Match the status words users see, preserving activity's richer working signal. */
1146
+ export function matchesLiveStatus(session, status) {
1147
+ if (status === 'working')
1148
+ return session.activity === 'working' || (!session.activity && session.status === 'running');
1149
+ if (status === 'waiting')
1150
+ return isAwaitingUser(session);
1151
+ return session.status === status;
1152
+ }
1153
+ /** Resolve convenience flags once. Multiple flags intentionally form a union. */
1154
+ export function requestedLiveStatuses(options) {
1155
+ const statuses = [];
1156
+ if (options.working)
1157
+ statuses.push('working');
1158
+ if (options.idle)
1159
+ statuses.push('idle');
1160
+ if (options.waiting)
1161
+ statuses.push('waiting');
1162
+ if (options.orphan || options.orphaned)
1163
+ statuses.push('orphaned');
1164
+ if (options.crashed)
1165
+ statuses.push('crashed');
1166
+ if (options.closed)
1167
+ statuses.push('closed');
1168
+ if (options.abandoned)
1169
+ statuses.push('abandoned');
1170
+ if (options.queued)
1171
+ statuses.push('queued');
1172
+ if (options.unknown)
1173
+ statuses.push('unknown');
1174
+ return [...new Set(statuses)];
1175
+ }
1142
1176
  /** Nudge shown when `--active` has no other machines to fold in. */
1143
1177
  function printCrossMachineTip() {
1144
1178
  console.log(chalk.gray("\nTip: include sessions from your other machines — register them with 'ag devices sync', then rerun. Use --local to skip."));
@@ -1204,6 +1238,22 @@ function canonicalSessionsCommand(query, options) {
1204
1238
  const a = ['sessions'];
1205
1239
  if (options.active)
1206
1240
  a.push('--active');
1241
+ if (options.working)
1242
+ a.push('--working');
1243
+ if (options.idle)
1244
+ a.push('--idle');
1245
+ if (options.orphan || options.orphaned)
1246
+ a.push('--orphan');
1247
+ if (options.crashed)
1248
+ a.push('--crashed');
1249
+ if (options.closed)
1250
+ a.push('--closed');
1251
+ if (options.abandoned)
1252
+ a.push('--abandoned');
1253
+ if (options.queued)
1254
+ a.push('--queued');
1255
+ if (options.unknown)
1256
+ a.push('--unknown');
1207
1257
  if (options.teams)
1208
1258
  a.push('--teams');
1209
1259
  if (options.inTeam)
@@ -1421,6 +1471,8 @@ async function sessionsAction(query, options,
1421
1471
  */
1422
1472
  limitSource) {
1423
1473
  const queryClauses = options.query ?? [];
1474
+ const liveStatuses = requestedLiveStatuses(options);
1475
+ const liveOnly = options.active === true || liveStatuses.length > 0;
1424
1476
  const toolOnly = options.include?.split(',').map((role) => role.trim()).filter(Boolean).join(',') === 'tools';
1425
1477
  const toolEvidenceMode = toolOnly;
1426
1478
  if (options.count && !toolOnly) {
@@ -1551,7 +1603,7 @@ limitSource) {
1551
1603
  // keeps the legacy per-host stream (each remote's raw stdout under a
1552
1604
  // `── host ──` banner). With --active, the hosts are folded into the merged
1553
1605
  // machine-grouped view instead (handled below).
1554
- if (options.host && options.host.length > 0 && !options.active && !toolEvidenceMode) {
1606
+ if (options.host && options.host.length > 0 && !liveOnly && !toolEvidenceMode) {
1555
1607
  // --local means "skip the SSH fan-out"; --host means "look only over there".
1556
1608
  // Together they ask for a peer's sessions without dialing the peer, which can
1557
1609
  // only ever be empty — so say that instead of rendering a blank list.
@@ -1591,7 +1643,7 @@ limitSource) {
1591
1643
  await renderSessionPreview(query, { agent: options.agent, project: options.project, local: options.local });
1592
1644
  return;
1593
1645
  }
1594
- if (options.active) {
1646
+ if (liveOnly) {
1595
1647
  // The running view is built from the live scan, which carries no team lineage
1596
1648
  // (that comes off the transcript index and the teams meta dir), so --in-team
1597
1649
  // has nothing to match on here. Say so rather than ignoring the flag.
@@ -1606,7 +1658,7 @@ limitSource) {
1606
1658
  // --since seeds the window; --until / --project (no browser field) or a
1607
1659
  // multi-host scope fall through to the static dump that already honors them.
1608
1660
  if (useInteractiveBrowser(options) &&
1609
- !options.waiting &&
1661
+ liveStatuses.length === 0 &&
1610
1662
  !options.until &&
1611
1663
  !options.project &&
1612
1664
  !options.sort &&
@@ -1630,6 +1682,7 @@ limitSource) {
1630
1682
  local: forceLocal,
1631
1683
  hosts: options.host,
1632
1684
  favoritesOnly: options.favorites === true,
1685
+ statuses: liveStatuses,
1633
1686
  });
1634
1687
  return;
1635
1688
  }
@@ -3617,7 +3670,16 @@ export function registerSessionsCommands(program) {
3617
3670
  .option('--active', 'Show only sessions running right now across terminals, teams, cloud, and headless agents')
3618
3671
  .option('--roots', 'With --json: emit the on-disk directories scanned for session transcripts, per agent (for external watchers)')
3619
3672
  .option('--local', 'Only this machine — skip the cross-machine SSH fan-out (default listing and --active)')
3620
- .option('--waiting', 'With --active: show only sessions waiting on your input (exits non-zero if any)')
3673
+ .option('--working', 'Show live sessions currently doing work (implies --active)')
3674
+ .option('--idle', 'Show live sessions that have stopped between turns (implies --active)')
3675
+ .option('--waiting', 'Show live sessions waiting on your input; exits non-zero if any (implies --active)')
3676
+ .option('--orphan', 'Show live sessions whose process outlived its terminal client (implies --active)')
3677
+ .option('--orphaned', 'Alias for --orphan')
3678
+ .option('--crashed', 'Show sessions whose terminal disappeared with the process (implies --active)')
3679
+ .option('--closed', 'Show recently observed sessions whose process exited normally (implies --active)')
3680
+ .option('--abandoned', 'Show sessions with no transcript progress for the abandonment window (implies --active)')
3681
+ .option('--queued', 'Show queued sessions that have not started running (implies --active)')
3682
+ .option('--unknown', 'Show sessions whose live state cannot be determined (implies --active)')
3621
3683
  .option('--favorites', 'Show only favorited (starred) sessions — star them with `*` in the browser or `agents sessions favorite <id>`')
3622
3684
  .option('--tree', 'Group the listing by directory; drops the id/version columns for readability')
3623
3685
  .option('--flat', 'Plain flat table (one row per session) instead of the grouped project overview')
@@ -3645,6 +3707,12 @@ export function registerSessionsCommands(program) {
3645
3707
  # Show only what's running right now (terminals, teams, cloud, headless)
3646
3708
  agents sessions --active
3647
3709
 
3710
+ # Filter the live fleet by the status word shown in the roster
3711
+ agents sessions --working
3712
+ agents sessions --idle
3713
+ agents sessions --orphan
3714
+ agents sessions --crashed
3715
+
3648
3716
  # --- Session lifecycle (one verb per intent) ---
3649
3717
  # Jump to a live session (attach its terminal, or open a tab + resume)
3650
3718
  agents sessions focus a1b2c3d4
@@ -3703,7 +3771,8 @@ export function registerSessionsCommands(program) {
3703
3771
  detach <id> interactive → headless continuation
3704
3772
  attach <id> headless → interactive in this terminal
3705
3773
  resume [query] multi-select history → open tabs (or run --resume <id>)
3706
- - The interactive listing folds in your other online machines automatically (live over SSH, no sync) — each row is labelled by host, this machine first. Use --local to skip the fan-out; --json and single-id lookups stay local.
3774
+ - The interactive listing and every live-status flag fold in your other online machines automatically (live over SSH, no sync) — each row is labelled by host, this machine first. Use --local to skip the fan-out; single-id lookups stay local.
3775
+ - --all is not a device flag: it widens historical directory and time filters. Fleet collection is already the default. A status flag (--working/--idle/--waiting/--orphan/--crashed/--closed/--abandoned/--queued/--unknown) implies --active; combine status flags for a union.
3707
3776
  - --host runs the query on the remote's own index over SSH (host alias or user@host); repeat or pass several to fan out. SSH access is the only auth.
3708
3777
  - --in-team matches both ends of the lineage: the session that ran 'agents teams create/add', and (with --teams) that team's teammates. In the interactive list, 't' cycles the same filter over the teams in view.
3709
3778
  - --include and --exclude are mutually exclusive.
@@ -32,7 +32,7 @@ export interface ViewSectionFilter {
32
32
  rules?: boolean;
33
33
  hooks?: boolean;
34
34
  promptcuts?: boolean;
35
- cli?: boolean;
35
+ clis?: boolean;
36
36
  }
37
37
  /** Trim a description to a column-friendly snippet. Strips newlines, collapses whitespace. */
38
38
  export declare function summarizeDescription(desc: string | undefined, maxLen?: number): string;
@@ -154,7 +154,7 @@ function getProjectVersionFromCwd(agent) {
154
154
  return null;
155
155
  }
156
156
  }
157
- const SECTION_KEYS = ['commands', 'skills', 'mcp', 'workflows', 'plugins', 'rules', 'hooks', 'promptcuts', 'cli'];
157
+ const SECTION_KEYS = ['commands', 'skills', 'mcp', 'workflows', 'plugins', 'rules', 'hooks', 'promptcuts', 'clis'];
158
158
  /**
159
159
  * Decide whether a section should render given the filter. If no flags are set,
160
160
  * everything renders (current behavior). If any flag is set, only those sections
@@ -231,7 +231,7 @@ function hostCliSourceTag(source) {
231
231
  }
232
232
  /**
233
233
  * Render the host-CLI section. Host CLIs are host-global: declared in any
234
- * DotAgents repo's `cli/` (project > user > system > extras), installed to PATH
234
+ * DotAgents repo's `clis/` (project > user > system > extras), installed to PATH
235
235
  * rather than copied into a version home. They render identically in the overview
236
236
  * and in a per-agent detail view because every agent on the host shares them.
237
237
  * The source tag shows which repo layer declared each — so user-level and
@@ -241,7 +241,7 @@ function renderHostClisSection(cwd) {
241
241
  const { statuses, errors } = listCliStatus(cwd);
242
242
  console.log(chalk.bold('\nHost CLIs\n'));
243
243
  if (statuses.length === 0) {
244
- console.log(` ${chalk.gray('none declared')} ${chalk.gray('— add one with `agents cli add <name>`')}`);
244
+ console.log(` ${chalk.gray('none declared')} ${chalk.gray('— add one with `agents clis add <name>`')}`);
245
245
  }
246
246
  else {
247
247
  const nameWidth = Math.max(...statuses.map((s) => s.manifest.name.length));
@@ -258,7 +258,7 @@ function renderHostClisSection(cwd) {
258
258
  console.log(prefix + desc);
259
259
  }
260
260
  if (anyMissing) {
261
- console.log(chalk.gray(' Install missing with `agents cli install`'));
261
+ console.log(chalk.gray(' Install missing with `agents clis install`'));
262
262
  }
263
263
  }
264
264
  for (const err of errors) {
@@ -1100,7 +1100,7 @@ async function showAgentResources(agentId, requestedVersion, filter) {
1100
1100
  if (shouldRenderSection('promptcuts', filter)) {
1101
1101
  renderPromptcuts();
1102
1102
  }
1103
- if (shouldRenderSection('cli', filter)) {
1103
+ if (shouldRenderSection('clis', filter)) {
1104
1104
  renderHostClisSection(cwd);
1105
1105
  }
1106
1106
  // Show legend at the end if git repo exists and we showed all sections.
@@ -1565,7 +1565,7 @@ export async function viewAction(agentArg, options) {
1565
1565
  rules: options?.rules,
1566
1566
  hooks: options?.hooks,
1567
1567
  promptcuts: options?.promptcuts,
1568
- cli: options?.cli,
1568
+ clis: options?.clis,
1569
1569
  };
1570
1570
  const filterIsSet = SECTION_KEYS.some((k) => filter[k]);
1571
1571
  // RUSH-1320: fold any stale literal `latest` version-home into its concrete
@@ -1684,7 +1684,7 @@ export function registerViewCommand(program) {
1684
1684
  .option('--rules', 'Show only rules in the detail view.')
1685
1685
  .option('--hooks', 'Show only hooks in the detail view.')
1686
1686
  .option('--promptcuts', 'Show only promptcuts in the detail view.')
1687
- .option('--cli', 'Show only host CLIs (declared in cli/, installed to PATH).')
1687
+ .option('--clis', 'Show only host CLIs (declared in clis/, installed to PATH).')
1688
1688
  .option('--merged', 'Show the merged, first-wins resource surface across all layers (project, user, extras, system) in one table with the winning layer per row.')
1689
1689
  .addHelpText('after', `
1690
1690
  Examples:
@@ -30,6 +30,7 @@ Structure:
30
30
  plugins/ optional: plugin bundles scoped to this workflow
31
31
 
32
32
  Resolution: project > user > plugin (plugins/*/workflows/) > extra > system.
33
+ Pin a source with name@plugin or name@extra-alias (optional workflow: prefix).
33
34
 
34
35
  Note: agents run defaults to --mode plan (read-only). For workflows that
35
36
  write files, post comments, or otherwise mutate state, pass --mode edit or
@@ -217,7 +217,7 @@ export function parseCliManifest(contents, opts) {
217
217
  * manifests and any parse errors separately so the CLI can show both.
218
218
  */
219
219
  export function listCliManifests(cwd) {
220
- const resolved = listResources('cli', cwd);
220
+ const resolved = listResources('clis', cwd);
221
221
  const manifests = [];
222
222
  const errors = [];
223
223
  for (const entry of resolved) {
@@ -240,7 +240,7 @@ export function listCliManifests(cwd) {
240
240
  }
241
241
  /** Resolve a single CLI manifest by name. Returns null when not declared. */
242
242
  export function resolveCliManifest(name, cwd) {
243
- const resolved = resolveResource('cli', name, cwd);
243
+ const resolved = resolveResource('clis', name, cwd);
244
244
  if (!resolved)
245
245
  return null;
246
246
  if (!resolved.path.endsWith('.yaml') && !resolved.path.endsWith('.yml'))
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The unified event reader -- one stream over BOTH operational events
3
- * (`~/.agents/.history/events/events.jsonl` via events.ts: secrets, commands, teams, ...) and
3
+ * (`~/.agents/.history/events/YYYY-MM-DD/` 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
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The unified event reader -- one stream over BOTH operational events
3
- * (`~/.agents/.history/events/events.jsonl` via events.ts: secrets, commands, teams, ...) and
3
+ * (`~/.agents/.history/events/YYYY-MM-DD/` 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
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Centralized event logging for agents-cli.
3
3
  *
4
- * Structured JSONL audit log at ~/.agents/.history/events/events.jsonl with lossless numbered
5
- * gzip rotation at 10 MB and rich metadata for debugging/auditing.
4
+ * Structured JSONL audit logs at ~/.agents/.history/events/YYYY-MM-DD/events.jsonl with
5
+ * lossless numbered gzip rotation at 10 MiB and bounded retention.
6
6
  *
7
7
  * Features:
8
8
  * - Rich metadata: hostname, platform, arch, pid, timezone
@@ -75,6 +75,17 @@ export interface EventPayload {
75
75
  [key: string]: unknown;
76
76
  }
77
77
  export type EventRecord = EventMeta & EventPayload;
78
+ /**
79
+ * Move root-level and interim flat-history event families into dated directories.
80
+ *
81
+ * The common case is a whole-family rename into an empty destination. A
82
+ * Each segment is assigned to the local calendar day of its filesystem mtime;
83
+ * new writes are split by day at source. A partially completed migration keeps
84
+ * the destination active file authoritative and assigns a fresh archive number,
85
+ * so no record is overwritten or silently discarded. The legacy active-file
86
+ * lock serializes this with older installed processes that still append there.
87
+ */
88
+ export declare function migrateLegacyEventLogs(userDir?: string): number;
78
89
  /**
79
90
  * Replace a prompt string with length + short SHA so we can correlate runs
80
91
  * without persisting the raw text. Returns the fields to spread into a payload.
@@ -187,15 +198,13 @@ export declare function emitError(err: Error | string, payload?: EventPayload):
187
198
  * the same failure across sessions (e.g. 'remote-cwd-on-add', 'not-installed').
188
199
  */
189
200
  export declare function emitFriction(surface: string, failureId: string, payload?: EventPayload): void;
190
- /**
191
- * Remove log files older than the retention period.
192
- * Removes numbered gzip archives whose filesystem mtime exceeds retention.
193
- *
194
- * @param retentionDays - Number of days to keep (default 7, from DEFAULT_RETENTION_DAYS)
195
- * @returns Number of files removed
196
- */
197
- export declare function rotate(retentionDays?: number): number;
198
- export declare function maybeRotate(): void;
201
+ export interface RotationResult {
202
+ removedByAge: number;
203
+ removedBySize: number;
204
+ bytesReclaimed: number;
205
+ }
206
+ /** Apply age retention and the total-size ceiling immediately. */
207
+ export declare function rotate(retentionDays?: number, maxStorageBytes?: number): RotationResult;
199
208
  /**
200
209
  * Read events from log files within a date range.
201
210
  *
@@ -245,4 +254,4 @@ export declare function stats(options?: {
245
254
  days?: number;
246
255
  }): EventStats;
247
256
  export declare function getLogsPath(): string;
248
- export declare function _resetForTest(overrideEventsPath?: string): void;
257
+ export declare function _resetForTest(overrideEventsPath?: string, overrideUserAgentsDir?: string): void;