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
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
registerTurn,
|
|
4
|
+
incrementSubagent,
|
|
5
|
+
decrementSubagent,
|
|
6
|
+
markParentExited,
|
|
7
|
+
_resetTurnRegistryForTests,
|
|
8
|
+
_getTurnStateForTests,
|
|
9
|
+
} from './turn-registry.js';
|
|
10
|
+
import { daemonEvents, DAEMON_EVENT_TURN_ENDED, type TurnEndedEvent } from '../events.js';
|
|
11
|
+
|
|
12
|
+
describe('turn-registry', () => {
|
|
13
|
+
let ended: TurnEndedEvent[];
|
|
14
|
+
const capture = (e: TurnEndedEvent) => {
|
|
15
|
+
ended.push(e);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
ended = [];
|
|
20
|
+
daemonEvents.on(DAEMON_EVENT_TURN_ENDED, capture);
|
|
21
|
+
_resetTurnRegistryForTests();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
daemonEvents.off(DAEMON_EVENT_TURN_ENDED, capture);
|
|
26
|
+
_resetTurnRegistryForTests();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('fires turnEnded immediately when parent exits with no outstanding subagents', () => {
|
|
30
|
+
registerTurn('chat-1', 'turn-a');
|
|
31
|
+
markParentExited('turn-a', 'ok');
|
|
32
|
+
expect(ended).toEqual([{ chatId: 'chat-1', turnId: 'turn-a', outcome: 'ok' }]);
|
|
33
|
+
expect(_getTurnStateForTests('turn-a')).toBeUndefined();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('defers turnEnded until outstanding subagents drain', () => {
|
|
37
|
+
registerTurn('chat-1', 'turn-a');
|
|
38
|
+
incrementSubagent('turn-a');
|
|
39
|
+
incrementSubagent('turn-a');
|
|
40
|
+
markParentExited('turn-a', 'ok');
|
|
41
|
+
expect(ended).toEqual([]);
|
|
42
|
+
decrementSubagent('turn-a');
|
|
43
|
+
expect(ended).toEqual([]);
|
|
44
|
+
decrementSubagent('turn-a');
|
|
45
|
+
expect(ended).toEqual([{ chatId: 'chat-1', turnId: 'turn-a', outcome: 'ok' }]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('preserves outcome=error when parent exited with an error', () => {
|
|
49
|
+
registerTurn('chat-1', 'turn-a');
|
|
50
|
+
incrementSubagent('turn-a');
|
|
51
|
+
markParentExited('turn-a', 'error');
|
|
52
|
+
decrementSubagent('turn-a');
|
|
53
|
+
expect(ended).toEqual([{ chatId: 'chat-1', turnId: 'turn-a', outcome: 'error' }]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('ignores decrements below zero and extra markParentExited calls', () => {
|
|
57
|
+
registerTurn('chat-1', 'turn-a');
|
|
58
|
+
decrementSubagent('turn-a'); // no-op
|
|
59
|
+
markParentExited('turn-a', 'ok');
|
|
60
|
+
markParentExited('turn-a', 'error'); // ignored
|
|
61
|
+
expect(ended).toEqual([{ chatId: 'chat-1', turnId: 'turn-a', outcome: 'ok' }]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('force-fires turnEnded with outcome=error when the timeout elapses', () => {
|
|
65
|
+
vi.useFakeTimers();
|
|
66
|
+
try {
|
|
67
|
+
registerTurn('chat-1', 'turn-a', 100);
|
|
68
|
+
incrementSubagent('turn-a');
|
|
69
|
+
markParentExited('turn-a', 'ok');
|
|
70
|
+
expect(ended).toEqual([]);
|
|
71
|
+
vi.advanceTimersByTime(150);
|
|
72
|
+
expect(ended).toEqual([{ chatId: 'chat-1', turnId: 'turn-a', outcome: 'error' }]);
|
|
73
|
+
// A later decrement after force-fire is a no-op (turn already removed).
|
|
74
|
+
decrementSubagent('turn-a');
|
|
75
|
+
expect(ended).toHaveLength(1);
|
|
76
|
+
} finally {
|
|
77
|
+
vi.useRealTimers();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('is a no-op for unknown turnIds and undefined turnIds', () => {
|
|
82
|
+
incrementSubagent(undefined);
|
|
83
|
+
decrementSubagent(undefined);
|
|
84
|
+
incrementSubagent('never-registered');
|
|
85
|
+
decrementSubagent('never-registered');
|
|
86
|
+
markParentExited('never-registered', 'ok');
|
|
87
|
+
expect(ended).toEqual([]);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { emitTurnEnded } from '../events.js';
|
|
2
|
+
|
|
3
|
+
interface TurnState {
|
|
4
|
+
chatId: string;
|
|
5
|
+
outstanding: number;
|
|
6
|
+
parentExited: boolean;
|
|
7
|
+
outcome: 'ok' | 'error';
|
|
8
|
+
timeoutHandle: NodeJS.Timeout;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const turns = new Map<string, TurnState>();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Watchdog for turns whose subagent count never drains. Pathological cases
|
|
15
|
+
* (crashed subagent, bug leaving the counter > 0) would otherwise pin the
|
|
16
|
+
* turn's activity log open indefinitely. Default is a guess — instrument
|
|
17
|
+
* before tuning.
|
|
18
|
+
*/
|
|
19
|
+
export const DEFAULT_TURN_MAX_DURATION_MS = 30 * 60 * 1000;
|
|
20
|
+
|
|
21
|
+
export function registerTurn(
|
|
22
|
+
chatId: string,
|
|
23
|
+
turnId: string,
|
|
24
|
+
maxDurationMs: number = DEFAULT_TURN_MAX_DURATION_MS
|
|
25
|
+
): void {
|
|
26
|
+
if (turns.has(turnId)) return;
|
|
27
|
+
const state: TurnState = {
|
|
28
|
+
chatId,
|
|
29
|
+
outstanding: 0,
|
|
30
|
+
parentExited: false,
|
|
31
|
+
outcome: 'ok',
|
|
32
|
+
timeoutHandle: setTimeout(() => {
|
|
33
|
+
const s = turns.get(turnId);
|
|
34
|
+
if (!s) return;
|
|
35
|
+
console.warn(
|
|
36
|
+
`Turn ${turnId} force-ended after ${maxDurationMs}ms (outstanding=${s.outstanding}).`
|
|
37
|
+
);
|
|
38
|
+
turns.delete(turnId);
|
|
39
|
+
emitTurnEnded({ chatId: s.chatId, turnId, outcome: 'error' });
|
|
40
|
+
}, maxDurationMs),
|
|
41
|
+
};
|
|
42
|
+
state.timeoutHandle.unref();
|
|
43
|
+
turns.set(turnId, state);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function incrementSubagent(turnId: string | undefined): void {
|
|
47
|
+
if (!turnId) return;
|
|
48
|
+
const state = turns.get(turnId);
|
|
49
|
+
if (!state) return;
|
|
50
|
+
state.outstanding++;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function decrementSubagent(turnId: string | undefined): void {
|
|
54
|
+
if (!turnId) return;
|
|
55
|
+
const state = turns.get(turnId);
|
|
56
|
+
if (!state) return;
|
|
57
|
+
state.outstanding = Math.max(0, state.outstanding - 1);
|
|
58
|
+
maybeFinalize(turnId, state);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Called once, when the parent agent's initial `handleMessage` promise
|
|
63
|
+
* settles. Records the outcome; the turn actually ends (emits `turnEnded`)
|
|
64
|
+
* only once the outstanding subagent count also reaches zero.
|
|
65
|
+
*/
|
|
66
|
+
export function markParentExited(turnId: string, outcome: 'ok' | 'error'): void {
|
|
67
|
+
const state = turns.get(turnId);
|
|
68
|
+
if (!state) return;
|
|
69
|
+
if (state.parentExited) return;
|
|
70
|
+
state.parentExited = true;
|
|
71
|
+
state.outcome = outcome;
|
|
72
|
+
maybeFinalize(turnId, state);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function maybeFinalize(turnId: string, state: TurnState): void {
|
|
76
|
+
if (!state.parentExited) return;
|
|
77
|
+
if (state.outstanding > 0) return;
|
|
78
|
+
clearTimeout(state.timeoutHandle);
|
|
79
|
+
turns.delete(turnId);
|
|
80
|
+
emitTurnEnded({ chatId: state.chatId, turnId, outcome: state.outcome });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Test hook: drop all state without emitting events. */
|
|
84
|
+
export function _resetTurnRegistryForTests(): void {
|
|
85
|
+
for (const state of turns.values()) {
|
|
86
|
+
clearTimeout(state.timeoutHandle);
|
|
87
|
+
}
|
|
88
|
+
turns.clear();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Test hook: inspect registry state. */
|
|
92
|
+
export function _getTurnStateForTests(turnId: string): Readonly<TurnState> | undefined {
|
|
93
|
+
return turns.get(turnId);
|
|
94
|
+
}
|
|
@@ -30,6 +30,7 @@ export interface Logger {
|
|
|
30
30
|
event: SystemMessage['event'];
|
|
31
31
|
messageId?: string;
|
|
32
32
|
displayRole?: 'user' | 'agent';
|
|
33
|
+
jobId?: string;
|
|
33
34
|
}): Promise<SystemMessage>;
|
|
34
35
|
logSubagentStatus(options: {
|
|
35
36
|
subagentId: string;
|
|
@@ -56,6 +57,7 @@ export interface Message {
|
|
|
56
57
|
id: string;
|
|
57
58
|
content: string;
|
|
58
59
|
env: Record<string, string>;
|
|
60
|
+
turnId?: string;
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
export interface ExecutionResponse {
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { TRPCError } from '@trpc/server';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
6
|
+
import { apiProcedure } from './trpc.js';
|
|
7
|
+
import { getWorkspaceRoot, readPoliciesForPath, getClawminiDir } from '../../shared/workspace.js';
|
|
8
|
+
import { pathIsInsideDir } from '../../shared/utils/fs.js';
|
|
9
|
+
import { resolveAgentDir } from './router-utils.js';
|
|
10
|
+
import { PolicyRequestService } from '../policy-request-service.js';
|
|
11
|
+
import { RequestStore } from '../request-store.js';
|
|
12
|
+
import {
|
|
13
|
+
executeSafe,
|
|
14
|
+
generateRequestPreview,
|
|
15
|
+
executeRequest,
|
|
16
|
+
resolveRequestCwd,
|
|
17
|
+
truncateLargeOutput,
|
|
18
|
+
} from '../policy-utils.js';
|
|
19
|
+
import { appendMessage, type PolicyRequestMessage } from '../chats.js';
|
|
20
|
+
|
|
21
|
+
const MAX_POLICY_SCRIPT_BYTES = 1 * 1024 * 1024;
|
|
22
|
+
// Above this, the script is copied into the agent's tmp/ instead of being
|
|
23
|
+
// inlined in the response, so `requests show` does not flood the agent's
|
|
24
|
+
// context with a long script body. Mirrors truncateLargeOutput in policy-utils.
|
|
25
|
+
const MAX_INLINE_SCRIPT_LENGTH = 4000;
|
|
26
|
+
|
|
27
|
+
export const listPolicies = apiProcedure.query(async ({ ctx }) => {
|
|
28
|
+
const workspaceRoot = getWorkspaceRoot();
|
|
29
|
+
const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
|
|
30
|
+
const config = await readPoliciesForPath(agentDir, workspaceRoot);
|
|
31
|
+
return { policies: config?.policies || {} };
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Returns the contents of a policy's script file. Restricted to scripts inside
|
|
35
|
+
// `.clawmini/policy-scripts/` so an arbitrary `command` path (e.g. `/etc/passwd`
|
|
36
|
+
// or a built-in node binary) cannot be exfiltrated through this endpoint.
|
|
37
|
+
export const readPolicyScript = apiProcedure
|
|
38
|
+
.input(z.object({ commandName: z.string() }))
|
|
39
|
+
.query(async ({ input, ctx }) => {
|
|
40
|
+
const workspaceRoot = getWorkspaceRoot();
|
|
41
|
+
const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
|
|
42
|
+
const config = await readPoliciesForPath(agentDir, workspaceRoot);
|
|
43
|
+
const policy = config?.policies?.[input.commandName];
|
|
44
|
+
|
|
45
|
+
if (!policy) {
|
|
46
|
+
throw new TRPCError({
|
|
47
|
+
code: 'NOT_FOUND',
|
|
48
|
+
message: `Policy not found: ${input.commandName}`,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const scriptsDir = path.join(getClawminiDir(), 'policy-scripts');
|
|
53
|
+
const resolvedCommand = path.resolve(policy.command);
|
|
54
|
+
|
|
55
|
+
if (!pathIsInsideDir(resolvedCommand, scriptsDir, { allowSameDir: false })) {
|
|
56
|
+
throw new TRPCError({
|
|
57
|
+
code: 'BAD_REQUEST',
|
|
58
|
+
message: `Policy '${input.commandName}' does not point at a script in policy-scripts/.`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// realpath resolves symlinks in both paths; without this, a symlink inside
|
|
63
|
+
// policy-scripts/ pointing at /etc/passwd would pass the string-prefix
|
|
64
|
+
// check above, then fs.stat/readFile would dereference and exfiltrate.
|
|
65
|
+
let realCommand: string;
|
|
66
|
+
let realScriptsDir: string;
|
|
67
|
+
try {
|
|
68
|
+
realCommand = await fs.realpath(resolvedCommand);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
throw new TRPCError({
|
|
71
|
+
code: 'NOT_FOUND',
|
|
72
|
+
message: `Script file not found for policy '${input.commandName}': ${
|
|
73
|
+
err instanceof Error ? err.message : String(err)
|
|
74
|
+
}`,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
realScriptsDir = await fs.realpath(scriptsDir);
|
|
79
|
+
} catch {
|
|
80
|
+
throw new TRPCError({
|
|
81
|
+
code: 'BAD_REQUEST',
|
|
82
|
+
message: `Policy '${input.commandName}' does not point at a script in policy-scripts/.`,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (!pathIsInsideDir(realCommand, realScriptsDir, { allowSameDir: false })) {
|
|
86
|
+
throw new TRPCError({
|
|
87
|
+
code: 'BAD_REQUEST',
|
|
88
|
+
message: `Policy '${input.commandName}' does not point at a script in policy-scripts/.`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let stat;
|
|
93
|
+
try {
|
|
94
|
+
stat = await fs.stat(realCommand);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
throw new TRPCError({
|
|
97
|
+
code: 'NOT_FOUND',
|
|
98
|
+
message: `Script file not found for policy '${input.commandName}': ${
|
|
99
|
+
err instanceof Error ? err.message : String(err)
|
|
100
|
+
}`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!stat.isFile()) {
|
|
105
|
+
throw new TRPCError({
|
|
106
|
+
code: 'BAD_REQUEST',
|
|
107
|
+
message: `Script path for policy '${input.commandName}' is not a regular file.`,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (stat.size > MAX_POLICY_SCRIPT_BYTES) {
|
|
112
|
+
throw new TRPCError({
|
|
113
|
+
code: 'BAD_REQUEST',
|
|
114
|
+
message: `Script file exceeds the ${MAX_POLICY_SCRIPT_BYTES}-byte limit.`,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (stat.size > MAX_INLINE_SCRIPT_LENGTH) {
|
|
119
|
+
const tmpDir = path.join(agentDir, 'tmp');
|
|
120
|
+
await fs.mkdir(tmpDir, { recursive: true });
|
|
121
|
+
const ext = path.extname(realCommand);
|
|
122
|
+
const safeName = input.commandName.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
123
|
+
const destPath = path.join(tmpDir, `policy-script-${safeName}${ext}`);
|
|
124
|
+
await fs.copyFile(realCommand, destPath);
|
|
125
|
+
return {
|
|
126
|
+
path: realCommand,
|
|
127
|
+
size: stat.size,
|
|
128
|
+
spilledTo: `./tmp/policy-script-${safeName}${ext}`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const content = await fs.readFile(realCommand, 'utf8');
|
|
133
|
+
return { path: realCommand, size: stat.size, content };
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
export const executePolicyHelp = apiProcedure
|
|
137
|
+
.input(z.object({ commandName: z.string() }))
|
|
138
|
+
.query(async ({ input, ctx }) => {
|
|
139
|
+
const workspaceRoot = getWorkspaceRoot();
|
|
140
|
+
const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
|
|
141
|
+
const config = await readPoliciesForPath(agentDir, workspaceRoot);
|
|
142
|
+
const policy = config?.policies?.[input.commandName];
|
|
143
|
+
|
|
144
|
+
if (!policy) {
|
|
145
|
+
throw new TRPCError({
|
|
146
|
+
code: 'NOT_FOUND',
|
|
147
|
+
message: `Policy not found: ${input.commandName}`,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!policy.allowHelp) {
|
|
152
|
+
return { stdout: '', stderr: 'This command does not support --help\n', exitCode: 1 };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const fullArgs = [...(policy.args || []), '--help'];
|
|
156
|
+
const { stdout, stderr, exitCode } = await executeSafe(policy.command, fullArgs, {
|
|
157
|
+
cwd: getWorkspaceRoot(),
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return { stdout, stderr, exitCode };
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
export const createPolicyRequest = apiProcedure
|
|
164
|
+
.input(
|
|
165
|
+
z.object({
|
|
166
|
+
commandName: z.string(),
|
|
167
|
+
args: z.array(z.string()),
|
|
168
|
+
fileMappings: z.record(z.string(), z.string()),
|
|
169
|
+
// Path traversal is guarded by assertPathInsideDir in resolveRequestCwd
|
|
170
|
+
// (policy-utils.ts), which realpath-resolves the cwd before comparing —
|
|
171
|
+
// so it covers encoded separators, symlinks, etc.
|
|
172
|
+
cwd: z.string().optional(),
|
|
173
|
+
})
|
|
174
|
+
)
|
|
175
|
+
.mutation(async ({ input, ctx }) => {
|
|
176
|
+
if (!ctx.tokenPayload) throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Missing token' });
|
|
177
|
+
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
178
|
+
const snapshotDir = path.join(getClawminiDir(process.cwd()), 'tmp', 'snapshots');
|
|
179
|
+
const store = new RequestStore(process.cwd());
|
|
180
|
+
const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
|
|
181
|
+
const service = new PolicyRequestService(store, agentDir, snapshotDir);
|
|
182
|
+
|
|
183
|
+
const chatId = ctx.tokenPayload.chatId;
|
|
184
|
+
const agentId = ctx.tokenPayload.agentId;
|
|
185
|
+
|
|
186
|
+
const config = await readPoliciesForPath(agentDir, workspaceRoot);
|
|
187
|
+
const policy = config?.policies?.[input.commandName];
|
|
188
|
+
|
|
189
|
+
if (!policy) {
|
|
190
|
+
throw new TRPCError({
|
|
191
|
+
code: 'NOT_FOUND',
|
|
192
|
+
message: `Policy not found: ${input.commandName}`,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const isAutoApprove = !!policy.autoApprove;
|
|
197
|
+
|
|
198
|
+
const request = await service.createRequest(
|
|
199
|
+
input.commandName,
|
|
200
|
+
input.args,
|
|
201
|
+
input.fileMappings,
|
|
202
|
+
chatId,
|
|
203
|
+
agentId,
|
|
204
|
+
isAutoApprove,
|
|
205
|
+
ctx.tokenPayload.subagentId,
|
|
206
|
+
input.cwd
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
if (isAutoApprove) {
|
|
210
|
+
const hostCwd = await resolveRequestCwd(request.cwd, agentId, workspaceRoot);
|
|
211
|
+
|
|
212
|
+
const result = await executeRequest(request, policy, hostCwd);
|
|
213
|
+
const { exitCode, commandStr } = result;
|
|
214
|
+
const { stdout, stderr } = await truncateLargeOutput(
|
|
215
|
+
result.stdout,
|
|
216
|
+
result.stderr,
|
|
217
|
+
request.id,
|
|
218
|
+
agentId
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
request.executionResult = { stdout, stderr, exitCode };
|
|
222
|
+
|
|
223
|
+
const logMsg: PolicyRequestMessage = {
|
|
224
|
+
id: randomUUID(),
|
|
225
|
+
// TODO: we should store the message ID in the CLAW_API_TOKEN, and extract it here
|
|
226
|
+
messageId: randomUUID(),
|
|
227
|
+
role: 'policy',
|
|
228
|
+
requestId: request.id,
|
|
229
|
+
commandName: input.commandName,
|
|
230
|
+
args: input.args,
|
|
231
|
+
status: 'approved',
|
|
232
|
+
content: `[Auto-approved] Policy ${input.commandName} was executed.\n\nCommand: ${commandStr}\nExit Code: ${exitCode}\n\nStdout:\n${stdout}\n\nStderr:\n${stderr}`,
|
|
233
|
+
timestamp: new Date().toISOString(),
|
|
234
|
+
sessionId: ctx.tokenPayload.sessionId,
|
|
235
|
+
...(ctx.tokenPayload.subagentId ? { subagentId: ctx.tokenPayload.subagentId } : {}),
|
|
236
|
+
...(ctx.tokenPayload.turnId ? { turnId: ctx.tokenPayload.turnId } : {}),
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
await appendMessage(chatId, logMsg);
|
|
240
|
+
return request;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const previewContent = await generateRequestPreview(request);
|
|
244
|
+
|
|
245
|
+
const logMsg: PolicyRequestMessage = {
|
|
246
|
+
id: randomUUID(),
|
|
247
|
+
// TODO: we should store the message ID in the CLAW_API_TOKEN, and extract it here
|
|
248
|
+
messageId: randomUUID(),
|
|
249
|
+
role: 'policy',
|
|
250
|
+
requestId: request.id,
|
|
251
|
+
commandName: input.commandName,
|
|
252
|
+
args: input.args,
|
|
253
|
+
status: 'pending',
|
|
254
|
+
content: previewContent,
|
|
255
|
+
timestamp: new Date().toISOString(),
|
|
256
|
+
displayRole: 'agent',
|
|
257
|
+
sessionId: ctx.tokenPayload.sessionId,
|
|
258
|
+
...(ctx.tokenPayload.turnId ? { turnId: ctx.tokenPayload.turnId } : {}),
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
await appendMessage(chatId, logMsg);
|
|
262
|
+
return request;
|
|
263
|
+
});
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import path from 'node:path';
|
|
4
3
|
import { TRPCError } from '@trpc/server';
|
|
5
4
|
import {
|
|
6
5
|
appendMessage,
|
|
7
6
|
type CommandLogMessage,
|
|
8
7
|
type AgentReplyMessage,
|
|
9
8
|
type ToolMessage,
|
|
10
|
-
type PolicyRequestMessage,
|
|
11
9
|
} from '../chats.js';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { PolicyRequestService } from '../policy-request-service.js';
|
|
15
|
-
import { RequestStore } from '../request-store.js';
|
|
16
|
-
import { CronJobSchema } from '../../shared/config.js';
|
|
10
|
+
import { getWorkspaceRoot } from '../../shared/workspace.js';
|
|
11
|
+
import type { CronJob } from '../../shared/config.js';
|
|
17
12
|
import { apiProcedure, router } from './trpc.js';
|
|
18
13
|
import { taskScheduler } from '../agent/task-scheduler.js';
|
|
19
14
|
import { formatPendingMessages } from '../agent/utils.js';
|
|
@@ -62,7 +57,9 @@ export const logMessage = apiProcedure
|
|
|
62
57
|
command: `clawmini-lite log${filesArgStr}`,
|
|
63
58
|
cwd: process.cwd(),
|
|
64
59
|
exitCode: 0,
|
|
60
|
+
sessionId: ctx.tokenPayload.sessionId,
|
|
65
61
|
...(ctx.tokenPayload.subagentId ? { subagentId: ctx.tokenPayload.subagentId } : {}),
|
|
62
|
+
...(ctx.tokenPayload.turnId ? { turnId: ctx.tokenPayload.turnId } : {}),
|
|
66
63
|
...(filePaths.length > 0 ? { files: filePaths } : {}),
|
|
67
64
|
};
|
|
68
65
|
|
|
@@ -99,7 +96,9 @@ export const logReplyMessage = apiProcedure
|
|
|
99
96
|
role: 'agent',
|
|
100
97
|
content: input.message,
|
|
101
98
|
timestamp,
|
|
99
|
+
sessionId: ctx.tokenPayload.sessionId,
|
|
102
100
|
...(ctx.tokenPayload.subagentId ? { subagentId: ctx.tokenPayload.subagentId } : {}),
|
|
101
|
+
...(ctx.tokenPayload.turnId ? { turnId: ctx.tokenPayload.turnId } : {}),
|
|
103
102
|
...(filePaths.length > 0 ? { files: filePaths } : {}),
|
|
104
103
|
};
|
|
105
104
|
|
|
@@ -141,7 +140,9 @@ export const logToolMessage = apiProcedure
|
|
|
141
140
|
payload: payloadObj,
|
|
142
141
|
content: contentStr,
|
|
143
142
|
timestamp,
|
|
143
|
+
sessionId: ctx.tokenPayload.sessionId,
|
|
144
144
|
...(ctx.tokenPayload.subagentId ? { subagentId: ctx.tokenPayload.subagentId } : {}),
|
|
145
|
+
...(ctx.tokenPayload.turnId ? { turnId: ctx.tokenPayload.turnId } : {}),
|
|
145
146
|
};
|
|
146
147
|
|
|
147
148
|
await appendMessage(chatId, logMsg);
|
|
@@ -154,12 +155,42 @@ export const agentListCronJobs = apiProcedure.query(async ({ ctx }) => {
|
|
|
154
155
|
return listCronJobsShared(chatId);
|
|
155
156
|
});
|
|
156
157
|
|
|
158
|
+
// Agents may only set a restricted subset of CronJob fields. The remaining
|
|
159
|
+
// fields (agentId, createdAt, env, nextSessionId, action, jobs) are reserved
|
|
160
|
+
// for internal use and filled in by the server.
|
|
161
|
+
export const AgentCronJobInputSchema = z.strictObject({
|
|
162
|
+
id: z.string().min(1),
|
|
163
|
+
message: z.string().default(''),
|
|
164
|
+
reply: z.string().optional(),
|
|
165
|
+
session: z
|
|
166
|
+
.union([
|
|
167
|
+
z.strictObject({ type: z.literal('new') }),
|
|
168
|
+
z.strictObject({ type: z.literal('existing'), id: z.string() }),
|
|
169
|
+
])
|
|
170
|
+
.optional(),
|
|
171
|
+
schedule: z.union([
|
|
172
|
+
z.strictObject({ cron: z.string() }),
|
|
173
|
+
z.strictObject({ every: z.string() }),
|
|
174
|
+
z.strictObject({ at: z.string() }),
|
|
175
|
+
]),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
export type AgentCronJobInput = z.infer<typeof AgentCronJobInputSchema>;
|
|
179
|
+
|
|
157
180
|
export const agentAddCronJob = apiProcedure
|
|
158
|
-
.input(z.object({ job:
|
|
181
|
+
.input(z.object({ job: AgentCronJobInputSchema }))
|
|
159
182
|
.mutation(async ({ input, ctx }) => {
|
|
160
183
|
if (!ctx.tokenPayload) throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Missing token' });
|
|
161
184
|
const chatId = ctx.tokenPayload.chatId;
|
|
162
|
-
const job = {
|
|
185
|
+
const job: CronJob = {
|
|
186
|
+
id: input.job.id,
|
|
187
|
+
message: input.job.message,
|
|
188
|
+
schedule: input.job.schedule,
|
|
189
|
+
createdAt: new Date().toISOString(),
|
|
190
|
+
agentId: ctx.tokenPayload.agentId,
|
|
191
|
+
...(input.job.reply !== undefined ? { reply: input.job.reply } : {}),
|
|
192
|
+
...(input.job.session !== undefined ? { session: input.job.session } : {}),
|
|
193
|
+
};
|
|
163
194
|
return addCronJobShared(chatId, job);
|
|
164
195
|
});
|
|
165
196
|
|
|
@@ -171,123 +202,12 @@ export const agentDeleteCronJob = apiProcedure
|
|
|
171
202
|
return deleteCronJobShared(chatId, input.id);
|
|
172
203
|
});
|
|
173
204
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
.query(async ({ input }) => {
|
|
181
|
-
const config = await readPolicies();
|
|
182
|
-
const policy = config?.policies?.[input.commandName];
|
|
183
|
-
|
|
184
|
-
if (!policy) {
|
|
185
|
-
throw new TRPCError({
|
|
186
|
-
code: 'NOT_FOUND',
|
|
187
|
-
message: `Policy not found: ${input.commandName}`,
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (!policy.allowHelp) {
|
|
192
|
-
return { stdout: '', stderr: 'This command does not support --help\n', exitCode: 1 };
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const fullArgs = [...(policy.args || []), '--help'];
|
|
196
|
-
const { stdout, stderr, exitCode } = await executeSafe(policy.command, fullArgs, {
|
|
197
|
-
cwd: getWorkspaceRoot(),
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
return { stdout, stderr, exitCode };
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
export const createPolicyRequest = apiProcedure
|
|
204
|
-
.input(
|
|
205
|
-
z.object({
|
|
206
|
-
commandName: z.string(),
|
|
207
|
-
args: z.array(z.string()),
|
|
208
|
-
fileMappings: z.record(z.string(), z.string()),
|
|
209
|
-
})
|
|
210
|
-
)
|
|
211
|
-
.mutation(async ({ input, ctx }) => {
|
|
212
|
-
if (!ctx.tokenPayload) throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Missing token' });
|
|
213
|
-
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
214
|
-
const snapshotDir = path.join(getClawminiDir(process.cwd()), 'tmp', 'snapshots');
|
|
215
|
-
const store = new RequestStore(process.cwd());
|
|
216
|
-
const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
|
|
217
|
-
const service = new PolicyRequestService(store, agentDir, snapshotDir);
|
|
218
|
-
|
|
219
|
-
const chatId = ctx.tokenPayload.chatId;
|
|
220
|
-
const agentId = ctx.tokenPayload.agentId;
|
|
221
|
-
|
|
222
|
-
const config = await readPolicies();
|
|
223
|
-
const policy = config?.policies?.[input.commandName];
|
|
224
|
-
|
|
225
|
-
if (!policy) {
|
|
226
|
-
throw new TRPCError({
|
|
227
|
-
code: 'NOT_FOUND',
|
|
228
|
-
message: `Policy not found: ${input.commandName}`,
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const isAutoApprove = !!policy.autoApprove;
|
|
233
|
-
|
|
234
|
-
const request = await service.createRequest(
|
|
235
|
-
input.commandName,
|
|
236
|
-
input.args,
|
|
237
|
-
input.fileMappings,
|
|
238
|
-
chatId,
|
|
239
|
-
agentId,
|
|
240
|
-
isAutoApprove,
|
|
241
|
-
ctx.tokenPayload.subagentId
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
if (isAutoApprove) {
|
|
245
|
-
const { stdout, stderr, exitCode, commandStr } = await executeRequest(
|
|
246
|
-
request,
|
|
247
|
-
policy,
|
|
248
|
-
getWorkspaceRoot()
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
request.executionResult = { stdout, stderr, exitCode };
|
|
252
|
-
await store.save(request);
|
|
253
|
-
|
|
254
|
-
const logMsg: PolicyRequestMessage = {
|
|
255
|
-
id: randomUUID(),
|
|
256
|
-
// TODO: we should store the message ID in the CLAW_API_TOKEN, and extract it here
|
|
257
|
-
messageId: randomUUID(),
|
|
258
|
-
role: 'policy',
|
|
259
|
-
requestId: request.id,
|
|
260
|
-
commandName: input.commandName,
|
|
261
|
-
args: input.args,
|
|
262
|
-
status: 'approved',
|
|
263
|
-
content: `[Auto-approved] Policy ${input.commandName} was executed.\n\nCommand: ${commandStr}\nExit Code: ${exitCode}\n\nStdout:\n${stdout}\n\nStderr:\n${stderr}`,
|
|
264
|
-
timestamp: new Date().toISOString(),
|
|
265
|
-
...(ctx.tokenPayload.subagentId ? { subagentId: ctx.tokenPayload.subagentId } : {}),
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
await appendMessage(chatId, logMsg);
|
|
269
|
-
return request;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const previewContent = await generateRequestPreview(request);
|
|
273
|
-
|
|
274
|
-
const logMsg: PolicyRequestMessage = {
|
|
275
|
-
id: randomUUID(),
|
|
276
|
-
// TODO: we should store the message ID in the CLAW_API_TOKEN, and extract it here
|
|
277
|
-
messageId: randomUUID(),
|
|
278
|
-
role: 'policy',
|
|
279
|
-
requestId: request.id,
|
|
280
|
-
commandName: input.commandName,
|
|
281
|
-
args: input.args,
|
|
282
|
-
status: 'pending',
|
|
283
|
-
content: previewContent,
|
|
284
|
-
timestamp: new Date().toISOString(),
|
|
285
|
-
displayRole: 'agent',
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
await appendMessage(chatId, logMsg);
|
|
289
|
-
return request;
|
|
290
|
-
});
|
|
205
|
+
import {
|
|
206
|
+
listPolicies,
|
|
207
|
+
executePolicyHelp,
|
|
208
|
+
createPolicyRequest,
|
|
209
|
+
readPolicyScript,
|
|
210
|
+
} from './agent-policy-endpoints.js';
|
|
291
211
|
|
|
292
212
|
import { ping } from './user-router.js';
|
|
293
213
|
|
|
@@ -325,6 +245,7 @@ export const agentRouter = router({
|
|
|
325
245
|
listPolicies,
|
|
326
246
|
executePolicyHelp,
|
|
327
247
|
createPolicyRequest,
|
|
248
|
+
readPolicyScript,
|
|
328
249
|
fetchPendingMessages,
|
|
329
250
|
ping,
|
|
330
251
|
subagentSpawn,
|