@phnx-labs/agents-cli 1.20.77 → 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 (212) hide show
  1. package/CHANGELOG.md +589 -0
  2. package/README.md +27 -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 +74 -0
  12. package/dist/commands/doctor.js +718 -40
  13. package/dist/commands/events.js +1 -1
  14. package/dist/commands/exec.js +53 -8
  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 +195 -11
  29. package/dist/commands/routines.js +16 -4
  30. package/dist/commands/rules.js +13 -12
  31. package/dist/commands/secrets.d.ts +3 -1
  32. package/dist/commands/secrets.js +108 -34
  33. package/dist/commands/sessions-browser.d.ts +45 -3
  34. package/dist/commands/sessions-browser.js +118 -12
  35. package/dist/commands/sessions-export.d.ts +3 -0
  36. package/dist/commands/sessions-export.js +13 -10
  37. package/dist/commands/sessions-inject.d.ts +7 -4
  38. package/dist/commands/sessions-inject.js +18 -18
  39. package/dist/commands/sessions-picker.d.ts +15 -1
  40. package/dist/commands/sessions-picker.js +90 -7
  41. package/dist/commands/sessions-resume.d.ts +16 -0
  42. package/dist/commands/sessions-resume.js +14 -13
  43. package/dist/commands/sessions.d.ts +126 -1
  44. package/dist/commands/sessions.js +365 -42
  45. package/dist/commands/set.d.ts +15 -0
  46. package/dist/commands/set.js +79 -0
  47. package/dist/commands/setup-fleet.d.ts +21 -0
  48. package/dist/commands/setup-fleet.js +201 -0
  49. package/dist/commands/setup-secrets.d.ts +20 -0
  50. package/dist/commands/setup-secrets.js +223 -0
  51. package/dist/commands/setup.js +17 -3
  52. package/dist/commands/skills.js +1 -1
  53. package/dist/commands/ssh.js +9 -1
  54. package/dist/commands/sync.js +7 -2
  55. package/dist/commands/teams-picker.js +0 -1
  56. package/dist/commands/teams.d.ts +33 -1
  57. package/dist/commands/teams.js +208 -44
  58. package/dist/commands/versions.js +10 -5
  59. package/dist/commands/view.d.ts +1 -0
  60. package/dist/commands/view.js +11 -0
  61. package/dist/index.js +8 -13
  62. package/dist/lib/acp/harnesses.js +0 -7
  63. package/dist/lib/activity.d.ts +156 -0
  64. package/dist/lib/activity.js +282 -0
  65. package/dist/lib/agents.d.ts +9 -23
  66. package/dist/lib/agents.js +72 -100
  67. package/dist/lib/auto-pull.d.ts +16 -8
  68. package/dist/lib/auto-pull.js +23 -28
  69. package/dist/lib/browser/service.js +3 -0
  70. package/dist/lib/browser/types.d.ts +7 -0
  71. package/dist/lib/capabilities.js +6 -2
  72. package/dist/lib/crabbox/lease.js +2 -2
  73. package/dist/lib/crabbox/setup-copy.d.ts +4 -4
  74. package/dist/lib/crabbox/setup-copy.js +4 -4
  75. package/dist/lib/daemon.d.ts +26 -40
  76. package/dist/lib/daemon.js +63 -123
  77. package/dist/lib/devices/fleet-divergence.d.ts +101 -0
  78. package/dist/lib/devices/fleet-divergence.js +188 -0
  79. package/dist/lib/devices/fleet-inventory.d.ts +19 -0
  80. package/dist/lib/devices/fleet-inventory.js +57 -0
  81. package/dist/lib/devices/fleet.d.ts +1 -1
  82. package/dist/lib/devices/fleet.js +1 -1
  83. package/dist/lib/devices/health-report.d.ts +10 -2
  84. package/dist/lib/devices/health-report.js +32 -1
  85. package/dist/lib/doctor-diff.d.ts +20 -0
  86. package/dist/lib/doctor-diff.js +6 -1
  87. package/dist/lib/drift.d.ts +31 -11
  88. package/dist/lib/drift.js +58 -7
  89. package/dist/lib/events.d.ts +8 -1
  90. package/dist/lib/events.js +10 -1
  91. package/dist/lib/exec.js +35 -8
  92. package/dist/lib/feed-post.d.ts +28 -1
  93. package/dist/lib/feed-post.js +110 -2
  94. package/dist/lib/fleet/apply.js +8 -1
  95. package/dist/lib/fleet/auth-sync.d.ts +18 -1
  96. package/dist/lib/fleet/auth-sync.js +25 -11
  97. package/dist/lib/fleet/remote-login.js +5 -0
  98. package/dist/lib/git.d.ts +27 -14
  99. package/dist/lib/git.js +138 -43
  100. package/dist/lib/hooks.d.ts +42 -0
  101. package/dist/lib/hooks.js +137 -93
  102. package/dist/lib/hosts/dispatch.d.ts +28 -1
  103. package/dist/lib/hosts/dispatch.js +33 -5
  104. package/dist/lib/hosts/option.js +2 -2
  105. package/dist/lib/hosts/passthrough.d.ts +22 -1
  106. package/dist/lib/hosts/passthrough.js +238 -13
  107. package/dist/lib/hosts/remote-cmd.js +2 -0
  108. package/dist/lib/hosts/run-target.d.ts +2 -0
  109. package/dist/lib/hosts/run-target.js +1 -0
  110. package/dist/lib/hq/floor.d.ts +1 -1
  111. package/dist/lib/hq/floor.js +6 -1
  112. package/dist/lib/mcp.js +3 -71
  113. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +2 -0
  114. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  115. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  116. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +15 -2
  117. package/dist/lib/menubar/notify-desktop.d.ts +15 -0
  118. package/dist/lib/menubar/notify-desktop.js +41 -5
  119. package/dist/lib/merged-resources.d.ts +11 -0
  120. package/dist/{commands/resources.js → lib/merged-resources.js} +11 -19
  121. package/dist/lib/permissions.d.ts +0 -32
  122. package/dist/lib/permissions.js +4 -157
  123. package/dist/lib/picker.js +1 -1
  124. package/dist/lib/platform/process.d.ts +16 -5
  125. package/dist/lib/platform/process.js +54 -0
  126. package/dist/lib/plugins.d.ts +0 -8
  127. package/dist/lib/plugins.js +4 -110
  128. package/dist/lib/project-resources.js +4 -1
  129. package/dist/lib/refresh.d.ts +5 -3
  130. package/dist/lib/refresh.js +7 -5
  131. package/dist/lib/resources/commands.js +4 -1
  132. package/dist/lib/resources/mcp.js +0 -4
  133. package/dist/lib/resources/permissions.d.ts +1 -1
  134. package/dist/lib/resources/permissions.js +1 -5
  135. package/dist/lib/resources/rules.js +4 -1
  136. package/dist/lib/resources/skills.js +4 -1
  137. package/dist/lib/resources/subagents.js +4 -0
  138. package/dist/lib/resources/types.d.ts +1 -1
  139. package/dist/lib/routines.d.ts +20 -0
  140. package/dist/lib/routines.js +6 -0
  141. package/dist/lib/run-defaults.d.ts +1 -0
  142. package/dist/lib/run-defaults.js +9 -0
  143. package/dist/lib/runner.d.ts +0 -12
  144. package/dist/lib/runner.js +71 -29
  145. package/dist/lib/sandbox.js +0 -16
  146. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  147. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +2 -0
  148. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  149. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
  150. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +13 -1
  151. package/dist/lib/secrets/agent.d.ts +18 -0
  152. package/dist/lib/secrets/agent.js +131 -17
  153. package/dist/lib/secrets/audit.d.ts +46 -0
  154. package/dist/lib/secrets/audit.js +56 -0
  155. package/dist/lib/secrets/bundles.d.ts +12 -9
  156. package/dist/lib/secrets/bundles.js +47 -31
  157. package/dist/lib/secrets/index.d.ts +2 -1
  158. package/dist/lib/secrets/index.js +8 -2
  159. package/dist/lib/secrets/remote.d.ts +75 -2
  160. package/dist/lib/secrets/remote.js +139 -5
  161. package/dist/lib/secrets/scope.d.ts +26 -0
  162. package/dist/lib/secrets/scope.js +29 -0
  163. package/dist/lib/secrets/session-store.d.ts +10 -0
  164. package/dist/lib/secrets/session-store.js +64 -11
  165. package/dist/lib/secrets/sync.js +3 -3
  166. package/dist/lib/session/active.d.ts +117 -26
  167. package/dist/lib/session/active.js +199 -53
  168. package/dist/lib/session/actor-sidecar.d.ts +23 -0
  169. package/dist/lib/session/actor-sidecar.js +101 -0
  170. package/dist/lib/session/db.d.ts +41 -1
  171. package/dist/lib/session/db.js +173 -8
  172. package/dist/lib/session/digest.js +126 -21
  173. package/dist/lib/session/discover.d.ts +19 -2
  174. package/dist/lib/session/discover.js +32 -11
  175. package/dist/lib/session/hook-sessions.d.ts +9 -0
  176. package/dist/lib/session/hook-sessions.js +57 -8
  177. package/dist/lib/session/origin-machine.d.ts +18 -0
  178. package/dist/lib/session/origin-machine.js +34 -0
  179. package/dist/lib/session/pid-registry.d.ts +13 -0
  180. package/dist/lib/session/render.d.ts +2 -0
  181. package/dist/lib/session/render.js +1 -1
  182. package/dist/lib/session/state.d.ts +1 -0
  183. package/dist/lib/session/state.js +1 -1
  184. package/dist/lib/session/sync/config.js +2 -2
  185. package/dist/lib/session/types.d.ts +16 -0
  186. package/dist/lib/smart-launch.d.ts +86 -0
  187. package/dist/lib/smart-launch.js +172 -0
  188. package/dist/lib/staleness/detectors/commands.js +2 -2
  189. package/dist/lib/staleness/detectors/permissions.js +1 -45
  190. package/dist/lib/staleness/writers/commands.js +4 -5
  191. package/dist/lib/staleness/writers/hooks.js +1 -1
  192. package/dist/lib/startup/command-registry.d.ts +1 -2
  193. package/dist/lib/startup/command-registry.js +2 -4
  194. package/dist/lib/subagents-registry.js +5 -12
  195. package/dist/lib/subagents.d.ts +0 -10
  196. package/dist/lib/subagents.js +0 -12
  197. package/dist/lib/teams/agents.d.ts +0 -2
  198. package/dist/lib/teams/agents.js +10 -27
  199. package/dist/lib/teams/index.d.ts +1 -1
  200. package/dist/lib/teams/index.js +1 -1
  201. package/dist/lib/types.d.ts +17 -9
  202. package/dist/lib/usage.d.ts +28 -5
  203. package/dist/lib/usage.js +271 -8
  204. package/dist/lib/versions.js +7 -1
  205. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  206. package/dist/lib/watchdog/watchdog.js +25 -13
  207. package/package.json +1 -2
  208. package/dist/commands/check.d.ts +0 -15
  209. package/dist/commands/check.js +0 -180
  210. package/dist/commands/resources.d.ts +0 -5
  211. package/dist/lib/secrets/account-token.d.ts +0 -20
  212. package/dist/lib/secrets/account-token.js +0 -64
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Harness scoping for an unlocked bundle — the shared vocabulary of the broker
3
+ * (agent.ts), the durable session store (session-store.ts), and the read path
4
+ * (bundles.ts).
5
+ *
6
+ * A grant is stored under a scope and read under a scope; the two must agree or
7
+ * the bundle is invisible. `agents secrets unlock --for <agent>` exists to NARROW
8
+ * a grant to one harness, so an unlock without it is global by definition.
9
+ *
10
+ * This module deliberately has NO imports: agent.ts and session-store.ts already
11
+ * import each other, and hanging the scope constants off either one would close
12
+ * that cycle — under ESM a cyclic `const` read can land in the temporal dead zone
13
+ * and throw at runtime even though tsc is happy.
14
+ */
15
+ /**
16
+ * Scope of an unlock that was not narrowed with `--for`: readable by every
17
+ * harness. Not a valid harness name, so it can never collide with one.
18
+ */
19
+ export declare const GLOBAL_HARNESS = "*";
20
+ /**
21
+ * Scopes a reader consults, most specific first: its own harness, then the global
22
+ * grant. This is the resolution order of the scoped-grant model — a narrow
23
+ * `--for claude` unlock stays claude-only while an unscoped unlock serves
24
+ * everyone — not a fallback papering over a miss.
25
+ */
26
+ export declare function bundleScopeChain(harness: string | undefined): string[];
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Harness scoping for an unlocked bundle — the shared vocabulary of the broker
3
+ * (agent.ts), the durable session store (session-store.ts), and the read path
4
+ * (bundles.ts).
5
+ *
6
+ * A grant is stored under a scope and read under a scope; the two must agree or
7
+ * the bundle is invisible. `agents secrets unlock --for <agent>` exists to NARROW
8
+ * a grant to one harness, so an unlock without it is global by definition.
9
+ *
10
+ * This module deliberately has NO imports: agent.ts and session-store.ts already
11
+ * import each other, and hanging the scope constants off either one would close
12
+ * that cycle — under ESM a cyclic `const` read can land in the temporal dead zone
13
+ * and throw at runtime even though tsc is happy.
14
+ */
15
+ /**
16
+ * Scope of an unlock that was not narrowed with `--for`: readable by every
17
+ * harness. Not a valid harness name, so it can never collide with one.
18
+ */
19
+ export const GLOBAL_HARNESS = '*';
20
+ /**
21
+ * Scopes a reader consults, most specific first: its own harness, then the global
22
+ * grant. This is the resolution order of the scoped-grant model — a narrow
23
+ * `--for claude` unlock stays claude-only while an unscoped unlock serves
24
+ * everyone — not a fallback papering over a miss.
25
+ */
26
+ export function bundleScopeChain(harness) {
27
+ const own = harness || GLOBAL_HARNESS;
28
+ return own === GLOBAL_HARNESS ? [GLOBAL_HARNESS] : [own, GLOBAL_HARNESS];
29
+ }
@@ -74,6 +74,16 @@ export declare function readIndex(): SessionIndex;
74
74
  export declare function writeIndex(index: SessionIndex): void;
75
75
  /** Persist one unlocked bundle: write its blob no-ACL and record it in the index. */
76
76
  export declare function saveSession(name: string, entry: SessionEntry): void;
77
+ /**
78
+ * Read a session honoring the scope chain: the caller's own harness first, then
79
+ * the global grant (see bundleScopeChain in scope.ts). This is the durable-store
80
+ * twin of the broker's `get` — both must resolve identically, or a bundle would
81
+ * be readable from RAM but not after a restart.
82
+ */
83
+ export declare function resolveSession(name: string, now?: number, harness?: string): {
84
+ entry: SessionEntry;
85
+ harness: string;
86
+ } | null;
77
87
  /** Read one session blob by known name. Null when absent/expired/malformed. */
78
88
  export declare function loadSession(name: string, now?: number, harness?: string): SessionEntry | null;
79
89
  /** Remove every persisted harness grant for one bundle. */
@@ -25,6 +25,7 @@
25
25
  * persistence is an optimization, not a correctness dependency.
26
26
  */
27
27
  import { getKeychainToken, setKeychainToken, deleteKeychainToken, isKeychainBackendOverridden } from './index.js';
28
+ import { GLOBAL_HARNESS, bundleScopeChain } from './scope.js';
28
29
  /** Prefix for all durable session items (device-local, no-ACL). */
29
30
  export const SESSION_ITEM_PREFIX = 'agents-cli.session.';
30
31
  /** Fixed-name index item — the ONLY thing we ever need to find without a known
@@ -116,7 +117,7 @@ export function saveSession(name, entry) {
116
117
  if (!shouldPersist())
117
118
  return;
118
119
  try {
119
- const harness = entry.harness || 'cli';
120
+ const harness = entry.harness || GLOBAL_HARNESS;
120
121
  const key = `${harness}:${name}`;
121
122
  setKeychainToken(sessionBlobItem(name, harness), JSON.stringify({ ...entry, harness }), { noAcl: true });
122
123
  writeIndex(upsertEntry(readIndex(), key, { expiresAt: entry.expiresAt, sleepPersist: entry.sleepPersist, harness }));
@@ -125,8 +126,22 @@ export function saveSession(name, entry) {
125
126
  /* best-effort — persistence is an optimization */
126
127
  }
127
128
  }
129
+ /**
130
+ * Read a session honoring the scope chain: the caller's own harness first, then
131
+ * the global grant (see bundleScopeChain in scope.ts). This is the durable-store
132
+ * twin of the broker's `get` — both must resolve identically, or a bundle would
133
+ * be readable from RAM but not after a restart.
134
+ */
135
+ export function resolveSession(name, now = Date.now(), harness = GLOBAL_HARNESS) {
136
+ for (const scope of bundleScopeChain(harness)) {
137
+ const entry = loadSession(name, now, scope);
138
+ if (entry)
139
+ return { entry, harness: scope };
140
+ }
141
+ return null;
142
+ }
128
143
  /** Read one session blob by known name. Null when absent/expired/malformed. */
129
- export function loadSession(name, now = Date.now(), harness = 'cli') {
144
+ export function loadSession(name, now = Date.now(), harness = GLOBAL_HARNESS) {
130
145
  if (!shouldPersist())
131
146
  return null;
132
147
  try {
@@ -154,7 +169,7 @@ export function deleteBundleSessions(name) {
154
169
  const scopedName = key.includes(':') ? key.split(':').slice(1).join(':') : key;
155
170
  if (scopedName !== name)
156
171
  continue;
157
- const harness = meta.harness || 'cli';
172
+ const harness = meta.harness || GLOBAL_HARNESS;
158
173
  try {
159
174
  deleteKeychainToken(key.includes(':') ? sessionBlobItem(name, harness) : `${SESSION_ITEM_PREFIX}${name}`);
160
175
  }
@@ -164,7 +179,7 @@ export function deleteBundleSessions(name) {
164
179
  writeIndex(next);
165
180
  }
166
181
  /** Delete one bundle's session blob and prune it from the index. */
167
- export function deleteSession(name, harness = 'cli') {
182
+ export function deleteSession(name, harness = GLOBAL_HARNESS) {
168
183
  if (!shouldPersist())
169
184
  return;
170
185
  try {
@@ -185,7 +200,7 @@ export function deleteAllSessions() {
185
200
  const bundleName = name.includes(':') ? name.split(':').slice(1).join(':') : name;
186
201
  try {
187
202
  deleteKeychainToken(name.includes(':')
188
- ? sessionBlobItem(bundleName, meta.harness || 'cli')
203
+ ? sessionBlobItem(bundleName, meta.harness || GLOBAL_HARNESS)
189
204
  : `${SESSION_ITEM_PREFIX}${bundleName}`);
190
205
  }
191
206
  catch { /* keep going */ }
@@ -214,23 +229,61 @@ export function rehydrateSessions(now = Date.now()) {
214
229
  try {
215
230
  const raw = getKeychainToken(`${SESSION_ITEM_PREFIX}${key}`);
216
231
  const legacy = JSON.parse(raw);
217
- setKeychainToken(sessionBlobItem(key, 'cli'), JSON.stringify({ ...legacy, harness: 'cli' }), { noAcl: true });
232
+ setKeychainToken(sessionBlobItem(key, GLOBAL_HARNESS), JSON.stringify({ ...legacy, harness: GLOBAL_HARNESS }), { noAcl: true });
218
233
  deleteKeychainToken(`${SESSION_ITEM_PREFIX}${key}`);
219
- migratedIndex = upsertEntry(removeEntry(migratedIndex, key), `cli:${key}`, {
234
+ migratedIndex = upsertEntry(removeEntry(migratedIndex, key), `${GLOBAL_HARNESS}:${key}`, {
220
235
  expiresAt: meta.expiresAt,
221
236
  sleepPersist: meta.sleepPersist,
222
- harness: 'cli',
237
+ harness: GLOBAL_HARNESS,
223
238
  });
224
239
  }
225
240
  catch { /* malformed/absent legacy entry is pruned below */ }
226
241
  }
242
+ // Second source migration: `cli`-scoped grants predate the global scope. `cli`
243
+ // was never a harness — it was the default when no AGENTS_AGENT_NAME was set,
244
+ // i.e. "unlocked from a terminal for general use", which is exactly the global
245
+ // grant. Rewriting them means an unlock a user already paid Touch ID for keeps
246
+ // working after upgrade instead of silently becoming unreadable to every agent.
247
+ for (const [key, meta] of Object.entries(migratedIndex.bundles)) {
248
+ if (!key.startsWith('cli:'))
249
+ continue;
250
+ const bundleName = key.slice('cli:'.length);
251
+ const globalKey = `${GLOBAL_HARNESS}:${bundleName}`;
252
+ // A global grant for this bundle can already exist: the user re-runs
253
+ // `unlock` on the new code (which writes the global scope) before the
254
+ // broker restarts to run this migration. The existing global grant is the
255
+ // NEWER one, so the stale `cli` entry is discarded, never merged over it —
256
+ // overwriting would restore a superseded token and, if the stale entry had
257
+ // expired, hand its expiry to the fresh grant so the prune below deletes a
258
+ // valid unlock outright.
259
+ if (migratedIndex.bundles[globalKey]) {
260
+ try {
261
+ deleteKeychainToken(sessionBlobItem(bundleName, 'cli'));
262
+ }
263
+ catch { /* already gone */ }
264
+ migratedIndex = removeEntry(migratedIndex, key);
265
+ continue;
266
+ }
267
+ try {
268
+ const raw = getKeychainToken(sessionBlobItem(bundleName, 'cli'));
269
+ const legacy = JSON.parse(raw);
270
+ setKeychainToken(sessionBlobItem(bundleName, GLOBAL_HARNESS), JSON.stringify({ ...legacy, harness: GLOBAL_HARNESS }), { noAcl: true });
271
+ deleteKeychainToken(sessionBlobItem(bundleName, 'cli'));
272
+ migratedIndex = upsertEntry(removeEntry(migratedIndex, key), globalKey, {
273
+ expiresAt: meta.expiresAt,
274
+ sleepPersist: meta.sleepPersist,
275
+ harness: GLOBAL_HARNESS,
276
+ });
277
+ }
278
+ catch { /* malformed/absent entry is pruned below */ }
279
+ }
227
280
  writeIndex(migratedIndex);
228
281
  index.bundles = migratedIndex.bundles;
229
282
  const { survivors, expiredNames } = pruneExpired(index, now);
230
283
  for (const name of expiredNames) {
231
284
  const meta = index.bundles[name];
232
285
  try {
233
- deleteKeychainToken(sessionBlobItem(name.split(':').slice(1).join(':'), meta.harness || 'cli'));
286
+ deleteKeychainToken(sessionBlobItem(name.split(':').slice(1).join(':'), meta.harness || GLOBAL_HARNESS));
234
287
  }
235
288
  catch { /* keep going */ }
236
289
  }
@@ -239,7 +292,7 @@ export function rehydrateSessions(now = Date.now()) {
239
292
  for (const name of selectRehydratable(survivors, now)) {
240
293
  const meta = survivors.bundles[name];
241
294
  const bundleName = name.split(':').slice(1).join(':');
242
- const entry = loadSession(bundleName, now, meta.harness || 'cli');
295
+ const entry = loadSession(bundleName, now, meta.harness || GLOBAL_HARNESS);
243
296
  if (entry)
244
297
  out.push({ name: bundleName, entry });
245
298
  }
@@ -259,7 +312,7 @@ export function pruneSessionsOnSleep() {
259
312
  for (const name of deletedNames) {
260
313
  const meta = readIndex().bundles[name];
261
314
  try {
262
- deleteKeychainToken(sessionBlobItem(name.split(':').slice(1).join(':'), meta.harness || 'cli'));
315
+ deleteKeychainToken(sessionBlobItem(name.split(':').slice(1).join(':'), meta.harness || GLOBAL_HARNESS));
263
316
  }
264
317
  catch { /* keep going */ }
265
318
  }
@@ -14,7 +14,7 @@ import * as crypto from 'crypto';
14
14
  import { deleteKeychainToken, getKeychainToken, hasKeychainToken, secretsKeychainItem, setKeychainToken, } from './index.js';
15
15
  import { readBundle, writeBundle, keychainItemsForBundle, validateBundleName, } from './bundles.js';
16
16
  import { rushSyncBackend } from './drivers/rush.js';
17
- import { emit } from '../events.js';
17
+ import { emitSecretAudit } from './audit.js';
18
18
  // PBKDF2 cost. 600k SHA-256 iters matches OWASP 2023+ guidance and keeps a
19
19
  // passphrase prompt under a second on the hardware the CLI targets.
20
20
  const PBKDF2_ITER = 600_000;
@@ -190,8 +190,8 @@ export async function pushBundle(name, opts) {
190
190
  // bundle off-machine — the most sensitive read there is. It bypasses
191
191
  // readAndResolveBundleEnv, so audit it explicitly. Values never enter the
192
192
  // payload; only the bundle name and how many keys were read.
193
- emit('secrets.get', {
194
- module: 'secrets',
193
+ emitSecretAudit({
194
+ event: 'secrets.get',
195
195
  bundle: name,
196
196
  operation: 'sync push',
197
197
  source: 'sync-push',
@@ -1,11 +1,20 @@
1
1
  import type { CloudTaskStatus } from '../cloud/types.js';
2
2
  import { type PidSessionEntry } from './pid-registry.js';
3
3
  import { type HookSessionIndex } from './hook-sessions.js';
4
- import { type SessionActivity, type AwaitingReason, type StructuredQuestion, type TodoProgress, type DetectedPr, type DetectedWorktree, type DetectedTicket } from './state.js';
5
- import type { SessionAttachment } from './types.js';
4
+ import { type SessionState, type SessionActivity, type AwaitingReason, type StructuredQuestion, type TodoProgress, type DetectedPr, type DetectedWorktree, type DetectedTicket } from './state.js';
5
+ import { type SessionAttachment } from './types.js';
6
6
  import { type SessionProvenance } from './provenance.js';
7
7
  import { type DeviceRegistry } from '../devices/registry.js';
8
8
  import { type Presence } from './detached.js';
9
+ /**
10
+ * The owner (actor id) to show for a session in `--active`. Prefers the actor
11
+ * recorded on the live-attribution source (the pid registry / teammate record),
12
+ * but falls back to the durable per-session actor sidecar — written at spawn and,
13
+ * unlike the pid entry, NOT overwritten by the SessionStart hook's own by-pid
14
+ * write. Without this fallback a real `agents run` shows no owner whenever the
15
+ * hook's actor-less entry wins the by-pid file (RUSH-2018 fix).
16
+ */
17
+ export declare function resolveOwner(pidActor: string | null | undefined, sessionId: string | undefined): string | undefined;
9
18
  /**
10
19
  * Per-PID `lsof` probes run bounded and staggered rather than as one parallel
11
20
  * fan-out: a simultaneous system-wide `lsof` burst reads to behavioral EDR
@@ -15,14 +24,27 @@ import { type Presence } from './detached.js';
15
24
  export declare const LSOF_CONCURRENCY = 4;
16
25
  export type ActiveContext = 'terminal' | 'teams' | 'cloud' | 'headless';
17
26
  /**
18
- * `unknown` = the process is alive but we cannot introspect what it is doing —
19
- * a live harness whose transcript format we do not parse (everything but
20
- * claude/codex), or a resolvable transcript whose `stat` momentarily failed. It
21
- * is NOT a synonym for idle: idle is a positive "not mid-turn, not waiting on
22
- * you" conclusion drawn from a readable transcript; unknown is the honest "we
23
- * can't tell", which we refuse to fake as idle.
27
+ * Every status here is COMPUTED by the framework from observable signals PID
28
+ * liveness and transcript last-write (mtime) never self-reported by the agent.
29
+ *
30
+ * - `running` / `idle` / `input_required` a live process's working / stopped
31
+ * / waiting-on-you activity, from its transcript (see {@link computeLiveSignals}).
32
+ * - `queued` — dispatched, work in the pipeline (cloud/headless launch).
33
+ * - `closed` — the PID is dead. The process has exited; report that, don't
34
+ * fabricate `idle` ("done, waiting for you") for a process that is simply gone.
35
+ * - `abandoned` — no transcript write in {@link ABANDONED_STALE_MS} (days): the
36
+ * session is dangling, whether its PID is dead (long gone) or still alive but
37
+ * stuck making no progress. A soft signal — it clears the moment it writes again.
38
+ * - `unknown` — the residual: genuinely NO signal (no PID info AND no file).
39
+ *
40
+ * A LIVE, fresh process is never `unknown` — "the process is alive" is itself a
41
+ * positive signal, so an opaque/unparseable live harness resolves to `running`
42
+ * (its honest floor), and every tracked harness with a locatable, parseable
43
+ * transcript (claude, codex, grok, droid, rush, gemini, kimi, hermes, opencode,
44
+ * antigravity) gets a real working/waiting/idle from its own parser — see
45
+ * {@link computeLiveSignals}, {@link lifecycleStatus} and {@link resolveFallbackStatus}.
24
46
  */
25
- export type ActiveStatus = 'running' | 'idle' | 'queued' | 'input_required' | 'unknown';
47
+ export type ActiveStatus = 'running' | 'idle' | 'queued' | 'input_required' | 'closed' | 'abandoned' | 'unknown';
26
48
  export interface ActiveSession {
27
49
  context: ActiveContext;
28
50
  kind: string;
@@ -109,6 +131,14 @@ export interface ActiveSession {
109
131
  * Absent for cloud sessions (no local pid) and any pid whose env is unreadable.
110
132
  */
111
133
  provenance?: SessionProvenance;
134
+ /**
135
+ * Who initiated this session — the resolved actor id stamped at spawn
136
+ * (`resolveActor().id`, read back from the pid registry / teammate record).
137
+ * A tailnet login/email for a resolved human, `UNRESOLVED@<host>` when it
138
+ * couldn't be determined, absent when the launch predates actor stamping.
139
+ * Surfaced as the owner column in `--active` (RUSH-2018).
140
+ */
141
+ owner?: string;
112
142
  /**
113
143
  * The machine this session runs on, as a normalized device id (machineId()
114
144
  * form). Set when merging cross-machine results so the grouped `--active`
@@ -172,6 +202,16 @@ export interface ActiveQueryOptions {
172
202
  * transcript just because a GUI app service with the same basename is alive.
173
203
  */
174
204
  export declare const ACTIVE_SESSION_STALE_MS: number;
205
+ /**
206
+ * A session whose transcript hasn't been written in this long is ABANDONED /
207
+ * dangling — the framework can no longer treat it as live work, whether its PID
208
+ * is dead (long gone) or still alive but making no progress (a hung agent).
209
+ * Two days: past a normal work gap, well short of a session legitimately kept
210
+ * open across a long weekend. Deliberately far wider than
211
+ * {@link ACTIVE_SESSION_STALE_MS} (which bounds transcript-borrowing, a different
212
+ * concern) — this is the lifecycle threshold, not the freshness window.
213
+ */
214
+ export declare const ABANDONED_STALE_MS: number;
175
215
  /**
176
216
  * Resolve an agent kind from a process's reported executable. `comm` may be an
177
217
  * absolute path (shim-launched agents), and Windows image names carry an
@@ -189,6 +229,13 @@ export declare function agentKindFromComm(commRaw: string): string | undefined;
189
229
  * the start time can't be read, we keep the existence answer.
190
230
  */
191
231
  export declare function isPidAlive(pid: number, startedAtMs?: number): boolean;
232
+ /**
233
+ * Resolve a Claude transcript for `cwd` across the given project roots, newest
234
+ * mtime winning when the same id/cwd resolves in more than one version home
235
+ * (the actively-written copy is the newest). Pure over `projectRoots`, so it is
236
+ * testable against temp dirs without touching the real home directory.
237
+ */
238
+ export declare function pickClaudeSessionFileAcrossRoots(projectRoots: string[], cwd: string, sessionId?: string): string | undefined;
192
239
  /**
193
240
  * Pick a Claude transcript file within a project dir.
194
241
  *
@@ -212,30 +259,74 @@ export declare function sessionFileTimes(sessionFile: string | undefined): {
212
259
  birthtimeMs?: number;
213
260
  mtimeMs?: number;
214
261
  };
262
+ /**
263
+ * The lifecycle status computed purely from the two hard signals the framework
264
+ * always has — PID liveness and transcript last-write (mtime) — independent of
265
+ * anything the agent self-reports or what its last transcript turn happened to
266
+ * look like. Returns the definitive lifecycle label when one applies, or
267
+ * `undefined` when the process is live and fresh (so the caller uses the richer
268
+ * activity-derived status instead).
269
+ *
270
+ * - No write in {@link ABANDONED_STALE_MS} ⇒ `abandoned` (dangling): a
271
+ * days-stale session is not live work, whether its PID is dead (long gone)
272
+ * or still alive but stuck. Checked first — it outranks a bare `closed`.
273
+ * - Otherwise, PID dead ⇒ `closed`: the process has exited. This is the fix
274
+ * for the old "dead PID reports idle" lie — `idle` reads as "done, waiting
275
+ * for you", but a dead process is done, period.
276
+ * - Otherwise (alive + fresh) ⇒ `undefined`: defer to the activity engine.
277
+ */
278
+ export declare function lifecycleStatus(pidAlive: boolean, mtimeMs: number | undefined, nowMs?: number): ActiveStatus | undefined;
215
279
  /**
216
280
  * The ONE place a fallback status is decided when no rich transcript state is
217
- * available — a non-Claude/Codex kind we cannot parse, or a Claude/Codex tail
218
- * that was empty or unreadable. Honest by construction: it never asserts a status
219
- * it cannot justify from a measured signal.
281
+ * available — an opaque kind we cannot parse (cursor, openclaw), or a transcript
282
+ * whose parse/tail was empty or unreadable. Honest by construction: computed from
283
+ * PID + mtime, never a fabricated `idle`.
220
284
  *
221
- * - Resolvable transcript, readable mtime the MEASURED freshness signal:
222
- * written within ACTIVE_MTIME_WINDOW_MS `running`, else `idle`.
223
- * - Resolvable transcript whose `stat` throws (file vanished / permission) we
224
- * genuinely cannot tell `unknown`. (This branch previously returned
225
- * `running`, which contradicted the `idle` default one branch up.)
226
- * - No resolvable transcript but the process is alive → alive-but-opaque
227
- * `unknown`. This is the truthful answer for a live gemini / droid / cursor /
228
- * opencode whose format we don't parse NOT a fabricated `idle` (which the
229
- * UI reads as "done and waiting"), and it never lies as `running` either.
230
- * - No transcript and the process is not known alive → nothing to report ⇒ `idle`.
285
+ * - Dead PID, or a days-stale transcript {@link lifecycleStatus} (`closed` /
286
+ * `abandoned`). A dead process is `closed`, not the old fabricated `idle`
287
+ * (which the UI reads as "done, waiting for you"); a dead process whose file
288
+ * also vanished is still `closed` (death is a definitive answer, not `unknown`).
289
+ * - Live + fresh ⇒ `running`. A live process is, at minimum, running: we may
290
+ * not see WHAT an opaque harness (or an empty tail) is doing, but the process
291
+ * being alive is a positive signal never a blank `unknown` (the old blanket
292
+ * bug for every non-claude/codex live agent) nor a downgraded `idle`.
231
293
  */
232
- export declare function resolveFallbackStatus(sessionFile: string | undefined, pidAlive: boolean): ActiveStatus;
294
+ export declare function resolveFallbackStatus(sessionFile: string | undefined, pidAlive: boolean, nowMs?: number): ActiveStatus;
233
295
  /**
234
- * Locate the live transcript for an agent process. Claude files are keyed by
235
- * cwd (+ optional session uuid); Codex files are date-partitioned, so we resolve
236
- * the newest indexed Codex session for the cwd instead.
296
+ * Locate the live transcript for an agent process. Claude files are keyed by cwd
297
+ * (+ optional session uuid), so they resolve straight off disk. Every OTHER
298
+ * tracked harness Codex (date-partitioned), plus grok / droid / rush / gemini /
299
+ * kimi / hermes / opencode / antigravity (per-session dirs, SQLite, single-JSON)
300
+ * — is resolved through the session index by cwd: the newest indexed transcript
301
+ * for that cwd, bounded by ACTIVE_SESSION_STALE_MS so a live pid never borrows a
302
+ * weeks-old transcript. This is what lets a live NON-claude/codex agent get a real
303
+ * status instead of falling through to `unknown` (the file feeds
304
+ * {@link computeLiveSignals}). An opaque kind we don't track (cursor) still yields
305
+ * undefined here and degrades honestly to a live `running`.
237
306
  */
238
307
  export declare function findSessionFileForKind(kind: string, cwd?: string, sessionId?: string): string | undefined;
308
+ /** Live per-session signals derived from one transcript-tail read. */
309
+ interface LiveSignals {
310
+ /** Rich inferred state (activity/preview/badges). Undefined when unreadable. */
311
+ state?: SessionState;
312
+ /** Rolling output-token throughput (tokens/sec). Undefined when no usage in the tail. */
313
+ tokPerSec?: number;
314
+ }
315
+ /**
316
+ * Derive the inferred state (working / waiting / idle + preview/badges) and, for
317
+ * the two harnesses whose raw lines carry it, the output-token throughput.
318
+ *
319
+ * Claude/Codex take the fast bounded byte-tail ({@link readSessionTailWithRaw}) —
320
+ * the hot path, and the only two that also yield throughput (their raw lines
321
+ * carry usage the event model drops). EVERY OTHER tracked harness (grok, droid,
322
+ * rush, gemini, kimi, hermes, opencode, antigravity) is parsed with its own
323
+ * parser and run through the SAME {@link inferSessionState}, so a live
324
+ * non-claude/codex agent gets a real working/waiting/idle instead of the blanket
325
+ * `unknown` it used to fall through to. An opaque/untracked kind (cursor) or an
326
+ * unreadable/empty transcript yields an empty signal set, and the caller's
327
+ * {@link resolveFallbackStatus} reports the honest live floor (`running`).
328
+ */
329
+ export declare function computeLiveSignals(kind: string, sessionFile: string | undefined, cwd: string | undefined, pidAlive: boolean): LiveSignals;
239
330
  /** Live teams teammates. Reuses AgentManager which already polls PIDs via `kill -0`. */
240
331
  export declare function listTeamsActive(): Promise<ActiveSession[]>;
241
332
  /** Live editor-terminal agents across every IDE window. */