clawmini 0.0.8 → 0.0.9

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 (367) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +14 -0
  3. package/.github/workflows/release.yml +49 -0
  4. package/CHANGELOG.md +36 -0
  5. package/README.md +5 -4
  6. package/dist/adapter-discord/index.d.mts.map +1 -1
  7. package/dist/adapter-discord/index.mjs +465 -282
  8. package/dist/adapter-discord/index.mjs.map +1 -1
  9. package/dist/adapter-google-chat/index.mjs +367 -243
  10. package/dist/adapter-google-chat/index.mjs.map +1 -1
  11. package/dist/cli/index.mjs +684 -24
  12. package/dist/cli/index.mjs.map +1 -1
  13. package/dist/cli/lite.mjs +43 -13
  14. package/dist/cli/lite.mjs.map +1 -1
  15. package/dist/cli/{propose-policy.mjs → manage-policies.mjs} +270 -47
  16. package/dist/cli/manage-policies.mjs.map +1 -0
  17. package/dist/cli/run-host.d.mts +1 -0
  18. package/dist/cli/run-host.mjs +3090 -0
  19. package/dist/cli/run-host.mjs.map +1 -0
  20. package/dist/config-CPFQIGdG.mjs +57 -0
  21. package/dist/config-CPFQIGdG.mjs.map +1 -0
  22. package/dist/config-Dvl-Pov4.mjs +76 -0
  23. package/dist/config-Dvl-Pov4.mjs.map +1 -0
  24. package/dist/daemon/index.d.mts.map +1 -1
  25. package/dist/daemon/index.mjs +970 -332
  26. package/dist/daemon/index.mjs.map +1 -1
  27. package/dist/supervisor-actions-CiW56eLi.mjs +843 -0
  28. package/dist/supervisor-actions-CiW56eLi.mjs.map +1 -0
  29. package/dist/turn-log-buffer-DRgW53gl.mjs +767 -0
  30. package/dist/turn-log-buffer-DRgW53gl.mjs.map +1 -0
  31. package/dist/web/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
  32. package/dist/web/_app/immutable/chunks/BhRSsUCh.js +2 -0
  33. package/dist/web/_app/immutable/chunks/BiLeM2i1.js +1 -0
  34. package/{web/.svelte-kit/output/client/_app/immutable/chunks/CME08kGM.js → dist/web/_app/immutable/chunks/BmBj85Ll.js} +1 -1
  35. package/dist/web/_app/immutable/chunks/BrERcKAH.js +1 -0
  36. package/dist/web/_app/immutable/chunks/Bv9252RM.js +1 -0
  37. package/dist/web/_app/immutable/chunks/CIXNBPKi.js +1 -0
  38. package/dist/web/_app/immutable/chunks/DISKL3GN.js +2 -0
  39. package/dist/web/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
  40. package/dist/web/_app/immutable/chunks/DnQ3vS13.js +1 -0
  41. package/dist/web/_app/immutable/chunks/KsloHTKS.js +1 -0
  42. package/{web/.svelte-kit/output/client/_app/immutable/chunks/Ck-be5J2.js → dist/web/_app/immutable/chunks/RsHsUj-8.js} +2 -2
  43. package/dist/web/_app/immutable/chunks/{vDehDcuJ.js → wpfV79dV.js} +1 -1
  44. package/dist/web/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
  45. package/dist/web/_app/immutable/entry/start.Di0-Jhte.js +1 -0
  46. package/dist/web/_app/immutable/nodes/{0.CUGC2p-K.js → 0.DYyUA1au.js} +1 -1
  47. package/dist/web/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
  48. package/dist/web/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
  49. package/{web/.svelte-kit/output/client/_app/immutable/nodes/3.0arZe_Uf.js → dist/web/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
  50. package/dist/web/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
  51. package/dist/web/_app/immutable/nodes/{5.Bq2JzCEj.js → 5.DLbPVJY2.js} +1 -1
  52. package/dist/web/_app/version.json +1 -1
  53. package/dist/web/index.html +12 -12
  54. package/dist/workspace-oWmVh5mi.mjs +1001 -0
  55. package/dist/workspace-oWmVh5mi.mjs.map +1 -0
  56. package/docs/23_adapter_slash_autocomplete/development_log.md +19 -0
  57. package/docs/23_adapter_slash_autocomplete/notes.md +18 -0
  58. package/docs/23_adapter_slash_autocomplete/prd.md +46 -0
  59. package/docs/23_adapter_slash_autocomplete/questions.md +6 -0
  60. package/docs/23_adapter_slash_autocomplete/tickets.md +21 -0
  61. package/docs/24_subagent_job_policy_fixes/development_log.md +22 -0
  62. package/docs/24_subagent_job_policy_fixes/notes.md +28 -0
  63. package/docs/24_subagent_job_policy_fixes/prd.md +59 -0
  64. package/docs/24_subagent_job_policy_fixes/questions.md +3 -0
  65. package/docs/24_subagent_job_policy_fixes/tickets.md +49 -0
  66. package/docs/25_e2e_test_improvements/development_log.md +30 -0
  67. package/docs/25_e2e_test_improvements/notes.md +29 -0
  68. package/docs/25_e2e_test_improvements/prd.md +43 -0
  69. package/docs/25_e2e_test_improvements/questions.md +12 -0
  70. package/docs/25_e2e_test_improvements/tickets-2.md +22 -0
  71. package/docs/25_e2e_test_improvements/tickets.md +22 -0
  72. package/docs/25_policy_cwd/development_log.md +30 -0
  73. package/docs/25_policy_cwd/notes.md +28 -0
  74. package/docs/25_policy_cwd/prd.md +77 -0
  75. package/docs/25_policy_cwd/questions.md +6 -0
  76. package/docs/25_policy_cwd/tickets.md +77 -0
  77. package/docs/CLI_REFERENCE.md +3 -1
  78. package/docs/PHILOSOPHY.md +35 -0
  79. package/docs/adapter-visibility/SPEC.md +461 -0
  80. package/docs/adapter-visibility/SPEC_v2.md +202 -0
  81. package/docs/auto-update/SPEC.md +344 -0
  82. package/docs/backups/SPEC.md +296 -0
  83. package/docs/backups/clawmini.gitignore +69 -0
  84. package/docs/guides/assets/clawmini-avatar.png +0 -0
  85. package/docs/guides/backups.md +332 -0
  86. package/docs/guides/discord_adapter_setup.md +1 -1
  87. package/docs/guides/google_chat_adapter_setup.md +81 -0
  88. package/docs/unified-startup/SPEC.md +203 -0
  89. package/e2e/_helpers/test-environment.test.ts +49 -0
  90. package/e2e/_helpers/test-environment.ts +548 -0
  91. package/e2e/adapters/_google-chat-fixtures.ts +340 -0
  92. package/{src/cli/e2e → e2e/adapters}/adapter-discord.test.ts +22 -23
  93. package/e2e/adapters/adapter-google-chat-downtime.test.ts +157 -0
  94. package/e2e/adapters/adapter-google-chat-inbound.test.ts +697 -0
  95. package/e2e/adapters/adapter-google-chat-outbound.test.ts +297 -0
  96. package/e2e/adapters/adapter-google-chat-roundtrip.test.ts +56 -0
  97. package/e2e/adapters/adapter-google-chat-threads.test.ts +1078 -0
  98. package/e2e/agents/custom-api-env.test.ts +80 -0
  99. package/e2e/agents/export-lite-func.test.ts +104 -0
  100. package/e2e/agents/fallbacks.test.ts +124 -0
  101. package/e2e/agents/interrupt.test.ts +50 -0
  102. package/e2e/agents/no-reply-necessary.test.ts +57 -0
  103. package/e2e/agents/session-timeout-subagents.test.ts +76 -0
  104. package/e2e/agents/subagent-authorization.test.ts +246 -0
  105. package/e2e/agents/subagent-env.test.ts +49 -0
  106. package/e2e/agents/subagent-lifecycle.test.ts +782 -0
  107. package/e2e/agents/subagents-depth.test.ts +47 -0
  108. package/e2e/cli/agents.test.ts +176 -0
  109. package/e2e/cli/auto-update.test.ts +741 -0
  110. package/e2e/cli/basic.test.ts +44 -0
  111. package/{src/cli/e2e → e2e/cli}/export-lite.test.ts +16 -12
  112. package/e2e/cli/init-gitignore.test.ts +86 -0
  113. package/e2e/cli/init.test.ts +76 -0
  114. package/e2e/cli/messages.test.ts +363 -0
  115. package/e2e/cli/serve.test.ts +76 -0
  116. package/{src/cli/e2e → e2e/cli}/skills.test.ts +11 -10
  117. package/{src/cli/e2e → e2e/daemon}/daemon.test.ts +57 -195
  118. package/e2e/jobs/agent-jobs.test.ts +216 -0
  119. package/e2e/jobs/cron.test.ts +64 -0
  120. package/e2e/jobs/restart.test.ts +108 -0
  121. package/e2e/policies/approval-session.test.ts +69 -0
  122. package/e2e/policies/auto-create-policies-file.test.ts +35 -0
  123. package/e2e/policies/builtin-manage-policies.test.ts +184 -0
  124. package/e2e/policies/builtin-run-host.test.ts +180 -0
  125. package/e2e/policies/environment-policies.test.ts +177 -0
  126. package/e2e/policies/manage-policies.test.ts +566 -0
  127. package/e2e/policies/output-size.test.ts +98 -0
  128. package/e2e/policies/policies-context-cwd.test.ts +160 -0
  129. package/e2e/policies/relative-script-path.test.ts +60 -0
  130. package/e2e/policies/requests-show.test.ts +135 -0
  131. package/e2e/policies/requests.test.ts +208 -0
  132. package/e2e/policies/slash-policies.test.ts +308 -0
  133. package/e2e/policies/startup-cleanup.test.ts +48 -0
  134. package/e2e/routers/session-timeout.test.ts +106 -0
  135. package/e2e/routers/slash-model.test.ts +152 -0
  136. package/e2e/routers/slash-new.test.ts +50 -0
  137. package/e2e/routers/slash-restart-adapter.test.ts +96 -0
  138. package/e2e/routers/slash-restart.test.ts +114 -0
  139. package/e2e/routers/slash-shutdown.test.ts +55 -0
  140. package/e2e/routers/slash-stop.test.ts +232 -0
  141. package/e2e/routers/slash-upgrade.test.ts +88 -0
  142. package/{src/cli/e2e → e2e/sandbox}/environments.test.ts +14 -13
  143. package/eslint.config.js +6 -0
  144. package/napkin.md +1 -1
  145. package/package.json +8 -3
  146. package/src/adapter-discord/commands.test.ts +42 -0
  147. package/src/adapter-discord/commands.ts +33 -0
  148. package/src/adapter-discord/config.ts +12 -0
  149. package/src/adapter-discord/forwarder.test.ts +499 -21
  150. package/src/adapter-discord/forwarder.ts +343 -124
  151. package/src/adapter-discord/inbound-cache.test.ts +47 -0
  152. package/src/adapter-discord/inbound-cache.ts +37 -0
  153. package/src/adapter-discord/index.test.ts +67 -2
  154. package/src/adapter-discord/index.ts +84 -216
  155. package/src/adapter-discord/interactions.test.ts +54 -3
  156. package/src/adapter-discord/interactions.ts +97 -53
  157. package/src/adapter-discord/processMessage.ts +239 -0
  158. package/src/adapter-discord/state.ts +1 -0
  159. package/src/adapter-google-chat/auth.test.ts +9 -5
  160. package/src/adapter-google-chat/auth.ts +29 -23
  161. package/src/adapter-google-chat/cards.ts +7 -2
  162. package/src/adapter-google-chat/client.test.ts +37 -2
  163. package/src/adapter-google-chat/client.ts +138 -38
  164. package/src/adapter-google-chat/config.ts +19 -0
  165. package/src/adapter-google-chat/forwarder.test.ts +81 -56
  166. package/src/adapter-google-chat/forwarder.ts +394 -185
  167. package/src/adapter-google-chat/inbound-cache.test.ts +61 -0
  168. package/src/adapter-google-chat/inbound-cache.ts +36 -0
  169. package/src/adapter-google-chat/state.test.ts +1 -0
  170. package/src/adapter-google-chat/state.ts +9 -1
  171. package/src/adapter-google-chat/subscriptions.ts +8 -6
  172. package/src/cli/builtin-policies.ts +44 -0
  173. package/src/cli/commands/agents.ts +59 -5
  174. package/src/cli/commands/down.ts +54 -2
  175. package/src/cli/commands/environments.ts +8 -2
  176. package/src/cli/commands/init.ts +31 -0
  177. package/src/cli/commands/logs.ts +116 -0
  178. package/src/cli/commands/policies.ts +6 -4
  179. package/src/cli/commands/serve.test.ts +67 -0
  180. package/src/cli/commands/serve.ts +284 -0
  181. package/src/cli/commands/up.ts +122 -2
  182. package/src/cli/commands/web-api/agents.ts +3 -2
  183. package/src/cli/index.ts +4 -0
  184. package/src/cli/install-detection.test.ts +72 -0
  185. package/src/cli/install-detection.ts +48 -0
  186. package/src/cli/lite.ts +54 -22
  187. package/src/cli/manage-policies-utils.ts +104 -0
  188. package/src/cli/manage-policies.ts +291 -0
  189. package/src/cli/run-host.ts +45 -0
  190. package/src/cli/supervisor-actions.ts +267 -0
  191. package/src/cli/supervisor-control.test.ts +129 -0
  192. package/src/cli/supervisor-control.ts +155 -0
  193. package/src/cli/supervisor-pid.ts +68 -0
  194. package/src/cli/supervisor.ts +277 -0
  195. package/src/daemon/agent/agent-context.ts +11 -11
  196. package/src/daemon/agent/agent-session.ts +8 -1
  197. package/src/daemon/agent/chat-logger.test.ts +78 -9
  198. package/src/daemon/agent/chat-logger.ts +25 -5
  199. package/src/daemon/agent/turn-registry.test.ts +89 -0
  200. package/src/daemon/agent/turn-registry.ts +94 -0
  201. package/src/daemon/agent/types.ts +2 -0
  202. package/src/daemon/api/agent-policy-endpoints.ts +263 -0
  203. package/src/daemon/api/agent-router.ts +47 -126
  204. package/src/daemon/api/index.test.ts +1 -0
  205. package/src/daemon/api/policy-request.test.ts +7 -5
  206. package/src/daemon/api/router-utils.ts +6 -5
  207. package/src/daemon/api/subagent-router.ts +110 -74
  208. package/src/daemon/api/subagent-utils.test.ts +60 -0
  209. package/src/daemon/api/subagent-utils.ts +113 -87
  210. package/src/daemon/api/user-router.ts +34 -8
  211. package/src/daemon/auth.ts +1 -0
  212. package/src/daemon/cron.test.ts +62 -4
  213. package/src/daemon/cron.ts +42 -16
  214. package/src/daemon/events.ts +65 -0
  215. package/src/daemon/index.ts +24 -1
  216. package/src/daemon/message-interruption.test.ts +1 -0
  217. package/src/daemon/message-jobs.test.ts +1 -0
  218. package/src/daemon/message.ts +78 -14
  219. package/src/daemon/observation.test.ts +26 -18
  220. package/src/daemon/pending-replies.test.ts +112 -0
  221. package/src/daemon/pending-replies.ts +162 -0
  222. package/src/daemon/policy-request-service.ts +3 -1
  223. package/src/daemon/policy-utils.test.ts +66 -1
  224. package/src/daemon/policy-utils.ts +126 -1
  225. package/src/daemon/request-store.ts +31 -0
  226. package/src/daemon/routers/session-timeout.ts +4 -0
  227. package/src/daemon/routers/slash-model.test.ts +344 -0
  228. package/src/daemon/routers/slash-model.ts +207 -0
  229. package/src/daemon/routers/slash-policies.test.ts +38 -32
  230. package/src/daemon/routers/slash-policies.ts +84 -33
  231. package/src/daemon/routers/slash-restart.test.ts +69 -0
  232. package/src/daemon/routers/slash-restart.ts +36 -0
  233. package/src/daemon/routers/slash-shutdown.test.ts +50 -0
  234. package/src/daemon/routers/slash-shutdown.ts +28 -0
  235. package/src/daemon/routers/slash-upgrade.test.ts +116 -0
  236. package/src/daemon/routers/slash-upgrade.ts +76 -0
  237. package/src/daemon/routers/types.ts +7 -0
  238. package/src/daemon/routers.ts +16 -0
  239. package/src/shared/adapters/blockquote.test.ts +28 -0
  240. package/src/shared/adapters/blockquote.ts +20 -0
  241. package/src/shared/adapters/filtering.test.ts +224 -10
  242. package/src/shared/adapters/filtering.ts +95 -7
  243. package/src/shared/adapters/inbound-cache.test.ts +48 -0
  244. package/src/shared/adapters/inbound-cache.ts +54 -0
  245. package/src/shared/adapters/turn-log-buffer.ts +266 -0
  246. package/src/shared/adapters/turn-log.test.ts +389 -0
  247. package/src/shared/adapters/turn-log.ts +357 -0
  248. package/src/shared/agent-utils.ts +12 -5
  249. package/src/shared/chats.test.ts +4 -0
  250. package/src/shared/chats.ts +9 -0
  251. package/src/shared/config.ts +16 -1
  252. package/src/shared/lite.ts +76 -2
  253. package/src/shared/policies.ts +26 -0
  254. package/src/shared/template-manifest.ts +267 -0
  255. package/src/shared/utils/shell.ts +61 -0
  256. package/src/shared/version.ts +34 -0
  257. package/src/shared/workspace.test.ts +217 -0
  258. package/src/shared/workspace.ts +626 -48
  259. package/templates/environments/cladding/allowlist-domain.mjs +125 -0
  260. package/templates/environments/cladding/env.json +21 -1
  261. package/templates/environments/cladding/run-with-network.mjs +54 -0
  262. package/templates/environments/macos-proxy/allowlist-domain.mjs +95 -0
  263. package/templates/environments/macos-proxy/env.json +8 -1
  264. package/templates/environments/macos-proxy/proxy.mjs +0 -1
  265. package/templates/gemini/template.json +5 -0
  266. package/templates/gemini-claw/template.json +13 -0
  267. package/templates/skills/clawmini-requests/SKILL.md +69 -10
  268. package/templates/skills/run-host/SKILL.md +51 -0
  269. package/templates/skills/skill-creator/SKILL.md +4 -3
  270. package/templates/skills/skill-creator/scripts/validate.sh +52 -0
  271. package/tsdown.config.ts +10 -1
  272. package/vitest.config.ts +2 -2
  273. package/web/.svelte-kit/ambient.d.ts +292 -118
  274. package/web/.svelte-kit/generated/server/internal.js +1 -1
  275. package/web/.svelte-kit/output/client/.vite/manifest.json +126 -136
  276. package/web/.svelte-kit/output/client/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
  277. package/web/.svelte-kit/output/client/_app/immutable/chunks/BhRSsUCh.js +2 -0
  278. package/web/.svelte-kit/output/client/_app/immutable/chunks/BiLeM2i1.js +1 -0
  279. package/{dist/web/_app/immutable/chunks/CME08kGM.js → web/.svelte-kit/output/client/_app/immutable/chunks/BmBj85Ll.js} +1 -1
  280. package/web/.svelte-kit/output/client/_app/immutable/chunks/BrERcKAH.js +1 -0
  281. package/web/.svelte-kit/output/client/_app/immutable/chunks/Bv9252RM.js +1 -0
  282. package/web/.svelte-kit/output/client/_app/immutable/chunks/CIXNBPKi.js +1 -0
  283. package/web/.svelte-kit/output/client/_app/immutable/chunks/DISKL3GN.js +2 -0
  284. package/web/.svelte-kit/output/client/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
  285. package/web/.svelte-kit/output/client/_app/immutable/chunks/DnQ3vS13.js +1 -0
  286. package/web/.svelte-kit/output/client/_app/immutable/chunks/KsloHTKS.js +1 -0
  287. package/{dist/web/_app/immutable/chunks/Ck-be5J2.js → web/.svelte-kit/output/client/_app/immutable/chunks/RsHsUj-8.js} +2 -2
  288. package/web/.svelte-kit/output/client/_app/immutable/chunks/{vDehDcuJ.js → wpfV79dV.js} +1 -1
  289. package/web/.svelte-kit/output/client/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
  290. package/web/.svelte-kit/output/client/_app/immutable/entry/start.Di0-Jhte.js +1 -0
  291. package/web/.svelte-kit/output/client/_app/immutable/nodes/{0.CUGC2p-K.js → 0.DYyUA1au.js} +1 -1
  292. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
  293. package/web/.svelte-kit/output/client/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
  294. package/{dist/web/_app/immutable/nodes/3.0arZe_Uf.js → web/.svelte-kit/output/client/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
  295. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
  296. package/web/.svelte-kit/output/client/_app/immutable/nodes/{5.Bq2JzCEj.js → 5.DLbPVJY2.js} +1 -1
  297. package/web/.svelte-kit/output/client/_app/version.json +1 -1
  298. package/web/.svelte-kit/output/server/.vite/manifest.json +12 -10
  299. package/web/.svelte-kit/output/server/chunks/Icon.js +1 -1
  300. package/web/.svelte-kit/output/server/chunks/client.js +1 -1
  301. package/web/.svelte-kit/output/server/chunks/exports.js +1 -1
  302. package/web/.svelte-kit/output/server/chunks/index-server.js +2 -1
  303. package/web/.svelte-kit/output/server/chunks/internal.js +1 -1
  304. package/web/.svelte-kit/output/server/chunks/render-context.js +77 -0
  305. package/web/.svelte-kit/output/server/chunks/root.js +739 -788
  306. package/web/.svelte-kit/output/server/chunks/shared.js +234 -21
  307. package/web/.svelte-kit/output/server/index.js +126 -90
  308. package/web/.svelte-kit/output/server/manifest-full.js +1 -1
  309. package/web/.svelte-kit/output/server/manifest.js +1 -1
  310. package/web/.svelte-kit/output/server/nodes/0.js +1 -1
  311. package/web/.svelte-kit/output/server/nodes/1.js +1 -1
  312. package/web/.svelte-kit/output/server/nodes/2.js +1 -1
  313. package/web/.svelte-kit/output/server/nodes/3.js +1 -1
  314. package/web/.svelte-kit/output/server/nodes/4.js +1 -1
  315. package/web/.svelte-kit/output/server/nodes/5.js +1 -1
  316. package/web/.svelte-kit/output/server/remote-entry.js +245 -81
  317. package/web/.svelte-kit/tsconfig.json +4 -1
  318. package/dist/cli/propose-policy.mjs.map +0 -1
  319. package/dist/lite-CBxOT1y5.mjs +0 -241
  320. package/dist/lite-CBxOT1y5.mjs.map +0 -1
  321. package/dist/routing-D8rTxtaV.mjs +0 -245
  322. package/dist/routing-D8rTxtaV.mjs.map +0 -1
  323. package/dist/web/_app/immutable/chunks/B6YN0Nuq.js +0 -1
  324. package/dist/web/_app/immutable/chunks/BmRlVmv6.js +0 -1
  325. package/dist/web/_app/immutable/chunks/CK9JZLaG.js +0 -2
  326. package/dist/web/_app/immutable/chunks/Ck3rYNON.js +0 -1
  327. package/dist/web/_app/immutable/chunks/D5iV40bG.js +0 -1
  328. package/dist/web/_app/immutable/chunks/DMtIqaiV.js +0 -2
  329. package/dist/web/_app/immutable/chunks/DhD271EB.js +0 -1
  330. package/dist/web/_app/immutable/chunks/DpuLqk8d.js +0 -1
  331. package/dist/web/_app/immutable/chunks/DsIToJCP.js +0 -1
  332. package/dist/web/_app/immutable/entry/app.BCSV3nrG.js +0 -2
  333. package/dist/web/_app/immutable/entry/start.D4eLEZUM.js +0 -1
  334. package/dist/web/_app/immutable/nodes/1.CGC_42IQ.js +0 -1
  335. package/dist/web/_app/immutable/nodes/4.ClM1bXLE.js +0 -60
  336. package/dist/workspace-BJmJBfKi.mjs +0 -456
  337. package/dist/workspace-BJmJBfKi.mjs.map +0 -1
  338. package/src/cli/e2e/agents.test.ts +0 -140
  339. package/src/cli/e2e/basic.test.ts +0 -43
  340. package/src/cli/e2e/cron.test.ts +0 -132
  341. package/src/cli/e2e/export-lite-func.test.ts +0 -206
  342. package/src/cli/e2e/fallbacks.test.ts +0 -175
  343. package/src/cli/e2e/init.test.ts +0 -77
  344. package/src/cli/e2e/messages.test.ts +0 -332
  345. package/src/cli/e2e/propose-policy.test.ts +0 -203
  346. package/src/cli/e2e/requests.test.ts +0 -180
  347. package/src/cli/e2e/session-timeout.test.ts +0 -192
  348. package/src/cli/e2e/slash-new.test.ts +0 -93
  349. package/src/cli/e2e/subagents.test.ts +0 -106
  350. package/src/cli/e2e/utils.ts +0 -66
  351. package/src/cli/propose-policy.ts +0 -91
  352. package/web/.svelte-kit/output/client/_app/immutable/chunks/B6YN0Nuq.js +0 -1
  353. package/web/.svelte-kit/output/client/_app/immutable/chunks/BmRlVmv6.js +0 -1
  354. package/web/.svelte-kit/output/client/_app/immutable/chunks/CK9JZLaG.js +0 -2
  355. package/web/.svelte-kit/output/client/_app/immutable/chunks/Ck3rYNON.js +0 -1
  356. package/web/.svelte-kit/output/client/_app/immutable/chunks/D5iV40bG.js +0 -1
  357. package/web/.svelte-kit/output/client/_app/immutable/chunks/DMtIqaiV.js +0 -2
  358. package/web/.svelte-kit/output/client/_app/immutable/chunks/DhD271EB.js +0 -1
  359. package/web/.svelte-kit/output/client/_app/immutable/chunks/DpuLqk8d.js +0 -1
  360. package/web/.svelte-kit/output/client/_app/immutable/chunks/DsIToJCP.js +0 -1
  361. package/web/.svelte-kit/output/client/_app/immutable/entry/app.BCSV3nrG.js +0 -2
  362. package/web/.svelte-kit/output/client/_app/immutable/entry/start.D4eLEZUM.js +0 -1
  363. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.CGC_42IQ.js +0 -1
  364. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.ClM1bXLE.js +0 -60
  365. package/web/.svelte-kit/output/server/chunks/false.js +0 -4
  366. /package/dist/cli/{propose-policy.d.mts → manage-policies.d.mts} +0 -0
  367. /package/{src/cli/e2e → e2e/_helpers}/global-setup.ts +0 -0
@@ -1,32 +1,49 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { shouldDisplayMessage, formatMessage } from './filtering.js';
2
+ import { shouldDisplayMessage, routeMessage, formatMessage } from './filtering.js';
3
3
  import type { ChatMessage } from '../chats.js';
4
4
 
5
- describe('shouldDisplayMessage', () => {
5
+ describe('shouldDisplayMessage / routeMessage', () => {
6
6
  const defaultConfig = {};
7
7
 
8
- it('hides messages with subagentId if subagent is not explicitly true', () => {
8
+ it('hides subagent messages from top-level output by default (legacy shouldDisplayMessage)', () => {
9
9
  const msg: ChatMessage = {
10
10
  id: '1',
11
11
  role: 'agent',
12
12
  content: 'hello',
13
13
  subagentId: 'sub1',
14
14
  timestamp: '',
15
+ sessionId: undefined,
15
16
  };
16
17
  expect(shouldDisplayMessage(msg, defaultConfig)).toBe(false);
18
+ // routeMessage still surfaces it inside the turn thread — that's where
19
+ // subagent activity now belongs.
20
+ expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'thread-log' });
17
21
  });
18
22
 
19
- it('hides standard user messages without subagentId', () => {
20
- const msg: ChatMessage = { id: '1', role: 'user', content: 'hello', timestamp: '' };
23
+ it('drops standard user messages without subagentId', () => {
24
+ const msg: ChatMessage = {
25
+ id: '1',
26
+ role: 'user',
27
+ content: 'hello',
28
+ timestamp: '',
29
+ sessionId: undefined,
30
+ };
21
31
  expect(shouldDisplayMessage(msg, defaultConfig)).toBe(false);
32
+ expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'drop' });
22
33
  });
23
34
 
24
- it('displays standard agent messages without subagentId', () => {
25
- const msg: ChatMessage = { id: '1', role: 'agent', content: 'hello', timestamp: '' };
26
- expect(shouldDisplayMessage(msg, defaultConfig)).toBe(true);
35
+ it('routes standard agent messages to top-level', () => {
36
+ const msg: ChatMessage = {
37
+ id: '1',
38
+ role: 'agent',
39
+ content: 'hello',
40
+ timestamp: '',
41
+ sessionId: undefined,
42
+ };
43
+ expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'top-level' });
27
44
  });
28
45
 
29
- it('hides non-standard messages by default', () => {
46
+ it('drops command messages by default', () => {
30
47
  const msg: ChatMessage = {
31
48
  id: '1',
32
49
  role: 'command',
@@ -38,10 +55,72 @@ describe('shouldDisplayMessage', () => {
38
55
  stderr: '',
39
56
  exitCode: 0,
40
57
  timestamp: '',
58
+ sessionId: undefined,
41
59
  };
60
+ expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'drop' });
61
+ // Legacy boolean retains the same "drop by default" semantics for Discord.
42
62
  expect(shouldDisplayMessage(msg, defaultConfig)).toBe(false);
43
63
  });
44
64
 
65
+ it('routes tool messages to thread-log by default', () => {
66
+ const msg: ChatMessage = {
67
+ id: '1',
68
+ role: 'tool',
69
+ content: '',
70
+ messageId: '123',
71
+ name: 'Read',
72
+ payload: {},
73
+ timestamp: '',
74
+ sessionId: undefined,
75
+ };
76
+ expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'thread-log' });
77
+ });
78
+
79
+ it('drops cron system messages by default (proactive turns are invisible)', () => {
80
+ // Cron prompts are agent-facing orchestration, not user-facing content.
81
+ // Adapters that want scheduled work visible opt into their own synthetic
82
+ // heartbeat (e.g. gchat `visibility.jobs: 'header'`).
83
+ const msg: ChatMessage = {
84
+ id: '1',
85
+ role: 'system',
86
+ content: 'run my daily report',
87
+ event: 'cron',
88
+ timestamp: '',
89
+ sessionId: undefined,
90
+ };
91
+ expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'drop' });
92
+ });
93
+
94
+ it('promotes cron system messages back to top-level when filters.system is true', () => {
95
+ // Escape hatch for debugging: `filters.system: true` forces cron activity
96
+ // back into the main log.
97
+ const msg: ChatMessage = {
98
+ id: '1',
99
+ role: 'system',
100
+ content: 'run my daily report',
101
+ event: 'cron',
102
+ timestamp: '',
103
+ sessionId: undefined,
104
+ };
105
+ expect(routeMessage(msg, { filters: { system: true } })).toEqual({ kind: 'top-level' });
106
+ });
107
+
108
+ it('routes pending policy messages to top-level', () => {
109
+ const msg: ChatMessage = {
110
+ id: '1',
111
+ role: 'policy',
112
+ content: '',
113
+ messageId: '123',
114
+ requestId: 'req',
115
+ commandName: 'rm',
116
+ args: [],
117
+ status: 'pending',
118
+ timestamp: '',
119
+ sessionId: undefined,
120
+ };
121
+ expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'top-level' });
122
+ });
123
+
45
124
  it('displays subagent messages if subagent: true', () => {
46
125
  const msg: ChatMessage = {
47
126
  id: '1',
@@ -49,6 +128,7 @@ describe('shouldDisplayMessage', () => {
49
128
  content: 'hello',
50
129
  subagentId: 'sub1',
51
130
  timestamp: '',
131
+ sessionId: undefined,
52
132
  };
53
133
  expect(shouldDisplayMessage(msg, { filters: { subagent: true } })).toBe(true);
54
134
  });
@@ -60,6 +140,7 @@ describe('shouldDisplayMessage', () => {
60
140
  content: 'hello subagent',
61
141
  subagentId: 'sub1',
62
142
  timestamp: '',
143
+ sessionId: undefined,
63
144
  };
64
145
  expect(shouldDisplayMessage(msg, { filters: { subagent: true } })).toBe(true);
65
146
  });
@@ -76,14 +157,100 @@ describe('shouldDisplayMessage', () => {
76
157
  stderr: '',
77
158
  exitCode: 0,
78
159
  timestamp: '',
160
+ sessionId: undefined,
79
161
  };
80
162
  expect(shouldDisplayMessage(msg, { filters: { command: true } })).toBe(true);
81
163
  });
164
+
165
+ it('hides a role when filter is explicitly false', () => {
166
+ const msg: ChatMessage = {
167
+ id: '1',
168
+ role: 'command',
169
+ content: 'ls',
170
+ messageId: '123',
171
+ command: 'ls',
172
+ cwd: '.',
173
+ stdout: '',
174
+ stderr: '',
175
+ exitCode: 0,
176
+ timestamp: '',
177
+ sessionId: undefined,
178
+ };
179
+ expect(shouldDisplayMessage(msg, { filters: { command: false } })).toBe(false);
180
+ });
181
+
182
+ it('promotes a user-role message to top-level when filter is true', () => {
183
+ const msg: ChatMessage = {
184
+ id: '1',
185
+ role: 'user',
186
+ content: 'hello',
187
+ timestamp: '',
188
+ sessionId: undefined,
189
+ };
190
+ expect(routeMessage(msg, { filters: { user: true } })).toEqual({ kind: 'top-level' });
191
+ });
192
+
193
+ it('routes subagent tool messages to thread-log even without the subagent filter', () => {
194
+ const msg: ChatMessage = {
195
+ id: '1',
196
+ role: 'tool',
197
+ content: 'ls',
198
+ messageId: 'mid',
199
+ name: 'Read',
200
+ payload: {},
201
+ subagentId: 'sub-1',
202
+ timestamp: '',
203
+ sessionId: undefined,
204
+ };
205
+ expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'thread-log' });
206
+ });
207
+
208
+ it('routes subagent final replies into the turn thread, not top-level', () => {
209
+ const msg: ChatMessage = {
210
+ id: '1',
211
+ role: 'agent',
212
+ content: 'done',
213
+ subagentId: 'sub-1',
214
+ timestamp: '',
215
+ sessionId: undefined,
216
+ };
217
+ expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'thread-log' });
218
+ });
219
+
220
+ it('routes subagent prompts (user role with subagentId) into the turn thread', () => {
221
+ const msg: ChatMessage = {
222
+ id: '1',
223
+ role: 'user',
224
+ content: 'research auth flow',
225
+ subagentId: 'sub-1',
226
+ timestamp: '',
227
+ sessionId: undefined,
228
+ };
229
+ expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'thread-log' });
230
+ });
231
+
232
+ it('surfaces subagent final replies at top-level when subagent filter is true', () => {
233
+ const msg: ChatMessage = {
234
+ id: '1',
235
+ role: 'agent',
236
+ content: 'done',
237
+ subagentId: 'sub-1',
238
+ timestamp: '',
239
+ sessionId: undefined,
240
+ };
241
+ expect(routeMessage(msg, { filters: { subagent: true } })).toEqual({ kind: 'top-level' });
242
+ });
82
243
  });
83
244
 
84
245
  describe('formatMessage', () => {
85
246
  it('returns content as-is for messages without subagentId', () => {
86
- const msg: ChatMessage = { id: '1', role: 'agent', content: 'hello world', timestamp: '' };
247
+ const msg: ChatMessage = {
248
+ id: '1',
249
+ role: 'agent',
250
+ content: 'hello world',
251
+ timestamp: '',
252
+ sessionId: undefined,
253
+ };
87
254
  expect(formatMessage(msg)).toBe('hello world');
88
255
  });
89
256
 
@@ -94,6 +261,7 @@ describe('formatMessage', () => {
94
261
  content: 'do task',
95
262
  subagentId: 'sub1',
96
263
  timestamp: '',
264
+ sessionId: undefined,
97
265
  };
98
266
  expect(formatMessage(msg)).toBe('[To:sub1]\ndo task');
99
267
  });
@@ -105,7 +273,53 @@ describe('formatMessage', () => {
105
273
  content: 'done',
106
274
  subagentId: 'sub1',
107
275
  timestamp: '',
276
+ sessionId: undefined,
108
277
  };
109
278
  expect(formatMessage(msg)).toBe('[From:sub1]\ndone');
110
279
  });
280
+
281
+ it('prepends [SYSTEM] for system-role messages with no displayRole', () => {
282
+ // System messages only reach `formatMessage` when they route to top-level
283
+ // (either a non-cron event, or cron opted back in via `filters.system`).
284
+ // Without the tag they look like either the user or the bot talking.
285
+ const msg: ChatMessage = {
286
+ id: '1',
287
+ role: 'system',
288
+ content: 'This chat session has ended.',
289
+ event: 'cron',
290
+ timestamp: '',
291
+ sessionId: undefined,
292
+ };
293
+ expect(formatMessage(msg)).toBe('[SYSTEM] This chat session has ended.');
294
+ });
295
+
296
+ it('does not prefix [SYSTEM] when displayRole is set (e.g. router auto-reply)', () => {
297
+ // Router auto-replies opt into being rendered as agent output. Tagging
298
+ // them [SYSTEM] would mislabel them.
299
+ const msg: ChatMessage = {
300
+ id: '1',
301
+ role: 'system',
302
+ content: 'Starting a fresh session...',
303
+ event: 'router',
304
+ displayRole: 'agent',
305
+ timestamp: '',
306
+ sessionId: undefined,
307
+ };
308
+ expect(formatMessage(msg)).toBe('Starting a fresh session...');
309
+ });
310
+
311
+ it('does not prefix [SYSTEM] for system messages from subagents', () => {
312
+ // Subagent output already gets [From:<id>]; a [SYSTEM] on top would be
313
+ // noisy and incorrect (the subagent is the speaker, not the system).
314
+ const msg: ChatMessage = {
315
+ id: '1',
316
+ role: 'system',
317
+ content: 'internal note',
318
+ event: 'subagent_update',
319
+ subagentId: 'sub1',
320
+ timestamp: '',
321
+ sessionId: undefined,
322
+ };
323
+ expect(formatMessage(msg)).toBe('[From:sub1]\ninternal note');
324
+ });
111
325
  });
@@ -4,26 +4,28 @@ export interface FilteringConfig {
4
4
  filters?: Record<string, boolean> | undefined;
5
5
  }
6
6
 
7
+ export type Destination = { kind: 'drop' } | { kind: 'top-level' } | { kind: 'thread-log' };
8
+
9
+ /**
10
+ * Legacy boolean. Returns true if the message role is permitted to be
11
+ * displayed at all; adapters that haven't migrated to `routeMessage` keep
12
+ * using this unchanged.
13
+ */
7
14
  export function shouldDisplayMessage(message: ChatMessage, config: FilteringConfig): boolean {
8
15
  const overrides = config.filters || {};
9
16
 
10
- // If the message has a subagentId, return false immediately unless subagent messages are allowed.
11
17
  if (message.subagentId && overrides['subagent'] !== true) {
12
18
  return false;
13
19
  }
14
20
 
15
- // Then check if it's a standard agent message (via role/displayRole) and always return true if so.
16
21
  const isStandardAgent =
17
22
  message.displayRole === 'agent' ||
18
23
  message.role === 'agent' ||
19
24
  message.role === 'legacy_log' ||
20
25
  (message.role === 'policy' && message.status === 'pending');
21
26
 
22
- if (isStandardAgent) {
23
- return true;
24
- }
27
+ if (isStandardAgent) return true;
25
28
 
26
- // Then check if it's a user message directed to a subagent, if subagent messages are allowed
27
29
  if (
28
30
  message.subagentId &&
29
31
  overrides['subagent'] === true &&
@@ -32,7 +34,6 @@ export function shouldDisplayMessage(message: ChatMessage, config: FilteringConf
32
34
  return true;
33
35
  }
34
36
 
35
- // Finally, check if the role is allowed and forward it if so.
36
37
  if (
37
38
  overrides[message.role] === true ||
38
39
  (message.displayRole && overrides[message.displayRole] === true)
@@ -43,7 +44,94 @@ export function shouldDisplayMessage(message: ChatMessage, config: FilteringConf
43
44
  return false;
44
45
  }
45
46
 
47
+ function defaultDestinationForRole(message: ChatMessage): Destination {
48
+ if (message.role === 'user') return { kind: 'drop' };
49
+ if (message.role === 'agent') return { kind: 'top-level' };
50
+ if (message.role === 'legacy_log') return { kind: 'top-level' };
51
+ if (message.role === 'tool') return { kind: 'thread-log' };
52
+ if (message.role === 'subagent_status') return { kind: 'thread-log' };
53
+ if (message.role === 'command') return { kind: 'drop' };
54
+ if (message.role === 'policy') {
55
+ return message.status === 'pending' ? { kind: 'top-level' } : { kind: 'thread-log' };
56
+ }
57
+ if (message.role === 'system') {
58
+ // Cron turns are invisible by default: the activity log anchors on the
59
+ // agent's eventual top-level reply (if any). Adapters that want a
60
+ // visible header post (gchat `visibility.jobs: 'header'`) promote this
61
+ // back to top-level at the forwarder layer.
62
+ if (message.event === 'cron') return { kind: 'drop' };
63
+ if (message.event === 'policy_approved' || message.event === 'policy_rejected') {
64
+ return { kind: 'thread-log' };
65
+ }
66
+ if (message.event === 'subagent_update') return { kind: 'thread-log' };
67
+ return { kind: 'top-level' };
68
+ }
69
+ return { kind: 'drop' };
70
+ }
71
+
72
+ /**
73
+ * Return the destination for a chat message given the adapter's filtering
74
+ * config. Adapters that support threaded activity logs (Google Chat) use this
75
+ * to decide whether each message becomes a top-level post, a thread-log entry,
76
+ * or is dropped entirely.
77
+ *
78
+ * A filter override of `true` on a role whose default destination is `drop`
79
+ * promotes it to `top-level` (matching the legacy "opted in → show" behavior).
80
+ * A filter override of `false` drops the role.
81
+ *
82
+ * Subagent messages route to their default destination when that default is a
83
+ * thread one (tool calls, command logs, status updates all belong in the turn
84
+ * log). They are dropped when the default is top-level — subagent prompts and
85
+ * final replies are orchestration, not user-facing content — unless
86
+ * `filters.subagent` is `true`, which surfaces them at top-level for debugging.
87
+ */
88
+ export function routeMessage(message: ChatMessage, config: FilteringConfig): Destination {
89
+ const overrides = config.filters || {};
90
+ const defaultDest = defaultDestinationForRole(message);
91
+
92
+ if (message.subagentId) {
93
+ if (overrides['subagent'] === true) {
94
+ return defaultDest.kind === 'drop' ? { kind: 'top-level' } : defaultDest;
95
+ }
96
+ // Everything produced inside a subagent — tool calls, command logs,
97
+ // status updates, the prompt handed to it, and its final reply — folds
98
+ // into the parent turn's activity log so the reader can see what the
99
+ // subagent did.
100
+ return { kind: 'thread-log' };
101
+ }
102
+
103
+ const isStandardAgent =
104
+ message.displayRole === 'agent' ||
105
+ message.role === 'agent' ||
106
+ message.role === 'legacy_log' ||
107
+ (message.role === 'policy' && message.status === 'pending');
108
+
109
+ if (isStandardAgent) return defaultDest;
110
+
111
+ const roleFilter = overrides[message.role];
112
+ const displayRoleFilter = message.displayRole ? overrides[message.displayRole] : undefined;
113
+
114
+ if (roleFilter === false || displayRoleFilter === false) {
115
+ return { kind: 'drop' };
116
+ }
117
+
118
+ if (roleFilter === true || displayRoleFilter === true) {
119
+ return defaultDest.kind === 'drop' ? { kind: 'top-level' } : defaultDest;
120
+ }
121
+
122
+ return defaultDest;
123
+ }
124
+
46
125
  export function formatMessage(message: ChatMessage): string {
126
+ // System-role messages that aren't explicitly re-displayed as user/agent
127
+ // (e.g. cron-triggered prompts, policy system notes) are posted verbatim
128
+ // today, which makes them look like either the user or the bot talking.
129
+ // Tag them so readers can distinguish automated system output from real
130
+ // conversation. Router auto-replies opt out via displayRole: 'agent'.
131
+ if (message.role === 'system' && !message.displayRole && !message.subagentId) {
132
+ return `[SYSTEM] ${message.content}`;
133
+ }
134
+
47
135
  if (!message.subagentId) {
48
136
  return message.content;
49
137
  }
@@ -0,0 +1,48 @@
1
+ import { describe, it, expect, vi, afterEach } from 'vitest';
2
+ import { createInboundCache } from './inbound-cache.js';
3
+
4
+ const TTL_MS = 10 * 60 * 1000;
5
+
6
+ describe('createInboundCache', () => {
7
+ afterEach(() => {
8
+ vi.useRealTimers();
9
+ });
10
+
11
+ it('records and resolves a value by key', () => {
12
+ const cache = createInboundCache<{ channelId: string }>(TTL_MS);
13
+ cache.record('msg-1', { channelId: 'chan-1' });
14
+ expect(cache.resolve('msg-1')).toEqual({ channelId: 'chan-1' });
15
+ });
16
+
17
+ it('returns null for unknown keys', () => {
18
+ const cache = createInboundCache<{ channelId: string }>(TTL_MS);
19
+ expect(cache.resolve('unknown')).toBeNull();
20
+ });
21
+
22
+ it('expires entries older than ttlMs on resolve', () => {
23
+ vi.useFakeTimers();
24
+ const cache = createInboundCache<{ channelId: string }>(TTL_MS);
25
+ cache.record('msg-1', { channelId: 'chan-1' });
26
+ expect(cache.resolve('msg-1')).not.toBeNull();
27
+
28
+ vi.advanceTimersByTime(TTL_MS + 1000);
29
+ expect(cache.resolve('msg-1')).toBeNull();
30
+ });
31
+
32
+ it('sweeps expired entries on every insert', () => {
33
+ vi.useFakeTimers();
34
+ const cache = createInboundCache<{ channelId: string }>(TTL_MS);
35
+ cache.record('msg-1', { channelId: 'chan-1' });
36
+ vi.advanceTimersByTime(TTL_MS + 1000);
37
+ cache.record('msg-2', { channelId: 'chan-2' });
38
+ expect(cache.resolve('msg-1')).toBeNull();
39
+ expect(cache.resolve('msg-2')).not.toBeNull();
40
+ });
41
+
42
+ it('reset() drops all entries', () => {
43
+ const cache = createInboundCache<{ channelId: string }>(TTL_MS);
44
+ cache.record('msg-1', { channelId: 'chan-1' });
45
+ cache.reset();
46
+ expect(cache.resolve('msg-1')).toBeNull();
47
+ });
48
+ });
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Generic in-memory inbound message cache shared by adapters that need to
3
+ * correlate a daemon `turnStarted` event with the inbound message that
4
+ * triggered it.
5
+ *
6
+ * Adapters whose ingestion and forwarder run in the same Node process don't
7
+ * need disk persistence: a `Map` keyed by external ref (the same id sent to
8
+ * the daemon as `externalRef`) is enough. Entries older than `ttlMs` are
9
+ * swept on every insert — bounded memory without an LRU. Adapter restart
10
+ * mid-turn loses the cache; that is an explicit tradeoff.
11
+ */
12
+ export interface InboundCache<TValue> {
13
+ record(key: string, value: TValue): void;
14
+ resolve(key: string): TValue | null;
15
+ /** Test hook: drop all cached records. */
16
+ reset(): void;
17
+ }
18
+
19
+ interface Entry<TValue> {
20
+ value: TValue;
21
+ receivedAt: number;
22
+ }
23
+
24
+ export function createInboundCache<TValue>(ttlMs: number): InboundCache<TValue> {
25
+ const cache = new Map<string, Entry<TValue>>();
26
+
27
+ const sweep = (now: number): void => {
28
+ for (const [key, entry] of cache) {
29
+ if (now - entry.receivedAt > ttlMs) cache.delete(key);
30
+ }
31
+ };
32
+
33
+ return {
34
+ record(key, value) {
35
+ const now = Date.now();
36
+ sweep(now);
37
+ cache.set(key, { value, receivedAt: now });
38
+ },
39
+
40
+ resolve(key) {
41
+ const entry = cache.get(key);
42
+ if (!entry) return null;
43
+ if (Date.now() - entry.receivedAt > ttlMs) {
44
+ cache.delete(key);
45
+ return null;
46
+ }
47
+ return entry.value;
48
+ },
49
+
50
+ reset() {
51
+ cache.clear();
52
+ },
53
+ };
54
+ }