@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
@@ -10,10 +10,110 @@
10
10
  * `tmux list-sessions` and prunes stale entries on the fly.
11
11
  */
12
12
  import * as fs from 'fs';
13
+ import * as os from 'os';
14
+ import * as path from 'path';
13
15
  import { runTmux, TmuxCommandError } from './binary.js';
14
16
  import { ensureTmuxDir, getDefaultSocketPath, getSessionMetaPath } from './paths.js';
15
17
  /** Tmux session names must not contain `.` or `:` — those are reserved for window/pane addressing. */
16
18
  const VALID_NAME = /^[A-Za-z0-9_-]{1,64}$/;
19
+ /**
20
+ * Ten times tmux's 2,000-line default, while keeping per-pane memory bounded:
21
+ * history storage scales with pane width, so a 200-column pane retains at most
22
+ * four million grid cells rather than an unbounded agent transcript.
23
+ */
24
+ export const AGENTS_TMUX_HISTORY_LIMIT = 20_000;
25
+ /**
26
+ * Bump when the generated startup config changes, so an ALREADY-RUNNING server
27
+ * picks the new settings up. tmux reads `-f` only when it actually starts a
28
+ * server: a second `new-session -f other.conf` against a live server exits 0
29
+ * and silently applies nothing (verified against tmux 3.4). Since agents-cli
30
+ * uses one long-lived shared socket, the normal state at upgrade is a running
31
+ * server — so without this reconcile every existing machine would get none of
32
+ * these defaults, silently. Same shape as AGENT_HOOK_SCHEMA below.
33
+ */
34
+ export const AGENTS_TMUX_CONFIG_SCHEMA = 1;
35
+ /** Server-scoped user-option recording which AGENTS_TMUX_CONFIG_SCHEMA is applied. */
36
+ const CONFIG_SCHEMA_OPTION = '@ag_tmux_config_schema';
37
+ /**
38
+ * Session-scoped user options naming WHICH agent session a wrapper session runs,
39
+ * so a status bar (or any tmux format) can show it without shelling out:
40
+ * `#{@ag_session_id}` / `#{@ag_agent}`.
41
+ *
42
+ * The session NAME cannot serve this purpose. runInTmux builds it from
43
+ * `(options.sessionId ?? randomUUID()).slice(0, 8)` (exec.ts), so a run with no
44
+ * pre-assigned id still gets 8 hex characters — indistinguishable from a real
45
+ * short session id but resolving to nothing. Only claude currently takes a
46
+ * forced `--session-id`, so on a box running both, `ag-claude-<real>` and
47
+ * `ag-codex-<fabricated>` look identical. These options are stamped ONLY from
48
+ * `labels.sessionId`, which is set only when the id is genuine — absent rather
49
+ * than wrong, so a consumer can fall back instead of displaying a fake handle.
50
+ */
51
+ const SESSION_ID_OPTION = '@ag_session_id';
52
+ const AGENT_NAME_OPTION = '@ag_agent';
53
+ let startupConfigSequence = 0;
54
+ function tmuxConfigArgument(value) {
55
+ return `"${value.replace(/([\\"$])/g, '\\$1')}"`;
56
+ }
57
+ /**
58
+ * tmux loads `-f` instead of its normal user config, so put agents-cli's
59
+ * defaults first and explicitly source the first user config tmux would have
60
+ * selected afterward. Configuration files execute in order; this makes these
61
+ * settings defaults while preserving every option or binding the user sets.
62
+ */
63
+ function writeStartupConfig(env) {
64
+ const effectiveEnv = env ?? process.env;
65
+ const home = effectiveEnv.HOME ?? os.homedir();
66
+ const candidates = [
67
+ path.join(home, '.tmux.conf'),
68
+ ...(effectiveEnv.XDG_CONFIG_HOME
69
+ ? [path.join(effectiveEnv.XDG_CONFIG_HOME, 'tmux', 'tmux.conf')]
70
+ : []),
71
+ path.join(home, '.config', 'tmux', 'tmux.conf'),
72
+ ];
73
+ // tmux's own start_cfg() sources EVERY entry of TMUX_CONF that exists, not
74
+ // just the first — a user with both ~/.tmux.conf and ~/.config/tmux/tmux.conf
75
+ // gets both. Sourcing only the first would silently drop the second and break
76
+ // the "never override a user-set value" contract this file exists to keep.
77
+ const userConfigs = candidates.filter((candidate) => fs.existsSync(candidate));
78
+ const startupConfig = path.join(ensureTmuxDir(), `startup-${process.pid}-${startupConfigSequence++}.conf`);
79
+ const lines = [
80
+ // Stamp the schema HERE as well as in reconcileServerConfig. Without it a
81
+ // cold start leaves the option unset, the post-new-session check sees a
82
+ // stale stamp, and the reconcile re-sources the user's config a second time
83
+ // on launch #1 — firing any run-shell side effect (TPM) twice.
84
+ `set-option -g ${CONFIG_SCHEMA_OPTION} ${AGENTS_TMUX_CONFIG_SCHEMA}`,
85
+ 'set-option -g mouse on',
86
+ 'set-option -s set-clipboard on',
87
+ `set-option -g history-limit ${AGENTS_TMUX_HISTORY_LIMIT}`,
88
+ 'bind-key -T copy-mode MouseDragEnd1Pane send-keys -X copy-selection-no-clear',
89
+ 'bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-selection-no-clear',
90
+ ];
91
+ for (const userConfig of userConfigs) {
92
+ lines.push(`source-file -q ${tmuxConfigArgument(userConfig)}`);
93
+ }
94
+ fs.writeFileSync(startupConfig, `${lines.join('\n')}\n`, { mode: 0o600 });
95
+ return startupConfig;
96
+ }
97
+ /** Build the atomic server-configuration + session-creation command. */
98
+ export function buildCreateSessionArgs(opts, startupConfig) {
99
+ const args = [
100
+ '-f', startupConfig,
101
+ 'set-option', '-g', 'remain-on-exit', 'on', ';',
102
+ 'new-session', '-d', '-s', opts.name, '-P', '-F', '#{pane_id}',
103
+ ];
104
+ if (opts.width)
105
+ args.push('-x', String(opts.width));
106
+ if (opts.height)
107
+ args.push('-y', String(opts.height));
108
+ if (opts.cwd)
109
+ args.push('-c', opts.cwd);
110
+ // Separator + child command. tmux passes the rest verbatim to exec, so no
111
+ // shell escaping is required — array args end-to-end.
112
+ if (opts.cmd) {
113
+ args.push('--', 'sh', '-c', opts.cmd);
114
+ }
115
+ return args;
116
+ }
17
117
  export class TmuxSessionError extends Error {
18
118
  cause;
19
119
  constructor(message, cause) {
@@ -44,6 +144,41 @@ export async function hasSession(name, socket) {
44
144
  });
45
145
  return res.code === 0;
46
146
  }
147
+ /** Which AGENTS_TMUX_CONFIG_SCHEMA a live server has applied; undefined when none. */
148
+ async function appliedConfigSchema(socket) {
149
+ const res = await runTmux({
150
+ socket,
151
+ args: ['show-options', '-gv', CONFIG_SCHEMA_OPTION],
152
+ throwOnError: false,
153
+ }).catch(() => null);
154
+ if (!res || res.code !== 0)
155
+ return undefined;
156
+ const n = Number(res.stdout.trim());
157
+ return Number.isFinite(n) ? n : undefined;
158
+ }
159
+ /**
160
+ * Apply the generated startup config to an already-running server and stamp it.
161
+ * Failure is surfaced, not swallowed: a server left without these settings is
162
+ * the silent no-op this exists to prevent.
163
+ */
164
+ async function reconcileServerConfig(socket, env) {
165
+ const conf = writeStartupConfig(env);
166
+ try {
167
+ const res = await runTmux({ socket, args: ['source-file', conf], throwOnError: false, env });
168
+ if (res.code !== 0) {
169
+ throw new TmuxSessionError(`could not apply agents-cli tmux settings to the running server (${res.stderr.trim() || `exit ${res.code}`})`);
170
+ }
171
+ await runTmux({
172
+ socket,
173
+ args: ['set-option', '-g', CONFIG_SCHEMA_OPTION, String(AGENTS_TMUX_CONFIG_SCHEMA)],
174
+ throwOnError: false,
175
+ env,
176
+ }).catch(() => { });
177
+ }
178
+ finally {
179
+ fs.rmSync(conf, { force: true });
180
+ }
181
+ }
47
182
  /**
48
183
  * Create a new detached session. Throws when the name is already taken unless
49
184
  * `replace` or `attachExisting` is set.
@@ -58,6 +193,13 @@ export async function createSession(opts) {
58
193
  const existed = await hasSession(opts.name, socket);
59
194
  if (existed) {
60
195
  if (opts.attachExisting) {
196
+ // Reuse of a live session is precisely the already-running-server case,
197
+ // so it needs the same reconcile the create path gets — otherwise
198
+ // `agents tmux new --attach-existing` silently keeps a pre-fix server
199
+ // with none of these settings.
200
+ if ((await appliedConfigSchema(socket)) !== AGENTS_TMUX_CONFIG_SCHEMA) {
201
+ await reconcileServerConfig(socket, opts.env);
202
+ }
61
203
  const meta = readSessionMeta(opts.name);
62
204
  return meta ?? {
63
205
  name: opts.name,
@@ -71,28 +213,32 @@ export async function createSession(opts) {
71
213
  }
72
214
  await killSession(opts.name, socket);
73
215
  }
74
- // Set remain-on-exit BEFORE the child command can finish — a fast-exiting
216
+ // Configure the agents-cli tmux server BEFORE the child command can finish — a fast-exiting
75
217
  // cmd (e.g. `echo BRIEF && true`) would otherwise collapse the only session,
76
218
  // exit the server, and the follow-up `set-option` would race with "no
77
219
  // server running". Server-wide (`-g`) is applied in the same tmux
78
220
  // invocation as new-session so they share one server lifetime.
79
221
  // `-P -F '#{pane_id}'` prints the new session's first pane id on stdout so we
80
222
  // can record the exact `%N` handle without a follow-up `list-panes`.
81
- const args = ['set-option', '-g', 'remain-on-exit', 'on', ';', 'new-session', '-d', '-s', opts.name, '-P', '-F', '#{pane_id}'];
82
- if (opts.width)
83
- args.push('-x', String(opts.width));
84
- if (opts.height)
85
- args.push('-y', String(opts.height));
86
- if (opts.cwd)
87
- args.push('-c', opts.cwd);
88
- // Separator + child command. tmux passes the rest verbatim to exec, so no
89
- // shell escaping is required — array args end-to-end.
90
- if (opts.cmd) {
91
- args.push('--', 'sh', '-c', opts.cmd);
223
+ const startupConfig = writeStartupConfig(opts.env);
224
+ const args = buildCreateSessionArgs(opts, startupConfig);
225
+ let res;
226
+ try {
227
+ res = await runTmux({ socket, args, env: opts.env });
228
+ }
229
+ finally {
230
+ fs.rmSync(startupConfig, { force: true });
92
231
  }
93
- const res = await runTmux({ socket, args, env: opts.env });
94
232
  // Only the new-session command in the `;`-chained invocation emits output.
95
233
  const pane = /^%\d+$/.test(res.stdout.trim()) ? res.stdout.trim() : undefined;
234
+ // If the server was ALREADY running, its `-f` was ignored, so the config above
235
+ // never applied. Re-source it — the generated file ends by sourcing the user's
236
+ // own tmux.conf, so re-applying preserves the same precedence a cold start
237
+ // gives. Gated on a generation stamp so a user config with side effects (tpm's
238
+ // run-shell, for one) is not re-executed on every single launch.
239
+ if (existed || (await appliedConfigSchema(socket)) !== AGENTS_TMUX_CONFIG_SCHEMA) {
240
+ await reconcileServerConfig(socket, opts.env);
241
+ }
96
242
  // Keep the agent pane around after its process exits (so runInTmux can read
97
243
  // the exit status and capture the final error), but do NOT keep that behavior
98
244
  // for user-created splits. Apply remain-on-exit to the agent pane only, then
@@ -102,6 +248,21 @@ export async function createSession(opts) {
102
248
  if (pane) {
103
249
  await runTmux({ socket, args: ['set-option', '-pt', pane, 'remain-on-exit', 'on', ';', 'set-option', '-g', 'remain-on-exit', 'off'], throwOnError: false }).catch(() => { });
104
250
  }
251
+ // Stamp the agent identity onto the session so tmux formats can read it.
252
+ // Best-effort: a failure here must not fail a launch that otherwise worked,
253
+ // and an unset option simply renders empty in a format.
254
+ const identityArgs = [];
255
+ if (opts.labels?.sessionId) {
256
+ identityArgs.push('set-option', '-t', opts.name, SESSION_ID_OPTION, opts.labels.sessionId);
257
+ }
258
+ if (opts.labels?.agent) {
259
+ if (identityArgs.length)
260
+ identityArgs.push(';');
261
+ identityArgs.push('set-option', '-t', opts.name, AGENT_NAME_OPTION, opts.labels.agent);
262
+ }
263
+ if (identityArgs.length) {
264
+ await runTmux({ socket, args: identityArgs, throwOnError: false }).catch(() => { });
265
+ }
105
266
  const meta = {
106
267
  name: opts.name,
107
268
  socket,
@@ -402,8 +563,17 @@ export async function setSessionHook(name, hook, command, socket, timeoutMs) {
402
563
  * variant could stall the hook on a loaded CI runner, letting the dead
403
564
  * split survive until the test's wait timeout expired (flake in CI shard
404
565
  * 3: pane-guarded pane-died hook / user split exit).
566
+ * v6 — the agent-pane branch is now `session_attached`-aware: with a client
567
+ * attached it `detach-client`s (the interactive path, so runInTmux's
568
+ * attach returns and reads the exit status, EXEC-23b unchanged); with NO
569
+ * client it `kill-session`s outright. A `remain-on-exit` pane with no
570
+ * attach client to reap it otherwise lingers as a dead husk until the
571
+ * daemon's periodic `reapDeadTmuxPanes` sweep — so a run whose terminal
572
+ * was closed, or a `/exit` after the user detached, left an idle-looking
573
+ * dead session on the socket. Killing it the instant the agent exits is
574
+ * the "one close tears down every layer" contract (#5a).
405
575
  */
406
- export const AGENT_HOOK_SCHEMA = 5;
576
+ export const AGENT_HOOK_SCHEMA = 6;
407
577
  /** Per-session tmux user-option that records which AGENT_HOOK_SCHEMA a session's hook is at. */
408
578
  const HOOK_SCHEMA_OPTION = '@ag_hook_schema';
409
579
  /**
@@ -416,21 +586,28 @@ const HOOK_SCHEMA_OPTION = '@ag_hook_schema';
416
586
  */
417
587
  const TMUX_HOOK_REPAIR_TIMEOUT_MS = 5_000;
418
588
  /**
419
- * The guarded `pane-died` hook. Detach the client ONLY when the agent pane dies
420
- * (so the blocking attach in runInTmux returns and the exit status can be read);
421
- * a user split's death runs the else-branch, closing just that split. The
422
- * else-branch goes through `run-shell -b -C` with an explicit `-t #{hook_pane}`
423
- * target: tmux format-expands the command at fire time and executes it inside
424
- * the server command queue in the background, so the event pane is always the
425
- * one killed without launching a second tmux client against the same socket and
426
- * without stalling the hook on a loaded runner. A bare `kill-pane` relied on
427
- * the hook context supplying a "current pane", while an external self-client
428
- * could race the server under load. Single source of truth: both the
429
- * spawn-wrap (exec.ts) and the daemon reconcile build the hook here, so the
430
- * two can never drift.
589
+ * The guarded `pane-died` hook. When the AGENT pane dies, tear the whole session
590
+ * down but HOW depends on whether a client is attached:
591
+ * - attached (`#{session_attached}`): `detach-client` so the blocking attach in
592
+ * runInTmux returns and `resolveAfterAttach` reads the exit status off the
593
+ * still-`remain-on-exit` dead pane, then kills the session (EXEC-23b, unchanged);
594
+ * - no client: `kill-session` outright. Without this the dead pane sits under
595
+ * `remain-on-exit` as an idle-looking husk until the daemon's periodic
596
+ * `reapDeadTmuxPanes` sweep the "second exit" / orphaned-idle session bug
597
+ * when a wrapped agent exits with nobody attached (a closed terminal, or a
598
+ * `/exit` after the user detached). `detach-client` on an unattached session
599
+ * is a no-op, so the old hook left the husk; `kill-session` ends it at once.
600
+ * A user split's death still runs the else-branch, closing just that split via
601
+ * `run-shell -b -C` with an explicit `-t #{hook_pane}` target: tmux format-expands
602
+ * the command at fire time and executes it inside the server command queue in the
603
+ * background, so the event pane is always the one killed without launching a second
604
+ * tmux client against the same socket and without stalling the hook on a loaded
605
+ * runner. Single source of truth: both the spawn-wrap (exec.ts) and the daemon
606
+ * reconcile build the hook here, so the two can never drift.
431
607
  */
432
608
  export function agentPaneDiedHook(sessionName, agentPane) {
433
- return `if -F '#{==:#{hook_pane},${agentPane}}' 'detach-client -s =${sessionName}' 'run-shell -b -C "kill-pane -t #{hook_pane}"'`;
609
+ const agentPaneAction = `if -F '#{session_attached}' 'detach-client -s =${sessionName}' 'kill-session -t =${sessionName}'`;
610
+ return `if -F '#{==:#{hook_pane},${agentPane}}' "${agentPaneAction}" 'run-shell -b -C "kill-pane -t #{hook_pane}"'`;
434
611
  }
435
612
  /** Stamp a session's hook-schema marker to the current version. */
436
613
  export async function markSessionHookSchema(name, socket, timeoutMs) {
@@ -0,0 +1,29 @@
1
+ /**
2
+ * TracesBackend — token-source seam for `agents traces sync`.
3
+ *
4
+ * Managed path: signed in to Phoenix (`readSession() != null`), no BYO
5
+ * override. Token is the Phoenix access_token; endpoint is the live
6
+ * `traces.agents-cli.sh` Worker. Zero Cloudflare setup.
7
+ *
8
+ * BYO path (future): TRACES_BASE_URL + TRACES_WRITE_TOKEN env vars. Not
9
+ * exposed in the CLI for M1 — follow-on milestone, same pattern as
10
+ * `agents artifacts setup`.
11
+ */
12
+ export declare const DEFAULT_TRACES_DOMAIN = "traces.agents-cli.sh";
13
+ export interface TracesBackend {
14
+ /** Public base URL of the traces Worker, no trailing slash. */
15
+ baseUrl: string;
16
+ /** Bearer sent as `Authorization`. Phoenix access_token. */
17
+ token: string;
18
+ /** Phoenix userId — the R2 namespace prefix. */
19
+ userId: string;
20
+ }
21
+ /**
22
+ * Resolve the backend for the current machine. Throws when not signed in.
23
+ *
24
+ * BYO override env vars (for testing / self-hosted):
25
+ * AGENTS_TRACES_BASE_URL — Worker URL, no trailing slash
26
+ * AGENTS_TRACES_WRITE_TOKEN — static write token (bypasses Phoenix auth)
27
+ */
28
+ export declare function resolveTracesBackend(): TracesBackend;
29
+ export declare function managedTracesBaseUrl(): string;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * TracesBackend — token-source seam for `agents traces sync`.
3
+ *
4
+ * Managed path: signed in to Phoenix (`readSession() != null`), no BYO
5
+ * override. Token is the Phoenix access_token; endpoint is the live
6
+ * `traces.agents-cli.sh` Worker. Zero Cloudflare setup.
7
+ *
8
+ * BYO path (future): TRACES_BASE_URL + TRACES_WRITE_TOKEN env vars. Not
9
+ * exposed in the CLI for M1 — follow-on milestone, same pattern as
10
+ * `agents artifacts setup`.
11
+ */
12
+ import { readSession } from '../identity/client.js';
13
+ export const DEFAULT_TRACES_DOMAIN = 'traces.agents-cli.sh';
14
+ /**
15
+ * Resolve the backend for the current machine. Throws when not signed in.
16
+ *
17
+ * BYO override env vars (for testing / self-hosted):
18
+ * AGENTS_TRACES_BASE_URL — Worker URL, no trailing slash
19
+ * AGENTS_TRACES_WRITE_TOKEN — static write token (bypasses Phoenix auth)
20
+ */
21
+ export function resolveTracesBackend() {
22
+ const envBase = (process.env['AGENTS_TRACES_BASE_URL'] ?? '').replace(/\/+$/, '').trim();
23
+ const envToken = (process.env['AGENTS_TRACES_WRITE_TOKEN'] ?? '').trim();
24
+ if (envBase && envToken) {
25
+ return { baseUrl: envBase, token: envToken, userId: 'byo' };
26
+ }
27
+ const session = readSession();
28
+ if (!session) {
29
+ throw new Error("Not signed in. Run 'agents auth login' to sync traces to your Phoenix account.");
30
+ }
31
+ if (!session.access_token) {
32
+ throw new Error("Session has no access token. Run 'agents auth login' again.");
33
+ }
34
+ const userId = (session.userId ?? '').trim();
35
+ if (!userId) {
36
+ throw new Error("Signed in but the session has no user id. Run 'agents auth login' again.");
37
+ }
38
+ return {
39
+ baseUrl: managedTracesBaseUrl(),
40
+ token: session.access_token,
41
+ userId,
42
+ };
43
+ }
44
+ export function managedTracesBaseUrl() {
45
+ return `https://${DEFAULT_TRACES_DOMAIN}`;
46
+ }
@@ -0,0 +1,52 @@
1
+ export type TraceTopicGroup = 'code' | 'research' | 'review' | 'content' | 'ops';
2
+ export type TraceFailureCause = 'real' | 'guard' | 'hook';
3
+ /** Per-bucket aggregate stats for one day, stored in the rolling bucketHistory. */
4
+ export interface BucketStats {
5
+ key: string;
6
+ date: string;
7
+ count: number;
8
+ /** Tool-error count / total tool calls in this session set (0–1). */
9
+ errorRate: number;
10
+ /** Sessions with ≥1 stall friction signal / total sessions in bucket (0–1). */
11
+ stallRate: number;
12
+ }
13
+ /** Movement signal for one topic bucket compared to its 7-day rolling average. */
14
+ export interface DriftSignal {
15
+ bucket: string;
16
+ /** current errorRate − 7d average (positive = degrading). */
17
+ errorDelta: number;
18
+ /** current stallRate − 7d average (positive = degrading). */
19
+ stallDelta: number;
20
+ severity: 'degrading' | 'stable' | 'improving';
21
+ }
22
+ export interface TopicEvidence {
23
+ cwd?: string | null;
24
+ gitBranch?: string | null;
25
+ topic?: string | null;
26
+ label?: string | null;
27
+ toolMix?: Record<string, number>;
28
+ }
29
+ export interface ClassifiedTopic {
30
+ group: TraceTopicGroup;
31
+ key: string;
32
+ label: string;
33
+ }
34
+ export interface ToolCallFailure {
35
+ tool: string;
36
+ exit_code?: number | null;
37
+ status_code?: number | null;
38
+ error_code?: string | null;
39
+ error?: string | null;
40
+ parse_error?: string | null;
41
+ outcome?: string | null;
42
+ }
43
+ /** Classify a session from metadata and aggregate tool names; never reads transcript text. */
44
+ export declare function classifyTopic(input: TopicEvidence): ClassifiedTopic;
45
+ /** Bucket a failed tool call without inspecting raw tool input or transcript text. */
46
+ export declare function classifyCause(call: ToolCallFailure): TraceFailureCause;
47
+ /**
48
+ * Compare today's per-bucket stats to the last 7 days of history and return
49
+ * movement signals. Buckets with fewer than 3 historical days are skipped —
50
+ * not enough signal to distinguish noise from drift.
51
+ */
52
+ export declare function computeDriftSignal(history: BucketStats[][], today: BucketStats[]): DriftSignal[];
@@ -0,0 +1,72 @@
1
+ function normalizedEvidence(input) {
2
+ return [input.cwd, input.gitBranch, input.topic, input.label]
3
+ .filter((value) => typeof value === 'string')
4
+ .join(' ')
5
+ .toLowerCase();
6
+ }
7
+ /** Classify a session from metadata and aggregate tool names; never reads transcript text. */
8
+ export function classifyTopic(input) {
9
+ const text = normalizedEvidence(input);
10
+ const tools = Object.keys(input.toolMix ?? {}).map((tool) => tool.toLowerCase());
11
+ const hasTool = (pattern) => tools.some((tool) => pattern.test(tool));
12
+ if (/\b(review|audit|pr[-_/ ]?review|code[-_/ ]?review)\b/.test(text)
13
+ || hasTool(/review|comment/)) {
14
+ return { group: 'review', key: 'code-review', label: 'Code review' };
15
+ }
16
+ if (/\b(research|investigat|analysis|benchmark|paper|docs?)\b/.test(text)
17
+ || hasTool(/web_search|search_query|webfetch|browser/)) {
18
+ return { group: 'research', key: 'research', label: 'Research' };
19
+ }
20
+ if (/\b(blog|copy|content|post|article|script|caption)\b/.test(text)) {
21
+ return { group: 'content', key: 'content', label: 'Content' };
22
+ }
23
+ if (/\b(deploy|release|infra|ci|ops|fleet|daemon|provision)\b/.test(text)
24
+ || hasTool(/ssh|deploy|computer/)) {
25
+ return { group: 'ops', key: 'operations', label: 'Operations' };
26
+ }
27
+ if (/\b(test|fix|bug|feat|refactor|implement|code)\b/.test(text)
28
+ || hasTool(/edit|write|apply_patch|exec|bash|shell/)) {
29
+ return { group: 'code', key: 'engineering', label: 'Engineering' };
30
+ }
31
+ return { group: 'research', key: 'general', label: 'General' };
32
+ }
33
+ /** Bucket a failed tool call without inspecting raw tool input or transcript text. */
34
+ export function classifyCause(call) {
35
+ const evidence = [call.error_code, call.error, call.parse_error]
36
+ .filter((value) => typeof value === 'string')
37
+ .join(' ')
38
+ .toLowerCase();
39
+ if (/\b(?:git-guard|main-branch-guard)\b/.test(evidence))
40
+ return 'guard';
41
+ if (/permission\b.*\bdenied\b.*\b(?:auto[- ]mode|classifier)\b/.test(evidence)) {
42
+ return 'hook';
43
+ }
44
+ return 'real';
45
+ }
46
+ const DRIFT_THRESHOLD = 0.20;
47
+ /**
48
+ * Compare today's per-bucket stats to the last 7 days of history and return
49
+ * movement signals. Buckets with fewer than 3 historical days are skipped —
50
+ * not enough signal to distinguish noise from drift.
51
+ */
52
+ export function computeDriftSignal(history, today) {
53
+ const signals = [];
54
+ for (const stat of today) {
55
+ const pastStats = history
56
+ .flatMap((day) => day.filter((b) => b.key === stat.key))
57
+ .slice(-7);
58
+ if (pastStats.length < 3)
59
+ continue;
60
+ const avgError = pastStats.reduce((sum, b) => sum + b.errorRate, 0) / pastStats.length;
61
+ const avgStall = pastStats.reduce((sum, b) => sum + b.stallRate, 0) / pastStats.length;
62
+ const errorDelta = stat.errorRate - avgError;
63
+ const stallDelta = stat.stallRate - avgStall;
64
+ const severity = errorDelta > DRIFT_THRESHOLD || stallDelta > DRIFT_THRESHOLD
65
+ ? 'degrading'
66
+ : errorDelta < -DRIFT_THRESHOLD || stallDelta < -DRIFT_THRESHOLD
67
+ ? 'improving'
68
+ : 'stable';
69
+ signals.push({ bucket: stat.key, errorDelta, stallDelta, severity });
70
+ }
71
+ return signals.sort((a, b) => b.errorDelta - a.errorDelta);
72
+ }
@@ -0,0 +1,10 @@
1
+ /** Isolated Cloudflare resources for the private Phoenix trace store. */
2
+ export interface TracesConfig {
3
+ baseUrl: string;
4
+ accountId: string;
5
+ workerName: string;
6
+ bucketName: string;
7
+ domain?: string;
8
+ }
9
+ export declare const DEFAULT_WORKER_NAME = "agents-traces";
10
+ export declare const DEFAULT_BUCKET_NAME = "agents-traces";
@@ -0,0 +1,2 @@
1
+ export const DEFAULT_WORKER_NAME = 'agents-traces';
2
+ export const DEFAULT_BUCKET_NAME = 'agents-traces';
@@ -0,0 +1,19 @@
1
+ import { type CloudflareRequester } from '../share/provision.js';
2
+ interface ProvisionOptions {
3
+ request?: CloudflareRequester;
4
+ }
5
+ /** Bind the Phoenix identity base URL used to verify every trace read and write. */
6
+ export declare function setPhoenixIdBaseSecret(apiToken: string, accountId: string, workerName: string, phoenixIdBase: string, opts?: ProvisionOptions): Promise<void>;
7
+ export interface ProvisionTracesOptions extends ProvisionOptions {
8
+ apiToken: string;
9
+ accountId: string;
10
+ workerName: string;
11
+ bucketName: string;
12
+ phoenixIdBase: string;
13
+ domain?: string;
14
+ }
15
+ /** Provision the complete isolated traces deployment using the canonical Worker template. */
16
+ export declare function provisionTraces(opts: ProvisionTracesOptions): Promise<{
17
+ baseUrl: string;
18
+ }>;
19
+ export {};
@@ -0,0 +1,30 @@
1
+ // Cloudflare orchestration for the private agents-traces store. The generic
2
+ // request primitives live in share/provision; only the isolated traces resource
3
+ // choices and Worker template belong here.
4
+ import { randomBytes } from 'node:crypto';
5
+ import { addCustomDomain, createBucket, deployWorker, enableWorkersDev, findZoneId, putWorkerSecret, } from '../share/provision.js';
6
+ import { DEFAULT_TRACES_DOMAIN } from './backend.js';
7
+ import { renderTracesWorkerScript } from './worker-template.js';
8
+ /** Bind the Phoenix identity base URL used to verify every trace read and write. */
9
+ export async function setPhoenixIdBaseSecret(apiToken, accountId, workerName, phoenixIdBase, opts = {}) {
10
+ const normalized = phoenixIdBase.replace(/\/+$/, '').trim();
11
+ if (!normalized)
12
+ throw new Error('PHOENIX_ID_BASE is required to verify trace requests.');
13
+ await putWorkerSecret(apiToken, accountId, workerName, 'PHOENIX_ID_BASE', normalized, opts);
14
+ }
15
+ /** Provision the complete isolated traces deployment using the canonical Worker template. */
16
+ export async function provisionTraces(opts) {
17
+ const domain = opts.domain ?? DEFAULT_TRACES_DOMAIN;
18
+ const requestOpts = opts.request ? { request: opts.request } : {};
19
+ await createBucket(opts.apiToken, opts.accountId, opts.bucketName, requestOpts);
20
+ await deployWorker(opts.apiToken, opts.accountId, opts.workerName, renderTracesWorkerScript(), opts.bucketName, requestOpts);
21
+ await putWorkerSecret(opts.apiToken, opts.accountId, opts.workerName, 'WRITE_TOKEN', randomBytes(32).toString('hex'), requestOpts);
22
+ await setPhoenixIdBaseSecret(opts.apiToken, opts.accountId, opts.workerName, opts.phoenixIdBase, requestOpts);
23
+ const workersDevSubdomain = await enableWorkersDev(opts.apiToken, opts.accountId, opts.workerName, requestOpts);
24
+ const zoneId = await findZoneId(opts.apiToken, domain, requestOpts);
25
+ if (!zoneId) {
26
+ return { baseUrl: `https://${opts.workerName}.${workersDevSubdomain}.workers.dev` };
27
+ }
28
+ await addCustomDomain(opts.apiToken, opts.accountId, opts.workerName, zoneId, domain, requestOpts);
29
+ return { baseUrl: `https://${domain}` };
30
+ }