@phnx-labs/agents-cli 1.22.46 → 1.22.49

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 (348) hide show
  1. package/CHANGELOG.md +663 -0
  2. package/LICENSE +102 -182
  3. package/README.md +96 -49
  4. package/dist/bootstrap.js +4 -2
  5. package/dist/cli/command-registry.d.ts +2 -1
  6. package/dist/cli/command-registry.js +6 -2
  7. package/dist/commands/accounts.d.ts +58 -0
  8. package/dist/commands/accounts.js +243 -25
  9. package/dist/commands/artifacts.js +7 -6
  10. package/dist/commands/attach.js +1 -1
  11. package/dist/commands/auth.js +7 -5
  12. package/dist/commands/browser-sessions-picker.js +9 -7
  13. package/dist/commands/browser.js +711 -62
  14. package/dist/commands/config.d.ts +0 -1
  15. package/dist/commands/config.js +26 -24
  16. package/dist/commands/cost.d.ts +1 -1
  17. package/dist/commands/daemon.d.ts +21 -0
  18. package/dist/commands/daemon.js +147 -12
  19. package/dist/commands/detach.d.ts +13 -0
  20. package/dist/commands/detach.js +11 -8
  21. package/dist/commands/exec.js +107 -53
  22. package/dist/commands/feed-watch.d.ts +2 -0
  23. package/dist/commands/feed-watch.js +28 -0
  24. package/dist/commands/feed.js +27 -0
  25. package/dist/commands/feedback.js +3 -19
  26. package/dist/commands/focus.d.ts +14 -0
  27. package/dist/commands/focus.js +48 -5
  28. package/dist/commands/go.d.ts +3 -0
  29. package/dist/commands/go.js +16 -3
  30. package/dist/commands/insights.d.ts +1 -1
  31. package/dist/commands/insights.js +2 -2
  32. package/dist/commands/inspect.js +2 -2
  33. package/dist/commands/lease.d.ts +1 -1
  34. package/dist/commands/lease.js +7 -6
  35. package/dist/commands/mailboxes.js +4 -3
  36. package/dist/commands/menubar.js +11 -4
  37. package/dist/commands/projects.d.ts +16 -1
  38. package/dist/commands/projects.js +58 -64
  39. package/dist/commands/resume.js +1 -1
  40. package/dist/commands/route.js +4 -4
  41. package/dist/commands/routines.js +4 -3
  42. package/dist/commands/run-broadcast.d.ts +1 -1
  43. package/dist/commands/run-broadcast.js +1 -1
  44. package/dist/commands/sessions-picker-factory.d.ts +2 -1
  45. package/dist/commands/sessions-picker-factory.js +1 -1
  46. package/dist/commands/sessions-picker.d.ts +22 -0
  47. package/dist/commands/sessions-picker.js +48 -9
  48. package/dist/commands/sessions-stop.d.ts +24 -0
  49. package/dist/commands/sessions-stop.js +66 -0
  50. package/dist/commands/sessions-trace.d.ts +82 -0
  51. package/dist/commands/sessions-trace.js +387 -0
  52. package/dist/commands/sessions.js +5 -0
  53. package/dist/commands/setup-secrets.js +23 -0
  54. package/dist/commands/share.d.ts +20 -5
  55. package/dist/commands/share.js +152 -52
  56. package/dist/commands/ssh.d.ts +15 -0
  57. package/dist/commands/ssh.js +243 -18
  58. package/dist/commands/teams.js +34 -2
  59. package/dist/commands/traces.d.ts +2 -0
  60. package/dist/commands/traces.js +184 -0
  61. package/dist/commands/view.d.ts +2 -0
  62. package/dist/commands/view.js +32 -62
  63. package/dist/commands/watchdog.js +9 -14
  64. package/dist/index.d.ts +1 -0
  65. package/dist/index.js +5 -0
  66. package/dist/lib/account-capabilities.d.ts +4 -0
  67. package/dist/lib/account-capabilities.js +13 -9
  68. package/dist/lib/account-registry.d.ts +2 -0
  69. package/dist/lib/account-registry.js +30 -2
  70. package/dist/lib/accounting/rotate.d.ts +9 -0
  71. package/dist/lib/accounting/rotate.js +12 -2
  72. package/dist/lib/accounting/usage.d.ts +84 -6
  73. package/dist/lib/accounting/usage.js +293 -75
  74. package/dist/lib/agent-modes.js +29 -1
  75. package/dist/lib/agent-spec/agents.d.ts +4 -3
  76. package/dist/lib/agent-spec/agents.js +25 -15
  77. package/dist/lib/analytics/mix-commands.d.ts +1 -1
  78. package/dist/lib/analytics/mix-commands.js +2 -2
  79. package/dist/lib/auth-health.d.ts +13 -3
  80. package/dist/lib/auth-health.js +50 -8
  81. package/dist/lib/browser/devices.d.ts +19 -0
  82. package/dist/lib/browser/devices.js +23 -0
  83. package/dist/lib/browser/drivers/ssh.d.ts +16 -2
  84. package/dist/lib/browser/drivers/ssh.js +36 -9
  85. package/dist/lib/browser/ipc.d.ts +1 -1
  86. package/dist/lib/browser/ipc.js +40 -9
  87. package/dist/lib/browser/profiles.d.ts +104 -65
  88. package/dist/lib/browser/profiles.js +277 -165
  89. package/dist/lib/browser/registry.d.ts +39 -0
  90. package/dist/lib/browser/registry.js +149 -0
  91. package/dist/lib/browser/remote-control.d.ts +28 -2
  92. package/dist/lib/browser/remote-control.js +35 -3
  93. package/dist/lib/browser/resolve-target.d.ts +68 -0
  94. package/dist/lib/browser/resolve-target.js +250 -0
  95. package/dist/lib/browser/runtime-state.d.ts +44 -11
  96. package/dist/lib/browser/runtime-state.js +98 -15
  97. package/dist/lib/browser/service.d.ts +65 -9
  98. package/dist/lib/browser/service.js +196 -61
  99. package/dist/lib/browser/sessions-list.d.ts +1 -3
  100. package/dist/lib/browser/sessions-list.js +3 -16
  101. package/dist/lib/browser/task-index.d.ts +70 -0
  102. package/dist/lib/browser/task-index.js +180 -0
  103. package/dist/lib/browser/types.d.ts +12 -1
  104. package/dist/lib/byok-usage.js +2 -2
  105. package/dist/lib/claude-statusline.d.ts +29 -0
  106. package/dist/lib/claude-statusline.js +139 -0
  107. package/dist/lib/cloud/host.d.ts +1 -1
  108. package/dist/lib/cloud/host.js +1 -1
  109. package/dist/lib/cloud/rush.js +1 -1
  110. package/dist/lib/codex-policy.d.ts +11 -1
  111. package/dist/lib/codex-policy.js +20 -5
  112. package/dist/lib/computer/computer-rpc.js +2 -2
  113. package/dist/lib/computer/download.d.ts +15 -15
  114. package/dist/lib/computer/download.js +35 -98
  115. package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
  116. package/dist/lib/computer/ssh-tunnel.js +12 -22
  117. package/dist/lib/config-keys.d.ts +6 -11
  118. package/dist/lib/config-keys.js +18 -19
  119. package/dist/lib/crabbox/cli.d.ts +1 -1
  120. package/dist/lib/crabbox/cli.js +2 -2
  121. package/dist/lib/crabbox/lease.d.ts +9 -0
  122. package/dist/lib/crabbox/lease.js +70 -15
  123. package/dist/lib/crabbox/runtimes.js +1 -1
  124. package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
  125. package/dist/lib/daemon/account-state-daemon-service.js +26 -0
  126. package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
  127. package/dist/lib/daemon/browser-ipc-service.js +46 -0
  128. package/dist/lib/daemon/daemon.d.ts +3 -0
  129. package/dist/lib/daemon/daemon.js +212 -444
  130. package/dist/lib/daemon/device-probe-service.d.ts +26 -0
  131. package/dist/lib/daemon/device-probe-service.js +47 -0
  132. package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
  133. package/dist/lib/daemon/keychain-reap-service.js +32 -0
  134. package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
  135. package/dist/lib/daemon/monitor-engine-service.js +29 -0
  136. package/dist/lib/daemon/runner.js +15 -2
  137. package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
  138. package/dist/lib/daemon/secrets-broker-service.js +66 -0
  139. package/dist/lib/daemon/self-heal-service.d.ts +27 -0
  140. package/dist/lib/daemon/self-heal-service.js +48 -0
  141. package/dist/lib/daemon/service.d.ts +102 -0
  142. package/dist/lib/daemon/service.js +98 -0
  143. package/dist/lib/daemon/session-index-service.d.ts +22 -0
  144. package/dist/lib/daemon/session-index-service.js +40 -0
  145. package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
  146. package/dist/lib/daemon/state-dir-check-service.js +63 -0
  147. package/dist/lib/daemon/supervisor.d.ts +87 -0
  148. package/dist/lib/daemon/supervisor.js +298 -0
  149. package/dist/lib/daemon/watchdog-service.d.ts +19 -0
  150. package/dist/lib/daemon/watchdog-service.js +40 -0
  151. package/dist/lib/daemon-health.d.ts +15 -0
  152. package/dist/lib/daemon-health.js +29 -5
  153. package/dist/lib/daemon-services.d.ts +8 -1
  154. package/dist/lib/daemon-services.js +55 -0
  155. package/dist/lib/daemon-ticks.d.ts +13 -2
  156. package/dist/lib/daemon-ticks.js +22 -20
  157. package/dist/lib/device-config.d.ts +25 -10
  158. package/dist/lib/device-config.js +90 -39
  159. package/dist/lib/devices/config-migration.d.ts +7 -0
  160. package/dist/lib/devices/config-migration.js +67 -3
  161. package/dist/lib/devices/health.d.ts +27 -2
  162. package/dist/lib/devices/health.js +39 -9
  163. package/dist/lib/devices/interactive-host.d.ts +15 -0
  164. package/dist/lib/devices/interactive-host.js +53 -0
  165. package/dist/lib/devices/registry.d.ts +61 -4
  166. package/dist/lib/devices/registry.js +94 -38
  167. package/dist/lib/devices/stats-cache.d.ts +26 -0
  168. package/dist/lib/devices/stats-cache.js +59 -7
  169. package/dist/lib/devices/worker-pick.d.ts +48 -0
  170. package/dist/lib/devices/worker-pick.js +113 -0
  171. package/dist/lib/exec.d.ts +70 -14
  172. package/dist/lib/exec.js +110 -38
  173. package/dist/lib/feed/answer.d.ts +29 -0
  174. package/dist/lib/feed/answer.js +145 -0
  175. package/dist/lib/feed/attention.d.ts +83 -0
  176. package/dist/lib/feed/attention.js +0 -0
  177. package/dist/lib/feed/feed.d.ts +109 -0
  178. package/dist/lib/feed/feed.js +154 -3
  179. package/dist/lib/feed/pr-status.d.ts +14 -0
  180. package/dist/lib/feed/pr-status.js +47 -0
  181. package/dist/lib/feed/watch.d.ts +59 -0
  182. package/dist/lib/feed/watch.js +180 -0
  183. package/dist/lib/fleet/capture.js +6 -0
  184. package/dist/lib/fleet/remote-login.js +8 -3
  185. package/dist/lib/fleet/types.d.ts +17 -0
  186. package/dist/lib/harness/adapter.d.ts +11 -0
  187. package/dist/lib/harness/adapters/claude.js +38 -24
  188. package/dist/lib/harness/adapters/codex.js +2 -4
  189. package/dist/lib/harness/adapters/cursor.js +8 -11
  190. package/dist/lib/helper-download.d.ts +108 -0
  191. package/dist/lib/helper-download.js +194 -0
  192. package/dist/lib/helper-versions.d.ts +52 -0
  193. package/dist/lib/helper-versions.js +58 -0
  194. package/dist/lib/hooks/cache.js +19 -0
  195. package/dist/lib/hooks/install.js +2 -2
  196. package/dist/lib/hooks/match.d.ts +3 -0
  197. package/dist/lib/hooks/match.js +25 -0
  198. package/dist/lib/hosts/credentials.d.ts +1 -1
  199. package/dist/lib/hosts/credentials.js +1 -1
  200. package/dist/lib/hosts/dispatch.d.ts +7 -1
  201. package/dist/lib/hosts/dispatch.js +24 -5
  202. package/dist/lib/hosts/passthrough.d.ts +8 -15
  203. package/dist/lib/hosts/passthrough.js +35 -16
  204. package/dist/lib/hosts/providers/devices.d.ts +1 -1
  205. package/dist/lib/hosts/providers/devices.js +1 -1
  206. package/dist/lib/hosts/reconnect.d.ts +179 -21
  207. package/dist/lib/hosts/reconnect.js +266 -45
  208. package/dist/lib/hosts/registry.js +7 -0
  209. package/dist/lib/installations/migrate.d.ts +4 -5
  210. package/dist/lib/installations/migrate.js +6 -7
  211. package/dist/lib/installations/shims.d.ts +1 -1
  212. package/dist/lib/installations/shims.js +23 -13
  213. package/dist/lib/installations/versions.js +25 -2
  214. package/dist/lib/linear-autoclose.d.ts +1 -1
  215. package/dist/lib/linear-autoclose.js +1 -1
  216. package/dist/lib/loop.d.ts +2 -2
  217. package/dist/lib/loop.js +1 -1
  218. package/dist/lib/menubar/download-menubar.d.ts +47 -0
  219. package/dist/lib/menubar/download-menubar.js +60 -0
  220. package/dist/lib/menubar/install-menubar.d.ts +44 -7
  221. package/dist/lib/menubar/install-menubar.js +105 -23
  222. package/dist/lib/menubar/notify-desktop.d.ts +3 -3
  223. package/dist/lib/menubar/notify-desktop.js +6 -6
  224. package/dist/lib/model-tiers.d.ts +1 -1
  225. package/dist/lib/open-url.d.ts +60 -2
  226. package/dist/lib/open-url.js +167 -12
  227. package/dist/lib/project-probe.d.ts +1 -1
  228. package/dist/lib/project-probe.js +1 -1
  229. package/dist/lib/project-pull.d.ts +1 -1
  230. package/dist/lib/project-pull.js +2 -2
  231. package/dist/lib/projects.d.ts +1 -1
  232. package/dist/lib/projects.js +1 -1
  233. package/dist/lib/refresh-coordinator.d.ts +1 -1
  234. package/dist/lib/refresh-coordinator.js +1 -1
  235. package/dist/lib/sandbox.js +6 -7
  236. package/dist/lib/secrets/download-keychain.d.ts +47 -0
  237. package/dist/lib/secrets/download-keychain.js +70 -0
  238. package/dist/lib/secrets/install-helper.d.ts +20 -1
  239. package/dist/lib/secrets/install-helper.js +44 -3
  240. package/dist/lib/session/active.d.ts +67 -0
  241. package/dist/lib/session/active.js +86 -2
  242. package/dist/lib/session/db.d.ts +11 -1
  243. package/dist/lib/session/db.js +142 -8
  244. package/dist/lib/session/discover.d.ts +23 -1
  245. package/dist/lib/session/discover.js +44 -10
  246. package/dist/lib/session/highlights.d.ts +49 -1
  247. package/dist/lib/session/highlights.js +95 -0
  248. package/dist/lib/session/host-link.d.ts +16 -2
  249. package/dist/lib/session/host-link.js +7 -1
  250. package/dist/lib/session/parse.js +33 -1
  251. package/dist/lib/session/prompt.d.ts +35 -0
  252. package/dist/lib/session/prompt.js +56 -0
  253. package/dist/lib/session/remote/remote-list.d.ts +9 -0
  254. package/dist/lib/session/remote/remote-list.js +19 -2
  255. package/dist/lib/session/remote/watch.d.ts +1 -1
  256. package/dist/lib/session/remote/watch.js +17 -3
  257. package/dist/lib/session/render.js +7 -3
  258. package/dist/lib/session/session-cache.d.ts +53 -0
  259. package/dist/lib/session/session-cache.js +93 -1
  260. package/dist/lib/session/state.js +1 -1
  261. package/dist/lib/session/tool-calls.d.ts +2 -0
  262. package/dist/lib/session/tool-calls.js +1 -1
  263. package/dist/lib/session/trajectory-compare.d.ts +47 -0
  264. package/dist/lib/session/trajectory-compare.js +116 -0
  265. package/dist/lib/session/trajectory-html.d.ts +19 -0
  266. package/dist/lib/session/trajectory-html.js +770 -0
  267. package/dist/lib/session/trajectory-lineage.d.ts +92 -0
  268. package/dist/lib/session/trajectory-lineage.js +195 -0
  269. package/dist/lib/session/trajectory-text.d.ts +37 -0
  270. package/dist/lib/session/trajectory-text.js +284 -0
  271. package/dist/lib/session/trajectory.d.ts +102 -0
  272. package/dist/lib/session/trajectory.js +384 -0
  273. package/dist/lib/session/types.d.ts +15 -0
  274. package/dist/lib/sha256-asset.d.ts +26 -0
  275. package/dist/lib/sha256-asset.js +41 -0
  276. package/dist/lib/share/backend.d.ts +95 -0
  277. package/dist/lib/share/backend.js +139 -0
  278. package/dist/lib/share/delete.d.ts +6 -2
  279. package/dist/lib/share/delete.js +8 -9
  280. package/dist/lib/share/html.d.ts +22 -0
  281. package/dist/lib/share/html.js +88 -0
  282. package/dist/lib/share/provision.d.ts +18 -4
  283. package/dist/lib/share/provision.js +38 -4
  284. package/dist/lib/share/publish.d.ts +39 -15
  285. package/dist/lib/share/publish.js +55 -26
  286. package/dist/lib/share/worker-template.js +171 -11
  287. package/dist/lib/sqlite.js +16 -3
  288. package/dist/lib/ssh-exec.d.ts +65 -0
  289. package/dist/lib/ssh-exec.js +120 -4
  290. package/dist/lib/startup/command-registry.d.ts +3 -1
  291. package/dist/lib/startup/command-registry.js +6 -3
  292. package/dist/lib/startup/dev-build.js +3 -3
  293. package/dist/lib/state.d.ts +2 -2
  294. package/dist/lib/state.js +11 -5
  295. package/dist/lib/subagents-registry.js +1 -1
  296. package/dist/lib/teams/agents.js +9 -0
  297. package/dist/lib/terminal/index.d.ts +1 -1
  298. package/dist/lib/terminal/inject.d.ts +19 -0
  299. package/dist/lib/terminal/inject.js +19 -9
  300. package/dist/lib/terminal/types.d.ts +1 -1
  301. package/dist/lib/terminal/types.js +1 -1
  302. package/dist/lib/tmux/orphan-reap.js +1 -1
  303. package/dist/lib/tmux/session.d.ts +46 -13
  304. package/dist/lib/tmux/session.js +204 -27
  305. package/dist/lib/traces/backend.d.ts +29 -0
  306. package/dist/lib/traces/backend.js +46 -0
  307. package/dist/lib/traces/classify.d.ts +52 -0
  308. package/dist/lib/traces/classify.js +72 -0
  309. package/dist/lib/traces/config.d.ts +10 -0
  310. package/dist/lib/traces/config.js +2 -0
  311. package/dist/lib/traces/provision.d.ts +19 -0
  312. package/dist/lib/traces/provision.js +30 -0
  313. package/dist/lib/traces/sync.d.ts +150 -0
  314. package/dist/lib/traces/sync.js +447 -0
  315. package/dist/lib/traces/worker-template.d.ts +4 -0
  316. package/dist/lib/traces/worker-template.js +159 -0
  317. package/dist/lib/types.d.ts +34 -19
  318. package/dist/lib/types.js +14 -0
  319. package/dist/lib/usage-backoff.d.ts +2 -1
  320. package/dist/lib/usage-backoff.js +60 -19
  321. package/dist/lib/usage-refresh.d.ts +18 -4
  322. package/dist/lib/usage-refresh.js +68 -19
  323. package/dist/lib/watchdog/log.d.ts +1 -1
  324. package/dist/lib/watchdog/log.js +1 -1
  325. package/dist/lib/watchdog/runner.d.ts +36 -45
  326. package/dist/lib/watchdog/runner.js +150 -119
  327. package/dist/lib/watchdog/service.d.ts +0 -1
  328. package/dist/lib/watchdog/service.js +0 -1
  329. package/dist/lib/watchdog/watchdog-agent.d.ts +38 -0
  330. package/dist/lib/watchdog/watchdog-agent.js +59 -0
  331. package/dist/lib/watchdog/watchdog.d.ts +11 -2
  332. package/dist/lib/watchdog/watchdog.js +37 -104
  333. package/dist/lib/workflows.d.ts +1 -1
  334. package/package.json +5 -7
  335. package/dist/commands/usage.d.ts +0 -27
  336. package/dist/commands/usage.js +0 -96
  337. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  338. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
  339. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  340. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  341. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
  342. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  343. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -24
  344. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  345. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -123
  346. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  347. package/dist/lib/usage-fleet.d.ts +0 -32
  348. package/dist/lib/usage-fleet.js +0 -125
@@ -0,0 +1,102 @@
1
+ import { type SessionStats } from './render.js';
2
+ import type { SessionEvent, SessionMeta } from './types.js';
3
+ /** One drawable step on a session's trajectory — a tool call or a thinking block. */
4
+ export interface TrajectoryStep {
5
+ /** 1-based position among drawn steps, stable for `--errors-only` and step refs. */
6
+ ordinal: number;
7
+ kind: 'tool' | 'thinking';
8
+ /** Tool name for `kind === 'tool'` (e.g. `Bash`, `Read`, `Task`). */
9
+ tool?: string;
10
+ /** The waterfall row this draws on — the tool name, or `think` for reasoning. */
11
+ lane: string;
12
+ /** Milliseconds from session start (the earliest event) to this step's start. */
13
+ startMs: number;
14
+ /**
15
+ * Duration in ms. Measured by pairing this `tool_use` with its `tool_result`
16
+ * on `callId`; when no paired result exists (or the harness omits `callId`),
17
+ * falls back to the delta to the next event and sets `durationEstimated`.
18
+ */
19
+ durationMs: number;
20
+ /** True when `durationMs` is the next-event fallback, not a measured pairing. */
21
+ durationEstimated: boolean;
22
+ /** Structured harness outcome from the paired result; never inferred from text. */
23
+ outcome?: 'ok' | 'error' | 'unknown';
24
+ /** Redacted one-line summary (command / path / subagent / pattern). */
25
+ label: string;
26
+ /** Redacted, bounded output/evidence snippet from the paired result. */
27
+ detail?: string;
28
+ /** An inline `Task`/`Agent` sub-agent branch (a cross-session teammate is lineage). */
29
+ delegation?: 'inline-task' | 'teammate';
30
+ /** Output tokens attributed from the nearest following usage event. */
31
+ outputTokens?: number;
32
+ /** Harness-native call identity, when present. */
33
+ callId?: string;
34
+ /**
35
+ * For a shell tool call (`Bash`), the effective program the command actually
36
+ * ran — `git`, `gh`, `agents`, `bun`, `sed`… — extracted by the shared shell
37
+ * parser (`extractShellPrograms`, wrappers like `timeout`/`sudo`/`agents ssh`
38
+ * unwrapped). Lets a Bash-dominated trajectory read by what it DID, not a wall
39
+ * of "Bash". Undefined for non-shell tools or an unparseable command.
40
+ */
41
+ program?: string;
42
+ /** Process exit code from the paired result (or the tool_use event), when the harness recorded one. */
43
+ exitCode?: number;
44
+ }
45
+ /** An idle stall between two events — a candidate "where did it hang" marker. */
46
+ export interface TrajectoryGap {
47
+ /** Milliseconds from session start to where the gap begins. */
48
+ startMs: number;
49
+ /** How long nothing happened, in ms. */
50
+ durationMs: number;
51
+ /** Ordinal of the last step before the gap (0 when the gap precedes step 1). */
52
+ afterOrdinal: number;
53
+ }
54
+ /** The derived trajectory of one session. */
55
+ export interface SessionTrajectory {
56
+ session: SessionMeta;
57
+ /** Wall-clock span of the session in ms (last event − first event). */
58
+ spanMs: number;
59
+ steps: TrajectoryStep[];
60
+ gaps: TrajectoryGap[];
61
+ /**
62
+ * Fraction of measured tool time per effective program, summing to ~1 (empty if
63
+ * no time). Keyed by the same tag the step list and badges use — a shell call's
64
+ * effective program (`git`, `bun`, `gh`), else the raw tool — so a Bash-heavy
65
+ * session reads as `git / bun / gh`, never a single opaque `Bash`. The single
66
+ * source both the HTML and text "where the time went" panels read.
67
+ */
68
+ programTimeShare: Record<string, number>;
69
+ /**
70
+ * Failed steps — steps whose paired result carried `outcome: 'error'`. Distinct
71
+ * from `stats.errorCount`, which counts only standalone `type: 'error'` events;
72
+ * a tool that failed via a `tool_result` outcome is invisible there but counted
73
+ * here, so this is the truthful "how many calls failed" for the trajectory view.
74
+ */
75
+ errorCount: number;
76
+ /** True when derived labels/details were secret-redacted (false under `--no-redact`). */
77
+ redacted: boolean;
78
+ /** Reused wholesale from `computeSummaryStats` — turns, tools, tokens, span. */
79
+ stats: SessionStats;
80
+ /** Steps dropped by the `maxSteps` cap, surfaced so truncation is never silent. */
81
+ truncatedSteps: number;
82
+ }
83
+ export interface BuildTrajectoryOptions {
84
+ /** Redact derived labels/details (default true; `--no-redact` is local-only). */
85
+ redact?: boolean;
86
+ /** Known secret values to mask, from `knownSecretValuesFromEnv()`. */
87
+ knownSecrets?: readonly string[];
88
+ /** A gap longer than this (ms) is recorded as an idle stall. Default 120000. */
89
+ idleThresholdMs?: number;
90
+ /** Cap on drawn steps; the tail is dropped and counted. Default 5000. */
91
+ maxSteps?: number;
92
+ }
93
+ /**
94
+ * Build the derived trajectory for one session's normalized events.
95
+ *
96
+ * Pairs each `tool_use` with its `tool_result`/`error` on `callId` (FIFO within a
97
+ * reused id — never across ids, and never by arrival order for concurrent calls,
98
+ * matching `ToolCallCollector.takePending`'s refusal to guess, `tool-calls.ts:509`).
99
+ * A harness with no parseable transcript (OpenClaw, `parse.ts:186`) yields an empty
100
+ * event array and therefore an empty trajectory — never a crash or a fabricated one.
101
+ */
102
+ export declare function buildTrajectory(events: SessionEvent[], meta: SessionMeta, options?: BuildTrajectoryOptions): SessionTrajectory;
@@ -0,0 +1,384 @@
1
+ /**
2
+ * Derived session trajectory model — the one genuinely new computation behind
3
+ * `agents sessions trace`.
4
+ *
5
+ * Nothing in the session pipeline records a per-step DURATION: the indexed tool
6
+ * call (`tool-calls.ts`) keeps only a start timestamp, and `SessionEvent`
7
+ * (`types.ts:46`) carries a `tool_use` and its `tool_result` as two separate
8
+ * events. This module pairs them by `callId` to recover how long each call took,
9
+ * flags the idle gaps between events (stalls), and rolls the per-tool time up
10
+ * into a "where the time went" share — all DERIVED from the normalized event
11
+ * stream, never persisted onto `SessionEvent` or the SQLite `tool_calls` index.
12
+ *
13
+ * Pure and framework-free: it takes the parsed `SessionEvent[]` and the session
14
+ * metadata and returns a plain object the three renderers (HTML / text / JSON)
15
+ * project. Redaction reuses the same `redactSecrets` path as `render`/`share`
16
+ * (`redact.ts`), applied to the derived one-line labels; the label is the only
17
+ * place raw command/argument text reaches the model.
18
+ *
19
+ * The types (`SessionTrajectory`, `TrajectoryStep`) are shaped to also carry the
20
+ * later compare + lineage layouts (a stats block, gaps, and time-share per
21
+ * session), even though this PR renders only the single-session layout.
22
+ */
23
+ import { redactSecrets } from '../redact.js';
24
+ import { computeSummaryStats } from './render.js';
25
+ import { extractShellPrograms } from './shell-programs.js';
26
+ const DEFAULT_IDLE_THRESHOLD_MS = 120_000;
27
+ const DEFAULT_MAX_STEPS = 5000;
28
+ const LABEL_MAX = 140;
29
+ const DETAIL_MAX = 400;
30
+ /** Tools that spawn an inline sub-agent inside THIS transcript (a `tool_use` row). */
31
+ const INLINE_TASK_TOOLS = new Set(['Task', 'Agent']);
32
+ /**
33
+ * Shell tools whose command is worth resolving to an effective program — every
34
+ * harness's shell-exec tool, kept in lockstep with the canonical set in
35
+ * `state.ts` (`['Bash', 'exec_command', 'exec', 'run_shell_command', 'shell', 'Execute']`)
36
+ * so Codex's `exec_command`/`exec` and the rest are covered, not just Claude's `Bash`.
37
+ * `exec` is newer Codex (gpt-5.6-sol) whose real command the parser unwraps from a
38
+ * JS cell into the event's `command` before this runs.
39
+ */
40
+ const SHELL_TOOLS = new Set(['Bash', 'exec_command', 'exec', 'run_shell_command', 'shell', 'Execute']);
41
+ /**
42
+ * Shell builtins/assignments that are rarely the POINT of a command — the action
43
+ * is whatever runs after them (`export X=Y; git push` → `git`, `cd dir && bun test`
44
+ * → `bun`). Skipped when a real program follows so a Bash row reads by what it did.
45
+ */
46
+ const SHELL_NOISE_PROGRAMS = new Set(['export', 'cd', 'set', 'source', '.', 'unset', 'local', 'eval']);
47
+ /**
48
+ * The effective program a shell command ran, via the shared shell parser
49
+ * (`extractShellPrograms` — which unwraps the wrappers it knows: `env`, `sudo`,
50
+ * `agents ssh`; note `timeout` is deliberately NOT a wrapper there, per SES-37,
51
+ * so `timeout 30 npm test` resolves to `timeout`). On top of that this skips
52
+ * bare builtins/assignments (`cd`, `export`, …) when a real program follows. For
53
+ * a pipeline it takes the LEFTMOST effective program (`cat f | grep x` → `cat`),
54
+ * matching how the parser orders occurrences. Undefined when nothing static is
55
+ * identifiable. Never executes anything.
56
+ */
57
+ function effectiveProgram(command) {
58
+ if (!command)
59
+ return undefined;
60
+ const { occurrences, programs } = extractShellPrograms(command);
61
+ const effective = occurrences.filter((o) => o.role === 'effective').map((o) => o.program);
62
+ const meaningful = effective.find((p) => !SHELL_NOISE_PROGRAMS.has(p))
63
+ ?? programs.find((p) => !SHELL_NOISE_PROGRAMS.has(p));
64
+ return meaningful ?? effective[0] ?? programs[0];
65
+ }
66
+ function toMs(timestamp) {
67
+ const ms = new Date(timestamp).getTime();
68
+ return Number.isNaN(ms) ? NaN : ms;
69
+ }
70
+ function firstLine(text) {
71
+ const nl = text.indexOf('\n');
72
+ return nl === -1 ? text : text.slice(0, nl);
73
+ }
74
+ function clip(text, max) {
75
+ const collapsed = firstLine(text).replace(/\s+/g, ' ').trim();
76
+ return collapsed.length <= max ? collapsed : `${collapsed.slice(0, max - 1)}…`;
77
+ }
78
+ function stringArg(args, ...keys) {
79
+ if (!args)
80
+ return undefined;
81
+ for (const key of keys) {
82
+ const value = args[key];
83
+ if (typeof value === 'string' && value.length > 0)
84
+ return value;
85
+ }
86
+ return undefined;
87
+ }
88
+ /**
89
+ * Strip a shell command's leading throwaway statements so its label reads by what
90
+ * it DID, not by its setup. Real agent commands overwhelmingly open with a
91
+ * `cd <repo> &&` (or a multi-line script whose first line is `cd <repo>`), plus the
92
+ * occasional `export X=Y;`, `set -e`, `source …`, or bare `VAR=val` assignment —
93
+ * exactly the {@link SHELL_NOISE_PROGRAMS} the badge already skips when resolving
94
+ * the effective program. Without this, every `cd /long/path && git …` row renders
95
+ * an identical `cd /long/path` and the trajectory is unreadable; here we drop that
96
+ * prefix so the label agrees with the badge (`git …`, `gh …`, `bun …`).
97
+ *
98
+ * Conservative on purpose: only a LEADING run of noise statements separated by
99
+ * `&&`, `;`, or a newline is removed (an assignment PREFIX like `VAR=val cmd` too),
100
+ * pipelines (`|`) are left intact, and a command that is nothing but noise (a lone
101
+ * `cd dir`) is returned unchanged rather than emptied. The separator match is a
102
+ * plain negated class, not a shell tokenizer, so a separator INSIDE a quoted arg
103
+ * (`cd "a; b" && x`) would cut mid-quote — guarded by refusing any strip that
104
+ * leaves the label with unbalanced quotes and falling back to the whole command.
105
+ * Display-only — the value is still clipped and secret-redacted downstream.
106
+ */
107
+ function quotesBalanced(text) {
108
+ const dq = (text.match(/(?<!\\)"/g) ?? []).length;
109
+ const sq = (text.match(/(?<!\\)'/g) ?? []).length;
110
+ return dq % 2 === 0 && sq % 2 === 0;
111
+ }
112
+ function stripLeadingShellNoise(command) {
113
+ // A leading `cd`/`export`/`set`/`source`/`unset`/`local`/`eval` statement, or an
114
+ // assignment, terminated by a statement separator (`&&`, `;`, or newline).
115
+ const noiseStatement = /^\s*(?:cd|export|set|source|unset|local|eval)\b[^\n&;|]*(?:&&|;|\n)+/i;
116
+ // A bare `VAR=val` — either a full statement (with a separator) or a prefix
117
+ // (followed by whitespace before the real program: `FOO=bar git push`).
118
+ const assignment = /^\s*[A-Za-z_][A-Za-z0-9_]*=[^\s&;|]*(?:\s*(?:&&|;|\n)+|\s+)/;
119
+ let cmd = command;
120
+ let prev = '';
121
+ while (cmd !== prev) {
122
+ prev = cmd;
123
+ cmd = cmd.replace(noiseStatement, '').replace(assignment, '');
124
+ }
125
+ const trimmed = cmd.trim();
126
+ if (trimmed.length === 0)
127
+ return command.trim();
128
+ // A strip that severed a quoted argument (a separator lived inside quotes) leaves
129
+ // a dangling quote — keep the whole command rather than show a mangled fragment.
130
+ if (!quotesBalanced(trimmed) && quotesBalanced(command))
131
+ return command.trim();
132
+ return trimmed;
133
+ }
134
+ /**
135
+ * A redacted one-line label for a tool call, derived from the raw arguments.
136
+ *
137
+ * This is the single place unredacted command/argument text enters the model, so
138
+ * it runs through the same `redactSecrets` pass `render`/`share` use. `--no-redact`
139
+ * (local only) skips that pass; the caller passes `redact: false`.
140
+ */
141
+ function toolLabel(tool, args, command, redact, knownSecrets) {
142
+ let raw;
143
+ const lower = tool.toLowerCase();
144
+ if (lower === 'bash' || lower === 'shell' || lower === 'run_command' || lower.includes('exec')) {
145
+ const cmd = command ?? stringArg(args, 'command', 'cmd', 'script');
146
+ raw = cmd ? stripLeadingShellNoise(cmd) : cmd;
147
+ }
148
+ else if (lower === 'read' || lower === 'edit' || lower === 'write' || lower === 'notebookedit' || lower === 'multiedit') {
149
+ raw = stringArg(args, 'file_path', 'path', 'notebook_path', 'filePath');
150
+ }
151
+ else if (lower === 'grep' || lower === 'glob' || lower === 'search' || lower === 'codebase_search') {
152
+ raw = stringArg(args, 'pattern', 'query', 'q');
153
+ }
154
+ else if (lower === 'task' || lower === 'agent') {
155
+ const subagent = stringArg(args, 'subagent_type', 'agentType', 'subagent');
156
+ const description = stringArg(args, 'description', 'prompt');
157
+ raw = subagent ? (description ? `${subagent}: ${description}` : subagent) : description;
158
+ }
159
+ else if (lower === 'webfetch' || lower === 'websearch' || lower === 'web_search') {
160
+ raw = stringArg(args, 'url', 'query', 'q');
161
+ }
162
+ else {
163
+ raw = stringArg(args, 'command', 'cmd', 'file_path', 'path', 'query', 'pattern', 'url', 'description');
164
+ }
165
+ const base = raw && raw.trim().length > 0 ? clip(raw, LABEL_MAX) : tool;
166
+ return redact ? redactSecrets(base, knownSecrets) : base;
167
+ }
168
+ function resultOutcome(event) {
169
+ if (event.outcome === 'ok' || event.outcome === 'error' || event.outcome === 'unknown')
170
+ return event.outcome;
171
+ if (event.success === true)
172
+ return 'ok';
173
+ if (event.success === false)
174
+ return 'error';
175
+ if (event.type === 'error')
176
+ return 'error';
177
+ return 'unknown';
178
+ }
179
+ function resultDetail(event, redact, knownSecrets) {
180
+ const text = event.output ?? event.content;
181
+ if (!text || text.trim().length === 0)
182
+ return undefined;
183
+ const clipped = clip(text, DETAIL_MAX);
184
+ return redact ? redactSecrets(clipped, knownSecrets) : clipped;
185
+ }
186
+ /**
187
+ * Build the derived trajectory for one session's normalized events.
188
+ *
189
+ * Pairs each `tool_use` with its `tool_result`/`error` on `callId` (FIFO within a
190
+ * reused id — never across ids, and never by arrival order for concurrent calls,
191
+ * matching `ToolCallCollector.takePending`'s refusal to guess, `tool-calls.ts:509`).
192
+ * A harness with no parseable transcript (OpenClaw, `parse.ts:186`) yields an empty
193
+ * event array and therefore an empty trajectory — never a crash or a fabricated one.
194
+ */
195
+ export function buildTrajectory(events, meta, options = {}) {
196
+ const redact = options.redact !== false;
197
+ const knownSecrets = options.knownSecrets;
198
+ const idleThreshold = options.idleThresholdMs ?? DEFAULT_IDLE_THRESHOLD_MS;
199
+ const maxSteps = options.maxSteps ?? DEFAULT_MAX_STEPS;
200
+ const stats = computeSummaryStats(events);
201
+ const firstTs = stats.firstTs;
202
+ const spanMs = stats.lastTs > stats.firstTs ? stats.lastTs - stats.firstTs : 0;
203
+ // Absolute ms per event index (NaN when the timestamp is unparseable).
204
+ const eventMs = events.map((e) => toMs(e.timestamp));
205
+ // The next event index (after i) that carries a valid timestamp — the anchor
206
+ // for the next-event duration fallback and for idle-gap detection.
207
+ const nextValidTs = new Array(events.length).fill(NaN);
208
+ for (let i = events.length - 1, later = NaN; i >= 0; i--) {
209
+ nextValidTs[i] = later;
210
+ if (!Number.isNaN(eventMs[i]))
211
+ later = eventMs[i];
212
+ }
213
+ // Draw a step for each thinking block and each non-local tool_use, in order.
214
+ // `_local` tool calls (Claude `!`-prefixed shell) are excluded to match the
215
+ // header's tool count (`computeSummaryStats` skips them, `render.ts:224`).
216
+ const drafts = [];
217
+ const pendingByCallId = new Map();
218
+ for (let i = 0; i < events.length; i++) {
219
+ const e = events[i];
220
+ if (e.type === 'thinking') {
221
+ drafts.push({
222
+ step: {
223
+ ordinal: 0,
224
+ kind: 'thinking',
225
+ lane: 'think',
226
+ startMs: Number.isNaN(eventMs[i]) ? 0 : Math.max(0, eventMs[i] - firstTs),
227
+ durationMs: 0,
228
+ durationEstimated: true,
229
+ label: 'thinking',
230
+ },
231
+ eventIndex: i,
232
+ });
233
+ }
234
+ else if (e.type === 'tool_use' && !e._local) {
235
+ const tool = e.tool || 'unknown';
236
+ const draftIndex = drafts.length;
237
+ drafts.push({
238
+ step: {
239
+ ordinal: 0,
240
+ kind: 'tool',
241
+ tool,
242
+ lane: tool,
243
+ startMs: Number.isNaN(eventMs[i]) ? 0 : Math.max(0, eventMs[i] - firstTs),
244
+ durationMs: 0,
245
+ durationEstimated: true,
246
+ outcome: 'unknown',
247
+ label: toolLabel(tool, e.args, e.command, redact, knownSecrets),
248
+ delegation: INLINE_TASK_TOOLS.has(tool) ? 'inline-task' : undefined,
249
+ callId: e.callId,
250
+ program: SHELL_TOOLS.has(tool) ? effectiveProgram(e.command ?? (typeof e.args?.command === 'string' ? e.args.command : undefined)) : undefined,
251
+ exitCode: typeof e.exitCode === 'number' ? e.exitCode : undefined,
252
+ },
253
+ eventIndex: i,
254
+ callId: e.callId,
255
+ });
256
+ if (e.callId) {
257
+ const queue = pendingByCallId.get(e.callId);
258
+ if (queue)
259
+ queue.push(draftIndex);
260
+ else
261
+ pendingByCallId.set(e.callId, [draftIndex]);
262
+ }
263
+ }
264
+ else if ((e.type === 'tool_result' || e.type === 'error') && e.callId) {
265
+ // Pair strictly by callId, FIFO within a reused id — never across ids.
266
+ const queue = pendingByCallId.get(e.callId);
267
+ if (queue && queue.length > 0) {
268
+ const draftIndex = queue.shift();
269
+ drafts[draftIndex].resultEventIndex = i;
270
+ }
271
+ }
272
+ }
273
+ // Resolve durations, outcomes, and detail now that pairing is complete.
274
+ for (const draft of drafts) {
275
+ const { step } = draft;
276
+ const startAbs = Number.isNaN(eventMs[draft.eventIndex]) ? NaN : eventMs[draft.eventIndex];
277
+ if (draft.resultEventIndex !== undefined) {
278
+ const resultEvent = events[draft.resultEventIndex];
279
+ const resultAbs = eventMs[draft.resultEventIndex];
280
+ if (!Number.isNaN(startAbs) && !Number.isNaN(resultAbs)) {
281
+ step.durationMs = Math.max(0, resultAbs - startAbs);
282
+ step.durationEstimated = false;
283
+ }
284
+ step.outcome = resultOutcome(resultEvent);
285
+ // Prefer the exit code recorded on the paired result; keep the tool_use
286
+ // event's code (set at draft time) only when the result carries none.
287
+ if (typeof resultEvent.exitCode === 'number')
288
+ step.exitCode = resultEvent.exitCode;
289
+ const detail = resultDetail(resultEvent, redact, knownSecrets);
290
+ if (detail)
291
+ step.detail = detail;
292
+ }
293
+ else {
294
+ // No paired result — fall back to the delta to the next timestamped event.
295
+ const nextAbs = nextValidTs[draft.eventIndex];
296
+ if (!Number.isNaN(startAbs) && !Number.isNaN(nextAbs)) {
297
+ step.durationMs = Math.max(0, nextAbs - startAbs);
298
+ }
299
+ step.durationEstimated = true;
300
+ }
301
+ }
302
+ // Attribute output tokens: each usage event lands on the most recent tool step
303
+ // (the nearest following usage from that step's point of view).
304
+ let lastToolDraft;
305
+ const draftByEventIndex = new Map();
306
+ for (const draft of drafts)
307
+ draftByEventIndex.set(draft.eventIndex, draft);
308
+ for (let i = 0; i < events.length; i++) {
309
+ const draft = draftByEventIndex.get(i);
310
+ if (draft && draft.step.kind === 'tool')
311
+ lastToolDraft = draft;
312
+ else if (events[i].type === 'usage' && lastToolDraft) {
313
+ const out = events[i].outputTokens ?? 0;
314
+ if (out > 0)
315
+ lastToolDraft.step.outputTokens = (lastToolDraft.step.outputTokens ?? 0) + out;
316
+ }
317
+ }
318
+ // Number the steps and apply the cap (tail dropped, counted — never silent).
319
+ let steps = drafts.map((d) => d.step);
320
+ let truncatedSteps = 0;
321
+ if (steps.length > maxSteps) {
322
+ truncatedSteps = steps.length - maxSteps;
323
+ steps = steps.slice(0, maxSteps);
324
+ }
325
+ steps.forEach((step, index) => { step.ordinal = index + 1; });
326
+ // Idle gaps: a long delta between consecutive timestamped events. `afterOrdinal`
327
+ // is the last step whose event index precedes the gap.
328
+ const gaps = [];
329
+ const orderedTs = [];
330
+ for (let i = 0; i < events.length; i++) {
331
+ if (!Number.isNaN(eventMs[i]))
332
+ orderedTs.push({ index: i, ms: eventMs[i] });
333
+ }
334
+ const stepOrdinalAtOrBefore = (eventIndex) => {
335
+ let ordinal = 0;
336
+ for (const d of drafts) {
337
+ if (d.eventIndex <= eventIndex && d.step.ordinal > 0)
338
+ ordinal = d.step.ordinal;
339
+ else if (d.eventIndex > eventIndex)
340
+ break;
341
+ }
342
+ return ordinal;
343
+ };
344
+ for (let k = 1; k < orderedTs.length; k++) {
345
+ // A span that OPENS on a `tool_use` is that tool executing (waiting on its
346
+ // own result), not the agent sitting idle — so it is not a stall. Counting
347
+ // it double-reports a long `bun test` as both its step duration and a fake
348
+ // "idle" gap. Only spans that open after a completed boundary are idle.
349
+ if (events[orderedTs[k - 1].index].type === 'tool_use')
350
+ continue;
351
+ const delta = orderedTs[k].ms - orderedTs[k - 1].ms;
352
+ if (delta > idleThreshold) {
353
+ gaps.push({
354
+ startMs: Math.max(0, orderedTs[k - 1].ms - firstTs),
355
+ durationMs: delta,
356
+ afterOrdinal: stepOrdinalAtOrBefore(orderedTs[k - 1].index),
357
+ });
358
+ }
359
+ }
360
+ // Where the time went — measured tool duration share per tool.
361
+ // Sum tool time by the EFFECTIVE PROGRAM (a shell call's `git`/`bun`/`gh`, else
362
+ // the raw tool) — the same tag the step list and badges show — so both renderers
363
+ // read one program-keyed source instead of each re-bucketing the steps.
364
+ const perProgram = {};
365
+ let totalToolMs = 0;
366
+ for (const step of steps) {
367
+ if (step.kind !== 'tool')
368
+ continue;
369
+ const tag = step.program ?? step.tool;
370
+ if (!tag)
371
+ continue;
372
+ perProgram[tag] = (perProgram[tag] ?? 0) + step.durationMs;
373
+ totalToolMs += step.durationMs;
374
+ }
375
+ const programTimeShare = {};
376
+ if (totalToolMs > 0) {
377
+ for (const [tag, ms] of Object.entries(perProgram)) {
378
+ if (ms > 0)
379
+ programTimeShare[tag] = ms / totalToolMs;
380
+ }
381
+ }
382
+ const errorCount = steps.reduce((n, s) => (s.outcome === 'error' ? n + 1 : n), 0);
383
+ return { session: meta, spanMs, steps, gaps, programTimeShare, errorCount, redacted: redact, stats, truncatedSteps };
384
+ }
@@ -247,6 +247,21 @@ export interface SessionMeta {
247
247
  * `isTeamOrigin` (which marks sessions spawned BY a team).
248
248
  */
249
249
  spawnedTeam?: string;
250
+ /**
251
+ * Fan-out this session left behind, captured at scan time so the preview can
252
+ * show it WITHOUT re-parsing the transcript — which is the only way a remote
253
+ * or unindexed row (rendered from `SessionMeta` alone) can show it at all.
254
+ *
255
+ * `undefined` and `0` are different claims and must render the same way (the
256
+ * segment omitted): `undefined` means the row predates the field or the
257
+ * harness cannot report it, `0` means scanned and none found. Rendering a
258
+ * literal "0 background shells" for a harness that cannot report them would
259
+ * assert "none running" where the truth is "unknown".
260
+ *
261
+ * Counts are "started", never "still running" — see `extractBackgroundShells`.
262
+ */
263
+ subAgentCount?: number;
264
+ backgroundShellCount?: number;
250
265
  /**
251
266
  * The plan markdown from the LAST `ExitPlanMode` tool call in the transcript
252
267
  * (Claude sessions only), captured at scan time. Present whenever the session
@@ -0,0 +1,26 @@
1
+ /**
2
+ * sha256 helpers for verifying downloaded release assets.
3
+ *
4
+ * These live in their own LEAF module — importing only `node:crypto` and
5
+ * `node:fs` — on purpose. They used to sit in `computer/ssh-tunnel.ts`, whose
6
+ * own import graph reaches `browser/drivers/ssh.ts` -> `browser/chrome.ts` ->
7
+ * `secrets/*`. `helper-download.ts` needs nothing from that graph but these two
8
+ * pure functions, and importing them from there closed a module-initialization
9
+ * cycle:
10
+ *
11
+ * helper-download.ts
12
+ * -> computer/ssh-tunnel.ts (evaluated BEFORE `EXPECTED_TEAM_ID`)
13
+ * -> browser/drivers/ssh.ts -> browser/chrome.ts
14
+ * -> secrets/bundles.ts -> secrets/index.ts -> secrets/install-helper.ts
15
+ * -> secrets/download-keychain.ts
16
+ * -> helper-download.ts (still evaluating; const not yet bound)
17
+ *
18
+ * which threw `ReferenceError: Cannot access 'EXPECTED_TEAM_ID' before
19
+ * initialization` at `secrets/download-keychain.ts:45` for any entry point that
20
+ * reached `helper-download.ts` first (RUSH-3113). Keep this module a leaf: adding
21
+ * a local import here can reintroduce that cycle.
22
+ */
23
+ /** Pull the digest out of a `<sha256> <filename>` release asset. */
24
+ export declare function parseSha256Asset(text: string): string;
25
+ /** Stream a file through sha256 — the exe is ~157MB, never read it whole. */
26
+ export declare function sha256File(file: string): Promise<string>;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * sha256 helpers for verifying downloaded release assets.
3
+ *
4
+ * These live in their own LEAF module — importing only `node:crypto` and
5
+ * `node:fs` — on purpose. They used to sit in `computer/ssh-tunnel.ts`, whose
6
+ * own import graph reaches `browser/drivers/ssh.ts` -> `browser/chrome.ts` ->
7
+ * `secrets/*`. `helper-download.ts` needs nothing from that graph but these two
8
+ * pure functions, and importing them from there closed a module-initialization
9
+ * cycle:
10
+ *
11
+ * helper-download.ts
12
+ * -> computer/ssh-tunnel.ts (evaluated BEFORE `EXPECTED_TEAM_ID`)
13
+ * -> browser/drivers/ssh.ts -> browser/chrome.ts
14
+ * -> secrets/bundles.ts -> secrets/index.ts -> secrets/install-helper.ts
15
+ * -> secrets/download-keychain.ts
16
+ * -> helper-download.ts (still evaluating; const not yet bound)
17
+ *
18
+ * which threw `ReferenceError: Cannot access 'EXPECTED_TEAM_ID' before
19
+ * initialization` at `secrets/download-keychain.ts:45` for any entry point that
20
+ * reached `helper-download.ts` first (RUSH-3113). Keep this module a leaf: adding
21
+ * a local import here can reintroduce that cycle.
22
+ */
23
+ import { createHash } from 'node:crypto';
24
+ import * as fs from 'node:fs';
25
+ /** Pull the digest out of a `<sha256> <filename>` release asset. */
26
+ export function parseSha256Asset(text) {
27
+ const m = text.trim().match(/^([A-Fa-f0-9]{64})(\s|$)/);
28
+ if (!m)
29
+ throw new Error(`malformed .sha256 release asset: ${JSON.stringify(text.slice(0, 80))}`);
30
+ return m[1].toLowerCase();
31
+ }
32
+ /** Stream a file through sha256 — the exe is ~157MB, never read it whole. */
33
+ export function sha256File(file) {
34
+ return new Promise((resolve, reject) => {
35
+ const hash = createHash('sha256');
36
+ fs.createReadStream(file)
37
+ .on('error', reject)
38
+ .on('data', (d) => hash.update(d))
39
+ .on('end', () => resolve(hash.digest('hex')));
40
+ });
41
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * ShareBackend — the token-source seam for `agents artifacts share`.
3
+ *
4
+ * Two legitimate principals, picked once at publish time:
5
+ * - **managed**: the caller is signed in to Phoenix (`readSession()`), no
6
+ * explicit BYO override. Token is the Phoenix access_token; endpoint is
7
+ * the already-live `share.agents-cli.sh`. Zero Cloudflare setup.
8
+ * - **byo**: the existing static WRITE_TOKEN path (`readShareConfig()` +
9
+ * `readWriteToken()`). Unchanged for power users / custom domains / fleet
10
+ * injection of `SHARE_WRITE_TOKEN`.
11
+ *
12
+ * Dispatch is not a fallback: signed-in AND no BYO override → managed;
13
+ * otherwise BYO. Fail loud when neither principal can authenticate.
14
+ *
15
+ * Explicit BYO override (any one is enough):
16
+ * - `opts.byo === true`
17
+ * - `opts.writeToken` (a caller-supplied static token, including tests)
18
+ * - `AGENTS_SHARE_BACKEND=byo`
19
+ */
20
+ import { type PhoenixSession } from '../identity/client.js';
21
+ import { type ShareConfig } from './config.js';
22
+ export type ShareBackendKind = 'managed' | 'byo';
23
+ export interface ShareBackend {
24
+ kind: ShareBackendKind;
25
+ /** Public base URL of the share Worker, no trailing slash. */
26
+ baseUrl: string;
27
+ /** Bearer sent as `Authorization`. Phoenix access_token or WRITE_TOKEN. */
28
+ token: string;
29
+ /** URL namespace prefix (`<namespace>/<slug>`). Phoenix handle (email local-part) or GitHub username. */
30
+ namespace: string;
31
+ }
32
+ export interface ResolveShareBackendOpts {
33
+ /** Force the BYO Cloudflare path even when signed in. */
34
+ byo?: boolean;
35
+ /** Override the namespace (BYO: GitHub username; ignored on managed — the
36
+ * worker 403s a Phoenix PUT whose first segment is not the verified handle). */
37
+ githubUser?: string;
38
+ /** DI seam — a static write token selects BYO. */
39
+ writeToken?: string;
40
+ /** DI seam — persisted BYO endpoint config. */
41
+ config?: ShareConfig;
42
+ /** DI seam — override `readSession()`. `null` means signed out. */
43
+ session?: PhoenixSession | null;
44
+ /**
45
+ * When false, BYO may resolve without a WRITE_TOKEN. Public GET routes
46
+ * (list / revisions / status) don't need one; publish and delete do.
47
+ * Default true.
48
+ */
49
+ requireToken?: boolean;
50
+ }
51
+ /** Env var that forces the BYO path. Value must be exactly `byo`. */
52
+ export declare const SHARE_BACKEND_ENV = "AGENTS_SHARE_BACKEND";
53
+ /** URL-safe namespace: lowercase `[a-z0-9-]+`. Matches the Worker's sanitize.
54
+ *
55
+ * This is URL-safety, not collision-resistance: two values that differ only
56
+ * in case or punctuation share a prefix. */
57
+ export declare function sanitizeShareNamespace(s: string): string;
58
+ /**
59
+ * Public managed-share handle from a verified email.
60
+ *
61
+ * `muqsitnawaz@gmail.com` → `muqsitnawaz`. Plus-tags are dropped
62
+ * (`muqsitnawaz+dev@gmail.com` → `muqsitnawaz`) so the URL stays the
63
+ * account's default name. Empty / missing email → `''` (caller falls
64
+ * back to userId). Must match the Worker `handleFromEmail`.
65
+ */
66
+ export declare function handleFromEmail(email: string | undefined): string;
67
+ /** Managed URL namespace: email handle, else sanitized userId (never empty if either is set). */
68
+ export declare function managedShareHandle(session: Pick<PhoenixSession, 'email' | 'userId'>): string;
69
+ export declare function managedShareBaseUrl(): string;
70
+ /**
71
+ * True when this config is the platform managed endpoint (`share.agents-cli.sh`).
72
+ * The deploy path uses this — not "the caller is signed in" — to decide whether
73
+ * to bind `PHOENIX_ID_BASE` on the Worker.
74
+ */
75
+ export declare function isManagedShareEndpoint(cfg: {
76
+ baseUrl?: string;
77
+ domain?: string;
78
+ }): boolean;
79
+ /**
80
+ * Phoenix ID URL to bind on a managed Worker deploy. `undefined` for a
81
+ * pure-BYO Worker (WRITE_TOKEN only). Fail loud if this is a managed deploy
82
+ * but the base is empty — never skip-set a blank secret.
83
+ */
84
+ export declare function phoenixIdBaseForDeploy(opts?: {
85
+ managed?: boolean;
86
+ }, cfg?: {
87
+ baseUrl?: string;
88
+ domain?: string;
89
+ }): string | undefined;
90
+ /**
91
+ * Pick the principal. Signed-in (`readSession() != null`) AND no explicit BYO
92
+ * override → managed; otherwise BYO.
93
+ */
94
+ export declare function shouldUseManaged(opts?: ResolveShareBackendOpts): boolean;
95
+ export declare function resolveShareBackend(opts?: ResolveShareBackendOpts): ShareBackend;