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,160 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fsPromises from 'node:fs/promises';
|
|
4
|
+
import { createTRPCClient, httpLink, TRPCClientError } from '@trpc/client';
|
|
5
|
+
import { TestEnvironment, type ChatSubscription, policyWith } from '../_helpers/test-environment.js';
|
|
6
|
+
import type { AgentRouter } from '../../src/daemon/api/agent-router.js';
|
|
7
|
+
|
|
8
|
+
describe('Context-Aware Execution E2E', () => {
|
|
9
|
+
let env: TestEnvironment;
|
|
10
|
+
let chat: ChatSubscription | undefined;
|
|
11
|
+
|
|
12
|
+
beforeAll(async () => {
|
|
13
|
+
env = new TestEnvironment('e2e-context-cwd');
|
|
14
|
+
await env.setup();
|
|
15
|
+
await env.setupSubagentEnv({
|
|
16
|
+
policies: {
|
|
17
|
+
'print-cwd': {
|
|
18
|
+
description: 'Print the current working directory',
|
|
19
|
+
command: 'pwd',
|
|
20
|
+
args: [],
|
|
21
|
+
autoApprove: true,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Create a 'foo' subdirectory inside the debug-agent's working directory.
|
|
27
|
+
await fsPromises.mkdir(path.join(env.e2eDir, 'debug-agent', 'foo'), { recursive: true });
|
|
28
|
+
}, 30000);
|
|
29
|
+
|
|
30
|
+
afterAll(() => env.teardown(), 30000);
|
|
31
|
+
afterEach(() => env.disconnectAll());
|
|
32
|
+
|
|
33
|
+
it('should execute policy in the requested subdirectory', async () => {
|
|
34
|
+
await env.addChat('chat-cwd');
|
|
35
|
+
chat = await env.connect('chat-cwd');
|
|
36
|
+
|
|
37
|
+
// Simulate the agent navigating to 'foo' and calling the policy.
|
|
38
|
+
await env.sendMessage('cd foo && clawmini-lite.js request print-cwd', {
|
|
39
|
+
chat: 'chat-cwd',
|
|
40
|
+
agent: 'debug-agent',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const policy = await chat.waitForMessage(policyWith('approved'));
|
|
44
|
+
expect(policy.content).toContain(path.join('debug-agent', 'foo'));
|
|
45
|
+
}, 30000);
|
|
46
|
+
|
|
47
|
+
// The tests below send a *crafted* sandbox-relative cwd directly to the
|
|
48
|
+
// tRPC endpoint, because lite always sends process.cwd() (an absolute host
|
|
49
|
+
// path). We extract the debug-agent's API credentials by asking it to echo
|
|
50
|
+
// them to stdout.
|
|
51
|
+
describe('direct tRPC cwd handling', () => {
|
|
52
|
+
let agentClient: ReturnType<typeof createTRPCClient<AgentRouter>>;
|
|
53
|
+
|
|
54
|
+
beforeAll(async () => {
|
|
55
|
+
const { url, token } = await env.getAgentCredentials();
|
|
56
|
+
agentClient = createTRPCClient<AgentRouter>({
|
|
57
|
+
links: [httpLink({ url, headers: () => ({ Authorization: `Bearer ${token}` }) })],
|
|
58
|
+
});
|
|
59
|
+
}, 30000);
|
|
60
|
+
|
|
61
|
+
it('should strip environment baseDir from a sandbox-relative cwd', async () => {
|
|
62
|
+
// Configure an environment with a baseDir. The environment matches the
|
|
63
|
+
// debug-agent directory, so when the agent reports cwd `/sandbox/foo`,
|
|
64
|
+
// the server strips `/sandbox` and executes inside `<agentDir>/foo`.
|
|
65
|
+
const envConfigDir = path.resolve(env.e2eDir, '.clawmini/environments/sandboxed');
|
|
66
|
+
await fsPromises.mkdir(envConfigDir, { recursive: true });
|
|
67
|
+
await fsPromises.writeFile(
|
|
68
|
+
path.join(envConfigDir, 'env.json'),
|
|
69
|
+
JSON.stringify({ baseDir: '/sandbox' })
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
env.updateSettings({ environments: { './debug-agent': 'sandboxed' } });
|
|
73
|
+
|
|
74
|
+
const result = await agentClient.createPolicyRequest.mutate({
|
|
75
|
+
commandName: 'print-cwd',
|
|
76
|
+
args: [],
|
|
77
|
+
fileMappings: {},
|
|
78
|
+
cwd: '/sandbox/foo',
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(result.executionResult).toBeDefined();
|
|
82
|
+
expect(result.executionResult!.exitCode).toBe(0);
|
|
83
|
+
expect(result.executionResult!.stdout).toContain(path.join('debug-agent', 'foo'));
|
|
84
|
+
}, 15000);
|
|
85
|
+
|
|
86
|
+
it('should translate sandbox cwd when the env targets a parent of the agent dir', async () => {
|
|
87
|
+
// Simulates a cladding-like setup: the environment is enabled at the
|
|
88
|
+
// workspace root, and the agent lives in a subdirectory inside it. The
|
|
89
|
+
// agent (running in the VM) reports its cwd relative to the sandbox
|
|
90
|
+
// baseDir (`/vm-workspace/debug-agent/nested`), so the server must
|
|
91
|
+
// strip baseDir and resolve the remainder against the env target path
|
|
92
|
+
// (the workspace root) — not the agent dir, which would double-nest.
|
|
93
|
+
const envConfigDir = path.resolve(env.e2eDir, '.clawmini/environments/cladding-like');
|
|
94
|
+
await fsPromises.mkdir(envConfigDir, { recursive: true });
|
|
95
|
+
await fsPromises.writeFile(
|
|
96
|
+
path.join(envConfigDir, 'env.json'),
|
|
97
|
+
JSON.stringify({ baseDir: '/vm-workspace' })
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const settings = env.getSettings();
|
|
101
|
+
settings.environments = { './': 'cladding-like' };
|
|
102
|
+
env.writeSettings(settings);
|
|
103
|
+
|
|
104
|
+
await fsPromises.mkdir(path.join(env.e2eDir, 'debug-agent', 'nested'), { recursive: true });
|
|
105
|
+
|
|
106
|
+
const result = await agentClient.createPolicyRequest.mutate({
|
|
107
|
+
commandName: 'print-cwd',
|
|
108
|
+
args: [],
|
|
109
|
+
fileMappings: {},
|
|
110
|
+
cwd: '/vm-workspace/debug-agent/nested',
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(result.executionResult).toBeDefined();
|
|
114
|
+
expect(result.executionResult!.exitCode).toBe(0);
|
|
115
|
+
expect(result.executionResult!.stdout.trim()).toBe(
|
|
116
|
+
path.join(env.e2eDir, 'debug-agent', 'nested')
|
|
117
|
+
);
|
|
118
|
+
}, 15000);
|
|
119
|
+
|
|
120
|
+
it('should reject a cwd that escapes the env target dir', async () => {
|
|
121
|
+
// With the cladding-like env from the previous test still active, a
|
|
122
|
+
// sandbox cwd that resolves above the env target must be rejected.
|
|
123
|
+
await expect(
|
|
124
|
+
agentClient.createPolicyRequest.mutate({
|
|
125
|
+
commandName: 'print-cwd',
|
|
126
|
+
args: [],
|
|
127
|
+
fileMappings: {},
|
|
128
|
+
cwd: '/vm-workspace/../etc/passwd',
|
|
129
|
+
})
|
|
130
|
+
).rejects.toSatisfy((err: unknown) => {
|
|
131
|
+
return (
|
|
132
|
+
err instanceof TRPCClientError &&
|
|
133
|
+
/Security Error: Path resolves outside/.test(err.message)
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
}, 15000);
|
|
137
|
+
|
|
138
|
+
it('should reject a cwd that escapes the agent directory', async () => {
|
|
139
|
+
// Remove the environment setup from the previous test so the baseDir
|
|
140
|
+
// branch does not apply here.
|
|
141
|
+
const settings = env.getSettings();
|
|
142
|
+
delete settings.environments;
|
|
143
|
+
env.writeSettings(settings);
|
|
144
|
+
|
|
145
|
+
await expect(
|
|
146
|
+
agentClient.createPolicyRequest.mutate({
|
|
147
|
+
commandName: 'print-cwd',
|
|
148
|
+
args: [],
|
|
149
|
+
fileMappings: {},
|
|
150
|
+
cwd: '../../escape',
|
|
151
|
+
})
|
|
152
|
+
).rejects.toSatisfy((err: unknown) => {
|
|
153
|
+
return (
|
|
154
|
+
err instanceof TRPCClientError &&
|
|
155
|
+
/Security Error: Path resolves outside/.test(err.message)
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
}, 15000);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { TestEnvironment } from '../_helpers/test-environment.js';
|
|
5
|
+
|
|
6
|
+
// Regression test: policies whose `command` is a relative path (e.g. the
|
|
7
|
+
// built-in run-host uses `./.clawmini/policy-scripts/run-host.js`) must run
|
|
8
|
+
// successfully even when the triggering agent's cwd is somewhere other than
|
|
9
|
+
// the workspace root. resolvePolicies now resolves relative paths against the
|
|
10
|
+
// workspace root so spawn no longer depends on the caller's cwd.
|
|
11
|
+
describe('Policy with relative script path', () => {
|
|
12
|
+
let env: TestEnvironment;
|
|
13
|
+
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
env = new TestEnvironment('e2e-policy-relative-path');
|
|
16
|
+
await env.setup();
|
|
17
|
+
await env.setupSubagentEnv();
|
|
18
|
+
|
|
19
|
+
const scriptRelDir = 'tools';
|
|
20
|
+
const scriptRelPath = `./${scriptRelDir}/relative-policy.sh`;
|
|
21
|
+
const scriptAbsDir = path.resolve(env.e2eDir, scriptRelDir);
|
|
22
|
+
const scriptAbsPath = path.resolve(env.e2eDir, scriptRelPath);
|
|
23
|
+
|
|
24
|
+
fs.mkdirSync(scriptAbsDir, { recursive: true });
|
|
25
|
+
fs.writeFileSync(
|
|
26
|
+
scriptAbsPath,
|
|
27
|
+
'#!/bin/sh\necho "arg: $1"\n',
|
|
28
|
+
{ mode: 0o755 }
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
// autoApprove:true executes the policy directly from
|
|
32
|
+
// agent-policy-endpoints.createPolicyRequest, where hostCwd is resolved
|
|
33
|
+
// from the requesting agent's directory — making this path diverge from
|
|
34
|
+
// the workspace root even for the default configuration.
|
|
35
|
+
env.writePolicies({
|
|
36
|
+
'relative-policy': {
|
|
37
|
+
description: 'Policy whose command is a relative path from the workspace root',
|
|
38
|
+
command: scriptRelPath,
|
|
39
|
+
autoApprove: true,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}, 30000);
|
|
43
|
+
|
|
44
|
+
afterAll(() => env.teardown(), 30000);
|
|
45
|
+
|
|
46
|
+
it('runs the script regardless of the requesting agent cwd', async () => {
|
|
47
|
+
// The debug-agent runs commands in its own work dir (<workspaceRoot>/debug-agent),
|
|
48
|
+
// so a relative `command` of `./tools/relative-policy.sh` would resolve
|
|
49
|
+
// against that subdirectory without the fix — which does not contain the
|
|
50
|
+
// script. runLite proxies through the debug-agent's CLAW_API_URL/TOKEN so
|
|
51
|
+
// the request is created with that agent's identity.
|
|
52
|
+
const { stdout, code } = await env.runLite(
|
|
53
|
+
['request', 'relative-policy', '--', 'hello'],
|
|
54
|
+
{ cwd: path.resolve(env.e2eDir, 'debug-agent') }
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
expect(code).toBe(0);
|
|
58
|
+
expect(stdout).toContain('arg: hello');
|
|
59
|
+
}, 30000);
|
|
60
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { TestEnvironment } from '../_helpers/test-environment.js';
|
|
5
|
+
|
|
6
|
+
describe('E2E `requests show` script size handling', () => {
|
|
7
|
+
let env: TestEnvironment;
|
|
8
|
+
let agentDir = '';
|
|
9
|
+
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
env = new TestEnvironment('e2e-tmp-requests-show');
|
|
12
|
+
await env.setup();
|
|
13
|
+
await env.setupSubagentEnv({
|
|
14
|
+
policies: {
|
|
15
|
+
'small-script': {
|
|
16
|
+
description: 'A small inline-able script',
|
|
17
|
+
command: './.clawmini/policy-scripts/small-script.sh',
|
|
18
|
+
},
|
|
19
|
+
'large-script': {
|
|
20
|
+
description: 'A large script that should spill to tmp',
|
|
21
|
+
command: './.clawmini/policy-scripts/large-script.sh',
|
|
22
|
+
},
|
|
23
|
+
'system-cmd': {
|
|
24
|
+
description: 'A system command (no script body)',
|
|
25
|
+
command: 'echo',
|
|
26
|
+
args: ['hi'],
|
|
27
|
+
},
|
|
28
|
+
'symlink-escape': {
|
|
29
|
+
description: 'A symlink in policy-scripts/ pointing outside it',
|
|
30
|
+
command: './.clawmini/policy-scripts/symlink-escape.sh',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const scriptsDir = env.getClawminiPath('policy-scripts');
|
|
36
|
+
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
37
|
+
fs.writeFileSync(
|
|
38
|
+
path.join(scriptsDir, 'small-script.sh'),
|
|
39
|
+
'#!/bin/sh\necho hello-from-small\n',
|
|
40
|
+
{ mode: 0o755 }
|
|
41
|
+
);
|
|
42
|
+
// 5000 chars > MAX_INLINE_SCRIPT_LENGTH (4000), forcing the spill path.
|
|
43
|
+
fs.writeFileSync(
|
|
44
|
+
path.join(scriptsDir, 'large-script.sh'),
|
|
45
|
+
'#!/bin/sh\n' + 'x'.repeat(5000) + '\n',
|
|
46
|
+
{ mode: 0o755 }
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Plant a sensitive file outside policy-scripts/, then a symlink inside
|
|
50
|
+
// policy-scripts/ pointing at it. The endpoint must refuse to read the
|
|
51
|
+
// symlink target — string-prefix containment alone would allow this.
|
|
52
|
+
const sensitive = path.resolve(env.e2eDir, 'outside-sensitive.txt');
|
|
53
|
+
fs.writeFileSync(sensitive, 'top secret\n');
|
|
54
|
+
fs.symlinkSync(sensitive, path.join(scriptsDir, 'symlink-escape.sh'));
|
|
55
|
+
|
|
56
|
+
agentDir = path.resolve(env.e2eDir, 'debug-agent');
|
|
57
|
+
await env.getAgentCredentials();
|
|
58
|
+
}, 30000);
|
|
59
|
+
|
|
60
|
+
afterAll(() => env.teardown(), 30000);
|
|
61
|
+
|
|
62
|
+
const runLite = (args: string[]) => env.runLite(args, { cwd: agentDir });
|
|
63
|
+
|
|
64
|
+
it('shows a small script body inline', async () => {
|
|
65
|
+
const { stdout, stderr, code } = await runLite(['requests', 'show', 'small-script']);
|
|
66
|
+
|
|
67
|
+
expect(stderr).toBe('');
|
|
68
|
+
expect(code).toBe(0);
|
|
69
|
+
expect(stdout).toContain('"description": "A small inline-able script"');
|
|
70
|
+
expect(stdout).toContain('--- Script:');
|
|
71
|
+
expect(stdout).toContain('hello-from-small');
|
|
72
|
+
expect(stdout).not.toContain('copied to ./tmp/');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('spills a large script to the agent tmp dir instead of inlining', async () => {
|
|
76
|
+
const { stdout, stderr, code } = await runLite(['requests', 'show', 'large-script']);
|
|
77
|
+
|
|
78
|
+
expect(stderr).toBe('');
|
|
79
|
+
expect(code).toBe(0);
|
|
80
|
+
expect(stdout).toContain('--- Script:');
|
|
81
|
+
expect(stdout).toContain('copied to ./tmp/policy-script-large-script.sh');
|
|
82
|
+
// The huge body must not be inlined.
|
|
83
|
+
expect(stdout).not.toContain('x'.repeat(100));
|
|
84
|
+
|
|
85
|
+
const spillPath = path.join(agentDir, 'tmp', 'policy-script-large-script.sh');
|
|
86
|
+
expect(fs.existsSync(spillPath)).toBe(true);
|
|
87
|
+
const spilled = fs.readFileSync(spillPath, 'utf8');
|
|
88
|
+
expect(spilled).toContain('x'.repeat(5000));
|
|
89
|
+
expect(spilled.startsWith('#!/bin/sh')).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('overwrites the same spill file when shown again', async () => {
|
|
93
|
+
const spillPath = path.join(agentDir, 'tmp', 'policy-script-large-script.sh');
|
|
94
|
+
const before = fs.statSync(spillPath).size;
|
|
95
|
+
|
|
96
|
+
// Update the source script to a different (still-large) body.
|
|
97
|
+
const scriptPath = env.getClawminiPath('policy-scripts', 'large-script.sh');
|
|
98
|
+
fs.writeFileSync(scriptPath, '#!/bin/sh\n' + 'y'.repeat(6000) + '\n', { mode: 0o755 });
|
|
99
|
+
|
|
100
|
+
const { code } = await runLite(['requests', 'show', 'large-script']);
|
|
101
|
+
expect(code).toBe(0);
|
|
102
|
+
|
|
103
|
+
const after = fs.readFileSync(spillPath, 'utf8');
|
|
104
|
+
expect(after).toContain('y'.repeat(6000));
|
|
105
|
+
expect(after).not.toContain('x'.repeat(100));
|
|
106
|
+
expect(fs.statSync(spillPath).size).not.toBe(before);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('prints JSON only with no script body for system-command policies', async () => {
|
|
110
|
+
const { stdout, stderr, code } = await runLite(['requests', 'show', 'system-cmd']);
|
|
111
|
+
|
|
112
|
+
expect(stderr).toBe('');
|
|
113
|
+
expect(code).toBe(0);
|
|
114
|
+
expect(stdout).toContain('"command": "echo"');
|
|
115
|
+
expect(stdout).toContain('(no script body:');
|
|
116
|
+
expect(stdout).not.toContain('--- Script:');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('refuses to read a symlink in policy-scripts/ that points outside', async () => {
|
|
120
|
+
const { stdout, stderr, code } = await runLite([
|
|
121
|
+
'requests',
|
|
122
|
+
'show',
|
|
123
|
+
'symlink-escape',
|
|
124
|
+
]);
|
|
125
|
+
|
|
126
|
+
expect(stderr).toBe('');
|
|
127
|
+
expect(code).toBe(0);
|
|
128
|
+
// The body itself must never appear, regardless of what error path the
|
|
129
|
+
// endpoint takes.
|
|
130
|
+
expect(stdout).not.toContain('top secret');
|
|
131
|
+
// The CLI surfaces BAD_REQUEST as a "(no script body: ...)" hint.
|
|
132
|
+
expect(stdout).toContain('(no script body:');
|
|
133
|
+
expect(stdout).toContain('does not point at a script in policy-scripts/');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import fsPromises from 'node:fs/promises';
|
|
5
|
+
import { TestEnvironment } from '../_helpers/test-environment.js';
|
|
6
|
+
|
|
7
|
+
describe('E2E Requests Tests (Lite)', () => {
|
|
8
|
+
let env: TestEnvironment;
|
|
9
|
+
let agentDir = '';
|
|
10
|
+
|
|
11
|
+
beforeAll(async () => {
|
|
12
|
+
env = new TestEnvironment('e2e-tmp-requests');
|
|
13
|
+
await env.setup();
|
|
14
|
+
await env.setupSubagentEnv({
|
|
15
|
+
policies: {
|
|
16
|
+
'test-cmd': {
|
|
17
|
+
description: 'A test policy',
|
|
18
|
+
command: 'echo',
|
|
19
|
+
args: ['hello'],
|
|
20
|
+
allowHelp: true,
|
|
21
|
+
},
|
|
22
|
+
'no-help-cmd': {
|
|
23
|
+
description: 'A no help policy',
|
|
24
|
+
command: 'echo',
|
|
25
|
+
args: ['nohelp'],
|
|
26
|
+
},
|
|
27
|
+
'auto-cmd': {
|
|
28
|
+
description: 'An auto approve policy',
|
|
29
|
+
command: 'echo',
|
|
30
|
+
args: ['autoresult'],
|
|
31
|
+
autoApprove: true,
|
|
32
|
+
},
|
|
33
|
+
'interp-cmd': {
|
|
34
|
+
description: 'Auto-approve cat with {{myfile}} interpolation',
|
|
35
|
+
command: 'cat',
|
|
36
|
+
args: ['{{myfile}}'],
|
|
37
|
+
autoApprove: true,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
agentDir = path.resolve(env.e2eDir, 'debug-agent');
|
|
43
|
+
await env.getAgentCredentials();
|
|
44
|
+
}, 30000);
|
|
45
|
+
|
|
46
|
+
afterAll(() => env.teardown(), 30000);
|
|
47
|
+
|
|
48
|
+
// All requests tests spawn lite from the debug-agent's working dir so
|
|
49
|
+
// --file relative paths resolve correctly.
|
|
50
|
+
const runLite = (args: string[]) => env.runLite(args, { cwd: agentDir });
|
|
51
|
+
|
|
52
|
+
it('should list policies', async () => {
|
|
53
|
+
const { stdout, code } = await runLite(['requests', 'list']);
|
|
54
|
+
expect(code).toBe(0);
|
|
55
|
+
expect(stdout).toContain('Available Policies:');
|
|
56
|
+
expect(stdout).toContain('- test-cmd');
|
|
57
|
+
expect(stdout).toContain('Description: A test policy');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should run --help on underlying command', async () => {
|
|
61
|
+
const { stdout, code } = await runLite(['request', 'test-cmd', '--help']);
|
|
62
|
+
expect(code).toBe(0);
|
|
63
|
+
expect(stdout).toBeTruthy();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should block --help if allowHelp is not true', async () => {
|
|
67
|
+
const { stderr, code } = await runLite(['request', 'no-help-cmd', '--help']);
|
|
68
|
+
expect(code).toBe(1);
|
|
69
|
+
expect(stderr).toContain('This command does not support --help');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should create a request and return an ID', async () => {
|
|
73
|
+
const dummyFilePath = path.join(agentDir, 'dummy.txt');
|
|
74
|
+
await fsPromises.writeFile(dummyFilePath, 'dummy content');
|
|
75
|
+
|
|
76
|
+
const { stdout, stderr, code } = await runLite([
|
|
77
|
+
'request',
|
|
78
|
+
'test-cmd',
|
|
79
|
+
'--file',
|
|
80
|
+
`target=${dummyFilePath}`,
|
|
81
|
+
'--',
|
|
82
|
+
'extra1',
|
|
83
|
+
'extra2',
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
expect(stderr).toBe('');
|
|
87
|
+
expect(code).toBe(0);
|
|
88
|
+
expect(stdout).toContain('Request created successfully.');
|
|
89
|
+
expect(stdout).toContain('Request ID:');
|
|
90
|
+
// Guidance for the agent: explain the request is queued, how the result
|
|
91
|
+
// arrives, that polling is forbidden, and what to do next. Normalize
|
|
92
|
+
// whitespace so assertions don't depend on where console.log wraps.
|
|
93
|
+
const flat = stdout.replace(/\s+/g, ' ');
|
|
94
|
+
expect(flat).toContain('has not run yet');
|
|
95
|
+
expect(flat).toContain('queued for user approval');
|
|
96
|
+
expect(flat).toContain('new user message in this chat');
|
|
97
|
+
expect(flat).toContain('do not poll');
|
|
98
|
+
expect(flat).toContain('end your turn');
|
|
99
|
+
|
|
100
|
+
const requestsDir = path.join(env.e2eDir, '.clawmini', 'tmp', 'requests');
|
|
101
|
+
const files = (await fsPromises.readdir(requestsDir)).filter((f) => f.endsWith('.json'));
|
|
102
|
+
expect(files.length).toBe(1);
|
|
103
|
+
|
|
104
|
+
const req = JSON.parse(await fsPromises.readFile(path.join(requestsDir, files[0]!), 'utf8'));
|
|
105
|
+
expect(req.commandName).toBe('test-cmd');
|
|
106
|
+
expect(req.args).toEqual(['extra1', 'extra2']);
|
|
107
|
+
expect(req.fileMappings).toHaveProperty('target');
|
|
108
|
+
|
|
109
|
+
const snapshotContent = await fsPromises.readFile(req.fileMappings.target, 'utf8');
|
|
110
|
+
expect(snapshotContent).toBe('dummy content');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should synchronously output execution result for auto-approved policy', async () => {
|
|
114
|
+
const { stdout, stderr, code } = await runLite(['request', 'auto-cmd', '--', 'extra-auto']);
|
|
115
|
+
|
|
116
|
+
expect(stderr).toBe('');
|
|
117
|
+
expect(code).toBe(0);
|
|
118
|
+
expect(stdout.trim()).toBe('autoresult extra-auto');
|
|
119
|
+
// Guidance text is only for queued requests — auto-approved policies
|
|
120
|
+
// run synchronously and must not include it.
|
|
121
|
+
expect(stdout).not.toContain('has not run yet');
|
|
122
|
+
expect(stdout).not.toContain('do not poll');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should interpolate {{placeholder}} in policy args with snapshot path', async () => {
|
|
126
|
+
const sourcePath = path.join(agentDir, 'interp-source.txt');
|
|
127
|
+
await fsPromises.writeFile(sourcePath, 'interpolated file content');
|
|
128
|
+
|
|
129
|
+
const { stdout, stderr, code } = await runLite([
|
|
130
|
+
'request',
|
|
131
|
+
'interp-cmd',
|
|
132
|
+
'--file',
|
|
133
|
+
`myfile=interp-source.txt`,
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
expect(stderr).toBe('');
|
|
137
|
+
expect(code).toBe(0);
|
|
138
|
+
expect(stdout).toContain('interpolated file content');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('--file snapshot security', () => {
|
|
142
|
+
it('should reject a --file path that resolves outside the agent directory', async () => {
|
|
143
|
+
const { stderr, code } = await runLite([
|
|
144
|
+
'request',
|
|
145
|
+
'test-cmd',
|
|
146
|
+
'--file',
|
|
147
|
+
`target=../../../etc/hosts`,
|
|
148
|
+
]);
|
|
149
|
+
|
|
150
|
+
expect(code).toBe(1);
|
|
151
|
+
expect(stderr).toMatch(/Security Error: Path resolves outside/);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should reject a --file pointing at an absolute path outside the agent directory', async () => {
|
|
155
|
+
const { stderr, code } = await runLite([
|
|
156
|
+
'request',
|
|
157
|
+
'test-cmd',
|
|
158
|
+
'--file',
|
|
159
|
+
`target=/etc/hosts`,
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
expect(code).toBe(1);
|
|
163
|
+
expect(stderr).toMatch(/Security Error: Path resolves outside/);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should reject a --file that is a symlink', async () => {
|
|
167
|
+
fs.symlinkSync('/etc/hosts', path.join(agentDir, 'link-to-host.txt'));
|
|
168
|
+
|
|
169
|
+
const { stderr, code } = await runLite([
|
|
170
|
+
'request',
|
|
171
|
+
'test-cmd',
|
|
172
|
+
'--file',
|
|
173
|
+
`target=link-to-host.txt`,
|
|
174
|
+
]);
|
|
175
|
+
|
|
176
|
+
expect(code).toBe(1);
|
|
177
|
+
expect(stderr).toMatch(/Security Error: Symlinks are not allowed/);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should reject a --file larger than the 5MB snapshot cap', async () => {
|
|
181
|
+
await fsPromises.writeFile(path.join(agentDir, 'big.bin'), Buffer.alloc(6 * 1024 * 1024));
|
|
182
|
+
|
|
183
|
+
const { stderr, code } = await runLite([
|
|
184
|
+
'request',
|
|
185
|
+
'test-cmd',
|
|
186
|
+
'--file',
|
|
187
|
+
`target=big.bin`,
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
expect(code).toBe(1);
|
|
191
|
+
expect(stderr).toMatch(/exceeds maximum snapshot size of 5MB/);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should reject a --file pointing at a directory', async () => {
|
|
195
|
+
fs.mkdirSync(path.join(agentDir, 'a-directory'));
|
|
196
|
+
|
|
197
|
+
const { stderr, code } = await runLite([
|
|
198
|
+
'request',
|
|
199
|
+
'test-cmd',
|
|
200
|
+
'--file',
|
|
201
|
+
`target=a-directory`,
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
expect(code).toBe(1);
|
|
205
|
+
expect(stderr).toMatch(/Requested path is not a file/);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
});
|