@animalabs/agent-framework 0.21.1 → 0.22.1

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 (348) hide show
  1. package/README.md +54 -4
  2. package/dist/src/agent.d.ts +17 -4
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +105 -43
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/api/server.d.ts.map +1 -1
  7. package/dist/src/api/server.js +41 -4
  8. package/dist/src/api/server.js.map +1 -1
  9. package/dist/src/code-execution/py-runner.d.ts +22 -1
  10. package/dist/src/code-execution/py-runner.d.ts.map +1 -1
  11. package/dist/src/code-execution/py-runner.js +64 -9
  12. package/dist/src/code-execution/py-runner.js.map +1 -1
  13. package/dist/src/code-execution/runtime-py.d.ts +4 -0
  14. package/dist/src/code-execution/runtime-py.d.ts.map +1 -1
  15. package/dist/src/code-execution/runtime-py.js +215 -38
  16. package/dist/src/code-execution/runtime-py.js.map +1 -1
  17. package/dist/src/framework.d.ts +569 -59
  18. package/dist/src/framework.d.ts.map +1 -1
  19. package/dist/src/framework.js +3091 -542
  20. package/dist/src/framework.js.map +1 -1
  21. package/dist/src/gate/event-gate.d.ts +21 -0
  22. package/dist/src/gate/event-gate.d.ts.map +1 -1
  23. package/dist/src/gate/event-gate.js +40 -7
  24. package/dist/src/gate/event-gate.js.map +1 -1
  25. package/dist/src/index.d.ts +7 -2
  26. package/dist/src/index.d.ts.map +1 -1
  27. package/dist/src/index.js +4 -0
  28. package/dist/src/index.js.map +1 -1
  29. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  30. package/dist/src/mcpl/channel-registry.js +26 -3
  31. package/dist/src/mcpl/channel-registry.js.map +1 -1
  32. package/dist/src/mcpl/conversation-router.d.ts +28 -1
  33. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  34. package/dist/src/mcpl/conversation-router.js +17 -0
  35. package/dist/src/mcpl/conversation-router.js.map +1 -1
  36. package/dist/src/mcpl/feature-set-manager.d.ts +11 -0
  37. package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
  38. package/dist/src/mcpl/feature-set-manager.js +31 -7
  39. package/dist/src/mcpl/feature-set-manager.js.map +1 -1
  40. package/dist/src/mcpl/index.d.ts +4 -0
  41. package/dist/src/mcpl/index.d.ts.map +1 -1
  42. package/dist/src/mcpl/index.js +1 -0
  43. package/dist/src/mcpl/index.js.map +1 -1
  44. package/dist/src/mcpl/prose-stream-router.d.ts +27 -6
  45. package/dist/src/mcpl/prose-stream-router.d.ts.map +1 -1
  46. package/dist/src/mcpl/prose-stream-router.js +85 -22
  47. package/dist/src/mcpl/prose-stream-router.js.map +1 -1
  48. package/dist/src/mcpl/push-coalescer.d.ts +32 -4
  49. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  50. package/dist/src/mcpl/push-coalescer.js +46 -13
  51. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  52. package/dist/src/mcpl/push-handler.d.ts +5 -3
  53. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  54. package/dist/src/mcpl/push-handler.js +41 -11
  55. package/dist/src/mcpl/push-handler.js.map +1 -1
  56. package/dist/src/mcpl/reference-fetcher.d.ts +2 -1
  57. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  58. package/dist/src/mcpl/reference-fetcher.js +20 -9
  59. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  60. package/dist/src/mcpl/references.d.ts +29 -6
  61. package/dist/src/mcpl/references.d.ts.map +1 -1
  62. package/dist/src/mcpl/references.js +57 -16
  63. package/dist/src/mcpl/references.js.map +1 -1
  64. package/dist/src/mcpl/server-connection.d.ts +32 -1
  65. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  66. package/dist/src/mcpl/server-connection.js +76 -11
  67. package/dist/src/mcpl/server-connection.js.map +1 -1
  68. package/dist/src/mcpl/server-registry.d.ts +5 -2
  69. package/dist/src/mcpl/server-registry.d.ts.map +1 -1
  70. package/dist/src/mcpl/server-registry.js +25 -7
  71. package/dist/src/mcpl/server-registry.js.map +1 -1
  72. package/dist/src/mcpl/tool-lifecycle.d.ts +52 -2
  73. package/dist/src/mcpl/tool-lifecycle.d.ts.map +1 -1
  74. package/dist/src/mcpl/tool-lifecycle.js +47 -1
  75. package/dist/src/mcpl/tool-lifecycle.js.map +1 -1
  76. package/dist/src/mcpl/tool-pattern-check.d.ts +51 -0
  77. package/dist/src/mcpl/tool-pattern-check.d.ts.map +1 -0
  78. package/dist/src/mcpl/tool-pattern-check.js +79 -0
  79. package/dist/src/mcpl/tool-pattern-check.js.map +1 -0
  80. package/dist/src/mcpl/types.d.ts +15 -5
  81. package/dist/src/mcpl/types.d.ts.map +1 -1
  82. package/dist/src/mcpl/types.js.map +1 -1
  83. package/dist/src/mcpl/visible-content.d.ts +39 -0
  84. package/dist/src/mcpl/visible-content.d.ts.map +1 -0
  85. package/dist/src/mcpl/visible-content.js +54 -0
  86. package/dist/src/mcpl/visible-content.js.map +1 -0
  87. package/dist/src/module-registry.d.ts +90 -6
  88. package/dist/src/module-registry.d.ts.map +1 -1
  89. package/dist/src/module-registry.js +260 -18
  90. package/dist/src/module-registry.js.map +1 -1
  91. package/dist/src/modules/workspace/branch-intent.d.ts +72 -0
  92. package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -0
  93. package/dist/src/modules/workspace/branch-intent.js +83 -0
  94. package/dist/src/modules/workspace/branch-intent.js.map +1 -0
  95. package/dist/src/modules/workspace/disk-agreement.d.ts +167 -0
  96. package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -0
  97. package/dist/src/modules/workspace/disk-agreement.js +259 -0
  98. package/dist/src/modules/workspace/disk-agreement.js.map +1 -0
  99. package/dist/src/modules/workspace/effects.d.ts +84 -0
  100. package/dist/src/modules/workspace/effects.d.ts.map +1 -0
  101. package/dist/src/modules/workspace/effects.js +368 -0
  102. package/dist/src/modules/workspace/effects.js.map +1 -0
  103. package/dist/src/modules/workspace/index.d.ts +155 -17
  104. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  105. package/dist/src/modules/workspace/index.js +1101 -424
  106. package/dist/src/modules/workspace/index.js.map +1 -1
  107. package/dist/src/modules/workspace/observe.d.ts +288 -0
  108. package/dist/src/modules/workspace/observe.d.ts.map +1 -0
  109. package/dist/src/modules/workspace/observe.js +825 -0
  110. package/dist/src/modules/workspace/observe.js.map +1 -0
  111. package/dist/src/modules/workspace/reconcile.d.ts +238 -0
  112. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -0
  113. package/dist/src/modules/workspace/reconcile.js +909 -0
  114. package/dist/src/modules/workspace/reconcile.js.map +1 -0
  115. package/dist/src/modules/workspace/sync.d.ts +6 -59
  116. package/dist/src/modules/workspace/sync.d.ts.map +1 -1
  117. package/dist/src/modules/workspace/sync.js +6 -257
  118. package/dist/src/modules/workspace/sync.js.map +1 -1
  119. package/dist/src/modules/workspace/types.d.ts +77 -10
  120. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  121. package/dist/src/modules/workspace/types.js.map +1 -1
  122. package/dist/src/operator-log.d.ts +41 -0
  123. package/dist/src/operator-log.d.ts.map +1 -1
  124. package/dist/src/operator-log.js.map +1 -1
  125. package/dist/src/provider-waits.d.ts +216 -0
  126. package/dist/src/provider-waits.d.ts.map +1 -0
  127. package/dist/src/provider-waits.js +482 -0
  128. package/dist/src/provider-waits.js.map +1 -0
  129. package/dist/src/record-journal.d.ts +127 -0
  130. package/dist/src/record-journal.d.ts.map +1 -0
  131. package/dist/src/record-journal.js +202 -0
  132. package/dist/src/record-journal.js.map +1 -0
  133. package/dist/src/refusal-reactions.d.ts +14 -12
  134. package/dist/src/refusal-reactions.d.ts.map +1 -1
  135. package/dist/src/refusal-reactions.js +14 -12
  136. package/dist/src/refusal-reactions.js.map +1 -1
  137. package/dist/src/shutdown.d.ts +13 -0
  138. package/dist/src/shutdown.d.ts.map +1 -0
  139. package/dist/src/shutdown.js +46 -0
  140. package/dist/src/shutdown.js.map +1 -0
  141. package/dist/src/silent-heartbeat.d.ts +74 -0
  142. package/dist/src/silent-heartbeat.d.ts.map +1 -0
  143. package/dist/src/silent-heartbeat.js +77 -0
  144. package/dist/src/silent-heartbeat.js.map +1 -0
  145. package/dist/src/tool-presentation.d.ts +50 -0
  146. package/dist/src/tool-presentation.d.ts.map +1 -0
  147. package/dist/src/tool-presentation.js +235 -0
  148. package/dist/src/tool-presentation.js.map +1 -0
  149. package/dist/src/tool-result-guard.d.ts +142 -6
  150. package/dist/src/tool-result-guard.d.ts.map +1 -1
  151. package/dist/src/tool-result-guard.js +357 -36
  152. package/dist/src/tool-result-guard.js.map +1 -1
  153. package/dist/src/tool-result-history.d.ts +13 -0
  154. package/dist/src/tool-result-history.d.ts.map +1 -1
  155. package/dist/src/tool-result-history.js +21 -1
  156. package/dist/src/tool-result-history.js.map +1 -1
  157. package/dist/src/types/agent.d.ts +18 -2
  158. package/dist/src/types/agent.d.ts.map +1 -1
  159. package/dist/src/types/events.d.ts +16 -0
  160. package/dist/src/types/events.d.ts.map +1 -1
  161. package/dist/src/types/framework.d.ts +12 -0
  162. package/dist/src/types/framework.d.ts.map +1 -1
  163. package/dist/src/types/module.d.ts +26 -4
  164. package/dist/src/types/module.d.ts.map +1 -1
  165. package/dist/src/types/trace.d.ts +87 -3
  166. package/dist/src/types/trace.d.ts.map +1 -1
  167. package/dist/test/batched-wake-locus.test.d.ts +2 -0
  168. package/dist/test/batched-wake-locus.test.d.ts.map +1 -0
  169. package/dist/test/batched-wake-locus.test.js +166 -0
  170. package/dist/test/batched-wake-locus.test.js.map +1 -0
  171. package/dist/test/code-execution.test.js +386 -2
  172. package/dist/test/code-execution.test.js.map +1 -1
  173. package/dist/test/component-defaults.test.d.ts +2 -0
  174. package/dist/test/component-defaults.test.d.ts.map +1 -0
  175. package/dist/test/component-defaults.test.js +54 -0
  176. package/dist/test/component-defaults.test.js.map +1 -0
  177. package/dist/test/conversation-routing.test.js +57 -1
  178. package/dist/test/conversation-routing.test.js.map +1 -1
  179. package/dist/test/discord-awareness-outbox.test.js +3 -0
  180. package/dist/test/discord-awareness-outbox.test.js.map +1 -1
  181. package/dist/test/ephemeral-settle.test.js +3 -0
  182. package/dist/test/ephemeral-settle.test.js.map +1 -1
  183. package/dist/test/event-gate.test.js +118 -0
  184. package/dist/test/event-gate.test.js.map +1 -1
  185. package/dist/test/explicit-prose-routing.test.js +975 -0
  186. package/dist/test/explicit-prose-routing.test.js.map +1 -1
  187. package/dist/test/framework.test.js +160 -1
  188. package/dist/test/framework.test.js.map +1 -1
  189. package/dist/test/helpers/coalescing-fixture.d.ts.map +1 -1
  190. package/dist/test/helpers/coalescing-fixture.js +3 -2
  191. package/dist/test/helpers/coalescing-fixture.js.map +1 -1
  192. package/dist/test/helpers/pytc-stop-rows.d.ts +11 -0
  193. package/dist/test/helpers/pytc-stop-rows.d.ts.map +1 -0
  194. package/dist/test/helpers/pytc-stop-rows.js +143 -0
  195. package/dist/test/helpers/pytc-stop-rows.js.map +1 -0
  196. package/dist/test/inference-exhaustion-rewind.test.js +142 -14
  197. package/dist/test/inference-exhaustion-rewind.test.js.map +1 -1
  198. package/dist/test/inference-failure-bounds.test.d.ts +2 -0
  199. package/dist/test/inference-failure-bounds.test.d.ts.map +1 -0
  200. package/dist/test/inference-failure-bounds.test.js +1201 -0
  201. package/dist/test/inference-failure-bounds.test.js.map +1 -0
  202. package/dist/test/late-events-after-stop.test.d.ts +2 -0
  203. package/dist/test/late-events-after-stop.test.d.ts.map +1 -0
  204. package/dist/test/late-events-after-stop.test.js +122 -0
  205. package/dist/test/late-events-after-stop.test.js.map +1 -0
  206. package/dist/test/mcpl-awareness-barrier.test.js +141 -0
  207. package/dist/test/mcpl-awareness-barrier.test.js.map +1 -1
  208. package/dist/test/mcpl-coalescing.test.js +97 -22
  209. package/dist/test/mcpl-coalescing.test.js.map +1 -1
  210. package/dist/test/mcpl-config-diagnostics.test.d.ts +2 -0
  211. package/dist/test/mcpl-config-diagnostics.test.d.ts.map +1 -0
  212. package/dist/test/mcpl-config-diagnostics.test.js +452 -0
  213. package/dist/test/mcpl-config-diagnostics.test.js.map +1 -0
  214. package/dist/test/mcpl-empty-content.test.d.ts +2 -0
  215. package/dist/test/mcpl-empty-content.test.d.ts.map +1 -0
  216. package/dist/test/mcpl-empty-content.test.js +368 -0
  217. package/dist/test/mcpl-empty-content.test.js.map +1 -0
  218. package/dist/test/mcpl-manifest-changed.test.js +2 -1
  219. package/dist/test/mcpl-manifest-changed.test.js.map +1 -1
  220. package/dist/test/mcpl-reference-fetcher.test.js +232 -4
  221. package/dist/test/mcpl-reference-fetcher.test.js.map +1 -1
  222. package/dist/test/mcpl-reference-fields.test.js +69 -2
  223. package/dist/test/mcpl-reference-fields.test.js.map +1 -1
  224. package/dist/test/mcpl-tool-lifecycle-emitter.test.js +106 -1
  225. package/dist/test/mcpl-tool-lifecycle-emitter.test.js.map +1 -1
  226. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts +2 -0
  227. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts.map +1 -0
  228. package/dist/test/mcpl-tool-lifecycle-script.test.js +288 -0
  229. package/dist/test/mcpl-tool-lifecycle-script.test.js.map +1 -0
  230. package/dist/test/mcpl-ws-transport.test.js +103 -0
  231. package/dist/test/mcpl-ws-transport.test.js.map +1 -1
  232. package/dist/test/ops-alert.test.js +2 -1
  233. package/dist/test/ops-alert.test.js.map +1 -1
  234. package/dist/test/placed-reaction-baseline.test.d.ts +2 -0
  235. package/dist/test/placed-reaction-baseline.test.d.ts.map +1 -0
  236. package/dist/test/placed-reaction-baseline.test.js +312 -0
  237. package/dist/test/placed-reaction-baseline.test.js.map +1 -0
  238. package/dist/test/prose-stream-router.test.js +41 -0
  239. package/dist/test/prose-stream-router.test.js.map +1 -1
  240. package/dist/test/provider-acceleration-cooldown.test.js +5 -1
  241. package/dist/test/provider-acceleration-cooldown.test.js.map +1 -1
  242. package/dist/test/provider-waits.test.d.ts +2 -0
  243. package/dist/test/provider-waits.test.d.ts.map +1 -0
  244. package/dist/test/provider-waits.test.js +2196 -0
  245. package/dist/test/provider-waits.test.js.map +1 -0
  246. package/dist/test/reaction-baseline-platform.test.d.ts +2 -0
  247. package/dist/test/reaction-baseline-platform.test.d.ts.map +1 -0
  248. package/dist/test/reaction-baseline-platform.test.js +124 -0
  249. package/dist/test/reaction-baseline-platform.test.js.map +1 -0
  250. package/dist/test/record-journal.test.d.ts +6 -0
  251. package/dist/test/record-journal.test.d.ts.map +1 -0
  252. package/dist/test/record-journal.test.js +269 -0
  253. package/dist/test/record-journal.test.js.map +1 -0
  254. package/dist/test/refusal-reaction-baseline.test.js +10 -3
  255. package/dist/test/refusal-reaction-baseline.test.js.map +1 -1
  256. package/dist/test/save-recent-image.test.js +240 -2
  257. package/dist/test/save-recent-image.test.js.map +1 -1
  258. package/dist/test/shutdown-errors.test.d.ts +2 -0
  259. package/dist/test/shutdown-errors.test.d.ts.map +1 -0
  260. package/dist/test/shutdown-errors.test.js +662 -0
  261. package/dist/test/shutdown-errors.test.js.map +1 -0
  262. package/dist/test/silent-heartbeat-rows.test.d.ts +2 -0
  263. package/dist/test/silent-heartbeat-rows.test.d.ts.map +1 -0
  264. package/dist/test/silent-heartbeat-rows.test.js +451 -0
  265. package/dist/test/silent-heartbeat-rows.test.js.map +1 -0
  266. package/dist/test/silent-heartbeat.test.js +49 -38
  267. package/dist/test/silent-heartbeat.test.js.map +1 -1
  268. package/dist/test/stop-with-work-in-flight.test.d.ts +23 -0
  269. package/dist/test/stop-with-work-in-flight.test.d.ts.map +1 -0
  270. package/dist/test/stop-with-work-in-flight.test.js +672 -0
  271. package/dist/test/stop-with-work-in-flight.test.js.map +1 -0
  272. package/dist/test/surgery-marks-receipt.test.d.ts +2 -0
  273. package/dist/test/surgery-marks-receipt.test.d.ts.map +1 -0
  274. package/dist/test/surgery-marks-receipt.test.js +540 -0
  275. package/dist/test/surgery-marks-receipt.test.js.map +1 -0
  276. package/dist/test/tool-batch-hook.test.d.ts +8 -0
  277. package/dist/test/tool-batch-hook.test.d.ts.map +1 -0
  278. package/dist/test/tool-batch-hook.test.js +275 -0
  279. package/dist/test/tool-batch-hook.test.js.map +1 -0
  280. package/dist/test/tool-presentation.test.d.ts +2 -0
  281. package/dist/test/tool-presentation.test.d.ts.map +1 -0
  282. package/dist/test/tool-presentation.test.js +268 -0
  283. package/dist/test/tool-presentation.test.js.map +1 -0
  284. package/dist/test/tool-result-guard.test.js +2545 -19
  285. package/dist/test/tool-result-guard.test.js.map +1 -1
  286. package/dist/test/tool-result-history.test.js +2 -1
  287. package/dist/test/tool-result-history.test.js.map +1 -1
  288. package/dist/test/turn-trigger-provenance.test.js +35 -0
  289. package/dist/test/turn-trigger-provenance.test.js.map +1 -1
  290. package/dist/test/unconvertible-tool-result.test.d.ts +2 -0
  291. package/dist/test/unconvertible-tool-result.test.d.ts.map +1 -0
  292. package/dist/test/unconvertible-tool-result.test.js +358 -0
  293. package/dist/test/unconvertible-tool-result.test.js.map +1 -0
  294. package/dist/test/workspace-materialize-freshness.test.d.ts +16 -0
  295. package/dist/test/workspace-materialize-freshness.test.d.ts.map +1 -0
  296. package/dist/test/workspace-materialize-freshness.test.js +252 -0
  297. package/dist/test/workspace-materialize-freshness.test.js.map +1 -0
  298. package/dist/test/workspace-three-way.test.d.ts +12 -0
  299. package/dist/test/workspace-three-way.test.d.ts.map +1 -0
  300. package/dist/test/workspace-three-way.test.js +3194 -0
  301. package/dist/test/workspace-three-way.test.js.map +1 -0
  302. package/dist/tsconfig.tsbuildinfo +1 -1
  303. package/package.json +2 -2
  304. package/src/agent.ts +124 -45
  305. package/src/api/server.ts +43 -4
  306. package/src/code-execution/py-runner.ts +66 -9
  307. package/src/code-execution/runtime-py.ts +215 -38
  308. package/src/framework.ts +3191 -544
  309. package/src/gate/event-gate.ts +59 -7
  310. package/src/index.ts +18 -2
  311. package/src/mcpl/channel-registry.ts +24 -3
  312. package/src/mcpl/conversation-router.ts +34 -1
  313. package/src/mcpl/feature-set-manager.ts +36 -11
  314. package/src/mcpl/index.ts +4 -0
  315. package/src/mcpl/prose-stream-router.ts +79 -23
  316. package/src/mcpl/push-coalescer.ts +62 -13
  317. package/src/mcpl/push-handler.ts +41 -12
  318. package/src/mcpl/reference-fetcher.ts +18 -8
  319. package/src/mcpl/references.ts +57 -13
  320. package/src/mcpl/server-connection.ts +95 -13
  321. package/src/mcpl/server-registry.ts +24 -7
  322. package/src/mcpl/tool-lifecycle.ts +77 -2
  323. package/src/mcpl/tool-pattern-check.ts +106 -0
  324. package/src/mcpl/types.ts +15 -5
  325. package/src/mcpl/visible-content.ts +57 -0
  326. package/src/module-registry.ts +269 -25
  327. package/src/modules/workspace/branch-intent.ts +123 -0
  328. package/src/modules/workspace/disk-agreement.ts +325 -0
  329. package/src/modules/workspace/effects.ts +362 -0
  330. package/src/modules/workspace/index.ts +1098 -446
  331. package/src/modules/workspace/observe.ts +865 -0
  332. package/src/modules/workspace/reconcile.ts +1157 -0
  333. package/src/modules/workspace/sync.ts +6 -336
  334. package/src/modules/workspace/types.ts +75 -10
  335. package/src/operator-log.ts +30 -0
  336. package/src/provider-waits.ts +523 -0
  337. package/src/record-journal.ts +246 -0
  338. package/src/refusal-reactions.ts +14 -12
  339. package/src/shutdown.ts +49 -0
  340. package/src/silent-heartbeat.ts +141 -0
  341. package/src/tool-presentation.ts +188 -0
  342. package/src/tool-result-guard.ts +384 -34
  343. package/src/tool-result-history.ts +25 -1
  344. package/src/types/agent.ts +18 -2
  345. package/src/types/events.ts +16 -0
  346. package/src/types/framework.ts +12 -0
  347. package/src/types/module.ts +27 -4
  348. package/src/types/trace.ts +94 -3
@@ -0,0 +1,188 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { fchmodSync, closeSync, fstatSync, lstatSync, openSync, readSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
3
+ import type { ToolDefinition, ToolResult } from './types/events.js';
4
+
5
+ export interface ToolPresentationConfig {
6
+ /** Absolute path to shared editable JSON. Missing file means no overrides. */
7
+ path: string;
8
+ /** Reserved generated workspace path, e.g. board/tool-catalogue.md. */
9
+ cataloguePath: string;
10
+ /** Optional description-only profiles, bound to a registered component source. */
11
+ defaults?: { source: string; path: string }[];
12
+ }
13
+ export interface ToolOverride { description?: string; visible?: boolean }
14
+ export interface PresentationDocument { version: 1; tools: Record<string, ToolOverride> }
15
+ export interface PresentationSnapshot {
16
+ revision: string;
17
+ source: string;
18
+ cataloguePath: string;
19
+ diagnostics: string[];
20
+ available: ToolDefinition[];
21
+ advertised: ToolDefinition[];
22
+ entries: { name: string; source: string; visible: boolean; originalDescription: string; description: string; descriptionSource?: string; inputSchema: ToolDefinition['inputSchema'] }[];
23
+ }
24
+ const MAX_BYTES = 256 * 1024;
25
+ const protectedNames = new Set(['set_tool_visibility', 'set_tool_description', 'workspace--read']);
26
+ export const isPresentationTool = (name: string): boolean => name === 'set_tool_visibility' || name === 'set_tool_description';
27
+ const hash = (value: string): string => createHash('sha256').update(value).digest('hex');
28
+
29
+ export function parsePresentation(raw: string): PresentationDocument {
30
+ const doc = JSON.parse(raw);
31
+ if (!doc || doc.version !== 1 || !doc.tools || typeof doc.tools !== 'object' || Array.isArray(doc.tools)
32
+ || Object.keys(doc).some(k => !['version', 'tools'].includes(k))) throw new Error('Expected {"version":1,"tools":{...}}');
33
+ for (const [name, value] of Object.entries(doc.tools)) {
34
+ if (!name || !value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`Invalid override: ${name}`);
35
+ const item = value as Record<string, unknown>;
36
+ if (Object.keys(item).some(k => !['description', 'visible'].includes(k))
37
+ || ('description' in item && (typeof item.description !== 'string' || item.description.length > 32768))
38
+ || ('visible' in item && typeof item.visible !== 'boolean')) throw new Error(`Invalid description/visibility for ${name}`);
39
+ if (protectedNames.has(name) && item.visible === false) throw new Error(`Cannot hide recovery tool ${name}`);
40
+ }
41
+ return doc;
42
+ }
43
+
44
+ export function presentationTools(path: string): ToolDefinition[] {
45
+ const signpost = ` Read the generated catalogue with workspace--read {"path":${JSON.stringify(path)},"limit":140}. Hidden tools remain available; changes affect the next newly compiled request, not an in-flight stream.`;
46
+ return [
47
+ {name:'set_tool_visibility', description:'Show or hide a tool definition. Hiding does not revoke permission.' + signpost,
48
+ inputSchema:{type:'object',properties:{name:{type:'string'},visible:{type:'boolean'}},required:['name','visible'],additionalProperties:false}},
49
+ {name:'set_tool_description', description:'Set a personal tool description; null restores the configured component default, or the installed description when none is configured.' + signpost,
50
+ inputSchema:{type:'object',properties:{name:{type:'string'},description:{type:['string','null']}},required:['name','description'],additionalProperties:false}},
51
+ ] as unknown as ToolDefinition[];
52
+ }
53
+
54
+ /** Per-agent presentation; files are the sole persistent source, independent of editor identity. */
55
+ export class ToolPresentation {
56
+ constructor(readonly config: ToolPresentationConfig) {}
57
+ private read(path = this.config.path, allowMissing = true): string {
58
+ let fd: number;
59
+ try { fd = openSync(path, 'r'); }
60
+ catch (error) { if (allowMissing && (error as NodeJS.ErrnoException).code === 'ENOENT') return '{"version":1,"tools":{}}'; throw error; }
61
+ try {
62
+ const stat = fstatSync(fd);
63
+ if (!stat.isFile() || stat.size > MAX_BYTES) throw new Error('Presentation must be a regular file under 256 KiB');
64
+ const data = Buffer.alloc(MAX_BYTES + 1);
65
+ let size = 0, count = 0;
66
+ while (size < data.length && (count = readSync(fd, data, size, data.length - size, null)) > 0) size += count;
67
+ if (size > MAX_BYTES) throw new Error('Presentation exceeds 256 KiB');
68
+ return data.subarray(0,size).toString('utf8');
69
+ } finally { closeSync(fd); }
70
+ }
71
+ resolve(tools: ToolDefinition[], sources: ReadonlyMap<string, string> = new Map()): PresentationSnapshot {
72
+ let raw = '', doc: PresentationDocument = {version:1,tools:{}}, diagnostics: string[] = [];
73
+ try { raw = this.read(); doc = parsePresentation(raw); }
74
+ catch (error) { diagnostics.push(`Overrides not applied: ${String(error)}. Default visibility and component descriptions retained.`); }
75
+ const defaults = new Map<string, {description: string; path: string}>();
76
+ const configuredSources = new Set<string>();
77
+ for (const profile of this.config.defaults ?? []) {
78
+ if (configuredSources.has(profile.source)) {
79
+ diagnostics.push(`Duplicate defaults source ignored: ${profile.source}`); continue;
80
+ }
81
+ configuredSources.add(profile.source);
82
+ // Absent components contribute neither tools nor missing-file errors.
83
+ if (!tools.some(t => (sources.get(t.name) ?? 'Unattributed') === profile.source)) continue;
84
+ try {
85
+ const profileDoc = parsePresentation(this.read(profile.path, false));
86
+ if (Object.values(profileDoc.tools).some(item => Object.keys(item).some(k => k !== 'description')))
87
+ throw new Error('Component defaults may contain descriptions only; visibility belongs to the resident');
88
+ for (const tool of tools) {
89
+ if ((sources.get(tool.name) ?? 'Unattributed') !== profile.source) continue;
90
+ const item = Object.hasOwn(profileDoc.tools, tool.name) ? profileDoc.tools[tool.name] : undefined;
91
+ if (item?.description !== undefined) defaults.set(tool.name, {description:item.description,path:profile.path});
92
+ }
93
+ } catch (error) { diagnostics.push(`Component defaults not applied (${profile.source}): ${String(error)}`); }
94
+ }
95
+ const names = new Set(tools.map(t => t.name));
96
+ for (const name of Object.keys(doc.tools)) if (!names.has(name)) diagnostics.push(`Not currently available: ${name}`);
97
+ const entries = tools.map(tool => {
98
+ const override = Object.hasOwn(doc.tools, tool.name) ? doc.tools[tool.name] : undefined;
99
+ // Always retain a usable discovery signpost, even when its wording is overridden.
100
+ const componentDefault = defaults.get(tool.name);
101
+ let description = override?.description ?? componentDefault?.description ?? tool.description;
102
+ const descriptionSource = override?.description !== undefined ? this.config.path : componentDefault?.path ?? "installed component";
103
+ if (isPresentationTool(tool.name) && (override?.description !== undefined || componentDefault !== undefined))
104
+ description += `\nCatalogue: workspace--read {"path":${JSON.stringify(this.config.cataloguePath)},"limit":140}. Changes affect the next newly compiled request.`;
105
+ return {name:tool.name,source:sources.get(tool.name) ?? 'Unattributed',visible:override?.visible ?? true,originalDescription:tool.description,description,descriptionSource,inputSchema:structuredClone(tool.inputSchema)};
106
+ });
107
+ const available = tools.map((tool,i) => ({...structuredClone(tool),description:entries[i].description}));
108
+ return {revision:hash(JSON.stringify({raw,entries,diagnostics})),source:this.config.path,cataloguePath:this.config.cataloguePath,diagnostics,available,
109
+ advertised:available.filter((_,i)=>entries[i].visible),entries};
110
+ }
111
+ edit(tool: string, input: unknown, available: ToolDefinition[]): ToolResult {
112
+ const lock = this.config.path + '.lock';
113
+ let fd: number | undefined, tempFd: number | undefined, temp: string | undefined;
114
+ try {
115
+ if (!isPresentationTool(tool)) throw new Error('Unknown editing tool');
116
+ const value = input as Record<string, unknown>;
117
+ const field = tool === 'set_tool_visibility' ? 'visible' : 'description';
118
+ if (!value || typeof value !== 'object' || Array.isArray(value) || typeof value.name !== 'string'
119
+ || Object.keys(value).some(k=> !['name',field].includes(k)) || !Object.hasOwn(value,field)) throw new Error('Invalid editing arguments');
120
+ if (!available.some(t=>t.name===value.name)) throw new Error('Tool is not currently available to this agent');
121
+ if (field === 'visible' ? typeof value.visible !== 'boolean' : value.description !== null && typeof value.description !== 'string') throw new Error(`Invalid ${field}`);
122
+ fd = openSync(lock, 'wx', 0o600);
123
+ let mode = 0o600;
124
+ try { const stat = lstatSync(this.config.path); if (stat.isSymbolicLink()) throw new Error('Edit the target file directly; tool editing refuses symlinks'); mode = stat.mode & 0o777; }
125
+ catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
126
+ const before = this.read(), doc = parsePresentation(before);
127
+ const old = Object.hasOwn(doc.tools,value.name) ? doc.tools[value.name] : {};
128
+ const updated = {...old};
129
+ if (field === 'description' && value.description === null) delete updated.description;
130
+ else Object.assign(updated,{[field]:value[field]});
131
+ Object.defineProperty(doc.tools,value.name,{value:updated,enumerable:true,writable:true,configurable:true});
132
+ const next = JSON.stringify(doc,null,2)+'\n';
133
+ parsePresentation(next);
134
+ if (Buffer.byteLength(next)>MAX_BYTES) throw new Error('Presentation exceeds 256 KiB');
135
+ temp = this.config.path + '.' + randomUUID() + '.tmp';
136
+ tempFd = openSync(temp,'wx',0o600);
137
+ writeFileSync(tempFd,next);
138
+ fchmodSync(tempFd,mode); // preserve permissions on the opened inode, never a replacement path
139
+ if (this.read() !== before) throw new Error('File changed concurrently; retry after reading it');
140
+ const opened = fstatSync(tempFd), named = lstatSync(temp);
141
+ if (!named.isFile() || named.dev !== opened.dev || named.ino !== opened.ino)
142
+ throw new Error('Temporary file changed concurrently; retry after checking the directory');
143
+ renameSync(temp,this.config.path); temp=undefined;
144
+ return {success:true,data:{name:value.name,[field]:value[field],effective:'next newly compiled request',catalogue:this.config.cataloguePath}};
145
+ } catch (error) { return {success:false,isError:true,error:String(error)}; }
146
+ finally { if(tempFd!==undefined) closeSync(tempFd); if(temp) try{unlinkSync(temp);}catch{} if(fd!==undefined){closeSync(fd);unlinkSync(lock);} }
147
+ }
148
+ }
149
+ export function renderCatalogue(snapshot: PresentationSnapshot): string {
150
+ const groups = new Map<string, PresentationSnapshot['entries']>();
151
+ for (const entry of snapshot.entries) {
152
+ const group = groups.get(entry.source) ?? [];
153
+ group.push(entry); groups.set(entry.source, group);
154
+ }
155
+ const visible = snapshot.entries.filter(e => e.visible).length;
156
+ const lines: string[] = [
157
+ '# Tool catalogue — generated from currently available tools', '',
158
+ `Revision: ${snapshot.revision}`, '',
159
+ `${snapshot.entries.length} tools: ${visible} visible, ${snapshot.entries.length-visible} hidden.`,
160
+ 'Visible = supplied in new requests. Hidden = stored away, still available.',
161
+ 'These are your visibility choices, not fixed primary/secondary rankings.',
162
+ 'Copy exact names from the index; similar-sounding names may not exist.',
163
+ 'Show a hidden tool: set_tool_visibility {"name":"EXACT_NAME","visible":true}.',
164
+ `Read details: workspace--read {"path":${JSON.stringify(snapshot.cataloguePath)},"offset":LINE,"limit":LINES}.`,
165
+ 'Line references below belong to this revision; reread the index after changes.', '',
166
+ ];
167
+ for (const diagnostic of snapshot.diagnostics) lines.push('Diagnostic: '+diagnostic);
168
+ lines.push('## Index by source', '');
169
+ const index = new Map<string, number>();
170
+ for (const [source, entries] of groups) {
171
+ lines.push(`### ${source} (${entries.length})`);
172
+ for (const entry of entries) {index.set(entry.name,lines.length);lines.push('');}
173
+ lines.push('');
174
+ }
175
+ lines.push('## Full definitions', '');
176
+ for (const [source, entries] of groups) {
177
+ lines.push(`### ${source}`, '');
178
+ for (const entry of entries) {
179
+ const start = lines.length + 1;
180
+ lines.push(`#### ${entry.name} (${entry.visible?'visible':'hidden'})`, '',
181
+ ...entry.description.split('\n'), '', `Description from: ${entry.descriptionSource ?? 'installed component'}`, '', `Schema: ${JSON.stringify(entry.inputSchema)}`);
182
+ if(entry.description!==entry.originalDescription) lines.push('', 'Original description:', ...entry.originalDescription.split('\n'));
183
+ lines.push('');
184
+ lines[index.get(entry.name)!] = `- [${entry.visible?'visible':'hidden'}] ${entry.name} — details: offset ${start}, limit ${lines.length-start+1}`;
185
+ }
186
+ }
187
+ return lines.join('\n');
188
+ }