@phnx-labs/agents-cli 1.20.74 → 1.20.77

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 (231) hide show
  1. package/CHANGELOG.md +1055 -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 +83 -13
  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 +286 -15
  26. package/dist/commands/secrets.js +102 -37
  27. package/dist/commands/sessions-browser.js +12 -2
  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.d.ts +14 -1
  32. package/dist/commands/sessions-picker.js +184 -16
  33. package/dist/commands/sessions.d.ts +56 -14
  34. package/dist/commands/sessions.js +322 -62
  35. package/dist/commands/setup-computer.js +2 -2
  36. package/dist/commands/ssh.d.ts +13 -0
  37. package/dist/commands/ssh.js +110 -37
  38. package/dist/commands/status.js +10 -2
  39. package/dist/commands/versions.js +15 -6
  40. package/dist/commands/view.d.ts +5 -0
  41. package/dist/commands/view.js +24 -4
  42. package/dist/commands/watchdog.d.ts +7 -2
  43. package/dist/commands/watchdog.js +73 -57
  44. package/dist/index.js +59 -7
  45. package/dist/lib/activity.d.ts +17 -6
  46. package/dist/lib/activity.js +466 -40
  47. package/dist/lib/actor.d.ts +50 -0
  48. package/dist/lib/actor.js +166 -0
  49. package/dist/lib/agent-spec/provider.js +2 -1
  50. package/dist/lib/agent-spec/resolve.js +19 -5
  51. package/dist/lib/agent-spec/types.d.ts +9 -1
  52. package/dist/lib/agents.d.ts +21 -0
  53. package/dist/lib/agents.js +70 -6
  54. package/dist/lib/cloud/codex.d.ts +2 -0
  55. package/dist/lib/cloud/codex.js +14 -3
  56. package/dist/lib/cloud/session-index.d.ts +32 -0
  57. package/dist/lib/cloud/session-index.js +58 -0
  58. package/dist/lib/cloud/store.d.ts +7 -0
  59. package/dist/lib/cloud/store.js +25 -0
  60. package/dist/lib/config-transfer.js +4 -0
  61. package/dist/lib/daemon.d.ts +10 -1
  62. package/dist/lib/daemon.js +114 -11
  63. package/dist/lib/devices/connect.d.ts +15 -1
  64. package/dist/lib/devices/connect.js +15 -1
  65. package/dist/lib/devices/fleet.d.ts +37 -1
  66. package/dist/lib/devices/fleet.js +36 -2
  67. package/dist/lib/devices/health-report.d.ts +38 -0
  68. package/dist/lib/devices/health-report.js +214 -0
  69. package/dist/lib/devices/health.js +4 -1
  70. package/dist/lib/devices/reachability.d.ts +31 -0
  71. package/dist/lib/devices/reachability.js +40 -0
  72. package/dist/lib/devices/registry.d.ts +33 -0
  73. package/dist/lib/devices/registry.js +37 -0
  74. package/dist/lib/devices/resolve-target.d.ts +15 -27
  75. package/dist/lib/devices/resolve-target.js +63 -102
  76. package/dist/lib/devices/sync.d.ts +18 -0
  77. package/dist/lib/devices/sync.js +23 -1
  78. package/dist/lib/devices/tailscale.d.ts +3 -0
  79. package/dist/lib/devices/tailscale.js +1 -0
  80. package/dist/lib/events.d.ts +1 -1
  81. package/dist/lib/exec.d.ts +82 -0
  82. package/dist/lib/exec.js +218 -6
  83. package/dist/lib/feed-post.d.ts +63 -0
  84. package/dist/lib/feed-post.js +204 -0
  85. package/dist/lib/feed.d.ts +7 -1
  86. package/dist/lib/feed.js +96 -6
  87. package/dist/lib/fleet/apply.d.ts +32 -2
  88. package/dist/lib/fleet/apply.js +97 -10
  89. package/dist/lib/fleet/types.d.ts +11 -0
  90. package/dist/lib/fs-walk.d.ts +13 -0
  91. package/dist/lib/fs-walk.js +16 -7
  92. package/dist/lib/heal.d.ts +7 -4
  93. package/dist/lib/heal.js +10 -22
  94. package/dist/lib/hooks.d.ts +1 -0
  95. package/dist/lib/hooks.js +156 -0
  96. package/dist/lib/hosts/dispatch.d.ts +16 -0
  97. package/dist/lib/hosts/dispatch.js +26 -4
  98. package/dist/lib/hosts/passthrough.js +9 -2
  99. package/dist/lib/hosts/reconnect.d.ts +78 -0
  100. package/dist/lib/hosts/reconnect.js +127 -0
  101. package/dist/lib/hosts/registry.d.ts +75 -14
  102. package/dist/lib/hosts/registry.js +205 -30
  103. package/dist/lib/hosts/remote-cmd.d.ts +11 -0
  104. package/dist/lib/hosts/remote-cmd.js +32 -8
  105. package/dist/lib/hosts/remote-session-id.d.ts +45 -0
  106. package/dist/lib/hosts/remote-session-id.js +84 -0
  107. package/dist/lib/hosts/run-target.d.ts +17 -5
  108. package/dist/lib/hosts/run-target.js +30 -10
  109. package/dist/lib/hosts/session-index.d.ts +18 -1
  110. package/dist/lib/hosts/session-index.js +40 -5
  111. package/dist/lib/hosts/session-marker.d.ts +33 -0
  112. package/dist/lib/hosts/session-marker.js +51 -0
  113. package/dist/lib/hosts/tasks.d.ts +8 -4
  114. package/dist/lib/import.d.ts +2 -0
  115. package/dist/lib/import.js +35 -2
  116. package/dist/lib/menubar/install-menubar.d.ts +29 -0
  117. package/dist/lib/menubar/install-menubar.js +59 -1
  118. package/dist/lib/menubar/notify-desktop.d.ts +44 -0
  119. package/dist/lib/menubar/notify-desktop.js +78 -0
  120. package/dist/lib/migrate.d.ts +16 -0
  121. package/dist/lib/migrate.js +36 -0
  122. package/dist/lib/models.d.ts +27 -0
  123. package/dist/lib/models.js +54 -1
  124. package/dist/lib/overdue.d.ts +6 -2
  125. package/dist/lib/overdue.js +10 -36
  126. package/dist/lib/picker.js +4 -1
  127. package/dist/lib/platform/process.d.ts +17 -0
  128. package/dist/lib/platform/process.js +70 -0
  129. package/dist/lib/profiles-presets.js +9 -7
  130. package/dist/lib/profiles.d.ts +31 -0
  131. package/dist/lib/profiles.js +70 -0
  132. package/dist/lib/pty-server.d.ts +2 -10
  133. package/dist/lib/pty-server.js +4 -38
  134. package/dist/lib/registry.d.ts +1 -1
  135. package/dist/lib/registry.js +48 -8
  136. package/dist/lib/rotate.d.ts +18 -0
  137. package/dist/lib/rotate.js +28 -0
  138. package/dist/lib/routine-notify.d.ts +76 -0
  139. package/dist/lib/routine-notify.js +190 -0
  140. package/dist/lib/routines-placement.d.ts +38 -0
  141. package/dist/lib/routines-placement.js +79 -0
  142. package/dist/lib/routines-project.d.ts +97 -0
  143. package/dist/lib/routines-project.js +349 -0
  144. package/dist/lib/routines.d.ts +94 -0
  145. package/dist/lib/routines.js +93 -9
  146. package/dist/lib/runner.d.ts +12 -2
  147. package/dist/lib/runner.js +242 -20
  148. package/dist/lib/sandbox.js +10 -0
  149. package/dist/lib/secrets/account-token.d.ts +20 -0
  150. package/dist/lib/secrets/account-token.js +64 -0
  151. package/dist/lib/secrets/agent.d.ts +74 -4
  152. package/dist/lib/secrets/agent.js +181 -107
  153. package/dist/lib/secrets/bundles.d.ts +54 -6
  154. package/dist/lib/secrets/bundles.js +230 -34
  155. package/dist/lib/secrets/index.d.ts +26 -3
  156. package/dist/lib/secrets/index.js +42 -12
  157. package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
  158. package/dist/lib/secrets/rc-hygiene.js +154 -0
  159. package/dist/lib/secrets/remote.d.ts +7 -4
  160. package/dist/lib/secrets/remote.js +7 -4
  161. package/dist/lib/secrets/session-store.d.ts +6 -2
  162. package/dist/lib/secrets/session-store.js +65 -15
  163. package/dist/lib/secrets/sync-commands.d.ts +21 -0
  164. package/dist/lib/secrets/sync-commands.js +21 -0
  165. package/dist/lib/secrets/unlock-hints.d.ts +27 -0
  166. package/dist/lib/secrets/unlock-hints.js +36 -0
  167. package/dist/lib/self-update.d.ts +23 -2
  168. package/dist/lib/self-update.js +86 -5
  169. package/dist/lib/session/active.d.ts +124 -7
  170. package/dist/lib/session/active.js +390 -56
  171. package/dist/lib/session/cloud.js +2 -1
  172. package/dist/lib/session/db.d.ts +54 -0
  173. package/dist/lib/session/db.js +320 -19
  174. package/dist/lib/session/detached.d.ts +30 -0
  175. package/dist/lib/session/detached.js +92 -0
  176. package/dist/lib/session/discover.d.ts +478 -3
  177. package/dist/lib/session/discover.js +1740 -472
  178. package/dist/lib/session/fork.js +2 -1
  179. package/dist/lib/session/hook-sessions.d.ts +43 -0
  180. package/dist/lib/session/hook-sessions.js +135 -0
  181. package/dist/lib/session/linear.d.ts +6 -0
  182. package/dist/lib/session/linear.js +55 -0
  183. package/dist/lib/session/migrate-targets.d.ts +65 -0
  184. package/dist/lib/session/migrate-targets.js +94 -0
  185. package/dist/lib/session/migrations.d.ts +37 -0
  186. package/dist/lib/session/migrations.js +60 -0
  187. package/dist/lib/session/parse.d.ts +18 -0
  188. package/dist/lib/session/parse.js +141 -32
  189. package/dist/lib/session/pid-registry.d.ts +34 -2
  190. package/dist/lib/session/pid-registry.js +49 -2
  191. package/dist/lib/session/prompt.d.ts +7 -0
  192. package/dist/lib/session/prompt.js +37 -0
  193. package/dist/lib/session/provenance.d.ts +7 -0
  194. package/dist/lib/session/render.d.ts +7 -2
  195. package/dist/lib/session/render.js +40 -26
  196. package/dist/lib/session/short-id.d.ts +17 -0
  197. package/dist/lib/session/short-id.js +20 -0
  198. package/dist/lib/session/state.d.ts +4 -0
  199. package/dist/lib/session/state.js +99 -13
  200. package/dist/lib/session/types.d.ts +25 -1
  201. package/dist/lib/session/types.js +8 -0
  202. package/dist/lib/shims.d.ts +1 -1
  203. package/dist/lib/shims.js +20 -6
  204. package/dist/lib/sqlite.d.ts +3 -2
  205. package/dist/lib/sqlite.js +27 -4
  206. package/dist/lib/staleness/detectors/commands.js +1 -1
  207. package/dist/lib/staleness/detectors/workflows.js +13 -2
  208. package/dist/lib/staleness/writers/commands.js +7 -7
  209. package/dist/lib/staleness/writers/workflows.d.ts +4 -2
  210. package/dist/lib/startup/command-registry.d.ts +1 -0
  211. package/dist/lib/startup/command-registry.js +3 -0
  212. package/dist/lib/state.d.ts +8 -4
  213. package/dist/lib/state.js +21 -37
  214. package/dist/lib/teams/agents.d.ts +19 -10
  215. package/dist/lib/teams/agents.js +40 -39
  216. package/dist/lib/types.d.ts +47 -2
  217. package/dist/lib/usage.d.ts +33 -1
  218. package/dist/lib/usage.js +172 -12
  219. package/dist/lib/versions.d.ts +9 -2
  220. package/dist/lib/versions.js +37 -8
  221. package/dist/lib/watchdog/log.d.ts +43 -0
  222. package/dist/lib/watchdog/log.js +69 -0
  223. package/dist/lib/watchdog/routine.d.ts +44 -0
  224. package/dist/lib/watchdog/routine.js +69 -0
  225. package/dist/lib/watchdog/runner.d.ts +51 -7
  226. package/dist/lib/watchdog/runner.js +239 -64
  227. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  228. package/dist/lib/watchdog/watchdog.js +31 -16
  229. package/dist/lib/workflows.d.ts +16 -0
  230. package/dist/lib/workflows.js +110 -1
  231. package/package.json +1 -1
@@ -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,11 @@
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 DeviceRegistry } from '../devices/registry.js';
8
+ import { type Presence } from './detached.js';
6
9
  /**
7
10
  * Per-PID `lsof` probes run bounded and staggered rather than as one parallel
8
11
  * fan-out: a simultaneous system-wide `lsof` burst reads to behavioral EDR
@@ -11,7 +14,15 @@ import { type SessionProvenance } from './provenance.js';
11
14
  */
12
15
  export declare const LSOF_CONCURRENCY = 4;
13
16
  export type ActiveContext = 'terminal' | 'teams' | 'cloud' | 'headless';
14
- export type ActiveStatus = 'running' | 'idle' | 'queued' | 'input_required';
17
+ /**
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.
24
+ */
25
+ export type ActiveStatus = 'running' | 'idle' | 'queued' | 'input_required' | 'unknown';
15
26
  export interface ActiveSession {
16
27
  context: ActiveContext;
17
28
  kind: string;
@@ -71,7 +82,24 @@ export interface ActiveSession {
71
82
  attachments?: SessionAttachment[];
72
83
  sessionFile?: string;
73
84
  startedAtMs?: number;
85
+ /**
86
+ * Last-activity epoch — the transcript's last write (mtime). Distinct from
87
+ * {@link startedAtMs} (session START): a session begun 3h ago but last touched
88
+ * 20s ago has an old start and a fresh last-activity. The Floor renders "Xs ago"
89
+ * off this so an idle-but-old session doesn't read as freshly active.
90
+ */
91
+ lastActivityMs?: number;
74
92
  status: ActiveStatus;
93
+ /**
94
+ * Foreground/background presence for the detach/attach model:
95
+ * `attached` — live interactive TUI you're watching;
96
+ * `background` — detached: running headless, unattended (via `agents sessions detach`);
97
+ * `parked` — the headless continuation has exited; the transcript is durable.
98
+ * Absent for ad-hoc headless runs and cloud/team rows, which aren't on the
99
+ * foreground/background axis. Folded on at the end of {@link getActiveSessions}
100
+ * from the detach store — never asserted by a source.
101
+ */
102
+ presence?: Presence;
75
103
  /** How many live PIDs resolve to this same session (subagents/forks). 1 unless collapsed. */
76
104
  pidCount?: number;
77
105
  /**
@@ -150,6 +178,17 @@ export declare const ACTIVE_SESSION_STALE_MS: number;
150
178
  * `.exe` suffix (`claude.exe`), so basename + suffix-strip before the lookup.
151
179
  */
152
180
  export declare function agentKindFromComm(commRaw: string): string | undefined;
181
+ /**
182
+ * True when `pid` names a live process AND — when a session's recorded
183
+ * `startedAtMs` is supplied — that process is plausibly the SAME one, not a later
184
+ * process that recycled the pid. The OS reuses pids, so a bare
185
+ * `process.kill(pid, 0)` existence check reports a dead session as alive (a
186
+ * "zombie") once its pid is handed to an unrelated process. A genuine session
187
+ * process starts at or before its own recorded start, so a process that began
188
+ * meaningfully AFTER `startedAtMs` is a reused pid and the session is dead. When
189
+ * the start time can't be read, we keep the existence answer.
190
+ */
191
+ export declare function isPidAlive(pid: number, startedAtMs?: number): boolean;
153
192
  /**
154
193
  * Pick a Claude transcript file within a project dir.
155
194
  *
@@ -163,6 +202,34 @@ export declare function agentKindFromComm(commRaw: string): string | undefined;
163
202
  * heuristic for a directly-launched agent with no registry entry).
164
203
  */
165
204
  export declare function pickSessionFile(projectDir: string, sessionId?: string): string | undefined;
205
+ /**
206
+ * One `stat` → the transcript's creation (≈ session start) and last-write (≈ last
207
+ * activity) epochs. Both `undefined` when the file can't be stat'd (vanished /
208
+ * unresolved). `birthtimeMs` can be 0 on filesystems without creation time — coerce
209
+ * that to `undefined` so callers fall through to a real signal instead of epoch 0.
210
+ */
211
+ export declare function sessionFileTimes(sessionFile: string | undefined): {
212
+ birthtimeMs?: number;
213
+ mtimeMs?: number;
214
+ };
215
+ /**
216
+ * 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.
220
+ *
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`.
231
+ */
232
+ export declare function resolveFallbackStatus(sessionFile: string | undefined, pidAlive: boolean): ActiveStatus;
166
233
  /**
167
234
  * Locate the live transcript for an agent process. Claude files are keyed by
168
235
  * cwd (+ optional session uuid); Codex files are date-partitioned, so we resolve
@@ -236,14 +303,47 @@ export declare function foldSubordinateAgents(candidates: AgentCandidate[], ppid
236
303
  * sort means `headless` (daemon, launchd-spawned, orphan).
237
304
  */
238
305
  export declare function listUnattributedActive(attributed: Set<number>): Promise<ActiveSession[]>;
306
+ /** One tmux pane's resolved agent identity for the authoritative source. */
307
+ export interface PaneIdentity {
308
+ agent: string;
309
+ /** Exact session id when resolvable (launch registry, or the hook join). */
310
+ sessionId?: string;
311
+ /** The agent's OS pid from the launch registry (may differ from `pane_pid`). */
312
+ pid?: number;
313
+ }
314
+ /**
315
+ * Attribute a single tmux pane to the agent actually running in it.
316
+ *
317
+ * The launch registry — written per bare-spawn AND per wrap, each stamped with the
318
+ * `tmuxPane` it targeted (see src/lib/exec.ts) — is the EXACT, per-pane source of
319
+ * truth. So an agent spawned into an EXISTING pane (a split, where `$TMUX` is
320
+ * already set so no new session meta is stamped) is attributed to its OWN launch,
321
+ * not the session's original agent — closing the gap where such an agent was
322
+ * dropped by this source and left to the weaker ps-scan fallback. Session-meta
323
+ * labels remain the fallback for the wrapped origin pane of a session whose
324
+ * registry entry is absent (a failed best-effort write, or a legacy session that
325
+ * predates the registry's `tmuxPane` field) — and ONLY for that origin pane
326
+ * (`meta.pane`), so a split shell pane of a labeled session isn't mis-attributed
327
+ * the wrapped agent. When `meta.pane` is unknown (attach-existing sessions), any
328
+ * labeled pane is accepted and the caller's per-session dedupe keeps one.
329
+ * `source: 'teams'` panes are skipped — teammates are surfaced by listTeamsActive.
330
+ * Pure so it is unit-tested without tmux.
331
+ */
332
+ export declare function resolvePaneIdentity(pane: string, meta: {
333
+ labels?: Record<string, string>;
334
+ source?: string;
335
+ pane?: string;
336
+ } | null, liveEntry: PidSessionEntry | undefined, getHookIndex: () => HookSessionIndex): PaneIdentity | undefined;
239
337
  /**
240
338
  * 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.
339
+ * tmux-hosted interactive spawns (see src/lib/exec.ts `runInTmux`). Enumerates
340
+ * every pane on the shared socket and attributes each to the agent running in it
341
+ * via {@link resolvePaneIdentity}: the per-pane launch registry (exact) first, the
342
+ * session-meta labels as fallback. Because tmux (not a per-window
343
+ * `live-terminals.json`) is the source of truth, a tmux-hosted agent is captured
344
+ * with its exact `%pane` even when the extension registry is stale — INCLUDING an
345
+ * agent bare-spawned into a split of an existing session, which older logic dropped
346
+ * (it kept only the first pane per session meta). `source: 'teams'` is skipped.
247
347
  */
248
348
  export declare function listTmuxAgentSessions(): Promise<ActiveSession[]>;
249
349
  /**
@@ -254,4 +354,21 @@ export declare function listTmuxAgentSessions(): Promise<ActiveSession[]>;
254
354
  * terminal/headless row for the same session id.
255
355
  */
256
356
  export declare function getActiveSessions(opts?: ActiveQueryOptions): Promise<ActiveSession[]>;
357
+ /**
358
+ * Match an SSH client IP to a registered device (pure — testable with a plain
359
+ * registry object). Returns the device name + ssh login user when the IP is a
360
+ * known device address.
361
+ */
362
+ export declare function matchOriginDevice(clientIp: string, reg: DeviceRegistry): {
363
+ device: string;
364
+ user?: string;
365
+ } | undefined;
366
+ /**
367
+ * Collapse rows that resolve to the *same* session — a session with many
368
+ * subagent/fork PIDs (all matched to one transcript file) would otherwise print
369
+ * dozens of identical rows. Keyed by session id, falling back to the transcript
370
+ * file, then the cloud/run handle, then {@link anonymousWorkerKey}. The first row
371
+ * wins and carries a `pidCount`.
372
+ */
373
+ export declare function dedupeBySession(sessions: ActiveSession[]): ActiveSession[];
257
374
  export {};