agent-orcha 0.0.4 → 0.0.7

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 (554) hide show
  1. package/README.md +216 -1323
  2. package/dist/lib/agents/agent-executor.d.ts +15 -4
  3. package/dist/lib/agents/agent-executor.d.ts.map +1 -1
  4. package/dist/lib/agents/agent-executor.js +337 -87
  5. package/dist/lib/agents/agent-executor.js.map +1 -1
  6. package/dist/lib/agents/agent-loader.d.ts +4 -1
  7. package/dist/lib/agents/agent-loader.d.ts.map +1 -1
  8. package/dist/lib/agents/agent-loader.js +20 -4
  9. package/dist/lib/agents/agent-loader.js.map +1 -1
  10. package/dist/lib/agents/index.d.ts +5 -5
  11. package/dist/lib/agents/index.js +4 -4
  12. package/dist/lib/agents/react-loop.d.ts +30 -0
  13. package/dist/lib/agents/react-loop.d.ts.map +1 -0
  14. package/dist/lib/agents/react-loop.js +193 -0
  15. package/dist/lib/agents/react-loop.js.map +1 -0
  16. package/dist/lib/agents/structured-output-wrapper.d.ts +3 -3
  17. package/dist/lib/agents/structured-output-wrapper.d.ts.map +1 -1
  18. package/dist/lib/agents/structured-output-wrapper.js +2 -2
  19. package/dist/lib/agents/structured-output-wrapper.js.map +1 -1
  20. package/dist/lib/agents/types.d.ts +306 -19
  21. package/dist/lib/agents/types.d.ts.map +1 -1
  22. package/dist/lib/agents/types.js +34 -4
  23. package/dist/lib/agents/types.js.map +1 -1
  24. package/dist/lib/functions/function-loader.d.ts +3 -1
  25. package/dist/lib/functions/function-loader.d.ts.map +1 -1
  26. package/dist/lib/functions/function-loader.js +23 -5
  27. package/dist/lib/functions/function-loader.js.map +1 -1
  28. package/dist/lib/functions/index.d.ts +2 -2
  29. package/dist/lib/functions/index.js +1 -1
  30. package/dist/lib/functions/simple-function-wrapper.d.ts +2 -11
  31. package/dist/lib/functions/simple-function-wrapper.d.ts.map +1 -1
  32. package/dist/lib/functions/simple-function-wrapper.js +3 -3
  33. package/dist/lib/functions/simple-function-wrapper.js.map +1 -1
  34. package/dist/lib/index.d.ts +29 -17
  35. package/dist/lib/index.d.ts.map +1 -1
  36. package/dist/lib/index.js +21 -9
  37. package/dist/lib/index.js.map +1 -1
  38. package/dist/lib/integrations/collabnook.d.ts +56 -0
  39. package/dist/lib/integrations/collabnook.d.ts.map +1 -0
  40. package/dist/lib/integrations/collabnook.js +307 -0
  41. package/dist/lib/integrations/collabnook.js.map +1 -0
  42. package/dist/lib/integrations/email.d.ts +38 -0
  43. package/dist/lib/integrations/email.d.ts.map +1 -0
  44. package/dist/lib/integrations/email.js +249 -0
  45. package/dist/lib/integrations/email.js.map +1 -0
  46. package/dist/lib/integrations/integration-manager.d.ts +19 -0
  47. package/dist/lib/integrations/integration-manager.d.ts.map +1 -0
  48. package/dist/lib/integrations/integration-manager.js +159 -0
  49. package/dist/lib/integrations/integration-manager.js.map +1 -0
  50. package/dist/lib/integrations/types.d.ts +232 -0
  51. package/dist/lib/integrations/types.d.ts.map +1 -0
  52. package/dist/lib/integrations/types.js +37 -0
  53. package/dist/lib/integrations/types.js.map +1 -0
  54. package/dist/lib/knowledge/direct-mapper.d.ts +21 -0
  55. package/dist/lib/knowledge/direct-mapper.d.ts.map +1 -0
  56. package/dist/lib/knowledge/direct-mapper.js +134 -0
  57. package/dist/lib/knowledge/direct-mapper.js.map +1 -0
  58. package/dist/lib/knowledge/index.d.ts +9 -8
  59. package/dist/lib/knowledge/index.d.ts.map +1 -1
  60. package/dist/lib/knowledge/index.js +9 -7
  61. package/dist/lib/knowledge/index.js.map +1 -1
  62. package/dist/lib/knowledge/knowledge-store-metadata.d.ts +40 -0
  63. package/dist/lib/knowledge/knowledge-store-metadata.d.ts.map +1 -0
  64. package/dist/lib/knowledge/knowledge-store-metadata.js +93 -0
  65. package/dist/lib/knowledge/knowledge-store-metadata.js.map +1 -0
  66. package/dist/lib/knowledge/knowledge-store.d.ts +49 -0
  67. package/dist/lib/knowledge/knowledge-store.d.ts.map +1 -0
  68. package/dist/lib/knowledge/knowledge-store.js +670 -0
  69. package/dist/lib/knowledge/knowledge-store.js.map +1 -0
  70. package/dist/lib/knowledge/loaders/database-loader.d.ts +5 -5
  71. package/dist/lib/knowledge/loaders/database-loader.d.ts.map +1 -1
  72. package/dist/lib/knowledge/loaders/database-loader.js +21 -10
  73. package/dist/lib/knowledge/loaders/database-loader.js.map +1 -1
  74. package/dist/lib/knowledge/loaders/file-loaders.d.ts +45 -0
  75. package/dist/lib/knowledge/loaders/file-loaders.d.ts.map +1 -0
  76. package/dist/lib/knowledge/loaders/file-loaders.js +160 -0
  77. package/dist/lib/knowledge/loaders/file-loaders.js.map +1 -0
  78. package/dist/lib/knowledge/loaders/index.d.ts +3 -3
  79. package/dist/lib/knowledge/loaders/index.d.ts.map +1 -1
  80. package/dist/lib/knowledge/loaders/index.js +3 -3
  81. package/dist/lib/knowledge/loaders/index.js.map +1 -1
  82. package/dist/lib/knowledge/loaders/web-loader.d.ts +14 -5
  83. package/dist/lib/knowledge/loaders/web-loader.d.ts.map +1 -1
  84. package/dist/lib/knowledge/loaders/web-loader.js +64 -29
  85. package/dist/lib/knowledge/loaders/web-loader.js.map +1 -1
  86. package/dist/lib/knowledge/sqlite-store.d.ts +107 -0
  87. package/dist/lib/knowledge/sqlite-store.d.ts.map +1 -0
  88. package/dist/lib/knowledge/sqlite-store.js +327 -0
  89. package/dist/lib/knowledge/sqlite-store.js.map +1 -0
  90. package/dist/lib/knowledge/types.d.ts +124 -1448
  91. package/dist/lib/knowledge/types.d.ts.map +1 -1
  92. package/dist/lib/knowledge/types.js +54 -60
  93. package/dist/lib/knowledge/types.js.map +1 -1
  94. package/dist/lib/knowledge/utils/connection-pool.d.ts +3 -2
  95. package/dist/lib/knowledge/utils/connection-pool.d.ts.map +1 -1
  96. package/dist/lib/knowledge/utils/connection-pool.js +20 -10
  97. package/dist/lib/knowledge/utils/connection-pool.js.map +1 -1
  98. package/dist/lib/knowledge/utils/index.d.ts +2 -2
  99. package/dist/lib/knowledge/utils/index.js +2 -2
  100. package/dist/lib/llm/index.d.ts +5 -5
  101. package/dist/lib/llm/index.d.ts.map +1 -1
  102. package/dist/lib/llm/index.js +3 -3
  103. package/dist/lib/llm/index.js.map +1 -1
  104. package/dist/lib/llm/llm-call-logger.d.ts +39 -0
  105. package/dist/lib/llm/llm-call-logger.d.ts.map +1 -0
  106. package/dist/lib/llm/llm-call-logger.js +110 -0
  107. package/dist/lib/llm/llm-call-logger.js.map +1 -0
  108. package/dist/lib/llm/llm-config.d.ts +30 -16
  109. package/dist/lib/llm/llm-config.d.ts.map +1 -1
  110. package/dist/lib/llm/llm-config.js +22 -4
  111. package/dist/lib/llm/llm-config.js.map +1 -1
  112. package/dist/lib/llm/llm-factory.d.ts +3 -16
  113. package/dist/lib/llm/llm-factory.d.ts.map +1 -1
  114. package/dist/lib/llm/llm-factory.js +33 -48
  115. package/dist/lib/llm/llm-factory.js.map +1 -1
  116. package/dist/lib/llm/provider-detector.d.ts +1 -1
  117. package/dist/lib/llm/providers/anthropic-chat-model.d.ts +31 -0
  118. package/dist/lib/llm/providers/anthropic-chat-model.d.ts.map +1 -0
  119. package/dist/lib/llm/providers/anthropic-chat-model.js +262 -0
  120. package/dist/lib/llm/providers/anthropic-chat-model.js.map +1 -0
  121. package/dist/lib/llm/providers/gemini-chat-model.d.ts +30 -0
  122. package/dist/lib/llm/providers/gemini-chat-model.d.ts.map +1 -0
  123. package/dist/lib/llm/providers/gemini-chat-model.js +300 -0
  124. package/dist/lib/llm/providers/gemini-chat-model.js.map +1 -0
  125. package/dist/lib/llm/providers/gemini-embeddings.d.ts +14 -0
  126. package/dist/lib/llm/providers/gemini-embeddings.d.ts.map +1 -0
  127. package/dist/lib/llm/providers/gemini-embeddings.js +20 -0
  128. package/dist/lib/llm/providers/gemini-embeddings.js.map +1 -0
  129. package/dist/lib/llm/providers/openai-chat-model.d.ts +39 -0
  130. package/dist/lib/llm/providers/openai-chat-model.d.ts.map +1 -0
  131. package/dist/lib/llm/providers/openai-chat-model.js +397 -0
  132. package/dist/lib/llm/providers/openai-chat-model.js.map +1 -0
  133. package/dist/lib/llm/providers/openai-embeddings.d.ts +17 -0
  134. package/dist/lib/llm/providers/openai-embeddings.d.ts.map +1 -0
  135. package/dist/lib/llm/providers/openai-embeddings.js +35 -0
  136. package/dist/lib/llm/providers/openai-embeddings.js.map +1 -0
  137. package/dist/lib/llm/types.d.ts +2 -2
  138. package/dist/lib/llm/types.js +1 -1
  139. package/dist/lib/logger.d.ts +7 -0
  140. package/dist/lib/logger.d.ts.map +1 -1
  141. package/dist/lib/logger.js +42 -4
  142. package/dist/lib/logger.js.map +1 -1
  143. package/dist/lib/mcp/index.d.ts +3 -3
  144. package/dist/lib/mcp/index.js +2 -2
  145. package/dist/lib/mcp/mcp-client.d.ts +3 -2
  146. package/dist/lib/mcp/mcp-client.d.ts.map +1 -1
  147. package/dist/lib/mcp/mcp-client.js +10 -5
  148. package/dist/lib/mcp/mcp-client.js.map +1 -1
  149. package/dist/lib/mcp/types.d.ts +26 -25
  150. package/dist/lib/mcp/types.d.ts.map +1 -1
  151. package/dist/lib/mcp/types.js +12 -4
  152. package/dist/lib/mcp/types.js.map +1 -1
  153. package/dist/lib/memory/conversation-store.d.ts +2 -2
  154. package/dist/lib/memory/conversation-store.d.ts.map +1 -1
  155. package/dist/lib/memory/conversation-store.js +1 -1
  156. package/dist/lib/memory/index.d.ts +3 -2
  157. package/dist/lib/memory/index.d.ts.map +1 -1
  158. package/dist/lib/memory/index.js +3 -2
  159. package/dist/lib/memory/index.js.map +1 -1
  160. package/dist/lib/memory/memory-manager.d.ts +10 -0
  161. package/dist/lib/memory/memory-manager.d.ts.map +1 -0
  162. package/dist/lib/memory/memory-manager.js +43 -0
  163. package/dist/lib/memory/memory-manager.js.map +1 -0
  164. package/dist/lib/memory/types.d.ts +1 -1
  165. package/dist/lib/memory/types.d.ts.map +1 -1
  166. package/dist/lib/orchestrator.d.ts +72 -18
  167. package/dist/lib/orchestrator.d.ts.map +1 -1
  168. package/dist/lib/orchestrator.js +341 -56
  169. package/dist/lib/orchestrator.js.map +1 -1
  170. package/dist/lib/sandbox/cdp-client.d.ts +14 -0
  171. package/dist/lib/sandbox/cdp-client.d.ts.map +1 -0
  172. package/dist/lib/sandbox/cdp-client.js +113 -0
  173. package/dist/lib/sandbox/cdp-client.js.map +1 -0
  174. package/dist/lib/sandbox/html-to-markdown.d.ts +10 -0
  175. package/dist/lib/sandbox/html-to-markdown.d.ts.map +1 -0
  176. package/dist/lib/sandbox/html-to-markdown.js +121 -0
  177. package/dist/lib/sandbox/html-to-markdown.js.map +1 -0
  178. package/dist/lib/sandbox/index.d.ts +12 -0
  179. package/dist/lib/sandbox/index.d.ts.map +1 -0
  180. package/dist/lib/sandbox/index.js +10 -0
  181. package/dist/lib/sandbox/index.js.map +1 -0
  182. package/dist/lib/sandbox/page-readiness.d.ts +37 -0
  183. package/dist/lib/sandbox/page-readiness.d.ts.map +1 -0
  184. package/dist/lib/sandbox/page-readiness.js +235 -0
  185. package/dist/lib/sandbox/page-readiness.js.map +1 -0
  186. package/dist/lib/sandbox/sandbox-browser.d.ts +4 -0
  187. package/dist/lib/sandbox/sandbox-browser.d.ts.map +1 -0
  188. package/dist/lib/sandbox/sandbox-browser.js +303 -0
  189. package/dist/lib/sandbox/sandbox-browser.js.map +1 -0
  190. package/dist/lib/sandbox/sandbox-exec.d.ts +5 -0
  191. package/dist/lib/sandbox/sandbox-exec.d.ts.map +1 -0
  192. package/dist/lib/sandbox/sandbox-exec.js +35 -0
  193. package/dist/lib/sandbox/sandbox-exec.js.map +1 -0
  194. package/dist/lib/sandbox/sandbox-file.d.ts +4 -0
  195. package/dist/lib/sandbox/sandbox-file.d.ts.map +1 -0
  196. package/dist/lib/sandbox/sandbox-file.js +168 -0
  197. package/dist/lib/sandbox/sandbox-file.js.map +1 -0
  198. package/dist/lib/sandbox/sandbox-shell.d.ts +4 -0
  199. package/dist/lib/sandbox/sandbox-shell.d.ts.map +1 -0
  200. package/dist/lib/sandbox/sandbox-shell.js +93 -0
  201. package/dist/lib/sandbox/sandbox-shell.js.map +1 -0
  202. package/dist/lib/sandbox/sandbox-web.d.ts +5 -0
  203. package/dist/lib/sandbox/sandbox-web.d.ts.map +1 -0
  204. package/dist/lib/sandbox/sandbox-web.js +226 -0
  205. package/dist/lib/sandbox/sandbox-web.js.map +1 -0
  206. package/dist/lib/sandbox/types.d.ts +30 -0
  207. package/dist/lib/sandbox/types.d.ts.map +1 -0
  208. package/dist/lib/sandbox/types.js +8 -0
  209. package/dist/lib/sandbox/types.js.map +1 -0
  210. package/dist/lib/sandbox/vision-browser.d.ts +4 -0
  211. package/dist/lib/sandbox/vision-browser.d.ts.map +1 -0
  212. package/dist/lib/sandbox/vision-browser.js +289 -0
  213. package/dist/lib/sandbox/vision-browser.js.map +1 -0
  214. package/dist/lib/sandbox/vm-executor.d.ts +12 -0
  215. package/dist/lib/sandbox/vm-executor.d.ts.map +1 -0
  216. package/dist/lib/sandbox/vm-executor.js +101 -0
  217. package/dist/lib/sandbox/vm-executor.js.map +1 -0
  218. package/dist/lib/skills/index.d.ts +4 -0
  219. package/dist/lib/skills/index.d.ts.map +1 -0
  220. package/dist/lib/skills/index.js +3 -0
  221. package/dist/lib/skills/index.js.map +1 -0
  222. package/dist/lib/skills/skill-loader.d.ts +21 -0
  223. package/dist/lib/skills/skill-loader.d.ts.map +1 -0
  224. package/dist/lib/skills/skill-loader.js +116 -0
  225. package/dist/lib/skills/skill-loader.js.map +1 -0
  226. package/dist/lib/skills/types.d.ts +17 -0
  227. package/dist/lib/skills/types.d.ts.map +1 -0
  228. package/dist/lib/skills/types.js +6 -0
  229. package/dist/lib/skills/types.js.map +1 -0
  230. package/dist/lib/tasks/index.d.ts +4 -0
  231. package/dist/lib/tasks/index.d.ts.map +1 -0
  232. package/dist/lib/tasks/index.js +4 -0
  233. package/dist/lib/tasks/index.js.map +1 -0
  234. package/dist/lib/tasks/task-manager.d.ts +27 -0
  235. package/dist/lib/tasks/task-manager.d.ts.map +1 -0
  236. package/dist/lib/tasks/task-manager.js +200 -0
  237. package/dist/lib/tasks/task-manager.js.map +1 -0
  238. package/dist/lib/tasks/task-store.d.ts +20 -0
  239. package/dist/lib/tasks/task-store.d.ts.map +1 -0
  240. package/dist/lib/tasks/task-store.js +102 -0
  241. package/dist/lib/tasks/task-store.js.map +1 -0
  242. package/dist/lib/tasks/types.d.ts +56 -0
  243. package/dist/lib/tasks/types.d.ts.map +1 -0
  244. package/dist/lib/tasks/types.js +2 -0
  245. package/dist/lib/tasks/types.js.map +1 -0
  246. package/dist/lib/templates/resource-templates.d.ts +6 -0
  247. package/dist/lib/templates/resource-templates.d.ts.map +1 -0
  248. package/dist/lib/templates/resource-templates.js +146 -0
  249. package/dist/lib/templates/resource-templates.js.map +1 -0
  250. package/dist/lib/tools/agent-tool-wrapper.d.ts +5 -5
  251. package/dist/lib/tools/agent-tool-wrapper.js +3 -3
  252. package/dist/lib/tools/agent-tool-wrapper.js.map +1 -1
  253. package/dist/lib/tools/built-in/ask-user.tool.d.ts +1 -1
  254. package/dist/lib/tools/built-in/ask-user.tool.d.ts.map +1 -1
  255. package/dist/lib/tools/built-in/ask-user.tool.js +2 -2
  256. package/dist/lib/tools/built-in/ask-user.tool.js.map +1 -1
  257. package/dist/lib/tools/built-in/index.d.ts +9 -2
  258. package/dist/lib/tools/built-in/index.d.ts.map +1 -1
  259. package/dist/lib/tools/built-in/index.js +9 -2
  260. package/dist/lib/tools/built-in/index.js.map +1 -1
  261. package/dist/lib/tools/built-in/integration-tools.d.ts +4 -0
  262. package/dist/lib/tools/built-in/integration-tools.d.ts.map +1 -0
  263. package/dist/lib/tools/built-in/integration-tools.js +47 -0
  264. package/dist/lib/tools/built-in/integration-tools.js.map +1 -0
  265. package/dist/lib/tools/built-in/knowledge-entity-lookup.tool.d.ts +9 -0
  266. package/dist/lib/tools/built-in/knowledge-entity-lookup.tool.d.ts.map +1 -0
  267. package/dist/lib/tools/built-in/knowledge-entity-lookup.tool.js +108 -0
  268. package/dist/lib/tools/built-in/knowledge-entity-lookup.tool.js.map +1 -0
  269. package/dist/lib/tools/built-in/knowledge-graph-schema.tool.d.ts +9 -0
  270. package/dist/lib/tools/built-in/knowledge-graph-schema.tool.d.ts.map +1 -0
  271. package/dist/lib/tools/built-in/knowledge-graph-schema.tool.js +96 -0
  272. package/dist/lib/tools/built-in/knowledge-graph-schema.tool.js.map +1 -0
  273. package/dist/lib/tools/built-in/knowledge-search.tool.d.ts +2 -2
  274. package/dist/lib/tools/built-in/knowledge-search.tool.d.ts.map +1 -1
  275. package/dist/lib/tools/built-in/knowledge-search.tool.js +1 -1
  276. package/dist/lib/tools/built-in/knowledge-search.tool.js.map +1 -1
  277. package/dist/lib/tools/built-in/knowledge-sql.tool.d.ts +7 -0
  278. package/dist/lib/tools/built-in/knowledge-sql.tool.d.ts.map +1 -0
  279. package/dist/lib/tools/built-in/knowledge-sql.tool.js +128 -0
  280. package/dist/lib/tools/built-in/knowledge-sql.tool.js.map +1 -0
  281. package/dist/lib/tools/built-in/knowledge-tools-factory.d.ts +17 -0
  282. package/dist/lib/tools/built-in/knowledge-tools-factory.d.ts.map +1 -0
  283. package/dist/lib/tools/built-in/knowledge-tools-factory.js +54 -0
  284. package/dist/lib/tools/built-in/knowledge-tools-factory.js.map +1 -0
  285. package/dist/lib/tools/built-in/knowledge-traverse.tool.d.ts +9 -0
  286. package/dist/lib/tools/built-in/knowledge-traverse.tool.d.ts.map +1 -0
  287. package/dist/lib/tools/built-in/knowledge-traverse.tool.js +91 -0
  288. package/dist/lib/tools/built-in/knowledge-traverse.tool.js.map +1 -0
  289. package/dist/lib/tools/built-in/memory-save.tool.d.ts +4 -0
  290. package/dist/lib/tools/built-in/memory-save.tool.d.ts.map +1 -0
  291. package/dist/lib/tools/built-in/memory-save.tool.js +21 -0
  292. package/dist/lib/tools/built-in/memory-save.tool.js.map +1 -0
  293. package/dist/lib/tools/built-in/query-validators.d.ts +18 -0
  294. package/dist/lib/tools/built-in/query-validators.d.ts.map +1 -0
  295. package/dist/lib/tools/built-in/query-validators.js +91 -0
  296. package/dist/lib/tools/built-in/query-validators.js.map +1 -0
  297. package/dist/lib/tools/index.d.ts +4 -4
  298. package/dist/lib/tools/index.js +4 -4
  299. package/dist/lib/tools/tool-discovery.d.ts +11 -33
  300. package/dist/lib/tools/tool-discovery.d.ts.map +1 -1
  301. package/dist/lib/tools/tool-discovery.js +16 -43
  302. package/dist/lib/tools/tool-discovery.js.map +1 -1
  303. package/dist/lib/tools/tool-registry.d.ts +10 -18
  304. package/dist/lib/tools/tool-registry.d.ts.map +1 -1
  305. package/dist/lib/tools/tool-registry.js +37 -23
  306. package/dist/lib/tools/tool-registry.js.map +1 -1
  307. package/dist/lib/tools/workspace/workspace-tools.d.ts +61 -0
  308. package/dist/lib/tools/workspace/workspace-tools.d.ts.map +1 -0
  309. package/dist/lib/tools/workspace/workspace-tools.js +179 -0
  310. package/dist/lib/tools/workspace/workspace-tools.js.map +1 -0
  311. package/dist/lib/triggers/cron-trigger.d.ts +12 -0
  312. package/dist/lib/triggers/cron-trigger.d.ts.map +1 -0
  313. package/dist/lib/triggers/cron-trigger.js +45 -0
  314. package/dist/lib/triggers/cron-trigger.js.map +1 -0
  315. package/dist/lib/triggers/index.d.ts +6 -0
  316. package/dist/lib/triggers/index.d.ts.map +1 -0
  317. package/dist/lib/triggers/index.js +5 -0
  318. package/dist/lib/triggers/index.js.map +1 -0
  319. package/dist/lib/triggers/trigger-manager.d.ts +12 -0
  320. package/dist/lib/triggers/trigger-manager.d.ts.map +1 -0
  321. package/dist/lib/triggers/trigger-manager.js +77 -0
  322. package/dist/lib/triggers/trigger-manager.js.map +1 -0
  323. package/dist/lib/triggers/types.d.ts +57 -0
  324. package/dist/lib/triggers/types.d.ts.map +1 -0
  325. package/dist/lib/triggers/types.js +16 -0
  326. package/dist/lib/triggers/types.js.map +1 -0
  327. package/dist/lib/triggers/webhook-trigger.d.ts +12 -0
  328. package/dist/lib/triggers/webhook-trigger.d.ts.map +1 -0
  329. package/dist/lib/triggers/webhook-trigger.js +34 -0
  330. package/dist/lib/triggers/webhook-trigger.js.map +1 -0
  331. package/dist/lib/types/llm-types.d.ts +72 -0
  332. package/dist/lib/types/llm-types.d.ts.map +1 -0
  333. package/dist/lib/types/llm-types.js +72 -0
  334. package/dist/lib/types/llm-types.js.map +1 -0
  335. package/dist/lib/types/text-splitters.d.ts +27 -0
  336. package/dist/lib/types/text-splitters.d.ts.map +1 -0
  337. package/dist/lib/types/text-splitters.js +114 -0
  338. package/dist/lib/types/text-splitters.js.map +1 -0
  339. package/dist/lib/types/tool-factory.d.ts +9 -0
  340. package/dist/lib/types/tool-factory.d.ts.map +1 -0
  341. package/dist/lib/types/tool-factory.js +12 -0
  342. package/dist/lib/types/tool-factory.js.map +1 -0
  343. package/dist/lib/utils/env-substitution.d.ts +6 -0
  344. package/dist/lib/utils/env-substitution.d.ts.map +1 -0
  345. package/dist/lib/utils/env-substitution.js +15 -0
  346. package/dist/lib/utils/env-substitution.js.map +1 -0
  347. package/dist/lib/utils/file-utils.d.ts +11 -0
  348. package/dist/lib/utils/file-utils.d.ts.map +1 -0
  349. package/dist/lib/utils/file-utils.js +86 -0
  350. package/dist/lib/utils/file-utils.js.map +1 -0
  351. package/dist/lib/workflows/index.d.ts +6 -6
  352. package/dist/lib/workflows/index.d.ts.map +1 -1
  353. package/dist/lib/workflows/index.js +5 -5
  354. package/dist/lib/workflows/index.js.map +1 -1
  355. package/dist/lib/workflows/interrupt-manager.d.ts +1 -1
  356. package/dist/lib/workflows/interrupt-manager.js +1 -1
  357. package/dist/lib/workflows/react-workflow-executor.d.ts +26 -0
  358. package/dist/lib/workflows/react-workflow-executor.d.ts.map +1 -0
  359. package/dist/lib/workflows/react-workflow-executor.js +333 -0
  360. package/dist/lib/workflows/react-workflow-executor.js.map +1 -0
  361. package/dist/lib/workflows/types.d.ts +106 -107
  362. package/dist/lib/workflows/types.d.ts.map +1 -1
  363. package/dist/lib/workflows/types.js +5 -5
  364. package/dist/lib/workflows/types.js.map +1 -1
  365. package/dist/lib/workflows/workflow-executor.d.ts +3 -3
  366. package/dist/lib/workflows/workflow-executor.js +2 -2
  367. package/dist/lib/workflows/workflow-executor.js.map +1 -1
  368. package/dist/lib/workflows/workflow-loader.d.ts +4 -1
  369. package/dist/lib/workflows/workflow-loader.d.ts.map +1 -1
  370. package/dist/lib/workflows/workflow-loader.js +20 -4
  371. package/dist/lib/workflows/workflow-loader.js.map +1 -1
  372. package/dist/public/chat.html +114 -0
  373. package/dist/public/index.html +189 -0
  374. package/dist/public/src/components/AgentComposer.js +807 -0
  375. package/dist/public/src/components/AgentsView.js +1021 -344
  376. package/dist/public/src/components/AppRoot.js +150 -5
  377. package/dist/public/src/components/GraphView.js +420 -0
  378. package/dist/public/src/components/IdeView.js +625 -14
  379. package/dist/public/src/components/KnowledgeView.js +443 -66
  380. package/dist/public/src/components/MonitorView.js +526 -0
  381. package/dist/public/src/components/NavBar.js +3 -0
  382. package/dist/public/src/components/SkillsView.js +137 -0
  383. package/dist/public/src/components/StandaloneChat.js +889 -0
  384. package/dist/public/src/components/WorkflowsView.js +454 -129
  385. package/dist/public/src/services/ApiService.js +170 -25
  386. package/dist/public/src/services/SessionStore.js +83 -0
  387. package/dist/public/src/store.js +3 -4
  388. package/dist/public/src/utils/markdown.js +14 -0
  389. package/dist/src/cli/commands/init.js +2 -2
  390. package/dist/src/cli/commands/init.js.map +1 -1
  391. package/dist/src/cli/commands/start.d.ts +0 -1
  392. package/dist/src/cli/commands/start.d.ts.map +1 -1
  393. package/dist/src/cli/commands/start.js +23 -14
  394. package/dist/src/cli/commands/start.js.map +1 -1
  395. package/dist/src/cli/index.js +8 -5
  396. package/dist/src/cli/index.js.map +1 -1
  397. package/dist/src/index.d.ts +1 -1
  398. package/dist/src/index.d.ts.map +1 -1
  399. package/dist/src/index.js +23 -9
  400. package/dist/src/index.js.map +1 -1
  401. package/dist/src/middleware/auth.d.ts +3 -0
  402. package/dist/src/middleware/auth.d.ts.map +1 -0
  403. package/dist/src/middleware/auth.js +129 -0
  404. package/dist/src/middleware/auth.js.map +1 -0
  405. package/dist/src/middleware/rate-limit.d.ts +8 -0
  406. package/dist/src/middleware/rate-limit.d.ts.map +1 -0
  407. package/dist/src/middleware/rate-limit.js +21 -0
  408. package/dist/src/middleware/rate-limit.js.map +1 -0
  409. package/dist/src/routes/agents.route.d.ts.map +1 -1
  410. package/dist/src/routes/agents.route.js +167 -12
  411. package/dist/src/routes/agents.route.js.map +1 -1
  412. package/dist/src/routes/chat.route.d.ts +3 -0
  413. package/dist/src/routes/chat.route.d.ts.map +1 -0
  414. package/dist/src/routes/chat.route.js +155 -0
  415. package/dist/src/routes/chat.route.js.map +1 -0
  416. package/dist/src/routes/files.route.d.ts.map +1 -1
  417. package/dist/src/routes/files.route.js +163 -87
  418. package/dist/src/routes/files.route.js.map +1 -1
  419. package/dist/src/routes/functions.route.js +1 -1
  420. package/dist/src/routes/graph.route.d.ts +3 -0
  421. package/dist/src/routes/graph.route.d.ts.map +1 -0
  422. package/dist/src/routes/graph.route.js +173 -0
  423. package/dist/src/routes/graph.route.js.map +1 -0
  424. package/dist/src/routes/knowledge.route.d.ts.map +1 -1
  425. package/dist/src/routes/knowledge.route.js +172 -69
  426. package/dist/src/routes/knowledge.route.js.map +1 -1
  427. package/dist/src/routes/llm.route.d.ts.map +1 -1
  428. package/dist/src/routes/llm.route.js +85 -9
  429. package/dist/src/routes/llm.route.js.map +1 -1
  430. package/dist/src/routes/mcp.route.js +1 -1
  431. package/dist/src/routes/skills.route.d.ts +3 -0
  432. package/dist/src/routes/skills.route.d.ts.map +1 -0
  433. package/dist/src/routes/skills.route.js +23 -0
  434. package/dist/src/routes/skills.route.js.map +1 -0
  435. package/dist/src/routes/tasks.route.d.ts +3 -0
  436. package/dist/src/routes/tasks.route.d.ts.map +1 -0
  437. package/dist/src/routes/tasks.route.js +149 -0
  438. package/dist/src/routes/tasks.route.js.map +1 -0
  439. package/dist/src/routes/vnc.route.d.ts +3 -0
  440. package/dist/src/routes/vnc.route.d.ts.map +1 -0
  441. package/dist/src/routes/vnc.route.js +49 -0
  442. package/dist/src/routes/vnc.route.js.map +1 -0
  443. package/dist/src/routes/workflows.route.d.ts.map +1 -1
  444. package/dist/src/routes/workflows.route.js +9 -1
  445. package/dist/src/routes/workflows.route.js.map +1 -1
  446. package/dist/src/server.d.ts +1 -1
  447. package/dist/src/server.d.ts.map +1 -1
  448. package/dist/src/server.js +26 -9
  449. package/dist/src/server.js.map +1 -1
  450. package/dist/templates/.env.example +4 -19
  451. package/dist/templates/Demo.md +152 -0
  452. package/dist/templates/README.md +28 -11
  453. package/dist/templates/agents/architect.agent.yaml +57 -0
  454. package/dist/templates/agents/chatbot.agent.yaml +55 -0
  455. package/dist/templates/agents/corporate.agent.yaml +65 -0
  456. package/dist/templates/agents/investment-analyst.agent.yaml +80 -0
  457. package/dist/templates/agents/music-librarian.agent.yaml +70 -0
  458. package/dist/templates/agents/network-security.agent.yaml +82 -0
  459. package/dist/templates/agents/transport-security.agent.yaml +70 -0
  460. package/dist/templates/agents/web-engineer.agent.yaml +99 -0
  461. package/dist/templates/agents/web-pilot.agent.yaml +58 -0
  462. package/dist/templates/knowledge/music-store/LICENSE.md +11 -0
  463. package/dist/templates/knowledge/music-store/musicstore.sqlite +0 -0
  464. package/dist/templates/knowledge/music-store/tables.png +0 -0
  465. package/dist/templates/knowledge/music-store.knowledge.yaml +138 -0
  466. package/dist/templates/knowledge/org-chart/personnel.csv +21 -0
  467. package/dist/templates/knowledge/org-chart.knowledge.yaml +53 -0
  468. package/dist/templates/knowledge/pet-store/pet-store.db +0 -0
  469. package/dist/templates/knowledge/pet-store.knowledge.yaml +81 -0
  470. package/dist/templates/knowledge/security-incidents/incidents.json +55935 -0
  471. package/dist/templates/knowledge/security-incidents.knowledge.yaml +46 -0
  472. package/dist/templates/knowledge/transcripts.knowledge.yaml +29 -0
  473. package/dist/templates/knowledge/transport-ot/systems.csv +117 -0
  474. package/dist/templates/knowledge/transport-ot.knowledge.yaml +55 -0
  475. package/dist/templates/knowledge/web-docs.knowledge.yaml +20 -0
  476. package/dist/templates/llm.json +7 -30
  477. package/dist/templates/mcp.json +10 -4
  478. package/dist/templates/skills/orcha-builder/SKILL.md +219 -0
  479. package/dist/templates/skills/pii-guard/SKILL.md +22 -0
  480. package/dist/templates/skills/sandbox/SKILL.md +40 -0
  481. package/dist/templates/skills/web-pilot/SKILL.md +51 -0
  482. package/dist/templates/workflows/example.workflow.yaml +25 -30
  483. package/dist/templates/workflows/react-example.workflow.yaml +53 -0
  484. package/package.json +22 -20
  485. package/dist/lib/knowledge/graph-rag/community-detector.d.ts +0 -16
  486. package/dist/lib/knowledge/graph-rag/community-detector.d.ts.map +0 -1
  487. package/dist/lib/knowledge/graph-rag/community-detector.js +0 -81
  488. package/dist/lib/knowledge/graph-rag/community-detector.js.map +0 -1
  489. package/dist/lib/knowledge/graph-rag/community-summarizer.d.ts +0 -17
  490. package/dist/lib/knowledge/graph-rag/community-summarizer.d.ts.map +0 -1
  491. package/dist/lib/knowledge/graph-rag/community-summarizer.js +0 -87
  492. package/dist/lib/knowledge/graph-rag/community-summarizer.js.map +0 -1
  493. package/dist/lib/knowledge/graph-rag/entity-extractor.d.ts +0 -36
  494. package/dist/lib/knowledge/graph-rag/entity-extractor.d.ts.map +0 -1
  495. package/dist/lib/knowledge/graph-rag/entity-extractor.js +0 -192
  496. package/dist/lib/knowledge/graph-rag/entity-extractor.js.map +0 -1
  497. package/dist/lib/knowledge/graph-rag/extraction-cache.d.ts +0 -30
  498. package/dist/lib/knowledge/graph-rag/extraction-cache.d.ts.map +0 -1
  499. package/dist/lib/knowledge/graph-rag/extraction-cache.js +0 -88
  500. package/dist/lib/knowledge/graph-rag/extraction-cache.js.map +0 -1
  501. package/dist/lib/knowledge/graph-rag/global-search.d.ts +0 -19
  502. package/dist/lib/knowledge/graph-rag/global-search.d.ts.map +0 -1
  503. package/dist/lib/knowledge/graph-rag/global-search.js +0 -96
  504. package/dist/lib/knowledge/graph-rag/global-search.js.map +0 -1
  505. package/dist/lib/knowledge/graph-rag/graph-rag-factory.d.ts +0 -24
  506. package/dist/lib/knowledge/graph-rag/graph-rag-factory.d.ts.map +0 -1
  507. package/dist/lib/knowledge/graph-rag/graph-rag-factory.js +0 -239
  508. package/dist/lib/knowledge/graph-rag/graph-rag-factory.js.map +0 -1
  509. package/dist/lib/knowledge/graph-rag/index.d.ts +0 -14
  510. package/dist/lib/knowledge/graph-rag/index.d.ts.map +0 -1
  511. package/dist/lib/knowledge/graph-rag/index.js +0 -12
  512. package/dist/lib/knowledge/graph-rag/index.js.map +0 -1
  513. package/dist/lib/knowledge/graph-rag/local-search.d.ts +0 -20
  514. package/dist/lib/knowledge/graph-rag/local-search.d.ts.map +0 -1
  515. package/dist/lib/knowledge/graph-rag/local-search.js +0 -110
  516. package/dist/lib/knowledge/graph-rag/local-search.js.map +0 -1
  517. package/dist/lib/knowledge/graph-rag/memory-graph-store.d.ts +0 -31
  518. package/dist/lib/knowledge/graph-rag/memory-graph-store.d.ts.map +0 -1
  519. package/dist/lib/knowledge/graph-rag/memory-graph-store.js +0 -165
  520. package/dist/lib/knowledge/graph-rag/memory-graph-store.js.map +0 -1
  521. package/dist/lib/knowledge/graph-rag/neo4j-graph-store.d.ts +0 -38
  522. package/dist/lib/knowledge/graph-rag/neo4j-graph-store.d.ts.map +0 -1
  523. package/dist/lib/knowledge/graph-rag/neo4j-graph-store.js +0 -190
  524. package/dist/lib/knowledge/graph-rag/neo4j-graph-store.js.map +0 -1
  525. package/dist/lib/knowledge/graph-rag/search-mode-detector.d.ts +0 -11
  526. package/dist/lib/knowledge/graph-rag/search-mode-detector.d.ts.map +0 -1
  527. package/dist/lib/knowledge/graph-rag/search-mode-detector.js +0 -50
  528. package/dist/lib/knowledge/graph-rag/search-mode-detector.js.map +0 -1
  529. package/dist/lib/knowledge/graph-rag/types.d.ts +0 -368
  530. package/dist/lib/knowledge/graph-rag/types.d.ts.map +0 -1
  531. package/dist/lib/knowledge/graph-rag/types.js +0 -48
  532. package/dist/lib/knowledge/graph-rag/types.js.map +0 -1
  533. package/dist/lib/knowledge/knowledge-store-factory.d.ts +0 -16
  534. package/dist/lib/knowledge/knowledge-store-factory.d.ts.map +0 -1
  535. package/dist/lib/knowledge/knowledge-store-factory.js +0 -376
  536. package/dist/lib/knowledge/knowledge-store-factory.js.map +0 -1
  537. package/dist/lib/knowledge/knowledge-store-manager.d.ts +0 -18
  538. package/dist/lib/knowledge/knowledge-store-manager.d.ts.map +0 -1
  539. package/dist/lib/knowledge/knowledge-store-manager.js +0 -98
  540. package/dist/lib/knowledge/knowledge-store-manager.js.map +0 -1
  541. package/dist/lib/knowledge/loaders/s3-loader.d.ts +0 -17
  542. package/dist/lib/knowledge/loaders/s3-loader.d.ts.map +0 -1
  543. package/dist/lib/knowledge/loaders/s3-loader.js +0 -185
  544. package/dist/lib/knowledge/loaders/s3-loader.js.map +0 -1
  545. package/dist/lib/workflows/langgraph-executor.d.ts +0 -51
  546. package/dist/lib/workflows/langgraph-executor.d.ts.map +0 -1
  547. package/dist/lib/workflows/langgraph-executor.js +0 -297
  548. package/dist/lib/workflows/langgraph-executor.js.map +0 -1
  549. package/dist/templates/agents/call-center-analyst-simple.agent.yaml +0 -36
  550. package/dist/templates/agents/math.agent.yaml +0 -28
  551. package/dist/templates/agents/sentiment-structured.agent.yaml +0 -42
  552. package/dist/templates/knowledge/example.knowledge.yaml +0 -28
  553. package/dist/templates/llm.md +0 -1195
  554. package/dist/templates/workflows/langgraph-example.workflow.yaml +0 -84
@@ -1,6 +1,7 @@
1
1
 
2
2
  import { Component } from '../utils/Component.js';
3
3
  import { api } from '../services/ApiService.js';
4
+ import { sessionStore } from '../services/SessionStore.js';
4
5
  import { store } from '../store.js';
5
6
  import { markdownRenderer } from '../utils/markdown.js';
6
7
 
@@ -8,25 +9,118 @@ export class AgentsView extends Component {
8
9
  constructor() {
9
10
  super();
10
11
  this.isLoading = false;
12
+ this.currentAbortController = null;
13
+ this.streamStartTime = null;
14
+ this.streamTimerInterval = null;
15
+ this.streamUsageData = null;
16
+ this.pendingAttachments = [];
11
17
  }
12
18
 
13
19
  async connectedCallback() {
14
20
  super.connectedCallback();
15
21
  await Promise.all([this.loadAgents(), this.loadLLMs()]);
22
+ this.restoreActiveSession();
23
+ }
24
+
25
+ disconnectedCallback() {
26
+ this.stopStreamTimer();
27
+ if (this.currentAbortController) {
28
+ this.currentAbortController.abort();
29
+ this.currentAbortController = null;
30
+ }
31
+ }
32
+
33
+ formatElapsedTime(ms) {
34
+ if (ms < 1000) return `${ms}ms`;
35
+ const seconds = ms / 1000;
36
+ if (seconds < 60) return `${seconds.toFixed(1)}s`;
37
+ const minutes = Math.floor(seconds / 60);
38
+ const remainingSeconds = (seconds % 60).toFixed(0);
39
+ return `${minutes}m ${remainingSeconds}s`;
40
+ }
41
+
42
+ estimateTokens(text) {
43
+ return Math.round((text || '').length / 4);
44
+ }
45
+
46
+ cancelCurrentStream() {
47
+ if (this.currentAbortController) {
48
+ this.currentAbortController.abort();
49
+ }
50
+ }
51
+
52
+ startStreamTimer(responseId) {
53
+ this.streamStartTime = Date.now();
54
+ this.streamTimerInterval = setInterval(() => {
55
+ const elapsed = Date.now() - this.streamStartTime;
56
+ const bubble = this.querySelector(`#${responseId}`);
57
+ if (!bubble) return;
58
+ const timerEl = bubble.parentElement.querySelector('.stream-elapsed');
59
+ if (timerEl) {
60
+ timerEl.textContent = this.formatElapsedTime(elapsed);
61
+ }
62
+ }, 100);
63
+ }
64
+
65
+ stopStreamTimer(responseId, inputMessage, finalContent, wasCancelled) {
66
+ if (this.streamTimerInterval) {
67
+ clearInterval(this.streamTimerInterval);
68
+ this.streamTimerInterval = null;
69
+ }
70
+
71
+ const elapsed = this.streamStartTime ? Date.now() - this.streamStartTime : 0;
72
+ this.streamStartTime = null;
73
+
74
+ const bubble = this.querySelector(`#${responseId}`);
75
+ if (!bubble) return;
76
+
77
+ const wrapper = bubble.parentElement;
78
+ const statusBar = wrapper.querySelector('.stream-status-bar');
79
+ const statsBar = wrapper.querySelector('.stream-stats-bar');
80
+
81
+ if (statusBar) statusBar.classList.add('hidden');
82
+
83
+ if (statsBar) {
84
+ const elapsedEl = statsBar.querySelector('.stats-elapsed');
85
+ const inputTokensEl = statsBar.querySelector('.stats-input-tokens');
86
+ const outputTokensEl = statsBar.querySelector('.stats-output-tokens');
87
+ const tpsEl = statsBar.querySelector('.stats-tps');
88
+
89
+ const usage = this.streamUsageData;
90
+ const hasRealUsage = usage && (usage.input_tokens > 0 || usage.output_tokens > 0);
91
+
92
+ const inputTokens = hasRealUsage ? usage.input_tokens : this.estimateTokens(inputMessage);
93
+ const outputTokens = hasRealUsage ? usage.output_tokens : this.estimateTokens(finalContent);
94
+ const prefix = hasRealUsage ? '' : '~';
95
+
96
+ if (elapsedEl) elapsedEl.textContent = this.formatElapsedTime(elapsed);
97
+ if (inputTokensEl) inputTokensEl.textContent = `${prefix}${inputTokens} input`;
98
+ if (outputTokensEl) outputTokensEl.textContent = `${prefix}${outputTokens} output`;
99
+
100
+ if (tpsEl) {
101
+ const seconds = elapsed / 1000;
102
+ const tps = seconds > 0 ? (outputTokens / seconds).toFixed(1) : 0;
103
+ tpsEl.textContent = `${prefix}${tps} tok/s`;
104
+ }
105
+
106
+ this.streamUsageData = null;
107
+
108
+ if (wasCancelled) {
109
+ const cancelBadge = document.createElement('span');
110
+ cancelBadge.className = 'text-xs text-amber-400 font-medium ml-2';
111
+ cancelBadge.textContent = 'Cancelled';
112
+ statsBar.appendChild(cancelBadge);
113
+ }
114
+
115
+ statsBar.classList.remove('hidden');
116
+ }
16
117
  }
17
118
 
18
119
  async loadAgents() {
19
120
  try {
20
121
  const agents = await api.getAgents();
122
+ agents.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
21
123
  store.set('agents', agents);
22
-
23
- if (agents.length > 0 && !store.get('selectedAgent') && !store.get('selectedLlm')) {
24
- store.set('selectedAgent', agents[agents.length - 1]);
25
- store.set('selectionType', 'agent');
26
- }
27
-
28
- this.renderAgentDropdown();
29
- this.updateSelectedAgentUI();
30
124
  } catch (e) {
31
125
  console.error('Failed to load agents', e);
32
126
  }
@@ -36,152 +130,491 @@ export class AgentsView extends Component {
36
130
  try {
37
131
  const llms = await api.getLLMs();
38
132
  store.set('llms', llms);
39
- this.renderAgentDropdown();
40
133
  } catch (e) {
41
134
  console.error('Failed to load LLMs', e);
42
135
  }
43
136
  }
44
137
 
45
- renderAgentDropdown() {
46
- const list = this.querySelector('#agentDropdownList');
138
+ // --- Sidebar toggle (mobile) ---
139
+
140
+ _isMobile() {
141
+ return !window.matchMedia('(min-width: 768px)').matches;
142
+ }
143
+
144
+ toggleSidebar(show) {
145
+ const sidebar = this.querySelector('#sidebar');
146
+ const backdrop = this.querySelector('#sidebarBackdrop');
147
+ if (!sidebar || !backdrop) return;
148
+
149
+ if (show) {
150
+ sidebar.classList.remove('hidden');
151
+ sidebar.classList.add('flex', 'sidebar-open');
152
+ backdrop.classList.remove('hidden');
153
+ } else {
154
+ sidebar.classList.add('hidden');
155
+ sidebar.classList.remove('flex', 'sidebar-open');
156
+ backdrop.classList.add('hidden');
157
+ }
158
+ }
159
+
160
+ // --- Session management ---
161
+
162
+ restoreActiveSession() {
163
+ const activeId = sessionStore.getActiveId();
164
+ if (activeId && sessionStore.get(activeId)) {
165
+ this.switchToSession(activeId);
166
+ } else {
167
+ this.showEmptyState();
168
+ }
169
+ this.renderSessionList();
170
+ }
171
+
172
+ renderSessionList() {
173
+ const list = this.querySelector('#sessionList');
174
+ if (!list) return;
175
+
176
+ const sessions = sessionStore.getAll();
177
+ const activeId = sessionStore.getActiveId();
178
+
179
+ if (sessions.length === 0) {
180
+ list.innerHTML = '<div class="text-gray-500 text-sm text-center py-8">No conversations yet</div>';
181
+ return;
182
+ }
183
+
184
+ list.innerHTML = sessions.map(s => {
185
+ const isActive = s.id === activeId;
186
+ const isAgent = s.agentType === 'agent';
187
+ const displayName = isAgent ? (s.agentName || 'Agent') : (s.llmName || 'LLM');
188
+ const icon = isAgent ? 'fa-robot' : 'fa-microchip';
189
+
190
+ const activeClasses = isActive
191
+ ? 'bg-dark-hover/80 border-l-2 border-l-blue-500'
192
+ : 'hover:bg-dark-hover/40 border-l-2 border-l-transparent';
193
+
194
+ return `
195
+ <div data-session-id="${s.id}" class="session-item group flex items-start gap-2 px-3 py-2.5 cursor-pointer rounded-lg mb-0.5 transition-colors ${activeClasses}">
196
+ <div class="flex-1 min-w-0">
197
+ <div class="text-sm ${isActive ? 'text-gray-100' : 'text-gray-300'} truncate">${this.escapeHtml(s.title)}</div>
198
+ <div class="flex items-center gap-1.5 mt-0.5 text-xs text-gray-500">
199
+ <i class="fas ${icon} text-[10px]"></i>
200
+ <span class="truncate">${this.escapeHtml(displayName)}</span>
201
+ </div>
202
+ </div>
203
+ <button data-delete-id="${s.id}" class="session-delete-btn flex-shrink-0 text-gray-600 hover:text-red-400 opacity-0 group-hover:opacity-100 transition-opacity p-1 mt-0.5" title="Delete">
204
+ <i class="fas fa-xmark text-xs"></i>
205
+ </button>
206
+ </div>
207
+ `;
208
+ }).join('');
209
+
210
+ // Event listeners
211
+ list.querySelectorAll('.session-item').forEach(item => {
212
+ item.addEventListener('click', (e) => {
213
+ if (e.target.closest('.session-delete-btn')) return;
214
+ this.switchToSession(item.dataset.sessionId);
215
+ });
216
+ });
217
+
218
+ list.querySelectorAll('.session-delete-btn').forEach(btn => {
219
+ btn.addEventListener('click', (e) => {
220
+ e.stopPropagation();
221
+ this.deleteSession(btn.dataset.deleteId);
222
+ });
223
+ });
224
+ }
225
+
226
+ switchToSession(sessionId) {
227
+ // Abort any running stream
228
+ if (this.currentAbortController) {
229
+ this.currentAbortController.abort();
230
+ this.currentAbortController = null;
231
+ }
232
+ this.isLoading = false;
233
+
234
+ const session = sessionStore.get(sessionId);
235
+ if (!session) return;
236
+
237
+ sessionStore.setActiveId(sessionId);
238
+
239
+ // Update store with this session's agent/LLM
47
240
  const agents = store.get('agents') || [];
48
241
  const llms = store.get('llms') || [];
49
- const selectionType = store.get('selectionType');
50
- const selectedAgent = store.get('selectedAgent');
51
- const selectedLlm = store.get('selectedLlm');
52
242
 
53
- if (!list) return;
243
+ if (session.agentType === 'agent') {
244
+ const agent = agents.find(a => a.name === session.agentName);
245
+ store.set('selectedAgent', agent || null);
246
+ store.set('selectedLlm', null);
247
+ store.set('selectionType', 'agent');
248
+ } else {
249
+ const llm = llms.find(l => l.name === session.llmName);
250
+ store.set('selectedLlm', llm || null);
251
+ store.set('selectedAgent', null);
252
+ store.set('selectionType', 'llm');
253
+ }
254
+
255
+ this.restoreMessages(session);
256
+ this.updateChatHeader(session);
257
+ this.renderSessionList();
258
+ this.updateUiState();
259
+
260
+ // Close sidebar on mobile after selecting
261
+ if (this._isMobile()) {
262
+ this.toggleSidebar(false);
263
+ }
264
+
265
+ const input = this.querySelector('#chatInput');
266
+ if (input) {
267
+ input.disabled = false;
268
+ input.readOnly = false;
269
+ input.classList.remove('cursor-pointer');
270
+ input.focus();
271
+ }
272
+ }
273
+
274
+ async restoreMessages(session) {
275
+ const container = this.querySelector('#chatMessages');
276
+ if (!container) return;
277
+ container.innerHTML = '';
54
278
 
55
- if (agents.length === 0 && llms.length === 0) {
56
- list.innerHTML = '<div class="text-gray-500 text-sm text-center py-4">No agents or LLMs available</div>';
279
+ if (session.messages.length === 0) {
280
+ this._appendWelcomeMessage(container);
57
281
  return;
58
282
  }
59
283
 
60
- let html = '';
284
+ for (const msg of session.messages) {
285
+ if (msg.role === 'user') {
286
+ this.appendMessage('user', msg.content);
287
+ } else {
288
+ this.appendRestoredAssistantMessage(msg.content);
289
+ }
290
+ }
291
+
292
+ // Check if server still has this session (survives restarts)
293
+ try {
294
+ const exists = await api.checkSession(session.id);
295
+ if (!exists) {
296
+ this._appendSessionResetBanner(container);
297
+ }
298
+ } catch {
299
+ // Server unreachable — skip banner
300
+ }
301
+ }
302
+
303
+ appendRestoredAssistantMessage(content) {
304
+ const container = this.querySelector('#chatMessages');
305
+ const div = document.createElement('div');
306
+ div.className = 'response-wrapper';
307
+
308
+ const bubble = document.createElement('div');
309
+ bubble.className = 'flex justify-start';
310
+ bubble.innerHTML = `
311
+ <div class="response-bubble-inner max-w-4xl bg-dark-surface border border-dark-border rounded-3xl px-5 py-3 text-gray-100 text-[15px] leading-relaxed relative group">
312
+ <div class="response-content markdown-content"></div>
313
+ </div>
314
+ `;
315
+
316
+ const contentDiv = bubble.querySelector('.response-content');
317
+ const rendered = markdownRenderer.render(content);
318
+ contentDiv.innerHTML = rendered;
319
+ markdownRenderer.highlightCode(contentDiv);
320
+
321
+ div.appendChild(bubble);
322
+ container.appendChild(div);
323
+ container.scrollTop = container.scrollHeight;
324
+ }
325
+
326
+ showNewSessionModal() {
327
+ // Remove existing modal if any
328
+ const existing = document.querySelector('#newSessionModal');
329
+ if (existing) existing.remove();
330
+
331
+ const agents = store.get('agents') || [];
332
+ const llms = store.get('llms') || [];
333
+
334
+ const overlay = document.createElement('div');
335
+ overlay.id = 'newSessionModal';
336
+ overlay.className = 'modal-backdrop fixed inset-0 z-50 flex items-center justify-center bg-black/70';
337
+
338
+ let itemsHtml = '';
61
339
 
62
- // Agents section
63
340
  if (agents.length > 0) {
64
- html += '<div class="px-4 py-2 text-xs font-semibold text-gray-400 uppercase tracking-wider bg-dark-bg/50">Agents</div>';
65
- html += agents.map(agent => {
66
- const isSelected = selectionType === 'agent' && selectedAgent?.name === agent.name;
67
- return `
68
- <div data-type="agent" data-name="${agent.name}" class="selection-item px-4 py-3 hover:bg-dark-hover cursor-pointer transition-colors border-b border-dark-border ${isSelected ? 'bg-dark-hover' : ''}">
69
- <div class="flex items-start justify-between">
70
- <div class="flex-1">
71
- <div class="font-medium text-gray-200 mb-0.5">${agent.name}</div>
72
- <div class="text-xs text-gray-500 line-clamp-2">${agent.description}</div>
73
- </div>
74
- ${isSelected ? '<i class="fas fa-check text-blue-400 ml-2 mt-1"></i>' : ''}
75
- </div>
341
+ itemsHtml += '<div class="px-4 py-2 text-xs font-semibold text-gray-400 uppercase tracking-wider">Agents</div>';
342
+ itemsHtml += agents.map(a => `
343
+ <button data-type="agent" data-name="${this.escapeHtml(a.name)}" class="modal-pick-item w-full text-left px-4 py-3 hover:bg-dark-hover cursor-pointer transition-colors border-b border-dark-border/50 flex items-center gap-3">
344
+ <i class="fas fa-robot text-blue-400 text-sm"></i>
345
+ <div class="flex-1 min-w-0">
346
+ <div class="text-sm font-medium text-gray-200">${this.escapeHtml(a.name)}</div>
347
+ <div class="text-xs text-gray-500 truncate">${this.escapeHtml(a.description || '')}</div>
76
348
  </div>
77
- `;
78
- }).join('');
349
+ </button>
350
+ `).join('');
79
351
  }
80
352
 
81
- // LLMs section
82
353
  if (llms.length > 0) {
83
- html += '<div class="px-4 py-2 text-xs font-semibold text-gray-400 uppercase tracking-wider bg-dark-bg/50">LLMs</div>';
84
- html += llms.map(llm => {
85
- const isSelected = selectionType === 'llm' && selectedLlm?.name === llm.name;
86
- return `
87
- <div data-type="llm" data-name="${llm.name}" class="selection-item px-4 py-3 hover:bg-dark-hover cursor-pointer transition-colors border-b border-dark-border last:border-b-0 ${isSelected ? 'bg-dark-hover' : ''}">
88
- <div class="flex items-start justify-between">
89
- <div class="flex-1">
90
- <div class="font-medium text-gray-200 mb-0.5">${llm.name}</div>
91
- <div class="text-xs text-gray-500 line-clamp-2">${llm.model}</div>
92
- </div>
93
- ${isSelected ? '<i class="fas fa-check text-blue-400 ml-2 mt-1"></i>' : ''}
94
- </div>
354
+ itemsHtml += '<div class="px-4 py-2 text-xs font-semibold text-gray-400 uppercase tracking-wider">LLMs</div>';
355
+ itemsHtml += llms.map(l => `
356
+ <button data-type="llm" data-name="${this.escapeHtml(l.name)}" class="modal-pick-item w-full text-left px-4 py-3 hover:bg-dark-hover cursor-pointer transition-colors border-b border-dark-border/50 flex items-center gap-3">
357
+ <i class="fas fa-microchip text-purple-400 text-sm"></i>
358
+ <div class="flex-1 min-w-0">
359
+ <div class="text-sm font-medium text-gray-200">${this.escapeHtml(l.name)}</div>
360
+ <div class="text-xs text-gray-500 truncate">${this.escapeHtml(l.model || '')}</div>
95
361
  </div>
96
- `;
97
- }).join('');
362
+ </button>
363
+ `).join('');
98
364
  }
99
365
 
100
- list.innerHTML = html;
366
+ if (!itemsHtml) {
367
+ itemsHtml = '<div class="text-gray-500 text-sm text-center py-8">No agents or LLMs available</div>';
368
+ }
101
369
 
102
- list.querySelectorAll('.selection-item').forEach(item => {
370
+ overlay.innerHTML = `
371
+ <div class="modal-content bg-dark-surface border border-dark-border rounded-2xl shadow-2xl w-[420px] max-w-[90vw] max-h-[70vh] flex flex-col overflow-hidden">
372
+ <div class="flex items-center justify-between px-5 py-4 border-b border-dark-border">
373
+ <h3 class="text-lg font-semibold text-gray-100">New conversation</h3>
374
+ <button id="closeNewSessionModal" class="text-gray-400 hover:text-gray-200 transition-colors p-1">
375
+ <i class="fas fa-xmark"></i>
376
+ </button>
377
+ </div>
378
+ <div class="overflow-y-auto custom-scrollbar flex-1">
379
+ ${itemsHtml}
380
+ </div>
381
+ </div>
382
+ `;
383
+
384
+ document.body.appendChild(overlay);
385
+
386
+ // Close on backdrop click
387
+ overlay.addEventListener('click', (e) => {
388
+ if (e.target === overlay) overlay.remove();
389
+ });
390
+
391
+ overlay.querySelector('#closeNewSessionModal').addEventListener('click', () => overlay.remove());
392
+
393
+ // Pick handler
394
+ overlay.querySelectorAll('.modal-pick-item').forEach(item => {
103
395
  item.addEventListener('click', () => {
104
396
  const type = item.dataset.type;
105
397
  const name = item.dataset.name;
106
398
 
107
- // Check if we're actually switching to a different agent/llm
108
- const currentType = store.get('selectionType');
109
- const currentAgent = store.get('selectedAgent');
110
- const currentLlm = store.get('selectedLlm');
111
- const currentName = currentType === 'agent' ? currentAgent?.name : currentLlm?.name;
112
-
113
- const isSwitching = !(type === currentType && name === currentName);
114
-
115
- if (type === 'agent') {
116
- const agent = agents.find(a => a.name === name);
117
- store.set('selectedAgent', agent);
118
- store.set('selectedLlm', null);
119
- store.set('selectionType', 'agent');
120
- } else if (type === 'llm') {
121
- const llm = llms.find(l => l.name === name);
122
- store.set('selectedLlm', llm);
123
- store.set('selectedAgent', null);
124
- store.set('selectionType', 'llm');
125
- }
126
-
127
- // Clear chat history when switching
128
- if (isSwitching) {
129
- this.clearChatHistory();
130
- }
399
+ const session = sessionStore.create({
400
+ agentName: type === 'agent' ? name : null,
401
+ agentType: type,
402
+ llmName: type === 'llm' ? name : null
403
+ });
131
404
 
132
- this.updateSelectedAgentUI();
133
- this.toggleDropdown(false);
134
- this.renderAgentDropdown(); // Re-render to update selection checkmark
405
+ overlay.remove();
406
+ this.switchToSession(session.id);
135
407
  });
136
408
  });
137
409
  }
138
410
 
139
- updateSelectedAgentUI() {
140
- const selectionType = store.get('selectionType');
141
- const agent = store.get('selectedAgent');
142
- const llm = store.get('selectedLlm');
143
- const nameEl = this.querySelector('#selectedAgentName');
411
+ deleteSession(sessionId) {
412
+ sessionStore.delete(sessionId);
413
+ const activeId = sessionStore.getActiveId();
414
+
415
+ if (!activeId || activeId === sessionId) {
416
+ // Switch to most recent remaining session
417
+ const sessions = sessionStore.getAll();
418
+ if (sessions.length > 0) {
419
+ this.switchToSession(sessions[0].id);
420
+ } else {
421
+ this.showEmptyState();
422
+ this.renderSessionList();
423
+ }
424
+ } else {
425
+ this.renderSessionList();
426
+ }
427
+ }
428
+
429
+ showEmptyState() {
430
+ const container = this.querySelector('#chatMessages');
431
+ if (container) {
432
+ container.innerHTML = `
433
+ <div class="flex-1 flex items-center justify-center h-full">
434
+ <div class="text-center text-gray-500">
435
+ <i class="fas fa-comments text-4xl mb-4 text-gray-600"></i>
436
+ <p class="text-lg">Start a new conversation</p>
437
+ <p class="text-sm mt-1">Click "New chat" to begin</p>
438
+ </div>
439
+ </div>
440
+ `;
441
+ }
442
+
443
+ this.updateChatHeader(null);
444
+
445
+ const input = this.querySelector('#chatInput');
446
+ if (input) {
447
+ input.disabled = false;
448
+ input.readOnly = true;
449
+ input.classList.add('cursor-pointer');
450
+ }
451
+
144
452
  const btn = this.querySelector('#sendMessageBtn');
453
+ if (btn) btn.disabled = true;
454
+ }
145
455
 
146
- const selected = selectionType === 'agent' ? agent : llm;
456
+ updateChatHeader(session) {
457
+ const header = this.querySelector('#chatHeader');
458
+ if (!header) return;
147
459
 
148
- if (nameEl && selected) {
149
- nameEl.textContent = selected.name;
150
- } else if (nameEl) {
151
- nameEl.textContent = 'Select Agent/LLM';
460
+ if (!session) {
461
+ header.innerHTML = '<span class="text-gray-500">No conversation selected</span>';
462
+ return;
463
+ }
464
+
465
+ const isAgent = session.agentType === 'agent';
466
+ const name = isAgent ? (session.agentName || 'Agent') : (session.llmName || 'LLM');
467
+ const badgeText = isAgent ? 'Agent' : 'LLM';
468
+ const badgeColor = isAgent ? 'bg-blue-500/20 text-blue-400' : 'bg-purple-500/20 text-purple-400';
469
+ const icon = isAgent ? 'fa-robot' : 'fa-microchip';
470
+
471
+ let extraBadges = '';
472
+ if (isAgent) {
473
+ const agents = store.get('agents') || [];
474
+ const agent = agents.find(a => a.name === session.agentName);
475
+ if (agent) {
476
+ if (agent.publish?.enabled) {
477
+ const chatUrl = `/chat/${encodeURIComponent(agent.name)}`;
478
+ extraBadges += `<a href="${chatUrl}" target="_blank" class="text-xs px-2 py-0.5 rounded-full bg-green-500/20 text-green-400 hover:bg-green-500/30 transition-colors no-underline" title="Open published chat"><i class="fas fa-globe text-[10px]"></i> Published</a>`;
479
+ }
480
+
481
+ const hasMemory = agent.memory === true || (agent.memory && agent.memory.enabled);
482
+ if (hasMemory) {
483
+ extraBadges += `<span class="text-xs px-2 py-0.5 rounded-full bg-amber-500/20 text-amber-400" title="Persistent memory enabled"><i class="fas fa-brain text-[10px]"></i> Memory</span>`;
484
+ }
485
+
486
+ if (agent.tools?.length) {
487
+ const toolNames = agent.tools.map(t => typeof t === 'string' ? t : t.name);
488
+ const toolListHtml = toolNames.map(t => `<div class="tools-popover-item">${this.escapeHtml(t)}</div>`).join('');
489
+ extraBadges += `
490
+ <span class="tools-badge-wrapper">
491
+ <span class="text-xs px-2 py-0.5 rounded-full bg-gray-500/20 text-gray-400 cursor-default"><i class="fas fa-wrench text-[10px]"></i> ${toolNames.length} tool${toolNames.length !== 1 ? 's' : ''}</span>
492
+ <div class="tools-popover">${toolListHtml}</div>
493
+ </span>`;
494
+ }
495
+ }
152
496
  }
153
497
 
154
- if (btn) btn.disabled = !selected || this.isLoading;
498
+ header.innerHTML = `
499
+ <div class="flex items-center gap-2 flex-wrap">
500
+ <i class="fas ${icon} text-sm text-gray-400"></i>
501
+ <span class="font-medium text-gray-200">${this.escapeHtml(name)}</span>
502
+ <span class="text-xs px-2 py-0.5 rounded-full ${badgeColor}">${badgeText}</span>
503
+ ${extraBadges}
504
+ </div>
505
+ `;
155
506
  }
156
507
 
157
- toggleDropdown(show) {
158
- const dropdown = this.querySelector('#agentDropdown');
159
- if (dropdown) {
160
- if (show === undefined) {
161
- dropdown.classList.toggle('hidden');
162
- } else if (show) {
163
- dropdown.classList.remove('hidden');
508
+ // --- File Attachments ---
509
+
510
+ handleFileSelect(e) {
511
+ const files = Array.from(e.target.files);
512
+ e.target.value = '';
513
+
514
+ const needsConversion = ['image/webp', 'image/bmp', 'image/tiff'];
515
+
516
+ for (const file of files) {
517
+ if (needsConversion.includes(file.type)) {
518
+ this.convertImageToJpeg(file);
164
519
  } else {
165
- dropdown.classList.add('hidden');
520
+ const reader = new FileReader();
521
+ reader.onload = () => {
522
+ const dataUrl = reader.result;
523
+ const commaIdx = dataUrl.indexOf(',');
524
+ const base64 = dataUrl.slice(commaIdx + 1);
525
+ const mediaType = file.type || 'application/octet-stream';
526
+
527
+ this.pendingAttachments.push({ data: base64, mediaType, name: file.name });
528
+ this.renderAttachmentPreview();
529
+ };
530
+ reader.readAsDataURL(file);
166
531
  }
167
532
  }
168
533
  }
169
534
 
535
+ convertImageToJpeg(file) {
536
+ const img = new Image();
537
+ const url = URL.createObjectURL(file);
538
+ img.onload = () => {
539
+ const canvas = document.createElement('canvas');
540
+ canvas.width = img.naturalWidth;
541
+ canvas.height = img.naturalHeight;
542
+ canvas.getContext('2d').drawImage(img, 0, 0);
543
+ URL.revokeObjectURL(url);
544
+
545
+ const dataUrl = canvas.toDataURL('image/jpeg', 0.92);
546
+ const base64 = dataUrl.split(',')[1];
547
+ this.pendingAttachments.push({ data: base64, mediaType: 'image/jpeg', name: file.name });
548
+ this.renderAttachmentPreview();
549
+ };
550
+ img.src = url;
551
+ }
552
+
553
+ renderAttachmentPreview() {
554
+ const preview = this.querySelector('#attachmentPreview');
555
+ if (!preview) return;
556
+
557
+ if (this.pendingAttachments.length === 0) {
558
+ preview.classList.add('hidden');
559
+ preview.innerHTML = '';
560
+ return;
561
+ }
562
+
563
+ preview.classList.remove('hidden');
564
+ preview.innerHTML = this.pendingAttachments.map((att, i) => {
565
+ const isImage = att.mediaType.startsWith('image/');
566
+ const thumb = isImage
567
+ ? `<img src="data:${att.mediaType};base64,${att.data}" class="w-10 h-10 object-cover rounded">`
568
+ : `<i class="fas fa-file text-gray-400 text-lg"></i>`;
569
+ return `
570
+ <div class="attachment-pill flex items-center gap-2 bg-dark-bg/60 border border-dark-border/50 rounded-lg px-2 py-1.5 text-xs text-gray-400">
571
+ ${thumb}
572
+ <span class="max-w-[120px] truncate">${this.escapeHtml(att.name)}</span>
573
+ <button class="attachment-remove text-gray-500 hover:text-gray-300 ml-1" data-index="${i}">
574
+ <i class="fas fa-xmark text-xs"></i>
575
+ </button>
576
+ </div>
577
+ `;
578
+ }).join('');
579
+
580
+ preview.querySelectorAll('.attachment-remove').forEach(btn => {
581
+ btn.addEventListener('click', (e) => {
582
+ const idx = parseInt(e.currentTarget.dataset.index, 10);
583
+ this.pendingAttachments.splice(idx, 1);
584
+ this.renderAttachmentPreview();
585
+ });
586
+ });
587
+ }
588
+
589
+ clearAttachments() {
590
+ this.pendingAttachments = [];
591
+ this.renderAttachmentPreview();
592
+ }
593
+
594
+ // --- Messaging ---
595
+
170
596
  async sendMessage() {
171
597
  const input = this.querySelector('#chatInput');
172
598
  const message = input.value.trim();
173
599
  const selectionType = store.get('selectionType');
174
600
  const agent = store.get('selectedAgent');
175
601
  const llm = store.get('selectedLlm');
602
+ const activeId = sessionStore.getActiveId();
176
603
 
177
604
  const selected = selectionType === 'agent' ? agent : llm;
605
+ const hasAttachments = this.pendingAttachments.length > 0;
606
+
607
+ if ((!message && !hasAttachments) || !selected || this.isLoading || !activeId) return;
178
608
 
179
- if (!message || !selected || this.isLoading) return;
609
+ // Capture attachments before clearing
610
+ const attachments = hasAttachments ? [...this.pendingAttachments] : null;
180
611
 
181
- // Add user message
182
- this.appendMessage('user', message);
612
+ // Add user message (with optional attachment thumbnails)
613
+ this.appendMessage('user', message || '(attached files)', { attachments });
614
+ sessionStore.addMessage(activeId, 'user', message || '(attached files)');
183
615
  input.value = '';
184
616
  input.style.height = 'auto';
617
+ this.clearAttachments();
185
618
 
186
619
  this.isLoading = true;
187
620
  this.updateUiState();
@@ -189,27 +622,52 @@ export class AgentsView extends Component {
189
622
  const responseId = 'response-' + Date.now();
190
623
  this.createResponseBubble(responseId);
191
624
 
625
+ this.currentAbortController = new AbortController();
626
+ this.streamUsageData = null;
627
+ this.startStreamTimer(responseId);
628
+
629
+ let finalContent = '';
630
+ let wasCancelled = false;
631
+
192
632
  try {
193
633
  if (selectionType === 'agent') {
194
- await this.sendAgentMessage(agent, message, responseId);
634
+ finalContent = await this.sendAgentMessage(agent, message, responseId, attachments);
195
635
  } else if (selectionType === 'llm') {
196
- await this.sendLlmMessage(llm, message, responseId);
636
+ finalContent = await this.sendLlmMessage(llm, message, responseId, attachments);
197
637
  }
198
638
  } catch (e) {
199
- this.updateResponseError(responseId, `Error: ${e.message}`);
639
+ if (e.name === 'AbortError') {
640
+ wasCancelled = true;
641
+ } else {
642
+ this.updateResponseError(responseId, `Error: ${e.message}`);
643
+ }
200
644
  } finally {
645
+ this.stopStreamTimer(responseId, message, finalContent, wasCancelled);
646
+ this.currentAbortController = null;
201
647
  this.isLoading = false;
202
648
  this.updateUiState();
203
649
  input.focus();
204
650
  }
651
+
652
+ // Persist assistant response
653
+ if (finalContent) {
654
+ sessionStore.addMessage(activeId, 'assistant', finalContent);
655
+ }
656
+
657
+ // Re-render sidebar (title/ordering may have changed)
658
+ this.renderSessionList();
205
659
  }
206
660
 
207
- async sendAgentMessage(agent, message, responseId) {
661
+ async sendAgentMessage(agent, message, responseId, attachments) {
208
662
  const inputVars = agent.inputVariables || ['message'];
209
663
  const inputObj = {};
210
664
  inputObj[inputVars[0] || 'message'] = message;
665
+ if (attachments) {
666
+ inputObj.attachments = attachments;
667
+ }
211
668
 
212
- const res = await api.streamAgent(agent.name, inputObj, store.get('sessionId'));
669
+ const activeId = sessionStore.getActiveId();
670
+ const res = await api.streamAgent(agent.name, inputObj, activeId, { signal: this.currentAbortController?.signal });
213
671
  const reader = res.body.getReader();
214
672
  const decoder = new TextDecoder();
215
673
 
@@ -224,6 +682,7 @@ export class AgentsView extends Component {
224
682
 
225
683
  let currentContent = '';
226
684
  let buffer = '';
685
+ let hasToolCalls = false;
227
686
 
228
687
  while (true) {
229
688
  const { done, value } = await reader.read();
@@ -233,7 +692,6 @@ export class AgentsView extends Component {
233
692
  buffer += chunk;
234
693
 
235
694
  const lines = buffer.split('\n');
236
- // Keep the last partial line in the buffer
237
695
  buffer = lines.pop() || '';
238
696
 
239
697
  for (const line of lines) {
@@ -245,9 +703,19 @@ export class AgentsView extends Component {
245
703
 
246
704
  try {
247
705
  const event = JSON.parse(data);
706
+
707
+ // Handle server-side errors
708
+ if (event.error) {
709
+ this.updateResponseError(responseId, `Error: ${event.error}`);
710
+ return currentContent;
711
+ }
712
+
248
713
  if (event.type === 'content') {
249
714
  currentContent += event.content;
250
715
  }
716
+ if (event.type === 'tool_start' || event.type === 'tool_end') {
717
+ hasToolCalls = true;
718
+ }
251
719
  this.handleStreamEvent(event, responseId, currentContent, thinkingState);
252
720
  } catch (e) {
253
721
  console.error('Error parsing stream event', e, data);
@@ -255,10 +723,29 @@ export class AgentsView extends Component {
255
723
  }
256
724
  }
257
725
  }
726
+
727
+ // Finalize any remaining thinking pill
728
+ const toolsDiv = bubble.querySelector('.tool-invocations');
729
+ this.finalizeThinkingPill(toolsDiv, thinkingState);
730
+
731
+ // If tools were called but no text content was produced, clear loading state
732
+ if (hasToolCalls && !currentContent.trim()) {
733
+ const loadingDots = contentDiv.querySelector('.loading-dots');
734
+ if (loadingDots) {
735
+ loadingDots.remove();
736
+ bubble.querySelector('.response-bubble-inner').classList.remove('py-4');
737
+ bubble.querySelector('.response-bubble-inner').classList.add('py-3');
738
+ contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
739
+ contentDiv.innerHTML = '';
740
+ }
741
+ }
742
+
743
+ return currentContent;
258
744
  }
259
745
 
260
- async sendLlmMessage(llm, message, responseId) {
261
- const res = await api.streamLLM(llm.name, message);
746
+ async sendLlmMessage(llm, message, responseId, attachments) {
747
+ const activeId = sessionStore.getActiveId();
748
+ const res = await api.streamLLM(llm.name, message, activeId, attachments, { signal: this.currentAbortController?.signal });
262
749
  const reader = res.body.getReader();
263
750
  const decoder = new TextDecoder();
264
751
 
@@ -269,8 +756,8 @@ export class AgentsView extends Component {
269
756
 
270
757
  if (loadingDots) {
271
758
  loadingDots.remove();
272
- bubble.querySelector('.max-w-4xl').classList.remove('py-4');
273
- bubble.querySelector('.max-w-4xl').classList.add('py-3');
759
+ bubble.querySelector('.response-bubble-inner').classList.remove('py-4');
760
+ bubble.querySelector('.response-bubble-inner').classList.add('py-3');
274
761
  contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
275
762
  contentDiv.innerHTML = '';
276
763
  }
@@ -305,7 +792,17 @@ export class AgentsView extends Component {
305
792
  const parsed = JSON.parse(data);
306
793
 
307
794
  if (parsed.error) {
308
- throw new Error(parsed.error);
795
+ this.updateResponseError(responseId, `Error: ${parsed.error}`);
796
+ return fullContent;
797
+ }
798
+
799
+ if (parsed.type === 'usage') {
800
+ this.streamUsageData = {
801
+ input_tokens: parsed.input_tokens || 0,
802
+ output_tokens: parsed.output_tokens || 0,
803
+ total_tokens: parsed.total_tokens || 0,
804
+ };
805
+ continue;
309
806
  }
310
807
 
311
808
  const text = parsed.content || '';
@@ -321,176 +818,152 @@ export class AgentsView extends Component {
321
818
  }
322
819
  }
323
820
  }
821
+
822
+ return fullContent;
324
823
  }
325
824
 
326
825
  renderLlmContentStreaming(contentDiv, fullContent, responseId, state) {
327
- // Parse content to find think sections and regular text
328
- const parts = [];
329
- let pos = 0;
330
- let thinkIndex = 0;
331
-
332
- while (pos < fullContent.length) {
333
- const thinkStart = fullContent.indexOf('[THINK]', pos);
334
-
335
- if (thinkStart === -1) {
336
- // No more think sections, add remaining text
337
- const text = fullContent.slice(pos).trim();
338
- if (text) {
339
- parts.push({ type: 'text', content: text });
340
- }
341
- break;
342
- }
343
-
344
- // Add text before [THINK]
345
- if (thinkStart > pos) {
346
- const text = fullContent.slice(pos, thinkStart).trim();
347
- if (text) {
348
- parts.push({ type: 'text', content: text });
349
- }
350
- }
351
-
352
- // Find the end of this think section
353
- const thinkContentStart = thinkStart + 7; // After [THINK]
354
- const thinkEnd = fullContent.indexOf('[/THINK]', thinkContentStart);
826
+ const existing = contentDiv.querySelector('.content-text');
827
+ if (existing) {
828
+ const renderedHtml = markdownRenderer.render(fullContent);
829
+ existing.innerHTML = renderedHtml;
830
+ markdownRenderer.highlightCode(existing);
831
+ } else {
832
+ const div = document.createElement('div');
833
+ div.className = 'content-text markdown-content';
834
+ div.innerHTML = markdownRenderer.render(fullContent);
835
+ markdownRenderer.highlightCode(div);
836
+ contentDiv.appendChild(div);
837
+ }
838
+ }
355
839
 
356
- if (thinkEnd === -1) {
357
- // Think section is still streaming
358
- const thinkContent = fullContent.slice(thinkContentStart).trim();
359
- parts.push({ type: 'think', content: thinkContent, complete: false, index: thinkIndex });
360
- thinkIndex++;
361
- break;
362
- } else {
363
- // Complete think section
364
- const thinkContent = fullContent.slice(thinkContentStart, thinkEnd).trim();
365
- parts.push({ type: 'think', content: thinkContent, complete: true, index: thinkIndex });
366
- thinkIndex++;
367
- pos = thinkEnd + 8; // After [/THINK]
368
- }
840
+ handleThinkingEvent(event, toolsDiv, thinkingState, container) {
841
+ if (!thinkingState.thinkingContent) {
842
+ thinkingState.thinkingContent = '';
369
843
  }
844
+ thinkingState.thinkingContent += event.content;
845
+
846
+ // Create pill on first thinking chunk
847
+ if (!thinkingState.thinkingPill) {
848
+ const pill = document.createElement('div');
849
+ pill.className = 'thinking-pill tool-pill inline-flex items-center gap-1.5 bg-dark-bg/50 border border-dark-border/60 rounded-full px-2.5 py-1 text-xs text-purple-400 font-mono';
850
+ pill.innerHTML = `
851
+ <i class="fas fa-brain animate-pulse text-[10px]"></i>
852
+ <span>Thinking...</span>
853
+ `;
854
+ toolsDiv.appendChild(pill);
855
+ thinkingState.thinkingPill = pill;
856
+ container.scrollTop = container.scrollHeight;
857
+ }
858
+ }
370
859
 
371
- // Update DOM incrementally instead of rebuilding
372
- let currentChildIndex = 0;
373
-
374
- for (let i = 0; i < parts.length; i++) {
375
- const part = parts[i];
376
- const existingChild = contentDiv.children[currentChildIndex];
377
-
378
- if (part.type === 'text') {
379
- if (existingChild && existingChild.classList.contains('content-text')) {
380
- // Update existing text element with markdown
381
- const renderedHtml = markdownRenderer.render(part.content);
382
- existingChild.innerHTML = renderedHtml;
383
- markdownRenderer.highlightCode(existingChild);
384
- } else {
385
- // Create new text element
386
- const div = document.createElement('div');
387
- div.className = 'content-text markdown-content';
388
- const renderedHtml = markdownRenderer.render(part.content);
389
- div.innerHTML = renderedHtml;
390
- markdownRenderer.highlightCode(div);
391
- if (existingChild) {
392
- contentDiv.insertBefore(div, existingChild);
393
- } else {
394
- contentDiv.appendChild(div);
395
- }
396
- }
397
- currentChildIndex++;
398
- } else if (part.type === 'think') {
399
- const thinkId = `think-${responseId}-${part.index}`;
400
-
401
- if (existingChild && existingChild.classList.contains('think-section')) {
402
- // Update existing think section
403
- const label = existingChild.querySelector('.think-label');
404
- const content = existingChild.querySelector('.think-content');
405
- if (label) {
406
- label.textContent = part.complete ? 'Thinking' : 'Thinking...';
407
- }
408
- if (content) {
409
- const renderedHtml = markdownRenderer.render(part.content);
410
- content.innerHTML = renderedHtml;
411
- content.classList.add('markdown-content');
412
- markdownRenderer.highlightCode(content);
413
- }
414
- } else {
415
- // Create new think section with event listener
416
- const section = document.createElement('div');
417
- section.className = 'think-section mb-3 border-l-2 border-blue-500/40 pl-3 py-1';
418
- section.dataset.thinkIndex = part.index;
419
-
420
- const toggle = document.createElement('button');
421
- toggle.className = 'think-toggle flex items-center gap-1.5 text-xs text-blue-400 hover:text-blue-300 py-1 cursor-pointer';
422
- toggle.dataset.thinkId = thinkId;
423
-
424
- toggle.innerHTML = `
425
- <i class="fas fa-brain text-xs"></i>
426
- <span class="font-medium think-label">${part.complete ? 'Thinking' : 'Thinking...'}</span>
427
- <i class="fas fa-chevron-right text-[10px] transition-transform think-chevron"></i>
428
- `;
429
-
430
- const thinkContent = document.createElement('div');
431
- thinkContent.id = thinkId;
432
- thinkContent.className = 'think-content hidden text-sm text-gray-400 markdown-content mt-1 leading-relaxed';
433
- const renderedHtml = markdownRenderer.render(part.content);
434
- thinkContent.innerHTML = renderedHtml;
435
- markdownRenderer.highlightCode(thinkContent);
436
-
437
- // Add click handler ONCE when creating
438
- toggle.addEventListener('click', (e) => {
439
- e.preventDefault();
440
- e.stopPropagation();
441
- const content = section.querySelector('.think-content');
442
- const chevron = section.querySelector('.think-chevron');
443
-
444
- if (content && chevron) {
445
- if (content.classList.contains('hidden')) {
446
- content.classList.remove('hidden');
447
- chevron.classList.remove('fa-chevron-right');
448
- chevron.classList.add('fa-chevron-down');
449
- } else {
450
- content.classList.add('hidden');
451
- chevron.classList.remove('fa-chevron-down');
452
- chevron.classList.add('fa-chevron-right');
453
- }
454
- }
455
- });
860
+ finalizeThinkingPill(toolsDiv, thinkingState) {
861
+ const pill = thinkingState.thinkingPill;
862
+ if (!pill) return;
456
863
 
457
- section.appendChild(toggle);
458
- section.appendChild(thinkContent);
864
+ const content = thinkingState.thinkingContent || '';
865
+ thinkingState.thinkingPill = null;
866
+ thinkingState.thinkingContent = '';
459
867
 
460
- if (existingChild) {
461
- contentDiv.insertBefore(section, existingChild);
462
- } else {
463
- contentDiv.appendChild(section);
464
- }
465
- }
466
- currentChildIndex++;
467
- }
468
- }
868
+ pill.className = 'thinking-pill tool-pill relative inline-flex items-center gap-1.5 bg-dark-bg/30 border border-dark-border/50 rounded-full px-2.5 py-1 text-xs text-gray-500 font-mono cursor-pointer hover:bg-dark-bg/60 hover:border-dark-border transition-colors';
869
+ pill.innerHTML = '';
469
870
 
470
- // Remove any extra children
471
- while (contentDiv.children.length > currentChildIndex) {
472
- contentDiv.removeChild(contentDiv.lastChild);
473
- }
871
+ const pillContent = document.createElement('span');
872
+ pillContent.className = 'inline-flex items-center gap-1.5';
873
+ pillContent.innerHTML = `
874
+ <i class="fas fa-brain text-purple-400 text-[10px]"></i>
875
+ <span>Thinking</span>
876
+ `;
877
+ pill.appendChild(pillContent);
878
+
879
+ const popover = document.createElement('div');
880
+ popover.className = 'hidden fixed z-50 bg-dark-surface border border-dark-border rounded-lg shadow-xl w-[400px] max-w-[90vw] p-3';
881
+
882
+ const popoverContent = document.createElement('div');
883
+ popoverContent.className = 'text-xs text-gray-400 max-h-64 overflow-y-auto markdown-content custom-scrollbar';
884
+ popoverContent.innerHTML = markdownRenderer.render(content);
885
+ markdownRenderer.highlightCode(popoverContent);
886
+ popover.appendChild(popoverContent);
887
+ pill.appendChild(popover);
888
+
889
+ pill.addEventListener('mouseenter', () => {
890
+ popover.classList.remove('hidden');
891
+ const pillRect = pill.getBoundingClientRect();
892
+ popover.style.bottom = (window.innerHeight - pillRect.top + 4) + 'px';
893
+ popover.style.top = 'auto';
894
+ if (pillRect.left + 400 > window.innerWidth - 16) {
895
+ popover.style.left = Math.max(8, pillRect.right - 400) + 'px';
896
+ } else {
897
+ popover.style.left = pillRect.left + 'px';
898
+ }
899
+ popover.style.right = 'auto';
900
+ });
901
+ pill.addEventListener('mouseleave', () => popover.classList.add('hidden'));
474
902
  }
475
903
 
476
904
  createResponseBubble(id) {
477
905
  const container = this.querySelector('#chatMessages');
906
+ const wrapper = document.createElement('div');
907
+ wrapper.className = 'response-wrapper';
908
+
478
909
  const div = document.createElement('div');
479
910
  div.id = id;
480
911
  div.className = 'flex justify-start';
481
912
  div.innerHTML = `
482
- <div class="max-w-4xl bg-dark-surface border border-dark-border rounded-3xl px-5 py-4 text-gray-100 text-[15px] leading-relaxed relative group">
913
+ <div class="response-bubble-inner max-w-4xl bg-dark-surface border border-dark-border rounded-3xl px-5 py-4 text-gray-100 text-[15px] leading-relaxed relative group">
483
914
  <div class="response-content whitespace-pre-wrap flex items-center">
484
915
  <div class="loading-dots flex gap-1">
485
916
  <div class="w-2 h-2 bg-blue-500 rounded-full animate-bounce"></div>
486
- <div class="w-2 h-2 bg-blue-500 rounded-full animate-bounce" style="animation-delay: 0.2s"></div>
487
- <div class="w-2 h-2 bg-blue-500 rounded-full animate-bounce" style="animation-delay: 0.4s"></div>
917
+ <div class="w-2 h-2 bg-blue-500 rounded-full animate-bounce animation-delay-200"></div>
918
+ <div class="w-2 h-2 bg-blue-500 rounded-full animate-bounce animation-delay-400"></div>
488
919
  </div>
489
920
  </div>
490
- <div class="tool-invocations space-y-2 mt-2"></div>
921
+ <div class="tool-invocations flex flex-wrap gap-1.5 mt-2"></div>
491
922
  </div>
492
923
  `;
493
- container.appendChild(div);
924
+
925
+ wrapper.appendChild(div);
926
+
927
+ const statusBar = document.createElement('div');
928
+ statusBar.className = 'stream-status-bar flex items-center gap-2 mt-1.5 ml-1 text-xs text-gray-400';
929
+ statusBar.innerHTML = `
930
+ <div class="w-2 h-2 bg-blue-500 rounded-full animate-pulse-dot"></div>
931
+ <span class="stream-status-text">Generating...</span>
932
+ <span class="stream-elapsed text-gray-500">0.0s</span>
933
+ <button class="stream-cancel-btn ml-auto text-gray-500 hover:text-gray-300 text-xs px-2 py-0.5 rounded border border-dark-border hover:border-gray-500 transition-colors">
934
+ Stop
935
+ </button>
936
+ `;
937
+ wrapper.appendChild(statusBar);
938
+
939
+ statusBar.querySelector('.stream-cancel-btn').addEventListener('click', () => this.cancelCurrentStream());
940
+
941
+ const statsBar = document.createElement('div');
942
+ statsBar.className = 'stream-stats-bar hidden flex items-center gap-3 mt-1.5 ml-1 text-xs text-gray-500';
943
+ statsBar.innerHTML = `
944
+ <span class="flex items-center gap-1">
945
+ <i class="far fa-clock"></i>
946
+ <span class="stats-elapsed"></span>
947
+ </span>
948
+ <span class="text-dark-border">|</span>
949
+ <span class="flex items-center gap-1">
950
+ <i class="fas fa-arrow-up text-[9px]"></i>
951
+ <span class="stats-input-tokens"></span>
952
+ </span>
953
+ <span class="text-dark-border">|</span>
954
+ <span class="flex items-center gap-1">
955
+ <i class="fas fa-arrow-down text-[9px]"></i>
956
+ <span class="stats-output-tokens"></span>
957
+ </span>
958
+ <span class="text-dark-border">|</span>
959
+ <span class="flex items-center gap-1">
960
+ <i class="fas fa-bolt text-[9px]"></i>
961
+ <span class="stats-tps"></span>
962
+ </span>
963
+ `;
964
+ wrapper.appendChild(statsBar);
965
+
966
+ container.appendChild(wrapper);
494
967
  container.scrollTop = container.scrollHeight;
495
968
  }
496
969
 
@@ -503,65 +976,124 @@ export class AgentsView extends Component {
503
976
  const loadingDots = contentDiv.querySelector('.loading-dots');
504
977
  const container = this.querySelector('#chatMessages');
505
978
 
506
- if (event.type === 'content') {
979
+ if (event.type === 'thinking') {
980
+ this.handleThinkingEvent(event, toolsDiv, thinkingState, container);
981
+ } else if (event.type === 'content') {
982
+ // Finalize any in-progress thinking pill
983
+ this.finalizeThinkingPill(toolsDiv, thinkingState);
507
984
  if (loadingDots) {
508
985
  loadingDots.remove();
509
- // Reset padding and remove flex classes
510
- bubble.querySelector('.max-w-4xl').classList.remove('py-4');
511
- bubble.querySelector('.max-w-4xl').classList.add('py-3');
986
+ bubble.querySelector('.response-bubble-inner').classList.remove('py-4');
987
+ bubble.querySelector('.response-bubble-inner').classList.add('py-3');
512
988
  contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
513
- // Clear any whitespace
514
989
  contentDiv.innerHTML = '';
515
990
  }
516
- // Use the same rendering method as LLMs to handle [THINK] tags
517
991
  this.renderLlmContentStreaming(contentDiv, currentContent, responseId, thinkingState);
518
- // Scroll to bottom as content streams in
519
992
  container.scrollTop = container.scrollHeight;
520
993
  } else if (event.type === 'tool_start') {
994
+ this.finalizeThinkingPill(toolsDiv, thinkingState);
521
995
  const toolId = `tool-${event.runId}`;
522
996
  const toolEl = document.createElement('div');
523
997
  toolEl.id = toolId;
524
- toolEl.className = 'bg-dark-bg/50 border border-dark-border rounded-lg p-2 text-sm text-gray-400 font-mono flex items-center gap-2';
998
+ toolEl.className = 'tool-pill inline-flex items-center gap-1.5 bg-dark-bg/50 border border-dark-border/60 rounded-full px-2.5 py-1 text-xs text-gray-400 font-mono';
999
+ toolEl.dataset.toolInput = typeof event.input === 'string' ? event.input : JSON.stringify(event.input, null, 2);
525
1000
  toolEl.innerHTML = `
526
- <i class="fas fa-cog animate-spin text-blue-400"></i>
527
- <span>Using ${event.tool}...</span>
1001
+ <i class="fas fa-circle-notch animate-spin text-blue-400 text-[10px]"></i>
1002
+ <span>${this.escapeHtml(event.tool)}</span>
528
1003
  `;
529
1004
  toolsDiv.appendChild(toolEl);
530
- // Scroll to bottom when tool starts
531
1005
  container.scrollTop = container.scrollHeight;
532
1006
 
533
- // If we have loading dots, keep them until text arrives
534
1007
  } else if (event.type === 'tool_end') {
535
1008
  const toolId = `tool-${event.runId}`;
536
1009
  const toolEl = toolsDiv.querySelector(`#${toolId}`);
537
1010
  if (toolEl) {
538
- toolEl.className = 'bg-dark-bg/30 border border-dark-border/50 rounded-lg p-2 text-sm text-gray-500 font-mono flex flex-col gap-1';
539
- // Success state
540
- toolEl.innerHTML = `
541
- <div class="flex items-center gap-2">
542
- <i class="fas fa-check text-green-500"></i>
543
- <span>Used ${event.tool}</span>
544
- </div>
1011
+ const toolInput = toolEl.dataset.toolInput || '';
1012
+ const toolOutput = typeof event.output === 'string' ? event.output : JSON.stringify(event.output, null, 2);
1013
+
1014
+ toolEl.className = 'tool-pill relative inline-flex items-center gap-1.5 bg-dark-bg/30 border border-dark-border/50 rounded-full px-2.5 py-1 text-xs text-gray-500 font-mono cursor-pointer hover:bg-dark-bg/60 hover:border-dark-border transition-colors';
1015
+ toolEl.innerHTML = '';
1016
+
1017
+ const pillContent = document.createElement('span');
1018
+ pillContent.className = 'inline-flex items-center gap-1.5';
1019
+ pillContent.innerHTML = `
1020
+ <i class="fas fa-check text-green-500 text-[10px]"></i>
1021
+ <span>${this.escapeHtml(event.tool)}</span>
545
1022
  `;
1023
+ toolEl.appendChild(pillContent);
1024
+
1025
+ const details = document.createElement('div');
1026
+ details.className = 'tool-invocation-details hidden absolute bottom-full mb-1 z-50 bg-dark-surface border border-dark-border rounded-lg shadow-xl w-[400px] max-w-[90vw]';
1027
+
1028
+ if (toolInput) {
1029
+ const inputSection = document.createElement('div');
1030
+ inputSection.className = 'p-3 border-b border-dark-border/50';
1031
+ inputSection.innerHTML = `<div class="text-xs font-semibold text-gray-400 mb-1">Input</div>`;
1032
+ const inputPre = document.createElement('pre');
1033
+ inputPre.className = 'text-xs text-gray-400 bg-dark-bg/60 rounded-md p-2 overflow-x-auto max-h-48 overflow-y-auto whitespace-pre-wrap break-all custom-scrollbar';
1034
+ inputPre.textContent = toolInput;
1035
+ inputSection.appendChild(inputPre);
1036
+ details.appendChild(inputSection);
1037
+ }
1038
+
1039
+ const outputSection = document.createElement('div');
1040
+ outputSection.className = 'p-3';
1041
+ outputSection.innerHTML = `<div class="text-xs font-semibold text-gray-400 mb-1">Output</div>`;
1042
+ const outputPre = document.createElement('pre');
1043
+ outputPre.className = 'text-xs text-gray-400 bg-dark-bg/60 rounded-md p-2 overflow-x-auto max-h-48 overflow-y-auto whitespace-pre-wrap break-all custom-scrollbar';
1044
+ outputPre.textContent = toolOutput;
1045
+ outputSection.appendChild(outputPre);
1046
+ details.appendChild(outputSection);
1047
+
1048
+ toolEl.appendChild(details);
1049
+
1050
+ toolEl.addEventListener('click', (e) => {
1051
+ if (details.contains(e.target)) return;
1052
+ e.preventDefault();
1053
+ e.stopPropagation();
1054
+ toolsDiv.querySelectorAll('.tool-invocation-details:not(.hidden)').forEach(d => {
1055
+ if (d !== details) d.classList.add('hidden');
1056
+ });
1057
+ const wasHidden = details.classList.contains('hidden');
1058
+ details.classList.toggle('hidden');
1059
+ if (wasHidden) {
1060
+ const pillRect = toolEl.getBoundingClientRect();
1061
+ const containerRect = container.getBoundingClientRect();
1062
+ const spaceRight = containerRect.right - pillRect.left;
1063
+ if (spaceRight < 420) {
1064
+ details.style.right = '0';
1065
+ details.style.left = 'auto';
1066
+ } else {
1067
+ details.style.left = '0';
1068
+ details.style.right = 'auto';
1069
+ }
1070
+ }
1071
+ });
546
1072
 
547
- // Add output preview
548
- const preview = document.createElement('div');
549
- preview.className = 'text-xs text-gray-600 pl-6 truncate';
550
- preview.textContent = typeof event.output === 'string' ? event.output : JSON.stringify(event.output);
551
- toolEl.appendChild(preview);
552
- // Scroll to bottom when tool ends (output preview added)
1073
+ const closeHandler = (e) => {
1074
+ if (!toolEl.contains(e.target)) {
1075
+ details.classList.add('hidden');
1076
+ }
1077
+ };
1078
+ document.addEventListener('click', closeHandler, { capture: true });
553
1079
  container.scrollTop = container.scrollHeight;
1080
+
1081
+ if (event.tool === 'workspace_write' || event.tool === 'workspace_delete') {
1082
+ try {
1083
+ const result = JSON.parse(typeof event.output === 'string' ? event.output : JSON.stringify(event.output));
1084
+ if (result.success && (result.reloaded === 'agent' || result.unloaded === 'agent')) this.loadAgents();
1085
+ } catch { /* ignore parse errors */ }
1086
+ }
554
1087
  }
555
1088
  } else if (event.type === 'result') {
556
1089
  if (loadingDots) {
557
1090
  loadingDots.remove();
558
- bubble.querySelector('.max-w-4xl').classList.remove('py-4');
559
- bubble.querySelector('.max-w-4xl').classList.add('py-3');
1091
+ bubble.querySelector('.response-bubble-inner').classList.remove('py-4');
1092
+ bubble.querySelector('.response-bubble-inner').classList.add('py-3');
560
1093
  contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
561
1094
  contentDiv.innerHTML = '';
562
1095
  }
563
1096
 
564
- // Display structured output as formatted JSON
565
1097
  const resultContainer = document.createElement('div');
566
1098
  resultContainer.className = 'bg-dark-bg/50 border border-dark-border rounded-lg p-4';
567
1099
 
@@ -572,8 +1104,32 @@ export class AgentsView extends Component {
572
1104
  resultContainer.appendChild(resultPre);
573
1105
  contentDiv.appendChild(resultContainer);
574
1106
 
575
- // Scroll to bottom
576
1107
  container.scrollTop = container.scrollHeight;
1108
+ } else if (event.type === 'error') {
1109
+ if (loadingDots) {
1110
+ loadingDots.remove();
1111
+ bubble.querySelector('.response-bubble-inner').classList.remove('py-4');
1112
+ bubble.querySelector('.response-bubble-inner').classList.add('py-3');
1113
+ contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
1114
+ }
1115
+ const errorDiv = document.createElement('div');
1116
+ errorDiv.className = 'text-red-400 text-sm mt-2';
1117
+ errorDiv.textContent = `Error: ${event.error}`;
1118
+ contentDiv.appendChild(errorDiv);
1119
+ container.scrollTop = container.scrollHeight;
1120
+ } else if (event.type === 'usage') {
1121
+ this.streamUsageData = {
1122
+ input_tokens: event.input_tokens || 0,
1123
+ output_tokens: event.output_tokens || 0,
1124
+ total_tokens: event.total_tokens || 0,
1125
+ };
1126
+ } else if (event.type === 'react_iteration') {
1127
+ const wrapper = bubble.closest('.response-wrapper');
1128
+ const statusText = wrapper?.querySelector('.stream-status-text');
1129
+ if (statusText) {
1130
+ const contextKB = (event.contextChars / 1024).toFixed(1);
1131
+ statusText.textContent = `Iteration ${event.iteration} · ${contextKB} KB context`;
1132
+ }
577
1133
  }
578
1134
  }
579
1135
 
@@ -588,8 +1144,13 @@ export class AgentsView extends Component {
588
1144
  updateUiState() {
589
1145
  const btn = this.querySelector('#sendMessageBtn');
590
1146
  const input = this.querySelector('#chatInput');
591
- if (btn) btn.disabled = this.isLoading;
592
- if (input) input.disabled = this.isLoading;
1147
+ const hasActiveSession = !!sessionStore.getActiveId();
1148
+ if (btn) btn.disabled = this.isLoading || !hasActiveSession;
1149
+ if (input) {
1150
+ input.disabled = this.isLoading;
1151
+ input.readOnly = !hasActiveSession;
1152
+ input.classList.toggle('cursor-pointer', !hasActiveSession);
1153
+ }
593
1154
  }
594
1155
 
595
1156
  appendMessage(role, content, metadata = {}) {
@@ -603,8 +1164,24 @@ export class AgentsView extends Component {
603
1164
  const bubbleColor = isUser ? 'bg-dark-surface' : (hasError ? 'bg-red-900/20 border-red-900/30' : 'bg-dark-surface');
604
1165
  const textColor = hasError ? 'text-red-300' : 'text-gray-100';
605
1166
 
1167
+ // Build attachment thumbnails for user messages
1168
+ let attachmentHtml = '';
1169
+ if (isUser && metadata.attachments && metadata.attachments.length > 0) {
1170
+ const thumbs = metadata.attachments.map(att => {
1171
+ if (att.mediaType.startsWith('image/')) {
1172
+ return `<img src="data:${att.mediaType};base64,${att.data}" class="w-16 h-16 object-cover rounded-lg border border-dark-border/50">`;
1173
+ }
1174
+ return `<div class="flex items-center gap-1.5 bg-dark-bg/60 border border-dark-border/50 rounded-lg px-2 py-1.5 text-xs text-gray-400">
1175
+ <i class="fas fa-file"></i>
1176
+ <span class="max-w-[100px] truncate">${this.escapeHtml(att.name)}</span>
1177
+ </div>`;
1178
+ }).join('');
1179
+ attachmentHtml = `<div class="flex flex-wrap gap-2 mb-2">${thumbs}</div>`;
1180
+ }
1181
+
606
1182
  div.innerHTML = `
607
1183
  <div class="max-w-4xl ${bubbleColor} border ${isUser ? 'border-transparent' : 'border-dark-border'} rounded-3xl px-5 py-3 ${textColor} text-[15px] leading-relaxed relative group">
1184
+ ${attachmentHtml}
608
1185
  <div class="whitespace-pre-wrap">${this.escapeHtml(content)}</div>
609
1186
  ${!isUser && !hasError ? `
610
1187
  <button class="copy-btn absolute -bottom-6 left-0 text-gray-500 hover:text-gray-300 opacity-0 group-hover:opacity-100 transition-opacity p-1" title="Copy">
@@ -637,8 +1214,8 @@ export class AgentsView extends Component {
637
1214
  <div class="max-w-4xl bg-dark-surface border border-dark-border rounded-3xl px-5 py-4">
638
1215
  <div class="flex gap-1">
639
1216
  <div class="w-2 h-2 bg-blue-500 rounded-full animate-bounce"></div>
640
- <div class="w-2 h-2 bg-blue-500 rounded-full animate-bounce" style="animation-delay: 0.2s"></div>
641
- <div class="w-2 h-2 bg-blue-500 rounded-full animate-bounce" style="animation-delay: 0.4s"></div>
1217
+ <div class="w-2 h-2 bg-blue-500 rounded-full animate-bounce animation-delay-200"></div>
1218
+ <div class="w-2 h-2 bg-blue-500 rounded-full animate-bounce animation-delay-400"></div>
642
1219
  </div>
643
1220
  </div>
644
1221
  `;
@@ -658,30 +1235,116 @@ export class AgentsView extends Component {
658
1235
  return div.innerHTML;
659
1236
  }
660
1237
 
661
- clearChatHistory() {
662
- const container = this.querySelector('#chatMessages');
663
- if (!container) return;
664
-
665
- // Clear all messages
666
- container.innerHTML = '';
1238
+ _getRandomWelcomeMessage() {
1239
+ const messages = [
1240
+ 'Awaiting your command, master.',
1241
+ 'The agents are restless. Give them purpose.',
1242
+ 'Ready when you are. No pressure... okay, maybe a little.',
1243
+ 'Spinning up neurons... just kidding, I was already ready.',
1244
+ 'All systems nominal. Your move, human.',
1245
+ 'The orchestrator awaits. What shall we build today?',
1246
+ 'Standing by. The agents are stretching their digital legs.',
1247
+ 'Another day, another chance to orchestrate greatness.',
1248
+ 'Agents assembled. Awaiting mission briefing.',
1249
+ 'The stage is set. You are the conductor.',
1250
+ ];
1251
+ return messages[Math.floor(Math.random() * messages.length)];
1252
+ }
667
1253
 
668
- // Add welcome message
1254
+ _appendWelcomeMessage(container) {
669
1255
  const div = document.createElement('div');
670
- div.className = 'flex justify-start';
1256
+ div.className = 'welcome-container';
671
1257
  div.innerHTML = `
672
- <div class="max-w-4xl bg-dark-surface border border-dark-border rounded-3xl px-5 py-3 text-gray-100 text-[15px] leading-relaxed">
673
- Welcome to Agent Orcha. Start chatting with your AI agents and LLMs.
674
- </div>
1258
+ <svg class="welcome-orca" viewBox="0 0 220 140" xmlns="http://www.w3.org/2000/svg">
1259
+ <!-- Main body -->
1260
+ <path class="orca-body" d="
1261
+ M 30,68
1262
+ C 28,58 38,42 58,38
1263
+ C 68,35 74,30 78,18
1264
+ C 80,12 84,12 85,18
1265
+ C 87,28 86,35 92,38
1266
+ C 112,34 148,40 172,54
1267
+ C 176,50 182,44 188,40
1268
+ C 192,38 194,42 190,46
1269
+ C 186,50 184,54 182,56
1270
+ C 186,60 188,66 184,68
1271
+ C 180,70 178,66 176,62
1272
+ C 168,72 142,78 112,76
1273
+ C 82,74 52,70 38,66
1274
+ C 34,64 30,68 30,68 Z
1275
+ "/>
1276
+ <!-- Dorsal fin accent line -->
1277
+ <path class="orca-detail" d="M 72,38 C 74,28 78,18 80,14"/>
1278
+ <!-- Belly line -->
1279
+ <path class="orca-detail" d="M 42,64 C 62,70 100,74 140,72 C 158,70 170,66 176,62"/>
1280
+ <!-- Saddle patch -->
1281
+ <path class="orca-patch" d="M 92,40 C 102,38 112,40 108,48 C 104,54 90,50 92,40 Z"/>
1282
+ <!-- Eye patch -->
1283
+ <path class="orca-patch" d="M 44,52 C 50,48 60,50 56,58 C 52,62 42,58 44,52 Z"/>
1284
+ <!-- Eye -->
1285
+ <circle class="orca-eye" cx="42" cy="54" r="2.5"/>
1286
+ <!-- Pectoral fin -->
1287
+ <path class="orca-body" d="M 65,66 C 70,74 64,82 58,76 C 54,72 60,66 65,66 Z"/>
1288
+ <!-- Tail detail -->
1289
+ <path class="orca-detail" d="M 172,54 C 176,52 180,48 184,44"/>
1290
+ <path class="orca-detail" d="M 176,62 C 180,64 184,66 186,64"/>
1291
+ </svg>
1292
+ <div class="welcome-text">${this._getRandomWelcomeMessage()}</div>
1293
+ ${this._renderSampleQuestionChips()}
675
1294
  `;
676
1295
  container.appendChild(div);
677
1296
 
678
- // Clear session ID to start fresh
679
- store.set('sessionId', null);
1297
+ div.querySelectorAll('.sample-question-chip').forEach(chip => {
1298
+ chip.addEventListener('click', () => {
1299
+ const input = this.querySelector('#chatInput');
1300
+ if (input) {
1301
+ input.value = chip.textContent;
1302
+ input.focus();
1303
+ }
1304
+ });
1305
+ });
1306
+ }
1307
+
1308
+ _renderSampleQuestionChips() {
1309
+ const agent = store.get('selectedAgent');
1310
+ const questions = agent?.sampleQuestions;
1311
+ if (!questions || questions.length === 0) return '';
1312
+
1313
+ const chips = questions.map(q =>
1314
+ `<button class="sample-question-chip bg-dark-surface border border-dark-border/60 hover:border-gray-500 text-gray-300 text-sm px-4 py-2 rounded-2xl transition-colors text-left">${this.escapeHtml(q)}</button>`
1315
+ ).join('');
1316
+
1317
+ return `
1318
+ <div class="flex flex-wrap justify-center gap-2 max-w-2xl mt-4">${chips}</div>
1319
+ `;
1320
+ }
1321
+
1322
+ _appendSessionResetBanner(container) {
1323
+ const div = document.createElement('div');
1324
+ div.className = 'session-reset-banner';
1325
+ div.innerHTML = `
1326
+ <div class="session-reset-line"></div>
1327
+ <span class="session-reset-label">
1328
+ <i class="fas fa-rotate-right"></i>
1329
+ Server restarted — new session
1330
+ </span>
1331
+ <div class="session-reset-line"></div>
1332
+ `;
1333
+ container.appendChild(div);
1334
+ container.scrollTop = container.scrollHeight;
680
1335
  }
681
1336
 
682
1337
  postRender() {
683
1338
  const input = this.querySelector('#chatInput');
684
1339
 
1340
+ // Open new conversation modal when clicking input with no active session
1341
+ input.addEventListener('mousedown', (e) => {
1342
+ if (!sessionStore.getActiveId()) {
1343
+ e.preventDefault();
1344
+ this.showNewSessionModal();
1345
+ }
1346
+ });
1347
+
685
1348
  // Auto-resize
686
1349
  input.addEventListener('input', () => {
687
1350
  input.style.height = 'auto';
@@ -698,60 +1361,74 @@ export class AgentsView extends Component {
698
1361
 
699
1362
  this.querySelector('#sendMessageBtn').addEventListener('click', () => this.sendMessage());
700
1363
 
701
- // Dropdown toggle
702
- const selectorBtn = this.querySelector('#agentSelectorBtn');
703
- const dropdown = this.querySelector('#agentDropdown');
1364
+ // Attach button + file input
1365
+ this.querySelector('#attachBtn').addEventListener('click', () => this.querySelector('#fileInput').click());
1366
+ this.querySelector('#fileInput').addEventListener('change', (e) => this.handleFileSelect(e));
704
1367
 
705
- selectorBtn.addEventListener('click', (e) => {
706
- e.stopPropagation();
707
- this.toggleDropdown();
708
- });
1368
+ // New chat button
1369
+ this.querySelector('#newChatBtn').addEventListener('click', () => this.showNewSessionModal());
709
1370
 
710
- document.addEventListener('click', (e) => {
711
- if (!selectorBtn.contains(e.target) && !dropdown.contains(e.target)) {
712
- this.toggleDropdown(false);
713
- }
714
- });
1371
+ // Mobile sidebar toggle
1372
+ this.querySelector('#sidebarToggleBtn').addEventListener('click', () => this.toggleSidebar(true));
1373
+ this.querySelector('#sidebarBackdrop').addEventListener('click', () => this.toggleSidebar(false));
715
1374
  }
716
1375
 
717
1376
  template() {
718
1377
  return `
719
- <div class="flex flex-col h-[calc(100vh-220px)]">
720
- <!-- Chat Messages -->
721
- <div id="chatMessages" class="flex-1 overflow-y-auto mb-6 space-y-4 pr-2 custom-scrollbar">
722
- <div class="flex justify-start">
723
- <div class="max-w-4xl bg-dark-surface border border-dark-border rounded-3xl px-5 py-3 text-gray-100 text-[15px] leading-relaxed">
724
- Welcome to Agent Orcha. Start chatting with your AI agents and LLMs.
725
- </div>
1378
+ <div class="flex h-full relative border border-dark-border rounded-xl overflow-hidden bg-dark-surface/30">
1379
+ <!-- Mobile sidebar backdrop -->
1380
+ <div id="sidebarBackdrop" class="hidden fixed inset-0 bg-black/50 z-30 md:hidden"></div>
1381
+
1382
+ <!-- Sidebar -->
1383
+ <div id="sidebar" class="hidden md:flex w-64 flex-shrink-0 bg-dark-bg md:bg-dark-bg/60 border-r border-dark-border/60 flex-col
1384
+ fixed md:relative inset-y-0 left-0 z-40 md:z-auto">
1385
+ <div class="p-3">
1386
+ <button id="newChatBtn" class="w-full flex items-center justify-center gap-2 px-3 py-2.5 hover:bg-dark-hover rounded-lg text-sm font-medium text-gray-300 transition-colors">
1387
+ <i class="fas fa-plus text-xs text-blue-400"></i>
1388
+ <span>New chat</span>
1389
+ </button>
726
1390
  </div>
1391
+ <div id="sessionList" class="flex-1 overflow-y-auto custom-scrollbar px-2 pb-2"></div>
727
1392
  </div>
728
1393
 
729
- <!-- Input Area -->
730
- <div class="border-t border-dark-border pt-4">
731
- <div class="relative bg-dark-surface border border-dark-border rounded-2xl focus-within:border-gray-500 transition-colors">
732
- <textarea id="chatInput" rows="1"
733
- class="w-full bg-transparent px-4 py-3 pr-32 text-gray-100 placeholder-gray-500 resize-none focus:outline-none max-h-[200px]"
734
- placeholder="Reply..."></textarea>
735
-
736
- <div class="absolute bottom-2 right-2 flex items-center gap-2">
737
- <!-- Agent Selector -->
738
- <div class="relative">
739
- <button id="agentSelectorBtn" class="flex items-center gap-2 px-3 py-1.5 bg-dark-bg hover:bg-dark-hover rounded-lg text-sm font-medium text-gray-300 transition-colors">
740
- <span id="selectedAgentName">Select Agent/LLM</span>
741
- <i class="fas fa-chevron-down text-xs text-gray-400"></i>
742
- </button>
1394
+ <!-- Chat Area -->
1395
+ <div class="flex-1 flex flex-col min-w-0">
1396
+ <!-- Chat Header -->
1397
+ <div class="flex-shrink-0 flex items-center gap-2 px-3 md:px-5 py-3 border-b border-dark-border/40 text-sm">
1398
+ <button id="sidebarToggleBtn" class="md:hidden text-gray-400 hover:text-gray-200 p-1 -ml-1">
1399
+ <i class="fas fa-bars"></i>
1400
+ </button>
1401
+ <div id="chatHeader" class="flex-1 min-w-0">
1402
+ <span class="text-gray-500">No conversation selected</span>
1403
+ </div>
1404
+ </div>
743
1405
 
744
- <div id="agentDropdown" class="hidden absolute bottom-full mb-2 right-0 w-80 bg-dark-surface border border-dark-border rounded-xl shadow-2xl overflow-hidden z-10 max-h-96 flex flex-col">
745
- <div id="agentDropdownList" class="overflow-y-auto custom-scrollbar">
746
- <div class="text-gray-500 text-sm text-center py-4">Loading...</div>
747
- </div>
748
- </div>
1406
+ <!-- Chat Messages -->
1407
+ <div id="chatMessages" class="flex-1 overflow-y-auto space-y-4 p-4 pr-2 pb-6 custom-scrollbar"></div>
1408
+
1409
+ <!-- Input Area -->
1410
+ <div class="p-3 pt-0">
1411
+ <div id="attachmentPreview" class="hidden flex flex-wrap gap-2 px-2 pb-2"></div>
1412
+ <div class="relative bg-dark-surface border border-dark-border/60 rounded-2xl focus-within:border-gray-500 transition-colors">
1413
+ <input type="file" id="fileInput" multiple accept="image/*,.pdf" class="hidden">
1414
+ <textarea id="chatInput" rows="1" readonly
1415
+ class="w-full bg-transparent pl-11 pr-14 py-3 text-gray-100 placeholder-gray-500 resize-none focus:outline-none max-h-[200px] cursor-pointer"
1416
+ placeholder="Ask anything"></textarea>
1417
+
1418
+ <div class="absolute bottom-2 left-2 flex items-center">
1419
+ <button id="attachBtn" type="button"
1420
+ class="text-gray-500 hover:text-gray-300 p-1.5 rounded-lg hover:bg-dark-hover transition-colors"
1421
+ title="Attach files">
1422
+ <i class="fas fa-plus text-sm"></i>
1423
+ </button>
749
1424
  </div>
750
1425
 
751
- <button id="sendMessageBtn" disabled
752
- class="bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-500 hover:to-blue-600 disabled:opacity-50 disabled:cursor-not-allowed text-white p-2 rounded-lg transition-all shadow-lg shadow-blue-900/20">
753
- <i class="fas fa-paper-plane text-sm"></i>
754
- </button>
1426
+ <div class="absolute bottom-2 right-2 flex items-center gap-2">
1427
+ <button id="sendMessageBtn" disabled
1428
+ class="bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-500 hover:to-blue-600 disabled:opacity-50 disabled:cursor-not-allowed text-white p-2 rounded-lg transition-all shadow-lg shadow-blue-900/20">
1429
+ <i class="fas fa-paper-plane text-sm"></i>
1430
+ </button>
1431
+ </div>
755
1432
  </div>
756
1433
  </div>
757
1434
  </div>