@xingular/harness 1.0.0 → 1.2.0

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 (397) hide show
  1. package/CHANGELOG.md +46 -5
  2. package/README.md +75 -21
  3. package/dist/cli.js +17421 -8711
  4. package/dist/sdk/accounts/accounts.d.ts +27 -3
  5. package/dist/sdk/accounts/accounts.js +78 -24
  6. package/dist/sdk/accounts/secrets.d.ts +12 -0
  7. package/dist/sdk/accounts/secrets.js +114 -7
  8. package/dist/sdk/accounts/store.d.ts +26 -1
  9. package/dist/sdk/accounts/store.js +1 -1
  10. package/dist/sdk/agents/builtin.d.ts +2 -0
  11. package/dist/sdk/agents/builtin.js +48 -0
  12. package/dist/sdk/agents/narrow.d.ts +13 -0
  13. package/dist/sdk/agents/narrow.js +29 -0
  14. package/dist/sdk/agents/planResearch.d.ts +5 -0
  15. package/dist/sdk/agents/planResearch.js +19 -0
  16. package/dist/sdk/agents/profiles.d.ts +81 -0
  17. package/dist/sdk/agents/profiles.js +226 -0
  18. package/dist/sdk/annotations/bridge.d.ts +42 -0
  19. package/dist/sdk/annotations/bridge.js +264 -0
  20. package/dist/sdk/annotations/capture.d.ts +116 -0
  21. package/dist/sdk/annotations/capture.js +308 -0
  22. package/dist/sdk/annotations/delivery.d.ts +72 -0
  23. package/dist/sdk/annotations/delivery.js +130 -0
  24. package/dist/sdk/annotations/inspect.d.ts +10 -0
  25. package/dist/sdk/annotations/inspect.js +33 -0
  26. package/dist/sdk/annotations/isolated.d.ts +6 -0
  27. package/dist/sdk/annotations/isolated.js +51 -0
  28. package/dist/sdk/annotations/pageScript.d.ts +28 -0
  29. package/dist/sdk/annotations/pageScript.js +871 -0
  30. package/dist/sdk/annotations/part.d.ts +10 -0
  31. package/dist/sdk/annotations/part.js +155 -0
  32. package/dist/sdk/annotations/queue.d.ts +4 -0
  33. package/dist/sdk/annotations/queue.js +40 -0
  34. package/dist/sdk/annotations/serverMethods.d.ts +3 -0
  35. package/dist/sdk/annotations/serverMethods.js +62 -0
  36. package/dist/sdk/annotations/store.d.ts +167 -0
  37. package/dist/sdk/annotations/store.js +394 -0
  38. package/dist/sdk/browser/backend.d.ts +16 -0
  39. package/dist/sdk/browser/backend.js +17 -0
  40. package/dist/sdk/browser/contracts.d.ts +19 -0
  41. package/dist/sdk/browser/contracts.js +116 -0
  42. package/dist/sdk/browser/index.d.ts +14 -3
  43. package/dist/sdk/browser/index.js +11 -4
  44. package/dist/sdk/browser/serverMethods.d.ts +3 -0
  45. package/dist/sdk/browser/serverMethods.js +7 -0
  46. package/dist/sdk/browser/session.d.ts +1 -0
  47. package/dist/sdk/browser/session.js +45 -9
  48. package/dist/sdk/browser/tools.d.ts +1 -4
  49. package/dist/sdk/browser/tools.js +14 -91
  50. package/dist/sdk/cli.js +24 -15
  51. package/dist/sdk/commands/list.d.ts +3 -0
  52. package/dist/sdk/commands/list.js +75 -0
  53. package/dist/sdk/commands/registry.d.ts +250 -0
  54. package/dist/sdk/commands/registry.js +47 -0
  55. package/dist/sdk/commands/routes.d.ts +16 -0
  56. package/dist/sdk/commands/routes.js +33 -0
  57. package/dist/sdk/core/bus.js +1 -1
  58. package/dist/sdk/core/commandBudget.d.ts +23 -0
  59. package/dist/sdk/core/commandBudget.js +57 -0
  60. package/dist/sdk/core/env.d.ts +1 -1
  61. package/dist/sdk/core/env.js +1 -1
  62. package/dist/sdk/core/index.js +17 -1
  63. package/dist/sdk/core/interactiveProfile.d.ts +46 -0
  64. package/dist/sdk/core/interactiveProfile.js +143 -0
  65. package/dist/sdk/core/workspace.d.ts +13 -0
  66. package/dist/sdk/core/workspace.js +88 -0
  67. package/dist/sdk/core/workspaceBashTool.d.ts +4 -0
  68. package/dist/sdk/core/workspaceBashTool.js +36 -0
  69. package/dist/sdk/core/workspaceSandbox.d.ts +31 -0
  70. package/dist/sdk/core/workspaceSandbox.js +320 -0
  71. package/dist/sdk/core/workspaceWriteOperations.d.ts +22 -0
  72. package/dist/sdk/core/workspaceWriteOperations.js +206 -0
  73. package/dist/sdk/doctor/serverMethods.d.ts +3 -0
  74. package/dist/sdk/doctor/serverMethods.js +8 -0
  75. package/dist/sdk/doctor/types.d.ts +2 -2
  76. package/dist/sdk/harness.d.ts +22 -0
  77. package/dist/sdk/harness.js +84 -5
  78. package/dist/sdk/history/recall.js +8 -8
  79. package/dist/sdk/history/serverMethods.d.ts +3 -0
  80. package/dist/sdk/history/serverMethods.js +8 -0
  81. package/dist/sdk/hub/cli.d.ts +1 -1
  82. package/dist/sdk/hub/cli.js +1 -1
  83. package/dist/sdk/hub/schema.d.ts +1 -1
  84. package/dist/sdk/hub/serverMethods.d.ts +3 -0
  85. package/dist/sdk/hub/serverMethods.js +11 -0
  86. package/dist/sdk/index.d.ts +16 -1
  87. package/dist/sdk/index.js +10 -0
  88. package/dist/sdk/mcp/connections.js +14 -7
  89. package/dist/sdk/mcp/serverMethods.d.ts +3 -0
  90. package/dist/sdk/mcp/serverMethods.js +22 -0
  91. package/dist/sdk/pi/runtime.d.ts +2 -1
  92. package/dist/sdk/pi/runtime.js +2 -2
  93. package/dist/sdk/plugins/builtin.d.ts +1 -0
  94. package/dist/sdk/plugins/builtin.js +145 -33
  95. package/dist/sdk/plugins/builtinRegistry.d.ts +60 -0
  96. package/dist/sdk/plugins/builtinRegistry.js +71 -0
  97. package/dist/sdk/plugins/catalogue.js +2 -1
  98. package/dist/sdk/plugins/cli.d.ts +1 -1
  99. package/dist/sdk/plugins/cli.js +4 -3
  100. package/dist/sdk/plugins/commandPrefix.d.ts +8 -0
  101. package/dist/sdk/plugins/commandPrefix.js +21 -0
  102. package/dist/sdk/plugins/firstParty.d.ts +1 -1
  103. package/dist/sdk/plugins/firstParty.js +8 -2
  104. package/dist/sdk/plugins/inventory.js +4 -3
  105. package/dist/sdk/plugins/manifest.d.ts +2 -0
  106. package/dist/sdk/plugins/manifest.js +2 -1
  107. package/dist/sdk/plugins/surfaces.d.ts +5 -0
  108. package/dist/sdk/plugins/surfaces.js +11 -0
  109. package/dist/sdk/plugins/types.d.ts +15 -0
  110. package/dist/sdk/prompt/compile.d.ts +19 -2
  111. package/dist/sdk/prompt/compile.js +79 -26
  112. package/dist/sdk/prompt/standard.d.ts +17 -0
  113. package/dist/sdk/prompt/standard.js +21 -0
  114. package/dist/sdk/refine/cli.d.ts +1 -1
  115. package/dist/sdk/refine/cli.js +2 -2
  116. package/dist/sdk/refine/serverMethods.d.ts +3 -0
  117. package/dist/sdk/refine/serverMethods.js +20 -0
  118. package/dist/sdk/server/methods.js +27 -86
  119. package/dist/sdk/server/params.d.ts +4 -0
  120. package/dist/sdk/server/params.js +11 -0
  121. package/dist/sdk/server/remote.js +36 -11
  122. package/dist/sdk/sessions/compactionCheckpoint.d.ts +11 -0
  123. package/dist/sdk/sessions/compactionCheckpoint.js +79 -0
  124. package/dist/sdk/sessions/continuation.d.ts +3 -0
  125. package/dist/sdk/sessions/continuation.js +5 -1
  126. package/dist/sdk/sessions/contracts.d.ts +185 -0
  127. package/dist/sdk/sessions/contracts.js +101 -4
  128. package/dist/sdk/sessions/control.d.ts +72 -0
  129. package/dist/sdk/sessions/control.js +158 -0
  130. package/dist/sdk/sessions/initialBash.d.ts +7 -0
  131. package/dist/sdk/sessions/initialBash.js +7 -0
  132. package/dist/sdk/sessions/planMode.d.ts +18 -4
  133. package/dist/sdk/sessions/planMode.js +82 -16
  134. package/dist/sdk/sessions/reasoningSummaries.d.ts +2 -2
  135. package/dist/sdk/sessions/reasoningSummaries.js +3 -3
  136. package/dist/sdk/sessions/registry.d.ts +2 -1
  137. package/dist/sdk/sessions/registry.js +6 -3
  138. package/dist/sdk/sessions/runIndex.js +7 -7
  139. package/dist/sdk/sessions/runStore.d.ts +14 -0
  140. package/dist/sdk/sessions/runStore.js +38 -1
  141. package/dist/sdk/sessions/runner.d.ts +40 -2
  142. package/dist/sdk/sessions/runner.js +163 -15
  143. package/dist/sdk/sessions/sessions.d.ts +40 -8
  144. package/dist/sdk/sessions/sessions.js +279 -27
  145. package/dist/sdk/sessions/sideQuestionServerMethods.d.ts +3 -0
  146. package/dist/sdk/sessions/sideQuestionServerMethods.js +19 -0
  147. package/dist/sdk/sessions/sideQuestions.d.ts +66 -0
  148. package/dist/sdk/sessions/sideQuestions.js +290 -0
  149. package/dist/sdk/sessions/subagentServerMethods.d.ts +3 -0
  150. package/dist/sdk/sessions/subagentServerMethods.js +41 -0
  151. package/dist/sdk/sessions/subagentService.d.ts +22 -2
  152. package/dist/sdk/sessions/subagentService.js +93 -16
  153. package/dist/sdk/sessions/subagents.d.ts +9 -1
  154. package/dist/sdk/sessions/subagents.js +19 -0
  155. package/dist/sdk/sessions/workSlice.d.ts +103 -0
  156. package/dist/sdk/sessions/workSlice.js +297 -0
  157. package/dist/sdk/sessions/workspaceBoundary.d.ts +15 -0
  158. package/dist/sdk/sessions/workspaceBoundary.js +46 -0
  159. package/dist/sdk/terminal/browser.d.ts +62 -0
  160. package/dist/sdk/terminal/browser.js +41 -0
  161. package/dist/sdk/terminal/browserControl.d.ts +29 -0
  162. package/dist/sdk/terminal/browserControl.js +187 -0
  163. package/dist/sdk/terminal/commandHelp.d.ts +17 -0
  164. package/dist/sdk/terminal/commandHelp.js +20 -0
  165. package/dist/sdk/terminal/controller.d.ts +15 -1
  166. package/dist/sdk/terminal/controller.js +67 -8
  167. package/dist/sdk/terminal/opentui/subagentsWidget.d.ts +28 -0
  168. package/dist/sdk/terminal/opentui/subagentsWidget.js +52 -0
  169. package/dist/sdk/terminal/opentui/theme.d.ts +43 -0
  170. package/dist/sdk/terminal/opentui/theme.js +87 -0
  171. package/dist/sdk/terminal/piExtension.d.ts +6 -1
  172. package/dist/sdk/terminal/piExtension.js +169 -84
  173. package/dist/sdk/terminal/socket.d.ts +6 -3
  174. package/dist/sdk/terminal/socket.js +40 -9
  175. package/dist/sdk/terminal/standalone.d.ts +9 -0
  176. package/dist/sdk/terminal/standalone.js +118 -19
  177. package/dist/sdk/terminal/subagentMonitor.d.ts +47 -0
  178. package/dist/sdk/terminal/subagentMonitor.js +152 -0
  179. package/dist/sdk/terminal/terminals.d.ts +60 -7
  180. package/dist/sdk/terminal/terminals.js +598 -47
  181. package/dist/sdk/terminal/trajectory.js +2 -2
  182. package/dist/sdk/terminal/tui/annotateCommand.d.ts +3 -0
  183. package/dist/sdk/terminal/tui/annotateCommand.js +22 -0
  184. package/dist/sdk/terminal/tui/commandPages.d.ts +7 -1
  185. package/dist/sdk/terminal/tui/commandPages.js +57 -6
  186. package/dist/sdk/terminal/tui/commands.d.ts +10 -4
  187. package/dist/sdk/terminal/tui/commands.js +269 -65
  188. package/dist/sdk/terminal/tui/dialogGeometry.d.ts +2 -2
  189. package/dist/sdk/terminal/tui/dialogGeometry.js +4 -2
  190. package/dist/sdk/terminal/tui/dialogs.d.ts +6 -1
  191. package/dist/sdk/terminal/tui/dialogs.js +9 -2
  192. package/dist/sdk/terminal/tui/list.d.ts +14 -1
  193. package/dist/sdk/terminal/tui/list.js +96 -14
  194. package/dist/sdk/terminal/tui/modelPicker.js +1 -6
  195. package/dist/sdk/terminal/tui/page.d.ts +3 -0
  196. package/dist/sdk/terminal/tui/pageOverlay.js +27 -7
  197. package/dist/sdk/terminal/tui/pages/accountsPage.d.ts +26 -4
  198. package/dist/sdk/terminal/tui/pages/accountsPage.js +264 -36
  199. package/dist/sdk/terminal/tui/pages/agentsPage.d.ts +74 -0
  200. package/dist/sdk/terminal/tui/pages/agentsPage.js +190 -0
  201. package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +94 -0
  202. package/dist/sdk/terminal/tui/pages/annotatePage.js +341 -0
  203. package/dist/sdk/terminal/tui/pages/browserPage.d.ts +87 -0
  204. package/dist/sdk/terminal/tui/pages/browserPage.js +738 -0
  205. package/dist/sdk/terminal/tui/pages/doctorPage.d.ts +2 -0
  206. package/dist/sdk/terminal/tui/pages/doctorPage.js +103 -15
  207. package/dist/sdk/terminal/tui/pages/notesPage.d.ts +75 -0
  208. package/dist/sdk/terminal/tui/pages/notesPage.js +201 -0
  209. package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +0 -5
  210. package/dist/sdk/terminal/tui/pages/pluginsPage.js +9 -3
  211. package/dist/sdk/terminal/tui/pages/settingsPage.d.ts +8 -0
  212. package/dist/sdk/terminal/tui/pages/settingsPage.js +16 -1
  213. package/dist/sdk/terminal/tui/pages/subagentsPage.d.ts +24 -0
  214. package/dist/sdk/terminal/tui/pages/subagentsPage.js +152 -0
  215. package/dist/sdk/terminal/tui/pages/trajectoryPage.d.ts +12 -5
  216. package/dist/sdk/terminal/tui/pages/trajectoryPage.js +156 -83
  217. package/dist/sdk/terminal/tui/pages/usagePage.d.ts +43 -0
  218. package/dist/sdk/terminal/tui/pages/usagePage.js +370 -0
  219. package/dist/sdk/terminal/tui/pages/worktreesPage.d.ts +72 -0
  220. package/dist/sdk/terminal/tui/pages/worktreesPage.js +203 -0
  221. package/dist/sdk/terminal/tui/piAdapter.d.ts +1 -2
  222. package/dist/sdk/terminal/tui/piAdapter.js +3 -5
  223. package/dist/sdk/terminal/tui/planMode.d.ts +1 -1
  224. package/dist/sdk/terminal/tui/planMode.js +1 -1
  225. package/dist/sdk/terminal/tui/pluginCommands.d.ts +16 -0
  226. package/dist/sdk/terminal/tui/pluginCommands.js +31 -0
  227. package/dist/sdk/terminal/tui/questions.d.ts +2 -0
  228. package/dist/sdk/terminal/tui/runtime.js +27 -5
  229. package/dist/sdk/terminal/tui/sessionPicker.d.ts +40 -0
  230. package/dist/sdk/terminal/tui/sessionPicker.js +141 -0
  231. package/dist/sdk/terminal/tui/settingsModel.d.ts +2 -0
  232. package/dist/sdk/terminal/tui/settingsModel.js +32 -2
  233. package/dist/sdk/terminal/tui/sideQuestion.d.ts +26 -26
  234. package/dist/sdk/terminal/tui/sideQuestion.js +131 -98
  235. package/dist/sdk/terminal/tui/ui.d.ts +2 -0
  236. package/dist/sdk/terminal/tui/ui.js +1 -0
  237. package/dist/sdk/tools/askUser.js +26 -8
  238. package/dist/sdk/tools/checklist.js +3 -2
  239. package/dist/sdk/tools/checkpoint.d.ts +27 -0
  240. package/dist/sdk/tools/checkpoint.js +154 -0
  241. package/dist/sdk/tools/getCapabilities.d.ts +31 -0
  242. package/dist/sdk/tools/getCapabilities.js +34 -0
  243. package/dist/sdk/tools/harnessTools.d.ts +2 -0
  244. package/dist/sdk/tools/harnessTools.js +1 -1
  245. package/dist/sdk/tools/spawnSubagent.js +14 -4
  246. package/dist/sdk/usage/analytics.d.ts +20 -0
  247. package/dist/sdk/usage/analytics.js +151 -0
  248. package/dist/sdk/usage/ledger.d.ts +38 -0
  249. package/dist/sdk/usage/ledger.js +142 -0
  250. package/dist/sdk/usage/limits/adapters.d.ts +11 -0
  251. package/dist/sdk/usage/limits/adapters.js +349 -0
  252. package/dist/sdk/usage/limits/capture.d.ts +8 -0
  253. package/dist/sdk/usage/limits/capture.js +47 -0
  254. package/dist/sdk/usage/limits/http.d.ts +12 -0
  255. package/dist/sdk/usage/limits/http.js +51 -0
  256. package/dist/sdk/usage/limits/normalize.d.ts +15 -0
  257. package/dist/sdk/usage/limits/normalize.js +211 -0
  258. package/dist/sdk/usage/limits/service.d.ts +30 -0
  259. package/dist/sdk/usage/limits/service.js +176 -0
  260. package/dist/sdk/usage/limits/types.d.ts +65 -0
  261. package/dist/sdk/usage/limits/types.js +1 -0
  262. package/dist/sdk/usage/serverMethods.d.ts +3 -0
  263. package/dist/sdk/usage/serverMethods.js +22 -0
  264. package/dist/sdk/usage/service.d.ts +32 -0
  265. package/dist/sdk/usage/service.js +66 -0
  266. package/dist/sdk/usage/settings.d.ts +7 -0
  267. package/dist/sdk/usage/settings.js +28 -0
  268. package/dist/sdk/usage/types.d.ts +60 -0
  269. package/dist/sdk/usage/types.js +4 -0
  270. package/dist/sdk/web/part.d.ts +10 -0
  271. package/dist/sdk/web/part.js +6 -0
  272. package/dist/sdk/web/serverMethods.d.ts +3 -0
  273. package/dist/sdk/web/serverMethods.js +7 -0
  274. package/dist/sdk/wiki/serverMethods.d.ts +3 -0
  275. package/dist/sdk/wiki/serverMethods.js +26 -0
  276. package/dist/sdk/worktrees/cli.d.ts +11 -0
  277. package/dist/sdk/worktrees/cli.js +146 -0
  278. package/dist/sdk/worktrees/copy.d.ts +9 -0
  279. package/dist/sdk/worktrees/copy.js +111 -0
  280. package/dist/sdk/worktrees/git.d.ts +16 -0
  281. package/dist/sdk/worktrees/git.js +81 -0
  282. package/dist/sdk/worktrees/serverMethods.d.ts +3 -0
  283. package/dist/sdk/worktrees/serverMethods.js +30 -0
  284. package/dist/sdk/worktrees/service.d.ts +73 -0
  285. package/dist/sdk/worktrees/service.js +483 -0
  286. package/dist/sdk/worktrees/setup.d.ts +23 -0
  287. package/dist/sdk/worktrees/setup.js +65 -0
  288. package/dist/sdk/worktrees/types.d.ts +91 -0
  289. package/dist/sdk/worktrees/types.js +1 -0
  290. package/dist/sdk/world/cli.d.ts +2 -2
  291. package/dist/sdk/world/cli.js +6 -6
  292. package/dist/sdk/world/world.d.ts +3 -1
  293. package/dist/sdk/world/world.js +8 -9
  294. package/dist/terminal-opentui.js +16456 -5966
  295. package/dist/terminal-pi.js +1579 -573
  296. package/docs/README.md +22 -2
  297. package/docs/architecture/00-overview.md +28 -16
  298. package/docs/architecture/02-sessions-and-runs.md +184 -25
  299. package/docs/architecture/03-variants.md +10 -2
  300. package/docs/architecture/04-prompt.md +65 -7
  301. package/docs/architecture/06-plugins-and-capabilities.md +57 -4
  302. package/docs/architecture/07-tools-and-gate.md +112 -17
  303. package/docs/architecture/08-memory-and-history.md +4 -2
  304. package/docs/architecture/09-accounts-and-connectors.md +39 -1
  305. package/docs/architecture/10-server-protocol.md +168 -4
  306. package/docs/architecture/11-terminal.md +251 -8
  307. package/docs/architecture/12-storage.md +45 -3
  308. package/docs/architecture/13-world-tools.md +49 -6
  309. package/docs/architecture/14-surfaces-and-parity.md +136 -0
  310. package/docs/architecture/README.md +17 -4
  311. package/docs/architecture/proposals/PROP-0006-run-checklist.md +4 -2
  312. package/docs/architecture/proposals/PROP-0013-resource-browser.md +58 -0
  313. package/docs/architecture/proposals/PROP-0014-fullscreen-browser-tabs.md +58 -0
  314. package/docs/architecture/proposals/PROP-0015-one-capability-surface.md +88 -0
  315. package/docs/architecture/proposals/PROP-0016-managed-worktrees.md +156 -0
  316. package/docs/architecture/proposals/PROP-0017-page-annotations.md +131 -0
  317. package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +179 -0
  318. package/docs/architecture/proposals/PROP-0019-contextual-side-questions.md +122 -0
  319. package/docs/architecture/proposals/PROP-0020-builtin-feature-plugins.md +105 -0
  320. package/docs/architecture/proposals/PROP-0021-command-namespaces-and-duplicates.md +99 -0
  321. package/docs/architecture/proposals/PROP-0022-plan-only-questions.md +102 -0
  322. package/docs/architecture/proposals/PROP-0023-xagent-cli-name.md +61 -0
  323. package/docs/architecture/proposals/PROP-0024-question-sketch-previews.md +130 -0
  324. package/docs/architecture/proposals/PROP-0025-contextual-question-wireframes.md +49 -0
  325. package/docs/architecture/proposals/PROP-0026-connected-sketch-display.md +94 -0
  326. package/docs/architecture/proposals/PROP-0027-xingular-agent-product-identity.md +60 -0
  327. package/docs/architecture/proposals/PROP-0028-normalize-question-wireframes.md +55 -0
  328. package/docs/architecture/proposals/PROP-0029-agent-profiles.md +124 -0
  329. package/docs/architecture/proposals/PROP-0030-subagent-title.md +86 -0
  330. package/docs/architecture/proposals/PROP-0031-plugin-command-owner-namespaces.md +54 -0
  331. package/docs/architecture/proposals/PROP-0032-planning-quality.md +196 -0
  332. package/docs/architecture/proposals/PROP-0033-compaction-plan-checkpoints.md +75 -0
  333. package/docs/architecture/proposals/PROP-0034-conversation-fast-mode.md +120 -0
  334. package/docs/architecture/proposals/PROP-0035-retain-subagent-status.md +47 -0
  335. package/docs/architecture/proposals/PROP-0036-first-class-harness-commands.md +59 -0
  336. package/docs/architecture/proposals/PROP-0037-workspace-write-boundary.md +63 -0
  337. package/docs/architecture/proposals/PROP-0038-annotate-open-shadow-roots.md +73 -0
  338. package/docs/architecture/proposals/PROP-0039-workspace-boundary-hardening.md +139 -0
  339. package/docs/architecture/proposals/PROP-0040-one-launch-path.md +87 -0
  340. package/docs/architecture/proposals/PROP-0041-bounded-work-slices.md +247 -0
  341. package/docs/architecture/proposals/PROP-0042-usage-and-provider-limits.md +127 -0
  342. package/docs/architecture/proposals/PROP-0043-read-only-plan-bash.md +74 -0
  343. package/docs/architecture/proposals/README.md +39 -0
  344. package/docs/decisions/ADR-0001-owned-terminal.md +1 -1
  345. package/docs/decisions/ADR-0009-xharness-command.md +6 -5
  346. package/docs/decisions/ADR-0014-public-release-baseline.md +3 -2
  347. package/docs/decisions/ADR-0015-resource-browser.md +28 -0
  348. package/docs/decisions/ADR-0016-fullscreen-browser-tabs.md +31 -0
  349. package/docs/decisions/ADR-0017-one-capability-surface.md +34 -0
  350. package/docs/decisions/ADR-0018-builtin-feature-plugins.md +35 -0
  351. package/docs/decisions/ADR-0019-managed-git-worktrees.md +31 -0
  352. package/docs/decisions/ADR-0020-page-annotations.md +37 -0
  353. package/docs/decisions/ADR-0021-required-command-namespaces.md +40 -0
  354. package/docs/decisions/ADR-0022-plan-only-question-tools.md +34 -0
  355. package/docs/decisions/ADR-0023-contextual-side-question-runs.md +35 -0
  356. package/docs/decisions/ADR-0024-xagent-command.md +40 -0
  357. package/docs/decisions/ADR-0025-question-choice-sketches.md +49 -0
  358. package/docs/decisions/ADR-0026-contextual-question-wireframes.md +58 -0
  359. package/docs/decisions/ADR-0027-connected-sketch-display.md +60 -0
  360. package/docs/decisions/ADR-0028-xingular-agent-product-identity.md +56 -0
  361. package/docs/decisions/ADR-0029-normalized-question-wireframe-borders.md +33 -0
  362. package/docs/decisions/ADR-0030-agent-profiles.md +43 -0
  363. package/docs/decisions/ADR-0031-plugin-command-owner-namespaces.md +37 -0
  364. package/docs/decisions/ADR-0032-compaction-run-checkpoints.md +33 -0
  365. package/docs/decisions/ADR-0033-plan-mode-research-delegation.md +47 -0
  366. package/docs/decisions/ADR-0034-first-class-harness-terminal-commands.md +25 -0
  367. package/docs/decisions/ADR-0035-one-launch-path.md +40 -0
  368. package/docs/decisions/ADR-0036-workspace-write-operation-boundary.md +54 -0
  369. package/docs/decisions/ADR-0037-usage-ledger-and-provider-limits.md +44 -0
  370. package/docs/decisions/ADR-0038-plan-bash-os-read-only.md +29 -0
  371. package/docs/doctor.md +10 -4
  372. package/docs/experiments/EXP-0003-performance-audit/data/session-runtime-replication.json +420 -0
  373. package/docs/experiments/EXP-0003-performance-audit/data/session-runtime.json +420 -0
  374. package/docs/experiments/EXP-0003-performance-audit/data/terminal-compact.json +376 -0
  375. package/docs/experiments/EXP-0003-performance-audit/data/terminal-history.json +422 -0
  376. package/docs/experiments/EXP-0003-performance-audit/data/terminal-question.json +95 -0
  377. package/docs/experiments/EXP-0003-performance-audit/data/terminal-wide.json +1036 -0
  378. package/docs/experiments/EXP-0003-performance-audit/plan.md +109 -0
  379. package/docs/experiments/EXP-0003-performance-audit/results.md +151 -0
  380. package/docs/features/README.md +26 -0
  381. package/docs/features/interfaces/README.md +1 -1
  382. package/docs/future-work.md +48 -0
  383. package/docs/handoff-2026-10-25-app-parity-and-solo-tabs.md +202 -0
  384. package/docs/parity.md +134 -0
  385. package/docs/releases/1.0.0.md +2 -2
  386. package/docs/releases/1.1.1.md +28 -0
  387. package/docs/releases/1.2.0.md +49 -0
  388. package/docs/releases/4.0.0.md +47 -0
  389. package/docs/releases/README.md +37 -27
  390. package/docs/study/00-index.md +8 -0
  391. package/docs/study/STUDY-0003-long-horizon-runs.md +210 -0
  392. package/docs/study/autonomous-engineering-sources.md +277 -0
  393. package/docs/study/autonomy-sources.md +147 -0
  394. package/docs/study/proactive-autonomy.md +408 -0
  395. package/docs/study/tui-resource-browser.md +78 -0
  396. package/docs/terminal-tui.md +386 -113
  397. package/package.json +6 -5
@@ -0,0 +1,211 @@
1
+ const headersOf = (input) => {
2
+ const map = new Map();
3
+ for (const [key, value] of Object.entries(input)) {
4
+ if (typeof value === 'string' && value.trim())
5
+ map.set(key.toLowerCase(), value.trim());
6
+ }
7
+ return map;
8
+ };
9
+ export function toNumber(value) {
10
+ if (value === undefined)
11
+ return null;
12
+ const cleaned = value.replace(/[%,\s]/g, '');
13
+ if (!cleaned)
14
+ return null;
15
+ const parsed = Number(cleaned);
16
+ return Number.isFinite(parsed) ? parsed : null;
17
+ }
18
+ /** A reset time as an ISO instant: epoch seconds or milliseconds, a duration, or a date string. */
19
+ export function toIso(value, now) {
20
+ if (!value)
21
+ return null;
22
+ const text = value.trim();
23
+ const duration = /^(?:(\d+(?:\.\d+)?)d)?(?:(\d+(?:\.\d+)?)h)?(?:(\d+(?:\.\d+)?)m)?(?:(\d+(?:\.\d+)?)s)?(?:(\d+(?:\.\d+)?)ms)?$/i.exec(text);
24
+ if (duration && text.match(/[a-z]/i)) {
25
+ const [, d, h, m, s, ms] = duration;
26
+ const total = Number(d ?? 0) * 86_400_000 + Number(h ?? 0) * 3_600_000 + Number(m ?? 0) * 60_000 + Number(s ?? 0) * 1_000 + Number(ms ?? 0);
27
+ if (Number.isFinite(total) && total > 0)
28
+ return new Date(now.getTime() + total).toISOString();
29
+ }
30
+ const numeric = /^\d+(?:\.\d+)?$/.test(text) ? Number(text) : Number.NaN;
31
+ if (Number.isFinite(numeric)) {
32
+ const millis = numeric > 10_000_000_000 ? numeric : numeric * 1_000;
33
+ const date = new Date(millis);
34
+ if (!Number.isNaN(date.getTime()))
35
+ return date.toISOString();
36
+ }
37
+ const parsed = Date.parse(text);
38
+ return Number.isNaN(parsed) ? null : new Date(parsed).toISOString();
39
+ }
40
+ const leftPercent = (usedPercent) => usedPercent === null ? null : Math.max(0, Math.min(100, 100 - usedPercent));
41
+ const statusFor = (percent, fallback = 'allowed') => percent === null ? fallback : percent >= 100 ? 'rejected' : percent >= 80 ? 'warning' : 'allowed';
42
+ function entry(limit, observedAt) {
43
+ return { ...limit, provider: limit.provider, observedAt };
44
+ }
45
+ /** The four well-known families, with their labels and units. */
46
+ const ANTHROPIC_FAMILIES = [
47
+ { key: 'requests', label: 'Requests', unit: 'requests' },
48
+ { key: 'input-tokens', label: 'Input tokens', unit: 'tokens' },
49
+ { key: 'output-tokens', label: 'Output tokens', unit: 'tokens' }
50
+ ];
51
+ const RATE_FAMILIES = ['requests', 'tokens', 'input-tokens', 'output-tokens', 'tokens-per-minute'];
52
+ const title = (value) => value.split(/[-_]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(' ');
53
+ /**
54
+ * Reads every limit family present in one provider response. `provider` is the Pi provider id;
55
+ * the returned ids are namespaced with it so two providers never overwrite each other.
56
+ */
57
+ export function normalizeHeaders(provider, response, now = new Date()) {
58
+ const headers = headersOf(response.headers);
59
+ const observedAt = now.toISOString();
60
+ const limits = new Map();
61
+ const put = (limit) => { limits.set(limit.id, limit); };
62
+ // Anthropic's per-resource rate limits.
63
+ for (const family of ANTHROPIC_FAMILIES) {
64
+ const prefix = `anthropic-ratelimit-${family.key}-`;
65
+ const limit = toNumber(headers.get(`${prefix}limit`));
66
+ const remaining = toNumber(headers.get(`${prefix}remaining`));
67
+ const resetsAt = toIso(headers.get(`${prefix}reset`), now);
68
+ if (limit === null && remaining === null && !resetsAt)
69
+ continue;
70
+ const used = limit !== null && remaining !== null ? Math.max(0, limit - remaining) : null;
71
+ const usedPercent = limit !== null && limit > 0 && used !== null ? (used / limit) * 100 : null;
72
+ put(entry({
73
+ id: `${provider}:${family.key}`,
74
+ label: family.label,
75
+ provider,
76
+ kind: 'rate',
77
+ unit: family.unit,
78
+ used,
79
+ limit,
80
+ remaining,
81
+ usedPercent,
82
+ remainingPercent: leftPercent(usedPercent),
83
+ resetsAt,
84
+ status: statusFor(usedPercent),
85
+ source: 'headers'
86
+ }, observedAt));
87
+ }
88
+ // Anthropic's unified window headers for subscription plans.
89
+ const unified = new Map();
90
+ for (const [key, value] of headers) {
91
+ const match = /^anthropic-ratelimit-unified-(.+?)-(utilization|reset|status)$/.exec(key);
92
+ if (!match)
93
+ continue;
94
+ const window = match[1];
95
+ const bucket = unified.get(window) ?? {};
96
+ bucket[match[2]] = value;
97
+ unified.set(window, bucket);
98
+ }
99
+ const unifiedStatus = headers.get('anthropic-ratelimit-unified-status');
100
+ for (const [window, bucket] of unified) {
101
+ const raw = toNumber(bucket.utilization);
102
+ const usedPercent = raw === null ? null : raw <= 1.5 ? raw * 100 : raw;
103
+ const id = `${provider}:window-${window}`;
104
+ put(entry({
105
+ id,
106
+ label: window === '5h' ? '5-hour window' : window === '7d' ? 'Weekly window' : window === 'primary' ? 'Primary window' : window === 'secondary' ? 'Secondary window' : `${title(window)} window`,
107
+ provider,
108
+ kind: 'window',
109
+ unit: 'percent',
110
+ usedPercent,
111
+ remainingPercent: leftPercent(usedPercent),
112
+ resetsAt: toIso(bucket.reset, now),
113
+ status: bucket.status ?? (unifiedStatus === 'rejected' ? 'rejected' : statusFor(usedPercent)),
114
+ source: 'headers'
115
+ }, observedAt));
116
+ }
117
+ // Codex-style plan windows.
118
+ for (const window of ['primary', 'secondary']) {
119
+ const percent = toNumber(headers.get(`x-codex-${window}-used-percent`));
120
+ const windowMinutes = toNumber(headers.get(`x-codex-${window}-window-minutes`));
121
+ const resetsAt = toIso(headers.get(`x-codex-${window}-reset-at`) ?? headers.get(`x-codex-${window}-reset`), now);
122
+ if (percent === null && !resetsAt)
123
+ continue;
124
+ put(entry({
125
+ id: `${provider}:codex-${window}`,
126
+ label: `${title(window)} window`,
127
+ provider,
128
+ kind: 'window',
129
+ unit: 'percent',
130
+ usedPercent: percent,
131
+ remainingPercent: leftPercent(percent),
132
+ windowMinutes,
133
+ resetsAt,
134
+ status: statusFor(percent),
135
+ source: 'headers'
136
+ }, observedAt));
137
+ }
138
+ // The common x-ratelimit-* headers.
139
+ const families = new Map();
140
+ for (const [key, value] of headers) {
141
+ const match = /^x-ratelimit-(limit|remaining|reset)-(.+)$/.exec(key);
142
+ if (!match)
143
+ continue;
144
+ const bucket = families.get(match[2]) ?? {};
145
+ bucket[match[1]] = value;
146
+ families.set(match[2], bucket);
147
+ }
148
+ for (const [family, bucket] of families) {
149
+ if (!RATE_FAMILIES.includes(family))
150
+ continue;
151
+ const limit = toNumber(bucket.limit);
152
+ const remaining = toNumber(bucket.remaining);
153
+ const resetsAt = toIso(bucket.reset, now);
154
+ if (limit === null && remaining === null && !resetsAt)
155
+ continue;
156
+ const used = limit !== null && remaining !== null ? Math.max(0, limit - remaining) : null;
157
+ const usedPercent = limit !== null && limit > 0 && used !== null ? (used / limit) * 100 : null;
158
+ put(entry({
159
+ id: `${provider}:rate-${family}`,
160
+ label: `${title(family)} (rate)`,
161
+ provider,
162
+ kind: 'rate',
163
+ unit: family.includes('token') ? 'tokens' : 'requests',
164
+ used,
165
+ limit,
166
+ remaining,
167
+ usedPercent,
168
+ remainingPercent: leftPercent(usedPercent),
169
+ resetsAt,
170
+ status: statusFor(usedPercent),
171
+ source: 'headers'
172
+ }, observedAt));
173
+ }
174
+ // A 429's retry hint, when the provider sent nothing richer.
175
+ if (response.status === 429 && !limits.size) {
176
+ const retry = headers.get('retry-after');
177
+ // `Retry-After` is a delay in seconds or an HTTP date, not an epoch instant.
178
+ const resetsAt = retry && /^\d+(?:\.\d+)?$/.test(retry.trim())
179
+ ? new Date(now.getTime() + Number(retry) * 1_000).toISOString()
180
+ : toIso(retry, now);
181
+ if (resetsAt) {
182
+ put(entry({
183
+ id: `${provider}:retry`,
184
+ label: 'Rate limited',
185
+ provider,
186
+ kind: 'error',
187
+ unit: 'percent',
188
+ status: 'rejected',
189
+ resetsAt,
190
+ source: 'headers'
191
+ }, observedAt));
192
+ }
193
+ }
194
+ return { limits: [...limits.values()] };
195
+ }
196
+ /** Only the limit-bearing headers, bounded, so a child process never relays an entire response. */
197
+ export function limitHeaders(input) {
198
+ const kept = {};
199
+ let count = 0;
200
+ for (const [key, value] of Object.entries(input)) {
201
+ const name = key.toLowerCase();
202
+ if (!/^(anthropic-ratelimit-|x-codex-|x-ratelimit-|retry-after$)/.test(name))
203
+ continue;
204
+ if (typeof value !== 'string' || !value.trim())
205
+ continue;
206
+ kept[name] = value.trim().slice(0, 200);
207
+ if (++count >= 40)
208
+ break;
209
+ }
210
+ return kept;
211
+ }
@@ -0,0 +1,30 @@
1
+ import type { Accounts } from "../../accounts/accounts.js";
2
+ import { type Limit } from "../../accounts/store.js";
3
+ import type { FetchLike, LimitReadout, ProviderResponseLike, UsageLimitsReport } from "./types.js";
4
+ export interface UsageLimitsOptions {
5
+ accounts: Accounts;
6
+ root: () => string | null;
7
+ fetch?: FetchLike;
8
+ now?: () => Date;
9
+ }
10
+ export declare class UsageLimits {
11
+ private readonly options;
12
+ private readonly fetchImpl;
13
+ private readonly now;
14
+ private readonly headerWrites;
15
+ constructor(options: UsageLimitsOptions);
16
+ /** Lets an account's model runtime report response headers without changing any request. */
17
+ observeModels(models: unknown, accountId: string): void;
18
+ /** Records what one provider response said. Throttled per account and provider. */
19
+ recordHeaders(accountId: string, provider: string, response: ProviderResponseLike): void;
20
+ /** What the accounts already know, without touching the network. */
21
+ read(accountId?: string | null): UsageLimitsReport;
22
+ /** Reads every provider that is due, then returns the stored report. */
23
+ refresh(accountId?: string | null, options?: {
24
+ force?: boolean;
25
+ }): Promise<UsageLimitsReport>;
26
+ private refreshProvider;
27
+ /** Limits known for one account and provider, for diagnostics. */
28
+ limitsOf(accountId: string, provider: string): Limit[];
29
+ }
30
+ export type { LimitReadout };
@@ -0,0 +1,176 @@
1
+ import { scrub } from "../../accounts/store.js";
2
+ import { readUsageSettings } from "../settings.js";
3
+ import { adapterFor } from "./adapters.js";
4
+ import { observeProviderResponses } from "./capture.js";
5
+ import { normalizeHeaders } from "./normalize.js";
6
+ const DEFAULT_BASE = {
7
+ openrouter: 'https://openrouter.ai',
8
+ deepseek: 'https://api.deepseek.com',
9
+ anthropic: 'https://api.anthropic.com',
10
+ 'openai-codex': 'https://chatgpt.com/backend-api',
11
+ 'opencode-go': 'https://opencode.ai'
12
+ };
13
+ const HEADER_WRITE_INTERVAL_MS = 15_000;
14
+ const REQUEST_TIMEOUT_MS = 6_000;
15
+ function origin(url) {
16
+ try {
17
+ return new URL(url).origin;
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ export class UsageLimits {
24
+ options;
25
+ fetchImpl;
26
+ now;
27
+ headerWrites = new Map();
28
+ constructor(options) {
29
+ this.options = options;
30
+ this.fetchImpl = options.fetch ?? ((url, init) => fetch(url, init));
31
+ this.now = options.now ?? (() => new Date());
32
+ }
33
+ /** Lets an account's model runtime report response headers without changing any request. */
34
+ observeModels(models, accountId) {
35
+ observeProviderResponses(models, (provider, response) => this.recordHeaders(accountId, provider, response));
36
+ }
37
+ /** Records what one provider response said. Throttled per account and provider. */
38
+ recordHeaders(accountId, provider, response) {
39
+ if (!provider || provider.length > 64)
40
+ return;
41
+ const key = `${accountId}\u0000${provider}`;
42
+ const at = this.now().getTime();
43
+ const last = this.headerWrites.get(key) ?? 0;
44
+ if (at - last < HEADER_WRITE_INTERVAL_MS)
45
+ return;
46
+ const normalized = normalizeHeaders(provider, response, this.now());
47
+ if (!normalized.limits.length)
48
+ return;
49
+ this.headerWrites.set(key, at);
50
+ try {
51
+ this.options.accounts.applyLimits(accountId, [{
52
+ provider,
53
+ limits: normalized.limits,
54
+ source: 'headers',
55
+ reason: null,
56
+ observedAt: this.now().toISOString()
57
+ }]);
58
+ }
59
+ catch {
60
+ // The account may have been removed while a response was arriving.
61
+ }
62
+ }
63
+ /** What the accounts already know, without touching the network. */
64
+ read(accountId) {
65
+ const overview = this.options.accounts.overview();
66
+ const accounts = overview.accounts.filter((account) => !accountId || account.id === accountId);
67
+ return {
68
+ generatedAt: this.now().toISOString(),
69
+ accounts: accounts.map((account) => {
70
+ const known = new Map();
71
+ for (const entry of account.state.providerLimits ?? [])
72
+ known.set(entry.provider, entry);
73
+ for (const limit of account.state.limits) {
74
+ const provider = limit.provider;
75
+ if (!provider || known.has(provider))
76
+ continue;
77
+ known.set(provider, { source: limit.source === 'provider' ? 'provider' : 'headers', reason: null, observedAt: limit.observedAt ?? this.now().toISOString() });
78
+ }
79
+ return {
80
+ accountId: account.id,
81
+ label: account.label,
82
+ plan: account.state.plan,
83
+ health: account.state.health,
84
+ providers: [...known.entries()].map(([provider, entry]) => ({
85
+ provider,
86
+ source: entry.source,
87
+ reason: entry.reason,
88
+ observedAt: entry.observedAt,
89
+ limits: account.state.limits.filter((limit) => limit.provider === provider && !(limit.resetsAt && Date.parse(limit.resetsAt) <= this.now().getTime()))
90
+ })).sort((left, right) => left.provider.localeCompare(right.provider))
91
+ };
92
+ })
93
+ };
94
+ }
95
+ /** Reads every provider that is due, then returns the stored report. */
96
+ async refresh(accountId, options = {}) {
97
+ const settings = readUsageSettings(this.options.root());
98
+ const overview = this.options.accounts.overview();
99
+ const accounts = overview.accounts.filter((account) => !accountId || account.id === accountId);
100
+ for (const account of accounts) {
101
+ let credentials;
102
+ try {
103
+ credentials = await this.options.accounts.listCredentials(account.id);
104
+ }
105
+ catch {
106
+ continue;
107
+ }
108
+ for (const credential of credentials) {
109
+ const known = this.read(account.id).accounts[0]?.providers.find((entry) => entry.provider === credential.providerId);
110
+ const age = known ? this.now().getTime() - Date.parse(known.observedAt) : Number.POSITIVE_INFINITY;
111
+ if (!options.force && Number.isFinite(age) && age < settings.refreshMinutes * 60_000)
112
+ continue;
113
+ await this.refreshProvider(this.options.accounts.find(account.id), credential.providerId, credential.type, settings.experimentalProbes);
114
+ }
115
+ }
116
+ return this.read(accountId);
117
+ }
118
+ async refreshProvider(account, provider, credentialType, experimental) {
119
+ const observedAt = this.now().toISOString();
120
+ const unavailable = (reason) => {
121
+ try {
122
+ this.options.accounts.applyLimits(account.id, [{ provider, limits: [], source: 'unavailable', reason: scrub(reason), observedAt }]);
123
+ }
124
+ catch {
125
+ // The account was removed while its limits were being read.
126
+ }
127
+ };
128
+ const adapter = adapterFor(provider);
129
+ if (!adapter)
130
+ return unavailable('No documented usage endpoint for this provider. Local usage is shown instead.');
131
+ if (adapter.stability === 'experimental' && !experimental)
132
+ return unavailable('Experimental provider probes are off. Turn them on in the usage plugin settings.');
133
+ let context;
134
+ try {
135
+ const runtime = await this.options.accounts.runtime(account);
136
+ const auth = await runtime.getAuth(provider);
137
+ if (!auth)
138
+ return unavailable('This provider is not signed in.');
139
+ const configured = runtime.getProvider(provider)?.baseUrl ?? null;
140
+ const expected = DEFAULT_BASE[provider];
141
+ if (configured && expected && origin(configured) !== origin(expected)) {
142
+ return unavailable('This provider uses a custom base URL, so its own usage endpoint cannot be read.');
143
+ }
144
+ context = {
145
+ provider,
146
+ credentialType,
147
+ auth: { ...auth.auth, ...(auth.auth.baseUrl ? {} : configured ? { baseUrl: configured } : {}) },
148
+ baseUrl: auth.auth.baseUrl ?? configured ?? null,
149
+ fetch: this.fetchImpl,
150
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
151
+ now: this.now()
152
+ };
153
+ }
154
+ catch (error) {
155
+ return unavailable(error instanceof Error ? error.message : 'The provider credential could not be resolved.');
156
+ }
157
+ try {
158
+ const reading = await adapter.read(context);
159
+ this.options.accounts.applyLimits(account.id, [{
160
+ provider,
161
+ limits: reading.limits.map((limit) => ({ ...limit, provider, observedAt, source: 'provider' })),
162
+ source: 'provider',
163
+ reason: reading.reason ? scrub(reading.reason) : null,
164
+ observedAt,
165
+ plan: reading.plan ?? null
166
+ }]);
167
+ }
168
+ catch (error) {
169
+ unavailable(error instanceof Error ? error.message : 'The provider usage endpoint could not be read.');
170
+ }
171
+ }
172
+ /** Limits known for one account and provider, for diagnostics. */
173
+ limitsOf(accountId, provider) {
174
+ return this.options.accounts.overview().accounts.find((account) => account.id === accountId)?.state.limits.filter((limit) => limit.provider === provider) ?? [];
175
+ }
176
+ }
@@ -0,0 +1,65 @@
1
+ import type { Limit } from "../../accounts/store.js";
2
+ export interface ProviderResponseLike {
3
+ status: number;
4
+ headers: Record<string, string>;
5
+ }
6
+ /** The credential Pi resolved for this provider: an API key, request headers, or both. */
7
+ export interface LimitAuth {
8
+ apiKey?: string;
9
+ headers?: Record<string, string | null>;
10
+ baseUrl?: string;
11
+ }
12
+ export interface LimitReadout {
13
+ provider: string;
14
+ limits: Limit[];
15
+ source: 'headers' | 'provider' | 'estimate' | 'unavailable';
16
+ reason: string | null;
17
+ observedAt: string;
18
+ plan?: string | null;
19
+ }
20
+ export type FetchLike = (url: string, init?: {
21
+ headers?: Record<string, string>;
22
+ signal?: AbortSignal;
23
+ redirect?: 'error' | 'follow' | 'manual';
24
+ }) => Promise<{
25
+ status: number;
26
+ text(): Promise<string>;
27
+ }>;
28
+ export interface AdapterContext {
29
+ provider: string;
30
+ credentialType: 'api_key' | 'oauth';
31
+ auth: LimitAuth;
32
+ /** The provider's base URL, reflected from a `models.json` override when one exists. */
33
+ baseUrl: string | null;
34
+ fetch: FetchLike;
35
+ signal: AbortSignal;
36
+ now: Date;
37
+ }
38
+ export interface AdapterReading {
39
+ limits: Limit[];
40
+ plan?: string | null;
41
+ reason?: string | null;
42
+ }
43
+ export interface ProviderLimitAdapter {
44
+ id: string;
45
+ providers: readonly string[];
46
+ /** `documented` reads the provider's own usage API; `experimental` is off until a person allows it. */
47
+ stability: 'documented' | 'experimental';
48
+ read(context: AdapterContext): Promise<AdapterReading>;
49
+ }
50
+ export interface UsageLimitsReport {
51
+ generatedAt: string;
52
+ accounts: {
53
+ accountId: string;
54
+ label: string;
55
+ plan: string | null;
56
+ health: string;
57
+ providers: {
58
+ provider: string;
59
+ source: 'headers' | 'provider' | 'estimate' | 'unavailable';
60
+ reason: string | null;
61
+ observedAt: string;
62
+ limits: Limit[];
63
+ }[];
64
+ }[];
65
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { Harness } from "../harness.js";
2
+ import type { Handler } from "../server/rpc.js";
3
+ export declare function usageServerMethods(harness: Harness): Record<string, Handler>;
@@ -0,0 +1,22 @@
1
+ const text = (params, name) => typeof params[name] === 'string' && params[name] ? params[name] : undefined;
2
+ const scopeOf = (value) => value === 'session' || value === 'account' || value === 'project' || value === 'all' ? value : undefined;
3
+ const periodOf = (value) => value === 'today' || value === '7d' || value === '30d' || value === 'all' ? value : undefined;
4
+ export function usageServerMethods(harness) {
5
+ return {
6
+ 'usage.summary': (params) => harness.usage.summary({
7
+ scope: scopeOf(params.scope),
8
+ sessionId: text(params, 'sessionId'),
9
+ accountId: text(params, 'accountId'),
10
+ projectId: text(params, 'projectId'),
11
+ period: periodOf(params.period),
12
+ ...(typeof params.timezoneOffsetMinutes === 'number' ? { timezoneOffsetMinutes: params.timezoneOffsetMinutes } : {})
13
+ }),
14
+ 'usage.limits': async (params) => {
15
+ const accountId = text(params, 'accountId');
16
+ return params.refresh === true
17
+ ? harness.usageLimits.refresh(accountId ?? null, { force: true })
18
+ : harness.usageLimits.read(accountId ?? null);
19
+ },
20
+ 'usage.sync': () => harness.usage.sync()
21
+ };
22
+ }
@@ -0,0 +1,32 @@
1
+ import type { Accounts } from "../accounts/accounts.js";
2
+ import { type RatesLookup } from "./analytics.js";
3
+ import { type UsagePeriod, type UsageScope, type UsageSummary } from "./types.js";
4
+ export interface UsageOptions {
5
+ root: () => string;
6
+ accounts: Accounts;
7
+ rates?: RatesLookup;
8
+ now?: () => Date;
9
+ }
10
+ export interface UsageSummaryInput {
11
+ scope?: UsageScope | null;
12
+ sessionId?: string | null;
13
+ accountId?: string | null;
14
+ projectId?: string | null;
15
+ period?: UsagePeriod | null;
16
+ timezoneOffsetMinutes?: number | null;
17
+ }
18
+ export declare class Usage {
19
+ private readonly options;
20
+ private readonly now;
21
+ private readonly rates;
22
+ constructor(options: UsageOptions);
23
+ /** The one question the analysis page asks. */
24
+ summary(input: UsageSummaryInput): Promise<UsageSummary>;
25
+ /** Indexes runs the ledger has not seen, such as runs recorded by an earlier process. */
26
+ sync(): {
27
+ runs: number;
28
+ indexed: number;
29
+ lastIndexedAt: string | null;
30
+ };
31
+ private filter;
32
+ }
@@ -0,0 +1,66 @@
1
+ import { invalid } from "../core/errors.js";
2
+ import { summarize } from "./analytics.js";
3
+ import { indexedRuns, lastIndexedAt, syncUsage } from "./ledger.js";
4
+ import { modelRates } from "../sessions/pricing.js";
5
+ import { USAGE_PERIODS } from "./types.js";
6
+ export class Usage {
7
+ options;
8
+ now;
9
+ rates;
10
+ constructor(options) {
11
+ this.options = options;
12
+ this.now = options.now ?? (() => new Date());
13
+ // List rates only price a request the provider did not price; a failed lookup leaves it unknown.
14
+ this.rates = options.rates ?? ((provider, model) => modelRates(provider, model));
15
+ }
16
+ /** The one question the analysis page asks. */
17
+ async summary(input) {
18
+ const root = this.options.root();
19
+ // A data root that has never been indexed gets one bounded catch-up pass on first use.
20
+ if (!lastIndexedAt(root))
21
+ syncUsage(root);
22
+ const period = input.period && USAGE_PERIODS.includes(input.period) ? input.period : '7d';
23
+ const scope = input.scope === 'session' || input.scope === 'project' || input.scope === 'all' || input.scope === 'account'
24
+ ? input.scope
25
+ : input.sessionId ? 'session' : input.projectId ? 'project' : input.accountId ? 'account' : 'all';
26
+ const filter = this.filter(scope, input);
27
+ const labels = new Map(this.options.accounts.overview().accounts.map((account) => [account.id, account.label]));
28
+ return summarize({
29
+ root,
30
+ filter,
31
+ period,
32
+ timezoneOffsetMinutes: typeof input.timezoneOffsetMinutes === 'number' ? input.timezoneOffsetMinutes : 0,
33
+ accountLabel: (id) => labels.get(id) ?? id.slice(0, 8),
34
+ rates: this.rates,
35
+ now: this.now()
36
+ });
37
+ }
38
+ /** Indexes runs the ledger has not seen, such as runs recorded by an earlier process. */
39
+ sync() {
40
+ const root = this.options.root();
41
+ const runs = syncUsage(root);
42
+ return { runs, indexed: indexedRuns(root).length, lastIndexedAt: lastIndexedAt(root) };
43
+ }
44
+ filter(scope, input) {
45
+ const overview = this.options.accounts.overview();
46
+ const defaultAccount = overview.defaults.pi ?? overview.accounts[0]?.id ?? null;
47
+ switch (scope) {
48
+ case 'session':
49
+ if (!input.sessionId)
50
+ throw invalid('A session id is required for session usage.');
51
+ return { session: input.sessionId, account: null, project: null };
52
+ case 'project':
53
+ if (!input.projectId)
54
+ throw invalid('A project id is required for project usage.');
55
+ return { session: null, account: null, project: input.projectId };
56
+ case 'account': {
57
+ const accountId = input.accountId ?? defaultAccount;
58
+ if (!accountId)
59
+ throw invalid('An account is required for account usage.');
60
+ return { session: null, account: accountId, project: null };
61
+ }
62
+ default:
63
+ return { session: null, account: null, project: null };
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,7 @@
1
+ import type { PluginSetting } from "../plugins/types.js";
2
+ export declare const USAGE_SETTINGS: readonly PluginSetting[];
3
+ export interface UsageSettings {
4
+ refreshMinutes: number;
5
+ experimentalProbes: boolean;
6
+ }
7
+ export declare function readUsageSettings(root: string | null): UsageSettings;
@@ -0,0 +1,28 @@
1
+ import { settingValue } from "../plugins/firstParty.js";
2
+ import * as attachments from "../plugins/attachments.js";
3
+ export const USAGE_SETTINGS = [
4
+ {
5
+ key: 'refreshMinutes',
6
+ label: 'Limit refresh interval',
7
+ description: 'How long a provider limit reading is kept before opening /usage:limit asks the provider again.',
8
+ default: 10,
9
+ min: 1,
10
+ max: 240
11
+ },
12
+ {
13
+ key: 'experimentalProbes',
14
+ label: 'Experimental provider probes',
15
+ description: 'Allow undocumented provider usage endpoints. Off by default; they can change without notice.',
16
+ default: 0,
17
+ min: 0,
18
+ max: 1
19
+ }
20
+ ];
21
+ export function readUsageSettings(root) {
22
+ const saved = root ? attachments.load(root).settings.usage : undefined;
23
+ const values = Object.fromEntries(USAGE_SETTINGS.map((setting) => [setting.key, settingValue(setting, saved)]));
24
+ return {
25
+ refreshMinutes: values.refreshMinutes ?? 10,
26
+ experimentalProbes: (values.experimentalProbes ?? 0) === 1
27
+ };
28
+ }