@phnx-labs/agents-cli 1.20.74 → 1.20.76

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 (199) hide show
  1. package/CHANGELOG.md +893 -0
  2. package/README.md +36 -10
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/apply.js +21 -3
  5. package/dist/commands/attach.d.ts +10 -0
  6. package/dist/commands/attach.js +41 -0
  7. package/dist/commands/computer.js +3 -3
  8. package/dist/commands/detach-core.d.ts +46 -0
  9. package/dist/commands/detach-core.js +61 -0
  10. package/dist/commands/detach.d.ts +5 -0
  11. package/dist/commands/detach.js +171 -0
  12. package/dist/commands/doctor.js +18 -0
  13. package/dist/commands/exec.js +31 -3
  14. package/dist/commands/feed.d.ts +7 -1
  15. package/dist/commands/feed.js +47 -4
  16. package/dist/commands/go.d.ts +9 -2
  17. package/dist/commands/go.js +16 -6
  18. package/dist/commands/harness.d.ts +14 -0
  19. package/dist/commands/harness.js +145 -0
  20. package/dist/commands/import.js +38 -8
  21. package/dist/commands/inspect.js +5 -2
  22. package/dist/commands/profiles.d.ts +20 -0
  23. package/dist/commands/profiles.js +68 -21
  24. package/dist/commands/repo.js +14 -0
  25. package/dist/commands/routines.js +15 -2
  26. package/dist/commands/secrets.js +44 -21
  27. package/dist/commands/sessions-browser.js +1 -1
  28. package/dist/commands/sessions-export.js +19 -5
  29. package/dist/commands/sessions-migrate.d.ts +29 -0
  30. package/dist/commands/sessions-migrate.js +596 -0
  31. package/dist/commands/sessions-picker.js +16 -1
  32. package/dist/commands/sessions.d.ts +36 -13
  33. package/dist/commands/sessions.js +210 -40
  34. package/dist/commands/setup-computer.js +2 -2
  35. package/dist/commands/ssh.js +71 -35
  36. package/dist/commands/status.js +10 -2
  37. package/dist/commands/versions.js +15 -6
  38. package/dist/commands/view.d.ts +5 -0
  39. package/dist/commands/view.js +24 -4
  40. package/dist/commands/watchdog.d.ts +7 -2
  41. package/dist/commands/watchdog.js +73 -57
  42. package/dist/index.js +58 -7
  43. package/dist/lib/activity.d.ts +14 -4
  44. package/dist/lib/activity.js +17 -5
  45. package/dist/lib/actor.d.ts +50 -0
  46. package/dist/lib/actor.js +166 -0
  47. package/dist/lib/agent-spec/provider.js +2 -1
  48. package/dist/lib/agent-spec/resolve.js +19 -5
  49. package/dist/lib/agent-spec/types.d.ts +9 -1
  50. package/dist/lib/agents.d.ts +21 -0
  51. package/dist/lib/agents.js +70 -6
  52. package/dist/lib/cloud/codex.d.ts +2 -0
  53. package/dist/lib/cloud/codex.js +14 -3
  54. package/dist/lib/cloud/session-index.d.ts +32 -0
  55. package/dist/lib/cloud/session-index.js +58 -0
  56. package/dist/lib/cloud/store.d.ts +7 -0
  57. package/dist/lib/cloud/store.js +25 -0
  58. package/dist/lib/config-transfer.js +4 -0
  59. package/dist/lib/daemon.js +18 -2
  60. package/dist/lib/devices/connect.d.ts +15 -1
  61. package/dist/lib/devices/connect.js +15 -1
  62. package/dist/lib/devices/fleet.d.ts +21 -0
  63. package/dist/lib/devices/fleet.js +26 -0
  64. package/dist/lib/devices/health-report.d.ts +38 -0
  65. package/dist/lib/devices/health-report.js +214 -0
  66. package/dist/lib/devices/health.js +4 -1
  67. package/dist/lib/devices/reachability.d.ts +31 -0
  68. package/dist/lib/devices/reachability.js +40 -0
  69. package/dist/lib/devices/registry.d.ts +33 -0
  70. package/dist/lib/devices/registry.js +37 -0
  71. package/dist/lib/devices/resolve-target.d.ts +15 -27
  72. package/dist/lib/devices/resolve-target.js +63 -102
  73. package/dist/lib/devices/sync.d.ts +18 -0
  74. package/dist/lib/devices/sync.js +23 -1
  75. package/dist/lib/devices/tailscale.d.ts +3 -0
  76. package/dist/lib/devices/tailscale.js +1 -0
  77. package/dist/lib/events.d.ts +1 -1
  78. package/dist/lib/exec.d.ts +66 -0
  79. package/dist/lib/exec.js +195 -6
  80. package/dist/lib/feed-post.d.ts +63 -0
  81. package/dist/lib/feed-post.js +204 -0
  82. package/dist/lib/fleet/apply.d.ts +32 -2
  83. package/dist/lib/fleet/apply.js +97 -10
  84. package/dist/lib/fleet/types.d.ts +11 -0
  85. package/dist/lib/fs-walk.d.ts +13 -0
  86. package/dist/lib/fs-walk.js +16 -7
  87. package/dist/lib/hooks.d.ts +1 -0
  88. package/dist/lib/hooks.js +156 -0
  89. package/dist/lib/hosts/dispatch.d.ts +7 -0
  90. package/dist/lib/hosts/dispatch.js +2 -0
  91. package/dist/lib/hosts/passthrough.js +2 -0
  92. package/dist/lib/hosts/reconnect.d.ts +78 -0
  93. package/dist/lib/hosts/reconnect.js +127 -0
  94. package/dist/lib/hosts/registry.d.ts +75 -14
  95. package/dist/lib/hosts/registry.js +205 -30
  96. package/dist/lib/hosts/remote-cmd.js +1 -0
  97. package/dist/lib/hosts/run-target.d.ts +13 -5
  98. package/dist/lib/hosts/run-target.js +26 -10
  99. package/dist/lib/hosts/session-index.d.ts +18 -1
  100. package/dist/lib/hosts/session-index.js +37 -2
  101. package/dist/lib/hosts/session-marker.d.ts +33 -0
  102. package/dist/lib/hosts/session-marker.js +51 -0
  103. package/dist/lib/hosts/tasks.d.ts +8 -4
  104. package/dist/lib/import.d.ts +2 -0
  105. package/dist/lib/import.js +35 -2
  106. package/dist/lib/menubar/install-menubar.d.ts +20 -0
  107. package/dist/lib/menubar/install-menubar.js +45 -1
  108. package/dist/lib/migrate.d.ts +16 -0
  109. package/dist/lib/migrate.js +36 -0
  110. package/dist/lib/models.d.ts +27 -0
  111. package/dist/lib/models.js +54 -1
  112. package/dist/lib/overdue.d.ts +7 -2
  113. package/dist/lib/overdue.js +18 -11
  114. package/dist/lib/picker.js +4 -1
  115. package/dist/lib/platform/process.d.ts +17 -0
  116. package/dist/lib/platform/process.js +70 -0
  117. package/dist/lib/profiles-presets.js +9 -7
  118. package/dist/lib/profiles.d.ts +31 -0
  119. package/dist/lib/profiles.js +70 -0
  120. package/dist/lib/pty-server.d.ts +2 -10
  121. package/dist/lib/pty-server.js +4 -38
  122. package/dist/lib/registry.d.ts +1 -1
  123. package/dist/lib/registry.js +48 -8
  124. package/dist/lib/rotate.d.ts +18 -0
  125. package/dist/lib/rotate.js +28 -0
  126. package/dist/lib/routines.d.ts +26 -0
  127. package/dist/lib/routines.js +19 -2
  128. package/dist/lib/runner.js +104 -7
  129. package/dist/lib/secrets/agent.d.ts +66 -1
  130. package/dist/lib/secrets/agent.js +154 -90
  131. package/dist/lib/secrets/bundles.d.ts +34 -0
  132. package/dist/lib/secrets/bundles.js +179 -19
  133. package/dist/lib/secrets/index.d.ts +15 -1
  134. package/dist/lib/secrets/index.js +21 -3
  135. package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
  136. package/dist/lib/secrets/rc-hygiene.js +154 -0
  137. package/dist/lib/secrets/remote.d.ts +7 -4
  138. package/dist/lib/secrets/remote.js +7 -4
  139. package/dist/lib/secrets/sync-commands.d.ts +21 -0
  140. package/dist/lib/secrets/sync-commands.js +21 -0
  141. package/dist/lib/self-update.d.ts +23 -2
  142. package/dist/lib/self-update.js +86 -5
  143. package/dist/lib/session/active.d.ts +114 -7
  144. package/dist/lib/session/active.js +323 -52
  145. package/dist/lib/session/cloud.js +2 -1
  146. package/dist/lib/session/db.d.ts +48 -0
  147. package/dist/lib/session/db.js +238 -14
  148. package/dist/lib/session/detached.d.ts +30 -0
  149. package/dist/lib/session/detached.js +92 -0
  150. package/dist/lib/session/discover.d.ts +465 -2
  151. package/dist/lib/session/discover.js +1648 -468
  152. package/dist/lib/session/fork.js +2 -1
  153. package/dist/lib/session/hook-sessions.d.ts +43 -0
  154. package/dist/lib/session/hook-sessions.js +135 -0
  155. package/dist/lib/session/linear.d.ts +6 -0
  156. package/dist/lib/session/linear.js +55 -0
  157. package/dist/lib/session/migrate-targets.d.ts +65 -0
  158. package/dist/lib/session/migrate-targets.js +94 -0
  159. package/dist/lib/session/migrations.d.ts +37 -0
  160. package/dist/lib/session/migrations.js +60 -0
  161. package/dist/lib/session/parse.d.ts +18 -0
  162. package/dist/lib/session/parse.js +130 -30
  163. package/dist/lib/session/pid-registry.d.ts +34 -2
  164. package/dist/lib/session/pid-registry.js +49 -2
  165. package/dist/lib/session/render.d.ts +7 -2
  166. package/dist/lib/session/render.js +22 -10
  167. package/dist/lib/session/short-id.d.ts +17 -0
  168. package/dist/lib/session/short-id.js +20 -0
  169. package/dist/lib/session/state.js +6 -2
  170. package/dist/lib/shims.d.ts +1 -1
  171. package/dist/lib/shims.js +20 -6
  172. package/dist/lib/sqlite.d.ts +3 -2
  173. package/dist/lib/sqlite.js +27 -4
  174. package/dist/lib/staleness/detectors/commands.js +1 -1
  175. package/dist/lib/staleness/detectors/workflows.js +13 -2
  176. package/dist/lib/staleness/writers/commands.js +7 -7
  177. package/dist/lib/staleness/writers/workflows.d.ts +4 -2
  178. package/dist/lib/startup/command-registry.d.ts +1 -0
  179. package/dist/lib/startup/command-registry.js +3 -0
  180. package/dist/lib/state.d.ts +1 -1
  181. package/dist/lib/state.js +1 -32
  182. package/dist/lib/teams/agents.d.ts +19 -10
  183. package/dist/lib/teams/agents.js +40 -39
  184. package/dist/lib/types.d.ts +37 -2
  185. package/dist/lib/usage.d.ts +33 -1
  186. package/dist/lib/usage.js +172 -12
  187. package/dist/lib/versions.d.ts +9 -2
  188. package/dist/lib/versions.js +37 -8
  189. package/dist/lib/watchdog/log.d.ts +43 -0
  190. package/dist/lib/watchdog/log.js +69 -0
  191. package/dist/lib/watchdog/routine.d.ts +44 -0
  192. package/dist/lib/watchdog/routine.js +69 -0
  193. package/dist/lib/watchdog/runner.d.ts +51 -7
  194. package/dist/lib/watchdog/runner.js +239 -64
  195. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  196. package/dist/lib/watchdog/watchdog.js +31 -16
  197. package/dist/lib/workflows.d.ts +16 -0
  198. package/dist/lib/workflows.js +110 -1
  199. package/package.json +1 -1
@@ -55,6 +55,23 @@ export declare function saveUpdateCheck(file: string, latestVersion: string): vo
55
55
  export declare function dismissUpdateVersion(file: string, version: string): void;
56
56
  /** Whether the cached state warrants an upgrade prompt for a copy running `currentVersion`. */
57
57
  export declare function shouldPromptUpgrade(cache: UpdateCheckCache | null, currentVersion: string): boolean;
58
+ /**
59
+ * The on-disk package root of the copy that is currently running.
60
+ *
61
+ * For a plain JS install this is just `<__dirname>/..`. Under the compiled
62
+ * standalone binary (shipped since 1.20.53) it is not: Bun sets `__dirname` to
63
+ * its embedded virtual FS, so `<__dirname>/..` yields `/$bunfs` — a path that
64
+ * exists nowhere. That phantom value was reported as a second install by the
65
+ * multi-install check, and rejected by deriveGlobalPrefix as "not an
66
+ * npm-managed install", so every self-upgrade from a compiled copy failed.
67
+ *
68
+ * The physical executable is `process.execPath`, which ships inside the
69
+ * package (`<packageRoot>/dist/bin/agents`). Walk up from it to the directory
70
+ * whose package.json actually names this package rather than assuming a fixed
71
+ * depth, so a change to the dist layout surfaces as a clear throw here instead
72
+ * of a wrong prefix that npm would happily install into.
73
+ */
74
+ export declare function resolveRunningPackageRoot(dirname: string, execPath?: string): string;
58
75
  /**
59
76
  * Derive the npm global prefix that owns the install at `packageRoot`.
60
77
  *
@@ -133,8 +150,12 @@ export interface AgentsCliInstall {
133
150
  *
134
151
  * npm bin entries are symlinks that resolve to `<packageRoot>/dist/index.js`
135
152
  * (the dev install's `~/.local/bin/agents` chains through the dev prefix to
136
- * the same shape). Anything that doesn't resolve to a dist/index.js inside a
137
- * package named @phnx-labs/agents-cli is some other tool and is skipped.
153
+ * the same shape). A shim pointed at the compiled standalone binary resolves
154
+ * to `<packageRoot>/dist/bin/agents` instead; that shape counts too, otherwise
155
+ * the copy that actually runs — the compiled one is typically first on PATH —
156
+ * is invisible to this scan and its root looks like a separate install.
157
+ * Anything that resolves to neither shape inside a package named
158
+ * @phnx-labs/agents-cli is some other tool and is skipped.
138
159
  * POSIX-only: Windows npm bins are .cmd wrappers, not symlinks.
139
160
  */
140
161
  export declare function findAgentsCliInstalls(pathEnv: string): AgentsCliInstall[];
@@ -116,6 +116,66 @@ export function shouldPromptUpgrade(cache, currentVersion) {
116
116
  compareVersions(cache.latestVersion, currentVersion) > 0 &&
117
117
  cache.latestVersion !== cache.dismissed);
118
118
  }
119
+ /**
120
+ * Whether `p` is Bun's embedded virtual filesystem — where a standalone
121
+ * executable exposes its bundled sources. Nothing there exists on disk: it
122
+ * cannot be stat'd, installed into, or compared against a real install path.
123
+ *
124
+ * Matches the root itself (`/$bunfs`) as well as paths under it, because
125
+ * `<__dirname>/..` from the embedded entry produces exactly that bare root.
126
+ * daemon.ts carries a narrower under-the-root-only guard for deciding what may
127
+ * be supervised; the two are deliberately not shared.
128
+ */
129
+ function isBunVirtualPath(p) {
130
+ return /(^|[/\\])\$bunfs([/\\]|$)/.test(p);
131
+ }
132
+ /** Whether `dir` is the root of an installed copy of this package. */
133
+ function isPackageRoot(dir) {
134
+ try {
135
+ const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
136
+ return pkg.name === NPM_PACKAGE_NAME;
137
+ }
138
+ catch {
139
+ return false;
140
+ }
141
+ }
142
+ /**
143
+ * The on-disk package root of the copy that is currently running.
144
+ *
145
+ * For a plain JS install this is just `<__dirname>/..`. Under the compiled
146
+ * standalone binary (shipped since 1.20.53) it is not: Bun sets `__dirname` to
147
+ * its embedded virtual FS, so `<__dirname>/..` yields `/$bunfs` — a path that
148
+ * exists nowhere. That phantom value was reported as a second install by the
149
+ * multi-install check, and rejected by deriveGlobalPrefix as "not an
150
+ * npm-managed install", so every self-upgrade from a compiled copy failed.
151
+ *
152
+ * The physical executable is `process.execPath`, which ships inside the
153
+ * package (`<packageRoot>/dist/bin/agents`). Walk up from it to the directory
154
+ * whose package.json actually names this package rather than assuming a fixed
155
+ * depth, so a change to the dist layout surfaces as a clear throw here instead
156
+ * of a wrong prefix that npm would happily install into.
157
+ */
158
+ export function resolveRunningPackageRoot(dirname, execPath = process.execPath) {
159
+ const fromDirname = path.resolve(dirname, '..');
160
+ if (!isBunVirtualPath(fromDirname))
161
+ return fromDirname;
162
+ if (!execPath || isBunVirtualPath(execPath)) {
163
+ throw new Error(`Cannot locate the running agents-cli install: __dirname is the Bun virtual path ${fromDirname} ` +
164
+ `and process.execPath (${execPath || '(empty)'}) is not a real file. ` +
165
+ `Reinstall with: npm install -g ${NPM_PACKAGE_NAME}`);
166
+ }
167
+ let dir = path.dirname(path.resolve(execPath));
168
+ for (;;) {
169
+ if (isPackageRoot(dir))
170
+ return dir;
171
+ const parent = path.dirname(dir);
172
+ if (parent === dir) {
173
+ throw new Error(`Cannot locate the running agents-cli install: no ${NPM_PACKAGE_NAME} package.json above ` +
174
+ `${execPath}. Reinstall with: npm install -g ${NPM_PACKAGE_NAME}`);
175
+ }
176
+ dir = parent;
177
+ }
178
+ }
119
179
  /**
120
180
  * Derive the npm global prefix that owns the install at `packageRoot`.
121
181
  *
@@ -249,6 +309,24 @@ export function refreshAliasShims(packageRoot) {
249
309
  stdio: 'ignore',
250
310
  });
251
311
  }
312
+ /**
313
+ * The package root a resolved `agents` entrypoint belongs to, or null when the
314
+ * path is not an agents-cli entry at all. Two shipped shapes:
315
+ * <packageRoot>/dist/index.js — the JS entry npm links as `agents`
316
+ * <packageRoot>/dist/bin/agents — the compiled standalone executable
317
+ */
318
+ function packageRootForEntry(real) {
319
+ const distDir = path.dirname(real);
320
+ if (path.basename(real) === 'index.js' && path.basename(distDir) === 'dist') {
321
+ return path.dirname(distDir);
322
+ }
323
+ if (path.basename(real) === 'agents' &&
324
+ path.basename(distDir) === 'bin' &&
325
+ path.basename(path.dirname(distDir)) === 'dist') {
326
+ return path.dirname(path.dirname(distDir));
327
+ }
328
+ return null;
329
+ }
252
330
  /**
253
331
  * Scan PATH for `agents` entrypoints and resolve each to the agents-cli
254
332
  * package root it executes. More than one distinct root means upgrades,
@@ -257,8 +335,12 @@ export function refreshAliasShims(packageRoot) {
257
335
  *
258
336
  * npm bin entries are symlinks that resolve to `<packageRoot>/dist/index.js`
259
337
  * (the dev install's `~/.local/bin/agents` chains through the dev prefix to
260
- * the same shape). Anything that doesn't resolve to a dist/index.js inside a
261
- * package named @phnx-labs/agents-cli is some other tool and is skipped.
338
+ * the same shape). A shim pointed at the compiled standalone binary resolves
339
+ * to `<packageRoot>/dist/bin/agents` instead; that shape counts too, otherwise
340
+ * the copy that actually runs — the compiled one is typically first on PATH —
341
+ * is invisible to this scan and its root looks like a separate install.
342
+ * Anything that resolves to neither shape inside a package named
343
+ * @phnx-labs/agents-cli is some other tool and is skipped.
262
344
  * POSIX-only: Windows npm bins are .cmd wrappers, not symlinks.
263
345
  */
264
346
  export function findAgentsCliInstalls(pathEnv) {
@@ -275,10 +357,9 @@ export function findAgentsCliInstalls(pathEnv) {
275
357
  catch {
276
358
  continue; // missing or dangling symlink
277
359
  }
278
- if (path.basename(real) !== 'index.js' || path.basename(path.dirname(real)) !== 'dist') {
360
+ const packageRoot = packageRootForEntry(real);
361
+ if (!packageRoot)
279
362
  continue;
280
- }
281
- const packageRoot = path.dirname(path.dirname(real));
282
363
  if (seenRoots.has(packageRoot))
283
364
  continue;
284
365
  let pkg;
@@ -1,8 +1,10 @@
1
1
  import type { CloudTaskStatus } from '../cloud/types.js';
2
2
  import { type PidSessionEntry } from './pid-registry.js';
3
+ import { type HookSessionIndex } from './hook-sessions.js';
3
4
  import { type SessionActivity, type AwaitingReason, type StructuredQuestion, type TodoProgress, type DetectedPr, type DetectedWorktree, type DetectedTicket } from './state.js';
4
5
  import type { SessionAttachment } from './types.js';
5
6
  import { type SessionProvenance } from './provenance.js';
7
+ import { type Presence } from './detached.js';
6
8
  /**
7
9
  * Per-PID `lsof` probes run bounded and staggered rather than as one parallel
8
10
  * fan-out: a simultaneous system-wide `lsof` burst reads to behavioral EDR
@@ -11,7 +13,15 @@ import { type SessionProvenance } from './provenance.js';
11
13
  */
12
14
  export declare const LSOF_CONCURRENCY = 4;
13
15
  export type ActiveContext = 'terminal' | 'teams' | 'cloud' | 'headless';
14
- export type ActiveStatus = 'running' | 'idle' | 'queued' | 'input_required';
16
+ /**
17
+ * `unknown` = the process is alive but we cannot introspect what it is doing —
18
+ * a live harness whose transcript format we do not parse (everything but
19
+ * claude/codex), or a resolvable transcript whose `stat` momentarily failed. It
20
+ * is NOT a synonym for idle: idle is a positive "not mid-turn, not waiting on
21
+ * you" conclusion drawn from a readable transcript; unknown is the honest "we
22
+ * can't tell", which we refuse to fake as idle.
23
+ */
24
+ export type ActiveStatus = 'running' | 'idle' | 'queued' | 'input_required' | 'unknown';
15
25
  export interface ActiveSession {
16
26
  context: ActiveContext;
17
27
  kind: string;
@@ -71,7 +81,24 @@ export interface ActiveSession {
71
81
  attachments?: SessionAttachment[];
72
82
  sessionFile?: string;
73
83
  startedAtMs?: number;
84
+ /**
85
+ * Last-activity epoch — the transcript's last write (mtime). Distinct from
86
+ * {@link startedAtMs} (session START): a session begun 3h ago but last touched
87
+ * 20s ago has an old start and a fresh last-activity. The Floor renders "Xs ago"
88
+ * off this so an idle-but-old session doesn't read as freshly active.
89
+ */
90
+ lastActivityMs?: number;
74
91
  status: ActiveStatus;
92
+ /**
93
+ * Foreground/background presence for the detach/attach model:
94
+ * `attached` — live interactive TUI you're watching;
95
+ * `background` — detached: running headless, unattended (via `agents sessions detach`);
96
+ * `parked` — the headless continuation has exited; the transcript is durable.
97
+ * Absent for ad-hoc headless runs and cloud/team rows, which aren't on the
98
+ * foreground/background axis. Folded on at the end of {@link getActiveSessions}
99
+ * from the detach store — never asserted by a source.
100
+ */
101
+ presence?: Presence;
75
102
  /** How many live PIDs resolve to this same session (subagents/forks). 1 unless collapsed. */
76
103
  pidCount?: number;
77
104
  /**
@@ -150,6 +177,17 @@ export declare const ACTIVE_SESSION_STALE_MS: number;
150
177
  * `.exe` suffix (`claude.exe`), so basename + suffix-strip before the lookup.
151
178
  */
152
179
  export declare function agentKindFromComm(commRaw: string): string | undefined;
180
+ /**
181
+ * True when `pid` names a live process AND — when a session's recorded
182
+ * `startedAtMs` is supplied — that process is plausibly the SAME one, not a later
183
+ * process that recycled the pid. The OS reuses pids, so a bare
184
+ * `process.kill(pid, 0)` existence check reports a dead session as alive (a
185
+ * "zombie") once its pid is handed to an unrelated process. A genuine session
186
+ * process starts at or before its own recorded start, so a process that began
187
+ * meaningfully AFTER `startedAtMs` is a reused pid and the session is dead. When
188
+ * the start time can't be read, we keep the existence answer.
189
+ */
190
+ export declare function isPidAlive(pid: number, startedAtMs?: number): boolean;
153
191
  /**
154
192
  * Pick a Claude transcript file within a project dir.
155
193
  *
@@ -163,6 +201,34 @@ export declare function agentKindFromComm(commRaw: string): string | undefined;
163
201
  * heuristic for a directly-launched agent with no registry entry).
164
202
  */
165
203
  export declare function pickSessionFile(projectDir: string, sessionId?: string): string | undefined;
204
+ /**
205
+ * One `stat` → the transcript's creation (≈ session start) and last-write (≈ last
206
+ * activity) epochs. Both `undefined` when the file can't be stat'd (vanished /
207
+ * unresolved). `birthtimeMs` can be 0 on filesystems without creation time — coerce
208
+ * that to `undefined` so callers fall through to a real signal instead of epoch 0.
209
+ */
210
+ export declare function sessionFileTimes(sessionFile: string | undefined): {
211
+ birthtimeMs?: number;
212
+ mtimeMs?: number;
213
+ };
214
+ /**
215
+ * The ONE place a fallback status is decided when no rich transcript state is
216
+ * available — a non-Claude/Codex kind we cannot parse, or a Claude/Codex tail
217
+ * that was empty or unreadable. Honest by construction: it never asserts a status
218
+ * it cannot justify from a measured signal.
219
+ *
220
+ * - Resolvable transcript, readable mtime → the MEASURED freshness signal:
221
+ * written within ACTIVE_MTIME_WINDOW_MS ⇒ `running`, else `idle`.
222
+ * - Resolvable transcript whose `stat` throws (file vanished / permission) → we
223
+ * genuinely cannot tell ⇒ `unknown`. (This branch previously returned
224
+ * `running`, which contradicted the `idle` default one branch up.)
225
+ * - No resolvable transcript but the process is alive → alive-but-opaque ⇒
226
+ * `unknown`. This is the truthful answer for a live gemini / droid / cursor /
227
+ * opencode whose format we don't parse — NOT a fabricated `idle` (which the
228
+ * UI reads as "done and waiting"), and it never lies as `running` either.
229
+ * - No transcript and the process is not known alive → nothing to report ⇒ `idle`.
230
+ */
231
+ export declare function resolveFallbackStatus(sessionFile: string | undefined, pidAlive: boolean): ActiveStatus;
166
232
  /**
167
233
  * Locate the live transcript for an agent process. Claude files are keyed by
168
234
  * cwd (+ optional session uuid); Codex files are date-partitioned, so we resolve
@@ -236,14 +302,47 @@ export declare function foldSubordinateAgents(candidates: AgentCandidate[], ppid
236
302
  * sort means `headless` (daemon, launchd-spawned, orphan).
237
303
  */
238
304
  export declare function listUnattributedActive(attributed: Set<number>): Promise<ActiveSession[]>;
305
+ /** One tmux pane's resolved agent identity for the authoritative source. */
306
+ export interface PaneIdentity {
307
+ agent: string;
308
+ /** Exact session id when resolvable (launch registry, or the hook join). */
309
+ sessionId?: string;
310
+ /** The agent's OS pid from the launch registry (may differ from `pane_pid`). */
311
+ pid?: number;
312
+ }
313
+ /**
314
+ * Attribute a single tmux pane to the agent actually running in it.
315
+ *
316
+ * The launch registry — written per bare-spawn AND per wrap, each stamped with the
317
+ * `tmuxPane` it targeted (see src/lib/exec.ts) — is the EXACT, per-pane source of
318
+ * truth. So an agent spawned into an EXISTING pane (a split, where `$TMUX` is
319
+ * already set so no new session meta is stamped) is attributed to its OWN launch,
320
+ * not the session's original agent — closing the gap where such an agent was
321
+ * dropped by this source and left to the weaker ps-scan fallback. Session-meta
322
+ * labels remain the fallback for the wrapped origin pane of a session whose
323
+ * registry entry is absent (a failed best-effort write, or a legacy session that
324
+ * predates the registry's `tmuxPane` field) — and ONLY for that origin pane
325
+ * (`meta.pane`), so a split shell pane of a labeled session isn't mis-attributed
326
+ * the wrapped agent. When `meta.pane` is unknown (attach-existing sessions), any
327
+ * labeled pane is accepted and the caller's per-session dedupe keeps one.
328
+ * `source: 'teams'` panes are skipped — teammates are surfaced by listTeamsActive.
329
+ * Pure so it is unit-tested without tmux.
330
+ */
331
+ export declare function resolvePaneIdentity(pane: string, meta: {
332
+ labels?: Record<string, string>;
333
+ source?: string;
334
+ pane?: string;
335
+ } | null, liveEntry: PidSessionEntry | undefined, getHookIndex: () => HookSessionIndex): PaneIdentity | undefined;
239
336
  /**
240
337
  * Agents hosted in the shared-socket tmux server — the authoritative source for
241
- * tmux-wrapped interactive spawns (see src/lib/exec.ts `runInTmux`). Enumerates
242
- * every pane on the shared socket and keeps those whose session meta was stamped
243
- * with `labels.agent` + `labels.sessionId` by the spawn-wrap. Because tmux (not a
244
- * per-window `live-terminals.json`) is the source of truth, a tmux-hosted agent is
245
- * ALWAYS captured with its exact `%pane` even when the extension registry is stale
246
- * or absent. `source: 'teams'` is skipped teammates are surfaced by listTeamsActive.
338
+ * tmux-hosted interactive spawns (see src/lib/exec.ts `runInTmux`). Enumerates
339
+ * every pane on the shared socket and attributes each to the agent running in it
340
+ * via {@link resolvePaneIdentity}: the per-pane launch registry (exact) first, the
341
+ * session-meta labels as fallback. Because tmux (not a per-window
342
+ * `live-terminals.json`) is the source of truth, a tmux-hosted agent is captured
343
+ * with its exact `%pane` even when the extension registry is stale — INCLUDING an
344
+ * agent bare-spawned into a split of an existing session, which older logic dropped
345
+ * (it kept only the first pane per session meta). `source: 'teams'` is skipped.
247
346
  */
248
347
  export declare function listTmuxAgentSessions(): Promise<ActiveSession[]>;
249
348
  /**
@@ -254,4 +353,12 @@ export declare function listTmuxAgentSessions(): Promise<ActiveSession[]>;
254
353
  * terminal/headless row for the same session id.
255
354
  */
256
355
  export declare function getActiveSessions(opts?: ActiveQueryOptions): Promise<ActiveSession[]>;
356
+ /**
357
+ * Collapse rows that resolve to the *same* session — a session with many
358
+ * subagent/fork PIDs (all matched to one transcript file) would otherwise print
359
+ * dozens of identical rows. Keyed by session id, falling back to the transcript
360
+ * file, then the cloud/run handle, then {@link anonymousWorkerKey}. The first row
361
+ * wins and carries a `pidCount`.
362
+ */
363
+ export declare function dedupeBySession(sessions: ActiveSession[]): ActiveSession[];
257
364
  export {};