clawmini 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.changeset/README.md +8 -0
- package/.changeset/config.json +14 -0
- package/.github/workflows/release.yml +49 -0
- package/CHANGELOG.md +36 -0
- package/README.md +5 -4
- package/dist/adapter-discord/index.d.mts.map +1 -1
- package/dist/adapter-discord/index.mjs +465 -282
- package/dist/adapter-discord/index.mjs.map +1 -1
- package/dist/adapter-google-chat/index.mjs +367 -243
- package/dist/adapter-google-chat/index.mjs.map +1 -1
- package/dist/cli/index.mjs +684 -24
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lite.mjs +43 -13
- package/dist/cli/lite.mjs.map +1 -1
- package/dist/cli/{propose-policy.mjs → manage-policies.mjs} +270 -47
- package/dist/cli/manage-policies.mjs.map +1 -0
- package/dist/cli/run-host.d.mts +1 -0
- package/dist/cli/run-host.mjs +3090 -0
- package/dist/cli/run-host.mjs.map +1 -0
- package/dist/config-CPFQIGdG.mjs +57 -0
- package/dist/config-CPFQIGdG.mjs.map +1 -0
- package/dist/config-Dvl-Pov4.mjs +76 -0
- package/dist/config-Dvl-Pov4.mjs.map +1 -0
- package/dist/daemon/index.d.mts.map +1 -1
- package/dist/daemon/index.mjs +970 -332
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/supervisor-actions-CiW56eLi.mjs +843 -0
- package/dist/supervisor-actions-CiW56eLi.mjs.map +1 -0
- package/dist/turn-log-buffer-DRgW53gl.mjs +767 -0
- package/dist/turn-log-buffer-DRgW53gl.mjs.map +1 -0
- package/dist/web/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
- package/dist/web/_app/immutable/chunks/BhRSsUCh.js +2 -0
- package/dist/web/_app/immutable/chunks/BiLeM2i1.js +1 -0
- package/{web/.svelte-kit/output/client/_app/immutable/chunks/CME08kGM.js → dist/web/_app/immutable/chunks/BmBj85Ll.js} +1 -1
- package/dist/web/_app/immutable/chunks/BrERcKAH.js +1 -0
- package/dist/web/_app/immutable/chunks/Bv9252RM.js +1 -0
- package/dist/web/_app/immutable/chunks/CIXNBPKi.js +1 -0
- package/dist/web/_app/immutable/chunks/DISKL3GN.js +2 -0
- package/dist/web/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
- package/dist/web/_app/immutable/chunks/DnQ3vS13.js +1 -0
- package/dist/web/_app/immutable/chunks/KsloHTKS.js +1 -0
- package/{web/.svelte-kit/output/client/_app/immutable/chunks/Ck-be5J2.js → dist/web/_app/immutable/chunks/RsHsUj-8.js} +2 -2
- package/dist/web/_app/immutable/chunks/{G_zz-Gou.js → wpfV79dV.js} +1 -1
- package/dist/web/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
- package/dist/web/_app/immutable/entry/start.Di0-Jhte.js +1 -0
- package/dist/web/_app/immutable/nodes/{0.CYS8iApT.js → 0.DYyUA1au.js} +1 -1
- package/dist/web/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
- package/dist/web/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
- package/{web/.svelte-kit/output/client/_app/immutable/nodes/3.Dr0ot9sV.js → dist/web/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
- package/dist/web/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
- package/dist/web/_app/immutable/nodes/{5.BBGQ_i84.js → 5.DLbPVJY2.js} +1 -1
- package/dist/web/_app/version.json +1 -1
- package/dist/web/index.html +12 -12
- package/dist/workspace-oWmVh5mi.mjs +1001 -0
- package/dist/workspace-oWmVh5mi.mjs.map +1 -0
- package/docs/23_adapter_slash_autocomplete/development_log.md +19 -0
- package/docs/23_adapter_slash_autocomplete/notes.md +18 -0
- package/docs/23_adapter_slash_autocomplete/prd.md +46 -0
- package/docs/23_adapter_slash_autocomplete/questions.md +6 -0
- package/docs/23_adapter_slash_autocomplete/tickets.md +21 -0
- package/docs/24_subagent_job_policy_fixes/development_log.md +22 -0
- package/docs/24_subagent_job_policy_fixes/notes.md +28 -0
- package/docs/24_subagent_job_policy_fixes/prd.md +59 -0
- package/docs/24_subagent_job_policy_fixes/questions.md +3 -0
- package/docs/24_subagent_job_policy_fixes/tickets.md +49 -0
- package/docs/25_e2e_test_improvements/development_log.md +30 -0
- package/docs/25_e2e_test_improvements/notes.md +29 -0
- package/docs/25_e2e_test_improvements/prd.md +43 -0
- package/docs/25_e2e_test_improvements/questions.md +12 -0
- package/docs/25_e2e_test_improvements/tickets-2.md +22 -0
- package/docs/25_e2e_test_improvements/tickets.md +22 -0
- package/docs/25_policy_cwd/development_log.md +30 -0
- package/docs/25_policy_cwd/notes.md +28 -0
- package/docs/25_policy_cwd/prd.md +77 -0
- package/docs/25_policy_cwd/questions.md +6 -0
- package/docs/25_policy_cwd/tickets.md +77 -0
- package/docs/CLI_REFERENCE.md +3 -1
- package/docs/PHILOSOPHY.md +35 -0
- package/docs/adapter-visibility/SPEC.md +461 -0
- package/docs/adapter-visibility/SPEC_v2.md +202 -0
- package/docs/auto-update/SPEC.md +344 -0
- package/docs/backups/SPEC.md +296 -0
- package/docs/backups/clawmini.gitignore +69 -0
- package/docs/guides/assets/clawmini-avatar.png +0 -0
- package/docs/guides/backups.md +332 -0
- package/docs/guides/discord_adapter_setup.md +1 -1
- package/docs/guides/google_chat_adapter_setup.md +81 -0
- package/docs/unified-startup/SPEC.md +203 -0
- package/e2e/_helpers/test-environment.test.ts +49 -0
- package/e2e/_helpers/test-environment.ts +548 -0
- package/e2e/adapters/_google-chat-fixtures.ts +340 -0
- package/{src/cli/e2e → e2e/adapters}/adapter-discord.test.ts +22 -23
- package/e2e/adapters/adapter-google-chat-downtime.test.ts +157 -0
- package/e2e/adapters/adapter-google-chat-inbound.test.ts +697 -0
- package/e2e/adapters/adapter-google-chat-outbound.test.ts +297 -0
- package/e2e/adapters/adapter-google-chat-roundtrip.test.ts +56 -0
- package/e2e/adapters/adapter-google-chat-threads.test.ts +1078 -0
- package/e2e/agents/custom-api-env.test.ts +80 -0
- package/e2e/agents/export-lite-func.test.ts +104 -0
- package/e2e/agents/fallbacks.test.ts +124 -0
- package/e2e/agents/interrupt.test.ts +50 -0
- package/e2e/agents/no-reply-necessary.test.ts +57 -0
- package/e2e/agents/session-timeout-subagents.test.ts +76 -0
- package/e2e/agents/subagent-authorization.test.ts +246 -0
- package/e2e/agents/subagent-env.test.ts +49 -0
- package/e2e/agents/subagent-lifecycle.test.ts +782 -0
- package/e2e/agents/subagents-depth.test.ts +47 -0
- package/e2e/cli/agents.test.ts +176 -0
- package/e2e/cli/auto-update.test.ts +741 -0
- package/e2e/cli/basic.test.ts +44 -0
- package/{src/cli/e2e → e2e/cli}/export-lite.test.ts +16 -12
- package/e2e/cli/init-gitignore.test.ts +86 -0
- package/e2e/cli/init.test.ts +76 -0
- package/e2e/cli/messages.test.ts +363 -0
- package/e2e/cli/serve.test.ts +76 -0
- package/{src/cli/e2e → e2e/cli}/skills.test.ts +11 -10
- package/{src/cli/e2e → e2e/daemon}/daemon.test.ts +57 -195
- package/e2e/jobs/agent-jobs.test.ts +216 -0
- package/e2e/jobs/cron.test.ts +64 -0
- package/e2e/jobs/restart.test.ts +108 -0
- package/e2e/policies/approval-session.test.ts +69 -0
- package/e2e/policies/auto-create-policies-file.test.ts +35 -0
- package/e2e/policies/builtin-manage-policies.test.ts +184 -0
- package/e2e/policies/builtin-run-host.test.ts +180 -0
- package/e2e/policies/environment-policies.test.ts +177 -0
- package/e2e/policies/manage-policies.test.ts +566 -0
- package/e2e/policies/output-size.test.ts +98 -0
- package/e2e/policies/policies-context-cwd.test.ts +160 -0
- package/e2e/policies/relative-script-path.test.ts +60 -0
- package/e2e/policies/requests-show.test.ts +135 -0
- package/e2e/policies/requests.test.ts +208 -0
- package/e2e/policies/slash-policies.test.ts +308 -0
- package/e2e/policies/startup-cleanup.test.ts +48 -0
- package/e2e/routers/session-timeout.test.ts +106 -0
- package/e2e/routers/slash-model.test.ts +152 -0
- package/e2e/routers/slash-new.test.ts +50 -0
- package/e2e/routers/slash-restart-adapter.test.ts +96 -0
- package/e2e/routers/slash-restart.test.ts +114 -0
- package/e2e/routers/slash-shutdown.test.ts +55 -0
- package/e2e/routers/slash-stop.test.ts +232 -0
- package/e2e/routers/slash-upgrade.test.ts +88 -0
- package/{src/cli/e2e → e2e/sandbox}/environments.test.ts +14 -13
- package/eslint.config.js +6 -0
- package/napkin.md +1 -1
- package/package.json +8 -3
- package/src/adapter-discord/commands.test.ts +42 -0
- package/src/adapter-discord/commands.ts +33 -0
- package/src/adapter-discord/config.ts +12 -0
- package/src/adapter-discord/forwarder.test.ts +499 -21
- package/src/adapter-discord/forwarder.ts +343 -124
- package/src/adapter-discord/inbound-cache.test.ts +47 -0
- package/src/adapter-discord/inbound-cache.ts +37 -0
- package/src/adapter-discord/index.test.ts +67 -2
- package/src/adapter-discord/index.ts +84 -216
- package/src/adapter-discord/interactions.test.ts +54 -3
- package/src/adapter-discord/interactions.ts +97 -53
- package/src/adapter-discord/processMessage.ts +239 -0
- package/src/adapter-discord/state.ts +1 -0
- package/src/adapter-google-chat/auth.test.ts +9 -5
- package/src/adapter-google-chat/auth.ts +29 -23
- package/src/adapter-google-chat/cards.ts +7 -2
- package/src/adapter-google-chat/client.test.ts +37 -2
- package/src/adapter-google-chat/client.ts +138 -38
- package/src/adapter-google-chat/config.ts +19 -0
- package/src/adapter-google-chat/forwarder.test.ts +81 -56
- package/src/adapter-google-chat/forwarder.ts +394 -185
- package/src/adapter-google-chat/inbound-cache.test.ts +61 -0
- package/src/adapter-google-chat/inbound-cache.ts +36 -0
- package/src/adapter-google-chat/state.test.ts +1 -0
- package/src/adapter-google-chat/state.ts +9 -1
- package/src/adapter-google-chat/subscriptions.ts +8 -6
- package/src/cli/builtin-policies.ts +44 -0
- package/src/cli/commands/agents.ts +59 -5
- package/src/cli/commands/down.ts +54 -2
- package/src/cli/commands/environments.ts +8 -2
- package/src/cli/commands/init.ts +31 -0
- package/src/cli/commands/logs.ts +116 -0
- package/src/cli/commands/policies.ts +6 -4
- package/src/cli/commands/serve.test.ts +67 -0
- package/src/cli/commands/serve.ts +284 -0
- package/src/cli/commands/up.ts +122 -2
- package/src/cli/commands/web-api/agents.ts +3 -2
- package/src/cli/index.ts +4 -0
- package/src/cli/install-detection.test.ts +72 -0
- package/src/cli/install-detection.ts +48 -0
- package/src/cli/lite.ts +54 -22
- package/src/cli/manage-policies-utils.ts +104 -0
- package/src/cli/manage-policies.ts +291 -0
- package/src/cli/run-host.ts +45 -0
- package/src/cli/supervisor-actions.ts +267 -0
- package/src/cli/supervisor-control.test.ts +129 -0
- package/src/cli/supervisor-control.ts +155 -0
- package/src/cli/supervisor-pid.ts +68 -0
- package/src/cli/supervisor.ts +277 -0
- package/src/daemon/agent/agent-context.ts +11 -11
- package/src/daemon/agent/agent-session.ts +8 -1
- package/src/daemon/agent/chat-logger.test.ts +78 -9
- package/src/daemon/agent/chat-logger.ts +25 -5
- package/src/daemon/agent/turn-registry.test.ts +89 -0
- package/src/daemon/agent/turn-registry.ts +94 -0
- package/src/daemon/agent/types.ts +2 -0
- package/src/daemon/api/agent-policy-endpoints.ts +263 -0
- package/src/daemon/api/agent-router.ts +47 -126
- package/src/daemon/api/index.test.ts +1 -0
- package/src/daemon/api/policy-request.test.ts +7 -5
- package/src/daemon/api/router-utils.ts +6 -5
- package/src/daemon/api/subagent-router.ts +110 -74
- package/src/daemon/api/subagent-utils.test.ts +60 -0
- package/src/daemon/api/subagent-utils.ts +113 -87
- package/src/daemon/api/user-router.ts +34 -8
- package/src/daemon/auth.ts +1 -0
- package/src/daemon/cron.test.ts +62 -4
- package/src/daemon/cron.ts +42 -16
- package/src/daemon/events.ts +65 -0
- package/src/daemon/index.ts +24 -1
- package/src/daemon/message-interruption.test.ts +1 -0
- package/src/daemon/message-jobs.test.ts +1 -0
- package/src/daemon/message.ts +78 -14
- package/src/daemon/observation.test.ts +26 -18
- package/src/daemon/pending-replies.test.ts +112 -0
- package/src/daemon/pending-replies.ts +162 -0
- package/src/daemon/policy-request-service.ts +3 -1
- package/src/daemon/policy-utils.test.ts +66 -1
- package/src/daemon/policy-utils.ts +126 -1
- package/src/daemon/request-store.ts +31 -0
- package/src/daemon/routers/session-timeout.ts +4 -0
- package/src/daemon/routers/slash-model.test.ts +344 -0
- package/src/daemon/routers/slash-model.ts +207 -0
- package/src/daemon/routers/slash-policies.test.ts +38 -32
- package/src/daemon/routers/slash-policies.ts +84 -33
- package/src/daemon/routers/slash-restart.test.ts +69 -0
- package/src/daemon/routers/slash-restart.ts +36 -0
- package/src/daemon/routers/slash-shutdown.test.ts +50 -0
- package/src/daemon/routers/slash-shutdown.ts +28 -0
- package/src/daemon/routers/slash-upgrade.test.ts +116 -0
- package/src/daemon/routers/slash-upgrade.ts +76 -0
- package/src/daemon/routers/types.ts +7 -0
- package/src/daemon/routers.ts +16 -0
- package/src/shared/adapters/blockquote.test.ts +28 -0
- package/src/shared/adapters/blockquote.ts +20 -0
- package/src/shared/adapters/filtering.test.ts +224 -10
- package/src/shared/adapters/filtering.ts +95 -7
- package/src/shared/adapters/inbound-cache.test.ts +48 -0
- package/src/shared/adapters/inbound-cache.ts +54 -0
- package/src/shared/adapters/turn-log-buffer.ts +266 -0
- package/src/shared/adapters/turn-log.test.ts +389 -0
- package/src/shared/adapters/turn-log.ts +357 -0
- package/src/shared/agent-utils.ts +12 -5
- package/src/shared/chats.test.ts +4 -0
- package/src/shared/chats.ts +9 -0
- package/src/shared/config.ts +16 -1
- package/src/shared/lite.ts +76 -2
- package/src/shared/policies.ts +26 -0
- package/src/shared/template-manifest.ts +267 -0
- package/src/shared/utils/shell.ts +61 -0
- package/src/shared/version.ts +34 -0
- package/src/shared/workspace.test.ts +217 -0
- package/src/shared/workspace.ts +626 -48
- package/templates/environments/cladding/allowlist-domain.mjs +125 -0
- package/templates/environments/cladding/env.json +21 -1
- package/templates/environments/cladding/run-with-network.mjs +54 -0
- package/templates/environments/macos-proxy/allowlist-domain.mjs +95 -0
- package/templates/environments/macos-proxy/env.json +8 -1
- package/templates/environments/macos-proxy/proxy.mjs +42 -13
- package/templates/gemini/template.json +5 -0
- package/templates/gemini-claw/template.json +13 -0
- package/templates/skills/clawmini-requests/SKILL.md +69 -10
- package/templates/skills/run-host/SKILL.md +51 -0
- package/templates/skills/skill-creator/SKILL.md +4 -3
- package/templates/skills/skill-creator/scripts/validate.sh +52 -0
- package/tsdown.config.ts +10 -1
- package/vitest.config.ts +2 -2
- package/web/.svelte-kit/ambient.d.ts +292 -176
- package/web/.svelte-kit/generated/server/internal.js +1 -1
- package/web/.svelte-kit/output/client/.vite/manifest.json +127 -137
- package/web/.svelte-kit/output/client/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/BhRSsUCh.js +2 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/BiLeM2i1.js +1 -0
- package/{dist/web/_app/immutable/chunks/CME08kGM.js → web/.svelte-kit/output/client/_app/immutable/chunks/BmBj85Ll.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/BrERcKAH.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/Bv9252RM.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/CIXNBPKi.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/DISKL3GN.js +2 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/DnQ3vS13.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/KsloHTKS.js +1 -0
- package/{dist/web/_app/immutable/chunks/Ck-be5J2.js → web/.svelte-kit/output/client/_app/immutable/chunks/RsHsUj-8.js} +2 -2
- package/web/.svelte-kit/output/client/_app/immutable/chunks/{G_zz-Gou.js → wpfV79dV.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
- package/web/.svelte-kit/output/client/_app/immutable/entry/start.Di0-Jhte.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/nodes/{0.CYS8iApT.js → 0.DYyUA1au.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
- package/{dist/web/_app/immutable/nodes/3.Dr0ot9sV.js → web/.svelte-kit/output/client/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
- package/web/.svelte-kit/output/client/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
- package/web/.svelte-kit/output/client/_app/immutable/nodes/{5.BBGQ_i84.js → 5.DLbPVJY2.js} +1 -1
- package/web/.svelte-kit/output/client/_app/version.json +1 -1
- package/web/.svelte-kit/output/server/.vite/manifest.json +12 -10
- package/web/.svelte-kit/output/server/chunks/Icon.js +1 -1
- package/web/.svelte-kit/output/server/chunks/client.js +1 -1
- package/web/.svelte-kit/output/server/chunks/exports.js +1 -1
- package/web/.svelte-kit/output/server/chunks/index-server.js +2 -1
- package/web/.svelte-kit/output/server/chunks/internal.js +1 -1
- package/web/.svelte-kit/output/server/chunks/render-context.js +77 -0
- package/web/.svelte-kit/output/server/chunks/root.js +739 -788
- package/web/.svelte-kit/output/server/chunks/shared.js +234 -21
- package/web/.svelte-kit/output/server/index.js +126 -90
- package/web/.svelte-kit/output/server/manifest-full.js +1 -1
- package/web/.svelte-kit/output/server/manifest.js +1 -1
- package/web/.svelte-kit/output/server/nodes/0.js +1 -1
- package/web/.svelte-kit/output/server/nodes/1.js +1 -1
- package/web/.svelte-kit/output/server/nodes/2.js +1 -1
- package/web/.svelte-kit/output/server/nodes/3.js +1 -1
- package/web/.svelte-kit/output/server/nodes/4.js +1 -1
- package/web/.svelte-kit/output/server/nodes/5.js +1 -1
- package/web/.svelte-kit/output/server/remote-entry.js +245 -81
- package/web/.svelte-kit/tsconfig.json +4 -1
- package/dist/cli/propose-policy.mjs.map +0 -1
- package/dist/lite-CBxOT1y5.mjs +0 -241
- package/dist/lite-CBxOT1y5.mjs.map +0 -1
- package/dist/routing-D8rTxtaV.mjs +0 -245
- package/dist/routing-D8rTxtaV.mjs.map +0 -1
- package/dist/web/_app/immutable/chunks/B6YN0Nuq.js +0 -1
- package/dist/web/_app/immutable/chunks/BmRlVmv6.js +0 -1
- package/dist/web/_app/immutable/chunks/CK9JZLaG.js +0 -2
- package/dist/web/_app/immutable/chunks/Ck3rYNON.js +0 -1
- package/dist/web/_app/immutable/chunks/DMtIqaiV.js +0 -2
- package/dist/web/_app/immutable/chunks/DhD271EB.js +0 -1
- package/dist/web/_app/immutable/chunks/DpuLqk8d.js +0 -1
- package/dist/web/_app/immutable/chunks/DsIToJCP.js +0 -1
- package/dist/web/_app/immutable/chunks/bBmtyQMj.js +0 -1
- package/dist/web/_app/immutable/entry/app.CJmSwntr.js +0 -2
- package/dist/web/_app/immutable/entry/start.ZpUrT2ak.js +0 -1
- package/dist/web/_app/immutable/nodes/1.Bli0Hqzn.js +0 -1
- package/dist/web/_app/immutable/nodes/4.oBhvQhcA.js +0 -60
- package/dist/workspace-BJmJBfKi.mjs +0 -456
- package/dist/workspace-BJmJBfKi.mjs.map +0 -1
- package/src/cli/e2e/agents.test.ts +0 -140
- package/src/cli/e2e/basic.test.ts +0 -43
- package/src/cli/e2e/cron.test.ts +0 -132
- package/src/cli/e2e/export-lite-func.test.ts +0 -206
- package/src/cli/e2e/fallbacks.test.ts +0 -175
- package/src/cli/e2e/init.test.ts +0 -77
- package/src/cli/e2e/messages.test.ts +0 -332
- package/src/cli/e2e/propose-policy.test.ts +0 -203
- package/src/cli/e2e/requests.test.ts +0 -180
- package/src/cli/e2e/session-timeout.test.ts +0 -192
- package/src/cli/e2e/slash-new.test.ts +0 -93
- package/src/cli/e2e/subagents.test.ts +0 -106
- package/src/cli/e2e/utils.ts +0 -66
- package/src/cli/propose-policy.ts +0 -91
- package/web/.svelte-kit/output/client/_app/immutable/chunks/B6YN0Nuq.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/BmRlVmv6.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/CK9JZLaG.js +0 -2
- package/web/.svelte-kit/output/client/_app/immutable/chunks/Ck3rYNON.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/DMtIqaiV.js +0 -2
- package/web/.svelte-kit/output/client/_app/immutable/chunks/DhD271EB.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/DpuLqk8d.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/DsIToJCP.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/bBmtyQMj.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/entry/app.CJmSwntr.js +0 -2
- package/web/.svelte-kit/output/client/_app/immutable/entry/start.ZpUrT2ak.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/1.Bli0Hqzn.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/4.oBhvQhcA.js +0 -60
- package/web/.svelte-kit/output/server/chunks/false.js +0 -4
- /package/dist/cli/{propose-policy.d.mts → manage-policies.d.mts} +0 -0
- /package/{src/cli/e2e → e2e/_helpers}/global-setup.ts +0 -0
|
@@ -11,7 +11,9 @@ vi.mock('../../shared/chats.js', () => ({
|
|
|
11
11
|
vi.mock('../../shared/workspace.js', () => ({
|
|
12
12
|
getWorkspaceRoot: vi.fn().mockReturnValue('/mock/workspace'),
|
|
13
13
|
getClawminiDir: vi.fn().mockReturnValue('/mock/.clawmini'),
|
|
14
|
-
|
|
14
|
+
getActiveEnvironmentInfo: vi.fn().mockResolvedValue(null),
|
|
15
|
+
readEnvironment: vi.fn().mockResolvedValue(null),
|
|
16
|
+
readPoliciesForPath: vi.fn().mockResolvedValue({
|
|
15
17
|
policies: {
|
|
16
18
|
'test-cmd': {
|
|
17
19
|
command: 'echo',
|
|
@@ -30,7 +32,7 @@ vi.mock('../policy-request-service.js', () => {
|
|
|
30
32
|
PolicyRequestService: class {
|
|
31
33
|
async createRequest() {
|
|
32
34
|
return {
|
|
33
|
-
id: '
|
|
35
|
+
id: 'REQ-123',
|
|
34
36
|
commandName: 'test-cmd',
|
|
35
37
|
args: ['arg1', 'arg2'],
|
|
36
38
|
fileMappings: {
|
|
@@ -101,7 +103,7 @@ describe('createPolicyRequest preview message', () => {
|
|
|
101
103
|
},
|
|
102
104
|
});
|
|
103
105
|
|
|
104
|
-
expect(result.id).toBe('
|
|
106
|
+
expect(result.id).toBe('REQ-123');
|
|
105
107
|
|
|
106
108
|
expect(chats.appendMessage).toHaveBeenCalledTimes(1);
|
|
107
109
|
const callArgs = vi.mocked(chats.appendMessage).mock.calls[0]!;
|
|
@@ -114,14 +116,14 @@ describe('createPolicyRequest preview message', () => {
|
|
|
114
116
|
// Assert preview content format
|
|
115
117
|
const content = logMsg.content;
|
|
116
118
|
expect(content).toContain('Sandbox Policy Request: test-cmd');
|
|
117
|
-
expect(content).toContain('ID:
|
|
119
|
+
expect(content).toContain('ID: REQ-123');
|
|
118
120
|
expect(content).toContain('Args: arg1 arg2');
|
|
119
121
|
|
|
120
122
|
expect(content).toContain('File [file1]:\n' + shortContent);
|
|
121
123
|
|
|
122
124
|
// The long file should be truncated to 500 chars + suffix
|
|
123
125
|
expect(content).toContain('File [file2]:\n' + 'A'.repeat(500) + '\n... (truncated)');
|
|
124
|
-
expect(content).toContain('Use /approve
|
|
126
|
+
expect(content).toContain('Use /approve REQ-123 or /reject REQ-123 [reason]');
|
|
125
127
|
});
|
|
126
128
|
|
|
127
129
|
it('should create an auto-approved request and execute it immediately', async () => {
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
readChatSettings,
|
|
9
9
|
writeChatSettings,
|
|
10
10
|
} from '../../shared/workspace.js';
|
|
11
|
-
import { cronManager } from '../cron.js';
|
|
11
|
+
import { cronManager, normalizeJob } from '../cron.js';
|
|
12
12
|
import type { z } from 'zod';
|
|
13
13
|
import type { CronJobSchema } from '../../shared/config.js';
|
|
14
14
|
|
|
@@ -129,17 +129,18 @@ export async function listCronJobsShared(chatId: string) {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
export async function addCronJobShared(chatId: string, job: z.infer<typeof CronJobSchema>) {
|
|
132
|
+
const normalized = normalizeJob(job);
|
|
132
133
|
const settings = (await readChatSettings(chatId)) || {};
|
|
133
134
|
const cronJobs = settings.jobs ?? [];
|
|
134
|
-
const existingIndex = cronJobs.findIndex((j) => j.id ===
|
|
135
|
+
const existingIndex = cronJobs.findIndex((j) => j.id === normalized.id);
|
|
135
136
|
if (existingIndex >= 0) {
|
|
136
|
-
cronJobs[existingIndex] =
|
|
137
|
+
cronJobs[existingIndex] = normalized;
|
|
137
138
|
} else {
|
|
138
|
-
cronJobs.push(
|
|
139
|
+
cronJobs.push(normalized);
|
|
139
140
|
}
|
|
140
141
|
settings.jobs = cronJobs;
|
|
141
142
|
await writeChatSettings(chatId, settings);
|
|
142
|
-
cronManager.scheduleJob(chatId,
|
|
143
|
+
cronManager.scheduleJob(chatId, normalized);
|
|
143
144
|
return { success: true };
|
|
144
145
|
}
|
|
145
146
|
|
|
@@ -8,10 +8,24 @@ import { on } from 'node:events';
|
|
|
8
8
|
import { daemonEvents, DAEMON_EVENT_MESSAGE_APPENDED } from '../events.js';
|
|
9
9
|
import { createAgentSession } from '../agent/agent-session.js';
|
|
10
10
|
import { executeSubagent, getSubagentDepth } from './subagent-utils.js';
|
|
11
|
-
import
|
|
11
|
+
import { incrementSubagent, decrementSubagent } from '../agent/turn-registry.js';
|
|
12
|
+
import type { ChatSettings, SubagentTracker } from '../../shared/config.js';
|
|
12
13
|
|
|
13
14
|
const MAX_SUBAGENT_DEPTH = 2;
|
|
14
15
|
|
|
16
|
+
function assertSubagentAccess(
|
|
17
|
+
settings: ChatSettings | null | undefined,
|
|
18
|
+
subagentId: string,
|
|
19
|
+
callerSubagentId: string | undefined
|
|
20
|
+
): SubagentTracker {
|
|
21
|
+
const sub = settings?.subagents?.[subagentId];
|
|
22
|
+
if (!sub) throw new TRPCError({ code: 'NOT_FOUND', message: 'Subagent not found' });
|
|
23
|
+
if (sub.parentId !== callerSubagentId) {
|
|
24
|
+
throw new TRPCError({ code: 'FORBIDDEN', message: 'Subagent is not a child of the caller' });
|
|
25
|
+
}
|
|
26
|
+
return sub;
|
|
27
|
+
}
|
|
28
|
+
|
|
15
29
|
export const subagentSpawn = apiProcedure
|
|
16
30
|
.input(
|
|
17
31
|
z.object({
|
|
@@ -26,54 +40,65 @@ export const subagentSpawn = apiProcedure
|
|
|
26
40
|
const chatId = ctx.tokenPayload.chatId;
|
|
27
41
|
const parentAgentId = ctx.tokenPayload.agentId;
|
|
28
42
|
const parentId = ctx.tokenPayload.subagentId;
|
|
43
|
+
const parentTurnId = ctx.tokenPayload.turnId;
|
|
29
44
|
|
|
30
45
|
const id = input.subagentId || randomUUID();
|
|
31
46
|
const sessionId = randomUUID();
|
|
32
47
|
const agentId = input.targetAgentId || parentAgentId;
|
|
33
48
|
let depth = 0;
|
|
34
49
|
|
|
35
|
-
await
|
|
36
|
-
|
|
50
|
+
// Increment synchronously before any await so a sibling subagent's
|
|
51
|
+
// completion cannot decrement the parent's counter to zero (firing
|
|
52
|
+
// turnEnded) during the window before executeSubagent is called.
|
|
53
|
+
incrementSubagent(parentTurnId);
|
|
54
|
+
let handedOff = false;
|
|
55
|
+
try {
|
|
56
|
+
await updateChatSettings(chatId, (settings) => {
|
|
57
|
+
settings.subagents = settings.subagents || {};
|
|
37
58
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
59
|
+
depth = getSubagentDepth(settings, parentId);
|
|
60
|
+
if (depth >= MAX_SUBAGENT_DEPTH) {
|
|
61
|
+
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Max subagent depth reached' });
|
|
62
|
+
}
|
|
42
63
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
64
|
+
// Make sure the id does not already exist
|
|
65
|
+
if (settings.subagents[id]) {
|
|
66
|
+
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Subagent ID already exists' });
|
|
67
|
+
}
|
|
47
68
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
69
|
+
settings.subagents[id] = {
|
|
70
|
+
id,
|
|
71
|
+
agentId,
|
|
72
|
+
sessionId,
|
|
73
|
+
createdAt: new Date().toISOString(),
|
|
74
|
+
status: 'active',
|
|
75
|
+
parentId,
|
|
76
|
+
};
|
|
56
77
|
|
|
57
|
-
|
|
58
|
-
|
|
78
|
+
return settings;
|
|
79
|
+
});
|
|
59
80
|
|
|
60
|
-
|
|
81
|
+
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
61
82
|
|
|
62
|
-
|
|
83
|
+
const isAsync = input.async ?? depth === 0;
|
|
63
84
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
85
|
+
// Execute asynchronously — executeSubagent's finally decrements.
|
|
86
|
+
handedOff = true;
|
|
87
|
+
executeSubagent(
|
|
88
|
+
chatId,
|
|
89
|
+
id,
|
|
90
|
+
agentId,
|
|
91
|
+
sessionId,
|
|
92
|
+
input.prompt,
|
|
93
|
+
isAsync,
|
|
94
|
+
ctx.tokenPayload,
|
|
95
|
+
workspaceRoot
|
|
96
|
+
);
|
|
75
97
|
|
|
76
|
-
|
|
98
|
+
return { id, depth, isAsync };
|
|
99
|
+
} finally {
|
|
100
|
+
if (!handedOff) decrementSubagent(parentTurnId);
|
|
101
|
+
}
|
|
77
102
|
});
|
|
78
103
|
|
|
79
104
|
export const subagentSend = apiProcedure
|
|
@@ -87,40 +112,46 @@ export const subagentSend = apiProcedure
|
|
|
87
112
|
.mutation(async ({ input, ctx }) => {
|
|
88
113
|
if (!ctx.tokenPayload) throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Missing token' });
|
|
89
114
|
const chatId = ctx.tokenPayload.chatId;
|
|
115
|
+
const parentTurnId = ctx.tokenPayload.turnId;
|
|
90
116
|
|
|
91
117
|
let sub: SubagentTracker | undefined;
|
|
92
118
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
119
|
+
incrementSubagent(parentTurnId);
|
|
120
|
+
let handedOff = false;
|
|
121
|
+
try {
|
|
122
|
+
await updateChatSettings(chatId, (settings) => {
|
|
123
|
+
sub = assertSubagentAccess(settings, input.subagentId, ctx.tokenPayload!.subagentId);
|
|
124
|
+
sub.status = 'active';
|
|
125
|
+
return settings;
|
|
126
|
+
});
|
|
104
127
|
|
|
105
|
-
|
|
106
|
-
executeSubagent(
|
|
107
|
-
chatId,
|
|
108
|
-
sub!.id,
|
|
109
|
-
sub!.agentId || 'default',
|
|
110
|
-
sub!.sessionId || 'default',
|
|
111
|
-
input.prompt,
|
|
112
|
-
input.async,
|
|
113
|
-
ctx.tokenPayload,
|
|
114
|
-
workspaceRoot
|
|
115
|
-
);
|
|
128
|
+
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
116
129
|
|
|
117
|
-
|
|
130
|
+
handedOff = true;
|
|
131
|
+
executeSubagent(
|
|
132
|
+
chatId,
|
|
133
|
+
sub!.id,
|
|
134
|
+
sub!.agentId || 'default',
|
|
135
|
+
sub!.sessionId || 'default',
|
|
136
|
+
input.prompt,
|
|
137
|
+
input.async,
|
|
138
|
+
ctx.tokenPayload,
|
|
139
|
+
workspaceRoot
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
return { success: true };
|
|
143
|
+
} finally {
|
|
144
|
+
if (!handedOff) decrementSubagent(parentTurnId);
|
|
145
|
+
}
|
|
118
146
|
});
|
|
119
147
|
|
|
120
|
-
async function checkSubagentStatus(
|
|
148
|
+
async function checkSubagentStatus(
|
|
149
|
+
chatId: string,
|
|
150
|
+
subagentId: string,
|
|
151
|
+
callerSubagentId: string | undefined
|
|
152
|
+
) {
|
|
121
153
|
const settings = await readChatSettings(chatId);
|
|
122
|
-
const sub = settings
|
|
123
|
-
if (!sub) throw new TRPCError({ code: 'NOT_FOUND', message: 'Subagent not found' });
|
|
154
|
+
const sub = assertSubagentAccess(settings, subagentId, callerSubagentId);
|
|
124
155
|
|
|
125
156
|
if (sub.status === 'completed' || sub.status === 'failed') {
|
|
126
157
|
let outputContent: string | undefined;
|
|
@@ -160,7 +191,11 @@ export const subagentWait = apiProcedure
|
|
|
160
191
|
|
|
161
192
|
try {
|
|
162
193
|
// Check status immediately before listening, but after event iterator is buffering
|
|
163
|
-
const initialStatus = await checkSubagentStatus(
|
|
194
|
+
const initialStatus = await checkSubagentStatus(
|
|
195
|
+
chatId,
|
|
196
|
+
input.subagentId,
|
|
197
|
+
ctx.tokenPayload.subagentId
|
|
198
|
+
);
|
|
164
199
|
if (initialStatus) {
|
|
165
200
|
clearTimeout(timeout);
|
|
166
201
|
if (signal) signal.removeEventListener('abort', onAbort);
|
|
@@ -171,7 +206,11 @@ export const subagentWait = apiProcedure
|
|
|
171
206
|
if (event.chatId === chatId && event.message?.subagentId === input.subagentId) {
|
|
172
207
|
const msg = event.message;
|
|
173
208
|
if (msg.role === 'subagent_status') {
|
|
174
|
-
const status = await checkSubagentStatus(
|
|
209
|
+
const status = await checkSubagentStatus(
|
|
210
|
+
chatId,
|
|
211
|
+
input.subagentId,
|
|
212
|
+
ctx.tokenPayload.subagentId
|
|
213
|
+
);
|
|
175
214
|
if (status) {
|
|
176
215
|
clearTimeout(timeout);
|
|
177
216
|
if (signal) signal.removeEventListener('abort', onAbort);
|
|
@@ -203,13 +242,9 @@ export const subagentStop = apiProcedure
|
|
|
203
242
|
let subToStop: SubagentTracker | undefined;
|
|
204
243
|
|
|
205
244
|
await updateChatSettings(chatId, (settings) => {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
sub.status = 'failed';
|
|
210
|
-
subToStop = sub;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
245
|
+
const sub = assertSubagentAccess(settings, input.subagentId, ctx.tokenPayload!.subagentId);
|
|
246
|
+
sub.status = 'failed';
|
|
247
|
+
subToStop = sub;
|
|
213
248
|
return settings;
|
|
214
249
|
});
|
|
215
250
|
|
|
@@ -236,10 +271,8 @@ export const subagentDelete = apiProcedure
|
|
|
236
271
|
let subToDelete: SubagentTracker | undefined;
|
|
237
272
|
|
|
238
273
|
await updateChatSettings(chatId, (settings) => {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
delete settings.subagents[input.subagentId];
|
|
242
|
-
}
|
|
274
|
+
subToDelete = assertSubagentAccess(settings, input.subagentId, ctx.tokenPayload!.subagentId);
|
|
275
|
+
delete settings.subagents![input.subagentId];
|
|
243
276
|
return settings;
|
|
244
277
|
});
|
|
245
278
|
|
|
@@ -289,6 +322,9 @@ export const subagentTail = apiProcedure
|
|
|
289
322
|
if (!ctx.tokenPayload) throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Missing token' });
|
|
290
323
|
const chatId = ctx.tokenPayload.chatId;
|
|
291
324
|
|
|
325
|
+
const settings = await readChatSettings(chatId);
|
|
326
|
+
assertSubagentAccess(settings, input.subagentId, ctx.tokenPayload.subagentId);
|
|
327
|
+
|
|
292
328
|
const logger = createChatLogger(chatId, input.subagentId);
|
|
293
329
|
const messages = await logger.getMessages(input.limit);
|
|
294
330
|
|
|
@@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
2
2
|
import { executeSubagent } from './subagent-utils.js';
|
|
3
3
|
import * as workspace from '../../shared/workspace.js';
|
|
4
4
|
import { taskScheduler } from '../agent/task-scheduler.js';
|
|
5
|
+
import * as turnRegistry from '../agent/turn-registry.js';
|
|
5
6
|
|
|
6
7
|
vi.mock('../../shared/workspace.js', () => ({
|
|
7
8
|
updateChatSettings: vi.fn(),
|
|
@@ -22,6 +23,7 @@ vi.mock('../agent/chat-logger.js', () => ({
|
|
|
22
23
|
createChatLogger: vi.fn(() => ({
|
|
23
24
|
findLastMessage: vi.fn().mockResolvedValue(null),
|
|
24
25
|
logSystemEvent: vi.fn(),
|
|
26
|
+
logSubagentStatus: vi.fn(),
|
|
25
27
|
})),
|
|
26
28
|
}));
|
|
27
29
|
|
|
@@ -31,6 +33,11 @@ vi.mock('../agent/task-scheduler.js', () => ({
|
|
|
31
33
|
},
|
|
32
34
|
}));
|
|
33
35
|
|
|
36
|
+
vi.mock('../agent/turn-registry.js', () => ({
|
|
37
|
+
incrementSubagent: vi.fn(),
|
|
38
|
+
decrementSubagent: vi.fn(),
|
|
39
|
+
}));
|
|
40
|
+
|
|
34
41
|
describe('executeSubagent', () => {
|
|
35
42
|
beforeEach(() => {
|
|
36
43
|
vi.clearAllMocks();
|
|
@@ -53,4 +60,57 @@ describe('executeSubagent', () => {
|
|
|
53
60
|
// Should NOT call updateChatSettings to set status to completed
|
|
54
61
|
expect(workspace.updateChatSettings).not.toHaveBeenCalled();
|
|
55
62
|
});
|
|
63
|
+
|
|
64
|
+
it('should not increment the parent turn counter — that is the caller-s responsibility', async () => {
|
|
65
|
+
vi.mocked(taskScheduler.hasTasks).mockReturnValue(false);
|
|
66
|
+
|
|
67
|
+
await executeSubagent(
|
|
68
|
+
'chat-1',
|
|
69
|
+
'sub-1',
|
|
70
|
+
'agent-1',
|
|
71
|
+
'session-1',
|
|
72
|
+
'hello',
|
|
73
|
+
false,
|
|
74
|
+
{ agentId: 'parent-agent', turnId: 'turn-1' },
|
|
75
|
+
'/workspace'
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
expect(turnRegistry.incrementSubagent).not.toHaveBeenCalled();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should decrement the parent turn counter exactly once on early return', async () => {
|
|
82
|
+
vi.mocked(taskScheduler.hasTasks).mockReturnValue(true);
|
|
83
|
+
|
|
84
|
+
await executeSubagent(
|
|
85
|
+
'chat-1',
|
|
86
|
+
'sub-1',
|
|
87
|
+
'agent-1',
|
|
88
|
+
'session-1',
|
|
89
|
+
'hello',
|
|
90
|
+
false,
|
|
91
|
+
{ agentId: 'parent-agent', turnId: 'turn-1' },
|
|
92
|
+
'/workspace'
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
expect(turnRegistry.decrementSubagent).toHaveBeenCalledTimes(1);
|
|
96
|
+
expect(turnRegistry.decrementSubagent).toHaveBeenCalledWith('turn-1');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should decrement the parent turn counter exactly once on normal completion', async () => {
|
|
100
|
+
vi.mocked(taskScheduler.hasTasks).mockReturnValue(false);
|
|
101
|
+
|
|
102
|
+
await executeSubagent(
|
|
103
|
+
'chat-1',
|
|
104
|
+
'sub-1',
|
|
105
|
+
'agent-1',
|
|
106
|
+
'session-1',
|
|
107
|
+
'hello',
|
|
108
|
+
false,
|
|
109
|
+
{ agentId: 'parent-agent', turnId: 'turn-1' },
|
|
110
|
+
'/workspace'
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect(turnRegistry.decrementSubagent).toHaveBeenCalledTimes(1);
|
|
114
|
+
expect(turnRegistry.decrementSubagent).toHaveBeenCalledWith('turn-1');
|
|
115
|
+
});
|
|
56
116
|
});
|
|
@@ -6,6 +6,7 @@ import type { RouterState } from '../routers/types.js';
|
|
|
6
6
|
import { createChatLogger } from '../agent/chat-logger.js';
|
|
7
7
|
import type { ChatSettings } from '../../shared/config.js';
|
|
8
8
|
import { taskScheduler } from '../agent/task-scheduler.js';
|
|
9
|
+
import { decrementSubagent } from '../agent/turn-registry.js';
|
|
9
10
|
|
|
10
11
|
export function getSubagentDepth(settings: ChatSettings, parentId: string | undefined): number {
|
|
11
12
|
let depth = 0;
|
|
@@ -17,6 +18,13 @@ export function getSubagentDepth(settings: ChatSettings, parentId: string | unde
|
|
|
17
18
|
return depth;
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Executes a subagent. Callers MUST have already called `incrementSubagent`
|
|
23
|
+
* for the parent's turn synchronously before any `await` — this function's
|
|
24
|
+
* `finally` block decrements, and we need the caller to increment earlier
|
|
25
|
+
* so that a sibling's completing task cannot decrement the parent's counter
|
|
26
|
+
* to zero (firing `turnEnded`) before this call's task is enqueued.
|
|
27
|
+
*/
|
|
20
28
|
export async function executeSubagent(
|
|
21
29
|
chatId: string,
|
|
22
30
|
subagentId: string,
|
|
@@ -24,107 +32,125 @@ export async function executeSubagent(
|
|
|
24
32
|
sessionId: string,
|
|
25
33
|
prompt: string,
|
|
26
34
|
isAsync: boolean | undefined,
|
|
27
|
-
parentTokenPayload: {
|
|
35
|
+
parentTokenPayload: {
|
|
36
|
+
agentId?: string;
|
|
37
|
+
subagentId?: string;
|
|
38
|
+
sessionId?: string;
|
|
39
|
+
turnId?: string;
|
|
40
|
+
},
|
|
28
41
|
workspaceRoot: string
|
|
29
42
|
) {
|
|
43
|
+
const parentTurnId = parentTokenPayload?.turnId;
|
|
30
44
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
45
|
+
try {
|
|
46
|
+
const settings = (await readChatSettings(chatId)) || {};
|
|
47
|
+
const routers = settings.routers ?? [];
|
|
48
|
+
const resolvedRouters = resolveRouters(routers, false);
|
|
34
49
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const initialState = { ...routerState };
|
|
45
|
-
routerState = await executeRouterPipeline(routerState, resolvedRouters);
|
|
50
|
+
let routerState: RouterState = {
|
|
51
|
+
messageId: randomUUID(),
|
|
52
|
+
message: prompt,
|
|
53
|
+
chatId,
|
|
54
|
+
agentId,
|
|
55
|
+
sessionId,
|
|
56
|
+
subagentId,
|
|
57
|
+
env: {},
|
|
58
|
+
};
|
|
46
59
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
workspaceRoot,
|
|
50
|
-
routerState,
|
|
51
|
-
settings,
|
|
52
|
-
initialState.agentId
|
|
53
|
-
);
|
|
60
|
+
const initialState = { ...routerState };
|
|
61
|
+
routerState = await executeRouterPipeline(routerState, resolvedRouters);
|
|
54
62
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
subagentId // subagentId
|
|
63
|
-
);
|
|
63
|
+
await applyRouterStateUpdates(
|
|
64
|
+
chatId,
|
|
65
|
+
workspaceRoot,
|
|
66
|
+
routerState,
|
|
67
|
+
settings,
|
|
68
|
+
initialState.agentId
|
|
69
|
+
);
|
|
64
70
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
await executeDirectMessage(
|
|
72
|
+
chatId,
|
|
73
|
+
routerState,
|
|
74
|
+
undefined, // settings
|
|
75
|
+
workspaceRoot,
|
|
76
|
+
false, // noWait
|
|
77
|
+
undefined, // userMessageContent
|
|
78
|
+
subagentId, // subagentId
|
|
79
|
+
undefined, // systemEvent
|
|
80
|
+
undefined, // displayRole
|
|
81
|
+
parentTurnId // parentTurnId — inherit from parent agent's turn
|
|
82
|
+
);
|
|
68
83
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (finalSettings.subagents?.[subagentId]) {
|
|
72
|
-
finalSettings.subagents[subagentId]!.status = 'completed';
|
|
84
|
+
if (taskScheduler.hasTasks(sessionId)) {
|
|
85
|
+
return;
|
|
73
86
|
}
|
|
74
|
-
return finalSettings;
|
|
75
|
-
});
|
|
76
87
|
|
|
77
|
-
|
|
88
|
+
// Update status
|
|
89
|
+
await updateChatSettings(chatId, (finalSettings) => {
|
|
90
|
+
if (finalSettings.subagents?.[subagentId]) {
|
|
91
|
+
finalSettings.subagents[subagentId]!.status = 'completed';
|
|
92
|
+
}
|
|
93
|
+
return finalSettings;
|
|
94
|
+
});
|
|
78
95
|
|
|
79
|
-
|
|
80
|
-
await logger.logSubagentStatus({ subagentId, status: 'completed' });
|
|
96
|
+
const logger = createChatLogger(chatId, subagentId, sessionId, parentTurnId);
|
|
81
97
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
(m) => m.role === 'agent' || m.displayRole === 'agent'
|
|
85
|
-
);
|
|
86
|
-
let outputContent = '';
|
|
87
|
-
if (lastLogMessage && 'content' in lastLogMessage) {
|
|
88
|
-
outputContent = `\n\n<subagent_output>\n${lastLogMessage.content}\n</subagent_output>`;
|
|
89
|
-
}
|
|
98
|
+
// Emit debug message to wake up waiters
|
|
99
|
+
await logger.logSubagentStatus({ subagentId, status: 'completed' });
|
|
90
100
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
{
|
|
103
|
-
messageId: randomUUID(),
|
|
104
|
-
message: `<notification>Subagent ${subagentId} completed.</notification>${outputContent}`,
|
|
101
|
+
if (isAsync) {
|
|
102
|
+
const lastLogMessage = await logger.findLastMessage(
|
|
103
|
+
(m) => m.role === 'agent' || m.displayRole === 'agent'
|
|
104
|
+
);
|
|
105
|
+
let outputContent = '';
|
|
106
|
+
if (lastLogMessage && 'content' in lastLogMessage) {
|
|
107
|
+
outputContent = `\n\n<subagent_output>\n${lastLogMessage.content}\n</subagent_output>`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log(
|
|
111
|
+
'Notifying parent',
|
|
105
112
|
chatId,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
113
|
+
parentTokenPayload?.agentId,
|
|
114
|
+
parentTokenPayload?.subagentId
|
|
115
|
+
);
|
|
116
|
+
// TODO: We need to overhaul the log system in general, and should not try to do it in this PR.
|
|
117
|
+
// Currently, if the parent is the root agent, this notification is logged as a normal user message
|
|
118
|
+
// and appears in the chat UI, violating the PRD requirement to hide orchestration.
|
|
119
|
+
await executeDirectMessage(
|
|
120
|
+
chatId,
|
|
121
|
+
{
|
|
122
|
+
messageId: randomUUID(),
|
|
123
|
+
message: `<notification>Subagent ${subagentId} completed.</notification>${outputContent}`,
|
|
124
|
+
chatId,
|
|
125
|
+
agentId: parentTokenPayload?.agentId || 'default',
|
|
126
|
+
...(parentTokenPayload?.subagentId
|
|
127
|
+
? { subagentId: parentTokenPayload.subagentId }
|
|
128
|
+
: {}),
|
|
129
|
+
sessionId: parentTokenPayload?.sessionId || 'default',
|
|
130
|
+
env: {},
|
|
131
|
+
},
|
|
132
|
+
undefined,
|
|
133
|
+
workspaceRoot,
|
|
134
|
+
true,
|
|
135
|
+
undefined,
|
|
136
|
+
parentTokenPayload?.subagentId,
|
|
137
|
+
'subagent_update',
|
|
138
|
+
undefined,
|
|
139
|
+
parentTurnId
|
|
140
|
+
);
|
|
124
141
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
142
|
+
} catch {
|
|
143
|
+
// TODO: Wrap this in a safe try-catch to prevent unhandled promise rejections crashing the daemon if disk errors occur
|
|
144
|
+
await updateChatSettings(chatId, (errSettings) => {
|
|
145
|
+
if (errSettings.subagents?.[subagentId]) {
|
|
146
|
+
errSettings.subagents[subagentId]!.status = 'failed';
|
|
147
|
+
}
|
|
148
|
+
return errSettings;
|
|
149
|
+
});
|
|
150
|
+
const logger = createChatLogger(chatId, subagentId, sessionId, parentTurnId);
|
|
151
|
+
await logger.logSubagentStatus({ subagentId, status: 'failed' });
|
|
152
|
+
}
|
|
153
|
+
} finally {
|
|
154
|
+
decrementSubagent(parentTurnId);
|
|
129
155
|
}
|
|
130
156
|
}
|