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,1222 @@
|
|
|
1
|
+
import { C as SettingsSchema, S as CronJobSchema, a as getAgent, b as writeChatSettings, c as getSettingsPath, g as readSettings, h as readEnvironment, i as getActiveEnvironmentInfo, l as getSocketPath, m as readChatSettings, o as getClawminiDir, p as readAgentSessionSettings, s as getEnvironmentPath, u as getWorkspaceRoot, v as writeAgentSessionSettings } from "../workspace-CSgfo_2J.mjs";
|
|
2
|
+
import { n as pathIsInsideDir } from "../fs-B5wW0oaH.mjs";
|
|
3
|
+
import { l as listChats, o as getDefaultChatId, s as getMessages } from "../chats-DKgTeU7i.mjs";
|
|
4
|
+
import { n as exportLiteToEnvironment } from "../lite-Dl7WXyaH.mjs";
|
|
5
|
+
import { a as daemonEvents, i as DAEMON_EVENT_TYPING, o as emitTyping, r as DAEMON_EVENT_MESSAGE_APPENDED, t as appendMessage } from "../chats-Zd_HXDHx.mjs";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { execSync, spawn } from "node:child_process";
|
|
9
|
+
import fs$1 from "node:fs/promises";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import http from "node:http";
|
|
12
|
+
import net from "node:net";
|
|
13
|
+
import { createHTTPHandler } from "@trpc/server/adapters/standalone";
|
|
14
|
+
import { TRPCError, initTRPC } from "@trpc/server";
|
|
15
|
+
import crypto$1 from "node:crypto";
|
|
16
|
+
import schedule from "node-schedule";
|
|
17
|
+
|
|
18
|
+
//#region src/daemon/queue.ts
|
|
19
|
+
var Queue = class {
|
|
20
|
+
pending = [];
|
|
21
|
+
isRunning = false;
|
|
22
|
+
currentController = null;
|
|
23
|
+
currentPayload;
|
|
24
|
+
enqueue(task, payload) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
this.pending.push({
|
|
27
|
+
task,
|
|
28
|
+
payload,
|
|
29
|
+
resolve,
|
|
30
|
+
reject
|
|
31
|
+
});
|
|
32
|
+
this.processNext().catch(() => {});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
async processNext() {
|
|
36
|
+
if (this.isRunning || this.pending.length === 0) return;
|
|
37
|
+
this.isRunning = true;
|
|
38
|
+
const entry = this.pending.shift();
|
|
39
|
+
this.currentController = new AbortController();
|
|
40
|
+
this.currentPayload = entry.payload;
|
|
41
|
+
try {
|
|
42
|
+
await entry.task(this.currentController.signal);
|
|
43
|
+
entry.resolve();
|
|
44
|
+
} catch (error) {
|
|
45
|
+
entry.reject(error);
|
|
46
|
+
} finally {
|
|
47
|
+
this.isRunning = false;
|
|
48
|
+
this.currentController = null;
|
|
49
|
+
this.currentPayload = void 0;
|
|
50
|
+
this.processNext().catch(() => {});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
abortCurrent() {
|
|
54
|
+
if (this.currentController) {
|
|
55
|
+
const error = /* @__PURE__ */ new Error("Task aborted");
|
|
56
|
+
error.name = "AbortError";
|
|
57
|
+
this.currentController.abort(error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
getCurrentPayload() {
|
|
61
|
+
return this.currentPayload;
|
|
62
|
+
}
|
|
63
|
+
clear(reason = "Task cleared") {
|
|
64
|
+
const tasksToClear = [...this.pending];
|
|
65
|
+
this.pending = [];
|
|
66
|
+
for (const { reject } of tasksToClear) {
|
|
67
|
+
const error = new Error(reason);
|
|
68
|
+
error.name = "AbortError";
|
|
69
|
+
reject(error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
extractPending() {
|
|
73
|
+
const extracted = this.pending.map((p) => p.payload).filter((p) => p !== void 0);
|
|
74
|
+
this.clear("Task extracted for batching");
|
|
75
|
+
return extracted;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const directoryQueues = /* @__PURE__ */ new Map();
|
|
79
|
+
function getQueue(dir) {
|
|
80
|
+
if (!directoryQueues.has(dir)) directoryQueues.set(dir, new Queue());
|
|
81
|
+
return directoryQueues.get(dir);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/daemon/routers/slash-new.ts
|
|
86
|
+
function slashNew(state) {
|
|
87
|
+
if (/^\/new(\s|$)/.test(state.message)) {
|
|
88
|
+
const newMessage = state.message.replace(/^\/new(\s+|$)/, "").trim();
|
|
89
|
+
return {
|
|
90
|
+
...state,
|
|
91
|
+
message: newMessage,
|
|
92
|
+
sessionId: crypto.randomUUID(),
|
|
93
|
+
reply: "[@clawmini/slash-new] Starting a new session..."
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return state;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/daemon/routers/slash-command.ts
|
|
101
|
+
async function slashCommand(state) {
|
|
102
|
+
const commandsDir = path.resolve(getClawminiDir(), "commands");
|
|
103
|
+
let currentMessage = state.message;
|
|
104
|
+
const matches = [...currentMessage.matchAll(/(?<=^|\s)\/([a-zA-Z0-9_\-:.]+)(?=\s|$)/g)];
|
|
105
|
+
if (matches.length === 0) return state;
|
|
106
|
+
for (const match of matches) {
|
|
107
|
+
const fullMatch = match[0];
|
|
108
|
+
const commandName = match[1];
|
|
109
|
+
if (!commandName) continue;
|
|
110
|
+
const targetPathMd = path.resolve(commandsDir, `${commandName}.md`);
|
|
111
|
+
const targetPathTxt = path.resolve(commandsDir, `${commandName}.txt`);
|
|
112
|
+
if (!pathIsInsideDir(path.resolve(commandsDir, commandName), commandsDir)) continue;
|
|
113
|
+
let content;
|
|
114
|
+
try {
|
|
115
|
+
content = await fs$1.readFile(targetPathMd, "utf8");
|
|
116
|
+
} catch {
|
|
117
|
+
try {
|
|
118
|
+
content = await fs$1.readFile(targetPathTxt, "utf8");
|
|
119
|
+
} catch {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
currentMessage = currentMessage.replace(fullMatch, content.trim());
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
...state,
|
|
127
|
+
message: currentMessage
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region src/daemon/routers/utils.ts
|
|
133
|
+
function createSlashActionRouter(command, action, replyMessage) {
|
|
134
|
+
return function(state) {
|
|
135
|
+
if (new RegExp(`^\\/${command}(\\s|$)`).test(state.message)) {
|
|
136
|
+
const replaceRegex = new RegExp(`^\\/${command}(\\s+|$)`);
|
|
137
|
+
const newMessage = state.message.replace(replaceRegex, "").trim();
|
|
138
|
+
return {
|
|
139
|
+
...state,
|
|
140
|
+
message: newMessage,
|
|
141
|
+
action,
|
|
142
|
+
reply: replyMessage
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return state;
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/daemon/routers/slash-stop.ts
|
|
151
|
+
const slashStop = createSlashActionRouter("stop", "stop", "Stopping current task...");
|
|
152
|
+
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/daemon/routers/slash-interrupt.ts
|
|
155
|
+
const slashInterrupt = createSlashActionRouter("interrupt", "interrupt", "Interrupting current task...");
|
|
156
|
+
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/daemon/routers.ts
|
|
159
|
+
async function executeRouterPipeline(initialState, routers) {
|
|
160
|
+
let state = { ...initialState };
|
|
161
|
+
for (const router of routers) if (router === "@clawmini/slash-new") state = slashNew(state);
|
|
162
|
+
else if (router === "@clawmini/slash-command") state = await slashCommand(state);
|
|
163
|
+
else if (router === "@clawmini/slash-stop") state = slashStop(state);
|
|
164
|
+
else if (router === "@clawmini/slash-interrupt") state = slashInterrupt(state);
|
|
165
|
+
else try {
|
|
166
|
+
state = await executeCustomRouter(router, state);
|
|
167
|
+
} catch (err) {
|
|
168
|
+
console.error(`Router error [${router}]:`, err);
|
|
169
|
+
}
|
|
170
|
+
return state;
|
|
171
|
+
}
|
|
172
|
+
async function executeCustomRouter(command, state) {
|
|
173
|
+
return new Promise((resolve, reject) => {
|
|
174
|
+
const child = spawn(command, { shell: true });
|
|
175
|
+
let stdout = "";
|
|
176
|
+
let stderr = "";
|
|
177
|
+
child.stdout.on("data", (chunk) => {
|
|
178
|
+
stdout += chunk.toString();
|
|
179
|
+
});
|
|
180
|
+
child.stderr.on("data", (chunk) => {
|
|
181
|
+
stderr += chunk.toString();
|
|
182
|
+
});
|
|
183
|
+
const timer = setTimeout(() => {
|
|
184
|
+
child.kill();
|
|
185
|
+
reject(/* @__PURE__ */ new Error("Router execution timed out"));
|
|
186
|
+
}, 1e4);
|
|
187
|
+
child.on("close", (code) => {
|
|
188
|
+
clearTimeout(timer);
|
|
189
|
+
if (code !== 0) return reject(/* @__PURE__ */ new Error(`Process exited with code ${code}. Stderr: ${stderr}`));
|
|
190
|
+
try {
|
|
191
|
+
const result = JSON.parse(stdout);
|
|
192
|
+
const newState = { ...state };
|
|
193
|
+
if (typeof result.message === "string") newState.message = result.message;
|
|
194
|
+
if (typeof result.agent === "string") newState.agentId = result.agent;
|
|
195
|
+
if (typeof result.session === "string") newState.sessionId = result.session;
|
|
196
|
+
if (typeof result.env === "object" && result.env !== null) newState.env = {
|
|
197
|
+
...newState.env,
|
|
198
|
+
...result.env
|
|
199
|
+
};
|
|
200
|
+
if (typeof result.reply === "string") newState.reply = result.reply;
|
|
201
|
+
if (typeof result.action === "string") newState.action = result.action;
|
|
202
|
+
resolve(newState);
|
|
203
|
+
} catch (err) {
|
|
204
|
+
reject(/* @__PURE__ */ new Error(`Failed to parse router output: ${err}. Stdout: ${stdout}`));
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
child.on("error", (err) => {
|
|
208
|
+
clearTimeout(timer);
|
|
209
|
+
reject(err);
|
|
210
|
+
});
|
|
211
|
+
const inputState = {
|
|
212
|
+
message: state.message,
|
|
213
|
+
chatId: state.chatId,
|
|
214
|
+
agentId: state.agentId,
|
|
215
|
+
sessionId: state.sessionId,
|
|
216
|
+
env: state.env,
|
|
217
|
+
action: state.action
|
|
218
|
+
};
|
|
219
|
+
if (child.stdin) {
|
|
220
|
+
child.stdin.on("error", (err) => {
|
|
221
|
+
if (err.code !== "EPIPE") console.error("stdin error:", err);
|
|
222
|
+
});
|
|
223
|
+
child.stdin.write(JSON.stringify(inputState));
|
|
224
|
+
child.stdin.end();
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
//#endregion
|
|
230
|
+
//#region src/daemon/auth.ts
|
|
231
|
+
const DAEMON_SECRET = crypto$1.randomBytes(32);
|
|
232
|
+
function generateToken(payload) {
|
|
233
|
+
const payloadStr = Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
234
|
+
return `${payloadStr}.${crypto$1.createHmac("sha256", DAEMON_SECRET).update(payloadStr).digest("hex")}`;
|
|
235
|
+
}
|
|
236
|
+
function validateToken(token) {
|
|
237
|
+
try {
|
|
238
|
+
const parts = token.split(".");
|
|
239
|
+
if (parts.length !== 2) return null;
|
|
240
|
+
const [payloadStr, signature] = parts;
|
|
241
|
+
if (!payloadStr || !signature) return null;
|
|
242
|
+
const expectedHmac = crypto$1.createHmac("sha256", DAEMON_SECRET).update(payloadStr).digest("hex");
|
|
243
|
+
const signatureBuffer = Buffer.from(signature, "hex");
|
|
244
|
+
const expectedHmacBuffer = Buffer.from(expectedHmac, "hex");
|
|
245
|
+
if (signatureBuffer.length !== expectedHmacBuffer.length || !crypto$1.timingSafeEqual(signatureBuffer, expectedHmacBuffer)) return null;
|
|
246
|
+
const payloadJson = Buffer.from(payloadStr, "base64").toString("utf8");
|
|
247
|
+
return JSON.parse(payloadJson);
|
|
248
|
+
} catch {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function getApiContext(settings) {
|
|
253
|
+
if (settings?.api === void 0) return null;
|
|
254
|
+
let isApiEnabled = false;
|
|
255
|
+
let apiHost = "127.0.0.1";
|
|
256
|
+
let apiPort = 3e3;
|
|
257
|
+
let proxyHost = void 0;
|
|
258
|
+
if (typeof settings.api === "boolean") isApiEnabled = settings.api;
|
|
259
|
+
else if (typeof settings.api === "object") {
|
|
260
|
+
isApiEnabled = true;
|
|
261
|
+
apiHost = settings.api.host ?? "127.0.0.1";
|
|
262
|
+
apiPort = settings.api.port ?? 3e3;
|
|
263
|
+
proxyHost = settings.api.proxy_host;
|
|
264
|
+
}
|
|
265
|
+
if (!isApiEnabled) return null;
|
|
266
|
+
return {
|
|
267
|
+
host: apiHost,
|
|
268
|
+
port: apiPort,
|
|
269
|
+
proxy_host: proxyHost
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region src/shared/utils/env.ts
|
|
275
|
+
function applyEnvOverrides(targetEnv, overrides) {
|
|
276
|
+
if (!overrides) return;
|
|
277
|
+
for (const [key, val] of Object.entries(overrides)) if (val === true && process.env[key] !== void 0) targetEnv[key] = process.env[key];
|
|
278
|
+
else if (typeof val === "string") targetEnv[key] = val;
|
|
279
|
+
}
|
|
280
|
+
function getActiveEnvKeys(...envs) {
|
|
281
|
+
const keys = /* @__PURE__ */ new Set();
|
|
282
|
+
for (const env of envs) {
|
|
283
|
+
if (!env) continue;
|
|
284
|
+
Object.entries(env).forEach(([key, val]) => {
|
|
285
|
+
if (val === true || typeof val === "string") keys.add(key);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
return keys;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
//#endregion
|
|
292
|
+
//#region src/daemon/message.ts
|
|
293
|
+
function calculateDelay(attempt, baseDelayMs, isFallback = false) {
|
|
294
|
+
const effectiveAttempt = isFallback ? attempt + 1 : attempt;
|
|
295
|
+
if (effectiveAttempt <= 0) return 0;
|
|
296
|
+
const delay = baseDelayMs * Math.pow(2, effectiveAttempt - 1);
|
|
297
|
+
return Math.min(delay, 15e3);
|
|
298
|
+
}
|
|
299
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
300
|
+
async function resolveSessionState(chatId, cwd, sessionId, overrideAgentId) {
|
|
301
|
+
const chatSettings = await readChatSettings(chatId, cwd);
|
|
302
|
+
const agentId = overrideAgentId ?? (typeof chatSettings?.defaultAgent === "string" ? chatSettings.defaultAgent : "default");
|
|
303
|
+
let targetSessionId = sessionId;
|
|
304
|
+
if (!targetSessionId) targetSessionId = (chatSettings?.sessions || {})[agentId] || "default";
|
|
305
|
+
const agentSessionSettings = await readAgentSessionSettings(agentId, targetSessionId, cwd);
|
|
306
|
+
return {
|
|
307
|
+
chatSettings,
|
|
308
|
+
agentId,
|
|
309
|
+
targetSessionId,
|
|
310
|
+
agentSessionSettings,
|
|
311
|
+
isNewSession: !agentSessionSettings
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
function prepareCommandAndEnv(agent, message, isNewSession, agentSessionSettings, fallback) {
|
|
315
|
+
const currentAgent = {
|
|
316
|
+
...agent,
|
|
317
|
+
commands: {
|
|
318
|
+
...agent.commands,
|
|
319
|
+
...fallback?.commands || {}
|
|
320
|
+
},
|
|
321
|
+
env: {
|
|
322
|
+
...agent.env,
|
|
323
|
+
...fallback?.env || {}
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
let command = currentAgent.commands?.new ?? "";
|
|
327
|
+
const env = {
|
|
328
|
+
...process.env,
|
|
329
|
+
CLAW_CLI_MESSAGE: message
|
|
330
|
+
};
|
|
331
|
+
applyEnvOverrides(env, currentAgent.env);
|
|
332
|
+
if (!isNewSession && currentAgent.commands?.append) {
|
|
333
|
+
command = currentAgent.commands.append;
|
|
334
|
+
applyEnvOverrides(env, agentSessionSettings?.env);
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
command,
|
|
338
|
+
env,
|
|
339
|
+
currentAgent
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
async function runExtractionCommand(name, command, runCommand, cwd, env, mainResult, signal) {
|
|
343
|
+
try {
|
|
344
|
+
console.log(`Executing extraction command (${name}): ${command}`);
|
|
345
|
+
const res = await runCommand({
|
|
346
|
+
command,
|
|
347
|
+
cwd,
|
|
348
|
+
env,
|
|
349
|
+
stdin: mainResult.stdout,
|
|
350
|
+
signal
|
|
351
|
+
});
|
|
352
|
+
if (res.exitCode === 0) return { result: res.stdout.trim() };
|
|
353
|
+
else return { error: `${name} failed: ${res.stderr}` };
|
|
354
|
+
} catch (e) {
|
|
355
|
+
return { error: `${name} error: ${e.message}` };
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Formats the environment prefix string by replacing placeholders with actual values.
|
|
360
|
+
* Available placeholders:
|
|
361
|
+
* - {WORKSPACE_DIR}: The root directory of the workspace.
|
|
362
|
+
* - {AGENT_DIR}: The directory where the agent is executing.
|
|
363
|
+
* - {ENV_DIR}: The directory of the active environment.
|
|
364
|
+
* - {HOME_DIR}: The home directory of the current user.
|
|
365
|
+
* - {ENV_ARGS}: The formatted environment arguments based on envFormat.
|
|
366
|
+
*/
|
|
367
|
+
function formatEnvironmentPrefix(prefix, replacements) {
|
|
368
|
+
const map = {
|
|
369
|
+
"{WORKSPACE_DIR}": replacements.targetPath,
|
|
370
|
+
"{AGENT_DIR}": replacements.executionCwd,
|
|
371
|
+
"{ENV_DIR}": replacements.envDir,
|
|
372
|
+
"{HOME_DIR}": process.env.HOME || "",
|
|
373
|
+
"{ENV_ARGS}": replacements.envArgs
|
|
374
|
+
};
|
|
375
|
+
return prefix.replace(/{(WORKSPACE_DIR|AGENT_DIR|ENV_DIR|HOME_DIR|ENV_ARGS)}/g, (match) => map[match] || match);
|
|
376
|
+
}
|
|
377
|
+
async function executeDirectMessage(chatId, state, settings, cwd, runCommand, noWait = true, userMessageContent) {
|
|
378
|
+
const userMsg = {
|
|
379
|
+
id: crypto.randomUUID(),
|
|
380
|
+
role: "user",
|
|
381
|
+
content: userMessageContent ?? state.message,
|
|
382
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
383
|
+
};
|
|
384
|
+
await appendMessage(chatId, userMsg);
|
|
385
|
+
if (state.reply) await appendMessage(chatId, {
|
|
386
|
+
id: crypto.randomUUID(),
|
|
387
|
+
messageId: userMsg.id,
|
|
388
|
+
role: "log",
|
|
389
|
+
source: "router",
|
|
390
|
+
content: state.reply,
|
|
391
|
+
stderr: "",
|
|
392
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
393
|
+
command: "router",
|
|
394
|
+
cwd,
|
|
395
|
+
exitCode: 0,
|
|
396
|
+
...state.reply.includes("NO_REPLY_NECESSARY") ? { level: "verbose" } : {}
|
|
397
|
+
});
|
|
398
|
+
if (!state.message.trim() && state.action !== "stop" && state.action !== "interrupt") return;
|
|
399
|
+
const queue = getQueue(cwd);
|
|
400
|
+
if (state.action === "stop") {
|
|
401
|
+
queue.abortCurrent();
|
|
402
|
+
queue.clear();
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if (state.action === "interrupt") {
|
|
406
|
+
const currentPayload = queue.getCurrentPayload();
|
|
407
|
+
queue.abortCurrent();
|
|
408
|
+
const extracted = queue.extractPending();
|
|
409
|
+
const payloads = currentPayload ? [currentPayload, ...extracted] : extracted;
|
|
410
|
+
if (payloads.length > 0) state.message = `${payloads.map((text) => `<message>\n${text}\n</message>`).join("\n\n")}\n\n<message>\n${state.message}\n</message>`.trim();
|
|
411
|
+
}
|
|
412
|
+
if (!state.message.trim()) return;
|
|
413
|
+
const routerEnv = state.env ?? {};
|
|
414
|
+
const taskPromise = queue.enqueue(async (signal) => {
|
|
415
|
+
const { agentId, agentSessionSettings, isNewSession, targetSessionId: finalSessionId } = await resolveSessionState(chatId, cwd, state.sessionId, state.agentId);
|
|
416
|
+
let mergedAgent = settings?.defaultAgent || {};
|
|
417
|
+
if (agentId !== "default") try {
|
|
418
|
+
const customAgent = await getAgent(agentId, cwd);
|
|
419
|
+
if (customAgent) mergedAgent = {
|
|
420
|
+
...mergedAgent,
|
|
421
|
+
...customAgent,
|
|
422
|
+
commands: {
|
|
423
|
+
...mergedAgent.commands,
|
|
424
|
+
...customAgent.commands
|
|
425
|
+
},
|
|
426
|
+
env: {
|
|
427
|
+
...mergedAgent.env,
|
|
428
|
+
...customAgent.env
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
} catch {}
|
|
432
|
+
const executionConfigs = [{
|
|
433
|
+
retries: 0,
|
|
434
|
+
delayMs: 1e3
|
|
435
|
+
}, ...(mergedAgent.fallbacks || []).map((f) => ({
|
|
436
|
+
fallback: f,
|
|
437
|
+
retries: f.retries,
|
|
438
|
+
delayMs: f.delayMs
|
|
439
|
+
}))];
|
|
440
|
+
const workspaceRoot = getWorkspaceRoot(cwd);
|
|
441
|
+
let executionCwd = cwd;
|
|
442
|
+
if (mergedAgent.directory) executionCwd = path.resolve(workspaceRoot, mergedAgent.directory);
|
|
443
|
+
else if (agentId !== "default") executionCwd = path.resolve(workspaceRoot, agentId);
|
|
444
|
+
let lastLogMsg;
|
|
445
|
+
let success = false;
|
|
446
|
+
for (let configIdx = 0; configIdx < executionConfigs.length; configIdx++) {
|
|
447
|
+
const config = executionConfigs[configIdx];
|
|
448
|
+
const isFallbackConfig = configIdx > 0;
|
|
449
|
+
for (let attempt = 0; attempt <= config.retries; attempt++) {
|
|
450
|
+
const delay = calculateDelay(attempt, config.delayMs, isFallbackConfig);
|
|
451
|
+
if (delay > 0) {
|
|
452
|
+
await appendMessage(chatId, {
|
|
453
|
+
id: crypto.randomUUID(),
|
|
454
|
+
messageId: userMsg.id,
|
|
455
|
+
role: "log",
|
|
456
|
+
content: `Error running agent, retrying in ${Math.round(delay / 1e3)} seconds...`,
|
|
457
|
+
stderr: "",
|
|
458
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
459
|
+
command: "retry-delay",
|
|
460
|
+
cwd: executionCwd,
|
|
461
|
+
exitCode: 0
|
|
462
|
+
});
|
|
463
|
+
await sleep(delay);
|
|
464
|
+
}
|
|
465
|
+
const { env, currentAgent, command: initialCommand } = prepareCommandAndEnv(mergedAgent, state.message, isNewSession, agentSessionSettings, config.fallback);
|
|
466
|
+
let command = initialCommand;
|
|
467
|
+
if (!command) continue;
|
|
468
|
+
const agentSpecificEnv = getActiveEnvKeys(currentAgent.env, !isNewSession ? agentSessionSettings?.env : void 0);
|
|
469
|
+
agentSpecificEnv.add("CLAW_CLI_MESSAGE");
|
|
470
|
+
Object.assign(env, routerEnv);
|
|
471
|
+
Object.keys(routerEnv).forEach((k) => agentSpecificEnv.add(k));
|
|
472
|
+
const apiCtx = getApiContext(settings);
|
|
473
|
+
if (apiCtx) {
|
|
474
|
+
env["CLAW_API_URL"] = apiCtx.proxy_host ? `${apiCtx.proxy_host}:${apiCtx.port}` : `http://${apiCtx.host}:${apiCtx.port}`;
|
|
475
|
+
agentSpecificEnv.add("CLAW_API_URL");
|
|
476
|
+
env["CLAW_API_TOKEN"] = generateToken({
|
|
477
|
+
chatId,
|
|
478
|
+
agentId,
|
|
479
|
+
sessionId: finalSessionId,
|
|
480
|
+
timestamp: Date.now()
|
|
481
|
+
});
|
|
482
|
+
agentSpecificEnv.add("CLAW_API_TOKEN");
|
|
483
|
+
}
|
|
484
|
+
const activeEnvInfo = await getActiveEnvironmentInfo(executionCwd, cwd);
|
|
485
|
+
if (activeEnvInfo) {
|
|
486
|
+
const activeEnvName = activeEnvInfo.name;
|
|
487
|
+
const activeEnv = await readEnvironment(activeEnvName, cwd);
|
|
488
|
+
if (activeEnv?.env) for (const [key, value] of Object.entries(activeEnv.env)) if (value === false) {
|
|
489
|
+
delete env[key];
|
|
490
|
+
agentSpecificEnv.delete(key);
|
|
491
|
+
} else {
|
|
492
|
+
let interpolatedValue = String(value);
|
|
493
|
+
interpolatedValue = interpolatedValue.replace(/\{PATH\}/g, process.env.PATH || "");
|
|
494
|
+
interpolatedValue = interpolatedValue.replace(/\{ENV_DIR\}/g, getEnvironmentPath(activeEnvName, cwd));
|
|
495
|
+
interpolatedValue = interpolatedValue.replace(/\{WORKSPACE_DIR\}/g, activeEnvInfo.targetPath);
|
|
496
|
+
env[key] = interpolatedValue;
|
|
497
|
+
agentSpecificEnv.add(key);
|
|
498
|
+
}
|
|
499
|
+
if (activeEnv?.prefix) {
|
|
500
|
+
const envArgs = Array.from(agentSpecificEnv).map((key) => {
|
|
501
|
+
if (activeEnv.envFormat) return activeEnv.envFormat.replace("{key}", key);
|
|
502
|
+
return key;
|
|
503
|
+
}).join(" ");
|
|
504
|
+
const prefixReplaced = formatEnvironmentPrefix(activeEnv.prefix, {
|
|
505
|
+
targetPath: activeEnvInfo.targetPath,
|
|
506
|
+
executionCwd,
|
|
507
|
+
envDir: getEnvironmentPath(activeEnvName, cwd),
|
|
508
|
+
envArgs
|
|
509
|
+
});
|
|
510
|
+
if (prefixReplaced.includes("{COMMAND}")) command = prefixReplaced.replace("{COMMAND}", command);
|
|
511
|
+
else command = `${prefixReplaced} ${command}`;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
console.log(`Executing command: ${command}`);
|
|
515
|
+
let mainResult;
|
|
516
|
+
const typingInterval = setInterval(() => {
|
|
517
|
+
emitTyping(chatId);
|
|
518
|
+
}, 5e3);
|
|
519
|
+
try {
|
|
520
|
+
mainResult = await runCommand({
|
|
521
|
+
command,
|
|
522
|
+
cwd: executionCwd,
|
|
523
|
+
env,
|
|
524
|
+
signal
|
|
525
|
+
});
|
|
526
|
+
} finally {
|
|
527
|
+
clearInterval(typingInterval);
|
|
528
|
+
}
|
|
529
|
+
const logMsg = {
|
|
530
|
+
id: crypto.randomUUID(),
|
|
531
|
+
messageId: userMsg.id,
|
|
532
|
+
role: "log",
|
|
533
|
+
content: mainResult.stdout,
|
|
534
|
+
stdout: mainResult.stdout,
|
|
535
|
+
stderr: "",
|
|
536
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
537
|
+
command,
|
|
538
|
+
cwd: executionCwd,
|
|
539
|
+
exitCode: mainResult.exitCode,
|
|
540
|
+
...mainResult.stdout.includes("NO_REPLY_NECESSARY") ? { level: "verbose" } : {}
|
|
541
|
+
};
|
|
542
|
+
const errors = [];
|
|
543
|
+
if (mainResult.stderr) errors.push(mainResult.stderr);
|
|
544
|
+
let currentSuccess = mainResult.exitCode === 0;
|
|
545
|
+
if (currentSuccess) {
|
|
546
|
+
if (currentAgent.commands?.getMessageContent) {
|
|
547
|
+
const { result, error } = await runExtractionCommand("getMessageContent", currentAgent.commands.getMessageContent, runCommand, executionCwd, env, mainResult, signal);
|
|
548
|
+
if (result !== void 0) {
|
|
549
|
+
logMsg.content = result;
|
|
550
|
+
logMsg.stdout = mainResult.stdout;
|
|
551
|
+
if (result.includes("NO_REPLY_NECESSARY")) logMsg.level = "verbose";
|
|
552
|
+
else delete logMsg.level;
|
|
553
|
+
if (result.trim() === "") currentSuccess = false;
|
|
554
|
+
}
|
|
555
|
+
if (error) errors.push(error);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
logMsg.stderr = errors.join("\n\n");
|
|
559
|
+
lastLogMsg = logMsg;
|
|
560
|
+
if (currentSuccess) {
|
|
561
|
+
success = true;
|
|
562
|
+
if (isNewSession && currentAgent.commands?.getSessionId) {
|
|
563
|
+
const { result, error } = await runExtractionCommand("getSessionId", currentAgent.commands.getSessionId, runCommand, executionCwd, env, mainResult, signal);
|
|
564
|
+
if (result) await writeAgentSessionSettings(agentId, finalSessionId, { env: { SESSION_ID: result } }, cwd);
|
|
565
|
+
if (error) logMsg.stderr = [logMsg.stderr, error].filter(Boolean).join("\n\n");
|
|
566
|
+
}
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (success) break;
|
|
571
|
+
}
|
|
572
|
+
if (lastLogMsg) await appendMessage(chatId, lastLogMsg);
|
|
573
|
+
}, state.message);
|
|
574
|
+
if (!noWait) await taskPromise;
|
|
575
|
+
else taskPromise.catch((err) => {
|
|
576
|
+
if (err.name !== "AbortError") console.error("Task execution error:", err);
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
async function getInitialRouterState(chatId, message, cwd = process.cwd(), overrideAgentId, overrideSessionId) {
|
|
580
|
+
const chatSettings = await readChatSettings(chatId, cwd) ?? {};
|
|
581
|
+
const agentId = overrideAgentId ?? chatSettings.defaultAgent ?? "default";
|
|
582
|
+
return {
|
|
583
|
+
message,
|
|
584
|
+
chatId,
|
|
585
|
+
agentId,
|
|
586
|
+
sessionId: overrideSessionId ?? chatSettings.sessions?.[agentId] ?? "default",
|
|
587
|
+
env: {}
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
async function handleUserMessage(chatId, message, settings, cwd = process.cwd(), noWait = false, runCommand, sessionId, overrideAgentId) {
|
|
591
|
+
const chatSettings = await readChatSettings(chatId, cwd) ?? {};
|
|
592
|
+
if (overrideAgentId && chatSettings.defaultAgent !== overrideAgentId) {
|
|
593
|
+
chatSettings.defaultAgent = overrideAgentId;
|
|
594
|
+
await writeChatSettings(chatId, chatSettings, cwd);
|
|
595
|
+
}
|
|
596
|
+
const initialState = await getInitialRouterState(chatId, message, cwd, overrideAgentId, sessionId);
|
|
597
|
+
const initialAgent = initialState.agentId;
|
|
598
|
+
const finalState = await executeRouterPipeline(initialState, chatSettings.routers ?? settings?.routers ?? []);
|
|
599
|
+
const finalMessage = finalState.message;
|
|
600
|
+
const finalAgentId = finalState.agentId;
|
|
601
|
+
const finalSessionId = finalState.sessionId ?? crypto.randomUUID();
|
|
602
|
+
const routerEnv = finalState.env ?? {};
|
|
603
|
+
const currentAgentId = finalAgentId ?? chatSettings.defaultAgent ?? "default";
|
|
604
|
+
let settingsChanged = false;
|
|
605
|
+
if (finalAgentId && finalAgentId !== initialAgent) {
|
|
606
|
+
chatSettings.defaultAgent = finalAgentId;
|
|
607
|
+
settingsChanged = true;
|
|
608
|
+
}
|
|
609
|
+
if (finalSessionId && chatSettings.sessions?.[currentAgentId] !== finalSessionId) {
|
|
610
|
+
chatSettings.sessions = chatSettings.sessions || {};
|
|
611
|
+
chatSettings.sessions[currentAgentId] = finalSessionId;
|
|
612
|
+
settingsChanged = true;
|
|
613
|
+
}
|
|
614
|
+
if (settingsChanged) await writeChatSettings(chatId, chatSettings, cwd);
|
|
615
|
+
const directState = {
|
|
616
|
+
message: finalMessage,
|
|
617
|
+
chatId,
|
|
618
|
+
env: routerEnv
|
|
619
|
+
};
|
|
620
|
+
if (finalAgentId !== void 0) directState.agentId = finalAgentId;
|
|
621
|
+
if (finalSessionId !== void 0) directState.sessionId = finalSessionId;
|
|
622
|
+
if (finalState.reply !== void 0) directState.reply = finalState.reply;
|
|
623
|
+
if (finalState.action !== void 0) directState.action = finalState.action;
|
|
624
|
+
await executeDirectMessage(chatId, directState, settings, cwd, runCommand, noWait, message);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
//#endregion
|
|
628
|
+
//#region src/daemon/utils/spawn.ts
|
|
629
|
+
const runCommand = async ({ command, cwd, env, stdin, signal, logToTerminal }) => {
|
|
630
|
+
return new Promise((resolve, reject) => {
|
|
631
|
+
const p = spawn(command, {
|
|
632
|
+
shell: true,
|
|
633
|
+
cwd,
|
|
634
|
+
env,
|
|
635
|
+
signal
|
|
636
|
+
});
|
|
637
|
+
if (stdin && p.stdin) {
|
|
638
|
+
p.stdin.on("error", (err) => {
|
|
639
|
+
if (err.code !== "EPIPE") console.error("stdin error:", err);
|
|
640
|
+
});
|
|
641
|
+
p.stdin.write(stdin);
|
|
642
|
+
p.stdin.end();
|
|
643
|
+
}
|
|
644
|
+
let stdout = "";
|
|
645
|
+
let stderr = "";
|
|
646
|
+
if (p.stdout) p.stdout.on("data", (data) => {
|
|
647
|
+
stdout += data.toString();
|
|
648
|
+
if (logToTerminal && !stdin) process.stdout.write(data);
|
|
649
|
+
});
|
|
650
|
+
if (p.stderr) p.stderr.on("data", (data) => {
|
|
651
|
+
stderr += data.toString();
|
|
652
|
+
if (logToTerminal && !stdin) process.stderr.write(data);
|
|
653
|
+
});
|
|
654
|
+
p.on("close", (code) => {
|
|
655
|
+
resolve({
|
|
656
|
+
stdout,
|
|
657
|
+
stderr,
|
|
658
|
+
exitCode: code ?? 1
|
|
659
|
+
});
|
|
660
|
+
});
|
|
661
|
+
p.on("error", (err) => {
|
|
662
|
+
if (err.name === "AbortError") {
|
|
663
|
+
reject(err);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
resolve({
|
|
667
|
+
stdout: "",
|
|
668
|
+
stderr: err.toString(),
|
|
669
|
+
exitCode: 1
|
|
670
|
+
});
|
|
671
|
+
});
|
|
672
|
+
});
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
//#endregion
|
|
676
|
+
//#region src/daemon/cron.ts
|
|
677
|
+
var CronManager = class {
|
|
678
|
+
jobs = /* @__PURE__ */ new Map();
|
|
679
|
+
getJobKey(chatId, jobId) {
|
|
680
|
+
return `${chatId}::${jobId}`;
|
|
681
|
+
}
|
|
682
|
+
async init() {
|
|
683
|
+
const chats = await listChats();
|
|
684
|
+
for (const chatId of chats) {
|
|
685
|
+
const settings = await readChatSettings(chatId);
|
|
686
|
+
if (settings?.jobs) for (const job of settings.jobs) try {
|
|
687
|
+
this.scheduleJob(chatId, job);
|
|
688
|
+
} catch (err) {
|
|
689
|
+
console.error(`Failed to initialize job ${job.id} for chat ${chatId}:`, err instanceof Error ? err.message : err);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
scheduleJob(chatId, job) {
|
|
694
|
+
this.unscheduleJob(chatId, job.id);
|
|
695
|
+
let rule;
|
|
696
|
+
let isOneOff = false;
|
|
697
|
+
if ("cron" in job.schedule) rule = job.schedule.cron;
|
|
698
|
+
else if ("every" in job.schedule) {
|
|
699
|
+
const everyStr = job.schedule.every;
|
|
700
|
+
const match = everyStr.match(/^(\d+)\s*(m|min|minutes?|h|hours?|d|days?)$/i);
|
|
701
|
+
if (match) {
|
|
702
|
+
const val = parseInt(match[1], 10);
|
|
703
|
+
const unit = match[2].toLowerCase();
|
|
704
|
+
if (unit.startsWith("m")) rule = `*/${val} * * * *`;
|
|
705
|
+
else if (unit.startsWith("h")) rule = `0 */${val} * * *`;
|
|
706
|
+
else if (unit.startsWith("d")) rule = `0 0 */${val} * *`;
|
|
707
|
+
else rule = everyStr;
|
|
708
|
+
} else rule = everyStr;
|
|
709
|
+
} else if ("at" in job.schedule) {
|
|
710
|
+
const atStr = job.schedule.at;
|
|
711
|
+
const match = atStr.match(/^(\d+)\s*(m|min|minutes?|h|hours?|d|days?|s|sec|seconds?)$/i);
|
|
712
|
+
if (match) {
|
|
713
|
+
const val = parseInt(match[1], 10);
|
|
714
|
+
const unit = match[2].toLowerCase();
|
|
715
|
+
let ms = 0;
|
|
716
|
+
if (unit.startsWith("s")) ms = val * 1e3;
|
|
717
|
+
else if (unit.startsWith("m")) ms = val * 60 * 1e3;
|
|
718
|
+
else if (unit.startsWith("h")) ms = val * 60 * 60 * 1e3;
|
|
719
|
+
else if (unit.startsWith("d")) ms = val * 24 * 60 * 60 * 1e3;
|
|
720
|
+
rule = new Date(Date.now() + ms);
|
|
721
|
+
} else {
|
|
722
|
+
rule = new Date(atStr);
|
|
723
|
+
if (isNaN(rule.getTime())) throw new Error(`Invalid date format for 'at' schedule: ${atStr}`);
|
|
724
|
+
}
|
|
725
|
+
isOneOff = true;
|
|
726
|
+
} else {
|
|
727
|
+
console.warn(`Unknown schedule format for job ${job.id}`);
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
try {
|
|
731
|
+
const scheduledJob = schedule.scheduleJob(rule, async () => {
|
|
732
|
+
await this.executeJob(chatId, job, isOneOff);
|
|
733
|
+
});
|
|
734
|
+
if (scheduledJob) this.jobs.set(this.getJobKey(chatId, job.id), scheduledJob);
|
|
735
|
+
} catch (err) {
|
|
736
|
+
console.error(`Failed to schedule job ${job.id} for chat ${chatId}:`, err);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
unscheduleJob(chatId, jobId) {
|
|
740
|
+
const key = this.getJobKey(chatId, jobId);
|
|
741
|
+
const job = this.jobs.get(key);
|
|
742
|
+
if (job) {
|
|
743
|
+
job.cancel();
|
|
744
|
+
this.jobs.delete(key);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
async executeJob(chatId, job, isOneOff) {
|
|
748
|
+
try {
|
|
749
|
+
const settingsPath = getSettingsPath();
|
|
750
|
+
let globalSettings;
|
|
751
|
+
try {
|
|
752
|
+
const settingsStr = await fs$1.readFile(settingsPath, "utf8");
|
|
753
|
+
globalSettings = JSON.parse(settingsStr);
|
|
754
|
+
} catch {
|
|
755
|
+
globalSettings = void 0;
|
|
756
|
+
}
|
|
757
|
+
const overrideSessionId = job.session?.type === "new" ? crypto.randomUUID() : void 0;
|
|
758
|
+
const routerState = await getInitialRouterState(chatId, job.message, process.cwd(), job.agentId, overrideSessionId);
|
|
759
|
+
if (job.env !== void 0) {
|
|
760
|
+
routerState.env = routerState.env || {};
|
|
761
|
+
applyEnvOverrides(routerState.env, job.env);
|
|
762
|
+
}
|
|
763
|
+
if (job.reply !== void 0) routerState.reply = job.reply;
|
|
764
|
+
await executeDirectMessage(chatId, routerState, globalSettings, process.cwd(), runCommand, false, job.message);
|
|
765
|
+
if (isOneOff) {
|
|
766
|
+
const chatSettings = await readChatSettings(chatId);
|
|
767
|
+
if (chatSettings && chatSettings.jobs) {
|
|
768
|
+
chatSettings.jobs = chatSettings.jobs.filter((j) => j.id !== job.id);
|
|
769
|
+
await writeChatSettings(chatId, chatSettings);
|
|
770
|
+
}
|
|
771
|
+
this.unscheduleJob(chatId, job.id);
|
|
772
|
+
}
|
|
773
|
+
} catch (err) {
|
|
774
|
+
console.error(`Error executing cron job ${job.id} for chat ${chatId}:`, err);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
const cronManager = new CronManager();
|
|
779
|
+
|
|
780
|
+
//#endregion
|
|
781
|
+
//#region src/daemon/router.ts
|
|
782
|
+
const t = initTRPC.context().create();
|
|
783
|
+
const router = t.router;
|
|
784
|
+
const publicProcedure = t.procedure;
|
|
785
|
+
const apiAuthMiddleware = t.middleware(({ ctx, next }) => {
|
|
786
|
+
if (ctx.isApiServer) {
|
|
787
|
+
if (!ctx.tokenPayload) throw new TRPCError({
|
|
788
|
+
code: "UNAUTHORIZED",
|
|
789
|
+
message: "Missing or invalid token"
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
return next({ ctx: {
|
|
793
|
+
...ctx,
|
|
794
|
+
tokenPayload: ctx.tokenPayload
|
|
795
|
+
} });
|
|
796
|
+
});
|
|
797
|
+
const apiProcedure = t.procedure.use(apiAuthMiddleware);
|
|
798
|
+
async function getUniquePath(p) {
|
|
799
|
+
let currentPath = p;
|
|
800
|
+
let counter = 1;
|
|
801
|
+
while (true) try {
|
|
802
|
+
await fs$1.stat(currentPath);
|
|
803
|
+
const ext = path.extname(p);
|
|
804
|
+
const base = path.basename(p, ext);
|
|
805
|
+
currentPath = path.join(path.dirname(p), `${base}-${counter}${ext}`);
|
|
806
|
+
counter++;
|
|
807
|
+
} catch {
|
|
808
|
+
return currentPath;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
async function resolveAgentDir(agentId, workspaceRoot) {
|
|
812
|
+
if (agentId && agentId !== "default") {
|
|
813
|
+
try {
|
|
814
|
+
const agent = await getAgent(agentId, workspaceRoot);
|
|
815
|
+
if (agent && agent.directory) return path.resolve(workspaceRoot, agent.directory);
|
|
816
|
+
} catch (err) {
|
|
817
|
+
console.warn(`Could not load custom agent '${agentId}' for resolving directory:`, err);
|
|
818
|
+
}
|
|
819
|
+
return path.resolve(workspaceRoot, agentId);
|
|
820
|
+
}
|
|
821
|
+
return workspaceRoot;
|
|
822
|
+
}
|
|
823
|
+
async function resolveAndCheckChatId(ctx, inputChatId) {
|
|
824
|
+
const chatId = inputChatId ?? (ctx.isApiServer && ctx.tokenPayload ? ctx.tokenPayload.chatId : await getDefaultChatId());
|
|
825
|
+
if (ctx.isApiServer && ctx.tokenPayload) {
|
|
826
|
+
if (ctx.tokenPayload.chatId !== chatId) throw new TRPCError({
|
|
827
|
+
code: "FORBIDDEN",
|
|
828
|
+
message: "Token not authorized for this chat"
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
return chatId;
|
|
832
|
+
}
|
|
833
|
+
async function getAgentFilesDir(agentId, chatId, settings, workspaceRoot) {
|
|
834
|
+
const chatSettings = await readChatSettings(chatId) ?? {};
|
|
835
|
+
const targetAgentId = agentId ?? chatSettings.defaultAgent ?? "default";
|
|
836
|
+
let agentFilesDir = settings?.defaultAgent?.files || "./attachments";
|
|
837
|
+
const agentDir = await resolveAgentDir(targetAgentId, workspaceRoot);
|
|
838
|
+
if (targetAgentId !== "default") try {
|
|
839
|
+
const customAgent = await getAgent(targetAgentId, workspaceRoot);
|
|
840
|
+
if (customAgent?.files) agentFilesDir = customAgent.files;
|
|
841
|
+
} catch (err) {
|
|
842
|
+
console.warn(`Could not load custom agent '${targetAgentId}' for resolving files directory:`, err);
|
|
843
|
+
}
|
|
844
|
+
return path.resolve(agentDir, agentFilesDir);
|
|
845
|
+
}
|
|
846
|
+
async function validateAttachments(files) {
|
|
847
|
+
const { pathIsInsideDir } = await import("../fs-B5wW0oaH.mjs").then((n) => n.t);
|
|
848
|
+
const { getClawminiDir } = await import("../workspace-CSgfo_2J.mjs").then((n) => n._);
|
|
849
|
+
const tmpDir = path.join(getClawminiDir(process.cwd()), "tmp");
|
|
850
|
+
for (const file of files) {
|
|
851
|
+
const absoluteFile = path.resolve(process.cwd(), file);
|
|
852
|
+
if (!pathIsInsideDir(absoluteFile, tmpDir)) throw new TRPCError({
|
|
853
|
+
code: "BAD_REQUEST",
|
|
854
|
+
message: "File must be inside the temporary directory."
|
|
855
|
+
});
|
|
856
|
+
try {
|
|
857
|
+
await fs$1.access(absoluteFile);
|
|
858
|
+
} catch {
|
|
859
|
+
throw new TRPCError({
|
|
860
|
+
code: "BAD_REQUEST",
|
|
861
|
+
message: `File does not exist: ${file}`
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
async function validateLogFile(file, agentDir, workspaceRoot) {
|
|
867
|
+
const { pathIsInsideDir } = await import("../fs-B5wW0oaH.mjs").then((n) => n.t);
|
|
868
|
+
const resolvedPath = path.resolve(agentDir, file);
|
|
869
|
+
if (!pathIsInsideDir(resolvedPath, agentDir, { allowSameDir: true })) throw new TRPCError({
|
|
870
|
+
code: "BAD_REQUEST",
|
|
871
|
+
message: "File must be within the agent workspace."
|
|
872
|
+
});
|
|
873
|
+
try {
|
|
874
|
+
await fs$1.access(resolvedPath);
|
|
875
|
+
} catch {
|
|
876
|
+
throw new TRPCError({
|
|
877
|
+
code: "BAD_REQUEST",
|
|
878
|
+
message: `File does not exist: ${file}`
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
return path.relative(workspaceRoot, resolvedPath);
|
|
882
|
+
}
|
|
883
|
+
const AppRouter = router({
|
|
884
|
+
sendMessage: apiProcedure.input(z.object({
|
|
885
|
+
type: z.literal("send-message"),
|
|
886
|
+
client: z.literal("cli"),
|
|
887
|
+
data: z.object({
|
|
888
|
+
message: z.string(),
|
|
889
|
+
chatId: z.string().optional(),
|
|
890
|
+
sessionId: z.string().optional(),
|
|
891
|
+
agentId: z.string().optional(),
|
|
892
|
+
noWait: z.boolean().optional(),
|
|
893
|
+
files: z.array(z.string()).optional(),
|
|
894
|
+
adapter: z.string().optional()
|
|
895
|
+
})
|
|
896
|
+
})).mutation(async ({ input, ctx }) => {
|
|
897
|
+
let message = input.data.message;
|
|
898
|
+
const chatId = await resolveAndCheckChatId(ctx, input.data.chatId);
|
|
899
|
+
const noWait = input.data.noWait ?? false;
|
|
900
|
+
const sessionId = input.data.sessionId;
|
|
901
|
+
const agentId = input.data.agentId;
|
|
902
|
+
const settingsPath = getSettingsPath();
|
|
903
|
+
let settings;
|
|
904
|
+
try {
|
|
905
|
+
const settingsStr = await fs$1.readFile(settingsPath, "utf8");
|
|
906
|
+
settings = JSON.parse(settingsStr);
|
|
907
|
+
} catch (err) {
|
|
908
|
+
throw new Error(`Failed to read settings from ${settingsPath}: ${err}`, { cause: err });
|
|
909
|
+
}
|
|
910
|
+
const files = input.data.files;
|
|
911
|
+
if (files && files.length > 0) {
|
|
912
|
+
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
913
|
+
const chatSettings = await readChatSettings(chatId) ?? {};
|
|
914
|
+
const agentDir = await resolveAgentDir(agentId ?? chatSettings.defaultAgent ?? "default", workspaceRoot);
|
|
915
|
+
const absoluteFilesDir = await getAgentFilesDir(agentId, chatId, settings, workspaceRoot);
|
|
916
|
+
const adapterNamespace = input.data.adapter || "cli";
|
|
917
|
+
const targetDir = path.join(absoluteFilesDir, adapterNamespace);
|
|
918
|
+
const { pathIsInsideDir } = await import("../fs-B5wW0oaH.mjs").then((n) => n.t);
|
|
919
|
+
if (!pathIsInsideDir(targetDir, workspaceRoot, { allowSameDir: true })) throw new TRPCError({
|
|
920
|
+
code: "BAD_REQUEST",
|
|
921
|
+
message: "Target directory must be within the workspace."
|
|
922
|
+
});
|
|
923
|
+
await validateAttachments(files);
|
|
924
|
+
await fs$1.mkdir(targetDir, { recursive: true });
|
|
925
|
+
const finalPaths = [];
|
|
926
|
+
for (const file of files) {
|
|
927
|
+
const fileName = path.basename(file);
|
|
928
|
+
const targetPath = await getUniquePath(path.join(targetDir, fileName));
|
|
929
|
+
try {
|
|
930
|
+
await fs$1.rename(file, targetPath);
|
|
931
|
+
} catch {
|
|
932
|
+
await fs$1.copyFile(file, targetPath);
|
|
933
|
+
await fs$1.unlink(file);
|
|
934
|
+
}
|
|
935
|
+
finalPaths.push(path.relative(agentDir, targetPath));
|
|
936
|
+
}
|
|
937
|
+
const fileList = `Attached files:\n${finalPaths.map((p) => `- ${p}`).join("\n")}`;
|
|
938
|
+
message = message ? `${message}\n\n${fileList}` : fileList;
|
|
939
|
+
}
|
|
940
|
+
await handleUserMessage(chatId, message, settings, void 0, noWait, (args) => runCommand({
|
|
941
|
+
...args,
|
|
942
|
+
logToTerminal: true
|
|
943
|
+
}), sessionId, agentId);
|
|
944
|
+
return { success: true };
|
|
945
|
+
}),
|
|
946
|
+
getMessages: apiProcedure.input(z.object({
|
|
947
|
+
chatId: z.string().optional(),
|
|
948
|
+
limit: z.number().optional()
|
|
949
|
+
})).query(async ({ input, ctx }) => {
|
|
950
|
+
return getMessages(await resolveAndCheckChatId(ctx, input.chatId), input.limit);
|
|
951
|
+
}),
|
|
952
|
+
waitForMessages: apiProcedure.input(z.object({
|
|
953
|
+
chatId: z.string().optional(),
|
|
954
|
+
lastMessageId: z.string().optional()
|
|
955
|
+
})).subscription(async function* ({ input, ctx, signal }) {
|
|
956
|
+
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
957
|
+
if (input.lastMessageId) {
|
|
958
|
+
const messages = await getMessages(chatId);
|
|
959
|
+
const lastIndex = messages.findIndex((m) => m.id === input.lastMessageId);
|
|
960
|
+
if (lastIndex !== -1 && lastIndex < messages.length - 1) yield messages.slice(lastIndex + 1);
|
|
961
|
+
}
|
|
962
|
+
const { on } = await import("node:events");
|
|
963
|
+
try {
|
|
964
|
+
for await (const [event] of on(daemonEvents, DAEMON_EVENT_MESSAGE_APPENDED, { signal })) if (event.chatId === chatId) yield [event.message];
|
|
965
|
+
} catch (err) {
|
|
966
|
+
if (err instanceof Error && err.name === "AbortError") return;
|
|
967
|
+
throw err;
|
|
968
|
+
}
|
|
969
|
+
}),
|
|
970
|
+
waitForTyping: apiProcedure.input(z.object({ chatId: z.string().optional() })).subscription(async function* ({ input, ctx, signal }) {
|
|
971
|
+
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
972
|
+
const { on } = await import("node:events");
|
|
973
|
+
try {
|
|
974
|
+
for await (const [event] of on(daemonEvents, DAEMON_EVENT_TYPING, { signal })) if (event.chatId === chatId) yield event;
|
|
975
|
+
} catch (err) {
|
|
976
|
+
if (err instanceof Error && err.name === "AbortError") return;
|
|
977
|
+
throw err;
|
|
978
|
+
}
|
|
979
|
+
}),
|
|
980
|
+
ping: publicProcedure.query(() => {
|
|
981
|
+
return { status: "ok" };
|
|
982
|
+
}),
|
|
983
|
+
shutdown: publicProcedure.mutation(() => {
|
|
984
|
+
setTimeout(() => {
|
|
985
|
+
console.log("Shutting down daemon...");
|
|
986
|
+
process.kill(process.pid, "SIGTERM");
|
|
987
|
+
}, 100);
|
|
988
|
+
return { success: true };
|
|
989
|
+
}),
|
|
990
|
+
logMessage: apiProcedure.input(z.object({
|
|
991
|
+
chatId: z.string().optional(),
|
|
992
|
+
message: z.string().optional(),
|
|
993
|
+
files: z.array(z.string()).optional()
|
|
994
|
+
})).mutation(async ({ input, ctx }) => {
|
|
995
|
+
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
996
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
997
|
+
const id = Date.now().toString() + Math.random().toString(36).substring(2, 7);
|
|
998
|
+
const filePaths = [];
|
|
999
|
+
if (input.files && input.files.length > 0) {
|
|
1000
|
+
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
1001
|
+
const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
|
|
1002
|
+
for (const file of input.files) {
|
|
1003
|
+
const validPath = await validateLogFile(file, agentDir, workspaceRoot);
|
|
1004
|
+
filePaths.push(validPath);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
const filesArgStr = filePaths.map((p) => ` --file ${p}`).join("");
|
|
1008
|
+
const logMsg = {
|
|
1009
|
+
id,
|
|
1010
|
+
messageId: id,
|
|
1011
|
+
role: "log",
|
|
1012
|
+
source: "router",
|
|
1013
|
+
content: input.message || "",
|
|
1014
|
+
stderr: "",
|
|
1015
|
+
timestamp,
|
|
1016
|
+
command: `clawmini-lite log${filesArgStr}`,
|
|
1017
|
+
cwd: process.cwd(),
|
|
1018
|
+
exitCode: 0,
|
|
1019
|
+
...filePaths.length > 0 ? { files: filePaths } : {}
|
|
1020
|
+
};
|
|
1021
|
+
await import("../chats-Zd_HXDHx.mjs").then((n) => n.n).then((m) => m.appendMessage(chatId, logMsg));
|
|
1022
|
+
return { success: true };
|
|
1023
|
+
}),
|
|
1024
|
+
listCronJobs: apiProcedure.input(z.object({ chatId: z.string().optional() })).query(async ({ input, ctx }) => {
|
|
1025
|
+
return (await readChatSettings(await resolveAndCheckChatId(ctx, input.chatId)))?.jobs ?? [];
|
|
1026
|
+
}),
|
|
1027
|
+
addCronJob: apiProcedure.input(z.object({
|
|
1028
|
+
chatId: z.string().optional(),
|
|
1029
|
+
job: CronJobSchema
|
|
1030
|
+
})).mutation(async ({ input, ctx }) => {
|
|
1031
|
+
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
1032
|
+
const settings = await readChatSettings(chatId) || {};
|
|
1033
|
+
const cronJobs = settings.jobs ?? [];
|
|
1034
|
+
const existingIndex = cronJobs.findIndex((j) => j.id === input.job.id);
|
|
1035
|
+
if (existingIndex >= 0) cronJobs[existingIndex] = input.job;
|
|
1036
|
+
else cronJobs.push(input.job);
|
|
1037
|
+
settings.jobs = cronJobs;
|
|
1038
|
+
await writeChatSettings(chatId, settings);
|
|
1039
|
+
cronManager.scheduleJob(chatId, input.job);
|
|
1040
|
+
return { success: true };
|
|
1041
|
+
}),
|
|
1042
|
+
deleteCronJob: apiProcedure.input(z.object({
|
|
1043
|
+
chatId: z.string().optional(),
|
|
1044
|
+
id: z.string()
|
|
1045
|
+
})).mutation(async ({ input, ctx }) => {
|
|
1046
|
+
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
1047
|
+
const settings = await readChatSettings(chatId);
|
|
1048
|
+
if (!settings || !settings.jobs) return {
|
|
1049
|
+
success: true,
|
|
1050
|
+
deleted: false
|
|
1051
|
+
};
|
|
1052
|
+
const initialLength = settings.jobs.length;
|
|
1053
|
+
settings.jobs = settings.jobs.filter((j) => j.id !== input.id);
|
|
1054
|
+
if (settings.jobs.length !== initialLength) {
|
|
1055
|
+
await writeChatSettings(chatId, settings);
|
|
1056
|
+
cronManager.unscheduleJob(chatId, input.id);
|
|
1057
|
+
return {
|
|
1058
|
+
success: true,
|
|
1059
|
+
deleted: true
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
return {
|
|
1063
|
+
success: true,
|
|
1064
|
+
deleted: false
|
|
1065
|
+
};
|
|
1066
|
+
})
|
|
1067
|
+
});
|
|
1068
|
+
const appRouter = AppRouter;
|
|
1069
|
+
|
|
1070
|
+
//#endregion
|
|
1071
|
+
//#region src/daemon/index.ts
|
|
1072
|
+
async function initDaemon() {
|
|
1073
|
+
const socketPath = getSocketPath();
|
|
1074
|
+
const clawminiDir = getClawminiDir();
|
|
1075
|
+
if (!fs.existsSync(clawminiDir)) throw new Error(`${clawminiDir} does not exist`);
|
|
1076
|
+
const settingsPath = getSettingsPath();
|
|
1077
|
+
let apiCtx = null;
|
|
1078
|
+
if (fs.existsSync(settingsPath)) try {
|
|
1079
|
+
const settingsStr = fs.readFileSync(settingsPath, "utf8");
|
|
1080
|
+
const settings = JSON.parse(settingsStr);
|
|
1081
|
+
const parsed = SettingsSchema.safeParse(settings);
|
|
1082
|
+
if (parsed.success) apiCtx = getApiContext(parsed.data);
|
|
1083
|
+
} catch (err) {
|
|
1084
|
+
console.warn(`Failed to read or parse settings from ${settingsPath}:`, err);
|
|
1085
|
+
}
|
|
1086
|
+
const runHooks = async (hookType) => {
|
|
1087
|
+
try {
|
|
1088
|
+
const currentSettings = await readSettings();
|
|
1089
|
+
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
1090
|
+
if (!currentSettings?.environments) return;
|
|
1091
|
+
for (const [envPath, envName] of Object.entries(currentSettings.environments)) try {
|
|
1092
|
+
const envConfig = await readEnvironment(envName);
|
|
1093
|
+
const envDir = getEnvironmentPath(envName);
|
|
1094
|
+
const affectedDir = path.resolve(workspaceRoot, envPath);
|
|
1095
|
+
if (hookType === "up" && envConfig) await exportLiteToEnvironment(envName, envConfig, affectedDir);
|
|
1096
|
+
const command = envConfig?.[hookType];
|
|
1097
|
+
if (command) {
|
|
1098
|
+
console.log(`Executing '${hookType}' hook for environment '${envName}': ${command}`);
|
|
1099
|
+
execSync(command, {
|
|
1100
|
+
cwd: affectedDir,
|
|
1101
|
+
stdio: "inherit",
|
|
1102
|
+
env: {
|
|
1103
|
+
...process.env,
|
|
1104
|
+
ENV_DIR: envDir
|
|
1105
|
+
},
|
|
1106
|
+
timeout: hookType === "down" ? 1e4 : void 0
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
} catch (err) {
|
|
1110
|
+
console.error(`Failed to execute '${hookType}' hook for environment '${envName}':`, err);
|
|
1111
|
+
if (hookType === "up") throw err;
|
|
1112
|
+
}
|
|
1113
|
+
} catch (err) {
|
|
1114
|
+
console.error(`Failed to run '${hookType}' hooks:`, err);
|
|
1115
|
+
if (hookType === "up") throw err;
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
1118
|
+
if (fs.existsSync(socketPath)) {
|
|
1119
|
+
if (await new Promise((resolve) => {
|
|
1120
|
+
const client = net.createConnection({ path: socketPath });
|
|
1121
|
+
client.on("connect", () => {
|
|
1122
|
+
client.destroy();
|
|
1123
|
+
resolve(true);
|
|
1124
|
+
});
|
|
1125
|
+
client.on("error", () => {
|
|
1126
|
+
resolve(false);
|
|
1127
|
+
});
|
|
1128
|
+
})) {
|
|
1129
|
+
console.log("Daemon is already running (socket is active). Exiting.");
|
|
1130
|
+
process.exit(0);
|
|
1131
|
+
}
|
|
1132
|
+
try {
|
|
1133
|
+
fs.unlinkSync(socketPath);
|
|
1134
|
+
} catch {}
|
|
1135
|
+
}
|
|
1136
|
+
let isReady = false;
|
|
1137
|
+
let readyPromiseResolve;
|
|
1138
|
+
const readyPromise = new Promise((resolve) => {
|
|
1139
|
+
readyPromiseResolve = resolve;
|
|
1140
|
+
});
|
|
1141
|
+
const handler = createHTTPHandler({
|
|
1142
|
+
router: appRouter,
|
|
1143
|
+
createContext: ({ req, res }) => ({
|
|
1144
|
+
req,
|
|
1145
|
+
res,
|
|
1146
|
+
isApiServer: false
|
|
1147
|
+
})
|
|
1148
|
+
});
|
|
1149
|
+
const server = http.createServer(async (req, res) => {
|
|
1150
|
+
if (!isReady) await readyPromise;
|
|
1151
|
+
handler(req, res);
|
|
1152
|
+
});
|
|
1153
|
+
await new Promise((resolve, reject) => {
|
|
1154
|
+
server.on("error", (err) => {
|
|
1155
|
+
if (err.code === "EADDRINUSE") {
|
|
1156
|
+
console.log("Daemon is already running (socket bind failed). Exiting.");
|
|
1157
|
+
process.exit(0);
|
|
1158
|
+
}
|
|
1159
|
+
reject(err);
|
|
1160
|
+
});
|
|
1161
|
+
server.listen(socketPath, () => {
|
|
1162
|
+
console.log(`Daemon initialized and listening on ${socketPath}`);
|
|
1163
|
+
resolve();
|
|
1164
|
+
});
|
|
1165
|
+
});
|
|
1166
|
+
await runHooks("up");
|
|
1167
|
+
isReady = true;
|
|
1168
|
+
readyPromiseResolve();
|
|
1169
|
+
cronManager.init().catch((err) => {
|
|
1170
|
+
console.error("Failed to initialize cron manager:", err);
|
|
1171
|
+
});
|
|
1172
|
+
let apiServer;
|
|
1173
|
+
if (apiCtx) {
|
|
1174
|
+
const apiHandler = createHTTPHandler({
|
|
1175
|
+
router: appRouter,
|
|
1176
|
+
createContext: ({ req, res }) => {
|
|
1177
|
+
let tokenPayload = null;
|
|
1178
|
+
const authHeader = req.headers.authorization;
|
|
1179
|
+
if (authHeader && authHeader.startsWith("Bearer ")) tokenPayload = validateToken(authHeader.substring(7));
|
|
1180
|
+
return {
|
|
1181
|
+
req,
|
|
1182
|
+
res,
|
|
1183
|
+
isApiServer: true,
|
|
1184
|
+
tokenPayload
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1188
|
+
apiServer = http.createServer((req, res) => {
|
|
1189
|
+
apiHandler(req, res);
|
|
1190
|
+
});
|
|
1191
|
+
const host = apiCtx.host;
|
|
1192
|
+
const port = apiCtx.port;
|
|
1193
|
+
apiServer.listen(port, host, () => {
|
|
1194
|
+
console.log(`Daemon HTTP API initialized and listening on http://${host}:${port}`);
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
let isShuttingDown = false;
|
|
1198
|
+
const shutdown = async () => {
|
|
1199
|
+
if (isShuttingDown) return;
|
|
1200
|
+
isShuttingDown = true;
|
|
1201
|
+
console.log("Daemon shutting down...");
|
|
1202
|
+
await runHooks("down");
|
|
1203
|
+
server.close();
|
|
1204
|
+
if (apiServer) apiServer.close();
|
|
1205
|
+
process.exit(0);
|
|
1206
|
+
};
|
|
1207
|
+
process.on("SIGINT", shutdown);
|
|
1208
|
+
process.on("SIGTERM", shutdown);
|
|
1209
|
+
process.on("exit", () => {
|
|
1210
|
+
if (fs.existsSync(socketPath)) try {
|
|
1211
|
+
fs.unlinkSync(socketPath);
|
|
1212
|
+
} catch {}
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
if (process.argv[1] === new URL(import.meta.url).pathname) initDaemon().catch((err) => {
|
|
1216
|
+
console.error("Daemon initialization failed:", err);
|
|
1217
|
+
process.exit(1);
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
//#endregion
|
|
1221
|
+
export { initDaemon };
|
|
1222
|
+
//# sourceMappingURL=index.mjs.map
|