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
|
@@ -1,76 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
122
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
async function getUserAuthClient(config) {
|
|
229
|
-
|
|
230
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
251
|
-
|
|
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
|
-
|
|
279
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
466
|
-
|
|
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?.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
647
|
-
await
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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: (
|
|
1022
|
+
onData: (items) => {
|
|
811
1023
|
retryDelay = 1e3;
|
|
812
|
-
if (!Array.isArray(
|
|
813
|
-
|
|
814
|
-
for (const
|
|
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
|
|
817
|
-
if (
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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("
|
|
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: () =>
|
|
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, (
|
|
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
|
});
|