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,10 +1,24 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import type { RouterState } from './types.js';
|
|
3
3
|
import { RequestStore } from '../request-store.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { readChatSettings, readPoliciesForPath, getWorkspaceRoot } from '../../shared/workspace.js';
|
|
5
|
+
import { resolveAgentDir } from '../api/router-utils.js';
|
|
6
|
+
import { executeRequest, resolveRequestCwd, truncateLargeOutput } from '../policy-utils.js';
|
|
6
7
|
import { appendMessage } from '../chats.js';
|
|
7
8
|
import type { SystemMessage } from '../../shared/chats.js';
|
|
9
|
+
import type { PolicyRequest } from '../../shared/policies.js';
|
|
10
|
+
import { executeDirectMessage } from '../message.js';
|
|
11
|
+
|
|
12
|
+
// Resolve which session the approval/rejection should be replayed on. The
|
|
13
|
+
// request may have been created in an earlier session (session-timeout, /new),
|
|
14
|
+
// so we always consult the chat's *current* session for that agent/subagent.
|
|
15
|
+
async function resolveTargetSessionId(chatId: string, req: PolicyRequest): Promise<string> {
|
|
16
|
+
const chatSettings = await readChatSettings(chatId);
|
|
17
|
+
if (req.subagentId) {
|
|
18
|
+
return chatSettings?.subagents?.[req.subagentId]?.sessionId ?? 'default';
|
|
19
|
+
}
|
|
20
|
+
return chatSettings?.sessions?.[req.agentId] ?? 'default';
|
|
21
|
+
}
|
|
8
22
|
|
|
9
23
|
async function loadAndValidateRequest(id: string, state: RouterState) {
|
|
10
24
|
const store = new RequestStore(getWorkspaceRoot());
|
|
@@ -47,39 +61,68 @@ export async function slashPolicies(state: RouterState): Promise<RouterState> {
|
|
|
47
61
|
if (error) return error;
|
|
48
62
|
if (!req || !store) return state; // Should not happen if error is undefined
|
|
49
63
|
|
|
50
|
-
const
|
|
64
|
+
const workspaceRoot = getWorkspaceRoot();
|
|
65
|
+
const agentDir = await resolveAgentDir(req.agentId, workspaceRoot);
|
|
66
|
+
const config = await readPoliciesForPath(agentDir, workspaceRoot);
|
|
51
67
|
const policy = config?.policies?.[req.commandName];
|
|
52
68
|
if (!policy) {
|
|
53
69
|
return { ...state, message: '', reply: `Policy not found: ${req.commandName}` };
|
|
54
70
|
}
|
|
55
71
|
|
|
56
|
-
req.state
|
|
72
|
+
const hostCwd = await resolveRequestCwd(req.cwd, state.agentId, workspaceRoot);
|
|
57
73
|
|
|
58
|
-
const
|
|
74
|
+
const result = await executeRequest(req, policy, hostCwd);
|
|
75
|
+
const { exitCode } = result;
|
|
76
|
+
const { stdout, stderr } = await truncateLargeOutput(
|
|
77
|
+
result.stdout,
|
|
78
|
+
result.stderr,
|
|
79
|
+
req.id,
|
|
80
|
+
state.agentId
|
|
81
|
+
);
|
|
59
82
|
|
|
60
|
-
req.
|
|
61
|
-
await store.save(req);
|
|
83
|
+
await store.delete(req.id);
|
|
62
84
|
|
|
63
85
|
const agentMessage = `Request ${id} approved.\n\n${wrapInHtml('stdout', stdout)}\n\n${wrapInHtml('stderr', stderr)}\n\nExit Code: ${exitCode}`;
|
|
64
86
|
|
|
65
|
-
const
|
|
87
|
+
const targetSessionId = await resolveTargetSessionId(state.chatId, req);
|
|
88
|
+
|
|
89
|
+
const userNotificationMsg: SystemMessage = {
|
|
66
90
|
id: randomUUID(),
|
|
67
91
|
messageId: state.messageId,
|
|
68
92
|
role: 'system',
|
|
69
93
|
event: 'policy_approved',
|
|
70
|
-
displayRole: '
|
|
71
|
-
content:
|
|
94
|
+
displayRole: 'agent',
|
|
95
|
+
content: `Request ${id} (\`${req.commandName}\`) approved.`,
|
|
72
96
|
timestamp: new Date().toISOString(),
|
|
73
|
-
|
|
97
|
+
// Explicitly omitted subagentId to show in main chat
|
|
98
|
+
sessionId: state.sessionId,
|
|
74
99
|
};
|
|
75
100
|
|
|
76
|
-
await appendMessage(state.chatId,
|
|
101
|
+
await appendMessage(state.chatId, userNotificationMsg);
|
|
102
|
+
|
|
103
|
+
await executeDirectMessage(
|
|
104
|
+
state.chatId,
|
|
105
|
+
{
|
|
106
|
+
messageId: randomUUID(),
|
|
107
|
+
message: agentMessage,
|
|
108
|
+
chatId: state.chatId,
|
|
109
|
+
agentId: req.agentId,
|
|
110
|
+
sessionId: targetSessionId,
|
|
111
|
+
...(req.subagentId ? { subagentId: req.subagentId } : {}),
|
|
112
|
+
env: state.env || {},
|
|
113
|
+
},
|
|
114
|
+
undefined,
|
|
115
|
+
getWorkspaceRoot(),
|
|
116
|
+
true, // noWait
|
|
117
|
+
agentMessage,
|
|
118
|
+
req.subagentId,
|
|
119
|
+
'policy_approved',
|
|
120
|
+
'user'
|
|
121
|
+
);
|
|
77
122
|
|
|
78
123
|
return {
|
|
79
124
|
...state,
|
|
80
|
-
message:
|
|
81
|
-
reply: `Approved request, running ${req.commandName}`,
|
|
82
|
-
...(req.subagentId ? { subagentId: req.subagentId } : {}),
|
|
125
|
+
message: '', // Prevents further router processing or duplicate user message logs
|
|
83
126
|
};
|
|
84
127
|
}
|
|
85
128
|
|
|
@@ -92,22 +135,11 @@ export async function slashPolicies(state: RouterState): Promise<RouterState> {
|
|
|
92
135
|
if (error) return error;
|
|
93
136
|
if (!req || !store) return state; // Should not happen if error is undefined
|
|
94
137
|
|
|
95
|
-
req.
|
|
96
|
-
req.rejectionReason = reason;
|
|
97
|
-
await store.save(req);
|
|
138
|
+
await store.delete(req.id);
|
|
98
139
|
|
|
99
140
|
const agentMessage = `Request ${id} rejected. Reason: ${reason}`;
|
|
100
141
|
|
|
101
|
-
const
|
|
102
|
-
id: randomUUID(),
|
|
103
|
-
messageId: state.messageId,
|
|
104
|
-
role: 'system',
|
|
105
|
-
event: 'policy_rejected',
|
|
106
|
-
displayRole: 'user',
|
|
107
|
-
content: agentMessage,
|
|
108
|
-
timestamp: new Date().toISOString(),
|
|
109
|
-
...(req.subagentId ? { subagentId: req.subagentId } : {}),
|
|
110
|
-
};
|
|
142
|
+
const targetSessionId = await resolveTargetSessionId(state.chatId, req);
|
|
111
143
|
|
|
112
144
|
const userNotificationMsg: SystemMessage = {
|
|
113
145
|
id: randomUUID(),
|
|
@@ -115,18 +147,37 @@ export async function slashPolicies(state: RouterState): Promise<RouterState> {
|
|
|
115
147
|
role: 'system',
|
|
116
148
|
event: 'policy_rejected',
|
|
117
149
|
displayRole: 'agent',
|
|
118
|
-
content:
|
|
150
|
+
content: `Request ${id} (\`${req.commandName}\`) rejected. Reason: ${reason}`,
|
|
119
151
|
timestamp: new Date().toISOString(),
|
|
120
|
-
|
|
152
|
+
// Explicitly omitted subagentId to show in main chat
|
|
153
|
+
sessionId: state.sessionId,
|
|
121
154
|
};
|
|
122
155
|
|
|
123
|
-
await appendMessage(state.chatId, logMsg);
|
|
124
156
|
await appendMessage(state.chatId, userNotificationMsg);
|
|
125
157
|
|
|
158
|
+
await executeDirectMessage(
|
|
159
|
+
state.chatId,
|
|
160
|
+
{
|
|
161
|
+
messageId: randomUUID(),
|
|
162
|
+
message: agentMessage,
|
|
163
|
+
chatId: state.chatId,
|
|
164
|
+
agentId: req.agentId,
|
|
165
|
+
sessionId: targetSessionId,
|
|
166
|
+
...(req.subagentId ? { subagentId: req.subagentId } : {}),
|
|
167
|
+
env: state.env || {},
|
|
168
|
+
},
|
|
169
|
+
undefined,
|
|
170
|
+
getWorkspaceRoot(),
|
|
171
|
+
true, // noWait
|
|
172
|
+
agentMessage,
|
|
173
|
+
req.subagentId,
|
|
174
|
+
'policy_rejected',
|
|
175
|
+
'user'
|
|
176
|
+
);
|
|
177
|
+
|
|
126
178
|
return {
|
|
127
179
|
...state,
|
|
128
|
-
message:
|
|
129
|
-
...(req.subagentId ? { subagentId: req.subagentId } : {}),
|
|
180
|
+
message: '', // Prevents further router processing or duplicate user message logs
|
|
130
181
|
};
|
|
131
182
|
}
|
|
132
183
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { slashRestart } from './slash-restart.js';
|
|
3
|
+
import { sendControlRequest } from '../../cli/supervisor-control.js';
|
|
4
|
+
import type { RouterState } from './types.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../cli/supervisor-control.js');
|
|
7
|
+
|
|
8
|
+
const baseState: RouterState = {
|
|
9
|
+
message: '',
|
|
10
|
+
messageId: 'mock-msg-id',
|
|
11
|
+
chatId: 'chat-1',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe('slashRestart', () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
vi.clearAllMocks();
|
|
17
|
+
vi.mocked(sendControlRequest).mockResolvedValue({ ok: true });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('passes through unrelated messages', async () => {
|
|
21
|
+
const state = { ...baseState, message: 'hello' };
|
|
22
|
+
const result = await slashRestart(state);
|
|
23
|
+
expect(result).toEqual(state);
|
|
24
|
+
expect(sendControlRequest).not.toHaveBeenCalled();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('forwards chatId/messageId to the supervisor for /restart', async () => {
|
|
28
|
+
const state = { ...baseState, message: '/restart' };
|
|
29
|
+
const result = await slashRestart(state);
|
|
30
|
+
expect(result.action).toBe('stop');
|
|
31
|
+
expect(result.reply).toBe('Restarting clawmini...');
|
|
32
|
+
expect(result.message).toBe('');
|
|
33
|
+
expect(sendControlRequest).toHaveBeenCalledWith({
|
|
34
|
+
action: 'restart',
|
|
35
|
+
chatId: 'chat-1',
|
|
36
|
+
messageId: 'mock-msg-id',
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('matches /restart with trailing whitespace', async () => {
|
|
41
|
+
const state = { ...baseState, message: '/restart ' };
|
|
42
|
+
const result = await slashRestart(state);
|
|
43
|
+
expect(result.action).toBe('stop');
|
|
44
|
+
expect(sendControlRequest).toHaveBeenCalled();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('does not match /restartfoo', async () => {
|
|
48
|
+
const state = { ...baseState, message: '/restartfoo' };
|
|
49
|
+
const result = await slashRestart(state);
|
|
50
|
+
expect(result).toEqual(state);
|
|
51
|
+
expect(sendControlRequest).not.toHaveBeenCalled();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('returns an error reply when the supervisor request rejects', async () => {
|
|
55
|
+
vi.mocked(sendControlRequest).mockRejectedValueOnce(new Error('socket missing'));
|
|
56
|
+
const state = { ...baseState, message: '/restart' };
|
|
57
|
+
const result = await slashRestart(state);
|
|
58
|
+
expect(result.action).toBe('stop');
|
|
59
|
+
expect(result.reply).toBe('Could not reach supervisor: socket missing.');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('returns an error reply when the supervisor reports !ok', async () => {
|
|
63
|
+
vi.mocked(sendControlRequest).mockResolvedValueOnce({ ok: false, error: 'busy' });
|
|
64
|
+
const state = { ...baseState, message: '/restart' };
|
|
65
|
+
const result = await slashRestart(state);
|
|
66
|
+
expect(result.action).toBe('stop');
|
|
67
|
+
expect(result.reply).toBe('Restart aborted: busy.');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { RouterState } from './types.js';
|
|
2
|
+
import { sendControlRequest } from '../../cli/supervisor-control.js';
|
|
3
|
+
|
|
4
|
+
export async function slashRestart(state: RouterState): Promise<RouterState> {
|
|
5
|
+
if (!/^\/restart(\s|$)/.test(state.message)) return state;
|
|
6
|
+
|
|
7
|
+
// Gated to user messages by USER_ROUTERS in resolveRouters — an agent
|
|
8
|
+
// tool output that happens to start with "/restart" never reaches us.
|
|
9
|
+
// The supervisor enqueues the post-restart "Clawmini restarted vX.Y.Z"
|
|
10
|
+
// SystemMessage on its side so the on-disk record reflects whether the
|
|
11
|
+
// restart was actually scheduled. We just plumb chatId + messageId
|
|
12
|
+
// through and surface any control-channel error to the user.
|
|
13
|
+
let res;
|
|
14
|
+
try {
|
|
15
|
+
res = await sendControlRequest({
|
|
16
|
+
action: 'restart',
|
|
17
|
+
chatId: state.chatId,
|
|
18
|
+
messageId: state.messageId,
|
|
19
|
+
});
|
|
20
|
+
} catch (err) {
|
|
21
|
+
return stop(
|
|
22
|
+
state,
|
|
23
|
+
`Could not reach supervisor: ${err instanceof Error ? err.message : String(err)}.`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
return stop(state, `Restart aborted: ${res.error ?? 'unknown error'}.`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return stop(state, 'Restarting clawmini...');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function stop(state: RouterState, reply: string): RouterState {
|
|
35
|
+
return { ...state, message: '', action: 'stop', reply };
|
|
36
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { slashShutdown } from './slash-shutdown.js';
|
|
3
|
+
import { sendControlRequest } from '../../cli/supervisor-control.js';
|
|
4
|
+
import type { RouterState } from './types.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../cli/supervisor-control.js');
|
|
7
|
+
|
|
8
|
+
const baseState: RouterState = {
|
|
9
|
+
message: '',
|
|
10
|
+
messageId: 'mock-msg-id',
|
|
11
|
+
chatId: 'chat-1',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe('slashShutdown', () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
vi.clearAllMocks();
|
|
17
|
+
vi.mocked(sendControlRequest).mockResolvedValue({ ok: true });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('passes through unrelated messages', async () => {
|
|
21
|
+
const state = { ...baseState, message: 'hello' };
|
|
22
|
+
const result = await slashShutdown(state);
|
|
23
|
+
expect(result).toEqual(state);
|
|
24
|
+
expect(sendControlRequest).not.toHaveBeenCalled();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('triggers shutdown for /shutdown', async () => {
|
|
28
|
+
const state = { ...baseState, message: '/shutdown' };
|
|
29
|
+
const result = await slashShutdown(state);
|
|
30
|
+
expect(result.action).toBe('stop');
|
|
31
|
+
expect(result.reply).toBe('Shutting down clawmini supervisor...');
|
|
32
|
+
expect(result.message).toBe('');
|
|
33
|
+
expect(sendControlRequest).toHaveBeenCalledWith({ action: 'shutdown' });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('does not match /shutdownz', async () => {
|
|
37
|
+
const state = { ...baseState, message: '/shutdownz' };
|
|
38
|
+
const result = await slashShutdown(state);
|
|
39
|
+
expect(result).toEqual(state);
|
|
40
|
+
expect(sendControlRequest).not.toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('returns an error reply when the supervisor request rejects', async () => {
|
|
44
|
+
vi.mocked(sendControlRequest).mockRejectedValueOnce(new Error('socket missing'));
|
|
45
|
+
const state = { ...baseState, message: '/shutdown' };
|
|
46
|
+
const result = await slashShutdown(state);
|
|
47
|
+
expect(result.action).toBe('stop');
|
|
48
|
+
expect(result.reply).toBe('Could not reach supervisor: socket missing.');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { RouterState } from './types.js';
|
|
2
|
+
import { sendControlRequest } from '../../cli/supervisor-control.js';
|
|
3
|
+
|
|
4
|
+
export async function slashShutdown(state: RouterState): Promise<RouterState> {
|
|
5
|
+
if (!/^\/shutdown(\s|$)/.test(state.message)) return state;
|
|
6
|
+
|
|
7
|
+
// Gated to user messages by USER_ROUTERS in resolveRouters — agents cannot
|
|
8
|
+
// trigger this even via tool output that happens to start with /shutdown.
|
|
9
|
+
let res;
|
|
10
|
+
try {
|
|
11
|
+
res = await sendControlRequest({ action: 'shutdown' });
|
|
12
|
+
} catch (err) {
|
|
13
|
+
return stop(
|
|
14
|
+
state,
|
|
15
|
+
`Could not reach supervisor: ${err instanceof Error ? err.message : String(err)}.`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!res.ok) {
|
|
20
|
+
return stop(state, `Shutdown aborted: ${res.error ?? 'unknown error'}.`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return stop(state, 'Shutting down clawmini supervisor...');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function stop(state: RouterState, reply: string): RouterState {
|
|
27
|
+
return { ...state, message: '', action: 'stop', reply };
|
|
28
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { slashUpgrade } from './slash-upgrade.js';
|
|
3
|
+
import { detectInstall } from '../../cli/install-detection.js';
|
|
4
|
+
import { sendControlRequest } from '../../cli/supervisor-control.js';
|
|
5
|
+
import type { RouterState } from './types.js';
|
|
6
|
+
|
|
7
|
+
vi.mock('../../cli/install-detection.js');
|
|
8
|
+
vi.mock('../../cli/supervisor-control.js');
|
|
9
|
+
|
|
10
|
+
const baseState: RouterState = {
|
|
11
|
+
message: '',
|
|
12
|
+
messageId: 'mock-msg-id',
|
|
13
|
+
chatId: 'chat-1',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const npmInstall = {
|
|
17
|
+
isNpmGlobal: true,
|
|
18
|
+
entryRealPath: '/usr/local/lib/node_modules/clawmini/dist/cli.js',
|
|
19
|
+
npmRootRealPath: '/usr/local/lib/node_modules',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe('slashUpgrade', () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
vi.clearAllMocks();
|
|
25
|
+
vi.mocked(sendControlRequest).mockResolvedValue({ ok: true });
|
|
26
|
+
vi.mocked(detectInstall).mockReturnValue(npmInstall);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('passes through unrelated messages', async () => {
|
|
30
|
+
const state = { ...baseState, message: 'hello' };
|
|
31
|
+
const result = await slashUpgrade(state);
|
|
32
|
+
expect(result).toEqual(state);
|
|
33
|
+
expect(sendControlRequest).not.toHaveBeenCalled();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('refuses upgrade when not installed via npm (e.g. npm link)', async () => {
|
|
37
|
+
vi.mocked(detectInstall).mockReturnValueOnce({
|
|
38
|
+
isNpmGlobal: false,
|
|
39
|
+
entryRealPath: '/Users/me/projects/clawmini/dist/cli/index.mjs',
|
|
40
|
+
npmRootRealPath: '/usr/local/lib/node_modules',
|
|
41
|
+
});
|
|
42
|
+
const state = { ...baseState, message: '/upgrade latest' };
|
|
43
|
+
const result = await slashUpgrade(state);
|
|
44
|
+
expect(result.action).toBe('stop');
|
|
45
|
+
expect(result.reply).toContain('not installed via');
|
|
46
|
+
expect(result.reply).toContain('/Users/me/projects/clawmini/dist/cli/index.mjs');
|
|
47
|
+
expect(sendControlRequest).not.toHaveBeenCalled();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('shows usage hint for bare /upgrade (does NOT trigger an install)', async () => {
|
|
51
|
+
const state = { ...baseState, message: '/upgrade' };
|
|
52
|
+
const result = await slashUpgrade(state);
|
|
53
|
+
expect(result.action).toBe('stop');
|
|
54
|
+
expect(result.reply).toContain('/upgrade requires an explicit target');
|
|
55
|
+
expect(result.reply).toContain('/upgrade latest');
|
|
56
|
+
expect(sendControlRequest).not.toHaveBeenCalled();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('forwards version, chatId, and messageId to the supervisor', async () => {
|
|
60
|
+
const state = { ...baseState, message: '/upgrade 1.2.3' };
|
|
61
|
+
const result = await slashUpgrade(state);
|
|
62
|
+
expect(result.action).toBe('stop');
|
|
63
|
+
expect(result.reply).toBe('Upgrading clawmini to 1.2.3... services will restart shortly.');
|
|
64
|
+
expect(sendControlRequest).toHaveBeenCalledWith({
|
|
65
|
+
action: 'upgrade',
|
|
66
|
+
version: '1.2.3',
|
|
67
|
+
chatId: 'chat-1',
|
|
68
|
+
messageId: 'mock-msg-id',
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('accepts the literal "latest"', async () => {
|
|
73
|
+
const state = { ...baseState, message: '/upgrade latest' };
|
|
74
|
+
const result = await slashUpgrade(state);
|
|
75
|
+
expect(result.action).toBe('stop');
|
|
76
|
+
expect(result.reply).toContain('Upgrading clawmini to latest');
|
|
77
|
+
expect(sendControlRequest).toHaveBeenCalledWith({
|
|
78
|
+
action: 'upgrade',
|
|
79
|
+
version: 'latest',
|
|
80
|
+
chatId: 'chat-1',
|
|
81
|
+
messageId: 'mock-msg-id',
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('rejects multi-token arguments to avoid smuggling extra npm flags', async () => {
|
|
86
|
+
const state = { ...baseState, message: '/upgrade 1.2.3 --registry=evil' };
|
|
87
|
+
const result = await slashUpgrade(state);
|
|
88
|
+
expect(result.action).toBe('stop');
|
|
89
|
+
expect(result.reply).toBe('Usage: /upgrade <version>');
|
|
90
|
+
expect(sendControlRequest).not.toHaveBeenCalled();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('rejects shell-meta versions even as a single token', async () => {
|
|
94
|
+
const state = { ...baseState, message: '/upgrade $(rm)' };
|
|
95
|
+
const result = await slashUpgrade(state);
|
|
96
|
+
expect(result.action).toBe('stop');
|
|
97
|
+
expect(result.reply).toContain('Invalid version');
|
|
98
|
+
expect(sendControlRequest).not.toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('returns an error reply when the supervisor reports !ok', async () => {
|
|
102
|
+
vi.mocked(sendControlRequest).mockResolvedValueOnce({ ok: false, error: 'busy' });
|
|
103
|
+
const state = { ...baseState, message: '/upgrade latest' };
|
|
104
|
+
const result = await slashUpgrade(state);
|
|
105
|
+
expect(result.action).toBe('stop');
|
|
106
|
+
expect(result.reply).toBe('Upgrade aborted: busy.');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('returns an error reply when the supervisor request rejects', async () => {
|
|
110
|
+
vi.mocked(sendControlRequest).mockRejectedValueOnce(new Error('socket missing'));
|
|
111
|
+
const state = { ...baseState, message: '/upgrade latest' };
|
|
112
|
+
const result = await slashUpgrade(state);
|
|
113
|
+
expect(result.action).toBe('stop');
|
|
114
|
+
expect(result.reply).toBe('Could not reach supervisor: socket missing.');
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { RouterState } from './types.js';
|
|
2
|
+
import { detectInstall } from '../../cli/install-detection.js';
|
|
3
|
+
import { sendControlRequest } from '../../cli/supervisor-control.js';
|
|
4
|
+
import { isAcceptableVersion } from '../../cli/supervisor-actions.js';
|
|
5
|
+
import { getClawminiVersion } from '../../shared/version.js';
|
|
6
|
+
|
|
7
|
+
export async function slashUpgrade(state: RouterState): Promise<RouterState> {
|
|
8
|
+
const trimmed = state.message.trim();
|
|
9
|
+
if (!/^\/upgrade(\s|$)/.test(trimmed)) return state;
|
|
10
|
+
|
|
11
|
+
// Gated to user messages by USER_ROUTERS in resolveRouters.
|
|
12
|
+
const info = detectInstall();
|
|
13
|
+
if (!info.isNpmGlobal) {
|
|
14
|
+
return stop(
|
|
15
|
+
state,
|
|
16
|
+
`Cannot upgrade: clawmini is not installed via \`npm install -g\` ` +
|
|
17
|
+
`(running from ${info.entryRealPath}). Skipping.`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const rest = trimmed.slice('/upgrade'.length).trim();
|
|
22
|
+
|
|
23
|
+
// Bare `/upgrade` is informational. Requiring an explicit version means a
|
|
24
|
+
// misclick (or a malicious tool output that somehow reached this router)
|
|
25
|
+
// can't silently install whatever the npm registry currently calls
|
|
26
|
+
// `latest`. The user has to opt in to the version they want.
|
|
27
|
+
if (rest === '') {
|
|
28
|
+
return stop(
|
|
29
|
+
state,
|
|
30
|
+
[
|
|
31
|
+
`Currently running clawmini v${getClawminiVersion()}.`,
|
|
32
|
+
'/upgrade requires an explicit target:',
|
|
33
|
+
' /upgrade latest — install whatever npm reports as the latest tag',
|
|
34
|
+
' /upgrade <version> — install a specific version (e.g. /upgrade 0.0.7)',
|
|
35
|
+
].join('\n')
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Single token only — extra args could be smuggled into the npm command
|
|
40
|
+
// line otherwise (npm install -g clawmini@<rest> via shell).
|
|
41
|
+
if (!/^\S+$/.test(rest)) {
|
|
42
|
+
return stop(state, 'Usage: /upgrade <version>');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const version = rest;
|
|
46
|
+
if (!isAcceptableVersion(version)) {
|
|
47
|
+
return stop(state, `Invalid version: ${version}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// The supervisor enqueues the post-upgrade reply on its side based on the
|
|
51
|
+
// outcome of `npm install -g`, so we don't pre-queue anything here.
|
|
52
|
+
let res;
|
|
53
|
+
try {
|
|
54
|
+
res = await sendControlRequest({
|
|
55
|
+
action: 'upgrade',
|
|
56
|
+
version,
|
|
57
|
+
chatId: state.chatId,
|
|
58
|
+
messageId: state.messageId,
|
|
59
|
+
});
|
|
60
|
+
} catch (err) {
|
|
61
|
+
return stop(
|
|
62
|
+
state,
|
|
63
|
+
`Could not reach supervisor: ${err instanceof Error ? err.message : String(err)}.`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
return stop(state, `Upgrade aborted: ${res.error ?? 'unknown error'}.`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return stop(state, `Upgrading clawmini to ${version}... services will restart shortly.`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function stop(state: RouterState, reply: string): RouterState {
|
|
75
|
+
return { ...state, message: '', action: 'stop', reply };
|
|
76
|
+
}
|
|
@@ -11,8 +11,15 @@ export interface RouterState {
|
|
|
11
11
|
env?: Record<string, string>;
|
|
12
12
|
reply?: string;
|
|
13
13
|
action?: 'stop' | 'interrupt' | 'continue';
|
|
14
|
+
externalRef?: string;
|
|
14
15
|
jobs?: {
|
|
15
16
|
add?: CronJob[];
|
|
16
17
|
remove?: string[];
|
|
17
18
|
};
|
|
19
|
+
/**
|
|
20
|
+
* CronJob id that fired this turn, when the router state was seeded by
|
|
21
|
+
* `cron.executeJob`. Threaded through to the `SystemMessage` so adapters
|
|
22
|
+
* can render a terse header instead of the prompt text.
|
|
23
|
+
*/
|
|
24
|
+
jobId?: string;
|
|
18
25
|
}
|
package/src/daemon/routers.ts
CHANGED
|
@@ -5,6 +5,10 @@ import { slashCommand } from './routers/slash-command.js';
|
|
|
5
5
|
import { slashStop } from './routers/slash-stop.js';
|
|
6
6
|
import { slashInterrupt } from './routers/slash-interrupt.js';
|
|
7
7
|
import { slashPolicies } from './routers/slash-policies.js';
|
|
8
|
+
import { slashModel } from './routers/slash-model.js';
|
|
9
|
+
import { slashRestart } from './routers/slash-restart.js';
|
|
10
|
+
import { slashShutdown } from './routers/slash-shutdown.js';
|
|
11
|
+
import { slashUpgrade } from './routers/slash-upgrade.js';
|
|
8
12
|
import { createSessionTimeoutRouter } from './routers/session-timeout.js';
|
|
9
13
|
import type { RouterConfig } from '../shared/config.js';
|
|
10
14
|
|
|
@@ -16,6 +20,10 @@ export const USER_ROUTERS: RouterConfig[] = [
|
|
|
16
20
|
'@clawmini/slash-stop',
|
|
17
21
|
'@clawmini/slash-interrupt',
|
|
18
22
|
'@clawmini/slash-policies',
|
|
23
|
+
'@clawmini/slash-model',
|
|
24
|
+
'@clawmini/slash-restart',
|
|
25
|
+
'@clawmini/slash-shutdown',
|
|
26
|
+
'@clawmini/slash-upgrade',
|
|
19
27
|
];
|
|
20
28
|
|
|
21
29
|
export function resolveRouters(
|
|
@@ -87,6 +95,14 @@ export async function executeRouterPipeline(
|
|
|
87
95
|
state = slashInterrupt(state);
|
|
88
96
|
} else if (router === '@clawmini/slash-policies') {
|
|
89
97
|
state = await slashPolicies(state);
|
|
98
|
+
} else if (router === '@clawmini/slash-model') {
|
|
99
|
+
state = await slashModel(state);
|
|
100
|
+
} else if (router === '@clawmini/slash-restart') {
|
|
101
|
+
state = await slashRestart(state);
|
|
102
|
+
} else if (router === '@clawmini/slash-shutdown') {
|
|
103
|
+
state = await slashShutdown(state);
|
|
104
|
+
} else if (router === '@clawmini/slash-upgrade') {
|
|
105
|
+
state = await slashUpgrade(state);
|
|
90
106
|
} else if (router === '@clawmini/session-timeout') {
|
|
91
107
|
state = createSessionTimeoutRouter(config)(state);
|
|
92
108
|
} else {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { prependBlockquote } from './blockquote.js';
|
|
3
|
+
|
|
4
|
+
describe('prependBlockquote', () => {
|
|
5
|
+
it('prefixes each line of the quote with "> " and separates body with a blank line', () => {
|
|
6
|
+
expect(prependBlockquote('hello\nworld', 'reply')).toBe('> hello\n> world\n\nreply');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('handles single-line quotes', () => {
|
|
10
|
+
expect(prependBlockquote('hello', 'reply')).toBe('> hello\n\nreply');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('handles multi-line replies', () => {
|
|
14
|
+
expect(prependBlockquote('q', 'a\nb')).toBe('> q\n\na\nb');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('trims surrounding whitespace from quoted text and body', () => {
|
|
18
|
+
expect(prependBlockquote(' hello\nworld \n', '\n\nreply\n')).toBe(
|
|
19
|
+
'> hello\n> world\n\nreply'
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('renders an attribution line when sender is provided', () => {
|
|
24
|
+
expect(prependBlockquote('hello\nworld', 'reply', 'Tom')).toBe(
|
|
25
|
+
'> **Tom said:**\n> hello\n> world\n\nreply'
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format `quoted` as a markdown blockquote prefixed before `body`.
|
|
3
|
+
*
|
|
4
|
+
* Each line of `quoted` is prefixed with `> `, and a blank line separates the
|
|
5
|
+
* quote from the body — CommonMark requires the blank line to terminate the
|
|
6
|
+
* blockquote, otherwise the body is lazily folded into it. If `sender` is
|
|
7
|
+
* provided, an attribution line (`> **{sender} said:**`) is rendered as the
|
|
8
|
+
* first line of the quote. Both inputs are trimmed.
|
|
9
|
+
*/
|
|
10
|
+
export function prependBlockquote(quoted: string, body: string, sender?: string): string {
|
|
11
|
+
const trimmedBody = body.trim();
|
|
12
|
+
const lines = quoted
|
|
13
|
+
.trim()
|
|
14
|
+
.split('\n')
|
|
15
|
+
.map((line) => `> ${line}`);
|
|
16
|
+
if (sender) {
|
|
17
|
+
lines.unshift(`> **${sender} said:**`);
|
|
18
|
+
}
|
|
19
|
+
return `${lines.join('\n')}\n\n${trimmedBody}`;
|
|
20
|
+
}
|