clawmini 0.0.1
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/.gemini/settings.json +46 -0
- package/.prettierrc +7 -0
- package/GEMINI.md +11 -0
- package/README.md +137 -0
- package/dist/adapter-discord/index.d.mts +5 -0
- package/dist/adapter-discord/index.d.mts.map +1 -0
- package/dist/adapter-discord/index.mjs +456 -0
- package/dist/adapter-discord/index.mjs.map +1 -0
- package/dist/chats-DKgTeU7i.mjs +91 -0
- package/dist/chats-DKgTeU7i.mjs.map +1 -0
- package/dist/chats-Zd_HXDHx.mjs +29 -0
- package/dist/chats-Zd_HXDHx.mjs.map +1 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +850 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/cli/lite.d.mts +1 -0
- package/dist/cli/lite.mjs +4434 -0
- package/dist/cli/lite.mjs.map +1 -0
- package/dist/daemon/index.d.mts +5 -0
- package/dist/daemon/index.d.mts.map +1 -0
- package/dist/daemon/index.mjs +1222 -0
- package/dist/daemon/index.mjs.map +1 -0
- package/dist/fetch-BjZVyU3Z.mjs +37 -0
- package/dist/fetch-BjZVyU3Z.mjs.map +1 -0
- package/dist/fs-B5wW0oaH.mjs +14 -0
- package/dist/fs-B5wW0oaH.mjs.map +1 -0
- package/dist/lite-Dl7WXyaH.mjs +80 -0
- package/dist/lite-Dl7WXyaH.mjs.map +1 -0
- package/dist/rolldown-runtime-95iHPtFO.mjs +18 -0
- package/dist/web/_app/env.js +1 -0
- package/dist/web/_app/immutable/assets/0.GI4C4dpV.css +1 -0
- package/dist/web/_app/immutable/chunks/B5abRDXp.js +1 -0
- package/dist/web/_app/immutable/chunks/B8yYFADm.js +1 -0
- package/dist/web/_app/immutable/chunks/BPy8HLo7.js +5 -0
- package/dist/web/_app/immutable/chunks/Bi0jeV7Q.js +1 -0
- package/dist/web/_app/immutable/chunks/BmUXQ3wy.js +2 -0
- package/dist/web/_app/immutable/chunks/C3k55nDF.js +1 -0
- package/dist/web/_app/immutable/chunks/COekwvP2.js +1 -0
- package/dist/web/_app/immutable/chunks/CSvS_NwK.js +1 -0
- package/dist/web/_app/immutable/chunks/CpaGRn9L.js +1 -0
- package/dist/web/_app/immutable/chunks/CyNaE55B.js +1 -0
- package/dist/web/_app/immutable/chunks/DG5RZBw-.js +2 -0
- package/dist/web/_app/immutable/chunks/Dc-UOHw9.js +1 -0
- package/dist/web/_app/immutable/chunks/DcrmIfTj.js +1 -0
- package/dist/web/_app/immutable/chunks/ZkLyk0mE.js +1 -0
- package/dist/web/_app/immutable/entry/app.B-vZe7PN.js +2 -0
- package/dist/web/_app/immutable/entry/start.oP1AgKhs.js +1 -0
- package/dist/web/_app/immutable/nodes/0.B5WFN0zw.js +1 -0
- package/dist/web/_app/immutable/nodes/1.D1wtJb2k.js +1 -0
- package/dist/web/_app/immutable/nodes/2.CK3CLC0f.js +1 -0
- package/dist/web/_app/immutable/nodes/3.BB5wCoBf.js +4 -0
- package/dist/web/_app/immutable/nodes/4.Dr2jvAXK.js +1 -0
- package/dist/web/_app/immutable/nodes/5.BJl7oM3b.js +1 -0
- package/dist/web/_app/version.json +1 -0
- package/dist/web/index.html +37 -0
- package/dist/web/robots.txt +3 -0
- package/dist/workspace-CSgfo_2J.mjs +383 -0
- package/dist/workspace-CSgfo_2J.mjs.map +1 -0
- package/docs/01_chats/development_log.md +36 -0
- package/docs/01_chats/notes.md +27 -0
- package/docs/01_chats/prd.md +47 -0
- package/docs/01_chats/questions.md +19 -0
- package/docs/01_chats/tickets.md +67 -0
- package/docs/02_sessions/development_log.md +79 -0
- package/docs/02_sessions/notes.md +40 -0
- package/docs/02_sessions/prd.md +75 -0
- package/docs/02_sessions/questions.md +7 -0
- package/docs/02_sessions/tickets.md +68 -0
- package/docs/03_web_interface/development_log.md +60 -0
- package/docs/03_web_interface/notes.md +29 -0
- package/docs/03_web_interface/prd.md +42 -0
- package/docs/03_web_interface/questions.md +8 -0
- package/docs/03_web_interface/tickets.md +59 -0
- package/docs/04_agents/development_log.md +54 -0
- package/docs/04_agents/notes.md +45 -0
- package/docs/04_agents/prd.md +47 -0
- package/docs/04_agents/questions.md +13 -0
- package/docs/04_agents/tickets.md +107 -0
- package/docs/05_routers/development_log.md +13 -0
- package/docs/05_routers/notes.md +40 -0
- package/docs/05_routers/prd.md +55 -0
- package/docs/05_routers/questions.md +21 -0
- package/docs/05_routers/tickets.md +109 -0
- package/docs/06_agent_templates/development_log.md +38 -0
- package/docs/06_agent_templates/notes.md +25 -0
- package/docs/06_agent_templates/prd.md +34 -0
- package/docs/06_agent_templates/questions.md +11 -0
- package/docs/06_agent_templates/tickets.md +49 -0
- package/docs/06_cron/development_log.md +51 -0
- package/docs/06_cron/notes.md +14 -0
- package/docs/06_cron/prd.md +92 -0
- package/docs/06_cron/questions.md +15 -0
- package/docs/06_cron/tickets.md +75 -0
- package/docs/07_web_chat_ux/development_log.md +30 -0
- package/docs/07_web_chat_ux/notes.md +25 -0
- package/docs/07_web_chat_ux/prd.md +46 -0
- package/docs/07_web_chat_ux/questions.md +7 -0
- package/docs/07_web_chat_ux/tickets.md +48 -0
- package/docs/08_agent_api/development_log.md +52 -0
- package/docs/08_agent_api/notes.md +31 -0
- package/docs/08_agent_api/prd.md +56 -0
- package/docs/08_agent_api/questions.md +14 -0
- package/docs/08_agent_api/tickets.md +104 -0
- package/docs/09_agent_fallbacks/development_log.md +52 -0
- package/docs/09_agent_fallbacks/notes.md +40 -0
- package/docs/09_agent_fallbacks/prd.md +55 -0
- package/docs/09_agent_fallbacks/questions.md +10 -0
- package/docs/09_agent_fallbacks/tickets.md +88 -0
- package/docs/09_discord_adapter/development_log.md +95 -0
- package/docs/09_discord_adapter/notes.md +18 -0
- package/docs/09_discord_adapter/prd.md +57 -0
- package/docs/09_discord_adapter/questions.md +16 -0
- package/docs/09_discord_adapter/tickets.md +116 -0
- package/docs/10_file_attachments/development_log.md +55 -0
- package/docs/10_file_attachments/notes.md +59 -0
- package/docs/10_file_attachments/prd.md +73 -0
- package/docs/10_file_attachments/questions.md +15 -0
- package/docs/10_file_attachments/tickets.md +88 -0
- package/docs/11_message_verbosity/development_log.md +43 -0
- package/docs/11_message_verbosity/notes.md +26 -0
- package/docs/11_message_verbosity/prd.md +44 -0
- package/docs/11_message_verbosity/questions.md +8 -0
- package/docs/11_message_verbosity/tickets.md +33 -0
- package/docs/12_environments/development_log.md +43 -0
- package/docs/12_environments/notes.md +45 -0
- package/docs/12_environments/prd.md +113 -0
- package/docs/12_environments/questions.md +17 -0
- package/docs/12_environments/tickets.md +87 -0
- package/docs/12_setup_flow_improvements/development_log.md +40 -0
- package/docs/12_setup_flow_improvements/notes.md +34 -0
- package/docs/12_setup_flow_improvements/prd.md +35 -0
- package/docs/12_setup_flow_improvements/questions.md +8 -0
- package/docs/12_setup_flow_improvements/tickets.md +122 -0
- package/docs/13_discord_typing_indicators/development_log.md +38 -0
- package/docs/13_discord_typing_indicators/notes.md +18 -0
- package/docs/13_discord_typing_indicators/prd.md +41 -0
- package/docs/13_discord_typing_indicators/questions.md +6 -0
- package/docs/13_discord_typing_indicators/tickets.md +60 -0
- package/docs/14_interruptions/development_log.md +50 -0
- package/docs/14_interruptions/notes.md +38 -0
- package/docs/14_interruptions/prd.md +46 -0
- package/docs/14_interruptions/questions.md +12 -0
- package/docs/14_interruptions/tickets.md +69 -0
- package/docs/15_sandbox_policies/development_log.md +95 -0
- package/docs/15_sandbox_policies/notes.md +33 -0
- package/docs/15_sandbox_policies/prd.md +163 -0
- package/docs/15_sandbox_policies/questions.md +10 -0
- package/docs/15_sandbox_policies/tickets.md +196 -0
- package/docs/CHECKS.md +9 -0
- package/docs/guides/discord_adapter_setup.md +69 -0
- package/docs/guides/sandbox_policies.md +76 -0
- package/eslint.config.js +47 -0
- package/napkin.md +21 -0
- package/package.json +50 -0
- package/scripts/create_worktree.sh +49 -0
- package/scripts/get_pr_comments.sh +36 -0
- package/src/adapter-discord/client.test.ts +65 -0
- package/src/adapter-discord/client.ts +41 -0
- package/src/adapter-discord/config.test.ts +156 -0
- package/src/adapter-discord/config.ts +61 -0
- package/src/adapter-discord/forwarder.test.ts +493 -0
- package/src/adapter-discord/forwarder.ts +246 -0
- package/src/adapter-discord/index.test.ts +399 -0
- package/src/adapter-discord/index.ts +147 -0
- package/src/adapter-discord/state.test.ts +65 -0
- package/src/adapter-discord/state.ts +44 -0
- package/src/cli/client.ts +46 -0
- package/src/cli/commands/agents.ts +138 -0
- package/src/cli/commands/chats.ts +79 -0
- package/src/cli/commands/down.ts +32 -0
- package/src/cli/commands/environments.ts +39 -0
- package/src/cli/commands/export-lite.ts +62 -0
- package/src/cli/commands/init.ts +79 -0
- package/src/cli/commands/jobs.ts +141 -0
- package/src/cli/commands/messages.ts +103 -0
- package/src/cli/commands/up.ts +26 -0
- package/src/cli/commands/web-api/agents.ts +138 -0
- package/src/cli/commands/web-api/chats.ts +213 -0
- package/src/cli/commands/web-api/utils.ts +27 -0
- package/src/cli/commands/web.ts +105 -0
- package/src/cli/e2e/adapter-discord.test.ts +76 -0
- package/src/cli/e2e/agents.test.ts +140 -0
- package/src/cli/e2e/basic.test.ts +43 -0
- package/src/cli/e2e/cron.test.ts +132 -0
- package/src/cli/e2e/daemon.test.ts +293 -0
- package/src/cli/e2e/environments.test.ts +66 -0
- package/src/cli/e2e/export-lite-func.test.ts +155 -0
- package/src/cli/e2e/export-lite.test.ts +51 -0
- package/src/cli/e2e/fallbacks.test.ts +169 -0
- package/src/cli/e2e/global-setup.ts +15 -0
- package/src/cli/e2e/init.test.ts +70 -0
- package/src/cli/e2e/messages.test.ts +294 -0
- package/src/cli/e2e/requests.test.ts +165 -0
- package/src/cli/e2e/utils.ts +66 -0
- package/src/cli/index.test.ts +7 -0
- package/src/cli/index.ts +29 -0
- package/src/cli/lite.ts +247 -0
- package/src/cli/utils.ts +4 -0
- package/src/daemon/auth.test.ts +50 -0
- package/src/daemon/auth.ts +69 -0
- package/src/daemon/chats.ts +26 -0
- package/src/daemon/cron.test.ts +28 -0
- package/src/daemon/cron.ts +159 -0
- package/src/daemon/events.ts +15 -0
- package/src/daemon/index.ts +212 -0
- package/src/daemon/message-agent.test.ts +132 -0
- package/src/daemon/message-extraction.test.ts +166 -0
- package/src/daemon/message-fallbacks.test.ts +313 -0
- package/src/daemon/message-interruption.test.ts +125 -0
- package/src/daemon/message-queue.test.ts +143 -0
- package/src/daemon/message-router.test.ts +106 -0
- package/src/daemon/message-session.test.ts +127 -0
- package/src/daemon/message-test-utils.ts +41 -0
- package/src/daemon/message-typing.test.ts +93 -0
- package/src/daemon/message-verbosity.test.ts +127 -0
- package/src/daemon/message.ts +600 -0
- package/src/daemon/observation.test.ts +118 -0
- package/src/daemon/policy-request-service.test.ts +87 -0
- package/src/daemon/policy-request-service.ts +62 -0
- package/src/daemon/policy-utils.test.ts +138 -0
- package/src/daemon/policy-utils.ts +152 -0
- package/src/daemon/queue.test.ts +89 -0
- package/src/daemon/queue.ts +87 -0
- package/src/daemon/request-store.test.ts +103 -0
- package/src/daemon/request-store.ts +96 -0
- package/src/daemon/router-policy-request.test.ts +99 -0
- package/src/daemon/router.test.ts +380 -0
- package/src/daemon/router.ts +510 -0
- package/src/daemon/routers/slash-command.test.ts +145 -0
- package/src/daemon/routers/slash-command.ts +58 -0
- package/src/daemon/routers/slash-interrupt.test.ts +30 -0
- package/src/daemon/routers/slash-interrupt.ts +7 -0
- package/src/daemon/routers/slash-new.test.ts +59 -0
- package/src/daemon/routers/slash-new.ts +14 -0
- package/src/daemon/routers/slash-policies.test.ts +167 -0
- package/src/daemon/routers/slash-policies.ts +131 -0
- package/src/daemon/routers/slash-stop.test.ts +30 -0
- package/src/daemon/routers/slash-stop.ts +3 -0
- package/src/daemon/routers/types.ts +10 -0
- package/src/daemon/routers/utils.ts +22 -0
- package/src/daemon/routers.test.ts +141 -0
- package/src/daemon/routers.ts +115 -0
- package/src/daemon/utils/spawn.ts +61 -0
- package/src/shared/agent-utils.ts +30 -0
- package/src/shared/chats.test.ts +112 -0
- package/src/shared/chats.ts +164 -0
- package/src/shared/config.test.ts +90 -0
- package/src/shared/config.ts +100 -0
- package/src/shared/event-source.ts +121 -0
- package/src/shared/fetch.ts +45 -0
- package/src/shared/lite.ts +129 -0
- package/src/shared/policies.ts +24 -0
- package/src/shared/utils/env.ts +27 -0
- package/src/shared/utils/fs.ts +13 -0
- package/src/shared/workspace.test.ts +345 -0
- package/src/shared/workspace.ts +500 -0
- package/templates/environments/cladding/env.json +7 -0
- package/templates/environments/macos/env.json +8 -0
- package/templates/environments/macos/sandbox.sb +21 -0
- package/templates/environments/macos-proxy/allowlist.txt +1 -0
- package/templates/environments/macos-proxy/env.json +14 -0
- package/templates/environments/macos-proxy/proxy.mjs +86 -0
- package/templates/environments/macos-proxy/sandbox.sb +34 -0
- package/templates/gemini/settings.json +11 -0
- package/templates/gemini-claw/.gemini/hooks/clawmini-logging.sh +17 -0
- package/templates/gemini-claw/.gemini/settings.json +24 -0
- package/templates/gemini-claw/.gemini/skills/clawmini-jobs/SKILL.md +40 -0
- package/templates/gemini-claw/.gemini/system.md +98 -0
- package/templates/gemini-claw/BOOTSTRAP.md +54 -0
- package/templates/gemini-claw/GEMINI.md +107 -0
- package/templates/gemini-claw/HEARTBEAT.md +3 -0
- package/templates/gemini-claw/MEMORY.md +2 -0
- package/templates/gemini-claw/SOUL.md +42 -0
- package/templates/gemini-claw/TOOLS.md +38 -0
- package/templates/gemini-claw/USER.md +15 -0
- package/templates/gemini-claw/memory/.gitkeep +0 -0
- package/templates/gemini-claw/settings.json +24 -0
- package/templates/opencode/settings.json +11 -0
- package/tsconfig.json +42 -0
- package/tsdown.config.ts +19 -0
- package/vitest.config.ts +9 -0
- package/web/.svelte-kit/ambient.d.ts +382 -0
- package/web/.svelte-kit/generated/client/app.js +35 -0
- package/web/.svelte-kit/generated/client/matchers.js +1 -0
- package/web/.svelte-kit/generated/client/nodes/0.js +3 -0
- package/web/.svelte-kit/generated/client/nodes/1.js +1 -0
- package/web/.svelte-kit/generated/client/nodes/2.js +1 -0
- package/web/.svelte-kit/generated/client/nodes/3.js +1 -0
- package/web/.svelte-kit/generated/client/nodes/4.js +3 -0
- package/web/.svelte-kit/generated/client/nodes/5.js +3 -0
- package/web/.svelte-kit/generated/client-optimized/app.js +35 -0
- package/web/.svelte-kit/generated/client-optimized/matchers.js +1 -0
- package/web/.svelte-kit/generated/client-optimized/nodes/0.js +3 -0
- package/web/.svelte-kit/generated/client-optimized/nodes/1.js +1 -0
- package/web/.svelte-kit/generated/client-optimized/nodes/2.js +1 -0
- package/web/.svelte-kit/generated/client-optimized/nodes/3.js +1 -0
- package/web/.svelte-kit/generated/client-optimized/nodes/4.js +3 -0
- package/web/.svelte-kit/generated/client-optimized/nodes/5.js +3 -0
- package/web/.svelte-kit/generated/root.js +3 -0
- package/web/.svelte-kit/generated/root.svelte +68 -0
- package/web/.svelte-kit/generated/server/internal.js +53 -0
- package/web/.svelte-kit/non-ambient.d.ts +46 -0
- package/web/.svelte-kit/output/client/.vite/manifest.json +251 -0
- package/web/.svelte-kit/output/client/_app/immutable/assets/0.GI4C4dpV.css +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/B5abRDXp.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/B8yYFADm.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/BPy8HLo7.js +5 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/Bi0jeV7Q.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/BmUXQ3wy.js +2 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/C3k55nDF.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/COekwvP2.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/CSvS_NwK.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/CpaGRn9L.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/CyNaE55B.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/DG5RZBw-.js +2 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/Dc-UOHw9.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/DcrmIfTj.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/ZkLyk0mE.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/entry/app.B-vZe7PN.js +2 -0
- package/web/.svelte-kit/output/client/_app/immutable/entry/start.oP1AgKhs.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/nodes/0.B5WFN0zw.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/nodes/1.D1wtJb2k.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/nodes/2.CK3CLC0f.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/nodes/3.BB5wCoBf.js +4 -0
- package/web/.svelte-kit/output/client/_app/immutable/nodes/4.Dr2jvAXK.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/nodes/5.BJl7oM3b.js +1 -0
- package/web/.svelte-kit/output/client/_app/version.json +1 -0
- package/web/.svelte-kit/output/client/robots.txt +3 -0
- package/web/.svelte-kit/output/prerendered/dependencies/_app/env.js +1 -0
- package/web/.svelte-kit/output/server/.vite/manifest.json +215 -0
- package/web/.svelte-kit/output/server/_app/immutable/assets/_layout.GI4C4dpV.css +1 -0
- package/web/.svelte-kit/output/server/chunks/Icon.js +153 -0
- package/web/.svelte-kit/output/server/chunks/bot.js +2753 -0
- package/web/.svelte-kit/output/server/chunks/client.js +47 -0
- package/web/.svelte-kit/output/server/chunks/environment.js +34 -0
- package/web/.svelte-kit/output/server/chunks/exports.js +231 -0
- package/web/.svelte-kit/output/server/chunks/false.js +4 -0
- package/web/.svelte-kit/output/server/chunks/index-server.js +20 -0
- package/web/.svelte-kit/output/server/chunks/index.js +24 -0
- package/web/.svelte-kit/output/server/chunks/internal.js +133 -0
- package/web/.svelte-kit/output/server/chunks/plus.js +81 -0
- package/web/.svelte-kit/output/server/chunks/root.js +4076 -0
- package/web/.svelte-kit/output/server/chunks/shared.js +789 -0
- package/web/.svelte-kit/output/server/chunks/utils.js +43 -0
- package/web/.svelte-kit/output/server/entries/fallbacks/error.svelte.js +11 -0
- package/web/.svelte-kit/output/server/entries/pages/_layout.svelte.js +3944 -0
- package/web/.svelte-kit/output/server/entries/pages/_layout.ts.js +28 -0
- package/web/.svelte-kit/output/server/entries/pages/_page.svelte.js +7 -0
- package/web/.svelte-kit/output/server/entries/pages/agents/_page.svelte.js +379 -0
- package/web/.svelte-kit/output/server/entries/pages/chats/_id_/_page.svelte.js +292 -0
- package/web/.svelte-kit/output/server/entries/pages/chats/_id_/_page.ts.js +17 -0
- package/web/.svelte-kit/output/server/entries/pages/chats/_id_/settings/_page.svelte.js +259 -0
- package/web/.svelte-kit/output/server/entries/pages/chats/_id_/settings/_page.ts.js +17 -0
- package/web/.svelte-kit/output/server/index.js +3748 -0
- package/web/.svelte-kit/output/server/internal.js +14 -0
- package/web/.svelte-kit/output/server/manifest-full.js +63 -0
- package/web/.svelte-kit/output/server/manifest.js +63 -0
- package/web/.svelte-kit/output/server/nodes/0.js +13 -0
- package/web/.svelte-kit/output/server/nodes/1.js +8 -0
- package/web/.svelte-kit/output/server/nodes/2.js +8 -0
- package/web/.svelte-kit/output/server/nodes/3.js +8 -0
- package/web/.svelte-kit/output/server/nodes/4.js +13 -0
- package/web/.svelte-kit/output/server/nodes/5.js +13 -0
- package/web/.svelte-kit/output/server/remote-entry.js +557 -0
- package/web/.svelte-kit/tsconfig.json +67 -0
- package/web/.svelte-kit/types/route_meta_data.json +17 -0
- package/web/.svelte-kit/types/src/routes/$types.d.ts +26 -0
- package/web/.svelte-kit/types/src/routes/agents/$types.d.ts +18 -0
- package/web/.svelte-kit/types/src/routes/chats/[id]/$types.d.ts +21 -0
- package/web/.svelte-kit/types/src/routes/chats/[id]/proxy+page.ts +20 -0
- package/web/.svelte-kit/types/src/routes/chats/[id]/settings/$types.d.ts +21 -0
- package/web/.svelte-kit/types/src/routes/chats/[id]/settings/proxy+page.ts +19 -0
- package/web/.svelte-kit/types/src/routes/proxy+layout.ts +35 -0
- package/web/README.md +42 -0
- package/web/components.json +16 -0
- package/web/package.json +41 -0
- package/web/src/app.css +121 -0
- package/web/src/app.d.ts +13 -0
- package/web/src/app.html +11 -0
- package/web/src/demo.spec.ts +7 -0
- package/web/src/lib/app-state.svelte.ts +3 -0
- package/web/src/lib/assets/favicon.svg +1 -0
- package/web/src/lib/components/app/app-sidebar-test-wrapper.svelte +10 -0
- package/web/src/lib/components/app/app-sidebar.svelte +171 -0
- package/web/src/lib/components/app/app-sidebar.svelte.spec.ts +13 -0
- package/web/src/lib/components/ui/button/button.svelte +82 -0
- package/web/src/lib/components/ui/button/index.ts +17 -0
- package/web/src/lib/components/ui/dialog/dialog-close.svelte +7 -0
- package/web/src/lib/components/ui/dialog/dialog-content.svelte +45 -0
- package/web/src/lib/components/ui/dialog/dialog-description.svelte +17 -0
- package/web/src/lib/components/ui/dialog/dialog-footer.svelte +20 -0
- package/web/src/lib/components/ui/dialog/dialog-header.svelte +20 -0
- package/web/src/lib/components/ui/dialog/dialog-overlay.svelte +20 -0
- package/web/src/lib/components/ui/dialog/dialog-portal.svelte +7 -0
- package/web/src/lib/components/ui/dialog/dialog-title.svelte +17 -0
- package/web/src/lib/components/ui/dialog/dialog-trigger.svelte +7 -0
- package/web/src/lib/components/ui/dialog/dialog.svelte +7 -0
- package/web/src/lib/components/ui/dialog/index.ts +34 -0
- package/web/src/lib/components/ui/input/index.ts +7 -0
- package/web/src/lib/components/ui/input/input.svelte +52 -0
- package/web/src/lib/components/ui/separator/index.ts +7 -0
- package/web/src/lib/components/ui/separator/separator.svelte +21 -0
- package/web/src/lib/components/ui/sheet/index.ts +34 -0
- package/web/src/lib/components/ui/sheet/sheet-close.svelte +7 -0
- package/web/src/lib/components/ui/sheet/sheet-content.svelte +60 -0
- package/web/src/lib/components/ui/sheet/sheet-description.svelte +17 -0
- package/web/src/lib/components/ui/sheet/sheet-footer.svelte +20 -0
- package/web/src/lib/components/ui/sheet/sheet-header.svelte +20 -0
- package/web/src/lib/components/ui/sheet/sheet-overlay.svelte +20 -0
- package/web/src/lib/components/ui/sheet/sheet-portal.svelte +7 -0
- package/web/src/lib/components/ui/sheet/sheet-title.svelte +17 -0
- package/web/src/lib/components/ui/sheet/sheet-trigger.svelte +7 -0
- package/web/src/lib/components/ui/sheet/sheet.svelte +7 -0
- package/web/src/lib/components/ui/sidebar/constants.ts +6 -0
- package/web/src/lib/components/ui/sidebar/context.svelte.ts +79 -0
- package/web/src/lib/components/ui/sidebar/index.ts +75 -0
- package/web/src/lib/components/ui/sidebar/sidebar-content.svelte +24 -0
- package/web/src/lib/components/ui/sidebar/sidebar-footer.svelte +21 -0
- package/web/src/lib/components/ui/sidebar/sidebar-group-action.svelte +36 -0
- package/web/src/lib/components/ui/sidebar/sidebar-group-content.svelte +21 -0
- package/web/src/lib/components/ui/sidebar/sidebar-group-label.svelte +34 -0
- package/web/src/lib/components/ui/sidebar/sidebar-group.svelte +21 -0
- package/web/src/lib/components/ui/sidebar/sidebar-header.svelte +21 -0
- package/web/src/lib/components/ui/sidebar/sidebar-input.svelte +21 -0
- package/web/src/lib/components/ui/sidebar/sidebar-inset.svelte +24 -0
- package/web/src/lib/components/ui/sidebar/sidebar-menu-action.svelte +43 -0
- package/web/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte +29 -0
- package/web/src/lib/components/ui/sidebar/sidebar-menu-button.svelte +103 -0
- package/web/src/lib/components/ui/sidebar/sidebar-menu-item.svelte +21 -0
- package/web/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte +36 -0
- package/web/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte +43 -0
- package/web/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte +21 -0
- package/web/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte +25 -0
- package/web/src/lib/components/ui/sidebar/sidebar-menu.svelte +21 -0
- package/web/src/lib/components/ui/sidebar/sidebar-provider.svelte +53 -0
- package/web/src/lib/components/ui/sidebar/sidebar-rail.svelte +36 -0
- package/web/src/lib/components/ui/sidebar/sidebar-separator.svelte +19 -0
- package/web/src/lib/components/ui/sidebar/sidebar-trigger.svelte +35 -0
- package/web/src/lib/components/ui/sidebar/sidebar.svelte +104 -0
- package/web/src/lib/components/ui/skeleton/index.ts +7 -0
- package/web/src/lib/components/ui/skeleton/skeleton.svelte +17 -0
- package/web/src/lib/components/ui/switch/index.ts +7 -0
- package/web/src/lib/components/ui/switch/switch.svelte +29 -0
- package/web/src/lib/components/ui/textarea/index.ts +7 -0
- package/web/src/lib/components/ui/textarea/textarea.svelte +23 -0
- package/web/src/lib/components/ui/tooltip/index.ts +19 -0
- package/web/src/lib/components/ui/tooltip/tooltip-content.svelte +52 -0
- package/web/src/lib/components/ui/tooltip/tooltip-portal.svelte +7 -0
- package/web/src/lib/components/ui/tooltip/tooltip-provider.svelte +7 -0
- package/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte +7 -0
- package/web/src/lib/components/ui/tooltip/tooltip.svelte +7 -0
- package/web/src/lib/hooks/is-mobile.svelte.ts +9 -0
- package/web/src/lib/index.ts +1 -0
- package/web/src/lib/types.ts +23 -0
- package/web/src/lib/utils.ts +13 -0
- package/web/src/routes/+layout.svelte +67 -0
- package/web/src/routes/+layout.ts +34 -0
- package/web/src/routes/+page.svelte +7 -0
- package/web/src/routes/agents/+page.svelte +206 -0
- package/web/src/routes/chats/[id]/+page.svelte +406 -0
- package/web/src/routes/chats/[id]/+page.ts +19 -0
- package/web/src/routes/chats/[id]/page.svelte.spec.ts +102 -0
- package/web/src/routes/chats/[id]/settings/+page.svelte +165 -0
- package/web/src/routes/chats/[id]/settings/+page.ts +18 -0
- package/web/src/routes/page.svelte.spec.ts +13 -0
- package/web/static/robots.txt +3 -0
- package/web/svelte.config.js +21 -0
- package/web/tsconfig.json +20 -0
- package/web/vite.config.ts +41 -0
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
2
|
+
import { initTRPC, TRPCError } from '@trpc/server';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { daemonEvents, DAEMON_EVENT_MESSAGE_APPENDED, DAEMON_EVENT_TYPING } from './events.js';
|
|
7
|
+
import {
|
|
8
|
+
getSettingsPath,
|
|
9
|
+
readChatSettings,
|
|
10
|
+
writeChatSettings,
|
|
11
|
+
getAgent,
|
|
12
|
+
getWorkspaceRoot,
|
|
13
|
+
readPolicies,
|
|
14
|
+
getClawminiDir,
|
|
15
|
+
} from '../shared/workspace.js';
|
|
16
|
+
import { PolicyRequestService } from './policy-request-service.js';
|
|
17
|
+
import { RequestStore } from './request-store.js';
|
|
18
|
+
import { CronJobSchema } from '../shared/config.js';
|
|
19
|
+
import { handleUserMessage } from './message.js';
|
|
20
|
+
import { getDefaultChatId, getMessages } from './chats.js';
|
|
21
|
+
import { runCommand } from './utils/spawn.js';
|
|
22
|
+
import { cronManager } from './cron.js';
|
|
23
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
24
|
+
import type { TokenPayload } from './auth.js';
|
|
25
|
+
|
|
26
|
+
export interface Context {
|
|
27
|
+
req?: IncomingMessage | undefined;
|
|
28
|
+
res?: ServerResponse | undefined;
|
|
29
|
+
isApiServer?: boolean | undefined;
|
|
30
|
+
tokenPayload?: TokenPayload | null | undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const t = initTRPC.context<Context>().create();
|
|
34
|
+
export const router = t.router;
|
|
35
|
+
export const publicProcedure = t.procedure;
|
|
36
|
+
|
|
37
|
+
const apiAuthMiddleware = t.middleware(({ ctx, next }) => {
|
|
38
|
+
if (ctx.isApiServer) {
|
|
39
|
+
if (!ctx.tokenPayload) {
|
|
40
|
+
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Missing or invalid token' });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return next({
|
|
44
|
+
ctx: {
|
|
45
|
+
...ctx,
|
|
46
|
+
tokenPayload: ctx.tokenPayload,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export const apiProcedure = t.procedure.use(apiAuthMiddleware);
|
|
52
|
+
|
|
53
|
+
export async function getUniquePath(p: string): Promise<string> {
|
|
54
|
+
let currentPath = p;
|
|
55
|
+
let counter = 1;
|
|
56
|
+
while (true) {
|
|
57
|
+
try {
|
|
58
|
+
await fs.stat(currentPath);
|
|
59
|
+
const ext = path.extname(p);
|
|
60
|
+
const base = path.basename(p, ext);
|
|
61
|
+
currentPath = path.join(path.dirname(p), `${base}-${counter}${ext}`);
|
|
62
|
+
counter++;
|
|
63
|
+
} catch {
|
|
64
|
+
return currentPath;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function resolveAgentDir(
|
|
70
|
+
agentId: string | undefined | null,
|
|
71
|
+
workspaceRoot: string
|
|
72
|
+
): Promise<string> {
|
|
73
|
+
if (agentId && agentId !== 'default') {
|
|
74
|
+
try {
|
|
75
|
+
const agent = await getAgent(agentId, workspaceRoot);
|
|
76
|
+
if (agent && agent.directory) {
|
|
77
|
+
return path.resolve(workspaceRoot, agent.directory);
|
|
78
|
+
}
|
|
79
|
+
} catch (err: unknown) {
|
|
80
|
+
console.warn(`Could not load custom agent '${agentId}' for resolving directory:`, err);
|
|
81
|
+
}
|
|
82
|
+
return path.resolve(workspaceRoot, agentId);
|
|
83
|
+
}
|
|
84
|
+
return workspaceRoot;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function resolveAndCheckChatId(ctx: Context, inputChatId?: string): Promise<string> {
|
|
88
|
+
const chatId =
|
|
89
|
+
inputChatId ??
|
|
90
|
+
(ctx.isApiServer && ctx.tokenPayload ? ctx.tokenPayload.chatId : await getDefaultChatId());
|
|
91
|
+
|
|
92
|
+
if (ctx.isApiServer && ctx.tokenPayload) {
|
|
93
|
+
if (ctx.tokenPayload.chatId !== chatId) {
|
|
94
|
+
throw new TRPCError({ code: 'FORBIDDEN', message: 'Token not authorized for this chat' });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return chatId;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function getAgentFilesDir(
|
|
102
|
+
agentId: string | undefined,
|
|
103
|
+
chatId: string,
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
105
|
+
settings: any,
|
|
106
|
+
workspaceRoot: string
|
|
107
|
+
): Promise<string> {
|
|
108
|
+
const chatSettings = (await readChatSettings(chatId)) ?? {};
|
|
109
|
+
const targetAgentId = agentId ?? chatSettings.defaultAgent ?? 'default';
|
|
110
|
+
let agentFilesDir = settings?.defaultAgent?.files || './attachments';
|
|
111
|
+
const agentDir = await resolveAgentDir(targetAgentId, workspaceRoot);
|
|
112
|
+
|
|
113
|
+
if (targetAgentId !== 'default') {
|
|
114
|
+
try {
|
|
115
|
+
const customAgent = await getAgent(targetAgentId, workspaceRoot);
|
|
116
|
+
if (customAgent?.files) {
|
|
117
|
+
agentFilesDir = customAgent.files;
|
|
118
|
+
}
|
|
119
|
+
} catch (err: unknown) {
|
|
120
|
+
console.warn(
|
|
121
|
+
`Could not load custom agent '${targetAgentId}' for resolving files directory:`,
|
|
122
|
+
err
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return path.resolve(agentDir, agentFilesDir);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function validateAttachments(files: string[]): Promise<void> {
|
|
131
|
+
const { pathIsInsideDir } = await import('../shared/utils/fs.js');
|
|
132
|
+
const { getClawminiDir } = await import('../shared/workspace.js');
|
|
133
|
+
const tmpDir = path.join(getClawminiDir(process.cwd()), 'tmp');
|
|
134
|
+
|
|
135
|
+
for (const file of files) {
|
|
136
|
+
const absoluteFile = path.resolve(process.cwd(), file);
|
|
137
|
+
if (!pathIsInsideDir(absoluteFile, tmpDir)) {
|
|
138
|
+
throw new TRPCError({
|
|
139
|
+
code: 'BAD_REQUEST',
|
|
140
|
+
message: 'File must be inside the temporary directory.',
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
await fs.access(absoluteFile);
|
|
145
|
+
} catch {
|
|
146
|
+
throw new TRPCError({
|
|
147
|
+
code: 'BAD_REQUEST',
|
|
148
|
+
message: `File does not exist: ${file}`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function validateLogFile(
|
|
155
|
+
file: string,
|
|
156
|
+
agentDir: string,
|
|
157
|
+
workspaceRoot: string
|
|
158
|
+
): Promise<string> {
|
|
159
|
+
const { pathIsInsideDir } = await import('../shared/utils/fs.js');
|
|
160
|
+
// The agent sends paths relative to its working directory.
|
|
161
|
+
// We resolve to an absolute path to verify it is within the agent's directory.
|
|
162
|
+
const resolvedPath = path.resolve(agentDir, file);
|
|
163
|
+
|
|
164
|
+
if (!pathIsInsideDir(resolvedPath, agentDir, { allowSameDir: true })) {
|
|
165
|
+
throw new TRPCError({
|
|
166
|
+
code: 'BAD_REQUEST',
|
|
167
|
+
message: 'File must be within the agent workspace.',
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
await fs.access(resolvedPath);
|
|
173
|
+
} catch {
|
|
174
|
+
throw new TRPCError({
|
|
175
|
+
code: 'BAD_REQUEST',
|
|
176
|
+
message: `File does not exist: ${file}`,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Convert the absolute path to a path relative to the WORKSPACE directory.
|
|
181
|
+
// This allows adapters (like discord-adapter) to easily resolve it against their own view of the workspace.
|
|
182
|
+
return path.relative(workspaceRoot, resolvedPath);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const AppRouter = router({
|
|
186
|
+
sendMessage: apiProcedure
|
|
187
|
+
.input(
|
|
188
|
+
z.object({
|
|
189
|
+
type: z.literal('send-message'),
|
|
190
|
+
client: z.literal('cli'),
|
|
191
|
+
data: z.object({
|
|
192
|
+
message: z.string(),
|
|
193
|
+
chatId: z.string().optional(),
|
|
194
|
+
sessionId: z.string().optional(),
|
|
195
|
+
agentId: z.string().optional(),
|
|
196
|
+
noWait: z.boolean().optional(),
|
|
197
|
+
files: z.array(z.string()).optional(),
|
|
198
|
+
adapter: z.string().optional(),
|
|
199
|
+
}),
|
|
200
|
+
})
|
|
201
|
+
)
|
|
202
|
+
.mutation(async ({ input, ctx }) => {
|
|
203
|
+
let message = input.data.message;
|
|
204
|
+
const chatId = await resolveAndCheckChatId(ctx, input.data.chatId);
|
|
205
|
+
const noWait = input.data.noWait ?? false;
|
|
206
|
+
const sessionId = input.data.sessionId;
|
|
207
|
+
const agentId = input.data.agentId;
|
|
208
|
+
const settingsPath = getSettingsPath();
|
|
209
|
+
|
|
210
|
+
let settings;
|
|
211
|
+
try {
|
|
212
|
+
const settingsStr = await fs.readFile(settingsPath, 'utf8');
|
|
213
|
+
settings = JSON.parse(settingsStr);
|
|
214
|
+
} catch (err) {
|
|
215
|
+
throw new Error(`Failed to read settings from ${settingsPath}: ${err}`, { cause: err });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const files = input.data.files;
|
|
219
|
+
if (files && files.length > 0) {
|
|
220
|
+
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
221
|
+
const chatSettings = (await readChatSettings(chatId)) ?? {};
|
|
222
|
+
const targetAgentId = agentId ?? chatSettings.defaultAgent ?? 'default';
|
|
223
|
+
const agentDir = await resolveAgentDir(targetAgentId, workspaceRoot);
|
|
224
|
+
const absoluteFilesDir = await getAgentFilesDir(agentId, chatId, settings, workspaceRoot);
|
|
225
|
+
|
|
226
|
+
const adapterNamespace = input.data.adapter || 'cli';
|
|
227
|
+
const targetDir = path.join(absoluteFilesDir, adapterNamespace);
|
|
228
|
+
|
|
229
|
+
const { pathIsInsideDir } = await import('../shared/utils/fs.js');
|
|
230
|
+
|
|
231
|
+
if (!pathIsInsideDir(targetDir, workspaceRoot, { allowSameDir: true })) {
|
|
232
|
+
throw new TRPCError({
|
|
233
|
+
code: 'BAD_REQUEST',
|
|
234
|
+
message: 'Target directory must be within the workspace.',
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
await validateAttachments(files);
|
|
239
|
+
|
|
240
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
241
|
+
|
|
242
|
+
const finalPaths: string[] = [];
|
|
243
|
+
for (const file of files) {
|
|
244
|
+
const fileName = path.basename(file);
|
|
245
|
+
const targetPath = await getUniquePath(path.join(targetDir, fileName));
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
await fs.rename(file, targetPath);
|
|
249
|
+
} catch {
|
|
250
|
+
await fs.copyFile(file, targetPath);
|
|
251
|
+
await fs.unlink(file);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
finalPaths.push(path.relative(agentDir, targetPath));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const fileList = `Attached files:\n${finalPaths.map((p) => `- ${p}`).join('\n')}`;
|
|
258
|
+
message = message ? `${message}\n\n${fileList}` : fileList;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
await handleUserMessage(
|
|
262
|
+
chatId,
|
|
263
|
+
message,
|
|
264
|
+
settings,
|
|
265
|
+
undefined,
|
|
266
|
+
noWait,
|
|
267
|
+
(args) => runCommand({ ...args, logToTerminal: true }),
|
|
268
|
+
sessionId,
|
|
269
|
+
agentId
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
return { success: true };
|
|
273
|
+
}),
|
|
274
|
+
getMessages: apiProcedure
|
|
275
|
+
.input(z.object({ chatId: z.string().optional(), limit: z.number().optional() }))
|
|
276
|
+
.query(async ({ input, ctx }) => {
|
|
277
|
+
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
278
|
+
return getMessages(chatId, input.limit);
|
|
279
|
+
}),
|
|
280
|
+
waitForMessages: apiProcedure
|
|
281
|
+
.input(
|
|
282
|
+
z.object({
|
|
283
|
+
chatId: z.string().optional(),
|
|
284
|
+
lastMessageId: z.string().optional(),
|
|
285
|
+
})
|
|
286
|
+
)
|
|
287
|
+
.subscription(async function* ({ input, ctx, signal }) {
|
|
288
|
+
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
289
|
+
|
|
290
|
+
// 1. Check if there are already new messages
|
|
291
|
+
if (input.lastMessageId) {
|
|
292
|
+
const messages = await getMessages(chatId);
|
|
293
|
+
const lastIndex = messages.findIndex((m) => m.id === input.lastMessageId);
|
|
294
|
+
if (lastIndex !== -1 && lastIndex < messages.length - 1) {
|
|
295
|
+
yield messages.slice(lastIndex + 1);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// 2. Listen for new messages
|
|
300
|
+
const { on } = await import('node:events');
|
|
301
|
+
try {
|
|
302
|
+
for await (const [event] of on(daemonEvents, DAEMON_EVENT_MESSAGE_APPENDED, { signal })) {
|
|
303
|
+
if (event.chatId === chatId) {
|
|
304
|
+
yield [event.message];
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
} catch (err) {
|
|
308
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
throw err;
|
|
312
|
+
}
|
|
313
|
+
}),
|
|
314
|
+
waitForTyping: apiProcedure
|
|
315
|
+
.input(
|
|
316
|
+
z.object({
|
|
317
|
+
chatId: z.string().optional(),
|
|
318
|
+
})
|
|
319
|
+
)
|
|
320
|
+
.subscription(async function* ({ input, ctx, signal }) {
|
|
321
|
+
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
322
|
+
|
|
323
|
+
const { on } = await import('node:events');
|
|
324
|
+
try {
|
|
325
|
+
for await (const [event] of on(daemonEvents, DAEMON_EVENT_TYPING, { signal })) {
|
|
326
|
+
if (event.chatId === chatId) {
|
|
327
|
+
yield event;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
} catch (err) {
|
|
331
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
throw err;
|
|
335
|
+
}
|
|
336
|
+
}),
|
|
337
|
+
ping: publicProcedure.query(() => {
|
|
338
|
+
return { status: 'ok' };
|
|
339
|
+
}),
|
|
340
|
+
shutdown: publicProcedure.mutation(() => {
|
|
341
|
+
// Schedule a shutdown shortly after the response is sent
|
|
342
|
+
setTimeout(() => {
|
|
343
|
+
console.log('Shutting down daemon...');
|
|
344
|
+
process.kill(process.pid, 'SIGTERM');
|
|
345
|
+
}, 100);
|
|
346
|
+
return { success: true };
|
|
347
|
+
}),
|
|
348
|
+
logMessage: apiProcedure
|
|
349
|
+
.input(
|
|
350
|
+
z.object({
|
|
351
|
+
chatId: z.string().optional(),
|
|
352
|
+
message: z.string().optional(),
|
|
353
|
+
files: z.array(z.string()).optional(),
|
|
354
|
+
})
|
|
355
|
+
)
|
|
356
|
+
.mutation(async ({ input, ctx }) => {
|
|
357
|
+
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
358
|
+
const timestamp = new Date().toISOString();
|
|
359
|
+
const id = Date.now().toString() + Math.random().toString(36).substring(2, 7);
|
|
360
|
+
|
|
361
|
+
const filePaths: string[] = [];
|
|
362
|
+
if (input.files && input.files.length > 0) {
|
|
363
|
+
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
364
|
+
const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
|
|
365
|
+
|
|
366
|
+
for (const file of input.files) {
|
|
367
|
+
const validPath = await validateLogFile(file, agentDir, workspaceRoot);
|
|
368
|
+
filePaths.push(validPath);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const filesArgStr = filePaths.map((p) => ` --file ${p}`).join('');
|
|
373
|
+
const messageStr = input.message || '';
|
|
374
|
+
const logMsg: import('./chats.js').CommandLogMessage = {
|
|
375
|
+
id,
|
|
376
|
+
messageId: id,
|
|
377
|
+
role: 'log',
|
|
378
|
+
source: 'router',
|
|
379
|
+
content: messageStr,
|
|
380
|
+
stderr: '',
|
|
381
|
+
timestamp,
|
|
382
|
+
command: `clawmini-lite log${filesArgStr}`,
|
|
383
|
+
cwd: process.cwd(),
|
|
384
|
+
exitCode: 0,
|
|
385
|
+
...(filePaths.length > 0 ? { files: filePaths } : {}),
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
await import('./chats.js').then((m) => m.appendMessage(chatId, logMsg));
|
|
389
|
+
return { success: true };
|
|
390
|
+
}),
|
|
391
|
+
listCronJobs: apiProcedure
|
|
392
|
+
.input(z.object({ chatId: z.string().optional() }))
|
|
393
|
+
.query(async ({ input, ctx }) => {
|
|
394
|
+
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
395
|
+
const settings = await readChatSettings(chatId);
|
|
396
|
+
return settings?.jobs ?? [];
|
|
397
|
+
}),
|
|
398
|
+
addCronJob: apiProcedure
|
|
399
|
+
.input(z.object({ chatId: z.string().optional(), job: CronJobSchema }))
|
|
400
|
+
.mutation(async ({ input, ctx }) => {
|
|
401
|
+
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
402
|
+
const settings = (await readChatSettings(chatId)) || {};
|
|
403
|
+
const cronJobs = settings.jobs ?? [];
|
|
404
|
+
const existingIndex = cronJobs.findIndex((j) => j.id === input.job.id);
|
|
405
|
+
if (existingIndex >= 0) {
|
|
406
|
+
cronJobs[existingIndex] = input.job;
|
|
407
|
+
} else {
|
|
408
|
+
cronJobs.push(input.job);
|
|
409
|
+
}
|
|
410
|
+
settings.jobs = cronJobs;
|
|
411
|
+
await writeChatSettings(chatId, settings);
|
|
412
|
+
cronManager.scheduleJob(chatId, input.job);
|
|
413
|
+
return { success: true };
|
|
414
|
+
}),
|
|
415
|
+
deleteCronJob: apiProcedure
|
|
416
|
+
.input(z.object({ chatId: z.string().optional(), id: z.string() }))
|
|
417
|
+
.mutation(async ({ input, ctx }) => {
|
|
418
|
+
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
419
|
+
const settings = await readChatSettings(chatId);
|
|
420
|
+
if (!settings || !settings.jobs) {
|
|
421
|
+
return { success: true, deleted: false };
|
|
422
|
+
}
|
|
423
|
+
const initialLength = settings.jobs.length;
|
|
424
|
+
settings.jobs = settings.jobs.filter((j) => j.id !== input.id);
|
|
425
|
+
if (settings.jobs.length !== initialLength) {
|
|
426
|
+
await writeChatSettings(chatId, settings);
|
|
427
|
+
cronManager.unscheduleJob(chatId, input.id);
|
|
428
|
+
return { success: true, deleted: true };
|
|
429
|
+
}
|
|
430
|
+
return { success: true, deleted: false };
|
|
431
|
+
}),
|
|
432
|
+
listPolicies: apiProcedure.query(async () => {
|
|
433
|
+
return await readPolicies();
|
|
434
|
+
}),
|
|
435
|
+
executePolicyHelp: apiProcedure
|
|
436
|
+
.input(z.object({ commandName: z.string() }))
|
|
437
|
+
.query(async ({ input }) => {
|
|
438
|
+
const config = await readPolicies();
|
|
439
|
+
const policy = config?.policies?.[input.commandName];
|
|
440
|
+
|
|
441
|
+
if (!policy) {
|
|
442
|
+
throw new TRPCError({
|
|
443
|
+
code: 'NOT_FOUND',
|
|
444
|
+
message: `Policy not found: ${input.commandName}`,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (!policy.allowHelp) {
|
|
449
|
+
return { stdout: '', stderr: 'This command does not support --help\n', exitCode: 1 };
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const { executeSafe } = await import('./policy-utils.js');
|
|
453
|
+
const fullArgs = [...(policy.args || []), '--help'];
|
|
454
|
+
const { stdout, stderr, exitCode } = await executeSafe(policy.command, fullArgs, {
|
|
455
|
+
cwd: getWorkspaceRoot(),
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
return { stdout, stderr, exitCode };
|
|
459
|
+
}),
|
|
460
|
+
createPolicyRequest: apiProcedure
|
|
461
|
+
.input(
|
|
462
|
+
z.object({
|
|
463
|
+
commandName: z.string(),
|
|
464
|
+
args: z.array(z.string()),
|
|
465
|
+
fileMappings: z.record(z.string(), z.string()),
|
|
466
|
+
chatId: z.string().optional(),
|
|
467
|
+
})
|
|
468
|
+
)
|
|
469
|
+
.mutation(async ({ input, ctx }) => {
|
|
470
|
+
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
471
|
+
const snapshotDir = path.join(getClawminiDir(process.cwd()), 'tmp', 'snapshots');
|
|
472
|
+
const store = new RequestStore(process.cwd());
|
|
473
|
+
const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
|
|
474
|
+
const service = new PolicyRequestService(store, agentDir, snapshotDir);
|
|
475
|
+
|
|
476
|
+
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
477
|
+
const agentId = ctx.tokenPayload?.agentId ?? 'unknown';
|
|
478
|
+
|
|
479
|
+
const request = await service.createRequest(
|
|
480
|
+
input.commandName,
|
|
481
|
+
input.args,
|
|
482
|
+
input.fileMappings,
|
|
483
|
+
chatId,
|
|
484
|
+
agentId
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
const { generateRequestPreview } = await import('./policy-utils.js');
|
|
488
|
+
const previewContent = await generateRequestPreview(request);
|
|
489
|
+
|
|
490
|
+
const logMsg = {
|
|
491
|
+
id: (await import('node:crypto')).randomUUID(),
|
|
492
|
+
// TODO: we should store the message ID in the CLAW_API_TOKEN, and extract it here
|
|
493
|
+
messageId: (await import('node:crypto')).randomUUID(),
|
|
494
|
+
role: 'log' as const,
|
|
495
|
+
source: 'router' as const,
|
|
496
|
+
content: previewContent,
|
|
497
|
+
stderr: '',
|
|
498
|
+
timestamp: new Date().toISOString(),
|
|
499
|
+
command: 'policy-request',
|
|
500
|
+
cwd: process.cwd(),
|
|
501
|
+
exitCode: 0,
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
await import('./chats.js').then((m) => m.appendMessage(chatId, logMsg));
|
|
505
|
+
return request;
|
|
506
|
+
}),
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
export type AppRouter = typeof AppRouter;
|
|
510
|
+
export const appRouter = AppRouter;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { slashCommand } from './slash-command.js';
|
|
3
|
+
import * as workspace from '../../shared/workspace.js';
|
|
4
|
+
import * as fsUtils from '../../shared/utils/fs.js';
|
|
5
|
+
import fs from 'node:fs/promises';
|
|
6
|
+
|
|
7
|
+
vi.mock('node:fs/promises');
|
|
8
|
+
vi.mock('../../shared/workspace.js');
|
|
9
|
+
vi.mock('../../shared/utils/fs.js');
|
|
10
|
+
|
|
11
|
+
describe('slashCommand router', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.resetAllMocks();
|
|
14
|
+
vi.mocked(workspace.getClawminiDir).mockReturnValue('/mock/workspace/.clawmini');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should replace a matching slash command with .md file contents', async () => {
|
|
18
|
+
vi.mocked(fsUtils.pathIsInsideDir).mockReturnValue(true);
|
|
19
|
+
vi.mocked(fs.readFile).mockResolvedValue('Hello from command!\n');
|
|
20
|
+
|
|
21
|
+
const initialState = {
|
|
22
|
+
message: 'Please run /test for me',
|
|
23
|
+
messageId: 'mock-msg-id',
|
|
24
|
+
chatId: 'test-chat',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const newState = await slashCommand(initialState);
|
|
28
|
+
|
|
29
|
+
expect(fsUtils.pathIsInsideDir).toHaveBeenCalledWith(
|
|
30
|
+
'/mock/workspace/.clawmini/commands/test',
|
|
31
|
+
'/mock/workspace/.clawmini/commands'
|
|
32
|
+
);
|
|
33
|
+
expect(fs.readFile).toHaveBeenCalledWith('/mock/workspace/.clawmini/commands/test.md', 'utf8');
|
|
34
|
+
expect(newState.message).toBe('Please run Hello from command! for me');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should fallback to .txt file contents if .md is not found', async () => {
|
|
38
|
+
vi.mocked(fsUtils.pathIsInsideDir).mockReturnValue(true);
|
|
39
|
+
vi.mocked(fs.readFile).mockImplementation(async (path) => {
|
|
40
|
+
if (path === '/mock/workspace/.clawmini/commands/test.txt')
|
|
41
|
+
return 'Hello from txt command!\n';
|
|
42
|
+
throw new Error('Not found');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const initialState = {
|
|
46
|
+
message: 'Please run /test for me',
|
|
47
|
+
messageId: 'mock-msg-id',
|
|
48
|
+
chatId: 'test-chat',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const newState = await slashCommand(initialState);
|
|
52
|
+
|
|
53
|
+
expect(fs.readFile).toHaveBeenCalledWith('/mock/workspace/.clawmini/commands/test.md', 'utf8');
|
|
54
|
+
expect(fs.readFile).toHaveBeenCalledWith('/mock/workspace/.clawmini/commands/test.txt', 'utf8');
|
|
55
|
+
expect(newState.message).toBe('Please run Hello from txt command! for me');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should handle multiple slash commands', async () => {
|
|
59
|
+
vi.mocked(fsUtils.pathIsInsideDir).mockReturnValue(true);
|
|
60
|
+
vi.mocked(fs.readFile).mockImplementation(async (path) => {
|
|
61
|
+
if (path === '/mock/workspace/.clawmini/commands/cmd1.md') return 'first';
|
|
62
|
+
if (path === '/mock/workspace/.clawmini/commands/cmd2.txt') return 'second';
|
|
63
|
+
throw new Error('Not found');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const initialState = {
|
|
67
|
+
message: '/cmd1 and /cmd2',
|
|
68
|
+
messageId: 'mock-msg-id',
|
|
69
|
+
chatId: 'test-chat',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const newState = await slashCommand(initialState);
|
|
73
|
+
expect(newState.message).toBe('first and second');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should leave the message unchanged if command file is not found', async () => {
|
|
77
|
+
vi.mocked(fsUtils.pathIsInsideDir).mockReturnValue(true);
|
|
78
|
+
vi.mocked(fs.readFile).mockRejectedValue(new Error('ENOENT'));
|
|
79
|
+
|
|
80
|
+
const initialState = {
|
|
81
|
+
message: 'Run /missing please',
|
|
82
|
+
messageId: 'mock-msg-id',
|
|
83
|
+
chatId: 'test-chat',
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const newState = await slashCommand(initialState);
|
|
87
|
+
expect(newState.message).toBe('Run /missing please');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should prevent path traversal attacks', async () => {
|
|
91
|
+
vi.mocked(fsUtils.pathIsInsideDir).mockReturnValue(false); // Simulate resolving outside
|
|
92
|
+
|
|
93
|
+
const initialState = {
|
|
94
|
+
message: 'Run /.. please',
|
|
95
|
+
messageId: 'mock-msg-id',
|
|
96
|
+
chatId: 'test-chat',
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const newState = await slashCommand(initialState);
|
|
100
|
+
|
|
101
|
+
expect(fsUtils.pathIsInsideDir).toHaveBeenCalled();
|
|
102
|
+
expect(fs.readFile).not.toHaveBeenCalled();
|
|
103
|
+
expect(newState.message).toBe('Run /.. please');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should support colons in command names', async () => {
|
|
107
|
+
vi.mocked(fsUtils.pathIsInsideDir).mockReturnValue(true);
|
|
108
|
+
vi.mocked(fs.readFile).mockImplementation(async (path) => {
|
|
109
|
+
if (path === '/mock/workspace/.clawmini/commands/foo:bar.md') return 'colon command result';
|
|
110
|
+
throw new Error('Not found');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const initialState = {
|
|
114
|
+
message: '/foo:bar is cool',
|
|
115
|
+
messageId: 'mock-msg-id',
|
|
116
|
+
chatId: 'test-chat',
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const newState = await slashCommand(initialState);
|
|
120
|
+
|
|
121
|
+
expect(fs.readFile).toHaveBeenCalledWith(
|
|
122
|
+
'/mock/workspace/.clawmini/commands/foo:bar.md',
|
|
123
|
+
'utf8'
|
|
124
|
+
);
|
|
125
|
+
expect(newState.message).toBe('colon command result is cool');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should not match commands embedded in words', async () => {
|
|
129
|
+
const initialState = {
|
|
130
|
+
message: 'https://example.com/foo /bar',
|
|
131
|
+
messageId: 'mock-msg-id',
|
|
132
|
+
chatId: 'test-chat',
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
vi.mocked(fsUtils.pathIsInsideDir).mockReturnValue(true);
|
|
136
|
+
vi.mocked(fs.readFile).mockImplementation(async (path) => {
|
|
137
|
+
if (path === '/mock/workspace/.clawmini/commands/bar.md') return 'bar result';
|
|
138
|
+
throw new Error('Not found');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const newState = await slashCommand(initialState);
|
|
142
|
+
expect(fs.readFile).toHaveBeenCalledWith('/mock/workspace/.clawmini/commands/bar.md', 'utf8');
|
|
143
|
+
expect(newState.message).toBe('https://example.com/foo bar result');
|
|
144
|
+
});
|
|
145
|
+
});
|