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,32 +1,49 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { shouldDisplayMessage, formatMessage } from './filtering.js';
|
|
2
|
+
import { shouldDisplayMessage, routeMessage, formatMessage } from './filtering.js';
|
|
3
3
|
import type { ChatMessage } from '../chats.js';
|
|
4
4
|
|
|
5
|
-
describe('shouldDisplayMessage', () => {
|
|
5
|
+
describe('shouldDisplayMessage / routeMessage', () => {
|
|
6
6
|
const defaultConfig = {};
|
|
7
7
|
|
|
8
|
-
it('hides messages
|
|
8
|
+
it('hides subagent messages from top-level output by default (legacy shouldDisplayMessage)', () => {
|
|
9
9
|
const msg: ChatMessage = {
|
|
10
10
|
id: '1',
|
|
11
11
|
role: 'agent',
|
|
12
12
|
content: 'hello',
|
|
13
13
|
subagentId: 'sub1',
|
|
14
14
|
timestamp: '',
|
|
15
|
+
sessionId: undefined,
|
|
15
16
|
};
|
|
16
17
|
expect(shouldDisplayMessage(msg, defaultConfig)).toBe(false);
|
|
18
|
+
// routeMessage still surfaces it inside the turn thread — that's where
|
|
19
|
+
// subagent activity now belongs.
|
|
20
|
+
expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'thread-log' });
|
|
17
21
|
});
|
|
18
22
|
|
|
19
|
-
it('
|
|
20
|
-
const msg: ChatMessage = {
|
|
23
|
+
it('drops standard user messages without subagentId', () => {
|
|
24
|
+
const msg: ChatMessage = {
|
|
25
|
+
id: '1',
|
|
26
|
+
role: 'user',
|
|
27
|
+
content: 'hello',
|
|
28
|
+
timestamp: '',
|
|
29
|
+
sessionId: undefined,
|
|
30
|
+
};
|
|
21
31
|
expect(shouldDisplayMessage(msg, defaultConfig)).toBe(false);
|
|
32
|
+
expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'drop' });
|
|
22
33
|
});
|
|
23
34
|
|
|
24
|
-
it('
|
|
25
|
-
const msg: ChatMessage = {
|
|
26
|
-
|
|
35
|
+
it('routes standard agent messages to top-level', () => {
|
|
36
|
+
const msg: ChatMessage = {
|
|
37
|
+
id: '1',
|
|
38
|
+
role: 'agent',
|
|
39
|
+
content: 'hello',
|
|
40
|
+
timestamp: '',
|
|
41
|
+
sessionId: undefined,
|
|
42
|
+
};
|
|
43
|
+
expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'top-level' });
|
|
27
44
|
});
|
|
28
45
|
|
|
29
|
-
it('
|
|
46
|
+
it('drops command messages by default', () => {
|
|
30
47
|
const msg: ChatMessage = {
|
|
31
48
|
id: '1',
|
|
32
49
|
role: 'command',
|
|
@@ -38,10 +55,72 @@ describe('shouldDisplayMessage', () => {
|
|
|
38
55
|
stderr: '',
|
|
39
56
|
exitCode: 0,
|
|
40
57
|
timestamp: '',
|
|
58
|
+
sessionId: undefined,
|
|
41
59
|
};
|
|
60
|
+
expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'drop' });
|
|
61
|
+
// Legacy boolean retains the same "drop by default" semantics for Discord.
|
|
42
62
|
expect(shouldDisplayMessage(msg, defaultConfig)).toBe(false);
|
|
43
63
|
});
|
|
44
64
|
|
|
65
|
+
it('routes tool messages to thread-log by default', () => {
|
|
66
|
+
const msg: ChatMessage = {
|
|
67
|
+
id: '1',
|
|
68
|
+
role: 'tool',
|
|
69
|
+
content: '',
|
|
70
|
+
messageId: '123',
|
|
71
|
+
name: 'Read',
|
|
72
|
+
payload: {},
|
|
73
|
+
timestamp: '',
|
|
74
|
+
sessionId: undefined,
|
|
75
|
+
};
|
|
76
|
+
expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'thread-log' });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('drops cron system messages by default (proactive turns are invisible)', () => {
|
|
80
|
+
// Cron prompts are agent-facing orchestration, not user-facing content.
|
|
81
|
+
// Adapters that want scheduled work visible opt into their own synthetic
|
|
82
|
+
// heartbeat (e.g. gchat `visibility.jobs: 'header'`).
|
|
83
|
+
const msg: ChatMessage = {
|
|
84
|
+
id: '1',
|
|
85
|
+
role: 'system',
|
|
86
|
+
content: 'run my daily report',
|
|
87
|
+
event: 'cron',
|
|
88
|
+
timestamp: '',
|
|
89
|
+
sessionId: undefined,
|
|
90
|
+
};
|
|
91
|
+
expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'drop' });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('promotes cron system messages back to top-level when filters.system is true', () => {
|
|
95
|
+
// Escape hatch for debugging: `filters.system: true` forces cron activity
|
|
96
|
+
// back into the main log.
|
|
97
|
+
const msg: ChatMessage = {
|
|
98
|
+
id: '1',
|
|
99
|
+
role: 'system',
|
|
100
|
+
content: 'run my daily report',
|
|
101
|
+
event: 'cron',
|
|
102
|
+
timestamp: '',
|
|
103
|
+
sessionId: undefined,
|
|
104
|
+
};
|
|
105
|
+
expect(routeMessage(msg, { filters: { system: true } })).toEqual({ kind: 'top-level' });
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('routes pending policy messages to top-level', () => {
|
|
109
|
+
const msg: ChatMessage = {
|
|
110
|
+
id: '1',
|
|
111
|
+
role: 'policy',
|
|
112
|
+
content: '',
|
|
113
|
+
messageId: '123',
|
|
114
|
+
requestId: 'req',
|
|
115
|
+
commandName: 'rm',
|
|
116
|
+
args: [],
|
|
117
|
+
status: 'pending',
|
|
118
|
+
timestamp: '',
|
|
119
|
+
sessionId: undefined,
|
|
120
|
+
};
|
|
121
|
+
expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'top-level' });
|
|
122
|
+
});
|
|
123
|
+
|
|
45
124
|
it('displays subagent messages if subagent: true', () => {
|
|
46
125
|
const msg: ChatMessage = {
|
|
47
126
|
id: '1',
|
|
@@ -49,6 +128,7 @@ describe('shouldDisplayMessage', () => {
|
|
|
49
128
|
content: 'hello',
|
|
50
129
|
subagentId: 'sub1',
|
|
51
130
|
timestamp: '',
|
|
131
|
+
sessionId: undefined,
|
|
52
132
|
};
|
|
53
133
|
expect(shouldDisplayMessage(msg, { filters: { subagent: true } })).toBe(true);
|
|
54
134
|
});
|
|
@@ -60,6 +140,7 @@ describe('shouldDisplayMessage', () => {
|
|
|
60
140
|
content: 'hello subagent',
|
|
61
141
|
subagentId: 'sub1',
|
|
62
142
|
timestamp: '',
|
|
143
|
+
sessionId: undefined,
|
|
63
144
|
};
|
|
64
145
|
expect(shouldDisplayMessage(msg, { filters: { subagent: true } })).toBe(true);
|
|
65
146
|
});
|
|
@@ -76,14 +157,100 @@ describe('shouldDisplayMessage', () => {
|
|
|
76
157
|
stderr: '',
|
|
77
158
|
exitCode: 0,
|
|
78
159
|
timestamp: '',
|
|
160
|
+
sessionId: undefined,
|
|
79
161
|
};
|
|
80
162
|
expect(shouldDisplayMessage(msg, { filters: { command: true } })).toBe(true);
|
|
81
163
|
});
|
|
164
|
+
|
|
165
|
+
it('hides a role when filter is explicitly false', () => {
|
|
166
|
+
const msg: ChatMessage = {
|
|
167
|
+
id: '1',
|
|
168
|
+
role: 'command',
|
|
169
|
+
content: 'ls',
|
|
170
|
+
messageId: '123',
|
|
171
|
+
command: 'ls',
|
|
172
|
+
cwd: '.',
|
|
173
|
+
stdout: '',
|
|
174
|
+
stderr: '',
|
|
175
|
+
exitCode: 0,
|
|
176
|
+
timestamp: '',
|
|
177
|
+
sessionId: undefined,
|
|
178
|
+
};
|
|
179
|
+
expect(shouldDisplayMessage(msg, { filters: { command: false } })).toBe(false);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('promotes a user-role message to top-level when filter is true', () => {
|
|
183
|
+
const msg: ChatMessage = {
|
|
184
|
+
id: '1',
|
|
185
|
+
role: 'user',
|
|
186
|
+
content: 'hello',
|
|
187
|
+
timestamp: '',
|
|
188
|
+
sessionId: undefined,
|
|
189
|
+
};
|
|
190
|
+
expect(routeMessage(msg, { filters: { user: true } })).toEqual({ kind: 'top-level' });
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('routes subagent tool messages to thread-log even without the subagent filter', () => {
|
|
194
|
+
const msg: ChatMessage = {
|
|
195
|
+
id: '1',
|
|
196
|
+
role: 'tool',
|
|
197
|
+
content: 'ls',
|
|
198
|
+
messageId: 'mid',
|
|
199
|
+
name: 'Read',
|
|
200
|
+
payload: {},
|
|
201
|
+
subagentId: 'sub-1',
|
|
202
|
+
timestamp: '',
|
|
203
|
+
sessionId: undefined,
|
|
204
|
+
};
|
|
205
|
+
expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'thread-log' });
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('routes subagent final replies into the turn thread, not top-level', () => {
|
|
209
|
+
const msg: ChatMessage = {
|
|
210
|
+
id: '1',
|
|
211
|
+
role: 'agent',
|
|
212
|
+
content: 'done',
|
|
213
|
+
subagentId: 'sub-1',
|
|
214
|
+
timestamp: '',
|
|
215
|
+
sessionId: undefined,
|
|
216
|
+
};
|
|
217
|
+
expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'thread-log' });
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('routes subagent prompts (user role with subagentId) into the turn thread', () => {
|
|
221
|
+
const msg: ChatMessage = {
|
|
222
|
+
id: '1',
|
|
223
|
+
role: 'user',
|
|
224
|
+
content: 'research auth flow',
|
|
225
|
+
subagentId: 'sub-1',
|
|
226
|
+
timestamp: '',
|
|
227
|
+
sessionId: undefined,
|
|
228
|
+
};
|
|
229
|
+
expect(routeMessage(msg, defaultConfig)).toEqual({ kind: 'thread-log' });
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('surfaces subagent final replies at top-level when subagent filter is true', () => {
|
|
233
|
+
const msg: ChatMessage = {
|
|
234
|
+
id: '1',
|
|
235
|
+
role: 'agent',
|
|
236
|
+
content: 'done',
|
|
237
|
+
subagentId: 'sub-1',
|
|
238
|
+
timestamp: '',
|
|
239
|
+
sessionId: undefined,
|
|
240
|
+
};
|
|
241
|
+
expect(routeMessage(msg, { filters: { subagent: true } })).toEqual({ kind: 'top-level' });
|
|
242
|
+
});
|
|
82
243
|
});
|
|
83
244
|
|
|
84
245
|
describe('formatMessage', () => {
|
|
85
246
|
it('returns content as-is for messages without subagentId', () => {
|
|
86
|
-
const msg: ChatMessage = {
|
|
247
|
+
const msg: ChatMessage = {
|
|
248
|
+
id: '1',
|
|
249
|
+
role: 'agent',
|
|
250
|
+
content: 'hello world',
|
|
251
|
+
timestamp: '',
|
|
252
|
+
sessionId: undefined,
|
|
253
|
+
};
|
|
87
254
|
expect(formatMessage(msg)).toBe('hello world');
|
|
88
255
|
});
|
|
89
256
|
|
|
@@ -94,6 +261,7 @@ describe('formatMessage', () => {
|
|
|
94
261
|
content: 'do task',
|
|
95
262
|
subagentId: 'sub1',
|
|
96
263
|
timestamp: '',
|
|
264
|
+
sessionId: undefined,
|
|
97
265
|
};
|
|
98
266
|
expect(formatMessage(msg)).toBe('[To:sub1]\ndo task');
|
|
99
267
|
});
|
|
@@ -105,7 +273,53 @@ describe('formatMessage', () => {
|
|
|
105
273
|
content: 'done',
|
|
106
274
|
subagentId: 'sub1',
|
|
107
275
|
timestamp: '',
|
|
276
|
+
sessionId: undefined,
|
|
108
277
|
};
|
|
109
278
|
expect(formatMessage(msg)).toBe('[From:sub1]\ndone');
|
|
110
279
|
});
|
|
280
|
+
|
|
281
|
+
it('prepends [SYSTEM] for system-role messages with no displayRole', () => {
|
|
282
|
+
// System messages only reach `formatMessage` when they route to top-level
|
|
283
|
+
// (either a non-cron event, or cron opted back in via `filters.system`).
|
|
284
|
+
// Without the tag they look like either the user or the bot talking.
|
|
285
|
+
const msg: ChatMessage = {
|
|
286
|
+
id: '1',
|
|
287
|
+
role: 'system',
|
|
288
|
+
content: 'This chat session has ended.',
|
|
289
|
+
event: 'cron',
|
|
290
|
+
timestamp: '',
|
|
291
|
+
sessionId: undefined,
|
|
292
|
+
};
|
|
293
|
+
expect(formatMessage(msg)).toBe('[SYSTEM] This chat session has ended.');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('does not prefix [SYSTEM] when displayRole is set (e.g. router auto-reply)', () => {
|
|
297
|
+
// Router auto-replies opt into being rendered as agent output. Tagging
|
|
298
|
+
// them [SYSTEM] would mislabel them.
|
|
299
|
+
const msg: ChatMessage = {
|
|
300
|
+
id: '1',
|
|
301
|
+
role: 'system',
|
|
302
|
+
content: 'Starting a fresh session...',
|
|
303
|
+
event: 'router',
|
|
304
|
+
displayRole: 'agent',
|
|
305
|
+
timestamp: '',
|
|
306
|
+
sessionId: undefined,
|
|
307
|
+
};
|
|
308
|
+
expect(formatMessage(msg)).toBe('Starting a fresh session...');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('does not prefix [SYSTEM] for system messages from subagents', () => {
|
|
312
|
+
// Subagent output already gets [From:<id>]; a [SYSTEM] on top would be
|
|
313
|
+
// noisy and incorrect (the subagent is the speaker, not the system).
|
|
314
|
+
const msg: ChatMessage = {
|
|
315
|
+
id: '1',
|
|
316
|
+
role: 'system',
|
|
317
|
+
content: 'internal note',
|
|
318
|
+
event: 'subagent_update',
|
|
319
|
+
subagentId: 'sub1',
|
|
320
|
+
timestamp: '',
|
|
321
|
+
sessionId: undefined,
|
|
322
|
+
};
|
|
323
|
+
expect(formatMessage(msg)).toBe('[From:sub1]\ninternal note');
|
|
324
|
+
});
|
|
111
325
|
});
|
|
@@ -4,26 +4,28 @@ export interface FilteringConfig {
|
|
|
4
4
|
filters?: Record<string, boolean> | undefined;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
export type Destination = { kind: 'drop' } | { kind: 'top-level' } | { kind: 'thread-log' };
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Legacy boolean. Returns true if the message role is permitted to be
|
|
11
|
+
* displayed at all; adapters that haven't migrated to `routeMessage` keep
|
|
12
|
+
* using this unchanged.
|
|
13
|
+
*/
|
|
7
14
|
export function shouldDisplayMessage(message: ChatMessage, config: FilteringConfig): boolean {
|
|
8
15
|
const overrides = config.filters || {};
|
|
9
16
|
|
|
10
|
-
// If the message has a subagentId, return false immediately unless subagent messages are allowed.
|
|
11
17
|
if (message.subagentId && overrides['subagent'] !== true) {
|
|
12
18
|
return false;
|
|
13
19
|
}
|
|
14
20
|
|
|
15
|
-
// Then check if it's a standard agent message (via role/displayRole) and always return true if so.
|
|
16
21
|
const isStandardAgent =
|
|
17
22
|
message.displayRole === 'agent' ||
|
|
18
23
|
message.role === 'agent' ||
|
|
19
24
|
message.role === 'legacy_log' ||
|
|
20
25
|
(message.role === 'policy' && message.status === 'pending');
|
|
21
26
|
|
|
22
|
-
if (isStandardAgent)
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
27
|
+
if (isStandardAgent) return true;
|
|
25
28
|
|
|
26
|
-
// Then check if it's a user message directed to a subagent, if subagent messages are allowed
|
|
27
29
|
if (
|
|
28
30
|
message.subagentId &&
|
|
29
31
|
overrides['subagent'] === true &&
|
|
@@ -32,7 +34,6 @@ export function shouldDisplayMessage(message: ChatMessage, config: FilteringConf
|
|
|
32
34
|
return true;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
// Finally, check if the role is allowed and forward it if so.
|
|
36
37
|
if (
|
|
37
38
|
overrides[message.role] === true ||
|
|
38
39
|
(message.displayRole && overrides[message.displayRole] === true)
|
|
@@ -43,7 +44,94 @@ export function shouldDisplayMessage(message: ChatMessage, config: FilteringConf
|
|
|
43
44
|
return false;
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
function defaultDestinationForRole(message: ChatMessage): Destination {
|
|
48
|
+
if (message.role === 'user') return { kind: 'drop' };
|
|
49
|
+
if (message.role === 'agent') return { kind: 'top-level' };
|
|
50
|
+
if (message.role === 'legacy_log') return { kind: 'top-level' };
|
|
51
|
+
if (message.role === 'tool') return { kind: 'thread-log' };
|
|
52
|
+
if (message.role === 'subagent_status') return { kind: 'thread-log' };
|
|
53
|
+
if (message.role === 'command') return { kind: 'drop' };
|
|
54
|
+
if (message.role === 'policy') {
|
|
55
|
+
return message.status === 'pending' ? { kind: 'top-level' } : { kind: 'thread-log' };
|
|
56
|
+
}
|
|
57
|
+
if (message.role === 'system') {
|
|
58
|
+
// Cron turns are invisible by default: the activity log anchors on the
|
|
59
|
+
// agent's eventual top-level reply (if any). Adapters that want a
|
|
60
|
+
// visible header post (gchat `visibility.jobs: 'header'`) promote this
|
|
61
|
+
// back to top-level at the forwarder layer.
|
|
62
|
+
if (message.event === 'cron') return { kind: 'drop' };
|
|
63
|
+
if (message.event === 'policy_approved' || message.event === 'policy_rejected') {
|
|
64
|
+
return { kind: 'thread-log' };
|
|
65
|
+
}
|
|
66
|
+
if (message.event === 'subagent_update') return { kind: 'thread-log' };
|
|
67
|
+
return { kind: 'top-level' };
|
|
68
|
+
}
|
|
69
|
+
return { kind: 'drop' };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Return the destination for a chat message given the adapter's filtering
|
|
74
|
+
* config. Adapters that support threaded activity logs (Google Chat) use this
|
|
75
|
+
* to decide whether each message becomes a top-level post, a thread-log entry,
|
|
76
|
+
* or is dropped entirely.
|
|
77
|
+
*
|
|
78
|
+
* A filter override of `true` on a role whose default destination is `drop`
|
|
79
|
+
* promotes it to `top-level` (matching the legacy "opted in → show" behavior).
|
|
80
|
+
* A filter override of `false` drops the role.
|
|
81
|
+
*
|
|
82
|
+
* Subagent messages route to their default destination when that default is a
|
|
83
|
+
* thread one (tool calls, command logs, status updates all belong in the turn
|
|
84
|
+
* log). They are dropped when the default is top-level — subagent prompts and
|
|
85
|
+
* final replies are orchestration, not user-facing content — unless
|
|
86
|
+
* `filters.subagent` is `true`, which surfaces them at top-level for debugging.
|
|
87
|
+
*/
|
|
88
|
+
export function routeMessage(message: ChatMessage, config: FilteringConfig): Destination {
|
|
89
|
+
const overrides = config.filters || {};
|
|
90
|
+
const defaultDest = defaultDestinationForRole(message);
|
|
91
|
+
|
|
92
|
+
if (message.subagentId) {
|
|
93
|
+
if (overrides['subagent'] === true) {
|
|
94
|
+
return defaultDest.kind === 'drop' ? { kind: 'top-level' } : defaultDest;
|
|
95
|
+
}
|
|
96
|
+
// Everything produced inside a subagent — tool calls, command logs,
|
|
97
|
+
// status updates, the prompt handed to it, and its final reply — folds
|
|
98
|
+
// into the parent turn's activity log so the reader can see what the
|
|
99
|
+
// subagent did.
|
|
100
|
+
return { kind: 'thread-log' };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const isStandardAgent =
|
|
104
|
+
message.displayRole === 'agent' ||
|
|
105
|
+
message.role === 'agent' ||
|
|
106
|
+
message.role === 'legacy_log' ||
|
|
107
|
+
(message.role === 'policy' && message.status === 'pending');
|
|
108
|
+
|
|
109
|
+
if (isStandardAgent) return defaultDest;
|
|
110
|
+
|
|
111
|
+
const roleFilter = overrides[message.role];
|
|
112
|
+
const displayRoleFilter = message.displayRole ? overrides[message.displayRole] : undefined;
|
|
113
|
+
|
|
114
|
+
if (roleFilter === false || displayRoleFilter === false) {
|
|
115
|
+
return { kind: 'drop' };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (roleFilter === true || displayRoleFilter === true) {
|
|
119
|
+
return defaultDest.kind === 'drop' ? { kind: 'top-level' } : defaultDest;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return defaultDest;
|
|
123
|
+
}
|
|
124
|
+
|
|
46
125
|
export function formatMessage(message: ChatMessage): string {
|
|
126
|
+
// System-role messages that aren't explicitly re-displayed as user/agent
|
|
127
|
+
// (e.g. cron-triggered prompts, policy system notes) are posted verbatim
|
|
128
|
+
// today, which makes them look like either the user or the bot talking.
|
|
129
|
+
// Tag them so readers can distinguish automated system output from real
|
|
130
|
+
// conversation. Router auto-replies opt out via displayRole: 'agent'.
|
|
131
|
+
if (message.role === 'system' && !message.displayRole && !message.subagentId) {
|
|
132
|
+
return `[SYSTEM] ${message.content}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
47
135
|
if (!message.subagentId) {
|
|
48
136
|
return message.content;
|
|
49
137
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
+
import { createInboundCache } from './inbound-cache.js';
|
|
3
|
+
|
|
4
|
+
const TTL_MS = 10 * 60 * 1000;
|
|
5
|
+
|
|
6
|
+
describe('createInboundCache', () => {
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
vi.useRealTimers();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('records and resolves a value by key', () => {
|
|
12
|
+
const cache = createInboundCache<{ channelId: string }>(TTL_MS);
|
|
13
|
+
cache.record('msg-1', { channelId: 'chan-1' });
|
|
14
|
+
expect(cache.resolve('msg-1')).toEqual({ channelId: 'chan-1' });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('returns null for unknown keys', () => {
|
|
18
|
+
const cache = createInboundCache<{ channelId: string }>(TTL_MS);
|
|
19
|
+
expect(cache.resolve('unknown')).toBeNull();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('expires entries older than ttlMs on resolve', () => {
|
|
23
|
+
vi.useFakeTimers();
|
|
24
|
+
const cache = createInboundCache<{ channelId: string }>(TTL_MS);
|
|
25
|
+
cache.record('msg-1', { channelId: 'chan-1' });
|
|
26
|
+
expect(cache.resolve('msg-1')).not.toBeNull();
|
|
27
|
+
|
|
28
|
+
vi.advanceTimersByTime(TTL_MS + 1000);
|
|
29
|
+
expect(cache.resolve('msg-1')).toBeNull();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('sweeps expired entries on every insert', () => {
|
|
33
|
+
vi.useFakeTimers();
|
|
34
|
+
const cache = createInboundCache<{ channelId: string }>(TTL_MS);
|
|
35
|
+
cache.record('msg-1', { channelId: 'chan-1' });
|
|
36
|
+
vi.advanceTimersByTime(TTL_MS + 1000);
|
|
37
|
+
cache.record('msg-2', { channelId: 'chan-2' });
|
|
38
|
+
expect(cache.resolve('msg-1')).toBeNull();
|
|
39
|
+
expect(cache.resolve('msg-2')).not.toBeNull();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('reset() drops all entries', () => {
|
|
43
|
+
const cache = createInboundCache<{ channelId: string }>(TTL_MS);
|
|
44
|
+
cache.record('msg-1', { channelId: 'chan-1' });
|
|
45
|
+
cache.reset();
|
|
46
|
+
expect(cache.resolve('msg-1')).toBeNull();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic in-memory inbound message cache shared by adapters that need to
|
|
3
|
+
* correlate a daemon `turnStarted` event with the inbound message that
|
|
4
|
+
* triggered it.
|
|
5
|
+
*
|
|
6
|
+
* Adapters whose ingestion and forwarder run in the same Node process don't
|
|
7
|
+
* need disk persistence: a `Map` keyed by external ref (the same id sent to
|
|
8
|
+
* the daemon as `externalRef`) is enough. Entries older than `ttlMs` are
|
|
9
|
+
* swept on every insert — bounded memory without an LRU. Adapter restart
|
|
10
|
+
* mid-turn loses the cache; that is an explicit tradeoff.
|
|
11
|
+
*/
|
|
12
|
+
export interface InboundCache<TValue> {
|
|
13
|
+
record(key: string, value: TValue): void;
|
|
14
|
+
resolve(key: string): TValue | null;
|
|
15
|
+
/** Test hook: drop all cached records. */
|
|
16
|
+
reset(): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface Entry<TValue> {
|
|
20
|
+
value: TValue;
|
|
21
|
+
receivedAt: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createInboundCache<TValue>(ttlMs: number): InboundCache<TValue> {
|
|
25
|
+
const cache = new Map<string, Entry<TValue>>();
|
|
26
|
+
|
|
27
|
+
const sweep = (now: number): void => {
|
|
28
|
+
for (const [key, entry] of cache) {
|
|
29
|
+
if (now - entry.receivedAt > ttlMs) cache.delete(key);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
record(key, value) {
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
sweep(now);
|
|
37
|
+
cache.set(key, { value, receivedAt: now });
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
resolve(key) {
|
|
41
|
+
const entry = cache.get(key);
|
|
42
|
+
if (!entry) return null;
|
|
43
|
+
if (Date.now() - entry.receivedAt > ttlMs) {
|
|
44
|
+
cache.delete(key);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return entry.value;
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
reset() {
|
|
51
|
+
cache.clear();
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|