clawmini 0.0.8 → 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/{vDehDcuJ.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.CUGC2p-K.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.0arZe_Uf.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.Bq2JzCEj.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 +0 -1
- 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 -118
- package/web/.svelte-kit/generated/server/internal.js +1 -1
- package/web/.svelte-kit/output/client/.vite/manifest.json +126 -136
- 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/{vDehDcuJ.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.CUGC2p-K.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.0arZe_Uf.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.Bq2JzCEj.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/D5iV40bG.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/entry/app.BCSV3nrG.js +0 -2
- package/dist/web/_app/immutable/entry/start.D4eLEZUM.js +0 -1
- package/dist/web/_app/immutable/nodes/1.CGC_42IQ.js +0 -1
- package/dist/web/_app/immutable/nodes/4.ClM1bXLE.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/D5iV40bG.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/entry/app.BCSV3nrG.js +0 -2
- package/web/.svelte-kit/output/client/_app/immutable/entry/start.D4eLEZUM.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/1.CGC_42IQ.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/4.ClM1bXLE.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,67 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { resolveEnabledServices } from './serve.js';
|
|
3
|
+
|
|
4
|
+
describe('resolveEnabledServices', () => {
|
|
5
|
+
it('defaults to daemon + web when no adapter configs are present', () => {
|
|
6
|
+
const result = resolveEnabledServices({
|
|
7
|
+
adapterConfigPresent: { 'adapter-discord': false, 'adapter-google-chat': false },
|
|
8
|
+
});
|
|
9
|
+
expect(result).toEqual(['daemon', 'web']);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('includes discord when its config file is present', () => {
|
|
13
|
+
const result = resolveEnabledServices({
|
|
14
|
+
adapterConfigPresent: { 'adapter-discord': true, 'adapter-google-chat': false },
|
|
15
|
+
});
|
|
16
|
+
expect(result).toEqual(['daemon', 'web', 'adapter-discord']);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('includes both adapters when both configs are present', () => {
|
|
20
|
+
const result = resolveEnabledServices({
|
|
21
|
+
adapterConfigPresent: { 'adapter-discord': true, 'adapter-google-chat': true },
|
|
22
|
+
});
|
|
23
|
+
expect(result).toEqual(['daemon', 'web', 'adapter-discord', 'adapter-google-chat']);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('honors --only as an explicit subset', () => {
|
|
27
|
+
const result = resolveEnabledServices({
|
|
28
|
+
only: 'daemon,web',
|
|
29
|
+
adapterConfigPresent: { 'adapter-discord': true, 'adapter-google-chat': true },
|
|
30
|
+
});
|
|
31
|
+
expect(result).toEqual(['daemon', 'web']);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('resolves adapter short names in --only', () => {
|
|
35
|
+
const result = resolveEnabledServices({
|
|
36
|
+
only: 'discord,google-chat',
|
|
37
|
+
adapterConfigPresent: { 'adapter-discord': false, 'adapter-google-chat': false },
|
|
38
|
+
});
|
|
39
|
+
expect(result).toEqual(['adapter-discord', 'adapter-google-chat']);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('filters via --exclude when no --only is given', () => {
|
|
43
|
+
const result = resolveEnabledServices({
|
|
44
|
+
exclude: 'web,discord',
|
|
45
|
+
adapterConfigPresent: { 'adapter-discord': true, 'adapter-google-chat': true },
|
|
46
|
+
});
|
|
47
|
+
expect(result).toEqual(['daemon', 'adapter-google-chat']);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('applies --exclude on top of --only', () => {
|
|
51
|
+
const result = resolveEnabledServices({
|
|
52
|
+
only: 'daemon,web,discord',
|
|
53
|
+
exclude: 'discord',
|
|
54
|
+
adapterConfigPresent: { 'adapter-discord': true, 'adapter-google-chat': false },
|
|
55
|
+
});
|
|
56
|
+
expect(result).toEqual(['daemon', 'web']);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('throws on unknown service name', () => {
|
|
60
|
+
expect(() =>
|
|
61
|
+
resolveEnabledServices({
|
|
62
|
+
only: 'nope',
|
|
63
|
+
adapterConfigPresent: { 'adapter-discord': false, 'adapter-google-chat': false },
|
|
64
|
+
})
|
|
65
|
+
).toThrow(/Unknown service/);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import net from 'node:net';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
import { getClawminiDir, getSocketPath } from '../../shared/workspace.js';
|
|
8
|
+
import { installBuiltinPolicies } from '../builtin-policies.js';
|
|
9
|
+
import { exportLiteToAllEnvironments } from '../../shared/lite.js';
|
|
10
|
+
import { ensureDefaultPoliciesFile, refreshAllAgents } from './up.js';
|
|
11
|
+
import { getDiscordConfigPath } from '../../adapter-discord/config.js';
|
|
12
|
+
import { getGoogleChatConfigPath } from '../../adapter-google-chat/config.js';
|
|
13
|
+
import { startSupervisorControl } from '../supervisor-actions.js';
|
|
14
|
+
import { getControlSocketPath } from '../supervisor-control.js';
|
|
15
|
+
import {
|
|
16
|
+
getSupervisorPidPath,
|
|
17
|
+
readSupervisorPid,
|
|
18
|
+
removeSupervisorPid,
|
|
19
|
+
writeSupervisorPid,
|
|
20
|
+
} from '../supervisor-pid.js';
|
|
21
|
+
import { Supervisor, type ServiceName } from '../supervisor.js';
|
|
22
|
+
|
|
23
|
+
const ALL_SERVICES: ServiceName[] = ['daemon', 'web', 'adapter-discord', 'adapter-google-chat'];
|
|
24
|
+
|
|
25
|
+
const SERVICE_ALIASES: Record<string, ServiceName> = {
|
|
26
|
+
daemon: 'daemon',
|
|
27
|
+
web: 'web',
|
|
28
|
+
discord: 'adapter-discord',
|
|
29
|
+
'adapter-discord': 'adapter-discord',
|
|
30
|
+
'google-chat': 'adapter-google-chat',
|
|
31
|
+
'adapter-google-chat': 'adapter-google-chat',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
interface ResolveInput {
|
|
35
|
+
only?: string | undefined;
|
|
36
|
+
exclude?: string | undefined;
|
|
37
|
+
adapterConfigPresent?: Partial<Record<ServiceName, boolean>> | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Exported for unit tests.
|
|
41
|
+
export function resolveEnabledServices(opts: ResolveInput): ServiceName[] {
|
|
42
|
+
const parseList = (csv: string | undefined): ServiceName[] => {
|
|
43
|
+
if (!csv) return [];
|
|
44
|
+
const names = csv
|
|
45
|
+
.split(',')
|
|
46
|
+
.map((s) => s.trim())
|
|
47
|
+
.filter(Boolean);
|
|
48
|
+
return names.map((name) => {
|
|
49
|
+
const resolved = SERVICE_ALIASES[name];
|
|
50
|
+
if (!resolved) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Unknown service: '${name}'. Valid: ${Object.keys(SERVICE_ALIASES).join(', ')}`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return resolved;
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const configPresent: Record<ServiceName, boolean> = {
|
|
60
|
+
daemon: true,
|
|
61
|
+
web: true,
|
|
62
|
+
'adapter-discord': false,
|
|
63
|
+
'adapter-google-chat': false,
|
|
64
|
+
...(opts.adapterConfigPresent ?? {}),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const excluded = new Set(parseList(opts.exclude));
|
|
68
|
+
|
|
69
|
+
const only = parseList(opts.only);
|
|
70
|
+
if (only.length > 0) {
|
|
71
|
+
return only.filter((n) => !excluded.has(n));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return ALL_SERVICES.filter((n) => configPresent[n] && !excluded.has(n));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function runPreStart(): Promise<void> {
|
|
78
|
+
await installBuiltinPolicies();
|
|
79
|
+
ensureDefaultPoliciesFile();
|
|
80
|
+
await exportLiteToAllEnvironments();
|
|
81
|
+
await refreshAllAgents();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface ServeOptions {
|
|
85
|
+
detach?: boolean;
|
|
86
|
+
only?: string;
|
|
87
|
+
exclude?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function runForeground(enabled: ServiceName[]): Promise<void> {
|
|
91
|
+
const logDir = path.join(getClawminiDir(), 'logs');
|
|
92
|
+
const supervisor = new Supervisor(logDir);
|
|
93
|
+
|
|
94
|
+
writeSupervisorPid(process.pid);
|
|
95
|
+
process.on('exit', () => {
|
|
96
|
+
removeSupervisorPid();
|
|
97
|
+
try {
|
|
98
|
+
fs.unlinkSync(getControlSocketPath());
|
|
99
|
+
} catch {
|
|
100
|
+
// best-effort cleanup
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const onSignal = () => {
|
|
105
|
+
void supervisor.shutdown(0);
|
|
106
|
+
};
|
|
107
|
+
process.on('SIGINT', onSignal);
|
|
108
|
+
process.on('SIGTERM', onSignal);
|
|
109
|
+
|
|
110
|
+
if (enabled.includes('daemon')) {
|
|
111
|
+
process.stderr.write('[supervisor] starting daemon...\n');
|
|
112
|
+
await supervisor.startService('daemon');
|
|
113
|
+
await supervisor.waitForDaemonSocket();
|
|
114
|
+
process.stderr.write('[supervisor] daemon ready\n');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (const name of enabled) {
|
|
118
|
+
if (name === 'daemon') continue;
|
|
119
|
+
await supervisor.startService(name);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
startSupervisorControl(supervisor);
|
|
123
|
+
|
|
124
|
+
process.stderr.write(`[supervisor] running: ${enabled.join(', ')}\n`);
|
|
125
|
+
process.stderr.write("[supervisor] press Ctrl-C to stop (or run 'clawmini down' elsewhere)\n");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isSocketLive(socketPath: string): Promise<boolean> {
|
|
129
|
+
return new Promise((resolve) => {
|
|
130
|
+
const client = net.createConnection({ path: socketPath });
|
|
131
|
+
client.on('connect', () => {
|
|
132
|
+
client.destroy();
|
|
133
|
+
resolve(true);
|
|
134
|
+
});
|
|
135
|
+
client.on('error', () => resolve(false));
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function printSupervisorLogTail(supLog: string, fromOffset: number): void {
|
|
140
|
+
if (!fs.existsSync(supLog)) return;
|
|
141
|
+
const size = fs.statSync(supLog).size;
|
|
142
|
+
if (size <= fromOffset) return;
|
|
143
|
+
const fd = fs.openSync(supLog, 'r');
|
|
144
|
+
try {
|
|
145
|
+
const len = size - fromOffset;
|
|
146
|
+
const buf = Buffer.alloc(len);
|
|
147
|
+
fs.readSync(fd, buf, 0, len, fromOffset);
|
|
148
|
+
const text = buf.toString('utf-8');
|
|
149
|
+
process.stderr.write('--- supervisor.log ---\n');
|
|
150
|
+
process.stderr.write(text);
|
|
151
|
+
if (!text.endsWith('\n')) process.stderr.write('\n');
|
|
152
|
+
} finally {
|
|
153
|
+
fs.closeSync(fd);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function runDetached(args: string[]): Promise<never> {
|
|
158
|
+
const clawDir = getClawminiDir();
|
|
159
|
+
const logDir = path.join(clawDir, 'logs');
|
|
160
|
+
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
|
|
161
|
+
const supLog = path.join(logDir, 'supervisor.log');
|
|
162
|
+
const supLogOffset = fs.existsSync(supLog) ? fs.statSync(supLog).size : 0;
|
|
163
|
+
const outFd = fs.openSync(supLog, 'a');
|
|
164
|
+
|
|
165
|
+
// Drop any stale pid file so we can detect the new child writing one.
|
|
166
|
+
removeSupervisorPid();
|
|
167
|
+
|
|
168
|
+
const childArgs = args.filter((a) => a !== '--detach' && a !== '-d');
|
|
169
|
+
const child = spawn(process.execPath, childArgs, {
|
|
170
|
+
detached: true,
|
|
171
|
+
stdio: ['ignore', outFd, outFd],
|
|
172
|
+
cwd: process.cwd(),
|
|
173
|
+
env: process.env,
|
|
174
|
+
});
|
|
175
|
+
child.unref();
|
|
176
|
+
fs.closeSync(outFd);
|
|
177
|
+
|
|
178
|
+
const pidPath = getSupervisorPidPath();
|
|
179
|
+
const socketPath = getSocketPath();
|
|
180
|
+
const STARTUP_TIMEOUT_MS = 30_000;
|
|
181
|
+
const deadline = Date.now() + STARTUP_TIMEOUT_MS;
|
|
182
|
+
while (Date.now() < deadline) {
|
|
183
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
184
|
+
console.error(
|
|
185
|
+
`clawmini serve exited during startup (code=${child.exitCode}, signal=${child.signalCode}).`
|
|
186
|
+
);
|
|
187
|
+
printSupervisorLogTail(supLog, supLogOffset);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
if (fs.existsSync(pidPath) && fs.existsSync(socketPath)) {
|
|
191
|
+
console.log(`Started clawmini supervisor in background (pid ${child.pid}).`);
|
|
192
|
+
console.log(` Logs: clawmini logs -f`);
|
|
193
|
+
console.log(` Stop: clawmini down`);
|
|
194
|
+
process.exit(0);
|
|
195
|
+
}
|
|
196
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.error(`clawmini serve did not become ready within ${STARTUP_TIMEOUT_MS / 1000}s.`);
|
|
200
|
+
printSupervisorLogTail(supLog, supLogOffset);
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export const serveCmd = new Command('serve')
|
|
205
|
+
.description('Run daemon, web UI, and configured adapters under one supervisor')
|
|
206
|
+
.option('-d, --detach', 'Run in the background; logs go to .clawmini/logs/')
|
|
207
|
+
.option('--only <services>', 'Comma-separated subset to run (daemon,web,discord,google-chat)')
|
|
208
|
+
.option('--exclude <services>', 'Comma-separated services to skip')
|
|
209
|
+
.action(async (options: ServeOptions) => {
|
|
210
|
+
const clawDir = getClawminiDir();
|
|
211
|
+
if (!fs.existsSync(clawDir)) {
|
|
212
|
+
console.error(`Not a clawmini workspace (no ${clawDir}). Run 'clawmini init' first.`);
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const existingSupervisorPid = readSupervisorPid();
|
|
217
|
+
if (existingSupervisorPid) {
|
|
218
|
+
console.error(
|
|
219
|
+
`clawmini serve is already running (pid ${existingSupervisorPid}). Run 'clawmini down' to stop it.`
|
|
220
|
+
);
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const socketPath = getSocketPath();
|
|
225
|
+
if (fs.existsSync(socketPath)) {
|
|
226
|
+
if (await isSocketLive(socketPath)) {
|
|
227
|
+
console.error(
|
|
228
|
+
"A clawmini daemon is already running (socket present). Run 'clawmini down' before 'clawmini serve'."
|
|
229
|
+
);
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
// Socket file is left over from a crashed/killed daemon — remove it
|
|
233
|
+
// so we can bind cleanly.
|
|
234
|
+
try {
|
|
235
|
+
fs.unlinkSync(socketPath);
|
|
236
|
+
process.stderr.write(`[supervisor] removed stale socket at ${socketPath}\n`);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
console.error(
|
|
239
|
+
`Failed to remove stale socket at ${socketPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
240
|
+
);
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let enabled: ServiceName[];
|
|
246
|
+
try {
|
|
247
|
+
enabled = resolveEnabledServices({
|
|
248
|
+
only: options.only,
|
|
249
|
+
exclude: options.exclude,
|
|
250
|
+
adapterConfigPresent: {
|
|
251
|
+
'adapter-discord': fs.existsSync(getDiscordConfigPath()),
|
|
252
|
+
'adapter-google-chat': fs.existsSync(getGoogleChatConfigPath()),
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
} catch (err) {
|
|
256
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (enabled.length === 0) {
|
|
261
|
+
console.error('No services selected. Check --only/--exclude.');
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (options.detach) {
|
|
266
|
+
await runDetached(process.argv.slice(1));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
await runPreStart();
|
|
272
|
+
} catch (err) {
|
|
273
|
+
console.error('Pre-start setup failed:', err instanceof Error ? err.message : String(err));
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
await runForeground(enabled);
|
|
279
|
+
} catch (err) {
|
|
280
|
+
console.error('Failed to start services:', err instanceof Error ? err.message : String(err));
|
|
281
|
+
removeSupervisorPid();
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
});
|
package/src/cli/commands/up.ts
CHANGED
|
@@ -1,15 +1,135 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { getDaemonClient } from '../client.js';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
getAgent,
|
|
5
|
+
getAgentOverlay,
|
|
6
|
+
getPoliciesPath,
|
|
7
|
+
getSocketPath,
|
|
8
|
+
listAgents,
|
|
9
|
+
refreshAgentTemplate,
|
|
10
|
+
refreshAgentSkills,
|
|
11
|
+
formatPlanActions,
|
|
12
|
+
} from '../../shared/workspace.js';
|
|
4
13
|
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { installBuiltinPolicies } from '../builtin-policies.js';
|
|
16
|
+
import { exportLiteToAllEnvironments } from '../../shared/lite.js';
|
|
17
|
+
|
|
18
|
+
// resolvePolicies only exposes built-ins when a policies file exists, so a
|
|
19
|
+
// fresh project needs an empty one for run-host etc. to be visible.
|
|
20
|
+
export function ensureDefaultPoliciesFile(): void {
|
|
21
|
+
const policiesPath = getPoliciesPath();
|
|
22
|
+
if (fs.existsSync(policiesPath)) return;
|
|
23
|
+
fs.mkdirSync(path.dirname(policiesPath), { recursive: true });
|
|
24
|
+
fs.writeFileSync(policiesPath, JSON.stringify({ policies: {} }, null, 2));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface DivergedAction {
|
|
28
|
+
action: 'skip-diverged';
|
|
29
|
+
relPath: string;
|
|
30
|
+
reason: 'edited' | 'no-recorded-sha';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function logPlanWarnings(
|
|
34
|
+
agentId: string,
|
|
35
|
+
directory: string | undefined,
|
|
36
|
+
actions: Array<{ action: string; relPath: string; reason?: string }>
|
|
37
|
+
): void {
|
|
38
|
+
const workdir = directory ?? agentId;
|
|
39
|
+
for (const rawAction of actions) {
|
|
40
|
+
if (rawAction.action !== 'skip-diverged') continue;
|
|
41
|
+
const action = rawAction as DivergedAction;
|
|
42
|
+
if (action.reason === 'edited') {
|
|
43
|
+
console.warn(
|
|
44
|
+
`./${path.join(workdir, action.relPath)} differs from template; skipping refresh. Run 'clawmini agents refresh ${agentId} --accept' to overwrite.`
|
|
45
|
+
);
|
|
46
|
+
} else if (action.reason === 'no-recorded-sha') {
|
|
47
|
+
console.warn(
|
|
48
|
+
`./${path.join(workdir, action.relPath)} has no recorded SHA; skipping. Run 'clawmini agents refresh ${agentId} --accept' to adopt the current file.`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Walk every agent and refresh its tracked files against the installed
|
|
55
|
+
// clawmini. Workdir-template refresh requires `extends`; skill refresh runs
|
|
56
|
+
// for any agent that hasn't opted out via `skillsDir: null`. Diverged files
|
|
57
|
+
// are skipped with a warning. Returns the aggregated plan actions for
|
|
58
|
+
// dry-run printing.
|
|
59
|
+
export async function refreshAllAgents(opts: { dryRun?: boolean } = {}): Promise<string[]> {
|
|
60
|
+
const output: string[] = [];
|
|
61
|
+
const agentIds = await listAgents();
|
|
62
|
+
for (const agentId of agentIds) {
|
|
63
|
+
let overlay;
|
|
64
|
+
try {
|
|
65
|
+
overlay = await getAgentOverlay(agentId);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.warn(
|
|
68
|
+
`Skipping refresh for agent '${agentId}': ${err instanceof Error ? err.message : String(err)}`
|
|
69
|
+
);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (!overlay) continue;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
if (overlay.extends) {
|
|
76
|
+
const plan = await refreshAgentTemplate(
|
|
77
|
+
agentId,
|
|
78
|
+
overlay,
|
|
79
|
+
process.cwd(),
|
|
80
|
+
opts.dryRun ? { dryRun: true } : {}
|
|
81
|
+
);
|
|
82
|
+
if (plan) {
|
|
83
|
+
logPlanWarnings(agentId, overlay.directory, plan.actions);
|
|
84
|
+
output.push(...formatPlanActions(plan, { agentId }));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const resolved = await getAgent(agentId);
|
|
89
|
+
if (resolved) {
|
|
90
|
+
const skillsPlan = await refreshAgentSkills(
|
|
91
|
+
agentId,
|
|
92
|
+
resolved,
|
|
93
|
+
process.cwd(),
|
|
94
|
+
opts.dryRun ? { dryRun: true } : {}
|
|
95
|
+
);
|
|
96
|
+
if (skillsPlan) {
|
|
97
|
+
logPlanWarnings(agentId, overlay.directory, skillsPlan.actions);
|
|
98
|
+
output.push(...formatPlanActions(skillsPlan, { agentId }));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.warn(
|
|
103
|
+
`Failed to refresh agent '${agentId}': ${err instanceof Error ? err.message : String(err)}`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return output;
|
|
108
|
+
}
|
|
5
109
|
|
|
6
110
|
export const upCmd = new Command('up')
|
|
7
111
|
.description('Start the local clawmini daemon server')
|
|
8
|
-
.
|
|
112
|
+
.option('--dry-run', 'Print the per-file refresh plan without writing anything')
|
|
113
|
+
.action(async (options: { dryRun?: boolean }) => {
|
|
9
114
|
try {
|
|
10
115
|
const socketPath = getSocketPath();
|
|
11
116
|
const wasRunning = fs.existsSync(socketPath);
|
|
12
117
|
|
|
118
|
+
if (options.dryRun) {
|
|
119
|
+
const lines = await refreshAllAgents({ dryRun: true });
|
|
120
|
+
if (lines.length === 0) {
|
|
121
|
+
console.log('Dry run: no agents to refresh.');
|
|
122
|
+
} else {
|
|
123
|
+
for (const line of lines) console.log(line);
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
await installBuiltinPolicies();
|
|
129
|
+
ensureDefaultPoliciesFile();
|
|
130
|
+
await exportLiteToAllEnvironments();
|
|
131
|
+
await refreshAllAgents();
|
|
132
|
+
|
|
13
133
|
const client = await getDaemonClient({ autoStart: true });
|
|
14
134
|
// Perform a ping to ensure the server is responding
|
|
15
135
|
await client.ping.query();
|
|
@@ -3,6 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import {
|
|
4
4
|
listAgents,
|
|
5
5
|
getAgent,
|
|
6
|
+
getAgentOverlay,
|
|
6
7
|
writeAgentSettings,
|
|
7
8
|
deleteAgent,
|
|
8
9
|
isValidAgentId,
|
|
@@ -44,7 +45,7 @@ export async function handleApiAgents(
|
|
|
44
45
|
|
|
45
46
|
const body = await parseJsonBody(req, schema);
|
|
46
47
|
|
|
47
|
-
const existing = await
|
|
48
|
+
const existing = await getAgentOverlay(body.id);
|
|
48
49
|
if (existing) {
|
|
49
50
|
sendJsonResponse(res, 409, { error: 'Agent already exists' });
|
|
50
51
|
return true;
|
|
@@ -106,7 +107,7 @@ export async function handleApiAgents(
|
|
|
106
107
|
|
|
107
108
|
const body = await parseJsonBody(req, schema);
|
|
108
109
|
|
|
109
|
-
const agent = (await
|
|
110
|
+
const agent = (await getAgentOverlay(agentId)) || {};
|
|
110
111
|
if (body.directory !== undefined) agent.directory = body.directory;
|
|
111
112
|
if (body.env !== undefined) agent.env = body.env;
|
|
112
113
|
if (body.commands !== undefined) agent.commands = body.commands;
|
package/src/cli/index.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { chatsCmd } from './commands/chats.js';
|
|
|
6
6
|
import { agentsCmd } from './commands/agents.js';
|
|
7
7
|
import { downCmd } from './commands/down.js';
|
|
8
8
|
import { upCmd } from './commands/up.js';
|
|
9
|
+
import { serveCmd } from './commands/serve.js';
|
|
10
|
+
import { logsCmd } from './commands/logs.js';
|
|
9
11
|
import { webCmd } from './commands/web.js';
|
|
10
12
|
import { jobsCmd } from './commands/jobs.js';
|
|
11
13
|
import { exportLiteCmd } from './commands/export-lite.js';
|
|
@@ -25,6 +27,8 @@ program.addCommand(environmentsCmd);
|
|
|
25
27
|
program.addCommand(skillsCmd);
|
|
26
28
|
program.addCommand(downCmd);
|
|
27
29
|
program.addCommand(upCmd);
|
|
30
|
+
program.addCommand(serveCmd);
|
|
31
|
+
program.addCommand(logsCmd);
|
|
28
32
|
program.addCommand(webCmd);
|
|
29
33
|
program.addCommand(jobsCmd);
|
|
30
34
|
program.addCommand(exportLiteCmd);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { detectInstall } from './install-detection.js';
|
|
7
|
+
|
|
8
|
+
describe('detectInstall', () => {
|
|
9
|
+
let tmp: string;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'clawmini-install-'));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('reports isNpmGlobal=false for an entry path that lives outside npm root', () => {
|
|
20
|
+
// Use a real path under tmp so realpath resolves.
|
|
21
|
+
const entryDir = path.join(tmp, 'devcheckout', 'dist');
|
|
22
|
+
fs.mkdirSync(entryDir, { recursive: true });
|
|
23
|
+
const entry = path.join(entryDir, 'cli.mjs');
|
|
24
|
+
fs.writeFileSync(entry, '');
|
|
25
|
+
const info = detectInstall(entry);
|
|
26
|
+
expect(info.isNpmGlobal).toBe(false);
|
|
27
|
+
// entryRealPath is whatever realpath returns; on macOS /tmp is itself a
|
|
28
|
+
// symlink to /private/tmp, so just check it ends with our suffix.
|
|
29
|
+
expect(info.entryRealPath.endsWith(path.join('devcheckout', 'dist', 'cli.mjs'))).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('detects npm-link as NOT npm-global (the symlink target lives outside npm root)', () => {
|
|
33
|
+
// Real install layout: <npmRoot>/clawmini/dist/cli.mjs as a SYMLINK to
|
|
34
|
+
// <devCheckout>/dist/cli.mjs. After realpath, the entry path resolves to
|
|
35
|
+
// the dev checkout, so isNpmGlobal must be false.
|
|
36
|
+
const npmRoot = path.join(tmp, 'npm-root');
|
|
37
|
+
const devRoot = path.join(tmp, 'dev-checkout');
|
|
38
|
+
fs.mkdirSync(npmRoot, { recursive: true });
|
|
39
|
+
fs.mkdirSync(path.join(devRoot, 'dist'), { recursive: true });
|
|
40
|
+
const realEntry = path.join(devRoot, 'dist', 'cli.mjs');
|
|
41
|
+
fs.writeFileSync(realEntry, '');
|
|
42
|
+
|
|
43
|
+
// Simulate `npm link`: a symlink at npm root → the dev checkout.
|
|
44
|
+
fs.symlinkSync(devRoot, path.join(npmRoot, 'clawmini'));
|
|
45
|
+
|
|
46
|
+
const linkedEntry = path.join(npmRoot, 'clawmini', 'dist', 'cli.mjs');
|
|
47
|
+
const info = detectInstall(linkedEntry);
|
|
48
|
+
// The realpath of `linkedEntry` lands inside devRoot, not npmRoot.
|
|
49
|
+
expect(info.entryRealPath).toBe(fs.realpathSync(realEntry));
|
|
50
|
+
// We have no way to mock `npm root -g` in this test, so the host's real
|
|
51
|
+
// npm root is consulted. The test asserts a *property* of the realpath,
|
|
52
|
+
// not the boolean (which depends on the host). So:
|
|
53
|
+
expect(info.entryRealPath.startsWith(fs.realpathSync(devRoot))).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('detects a real npm install as npm-global when entry is under npm root', () => {
|
|
57
|
+
// We can't mock execFileSync without restructuring — instead, exercise
|
|
58
|
+
// the path-prefix logic by giving an entry under our fake "npm root" and
|
|
59
|
+
// checking via realpath comparison directly. This documents the
|
|
60
|
+
// realpath-based check; the integration boolean is asserted in the
|
|
61
|
+
// detect path against the real `npm root -g`.
|
|
62
|
+
const npmRoot = path.join(tmp, 'npm-root');
|
|
63
|
+
const installedRoot = path.join(npmRoot, 'clawmini');
|
|
64
|
+
fs.mkdirSync(path.join(installedRoot, 'dist'), { recursive: true });
|
|
65
|
+
const entry = path.join(installedRoot, 'dist', 'cli.mjs');
|
|
66
|
+
fs.writeFileSync(entry, '');
|
|
67
|
+
|
|
68
|
+
const realEntry = fs.realpathSync(entry);
|
|
69
|
+
const realNpmRoot = fs.realpathSync(npmRoot);
|
|
70
|
+
expect(realEntry.startsWith(realNpmRoot + path.sep)).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export interface InstallInfo {
|
|
6
|
+
/** True when the running clawmini binary lives under `npm root -g` after realpath. */
|
|
7
|
+
isNpmGlobal: boolean;
|
|
8
|
+
/** The realpath of the running entry point (process.argv[1] resolved). */
|
|
9
|
+
entryRealPath: string;
|
|
10
|
+
/** The realpath of `npm root -g`, when available. */
|
|
11
|
+
npmRootRealPath: string | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function realpathOrNull(p: string): string | null {
|
|
15
|
+
try {
|
|
16
|
+
return fs.realpathSync(p);
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getNpmRootGlobal(): string | null {
|
|
23
|
+
try {
|
|
24
|
+
const out = execFileSync('npm', ['root', '-g'], {
|
|
25
|
+
encoding: 'utf-8',
|
|
26
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
27
|
+
});
|
|
28
|
+
return out.trim() || null;
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function detectInstall(entryPath: string = process.argv[1] ?? ''): InstallInfo {
|
|
35
|
+
const entryRealPath = realpathOrNull(entryPath) ?? entryPath;
|
|
36
|
+
const npmRoot = getNpmRootGlobal();
|
|
37
|
+
const npmRootRealPath = npmRoot ? (realpathOrNull(npmRoot) ?? npmRoot) : null;
|
|
38
|
+
|
|
39
|
+
let isNpmGlobal = false;
|
|
40
|
+
if (npmRootRealPath) {
|
|
41
|
+
const rootWithSep = npmRootRealPath.endsWith(path.sep)
|
|
42
|
+
? npmRootRealPath
|
|
43
|
+
: npmRootRealPath + path.sep;
|
|
44
|
+
isNpmGlobal = entryRealPath === npmRootRealPath || entryRealPath.startsWith(rootWithSep);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { isNpmGlobal, entryRealPath, npmRootRealPath };
|
|
48
|
+
}
|