@caupulican/pi-adaptative 0.80.103 → 0.81.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 (401) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/dist/cli/session-picker.d.ts +1 -1
  3. package/dist/cli/session-picker.d.ts.map +1 -1
  4. package/dist/cli/session-picker.js.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +7 -1
  7. package/dist/cli.js.map +1 -1
  8. package/dist/core/agent-session-runtime.d.ts +1 -1
  9. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  10. package/dist/core/agent-session-runtime.js +6 -6
  11. package/dist/core/agent-session-runtime.js.map +1 -1
  12. package/dist/core/agent-session-services.d.ts +1 -1
  13. package/dist/core/agent-session-services.d.ts.map +1 -1
  14. package/dist/core/agent-session-services.js.map +1 -1
  15. package/dist/core/agent-session.d.ts +152 -512
  16. package/dist/core/agent-session.d.ts.map +1 -1
  17. package/dist/core/agent-session.js +635 -4590
  18. package/dist/core/agent-session.js.map +1 -1
  19. package/dist/core/autonomy/session-lane-record.d.ts +1 -1
  20. package/dist/core/autonomy/session-lane-record.d.ts.map +1 -1
  21. package/dist/core/autonomy/session-lane-record.js.map +1 -1
  22. package/dist/core/autonomy/status.d.ts +1 -0
  23. package/dist/core/autonomy/status.d.ts.map +1 -1
  24. package/dist/core/autonomy/status.js +1 -0
  25. package/dist/core/autonomy/status.js.map +1 -1
  26. package/dist/core/autonomy-telemetry.d.ts +80 -0
  27. package/dist/core/autonomy-telemetry.d.ts.map +1 -0
  28. package/dist/core/autonomy-telemetry.js +266 -0
  29. package/dist/core/autonomy-telemetry.js.map +1 -0
  30. package/dist/core/background-lane-controller.d.ts +186 -0
  31. package/dist/core/background-lane-controller.d.ts.map +1 -0
  32. package/dist/core/background-lane-controller.js +691 -0
  33. package/dist/core/background-lane-controller.js.map +1 -0
  34. package/dist/core/bash-execution-controller.d.ts +51 -0
  35. package/dist/core/bash-execution-controller.d.ts.map +1 -0
  36. package/dist/core/bash-execution-controller.js +96 -0
  37. package/dist/core/bash-execution-controller.js.map +1 -0
  38. package/dist/core/bash-executor.d.ts.map +1 -1
  39. package/dist/core/bash-executor.js +9 -2
  40. package/dist/core/bash-executor.js.map +1 -1
  41. package/dist/core/compaction-support.d.ts +56 -0
  42. package/dist/core/compaction-support.d.ts.map +1 -0
  43. package/dist/core/compaction-support.js +99 -0
  44. package/dist/core/compaction-support.js.map +1 -0
  45. package/dist/core/context/artifact-retrieval.d.ts +1 -1
  46. package/dist/core/context/artifact-retrieval.d.ts.map +1 -1
  47. package/dist/core/context/artifact-retrieval.js +1 -1
  48. package/dist/core/context/artifact-retrieval.js.map +1 -1
  49. package/dist/core/context/context-composition.d.ts.map +1 -1
  50. package/dist/core/context/context-composition.js +1 -1
  51. package/dist/core/context/context-composition.js.map +1 -1
  52. package/dist/core/context/tool-output-packer.d.ts +1 -1
  53. package/dist/core/context/tool-output-packer.d.ts.map +1 -1
  54. package/dist/core/context/tool-output-packer.js +1 -1
  55. package/dist/core/context/tool-output-packer.js.map +1 -1
  56. package/dist/core/context-gc.d.ts.map +1 -1
  57. package/dist/core/context-gc.js +1 -1
  58. package/dist/core/context-gc.js.map +1 -1
  59. package/dist/core/context-pipeline.d.ts +223 -0
  60. package/dist/core/context-pipeline.d.ts.map +1 -0
  61. package/dist/core/context-pipeline.js +594 -0
  62. package/dist/core/context-pipeline.js.map +1 -0
  63. package/dist/core/cost/daily-usage.d.ts +1 -1
  64. package/dist/core/cost/daily-usage.d.ts.map +1 -1
  65. package/dist/core/cost/daily-usage.js +1 -1
  66. package/dist/core/cost/daily-usage.js.map +1 -1
  67. package/dist/core/cost/session-usage.d.ts +1 -1
  68. package/dist/core/cost/session-usage.d.ts.map +1 -1
  69. package/dist/core/cost/session-usage.js +1 -1
  70. package/dist/core/cost/session-usage.js.map +1 -1
  71. package/dist/core/delegation/session-worker-result.d.ts +1 -1
  72. package/dist/core/delegation/session-worker-result.d.ts.map +1 -1
  73. package/dist/core/delegation/session-worker-result.js.map +1 -1
  74. package/dist/core/exec.d.ts +2 -0
  75. package/dist/core/exec.d.ts.map +1 -1
  76. package/dist/core/exec.js +15 -9
  77. package/dist/core/exec.js.map +1 -1
  78. package/dist/core/export-html/index.d.ts +1 -1
  79. package/dist/core/export-html/index.d.ts.map +1 -1
  80. package/dist/core/export-html/index.js +2 -2
  81. package/dist/core/export-html/index.js.map +1 -1
  82. package/dist/core/extensions/builtin.d.ts.map +1 -1
  83. package/dist/core/extensions/builtin.js +2 -2
  84. package/dist/core/extensions/builtin.js.map +1 -1
  85. package/dist/core/extensions/runner.d.ts +1 -1
  86. package/dist/core/extensions/runner.d.ts.map +1 -1
  87. package/dist/core/extensions/runner.js.map +1 -1
  88. package/dist/core/extensions/types.d.ts +2 -4
  89. package/dist/core/extensions/types.d.ts.map +1 -1
  90. package/dist/core/extensions/types.js.map +1 -1
  91. package/dist/core/goal-loop-controller.d.ts +24 -0
  92. package/dist/core/goal-loop-controller.d.ts.map +1 -0
  93. package/dist/core/goal-loop-controller.js +83 -0
  94. package/dist/core/goal-loop-controller.js.map +1 -0
  95. package/dist/core/goals/goal-runtime-snapshot.d.ts +1 -1
  96. package/dist/core/goals/goal-runtime-snapshot.d.ts.map +1 -1
  97. package/dist/core/goals/goal-runtime-snapshot.js.map +1 -1
  98. package/dist/core/goals/session-goal-state.d.ts +1 -1
  99. package/dist/core/goals/session-goal-state.d.ts.map +1 -1
  100. package/dist/core/goals/session-goal-state.js.map +1 -1
  101. package/dist/core/index.d.ts +1 -1
  102. package/dist/core/index.d.ts.map +1 -1
  103. package/dist/core/index.js.map +1 -1
  104. package/dist/core/learning/learning-audit.d.ts +1 -1
  105. package/dist/core/learning/learning-audit.d.ts.map +1 -1
  106. package/dist/core/learning/learning-audit.js.map +1 -1
  107. package/dist/core/learning/session-learning-decision.d.ts +1 -1
  108. package/dist/core/learning/session-learning-decision.d.ts.map +1 -1
  109. package/dist/core/learning/session-learning-decision.js.map +1 -1
  110. package/dist/core/local-runtime-controller.d.ts +99 -0
  111. package/dist/core/local-runtime-controller.d.ts.map +1 -0
  112. package/dist/core/local-runtime-controller.js +214 -0
  113. package/dist/core/local-runtime-controller.js.map +1 -0
  114. package/dist/core/memory/providers/transcript-recall.d.ts.map +1 -1
  115. package/dist/core/memory/providers/transcript-recall.js +1 -1
  116. package/dist/core/memory/providers/transcript-recall.js.map +1 -1
  117. package/dist/core/memory-controller.d.ts +139 -0
  118. package/dist/core/memory-controller.d.ts.map +1 -0
  119. package/dist/core/memory-controller.js +323 -0
  120. package/dist/core/memory-controller.js.map +1 -0
  121. package/dist/core/model-router/status.d.ts +1 -1
  122. package/dist/core/model-router/status.d.ts.map +1 -1
  123. package/dist/core/model-router/status.js.map +1 -1
  124. package/dist/core/model-router-controller.d.ts +140 -0
  125. package/dist/core/model-router-controller.d.ts.map +1 -0
  126. package/dist/core/model-router-controller.js +623 -0
  127. package/dist/core/model-router-controller.js.map +1 -0
  128. package/dist/core/model-selection-controller.d.ts +95 -0
  129. package/dist/core/model-selection-controller.d.ts.map +1 -0
  130. package/dist/core/model-selection-controller.js +252 -0
  131. package/dist/core/model-selection-controller.js.map +1 -0
  132. package/dist/core/models/model-ref.d.ts +13 -0
  133. package/dist/core/models/model-ref.d.ts.map +1 -1
  134. package/dist/core/models/model-ref.js +22 -0
  135. package/dist/core/models/model-ref.js.map +1 -1
  136. package/dist/core/profile-filter-controller.d.ts +77 -0
  137. package/dist/core/profile-filter-controller.d.ts.map +1 -0
  138. package/dist/core/profile-filter-controller.js +159 -0
  139. package/dist/core/profile-filter-controller.js.map +1 -0
  140. package/dist/core/reflection-controller.d.ts +111 -0
  141. package/dist/core/reflection-controller.d.ts.map +1 -0
  142. package/dist/core/reflection-controller.js +412 -0
  143. package/dist/core/reflection-controller.js.map +1 -0
  144. package/dist/core/research/model-fitness.d.ts +12 -0
  145. package/dist/core/research/model-fitness.d.ts.map +1 -1
  146. package/dist/core/research/model-fitness.js +21 -0
  147. package/dist/core/research/model-fitness.js.map +1 -1
  148. package/dist/core/research/session-evidence-bundle.d.ts +1 -1
  149. package/dist/core/research/session-evidence-bundle.d.ts.map +1 -1
  150. package/dist/core/research/session-evidence-bundle.js.map +1 -1
  151. package/dist/core/runtime-builder.d.ts +222 -0
  152. package/dist/core/runtime-builder.d.ts.map +1 -0
  153. package/dist/core/runtime-builder.js +640 -0
  154. package/dist/core/runtime-builder.js.map +1 -0
  155. package/dist/core/sdk.d.ts +2 -2
  156. package/dist/core/sdk.d.ts.map +1 -1
  157. package/dist/core/sdk.js +3 -4
  158. package/dist/core/sdk.js.map +1 -1
  159. package/dist/core/session-analytics.d.ts +96 -0
  160. package/dist/core/session-analytics.d.ts.map +1 -0
  161. package/dist/core/session-analytics.js +331 -0
  162. package/dist/core/session-analytics.js.map +1 -0
  163. package/dist/core/session-manager-factory.d.ts +18 -0
  164. package/dist/core/session-manager-factory.d.ts.map +1 -0
  165. package/dist/core/session-manager-factory.js +32 -0
  166. package/dist/core/session-manager-factory.js.map +1 -0
  167. package/dist/core/session-tree-navigator.d.ts +68 -0
  168. package/dist/core/session-tree-navigator.d.ts.map +1 -0
  169. package/dist/core/session-tree-navigator.js +217 -0
  170. package/dist/core/session-tree-navigator.js.map +1 -0
  171. package/dist/core/system-prompt-builder.d.ts +63 -0
  172. package/dist/core/system-prompt-builder.d.ts.map +1 -0
  173. package/dist/core/system-prompt-builder.js +181 -0
  174. package/dist/core/system-prompt-builder.js.map +1 -0
  175. package/dist/core/tool-gate-controller.d.ts +36 -0
  176. package/dist/core/tool-gate-controller.d.ts.map +1 -0
  177. package/dist/core/tool-gate-controller.js +94 -0
  178. package/dist/core/tool-gate-controller.js.map +1 -0
  179. package/dist/core/tools/artifact-retrieve.d.ts.map +1 -1
  180. package/dist/core/tools/artifact-retrieve.js +1 -1
  181. package/dist/core/tools/artifact-retrieve.js.map +1 -1
  182. package/dist/core/tools/bash.d.ts +4 -2
  183. package/dist/core/tools/bash.d.ts.map +1 -1
  184. package/dist/core/tools/bash.js +49 -7
  185. package/dist/core/tools/bash.js.map +1 -1
  186. package/dist/core/tools/file-mutation-queue.d.ts +6 -0
  187. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  188. package/dist/core/tools/file-mutation-queue.js +61 -1
  189. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  190. package/dist/core/tools/find.d.ts +1 -1
  191. package/dist/core/tools/find.d.ts.map +1 -1
  192. package/dist/core/tools/find.js +1 -1
  193. package/dist/core/tools/find.js.map +1 -1
  194. package/dist/core/tools/grep.d.ts +1 -1
  195. package/dist/core/tools/grep.d.ts.map +1 -1
  196. package/dist/core/tools/grep.js +1 -1
  197. package/dist/core/tools/grep.js.map +1 -1
  198. package/dist/core/tools/index.d.ts +1 -1
  199. package/dist/core/tools/index.d.ts.map +1 -1
  200. package/dist/core/tools/index.js +1 -1
  201. package/dist/core/tools/index.js.map +1 -1
  202. package/dist/core/tools/ls.d.ts +1 -1
  203. package/dist/core/tools/ls.d.ts.map +1 -1
  204. package/dist/core/tools/ls.js +1 -1
  205. package/dist/core/tools/ls.js.map +1 -1
  206. package/dist/core/tools/output-accumulator.d.ts +1 -1
  207. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  208. package/dist/core/tools/output-accumulator.js +1 -1
  209. package/dist/core/tools/output-accumulator.js.map +1 -1
  210. package/dist/core/tools/read.d.ts +1 -1
  211. package/dist/core/tools/read.d.ts.map +1 -1
  212. package/dist/core/tools/read.js +1 -1
  213. package/dist/core/tools/read.js.map +1 -1
  214. package/dist/core/tools/render-utils.d.ts.map +1 -1
  215. package/dist/core/tools/render-utils.js +1 -1
  216. package/dist/core/tools/render-utils.js.map +1 -1
  217. package/dist/index.d.ts +2 -3
  218. package/dist/index.d.ts.map +1 -1
  219. package/dist/index.js +3 -4
  220. package/dist/index.js.map +1 -1
  221. package/dist/main.d.ts.map +1 -1
  222. package/dist/main.js +13 -12
  223. package/dist/main.js.map +1 -1
  224. package/dist/modes/interactive/auth-dialogs-controller.d.ts +59 -0
  225. package/dist/modes/interactive/auth-dialogs-controller.d.ts.map +1 -0
  226. package/dist/modes/interactive/auth-dialogs-controller.js +398 -0
  227. package/dist/modes/interactive/auth-dialogs-controller.js.map +1 -0
  228. package/dist/modes/interactive/auto-learn-controller.d.ts +168 -0
  229. package/dist/modes/interactive/auto-learn-controller.d.ts.map +1 -0
  230. package/dist/modes/interactive/auto-learn-controller.js +1239 -0
  231. package/dist/modes/interactive/auto-learn-controller.js.map +1 -0
  232. package/dist/modes/interactive/autocomplete-provider.d.ts +26 -0
  233. package/dist/modes/interactive/autocomplete-provider.d.ts.map +1 -0
  234. package/dist/modes/interactive/autocomplete-provider.js +103 -0
  235. package/dist/modes/interactive/autocomplete-provider.js.map +1 -0
  236. package/dist/modes/interactive/autonomy-commands.d.ts +38 -0
  237. package/dist/modes/interactive/autonomy-commands.d.ts.map +1 -0
  238. package/dist/modes/interactive/autonomy-commands.js +142 -0
  239. package/dist/modes/interactive/autonomy-commands.js.map +1 -0
  240. package/dist/modes/interactive/clipboard-input.d.ts +37 -0
  241. package/dist/modes/interactive/clipboard-input.d.ts.map +1 -0
  242. package/dist/modes/interactive/clipboard-input.js +59 -0
  243. package/dist/modes/interactive/clipboard-input.js.map +1 -0
  244. package/dist/modes/interactive/compaction-queue.d.ts +30 -0
  245. package/dist/modes/interactive/compaction-queue.d.ts.map +1 -0
  246. package/dist/modes/interactive/compaction-queue.js +91 -0
  247. package/dist/modes/interactive/compaction-queue.js.map +1 -0
  248. package/dist/modes/interactive/components/bash-execution.d.ts +1 -1
  249. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  250. package/dist/modes/interactive/components/bash-execution.js +1 -1
  251. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  252. package/dist/modes/interactive/components/branch-summary-message.d.ts +1 -1
  253. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  254. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  255. package/dist/modes/interactive/components/compaction-summary-message.d.ts +1 -1
  256. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  257. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  258. package/dist/modes/interactive/components/custom-message.d.ts +1 -1
  259. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  260. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  261. package/dist/modes/interactive/components/session-selector-search.d.ts +1 -1
  262. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
  263. package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  264. package/dist/modes/interactive/components/session-selector.d.ts +1 -1
  265. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  266. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  267. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  268. package/dist/modes/interactive/components/tool-execution.js +2 -2
  269. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  270. package/dist/modes/interactive/components/tree-selector.d.ts +1 -1
  271. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  272. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  273. package/dist/modes/interactive/config-backup.d.ts +27 -0
  274. package/dist/modes/interactive/config-backup.d.ts.map +1 -0
  275. package/dist/modes/interactive/config-backup.js +146 -0
  276. package/dist/modes/interactive/config-backup.js.map +1 -0
  277. package/dist/modes/interactive/editor-overlay-host.d.ts +48 -0
  278. package/dist/modes/interactive/editor-overlay-host.d.ts.map +1 -0
  279. package/dist/modes/interactive/editor-overlay-host.js +43 -0
  280. package/dist/modes/interactive/editor-overlay-host.js.map +1 -0
  281. package/dist/modes/interactive/extension-ui-host.d.ts +165 -0
  282. package/dist/modes/interactive/extension-ui-host.d.ts.map +1 -0
  283. package/dist/modes/interactive/extension-ui-host.js +610 -0
  284. package/dist/modes/interactive/extension-ui-host.js.map +1 -0
  285. package/dist/modes/interactive/external-editor.d.ts +18 -0
  286. package/dist/modes/interactive/external-editor.d.ts.map +1 -0
  287. package/dist/modes/interactive/external-editor.js +107 -0
  288. package/dist/modes/interactive/external-editor.js.map +1 -0
  289. package/dist/modes/interactive/history-reload-math.d.ts +24 -0
  290. package/dist/modes/interactive/history-reload-math.d.ts.map +1 -0
  291. package/dist/modes/interactive/history-reload-math.js +129 -0
  292. package/dist/modes/interactive/history-reload-math.js.map +1 -0
  293. package/dist/modes/interactive/interactive-mode.d.ts +22 -238
  294. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  295. package/dist/modes/interactive/interactive-mode.js +714 -5910
  296. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  297. package/dist/modes/interactive/key-handlers.d.ts +46 -0
  298. package/dist/modes/interactive/key-handlers.d.ts.map +1 -0
  299. package/dist/modes/interactive/key-handlers.js +80 -0
  300. package/dist/modes/interactive/key-handlers.js.map +1 -0
  301. package/dist/modes/interactive/local-model-commands.d.ts +68 -0
  302. package/dist/modes/interactive/local-model-commands.d.ts.map +1 -0
  303. package/dist/modes/interactive/local-model-commands.js +288 -0
  304. package/dist/modes/interactive/local-model-commands.js.map +1 -0
  305. package/dist/modes/interactive/profile-menu-controller.d.ts +77 -0
  306. package/dist/modes/interactive/profile-menu-controller.d.ts.map +1 -0
  307. package/dist/modes/interactive/profile-menu-controller.js +917 -0
  308. package/dist/modes/interactive/profile-menu-controller.js.map +1 -0
  309. package/dist/modes/interactive/report-commands.d.ts +46 -0
  310. package/dist/modes/interactive/report-commands.d.ts.map +1 -0
  311. package/dist/modes/interactive/report-commands.js +255 -0
  312. package/dist/modes/interactive/report-commands.js.map +1 -0
  313. package/dist/modes/interactive/resource-display.d.ts +73 -0
  314. package/dist/modes/interactive/resource-display.d.ts.map +1 -0
  315. package/dist/modes/interactive/resource-display.js +278 -0
  316. package/dist/modes/interactive/resource-display.js.map +1 -0
  317. package/dist/modes/interactive/resource-shell-commands.d.ts +64 -0
  318. package/dist/modes/interactive/resource-shell-commands.d.ts.map +1 -0
  319. package/dist/modes/interactive/resource-shell-commands.js +202 -0
  320. package/dist/modes/interactive/resource-shell-commands.js.map +1 -0
  321. package/dist/modes/interactive/session-flow-commands.d.ts +139 -0
  322. package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -0
  323. package/dist/modes/interactive/session-flow-commands.js +458 -0
  324. package/dist/modes/interactive/session-flow-commands.js.map +1 -0
  325. package/dist/modes/interactive/session-io-commands.d.ts +73 -0
  326. package/dist/modes/interactive/session-io-commands.d.ts.map +1 -0
  327. package/dist/modes/interactive/session-io-commands.js +202 -0
  328. package/dist/modes/interactive/session-io-commands.js.map +1 -0
  329. package/dist/modes/interactive/settings-selector-flow.d.ts +42 -0
  330. package/dist/modes/interactive/settings-selector-flow.d.ts.map +1 -0
  331. package/dist/modes/interactive/settings-selector-flow.js +287 -0
  332. package/dist/modes/interactive/settings-selector-flow.js.map +1 -0
  333. package/dist/modes/interactive/signal-lifecycle.d.ts +55 -0
  334. package/dist/modes/interactive/signal-lifecycle.d.ts.map +1 -0
  335. package/dist/modes/interactive/signal-lifecycle.js +177 -0
  336. package/dist/modes/interactive/signal-lifecycle.js.map +1 -0
  337. package/dist/modes/interactive/startup-checks.d.ts +38 -0
  338. package/dist/modes/interactive/startup-checks.d.ts.map +1 -0
  339. package/dist/modes/interactive/startup-checks.js +189 -0
  340. package/dist/modes/interactive/startup-checks.js.map +1 -0
  341. package/dist/modes/rpc/rpc-client.d.ts +1 -1
  342. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  343. package/dist/modes/rpc/rpc-client.js.map +1 -1
  344. package/dist/modes/rpc/rpc-types.d.ts +1 -1
  345. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  346. package/dist/modes/rpc/rpc-types.js.map +1 -1
  347. package/dist/utils/paths.d.ts +2 -14
  348. package/dist/utils/paths.d.ts.map +1 -1
  349. package/dist/utils/paths.js +5 -30
  350. package/dist/utils/paths.js.map +1 -1
  351. package/dist/utils/process-memory.d.ts +8 -0
  352. package/dist/utils/process-memory.d.ts.map +1 -0
  353. package/dist/utils/process-memory.js +11 -0
  354. package/dist/utils/process-memory.js.map +1 -0
  355. package/dist/utils/shell.d.ts +0 -9
  356. package/dist/utils/shell.d.ts.map +1 -1
  357. package/dist/utils/shell.js +0 -37
  358. package/dist/utils/shell.js.map +1 -1
  359. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  360. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  361. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  362. package/examples/extensions/sandbox/package-lock.json +2 -2
  363. package/examples/extensions/sandbox/package.json +1 -1
  364. package/examples/extensions/with-deps/package-lock.json +2 -2
  365. package/examples/extensions/with-deps/package.json +1 -1
  366. package/examples/sdk/11-sessions.ts +8 -8
  367. package/examples/sdk/13-session-runtime.ts +1 -1
  368. package/npm-shrinkwrap.json +12 -12
  369. package/package.json +4 -4
  370. package/dist/core/compaction/branch-summarization.d.ts +0 -88
  371. package/dist/core/compaction/branch-summarization.d.ts.map +0 -1
  372. package/dist/core/compaction/branch-summarization.js +0 -243
  373. package/dist/core/compaction/branch-summarization.js.map +0 -1
  374. package/dist/core/compaction/compaction.d.ts +0 -143
  375. package/dist/core/compaction/compaction.d.ts.map +0 -1
  376. package/dist/core/compaction/compaction.js +0 -659
  377. package/dist/core/compaction/compaction.js.map +0 -1
  378. package/dist/core/compaction/index.d.ts +0 -7
  379. package/dist/core/compaction/index.d.ts.map +0 -1
  380. package/dist/core/compaction/index.js +0 -7
  381. package/dist/core/compaction/index.js.map +0 -1
  382. package/dist/core/compaction/utils.d.ts +0 -38
  383. package/dist/core/compaction/utils.d.ts.map +0 -1
  384. package/dist/core/compaction/utils.js +0 -153
  385. package/dist/core/compaction/utils.js.map +0 -1
  386. package/dist/core/message-retention.d.ts +0 -26
  387. package/dist/core/message-retention.d.ts.map +0 -1
  388. package/dist/core/message-retention.js +0 -95
  389. package/dist/core/message-retention.js.map +0 -1
  390. package/dist/core/messages.d.ts +0 -77
  391. package/dist/core/messages.d.ts.map +0 -1
  392. package/dist/core/messages.js +0 -123
  393. package/dist/core/messages.js.map +0 -1
  394. package/dist/core/session-manager.d.ts +0 -337
  395. package/dist/core/session-manager.d.ts.map +0 -1
  396. package/dist/core/session-manager.js +0 -1328
  397. package/dist/core/session-manager.js.map +0 -1
  398. package/dist/core/tools/truncate.d.ts +0 -70
  399. package/dist/core/tools/truncate.d.ts.map +0 -1
  400. package/dist/core/tools/truncate.js +0 -215
  401. package/dist/core/tools/truncate.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACN,YAAY,GAOZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACN,mBAAmB,EAGnB,yBAAyB,GACzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAKN,8BAA8B,EAC9B,0BAA0B,GAC1B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAA6C,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAE1G,OAAO,EAAE,cAAc,EAA0C,MAAM,gBAAgB,CAAC;AACxF,oBAAoB;AACpB,OAAO,EASN,UAAU,EACV,yBAAyB,EAYzB,eAAe,GAsBf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["/**\n * Core modules shared between all run modes.\n */\n\nexport {\n\tAgentSession,\n\ttype AgentSessionConfig,\n\ttype AgentSessionEvent,\n\ttype AgentSessionEventListener,\n\ttype ModelCycleResult,\n\ttype PromptOptions,\n\ttype SessionStats,\n} from \"./agent-session.ts\";\nexport {\n\tAgentSessionRuntime,\n\ttype CreateAgentSessionRuntimeFactory,\n\ttype CreateAgentSessionRuntimeResult,\n\tcreateAgentSessionRuntime,\n} from \"./agent-session-runtime.ts\";\nexport {\n\ttype AgentSessionRuntimeDiagnostic,\n\ttype AgentSessionServices,\n\ttype CreateAgentSessionFromServicesOptions,\n\ttype CreateAgentSessionServicesOptions,\n\tcreateAgentSessionFromServices,\n\tcreateAgentSessionServices,\n} from \"./agent-session-services.ts\";\nexport { type BashExecutorOptions, type BashResult, executeBashWithOperations } from \"./bash-executor.ts\";\nexport type { CompactionResult } from \"./compaction/index.ts\";\nexport { createEventBus, type EventBus, type EventBusController } from \"./event-bus.ts\";\n// Extensions system\nexport {\n\ttype AgentEndEvent,\n\ttype AgentStartEvent,\n\ttype AgentToolResult,\n\ttype AgentToolUpdateCallback,\n\ttype BeforeAgentStartEvent,\n\ttype BeforeAgentStartEventResult,\n\ttype BuildSystemPromptOptions,\n\ttype ContextEvent,\n\tdefineTool,\n\tdiscoverAndLoadExtensions,\n\ttype ExecOptions,\n\ttype ExecResult,\n\ttype Extension,\n\ttype ExtensionAPI,\n\ttype ExtensionCommandContext,\n\ttype ExtensionContext,\n\ttype ExtensionError,\n\ttype ExtensionEvent,\n\ttype ExtensionFactory,\n\ttype ExtensionFlag,\n\ttype ExtensionHandler,\n\tExtensionRunner,\n\ttype ExtensionShortcut,\n\ttype ExtensionUIContext,\n\ttype LoadExtensionsResult,\n\ttype MessageRenderer,\n\ttype RegisteredCommand,\n\ttype SessionBeforeCompactEvent,\n\ttype SessionBeforeForkEvent,\n\ttype SessionBeforeSwitchEvent,\n\ttype SessionBeforeTreeEvent,\n\ttype SessionCompactEvent,\n\ttype SessionShutdownEvent,\n\ttype SessionStartEvent,\n\ttype SessionTreeEvent,\n\ttype ToolCallEvent,\n\ttype ToolCallEventResult,\n\ttype ToolDefinition,\n\ttype ToolRenderResultOptions,\n\ttype ToolResultEvent,\n\ttype TurnEndEvent,\n\ttype TurnStartEvent,\n\ttype WorkingIndicatorOptions,\n} from \"./extensions/index.ts\";\nexport { createSyntheticSourceInfo } from \"./source-info.ts\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EACN,YAAY,GAOZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACN,mBAAmB,EAGnB,yBAAyB,GACzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAKN,8BAA8B,EAC9B,0BAA0B,GAC1B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAA6C,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC1G,OAAO,EAAE,cAAc,EAA0C,MAAM,gBAAgB,CAAC;AACxF,oBAAoB;AACpB,OAAO,EASN,UAAU,EACV,yBAAyB,EAYzB,eAAe,GAsBf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["/**\n * Core modules shared between all run modes.\n */\n\nexport type { CompactionResult } from \"@caupulican/pi-agent-core/node\";\nexport {\n\tAgentSession,\n\ttype AgentSessionConfig,\n\ttype AgentSessionEvent,\n\ttype AgentSessionEventListener,\n\ttype ModelCycleResult,\n\ttype PromptOptions,\n\ttype SessionStats,\n} from \"./agent-session.ts\";\nexport {\n\tAgentSessionRuntime,\n\ttype CreateAgentSessionRuntimeFactory,\n\ttype CreateAgentSessionRuntimeResult,\n\tcreateAgentSessionRuntime,\n} from \"./agent-session-runtime.ts\";\nexport {\n\ttype AgentSessionRuntimeDiagnostic,\n\ttype AgentSessionServices,\n\ttype CreateAgentSessionFromServicesOptions,\n\ttype CreateAgentSessionServicesOptions,\n\tcreateAgentSessionFromServices,\n\tcreateAgentSessionServices,\n} from \"./agent-session-services.ts\";\nexport { type BashExecutorOptions, type BashResult, executeBashWithOperations } from \"./bash-executor.ts\";\nexport { createEventBus, type EventBus, type EventBusController } from \"./event-bus.ts\";\n// Extensions system\nexport {\n\ttype AgentEndEvent,\n\ttype AgentStartEvent,\n\ttype AgentToolResult,\n\ttype AgentToolUpdateCallback,\n\ttype BeforeAgentStartEvent,\n\ttype BeforeAgentStartEventResult,\n\ttype BuildSystemPromptOptions,\n\ttype ContextEvent,\n\tdefineTool,\n\tdiscoverAndLoadExtensions,\n\ttype ExecOptions,\n\ttype ExecResult,\n\ttype Extension,\n\ttype ExtensionAPI,\n\ttype ExtensionCommandContext,\n\ttype ExtensionContext,\n\ttype ExtensionError,\n\ttype ExtensionEvent,\n\ttype ExtensionFactory,\n\ttype ExtensionFlag,\n\ttype ExtensionHandler,\n\tExtensionRunner,\n\ttype ExtensionShortcut,\n\ttype ExtensionUIContext,\n\ttype LoadExtensionsResult,\n\ttype MessageRenderer,\n\ttype RegisteredCommand,\n\ttype SessionBeforeCompactEvent,\n\ttype SessionBeforeForkEvent,\n\ttype SessionBeforeSwitchEvent,\n\ttype SessionBeforeTreeEvent,\n\ttype SessionCompactEvent,\n\ttype SessionShutdownEvent,\n\ttype SessionStartEvent,\n\ttype SessionTreeEvent,\n\ttype ToolCallEvent,\n\ttype ToolCallEventResult,\n\ttype ToolDefinition,\n\ttype ToolRenderResultOptions,\n\ttype ToolResultEvent,\n\ttype TurnEndEvent,\n\ttype TurnStartEvent,\n\ttype WorkingIndicatorOptions,\n} from \"./extensions/index.ts\";\nexport { createSyntheticSourceInfo } from \"./source-info.ts\";\n"]}
@@ -1,5 +1,5 @@
1
+ import type { SessionEntry, SessionManager } from "@caupulican/pi-agent-core/node";
1
2
  import type { LearningDecision } from "../autonomy/contracts.ts";
2
- import type { SessionEntry, SessionManager } from "../session-manager.ts";
3
3
  import type { DurableChangeLayer, DurableChangeProposal } from "./learning-gate.ts";
4
4
  import type { ReflectionWrite } from "./reflection-engine.ts";
5
5
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"learning-audit.d.ts","sourceRoot":"","sources":["../../../src/core/learning/learning-audit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,KAAK,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AACpF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;;;;GAKG;AAEH,MAAM,MAAM,oBAAoB,GAAG,eAAe,GAAG,gBAAgB,GAAG,YAAY,GAAG,eAAe,CAAC;AAEvG,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,oBAAoB,CAAC;IAC3B,sGAAsG;IACtG,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACrB;AAKD,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,cAAc,GAAG,SAAS,GAAG,UAAU,CAAC;AAEpF;;0DAE0D;AAC1D,eAAO,MAAM,+BAA+B,wBAAwB,CAAC;AAErE,MAAM,WAAW,mBAAmB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,MAAM,EAAE,mBAAmB,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CAClB;AAeD,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,GAAG,qBAAqB,CAO7G;AAED;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAS/E;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,eAAe,GAAG,oBAAoB,CA4B3F;AA8BD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,mBAAmB,CAYlF;AAED,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,mBAAmB,GAAG,mBAAmB,CAMnG;AAED,eAAO,MAAM,0BAA0B,mBAAmB,CAAC;AAE3D,MAAM,WAAW,4BAA4B;IAC5C,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,mBAAmB,CAAC;CAC5B;AAED,wBAAgB,2BAA2B,CAC1C,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,mBAAmB,CAAC,EACzD,MAAM,EAAE,mBAAmB,GACzB,MAAM,CAMR;AAQD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,mBAAmB,EAAE,CAmBjG","sourcesContent":["import type { LearningDecision } from \"../autonomy/contracts.ts\";\nimport type { SessionEntry, SessionManager } from \"../session-manager.ts\";\nimport type { DurableChangeLayer, DurableChangeProposal } from \"./learning-gate.ts\";\nimport type { ReflectionWrite } from \"./reflection-engine.ts\";\n\n/**\n * Audit + rollback metadata for durable learning changes. Every reflection-sourced write that the\n * learning policy applies (or converts to a proposal) leaves one of these records in the session\n * log, so `/autonomy diagnostics` can explain what changed, why, and how to undo it — and\n * `rollbackLearningWrite` can execute the inverse operation.\n */\n\nexport type LearningRollbackKind = \"memory_remove\" | \"memory_restore\" | \"memory_add\" | \"archive_skill\";\n\nexport interface LearningRollbackPlan {\n\tkind: LearningRollbackKind;\n\t/** Text currently present because of the change (to remove/replace), or the skill name to archive. */\n\ttarget?: string;\n\t/** Original text to restore (memory_restore/memory_add). */\n\tprevious?: string;\n\tinstructions: string;\n}\n\n// \"apply_failed\" is distinct from \"apply\": the gate decided to apply, but the underlying write tool\n// (e.g. the memory tool) refused it (budget exceeded, drift, threat) without throwing. It must never\n// be treated as \"apply\" by a rollback-eligibility check — there is nothing durable to undo.\nexport type LearningAuditAction = \"apply\" | \"apply_failed\" | \"propose\" | \"rollback\";\n\n/** reasonCode for an \"apply_failed\" audit — distinct from the gate's own reasonCode (e.g.\n * \"eligible_auto_apply\") so the record is honest about WHERE it stopped: the gate approved the\n * write, but the write tool refused it after the fact. */\nexport const APPLY_WRITE_REFUSED_REASON_CODE = \"apply_write_refused\";\n\nexport interface LearningAuditRecord {\n\tid: string;\n\tproposalId: string;\n\tlayer: DurableChangeLayer;\n\taction: LearningAuditAction;\n\tsummary: string;\n\treasonCode: string;\n\tdecision: LearningDecision;\n\trollback?: LearningRollbackPlan;\n\t/** For action \"rollback\": the audit id of the applied change this record undoes. */\n\trollbackOf?: string;\n\tcreatedAt: string;\n}\n\nfunction describeWrite(write: ReflectionWrite): string {\n\tswitch (write.kind) {\n\t\tcase \"memory_add\":\n\t\t\treturn `Add ${write.section} memory: ${write.text}`;\n\t\tcase \"memory_replace\":\n\t\t\treturn `Replace memory \"${write.target}\" with \"${write.text}\"`;\n\t\tcase \"memory_remove\":\n\t\t\treturn `Remove memory: ${write.target}`;\n\t\tcase \"promote_skill\":\n\t\t\treturn `Promote skill \"${write.name}\": ${write.description}`;\n\t}\n}\n\nexport function proposalFromReflectionWrite(write: ReflectionWrite, proposalId: string): DurableChangeProposal {\n\treturn {\n\t\tid: proposalId,\n\t\tlayer: write.kind === \"promote_skill\" ? \"skill\" : \"memory\",\n\t\tsummary: describeWrite(write),\n\t\trollbackPlan: rollbackPlanForReflectionWrite(write).instructions,\n\t};\n}\n\n/**\n * Contradiction count a reflection write carries against existing durable knowledge. A\n * `memory_replace`/`memory_remove` is only emitted when the reflection engine CONFRONTS an existing\n * fact (it supersedes or deletes it) — that supersession is the gate's contradiction signal, so such\n * a write must route through approval rather than silently overwriting prior memory. A `memory_add`\n * or `promote_skill` is purely additive and contradicts nothing.\n */\nexport function contradictionsForReflectionWrite(write: ReflectionWrite): number {\n\tswitch (write.kind) {\n\t\tcase \"memory_replace\":\n\t\tcase \"memory_remove\":\n\t\t\treturn 1;\n\t\tcase \"memory_add\":\n\t\tcase \"promote_skill\":\n\t\t\treturn 0;\n\t}\n}\n\nexport function rollbackPlanForReflectionWrite(write: ReflectionWrite): LearningRollbackPlan {\n\tswitch (write.kind) {\n\t\tcase \"memory_add\":\n\t\t\treturn {\n\t\t\t\tkind: \"memory_remove\",\n\t\t\t\ttarget: write.text,\n\t\t\t\tinstructions: `Remove the added ${write.section} memory text.`,\n\t\t\t};\n\t\tcase \"memory_replace\":\n\t\t\treturn {\n\t\t\t\tkind: \"memory_restore\",\n\t\t\t\ttarget: write.text,\n\t\t\t\tprevious: write.target,\n\t\t\t\tinstructions: \"Replace the new memory text with the original text it overwrote.\",\n\t\t\t};\n\t\tcase \"memory_remove\":\n\t\t\treturn {\n\t\t\t\tkind: \"memory_add\",\n\t\t\t\tprevious: write.target,\n\t\t\t\tinstructions: \"Re-add the removed text to the MEMORY file (it may originally have lived in USER).\",\n\t\t\t};\n\t\tcase \"promote_skill\":\n\t\t\treturn {\n\t\t\t\tkind: \"archive_skill\",\n\t\t\t\ttarget: write.name,\n\t\t\t\tinstructions: \"Archive the promoted skill so it no longer loads.\",\n\t\t\t};\n\t}\n}\n\nconst AUDIT_ACTIONS: readonly string[] = [\"apply\", \"apply_failed\", \"propose\", \"rollback\"];\nconst ROLLBACK_KINDS: readonly string[] = [\"memory_remove\", \"memory_restore\", \"memory_add\", \"archive_skill\"];\nconst LAYERS: readonly string[] = [\"memory\", \"skill\", \"prompt\", \"extension\", \"tool\", \"script\", \"settings\", \"source\"];\n\nfunction isOptionalString(value: unknown): boolean {\n\treturn value === undefined || typeof value === \"string\";\n}\n\nfunction isLearningRollbackPlan(value: unknown): value is LearningRollbackPlan {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst plan = value as Record<string, unknown>;\n\tif (typeof plan.kind !== \"string\" || !ROLLBACK_KINDS.includes(plan.kind)) return false;\n\tif (!isOptionalString(plan.target) || !isOptionalString(plan.previous)) return false;\n\treturn typeof plan.instructions === \"string\";\n}\n\nfunction isLearningDecisionShape(value: unknown): value is LearningDecision {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst decision = value as Record<string, unknown>;\n\treturn (\n\t\t(decision.kind === \"no-op\" || decision.kind === \"proposal\" || decision.kind === \"apply\") &&\n\t\ttypeof decision.reasonCode === \"string\" &&\n\t\ttypeof decision.confidence === \"number\" &&\n\t\ttypeof decision.summary === \"string\" &&\n\t\ttypeof decision.requiresApproval === \"boolean\"\n\t);\n}\n\nexport function isLearningAuditRecord(value: unknown): value is LearningAuditRecord {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst record = value as Record<string, unknown>;\n\tif (typeof record.id !== \"string\" || record.id.length === 0) return false;\n\tif (typeof record.proposalId !== \"string\") return false;\n\tif (typeof record.layer !== \"string\" || !LAYERS.includes(record.layer)) return false;\n\tif (typeof record.action !== \"string\" || !AUDIT_ACTIONS.includes(record.action)) return false;\n\tif (typeof record.summary !== \"string\" || typeof record.reasonCode !== \"string\") return false;\n\tif (!isLearningDecisionShape(record.decision)) return false;\n\tif (record.rollback !== undefined && !isLearningRollbackPlan(record.rollback)) return false;\n\tif (!isOptionalString(record.rollbackOf)) return false;\n\treturn typeof record.createdAt === \"string\";\n}\n\nexport function cloneLearningAuditRecordForStorage(record: LearningAuditRecord): LearningAuditRecord {\n\treturn {\n\t\t...record,\n\t\tdecision: { ...record.decision },\n\t\t...(record.rollback ? { rollback: { ...record.rollback } } : {}),\n\t};\n}\n\nexport const LEARNING_AUDIT_CUSTOM_TYPE = \"learning_audit\";\n\nexport interface LearningAuditSnapshotPayload {\n\tversion: 1;\n\trecord: LearningAuditRecord;\n}\n\nexport function appendLearningAuditSnapshot(\n\tsessionManager: Pick<SessionManager, \"appendCustomEntry\">,\n\trecord: LearningAuditRecord,\n): string {\n\tconst payload: LearningAuditSnapshotPayload = {\n\t\tversion: 1,\n\t\trecord: cloneLearningAuditRecordForStorage(record),\n\t};\n\treturn sessionManager.appendCustomEntry(LEARNING_AUDIT_CUSTOM_TYPE, payload);\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\nexport function getLearningAuditSnapshots(entries: readonly SessionEntry[]): LearningAuditRecord[] {\n\tconst records: LearningAuditRecord[] = [];\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== LEARNING_AUDIT_CUSTOM_TYPE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst payload = entry.data;\n\t\tif (!isPlainRecord(payload)) continue;\n\t\tif (payload.version !== 1) continue;\n\t\tif (!(\"record\" in payload)) continue;\n\t\tconst record = payload.record;\n\t\tif (isLearningAuditRecord(record)) {\n\t\t\trecords.push(cloneLearningAuditRecordForStorage(record));\n\t\t}\n\t}\n\n\treturn records;\n}\n"]}
1
+ {"version":3,"file":"learning-audit.d.ts","sourceRoot":"","sources":["../../../src/core/learning/learning-audit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AACpF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;;;;GAKG;AAEH,MAAM,MAAM,oBAAoB,GAAG,eAAe,GAAG,gBAAgB,GAAG,YAAY,GAAG,eAAe,CAAC;AAEvG,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,oBAAoB,CAAC;IAC3B,sGAAsG;IACtG,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACrB;AAKD,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,cAAc,GAAG,SAAS,GAAG,UAAU,CAAC;AAEpF;;0DAE0D;AAC1D,eAAO,MAAM,+BAA+B,wBAAwB,CAAC;AAErE,MAAM,WAAW,mBAAmB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,MAAM,EAAE,mBAAmB,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CAClB;AAeD,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,GAAG,qBAAqB,CAO7G;AAED;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAS/E;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,eAAe,GAAG,oBAAoB,CA4B3F;AA8BD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,mBAAmB,CAYlF;AAED,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,mBAAmB,GAAG,mBAAmB,CAMnG;AAED,eAAO,MAAM,0BAA0B,mBAAmB,CAAC;AAE3D,MAAM,WAAW,4BAA4B;IAC5C,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,mBAAmB,CAAC;CAC5B;AAED,wBAAgB,2BAA2B,CAC1C,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,mBAAmB,CAAC,EACzD,MAAM,EAAE,mBAAmB,GACzB,MAAM,CAMR;AAQD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,mBAAmB,EAAE,CAmBjG","sourcesContent":["import type { SessionEntry, SessionManager } from \"@caupulican/pi-agent-core/node\";\nimport type { LearningDecision } from \"../autonomy/contracts.ts\";\nimport type { DurableChangeLayer, DurableChangeProposal } from \"./learning-gate.ts\";\nimport type { ReflectionWrite } from \"./reflection-engine.ts\";\n\n/**\n * Audit + rollback metadata for durable learning changes. Every reflection-sourced write that the\n * learning policy applies (or converts to a proposal) leaves one of these records in the session\n * log, so `/autonomy diagnostics` can explain what changed, why, and how to undo it — and\n * `rollbackLearningWrite` can execute the inverse operation.\n */\n\nexport type LearningRollbackKind = \"memory_remove\" | \"memory_restore\" | \"memory_add\" | \"archive_skill\";\n\nexport interface LearningRollbackPlan {\n\tkind: LearningRollbackKind;\n\t/** Text currently present because of the change (to remove/replace), or the skill name to archive. */\n\ttarget?: string;\n\t/** Original text to restore (memory_restore/memory_add). */\n\tprevious?: string;\n\tinstructions: string;\n}\n\n// \"apply_failed\" is distinct from \"apply\": the gate decided to apply, but the underlying write tool\n// (e.g. the memory tool) refused it (budget exceeded, drift, threat) without throwing. It must never\n// be treated as \"apply\" by a rollback-eligibility check — there is nothing durable to undo.\nexport type LearningAuditAction = \"apply\" | \"apply_failed\" | \"propose\" | \"rollback\";\n\n/** reasonCode for an \"apply_failed\" audit — distinct from the gate's own reasonCode (e.g.\n * \"eligible_auto_apply\") so the record is honest about WHERE it stopped: the gate approved the\n * write, but the write tool refused it after the fact. */\nexport const APPLY_WRITE_REFUSED_REASON_CODE = \"apply_write_refused\";\n\nexport interface LearningAuditRecord {\n\tid: string;\n\tproposalId: string;\n\tlayer: DurableChangeLayer;\n\taction: LearningAuditAction;\n\tsummary: string;\n\treasonCode: string;\n\tdecision: LearningDecision;\n\trollback?: LearningRollbackPlan;\n\t/** For action \"rollback\": the audit id of the applied change this record undoes. */\n\trollbackOf?: string;\n\tcreatedAt: string;\n}\n\nfunction describeWrite(write: ReflectionWrite): string {\n\tswitch (write.kind) {\n\t\tcase \"memory_add\":\n\t\t\treturn `Add ${write.section} memory: ${write.text}`;\n\t\tcase \"memory_replace\":\n\t\t\treturn `Replace memory \"${write.target}\" with \"${write.text}\"`;\n\t\tcase \"memory_remove\":\n\t\t\treturn `Remove memory: ${write.target}`;\n\t\tcase \"promote_skill\":\n\t\t\treturn `Promote skill \"${write.name}\": ${write.description}`;\n\t}\n}\n\nexport function proposalFromReflectionWrite(write: ReflectionWrite, proposalId: string): DurableChangeProposal {\n\treturn {\n\t\tid: proposalId,\n\t\tlayer: write.kind === \"promote_skill\" ? \"skill\" : \"memory\",\n\t\tsummary: describeWrite(write),\n\t\trollbackPlan: rollbackPlanForReflectionWrite(write).instructions,\n\t};\n}\n\n/**\n * Contradiction count a reflection write carries against existing durable knowledge. A\n * `memory_replace`/`memory_remove` is only emitted when the reflection engine CONFRONTS an existing\n * fact (it supersedes or deletes it) — that supersession is the gate's contradiction signal, so such\n * a write must route through approval rather than silently overwriting prior memory. A `memory_add`\n * or `promote_skill` is purely additive and contradicts nothing.\n */\nexport function contradictionsForReflectionWrite(write: ReflectionWrite): number {\n\tswitch (write.kind) {\n\t\tcase \"memory_replace\":\n\t\tcase \"memory_remove\":\n\t\t\treturn 1;\n\t\tcase \"memory_add\":\n\t\tcase \"promote_skill\":\n\t\t\treturn 0;\n\t}\n}\n\nexport function rollbackPlanForReflectionWrite(write: ReflectionWrite): LearningRollbackPlan {\n\tswitch (write.kind) {\n\t\tcase \"memory_add\":\n\t\t\treturn {\n\t\t\t\tkind: \"memory_remove\",\n\t\t\t\ttarget: write.text,\n\t\t\t\tinstructions: `Remove the added ${write.section} memory text.`,\n\t\t\t};\n\t\tcase \"memory_replace\":\n\t\t\treturn {\n\t\t\t\tkind: \"memory_restore\",\n\t\t\t\ttarget: write.text,\n\t\t\t\tprevious: write.target,\n\t\t\t\tinstructions: \"Replace the new memory text with the original text it overwrote.\",\n\t\t\t};\n\t\tcase \"memory_remove\":\n\t\t\treturn {\n\t\t\t\tkind: \"memory_add\",\n\t\t\t\tprevious: write.target,\n\t\t\t\tinstructions: \"Re-add the removed text to the MEMORY file (it may originally have lived in USER).\",\n\t\t\t};\n\t\tcase \"promote_skill\":\n\t\t\treturn {\n\t\t\t\tkind: \"archive_skill\",\n\t\t\t\ttarget: write.name,\n\t\t\t\tinstructions: \"Archive the promoted skill so it no longer loads.\",\n\t\t\t};\n\t}\n}\n\nconst AUDIT_ACTIONS: readonly string[] = [\"apply\", \"apply_failed\", \"propose\", \"rollback\"];\nconst ROLLBACK_KINDS: readonly string[] = [\"memory_remove\", \"memory_restore\", \"memory_add\", \"archive_skill\"];\nconst LAYERS: readonly string[] = [\"memory\", \"skill\", \"prompt\", \"extension\", \"tool\", \"script\", \"settings\", \"source\"];\n\nfunction isOptionalString(value: unknown): boolean {\n\treturn value === undefined || typeof value === \"string\";\n}\n\nfunction isLearningRollbackPlan(value: unknown): value is LearningRollbackPlan {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst plan = value as Record<string, unknown>;\n\tif (typeof plan.kind !== \"string\" || !ROLLBACK_KINDS.includes(plan.kind)) return false;\n\tif (!isOptionalString(plan.target) || !isOptionalString(plan.previous)) return false;\n\treturn typeof plan.instructions === \"string\";\n}\n\nfunction isLearningDecisionShape(value: unknown): value is LearningDecision {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst decision = value as Record<string, unknown>;\n\treturn (\n\t\t(decision.kind === \"no-op\" || decision.kind === \"proposal\" || decision.kind === \"apply\") &&\n\t\ttypeof decision.reasonCode === \"string\" &&\n\t\ttypeof decision.confidence === \"number\" &&\n\t\ttypeof decision.summary === \"string\" &&\n\t\ttypeof decision.requiresApproval === \"boolean\"\n\t);\n}\n\nexport function isLearningAuditRecord(value: unknown): value is LearningAuditRecord {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst record = value as Record<string, unknown>;\n\tif (typeof record.id !== \"string\" || record.id.length === 0) return false;\n\tif (typeof record.proposalId !== \"string\") return false;\n\tif (typeof record.layer !== \"string\" || !LAYERS.includes(record.layer)) return false;\n\tif (typeof record.action !== \"string\" || !AUDIT_ACTIONS.includes(record.action)) return false;\n\tif (typeof record.summary !== \"string\" || typeof record.reasonCode !== \"string\") return false;\n\tif (!isLearningDecisionShape(record.decision)) return false;\n\tif (record.rollback !== undefined && !isLearningRollbackPlan(record.rollback)) return false;\n\tif (!isOptionalString(record.rollbackOf)) return false;\n\treturn typeof record.createdAt === \"string\";\n}\n\nexport function cloneLearningAuditRecordForStorage(record: LearningAuditRecord): LearningAuditRecord {\n\treturn {\n\t\t...record,\n\t\tdecision: { ...record.decision },\n\t\t...(record.rollback ? { rollback: { ...record.rollback } } : {}),\n\t};\n}\n\nexport const LEARNING_AUDIT_CUSTOM_TYPE = \"learning_audit\";\n\nexport interface LearningAuditSnapshotPayload {\n\tversion: 1;\n\trecord: LearningAuditRecord;\n}\n\nexport function appendLearningAuditSnapshot(\n\tsessionManager: Pick<SessionManager, \"appendCustomEntry\">,\n\trecord: LearningAuditRecord,\n): string {\n\tconst payload: LearningAuditSnapshotPayload = {\n\t\tversion: 1,\n\t\trecord: cloneLearningAuditRecordForStorage(record),\n\t};\n\treturn sessionManager.appendCustomEntry(LEARNING_AUDIT_CUSTOM_TYPE, payload);\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\nexport function getLearningAuditSnapshots(entries: readonly SessionEntry[]): LearningAuditRecord[] {\n\tconst records: LearningAuditRecord[] = [];\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== LEARNING_AUDIT_CUSTOM_TYPE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst payload = entry.data;\n\t\tif (!isPlainRecord(payload)) continue;\n\t\tif (payload.version !== 1) continue;\n\t\tif (!(\"record\" in payload)) continue;\n\t\tconst record = payload.record;\n\t\tif (isLearningAuditRecord(record)) {\n\t\t\trecords.push(cloneLearningAuditRecordForStorage(record));\n\t\t}\n\t}\n\n\treturn records;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"learning-audit.js","sourceRoot":"","sources":["../../../src/core/learning/learning-audit.ts"],"names":[],"mappings":"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","sourcesContent":["import type { LearningDecision } from \"../autonomy/contracts.ts\";\nimport type { SessionEntry, SessionManager } from \"../session-manager.ts\";\nimport type { DurableChangeLayer, DurableChangeProposal } from \"./learning-gate.ts\";\nimport type { ReflectionWrite } from \"./reflection-engine.ts\";\n\n/**\n * Audit + rollback metadata for durable learning changes. Every reflection-sourced write that the\n * learning policy applies (or converts to a proposal) leaves one of these records in the session\n * log, so `/autonomy diagnostics` can explain what changed, why, and how to undo it — and\n * `rollbackLearningWrite` can execute the inverse operation.\n */\n\nexport type LearningRollbackKind = \"memory_remove\" | \"memory_restore\" | \"memory_add\" | \"archive_skill\";\n\nexport interface LearningRollbackPlan {\n\tkind: LearningRollbackKind;\n\t/** Text currently present because of the change (to remove/replace), or the skill name to archive. */\n\ttarget?: string;\n\t/** Original text to restore (memory_restore/memory_add). */\n\tprevious?: string;\n\tinstructions: string;\n}\n\n// \"apply_failed\" is distinct from \"apply\": the gate decided to apply, but the underlying write tool\n// (e.g. the memory tool) refused it (budget exceeded, drift, threat) without throwing. It must never\n// be treated as \"apply\" by a rollback-eligibility check — there is nothing durable to undo.\nexport type LearningAuditAction = \"apply\" | \"apply_failed\" | \"propose\" | \"rollback\";\n\n/** reasonCode for an \"apply_failed\" audit — distinct from the gate's own reasonCode (e.g.\n * \"eligible_auto_apply\") so the record is honest about WHERE it stopped: the gate approved the\n * write, but the write tool refused it after the fact. */\nexport const APPLY_WRITE_REFUSED_REASON_CODE = \"apply_write_refused\";\n\nexport interface LearningAuditRecord {\n\tid: string;\n\tproposalId: string;\n\tlayer: DurableChangeLayer;\n\taction: LearningAuditAction;\n\tsummary: string;\n\treasonCode: string;\n\tdecision: LearningDecision;\n\trollback?: LearningRollbackPlan;\n\t/** For action \"rollback\": the audit id of the applied change this record undoes. */\n\trollbackOf?: string;\n\tcreatedAt: string;\n}\n\nfunction describeWrite(write: ReflectionWrite): string {\n\tswitch (write.kind) {\n\t\tcase \"memory_add\":\n\t\t\treturn `Add ${write.section} memory: ${write.text}`;\n\t\tcase \"memory_replace\":\n\t\t\treturn `Replace memory \"${write.target}\" with \"${write.text}\"`;\n\t\tcase \"memory_remove\":\n\t\t\treturn `Remove memory: ${write.target}`;\n\t\tcase \"promote_skill\":\n\t\t\treturn `Promote skill \"${write.name}\": ${write.description}`;\n\t}\n}\n\nexport function proposalFromReflectionWrite(write: ReflectionWrite, proposalId: string): DurableChangeProposal {\n\treturn {\n\t\tid: proposalId,\n\t\tlayer: write.kind === \"promote_skill\" ? \"skill\" : \"memory\",\n\t\tsummary: describeWrite(write),\n\t\trollbackPlan: rollbackPlanForReflectionWrite(write).instructions,\n\t};\n}\n\n/**\n * Contradiction count a reflection write carries against existing durable knowledge. A\n * `memory_replace`/`memory_remove` is only emitted when the reflection engine CONFRONTS an existing\n * fact (it supersedes or deletes it) — that supersession is the gate's contradiction signal, so such\n * a write must route through approval rather than silently overwriting prior memory. A `memory_add`\n * or `promote_skill` is purely additive and contradicts nothing.\n */\nexport function contradictionsForReflectionWrite(write: ReflectionWrite): number {\n\tswitch (write.kind) {\n\t\tcase \"memory_replace\":\n\t\tcase \"memory_remove\":\n\t\t\treturn 1;\n\t\tcase \"memory_add\":\n\t\tcase \"promote_skill\":\n\t\t\treturn 0;\n\t}\n}\n\nexport function rollbackPlanForReflectionWrite(write: ReflectionWrite): LearningRollbackPlan {\n\tswitch (write.kind) {\n\t\tcase \"memory_add\":\n\t\t\treturn {\n\t\t\t\tkind: \"memory_remove\",\n\t\t\t\ttarget: write.text,\n\t\t\t\tinstructions: `Remove the added ${write.section} memory text.`,\n\t\t\t};\n\t\tcase \"memory_replace\":\n\t\t\treturn {\n\t\t\t\tkind: \"memory_restore\",\n\t\t\t\ttarget: write.text,\n\t\t\t\tprevious: write.target,\n\t\t\t\tinstructions: \"Replace the new memory text with the original text it overwrote.\",\n\t\t\t};\n\t\tcase \"memory_remove\":\n\t\t\treturn {\n\t\t\t\tkind: \"memory_add\",\n\t\t\t\tprevious: write.target,\n\t\t\t\tinstructions: \"Re-add the removed text to the MEMORY file (it may originally have lived in USER).\",\n\t\t\t};\n\t\tcase \"promote_skill\":\n\t\t\treturn {\n\t\t\t\tkind: \"archive_skill\",\n\t\t\t\ttarget: write.name,\n\t\t\t\tinstructions: \"Archive the promoted skill so it no longer loads.\",\n\t\t\t};\n\t}\n}\n\nconst AUDIT_ACTIONS: readonly string[] = [\"apply\", \"apply_failed\", \"propose\", \"rollback\"];\nconst ROLLBACK_KINDS: readonly string[] = [\"memory_remove\", \"memory_restore\", \"memory_add\", \"archive_skill\"];\nconst LAYERS: readonly string[] = [\"memory\", \"skill\", \"prompt\", \"extension\", \"tool\", \"script\", \"settings\", \"source\"];\n\nfunction isOptionalString(value: unknown): boolean {\n\treturn value === undefined || typeof value === \"string\";\n}\n\nfunction isLearningRollbackPlan(value: unknown): value is LearningRollbackPlan {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst plan = value as Record<string, unknown>;\n\tif (typeof plan.kind !== \"string\" || !ROLLBACK_KINDS.includes(plan.kind)) return false;\n\tif (!isOptionalString(plan.target) || !isOptionalString(plan.previous)) return false;\n\treturn typeof plan.instructions === \"string\";\n}\n\nfunction isLearningDecisionShape(value: unknown): value is LearningDecision {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst decision = value as Record<string, unknown>;\n\treturn (\n\t\t(decision.kind === \"no-op\" || decision.kind === \"proposal\" || decision.kind === \"apply\") &&\n\t\ttypeof decision.reasonCode === \"string\" &&\n\t\ttypeof decision.confidence === \"number\" &&\n\t\ttypeof decision.summary === \"string\" &&\n\t\ttypeof decision.requiresApproval === \"boolean\"\n\t);\n}\n\nexport function isLearningAuditRecord(value: unknown): value is LearningAuditRecord {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst record = value as Record<string, unknown>;\n\tif (typeof record.id !== \"string\" || record.id.length === 0) return false;\n\tif (typeof record.proposalId !== \"string\") return false;\n\tif (typeof record.layer !== \"string\" || !LAYERS.includes(record.layer)) return false;\n\tif (typeof record.action !== \"string\" || !AUDIT_ACTIONS.includes(record.action)) return false;\n\tif (typeof record.summary !== \"string\" || typeof record.reasonCode !== \"string\") return false;\n\tif (!isLearningDecisionShape(record.decision)) return false;\n\tif (record.rollback !== undefined && !isLearningRollbackPlan(record.rollback)) return false;\n\tif (!isOptionalString(record.rollbackOf)) return false;\n\treturn typeof record.createdAt === \"string\";\n}\n\nexport function cloneLearningAuditRecordForStorage(record: LearningAuditRecord): LearningAuditRecord {\n\treturn {\n\t\t...record,\n\t\tdecision: { ...record.decision },\n\t\t...(record.rollback ? { rollback: { ...record.rollback } } : {}),\n\t};\n}\n\nexport const LEARNING_AUDIT_CUSTOM_TYPE = \"learning_audit\";\n\nexport interface LearningAuditSnapshotPayload {\n\tversion: 1;\n\trecord: LearningAuditRecord;\n}\n\nexport function appendLearningAuditSnapshot(\n\tsessionManager: Pick<SessionManager, \"appendCustomEntry\">,\n\trecord: LearningAuditRecord,\n): string {\n\tconst payload: LearningAuditSnapshotPayload = {\n\t\tversion: 1,\n\t\trecord: cloneLearningAuditRecordForStorage(record),\n\t};\n\treturn sessionManager.appendCustomEntry(LEARNING_AUDIT_CUSTOM_TYPE, payload);\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\nexport function getLearningAuditSnapshots(entries: readonly SessionEntry[]): LearningAuditRecord[] {\n\tconst records: LearningAuditRecord[] = [];\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== LEARNING_AUDIT_CUSTOM_TYPE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst payload = entry.data;\n\t\tif (!isPlainRecord(payload)) continue;\n\t\tif (payload.version !== 1) continue;\n\t\tif (!(\"record\" in payload)) continue;\n\t\tconst record = payload.record;\n\t\tif (isLearningAuditRecord(record)) {\n\t\t\trecords.push(cloneLearningAuditRecordForStorage(record));\n\t\t}\n\t}\n\n\treturn records;\n}\n"]}
1
+ {"version":3,"file":"learning-audit.js","sourceRoot":"","sources":["../../../src/core/learning/learning-audit.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SessionEntry, SessionManager } from \"@caupulican/pi-agent-core/node\";\nimport type { LearningDecision } from \"../autonomy/contracts.ts\";\nimport type { DurableChangeLayer, DurableChangeProposal } from \"./learning-gate.ts\";\nimport type { ReflectionWrite } from \"./reflection-engine.ts\";\n\n/**\n * Audit + rollback metadata for durable learning changes. Every reflection-sourced write that the\n * learning policy applies (or converts to a proposal) leaves one of these records in the session\n * log, so `/autonomy diagnostics` can explain what changed, why, and how to undo it — and\n * `rollbackLearningWrite` can execute the inverse operation.\n */\n\nexport type LearningRollbackKind = \"memory_remove\" | \"memory_restore\" | \"memory_add\" | \"archive_skill\";\n\nexport interface LearningRollbackPlan {\n\tkind: LearningRollbackKind;\n\t/** Text currently present because of the change (to remove/replace), or the skill name to archive. */\n\ttarget?: string;\n\t/** Original text to restore (memory_restore/memory_add). */\n\tprevious?: string;\n\tinstructions: string;\n}\n\n// \"apply_failed\" is distinct from \"apply\": the gate decided to apply, but the underlying write tool\n// (e.g. the memory tool) refused it (budget exceeded, drift, threat) without throwing. It must never\n// be treated as \"apply\" by a rollback-eligibility check — there is nothing durable to undo.\nexport type LearningAuditAction = \"apply\" | \"apply_failed\" | \"propose\" | \"rollback\";\n\n/** reasonCode for an \"apply_failed\" audit — distinct from the gate's own reasonCode (e.g.\n * \"eligible_auto_apply\") so the record is honest about WHERE it stopped: the gate approved the\n * write, but the write tool refused it after the fact. */\nexport const APPLY_WRITE_REFUSED_REASON_CODE = \"apply_write_refused\";\n\nexport interface LearningAuditRecord {\n\tid: string;\n\tproposalId: string;\n\tlayer: DurableChangeLayer;\n\taction: LearningAuditAction;\n\tsummary: string;\n\treasonCode: string;\n\tdecision: LearningDecision;\n\trollback?: LearningRollbackPlan;\n\t/** For action \"rollback\": the audit id of the applied change this record undoes. */\n\trollbackOf?: string;\n\tcreatedAt: string;\n}\n\nfunction describeWrite(write: ReflectionWrite): string {\n\tswitch (write.kind) {\n\t\tcase \"memory_add\":\n\t\t\treturn `Add ${write.section} memory: ${write.text}`;\n\t\tcase \"memory_replace\":\n\t\t\treturn `Replace memory \"${write.target}\" with \"${write.text}\"`;\n\t\tcase \"memory_remove\":\n\t\t\treturn `Remove memory: ${write.target}`;\n\t\tcase \"promote_skill\":\n\t\t\treturn `Promote skill \"${write.name}\": ${write.description}`;\n\t}\n}\n\nexport function proposalFromReflectionWrite(write: ReflectionWrite, proposalId: string): DurableChangeProposal {\n\treturn {\n\t\tid: proposalId,\n\t\tlayer: write.kind === \"promote_skill\" ? \"skill\" : \"memory\",\n\t\tsummary: describeWrite(write),\n\t\trollbackPlan: rollbackPlanForReflectionWrite(write).instructions,\n\t};\n}\n\n/**\n * Contradiction count a reflection write carries against existing durable knowledge. A\n * `memory_replace`/`memory_remove` is only emitted when the reflection engine CONFRONTS an existing\n * fact (it supersedes or deletes it) — that supersession is the gate's contradiction signal, so such\n * a write must route through approval rather than silently overwriting prior memory. A `memory_add`\n * or `promote_skill` is purely additive and contradicts nothing.\n */\nexport function contradictionsForReflectionWrite(write: ReflectionWrite): number {\n\tswitch (write.kind) {\n\t\tcase \"memory_replace\":\n\t\tcase \"memory_remove\":\n\t\t\treturn 1;\n\t\tcase \"memory_add\":\n\t\tcase \"promote_skill\":\n\t\t\treturn 0;\n\t}\n}\n\nexport function rollbackPlanForReflectionWrite(write: ReflectionWrite): LearningRollbackPlan {\n\tswitch (write.kind) {\n\t\tcase \"memory_add\":\n\t\t\treturn {\n\t\t\t\tkind: \"memory_remove\",\n\t\t\t\ttarget: write.text,\n\t\t\t\tinstructions: `Remove the added ${write.section} memory text.`,\n\t\t\t};\n\t\tcase \"memory_replace\":\n\t\t\treturn {\n\t\t\t\tkind: \"memory_restore\",\n\t\t\t\ttarget: write.text,\n\t\t\t\tprevious: write.target,\n\t\t\t\tinstructions: \"Replace the new memory text with the original text it overwrote.\",\n\t\t\t};\n\t\tcase \"memory_remove\":\n\t\t\treturn {\n\t\t\t\tkind: \"memory_add\",\n\t\t\t\tprevious: write.target,\n\t\t\t\tinstructions: \"Re-add the removed text to the MEMORY file (it may originally have lived in USER).\",\n\t\t\t};\n\t\tcase \"promote_skill\":\n\t\t\treturn {\n\t\t\t\tkind: \"archive_skill\",\n\t\t\t\ttarget: write.name,\n\t\t\t\tinstructions: \"Archive the promoted skill so it no longer loads.\",\n\t\t\t};\n\t}\n}\n\nconst AUDIT_ACTIONS: readonly string[] = [\"apply\", \"apply_failed\", \"propose\", \"rollback\"];\nconst ROLLBACK_KINDS: readonly string[] = [\"memory_remove\", \"memory_restore\", \"memory_add\", \"archive_skill\"];\nconst LAYERS: readonly string[] = [\"memory\", \"skill\", \"prompt\", \"extension\", \"tool\", \"script\", \"settings\", \"source\"];\n\nfunction isOptionalString(value: unknown): boolean {\n\treturn value === undefined || typeof value === \"string\";\n}\n\nfunction isLearningRollbackPlan(value: unknown): value is LearningRollbackPlan {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst plan = value as Record<string, unknown>;\n\tif (typeof plan.kind !== \"string\" || !ROLLBACK_KINDS.includes(plan.kind)) return false;\n\tif (!isOptionalString(plan.target) || !isOptionalString(plan.previous)) return false;\n\treturn typeof plan.instructions === \"string\";\n}\n\nfunction isLearningDecisionShape(value: unknown): value is LearningDecision {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst decision = value as Record<string, unknown>;\n\treturn (\n\t\t(decision.kind === \"no-op\" || decision.kind === \"proposal\" || decision.kind === \"apply\") &&\n\t\ttypeof decision.reasonCode === \"string\" &&\n\t\ttypeof decision.confidence === \"number\" &&\n\t\ttypeof decision.summary === \"string\" &&\n\t\ttypeof decision.requiresApproval === \"boolean\"\n\t);\n}\n\nexport function isLearningAuditRecord(value: unknown): value is LearningAuditRecord {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst record = value as Record<string, unknown>;\n\tif (typeof record.id !== \"string\" || record.id.length === 0) return false;\n\tif (typeof record.proposalId !== \"string\") return false;\n\tif (typeof record.layer !== \"string\" || !LAYERS.includes(record.layer)) return false;\n\tif (typeof record.action !== \"string\" || !AUDIT_ACTIONS.includes(record.action)) return false;\n\tif (typeof record.summary !== \"string\" || typeof record.reasonCode !== \"string\") return false;\n\tif (!isLearningDecisionShape(record.decision)) return false;\n\tif (record.rollback !== undefined && !isLearningRollbackPlan(record.rollback)) return false;\n\tif (!isOptionalString(record.rollbackOf)) return false;\n\treturn typeof record.createdAt === \"string\";\n}\n\nexport function cloneLearningAuditRecordForStorage(record: LearningAuditRecord): LearningAuditRecord {\n\treturn {\n\t\t...record,\n\t\tdecision: { ...record.decision },\n\t\t...(record.rollback ? { rollback: { ...record.rollback } } : {}),\n\t};\n}\n\nexport const LEARNING_AUDIT_CUSTOM_TYPE = \"learning_audit\";\n\nexport interface LearningAuditSnapshotPayload {\n\tversion: 1;\n\trecord: LearningAuditRecord;\n}\n\nexport function appendLearningAuditSnapshot(\n\tsessionManager: Pick<SessionManager, \"appendCustomEntry\">,\n\trecord: LearningAuditRecord,\n): string {\n\tconst payload: LearningAuditSnapshotPayload = {\n\t\tversion: 1,\n\t\trecord: cloneLearningAuditRecordForStorage(record),\n\t};\n\treturn sessionManager.appendCustomEntry(LEARNING_AUDIT_CUSTOM_TYPE, payload);\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\nexport function getLearningAuditSnapshots(entries: readonly SessionEntry[]): LearningAuditRecord[] {\n\tconst records: LearningAuditRecord[] = [];\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== LEARNING_AUDIT_CUSTOM_TYPE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst payload = entry.data;\n\t\tif (!isPlainRecord(payload)) continue;\n\t\tif (payload.version !== 1) continue;\n\t\tif (!(\"record\" in payload)) continue;\n\t\tconst record = payload.record;\n\t\tif (isLearningAuditRecord(record)) {\n\t\t\trecords.push(cloneLearningAuditRecordForStorage(record));\n\t\t}\n\t}\n\n\treturn records;\n}\n"]}
@@ -1,5 +1,5 @@
1
+ import type { SessionEntry, SessionManager } from "@caupulican/pi-agent-core/node";
1
2
  import type { LearningDecision } from "../autonomy/contracts.ts";
2
- import type { SessionEntry, SessionManager } from "../session-manager.ts";
3
3
  export declare const LEARNING_DECISION_CUSTOM_TYPE = "learning_decision";
4
4
  export interface LearningDecisionSnapshotPayload {
5
5
  version: 1;
@@ -1 +1 @@
1
- {"version":3,"file":"session-learning-decision.d.ts","sourceRoot":"","sources":["../../../src/core/learning/session-learning-decision.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG1E,eAAO,MAAM,6BAA6B,sBAAsB,CAAC;AAEjE,MAAM,WAAW,+BAA+B;IAC/C,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,gBAAgB,CAAC;CAC3B;AAED,wBAAgB,8BAA8B,CAC7C,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,mBAAmB,CAAC,EACzD,QAAQ,EAAE,gBAAgB,GACxB,MAAM,CAMR;AAQD,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,gBAAgB,EAAE,CAmBjG","sourcesContent":["import type { LearningDecision } from \"../autonomy/contracts.ts\";\nimport type { SessionEntry, SessionManager } from \"../session-manager.ts\";\nimport { cloneLearningDecisionForStorage, isLearningDecision } from \"./learning-gate.ts\";\n\nexport const LEARNING_DECISION_CUSTOM_TYPE = \"learning_decision\";\n\nexport interface LearningDecisionSnapshotPayload {\n\tversion: 1;\n\tdecision: LearningDecision;\n}\n\nexport function appendLearningDecisionSnapshot(\n\tsessionManager: Pick<SessionManager, \"appendCustomEntry\">,\n\tdecision: LearningDecision,\n): string {\n\tconst payload: LearningDecisionSnapshotPayload = {\n\t\tversion: 1,\n\t\tdecision: cloneLearningDecisionForStorage(decision),\n\t};\n\treturn sessionManager.appendCustomEntry(LEARNING_DECISION_CUSTOM_TYPE, payload);\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\nexport function getLearningDecisionSnapshots(entries: readonly SessionEntry[]): LearningDecision[] {\n\tconst results: LearningDecision[] = [];\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== LEARNING_DECISION_CUSTOM_TYPE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst payload = entry.data;\n\t\tif (!isPlainRecord(payload)) continue;\n\t\tif (payload.version !== 1) continue;\n\t\tif (!(\"decision\" in payload)) continue;\n\t\tconst decision = payload.decision;\n\t\tif (isLearningDecision(decision)) {\n\t\t\tresults.push(cloneLearningDecisionForStorage(decision));\n\t\t}\n\t}\n\n\treturn results;\n}\n"]}
1
+ {"version":3,"file":"session-learning-decision.d.ts","sourceRoot":"","sources":["../../../src/core/learning/session-learning-decision.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAGjE,eAAO,MAAM,6BAA6B,sBAAsB,CAAC;AAEjE,MAAM,WAAW,+BAA+B;IAC/C,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,gBAAgB,CAAC;CAC3B;AAED,wBAAgB,8BAA8B,CAC7C,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,mBAAmB,CAAC,EACzD,QAAQ,EAAE,gBAAgB,GACxB,MAAM,CAMR;AAQD,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,gBAAgB,EAAE,CAmBjG","sourcesContent":["import type { SessionEntry, SessionManager } from \"@caupulican/pi-agent-core/node\";\nimport type { LearningDecision } from \"../autonomy/contracts.ts\";\nimport { cloneLearningDecisionForStorage, isLearningDecision } from \"./learning-gate.ts\";\n\nexport const LEARNING_DECISION_CUSTOM_TYPE = \"learning_decision\";\n\nexport interface LearningDecisionSnapshotPayload {\n\tversion: 1;\n\tdecision: LearningDecision;\n}\n\nexport function appendLearningDecisionSnapshot(\n\tsessionManager: Pick<SessionManager, \"appendCustomEntry\">,\n\tdecision: LearningDecision,\n): string {\n\tconst payload: LearningDecisionSnapshotPayload = {\n\t\tversion: 1,\n\t\tdecision: cloneLearningDecisionForStorage(decision),\n\t};\n\treturn sessionManager.appendCustomEntry(LEARNING_DECISION_CUSTOM_TYPE, payload);\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\nexport function getLearningDecisionSnapshots(entries: readonly SessionEntry[]): LearningDecision[] {\n\tconst results: LearningDecision[] = [];\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== LEARNING_DECISION_CUSTOM_TYPE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst payload = entry.data;\n\t\tif (!isPlainRecord(payload)) continue;\n\t\tif (payload.version !== 1) continue;\n\t\tif (!(\"decision\" in payload)) continue;\n\t\tconst decision = payload.decision;\n\t\tif (isLearningDecision(decision)) {\n\t\t\tresults.push(cloneLearningDecisionForStorage(decision));\n\t\t}\n\t}\n\n\treturn results;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"session-learning-decision.js","sourceRoot":"","sources":["../../../src/core/learning/session-learning-decision.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,+BAA+B,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAEzF,MAAM,CAAC,MAAM,6BAA6B,GAAG,mBAAmB,CAAC;AAOjE,MAAM,UAAU,8BAA8B,CAC7C,cAAyD,EACzD,QAA0B,EACjB;IACT,MAAM,OAAO,GAAoC;QAChD,OAAO,EAAE,CAAC;QACV,QAAQ,EAAE,+BAA+B,CAAC,QAAQ,CAAC;KACnD,CAAC;IACF,OAAO,cAAc,CAAC,iBAAiB,CAAC,6BAA6B,EAAE,OAAO,CAAC,CAAC;AAAA,CAChF;AAED,SAAS,aAAa,CAAC,KAAc,EAAoC;IACxE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9E,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC/C,OAAO,SAAS,KAAK,MAAM,CAAC,SAAS,IAAI,SAAS,KAAK,IAAI,CAAC;AAAA,CAC5D;AAED,MAAM,UAAU,4BAA4B,CAAC,OAAgC,EAAsB;IAClG,MAAM,OAAO,GAAuB,EAAE,CAAC;IAEvC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,KAAK,6BAA6B,EAAE,CAAC;YACnF,SAAS;QACV,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC;QAC3B,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC;YAAE,SAAS;QACtC,IAAI,OAAO,CAAC,OAAO,KAAK,CAAC;YAAE,SAAS;QACpC,IAAI,CAAC,CAAC,UAAU,IAAI,OAAO,CAAC;YAAE,SAAS;QACvC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QAClC,IAAI,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;YAClC,OAAO,CAAC,IAAI,CAAC,+BAA+B,CAAC,QAAQ,CAAC,CAAC,CAAC;QACzD,CAAC;IACF,CAAC;IAED,OAAO,OAAO,CAAC;AAAA,CACf","sourcesContent":["import type { LearningDecision } from \"../autonomy/contracts.ts\";\nimport type { SessionEntry, SessionManager } from \"../session-manager.ts\";\nimport { cloneLearningDecisionForStorage, isLearningDecision } from \"./learning-gate.ts\";\n\nexport const LEARNING_DECISION_CUSTOM_TYPE = \"learning_decision\";\n\nexport interface LearningDecisionSnapshotPayload {\n\tversion: 1;\n\tdecision: LearningDecision;\n}\n\nexport function appendLearningDecisionSnapshot(\n\tsessionManager: Pick<SessionManager, \"appendCustomEntry\">,\n\tdecision: LearningDecision,\n): string {\n\tconst payload: LearningDecisionSnapshotPayload = {\n\t\tversion: 1,\n\t\tdecision: cloneLearningDecisionForStorage(decision),\n\t};\n\treturn sessionManager.appendCustomEntry(LEARNING_DECISION_CUSTOM_TYPE, payload);\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\nexport function getLearningDecisionSnapshots(entries: readonly SessionEntry[]): LearningDecision[] {\n\tconst results: LearningDecision[] = [];\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== LEARNING_DECISION_CUSTOM_TYPE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst payload = entry.data;\n\t\tif (!isPlainRecord(payload)) continue;\n\t\tif (payload.version !== 1) continue;\n\t\tif (!(\"decision\" in payload)) continue;\n\t\tconst decision = payload.decision;\n\t\tif (isLearningDecision(decision)) {\n\t\t\tresults.push(cloneLearningDecisionForStorage(decision));\n\t\t}\n\t}\n\n\treturn results;\n}\n"]}
1
+ {"version":3,"file":"session-learning-decision.js","sourceRoot":"","sources":["../../../src/core/learning/session-learning-decision.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,+BAA+B,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAEzF,MAAM,CAAC,MAAM,6BAA6B,GAAG,mBAAmB,CAAC;AAOjE,MAAM,UAAU,8BAA8B,CAC7C,cAAyD,EACzD,QAA0B,EACjB;IACT,MAAM,OAAO,GAAoC;QAChD,OAAO,EAAE,CAAC;QACV,QAAQ,EAAE,+BAA+B,CAAC,QAAQ,CAAC;KACnD,CAAC;IACF,OAAO,cAAc,CAAC,iBAAiB,CAAC,6BAA6B,EAAE,OAAO,CAAC,CAAC;AAAA,CAChF;AAED,SAAS,aAAa,CAAC,KAAc,EAAoC;IACxE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9E,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC/C,OAAO,SAAS,KAAK,MAAM,CAAC,SAAS,IAAI,SAAS,KAAK,IAAI,CAAC;AAAA,CAC5D;AAED,MAAM,UAAU,4BAA4B,CAAC,OAAgC,EAAsB;IAClG,MAAM,OAAO,GAAuB,EAAE,CAAC;IAEvC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,KAAK,6BAA6B,EAAE,CAAC;YACnF,SAAS;QACV,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC;QAC3B,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC;YAAE,SAAS;QACtC,IAAI,OAAO,CAAC,OAAO,KAAK,CAAC;YAAE,SAAS;QACpC,IAAI,CAAC,CAAC,UAAU,IAAI,OAAO,CAAC;YAAE,SAAS;QACvC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QAClC,IAAI,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;YAClC,OAAO,CAAC,IAAI,CAAC,+BAA+B,CAAC,QAAQ,CAAC,CAAC,CAAC;QACzD,CAAC;IACF,CAAC;IAED,OAAO,OAAO,CAAC;AAAA,CACf","sourcesContent":["import type { SessionEntry, SessionManager } from \"@caupulican/pi-agent-core/node\";\nimport type { LearningDecision } from \"../autonomy/contracts.ts\";\nimport { cloneLearningDecisionForStorage, isLearningDecision } from \"./learning-gate.ts\";\n\nexport const LEARNING_DECISION_CUSTOM_TYPE = \"learning_decision\";\n\nexport interface LearningDecisionSnapshotPayload {\n\tversion: 1;\n\tdecision: LearningDecision;\n}\n\nexport function appendLearningDecisionSnapshot(\n\tsessionManager: Pick<SessionManager, \"appendCustomEntry\">,\n\tdecision: LearningDecision,\n): string {\n\tconst payload: LearningDecisionSnapshotPayload = {\n\t\tversion: 1,\n\t\tdecision: cloneLearningDecisionForStorage(decision),\n\t};\n\treturn sessionManager.appendCustomEntry(LEARNING_DECISION_CUSTOM_TYPE, payload);\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\nexport function getLearningDecisionSnapshots(entries: readonly SessionEntry[]): LearningDecision[] {\n\tconst results: LearningDecision[] = [];\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== LEARNING_DECISION_CUSTOM_TYPE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst payload = entry.data;\n\t\tif (!isPlainRecord(payload)) continue;\n\t\tif (payload.version !== 1) continue;\n\t\tif (!(\"decision\" in payload)) continue;\n\t\tconst decision = payload.decision;\n\t\tif (isLearningDecision(decision)) {\n\t\t\tresults.push(cloneLearningDecisionForStorage(decision));\n\t\t}\n\t}\n\n\treturn results;\n}\n"]}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Local-runtime (Ollama) lifecycle controller.
3
+ *
4
+ * Extracted verbatim from agent-session.ts (god-file decomposition). Owns the cached, per-server
5
+ * {@link OllamaRuntime} instances, the "confirmed up this session" flag, and the router's readiness
6
+ * gate for a turn routed to a local (`ollama`) model — including the #31 install-on-consent flow and
7
+ * the #27 graceful tier-escalation fallback. Takes narrow deps (agent dir, a last-assistant-message
8
+ * accessor, the session's UI context/event emitter, and the router's own tier resolver) rather than
9
+ * the whole AgentSession.
10
+ */
11
+ import type { Api, AssistantMessage, Model } from "@caupulican/pi-ai";
12
+ import type { AgentSessionEvent } from "./agent-session.ts";
13
+ import type { RouteDecision } from "./autonomy/contracts.ts";
14
+ import type { ExtensionUIContext } from "./extensions/index.ts";
15
+ import { type LocalRuntimeDeps, OllamaRuntime } from "./models/local-runtime.ts";
16
+ export interface LocalRuntimeControllerDeps {
17
+ /** Root directory OllamaRuntime instances are scoped under — fixed for the session's lifetime. */
18
+ agentDir: string;
19
+ /** Test-injectable seams for OllamaRuntime's own fetch/spawn/exists calls; unset in production. */
20
+ localRuntimeDeps?: LocalRuntimeDeps;
21
+ /** The session's last assistant message, to detect a just-failed local call and drop a stale
22
+ * "confirmed up" flag. */
23
+ getLastAssistantMessage(): AssistantMessage | undefined;
24
+ /** The session's live interactive UI context, if any — undefined in headless/RPC/print modes. */
25
+ getUIContext(): ExtensionUIContext | undefined;
26
+ /** Emits a session event (only ever `warning` / `routing_start` / `routing_end` from this controller). */
27
+ emit(event: AgentSessionEvent): void;
28
+ /** Resolves the model configured for a router tier, respecting configured auth — owned by the
29
+ * router itself, not this controller. */
30
+ resolveConfiguredTierModel(tier: "medium" | "expensive"): Model<Api> | undefined;
31
+ /** `${provider}/${id}` label for a model, for warning/confirm text. */
32
+ formatModel(model: Model<Api>): string;
33
+ }
34
+ export declare class LocalRuntimeController {
35
+ /** Lazy, cached by baseUrl so the router path and any other caller share one instance per server. */
36
+ private readonly _runtimes;
37
+ /** Server URLs confirmed reachable THIS session — skips the health-check round trip on every
38
+ * local-routed turn once warm. Keyed the same way as _runtimes. */
39
+ private readonly _confirmedUp;
40
+ private readonly deps;
41
+ constructor(deps: LocalRuntimeControllerDeps);
42
+ /**
43
+ * Shared {@link OllamaRuntime} for a given server, lazily created and cached by baseUrl so every
44
+ * caller — the router's readiness gate below and any host UI's own model-lifecycle commands
45
+ * (e.g. `/models`) — sees and can stop the SAME pi-managed process instead of each tracking its
46
+ * own untracked child.
47
+ */
48
+ getLocalRuntime(baseUrl?: string): OllamaRuntime;
49
+ /** models.json registers a local model's baseUrl as `<server>/v1` (OpenAI-compat); the runtime's
50
+ * own health/boot endpoints are on the Ollama-native server root. */
51
+ deriveOllamaServerUrl(modelBaseUrl: string): string;
52
+ /**
53
+ * If the last assistant message in this session was an error from THIS exact local server, a
54
+ * cached "confirmed up" flag would be stale (the server may have died mid-session) — drop it so
55
+ * the next ensure-check is a real one instead of trusting stale state.
56
+ */
57
+ private invalidateIfLastCallFailed;
58
+ /**
59
+ * Ensure a routed model is actually reachable before the turn calls it. No-op (and free) for any
60
+ * non-local model — this only ever does network/process work for the `ollama` provider. Caches a
61
+ * "confirmed up this session" flag per server so a steady-state session pays the health-check
62
+ * round trip once, not on every turn; invalidated above when a prior local call actually failed,
63
+ * so a server that died mid-session gets re-detected rather than trusted forever. Boots via
64
+ * `startReuseExisting()` — never owned storage — so the turn sees the user's OWN pulled models,
65
+ * the same server `/models` commands and the user's own `ollama` CLI already talk to. Never
66
+ * installs anything itself (installGuide is GUIDE MODE: printed, never executed).
67
+ */
68
+ ensureLocalModelReady(model: Model<Api>): Promise<{
69
+ ready: boolean;
70
+ reason: string;
71
+ installGuide?: string[];
72
+ }>;
73
+ private maybeInstallOllamaOnConsent;
74
+ /**
75
+ * Router-swap gate (#27): a turn routed to a local model (any tier, including an executor-direct
76
+ * route — both carry tier "cheap") must not dead-end the turn just because ollama isn't up.
77
+ * Never a SILENT swap: every fallback is announced in a warning that states (i) the local model
78
+ * was unavailable and WHY — binary missing surfaces the install guide inline; any other reason
79
+ * gets a "check that ollama is running" hint — and (ii) which tier is now handling the turn, so
80
+ * the cost shift is never a surprise. Escalates cheap -> medium -> expensive, skipping any
81
+ * unconfigured intermediate tier, reusing the router's own existing "model unavailable"
82
+ * resolution (resolveConfiguredTierModel) rather than inventing a new fallback mechanism.
83
+ * Escalation is bounded: tier strictly increases each hop, so it terminates within two hops.
84
+ *
85
+ * Before the warning/escalation below: #31's consent gate gets one shot at fixing a missing
86
+ * binary interactively (see maybeInstallOllamaOnConsent) — declining, timing out, running
87
+ * headless, or the install attempt itself failing all fall through here unchanged, just with an
88
+ * honest reason (an install that failed is worded as a failed install, not re-labeled as if
89
+ * nothing was ever tried).
90
+ */
91
+ ensureRouteModelReady(resolved: {
92
+ decision: RouteDecision;
93
+ model: Model<Api>;
94
+ } | undefined): Promise<{
95
+ decision: RouteDecision;
96
+ model: Model<Api>;
97
+ } | undefined>;
98
+ }
99
+ //# sourceMappingURL=local-runtime-controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-runtime-controller.d.ts","sourceRoot":"","sources":["../../src/core/local-runtime-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,EAAE,KAAK,gBAAgB,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAUjF,MAAM,WAAW,0BAA0B;IAC1C,oGAAkG;IAClG,QAAQ,EAAE,MAAM,CAAC;IACjB,mGAAmG;IACnG,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC;8BAC0B;IAC1B,uBAAuB,IAAI,gBAAgB,GAAG,SAAS,CAAC;IACxD,mGAAiG;IACjG,YAAY,IAAI,kBAAkB,GAAG,SAAS,CAAC;IAC/C,0GAA0G;IAC1G,IAAI,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACrC;6CACyC;IACzC,0BAA0B,CAAC,IAAI,EAAE,QAAQ,GAAG,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IACjF,uEAAuE;IACvE,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC;CACvC;AAED,qBAAa,sBAAsB;IAClC,qGAAqG;IACrG,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoC;IAC9D;uEACmE;IACnE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAElD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA6B;IAElD,YAAY,IAAI,EAAE,0BAA0B,EAE3C;IAED;;;;;OAKG;IACH,eAAe,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,aAAa,CAQ/C;IAED;yEACqE;IACrE,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAElD;IAED;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;IAWlC;;;;;;;;;OASG;IACG,qBAAqB,CAC1B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GACf,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAuBtE;YAgBa,2BAA2B;IAsCzC;;;;;;;;;;;;;;;;OAgBG;IACG,qBAAqB,CAC1B,QAAQ,EAAE;QAAE,QAAQ,EAAE,aAAa,CAAC;QAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;KAAE,GAAG,SAAS,GAClE,OAAO,CAAC;QAAE,QAAQ,EAAE,aAAa,CAAC;QAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;KAAE,GAAG,SAAS,CAAC,CAuDrE;CACD","sourcesContent":["/**\n * Local-runtime (Ollama) lifecycle controller.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Owns the cached, per-server\n * {@link OllamaRuntime} instances, the \"confirmed up this session\" flag, and the router's readiness\n * gate for a turn routed to a local (`ollama`) model — including the #31 install-on-consent flow and\n * the #27 graceful tier-escalation fallback. Takes narrow deps (agent dir, a last-assistant-message\n * accessor, the session's UI context/event emitter, and the router's own tier resolver) rather than\n * the whole AgentSession.\n */\n\nimport type { Api, AssistantMessage, Model } from \"@caupulican/pi-ai\";\nimport type { AgentSessionEvent } from \"./agent-session.ts\";\nimport type { RouteDecision } from \"./autonomy/contracts.ts\";\nimport type { ExtensionUIContext } from \"./extensions/index.ts\";\nimport { OLLAMA_PROVIDER } from \"./models/local-registration.ts\";\nimport { type LocalRuntimeDeps, OllamaRuntime } from \"./models/local-runtime.ts\";\n\n/** User-facing router tiers in ascending order — \"learning\" is never selected for a user turn, so\n * it has no place in the escalation ladder (#27's ensureRouteModelReady walks this forward only). */\nconst MODEL_ROUTER_TIER_ORDER: readonly (\"cheap\" | \"medium\" | \"expensive\")[] = [\"cheap\", \"medium\", \"expensive\"];\n\n/** How long the #31 \"install ollama now?\" confirm waits before auto-dismissing (same as a \"No\") —\n * long enough to read and decide, short enough that an unattended session doesn't hang a turn on it. */\nconst OLLAMA_INSTALL_CONFIRM_TIMEOUT_MS = 30_000;\n\nexport interface LocalRuntimeControllerDeps {\n\t/** Root directory OllamaRuntime instances are scoped under — fixed for the session's lifetime. */\n\tagentDir: string;\n\t/** Test-injectable seams for OllamaRuntime's own fetch/spawn/exists calls; unset in production. */\n\tlocalRuntimeDeps?: LocalRuntimeDeps;\n\t/** The session's last assistant message, to detect a just-failed local call and drop a stale\n\t * \"confirmed up\" flag. */\n\tgetLastAssistantMessage(): AssistantMessage | undefined;\n\t/** The session's live interactive UI context, if any — undefined in headless/RPC/print modes. */\n\tgetUIContext(): ExtensionUIContext | undefined;\n\t/** Emits a session event (only ever `warning` / `routing_start` / `routing_end` from this controller). */\n\temit(event: AgentSessionEvent): void;\n\t/** Resolves the model configured for a router tier, respecting configured auth — owned by the\n\t * router itself, not this controller. */\n\tresolveConfiguredTierModel(tier: \"medium\" | \"expensive\"): Model<Api> | undefined;\n\t/** `${provider}/${id}` label for a model, for warning/confirm text. */\n\tformatModel(model: Model<Api>): string;\n}\n\nexport class LocalRuntimeController {\n\t/** Lazy, cached by baseUrl so the router path and any other caller share one instance per server. */\n\tprivate readonly _runtimes = new Map<string, OllamaRuntime>();\n\t/** Server URLs confirmed reachable THIS session — skips the health-check round trip on every\n\t * local-routed turn once warm. Keyed the same way as _runtimes. */\n\tprivate readonly _confirmedUp = new Set<string>();\n\n\tprivate readonly deps: LocalRuntimeControllerDeps;\n\n\tconstructor(deps: LocalRuntimeControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\t/**\n\t * Shared {@link OllamaRuntime} for a given server, lazily created and cached by baseUrl so every\n\t * caller — the router's readiness gate below and any host UI's own model-lifecycle commands\n\t * (e.g. `/models`) — sees and can stop the SAME pi-managed process instead of each tracking its\n\t * own untracked child.\n\t */\n\tgetLocalRuntime(baseUrl?: string): OllamaRuntime {\n\t\tconst key = baseUrl ?? \"default\";\n\t\tlet runtime = this._runtimes.get(key);\n\t\tif (!runtime) {\n\t\t\truntime = new OllamaRuntime({ agentDir: this.deps.agentDir, baseUrl, deps: this.deps.localRuntimeDeps });\n\t\t\tthis._runtimes.set(key, runtime);\n\t\t}\n\t\treturn runtime;\n\t}\n\n\t/** models.json registers a local model's baseUrl as `<server>/v1` (OpenAI-compat); the runtime's\n\t * own health/boot endpoints are on the Ollama-native server root. */\n\tderiveOllamaServerUrl(modelBaseUrl: string): string {\n\t\treturn modelBaseUrl.replace(/\\/v1\\/?$/, \"\");\n\t}\n\n\t/**\n\t * If the last assistant message in this session was an error from THIS exact local server, a\n\t * cached \"confirmed up\" flag would be stale (the server may have died mid-session) — drop it so\n\t * the next ensure-check is a real one instead of trusting stale state.\n\t */\n\tprivate invalidateIfLastCallFailed(model: Model<Api>, serverUrl: string): void {\n\t\tconst lastAssistant = this.deps.getLastAssistantMessage();\n\t\tif (\n\t\t\tlastAssistant?.stopReason === \"error\" &&\n\t\t\tlastAssistant.provider === OLLAMA_PROVIDER &&\n\t\t\tlastAssistant.model === model.id\n\t\t) {\n\t\t\tthis._confirmedUp.delete(serverUrl);\n\t\t}\n\t}\n\n\t/**\n\t * Ensure a routed model is actually reachable before the turn calls it. No-op (and free) for any\n\t * non-local model — this only ever does network/process work for the `ollama` provider. Caches a\n\t * \"confirmed up this session\" flag per server so a steady-state session pays the health-check\n\t * round trip once, not on every turn; invalidated above when a prior local call actually failed,\n\t * so a server that died mid-session gets re-detected rather than trusted forever. Boots via\n\t * `startReuseExisting()` — never owned storage — so the turn sees the user's OWN pulled models,\n\t * the same server `/models` commands and the user's own `ollama` CLI already talk to. Never\n\t * installs anything itself (installGuide is GUIDE MODE: printed, never executed).\n\t */\n\tasync ensureLocalModelReady(\n\t\tmodel: Model<Api>,\n\t): Promise<{ ready: boolean; reason: string; installGuide?: string[] }> {\n\t\tif (model.provider !== OLLAMA_PROVIDER) {\n\t\t\treturn { ready: true, reason: \"not_local\" };\n\t\t}\n\t\tconst serverUrl = this.deriveOllamaServerUrl(model.baseUrl);\n\t\tthis.invalidateIfLastCallFailed(model, serverUrl);\n\t\tif (this._confirmedUp.has(serverUrl)) {\n\t\t\treturn { ready: true, reason: \"confirmed_up_cached\" };\n\t\t}\n\t\tconst runtime = this.getLocalRuntime(serverUrl);\n\t\tconst status = await runtime.detect();\n\t\tif (status.serverUp) {\n\t\t\tthis._confirmedUp.add(serverUrl);\n\t\t\treturn { ready: true, reason: \"already_running\" };\n\t\t}\n\t\tif (!status.binaryPath) {\n\t\t\treturn { ready: false, reason: \"binary_missing\", installGuide: runtime.installGuide() };\n\t\t}\n\t\tconst started = await runtime.startReuseExisting();\n\t\tif (started.started) {\n\t\t\tthis._confirmedUp.add(serverUrl);\n\t\t}\n\t\treturn { ready: started.started, reason: started.reason };\n\t}\n\n\t/**\n\t * #31: the ONE case a routed local model's unreadiness can be fixed automatically is a missing\n\t * ollama binary — an unreachable server can't be helped by installing, so that reason is left to\n\t * the graceful-fallback warning below unchanged. Only offered when there's an interactive UI to\n\t * ask through: headless/RPC/print sessions have no UI context and fall straight through, same as\n\t * declining or timing out (both resolve confirm() to false). Reverses \"pi never runs installers\n\t * itself\" specifically for this one path — the user is asked first, the download is pi's own\n\t * (never curl|sh), and it lands in pi's own runtimes dir (see OllamaRuntime.installManaged).\n\t *\n\t * Pauses/resumes the routing working-indicator around the confirm dialog itself (re-emitting\n\t * routing_end/routing_start — both already idempotent, see interactive-mode.ts's handlers) so an\n\t * animated spinner doesn't fight a dialog the user is trying to read and answer; the indicator\n\t * comes back for the download/extract that follows a \"yes\", which is genuine processing feedback.\n\t */\n\tprivate async maybeInstallOllamaOnConsent(\n\t\tmodel: Model<Api>,\n\t\treadiness: { ready: boolean; reason: string; installGuide?: string[] },\n\t): Promise<{ ready: boolean; reason: string; installGuide?: string[]; installAttemptError?: string }> {\n\t\tconst ui = this.deps.getUIContext();\n\t\tif (!ui || readiness.ready || readiness.reason !== \"binary_missing\") return readiness;\n\n\t\tconst modelLabel = this.deps.formatModel(model);\n\t\tthis.deps.emit({ type: \"routing_end\" });\n\t\tlet confirmed: boolean;\n\t\ttry {\n\t\t\tconfirmed = await ui.confirm(\n\t\t\t\t\"Install Ollama?\",\n\t\t\t\t`Ollama isn't installed, so the local model \"${modelLabel}\" can't run. Pi can download and ` +\n\t\t\t\t\t\"install it now (a large one-time download, possibly over 1 GB depending on your platform) \" +\n\t\t\t\t\t\"into its own runtimes folder — never curl|sh, never touching anything outside pi's own \" +\n\t\t\t\t\t\"directory. Install it now?\",\n\t\t\t\t{ timeout: OLLAMA_INSTALL_CONFIRM_TIMEOUT_MS },\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.deps.emit({ type: \"routing_start\" });\n\t\t}\n\t\tif (!confirmed) return readiness;\n\n\t\tconst serverUrl = this.deriveOllamaServerUrl(model.baseUrl);\n\t\tconst runtime = this.getLocalRuntime(serverUrl);\n\t\tlet installResult: { ok: boolean; error?: string };\n\t\ttry {\n\t\t\tinstallResult = await runtime.installManaged((status) => ui.setStatus(\"ollama-install\", status));\n\t\t} finally {\n\t\t\tui.setStatus(\"ollama-install\", undefined);\n\t\t}\n\t\tif (!installResult.ok) {\n\t\t\treturn { ready: false, reason: \"install_failed\", installAttemptError: installResult.error };\n\t\t}\n\t\treturn this.ensureLocalModelReady(model);\n\t}\n\n\t/**\n\t * Router-swap gate (#27): a turn routed to a local model (any tier, including an executor-direct\n\t * route — both carry tier \"cheap\") must not dead-end the turn just because ollama isn't up.\n\t * Never a SILENT swap: every fallback is announced in a warning that states (i) the local model\n\t * was unavailable and WHY — binary missing surfaces the install guide inline; any other reason\n\t * gets a \"check that ollama is running\" hint — and (ii) which tier is now handling the turn, so\n\t * the cost shift is never a surprise. Escalates cheap -> medium -> expensive, skipping any\n\t * unconfigured intermediate tier, reusing the router's own existing \"model unavailable\"\n\t * resolution (resolveConfiguredTierModel) rather than inventing a new fallback mechanism.\n\t * Escalation is bounded: tier strictly increases each hop, so it terminates within two hops.\n\t *\n\t * Before the warning/escalation below: #31's consent gate gets one shot at fixing a missing\n\t * binary interactively (see maybeInstallOllamaOnConsent) — declining, timing out, running\n\t * headless, or the install attempt itself failing all fall through here unchanged, just with an\n\t * honest reason (an install that failed is worded as a failed install, not re-labeled as if\n\t * nothing was ever tried).\n\t */\n\tasync ensureRouteModelReady(\n\t\tresolved: { decision: RouteDecision; model: Model<Api> } | undefined,\n\t): Promise<{ decision: RouteDecision; model: Model<Api> } | undefined> {\n\t\tlet current = resolved;\n\t\twhile (current && current.model.provider === OLLAMA_PROVIDER) {\n\t\t\tlet readiness: { ready: boolean; reason: string; installGuide?: string[]; installAttemptError?: string } =\n\t\t\t\tawait this.ensureLocalModelReady(current.model);\n\t\t\tif (!readiness.ready) {\n\t\t\t\treadiness = await this.maybeInstallOllamaOnConsent(current.model, readiness);\n\t\t\t}\n\t\t\tif (readiness.ready) return current;\n\n\t\t\t// Walk the remaining tiers in order (never back down to cheap) and take the first one that\n\t\t\t// actually resolves — an unconfigured intermediate tier (e.g. no mediumModel set) must be\n\t\t\t// skipped, not treated as \"no fallback available\".\n\t\t\tconst startIndex = MODEL_ROUTER_TIER_ORDER.indexOf(current.decision.tier as \"cheap\" | \"medium\" | \"expensive\");\n\t\t\tlet escalated: { tier: \"medium\" | \"expensive\"; model: Model<Api> } | undefined;\n\t\t\tfor (let i = startIndex + 1; startIndex !== -1 && i < MODEL_ROUTER_TIER_ORDER.length; i++) {\n\t\t\t\tconst tier = MODEL_ROUTER_TIER_ORDER[i] as \"medium\" | \"expensive\";\n\t\t\t\tconst model = this.deps.resolveConfiguredTierModel(tier);\n\t\t\t\tif (model) {\n\t\t\t\t\tescalated = { tier, model };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst modelLabel = this.deps.formatModel(current.model);\n\t\t\tconst whyText = readiness.installAttemptError\n\t\t\t\t? `pi tried to install it just now, but the install attempt failed: ${readiness.installAttemptError}`\n\t\t\t\t: readiness.installGuide\n\t\t\t\t\t? [\"the ollama binary is not installed.\", ...readiness.installGuide].join(\"\\n\")\n\t\t\t\t\t: `its server is not reachable (${readiness.reason}) — check that ollama is running.`;\n\t\t\tconst fallbackText = escalated\n\t\t\t\t? `Falling back to the ${escalated.tier} tier for this turn.`\n\t\t\t\t: \"No other tier is configured — falling back to the session's default model.\";\n\t\t\tthis.deps.emit({\n\t\t\t\ttype: \"warning\",\n\t\t\t\tmessage: `Local model \"${modelLabel}\" is unavailable: ${whyText}\\n${fallbackText}`,\n\t\t\t});\n\n\t\t\tif (!escalated) return undefined; // no higher tier resolves — caller falls back to the session default\n\t\t\tcurrent = {\n\t\t\t\tmodel: escalated.model,\n\t\t\t\tdecision: {\n\t\t\t\t\t...current.decision,\n\t\t\t\t\ttier: escalated.tier,\n\t\t\t\t\tfallbackFrom: current.decision.tier,\n\t\t\t\t\treasonCode: \"local_model_not_ready_fallback\",\n\t\t\t\t\treasons: [\n\t\t\t\t\t\t...current.decision.reasons,\n\t\t\t\t\t\t`Local model not ready (${readiness.reason}); escalated to ${escalated.tier}`,\n\t\t\t\t\t],\n\t\t\t\t\tmodel: this.deps.formatModel(escalated.model),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\treturn current;\n\t}\n}\n"]}
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Local-runtime (Ollama) lifecycle controller.
3
+ *
4
+ * Extracted verbatim from agent-session.ts (god-file decomposition). Owns the cached, per-server
5
+ * {@link OllamaRuntime} instances, the "confirmed up this session" flag, and the router's readiness
6
+ * gate for a turn routed to a local (`ollama`) model — including the #31 install-on-consent flow and
7
+ * the #27 graceful tier-escalation fallback. Takes narrow deps (agent dir, a last-assistant-message
8
+ * accessor, the session's UI context/event emitter, and the router's own tier resolver) rather than
9
+ * the whole AgentSession.
10
+ */
11
+ import { OLLAMA_PROVIDER } from "./models/local-registration.js";
12
+ import { OllamaRuntime } from "./models/local-runtime.js";
13
+ /** User-facing router tiers in ascending order — "learning" is never selected for a user turn, so
14
+ * it has no place in the escalation ladder (#27's ensureRouteModelReady walks this forward only). */
15
+ const MODEL_ROUTER_TIER_ORDER = ["cheap", "medium", "expensive"];
16
+ /** How long the #31 "install ollama now?" confirm waits before auto-dismissing (same as a "No") —
17
+ * long enough to read and decide, short enough that an unattended session doesn't hang a turn on it. */
18
+ const OLLAMA_INSTALL_CONFIRM_TIMEOUT_MS = 30_000;
19
+ export class LocalRuntimeController {
20
+ /** Lazy, cached by baseUrl so the router path and any other caller share one instance per server. */
21
+ _runtimes = new Map();
22
+ /** Server URLs confirmed reachable THIS session — skips the health-check round trip on every
23
+ * local-routed turn once warm. Keyed the same way as _runtimes. */
24
+ _confirmedUp = new Set();
25
+ deps;
26
+ constructor(deps) {
27
+ this.deps = deps;
28
+ }
29
+ /**
30
+ * Shared {@link OllamaRuntime} for a given server, lazily created and cached by baseUrl so every
31
+ * caller — the router's readiness gate below and any host UI's own model-lifecycle commands
32
+ * (e.g. `/models`) — sees and can stop the SAME pi-managed process instead of each tracking its
33
+ * own untracked child.
34
+ */
35
+ getLocalRuntime(baseUrl) {
36
+ const key = baseUrl ?? "default";
37
+ let runtime = this._runtimes.get(key);
38
+ if (!runtime) {
39
+ runtime = new OllamaRuntime({ agentDir: this.deps.agentDir, baseUrl, deps: this.deps.localRuntimeDeps });
40
+ this._runtimes.set(key, runtime);
41
+ }
42
+ return runtime;
43
+ }
44
+ /** models.json registers a local model's baseUrl as `<server>/v1` (OpenAI-compat); the runtime's
45
+ * own health/boot endpoints are on the Ollama-native server root. */
46
+ deriveOllamaServerUrl(modelBaseUrl) {
47
+ return modelBaseUrl.replace(/\/v1\/?$/, "");
48
+ }
49
+ /**
50
+ * If the last assistant message in this session was an error from THIS exact local server, a
51
+ * cached "confirmed up" flag would be stale (the server may have died mid-session) — drop it so
52
+ * the next ensure-check is a real one instead of trusting stale state.
53
+ */
54
+ invalidateIfLastCallFailed(model, serverUrl) {
55
+ const lastAssistant = this.deps.getLastAssistantMessage();
56
+ if (lastAssistant?.stopReason === "error" &&
57
+ lastAssistant.provider === OLLAMA_PROVIDER &&
58
+ lastAssistant.model === model.id) {
59
+ this._confirmedUp.delete(serverUrl);
60
+ }
61
+ }
62
+ /**
63
+ * Ensure a routed model is actually reachable before the turn calls it. No-op (and free) for any
64
+ * non-local model — this only ever does network/process work for the `ollama` provider. Caches a
65
+ * "confirmed up this session" flag per server so a steady-state session pays the health-check
66
+ * round trip once, not on every turn; invalidated above when a prior local call actually failed,
67
+ * so a server that died mid-session gets re-detected rather than trusted forever. Boots via
68
+ * `startReuseExisting()` — never owned storage — so the turn sees the user's OWN pulled models,
69
+ * the same server `/models` commands and the user's own `ollama` CLI already talk to. Never
70
+ * installs anything itself (installGuide is GUIDE MODE: printed, never executed).
71
+ */
72
+ async ensureLocalModelReady(model) {
73
+ if (model.provider !== OLLAMA_PROVIDER) {
74
+ return { ready: true, reason: "not_local" };
75
+ }
76
+ const serverUrl = this.deriveOllamaServerUrl(model.baseUrl);
77
+ this.invalidateIfLastCallFailed(model, serverUrl);
78
+ if (this._confirmedUp.has(serverUrl)) {
79
+ return { ready: true, reason: "confirmed_up_cached" };
80
+ }
81
+ const runtime = this.getLocalRuntime(serverUrl);
82
+ const status = await runtime.detect();
83
+ if (status.serverUp) {
84
+ this._confirmedUp.add(serverUrl);
85
+ return { ready: true, reason: "already_running" };
86
+ }
87
+ if (!status.binaryPath) {
88
+ return { ready: false, reason: "binary_missing", installGuide: runtime.installGuide() };
89
+ }
90
+ const started = await runtime.startReuseExisting();
91
+ if (started.started) {
92
+ this._confirmedUp.add(serverUrl);
93
+ }
94
+ return { ready: started.started, reason: started.reason };
95
+ }
96
+ /**
97
+ * #31: the ONE case a routed local model's unreadiness can be fixed automatically is a missing
98
+ * ollama binary — an unreachable server can't be helped by installing, so that reason is left to
99
+ * the graceful-fallback warning below unchanged. Only offered when there's an interactive UI to
100
+ * ask through: headless/RPC/print sessions have no UI context and fall straight through, same as
101
+ * declining or timing out (both resolve confirm() to false). Reverses "pi never runs installers
102
+ * itself" specifically for this one path — the user is asked first, the download is pi's own
103
+ * (never curl|sh), and it lands in pi's own runtimes dir (see OllamaRuntime.installManaged).
104
+ *
105
+ * Pauses/resumes the routing working-indicator around the confirm dialog itself (re-emitting
106
+ * routing_end/routing_start — both already idempotent, see interactive-mode.ts's handlers) so an
107
+ * animated spinner doesn't fight a dialog the user is trying to read and answer; the indicator
108
+ * comes back for the download/extract that follows a "yes", which is genuine processing feedback.
109
+ */
110
+ async maybeInstallOllamaOnConsent(model, readiness) {
111
+ const ui = this.deps.getUIContext();
112
+ if (!ui || readiness.ready || readiness.reason !== "binary_missing")
113
+ return readiness;
114
+ const modelLabel = this.deps.formatModel(model);
115
+ this.deps.emit({ type: "routing_end" });
116
+ let confirmed;
117
+ try {
118
+ confirmed = await ui.confirm("Install Ollama?", `Ollama isn't installed, so the local model "${modelLabel}" can't run. Pi can download and ` +
119
+ "install it now (a large one-time download, possibly over 1 GB depending on your platform) " +
120
+ "into its own runtimes folder — never curl|sh, never touching anything outside pi's own " +
121
+ "directory. Install it now?", { timeout: OLLAMA_INSTALL_CONFIRM_TIMEOUT_MS });
122
+ }
123
+ finally {
124
+ this.deps.emit({ type: "routing_start" });
125
+ }
126
+ if (!confirmed)
127
+ return readiness;
128
+ const serverUrl = this.deriveOllamaServerUrl(model.baseUrl);
129
+ const runtime = this.getLocalRuntime(serverUrl);
130
+ let installResult;
131
+ try {
132
+ installResult = await runtime.installManaged((status) => ui.setStatus("ollama-install", status));
133
+ }
134
+ finally {
135
+ ui.setStatus("ollama-install", undefined);
136
+ }
137
+ if (!installResult.ok) {
138
+ return { ready: false, reason: "install_failed", installAttemptError: installResult.error };
139
+ }
140
+ return this.ensureLocalModelReady(model);
141
+ }
142
+ /**
143
+ * Router-swap gate (#27): a turn routed to a local model (any tier, including an executor-direct
144
+ * route — both carry tier "cheap") must not dead-end the turn just because ollama isn't up.
145
+ * Never a SILENT swap: every fallback is announced in a warning that states (i) the local model
146
+ * was unavailable and WHY — binary missing surfaces the install guide inline; any other reason
147
+ * gets a "check that ollama is running" hint — and (ii) which tier is now handling the turn, so
148
+ * the cost shift is never a surprise. Escalates cheap -> medium -> expensive, skipping any
149
+ * unconfigured intermediate tier, reusing the router's own existing "model unavailable"
150
+ * resolution (resolveConfiguredTierModel) rather than inventing a new fallback mechanism.
151
+ * Escalation is bounded: tier strictly increases each hop, so it terminates within two hops.
152
+ *
153
+ * Before the warning/escalation below: #31's consent gate gets one shot at fixing a missing
154
+ * binary interactively (see maybeInstallOllamaOnConsent) — declining, timing out, running
155
+ * headless, or the install attempt itself failing all fall through here unchanged, just with an
156
+ * honest reason (an install that failed is worded as a failed install, not re-labeled as if
157
+ * nothing was ever tried).
158
+ */
159
+ async ensureRouteModelReady(resolved) {
160
+ let current = resolved;
161
+ while (current && current.model.provider === OLLAMA_PROVIDER) {
162
+ let readiness = await this.ensureLocalModelReady(current.model);
163
+ if (!readiness.ready) {
164
+ readiness = await this.maybeInstallOllamaOnConsent(current.model, readiness);
165
+ }
166
+ if (readiness.ready)
167
+ return current;
168
+ // Walk the remaining tiers in order (never back down to cheap) and take the first one that
169
+ // actually resolves — an unconfigured intermediate tier (e.g. no mediumModel set) must be
170
+ // skipped, not treated as "no fallback available".
171
+ const startIndex = MODEL_ROUTER_TIER_ORDER.indexOf(current.decision.tier);
172
+ let escalated;
173
+ for (let i = startIndex + 1; startIndex !== -1 && i < MODEL_ROUTER_TIER_ORDER.length; i++) {
174
+ const tier = MODEL_ROUTER_TIER_ORDER[i];
175
+ const model = this.deps.resolveConfiguredTierModel(tier);
176
+ if (model) {
177
+ escalated = { tier, model };
178
+ break;
179
+ }
180
+ }
181
+ const modelLabel = this.deps.formatModel(current.model);
182
+ const whyText = readiness.installAttemptError
183
+ ? `pi tried to install it just now, but the install attempt failed: ${readiness.installAttemptError}`
184
+ : readiness.installGuide
185
+ ? ["the ollama binary is not installed.", ...readiness.installGuide].join("\n")
186
+ : `its server is not reachable (${readiness.reason}) — check that ollama is running.`;
187
+ const fallbackText = escalated
188
+ ? `Falling back to the ${escalated.tier} tier for this turn.`
189
+ : "No other tier is configured — falling back to the session's default model.";
190
+ this.deps.emit({
191
+ type: "warning",
192
+ message: `Local model "${modelLabel}" is unavailable: ${whyText}\n${fallbackText}`,
193
+ });
194
+ if (!escalated)
195
+ return undefined; // no higher tier resolves — caller falls back to the session default
196
+ current = {
197
+ model: escalated.model,
198
+ decision: {
199
+ ...current.decision,
200
+ tier: escalated.tier,
201
+ fallbackFrom: current.decision.tier,
202
+ reasonCode: "local_model_not_ready_fallback",
203
+ reasons: [
204
+ ...current.decision.reasons,
205
+ `Local model not ready (${readiness.reason}); escalated to ${escalated.tier}`,
206
+ ],
207
+ model: this.deps.formatModel(escalated.model),
208
+ },
209
+ };
210
+ }
211
+ return current;
212
+ }
213
+ }
214
+ //# sourceMappingURL=local-runtime-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-runtime-controller.js","sourceRoot":"","sources":["../../src/core/local-runtime-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Local-runtime (Ollama) lifecycle controller.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Owns the cached, per-server\n * {@link OllamaRuntime} instances, the \"confirmed up this session\" flag, and the router's readiness\n * gate for a turn routed to a local (`ollama`) model — including the #31 install-on-consent flow and\n * the #27 graceful tier-escalation fallback. Takes narrow deps (agent dir, a last-assistant-message\n * accessor, the session's UI context/event emitter, and the router's own tier resolver) rather than\n * the whole AgentSession.\n */\n\nimport type { Api, AssistantMessage, Model } from \"@caupulican/pi-ai\";\nimport type { AgentSessionEvent } from \"./agent-session.ts\";\nimport type { RouteDecision } from \"./autonomy/contracts.ts\";\nimport type { ExtensionUIContext } from \"./extensions/index.ts\";\nimport { OLLAMA_PROVIDER } from \"./models/local-registration.ts\";\nimport { type LocalRuntimeDeps, OllamaRuntime } from \"./models/local-runtime.ts\";\n\n/** User-facing router tiers in ascending order — \"learning\" is never selected for a user turn, so\n * it has no place in the escalation ladder (#27's ensureRouteModelReady walks this forward only). */\nconst MODEL_ROUTER_TIER_ORDER: readonly (\"cheap\" | \"medium\" | \"expensive\")[] = [\"cheap\", \"medium\", \"expensive\"];\n\n/** How long the #31 \"install ollama now?\" confirm waits before auto-dismissing (same as a \"No\") —\n * long enough to read and decide, short enough that an unattended session doesn't hang a turn on it. */\nconst OLLAMA_INSTALL_CONFIRM_TIMEOUT_MS = 30_000;\n\nexport interface LocalRuntimeControllerDeps {\n\t/** Root directory OllamaRuntime instances are scoped under — fixed for the session's lifetime. */\n\tagentDir: string;\n\t/** Test-injectable seams for OllamaRuntime's own fetch/spawn/exists calls; unset in production. */\n\tlocalRuntimeDeps?: LocalRuntimeDeps;\n\t/** The session's last assistant message, to detect a just-failed local call and drop a stale\n\t * \"confirmed up\" flag. */\n\tgetLastAssistantMessage(): AssistantMessage | undefined;\n\t/** The session's live interactive UI context, if any — undefined in headless/RPC/print modes. */\n\tgetUIContext(): ExtensionUIContext | undefined;\n\t/** Emits a session event (only ever `warning` / `routing_start` / `routing_end` from this controller). */\n\temit(event: AgentSessionEvent): void;\n\t/** Resolves the model configured for a router tier, respecting configured auth — owned by the\n\t * router itself, not this controller. */\n\tresolveConfiguredTierModel(tier: \"medium\" | \"expensive\"): Model<Api> | undefined;\n\t/** `${provider}/${id}` label for a model, for warning/confirm text. */\n\tformatModel(model: Model<Api>): string;\n}\n\nexport class LocalRuntimeController {\n\t/** Lazy, cached by baseUrl so the router path and any other caller share one instance per server. */\n\tprivate readonly _runtimes = new Map<string, OllamaRuntime>();\n\t/** Server URLs confirmed reachable THIS session — skips the health-check round trip on every\n\t * local-routed turn once warm. Keyed the same way as _runtimes. */\n\tprivate readonly _confirmedUp = new Set<string>();\n\n\tprivate readonly deps: LocalRuntimeControllerDeps;\n\n\tconstructor(deps: LocalRuntimeControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\t/**\n\t * Shared {@link OllamaRuntime} for a given server, lazily created and cached by baseUrl so every\n\t * caller — the router's readiness gate below and any host UI's own model-lifecycle commands\n\t * (e.g. `/models`) — sees and can stop the SAME pi-managed process instead of each tracking its\n\t * own untracked child.\n\t */\n\tgetLocalRuntime(baseUrl?: string): OllamaRuntime {\n\t\tconst key = baseUrl ?? \"default\";\n\t\tlet runtime = this._runtimes.get(key);\n\t\tif (!runtime) {\n\t\t\truntime = new OllamaRuntime({ agentDir: this.deps.agentDir, baseUrl, deps: this.deps.localRuntimeDeps });\n\t\t\tthis._runtimes.set(key, runtime);\n\t\t}\n\t\treturn runtime;\n\t}\n\n\t/** models.json registers a local model's baseUrl as `<server>/v1` (OpenAI-compat); the runtime's\n\t * own health/boot endpoints are on the Ollama-native server root. */\n\tderiveOllamaServerUrl(modelBaseUrl: string): string {\n\t\treturn modelBaseUrl.replace(/\\/v1\\/?$/, \"\");\n\t}\n\n\t/**\n\t * If the last assistant message in this session was an error from THIS exact local server, a\n\t * cached \"confirmed up\" flag would be stale (the server may have died mid-session) — drop it so\n\t * the next ensure-check is a real one instead of trusting stale state.\n\t */\n\tprivate invalidateIfLastCallFailed(model: Model<Api>, serverUrl: string): void {\n\t\tconst lastAssistant = this.deps.getLastAssistantMessage();\n\t\tif (\n\t\t\tlastAssistant?.stopReason === \"error\" &&\n\t\t\tlastAssistant.provider === OLLAMA_PROVIDER &&\n\t\t\tlastAssistant.model === model.id\n\t\t) {\n\t\t\tthis._confirmedUp.delete(serverUrl);\n\t\t}\n\t}\n\n\t/**\n\t * Ensure a routed model is actually reachable before the turn calls it. No-op (and free) for any\n\t * non-local model — this only ever does network/process work for the `ollama` provider. Caches a\n\t * \"confirmed up this session\" flag per server so a steady-state session pays the health-check\n\t * round trip once, not on every turn; invalidated above when a prior local call actually failed,\n\t * so a server that died mid-session gets re-detected rather than trusted forever. Boots via\n\t * `startReuseExisting()` — never owned storage — so the turn sees the user's OWN pulled models,\n\t * the same server `/models` commands and the user's own `ollama` CLI already talk to. Never\n\t * installs anything itself (installGuide is GUIDE MODE: printed, never executed).\n\t */\n\tasync ensureLocalModelReady(\n\t\tmodel: Model<Api>,\n\t): Promise<{ ready: boolean; reason: string; installGuide?: string[] }> {\n\t\tif (model.provider !== OLLAMA_PROVIDER) {\n\t\t\treturn { ready: true, reason: \"not_local\" };\n\t\t}\n\t\tconst serverUrl = this.deriveOllamaServerUrl(model.baseUrl);\n\t\tthis.invalidateIfLastCallFailed(model, serverUrl);\n\t\tif (this._confirmedUp.has(serverUrl)) {\n\t\t\treturn { ready: true, reason: \"confirmed_up_cached\" };\n\t\t}\n\t\tconst runtime = this.getLocalRuntime(serverUrl);\n\t\tconst status = await runtime.detect();\n\t\tif (status.serverUp) {\n\t\t\tthis._confirmedUp.add(serverUrl);\n\t\t\treturn { ready: true, reason: \"already_running\" };\n\t\t}\n\t\tif (!status.binaryPath) {\n\t\t\treturn { ready: false, reason: \"binary_missing\", installGuide: runtime.installGuide() };\n\t\t}\n\t\tconst started = await runtime.startReuseExisting();\n\t\tif (started.started) {\n\t\t\tthis._confirmedUp.add(serverUrl);\n\t\t}\n\t\treturn { ready: started.started, reason: started.reason };\n\t}\n\n\t/**\n\t * #31: the ONE case a routed local model's unreadiness can be fixed automatically is a missing\n\t * ollama binary — an unreachable server can't be helped by installing, so that reason is left to\n\t * the graceful-fallback warning below unchanged. Only offered when there's an interactive UI to\n\t * ask through: headless/RPC/print sessions have no UI context and fall straight through, same as\n\t * declining or timing out (both resolve confirm() to false). Reverses \"pi never runs installers\n\t * itself\" specifically for this one path — the user is asked first, the download is pi's own\n\t * (never curl|sh), and it lands in pi's own runtimes dir (see OllamaRuntime.installManaged).\n\t *\n\t * Pauses/resumes the routing working-indicator around the confirm dialog itself (re-emitting\n\t * routing_end/routing_start — both already idempotent, see interactive-mode.ts's handlers) so an\n\t * animated spinner doesn't fight a dialog the user is trying to read and answer; the indicator\n\t * comes back for the download/extract that follows a \"yes\", which is genuine processing feedback.\n\t */\n\tprivate async maybeInstallOllamaOnConsent(\n\t\tmodel: Model<Api>,\n\t\treadiness: { ready: boolean; reason: string; installGuide?: string[] },\n\t): Promise<{ ready: boolean; reason: string; installGuide?: string[]; installAttemptError?: string }> {\n\t\tconst ui = this.deps.getUIContext();\n\t\tif (!ui || readiness.ready || readiness.reason !== \"binary_missing\") return readiness;\n\n\t\tconst modelLabel = this.deps.formatModel(model);\n\t\tthis.deps.emit({ type: \"routing_end\" });\n\t\tlet confirmed: boolean;\n\t\ttry {\n\t\t\tconfirmed = await ui.confirm(\n\t\t\t\t\"Install Ollama?\",\n\t\t\t\t`Ollama isn't installed, so the local model \"${modelLabel}\" can't run. Pi can download and ` +\n\t\t\t\t\t\"install it now (a large one-time download, possibly over 1 GB depending on your platform) \" +\n\t\t\t\t\t\"into its own runtimes folder — never curl|sh, never touching anything outside pi's own \" +\n\t\t\t\t\t\"directory. Install it now?\",\n\t\t\t\t{ timeout: OLLAMA_INSTALL_CONFIRM_TIMEOUT_MS },\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.deps.emit({ type: \"routing_start\" });\n\t\t}\n\t\tif (!confirmed) return readiness;\n\n\t\tconst serverUrl = this.deriveOllamaServerUrl(model.baseUrl);\n\t\tconst runtime = this.getLocalRuntime(serverUrl);\n\t\tlet installResult: { ok: boolean; error?: string };\n\t\ttry {\n\t\t\tinstallResult = await runtime.installManaged((status) => ui.setStatus(\"ollama-install\", status));\n\t\t} finally {\n\t\t\tui.setStatus(\"ollama-install\", undefined);\n\t\t}\n\t\tif (!installResult.ok) {\n\t\t\treturn { ready: false, reason: \"install_failed\", installAttemptError: installResult.error };\n\t\t}\n\t\treturn this.ensureLocalModelReady(model);\n\t}\n\n\t/**\n\t * Router-swap gate (#27): a turn routed to a local model (any tier, including an executor-direct\n\t * route — both carry tier \"cheap\") must not dead-end the turn just because ollama isn't up.\n\t * Never a SILENT swap: every fallback is announced in a warning that states (i) the local model\n\t * was unavailable and WHY — binary missing surfaces the install guide inline; any other reason\n\t * gets a \"check that ollama is running\" hint — and (ii) which tier is now handling the turn, so\n\t * the cost shift is never a surprise. Escalates cheap -> medium -> expensive, skipping any\n\t * unconfigured intermediate tier, reusing the router's own existing \"model unavailable\"\n\t * resolution (resolveConfiguredTierModel) rather than inventing a new fallback mechanism.\n\t * Escalation is bounded: tier strictly increases each hop, so it terminates within two hops.\n\t *\n\t * Before the warning/escalation below: #31's consent gate gets one shot at fixing a missing\n\t * binary interactively (see maybeInstallOllamaOnConsent) — declining, timing out, running\n\t * headless, or the install attempt itself failing all fall through here unchanged, just with an\n\t * honest reason (an install that failed is worded as a failed install, not re-labeled as if\n\t * nothing was ever tried).\n\t */\n\tasync ensureRouteModelReady(\n\t\tresolved: { decision: RouteDecision; model: Model<Api> } | undefined,\n\t): Promise<{ decision: RouteDecision; model: Model<Api> } | undefined> {\n\t\tlet current = resolved;\n\t\twhile (current && current.model.provider === OLLAMA_PROVIDER) {\n\t\t\tlet readiness: { ready: boolean; reason: string; installGuide?: string[]; installAttemptError?: string } =\n\t\t\t\tawait this.ensureLocalModelReady(current.model);\n\t\t\tif (!readiness.ready) {\n\t\t\t\treadiness = await this.maybeInstallOllamaOnConsent(current.model, readiness);\n\t\t\t}\n\t\t\tif (readiness.ready) return current;\n\n\t\t\t// Walk the remaining tiers in order (never back down to cheap) and take the first one that\n\t\t\t// actually resolves — an unconfigured intermediate tier (e.g. no mediumModel set) must be\n\t\t\t// skipped, not treated as \"no fallback available\".\n\t\t\tconst startIndex = MODEL_ROUTER_TIER_ORDER.indexOf(current.decision.tier as \"cheap\" | \"medium\" | \"expensive\");\n\t\t\tlet escalated: { tier: \"medium\" | \"expensive\"; model: Model<Api> } | undefined;\n\t\t\tfor (let i = startIndex + 1; startIndex !== -1 && i < MODEL_ROUTER_TIER_ORDER.length; i++) {\n\t\t\t\tconst tier = MODEL_ROUTER_TIER_ORDER[i] as \"medium\" | \"expensive\";\n\t\t\t\tconst model = this.deps.resolveConfiguredTierModel(tier);\n\t\t\t\tif (model) {\n\t\t\t\t\tescalated = { tier, model };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst modelLabel = this.deps.formatModel(current.model);\n\t\t\tconst whyText = readiness.installAttemptError\n\t\t\t\t? `pi tried to install it just now, but the install attempt failed: ${readiness.installAttemptError}`\n\t\t\t\t: readiness.installGuide\n\t\t\t\t\t? [\"the ollama binary is not installed.\", ...readiness.installGuide].join(\"\\n\")\n\t\t\t\t\t: `its server is not reachable (${readiness.reason}) — check that ollama is running.`;\n\t\t\tconst fallbackText = escalated\n\t\t\t\t? `Falling back to the ${escalated.tier} tier for this turn.`\n\t\t\t\t: \"No other tier is configured — falling back to the session's default model.\";\n\t\t\tthis.deps.emit({\n\t\t\t\ttype: \"warning\",\n\t\t\t\tmessage: `Local model \"${modelLabel}\" is unavailable: ${whyText}\\n${fallbackText}`,\n\t\t\t});\n\n\t\t\tif (!escalated) return undefined; // no higher tier resolves — caller falls back to the session default\n\t\t\tcurrent = {\n\t\t\t\tmodel: escalated.model,\n\t\t\t\tdecision: {\n\t\t\t\t\t...current.decision,\n\t\t\t\t\ttier: escalated.tier,\n\t\t\t\t\tfallbackFrom: current.decision.tier,\n\t\t\t\t\treasonCode: \"local_model_not_ready_fallback\",\n\t\t\t\t\treasons: [\n\t\t\t\t\t\t...current.decision.reasons,\n\t\t\t\t\t\t`Local model not ready (${readiness.reason}); escalated to ${escalated.tier}`,\n\t\t\t\t\t],\n\t\t\t\t\tmodel: this.deps.formatModel(escalated.model),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\treturn current;\n\t}\n}\n"]}