@phnx-labs/agents-cli 1.20.88 → 1.20.90

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 (102) hide show
  1. package/CHANGELOG.md +503 -0
  2. package/README.md +15 -1
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/commands.js +7 -7
  5. package/dist/commands/exec.js +7 -1
  6. package/dist/commands/factory.js +26 -2
  7. package/dist/commands/funnel.js +16 -1
  8. package/dist/commands/harness.d.ts +27 -0
  9. package/dist/commands/harness.js +120 -13
  10. package/dist/commands/menubar.js +117 -34
  11. package/dist/commands/profiles.d.ts +3 -0
  12. package/dist/commands/profiles.js +1 -1
  13. package/dist/commands/routines.d.ts +19 -0
  14. package/dist/commands/routines.js +51 -7
  15. package/dist/commands/secrets-rotate-passphrase.d.ts +17 -0
  16. package/dist/commands/secrets-rotate-passphrase.js +96 -0
  17. package/dist/commands/secrets.d.ts +10 -1
  18. package/dist/commands/secrets.js +20 -6
  19. package/dist/commands/sessions-browser.d.ts +4 -0
  20. package/dist/commands/sessions-browser.js +51 -9
  21. package/dist/commands/sessions-favorite.d.ts +20 -0
  22. package/dist/commands/sessions-favorite.js +120 -0
  23. package/dist/commands/sessions.d.ts +110 -21
  24. package/dist/commands/sessions.js +395 -74
  25. package/dist/commands/setup-secrets.d.ts +7 -0
  26. package/dist/commands/setup-secrets.js +12 -9
  27. package/dist/commands/versions.js +12 -4
  28. package/dist/commands/view.d.ts +14 -1
  29. package/dist/commands/view.js +103 -128
  30. package/dist/commands/webhook.js +7 -2
  31. package/dist/lib/agents.d.ts +4 -2
  32. package/dist/lib/agents.js +21 -6
  33. package/dist/lib/commands.js +9 -1
  34. package/dist/lib/daemon.d.ts +29 -0
  35. package/dist/lib/daemon.js +58 -4
  36. package/dist/lib/events.d.ts +1 -1
  37. package/dist/lib/factory/snapshot.d.ts +78 -0
  38. package/dist/lib/factory/snapshot.js +209 -0
  39. package/dist/lib/fs-atomic.d.ts +14 -1
  40. package/dist/lib/fs-atomic.js +35 -3
  41. package/dist/lib/funnel.d.ts +1 -0
  42. package/dist/lib/funnel.js +8 -0
  43. package/dist/lib/hosts/dispatch.js +19 -1
  44. package/dist/lib/hq/floor.js +12 -0
  45. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  46. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  47. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +2 -2
  48. package/dist/lib/menubar/install-menubar.d.ts +53 -2
  49. package/dist/lib/menubar/install-menubar.js +183 -28
  50. package/dist/lib/picker.d.ts +27 -2
  51. package/dist/lib/picker.js +71 -7
  52. package/dist/lib/platform/process.d.ts +2 -0
  53. package/dist/lib/platform/process.js +5 -3
  54. package/dist/lib/profiles.d.ts +48 -0
  55. package/dist/lib/profiles.js +67 -0
  56. package/dist/lib/resources.d.ts +8 -0
  57. package/dist/lib/resources.js +34 -1
  58. package/dist/lib/rotate.d.ts +24 -2
  59. package/dist/lib/rotate.js +63 -6
  60. package/dist/lib/routines-placement.d.ts +2 -1
  61. package/dist/lib/routines-placement.js +8 -4
  62. package/dist/lib/routines.d.ts +57 -1
  63. package/dist/lib/routines.js +74 -1
  64. package/dist/lib/runner.d.ts +2 -0
  65. package/dist/lib/runner.js +21 -8
  66. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  67. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  68. package/dist/lib/secrets/bundles.js +9 -34
  69. package/dist/lib/secrets/filestore.d.ts +152 -34
  70. package/dist/lib/secrets/filestore.js +676 -123
  71. package/dist/lib/session/active.d.ts +109 -3
  72. package/dist/lib/session/active.js +269 -13
  73. package/dist/lib/session/db.d.ts +14 -0
  74. package/dist/lib/session/db.js +35 -0
  75. package/dist/lib/session/favorites.d.ts +39 -0
  76. package/dist/lib/session/favorites.js +101 -0
  77. package/dist/lib/session/host-link.d.ts +68 -0
  78. package/dist/lib/session/host-link.js +64 -0
  79. package/dist/lib/session/presence.d.ts +85 -0
  80. package/dist/lib/session/presence.js +150 -0
  81. package/dist/lib/session/remote-active.d.ts +4 -1
  82. package/dist/lib/session/remote-active.js +8 -2
  83. package/dist/lib/session/remote-list.d.ts +10 -0
  84. package/dist/lib/session/remote-list.js +47 -9
  85. package/dist/lib/session/viewing-in.d.ts +31 -0
  86. package/dist/lib/session/viewing-in.js +47 -0
  87. package/dist/lib/state.d.ts +17 -0
  88. package/dist/lib/state.js +30 -2
  89. package/dist/lib/tmux/binary.d.ts +7 -0
  90. package/dist/lib/tmux/binary.js +11 -1
  91. package/dist/lib/triggers/handlers.d.ts +95 -0
  92. package/dist/lib/triggers/handlers.js +384 -0
  93. package/dist/lib/triggers/webhook.d.ts +10 -2
  94. package/dist/lib/triggers/webhook.js +65 -11
  95. package/dist/lib/types.d.ts +4 -3
  96. package/dist/lib/usage-backoff.d.ts +29 -0
  97. package/dist/lib/usage-backoff.js +165 -0
  98. package/dist/lib/usage.d.ts +112 -5
  99. package/dist/lib/usage.js +464 -46
  100. package/dist/lib/watchdog/runner.d.ts +13 -0
  101. package/dist/lib/watchdog/runner.js +16 -1
  102. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import type { Command } from 'commander';
1
+ import { type Command } from 'commander';
2
2
  import type { SessionAgentId, SessionMeta, ViewMode } from '../lib/session/types.js';
3
3
  import { type ActiveSession } from '../lib/session/active.js';
4
4
  import { gatherRemoteList, runOnPeer } from '../lib/session/remote-list.js';
@@ -17,6 +17,10 @@ interface SessionsOptions extends SessionFilterOptions {
17
17
  /** Also list sessions from the user's own unmanaged ~/.<agent> installs. */
18
18
  unmanaged?: boolean;
19
19
  query?: string;
20
+ /** Resolve one historical selector to metadata only (requires --json). */
21
+ resolve?: string;
22
+ /** Versioned internal peer protocol; old/unsafe peers must reject it. */
23
+ resolveSafeV1?: string;
20
24
  limit?: string;
21
25
  sort?: string;
22
26
  json?: boolean;
@@ -40,6 +44,8 @@ interface SessionsOptions extends SessionFilterOptions {
40
44
  flat?: boolean;
41
45
  /** With --active: show only sessions waiting on user input; exit 1 if any. */
42
46
  waiting?: boolean;
47
+ /** Show only favorited (starred) sessions — the `f` key's flag twin. */
48
+ favorites?: boolean;
43
49
  /** Enrich the listing with live glyphs/preview for running rows. Default on;
44
50
  * `--no-live` sets this false. Commander's `--no-` convention. */
45
51
  live?: boolean;
@@ -120,6 +126,29 @@ export declare function liveGlyphAndPreview(a: ActiveSession | undefined): {
120
126
  * exported for the row tests.
121
127
  */
122
128
  export declare function liveStatusWord(a: ActiveSession | undefined): string;
129
+ /**
130
+ * True when a session is blocked on a human — the `--waiting` contract.
131
+ *
132
+ * NOT `status === 'input_required'`. `foldHostLink` rewrites that status to
133
+ * `orphaned` when nothing is attached, and a session waiting on a question with
134
+ * NOBODY watching is the most acute case `--waiting` exists to surface, not one
135
+ * it should drop. The underlying `activity` is never rewritten, so it is the
136
+ * honest signal here.
137
+ *
138
+ * But `activity` is never rewritten for a DEAD session either: one that died
139
+ * mid-question keeps `waiting_input` forever, and answering it is not a thing a
140
+ * human can do — it needs a relaunch. `--waiting` is a scriptable gate ("does
141
+ * anything need me?"), so a corpse must not trip it.
142
+ *
143
+ * `closed` and `crashed` are unconditionally dead, so they are excluded outright.
144
+ * `abandoned` is NOT: it fires on transcript staleness before the liveness check,
145
+ * so it also covers the live-but-forgotten case — an interactive session that
146
+ * asked a question and sat untouched over a long weekend is still answerable, and
147
+ * is exactly what this gate exists for. It is excluded only when we positively
148
+ * know its process is gone; unknown liveness (an older peer, a row with no pid)
149
+ * stays excluded rather than inventing a human who can answer.
150
+ */
151
+ export declare function isAwaitingUser(s: ActiveSession): boolean;
123
152
  /**
124
153
  * The tracker/PR ref for a session's dedicated column: the ticket id when known,
125
154
  * else `PR#<n>`, else empty. Pulled out of the trailing badge blob so refs align
@@ -136,11 +165,17 @@ export declare function linkCwdCell(s: Pick<SessionMeta, 'cwd' | '_remote'>, lab
136
165
  * (null when unknown) alongside the raw fields, so every active row is joinable.
137
166
  * `project` uses the same derivation SessionMeta does — basename(cwd) (see
138
167
  * discover.ts) — so the active view and the history view join identically.
168
+ *
169
+ * `viewingIn` flattens to the same display string the row renderer prints —
170
+ * `'codium tab 3'` / `'detached'` / null — so a consumer can tell a watched
171
+ * session from an orphaned one (its terminal died, the agent is still running)
172
+ * without re-implementing the tmux client lookup.
139
173
  */
140
- export declare function serializeActiveSessionsForJson(sessions: ActiveSession[]): Array<ActiveSession & {
174
+ export declare function serializeActiveSessionsForJson(sessions: ActiveSession[]): Array<Omit<ActiveSession, 'viewingIn'> & {
141
175
  ticketId: string | null;
142
176
  project: string | null;
143
177
  prLink: string | null;
178
+ viewingIn: string | null;
144
179
  }>;
145
180
  /**
146
181
  * Compact owner display for the `--active` owner column: the local-part of a
@@ -224,6 +259,10 @@ export declare function mergeLocalFirst(sessions: SessionMeta[], localMachine: s
224
259
  * `--json --host` remote fan-out so both emit byte-identical row shapes.
225
260
  */
226
261
  export declare function serializeSessionsJson(sessions: SessionMeta[]): string;
262
+ /** The intentionally small metadata contract emitted by `sessions --resolve`.
263
+ * Transcript locations, extracted plans, account data, costs, and other indexed
264
+ * payload stay local to the machine that owns them. */
265
+ export declare function serializeResolvedSessionsJson(sessions: SessionMeta[]): string;
227
266
  /**
228
267
  * Whether the local machine's sessions belong in an `--active` view. Local is
229
268
  * included by default; an explicit `--host`/`--device` list scopes the view to
@@ -275,6 +314,17 @@ export declare function isBareBrowserListing(options: SessionsOptions, query: st
275
314
  * `runSessionBrowser` picker cannot represent.
276
315
  */
277
316
  export declare function hasNoBrowserDisqualifyingFlags(options: SessionsOptions, query: string | undefined): boolean;
317
+ /**
318
+ * The one-line live status banner shown above a session preview: the glyph, the
319
+ * status word, and — when the session needs a human or has LOST one — a plain
320
+ * sentence saying so. Shared by `--preview` and the interactive browser's preview
321
+ * pane so both explain a state the same way.
322
+ *
323
+ * `crashed` and `orphaned` are the states a glyph alone cannot carry: nobody
324
+ * reads "orphan" and knows it means "still running in tmux with no window
325
+ * attached", so those two spell it out.
326
+ */
327
+ export declare function formatLiveStatusHeadline(live: ActiveSession | undefined, favorite?: boolean): string;
278
328
  /**
279
329
  * Whether a session belongs to `team`, from either end: it spawned the team, or
280
330
  * it is one of the team's teammates. Case-insensitive, matching the SQL
@@ -299,7 +349,7 @@ export declare function teamBadge(session: SessionMeta): {
299
349
  * (tracker/PR ref, pulled out of the badge blob so refs align) is only rendered
300
350
  * when `showTicket` — otherwise a listing with no refs would waste a column of
301
351
  * dashes and needlessly truncate the topic. Worktree stays a trailing badge. */
302
- export declare function flatSessionRow(session: SessionMeta, live?: ActiveSession, showTicket?: boolean, cols?: PickerColumns): string;
352
+ export declare function flatSessionRow(session: SessionMeta, live?: ActiveSession, showTicket?: boolean, cols?: PickerColumns, favorite?: boolean): string;
303
353
  /**
304
354
  * Group key for the overview: prefer the indexed project name; else fold the cwd
305
355
  * to its repo — a worktree (`.../<repo>/.agents/worktrees/<slug>`) folds to the
@@ -364,6 +414,19 @@ export interface PickerColumns {
364
414
  * off for a plain transcript listing, where no row has a host.
365
415
  */
366
416
  showHost?: boolean;
417
+ /**
418
+ * Render the favorite marker column. Like every other conditional column here,
419
+ * it earns its 2 cells only when some row in the pool is actually starred — a
420
+ * user who has never favorited anything pays nothing for the feature.
421
+ */
422
+ showFavorite?: boolean;
423
+ /**
424
+ * Render the live status column (`working` / `waiting` / `orphan` / `crashed`).
425
+ * Live-only, gated the same way as {@link showHost}: it comes from the
426
+ * active-session scan, so the running-filtered browser sets it and a plain
427
+ * transcript listing — where no row has a status — leaves it off.
428
+ */
429
+ showStatus?: boolean;
367
430
  /**
368
431
  * Cells the picker prepends before each row: 2 for the single-select cursor
369
432
  * ('> '), 6 for the multi-select cursor + checkbox ('> [x] '). Reserved from
@@ -393,7 +456,7 @@ export declare function pickerColumnsFor(sessions: SessionMeta[]): PickerColumns
393
456
  * resolvable host (cloud rows, an unreadable process env).
394
457
  */
395
458
  export declare function liveHostLabel(a: ActiveSession | undefined): string;
396
- export declare function formatPickerLabel(s: SessionMeta, query: string, cols?: PickerColumns, ssh?: SshOriginTag, host?: string): string;
459
+ export declare function formatPickerLabel(s: SessionMeta, query: string, cols?: PickerColumns, ssh?: SshOriginTag, host?: string, favorite?: boolean, live?: ActiveSession): string;
397
460
  /**
398
461
  * Pick a hint to show above the picker. Deterministic (keys off the pool size)
399
462
  * so it stays fixed across the picker's re-renders within a single run.
@@ -460,7 +523,9 @@ export interface SessionQueryResolution {
460
523
  * search (a bare id must not surface every transcript that merely mentions it).
461
524
  * A genuine search phrase keeps the ranked metadata+content search.
462
525
  */
463
- export declare function resolveSessionQuery(pool: SessionMeta[], query: string): SessionQueryResolution;
526
+ export declare function resolveSessionQuery(pool: SessionMeta[], query: string, options?: {
527
+ indexFallback?: boolean;
528
+ }): SessionQueryResolution;
464
529
  /** Filter and rank sessions by a multi-term search query across metadata and content. */
465
530
  export declare function filterSessionsByQuery(sessions: SessionMeta[], query: string | undefined): SessionMeta[];
466
531
  /**
@@ -483,36 +548,60 @@ export interface FleetResolveDeps {
483
548
  gatherRemoteList: typeof gatherRemoteList;
484
549
  runOnPeer: typeof runOnPeer;
485
550
  }
486
- /** One distinct machine that reported the id, plus its winning row. */
551
+ /** One distinct machine that reported a logical session, plus its winning row. */
487
552
  interface FleetHit {
488
553
  machine: string;
489
554
  session: SessionMeta;
490
555
  }
491
- /** Group a fleet sweep's rows to the DISTINCT machines that hold the id. Each
492
- * peer answered `sessions <id> --json --local`, which (post-fix) id-resolves and
493
- * so returns the matching row(s); a peer with a synced MIRROR of the same id can
494
- * emit more than one row, so we keep the first per machine. Rows the peer somehow
495
- * returned that do NOT match the id (defensive against version skew) are dropped
496
- * so a stray content hit can never masquerade as an exact resolution. */
497
- export declare function fleetHitsById(rows: SessionMeta[], id: string): FleetHit[];
498
- /**
499
- * Locate a full session id across the online fleet and render it from the machine
556
+ /** One logical session returned by the fleet, including every machine holding a copy. */
557
+ export interface FleetSessionCandidate {
558
+ id: string;
559
+ hits: FleetHit[];
560
+ }
561
+ export type MetadataResolveOutcome = {
562
+ kind: 'resolved';
563
+ session: SessionMeta;
564
+ } | {
565
+ kind: 'not-found';
566
+ } | {
567
+ kind: 'ambiguous';
568
+ candidates: FleetSessionCandidate[];
569
+ } | {
570
+ kind: 'partial';
571
+ failedPeers: string[];
572
+ };
573
+ /** Resolve a fleet sweep through the same canonical full-id / prefix resolver as
574
+ * local lookups, then group copies by logical session id. Synced mirrors of one
575
+ * session therefore stay one candidate even when several machines report them;
576
+ * distinct ids sharing a prefix remain distinct ambiguity candidates. */
577
+ export declare function fleetCandidatesByQuery(rows: SessionMeta[], query: string): FleetSessionCandidate[];
578
+ /** Fixed peer argv for the metadata resolver. Scope flags compose identically on
579
+ * every host; `--all` removes the SSH login cwd/time window, not agent/project filters. */
580
+ export declare function metadataResolveForwardedArgs(selector: string, scope: Pick<SessionFilterOptions, 'agent' | 'project'>): string[];
581
+ /** Resolution must fail closed when any selected peer did not answer. Choosing
582
+ * from a partial fleet can turn an unseen candidate into a false unique match. */
583
+ export declare function metadataResolveOutcome(localMatches: SessionMeta[], remote: {
584
+ sessions: SessionMeta[];
585
+ unreachable: string[];
586
+ }, selector: string): MetadataResolveOutcome;
587
+ /**
588
+ * Locate a full session id or short id prefix across the online fleet and render it from the machine
500
589
  * that holds it. The local disk already missed; this fans `sessions <id> --json
501
590
  * --all` out to every registered online peer (or the explicit `hosts` set),
502
591
  * groups the rows to distinct machines, then:
503
592
  *
504
- * - exactly one machine → delegate rendering to that peer via `runOnPeer`
593
+ * - exactly one logical session → delegate rendering to one peer via `runOnPeer`
505
594
  * (its transcript and agent binary live there — a local `--host` hop would
506
595
  * re-discover locally and dead-end), returning `'rendered'`.
507
- * - more than one machine → print the conflict with machine labels so the user
508
- * can disambiguate with `--device <host>`, returning `'conflict'`.
596
+ * - more than one logical session → print every full-id candidate with its
597
+ * machine labels, returning `'conflict'`.
509
598
  * - none → `'not-found'`, letting the caller print the local
510
599
  * "no session on this machine" message.
511
600
  *
512
- * No fuzzy/content fallback: the sweep forwards a UUID, each peer id-resolves it,
513
- * and `fleetHitsById` drops anything that isn't an exact id match.
601
+ * No fuzzy/content fallback: the sweep forwards the id selector and every result
602
+ * is resolved through `resolveSessionQuery`, the same id-only resolver used locally.
514
603
  */
515
- export declare function resolveSessionAcrossFleet(id: string, mode: ViewMode, hosts?: string[], deps?: FleetResolveDeps): Promise<'rendered' | 'conflict' | 'not-found'>;
604
+ export declare function resolveSessionAcrossFleet(query: string, mode: ViewMode, hosts?: string[], deps?: FleetResolveDeps): Promise<'rendered' | 'conflict' | 'not-found'>;
516
605
  /** Register the `agents sessions` command with all its options and help text. */
517
606
  export declare function registerSessionsCommands(program: Command): void;
518
607
  export {};