luca 3.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 (372) hide show
  1. package/.github/workflows/release.yaml +1 -0
  2. package/CLAUDE.md +10 -2
  3. package/README.md +130 -112
  4. package/assistants/codingAssistant/CORE.md +6 -1
  5. package/assistants/codingAssistant/hooks.ts +1 -1
  6. package/assistants/inkbot/hooks.ts +1 -1
  7. package/assistants/inkbot/tools.ts +1 -1
  8. package/bun.lock +220 -322
  9. package/commands/audit-docs.ts +2 -2
  10. package/commands/build-bootstrap.ts +2 -3
  11. package/commands/build-python-bridge.ts +2 -3
  12. package/commands/build-scaffolds.ts +2 -3
  13. package/commands/bundle-consumer-project.ts +521 -0
  14. package/commands/generate-api-docs.ts +2 -2
  15. package/commands/inkbot.ts +2 -2
  16. package/commands/release.ts +2 -2
  17. package/commands/try-all-challenges.ts +3 -3
  18. package/commands/try-challenge.ts +3 -3
  19. package/dist/agi/container.server.d.ts +2 -2
  20. package/dist/agi/features/assistant.d.ts +2 -2
  21. package/dist/agi/features/assistants-manager.d.ts +1 -1
  22. package/dist/agi/features/autonomous-assistant.d.ts +1 -1
  23. package/dist/agi/features/browser-use.d.ts +1 -1
  24. package/dist/agi/features/claude-code.d.ts +1 -1
  25. package/dist/agi/features/conversation-history.d.ts +2 -2
  26. package/dist/agi/features/conversation.d.ts +1 -1
  27. package/dist/agi/features/docs-reader.d.ts +1 -1
  28. package/dist/agi/features/file-tools.d.ts +1 -1
  29. package/dist/agi/features/luca-coder.d.ts +1 -1
  30. package/dist/agi/features/openai-codex.d.ts +1 -1
  31. package/dist/agi/features/skills-library.d.ts +1 -1
  32. package/dist/clients/civitai/index.d.ts +4 -4
  33. package/dist/clients/client-template.d.ts +4 -4
  34. package/dist/clients/comfyui/index.d.ts +2 -2
  35. package/dist/clients/elevenlabs/index.d.ts +2 -2
  36. package/dist/clients/openai/index.d.ts +2 -2
  37. package/dist/clients/supabase/index.d.ts +3 -3
  38. package/dist/command.d.ts +1 -1
  39. package/dist/node/container.d.ts +1 -1
  40. package/dist/node/features/helpers.d.ts +3 -3
  41. package/dist/node/features/semantic-search.d.ts +1 -1
  42. package/dist/node/features/vm.d.ts +3 -3
  43. package/dist/node.d.ts +1 -1
  44. package/dist/scaffolds/generated.d.ts +1 -1
  45. package/dist/selector.d.ts +1 -1
  46. package/index.html +217 -190
  47. package/luca.console.ts +1 -1
  48. package/package.json +2 -2
  49. package/public/index.html +217 -190
  50. package/public/slides-ai-native.html +1 -1
  51. package/public/slides-intro.html +2 -2
  52. package/scripts/examples/ask-luca-expert.ts +1 -1
  53. package/scripts/examples/assistant-questions.ts +1 -1
  54. package/scripts/examples/excalidraw-expert.ts +1 -1
  55. package/scripts/examples/file-manager.ts +1 -1
  56. package/scripts/examples/ideas.ts +1 -1
  57. package/scripts/examples/interactive-chat.ts +1 -1
  58. package/scripts/examples/opening-a-web-browser.ts +1 -1
  59. package/scripts/examples/telegram-bot.ts +1 -1
  60. package/scripts/examples/using-assistant-with-mcp.ts +1 -1
  61. package/scripts/examples/using-claude-code.ts +1 -1
  62. package/scripts/examples/using-contentdb.ts +2 -2
  63. package/scripts/examples/using-conversations.ts +1 -1
  64. package/scripts/examples/using-disk-cache.ts +1 -1
  65. package/scripts/examples/using-docker-shell.ts +1 -1
  66. package/scripts/examples/using-elevenlabs.ts +1 -1
  67. package/scripts/examples/using-google-calendar.ts +1 -1
  68. package/scripts/examples/using-google-docs.ts +1 -1
  69. package/scripts/examples/using-google-drive.ts +1 -1
  70. package/scripts/examples/using-google-sheets.ts +1 -1
  71. package/scripts/examples/using-nlp.ts +1 -1
  72. package/scripts/examples/using-ollama.ts +1 -1
  73. package/scripts/examples/using-postgres.ts +1 -1
  74. package/scripts/examples/using-runpod.ts +1 -1
  75. package/scripts/examples/using-tts.ts +1 -1
  76. package/scripts/scaffold.ts +5 -5
  77. package/scripts/scratch.ts +1 -1
  78. package/scripts/test-assistant-hooks.ts +1 -1
  79. package/scripts/test-docs-reader.ts +1 -1
  80. package/src/agi/container.server.ts +6 -2
  81. package/src/agi/features/agent-memory.ts +25 -25
  82. package/src/agi/features/assistant.ts +34 -5
  83. package/src/agi/features/assistants-manager.ts +122 -6
  84. package/src/agi/features/autonomous-assistant.ts +1 -1
  85. package/src/agi/features/browser-use.ts +20 -1
  86. package/src/agi/features/claude-code.ts +51 -5
  87. package/src/agi/features/coding-tools.ts +1 -1
  88. package/src/agi/features/conversation-history.ts +181 -4
  89. package/src/agi/features/conversation.ts +186 -15
  90. package/src/agi/features/docs-reader.ts +2 -2
  91. package/src/agi/features/file-tools.ts +49 -2
  92. package/src/agi/features/luca-coder.ts +7 -5
  93. package/src/agi/features/mcp-bridge.ts +532 -0
  94. package/src/agi/features/openai-codex.ts +2 -2
  95. package/src/agi/features/skills-library.ts +131 -52
  96. package/src/agi/lib/token-counter.ts +80 -0
  97. package/src/bootstrap/generated.ts +56 -57
  98. package/src/browser.ts +1 -1
  99. package/src/cli/build-info.ts +2 -2
  100. package/src/cli/cli.ts +2 -2
  101. package/src/clients/civitai/index.ts +5 -5
  102. package/src/clients/client-template.ts +4 -4
  103. package/src/clients/comfyui/index.ts +4 -4
  104. package/src/clients/elevenlabs/index.ts +4 -4
  105. package/src/clients/openai/index.ts +7 -7
  106. package/src/clients/supabase/index.ts +4 -4
  107. package/src/clients/voicebox/index.ts +4 -4
  108. package/src/command.ts +2 -1
  109. package/src/commands/chat.ts +1 -0
  110. package/src/commands/eval.ts +2 -56
  111. package/src/commands/introspect.ts +1 -1
  112. package/src/commands/prompt.ts +41 -9
  113. package/src/container-describer.ts +8 -1
  114. package/src/container.ts +13 -0
  115. package/src/entity.ts +2 -2
  116. package/src/helper.ts +1 -1
  117. package/src/introspection/generated.agi.ts +28563 -27571
  118. package/src/introspection/generated.node.ts +20281 -20194
  119. package/src/introspection/generated.web.ts +605 -584
  120. package/src/introspection/scan.ts +11 -6
  121. package/src/node/container.ts +1 -1
  122. package/src/node/features/content-db.ts +39 -2
  123. package/src/node/features/display-result.ts +57 -0
  124. package/src/node/features/helpers.ts +42 -15
  125. package/src/node/features/python.ts +25 -19
  126. package/src/node/features/repl.ts +1 -1
  127. package/src/node/features/secure-shell.ts +11 -17
  128. package/src/node/features/semantic-search.ts +2 -2
  129. package/src/node/features/transpiler.ts +2 -3
  130. package/src/node/features/ui.ts +5 -0
  131. package/src/node/features/vm.ts +3 -3
  132. package/src/node.ts +3 -3
  133. package/src/python/generated.ts +0 -1
  134. package/src/scaffolds/generated.ts +82 -83
  135. package/src/selector.ts +1 -1
  136. package/src/servers/express.ts +1 -1
  137. package/src/web/features/helpers.ts +22 -0
  138. package/tsconfig.json +12 -12
  139. package/docs/CLI.md +0 -335
  140. package/docs/CNAME +0 -1
  141. package/docs/README.md +0 -60
  142. package/docs/TABLE-OF-CONTENTS.md +0 -183
  143. package/docs/apis/clients/elevenlabs.md +0 -308
  144. package/docs/apis/clients/graph.md +0 -107
  145. package/docs/apis/clients/openai.md +0 -429
  146. package/docs/apis/clients/rest.md +0 -161
  147. package/docs/apis/clients/websocket.md +0 -174
  148. package/docs/apis/features/agi/assistant.md +0 -625
  149. package/docs/apis/features/agi/assistants-manager.md +0 -282
  150. package/docs/apis/features/agi/auto-assistant.md +0 -279
  151. package/docs/apis/features/agi/browser-use.md +0 -802
  152. package/docs/apis/features/agi/claude-code.md +0 -884
  153. package/docs/apis/features/agi/conversation-history.md +0 -364
  154. package/docs/apis/features/agi/conversation.md +0 -548
  155. package/docs/apis/features/agi/docs-reader.md +0 -99
  156. package/docs/apis/features/agi/file-tools.md +0 -163
  157. package/docs/apis/features/agi/luca-coder.md +0 -407
  158. package/docs/apis/features/agi/openai-codex.md +0 -396
  159. package/docs/apis/features/agi/openapi.md +0 -138
  160. package/docs/apis/features/agi/semantic-search.md +0 -387
  161. package/docs/apis/features/agi/skills-library.md +0 -239
  162. package/docs/apis/features/node/container-link.md +0 -192
  163. package/docs/apis/features/node/content-db.md +0 -450
  164. package/docs/apis/features/node/disk-cache.md +0 -379
  165. package/docs/apis/features/node/dns.md +0 -652
  166. package/docs/apis/features/node/docker.md +0 -706
  167. package/docs/apis/features/node/downloader.md +0 -81
  168. package/docs/apis/features/node/esbuild.md +0 -60
  169. package/docs/apis/features/node/file-manager.md +0 -191
  170. package/docs/apis/features/node/fs.md +0 -1217
  171. package/docs/apis/features/node/git.md +0 -371
  172. package/docs/apis/features/node/google-auth.md +0 -193
  173. package/docs/apis/features/node/google-calendar.md +0 -202
  174. package/docs/apis/features/node/google-docs.md +0 -173
  175. package/docs/apis/features/node/google-drive.md +0 -246
  176. package/docs/apis/features/node/google-mail.md +0 -214
  177. package/docs/apis/features/node/google-sheets.md +0 -194
  178. package/docs/apis/features/node/grep.md +0 -292
  179. package/docs/apis/features/node/helpers.md +0 -164
  180. package/docs/apis/features/node/ink.md +0 -334
  181. package/docs/apis/features/node/ipc-socket.md +0 -249
  182. package/docs/apis/features/node/json-tree.md +0 -86
  183. package/docs/apis/features/node/networking.md +0 -316
  184. package/docs/apis/features/node/nlp.md +0 -133
  185. package/docs/apis/features/node/opener.md +0 -97
  186. package/docs/apis/features/node/os.md +0 -146
  187. package/docs/apis/features/node/package-finder.md +0 -392
  188. package/docs/apis/features/node/postgres.md +0 -234
  189. package/docs/apis/features/node/proc.md +0 -399
  190. package/docs/apis/features/node/process-manager.md +0 -305
  191. package/docs/apis/features/node/python.md +0 -604
  192. package/docs/apis/features/node/redis.md +0 -380
  193. package/docs/apis/features/node/repl.md +0 -88
  194. package/docs/apis/features/node/runpod.md +0 -674
  195. package/docs/apis/features/node/secure-shell.md +0 -176
  196. package/docs/apis/features/node/semantic-search.md +0 -408
  197. package/docs/apis/features/node/sqlite.md +0 -233
  198. package/docs/apis/features/node/telegram.md +0 -279
  199. package/docs/apis/features/node/transpiler.md +0 -74
  200. package/docs/apis/features/node/tts.md +0 -133
  201. package/docs/apis/features/node/ui.md +0 -701
  202. package/docs/apis/features/node/vault.md +0 -59
  203. package/docs/apis/features/node/vm.md +0 -75
  204. package/docs/apis/features/node/yaml-tree.md +0 -85
  205. package/docs/apis/features/node/yaml.md +0 -176
  206. package/docs/apis/features/web/asset-loader.md +0 -59
  207. package/docs/apis/features/web/container-link.md +0 -192
  208. package/docs/apis/features/web/esbuild.md +0 -54
  209. package/docs/apis/features/web/helpers.md +0 -164
  210. package/docs/apis/features/web/network.md +0 -44
  211. package/docs/apis/features/web/speech.md +0 -69
  212. package/docs/apis/features/web/vault.md +0 -59
  213. package/docs/apis/features/web/vm.md +0 -75
  214. package/docs/apis/features/web/voice.md +0 -84
  215. package/docs/apis/servers/express.md +0 -171
  216. package/docs/apis/servers/mcp.md +0 -238
  217. package/docs/apis/servers/websocket.md +0 -170
  218. package/docs/bootstrap/CLAUDE.md +0 -101
  219. package/docs/bootstrap/SKILL.md +0 -341
  220. package/docs/bootstrap/templates/about-command.ts +0 -41
  221. package/docs/bootstrap/templates/docs-models.ts +0 -22
  222. package/docs/bootstrap/templates/docs-readme.md +0 -43
  223. package/docs/bootstrap/templates/example-feature.ts +0 -53
  224. package/docs/bootstrap/templates/health-endpoint.ts +0 -15
  225. package/docs/bootstrap/templates/luca-cli.ts +0 -30
  226. package/docs/bootstrap/templates/runme.md +0 -54
  227. package/docs/challenges/caching-proxy.md +0 -16
  228. package/docs/challenges/content-db-round-trip.md +0 -14
  229. package/docs/challenges/custom-command.md +0 -9
  230. package/docs/challenges/file-watcher-pipeline.md +0 -11
  231. package/docs/challenges/grep-audit-report.md +0 -15
  232. package/docs/challenges/multi-feature-dashboard.md +0 -14
  233. package/docs/challenges/process-orchestrator.md +0 -17
  234. package/docs/challenges/rest-api-server-with-client.md +0 -12
  235. package/docs/challenges/script-runner-with-vm.md +0 -11
  236. package/docs/challenges/simple-rest-api.md +0 -15
  237. package/docs/challenges/websocket-serve-and-client.md +0 -11
  238. package/docs/challenges/yaml-config-system.md +0 -14
  239. package/docs/command-system-overhaul.md +0 -94
  240. package/docs/documentation-audit.md +0 -134
  241. package/docs/examples/assistant/CORE.md +0 -18
  242. package/docs/examples/assistant/hooks.ts +0 -3
  243. package/docs/examples/assistant/tools.ts +0 -10
  244. package/docs/examples/assistant-hooks-reference.ts +0 -171
  245. package/docs/examples/assistant-with-process-manager.md +0 -84
  246. package/docs/examples/content-db.md +0 -77
  247. package/docs/examples/disk-cache.md +0 -83
  248. package/docs/examples/docker.md +0 -101
  249. package/docs/examples/downloader.md +0 -70
  250. package/docs/examples/entity.md +0 -124
  251. package/docs/examples/esbuild.md +0 -80
  252. package/docs/examples/feature-as-tool-provider.md +0 -143
  253. package/docs/examples/file-manager.md +0 -82
  254. package/docs/examples/fs.md +0 -83
  255. package/docs/examples/git.md +0 -85
  256. package/docs/examples/google-auth.md +0 -88
  257. package/docs/examples/google-calendar.md +0 -94
  258. package/docs/examples/google-docs.md +0 -82
  259. package/docs/examples/google-drive.md +0 -96
  260. package/docs/examples/google-sheets.md +0 -95
  261. package/docs/examples/grep.md +0 -85
  262. package/docs/examples/ink-blocks.md +0 -75
  263. package/docs/examples/ink-renderer.md +0 -41
  264. package/docs/examples/ink.md +0 -103
  265. package/docs/examples/ipc-socket.md +0 -103
  266. package/docs/examples/json-tree.md +0 -91
  267. package/docs/examples/networking.md +0 -58
  268. package/docs/examples/nlp.md +0 -91
  269. package/docs/examples/opener.md +0 -78
  270. package/docs/examples/os.md +0 -72
  271. package/docs/examples/package-finder.md +0 -89
  272. package/docs/examples/postgres.md +0 -91
  273. package/docs/examples/proc.md +0 -81
  274. package/docs/examples/process-manager.md +0 -79
  275. package/docs/examples/python.md +0 -132
  276. package/docs/examples/repl.md +0 -93
  277. package/docs/examples/runpod.md +0 -119
  278. package/docs/examples/secure-shell.md +0 -92
  279. package/docs/examples/sqlite.md +0 -86
  280. package/docs/examples/structured-output-with-assistants.md +0 -144
  281. package/docs/examples/telegram.md +0 -77
  282. package/docs/examples/tts.md +0 -86
  283. package/docs/examples/ui.md +0 -80
  284. package/docs/examples/vault.md +0 -70
  285. package/docs/examples/vm.md +0 -86
  286. package/docs/examples/websocket-ask-and-reply-example.md +0 -128
  287. package/docs/examples/yaml-tree.md +0 -93
  288. package/docs/examples/yaml.md +0 -104
  289. package/docs/ideas/assistant-factory-pattern.md +0 -142
  290. package/docs/in-memory-fs.md +0 -4
  291. package/docs/introspection-audit.md +0 -49
  292. package/docs/introspection.md +0 -164
  293. package/docs/mcp/readme.md +0 -162
  294. package/docs/models.ts +0 -41
  295. package/docs/philosophy.md +0 -86
  296. package/docs/principles.md +0 -7
  297. package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +0 -34
  298. package/docs/prompts/check-for-undocumented-features.md +0 -27
  299. package/docs/prompts/mcp-test-easy-command.md +0 -27
  300. package/docs/scaffolds/client.md +0 -149
  301. package/docs/scaffolds/command.md +0 -120
  302. package/docs/scaffolds/endpoint.md +0 -171
  303. package/docs/scaffolds/feature.md +0 -158
  304. package/docs/scaffolds/selector.md +0 -91
  305. package/docs/scaffolds/server.md +0 -196
  306. package/docs/selectors.md +0 -115
  307. package/docs/sessions/custom-command/attempt-log-2.md +0 -195
  308. package/docs/sessions/file-watcher-pipeline/attempt-log-1.md +0 -728
  309. package/docs/sessions/file-watcher-pipeline/attempt-log-2.md +0 -555
  310. package/docs/sessions/grep-audit-report/attempt-log-1.md +0 -289
  311. package/docs/sessions/multi-feature-dashboard/attempt-log-2.md +0 -679
  312. package/docs/sessions/rest-api-server-with-client/attempt-log-1.md +0 -1
  313. package/docs/sessions/rest-api-server-with-client/attempt-log-3.md +0 -920
  314. package/docs/sessions/simple-rest-api/attempt-log-1.md +0 -593
  315. package/docs/sessions/websocket-serve-and-client/attempt-log-2.md +0 -995
  316. package/docs/tutorials/00-bootstrap.md +0 -166
  317. package/docs/tutorials/01-getting-started.md +0 -106
  318. package/docs/tutorials/02-container.md +0 -210
  319. package/docs/tutorials/03-scripts.md +0 -194
  320. package/docs/tutorials/04-features-overview.md +0 -196
  321. package/docs/tutorials/05-state-and-events.md +0 -171
  322. package/docs/tutorials/06-servers.md +0 -157
  323. package/docs/tutorials/07-endpoints.md +0 -198
  324. package/docs/tutorials/08-commands.md +0 -252
  325. package/docs/tutorials/09-clients.md +0 -162
  326. package/docs/tutorials/10-creating-features.md +0 -203
  327. package/docs/tutorials/11-contentbase.md +0 -191
  328. package/docs/tutorials/12-assistants.md +0 -215
  329. package/docs/tutorials/13-introspection.md +0 -157
  330. package/docs/tutorials/14-type-system.md +0 -174
  331. package/docs/tutorials/15-project-patterns.md +0 -222
  332. package/docs/tutorials/16-google-features.md +0 -534
  333. package/docs/tutorials/17-tui-blocks.md +0 -530
  334. package/docs/tutorials/18-semantic-search.md +0 -334
  335. package/docs/tutorials/19-python-sessions.md +0 -401
  336. package/docs/tutorials/20-browser-esm.md +0 -234
  337. package/src/agi/endpoints/ask.ts +0 -60
  338. package/src/agi/endpoints/conversations/[id].ts +0 -45
  339. package/src/agi/endpoints/conversations.ts +0 -31
  340. package/src/agi/endpoints/experts.ts +0 -37
  341. package/test/assistant-hooks.test.ts +0 -306
  342. package/test/assistant.test.ts +0 -81
  343. package/test/bus.test.ts +0 -134
  344. package/test/clients-servers.test.ts +0 -217
  345. package/test/command.test.ts +0 -267
  346. package/test/container-link.test.ts +0 -274
  347. package/test/conversation.test.ts +0 -220
  348. package/test/features.test.ts +0 -160
  349. package/test/fork-and-research.test.ts +0 -450
  350. package/test/integration.test.ts +0 -787
  351. package/test/interceptor-chain.test.ts +0 -61
  352. package/test/node-container.test.ts +0 -121
  353. package/test/python-session.test.ts +0 -105
  354. package/test/rate-limit.test.ts +0 -272
  355. package/test/semantic-search.test.ts +0 -550
  356. package/test/state.test.ts +0 -121
  357. package/test/vm-context.test.ts +0 -146
  358. package/test/vm-loadmodule.test.ts +0 -213
  359. package/test/websocket-ask.test.ts +0 -101
  360. package/test-integration/assistant.test.ts +0 -138
  361. package/test-integration/assistants-manager.test.ts +0 -113
  362. package/test-integration/claude-code.test.ts +0 -98
  363. package/test-integration/conversation-history.test.ts +0 -205
  364. package/test-integration/conversation.test.ts +0 -137
  365. package/test-integration/elevenlabs.test.ts +0 -55
  366. package/test-integration/google-services.test.ts +0 -80
  367. package/test-integration/helpers.ts +0 -89
  368. package/test-integration/memory.test.ts +0 -204
  369. package/test-integration/openai-codex.test.ts +0 -93
  370. package/test-integration/runpod.test.ts +0 -58
  371. package/test-integration/server-endpoints.test.ts +0 -97
  372. package/test-integration/telegram.test.ts +0 -46
@@ -1,61 +0,0 @@
1
- import { describe, it, expect } from 'bun:test'
2
- import { InterceptorChain } from '../src/agi/lib/interceptor-chain'
3
-
4
- describe('InterceptorChain', () => {
5
- it('runs interceptors in order then the final', async () => {
6
- const chain = new InterceptorChain<{ log: string[] }>()
7
- chain.add(async (ctx, next) => { ctx.log.push('a'); await next() })
8
- chain.add(async (ctx, next) => { ctx.log.push('b'); await next() })
9
-
10
- const ctx = { log: [] as string[] }
11
- await chain.run(ctx, async () => { ctx.log.push('final') })
12
-
13
- expect(ctx.log).toEqual(['a', 'b', 'final'])
14
- })
15
-
16
- it('skips the final when an interceptor does not call next', async () => {
17
- const chain = new InterceptorChain<{ log: string[] }>()
18
- chain.add(async (ctx, _next) => { ctx.log.push('blocker') })
19
- chain.add(async (ctx, next) => { ctx.log.push('never'); await next() })
20
-
21
- const ctx = { log: [] as string[] }
22
- await chain.run(ctx, async () => { ctx.log.push('final') })
23
-
24
- expect(ctx.log).toEqual(['blocker'])
25
- })
26
-
27
- it('allows interceptors to mutate ctx before and after next', async () => {
28
- const chain = new InterceptorChain<{ value: number }>()
29
- chain.add(async (ctx, next) => {
30
- ctx.value *= 2
31
- await next()
32
- ctx.value += 100
33
- })
34
-
35
- const ctx = { value: 5 }
36
- await chain.run(ctx, async () => { ctx.value += 1 })
37
-
38
- expect(ctx.value).toBe(111) // (5*2)=10, final +1=11, after +100=111
39
- })
40
-
41
- it('reports hasInterceptors and size', () => {
42
- const chain = new InterceptorChain<{}>()
43
- expect(chain.hasInterceptors).toBe(false)
44
- expect(chain.size).toBe(0)
45
-
46
- const fn = async (_ctx: {}, next: () => Promise<void>) => { await next() }
47
- chain.add(fn)
48
- expect(chain.hasInterceptors).toBe(true)
49
- expect(chain.size).toBe(1)
50
-
51
- chain.remove(fn)
52
- expect(chain.hasInterceptors).toBe(false)
53
- })
54
-
55
- it('runs just the final when no interceptors are registered', async () => {
56
- const chain = new InterceptorChain<{ ran: boolean }>()
57
- const ctx = { ran: false }
58
- await chain.run(ctx, async () => { ctx.ran = true })
59
- expect(ctx.ran).toBe(true)
60
- })
61
- })
@@ -1,121 +0,0 @@
1
- import { describe, it, expect, mock } from 'bun:test'
2
- import { NodeContainer } from '../src/node/container'
3
-
4
- describe('NodeContainer', () => {
5
- it('creates a container with a uuid', () => {
6
- const c = new NodeContainer()
7
- expect(c.uuid).toBeDefined()
8
- expect(typeof c.uuid).toBe('string')
9
- expect(c.uuid.length).toBeGreaterThan(0)
10
- })
11
-
12
- it('has a cwd', () => {
13
- const c = new NodeContainer()
14
- expect(c.cwd).toBe(process.cwd())
15
- })
16
-
17
- it('accepts a custom cwd', () => {
18
- const c = new NodeContainer({ cwd: '/tmp' })
19
- expect(c.cwd).toBe('/tmp')
20
- })
21
-
22
- it('detects node environment', () => {
23
- const c = new NodeContainer()
24
- expect(c.isNode).toBe(true)
25
- expect(c.isBrowser).toBe(false)
26
- })
27
-
28
- it('has state with started=false initially', () => {
29
- const c = new NodeContainer()
30
- expect(c.currentState.started).toBe(false)
31
- })
32
-
33
- it('start() sets started state and emits event', async () => {
34
- const c = new NodeContainer()
35
- const listener = mock()
36
- c.on('started', listener)
37
- await c.start()
38
- expect(c.currentState.started).toBe(true)
39
- expect(listener).toHaveBeenCalled()
40
- })
41
-
42
- it('has utils with hashObject, stringUtils, uuid, lodash', () => {
43
- const c = new NodeContainer()
44
- expect(typeof c.utils.hashObject).toBe('function')
45
- expect(typeof c.utils.stringUtils.camelCase).toBe('function')
46
- expect(typeof c.utils.stringUtils.kebabCase).toBe('function')
47
- expect(typeof c.utils.uuid).toBe('function')
48
- expect(typeof c.utils.lodash.uniq).toBe('function')
49
- })
50
-
51
- it('utils.hashObject produces deterministic results', () => {
52
- const c = new NodeContainer()
53
- const a = c.utils.hashObject({ x: 1, y: 2 })
54
- const b = c.utils.hashObject({ x: 1, y: 2 })
55
- expect(a).toBe(b)
56
- })
57
-
58
- it('paths.resolve works relative to cwd', () => {
59
- const c = new NodeContainer({ cwd: '/tmp' })
60
- expect(c.paths.resolve('foo')).toBe('/tmp/foo')
61
- expect(c.paths.resolve('foo', 'bar')).toBe('/tmp/foo/bar')
62
- })
63
-
64
- it('paths.join works relative to cwd', () => {
65
- const c = new NodeContainer({ cwd: '/tmp' })
66
- expect(c.paths.join('a', 'b')).toBe('/tmp/a/b')
67
- })
68
-
69
- it('bus() creates a new Bus instance', () => {
70
- const c = new NodeContainer()
71
- const bus = c.bus()
72
- expect(typeof bus.on).toBe('function')
73
- expect(typeof bus.emit).toBe('function')
74
- })
75
-
76
- it('newState() creates a new State instance', () => {
77
- const c = new NodeContainer()
78
- const state = c.newState({ count: 0 })
79
- expect(state.get('count')).toBe(0)
80
- state.set('count', 5)
81
- expect(state.get('count')).toBe(5)
82
- })
83
-
84
- it('event system works (on, emit, off)', () => {
85
- const c = new NodeContainer()
86
- const listener = mock()
87
- c.on('custom', listener)
88
- c.emit('custom', 'hello')
89
- expect(listener).toHaveBeenCalledWith('hello')
90
- c.off('custom', listener)
91
- c.emit('custom', 'world')
92
- expect(listener).toHaveBeenCalledTimes(1)
93
- })
94
-
95
- it('waitFor resolves when event fires', async () => {
96
- const c = new NodeContainer()
97
- const promise = c.waitFor('ready')
98
- c.emit('ready', 42)
99
- expect(await promise).toBe(42)
100
- })
101
-
102
- it('each container gets a unique uuid', () => {
103
- const a = new NodeContainer()
104
- const b = new NodeContainer()
105
- expect(a.uuid).not.toBe(b.uuid)
106
- })
107
-
108
- describe('plugin system', () => {
109
- it('use() with a plugin object calls attach', () => {
110
- const c = new NodeContainer()
111
- const plugin = { attach: mock() }
112
- c.use(plugin)
113
- expect(plugin.attach).toHaveBeenCalledWith(c, {})
114
- })
115
-
116
- it('use() with an unknown feature string throws', () => {
117
- const c = new NodeContainer()
118
- expect(() => c.use('nonexistent_feature_xyz' as any)).toThrow()
119
- })
120
- })
121
- })
@@ -1,105 +0,0 @@
1
- import { describe, it, expect, beforeAll, afterAll } from 'bun:test'
2
-
3
- describe('Python Persistent Session', () => {
4
- let container: any
5
- let python: any
6
-
7
- beforeAll(async () => {
8
- const mod = await import('../src/node.js')
9
- container = mod.default
10
- python = container.feature('python', { dir: container.cwd })
11
- await python.enable()
12
- await python.startSession()
13
- })
14
-
15
- afterAll(async () => {
16
- if (python?.state?.get('sessionActive')) {
17
- await python.stopSession()
18
- }
19
- })
20
-
21
- it('session is active after start', () => {
22
- expect(python.state.get('sessionActive')).toBe(true)
23
- expect(python.state.get('sessionId')).toBeTruthy()
24
- })
25
-
26
- it('run() executes code and captures stdout', async () => {
27
- const result = await python.run('print("hello from bridge")')
28
- expect(result.ok).toBe(true)
29
- expect(result.stdout).toContain('hello from bridge')
30
- })
31
-
32
- it('state persists across calls', async () => {
33
- await python.run('x = 42')
34
- const result = await python.eval('x + 1')
35
- expect(result).toBe(43)
36
- })
37
-
38
- it('variables are injected', async () => {
39
- const result = await python.run('print(name)', { name: 'luca' })
40
- expect(result.ok).toBe(true)
41
- expect(result.stdout).toContain('luca')
42
- })
43
-
44
- it('importModule() works', async () => {
45
- await python.importModule('json')
46
- const result = await python.eval('json.dumps({"a": 1})')
47
- expect(result).toBe('{"a": 1}')
48
- })
49
-
50
- it('call() invokes functions', async () => {
51
- await python.run('def add(a, b): return a + b')
52
- const result = await python.call('add', [3, 4])
53
- expect(result).toBe(7)
54
- })
55
-
56
- it('getLocals() returns namespace', async () => {
57
- await python.run('y = 99')
58
- const locals = await python.getLocals()
59
- expect(locals.y).toBe(99)
60
- expect(locals.x).toBe(42)
61
- })
62
-
63
- it('handles errors gracefully without crashing the session', async () => {
64
- const result = await python.run('raise ValueError("test error")')
65
- expect(result.ok).toBe(false)
66
- expect(result.error).toContain('test error')
67
- // Session should still be alive
68
- const check = await python.run('print("still alive")')
69
- expect(check.ok).toBe(true)
70
- expect(check.stdout).toContain('still alive')
71
- })
72
-
73
- it('resetSession() clears state', async () => {
74
- await python.run('z = 123')
75
- await python.resetSession()
76
- const result = await python.run('print(z)')
77
- expect(result.ok).toBe(false)
78
- expect(result.error).toContain('is not defined')
79
- })
80
-
81
- it('stopSession() cleans up', async () => {
82
- await python.stopSession()
83
- expect(python.state.get('sessionActive')).toBe(false)
84
- expect(python.state.get('sessionId')).toBeNull()
85
- })
86
-
87
- it('can start a new session after stopping', async () => {
88
- await python.startSession()
89
- expect(python.state.get('sessionActive')).toBe(true)
90
- const result = await python.run('print("fresh session")')
91
- expect(result.ok).toBe(true)
92
- expect(result.stdout).toContain('fresh session')
93
- })
94
- })
95
-
96
- describe('Python backward compatibility', () => {
97
- it('execute() still works without a session', async () => {
98
- const mod = await import('../src/node.js')
99
- const container = mod.default
100
- const python = container.feature('python', { dir: container.cwd })
101
- await python.enable()
102
- const result = await python.execute('print("stateless")')
103
- expect(result.stdout).toContain('stateless')
104
- })
105
- })
@@ -1,272 +0,0 @@
1
- import { NodeContainer } from '../src/node/container'
2
- import { Endpoint, type EndpointModule } from '../src/endpoint'
3
-
4
- /** Creates a minimal mock Express app that records registered routes and lets us fire requests. */
5
- function createMockApp() {
6
- const routes: Record<string, Function> = {}
7
-
8
- const app: any = {}
9
- for (const method of ['get', 'post', 'put', 'patch', 'delete']) {
10
- app[method] = (path: string, handler: Function) => {
11
- routes[`${method}:${path}`] = handler
12
- }
13
- }
14
-
15
- /** Simulate a request hitting a registered route */
16
- async function request(method: string, path: string, options: { ip?: string; query?: any; body?: any; params?: any } = {}) {
17
- const handler = routes[`${method}:${path}`]
18
- if (!handler) throw new Error(`No route for ${method} ${path}`)
19
-
20
- let statusCode = 200
21
- let responseBody: any = null
22
- let headersSent = false
23
-
24
- const req = {
25
- ip: options.ip || '127.0.0.1',
26
- socket: { remoteAddress: options.ip || '127.0.0.1' },
27
- query: options.query || {},
28
- body: options.body || {},
29
- params: options.params || {},
30
- }
31
-
32
- const res = {
33
- status(code: number) {
34
- statusCode = code
35
- return res
36
- },
37
- json(body: any) {
38
- responseBody = body
39
- headersSent = true
40
- },
41
- get headersSent() {
42
- return headersSent
43
- },
44
- }
45
-
46
- await handler(req, res)
47
- return { status: statusCode, body: responseBody }
48
- }
49
-
50
- return { app, routes, request }
51
- }
52
-
53
- function createEndpoint(mod: EndpointModule) {
54
- const c = new NodeContainer()
55
- const endpoint = new Endpoint({ path: mod.path }, c.context)
56
- endpoint.load(mod)
57
- return endpoint
58
- }
59
-
60
- describe('Endpoint Rate Limiting', () => {
61
- it('allows requests under the limit', async () => {
62
- const { app, request } = createMockApp()
63
- const endpoint = createEndpoint({
64
- path: '/api/test',
65
- rateLimit: { maxRequests: 5 },
66
- get: async () => ({ ok: true }),
67
- })
68
- endpoint.mount(app)
69
-
70
- for (let i = 0; i < 5; i++) {
71
- const res = await request('get', '/api/test')
72
- expect(res.status).toBe(200)
73
- expect(res.body).toEqual({ ok: true })
74
- }
75
- })
76
-
77
- it('returns 429 when rate limit is exceeded', async () => {
78
- const { app, request } = createMockApp()
79
- const endpoint = createEndpoint({
80
- path: '/api/test',
81
- rateLimit: { maxRequests: 3 },
82
- get: async () => ({ ok: true }),
83
- })
84
- endpoint.mount(app)
85
-
86
- // First 3 should succeed
87
- for (let i = 0; i < 3; i++) {
88
- const res = await request('get', '/api/test')
89
- expect(res.status).toBe(200)
90
- }
91
-
92
- // 4th should be rate limited
93
- const res = await request('get', '/api/test')
94
- expect(res.status).toBe(429)
95
- expect(res.body).toEqual({ error: 'Too Many Requests' })
96
- })
97
-
98
- it('emits error event when rate limited', async () => {
99
- const { app, request } = createMockApp()
100
- const endpoint = createEndpoint({
101
- path: '/api/test',
102
- rateLimit: { maxRequests: 1 },
103
- get: async () => ({ ok: true }),
104
- })
105
- endpoint.mount(app)
106
-
107
- const errors: any[] = []
108
- endpoint.on('error', (err: any) => errors.push(err))
109
-
110
- await request('get', '/api/test') // allowed
111
- await request('get', '/api/test') // rate limited
112
-
113
- expect(errors).toHaveLength(1)
114
- expect(errors[0].message).toContain('Rate limit exceeded')
115
- })
116
-
117
- it('tracks rate limits per IP address', async () => {
118
- const { app, request } = createMockApp()
119
- const endpoint = createEndpoint({
120
- path: '/api/test',
121
- rateLimit: { maxRequests: 2 },
122
- get: async () => ({ ok: true }),
123
- })
124
- endpoint.mount(app)
125
-
126
- // IP A uses its 2 requests
127
- await request('get', '/api/test', { ip: '10.0.0.1' })
128
- await request('get', '/api/test', { ip: '10.0.0.1' })
129
- const limitedA = await request('get', '/api/test', { ip: '10.0.0.1' })
130
- expect(limitedA.status).toBe(429)
131
-
132
- // IP B should still be allowed
133
- const okB = await request('get', '/api/test', { ip: '10.0.0.2' })
134
- expect(okB.status).toBe(200)
135
- })
136
-
137
- it('supports per-method rate limits that override endpoint-level', async () => {
138
- const { app, request } = createMockApp()
139
- const endpoint = createEndpoint({
140
- path: '/api/test',
141
- rateLimit: { maxRequests: 10 }, // general: 10 rps
142
- getRateLimit: { maxRequests: 2 }, // GET: 2 rps
143
- get: async () => ({ method: 'get' }),
144
- post: async () => ({ method: 'post' }),
145
- })
146
- endpoint.mount(app)
147
-
148
- // GET should be limited at 2
149
- await request('get', '/api/test')
150
- await request('get', '/api/test')
151
- const limited = await request('get', '/api/test')
152
- expect(limited.status).toBe(429)
153
-
154
- // POST should still use the general limit of 10
155
- for (let i = 0; i < 10; i++) {
156
- const res = await request('post', '/api/test')
157
- expect(res.status).toBe(200)
158
- }
159
- const postLimited = await request('post', '/api/test')
160
- expect(postLimited.status).toBe(429)
161
- })
162
-
163
- it('does not rate limit when no rateLimit is set', async () => {
164
- const { app, request } = createMockApp()
165
- const endpoint = createEndpoint({
166
- path: '/api/test',
167
- get: async () => ({ ok: true }),
168
- })
169
- endpoint.mount(app)
170
-
171
- // Should handle many requests without 429
172
- for (let i = 0; i < 100; i++) {
173
- const res = await request('get', '/api/test')
174
- expect(res.status).toBe(200)
175
- }
176
- })
177
-
178
- it('resets the window after time passes', async () => {
179
- const { app, request } = createMockApp()
180
- const endpoint = createEndpoint({
181
- path: '/api/test',
182
- rateLimit: { maxRequests: 2, windowSeconds: 0.1 },
183
- get: async () => ({ ok: true }),
184
- })
185
- endpoint.mount(app)
186
-
187
- await request('get', '/api/test')
188
- await request('get', '/api/test')
189
- const limited = await request('get', '/api/test')
190
- expect(limited.status).toBe(429)
191
-
192
- // Wait for the 100ms window to expire
193
- await new Promise((resolve) => setTimeout(resolve, 150))
194
-
195
- const allowed = await request('get', '/api/test')
196
- expect(allowed.status).toBe(200)
197
- })
198
-
199
- it('rateLimitFor returns correct config', () => {
200
- const endpoint = createEndpoint({
201
- path: '/api/test',
202
- rateLimit: { maxRequests: 10 },
203
- postRateLimit: { maxRequests: 2 },
204
- get: async () => ({}),
205
- post: async () => ({}),
206
- })
207
-
208
- // GET falls back to endpoint-level rateLimit
209
- expect(endpoint.rateLimitFor('get')).toEqual({ maxRequests: 10 })
210
- // POST uses its own override
211
- expect(endpoint.rateLimitFor('post')).toEqual({ maxRequests: 2 })
212
- // DELETE has no handler but we can still query the config
213
- expect(endpoint.rateLimitFor('delete')).toEqual({ maxRequests: 10 })
214
- })
215
-
216
- it('rateLimiter.reset() clears all tracking', async () => {
217
- const { app, request } = createMockApp()
218
- const endpoint = createEndpoint({
219
- path: '/api/test',
220
- rateLimit: { maxRequests: 1 },
221
- get: async () => ({ ok: true }),
222
- })
223
- endpoint.mount(app)
224
-
225
- await request('get', '/api/test')
226
- const limited = await request('get', '/api/test')
227
- expect(limited.status).toBe(429)
228
-
229
- // Reset the limiter
230
- endpoint.rateLimiter.reset()
231
-
232
- const allowed = await request('get', '/api/test')
233
- expect(allowed.status).toBe(200)
234
- })
235
-
236
- it('does not increment requestCount on rate-limited requests', async () => {
237
- const { app, request } = createMockApp()
238
- const endpoint = createEndpoint({
239
- path: '/api/test',
240
- rateLimit: { maxRequests: 2 },
241
- get: async () => ({ ok: true }),
242
- })
243
- endpoint.mount(app)
244
-
245
- await request('get', '/api/test')
246
- await request('get', '/api/test')
247
- await request('get', '/api/test') // rate limited
248
-
249
- expect(endpoint.state.get('requestCount')).toBe(2)
250
- })
251
-
252
- it('includes 429 in OpenAPI spec when rate limit is configured', () => {
253
- const endpoint = createEndpoint({
254
- path: '/api/test',
255
- rateLimit: { maxRequests: 5 },
256
- get: async () => ({}),
257
- })
258
-
259
- const spec = endpoint.toOpenAPIPathItem()
260
- expect(spec.get.responses['429']).toEqual({ description: 'Rate limit exceeded' })
261
- })
262
-
263
- it('omits 429 from OpenAPI spec when no rate limit is configured', () => {
264
- const endpoint = createEndpoint({
265
- path: '/api/test',
266
- get: async () => ({}),
267
- })
268
-
269
- const spec = endpoint.toOpenAPIPathItem()
270
- expect(spec.get.responses['429']).toBeUndefined()
271
- })
272
- })