@phnx-labs/agents-cli 1.20.78 → 1.20.82

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 (176) hide show
  1. package/CHANGELOG.md +487 -0
  2. package/README.md +25 -25
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/activity.d.ts +9 -0
  5. package/dist/commands/activity.js +153 -24
  6. package/dist/commands/apply.js +11 -4
  7. package/dist/commands/cli.js +1 -1
  8. package/dist/commands/commands.d.ts +1 -1
  9. package/dist/commands/commands.js +7 -6
  10. package/dist/commands/defaults.js +3 -11
  11. package/dist/commands/doctor.d.ts +65 -0
  12. package/dist/commands/doctor.js +575 -31
  13. package/dist/commands/events.js +1 -1
  14. package/dist/commands/exec.js +24 -5
  15. package/dist/commands/feed.d.ts +4 -0
  16. package/dist/commands/feed.js +87 -11
  17. package/dist/commands/harness.js +2 -2
  18. package/dist/commands/hooks.js +1 -1
  19. package/dist/commands/import.js +8 -4
  20. package/dist/commands/logs.js +6 -0
  21. package/dist/commands/models.js +1 -1
  22. package/dist/commands/packages.js +6 -6
  23. package/dist/commands/permissions.js +1 -1
  24. package/dist/commands/profiles.js +1 -1
  25. package/dist/commands/pull.d.ts +1 -1
  26. package/dist/commands/pull.js +4 -4
  27. package/dist/commands/repo.d.ts +78 -0
  28. package/dist/commands/repo.js +187 -11
  29. package/dist/commands/routines.js +12 -2
  30. package/dist/commands/rules.js +13 -12
  31. package/dist/commands/secrets.d.ts +3 -1
  32. package/dist/commands/secrets.js +100 -32
  33. package/dist/commands/sessions-inject.d.ts +7 -4
  34. package/dist/commands/sessions-inject.js +18 -18
  35. package/dist/commands/sessions-picker.js +32 -3
  36. package/dist/commands/sessions-resume.d.ts +16 -0
  37. package/dist/commands/sessions-resume.js +14 -13
  38. package/dist/commands/sessions.d.ts +31 -0
  39. package/dist/commands/sessions.js +141 -16
  40. package/dist/commands/set.d.ts +15 -0
  41. package/dist/commands/set.js +79 -0
  42. package/dist/commands/setup-fleet.d.ts +21 -0
  43. package/dist/commands/setup-fleet.js +201 -0
  44. package/dist/commands/setup-secrets.d.ts +20 -0
  45. package/dist/commands/setup-secrets.js +223 -0
  46. package/dist/commands/setup.js +17 -3
  47. package/dist/commands/skills.js +1 -1
  48. package/dist/commands/sync.js +7 -2
  49. package/dist/commands/teams-picker.js +0 -1
  50. package/dist/commands/teams.d.ts +33 -1
  51. package/dist/commands/teams.js +208 -44
  52. package/dist/commands/versions.js +10 -5
  53. package/dist/commands/view.d.ts +1 -0
  54. package/dist/commands/view.js +11 -0
  55. package/dist/index.js +5 -8
  56. package/dist/lib/acp/harnesses.js +0 -7
  57. package/dist/lib/activity.d.ts +156 -0
  58. package/dist/lib/activity.js +282 -0
  59. package/dist/lib/agents.d.ts +9 -2
  60. package/dist/lib/agents.js +72 -63
  61. package/dist/lib/browser/service.js +3 -0
  62. package/dist/lib/browser/types.d.ts +7 -0
  63. package/dist/lib/capabilities.js +6 -2
  64. package/dist/lib/crabbox/lease.js +2 -2
  65. package/dist/lib/crabbox/setup-copy.d.ts +4 -4
  66. package/dist/lib/crabbox/setup-copy.js +4 -4
  67. package/dist/lib/daemon.d.ts +18 -0
  68. package/dist/lib/daemon.js +47 -6
  69. package/dist/lib/devices/fleet.d.ts +1 -1
  70. package/dist/lib/devices/fleet.js +1 -1
  71. package/dist/lib/doctor-diff.d.ts +20 -0
  72. package/dist/lib/doctor-diff.js +6 -1
  73. package/dist/lib/drift.d.ts +31 -11
  74. package/dist/lib/drift.js +58 -7
  75. package/dist/lib/events.d.ts +8 -1
  76. package/dist/lib/events.js +10 -1
  77. package/dist/lib/exec.js +35 -8
  78. package/dist/lib/feed-post.d.ts +28 -1
  79. package/dist/lib/feed-post.js +110 -2
  80. package/dist/lib/fleet/apply.js +8 -1
  81. package/dist/lib/fleet/auth-sync.d.ts +18 -1
  82. package/dist/lib/fleet/auth-sync.js +25 -11
  83. package/dist/lib/fleet/remote-login.js +5 -0
  84. package/dist/lib/git.d.ts +14 -14
  85. package/dist/lib/git.js +41 -44
  86. package/dist/lib/hooks.d.ts +42 -0
  87. package/dist/lib/hooks.js +137 -93
  88. package/dist/lib/hosts/dispatch.d.ts +28 -1
  89. package/dist/lib/hosts/dispatch.js +33 -5
  90. package/dist/lib/hosts/option.js +2 -2
  91. package/dist/lib/hosts/passthrough.d.ts +22 -1
  92. package/dist/lib/hosts/passthrough.js +238 -13
  93. package/dist/lib/hosts/run-target.d.ts +2 -0
  94. package/dist/lib/hosts/run-target.js +1 -0
  95. package/dist/lib/hq/floor.d.ts +1 -1
  96. package/dist/lib/hq/floor.js +6 -1
  97. package/dist/lib/mcp.js +3 -71
  98. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  99. package/dist/lib/menubar/notify-desktop.d.ts +15 -0
  100. package/dist/lib/menubar/notify-desktop.js +41 -5
  101. package/dist/lib/merged-resources.d.ts +11 -0
  102. package/dist/{commands/resources.js → lib/merged-resources.js} +11 -19
  103. package/dist/lib/permissions.d.ts +0 -32
  104. package/dist/lib/permissions.js +4 -157
  105. package/dist/lib/picker.js +1 -1
  106. package/dist/lib/platform/process.d.ts +16 -5
  107. package/dist/lib/platform/process.js +54 -0
  108. package/dist/lib/plugins.d.ts +0 -8
  109. package/dist/lib/plugins.js +4 -110
  110. package/dist/lib/project-resources.js +4 -1
  111. package/dist/lib/refresh.d.ts +5 -3
  112. package/dist/lib/refresh.js +7 -5
  113. package/dist/lib/resources/commands.js +4 -1
  114. package/dist/lib/resources/mcp.js +0 -4
  115. package/dist/lib/resources/permissions.d.ts +1 -1
  116. package/dist/lib/resources/permissions.js +1 -5
  117. package/dist/lib/resources/rules.js +4 -1
  118. package/dist/lib/resources/skills.js +4 -1
  119. package/dist/lib/resources/subagents.js +4 -0
  120. package/dist/lib/resources/types.d.ts +1 -1
  121. package/dist/lib/routines.d.ts +20 -0
  122. package/dist/lib/routines.js +6 -0
  123. package/dist/lib/run-defaults.d.ts +1 -0
  124. package/dist/lib/run-defaults.js +9 -0
  125. package/dist/lib/runner.d.ts +0 -12
  126. package/dist/lib/runner.js +49 -10
  127. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  128. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  129. package/dist/lib/secrets/agent.d.ts +16 -0
  130. package/dist/lib/secrets/agent.js +105 -3
  131. package/dist/lib/secrets/audit.d.ts +46 -0
  132. package/dist/lib/secrets/audit.js +56 -0
  133. package/dist/lib/secrets/bundles.d.ts +11 -8
  134. package/dist/lib/secrets/bundles.js +31 -23
  135. package/dist/lib/secrets/index.d.ts +2 -1
  136. package/dist/lib/secrets/index.js +8 -2
  137. package/dist/lib/secrets/remote.d.ts +75 -2
  138. package/dist/lib/secrets/remote.js +139 -5
  139. package/dist/lib/secrets/sync.js +3 -3
  140. package/dist/lib/session/active.d.ts +110 -26
  141. package/dist/lib/session/active.js +161 -50
  142. package/dist/lib/session/actor-sidecar.d.ts +23 -0
  143. package/dist/lib/session/actor-sidecar.js +101 -0
  144. package/dist/lib/session/db.d.ts +4 -1
  145. package/dist/lib/session/db.js +65 -8
  146. package/dist/lib/session/discover.d.ts +4 -0
  147. package/dist/lib/session/discover.js +13 -0
  148. package/dist/lib/session/hook-sessions.d.ts +9 -0
  149. package/dist/lib/session/hook-sessions.js +57 -8
  150. package/dist/lib/session/pid-registry.d.ts +13 -0
  151. package/dist/lib/session/render.d.ts +2 -0
  152. package/dist/lib/session/render.js +1 -1
  153. package/dist/lib/session/types.d.ts +16 -0
  154. package/dist/lib/staleness/detectors/commands.js +2 -2
  155. package/dist/lib/staleness/detectors/permissions.js +1 -45
  156. package/dist/lib/staleness/writers/commands.js +4 -5
  157. package/dist/lib/staleness/writers/hooks.js +1 -1
  158. package/dist/lib/startup/command-registry.d.ts +1 -2
  159. package/dist/lib/startup/command-registry.js +2 -4
  160. package/dist/lib/subagents-registry.js +5 -12
  161. package/dist/lib/subagents.d.ts +0 -10
  162. package/dist/lib/subagents.js +0 -12
  163. package/dist/lib/teams/agents.d.ts +0 -2
  164. package/dist/lib/teams/agents.js +10 -27
  165. package/dist/lib/teams/index.d.ts +1 -1
  166. package/dist/lib/teams/index.js +1 -1
  167. package/dist/lib/types.d.ts +17 -9
  168. package/dist/lib/usage.d.ts +28 -5
  169. package/dist/lib/usage.js +271 -8
  170. package/dist/lib/versions.js +7 -1
  171. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  172. package/dist/lib/watchdog/watchdog.js +25 -13
  173. package/package.json +1 -2
  174. package/dist/commands/check.d.ts +0 -15
  175. package/dist/commands/check.js +0 -180
  176. package/dist/commands/resources.d.ts +0 -5
@@ -2,7 +2,7 @@
2
2
  * SQLite-backed session index and full-text search.
3
3
  *
4
4
  * Stores session metadata and user-prompt text in a WAL-mode SQLite database
5
- * at ~/.agents/sessions/sessions.db. Provides incremental upsert, scan-stamp
5
+ * at ~/.agents/.history/sessions/sessions.db. Provides incremental upsert, scan-stamp
6
6
  * ledger (mtime/size tracking to skip unchanged files), FTS5 search with
7
7
  * BM25 ranking, and label-first search for /rename'd sessions.
8
8
  */
@@ -13,10 +13,11 @@ import { parseSession } from './parse.js';
13
13
  import { extractRecentDirectoriesTouched, extractTodoProgressFromEvents } from './state.js';
14
14
  import { getSessionsDir, getSessionsDbPath } from '../state.js';
15
15
  import { machineForSessionFile } from './origin-machine.js';
16
+ import { loadSessionActorIndex, readSessionActorRecord } from './actor-sidecar.js';
16
17
  const SESSIONS_DIR = getSessionsDir();
17
18
  const DB_PATH = getSessionsDbPath();
18
19
  /** Current schema version; bumped when migrations are added. */
19
- const SCHEMA_VERSION = 18;
20
+ const SCHEMA_VERSION = 20;
20
21
  /**
21
22
  * Canonicalize a file path for use as a scan_ledger key. The same physical
22
23
  * session file is reachable via multiple aliases — `~/.claude/projects/x.jsonl`
@@ -63,6 +64,7 @@ CREATE TABLE IF NOT EXISTS sessions (
63
64
  output_tokens INTEGER,
64
65
  cost_usd REAL,
65
66
  duration_ms INTEGER,
67
+ model TEXT,
66
68
  file_path TEXT NOT NULL,
67
69
  file_mtime_ms INTEGER,
68
70
  file_size INTEGER,
@@ -77,7 +79,9 @@ CREATE TABLE IF NOT EXISTS sessions (
77
79
  todos TEXT,
78
80
  recent_directories_touched TEXT,
79
81
  linear_project TEXT,
80
- linear_project_url TEXT
82
+ linear_project_url TEXT,
83
+ actor TEXT,
84
+ initiated_by TEXT
81
85
  );
82
86
  CREATE INDEX IF NOT EXISTS idx_sessions_timestamp ON sessions(timestamp DESC);
83
87
  CREATE INDEX IF NOT EXISTS idx_sessions_cwd ON sessions(cwd);
@@ -360,6 +364,25 @@ function migrateSchema(db, fromVersion) {
360
364
  });
361
365
  txn(rows);
362
366
  }
367
+ if (fromVersion < 19) {
368
+ // v18 → v19: actor provenance (RUSH-2018) — who initiated the session and
369
+ // the actor's kind. Populated at write time from the resolved actor (the
370
+ // pid-registry/spawn path), not derivable from the transcript, so there is
371
+ // no ledger wipe here: a rescan can't backfill these, and forcing a full
372
+ // re-parse would be pure churn. Existing rows stay NULL until re-created.
373
+ const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
374
+ if (!cols.some(c => c.name === 'actor'))
375
+ db.exec(`ALTER TABLE sessions ADD COLUMN actor TEXT`);
376
+ if (!cols.some(c => c.name === 'initiated_by'))
377
+ db.exec(`ALTER TABLE sessions ADD COLUMN initiated_by TEXT`);
378
+ }
379
+ if (fromVersion < 20) {
380
+ // v19 → v20: persist the transcript's model for the static session list.
381
+ const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
382
+ if (!cols.some(c => c.name === 'model'))
383
+ db.exec(`ALTER TABLE sessions ADD COLUMN model TEXT`);
384
+ db.exec(`DELETE FROM scan_ledger; DELETE FROM dir_ledger;`);
385
+ }
363
386
  }
364
387
  /** Open (or return the cached) sessions database, applying migrations as needed. */
365
388
  export function getDB() {
@@ -719,18 +742,20 @@ const upsertSessionStmt = (db) => db.prepare(`
719
742
  id, short_id, agent, origin, routine_name, routine_run_id,
720
743
  version, account, timestamp, last_activity,
721
744
  project, cwd, git_branch, topic, label, message_count, token_count,
722
- output_tokens, cost_usd, duration_ms,
745
+ output_tokens, cost_usd, duration_ms, model,
723
746
  file_path, file_mtime_ms, file_size, scanned_at, is_team_origin,
724
747
  pr_url, pr_number, worktree_slug, ticket_id, plan, todos,
725
- recent_directories_touched, linear_project, linear_project_url, machine
748
+ recent_directories_touched, linear_project, linear_project_url, machine,
749
+ actor, initiated_by
726
750
  ) VALUES (
727
751
  @id, @short_id, @agent, @origin, @routine_name, @routine_run_id,
728
752
  @version, @account, @timestamp, @last_activity,
729
753
  @project, @cwd, @git_branch, @topic, @label, @message_count, @token_count,
730
- @output_tokens, @cost_usd, @duration_ms,
754
+ @output_tokens, @cost_usd, @duration_ms, @model,
731
755
  @file_path, @file_mtime_ms, @file_size, @scanned_at, @is_team_origin,
732
756
  @pr_url, @pr_number, @worktree_slug, @ticket_id, @plan, @todos,
733
- @recent_directories_touched, @linear_project, @linear_project_url, @machine
757
+ @recent_directories_touched, @linear_project, @linear_project_url, @machine,
758
+ @actor, @initiated_by
734
759
  )
735
760
  ON CONFLICT(id) DO UPDATE SET
736
761
  short_id = excluded.short_id,
@@ -760,6 +785,7 @@ const upsertSessionStmt = (db) => db.prepare(`
760
785
  output_tokens = excluded.output_tokens,
761
786
  cost_usd = excluded.cost_usd,
762
787
  duration_ms = excluded.duration_ms,
788
+ model = excluded.model,
763
789
  file_path = excluded.file_path,
764
790
  file_mtime_ms = excluded.file_mtime_ms,
765
791
  file_size = excluded.file_size,
@@ -780,7 +806,15 @@ const upsertSessionStmt = (db) => db.prepare(`
780
806
  WHEN excluded.ticket_id IS NOT sessions.ticket_id THEN excluded.linear_project_url
781
807
  ELSE COALESCE(excluded.linear_project_url, sessions.linear_project_url)
782
808
  END,
783
- machine = excluded.machine
809
+ machine = excluded.machine,
810
+ -- actor / initiated_by record who launched the session. COALESCE(existing,
811
+ -- incoming) keeps a stored owner (a rescan carries no actor -> excluded.actor
812
+ -- is NULL -> the stored value wins, never clobbered) BUT backfills a row that
813
+ -- was inserted NULL-first — e.g. an older scanner, or any scan that ran before
814
+ -- the actor sidecar landed — once the sidecar-join finally provides one. Plain
815
+ -- exclusion locked those rows to NULL forever (RUSH-2018/2019 fix).
816
+ actor = COALESCE(sessions.actor, excluded.actor),
817
+ initiated_by = COALESCE(sessions.initiated_by, excluded.initiated_by)
784
818
  `);
785
819
  function enrichCachedSessionMeta(meta) {
786
820
  if (!meta.filePath)
@@ -838,6 +872,12 @@ function resolveMachine(meta) {
838
872
  */
839
873
  export function upsertSession(meta, content, scan) {
840
874
  meta = enrichCachedSessionMeta(meta);
875
+ // Join the durable sessionId -> actor sidecar (RUSH-2019) when the caller
876
+ // didn't already carry an actor, so a scanned transcript still attributes to a
877
+ // person. The ON CONFLICT COALESCEs actor/initiated_by, so this fills a fresh
878
+ // row AND backfills one indexed null-first (before its sidecar existed), while a
879
+ // rescan carrying no actor still keeps the stored owner.
880
+ const actorRec = meta.actor ? undefined : readSessionActorRecord(meta.id);
841
881
  const db = getDB();
842
882
  const { upsert, delText, insText, readLabel } = stmts(db);
843
883
  const row = {
@@ -861,6 +901,7 @@ export function upsertSession(meta, content, scan) {
861
901
  output_tokens: meta.outputTokens ?? null,
862
902
  cost_usd: meta.costUsd ?? null,
863
903
  duration_ms: meta.durationMs ?? null,
904
+ model: meta.model ?? null,
864
905
  file_path: meta.filePath,
865
906
  file_mtime_ms: scan?.fileMtimeMs ?? null,
866
907
  file_size: scan?.fileSize ?? null,
@@ -876,6 +917,8 @@ export function upsertSession(meta, content, scan) {
876
917
  linear_project: meta.linearProject ?? null,
877
918
  linear_project_url: meta.linearProjectUrl ?? null,
878
919
  machine: resolveMachine(meta),
920
+ actor: meta.actor ?? actorRec?.actor ?? null,
921
+ initiated_by: meta.initiatedBy ?? actorRec?.initiatedBy ?? null,
879
922
  };
880
923
  const txn = db.transaction(() => {
881
924
  upsert.run(row);
@@ -894,6 +937,12 @@ export function upsertSessionsBatch(entries) {
894
937
  const db = getDB();
895
938
  const { upsert, delText, insText, readLabel } = stmts(db);
896
939
  const now = Date.now();
940
+ // One directory read for the whole batch: join the durable sessionId -> actor
941
+ // sidecar (RUSH-2019) so scanned transcripts attribute to a person. Only used
942
+ // for entries whose meta carries no actor; the ON CONFLICT COALESCEs the column,
943
+ // so this fills fresh rows AND backfills null-first ones, never clobbering a
944
+ // stored owner on rescan.
945
+ const actorIndex = loadSessionActorIndex();
897
946
  // Persist the Claude resumable-parse continuation (parser_state + content_text)
898
947
  // alongside the stamp. On a full/incremental Claude parse the caller passes the
899
948
  // serialized newState + accumulated user doc so the NEXT scan can resume from
@@ -972,6 +1021,7 @@ export function upsertSessionsBatch(entries) {
972
1021
  output_tokens: meta.outputTokens ?? null,
973
1022
  cost_usd: meta.costUsd ?? null,
974
1023
  duration_ms: meta.durationMs ?? null,
1024
+ model: meta.model ?? null,
975
1025
  file_path: meta.filePath,
976
1026
  file_mtime_ms: scan?.fileMtimeMs ?? null,
977
1027
  file_size: scan?.fileSize ?? null,
@@ -987,6 +1037,8 @@ export function upsertSessionsBatch(entries) {
987
1037
  linear_project: meta.linearProject ?? null,
988
1038
  linear_project_url: meta.linearProjectUrl ?? null,
989
1039
  machine: resolveMachine(meta),
1040
+ actor: meta.actor ?? actorIndex.get(meta.id)?.actor ?? null,
1041
+ initiated_by: meta.initiatedBy ?? actorIndex.get(meta.id)?.initiatedBy ?? null,
990
1042
  });
991
1043
  delText.run(meta.id);
992
1044
  insText.run(meta.id,
@@ -1157,6 +1209,7 @@ function rowToMeta(row) {
1157
1209
  outputTokens: row.output_tokens ?? undefined,
1158
1210
  costUsd: row.cost_usd ?? undefined,
1159
1211
  durationMs: row.duration_ms ?? undefined,
1212
+ model: row.model ?? undefined,
1160
1213
  version: row.version ?? undefined,
1161
1214
  account: row.account ?? undefined,
1162
1215
  topic: row.topic ?? undefined,
@@ -1172,6 +1225,10 @@ function rowToMeta(row) {
1172
1225
  linearProject: row.linear_project ?? undefined,
1173
1226
  linearProjectUrl: row.linear_project_url ?? undefined,
1174
1227
  machine: row.machine ?? undefined,
1228
+ actor: row.actor ?? undefined,
1229
+ // Narrow the free-text column to the known kinds; an unexpected value maps
1230
+ // to undefined rather than being asserted as a valid kind.
1231
+ initiatedBy: row.initiated_by === 'human' || row.initiated_by === 'agent' ? row.initiated_by : undefined,
1175
1232
  };
1176
1233
  }
1177
1234
  function parseJsonColumn(value) {
@@ -54,6 +54,7 @@ interface ClaudeSessionScan {
54
54
  cwd?: string;
55
55
  gitBranch?: string;
56
56
  version?: string;
57
+ model?: string;
57
58
  topic?: string;
58
59
  messageCount: number;
59
60
  tokenCount?: number;
@@ -93,6 +94,7 @@ interface CodexSessionScan {
93
94
  cwd?: string;
94
95
  gitBranch?: string;
95
96
  version?: string;
97
+ model?: string;
96
98
  topic?: string;
97
99
  messageCount: number;
98
100
  tokenCount?: number;
@@ -366,6 +368,7 @@ export interface ClaudeParseState {
366
368
  cwd?: string;
367
369
  gitBranch?: string;
368
370
  version?: string;
371
+ model?: string;
369
372
  topic?: string;
370
373
  customTitle?: string;
371
374
  aiTitle?: string;
@@ -424,6 +427,7 @@ export interface ClaudeParserState {
424
427
  cwd?: string;
425
428
  gitBranch?: string;
426
429
  version?: string;
430
+ model?: string;
427
431
  entrypoint?: string;
428
432
  firstTsMs?: number;
429
433
  topic?: string;
@@ -1000,6 +1000,7 @@ async function readClaudeMeta(filePath, sessionId, scanStamp, priorRow, account,
1000
1000
  filePath,
1001
1001
  gitBranch: scan.gitBranch,
1002
1002
  version: scan.version,
1003
+ model: scan.model,
1003
1004
  account,
1004
1005
  topic: scan.topic,
1005
1006
  label,
@@ -1030,6 +1031,7 @@ async function readClaudeMeta(filePath, sessionId, scanStamp, priorRow, account,
1030
1031
  lastActivity: scan.lastActivity,
1031
1032
  filePath,
1032
1033
  account,
1034
+ model: scan.model,
1033
1035
  label,
1034
1036
  messageCount: scan.messageCount,
1035
1037
  tokenCount: scan.tokenCount,
@@ -1327,6 +1329,7 @@ export async function readCodexMeta(filePath, resolveAccount, currentVersion, sc
1327
1329
  filePath,
1328
1330
  gitBranch: scan.gitBranch,
1329
1331
  version: resolveSessionVersion('codex', filePath, scan.version, currentVersion),
1332
+ model: scan.model,
1330
1333
  topic: scan.topic,
1331
1334
  messageCount: scan.messageCount,
1332
1335
  tokenCount: scan.tokenCount,
@@ -1545,6 +1548,7 @@ function readGeminiMeta(filePath, hashDir, projectMap, currentVersion) {
1545
1548
  cwd,
1546
1549
  filePath,
1547
1550
  version: resolveSessionVersion('gemini', filePath, embeddedVersion, currentVersion),
1551
+ model: sessionModel,
1548
1552
  topic,
1549
1553
  messageCount,
1550
1554
  tokenCount: sawTokenCount ? tokenCount : undefined,
@@ -2162,6 +2166,7 @@ function readHermesMeta(filePath) {
2162
2166
  project: platform,
2163
2167
  filePath,
2164
2168
  version: model,
2169
+ model,
2165
2170
  topic,
2166
2171
  messageCount: messageCount || (typeof session.message_count === 'number' ? session.message_count : undefined),
2167
2172
  };
@@ -2260,6 +2265,7 @@ async function readDroidMeta(filePath, currentVersion) {
2260
2265
  cwd,
2261
2266
  filePath,
2262
2267
  version: resolveSessionVersion('droid', filePath, undefined, currentVersion),
2268
+ model,
2263
2269
  topic: scan.topic,
2264
2270
  messageCount: scan.messageCount,
2265
2271
  tokenCount,
@@ -2403,6 +2409,7 @@ export function initClaudeParseState() {
2403
2409
  cwd: undefined,
2404
2410
  gitBranch: undefined,
2405
2411
  version: undefined,
2412
+ model: undefined,
2406
2413
  topic: undefined,
2407
2414
  customTitle: undefined,
2408
2415
  aiTitle: undefined,
@@ -2587,6 +2594,8 @@ export function applyClaudeLine(state, parsed) {
2587
2594
  // Per-assistant-message cost: each event carries its own model, so we
2588
2595
  // multiply that event's raw token directions by that model's price.
2589
2596
  const model = parsed.message?.model;
2597
+ if (typeof model === 'string' && model)
2598
+ state.model = model;
2590
2599
  if (model && usageObj && typeof usageObj === 'object') {
2591
2600
  const eventCost = costOfUsage({
2592
2601
  model,
@@ -2619,6 +2628,7 @@ export function finalizeClaudeScan(state) {
2619
2628
  cwd: state.cwd,
2620
2629
  gitBranch: state.gitBranch,
2621
2630
  version: state.version,
2631
+ model: state.model,
2622
2632
  topic: resolvedTopic,
2623
2633
  entrypoint: state.entrypoint,
2624
2634
  messageCount: state.messageCount,
@@ -2684,6 +2694,7 @@ export function serializeClaudeParserState(state, offset) {
2684
2694
  cwd: state.cwd,
2685
2695
  gitBranch: state.gitBranch,
2686
2696
  version: state.version,
2697
+ model: state.model,
2687
2698
  entrypoint: state.entrypoint,
2688
2699
  firstTsMs: state.firstTsMs,
2689
2700
  topic: state.topic,
@@ -2744,6 +2755,7 @@ export function hydrateClaudeParseState(prior) {
2744
2755
  cwd: prior.cwd,
2745
2756
  gitBranch: prior.gitBranch,
2746
2757
  version: prior.version,
2758
+ model: prior.model,
2747
2759
  topic: prior.topic,
2748
2760
  customTitle: prior.customTitle,
2749
2761
  aiTitle: prior.aiTitle,
@@ -3098,6 +3110,7 @@ export function finalizeCodexScan(state) {
3098
3110
  cwd: state.cwd,
3099
3111
  gitBranch: state.gitBranch,
3100
3112
  version: state.version,
3113
+ model: state.model,
3101
3114
  topic: state.topic,
3102
3115
  messageCount: state.messageCount,
3103
3116
  tokenCount: state.tokenCount,
@@ -14,6 +14,15 @@ export interface HookSessionIndex {
14
14
  byTerminalId: Map<string, HookSessionRecord>;
15
15
  byPid: Map<number, HookSessionRecord>;
16
16
  }
17
+ /**
18
+ * Read the deployed hook's record for one specific pid, or undefined if absent /
19
+ * corrupt / not fresh. `startedAtMs` (the live process's known start, when the
20
+ * caller has it) rejects a stale record left by a PRIOR process at a reused pid:
21
+ * the hook writes its record AFTER the agent boots, so a record whose `ts`
22
+ * predates the current process's start belongs to a dead predecessor. `ts` is
23
+ * Unix SECONDS (the hook stamps `date +%s`); `startedAtMs` is millis.
24
+ */
25
+ export declare function readStateSessionRecord(pid: number, startedAtMs?: number): HookSessionRecord | undefined;
17
26
  /**
18
27
  * Scan the hook state dir once and index every record by launch_id, terminal_id,
19
28
  * and pid. Returns empty maps if the dir is absent. Newest-by-`ts` wins a key
@@ -1,13 +1,18 @@
1
1
  /**
2
2
  * Read-only view of the SessionStart hook's live-session state files.
3
3
  *
4
- * The `@agents/session-tracker` hook (installed into each agent's own config)
5
- * writes `~/.agents/.cache/terminals/sessions/<pid>.json` AFTER an agent boots,
6
- * carrying the agent's OWN authoritative session id (from its SessionStart
7
- * payload) plus the join keys `launch_id` / `terminal_id`. It is the only writer
8
- * that sees agents `ag run` did NOT launch (you typing `claude` in a terminal),
9
- * and the only source of an exact id for non-Claude agents (whose id we can't
10
- * know at spawn).
4
+ * Two on-disk sources carry a non-Claude agent's authoritative id (whose id we
5
+ * can't know at spawn), both read here:
6
+ * 1. The `@agents/session-tracker` hook writes
7
+ * `~/.agents/.cache/terminals/sessions/<pid>.json` with the id plus the join
8
+ * keys `launch_id` / `terminal_id`. Rich schema, but this package is NOT
9
+ * deployed on the fleet its dir is empty there. Scanned into the index by
10
+ * {@link loadHookSessionIndex}.
11
+ * 2. The ACTUALLY-DEPLOYED SessionStart hook writes
12
+ * `~/.agents/.cache/state/sessions/<pid>.json` ({@link readStateSessionRecord})
13
+ * — `{session_id,cwd,pid,ts}`, keyed purely by pid. This is the real fleet id
14
+ * source for every agent; read targeted per-pid, never scanned (RUSH-2007).
15
+ * Both see agents `ag run` did NOT launch (you typing `claude` in a terminal).
11
16
  *
12
17
  * This module lets `sessions --active` reconcile a `ps`-discovered pid to that
13
18
  * authoritative id. It is deliberately a small hand-rolled reader — the CLI does
@@ -23,12 +28,56 @@
23
28
  */
24
29
  import fs from 'fs';
25
30
  import path from 'path';
26
- import { getTerminalsDir } from '../state.js';
31
+ import { getTerminalsDir, getRuntimeStateDir } from '../state.js';
27
32
  function hookSessionsDir() {
28
33
  // Sibling of the pid-registry's by-pid/ dir. The hook hardcodes this same path
29
34
  // (packages/session-tracker/src/hook.sh); we read it, never move it.
30
35
  return path.join(getTerminalsDir(), 'sessions');
31
36
  }
37
+ /**
38
+ * The path the ACTUALLY-DEPLOYED SessionStart hook writes:
39
+ * `~/.agents/.cache/state/sessions/<pid>.json`, carrying `{session_id,cwd,pid,ts}`
40
+ * for EVERY agent (claude/codex/gemini/kimi/grok/antigravity). This is the fleet's
41
+ * real id source; the `terminals/sessions/` path above belongs to the separate
42
+ * `@agents/session-tracker` package, which is not deployed (its dir is empty on the
43
+ * fleet), so a non-Claude tmux session's id resolved ONLY through the index above
44
+ * never landed (RUSH-2007). These records are keyed purely by pid — no
45
+ * launch_id/terminal_id/agent — so they join only on pid. The dir is an unpruned
46
+ * graveyard (thousands of dead-pid files), so this is a TARGETED per-pid read on
47
+ * demand, never a full-dir scan folded into {@link loadHookSessionIndex}.
48
+ */
49
+ function stateSessionRecordPath(pid) {
50
+ return path.join(getRuntimeStateDir(), 'sessions', `${pid}.json`);
51
+ }
52
+ /**
53
+ * Read the deployed hook's record for one specific pid, or undefined if absent /
54
+ * corrupt / not fresh. `startedAtMs` (the live process's known start, when the
55
+ * caller has it) rejects a stale record left by a PRIOR process at a reused pid:
56
+ * the hook writes its record AFTER the agent boots, so a record whose `ts`
57
+ * predates the current process's start belongs to a dead predecessor. `ts` is
58
+ * Unix SECONDS (the hook stamps `date +%s`); `startedAtMs` is millis.
59
+ */
60
+ export function readStateSessionRecord(pid, startedAtMs) {
61
+ if (!pid || pid < 1)
62
+ return undefined;
63
+ let rec;
64
+ try {
65
+ rec = parseRecord(fs.readFileSync(stateSessionRecordPath(pid), 'utf8'));
66
+ }
67
+ catch {
68
+ return undefined; // absent (the common case) or unreadable
69
+ }
70
+ if (!rec)
71
+ return undefined;
72
+ if (startedAtMs !== undefined && typeof rec.ts === 'number') {
73
+ // Allow a small skew: the hook fires just after exec, and ts is second-
74
+ // granular so it can floor to just before a sub-second process start.
75
+ const SKEW_MS = 5_000;
76
+ if (rec.ts * 1000 < startedAtMs - SKEW_MS)
77
+ return undefined; // reused-pid graveyard record
78
+ }
79
+ return rec;
80
+ }
32
81
  function parseRecord(raw) {
33
82
  try {
34
83
  const o = JSON.parse(raw);
@@ -4,6 +4,19 @@ export interface PidSessionEntry {
4
4
  /** The launch session id. Present for agents launched with a known id (Claude). */
5
5
  sessionId?: string;
6
6
  cwd?: string;
7
+ /**
8
+ * Resolved actor id (`resolveActor().id`) stamped at spawn — who initiated this
9
+ * run. A tailnet login/email for a resolved human, or `UNRESOLVED@<host>` when
10
+ * it can't be determined. Read back by the active-sessions path to surface an
11
+ * `owner` per session (RUSH-2018), so a co-located fleet shows who launched what.
12
+ */
13
+ actor?: string;
14
+ /**
15
+ * The actor's kind (`resolveActor().kind`): `'human'` for a person-initiated
16
+ * run, `'agent'` for one an agent spawned. Pairs with {@link actor} the same way
17
+ * `AGENTS_ACTOR` / `AGENTS_ACTOR_KIND` do on the exec env.
18
+ */
19
+ initiatedBy?: 'human' | 'agent';
7
20
  /**
8
21
  * The launch id minted by `ag run` and exported to the child as
9
22
  * `AGENT_LAUNCH_ID`. The agent's SessionStart hook records the SAME id in its
@@ -64,6 +64,8 @@ export interface SessionStats {
64
64
  }
65
65
  /** Compute aggregate statistics (turns, tools, tokens, duration) from session events. */
66
66
  export declare function computeSummaryStats(events: SessionEvent[]): SessionStats;
67
+ /** Strip the 'claude-' prefix and date suffix from a model identifier. */
68
+ export declare function shortenModel(model: string): string;
67
69
  /** Format a duration in milliseconds as a human-readable string (e.g. '12 min', '2h 30min'). */
68
70
  export declare function formatDuration(ms: number): string;
69
71
  /**
@@ -220,7 +220,7 @@ export function computeSummaryStats(events) {
220
220
  };
221
221
  }
222
222
  /** Strip the 'claude-' prefix and date suffix from a model identifier. */
223
- function shortenModel(model) {
223
+ export function shortenModel(model) {
224
224
  return model.replace(/^claude-/, '').replace(/-\d{8}$/, '');
225
225
  }
226
226
  /** Format a token count as a human-readable string (e.g. 67.5K, 1.2M). */
@@ -119,6 +119,8 @@ export interface SessionMeta {
119
119
  costUsd?: number;
120
120
  /** Wall-clock duration in ms (lastTs − firstTs), persisted at scan time. */
121
121
  durationMs?: number;
122
+ /** Underlying LLM model observed in the transcript, when the agent records one. */
123
+ model?: string;
122
124
  version?: string;
123
125
  account?: string;
124
126
  topic?: string;
@@ -190,6 +192,20 @@ export interface SessionMeta {
190
192
  * undefined for sessions obtained outside that path.
191
193
  */
192
194
  machine?: string;
195
+ /**
196
+ * Resolved actor id (`resolveActor().id`) who initiated this session — a
197
+ * tailnet login/email for a resolved human, or `UNRESOLVED@<host>`. Persisted
198
+ * write-once at session creation and preserved across content rescans (kept
199
+ * out of the DB upsert's ON CONFLICT set). Undefined for rows created before
200
+ * actor stamping. RUSH-2018.
201
+ */
202
+ actor?: string;
203
+ /**
204
+ * The actor's kind (`resolveActor().kind`): `'human'` for a person-initiated
205
+ * run, `'agent'` for one an agent spawned. Pairs with {@link actor}, same
206
+ * split as `AGENTS_ACTOR` / `AGENTS_ACTOR_KIND` on the exec env. RUSH-2018.
207
+ */
208
+ initiatedBy?: 'human' | 'agent';
193
209
  /**
194
210
  * True only for rows pulled from another machine over the live cross-machine
195
211
  * fan-out (`remote-list.ts`) — their transcript is on that peer's disk, so
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import * as fs from 'fs';
8
8
  import * as path from 'path';
9
- import { AGENTS, agentConfigDirName } from '../../agents.js';
9
+ import { AGENTS, MANAGED_AGENT_IDS, agentConfigDirName } from '../../agents.js';
10
10
  import { shouldInstallCommandAsSkill, listCommandSkillsInVersion } from '../../command-skills.js';
11
11
  import { lazyAgentMap } from '../writers/lazy-map.js';
12
12
  function buildCommandsDetector(agent) {
@@ -34,7 +34,7 @@ function buildCommandsDetector(agent) {
34
34
  // agents with their own slash-command runtime (nativeCommandRuntime) opt out.
35
35
  export const commandsDetectors = lazyAgentMap(() => {
36
36
  const m = {};
37
- for (const id of Object.keys(AGENTS)) {
37
+ for (const id of MANAGED_AGENT_IDS) {
38
38
  const cfg = AGENTS[id];
39
39
  if (cfg.capabilities.commands === false && (!cfg.commandsSubdir || cfg.commandsSubdir === '') && cfg.nativeCommandRuntime)
40
40
  continue;
@@ -3,7 +3,7 @@
3
3
  * reports the permission GROUP names that have been applied.
4
4
  *
5
5
  * For claude/opencode the detector intersects with discovered groups (a group
6
- * is "applied" if any of its allow/deny rules are present). For Codex / Gemini /
6
+ * is "applied" if any of its allow/deny rules are present). For Codex /
7
7
  * Antigravity / Grok the on-disk format is lossy — once any group has been
8
8
  * applied the storage doesn't carry per-group provenance back, so we report
9
9
  * "all known groups applied" when any permission artifact is present. This
@@ -100,29 +100,6 @@ function buildOpenCodeDetector() {
100
100
  },
101
101
  };
102
102
  }
103
- function buildGeminiDetector() {
104
- return {
105
- kind: 'permissions',
106
- agent: 'gemini',
107
- list({ versionHome }) {
108
- const settingsPath = path.join(versionHome, '.gemini', 'settings.json');
109
- if (!fs.existsSync(settingsPath))
110
- return [];
111
- try {
112
- const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
113
- const core = settings?.tools?.core;
114
- const exclude = settings?.tools?.exclude;
115
- const hasCore = Array.isArray(core) && core.length > 0;
116
- const hasExclude = Array.isArray(exclude) && exclude.length > 0;
117
- if (hasCore || hasExclude) {
118
- return discoverPermissionGroups().map(g => g.name);
119
- }
120
- }
121
- catch { /* parse fail */ }
122
- return [];
123
- },
124
- };
125
- }
126
103
  function buildAntigravityDetector() {
127
104
  return {
128
105
  kind: 'permissions',
@@ -312,25 +289,6 @@ function buildCopilotDetector() {
312
289
  },
313
290
  };
314
291
  }
315
- function buildForgeDetector() {
316
- return {
317
- kind: 'permissions',
318
- agent: 'forge',
319
- list({ versionHome }) {
320
- const permissionsPath = path.join(versionHome, '.forge', 'permissions.yaml');
321
- if (!fs.existsSync(permissionsPath))
322
- return [];
323
- try {
324
- const config = yaml.parse(fs.readFileSync(permissionsPath, 'utf-8'));
325
- if (config && Array.isArray(config.policies) && config.policies.length > 0) {
326
- return discoverPermissionGroups().map(g => g.name);
327
- }
328
- }
329
- catch { /* parse fail */ }
330
- return [];
331
- },
332
- };
333
- }
334
292
  function buildHermesDetector() {
335
293
  return {
336
294
  kind: 'permissions',
@@ -355,7 +313,6 @@ const handlers = {
355
313
  claude: buildClaudeDetector,
356
314
  codex: buildCodexDetector,
357
315
  opencode: buildOpenCodeDetector,
358
- gemini: buildGeminiDetector,
359
316
  antigravity: buildAntigravityDetector,
360
317
  grok: buildGrokDetector,
361
318
  goose: buildGooseDetector,
@@ -365,7 +322,6 @@ const handlers = {
365
322
  kiro: buildKiroDetector,
366
323
  openclaw: buildOpenClawDetector,
367
324
  copilot: buildCopilotDetector,
368
- forge: buildForgeDetector,
369
325
  hermes: buildHermesDetector,
370
326
  };
371
327
  export const permissionsDetectors = lazyAgentMap(() => {
@@ -11,15 +11,14 @@
11
11
  *
12
12
  * - native command file — `{agentDir}/<commandsSubdir>/<name>.md` (or .toml
13
13
  * when the agent's format is toml). Standard path for Claude, Codex
14
- * < 0.117.0, Gemini, Cursor, OpenCode, Copilot, Amp, Kiro, Roo,
15
- * Antigravity.
14
+ * < 0.117.0, Cursor, OpenCode, Copilot, Amp, Kiro, Roo, Antigravity.
16
15
  *
17
16
  * Source resolution is `resolveCommandSource` (user → system → extras —
18
17
  * project layer intentionally excluded).
19
18
  */
20
19
  import * as fs from 'fs';
21
20
  import * as path from 'path';
22
- import { AGENTS, agentConfigDirName } from '../../agents.js';
21
+ import { AGENTS, MANAGED_AGENT_IDS, agentConfigDirName } from '../../agents.js';
23
22
  import { supports } from '../../capabilities.js';
24
23
  import { safeJoin } from '../../paths.js';
25
24
  import { markdownToToml } from '../../convert.js';
@@ -83,7 +82,7 @@ function buildCommandsWriter(agent) {
83
82
  // Built lazily on first access — see lazy-map.ts for the cycle rationale.
84
83
  //
85
84
  // Registration covers two cases:
86
- // - native commands (claude, codex < 0.117.0, gemini, grok, etc.) — `commands` cap
85
+ // - native commands (claude, codex < 0.117.0, grok, etc.) — `commands` cap
87
86
  // - commands-as-skills (kimi, codex >= 0.117.0)
88
87
  //
89
88
  // Agents that have skills but use a NATIVE non-file slash-command system
@@ -92,7 +91,7 @@ function buildCommandsWriter(agent) {
92
91
  // commands, so there's nothing to write and nothing to convert.
93
92
  export const commandsWriters = lazyAgentMap(() => {
94
93
  const m = {};
95
- for (const id of Object.keys(AGENTS)) {
94
+ for (const id of MANAGED_AGENT_IDS) {
96
95
  const cfg = AGENTS[id];
97
96
  if (cfg.capabilities.commands === false && (!cfg.skillsDir || cfg.skillsDir === ''))
98
97
  continue;
@@ -90,7 +90,7 @@ function buildHooksWriter(agent) {
90
90
  // registerHooksForGrok — file copy alone only sees top-level available.hooks
91
91
  // names (RUSH-1353). Copilot/Kiro/Goose load managed *.json under their
92
92
  // hooks dirs the same way.
93
- if (agent === 'claude' || agent === 'codex' || agent === 'gemini' || agent === 'antigravity' || agent === 'kimi' || agent === 'droid' || agent === 'copilot' || agent === 'kiro' || agent === 'goose' || agent === 'cursor' || agent === 'grok' || agent === 'hermes') {
93
+ if (agent === 'claude' || agent === 'codex' || agent === 'antigravity' || agent === 'kimi' || agent === 'droid' || agent === 'copilot' || agent === 'kiro' || agent === 'goose' || agent === 'cursor' || agent === 'grok' || agent === 'hermes') {
94
94
  registerHooksToSettings(agent, versionHome);
95
95
  }
96
96
  return { synced };
@@ -25,7 +25,6 @@ export type Registrar = (program: Command) => void;
25
25
  export type ModuleLoader = () => Promise<Registrar>;
26
26
  export declare const loadView: ModuleLoader;
27
27
  export declare const loadInspect: ModuleLoader;
28
- export declare const loadResources: ModuleLoader;
29
28
  export declare const loadFeedback: ModuleLoader;
30
29
  export declare const loadCommands: ModuleLoader;
31
30
  export declare const loadHooks: ModuleLoader;
@@ -48,13 +47,13 @@ export declare const loadMonitors: ModuleLoader;
48
47
  export declare const loadRun: ModuleLoader;
49
48
  export declare const loadFork: ModuleLoader;
50
49
  export declare const loadDefaults: ModuleLoader;
50
+ export declare const loadSet: ModuleLoader;
51
51
  export declare const loadModels: ModuleLoader;
52
52
  export declare const loadPrune: ModuleLoader;
53
53
  export declare const loadTrash: ModuleLoader;
54
54
  export declare const loadRestore: ModuleLoader;
55
55
  export declare const loadDoctor: ModuleLoader;
56
56
  export declare const loadApply: ModuleLoader;
57
- export declare const loadCheck: ModuleLoader;
58
57
  export declare const loadStatus: ModuleLoader;
59
58
  export declare const loadProfiles: ModuleLoader;
60
59
  export declare const loadHarness: ModuleLoader;