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.
Files changed (469) hide show
  1. package/.gemini/settings.json +46 -0
  2. package/.prettierrc +7 -0
  3. package/GEMINI.md +11 -0
  4. package/README.md +137 -0
  5. package/dist/adapter-discord/index.d.mts +5 -0
  6. package/dist/adapter-discord/index.d.mts.map +1 -0
  7. package/dist/adapter-discord/index.mjs +456 -0
  8. package/dist/adapter-discord/index.mjs.map +1 -0
  9. package/dist/chats-DKgTeU7i.mjs +91 -0
  10. package/dist/chats-DKgTeU7i.mjs.map +1 -0
  11. package/dist/chats-Zd_HXDHx.mjs +29 -0
  12. package/dist/chats-Zd_HXDHx.mjs.map +1 -0
  13. package/dist/cli/index.d.mts +1 -0
  14. package/dist/cli/index.mjs +850 -0
  15. package/dist/cli/index.mjs.map +1 -0
  16. package/dist/cli/lite.d.mts +1 -0
  17. package/dist/cli/lite.mjs +4434 -0
  18. package/dist/cli/lite.mjs.map +1 -0
  19. package/dist/daemon/index.d.mts +5 -0
  20. package/dist/daemon/index.d.mts.map +1 -0
  21. package/dist/daemon/index.mjs +1222 -0
  22. package/dist/daemon/index.mjs.map +1 -0
  23. package/dist/fetch-BjZVyU3Z.mjs +37 -0
  24. package/dist/fetch-BjZVyU3Z.mjs.map +1 -0
  25. package/dist/fs-B5wW0oaH.mjs +14 -0
  26. package/dist/fs-B5wW0oaH.mjs.map +1 -0
  27. package/dist/lite-Dl7WXyaH.mjs +80 -0
  28. package/dist/lite-Dl7WXyaH.mjs.map +1 -0
  29. package/dist/rolldown-runtime-95iHPtFO.mjs +18 -0
  30. package/dist/web/_app/env.js +1 -0
  31. package/dist/web/_app/immutable/assets/0.GI4C4dpV.css +1 -0
  32. package/dist/web/_app/immutable/chunks/B5abRDXp.js +1 -0
  33. package/dist/web/_app/immutable/chunks/B8yYFADm.js +1 -0
  34. package/dist/web/_app/immutable/chunks/BPy8HLo7.js +5 -0
  35. package/dist/web/_app/immutable/chunks/Bi0jeV7Q.js +1 -0
  36. package/dist/web/_app/immutable/chunks/BmUXQ3wy.js +2 -0
  37. package/dist/web/_app/immutable/chunks/C3k55nDF.js +1 -0
  38. package/dist/web/_app/immutable/chunks/COekwvP2.js +1 -0
  39. package/dist/web/_app/immutable/chunks/CSvS_NwK.js +1 -0
  40. package/dist/web/_app/immutable/chunks/CpaGRn9L.js +1 -0
  41. package/dist/web/_app/immutable/chunks/CyNaE55B.js +1 -0
  42. package/dist/web/_app/immutable/chunks/DG5RZBw-.js +2 -0
  43. package/dist/web/_app/immutable/chunks/Dc-UOHw9.js +1 -0
  44. package/dist/web/_app/immutable/chunks/DcrmIfTj.js +1 -0
  45. package/dist/web/_app/immutable/chunks/ZkLyk0mE.js +1 -0
  46. package/dist/web/_app/immutable/entry/app.B-vZe7PN.js +2 -0
  47. package/dist/web/_app/immutable/entry/start.oP1AgKhs.js +1 -0
  48. package/dist/web/_app/immutable/nodes/0.B5WFN0zw.js +1 -0
  49. package/dist/web/_app/immutable/nodes/1.D1wtJb2k.js +1 -0
  50. package/dist/web/_app/immutable/nodes/2.CK3CLC0f.js +1 -0
  51. package/dist/web/_app/immutable/nodes/3.BB5wCoBf.js +4 -0
  52. package/dist/web/_app/immutable/nodes/4.Dr2jvAXK.js +1 -0
  53. package/dist/web/_app/immutable/nodes/5.BJl7oM3b.js +1 -0
  54. package/dist/web/_app/version.json +1 -0
  55. package/dist/web/index.html +37 -0
  56. package/dist/web/robots.txt +3 -0
  57. package/dist/workspace-CSgfo_2J.mjs +383 -0
  58. package/dist/workspace-CSgfo_2J.mjs.map +1 -0
  59. package/docs/01_chats/development_log.md +36 -0
  60. package/docs/01_chats/notes.md +27 -0
  61. package/docs/01_chats/prd.md +47 -0
  62. package/docs/01_chats/questions.md +19 -0
  63. package/docs/01_chats/tickets.md +67 -0
  64. package/docs/02_sessions/development_log.md +79 -0
  65. package/docs/02_sessions/notes.md +40 -0
  66. package/docs/02_sessions/prd.md +75 -0
  67. package/docs/02_sessions/questions.md +7 -0
  68. package/docs/02_sessions/tickets.md +68 -0
  69. package/docs/03_web_interface/development_log.md +60 -0
  70. package/docs/03_web_interface/notes.md +29 -0
  71. package/docs/03_web_interface/prd.md +42 -0
  72. package/docs/03_web_interface/questions.md +8 -0
  73. package/docs/03_web_interface/tickets.md +59 -0
  74. package/docs/04_agents/development_log.md +54 -0
  75. package/docs/04_agents/notes.md +45 -0
  76. package/docs/04_agents/prd.md +47 -0
  77. package/docs/04_agents/questions.md +13 -0
  78. package/docs/04_agents/tickets.md +107 -0
  79. package/docs/05_routers/development_log.md +13 -0
  80. package/docs/05_routers/notes.md +40 -0
  81. package/docs/05_routers/prd.md +55 -0
  82. package/docs/05_routers/questions.md +21 -0
  83. package/docs/05_routers/tickets.md +109 -0
  84. package/docs/06_agent_templates/development_log.md +38 -0
  85. package/docs/06_agent_templates/notes.md +25 -0
  86. package/docs/06_agent_templates/prd.md +34 -0
  87. package/docs/06_agent_templates/questions.md +11 -0
  88. package/docs/06_agent_templates/tickets.md +49 -0
  89. package/docs/06_cron/development_log.md +51 -0
  90. package/docs/06_cron/notes.md +14 -0
  91. package/docs/06_cron/prd.md +92 -0
  92. package/docs/06_cron/questions.md +15 -0
  93. package/docs/06_cron/tickets.md +75 -0
  94. package/docs/07_web_chat_ux/development_log.md +30 -0
  95. package/docs/07_web_chat_ux/notes.md +25 -0
  96. package/docs/07_web_chat_ux/prd.md +46 -0
  97. package/docs/07_web_chat_ux/questions.md +7 -0
  98. package/docs/07_web_chat_ux/tickets.md +48 -0
  99. package/docs/08_agent_api/development_log.md +52 -0
  100. package/docs/08_agent_api/notes.md +31 -0
  101. package/docs/08_agent_api/prd.md +56 -0
  102. package/docs/08_agent_api/questions.md +14 -0
  103. package/docs/08_agent_api/tickets.md +104 -0
  104. package/docs/09_agent_fallbacks/development_log.md +52 -0
  105. package/docs/09_agent_fallbacks/notes.md +40 -0
  106. package/docs/09_agent_fallbacks/prd.md +55 -0
  107. package/docs/09_agent_fallbacks/questions.md +10 -0
  108. package/docs/09_agent_fallbacks/tickets.md +88 -0
  109. package/docs/09_discord_adapter/development_log.md +95 -0
  110. package/docs/09_discord_adapter/notes.md +18 -0
  111. package/docs/09_discord_adapter/prd.md +57 -0
  112. package/docs/09_discord_adapter/questions.md +16 -0
  113. package/docs/09_discord_adapter/tickets.md +116 -0
  114. package/docs/10_file_attachments/development_log.md +55 -0
  115. package/docs/10_file_attachments/notes.md +59 -0
  116. package/docs/10_file_attachments/prd.md +73 -0
  117. package/docs/10_file_attachments/questions.md +15 -0
  118. package/docs/10_file_attachments/tickets.md +88 -0
  119. package/docs/11_message_verbosity/development_log.md +43 -0
  120. package/docs/11_message_verbosity/notes.md +26 -0
  121. package/docs/11_message_verbosity/prd.md +44 -0
  122. package/docs/11_message_verbosity/questions.md +8 -0
  123. package/docs/11_message_verbosity/tickets.md +33 -0
  124. package/docs/12_environments/development_log.md +43 -0
  125. package/docs/12_environments/notes.md +45 -0
  126. package/docs/12_environments/prd.md +113 -0
  127. package/docs/12_environments/questions.md +17 -0
  128. package/docs/12_environments/tickets.md +87 -0
  129. package/docs/12_setup_flow_improvements/development_log.md +40 -0
  130. package/docs/12_setup_flow_improvements/notes.md +34 -0
  131. package/docs/12_setup_flow_improvements/prd.md +35 -0
  132. package/docs/12_setup_flow_improvements/questions.md +8 -0
  133. package/docs/12_setup_flow_improvements/tickets.md +122 -0
  134. package/docs/13_discord_typing_indicators/development_log.md +38 -0
  135. package/docs/13_discord_typing_indicators/notes.md +18 -0
  136. package/docs/13_discord_typing_indicators/prd.md +41 -0
  137. package/docs/13_discord_typing_indicators/questions.md +6 -0
  138. package/docs/13_discord_typing_indicators/tickets.md +60 -0
  139. package/docs/14_interruptions/development_log.md +50 -0
  140. package/docs/14_interruptions/notes.md +38 -0
  141. package/docs/14_interruptions/prd.md +46 -0
  142. package/docs/14_interruptions/questions.md +12 -0
  143. package/docs/14_interruptions/tickets.md +69 -0
  144. package/docs/15_sandbox_policies/development_log.md +95 -0
  145. package/docs/15_sandbox_policies/notes.md +33 -0
  146. package/docs/15_sandbox_policies/prd.md +163 -0
  147. package/docs/15_sandbox_policies/questions.md +10 -0
  148. package/docs/15_sandbox_policies/tickets.md +196 -0
  149. package/docs/CHECKS.md +9 -0
  150. package/docs/guides/discord_adapter_setup.md +69 -0
  151. package/docs/guides/sandbox_policies.md +76 -0
  152. package/eslint.config.js +47 -0
  153. package/napkin.md +21 -0
  154. package/package.json +50 -0
  155. package/scripts/create_worktree.sh +49 -0
  156. package/scripts/get_pr_comments.sh +36 -0
  157. package/src/adapter-discord/client.test.ts +65 -0
  158. package/src/adapter-discord/client.ts +41 -0
  159. package/src/adapter-discord/config.test.ts +156 -0
  160. package/src/adapter-discord/config.ts +61 -0
  161. package/src/adapter-discord/forwarder.test.ts +493 -0
  162. package/src/adapter-discord/forwarder.ts +246 -0
  163. package/src/adapter-discord/index.test.ts +399 -0
  164. package/src/adapter-discord/index.ts +147 -0
  165. package/src/adapter-discord/state.test.ts +65 -0
  166. package/src/adapter-discord/state.ts +44 -0
  167. package/src/cli/client.ts +46 -0
  168. package/src/cli/commands/agents.ts +138 -0
  169. package/src/cli/commands/chats.ts +79 -0
  170. package/src/cli/commands/down.ts +32 -0
  171. package/src/cli/commands/environments.ts +39 -0
  172. package/src/cli/commands/export-lite.ts +62 -0
  173. package/src/cli/commands/init.ts +79 -0
  174. package/src/cli/commands/jobs.ts +141 -0
  175. package/src/cli/commands/messages.ts +103 -0
  176. package/src/cli/commands/up.ts +26 -0
  177. package/src/cli/commands/web-api/agents.ts +138 -0
  178. package/src/cli/commands/web-api/chats.ts +213 -0
  179. package/src/cli/commands/web-api/utils.ts +27 -0
  180. package/src/cli/commands/web.ts +105 -0
  181. package/src/cli/e2e/adapter-discord.test.ts +76 -0
  182. package/src/cli/e2e/agents.test.ts +140 -0
  183. package/src/cli/e2e/basic.test.ts +43 -0
  184. package/src/cli/e2e/cron.test.ts +132 -0
  185. package/src/cli/e2e/daemon.test.ts +293 -0
  186. package/src/cli/e2e/environments.test.ts +66 -0
  187. package/src/cli/e2e/export-lite-func.test.ts +155 -0
  188. package/src/cli/e2e/export-lite.test.ts +51 -0
  189. package/src/cli/e2e/fallbacks.test.ts +169 -0
  190. package/src/cli/e2e/global-setup.ts +15 -0
  191. package/src/cli/e2e/init.test.ts +70 -0
  192. package/src/cli/e2e/messages.test.ts +294 -0
  193. package/src/cli/e2e/requests.test.ts +165 -0
  194. package/src/cli/e2e/utils.ts +66 -0
  195. package/src/cli/index.test.ts +7 -0
  196. package/src/cli/index.ts +29 -0
  197. package/src/cli/lite.ts +247 -0
  198. package/src/cli/utils.ts +4 -0
  199. package/src/daemon/auth.test.ts +50 -0
  200. package/src/daemon/auth.ts +69 -0
  201. package/src/daemon/chats.ts +26 -0
  202. package/src/daemon/cron.test.ts +28 -0
  203. package/src/daemon/cron.ts +159 -0
  204. package/src/daemon/events.ts +15 -0
  205. package/src/daemon/index.ts +212 -0
  206. package/src/daemon/message-agent.test.ts +132 -0
  207. package/src/daemon/message-extraction.test.ts +166 -0
  208. package/src/daemon/message-fallbacks.test.ts +313 -0
  209. package/src/daemon/message-interruption.test.ts +125 -0
  210. package/src/daemon/message-queue.test.ts +143 -0
  211. package/src/daemon/message-router.test.ts +106 -0
  212. package/src/daemon/message-session.test.ts +127 -0
  213. package/src/daemon/message-test-utils.ts +41 -0
  214. package/src/daemon/message-typing.test.ts +93 -0
  215. package/src/daemon/message-verbosity.test.ts +127 -0
  216. package/src/daemon/message.ts +600 -0
  217. package/src/daemon/observation.test.ts +118 -0
  218. package/src/daemon/policy-request-service.test.ts +87 -0
  219. package/src/daemon/policy-request-service.ts +62 -0
  220. package/src/daemon/policy-utils.test.ts +138 -0
  221. package/src/daemon/policy-utils.ts +152 -0
  222. package/src/daemon/queue.test.ts +89 -0
  223. package/src/daemon/queue.ts +87 -0
  224. package/src/daemon/request-store.test.ts +103 -0
  225. package/src/daemon/request-store.ts +96 -0
  226. package/src/daemon/router-policy-request.test.ts +99 -0
  227. package/src/daemon/router.test.ts +380 -0
  228. package/src/daemon/router.ts +510 -0
  229. package/src/daemon/routers/slash-command.test.ts +145 -0
  230. package/src/daemon/routers/slash-command.ts +58 -0
  231. package/src/daemon/routers/slash-interrupt.test.ts +30 -0
  232. package/src/daemon/routers/slash-interrupt.ts +7 -0
  233. package/src/daemon/routers/slash-new.test.ts +59 -0
  234. package/src/daemon/routers/slash-new.ts +14 -0
  235. package/src/daemon/routers/slash-policies.test.ts +167 -0
  236. package/src/daemon/routers/slash-policies.ts +131 -0
  237. package/src/daemon/routers/slash-stop.test.ts +30 -0
  238. package/src/daemon/routers/slash-stop.ts +3 -0
  239. package/src/daemon/routers/types.ts +10 -0
  240. package/src/daemon/routers/utils.ts +22 -0
  241. package/src/daemon/routers.test.ts +141 -0
  242. package/src/daemon/routers.ts +115 -0
  243. package/src/daemon/utils/spawn.ts +61 -0
  244. package/src/shared/agent-utils.ts +30 -0
  245. package/src/shared/chats.test.ts +112 -0
  246. package/src/shared/chats.ts +164 -0
  247. package/src/shared/config.test.ts +90 -0
  248. package/src/shared/config.ts +100 -0
  249. package/src/shared/event-source.ts +121 -0
  250. package/src/shared/fetch.ts +45 -0
  251. package/src/shared/lite.ts +129 -0
  252. package/src/shared/policies.ts +24 -0
  253. package/src/shared/utils/env.ts +27 -0
  254. package/src/shared/utils/fs.ts +13 -0
  255. package/src/shared/workspace.test.ts +345 -0
  256. package/src/shared/workspace.ts +500 -0
  257. package/templates/environments/cladding/env.json +7 -0
  258. package/templates/environments/macos/env.json +8 -0
  259. package/templates/environments/macos/sandbox.sb +21 -0
  260. package/templates/environments/macos-proxy/allowlist.txt +1 -0
  261. package/templates/environments/macos-proxy/env.json +14 -0
  262. package/templates/environments/macos-proxy/proxy.mjs +86 -0
  263. package/templates/environments/macos-proxy/sandbox.sb +34 -0
  264. package/templates/gemini/settings.json +11 -0
  265. package/templates/gemini-claw/.gemini/hooks/clawmini-logging.sh +17 -0
  266. package/templates/gemini-claw/.gemini/settings.json +24 -0
  267. package/templates/gemini-claw/.gemini/skills/clawmini-jobs/SKILL.md +40 -0
  268. package/templates/gemini-claw/.gemini/system.md +98 -0
  269. package/templates/gemini-claw/BOOTSTRAP.md +54 -0
  270. package/templates/gemini-claw/GEMINI.md +107 -0
  271. package/templates/gemini-claw/HEARTBEAT.md +3 -0
  272. package/templates/gemini-claw/MEMORY.md +2 -0
  273. package/templates/gemini-claw/SOUL.md +42 -0
  274. package/templates/gemini-claw/TOOLS.md +38 -0
  275. package/templates/gemini-claw/USER.md +15 -0
  276. package/templates/gemini-claw/memory/.gitkeep +0 -0
  277. package/templates/gemini-claw/settings.json +24 -0
  278. package/templates/opencode/settings.json +11 -0
  279. package/tsconfig.json +42 -0
  280. package/tsdown.config.ts +19 -0
  281. package/vitest.config.ts +9 -0
  282. package/web/.svelte-kit/ambient.d.ts +382 -0
  283. package/web/.svelte-kit/generated/client/app.js +35 -0
  284. package/web/.svelte-kit/generated/client/matchers.js +1 -0
  285. package/web/.svelte-kit/generated/client/nodes/0.js +3 -0
  286. package/web/.svelte-kit/generated/client/nodes/1.js +1 -0
  287. package/web/.svelte-kit/generated/client/nodes/2.js +1 -0
  288. package/web/.svelte-kit/generated/client/nodes/3.js +1 -0
  289. package/web/.svelte-kit/generated/client/nodes/4.js +3 -0
  290. package/web/.svelte-kit/generated/client/nodes/5.js +3 -0
  291. package/web/.svelte-kit/generated/client-optimized/app.js +35 -0
  292. package/web/.svelte-kit/generated/client-optimized/matchers.js +1 -0
  293. package/web/.svelte-kit/generated/client-optimized/nodes/0.js +3 -0
  294. package/web/.svelte-kit/generated/client-optimized/nodes/1.js +1 -0
  295. package/web/.svelte-kit/generated/client-optimized/nodes/2.js +1 -0
  296. package/web/.svelte-kit/generated/client-optimized/nodes/3.js +1 -0
  297. package/web/.svelte-kit/generated/client-optimized/nodes/4.js +3 -0
  298. package/web/.svelte-kit/generated/client-optimized/nodes/5.js +3 -0
  299. package/web/.svelte-kit/generated/root.js +3 -0
  300. package/web/.svelte-kit/generated/root.svelte +68 -0
  301. package/web/.svelte-kit/generated/server/internal.js +53 -0
  302. package/web/.svelte-kit/non-ambient.d.ts +46 -0
  303. package/web/.svelte-kit/output/client/.vite/manifest.json +251 -0
  304. package/web/.svelte-kit/output/client/_app/immutable/assets/0.GI4C4dpV.css +1 -0
  305. package/web/.svelte-kit/output/client/_app/immutable/chunks/B5abRDXp.js +1 -0
  306. package/web/.svelte-kit/output/client/_app/immutable/chunks/B8yYFADm.js +1 -0
  307. package/web/.svelte-kit/output/client/_app/immutable/chunks/BPy8HLo7.js +5 -0
  308. package/web/.svelte-kit/output/client/_app/immutable/chunks/Bi0jeV7Q.js +1 -0
  309. package/web/.svelte-kit/output/client/_app/immutable/chunks/BmUXQ3wy.js +2 -0
  310. package/web/.svelte-kit/output/client/_app/immutable/chunks/C3k55nDF.js +1 -0
  311. package/web/.svelte-kit/output/client/_app/immutable/chunks/COekwvP2.js +1 -0
  312. package/web/.svelte-kit/output/client/_app/immutable/chunks/CSvS_NwK.js +1 -0
  313. package/web/.svelte-kit/output/client/_app/immutable/chunks/CpaGRn9L.js +1 -0
  314. package/web/.svelte-kit/output/client/_app/immutable/chunks/CyNaE55B.js +1 -0
  315. package/web/.svelte-kit/output/client/_app/immutable/chunks/DG5RZBw-.js +2 -0
  316. package/web/.svelte-kit/output/client/_app/immutable/chunks/Dc-UOHw9.js +1 -0
  317. package/web/.svelte-kit/output/client/_app/immutable/chunks/DcrmIfTj.js +1 -0
  318. package/web/.svelte-kit/output/client/_app/immutable/chunks/ZkLyk0mE.js +1 -0
  319. package/web/.svelte-kit/output/client/_app/immutable/entry/app.B-vZe7PN.js +2 -0
  320. package/web/.svelte-kit/output/client/_app/immutable/entry/start.oP1AgKhs.js +1 -0
  321. package/web/.svelte-kit/output/client/_app/immutable/nodes/0.B5WFN0zw.js +1 -0
  322. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.D1wtJb2k.js +1 -0
  323. package/web/.svelte-kit/output/client/_app/immutable/nodes/2.CK3CLC0f.js +1 -0
  324. package/web/.svelte-kit/output/client/_app/immutable/nodes/3.BB5wCoBf.js +4 -0
  325. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.Dr2jvAXK.js +1 -0
  326. package/web/.svelte-kit/output/client/_app/immutable/nodes/5.BJl7oM3b.js +1 -0
  327. package/web/.svelte-kit/output/client/_app/version.json +1 -0
  328. package/web/.svelte-kit/output/client/robots.txt +3 -0
  329. package/web/.svelte-kit/output/prerendered/dependencies/_app/env.js +1 -0
  330. package/web/.svelte-kit/output/server/.vite/manifest.json +215 -0
  331. package/web/.svelte-kit/output/server/_app/immutable/assets/_layout.GI4C4dpV.css +1 -0
  332. package/web/.svelte-kit/output/server/chunks/Icon.js +153 -0
  333. package/web/.svelte-kit/output/server/chunks/bot.js +2753 -0
  334. package/web/.svelte-kit/output/server/chunks/client.js +47 -0
  335. package/web/.svelte-kit/output/server/chunks/environment.js +34 -0
  336. package/web/.svelte-kit/output/server/chunks/exports.js +231 -0
  337. package/web/.svelte-kit/output/server/chunks/false.js +4 -0
  338. package/web/.svelte-kit/output/server/chunks/index-server.js +20 -0
  339. package/web/.svelte-kit/output/server/chunks/index.js +24 -0
  340. package/web/.svelte-kit/output/server/chunks/internal.js +133 -0
  341. package/web/.svelte-kit/output/server/chunks/plus.js +81 -0
  342. package/web/.svelte-kit/output/server/chunks/root.js +4076 -0
  343. package/web/.svelte-kit/output/server/chunks/shared.js +789 -0
  344. package/web/.svelte-kit/output/server/chunks/utils.js +43 -0
  345. package/web/.svelte-kit/output/server/entries/fallbacks/error.svelte.js +11 -0
  346. package/web/.svelte-kit/output/server/entries/pages/_layout.svelte.js +3944 -0
  347. package/web/.svelte-kit/output/server/entries/pages/_layout.ts.js +28 -0
  348. package/web/.svelte-kit/output/server/entries/pages/_page.svelte.js +7 -0
  349. package/web/.svelte-kit/output/server/entries/pages/agents/_page.svelte.js +379 -0
  350. package/web/.svelte-kit/output/server/entries/pages/chats/_id_/_page.svelte.js +292 -0
  351. package/web/.svelte-kit/output/server/entries/pages/chats/_id_/_page.ts.js +17 -0
  352. package/web/.svelte-kit/output/server/entries/pages/chats/_id_/settings/_page.svelte.js +259 -0
  353. package/web/.svelte-kit/output/server/entries/pages/chats/_id_/settings/_page.ts.js +17 -0
  354. package/web/.svelte-kit/output/server/index.js +3748 -0
  355. package/web/.svelte-kit/output/server/internal.js +14 -0
  356. package/web/.svelte-kit/output/server/manifest-full.js +63 -0
  357. package/web/.svelte-kit/output/server/manifest.js +63 -0
  358. package/web/.svelte-kit/output/server/nodes/0.js +13 -0
  359. package/web/.svelte-kit/output/server/nodes/1.js +8 -0
  360. package/web/.svelte-kit/output/server/nodes/2.js +8 -0
  361. package/web/.svelte-kit/output/server/nodes/3.js +8 -0
  362. package/web/.svelte-kit/output/server/nodes/4.js +13 -0
  363. package/web/.svelte-kit/output/server/nodes/5.js +13 -0
  364. package/web/.svelte-kit/output/server/remote-entry.js +557 -0
  365. package/web/.svelte-kit/tsconfig.json +67 -0
  366. package/web/.svelte-kit/types/route_meta_data.json +17 -0
  367. package/web/.svelte-kit/types/src/routes/$types.d.ts +26 -0
  368. package/web/.svelte-kit/types/src/routes/agents/$types.d.ts +18 -0
  369. package/web/.svelte-kit/types/src/routes/chats/[id]/$types.d.ts +21 -0
  370. package/web/.svelte-kit/types/src/routes/chats/[id]/proxy+page.ts +20 -0
  371. package/web/.svelte-kit/types/src/routes/chats/[id]/settings/$types.d.ts +21 -0
  372. package/web/.svelte-kit/types/src/routes/chats/[id]/settings/proxy+page.ts +19 -0
  373. package/web/.svelte-kit/types/src/routes/proxy+layout.ts +35 -0
  374. package/web/README.md +42 -0
  375. package/web/components.json +16 -0
  376. package/web/package.json +41 -0
  377. package/web/src/app.css +121 -0
  378. package/web/src/app.d.ts +13 -0
  379. package/web/src/app.html +11 -0
  380. package/web/src/demo.spec.ts +7 -0
  381. package/web/src/lib/app-state.svelte.ts +3 -0
  382. package/web/src/lib/assets/favicon.svg +1 -0
  383. package/web/src/lib/components/app/app-sidebar-test-wrapper.svelte +10 -0
  384. package/web/src/lib/components/app/app-sidebar.svelte +171 -0
  385. package/web/src/lib/components/app/app-sidebar.svelte.spec.ts +13 -0
  386. package/web/src/lib/components/ui/button/button.svelte +82 -0
  387. package/web/src/lib/components/ui/button/index.ts +17 -0
  388. package/web/src/lib/components/ui/dialog/dialog-close.svelte +7 -0
  389. package/web/src/lib/components/ui/dialog/dialog-content.svelte +45 -0
  390. package/web/src/lib/components/ui/dialog/dialog-description.svelte +17 -0
  391. package/web/src/lib/components/ui/dialog/dialog-footer.svelte +20 -0
  392. package/web/src/lib/components/ui/dialog/dialog-header.svelte +20 -0
  393. package/web/src/lib/components/ui/dialog/dialog-overlay.svelte +20 -0
  394. package/web/src/lib/components/ui/dialog/dialog-portal.svelte +7 -0
  395. package/web/src/lib/components/ui/dialog/dialog-title.svelte +17 -0
  396. package/web/src/lib/components/ui/dialog/dialog-trigger.svelte +7 -0
  397. package/web/src/lib/components/ui/dialog/dialog.svelte +7 -0
  398. package/web/src/lib/components/ui/dialog/index.ts +34 -0
  399. package/web/src/lib/components/ui/input/index.ts +7 -0
  400. package/web/src/lib/components/ui/input/input.svelte +52 -0
  401. package/web/src/lib/components/ui/separator/index.ts +7 -0
  402. package/web/src/lib/components/ui/separator/separator.svelte +21 -0
  403. package/web/src/lib/components/ui/sheet/index.ts +34 -0
  404. package/web/src/lib/components/ui/sheet/sheet-close.svelte +7 -0
  405. package/web/src/lib/components/ui/sheet/sheet-content.svelte +60 -0
  406. package/web/src/lib/components/ui/sheet/sheet-description.svelte +17 -0
  407. package/web/src/lib/components/ui/sheet/sheet-footer.svelte +20 -0
  408. package/web/src/lib/components/ui/sheet/sheet-header.svelte +20 -0
  409. package/web/src/lib/components/ui/sheet/sheet-overlay.svelte +20 -0
  410. package/web/src/lib/components/ui/sheet/sheet-portal.svelte +7 -0
  411. package/web/src/lib/components/ui/sheet/sheet-title.svelte +17 -0
  412. package/web/src/lib/components/ui/sheet/sheet-trigger.svelte +7 -0
  413. package/web/src/lib/components/ui/sheet/sheet.svelte +7 -0
  414. package/web/src/lib/components/ui/sidebar/constants.ts +6 -0
  415. package/web/src/lib/components/ui/sidebar/context.svelte.ts +79 -0
  416. package/web/src/lib/components/ui/sidebar/index.ts +75 -0
  417. package/web/src/lib/components/ui/sidebar/sidebar-content.svelte +24 -0
  418. package/web/src/lib/components/ui/sidebar/sidebar-footer.svelte +21 -0
  419. package/web/src/lib/components/ui/sidebar/sidebar-group-action.svelte +36 -0
  420. package/web/src/lib/components/ui/sidebar/sidebar-group-content.svelte +21 -0
  421. package/web/src/lib/components/ui/sidebar/sidebar-group-label.svelte +34 -0
  422. package/web/src/lib/components/ui/sidebar/sidebar-group.svelte +21 -0
  423. package/web/src/lib/components/ui/sidebar/sidebar-header.svelte +21 -0
  424. package/web/src/lib/components/ui/sidebar/sidebar-input.svelte +21 -0
  425. package/web/src/lib/components/ui/sidebar/sidebar-inset.svelte +24 -0
  426. package/web/src/lib/components/ui/sidebar/sidebar-menu-action.svelte +43 -0
  427. package/web/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte +29 -0
  428. package/web/src/lib/components/ui/sidebar/sidebar-menu-button.svelte +103 -0
  429. package/web/src/lib/components/ui/sidebar/sidebar-menu-item.svelte +21 -0
  430. package/web/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte +36 -0
  431. package/web/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte +43 -0
  432. package/web/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte +21 -0
  433. package/web/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte +25 -0
  434. package/web/src/lib/components/ui/sidebar/sidebar-menu.svelte +21 -0
  435. package/web/src/lib/components/ui/sidebar/sidebar-provider.svelte +53 -0
  436. package/web/src/lib/components/ui/sidebar/sidebar-rail.svelte +36 -0
  437. package/web/src/lib/components/ui/sidebar/sidebar-separator.svelte +19 -0
  438. package/web/src/lib/components/ui/sidebar/sidebar-trigger.svelte +35 -0
  439. package/web/src/lib/components/ui/sidebar/sidebar.svelte +104 -0
  440. package/web/src/lib/components/ui/skeleton/index.ts +7 -0
  441. package/web/src/lib/components/ui/skeleton/skeleton.svelte +17 -0
  442. package/web/src/lib/components/ui/switch/index.ts +7 -0
  443. package/web/src/lib/components/ui/switch/switch.svelte +29 -0
  444. package/web/src/lib/components/ui/textarea/index.ts +7 -0
  445. package/web/src/lib/components/ui/textarea/textarea.svelte +23 -0
  446. package/web/src/lib/components/ui/tooltip/index.ts +19 -0
  447. package/web/src/lib/components/ui/tooltip/tooltip-content.svelte +52 -0
  448. package/web/src/lib/components/ui/tooltip/tooltip-portal.svelte +7 -0
  449. package/web/src/lib/components/ui/tooltip/tooltip-provider.svelte +7 -0
  450. package/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte +7 -0
  451. package/web/src/lib/components/ui/tooltip/tooltip.svelte +7 -0
  452. package/web/src/lib/hooks/is-mobile.svelte.ts +9 -0
  453. package/web/src/lib/index.ts +1 -0
  454. package/web/src/lib/types.ts +23 -0
  455. package/web/src/lib/utils.ts +13 -0
  456. package/web/src/routes/+layout.svelte +67 -0
  457. package/web/src/routes/+layout.ts +34 -0
  458. package/web/src/routes/+page.svelte +7 -0
  459. package/web/src/routes/agents/+page.svelte +206 -0
  460. package/web/src/routes/chats/[id]/+page.svelte +406 -0
  461. package/web/src/routes/chats/[id]/+page.ts +19 -0
  462. package/web/src/routes/chats/[id]/page.svelte.spec.ts +102 -0
  463. package/web/src/routes/chats/[id]/settings/+page.svelte +165 -0
  464. package/web/src/routes/chats/[id]/settings/+page.ts +18 -0
  465. package/web/src/routes/page.svelte.spec.ts +13 -0
  466. package/web/static/robots.txt +3 -0
  467. package/web/svelte.config.js +21 -0
  468. package/web/tsconfig.json +20 -0
  469. package/web/vite.config.ts +41 -0
@@ -0,0 +1,206 @@
1
+ <script lang="ts">
2
+ import { invalidate } from '$app/navigation';
3
+ import { Button } from '$lib/components/ui/button/index.js';
4
+ import { Input } from '$lib/components/ui/input/index.js';
5
+ import * as Dialog from '$lib/components/ui/dialog/index.js';
6
+ import { Plus, Trash, Edit, Bot } from 'lucide-svelte';
7
+
8
+ let { data }: { data: { agents: { id: string; directory?: string; env?: Record<string, string> }[] } } = $props();
9
+
10
+ let agents = $derived(data.agents || []);
11
+
12
+ let editAgentOpen = $state(false);
13
+ let isEditing = $state(false);
14
+
15
+ let agentId = $state('');
16
+ let agentDirectory = $state('');
17
+ let agentEnv = $state(''); // will parse as JSON or multiline KEY=VALUE
18
+
19
+ let isSaving = $state(false);
20
+ let errorMessage = $state('');
21
+ let globalError = $state('');
22
+
23
+ // parse/unparse env for editing
24
+ function unparseEnv(envObj: any) {
25
+ if (!envObj) return '';
26
+ return Object.entries(envObj).map(([k, v]) => `${k}=${v}`).join('\n');
27
+ }
28
+
29
+ function parseEnv(envStr: string) {
30
+ const obj: Record<string, string> = {};
31
+ const lines = envStr.split('\n');
32
+ for (const line of lines) {
33
+ const match = line.match(/^([^=]+)=(.*)$/);
34
+ if (match) {
35
+ obj[match[1].trim()] = match[2].trim();
36
+ }
37
+ }
38
+ return obj;
39
+ }
40
+
41
+ function openCreate() {
42
+ isEditing = false;
43
+ agentId = '';
44
+ agentDirectory = '';
45
+ agentEnv = '';
46
+ errorMessage = '';
47
+ globalError = '';
48
+ editAgentOpen = true;
49
+ }
50
+
51
+ function openEdit(agent: any) {
52
+ isEditing = true;
53
+ agentId = agent.id;
54
+ agentDirectory = agent.directory || '';
55
+ agentEnv = unparseEnv(agent.env);
56
+ errorMessage = '';
57
+ globalError = '';
58
+ editAgentOpen = true;
59
+ }
60
+
61
+ async function saveAgent() {
62
+ isSaving = true;
63
+ errorMessage = '';
64
+ try {
65
+ const payload: any = {
66
+ directory: agentDirectory.trim(),
67
+ env: parseEnv(agentEnv),
68
+ };
69
+ if (!isEditing) payload.id = agentId;
70
+
71
+ const url = isEditing ? `/api/agents/${agentId}` : '/api/agents';
72
+ const method = isEditing ? 'PUT' : 'POST';
73
+
74
+ const res = await fetch(url, {
75
+ method,
76
+ headers: { 'Content-Type': 'application/json' },
77
+ body: JSON.stringify(payload)
78
+ });
79
+
80
+ if (res.ok) {
81
+ editAgentOpen = false;
82
+ await invalidate('app:agents');
83
+ } else {
84
+ const d = await res.json();
85
+ errorMessage = d.error || 'Failed to save agent';
86
+ }
87
+ } catch (e) {
88
+ errorMessage = 'Error saving agent';
89
+ } finally {
90
+ isSaving = false;
91
+ }
92
+ }
93
+
94
+ async function deleteAgent(id: string) {
95
+ if (!confirm(`Are you sure you want to delete agent "${id}"?`)) return;
96
+ globalError = '';
97
+ try {
98
+ const res = await fetch(`/api/agents/${id}`, { method: 'DELETE' });
99
+ if (res.ok) {
100
+ await invalidate('app:agents');
101
+ } else {
102
+ globalError = 'Failed to delete agent';
103
+ }
104
+ } catch (e) {
105
+ globalError = 'Error deleting agent';
106
+ }
107
+ }
108
+ </script>
109
+
110
+ <div class="p-6 max-w-4xl mx-auto flex flex-col gap-6 h-full overflow-y-auto">
111
+ <div class="flex items-center justify-between">
112
+ <h1 class="text-2xl font-bold tracking-tight">Agents</h1>
113
+ <Button onclick={openCreate} size="sm">
114
+ <Plus class="w-4 h-4 mr-2" />
115
+ Create Agent
116
+ </Button>
117
+ </div>
118
+
119
+ {#if globalError}
120
+ <div class="bg-destructive/15 text-destructive text-sm p-3 rounded-md flex items-center">
121
+ {globalError}
122
+ </div>
123
+ {/if}
124
+
125
+ {#if agents.length === 0}
126
+ <div class="flex flex-col items-center justify-center p-12 text-center text-muted-foreground border rounded-lg bg-background">
127
+ <Bot class="w-12 h-12 mb-4 text-muted-foreground/50" />
128
+ <p>No agents created yet.</p>
129
+ </div>
130
+ {:else}
131
+ <div class="grid gap-4 md:grid-cols-2">
132
+ {#each agents as agent}
133
+ <div class="flex flex-col gap-2 p-4 border rounded-lg bg-background shadow-sm">
134
+ <div class="flex items-center justify-between">
135
+ <h2 class="font-semibold text-lg flex items-center gap-2">
136
+ <Bot class="w-5 h-5 text-primary" />
137
+ {agent.id}
138
+ </h2>
139
+ <div class="flex gap-2">
140
+ <Button variant="outline" size="icon" onclick={() => openEdit(agent)}>
141
+ <Edit class="w-4 h-4" />
142
+ </Button>
143
+ <Button variant="destructive" size="icon" onclick={() => deleteAgent(agent.id)}>
144
+ <Trash class="w-4 h-4" />
145
+ </Button>
146
+ </div>
147
+ </div>
148
+ {#if agent.directory}
149
+ <div class="text-sm mt-2">
150
+ <span class="text-muted-foreground font-medium">Directory:</span>
151
+ <code class="ml-1 bg-muted px-1 py-0.5 rounded text-xs">{agent.directory}</code>
152
+ </div>
153
+ {/if}
154
+ {#if agent.env && Object.keys(agent.env).length > 0}
155
+ <div class="text-sm mt-2">
156
+ <span class="text-muted-foreground font-medium">Environment Variables:</span>
157
+ <div class="mt-1 bg-muted p-2 rounded text-xs overflow-x-auto whitespace-pre">
158
+ {Object.entries(agent.env).map(([k, v]) => `${k}=${v}`).join('\n')}
159
+ </div>
160
+ </div>
161
+ {/if}
162
+ </div>
163
+ {/each}
164
+ </div>
165
+ {/if}
166
+
167
+ <Dialog.Root bind:open={editAgentOpen}>
168
+ <Dialog.Content class="sm:max-w-[425px]">
169
+ <Dialog.Header>
170
+ <Dialog.Title>{isEditing ? 'Edit Agent' : 'Create Agent'}</Dialog.Title>
171
+ <Dialog.Description>
172
+ Configure the agent's working directory and environment variables.
173
+ </Dialog.Description>
174
+ </Dialog.Header>
175
+ <div class="grid gap-4 py-4">
176
+ {#if errorMessage}
177
+ <div class="bg-destructive/15 text-destructive text-sm p-3 rounded-md flex items-center">
178
+ {errorMessage}
179
+ </div>
180
+ {/if}
181
+ <div class="flex flex-col gap-2">
182
+ <label class="text-sm font-medium leading-none" for="agent-id">ID</label>
183
+ <Input id="agent-id" bind:value={agentId} disabled={isEditing} placeholder="e.g. backend-agent" />
184
+ </div>
185
+ <div class="flex flex-col gap-2">
186
+ <label class="text-sm font-medium leading-none" for="agent-dir">Directory</label>
187
+ <Input id="agent-dir" bind:value={agentDirectory} placeholder="e.g. ./apps/backend" />
188
+ </div>
189
+ <div class="flex flex-col gap-2">
190
+ <label class="text-sm font-medium leading-none" for="agent-env">Environment Variables (KEY=VALUE)</label>
191
+ <textarea
192
+ id="agent-env"
193
+ bind:value={agentEnv}
194
+ placeholder="PORT=8080&#10;NODE_ENV=development"
195
+ class="flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
196
+ ></textarea>
197
+ </div>
198
+ </div>
199
+ <Dialog.Footer>
200
+ <Button disabled={!agentId || isSaving} onclick={saveAgent}>
201
+ {isSaving ? 'Saving...' : 'Save Agent'}
202
+ </Button>
203
+ </Dialog.Footer>
204
+ </Dialog.Content>
205
+ </Dialog.Root>
206
+ </div>
@@ -0,0 +1,406 @@
1
+ <script lang="ts">
2
+ import type { PageData } from './$types';
3
+ import type { ChatMessage } from '$lib/types';
4
+ import { invalidate } from '$app/navigation';
5
+ import { Send, Clock, AlertCircle } from 'lucide-svelte';
6
+ import { Button } from '$lib/components/ui/button/index.js';
7
+ import { Textarea } from '$lib/components/ui/textarea/index.js';
8
+ import { tick, onMount, onDestroy } from 'svelte';
9
+ import { appState } from '$lib/app-state.svelte.js';
10
+
11
+ let { data } = $props<{ data: PageData }>();
12
+
13
+ type PendingStatus = 'sending' | 'pending' | 'failed';
14
+ interface PendingMessage {
15
+ id: string;
16
+ content: string;
17
+ timestamp: string;
18
+ status: PendingStatus;
19
+ }
20
+
21
+ let inputValue = $state('');
22
+ let liveMessages = $state<ChatMessage[]>([]);
23
+ let filteredMessages = $derived(liveMessages.filter((msg) => {
24
+ if (msg.role === 'user') return true;
25
+ if (appState.verbosityLevel === 'verbose') return true;
26
+ if (appState.verbosityLevel === 'debug') {
27
+ return !msg.level || msg.level === 'default' || msg.level === 'debug';
28
+ }
29
+ return !msg.level || msg.level === 'default';
30
+ }));
31
+ let pendingMessages = $state<PendingMessage[]>([]);
32
+ let chatContainer: HTMLElement | undefined = $state();
33
+ let eventSource: EventSource | null = null;
34
+ let isScrolledToBottom = $state(true);
35
+ let isReconnecting = $state(false);
36
+ let reconnectTimeout: ReturnType<typeof setTimeout>;
37
+ let activeActionId = $state<string | null>(null);
38
+
39
+ async function retryMessage(msgId: string) {
40
+ const msgIndex = pendingMessages.findIndex(m => m.id === msgId);
41
+ if (msgIndex === -1) return;
42
+
43
+ pendingMessages[msgIndex].status = 'sending';
44
+ savePendingMessages(data.id, pendingMessages);
45
+
46
+ try {
47
+ const res = await fetch(`/api/chats/${data.id}/messages`, {
48
+ method: 'POST',
49
+ headers: { 'Content-Type': 'application/json' },
50
+ body: JSON.stringify({ message: pendingMessages[msgIndex].content })
51
+ });
52
+ if (!res.ok) throw new Error('Failed to send');
53
+
54
+ await invalidate(`app:chat:${data.id}`);
55
+ } catch (err) {
56
+ console.error('Failed to retry message:', err);
57
+ const idx = pendingMessages.findIndex(m => m.id === msgId);
58
+ if (idx !== -1) {
59
+ pendingMessages[idx].status = 'failed';
60
+ savePendingMessages(data.id, pendingMessages);
61
+ }
62
+ }
63
+ }
64
+
65
+ function deleteMessage(msgId: string) {
66
+ pendingMessages = pendingMessages.filter(m => m.id !== msgId);
67
+ savePendingMessages(data.id, pendingMessages);
68
+ }
69
+
70
+ $effect(() => {
71
+ const handleOnline = () => {
72
+ pendingMessages.forEach((msg) => {
73
+ if (msg.status === 'pending' || msg.status === 'failed') {
74
+ retryMessage(msg.id);
75
+ }
76
+ });
77
+ };
78
+
79
+ window.addEventListener('online', handleOnline);
80
+ return () => {
81
+ window.removeEventListener('online', handleOnline);
82
+ };
83
+ });
84
+
85
+ function checkScroll(e: Event) {
86
+ const target = e.target as HTMLElement;
87
+ // Allow a 10px threshold for being at the bottom
88
+ isScrolledToBottom = Math.abs(target.scrollHeight - target.scrollTop - target.clientHeight) < 10;
89
+ }
90
+
91
+ function scrollToBottom() {
92
+ if (chatContainer) {
93
+ chatContainer.scrollTop = chatContainer.scrollHeight;
94
+ }
95
+ }
96
+
97
+ // We sync live messages with initial loaded data whenever the ID changes
98
+ $effect(() => {
99
+ liveMessages = data.messages as ChatMessage[];
100
+
101
+ // Load pending from local storage
102
+ try {
103
+ const stored = localStorage.getItem(`pending_messages_${data.id}`);
104
+ if (stored) {
105
+ pendingMessages = JSON.parse(stored);
106
+ } else {
107
+ pendingMessages = [];
108
+ }
109
+ } catch {
110
+ pendingMessages = [];
111
+ }
112
+
113
+ isScrolledToBottom = true;
114
+ setupSSE(data.id);
115
+ });
116
+
117
+ function savePendingMessages(chatId: string, messages: PendingMessage[]) {
118
+ localStorage.setItem(`pending_messages_${chatId}`, JSON.stringify(messages));
119
+ }
120
+
121
+ // Auto-scroll on new messages
122
+ $effect(() => {
123
+ if ((liveMessages.length > 0 || pendingMessages.length > 0) && chatContainer && isScrolledToBottom) {
124
+ tick().then(scrollToBottom);
125
+ }
126
+ });
127
+
128
+ // Keep scrolled to bottom if textarea grows
129
+ $effect(() => {
130
+ // depend on inputValue changes
131
+ inputValue;
132
+ if (isScrolledToBottom) {
133
+ tick().then(scrollToBottom);
134
+ }
135
+ });
136
+
137
+ // Keep scrolled to bottom on window or visualViewport resize (e.g., keyboard toggle)
138
+ $effect(() => {
139
+ const handleResize = () => {
140
+ if (isScrolledToBottom) {
141
+ scrollToBottom();
142
+ }
143
+ };
144
+
145
+ window.addEventListener('resize', handleResize);
146
+ window.visualViewport?.addEventListener('resize', handleResize);
147
+
148
+ return () => {
149
+ window.removeEventListener('resize', handleResize);
150
+ window.visualViewport?.removeEventListener('resize', handleResize);
151
+ };
152
+ });
153
+
154
+ async function fetchDeltaMessages() {
155
+ const lastMsgId = liveMessages.length > 0 ? liveMessages[liveMessages.length - 1].id : null;
156
+ if (lastMsgId) {
157
+ try {
158
+ const res = await fetch(`/api/chats/${data.id}?since=${lastMsgId}`);
159
+ if (res.ok) {
160
+ const newMessages: ChatMessage[] = await res.json();
161
+ for (const msg of newMessages) {
162
+ if (!liveMessages.find((m) => m.id === msg.id)) {
163
+ liveMessages = [...liveMessages, msg];
164
+ }
165
+ }
166
+ }
167
+ } catch (e) {
168
+ console.error('Failed to fetch delta messages:', e);
169
+ }
170
+ } else {
171
+ await invalidate(`app:chat:${data.id}`);
172
+ }
173
+ }
174
+
175
+ $effect(() => {
176
+ const handleVisibilityChange = () => {
177
+ if (document.visibilityState === 'visible') {
178
+ fetchDeltaMessages();
179
+ if (!eventSource || eventSource.readyState === EventSource.CLOSED) {
180
+ setupSSE(data.id);
181
+ }
182
+ }
183
+ };
184
+
185
+ document.addEventListener('visibilitychange', handleVisibilityChange);
186
+ return () => {
187
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
188
+ };
189
+ });
190
+
191
+ function setupSSE(chatId: string) {
192
+ if (eventSource) {
193
+ eventSource.close();
194
+ clearTimeout(reconnectTimeout);
195
+ }
196
+
197
+ eventSource = new EventSource(`/api/chats/${chatId}/stream`);
198
+
199
+ eventSource.onopen = () => {
200
+ isReconnecting = false;
201
+ };
202
+
203
+ eventSource.onerror = () => {
204
+ if (eventSource?.readyState === EventSource.CLOSED || eventSource?.readyState === EventSource.CONNECTING) {
205
+ isReconnecting = true;
206
+ eventSource.close();
207
+ clearTimeout(reconnectTimeout);
208
+ reconnectTimeout = setTimeout(async () => {
209
+ await fetchDeltaMessages();
210
+ setupSSE(chatId);
211
+ }, 3000);
212
+ }
213
+ };
214
+
215
+ eventSource.onmessage = (event) => {
216
+ try {
217
+ const newMessage = JSON.parse(event.data);
218
+ // Ensure we don't duplicate messages we just sent and received via SSE
219
+ if (!liveMessages.find((m) => m.id === newMessage.id)) {
220
+ liveMessages = [...liveMessages, newMessage];
221
+
222
+ if (newMessage.role === 'user') {
223
+ // Remove from pending by matching content
224
+ const idx = pendingMessages.findIndex(m => m.content === newMessage.content);
225
+ if (idx !== -1) {
226
+ pendingMessages = pendingMessages.filter((_, i) => i !== idx);
227
+ savePendingMessages(chatId, pendingMessages);
228
+ }
229
+ }
230
+ }
231
+ } catch (e) {
232
+ console.error('Failed to parse SSE message', e);
233
+ }
234
+ };
235
+ }
236
+
237
+ onDestroy(() => {
238
+ if (eventSource) {
239
+ eventSource.close();
240
+ }
241
+ clearTimeout(reconnectTimeout);
242
+ });
243
+
244
+ async function sendMessage(e: Event) {
245
+ e.preventDefault();
246
+ if (!inputValue.trim()) return;
247
+
248
+ const currentInput = inputValue;
249
+ inputValue = '';
250
+
251
+ const pendingMsg: PendingMessage = {
252
+ id: `pending-${Date.now()}-${Math.random()}`,
253
+ content: currentInput,
254
+ timestamp: new Date().toISOString(),
255
+ status: navigator.onLine ? 'sending' : 'pending'
256
+ };
257
+ pendingMessages = [...pendingMessages, pendingMsg];
258
+ savePendingMessages(data.id, pendingMessages);
259
+
260
+ if (!navigator.onLine) {
261
+ return; // Offline, stays pending
262
+ }
263
+
264
+ try {
265
+ const res = await fetch(`/api/chats/${data.id}/messages`, {
266
+ method: 'POST',
267
+ headers: { 'Content-Type': 'application/json' },
268
+ body: JSON.stringify({ message: currentInput })
269
+ });
270
+ if (!res.ok) throw new Error('Failed to send');
271
+
272
+ // SSE should handle the incoming log and user messages now.
273
+ // But we can still invalidate to be safe.
274
+ await invalidate(`app:chat:${data.id}`);
275
+ } catch (err) {
276
+ console.error('Failed to send message:', err);
277
+ // Mark as failed instead of removing
278
+ pendingMessages = pendingMessages.map((m) => m.id === pendingMsg.id ? { ...m, status: 'failed' } : m);
279
+ savePendingMessages(data.id, pendingMessages);
280
+ }
281
+ }
282
+
283
+ function formatTime(iso: string) {
284
+ return new Date(iso).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
285
+ }
286
+ </script>
287
+
288
+ <div class="flex flex-col flex-1 h-full overflow-hidden relative">
289
+ {#if isReconnecting}
290
+ <div class="absolute top-4 left-1/2 -translate-x-1/2 bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 text-xs px-3 py-1 rounded-full border border-yellow-500/20 backdrop-blur-sm shadow-sm z-10 flex items-center gap-2">
291
+ <span class="inline-block w-3 h-3 rounded-full border-2 border-current border-t-transparent animate-spin"></span>
292
+ Reconnecting...
293
+ </div>
294
+ {/if}
295
+
296
+ <div bind:this={chatContainer} onscroll={checkScroll} class="flex-1 overflow-y-auto p-4">
297
+ <div class="w-full max-w-4xl mx-auto space-y-6 flex flex-col min-h-full">
298
+ {#if liveMessages.length === 0}
299
+ <div class="flex-1 flex items-center justify-center text-muted-foreground text-sm">
300
+ No messages yet. Send a message to start the conversation!
301
+ </div>
302
+ {/if}
303
+
304
+ {#each filteredMessages as msg (msg.id)}
305
+ <div class="flex flex-col gap-1 {msg.role === 'user' ? 'items-end' : 'items-start'}">
306
+ <div class="flex items-baseline gap-2 max-w-[80%] {msg.role === 'user' ? 'flex-row-reverse' : 'flex-row'}">
307
+ {#if msg.role === 'user'}
308
+ <div class="px-4 py-2 rounded-2xl bg-primary text-primary-foreground text-sm" data-testid="user-message">
309
+ {msg.content}
310
+ </div>
311
+ {:else}
312
+ <div class="px-4 py-3 rounded-2xl bg-card border text-card-foreground text-sm shadow-sm {msg.level === 'verbose' ? 'border-primary/50 bg-primary/5 shadow-md' : ''}" data-testid="log-message">
313
+ {#if appState.verbosityLevel === 'verbose'}
314
+ <div class="font-mono text-xs text-muted-foreground mb-2 flex items-center gap-2">
315
+ <span>$ {msg.command}</span>
316
+ {#if msg.exitCode !== 0}
317
+ <span class="text-destructive font-bold">Exit: {msg.exitCode}</span>
318
+ {/if}
319
+ </div>
320
+
321
+ {#if msg.content}
322
+ <div class="whitespace-pre-wrap">{msg.content}</div>
323
+ {:else if msg.stdout}
324
+ <div class="whitespace-pre-wrap font-mono text-xs mt-2">{msg.stdout}</div>
325
+ {:else}
326
+ <div class="whitespace-pre-wrap italic opacity-50 text-xs mt-2">No output</div>
327
+ {/if}
328
+
329
+ {#if msg.stderr}
330
+ <div class="whitespace-pre-wrap font-mono text-xs mt-2 text-destructive border border-destructive/20 bg-destructive/5 p-2 rounded">
331
+ {msg.stderr}
332
+ </div>
333
+ {/if}
334
+ {:else}
335
+ {#if msg.content}
336
+ <div class="whitespace-pre-wrap">{msg.content}</div>
337
+ {/if}
338
+ {/if}
339
+ </div>
340
+ {/if}
341
+ </div>
342
+ <div class="text-[10px] text-muted-foreground px-2">
343
+ {formatTime(msg.timestamp)}
344
+ </div>
345
+ </div>
346
+ {/each}
347
+
348
+ {#each pendingMessages as msg (msg.id)}
349
+ <div class="flex flex-col gap-1 items-end {msg.status === 'sending' ? 'opacity-50' : ''} transition-opacity">
350
+ <button
351
+ class="flex items-baseline gap-2 max-w-[80%] flex-row-reverse text-left focus:outline-none"
352
+ onclick={() => {
353
+ if (msg.status !== 'sending') {
354
+ activeActionId = activeActionId === msg.id ? null : msg.id;
355
+ }
356
+ }}
357
+ >
358
+ <div class="px-4 py-2 rounded-2xl {msg.status === 'failed' ? 'bg-destructive/90 text-destructive-foreground' : 'bg-primary text-primary-foreground'} text-sm" data-testid="pending-message">
359
+ {msg.content}
360
+ </div>
361
+ </button>
362
+ <div class="text-[10px] px-2 flex items-center gap-1 {msg.status === 'failed' ? 'text-destructive font-medium' : 'text-muted-foreground'}">
363
+ {#if msg.status === 'sending'}
364
+ <span class="inline-block w-2 h-2 rounded-full border border-current border-t-transparent animate-spin"></span>
365
+ Sending...
366
+ {:else if msg.status === 'pending'}
367
+ <Clock class="w-3 h-3" />
368
+ Offline / Pending
369
+ {:else if msg.status === 'failed'}
370
+ <AlertCircle class="w-3 h-3" />
371
+ Failed
372
+ {/if}
373
+ </div>
374
+ {#if activeActionId === msg.id && msg.status !== 'sending'}
375
+ <div class="flex items-center gap-2 mt-1 mr-2 bg-card border rounded-md p-1 shadow-sm text-xs">
376
+ <button class="px-2 py-1 hover:bg-muted rounded text-primary transition-colors focus:outline-none" onclick={(e) => { e.stopPropagation(); activeActionId = null; retryMessage(msg.id); }}>Retry manual send</button>
377
+ <div class="w-px h-3 bg-border"></div>
378
+ <button class="px-2 py-1 hover:bg-muted rounded text-destructive transition-colors focus:outline-none" onclick={(e) => { e.stopPropagation(); activeActionId = null; deleteMessage(msg.id); }}>Delete message</button>
379
+ </div>
380
+ {/if}
381
+ </div>
382
+ {/each}
383
+ </div>
384
+ </div>
385
+
386
+ <div class="p-4 bg-background/80 backdrop-blur-sm border-t shrink-0">
387
+ <form onsubmit={sendMessage} class="flex items-center gap-2 max-w-4xl mx-auto">
388
+ <Textarea
389
+ bind:value={inputValue}
390
+ placeholder="Type your message..."
391
+ class="flex-1 min-h-[0px] resize-none overflow-hidden h-auto"
392
+ onkeydown={(e) => {
393
+ if (e.key === 'Enter' && !e.shiftKey) {
394
+ e.preventDefault();
395
+ sendMessage(e);
396
+ }
397
+ }}
398
+ data-testid="message-input"
399
+ />
400
+ <Button type="submit" disabled={!inputValue.trim()} size="icon" data-testid="send-button">
401
+ <Send class="w-4 h-4" />
402
+ <span class="sr-only">Send</span>
403
+ </Button>
404
+ </form>
405
+ </div>
406
+ </div>
@@ -0,0 +1,19 @@
1
+ import type { PageLoad } from './$types';
2
+ import type { ChatMessage } from '$lib/types';
3
+
4
+ export const load: PageLoad = async ({ params, fetch, depends }) => {
5
+ const { id } = params;
6
+ depends(`app:chat:${id}`);
7
+
8
+ try {
9
+ const res = await fetch(`/api/chats/${id}`);
10
+ if (res.ok) {
11
+ const messages: ChatMessage[] = await res.json();
12
+ return { id, messages };
13
+ }
14
+ } catch (e) {
15
+ console.error(e);
16
+ }
17
+
18
+ return { id, messages: [] };
19
+ };