@ponythewhite/base-context 1.0.0 → 1.0.2

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 (285) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/NOTICE +0 -5
  3. package/README.md +44 -27
  4. package/dist/NOTICE +0 -5
  5. package/dist/base-context-runtime/pyproject.toml +1 -1
  6. package/dist/base-context-runtime/src/rlm/__init__.py +7 -5
  7. package/dist/base-context-runtime/test/test_subagent_registry.py +28 -1
  8. package/dist/base-context-runtime/uv.lock +1 -1
  9. package/dist/build-info.json +1 -1
  10. package/dist/bundle/amazon-bedrock.js +21 -0
  11. package/dist/bundle/{anthropic-YY4S534V.js → anthropic-UV5GZDGU.js} +5 -5
  12. package/dist/bundle/{azure-openai-responses-JRP6MIM3.js → azure-openai-responses-WSNPXLYC.js} +6 -6
  13. package/dist/bundle/{bundled-modules-P5HARVZA.js → bundled-modules-PWOGKUQQ.js} +71 -16
  14. package/dist/bundle/chunk-27QCJ2AZ.js +2578 -0
  15. package/dist/bundle/{chunk-BYVP7LWV.js → chunk-44PEHPOG.js} +1916 -4689
  16. package/dist/bundle/{chunk-MZHTNNVA.js → chunk-4GXPNXOJ.js} +8 -0
  17. package/dist/bundle/{chunk-SC5KG5R4.js → chunk-DGNX7HQ3.js} +1 -7
  18. package/dist/bundle/{chunk-A53O363M.js → chunk-GRJFXLBM.js} +0 -1
  19. package/dist/bundle/chunk-HKINIBP4.js +29 -0
  20. package/dist/bundle/{chunk-73DCPU3D.js → chunk-IADCPVEI.js} +1019 -3427
  21. package/dist/bundle/{chunk-UDSW6FNG.js → chunk-KTNETTHI.js} +1 -1
  22. package/dist/bundle/{chunk-U7AUVG3B.js → chunk-VSIYFWYI.js} +24 -6
  23. package/dist/bundle/{chunk-IW77OXFS.js → chunk-W46VZFI6.js} +0 -2099
  24. package/dist/bundle/{cli-main-O4X7GFGY.js → cli-main-4DS7Y3PI.js} +8 -6
  25. package/dist/bundle/cli.js +2 -2
  26. package/dist/bundle/{google-AUTVGDGZ.js → google-T32FMD4G.js} +6 -6
  27. package/dist/bundle/{google-vertex-Q75D7RKS.js → google-vertex-56KP6I3B.js} +3 -3
  28. package/dist/bundle/{main-XKOW2BV6.js → main-SV7JLVEB.js} +7 -5
  29. package/dist/bundle/{mistral-7ESB7QUD.js → mistral-WGBMYIVU.js} +5 -5
  30. package/dist/bundle/{openai-codex-responses-LV3QRPN4.js → openai-codex-responses-PIV2RQ3G.js} +6 -6
  31. package/dist/bundle/{openai-completions-OJECLPXJ.js → openai-completions-64C3JPR4.js} +11 -22
  32. package/dist/bundle/{openai-responses-FN6HZMBC.js → openai-responses-UI7RSTDV.js} +6 -6
  33. package/dist/cli/args.d.ts.map +1 -1
  34. package/dist/cli/args.js +108 -59
  35. package/dist/cli/args.js.map +1 -1
  36. package/dist/cli/daemon-ps.d.ts +4 -3
  37. package/dist/cli/daemon-ps.d.ts.map +1 -1
  38. package/dist/cli/daemon-ps.js +6 -1
  39. package/dist/cli/daemon-ps.js.map +1 -1
  40. package/dist/cli/product-doctor.d.ts +0 -1
  41. package/dist/cli/product-doctor.d.ts.map +1 -1
  42. package/dist/cli/product-doctor.js +6 -3
  43. package/dist/cli/product-doctor.js.map +1 -1
  44. package/dist/config.d.ts +0 -3
  45. package/dist/config.d.ts.map +1 -1
  46. package/dist/config.js +0 -8
  47. package/dist/config.js.map +1 -1
  48. package/dist/core/agent-session-config.d.ts +1 -2
  49. package/dist/core/agent-session-config.d.ts.map +1 -1
  50. package/dist/core/agent-session-config.js +0 -3
  51. package/dist/core/agent-session-config.js.map +1 -1
  52. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  53. package/dist/core/agent-session-runtime.js +5 -4
  54. package/dist/core/agent-session-runtime.js.map +1 -1
  55. package/dist/core/agent-session-services.d.ts +5 -3
  56. package/dist/core/agent-session-services.d.ts.map +1 -1
  57. package/dist/core/agent-session-services.js +2 -27
  58. package/dist/core/agent-session-services.js.map +1 -1
  59. package/dist/core/agent-session.d.ts +10 -6
  60. package/dist/core/agent-session.d.ts.map +1 -1
  61. package/dist/core/agent-session.js +107 -107
  62. package/dist/core/agent-session.js.map +1 -1
  63. package/dist/core/auth-storage.d.ts +8 -35
  64. package/dist/core/auth-storage.d.ts.map +1 -1
  65. package/dist/core/auth-storage.js +33 -243
  66. package/dist/core/auth-storage.js.map +1 -1
  67. package/dist/core/cron-jobs.d.ts.map +1 -1
  68. package/dist/core/cron-jobs.js +7 -1
  69. package/dist/core/cron-jobs.js.map +1 -1
  70. package/dist/core/inference-coordinator.d.ts +6 -1
  71. package/dist/core/inference-coordinator.d.ts.map +1 -1
  72. package/dist/core/inference-coordinator.js +18 -2
  73. package/dist/core/inference-coordinator.js.map +1 -1
  74. package/dist/core/kernel/bootstrap.d.ts.map +1 -1
  75. package/dist/core/kernel/bootstrap.js +2 -2
  76. package/dist/core/kernel/bootstrap.js.map +1 -1
  77. package/dist/core/kernel/repl-manager.d.ts +2 -0
  78. package/dist/core/kernel/repl-manager.d.ts.map +1 -1
  79. package/dist/core/kernel/repl-manager.js +44 -1
  80. package/dist/core/kernel/repl-manager.js.map +1 -1
  81. package/dist/core/logging.d.ts +4 -3
  82. package/dist/core/logging.d.ts.map +1 -1
  83. package/dist/core/logging.js +6 -6
  84. package/dist/core/logging.js.map +1 -1
  85. package/dist/core/mcp/mcp-manager.d.ts.map +1 -1
  86. package/dist/core/mcp/mcp-manager.js +3 -3
  87. package/dist/core/mcp/mcp-manager.js.map +1 -1
  88. package/dist/core/model-registry.d.ts +1 -16
  89. package/dist/core/model-registry.d.ts.map +1 -1
  90. package/dist/core/model-registry.js +88 -196
  91. package/dist/core/model-registry.js.map +1 -1
  92. package/dist/core/model-resolver.d.ts +2 -19
  93. package/dist/core/model-resolver.d.ts.map +1 -1
  94. package/dist/core/model-resolver.js +36 -266
  95. package/dist/core/model-resolver.js.map +1 -1
  96. package/dist/core/provider-contracts.d.ts +5 -6
  97. package/dist/core/provider-contracts.d.ts.map +1 -1
  98. package/dist/core/provider-contracts.js +25 -36
  99. package/dist/core/provider-contracts.js.map +1 -1
  100. package/dist/core/provider-display-names.d.ts.map +1 -1
  101. package/dist/core/provider-display-names.js +0 -2
  102. package/dist/core/provider-display-names.js.map +1 -1
  103. package/dist/core/rlm-max-subagents.d.ts +32 -0
  104. package/dist/core/rlm-max-subagents.d.ts.map +1 -0
  105. package/dist/core/rlm-max-subagents.js +50 -0
  106. package/dist/core/rlm-max-subagents.js.map +1 -0
  107. package/dist/core/rlm-runtime.d.ts +3 -3
  108. package/dist/core/rlm-runtime.d.ts.map +1 -1
  109. package/dist/core/rlm-runtime.js.map +1 -1
  110. package/dist/core/sdk.d.ts +4 -3
  111. package/dist/core/sdk.d.ts.map +1 -1
  112. package/dist/core/sdk.js +27 -14
  113. package/dist/core/sdk.js.map +1 -1
  114. package/dist/core/settings-manager.d.ts +3 -16
  115. package/dist/core/settings-manager.d.ts.map +1 -1
  116. package/dist/core/settings-manager.js +12 -44
  117. package/dist/core/settings-manager.js.map +1 -1
  118. package/dist/core/slash-commands.d.ts.map +1 -1
  119. package/dist/core/slash-commands.js +6 -6
  120. package/dist/core/slash-commands.js.map +1 -1
  121. package/dist/index.d.ts +1 -1
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/index.js.map +1 -1
  124. package/dist/installer.mjs +2 -3
  125. package/dist/main.d.ts.map +1 -1
  126. package/dist/main.js +39 -34
  127. package/dist/main.js.map +1 -1
  128. package/dist/modes/acp/acp-mode.d.ts.map +1 -1
  129. package/dist/modes/acp/acp-mode.js +1 -1
  130. package/dist/modes/acp/acp-mode.js.map +1 -1
  131. package/dist/modes/agent-connection/daemon-agent-connection.d.ts +6 -2
  132. package/dist/modes/agent-connection/daemon-agent-connection.d.ts.map +1 -1
  133. package/dist/modes/agent-connection/daemon-agent-connection.js +13 -2
  134. package/dist/modes/agent-connection/daemon-agent-connection.js.map +1 -1
  135. package/dist/modes/agent-connection/in-process-agent-connection.d.ts +2 -0
  136. package/dist/modes/agent-connection/in-process-agent-connection.d.ts.map +1 -1
  137. package/dist/modes/agent-connection/in-process-agent-connection.js +6 -0
  138. package/dist/modes/agent-connection/in-process-agent-connection.js.map +1 -1
  139. package/dist/modes/agent-connection/types.d.ts +3 -0
  140. package/dist/modes/agent-connection/types.d.ts.map +1 -1
  141. package/dist/modes/agent-connection/types.js.map +1 -1
  142. package/dist/modes/agents-view/agents-view-mode.d.ts.map +1 -1
  143. package/dist/modes/agents-view/agents-view-mode.js +2 -19
  144. package/dist/modes/agents-view/agents-view-mode.js.map +1 -1
  145. package/dist/modes/daemon/daemon-mode.d.ts +5 -0
  146. package/dist/modes/daemon/daemon-mode.d.ts.map +1 -1
  147. package/dist/modes/daemon/daemon-mode.js +107 -12
  148. package/dist/modes/daemon/daemon-mode.js.map +1 -1
  149. package/dist/modes/daemon/daemon-protocol.d.ts +194 -165
  150. package/dist/modes/daemon/daemon-protocol.d.ts.map +1 -1
  151. package/dist/modes/daemon/daemon-protocol.js +16 -11
  152. package/dist/modes/daemon/daemon-protocol.js.map +1 -1
  153. package/dist/modes/daemon/daemon-rlm-capacity-coordinator.d.ts +19 -0
  154. package/dist/modes/daemon/daemon-rlm-capacity-coordinator.d.ts.map +1 -0
  155. package/dist/modes/daemon/daemon-rlm-capacity-coordinator.js +74 -0
  156. package/dist/modes/daemon/daemon-rlm-capacity-coordinator.js.map +1 -0
  157. package/dist/modes/daemon/daemon-rlm-capacity.d.ts +19 -0
  158. package/dist/modes/daemon/daemon-rlm-capacity.d.ts.map +1 -0
  159. package/dist/modes/daemon/daemon-rlm-capacity.js +85 -0
  160. package/dist/modes/daemon/daemon-rlm-capacity.js.map +1 -0
  161. package/dist/modes/daemon/daemon-session-summarizer.d.ts +2 -3
  162. package/dist/modes/daemon/daemon-session-summarizer.d.ts.map +1 -1
  163. package/dist/modes/daemon/daemon-session-summarizer.js +32 -23
  164. package/dist/modes/daemon/daemon-session-summarizer.js.map +1 -1
  165. package/dist/modes/daemon/daemon-state-root.d.ts +15 -0
  166. package/dist/modes/daemon/daemon-state-root.d.ts.map +1 -0
  167. package/dist/modes/daemon/daemon-state-root.js +40 -0
  168. package/dist/modes/daemon/daemon-state-root.js.map +1 -0
  169. package/dist/modes/daemon/daemon-supervisor-ownership.d.ts +2 -0
  170. package/dist/modes/daemon/daemon-supervisor-ownership.d.ts.map +1 -1
  171. package/dist/modes/daemon/daemon-supervisor-ownership.js +27 -0
  172. package/dist/modes/daemon/daemon-supervisor-ownership.js.map +1 -1
  173. package/dist/modes/daemon/daemon-supervisor.d.ts +10 -1
  174. package/dist/modes/daemon/daemon-supervisor.d.ts.map +1 -1
  175. package/dist/modes/daemon/daemon-supervisor.js +213 -20
  176. package/dist/modes/daemon/daemon-supervisor.js.map +1 -1
  177. package/dist/modes/daemon/daemon-worker-protocol.d.ts +28 -1
  178. package/dist/modes/daemon/daemon-worker-protocol.d.ts.map +1 -1
  179. package/dist/modes/daemon/daemon-worker-protocol.js +16 -2
  180. package/dist/modes/daemon/daemon-worker-protocol.js.map +1 -1
  181. package/dist/modes/daemon/saved-session-catalog.d.ts.map +1 -1
  182. package/dist/modes/daemon/saved-session-catalog.js +2 -0
  183. package/dist/modes/daemon/saved-session-catalog.js.map +1 -1
  184. package/dist/modes/interactive/auth-flows.d.ts +0 -6
  185. package/dist/modes/interactive/auth-flows.d.ts.map +1 -1
  186. package/dist/modes/interactive/auth-flows.js +6 -126
  187. package/dist/modes/interactive/auth-flows.js.map +1 -1
  188. package/dist/modes/interactive/components/configuration-menu.d.ts +3 -0
  189. package/dist/modes/interactive/components/configuration-menu.d.ts.map +1 -1
  190. package/dist/modes/interactive/components/configuration-menu.js +13 -2
  191. package/dist/modes/interactive/components/configuration-menu.js.map +1 -1
  192. package/dist/modes/interactive/components/footer.d.ts +1 -1
  193. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  194. package/dist/modes/interactive/components/footer.js +2 -2
  195. package/dist/modes/interactive/components/footer.js.map +1 -1
  196. package/dist/modes/interactive/components/index.d.ts +0 -1
  197. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  198. package/dist/modes/interactive/components/index.js +0 -1
  199. package/dist/modes/interactive/components/index.js.map +1 -1
  200. package/dist/modes/interactive/components/login-dialog.d.ts +0 -1
  201. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  202. package/dist/modes/interactive/components/login-dialog.js +3 -40
  203. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  204. package/dist/modes/interactive/components/model-selector.d.ts +2 -0
  205. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  206. package/dist/modes/interactive/components/model-selector.js +13 -2
  207. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  208. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  209. package/dist/modes/interactive/components/oauth-selector.js +2 -11
  210. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  211. package/dist/modes/interactive/feature-hints.d.ts.map +1 -1
  212. package/dist/modes/interactive/feature-hints.js +0 -4
  213. package/dist/modes/interactive/feature-hints.js.map +1 -1
  214. package/dist/modes/interactive/interactive-mode.d.ts +3 -12
  215. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  216. package/dist/modes/interactive/interactive-mode.js +81 -507
  217. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  218. package/dist/modes/interactive/onboarding.d.ts +0 -3
  219. package/dist/modes/interactive/onboarding.d.ts.map +1 -1
  220. package/dist/modes/interactive/onboarding.js +0 -17
  221. package/dist/modes/interactive/onboarding.js.map +1 -1
  222. package/dist/modes/rpc/rpc-client.d.ts +4 -1
  223. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  224. package/dist/modes/rpc/rpc-client.js +82 -42
  225. package/dist/modes/rpc/rpc-client.js.map +1 -1
  226. package/dist/node/amazon-bedrock.d.ts +2 -0
  227. package/dist/node/amazon-bedrock.d.ts.map +1 -0
  228. package/dist/node/amazon-bedrock.js +6 -0
  229. package/dist/node/amazon-bedrock.js.map +1 -0
  230. package/dist/product-identity.d.ts +0 -1
  231. package/dist/product-identity.d.ts.map +1 -1
  232. package/dist/product-identity.js +0 -1
  233. package/dist/product-identity.js.map +1 -1
  234. package/docs/development.md +1 -1
  235. package/docs/fork-philosophy.md +4 -8
  236. package/docs/installation.md +83 -62
  237. package/docs/providers.md +23 -25
  238. package/docs/quickstart.md +19 -10
  239. package/docs/rlm-runtime.md +13 -5
  240. package/docs/rpc.md +4 -4
  241. package/docs/sdk.md +20 -11
  242. package/docs/sessions.md +0 -1
  243. package/docs/settings.md +9 -42
  244. package/docs/skills.md +0 -1
  245. package/docs/upstream-0.9.5.md +171 -0
  246. package/docs/usage.md +14 -10
  247. package/package.json +4 -4
  248. package/dist/core/agent-traces.d.ts +0 -149
  249. package/dist/core/agent-traces.d.ts.map +0 -1
  250. package/dist/core/agent-traces.js +0 -904
  251. package/dist/core/agent-traces.js.map +0 -1
  252. package/dist/core/prime-inference-auth.d.ts +0 -70
  253. package/dist/core/prime-inference-auth.d.ts.map +0 -1
  254. package/dist/core/prime-inference-auth.js +0 -352
  255. package/dist/core/prime-inference-auth.js.map +0 -1
  256. package/dist/core/prime-inference-model-selection.d.ts +0 -16
  257. package/dist/core/prime-inference-model-selection.d.ts.map +0 -1
  258. package/dist/core/prime-inference-model-selection.js +0 -16
  259. package/dist/core/prime-inference-model-selection.js.map +0 -1
  260. package/dist/core/prime-inference-models.d.ts +0 -6
  261. package/dist/core/prime-inference-models.d.ts.map +0 -1
  262. package/dist/core/prime-inference-models.js +0 -62
  263. package/dist/core/prime-inference-models.js.map +0 -1
  264. package/dist/core/telemetry.d.ts +0 -95
  265. package/dist/core/telemetry.d.ts.map +0 -1
  266. package/dist/core/telemetry.js +0 -637
  267. package/dist/core/telemetry.js.map +0 -1
  268. package/dist/modes/interactive/components/prime-onboarding-splash.d.ts +0 -39
  269. package/dist/modes/interactive/components/prime-onboarding-splash.d.ts.map +0 -1
  270. package/dist/modes/interactive/components/prime-onboarding-splash.js +0 -286
  271. package/dist/modes/interactive/components/prime-onboarding-splash.js.map +0 -1
  272. package/dist/modes/interactive/components/prime-team-selector.d.ts +0 -30
  273. package/dist/modes/interactive/components/prime-team-selector.d.ts.map +0 -1
  274. package/dist/modes/interactive/components/prime-team-selector.js +0 -166
  275. package/dist/modes/interactive/components/prime-team-selector.js.map +0 -1
  276. package/dist/skills/prime-intellect/SKILL.md +0 -87
  277. package/dist/skills/prime-intellect/references/compute.md +0 -45
  278. package/dist/skills/prime-intellect/references/environments.md +0 -73
  279. package/dist/skills/prime-intellect/references/inference.md +0 -47
  280. package/dist/skills/prime-intellect/references/sandboxes.md +0 -75
  281. package/skills/prime-intellect/SKILL.md +0 -87
  282. package/skills/prime-intellect/references/compute.md +0 -45
  283. package/skills/prime-intellect/references/environments.md +0 -73
  284. package/skills/prime-intellect/references/inference.md +0 -47
  285. package/skills/prime-intellect/references/sandboxes.md +0 -75
package/docs/usage.md CHANGED
@@ -42,13 +42,13 @@ Type `/` in the editor to open command completion. Extensions can register custo
42
42
  | `/login`, `/logout` | Manage OAuth or API-key credentials |
43
43
  | `/model` | Switch models |
44
44
  | `/effort` | Set the reasoning/thinking level |
45
+ | `/agents [N]` | Show or save the maximum live subagents (default: 4) |
45
46
  | `/scoped-models` | Enable/disable models for Ctrl+P cycling |
46
47
  | `/settings` | Thinking level, theme, message delivery, transport |
47
48
  | `/resume [id\|path]` | Open the agents view, or resume a session directly |
48
49
  | `/new` | Start a new session |
49
50
  | `/name <name>` | Set session display name |
50
51
  | `/session` | Show session file, ID, and message counts |
51
- | `/traces [status\|on\|off\|preview\|upload-current\|upload-all\|login]` | Preview, upload, or manage opt-in trace sharing |
52
52
  | `/usage`, `/context` | Show the parent and subagent context, token, and cost breakdown |
53
53
  | `/tree` | Jump to any point in the session and continue from there |
54
54
  | `/fork` | Create a new session from a previous user message |
@@ -58,12 +58,21 @@ Type `/` in the editor to open command completion. Extensions can register custo
58
58
  | `/copy` | Copy last assistant message to clipboard |
59
59
  | `/btw <question>`, `/side <question>` | Ask an inline side question without adding it to the session; replies continue the side conversation, esc returns |
60
60
  | `/export [file]` | Export session to HTML |
61
- | `/share` | Upload as private GitHub gist with shareable HTML link |
62
61
  | `/reload` | Reload keybindings, extensions, skills, prompts, and context files |
63
62
  | `/hotkeys` | Show all keyboard shortcuts |
64
63
  | `/changelog` | Display version history |
65
64
  | `/quit` | Quit Base Context |
66
65
 
66
+ ### Limit concurrent subagents
67
+
68
+ `/agents` reads the current configured limit from the active root-agent family and displays exactly `The current maximum number of concurrent subagents is {N}.`, with `{N}` replaced by that current value, not a hardcoded default.
69
+
70
+ `/agents 6` sets the limit to 6 for the current root agent and all its descendants. The main agent does not count. Running and idle subagents count; inactive saved sessions do not. A fresh installation defaults to **4** live subagents.
71
+
72
+ The value is saved as the global `rlmMaxSubagents` preference, so it survives restarts and supplies the limit for later sessions. Use a non-negative safe integer. `/agents 0` disables new subagent spawns. Malformed, fractional, negative, and unsafe integer values are rejected without changing the setting.
73
+
74
+ Lowering the limit never kills or passivates existing agents. They keep running or remain idle. Only new spawns/admissions are blocked until the live count is below the limit. This slash command is separate from `base-context agents`, which lists agents.
75
+
67
76
  ## Message Queue
68
77
 
69
78
  You can submit messages while the agent is still working:
@@ -165,11 +174,9 @@ Replace the default system prompt with:
165
174
 
166
175
  Append to the default prompt without replacing it with `APPEND_SYSTEM.md` in either location.
167
176
 
168
- ## Exporting and Sharing Sessions
169
-
170
- Use `/export [file]` to write a session to HTML.
177
+ ## Exporting Sessions
171
178
 
172
- Use `/share` to upload a private GitHub gist with a shareable HTML link.
179
+ Use `/export [file]` to write a session to a local HTML file.
173
180
 
174
181
  ## CLI Reference
175
182
 
@@ -373,15 +380,12 @@ base-context --tools ipython -p "Review the code"
373
380
  | `BASE_CONTEXT_SKIP_VERSION_CHECK` | Skip the Base Context version lookup at startup |
374
381
  | `BASE_CONTEXT_DOWNLOAD_BASE_URL` | Explicit custom static update-manifest base; leave unset for owned npm updates. A GitHub repository URL is not that manifest endpoint; see [update settings](settings.md#update-checks) |
375
382
  | `BASE_CONTEXT_CACHE_RETENTION` | Set to `long` for extended prompt cache where the provider supports it |
376
- | `PRIME_API_KEY` | Prime Inference API key; not a trace-sharing credential |
377
- | `BASE_CONTEXT_TRACES_API_KEY` | Dedicated key for explicitly configured, opt-in Base Context trace sharing |
378
- | `BASE_CONTEXT_TRACES_BASE_URL` | Explicit trace upload API base; no upstream destination is selected by default |
379
383
  | `BASE_CONTEXT_KERNEL_PYTHON` | Explicit Python executable with a current `base-context-runtime` for a manual runtime route |
380
384
  | `BASE_CONTEXT_KERNEL_VENV` | Explicit manual kernel environment directory; owned preparation still creates its own fresh release-local environment |
381
385
  | `BASE_CONTEXT_INSTALL_UV` | Set to `1` to allow uv installation for kernel bootstrap |
382
386
  | `VISUAL`, `EDITOR` | External editor for Ctrl+G |
383
387
 
384
- `BASE_CONTEXT_HOME`, `BASE_CONTEXT_SESSION_DIR` and kernel path overrides require non-empty absolute paths (`~/` is supported). The project configuration directory is `.base-context/`; changing the product HOME does not rename it. These product settings do not redirect into old Prime state. `PRIME_API_KEY` remains a provider name, not a product-prefix alias. The Python import stays `rlm`, but an upstream `prime-agent-runtime` environment is not the Base Context runtime. Non-owned default bootstrap uses `~/.base-context/runtime`; the owned installer prepares each release's runtime before activation.
388
+ `BASE_CONTEXT_HOME`, `BASE_CONTEXT_SESSION_DIR` and kernel path overrides require non-empty absolute paths (`~/` is supported). The project configuration directory is `.base-context/`; changing the product HOME does not rename it. These product settings do not redirect into old Prime state. Provider variables such as `OPENAI_API_KEY` keep their provider names; they are not product-prefix aliases. The Python import stays `rlm`, but an upstream `prime-agent-runtime` environment is not the Base Context runtime. Non-owned default bootstrap uses `~/.base-context/runtime`; the owned installer prepares each release's runtime before activation.
385
389
 
386
390
  ## Design Principles
387
391
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ponythewhite/base-context",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Synerise base-context: a coding and research agent with durable context and a persistent Python REPL",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42,9 +42,9 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@agentclientprotocol/sdk": "^1.3.0",
45
- "@ponythewhite/base-context-agent": "1.0.0",
46
- "@ponythewhite/base-context-ai": "1.0.0",
47
- "@ponythewhite/base-context-tui": "1.0.0",
45
+ "@ponythewhite/base-context-agent": "1.0.2",
46
+ "@ponythewhite/base-context-ai": "1.0.2",
47
+ "@ponythewhite/base-context-tui": "1.0.2",
48
48
  "@silvia-odwyer/photon-node": "^0.3.4",
49
49
  "chalk": "^5.5.0",
50
50
  "cli-highlight": "^2.1.11",
@@ -1,149 +0,0 @@
1
- import type { AuthStorage } from "./auth-storage.js";
2
- import { type SessionHeader, type SessionManager } from "./session-manager.js";
3
- import type { SettingsManager } from "./settings-manager.js";
4
- export type AgentTraceCredentialSource = "environment" | "stored";
5
- export interface AgentTraceCredential {
6
- apiKey: string;
7
- source: AgentTraceCredentialSource;
8
- label: string;
9
- }
10
- export type AgentTraceUploadResult = {
11
- status: "uploaded";
12
- sessionId: string;
13
- traceId: string;
14
- bytesStored: number;
15
- key?: string;
16
- } | {
17
- status: "disabled";
18
- } | {
19
- status: "unchanged";
20
- } | {
21
- status: "missing_credentials";
22
- } | {
23
- status: "no_session_file";
24
- } | {
25
- status: "empty_session";
26
- } | {
27
- status: "invalid_session";
28
- message: string;
29
- } | {
30
- status: "too_large";
31
- size: number;
32
- maxBytes: number;
33
- } | {
34
- status: "failed";
35
- statusCode?: number;
36
- message: string;
37
- retryAfterMs?: number;
38
- };
39
- export interface AgentTraceUploadOptions {
40
- sessionFile: string | undefined;
41
- authStorage: AuthStorage;
42
- settingsManager: SettingsManager;
43
- /** Require the global automatic-sharing opt-in. Set false only for an explicit one-shot upload command. */
44
- requireEnabled?: boolean;
45
- baseUrl?: string;
46
- configPath?: string;
47
- fetchFn?: typeof fetch;
48
- reloadConfig?: boolean;
49
- requestTimeoutMs?: number;
50
- signal?: AbortSignal;
51
- }
52
- export interface AgentTraceSessionUploadOptions extends Omit<AgentTraceUploadOptions, "sessionFile"> {
53
- sessionManager: SessionManager;
54
- }
55
- export interface AgentTraceUploadInstallOptions {
56
- authStorage: AuthStorage;
57
- settingsManager: SettingsManager;
58
- baseUrl?: string;
59
- configPath?: string;
60
- fetchFn?: typeof fetch;
61
- requestTimeoutMs?: number;
62
- /** The session's semantic-edge ledger; registered with the outbox as its own delivery kind. */
63
- semanticEdgesLedgerPath?: string;
64
- }
65
- export type AgentTracePreviewResult = {
66
- status: "ready";
67
- sessionFile: string;
68
- sessionId: string;
69
- traceId: string;
70
- parentSessionId?: string;
71
- cwd: string;
72
- size: number;
73
- maxBytes: number;
74
- uploadable: boolean;
75
- endpoint?: string;
76
- gitRepo?: string;
77
- gitCommit?: string;
78
- contentPreview: string;
79
- truncated: boolean;
80
- } | {
81
- status: "no_session_file";
82
- } | {
83
- status: "empty_session";
84
- } | {
85
- status: "invalid_session";
86
- message: string;
87
- } | {
88
- status: "failed";
89
- message: string;
90
- };
91
- export interface AgentTracePreviewOptions {
92
- sessionFile: string | undefined;
93
- baseUrl?: string;
94
- maxContentChars?: number;
95
- }
96
- export interface AgentTraceUploadAllProgress {
97
- completed: number;
98
- total: number;
99
- sessionFile?: string;
100
- result?: AgentTraceUploadResult;
101
- }
102
- export interface AgentTraceUploadAllOptions extends Omit<AgentTraceUploadOptions, "sessionFile"> {
103
- sessionDir?: string;
104
- concurrency?: number;
105
- onProgress?: (progress: AgentTraceUploadAllProgress) => void;
106
- }
107
- export interface AgentTraceUploadAllResult {
108
- total: number;
109
- uploaded: number;
110
- failed: number;
111
- skipped: number;
112
- bytesStored: number;
113
- results: Array<{
114
- sessionFile: string;
115
- result: AgentTraceUploadResult;
116
- }>;
117
- }
118
- /** Active-branch git for the indexing headers: walk leaf to root, not the last git_state in
119
- * file order (which may belong to a sibling branch). */
120
- export declare function activeGitContext(body: string, header: SessionHeader): {
121
- repoUrl?: string;
122
- commit?: string;
123
- } | undefined;
124
- export declare function previewAgentTraceFile(options: AgentTracePreviewOptions): Promise<AgentTracePreviewResult>;
125
- export declare function findAgentTraceFiles(sessionDir?: string): Promise<string[]>;
126
- export declare function uploadAllAgentTraces(options: AgentTraceUploadAllOptions): Promise<AgentTraceUploadAllResult>;
127
- export declare const SEMANTIC_EDGES_OUTBOX_KIND = "semantic-edges";
128
- export interface AgentTraceCatchUpResult {
129
- pruned: number;
130
- /** Registered semantic-edge ledgers with bytes beyond their cursor; no delivery endpoint exists yet. */
131
- semanticEdgeLedgersPending: number;
132
- results: Array<{
133
- sessionFile: string;
134
- result: AgentTraceUploadResult;
135
- }>;
136
- }
137
- /**
138
- * Explicit catch-up for the Base Context export outbox. This is never run at
139
- * startup and does not read inherited agent-traces-outbox entries.
140
- */
141
- export declare function catchUpAgentTraceUploads(options: Omit<AgentTraceUploadOptions, "sessionFile">): Promise<AgentTraceCatchUpResult>;
142
- export declare function getPrimeAgentTraceCredential(authStorage: AuthStorage, options?: {
143
- reloadAuth?: boolean;
144
- configPath?: string;
145
- }): Promise<AgentTraceCredential | undefined>;
146
- export declare function uploadAgentTraceFile(options: AgentTraceUploadOptions): Promise<AgentTraceUploadResult>;
147
- export declare function uploadAgentTraceSession(options: AgentTraceSessionUploadOptions): Promise<AgentTraceUploadResult>;
148
- export declare function installAgentTraceUpload(sessionManager: SessionManager, options: AgentTraceUploadInstallOptions): void;
149
- //# sourceMappingURL=agent-traces.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"agent-traces.d.ts","sourceRoot":"","sources":["../../src/core/agent-traces.ts"],"names":[],"mappings":"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","sourcesContent":["import { Buffer } from \"node:buffer\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { type Dirent, existsSync, mkdirSync, renameSync, writeFileSync } from \"node:fs\";\nimport { mkdir, readdir, readFile, rename, stat, unlink, writeFile } from \"node:fs/promises\";\nimport { dirname, isAbsolute, join, resolve } from \"node:path\";\nimport { appendRotatingLog, getAgentDir, getAgentTracesLogPath, getSessionsDir, VERSION } from \"../config.js\";\nimport { assertProductStatePath } from \"../runtime-paths.js\";\nimport type { AuthStorage } from \"./auth-storage.js\";\nimport { BASE_CONTEXT_TRACES_PROVIDER_ID, resolvePrimeAgentTracesBaseUrl } from \"./prime-inference-auth.js\";\nimport { readSessionJournal, readSessionJournalHeader } from \"./session-journal-reader.js\";\nimport { getSessionArtifactsRoot, type SessionHeader, type SessionManager } from \"./session-manager.js\";\nimport type { SettingsManager } from \"./settings-manager.js\";\n\nconst MAX_TRACE_BYTES = 20 * 1024 * 1024;\nconst DEFAULT_REQUEST_TIMEOUT_MS = 15_000;\nconst TRACE_UPLOAD_DEBOUNCE_MS = 1_000;\nconst TRACE_UPLOAD_MIN_INTERVAL_MS = 60_000;\nconst TRACE_UPLOAD_RETRY_BASE_DELAY_MS = 500;\nconst TRACE_UPLOAD_RETRY_MAX_DELAY_MS = 10_000;\nconst TRACE_UPLOAD_MAX_RETRIES = 3;\nconst TRACE_UPLOAD_RETRY_JITTER = 0.2;\nconst TRACE_PREVIEW_MAX_CHARS = 8_000;\nconst TRACE_UPLOAD_ALL_CONCURRENCY = 4;\nconst TRACE_UPLOAD_RATE_LIMIT_REQUESTS = 5;\nconst TRACE_UPLOAD_RATE_LIMIT_WINDOW_MS = 60_000;\nconst TRACE_UPLOAD_RATE_LIMIT_SAFETY_MS = 100;\nconst MAX_TIMER_DELAY_MS = 2 ** 31 - 1;\nconst TRACE_UPLOAD_ALL_MIN_REQUEST_INTERVAL_MS =\n\tMath.ceil(TRACE_UPLOAD_RATE_LIMIT_WINDOW_MS / TRACE_UPLOAD_RATE_LIMIT_REQUESTS) + TRACE_UPLOAD_RATE_LIMIT_SAFETY_MS;\n\nexport type AgentTraceCredentialSource = \"environment\" | \"stored\";\n\nexport interface AgentTraceCredential {\n\tapiKey: string;\n\tsource: AgentTraceCredentialSource;\n\tlabel: string;\n}\n\nexport type AgentTraceUploadResult =\n\t| {\n\t\t\tstatus: \"uploaded\";\n\t\t\tsessionId: string;\n\t\t\ttraceId: string;\n\t\t\tbytesStored: number;\n\t\t\tkey?: string;\n\t }\n\t| { status: \"disabled\" }\n\t| { status: \"unchanged\" }\n\t| { status: \"missing_credentials\" }\n\t| { status: \"no_session_file\" }\n\t| { status: \"empty_session\" }\n\t| { status: \"invalid_session\"; message: string }\n\t| { status: \"too_large\"; size: number; maxBytes: number }\n\t| { status: \"failed\"; statusCode?: number; message: string; retryAfterMs?: number };\n\nexport interface AgentTraceUploadOptions {\n\tsessionFile: string | undefined;\n\tauthStorage: AuthStorage;\n\tsettingsManager: SettingsManager;\n\t/** Require the global automatic-sharing opt-in. Set false only for an explicit one-shot upload command. */\n\trequireEnabled?: boolean;\n\tbaseUrl?: string;\n\tconfigPath?: string;\n\tfetchFn?: typeof fetch;\n\treloadConfig?: boolean;\n\trequestTimeoutMs?: number;\n\tsignal?: AbortSignal;\n}\n\nexport interface AgentTraceSessionUploadOptions extends Omit<AgentTraceUploadOptions, \"sessionFile\"> {\n\tsessionManager: SessionManager;\n}\n\nexport interface AgentTraceUploadInstallOptions {\n\tauthStorage: AuthStorage;\n\tsettingsManager: SettingsManager;\n\tbaseUrl?: string;\n\tconfigPath?: string;\n\tfetchFn?: typeof fetch;\n\trequestTimeoutMs?: number;\n\t/** The session's semantic-edge ledger; registered with the outbox as its own delivery kind. */\n\tsemanticEdgesLedgerPath?: string;\n}\n\nexport type AgentTracePreviewResult =\n\t| {\n\t\t\tstatus: \"ready\";\n\t\t\tsessionFile: string;\n\t\t\tsessionId: string;\n\t\t\ttraceId: string;\n\t\t\tparentSessionId?: string;\n\t\t\tcwd: string;\n\t\t\tsize: number;\n\t\t\tmaxBytes: number;\n\t\t\tuploadable: boolean;\n\t\t\tendpoint?: string;\n\t\t\tgitRepo?: string;\n\t\t\tgitCommit?: string;\n\t\t\tcontentPreview: string;\n\t\t\ttruncated: boolean;\n\t }\n\t| { status: \"no_session_file\" }\n\t| { status: \"empty_session\" }\n\t| { status: \"invalid_session\"; message: string }\n\t| { status: \"failed\"; message: string };\n\nexport interface AgentTracePreviewOptions {\n\tsessionFile: string | undefined;\n\tbaseUrl?: string;\n\tmaxContentChars?: number;\n}\n\nexport interface AgentTraceUploadAllProgress {\n\tcompleted: number;\n\ttotal: number;\n\tsessionFile?: string;\n\tresult?: AgentTraceUploadResult;\n}\n\nexport interface AgentTraceUploadAllOptions extends Omit<AgentTraceUploadOptions, \"sessionFile\"> {\n\tsessionDir?: string;\n\tconcurrency?: number;\n\tonProgress?: (progress: AgentTraceUploadAllProgress) => void;\n}\n\nexport interface AgentTraceUploadAllResult {\n\ttotal: number;\n\tuploaded: number;\n\tfailed: number;\n\tskipped: number;\n\tbytesStored: number;\n\tresults: Array<{ sessionFile: string; result: AgentTraceUploadResult }>;\n}\n\nfunction stringEnv(name: string): string | undefined {\n\tconst value = process.env[name];\n\treturn typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction describeError(error: unknown): string {\n\tif (!(error instanceof Error)) {\n\t\treturn String(error);\n\t}\n\tconst cause = (error as { cause?: unknown }).cause;\n\tif (isRecord(cause)) {\n\t\tconst code = typeof cause.code === \"string\" ? cause.code : undefined;\n\t\tconst causeMessage = typeof cause.message === \"string\" ? cause.message : undefined;\n\t\tconst detail = code ?? causeMessage;\n\t\tif (detail && detail !== error.message) {\n\t\t\treturn `${error.message} (${detail})`;\n\t\t}\n\t}\n\treturn error.message;\n}\n\nclass TraceUploadTimeoutError extends Error {\n\tconstructor(timeoutMs: number) {\n\t\tsuper(`Trace upload timed out after ${timeoutMs}ms`);\n\t\tthis.name = \"TraceUploadTimeoutError\";\n\t}\n}\n\nconst RETRIABLE_NETWORK_CODES = new Set([\n\t\"ECONNRESET\",\n\t\"ECONNREFUSED\",\n\t\"ECONNABORTED\",\n\t\"EPIPE\",\n\t\"ETIMEDOUT\",\n\t\"ENETUNREACH\",\n\t\"ENETDOWN\",\n\t\"EAI_AGAIN\",\n\t\"UND_ERR_SOCKET\",\n\t\"UND_ERR_CONNECT_TIMEOUT\",\n]);\n\n// 429 is deliberately absent: a rate-limited upload is rescheduled by its caller instead of sleeping in-request.\nconst RETRIABLE_HTTP_STATUSES = new Set([408, 425, 500, 502, 503, 504]);\n\nfunction isRetriableNetworkError(error: unknown): boolean {\n\tif (error instanceof TraceUploadTimeoutError) {\n\t\treturn true;\n\t}\n\tif (!(error instanceof Error) || error.name === \"AbortError\") {\n\t\treturn false;\n\t}\n\tconst cause = (error as { cause?: unknown }).cause;\n\treturn isRecord(cause) && typeof cause.code === \"string\" && RETRIABLE_NETWORK_CODES.has(cause.code);\n}\n\nfunction stringField(data: Record<string, unknown>, key: string): string | undefined {\n\tconst value = data[key];\n\treturn typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction numberField(data: Record<string, unknown>, key: string): number | undefined {\n\tconst value = data[key];\n\treturn typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\nfunction isSessionHeader(value: unknown): value is SessionHeader {\n\treturn (\n\t\tisRecord(value) &&\n\t\tvalue.type === \"session\" &&\n\t\ttypeof value.id === \"string\" &&\n\t\ttypeof value.timestamp === \"string\" &&\n\t\ttypeof value.cwd === \"string\" &&\n\t\t(value.parentSession === undefined || typeof value.parentSession === \"string\")\n\t);\n}\n\nfunction readSessionHeader(sessionFile: string): SessionHeader | undefined {\n\ttry {\n\t\tconst parsed = readSessionJournalHeader(sessionFile);\n\t\treturn isSessionHeader(parsed) ? parsed : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nasync function readSessionTraceBody(sessionFile: string): Promise<string> {\n\tconst lines: string[] = [];\n\tfor await (const { json } of readSessionJournal(sessionFile)) {\n\t\tlines.push(`${json}\\n`);\n\t}\n\treturn lines.join(\"\");\n}\n\n/** Active-branch git for the indexing headers: walk leaf to root, not the last git_state in\n * file order (which may belong to a sibling branch). */\nexport function activeGitContext(\n\tbody: string,\n\theader: SessionHeader,\n): { repoUrl?: string; commit?: string } | undefined {\n\tconst byId = new Map<string, { parentId: string | null; type: string; git?: unknown }>();\n\tlet leafId: string | null = null;\n\tfor (const line of body.split(\"\\n\")) {\n\t\tif (!line.trim()) continue;\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(line);\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (!isRecord(parsed) || parsed.type === \"session\" || typeof parsed.id !== \"string\") continue;\n\t\tbyId.set(parsed.id, {\n\t\t\tparentId: typeof parsed.parentId === \"string\" ? parsed.parentId : null,\n\t\t\ttype: typeof parsed.type === \"string\" ? parsed.type : \"\",\n\t\t\tgit: parsed.git,\n\t\t});\n\t\tleafId = parsed.id;\n\t}\n\n\tlet current = leafId ? byId.get(leafId) : undefined;\n\tfor (let depth = 0; current && depth < byId.size + 1; depth += 1) {\n\t\tif (current.type === \"git_state\" && isRecord(current.git)) {\n\t\t\treturn current.git as { repoUrl?: string; commit?: string };\n\t\t}\n\t\tcurrent = current.parentId ? byId.get(current.parentId) : undefined;\n\t}\n\treturn header.git;\n}\n\nfunction resolveParentSessionPath(sessionFile: string, parentSession: string): string {\n\treturn isAbsolute(parentSession) ? parentSession : resolve(dirname(sessionFile), parentSession);\n}\n\nfunction resolveTraceContext(\n\tsessionFile: string,\n\theader: SessionHeader,\n): { traceId: string; parentSessionId?: string } {\n\tlet traceId = header.id;\n\tlet parentSessionId: string | undefined;\n\tlet currentFile = sessionFile;\n\tlet currentHeader = header;\n\n\tfor (let depth = 0; depth < 32; depth += 1) {\n\t\tif (!currentHeader.parentSession) {\n\t\t\tbreak;\n\t\t}\n\n\t\tconst parentPath = resolveParentSessionPath(currentFile, currentHeader.parentSession);\n\t\tconst parentHeader = readSessionHeader(parentPath);\n\t\tif (!parentHeader) {\n\t\t\tbreak;\n\t\t}\n\n\t\tif (depth === 0) {\n\t\t\tparentSessionId = parentHeader.id;\n\t\t}\n\t\ttraceId = parentHeader.id;\n\t\tcurrentFile = parentPath;\n\t\tcurrentHeader = parentHeader;\n\t}\n\n\treturn { traceId, parentSessionId };\n}\n\nfunction parseResponseObject(text: string): Record<string, unknown> | undefined {\n\tif (!text.trim()) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\tconst parsed = JSON.parse(text) as unknown;\n\t\treturn isRecord(parsed) ? parsed : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nasync function readResponseMessage(response: Response): Promise<string> {\n\tconst text = await response.text().catch(() => \"\");\n\tif (!text.trim()) {\n\t\treturn response.statusText || \"Unknown error\";\n\t}\n\n\tconst parsed = parseResponseObject(text);\n\tif (parsed) {\n\t\tconst error = parsed.error;\n\t\tif (isRecord(error)) {\n\t\t\tconst message = stringField(error, \"message\");\n\t\t\tif (message) return message;\n\t\t}\n\t\tconst detail = stringField(parsed, \"detail\");\n\t\tif (detail) return detail;\n\t\tconst message = stringField(parsed, \"message\");\n\t\tif (message) return message;\n\t}\n\n\treturn text.trim();\n}\n\nasync function fetchWithTimeout(\n\tfetchFn: typeof fetch,\n\turl: string,\n\tinit: RequestInit,\n\ttimeoutMs: number,\n\tsignal?: AbortSignal,\n): Promise<Response> {\n\tconst controller = new AbortController();\n\tconst timeoutError = new TraceUploadTimeoutError(timeoutMs);\n\tlet timedOut = false;\n\tconst timeout = setTimeout(() => {\n\t\ttimedOut = true;\n\t\tcontroller.abort(timeoutError);\n\t}, timeoutMs);\n\ttimeout.unref();\n\tconst onAbort = () => controller.abort(signal?.reason);\n\tif (signal?.aborted) {\n\t\tonAbort();\n\t} else {\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t}\n\n\ttry {\n\t\treturn await fetchFn(url, { ...init, signal: controller.signal });\n\t} catch (error) {\n\t\tif (timedOut) {\n\t\t\tthrow timeoutError;\n\t\t}\n\t\tthrow error;\n\t} finally {\n\t\tclearTimeout(timeout);\n\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t}\n}\n\nfunction delay(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve) => {\n\t\tif (signal?.aborted) {\n\t\t\tresolve();\n\t\t\treturn;\n\t\t}\n\t\tconst timeout = setTimeout(finish, ms);\n\t\ttimeout.unref();\n\t\tconst onAbort = () => finish();\n\t\tfunction finish() {\n\t\t\tclearTimeout(timeout);\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tresolve();\n\t\t}\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n}\n\nfunction traceUploadRetryDelay(retryIndex: number): number {\n\tconst exponentialDelay = Math.min(\n\t\tTRACE_UPLOAD_RETRY_MAX_DELAY_MS,\n\t\tTRACE_UPLOAD_RETRY_BASE_DELAY_MS * 2 ** retryIndex,\n\t);\n\tconst jitterMultiplier = 1 - TRACE_UPLOAD_RETRY_JITTER + Math.random() * TRACE_UPLOAD_RETRY_JITTER * 2;\n\treturn Math.max(0, Math.round(exponentialDelay * jitterMultiplier));\n}\n\nfunction retryAfterDelay(response: Response, capMs: number = TRACE_UPLOAD_RATE_LIMIT_WINDOW_MS): number | undefined {\n\tconst value = response.headers.get(\"retry-after\")?.trim();\n\tif (!value) {\n\t\treturn undefined;\n\t}\n\tconst seconds = Number(value);\n\tif (Number.isFinite(seconds) && seconds >= 0) {\n\t\treturn Math.min(Math.ceil(seconds * 1_000), capMs);\n\t}\n\tconst retryAt = Date.parse(value);\n\treturn Number.isFinite(retryAt) ? Math.min(Math.max(0, retryAt - Date.now()), capMs) : undefined;\n}\n\ntype BeforeTraceUploadRequest = () => Promise<void>;\n\nasync function fetchWithRetry(\n\tfetchFn: typeof fetch,\n\turl: string,\n\tinit: RequestInit,\n\ttimeoutMs: number,\n\tsignal?: AbortSignal,\n\tbeforeRequest?: BeforeTraceUploadRequest,\n): Promise<Response> {\n\tfor (let attempt = 0; ; attempt += 1) {\n\t\tlet retryDelayMs: number | undefined;\n\t\ttry {\n\t\t\tawait beforeRequest?.();\n\t\t\tif (signal?.aborted) {\n\t\t\t\tthrow signal.reason ?? new Error(\"Trace upload cancelled\");\n\t\t\t}\n\t\t\tconst response = await fetchWithTimeout(fetchFn, url, init, timeoutMs, signal);\n\t\t\tif (attempt >= TRACE_UPLOAD_MAX_RETRIES || !RETRIABLE_HTTP_STATUSES.has(response.status)) {\n\t\t\t\treturn response;\n\t\t\t}\n\t\t\tif (response.status === 503) {\n\t\t\t\tretryDelayMs = retryAfterDelay(response);\n\t\t\t}\n\t\t\tawait response.body?.cancel().catch(() => undefined);\n\t\t} catch (error) {\n\t\t\tif (signal?.aborted) {\n\t\t\t\tthrow signal.reason ?? error;\n\t\t\t}\n\t\t\tif (attempt >= TRACE_UPLOAD_MAX_RETRIES || !isRetriableNetworkError(error)) {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tawait delay(retryDelayMs ?? traceUploadRetryDelay(attempt), signal);\n\t\tif (signal?.aborted) {\n\t\t\tthrow signal.reason ?? new Error(\"Trace upload cancelled\");\n\t\t}\n\t}\n}\n\nfunction traceContentPreview(body: string, maxChars: number): { content: string; truncated: boolean } {\n\tif (body.length <= maxChars) {\n\t\treturn { content: body.trimEnd(), truncated: false };\n\t}\n\tconst marker = \"\\n... middle of trace omitted ...\\n\";\n\tconst available = Math.max(0, maxChars - marker.length);\n\tconst headChars = Math.ceil(available / 2);\n\tconst tailChars = Math.floor(available / 2);\n\treturn {\n\t\tcontent: `${body.slice(0, headChars).trimEnd()}${marker}${body.slice(body.length - tailChars).trimStart()}`,\n\t\ttruncated: true,\n\t};\n}\n\nexport async function previewAgentTraceFile(options: AgentTracePreviewOptions): Promise<AgentTracePreviewResult> {\n\tif (!options.sessionFile) {\n\t\treturn { status: \"no_session_file\" };\n\t}\n\n\tlet fileSize: number;\n\ttry {\n\t\tconst stats = await stat(options.sessionFile);\n\t\tif (!stats.isFile()) {\n\t\t\treturn { status: \"no_session_file\" };\n\t\t}\n\t\tfileSize = stats.size;\n\t} catch {\n\t\treturn { status: \"no_session_file\" };\n\t}\n\tif (fileSize === 0) {\n\t\treturn { status: \"empty_session\" };\n\t}\n\n\tconst header = readSessionHeader(options.sessionFile);\n\tif (!header) {\n\t\treturn { status: \"invalid_session\", message: \"Session file is missing a valid session header\" };\n\t}\n\n\tlet body = \"\";\n\tif (fileSize <= MAX_TRACE_BYTES) {\n\t\ttry {\n\t\t\tbody = await readSessionTraceBody(options.sessionFile);\n\t\t} catch (error) {\n\t\t\treturn { status: \"failed\", message: describeError(error) };\n\t\t}\n\t\tif (!body.trim()) {\n\t\t\treturn { status: \"empty_session\" };\n\t\t}\n\t}\n\n\tconst traceContext = resolveTraceContext(options.sessionFile, header);\n\tconst baseUrl = resolvePrimeAgentTracesBaseUrl(options.baseUrl);\n\tconst git = body ? activeGitContext(body, header) : header.git;\n\tconst preview = body\n\t\t? traceContentPreview(body, Math.max(256, options.maxContentChars ?? TRACE_PREVIEW_MAX_CHARS))\n\t\t: { content: \"\", truncated: true };\n\treturn {\n\t\tstatus: \"ready\",\n\t\tsessionFile: options.sessionFile,\n\t\tsessionId: header.id,\n\t\ttraceId: traceContext.traceId,\n\t\tparentSessionId: traceContext.parentSessionId,\n\t\tcwd: header.cwd,\n\t\tsize: fileSize,\n\t\tmaxBytes: MAX_TRACE_BYTES,\n\t\tuploadable: fileSize <= MAX_TRACE_BYTES && baseUrl !== undefined,\n\t\tendpoint: baseUrl ? `${baseUrl}/api/v1/agent-traces/sessions/${encodeURIComponent(header.id)}` : undefined,\n\t\tgitRepo: git?.repoUrl,\n\t\tgitCommit: git?.commit,\n\t\tcontentPreview: preview.content,\n\t\ttruncated: preview.truncated,\n\t};\n}\n\nasync function findSessionFilesUnder(root: string, files: Set<string>): Promise<void> {\n\tlet entries: Dirent[];\n\ttry {\n\t\tentries = await readdir(root, { withFileTypes: true });\n\t} catch {\n\t\treturn;\n\t}\n\n\tfor (const entry of entries) {\n\t\tconst entryPath = join(root, entry.name);\n\t\tif (entry.isDirectory()) {\n\t\t\tawait findSessionFilesUnder(entryPath, files);\n\t\t\tcontinue;\n\t\t}\n\t\tif (entry.isFile() && entry.name.endsWith(\".jsonl\") && readSessionHeader(entryPath)) {\n\t\t\tfiles.add(entryPath);\n\t\t}\n\t}\n}\n\nexport async function findAgentTraceFiles(sessionDir: string = getSessionsDir()): Promise<string[]> {\n\tconst files = new Set<string>();\n\tconst roots = new Set([resolve(sessionDir), resolve(getSessionArtifactsRoot(sessionDir))]);\n\tawait Promise.all([...roots].map((root) => findSessionFilesUnder(root, files)));\n\treturn [...files].sort();\n}\n\nfunction createTraceUploadAllRequestGate(signal?: AbortSignal): BeforeTraceUploadRequest {\n\tlet nextRequestAt = 0;\n\tlet queue = Promise.resolve();\n\treturn () => {\n\t\tconst slot = queue.then(async () => {\n\t\t\tconst waitMs = Math.max(0, nextRequestAt - Date.now());\n\t\t\tif (waitMs > 0) {\n\t\t\t\tawait delay(waitMs, signal);\n\t\t\t}\n\t\t\tif (!signal?.aborted) {\n\t\t\t\tnextRequestAt = Date.now() + TRACE_UPLOAD_ALL_MIN_REQUEST_INTERVAL_MS;\n\t\t\t}\n\t\t});\n\t\tqueue = slot.catch(() => undefined);\n\t\treturn slot;\n\t};\n}\n\nexport async function uploadAllAgentTraces(options: AgentTraceUploadAllOptions): Promise<AgentTraceUploadAllResult> {\n\tconst { sessionDir, concurrency, onProgress, ...uploadOptions } = options;\n\tconst sessionFiles = await findAgentTraceFiles(sessionDir);\n\ttype UploadResultItem = AgentTraceUploadAllResult[\"results\"][number];\n\tconst results: Array<UploadResultItem | undefined> = new Array(sessionFiles.length);\n\tlet cursor = 0;\n\tlet completed = 0;\n\tconst beforeRequest = createTraceUploadAllRequestGate(uploadOptions.signal);\n\tonProgress?.({ completed, total: sessionFiles.length });\n\n\tconst worker = async () => {\n\t\twhile (true) {\n\t\t\tif (uploadOptions.signal?.aborted) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst index = cursor;\n\t\t\tcursor += 1;\n\t\t\tconst sessionFile = sessionFiles[index];\n\t\t\tif (!sessionFile) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst result = await uploadAgentTraceFileWithRequestGate(\n\t\t\t\t{\n\t\t\t\t\t...uploadOptions,\n\t\t\t\t\tsessionFile,\n\t\t\t\t\treloadConfig: false,\n\t\t\t\t},\n\t\t\t\tbeforeRequest,\n\t\t\t);\n\t\t\tif (uploadOptions.signal?.aborted && result.status === \"failed\") {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresults[index] = { sessionFile, result };\n\t\t\tcompleted += 1;\n\t\t\tonProgress?.({ completed, total: sessionFiles.length, sessionFile, result });\n\t\t}\n\t};\n\n\tconst requestedConcurrency = concurrency ?? TRACE_UPLOAD_ALL_CONCURRENCY;\n\tconst normalizedConcurrency =\n\t\tNumber.isFinite(requestedConcurrency) && requestedConcurrency > 0\n\t\t\t? Math.max(1, Math.floor(requestedConcurrency))\n\t\t\t: TRACE_UPLOAD_ALL_CONCURRENCY;\n\tconst workerCount = Math.min(sessionFiles.length, normalizedConcurrency);\n\tawait Promise.all(Array.from({ length: workerCount }, worker));\n\n\tconst completedResults = results.filter((item): item is UploadResultItem => item !== undefined);\n\tlet uploaded = 0;\n\tlet failed = 0;\n\tlet bytesStored = 0;\n\tfor (const item of completedResults) {\n\t\tif (item.result.status === \"uploaded\") {\n\t\t\tuploaded += 1;\n\t\t\tbytesStored += item.result.bytesStored;\n\t\t} else if (item.result.status === \"failed\") {\n\t\t\tfailed += 1;\n\t\t}\n\t}\n\treturn {\n\t\ttotal: sessionFiles.length,\n\t\tuploaded,\n\t\tfailed,\n\t\tskipped: sessionFiles.length - uploaded - failed,\n\t\tbytesStored,\n\t\tresults: completedResults,\n\t};\n}\n\ninterface AgentTraceUploadedSignature {\n\tsize: number;\n\tmtimeMs: number;\n}\n\nexport const SEMANTIC_EDGES_OUTBOX_KIND = \"semantic-edges\";\n\nexport interface AgentTraceCatchUpResult {\n\tpruned: number;\n\t/** Registered semantic-edge ledgers with bytes beyond their cursor; no delivery endpoint exists yet. */\n\tsemanticEdgeLedgersPending: number;\n\tresults: Array<{ sessionFile: string; result: AgentTraceUploadResult }>;\n}\n\nfunction getAgentTraceOutboxDir(): string {\n\treturn assertProductStatePath(join(getAgentDir(), \"trace-export-outbox\"));\n}\n\n// One entry file per session file (keyed by path hash): concurrent writers cannot lose each other's cursors, and a bad read costs only its own entry.\nfunction agentTraceOutboxEntryPath(sessionFile: string): string {\n\tconst key = createHash(\"sha256\").update(sessionFile).digest(\"hex\").slice(0, 32);\n\treturn join(getAgentTraceOutboxDir(), `${key}.json`);\n}\n\nfunction parseOutboxEntry(raw: string):\n\t| {\n\t\t\tsessionFile: string;\n\t\t\tkind?: string;\n\t\t\tuploaded: AgentTraceUploadedSignature | null;\n\t\t\tuploadedBytes?: number;\n\t }\n\t| undefined {\n\tconst parsed = parseResponseObject(raw);\n\tif (!parsed || typeof parsed.sessionFile !== \"string\") {\n\t\treturn undefined;\n\t}\n\tconst uploaded =\n\t\ttypeof parsed.size === \"number\" && typeof parsed.mtimeMs === \"number\"\n\t\t\t? { size: parsed.size, mtimeMs: parsed.mtimeMs }\n\t\t\t: null;\n\treturn {\n\t\tsessionFile: parsed.sessionFile,\n\t\tkind: typeof parsed.kind === \"string\" ? parsed.kind : undefined,\n\t\tuploaded,\n\t\tuploadedBytes: typeof parsed.uploadedBytes === \"number\" ? parsed.uploadedBytes : undefined,\n\t};\n}\n\n/** `undefined` = no usable cursor; `null` = scheduled but never uploaded. */\nasync function readAgentTraceOutboxEntry(sessionFile: string): Promise<AgentTraceUploadedSignature | null | undefined> {\n\tlet raw: string;\n\ttry {\n\t\traw = await readFile(agentTraceOutboxEntryPath(sessionFile), \"utf8\");\n\t} catch {\n\t\treturn undefined;\n\t}\n\tconst entry = parseOutboxEntry(raw);\n\treturn entry && entry.sessionFile === sessionFile ? entry.uploaded : undefined;\n}\n\nfunction signatureEquals(a: AgentTraceUploadedSignature | null | undefined, b: AgentTraceUploadedSignature): boolean {\n\treturn a != null && a.size === b.size && a.mtimeMs === b.mtimeMs;\n}\n\n/** Session files with a live upload controller in this process; catch-up leaves them to their controller. */\nconst locallyManagedSessionFiles = new Set<string>();\n\n/** Best-effort and synchronous: upload intent must be on disk the moment the transcript persist returns. */\nfunction markAgentTraceOutboxPendingSync(sessionFile: string, kind?: string): boolean {\n\ttry {\n\t\tconst entryPath = agentTraceOutboxEntryPath(sessionFile);\n\t\tif (existsSync(entryPath)) {\n\t\t\treturn true;\n\t\t}\n\t\tmkdirSync(getAgentTraceOutboxDir(), { recursive: true });\n\t\tconst tempPath = `${entryPath}.${process.pid}.${randomUUID()}.tmp`;\n\t\twriteFileSync(\n\t\t\ttempPath,\n\t\t\t`${JSON.stringify(kind === undefined ? { sessionFile } : { sessionFile, kind })}\\n`,\n\t\t\t\"utf8\",\n\t\t);\n\t\trenameSync(tempPath, entryPath);\n\t\treturn true;\n\t} catch {\n\t\t// A broken agent dir must not break session persists.\n\t\treturn false;\n\t}\n}\n\nasync function recordAgentTraceOutboxUpload(\n\tsessionFile: string,\n\tsignature: AgentTraceUploadedSignature,\n): Promise<void> {\n\tconst entryPath = agentTraceOutboxEntryPath(sessionFile);\n\tawait mkdir(getAgentTraceOutboxDir(), { recursive: true });\n\tconst tempPath = `${entryPath}.${process.pid}.${randomUUID()}.tmp`;\n\tawait writeFile(tempPath, `${JSON.stringify({ sessionFile, ...signature })}\\n`, \"utf8\");\n\tawait rename(tempPath, entryPath);\n}\n\n/**\n * Explicit catch-up for the Base Context export outbox. This is never run at\n * startup and does not read inherited agent-traces-outbox entries.\n */\nexport async function catchUpAgentTraceUploads(\n\toptions: Omit<AgentTraceUploadOptions, \"sessionFile\">,\n): Promise<AgentTraceCatchUpResult> {\n\tconst catchUp: AgentTraceCatchUpResult = { pruned: 0, semanticEdgeLedgersPending: 0, results: [] };\n\tif (options.requireEnabled !== false && !(await getAgentTracesEnabled(options))) {\n\t\treturn catchUp;\n\t}\n\tlet entryNames: string[];\n\ttry {\n\t\tentryNames = await readdir(getAgentTraceOutboxDir());\n\t} catch {\n\t\treturn catchUp;\n\t}\n\tconst beforeRequest = createTraceUploadAllRequestGate(options.signal);\n\tfor (const entryName of entryNames) {\n\t\tif (options.signal?.aborted) {\n\t\t\tbreak;\n\t\t}\n\t\tif (!entryName.endsWith(\".json\")) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst entryPath = join(getAgentTraceOutboxDir(), entryName);\n\t\tlet raw: string;\n\t\ttry {\n\t\t\traw = await readFile(entryPath, \"utf8\");\n\t\t} catch {\n\t\t\t// Transient read error: keep the entry and retry at the next startup.\n\t\t\tcontinue;\n\t\t}\n\t\tconst entry = parseOutboxEntry(raw);\n\t\tif (!entry) {\n\t\t\tawait unlink(entryPath).catch(() => undefined);\n\t\t\tcatchUp.pruned += 1;\n\t\t\tcontinue;\n\t\t}\n\t\tif (entry.kind === SEMANTIC_EDGES_OUTBOX_KIND) {\n\t\t\tlet ledgerStats: Awaited<ReturnType<typeof stat>>;\n\t\t\ttry {\n\t\t\t\tledgerStats = await stat(entry.sessionFile);\n\t\t\t} catch (error) {\n\t\t\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n\t\t\t\t\tawait unlink(entryPath).catch(() => undefined);\n\t\t\t\t\tcatchUp.pruned += 1;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!ledgerStats.isFile()) {\n\t\t\t\tawait unlink(entryPath).catch(() => undefined);\n\t\t\t\tcatchUp.pruned += 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// Append-only byte cursor: a ledger whose size equals its delivered offset has nothing new.\n\t\t\tif (ledgerStats.size === entry.uploadedBytes) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// No delivery endpoint exists yet (verifiers#2449 consumes edges in-band over ACP\n\t\t\t// metadata; the trace server has no semantic-edges route). The delta and cursor stay\n\t\t\t// untouched so the first real sender delivers the whole backlog.\n\t\t\tcatchUp.semanticEdgeLedgersPending += 1;\n\t\t\tcontinue;\n\t\t}\n\t\tif (entry.kind !== undefined) {\n\t\t\t// A newer build may register kinds this one cannot deliver; leave their cursors alone.\n\t\t\tcontinue;\n\t\t}\n\t\tif (locallyManagedSessionFiles.has(entry.sessionFile)) {\n\t\t\tcontinue;\n\t\t}\n\t\tlet stats: Awaited<ReturnType<typeof stat>>;\n\t\ttry {\n\t\t\tstats = await stat(entry.sessionFile);\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n\t\t\t\tawait unlink(entryPath).catch(() => undefined);\n\t\t\t\tcatchUp.pruned += 1;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (!stats.isFile()) {\n\t\t\tawait unlink(entryPath).catch(() => undefined);\n\t\t\tcatchUp.pruned += 1;\n\t\t\tcontinue;\n\t\t}\n\t\tif (signatureEquals(entry.uploaded, { size: stats.size, mtimeMs: stats.mtimeMs })) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst result = await uploadAgentTraceFileWithRequestGate(\n\t\t\t{ ...options, sessionFile: entry.sessionFile, reloadConfig: false },\n\t\t\tbeforeRequest,\n\t\t);\n\t\tcatchUp.results.push({ sessionFile: entry.sessionFile, result });\n\t}\n\treturn catchUp;\n}\n\nexport async function getPrimeAgentTraceCredential(\n\tauthStorage: AuthStorage,\n\toptions: { reloadAuth?: boolean; configPath?: string } = {},\n): Promise<AgentTraceCredential | undefined> {\n\tconst traceEnvKey = stringEnv(\"BASE_CONTEXT_TRACES_API_KEY\");\n\tif (traceEnvKey) {\n\t\treturn { apiKey: traceEnvKey, source: \"environment\", label: \"BASE_CONTEXT_TRACES_API_KEY\" };\n\t}\n\n\tif (options.reloadAuth !== false) {\n\t\tauthStorage.reload();\n\t}\n\n\tconst traceKey = await authStorage.getApiKey(BASE_CONTEXT_TRACES_PROVIDER_ID, { includeFallback: false });\n\tif (traceKey) {\n\t\treturn { apiKey: traceKey, source: \"stored\", label: \"Base Context Traces credential\" };\n\t}\n\n\treturn undefined;\n}\n\nasync function getAgentTracesEnabled(\n\toptions: Pick<AgentTraceUploadOptions, \"reloadConfig\" | \"settingsManager\">,\n): Promise<boolean> {\n\tif (options.reloadConfig !== false) {\n\t\tawait options.settingsManager.reload().catch(() => undefined);\n\t}\n\treturn options.settingsManager.getAgentTracesEnabled();\n}\n\nasync function uploadAgentTraceFileWithRequestGate(\n\toptions: AgentTraceUploadOptions,\n\tbeforeRequest?: BeforeTraceUploadRequest,\n): Promise<AgentTraceUploadResult> {\n\tconst result = await performAgentTraceUpload(options, beforeRequest);\n\tlogAgentTraceOutcome(options.sessionFile, result);\n\treturn result;\n}\n\nexport function uploadAgentTraceFile(options: AgentTraceUploadOptions): Promise<AgentTraceUploadResult> {\n\treturn uploadAgentTraceFileWithRequestGate(options);\n}\n\nfunction logAgentTraceOutcome(sessionFile: string | undefined, result: AgentTraceUploadResult): void {\n\tlet line: string | undefined;\n\tswitch (result.status) {\n\t\tcase \"uploaded\":\n\t\t\tline = `uploaded session ${result.sessionId} (${result.bytesStored} bytes)`;\n\t\t\tbreak;\n\t\tcase \"failed\":\n\t\t\tline = `upload failed${result.statusCode ? ` (HTTP ${result.statusCode})` : \"\"}: ${result.message}`;\n\t\t\tbreak;\n\t\tcase \"too_large\":\n\t\t\tline = `upload skipped: session is ${result.size} bytes (limit ${result.maxBytes})`;\n\t\t\tbreak;\n\t\tcase \"invalid_session\":\n\t\t\tline = `upload skipped: ${result.message}`;\n\t\t\tbreak;\n\t\tcase \"missing_credentials\":\n\t\t\tline = \"upload skipped: configure a dedicated BASE_CONTEXT_TRACES_API_KEY or Base Context Traces credential\";\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn;\n\t}\n\tconst suffix = sessionFile ? ` [${sessionFile}]` : \"\";\n\tappendRotatingLog(getAgentTracesLogPath(), `[${new Date().toISOString()}] ${line}${suffix}`);\n}\n\nasync function performAgentTraceUpload(\n\toptions: AgentTraceUploadOptions,\n\tbeforeRequest?: BeforeTraceUploadRequest,\n): Promise<AgentTraceUploadResult> {\n\tconst requireEnabled = options.requireEnabled !== false;\n\tif (requireEnabled && !(await getAgentTracesEnabled(options))) {\n\t\treturn { status: \"disabled\" };\n\t}\n\tconst baseUrl = resolvePrimeAgentTracesBaseUrl(options.baseUrl);\n\tif (!baseUrl) {\n\t\treturn {\n\t\t\tstatus: \"failed\",\n\t\t\tmessage:\n\t\t\t\t\"Trace export requires an explicit BASE_CONTEXT_TRACES_BASE_URL and dedicated BASE_CONTEXT_TRACES_API_KEY.\",\n\t\t};\n\t}\n\tif (!options.sessionFile) {\n\t\treturn { status: \"no_session_file\" };\n\t}\n\n\tlet signature: AgentTraceUploadedSignature;\n\ttry {\n\t\tconst stats = await stat(options.sessionFile);\n\t\tif (!stats.isFile()) {\n\t\t\treturn { status: \"no_session_file\" };\n\t\t}\n\t\tsignature = { size: stats.size, mtimeMs: stats.mtimeMs };\n\t} catch {\n\t\treturn { status: \"no_session_file\" };\n\t}\n\tconst fileSize = signature.size;\n\tif (fileSize === 0) {\n\t\treturn { status: \"empty_session\" };\n\t}\n\tif (fileSize > MAX_TRACE_BYTES) {\n\t\treturn { status: \"too_large\", size: fileSize, maxBytes: MAX_TRACE_BYTES };\n\t}\n\t// Cursor invariant: an automatic upload never re-sends a file whose content already matches its uploaded cursor.\n\tif (requireEnabled && signatureEquals(await readAgentTraceOutboxEntry(options.sessionFile), signature)) {\n\t\treturn { status: \"unchanged\" };\n\t}\n\n\tconst header = readSessionHeader(options.sessionFile);\n\tif (!header) {\n\t\treturn { status: \"invalid_session\", message: \"Session file is missing a valid session header\" };\n\t}\n\n\tconst credential = await getPrimeAgentTraceCredential(options.authStorage, {\n\t\tconfigPath: options.configPath,\n\t\treloadAuth: options.reloadConfig !== false,\n\t});\n\tif (!credential) {\n\t\treturn { status: \"missing_credentials\" };\n\t}\n\n\tif (requireEnabled && !(await getAgentTracesEnabled(options))) {\n\t\treturn { status: \"disabled\" };\n\t}\n\n\tlet body: string;\n\ttry {\n\t\tbody = await readSessionTraceBody(options.sessionFile);\n\t} catch (error) {\n\t\treturn { status: \"failed\", message: describeError(error) };\n\t}\n\tif (!body.trim()) {\n\t\treturn { status: \"empty_session\" };\n\t}\n\n\tconst traceContext = resolveTraceContext(options.sessionFile, header);\n\tconst bodyBytes = Buffer.byteLength(body, \"utf8\");\n\tconst headers: Record<string, string> = {\n\t\tAuthorization: `Bearer ${credential.apiKey}`,\n\t\t\"Content-Type\": \"application/x-ndjson\",\n\t\tAccept: \"application/json\",\n\t\t\"X-Trace-Id\": traceContext.traceId,\n\t\t\"X-Cwd\": header.cwd,\n\t\t\"X-Agent-Version\": VERSION,\n\t};\n\tif (traceContext.parentSessionId) {\n\t\theaders[\"X-Parent-Session\"] = traceContext.parentSessionId;\n\t}\n\tconst git = activeGitContext(body, header);\n\tif (git?.repoUrl) {\n\t\theaders[\"X-Git-Repo\"] = git.repoUrl;\n\t}\n\tif (git?.commit) {\n\t\theaders[\"X-Git-Commit\"] = git.commit;\n\t}\n\n\tif (requireEnabled && !(await getAgentTracesEnabled(options))) {\n\t\treturn { status: \"disabled\" };\n\t}\n\n\tconst url = `${baseUrl}/api/v1/agent-traces/sessions/${encodeURIComponent(header.id)}`;\n\tconst fetchFn = options.fetchFn ?? fetch;\n\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetchWithRetry(\n\t\t\tfetchFn,\n\t\t\turl,\n\t\t\t{\n\t\t\t\tmethod: \"PUT\",\n\t\t\t\tredirect: \"error\",\n\t\t\t\theaders,\n\t\t\t\tbody,\n\t\t\t},\n\t\t\toptions.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n\t\t\toptions.signal,\n\t\t\tbeforeRequest,\n\t\t);\n\t} catch (error) {\n\t\treturn { status: \"failed\", message: describeError(error) };\n\t}\n\n\tif (!response.ok) {\n\t\treturn {\n\t\t\tstatus: \"failed\",\n\t\t\tstatusCode: response.status,\n\t\t\tmessage: await readResponseMessage(response),\n\t\t\tretryAfterMs: retryAfterDelay(response, MAX_TIMER_DELAY_MS),\n\t\t};\n\t}\n\n\tconst responseText = await response.text().catch(() => \"\");\n\tconst responseData = parseResponseObject(responseText);\n\ttry {\n\t\tawait recordAgentTraceOutboxUpload(options.sessionFile, signature);\n\t} catch (error) {\n\t\treturn { status: \"failed\", message: `stored, but recording the upload cursor failed: ${describeError(error)}` };\n\t}\n\treturn {\n\t\tstatus: \"uploaded\",\n\t\tsessionId: responseData ? (stringField(responseData, \"session_id\") ?? header.id) : header.id,\n\t\ttraceId: responseData ? (stringField(responseData, \"trace_id\") ?? traceContext.traceId) : traceContext.traceId,\n\t\tbytesStored: responseData ? (numberField(responseData, \"bytes_stored\") ?? bodyBytes) : bodyBytes,\n\t\tkey: responseData ? stringField(responseData, \"key\") : undefined,\n\t};\n}\n\nexport function uploadAgentTraceSession(options: AgentTraceSessionUploadOptions): Promise<AgentTraceUploadResult> {\n\treturn uploadAgentTraceFile({\n\t\t...options,\n\t\tsessionFile: options.sessionManager.getSessionFile(),\n\t});\n}\n\nclass AgentTraceUploadController {\n\tprivate timeout: NodeJS.Timeout | undefined;\n\tprivate pending = false;\n\tprivate inFlight: Promise<void> | undefined;\n\tprivate lastUploadStartedAt: number | undefined;\n\tprivate notBeforeAt = 0;\n\n\tconstructor(\n\t\tprivate readonly sessionManager: SessionManager,\n\t\tprivate options: AgentTraceUploadInstallOptions,\n\t) {}\n\n\tupdate(options: AgentTraceUploadInstallOptions): void {\n\t\tthis.options = options;\n\t}\n\n\tschedule = (): void => {\n\t\tif (\n\t\t\t!this.options.settingsManager.getAgentTracesEnabled() ||\n\t\t\t!resolvePrimeAgentTracesBaseUrl(this.options.baseUrl)\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tthis.pending = true;\n\t\t// Intent is consent-gated at persist time: an entry created while sharing\n\t\t// is off would turn a later enable into retroactive collection of\n\t\t// opted-out sessions. Marking re-runs every persist (existsSync-cheap),\n\t\t// so an entry pruned by a racing catch-up is re-registered.\n\t\tif (this.options.settingsManager.getAgentTracesEnabled()) {\n\t\t\tconst sessionFile = this.sessionManager.getSessionFile();\n\t\t\tif (\n\t\t\t\tsessionFile &&\n\t\t\t\t!locallyManagedSessionFiles.has(sessionFile) &&\n\t\t\t\tmarkAgentTraceOutboxPendingSync(sessionFile)\n\t\t\t) {\n\t\t\t\tlocallyManagedSessionFiles.add(sessionFile);\n\t\t\t}\n\t\t\tconst ledgerPath = this.options.semanticEdgesLedgerPath;\n\t\t\tif (ledgerPath) {\n\t\t\t\tmarkAgentTraceOutboxPendingSync(ledgerPath, SEMANTIC_EDGES_OUTBOX_KIND);\n\t\t\t}\n\t\t}\n\t\tthis.arm();\n\t};\n\n\tprivate arm(): void {\n\t\tif (this.timeout) {\n\t\t\tclearTimeout(this.timeout);\n\t\t}\n\t\tconst elapsed = this.lastUploadStartedAt === undefined ? undefined : Date.now() - this.lastUploadStartedAt;\n\t\tconst throttleDelay = elapsed === undefined ? 0 : Math.max(0, TRACE_UPLOAD_MIN_INTERVAL_MS - elapsed);\n\t\tconst notBeforeDelay = Math.max(0, this.notBeforeAt - Date.now());\n\t\tthis.timeout = setTimeout(\n\t\t\t() => {\n\t\t\t\tthis.timeout = undefined;\n\t\t\t\tvoid this.runScheduledUpload();\n\t\t\t},\n\t\t\tMath.max(TRACE_UPLOAD_DEBOUNCE_MS, throttleDelay, notBeforeDelay),\n\t\t);\n\t\tthis.timeout.unref();\n\t}\n\n\tprivate async runScheduledUpload(): Promise<void> {\n\t\tif (this.inFlight) {\n\t\t\treturn;\n\t\t}\n\t\tthis.pending = false;\n\t\tthis.lastUploadStartedAt = Date.now();\n\t\tthis.inFlight = uploadAgentTraceSession({\n\t\t\t...this.options,\n\t\t\tsessionManager: this.sessionManager,\n\t\t}).then(\n\t\t\t(result) => {\n\t\t\t\tif (result.status === \"failed\" && isRescheduledUploadFailure(result.statusCode)) {\n\t\t\t\t\tthis.pending = true;\n\t\t\t\t\tif (result.retryAfterMs !== undefined) {\n\t\t\t\t\t\tthis.notBeforeAt = Date.now() + result.retryAfterMs;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t() => undefined,\n\t\t);\n\t\tawait this.inFlight;\n\t\tthis.inFlight = undefined;\n\t\tif (this.pending) {\n\t\t\tthis.arm();\n\t\t}\n\t}\n}\n\nfunction isRescheduledUploadFailure(statusCode: number | undefined): boolean {\n\treturn statusCode === undefined || statusCode === 429 || RETRIABLE_HTTP_STATUSES.has(statusCode);\n}\n\nconst traceUploadControllers = new WeakMap<SessionManager, AgentTraceUploadController>();\nexport function installAgentTraceUpload(sessionManager: SessionManager, options: AgentTraceUploadInstallOptions): void {\n\t// Enabling export applies to live sessions, not an inherited outbox backlog.\n\tlet controller = traceUploadControllers.get(sessionManager);\n\tif (controller) {\n\t\tcontroller.update(options);\n\t\treturn;\n\t}\n\n\tcontroller = new AgentTraceUploadController(sessionManager, options);\n\ttraceUploadControllers.set(sessionManager, controller);\n\tsessionManager.onPersist(controller.schedule);\n}\n"]}