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
package/dist/daemon/index.mjs
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
import {
|
|
1
|
+
import { A as updateChatSettings, C as readSettings, E as resolveAgentWorkDir, F as pathIsInsideDir, I as CronJobSchema, L as SettingsSchema, N as writeChatSettings, O as substituteLayeredEnvDir, S as readPoliciesForPath, b as readChatSettings, c as getAgent, d as getEnvironmentPath, f as getEnvironmentSearchDirs, g as getWorkspaceRoot, h as getSocketPath, j as writeAgentSessionSettings, k as updateAgentOverlay, l as getAgentOverlay, m as getSettingsPath, s as getActiveEnvironmentInfo, u as getClawminiDir, v as listAgents, x as readEnvironment, y as readAgentSessionSettings } from "../workspace-oWmVh5mi.mjs";
|
|
2
|
+
import { A as getMessages$1, D as findLastMessage, M as listChats, T as createChat, a as DAEMON_EVENT_CHAT_STREAM, b as exportLiteToEnvironment, c as daemonEvents, d as emitTyping, i as appendMessage, k as getDefaultChatId, l as emitTurnEnded, m as detectInstall, o as DAEMON_EVENT_MESSAGE_APPENDED, p as sendControlRequest, r as drainPendingReplies, s as DAEMON_EVENT_TYPING, t as isAcceptableVersion, u as emitTurnStarted } from "../supervisor-actions-CiW56eLi.mjs";
|
|
3
3
|
import fs, { constants } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { execSync, spawn } from "node:child_process";
|
|
6
|
-
import
|
|
7
|
+
import crypto$1, { randomBytes, randomUUID } from "node:crypto";
|
|
8
|
+
import fsPromises from "node:fs/promises";
|
|
7
9
|
import { z } from "zod";
|
|
8
10
|
import http from "node:http";
|
|
9
11
|
import net from "node:net";
|
|
12
|
+
import { on } from "node:events";
|
|
10
13
|
import { createHTTPHandler } from "@trpc/server/adapters/standalone";
|
|
11
14
|
import { TRPCError, initTRPC } from "@trpc/server";
|
|
12
15
|
import schedule from "node-schedule";
|
|
13
|
-
import
|
|
14
|
-
import fs$2 from "fs/promises";
|
|
16
|
+
import fs$1 from "fs/promises";
|
|
15
17
|
import path$1 from "path";
|
|
16
18
|
import { randomInt } from "crypto";
|
|
17
|
-
import { EventEmitter, on } from "node:events";
|
|
18
19
|
|
|
19
20
|
//#region src/daemon/api/trpc.ts
|
|
20
21
|
const t = initTRPC.context().create();
|
|
@@ -67,10 +68,10 @@ async function slashCommand(state) {
|
|
|
67
68
|
if (!pathIsInsideDir(path.resolve(commandsDir, commandName), commandsDir)) continue;
|
|
68
69
|
let content;
|
|
69
70
|
try {
|
|
70
|
-
content = await
|
|
71
|
+
content = await fsPromises.readFile(targetPathMd, "utf8");
|
|
71
72
|
} catch {
|
|
72
73
|
try {
|
|
73
|
-
content = await
|
|
74
|
+
content = await fsPromises.readFile(targetPathTxt, "utf8");
|
|
74
75
|
} catch {
|
|
75
76
|
continue;
|
|
76
77
|
}
|
|
@@ -124,7 +125,9 @@ const PolicyRequestSchema = z.object({
|
|
|
124
125
|
createdAt: z.number(),
|
|
125
126
|
rejectionReason: z.string().optional(),
|
|
126
127
|
chatId: z.string(),
|
|
127
|
-
agentId: z.string()
|
|
128
|
+
agentId: z.string(),
|
|
129
|
+
subagentId: z.string().optional(),
|
|
130
|
+
cwd: z.string().optional()
|
|
128
131
|
});
|
|
129
132
|
function isENOENT(err) {
|
|
130
133
|
return Boolean(err && typeof err === "object" && "code" in err && err.code === "ENOENT");
|
|
@@ -135,7 +138,7 @@ var RequestStore = class {
|
|
|
135
138
|
this.baseDir = path$1.join(getClawminiDir(startDir), "tmp", "requests");
|
|
136
139
|
}
|
|
137
140
|
async init() {
|
|
138
|
-
await fs$
|
|
141
|
+
await fs$1.mkdir(this.baseDir, { recursive: true });
|
|
139
142
|
}
|
|
140
143
|
getFilePath(id) {
|
|
141
144
|
return path$1.join(this.baseDir, `${id}.json`);
|
|
@@ -145,13 +148,40 @@ var RequestStore = class {
|
|
|
145
148
|
const normalizedId = normalizePolicyId(request.id);
|
|
146
149
|
request.id = normalizedId;
|
|
147
150
|
const filePath = this.getFilePath(normalizedId);
|
|
148
|
-
await fs$
|
|
151
|
+
await fs$1.writeFile(filePath, JSON.stringify(request, null, 2), "utf8");
|
|
152
|
+
}
|
|
153
|
+
async delete(id) {
|
|
154
|
+
const normalizedId = normalizePolicyId(id);
|
|
155
|
+
const filePath = this.getFilePath(normalizedId);
|
|
156
|
+
try {
|
|
157
|
+
await fs$1.unlink(filePath);
|
|
158
|
+
} catch (err) {
|
|
159
|
+
if (!isENOENT(err)) throw err;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async cleanupCompleted() {
|
|
163
|
+
let removed = 0;
|
|
164
|
+
try {
|
|
165
|
+
const files = await fs$1.readdir(this.baseDir);
|
|
166
|
+
for (const file of files) {
|
|
167
|
+
if (!file.endsWith(".json")) continue;
|
|
168
|
+
const id = path$1.basename(file, ".json");
|
|
169
|
+
const req = await this.load(id);
|
|
170
|
+
if (req && req.state !== "Pending") {
|
|
171
|
+
await this.delete(id);
|
|
172
|
+
removed++;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
} catch (err) {
|
|
176
|
+
if (!isENOENT(err)) throw err;
|
|
177
|
+
}
|
|
178
|
+
return removed;
|
|
149
179
|
}
|
|
150
180
|
async load(id) {
|
|
151
181
|
const normalizedId = normalizePolicyId(id);
|
|
152
182
|
const filePath = this.getFilePath(normalizedId);
|
|
153
183
|
try {
|
|
154
|
-
const data = await fs$
|
|
184
|
+
const data = await fs$1.readFile(filePath, "utf8");
|
|
155
185
|
return PolicyRequestSchema.parse(JSON.parse(data));
|
|
156
186
|
} catch (err) {
|
|
157
187
|
if (isENOENT(err)) return null;
|
|
@@ -164,7 +194,7 @@ var RequestStore = class {
|
|
|
164
194
|
await this.init();
|
|
165
195
|
const requests = [];
|
|
166
196
|
try {
|
|
167
|
-
const files = await fs$
|
|
197
|
+
const files = await fs$1.readdir(this.baseDir);
|
|
168
198
|
for (const file of files) {
|
|
169
199
|
if (!file.endsWith(".json")) continue;
|
|
170
200
|
const id = path$1.basename(file, ".json");
|
|
@@ -190,10 +220,56 @@ function normalizePolicyId(id) {
|
|
|
190
220
|
//#endregion
|
|
191
221
|
//#region src/daemon/policy-utils.ts
|
|
192
222
|
const MAX_SNAPSHOT_SIZE = 5 * 1024 * 1024;
|
|
223
|
+
const MAX_INLINE_OUTPUT_LENGTH = 500;
|
|
224
|
+
/**
|
|
225
|
+
* Strips the sandbox `baseDir` from `sandboxCwd` and resolves the remainder
|
|
226
|
+
* against `hostTargetDir` (the host dir that mirrors baseDir inside the
|
|
227
|
+
* sandbox). Pure translation — no security validation; callers must validate
|
|
228
|
+
* the result with `assertPathInsideDir`.
|
|
229
|
+
*/
|
|
230
|
+
function translateSandboxPath(sandboxCwd, baseDir, hostTargetDir) {
|
|
231
|
+
let relativePath = sandboxCwd;
|
|
232
|
+
if (sandboxCwd.startsWith(baseDir)) relativePath = sandboxCwd.slice(baseDir.length);
|
|
233
|
+
if (relativePath.startsWith("/") || relativePath.startsWith("\\")) relativePath = relativePath.slice(1);
|
|
234
|
+
return path.resolve(hostTargetDir, relativePath);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Throws if `cwd` (after symlink resolution) is not inside `boundaryDir`.
|
|
238
|
+
*
|
|
239
|
+
* Security note (TOCTOU): There is an inherent race between validating the
|
|
240
|
+
* resolved path here and the moment `spawn` uses it as cwd. A symlink created
|
|
241
|
+
* on the host filesystem in that window could redirect execution outside
|
|
242
|
+
* boundaryDir. We accept this because the sandboxed agent cannot modify the
|
|
243
|
+
* host filesystem — only a local user or process with host-level access could
|
|
244
|
+
* exploit the gap, and that is outside our threat model.
|
|
245
|
+
*/
|
|
246
|
+
function assertPathInsideDir(cwd, boundaryDir) {
|
|
247
|
+
if (!pathIsInsideDir(tryRealpath(cwd), tryRealpath(boundaryDir), { allowSameDir: true })) throw new Error(`Security Error: Path resolves outside the allowed directory: ${cwd}`);
|
|
248
|
+
}
|
|
249
|
+
function tryRealpath(p) {
|
|
250
|
+
const resolved = path.resolve(p);
|
|
251
|
+
try {
|
|
252
|
+
return fs.realpathSync(resolved);
|
|
253
|
+
} catch (err) {
|
|
254
|
+
if (!(err instanceof Error && "code" in err && err.code === "ENOENT")) throw err;
|
|
255
|
+
}
|
|
256
|
+
const parent = path.dirname(resolved);
|
|
257
|
+
if (parent === resolved) return resolved;
|
|
258
|
+
return path.join(tryRealpath(parent), path.basename(resolved));
|
|
259
|
+
}
|
|
260
|
+
async function resolveRequestCwd(requestCwd, agentId, workspaceRoot) {
|
|
261
|
+
if (!requestCwd) return workspaceRoot;
|
|
262
|
+
const agentDir = await resolveAgentDir(agentId, workspaceRoot);
|
|
263
|
+
const envInfo = await getActiveEnvironmentInfo(agentDir, workspaceRoot);
|
|
264
|
+
const envConfig = envInfo ? await readEnvironment(envInfo.name, workspaceRoot) : null;
|
|
265
|
+
const hostCwd = envInfo && envConfig?.baseDir ? translateSandboxPath(requestCwd, envConfig.baseDir, envInfo.targetPath) : path.resolve(agentDir, requestCwd);
|
|
266
|
+
assertPathInsideDir(hostCwd, agentDir);
|
|
267
|
+
return hostCwd;
|
|
268
|
+
}
|
|
193
269
|
async function createSnapshot(requestedPath, agentDir, snapshotDir) {
|
|
194
270
|
let realAgentDir;
|
|
195
271
|
try {
|
|
196
|
-
realAgentDir = await
|
|
272
|
+
realAgentDir = await fsPromises.realpath(agentDir);
|
|
197
273
|
} catch (err) {
|
|
198
274
|
throw new Error(`Agent directory not found or cannot be resolved: ${agentDir}`, { cause: err });
|
|
199
275
|
}
|
|
@@ -201,7 +277,7 @@ async function createSnapshot(requestedPath, agentDir, snapshotDir) {
|
|
|
201
277
|
if (!pathIsInsideDir(resolvedRequestedPath, realAgentDir, { allowSameDir: true })) throw new Error(`Security Error: Path resolves outside the allowed agent directory: ${resolvedRequestedPath}`);
|
|
202
278
|
let stat;
|
|
203
279
|
try {
|
|
204
|
-
stat = await
|
|
280
|
+
stat = await fsPromises.lstat(resolvedRequestedPath);
|
|
205
281
|
} catch (err) {
|
|
206
282
|
throw new Error(`File not found or cannot be accessed: ${requestedPath}`, { cause: err });
|
|
207
283
|
}
|
|
@@ -210,13 +286,13 @@ async function createSnapshot(requestedPath, agentDir, snapshotDir) {
|
|
|
210
286
|
if (stat.size > MAX_SNAPSHOT_SIZE) throw new Error(`File exceeds maximum snapshot size of 5MB: ${requestedPath}`);
|
|
211
287
|
const ext = path.extname(resolvedRequestedPath);
|
|
212
288
|
const base = path.basename(resolvedRequestedPath, ext);
|
|
213
|
-
await
|
|
289
|
+
await fsPromises.mkdir(snapshotDir, { recursive: true });
|
|
214
290
|
let snapshotPath;
|
|
215
291
|
while (true) {
|
|
216
292
|
const snapshotFileName = `${base}_${randomBytes(8).toString("hex")}${ext}`;
|
|
217
293
|
snapshotPath = path.join(snapshotDir, snapshotFileName);
|
|
218
294
|
try {
|
|
219
|
-
await
|
|
295
|
+
await fsPromises.copyFile(resolvedRequestedPath, snapshotPath, constants.COPYFILE_EXCL);
|
|
220
296
|
break;
|
|
221
297
|
} catch (err) {
|
|
222
298
|
if (err instanceof Error && "code" in err && err.code === "EEXIST") continue;
|
|
@@ -276,6 +352,27 @@ async function executeRequest(request, policy, cwd) {
|
|
|
276
352
|
commandStr: `${policy.command} ${interpolatedArgs.join(" ")}`
|
|
277
353
|
};
|
|
278
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* Saves large stdout/stderr to files in the agent's tmp/ directory and returns
|
|
357
|
+
* placeholder strings pointing to those files. Small outputs are returned as-is.
|
|
358
|
+
*/
|
|
359
|
+
async function truncateLargeOutput(stdout, stderr, requestId, agentId) {
|
|
360
|
+
const agentDir = await resolveAgentDir(agentId, getWorkspaceRoot());
|
|
361
|
+
const tmpDir = path.join(agentDir, "tmp");
|
|
362
|
+
if (stdout.length >= MAX_INLINE_OUTPUT_LENGTH || stderr.length >= MAX_INLINE_OUTPUT_LENGTH) await fsPromises.mkdir(tmpDir, { recursive: true });
|
|
363
|
+
if (stdout.length >= MAX_INLINE_OUTPUT_LENGTH) {
|
|
364
|
+
await fsPromises.writeFile(path.join(tmpDir, `stdout-${requestId}.txt`), stdout, "utf-8");
|
|
365
|
+
stdout = `stdout is ${stdout.length} characters, saved to ./tmp/stdout-${requestId}.txt\n`;
|
|
366
|
+
}
|
|
367
|
+
if (stderr.length >= MAX_INLINE_OUTPUT_LENGTH) {
|
|
368
|
+
await fsPromises.writeFile(path.join(tmpDir, `stderr-${requestId}.txt`), stderr, "utf-8");
|
|
369
|
+
stderr = `stderr is ${stderr.length} characters, saved to ./tmp/stderr-${requestId}.txt\n`;
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
stdout,
|
|
373
|
+
stderr
|
|
374
|
+
};
|
|
375
|
+
}
|
|
279
376
|
async function generateRequestPreview(request) {
|
|
280
377
|
let previewContent = `Sandbox Policy Request: ${request.commandName}\n`;
|
|
281
378
|
previewContent += `ID: ${request.id}\n`;
|
|
@@ -283,7 +380,7 @@ async function generateRequestPreview(request) {
|
|
|
283
380
|
for (const [name, snapPath] of Object.entries(request.fileMappings)) {
|
|
284
381
|
previewContent += `File [${name}]:\n`;
|
|
285
382
|
try {
|
|
286
|
-
let content = await
|
|
383
|
+
let content = await fsPromises.readFile(snapPath, "utf8");
|
|
287
384
|
if (content.length > 500) content = content.substring(0, 500) + "\n... (truncated)\n";
|
|
288
385
|
previewContent += content;
|
|
289
386
|
} catch (e) {
|
|
@@ -294,30 +391,13 @@ async function generateRequestPreview(request) {
|
|
|
294
391
|
return previewContent;
|
|
295
392
|
}
|
|
296
393
|
|
|
297
|
-
//#endregion
|
|
298
|
-
//#region src/daemon/events.ts
|
|
299
|
-
const daemonEvents = new EventEmitter();
|
|
300
|
-
const DAEMON_EVENT_MESSAGE_APPENDED = "message-appended";
|
|
301
|
-
const DAEMON_EVENT_TYPING = "typing";
|
|
302
|
-
function emitMessageAppended(chatId, message) {
|
|
303
|
-
daemonEvents.emit(DAEMON_EVENT_MESSAGE_APPENDED, {
|
|
304
|
-
chatId,
|
|
305
|
-
message
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
function emitTyping(chatId) {
|
|
309
|
-
daemonEvents.emit(DAEMON_EVENT_TYPING, { chatId });
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
//#endregion
|
|
313
|
-
//#region src/daemon/chats.ts
|
|
314
|
-
async function appendMessage(id, message, startDir = process.cwd()) {
|
|
315
|
-
await appendMessage$1(id, message, startDir);
|
|
316
|
-
emitMessageAppended(id, message);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
394
|
//#endregion
|
|
320
395
|
//#region src/daemon/routers/slash-policies.ts
|
|
396
|
+
async function resolveTargetSessionId(chatId, req) {
|
|
397
|
+
const chatSettings = await readChatSettings(chatId);
|
|
398
|
+
if (req.subagentId) return chatSettings?.subagents?.[req.subagentId]?.sessionId ?? "default";
|
|
399
|
+
return chatSettings?.sessions?.[req.agentId] ?? "default";
|
|
400
|
+
}
|
|
321
401
|
async function loadAndValidateRequest(id, state) {
|
|
322
402
|
const store = new RequestStore(getWorkspaceRoot());
|
|
323
403
|
const req = await store.load(id);
|
|
@@ -360,37 +440,42 @@ async function slashPolicies(state) {
|
|
|
360
440
|
const { req, store, error } = await loadAndValidateRequest(id, state);
|
|
361
441
|
if (error) return error;
|
|
362
442
|
if (!req || !store) return state;
|
|
363
|
-
const
|
|
443
|
+
const workspaceRoot = getWorkspaceRoot();
|
|
444
|
+
const policy = (await readPoliciesForPath(await resolveAgentDir(req.agentId, workspaceRoot), workspaceRoot))?.policies?.[req.commandName];
|
|
364
445
|
if (!policy) return {
|
|
365
446
|
...state,
|
|
366
447
|
message: "",
|
|
367
448
|
reply: `Policy not found: ${req.commandName}`
|
|
368
449
|
};
|
|
369
|
-
req.state
|
|
370
|
-
const {
|
|
371
|
-
req.
|
|
372
|
-
|
|
373
|
-
stderr,
|
|
374
|
-
exitCode
|
|
375
|
-
};
|
|
376
|
-
await store.save(req);
|
|
450
|
+
const result = await executeRequest(req, policy, await resolveRequestCwd(req.cwd, state.agentId, workspaceRoot));
|
|
451
|
+
const { exitCode } = result;
|
|
452
|
+
const { stdout, stderr } = await truncateLargeOutput(result.stdout, result.stderr, req.id, state.agentId);
|
|
453
|
+
await store.delete(req.id);
|
|
377
454
|
const agentMessage = `Request ${id} approved.\n\n${wrapInHtml("stdout", stdout)}\n\n${wrapInHtml("stderr", stderr)}\n\nExit Code: ${exitCode}`;
|
|
378
|
-
const
|
|
455
|
+
const targetSessionId = await resolveTargetSessionId(state.chatId, req);
|
|
456
|
+
const userNotificationMsg = {
|
|
379
457
|
id: randomUUID(),
|
|
380
458
|
messageId: state.messageId,
|
|
381
459
|
role: "system",
|
|
382
460
|
event: "policy_approved",
|
|
383
|
-
displayRole: "
|
|
384
|
-
content:
|
|
461
|
+
displayRole: "agent",
|
|
462
|
+
content: `Request ${id} (\`${req.commandName}\`) approved.`,
|
|
385
463
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
386
|
-
|
|
464
|
+
sessionId: state.sessionId
|
|
387
465
|
};
|
|
388
|
-
await appendMessage(state.chatId,
|
|
466
|
+
await appendMessage(state.chatId, userNotificationMsg);
|
|
467
|
+
await executeDirectMessage(state.chatId, {
|
|
468
|
+
messageId: randomUUID(),
|
|
469
|
+
message: agentMessage,
|
|
470
|
+
chatId: state.chatId,
|
|
471
|
+
agentId: req.agentId,
|
|
472
|
+
sessionId: targetSessionId,
|
|
473
|
+
...req.subagentId ? { subagentId: req.subagentId } : {},
|
|
474
|
+
env: state.env || {}
|
|
475
|
+
}, void 0, getWorkspaceRoot(), true, agentMessage, req.subagentId, "policy_approved", "user");
|
|
389
476
|
return {
|
|
390
477
|
...state,
|
|
391
|
-
message:
|
|
392
|
-
reply: `Approved request, running ${req.commandName}`,
|
|
393
|
-
...req.subagentId ? { subagentId: req.subagentId } : {}
|
|
478
|
+
message: ""
|
|
394
479
|
};
|
|
395
480
|
}
|
|
396
481
|
const rejectMatch = message.match(/^\/reject\s+([^\s]+)(?:\s+(.*))?/);
|
|
@@ -401,36 +486,32 @@ async function slashPolicies(state) {
|
|
|
401
486
|
const { req, store, error } = await loadAndValidateRequest(id, state);
|
|
402
487
|
if (error) return error;
|
|
403
488
|
if (!req || !store) return state;
|
|
404
|
-
req.
|
|
405
|
-
req.rejectionReason = reason;
|
|
406
|
-
await store.save(req);
|
|
489
|
+
await store.delete(req.id);
|
|
407
490
|
const agentMessage = `Request ${id} rejected. Reason: ${reason}`;
|
|
408
|
-
const
|
|
409
|
-
id: randomUUID(),
|
|
410
|
-
messageId: state.messageId,
|
|
411
|
-
role: "system",
|
|
412
|
-
event: "policy_rejected",
|
|
413
|
-
displayRole: "user",
|
|
414
|
-
content: agentMessage,
|
|
415
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
416
|
-
...req.subagentId ? { subagentId: req.subagentId } : {}
|
|
417
|
-
};
|
|
491
|
+
const targetSessionId = await resolveTargetSessionId(state.chatId, req);
|
|
418
492
|
const userNotificationMsg = {
|
|
419
493
|
id: randomUUID(),
|
|
420
494
|
messageId: state.messageId,
|
|
421
495
|
role: "system",
|
|
422
496
|
event: "policy_rejected",
|
|
423
497
|
displayRole: "agent",
|
|
424
|
-
content:
|
|
498
|
+
content: `Request ${id} (\`${req.commandName}\`) rejected. Reason: ${reason}`,
|
|
425
499
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
426
|
-
|
|
500
|
+
sessionId: state.sessionId
|
|
427
501
|
};
|
|
428
|
-
await appendMessage(state.chatId, logMsg);
|
|
429
502
|
await appendMessage(state.chatId, userNotificationMsg);
|
|
503
|
+
await executeDirectMessage(state.chatId, {
|
|
504
|
+
messageId: randomUUID(),
|
|
505
|
+
message: agentMessage,
|
|
506
|
+
chatId: state.chatId,
|
|
507
|
+
agentId: req.agentId,
|
|
508
|
+
sessionId: targetSessionId,
|
|
509
|
+
...req.subagentId ? { subagentId: req.subagentId } : {},
|
|
510
|
+
env: state.env || {}
|
|
511
|
+
}, void 0, getWorkspaceRoot(), true, agentMessage, req.subagentId, "policy_rejected", "user");
|
|
430
512
|
return {
|
|
431
513
|
...state,
|
|
432
|
-
message:
|
|
433
|
-
...req.subagentId ? { subagentId: req.subagentId } : {}
|
|
514
|
+
message: ""
|
|
434
515
|
};
|
|
435
516
|
}
|
|
436
517
|
return state;
|
|
@@ -440,6 +521,247 @@ function wrapInHtml(tag, text) {
|
|
|
440
521
|
return `<${tag}>\n${text.trim()}\n</${tag}>`;
|
|
441
522
|
}
|
|
442
523
|
|
|
524
|
+
//#endregion
|
|
525
|
+
//#region src/daemon/routers/slash-model.ts
|
|
526
|
+
const RESERVED_SHORTHANDS = new Set([
|
|
527
|
+
"help",
|
|
528
|
+
"add",
|
|
529
|
+
"remove",
|
|
530
|
+
"rm"
|
|
531
|
+
]);
|
|
532
|
+
function stop$3(state, reply) {
|
|
533
|
+
return {
|
|
534
|
+
...state,
|
|
535
|
+
message: "",
|
|
536
|
+
reply,
|
|
537
|
+
action: "stop"
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
function formatHelp() {
|
|
541
|
+
return [
|
|
542
|
+
"Usage:",
|
|
543
|
+
"- /model — List current model and shorthands.",
|
|
544
|
+
"- /model <name> — Set MODEL (resolves shorthand if defined).",
|
|
545
|
+
"- /model add <shorthand> <full-name> — Add or replace a shorthand.",
|
|
546
|
+
"- /model remove <shorthand> — Remove a shorthand (alias: rm).",
|
|
547
|
+
"- /model help — Show this help."
|
|
548
|
+
].join("\n");
|
|
549
|
+
}
|
|
550
|
+
function formatList(agent) {
|
|
551
|
+
const current = agent?.env?.MODEL ?? "(unset)";
|
|
552
|
+
const shorthands = agent?.modelShorthands ?? {};
|
|
553
|
+
const entries = Object.entries(shorthands);
|
|
554
|
+
const lines = [`Current model: ${current}`];
|
|
555
|
+
if (entries.length === 0) lines.push("No shorthands defined. Add one with /model add <shorthand> <full-name>.");
|
|
556
|
+
else {
|
|
557
|
+
lines.push("Shorthands:");
|
|
558
|
+
for (const [short, full] of entries.sort(([a], [b]) => a.localeCompare(b))) lines.push(`- ${short} -> ${full}`);
|
|
559
|
+
}
|
|
560
|
+
return lines.join("\n");
|
|
561
|
+
}
|
|
562
|
+
function looksLikeShorthand(name) {
|
|
563
|
+
return name.length <= 16 && !/[-./:]/.test(name);
|
|
564
|
+
}
|
|
565
|
+
async function setModel(agentId, fullModel, workspaceRoot) {
|
|
566
|
+
await updateAgentOverlay(agentId, (overlay) => {
|
|
567
|
+
const nextEnv = {
|
|
568
|
+
...overlay.env ?? {},
|
|
569
|
+
MODEL: fullModel
|
|
570
|
+
};
|
|
571
|
+
return {
|
|
572
|
+
...overlay,
|
|
573
|
+
env: nextEnv
|
|
574
|
+
};
|
|
575
|
+
}, workspaceRoot);
|
|
576
|
+
}
|
|
577
|
+
async function addShorthand(agentId, shorthand, fullModel, workspaceRoot) {
|
|
578
|
+
await updateAgentOverlay(agentId, (overlay) => {
|
|
579
|
+
const nextShorthands = {
|
|
580
|
+
...overlay.modelShorthands ?? {},
|
|
581
|
+
[shorthand]: fullModel
|
|
582
|
+
};
|
|
583
|
+
return {
|
|
584
|
+
...overlay,
|
|
585
|
+
modelShorthands: nextShorthands
|
|
586
|
+
};
|
|
587
|
+
}, workspaceRoot);
|
|
588
|
+
}
|
|
589
|
+
async function removeOverlayShorthand(agentId, shorthand, workspaceRoot) {
|
|
590
|
+
return await updateAgentOverlay(agentId, (overlay) => {
|
|
591
|
+
const overlayShorthands = overlay.modelShorthands ?? {};
|
|
592
|
+
if (!(shorthand in overlayShorthands)) return null;
|
|
593
|
+
const next = { ...overlayShorthands };
|
|
594
|
+
delete next[shorthand];
|
|
595
|
+
const updated = { ...overlay };
|
|
596
|
+
if (Object.keys(next).length === 0) delete updated.modelShorthands;
|
|
597
|
+
else updated.modelShorthands = next;
|
|
598
|
+
return updated;
|
|
599
|
+
}, workspaceRoot);
|
|
600
|
+
}
|
|
601
|
+
async function ensureOverlay(state, agentId, workspaceRoot) {
|
|
602
|
+
if (await getAgentOverlay(agentId, workspaceRoot) !== null) return null;
|
|
603
|
+
return stop$3(state, `Agent '${agentId}' has no settings overlay; cannot configure model.`);
|
|
604
|
+
}
|
|
605
|
+
async function slashModel(state) {
|
|
606
|
+
const message = state.message.trim();
|
|
607
|
+
if (!/^\/model(\s|$)/.test(message)) return state;
|
|
608
|
+
const agentId = state.agentId;
|
|
609
|
+
if (!agentId) return stop$3(state, "/model requires an agent. Set a defaultAgent for this chat.");
|
|
610
|
+
const workspaceRoot = getWorkspaceRoot();
|
|
611
|
+
const rest = message.slice(6).trim();
|
|
612
|
+
if (rest === "") return stop$3(state, formatList(await getAgent(agentId, workspaceRoot)));
|
|
613
|
+
const firstSpace = rest.search(/\s/);
|
|
614
|
+
const subcommand = firstSpace === -1 ? rest : rest.slice(0, firstSpace);
|
|
615
|
+
const remainder = firstSpace === -1 ? "" : rest.slice(firstSpace + 1).trim();
|
|
616
|
+
if (subcommand === "help") return stop$3(state, formatHelp());
|
|
617
|
+
if (subcommand === "add") {
|
|
618
|
+
const addMatch = remainder.match(/^(\S+)\s+(\S+)\s*$/);
|
|
619
|
+
if (!addMatch) return stop$3(state, "Usage: /model add <shorthand> <full-name>");
|
|
620
|
+
const shorthand = addMatch[1];
|
|
621
|
+
const fullModel = addMatch[2];
|
|
622
|
+
if (RESERVED_SHORTHANDS.has(shorthand)) return stop$3(state, `Invalid shorthand: '${shorthand}' is reserved.`);
|
|
623
|
+
const guard = await ensureOverlay(state, agentId, workspaceRoot);
|
|
624
|
+
if (guard) return guard;
|
|
625
|
+
await addShorthand(agentId, shorthand, fullModel, workspaceRoot);
|
|
626
|
+
return stop$3(state, `Added shorthand: ${shorthand} -> ${fullModel}`);
|
|
627
|
+
}
|
|
628
|
+
if (subcommand === "remove" || subcommand === "rm") {
|
|
629
|
+
if (!/^\S+$/.test(remainder)) return stop$3(state, "Usage: /model remove <shorthand>");
|
|
630
|
+
const guard = await ensureOverlay(state, agentId, workspaceRoot);
|
|
631
|
+
if (guard) return guard;
|
|
632
|
+
if (!await removeOverlayShorthand(agentId, remainder, workspaceRoot)) {
|
|
633
|
+
if ((await getAgent(agentId, workspaceRoot))?.modelShorthands?.[remainder] !== void 0) return stop$3(state, `Shorthand '${remainder}' is defined in the template, not the overlay. Edit the template to remove it.`);
|
|
634
|
+
return stop$3(state, `Shorthand '${remainder}' not found.`);
|
|
635
|
+
}
|
|
636
|
+
const fallback = (await getAgent(agentId, workspaceRoot))?.modelShorthands?.[remainder];
|
|
637
|
+
return stop$3(state, `Removed shorthand: ${remainder}${fallback !== void 0 ? ` (still resolves to '${fallback}' from template)` : ""}.`);
|
|
638
|
+
}
|
|
639
|
+
if (subcommand.startsWith("-")) return stop$3(state, `Unknown option: ${subcommand}\n${formatHelp()}`);
|
|
640
|
+
if (remainder !== "") return stop$3(state, `Unknown subcommand: ${subcommand}\n${formatHelp()}`);
|
|
641
|
+
const guard = await ensureOverlay(state, agentId, workspaceRoot);
|
|
642
|
+
if (guard) return guard;
|
|
643
|
+
const shorthands = (await getAgent(agentId, workspaceRoot))?.modelShorthands ?? {};
|
|
644
|
+
const matched = Object.prototype.hasOwnProperty.call(shorthands, subcommand);
|
|
645
|
+
const fullModel = matched ? shorthands[subcommand] : subcommand;
|
|
646
|
+
await setModel(agentId, fullModel, workspaceRoot);
|
|
647
|
+
if (matched) return stop$3(state, `Set MODEL to ${fullModel} (shorthand '${subcommand}').`);
|
|
648
|
+
if (looksLikeShorthand(subcommand)) return stop$3(state, `Set MODEL to ${fullModel}. (No shorthand matched — was that the literal model name? Run /model add ${subcommand} <full-name> if not.)`);
|
|
649
|
+
return stop$3(state, `Set MODEL to ${fullModel}.`);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
//#endregion
|
|
653
|
+
//#region src/daemon/routers/slash-restart.ts
|
|
654
|
+
async function slashRestart(state) {
|
|
655
|
+
if (!/^\/restart(\s|$)/.test(state.message)) return state;
|
|
656
|
+
let res;
|
|
657
|
+
try {
|
|
658
|
+
res = await sendControlRequest({
|
|
659
|
+
action: "restart",
|
|
660
|
+
chatId: state.chatId,
|
|
661
|
+
messageId: state.messageId
|
|
662
|
+
});
|
|
663
|
+
} catch (err) {
|
|
664
|
+
return stop$2(state, `Could not reach supervisor: ${err instanceof Error ? err.message : String(err)}.`);
|
|
665
|
+
}
|
|
666
|
+
if (!res.ok) return stop$2(state, `Restart aborted: ${res.error ?? "unknown error"}.`);
|
|
667
|
+
return stop$2(state, "Restarting clawmini...");
|
|
668
|
+
}
|
|
669
|
+
function stop$2(state, reply) {
|
|
670
|
+
return {
|
|
671
|
+
...state,
|
|
672
|
+
message: "",
|
|
673
|
+
action: "stop",
|
|
674
|
+
reply
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
//#endregion
|
|
679
|
+
//#region src/daemon/routers/slash-shutdown.ts
|
|
680
|
+
async function slashShutdown(state) {
|
|
681
|
+
if (!/^\/shutdown(\s|$)/.test(state.message)) return state;
|
|
682
|
+
let res;
|
|
683
|
+
try {
|
|
684
|
+
res = await sendControlRequest({ action: "shutdown" });
|
|
685
|
+
} catch (err) {
|
|
686
|
+
return stop$1(state, `Could not reach supervisor: ${err instanceof Error ? err.message : String(err)}.`);
|
|
687
|
+
}
|
|
688
|
+
if (!res.ok) return stop$1(state, `Shutdown aborted: ${res.error ?? "unknown error"}.`);
|
|
689
|
+
return stop$1(state, "Shutting down clawmini supervisor...");
|
|
690
|
+
}
|
|
691
|
+
function stop$1(state, reply) {
|
|
692
|
+
return {
|
|
693
|
+
...state,
|
|
694
|
+
message: "",
|
|
695
|
+
action: "stop",
|
|
696
|
+
reply
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
//#endregion
|
|
701
|
+
//#region src/shared/version.ts
|
|
702
|
+
let cached = null;
|
|
703
|
+
/**
|
|
704
|
+
* Read the version from the clawmini package.json. Walks up from the current
|
|
705
|
+
* module's location until a package.json named "clawmini" is found.
|
|
706
|
+
*/
|
|
707
|
+
function getClawminiVersion() {
|
|
708
|
+
if (cached !== null) return cached;
|
|
709
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
710
|
+
while (dir !== path.parse(dir).root) {
|
|
711
|
+
const pkgPath = path.join(dir, "package.json");
|
|
712
|
+
if (fs.existsSync(pkgPath)) try {
|
|
713
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
714
|
+
if (pkg.name === "clawmini" && typeof pkg.version === "string") {
|
|
715
|
+
cached = pkg.version;
|
|
716
|
+
return cached;
|
|
717
|
+
}
|
|
718
|
+
} catch {}
|
|
719
|
+
dir = path.dirname(dir);
|
|
720
|
+
}
|
|
721
|
+
cached = "unknown";
|
|
722
|
+
return cached;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
//#endregion
|
|
726
|
+
//#region src/daemon/routers/slash-upgrade.ts
|
|
727
|
+
async function slashUpgrade(state) {
|
|
728
|
+
const trimmed = state.message.trim();
|
|
729
|
+
if (!/^\/upgrade(\s|$)/.test(trimmed)) return state;
|
|
730
|
+
const info = detectInstall();
|
|
731
|
+
if (!info.isNpmGlobal) return stop(state, `Cannot upgrade: clawmini is not installed via \`npm install -g\` (running from ${info.entryRealPath}). Skipping.`);
|
|
732
|
+
const rest = trimmed.slice(8).trim();
|
|
733
|
+
if (rest === "") return stop(state, [
|
|
734
|
+
`Currently running clawmini v${getClawminiVersion()}.`,
|
|
735
|
+
"/upgrade requires an explicit target:",
|
|
736
|
+
" /upgrade latest — install whatever npm reports as the latest tag",
|
|
737
|
+
" /upgrade <version> — install a specific version (e.g. /upgrade 0.0.7)"
|
|
738
|
+
].join("\n"));
|
|
739
|
+
if (!/^\S+$/.test(rest)) return stop(state, "Usage: /upgrade <version>");
|
|
740
|
+
const version = rest;
|
|
741
|
+
if (!isAcceptableVersion(version)) return stop(state, `Invalid version: ${version}`);
|
|
742
|
+
let res;
|
|
743
|
+
try {
|
|
744
|
+
res = await sendControlRequest({
|
|
745
|
+
action: "upgrade",
|
|
746
|
+
version,
|
|
747
|
+
chatId: state.chatId,
|
|
748
|
+
messageId: state.messageId
|
|
749
|
+
});
|
|
750
|
+
} catch (err) {
|
|
751
|
+
return stop(state, `Could not reach supervisor: ${err instanceof Error ? err.message : String(err)}.`);
|
|
752
|
+
}
|
|
753
|
+
if (!res.ok) return stop(state, `Upgrade aborted: ${res.error ?? "unknown error"}.`);
|
|
754
|
+
return stop(state, `Upgrading clawmini to ${version}... services will restart shortly.`);
|
|
755
|
+
}
|
|
756
|
+
function stop(state, reply) {
|
|
757
|
+
return {
|
|
758
|
+
...state,
|
|
759
|
+
message: "",
|
|
760
|
+
action: "stop",
|
|
761
|
+
reply
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
|
|
443
765
|
//#endregion
|
|
444
766
|
//#region src/daemon/routers/session-timeout.ts
|
|
445
767
|
/**
|
|
@@ -465,6 +787,7 @@ function createSessionTimeoutRouter(config = {}) {
|
|
|
465
787
|
const prompt = config.prompt ?? "This chat session has ended. Save any important details from it to your memory. When finished, reply with NO_REPLY_NECESSARY.";
|
|
466
788
|
return function(state) {
|
|
467
789
|
if (state.env?.__SESSION_TIMEOUT__ === "true") return state;
|
|
790
|
+
if (state.subagentId) return state;
|
|
468
791
|
const sessionId = state.sessionId || crypto.randomUUID();
|
|
469
792
|
const jobId = `__session_timeout__${sessionId}`;
|
|
470
793
|
const jobs = {
|
|
@@ -506,7 +829,11 @@ const USER_ROUTERS = [
|
|
|
506
829
|
"@clawmini/slash-command",
|
|
507
830
|
"@clawmini/slash-stop",
|
|
508
831
|
"@clawmini/slash-interrupt",
|
|
509
|
-
"@clawmini/slash-policies"
|
|
832
|
+
"@clawmini/slash-policies",
|
|
833
|
+
"@clawmini/slash-model",
|
|
834
|
+
"@clawmini/slash-restart",
|
|
835
|
+
"@clawmini/slash-shutdown",
|
|
836
|
+
"@clawmini/slash-upgrade"
|
|
510
837
|
];
|
|
511
838
|
function resolveRouters(userRouters, isUserMessage) {
|
|
512
839
|
const resolvedGlobals = [];
|
|
@@ -563,6 +890,10 @@ async function executeRouterPipeline(initialState, routers) {
|
|
|
563
890
|
else if (router === "@clawmini/slash-stop") state = slashStop(state);
|
|
564
891
|
else if (router === "@clawmini/slash-interrupt") state = slashInterrupt(state);
|
|
565
892
|
else if (router === "@clawmini/slash-policies") state = await slashPolicies(state);
|
|
893
|
+
else if (router === "@clawmini/slash-model") state = await slashModel(state);
|
|
894
|
+
else if (router === "@clawmini/slash-restart") state = await slashRestart(state);
|
|
895
|
+
else if (router === "@clawmini/slash-shutdown") state = await slashShutdown(state);
|
|
896
|
+
else if (router === "@clawmini/slash-upgrade") state = await slashUpgrade(state);
|
|
566
897
|
else if (router === "@clawmini/session-timeout") state = createSessionTimeoutRouter(config)(state);
|
|
567
898
|
else try {
|
|
568
899
|
state = await executeCustomRouter(router, state);
|
|
@@ -631,15 +962,16 @@ async function executeCustomRouter(command, state) {
|
|
|
631
962
|
|
|
632
963
|
//#endregion
|
|
633
964
|
//#region src/daemon/agent/agent-context.ts
|
|
634
|
-
function formatEnvironmentPrefix(prefix, replacements) {
|
|
965
|
+
function formatEnvironmentPrefix(prefix, searchDirs, replacements) {
|
|
966
|
+
let out = substituteLayeredEnvDir(prefix, searchDirs);
|
|
635
967
|
const map = {
|
|
636
968
|
"{WORKSPACE_DIR}": replacements.targetPath,
|
|
637
969
|
"{AGENT_DIR}": replacements.executionCwd,
|
|
638
|
-
"{ENV_DIR}": replacements.envDir,
|
|
639
970
|
"{HOME_DIR}": process.env.HOME || "",
|
|
640
971
|
"{ENV_ARGS}": replacements.envArgs
|
|
641
972
|
};
|
|
642
|
-
|
|
973
|
+
out = out.replace(/{(WORKSPACE_DIR|AGENT_DIR|HOME_DIR|ENV_ARGS)}/g, (match) => map[match] || match);
|
|
974
|
+
return out;
|
|
643
975
|
}
|
|
644
976
|
async function sandboxExecutionContext(initialCommand, env, agentSpecificEnvKeys, executionCwd, cwd) {
|
|
645
977
|
let command = initialCommand;
|
|
@@ -647,13 +979,14 @@ async function sandboxExecutionContext(initialCommand, env, agentSpecificEnvKeys
|
|
|
647
979
|
if (!activeEnvInfo) return command;
|
|
648
980
|
const activeEnvName = activeEnvInfo.name;
|
|
649
981
|
const activeEnv = await readEnvironment(activeEnvName, cwd);
|
|
982
|
+
const searchDirs = await getEnvironmentSearchDirs(activeEnvName, cwd);
|
|
650
983
|
if (activeEnv?.env) for (const [key, value] of Object.entries(activeEnv.env)) if (value === false) {
|
|
651
984
|
delete env[key];
|
|
652
985
|
agentSpecificEnvKeys.delete(key);
|
|
653
986
|
} else {
|
|
654
987
|
let interpolatedValue = String(value);
|
|
655
988
|
interpolatedValue = interpolatedValue.replace(/\{PATH\}/g, process.env.PATH || "");
|
|
656
|
-
interpolatedValue = interpolatedValue
|
|
989
|
+
interpolatedValue = substituteLayeredEnvDir(interpolatedValue, searchDirs);
|
|
657
990
|
interpolatedValue = interpolatedValue.replace(/\{WORKSPACE_DIR\}/g, activeEnvInfo.targetPath);
|
|
658
991
|
env[key] = interpolatedValue;
|
|
659
992
|
agentSpecificEnvKeys.add(key);
|
|
@@ -663,10 +996,9 @@ async function sandboxExecutionContext(initialCommand, env, agentSpecificEnvKeys
|
|
|
663
996
|
if (activeEnv.envFormat) return activeEnv.envFormat.replace("{key}", key);
|
|
664
997
|
return key;
|
|
665
998
|
}).join(" ");
|
|
666
|
-
const prefixReplaced = formatEnvironmentPrefix(activeEnv.prefix, {
|
|
999
|
+
const prefixReplaced = formatEnvironmentPrefix(activeEnv.prefix, searchDirs, {
|
|
667
1000
|
targetPath: activeEnvInfo.targetPath,
|
|
668
1001
|
executionCwd,
|
|
669
|
-
envDir: getEnvironmentPath(activeEnvName, cwd),
|
|
670
1002
|
envArgs
|
|
671
1003
|
});
|
|
672
1004
|
if (prefixReplaced.includes("{COMMAND}")) command = prefixReplaced.replace("{COMMAND}", command);
|
|
@@ -859,12 +1191,17 @@ const runCommand = async ({ command, cwd, env, stdin, signal }) => {
|
|
|
859
1191
|
|
|
860
1192
|
//#endregion
|
|
861
1193
|
//#region src/daemon/agent/chat-logger.ts
|
|
862
|
-
function createChatLogger(chatId, subagentId) {
|
|
1194
|
+
function createChatLogger(chatId, subagentId, sessionId, turnId) {
|
|
863
1195
|
async function append(msg) {
|
|
864
|
-
|
|
865
|
-
|
|
1196
|
+
let finalMsg = msg;
|
|
1197
|
+
if (subagentId) finalMsg = {
|
|
1198
|
+
...finalMsg,
|
|
866
1199
|
subagentId
|
|
867
|
-
}
|
|
1200
|
+
};
|
|
1201
|
+
if (turnId) finalMsg = {
|
|
1202
|
+
...finalMsg,
|
|
1203
|
+
turnId
|
|
1204
|
+
};
|
|
868
1205
|
await appendMessage(chatId, finalMsg);
|
|
869
1206
|
return finalMsg;
|
|
870
1207
|
}
|
|
@@ -885,13 +1222,15 @@ function createChatLogger(chatId, subagentId) {
|
|
|
885
1222
|
id: crypto.randomUUID(),
|
|
886
1223
|
role: "user",
|
|
887
1224
|
content: msg,
|
|
888
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1225
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1226
|
+
sessionId
|
|
889
1227
|
}),
|
|
890
1228
|
logCommandResult: async ({ messageId, content, command, cwd, result }) => append({
|
|
891
1229
|
id: crypto.randomUUID(),
|
|
892
1230
|
role: "command",
|
|
893
1231
|
content,
|
|
894
1232
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1233
|
+
sessionId,
|
|
895
1234
|
messageId,
|
|
896
1235
|
command,
|
|
897
1236
|
cwd,
|
|
@@ -904,6 +1243,7 @@ function createChatLogger(chatId, subagentId) {
|
|
|
904
1243
|
role: "command",
|
|
905
1244
|
content,
|
|
906
1245
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1246
|
+
sessionId,
|
|
907
1247
|
messageId: crypto.randomUUID(),
|
|
908
1248
|
stderr: "",
|
|
909
1249
|
command: "",
|
|
@@ -916,6 +1256,7 @@ function createChatLogger(chatId, subagentId) {
|
|
|
916
1256
|
role: "system",
|
|
917
1257
|
content,
|
|
918
1258
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1259
|
+
sessionId,
|
|
919
1260
|
messageId,
|
|
920
1261
|
event: "router",
|
|
921
1262
|
displayRole: "agent"
|
|
@@ -925,6 +1266,7 @@ function createChatLogger(chatId, subagentId) {
|
|
|
925
1266
|
role: "command",
|
|
926
1267
|
content,
|
|
927
1268
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1269
|
+
sessionId,
|
|
928
1270
|
messageId,
|
|
929
1271
|
command: "retry-delay",
|
|
930
1272
|
stderr: "",
|
|
@@ -932,16 +1274,18 @@ function createChatLogger(chatId, subagentId) {
|
|
|
932
1274
|
cwd,
|
|
933
1275
|
exitCode: 0
|
|
934
1276
|
}),
|
|
935
|
-
logSystemMessage: async ({ content, event, messageId, displayRole }) => {
|
|
1277
|
+
logSystemMessage: async ({ content, event, messageId, displayRole, jobId }) => {
|
|
936
1278
|
const msg = {
|
|
937
1279
|
id: crypto.randomUUID(),
|
|
938
1280
|
role: "system",
|
|
939
1281
|
content,
|
|
940
1282
|
event,
|
|
941
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1283
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1284
|
+
sessionId
|
|
942
1285
|
};
|
|
943
1286
|
if (messageId !== void 0) msg.messageId = messageId;
|
|
944
1287
|
if (displayRole !== void 0) msg.displayRole = displayRole;
|
|
1288
|
+
if (jobId !== void 0) msg.jobId = jobId;
|
|
945
1289
|
return append(msg);
|
|
946
1290
|
},
|
|
947
1291
|
logSubagentStatus: async ({ subagentId: targetSubagentId, status }) => {
|
|
@@ -951,7 +1295,8 @@ function createChatLogger(chatId, subagentId) {
|
|
|
951
1295
|
content: `Subagent ${status}`,
|
|
952
1296
|
subagentId: targetSubagentId,
|
|
953
1297
|
status,
|
|
954
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1298
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1299
|
+
sessionId
|
|
955
1300
|
});
|
|
956
1301
|
},
|
|
957
1302
|
logAgentReply: async ({ content, files }) => {
|
|
@@ -959,7 +1304,8 @@ function createChatLogger(chatId, subagentId) {
|
|
|
959
1304
|
id: crypto.randomUUID(),
|
|
960
1305
|
role: "agent",
|
|
961
1306
|
content,
|
|
962
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1307
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1308
|
+
sessionId
|
|
963
1309
|
};
|
|
964
1310
|
if (files !== void 0) msg.files = files;
|
|
965
1311
|
return append(msg);
|
|
@@ -972,7 +1318,8 @@ function createChatLogger(chatId, subagentId) {
|
|
|
972
1318
|
messageId,
|
|
973
1319
|
name,
|
|
974
1320
|
payload,
|
|
975
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1321
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1322
|
+
sessionId
|
|
976
1323
|
});
|
|
977
1324
|
},
|
|
978
1325
|
logPolicyRequestMessage: async ({ content, messageId, requestId, commandName, args, status }) => {
|
|
@@ -985,7 +1332,8 @@ function createChatLogger(chatId, subagentId) {
|
|
|
985
1332
|
commandName,
|
|
986
1333
|
args,
|
|
987
1334
|
status,
|
|
988
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1335
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1336
|
+
sessionId
|
|
989
1337
|
});
|
|
990
1338
|
}
|
|
991
1339
|
};
|
|
@@ -1247,6 +1595,7 @@ var AgentSession = class {
|
|
|
1247
1595
|
sessionId;
|
|
1248
1596
|
chatId;
|
|
1249
1597
|
subagentId;
|
|
1598
|
+
turnId;
|
|
1250
1599
|
settings;
|
|
1251
1600
|
workspaceRoot;
|
|
1252
1601
|
globalSettings;
|
|
@@ -1256,10 +1605,11 @@ var AgentSession = class {
|
|
|
1256
1605
|
this.sessionId = config.sessionId;
|
|
1257
1606
|
this.chatId = config.chatId;
|
|
1258
1607
|
this.subagentId = config.subagentId;
|
|
1608
|
+
this.turnId = config.turnId;
|
|
1259
1609
|
this.settings = config.settings;
|
|
1260
1610
|
this.workspaceRoot = config.workspaceRoot;
|
|
1261
1611
|
this.globalSettings = config.globalSettings;
|
|
1262
|
-
this.logger = config.logger ?? createChatLogger(this.chatId, this.subagentId);
|
|
1612
|
+
this.logger = config.logger ?? createChatLogger(this.chatId, this.subagentId, this.sessionId, this.turnId);
|
|
1263
1613
|
}
|
|
1264
1614
|
async buildExecutionContext(messageContent, routerEnv, fallback) {
|
|
1265
1615
|
const currentAgent = {
|
|
@@ -1303,6 +1653,7 @@ var AgentSession = class {
|
|
|
1303
1653
|
agentId: this.agentId,
|
|
1304
1654
|
sessionId: this.sessionId,
|
|
1305
1655
|
...this.subagentId ? { subagentId: this.subagentId } : {},
|
|
1656
|
+
...this.turnId ? { turnId: this.turnId } : {},
|
|
1306
1657
|
timestamp: Date.now()
|
|
1307
1658
|
});
|
|
1308
1659
|
if (currentAgent.apiTokenEnvVar) {
|
|
@@ -1372,6 +1723,7 @@ async function createAgentSession(options) {
|
|
|
1372
1723
|
sessionId: options.sessionId,
|
|
1373
1724
|
chatId: options.chatId,
|
|
1374
1725
|
...options.subagentId ? { subagentId: options.subagentId } : {},
|
|
1726
|
+
...options.turnId ? { turnId: options.turnId } : {},
|
|
1375
1727
|
settings: mergedAgent,
|
|
1376
1728
|
workspaceRoot,
|
|
1377
1729
|
globalSettings: settings,
|
|
@@ -1398,16 +1750,89 @@ async function resolveMergedAgent(agentId, settings, cwd) {
|
|
|
1398
1750
|
return mergedAgent;
|
|
1399
1751
|
}
|
|
1400
1752
|
|
|
1753
|
+
//#endregion
|
|
1754
|
+
//#region src/daemon/agent/turn-registry.ts
|
|
1755
|
+
const turns = /* @__PURE__ */ new Map();
|
|
1756
|
+
/**
|
|
1757
|
+
* Watchdog for turns whose subagent count never drains. Pathological cases
|
|
1758
|
+
* (crashed subagent, bug leaving the counter > 0) would otherwise pin the
|
|
1759
|
+
* turn's activity log open indefinitely. Default is a guess — instrument
|
|
1760
|
+
* before tuning.
|
|
1761
|
+
*/
|
|
1762
|
+
const DEFAULT_TURN_MAX_DURATION_MS = 1800 * 1e3;
|
|
1763
|
+
function registerTurn(chatId, turnId, maxDurationMs = DEFAULT_TURN_MAX_DURATION_MS) {
|
|
1764
|
+
if (turns.has(turnId)) return;
|
|
1765
|
+
const state = {
|
|
1766
|
+
chatId,
|
|
1767
|
+
outstanding: 0,
|
|
1768
|
+
parentExited: false,
|
|
1769
|
+
outcome: "ok",
|
|
1770
|
+
timeoutHandle: setTimeout(() => {
|
|
1771
|
+
const s = turns.get(turnId);
|
|
1772
|
+
if (!s) return;
|
|
1773
|
+
console.warn(`Turn ${turnId} force-ended after ${maxDurationMs}ms (outstanding=${s.outstanding}).`);
|
|
1774
|
+
turns.delete(turnId);
|
|
1775
|
+
emitTurnEnded({
|
|
1776
|
+
chatId: s.chatId,
|
|
1777
|
+
turnId,
|
|
1778
|
+
outcome: "error"
|
|
1779
|
+
});
|
|
1780
|
+
}, maxDurationMs)
|
|
1781
|
+
};
|
|
1782
|
+
state.timeoutHandle.unref();
|
|
1783
|
+
turns.set(turnId, state);
|
|
1784
|
+
}
|
|
1785
|
+
function incrementSubagent(turnId) {
|
|
1786
|
+
if (!turnId) return;
|
|
1787
|
+
const state = turns.get(turnId);
|
|
1788
|
+
if (!state) return;
|
|
1789
|
+
state.outstanding++;
|
|
1790
|
+
}
|
|
1791
|
+
function decrementSubagent(turnId) {
|
|
1792
|
+
if (!turnId) return;
|
|
1793
|
+
const state = turns.get(turnId);
|
|
1794
|
+
if (!state) return;
|
|
1795
|
+
state.outstanding = Math.max(0, state.outstanding - 1);
|
|
1796
|
+
maybeFinalize(turnId, state);
|
|
1797
|
+
}
|
|
1798
|
+
/**
|
|
1799
|
+
* Called once, when the parent agent's initial `handleMessage` promise
|
|
1800
|
+
* settles. Records the outcome; the turn actually ends (emits `turnEnded`)
|
|
1801
|
+
* only once the outstanding subagent count also reaches zero.
|
|
1802
|
+
*/
|
|
1803
|
+
function markParentExited(turnId, outcome) {
|
|
1804
|
+
const state = turns.get(turnId);
|
|
1805
|
+
if (!state) return;
|
|
1806
|
+
if (state.parentExited) return;
|
|
1807
|
+
state.parentExited = true;
|
|
1808
|
+
state.outcome = outcome;
|
|
1809
|
+
maybeFinalize(turnId, state);
|
|
1810
|
+
}
|
|
1811
|
+
function maybeFinalize(turnId, state) {
|
|
1812
|
+
if (!state.parentExited) return;
|
|
1813
|
+
if (state.outstanding > 0) return;
|
|
1814
|
+
clearTimeout(state.timeoutHandle);
|
|
1815
|
+
turns.delete(turnId);
|
|
1816
|
+
emitTurnEnded({
|
|
1817
|
+
chatId: state.chatId,
|
|
1818
|
+
turnId,
|
|
1819
|
+
outcome: state.outcome
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1401
1823
|
//#endregion
|
|
1402
1824
|
//#region src/daemon/message.ts
|
|
1403
|
-
async function executeDirectMessage(chatId, state, settings, cwd, noWait = false, userMessageContent, subagentId, systemEvent, displayRole) {
|
|
1404
|
-
const
|
|
1825
|
+
async function executeDirectMessage(chatId, state, settings, cwd, noWait = false, userMessageContent, subagentId, systemEvent, displayRole, parentTurnId) {
|
|
1826
|
+
const turnId = parentTurnId ?? randomUUID();
|
|
1827
|
+
const emitLifecycle = !parentTurnId;
|
|
1828
|
+
const logger = createChatLogger(chatId, subagentId, state.sessionId, turnId);
|
|
1405
1829
|
let msgId;
|
|
1406
1830
|
if (systemEvent) msgId = (await logger.logSystemMessage({
|
|
1407
1831
|
content: userMessageContent ?? state.message,
|
|
1408
1832
|
event: systemEvent,
|
|
1409
1833
|
messageId: state.messageId,
|
|
1410
|
-
...displayRole ? { displayRole } : {}
|
|
1834
|
+
...displayRole ? { displayRole } : {},
|
|
1835
|
+
...state.jobId ? { jobId: state.jobId } : {}
|
|
1411
1836
|
})).id;
|
|
1412
1837
|
else msgId = (await logger.logUserMessage(userMessageContent ?? state.message)).id;
|
|
1413
1838
|
if (state.reply) await logger.logAutomaticReply({
|
|
@@ -1422,29 +1847,46 @@ async function executeDirectMessage(chatId, state, settings, cwd, noWait = false
|
|
|
1422
1847
|
...subagentId ? { subagentId } : {},
|
|
1423
1848
|
cwd,
|
|
1424
1849
|
settings,
|
|
1425
|
-
logger
|
|
1850
|
+
logger,
|
|
1851
|
+
turnId
|
|
1426
1852
|
});
|
|
1427
1853
|
let finalMessage = {
|
|
1428
1854
|
id: state.messageId,
|
|
1429
1855
|
content: state.message,
|
|
1430
|
-
env: state.env ?? {}
|
|
1856
|
+
env: state.env ?? {},
|
|
1857
|
+
turnId
|
|
1431
1858
|
};
|
|
1432
1859
|
if (state.action === "stop") {
|
|
1433
1860
|
agentSession.stop();
|
|
1861
|
+
if (!subagentId) await stopActiveSubagents(chatId, cwd);
|
|
1434
1862
|
return;
|
|
1435
1863
|
}
|
|
1436
1864
|
if (state.action === "interrupt") finalMessage = agentSession.interrupt(finalMessage);
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1865
|
+
if (emitLifecycle) {
|
|
1866
|
+
registerTurn(chatId, turnId);
|
|
1867
|
+
emitTurnStarted({
|
|
1868
|
+
chatId,
|
|
1869
|
+
turnId,
|
|
1870
|
+
rootMessageId: msgId,
|
|
1871
|
+
...state.externalRef ? { externalRef: state.externalRef } : {}
|
|
1872
|
+
});
|
|
1442
1873
|
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1874
|
+
const taskPromise = agentSession.handleMessage(finalMessage);
|
|
1875
|
+
const settleTurn = async () => {
|
|
1876
|
+
try {
|
|
1877
|
+
await taskPromise;
|
|
1878
|
+
if (emitLifecycle) markParentExited(turnId, "ok");
|
|
1879
|
+
} catch (err) {
|
|
1880
|
+
if (emitLifecycle) markParentExited(turnId, "error");
|
|
1881
|
+
if (!(err instanceof Error && err.name === "AbortError")) throw err;
|
|
1882
|
+
}
|
|
1883
|
+
};
|
|
1884
|
+
if (!noWait) await settleTurn();
|
|
1885
|
+
else settleTurn().catch((err) => {
|
|
1886
|
+
if (err?.name !== "AbortError") console.error("Task execution error:", err);
|
|
1445
1887
|
});
|
|
1446
1888
|
}
|
|
1447
|
-
async function getInitialRouterState(chatId, message, chatSettings, overrideAgentId, overrideSessionId) {
|
|
1889
|
+
async function getInitialRouterState(chatId, message, chatSettings, overrideAgentId, overrideSessionId, externalRef) {
|
|
1448
1890
|
const agentId = overrideAgentId ?? chatSettings.defaultAgent ?? "default";
|
|
1449
1891
|
const sessionId = overrideSessionId ?? chatSettings.sessions?.[agentId] ?? "default";
|
|
1450
1892
|
return {
|
|
@@ -1453,19 +1895,20 @@ async function getInitialRouterState(chatId, message, chatSettings, overrideAgen
|
|
|
1453
1895
|
chatId,
|
|
1454
1896
|
agentId,
|
|
1455
1897
|
sessionId,
|
|
1456
|
-
env: {}
|
|
1898
|
+
env: {},
|
|
1899
|
+
...externalRef ? { externalRef } : {}
|
|
1457
1900
|
};
|
|
1458
1901
|
}
|
|
1459
|
-
async function handleUserMessage(chatId, message, settings, cwd = process.cwd(), noWait = false, sessionId, overrideAgentId) {
|
|
1902
|
+
async function handleUserMessage(chatId, message, settings, cwd = process.cwd(), noWait = false, sessionId, overrideAgentId, externalRef) {
|
|
1460
1903
|
const chatSettings = await readChatSettings(chatId, cwd) ?? {};
|
|
1461
1904
|
if (overrideAgentId && chatSettings.defaultAgent !== overrideAgentId) {
|
|
1462
1905
|
chatSettings.defaultAgent = overrideAgentId;
|
|
1463
1906
|
await writeChatSettings(chatId, chatSettings, cwd);
|
|
1464
1907
|
}
|
|
1465
|
-
const initialState = await getInitialRouterState(chatId, message, chatSettings, overrideAgentId, sessionId);
|
|
1908
|
+
const initialState = await getInitialRouterState(chatId, message, chatSettings, overrideAgentId, sessionId, externalRef);
|
|
1466
1909
|
const finalState = await executeRouterPipeline(initialState, resolveRouters(chatSettings.routers ?? settings?.routers ?? [], true));
|
|
1467
1910
|
await applyRouterStateUpdates(chatId, cwd, finalState, chatSettings, initialState.agentId);
|
|
1468
|
-
await executeDirectMessage(chatId, finalState, settings, cwd, noWait, message);
|
|
1911
|
+
await executeDirectMessage(chatId, finalState, settings, cwd, noWait, message, finalState.subagentId);
|
|
1469
1912
|
}
|
|
1470
1913
|
async function applyRouterStateUpdates(chatId, cwd, finalState, chatSettings, initialAgent) {
|
|
1471
1914
|
const finalAgentId = finalState.agentId;
|
|
@@ -1490,10 +1933,11 @@ async function applyRouterStateUpdates(chatId, cwd, finalState, chatSettings, in
|
|
|
1490
1933
|
settingsChanged = true;
|
|
1491
1934
|
}
|
|
1492
1935
|
if (finalState.jobs.add?.length) {
|
|
1493
|
-
const
|
|
1494
|
-
|
|
1936
|
+
const normalized = finalState.jobs.add.map(normalizeJob);
|
|
1937
|
+
const addMap = new Map(normalized.map((job) => [job.id, job]));
|
|
1938
|
+
for (const job of normalized) cronManager.scheduleJob(chatId, job);
|
|
1495
1939
|
chatSettings.jobs = chatSettings.jobs.filter((job) => !addMap.has(job.id));
|
|
1496
|
-
chatSettings.jobs.push(...
|
|
1940
|
+
chatSettings.jobs.push(...normalized);
|
|
1497
1941
|
settingsChanged = true;
|
|
1498
1942
|
}
|
|
1499
1943
|
}
|
|
@@ -1501,9 +1945,45 @@ async function applyRouterStateUpdates(chatId, cwd, finalState, chatSettings, in
|
|
|
1501
1945
|
if (finalState.sessionId === void 0) finalState.sessionId = finalSessionId;
|
|
1502
1946
|
if (finalState.agentId === void 0) finalState.agentId = currentAgentId;
|
|
1503
1947
|
}
|
|
1948
|
+
async function stopActiveSubagents(chatId, cwd) {
|
|
1949
|
+
const sessionsToAbort = [];
|
|
1950
|
+
await updateChatSettings(chatId, (settings) => {
|
|
1951
|
+
if (settings.subagents) {
|
|
1952
|
+
for (const sub of Object.values(settings.subagents)) if (sub.status === "active") {
|
|
1953
|
+
if (sub.sessionId) sessionsToAbort.push(sub.sessionId);
|
|
1954
|
+
sub.status = "failed";
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
return settings;
|
|
1958
|
+
}, cwd);
|
|
1959
|
+
for (const sessionId of sessionsToAbort) taskScheduler.abortTasks(sessionId);
|
|
1960
|
+
}
|
|
1504
1961
|
|
|
1505
1962
|
//#endregion
|
|
1506
1963
|
//#region src/daemon/cron.ts
|
|
1964
|
+
function normalizeJob(job) {
|
|
1965
|
+
if (!("at" in job.schedule)) return job;
|
|
1966
|
+
const atStr = job.schedule.at;
|
|
1967
|
+
const match = atStr.match(/^(\d+)\s*(m|min|minutes?|h|hours?|d|days?|s|sec|seconds?)$/i);
|
|
1968
|
+
let absolute;
|
|
1969
|
+
if (match) {
|
|
1970
|
+
const val = parseInt(match[1], 10);
|
|
1971
|
+
const unit = match[2].toLowerCase();
|
|
1972
|
+
let ms = 0;
|
|
1973
|
+
if (unit.startsWith("s")) ms = val * 1e3;
|
|
1974
|
+
else if (unit.startsWith("m")) ms = val * 60 * 1e3;
|
|
1975
|
+
else if (unit.startsWith("h")) ms = val * 60 * 60 * 1e3;
|
|
1976
|
+
else if (unit.startsWith("d")) ms = val * 24 * 60 * 60 * 1e3;
|
|
1977
|
+
absolute = new Date(Date.now() + ms);
|
|
1978
|
+
} else {
|
|
1979
|
+
absolute = new Date(atStr);
|
|
1980
|
+
if (isNaN(absolute.getTime())) throw new Error(`Invalid date format for 'at' schedule: ${atStr}`);
|
|
1981
|
+
}
|
|
1982
|
+
return {
|
|
1983
|
+
...job,
|
|
1984
|
+
schedule: { at: absolute.toISOString() }
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1507
1987
|
var CronManager = class {
|
|
1508
1988
|
jobs = /* @__PURE__ */ new Map();
|
|
1509
1989
|
getJobKey(chatId, jobId) {
|
|
@@ -1538,19 +2018,11 @@ var CronManager = class {
|
|
|
1538
2018
|
} else rule = everyStr;
|
|
1539
2019
|
} else if ("at" in job.schedule) {
|
|
1540
2020
|
const atStr = job.schedule.at;
|
|
1541
|
-
|
|
1542
|
-
if (
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
if (unit.startsWith("s")) ms = val * 1e3;
|
|
1547
|
-
else if (unit.startsWith("m")) ms = val * 60 * 1e3;
|
|
1548
|
-
else if (unit.startsWith("h")) ms = val * 60 * 60 * 1e3;
|
|
1549
|
-
else if (unit.startsWith("d")) ms = val * 24 * 60 * 60 * 1e3;
|
|
1550
|
-
rule = new Date(Date.now() + ms);
|
|
1551
|
-
} else {
|
|
1552
|
-
rule = new Date(atStr);
|
|
1553
|
-
if (isNaN(rule.getTime())) throw new Error(`Invalid date format for 'at' schedule: ${atStr}`);
|
|
2021
|
+
rule = new Date(atStr);
|
|
2022
|
+
if (isNaN(rule.getTime())) throw new Error(`Invalid date format for 'at' schedule: ${atStr}`);
|
|
2023
|
+
if (rule.getTime() <= Date.now()) {
|
|
2024
|
+
console.warn(`One-off job ${job.id} for chat ${chatId} is overdue (target ${atStr}); firing immediately.`);
|
|
2025
|
+
rule = new Date(Date.now() + 100);
|
|
1554
2026
|
}
|
|
1555
2027
|
isOneOff = true;
|
|
1556
2028
|
} else {
|
|
@@ -1579,7 +2051,7 @@ var CronManager = class {
|
|
|
1579
2051
|
const settingsPath = getSettingsPath();
|
|
1580
2052
|
let globalSettings;
|
|
1581
2053
|
try {
|
|
1582
|
-
const settingsStr = await
|
|
2054
|
+
const settingsStr = await fsPromises.readFile(settingsPath, "utf8");
|
|
1583
2055
|
globalSettings = JSON.parse(settingsStr);
|
|
1584
2056
|
} catch {
|
|
1585
2057
|
globalSettings = void 0;
|
|
@@ -1598,6 +2070,7 @@ var CronManager = class {
|
|
|
1598
2070
|
if (job.nextSessionId !== void 0 && !isOutdatedSession) routerState.nextSessionId = job.nextSessionId;
|
|
1599
2071
|
if (job.action !== void 0) routerState.action = job.action;
|
|
1600
2072
|
if (job.jobs !== void 0) routerState.jobs = job.jobs;
|
|
2073
|
+
routerState.jobId = job.id;
|
|
1601
2074
|
const resolvedRouters = resolveRouters(chatSettings.routers ?? globalSettings?.routers ?? [], false);
|
|
1602
2075
|
const initialState = { ...routerState };
|
|
1603
2076
|
routerState = await executeRouterPipeline(routerState, resolvedRouters);
|
|
@@ -1624,7 +2097,7 @@ async function getUniquePath(p) {
|
|
|
1624
2097
|
let currentPath = p;
|
|
1625
2098
|
let counter = 1;
|
|
1626
2099
|
while (true) try {
|
|
1627
|
-
await
|
|
2100
|
+
await fsPromises.stat(currentPath);
|
|
1628
2101
|
const ext = path.extname(p);
|
|
1629
2102
|
const base = path.basename(p, ext);
|
|
1630
2103
|
currentPath = path.join(path.dirname(p), `${base}-${counter}${ext}`);
|
|
@@ -1667,7 +2140,7 @@ async function validateAttachments(files) {
|
|
|
1667
2140
|
message: "File must be inside the temporary directory."
|
|
1668
2141
|
});
|
|
1669
2142
|
try {
|
|
1670
|
-
await
|
|
2143
|
+
await fsPromises.access(absoluteFile);
|
|
1671
2144
|
} catch {
|
|
1672
2145
|
throw new TRPCError({
|
|
1673
2146
|
code: "BAD_REQUEST",
|
|
@@ -1683,7 +2156,7 @@ async function validateLogFile(file, agentDir, workspaceRoot) {
|
|
|
1683
2156
|
message: "File must be within the agent workspace."
|
|
1684
2157
|
});
|
|
1685
2158
|
try {
|
|
1686
|
-
await
|
|
2159
|
+
await fsPromises.access(resolvedPath);
|
|
1687
2160
|
} catch {
|
|
1688
2161
|
throw new TRPCError({
|
|
1689
2162
|
code: "BAD_REQUEST",
|
|
@@ -1696,14 +2169,15 @@ async function listCronJobsShared(chatId) {
|
|
|
1696
2169
|
return (await readChatSettings(chatId))?.jobs ?? [];
|
|
1697
2170
|
}
|
|
1698
2171
|
async function addCronJobShared(chatId, job) {
|
|
2172
|
+
const normalized = normalizeJob(job);
|
|
1699
2173
|
const settings = await readChatSettings(chatId) || {};
|
|
1700
2174
|
const cronJobs = settings.jobs ?? [];
|
|
1701
|
-
const existingIndex = cronJobs.findIndex((j) => j.id ===
|
|
1702
|
-
if (existingIndex >= 0) cronJobs[existingIndex] =
|
|
1703
|
-
else cronJobs.push(
|
|
2175
|
+
const existingIndex = cronJobs.findIndex((j) => j.id === normalized.id);
|
|
2176
|
+
if (existingIndex >= 0) cronJobs[existingIndex] = normalized;
|
|
2177
|
+
else cronJobs.push(normalized);
|
|
1704
2178
|
settings.jobs = cronJobs;
|
|
1705
2179
|
await writeChatSettings(chatId, settings);
|
|
1706
|
-
cronManager.scheduleJob(chatId,
|
|
2180
|
+
cronManager.scheduleJob(chatId, normalized);
|
|
1707
2181
|
return { success: true };
|
|
1708
2182
|
}
|
|
1709
2183
|
async function deleteCronJobShared(chatId, id) {
|
|
@@ -1740,7 +2214,8 @@ const sendMessage = apiProcedure.input(z.object({
|
|
|
1740
2214
|
agentId: z.string().optional(),
|
|
1741
2215
|
noWait: z.boolean().optional(),
|
|
1742
2216
|
files: z.array(z.string()).optional(),
|
|
1743
|
-
adapter: z.string().optional()
|
|
2217
|
+
adapter: z.string().optional(),
|
|
2218
|
+
externalRef: z.string().optional()
|
|
1744
2219
|
})
|
|
1745
2220
|
})).mutation(async ({ input }) => {
|
|
1746
2221
|
let message = input.data.message;
|
|
@@ -1751,7 +2226,7 @@ const sendMessage = apiProcedure.input(z.object({
|
|
|
1751
2226
|
const settingsPath = getSettingsPath();
|
|
1752
2227
|
let settings;
|
|
1753
2228
|
try {
|
|
1754
|
-
const settingsStr = await
|
|
2229
|
+
const settingsStr = await fsPromises.readFile(settingsPath, "utf8");
|
|
1755
2230
|
settings = JSON.parse(settingsStr);
|
|
1756
2231
|
} catch (err) {
|
|
1757
2232
|
throw new Error(`Failed to read settings from ${settingsPath}: ${err}`, { cause: err });
|
|
@@ -1769,23 +2244,23 @@ const sendMessage = apiProcedure.input(z.object({
|
|
|
1769
2244
|
message: "Target directory must be within the workspace."
|
|
1770
2245
|
});
|
|
1771
2246
|
await validateAttachments(files);
|
|
1772
|
-
await
|
|
2247
|
+
await fsPromises.mkdir(targetDir, { recursive: true });
|
|
1773
2248
|
const finalPaths = [];
|
|
1774
2249
|
for (const file of files) {
|
|
1775
2250
|
const fileName = path.basename(file);
|
|
1776
2251
|
const targetPath = await getUniquePath(path.join(targetDir, fileName));
|
|
1777
2252
|
try {
|
|
1778
|
-
await
|
|
2253
|
+
await fsPromises.rename(file, targetPath);
|
|
1779
2254
|
} catch {
|
|
1780
|
-
await
|
|
1781
|
-
await
|
|
2255
|
+
await fsPromises.copyFile(file, targetPath);
|
|
2256
|
+
await fsPromises.unlink(file);
|
|
1782
2257
|
}
|
|
1783
2258
|
finalPaths.push(path.relative(agentDir, targetPath));
|
|
1784
2259
|
}
|
|
1785
2260
|
const fileList = `Attached files:\n${finalPaths.map((p) => `- ${p}`).join("\n")}`;
|
|
1786
2261
|
message = message ? `${message}\n\n${fileList}` : fileList;
|
|
1787
2262
|
}
|
|
1788
|
-
await handleUserMessage(chatId, message, settings, void 0, noWait, sessionId, agentId);
|
|
2263
|
+
await handleUserMessage(chatId, message, settings, void 0, noWait, sessionId, agentId, input.data.externalRef);
|
|
1789
2264
|
return { success: true };
|
|
1790
2265
|
});
|
|
1791
2266
|
const getMessages = apiProcedure.input(z.object({
|
|
@@ -1794,6 +2269,11 @@ const getMessages = apiProcedure.input(z.object({
|
|
|
1794
2269
|
})).query(async ({ input }) => {
|
|
1795
2270
|
return getMessages$1(input.chatId ?? await getDefaultChatId(), input.limit);
|
|
1796
2271
|
});
|
|
2272
|
+
/**
|
|
2273
|
+
* Interleaved chat stream: `ChatMessage` appends and turn lifecycle events
|
|
2274
|
+
* arrive on a single subscription in emission order. Consumers that only
|
|
2275
|
+
* care about messages can ignore items with `kind: 'turn'`.
|
|
2276
|
+
*/
|
|
1797
2277
|
const waitForMessages = apiProcedure.input(z.object({
|
|
1798
2278
|
chatId: z.string().optional(),
|
|
1799
2279
|
lastMessageId: z.string().optional()
|
|
@@ -1802,10 +2282,16 @@ const waitForMessages = apiProcedure.input(z.object({
|
|
|
1802
2282
|
if (input.lastMessageId) {
|
|
1803
2283
|
const messages = await getMessages$1(chatId);
|
|
1804
2284
|
const lastIndex = messages.findIndex((m) => m.id === input.lastMessageId);
|
|
1805
|
-
if (lastIndex !== -1 && lastIndex < messages.length - 1) yield messages.slice(lastIndex + 1)
|
|
2285
|
+
if (lastIndex !== -1 && lastIndex < messages.length - 1) yield messages.slice(lastIndex + 1).map((message) => ({
|
|
2286
|
+
kind: "message",
|
|
2287
|
+
message
|
|
2288
|
+
}));
|
|
1806
2289
|
}
|
|
1807
2290
|
try {
|
|
1808
|
-
for await (const [
|
|
2291
|
+
for await (const [envelope] of on(daemonEvents, DAEMON_EVENT_CHAT_STREAM, { signal })) {
|
|
2292
|
+
const e = envelope;
|
|
2293
|
+
if (e.chatId === chatId) yield [e.item];
|
|
2294
|
+
}
|
|
1809
2295
|
} catch (err) {
|
|
1810
2296
|
if (err instanceof Error && err.name === "AbortError") return;
|
|
1811
2297
|
throw err;
|
|
@@ -1890,7 +2376,7 @@ var PolicyRequestService = class {
|
|
|
1890
2376
|
this.snapshotDir = snapshotDir;
|
|
1891
2377
|
this.maxPending = maxPending;
|
|
1892
2378
|
}
|
|
1893
|
-
async createRequest(commandName, args, fileMappings, chatId, agentId, skipSave = false, subagentId) {
|
|
2379
|
+
async createRequest(commandName, args, fileMappings, chatId, agentId, skipSave = false, subagentId, cwd) {
|
|
1894
2380
|
const allRequests = await this.store.list();
|
|
1895
2381
|
if (allRequests.filter((r) => r.state === "Pending").length >= this.maxPending) throw new Error(`Maximum number of pending requests (${this.maxPending}) reached.`);
|
|
1896
2382
|
const snapshotMappings = {};
|
|
@@ -1904,6 +2390,7 @@ var PolicyRequestService = class {
|
|
|
1904
2390
|
commandName,
|
|
1905
2391
|
args,
|
|
1906
2392
|
fileMappings: snapshotMappings,
|
|
2393
|
+
...cwd ? { cwd } : {},
|
|
1907
2394
|
state: skipSave ? "Approved" : "Pending",
|
|
1908
2395
|
createdAt: Date.now(),
|
|
1909
2396
|
chatId,
|
|
@@ -1918,6 +2405,174 @@ var PolicyRequestService = class {
|
|
|
1918
2405
|
}
|
|
1919
2406
|
};
|
|
1920
2407
|
|
|
2408
|
+
//#endregion
|
|
2409
|
+
//#region src/daemon/api/agent-policy-endpoints.ts
|
|
2410
|
+
const MAX_POLICY_SCRIPT_BYTES = 1 * 1024 * 1024;
|
|
2411
|
+
const MAX_INLINE_SCRIPT_LENGTH = 4e3;
|
|
2412
|
+
const listPolicies = apiProcedure.query(async ({ ctx }) => {
|
|
2413
|
+
const workspaceRoot = getWorkspaceRoot();
|
|
2414
|
+
return { policies: (await readPoliciesForPath(await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot), workspaceRoot))?.policies || {} };
|
|
2415
|
+
});
|
|
2416
|
+
const readPolicyScript = apiProcedure.input(z.object({ commandName: z.string() })).query(async ({ input, ctx }) => {
|
|
2417
|
+
const workspaceRoot = getWorkspaceRoot();
|
|
2418
|
+
const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
|
|
2419
|
+
const policy = (await readPoliciesForPath(agentDir, workspaceRoot))?.policies?.[input.commandName];
|
|
2420
|
+
if (!policy) throw new TRPCError({
|
|
2421
|
+
code: "NOT_FOUND",
|
|
2422
|
+
message: `Policy not found: ${input.commandName}`
|
|
2423
|
+
});
|
|
2424
|
+
const scriptsDir = path.join(getClawminiDir(), "policy-scripts");
|
|
2425
|
+
const resolvedCommand = path.resolve(policy.command);
|
|
2426
|
+
if (!pathIsInsideDir(resolvedCommand, scriptsDir, { allowSameDir: false })) throw new TRPCError({
|
|
2427
|
+
code: "BAD_REQUEST",
|
|
2428
|
+
message: `Policy '${input.commandName}' does not point at a script in policy-scripts/.`
|
|
2429
|
+
});
|
|
2430
|
+
let realCommand;
|
|
2431
|
+
let realScriptsDir;
|
|
2432
|
+
try {
|
|
2433
|
+
realCommand = await fsPromises.realpath(resolvedCommand);
|
|
2434
|
+
} catch (err) {
|
|
2435
|
+
throw new TRPCError({
|
|
2436
|
+
code: "NOT_FOUND",
|
|
2437
|
+
message: `Script file not found for policy '${input.commandName}': ${err instanceof Error ? err.message : String(err)}`
|
|
2438
|
+
});
|
|
2439
|
+
}
|
|
2440
|
+
try {
|
|
2441
|
+
realScriptsDir = await fsPromises.realpath(scriptsDir);
|
|
2442
|
+
} catch {
|
|
2443
|
+
throw new TRPCError({
|
|
2444
|
+
code: "BAD_REQUEST",
|
|
2445
|
+
message: `Policy '${input.commandName}' does not point at a script in policy-scripts/.`
|
|
2446
|
+
});
|
|
2447
|
+
}
|
|
2448
|
+
if (!pathIsInsideDir(realCommand, realScriptsDir, { allowSameDir: false })) throw new TRPCError({
|
|
2449
|
+
code: "BAD_REQUEST",
|
|
2450
|
+
message: `Policy '${input.commandName}' does not point at a script in policy-scripts/.`
|
|
2451
|
+
});
|
|
2452
|
+
let stat;
|
|
2453
|
+
try {
|
|
2454
|
+
stat = await fsPromises.stat(realCommand);
|
|
2455
|
+
} catch (err) {
|
|
2456
|
+
throw new TRPCError({
|
|
2457
|
+
code: "NOT_FOUND",
|
|
2458
|
+
message: `Script file not found for policy '${input.commandName}': ${err instanceof Error ? err.message : String(err)}`
|
|
2459
|
+
});
|
|
2460
|
+
}
|
|
2461
|
+
if (!stat.isFile()) throw new TRPCError({
|
|
2462
|
+
code: "BAD_REQUEST",
|
|
2463
|
+
message: `Script path for policy '${input.commandName}' is not a regular file.`
|
|
2464
|
+
});
|
|
2465
|
+
if (stat.size > MAX_POLICY_SCRIPT_BYTES) throw new TRPCError({
|
|
2466
|
+
code: "BAD_REQUEST",
|
|
2467
|
+
message: `Script file exceeds the ${MAX_POLICY_SCRIPT_BYTES}-byte limit.`
|
|
2468
|
+
});
|
|
2469
|
+
if (stat.size > MAX_INLINE_SCRIPT_LENGTH) {
|
|
2470
|
+
const tmpDir = path.join(agentDir, "tmp");
|
|
2471
|
+
await fsPromises.mkdir(tmpDir, { recursive: true });
|
|
2472
|
+
const ext = path.extname(realCommand);
|
|
2473
|
+
const safeName = input.commandName.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
2474
|
+
const destPath = path.join(tmpDir, `policy-script-${safeName}${ext}`);
|
|
2475
|
+
await fsPromises.copyFile(realCommand, destPath);
|
|
2476
|
+
return {
|
|
2477
|
+
path: realCommand,
|
|
2478
|
+
size: stat.size,
|
|
2479
|
+
spilledTo: `./tmp/policy-script-${safeName}${ext}`
|
|
2480
|
+
};
|
|
2481
|
+
}
|
|
2482
|
+
const content = await fsPromises.readFile(realCommand, "utf8");
|
|
2483
|
+
return {
|
|
2484
|
+
path: realCommand,
|
|
2485
|
+
size: stat.size,
|
|
2486
|
+
content
|
|
2487
|
+
};
|
|
2488
|
+
});
|
|
2489
|
+
const executePolicyHelp = apiProcedure.input(z.object({ commandName: z.string() })).query(async ({ input, ctx }) => {
|
|
2490
|
+
const workspaceRoot = getWorkspaceRoot();
|
|
2491
|
+
const policy = (await readPoliciesForPath(await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot), workspaceRoot))?.policies?.[input.commandName];
|
|
2492
|
+
if (!policy) throw new TRPCError({
|
|
2493
|
+
code: "NOT_FOUND",
|
|
2494
|
+
message: `Policy not found: ${input.commandName}`
|
|
2495
|
+
});
|
|
2496
|
+
if (!policy.allowHelp) return {
|
|
2497
|
+
stdout: "",
|
|
2498
|
+
stderr: "This command does not support --help\n",
|
|
2499
|
+
exitCode: 1
|
|
2500
|
+
};
|
|
2501
|
+
const fullArgs = [...policy.args || [], "--help"];
|
|
2502
|
+
const { stdout, stderr, exitCode } = await executeSafe(policy.command, fullArgs, { cwd: getWorkspaceRoot() });
|
|
2503
|
+
return {
|
|
2504
|
+
stdout,
|
|
2505
|
+
stderr,
|
|
2506
|
+
exitCode
|
|
2507
|
+
};
|
|
2508
|
+
});
|
|
2509
|
+
const createPolicyRequest = apiProcedure.input(z.object({
|
|
2510
|
+
commandName: z.string(),
|
|
2511
|
+
args: z.array(z.string()),
|
|
2512
|
+
fileMappings: z.record(z.string(), z.string()),
|
|
2513
|
+
cwd: z.string().optional()
|
|
2514
|
+
})).mutation(async ({ input, ctx }) => {
|
|
2515
|
+
if (!ctx.tokenPayload) throw new TRPCError({
|
|
2516
|
+
code: "UNAUTHORIZED",
|
|
2517
|
+
message: "Missing token"
|
|
2518
|
+
});
|
|
2519
|
+
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
2520
|
+
const snapshotDir = path.join(getClawminiDir(process.cwd()), "tmp", "snapshots");
|
|
2521
|
+
const store = new RequestStore(process.cwd());
|
|
2522
|
+
const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
|
|
2523
|
+
const service = new PolicyRequestService(store, agentDir, snapshotDir);
|
|
2524
|
+
const chatId = ctx.tokenPayload.chatId;
|
|
2525
|
+
const agentId = ctx.tokenPayload.agentId;
|
|
2526
|
+
const policy = (await readPoliciesForPath(agentDir, workspaceRoot))?.policies?.[input.commandName];
|
|
2527
|
+
if (!policy) throw new TRPCError({
|
|
2528
|
+
code: "NOT_FOUND",
|
|
2529
|
+
message: `Policy not found: ${input.commandName}`
|
|
2530
|
+
});
|
|
2531
|
+
const isAutoApprove = !!policy.autoApprove;
|
|
2532
|
+
const request = await service.createRequest(input.commandName, input.args, input.fileMappings, chatId, agentId, isAutoApprove, ctx.tokenPayload.subagentId, input.cwd);
|
|
2533
|
+
if (isAutoApprove) {
|
|
2534
|
+
const result = await executeRequest(request, policy, await resolveRequestCwd(request.cwd, agentId, workspaceRoot));
|
|
2535
|
+
const { exitCode, commandStr } = result;
|
|
2536
|
+
const { stdout, stderr } = await truncateLargeOutput(result.stdout, result.stderr, request.id, agentId);
|
|
2537
|
+
request.executionResult = {
|
|
2538
|
+
stdout,
|
|
2539
|
+
stderr,
|
|
2540
|
+
exitCode
|
|
2541
|
+
};
|
|
2542
|
+
await appendMessage(chatId, {
|
|
2543
|
+
id: randomUUID(),
|
|
2544
|
+
messageId: randomUUID(),
|
|
2545
|
+
role: "policy",
|
|
2546
|
+
requestId: request.id,
|
|
2547
|
+
commandName: input.commandName,
|
|
2548
|
+
args: input.args,
|
|
2549
|
+
status: "approved",
|
|
2550
|
+
content: `[Auto-approved] Policy ${input.commandName} was executed.\n\nCommand: ${commandStr}\nExit Code: ${exitCode}\n\nStdout:\n${stdout}\n\nStderr:\n${stderr}`,
|
|
2551
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2552
|
+
sessionId: ctx.tokenPayload.sessionId,
|
|
2553
|
+
...ctx.tokenPayload.subagentId ? { subagentId: ctx.tokenPayload.subagentId } : {},
|
|
2554
|
+
...ctx.tokenPayload.turnId ? { turnId: ctx.tokenPayload.turnId } : {}
|
|
2555
|
+
});
|
|
2556
|
+
return request;
|
|
2557
|
+
}
|
|
2558
|
+
const previewContent = await generateRequestPreview(request);
|
|
2559
|
+
await appendMessage(chatId, {
|
|
2560
|
+
id: randomUUID(),
|
|
2561
|
+
messageId: randomUUID(),
|
|
2562
|
+
role: "policy",
|
|
2563
|
+
requestId: request.id,
|
|
2564
|
+
commandName: input.commandName,
|
|
2565
|
+
args: input.args,
|
|
2566
|
+
status: "pending",
|
|
2567
|
+
content: previewContent,
|
|
2568
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2569
|
+
displayRole: "agent",
|
|
2570
|
+
sessionId: ctx.tokenPayload.sessionId,
|
|
2571
|
+
...ctx.tokenPayload.turnId ? { turnId: ctx.tokenPayload.turnId } : {}
|
|
2572
|
+
});
|
|
2573
|
+
return request;
|
|
2574
|
+
});
|
|
2575
|
+
|
|
1921
2576
|
//#endregion
|
|
1922
2577
|
//#region src/daemon/api/subagent-utils.ts
|
|
1923
2578
|
function getSubagentDepth(settings, parentId) {
|
|
@@ -1929,62 +2584,87 @@ function getSubagentDepth(settings, parentId) {
|
|
|
1929
2584
|
}
|
|
1930
2585
|
return depth;
|
|
1931
2586
|
}
|
|
2587
|
+
/**
|
|
2588
|
+
* Executes a subagent. Callers MUST have already called `incrementSubagent`
|
|
2589
|
+
* for the parent's turn synchronously before any `await` — this function's
|
|
2590
|
+
* `finally` block decrements, and we need the caller to increment earlier
|
|
2591
|
+
* so that a sibling's completing task cannot decrement the parent's counter
|
|
2592
|
+
* to zero (firing `turnEnded`) before this call's task is enqueued.
|
|
2593
|
+
*/
|
|
1932
2594
|
async function executeSubagent(chatId, subagentId, agentId, sessionId, prompt, isAsync, parentTokenPayload, workspaceRoot) {
|
|
2595
|
+
const parentTurnId = parentTokenPayload?.turnId;
|
|
1933
2596
|
try {
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
message: prompt,
|
|
1939
|
-
chatId,
|
|
1940
|
-
agentId,
|
|
1941
|
-
sessionId,
|
|
1942
|
-
env: {}
|
|
1943
|
-
};
|
|
1944
|
-
const initialState = { ...routerState };
|
|
1945
|
-
routerState = await executeRouterPipeline(routerState, resolvedRouters);
|
|
1946
|
-
await applyRouterStateUpdates(chatId, workspaceRoot, routerState, settings, initialState.agentId);
|
|
1947
|
-
await executeDirectMessage(chatId, routerState, void 0, workspaceRoot, false, void 0, subagentId);
|
|
1948
|
-
if (taskScheduler.hasTasks(sessionId)) return;
|
|
1949
|
-
await updateChatSettings(chatId, (finalSettings) => {
|
|
1950
|
-
if (finalSettings.subagents?.[subagentId]) finalSettings.subagents[subagentId].status = "completed";
|
|
1951
|
-
return finalSettings;
|
|
1952
|
-
});
|
|
1953
|
-
const logger = createChatLogger(chatId, subagentId);
|
|
1954
|
-
await logger.logSubagentStatus({
|
|
1955
|
-
subagentId,
|
|
1956
|
-
status: "completed"
|
|
1957
|
-
});
|
|
1958
|
-
if (isAsync) {
|
|
1959
|
-
const lastLogMessage = await logger.findLastMessage((m) => m.role === "agent" || m.displayRole === "agent");
|
|
1960
|
-
let outputContent = "";
|
|
1961
|
-
if (lastLogMessage && "content" in lastLogMessage) outputContent = `\n\n<subagent_output>\n${lastLogMessage.content}\n</subagent_output>`;
|
|
1962
|
-
console.log("Notifying parent", chatId, parentTokenPayload?.agentId, parentTokenPayload?.subagentId);
|
|
1963
|
-
await executeDirectMessage(chatId, {
|
|
2597
|
+
try {
|
|
2598
|
+
const settings = await readChatSettings(chatId) || {};
|
|
2599
|
+
const resolvedRouters = resolveRouters(settings.routers ?? [], false);
|
|
2600
|
+
let routerState = {
|
|
1964
2601
|
messageId: randomUUID(),
|
|
1965
|
-
message:
|
|
2602
|
+
message: prompt,
|
|
1966
2603
|
chatId,
|
|
1967
|
-
agentId
|
|
1968
|
-
|
|
1969
|
-
|
|
2604
|
+
agentId,
|
|
2605
|
+
sessionId,
|
|
2606
|
+
subagentId,
|
|
1970
2607
|
env: {}
|
|
1971
|
-
}
|
|
2608
|
+
};
|
|
2609
|
+
const initialState = { ...routerState };
|
|
2610
|
+
routerState = await executeRouterPipeline(routerState, resolvedRouters);
|
|
2611
|
+
await applyRouterStateUpdates(chatId, workspaceRoot, routerState, settings, initialState.agentId);
|
|
2612
|
+
await executeDirectMessage(chatId, routerState, void 0, workspaceRoot, false, void 0, subagentId, void 0, void 0, parentTurnId);
|
|
2613
|
+
if (taskScheduler.hasTasks(sessionId)) return;
|
|
2614
|
+
await updateChatSettings(chatId, (finalSettings) => {
|
|
2615
|
+
if (finalSettings.subagents?.[subagentId]) finalSettings.subagents[subagentId].status = "completed";
|
|
2616
|
+
return finalSettings;
|
|
2617
|
+
});
|
|
2618
|
+
const logger = createChatLogger(chatId, subagentId, sessionId, parentTurnId);
|
|
2619
|
+
await logger.logSubagentStatus({
|
|
2620
|
+
subagentId,
|
|
2621
|
+
status: "completed"
|
|
2622
|
+
});
|
|
2623
|
+
if (isAsync) {
|
|
2624
|
+
const lastLogMessage = await logger.findLastMessage((m) => m.role === "agent" || m.displayRole === "agent");
|
|
2625
|
+
let outputContent = "";
|
|
2626
|
+
if (lastLogMessage && "content" in lastLogMessage) outputContent = `\n\n<subagent_output>\n${lastLogMessage.content}\n</subagent_output>`;
|
|
2627
|
+
console.log("Notifying parent", chatId, parentTokenPayload?.agentId, parentTokenPayload?.subagentId);
|
|
2628
|
+
await executeDirectMessage(chatId, {
|
|
2629
|
+
messageId: randomUUID(),
|
|
2630
|
+
message: `<notification>Subagent ${subagentId} completed.</notification>${outputContent}`,
|
|
2631
|
+
chatId,
|
|
2632
|
+
agentId: parentTokenPayload?.agentId || "default",
|
|
2633
|
+
...parentTokenPayload?.subagentId ? { subagentId: parentTokenPayload.subagentId } : {},
|
|
2634
|
+
sessionId: parentTokenPayload?.sessionId || "default",
|
|
2635
|
+
env: {}
|
|
2636
|
+
}, void 0, workspaceRoot, true, void 0, parentTokenPayload?.subagentId, "subagent_update", void 0, parentTurnId);
|
|
2637
|
+
}
|
|
2638
|
+
} catch {
|
|
2639
|
+
await updateChatSettings(chatId, (errSettings) => {
|
|
2640
|
+
if (errSettings.subagents?.[subagentId]) errSettings.subagents[subagentId].status = "failed";
|
|
2641
|
+
return errSettings;
|
|
2642
|
+
});
|
|
2643
|
+
await createChatLogger(chatId, subagentId, sessionId, parentTurnId).logSubagentStatus({
|
|
2644
|
+
subagentId,
|
|
2645
|
+
status: "failed"
|
|
2646
|
+
});
|
|
1972
2647
|
}
|
|
1973
|
-
}
|
|
1974
|
-
|
|
1975
|
-
if (errSettings.subagents?.[subagentId]) errSettings.subagents[subagentId].status = "failed";
|
|
1976
|
-
return errSettings;
|
|
1977
|
-
});
|
|
1978
|
-
await createChatLogger(chatId, subagentId).logSubagentStatus({
|
|
1979
|
-
subagentId,
|
|
1980
|
-
status: "failed"
|
|
1981
|
-
});
|
|
2648
|
+
} finally {
|
|
2649
|
+
decrementSubagent(parentTurnId);
|
|
1982
2650
|
}
|
|
1983
2651
|
}
|
|
1984
2652
|
|
|
1985
2653
|
//#endregion
|
|
1986
2654
|
//#region src/daemon/api/subagent-router.ts
|
|
1987
2655
|
const MAX_SUBAGENT_DEPTH = 2;
|
|
2656
|
+
function assertSubagentAccess(settings, subagentId, callerSubagentId) {
|
|
2657
|
+
const sub = settings?.subagents?.[subagentId];
|
|
2658
|
+
if (!sub) throw new TRPCError({
|
|
2659
|
+
code: "NOT_FOUND",
|
|
2660
|
+
message: "Subagent not found"
|
|
2661
|
+
});
|
|
2662
|
+
if (sub.parentId !== callerSubagentId) throw new TRPCError({
|
|
2663
|
+
code: "FORBIDDEN",
|
|
2664
|
+
message: "Subagent is not a child of the caller"
|
|
2665
|
+
});
|
|
2666
|
+
return sub;
|
|
2667
|
+
}
|
|
1988
2668
|
const subagentSpawn = apiProcedure.input(z.object({
|
|
1989
2669
|
subagentId: z.string().optional(),
|
|
1990
2670
|
targetAgentId: z.string().optional(),
|
|
@@ -1998,39 +2678,47 @@ const subagentSpawn = apiProcedure.input(z.object({
|
|
|
1998
2678
|
const chatId = ctx.tokenPayload.chatId;
|
|
1999
2679
|
const parentAgentId = ctx.tokenPayload.agentId;
|
|
2000
2680
|
const parentId = ctx.tokenPayload.subagentId;
|
|
2681
|
+
const parentTurnId = ctx.tokenPayload.turnId;
|
|
2001
2682
|
const id = input.subagentId || randomUUID();
|
|
2002
2683
|
const sessionId = randomUUID();
|
|
2003
2684
|
const agentId = input.targetAgentId || parentAgentId;
|
|
2004
2685
|
let depth = 0;
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2686
|
+
incrementSubagent(parentTurnId);
|
|
2687
|
+
let handedOff = false;
|
|
2688
|
+
try {
|
|
2689
|
+
await updateChatSettings(chatId, (settings) => {
|
|
2690
|
+
settings.subagents = settings.subagents || {};
|
|
2691
|
+
depth = getSubagentDepth(settings, parentId);
|
|
2692
|
+
if (depth >= MAX_SUBAGENT_DEPTH) throw new TRPCError({
|
|
2693
|
+
code: "BAD_REQUEST",
|
|
2694
|
+
message: "Max subagent depth reached"
|
|
2695
|
+
});
|
|
2696
|
+
if (settings.subagents[id]) throw new TRPCError({
|
|
2697
|
+
code: "BAD_REQUEST",
|
|
2698
|
+
message: "Subagent ID already exists"
|
|
2699
|
+
});
|
|
2700
|
+
settings.subagents[id] = {
|
|
2701
|
+
id,
|
|
2702
|
+
agentId,
|
|
2703
|
+
sessionId,
|
|
2704
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2705
|
+
status: "active",
|
|
2706
|
+
parentId
|
|
2707
|
+
};
|
|
2708
|
+
return settings;
|
|
2015
2709
|
});
|
|
2016
|
-
|
|
2710
|
+
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
2711
|
+
const isAsync = input.async ?? depth === 0;
|
|
2712
|
+
handedOff = true;
|
|
2713
|
+
executeSubagent(chatId, id, agentId, sessionId, input.prompt, isAsync, ctx.tokenPayload, workspaceRoot);
|
|
2714
|
+
return {
|
|
2017
2715
|
id,
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2021
|
-
status: "active",
|
|
2022
|
-
parentId
|
|
2716
|
+
depth,
|
|
2717
|
+
isAsync
|
|
2023
2718
|
};
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
const isAsync = input.async ?? depth === 0;
|
|
2028
|
-
executeSubagent(chatId, id, agentId, sessionId, input.prompt, isAsync, ctx.tokenPayload, workspaceRoot);
|
|
2029
|
-
return {
|
|
2030
|
-
id,
|
|
2031
|
-
depth,
|
|
2032
|
-
isAsync
|
|
2033
|
-
};
|
|
2719
|
+
} finally {
|
|
2720
|
+
if (!handedOff) decrementSubagent(parentTurnId);
|
|
2721
|
+
}
|
|
2034
2722
|
});
|
|
2035
2723
|
const subagentSend = apiProcedure.input(z.object({
|
|
2036
2724
|
subagentId: z.string(),
|
|
@@ -2042,26 +2730,26 @@ const subagentSend = apiProcedure.input(z.object({
|
|
|
2042
2730
|
message: "Missing token"
|
|
2043
2731
|
});
|
|
2044
2732
|
const chatId = ctx.tokenPayload.chatId;
|
|
2733
|
+
const parentTurnId = ctx.tokenPayload.turnId;
|
|
2045
2734
|
let sub;
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2735
|
+
incrementSubagent(parentTurnId);
|
|
2736
|
+
let handedOff = false;
|
|
2737
|
+
try {
|
|
2738
|
+
await updateChatSettings(chatId, (settings) => {
|
|
2739
|
+
sub = assertSubagentAccess(settings, input.subagentId, ctx.tokenPayload.subagentId);
|
|
2740
|
+
sub.status = "active";
|
|
2741
|
+
return settings;
|
|
2050
2742
|
});
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2743
|
+
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
2744
|
+
handedOff = true;
|
|
2745
|
+
executeSubagent(chatId, sub.id, sub.agentId || "default", sub.sessionId || "default", input.prompt, input.async, ctx.tokenPayload, workspaceRoot);
|
|
2746
|
+
return { success: true };
|
|
2747
|
+
} finally {
|
|
2748
|
+
if (!handedOff) decrementSubagent(parentTurnId);
|
|
2749
|
+
}
|
|
2058
2750
|
});
|
|
2059
|
-
async function checkSubagentStatus(chatId, subagentId) {
|
|
2060
|
-
const sub = (await readChatSettings(chatId))
|
|
2061
|
-
if (!sub) throw new TRPCError({
|
|
2062
|
-
code: "NOT_FOUND",
|
|
2063
|
-
message: "Subagent not found"
|
|
2064
|
-
});
|
|
2751
|
+
async function checkSubagentStatus(chatId, subagentId, callerSubagentId) {
|
|
2752
|
+
const sub = assertSubagentAccess(await readChatSettings(chatId), subagentId, callerSubagentId);
|
|
2065
2753
|
if (sub.status === "completed" || sub.status === "failed") {
|
|
2066
2754
|
let outputContent;
|
|
2067
2755
|
if (sub.status === "completed") {
|
|
@@ -2090,7 +2778,7 @@ const subagentWait = apiProcedure.input(z.object({ subagentId: z.string() })).mu
|
|
|
2090
2778
|
if (signal) signal.addEventListener("abort", onAbort);
|
|
2091
2779
|
const eventIterator = on(daemonEvents, DAEMON_EVENT_MESSAGE_APPENDED, { signal: ac.signal });
|
|
2092
2780
|
try {
|
|
2093
|
-
const initialStatus = await checkSubagentStatus(chatId, input.subagentId);
|
|
2781
|
+
const initialStatus = await checkSubagentStatus(chatId, input.subagentId, ctx.tokenPayload.subagentId);
|
|
2094
2782
|
if (initialStatus) {
|
|
2095
2783
|
clearTimeout(timeout);
|
|
2096
2784
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
@@ -2098,7 +2786,7 @@ const subagentWait = apiProcedure.input(z.object({ subagentId: z.string() })).mu
|
|
|
2098
2786
|
}
|
|
2099
2787
|
for await (const [event] of eventIterator) if (event.chatId === chatId && event.message?.subagentId === input.subagentId) {
|
|
2100
2788
|
if (event.message.role === "subagent_status") {
|
|
2101
|
-
const status = await checkSubagentStatus(chatId, input.subagentId);
|
|
2789
|
+
const status = await checkSubagentStatus(chatId, input.subagentId, ctx.tokenPayload.subagentId);
|
|
2102
2790
|
if (status) {
|
|
2103
2791
|
clearTimeout(timeout);
|
|
2104
2792
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
@@ -2130,13 +2818,9 @@ const subagentStop = apiProcedure.input(z.object({ subagentId: z.string() })).mu
|
|
|
2130
2818
|
const chatId = ctx.tokenPayload.chatId;
|
|
2131
2819
|
let subToStop;
|
|
2132
2820
|
await updateChatSettings(chatId, (settings) => {
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
sub.status = "failed";
|
|
2137
|
-
subToStop = sub;
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2821
|
+
const sub = assertSubagentAccess(settings, input.subagentId, ctx.tokenPayload.subagentId);
|
|
2822
|
+
sub.status = "failed";
|
|
2823
|
+
subToStop = sub;
|
|
2140
2824
|
return settings;
|
|
2141
2825
|
});
|
|
2142
2826
|
if (subToStop) (await createAgentSession({
|
|
@@ -2156,10 +2840,8 @@ const subagentDelete = apiProcedure.input(z.object({ subagentId: z.string() })).
|
|
|
2156
2840
|
const chatId = ctx.tokenPayload.chatId;
|
|
2157
2841
|
let subToDelete;
|
|
2158
2842
|
await updateChatSettings(chatId, (settings) => {
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
delete settings.subagents[input.subagentId];
|
|
2162
|
-
}
|
|
2843
|
+
subToDelete = assertSubagentAccess(settings, input.subagentId, ctx.tokenPayload.subagentId);
|
|
2844
|
+
delete settings.subagents[input.subagentId];
|
|
2163
2845
|
return settings;
|
|
2164
2846
|
});
|
|
2165
2847
|
if (subToDelete) {
|
|
@@ -2204,6 +2886,7 @@ const subagentTail = apiProcedure.input(z.object({
|
|
|
2204
2886
|
message: "Missing token"
|
|
2205
2887
|
});
|
|
2206
2888
|
const chatId = ctx.tokenPayload.chatId;
|
|
2889
|
+
assertSubagentAccess(await readChatSettings(chatId), input.subagentId, ctx.tokenPayload.subagentId);
|
|
2207
2890
|
return { messages: await createChatLogger(chatId, input.subagentId).getMessages(input.limit) };
|
|
2208
2891
|
});
|
|
2209
2892
|
|
|
@@ -2241,7 +2924,9 @@ const logMessage = apiProcedure.input(z.object({
|
|
|
2241
2924
|
command: `clawmini-lite log${filesArgStr}`,
|
|
2242
2925
|
cwd: process.cwd(),
|
|
2243
2926
|
exitCode: 0,
|
|
2927
|
+
sessionId: ctx.tokenPayload.sessionId,
|
|
2244
2928
|
...ctx.tokenPayload.subagentId ? { subagentId: ctx.tokenPayload.subagentId } : {},
|
|
2929
|
+
...ctx.tokenPayload.turnId ? { turnId: ctx.tokenPayload.turnId } : {},
|
|
2245
2930
|
...filePaths.length > 0 ? { files: filePaths } : {}
|
|
2246
2931
|
});
|
|
2247
2932
|
return { success: true };
|
|
@@ -2271,7 +2956,9 @@ const logReplyMessage = apiProcedure.input(z.object({
|
|
|
2271
2956
|
role: "agent",
|
|
2272
2957
|
content: input.message,
|
|
2273
2958
|
timestamp,
|
|
2959
|
+
sessionId: ctx.tokenPayload.sessionId,
|
|
2274
2960
|
...ctx.tokenPayload.subagentId ? { subagentId: ctx.tokenPayload.subagentId } : {},
|
|
2961
|
+
...ctx.tokenPayload.turnId ? { turnId: ctx.tokenPayload.turnId } : {},
|
|
2275
2962
|
...filePaths.length > 0 ? { files: filePaths } : {}
|
|
2276
2963
|
});
|
|
2277
2964
|
return { success: true };
|
|
@@ -2304,7 +2991,9 @@ const logToolMessage = apiProcedure.input(z.object({
|
|
|
2304
2991
|
payload: payloadObj,
|
|
2305
2992
|
content: contentStr,
|
|
2306
2993
|
timestamp,
|
|
2307
|
-
|
|
2994
|
+
sessionId: ctx.tokenPayload.sessionId,
|
|
2995
|
+
...ctx.tokenPayload.subagentId ? { subagentId: ctx.tokenPayload.subagentId } : {},
|
|
2996
|
+
...ctx.tokenPayload.turnId ? { turnId: ctx.tokenPayload.turnId } : {}
|
|
2308
2997
|
});
|
|
2309
2998
|
return { success: true };
|
|
2310
2999
|
});
|
|
@@ -2316,15 +3005,34 @@ const agentListCronJobs = apiProcedure.query(async ({ ctx }) => {
|
|
|
2316
3005
|
const chatId = ctx.tokenPayload.chatId;
|
|
2317
3006
|
return listCronJobsShared(chatId);
|
|
2318
3007
|
});
|
|
2319
|
-
const
|
|
3008
|
+
const AgentCronJobInputSchema = z.strictObject({
|
|
3009
|
+
id: z.string().min(1),
|
|
3010
|
+
message: z.string().default(""),
|
|
3011
|
+
reply: z.string().optional(),
|
|
3012
|
+
session: z.union([z.strictObject({ type: z.literal("new") }), z.strictObject({
|
|
3013
|
+
type: z.literal("existing"),
|
|
3014
|
+
id: z.string()
|
|
3015
|
+
})]).optional(),
|
|
3016
|
+
schedule: z.union([
|
|
3017
|
+
z.strictObject({ cron: z.string() }),
|
|
3018
|
+
z.strictObject({ every: z.string() }),
|
|
3019
|
+
z.strictObject({ at: z.string() })
|
|
3020
|
+
])
|
|
3021
|
+
});
|
|
3022
|
+
const agentAddCronJob = apiProcedure.input(z.object({ job: AgentCronJobInputSchema })).mutation(async ({ input, ctx }) => {
|
|
2320
3023
|
if (!ctx.tokenPayload) throw new TRPCError({
|
|
2321
3024
|
code: "UNAUTHORIZED",
|
|
2322
3025
|
message: "Missing token"
|
|
2323
3026
|
});
|
|
2324
3027
|
const chatId = ctx.tokenPayload.chatId;
|
|
2325
3028
|
return addCronJobShared(chatId, {
|
|
2326
|
-
|
|
2327
|
-
|
|
3029
|
+
id: input.job.id,
|
|
3030
|
+
message: input.job.message,
|
|
3031
|
+
schedule: input.job.schedule,
|
|
3032
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3033
|
+
agentId: ctx.tokenPayload.agentId,
|
|
3034
|
+
...input.job.reply !== void 0 ? { reply: input.job.reply } : {},
|
|
3035
|
+
...input.job.session !== void 0 ? { session: input.job.session } : {}
|
|
2328
3036
|
});
|
|
2329
3037
|
});
|
|
2330
3038
|
const agentDeleteCronJob = apiProcedure.input(z.object({ id: z.string() })).mutation(async ({ input, ctx }) => {
|
|
@@ -2335,87 +3043,6 @@ const agentDeleteCronJob = apiProcedure.input(z.object({ id: z.string() })).muta
|
|
|
2335
3043
|
const chatId = ctx.tokenPayload.chatId;
|
|
2336
3044
|
return deleteCronJobShared(chatId, input.id);
|
|
2337
3045
|
});
|
|
2338
|
-
const listPolicies = apiProcedure.query(async () => {
|
|
2339
|
-
return await readPolicies();
|
|
2340
|
-
});
|
|
2341
|
-
const executePolicyHelp = apiProcedure.input(z.object({ commandName: z.string() })).query(async ({ input }) => {
|
|
2342
|
-
const policy = (await readPolicies())?.policies?.[input.commandName];
|
|
2343
|
-
if (!policy) throw new TRPCError({
|
|
2344
|
-
code: "NOT_FOUND",
|
|
2345
|
-
message: `Policy not found: ${input.commandName}`
|
|
2346
|
-
});
|
|
2347
|
-
if (!policy.allowHelp) return {
|
|
2348
|
-
stdout: "",
|
|
2349
|
-
stderr: "This command does not support --help\n",
|
|
2350
|
-
exitCode: 1
|
|
2351
|
-
};
|
|
2352
|
-
const fullArgs = [...policy.args || [], "--help"];
|
|
2353
|
-
const { stdout, stderr, exitCode } = await executeSafe(policy.command, fullArgs, { cwd: getWorkspaceRoot() });
|
|
2354
|
-
return {
|
|
2355
|
-
stdout,
|
|
2356
|
-
stderr,
|
|
2357
|
-
exitCode
|
|
2358
|
-
};
|
|
2359
|
-
});
|
|
2360
|
-
const createPolicyRequest = apiProcedure.input(z.object({
|
|
2361
|
-
commandName: z.string(),
|
|
2362
|
-
args: z.array(z.string()),
|
|
2363
|
-
fileMappings: z.record(z.string(), z.string())
|
|
2364
|
-
})).mutation(async ({ input, ctx }) => {
|
|
2365
|
-
if (!ctx.tokenPayload) throw new TRPCError({
|
|
2366
|
-
code: "UNAUTHORIZED",
|
|
2367
|
-
message: "Missing token"
|
|
2368
|
-
});
|
|
2369
|
-
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
2370
|
-
const snapshotDir = path.join(getClawminiDir(process.cwd()), "tmp", "snapshots");
|
|
2371
|
-
const store = new RequestStore(process.cwd());
|
|
2372
|
-
const service = new PolicyRequestService(store, await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot), snapshotDir);
|
|
2373
|
-
const chatId = ctx.tokenPayload.chatId;
|
|
2374
|
-
const agentId = ctx.tokenPayload.agentId;
|
|
2375
|
-
const policy = (await readPolicies())?.policies?.[input.commandName];
|
|
2376
|
-
if (!policy) throw new TRPCError({
|
|
2377
|
-
code: "NOT_FOUND",
|
|
2378
|
-
message: `Policy not found: ${input.commandName}`
|
|
2379
|
-
});
|
|
2380
|
-
const isAutoApprove = !!policy.autoApprove;
|
|
2381
|
-
const request = await service.createRequest(input.commandName, input.args, input.fileMappings, chatId, agentId, isAutoApprove, ctx.tokenPayload.subagentId);
|
|
2382
|
-
if (isAutoApprove) {
|
|
2383
|
-
const { stdout, stderr, exitCode, commandStr } = await executeRequest(request, policy, getWorkspaceRoot());
|
|
2384
|
-
request.executionResult = {
|
|
2385
|
-
stdout,
|
|
2386
|
-
stderr,
|
|
2387
|
-
exitCode
|
|
2388
|
-
};
|
|
2389
|
-
await store.save(request);
|
|
2390
|
-
await appendMessage(chatId, {
|
|
2391
|
-
id: randomUUID(),
|
|
2392
|
-
messageId: randomUUID(),
|
|
2393
|
-
role: "policy",
|
|
2394
|
-
requestId: request.id,
|
|
2395
|
-
commandName: input.commandName,
|
|
2396
|
-
args: input.args,
|
|
2397
|
-
status: "approved",
|
|
2398
|
-
content: `[Auto-approved] Policy ${input.commandName} was executed.\n\nCommand: ${commandStr}\nExit Code: ${exitCode}\n\nStdout:\n${stdout}\n\nStderr:\n${stderr}`,
|
|
2399
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2400
|
-
...ctx.tokenPayload.subagentId ? { subagentId: ctx.tokenPayload.subagentId } : {}
|
|
2401
|
-
});
|
|
2402
|
-
return request;
|
|
2403
|
-
}
|
|
2404
|
-
const previewContent = await generateRequestPreview(request);
|
|
2405
|
-
await appendMessage(chatId, {
|
|
2406
|
-
id: randomUUID(),
|
|
2407
|
-
messageId: randomUUID(),
|
|
2408
|
-
role: "policy",
|
|
2409
|
-
requestId: request.id,
|
|
2410
|
-
commandName: input.commandName,
|
|
2411
|
-
args: input.args,
|
|
2412
|
-
status: "pending",
|
|
2413
|
-
content: previewContent,
|
|
2414
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2415
|
-
displayRole: "agent"
|
|
2416
|
-
});
|
|
2417
|
-
return request;
|
|
2418
|
-
});
|
|
2419
3046
|
const fetchPendingMessages = apiProcedure.mutation(async ({ ctx }) => {
|
|
2420
3047
|
if (!ctx.tokenPayload?.agentId) throw new TRPCError({
|
|
2421
3048
|
code: "UNAUTHORIZED",
|
|
@@ -2436,6 +3063,7 @@ const agentRouter = router({
|
|
|
2436
3063
|
listPolicies,
|
|
2437
3064
|
executePolicyHelp,
|
|
2438
3065
|
createPolicyRequest,
|
|
3066
|
+
readPolicyScript,
|
|
2439
3067
|
fetchPendingMessages,
|
|
2440
3068
|
ping,
|
|
2441
3069
|
subagentSpawn,
|
|
@@ -2482,8 +3110,7 @@ async function initDaemon() {
|
|
|
2482
3110
|
env: {
|
|
2483
3111
|
...process.env,
|
|
2484
3112
|
ENV_DIR: envDir
|
|
2485
|
-
}
|
|
2486
|
-
timeout: hookType === "down" ? 1e4 : void 0
|
|
3113
|
+
}
|
|
2487
3114
|
});
|
|
2488
3115
|
}
|
|
2489
3116
|
} catch (err) {
|
|
@@ -2557,9 +3184,20 @@ async function initDaemon() {
|
|
|
2557
3184
|
}
|
|
2558
3185
|
};
|
|
2559
3186
|
await cleanOrphanedSubagents();
|
|
3187
|
+
try {
|
|
3188
|
+
const removed = await new RequestStore(getWorkspaceRoot()).cleanupCompleted();
|
|
3189
|
+
if (removed > 0) console.log(`Cleaned up ${removed} completed policy request file(s).`);
|
|
3190
|
+
} catch (err) {
|
|
3191
|
+
console.warn("Failed to clean completed policy requests:", err);
|
|
3192
|
+
}
|
|
2560
3193
|
await runHooks("up");
|
|
2561
3194
|
isReady = true;
|
|
2562
3195
|
readyPromiseResolve();
|
|
3196
|
+
try {
|
|
3197
|
+
await drainPendingReplies(getClawminiVersion());
|
|
3198
|
+
} catch (err) {
|
|
3199
|
+
console.warn("Failed to drain pending replies:", err);
|
|
3200
|
+
}
|
|
2563
3201
|
cronManager.init().catch((err) => {
|
|
2564
3202
|
console.error("Failed to initialize cron manager:", err);
|
|
2565
3203
|
});
|