luca 2.0.0 → 3.0.2

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 (532) hide show
  1. package/.github/workflows/release.yaml +170 -0
  2. package/AGENTS.md +99 -0
  3. package/CLAUDE.md +123 -0
  4. package/CNAME +1 -0
  5. package/README.md +275 -9
  6. package/RUNME.md +56 -0
  7. package/assistants/codingAssistant/ABOUT.md +5 -0
  8. package/assistants/codingAssistant/CORE.md +33 -0
  9. package/assistants/codingAssistant/hooks.ts +21 -0
  10. package/assistants/codingAssistant/tools.ts +12 -0
  11. package/assistants/inkbot/ABOUT.md +16 -0
  12. package/assistants/inkbot/CORE.md +330 -0
  13. package/assistants/inkbot/hooks.ts +6 -0
  14. package/assistants/inkbot/tools.ts +53 -0
  15. package/assistants/researcher/ABOUT.md +5 -0
  16. package/assistants/researcher/CORE.md +46 -0
  17. package/assistants/researcher/hooks.ts +16 -0
  18. package/assistants/researcher/tools.ts +237 -0
  19. package/bun.lock +2667 -0
  20. package/bunfig.toml +3 -0
  21. package/commands/audit-docs.ts +740 -0
  22. package/commands/build-bootstrap.ts +117 -0
  23. package/commands/build-python-bridge.ts +42 -0
  24. package/commands/build-scaffolds.ts +175 -0
  25. package/commands/bundle-consumer-project.ts +521 -0
  26. package/commands/generate-api-docs.ts +114 -0
  27. package/commands/inkbot.ts +874 -0
  28. package/commands/release.ts +80 -0
  29. package/commands/try-all-challenges.ts +543 -0
  30. package/commands/try-challenge.ts +100 -0
  31. package/dist/agi/container.server.d.ts +63 -0
  32. package/dist/agi/container.server.d.ts.map +1 -0
  33. package/dist/agi/endpoints/ask.d.ts +20 -0
  34. package/dist/agi/endpoints/ask.d.ts.map +1 -0
  35. package/dist/agi/endpoints/conversations/[id].d.ts +27 -0
  36. package/dist/agi/endpoints/conversations/[id].d.ts.map +1 -0
  37. package/dist/agi/endpoints/conversations.d.ts +18 -0
  38. package/dist/agi/endpoints/conversations.d.ts.map +1 -0
  39. package/dist/agi/endpoints/experts.d.ts +8 -0
  40. package/dist/agi/endpoints/experts.d.ts.map +1 -0
  41. package/dist/agi/feature.d.ts +9 -0
  42. package/dist/agi/feature.d.ts.map +1 -0
  43. package/dist/agi/features/assistant.d.ts +509 -0
  44. package/dist/agi/features/assistant.d.ts.map +1 -0
  45. package/dist/agi/features/assistants-manager.d.ts +236 -0
  46. package/dist/agi/features/assistants-manager.d.ts.map +1 -0
  47. package/dist/agi/features/autonomous-assistant.d.ts +281 -0
  48. package/dist/agi/features/autonomous-assistant.d.ts.map +1 -0
  49. package/dist/agi/features/browser-use.d.ts +479 -0
  50. package/dist/agi/features/browser-use.d.ts.map +1 -0
  51. package/dist/agi/features/claude-code.d.ts +824 -0
  52. package/dist/agi/features/claude-code.d.ts.map +1 -0
  53. package/dist/agi/features/conversation-history.d.ts +245 -0
  54. package/dist/agi/features/conversation-history.d.ts.map +1 -0
  55. package/dist/agi/features/conversation.d.ts +464 -0
  56. package/dist/agi/features/conversation.d.ts.map +1 -0
  57. package/dist/agi/features/docs-reader.d.ts +72 -0
  58. package/dist/agi/features/docs-reader.d.ts.map +1 -0
  59. package/dist/agi/features/file-tools.d.ts +110 -0
  60. package/dist/agi/features/file-tools.d.ts.map +1 -0
  61. package/dist/agi/features/luca-coder.d.ts +323 -0
  62. package/dist/agi/features/luca-coder.d.ts.map +1 -0
  63. package/dist/agi/features/openai-codex.d.ts +381 -0
  64. package/dist/agi/features/openai-codex.d.ts.map +1 -0
  65. package/dist/agi/features/openapi.d.ts +200 -0
  66. package/dist/agi/features/openapi.d.ts.map +1 -0
  67. package/dist/agi/features/skills-library.d.ts +167 -0
  68. package/dist/agi/features/skills-library.d.ts.map +1 -0
  69. package/dist/agi/index.d.ts +5 -0
  70. package/dist/agi/index.d.ts.map +1 -0
  71. package/dist/agi/lib/interceptor-chain.d.ts +44 -0
  72. package/dist/agi/lib/interceptor-chain.d.ts.map +1 -0
  73. package/dist/agi/lib/token-counter.d.ts +13 -0
  74. package/dist/agi/lib/token-counter.d.ts.map +1 -0
  75. package/dist/bootstrap/generated.d.ts +5 -0
  76. package/dist/bootstrap/generated.d.ts.map +1 -0
  77. package/dist/browser.d.ts +12 -0
  78. package/dist/browser.d.ts.map +1 -0
  79. package/dist/bus.d.ts +29 -0
  80. package/dist/bus.d.ts.map +1 -0
  81. package/dist/cli/build-info.d.ts +4 -0
  82. package/dist/cli/build-info.d.ts.map +1 -0
  83. package/dist/cli/cli.d.ts +3 -12
  84. package/dist/cli/cli.d.ts.map +1 -0
  85. package/dist/client.d.ts +60 -0
  86. package/dist/client.d.ts.map +1 -0
  87. package/dist/clients/civitai/index.d.ts +472 -0
  88. package/dist/clients/civitai/index.d.ts.map +1 -0
  89. package/dist/clients/client-template.d.ts +30 -0
  90. package/dist/clients/client-template.d.ts.map +1 -0
  91. package/dist/clients/comfyui/index.d.ts +281 -0
  92. package/dist/clients/comfyui/index.d.ts.map +1 -0
  93. package/dist/clients/elevenlabs/index.d.ts +197 -0
  94. package/dist/clients/elevenlabs/index.d.ts.map +1 -0
  95. package/dist/clients/graph.d.ts +64 -0
  96. package/dist/clients/graph.d.ts.map +1 -0
  97. package/dist/clients/openai/index.d.ts +247 -0
  98. package/dist/clients/openai/index.d.ts.map +1 -0
  99. package/dist/clients/rest.d.ts +92 -0
  100. package/dist/clients/rest.d.ts.map +1 -0
  101. package/dist/clients/supabase/index.d.ts +176 -0
  102. package/dist/clients/supabase/index.d.ts.map +1 -0
  103. package/dist/clients/websocket.d.ts +127 -0
  104. package/dist/clients/websocket.d.ts.map +1 -0
  105. package/dist/command.d.ts +163 -0
  106. package/dist/command.d.ts.map +1 -0
  107. package/dist/commands/bootstrap.d.ts +20 -0
  108. package/dist/commands/bootstrap.d.ts.map +1 -0
  109. package/dist/commands/chat.d.ts +37 -0
  110. package/dist/commands/chat.d.ts.map +1 -0
  111. package/dist/commands/code.d.ts +28 -0
  112. package/dist/commands/code.d.ts.map +1 -0
  113. package/dist/commands/console.d.ts +22 -0
  114. package/dist/commands/console.d.ts.map +1 -0
  115. package/dist/commands/describe.d.ts +50 -0
  116. package/dist/commands/describe.d.ts.map +1 -0
  117. package/dist/commands/eval.d.ts +23 -0
  118. package/dist/commands/eval.d.ts.map +1 -0
  119. package/dist/commands/help.d.ts +25 -0
  120. package/dist/commands/help.d.ts.map +1 -0
  121. package/dist/commands/index.d.ts +18 -0
  122. package/dist/commands/index.d.ts.map +1 -0
  123. package/dist/commands/introspect.d.ts +24 -0
  124. package/dist/commands/introspect.d.ts.map +1 -0
  125. package/dist/commands/mcp.d.ts +35 -0
  126. package/dist/commands/mcp.d.ts.map +1 -0
  127. package/dist/commands/prompt.d.ts +38 -0
  128. package/dist/commands/prompt.d.ts.map +1 -0
  129. package/dist/commands/run.d.ts +24 -0
  130. package/dist/commands/run.d.ts.map +1 -0
  131. package/dist/commands/sandbox-mcp.d.ts +34 -0
  132. package/dist/commands/sandbox-mcp.d.ts.map +1 -0
  133. package/dist/commands/save-api-docs.d.ts +21 -0
  134. package/dist/commands/save-api-docs.d.ts.map +1 -0
  135. package/dist/commands/scaffold.d.ts +24 -0
  136. package/dist/commands/scaffold.d.ts.map +1 -0
  137. package/dist/commands/select.d.ts +22 -0
  138. package/dist/commands/select.d.ts.map +1 -0
  139. package/dist/commands/serve.d.ts +29 -0
  140. package/dist/commands/serve.d.ts.map +1 -0
  141. package/dist/container-describer.d.ts +144 -0
  142. package/dist/container-describer.d.ts.map +1 -0
  143. package/dist/container.d.ts +451 -0
  144. package/dist/container.d.ts.map +1 -0
  145. package/dist/endpoint.d.ts +113 -0
  146. package/dist/endpoint.d.ts.map +1 -0
  147. package/dist/feature.d.ts +47 -0
  148. package/dist/feature.d.ts.map +1 -0
  149. package/dist/graft.d.ts +29 -0
  150. package/dist/graft.d.ts.map +1 -0
  151. package/dist/hash-object.d.ts +8 -0
  152. package/dist/hash-object.d.ts.map +1 -0
  153. package/dist/helper.d.ts +209 -0
  154. package/dist/helper.d.ts.map +1 -0
  155. package/dist/introspection/generated.node.d.ts +44623 -0
  156. package/dist/introspection/generated.node.d.ts.map +1 -0
  157. package/dist/introspection/generated.web.d.ts +1412 -0
  158. package/dist/introspection/generated.web.d.ts.map +1 -0
  159. package/dist/introspection/index.d.ts +156 -0
  160. package/dist/introspection/index.d.ts.map +1 -0
  161. package/dist/introspection/scan.d.ts +147 -0
  162. package/dist/introspection/scan.d.ts.map +1 -0
  163. package/dist/node/container.d.ts +256 -0
  164. package/dist/node/container.d.ts.map +1 -0
  165. package/dist/node/feature.d.ts +9 -0
  166. package/dist/node/feature.d.ts.map +1 -0
  167. package/dist/node/features/container-link.d.ts +213 -0
  168. package/dist/node/features/container-link.d.ts.map +1 -0
  169. package/dist/node/features/content-db.d.ts +354 -0
  170. package/dist/node/features/content-db.d.ts.map +1 -0
  171. package/dist/node/features/disk-cache.d.ts +236 -0
  172. package/dist/node/features/disk-cache.d.ts.map +1 -0
  173. package/dist/node/features/dns.d.ts +511 -0
  174. package/dist/node/features/dns.d.ts.map +1 -0
  175. package/dist/node/features/docker.d.ts +485 -0
  176. package/dist/node/features/docker.d.ts.map +1 -0
  177. package/dist/node/features/downloader.d.ts +73 -0
  178. package/dist/node/features/downloader.d.ts.map +1 -0
  179. package/dist/node/features/figlet-fonts.d.ts +4 -0
  180. package/dist/node/features/figlet-fonts.d.ts.map +1 -0
  181. package/dist/node/features/file-manager.d.ts +177 -0
  182. package/dist/node/features/file-manager.d.ts.map +1 -0
  183. package/dist/node/features/fs.d.ts +635 -0
  184. package/dist/node/features/fs.d.ts.map +1 -0
  185. package/dist/node/features/git.d.ts +329 -0
  186. package/dist/node/features/git.d.ts.map +1 -0
  187. package/dist/node/features/google-auth.d.ts +200 -0
  188. package/dist/node/features/google-auth.d.ts.map +1 -0
  189. package/dist/node/features/google-calendar.d.ts +194 -0
  190. package/dist/node/features/google-calendar.d.ts.map +1 -0
  191. package/dist/node/features/google-docs.d.ts +138 -0
  192. package/dist/node/features/google-docs.d.ts.map +1 -0
  193. package/dist/node/features/google-drive.d.ts +202 -0
  194. package/dist/node/features/google-drive.d.ts.map +1 -0
  195. package/dist/node/features/google-mail.d.ts +221 -0
  196. package/dist/node/features/google-mail.d.ts.map +1 -0
  197. package/dist/node/features/google-sheets.d.ts +157 -0
  198. package/dist/node/features/google-sheets.d.ts.map +1 -0
  199. package/dist/node/features/grep.d.ts +207 -0
  200. package/dist/node/features/grep.d.ts.map +1 -0
  201. package/dist/node/features/helpers.d.ts +236 -0
  202. package/dist/node/features/helpers.d.ts.map +1 -0
  203. package/dist/node/features/ink.d.ts +332 -0
  204. package/dist/node/features/ink.d.ts.map +1 -0
  205. package/dist/node/features/ipc-socket.d.ts +298 -0
  206. package/dist/node/features/ipc-socket.d.ts.map +1 -0
  207. package/dist/node/features/json-tree.d.ts +140 -0
  208. package/dist/node/features/json-tree.d.ts.map +1 -0
  209. package/dist/node/features/networking.d.ts +373 -0
  210. package/dist/node/features/networking.d.ts.map +1 -0
  211. package/dist/node/features/nlp.d.ts +125 -0
  212. package/dist/node/features/nlp.d.ts.map +1 -0
  213. package/dist/node/features/opener.d.ts +93 -0
  214. package/dist/node/features/opener.d.ts.map +1 -0
  215. package/dist/node/features/os.d.ts +168 -0
  216. package/dist/node/features/os.d.ts.map +1 -0
  217. package/dist/node/features/package-finder.d.ts +419 -0
  218. package/dist/node/features/package-finder.d.ts.map +1 -0
  219. package/dist/node/features/postgres.d.ts +173 -0
  220. package/dist/node/features/postgres.d.ts.map +1 -0
  221. package/dist/node/features/proc.d.ts +285 -0
  222. package/dist/node/features/proc.d.ts.map +1 -0
  223. package/dist/node/features/process-manager.d.ts +427 -0
  224. package/dist/node/features/process-manager.d.ts.map +1 -0
  225. package/dist/node/features/python.d.ts +477 -0
  226. package/dist/node/features/python.d.ts.map +1 -0
  227. package/dist/node/features/redis.d.ts +247 -0
  228. package/dist/node/features/redis.d.ts.map +1 -0
  229. package/dist/node/features/repl.d.ts +84 -0
  230. package/dist/node/features/repl.d.ts.map +1 -0
  231. package/dist/node/features/runpod.d.ts +527 -0
  232. package/dist/node/features/runpod.d.ts.map +1 -0
  233. package/dist/node/features/secure-shell.d.ts +145 -0
  234. package/dist/node/features/secure-shell.d.ts.map +1 -0
  235. package/dist/node/features/semantic-search.d.ts +207 -0
  236. package/dist/node/features/semantic-search.d.ts.map +1 -0
  237. package/dist/node/features/sqlite.d.ts +180 -0
  238. package/dist/node/features/sqlite.d.ts.map +1 -0
  239. package/dist/node/features/telegram.d.ts +173 -0
  240. package/dist/node/features/telegram.d.ts.map +1 -0
  241. package/dist/node/features/transpiler.d.ts +51 -0
  242. package/dist/node/features/transpiler.d.ts.map +1 -0
  243. package/dist/node/features/tts.d.ts +108 -0
  244. package/dist/node/features/tts.d.ts.map +1 -0
  245. package/dist/node/features/ui.d.ts +562 -0
  246. package/dist/node/features/ui.d.ts.map +1 -0
  247. package/dist/node/features/vault.d.ts +90 -0
  248. package/dist/node/features/vault.d.ts.map +1 -0
  249. package/dist/node/features/vm.d.ts +285 -0
  250. package/dist/node/features/vm.d.ts.map +1 -0
  251. package/dist/node/features/yaml-tree.d.ts +118 -0
  252. package/dist/node/features/yaml-tree.d.ts.map +1 -0
  253. package/dist/node/features/yaml.d.ts +127 -0
  254. package/dist/node/features/yaml.d.ts.map +1 -0
  255. package/dist/node.d.ts +67 -0
  256. package/dist/node.d.ts.map +1 -0
  257. package/dist/python/generated.d.ts +2 -0
  258. package/dist/python/generated.d.ts.map +1 -0
  259. package/dist/react/index.d.ts +36 -0
  260. package/dist/react/index.d.ts.map +1 -0
  261. package/dist/registry.d.ts +97 -0
  262. package/dist/registry.d.ts.map +1 -0
  263. package/dist/scaffolds/generated.d.ts +13 -0
  264. package/dist/scaffolds/generated.d.ts.map +1 -0
  265. package/dist/scaffolds/template.d.ts +11 -0
  266. package/dist/scaffolds/template.d.ts.map +1 -0
  267. package/dist/schemas/base.d.ts +254 -0
  268. package/dist/schemas/base.d.ts.map +1 -0
  269. package/dist/selector.d.ts +130 -0
  270. package/dist/selector.d.ts.map +1 -0
  271. package/dist/server.d.ts +89 -0
  272. package/dist/server.d.ts.map +1 -0
  273. package/dist/servers/express.d.ts +104 -0
  274. package/dist/servers/express.d.ts.map +1 -0
  275. package/dist/servers/mcp.d.ts +201 -0
  276. package/dist/servers/mcp.d.ts.map +1 -0
  277. package/dist/servers/socket.d.ts +121 -0
  278. package/dist/servers/socket.d.ts.map +1 -0
  279. package/dist/state.d.ts +24 -0
  280. package/dist/state.d.ts.map +1 -0
  281. package/dist/web/clients/socket.d.ts +37 -0
  282. package/dist/web/clients/socket.d.ts.map +1 -0
  283. package/dist/web/container.d.ts +55 -0
  284. package/dist/web/container.d.ts.map +1 -0
  285. package/dist/web/extension.d.ts +4 -0
  286. package/dist/web/extension.d.ts.map +1 -0
  287. package/dist/web/feature.d.ts +8 -0
  288. package/dist/web/feature.d.ts.map +1 -0
  289. package/dist/web/features/asset-loader.d.ts +35 -0
  290. package/dist/web/features/asset-loader.d.ts.map +1 -0
  291. package/dist/web/features/container-link.d.ts +167 -0
  292. package/dist/web/features/container-link.d.ts.map +1 -0
  293. package/dist/web/features/esbuild.d.ts +51 -0
  294. package/dist/web/features/esbuild.d.ts.map +1 -0
  295. package/dist/web/features/helpers.d.ts +140 -0
  296. package/dist/web/features/helpers.d.ts.map +1 -0
  297. package/dist/web/features/network.d.ts +69 -0
  298. package/dist/web/features/network.d.ts.map +1 -0
  299. package/dist/web/features/speech.d.ts +71 -0
  300. package/dist/web/features/speech.d.ts.map +1 -0
  301. package/dist/web/features/vault.d.ts +62 -0
  302. package/dist/web/features/vault.d.ts.map +1 -0
  303. package/dist/web/features/vm.d.ts +48 -0
  304. package/dist/web/features/vm.d.ts.map +1 -0
  305. package/dist/web/features/voice-recognition.d.ts +96 -0
  306. package/dist/web/features/voice-recognition.d.ts.map +1 -0
  307. package/dist/web/shims/isomorphic-vm.d.ts +22 -0
  308. package/dist/web/shims/isomorphic-vm.d.ts.map +1 -0
  309. package/index.html +1457 -0
  310. package/index.ts +1 -0
  311. package/install.sh +84 -0
  312. package/luca.cli.ts +16 -0
  313. package/luca.console.ts +9 -0
  314. package/main.py +6 -0
  315. package/package.json +219 -58
  316. package/public/index.html +1457 -0
  317. package/public/slides-ai-native.html +902 -0
  318. package/public/slides-intro.html +974 -0
  319. package/pyproject.toml +7 -0
  320. package/scripts/build-web.ts +28 -0
  321. package/scripts/examples/ask-luca-expert.ts +42 -0
  322. package/scripts/examples/assistant-questions.ts +12 -0
  323. package/scripts/examples/excalidraw-expert.ts +75 -0
  324. package/scripts/examples/expert-chat.ts +0 -0
  325. package/scripts/examples/file-manager.ts +14 -0
  326. package/scripts/examples/ideas.ts +12 -0
  327. package/scripts/examples/interactive-chat.ts +20 -0
  328. package/scripts/examples/openai-tool-calls.ts +113 -0
  329. package/scripts/examples/opening-a-web-browser.ts +5 -0
  330. package/scripts/examples/telegram-bot.ts +79 -0
  331. package/scripts/examples/using-assistant-with-mcp.ts +555 -0
  332. package/scripts/examples/using-claude-code.ts +10 -0
  333. package/scripts/examples/using-contentdb.ts +35 -0
  334. package/scripts/examples/using-conversations.ts +35 -0
  335. package/scripts/examples/using-disk-cache.ts +10 -0
  336. package/scripts/examples/using-docker-shell.ts +75 -0
  337. package/scripts/examples/using-elevenlabs.ts +25 -0
  338. package/scripts/examples/using-google-calendar.ts +57 -0
  339. package/scripts/examples/using-google-docs.ts +74 -0
  340. package/scripts/examples/using-google-drive.ts +74 -0
  341. package/scripts/examples/using-google-sheets.ts +89 -0
  342. package/scripts/examples/using-nlp.ts +55 -0
  343. package/scripts/examples/using-ollama.ts +11 -0
  344. package/scripts/examples/using-postgres.ts +55 -0
  345. package/scripts/examples/using-runpod.ts +32 -0
  346. package/scripts/examples/using-tts.ts +40 -0
  347. package/scripts/scaffold.ts +391 -0
  348. package/scripts/scratch.ts +15 -0
  349. package/scripts/stamp-build.sh +12 -0
  350. package/scripts/test-assistant-hooks.ts +13 -0
  351. package/scripts/test-docs-reader.ts +10 -0
  352. package/scripts/test-linux-binary.sh +80 -0
  353. package/scripts/update-introspection-data.ts +58 -0
  354. package/src/agi/README.md +14 -0
  355. package/src/agi/container.server.ts +156 -0
  356. package/src/agi/feature.ts +13 -0
  357. package/src/agi/features/agent-memory.ts +694 -0
  358. package/src/agi/features/assistant.ts +1653 -0
  359. package/src/agi/features/assistants-manager.ts +534 -0
  360. package/src/agi/features/autonomous-assistant.ts +431 -0
  361. package/src/agi/features/browser-use.ts +672 -0
  362. package/src/agi/features/claude-code.ts +1584 -0
  363. package/src/agi/features/coding-tools.ts +175 -0
  364. package/src/agi/features/conversation-history.ts +672 -0
  365. package/src/agi/features/conversation.ts +1494 -0
  366. package/src/agi/features/docs-reader.ts +167 -0
  367. package/src/agi/features/file-tools.ts +340 -0
  368. package/src/agi/features/luca-coder.ts +641 -0
  369. package/src/agi/features/mcp-bridge.ts +532 -0
  370. package/src/agi/features/openai-codex.ts +651 -0
  371. package/src/agi/features/openapi.ts +445 -0
  372. package/src/agi/features/skills-library.ts +557 -0
  373. package/src/agi/index.ts +6 -0
  374. package/src/agi/lib/interceptor-chain.ts +89 -0
  375. package/src/agi/lib/token-counter.ts +202 -0
  376. package/src/bootstrap/generated.ts +9791 -0
  377. package/src/browser.ts +25 -0
  378. package/src/bus.ts +122 -0
  379. package/src/cli/build-info.ts +4 -0
  380. package/src/cli/cli.ts +355 -0
  381. package/src/client.ts +170 -0
  382. package/src/clients/civitai/index.ts +537 -0
  383. package/src/clients/client-template.ts +41 -0
  384. package/src/clients/comfyui/index.ts +604 -0
  385. package/src/clients/elevenlabs/index.ts +317 -0
  386. package/src/clients/graph.ts +87 -0
  387. package/src/clients/openai/index.ts +456 -0
  388. package/src/clients/rest.ts +207 -0
  389. package/src/clients/supabase/index.ts +357 -0
  390. package/src/clients/voicebox/index.ts +300 -0
  391. package/src/clients/websocket.ts +251 -0
  392. package/src/command.ts +506 -0
  393. package/src/commands/bootstrap.ts +244 -0
  394. package/src/commands/chat.ts +309 -0
  395. package/src/commands/code.ts +371 -0
  396. package/src/commands/console.ts +189 -0
  397. package/src/commands/describe.ts +243 -0
  398. package/src/commands/eval.ts +67 -0
  399. package/src/commands/help.ts +240 -0
  400. package/src/commands/index.ts +19 -0
  401. package/src/commands/introspect.ts +218 -0
  402. package/src/commands/mcp.ts +64 -0
  403. package/src/commands/prompt.ts +1014 -0
  404. package/src/commands/run.ts +278 -0
  405. package/src/commands/sandbox-mcp.ts +343 -0
  406. package/src/commands/save-api-docs.ts +51 -0
  407. package/src/commands/scaffold.ts +225 -0
  408. package/src/commands/select.ts +99 -0
  409. package/src/commands/serve.ts +208 -0
  410. package/src/container-describer.ts +1091 -0
  411. package/src/container.ts +1199 -0
  412. package/src/endpoint.ts +365 -0
  413. package/src/entity.ts +173 -0
  414. package/src/feature.ts +118 -0
  415. package/src/graft.ts +181 -0
  416. package/src/hash-object.ts +97 -0
  417. package/src/helper.ts +849 -0
  418. package/src/introspection/generated.agi.ts +41200 -0
  419. package/src/introspection/generated.node.ts +28773 -0
  420. package/src/introspection/generated.web.ts +2272 -0
  421. package/src/introspection/index.ts +296 -0
  422. package/src/introspection/scan.ts +1136 -0
  423. package/src/node/container.ts +409 -0
  424. package/src/node/feature.ts +13 -0
  425. package/src/node/features/container-link.ts +559 -0
  426. package/src/node/features/content-db.ts +849 -0
  427. package/src/node/features/disk-cache.ts +388 -0
  428. package/src/node/features/display-result.ts +57 -0
  429. package/src/node/features/dns.ts +669 -0
  430. package/src/node/features/docker.ts +921 -0
  431. package/src/node/features/downloader.ts +79 -0
  432. package/src/node/features/figlet-fonts.ts +600 -0
  433. package/src/node/features/file-manager.ts +535 -0
  434. package/src/node/features/fs.ts +1050 -0
  435. package/src/node/features/git.ts +592 -0
  436. package/src/node/features/google-auth.ts +504 -0
  437. package/src/node/features/google-calendar.ts +306 -0
  438. package/src/node/features/google-docs.ts +412 -0
  439. package/src/node/features/google-drive.ts +346 -0
  440. package/src/node/features/google-mail.ts +540 -0
  441. package/src/node/features/google-sheets.ts +286 -0
  442. package/src/node/features/grep.ts +427 -0
  443. package/src/node/features/helpers.ts +762 -0
  444. package/src/node/features/ink.ts +490 -0
  445. package/src/node/features/ipc-socket.ts +649 -0
  446. package/src/node/features/json-tree.ts +170 -0
  447. package/src/node/features/networking.ts +961 -0
  448. package/src/node/features/nlp.ts +212 -0
  449. package/src/node/features/opener.ts +180 -0
  450. package/src/node/features/os.ts +403 -0
  451. package/src/node/features/package-finder.ts +540 -0
  452. package/src/node/features/postgres.ts +289 -0
  453. package/src/node/features/proc.ts +503 -0
  454. package/src/node/features/process-manager.ts +844 -0
  455. package/src/node/features/python.ts +912 -0
  456. package/src/node/features/redis.ts +446 -0
  457. package/src/node/features/repl.ts +212 -0
  458. package/src/node/features/runpod.ts +811 -0
  459. package/src/node/features/secure-shell.ts +261 -0
  460. package/src/node/features/semantic-search.ts +935 -0
  461. package/src/node/features/sqlite.ts +289 -0
  462. package/src/node/features/telegram.ts +343 -0
  463. package/src/node/features/transpiler.ts +160 -0
  464. package/src/node/features/tts.ts +185 -0
  465. package/src/node/features/ui.ts +791 -0
  466. package/src/node/features/vault.ts +153 -0
  467. package/src/node/features/vm.ts +462 -0
  468. package/src/node/features/yaml-tree.ts +148 -0
  469. package/src/node/features/yaml.ts +133 -0
  470. package/src/node.ts +76 -0
  471. package/src/python/bridge.py +220 -0
  472. package/src/python/generated.ts +226 -0
  473. package/src/react/index.ts +175 -0
  474. package/src/registry.ts +210 -0
  475. package/src/scaffolds/generated.ts +1814 -0
  476. package/src/scaffolds/template.ts +46 -0
  477. package/src/schemas/base.ts +296 -0
  478. package/src/selector.ts +352 -0
  479. package/src/server.ts +229 -0
  480. package/src/servers/express.ts +283 -0
  481. package/src/servers/mcp.ts +802 -0
  482. package/src/servers/socket.ts +258 -0
  483. package/src/state.ts +101 -0
  484. package/src/web/clients/socket.ts +99 -0
  485. package/src/web/container.ts +75 -0
  486. package/src/web/extension.ts +30 -0
  487. package/src/web/feature.ts +12 -0
  488. package/src/web/features/asset-loader.ts +72 -0
  489. package/src/web/features/container-link.ts +382 -0
  490. package/src/web/features/esbuild.ts +93 -0
  491. package/src/web/features/helpers.ts +291 -0
  492. package/src/web/features/network.ts +85 -0
  493. package/src/web/features/speech.ts +104 -0
  494. package/src/web/features/vault.ts +207 -0
  495. package/src/web/features/vm.ts +85 -0
  496. package/src/web/features/voice-recognition.ts +161 -0
  497. package/src/web/shims/isomorphic-vm.ts +149 -0
  498. package/tsconfig.build.json +12 -0
  499. package/tsconfig.json +58 -0
  500. package/uv.lock +8 -0
  501. package/LICENSE +0 -21
  502. package/dist/cli/cli.js +0 -48
  503. package/dist/cli/common.d.ts +0 -2
  504. package/dist/cli/common.js +0 -6
  505. package/dist/cli/index.d.ts +0 -2
  506. package/dist/cli/index.js +0 -5
  507. package/dist/cli/run.d.ts +0 -1
  508. package/dist/cli/run.js +0 -38
  509. package/dist/core/index.d.ts +0 -4
  510. package/dist/core/index.js +0 -32
  511. package/dist/core/read.d.ts +0 -2
  512. package/dist/core/read.js +0 -29
  513. package/dist/core/request.d.ts +0 -1
  514. package/dist/core/request.js +0 -2
  515. package/dist/core/write.d.ts +0 -2
  516. package/dist/core/write.js +0 -21
  517. package/dist/index.d.ts +0 -1
  518. package/dist/index.js +0 -5
  519. package/dist/utils/common.d.ts +0 -9
  520. package/dist/utils/common.js +0 -57
  521. package/dist/utils/consts.d.ts +0 -3
  522. package/dist/utils/consts.js +0 -11
  523. package/dist/utils/dict.d.ts +0 -1
  524. package/dist/utils/dict.js +0 -7
  525. package/dist/utils/index.d.ts +0 -5
  526. package/dist/utils/index.js +0 -21
  527. package/dist/utils/log.d.ts +0 -1
  528. package/dist/utils/log.js +0 -5
  529. package/dist/utils/types.d.ts +0 -1
  530. package/dist/utils/types.js +0 -2
  531. package/dist/utils/utils.test.d.ts +0 -1
  532. package/dist/utils/utils.test.js +0 -7
@@ -0,0 +1,672 @@
1
+ import { z } from 'zod'
2
+ import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
3
+ import { type AvailableFeatures } from 'luca/feature'
4
+ import { Feature } from '../feature.js'
5
+ import type { DiskCache } from 'luca/node/container'
6
+ import type { OpenAIClient } from '../../clients/openai'
7
+ import type { Message } from './conversation'
8
+
9
+ declare module 'luca/feature' {
10
+ interface AvailableFeatures {
11
+ conversationHistory: typeof ConversationHistory
12
+ }
13
+ }
14
+
15
+ export interface TokenUsage {
16
+ prompt: number
17
+ completion: number
18
+ total: number
19
+ cachedTokens?: number
20
+ reasoningTokens?: number
21
+ }
22
+
23
+ export interface CostInfo {
24
+ inputCost: number
25
+ outputCost: number
26
+ totalCost: number
27
+ }
28
+
29
+ export interface ConversationRecord {
30
+ id: string
31
+ title: string
32
+ model: string
33
+ messages: Message[]
34
+ tags: string[]
35
+ thread: string
36
+ createdAt: string
37
+ updatedAt: string
38
+ messageCount: number
39
+ tokenUsage?: TokenUsage
40
+ cost?: CostInfo
41
+ metadata: Record<string, any>
42
+ }
43
+
44
+ export type ConversationMeta = Omit<ConversationRecord, 'messages'>
45
+
46
+ export interface SearchOptions {
47
+ tag?: string
48
+ tags?: string[]
49
+ thread?: string
50
+ model?: string
51
+ before?: string | Date
52
+ after?: string | Date
53
+ query?: string
54
+ limit?: number
55
+ offset?: number
56
+ }
57
+
58
+ export const ConversationHistoryOptionsSchema = FeatureOptionsSchema.extend({
59
+ cachePath: z.string().optional().describe('Custom cache directory for conversation storage'),
60
+ namespace: z.string().optional().describe('Namespace prefix for cache keys to isolate datasets'),
61
+ })
62
+
63
+ export const ConversationHistoryStateSchema = FeatureStateSchema.extend({
64
+ conversationCount: z.number().describe('Total number of stored conversations'),
65
+ lastSaved: z.string().optional().describe('ISO timestamp of the last save operation'),
66
+ })
67
+
68
+ export const ConversationHistoryEventsSchema = FeatureEventsSchema.extend({
69
+ saved: z.tuple([z.string().describe('The conversation ID that was saved')]).describe('Fired after a conversation record is persisted'),
70
+ deleted: z.tuple([z.string().describe('The conversation ID that was deleted')]).describe('Fired after a conversation record is deleted'),
71
+ }).describe('ConversationHistory events')
72
+
73
+ export type ConversationHistoryOptions = z.infer<typeof ConversationHistoryOptionsSchema>
74
+ export type ConversationHistoryState = z.infer<typeof ConversationHistoryStateSchema>
75
+
76
+ /**
77
+ * Persists conversations to disk using the diskCache feature (cacache).
78
+ * Each conversation is stored as a JSON blob keyed by ID, with metadata
79
+ * stored alongside for efficient listing and search without loading full message arrays.
80
+ *
81
+ * @extends Feature
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * const history = container.feature('conversationHistory', {
86
+ * namespace: 'my-app',
87
+ * cachePath: '/tmp/conversations'
88
+ * })
89
+ *
90
+ * // Create and retrieve conversations
91
+ * const record = await history.create({ messages, title: 'My Chat' })
92
+ * const loaded = await history.load(record.id)
93
+ *
94
+ * // Search and filter
95
+ * const results = await history.search({ tag: 'important', limit: 10 })
96
+ * ```
97
+ */
98
+ export class ConversationHistory extends Feature<ConversationHistoryState, ConversationHistoryOptions> {
99
+ static override stateSchema = ConversationHistoryStateSchema
100
+ static override optionsSchema = ConversationHistoryOptionsSchema
101
+ static override eventsSchema = ConversationHistoryEventsSchema
102
+ static override shortcut = 'features.conversationHistory' as const
103
+
104
+ static { Feature.register(this, 'conversationHistory') }
105
+
106
+ /** @returns Default state with zero conversations and no last-saved timestamp. */
107
+ override get initialState(): ConversationHistoryState {
108
+ return {
109
+ ...super.initialState,
110
+ conversationCount: 0,
111
+ lastSaved: undefined,
112
+ }
113
+ }
114
+
115
+
116
+ /** @returns The diskCache feature instance used for persistence, configured with the optional cachePath. */
117
+ get diskCache(): DiskCache {
118
+ const opts: Record<string, any> = {}
119
+ if (this.options.cachePath) {
120
+ opts.path = this.options.cachePath
121
+ }
122
+ return this.container.feature('diskCache', opts) as DiskCache
123
+ }
124
+
125
+ /** @returns The namespace prefix used for all cache keys, defaults to 'conversation-history'. */
126
+ get namespace(): string {
127
+ return this.options.namespace || 'conversation-history'
128
+ }
129
+
130
+ private buildCacheKey(id: string): string {
131
+ return `${this.namespace}:${id}`
132
+ }
133
+
134
+ private metaKey(id: string): string {
135
+ return `${this.namespace}:meta:${id}`
136
+ }
137
+
138
+ private indexKey(): string {
139
+ return `${this.namespace}:__index__`
140
+ }
141
+
142
+ /**
143
+ * Save a conversation. Creates or overwrites by ID.
144
+ *
145
+ * @param {ConversationRecord} record - The full conversation record to persist
146
+ * @returns {Promise<void>}
147
+ */
148
+ async save(record: ConversationRecord): Promise<void> {
149
+ record.updatedAt = new Date().toISOString()
150
+ record.messageCount = record.messages.length
151
+
152
+ // store the full conversation (messages included)
153
+ await this.diskCache.set(this.buildCacheKey(record.id), record)
154
+
155
+ // store lightweight metadata separately for fast listing
156
+ const meta: ConversationMeta = { ...record }
157
+ delete (meta as any).messages
158
+ await this.diskCache.set(this.metaKey(record.id), meta)
159
+
160
+ // update the index
161
+ await this.addToIndex(record.id)
162
+
163
+ this.state.set('lastSaved', record.updatedAt)
164
+ this.emit('saved', record.id)
165
+ }
166
+
167
+ /**
168
+ * Create a new conversation from messages, returning the saved record.
169
+ *
170
+ * @param {object} opts - Creation options including messages, optional title, model, tags, thread, and metadata
171
+ * @returns {Promise<ConversationRecord>} The newly created and persisted conversation record
172
+ */
173
+ async create(opts: {
174
+ id?: string
175
+ title?: string
176
+ model?: string
177
+ messages: Message[]
178
+ tags?: string[]
179
+ thread?: string
180
+ tokenUsage?: TokenUsage
181
+ cost?: CostInfo
182
+ metadata?: Record<string, any>
183
+ }): Promise<ConversationRecord> {
184
+ const now = new Date().toISOString()
185
+ const title = opts.title || await this.autoTitle(opts.messages)
186
+ const record: ConversationRecord = {
187
+ id: opts.id || crypto.randomUUID(),
188
+ title,
189
+ model: opts.model || 'unknown',
190
+ messages: opts.messages,
191
+ tags: opts.tags || [],
192
+ thread: opts.thread || 'default',
193
+ createdAt: now,
194
+ updatedAt: now,
195
+ messageCount: opts.messages.length,
196
+ tokenUsage: opts.tokenUsage,
197
+ cost: opts.cost,
198
+ metadata: opts.metadata || {},
199
+ }
200
+
201
+ await this.save(record)
202
+ return record
203
+ }
204
+
205
+ /**
206
+ * Load a full conversation by ID, including all messages.
207
+ *
208
+ * @param {string} id - The conversation ID
209
+ * @returns {Promise<ConversationRecord | null>} The full record, or null if not found
210
+ */
211
+ async load(id: string): Promise<ConversationRecord | null> {
212
+ const exists = await this.diskCache.has(this.buildCacheKey(id))
213
+ if (!exists) return null
214
+ return this.diskCache.get(this.buildCacheKey(id), true)
215
+ }
216
+
217
+ /**
218
+ * Load just the metadata for a conversation (no messages).
219
+ *
220
+ * @param {string} id - The conversation ID
221
+ * @returns {Promise<ConversationMeta | null>} The lightweight metadata record, or null if not found
222
+ */
223
+ async getMeta(id: string): Promise<ConversationMeta | null> {
224
+ const exists = await this.diskCache.has(this.metaKey(id))
225
+ if (!exists) return null
226
+ return this.diskCache.get(this.metaKey(id), true)
227
+ }
228
+
229
+ /**
230
+ * Append messages to an existing conversation.
231
+ *
232
+ * @param {string} id - The conversation ID to append to
233
+ * @param {Message[]} messages - The messages to append
234
+ * @returns {Promise<ConversationRecord | null>} The updated record, or null if the conversation was not found
235
+ */
236
+ async append(id: string, messages: Message[]): Promise<ConversationRecord | null> {
237
+ const record = await this.load(id)
238
+ if (!record) return null
239
+
240
+ record.messages.push(...messages)
241
+ await this.save(record)
242
+ return record
243
+ }
244
+
245
+ /**
246
+ * Delete a conversation by ID.
247
+ *
248
+ * @param {string} id - The conversation ID to delete
249
+ * @returns {Promise<boolean>} True if the conversation existed and was deleted
250
+ */
251
+ async delete(id: string): Promise<boolean> {
252
+ const exists = await this.diskCache.has(this.buildCacheKey(id))
253
+ if (!exists) return false
254
+
255
+ await this.diskCache.rm(this.buildCacheKey(id))
256
+ await this.diskCache.rm(this.metaKey(id)).catch(() => {})
257
+ await this.removeFromIndex(id)
258
+
259
+ this.emit('deleted', id)
260
+ return true
261
+ }
262
+
263
+ /**
264
+ * List all conversation metadata, with optional search/filter.
265
+ * Loads only the lightweight meta records, never the full messages.
266
+ *
267
+ * @param {SearchOptions} [options] - Optional filters for tag, thread, model, date range, and text query
268
+ * @returns {Promise<ConversationMeta[]>} Filtered and sorted metadata records (newest first)
269
+ */
270
+ async list(options?: SearchOptions): Promise<ConversationMeta[]> {
271
+ const ids = await this.getIndex()
272
+ const metas: ConversationMeta[] = []
273
+
274
+ for (const id of ids) {
275
+ const meta = await this.getMeta(id)
276
+ if (meta) metas.push(meta)
277
+ }
278
+
279
+ return this.applyFilters(metas, options)
280
+ }
281
+
282
+ /**
283
+ * Search conversations by text query across titles, tags, and metadata.
284
+ * Also supports filtering by tag, thread, model, and date range.
285
+ *
286
+ * @param {SearchOptions} options - Search and filter criteria
287
+ * @returns {Promise<ConversationMeta[]>} Matching metadata records (newest first)
288
+ */
289
+ async search(options: SearchOptions): Promise<ConversationMeta[]> {
290
+ return this.list(options)
291
+ }
292
+
293
+ /**
294
+ * Get all unique tags across all conversations.
295
+ *
296
+ * @returns {Promise<string[]>} Sorted array of unique tag strings
297
+ */
298
+ async allTags(): Promise<string[]> {
299
+ const metas = await this.list()
300
+ const tags = new Set<string>()
301
+ for (const meta of metas) {
302
+ for (const tag of meta.tags) {
303
+ tags.add(tag)
304
+ }
305
+ }
306
+ return [...tags].sort()
307
+ }
308
+
309
+ /**
310
+ * Get all unique threads across all conversations.
311
+ *
312
+ * @returns {Promise<string[]>} Sorted array of unique thread identifiers
313
+ */
314
+ async allThreads(): Promise<string[]> {
315
+ const metas = await this.list()
316
+ const threads = new Set<string>()
317
+ for (const meta of metas) {
318
+ threads.add(meta.thread)
319
+ }
320
+ return [...threads].sort()
321
+ }
322
+
323
+ /**
324
+ * Tag a conversation. Adds tags without duplicates.
325
+ *
326
+ * @param {string} id - The conversation ID
327
+ * @param {...string} tags - One or more tags to add
328
+ * @returns {Promise<boolean>} True if the conversation was found and updated
329
+ */
330
+ async tag(id: string, ...tags: string[]): Promise<boolean> {
331
+ const record = await this.load(id)
332
+ if (!record) return false
333
+
334
+ const tagSet = new Set(record.tags)
335
+ for (const t of tags) tagSet.add(t)
336
+ record.tags = [...tagSet]
337
+
338
+ await this.save(record)
339
+ return true
340
+ }
341
+
342
+ /**
343
+ * Remove tags from a conversation.
344
+ *
345
+ * @param {string} id - The conversation ID
346
+ * @param {...string} tags - One or more tags to remove
347
+ * @returns {Promise<boolean>} True if the conversation was found and updated
348
+ */
349
+ async untag(id: string, ...tags: string[]): Promise<boolean> {
350
+ const record = await this.load(id)
351
+ if (!record) return false
352
+
353
+ const removeSet = new Set(tags)
354
+ record.tags = record.tags.filter(t => !removeSet.has(t))
355
+
356
+ await this.save(record)
357
+ return true
358
+ }
359
+
360
+ /**
361
+ * Update metadata on a conversation without touching messages.
362
+ *
363
+ * @param {string} id - The conversation ID
364
+ * @param {object} updates - Partial updates for title, tags, thread, and/or metadata
365
+ * @returns {Promise<boolean>} True if the conversation was found and updated
366
+ */
367
+ async updateMeta(id: string, updates: Partial<Pick<ConversationRecord, 'title' | 'tags' | 'thread' | 'metadata'>>): Promise<boolean> {
368
+ const record = await this.load(id)
369
+ if (!record) return false
370
+
371
+ if (updates.title !== undefined) record.title = updates.title
372
+ if (updates.tags !== undefined) record.tags = updates.tags
373
+ if (updates.thread !== undefined) record.thread = updates.thread
374
+ if (updates.metadata !== undefined) record.metadata = { ...record.metadata, ...updates.metadata }
375
+
376
+ await this.save(record)
377
+ return true
378
+ }
379
+
380
+ /**
381
+ * Find the most recent conversation for an exact thread ID.
382
+ *
383
+ * @param {string} thread - The exact thread ID to match
384
+ * @returns {Promise<ConversationRecord | null>} The full record with messages, or null if none found
385
+ */
386
+ async findByThread(thread: string): Promise<ConversationRecord | null> {
387
+ const metas = await this.list({ thread })
388
+ if (!metas.length) return null
389
+ return this.load(metas[0]!.id)
390
+ }
391
+
392
+ /**
393
+ * Find all conversations whose thread starts with a prefix.
394
+ *
395
+ * @param {string} prefix - The thread prefix to match
396
+ * @returns {Promise<ConversationMeta[]>} Matching metadata records (newest first)
397
+ */
398
+ async findByThreadPrefix(prefix: string): Promise<ConversationMeta[]> {
399
+ const all = await this.list()
400
+ return all.filter(m => m.thread.startsWith(prefix))
401
+ }
402
+
403
+ /**
404
+ * Delete all conversations for an exact thread.
405
+ *
406
+ * @param {string} thread - The exact thread ID
407
+ * @returns {Promise<number>} Number of conversations deleted
408
+ */
409
+ async deleteThread(thread: string): Promise<number> {
410
+ const metas = await this.list({ thread })
411
+ let count = 0
412
+ for (const meta of metas) {
413
+ if (await this.delete(meta.id)) count++
414
+ }
415
+ return count
416
+ }
417
+
418
+ /**
419
+ * Delete all conversations matching a thread prefix.
420
+ *
421
+ * @param {string} prefix - The thread prefix to match
422
+ * @returns {Promise<number>} Number of conversations deleted
423
+ */
424
+ async deleteByThreadPrefix(prefix: string): Promise<number> {
425
+ const metas = await this.findByThreadPrefix(prefix)
426
+ let count = 0
427
+ for (const meta of metas) {
428
+ if (await this.delete(meta.id)) count++
429
+ }
430
+ return count
431
+ }
432
+
433
+ /** @returns An OpenAI client from the container for LLM calls. */
434
+ private get openai(): OpenAIClient {
435
+ return (this.container as any).client('openai') as OpenAIClient
436
+ }
437
+
438
+ /**
439
+ * Generate a short title from conversation messages. Uses the first user
440
+ * message (and optionally the first assistant reply) to produce a concise
441
+ * title via a cheap LLM call. Falls back to a truncated first message
442
+ * if the LLM call fails.
443
+ */
444
+ private async autoTitle(messages: Message[]): Promise<string> {
445
+ const userMsg = messages.find(m => m.role === 'user')
446
+ if (!userMsg) return `Conversation ${new Date().toISOString().slice(0, 16)}`
447
+
448
+ const userContent = typeof userMsg.content === 'string'
449
+ ? userMsg.content
450
+ : Array.isArray(userMsg.content)
451
+ ? (userMsg.content as any[]).filter((p: any) => p.type === 'text').map((p: any) => p.text).join(' ')
452
+ : ''
453
+
454
+ if (!userContent.trim()) return `Conversation ${new Date().toISOString().slice(0, 16)}`
455
+
456
+ // Build a small context snippet: first user message + first assistant reply if available
457
+ const assistantMsg = messages.find(m => m.role === 'assistant')
458
+ let context = `User: ${userContent}`
459
+ if (assistantMsg) {
460
+ const assistantContent = typeof assistantMsg.content === 'string'
461
+ ? assistantMsg.content
462
+ : Array.isArray(assistantMsg.content)
463
+ ? (assistantMsg.content as any[]).filter((p: any) => p.type === 'text').map((p: any) => p.text).join(' ')
464
+ : ''
465
+ if (assistantContent.trim()) {
466
+ context += `\nAssistant: ${assistantContent.slice(0, 300)}`
467
+ }
468
+ }
469
+
470
+ try {
471
+ const response = await this.openai.raw.chat.completions.create({
472
+ model: 'gpt-4o-mini',
473
+ messages: [
474
+ {
475
+ role: 'system',
476
+ content: 'Generate a short title (max 8 words) for this conversation. Output only the title, no quotes or punctuation wrapping.',
477
+ },
478
+ { role: 'user', content: context.slice(0, 1000) },
479
+ ],
480
+ max_tokens: 30,
481
+ temperature: 0.3,
482
+ stream: false,
483
+ })
484
+
485
+ const title = (response as any).choices?.[0]?.message?.content?.trim()
486
+ if (title) return title
487
+ } catch {
488
+ // LLM unavailable — fall back to truncation
489
+ }
490
+
491
+ // Fallback: truncate the first user message
492
+ return userContent.length > 60 ? userContent.slice(0, 57) + '...' : userContent
493
+ }
494
+
495
+ /**
496
+ * Build a plain-text transcript from a messages array,
497
+ * suitable for feeding to a summarizer or title generator.
498
+ */
499
+ private buildTranscript(messages: Message[]): string {
500
+ return messages
501
+ .map(m => {
502
+ const role = m.role
503
+ const content = typeof m.content === 'string'
504
+ ? m.content
505
+ : Array.isArray(m.content)
506
+ ? (m.content as any[]).filter((p: any) => p.type === 'text').map((p: any) => p.text).join('\n')
507
+ : (m.content != null ? JSON.stringify(m.content) : '(no content)')
508
+ return `[${role}]: ${content || '(no text content)'}`
509
+ })
510
+ .join('\n\n')
511
+ }
512
+
513
+ /**
514
+ * Generate a concise summary of a stored conversation using the LLM.
515
+ * The summary is stored in `metadata.summary` and returned.
516
+ * No tool calls are made — this is a single completion request.
517
+ *
518
+ * @param {string} id - The conversation ID to summarize
519
+ * @param {object} [options] - Optional settings
520
+ * @param {string} [options.model] - Override the model used for summarization
521
+ * @returns {Promise<string | null>} The generated summary, or null if conversation not found
522
+ */
523
+ async summarize(id: string, options?: { model?: string }): Promise<string | null> {
524
+ const record = await this.load(id)
525
+ if (!record) return null
526
+
527
+ const transcript = this.buildTranscript(record.messages)
528
+ const model = options?.model || record.model || 'gpt-5'
529
+
530
+ const response = await this.openai.raw.chat.completions.create({
531
+ model,
532
+ messages: [
533
+ {
534
+ role: 'system',
535
+ content: 'You are a conversation summarizer. Produce a concise but comprehensive summary of the following conversation. Preserve all key facts, decisions, context, user preferences, and any important details needed to understand what was discussed. Output only the summary.',
536
+ },
537
+ { role: 'user', content: transcript },
538
+ ],
539
+ stream: false,
540
+ })
541
+
542
+ const summary = (response as any).choices?.[0]?.message?.content || ''
543
+
544
+ record.metadata = { ...record.metadata, summary }
545
+ await this.save(record)
546
+
547
+ return summary
548
+ }
549
+
550
+ /**
551
+ * Generate a short, descriptive title for a stored conversation using the LLM.
552
+ * The title is stored both as the record's `title` field and in `metadata.generatedTitle`,
553
+ * then returned. No tool calls are made.
554
+ *
555
+ * @param {string} id - The conversation ID to generate a title for
556
+ * @param {object} [options] - Optional settings
557
+ * @param {string} [options.model] - Override the model used for title generation
558
+ * @returns {Promise<string | null>} The generated title, or null if conversation not found
559
+ */
560
+ async generateTitle(id: string, options?: { model?: string }): Promise<string | null> {
561
+ const record = await this.load(id)
562
+ if (!record) return null
563
+
564
+ const transcript = this.buildTranscript(record.messages)
565
+ const model = options?.model || record.model || 'gpt-5'
566
+
567
+ const response = await this.openai.raw.chat.completions.create({
568
+ model,
569
+ messages: [
570
+ {
571
+ role: 'system',
572
+ content: 'Generate a short, descriptive title (under 60 characters) for the following conversation. The title should capture the main topic or purpose. Output only the title text, with no quotes or punctuation wrapping it.',
573
+ },
574
+ { role: 'user', content: transcript },
575
+ ],
576
+ stream: false,
577
+ })
578
+
579
+ const title = ((response as any).choices?.[0]?.message?.content || '').trim()
580
+
581
+ record.title = title
582
+ record.metadata = { ...record.metadata, generatedTitle: title }
583
+ await this.save(record)
584
+
585
+ return title
586
+ }
587
+
588
+ // -- index management --
589
+
590
+ private async getIndex(): Promise<string[]> {
591
+ const exists = await this.diskCache.has(this.indexKey())
592
+ if (!exists) return []
593
+ return this.diskCache.get(this.indexKey(), true)
594
+ }
595
+
596
+ private async setIndex(ids: string[]): Promise<void> {
597
+ await this.diskCache.set(this.indexKey(), ids)
598
+ this.state.set('conversationCount', ids.length)
599
+ }
600
+
601
+ private async addToIndex(id: string): Promise<void> {
602
+ const ids = await this.getIndex()
603
+ if (!ids.includes(id)) {
604
+ ids.push(id)
605
+ await this.setIndex(ids)
606
+ }
607
+ }
608
+
609
+ private async removeFromIndex(id: string): Promise<void> {
610
+ const ids = await this.getIndex()
611
+ await this.setIndex(ids.filter(i => i !== id))
612
+ }
613
+
614
+ // -- filtering --
615
+
616
+ private applyFilters(metas: ConversationMeta[], options?: SearchOptions): ConversationMeta[] {
617
+ if (!options) return metas
618
+
619
+ let results = metas
620
+
621
+ if (options.tag) {
622
+ results = results.filter(m => m.tags.includes(options.tag!))
623
+ }
624
+
625
+ if (options.tags && options.tags.length) {
626
+ results = results.filter(m => options.tags!.every(t => m.tags.includes(t)))
627
+ }
628
+
629
+ if (options.thread) {
630
+ results = results.filter(m => m.thread === options.thread)
631
+ }
632
+
633
+ if (options.model) {
634
+ results = results.filter(m => m.model === options.model)
635
+ }
636
+
637
+ if (options.after) {
638
+ const after = new Date(options.after).getTime()
639
+ results = results.filter(m => new Date(m.createdAt).getTime() >= after)
640
+ }
641
+
642
+ if (options.before) {
643
+ const before = new Date(options.before).getTime()
644
+ results = results.filter(m => new Date(m.createdAt).getTime() <= before)
645
+ }
646
+
647
+ if (options.query) {
648
+ const q = options.query.toLowerCase()
649
+ results = results.filter(m =>
650
+ m.title.toLowerCase().includes(q) ||
651
+ m.tags.some(t => t.toLowerCase().includes(q)) ||
652
+ m.thread.toLowerCase().includes(q) ||
653
+ JSON.stringify(m.metadata).toLowerCase().includes(q)
654
+ )
655
+ }
656
+
657
+ // sort newest first
658
+ results.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())
659
+
660
+ if (options.offset) {
661
+ results = results.slice(options.offset)
662
+ }
663
+
664
+ if (options.limit) {
665
+ results = results.slice(0, options.limit)
666
+ }
667
+
668
+ return results
669
+ }
670
+ }
671
+
672
+ export default ConversationHistory