@soederpop/luca 0.1.1 → 0.1.3

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 (369) hide show
  1. package/CLAUDE.md +2 -0
  2. package/assistants/codingAssistant/hooks.ts +3 -0
  3. package/assistants/inkbot/CORE.md +69 -0
  4. package/assistants/inkbot/hooks.ts +14 -0
  5. package/assistants/inkbot/tools.ts +47 -0
  6. package/commands/inkbot.ts +353 -0
  7. package/dist/agi/container.server.d.ts +63 -0
  8. package/dist/agi/container.server.d.ts.map +1 -0
  9. package/dist/agi/endpoints/ask.d.ts +20 -0
  10. package/dist/agi/endpoints/ask.d.ts.map +1 -0
  11. package/dist/agi/endpoints/conversations/[id].d.ts +27 -0
  12. package/dist/agi/endpoints/conversations/[id].d.ts.map +1 -0
  13. package/dist/agi/endpoints/conversations.d.ts +18 -0
  14. package/dist/agi/endpoints/conversations.d.ts.map +1 -0
  15. package/dist/agi/endpoints/experts.d.ts +8 -0
  16. package/dist/agi/endpoints/experts.d.ts.map +1 -0
  17. package/dist/agi/feature.d.ts +9 -0
  18. package/dist/agi/feature.d.ts.map +1 -0
  19. package/dist/agi/features/assistant.d.ts +509 -0
  20. package/dist/agi/features/assistant.d.ts.map +1 -0
  21. package/dist/agi/features/assistants-manager.d.ts +236 -0
  22. package/dist/agi/features/assistants-manager.d.ts.map +1 -0
  23. package/dist/agi/features/autonomous-assistant.d.ts +281 -0
  24. package/dist/agi/features/autonomous-assistant.d.ts.map +1 -0
  25. package/dist/agi/features/browser-use.d.ts +479 -0
  26. package/dist/agi/features/browser-use.d.ts.map +1 -0
  27. package/dist/agi/features/claude-code.d.ts +824 -0
  28. package/dist/agi/features/claude-code.d.ts.map +1 -0
  29. package/dist/agi/features/conversation-history.d.ts +245 -0
  30. package/dist/agi/features/conversation-history.d.ts.map +1 -0
  31. package/dist/agi/features/conversation.d.ts +464 -0
  32. package/dist/agi/features/conversation.d.ts.map +1 -0
  33. package/dist/agi/features/docs-reader.d.ts +72 -0
  34. package/dist/agi/features/docs-reader.d.ts.map +1 -0
  35. package/dist/agi/features/file-tools.d.ts +110 -0
  36. package/dist/agi/features/file-tools.d.ts.map +1 -0
  37. package/dist/agi/features/luca-coder.d.ts +323 -0
  38. package/dist/agi/features/luca-coder.d.ts.map +1 -0
  39. package/dist/agi/features/openai-codex.d.ts +381 -0
  40. package/dist/agi/features/openai-codex.d.ts.map +1 -0
  41. package/dist/agi/features/openapi.d.ts +200 -0
  42. package/dist/agi/features/openapi.d.ts.map +1 -0
  43. package/dist/agi/features/skills-library.d.ts +167 -0
  44. package/dist/agi/features/skills-library.d.ts.map +1 -0
  45. package/dist/agi/index.d.ts +5 -0
  46. package/dist/agi/index.d.ts.map +1 -0
  47. package/dist/agi/lib/interceptor-chain.d.ts +44 -0
  48. package/dist/agi/lib/interceptor-chain.d.ts.map +1 -0
  49. package/dist/agi/lib/token-counter.d.ts +13 -0
  50. package/dist/agi/lib/token-counter.d.ts.map +1 -0
  51. package/dist/bootstrap/generated.d.ts +5 -0
  52. package/dist/bootstrap/generated.d.ts.map +1 -0
  53. package/dist/browser.d.ts +12 -0
  54. package/dist/browser.d.ts.map +1 -0
  55. package/dist/bus.d.ts +29 -0
  56. package/dist/bus.d.ts.map +1 -0
  57. package/dist/cli/build-info.d.ts +4 -0
  58. package/dist/cli/build-info.d.ts.map +1 -0
  59. package/dist/cli/cli.d.ts +3 -0
  60. package/dist/cli/cli.d.ts.map +1 -0
  61. package/dist/client.d.ts +60 -0
  62. package/dist/client.d.ts.map +1 -0
  63. package/dist/clients/civitai/index.d.ts +472 -0
  64. package/dist/clients/civitai/index.d.ts.map +1 -0
  65. package/dist/clients/client-template.d.ts +30 -0
  66. package/dist/clients/client-template.d.ts.map +1 -0
  67. package/dist/clients/comfyui/index.d.ts +281 -0
  68. package/dist/clients/comfyui/index.d.ts.map +1 -0
  69. package/dist/clients/elevenlabs/index.d.ts +197 -0
  70. package/dist/clients/elevenlabs/index.d.ts.map +1 -0
  71. package/dist/clients/graph.d.ts +64 -0
  72. package/dist/clients/graph.d.ts.map +1 -0
  73. package/dist/clients/openai/index.d.ts +247 -0
  74. package/dist/clients/openai/index.d.ts.map +1 -0
  75. package/dist/clients/rest.d.ts +92 -0
  76. package/dist/clients/rest.d.ts.map +1 -0
  77. package/dist/clients/supabase/index.d.ts +176 -0
  78. package/dist/clients/supabase/index.d.ts.map +1 -0
  79. package/dist/clients/websocket.d.ts +127 -0
  80. package/dist/clients/websocket.d.ts.map +1 -0
  81. package/dist/command.d.ts +163 -0
  82. package/dist/command.d.ts.map +1 -0
  83. package/dist/commands/bootstrap.d.ts +20 -0
  84. package/dist/commands/bootstrap.d.ts.map +1 -0
  85. package/dist/commands/chat.d.ts +37 -0
  86. package/dist/commands/chat.d.ts.map +1 -0
  87. package/dist/commands/code.d.ts +28 -0
  88. package/dist/commands/code.d.ts.map +1 -0
  89. package/dist/commands/console.d.ts +22 -0
  90. package/dist/commands/console.d.ts.map +1 -0
  91. package/dist/commands/describe.d.ts +50 -0
  92. package/dist/commands/describe.d.ts.map +1 -0
  93. package/dist/commands/eval.d.ts +23 -0
  94. package/dist/commands/eval.d.ts.map +1 -0
  95. package/dist/commands/help.d.ts +25 -0
  96. package/dist/commands/help.d.ts.map +1 -0
  97. package/dist/commands/index.d.ts +18 -0
  98. package/dist/commands/index.d.ts.map +1 -0
  99. package/dist/commands/introspect.d.ts +24 -0
  100. package/dist/commands/introspect.d.ts.map +1 -0
  101. package/dist/commands/mcp.d.ts +35 -0
  102. package/dist/commands/mcp.d.ts.map +1 -0
  103. package/dist/commands/prompt.d.ts +38 -0
  104. package/dist/commands/prompt.d.ts.map +1 -0
  105. package/dist/commands/run.d.ts +24 -0
  106. package/dist/commands/run.d.ts.map +1 -0
  107. package/dist/commands/sandbox-mcp.d.ts +34 -0
  108. package/dist/commands/sandbox-mcp.d.ts.map +1 -0
  109. package/dist/commands/save-api-docs.d.ts +21 -0
  110. package/dist/commands/save-api-docs.d.ts.map +1 -0
  111. package/dist/commands/scaffold.d.ts +24 -0
  112. package/dist/commands/scaffold.d.ts.map +1 -0
  113. package/dist/commands/select.d.ts +22 -0
  114. package/dist/commands/select.d.ts.map +1 -0
  115. package/dist/commands/serve.d.ts +29 -0
  116. package/dist/commands/serve.d.ts.map +1 -0
  117. package/dist/container-describer.d.ts +144 -0
  118. package/dist/container-describer.d.ts.map +1 -0
  119. package/dist/container.d.ts +451 -0
  120. package/dist/container.d.ts.map +1 -0
  121. package/dist/endpoint.d.ts +113 -0
  122. package/dist/endpoint.d.ts.map +1 -0
  123. package/dist/feature.d.ts +47 -0
  124. package/dist/feature.d.ts.map +1 -0
  125. package/dist/graft.d.ts +29 -0
  126. package/dist/graft.d.ts.map +1 -0
  127. package/dist/hash-object.d.ts +8 -0
  128. package/dist/hash-object.d.ts.map +1 -0
  129. package/dist/helper.d.ts +209 -0
  130. package/dist/helper.d.ts.map +1 -0
  131. package/dist/introspection/generated.node.d.ts +44623 -0
  132. package/dist/introspection/generated.node.d.ts.map +1 -0
  133. package/dist/introspection/generated.web.d.ts +1412 -0
  134. package/dist/introspection/generated.web.d.ts.map +1 -0
  135. package/dist/introspection/index.d.ts +156 -0
  136. package/dist/introspection/index.d.ts.map +1 -0
  137. package/dist/introspection/scan.d.ts +147 -0
  138. package/dist/introspection/scan.d.ts.map +1 -0
  139. package/dist/node/container.d.ts +256 -0
  140. package/dist/node/container.d.ts.map +1 -0
  141. package/dist/node/feature.d.ts +9 -0
  142. package/dist/node/feature.d.ts.map +1 -0
  143. package/dist/node/features/container-link.d.ts +213 -0
  144. package/dist/node/features/container-link.d.ts.map +1 -0
  145. package/dist/node/features/content-db.d.ts +354 -0
  146. package/dist/node/features/content-db.d.ts.map +1 -0
  147. package/dist/node/features/disk-cache.d.ts +236 -0
  148. package/dist/node/features/disk-cache.d.ts.map +1 -0
  149. package/dist/node/features/dns.d.ts +511 -0
  150. package/dist/node/features/dns.d.ts.map +1 -0
  151. package/dist/node/features/docker.d.ts +485 -0
  152. package/dist/node/features/docker.d.ts.map +1 -0
  153. package/dist/node/features/downloader.d.ts +73 -0
  154. package/dist/node/features/downloader.d.ts.map +1 -0
  155. package/dist/node/features/figlet-fonts.d.ts +4 -0
  156. package/dist/node/features/figlet-fonts.d.ts.map +1 -0
  157. package/dist/node/features/file-manager.d.ts +177 -0
  158. package/dist/node/features/file-manager.d.ts.map +1 -0
  159. package/dist/node/features/fs.d.ts +635 -0
  160. package/dist/node/features/fs.d.ts.map +1 -0
  161. package/dist/node/features/git.d.ts +329 -0
  162. package/dist/node/features/git.d.ts.map +1 -0
  163. package/dist/node/features/google-auth.d.ts +200 -0
  164. package/dist/node/features/google-auth.d.ts.map +1 -0
  165. package/dist/node/features/google-calendar.d.ts +194 -0
  166. package/dist/node/features/google-calendar.d.ts.map +1 -0
  167. package/dist/node/features/google-docs.d.ts +138 -0
  168. package/dist/node/features/google-docs.d.ts.map +1 -0
  169. package/dist/node/features/google-drive.d.ts +202 -0
  170. package/dist/node/features/google-drive.d.ts.map +1 -0
  171. package/dist/node/features/google-mail.d.ts +221 -0
  172. package/dist/node/features/google-mail.d.ts.map +1 -0
  173. package/dist/node/features/google-sheets.d.ts +157 -0
  174. package/dist/node/features/google-sheets.d.ts.map +1 -0
  175. package/dist/node/features/grep.d.ts +207 -0
  176. package/dist/node/features/grep.d.ts.map +1 -0
  177. package/dist/node/features/helpers.d.ts +236 -0
  178. package/dist/node/features/helpers.d.ts.map +1 -0
  179. package/dist/node/features/ink.d.ts +332 -0
  180. package/dist/node/features/ink.d.ts.map +1 -0
  181. package/dist/node/features/ipc-socket.d.ts +298 -0
  182. package/dist/node/features/ipc-socket.d.ts.map +1 -0
  183. package/dist/node/features/json-tree.d.ts +140 -0
  184. package/dist/node/features/json-tree.d.ts.map +1 -0
  185. package/dist/node/features/networking.d.ts +373 -0
  186. package/dist/node/features/networking.d.ts.map +1 -0
  187. package/dist/node/features/nlp.d.ts +125 -0
  188. package/dist/node/features/nlp.d.ts.map +1 -0
  189. package/dist/node/features/opener.d.ts +93 -0
  190. package/dist/node/features/opener.d.ts.map +1 -0
  191. package/dist/node/features/os.d.ts +168 -0
  192. package/dist/node/features/os.d.ts.map +1 -0
  193. package/dist/node/features/package-finder.d.ts +419 -0
  194. package/dist/node/features/package-finder.d.ts.map +1 -0
  195. package/dist/node/features/postgres.d.ts +173 -0
  196. package/dist/node/features/postgres.d.ts.map +1 -0
  197. package/dist/node/features/proc.d.ts +285 -0
  198. package/dist/node/features/proc.d.ts.map +1 -0
  199. package/dist/node/features/process-manager.d.ts +427 -0
  200. package/dist/node/features/process-manager.d.ts.map +1 -0
  201. package/dist/node/features/python.d.ts +477 -0
  202. package/dist/node/features/python.d.ts.map +1 -0
  203. package/dist/node/features/redis.d.ts +247 -0
  204. package/dist/node/features/redis.d.ts.map +1 -0
  205. package/dist/node/features/repl.d.ts +84 -0
  206. package/dist/node/features/repl.d.ts.map +1 -0
  207. package/dist/node/features/runpod.d.ts +527 -0
  208. package/dist/node/features/runpod.d.ts.map +1 -0
  209. package/dist/node/features/secure-shell.d.ts +145 -0
  210. package/dist/node/features/secure-shell.d.ts.map +1 -0
  211. package/dist/node/features/semantic-search.d.ts +207 -0
  212. package/dist/node/features/semantic-search.d.ts.map +1 -0
  213. package/dist/node/features/sqlite.d.ts +180 -0
  214. package/dist/node/features/sqlite.d.ts.map +1 -0
  215. package/dist/node/features/telegram.d.ts +173 -0
  216. package/dist/node/features/telegram.d.ts.map +1 -0
  217. package/dist/node/features/transpiler.d.ts +51 -0
  218. package/dist/node/features/transpiler.d.ts.map +1 -0
  219. package/dist/node/features/tts.d.ts +108 -0
  220. package/dist/node/features/tts.d.ts.map +1 -0
  221. package/dist/node/features/ui.d.ts +562 -0
  222. package/dist/node/features/ui.d.ts.map +1 -0
  223. package/dist/node/features/vault.d.ts +90 -0
  224. package/dist/node/features/vault.d.ts.map +1 -0
  225. package/dist/node/features/vm.d.ts +285 -0
  226. package/dist/node/features/vm.d.ts.map +1 -0
  227. package/dist/node/features/yaml-tree.d.ts +118 -0
  228. package/dist/node/features/yaml-tree.d.ts.map +1 -0
  229. package/dist/node/features/yaml.d.ts +127 -0
  230. package/dist/node/features/yaml.d.ts.map +1 -0
  231. package/dist/node.d.ts +67 -0
  232. package/dist/node.d.ts.map +1 -0
  233. package/dist/python/generated.d.ts +2 -0
  234. package/dist/python/generated.d.ts.map +1 -0
  235. package/dist/react/index.d.ts +36 -0
  236. package/dist/react/index.d.ts.map +1 -0
  237. package/dist/registry.d.ts +97 -0
  238. package/dist/registry.d.ts.map +1 -0
  239. package/dist/scaffolds/generated.d.ts +13 -0
  240. package/dist/scaffolds/generated.d.ts.map +1 -0
  241. package/dist/scaffolds/template.d.ts +11 -0
  242. package/dist/scaffolds/template.d.ts.map +1 -0
  243. package/dist/schemas/base.d.ts +254 -0
  244. package/dist/schemas/base.d.ts.map +1 -0
  245. package/dist/selector.d.ts +130 -0
  246. package/dist/selector.d.ts.map +1 -0
  247. package/dist/server.d.ts +89 -0
  248. package/dist/server.d.ts.map +1 -0
  249. package/dist/servers/express.d.ts +104 -0
  250. package/dist/servers/express.d.ts.map +1 -0
  251. package/dist/servers/mcp.d.ts +201 -0
  252. package/dist/servers/mcp.d.ts.map +1 -0
  253. package/dist/servers/socket.d.ts +121 -0
  254. package/dist/servers/socket.d.ts.map +1 -0
  255. package/dist/state.d.ts +24 -0
  256. package/dist/state.d.ts.map +1 -0
  257. package/dist/web/clients/socket.d.ts +37 -0
  258. package/dist/web/clients/socket.d.ts.map +1 -0
  259. package/dist/web/container.d.ts +55 -0
  260. package/dist/web/container.d.ts.map +1 -0
  261. package/dist/web/extension.d.ts +4 -0
  262. package/dist/web/extension.d.ts.map +1 -0
  263. package/dist/web/feature.d.ts +8 -0
  264. package/dist/web/feature.d.ts.map +1 -0
  265. package/dist/web/features/asset-loader.d.ts +35 -0
  266. package/dist/web/features/asset-loader.d.ts.map +1 -0
  267. package/dist/web/features/container-link.d.ts +167 -0
  268. package/dist/web/features/container-link.d.ts.map +1 -0
  269. package/dist/web/features/esbuild.d.ts +51 -0
  270. package/dist/web/features/esbuild.d.ts.map +1 -0
  271. package/dist/web/features/helpers.d.ts +140 -0
  272. package/dist/web/features/helpers.d.ts.map +1 -0
  273. package/dist/web/features/network.d.ts +69 -0
  274. package/dist/web/features/network.d.ts.map +1 -0
  275. package/dist/web/features/speech.d.ts +71 -0
  276. package/dist/web/features/speech.d.ts.map +1 -0
  277. package/dist/web/features/vault.d.ts +62 -0
  278. package/dist/web/features/vault.d.ts.map +1 -0
  279. package/dist/web/features/vm.d.ts +48 -0
  280. package/dist/web/features/vm.d.ts.map +1 -0
  281. package/dist/web/features/voice-recognition.d.ts +96 -0
  282. package/dist/web/features/voice-recognition.d.ts.map +1 -0
  283. package/dist/web/shims/isomorphic-vm.d.ts +22 -0
  284. package/dist/web/shims/isomorphic-vm.d.ts.map +1 -0
  285. package/docs/apis/features/agi/assistant.md +1 -0
  286. package/docs/apis/features/agi/assistants-manager.md +62 -2
  287. package/docs/apis/features/agi/auto-assistant.md +11 -109
  288. package/docs/apis/features/agi/claude-code.md +138 -0
  289. package/docs/apis/features/agi/conversation.md +60 -31
  290. package/docs/apis/features/agi/luca-coder.md +407 -0
  291. package/docs/apis/features/agi/openapi.md +2 -2
  292. package/docs/apis/features/agi/skills-library.md +12 -0
  293. package/docs/apis/features/node/python.md +81 -11
  294. package/docs/apis/features/node/transpiler.md +74 -0
  295. package/docs/apis/features/web/esbuild.md +0 -6
  296. package/docs/apis/servers/mcp.md +2 -2
  297. package/docs/examples/entity.md +124 -0
  298. package/package.json +73 -21
  299. package/scripts/test-assistant-hooks.ts +13 -0
  300. package/src/agi/feature.ts +13 -0
  301. package/src/agi/features/assistant.ts +36 -25
  302. package/src/agi/features/assistants-manager.ts +70 -5
  303. package/src/agi/features/autonomous-assistant.ts +1 -5
  304. package/src/agi/features/browser-use.ts +2 -2
  305. package/src/agi/features/claude-code.ts +165 -1
  306. package/src/agi/features/conversation-history.ts +2 -6
  307. package/src/agi/features/conversation.ts +95 -3
  308. package/src/agi/features/docs-reader.ts +2 -1
  309. package/src/agi/features/file-tools.ts +2 -2
  310. package/src/agi/features/luca-coder.ts +1 -5
  311. package/src/agi/features/openai-codex.ts +1 -1
  312. package/src/agi/features/openapi.ts +3 -3
  313. package/src/agi/features/skills-library.ts +90 -2
  314. package/src/agi/lib/interceptor-chain.ts +10 -0
  315. package/src/agi/lib/token-counter.ts +1 -1
  316. package/src/bootstrap/generated.ts +126 -1
  317. package/src/bus.ts +27 -5
  318. package/src/cli/build-info.ts +2 -2
  319. package/src/client.ts +2 -2
  320. package/src/clients/elevenlabs/index.ts +5 -0
  321. package/src/commands/bootstrap.ts +2 -1
  322. package/src/commands/chat.ts +1 -0
  323. package/src/commands/code.ts +4 -2
  324. package/src/commands/prompt.ts +34 -34
  325. package/src/commands/sandbox-mcp.ts +69 -163
  326. package/src/commands/save-api-docs.ts +10 -8
  327. package/src/commands/select.ts +8 -3
  328. package/src/container-describer.ts +70 -84
  329. package/src/container.ts +93 -3
  330. package/src/endpoint.ts +1 -1
  331. package/src/entity.ts +173 -0
  332. package/src/feature.ts +3 -3
  333. package/src/helper.ts +8 -4
  334. package/src/introspection/generated.agi.ts +1403 -929
  335. package/src/introspection/generated.node.ts +127 -33
  336. package/src/introspection/generated.web.ts +95 -3
  337. package/src/introspection/scan.ts +1 -1
  338. package/src/node/container.ts +1 -1
  339. package/src/node/features/content-db.ts +3 -3
  340. package/src/node/features/file-manager.ts +10 -9
  341. package/src/node/features/git.ts +5 -5
  342. package/src/node/features/helpers.ts +1 -1
  343. package/src/node/features/json-tree.ts +1 -1
  344. package/src/node/features/os.ts +3 -3
  345. package/src/node/features/package-finder.ts +1 -1
  346. package/src/node/features/process-manager.ts +1 -1
  347. package/src/node/features/python.ts +3 -3
  348. package/src/node/features/redis.ts +1 -1
  349. package/src/node/features/repl.ts +2 -2
  350. package/src/node/features/transpiler.ts +34 -9
  351. package/src/node/features/ui.ts +1 -1
  352. package/src/node/features/vm.ts +6 -5
  353. package/src/node/features/yaml-tree.ts +1 -1
  354. package/src/node.ts +1 -0
  355. package/src/python/generated.ts +1 -1
  356. package/src/scaffolds/generated.ts +1 -1
  357. package/src/selector.ts +74 -4
  358. package/src/server.ts +2 -2
  359. package/src/servers/mcp.ts +6 -6
  360. package/src/web/features/helpers.ts +1 -1
  361. package/src/web/features/network.ts +1 -0
  362. package/test/assistant.test.ts +72 -0
  363. package/test/conversation.test.ts +220 -0
  364. package/test/vm-loadmodule.test.ts +213 -0
  365. package/tsconfig.build.json +12 -0
  366. package/tsconfig.json +1 -1
  367. package/scripts/examples/telegram-ink-ui.ts +0 -302
  368. package/scripts/examples/using-openai-codex.ts +0 -23
  369. package/scripts/examples/vm-loading-esm-modules.ts +0 -16
@@ -400,7 +400,7 @@ export class RedisFeature extends Feature<RedisState, RedisOptions> {
400
400
  )
401
401
 
402
402
  if (existing) {
403
- if (existing.state !== 'running') {
403
+ if (existing.status !== 'running') {
404
404
  await docker.startContainer(name)
405
405
  }
406
406
  return existing.id
@@ -101,7 +101,7 @@ export class Repl<
101
101
 
102
102
  // Load existing history
103
103
  try {
104
- const content = fs.readFile(this._historyPath, 'utf-8')
104
+ const content = this.container.fs.readFile(this._historyPath, 'utf-8') as string
105
105
  this._history = content.split('\n').filter(Boolean).reverse()
106
106
  } catch {}
107
107
 
@@ -204,7 +204,7 @@ export class Repl<
204
204
  private _saveHistory(line: string) {
205
205
  if (!this._historyPath || !line.trim()) return
206
206
  try {
207
- fs.appendFileSync(this._historyPath, line + '\n')
207
+ this.container.fs.appendFile(this._historyPath, line + '\n')
208
208
  } catch {}
209
209
  }
210
210
  }
@@ -18,7 +18,9 @@ export interface TransformResult {
18
18
  * so the code can run in a vm context that provides `require`.
19
19
  */
20
20
  function esmToCjs(code: string): string {
21
- return code
21
+ const exportedNames: string[] = []
22
+
23
+ let result = code
22
24
  // import Foo, { bar, baz } from 'x' → const Foo = require('x').default ?? require('x'); const { bar, baz } = require('x')
23
25
  .replace(/^import\s+(\w+)\s*,\s*\{([^}]+)\}\s+from\s+(['"][^'"]+['"])\s*;?$/gm,
24
26
  'const $1 = require($3).default ?? require($3); const {$2} = require($3);')
@@ -39,14 +41,37 @@ function esmToCjs(code: string): string {
39
41
  // export { a, b } from 'x' → Object.assign(module.exports, require('x')) (re-exports)
40
42
  .replace(/^export\s+\{[^}]*\}\s+from\s+(['"][^'"]+['"])\s*;?$/gm,
41
43
  'Object.assign(module.exports, require($1));')
42
- // export { ... } → strip (vars already in scope)
43
- .replace(/^export\s+\{[^}]*\}\s*;?$/gm, '')
44
- // export const/let/var → const/let/var
45
- .replace(/^export\s+(const|let|var)\s+/gm, '$1 ')
46
- // export function → function (keep declaration, strip export keyword)
47
- .replace(/^export\s+(function|class)\s+/gm, '$1 ')
48
- // export async function async function
49
- .replace(/^export\s+(async\s+function)\s+/gm, '$1 ')
44
+ // export { a, b as c } → exports.a = a; exports.c = b;
45
+ .replace(/^export\s+\{([^}]*)\}\s*;?$/gm, (_match, body: string) => {
46
+ return body.split(',').map(s => {
47
+ const parts = s.trim().split(/\s+as\s+/)
48
+ const local = (parts[0] ?? '').trim()
49
+ const exported = (parts[1] ?? parts[0] ?? '').trim()
50
+ return local ? `exports['${exported}'] = ${local};` : ''
51
+ }).filter(Boolean).join(' ')
52
+ })
53
+ // export const/let/var NAME → const/let/var NAME (track for deferred export)
54
+ .replace(/^export\s+(const|let|var)\s+(\w+)/gm, (_match, decl: string, name: string) => {
55
+ exportedNames.push(name)
56
+ return `${decl} ${name}`
57
+ })
58
+ // export function NAME / export class NAME → function/class NAME (track for deferred export)
59
+ .replace(/^export\s+(function|class)\s+(\w+)/gm, (_match, type: string, name: string) => {
60
+ exportedNames.push(name)
61
+ return `${type} ${name}`
62
+ })
63
+ // export async function NAME → async function NAME (track for deferred export)
64
+ .replace(/^export\s+(async\s+function)\s+(\w+)/gm, (_match, type: string, name: string) => {
65
+ exportedNames.push(name)
66
+ return `${type} ${name}`
67
+ })
68
+
69
+ // Append exports for all tracked named exports
70
+ if (exportedNames.length > 0) {
71
+ result += '\n' + exportedNames.map(n => `exports['${n}'] = ${n};`).join('\n')
72
+ }
73
+
74
+ return result
50
75
  }
51
76
 
52
77
  /**
@@ -2,7 +2,7 @@ import { z } from 'zod'
2
2
  import { FeatureStateSchema } from '../../schemas/base.js'
3
3
  import { Feature } from "../feature.js";
4
4
  import colors from "chalk";
5
- import type { Fonts } from "figlet";
5
+ import type { FontName as Fonts } from "figlet";
6
6
  import { figlet, fontNames } from "./figlet-fonts.js";
7
7
  import inquirer from "inquirer";
8
8
  import { marked } from 'marked';
@@ -219,9 +219,9 @@ export class VM<
219
219
 
220
220
  // Find the last non-empty line
221
221
  let lastIdx = lines.length - 1
222
- while (lastIdx > 0 && !lines[lastIdx].trim()) lastIdx--
222
+ while (lastIdx > 0 && !(lines[lastIdx] ?? '').trim()) lastIdx--
223
223
 
224
- let lastLine = lines[lastIdx]!
224
+ let lastLine = lines[lastIdx] ?? ''
225
225
 
226
226
  // For single-line code with semicolons (e.g. CLI eval), split the last line
227
227
  // into statements and only try to return the final statement.
@@ -390,12 +390,13 @@ export class VM<
390
390
  if (!fs.exists(filePath)) return {}
391
391
 
392
392
  const raw = fs.readFile(filePath)
393
- const { code } = this.container.feature('transpiler').transformSync(raw, { format: 'cjs' })
393
+ const { code } = this.container.feature('transpiler').transformSync(String(raw), { format: 'cjs' })
394
394
 
395
+ const sharedExports = {}
395
396
  const { context } = this.performSync(code, {
396
397
  require: this.createRequireFor(filePath),
397
- exports: {},
398
- module: { exports: {} },
398
+ exports: sharedExports,
399
+ module: { exports: sharedExports },
399
400
  console,
400
401
  setTimeout,
401
402
  setInterval,
@@ -113,7 +113,7 @@ export class YamlTree<T extends YamlTreeState = YamlTreeState> extends Feature<T
113
113
  for (const file of yamlFiles.filter(Boolean)) {
114
114
  if(file?.relativePath) {
115
115
  const fileContent = fileSystem.readFile(file.relativePath);
116
- const fileData = yamlFeature.parse(fileContent);
116
+ const fileData = yamlFeature.parse(String(fileContent));
117
117
  const path = file.relativePath.replace(/\.ya?ml$/, "").replace(basePath + "/", "").split("/").filter(v => v?.length).map(p => camelCase(p));
118
118
  set(tree, path, fileData)
119
119
  }
package/src/node.ts CHANGED
@@ -65,6 +65,7 @@ export type { AvailableFeatures, FeatureOptions, FeatureState } from './feature'
65
65
  export type { NodeContainer, NodeFeatures } from './node/container'
66
66
  export type { AvailableServers, StartOptions, ServersInterface } from './server'
67
67
  export type { HelperState, HelperOptions } from './helper'
68
+ export type { Entity } from './entity'
68
69
  export type { EventMap } from './bus'
69
70
  export type { SetStateValue, StateChangeType } from './state'
70
71
 
@@ -1,5 +1,5 @@
1
1
  // Auto-generated Python bridge script
2
- // Generated at: 2026-03-28T06:19:50.936Z
2
+ // Generated at: 2026-04-03T01:24:54.795Z
3
3
  // Source: src/python/bridge.py
4
4
  //
5
5
  // Do not edit manually. Run: luca build-python-bridge
@@ -1,5 +1,5 @@
1
1
  // Auto-generated scaffold and MCP readme content
2
- // Generated at: 2026-03-28T06:19:49.219Z
2
+ // Generated at: 2026-04-03T01:24:53.146Z
3
3
  // Source: docs/scaffolds/*.md, docs/examples/assistant/, and docs/mcp/readme.md
4
4
  //
5
5
  // Do not edit manually. Run: luca build-scaffolds
package/src/selector.ts CHANGED
@@ -135,17 +135,17 @@ export class Selector<
135
135
  }
136
136
 
137
137
  // Run the selector
138
- this.state.set('running' as any, true)
138
+ this.state.set('running' as any, true as any)
139
139
  this.emit('started' as any)
140
140
 
141
141
  let data: any
142
142
  try {
143
143
  data = await this.run(parsed, this.context)
144
- this.state.set('running' as any, false)
145
- this.state.set('lastRanAt' as any, Date.now())
144
+ this.state.set('running' as any, false as any)
145
+ this.state.set('lastRanAt' as any, Date.now() as any)
146
146
  this.emit('completed' as any, data)
147
147
  } catch (err: any) {
148
- this.state.set('running' as any, false)
148
+ this.state.set('running' as any, false as any)
149
149
  this.emit('failed' as any, err)
150
150
  throw err
151
151
  }
@@ -197,6 +197,76 @@ export class SelectorsRegistry extends Registry<Selector<any>> {
197
197
  override scope = 'selectors'
198
198
  override baseClass = Selector as any
199
199
 
200
+ /**
201
+ * Convert all registered selectors into a `{ schemas, handlers }` object
202
+ * compatible with `assistant.use()`.
203
+ *
204
+ * Each selector becomes a tool whose parameters come from the selector's
205
+ * `argsSchema` (with internal fields stripped) and whose handler dispatches
206
+ * the selector and returns the data directly (cache metadata is not exposed).
207
+ *
208
+ * @param container - The container used to instantiate and run selectors
209
+ * @param options - Optional filter/transform options
210
+ * @param options.include - Only include these selector names (default: all)
211
+ * @param options.exclude - Exclude these selector names (default: none)
212
+ * @param options.prefix - Prefix tool names (e.g. 'sel_' → 'sel_packageInfo')
213
+ */
214
+ toTools(
215
+ container: Container<any> & SelectorsInterface,
216
+ options?: { include?: string[], exclude?: string[], prefix?: string },
217
+ ): { schemas: Record<string, z.ZodType>, handlers: Record<string, Function> } {
218
+ const schemas: Record<string, z.ZodType> = {}
219
+ const handlers: Record<string, Function> = {}
220
+ const prefix = options?.prefix ?? ''
221
+ const includeSet = options?.include ? new Set(options.include) : null
222
+ const excludeSet = new Set(options?.exclude ?? [])
223
+
224
+ // Internal fields from HelperOptionsSchema and SelectorOptionsSchema
225
+ const internalFields = ['name', '_cacheKey', 'dispatchSource']
226
+
227
+ for (const name of this.available) {
228
+ if (excludeSet.has(name)) continue
229
+ if (includeSet && !includeSet.has(name)) continue
230
+
231
+ const Sel = this.lookup(name) as typeof Selector
232
+ const rawSchema = Sel.argsSchema
233
+ const description = Sel.selectorDescription || Sel.description || name
234
+
235
+ let toolSchema: z.ZodType
236
+ try {
237
+ const shape = typeof (rawSchema as any)?._def?.shape === 'function'
238
+ ? (rawSchema as any)._def.shape()
239
+ : (rawSchema as any)?._def?.shape
240
+
241
+ if (shape) {
242
+ const cleanShape: Record<string, z.ZodType> = {}
243
+ for (const [key, val] of Object.entries(shape)) {
244
+ if (internalFields.includes(key)) continue
245
+ cleanShape[key] = val as z.ZodType
246
+ }
247
+
248
+ toolSchema = Object.keys(cleanShape).length > 0
249
+ ? z.object(cleanShape).describe(description)
250
+ : z.object({}).describe(description)
251
+ } else {
252
+ toolSchema = z.object({}).describe(description)
253
+ }
254
+ } catch {
255
+ toolSchema = z.object({}).describe(description)
256
+ }
257
+
258
+ const toolName = `${prefix}${name}`
259
+ schemas[toolName] = toolSchema
260
+ handlers[toolName] = async (args: Record<string, any>) => {
261
+ const sel = (container.select as any)(name)
262
+ const result = await sel.select(args ?? {})
263
+ return result.data
264
+ }
265
+ }
266
+
267
+ return { schemas, handlers }
268
+ }
269
+
200
270
  /**
201
271
  * Discover and register selectors from a directory.
202
272
  * Detection order:
package/src/server.ts CHANGED
@@ -30,7 +30,7 @@ export class Server<T extends ServerState = ServerState, K extends ServerOptions
30
30
  static override eventsSchema = ServerEventsSchema
31
31
 
32
32
  /** Self-register a Server subclass from a static initialization block. */
33
- static register: (SubClass: typeof Server, id?: string) => typeof Server
33
+ static register: (SubClass: abstract new (options: any, context: any) => Server, id?: string) => abstract new (options: any, context: any) => Server
34
34
 
35
35
  override get initialState() : T {
36
36
  return ({
@@ -203,7 +203,7 @@ export const helperCache = new Map()
203
203
  * ```
204
204
  */
205
205
  Server.register = function registerServer(
206
- SubClass: typeof Server,
206
+ SubClass: abstract new (options: any, context: any) => Server,
207
207
  id?: string,
208
208
  ) {
209
209
  const registryId = id ?? SubClass.name[0]!.toLowerCase() + SubClass.name.slice(1)
@@ -33,9 +33,9 @@ export type MCPContext = {
33
33
  export interface RegisteredTool {
34
34
  name: string
35
35
  description?: string
36
- schema?: z.ZodObject<any>
36
+ schema?: z.ZodType
37
37
  jsonSchema?: Record<string, any>
38
- handler: (args: any, ctx: MCPContext) => any
38
+ handler: Function
39
39
  }
40
40
 
41
41
  /** A registered MCP resource with its URI, metadata, and handler. */
@@ -61,9 +61,9 @@ export type PromptMessage = {
61
61
  }
62
62
 
63
63
  type ToolRegistrationOptions = {
64
- schema?: z.ZodObject<any>
64
+ schema?: z.ZodType
65
65
  description?: string
66
- handler: (args: any, ctx: MCPContext) => any
66
+ handler?: Function | ((args: any, ctx: any) => any)
67
67
  }
68
68
 
69
69
  type ResourceRegistrationOptions = {
@@ -340,7 +340,7 @@ export class MCPServer extends Server<MCPServerState, MCPServerOptions> {
340
340
  * @param name - Unique tool name
341
341
  * @param options - Tool schema, description, and handler
342
342
  */
343
- tool(name: string, options: ToolRegistrationOptions): this {
343
+ override tool(name: string, options: ToolRegistrationOptions): this {
344
344
  let jsonSchema: Record<string, any> | undefined
345
345
 
346
346
  if (options.schema) {
@@ -357,7 +357,7 @@ export class MCPServer extends Server<MCPServerState, MCPServerOptions> {
357
357
  description: options.description,
358
358
  schema: options.schema,
359
359
  jsonSchema,
360
- handler: options.handler,
360
+ handler: options.handler ?? (() => {}),
361
361
  }
362
362
 
363
363
  this._tools.set(name, registered)
@@ -136,7 +136,7 @@ export class Helpers extends Feature<HelpersState, HelpersOptions> {
136
136
  return {}
137
137
  }
138
138
 
139
- const manifest: Manifest = await response.json()
139
+ const manifest = await response.json() as Manifest
140
140
  this._manifest = manifest
141
141
  this.state.set('manifestLoaded', true)
142
142
  this.emit('manifestLoaded' as any, manifest)
@@ -1,3 +1,4 @@
1
+ /// <reference lib="dom" />
1
2
  import { z } from 'zod'
2
3
  import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
3
4
  import { Feature } from "../feature.js";
@@ -0,0 +1,72 @@
1
+ import { describe, it, expect, beforeEach } from 'bun:test'
2
+ import { AGIContainer } from '../src/agi/container.server'
3
+
4
+ describe('Assistant', () => {
5
+ let container: AGIContainer
6
+
7
+ beforeEach(() => {
8
+ container = new AGIContainer()
9
+ })
10
+
11
+ describe('codingAssistant', () => {
12
+ it('loads a non-empty system prompt from CORE.md', () => {
13
+ const assistant = container.feature('assistant', { folder: 'assistants/codingAssistant' })
14
+ expect(assistant.systemPrompt.length).toBeGreaterThan(0)
15
+ expect(assistant.systemPrompt).toContain('coding assistant')
16
+ })
17
+
18
+ it('loads tools from tools.ts via the VM', () => {
19
+ const assistant = container.feature('assistant', { folder: 'assistants/codingAssistant' })
20
+ const tools = assistant.availableTools
21
+ expect(tools).toContain('rg')
22
+ expect(tools).toContain('ls')
23
+ expect(tools).toContain('cat')
24
+ expect(tools).toContain('pwd')
25
+ expect(tools.length).toBeGreaterThan(0)
26
+ })
27
+
28
+ it('tools have descriptions and parameter schemas', () => {
29
+ const assistant = container.feature('assistant', { folder: 'assistants/codingAssistant' })
30
+ const { rg, ls, cat } = assistant.tools
31
+ expect(rg.description.length).toBeGreaterThan(0)
32
+ expect(rg.parameters.type).toBe('object')
33
+ expect(rg.parameters.properties).toHaveProperty('args')
34
+ expect(ls.parameters.properties).toHaveProperty('args')
35
+ expect(cat.parameters.properties).toHaveProperty('args')
36
+ })
37
+
38
+ it('loads hooks from hooks.ts via the VM', () => {
39
+ const assistant = container.feature('assistant', { folder: 'assistants/codingAssistant' })
40
+ const hooks = assistant.state.get('hooks') as Record<string, Function>
41
+ expect(hooks).toBeDefined()
42
+ expect(typeof hooks.started).toBe('function')
43
+ })
44
+
45
+ it('hooks fire when the assistant starts', async () => {
46
+ const assistant = container.feature('assistant', {
47
+ folder: 'assistants/codingAssistant',
48
+ local: true,
49
+ model: 'qwen/qwen3-8b',
50
+ })
51
+
52
+ // bindHooksToEvents emits 'hookFired' with the event name each time a hook runs
53
+ const fired: string[] = []
54
+ assistant.on('hookFired', (eventName: string) => { fired.push(eventName) })
55
+
56
+ await assistant.start()
57
+ expect(fired).toContain('started')
58
+ })
59
+
60
+ it('tools are wired into the conversation after start', async () => {
61
+ const assistant = container.feature('assistant', {
62
+ folder: 'assistants/codingAssistant',
63
+ local: true,
64
+ model: 'qwen/qwen3-8b',
65
+ })
66
+ await assistant.start()
67
+ const convTools = assistant.conversation.tools
68
+ expect(Object.keys(convTools)).toContain('rg')
69
+ expect(Object.keys(convTools)).toContain('ls')
70
+ })
71
+ })
72
+ })
@@ -0,0 +1,220 @@
1
+ import { describe, it, expect, beforeEach } from 'bun:test'
2
+ import { AGIContainer } from '../src/agi/container.server'
3
+ import type { Conversation } from '../src/agi/features/conversation'
4
+
5
+ function makeConversation(opts: Record<string, any> = {}): Conversation {
6
+ const container = new AGIContainer()
7
+ return container.feature('conversation', { model: 'gpt-5', ...opts }) as Conversation
8
+ }
9
+
10
+ describe('Conversation', () => {
11
+ describe('state', () => {
12
+ it('initializes with empty messages when no history provided', () => {
13
+ const conv = makeConversation()
14
+ expect(conv.messages).toEqual([])
15
+ })
16
+
17
+ it('seeds message history from options', () => {
18
+ const history = [{ role: 'system', content: 'You are helpful.' }]
19
+ const conv = makeConversation({ history })
20
+ expect(conv.messages).toHaveLength(1)
21
+ expect(conv.messages[0]).toEqual(history[0])
22
+ })
23
+
24
+ it('uses provided model', () => {
25
+ const conv = makeConversation({ model: 'gpt-4.1' })
26
+ expect(conv.model).toBe('gpt-4.1')
27
+ })
28
+
29
+ it('isStreaming is false initially', () => {
30
+ const conv = makeConversation()
31
+ expect(conv.isStreaming).toBe(false)
32
+ })
33
+ })
34
+
35
+ describe('pushMessage', () => {
36
+ it('appends to messages', () => {
37
+ const conv = makeConversation()
38
+ conv.pushMessage({ role: 'user', content: 'hello' })
39
+ expect(conv.messages).toHaveLength(1)
40
+ expect(conv.messages[0]!.role).toBe('user')
41
+ })
42
+
43
+ it('does not mutate prior messages array', () => {
44
+ const conv = makeConversation()
45
+ const before = conv.messages
46
+ conv.pushMessage({ role: 'user', content: 'hi' })
47
+ expect(conv.messages).not.toBe(before)
48
+ })
49
+ })
50
+
51
+ describe('tools', () => {
52
+ it('starts with no tools', () => {
53
+ const conv = makeConversation()
54
+ expect(conv.availableTools).toHaveLength(0)
55
+ })
56
+
57
+ it('addTool registers a tool', () => {
58
+ const conv = makeConversation()
59
+ conv.addTool('greet', {
60
+ description: 'Says hello',
61
+ parameters: { type: 'object', properties: {} },
62
+ handler: async () => 'hello',
63
+ })
64
+ expect(conv.availableTools).toContain('greet')
65
+ })
66
+
67
+ it('removeTool deregisters a tool', () => {
68
+ const conv = makeConversation()
69
+ conv.addTool('greet', {
70
+ description: 'Says hello',
71
+ parameters: { type: 'object', properties: {} },
72
+ handler: async () => 'hello',
73
+ })
74
+ conv.removeTool('greet')
75
+ expect(conv.availableTools).not.toContain('greet')
76
+ })
77
+
78
+ it('updateTools merges without replacing unrelated tools', () => {
79
+ const conv = makeConversation()
80
+ conv.addTool('a', { description: 'A', parameters: { type: 'object', properties: {} }, handler: async () => 'a' })
81
+ conv.updateTools({ b: { description: 'B', parameters: { type: 'object', properties: {} }, handler: async () => 'b' } })
82
+ expect(conv.availableTools).toContain('a')
83
+ expect(conv.availableTools).toContain('b')
84
+ })
85
+
86
+ it('construction-time tools are available immediately', () => {
87
+ const conv = makeConversation({
88
+ tools: {
89
+ ping: { description: 'Ping', parameters: { type: 'object', properties: {} }, handler: async () => 'pong' }
90
+ }
91
+ })
92
+ expect(conv.availableTools).toContain('ping')
93
+ })
94
+ })
95
+
96
+ describe('estimateTokens', () => {
97
+ it('returns a near-zero baseline for empty messages', () => {
98
+ const conv = makeConversation()
99
+ expect(conv.estimateTokens()).toBeLessThan(10)
100
+ })
101
+
102
+ it('returns a positive number when messages exist', () => {
103
+ const conv = makeConversation({
104
+ history: [{ role: 'user', content: 'What is the capital of France?' }]
105
+ })
106
+ expect(conv.estimateTokens()).toBeGreaterThan(0)
107
+ })
108
+
109
+ it('increases as more messages are added', () => {
110
+ const conv = makeConversation()
111
+ conv.pushMessage({ role: 'user', content: 'Hello' })
112
+ const first = conv.estimateTokens()
113
+ conv.pushMessage({ role: 'assistant', content: 'Hi there, how can I help you today?' })
114
+ const second = conv.estimateTokens()
115
+ expect(second).toBeGreaterThan(first)
116
+ })
117
+ })
118
+
119
+ describe('stub()', () => {
120
+ it('exact string match returns the stub response', async () => {
121
+ const conv = makeConversation()
122
+ conv.stub('ping', 'pong')
123
+ const result = await conv.ask('ping')
124
+ expect(result).toBe('pong')
125
+ })
126
+
127
+ it('substring match triggers the stub', async () => {
128
+ const conv = makeConversation()
129
+ conv.stub('weather', 'Sunny and 72°F.')
130
+ const result = await conv.ask('what is the weather today?')
131
+ expect(result).toBe('Sunny and 72°F.')
132
+ })
133
+
134
+ it('regex match triggers the stub', async () => {
135
+ const conv = makeConversation()
136
+ conv.stub(/hello/i, 'Hey there!')
137
+ const result = await conv.ask('HELLO')
138
+ expect(result).toBe('Hey there!')
139
+ })
140
+
141
+ it('function response is called on each match', async () => {
142
+ const conv = makeConversation()
143
+ let callCount = 0
144
+ conv.stub('count', () => `call ${++callCount}`)
145
+ expect(await conv.ask('count')).toBe('call 1')
146
+ expect(await conv.ask('count')).toBe('call 2')
147
+ })
148
+
149
+ it('first matching stub wins', async () => {
150
+ const conv = makeConversation()
151
+ conv.stub('hello', 'first')
152
+ conv.stub('hello', 'second')
153
+ expect(await conv.ask('hello')).toBe('first')
154
+ })
155
+
156
+ it('appends user and assistant messages to history', async () => {
157
+ const conv = makeConversation()
158
+ conv.stub('hi', 'hello back')
159
+ await conv.ask('hi')
160
+ expect(conv.messages).toHaveLength(2)
161
+ expect(conv.messages[0]).toMatchObject({ role: 'user', content: 'hi' })
162
+ expect(conv.messages[1]).toMatchObject({ role: 'assistant', content: 'hello back' })
163
+ })
164
+
165
+ it('isStreaming is false after the call resolves', async () => {
166
+ const conv = makeConversation()
167
+ conv.stub('test', 'response')
168
+ await conv.ask('test')
169
+ expect(conv.isStreaming).toBe(false)
170
+ })
171
+
172
+ it('emits chunk events for each word', async () => {
173
+ const conv = makeConversation()
174
+ conv.stub('go', 'one two three')
175
+ const chunks: string[] = []
176
+ conv.on('chunk', (delta: string) => chunks.push(delta))
177
+ await conv.ask('go')
178
+ expect(chunks.join('')).toBe('one two three')
179
+ })
180
+
181
+ it('emits preview events with accumulating text', async () => {
182
+ const conv = makeConversation()
183
+ conv.stub('go', 'alpha beta')
184
+ const previews: string[] = []
185
+ conv.on('preview', (text: string) => previews.push(text))
186
+ await conv.ask('go')
187
+ expect(previews.at(-1)).toBe('alpha beta')
188
+ // Each preview should be longer than or equal to the previous
189
+ for (let i = 1; i < previews.length; i++) {
190
+ expect(previews[i]!.length).toBeGreaterThanOrEqual(previews[i - 1]!.length)
191
+ }
192
+ })
193
+
194
+ it('emits turnStart and turnEnd events', async () => {
195
+ const conv = makeConversation()
196
+ conv.stub('x', 'y')
197
+ const events: string[] = []
198
+ conv.on('turnStart', () => events.push('turnStart'))
199
+ conv.on('turnEnd', () => events.push('turnEnd'))
200
+ await conv.ask('x')
201
+ expect(events).toEqual(['turnStart', 'turnEnd'])
202
+ })
203
+
204
+ it('emits response event with the full text', async () => {
205
+ const conv = makeConversation()
206
+ conv.stub('question', 'the answer')
207
+ let emitted = ''
208
+ conv.on('response', (text: string) => { emitted = text })
209
+ await conv.ask('question')
210
+ expect(emitted).toBe('the answer')
211
+ })
212
+
213
+ it('stub is chainable', () => {
214
+ const conv = makeConversation()
215
+ const result = conv.stub('a', 'A').stub('b', 'B')
216
+ expect(result).toBe(conv)
217
+ expect(conv.availableTools).toHaveLength(0) // stubs don't affect tools
218
+ })
219
+ })
220
+ })