@north-light/crouter 0.3.348 → 0.3.350

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 (634) hide show
  1. package/bin/crtr-launcher +4 -0
  2. package/bin/runtime-generation-manifest.mjs +12 -2
  3. package/dist/api/client.d.ts +50 -7
  4. package/dist/api/client.js +2 -2
  5. package/dist/api/command-manifest/schema.js +1 -1
  6. package/dist/api/dto/analytics.d.ts +184 -0
  7. package/dist/api/dto/analytics.js +0 -0
  8. package/dist/api/dto/broker-ops.d.ts +43 -1
  9. package/dist/api/dto/config.d.ts +11 -0
  10. package/dist/api/dto/grants.d.ts +14 -0
  11. package/dist/api/dto/grants.js +0 -0
  12. package/dist/api/dto/human-requests.d.ts +14 -0
  13. package/dist/api/dto/memory.d.ts +6 -0
  14. package/dist/api/dto/modelauth.d.ts +35 -0
  15. package/dist/api/dto/node-records.d.ts +35 -0
  16. package/dist/api/dto/node-records.js +0 -0
  17. package/dist/api/dto/nodes.d.ts +5 -0
  18. package/dist/api/dto/profiles.d.ts +38 -3
  19. package/dist/api/dto/worktree.d.ts +11 -0
  20. package/dist/api/errors.d.ts +23 -1
  21. package/dist/api/errors.js +1 -1
  22. package/dist/api/index.d.ts +2 -0
  23. package/dist/api/index.js +1 -1
  24. package/dist/api/node-transport.js +1 -1
  25. package/dist/api/routes.d.ts +22 -0
  26. package/dist/api/routes.js +1 -1
  27. package/dist/build-root.d.ts +6 -1
  28. package/dist/build-root.js +1 -1
  29. package/dist/builtin-memory/crouter-sdk/client.md +34 -0
  30. package/dist/builtin-memory/crouter-sdk/errors.md +2 -0
  31. package/dist/builtin-memory/crouter-sdk/memory.md +2 -2
  32. package/dist/builtin-memory/crouter-sdk/streaming.md +21 -0
  33. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +8 -4
  34. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +39 -118
  35. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +6 -1
  36. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +41 -27
  37. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +52 -29
  38. package/dist/cli.js +9 -8
  39. package/dist/clients/attach/chrome/inbox-strip.js +1 -1
  40. package/dist/clients/attach/chrome/roster.js +1 -1
  41. package/dist/clients/attach/config.js +1 -1
  42. package/dist/clients/attach/overlays/auth.js +2 -2
  43. package/dist/clients/attach/overlays/graph.js +1 -1
  44. package/dist/clients/attach/render/tool-calls.js +3 -3
  45. package/dist/clients/attach/session/connection.js +1 -1
  46. package/dist/clients/attach/session/profile-files.js +1 -1
  47. package/dist/clients/attach/viewer.js +1569 -816
  48. package/dist/commands/api-client.js +3 -3
  49. package/dist/commands/broker-permissions.d.ts +2 -0
  50. package/dist/commands/broker-permissions.js +1 -0
  51. package/dist/commands/canvas-analytics/chart.d.ts +52 -0
  52. package/dist/commands/canvas-analytics/chart.js +1 -0
  53. package/dist/commands/canvas-analytics/drilldown.d.ts +34 -0
  54. package/dist/commands/canvas-analytics/drilldown.js +1 -0
  55. package/dist/commands/canvas-analytics/format.d.ts +29 -0
  56. package/dist/commands/canvas-analytics/format.js +1 -0
  57. package/dist/commands/canvas-analytics/graph-detail.d.ts +2 -0
  58. package/dist/commands/canvas-analytics/graph-detail.js +2 -0
  59. package/dist/commands/canvas-analytics/graphs.d.ts +9 -0
  60. package/dist/commands/canvas-analytics/graphs.js +1 -0
  61. package/dist/commands/canvas-analytics/memory.d.ts +2 -0
  62. package/dist/commands/canvas-analytics/memory.js +1 -0
  63. package/dist/commands/canvas-analytics/overview.d.ts +2 -0
  64. package/dist/commands/canvas-analytics/overview.js +1 -0
  65. package/dist/commands/canvas-analytics/shell.d.ts +60 -0
  66. package/dist/commands/canvas-analytics/shell.js +2 -0
  67. package/dist/commands/canvas-analytics/time.d.ts +2 -0
  68. package/dist/commands/canvas-analytics/time.js +1 -0
  69. package/dist/commands/canvas-analytics/usage.d.ts +2 -0
  70. package/dist/commands/canvas-analytics/usage.js +1 -0
  71. package/dist/commands/canvas-analytics/view.d.ts +85 -0
  72. package/dist/commands/canvas-analytics/view.js +1 -0
  73. package/dist/commands/canvas-analytics.d.ts +2 -0
  74. package/dist/commands/canvas-analytics.js +1 -0
  75. package/dist/commands/canvas-prune.js +1 -1
  76. package/dist/commands/canvas.js +1 -1
  77. package/dist/commands/cron.js +5 -5
  78. package/dist/commands/human/components.d.ts +2 -0
  79. package/dist/commands/human/components.js +2 -2
  80. package/dist/commands/human/prompts.js +1 -1
  81. package/dist/commands/human/queue.js +1 -1
  82. package/dist/commands/memory/client.d.ts +11 -0
  83. package/dist/commands/memory/client.js +1 -0
  84. package/dist/commands/memory/delete.js +1 -1
  85. package/dist/commands/memory/edit.js +3 -3
  86. package/dist/commands/memory/find.js +1 -1
  87. package/dist/commands/memory/history.js +4 -4
  88. package/dist/commands/memory/lint.js +1 -1
  89. package/dist/commands/memory/list.d.ts +0 -2
  90. package/dist/commands/memory/list.js +1 -1
  91. package/dist/commands/memory/move.js +1 -1
  92. package/dist/commands/memory/origin.js +1 -1
  93. package/dist/commands/memory/read.d.ts +0 -5
  94. package/dist/commands/memory/read.js +3 -3
  95. package/dist/commands/memory/shared.d.ts +0 -178
  96. package/dist/commands/memory/shared.js +1 -12
  97. package/dist/commands/memory/write.js +5 -5
  98. package/dist/commands/node/bash.js +5 -5
  99. package/dist/commands/node/create.d.ts +0 -17
  100. package/dist/commands/node/create.js +2 -3
  101. package/dist/commands/node/inspect.js +6 -6
  102. package/dist/commands/node/lifecycle.js +2 -2
  103. package/dist/commands/node-context.js +2 -2
  104. package/dist/commands/node-inspect-artifacts.js +6 -6
  105. package/dist/commands/pkg/browse/catalog.js +1 -1
  106. package/dist/commands/pkg/browse/model.d.ts +1 -1
  107. package/dist/commands/pkg/market-manage.js +1 -1
  108. package/dist/commands/pkg/plugin-manage.js +10 -10
  109. package/dist/commands/profile/daemon-profiles.d.ts +13 -0
  110. package/dist/commands/profile/daemon-profiles.js +4 -0
  111. package/dist/commands/profile/delete.js +1 -1
  112. package/dist/commands/profile/env.d.ts +6 -0
  113. package/dist/commands/profile/env.js +2 -2
  114. package/dist/commands/profile/kind.js +1 -2
  115. package/dist/commands/profile/list.js +1 -1
  116. package/dist/commands/profile/meta.js +1 -1
  117. package/dist/commands/profile/new.js +1 -1
  118. package/dist/commands/profile/pause.js +1 -1
  119. package/dist/commands/profile/project.js +1 -1
  120. package/dist/commands/profile/rename.js +1 -1
  121. package/dist/commands/profile/show.js +1 -1
  122. package/dist/commands/profile.js +1 -1
  123. package/dist/commands/push.js +3 -3
  124. package/dist/commands/surface-inspect.js +1 -1
  125. package/dist/commands/sys/branch.js +1 -1
  126. package/dist/commands/sys/config.js +1 -1
  127. package/dist/commands/sys/connect.js +3 -3
  128. package/dist/commands/sys/context/admin/actions.js +1 -1
  129. package/dist/commands/sys/context/admin/model.d.ts +1 -0
  130. package/dist/commands/sys/context/admin/model.js +1 -1
  131. package/dist/commands/sys/context/admin.js +1 -1
  132. package/dist/commands/sys/context/expose.js +2 -1
  133. package/dist/commands/sys/context/prompt-review.js +2 -2
  134. package/dist/commands/sys/context/resolve.js +7 -7
  135. package/dist/commands/sys/context/sysprompt.js +1 -1
  136. package/dist/commands/sys/join.d.ts +1 -0
  137. package/dist/commands/sys/join.js +1 -0
  138. package/dist/commands/sys/migrate.js +1 -1
  139. package/dist/commands/sys/panels/models-panel.js +1 -1
  140. package/dist/commands/sys/panels/profiles-panel.d.ts +0 -1
  141. package/dist/commands/sys/panels/profiles-panel.js +1 -1
  142. package/dist/commands/sys/provider-login.js +6 -6
  143. package/dist/commands/sys/setup-core.d.ts +1 -1
  144. package/dist/commands/sys/setup-core.js +6 -6
  145. package/dist/commands/sys/setup-settings-runner.js +1 -1
  146. package/dist/commands/sys/setup.js +1 -1
  147. package/dist/commands/sys/spaces.d.ts +1 -0
  148. package/dist/commands/sys/spaces.js +2 -0
  149. package/dist/commands/sys/sync-deps.js +2 -2
  150. package/dist/commands/sys/sync-project-guidance.js +4 -4
  151. package/dist/commands/sys/sync-skills.js +2 -2
  152. package/dist/commands/sys/worktrees.d.ts +1 -0
  153. package/dist/commands/sys/worktrees.js +1 -1
  154. package/dist/commands/sys.js +1 -1
  155. package/dist/core/auth-file.js +2 -2
  156. package/dist/core/bash-job-supervisor.d.ts +6 -0
  157. package/dist/core/bash-job-supervisor.js +4 -0
  158. package/dist/core/bash-jobs.d.ts +13 -6
  159. package/dist/core/bash-jobs.js +8 -7
  160. package/dist/core/broker-client/transport-relay.d.ts +6 -3
  161. package/dist/core/broker-client/transport-relay.js +1 -1
  162. package/dist/core/broker-client/transport-socket.d.ts +6 -1
  163. package/dist/core/broker-client/transport-socket.js +1 -1
  164. package/dist/core/canvas/canvas.d.ts +39 -18
  165. package/dist/core/canvas/canvas.js +21 -20
  166. package/dist/core/canvas/context-strip.d.ts +20 -0
  167. package/dist/core/canvas/context-strip.js +5 -0
  168. package/dist/core/canvas/crons.d.ts +12 -20
  169. package/dist/core/canvas/crons.js +15 -13
  170. package/dist/core/canvas/db.js +2 -2
  171. package/dist/core/canvas/deliveries.d.ts +4 -1
  172. package/dist/core/canvas/deliveries.js +1 -1
  173. package/dist/core/canvas/executions.js +1 -1
  174. package/dist/core/canvas/history.d.ts +2 -1
  175. package/dist/core/canvas/history.js +17 -18
  176. package/dist/core/canvas/memory-reads.d.ts +19 -0
  177. package/dist/core/canvas/memory-reads.js +1 -0
  178. package/dist/core/canvas/migrations.d.ts +0 -4
  179. package/dist/core/canvas/migrations.js +99 -32
  180. package/dist/core/canvas/nav-render.js +1 -1
  181. package/dist/core/canvas/node-agent-paths.d.ts +8 -0
  182. package/dist/core/canvas/node-agent-paths.js +1 -0
  183. package/dist/core/canvas/node-recap.d.ts +2 -2
  184. package/dist/core/canvas/node-recap.js +2 -2
  185. package/dist/core/canvas/node-tokens.d.ts +24 -0
  186. package/dist/core/canvas/node-tokens.js +5 -0
  187. package/dist/core/canvas/paths.d.ts +16 -19
  188. package/dist/core/canvas/paths.js +1 -1
  189. package/dist/core/canvas/render-source.d.ts +2 -2
  190. package/dist/core/canvas/render-source.js +6 -9
  191. package/dist/core/canvas/sessions.d.ts +18 -0
  192. package/dist/core/canvas/sessions.js +4 -1
  193. package/dist/core/canvas/types.d.ts +22 -6
  194. package/dist/core/command-hooks/compose.js +1 -1
  195. package/dist/core/command-hooks/discovery.d.ts +15 -1
  196. package/dist/core/command-hooks/discovery.js +1 -1
  197. package/dist/core/command-hooks/help.d.ts +2 -2
  198. package/dist/core/command-hooks/lifecycle-create.d.ts +1 -1
  199. package/dist/core/command-hooks/lifecycle-create.js +1 -1
  200. package/dist/core/command-hooks/transport/exec-invoke.d.ts +2 -0
  201. package/dist/core/command-hooks/transport/exec-invoke.js +1 -1
  202. package/dist/core/command-hooks/transport/exec-lifecycle.d.ts +1 -0
  203. package/dist/core/command-hooks/transport/exec-lifecycle.js +1 -1
  204. package/dist/core/command-hooks/transport/plan.d.ts +3 -0
  205. package/dist/core/command-hooks/transport/plan.js +1 -0
  206. package/dist/core/command-plugins/discovery.js +1 -1
  207. package/dist/core/command-plugins/transport/http-invoke.js +2 -2
  208. package/dist/core/command.d.ts +6 -2
  209. package/dist/core/command.js +6 -6
  210. package/dist/core/config.d.ts +8 -5
  211. package/dist/core/config.js +1 -1
  212. package/dist/core/conversation-store/client-worker.d.ts +1 -0
  213. package/dist/core/conversation-store/client-worker.js +1 -0
  214. package/dist/core/conversation-store/client.d.ts +2 -0
  215. package/dist/core/conversation-store/client.js +2 -0
  216. package/dist/core/conversation-store/file-store.d.ts +37 -0
  217. package/dist/core/conversation-store/file-store.js +9 -0
  218. package/dist/core/conversation-store/index.d.ts +50 -0
  219. package/dist/core/conversation-store/index.js +1 -0
  220. package/dist/core/conversation-store/listener.d.ts +4 -0
  221. package/dist/core/conversation-store/listener.js +3 -0
  222. package/dist/core/conversation-store/patch.d.ts +1 -0
  223. package/dist/core/conversation-store/patch.js +1 -0
  224. package/dist/core/directory/join.d.ts +27 -0
  225. package/dist/core/directory/join.js +1 -0
  226. package/dist/core/disk-usage.d.ts +4 -0
  227. package/dist/core/disk-usage.js +1 -0
  228. package/dist/core/events/emit.d.ts +2 -0
  229. package/dist/core/events/emit.js +3 -1
  230. package/dist/core/feed/feed.js +4 -4
  231. package/dist/core/feed/messages.d.ts +3 -0
  232. package/dist/core/feed/messages.js +6 -6
  233. package/dist/core/fs-utils.d.ts +2 -0
  234. package/dist/core/fs-utils.js +3 -3
  235. package/dist/core/grants/local.d.ts +4 -0
  236. package/dist/core/grants/local.js +1 -0
  237. package/dist/core/grants/mirror.d.ts +44 -0
  238. package/dist/core/grants/mirror.js +4 -0
  239. package/dist/core/grants/on-sync.d.ts +22 -0
  240. package/dist/core/grants/on-sync.js +1 -0
  241. package/dist/core/grants/remove.d.ts +7 -0
  242. package/dist/core/grants/remove.js +1 -0
  243. package/dist/core/grants/sandboxes.d.ts +7 -0
  244. package/dist/core/grants/sandboxes.js +1 -0
  245. package/dist/core/grants/sync.d.ts +12 -0
  246. package/dist/core/grants/sync.js +1 -0
  247. package/dist/core/help/memory-extensions.d.ts +3 -0
  248. package/dist/core/help/memory-extensions.js +2 -0
  249. package/dist/core/help.d.ts +14 -0
  250. package/dist/core/help.js +7 -7
  251. package/dist/core/host-exports/builtins.js +3 -3
  252. package/dist/core/host-exports/export.js +3 -3
  253. package/dist/core/human/action-binding.d.ts +2 -0
  254. package/dist/core/human/action-binding.js +1 -1
  255. package/dist/core/human/feedback-companion.d.ts +0 -1
  256. package/dist/core/human/feedback-companion.js +1 -1
  257. package/dist/core/human/page-schema.d.ts +1 -1
  258. package/dist/core/inspector/core.d.ts +2 -0
  259. package/dist/core/inspector/core.js +1 -1
  260. package/dist/core/inspector/tui.js +2 -2
  261. package/dist/core/installed-plugins.js +1 -1
  262. package/dist/core/io.js +6 -6
  263. package/dist/core/keybindings/catalog.d.ts +1 -1
  264. package/dist/core/keybindings/catalog.js +1 -1
  265. package/dist/core/layout-migrate/index.d.ts +15 -0
  266. package/dist/core/layout-migrate/index.js +4 -0
  267. package/dist/core/layout-migrate/paths.d.ts +26 -0
  268. package/dist/core/layout-migrate/paths.js +1 -0
  269. package/dist/core/layout-migrate/stored-paths.d.ts +2 -0
  270. package/dist/core/layout-migrate/stored-paths.js +11 -0
  271. package/dist/core/layout-migrate/stores.d.ts +17 -0
  272. package/dist/core/layout-migrate/stores.js +4 -0
  273. package/dist/core/layout.d.ts +26 -0
  274. package/dist/core/layout.js +2 -0
  275. package/dist/core/llm/run.d.ts +60 -0
  276. package/dist/core/llm/run.js +4 -0
  277. package/dist/core/memory/cli-selector.d.ts +13 -0
  278. package/dist/core/memory/cli-selector.js +1 -0
  279. package/dist/core/memory/history.d.ts +5 -4
  280. package/dist/core/memory/history.js +6 -6
  281. package/dist/core/memory/inline-ref-inventory.d.ts +2 -1
  282. package/dist/core/memory/inline-ref-inventory.js +1 -1
  283. package/dist/core/memory/lint.js +2 -2
  284. package/dist/core/memory/mutation-domain.d.ts +2 -2
  285. package/dist/core/memory/mutation-domain.js +8 -7
  286. package/dist/core/memory/mutations.js +1 -1
  287. package/dist/core/memory/service.d.ts +5 -0
  288. package/dist/core/memory/service.js +2 -2
  289. package/dist/core/memory-resolver.d.ts +11 -7
  290. package/dist/core/memory-resolver.js +2 -2
  291. package/dist/core/model-auth/daemon-login.d.ts +13 -0
  292. package/dist/core/model-auth/daemon-login.js +1 -0
  293. package/dist/core/model-auth/flows.d.ts +45 -0
  294. package/dist/core/model-auth/flows.js +1 -0
  295. package/dist/core/model-auth/handoff-jti.d.ts +4 -0
  296. package/dist/core/model-auth/handoff-jti.js +1 -0
  297. package/dist/core/model-routes.js +1 -1
  298. package/dist/core/nested-stores.d.ts +2 -1
  299. package/dist/core/nested-stores.js +1 -2
  300. package/dist/core/preview-registry.js +2 -2
  301. package/dist/core/profiles/env-store.js +2 -2
  302. package/dist/core/profiles/manifest.d.ts +16 -5
  303. package/dist/core/profiles/manifest.js +1 -1
  304. package/dist/core/provider-management.js +1 -1
  305. package/dist/core/review/birth.js +1 -1
  306. package/dist/core/review/companion.d.ts +0 -2
  307. package/dist/core/review/companion.js +1 -1
  308. package/dist/core/review/fork.js +2 -2
  309. package/dist/core/review/realize.js +1 -1
  310. package/dist/core/review/stage.d.ts +1 -1
  311. package/dist/core/review/stage.js +1 -1
  312. package/dist/core/review/types.d.ts +1 -1
  313. package/dist/core/runs/events.d.ts +34 -0
  314. package/dist/core/runs/events.js +1 -0
  315. package/dist/core/runs/operations.d.ts +43 -0
  316. package/dist/core/runs/operations.js +12 -0
  317. package/dist/core/runtime/bearings-render.d.ts +2 -2
  318. package/dist/core/runtime/bearings-render.js +11 -11
  319. package/dist/core/runtime/bearings.js +2 -2
  320. package/dist/core/runtime/broker/cli-relay.d.ts +5 -0
  321. package/dist/core/runtime/broker/cli-relay.js +1 -0
  322. package/dist/core/runtime/broker/client-registry.d.ts +0 -5
  323. package/dist/core/runtime/broker/client-registry.js +1 -1
  324. package/dist/core/runtime/broker/daemon-ops.d.ts +28 -1
  325. package/dist/core/runtime/broker/daemon-ops.js +1 -1
  326. package/dist/core/runtime/broker/engine-drive.d.ts +0 -2
  327. package/dist/core/runtime/broker/engine-drive.js +1 -1
  328. package/dist/core/runtime/broker/fault-retry.d.ts +9 -0
  329. package/dist/core/runtime/broker/fault-retry.js +1 -1
  330. package/dist/core/runtime/broker/frame-dispatch.d.ts +5 -9
  331. package/dist/core/runtime/broker/frame-dispatch.js +1 -1
  332. package/dist/core/runtime/broker/frame-engine-control.d.ts +3 -2
  333. package/dist/core/runtime/broker/frame-engine-control.js +1 -1
  334. package/dist/core/runtime/broker/frame-memory-refs.d.ts +4 -3
  335. package/dist/core/runtime/broker/frame-memory-refs.js +1 -1
  336. package/dist/core/runtime/broker/frame-read.js +1 -1
  337. package/dist/core/runtime/broker/frame-session.js +1 -1
  338. package/dist/core/runtime/broker/held-deferred-inbox.d.ts +3 -1
  339. package/dist/core/runtime/broker/held-deferred-inbox.js +1 -1
  340. package/dist/core/runtime/broker/inbox.d.ts +1 -2
  341. package/dist/core/runtime/broker/inbox.js +7 -10
  342. package/dist/core/runtime/broker/model-adapter.d.ts +9 -0
  343. package/dist/core/runtime/broker/model-adapter.js +2 -0
  344. package/dist/core/runtime/broker/node-token.d.ts +3 -0
  345. package/dist/core/runtime/broker/node-token.js +1 -0
  346. package/dist/core/runtime/broker/read-ops.js +1 -1
  347. package/dist/core/runtime/broker/rebind.d.ts +2 -0
  348. package/dist/core/runtime/broker/rebind.js +1 -1
  349. package/dist/core/runtime/broker/tool-groups.js +1 -1
  350. package/dist/core/runtime/broker/turn-ignition.d.ts +3 -3
  351. package/dist/core/runtime/broker/turn-ignition.js +1 -1
  352. package/dist/core/runtime/broker-cli.js +3 -3
  353. package/dist/core/runtime/broker-extension-render.d.ts +2 -5
  354. package/dist/core/runtime/broker-extension-render.js +4 -4
  355. package/dist/core/runtime/broker-persona-guidance.js +3 -3
  356. package/dist/core/runtime/broker-protocol.d.ts +7 -8
  357. package/dist/core/runtime/broker.js +1 -1
  358. package/dist/core/runtime/canvas-extensions.d.ts +1 -0
  359. package/dist/core/runtime/canvas-extensions.js +1 -1
  360. package/dist/core/runtime/close.d.ts +9 -0
  361. package/dist/core/runtime/close.js +1 -1
  362. package/dist/core/runtime/deliver-live.js +1 -1
  363. package/dist/core/runtime/fleet.d.ts +1 -1
  364. package/dist/core/runtime/front-door-login.js +1 -1
  365. package/dist/core/runtime/front-door.js +2 -2
  366. package/dist/core/runtime/headless-pi.d.ts +1 -1
  367. package/dist/core/runtime/headless-pi.js +1 -1
  368. package/dist/core/runtime/host.d.ts +1 -1
  369. package/dist/core/runtime/host.js +3 -1
  370. package/dist/core/runtime/invocation.d.ts +3 -0
  371. package/dist/core/runtime/kickoff.d.ts +1 -0
  372. package/dist/core/runtime/kickoff.js +16 -16
  373. package/dist/core/runtime/launch-target.d.ts +1 -1
  374. package/dist/core/runtime/launch-target.js +1 -1
  375. package/dist/core/runtime/launch.d.ts +10 -35
  376. package/dist/core/runtime/launch.js +1 -1
  377. package/dist/core/runtime/lifecycle.js +2 -2
  378. package/dist/core/runtime/memory.d.ts +1 -1
  379. package/dist/core/runtime/memory.js +1 -1
  380. package/dist/core/runtime/model-registry.js +1 -1
  381. package/dist/core/runtime/model-swap.d.ts +17 -19
  382. package/dist/core/runtime/model-swap.js +1 -1
  383. package/dist/core/runtime/node-read.js +3 -2
  384. package/dist/core/runtime/nodes.d.ts +7 -0
  385. package/dist/core/runtime/nodes.js +1 -1
  386. package/dist/core/runtime/outcome-document.js +1 -1
  387. package/dist/core/runtime/package-health.js +2 -2
  388. package/dist/core/runtime/persona.js +2 -2
  389. package/dist/core/runtime/pi-vendored.js +1 -1
  390. package/dist/core/runtime/placement-tmux.d.ts +1 -1
  391. package/dist/core/runtime/placement-tmux.js +1 -1
  392. package/dist/core/runtime/placement.js +1 -1
  393. package/dist/core/runtime/promote.js +1 -1
  394. package/dist/core/runtime/provider-credential-gate.js +2 -2
  395. package/dist/core/runtime/recovery-hold.d.ts +2 -0
  396. package/dist/core/runtime/recovery-hold.js +1 -0
  397. package/dist/core/runtime/recycle.js +1 -1
  398. package/dist/core/runtime/reopen.js +1 -1
  399. package/dist/core/runtime/reset.js +1 -1
  400. package/dist/core/runtime/revive.d.ts +1 -1
  401. package/dist/core/runtime/revive.js +2 -3
  402. package/dist/core/runtime/roadmap.js +4 -4
  403. package/dist/core/runtime/run-command.d.ts +6 -0
  404. package/dist/core/runtime/run-command.js +1 -1
  405. package/dist/core/runtime/session-branch.d.ts +2 -8
  406. package/dist/core/runtime/session-branch.js +1 -1
  407. package/dist/core/runtime/session-list-cache.d.ts +6 -22
  408. package/dist/core/runtime/session-list-cache.js +1 -1
  409. package/dist/core/runtime/situational-context.js +2 -2
  410. package/dist/core/runtime/spawn-env.d.ts +6 -6
  411. package/dist/core/runtime/spawn-env.js +1 -1
  412. package/dist/core/runtime/spawn.d.ts +5 -0
  413. package/dist/core/runtime/spawn.js +2 -2
  414. package/dist/core/runtime/stop-guard.js +2 -2
  415. package/dist/core/runtime/stop-signals.d.ts +2 -2
  416. package/dist/core/runtime/stop-signals.js +1 -1
  417. package/dist/core/runtime/structured-output.d.ts +10 -4
  418. package/dist/core/runtime/structured-output.js +4 -4
  419. package/dist/core/runtime/tmux-bindings.js +3 -3
  420. package/dist/core/runtime/tmux-driver.d.ts +4 -0
  421. package/dist/core/runtime/tmux-driver.js +7 -7
  422. package/dist/core/runtime/view-socket.d.ts +9 -2
  423. package/dist/core/runtime/view-socket.js +1 -1
  424. package/dist/core/runtime/warm-pool.js +1 -1
  425. package/dist/core/scope.d.ts +3 -2
  426. package/dist/core/scope.js +1 -1
  427. package/dist/core/scoped-state/db.js +1 -1
  428. package/dist/core/scoped-state/migrate.js +3 -3
  429. package/dist/core/scoped-state/profiles.d.ts +2 -1
  430. package/dist/core/scoped-state/profiles.js +1 -1
  431. package/dist/core/scoped-state/schema.d.ts +2 -1
  432. package/dist/core/scoped-state/schema.js +4 -4
  433. package/dist/core/scopes.d.ts +2 -2
  434. package/dist/core/scopes.js +1 -1
  435. package/dist/core/secrets.d.ts +1 -0
  436. package/dist/core/secrets.js +2 -2
  437. package/dist/core/spaces/mounts.d.ts +32 -0
  438. package/dist/core/spaces/mounts.js +1 -0
  439. package/dist/core/spaces/open-beneath.d.ts +71 -0
  440. package/dist/core/spaces/open-beneath.js +1 -0
  441. package/dist/core/spaces/permissions.d.ts +46 -0
  442. package/dist/core/spaces/permissions.js +1 -0
  443. package/dist/core/spaces/process.d.ts +27 -0
  444. package/dist/core/spaces/process.js +1 -0
  445. package/dist/core/spaces/space.d.ts +8 -0
  446. package/dist/core/spaces/space.js +2 -0
  447. package/dist/core/spaces/stop.d.ts +8 -0
  448. package/dist/core/spaces/stop.js +2 -0
  449. package/dist/core/spaces/users.d.ts +19 -0
  450. package/dist/core/spaces/users.js +1 -0
  451. package/dist/core/subscription-state.d.ts +2 -0
  452. package/dist/core/subscription-state.js +3 -3
  453. package/dist/core/substrate/frontmatter-validation.js +1 -1
  454. package/dist/core/substrate/injected-store.d.ts +28 -3
  455. package/dist/core/substrate/injected-store.js +2 -1
  456. package/dist/core/substrate/listings.js +1 -1
  457. package/dist/core/substrate/memory-events.d.ts +6 -0
  458. package/dist/core/substrate/memory-events.js +1 -0
  459. package/dist/core/substrate/on-read-node.js +1 -1
  460. package/dist/core/substrate/on-read.d.ts +6 -6
  461. package/dist/core/substrate/on-read.js +5 -5
  462. package/dist/core/substrate/plan.js +1 -1
  463. package/dist/core/substrate/render-node.js +1 -1
  464. package/dist/core/substrate/render.d.ts +4 -3
  465. package/dist/core/substrate/render.js +38 -38
  466. package/dist/core/substrate/schema.d.ts +6 -0
  467. package/dist/core/substrate/schema.js +1 -1
  468. package/dist/core/tui/panel.js +1 -1
  469. package/dist/core/user-settings.d.ts +12 -0
  470. package/dist/core/user-settings.js +1 -1
  471. package/dist/core/worktree-mutation-async.js +5 -5
  472. package/dist/core/worktree-quarantine.js +1 -1
  473. package/dist/core/worktree-remove.d.ts +15 -0
  474. package/dist/core/worktree-remove.js +1 -0
  475. package/dist/core/worktree-sweep.js +6 -6
  476. package/dist/daemon/analytics/compute.d.ts +37 -0
  477. package/dist/daemon/analytics/compute.js +1 -0
  478. package/dist/daemon/analytics/scan-worker.d.ts +1 -0
  479. package/dist/daemon/analytics/scan-worker.js +2 -0
  480. package/dist/daemon/analytics/scan.d.ts +37 -0
  481. package/dist/daemon/analytics/scan.js +1 -0
  482. package/dist/daemon/api/app-listener.d.ts +11 -0
  483. package/dist/daemon/api/app-listener.js +21 -0
  484. package/dist/daemon/api/bridge.js +5 -5
  485. package/dist/daemon/api/handlers/analytics.d.ts +2 -0
  486. package/dist/daemon/api/handlers/analytics.js +1 -0
  487. package/dist/daemon/api/handlers/attach.js +1 -1
  488. package/dist/daemon/api/handlers/bash-jobs.js +2 -2
  489. package/dist/daemon/api/handlers/bash.js +1 -1
  490. package/dist/daemon/api/handlers/broker-ops.js +1 -1
  491. package/dist/daemon/api/handlers/canvas.d.ts +1 -1
  492. package/dist/daemon/api/handlers/canvas.js +6 -6
  493. package/dist/daemon/api/handlers/crons.js +1 -1
  494. package/dist/daemon/api/handlers/files.d.ts +1 -1
  495. package/dist/daemon/api/handlers/files.js +1 -1
  496. package/dist/daemon/api/handlers/health.js +2 -2
  497. package/dist/daemon/api/handlers/hook-exec.d.ts +2 -0
  498. package/dist/daemon/api/handlers/hook-exec.js +1 -0
  499. package/dist/daemon/api/handlers/human-requests.js +1 -1
  500. package/dist/daemon/api/handlers/inbox.js +1 -1
  501. package/dist/daemon/api/handlers/llm.d.ts +2 -0
  502. package/dist/daemon/api/handlers/llm.js +3 -0
  503. package/dist/daemon/api/handlers/memory-reads.d.ts +4 -0
  504. package/dist/daemon/api/handlers/memory-reads.js +1 -0
  505. package/dist/daemon/api/handlers/memory.js +5 -1
  506. package/dist/daemon/api/handlers/messages.js +2 -2
  507. package/dist/daemon/api/handlers/modelauth.js +1 -1
  508. package/dist/daemon/api/handlers/node-outcomes.js +1 -1
  509. package/dist/daemon/api/handlers/node-records.d.ts +2 -0
  510. package/dist/daemon/api/handlers/node-records.js +3 -0
  511. package/dist/daemon/api/handlers/nodes.d.ts +1 -1
  512. package/dist/daemon/api/handlers/nodes.js +1 -1
  513. package/dist/daemon/api/handlers/profiles.d.ts +4 -1
  514. package/dist/daemon/api/handlers/profiles.js +1 -1
  515. package/dist/daemon/api/handlers/reports.js +5 -3
  516. package/dist/daemon/api/handlers/reviews.d.ts +1 -1
  517. package/dist/daemon/api/handlers/reviews.js +1 -1
  518. package/dist/daemon/api/handlers/run-routes.d.ts +2 -0
  519. package/dist/daemon/api/handlers/run-routes.js +1 -0
  520. package/dist/daemon/api/handlers/runs.d.ts +9 -0
  521. package/dist/daemon/api/handlers/runs.js +1 -0
  522. package/dist/daemon/api/handlers/subscriptions.js +1 -1
  523. package/dist/daemon/api/handlers/worktree.js +1 -1
  524. package/dist/daemon/api/map.d.ts +10 -4
  525. package/dist/daemon/api/map.js +2 -2
  526. package/dist/daemon/api/operations.d.ts +18 -0
  527. package/dist/daemon/api/operations.js +1 -0
  528. package/dist/daemon/api/principal.d.ts +42 -0
  529. package/dist/daemon/api/principal.js +1 -0
  530. package/dist/daemon/api/router.d.ts +27 -3
  531. package/dist/daemon/api/router.js +1 -1
  532. package/dist/daemon/api/server.d.ts +4 -0
  533. package/dist/daemon/api/server.js +1 -1
  534. package/dist/daemon/api/vendor-signin.d.ts +7 -0
  535. package/dist/daemon/api/vendor-signin.js +77 -0
  536. package/dist/daemon/api/viewer-presence.d.ts +3 -0
  537. package/dist/daemon/api/viewer-presence.js +1 -0
  538. package/dist/daemon/cron/armed-context.d.ts +4 -0
  539. package/dist/daemon/cron/armed-context.js +1 -1
  540. package/dist/daemon/cron/escalation.js +3 -3
  541. package/dist/daemon/cron/lease-recovery.js +1 -1
  542. package/dist/daemon/cron/sinks.js +2 -2
  543. package/dist/daemon/cron-run.d.ts +5 -5
  544. package/dist/daemon/cron-run.js +1 -1
  545. package/dist/daemon/crtrd-cli.js +1 -1
  546. package/dist/daemon/crtrd.js +8 -7
  547. package/dist/daemon/delivery/run-delivery.d.ts +4 -0
  548. package/dist/daemon/delivery/run-delivery.js +1 -1
  549. package/dist/daemon/fleet.d.ts +1 -1
  550. package/dist/daemon/fleet.js +6 -6
  551. package/dist/daemon/http-dispatcher.d.ts +1 -0
  552. package/dist/daemon/http-dispatcher.js +1 -0
  553. package/dist/daemon/human/finish.js +7 -7
  554. package/dist/daemon/joined-owner.d.ts +5 -0
  555. package/dist/daemon/joined-owner.js +1 -0
  556. package/dist/daemon/manage.js +2 -2
  557. package/dist/daemon/messaging/node-message.js +1 -1
  558. package/dist/daemon/park-activity.js +1 -1
  559. package/dist/daemon/reconcilers/bash-deadline.js +1 -1
  560. package/dist/daemon/reconcilers/broker-supervision.d.ts +1 -1
  561. package/dist/daemon/reconcilers/broker-supervision.js +4 -4
  562. package/dist/daemon/reconcilers/delivery-lane.js +1 -1
  563. package/dist/daemon/reconcilers/live-obligation.d.ts +7 -0
  564. package/dist/daemon/reconcilers/live-obligation.js +1 -1
  565. package/dist/daemon/reconcilers/node-lifecycle/tick.js +1 -1
  566. package/dist/daemon/reconcilers/storage-maintenance.d.ts +20 -1
  567. package/dist/daemon/reconcilers/storage-maintenance.js +1 -1
  568. package/dist/daemon/runs/append-section.d.ts +6 -0
  569. package/dist/daemon/runs/append-section.js +2 -0
  570. package/dist/daemon/runs/observer.d.ts +5 -0
  571. package/dist/daemon/runs/observer.js +4 -0
  572. package/dist/launcher/bwrap.d.ts +4 -0
  573. package/dist/launcher/bwrap.js +2 -0
  574. package/dist/launcher/client.d.ts +2 -0
  575. package/dist/launcher/client.js +3 -0
  576. package/dist/launcher/main.d.ts +1 -0
  577. package/dist/launcher/main.js +3 -0
  578. package/dist/launcher/netrule.d.ts +1 -0
  579. package/dist/launcher/netrule.js +1 -0
  580. package/dist/launcher/seccomp.d.ts +1 -0
  581. package/dist/launcher/seccomp.js +1 -0
  582. package/dist/launcher/spec.d.ts +88 -0
  583. package/dist/launcher/spec.js +1 -0
  584. package/dist/launcher/users.d.ts +18 -0
  585. package/dist/launcher/users.js +7 -0
  586. package/dist/launcher/validate.d.ts +22 -0
  587. package/dist/launcher/validate.js +1 -0
  588. package/dist/migrations/corpus.js +3 -3
  589. package/dist/migrations/runner.js +1 -1
  590. package/dist/native/linux/addon.node +0 -0
  591. package/dist/native/linux.d.ts +29 -0
  592. package/dist/native/linux.js +1 -0
  593. package/dist/pi-extensions/broker-local.js +2 -2
  594. package/dist/pi-extensions/canvas-bash-valve.d.ts +1 -3
  595. package/dist/pi-extensions/canvas-bash-valve.js +4 -7
  596. package/dist/pi-extensions/canvas-context-intro.d.ts +1 -2
  597. package/dist/pi-extensions/canvas-context-intro.js +2 -2
  598. package/dist/pi-extensions/canvas-doc-substrate.js +6 -6
  599. package/dist/pi-extensions/canvas-inbox-watcher.d.ts +2 -1
  600. package/dist/pi-extensions/canvas-inbox-watcher.js +2 -1
  601. package/dist/pi-extensions/canvas-passive-context.d.ts +1 -1
  602. package/dist/pi-extensions/canvas-passive-context.js +11 -11
  603. package/dist/pi-extensions/canvas-recap.js +6 -6
  604. package/dist/pi-extensions/canvas-stophook.js +1 -1
  605. package/dist/pi-extensions/canvas-structured-output.js +4 -4
  606. package/dist/pi-extensions/canvas-tool-guide.js +1 -1
  607. package/dist/pi-extensions/system-prompt-entry.d.ts +15 -0
  608. package/dist/pi-extensions/system-prompt-entry.js +1 -0
  609. package/dist/shared/env.d.ts +5 -1
  610. package/dist/shared/env.js +1 -1
  611. package/dist/shared/strip-cd-prefix.d.ts +1 -0
  612. package/dist/shared/strip-cd-prefix.js +1 -0
  613. package/dist/types.d.ts +31 -15
  614. package/docs/cli/memory-and-preferences.md +3 -1
  615. package/docs/sdk/client.md +34 -0
  616. package/docs/sdk/errors.md +2 -0
  617. package/docs/sdk/memory.md +2 -2
  618. package/docs/sdk/streaming.md +21 -0
  619. package/package.json +19 -4
  620. package/packages/crouter-identity/dist/index.d.ts +54 -0
  621. package/packages/crouter-identity/dist/index.js +166 -0
  622. package/packages/crouter-identity/package.json +33 -0
  623. package/runtime.lock.json +44 -8
  624. package/scripts/build-native-linux.mjs +16 -0
  625. package/scripts/install-runtime.mjs +9 -7
  626. package/scripts/lib/isolation-env.mjs +42 -0
  627. package/scripts/runtime-smoke.mjs +49 -0
  628. package/src/native/linux/addon.c +249 -0
  629. package/dist/core/profiles/state-block.d.ts +0 -4
  630. package/dist/core/profiles/state-block.js +0 -3
  631. package/dist/core/runtime/broker/auth-reload.d.ts +0 -41
  632. package/dist/core/runtime/broker/auth-reload.js +0 -1
  633. package/dist/core/substrate/cli-context-delivery.d.ts +0 -22
  634. package/dist/core/substrate/cli-context-delivery.js +0 -1
@@ -17,6 +17,40 @@ const customSocketClient = new Crouter({ socketPath: '/custom/path/crtrd.sock' }
17
17
 
18
18
  `Crouter` is the default export and the only class an application constructs. `CrtrClient` from `@north-light/crouter-api` is an implementation detail and is not re-exported.
19
19
 
20
+ ## An app connection and refresh
21
+
22
+ ```ts
23
+ import { OAuth2Client } from '@north-light/crouter-sdk';
24
+
25
+ const oauth = new OAuth2Client({clientId, privateKey, keyId, redirectUri, issuer});
26
+ const {url, codeVerifier, state, nonce} = await oauth.authorizeUrl({scopes: ['openid', 'crtr:act'], state: sessionState});
27
+ // Save codeVerifier, state, and nonce in the app session; redirect the user to url.
28
+ const connection = await oauth.exchangeCode({code: callbackCode, codeVerifier, state, nonce});
29
+ const client = oauth.client(connection, store);
30
+ ```
31
+
32
+ `ConnectionStore` provides `load(userId)` and `save(connection)`; it may also implement `lock(userId, fn)`. A multi-process app must implement the lock across every process: the SDK reloads the connection, sends the refresh request only if no other process rotated its token, and saves the result while the lock is held. `MemoryConnectionStore` locks only within one process and is for local development.
33
+
34
+ A Postgres store can hold a transaction-scoped advisory lock for the whole callback (all workers must use the same database):
35
+
36
+ ```ts
37
+ async function lock<T>(userId: string, fn: () => Promise<T>): Promise<T> {
38
+ const db = await pool.connect();
39
+ try {
40
+ await db.query('BEGIN');
41
+ await db.query('SELECT pg_advisory_xact_lock(hashtextextended($1, 0))', [userId]);
42
+ const result = await fn(); // load, token request, and save complete before COMMIT
43
+ await db.query('COMMIT');
44
+ return result;
45
+ } catch (error) {
46
+ await db.query('ROLLBACK');
47
+ throw error;
48
+ } finally { db.release(); }
49
+ }
50
+ ```
51
+
52
+ `refresh_token_reused` means the app's processes raced on one refresh token; the directory invalidates the grant's refresh tokens, so the user reconnects (without another consent page). Fix the store's `lock` before reconnecting or the race will recur. `runtime_provisioning` means the user's runtime is still being set up and the SDK retries the token request.
53
+
20
54
  ## Options
21
55
 
22
56
  | Option | Type | Default | Meaning |
@@ -13,6 +13,8 @@ when-and-why-to-read: When you need Errors, this knowledge should be read
13
13
 
14
14
  Exceptions are for the layer underneath: the SDK rejected an invalid local identifier or configuration, the daemon refused the request, the connection failed, the request timed out, or you aborted.
15
15
 
16
+ An error without `origin` was produced by the SDK because the request reached no server (runtime, router, or directory). Show it as a connection failure, never as a refusal. An OAuth ID token with a missing or mismatched nonce raises the SDK-only `id_token_invalid` code.
17
+
16
18
  ## Classes and mappings
17
19
 
18
20
  Every class below extends `APIError`, which carries `status`, `code`, `message`, `details`, and `headers`.
@@ -45,9 +45,9 @@ Every method accepts `RequestOptions` as its final optional argument (`signal`,
45
45
  | `memory.history(name, query?, options?)` | `name`; `MemoryHistoryQuery` | `MemoryHistoryDTO` |
46
46
  | `memory.resolve(name, scope?, options?)` | `name`; `MemoryScope` | `MemoryDocRefDTO` |
47
47
 
48
- `MemoryDocCreateRequest` requires `name`, `kind`, `when_and_why_to_read`, and `body`; it also accepts `frontmatter`, `extensions`, and the scope fields. `MemoryDocUpdateRequest` requires `rationale`, which is stored in revision history rather than document text, and optionally accepts `body`, `frontmatter`, `extensions`, `unset`, and the scope fields. `MemoryDocMoveRequest` requires `to` and accepts the scope fields.
48
+ `MemoryDocCreateRequest` requires `name`, `kind`, `when_and_why_to_read`, and `body`; it also accepts `frontmatter`, `extensions`, and the scope fields. `frontmatter.privacy` accepts `public`, `friends`, or `private`; creation defaults to persisted `public`. `MemoryDocUpdateRequest` requires `rationale`, which is stored in revision history rather than document text, and optionally accepts `body`, `frontmatter`, `extensions`, `unset`, and the scope fields. Set `frontmatter.privacy` to change the level; it cannot be unset. A legacy document without the field reads as `private`. `MemoryDocMoveRequest` requires `to` and accepts the scope fields.
49
49
 
50
- `retrieve` returns the body by default. With `frontmatter: true`, its `body` is the complete document source. `MemoryDocDTO` also contains `frontmatter`, `representation` (`'leaf-file'` or `'directory-index'`), `links`, and any lint `findings`; document DTOs include their canonical name, kind, scope, extensions, path, store root, physical relative path, and any competing candidates. `resolve` returns the winning document's location and optional plugin name without reading its body.
50
+ `retrieve` returns the body by default. With `frontmatter: true`, its `body` is the complete document source with the effective privacy level included for older documents. `MemoryDocDTO` also contains `privacy`, `frontmatter`, `representation` (`'leaf-file'` or `'directory-index'`), `links`, and any lint `findings`; document DTOs include their canonical name, kind, scope, extensions, path, store root, physical relative path, and any competing candidates. `resolve` returns the winning document's location and optional plugin name without reading its body.
51
51
 
52
52
  ```ts
53
53
  await client.memory.create({
@@ -9,6 +9,27 @@ when-and-why-to-read: When you need Streaming, this knowledge should be read
9
9
 
10
10
  Watch a run as it works: assistant text as it is produced, tool calls as they start and finish, reports as they are pushed, and the settled outcome.
11
11
 
12
+ ## Forwarding a run to a browser
13
+
14
+ The app server keeps the runtime token and connection; the browser connects only to the app. The app server calls `forwardRunEvents(client, runId, {after: cursorFromRequest(request), signal: request.signal})` and returns the Fetch `Response`. After a host cuts the browser connection, EventSource sends its last SSE id as `Last-Event-ID`; `cursorFromRequest` prefers it over the URL's `after` query parameter.
15
+
16
+ Every forwarded run event is an unnamed SSE record with its `sequence_number` as `id` and the complete event object as `data`. Receive all event types with one handler:
17
+
18
+ ```ts
19
+ const source = new EventSource(`/api/runs/${runId}/events?after=${cursor}`);
20
+ source.onmessage = ({data}) => {
21
+ const event = JSON.parse(data);
22
+ if (event.type === 'stream.refused') { showRefusal(event.error); source.close(); return; }
23
+ if (event.type === 'stream.reconnecting') { showConnectionFailure(event.error); return; }
24
+ if (event.type === 'stream.reconnected') { clearConnectionFailure(); return; }
25
+ showRunEvent(event);
26
+ };
27
+ ```
28
+
29
+ The three `stream.*` records are forwarded-stream notices, not daemon run events; they have no `sequence_number` or SSE id. A `stream.refused` carries the full error envelope, including `stream_gap`'s `details.latest_sequence` and `details.earliest_sequence`. The helper ends that response after a refusal. The SDK does not recover a gap by skipping missing events.
30
+
31
+ When the page reloads mid-reply or gets `stream_gap`, the app reads `runs.trace(runId)` through its server and shows each node's `in_progress_message` (`{thinking, text}` or `null`) as that node's open message. The trace's `latest_sequence` is read consistently with its conversations and open messages. Resume `runs.events(runId, {after: trace.latest_sequence})` (or forward with that cursor); do not use a separately read `runs.get` cursor. Continue appending new deltas to the open message, but replace its thinking or text with the complete content repeated by `node.thinking.done` or `node.output_text.done`. This shows the message from its beginning without duplicating content. `runs.get` and every `runs.list` entry expose `latest_sequence` and `last_activity_at`; a cursor equal to `latest_sequence` is not a gap even when older events have expired.
32
+
12
33
  ## The SDK surface
13
34
 
14
35
  ```ts
@@ -1,4 +1,4 @@
1
- import { envProfileId } from '../../../shared/env.js';
1
+ import { envProfileId, isSandboxedNode } from '../../../shared/env.js';
2
2
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
3
3
  import {
4
4
  readFileSync,
@@ -11,7 +11,7 @@ import {
11
11
  realpathSync,
12
12
  } from 'node:fs';
13
13
  import { homedir } from 'node:os';
14
- import { join, resolve, dirname } from 'node:path';
14
+ import { join, resolve, dirname, sep } from 'node:path';
15
15
  import { loadProfileManifest } from '../../../core/profiles/manifest.js';
16
16
  import { stripMarkdownComments } from '../../../core/runtime/command-expansion.js';
17
17
  import { expandShellBlocks, DEFAULT_SHELL_TIMEOUT_MS } from '../../../core/runtime/shell-expansion.js';
@@ -146,6 +146,7 @@ function canonicalDir(path: string): string {
146
146
  * and its ancestors come first; selected-profile project pointers widen the
147
147
  * search without letting a duplicate profile entry displace the local copy. */
148
148
  export function projectClaudeDirs(cwd: string, profileProjectDirs: readonly string[]): string[] {
149
+ if (isSandboxedNode()) return [];
149
150
  const out: string[] = [];
150
151
  const seen = new Set<string>();
151
152
  for (const pointer of [cwd, ...profileProjectDirs]) {
@@ -160,6 +161,7 @@ export function projectClaudeDirs(cwd: string, profileProjectDirs: readonly stri
160
161
  }
161
162
 
162
163
  function selectedProfileProjects(): string[] {
164
+ if (isSandboxedNode()) return [];
163
165
  const profileId = envProfileId() ?? "";
164
166
  if (profileId === "") return [];
165
167
  try {
@@ -184,7 +186,9 @@ function pluginDirs(): { name: string; installPath: string }[] {
184
186
  const name = key.split("@")[0];
185
187
  for (const rec of records ?? []) {
186
188
  if (!rec.installPath) continue;
187
- out.push({ name, installPath: rec.installPath });
189
+ const installPath = resolve(rec.installPath);
190
+ if (isSandboxedNode() && !installPath.startsWith(resolve(HOME) + sep)) continue;
191
+ out.push({ name, installPath });
188
192
  }
189
193
  }
190
194
  return out;
@@ -330,7 +334,7 @@ const TOKEN_REGEX = /\$(?:\{\d+:-[^}]*\}|\{@:\d+(?::\d+)?\}|\d+|ARGUMENTS|@)/;
330
334
  * handler (such as the command shim path above). */
331
335
  function nativePromptBody(name: string, cwd: string): string | undefined {
332
336
  if (name.includes("/") || name.includes("\\") || name.includes("\0")) return undefined;
333
- for (const dir of [PI_PROMPTS, join(cwd, PROJECT_PI_PROMPTS)]) {
337
+ for (const dir of isSandboxedNode() ? [PI_PROMPTS] : [PI_PROMPTS, join(cwd, PROJECT_PI_PROMPTS)]) {
334
338
  try {
335
339
  const raw = readFileSync(join(dir, `${name}.md`), "utf8");
336
340
  return splitFrontmatter(raw).body;
@@ -1,25 +1,12 @@
1
- import { envNodeId } from '../../../shared/env.js';
1
+ import { envNodeId, envProfileId } from '../../../shared/env.js';
2
2
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
3
- import { createMemoryDocSnapshot, listAllMemoryDocs, readMemoryDocContent } from '../../../core/memory-resolver.js';
4
- import { parseSubstrateDoc } from '../../../core/substrate/schema.js';
5
- import { docsByName } from '../../../core/substrate/listings.js';
6
- import { contentReadAttachments } from '../../../core/substrate/on-read.js';
7
- import { brokerExtensionState } from '../../../core/runtime/broker/daemon-ops.js';
8
- import type { NodeConfigSubject } from '../../../core/substrate/subject-fields.js';
3
+ import { localClient } from '../../../api/node-transport.js';
9
4
  import {
10
5
  expandDeterministicCommand,
11
6
  type DeterministicCommandExpansion,
12
7
  } from '../../../core/runtime/command-expansion.js';
13
8
  import { expandShellBlocks, hasShellBlocks, DEFAULT_SHELL_TIMEOUT_MS } from '../../../core/runtime/shell-expansion.js';
14
9
  import type { ChatCommandMetadata } from '../../../core/runtime/command-surface.js';
15
- import {
16
- exposureTarget,
17
- loadContextExposureState,
18
- mergeContextExposureState,
19
- registerDocumentExposure,
20
- saveContextExposureState,
21
- sharedContextExposureState,
22
- } from '../../../core/substrate/injected-store.js';
23
10
  import { piShellRunner } from './pi-shell-runner.js';
24
11
 
25
12
  // memory-slash-commands: any memory doc frontmatter-flagged `slash: true`
@@ -28,15 +15,16 @@ import { piShellRunner } from './pi-shell-runner.js';
28
15
  // the doc body (plus the user's free-text args) as a normal turn instead of
29
16
  // requiring a separate skill-invocation mechanism.
30
17
  //
31
- // Discovery uses one in-process target snapshot for both listing and body
32
- // resolution. The snapshot already resolves exact canonical identities in
33
- // source precedence; this file does not walk memory directories or reimplement
34
- // scope resolution.
18
+ // The daemon resolves the target snapshot and returns command bodies. The
19
+ // broker never opens memory stores; shell blocks run only when invoked here.
35
20
 
36
21
  interface MemoryListItem {
37
22
  name: string;
38
23
  short_form: string;
39
- slash: boolean;
24
+ path: string;
25
+ canonical_body: string;
26
+ execution_body?: string;
27
+ error?: string;
40
28
  }
41
29
 
42
30
  export interface SlashDoc {
@@ -48,85 +36,32 @@ export interface SlashDoc {
48
36
  description: string;
49
37
  }
50
38
 
51
- // Keep each canonical winner's body and path for disclosure classification and
52
- // the existing execution read.
53
- const resolvedMemoryDocs = new Map<string, { path: string; canonicalBody: string }>();
39
+ // Keep each canonical winner's body for registration-time disclosure classification.
40
+ const resolvedMemoryDocs = new Map<string, MemoryListItem>();
54
41
 
55
42
  async function defaultListMemoryDocs(): Promise<MemoryListItem[]> {
56
- const snapshot = createMemoryDocSnapshot();
57
- const items: MemoryListItem[] = [];
58
- const seen = new Set<string>();
59
- for (const memoryDoc of snapshot.docs) {
60
- if (seen.has(memoryDoc.name)) continue;
61
- seen.add(memoryDoc.name);
62
- const doc = parseSubstrateDoc(memoryDoc);
63
- if (doc === null) continue;
64
- items.push({
65
- name: memoryDoc.name,
66
- short_form: doc.shortForm,
67
- slash: doc.slash,
68
- });
69
- }
70
-
71
- // Resolve eligible canonical names against the same target snapshot that
72
- // supplied the list rows.
73
- const resolved = snapshot.resolve(items.filter((item) => item.slash).map((item) => item.name));
43
+ const node = envNodeId();
44
+ const query = new URLSearchParams(node ? { node } : { cwd: process.cwd(), ...(envProfileId() ? { profile: envProfileId() } : {}) });
45
+ const docs = await localClient({ autostart: false }).request<MemoryListItem[]>('GET', `/v1/memory/slash?${query}`);
74
46
  resolvedMemoryDocs.clear();
75
- for (const [name, doc] of resolved) {
76
- resolvedMemoryDocs.set(name, { path: doc.path, canonicalBody: doc.body });
77
- }
78
- return items;
47
+ for (const doc of docs) resolvedMemoryDocs.set(doc.name, doc);
48
+ return docs;
79
49
  }
80
50
 
81
- function defaultReadMemoryDoc(name: string): { path: string; canonicalBody: string; executionBody: string } {
51
+ function defaultReadMemoryDoc(name: string): { canonicalBody: string; executionBody: string } {
82
52
  const resolved = resolvedMemoryDocs.get(name);
83
53
  if (resolved === undefined) throw new Error(`memory slash document was not resolved: ${name}`);
84
- return {
85
- path: resolved.path,
86
- canonicalBody: resolved.canonicalBody,
87
- executionBody: readMemoryDocContent(resolved.path),
88
- };
54
+ if (resolved.error !== undefined) throw new Error(resolved.error);
55
+ if (resolved.execution_body === undefined) throw new Error(`memory slash document has no execution body: ${name}`);
56
+ return { canonicalBody: resolved.canonical_body, executionBody: resolved.execution_body };
89
57
  }
90
58
 
91
- /** The node-config subject the companion `memory-read` gates evaluate against.
92
- * Unreachable daemon = null, which skips gated docs and delivers ungated ones. */
93
- async function slashSubject(nodeId: string): Promise<NodeConfigSubject | null> {
94
- try {
95
- return (await brokerExtensionState(nodeId)).subject;
96
- } catch {
97
- return null;
98
- }
99
- }
100
-
101
- /** Invoking a document is content delivery, so it carries the same companions
102
- * `crtr memory read` attaches: the directory listings that place it, and every
103
- * corpus doc whose `memory-read` route matches its name. Returns the blocks to
104
- * prepend and records the whole delivery in the exposure ledger. Without a node
105
- * identity nothing records and nothing attaches. */
106
- async function slashReadAttachments(name: string, path: string, body: string): Promise<string[]> {
107
- const nodeId = envNodeId();
108
- if (nodeId === undefined || nodeId === "") return [];
109
- const state = sharedContextExposureState(nodeId);
110
- mergeContextExposureState(state, loadContextExposureState(nodeId));
111
- const target = exposureTarget(state, "transcript");
112
- // Register the invoked body BEFORE the attachments, so the listings and
113
- // companion routes dedupe against the document about to be delivered.
114
- registerDocumentExposure(target, path, body, "content");
115
- let blocks: string[] = [];
116
- try {
117
- const byName = docsByName(listAllMemoryDocs(undefined, true, true));
118
- // No `explicit`: the read leaf returns a doc's own directory listing in a
119
- // separate field, while this path has only the turn — it wants that
120
- // listing attached as a block like every other.
121
- blocks = contentReadAttachments(
122
- { name, path, body },
123
- { subject: await slashSubject(nodeId), target, byName, listings: "blocks" },
124
- ).blocks;
125
- } catch {
126
- blocks = [];
127
- }
128
- saveContextExposureState(nodeId, state);
129
- return blocks;
59
+ async function slashReadAttachments(name: string): Promise<{ blocks: string[]; execution_body: string }> {
60
+ const node = envNodeId();
61
+ if (!node) throw new Error('memory slash attachments require a node');
62
+ return localClient({ autostart: false }).request<{ blocks: string[]; execution_body: string }>(
63
+ 'POST', '/v1/memory/slash/attachments', { node, name },
64
+ );
130
65
  }
131
66
 
132
67
  function hasNodeRelativeExpansion(body: string): boolean {
@@ -141,20 +76,13 @@ export function slashCommandName(docName: string): string {
141
76
  /** Every `slash: true` canonical winner, scope-precedence-deduped by name. A
142
77
  * doc without `slash` (or `slash: false`) is silently excluded. */
143
78
  export async function discoverSlashDocs(): Promise<SlashDoc[]> {
144
- let items: MemoryListItem[];
145
- try {
146
- items = await defaultListMemoryDocs();
147
- } catch {
148
- return [];
149
- }
79
+ const items = await defaultListMemoryDocs();
150
80
  const seen = new Set<string>();
151
81
  const out: SlashDoc[] = [];
152
82
  for (const item of items) {
153
83
  if (seen.has(item.name)) continue;
154
84
  seen.add(item.name);
155
- if (item.slash) {
156
- out.push({ name: item.name, commandName: slashCommandName(item.name), description: item.short_form });
157
- }
85
+ out.push({ name: item.name, commandName: slashCommandName(item.name), description: item.short_form });
158
86
  }
159
87
  return out;
160
88
  }
@@ -163,9 +91,7 @@ export default async function (pi: ExtensionAPI) {
163
91
  const docs = await discoverSlashDocs();
164
92
  for (const doc of docs) {
165
93
  let expansion: DeterministicCommandExpansion | undefined;
166
- let exposure: { path: string; body: string } | undefined;
167
94
  let discloseExpansion = false;
168
- let loadError: unknown;
169
95
  try {
170
96
  const body = defaultReadMemoryDoc(doc.name);
171
97
  expansion = {
@@ -173,10 +99,10 @@ export default async function (pi: ExtensionAPI) {
173
99
  commandName: doc.commandName,
174
100
  body: body.executionBody,
175
101
  };
176
- exposure = { path: body.path, body: body.canonicalBody };
177
102
  discloseExpansion = !hasNodeRelativeExpansion(body.canonicalBody);
178
- } catch (err) {
179
- loadError = err;
103
+ } catch {
104
+ // Keep registration for a document that became unreadable; invocation
105
+ // reports the current read error without executing the cached body.
180
106
  }
181
107
 
182
108
  // pi preserves unknown registration fields on the resolved command object.
@@ -193,28 +119,23 @@ export default async function (pi: ExtensionAPI) {
193
119
  ? {}
194
120
  : { gateway: true as const, ...(discloseExpansion ? { expansion } : {}) }),
195
121
  handler: async (args, ctx) => {
196
- if (expansion === undefined) {
197
- ctx.ui.notify(
198
- `/${doc.commandName}: failed to read memory doc "${doc.name}": ${loadError instanceof Error ? loadError.message : String(loadError)}`,
199
- "error",
200
- );
122
+ let current: { blocks: string[]; execution_body: string };
123
+ try {
124
+ current = await slashReadAttachments(doc.name);
125
+ } catch (error) {
126
+ ctx.ui.notify(`/${doc.commandName}: failed to read memory doc "${doc.name}": ${error instanceof Error ? error.message : String(error)}`, "error");
201
127
  return;
202
128
  }
203
- // Shell runs HERE and only here. `expandDeterministicCommand` stays
204
- // inert because it also drives the attach viewer's pre-submit preview,
205
- // which re-expands on every keystroke — executing there would run the
206
- // document's commands repeatedly while the user is still typing. This
207
- // handler fires once, at submit, so the preview shows the command text
208
- // and the submitted turn carries its output.
209
- const expanded = expandDeterministicCommand(expansion, args ?? "");
129
+ // The daemon resolves the current slash document and attachments in one
130
+ // request, before the document can execute any shell block.
131
+ const expanded = expandDeterministicCommand({ kind: 'memory-slash', commandName: doc.commandName, body: current.execution_body }, args ?? "");
210
132
  const content = hasShellBlocks(expanded)
211
133
  ? await expandShellBlocks(
212
134
  expanded,
213
135
  piShellRunner(pi, (ctx as { cwd?: string }).cwd ?? process.cwd(), DEFAULT_SHELL_TIMEOUT_MS),
214
136
  )
215
137
  : expanded;
216
- const blocks =
217
- exposure === undefined ? [] : await slashReadAttachments(doc.name, exposure.path, exposure.body);
138
+ const blocks = current.blocks;
218
139
  pi.sendMessage(
219
140
  {
220
141
  customType: "memory-slash-command",
@@ -11,12 +11,17 @@ type ProviderErrorClassification = {
11
11
  status?: number;
12
12
  reason: string;
13
13
  hadRetryAfterHeader: boolean;
14
+ /** Cooldown this classification imposes regardless of any Retry-After header. */
15
+ cooldownMs?: number;
16
+ };
17
+ type ProviderClassificationPolicy = {
18
+ rotateOnAuth: boolean;
14
19
  };
15
20
  /** Test seam for the policy mapping over the shared recognition signal. */
16
21
  export declare function __classifyProviderErrorForTest(error: unknown, observed?: {
17
22
  status?: number;
18
23
  retryAfterMs?: number;
19
- }): ProviderErrorClassification;
24
+ }, policy?: ProviderClassificationPolicy): ProviderErrorClassification;
20
25
  declare function defaultSleep(ms: number): Promise<void>;
21
26
  /** Test-only seam: override the sleep implementation used for in-place/wait-out retries. Pass undefined to restore the default. */
22
27
  export declare function __setSleepForTest(fn: typeof defaultSleep | undefined): void;
@@ -1,7 +1,7 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { createAssistantMessageEventStream, } from '@earendil-works/pi-ai';
3
3
  import { anthropicMessagesApi, openAICodexResponsesApi } from '@earendil-works/pi-ai/compat';
4
- import { ANTHROPIC_PROVIDER_ID, OPENAI_CODEX_PROVIDER_ID, INVALID_REFRESH_TOKEN_BACKOFF_MS, formatStatusLine, getProviderLabel, isManagedProvider, isInvalidRefreshTokenError, addSubscription, enrichManagedLoginIdentity, markSubscriptionAuthenticationFailed, markSubscriptionRateLimited, markSubscriptionSuccess, parseRetryAfterHeader, promoteSubscription, readRotationConfig, readSubscriptionPool, refreshSubscriptionCredential, removeManagedAccount, resolveProviderCandidates, upsertSubscriptionWithUniqueAccount, } from '../../../core/subscription-state.js';
4
+ import { ANTHROPIC_PROVIDER_ID, OPENAI_CODEX_PROVIDER_ID, INVALID_REFRESH_TOKEN_BACKOFF_MS, formatStatusLine, getProviderLabel, isManagedProvider, isInvalidRefreshTokenError, addSubscription, enrichManagedLoginIdentity, markSubscriptionAuthenticationFailed, markSubscriptionRateLimited, markSubscriptionSuccess, retryAfterMsFromHeaders, retryAfterMsFromError, promoteSubscription, readRotationConfig, readSubscriptionPool, refreshSubscriptionCredential, removeManagedAccount, resolveProviderCandidates, upsertSubscriptionWithUniqueAccount, } from '../../../core/subscription-state.js';
5
5
  import { readMergedLaunchConfig } from '../../../core/config.js';
6
6
  import { expandModelCandidates, modelRequestFromConfig, probeRouteAvailability } from '../../../core/model-routes.js';
7
7
  import { envModelExact, envNodeId } from '../../../shared/env.js';
@@ -23,6 +23,11 @@ const TRANSIENT_RETRY_BACKOFFS_MS = [250, 750];
23
23
  // so cool it down only briefly -- long enough to rotate to another credential / fallback
24
24
  // provider, short enough that it comes back quickly if it was the only option.
25
25
  const TRANSIENT_ROTATE_COOLDOWN_MS = 60 * 1000;
26
+ // A 401/403 before any content means the provider refused this credential (a lapsed
27
+ // plan, a revoked server-side key). That is not a transient blip, but it is not proof
28
+ // the account is gone either: cool the account for 30 minutes and rotate, rather than
29
+ // the 30-day invalid_grant backoff. Exact requests keep the auth error.
30
+ const AUTH_REJECTED_COOLDOWN_MS = 30 * 60 * 1000;
26
31
  // A few short outer retries absorb a normal multi-broker collision; if the metadata file
27
32
  // remains busy, the provider call still proceeds. Subscription bookkeeping must never turn a
28
33
  // healthy model response into a fatal generation fault.
@@ -38,7 +43,7 @@ function configuredValue(state, key, envName) {
38
43
  function isExactModel(state) {
39
44
  return state.options === undefined && envModelExact();
40
45
  }
41
- function classifyProviderSignal(signal) {
46
+ function classifyProviderSignal(signal, policy) {
42
47
  const hadRetryAfterHeader = signal.retryAfterMs !== undefined;
43
48
  if (signal.kind === 'rate-limit' && signal.matchedOn === 'status') {
44
49
  return { kind: 'rate_limit', status: signal.status, reason: 'http 429', hadRetryAfterHeader };
@@ -50,6 +55,9 @@ function classifyProviderSignal(signal) {
50
55
  return { kind: 'transient', status: signal.status, reason: `http ${signal.status}`, hadRetryAfterHeader };
51
56
  }
52
57
  const textMatches = signal.textMatches ?? (signal.matchedOn === 'text' && signal.match ? [{ kind: signal.kind, match: signal.match }] : []);
58
+ if (policy.rotateOnAuth && signal.kind === 'auth' && signal.matchedOn === 'status' && !textMatches.some(({ kind }) => kind === 'rate-limit')) {
59
+ return { kind: 'rate_limit', status: signal.status, reason: `http ${signal.status}`, hadRetryAfterHeader, cooldownMs: AUTH_REJECTED_COOLDOWN_MS };
60
+ }
53
61
  const transientMatch = textMatches.find(({ kind }) => kind === 'connection' || kind === 'socket' || kind === 'service-unavailable' || kind === 'overloaded');
54
62
  if (transientMatch) {
55
63
  return { kind: 'transient', status: signal.status, reason: transientMatch.match.toLowerCase(), hadRetryAfterHeader };
@@ -60,7 +68,7 @@ function classifyProviderSignal(signal) {
60
68
  }
61
69
  return { kind: 'fatal', status: signal.status, reason: 'unclassified', hadRetryAfterHeader };
62
70
  }
63
- function classifyProviderFailure(failure, retryAfterMs) {
71
+ function classifyProviderFailure(failure, retryAfterMs, policy) {
64
72
  const nested = failure.causes ?? [];
65
73
  const code = failure.code ?? nested.find((cause) => cause.code !== undefined)?.code;
66
74
  const errno = failure.errno ?? nested.find((cause) => cause.errno !== undefined)?.errno;
@@ -73,14 +81,11 @@ function classifyProviderFailure(failure, retryAfterMs) {
73
81
  code,
74
82
  errno,
75
83
  message,
76
- }));
77
- }
78
- function classifyProviderError(error, observed) {
79
- return classifyProviderFailure(normalizeProviderFailure(error, { status: observed.status }), observed.retryAfterMs);
84
+ }), policy);
80
85
  }
81
86
  /** Test seam for the policy mapping over the shared recognition signal. */
82
- export function __classifyProviderErrorForTest(error, observed = {}) {
83
- return classifyProviderError(error, observed);
87
+ export function __classifyProviderErrorForTest(error, observed = {}, policy = { rotateOnAuth: false }) {
88
+ return classifyProviderFailure(normalizeProviderFailure(error, { status: observed.status }), observed.retryAfterMs, policy);
84
89
  }
85
90
  function providerFailureAssistantMessage(model, failure) {
86
91
  const message = toAssistantError(model, failure.message);
@@ -214,15 +219,6 @@ function toAssistantError(model, errorMessage) {
214
219
  timestamp: Date.now(),
215
220
  };
216
221
  }
217
- function retryAfterMs(headers) {
218
- const retryAfterMsHeader = headers["retry-after-ms"];
219
- if (retryAfterMsHeader) {
220
- const millis = Number(retryAfterMsHeader);
221
- if (Number.isFinite(millis))
222
- return Math.max(0, millis);
223
- }
224
- return parseRetryAfterHeader(headers["retry-after"]);
225
- }
226
222
  // Extract an account identity supplied directly by the OAuth credential.
227
223
  function loginAccountId(cred) {
228
224
  return typeof cred.accountId === "string" && cred.accountId.trim() ? cred.accountId.trim() : undefined;
@@ -392,6 +388,7 @@ export function __setStreamForProviderForTest(fn) {
392
388
  // A genuine rate limit ends the attempt immediately (the caller cools the credential down
393
389
  // and rotates); a transient blip gets a short in-place retry budget before giving up.
394
390
  async function streamProviderAttempt(state, providerId, model, context, options, credential, outer) {
391
+ const policy = { rotateOnAuth: !isExactModel(state) };
395
392
  for (let attempt = 1;; attempt++) {
396
393
  const attemptAt = Date.now();
397
394
  let observedStatus;
@@ -429,7 +426,7 @@ async function streamProviderAttempt(state, providerId, model, context, options,
429
426
  onResponse: async (response, responseModel) => {
430
427
  observedStatus = response.status;
431
428
  if (response.status === 429 || response.status === 503) {
432
- requestedRetryAfterMs = retryAfterMs(response.headers);
429
+ requestedRetryAfterMs = retryAfterMsFromHeaders(response.headers);
433
430
  }
434
431
  await options?.onResponse?.(response, responseModel);
435
432
  },
@@ -474,7 +471,7 @@ async function streamProviderAttempt(state, providerId, model, context, options,
474
471
  }
475
472
  failure = normalizeProviderFailure(event.error, { status: observedStatus });
476
473
  failureMessage = failure.message;
477
- classification = classifyProviderFailure(failure, requestedRetryAfterMs);
474
+ classification = classifyProviderFailure(failure, requestedRetryAfterMs, policy);
478
475
  if (classification.kind === "fatal" || sawContent) {
479
476
  emit({ ...event, error: providerFailureAssistantMessage(model, failure) });
480
477
  flush();
@@ -501,9 +498,10 @@ async function streamProviderAttempt(state, providerId, model, context, options,
501
498
  flush();
502
499
  return { result: "fatal", attemptAt };
503
500
  }
501
+ requestedRetryAfterMs ??= retryAfterMsFromError(error);
504
502
  failure = normalizeProviderFailure(error, { status: observedStatus });
505
503
  failureMessage = failure.message;
506
- classification = classifyProviderFailure(failure, requestedRetryAfterMs);
504
+ classification = classifyProviderFailure(failure, requestedRetryAfterMs, policy);
507
505
  if (classification.kind === "fatal" || sawContent) {
508
506
  emit({
509
507
  type: "error",
@@ -524,7 +522,7 @@ async function streamProviderAttempt(state, providerId, model, context, options,
524
522
  return { result: "success", attemptAt };
525
523
  }
526
524
  if (classification.kind === "rate_limit") {
527
- return { result: "rate_limited", retryAfterMs: requestedRetryAfterMs, attemptAt, classification };
525
+ return { result: "rate_limited", retryAfterMs: classification.cooldownMs ?? requestedRetryAfterMs, attemptAt, classification, failureMessage };
528
526
  }
529
527
  // Transient: retry the SAME credential/provider in place first -- no cooldown for the
530
528
  // quick in-place budget.
@@ -582,7 +580,10 @@ async function runManagedProvider(state, model, context, options, stream) {
582
580
  : state.runtimeContext.thinkingLevel;
583
581
  const request = modelRequestFromConfig(`${model.provider}/${model.id}${liveThinking ? `:${liveThinking}` : ''}`, configuredValue(state, 'modelIntent', 'CRTR_MODEL_INTENT'), isExactModel(state));
584
582
  const expanded = request ? expandModelCandidates(request, configuredValue(state, 'nodeCwd', 'CRTR_NODE_CWD'), configuredValue(state, 'profileId', 'CRTR_PROFILE_ID')) : [];
585
- const liveRank = expanded.find((candidate) => candidate.providerId === model.provider && candidate.modelId === model.id && candidate.thinkingLevel === liveThinking)?.rank ?? 0;
583
+ // The node's thinking level holds on its own model; a route to another
584
+ // provider or model takes that cell's thinking.
585
+ const onLiveModel = (candidate) => candidate.providerId === model.provider && candidate.modelId === model.id;
586
+ const liveRank = (expanded.find((candidate) => onLiveModel(candidate) && candidate.thinkingLevel === liveThinking) ?? expanded.find(onLiveModel))?.rank ?? 0;
586
587
  const routeCandidates = expanded.slice(liveRank)
587
588
  .map((route) => {
588
589
  const registry = state.runtimeContext?.modelRegistry;
@@ -592,9 +593,10 @@ async function runManagedProvider(state, model, context, options, stream) {
592
593
  const availability = probeRouteAvailability(route, registry);
593
594
  if (!availability.registered || (!isManagedProvider(route.providerId) && !availability.authenticated))
594
595
  return undefined;
595
- return { routeId: route.routeId, credentialSource: route.credentialSource, rank: route.rank, providerId: route.providerId, model: routeModel, thinkingLevel: route.thinkingLevel };
596
+ return { routeId: route.routeId, credentialSource: route.credentialSource, rank: route.rank, providerId: route.providerId, model: routeModel, thinkingLevel: onLiveModel(route) ? liveThinking : route.thinkingLevel };
596
597
  })
597
- .filter((candidate) => candidate !== undefined);
598
+ .filter((candidate) => candidate !== undefined)
599
+ .filter((candidate, index, all) => all.findIndex((other) => other.routeId === candidate.routeId && other.providerId === candidate.providerId && other.model.id === candidate.model.id && other.thinkingLevel === candidate.thinkingLevel) === index);
598
600
  const isLiveRoute = (candidate) => candidate.providerId === model.provider && candidate.model.id === model.id && candidate.thinkingLevel === liveThinking;
599
601
  // A cooling or unauthenticated managed route contributes its diagnostic and may
600
602
  // later re-enter when a credential becomes ready, but it is never selected now.
@@ -618,6 +620,9 @@ async function runManagedProvider(state, model, context, options, stream) {
618
620
  const cooldownAttempts = new Map();
619
621
  let terminalModel = model;
620
622
  let lastTransientFailure;
623
+ // The last pre-content 401/403 that cooled a route; its text is the only
624
+ // record of why the routes ran out when they end in a cooling error.
625
+ let lastAuthRejection;
621
626
  // Any HTTP response observed this pass (a status, a Retry-After header, or a
622
627
  // rate-limit classification) proves this process can still reach a provider,
623
628
  // clearing the poisoned-transport suspicion.
@@ -661,6 +666,8 @@ async function runManagedProvider(state, model, context, options, stream) {
661
666
  const cooldownMs = result.result === 'rate_limited' ? result.retryAfterMs ?? DEFAULT_RATE_LIMIT_BACKOFF_MS : TRANSIENT_ROTATE_COOLDOWN_MS;
662
667
  if (result.result === 'transient_exhausted')
663
668
  lastTransientFailure = result;
669
+ if (result.result === 'rate_limited' && (result.classification?.status === 401 || result.classification?.status === 403))
670
+ lastAuthRejection = result;
664
671
  localCoolingDeadlines.set(routeId, Math.max(localCoolingDeadlines.get(routeId) ?? 0, Date.now() + cooldownMs));
665
672
  logRouteQueueAdvance(candidate, candidates[0], result);
666
673
  continue;
@@ -715,6 +722,8 @@ async function runManagedProvider(state, model, context, options, stream) {
715
722
  const cooldownMs = result.result === 'rate_limited' ? result.retryAfterMs ?? DEFAULT_RATE_LIMIT_BACKOFF_MS : TRANSIENT_ROTATE_COOLDOWN_MS;
716
723
  if (result.result === 'transient_exhausted')
717
724
  lastTransientFailure = result;
725
+ if (result.result === 'rate_limited' && (result.classification?.status === 401 || result.classification?.status === 403))
726
+ lastAuthRejection = result;
718
727
  if (result.result === 'rate_limited' && result.retryAfterMs !== undefined) {
719
728
  const persisted = await recordSubscriptionMutation(`recording ${getProviderLabel(providerId)} server cooldown`, () => markSubscriptionRateLimited(providerId, freshCredential.label, cooldownMs, result.attemptAt));
720
729
  if (persisted)
@@ -750,7 +759,11 @@ async function runManagedProvider(state, model, context, options, stream) {
750
759
  }
751
760
  state.transportOnlyExhaustedPasses = 0;
752
761
  if (coolingDeadline !== undefined) {
753
- emitCoolingError(stream, terminalModel, new ManagedProviderCoolingError(`All provider routes are rate limited (cooling down ~${Math.ceil((coolingDeadline - Date.now()) / 1000)}s; tried ${tried})`, coolingDeadline));
762
+ const cooling = `cooling down ~${Math.ceil((coolingDeadline - Date.now()) / 1000)}s`;
763
+ const message = lastAuthRejection === undefined
764
+ ? `All provider routes are rate limited (${cooling}; tried ${tried})`
765
+ : `All provider routes are rate limited or rejected (${cooling}; last rejection ${lastAuthRejection.classification.reason}: ${lastAuthRejection.failureMessage ?? 'no error message'}; tried ${tried})`;
766
+ emitCoolingError(stream, terminalModel, new ManagedProviderCoolingError(message, coolingDeadline));
754
767
  return;
755
768
  }
756
769
  const authFailure = [...authFailures][0];
@@ -856,7 +869,8 @@ async function restoreHigherRankIfAvailable(state, pi, ctx) {
856
869
  if (!request || !ctx.model)
857
870
  return false;
858
871
  const candidates = expandModelCandidates(request, configuredValue(state, 'nodeCwd', 'CRTR_NODE_CWD'), configuredValue(state, 'profileId', 'CRTR_PROFILE_ID'));
859
- const liveRank = candidates.find((candidate) => candidate.providerId === ctx.model?.provider && candidate.modelId === ctx.model?.id && candidate.thinkingLevel === liveThinking)?.rank;
872
+ const onLiveModel = (candidate) => candidate.providerId === ctx.model?.provider && candidate.modelId === ctx.model?.id;
873
+ const liveRank = (candidates.find((candidate) => onLiveModel(candidate) && candidate.thinkingLevel === liveThinking) ?? candidates.find(onLiveModel))?.rank;
860
874
  if (liveRank === undefined)
861
875
  return false;
862
876
  const preferred = candidates.find((candidate) => candidate.rank < liveRank && resolveProviderCandidates([{