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,76 +1,19 @@
1
1
  #!/usr/bin/env node
2
- import { c as getClawminiDir, d as getSocketPath, f as getWorkspaceRoot } from "../workspace-BJmJBfKi.mjs";
2
+ import { g as getWorkspaceRoot, h as getSocketPath, u as getClawminiDir } from "../workspace-oWmVh5mi.mjs";
3
3
  import { t as createUnixSocketFetch } from "../fetch-Cn1XNyiO.mjs";
4
- import { a as createUnixSocketEventSource, i as shouldDisplayMessage, n as handleAdapterCommand, r as formatMessage, t as handleRoutingCommand } from "../routing-D8rTxtaV.mjs";
4
+ import { a as updateGoogleChatConfig, i as readGoogleChatConfig, n as initGoogleChatConfig, r as isAuthorized } from "../config-Dvl-Pov4.mjs";
5
+ import { a as handleAdapterCommand, c as routeMessage, i as handleRoutingCommand, n as createUnixSocketEventSource, o as createInboundCache, r as prependBlockquote, s as formatMessage, t as createTurnLogBuffer } from "../turn-log-buffer-DRgW53gl.mjs";
5
6
  import fs from "node:fs";
6
7
  import path from "node:path";
7
- import fs$1 from "node:fs/promises";
8
+ import crypto from "node:crypto";
9
+ import fsPromises from "node:fs/promises";
8
10
  import { z } from "zod";
9
11
  import { createTRPCClient, httpLink, httpSubscriptionLink, splitLink } from "@trpc/client";
10
12
  import http from "node:http";
11
- import crypto from "node:crypto";
12
13
  import { PubSub } from "@google-cloud/pubsub";
13
14
  import { google } from "googleapis";
14
15
  import mime from "mime-types";
15
16
 
16
- //#region src/adapter-google-chat/config.ts
17
- const GoogleChatConfigSchema = z.looseObject({
18
- projectId: z.string().min(1, "GCP Project ID is required."),
19
- subscriptionName: z.string().min(1, "Pub/Sub Subscription Name is required."),
20
- topicName: z.string().min(1, "Pub/Sub Topic Name is required."),
21
- authorizedUsers: z.array(z.string()).min(1, "At least one Authorized User is required."),
22
- maxAttachmentSizeMB: z.number().default(25).optional(),
23
- chatId: z.string().default("default").optional(),
24
- directMessageName: z.string().optional(),
25
- driveUploadEnabled: z.boolean().default(true).optional(),
26
- requireMention: z.boolean().default(false),
27
- oauthClientId: z.string().optional(),
28
- oauthClientSecret: z.string().optional()
29
- });
30
- function getGoogleChatConfigPath(startDir = process.cwd()) {
31
- return path.join(getClawminiDir(startDir), "adapters", "google-chat", "config.json");
32
- }
33
- async function readGoogleChatConfig(startDir = process.cwd()) {
34
- const configPath = getGoogleChatConfigPath(startDir);
35
- try {
36
- const data = await fs$1.readFile(configPath, "utf-8");
37
- const parsed = JSON.parse(data);
38
- return GoogleChatConfigSchema.parse(parsed);
39
- } catch (err) {
40
- if (err.code === "ENOENT") return null;
41
- throw err;
42
- }
43
- }
44
- async function updateGoogleChatConfig(config, startDir = process.cwd()) {
45
- const configPath = getGoogleChatConfigPath(startDir);
46
- await fs$1.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
47
- }
48
- async function initGoogleChatConfig(startDir = process.cwd()) {
49
- const configPath = getGoogleChatConfigPath(startDir);
50
- const configDir = path.dirname(configPath);
51
- await fs$1.mkdir(configDir, { recursive: true });
52
- if (fs.existsSync(configPath)) {
53
- console.log(`Config file already exists at ${configPath}`);
54
- return;
55
- }
56
- await fs$1.writeFile(configPath, JSON.stringify({
57
- projectId: "YOUR_PROJECT_ID",
58
- topicName: "YOUR_TOPIC_NAME",
59
- subscriptionName: "YOUR_SUBSCRIPTION_NAME",
60
- authorizedUsers: ["user@example.com"],
61
- chatId: "default",
62
- requireMention: false,
63
- oauthClientId: "YOUR_OAUTH_CLIENT_ID",
64
- oauthClientSecret: "YOUR_OAUTH_CLIENT_SECRET"
65
- }, null, 2), "utf-8");
66
- console.log(`Created template configuration file at ${configPath}`);
67
- console.log("Please update it with your actual GCP Project ID, Pub/Sub Topic Name, Pub/Sub Subscription Name, and Authorized Users.");
68
- }
69
- function isAuthorized(userIdOrEmail, authorizedUsers) {
70
- return authorizedUsers.some((u) => u.toLowerCase() === userIdOrEmail.toLowerCase());
71
- }
72
-
73
- //#endregion
74
17
  //#region src/adapter-google-chat/state.ts
75
18
  const GoogleChatStateSchema = z.object({
76
19
  lastSyncedMessageIds: z.record(z.string(), z.string()).optional(),
@@ -78,7 +21,8 @@ const GoogleChatStateSchema = z.object({
78
21
  chatId: z.string().nullable().optional(),
79
22
  subscriptionId: z.string().optional(),
80
23
  expirationDate: z.string().optional(),
81
- requireMention: z.boolean().optional()
24
+ requireMention: z.boolean().optional(),
25
+ threadsDisabled: z.boolean().optional()
82
26
  })).optional(),
83
27
  oauthTokens: z.any().optional(),
84
28
  filters: z.record(z.string(), z.boolean()).optional()
@@ -89,7 +33,7 @@ function getGoogleChatStatePath(startDir = process.cwd()) {
89
33
  async function readGoogleChatState(startDir = process.cwd()) {
90
34
  const statePath = getGoogleChatStatePath(startDir);
91
35
  try {
92
- const data = await fs$1.readFile(statePath, "utf-8");
36
+ const data = await fsPromises.readFile(statePath, "utf-8");
93
37
  const parsed = JSON.parse(data);
94
38
  if (parsed.lastSyncedMessageId && !parsed.lastSyncedMessageIds) parsed.lastSyncedMessageIds = { default: parsed.lastSyncedMessageId };
95
39
  if (parsed.driveOauthTokens && !parsed.oauthTokens) {
@@ -118,8 +62,10 @@ function updateGoogleChatState(updates, startDir = process.cwd()) {
118
62
  };
119
63
  const statePath = getGoogleChatStatePath(startDir);
120
64
  const dir = path.dirname(statePath);
121
- await fs$1.mkdir(dir, { recursive: true });
122
- await fs$1.writeFile(statePath, JSON.stringify(newState, null, 2), "utf-8");
65
+ await fsPromises.mkdir(dir, { recursive: true });
66
+ const tmpPath = `${statePath}.${process.pid}.${crypto.randomBytes(4).toString("hex")}.tmp`;
67
+ await fsPromises.writeFile(tmpPath, JSON.stringify(newState, null, 2), "utf-8");
68
+ await fsPromises.rename(tmpPath, statePath);
123
69
  resolve(newState);
124
70
  } catch (err) {
125
71
  console.error(`Failed to write Google Chat state:`, err);
@@ -129,6 +75,29 @@ function updateGoogleChatState(updates, startDir = process.cwd()) {
129
75
  });
130
76
  }
131
77
 
78
+ //#endregion
79
+ //#region src/adapter-google-chat/inbound-cache.ts
80
+ /**
81
+ * Google Chat-side wrapper around the shared inbound-message TTL cache.
82
+ *
83
+ * Ingestion records each inbound by its `gchatMessageName` (also sent to the
84
+ * daemon as `externalRef`). When the forwarder later sees `turnStarted` with
85
+ * that `externalRef`, it resolves the thread anchor by looking up the same
86
+ * key here.
87
+ */
88
+ const INBOUND_TTL_MS = 600 * 1e3;
89
+ const cache = createInboundCache(INBOUND_TTL_MS);
90
+ function recordInbound(entry) {
91
+ cache.record(entry.gchatMessageName, { gchatThreadName: entry.gchatThreadName });
92
+ }
93
+ function resolveInbound(gchatMessageName) {
94
+ const value = cache.resolve(gchatMessageName);
95
+ return value ? {
96
+ gchatMessageName,
97
+ gchatThreadName: value.gchatThreadName
98
+ } : null;
99
+ }
100
+
132
101
  //#endregion
133
102
  //#region src/adapter-google-chat/utils.ts
134
103
  let authClient$1 = null;
@@ -223,32 +192,34 @@ async function getAuthClient() {
223
192
  if (!authClient) authClient = await google.auth.getClient({ scopes: ["https://www.googleapis.com/auth/chat.bot"] });
224
193
  return authClient;
225
194
  }
226
- let userAuthClient = null;
227
- let userAuthPromise = null;
228
- async function getUserAuthClient(config) {
229
- if (userAuthClient) return userAuthClient;
230
- if (userAuthPromise) return userAuthPromise;
195
+ const userAuthClients = /* @__PURE__ */ new Map();
196
+ const userAuthPromises = /* @__PURE__ */ new Map();
197
+ async function getUserAuthClient(config, startDir = process.cwd()) {
198
+ const cached = userAuthClients.get(startDir);
199
+ if (cached) return cached;
200
+ const pending = userAuthPromises.get(startDir);
201
+ if (pending) return pending;
231
202
  if (!config.oauthClientId || !config.oauthClientSecret) {
232
203
  console.error("DEBUG config:", config);
233
204
  throw new Error("oauthClientId and oauthClientSecret are required in config.json for user authentication.");
234
205
  }
235
- userAuthPromise = (async () => {
206
+ const promise = (async () => {
236
207
  const oauth2Client = new google.auth.OAuth2(config.oauthClientId, config.oauthClientSecret, "http://localhost:31338/oauth2callback");
237
208
  oauth2Client.on("tokens", async (tokens) => {
238
209
  try {
239
210
  await updateGoogleChatState({ oauthTokens: {
240
- ...(await readGoogleChatState()).oauthTokens,
211
+ ...(await readGoogleChatState(startDir)).oauthTokens,
241
212
  ...tokens
242
- } });
213
+ } }, startDir);
243
214
  } catch (err) {
244
215
  console.error("Failed to save refreshed Google User tokens", err);
245
216
  }
246
217
  });
247
- const state = await readGoogleChatState();
218
+ const state = await readGoogleChatState(startDir);
248
219
  if (state.oauthTokens) {
249
220
  oauth2Client.setCredentials(state.oauthTokens);
250
- userAuthClient = oauth2Client;
251
- userAuthPromise = null;
221
+ userAuthClients.set(startDir, oauth2Client);
222
+ userAuthPromises.delete(startDir);
252
223
  return oauth2Client;
253
224
  }
254
225
  const authUrl = oauth2Client.generateAuthUrl({
@@ -273,21 +244,21 @@ async function getUserAuthClient(config) {
273
244
  try {
274
245
  const { tokens } = await oauth2Client.getToken(code);
275
246
  oauth2Client.setCredentials(tokens);
276
- await updateGoogleChatState({ oauthTokens: tokens });
247
+ await updateGoogleChatState({ oauthTokens: tokens }, startDir);
277
248
  console.log("Google User authorization successful!");
278
- userAuthClient = oauth2Client;
279
- userAuthPromise = null;
249
+ userAuthClients.set(startDir, oauth2Client);
250
+ userAuthPromises.delete(startDir);
280
251
  resolve(oauth2Client);
281
252
  } catch (err) {
282
253
  console.error("Failed to get token", err);
283
- userAuthPromise = null;
254
+ userAuthPromises.delete(startDir);
284
255
  reject(err);
285
256
  }
286
257
  } else {
287
258
  res.end("Authentication failed!");
288
259
  clearTimeout(timeoutId);
289
260
  server.close();
290
- userAuthPromise = null;
261
+ userAuthPromises.delete(startDir);
291
262
  reject(/* @__PURE__ */ new Error("No code provided in OAuth callback"));
292
263
  }
293
264
  }
@@ -295,27 +266,28 @@ async function getUserAuthClient(config) {
295
266
  server.on("error", (err) => {
296
267
  console.error("Failed to start local OAuth server on port 31338", err);
297
268
  clearTimeout(timeoutId);
298
- userAuthPromise = null;
269
+ userAuthPromises.delete(startDir);
299
270
  reject(err);
300
271
  });
301
272
  server.listen(31338, "127.0.0.1", () => {
302
273
  timeoutId = setTimeout(() => {
303
274
  server.close();
304
- userAuthPromise = null;
275
+ userAuthPromises.delete(startDir);
305
276
  console.error("Google User authorization timed out after 5 minutes.");
306
277
  reject(/* @__PURE__ */ new Error("Google User authorization timed out."));
307
278
  }, 300 * 1e3);
308
279
  });
309
280
  });
310
281
  })();
311
- return userAuthPromise;
282
+ userAuthPromises.set(startDir, promise);
283
+ return promise;
312
284
  }
313
285
 
314
286
  //#endregion
315
287
  //#region src/adapter-google-chat/subscriptions.ts
316
- async function handleAddedToSpace(spaceName, externalContextId, spaceType, targetChatId, mappedChatId, config) {
288
+ async function handleAddedToSpace(spaceName, externalContextId, spaceType, targetChatId, mappedChatId, config, startDir = process.cwd()) {
317
289
  if (spaceType !== "DIRECT_MESSAGE") try {
318
- const token = (await (await getUserAuthClient(config)).getAccessToken()).token;
290
+ const token = (await (await getUserAuthClient(config, startDir)).getAccessToken()).token;
319
291
  if (token) {
320
292
  const res = await fetch("https://workspaceevents.googleapis.com/v1/subscriptions", {
321
293
  method: "POST",
@@ -342,7 +314,7 @@ async function handleAddedToSpace(spaceName, externalContextId, spaceType, targe
342
314
  expirationDate: subData.expireTime
343
315
  }
344
316
  } };
345
- });
317
+ }, startDir);
346
318
  console.log(`Created subscription ${subData.name} for space ${externalContextId}`);
347
319
  } else {
348
320
  const errText = await res.text();
@@ -365,10 +337,10 @@ async function handleAddedToSpace(spaceName, externalContextId, spaceType, targe
365
337
  console.error("Failed to send greeting on ADDED_TO_SPACE:", err);
366
338
  }
367
339
  }
368
- async function handleRemovedFromSpace(externalContextId, currentState, config) {
340
+ async function handleRemovedFromSpace(externalContextId, currentState, config, startDir = process.cwd()) {
369
341
  const subId = currentState.channelChatMap?.[externalContextId]?.subscriptionId;
370
342
  if (subId) try {
371
- const token = (await (await getUserAuthClient(config)).getAccessToken()).token;
343
+ const token = (await (await getUserAuthClient(config, startDir)).getAccessToken()).token;
372
344
  if (token) {
373
345
  const res = await fetch(`https://workspaceevents.googleapis.com/v1/${subId}`, {
374
346
  method: "DELETE",
@@ -392,12 +364,12 @@ async function handleRemovedFromSpace(externalContextId, currentState, config) {
392
364
  delete entry.expirationDate;
393
365
  }
394
366
  return { channelChatMap: map };
395
- });
367
+ }, startDir);
396
368
  }
397
369
 
398
370
  //#endregion
399
371
  //#region src/adapter-google-chat/cards.ts
400
- async function handleCardClicked(event, targetChatId, trpc) {
372
+ async function handleCardClicked(event, targetChatId, trpc, getChatApi) {
401
373
  const action = event.action;
402
374
  if (!action) return;
403
375
  const methodName = action.actionMethodName;
@@ -405,7 +377,7 @@ async function handleCardClicked(event, targetChatId, trpc) {
405
377
  if (policyId && (methodName === "approve" || methodName === "reject")) {
406
378
  const cmd = methodName === "approve" ? `/approve ${policyId}` : `/reject ${policyId}`;
407
379
  if (event.message?.name) try {
408
- const chatApi = google.chat({
380
+ const chatApi = getChatApi ? await getChatApi() : google.chat({
409
381
  version: "v1",
410
382
  auth: await getAuthClient()
411
383
  });
@@ -462,8 +434,31 @@ function getTRPCClient(options = {}) {
462
434
  })
463
435
  })] });
464
436
  }
465
- function startGoogleChatIngestion(config, trpc, filteringConfig) {
466
- const subscription = new PubSub({ projectId: config.projectId }).subscription(config.subscriptionName);
437
+ /**
438
+ * Map a quoted message's sender to a short attribution label. Returns "Bot"
439
+ * for the assistant, "You" if it's one of the configured authorized users,
440
+ * and otherwise the sender's email (preferred) or `users/{id}` resource name.
441
+ */
442
+ function formatQuotedSender(sender, authorizedUsers) {
443
+ if (!sender) return void 0;
444
+ if (sender.type === "BOT") return "Assistant";
445
+ const email = sender.email ?? void 0;
446
+ const name = sender.name ?? void 0;
447
+ if (email && isAuthorized(email, authorizedUsers) || name && isAuthorized(name, authorizedUsers)) return;
448
+ return email || sender.displayName || name || void 0;
449
+ }
450
+ function startGoogleChatIngestion(config, trpc, filteringConfig, deps = {}) {
451
+ const startDir = deps.startDir ?? process.cwd();
452
+ const subscription = deps.subscription ?? new PubSub({ projectId: config.projectId }).subscription(config.subscriptionName);
453
+ const getChatApi = async () => {
454
+ if (deps.chatApi) return deps.chatApi;
455
+ const authClient = await getAuthClient();
456
+ return google.chat({
457
+ version: "v1",
458
+ auth: authClient
459
+ });
460
+ };
461
+ const downloadAttachment$1 = deps.downloadAttachment ?? downloadAttachment;
467
462
  const seenMessageIds = /* @__PURE__ */ new Map();
468
463
  setInterval(() => {
469
464
  const now = Date.now();
@@ -482,8 +477,8 @@ function startGoogleChatIngestion(config, trpc, filteringConfig) {
482
477
  const space = isWorkspaceEvent ? eventMessage?.space : parsedData.space || eventMessage?.space;
483
478
  const senderType = eventMessage?.sender?.type || "";
484
479
  const messageId = eventMessage?.name || "";
485
- const text = (eventMessage?.argumentText || eventMessage?.text || "").trim();
486
- if (senderType === "BOT") return void message.ack();
480
+ const text = (eventMessage?.text || "").trim();
481
+ if (senderType === "BOT" && eventType !== "CARD_CLICKED") return void message.ack();
487
482
  if (messageId) {
488
483
  if (seenMessageIds.has(messageId)) return void message.ack();
489
484
  seenMessageIds.set(messageId, Date.now());
@@ -507,7 +502,7 @@ function startGoogleChatIngestion(config, trpc, filteringConfig) {
507
502
  if (authorizedByEmail && senderName && !isAuthorized(senderName, config.authorizedUsers)) {
508
503
  console.log(`Automatically authorizing user ID ${senderName} based on authorized email ${email}`);
509
504
  config.authorizedUsers.push(senderName);
510
- updateGoogleChatConfig(config).catch((err) => console.error("Failed to update config with new user ID:", err));
505
+ updateGoogleChatConfig(config, startDir).catch((err) => console.error("Failed to update config with new user ID:", err));
511
506
  }
512
507
  const identifier = email || senderName;
513
508
  const spaceName = space?.name;
@@ -516,19 +511,19 @@ function startGoogleChatIngestion(config, trpc, filteringConfig) {
516
511
  message.ack();
517
512
  return;
518
513
  }
519
- const currentState = await readGoogleChatState();
514
+ const currentState = await readGoogleChatState(startDir);
520
515
  const externalContextId = spaceName;
521
516
  const mappedChatId = currentState.channelChatMap?.[externalContextId]?.chatId;
522
517
  const isRoutingCommand = text.startsWith("/chat") || text.startsWith("/agent");
523
518
  if (eventType === "ADDED_TO_SPACE") {
524
- await handleAddedToSpace(spaceName, externalContextId, space?.type, mappedChatId, mappedChatId, config);
519
+ await handleAddedToSpace(spaceName, externalContextId, space?.type, mappedChatId, mappedChatId, config, startDir);
525
520
  if (!text) {
526
521
  message.ack();
527
522
  return;
528
523
  }
529
524
  }
530
525
  if (eventType === "REMOVED_FROM_SPACE") {
531
- await handleRemovedFromSpace(externalContextId, currentState, config);
526
+ await handleRemovedFromSpace(externalContextId, currentState, config, startDir);
532
527
  message.ack();
533
528
  return;
534
529
  }
@@ -541,13 +536,9 @@ function startGoogleChatIngestion(config, trpc, filteringConfig) {
541
536
  ...latestState.channelChatMap?.[externalContextId] || {},
542
537
  chatId: routingResult.newChatId
543
538
  }
544
- } }));
539
+ } }), startDir);
545
540
  try {
546
- const authClient = await getAuthClient();
547
- await google.chat({
548
- version: "v1",
549
- auth: authClient
550
- }).spaces.messages.create({
541
+ await (await getChatApi()).spaces.messages.create({
551
542
  parent: externalContextId,
552
543
  requestBody: { text: routingResult.text }
553
544
  });
@@ -568,7 +559,7 @@ function startGoogleChatIngestion(config, trpc, filteringConfig) {
568
559
  ...latestState.channelChatMap?.[externalContextId] || {},
569
560
  chatId: targetChatId
570
561
  }
571
- } }));
562
+ } }), startDir);
572
563
  } else {
573
564
  const isDirectMessage = space?.type === "DIRECT_MESSAGE" || space?.singleUserBotDm === true;
574
565
  const isMentioned = Array.isArray(eventMessage?.annotations) && eventMessage.annotations.some((a) => a.type === "USER_MENTION");
@@ -576,11 +567,7 @@ function startGoogleChatIngestion(config, trpc, filteringConfig) {
576
567
  if (isDirectMessage || isMentioned || isSlashCommand) {
577
568
  console.log(`Unmapped space ${externalContextId}, sending first contact warning.`);
578
569
  try {
579
- const authClient = await getAuthClient();
580
- await google.chat({
581
- version: "v1",
582
- auth: authClient
583
- }).spaces.messages.create({
570
+ await (await getChatApi()).spaces.messages.create({
584
571
  parent: externalContextId,
585
572
  requestBody: { text: "This channel/space is not currently mapped to a daemon chat. Please use `/chat [chat-id]` or `/agent [agent-id]` to map it." }
586
573
  });
@@ -598,11 +585,7 @@ function startGoogleChatIngestion(config, trpc, filteringConfig) {
598
585
  const isMentioned = Array.isArray(eventMessage?.annotations) && eventMessage.annotations.some((a) => a.type === "USER_MENTION" && a.userMention?.user?.type === "BOT");
599
586
  let isReplyToBot = false;
600
587
  if (eventMessage?.threadReply && eventMessage.thread?.name) try {
601
- const authClient = await getAuthClient();
602
- isReplyToBot = (await google.chat({
603
- version: "v1",
604
- auth: authClient
605
- }).spaces.messages.list({
588
+ isReplyToBot = (await (await getChatApi()).spaces.messages.list({
606
589
  parent: externalContextId,
607
590
  filter: `thread.name="${eventMessage.thread.name}"`
608
591
  })).data.messages?.some((m) => m.sender?.type === "BOT" || m.annotations?.some((a) => a.type === "USER_MENTION" && a.userMention?.user?.type === "BOT")) ?? false;
@@ -616,7 +599,7 @@ function startGoogleChatIngestion(config, trpc, filteringConfig) {
616
599
  }
617
600
  }
618
601
  if (eventType === "CARD_CLICKED") {
619
- await handleCardClicked(parsedData, targetChatId, trpc);
602
+ await handleCardClicked(parsedData, targetChatId, trpc, getChatApi);
620
603
  message.ack();
621
604
  return;
622
605
  }
@@ -626,15 +609,11 @@ function startGoogleChatIngestion(config, trpc, filteringConfig) {
626
609
  if (commandResult.type === "text") {
627
610
  if (commandResult.newConfig) {
628
611
  filteringConfig.filters = commandResult.newConfig.filters;
629
- await updateGoogleChatState({ filters: filteringConfig.filters });
612
+ await updateGoogleChatState({ filters: filteringConfig.filters }, startDir);
630
613
  }
631
614
  resultText = commandResult.text;
632
615
  } else if (commandResult.type === "debug") resultText = commandResult.messages.length === 0 ? "No ignored background messages found." : `**Debug Output (${commandResult.messages.length} ignored messages):**\n\n` + commandResult.messages.map((msg) => formatMessage(msg)).join("\n\n---\n\n");
633
- const authClient = await getAuthClient();
634
- await google.chat({
635
- version: "v1",
636
- auth: authClient
637
- }).spaces.messages.create({
616
+ await (await getChatApi()).spaces.messages.create({
638
617
  parent: spaceName,
639
618
  requestBody: { text: resultText }
640
619
  });
@@ -643,30 +622,53 @@ function startGoogleChatIngestion(config, trpc, filteringConfig) {
643
622
  }
644
623
  const attachments = eventMessage?.attachment || [];
645
624
  if (attachments.length > 0) {
646
- const tmpDir = path.join(getClawminiDir(process.cwd()), "tmp", "google-chat");
647
- await fs$1.mkdir(tmpDir, { recursive: true });
625
+ const tmpDir = path.join(getClawminiDir(startDir), "tmp", "google-chat");
626
+ await fsPromises.mkdir(tmpDir, { recursive: true });
648
627
  for (const att of attachments) {
649
628
  const resourceName = att.attachmentDataRef?.resourceName;
650
629
  if (resourceName) try {
651
- const buffer = await downloadAttachment(resourceName, config.maxAttachmentSizeMB);
630
+ const buffer = await downloadAttachment$1(resourceName, config.maxAttachmentSizeMB);
652
631
  const uniqueName = `${crypto.randomUUID()}-${att.contentName || "attachment"}`;
653
632
  const filePath = path.join(tmpDir, uniqueName);
654
- await fs$1.writeFile(filePath, buffer);
633
+ await fsPromises.writeFile(filePath, buffer);
655
634
  downloadedFiles.push(filePath);
656
635
  } catch (err) {
657
636
  console.error(`Error downloading attachment:`, err);
658
637
  }
659
638
  }
660
639
  }
640
+ let forwardedText = text;
641
+ const quotedMetadata = eventMessage?.quotedMessageMetadata;
642
+ if (quotedMetadata) {
643
+ let quotedText = quotedMetadata.quotedMessageSnapshot?.text;
644
+ let quotedSender = quotedMetadata.quotedMessageSnapshot?.sender;
645
+ if ((!quotedText || !quotedSender) && quotedMetadata.name) try {
646
+ const quotedRes = await (await getChatApi()).spaces.messages.get({ name: quotedMetadata.name });
647
+ quotedText = quotedText || quotedRes.data?.text || void 0;
648
+ quotedSender = quotedSender || quotedRes.data?.sender || void 0;
649
+ } catch (err) {
650
+ console.error("Failed to fetch quoted message:", err);
651
+ }
652
+ if (quotedText) {
653
+ const senderLabel = formatQuotedSender(quotedSender, config.authorizedUsers);
654
+ forwardedText = prependBlockquote(quotedText, text, senderLabel);
655
+ }
656
+ }
657
+ const gchatThreadName = eventMessage?.thread?.name ?? eventMessage?.threadName ?? void 0;
658
+ if (messageId && gchatThreadName) recordInbound({
659
+ gchatMessageName: messageId,
660
+ gchatThreadName
661
+ });
661
662
  await trpc.sendMessage.mutate({
662
663
  type: "send-message",
663
664
  client: "cli",
664
665
  data: {
665
- message: text,
666
+ message: forwardedText,
666
667
  chatId: targetChatId,
667
668
  files: downloadedFiles.length > 0 ? downloadedFiles : void 0,
668
669
  adapter: "google-chat",
669
- noWait: true
670
+ noWait: true,
671
+ ...messageId ? { externalRef: messageId } : {}
670
672
  }
671
673
  });
672
674
  console.log(`Forwarded message from ${identifier} to daemon.`);
@@ -674,7 +676,7 @@ function startGoogleChatIngestion(config, trpc, filteringConfig) {
674
676
  } catch (error) {
675
677
  console.error("Error processing Pub/Sub message:", error);
676
678
  for (const file of downloadedFiles) try {
677
- await fs$1.unlink(file);
679
+ await fsPromises.unlink(file);
678
680
  } catch (unlinkErr) {
679
681
  console.error(`Failed to delete downloaded file ${file} after error:`, unlinkErr);
680
682
  }
@@ -762,10 +764,62 @@ async function uploadFilesToDrive(files, config) {
762
764
 
763
765
  //#endregion
764
766
  //#region src/adapter-google-chat/forwarder.ts
765
- async function startDaemonToGoogleChatForwarder(trpc, config, filteringConfig, signal) {
767
+ const DEFAULT_THREAD_LOG_OPTS = {
768
+ maxToolPreview: 400,
769
+ maxLogMessageChars: 3500,
770
+ editDebounceMs: 1e3
771
+ };
772
+ function resolveThreadLogOpts(config) {
773
+ const v = config.visibility?.threadLog;
774
+ return {
775
+ maxToolPreview: v?.maxToolPreview ?? DEFAULT_THREAD_LOG_OPTS.maxToolPreview,
776
+ maxLogMessageChars: v?.maxLogMessageChars ?? DEFAULT_THREAD_LOG_OPTS.maxLogMessageChars,
777
+ editDebounceMs: v?.editDebounceMs ?? DEFAULT_THREAD_LOG_OPTS.editDebounceMs
778
+ };
779
+ }
780
+ async function resolveSpaceForChat(chatId, startDir) {
781
+ const state = await readGoogleChatState(startDir);
782
+ const entry = Object.entries(state.channelChatMap || {}).find(([, v]) => v?.chatId === chatId);
783
+ if (!entry) return null;
784
+ const [spaceName, mapping] = entry;
785
+ return {
786
+ spaceName,
787
+ threadsDisabled: mapping.threadsDisabled === true
788
+ };
789
+ }
790
+ async function startDaemonToGoogleChatForwarder(trpc, config, filteringConfig, signal, deps = {}) {
766
791
  const defaultChatId = config.chatId || "default";
792
+ const startDir = deps.startDir ?? process.cwd();
793
+ const threadLogOpts = resolveThreadLogOpts(config);
794
+ const threadsGloballyEnabled = config.visibility?.threads !== false;
795
+ const jobsMode = config.visibility?.jobs ?? "silent";
796
+ const getChatApi = async () => {
797
+ if (deps.chatApi) return deps.chatApi;
798
+ const authClient = await getAuthClient();
799
+ return google.chat({
800
+ version: "v1",
801
+ auth: authClient
802
+ });
803
+ };
767
804
  const activeSubscriptions = /* @__PURE__ */ new Map();
768
- let currentLastSyncedMessageIds = (await readGoogleChatState()).lastSyncedMessageIds || {};
805
+ /**
806
+ * When a turn has no inbound-user anchor (cron, subagent completion, any
807
+ * proactive turn), its first top-level post implicitly creates a GChat
808
+ * thread. Record `daemonMessageId -> gchatThreadName` here so a late
809
+ * `turnStarted` event can still resolve the anchor. LRU-bounded to keep
810
+ * memory predictable on long-running daemons.
811
+ */
812
+ const proactiveAnchors = /* @__PURE__ */ new Map();
813
+ const MAX_PROACTIVE_ANCHORS = 64;
814
+ const recordProactiveAnchor = (daemonMessageId, threadName) => {
815
+ while (proactiveAnchors.size >= MAX_PROACTIVE_ANCHORS) {
816
+ const oldest = proactiveAnchors.keys().next().value;
817
+ if (!oldest) break;
818
+ proactiveAnchors.delete(oldest);
819
+ }
820
+ proactiveAnchors.set(daemonMessageId, threadName);
821
+ };
822
+ let currentLastSyncedMessageIds = (await readGoogleChatState(startDir)).lastSyncedMessageIds || {};
769
823
  const saveLastMessageId = async (chatId, id) => {
770
824
  currentLastSyncedMessageIds = {
771
825
  ...currentLastSyncedMessageIds,
@@ -774,7 +828,165 @@ async function startDaemonToGoogleChatForwarder(trpc, config, filteringConfig, s
774
828
  return updateGoogleChatState((state) => ({ lastSyncedMessageIds: {
775
829
  ...state.lastSyncedMessageIds,
776
830
  ...currentLastSyncedMessageIds
777
- } }));
831
+ } }), startDir);
832
+ };
833
+ /**
834
+ * Post a top-level message (no `thread` field; GChat auto-creates a fresh
835
+ * thread). Returns the newly-created thread's `name` so callers can anchor
836
+ * subsequent threaded replies on it — used to thread activity under
837
+ * proactive turns (cron, subagent_update) that have no inbound user
838
+ * message to anchor on.
839
+ */
840
+ const postTopLevel = async (spaceName, text, cardsV2) => {
841
+ const chatApi = await getChatApi();
842
+ const extractThread = (res) => {
843
+ return (res.data ?? res)?.thread?.name ?? void 0;
844
+ };
845
+ if (cardsV2 && cardsV2.length > 0) return { threadName: extractThread(await chatApi.spaces.messages.create({
846
+ parent: spaceName,
847
+ requestBody: {
848
+ text: text || "",
849
+ cardsV2
850
+ }
851
+ })) };
852
+ if (text.length > 4e3) {
853
+ const chunks = chunkString(text, 4e3);
854
+ let firstThread;
855
+ for (const chunk of chunks) {
856
+ const res = await chatApi.spaces.messages.create({
857
+ parent: spaceName,
858
+ requestBody: { text: chunk }
859
+ });
860
+ firstThread ??= extractThread(res);
861
+ }
862
+ return { threadName: firstThread };
863
+ }
864
+ return { threadName: extractThread(await chatApi.spaces.messages.create({
865
+ parent: spaceName,
866
+ requestBody: { text }
867
+ })) };
868
+ };
869
+ const postThreaded = async (anchor, text) => {
870
+ const res = await (await getChatApi()).spaces.messages.create({
871
+ parent: anchor.spaceName,
872
+ requestBody: {
873
+ text: text || "",
874
+ thread: { name: anchor.threadName }
875
+ },
876
+ messageReplyOption: "REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD"
877
+ });
878
+ return (res.data ?? res)?.name ?? void 0;
879
+ };
880
+ const editThreaded = async (_anchor, messageName, text) => {
881
+ await (await getChatApi()).spaces.messages.update({
882
+ name: messageName,
883
+ updateMask: "text",
884
+ requestBody: { text }
885
+ });
886
+ };
887
+ const isMissingMessageError = (err) => {
888
+ return (err?.code ?? 0) === 404;
889
+ };
890
+ const turnLog = createTurnLogBuffer({
891
+ postThreaded,
892
+ editThreaded,
893
+ isMissingMessageError,
894
+ options: threadLogOpts,
895
+ threadsEnabled: threadsGloballyEnabled
896
+ });
897
+ const handlePolicyCard = async (message, spaceName) => {
898
+ const cards = buildPolicyCard(message);
899
+ try {
900
+ await postTopLevel(spaceName, "", cards);
901
+ } catch (richError) {
902
+ console.warn("Failed to send rich policy request to Google Chat, falling back to plain text:", richError);
903
+ const policyId = "requestId" in message && message.requestId || message.id;
904
+ await postTopLevel(spaceName, `Action Required: Policy Request\n\n${message.content || "A pending policy request requires your attention."}\n\nApprove: \`/approve ${policyId}\`\nReject: \`/reject ${policyId} <optional_rationale>\``);
905
+ }
906
+ };
907
+ const collapseDestination = (dest, turnId) => {
908
+ if (dest.kind !== "thread-log") return dest;
909
+ if (!threadsGloballyEnabled) return { kind: "drop" };
910
+ if (turnId && turnLog.threadsDisabledFor(turnId)) return { kind: "drop" };
911
+ return dest;
912
+ };
913
+ const handleMessageForChat = async (chatId, message) => {
914
+ const routed = routeMessage(message, filteringConfig);
915
+ const isCronHeader = jobsMode === "header" && message.role === "system" && message.event === "cron";
916
+ const effective = collapseDestination(routed, message.turnId);
917
+ if (!isCronHeader && effective.kind === "drop") return;
918
+ const space = await resolveSpaceForChat(chatId, startDir);
919
+ if (!space) {
920
+ console.warn("No active Google Chat space to reply to. Ignoring message:", message.content);
921
+ return;
922
+ }
923
+ if ("level" in message && message.level === "verbose") return;
924
+ const hasContent = !!message.content?.trim();
925
+ const files = "files" in message ? message.files ?? [] : [];
926
+ const hasFiles = files.length > 0;
927
+ if (!isCronHeader && effective.kind === "thread-log") {
928
+ if (!message.turnId) {
929
+ console.warn(`thread-log event for ${message.role} has no turnId — dropping.`);
930
+ return;
931
+ }
932
+ if (!turnLog.has(message.turnId)) return;
933
+ turnLog.append(message.turnId, message);
934
+ return;
935
+ }
936
+ if (!isCronHeader && !hasContent && !hasFiles && message.role !== "policy") return;
937
+ try {
938
+ if (message.role === "policy" && message.status === "pending") {
939
+ await handlePolicyCard(message, space.spaceName);
940
+ return;
941
+ }
942
+ let text;
943
+ if (isCronHeader) text = `🕒 ${message.jobId ?? "scheduled"}`;
944
+ else text = formatMessage(message) || "";
945
+ if (hasFiles) {
946
+ const fileNames = files.map((f) => path.basename(f)).join(", ");
947
+ if (config.driveUploadEnabled !== false && config.oauthClientId && config.oauthClientSecret) {
948
+ text += `\n\n`;
949
+ try {
950
+ const uploadResults = await uploadFilesToDrive(files, config);
951
+ for (const result of uploadResults) text += `${result}\n`;
952
+ } catch (driveAuthErr) {
953
+ console.error("Drive API/Auth Failed, degrading to local files output:", driveAuthErr);
954
+ text += `*(Files generated: ${fileNames})*`;
955
+ }
956
+ } else text += `\n\n*(Files generated: ${fileNames})*`;
957
+ }
958
+ const { threadName: createdThread } = await postTopLevel(space.spaceName, text);
959
+ if (createdThread && message.turnId) if (turnLog.has(message.turnId)) {
960
+ if (!turnLog.isAnchored(message.turnId)) turnLog.assignAnchor(message.turnId, {
961
+ spaceName: space.spaceName,
962
+ threadName: createdThread
963
+ });
964
+ } else recordProactiveAnchor(message.id, createdThread);
965
+ } catch (err) {
966
+ console.error("Failed to send message to Google Chat:", err);
967
+ }
968
+ };
969
+ const handleTurnStarted = async (chatId, turnId, rootMessageId, externalRef) => {
970
+ const space = await resolveSpaceForChat(chatId, startDir);
971
+ if (!space) {
972
+ console.warn(`turnStarted for chat ${chatId} with no mapped space.`);
973
+ return;
974
+ }
975
+ const entry = externalRef ? resolveInbound(externalRef) : null;
976
+ const proactiveThread = entry ? void 0 : proactiveAnchors.get(rootMessageId);
977
+ if (proactiveThread) proactiveAnchors.delete(rootMessageId);
978
+ const threadName = entry?.gchatThreadName ?? proactiveThread;
979
+ turnLog.start({
980
+ turnId,
981
+ threadsDisabled: space.threadsDisabled,
982
+ anchorThread: threadName ? {
983
+ spaceName: space.spaceName,
984
+ threadName
985
+ } : void 0
986
+ });
987
+ };
988
+ const handleTurnEnded = async (turnId) => {
989
+ await turnLog.end(turnId);
778
990
  };
779
991
  const startSubscriptionForChat = async (chatId) => {
780
992
  if (activeSubscriptions.has(chatId)) return;
@@ -800,120 +1012,29 @@ async function startDaemonToGoogleChatForwarder(trpc, config, filteringConfig, s
800
1012
  let retryDelay = 1e3;
801
1013
  const maxRetryDelay = 3e4;
802
1014
  let subscription = null;
803
- let messageQueue = Promise.resolve();
1015
+ let pending = Promise.resolve();
804
1016
  const connect = () => {
805
1017
  if (signal?.aborted || !activeSubscriptions.has(chatId)) return;
806
1018
  subscription = trpc.waitForMessages.subscribe({
807
1019
  chatId,
808
1020
  lastMessageId
809
1021
  }, {
810
- onData: (messages) => {
1022
+ onData: (items) => {
811
1023
  retryDelay = 1e3;
812
- if (!Array.isArray(messages) || messages.length === 0) return;
813
- messageQueue = messageQueue.then(async () => {
814
- for (const rawMessage of messages) {
1024
+ if (!Array.isArray(items) || items.length === 0) return;
1025
+ pending = pending.then(async () => {
1026
+ for (const raw of items) {
815
1027
  if (signal?.aborted || !activeSubscriptions.has(chatId)) break;
816
- const message = rawMessage;
817
- if (shouldDisplayMessage(message, filteringConfig)) {
818
- const logMessage = message;
819
- const currentState = await readGoogleChatState();
820
- let activeSpaceName;
821
- if (!activeSpaceName && currentState.channelChatMap) {
822
- const entry = Object.entries(currentState.channelChatMap).find(([_, mapChatId]) => mapChatId?.chatId === chatId);
823
- if (entry) activeSpaceName = entry[0];
824
- }
825
- if (logMessage.role === "policy" && logMessage.status === "pending") {
826
- if (!activeSpaceName) {
827
- console.warn("No active Google Chat space to reply to. Ignoring policy request:", logMessage.content);
828
- await saveLastMessageId(chatId, logMessage.id).catch(console.error);
829
- lastMessageId = logMessage.id;
830
- continue;
831
- }
832
- try {
833
- const client = await getAuthClient();
834
- const chatApi = google.chat({
835
- version: "v1",
836
- auth: client
837
- });
838
- try {
839
- await chatApi.spaces.messages.create({
840
- parent: activeSpaceName,
841
- requestBody: {
842
- text: "",
843
- cardsV2: buildPolicyCard(logMessage)
844
- }
845
- });
846
- } catch (richError) {
847
- console.warn("Failed to send rich policy request to Google Chat, falling back to plain text:", richError);
848
- const policyId = "requestId" in logMessage && logMessage.requestId || logMessage.id;
849
- await chatApi.spaces.messages.create({
850
- parent: activeSpaceName,
851
- requestBody: { text: `Action Required: Policy Request\n\n${logMessage.content || "A pending policy request requires your attention."}\n\nApprove: \`/approve ${policyId}\`\nReject: \`/reject ${policyId} <optional_rationale>\`` }
852
- });
853
- }
854
- } catch (error) {
855
- console.error("Failed to send policy request to Google Chat:", error);
856
- }
857
- await saveLastMessageId(chatId, logMessage.id).catch(console.error);
858
- lastMessageId = logMessage.id;
859
- continue;
860
- }
861
- const hasContent = !!logMessage.content?.trim();
862
- const files = "files" in logMessage ? logMessage.files : void 0;
863
- const hasFiles = Array.isArray(files) && files.length > 0;
864
- if ("level" in logMessage && logMessage.level === "verbose" || !hasContent && !hasFiles) {
865
- await saveLastMessageId(chatId, logMessage.id).catch(console.error);
866
- lastMessageId = logMessage.id;
867
- continue;
868
- }
869
- if (!activeSpaceName) {
870
- console.warn("No active Google Chat space to reply to. Ignoring message:", logMessage.content);
871
- await saveLastMessageId(chatId, logMessage.id).catch(console.error);
872
- lastMessageId = logMessage.id;
873
- continue;
874
- }
875
- try {
876
- const client = await getAuthClient();
877
- const chatApi = google.chat({
878
- version: "v1",
879
- auth: client
880
- });
881
- let text = formatMessage(logMessage) || "";
882
- if (hasFiles && files) {
883
- const fileNames = files.map((f) => path.basename(f)).join(", ");
884
- if (config.driveUploadEnabled !== false && config.oauthClientId && config.oauthClientSecret) {
885
- text += `\n\n`;
886
- try {
887
- const uploadResults = await uploadFilesToDrive(files, config);
888
- for (const result of uploadResults) text += `${result}\n`;
889
- } catch (driveAuthErr) {
890
- console.error("Drive API/Auth Failed, degrading to local files output:", driveAuthErr);
891
- text += `*(Files generated: ${fileNames})*`;
892
- }
893
- } else text += `\n\n*(Files generated: ${fileNames})*`;
894
- }
895
- if (text.length > 4e3) {
896
- const chunks = chunkString(text, 4e3);
897
- for (let i = 0; i < chunks.length; i++) {
898
- if (signal?.aborted || !activeSubscriptions.has(chatId)) break;
899
- await chatApi.spaces.messages.create({
900
- parent: activeSpaceName,
901
- requestBody: { text: chunks[i] }
902
- });
903
- }
904
- } else await chatApi.spaces.messages.create({
905
- parent: activeSpaceName,
906
- requestBody: { text }
907
- });
908
- } catch (error) {
909
- console.error("Failed to send message to Google Chat:", error);
910
- }
911
- }
912
- await saveLastMessageId(chatId, message.id).catch(console.error);
913
- lastMessageId = message.id;
1028
+ const item = raw;
1029
+ if (item.kind === "message") {
1030
+ await handleMessageForChat(chatId, item.message);
1031
+ await saveLastMessageId(chatId, item.message.id).catch(console.error);
1032
+ lastMessageId = item.message.id;
1033
+ } else if (item.event.type === "started") await handleTurnStarted(chatId, item.event.turnId, item.event.rootMessageId, item.event.externalRef);
1034
+ else await handleTurnEnded(item.event.turnId);
914
1035
  }
915
1036
  }).catch((error) => {
916
- console.error("Message queue failed, forcing reconnect...", error);
1037
+ console.error("Stream queue failed, forcing reconnect...", error);
917
1038
  subscription?.unsubscribe();
918
1039
  subscription = null;
919
1040
  if (signal?.aborted || !activeSubscriptions.has(chatId)) return;
@@ -939,12 +1060,14 @@ async function startDaemonToGoogleChatForwarder(trpc, config, filteringConfig, s
939
1060
  }
940
1061
  });
941
1062
  };
942
- activeSubscriptions.set(chatId, { unsubscribe: () => subscription?.unsubscribe() });
1063
+ activeSubscriptions.set(chatId, { unsubscribe: () => {
1064
+ subscription?.unsubscribe();
1065
+ } });
943
1066
  connect();
944
1067
  };
945
1068
  const syncSubscriptions = async () => {
946
1069
  if (signal?.aborted) return;
947
- const state = await readGoogleChatState();
1070
+ const state = await readGoogleChatState(startDir);
948
1071
  if (state.lastSyncedMessageIds) currentLastSyncedMessageIds = {
949
1072
  ...state.lastSyncedMessageIds,
950
1073
  ...currentLastSyncedMessageIds
@@ -962,11 +1085,11 @@ async function startDaemonToGoogleChatForwarder(trpc, config, filteringConfig, s
962
1085
  };
963
1086
  return new Promise((resolve) => {
964
1087
  syncSubscriptions().catch(console.error);
965
- const statePath = getGoogleChatStatePath();
1088
+ const statePath = getGoogleChatStatePath(startDir);
966
1089
  const stateDir = path.dirname(statePath);
967
1090
  if (!fs.existsSync(stateDir)) fs.mkdirSync(stateDir, { recursive: true });
968
1091
  let debounceTimer = null;
969
- const watcher = fs.watch(stateDir, (eventType, filename) => {
1092
+ const watcher = fs.watch(stateDir, (_eventType, filename) => {
970
1093
  if (filename === path.basename(statePath)) {
971
1094
  if (debounceTimer) clearTimeout(debounceTimer);
972
1095
  debounceTimer = setTimeout(() => {
@@ -978,6 +1101,7 @@ async function startDaemonToGoogleChatForwarder(trpc, config, filteringConfig, s
978
1101
  if (debounceTimer) clearTimeout(debounceTimer);
979
1102
  watcher.close();
980
1103
  for (const sub of activeSubscriptions.values()) sub.unsubscribe();
1104
+ turnLog.shutdown();
981
1105
  resolve();
982
1106
  });
983
1107
  });