@caupulican/pi-adaptative 0.80.86 → 0.80.88

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 (337) hide show
  1. package/CHANGELOG.md +149 -0
  2. package/dist/core/agent-session.d.ts +377 -1
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +1791 -41
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/autonomy/approval-gate.d.ts +4 -0
  7. package/dist/core/autonomy/approval-gate.d.ts.map +1 -0
  8. package/dist/core/autonomy/approval-gate.js +27 -0
  9. package/dist/core/autonomy/approval-gate.js.map +1 -0
  10. package/dist/core/autonomy/bounded-completion.d.ts +27 -0
  11. package/dist/core/autonomy/bounded-completion.d.ts.map +1 -0
  12. package/dist/core/autonomy/bounded-completion.js +44 -0
  13. package/dist/core/autonomy/bounded-completion.js.map +1 -0
  14. package/dist/core/autonomy/contracts.d.ts +129 -0
  15. package/dist/core/autonomy/contracts.d.ts.map +1 -0
  16. package/dist/core/autonomy/contracts.js +2 -0
  17. package/dist/core/autonomy/contracts.js.map +1 -0
  18. package/dist/core/autonomy/gates.d.ts +15 -0
  19. package/dist/core/autonomy/gates.d.ts.map +1 -0
  20. package/dist/core/autonomy/gates.js +205 -0
  21. package/dist/core/autonomy/gates.js.map +1 -0
  22. package/dist/core/autonomy/lane-tracker.d.ts +48 -0
  23. package/dist/core/autonomy/lane-tracker.d.ts.map +1 -0
  24. package/dist/core/autonomy/lane-tracker.js +125 -0
  25. package/dist/core/autonomy/lane-tracker.js.map +1 -0
  26. package/dist/core/autonomy/path-scope.d.ts +9 -0
  27. package/dist/core/autonomy/path-scope.d.ts.map +1 -0
  28. package/dist/core/autonomy/path-scope.js +122 -0
  29. package/dist/core/autonomy/path-scope.js.map +1 -0
  30. package/dist/core/autonomy/risk-assessment.d.ts +3 -0
  31. package/dist/core/autonomy/risk-assessment.d.ts.map +1 -0
  32. package/dist/core/autonomy/risk-assessment.js +122 -0
  33. package/dist/core/autonomy/risk-assessment.js.map +1 -0
  34. package/dist/core/autonomy/session-lane-record.d.ts +10 -0
  35. package/dist/core/autonomy/session-lane-record.d.ts.map +1 -0
  36. package/dist/core/autonomy/session-lane-record.js +36 -0
  37. package/dist/core/autonomy/session-lane-record.js.map +1 -0
  38. package/dist/core/autonomy/status.d.ts +40 -0
  39. package/dist/core/autonomy/status.d.ts.map +1 -0
  40. package/dist/core/autonomy/status.js +107 -0
  41. package/dist/core/autonomy/status.js.map +1 -0
  42. package/dist/core/autonomy/subagent-prompt.d.ts +21 -0
  43. package/dist/core/autonomy/subagent-prompt.d.ts.map +1 -0
  44. package/dist/core/autonomy/subagent-prompt.js +28 -0
  45. package/dist/core/autonomy/subagent-prompt.js.map +1 -0
  46. package/dist/core/autonomy/telemetry-events.d.ts +18 -0
  47. package/dist/core/autonomy/telemetry-events.d.ts.map +1 -0
  48. package/dist/core/autonomy/telemetry-events.js +60 -0
  49. package/dist/core/autonomy/telemetry-events.js.map +1 -0
  50. package/dist/core/context/artifact-retrieval.d.ts +49 -0
  51. package/dist/core/context/artifact-retrieval.d.ts.map +1 -0
  52. package/dist/core/context/artifact-retrieval.js +49 -0
  53. package/dist/core/context/artifact-retrieval.js.map +1 -0
  54. package/dist/core/context/context-artifacts.d.ts +94 -0
  55. package/dist/core/context/context-artifacts.d.ts.map +1 -0
  56. package/dist/core/context/context-artifacts.js +307 -0
  57. package/dist/core/context/context-artifacts.js.map +1 -0
  58. package/dist/core/context/context-audit.d.ts +66 -0
  59. package/dist/core/context/context-audit.d.ts.map +1 -0
  60. package/dist/core/context/context-audit.js +173 -0
  61. package/dist/core/context/context-audit.js.map +1 -0
  62. package/dist/core/context/context-item.d.ts +117 -0
  63. package/dist/core/context/context-item.d.ts.map +1 -0
  64. package/dist/core/context/context-item.js +36 -0
  65. package/dist/core/context/context-item.js.map +1 -0
  66. package/dist/core/context/context-prompt-enforcement.d.ts +73 -0
  67. package/dist/core/context/context-prompt-enforcement.d.ts.map +1 -0
  68. package/dist/core/context/context-prompt-enforcement.js +153 -0
  69. package/dist/core/context/context-prompt-enforcement.js.map +1 -0
  70. package/dist/core/context/context-prompt-policy.d.ts +90 -0
  71. package/dist/core/context/context-prompt-policy.d.ts.map +1 -0
  72. package/dist/core/context/context-prompt-policy.js +73 -0
  73. package/dist/core/context/context-prompt-policy.js.map +1 -0
  74. package/dist/core/context/context-retention.d.ts +36 -0
  75. package/dist/core/context/context-retention.d.ts.map +1 -0
  76. package/dist/core/context/context-retention.js +108 -0
  77. package/dist/core/context/context-retention.js.map +1 -0
  78. package/dist/core/context/context-store.d.ts +37 -0
  79. package/dist/core/context/context-store.d.ts.map +1 -0
  80. package/dist/core/context/context-store.js +45 -0
  81. package/dist/core/context/context-store.js.map +1 -0
  82. package/dist/core/context/memory-diagnostics.d.ts +50 -0
  83. package/dist/core/context/memory-diagnostics.d.ts.map +1 -0
  84. package/dist/core/context/memory-diagnostics.js +43 -0
  85. package/dist/core/context/memory-diagnostics.js.map +1 -0
  86. package/dist/core/context/memory-index-store.d.ts +28 -0
  87. package/dist/core/context/memory-index-store.d.ts.map +1 -0
  88. package/dist/core/context/memory-index-store.js +38 -0
  89. package/dist/core/context/memory-index-store.js.map +1 -0
  90. package/dist/core/context/memory-prompt-block.d.ts +34 -0
  91. package/dist/core/context/memory-prompt-block.d.ts.map +1 -0
  92. package/dist/core/context/memory-prompt-block.js +58 -0
  93. package/dist/core/context/memory-prompt-block.js.map +1 -0
  94. package/dist/core/context/memory-provider-contract.d.ts +114 -0
  95. package/dist/core/context/memory-provider-contract.d.ts.map +1 -0
  96. package/dist/core/context/memory-provider-contract.js +121 -0
  97. package/dist/core/context/memory-provider-contract.js.map +1 -0
  98. package/dist/core/context/memory-retrieval.d.ts +27 -0
  99. package/dist/core/context/memory-retrieval.d.ts.map +1 -0
  100. package/dist/core/context/memory-retrieval.js +91 -0
  101. package/dist/core/context/memory-retrieval.js.map +1 -0
  102. package/dist/core/context/okf-memory-provider.d.ts +26 -0
  103. package/dist/core/context/okf-memory-provider.d.ts.map +1 -0
  104. package/dist/core/context/okf-memory-provider.js +154 -0
  105. package/dist/core/context/okf-memory-provider.js.map +1 -0
  106. package/dist/core/context/okf-memory.d.ts +42 -0
  107. package/dist/core/context/okf-memory.d.ts.map +1 -0
  108. package/dist/core/context/okf-memory.js +175 -0
  109. package/dist/core/context/okf-memory.js.map +1 -0
  110. package/dist/core/context/policy-engine.d.ts +66 -0
  111. package/dist/core/context/policy-engine.d.ts.map +1 -0
  112. package/dist/core/context/policy-engine.js +171 -0
  113. package/dist/core/context/policy-engine.js.map +1 -0
  114. package/dist/core/context/policy-types.d.ts +102 -0
  115. package/dist/core/context/policy-types.d.ts.map +1 -0
  116. package/dist/core/context/policy-types.js +7 -0
  117. package/dist/core/context/policy-types.js.map +1 -0
  118. package/dist/core/context/sqlite-runtime-index.d.ts +19 -0
  119. package/dist/core/context/sqlite-runtime-index.d.ts.map +1 -0
  120. package/dist/core/context/sqlite-runtime-index.js +344 -0
  121. package/dist/core/context/sqlite-runtime-index.js.map +1 -0
  122. package/dist/core/context/storage-authority.d.ts +20 -0
  123. package/dist/core/context/storage-authority.d.ts.map +1 -0
  124. package/dist/core/context/storage-authority.js +51 -0
  125. package/dist/core/context/storage-authority.js.map +1 -0
  126. package/dist/core/context/tool-output-packer.d.ts +75 -0
  127. package/dist/core/context/tool-output-packer.d.ts.map +1 -0
  128. package/dist/core/context/tool-output-packer.js +77 -0
  129. package/dist/core/context/tool-output-packer.js.map +1 -0
  130. package/dist/core/cost/session-usage.d.ts +20 -0
  131. package/dist/core/cost/session-usage.d.ts.map +1 -0
  132. package/dist/core/cost/session-usage.js +164 -0
  133. package/dist/core/cost/session-usage.js.map +1 -0
  134. package/dist/core/delegation/session-worker-result.d.ts +10 -0
  135. package/dist/core/delegation/session-worker-result.d.ts.map +1 -0
  136. package/dist/core/delegation/session-worker-result.js +36 -0
  137. package/dist/core/delegation/session-worker-result.js.map +1 -0
  138. package/dist/core/delegation/worker-result.d.ts +9 -0
  139. package/dist/core/delegation/worker-result.d.ts.map +1 -0
  140. package/dist/core/delegation/worker-result.js +152 -0
  141. package/dist/core/delegation/worker-result.js.map +1 -0
  142. package/dist/core/delegation/worker-runner.d.ts +58 -0
  143. package/dist/core/delegation/worker-runner.d.ts.map +1 -0
  144. package/dist/core/delegation/worker-runner.js +188 -0
  145. package/dist/core/delegation/worker-runner.js.map +1 -0
  146. package/dist/core/extensions/builtin.d.ts +5 -1
  147. package/dist/core/extensions/builtin.d.ts.map +1 -1
  148. package/dist/core/extensions/builtin.js +23 -1
  149. package/dist/core/extensions/builtin.js.map +1 -1
  150. package/dist/core/footer-data-provider.d.ts +5 -1
  151. package/dist/core/footer-data-provider.d.ts.map +1 -1
  152. package/dist/core/footer-data-provider.js +13 -0
  153. package/dist/core/footer-data-provider.js.map +1 -1
  154. package/dist/core/goals/goal-continuation-controller.d.ts +22 -0
  155. package/dist/core/goals/goal-continuation-controller.d.ts.map +1 -0
  156. package/dist/core/goals/goal-continuation-controller.js +88 -0
  157. package/dist/core/goals/goal-continuation-controller.js.map +1 -0
  158. package/dist/core/goals/goal-continuation-defaults.d.ts +10 -0
  159. package/dist/core/goals/goal-continuation-defaults.d.ts.map +1 -0
  160. package/dist/core/goals/goal-continuation-defaults.js +10 -0
  161. package/dist/core/goals/goal-continuation-defaults.js.map +1 -0
  162. package/dist/core/goals/goal-continuation-prompt.d.ts +18 -0
  163. package/dist/core/goals/goal-continuation-prompt.d.ts.map +1 -0
  164. package/dist/core/goals/goal-continuation-prompt.js +141 -0
  165. package/dist/core/goals/goal-continuation-prompt.js.map +1 -0
  166. package/dist/core/goals/goal-runtime-snapshot.d.ts +19 -0
  167. package/dist/core/goals/goal-runtime-snapshot.d.ts.map +1 -0
  168. package/dist/core/goals/goal-runtime-snapshot.js +23 -0
  169. package/dist/core/goals/goal-runtime-snapshot.js.map +1 -0
  170. package/dist/core/goals/goal-state.d.ts +87 -0
  171. package/dist/core/goals/goal-state.d.ts.map +1 -0
  172. package/dist/core/goals/goal-state.js +259 -0
  173. package/dist/core/goals/goal-state.js.map +1 -0
  174. package/dist/core/goals/goal-tool-core.d.ts +66 -0
  175. package/dist/core/goals/goal-tool-core.d.ts.map +1 -0
  176. package/dist/core/goals/goal-tool-core.js +146 -0
  177. package/dist/core/goals/goal-tool-core.js.map +1 -0
  178. package/dist/core/goals/session-goal-state.d.ts +10 -0
  179. package/dist/core/goals/session-goal-state.d.ts.map +1 -0
  180. package/dist/core/goals/session-goal-state.js +35 -0
  181. package/dist/core/goals/session-goal-state.js.map +1 -0
  182. package/dist/core/learning/learning-audit.d.ts +45 -0
  183. package/dist/core/learning/learning-audit.d.ts.map +1 -0
  184. package/dist/core/learning/learning-audit.js +139 -0
  185. package/dist/core/learning/learning-audit.js.map +1 -0
  186. package/dist/core/learning/learning-gate.d.ts +29 -0
  187. package/dist/core/learning/learning-gate.d.ts.map +1 -0
  188. package/dist/core/learning/learning-gate.js +150 -0
  189. package/dist/core/learning/learning-gate.js.map +1 -0
  190. package/dist/core/learning/session-learning-decision.d.ts +10 -0
  191. package/dist/core/learning/session-learning-decision.d.ts.map +1 -0
  192. package/dist/core/learning/session-learning-decision.js +36 -0
  193. package/dist/core/learning/session-learning-decision.js.map +1 -0
  194. package/dist/core/model-capability.d.ts +41 -0
  195. package/dist/core/model-capability.d.ts.map +1 -0
  196. package/dist/core/model-capability.js +101 -0
  197. package/dist/core/model-capability.js.map +1 -0
  198. package/dist/core/model-router/config-diagnostics.d.ts.map +1 -1
  199. package/dist/core/model-router/config-diagnostics.js +1 -0
  200. package/dist/core/model-router/config-diagnostics.js.map +1 -1
  201. package/dist/core/model-router/intent-classifier.d.ts +2 -0
  202. package/dist/core/model-router/intent-classifier.d.ts.map +1 -1
  203. package/dist/core/model-router/intent-classifier.js +154 -9
  204. package/dist/core/model-router/intent-classifier.js.map +1 -1
  205. package/dist/core/model-router/route-judge.d.ts +54 -0
  206. package/dist/core/model-router/route-judge.d.ts.map +1 -0
  207. package/dist/core/model-router/route-judge.js +128 -0
  208. package/dist/core/model-router/route-judge.js.map +1 -0
  209. package/dist/core/model-router/status.d.ts +4 -1
  210. package/dist/core/model-router/status.d.ts.map +1 -1
  211. package/dist/core/model-router/status.js +30 -6
  212. package/dist/core/model-router/status.js.map +1 -1
  213. package/dist/core/model-router/tool-escalation.d.ts +4 -6
  214. package/dist/core/model-router/tool-escalation.d.ts.map +1 -1
  215. package/dist/core/model-router/tool-escalation.js +1 -1
  216. package/dist/core/model-router/tool-escalation.js.map +1 -1
  217. package/dist/core/models/fitness-store.d.ts +40 -0
  218. package/dist/core/models/fitness-store.d.ts.map +1 -0
  219. package/dist/core/models/fitness-store.js +61 -0
  220. package/dist/core/models/fitness-store.js.map +1 -0
  221. package/dist/core/profile-registry.d.ts.map +1 -1
  222. package/dist/core/profile-registry.js +1 -1
  223. package/dist/core/profile-registry.js.map +1 -1
  224. package/dist/core/prompt-templates.d.ts +2 -0
  225. package/dist/core/prompt-templates.d.ts.map +1 -1
  226. package/dist/core/prompt-templates.js +12 -4
  227. package/dist/core/prompt-templates.js.map +1 -1
  228. package/dist/core/research/automata-provider.d.ts +5 -0
  229. package/dist/core/research/automata-provider.d.ts.map +1 -0
  230. package/dist/core/research/automata-provider.js +15 -0
  231. package/dist/core/research/automata-provider.js.map +1 -0
  232. package/dist/core/research/evidence-bundle.d.ts +10 -0
  233. package/dist/core/research/evidence-bundle.d.ts.map +1 -0
  234. package/dist/core/research/evidence-bundle.js +116 -0
  235. package/dist/core/research/evidence-bundle.js.map +1 -0
  236. package/dist/core/research/model-fitness.d.ts +79 -0
  237. package/dist/core/research/model-fitness.d.ts.map +1 -0
  238. package/dist/core/research/model-fitness.js +257 -0
  239. package/dist/core/research/model-fitness.js.map +1 -0
  240. package/dist/core/research/research-gate.d.ts +11 -0
  241. package/dist/core/research/research-gate.d.ts.map +1 -0
  242. package/dist/core/research/research-gate.js +82 -0
  243. package/dist/core/research/research-gate.js.map +1 -0
  244. package/dist/core/research/research-runner.d.ts +59 -0
  245. package/dist/core/research/research-runner.d.ts.map +1 -0
  246. package/dist/core/research/research-runner.js +155 -0
  247. package/dist/core/research/research-runner.js.map +1 -0
  248. package/dist/core/research/session-evidence-bundle.d.ts +11 -0
  249. package/dist/core/research/session-evidence-bundle.d.ts.map +1 -0
  250. package/dist/core/research/session-evidence-bundle.js +55 -0
  251. package/dist/core/research/session-evidence-bundle.js.map +1 -0
  252. package/dist/core/resource-loader.d.ts.map +1 -1
  253. package/dist/core/resource-loader.js +4 -0
  254. package/dist/core/resource-loader.js.map +1 -1
  255. package/dist/core/settings-manager.d.ts +147 -4
  256. package/dist/core/settings-manager.d.ts.map +1 -1
  257. package/dist/core/settings-manager.js +285 -9
  258. package/dist/core/settings-manager.js.map +1 -1
  259. package/dist/core/skills.d.ts +4 -0
  260. package/dist/core/skills.d.ts.map +1 -1
  261. package/dist/core/skills.js +18 -6
  262. package/dist/core/skills.js.map +1 -1
  263. package/dist/core/slash-commands.d.ts.map +1 -1
  264. package/dist/core/slash-commands.js +4 -0
  265. package/dist/core/slash-commands.js.map +1 -1
  266. package/dist/core/toolkit/script-registry.d.ts +34 -0
  267. package/dist/core/toolkit/script-registry.d.ts.map +1 -0
  268. package/dist/core/toolkit/script-registry.js +71 -0
  269. package/dist/core/toolkit/script-registry.js.map +1 -0
  270. package/dist/core/toolkit/script-runner.d.ts +28 -0
  271. package/dist/core/toolkit/script-runner.d.ts.map +1 -0
  272. package/dist/core/toolkit/script-runner.js +48 -0
  273. package/dist/core/toolkit/script-runner.js.map +1 -0
  274. package/dist/core/tools/artifact-retrieve.d.ts +23 -0
  275. package/dist/core/tools/artifact-retrieve.d.ts.map +1 -0
  276. package/dist/core/tools/artifact-retrieve.js +110 -0
  277. package/dist/core/tools/artifact-retrieve.js.map +1 -0
  278. package/dist/core/tools/delegate.d.ts +32 -0
  279. package/dist/core/tools/delegate.d.ts.map +1 -0
  280. package/dist/core/tools/delegate.js +60 -0
  281. package/dist/core/tools/delegate.js.map +1 -0
  282. package/dist/core/tools/fff-search-backend.d.ts +103 -0
  283. package/dist/core/tools/fff-search-backend.d.ts.map +1 -0
  284. package/dist/core/tools/fff-search-backend.js +151 -0
  285. package/dist/core/tools/fff-search-backend.js.map +1 -0
  286. package/dist/core/tools/find.d.ts +21 -1
  287. package/dist/core/tools/find.d.ts.map +1 -1
  288. package/dist/core/tools/find.js +183 -10
  289. package/dist/core/tools/find.js.map +1 -1
  290. package/dist/core/tools/goal.d.ts +35 -0
  291. package/dist/core/tools/goal.d.ts.map +1 -0
  292. package/dist/core/tools/goal.js +122 -0
  293. package/dist/core/tools/goal.js.map +1 -0
  294. package/dist/core/tools/grep.d.ts +21 -1
  295. package/dist/core/tools/grep.d.ts.map +1 -1
  296. package/dist/core/tools/grep.js +272 -27
  297. package/dist/core/tools/grep.js.map +1 -1
  298. package/dist/core/tools/index.d.ts +4 -1
  299. package/dist/core/tools/index.d.ts.map +1 -1
  300. package/dist/core/tools/index.js +9 -0
  301. package/dist/core/tools/index.js.map +1 -1
  302. package/dist/core/tools/model-fitness.d.ts +30 -0
  303. package/dist/core/tools/model-fitness.d.ts.map +1 -0
  304. package/dist/core/tools/model-fitness.js +38 -0
  305. package/dist/core/tools/model-fitness.js.map +1 -0
  306. package/dist/core/tools/run-toolkit-script.d.ts +24 -0
  307. package/dist/core/tools/run-toolkit-script.d.ts.map +1 -0
  308. package/dist/core/tools/run-toolkit-script.js +103 -0
  309. package/dist/core/tools/run-toolkit-script.js.map +1 -0
  310. package/dist/core/tools/search-router.d.ts +75 -0
  311. package/dist/core/tools/search-router.d.ts.map +1 -0
  312. package/dist/core/tools/search-router.js +85 -0
  313. package/dist/core/tools/search-router.js.map +1 -0
  314. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  315. package/dist/modes/interactive/components/footer.js +18 -16
  316. package/dist/modes/interactive/components/footer.js.map +1 -1
  317. package/dist/modes/interactive/components/settings-selector.d.ts +13 -1
  318. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  319. package/dist/modes/interactive/components/settings-selector.js +471 -11
  320. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  321. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  322. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  323. package/dist/modes/interactive/interactive-mode.js +217 -39
  324. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  325. package/dist/utils/tools-manager.d.ts +2 -0
  326. package/dist/utils/tools-manager.d.ts.map +1 -1
  327. package/dist/utils/tools-manager.js +154 -2
  328. package/dist/utils/tools-manager.js.map +1 -1
  329. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  330. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  331. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  332. package/examples/extensions/sandbox/package-lock.json +2 -2
  333. package/examples/extensions/sandbox/package.json +1 -1
  334. package/examples/extensions/with-deps/package-lock.json +2 -2
  335. package/examples/extensions/with-deps/package.json +1 -1
  336. package/npm-shrinkwrap.json +368 -12
  337. package/package.json +5 -4
@@ -0,0 +1,307 @@
1
+ /**
2
+ * Artifact store abstraction (Phase 2): stable refs for raw large payloads, kept out of
3
+ * band from prompt context. This module defines the `ArtifactStore` interface plus two
4
+ * implementations: an in-memory one for tests, and `createFileArtifactStore` (session-
5
+ * scoped, filesystem-backed). A SQLite-backed implementation waits until the Phase M0
6
+ * storage-authority/location/concurrency decisions are accepted (see
7
+ * docs/context-management-rework/memory-architecture.md).
8
+ *
9
+ * `createFileArtifactStore` is wired into live grep/find tool construction in
10
+ * agent-session.ts (session-scoped under `<agentDir>/context-artifacts/<sessionId>/`).
11
+ * References are registered at pack time and released when context-gc evicts the
12
+ * corresponding grep/find tool result (opportunistic, conservative cleanup), with a
13
+ * best-effort dispose-time sweep for zero-reference artifacts. Payloads are retrievable
14
+ * out of band via the artifact_retrieve tool (context/artifact-retrieval.ts).
15
+ */
16
+ import { createHash } from "node:crypto";
17
+ import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
18
+ import { join } from "node:path";
19
+ import { estimateByteLength, estimateLineCount } from "./context-item.js";
20
+ export function isMissingArtifactMarker(value) {
21
+ return value.missing === true;
22
+ }
23
+ /**
24
+ * Artifact id for a capture event, not merely a payload: it hashes every ref-defining
25
+ * field (kind, tool/command/path, content, sessionEntryId, createdAtTurn, reproducible).
26
+ * A repeat write with identical content but a different turn or session entry is a
27
+ * distinct capture and must get a distinct id -- otherwise the later capture's metadata
28
+ * would be silently discarded in favor of the first write. Only a truly identical
29
+ * request (same capture, re-submitted) is idempotent under this id.
30
+ */
31
+ export function generateArtifactId(request) {
32
+ return createHash("sha256")
33
+ .update([
34
+ request.kind,
35
+ request.toolName ?? "",
36
+ request.command ?? "",
37
+ request.path ?? "",
38
+ request.sessionEntryId ?? "",
39
+ String(request.createdAtTurn),
40
+ String(request.reproducible),
41
+ request.content,
42
+ ].join("\0"))
43
+ .digest("hex")
44
+ .slice(0, 24);
45
+ }
46
+ export function createInMemoryArtifactStore() {
47
+ const artifacts = new Map();
48
+ const cleanedUp = new Set();
49
+ return {
50
+ write(request) {
51
+ const id = generateArtifactId(request);
52
+ const existing = artifacts.get(id);
53
+ if (existing) {
54
+ cleanedUp.delete(id);
55
+ return { ref: existing.ref, content: existing.content };
56
+ }
57
+ const ref = {
58
+ id,
59
+ kind: request.kind,
60
+ sessionEntryId: request.sessionEntryId,
61
+ toolName: request.toolName,
62
+ command: request.command,
63
+ path: request.path,
64
+ byteLength: estimateByteLength(request.content),
65
+ lineCount: estimateLineCount(request.content),
66
+ createdAtTurn: request.createdAtTurn,
67
+ reproducible: request.reproducible,
68
+ };
69
+ artifacts.set(id, { ref, content: request.content, references: new Set() });
70
+ cleanedUp.delete(id);
71
+ return { ref, content: request.content };
72
+ },
73
+ read(id) {
74
+ const stored = artifacts.get(id);
75
+ if (!stored) {
76
+ return { id, missing: true, reason: cleanedUp.has(id) ? "cleaned_up" : "not_found" };
77
+ }
78
+ return { ref: stored.ref, content: stored.content };
79
+ },
80
+ readRef(id) {
81
+ return artifacts.get(id)?.ref;
82
+ },
83
+ has(id) {
84
+ return artifacts.has(id);
85
+ },
86
+ addReference(id, holderId) {
87
+ const stored = artifacts.get(id);
88
+ if (!stored)
89
+ return false;
90
+ stored.references.add(holderId);
91
+ return true;
92
+ },
93
+ removeReference(id, holderId) {
94
+ const stored = artifacts.get(id);
95
+ if (!stored)
96
+ return false;
97
+ return stored.references.delete(holderId);
98
+ },
99
+ referenceCount(id) {
100
+ return artifacts.get(id)?.references.size ?? 0;
101
+ },
102
+ cleanup() {
103
+ const deleted = [];
104
+ for (const [id, stored] of artifacts) {
105
+ if (stored.references.size === 0) {
106
+ artifacts.delete(id);
107
+ cleanedUp.add(id);
108
+ deleted.push(id);
109
+ }
110
+ }
111
+ return deleted;
112
+ },
113
+ };
114
+ }
115
+ const META_SUFFIX = ".meta.json";
116
+ const PAYLOAD_SUFFIX = ".payload";
117
+ /**
118
+ * Artifact ids are generated by `generateArtifactId` as a lowercase hex digest. Reject
119
+ * anything else so a caller-supplied id (including one echoed back from model output)
120
+ * can never be used as a path-traversal vector into `baseDir`.
121
+ */
122
+ function isSafeArtifactId(id) {
123
+ return /^[0-9a-f]{1,64}$/.test(id);
124
+ }
125
+ function payloadPath(baseDir, id) {
126
+ return join(baseDir, `${id}${PAYLOAD_SUFFIX}`);
127
+ }
128
+ function metaPath(baseDir, id) {
129
+ return join(baseDir, `${id}${META_SUFFIX}`);
130
+ }
131
+ const VALID_ARTIFACT_KINDS = new Set([
132
+ "tool_output",
133
+ "file_snapshot",
134
+ "test_output",
135
+ "diff",
136
+ "transcript_slice",
137
+ ]);
138
+ function isValidArtifactRefShape(value) {
139
+ if (typeof value !== "object" || value === null)
140
+ return false;
141
+ const ref = value;
142
+ return (typeof ref.id === "string" &&
143
+ typeof ref.kind === "string" &&
144
+ VALID_ARTIFACT_KINDS.has(ref.kind) &&
145
+ typeof ref.byteLength === "number" &&
146
+ typeof ref.createdAtTurn === "number" &&
147
+ typeof ref.reproducible === "boolean");
148
+ }
149
+ /**
150
+ * A parsed JSON value can be syntactically valid but semantically garbage (truncated
151
+ * write, hand-edited file, future/incompatible format). Validate shape, not just parse
152
+ * success, so a malformed sidecar can never produce an invalid ref or crash `cleanup()` --
153
+ * it is treated as unusable/missing, the same as a sidecar that doesn't exist.
154
+ */
155
+ function isValidPersistedArtifactMeta(value) {
156
+ if (typeof value !== "object" || value === null)
157
+ return false;
158
+ const meta = value;
159
+ return (isValidArtifactRefShape(meta.ref) &&
160
+ Array.isArray(meta.references) &&
161
+ meta.references.every((entry) => typeof entry === "string"));
162
+ }
163
+ function readMeta(baseDir, id) {
164
+ const path = metaPath(baseDir, id);
165
+ if (!existsSync(path))
166
+ return undefined;
167
+ try {
168
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
169
+ return isValidPersistedArtifactMeta(parsed) ? parsed : undefined;
170
+ }
171
+ catch {
172
+ return undefined;
173
+ }
174
+ }
175
+ function writeMeta(baseDir, id, meta) {
176
+ writeFileSync(metaPath(baseDir, id), JSON.stringify(meta), "utf8");
177
+ }
178
+ /**
179
+ * Filesystem-backed `ArtifactStore`: payload and metadata (including reference holder ids)
180
+ * are written to `baseDir` so content, ref fields, and cleanup-protecting references all
181
+ * survive recreating the store (e.g. across a process restart against the same
182
+ * directory) -- unlike the in-memory store, which loses everything when the instance is
183
+ * dropped. No SQLite or other index is used; each artifact's metadata is a small sidecar
184
+ * JSON file next to its payload file, per the "keep SQLite out of scope unless a minimal
185
+ * metadata shape is unavoidable" constraint for this slice.
186
+ *
187
+ * The one thing that does NOT survive recreation: the missing-artifact reason
188
+ * distinction. A fresh instance has no in-memory record of which ids it personally
189
+ * cleaned up, so a previously-cleaned-up id reads back as "not_found" rather than
190
+ * "cleaned_up" after a restart. This still always returns an explicit missing marker,
191
+ * never fabricated or empty content -- it only affects which of the two reason codes is
192
+ * reported.
193
+ */
194
+ export function createFileArtifactStore(options) {
195
+ const baseDir = options.baseDir;
196
+ mkdirSync(baseDir, { recursive: true });
197
+ const cleanedUpThisInstance = new Set();
198
+ return {
199
+ write(request) {
200
+ const id = generateArtifactId(request);
201
+ const existingMeta = readMeta(baseDir, id);
202
+ const existingPayloadPath = payloadPath(baseDir, id);
203
+ if (existingMeta && existsSync(existingPayloadPath)) {
204
+ cleanedUpThisInstance.delete(id);
205
+ return { ref: existingMeta.ref, content: readFileSync(existingPayloadPath, "utf8") };
206
+ }
207
+ const ref = {
208
+ id,
209
+ kind: request.kind,
210
+ sessionEntryId: request.sessionEntryId,
211
+ toolName: request.toolName,
212
+ command: request.command,
213
+ path: request.path,
214
+ byteLength: estimateByteLength(request.content),
215
+ lineCount: estimateLineCount(request.content),
216
+ createdAtTurn: request.createdAtTurn,
217
+ reproducible: request.reproducible,
218
+ };
219
+ writeFileSync(payloadPath(baseDir, id), request.content, "utf8");
220
+ writeMeta(baseDir, id, { ref, references: [] });
221
+ cleanedUpThisInstance.delete(id);
222
+ return { ref, content: request.content };
223
+ },
224
+ read(id) {
225
+ if (!isSafeArtifactId(id))
226
+ return { id, missing: true, reason: "not_found" };
227
+ const meta = readMeta(baseDir, id);
228
+ const pPath = payloadPath(baseDir, id);
229
+ if (!meta || !existsSync(pPath)) {
230
+ return { id, missing: true, reason: cleanedUpThisInstance.has(id) ? "cleaned_up" : "not_found" };
231
+ }
232
+ return { ref: meta.ref, content: readFileSync(pPath, "utf8") };
233
+ },
234
+ readRef(id) {
235
+ if (!isSafeArtifactId(id))
236
+ return undefined;
237
+ const meta = readMeta(baseDir, id);
238
+ if (!meta || !existsSync(payloadPath(baseDir, id)))
239
+ return undefined;
240
+ return meta.ref;
241
+ },
242
+ has(id) {
243
+ if (!isSafeArtifactId(id))
244
+ return false;
245
+ return readMeta(baseDir, id) !== undefined && existsSync(payloadPath(baseDir, id));
246
+ },
247
+ addReference(id, holderId) {
248
+ if (!isSafeArtifactId(id))
249
+ return false;
250
+ const meta = readMeta(baseDir, id);
251
+ if (!meta || !existsSync(payloadPath(baseDir, id)))
252
+ return false;
253
+ if (!meta.references.includes(holderId)) {
254
+ meta.references.push(holderId);
255
+ writeMeta(baseDir, id, meta);
256
+ }
257
+ return true;
258
+ },
259
+ removeReference(id, holderId) {
260
+ if (!isSafeArtifactId(id))
261
+ return false;
262
+ const meta = readMeta(baseDir, id);
263
+ if (!meta)
264
+ return false;
265
+ const index = meta.references.indexOf(holderId);
266
+ if (index === -1)
267
+ return false;
268
+ meta.references.splice(index, 1);
269
+ writeMeta(baseDir, id, meta);
270
+ return true;
271
+ },
272
+ referenceCount(id) {
273
+ if (!isSafeArtifactId(id))
274
+ return 0;
275
+ return readMeta(baseDir, id)?.references.length ?? 0;
276
+ },
277
+ cleanup() {
278
+ const deleted = [];
279
+ for (const entry of readdirSync(baseDir)) {
280
+ if (!entry.endsWith(META_SUFFIX))
281
+ continue;
282
+ const id = entry.slice(0, -META_SUFFIX.length);
283
+ if (!isSafeArtifactId(id))
284
+ continue;
285
+ const meta = readMeta(baseDir, id);
286
+ if (!meta || meta.references.length > 0)
287
+ continue;
288
+ try {
289
+ unlinkSync(metaPath(baseDir, id));
290
+ }
291
+ catch {
292
+ continue;
293
+ }
294
+ try {
295
+ unlinkSync(payloadPath(baseDir, id));
296
+ }
297
+ catch {
298
+ // Payload already gone; metadata removal above is what matters for reachability.
299
+ }
300
+ cleanedUpThisInstance.add(id);
301
+ deleted.push(id);
302
+ }
303
+ return deleted;
304
+ },
305
+ };
306
+ }
307
+ //# sourceMappingURL=context-artifacts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-artifacts.js","sourceRoot":"","sources":["../../../src/core/context/context-artifacts.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Artifact store abstraction (Phase 2): stable refs for raw large payloads, kept out of\n * band from prompt context. This module defines the `ArtifactStore` interface plus two\n * implementations: an in-memory one for tests, and `createFileArtifactStore` (session-\n * scoped, filesystem-backed). A SQLite-backed implementation waits until the Phase M0\n * storage-authority/location/concurrency decisions are accepted (see\n * docs/context-management-rework/memory-architecture.md).\n *\n * `createFileArtifactStore` is wired into live grep/find tool construction in\n * agent-session.ts (session-scoped under `<agentDir>/context-artifacts/<sessionId>/`).\n * References are registered at pack time and released when context-gc evicts the\n * corresponding grep/find tool result (opportunistic, conservative cleanup), with a\n * best-effort dispose-time sweep for zero-reference artifacts. Payloads are retrievable\n * out of band via the artifact_retrieve tool (context/artifact-retrieval.ts).\n */\n\nimport { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { type ContextArtifactRef, estimateByteLength, estimateLineCount } from \"./context-item.ts\";\n\nexport interface ArtifactWriteRequest {\n\tkind: ContextArtifactRef[\"kind\"];\n\tcontent: string;\n\ttoolName?: string;\n\tcommand?: string;\n\tpath?: string;\n\tsessionEntryId?: string;\n\tcreatedAtTurn: number;\n\treproducible: boolean;\n}\n\nexport interface ArtifactRecord {\n\tref: ContextArtifactRef;\n\tcontent: string;\n}\n\nexport type MissingArtifactReason = \"not_found\" | \"cleaned_up\";\n\nexport interface MissingArtifactMarker {\n\tid: string;\n\tmissing: true;\n\treason: MissingArtifactReason;\n}\n\nexport function isMissingArtifactMarker(value: ArtifactRecord | MissingArtifactMarker): value is MissingArtifactMarker {\n\treturn (value as MissingArtifactMarker).missing === true;\n}\n\n/**\n * Artifact id for a capture event, not merely a payload: it hashes every ref-defining\n * field (kind, tool/command/path, content, sessionEntryId, createdAtTurn, reproducible).\n * A repeat write with identical content but a different turn or session entry is a\n * distinct capture and must get a distinct id -- otherwise the later capture's metadata\n * would be silently discarded in favor of the first write. Only a truly identical\n * request (same capture, re-submitted) is idempotent under this id.\n */\nexport function generateArtifactId(\n\trequest: Pick<\n\t\tArtifactWriteRequest,\n\t\t\"kind\" | \"content\" | \"toolName\" | \"command\" | \"path\" | \"sessionEntryId\" | \"createdAtTurn\" | \"reproducible\"\n\t>,\n): string {\n\treturn createHash(\"sha256\")\n\t\t.update(\n\t\t\t[\n\t\t\t\trequest.kind,\n\t\t\t\trequest.toolName ?? \"\",\n\t\t\t\trequest.command ?? \"\",\n\t\t\t\trequest.path ?? \"\",\n\t\t\t\trequest.sessionEntryId ?? \"\",\n\t\t\t\tString(request.createdAtTurn),\n\t\t\t\tString(request.reproducible),\n\t\t\t\trequest.content,\n\t\t\t].join(\"\\0\"),\n\t\t)\n\t\t.digest(\"hex\")\n\t\t.slice(0, 24);\n}\n\nexport interface ArtifactStore {\n\twrite(request: ArtifactWriteRequest): ArtifactRecord;\n\tread(id: string): ArtifactRecord | MissingArtifactMarker;\n\t/**\n\t * Metadata-only lookup: the ref if `id` resolves to a live artifact, `undefined`\n\t * otherwise. Never loads the payload -- for the file store this must not touch the\n\t * payload file at all beyond an existence check, so a caller that only needs to know\n\t * \"does this still exist, and what are its ref fields\" (e.g. a per-turn audit pass)\n\t * never pays the cost of reading potentially large content off disk.\n\t */\n\treadRef(id: string): ContextArtifactRef | undefined;\n\thas(id: string): boolean;\n\t/**\n\t * Register that `holderId` (a context item id, session entry id, etc.) depends on this\n\t * artifact. Returns false if `id` does not exist (never written, or already cleaned\n\t * up) so a caller cannot believe it protected an artifact that was never registered.\n\t * Callers must fail closed (treat the artifact as unprotected) on a false return.\n\t */\n\taddReference(id: string, holderId: string): boolean;\n\t/** Release a previously registered dependency. Returns true only if a reference was actually removed. */\n\tremoveReference(id: string, holderId: string): boolean;\n\treferenceCount(id: string): number;\n\t/** Delete only artifacts with zero active references. Returns the ids actually deleted. */\n\tcleanup(): string[];\n}\n\ninterface StoredArtifact {\n\tref: ContextArtifactRef;\n\tcontent: string;\n\treferences: Set<string>;\n}\n\nexport function createInMemoryArtifactStore(): ArtifactStore {\n\tconst artifacts = new Map<string, StoredArtifact>();\n\tconst cleanedUp = new Set<string>();\n\n\treturn {\n\t\twrite(request: ArtifactWriteRequest): ArtifactRecord {\n\t\t\tconst id = generateArtifactId(request);\n\t\t\tconst existing = artifacts.get(id);\n\t\t\tif (existing) {\n\t\t\t\tcleanedUp.delete(id);\n\t\t\t\treturn { ref: existing.ref, content: existing.content };\n\t\t\t}\n\n\t\t\tconst ref: ContextArtifactRef = {\n\t\t\t\tid,\n\t\t\t\tkind: request.kind,\n\t\t\t\tsessionEntryId: request.sessionEntryId,\n\t\t\t\ttoolName: request.toolName,\n\t\t\t\tcommand: request.command,\n\t\t\t\tpath: request.path,\n\t\t\t\tbyteLength: estimateByteLength(request.content),\n\t\t\t\tlineCount: estimateLineCount(request.content),\n\t\t\t\tcreatedAtTurn: request.createdAtTurn,\n\t\t\t\treproducible: request.reproducible,\n\t\t\t};\n\t\t\tartifacts.set(id, { ref, content: request.content, references: new Set() });\n\t\t\tcleanedUp.delete(id);\n\t\t\treturn { ref, content: request.content };\n\t\t},\n\n\t\tread(id: string): ArtifactRecord | MissingArtifactMarker {\n\t\t\tconst stored = artifacts.get(id);\n\t\t\tif (!stored) {\n\t\t\t\treturn { id, missing: true, reason: cleanedUp.has(id) ? \"cleaned_up\" : \"not_found\" };\n\t\t\t}\n\t\t\treturn { ref: stored.ref, content: stored.content };\n\t\t},\n\n\t\treadRef(id: string): ContextArtifactRef | undefined {\n\t\t\treturn artifacts.get(id)?.ref;\n\t\t},\n\n\t\thas(id: string): boolean {\n\t\t\treturn artifacts.has(id);\n\t\t},\n\n\t\taddReference(id: string, holderId: string): boolean {\n\t\t\tconst stored = artifacts.get(id);\n\t\t\tif (!stored) return false;\n\t\t\tstored.references.add(holderId);\n\t\t\treturn true;\n\t\t},\n\n\t\tremoveReference(id: string, holderId: string): boolean {\n\t\t\tconst stored = artifacts.get(id);\n\t\t\tif (!stored) return false;\n\t\t\treturn stored.references.delete(holderId);\n\t\t},\n\n\t\treferenceCount(id: string): number {\n\t\t\treturn artifacts.get(id)?.references.size ?? 0;\n\t\t},\n\n\t\tcleanup(): string[] {\n\t\t\tconst deleted: string[] = [];\n\t\t\tfor (const [id, stored] of artifacts) {\n\t\t\t\tif (stored.references.size === 0) {\n\t\t\t\t\tartifacts.delete(id);\n\t\t\t\t\tcleanedUp.add(id);\n\t\t\t\t\tdeleted.push(id);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn deleted;\n\t\t},\n\t};\n}\n\nexport interface FileArtifactStoreOptions {\n\t/** Directory the store persists artifact payloads and metadata under. Created if missing. */\n\tbaseDir: string;\n}\n\ninterface PersistedArtifactMeta {\n\tref: ContextArtifactRef;\n\treferences: string[];\n}\n\nconst META_SUFFIX = \".meta.json\";\nconst PAYLOAD_SUFFIX = \".payload\";\n\n/**\n * Artifact ids are generated by `generateArtifactId` as a lowercase hex digest. Reject\n * anything else so a caller-supplied id (including one echoed back from model output)\n * can never be used as a path-traversal vector into `baseDir`.\n */\nfunction isSafeArtifactId(id: string): boolean {\n\treturn /^[0-9a-f]{1,64}$/.test(id);\n}\n\nfunction payloadPath(baseDir: string, id: string): string {\n\treturn join(baseDir, `${id}${PAYLOAD_SUFFIX}`);\n}\n\nfunction metaPath(baseDir: string, id: string): string {\n\treturn join(baseDir, `${id}${META_SUFFIX}`);\n}\n\nconst VALID_ARTIFACT_KINDS: ReadonlySet<ContextArtifactRef[\"kind\"]> = new Set([\n\t\"tool_output\",\n\t\"file_snapshot\",\n\t\"test_output\",\n\t\"diff\",\n\t\"transcript_slice\",\n]);\n\nfunction isValidArtifactRefShape(value: unknown): value is ContextArtifactRef {\n\tif (typeof value !== \"object\" || value === null) return false;\n\tconst ref = value as Record<string, unknown>;\n\treturn (\n\t\ttypeof ref.id === \"string\" &&\n\t\ttypeof ref.kind === \"string\" &&\n\t\tVALID_ARTIFACT_KINDS.has(ref.kind as ContextArtifactRef[\"kind\"]) &&\n\t\ttypeof ref.byteLength === \"number\" &&\n\t\ttypeof ref.createdAtTurn === \"number\" &&\n\t\ttypeof ref.reproducible === \"boolean\"\n\t);\n}\n\n/**\n * A parsed JSON value can be syntactically valid but semantically garbage (truncated\n * write, hand-edited file, future/incompatible format). Validate shape, not just parse\n * success, so a malformed sidecar can never produce an invalid ref or crash `cleanup()` --\n * it is treated as unusable/missing, the same as a sidecar that doesn't exist.\n */\nfunction isValidPersistedArtifactMeta(value: unknown): value is PersistedArtifactMeta {\n\tif (typeof value !== \"object\" || value === null) return false;\n\tconst meta = value as Record<string, unknown>;\n\treturn (\n\t\tisValidArtifactRefShape(meta.ref) &&\n\t\tArray.isArray(meta.references) &&\n\t\tmeta.references.every((entry) => typeof entry === \"string\")\n\t);\n}\n\nfunction readMeta(baseDir: string, id: string): PersistedArtifactMeta | undefined {\n\tconst path = metaPath(baseDir, id);\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(readFileSync(path, \"utf8\"));\n\t\treturn isValidPersistedArtifactMeta(parsed) ? parsed : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction writeMeta(baseDir: string, id: string, meta: PersistedArtifactMeta): void {\n\twriteFileSync(metaPath(baseDir, id), JSON.stringify(meta), \"utf8\");\n}\n\n/**\n * Filesystem-backed `ArtifactStore`: payload and metadata (including reference holder ids)\n * are written to `baseDir` so content, ref fields, and cleanup-protecting references all\n * survive recreating the store (e.g. across a process restart against the same\n * directory) -- unlike the in-memory store, which loses everything when the instance is\n * dropped. No SQLite or other index is used; each artifact's metadata is a small sidecar\n * JSON file next to its payload file, per the \"keep SQLite out of scope unless a minimal\n * metadata shape is unavoidable\" constraint for this slice.\n *\n * The one thing that does NOT survive recreation: the missing-artifact reason\n * distinction. A fresh instance has no in-memory record of which ids it personally\n * cleaned up, so a previously-cleaned-up id reads back as \"not_found\" rather than\n * \"cleaned_up\" after a restart. This still always returns an explicit missing marker,\n * never fabricated or empty content -- it only affects which of the two reason codes is\n * reported.\n */\nexport function createFileArtifactStore(options: FileArtifactStoreOptions): ArtifactStore {\n\tconst baseDir = options.baseDir;\n\tmkdirSync(baseDir, { recursive: true });\n\tconst cleanedUpThisInstance = new Set<string>();\n\n\treturn {\n\t\twrite(request: ArtifactWriteRequest): ArtifactRecord {\n\t\t\tconst id = generateArtifactId(request);\n\t\t\tconst existingMeta = readMeta(baseDir, id);\n\t\t\tconst existingPayloadPath = payloadPath(baseDir, id);\n\t\t\tif (existingMeta && existsSync(existingPayloadPath)) {\n\t\t\t\tcleanedUpThisInstance.delete(id);\n\t\t\t\treturn { ref: existingMeta.ref, content: readFileSync(existingPayloadPath, \"utf8\") };\n\t\t\t}\n\n\t\t\tconst ref: ContextArtifactRef = {\n\t\t\t\tid,\n\t\t\t\tkind: request.kind,\n\t\t\t\tsessionEntryId: request.sessionEntryId,\n\t\t\t\ttoolName: request.toolName,\n\t\t\t\tcommand: request.command,\n\t\t\t\tpath: request.path,\n\t\t\t\tbyteLength: estimateByteLength(request.content),\n\t\t\t\tlineCount: estimateLineCount(request.content),\n\t\t\t\tcreatedAtTurn: request.createdAtTurn,\n\t\t\t\treproducible: request.reproducible,\n\t\t\t};\n\t\t\twriteFileSync(payloadPath(baseDir, id), request.content, \"utf8\");\n\t\t\twriteMeta(baseDir, id, { ref, references: [] });\n\t\t\tcleanedUpThisInstance.delete(id);\n\t\t\treturn { ref, content: request.content };\n\t\t},\n\n\t\tread(id: string): ArtifactRecord | MissingArtifactMarker {\n\t\t\tif (!isSafeArtifactId(id)) return { id, missing: true, reason: \"not_found\" };\n\t\t\tconst meta = readMeta(baseDir, id);\n\t\t\tconst pPath = payloadPath(baseDir, id);\n\t\t\tif (!meta || !existsSync(pPath)) {\n\t\t\t\treturn { id, missing: true, reason: cleanedUpThisInstance.has(id) ? \"cleaned_up\" : \"not_found\" };\n\t\t\t}\n\t\t\treturn { ref: meta.ref, content: readFileSync(pPath, \"utf8\") };\n\t\t},\n\n\t\treadRef(id: string): ContextArtifactRef | undefined {\n\t\t\tif (!isSafeArtifactId(id)) return undefined;\n\t\t\tconst meta = readMeta(baseDir, id);\n\t\t\tif (!meta || !existsSync(payloadPath(baseDir, id))) return undefined;\n\t\t\treturn meta.ref;\n\t\t},\n\n\t\thas(id: string): boolean {\n\t\t\tif (!isSafeArtifactId(id)) return false;\n\t\t\treturn readMeta(baseDir, id) !== undefined && existsSync(payloadPath(baseDir, id));\n\t\t},\n\n\t\taddReference(id: string, holderId: string): boolean {\n\t\t\tif (!isSafeArtifactId(id)) return false;\n\t\t\tconst meta = readMeta(baseDir, id);\n\t\t\tif (!meta || !existsSync(payloadPath(baseDir, id))) return false;\n\t\t\tif (!meta.references.includes(holderId)) {\n\t\t\t\tmeta.references.push(holderId);\n\t\t\t\twriteMeta(baseDir, id, meta);\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\n\t\tremoveReference(id: string, holderId: string): boolean {\n\t\t\tif (!isSafeArtifactId(id)) return false;\n\t\t\tconst meta = readMeta(baseDir, id);\n\t\t\tif (!meta) return false;\n\t\t\tconst index = meta.references.indexOf(holderId);\n\t\t\tif (index === -1) return false;\n\t\t\tmeta.references.splice(index, 1);\n\t\t\twriteMeta(baseDir, id, meta);\n\t\t\treturn true;\n\t\t},\n\n\t\treferenceCount(id: string): number {\n\t\t\tif (!isSafeArtifactId(id)) return 0;\n\t\t\treturn readMeta(baseDir, id)?.references.length ?? 0;\n\t\t},\n\n\t\tcleanup(): string[] {\n\t\t\tconst deleted: string[] = [];\n\t\t\tfor (const entry of readdirSync(baseDir)) {\n\t\t\t\tif (!entry.endsWith(META_SUFFIX)) continue;\n\t\t\t\tconst id = entry.slice(0, -META_SUFFIX.length);\n\t\t\t\tif (!isSafeArtifactId(id)) continue;\n\t\t\t\tconst meta = readMeta(baseDir, id);\n\t\t\t\tif (!meta || meta.references.length > 0) continue;\n\t\t\t\ttry {\n\t\t\t\t\tunlinkSync(metaPath(baseDir, id));\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tunlinkSync(payloadPath(baseDir, id));\n\t\t\t\t} catch {\n\t\t\t\t\t// Payload already gone; metadata removal above is what matters for reachability.\n\t\t\t\t}\n\t\t\t\tcleanedUpThisInstance.add(id);\n\t\t\t\tdeleted.push(id);\n\t\t\t}\n\t\t\treturn deleted;\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Live observe-only bridge from AgentMessage[] to the ContextItem/policy-engine layer
3
+ * (Phase 1 audit pass). This module only ever reads messages and the artifact store; it
4
+ * never mutates messages, the transcript, or artifact references. It is the first thing to
5
+ * consume the context-item.ts/context-retention.ts/policy-engine.ts contracts against live
6
+ * session state, but it does not yet change what the model sees -- see
7
+ * docs/context-management-rework/implementation-phases.md for where this sits.
8
+ *
9
+ * Scope for this pass: only `toolResult` messages become `ContextItem`s (kind
10
+ * "tool_output"). Other roles (user/assistant) are skipped; representing them is later
11
+ * work once their ContextItemKind mapping (user_instruction/approval/etc.) is designed.
12
+ *
13
+ * Retrieval-path semantics (deliberately narrower than context-retention.ts's item-level
14
+ * `hasReferencedEvidence`): a transcript ref is attached to every item as provenance
15
+ * evidence (which live message this item came from), but it never counts toward
16
+ * `HardConstraintFlags.hasAvailableRetrievalPath` on its own -- there is no live mechanism
17
+ * today for the model to fetch an older message back into context by session-entry id, so
18
+ * claiming that would overclaim retrievability (the same fail-closed principle
19
+ * `artifact_retrieve` follows). Only a resolved artifact ref (the store still has the
20
+ * payload) counts as an available retrieval path.
21
+ */
22
+ import type { AgentMessage } from "@caupulican/pi-agent-core";
23
+ import type { ToolResultMessage } from "@caupulican/pi-ai";
24
+ import type { ArtifactStore } from "./context-artifacts.ts";
25
+ import { type ContextItem } from "./context-item.ts";
26
+ import { type RetentionEligibility } from "./context-retention.ts";
27
+ import type { PolicyHardConstraintCode } from "./policy-types.ts";
28
+ export interface ContextAuditOptions {
29
+ /** Current turn index (AgentSession's own per-run counter); used as `createdAtTurn`. */
30
+ turnIndex: number;
31
+ /** Session-scoped artifact store, if one has been constructed. Read-only here. */
32
+ artifactStore?: ArtifactStore;
33
+ /** Resolve the persisted session-entry id for a toolResult message's toolCallId, if known. */
34
+ sessionEntryIdForToolCallId?: (toolCallId: string) => string | undefined;
35
+ }
36
+ export interface ContextAuditItemReport {
37
+ item: ContextItem;
38
+ /** The source toolResult message's own id and position, always available (unlike refs). */
39
+ toolCallId: string;
40
+ messageIndex: number;
41
+ /** Coarse, store-free eligibility from context-retention.ts (treats any ref as retrievable). */
42
+ retention: RetentionEligibility;
43
+ /** Store-aware hard-constraint codes for keep_raw; always empty (no evaluated action restricts it), included for reportability. */
44
+ keepRawHardConstraints: PolicyHardConstraintCode[];
45
+ /** Store-aware hard-constraint codes for pack_to_artifact; empty means no hard rejection. */
46
+ packToArtifactHardConstraints: PolicyHardConstraintCode[];
47
+ /** Store-aware hard-constraint codes for drop_from_prompt; empty means no hard rejection. */
48
+ dropFromPromptHardConstraints: PolicyHardConstraintCode[];
49
+ /** Store-aware hard-constraint codes for summarize; empty means no hard rejection. */
50
+ summarizeHardConstraints: PolicyHardConstraintCode[];
51
+ }
52
+ export interface ContextAuditReport {
53
+ turnIndex: number;
54
+ items: ContextAuditItemReport[];
55
+ }
56
+ /** Build the ContextItem for a single toolResult message, for direct unit testing. */
57
+ export declare function buildToolResultContextItem(message: ToolResultMessage, messageIndex: number, options: ContextAuditOptions): ContextItem;
58
+ /**
59
+ * Read-only audit pass: converts live toolResult messages into ContextItems and runs the
60
+ * existing pure retention/hard-constraint evaluators over them. Never mutates `messages`,
61
+ * the transcript, or artifact references -- deterministic given the same messages,
62
+ * `turnIndex`, and artifact-store state (not a cross-turn stability guarantee: turnIndex
63
+ * and artifact-backed createdAtTurn values are expected to change turn over turn).
64
+ */
65
+ export declare function runContextAudit(messages: AgentMessage[], options: ContextAuditOptions): ContextAuditReport;
66
+ //# sourceMappingURL=context-audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-audit.d.ts","sourceRoot":"","sources":["../../../src/core/context/context-audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAEN,KAAK,WAAW,EAGhB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAgC,KAAK,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEjG,OAAO,KAAK,EAAuC,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAEvG,MAAM,WAAW,mBAAmB;IACnC,wFAAwF;IACxF,SAAS,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,8FAA8F;IAC9F,2BAA2B,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CACzE;AAED,MAAM,WAAW,sBAAsB;IACtC,IAAI,EAAE,WAAW,CAAC;IAClB,2FAA2F;IAC3F,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,gGAAgG;IAChG,SAAS,EAAE,oBAAoB,CAAC;IAChC,mIAAmI;IACnI,sBAAsB,EAAE,wBAAwB,EAAE,CAAC;IACnD,6FAA6F;IAC7F,6BAA6B,EAAE,wBAAwB,EAAE,CAAC;IAC1D,6FAA6F;IAC7F,6BAA6B,EAAE,wBAAwB,EAAE,CAAC;IAC1D,sFAAsF;IACtF,wBAAwB,EAAE,wBAAwB,EAAE,CAAC;CACrD;AAED,MAAM,WAAW,kBAAkB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,sBAAsB,EAAE,CAAC;CAChC;AAqID,sFAAsF;AACtF,wBAAgB,0BAA0B,CACzC,OAAO,EAAE,iBAAiB,EAC1B,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,mBAAmB,GAC1B,WAAW,CAEb;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,OAAO,EAAE,mBAAmB,GAAG,kBAAkB,CAmB1G","sourcesContent":["/**\n * Live observe-only bridge from AgentMessage[] to the ContextItem/policy-engine layer\n * (Phase 1 audit pass). This module only ever reads messages and the artifact store; it\n * never mutates messages, the transcript, or artifact references. It is the first thing to\n * consume the context-item.ts/context-retention.ts/policy-engine.ts contracts against live\n * session state, but it does not yet change what the model sees -- see\n * docs/context-management-rework/implementation-phases.md for where this sits.\n *\n * Scope for this pass: only `toolResult` messages become `ContextItem`s (kind\n * \"tool_output\"). Other roles (user/assistant) are skipped; representing them is later\n * work once their ContextItemKind mapping (user_instruction/approval/etc.) is designed.\n *\n * Retrieval-path semantics (deliberately narrower than context-retention.ts's item-level\n * `hasReferencedEvidence`): a transcript ref is attached to every item as provenance\n * evidence (which live message this item came from), but it never counts toward\n * `HardConstraintFlags.hasAvailableRetrievalPath` on its own -- there is no live mechanism\n * today for the model to fetch an older message back into context by session-entry id, so\n * claiming that would overclaim retrievability (the same fail-closed principle\n * `artifact_retrieve` follows). Only a resolved artifact ref (the store still has the\n * payload) counts as an available retrieval path.\n */\n\nimport type { AgentMessage } from \"@caupulican/pi-agent-core\";\nimport type { ToolResultMessage } from \"@caupulican/pi-ai\";\nimport type { ArtifactStore } from \"./context-artifacts.ts\";\nimport {\n\ttype ContextEvidenceRef,\n\ttype ContextItem,\n\testimateByteLength,\n\testimateTokensFromText,\n} from \"./context-item.ts\";\nimport { evaluateRetentionEligibility, type RetentionEligibility } from \"./context-retention.ts\";\nimport { evaluateHardConstraints } from \"./policy-engine.ts\";\nimport type { HardConstraintFlags, PolicyFeatures, PolicyHardConstraintCode } from \"./policy-types.ts\";\n\nexport interface ContextAuditOptions {\n\t/** Current turn index (AgentSession's own per-run counter); used as `createdAtTurn`. */\n\tturnIndex: number;\n\t/** Session-scoped artifact store, if one has been constructed. Read-only here. */\n\tartifactStore?: ArtifactStore;\n\t/** Resolve the persisted session-entry id for a toolResult message's toolCallId, if known. */\n\tsessionEntryIdForToolCallId?: (toolCallId: string) => string | undefined;\n}\n\nexport interface ContextAuditItemReport {\n\titem: ContextItem;\n\t/** The source toolResult message's own id and position, always available (unlike refs). */\n\ttoolCallId: string;\n\tmessageIndex: number;\n\t/** Coarse, store-free eligibility from context-retention.ts (treats any ref as retrievable). */\n\tretention: RetentionEligibility;\n\t/** Store-aware hard-constraint codes for keep_raw; always empty (no evaluated action restricts it), included for reportability. */\n\tkeepRawHardConstraints: PolicyHardConstraintCode[];\n\t/** Store-aware hard-constraint codes for pack_to_artifact; empty means no hard rejection. */\n\tpackToArtifactHardConstraints: PolicyHardConstraintCode[];\n\t/** Store-aware hard-constraint codes for drop_from_prompt; empty means no hard rejection. */\n\tdropFromPromptHardConstraints: PolicyHardConstraintCode[];\n\t/** Store-aware hard-constraint codes for summarize; empty means no hard rejection. */\n\tsummarizeHardConstraints: PolicyHardConstraintCode[];\n}\n\nexport interface ContextAuditReport {\n\tturnIndex: number;\n\titems: ContextAuditItemReport[];\n}\n\nfunction extractToolResultArtifactId(message: ToolResultMessage): string | undefined {\n\tconst details = message.details;\n\tif (typeof details !== \"object\" || details === null) return undefined;\n\tconst artifactId = (details as { artifactId?: unknown }).artifactId;\n\treturn typeof artifactId === \"string\" ? artifactId : undefined;\n}\n\nfunction toolResultText(message: ToolResultMessage): string {\n\tconst parts: string[] = [];\n\tfor (const part of message.content) {\n\t\tif (part.type === \"text\") parts.push(part.text);\n\t}\n\treturn parts.join(\"\\n\");\n}\n\ninterface BuiltToolOutputItem {\n\titem: ContextItem;\n\t/** True only if an artifact ref was found AND resolved against a live store. */\n\thasResolvedArtifact: boolean;\n}\n\nfunction buildToolOutputItem(\n\tmessage: ToolResultMessage,\n\tmessageIndex: number,\n\toptions: ContextAuditOptions,\n): BuiltToolOutputItem {\n\tconst text = toolResultText(message);\n\tconst artifactId = extractToolResultArtifactId(message);\n\tconst sessionEntryId = options.sessionEntryIdForToolCallId?.(message.toolCallId);\n\n\tlet primaryRef: ContextEvidenceRef | undefined;\n\tlet hasResolvedArtifact = false;\n\t// createdAtTurn defaults to the current audit turn (an approximation -- see below) and\n\t// is overridden with the real capture turn when an artifact ref resolves.\n\tlet createdAtTurn = options.turnIndex;\n\tif (artifactId && options.artifactStore) {\n\t\t// Metadata-only: never loads the artifact's payload just to check resolvability and\n\t\t// grab ref fields (a per-turn audit pass over every artifact-backed result would\n\t\t// otherwise re-read every payload off disk each turn for no reason).\n\t\tconst ref = options.artifactStore.readRef(artifactId);\n\t\tif (ref) {\n\t\t\tprimaryRef = { type: \"artifact\", ref };\n\t\t\thasResolvedArtifact = true;\n\t\t\tcreatedAtTurn = ref.createdAtTurn;\n\t\t}\n\t}\n\n\tconst evidenceRefs: ContextEvidenceRef[] = [];\n\tif (sessionEntryId) {\n\t\tconst transcriptRef: ContextEvidenceRef = {\n\t\t\ttype: \"transcript\",\n\t\t\tref: { sessionEntryId, messageIndex },\n\t\t};\n\t\tif (!primaryRef) primaryRef = transcriptRef;\n\t\telse evidenceRefs.push(transcriptRef);\n\t}\n\n\tconst item: ContextItem = {\n\t\tid: `tool-output:${message.toolCallId}`,\n\t\tkind: \"tool_output\",\n\t\tretentionClass: \"ephemeral\",\n\t\tsource: \"tool\",\n\t\t// Transcript-only items (no artifact ref) have no real creation-turn source threaded\n\t\t// in yet, so this falls back to the current audit turn as an approximation --\n\t\t// deliberately not left silently wrong: no evaluator reads createdAtTurn today, but\n\t\t// any future staleness/break-even math must use the real per-item value above, not\n\t\t// this fallback, once one exists for non-artifact items too.\n\t\tcreatedAtTurn,\n\t\tsummary: `${message.toolName} tool result`,\n\t\tprimaryRef,\n\t\tevidenceRefs: evidenceRefs.length > 0 ? evidenceRefs : undefined,\n\t\ttokenEstimate: estimateTokensFromText(text),\n\t\tbyteEstimate: estimateByteLength(text),\n\t};\n\n\treturn { item, hasResolvedArtifact };\n}\n\n/**\n * Economic/probability fields are zeroed: `evaluateHardConstraints` does not read them for\n * pack_to_artifact/drop_from_prompt (only the break-even scoring math this audit\n * deliberately does not invoke would need real numbers here).\n */\nfunction buildPolicyFeatures(item: ContextItem, built: BuiltToolOutputItem, turnIndex: number): PolicyFeatures {\n\treturn {\n\t\tturnIndex,\n\t\texpectedRemainingTurns: 0,\n\t\tinputTokens: 0,\n\t\toutputTokens: 0,\n\t\tcacheReadTokens: 0,\n\t\tcacheWriteTokens: 0,\n\t\tartifactBytes: built.hasResolvedArtifact ? item.byteEstimate : 0,\n\t\tcharEstimate: item.byteEstimate,\n\t\tcalibratedTokenEstimate: item.tokenEstimate,\n\t\tpromptSection: \"volatile_tail\",\n\t\tretentionClass: item.retentionClass,\n\t\tisReproducible: true,\n\t\tisDecisionBearing: false,\n\t\tisPinned: false,\n\t\tisOpenRequirement: false,\n\t\tisLatestFailure: false,\n\t\tisCurrentDiff: false,\n\t\tprobabilityNeededAgain: 0,\n\t\tprobabilityErrorIfDropped: 0,\n\t\tretrievalCostTokens: 0,\n\t\tpackCostTokens: 0,\n\t\tretryCostTokens: 0,\n\t\tfailureCostTokens: 0,\n\t\tvalidationCostTokens: 0,\n\t};\n}\n\nfunction buildHardConstraintFlags(built: BuiltToolOutputItem, artifactStoreAvailable: boolean): HardConstraintFlags {\n\treturn {\n\t\tisApprovalOrDenial: false,\n\t\tisSafetyConstraint: false,\n\t\tisActiveBlocker: false,\n\t\tisCurrentValidationResult: false,\n\t\tisPathOrToolScope: false,\n\t\t// Only a resolved artifact counts; a transcript ref is provenance, not a live\n\t\t// retrieval mechanism -- see the module doc comment.\n\t\thasAvailableRetrievalPath: built.hasResolvedArtifact,\n\t\tartifactStoreAvailable,\n\t\thasEvidenceRefForSummary: built.item.primaryRef !== undefined || (built.item.evidenceRefs?.length ?? 0) > 0,\n\t\tpathOrToolBoundariesEnforced: true,\n\t\tvalidationAvailableAndStrong: false,\n\t\tpriorAttemptFailedForReasoningOrArchitecture: false,\n\t\tisHighImpactOrBroadMultiFileEdit: false,\n\t};\n}\n\n/** Build the ContextItem for a single toolResult message, for direct unit testing. */\nexport function buildToolResultContextItem(\n\tmessage: ToolResultMessage,\n\tmessageIndex: number,\n\toptions: ContextAuditOptions,\n): ContextItem {\n\treturn buildToolOutputItem(message, messageIndex, options).item;\n}\n\n/**\n * Read-only audit pass: converts live toolResult messages into ContextItems and runs the\n * existing pure retention/hard-constraint evaluators over them. Never mutates `messages`,\n * the transcript, or artifact references -- deterministic given the same messages,\n * `turnIndex`, and artifact-store state (not a cross-turn stability guarantee: turnIndex\n * and artifact-backed createdAtTurn values are expected to change turn over turn).\n */\nexport function runContextAudit(messages: AgentMessage[], options: ContextAuditOptions): ContextAuditReport {\n\tconst items: ContextAuditItemReport[] = [];\n\tmessages.forEach((message, messageIndex) => {\n\t\tif (message.role !== \"toolResult\") return;\n\t\tconst built = buildToolOutputItem(message, messageIndex, options);\n\t\tconst features = buildPolicyFeatures(built.item, built, options.turnIndex);\n\t\tconst flags = buildHardConstraintFlags(built, options.artifactStore !== undefined);\n\t\titems.push({\n\t\t\titem: built.item,\n\t\t\ttoolCallId: message.toolCallId,\n\t\t\tmessageIndex,\n\t\t\tretention: evaluateRetentionEligibility(built.item),\n\t\t\tkeepRawHardConstraints: evaluateHardConstraints(\"keep_raw\", features, flags),\n\t\t\tpackToArtifactHardConstraints: evaluateHardConstraints(\"pack_to_artifact\", features, flags),\n\t\t\tdropFromPromptHardConstraints: evaluateHardConstraints(\"drop_from_prompt\", features, flags),\n\t\t\tsummarizeHardConstraints: evaluateHardConstraints(\"summarize\", features, flags),\n\t\t});\n\t});\n\treturn { turnIndex: options.turnIndex, items };\n}\n"]}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Live observe-only bridge from AgentMessage[] to the ContextItem/policy-engine layer
3
+ * (Phase 1 audit pass). This module only ever reads messages and the artifact store; it
4
+ * never mutates messages, the transcript, or artifact references. It is the first thing to
5
+ * consume the context-item.ts/context-retention.ts/policy-engine.ts contracts against live
6
+ * session state, but it does not yet change what the model sees -- see
7
+ * docs/context-management-rework/implementation-phases.md for where this sits.
8
+ *
9
+ * Scope for this pass: only `toolResult` messages become `ContextItem`s (kind
10
+ * "tool_output"). Other roles (user/assistant) are skipped; representing them is later
11
+ * work once their ContextItemKind mapping (user_instruction/approval/etc.) is designed.
12
+ *
13
+ * Retrieval-path semantics (deliberately narrower than context-retention.ts's item-level
14
+ * `hasReferencedEvidence`): a transcript ref is attached to every item as provenance
15
+ * evidence (which live message this item came from), but it never counts toward
16
+ * `HardConstraintFlags.hasAvailableRetrievalPath` on its own -- there is no live mechanism
17
+ * today for the model to fetch an older message back into context by session-entry id, so
18
+ * claiming that would overclaim retrievability (the same fail-closed principle
19
+ * `artifact_retrieve` follows). Only a resolved artifact ref (the store still has the
20
+ * payload) counts as an available retrieval path.
21
+ */
22
+ import { estimateByteLength, estimateTokensFromText, } from "./context-item.js";
23
+ import { evaluateRetentionEligibility } from "./context-retention.js";
24
+ import { evaluateHardConstraints } from "./policy-engine.js";
25
+ function extractToolResultArtifactId(message) {
26
+ const details = message.details;
27
+ if (typeof details !== "object" || details === null)
28
+ return undefined;
29
+ const artifactId = details.artifactId;
30
+ return typeof artifactId === "string" ? artifactId : undefined;
31
+ }
32
+ function toolResultText(message) {
33
+ const parts = [];
34
+ for (const part of message.content) {
35
+ if (part.type === "text")
36
+ parts.push(part.text);
37
+ }
38
+ return parts.join("\n");
39
+ }
40
+ function buildToolOutputItem(message, messageIndex, options) {
41
+ const text = toolResultText(message);
42
+ const artifactId = extractToolResultArtifactId(message);
43
+ const sessionEntryId = options.sessionEntryIdForToolCallId?.(message.toolCallId);
44
+ let primaryRef;
45
+ let hasResolvedArtifact = false;
46
+ // createdAtTurn defaults to the current audit turn (an approximation -- see below) and
47
+ // is overridden with the real capture turn when an artifact ref resolves.
48
+ let createdAtTurn = options.turnIndex;
49
+ if (artifactId && options.artifactStore) {
50
+ // Metadata-only: never loads the artifact's payload just to check resolvability and
51
+ // grab ref fields (a per-turn audit pass over every artifact-backed result would
52
+ // otherwise re-read every payload off disk each turn for no reason).
53
+ const ref = options.artifactStore.readRef(artifactId);
54
+ if (ref) {
55
+ primaryRef = { type: "artifact", ref };
56
+ hasResolvedArtifact = true;
57
+ createdAtTurn = ref.createdAtTurn;
58
+ }
59
+ }
60
+ const evidenceRefs = [];
61
+ if (sessionEntryId) {
62
+ const transcriptRef = {
63
+ type: "transcript",
64
+ ref: { sessionEntryId, messageIndex },
65
+ };
66
+ if (!primaryRef)
67
+ primaryRef = transcriptRef;
68
+ else
69
+ evidenceRefs.push(transcriptRef);
70
+ }
71
+ const item = {
72
+ id: `tool-output:${message.toolCallId}`,
73
+ kind: "tool_output",
74
+ retentionClass: "ephemeral",
75
+ source: "tool",
76
+ // Transcript-only items (no artifact ref) have no real creation-turn source threaded
77
+ // in yet, so this falls back to the current audit turn as an approximation --
78
+ // deliberately not left silently wrong: no evaluator reads createdAtTurn today, but
79
+ // any future staleness/break-even math must use the real per-item value above, not
80
+ // this fallback, once one exists for non-artifact items too.
81
+ createdAtTurn,
82
+ summary: `${message.toolName} tool result`,
83
+ primaryRef,
84
+ evidenceRefs: evidenceRefs.length > 0 ? evidenceRefs : undefined,
85
+ tokenEstimate: estimateTokensFromText(text),
86
+ byteEstimate: estimateByteLength(text),
87
+ };
88
+ return { item, hasResolvedArtifact };
89
+ }
90
+ /**
91
+ * Economic/probability fields are zeroed: `evaluateHardConstraints` does not read them for
92
+ * pack_to_artifact/drop_from_prompt (only the break-even scoring math this audit
93
+ * deliberately does not invoke would need real numbers here).
94
+ */
95
+ function buildPolicyFeatures(item, built, turnIndex) {
96
+ return {
97
+ turnIndex,
98
+ expectedRemainingTurns: 0,
99
+ inputTokens: 0,
100
+ outputTokens: 0,
101
+ cacheReadTokens: 0,
102
+ cacheWriteTokens: 0,
103
+ artifactBytes: built.hasResolvedArtifact ? item.byteEstimate : 0,
104
+ charEstimate: item.byteEstimate,
105
+ calibratedTokenEstimate: item.tokenEstimate,
106
+ promptSection: "volatile_tail",
107
+ retentionClass: item.retentionClass,
108
+ isReproducible: true,
109
+ isDecisionBearing: false,
110
+ isPinned: false,
111
+ isOpenRequirement: false,
112
+ isLatestFailure: false,
113
+ isCurrentDiff: false,
114
+ probabilityNeededAgain: 0,
115
+ probabilityErrorIfDropped: 0,
116
+ retrievalCostTokens: 0,
117
+ packCostTokens: 0,
118
+ retryCostTokens: 0,
119
+ failureCostTokens: 0,
120
+ validationCostTokens: 0,
121
+ };
122
+ }
123
+ function buildHardConstraintFlags(built, artifactStoreAvailable) {
124
+ return {
125
+ isApprovalOrDenial: false,
126
+ isSafetyConstraint: false,
127
+ isActiveBlocker: false,
128
+ isCurrentValidationResult: false,
129
+ isPathOrToolScope: false,
130
+ // Only a resolved artifact counts; a transcript ref is provenance, not a live
131
+ // retrieval mechanism -- see the module doc comment.
132
+ hasAvailableRetrievalPath: built.hasResolvedArtifact,
133
+ artifactStoreAvailable,
134
+ hasEvidenceRefForSummary: built.item.primaryRef !== undefined || (built.item.evidenceRefs?.length ?? 0) > 0,
135
+ pathOrToolBoundariesEnforced: true,
136
+ validationAvailableAndStrong: false,
137
+ priorAttemptFailedForReasoningOrArchitecture: false,
138
+ isHighImpactOrBroadMultiFileEdit: false,
139
+ };
140
+ }
141
+ /** Build the ContextItem for a single toolResult message, for direct unit testing. */
142
+ export function buildToolResultContextItem(message, messageIndex, options) {
143
+ return buildToolOutputItem(message, messageIndex, options).item;
144
+ }
145
+ /**
146
+ * Read-only audit pass: converts live toolResult messages into ContextItems and runs the
147
+ * existing pure retention/hard-constraint evaluators over them. Never mutates `messages`,
148
+ * the transcript, or artifact references -- deterministic given the same messages,
149
+ * `turnIndex`, and artifact-store state (not a cross-turn stability guarantee: turnIndex
150
+ * and artifact-backed createdAtTurn values are expected to change turn over turn).
151
+ */
152
+ export function runContextAudit(messages, options) {
153
+ const items = [];
154
+ messages.forEach((message, messageIndex) => {
155
+ if (message.role !== "toolResult")
156
+ return;
157
+ const built = buildToolOutputItem(message, messageIndex, options);
158
+ const features = buildPolicyFeatures(built.item, built, options.turnIndex);
159
+ const flags = buildHardConstraintFlags(built, options.artifactStore !== undefined);
160
+ items.push({
161
+ item: built.item,
162
+ toolCallId: message.toolCallId,
163
+ messageIndex,
164
+ retention: evaluateRetentionEligibility(built.item),
165
+ keepRawHardConstraints: evaluateHardConstraints("keep_raw", features, flags),
166
+ packToArtifactHardConstraints: evaluateHardConstraints("pack_to_artifact", features, flags),
167
+ dropFromPromptHardConstraints: evaluateHardConstraints("drop_from_prompt", features, flags),
168
+ summarizeHardConstraints: evaluateHardConstraints("summarize", features, flags),
169
+ });
170
+ });
171
+ return { turnIndex: options.turnIndex, items };
172
+ }
173
+ //# sourceMappingURL=context-audit.js.map