@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":"model-ref.d.ts","sourceRoot":"","sources":["../../../src/core/models/model-ref.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,WAAW,GACpB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC5B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAOxC,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAqDlE","sourcesContent":["/**\n * Model source normalizer (local-model-lifecycle-design.md, \"one panel, three types\"):\n * everything a user can paste — an ollama tag, an hf.co GGUF ref, a full HuggingFace URL, a\n * copied `ollama pull ...` install command, or an API `provider/model` name — normalizes to ONE\n * typed source. Pure string work: pasted install commands are PARSED for their reference and\n * NEVER executed as shell; unknown forms are rejected with the reason.\n */\n\nexport type ModelSource =\n\t| { type: \"api\"; ref: string }\n\t| { type: \"local\"; pullRef: string }\n\t| { type: \"rejected\"; reason: string };\n\nconst OLLAMA_TAG = /^[a-z0-9][a-z0-9._-]*(?::[A-Za-z0-9._-]+)?$/;\nconst HF_REF = /^hf\\.co\\/([\\w.-]+)\\/([\\w.-]+)(?::([\\w.-]+))?$/i;\nconst HF_URL = /^https?:\\/\\/(?:www\\.)?huggingface\\.co\\/([\\w.-]+)\\/([\\w.-]+)(?:\\/.*)?$/i;\nconst SHELL_METACHARS = /[;&|`$<>(){}\\\\]/;\n\nexport function normalizeModelSource(rawInput: string): ModelSource {\n\tconst input = rawInput.trim();\n\tif (input.length === 0) return { type: \"rejected\", reason: \"empty input\" };\n\tif (input.length > 500) return { type: \"rejected\", reason: \"input too long to be a model reference\" };\n\n\t// Pasted install command: extract the reference, never execute anything.\n\tconst installCommand = /^ollama\\s+(?:pull|run)\\s+(.+)$/i.exec(input);\n\tif (installCommand) {\n\t\tconst argument = installCommand[1]!.trim().split(/\\s+/)[0] ?? \"\";\n\t\tif (SHELL_METACHARS.test(argument)) {\n\t\t\treturn { type: \"rejected\", reason: \"install command argument contains shell metacharacters\" };\n\t\t}\n\t\tconst inner = normalizeModelSource(argument);\n\t\tif (inner.type === \"local\") return inner;\n\t\treturn { type: \"rejected\", reason: `could not extract a model reference from the install command` };\n\t}\n\n\tif (SHELL_METACHARS.test(input) || /\\s/.test(input)) {\n\t\treturn { type: \"rejected\", reason: \"not a recognized model reference (contains spaces or shell characters)\" };\n\t}\n\n\t// Full HuggingFace URL -> hf.co pull ref (org/repo; a :quant suffix must be given explicitly).\n\tconst hfUrl = HF_URL.exec(input);\n\tif (hfUrl) {\n\t\treturn { type: \"local\", pullRef: `hf.co/${hfUrl[1]}/${hfUrl[2]}` };\n\t}\n\n\t// hf.co/org/repo[:quant]\n\tconst hfRef = HF_REF.exec(input);\n\tif (hfRef) {\n\t\treturn { type: \"local\", pullRef: `hf.co/${hfRef[1]}/${hfRef[2]}${hfRef[3] ? `:${hfRef[3]}` : \"\"}` };\n\t}\n\n\tif (input.includes(\"://\")) {\n\t\treturn { type: \"rejected\", reason: \"only huggingface.co URLs are recognized as local model links\" };\n\t}\n\n\t// provider/model -> API-registered model (nothing to install; auth + selection only).\n\tif (input.includes(\"/\")) {\n\t\tconst [provider, ...rest] = input.split(\"/\");\n\t\tconst model = rest.join(\"/\");\n\t\tif (provider && model && !model.includes(\"/\")) {\n\t\t\treturn { type: \"api\", ref: `${provider}/${model}` };\n\t\t}\n\t\treturn { type: \"rejected\", reason: \"expected provider/model or hf.co/org/repo[:quant]\" };\n\t}\n\n\t// Bare ollama tag (\"qwen3:1.7b\", \"pi-lifter:latest\", \"llama3\").\n\tif (OLLAMA_TAG.test(input)) {\n\t\treturn { type: \"local\", pullRef: input };\n\t}\n\n\treturn { type: \"rejected\", reason: \"not a recognized model reference\" };\n}\n"]}
1
+ {"version":3,"file":"model-ref.d.ts","sourceRoot":"","sources":["../../../src/core/models/model-ref.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,WAAW,GACpB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC5B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAOxC,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAqDlE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAMtF","sourcesContent":["/**\n * Model source normalizer (local-model-lifecycle-design.md, \"one panel, three types\"):\n * everything a user can paste — an ollama tag, an hf.co GGUF ref, a full HuggingFace URL, a\n * copied `ollama pull ...` install command, or an API `provider/model` name — normalizes to ONE\n * typed source. Pure string work: pasted install commands are PARSED for their reference and\n * NEVER executed as shell; unknown forms are rejected with the reason.\n */\n\nexport type ModelSource =\n\t| { type: \"api\"; ref: string }\n\t| { type: \"local\"; pullRef: string }\n\t| { type: \"rejected\"; reason: string };\n\nconst OLLAMA_TAG = /^[a-z0-9][a-z0-9._-]*(?::[A-Za-z0-9._-]+)?$/;\nconst HF_REF = /^hf\\.co\\/([\\w.-]+)\\/([\\w.-]+)(?::([\\w.-]+))?$/i;\nconst HF_URL = /^https?:\\/\\/(?:www\\.)?huggingface\\.co\\/([\\w.-]+)\\/([\\w.-]+)(?:\\/.*)?$/i;\nconst SHELL_METACHARS = /[;&|`$<>(){}\\\\]/;\n\nexport function normalizeModelSource(rawInput: string): ModelSource {\n\tconst input = rawInput.trim();\n\tif (input.length === 0) return { type: \"rejected\", reason: \"empty input\" };\n\tif (input.length > 500) return { type: \"rejected\", reason: \"input too long to be a model reference\" };\n\n\t// Pasted install command: extract the reference, never execute anything.\n\tconst installCommand = /^ollama\\s+(?:pull|run)\\s+(.+)$/i.exec(input);\n\tif (installCommand) {\n\t\tconst argument = installCommand[1]!.trim().split(/\\s+/)[0] ?? \"\";\n\t\tif (SHELL_METACHARS.test(argument)) {\n\t\t\treturn { type: \"rejected\", reason: \"install command argument contains shell metacharacters\" };\n\t\t}\n\t\tconst inner = normalizeModelSource(argument);\n\t\tif (inner.type === \"local\") return inner;\n\t\treturn { type: \"rejected\", reason: `could not extract a model reference from the install command` };\n\t}\n\n\tif (SHELL_METACHARS.test(input) || /\\s/.test(input)) {\n\t\treturn { type: \"rejected\", reason: \"not a recognized model reference (contains spaces or shell characters)\" };\n\t}\n\n\t// Full HuggingFace URL -> hf.co pull ref (org/repo; a :quant suffix must be given explicitly).\n\tconst hfUrl = HF_URL.exec(input);\n\tif (hfUrl) {\n\t\treturn { type: \"local\", pullRef: `hf.co/${hfUrl[1]}/${hfUrl[2]}` };\n\t}\n\n\t// hf.co/org/repo[:quant]\n\tconst hfRef = HF_REF.exec(input);\n\tif (hfRef) {\n\t\treturn { type: \"local\", pullRef: `hf.co/${hfRef[1]}/${hfRef[2]}${hfRef[3] ? `:${hfRef[3]}` : \"\"}` };\n\t}\n\n\tif (input.includes(\"://\")) {\n\t\treturn { type: \"rejected\", reason: \"only huggingface.co URLs are recognized as local model links\" };\n\t}\n\n\t// provider/model -> API-registered model (nothing to install; auth + selection only).\n\tif (input.includes(\"/\")) {\n\t\tconst [provider, ...rest] = input.split(\"/\");\n\t\tconst model = rest.join(\"/\");\n\t\tif (provider && model && !model.includes(\"/\")) {\n\t\t\treturn { type: \"api\", ref: `${provider}/${model}` };\n\t\t}\n\t\treturn { type: \"rejected\", reason: \"expected provider/model or hf.co/org/repo[:quant]\" };\n\t}\n\n\t// Bare ollama tag (\"qwen3:1.7b\", \"pi-lifter:latest\", \"llama3\").\n\tif (OLLAMA_TAG.test(input)) {\n\t\treturn { type: \"local\", pullRef: input };\n\t}\n\n\treturn { type: \"rejected\", reason: \"not a recognized model reference\" };\n}\n\n/**\n * Whether an installed-model listing entry (`installedName`, as `OllamaRuntime.list()` reports it)\n * is the same model as `ref` (a user-typed reference, or a `models.json`-registered id). Ollama\n * always reports installed models with an explicit tag — a pull with no tag is normalized to\n * `:latest` server-side — but a `ref` is frequently bare (\"llama3\"), so an exact string compare\n * alone misses that pairing. Exact match always counts; otherwise treat a bare ref against its\n * `:latest`-tagged listing as the same model, and the reverse (a `ref` explicitly pinned to\n * `:latest` against a bare listing), since the local-runtime interface is kept runtime-agnostic\n * (see local-runtime.ts's module docstring) and a future/alternate runtime could list bare names.\n * Shared by every ref-vs-installed-list comparison — see `AgentSession._warnIfManualModelChoiceIsRisky`\n * and `removeLocalModel` in interactive-mode.ts, which had the identical bug independently.\n */\nexport function matchesInstalledLocalModel(ref: string, installedName: string): boolean {\n\tif (ref === installedName) return true;\n\tconst hasTag = (value: string): boolean => value.includes(\":\");\n\tif (!hasTag(ref) && installedName === `${ref}:latest`) return true;\n\tif (!hasTag(installedName) && ref === `${installedName}:latest`) return true;\n\treturn false;\n}\n"]}
@@ -58,4 +58,26 @@ export function normalizeModelSource(rawInput) {
58
58
  }
59
59
  return { type: "rejected", reason: "not a recognized model reference" };
60
60
  }
61
+ /**
62
+ * Whether an installed-model listing entry (`installedName`, as `OllamaRuntime.list()` reports it)
63
+ * is the same model as `ref` (a user-typed reference, or a `models.json`-registered id). Ollama
64
+ * always reports installed models with an explicit tag — a pull with no tag is normalized to
65
+ * `:latest` server-side — but a `ref` is frequently bare ("llama3"), so an exact string compare
66
+ * alone misses that pairing. Exact match always counts; otherwise treat a bare ref against its
67
+ * `:latest`-tagged listing as the same model, and the reverse (a `ref` explicitly pinned to
68
+ * `:latest` against a bare listing), since the local-runtime interface is kept runtime-agnostic
69
+ * (see local-runtime.ts's module docstring) and a future/alternate runtime could list bare names.
70
+ * Shared by every ref-vs-installed-list comparison — see `AgentSession._warnIfManualModelChoiceIsRisky`
71
+ * and `removeLocalModel` in interactive-mode.ts, which had the identical bug independently.
72
+ */
73
+ export function matchesInstalledLocalModel(ref, installedName) {
74
+ if (ref === installedName)
75
+ return true;
76
+ const hasTag = (value) => value.includes(":");
77
+ if (!hasTag(ref) && installedName === `${ref}:latest`)
78
+ return true;
79
+ if (!hasTag(installedName) && ref === `${installedName}:latest`)
80
+ return true;
81
+ return false;
82
+ }
61
83
  //# sourceMappingURL=model-ref.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"model-ref.js","sourceRoot":"","sources":["../../../src/core/models/model-ref.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Model source normalizer (local-model-lifecycle-design.md, \"one panel, three types\"):\n * everything a user can paste — an ollama tag, an hf.co GGUF ref, a full HuggingFace URL, a\n * copied `ollama pull ...` install command, or an API `provider/model` name — normalizes to ONE\n * typed source. Pure string work: pasted install commands are PARSED for their reference and\n * NEVER executed as shell; unknown forms are rejected with the reason.\n */\n\nexport type ModelSource =\n\t| { type: \"api\"; ref: string }\n\t| { type: \"local\"; pullRef: string }\n\t| { type: \"rejected\"; reason: string };\n\nconst OLLAMA_TAG = /^[a-z0-9][a-z0-9._-]*(?::[A-Za-z0-9._-]+)?$/;\nconst HF_REF = /^hf\\.co\\/([\\w.-]+)\\/([\\w.-]+)(?::([\\w.-]+))?$/i;\nconst HF_URL = /^https?:\\/\\/(?:www\\.)?huggingface\\.co\\/([\\w.-]+)\\/([\\w.-]+)(?:\\/.*)?$/i;\nconst SHELL_METACHARS = /[;&|`$<>(){}\\\\]/;\n\nexport function normalizeModelSource(rawInput: string): ModelSource {\n\tconst input = rawInput.trim();\n\tif (input.length === 0) return { type: \"rejected\", reason: \"empty input\" };\n\tif (input.length > 500) return { type: \"rejected\", reason: \"input too long to be a model reference\" };\n\n\t// Pasted install command: extract the reference, never execute anything.\n\tconst installCommand = /^ollama\\s+(?:pull|run)\\s+(.+)$/i.exec(input);\n\tif (installCommand) {\n\t\tconst argument = installCommand[1]!.trim().split(/\\s+/)[0] ?? \"\";\n\t\tif (SHELL_METACHARS.test(argument)) {\n\t\t\treturn { type: \"rejected\", reason: \"install command argument contains shell metacharacters\" };\n\t\t}\n\t\tconst inner = normalizeModelSource(argument);\n\t\tif (inner.type === \"local\") return inner;\n\t\treturn { type: \"rejected\", reason: `could not extract a model reference from the install command` };\n\t}\n\n\tif (SHELL_METACHARS.test(input) || /\\s/.test(input)) {\n\t\treturn { type: \"rejected\", reason: \"not a recognized model reference (contains spaces or shell characters)\" };\n\t}\n\n\t// Full HuggingFace URL -> hf.co pull ref (org/repo; a :quant suffix must be given explicitly).\n\tconst hfUrl = HF_URL.exec(input);\n\tif (hfUrl) {\n\t\treturn { type: \"local\", pullRef: `hf.co/${hfUrl[1]}/${hfUrl[2]}` };\n\t}\n\n\t// hf.co/org/repo[:quant]\n\tconst hfRef = HF_REF.exec(input);\n\tif (hfRef) {\n\t\treturn { type: \"local\", pullRef: `hf.co/${hfRef[1]}/${hfRef[2]}${hfRef[3] ? `:${hfRef[3]}` : \"\"}` };\n\t}\n\n\tif (input.includes(\"://\")) {\n\t\treturn { type: \"rejected\", reason: \"only huggingface.co URLs are recognized as local model links\" };\n\t}\n\n\t// provider/model -> API-registered model (nothing to install; auth + selection only).\n\tif (input.includes(\"/\")) {\n\t\tconst [provider, ...rest] = input.split(\"/\");\n\t\tconst model = rest.join(\"/\");\n\t\tif (provider && model && !model.includes(\"/\")) {\n\t\t\treturn { type: \"api\", ref: `${provider}/${model}` };\n\t\t}\n\t\treturn { type: \"rejected\", reason: \"expected provider/model or hf.co/org/repo[:quant]\" };\n\t}\n\n\t// Bare ollama tag (\"qwen3:1.7b\", \"pi-lifter:latest\", \"llama3\").\n\tif (OLLAMA_TAG.test(input)) {\n\t\treturn { type: \"local\", pullRef: input };\n\t}\n\n\treturn { type: \"rejected\", reason: \"not a recognized model reference\" };\n}\n"]}
1
+ {"version":3,"file":"model-ref.js","sourceRoot":"","sources":["../../../src/core/models/model-ref.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Model source normalizer (local-model-lifecycle-design.md, \"one panel, three types\"):\n * everything a user can paste — an ollama tag, an hf.co GGUF ref, a full HuggingFace URL, a\n * copied `ollama pull ...` install command, or an API `provider/model` name — normalizes to ONE\n * typed source. Pure string work: pasted install commands are PARSED for their reference and\n * NEVER executed as shell; unknown forms are rejected with the reason.\n */\n\nexport type ModelSource =\n\t| { type: \"api\"; ref: string }\n\t| { type: \"local\"; pullRef: string }\n\t| { type: \"rejected\"; reason: string };\n\nconst OLLAMA_TAG = /^[a-z0-9][a-z0-9._-]*(?::[A-Za-z0-9._-]+)?$/;\nconst HF_REF = /^hf\\.co\\/([\\w.-]+)\\/([\\w.-]+)(?::([\\w.-]+))?$/i;\nconst HF_URL = /^https?:\\/\\/(?:www\\.)?huggingface\\.co\\/([\\w.-]+)\\/([\\w.-]+)(?:\\/.*)?$/i;\nconst SHELL_METACHARS = /[;&|`$<>(){}\\\\]/;\n\nexport function normalizeModelSource(rawInput: string): ModelSource {\n\tconst input = rawInput.trim();\n\tif (input.length === 0) return { type: \"rejected\", reason: \"empty input\" };\n\tif (input.length > 500) return { type: \"rejected\", reason: \"input too long to be a model reference\" };\n\n\t// Pasted install command: extract the reference, never execute anything.\n\tconst installCommand = /^ollama\\s+(?:pull|run)\\s+(.+)$/i.exec(input);\n\tif (installCommand) {\n\t\tconst argument = installCommand[1]!.trim().split(/\\s+/)[0] ?? \"\";\n\t\tif (SHELL_METACHARS.test(argument)) {\n\t\t\treturn { type: \"rejected\", reason: \"install command argument contains shell metacharacters\" };\n\t\t}\n\t\tconst inner = normalizeModelSource(argument);\n\t\tif (inner.type === \"local\") return inner;\n\t\treturn { type: \"rejected\", reason: `could not extract a model reference from the install command` };\n\t}\n\n\tif (SHELL_METACHARS.test(input) || /\\s/.test(input)) {\n\t\treturn { type: \"rejected\", reason: \"not a recognized model reference (contains spaces or shell characters)\" };\n\t}\n\n\t// Full HuggingFace URL -> hf.co pull ref (org/repo; a :quant suffix must be given explicitly).\n\tconst hfUrl = HF_URL.exec(input);\n\tif (hfUrl) {\n\t\treturn { type: \"local\", pullRef: `hf.co/${hfUrl[1]}/${hfUrl[2]}` };\n\t}\n\n\t// hf.co/org/repo[:quant]\n\tconst hfRef = HF_REF.exec(input);\n\tif (hfRef) {\n\t\treturn { type: \"local\", pullRef: `hf.co/${hfRef[1]}/${hfRef[2]}${hfRef[3] ? `:${hfRef[3]}` : \"\"}` };\n\t}\n\n\tif (input.includes(\"://\")) {\n\t\treturn { type: \"rejected\", reason: \"only huggingface.co URLs are recognized as local model links\" };\n\t}\n\n\t// provider/model -> API-registered model (nothing to install; auth + selection only).\n\tif (input.includes(\"/\")) {\n\t\tconst [provider, ...rest] = input.split(\"/\");\n\t\tconst model = rest.join(\"/\");\n\t\tif (provider && model && !model.includes(\"/\")) {\n\t\t\treturn { type: \"api\", ref: `${provider}/${model}` };\n\t\t}\n\t\treturn { type: \"rejected\", reason: \"expected provider/model or hf.co/org/repo[:quant]\" };\n\t}\n\n\t// Bare ollama tag (\"qwen3:1.7b\", \"pi-lifter:latest\", \"llama3\").\n\tif (OLLAMA_TAG.test(input)) {\n\t\treturn { type: \"local\", pullRef: input };\n\t}\n\n\treturn { type: \"rejected\", reason: \"not a recognized model reference\" };\n}\n\n/**\n * Whether an installed-model listing entry (`installedName`, as `OllamaRuntime.list()` reports it)\n * is the same model as `ref` (a user-typed reference, or a `models.json`-registered id). Ollama\n * always reports installed models with an explicit tag — a pull with no tag is normalized to\n * `:latest` server-side — but a `ref` is frequently bare (\"llama3\"), so an exact string compare\n * alone misses that pairing. Exact match always counts; otherwise treat a bare ref against its\n * `:latest`-tagged listing as the same model, and the reverse (a `ref` explicitly pinned to\n * `:latest` against a bare listing), since the local-runtime interface is kept runtime-agnostic\n * (see local-runtime.ts's module docstring) and a future/alternate runtime could list bare names.\n * Shared by every ref-vs-installed-list comparison — see `AgentSession._warnIfManualModelChoiceIsRisky`\n * and `removeLocalModel` in interactive-mode.ts, which had the identical bug independently.\n */\nexport function matchesInstalledLocalModel(ref: string, installedName: string): boolean {\n\tif (ref === installedName) return true;\n\tconst hasTag = (value: string): boolean => value.includes(\":\");\n\tif (!hasTag(ref) && installedName === `${ref}:latest`) return true;\n\tif (!hasTag(installedName) && ref === `${installedName}:latest`) return true;\n\treturn false;\n}\n"]}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Resource-profile tool/extension gating + reload-time profile model re-application.
3
+ *
4
+ * Extracted verbatim from agent-session.ts (god-file decomposition). Resolves the active resource
5
+ * profile's tool allow/block filter, filters the runtime extension set (tracking the withheld/inert
6
+ * tallies it owns), reports the /context "withheld by profile" observations, and — on reload —
7
+ * re-applies a profile-bound model/thinking level unless an explicit launch flag set them. Owns the
8
+ * inert-extension warnings and profile-denied-extension count; everything else is read through deps.
9
+ */
10
+ import type { Agent, ThinkingLevel } from "@caupulican/pi-agent-core";
11
+ import type { SessionManager } from "@caupulican/pi-agent-core/node";
12
+ import type { Extension } from "./extensions/index.ts";
13
+ import type { ModelRegistry } from "./model-registry.ts";
14
+ import type { ResourceLoader } from "./resource-loader.ts";
15
+ import { type ResourceProfileFilterSettings, type SettingsManager } from "./settings-manager.ts";
16
+ export interface ProfileFilterControllerDeps {
17
+ getSettingsManager(): SettingsManager;
18
+ getResourceLoader(): ResourceLoader;
19
+ getModelRegistry(): ModelRegistry;
20
+ getCwd(): string;
21
+ getAgent(): Agent;
22
+ getSessionManager(): SessionManager;
23
+ /** Construction-time tool allow-set (undefined = no allow-list gate). */
24
+ getAllowedToolNames(): Set<string> | undefined;
25
+ /** Construction-time tool exclude-set. */
26
+ getExcludedToolNames(): Set<string> | undefined;
27
+ /** Live resource-profile tool filter (re-derived on reload; may be undefined). */
28
+ getToolProfileFilter(): Required<ResourceProfileFilterSettings> | undefined;
29
+ /** Whether the model was set by an explicit launch flag (profile must not clobber it). */
30
+ isExplicitModel(): boolean;
31
+ /** Whether the thinking level was set by an explicit launch flag. */
32
+ isExplicitThinking(): boolean;
33
+ /** Apply a thinking level (delegates to model-selection). */
34
+ setThinkingLevel(level: ThinkingLevel): void;
35
+ }
36
+ export declare class ProfileFilterController {
37
+ /** G12: extensions loaded but rendered fully inert by the profile's tools filter. */
38
+ private _inertExtensionWarnings;
39
+ /** Count of extensions withheld by the active resource profile (for the /context observation). */
40
+ private _profileDeniedExtensionCount;
41
+ private readonly deps;
42
+ constructor(deps: ProfileFilterControllerDeps);
43
+ /** Inert-extension warnings tracked by the last {@link filterExtensionsForRuntime} pass. */
44
+ getInertExtensionWarnings(): string[];
45
+ /**
46
+ * Resolve the active resource-profile tool allow/block filter from current settings.
47
+ * Mirrors the construction-time derivation (settingsManager.getResourceProfileFilter("tools"))
48
+ * so reload() can re-apply it after a live settings/profile edit.
49
+ */
50
+ deriveToolProfileFilter(): Required<ResourceProfileFilterSettings>;
51
+ isToolOrCommandAllowedByProfile(name: string): boolean;
52
+ private _hasToolOrCommandProfileGate;
53
+ filterExtensionsForRuntime(extensions: Extension[]): Extension[];
54
+ /**
55
+ * /context observations for skills/prompts/extensions the active resource profile removed from
56
+ * listings — the analog of the withheld-AGENTS.md warning. Strict UAC makes these silently absent,
57
+ * so a lean profile's effect on the resource surface stays visible. Counts are profile-scoped
58
+ * (skills/prompts via the profile-independent discovery universe filtered by the live profile
59
+ * filter; extensions via the runtime filter's denied tally). Empty when nothing is withheld.
60
+ *
61
+ * Uses `isResourceDeniedByActiveProfile` (profile-only), not `isResourceAllowedByProfile` (which
62
+ * also folds in the user's own legacy `disabledResources` list): a plain user-disabled resource
63
+ * must never be misattributed to "the active resource profile" — that case is already surfaced by
64
+ * the G14 disable-wins warning. With no active profile at all, the helper always reports nothing
65
+ * denied, so this naturally stays silent (extensions keep their own runtime-filter-derived count,
66
+ * which is already correctly zero absent a profile).
67
+ */
68
+ profileDeniedResourceObservations(): string[];
69
+ /**
70
+ * Re-resolve the active resource profile's model/thinking from current settings and apply it.
71
+ * Only acts when the profile actually binds model/thinking AND that field was not set by an
72
+ * explicit launch flag — so live profile edits apply on reload without clobbering an explicit
73
+ * --model/--thinking. A no-op for profiles that don't bind a model.
74
+ */
75
+ reapplyActiveProfileModelSettings(): Promise<void>;
76
+ }
77
+ //# sourceMappingURL=profile-filter-controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-filter-controller.d.ts","sourceRoot":"","sources":["../../src/core/profile-filter-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAEN,KAAK,6BAA6B,EAClC,KAAK,eAAe,EACpB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,WAAW,2BAA2B;IAC3C,kBAAkB,IAAI,eAAe,CAAC;IACtC,iBAAiB,IAAI,cAAc,CAAC;IACpC,gBAAgB,IAAI,aAAa,CAAC;IAClC,MAAM,IAAI,MAAM,CAAC;IACjB,QAAQ,IAAI,KAAK,CAAC;IAClB,iBAAiB,IAAI,cAAc,CAAC;IACpC,yEAAyE;IACzE,mBAAmB,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;IAC/C,0CAA0C;IAC1C,oBAAoB,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;IAChD,kFAAkF;IAClF,oBAAoB,IAAI,QAAQ,CAAC,6BAA6B,CAAC,GAAG,SAAS,CAAC;IAC5E,0FAA0F;IAC1F,eAAe,IAAI,OAAO,CAAC;IAC3B,qEAAqE;IACrE,kBAAkB,IAAI,OAAO,CAAC;IAC9B,6DAA6D;IAC7D,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;CAC7C;AAED,qBAAa,uBAAuB;IACnC,qFAAqF;IACrF,OAAO,CAAC,uBAAuB,CAAgB;IAC/C,kGAAkG;IAClG,OAAO,CAAC,4BAA4B,CAAK;IAEzC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA8B;IAEnD,YAAY,IAAI,EAAE,2BAA2B,EAE5C;IAED,4FAA4F;IAC5F,yBAAyB,IAAI,MAAM,EAAE,CAEpC;IAED;;;;OAIG;IACH,uBAAuB,IAAI,QAAQ,CAAC,6BAA6B,CAAC,CAGjE;IAED,+BAA+B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAUrD;IAED,OAAO,CAAC,4BAA4B;IASpC,0BAA0B,CAAC,UAAU,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CAuC/D;IAED;;;;;;;;;;;;;OAaG;IACH,iCAAiC,IAAI,MAAM,EAAE,CA0B5C;IAED;;;;;OAKG;IACG,iCAAiC,IAAI,OAAO,CAAC,IAAI,CAAC,CA2BvD;CACD","sourcesContent":["/**\n * Resource-profile tool/extension gating + reload-time profile model re-application.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Resolves the active resource\n * profile's tool allow/block filter, filters the runtime extension set (tracking the withheld/inert\n * tallies it owns), reports the /context \"withheld by profile\" observations, and — on reload —\n * re-applies a profile-bound model/thinking level unless an explicit launch flag set them. Owns the\n * inert-extension warnings and profile-denied-extension count; everything else is read through deps.\n */\n\nimport type { Agent, ThinkingLevel } from \"@caupulican/pi-agent-core\";\nimport type { SessionManager } from \"@caupulican/pi-agent-core/node\";\nimport type { Extension } from \"./extensions/index.ts\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\nimport { resolveProfileModelSettings } from \"./model-resolver.ts\";\nimport type { ResourceLoader } from \"./resource-loader.ts\";\nimport {\n\tmatchesResourceProfilePattern,\n\ttype ResourceProfileFilterSettings,\n\ttype SettingsManager,\n} from \"./settings-manager.ts\";\n\nexport interface ProfileFilterControllerDeps {\n\tgetSettingsManager(): SettingsManager;\n\tgetResourceLoader(): ResourceLoader;\n\tgetModelRegistry(): ModelRegistry;\n\tgetCwd(): string;\n\tgetAgent(): Agent;\n\tgetSessionManager(): SessionManager;\n\t/** Construction-time tool allow-set (undefined = no allow-list gate). */\n\tgetAllowedToolNames(): Set<string> | undefined;\n\t/** Construction-time tool exclude-set. */\n\tgetExcludedToolNames(): Set<string> | undefined;\n\t/** Live resource-profile tool filter (re-derived on reload; may be undefined). */\n\tgetToolProfileFilter(): Required<ResourceProfileFilterSettings> | undefined;\n\t/** Whether the model was set by an explicit launch flag (profile must not clobber it). */\n\tisExplicitModel(): boolean;\n\t/** Whether the thinking level was set by an explicit launch flag. */\n\tisExplicitThinking(): boolean;\n\t/** Apply a thinking level (delegates to model-selection). */\n\tsetThinkingLevel(level: ThinkingLevel): void;\n}\n\nexport class ProfileFilterController {\n\t/** G12: extensions loaded but rendered fully inert by the profile's tools filter. */\n\tprivate _inertExtensionWarnings: string[] = [];\n\t/** Count of extensions withheld by the active resource profile (for the /context observation). */\n\tprivate _profileDeniedExtensionCount = 0;\n\n\tprivate readonly deps: ProfileFilterControllerDeps;\n\n\tconstructor(deps: ProfileFilterControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\t/** Inert-extension warnings tracked by the last {@link filterExtensionsForRuntime} pass. */\n\tgetInertExtensionWarnings(): string[] {\n\t\treturn this._inertExtensionWarnings;\n\t}\n\n\t/**\n\t * Resolve the active resource-profile tool allow/block filter from current settings.\n\t * Mirrors the construction-time derivation (settingsManager.getResourceProfileFilter(\"tools\"))\n\t * so reload() can re-apply it after a live settings/profile edit.\n\t */\n\tderiveToolProfileFilter(): Required<ResourceProfileFilterSettings> {\n\t\tconst filter = this.deps.getSettingsManager().getResourceProfileFilter(\"tools\");\n\t\treturn { allow: filter.allow ?? [], block: filter.block ?? [] };\n\t}\n\n\tisToolOrCommandAllowedByProfile(name: string): boolean {\n\t\tconst allowedToolNames = this.deps.getAllowedToolNames();\n\t\tconst excludedToolNames = this.deps.getExcludedToolNames();\n\t\tif (allowedToolNames && !allowedToolNames.has(name)) return false;\n\t\tif (excludedToolNames?.has(name)) return false;\n\t\tconst filter = this.deps.getToolProfileFilter();\n\t\tif (!filter) return true;\n\t\tif (filter.allow.length > 0 && !matchesResourceProfilePattern(name, filter.allow)) return false;\n\t\tif (matchesResourceProfilePattern(name, filter.block)) return false;\n\t\treturn true;\n\t}\n\n\tprivate _hasToolOrCommandProfileGate(): boolean {\n\t\tconst toolProfileFilter = this.deps.getToolProfileFilter();\n\t\treturn Boolean(\n\t\t\tthis.deps.getAllowedToolNames() ||\n\t\t\t\tthis.deps.getExcludedToolNames() ||\n\t\t\t\t(toolProfileFilter && (toolProfileFilter.allow.length > 0 || toolProfileFilter.block.length > 0)),\n\t\t);\n\t}\n\n\tfilterExtensionsForRuntime(extensions: Extension[]): Extension[] {\n\t\tconst settingsManager = this.deps.getSettingsManager();\n\t\tthis._inertExtensionWarnings = [];\n\t\tthis._profileDeniedExtensionCount = 0;\n\t\tif (settingsManager.getActiveResourceProfileNames().length === 0) {\n\t\t\tif (settingsManager.hasExplicitActiveResourceProfileSelection()) {\n\t\t\t\t// An explicit profile selection that resolves to no active profile is a deliberate\n\t\t\t\t// deny-all — every extension is withheld by that choice.\n\t\t\t\tthis._profileDeniedExtensionCount = extensions.length;\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\t// No profile in play: only inline/SDK extensions load by default. That is the baseline, not\n\t\t\t// a profile denial, so it is not counted as withheld.\n\t\t\treturn extensions.filter((extension) => extension.sourceInfo.source === \"inline\");\n\t\t}\n\t\tconst hasToolOrCommandGate = this._hasToolOrCommandProfileGate();\n\t\tconst allowedExtensions = extensions.filter((extension) =>\n\t\t\tsettingsManager.isResourceAllowedByProfile(\"extensions\", extension.path, extension.sourceInfo.baseDir),\n\t\t);\n\t\tthis._profileDeniedExtensionCount = extensions.length - allowedExtensions.length;\n\t\treturn allowedExtensions.map((extension) => {\n\t\t\tif (!hasToolOrCommandGate) return extension;\n\t\t\tconst tools = new Map(\n\t\t\t\tArray.from(extension.tools.entries()).filter(([name]) => this.isToolOrCommandAllowedByProfile(name)),\n\t\t\t);\n\t\t\tconst commands = new Map(\n\t\t\t\tArray.from(extension.commands.entries()).filter(([name]) => this.isToolOrCommandAllowedByProfile(name)),\n\t\t\t);\n\t\t\t// G12: an extension the profile ALLOWS whose every tool and command the tools filter\n\t\t\t// then denies loads and runs lifecycle hooks but is completely uninvocable — surface\n\t\t\t// it instead of presenting a \"loaded\" extension that silently does nothing.\n\t\t\tif (extension.tools.size + extension.commands.size > 0 && tools.size + commands.size === 0) {\n\t\t\t\tconst name = extension.path.split(/[\\\\/]/).pop() ?? extension.path;\n\t\t\t\tthis._inertExtensionWarnings.push(\n\t\t\t\t\t`extension \"${name}\" is loaded but fully inert: the active profile's tools filter denies all ${extension.tools.size} tool(s) and ${extension.commands.size} command(s) it contributes`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn { ...extension, tools, commands };\n\t\t});\n\t}\n\n\t/**\n\t * /context observations for skills/prompts/extensions the active resource profile removed from\n\t * listings — the analog of the withheld-AGENTS.md warning. Strict UAC makes these silently absent,\n\t * so a lean profile's effect on the resource surface stays visible. Counts are profile-scoped\n\t * (skills/prompts via the profile-independent discovery universe filtered by the live profile\n\t * filter; extensions via the runtime filter's denied tally). Empty when nothing is withheld.\n\t *\n\t * Uses `isResourceDeniedByActiveProfile` (profile-only), not `isResourceAllowedByProfile` (which\n\t * also folds in the user's own legacy `disabledResources` list): a plain user-disabled resource\n\t * must never be misattributed to \"the active resource profile\" — that case is already surfaced by\n\t * the G14 disable-wins warning. With no active profile at all, the helper always reports nothing\n\t * denied, so this naturally stays silent (extensions keep their own runtime-filter-derived count,\n\t * which is already correctly zero absent a profile).\n\t */\n\tprofileDeniedResourceObservations(): string[] {\n\t\tconst settingsManager = this.deps.getSettingsManager();\n\t\tconst resourceLoader = this.deps.getResourceLoader();\n\t\tconst cwd = this.deps.getCwd();\n\t\tconst observations: string[] = [];\n\t\tconst withheld = (kind: \"skills\" | \"prompts\", paths: string[]): number =>\n\t\t\tpaths.filter((path) => settingsManager.isResourceDeniedByActiveProfile(kind, path, cwd)).length;\n\n\t\tconst skillsWithheld = withheld(\"skills\", resourceLoader.getDiscoverableSkillPaths());\n\t\tif (skillsWithheld > 0) {\n\t\t\tobservations.push(\n\t\t\t\t`${skillsWithheld} skill(s) withheld by the active resource profile — grant the \"skills\" kind to restore them`,\n\t\t\t);\n\t\t}\n\t\tconst promptsWithheld = withheld(\"prompts\", resourceLoader.getDiscoverablePromptPaths());\n\t\tif (promptsWithheld > 0) {\n\t\t\tobservations.push(\n\t\t\t\t`${promptsWithheld} prompt(s) withheld by the active resource profile — grant the \"prompts\" kind to restore them`,\n\t\t\t);\n\t\t}\n\t\tif (this._profileDeniedExtensionCount > 0) {\n\t\t\tobservations.push(\n\t\t\t\t`${this._profileDeniedExtensionCount} extension(s) withheld by the active resource profile — grant the \"extensions\" kind to restore them`,\n\t\t\t);\n\t\t}\n\t\treturn observations;\n\t}\n\n\t/**\n\t * Re-resolve the active resource profile's model/thinking from current settings and apply it.\n\t * Only acts when the profile actually binds model/thinking AND that field was not set by an\n\t * explicit launch flag — so live profile edits apply on reload without clobbering an explicit\n\t * --model/--thinking. A no-op for profiles that don't bind a model.\n\t */\n\tasync reapplyActiveProfileModelSettings(): Promise<void> {\n\t\tif (this.deps.isExplicitModel() && this.deps.isExplicitThinking()) return;\n\t\tconst settingsManager = this.deps.getSettingsManager();\n\t\tconst activeProfileNames = settingsManager.getActiveResourceProfileNames();\n\t\tif (activeProfileNames.length === 0) return;\n\t\tconst profileSettings = resolveProfileModelSettings({\n\t\t\tactiveProfileNames,\n\t\t\tregistry: settingsManager.getProfileRegistry(),\n\t\t\tmodelRegistry: this.deps.getModelRegistry(),\n\t\t\tcwd: this.deps.getCwd(),\n\t\t});\n\t\tif (!this.deps.isExplicitModel() && profileSettings.model) {\n\t\t\tconst current = this.deps.getAgent().state.model;\n\t\t\tconst next = profileSettings.model;\n\t\t\tif (!current || current.provider !== next.provider || current.id !== next.id) {\n\t\t\t\t// Mirror the startup/cycle path: set the model directly (no auth gate, no settings\n\t\t\t\t// persist) so re-applying the profile model behaves like initial resolution rather\n\t\t\t\t// than a runtime model switch. No model_select emit here — reload rebuilds the\n\t\t\t\t// extension runtime and emits session_start(\"reload\") right after, and the UI\n\t\t\t\t// re-renders from session.model.\n\t\t\t\tthis.deps.getAgent().state.model = next;\n\t\t\t\tthis.deps.getSessionManager().appendModelChange(next.provider, next.id);\n\t\t\t}\n\t\t}\n\t\tif (!this.deps.isExplicitThinking() && profileSettings.thinkingLevel) {\n\t\t\tthis.deps.setThinkingLevel(profileSettings.thinkingLevel);\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Resource-profile tool/extension gating + reload-time profile model re-application.
3
+ *
4
+ * Extracted verbatim from agent-session.ts (god-file decomposition). Resolves the active resource
5
+ * profile's tool allow/block filter, filters the runtime extension set (tracking the withheld/inert
6
+ * tallies it owns), reports the /context "withheld by profile" observations, and — on reload —
7
+ * re-applies a profile-bound model/thinking level unless an explicit launch flag set them. Owns the
8
+ * inert-extension warnings and profile-denied-extension count; everything else is read through deps.
9
+ */
10
+ import { resolveProfileModelSettings } from "./model-resolver.js";
11
+ import { matchesResourceProfilePattern, } from "./settings-manager.js";
12
+ export class ProfileFilterController {
13
+ /** G12: extensions loaded but rendered fully inert by the profile's tools filter. */
14
+ _inertExtensionWarnings = [];
15
+ /** Count of extensions withheld by the active resource profile (for the /context observation). */
16
+ _profileDeniedExtensionCount = 0;
17
+ deps;
18
+ constructor(deps) {
19
+ this.deps = deps;
20
+ }
21
+ /** Inert-extension warnings tracked by the last {@link filterExtensionsForRuntime} pass. */
22
+ getInertExtensionWarnings() {
23
+ return this._inertExtensionWarnings;
24
+ }
25
+ /**
26
+ * Resolve the active resource-profile tool allow/block filter from current settings.
27
+ * Mirrors the construction-time derivation (settingsManager.getResourceProfileFilter("tools"))
28
+ * so reload() can re-apply it after a live settings/profile edit.
29
+ */
30
+ deriveToolProfileFilter() {
31
+ const filter = this.deps.getSettingsManager().getResourceProfileFilter("tools");
32
+ return { allow: filter.allow ?? [], block: filter.block ?? [] };
33
+ }
34
+ isToolOrCommandAllowedByProfile(name) {
35
+ const allowedToolNames = this.deps.getAllowedToolNames();
36
+ const excludedToolNames = this.deps.getExcludedToolNames();
37
+ if (allowedToolNames && !allowedToolNames.has(name))
38
+ return false;
39
+ if (excludedToolNames?.has(name))
40
+ return false;
41
+ const filter = this.deps.getToolProfileFilter();
42
+ if (!filter)
43
+ return true;
44
+ if (filter.allow.length > 0 && !matchesResourceProfilePattern(name, filter.allow))
45
+ return false;
46
+ if (matchesResourceProfilePattern(name, filter.block))
47
+ return false;
48
+ return true;
49
+ }
50
+ _hasToolOrCommandProfileGate() {
51
+ const toolProfileFilter = this.deps.getToolProfileFilter();
52
+ return Boolean(this.deps.getAllowedToolNames() ||
53
+ this.deps.getExcludedToolNames() ||
54
+ (toolProfileFilter && (toolProfileFilter.allow.length > 0 || toolProfileFilter.block.length > 0)));
55
+ }
56
+ filterExtensionsForRuntime(extensions) {
57
+ const settingsManager = this.deps.getSettingsManager();
58
+ this._inertExtensionWarnings = [];
59
+ this._profileDeniedExtensionCount = 0;
60
+ if (settingsManager.getActiveResourceProfileNames().length === 0) {
61
+ if (settingsManager.hasExplicitActiveResourceProfileSelection()) {
62
+ // An explicit profile selection that resolves to no active profile is a deliberate
63
+ // deny-all — every extension is withheld by that choice.
64
+ this._profileDeniedExtensionCount = extensions.length;
65
+ return [];
66
+ }
67
+ // No profile in play: only inline/SDK extensions load by default. That is the baseline, not
68
+ // a profile denial, so it is not counted as withheld.
69
+ return extensions.filter((extension) => extension.sourceInfo.source === "inline");
70
+ }
71
+ const hasToolOrCommandGate = this._hasToolOrCommandProfileGate();
72
+ const allowedExtensions = extensions.filter((extension) => settingsManager.isResourceAllowedByProfile("extensions", extension.path, extension.sourceInfo.baseDir));
73
+ this._profileDeniedExtensionCount = extensions.length - allowedExtensions.length;
74
+ return allowedExtensions.map((extension) => {
75
+ if (!hasToolOrCommandGate)
76
+ return extension;
77
+ const tools = new Map(Array.from(extension.tools.entries()).filter(([name]) => this.isToolOrCommandAllowedByProfile(name)));
78
+ const commands = new Map(Array.from(extension.commands.entries()).filter(([name]) => this.isToolOrCommandAllowedByProfile(name)));
79
+ // G12: an extension the profile ALLOWS whose every tool and command the tools filter
80
+ // then denies loads and runs lifecycle hooks but is completely uninvocable — surface
81
+ // it instead of presenting a "loaded" extension that silently does nothing.
82
+ if (extension.tools.size + extension.commands.size > 0 && tools.size + commands.size === 0) {
83
+ const name = extension.path.split(/[\\/]/).pop() ?? extension.path;
84
+ this._inertExtensionWarnings.push(`extension "${name}" is loaded but fully inert: the active profile's tools filter denies all ${extension.tools.size} tool(s) and ${extension.commands.size} command(s) it contributes`);
85
+ }
86
+ return { ...extension, tools, commands };
87
+ });
88
+ }
89
+ /**
90
+ * /context observations for skills/prompts/extensions the active resource profile removed from
91
+ * listings — the analog of the withheld-AGENTS.md warning. Strict UAC makes these silently absent,
92
+ * so a lean profile's effect on the resource surface stays visible. Counts are profile-scoped
93
+ * (skills/prompts via the profile-independent discovery universe filtered by the live profile
94
+ * filter; extensions via the runtime filter's denied tally). Empty when nothing is withheld.
95
+ *
96
+ * Uses `isResourceDeniedByActiveProfile` (profile-only), not `isResourceAllowedByProfile` (which
97
+ * also folds in the user's own legacy `disabledResources` list): a plain user-disabled resource
98
+ * must never be misattributed to "the active resource profile" — that case is already surfaced by
99
+ * the G14 disable-wins warning. With no active profile at all, the helper always reports nothing
100
+ * denied, so this naturally stays silent (extensions keep their own runtime-filter-derived count,
101
+ * which is already correctly zero absent a profile).
102
+ */
103
+ profileDeniedResourceObservations() {
104
+ const settingsManager = this.deps.getSettingsManager();
105
+ const resourceLoader = this.deps.getResourceLoader();
106
+ const cwd = this.deps.getCwd();
107
+ const observations = [];
108
+ const withheld = (kind, paths) => paths.filter((path) => settingsManager.isResourceDeniedByActiveProfile(kind, path, cwd)).length;
109
+ const skillsWithheld = withheld("skills", resourceLoader.getDiscoverableSkillPaths());
110
+ if (skillsWithheld > 0) {
111
+ observations.push(`${skillsWithheld} skill(s) withheld by the active resource profile — grant the "skills" kind to restore them`);
112
+ }
113
+ const promptsWithheld = withheld("prompts", resourceLoader.getDiscoverablePromptPaths());
114
+ if (promptsWithheld > 0) {
115
+ observations.push(`${promptsWithheld} prompt(s) withheld by the active resource profile — grant the "prompts" kind to restore them`);
116
+ }
117
+ if (this._profileDeniedExtensionCount > 0) {
118
+ observations.push(`${this._profileDeniedExtensionCount} extension(s) withheld by the active resource profile — grant the "extensions" kind to restore them`);
119
+ }
120
+ return observations;
121
+ }
122
+ /**
123
+ * Re-resolve the active resource profile's model/thinking from current settings and apply it.
124
+ * Only acts when the profile actually binds model/thinking AND that field was not set by an
125
+ * explicit launch flag — so live profile edits apply on reload without clobbering an explicit
126
+ * --model/--thinking. A no-op for profiles that don't bind a model.
127
+ */
128
+ async reapplyActiveProfileModelSettings() {
129
+ if (this.deps.isExplicitModel() && this.deps.isExplicitThinking())
130
+ return;
131
+ const settingsManager = this.deps.getSettingsManager();
132
+ const activeProfileNames = settingsManager.getActiveResourceProfileNames();
133
+ if (activeProfileNames.length === 0)
134
+ return;
135
+ const profileSettings = resolveProfileModelSettings({
136
+ activeProfileNames,
137
+ registry: settingsManager.getProfileRegistry(),
138
+ modelRegistry: this.deps.getModelRegistry(),
139
+ cwd: this.deps.getCwd(),
140
+ });
141
+ if (!this.deps.isExplicitModel() && profileSettings.model) {
142
+ const current = this.deps.getAgent().state.model;
143
+ const next = profileSettings.model;
144
+ if (!current || current.provider !== next.provider || current.id !== next.id) {
145
+ // Mirror the startup/cycle path: set the model directly (no auth gate, no settings
146
+ // persist) so re-applying the profile model behaves like initial resolution rather
147
+ // than a runtime model switch. No model_select emit here — reload rebuilds the
148
+ // extension runtime and emits session_start("reload") right after, and the UI
149
+ // re-renders from session.model.
150
+ this.deps.getAgent().state.model = next;
151
+ this.deps.getSessionManager().appendModelChange(next.provider, next.id);
152
+ }
153
+ }
154
+ if (!this.deps.isExplicitThinking() && profileSettings.thinkingLevel) {
155
+ this.deps.setThinkingLevel(profileSettings.thinkingLevel);
156
+ }
157
+ }
158
+ }
159
+ //# sourceMappingURL=profile-filter-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-filter-controller.js","sourceRoot":"","sources":["../../src/core/profile-filter-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Resource-profile tool/extension gating + reload-time profile model re-application.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Resolves the active resource\n * profile's tool allow/block filter, filters the runtime extension set (tracking the withheld/inert\n * tallies it owns), reports the /context \"withheld by profile\" observations, and — on reload —\n * re-applies a profile-bound model/thinking level unless an explicit launch flag set them. Owns the\n * inert-extension warnings and profile-denied-extension count; everything else is read through deps.\n */\n\nimport type { Agent, ThinkingLevel } from \"@caupulican/pi-agent-core\";\nimport type { SessionManager } from \"@caupulican/pi-agent-core/node\";\nimport type { Extension } from \"./extensions/index.ts\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\nimport { resolveProfileModelSettings } from \"./model-resolver.ts\";\nimport type { ResourceLoader } from \"./resource-loader.ts\";\nimport {\n\tmatchesResourceProfilePattern,\n\ttype ResourceProfileFilterSettings,\n\ttype SettingsManager,\n} from \"./settings-manager.ts\";\n\nexport interface ProfileFilterControllerDeps {\n\tgetSettingsManager(): SettingsManager;\n\tgetResourceLoader(): ResourceLoader;\n\tgetModelRegistry(): ModelRegistry;\n\tgetCwd(): string;\n\tgetAgent(): Agent;\n\tgetSessionManager(): SessionManager;\n\t/** Construction-time tool allow-set (undefined = no allow-list gate). */\n\tgetAllowedToolNames(): Set<string> | undefined;\n\t/** Construction-time tool exclude-set. */\n\tgetExcludedToolNames(): Set<string> | undefined;\n\t/** Live resource-profile tool filter (re-derived on reload; may be undefined). */\n\tgetToolProfileFilter(): Required<ResourceProfileFilterSettings> | undefined;\n\t/** Whether the model was set by an explicit launch flag (profile must not clobber it). */\n\tisExplicitModel(): boolean;\n\t/** Whether the thinking level was set by an explicit launch flag. */\n\tisExplicitThinking(): boolean;\n\t/** Apply a thinking level (delegates to model-selection). */\n\tsetThinkingLevel(level: ThinkingLevel): void;\n}\n\nexport class ProfileFilterController {\n\t/** G12: extensions loaded but rendered fully inert by the profile's tools filter. */\n\tprivate _inertExtensionWarnings: string[] = [];\n\t/** Count of extensions withheld by the active resource profile (for the /context observation). */\n\tprivate _profileDeniedExtensionCount = 0;\n\n\tprivate readonly deps: ProfileFilterControllerDeps;\n\n\tconstructor(deps: ProfileFilterControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\t/** Inert-extension warnings tracked by the last {@link filterExtensionsForRuntime} pass. */\n\tgetInertExtensionWarnings(): string[] {\n\t\treturn this._inertExtensionWarnings;\n\t}\n\n\t/**\n\t * Resolve the active resource-profile tool allow/block filter from current settings.\n\t * Mirrors the construction-time derivation (settingsManager.getResourceProfileFilter(\"tools\"))\n\t * so reload() can re-apply it after a live settings/profile edit.\n\t */\n\tderiveToolProfileFilter(): Required<ResourceProfileFilterSettings> {\n\t\tconst filter = this.deps.getSettingsManager().getResourceProfileFilter(\"tools\");\n\t\treturn { allow: filter.allow ?? [], block: filter.block ?? [] };\n\t}\n\n\tisToolOrCommandAllowedByProfile(name: string): boolean {\n\t\tconst allowedToolNames = this.deps.getAllowedToolNames();\n\t\tconst excludedToolNames = this.deps.getExcludedToolNames();\n\t\tif (allowedToolNames && !allowedToolNames.has(name)) return false;\n\t\tif (excludedToolNames?.has(name)) return false;\n\t\tconst filter = this.deps.getToolProfileFilter();\n\t\tif (!filter) return true;\n\t\tif (filter.allow.length > 0 && !matchesResourceProfilePattern(name, filter.allow)) return false;\n\t\tif (matchesResourceProfilePattern(name, filter.block)) return false;\n\t\treturn true;\n\t}\n\n\tprivate _hasToolOrCommandProfileGate(): boolean {\n\t\tconst toolProfileFilter = this.deps.getToolProfileFilter();\n\t\treturn Boolean(\n\t\t\tthis.deps.getAllowedToolNames() ||\n\t\t\t\tthis.deps.getExcludedToolNames() ||\n\t\t\t\t(toolProfileFilter && (toolProfileFilter.allow.length > 0 || toolProfileFilter.block.length > 0)),\n\t\t);\n\t}\n\n\tfilterExtensionsForRuntime(extensions: Extension[]): Extension[] {\n\t\tconst settingsManager = this.deps.getSettingsManager();\n\t\tthis._inertExtensionWarnings = [];\n\t\tthis._profileDeniedExtensionCount = 0;\n\t\tif (settingsManager.getActiveResourceProfileNames().length === 0) {\n\t\t\tif (settingsManager.hasExplicitActiveResourceProfileSelection()) {\n\t\t\t\t// An explicit profile selection that resolves to no active profile is a deliberate\n\t\t\t\t// deny-all — every extension is withheld by that choice.\n\t\t\t\tthis._profileDeniedExtensionCount = extensions.length;\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\t// No profile in play: only inline/SDK extensions load by default. That is the baseline, not\n\t\t\t// a profile denial, so it is not counted as withheld.\n\t\t\treturn extensions.filter((extension) => extension.sourceInfo.source === \"inline\");\n\t\t}\n\t\tconst hasToolOrCommandGate = this._hasToolOrCommandProfileGate();\n\t\tconst allowedExtensions = extensions.filter((extension) =>\n\t\t\tsettingsManager.isResourceAllowedByProfile(\"extensions\", extension.path, extension.sourceInfo.baseDir),\n\t\t);\n\t\tthis._profileDeniedExtensionCount = extensions.length - allowedExtensions.length;\n\t\treturn allowedExtensions.map((extension) => {\n\t\t\tif (!hasToolOrCommandGate) return extension;\n\t\t\tconst tools = new Map(\n\t\t\t\tArray.from(extension.tools.entries()).filter(([name]) => this.isToolOrCommandAllowedByProfile(name)),\n\t\t\t);\n\t\t\tconst commands = new Map(\n\t\t\t\tArray.from(extension.commands.entries()).filter(([name]) => this.isToolOrCommandAllowedByProfile(name)),\n\t\t\t);\n\t\t\t// G12: an extension the profile ALLOWS whose every tool and command the tools filter\n\t\t\t// then denies loads and runs lifecycle hooks but is completely uninvocable — surface\n\t\t\t// it instead of presenting a \"loaded\" extension that silently does nothing.\n\t\t\tif (extension.tools.size + extension.commands.size > 0 && tools.size + commands.size === 0) {\n\t\t\t\tconst name = extension.path.split(/[\\\\/]/).pop() ?? extension.path;\n\t\t\t\tthis._inertExtensionWarnings.push(\n\t\t\t\t\t`extension \"${name}\" is loaded but fully inert: the active profile's tools filter denies all ${extension.tools.size} tool(s) and ${extension.commands.size} command(s) it contributes`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn { ...extension, tools, commands };\n\t\t});\n\t}\n\n\t/**\n\t * /context observations for skills/prompts/extensions the active resource profile removed from\n\t * listings — the analog of the withheld-AGENTS.md warning. Strict UAC makes these silently absent,\n\t * so a lean profile's effect on the resource surface stays visible. Counts are profile-scoped\n\t * (skills/prompts via the profile-independent discovery universe filtered by the live profile\n\t * filter; extensions via the runtime filter's denied tally). Empty when nothing is withheld.\n\t *\n\t * Uses `isResourceDeniedByActiveProfile` (profile-only), not `isResourceAllowedByProfile` (which\n\t * also folds in the user's own legacy `disabledResources` list): a plain user-disabled resource\n\t * must never be misattributed to \"the active resource profile\" — that case is already surfaced by\n\t * the G14 disable-wins warning. With no active profile at all, the helper always reports nothing\n\t * denied, so this naturally stays silent (extensions keep their own runtime-filter-derived count,\n\t * which is already correctly zero absent a profile).\n\t */\n\tprofileDeniedResourceObservations(): string[] {\n\t\tconst settingsManager = this.deps.getSettingsManager();\n\t\tconst resourceLoader = this.deps.getResourceLoader();\n\t\tconst cwd = this.deps.getCwd();\n\t\tconst observations: string[] = [];\n\t\tconst withheld = (kind: \"skills\" | \"prompts\", paths: string[]): number =>\n\t\t\tpaths.filter((path) => settingsManager.isResourceDeniedByActiveProfile(kind, path, cwd)).length;\n\n\t\tconst skillsWithheld = withheld(\"skills\", resourceLoader.getDiscoverableSkillPaths());\n\t\tif (skillsWithheld > 0) {\n\t\t\tobservations.push(\n\t\t\t\t`${skillsWithheld} skill(s) withheld by the active resource profile — grant the \"skills\" kind to restore them`,\n\t\t\t);\n\t\t}\n\t\tconst promptsWithheld = withheld(\"prompts\", resourceLoader.getDiscoverablePromptPaths());\n\t\tif (promptsWithheld > 0) {\n\t\t\tobservations.push(\n\t\t\t\t`${promptsWithheld} prompt(s) withheld by the active resource profile — grant the \"prompts\" kind to restore them`,\n\t\t\t);\n\t\t}\n\t\tif (this._profileDeniedExtensionCount > 0) {\n\t\t\tobservations.push(\n\t\t\t\t`${this._profileDeniedExtensionCount} extension(s) withheld by the active resource profile — grant the \"extensions\" kind to restore them`,\n\t\t\t);\n\t\t}\n\t\treturn observations;\n\t}\n\n\t/**\n\t * Re-resolve the active resource profile's model/thinking from current settings and apply it.\n\t * Only acts when the profile actually binds model/thinking AND that field was not set by an\n\t * explicit launch flag — so live profile edits apply on reload without clobbering an explicit\n\t * --model/--thinking. A no-op for profiles that don't bind a model.\n\t */\n\tasync reapplyActiveProfileModelSettings(): Promise<void> {\n\t\tif (this.deps.isExplicitModel() && this.deps.isExplicitThinking()) return;\n\t\tconst settingsManager = this.deps.getSettingsManager();\n\t\tconst activeProfileNames = settingsManager.getActiveResourceProfileNames();\n\t\tif (activeProfileNames.length === 0) return;\n\t\tconst profileSettings = resolveProfileModelSettings({\n\t\t\tactiveProfileNames,\n\t\t\tregistry: settingsManager.getProfileRegistry(),\n\t\t\tmodelRegistry: this.deps.getModelRegistry(),\n\t\t\tcwd: this.deps.getCwd(),\n\t\t});\n\t\tif (!this.deps.isExplicitModel() && profileSettings.model) {\n\t\t\tconst current = this.deps.getAgent().state.model;\n\t\t\tconst next = profileSettings.model;\n\t\t\tif (!current || current.provider !== next.provider || current.id !== next.id) {\n\t\t\t\t// Mirror the startup/cycle path: set the model directly (no auth gate, no settings\n\t\t\t\t// persist) so re-applying the profile model behaves like initial resolution rather\n\t\t\t\t// than a runtime model switch. No model_select emit here — reload rebuilds the\n\t\t\t\t// extension runtime and emits session_start(\"reload\") right after, and the UI\n\t\t\t\t// re-renders from session.model.\n\t\t\t\tthis.deps.getAgent().state.model = next;\n\t\t\t\tthis.deps.getSessionManager().appendModelChange(next.provider, next.id);\n\t\t\t}\n\t\t}\n\t\tif (!this.deps.isExplicitThinking() && profileSettings.thinkingLevel) {\n\t\t\tthis.deps.setThinkingLevel(profileSettings.thinkingLevel);\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Native reflection + learning-write controller.
3
+ *
4
+ * Extracted verbatim from agent-session.ts (god-file decomposition). Owns the end-of-loop reflection
5
+ * pass (R2), the isolated-completion primitive it runs on, and the learning-apply/rollback path that
6
+ * turns reflection writes into gated, audited durable memory/skill changes. It mutates NO session
7
+ * fields — every durable effect goes through the bundled memory tool, the session log (via deps), or
8
+ * the skills dir; the whole pass is best-effort and never throws into the turn loop. Reads live
9
+ * session state (model/agent/registry/memory/settings) through narrow deps accessors rather than the
10
+ * whole AgentSession.
11
+ */
12
+ import type { Agent, ThinkingLevel } from "@caupulican/pi-agent-core";
13
+ import type { SessionManager } from "@caupulican/pi-agent-core/node";
14
+ import type { Model, Usage } from "@caupulican/pi-ai";
15
+ import type { IsolatedCompletionOptions, IsolatedCompletionResult } from "./agent-session.ts";
16
+ import type { LearningDecision } from "./autonomy/contracts.ts";
17
+ import { type AutonomyTelemetryEvent } from "./autonomy/telemetry-events.ts";
18
+ import { type LearningAuditRecord } from "./learning/learning-audit.ts";
19
+ import { type DemandSignals, type ReflectionResult } from "./learning/reflection-engine.ts";
20
+ import type { MemoryManager } from "./memory/memory-manager.ts";
21
+ import type { ModelRegistry } from "./model-registry.ts";
22
+ import type { SettingsManager } from "./settings-manager.ts";
23
+ export interface ReflectionControllerDeps {
24
+ /** Current session model (fallback for an isolated call that omits its own model). */
25
+ getModel(): Model<any> | undefined;
26
+ /** The underlying agent — its `streamFn` runs the isolated completion. */
27
+ getAgent(): Agent;
28
+ /** True when the session's stream fn is the raw `streamSimple` provider (auth must be injected). */
29
+ isRawStreamSimple(): boolean;
30
+ /** Model registry for API-key/header resolution on the raw-provider path. */
31
+ getModelRegistry(): ModelRegistry;
32
+ /** Memory subsystem — the bundled `memory` tool applies durable writes; fresh block feeds reflection. */
33
+ getMemoryManager(): MemoryManager;
34
+ /** Settings — the learning-apply policy (gate thresholds, auto-apply layers) is read here. */
35
+ getSettingsManager(): SettingsManager;
36
+ /** Session log — audit snapshots and learning-audit reads go through this. */
37
+ getSessionManager(): SessionManager;
38
+ /** Agent dir — reflection-promoted skills are written under `<agentDir>/skills/`. */
39
+ getAgentDir(): string;
40
+ /** Child sessions must not learn — the pass returns null for them. */
41
+ isChildSession(): boolean;
42
+ /** Disposal short-circuits: no completion, no writes against a dead session. */
43
+ isDisposed(): boolean;
44
+ /** Session-lifetime abort signal — aborts an in-flight reflection completion on dispose. */
45
+ getReflectionSignal(): AbortSignal;
46
+ /** Archive a promoted skill (rollback of a `promote_skill` write). */
47
+ archivePromotedSkill(name: string): boolean;
48
+ /** G3/G8 autonomy telemetry sink for learning-gate outcomes and approval requests. */
49
+ emitAutonomyTelemetry(event: AutonomyTelemetryEvent): void;
50
+ /** Account the reflection pass's token spend into the cost roll-up (idempotent on reportId). */
51
+ addSpawnedUsage(usage: Usage, opts?: {
52
+ label?: string;
53
+ sourceSessionId?: string;
54
+ reportId?: string;
55
+ }): string | undefined;
56
+ /** Persist a learning-gate decision snapshot to the session log. */
57
+ saveLearningDecisionSnapshot(decision: LearningDecision): string;
58
+ }
59
+ export declare class ReflectionController {
60
+ private readonly deps;
61
+ constructor(deps: ReflectionControllerDeps);
62
+ /**
63
+ * Run a one-shot LLM completion fully ISOLATED from the main session — the load-bearing
64
+ * primitive for the native reflection engine (adaptive-agent design §6c/§7).
65
+ *
66
+ * Isolation invariants (audited by codex): builds a fresh {@link Context} (no main history), runs
67
+ * with `tools: []`, sets `cacheRetention: "none"`, and passes **no `sessionId`** — so it cannot
68
+ * mutate `agent.state.messages`, cannot append session entries, cannot touch the tool registry,
69
+ * and cannot churn the main session's prompt cache. Mirrors `generateSummary()`'s mechanics.
70
+ *
71
+ * Returns the result even on an error/aborted stop reason (callers — e.g. a background reflection
72
+ * microtask — decide whether to act); it does not throw on a model-level error.
73
+ */
74
+ runIsolatedCompletion(opts: IsolatedCompletionOptions): Promise<IsolatedCompletionResult>;
75
+ /**
76
+ * Native end-of-loop reflection pass (R2). Demand-gates (zero-I/O), and when warranted runs the
77
+ * {@link ReflectionEngine} via an isolated completion ({@link runIsolatedCompletion}), applies the
78
+ * resulting memory writes through the bundled `memory` tool, and accounts the reflection's token
79
+ * cost via the cost-aggregation surface so it stays visible and net-negative-auditable.
80
+ *
81
+ * Returns `null` when the gate skips (or in a child session, which must not learn). The whole pass
82
+ * is best-effort: a model/parse error yields no writes, never throws into the caller.
83
+ */
84
+ runReflectionPass(input: {
85
+ signals: DemandSignals;
86
+ recentTurnText: string;
87
+ model?: Model<any>;
88
+ thinkingLevel?: ThinkingLevel;
89
+ signal?: AbortSignal;
90
+ /** Stable id so a duplicate scheduling/retry of the same pass can't double-count its cost. */
91
+ reportId?: string;
92
+ }): Promise<ReflectionResult | null>;
93
+ getLearningAuditRecords(): LearningAuditRecord[];
94
+ /**
95
+ * Roll back one applied durable learning change by executing the inverse operation recorded in
96
+ * its audit record (memory ops run through the same bundled memory-tool path as the original
97
+ * apply; promoted skills are archived). Appends a linked "rollback" audit record on success so
98
+ * the change history stays complete and a change cannot be rolled back twice.
99
+ */
100
+ rollbackLearningWrite(auditId: string): Promise<{
101
+ ok: boolean;
102
+ reason: string;
103
+ }>;
104
+ private _applyReflectionWrite;
105
+ /**
106
+ * R7: write a reflection-promoted skill as `<agentDir>/skills/<name>/SKILL.md` so it loads like any
107
+ * user skill. Best-effort; never clobbers an existing (hand-authored) skill of the same name.
108
+ */
109
+ private _promoteReflectionSkill;
110
+ }
111
+ //# sourceMappingURL=reflection-controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reflection-controller.d.ts","sourceRoot":"","sources":["../../src/core/reflection-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Native reflection + learning-write controller.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Owns the end-of-loop reflection\n * pass (R2), the isolated-completion primitive it runs on, and the learning-apply/rollback path that\n * turns reflection writes into gated, audited durable memory/skill changes. It mutates NO session\n * fields — every durable effect goes through the bundled memory tool, the session log (via deps), or\n * the skills dir; the whole pass is best-effort and never throws into the turn loop. Reads live\n * session state (model/agent/registry/memory/settings) through narrow deps accessors rather than the\n * whole AgentSession.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Agent, ThinkingLevel } from \"@caupulican/pi-agent-core\";\nimport type { SessionManager } from \"@caupulican/pi-agent-core/node\";\nimport type { Context, Model, SimpleStreamOptions, TextContent, Usage } from \"@caupulican/pi-ai\";\nimport type { IsolatedCompletionOptions, IsolatedCompletionResult } from \"./agent-session.ts\";\nimport type { LearningDecision } from \"./autonomy/contracts.ts\";\nimport { AUTONOMY_TELEMETRY_EVENT_TYPES, type AutonomyTelemetryEvent } from \"./autonomy/telemetry-events.ts\";\nimport {\n\tAPPLY_WRITE_REFUSED_REASON_CODE,\n\tappendLearningAuditSnapshot,\n\tcontradictionsForReflectionWrite,\n\tgetLearningAuditSnapshots,\n\ttype LearningAuditRecord,\n\tproposalFromReflectionWrite,\n\trollbackPlanForReflectionWrite,\n} from \"./learning/learning-audit.ts\";\nimport { evaluateLearningDecision } from \"./learning/learning-gate.ts\";\nimport { ObservationStore, observationKey } from \"./learning/observation-store.ts\";\nimport {\n\ttype DemandSignals,\n\tdecideDemand,\n\tReflectionEngine,\n\ttype ReflectionResult,\n\ttype ReflectionWrite,\n} from \"./learning/reflection-engine.ts\";\nimport type { MemoryManager } from \"./memory/memory-manager.ts\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\n\nexport interface ReflectionControllerDeps {\n\t/** Current session model (fallback for an isolated call that omits its own model). */\n\tgetModel(): Model<any> | undefined;\n\t/** The underlying agent — its `streamFn` runs the isolated completion. */\n\tgetAgent(): Agent;\n\t/** True when the session's stream fn is the raw `streamSimple` provider (auth must be injected). */\n\tisRawStreamSimple(): boolean;\n\t/** Model registry for API-key/header resolution on the raw-provider path. */\n\tgetModelRegistry(): ModelRegistry;\n\t/** Memory subsystem — the bundled `memory` tool applies durable writes; fresh block feeds reflection. */\n\tgetMemoryManager(): MemoryManager;\n\t/** Settings — the learning-apply policy (gate thresholds, auto-apply layers) is read here. */\n\tgetSettingsManager(): SettingsManager;\n\t/** Session log — audit snapshots and learning-audit reads go through this. */\n\tgetSessionManager(): SessionManager;\n\t/** Agent dir — reflection-promoted skills are written under `<agentDir>/skills/`. */\n\tgetAgentDir(): string;\n\t/** Child sessions must not learn — the pass returns null for them. */\n\tisChildSession(): boolean;\n\t/** Disposal short-circuits: no completion, no writes against a dead session. */\n\tisDisposed(): boolean;\n\t/** Session-lifetime abort signal — aborts an in-flight reflection completion on dispose. */\n\tgetReflectionSignal(): AbortSignal;\n\t/** Archive a promoted skill (rollback of a `promote_skill` write). */\n\tarchivePromotedSkill(name: string): boolean;\n\t/** G3/G8 autonomy telemetry sink for learning-gate outcomes and approval requests. */\n\temitAutonomyTelemetry(event: AutonomyTelemetryEvent): void;\n\t/** Account the reflection pass's token spend into the cost roll-up (idempotent on reportId). */\n\taddSpawnedUsage(\n\t\tusage: Usage,\n\t\topts?: { label?: string; sourceSessionId?: string; reportId?: string },\n\t): string | undefined;\n\t/** Persist a learning-gate decision snapshot to the session log. */\n\tsaveLearningDecisionSnapshot(decision: LearningDecision): string;\n}\n\nexport class ReflectionController {\n\tprivate readonly deps: ReflectionControllerDeps;\n\n\tconstructor(deps: ReflectionControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\t/**\n\t * Run a one-shot LLM completion fully ISOLATED from the main session — the load-bearing\n\t * primitive for the native reflection engine (adaptive-agent design §6c/§7).\n\t *\n\t * Isolation invariants (audited by codex): builds a fresh {@link Context} (no main history), runs\n\t * with `tools: []`, sets `cacheRetention: \"none\"`, and passes **no `sessionId`** — so it cannot\n\t * mutate `agent.state.messages`, cannot append session entries, cannot touch the tool registry,\n\t * and cannot churn the main session's prompt cache. Mirrors `generateSummary()`'s mechanics.\n\t *\n\t * Returns the result even on an error/aborted stop reason (callers — e.g. a background reflection\n\t * microtask — decide whether to act); it does not throw on a model-level error.\n\t */\n\tasync runIsolatedCompletion(opts: IsolatedCompletionOptions): Promise<IsolatedCompletionResult> {\n\t\tconst model = opts.model ?? this.deps.getModel();\n\t\tif (!model) {\n\t\t\tthrow new Error(\"runIsolatedCompletion: no model available\");\n\t\t}\n\t\tconst thinkingLevel = opts.thinkingLevel ?? \"off\";\n\n\t\t// Fresh, isolated context: explicit messages, no tools, nothing from the main session.\n\t\tconst context: Context = {\n\t\t\tsystemPrompt: opts.systemPrompt,\n\t\t\tmessages: opts.messages,\n\t\t\ttools: [],\n\t\t};\n\n\t\t// Isolate the prompt cache and DELIBERATELY omit sessionId so no session-aware caching/routing\n\t\t// can entangle this call with the main session.\n\t\tconst options: SimpleStreamOptions = {\n\t\t\tmaxTokens: opts.maxTokens,\n\t\t\tsignal: opts.signal,\n\t\t\tcacheRetention: opts.cacheRetention ?? \"none\",\n\t\t};\n\t\t// pi-ai's `reasoning` option does not include \"off\" (that's the provider default already).\n\t\tif (thinkingLevel !== \"off\") {\n\t\t\toptions.reasoning = thinkingLevel;\n\t\t}\n\n\t\t// When streamFn is the raw streamSimple (e.g. in tests), auth must be injected explicitly.\n\t\t// Throw only when auth genuinely fails — providers that authenticate without an API key\n\t\t// (OAuth, local no-key) legitimately return ok with an undefined apiKey.\n\t\tif (this.deps.isRawStreamSimple()) {\n\t\t\tconst auth = await this.deps.getModelRegistry().getApiKeyAndHeaders(model);\n\t\t\tif (!auth.ok) {\n\t\t\t\tthrow new Error(auth.error);\n\t\t\t}\n\t\t\toptions.apiKey = auth.apiKey;\n\t\t\toptions.headers = auth.headers;\n\t\t}\n\n\t\tconst stream = await this.deps.getAgent().streamFn(model, context, options);\n\t\tconst result = await stream.result();\n\t\tconst text = result.content\n\t\t\t.filter((c): c is TextContent => c.type === \"text\")\n\t\t\t.map((c) => c.text)\n\t\t\t.join(\"\");\n\t\tconst usage: Usage = result.usage ?? {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\ttotalTokens: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t};\n\t\treturn { text, usage, stopReason: result.stopReason };\n\t}\n\n\t/**\n\t * Native end-of-loop reflection pass (R2). Demand-gates (zero-I/O), and when warranted runs the\n\t * {@link ReflectionEngine} via an isolated completion ({@link runIsolatedCompletion}), applies the\n\t * resulting memory writes through the bundled `memory` tool, and accounts the reflection's token\n\t * cost via the cost-aggregation surface so it stays visible and net-negative-auditable.\n\t *\n\t * Returns `null` when the gate skips (or in a child session, which must not learn). The whole pass\n\t * is best-effort: a model/parse error yields no writes, never throws into the caller.\n\t */\n\tasync runReflectionPass(input: {\n\t\tsignals: DemandSignals;\n\t\trecentTurnText: string;\n\t\tmodel?: Model<any>;\n\t\tthinkingLevel?: ThinkingLevel;\n\t\tsignal?: AbortSignal;\n\t\t/** Stable id so a duplicate scheduling/retry of the same pass can't double-count its cost. */\n\t\treportId?: string;\n\t}): Promise<ReflectionResult | null> {\n\t\tif (this.deps.isChildSession() || this.deps.isDisposed()) return null;\n\t\tconst plan = decideDemand(input.signals);\n\t\tif (plan.act === \"skip\") return null;\n\n\t\t// Bug #21: tie this background pass to the session lifetime. Disposing the session aborts the\n\t\t// in-flight completion (input.signal can add a more specific abort).\n\t\tconst signal = input.signal\n\t\t\t? AbortSignal.any([input.signal, this.deps.getReflectionSignal()])\n\t\t\t: this.deps.getReflectionSignal();\n\n\t\tconst complete = (systemPrompt: string, userPrompt: string) =>\n\t\t\tthis.runIsolatedCompletion({\n\t\t\t\tsystemPrompt,\n\t\t\t\tmessages: [{ role: \"user\", content: [{ type: \"text\", text: userPrompt }], timestamp: Date.now() }],\n\t\t\t\tmodel: input.model,\n\t\t\t\tthinkingLevel: input.thinkingLevel ?? \"low\",\n\t\t\t\tmaxTokens: plan.tokenBudget,\n\t\t\t\tsignal,\n\t\t\t\t// The reflection system prompt is static (#33) — let the provider cache the prefix so\n\t\t\t\t// repeated passes only pay for the variable tail.\n\t\t\t\tcacheRetention: \"short\",\n\t\t\t});\n\n\t\tconst result = await new ReflectionEngine().reflect({\n\t\t\trecentTurnText: input.recentTurnText,\n\t\t\t// Read memory FRESH (not the prefix-cache-frozen system-prompt block) so confront-before-write\n\t\t\t// sees writes made earlier this session.\n\t\t\texistingMemory: this.deps.getMemoryManager().buildSystemPromptBlockFresh() || \"\",\n\t\t\tplan,\n\t\t\tcomplete,\n\t\t});\n\n\t\t// Bug #21: if the session was disposed while the completion was in flight, do NOT write memory\n\t\t// or skills against the dead session.\n\t\tif (this.deps.isDisposed()) return result;\n\n\t\t// Learning apply policy: every durable write is converted to a proposal, decided by the\n\t\t// learning gate, and audited with a rollback plan. With the policy disabled (default) the\n\t\t// legacy direct-apply behavior is preserved — but now leaves audit records with rollback info.\n\t\tconst policy = this.deps.getSettingsManager().getLearningPolicySettings();\n\t\t// The audit id sequence counts STORED snapshots only: it reseeds from the stored count on\n\t\t// every pass, so advancing it for a no-op (which stores nothing) would make later passes\n\t\t// reuse ids — and rollback keys on the id, so a collision blocks or misdirects rollback.\n\t\tlet auditSequence = getLearningAuditSnapshots(this.deps.getSessionManager().getEntries()).length;\n\t\t// G6 evidence strength: durable proposals accumulate observation counts across passes/sessions\n\t\t// so the gate can distinguish a one-off cue from a repeatedly-confirmed lesson. Built once per\n\t\t// pass; every increment is best-effort (store IO must never break reflection).\n\t\tconst observationStore = ObservationStore.forAgentDir(this.deps.getAgentDir());\n\t\tlet writeIndex = 0;\n\t\tfor (const write of result.writes) {\n\t\t\twriteIndex += 1;\n\t\t\tconst proposalId = `${input.reportId ?? \"reflection\"}-w${writeIndex}`;\n\t\t\tconst proposal = proposalFromReflectionWrite(write, proposalId);\n\t\t\tconst rollback = rollbackPlanForReflectionWrite(write);\n\t\t\tlet observations = 1;\n\t\t\tif (policy.enabled) {\n\t\t\t\ttry {\n\t\t\t\t\tobservations = observationStore.increment(observationKey(proposal.layer, proposal.summary));\n\t\t\t\t} catch {\n\t\t\t\t\t// A store read/write failure falls back to a fresh count of 1, which keeps the gate\n\t\t\t\t\t// proposal-first (never spuriously auto-applies) rather than crashing the pass.\n\t\t\t\t\tobservations = 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst decision: LearningDecision = policy.enabled\n\t\t\t\t? evaluateLearningDecision({\n\t\t\t\t\t\tproposal,\n\t\t\t\t\t\tconfidence: policy.reflectionSourceConfidence,\n\t\t\t\t\t\tobservations,\n\t\t\t\t\t\t// A replace/remove supersedes an existing durable fact — the reflection engine's\n\t\t\t\t\t\t// confront-before-write conflict signal — so it routes through approval instead of\n\t\t\t\t\t\t// silently overwriting prior memory. Additive writes contradict nothing.\n\t\t\t\t\t\tcontradictions: contradictionsForReflectionWrite(write),\n\t\t\t\t\t\tsettings: {\n\t\t\t\t\t\t\tenabled: true,\n\t\t\t\t\t\t\tautoApplyEnabled: policy.autoApplyEnabled,\n\t\t\t\t\t\t\tconfidenceThreshold: policy.confidenceThreshold,\n\t\t\t\t\t\t\tminObservations: policy.minObservations,\n\t\t\t\t\t\t\tallowedAutoApplyLayers: policy.allowedAutoApplyLayers,\n\t\t\t\t\t\t\trequireRollbackPlan: policy.requireRollbackPlan,\n\t\t\t\t\t\t\tautoApplySupersessions: policy.autoApplySupersessions,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t: {\n\t\t\t\t\t\tkind: \"apply\",\n\t\t\t\t\t\treasonCode: \"learning_policy_disabled_legacy_apply\",\n\t\t\t\t\t\tconfidence: 0,\n\t\t\t\t\t\tsummary: proposal.summary,\n\t\t\t\t\t\trequiresApproval: false,\n\t\t\t\t\t};\n\n\t\t\tthis.deps.saveLearningDecisionSnapshot(decision);\n\t\t\t// G3: learning-gate outcome. Codes/numbers only — never the proposal summary/memory text.\n\t\t\tthis.deps.emitAutonomyTelemetry({\n\t\t\t\ttype: AUTONOMY_TELEMETRY_EVENT_TYPES.learningDecision,\n\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\tpayload: {\n\t\t\t\t\tkind: decision.kind,\n\t\t\t\t\treasonCode: decision.reasonCode,\n\t\t\t\t\tlayer: proposal.layer,\n\t\t\t\t\tconfidence: decision.confidence,\n\t\t\t\t\trequiresApproval: decision.requiresApproval,\n\t\t\t\t},\n\t\t\t});\n\t\t\t// G8: a proposal that needs human sign-off is an approval REQUEST. Codes/layer only —\n\t\t\t// never the proposal summary/memory text (those live only in the audit snapshot).\n\t\t\tif (decision.requiresApproval) {\n\t\t\t\tthis.deps.emitAutonomyTelemetry({\n\t\t\t\t\ttype: AUTONOMY_TELEMETRY_EVENT_TYPES.approvalRequest,\n\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\tpayload: {\n\t\t\t\t\t\tkind: decision.kind,\n\t\t\t\t\t\treasonCode: decision.reasonCode,\n\t\t\t\t\t\tlayer: proposal.layer,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t\t// The gate's decision and the write's actual outcome are two different questions: the memory\n\t\t\t// tool can refuse a write (budget exceeded, drift, threat) via details.success:false without\n\t\t\t// throwing. Capture that outcome instead of assuming \"decision.kind === apply\" means it landed\n\t\t\t// — otherwise a refused write leaves a phantom \"apply\" audit whose rollback later fails\n\t\t\t// not-found (or, worse, misfires against whatever now occupies that text).\n\t\t\tconst applied = decision.kind === \"apply\" ? await this._applyReflectionWrite(write, signal) : false;\n\t\t\tconst writeFailed = decision.kind === \"apply\" && !applied;\n\t\t\tif (decision.kind !== \"no-op\") {\n\t\t\t\tauditSequence += 1;\n\t\t\t\tappendLearningAuditSnapshot(this.deps.getSessionManager(), {\n\t\t\t\t\tid: `audit-${auditSequence}`,\n\t\t\t\t\tproposalId,\n\t\t\t\t\tlayer: proposal.layer,\n\t\t\t\t\taction: writeFailed ? \"apply_failed\" : decision.kind === \"apply\" ? \"apply\" : \"propose\",\n\t\t\t\t\tsummary: proposal.summary,\n\t\t\t\t\treasonCode: writeFailed ? APPLY_WRITE_REFUSED_REASON_CODE : decision.reasonCode,\n\t\t\t\t\tdecision,\n\t\t\t\t\t// No rollback plan on a failed apply — nothing durable landed, so there is nothing to undo.\n\t\t\t\t\trollback: writeFailed ? undefined : rollback,\n\t\t\t\t\tcreatedAt: new Date().toISOString(),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Account the reflection's spend so it surfaces in the footer roll-up (net-token visibility).\n\t\t// Idempotent on reportId so a retried/duplicated pass cannot double-count.\n\t\tif (result.usage.cost.total > 0 || result.usage.totalTokens > 0) {\n\t\t\tthis.deps.addSpawnedUsage(result.usage, { label: \"reflection\", reportId: input.reportId });\n\t\t}\n\t\treturn result;\n\t}\n\n\tgetLearningAuditRecords(): LearningAuditRecord[] {\n\t\treturn getLearningAuditSnapshots(this.deps.getSessionManager().getEntries());\n\t}\n\n\t/**\n\t * Roll back one applied durable learning change by executing the inverse operation recorded in\n\t * its audit record (memory ops run through the same bundled memory-tool path as the original\n\t * apply; promoted skills are archived). Appends a linked \"rollback\" audit record on success so\n\t * the change history stays complete and a change cannot be rolled back twice.\n\t */\n\tasync rollbackLearningWrite(auditId: string): Promise<{ ok: boolean; reason: string }> {\n\t\tif (this.deps.isDisposed()) return { ok: false, reason: \"session_disposed\" };\n\n\t\tconst audits = this.getLearningAuditRecords();\n\t\tconst audit = audits.find((record) => record.id === auditId);\n\t\tif (!audit) return { ok: false, reason: \"audit_not_found\" };\n\t\tif (audit.action !== \"apply\") return { ok: false, reason: \"not_an_applied_change\" };\n\t\tif (audits.some((record) => record.action === \"rollback\" && record.rollbackOf === auditId)) {\n\t\t\treturn { ok: false, reason: \"already_rolled_back\" };\n\t\t}\n\t\tconst rollback = audit.rollback;\n\t\tif (!rollback) return { ok: false, reason: \"no_rollback_plan\" };\n\n\t\t// Every inverse must be VERIFIED-applied before the rollback audit is appended: a silently\n\t\t// failed inverse that still recorded \"rollback\" would permanently self-lock the change\n\t\t// behind already_rolled_back while the durable write is in fact still live.\n\t\tswitch (rollback.kind) {\n\t\t\tcase \"memory_remove\": {\n\t\t\t\tif (!rollback.target) return { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\tif (!(await this._applyReflectionWrite({ kind: \"memory_remove\", target: rollback.target }))) {\n\t\t\t\t\treturn { ok: false, reason: \"rollback_apply_failed\" };\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"memory_restore\": {\n\t\t\t\tif (!rollback.target || rollback.previous === undefined) {\n\t\t\t\t\treturn { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\t}\n\t\t\t\tconst applied = await this._applyReflectionWrite({\n\t\t\t\t\tkind: \"memory_replace\",\n\t\t\t\t\ttarget: rollback.target,\n\t\t\t\t\ttext: rollback.previous,\n\t\t\t\t});\n\t\t\t\tif (!applied) return { ok: false, reason: \"rollback_apply_failed\" };\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"memory_add\": {\n\t\t\t\tif (rollback.previous === undefined) return { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\tconst applied = await this._applyReflectionWrite({\n\t\t\t\t\tkind: \"memory_add\",\n\t\t\t\t\tsection: \"MEMORY\",\n\t\t\t\t\ttext: rollback.previous,\n\t\t\t\t});\n\t\t\t\tif (!applied) return { ok: false, reason: \"rollback_apply_failed\" };\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"archive_skill\": {\n\t\t\t\tif (!rollback.target) return { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\tif (!this.deps.archivePromotedSkill(rollback.target)) {\n\t\t\t\t\treturn { ok: false, reason: \"skill_archive_failed\" };\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tappendLearningAuditSnapshot(this.deps.getSessionManager(), {\n\t\t\tid: `${audit.id}-rollback`,\n\t\t\tproposalId: audit.proposalId,\n\t\t\tlayer: audit.layer,\n\t\t\taction: \"rollback\",\n\t\t\tsummary: `Rolled back: ${audit.summary}`,\n\t\t\treasonCode: \"user_requested_rollback\",\n\t\t\tdecision: audit.decision,\n\t\t\trollbackOf: audit.id,\n\t\t\tcreatedAt: new Date().toISOString(),\n\t\t});\n\t\treturn { ok: true, reason: \"rollback_applied\" };\n\t}\n\n\t/**\n\t * Apply one reflection write through the bundled `memory` tool. `memory_replace`/`memory_remove`\n\t * don't carry a target file, so we try MEMORY.md first and fall back to USER.md when the substring\n\t * isn't found there. Never throws (reflection must never break a turn); returns whether the write\n\t * actually applied so callers that MUST know — rollback's once-only accounting — can react instead\n\t * of recording a success that never happened.\n\t */\n\tprivate async _applyReflectionWrite(write: ReflectionWrite, signal?: AbortSignal): Promise<boolean> {\n\t\t// R7 memory-to-behavior: a recurring procedure is compiled into an executable skill file rather\n\t\t// than stored as a flat fact. Written under the agent skills dir so it loads like any user skill.\n\t\tif (write.kind === \"promote_skill\") {\n\t\t\treturn this._promoteReflectionSkill(write.name, write.description, write.body);\n\t\t}\n\n\t\ttype MemResult = { details?: { success?: boolean; error?: string } };\n\t\ttype MemExec = (\n\t\t\ttoolCallId: string,\n\t\t\tparams: { action: string; target: string; content?: string; oldContent?: string },\n\t\t\tsignal: AbortSignal | undefined,\n\t\t\tonUpdate: undefined,\n\t\t\tctx: undefined,\n\t\t) => Promise<MemResult>;\n\t\tconst memTool = this.deps\n\t\t\t.getMemoryManager()\n\t\t\t.getToolDefinitions()\n\t\t\t.find((t) => t.name === \"memory\");\n\t\tconst exec = memTool?.execute as unknown as MemExec | undefined;\n\t\tif (!exec) return false;\n\n\t\tconst run = (params: Parameters<MemExec>[1]) => exec(\"reflection\", params, signal, undefined, undefined);\n\n\t\tif (write.kind === \"memory_add\") {\n\t\t\ttry {\n\t\t\t\tconst res = await run({\n\t\t\t\t\taction: \"add\",\n\t\t\t\t\ttarget: write.section === \"USER\" ? \"user\" : \"memory\",\n\t\t\t\t\tcontent: write.text,\n\t\t\t\t});\n\t\t\t\treturn res?.details?.success === true;\n\t\t\t} catch {\n\t\t\t\t// best-effort; reflection writes must never throw into the turn loop\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t// replace / remove carry no target file — try MEMORY.md, then USER.md. The memory tool reports\n\t\t// outcomes via `details.success` (it catches its own errors rather than throwing). Only a\n\t\t// genuine \"not found in the file\" justifies trying the other file; a real failure for a file\n\t\t// (budget exceeded / drift) must NOT fall through and mutate the wrong target.\n\t\tfor (const target of [\"memory\", \"user\"] as const) {\n\t\t\ttry {\n\t\t\t\tconst params =\n\t\t\t\t\twrite.kind === \"memory_replace\"\n\t\t\t\t\t\t? { action: \"replace\", target, oldContent: write.target, content: write.text }\n\t\t\t\t\t\t: { action: \"remove\", target, oldContent: write.target };\n\t\t\t\tconst res = await run(params);\n\t\t\t\tif (res?.details?.success === true) return true; // applied\n\t\t\t\tif (!/not found/i.test(String(res?.details?.error ?? \"\"))) return false; // real failure — don't misapply\n\t\t\t\t// substring simply absent from this file — try the next target\n\t\t\t} catch {\n\t\t\t\t// defensive: if the tool ever does throw, try the next target\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * R7: write a reflection-promoted skill as `<agentDir>/skills/<name>/SKILL.md` so it loads like any\n\t * user skill. Best-effort; never clobbers an existing (hand-authored) skill of the same name.\n\t */\n\tprivate _promoteReflectionSkill(rawName: string, description: string, body: string): boolean {\n\t\tconst name = rawName\n\t\t\t.trim()\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9-]+/g, \"-\")\n\t\t\t.replace(/^-+|-+$/g, \"\")\n\t\t\t.slice(0, 64);\n\t\tif (!name || !body.trim()) return false;\n\t\ttry {\n\t\t\tconst dir = join(this.deps.getAgentDir(), \"skills\", name);\n\t\t\tconst file = join(dir, \"SKILL.md\");\n\t\t\tif (existsSync(file)) return false; // do not overwrite an existing skill\n\t\t\tmkdirSync(dir, { recursive: true });\n\t\t\tconst safeDescription = description.replace(/[\\r\\n]+/g, \" \").trim();\n\t\t\t// `promoted: true` marks this as reflection-generated so the curator (#32) can lifecycle-manage\n\t\t\t// it (archive/consolidate) WITHOUT ever touching hand-authored user skills.\n\t\t\tconst content = `---\\nname: ${name}\\ndescription: ${safeDescription}\\npromoted: true\\n---\\n\\n<!-- Auto-generated by the reflection engine (R7 memory-to-behavior). Review and refine. -->\\n\\n${body.trim()}\\n`;\n\t\t\twriteFileSync(file, content, \"utf-8\");\n\t\t\treturn true;\n\t\t} catch {\n\t\t\t// promotion must never break a turn\n\t\t\treturn false;\n\t\t}\n\t}\n}\n"]}