clawmini 0.0.7 → 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/{G_zz-Gou.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.CYS8iApT.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.Dr0ot9sV.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.BBGQ_i84.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 +42 -13
  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 -176
  274. package/web/.svelte-kit/generated/server/internal.js +1 -1
  275. package/web/.svelte-kit/output/client/.vite/manifest.json +127 -137
  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/{G_zz-Gou.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.CYS8iApT.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.Dr0ot9sV.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.BBGQ_i84.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/DMtIqaiV.js +0 -2
  328. package/dist/web/_app/immutable/chunks/DhD271EB.js +0 -1
  329. package/dist/web/_app/immutable/chunks/DpuLqk8d.js +0 -1
  330. package/dist/web/_app/immutable/chunks/DsIToJCP.js +0 -1
  331. package/dist/web/_app/immutable/chunks/bBmtyQMj.js +0 -1
  332. package/dist/web/_app/immutable/entry/app.CJmSwntr.js +0 -2
  333. package/dist/web/_app/immutable/entry/start.ZpUrT2ak.js +0 -1
  334. package/dist/web/_app/immutable/nodes/1.Bli0Hqzn.js +0 -1
  335. package/dist/web/_app/immutable/nodes/4.oBhvQhcA.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/DMtIqaiV.js +0 -2
  357. package/web/.svelte-kit/output/client/_app/immutable/chunks/DhD271EB.js +0 -1
  358. package/web/.svelte-kit/output/client/_app/immutable/chunks/DpuLqk8d.js +0 -1
  359. package/web/.svelte-kit/output/client/_app/immutable/chunks/DsIToJCP.js +0 -1
  360. package/web/.svelte-kit/output/client/_app/immutable/chunks/bBmtyQMj.js +0 -1
  361. package/web/.svelte-kit/output/client/_app/immutable/entry/app.CJmSwntr.js +0 -2
  362. package/web/.svelte-kit/output/client/_app/immutable/entry/start.ZpUrT2ak.js +0 -1
  363. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.Bli0Hqzn.js +0 -1
  364. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.oBhvQhcA.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
@@ -0,0 +1,340 @@
1
+ import { afterAll, afterEach, beforeAll, vi } from 'vitest';
2
+ import { EventEmitter } from 'node:events';
3
+ import { TestEnvironment } from '../_helpers/test-environment.js';
4
+ import {
5
+ getTRPCClient,
6
+ type GoogleChatApi,
7
+ type MessageSourceLike,
8
+ } from '../../src/adapter-google-chat/client.js';
9
+ import type { GoogleChatConfig } from '../../src/adapter-google-chat/config.js';
10
+ import { startDaemonToGoogleChatForwarder } from '../../src/adapter-google-chat/forwarder.js';
11
+ import { updateGoogleChatState } from '../../src/adapter-google-chat/state.js';
12
+
13
+ export const BASE_CONFIG: GoogleChatConfig = {
14
+ projectId: 'fake-project',
15
+ subscriptionName: 'fake-sub',
16
+ topicName: 'fake-topic',
17
+ authorizedUsers: ['user@example.com'],
18
+ requireMention: false,
19
+ chatId: 'gc-chat',
20
+ driveUploadEnabled: false,
21
+ };
22
+
23
+ export interface FakeMessage {
24
+ data: Buffer;
25
+ attributes: Record<string, string>;
26
+ ack: ReturnType<typeof vi.fn>;
27
+ nack: ReturnType<typeof vi.fn>;
28
+ }
29
+
30
+ export function makePubsubMessage(
31
+ body: Record<string, unknown>,
32
+ attributes: Record<string, string> = {}
33
+ ): FakeMessage {
34
+ return {
35
+ data: Buffer.from(JSON.stringify(body)),
36
+ attributes,
37
+ ack: vi.fn(),
38
+ nack: vi.fn(),
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Pub/Sub-shaped MESSAGE event in a DIRECT_MESSAGE (singleUserBotDm) space.
44
+ * `sender` defaults to the authorized user. Pass `attachment` for attachment
45
+ * tests, or `quotedMessageMetadata` to model a quote-reply.
46
+ */
47
+ export function makeDmMessage(opts: {
48
+ space: string;
49
+ messageId: string;
50
+ text: string;
51
+ sender?: string;
52
+ attachment?: unknown[];
53
+ quotedMessageMetadata?: unknown;
54
+ }): FakeMessage {
55
+ const sender = opts.sender ?? 'user@example.com';
56
+ return makePubsubMessage({
57
+ type: 'MESSAGE',
58
+ space: { name: opts.space, type: 'DIRECT_MESSAGE', singleUserBotDm: true },
59
+ message: {
60
+ name: `${opts.space}/messages/${opts.messageId}`,
61
+ sender: { email: sender, type: 'USER' },
62
+ text: opts.text,
63
+ ...(opts.attachment ? { attachment: opts.attachment } : {}),
64
+ ...(opts.quotedMessageMetadata
65
+ ? { quotedMessageMetadata: opts.quotedMessageMetadata }
66
+ : {}),
67
+ },
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Pub/Sub-shaped MESSAGE event in a non-DM SPACE. `authUser` authenticates the
73
+ * caller (top-level `user.email`); `sender` (if provided) sets
74
+ * `message.sender` — omit it for command-style messages that arrive without a
75
+ * sender. `mention: true` adds the bot-targeted USER_MENTION annotation used
76
+ * by the `requireMention` path; pass `annotations` directly to control the
77
+ * exact shape (e.g. the simpler `[{ type: 'USER_MENTION' }]` form).
78
+ */
79
+ export function makeSpaceMessage(opts: {
80
+ space: string;
81
+ messageId: string;
82
+ text: string;
83
+ authUser?: string;
84
+ sender?: string;
85
+ mention?: boolean;
86
+ annotations?: unknown[];
87
+ }): FakeMessage {
88
+ const authUser = opts.authUser ?? 'user@example.com';
89
+ const annotations =
90
+ opts.annotations ??
91
+ (opts.mention
92
+ ? [{ type: 'USER_MENTION', userMention: { user: { type: 'BOT' } } }]
93
+ : undefined);
94
+ return makePubsubMessage({
95
+ type: 'MESSAGE',
96
+ space: { name: opts.space, type: 'SPACE' },
97
+ user: { email: authUser },
98
+ message: {
99
+ name: `${opts.space}/messages/${opts.messageId}`,
100
+ text: opts.text,
101
+ ...(opts.sender ? { sender: { email: opts.sender, type: 'USER' } } : {}),
102
+ ...(annotations ? { annotations } : {}),
103
+ },
104
+ });
105
+ }
106
+
107
+ export interface ChatCreateParams {
108
+ parent: string;
109
+ requestBody: {
110
+ text?: string;
111
+ cardsV2?: unknown[];
112
+ [key: string]: unknown;
113
+ };
114
+ }
115
+
116
+ export interface ChatUpdateParams {
117
+ name: string;
118
+ updateMask: string;
119
+ requestBody: {
120
+ text?: string;
121
+ cardsV2?: unknown[];
122
+ [key: string]: unknown;
123
+ };
124
+ }
125
+
126
+ type ChatCreateFn = (params: ChatCreateParams) => Promise<unknown>;
127
+ type ChatUpdateFn = (params: ChatUpdateParams) => Promise<unknown>;
128
+
129
+ export function makeFakeChatApi() {
130
+ const create = vi.fn<ChatCreateFn>().mockResolvedValue({});
131
+ const update = vi.fn<ChatUpdateFn>().mockResolvedValue({});
132
+ const list = vi.fn().mockResolvedValue({ data: { messages: [] } });
133
+ const api = {
134
+ spaces: {
135
+ messages: { create, update, list },
136
+ },
137
+ } as unknown as GoogleChatApi;
138
+ return { api, create, update, list };
139
+ }
140
+
141
+ /** Find a `chatApi.spaces.messages.create` call by `requestBody.text`. */
142
+ export function findCreateByText(
143
+ create: ReturnType<typeof makeFakeChatApi>['create'],
144
+ match: string | ((text: string) => boolean)
145
+ ): ChatCreateParams | undefined {
146
+ const test = typeof match === 'string' ? (t: string) => t === match : match;
147
+ return create.mock.calls.find(
148
+ ([params]) => typeof params.requestBody.text === 'string' && test(params.requestBody.text)
149
+ )?.[0];
150
+ }
151
+
152
+ /** Find the first `create` call carrying a non-empty `cardsV2` payload. */
153
+ export function findCreateWithCard(
154
+ create: ReturnType<typeof makeFakeChatApi>['create']
155
+ ): ChatCreateParams | undefined {
156
+ return create.mock.calls.find(
157
+ ([params]) => Array.isArray(params.requestBody.cardsV2) && params.requestBody.cardsV2.length > 0
158
+ )?.[0];
159
+ }
160
+
161
+ export function makeFakeSubscription(): MessageSourceLike & {
162
+ emitMessage: (msg: FakeMessage) => void;
163
+ } {
164
+ const emitter = new EventEmitter();
165
+ const messageSource = emitter as unknown as MessageSourceLike;
166
+ return Object.assign(messageSource, {
167
+ emitMessage: (msg: FakeMessage) => emitter.emit('message', msg),
168
+ });
169
+ }
170
+
171
+ export interface QueuingFakeSubscription extends MessageSourceLike {
172
+ emitMessage: (msg: FakeMessage) => void;
173
+ detach: () => void;
174
+ pendingCount: () => number;
175
+ }
176
+
177
+ /**
178
+ * Fake Pub/Sub subscription that buffers emitted messages until a 'message'
179
+ * listener is attached, and replays them on attach. Models Pub/Sub's at-least-
180
+ * once redelivery: when the consumer (adapter process) is offline, messages
181
+ * aren't lost — they reappear when a new consumer connects.
182
+ *
183
+ * `detach()` removes the current listener, simulating the adapter crashing/
184
+ * shutting down. Subsequent `emitMessage` calls queue until a fresh
185
+ * `.on('message', ...)` attaches.
186
+ */
187
+ export function makeQueuingFakeSubscription(): QueuingFakeSubscription {
188
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
189
+ let listener: ((msg: any) => void | Promise<void>) | null = null;
190
+ const queue: FakeMessage[] = [];
191
+
192
+ const sub: QueuingFakeSubscription = {
193
+ on(event, l) {
194
+ if (event === 'message') {
195
+ listener = l;
196
+ while (queue.length > 0 && listener) {
197
+ const next = queue.shift()!;
198
+ listener(next);
199
+ }
200
+ }
201
+ return sub;
202
+ },
203
+ emitMessage(msg) {
204
+ if (listener) {
205
+ listener(msg);
206
+ } else {
207
+ queue.push(msg);
208
+ }
209
+ },
210
+ detach() {
211
+ listener = null;
212
+ },
213
+ pendingCount() {
214
+ return queue.length;
215
+ },
216
+ };
217
+
218
+ return sub;
219
+ }
220
+
221
+ /**
222
+ * Wrap a trpc client so we can observe when `waitForMessages.subscribe` has been
223
+ * acknowledged by the server (SSE `started` frame). Tests `await ready` before
224
+ * sending a message so the forwarder's subscription is guaranteed to be live.
225
+ */
226
+ export function instrumentTrpcForReadiness(trpc: ReturnType<typeof getTRPCClient>) {
227
+ let resolveReady: () => void = () => {};
228
+ const ready = new Promise<void>((r) => {
229
+ resolveReady = r;
230
+ });
231
+ const wrapped = new Proxy(trpc, {
232
+ get(target, prop, receiver) {
233
+ if (prop === 'waitForMessages') {
234
+ const route = Reflect.get(target, prop, receiver) as {
235
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
236
+ subscribe: (input: unknown, opts: any) => { unsubscribe: () => void };
237
+ };
238
+ return {
239
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
240
+ subscribe: (input: unknown, opts: any) =>
241
+ route.subscribe(input, {
242
+ ...opts,
243
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
244
+ onStarted: (ctx: any) => {
245
+ resolveReady();
246
+ opts?.onStarted?.(ctx);
247
+ },
248
+ }),
249
+ };
250
+ }
251
+ return Reflect.get(target, prop, receiver);
252
+ },
253
+ }) as ReturnType<typeof getTRPCClient>;
254
+ return { trpc: wrapped, ready };
255
+ }
256
+
257
+ /**
258
+ * Run the daemon → Google Chat forwarder for the duration of `body`. Wraps
259
+ * `trpc` with readiness instrumentation, awaits the SSE `started` frame so
260
+ * body code can rely on the subscription being live, and aborts + joins the
261
+ * forwarder in a finally block so a throwing body doesn't leak it.
262
+ */
263
+ export async function runForwarder(
264
+ options: {
265
+ trpc: ReturnType<typeof getTRPCClient>;
266
+ chatApi: GoogleChatApi;
267
+ startDir: string;
268
+ config?: GoogleChatConfig;
269
+ filters?: Record<string, boolean>;
270
+ },
271
+ body: () => Promise<void>
272
+ ): Promise<void> {
273
+ const { trpc, ready } = instrumentTrpcForReadiness(options.trpc);
274
+ const abort = new AbortController();
275
+ const forwarderPromise = startDaemonToGoogleChatForwarder(
276
+ trpc,
277
+ options.config ?? BASE_CONFIG,
278
+ { filters: options.filters ?? {} },
279
+ abort.signal,
280
+ { chatApi: options.chatApi, startDir: options.startDir }
281
+ );
282
+ try {
283
+ await ready;
284
+ await body();
285
+ } finally {
286
+ abort.abort();
287
+ await forwarderPromise;
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Boilerplate for a Google Chat adapter e2e suite: spins up a dedicated
293
+ * TestEnvironment for the suite and resets adapter state between tests.
294
+ *
295
+ * Pass `{ subagents: true }` to install the `debug-agent` + exported
296
+ * `clawmini-lite.js` on PATH, so tests can drive thread-log events by spawning
297
+ * subagents (the echo agent init() installs only produces command + agent_reply
298
+ * messages, and command messages are dropped from the turn log).
299
+ *
300
+ * The reset is queued through `updateGoogleChatState` so it runs after any
301
+ * pending writes (e.g. a late `saveLastMessageId` from the prior test's
302
+ * forwarder) — otherwise those writes could resurrect `channelChatMap`
303
+ * entries after the reset.
304
+ */
305
+ export function useGoogleChatAdapterEnv(
306
+ suiteName: string,
307
+ options: { subagents?: boolean } = {}
308
+ ) {
309
+ const ref: { env: TestEnvironment } = { env: null as unknown as TestEnvironment };
310
+
311
+ beforeAll(async () => {
312
+ ref.env = new TestEnvironment(suiteName);
313
+ await ref.env.setup();
314
+ if (options.subagents) {
315
+ await ref.env.setupSubagentEnv();
316
+ } else {
317
+ await ref.env.init();
318
+ await ref.env.up();
319
+ }
320
+ }, 60000);
321
+
322
+ afterAll(async () => {
323
+ await ref.env.teardown();
324
+ }, 30000);
325
+
326
+ afterEach(async () => {
327
+ await ref.env.disconnectAll();
328
+ await updateGoogleChatState(
329
+ () => ({
330
+ lastSyncedMessageIds: {},
331
+ channelChatMap: {},
332
+ oauthTokens: undefined,
333
+ filters: {},
334
+ }),
335
+ ref.env.e2eDir
336
+ );
337
+ });
338
+
339
+ return ref;
340
+ }
@@ -1,29 +1,29 @@
1
- import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
- import { createE2EContext } from './utils.js';
3
- import { getTRPCClient } from '../../adapter-discord/client.js';
4
- import { getSocketPath } from '../../shared/workspace.js';
1
+ import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
2
+ import { TestEnvironment } from '../_helpers/test-environment.js';
3
+ import { getTRPCClient } from '../../src/adapter-discord/client.js';
4
+ import { getSocketPath } from '../../src/shared/workspace.js';
5
5
 
6
- const { runCli, e2eDir, setupE2E, teardownE2E } = createE2EContext('e2e-discord');
7
6
  describe('Discord Adapter Client E2E', () => {
7
+ let env: TestEnvironment;
8
+
8
9
  beforeAll(async () => {
9
- await setupE2E();
10
- await runCli(['init']);
11
- await runCli(['up']);
10
+ env = new TestEnvironment('e2e-discord');
11
+ await env.setup();
12
+ await env.init();
13
+ await env.up();
12
14
  }, 30000);
13
15
 
14
- afterAll(async () => {
15
- await runCli(['down']);
16
- await teardownE2E();
17
- }, 30000);
16
+ afterAll(() => env.teardown(), 30000);
17
+ afterEach(() => env.disconnectAll());
18
18
 
19
19
  it('should successfully connect to the daemon and subscribe to messages', async () => {
20
- const socketPath = getSocketPath(e2eDir);
20
+ const socketPath = getSocketPath(env.e2eDir);
21
21
  const trpc = getTRPCClient({ socketPath });
22
22
 
23
23
  const pingResult = await trpc.ping.query();
24
24
  expect(pingResult).toEqual({ status: 'ok' });
25
25
 
26
- await runCli(['chats', 'add', 'discord-chat']);
26
+ await env.addChat('discord-chat');
27
27
 
28
28
  let subscription: { unsubscribe: () => void } | undefined;
29
29
  const messages: Record<string, unknown>[] = [];
@@ -33,7 +33,10 @@ describe('Discord Adapter Client E2E', () => {
33
33
  { chatId: 'discord-chat' },
34
34
  {
35
35
  onData: (data) => {
36
- messages.push(...(data as Record<string, unknown>[]));
36
+ const items = data as Array<{ kind: string; message?: Record<string, unknown> }>;
37
+ for (const item of items) {
38
+ if (item.kind === 'message' && item.message) messages.push(item.message);
39
+ }
37
40
  if (messages.some((m) => m.content === 'hello from adapter e2e test')) {
38
41
  resolve();
39
42
  }
@@ -47,14 +50,10 @@ describe('Discord Adapter Client E2E', () => {
47
50
  // Wait a brief moment to ensure subscription is established before sending a message
48
51
  setTimeout(async () => {
49
52
  try {
50
- await runCli([
51
- 'messages',
52
- 'send',
53
- 'hello from adapter e2e test',
54
- '--chat',
55
- 'discord-chat',
56
- '--no-wait',
57
- ]);
53
+ await env.sendMessage('hello from adapter e2e test', {
54
+ chat: 'discord-chat',
55
+ noWait: true,
56
+ });
58
57
  } catch (e) {
59
58
  reject(e);
60
59
  }
@@ -0,0 +1,157 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import {
3
+ getTRPCClient,
4
+ startGoogleChatIngestion,
5
+ } from '../../src/adapter-google-chat/client.js';
6
+ import {
7
+ readGoogleChatState,
8
+ updateGoogleChatState,
9
+ } from '../../src/adapter-google-chat/state.js';
10
+ import { getSocketPath } from '../../src/shared/workspace.js';
11
+ import {
12
+ BASE_CONFIG,
13
+ findCreateByText,
14
+ makeDmMessage,
15
+ makeFakeChatApi,
16
+ makeQueuingFakeSubscription,
17
+ runForwarder,
18
+ useGoogleChatAdapterEnv,
19
+ } from './_google-chat-fixtures.js';
20
+
21
+ describe('Google Chat Adapter E2E — adapter downtime', () => {
22
+ const envRef = useGoogleChatAdapterEnv('e2e-google-chat-downtime');
23
+
24
+ it('processes Pub/Sub messages that arrived while inbound ingestion was down', async () => {
25
+ const { env } = envRef;
26
+ const trpc = getTRPCClient({ socketPath: getSocketPath(env.e2eDir) });
27
+ const subscription = makeQueuingFakeSubscription();
28
+ const { api } = makeFakeChatApi();
29
+
30
+ await updateGoogleChatState(
31
+ { channelChatMap: { 'spaces/dt-in': { chatId: 'gc-dt-in' } } },
32
+ env.e2eDir
33
+ );
34
+ await env.addChat('gc-dt-in');
35
+ const chat = await env.connect('gc-dt-in');
36
+
37
+ // First ingestion consumer. After it processes msg A we 'detach' it to
38
+ // simulate the adapter crashing, then emit B/C while nothing is listening.
39
+ startGoogleChatIngestion(
40
+ BASE_CONFIG,
41
+ trpc,
42
+ {},
43
+ { subscription, chatApi: api, startDir: env.e2eDir }
44
+ );
45
+
46
+ const msgA = makeDmMessage({ space: 'spaces/dt-in', messageId: 'a', text: 'msg A' });
47
+ subscription.emitMessage(msgA);
48
+
49
+ await chat.waitForMessage((m) => m.role === 'user' && m.content === 'msg A');
50
+ await vi.waitFor(() => expect(msgA.ack).toHaveBeenCalled());
51
+
52
+ // Simulate adapter downtime.
53
+ subscription.detach();
54
+
55
+ const msgB = makeDmMessage({ space: 'spaces/dt-in', messageId: 'b', text: 'msg B' });
56
+ const msgC = makeDmMessage({ space: 'spaces/dt-in', messageId: 'c', text: 'msg C' });
57
+ subscription.emitMessage(msgB);
58
+ subscription.emitMessage(msgC);
59
+
60
+ // While the adapter is down the messages are buffered (like Pub/Sub's
61
+ // unacked queue), not dropped.
62
+ expect(subscription.pendingCount()).toBe(2);
63
+ expect(msgB.ack).not.toHaveBeenCalled();
64
+ expect(msgC.ack).not.toHaveBeenCalled();
65
+
66
+ // A fresh ingestion consumer attaches — equivalent to restarting the
67
+ // adapter process. The buffered messages should replay and reach the
68
+ // daemon in order.
69
+ startGoogleChatIngestion(
70
+ BASE_CONFIG,
71
+ trpc,
72
+ {},
73
+ { subscription, chatApi: api, startDir: env.e2eDir }
74
+ );
75
+
76
+ await chat.waitForMessage((m) => m.role === 'user' && m.content === 'msg B');
77
+ await chat.waitForMessage((m) => m.role === 'user' && m.content === 'msg C');
78
+ await vi.waitFor(() => expect(msgB.ack).toHaveBeenCalled());
79
+ await vi.waitFor(() => expect(msgC.ack).toHaveBeenCalled());
80
+ }, 30000);
81
+
82
+ it('resumes outbound forwarding from lastSyncedMessageIds after a restart', async () => {
83
+ const { env } = envRef;
84
+ const trpc = getTRPCClient({ socketPath: getSocketPath(env.e2eDir) });
85
+ const { api, create } = makeFakeChatApi();
86
+
87
+ await updateGoogleChatState(
88
+ { channelChatMap: { 'spaces/dt-out': { chatId: 'gc-dt-out' } } },
89
+ env.e2eDir
90
+ );
91
+ await env.addChat('gc-dt-out');
92
+
93
+ // Scope the forwarder to just `gc-dt-out` so we don't also spin up a
94
+ // default-chat subscription that spams `getMessages` errors.
95
+ // The default agent echoes $CLAW_CLI_MESSAGE into an agent reply, so each
96
+ // env.sendMessage produces exactly one agent-reply create call when we
97
+ // leave filters at their default (agent-only) setting.
98
+ const config = { ...BASE_CONFIG, chatId: 'gc-dt-out' };
99
+
100
+ let cursorAfterMsg1 = '';
101
+
102
+ await runForwarder({ trpc, chatApi: api, startDir: env.e2eDir, config }, async () => {
103
+ // Send "msg 1" and wait for it to both land on the chat API and for the
104
+ // lastSyncedMessageIds cursor to advance. That's the durable proof that
105
+ // the state file we'll reopen from holds a real checkpoint.
106
+ await env.sendMessage('msg 1', { chat: 'gc-dt-out', noWait: true });
107
+ await vi.waitFor(
108
+ () => {
109
+ expect(findCreateByText(create, 'msg 1')).toBeDefined();
110
+ },
111
+ { timeout: 10000 }
112
+ );
113
+ await vi.waitFor(async () => {
114
+ const id = (await readGoogleChatState(env.e2eDir)).lastSyncedMessageIds?.['gc-dt-out'];
115
+ expect(id).toBeTruthy();
116
+ });
117
+ cursorAfterMsg1 = (await readGoogleChatState(env.e2eDir)).lastSyncedMessageIds![
118
+ 'gc-dt-out'
119
+ ]!;
120
+ });
121
+
122
+ // Forwarder is now down. Daemon continues to receive messages — these are
123
+ // the ones that would arrive at the forwarder if it were still online.
124
+ await env.sendMessage('msg 2', { chat: 'gc-dt-out', noWait: true });
125
+ await env.sendMessage('msg 3', { chat: 'gc-dt-out', noWait: true });
126
+
127
+ // Nothing should have been posted to Google Chat while the forwarder was
128
+ // down.
129
+ expect(findCreateByText(create, 'msg 2')).toBeUndefined();
130
+ expect(findCreateByText(create, 'msg 3')).toBeUndefined();
131
+
132
+ // Restart the forwarder with the same startDir — so it reads back
133
+ // lastSyncedMessageIds and resumes.
134
+ await runForwarder({ trpc, chatApi: api, startDir: env.e2eDir, config }, async () => {
135
+ await vi.waitFor(
136
+ () => {
137
+ expect(findCreateByText(create, 'msg 2')).toBeDefined();
138
+ expect(findCreateByText(create, 'msg 3')).toBeDefined();
139
+ },
140
+ { timeout: 15000 }
141
+ );
142
+
143
+ // And the cursor should have advanced past the restart-point.
144
+ await vi.waitFor(async () => {
145
+ const now = (await readGoogleChatState(env.e2eDir)).lastSyncedMessageIds?.['gc-dt-out'];
146
+ expect(now).toBeDefined();
147
+ expect(now).not.toBe(cursorAfterMsg1);
148
+ });
149
+
150
+ // msg 1 must not be re-posted by the resumed forwarder.
151
+ const msg1Calls = create.mock.calls.filter(
152
+ ([params]) => params.requestBody.text === 'msg 1'
153
+ );
154
+ expect(msg1Calls.length).toBe(1);
155
+ });
156
+ }, 45000);
157
+ });