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.
- package/.changeset/README.md +8 -0
- package/.changeset/config.json +14 -0
- package/.github/workflows/release.yml +49 -0
- package/CHANGELOG.md +36 -0
- package/README.md +5 -4
- package/dist/adapter-discord/index.d.mts.map +1 -1
- package/dist/adapter-discord/index.mjs +465 -282
- package/dist/adapter-discord/index.mjs.map +1 -1
- package/dist/adapter-google-chat/index.mjs +367 -243
- package/dist/adapter-google-chat/index.mjs.map +1 -1
- package/dist/cli/index.mjs +684 -24
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lite.mjs +43 -13
- package/dist/cli/lite.mjs.map +1 -1
- package/dist/cli/{propose-policy.mjs → manage-policies.mjs} +270 -47
- package/dist/cli/manage-policies.mjs.map +1 -0
- package/dist/cli/run-host.d.mts +1 -0
- package/dist/cli/run-host.mjs +3090 -0
- package/dist/cli/run-host.mjs.map +1 -0
- package/dist/config-CPFQIGdG.mjs +57 -0
- package/dist/config-CPFQIGdG.mjs.map +1 -0
- package/dist/config-Dvl-Pov4.mjs +76 -0
- package/dist/config-Dvl-Pov4.mjs.map +1 -0
- package/dist/daemon/index.d.mts.map +1 -1
- package/dist/daemon/index.mjs +970 -332
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/supervisor-actions-CiW56eLi.mjs +843 -0
- package/dist/supervisor-actions-CiW56eLi.mjs.map +1 -0
- package/dist/turn-log-buffer-DRgW53gl.mjs +767 -0
- package/dist/turn-log-buffer-DRgW53gl.mjs.map +1 -0
- package/dist/web/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
- package/dist/web/_app/immutable/chunks/BhRSsUCh.js +2 -0
- package/dist/web/_app/immutable/chunks/BiLeM2i1.js +1 -0
- package/{web/.svelte-kit/output/client/_app/immutable/chunks/CME08kGM.js → dist/web/_app/immutable/chunks/BmBj85Ll.js} +1 -1
- package/dist/web/_app/immutable/chunks/BrERcKAH.js +1 -0
- package/dist/web/_app/immutable/chunks/Bv9252RM.js +1 -0
- package/dist/web/_app/immutable/chunks/CIXNBPKi.js +1 -0
- package/dist/web/_app/immutable/chunks/DISKL3GN.js +2 -0
- package/dist/web/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
- package/dist/web/_app/immutable/chunks/DnQ3vS13.js +1 -0
- package/dist/web/_app/immutable/chunks/KsloHTKS.js +1 -0
- package/{web/.svelte-kit/output/client/_app/immutable/chunks/Ck-be5J2.js → dist/web/_app/immutable/chunks/RsHsUj-8.js} +2 -2
- package/dist/web/_app/immutable/chunks/{vDehDcuJ.js → wpfV79dV.js} +1 -1
- package/dist/web/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
- package/dist/web/_app/immutable/entry/start.Di0-Jhte.js +1 -0
- package/dist/web/_app/immutable/nodes/{0.CUGC2p-K.js → 0.DYyUA1au.js} +1 -1
- package/dist/web/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
- package/dist/web/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
- package/{web/.svelte-kit/output/client/_app/immutable/nodes/3.0arZe_Uf.js → dist/web/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
- package/dist/web/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
- package/dist/web/_app/immutable/nodes/{5.Bq2JzCEj.js → 5.DLbPVJY2.js} +1 -1
- package/dist/web/_app/version.json +1 -1
- package/dist/web/index.html +12 -12
- package/dist/workspace-oWmVh5mi.mjs +1001 -0
- package/dist/workspace-oWmVh5mi.mjs.map +1 -0
- package/docs/23_adapter_slash_autocomplete/development_log.md +19 -0
- package/docs/23_adapter_slash_autocomplete/notes.md +18 -0
- package/docs/23_adapter_slash_autocomplete/prd.md +46 -0
- package/docs/23_adapter_slash_autocomplete/questions.md +6 -0
- package/docs/23_adapter_slash_autocomplete/tickets.md +21 -0
- package/docs/24_subagent_job_policy_fixes/development_log.md +22 -0
- package/docs/24_subagent_job_policy_fixes/notes.md +28 -0
- package/docs/24_subagent_job_policy_fixes/prd.md +59 -0
- package/docs/24_subagent_job_policy_fixes/questions.md +3 -0
- package/docs/24_subagent_job_policy_fixes/tickets.md +49 -0
- package/docs/25_e2e_test_improvements/development_log.md +30 -0
- package/docs/25_e2e_test_improvements/notes.md +29 -0
- package/docs/25_e2e_test_improvements/prd.md +43 -0
- package/docs/25_e2e_test_improvements/questions.md +12 -0
- package/docs/25_e2e_test_improvements/tickets-2.md +22 -0
- package/docs/25_e2e_test_improvements/tickets.md +22 -0
- package/docs/25_policy_cwd/development_log.md +30 -0
- package/docs/25_policy_cwd/notes.md +28 -0
- package/docs/25_policy_cwd/prd.md +77 -0
- package/docs/25_policy_cwd/questions.md +6 -0
- package/docs/25_policy_cwd/tickets.md +77 -0
- package/docs/CLI_REFERENCE.md +3 -1
- package/docs/PHILOSOPHY.md +35 -0
- package/docs/adapter-visibility/SPEC.md +461 -0
- package/docs/adapter-visibility/SPEC_v2.md +202 -0
- package/docs/auto-update/SPEC.md +344 -0
- package/docs/backups/SPEC.md +296 -0
- package/docs/backups/clawmini.gitignore +69 -0
- package/docs/guides/assets/clawmini-avatar.png +0 -0
- package/docs/guides/backups.md +332 -0
- package/docs/guides/discord_adapter_setup.md +1 -1
- package/docs/guides/google_chat_adapter_setup.md +81 -0
- package/docs/unified-startup/SPEC.md +203 -0
- package/e2e/_helpers/test-environment.test.ts +49 -0
- package/e2e/_helpers/test-environment.ts +548 -0
- package/e2e/adapters/_google-chat-fixtures.ts +340 -0
- package/{src/cli/e2e → e2e/adapters}/adapter-discord.test.ts +22 -23
- package/e2e/adapters/adapter-google-chat-downtime.test.ts +157 -0
- package/e2e/adapters/adapter-google-chat-inbound.test.ts +697 -0
- package/e2e/adapters/adapter-google-chat-outbound.test.ts +297 -0
- package/e2e/adapters/adapter-google-chat-roundtrip.test.ts +56 -0
- package/e2e/adapters/adapter-google-chat-threads.test.ts +1078 -0
- package/e2e/agents/custom-api-env.test.ts +80 -0
- package/e2e/agents/export-lite-func.test.ts +104 -0
- package/e2e/agents/fallbacks.test.ts +124 -0
- package/e2e/agents/interrupt.test.ts +50 -0
- package/e2e/agents/no-reply-necessary.test.ts +57 -0
- package/e2e/agents/session-timeout-subagents.test.ts +76 -0
- package/e2e/agents/subagent-authorization.test.ts +246 -0
- package/e2e/agents/subagent-env.test.ts +49 -0
- package/e2e/agents/subagent-lifecycle.test.ts +782 -0
- package/e2e/agents/subagents-depth.test.ts +47 -0
- package/e2e/cli/agents.test.ts +176 -0
- package/e2e/cli/auto-update.test.ts +741 -0
- package/e2e/cli/basic.test.ts +44 -0
- package/{src/cli/e2e → e2e/cli}/export-lite.test.ts +16 -12
- package/e2e/cli/init-gitignore.test.ts +86 -0
- package/e2e/cli/init.test.ts +76 -0
- package/e2e/cli/messages.test.ts +363 -0
- package/e2e/cli/serve.test.ts +76 -0
- package/{src/cli/e2e → e2e/cli}/skills.test.ts +11 -10
- package/{src/cli/e2e → e2e/daemon}/daemon.test.ts +57 -195
- package/e2e/jobs/agent-jobs.test.ts +216 -0
- package/e2e/jobs/cron.test.ts +64 -0
- package/e2e/jobs/restart.test.ts +108 -0
- package/e2e/policies/approval-session.test.ts +69 -0
- package/e2e/policies/auto-create-policies-file.test.ts +35 -0
- package/e2e/policies/builtin-manage-policies.test.ts +184 -0
- package/e2e/policies/builtin-run-host.test.ts +180 -0
- package/e2e/policies/environment-policies.test.ts +177 -0
- package/e2e/policies/manage-policies.test.ts +566 -0
- package/e2e/policies/output-size.test.ts +98 -0
- package/e2e/policies/policies-context-cwd.test.ts +160 -0
- package/e2e/policies/relative-script-path.test.ts +60 -0
- package/e2e/policies/requests-show.test.ts +135 -0
- package/e2e/policies/requests.test.ts +208 -0
- package/e2e/policies/slash-policies.test.ts +308 -0
- package/e2e/policies/startup-cleanup.test.ts +48 -0
- package/e2e/routers/session-timeout.test.ts +106 -0
- package/e2e/routers/slash-model.test.ts +152 -0
- package/e2e/routers/slash-new.test.ts +50 -0
- package/e2e/routers/slash-restart-adapter.test.ts +96 -0
- package/e2e/routers/slash-restart.test.ts +114 -0
- package/e2e/routers/slash-shutdown.test.ts +55 -0
- package/e2e/routers/slash-stop.test.ts +232 -0
- package/e2e/routers/slash-upgrade.test.ts +88 -0
- package/{src/cli/e2e → e2e/sandbox}/environments.test.ts +14 -13
- package/eslint.config.js +6 -0
- package/napkin.md +1 -1
- package/package.json +8 -3
- package/src/adapter-discord/commands.test.ts +42 -0
- package/src/adapter-discord/commands.ts +33 -0
- package/src/adapter-discord/config.ts +12 -0
- package/src/adapter-discord/forwarder.test.ts +499 -21
- package/src/adapter-discord/forwarder.ts +343 -124
- package/src/adapter-discord/inbound-cache.test.ts +47 -0
- package/src/adapter-discord/inbound-cache.ts +37 -0
- package/src/adapter-discord/index.test.ts +67 -2
- package/src/adapter-discord/index.ts +84 -216
- package/src/adapter-discord/interactions.test.ts +54 -3
- package/src/adapter-discord/interactions.ts +97 -53
- package/src/adapter-discord/processMessage.ts +239 -0
- package/src/adapter-discord/state.ts +1 -0
- package/src/adapter-google-chat/auth.test.ts +9 -5
- package/src/adapter-google-chat/auth.ts +29 -23
- package/src/adapter-google-chat/cards.ts +7 -2
- package/src/adapter-google-chat/client.test.ts +37 -2
- package/src/adapter-google-chat/client.ts +138 -38
- package/src/adapter-google-chat/config.ts +19 -0
- package/src/adapter-google-chat/forwarder.test.ts +81 -56
- package/src/adapter-google-chat/forwarder.ts +394 -185
- package/src/adapter-google-chat/inbound-cache.test.ts +61 -0
- package/src/adapter-google-chat/inbound-cache.ts +36 -0
- package/src/adapter-google-chat/state.test.ts +1 -0
- package/src/adapter-google-chat/state.ts +9 -1
- package/src/adapter-google-chat/subscriptions.ts +8 -6
- package/src/cli/builtin-policies.ts +44 -0
- package/src/cli/commands/agents.ts +59 -5
- package/src/cli/commands/down.ts +54 -2
- package/src/cli/commands/environments.ts +8 -2
- package/src/cli/commands/init.ts +31 -0
- package/src/cli/commands/logs.ts +116 -0
- package/src/cli/commands/policies.ts +6 -4
- package/src/cli/commands/serve.test.ts +67 -0
- package/src/cli/commands/serve.ts +284 -0
- package/src/cli/commands/up.ts +122 -2
- package/src/cli/commands/web-api/agents.ts +3 -2
- package/src/cli/index.ts +4 -0
- package/src/cli/install-detection.test.ts +72 -0
- package/src/cli/install-detection.ts +48 -0
- package/src/cli/lite.ts +54 -22
- package/src/cli/manage-policies-utils.ts +104 -0
- package/src/cli/manage-policies.ts +291 -0
- package/src/cli/run-host.ts +45 -0
- package/src/cli/supervisor-actions.ts +267 -0
- package/src/cli/supervisor-control.test.ts +129 -0
- package/src/cli/supervisor-control.ts +155 -0
- package/src/cli/supervisor-pid.ts +68 -0
- package/src/cli/supervisor.ts +277 -0
- package/src/daemon/agent/agent-context.ts +11 -11
- package/src/daemon/agent/agent-session.ts +8 -1
- package/src/daemon/agent/chat-logger.test.ts +78 -9
- package/src/daemon/agent/chat-logger.ts +25 -5
- package/src/daemon/agent/turn-registry.test.ts +89 -0
- package/src/daemon/agent/turn-registry.ts +94 -0
- package/src/daemon/agent/types.ts +2 -0
- package/src/daemon/api/agent-policy-endpoints.ts +263 -0
- package/src/daemon/api/agent-router.ts +47 -126
- package/src/daemon/api/index.test.ts +1 -0
- package/src/daemon/api/policy-request.test.ts +7 -5
- package/src/daemon/api/router-utils.ts +6 -5
- package/src/daemon/api/subagent-router.ts +110 -74
- package/src/daemon/api/subagent-utils.test.ts +60 -0
- package/src/daemon/api/subagent-utils.ts +113 -87
- package/src/daemon/api/user-router.ts +34 -8
- package/src/daemon/auth.ts +1 -0
- package/src/daemon/cron.test.ts +62 -4
- package/src/daemon/cron.ts +42 -16
- package/src/daemon/events.ts +65 -0
- package/src/daemon/index.ts +24 -1
- package/src/daemon/message-interruption.test.ts +1 -0
- package/src/daemon/message-jobs.test.ts +1 -0
- package/src/daemon/message.ts +78 -14
- package/src/daemon/observation.test.ts +26 -18
- package/src/daemon/pending-replies.test.ts +112 -0
- package/src/daemon/pending-replies.ts +162 -0
- package/src/daemon/policy-request-service.ts +3 -1
- package/src/daemon/policy-utils.test.ts +66 -1
- package/src/daemon/policy-utils.ts +126 -1
- package/src/daemon/request-store.ts +31 -0
- package/src/daemon/routers/session-timeout.ts +4 -0
- package/src/daemon/routers/slash-model.test.ts +344 -0
- package/src/daemon/routers/slash-model.ts +207 -0
- package/src/daemon/routers/slash-policies.test.ts +38 -32
- package/src/daemon/routers/slash-policies.ts +84 -33
- package/src/daemon/routers/slash-restart.test.ts +69 -0
- package/src/daemon/routers/slash-restart.ts +36 -0
- package/src/daemon/routers/slash-shutdown.test.ts +50 -0
- package/src/daemon/routers/slash-shutdown.ts +28 -0
- package/src/daemon/routers/slash-upgrade.test.ts +116 -0
- package/src/daemon/routers/slash-upgrade.ts +76 -0
- package/src/daemon/routers/types.ts +7 -0
- package/src/daemon/routers.ts +16 -0
- package/src/shared/adapters/blockquote.test.ts +28 -0
- package/src/shared/adapters/blockquote.ts +20 -0
- package/src/shared/adapters/filtering.test.ts +224 -10
- package/src/shared/adapters/filtering.ts +95 -7
- package/src/shared/adapters/inbound-cache.test.ts +48 -0
- package/src/shared/adapters/inbound-cache.ts +54 -0
- package/src/shared/adapters/turn-log-buffer.ts +266 -0
- package/src/shared/adapters/turn-log.test.ts +389 -0
- package/src/shared/adapters/turn-log.ts +357 -0
- package/src/shared/agent-utils.ts +12 -5
- package/src/shared/chats.test.ts +4 -0
- package/src/shared/chats.ts +9 -0
- package/src/shared/config.ts +16 -1
- package/src/shared/lite.ts +76 -2
- package/src/shared/policies.ts +26 -0
- package/src/shared/template-manifest.ts +267 -0
- package/src/shared/utils/shell.ts +61 -0
- package/src/shared/version.ts +34 -0
- package/src/shared/workspace.test.ts +217 -0
- package/src/shared/workspace.ts +626 -48
- package/templates/environments/cladding/allowlist-domain.mjs +125 -0
- package/templates/environments/cladding/env.json +21 -1
- package/templates/environments/cladding/run-with-network.mjs +54 -0
- package/templates/environments/macos-proxy/allowlist-domain.mjs +95 -0
- package/templates/environments/macos-proxy/env.json +8 -1
- package/templates/environments/macos-proxy/proxy.mjs +0 -1
- package/templates/gemini/template.json +5 -0
- package/templates/gemini-claw/template.json +13 -0
- package/templates/skills/clawmini-requests/SKILL.md +69 -10
- package/templates/skills/run-host/SKILL.md +51 -0
- package/templates/skills/skill-creator/SKILL.md +4 -3
- package/templates/skills/skill-creator/scripts/validate.sh +52 -0
- package/tsdown.config.ts +10 -1
- package/vitest.config.ts +2 -2
- package/web/.svelte-kit/ambient.d.ts +292 -118
- package/web/.svelte-kit/generated/server/internal.js +1 -1
- package/web/.svelte-kit/output/client/.vite/manifest.json +126 -136
- package/web/.svelte-kit/output/client/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/BhRSsUCh.js +2 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/BiLeM2i1.js +1 -0
- package/{dist/web/_app/immutable/chunks/CME08kGM.js → web/.svelte-kit/output/client/_app/immutable/chunks/BmBj85Ll.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/BrERcKAH.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/Bv9252RM.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/CIXNBPKi.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/DISKL3GN.js +2 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/DnQ3vS13.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/KsloHTKS.js +1 -0
- package/{dist/web/_app/immutable/chunks/Ck-be5J2.js → web/.svelte-kit/output/client/_app/immutable/chunks/RsHsUj-8.js} +2 -2
- package/web/.svelte-kit/output/client/_app/immutable/chunks/{vDehDcuJ.js → wpfV79dV.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
- package/web/.svelte-kit/output/client/_app/immutable/entry/start.Di0-Jhte.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/nodes/{0.CUGC2p-K.js → 0.DYyUA1au.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
- package/{dist/web/_app/immutable/nodes/3.0arZe_Uf.js → web/.svelte-kit/output/client/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
- package/web/.svelte-kit/output/client/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
- package/web/.svelte-kit/output/client/_app/immutable/nodes/{5.Bq2JzCEj.js → 5.DLbPVJY2.js} +1 -1
- package/web/.svelte-kit/output/client/_app/version.json +1 -1
- package/web/.svelte-kit/output/server/.vite/manifest.json +12 -10
- package/web/.svelte-kit/output/server/chunks/Icon.js +1 -1
- package/web/.svelte-kit/output/server/chunks/client.js +1 -1
- package/web/.svelte-kit/output/server/chunks/exports.js +1 -1
- package/web/.svelte-kit/output/server/chunks/index-server.js +2 -1
- package/web/.svelte-kit/output/server/chunks/internal.js +1 -1
- package/web/.svelte-kit/output/server/chunks/render-context.js +77 -0
- package/web/.svelte-kit/output/server/chunks/root.js +739 -788
- package/web/.svelte-kit/output/server/chunks/shared.js +234 -21
- package/web/.svelte-kit/output/server/index.js +126 -90
- package/web/.svelte-kit/output/server/manifest-full.js +1 -1
- package/web/.svelte-kit/output/server/manifest.js +1 -1
- package/web/.svelte-kit/output/server/nodes/0.js +1 -1
- package/web/.svelte-kit/output/server/nodes/1.js +1 -1
- package/web/.svelte-kit/output/server/nodes/2.js +1 -1
- package/web/.svelte-kit/output/server/nodes/3.js +1 -1
- package/web/.svelte-kit/output/server/nodes/4.js +1 -1
- package/web/.svelte-kit/output/server/nodes/5.js +1 -1
- package/web/.svelte-kit/output/server/remote-entry.js +245 -81
- package/web/.svelte-kit/tsconfig.json +4 -1
- package/dist/cli/propose-policy.mjs.map +0 -1
- package/dist/lite-CBxOT1y5.mjs +0 -241
- package/dist/lite-CBxOT1y5.mjs.map +0 -1
- package/dist/routing-D8rTxtaV.mjs +0 -245
- package/dist/routing-D8rTxtaV.mjs.map +0 -1
- package/dist/web/_app/immutable/chunks/B6YN0Nuq.js +0 -1
- package/dist/web/_app/immutable/chunks/BmRlVmv6.js +0 -1
- package/dist/web/_app/immutable/chunks/CK9JZLaG.js +0 -2
- package/dist/web/_app/immutable/chunks/Ck3rYNON.js +0 -1
- package/dist/web/_app/immutable/chunks/D5iV40bG.js +0 -1
- package/dist/web/_app/immutable/chunks/DMtIqaiV.js +0 -2
- package/dist/web/_app/immutable/chunks/DhD271EB.js +0 -1
- package/dist/web/_app/immutable/chunks/DpuLqk8d.js +0 -1
- package/dist/web/_app/immutable/chunks/DsIToJCP.js +0 -1
- package/dist/web/_app/immutable/entry/app.BCSV3nrG.js +0 -2
- package/dist/web/_app/immutable/entry/start.D4eLEZUM.js +0 -1
- package/dist/web/_app/immutable/nodes/1.CGC_42IQ.js +0 -1
- package/dist/web/_app/immutable/nodes/4.ClM1bXLE.js +0 -60
- package/dist/workspace-BJmJBfKi.mjs +0 -456
- package/dist/workspace-BJmJBfKi.mjs.map +0 -1
- package/src/cli/e2e/agents.test.ts +0 -140
- package/src/cli/e2e/basic.test.ts +0 -43
- package/src/cli/e2e/cron.test.ts +0 -132
- package/src/cli/e2e/export-lite-func.test.ts +0 -206
- package/src/cli/e2e/fallbacks.test.ts +0 -175
- package/src/cli/e2e/init.test.ts +0 -77
- package/src/cli/e2e/messages.test.ts +0 -332
- package/src/cli/e2e/propose-policy.test.ts +0 -203
- package/src/cli/e2e/requests.test.ts +0 -180
- package/src/cli/e2e/session-timeout.test.ts +0 -192
- package/src/cli/e2e/slash-new.test.ts +0 -93
- package/src/cli/e2e/subagents.test.ts +0 -106
- package/src/cli/e2e/utils.ts +0 -66
- package/src/cli/propose-policy.ts +0 -91
- package/web/.svelte-kit/output/client/_app/immutable/chunks/B6YN0Nuq.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/BmRlVmv6.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/CK9JZLaG.js +0 -2
- package/web/.svelte-kit/output/client/_app/immutable/chunks/Ck3rYNON.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/D5iV40bG.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/DMtIqaiV.js +0 -2
- package/web/.svelte-kit/output/client/_app/immutable/chunks/DhD271EB.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/DpuLqk8d.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/DsIToJCP.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/entry/app.BCSV3nrG.js +0 -2
- package/web/.svelte-kit/output/client/_app/immutable/entry/start.D4eLEZUM.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/1.CGC_42IQ.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/4.ClM1bXLE.js +0 -60
- package/web/.svelte-kit/output/server/chunks/false.js +0 -4
- /package/dist/cli/{propose-policy.d.mts → manage-policies.d.mts} +0 -0
- /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 {
|
|
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
|
-
|
|
10
|
-
await
|
|
11
|
-
await
|
|
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(
|
|
15
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
51
|
-
'
|
|
52
|
-
|
|
53
|
-
|
|
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
|
+
});
|