clawmini 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/{G_zz-Gou.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.CYS8iApT.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.Dr0ot9sV.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.BBGQ_i84.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 +42 -13
- 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 -176
- package/web/.svelte-kit/generated/server/internal.js +1 -1
- package/web/.svelte-kit/output/client/.vite/manifest.json +127 -137
- 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/{G_zz-Gou.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.CYS8iApT.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.Dr0ot9sV.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.BBGQ_i84.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/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/chunks/bBmtyQMj.js +0 -1
- package/dist/web/_app/immutable/entry/app.CJmSwntr.js +0 -2
- package/dist/web/_app/immutable/entry/start.ZpUrT2ak.js +0 -1
- package/dist/web/_app/immutable/nodes/1.Bli0Hqzn.js +0 -1
- package/dist/web/_app/immutable/nodes/4.oBhvQhcA.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/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/chunks/bBmtyQMj.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/entry/app.CJmSwntr.js +0 -2
- package/web/.svelte-kit/output/client/_app/immutable/entry/start.ZpUrT2ak.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/1.Bli0Hqzn.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/4.oBhvQhcA.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
|
@@ -5,8 +5,10 @@ import {
|
|
|
5
5
|
TextInputStyle,
|
|
6
6
|
type Interaction,
|
|
7
7
|
} from 'discord.js';
|
|
8
|
-
import { readDiscordState } from './state.js';
|
|
9
8
|
import type { DiscordConfig } from './config.js';
|
|
9
|
+
import { readDiscordState } from './state.js';
|
|
10
|
+
import { type FilteringConfig } from '../shared/adapters/filtering.js';
|
|
11
|
+
import { processDiscordMessage } from './processMessage.js';
|
|
10
12
|
|
|
11
13
|
function isAuthorized(userId: string, authorizedUserId: string): boolean {
|
|
12
14
|
return userId === authorizedUserId;
|
|
@@ -16,9 +18,16 @@ export async function handleDiscordInteraction(
|
|
|
16
18
|
interaction: Interaction,
|
|
17
19
|
config: DiscordConfig,
|
|
18
20
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
-
trpc: any
|
|
21
|
+
trpc: any,
|
|
22
|
+
filteringConfig: FilteringConfig
|
|
20
23
|
) {
|
|
21
|
-
if (
|
|
24
|
+
if (
|
|
25
|
+
!interaction.isButton() &&
|
|
26
|
+
!interaction.isModalSubmit() &&
|
|
27
|
+
!interaction.isChatInputCommand()
|
|
28
|
+
) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
22
31
|
|
|
23
32
|
if (!isAuthorized(interaction.user.id, config.authorizedUserId)) {
|
|
24
33
|
if (interaction.isRepliable()) {
|
|
@@ -30,6 +39,48 @@ export async function handleDiscordInteraction(
|
|
|
30
39
|
return;
|
|
31
40
|
}
|
|
32
41
|
|
|
42
|
+
if (interaction.isChatInputCommand()) {
|
|
43
|
+
const { commandName } = interaction;
|
|
44
|
+
let commandStr = `/${commandName}`;
|
|
45
|
+
|
|
46
|
+
if (commandName === 'approve' || commandName === 'reject') {
|
|
47
|
+
const policyId = interaction.options.getString('policy_id');
|
|
48
|
+
if (policyId) commandStr += ` ${policyId}`;
|
|
49
|
+
}
|
|
50
|
+
if (commandName === 'reject') {
|
|
51
|
+
const rationale = interaction.options.getString('rationale');
|
|
52
|
+
if (rationale) commandStr += ` ${rationale}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await interaction.deferReply({ ephemeral: true });
|
|
56
|
+
|
|
57
|
+
const currentState = await readDiscordState();
|
|
58
|
+
const targetChatId = interaction.channelId
|
|
59
|
+
? currentState.channelChatMap?.[interaction.channelId]?.chatId || config.chatId
|
|
60
|
+
: config.chatId;
|
|
61
|
+
|
|
62
|
+
let replied = false;
|
|
63
|
+
await processDiscordMessage(
|
|
64
|
+
commandStr,
|
|
65
|
+
interaction.user,
|
|
66
|
+
interaction.channelId,
|
|
67
|
+
interaction.guild,
|
|
68
|
+
async (text) => {
|
|
69
|
+
replied = true;
|
|
70
|
+
await interaction.followUp({ content: text, ephemeral: true });
|
|
71
|
+
},
|
|
72
|
+
config,
|
|
73
|
+
trpc,
|
|
74
|
+
filteringConfig,
|
|
75
|
+
{ explicitChatId: targetChatId, mentionsBot: true }
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (!replied) {
|
|
79
|
+
await interaction.deleteReply();
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
33
84
|
if (interaction.isButton()) {
|
|
34
85
|
if (
|
|
35
86
|
interaction.customId.startsWith('approve_') ||
|
|
@@ -46,30 +97,27 @@ export async function handleDiscordInteraction(
|
|
|
46
97
|
|
|
47
98
|
await interaction.update({ components: [] });
|
|
48
99
|
await interaction.followUp({ content: `Approving policy ${policyId}...`, ephemeral: true });
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
ephemeral: true,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
100
|
+
|
|
101
|
+
const currentState = await readDiscordState();
|
|
102
|
+
const targetChatId =
|
|
103
|
+
explicitChatId ||
|
|
104
|
+
(interaction.channelId
|
|
105
|
+
? currentState.channelChatMap?.[interaction.channelId]?.chatId || config.chatId
|
|
106
|
+
: config.chatId);
|
|
107
|
+
|
|
108
|
+
await processDiscordMessage(
|
|
109
|
+
`/approve ${policyId}`,
|
|
110
|
+
interaction.user,
|
|
111
|
+
interaction.channelId,
|
|
112
|
+
interaction.guild,
|
|
113
|
+
async (text) => {
|
|
114
|
+
await interaction.followUp({ content: text, ephemeral: true });
|
|
115
|
+
},
|
|
116
|
+
config,
|
|
117
|
+
trpc,
|
|
118
|
+
filteringConfig,
|
|
119
|
+
{ explicitChatId: targetChatId, mentionsBot: true }
|
|
120
|
+
);
|
|
73
121
|
} else if (
|
|
74
122
|
interaction.customId.startsWith('reject_') ||
|
|
75
123
|
interaction.customId.startsWith('reject|')
|
|
@@ -123,34 +171,30 @@ export async function handleDiscordInteraction(
|
|
|
123
171
|
ephemeral: true,
|
|
124
172
|
});
|
|
125
173
|
} else {
|
|
126
|
-
await interaction.
|
|
174
|
+
await interaction.deferReply({ ephemeral: true });
|
|
175
|
+
await interaction.followUp({ content: `Rejecting policy ${policyId}...`, ephemeral: true });
|
|
127
176
|
}
|
|
128
177
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
await interaction.followUp({
|
|
150
|
-
content: `Failed to reject policy ${policyId}.`,
|
|
151
|
-
ephemeral: true,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
178
|
+
const currentState = await readDiscordState();
|
|
179
|
+
const targetChatId =
|
|
180
|
+
explicitChatId ||
|
|
181
|
+
(interaction.channelId
|
|
182
|
+
? currentState.channelChatMap?.[interaction.channelId]?.chatId || config.chatId
|
|
183
|
+
: config.chatId);
|
|
184
|
+
|
|
185
|
+
await processDiscordMessage(
|
|
186
|
+
command,
|
|
187
|
+
interaction.user,
|
|
188
|
+
interaction.channelId,
|
|
189
|
+
interaction.guild,
|
|
190
|
+
async (text) => {
|
|
191
|
+
await interaction.followUp({ content: text, ephemeral: true });
|
|
192
|
+
},
|
|
193
|
+
config,
|
|
194
|
+
trpc,
|
|
195
|
+
filteringConfig,
|
|
196
|
+
{ explicitChatId: targetChatId, mentionsBot: true }
|
|
197
|
+
);
|
|
154
198
|
}
|
|
155
199
|
}
|
|
156
200
|
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { readDiscordState, updateDiscordState } from './state.js';
|
|
2
|
+
import type { DiscordConfig } from './config.js';
|
|
3
|
+
import { recordInbound } from './inbound-cache.js';
|
|
4
|
+
import { handleAdapterCommand, type CommandTrpcClient } from '../shared/adapters/commands.js';
|
|
5
|
+
import { formatMessage, type FilteringConfig } from '../shared/adapters/filtering.js';
|
|
6
|
+
import { handleRoutingCommand, type RoutingTrpcClient } from '../shared/adapters/routing.js';
|
|
7
|
+
import { prependBlockquote } from '../shared/adapters/blockquote.js';
|
|
8
|
+
import { getClawminiDir } from '../shared/workspace.js';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import fs from 'node:fs/promises';
|
|
11
|
+
|
|
12
|
+
export type ProcessMessageOptions = {
|
|
13
|
+
mentionsBot?: boolean;
|
|
14
|
+
isReplyToBot?: boolean;
|
|
15
|
+
attachments?: { name: string; size: number; url: string }[];
|
|
16
|
+
referenceContent?: string;
|
|
17
|
+
referenceAuthor?: string;
|
|
18
|
+
explicitChatId?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Discord message id of the inbound. Recorded in the in-memory inbound
|
|
21
|
+
* cache and sent to the daemon as `externalRef` so the forwarder can
|
|
22
|
+
* resolve the user's message and start a Discord thread anchored on it
|
|
23
|
+
* when `turnStarted` arrives.
|
|
24
|
+
*/
|
|
25
|
+
messageId?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export async function processDiscordMessage(
|
|
29
|
+
content: string,
|
|
30
|
+
author: { id: string; tag: string; bot?: boolean },
|
|
31
|
+
channelId: string | null,
|
|
32
|
+
guild: unknown | null,
|
|
33
|
+
reply: (text: string) => Promise<unknown>,
|
|
34
|
+
config: DiscordConfig,
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
trpc: any,
|
|
37
|
+
filteringConfig: FilteringConfig,
|
|
38
|
+
options: ProcessMessageOptions = {}
|
|
39
|
+
) {
|
|
40
|
+
if (author.bot) return;
|
|
41
|
+
|
|
42
|
+
const externalContextId = channelId || 'default';
|
|
43
|
+
const currentState = await readDiscordState();
|
|
44
|
+
const mappedChatId =
|
|
45
|
+
options.explicitChatId || (channelId ? currentState.channelChatMap?.[channelId]?.chatId : null);
|
|
46
|
+
const isRoutingCommand = content.startsWith('/chat') || content.startsWith('/agent');
|
|
47
|
+
|
|
48
|
+
// Enforce requireMention config for guild messages
|
|
49
|
+
if (guild && channelId) {
|
|
50
|
+
const channelConfig = currentState.channelChatMap?.[channelId];
|
|
51
|
+
const requiresMention =
|
|
52
|
+
channelConfig?.requireMention !== undefined
|
|
53
|
+
? channelConfig.requireMention
|
|
54
|
+
: config.requireMention;
|
|
55
|
+
|
|
56
|
+
if (requiresMention) {
|
|
57
|
+
if (!options.mentionsBot && !options.isReplyToBot) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isAuthorized(userId: string, authorizedUserId: string): boolean {
|
|
64
|
+
return userId === authorizedUserId;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Check if the user is authorized
|
|
68
|
+
if (!isAuthorized(author.id, config.authorizedUserId)) {
|
|
69
|
+
console.log(`Unauthorized message from ${author.tag} (${author.id}) ignored.`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log(`Received message from ${author.tag}: ${content}`);
|
|
74
|
+
|
|
75
|
+
if (isRoutingCommand) {
|
|
76
|
+
const stringChatMap = Object.fromEntries(
|
|
77
|
+
Object.entries(currentState.channelChatMap || {}).map(([k, v]) => [k, v.chatId || ''])
|
|
78
|
+
);
|
|
79
|
+
const routingResult = await handleRoutingCommand(
|
|
80
|
+
content,
|
|
81
|
+
externalContextId,
|
|
82
|
+
stringChatMap,
|
|
83
|
+
'discord',
|
|
84
|
+
trpc as unknown as RoutingTrpcClient
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
if (routingResult) {
|
|
88
|
+
if (routingResult.type === 'mapped') {
|
|
89
|
+
await updateDiscordState((latestState) => ({
|
|
90
|
+
channelChatMap: {
|
|
91
|
+
...(latestState.channelChatMap || {}),
|
|
92
|
+
[externalContextId]: {
|
|
93
|
+
...(latestState.channelChatMap?.[externalContextId] || {}),
|
|
94
|
+
chatId: routingResult.newChatId,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
await reply(routingResult.text);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let targetChatId = mappedChatId;
|
|
105
|
+
|
|
106
|
+
if (!targetChatId && !isRoutingCommand) {
|
|
107
|
+
const isFirstEverMessage =
|
|
108
|
+
!currentState.channelChatMap ||
|
|
109
|
+
Object.values(currentState.channelChatMap).every((entry) => !entry.chatId);
|
|
110
|
+
|
|
111
|
+
if (isFirstEverMessage) {
|
|
112
|
+
targetChatId = config.chatId || 'default';
|
|
113
|
+
console.log(
|
|
114
|
+
`First contact detected. Automatically mapping channel ${externalContextId} to chat ${targetChatId}.`
|
|
115
|
+
);
|
|
116
|
+
await updateDiscordState((latestState) => ({
|
|
117
|
+
channelChatMap: {
|
|
118
|
+
...(latestState.channelChatMap || {}),
|
|
119
|
+
[externalContextId]: {
|
|
120
|
+
...(latestState.channelChatMap?.[externalContextId] || {}),
|
|
121
|
+
chatId: targetChatId as string,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
}));
|
|
125
|
+
} else {
|
|
126
|
+
const isDirectMessage = !guild;
|
|
127
|
+
const isSlashCommand = content.startsWith('/');
|
|
128
|
+
if (isDirectMessage || options.mentionsBot || isSlashCommand) {
|
|
129
|
+
console.log(`Unmapped channel ${externalContextId}, sending first contact warning.`);
|
|
130
|
+
await reply(
|
|
131
|
+
'This channel/space is not currently mapped to a daemon chat. Please use `/chat [chat-id]` or `/agent [agent-id]` to map it.'
|
|
132
|
+
);
|
|
133
|
+
} else {
|
|
134
|
+
console.log(`Unmapped channel ${externalContextId}, silently ignoring background message.`);
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Fallback typing safeguard
|
|
141
|
+
if (!targetChatId) targetChatId = config.chatId || 'default';
|
|
142
|
+
|
|
143
|
+
const commandResult = await handleAdapterCommand(
|
|
144
|
+
content,
|
|
145
|
+
filteringConfig,
|
|
146
|
+
trpc as unknown as CommandTrpcClient,
|
|
147
|
+
targetChatId
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
if (commandResult) {
|
|
151
|
+
if (commandResult.type === 'text') {
|
|
152
|
+
if (commandResult.newConfig) {
|
|
153
|
+
filteringConfig.filters = commandResult.newConfig.filters;
|
|
154
|
+
await updateDiscordState({ filters: filteringConfig.filters });
|
|
155
|
+
}
|
|
156
|
+
await reply(commandResult.text);
|
|
157
|
+
} else if (commandResult.type === 'debug') {
|
|
158
|
+
// Debug output echoes raw message content (which may include @everyone /
|
|
159
|
+
// @here as a substring). The `reply` lambda already strips mentions, so
|
|
160
|
+
// there's no extra escaping to do here.
|
|
161
|
+
const formatted =
|
|
162
|
+
commandResult.messages.length === 0
|
|
163
|
+
? 'No ignored background messages found.'
|
|
164
|
+
: `**Debug Output (${commandResult.messages.length} ignored messages):**\n\n` +
|
|
165
|
+
commandResult.messages.map((msg) => formatMessage(msg)).join('\n\n---\n\n');
|
|
166
|
+
await reply(formatted);
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const downloadedFiles: string[] = [];
|
|
172
|
+
if (options.attachments && options.attachments.length > 0) {
|
|
173
|
+
const tmpDir = path.join(getClawminiDir(process.cwd()), 'tmp', 'discord');
|
|
174
|
+
await fs.mkdir(tmpDir, { recursive: true });
|
|
175
|
+
const maxSizeMB = config.maxAttachmentSizeMB ?? 25;
|
|
176
|
+
const maxSizeBytes = maxSizeMB * 1024 * 1024;
|
|
177
|
+
|
|
178
|
+
for (const attachment of options.attachments) {
|
|
179
|
+
if (attachment.size > maxSizeBytes) {
|
|
180
|
+
console.warn(
|
|
181
|
+
`Attachment ${attachment.name} exceeds size limit (${maxSizeMB}MB). Ignoring.`
|
|
182
|
+
);
|
|
183
|
+
await reply(
|
|
184
|
+
`Warning: Attachment ${attachment.name} exceeds the size limit of ${maxSizeMB}MB and was ignored.`
|
|
185
|
+
);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const res = await fetch(attachment.url);
|
|
191
|
+
if (!res.ok) {
|
|
192
|
+
console.error(`Failed to download attachment ${attachment.name}`);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const uniqueName = `${Date.now()}-${attachment.name}`;
|
|
197
|
+
const filePath = path.join(tmpDir, uniqueName);
|
|
198
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
199
|
+
await fs.writeFile(filePath, Buffer.from(arrayBuffer));
|
|
200
|
+
downloadedFiles.push(filePath);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
console.error(`Error downloading attachment ${attachment.name}:`, err);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let finalContent = content;
|
|
208
|
+
|
|
209
|
+
if (options.referenceContent) {
|
|
210
|
+
finalContent = prependBlockquote(
|
|
211
|
+
options.referenceContent,
|
|
212
|
+
finalContent,
|
|
213
|
+
options.referenceAuthor
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log(`Forwarding message to daemon: ${finalContent}`);
|
|
218
|
+
if (options.messageId && channelId) {
|
|
219
|
+
recordInbound({ messageId: options.messageId, channelId });
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
await trpc.sendMessage.mutate({
|
|
223
|
+
type: 'send-message',
|
|
224
|
+
client: 'cli',
|
|
225
|
+
data: {
|
|
226
|
+
message: finalContent,
|
|
227
|
+
chatId: targetChatId,
|
|
228
|
+
files: downloadedFiles.length > 0 ? downloadedFiles : undefined,
|
|
229
|
+
adapter: 'discord',
|
|
230
|
+
noWait: true,
|
|
231
|
+
...(options.messageId ? { externalRef: options.messageId } : {}),
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
console.log('Message forwarded to daemon successfully.');
|
|
235
|
+
} catch (error) {
|
|
236
|
+
console.error('Failed to forward message to daemon:', error);
|
|
237
|
+
await reply('Failed to forward message to the daemon. Please check the logs.');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -71,17 +71,21 @@ describe('auth.ts', () => {
|
|
|
71
71
|
const newTokens = { access_token: 'new_token' };
|
|
72
72
|
await tokenCallback(newTokens);
|
|
73
73
|
|
|
74
|
-
expect(state.updateGoogleChatState).toHaveBeenCalledWith(
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
expect(state.updateGoogleChatState).toHaveBeenCalledWith(
|
|
75
|
+
{
|
|
76
|
+
oauthTokens: {
|
|
77
|
+
access_token: 'new_token',
|
|
78
|
+
},
|
|
77
79
|
},
|
|
78
|
-
|
|
80
|
+
expect.any(String)
|
|
81
|
+
);
|
|
79
82
|
|
|
80
83
|
expect(state.updateGoogleChatState).not.toHaveBeenCalledWith(
|
|
81
84
|
expect.objectContaining({
|
|
82
85
|
lastSyncedMessageIds: { default: '123' },
|
|
83
86
|
activeSpaceName: 'Space1',
|
|
84
|
-
})
|
|
87
|
+
}),
|
|
88
|
+
expect.any(String)
|
|
85
89
|
);
|
|
86
90
|
});
|
|
87
91
|
});
|
|
@@ -13,12 +13,14 @@ export async function getAuthClient() {
|
|
|
13
13
|
return authClient;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
const userAuthClients = new Map<string, InstanceType<typeof google.auth.OAuth2>>();
|
|
17
|
+
const userAuthPromises = new Map<string, Promise<InstanceType<typeof google.auth.OAuth2>>>();
|
|
18
18
|
|
|
19
|
-
export async function getUserAuthClient(config: GoogleChatConfig) {
|
|
20
|
-
|
|
21
|
-
if (
|
|
19
|
+
export async function getUserAuthClient(config: GoogleChatConfig, startDir = process.cwd()) {
|
|
20
|
+
const cached = userAuthClients.get(startDir);
|
|
21
|
+
if (cached) return cached;
|
|
22
|
+
const pending = userAuthPromises.get(startDir);
|
|
23
|
+
if (pending) return pending;
|
|
22
24
|
|
|
23
25
|
if (!config.oauthClientId || !config.oauthClientSecret) {
|
|
24
26
|
console.error('DEBUG config:', config);
|
|
@@ -27,7 +29,7 @@ export async function getUserAuthClient(config: GoogleChatConfig) {
|
|
|
27
29
|
);
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
const promise = (async () => {
|
|
31
33
|
const oauth2Client = new google.auth.OAuth2(
|
|
32
34
|
config.oauthClientId,
|
|
33
35
|
config.oauthClientSecret,
|
|
@@ -36,23 +38,26 @@ export async function getUserAuthClient(config: GoogleChatConfig) {
|
|
|
36
38
|
|
|
37
39
|
oauth2Client.on('tokens', async (tokens) => {
|
|
38
40
|
try {
|
|
39
|
-
const currentState = await readGoogleChatState();
|
|
40
|
-
await updateGoogleChatState(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
const currentState = await readGoogleChatState(startDir);
|
|
42
|
+
await updateGoogleChatState(
|
|
43
|
+
{
|
|
44
|
+
oauthTokens: {
|
|
45
|
+
...currentState.oauthTokens,
|
|
46
|
+
...tokens,
|
|
47
|
+
},
|
|
44
48
|
},
|
|
45
|
-
|
|
49
|
+
startDir
|
|
50
|
+
);
|
|
46
51
|
} catch (err) {
|
|
47
52
|
console.error('Failed to save refreshed Google User tokens', err);
|
|
48
53
|
}
|
|
49
54
|
});
|
|
50
55
|
|
|
51
|
-
const state = await readGoogleChatState();
|
|
56
|
+
const state = await readGoogleChatState(startDir);
|
|
52
57
|
if (state.oauthTokens) {
|
|
53
58
|
oauth2Client.setCredentials(state.oauthTokens);
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
userAuthClients.set(startDir, oauth2Client);
|
|
60
|
+
userAuthPromises.delete(startDir);
|
|
56
61
|
return oauth2Client;
|
|
57
62
|
}
|
|
58
63
|
|
|
@@ -86,22 +91,22 @@ export async function getUserAuthClient(config: GoogleChatConfig) {
|
|
|
86
91
|
const { tokens } = await oauth2Client.getToken(code);
|
|
87
92
|
oauth2Client.setCredentials(tokens);
|
|
88
93
|
|
|
89
|
-
await updateGoogleChatState({ oauthTokens: tokens });
|
|
94
|
+
await updateGoogleChatState({ oauthTokens: tokens }, startDir);
|
|
90
95
|
|
|
91
96
|
console.log('Google User authorization successful!');
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
userAuthClients.set(startDir, oauth2Client);
|
|
98
|
+
userAuthPromises.delete(startDir);
|
|
94
99
|
resolve(oauth2Client);
|
|
95
100
|
} catch (err) {
|
|
96
101
|
console.error('Failed to get token', err);
|
|
97
|
-
|
|
102
|
+
userAuthPromises.delete(startDir);
|
|
98
103
|
reject(err);
|
|
99
104
|
}
|
|
100
105
|
} else {
|
|
101
106
|
res.end('Authentication failed!');
|
|
102
107
|
clearTimeout(timeoutId);
|
|
103
108
|
server.close();
|
|
104
|
-
|
|
109
|
+
userAuthPromises.delete(startDir);
|
|
105
110
|
reject(new Error('No code provided in OAuth callback'));
|
|
106
111
|
}
|
|
107
112
|
}
|
|
@@ -110,7 +115,7 @@ export async function getUserAuthClient(config: GoogleChatConfig) {
|
|
|
110
115
|
server.on('error', (err) => {
|
|
111
116
|
console.error('Failed to start local OAuth server on port 31338', err);
|
|
112
117
|
clearTimeout(timeoutId);
|
|
113
|
-
|
|
118
|
+
userAuthPromises.delete(startDir);
|
|
114
119
|
reject(err);
|
|
115
120
|
});
|
|
116
121
|
|
|
@@ -118,7 +123,7 @@ export async function getUserAuthClient(config: GoogleChatConfig) {
|
|
|
118
123
|
timeoutId = setTimeout(
|
|
119
124
|
() => {
|
|
120
125
|
server.close();
|
|
121
|
-
|
|
126
|
+
userAuthPromises.delete(startDir);
|
|
122
127
|
console.error('Google User authorization timed out after 5 minutes.');
|
|
123
128
|
reject(new Error('Google User authorization timed out.'));
|
|
124
129
|
},
|
|
@@ -128,5 +133,6 @@ export async function getUserAuthClient(config: GoogleChatConfig) {
|
|
|
128
133
|
});
|
|
129
134
|
})();
|
|
130
135
|
|
|
131
|
-
|
|
136
|
+
userAuthPromises.set(startDir, promise);
|
|
137
|
+
return promise;
|
|
132
138
|
}
|
|
@@ -2,11 +2,14 @@ import { google } from 'googleapis';
|
|
|
2
2
|
import { getAuthClient } from './auth.js';
|
|
3
3
|
import type { RoutingTrpcClient } from '../shared/adapters/routing.js';
|
|
4
4
|
|
|
5
|
+
type ChatApiLike = ReturnType<typeof google.chat>;
|
|
6
|
+
|
|
5
7
|
export async function handleCardClicked(
|
|
6
8
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
9
|
event: any,
|
|
8
10
|
targetChatId: string,
|
|
9
|
-
trpc: RoutingTrpcClient
|
|
11
|
+
trpc: RoutingTrpcClient,
|
|
12
|
+
getChatApi?: () => Promise<ChatApiLike>
|
|
10
13
|
) {
|
|
11
14
|
const action = event.action;
|
|
12
15
|
if (!action) return;
|
|
@@ -22,7 +25,9 @@ export async function handleCardClicked(
|
|
|
22
25
|
|
|
23
26
|
if (event.message?.name) {
|
|
24
27
|
try {
|
|
25
|
-
const chatApi =
|
|
28
|
+
const chatApi = getChatApi
|
|
29
|
+
? await getChatApi()
|
|
30
|
+
: google.chat({ version: 'v1', auth: await getAuthClient() });
|
|
26
31
|
|
|
27
32
|
const originalCards = event.message.cardsV2 || [];
|
|
28
33
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { getTRPCClient, startGoogleChatIngestion } from './client.js';
|
|
2
|
+
import { formatQuotedSender, getTRPCClient, startGoogleChatIngestion } from './client.js';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import fsPromises from 'node:fs/promises';
|
|
5
5
|
import * as workspace from '../shared/workspace.js';
|
|
@@ -87,6 +87,41 @@ describe('Google Chat Adapter Client', () => {
|
|
|
87
87
|
vi.clearAllMocks();
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
+
describe('formatQuotedSender', () => {
|
|
91
|
+
const authorized = ['user@example.com', 'users/42'];
|
|
92
|
+
|
|
93
|
+
it('returns undefined when sender is missing', () => {
|
|
94
|
+
expect(formatQuotedSender(undefined, authorized)).toBeUndefined();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('labels bots as "Assistant"', () => {
|
|
98
|
+
expect(formatQuotedSender({ type: 'BOT' }, authorized)).toBe('Assistant');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('returns undefined for authorized users by email', () => {
|
|
102
|
+
expect(
|
|
103
|
+
formatQuotedSender({ email: 'user@example.com', type: 'HUMAN' }, authorized)
|
|
104
|
+
).toBeUndefined();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('returns undefined for authorized users by user resource name', () => {
|
|
108
|
+
expect(formatQuotedSender({ name: 'users/42', type: 'HUMAN' }, authorized)).toBeUndefined();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('falls back to email for other people', () => {
|
|
112
|
+
expect(
|
|
113
|
+
formatQuotedSender(
|
|
114
|
+
{ email: 'other@example.com', name: 'users/9', type: 'HUMAN' },
|
|
115
|
+
authorized
|
|
116
|
+
)
|
|
117
|
+
).toBe('other@example.com');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('falls back to user resource name when email is absent', () => {
|
|
121
|
+
expect(formatQuotedSender({ name: 'users/9', type: 'HUMAN' }, authorized)).toBe('users/9');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
90
125
|
describe('getTRPCClient', () => {
|
|
91
126
|
it('should throw error if daemon socket does not exist', () => {
|
|
92
127
|
vi.mocked(workspace.getSocketPath).mockReturnValue('/tmp/test.sock');
|
|
@@ -533,7 +568,7 @@ describe('Google Chat Adapter Client', () => {
|
|
|
533
568
|
|
|
534
569
|
vi.mocked(google.chat({ version: 'v1' }).spaces.messages.list).mockResolvedValueOnce({
|
|
535
570
|
data: { messages: [{ sender: { type: 'BOT' } }] },
|
|
536
|
-
} as
|
|
571
|
+
} as never);
|
|
537
572
|
|
|
538
573
|
const mockMsg = {
|
|
539
574
|
data: Buffer.from(
|