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
|
@@ -4,7 +4,13 @@ import path from 'node:path';
|
|
|
4
4
|
import { TRPCError } from '@trpc/server';
|
|
5
5
|
import { pathIsInsideDir } from '../../shared/utils/fs.js';
|
|
6
6
|
import { on } from 'node:events';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
daemonEvents,
|
|
9
|
+
DAEMON_EVENT_CHAT_STREAM,
|
|
10
|
+
DAEMON_EVENT_TYPING,
|
|
11
|
+
type ChatStreamEnvelope,
|
|
12
|
+
type ChatStreamItem,
|
|
13
|
+
} from '../events.js';
|
|
8
14
|
import {
|
|
9
15
|
getSettingsPath,
|
|
10
16
|
readChatSettings,
|
|
@@ -39,6 +45,7 @@ export const sendMessage = apiProcedure
|
|
|
39
45
|
noWait: z.boolean().optional(),
|
|
40
46
|
files: z.array(z.string()).optional(),
|
|
41
47
|
adapter: z.string().optional(),
|
|
48
|
+
externalRef: z.string().optional(),
|
|
42
49
|
}),
|
|
43
50
|
})
|
|
44
51
|
)
|
|
@@ -99,7 +106,16 @@ export const sendMessage = apiProcedure
|
|
|
99
106
|
message = message ? `${message}\n\n${fileList}` : fileList;
|
|
100
107
|
}
|
|
101
108
|
|
|
102
|
-
await handleUserMessage(
|
|
109
|
+
await handleUserMessage(
|
|
110
|
+
chatId,
|
|
111
|
+
message,
|
|
112
|
+
settings,
|
|
113
|
+
undefined,
|
|
114
|
+
noWait,
|
|
115
|
+
sessionId,
|
|
116
|
+
agentId,
|
|
117
|
+
input.data.externalRef
|
|
118
|
+
);
|
|
103
119
|
|
|
104
120
|
return { success: true };
|
|
105
121
|
});
|
|
@@ -111,6 +127,11 @@ export const getMessages = apiProcedure
|
|
|
111
127
|
return fetchMessages(chatId, input.limit);
|
|
112
128
|
});
|
|
113
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Interleaved chat stream: `ChatMessage` appends and turn lifecycle events
|
|
132
|
+
* arrive on a single subscription in emission order. Consumers that only
|
|
133
|
+
* care about messages can ignore items with `kind: 'turn'`.
|
|
134
|
+
*/
|
|
114
135
|
export const waitForMessages = apiProcedure
|
|
115
136
|
.input(
|
|
116
137
|
z.object({
|
|
@@ -121,20 +142,25 @@ export const waitForMessages = apiProcedure
|
|
|
121
142
|
.subscription(async function* ({ input, signal }) {
|
|
122
143
|
const chatId = input.chatId ?? (await getDefaultChatId());
|
|
123
144
|
|
|
124
|
-
// 1.
|
|
145
|
+
// 1. Catch up on any messages missed since `lastMessageId`. Turn events
|
|
146
|
+
// have no persisted backlog — they're live-only, like today.
|
|
125
147
|
if (input.lastMessageId) {
|
|
126
148
|
const messages = await fetchMessages(chatId);
|
|
127
149
|
const lastIndex = messages.findIndex((m) => m.id === input.lastMessageId);
|
|
128
150
|
if (lastIndex !== -1 && lastIndex < messages.length - 1) {
|
|
129
|
-
|
|
151
|
+
const items: ChatStreamItem[] = messages
|
|
152
|
+
.slice(lastIndex + 1)
|
|
153
|
+
.map((message) => ({ kind: 'message', message }));
|
|
154
|
+
yield items;
|
|
130
155
|
}
|
|
131
156
|
}
|
|
132
157
|
|
|
133
|
-
// 2.
|
|
158
|
+
// 2. Live: messages and turn events on the same channel, in emission order.
|
|
134
159
|
try {
|
|
135
|
-
for await (const [
|
|
136
|
-
|
|
137
|
-
|
|
160
|
+
for await (const [envelope] of on(daemonEvents, DAEMON_EVENT_CHAT_STREAM, { signal })) {
|
|
161
|
+
const e = envelope as ChatStreamEnvelope;
|
|
162
|
+
if (e.chatId === chatId) {
|
|
163
|
+
yield [e.item];
|
|
138
164
|
}
|
|
139
165
|
}
|
|
140
166
|
} catch (err) {
|
package/src/daemon/auth.ts
CHANGED
package/src/daemon/cron.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
-
import { CronManager } from './cron.js';
|
|
3
|
+
import { CronManager, normalizeJob } from './cron.js';
|
|
4
4
|
// @ts-expect-error - node-schedule types are missing
|
|
5
5
|
import schedule from 'node-schedule';
|
|
6
6
|
import { getInitialRouterState } from './message.js';
|
|
@@ -49,18 +49,76 @@ describe('CronManager', () => {
|
|
|
49
49
|
}).toThrow("Invalid date format for 'at' schedule: invalid-date");
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
it('
|
|
52
|
+
it('fires an overdue "at" job immediately instead of dropping it', () => {
|
|
53
|
+
const cronManager = new CronManager();
|
|
54
|
+
vi.mocked(schedule.scheduleJob as any).mockImplementation((rule: any, _cb: any) => {
|
|
55
|
+
return { cancel: vi.fn(), rule } as any;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const past = new Date(Date.now() - 60_000).toISOString();
|
|
59
|
+
cronManager.scheduleJob('chat-overdue', {
|
|
60
|
+
id: 'job-overdue',
|
|
61
|
+
createdAt: new Date().toISOString(),
|
|
62
|
+
message: 'hello',
|
|
63
|
+
schedule: { at: past },
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(schedule.scheduleJob).toHaveBeenCalledTimes(1);
|
|
67
|
+
const [rule] = vi.mocked(schedule.scheduleJob as any).mock.calls[0]!;
|
|
68
|
+
expect(rule).toBeInstanceOf(Date);
|
|
69
|
+
expect((rule as Date).getTime()).toBeGreaterThan(Date.now() - 1_000);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('correctly schedules an absolute "at" schedule', () => {
|
|
53
73
|
const cronManager = new CronManager();
|
|
54
74
|
expect(() => {
|
|
55
75
|
cronManager.scheduleJob('chat2', {
|
|
56
76
|
id: 'job2',
|
|
57
77
|
createdAt: new Date().toISOString(),
|
|
58
78
|
message: 'hello',
|
|
59
|
-
schedule: { at:
|
|
79
|
+
schedule: { at: new Date(Date.now() + 120_000).toISOString() },
|
|
60
80
|
});
|
|
61
81
|
}).not.toThrow();
|
|
62
82
|
});
|
|
63
83
|
|
|
84
|
+
it('normalizeJob resolves interval "at" to an absolute ISO timestamp', () => {
|
|
85
|
+
const before = Date.now();
|
|
86
|
+
const normalized = normalizeJob({
|
|
87
|
+
id: 'job-norm',
|
|
88
|
+
createdAt: new Date().toISOString(),
|
|
89
|
+
message: 'hi',
|
|
90
|
+
schedule: { at: '2s' },
|
|
91
|
+
});
|
|
92
|
+
const after = Date.now();
|
|
93
|
+
expect('at' in normalized.schedule).toBe(true);
|
|
94
|
+
const at = (normalized.schedule as { at: string }).at;
|
|
95
|
+
const ms = new Date(at).getTime();
|
|
96
|
+
expect(ms).toBeGreaterThanOrEqual(before + 2000);
|
|
97
|
+
expect(ms).toBeLessThanOrEqual(after + 2000);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('normalizeJob preserves an already-absolute "at" value', () => {
|
|
101
|
+
const iso = new Date(Date.now() + 60_000).toISOString();
|
|
102
|
+
const normalized = normalizeJob({
|
|
103
|
+
id: 'job-abs',
|
|
104
|
+
createdAt: new Date().toISOString(),
|
|
105
|
+
message: 'hi',
|
|
106
|
+
schedule: { at: iso },
|
|
107
|
+
});
|
|
108
|
+
expect((normalized.schedule as { at: string }).at).toBe(iso);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('normalizeJob throws on unparseable "at" value', () => {
|
|
112
|
+
expect(() =>
|
|
113
|
+
normalizeJob({
|
|
114
|
+
id: 'job-bad',
|
|
115
|
+
createdAt: new Date().toISOString(),
|
|
116
|
+
message: 'hi',
|
|
117
|
+
schedule: { at: 'not-a-date' },
|
|
118
|
+
})
|
|
119
|
+
).toThrow("Invalid date format for 'at' schedule: not-a-date");
|
|
120
|
+
});
|
|
121
|
+
|
|
64
122
|
it('passes job.session.id to getInitialRouterState when session type is existing', async () => {
|
|
65
123
|
const cronManager = new CronManager();
|
|
66
124
|
let scheduledCallback: any = null;
|
|
@@ -73,7 +131,7 @@ describe('CronManager', () => {
|
|
|
73
131
|
id: 'job3',
|
|
74
132
|
createdAt: new Date().toISOString(),
|
|
75
133
|
message: 'test existing session',
|
|
76
|
-
schedule: { at:
|
|
134
|
+
schedule: { at: new Date(Date.now() + 60_000).toISOString() },
|
|
77
135
|
session: { type: 'existing', id: 'my-old-session-id' },
|
|
78
136
|
});
|
|
79
137
|
|
package/src/daemon/cron.ts
CHANGED
|
@@ -9,6 +9,34 @@ import fs from 'node:fs/promises';
|
|
|
9
9
|
import { getSettingsPath } from '../shared/workspace.js';
|
|
10
10
|
import { applyEnvOverrides } from '../shared/utils/env.js';
|
|
11
11
|
|
|
12
|
+
// Resolves relative `at` intervals (e.g. "2s", "5m") to absolute ISO timestamps
|
|
13
|
+
// so a job's target time is stable across daemon restarts. Throws on unparseable
|
|
14
|
+
// `at` values so invalid input is rejected at creation rather than silently
|
|
15
|
+
// shifted by `scheduleJob`. `cron` and `every` schedules pass through unchanged.
|
|
16
|
+
export function normalizeJob(job: CronJob): CronJob {
|
|
17
|
+
if (!('at' in job.schedule)) return job;
|
|
18
|
+
|
|
19
|
+
const atStr = job.schedule.at;
|
|
20
|
+
const match = atStr.match(/^(\d+)\s*(m|min|minutes?|h|hours?|d|days?|s|sec|seconds?)$/i);
|
|
21
|
+
let absolute: Date;
|
|
22
|
+
if (match) {
|
|
23
|
+
const val = parseInt(match[1]!, 10);
|
|
24
|
+
const unit = match[2]!.toLowerCase();
|
|
25
|
+
let ms = 0;
|
|
26
|
+
if (unit.startsWith('s')) ms = val * 1000;
|
|
27
|
+
else if (unit.startsWith('m')) ms = val * 60 * 1000;
|
|
28
|
+
else if (unit.startsWith('h')) ms = val * 60 * 60 * 1000;
|
|
29
|
+
else if (unit.startsWith('d')) ms = val * 24 * 60 * 60 * 1000;
|
|
30
|
+
absolute = new Date(Date.now() + ms);
|
|
31
|
+
} else {
|
|
32
|
+
absolute = new Date(atStr);
|
|
33
|
+
if (isNaN(absolute.getTime())) {
|
|
34
|
+
throw new Error(`Invalid date format for 'at' schedule: ${atStr}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { ...job, schedule: { at: absolute.toISOString() } };
|
|
38
|
+
}
|
|
39
|
+
|
|
12
40
|
export class CronManager {
|
|
13
41
|
private jobs = new Map<string, schedule.Job>();
|
|
14
42
|
|
|
@@ -62,22 +90,19 @@ export class CronManager {
|
|
|
62
90
|
rule = everyStr;
|
|
63
91
|
}
|
|
64
92
|
} else if ('at' in job.schedule) {
|
|
65
|
-
const atStr =
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
rule = new Date(
|
|
78
|
-
if (isNaN(rule.getTime())) {
|
|
79
|
-
throw new Error(`Invalid date format for 'at' schedule: ${atStr}`);
|
|
80
|
-
}
|
|
93
|
+
const atStr = job.schedule.at;
|
|
94
|
+
rule = new Date(atStr);
|
|
95
|
+
if (isNaN(rule.getTime())) {
|
|
96
|
+
throw new Error(`Invalid date format for 'at' schedule: ${atStr}`);
|
|
97
|
+
}
|
|
98
|
+
// If the target time has already passed (e.g. daemon was down past it),
|
|
99
|
+
// node-schedule returns null and the job silently never fires. Push it
|
|
100
|
+
// just into the future so it executes on the next tick and cleans up.
|
|
101
|
+
if (rule.getTime() <= Date.now()) {
|
|
102
|
+
console.warn(
|
|
103
|
+
`One-off job ${job.id} for chat ${chatId} is overdue (target ${atStr}); firing immediately.`
|
|
104
|
+
);
|
|
105
|
+
rule = new Date(Date.now() + 100);
|
|
81
106
|
}
|
|
82
107
|
isOneOff = true;
|
|
83
108
|
} else {
|
|
@@ -144,6 +169,7 @@ export class CronManager {
|
|
|
144
169
|
routerState.nextSessionId = job.nextSessionId;
|
|
145
170
|
if (job.action !== undefined) routerState.action = job.action;
|
|
146
171
|
if (job.jobs !== undefined) routerState.jobs = job.jobs;
|
|
172
|
+
routerState.jobId = job.id;
|
|
147
173
|
|
|
148
174
|
const routers = chatSettings.routers ?? globalSettings?.routers ?? [];
|
|
149
175
|
const resolvedRouters = resolveRouters(routers, false);
|
package/src/daemon/events.ts
CHANGED
|
@@ -5,11 +5,76 @@ export const daemonEvents = new EventEmitter();
|
|
|
5
5
|
|
|
6
6
|
export const DAEMON_EVENT_MESSAGE_APPENDED = 'message-appended';
|
|
7
7
|
export const DAEMON_EVENT_TYPING = 'typing';
|
|
8
|
+
export const DAEMON_EVENT_TURN_STARTED = 'turn-started';
|
|
9
|
+
export const DAEMON_EVENT_TURN_ENDED = 'turn-ended';
|
|
10
|
+
/**
|
|
11
|
+
* Unified event carrying both `ChatMessage` appends and turn lifecycle
|
|
12
|
+
* events so a single `waitForMessages` subscription can interleave them
|
|
13
|
+
* in emission order (no merge logic or messageQueue on the consumer side).
|
|
14
|
+
*/
|
|
15
|
+
export const DAEMON_EVENT_CHAT_STREAM = 'chat-stream';
|
|
16
|
+
|
|
17
|
+
export interface TurnStartedEvent {
|
|
18
|
+
chatId: string;
|
|
19
|
+
turnId: string;
|
|
20
|
+
rootMessageId: string;
|
|
21
|
+
externalRef?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface TurnEndedEvent {
|
|
25
|
+
chatId: string;
|
|
26
|
+
turnId: string;
|
|
27
|
+
outcome: 'ok' | 'error';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type TurnLifecycleEvent =
|
|
31
|
+
| { type: 'started'; turnId: string; rootMessageId: string; externalRef?: string }
|
|
32
|
+
| { type: 'ended'; turnId: string; outcome: 'ok' | 'error' };
|
|
33
|
+
|
|
34
|
+
export type ChatStreamItem =
|
|
35
|
+
| { kind: 'message'; message: ChatMessage }
|
|
36
|
+
| { kind: 'turn'; event: TurnLifecycleEvent };
|
|
37
|
+
|
|
38
|
+
export interface ChatStreamEnvelope {
|
|
39
|
+
chatId: string;
|
|
40
|
+
item: ChatStreamItem;
|
|
41
|
+
}
|
|
8
42
|
|
|
9
43
|
export function emitMessageAppended(chatId: string, message: ChatMessage) {
|
|
10
44
|
daemonEvents.emit(DAEMON_EVENT_MESSAGE_APPENDED, { chatId, message });
|
|
45
|
+
const envelope: ChatStreamEnvelope = { chatId, item: { kind: 'message', message } };
|
|
46
|
+
daemonEvents.emit(DAEMON_EVENT_CHAT_STREAM, envelope);
|
|
11
47
|
}
|
|
12
48
|
|
|
13
49
|
export function emitTyping(chatId: string) {
|
|
14
50
|
daemonEvents.emit(DAEMON_EVENT_TYPING, { chatId });
|
|
15
51
|
}
|
|
52
|
+
|
|
53
|
+
export function emitTurnStarted(event: TurnStartedEvent) {
|
|
54
|
+
daemonEvents.emit(DAEMON_EVENT_TURN_STARTED, event);
|
|
55
|
+
const lifecycle: TurnLifecycleEvent = {
|
|
56
|
+
type: 'started',
|
|
57
|
+
turnId: event.turnId,
|
|
58
|
+
rootMessageId: event.rootMessageId,
|
|
59
|
+
...(event.externalRef ? { externalRef: event.externalRef } : {}),
|
|
60
|
+
};
|
|
61
|
+
const envelope: ChatStreamEnvelope = {
|
|
62
|
+
chatId: event.chatId,
|
|
63
|
+
item: { kind: 'turn', event: lifecycle },
|
|
64
|
+
};
|
|
65
|
+
daemonEvents.emit(DAEMON_EVENT_CHAT_STREAM, envelope);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function emitTurnEnded(event: TurnEndedEvent) {
|
|
69
|
+
daemonEvents.emit(DAEMON_EVENT_TURN_ENDED, event);
|
|
70
|
+
const lifecycle: TurnLifecycleEvent = {
|
|
71
|
+
type: 'ended',
|
|
72
|
+
turnId: event.turnId,
|
|
73
|
+
outcome: event.outcome,
|
|
74
|
+
};
|
|
75
|
+
const envelope: ChatStreamEnvelope = {
|
|
76
|
+
chatId: event.chatId,
|
|
77
|
+
item: { kind: 'turn', event: lifecycle },
|
|
78
|
+
};
|
|
79
|
+
daemonEvents.emit(DAEMON_EVENT_CHAT_STREAM, envelope);
|
|
80
|
+
}
|
package/src/daemon/index.ts
CHANGED
|
@@ -20,6 +20,9 @@ import { SettingsSchema } from '../shared/config.js';
|
|
|
20
20
|
import { validateToken, getApiContext } from './auth.js';
|
|
21
21
|
import path from 'node:path';
|
|
22
22
|
import { exportLiteToEnvironment } from '../shared/lite.js';
|
|
23
|
+
import { RequestStore } from './request-store.js';
|
|
24
|
+
import { drainPendingReplies } from './pending-replies.js';
|
|
25
|
+
import { getClawminiVersion } from '../shared/version.js';
|
|
23
26
|
|
|
24
27
|
export async function initDaemon() {
|
|
25
28
|
const socketPath = getSocketPath();
|
|
@@ -66,11 +69,13 @@ export async function initDaemon() {
|
|
|
66
69
|
const command = envConfig?.[hookType];
|
|
67
70
|
if (command) {
|
|
68
71
|
console.log(`Executing '${hookType}' hook for environment '${envName}': ${command}`);
|
|
72
|
+
// No timeout: a SIGTERM mid-hook risks partial teardown (e.g.
|
|
73
|
+
// containers left running). The supervisor's per-service SIGKILL
|
|
74
|
+
// timer is the ultimate backstop for runaway hooks.
|
|
69
75
|
execSync(command, {
|
|
70
76
|
cwd: affectedDir,
|
|
71
77
|
stdio: 'inherit',
|
|
72
78
|
env: { ...process.env, ENV_DIR: envDir },
|
|
73
|
-
timeout: hookType === 'down' ? 10000 : undefined,
|
|
74
79
|
});
|
|
75
80
|
}
|
|
76
81
|
} catch (err) {
|
|
@@ -163,11 +168,29 @@ export async function initDaemon() {
|
|
|
163
168
|
};
|
|
164
169
|
await cleanOrphanedSubagents();
|
|
165
170
|
|
|
171
|
+
try {
|
|
172
|
+
const removed = await new RequestStore(getWorkspaceRoot()).cleanupCompleted();
|
|
173
|
+
if (removed > 0) {
|
|
174
|
+
console.log(`Cleaned up ${removed} completed policy request file(s).`);
|
|
175
|
+
}
|
|
176
|
+
} catch (err) {
|
|
177
|
+
console.warn('Failed to clean completed policy requests:', err);
|
|
178
|
+
}
|
|
179
|
+
|
|
166
180
|
await runHooks('up');
|
|
167
181
|
|
|
168
182
|
isReady = true;
|
|
169
183
|
readyPromiseResolve!();
|
|
170
184
|
|
|
185
|
+
// Drain any replies queued by the previous daemon instance (e.g. /restart
|
|
186
|
+
// or /upgrade). This must run after the daemon is `isReady` so the tRPC
|
|
187
|
+
// subscription that the adapter forwarder reconnects to can deliver them.
|
|
188
|
+
try {
|
|
189
|
+
await drainPendingReplies(getClawminiVersion());
|
|
190
|
+
} catch (err) {
|
|
191
|
+
console.warn('Failed to drain pending replies:', err);
|
|
192
|
+
}
|
|
193
|
+
|
|
171
194
|
// Initialize cron jobs
|
|
172
195
|
cronManager.init().catch((err) => {
|
|
173
196
|
console.error('Failed to initialize cron manager:', err);
|
|
@@ -19,6 +19,7 @@ vi.mock('../shared/workspace.js', () => ({
|
|
|
19
19
|
|
|
20
20
|
readChatSettings: vi.fn().mockResolvedValue(null),
|
|
21
21
|
writeChatSettings: vi.fn().mockResolvedValue(undefined),
|
|
22
|
+
updateChatSettings: vi.fn().mockResolvedValue(undefined),
|
|
22
23
|
readAgentSessionSettings: vi.fn().mockResolvedValue(null),
|
|
23
24
|
writeAgentSessionSettings: vi.fn().mockResolvedValue(undefined),
|
|
24
25
|
getAgent: vi.fn().mockResolvedValue(null),
|
package/src/daemon/message.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import { executeRouterPipeline, resolveRouters } from './routers.js';
|
|
2
3
|
import type { RouterState } from './routers/types.js';
|
|
3
4
|
import { type ChatSettings, type Settings } from '../shared/config.js';
|
|
4
|
-
import { readChatSettings, writeChatSettings } from '../shared/workspace.js';
|
|
5
|
-
import { cronManager } from './cron.js';
|
|
5
|
+
import { readChatSettings, updateChatSettings, writeChatSettings } from '../shared/workspace.js';
|
|
6
|
+
import { cronManager, normalizeJob } from './cron.js';
|
|
6
7
|
import type { Message } from './agent/types.js';
|
|
7
8
|
import { createAgentSession } from './agent/agent-session.js';
|
|
8
9
|
import { createChatLogger } from './agent/chat-logger.js';
|
|
10
|
+
import { taskScheduler } from './agent/task-scheduler.js';
|
|
11
|
+
import { emitTurnStarted } from './events.js';
|
|
12
|
+
import { registerTurn, markParentExited } from './agent/turn-registry.js';
|
|
9
13
|
|
|
10
14
|
export { calculateDelay } from './agent/agent-runner.js';
|
|
11
15
|
|
|
@@ -24,9 +28,12 @@ export async function executeDirectMessage(
|
|
|
24
28
|
| 'subagent_update'
|
|
25
29
|
| 'router'
|
|
26
30
|
| 'other',
|
|
27
|
-
displayRole?: 'user' | 'agent'
|
|
31
|
+
displayRole?: 'user' | 'agent',
|
|
32
|
+
parentTurnId?: string
|
|
28
33
|
) {
|
|
29
|
-
const
|
|
34
|
+
const turnId = parentTurnId ?? randomUUID();
|
|
35
|
+
const emitLifecycle = !parentTurnId;
|
|
36
|
+
const logger = createChatLogger(chatId, subagentId, state.sessionId, turnId);
|
|
30
37
|
|
|
31
38
|
let msgId: string;
|
|
32
39
|
if (systemEvent) {
|
|
@@ -35,6 +42,7 @@ export async function executeDirectMessage(
|
|
|
35
42
|
event: systemEvent,
|
|
36
43
|
messageId: state.messageId,
|
|
37
44
|
...(displayRole ? { displayRole } : {}),
|
|
45
|
+
...(state.jobId ? { jobId: state.jobId } : {}),
|
|
38
46
|
});
|
|
39
47
|
msgId = sysMsg.id;
|
|
40
48
|
} else {
|
|
@@ -59,36 +67,57 @@ export async function executeDirectMessage(
|
|
|
59
67
|
cwd,
|
|
60
68
|
settings,
|
|
61
69
|
logger,
|
|
70
|
+
turnId,
|
|
62
71
|
});
|
|
63
72
|
let finalMessage: Message = {
|
|
64
73
|
id: state.messageId,
|
|
65
74
|
content: state.message,
|
|
66
75
|
env: state.env ?? {},
|
|
76
|
+
turnId,
|
|
67
77
|
};
|
|
68
78
|
|
|
69
79
|
// Process actions
|
|
70
80
|
if (state.action === 'stop') {
|
|
71
81
|
agentSession.stop();
|
|
82
|
+
if (!subagentId) {
|
|
83
|
+
await stopActiveSubagents(chatId, cwd);
|
|
84
|
+
}
|
|
72
85
|
return;
|
|
73
86
|
}
|
|
74
87
|
if (state.action === 'interrupt') {
|
|
75
88
|
finalMessage = agentSession.interrupt(finalMessage);
|
|
76
89
|
}
|
|
77
90
|
|
|
91
|
+
if (emitLifecycle) {
|
|
92
|
+
registerTurn(chatId, turnId);
|
|
93
|
+
emitTurnStarted({
|
|
94
|
+
chatId,
|
|
95
|
+
turnId,
|
|
96
|
+
rootMessageId: msgId,
|
|
97
|
+
...(state.externalRef ? { externalRef: state.externalRef } : {}),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
78
101
|
// Process message
|
|
79
102
|
const taskPromise = agentSession.handleMessage(finalMessage);
|
|
80
103
|
|
|
81
|
-
|
|
104
|
+
const settleTurn = async () => {
|
|
82
105
|
try {
|
|
83
106
|
await taskPromise;
|
|
107
|
+
if (emitLifecycle) markParentExited(turnId, 'ok');
|
|
84
108
|
} catch (err) {
|
|
109
|
+
if (emitLifecycle) markParentExited(turnId, 'error');
|
|
85
110
|
if (!(err instanceof Error && err.name === 'AbortError')) {
|
|
86
111
|
throw err;
|
|
87
112
|
}
|
|
88
113
|
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
if (!noWait) {
|
|
117
|
+
await settleTurn();
|
|
89
118
|
} else {
|
|
90
|
-
|
|
91
|
-
if (err
|
|
119
|
+
settleTurn().catch((err) => {
|
|
120
|
+
if (err?.name !== 'AbortError') {
|
|
92
121
|
console.error('Task execution error:', err);
|
|
93
122
|
}
|
|
94
123
|
});
|
|
@@ -100,7 +129,8 @@ export async function getInitialRouterState(
|
|
|
100
129
|
message: string,
|
|
101
130
|
chatSettings: Partial<ChatSettings>,
|
|
102
131
|
overrideAgentId?: string,
|
|
103
|
-
overrideSessionId?: string
|
|
132
|
+
overrideSessionId?: string,
|
|
133
|
+
externalRef?: string
|
|
104
134
|
): Promise<RouterState> {
|
|
105
135
|
const agentId = overrideAgentId ?? chatSettings.defaultAgent ?? 'default';
|
|
106
136
|
const sessionId = overrideSessionId ?? chatSettings.sessions?.[agentId] ?? 'default';
|
|
@@ -113,6 +143,7 @@ export async function getInitialRouterState(
|
|
|
113
143
|
agentId,
|
|
114
144
|
sessionId,
|
|
115
145
|
env: {},
|
|
146
|
+
...(externalRef ? { externalRef } : {}),
|
|
116
147
|
};
|
|
117
148
|
}
|
|
118
149
|
|
|
@@ -123,7 +154,8 @@ export async function handleUserMessage(
|
|
|
123
154
|
cwd: string = process.cwd(),
|
|
124
155
|
noWait: boolean = false,
|
|
125
156
|
sessionId?: string,
|
|
126
|
-
overrideAgentId?: string
|
|
157
|
+
overrideAgentId?: string,
|
|
158
|
+
externalRef?: string
|
|
127
159
|
): Promise<void> {
|
|
128
160
|
const chatSettings = (await readChatSettings(chatId, cwd)) ?? {};
|
|
129
161
|
|
|
@@ -137,7 +169,8 @@ export async function handleUserMessage(
|
|
|
137
169
|
message,
|
|
138
170
|
chatSettings,
|
|
139
171
|
overrideAgentId,
|
|
140
|
-
sessionId
|
|
172
|
+
sessionId,
|
|
173
|
+
externalRef
|
|
141
174
|
);
|
|
142
175
|
|
|
143
176
|
const routers = chatSettings.routers ?? settings?.routers ?? [];
|
|
@@ -146,7 +179,15 @@ export async function handleUserMessage(
|
|
|
146
179
|
|
|
147
180
|
await applyRouterStateUpdates(chatId, cwd, finalState, chatSettings, initialState.agentId);
|
|
148
181
|
|
|
149
|
-
await executeDirectMessage(
|
|
182
|
+
await executeDirectMessage(
|
|
183
|
+
chatId,
|
|
184
|
+
finalState,
|
|
185
|
+
settings,
|
|
186
|
+
cwd,
|
|
187
|
+
noWait,
|
|
188
|
+
message,
|
|
189
|
+
finalState.subagentId
|
|
190
|
+
);
|
|
150
191
|
}
|
|
151
192
|
|
|
152
193
|
export async function applyRouterStateUpdates(
|
|
@@ -185,12 +226,13 @@ export async function applyRouterStateUpdates(
|
|
|
185
226
|
}
|
|
186
227
|
|
|
187
228
|
if (finalState.jobs.add?.length) {
|
|
188
|
-
const
|
|
189
|
-
|
|
229
|
+
const normalized = finalState.jobs.add.map(normalizeJob);
|
|
230
|
+
const addMap = new Map(normalized.map((job) => [job.id, job]));
|
|
231
|
+
for (const job of normalized) {
|
|
190
232
|
cronManager.scheduleJob(chatId, job);
|
|
191
233
|
}
|
|
192
234
|
chatSettings.jobs = chatSettings.jobs.filter((job) => !addMap.has(job.id));
|
|
193
|
-
chatSettings.jobs.push(...
|
|
235
|
+
chatSettings.jobs.push(...normalized);
|
|
194
236
|
settingsChanged = true;
|
|
195
237
|
}
|
|
196
238
|
}
|
|
@@ -207,3 +249,25 @@ export async function applyRouterStateUpdates(
|
|
|
207
249
|
finalState.agentId = currentAgentId;
|
|
208
250
|
}
|
|
209
251
|
}
|
|
252
|
+
|
|
253
|
+
async function stopActiveSubagents(chatId: string, cwd: string): Promise<void> {
|
|
254
|
+
const sessionsToAbort: string[] = [];
|
|
255
|
+
await updateChatSettings(
|
|
256
|
+
chatId,
|
|
257
|
+
(settings) => {
|
|
258
|
+
if (settings.subagents) {
|
|
259
|
+
for (const sub of Object.values(settings.subagents)) {
|
|
260
|
+
if (sub.status === 'active') {
|
|
261
|
+
if (sub.sessionId) sessionsToAbort.push(sub.sessionId);
|
|
262
|
+
sub.status = 'failed';
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return settings;
|
|
267
|
+
},
|
|
268
|
+
cwd
|
|
269
|
+
);
|
|
270
|
+
for (const sessionId of sessionsToAbort) {
|
|
271
|
+
taskScheduler.abortTasks(sessionId);
|
|
272
|
+
}
|
|
273
|
+
}
|