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,76 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { TestEnvironment } from '../_helpers/test-environment.js';
|
|
4
|
+
|
|
5
|
+
async function waitUntil(
|
|
6
|
+
cond: () => boolean,
|
|
7
|
+
timeoutMs: number,
|
|
8
|
+
intervalMs = 100
|
|
9
|
+
): Promise<boolean> {
|
|
10
|
+
const deadline = Date.now() + timeoutMs;
|
|
11
|
+
while (Date.now() < deadline) {
|
|
12
|
+
if (cond()) return true;
|
|
13
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
14
|
+
}
|
|
15
|
+
return cond();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('clawmini serve (detached) + down', () => {
|
|
19
|
+
let env: TestEnvironment;
|
|
20
|
+
|
|
21
|
+
beforeAll(async () => {
|
|
22
|
+
env = new TestEnvironment('e2e-tmp-serve');
|
|
23
|
+
await env.setup();
|
|
24
|
+
await env.init();
|
|
25
|
+
}, 30000);
|
|
26
|
+
|
|
27
|
+
afterAll(() => env.teardown(), 30000);
|
|
28
|
+
|
|
29
|
+
it('starts the supervisor in the background and writes a pid file', async () => {
|
|
30
|
+
const { code, stdout } = await env.runCli(['serve', '--detach']);
|
|
31
|
+
expect(code).toBe(0);
|
|
32
|
+
expect(stdout).toContain('Started clawmini supervisor in background');
|
|
33
|
+
|
|
34
|
+
const pidPath = env.getClawminiPath('supervisor.pid');
|
|
35
|
+
expect(await waitUntil(() => fs.existsSync(pidPath), 5000)).toBe(true);
|
|
36
|
+
|
|
37
|
+
const socketPath = env.getClawminiPath('daemon.sock');
|
|
38
|
+
expect(await waitUntil(() => fs.existsSync(socketPath), 10000)).toBe(true);
|
|
39
|
+
}, 30000);
|
|
40
|
+
|
|
41
|
+
it('refuses to start a second supervisor while one is running', async () => {
|
|
42
|
+
const { code, stderr } = await env.runCli(['serve', '--detach']);
|
|
43
|
+
expect(code).toBe(1);
|
|
44
|
+
expect(stderr).toContain('already running');
|
|
45
|
+
}, 15000);
|
|
46
|
+
|
|
47
|
+
it('clawmini down stops the supervisor and clears the socket + pid file', async () => {
|
|
48
|
+
const { code } = await env.runCli(['down']);
|
|
49
|
+
expect(code).toBe(0);
|
|
50
|
+
|
|
51
|
+
const pidPath = env.getClawminiPath('supervisor.pid');
|
|
52
|
+
const socketPath = env.getClawminiPath('daemon.sock');
|
|
53
|
+
expect(await waitUntil(() => !fs.existsSync(pidPath), 10000)).toBe(true);
|
|
54
|
+
expect(await waitUntil(() => !fs.existsSync(socketPath), 10000)).toBe(true);
|
|
55
|
+
}, 30000);
|
|
56
|
+
|
|
57
|
+
it('writes daemon output to .clawmini/logs/daemon.log', () => {
|
|
58
|
+
const logPath = env.getClawminiPath('logs', 'daemon.log');
|
|
59
|
+
expect(fs.existsSync(logPath)).toBe(true);
|
|
60
|
+
const content = fs.readFileSync(logPath, 'utf-8');
|
|
61
|
+
expect(content).toContain('Daemon initialized and listening');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('clawmini logs --service daemon prints prefixed output', async () => {
|
|
65
|
+
const { code, stdout } = await env.runCli(['logs', '--service', 'daemon']);
|
|
66
|
+
expect(code).toBe(0);
|
|
67
|
+
expect(stdout).toContain('[daemon]');
|
|
68
|
+
expect(stdout).toContain('Daemon initialized and listening');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('clawmini down is a no-op when nothing is running', async () => {
|
|
72
|
+
const { stdout, code } = await env.runCli(['down']);
|
|
73
|
+
expect(code).toBe(0);
|
|
74
|
+
expect(stdout).toContain('Daemon is not running');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const { runCli, setupE2E, teardownE2E } = createE2EContext('e2e-tmp-skills');
|
|
2
|
+
import { TestEnvironment } from '../_helpers/test-environment.js';
|
|
5
3
|
|
|
6
4
|
describe('E2E Skills Tests', () => {
|
|
5
|
+
let env: TestEnvironment;
|
|
6
|
+
|
|
7
7
|
beforeAll(async () => {
|
|
8
|
-
|
|
9
|
-
await
|
|
8
|
+
env = new TestEnvironment('e2e-tmp-skills');
|
|
9
|
+
await env.setup();
|
|
10
|
+
await env.runCli(['init', '--agent', 'test-agent']);
|
|
10
11
|
}, 30000);
|
|
11
12
|
|
|
12
|
-
afterAll(
|
|
13
|
+
afterAll(() => env.teardown(), 30000);
|
|
13
14
|
|
|
14
15
|
it('should list available template skills', async () => {
|
|
15
|
-
const { stdout, code, stderr } = await runCli(['skills', 'list']);
|
|
16
|
+
const { stdout, code, stderr } = await env.runCli(['skills', 'list']);
|
|
16
17
|
expect(code).toBe(0);
|
|
17
18
|
// As it reads from the internal templates/skills directory,
|
|
18
19
|
// it should at least output one of the default template skills, or "No skills found."
|
|
@@ -22,7 +23,7 @@ describe('E2E Skills Tests', () => {
|
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
it('should add a specific skill', async () => {
|
|
25
|
-
const { stdout, code, stderr } = await runCli([
|
|
26
|
+
const { stdout, code, stderr } = await env.runCli([
|
|
26
27
|
'skills',
|
|
27
28
|
'add',
|
|
28
29
|
'skill-creator',
|
|
@@ -35,14 +36,14 @@ describe('E2E Skills Tests', () => {
|
|
|
35
36
|
});
|
|
36
37
|
|
|
37
38
|
it('should add all skills when no skill name is provided', async () => {
|
|
38
|
-
const { stdout, code, stderr } = await runCli(['skills', 'add', '--agent', 'test-agent']);
|
|
39
|
+
const { stdout, code, stderr } = await env.runCli(['skills', 'add', '--agent', 'test-agent']);
|
|
39
40
|
expect(stderr).toBe('');
|
|
40
41
|
expect(code).toBe(0);
|
|
41
42
|
expect(stdout).toContain('Successfully added all skills to agent');
|
|
42
43
|
});
|
|
43
44
|
|
|
44
45
|
it('should handle adding an invalid skill gracefully', async () => {
|
|
45
|
-
const { code, stderr } = await runCli([
|
|
46
|
+
const { code, stderr } = await env.runCli([
|
|
46
47
|
'skills',
|
|
47
48
|
'add',
|
|
48
49
|
'invalid-skill-name-123',
|
|
@@ -1,22 +1,30 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import fs from 'node:fs';
|
|
5
|
-
import { getSocketPath } from '../../shared/workspace.js';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
import { getSocketPath } from '../../src/shared/workspace.js';
|
|
6
|
+
import {
|
|
7
|
+
TestEnvironment,
|
|
8
|
+
findFreePort,
|
|
9
|
+
type ChatSubscription,
|
|
10
|
+
} from '../_helpers/test-environment.js';
|
|
11
|
+
import { commandWith } from '../_helpers/test-environment.js';
|
|
9
12
|
|
|
10
13
|
describe('E2E Daemon and Web Tests', () => {
|
|
14
|
+
let env: TestEnvironment;
|
|
15
|
+
let chat: ChatSubscription | undefined;
|
|
16
|
+
|
|
11
17
|
beforeAll(async () => {
|
|
12
|
-
|
|
13
|
-
await
|
|
18
|
+
env = new TestEnvironment('e2e-daemon');
|
|
19
|
+
await env.setup();
|
|
20
|
+
await env.init();
|
|
14
21
|
}, 30000);
|
|
15
22
|
|
|
16
|
-
afterAll(
|
|
23
|
+
afterAll(() => env.teardown(), 30000);
|
|
24
|
+
afterEach(() => env.disconnectAll());
|
|
17
25
|
|
|
18
26
|
it('should explicitly start the daemon via up command', async () => {
|
|
19
|
-
const { stdout, code } = await runCli(['up']);
|
|
27
|
+
const { stdout, code } = await env.runCli(['up']);
|
|
20
28
|
expect(code).toBe(0);
|
|
21
29
|
// Since the daemon is likely running from previous tests or init, it should say so
|
|
22
30
|
// or it will start successfully.
|
|
@@ -24,29 +32,29 @@ describe('E2E Daemon and Web Tests', () => {
|
|
|
24
32
|
});
|
|
25
33
|
|
|
26
34
|
it('should successfully shut down the daemon', async () => {
|
|
27
|
-
const { stdout, code } = await runCli(['down']);
|
|
35
|
+
const { stdout, code } = await env.runCli(['down']);
|
|
28
36
|
|
|
29
37
|
expect(code).toBe(0);
|
|
30
38
|
expect(stdout).toContain('Successfully shut down clawmini daemon.');
|
|
31
39
|
|
|
32
40
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
33
41
|
|
|
34
|
-
const socketPath = getSocketPath(e2eDir);
|
|
42
|
+
const socketPath = getSocketPath(env.e2eDir);
|
|
35
43
|
expect(fs.existsSync(socketPath)).toBe(false);
|
|
36
44
|
|
|
37
|
-
const { stdout: stdoutAgain, code: codeAgain } = await runCli(['down']);
|
|
45
|
+
const { stdout: stdoutAgain, code: codeAgain } = await env.runCli(['down']);
|
|
38
46
|
expect(codeAgain).toBe(0);
|
|
39
47
|
expect(stdoutAgain).toContain('Daemon is not running.');
|
|
40
48
|
|
|
41
|
-
const { stdout: stdoutUp, code: codeUp } = await runCli(['up']);
|
|
49
|
+
const { stdout: stdoutUp, code: codeUp } = await env.runCli(['up']);
|
|
42
50
|
expect(codeUp).toBe(0);
|
|
43
51
|
expect(stdoutUp).toContain('Successfully started clawmini daemon.');
|
|
44
52
|
}, 15000);
|
|
45
53
|
|
|
46
54
|
it('should run web command and serve static files', async () => {
|
|
47
|
-
const webPort =
|
|
48
|
-
const child = spawn('node', [binPath, 'web', '--port', webPort.toString()], {
|
|
49
|
-
cwd: e2eDir,
|
|
55
|
+
const webPort = await findFreePort();
|
|
56
|
+
const child = spawn('node', [env.binPath, 'web', '--port', webPort.toString()], {
|
|
57
|
+
cwd: env.e2eDir,
|
|
50
58
|
env: { ...process.env },
|
|
51
59
|
});
|
|
52
60
|
|
|
@@ -83,7 +91,7 @@ describe('E2E Daemon and Web Tests', () => {
|
|
|
83
91
|
const html404 = await res404.text();
|
|
84
92
|
expect(html404.toLowerCase()).toContain('<!doctype html>');
|
|
85
93
|
|
|
86
|
-
await
|
|
94
|
+
await env.addChat('api-test-chat');
|
|
87
95
|
|
|
88
96
|
const resChats = await fetch(`http://127.0.0.1:${webPort}/api/chats`);
|
|
89
97
|
expect(resChats.status).toBe(200);
|
|
@@ -144,7 +152,7 @@ describe('E2E Daemon and Web Tests', () => {
|
|
|
144
152
|
const reader = sseResponse.body.getReader();
|
|
145
153
|
const decoder = new TextDecoder();
|
|
146
154
|
|
|
147
|
-
const chatLogPath = path.resolve(e2eDir, '.clawmini/chats/api-test-chat/chat.jsonl');
|
|
155
|
+
const chatLogPath = path.resolve(env.e2eDir, '.clawmini/chats/api-test-chat/chat.jsonl');
|
|
148
156
|
const mockMessage = {
|
|
149
157
|
id: 'mock-1',
|
|
150
158
|
role: 'user',
|
|
@@ -220,203 +228,57 @@ describe('E2E Daemon and Web Tests', () => {
|
|
|
220
228
|
}, 30000);
|
|
221
229
|
|
|
222
230
|
it('should optionally start an HTTP API server for the daemon when configured', async () => {
|
|
223
|
-
await runCli(['down']);
|
|
224
|
-
|
|
225
|
-
const settingsPath = path.resolve(e2eDir, '.clawmini/settings.json');
|
|
226
|
-
let originalSettings = '{}';
|
|
227
|
-
if (fs.existsSync(settingsPath)) {
|
|
228
|
-
originalSettings = fs.readFileSync(settingsPath, 'utf8');
|
|
229
|
-
}
|
|
231
|
+
await env.runCli(['down']);
|
|
232
|
+
const originalSettings = env.getSettings();
|
|
230
233
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
JSON.stringify({
|
|
234
|
-
...JSON.parse(originalSettings),
|
|
235
|
-
api: { host: '127.0.0.1', port: 3005 },
|
|
236
|
-
})
|
|
237
|
-
);
|
|
234
|
+
const apiPort = await findFreePort();
|
|
235
|
+
env.writeSettings({ ...originalSettings, api: { host: '127.0.0.1', port: apiPort } });
|
|
238
236
|
|
|
239
|
-
const { stdout, code } = await runCli(['up']);
|
|
237
|
+
const { stdout, code } = await env.runCli(['up']);
|
|
240
238
|
expect(code).toBe(0);
|
|
241
239
|
expect(stdout).toContain('Successfully started clawmini daemon.');
|
|
242
240
|
|
|
243
241
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
244
242
|
|
|
245
|
-
const res = await fetch(
|
|
243
|
+
const res = await fetch(`http://127.0.0.1:${apiPort}/ping`);
|
|
246
244
|
expect(res.status).toBe(200);
|
|
247
245
|
const data = (await res.json()) as { result: { data: { status: string } } };
|
|
248
246
|
expect(data.result.data.status).toBe('ok');
|
|
249
247
|
|
|
250
|
-
await runCli(['down']);
|
|
251
|
-
|
|
252
|
-
await runCli(['up']);
|
|
248
|
+
await env.runCli(['down']);
|
|
249
|
+
env.writeSettings(originalSettings);
|
|
250
|
+
await env.runCli(['up']);
|
|
253
251
|
}, 15000);
|
|
254
252
|
|
|
255
253
|
it('should inject CLAW_API_URL and CLAW_API_TOKEN into spawned agents when API is enabled', async () => {
|
|
256
|
-
await runCli(['down']);
|
|
257
|
-
const
|
|
258
|
-
let originalSettings = '{}';
|
|
259
|
-
if (fs.existsSync(settingsPath)) {
|
|
260
|
-
originalSettings = fs.readFileSync(settingsPath, 'utf8');
|
|
261
|
-
}
|
|
262
|
-
fs.writeFileSync(
|
|
263
|
-
settingsPath,
|
|
264
|
-
JSON.stringify({
|
|
265
|
-
...JSON.parse(originalSettings),
|
|
266
|
-
api: { host: '127.0.0.1', port: 3006 },
|
|
267
|
-
})
|
|
268
|
-
);
|
|
269
|
-
await runCli(['up']);
|
|
270
|
-
|
|
271
|
-
await runCli(['agents', 'add', 'env-dumper', '--dir', 'env-dumper']);
|
|
272
|
-
const envDumperSettingsPath = path.resolve(e2eDir, '.clawmini/agents/env-dumper/settings.json');
|
|
273
|
-
fs.mkdirSync(path.dirname(envDumperSettingsPath), { recursive: true });
|
|
254
|
+
await env.runCli(['down']);
|
|
255
|
+
const originalSettings = env.getSettings();
|
|
274
256
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
fs.writeFileSync(
|
|
280
|
-
envDumperSettingsPath,
|
|
281
|
-
JSON.stringify({
|
|
282
|
-
commands: {
|
|
283
|
-
new: process.platform === 'win32' ? 'set' : 'env',
|
|
284
|
-
},
|
|
285
|
-
})
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
await runCli(['chats', 'add', 'env-chat']);
|
|
289
|
-
const { stdout, stderr, code } = await runCli([
|
|
290
|
-
'messages',
|
|
291
|
-
'send',
|
|
292
|
-
'dump it',
|
|
293
|
-
'--chat',
|
|
294
|
-
'env-chat',
|
|
295
|
-
'--agent',
|
|
296
|
-
'env-dumper',
|
|
297
|
-
]);
|
|
298
|
-
if (code !== 0) {
|
|
299
|
-
console.error('send failed:', stdout, stderr);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
303
|
-
|
|
304
|
-
const chatLogPath = path.resolve(e2eDir, '.clawmini/chats/env-chat/chat.jsonl');
|
|
305
|
-
expect(fs.existsSync(chatLogPath)).toBe(true);
|
|
306
|
-
const chatLogContent = fs.readFileSync(chatLogPath, 'utf8');
|
|
307
|
-
|
|
308
|
-
if (!chatLogContent.includes('CLAW_API_URL')) {
|
|
309
|
-
console.error('CHAT LOG:', chatLogContent);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
expect(chatLogContent).toContain('CLAW_API_URL=http://127.0.0.1:3006');
|
|
313
|
-
expect(chatLogContent).toContain('CLAW_API_TOKEN=');
|
|
314
|
-
|
|
315
|
-
await runCli(['down']);
|
|
316
|
-
fs.writeFileSync(settingsPath, originalSettings);
|
|
317
|
-
await runCli(['up']);
|
|
318
|
-
}, 15000);
|
|
319
|
-
|
|
320
|
-
it('should inject custom env vars and pointers when configured, and lite should work', async () => {
|
|
321
|
-
await runCli(['down']);
|
|
322
|
-
const settingsPath = path.resolve(e2eDir, '.clawmini/settings.json');
|
|
323
|
-
let originalSettings = '{}';
|
|
324
|
-
if (fs.existsSync(settingsPath)) {
|
|
325
|
-
originalSettings = fs.readFileSync(settingsPath, 'utf8');
|
|
326
|
-
}
|
|
327
|
-
fs.writeFileSync(
|
|
328
|
-
settingsPath,
|
|
329
|
-
JSON.stringify({
|
|
330
|
-
...JSON.parse(originalSettings),
|
|
331
|
-
api: { host: '127.0.0.1', port: 3006 },
|
|
332
|
-
})
|
|
333
|
-
);
|
|
334
|
-
await runCli(['up']);
|
|
335
|
-
|
|
336
|
-
await runCli(['agents', 'add', 'custom-env-dumper', '--dir', 'custom-env-dumper']);
|
|
337
|
-
const agentSettingsPath = path.resolve(
|
|
338
|
-
e2eDir,
|
|
339
|
-
'.clawmini/agents/custom-env-dumper/settings.json'
|
|
340
|
-
);
|
|
341
|
-
fs.mkdirSync(path.dirname(agentSettingsPath), { recursive: true });
|
|
257
|
+
const apiPort = await findFreePort();
|
|
258
|
+
env.writeSettings({ ...originalSettings, api: { host: '127.0.0.1', port: apiPort } });
|
|
259
|
+
await env.runCli(['up']);
|
|
342
260
|
|
|
261
|
+
await env.runCli(['agents', 'add', 'env-dumper', '--dir', 'env-dumper']);
|
|
343
262
|
// Create the actual agent working directory so spawn doesn't fail with ENOENT
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
agentSettingsPath,
|
|
351
|
-
JSON.stringify({
|
|
352
|
-
apiTokenEnvVar: 'MY_CUSTOM_TOKEN',
|
|
353
|
-
apiUrlEnvVar: 'MY_CUSTOM_URL',
|
|
354
|
-
commands: {
|
|
355
|
-
new: dumperScript,
|
|
356
|
-
},
|
|
357
|
-
})
|
|
358
|
-
);
|
|
359
|
-
|
|
360
|
-
await runCli(['chats', 'add', 'custom-env-chat']);
|
|
361
|
-
const { stdout, stderr, code } = await runCli([
|
|
362
|
-
'messages',
|
|
363
|
-
'send',
|
|
364
|
-
'dump custom',
|
|
365
|
-
'--chat',
|
|
366
|
-
'custom-env-chat',
|
|
367
|
-
'--agent',
|
|
368
|
-
'custom-env-dumper',
|
|
369
|
-
]);
|
|
370
|
-
if (code !== 0) {
|
|
371
|
-
console.error('send failed:', stdout, stderr);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
375
|
-
|
|
376
|
-
const chatLogPath = path.resolve(e2eDir, '.clawmini/chats/custom-env-chat/chat.jsonl');
|
|
377
|
-
expect(fs.existsSync(chatLogPath)).toBe(true);
|
|
378
|
-
|
|
379
|
-
const envTxtPath = path.resolve(agentWorkingDir, 'env.txt');
|
|
380
|
-
expect(fs.existsSync(envTxtPath)).toBe(true);
|
|
381
|
-
const envContent = fs.readFileSync(envTxtPath, 'utf8');
|
|
382
|
-
|
|
383
|
-
expect(envContent).toContain('CLAW_LITE_API_VAR=MY_CUSTOM_TOKEN');
|
|
384
|
-
expect(envContent).toContain('CLAW_LITE_URL_VAR=MY_CUSTOM_URL');
|
|
385
|
-
expect(envContent).toContain('MY_CUSTOM_URL=http://127.0.0.1:3006');
|
|
386
|
-
expect(envContent).toContain('MY_CUSTOM_TOKEN=');
|
|
263
|
+
fs.mkdirSync(path.resolve(env.e2eDir, 'env-dumper'), { recursive: true });
|
|
264
|
+
env.writeAgentSettings('env-dumper', {
|
|
265
|
+
commands: {
|
|
266
|
+
new: process.platform === 'win32' ? 'set' : 'env',
|
|
267
|
+
},
|
|
268
|
+
});
|
|
387
269
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
270
|
+
await env.addChat('env-chat');
|
|
271
|
+
chat = await env.connect('env-chat');
|
|
272
|
+
await env.sendMessage('dump it', { chat: 'env-chat', agent: 'env-dumper' });
|
|
391
273
|
|
|
392
|
-
|
|
393
|
-
expect(
|
|
274
|
+
const log = await chat.waitForMessage(commandWith('CLAW_API_URL='));
|
|
275
|
+
expect(log.stdout).toContain(`CLAW_API_URL=http://127.0.0.1:${apiPort}`);
|
|
276
|
+
expect(log.stdout).toMatch(/CLAW_API_TOKEN=.+/);
|
|
394
277
|
|
|
395
|
-
|
|
396
|
-
const envToken = tokenMatch![1]!.trim();
|
|
278
|
+
await env.disconnectAll();
|
|
397
279
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
...process.env,
|
|
402
|
-
MY_CUSTOM_URL: envUrl,
|
|
403
|
-
MY_CUSTOM_TOKEN: envToken,
|
|
404
|
-
CLAW_LITE_URL_VAR: 'MY_CUSTOM_URL',
|
|
405
|
-
CLAW_LITE_API_VAR: 'MY_CUSTOM_TOKEN',
|
|
406
|
-
},
|
|
407
|
-
cwd: agentWorkingDir,
|
|
408
|
-
});
|
|
409
|
-
let replyStdout = '';
|
|
410
|
-
replyProcess.stdout.on('data', (d) => (replyStdout += d.toString()));
|
|
411
|
-
replyProcess.stderr.on('data', (d) => (replyStdout += d.toString()));
|
|
412
|
-
await new Promise((resolve) => replyProcess.on('close', resolve));
|
|
413
|
-
expect(replyStdout).toContain('Reply message appended');
|
|
414
|
-
|
|
415
|
-
const chatLogContentReply = fs.readFileSync(chatLogPath, 'utf8');
|
|
416
|
-
expect(chatLogContentReply).toContain('hello from custom env');
|
|
417
|
-
|
|
418
|
-
await runCli(['down']);
|
|
419
|
-
fs.writeFileSync(settingsPath, originalSettings);
|
|
420
|
-
await runCli(['up']);
|
|
280
|
+
await env.runCli(['down']);
|
|
281
|
+
env.writeSettings(originalSettings);
|
|
282
|
+
await env.runCli(['up']);
|
|
421
283
|
}, 15000);
|
|
422
284
|
});
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import { TestEnvironment, commandWith } from '../_helpers/test-environment.js';
|
|
3
|
+
|
|
4
|
+
describe('E2E Agent Jobs (Lite)', () => {
|
|
5
|
+
let env: TestEnvironment;
|
|
6
|
+
let envUrl = '';
|
|
7
|
+
let envToken = '';
|
|
8
|
+
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
env = new TestEnvironment('e2e-tmp-agent-jobs');
|
|
11
|
+
await env.setup();
|
|
12
|
+
await env.setupSubagentEnv();
|
|
13
|
+
({ url: envUrl, token: envToken } = await env.getAgentCredentials());
|
|
14
|
+
}, 30000);
|
|
15
|
+
|
|
16
|
+
afterAll(() => env.teardown(), 30000);
|
|
17
|
+
|
|
18
|
+
// Filter out the session-timeout fixture job scheduled automatically by the
|
|
19
|
+
// debug-agent's router pipeline so assertions focus on user-added jobs.
|
|
20
|
+
async function listUserJobs(): Promise<Array<Record<string, unknown>>> {
|
|
21
|
+
const { stdout, code } = await env.runLite(['jobs', 'list']);
|
|
22
|
+
expect(code).toBe(0);
|
|
23
|
+
const all = JSON.parse(stdout) as Array<Record<string, unknown>>;
|
|
24
|
+
return all.filter(
|
|
25
|
+
(j) => !(typeof j.id === 'string' && j.id.startsWith('__session_timeout__'))
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
it('should return a parseable JSON array from list', async () => {
|
|
30
|
+
const { stdout, code } = await env.runLite(['jobs', 'list']);
|
|
31
|
+
expect(code).toBe(0);
|
|
32
|
+
expect(Array.isArray(JSON.parse(stdout))).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should add a job with the allowed flags and stamp agentId from the token', async () => {
|
|
36
|
+
const { stdout, code } = await env.runLite([
|
|
37
|
+
'jobs', 'add', 'agent-job-1',
|
|
38
|
+
'--every', '999h',
|
|
39
|
+
'--message', 'hello from agent',
|
|
40
|
+
'--reply', 'queued',
|
|
41
|
+
'--session', 'new',
|
|
42
|
+
]);
|
|
43
|
+
expect(code).toBe(0);
|
|
44
|
+
expect(stdout).toContain("Job 'agent-job-1' created successfully.");
|
|
45
|
+
|
|
46
|
+
const jobs = await listUserJobs();
|
|
47
|
+
const job = jobs.find((j) => j.id === 'agent-job-1');
|
|
48
|
+
expect(job).toMatchObject({
|
|
49
|
+
id: 'agent-job-1',
|
|
50
|
+
message: 'hello from agent',
|
|
51
|
+
reply: 'queued',
|
|
52
|
+
// Server stamps agentId from the token rather than accepting it as input.
|
|
53
|
+
agentId: 'debug-agent',
|
|
54
|
+
session: { type: 'new' },
|
|
55
|
+
schedule: { every: '999h' },
|
|
56
|
+
});
|
|
57
|
+
// Internal-only fields must never leak in from agent input.
|
|
58
|
+
expect(job).not.toHaveProperty('env');
|
|
59
|
+
expect(job).not.toHaveProperty('action');
|
|
60
|
+
expect(job).not.toHaveProperty('nextSessionId');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should reject job input containing internal-only fields', async () => {
|
|
64
|
+
const attempts = [
|
|
65
|
+
{ id: 'bad-env', schedule: { every: '999h' }, env: { FOO: 'BAR' } },
|
|
66
|
+
{ id: 'bad-action', schedule: { every: '999h' }, action: 'stop' },
|
|
67
|
+
{ id: 'bad-agent', schedule: { every: '999h' }, agentId: 'someone-else' },
|
|
68
|
+
{ id: 'bad-next', schedule: { every: '999h' }, nextSessionId: 'abc' },
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
for (const job of attempts) {
|
|
72
|
+
const res = await fetch(`${envUrl}/addCronJob`, {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: {
|
|
75
|
+
'Content-Type': 'application/json',
|
|
76
|
+
Authorization: `Bearer ${envToken}`,
|
|
77
|
+
},
|
|
78
|
+
body: JSON.stringify({ job }),
|
|
79
|
+
});
|
|
80
|
+
expect(res.status).toBe(400);
|
|
81
|
+
const body = (await res.json()) as { error?: { message?: string } };
|
|
82
|
+
expect(body.error?.message ?? '').toMatch(/unrecognized|invalid/i);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const jobs = await listUserJobs();
|
|
86
|
+
for (const attempt of attempts) {
|
|
87
|
+
expect(jobs.find((j) => j.id === attempt.id)).toBeUndefined();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should add a job with --cron schedule', async () => {
|
|
92
|
+
const { code } = await env.runLite([
|
|
93
|
+
'jobs', 'add', 'agent-job-cron',
|
|
94
|
+
'--cron', '0 0 * * *',
|
|
95
|
+
'--message', 'nightly',
|
|
96
|
+
]);
|
|
97
|
+
expect(code).toBe(0);
|
|
98
|
+
|
|
99
|
+
const jobs = await listUserJobs();
|
|
100
|
+
const job = jobs.find((j) => j.id === 'agent-job-cron');
|
|
101
|
+
expect(job).toMatchObject({ schedule: { cron: '0 0 * * *' }, message: 'nightly' });
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should add a job with --at schedule (interval), execute it, and auto-delete it', async () => {
|
|
105
|
+
// Jobs created via agent credentials run in the __creds__ chat (the chat
|
|
106
|
+
// the agent token is bound to); subscribe there to observe execution.
|
|
107
|
+
const chat = await env.connect('__creds__');
|
|
108
|
+
try {
|
|
109
|
+
const { code, stdout } = await env.runLite([
|
|
110
|
+
'jobs', 'add', 'agent-job-at-interval',
|
|
111
|
+
'--at', '2s',
|
|
112
|
+
'--message', 'echo job-ran-interval',
|
|
113
|
+
'--reply', 'queued',
|
|
114
|
+
'--session', 'new',
|
|
115
|
+
]);
|
|
116
|
+
expect(code).toBe(0);
|
|
117
|
+
expect(stdout).toContain("Job 'agent-job-at-interval' created successfully.");
|
|
118
|
+
|
|
119
|
+
const jobsBefore = await listUserJobs();
|
|
120
|
+
const jobBefore = jobsBefore.find((j) => j.id === 'agent-job-at-interval');
|
|
121
|
+
expect(jobBefore).toMatchObject({
|
|
122
|
+
schedule: { at: expect.stringMatching(/^\d{4}-\d{2}-\d{2}T/) },
|
|
123
|
+
message: 'echo job-ran-interval',
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Wait for the debug agent to echo the job message — proves the job
|
|
127
|
+
// actually fired end-to-end, not just that it was scheduled.
|
|
128
|
+
await chat.waitForMessage(commandWith('[DEBUG] echo job-ran-interval'), 8000);
|
|
129
|
+
|
|
130
|
+
const jobsAfter = await listUserJobs();
|
|
131
|
+
expect(jobsAfter.find((j) => j.id === 'agent-job-at-interval')).toBeUndefined();
|
|
132
|
+
} finally {
|
|
133
|
+
await chat.disconnect();
|
|
134
|
+
}
|
|
135
|
+
}, 15000);
|
|
136
|
+
|
|
137
|
+
it('should add a job with --at schedule (timestamp), execute it, and auto-delete it', async () => {
|
|
138
|
+
const chat = await env.connect('__creds__');
|
|
139
|
+
try {
|
|
140
|
+
const futureTime = new Date(Date.now() + 2000).toISOString();
|
|
141
|
+
const { code, stdout } = await env.runLite([
|
|
142
|
+
'jobs', 'add', 'agent-job-at-timestamp',
|
|
143
|
+
'--at', futureTime,
|
|
144
|
+
'--message', 'echo job-ran-timestamp',
|
|
145
|
+
'--reply', 'queued',
|
|
146
|
+
'--session', 'new',
|
|
147
|
+
]);
|
|
148
|
+
expect(code).toBe(0);
|
|
149
|
+
expect(stdout).toContain("Job 'agent-job-at-timestamp' created successfully.");
|
|
150
|
+
|
|
151
|
+
const jobsBefore = await listUserJobs();
|
|
152
|
+
const jobBefore = jobsBefore.find((j) => j.id === 'agent-job-at-timestamp');
|
|
153
|
+
expect(jobBefore).toMatchObject({
|
|
154
|
+
schedule: { at: futureTime },
|
|
155
|
+
message: 'echo job-ran-timestamp',
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
await chat.waitForMessage(commandWith('[DEBUG] echo job-ran-timestamp'), 8000);
|
|
159
|
+
|
|
160
|
+
const jobsAfter = await listUserJobs();
|
|
161
|
+
expect(jobsAfter.find((j) => j.id === 'agent-job-at-timestamp')).toBeUndefined();
|
|
162
|
+
} finally {
|
|
163
|
+
await chat.disconnect();
|
|
164
|
+
}
|
|
165
|
+
}, 15000);
|
|
166
|
+
|
|
167
|
+
it('should reject jobs with no schedule flag', async () => {
|
|
168
|
+
const { stderr, code } = await env.runLite(['jobs', 'add', 'no-sched', '--message', 'x']);
|
|
169
|
+
expect(code).toBe(1);
|
|
170
|
+
expect(stderr).toContain('A schedule must be specified');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should reject --session values other than "new"', async () => {
|
|
174
|
+
const { stderr, code } = await env.runLite([
|
|
175
|
+
'jobs', 'add', 'bad-session',
|
|
176
|
+
'--every', '999h',
|
|
177
|
+
'--session', 'bogus',
|
|
178
|
+
]);
|
|
179
|
+
expect(code).toBe(1);
|
|
180
|
+
expect(stderr).toContain('Only "new" session type is supported');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should replace a job when adding the same id twice', async () => {
|
|
184
|
+
await env.runLite(['jobs', 'add', 'dup-job', '--every', '999h', '--message', 'first']);
|
|
185
|
+
const { code } = await env.runLite([
|
|
186
|
+
'jobs', 'add', 'dup-job',
|
|
187
|
+
'--every', '888h',
|
|
188
|
+
'--message', 'second',
|
|
189
|
+
]);
|
|
190
|
+
expect(code).toBe(0);
|
|
191
|
+
|
|
192
|
+
const jobs = await listUserJobs();
|
|
193
|
+
const matches = jobs.filter((j) => j.id === 'dup-job');
|
|
194
|
+
expect(matches).toHaveLength(1);
|
|
195
|
+
expect(matches[0]).toMatchObject({
|
|
196
|
+
message: 'second',
|
|
197
|
+
schedule: { every: '888h' },
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should delete an existing job', async () => {
|
|
202
|
+
await env.runLite(['jobs', 'add', 'del-me', '--every', '999h']);
|
|
203
|
+
const { stdout, code } = await env.runLite(['jobs', 'delete', 'del-me']);
|
|
204
|
+
expect(code).toBe(0);
|
|
205
|
+
expect(stdout).toContain("Job 'del-me' deleted successfully.");
|
|
206
|
+
|
|
207
|
+
const jobs = await listUserJobs();
|
|
208
|
+
expect(jobs.find((j) => j.id === 'del-me')).toBeUndefined();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should report "not found" when deleting a missing job', async () => {
|
|
212
|
+
const { stdout, code } = await env.runLite(['jobs', 'delete', 'does-not-exist']);
|
|
213
|
+
expect(code).toBe(0);
|
|
214
|
+
expect(stdout).toContain("Job 'does-not-exist' not found.");
|
|
215
|
+
});
|
|
216
|
+
});
|