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,11 +1,13 @@
1
1
  import { z } from 'zod'
2
2
  import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
3
- import { type AvailableFeatures } from '@soederpop/luca/feature'
3
+ import { type AvailableFeatures } from 'luca/feature'
4
4
  import { Feature } from '../feature.js'
5
5
  import type { Assistant } from './assistant.js'
6
+ import type { ConversationHistory, ConversationMeta, ConversationRecord } from './conversation-history.js'
6
7
  import type { InterceptorFn, InterceptorPoint, InterceptorPoints } from '../lib/interceptor-chain.js'
8
+ import hashObject from '../../hash-object.js'
7
9
 
8
- declare module '@soederpop/luca/feature' {
10
+ declare module 'luca/feature' {
9
11
  interface AvailableFeatures {
10
12
  assistantsManager: typeof AssistantsManager
11
13
  }
@@ -200,15 +202,15 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
200
202
  let meta: Record<string, any> | undefined
201
203
 
202
204
  if (hasAbout) {
203
- about = fs.readFileSync(`${folder}/ABOUT.md`, 'utf8')
205
+ about = fs.readFileSync(`${folder}/ABOUT.md`, 'utf8') as string
204
206
  }
205
207
 
206
208
  try {
207
- const coreContent = fs.readFileSync(`${folder}/CORE.md`, 'utf8')
209
+ const coreContent = fs.readFileSync(`${folder}/CORE.md`, 'utf8') as string
208
210
  const fmMatch = coreContent.match(/^---\r?\n([\s\S]*?)\r?\n---/)
209
211
  if (fmMatch) {
210
212
  const yaml = this.container.feature('yaml')
211
- meta = yaml.parse(fmMatch[1])
213
+ meta = yaml.parse(fmMatch[1]!)
212
214
  }
213
215
  } catch {
214
216
  // CORE.md exists but couldn't be parsed — skip meta
@@ -275,7 +277,22 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
275
277
  * @returns {AssistantEntry[]} All discovered entries
276
278
  */
277
279
  list(): AssistantEntry[] {
278
- return Object.values(this.entries)
280
+ const discovered = Object.values(this.entries)
281
+ const discoveredNames = new Set(discovered.map((e) => e.name))
282
+
283
+ // Include registered factories that weren't discovered on disk
284
+ const registeredOnly = Object.keys(this.factories)
285
+ .filter((name) => !discoveredNames.has(name))
286
+ .map((name): AssistantEntry => ({
287
+ name,
288
+ folder: '',
289
+ hasCorePrompt: false,
290
+ hasTools: false,
291
+ hasHooks: false,
292
+ hasVoice: false,
293
+ }))
294
+
295
+ return [...discovered, ...registeredOnly]
279
296
  }
280
297
 
281
298
  /**
@@ -378,6 +395,105 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
378
395
  }
379
396
  }
380
397
 
398
+ /**
399
+ * Reload tools, hooks, and system prompt from disk for active assistants.
400
+ * When called with a name, reloads only that assistant. When called without
401
+ * arguments, reloads all active instances.
402
+ *
403
+ * @param {string} [name] - Optional assistant name to reload. Omit to reload all.
404
+ * @returns {{ reloaded: string[] }} Names of assistants that were reloaded
405
+ * @throws {Error} If a specific name is given but no active instance exists for it
406
+ *
407
+ * @example
408
+ * ```typescript
409
+ * manager.reload('researcher') // reload one
410
+ * manager.reload() // reload all active
411
+ * ```
412
+ */
413
+ reload(name?: string): { reloaded: string[] } {
414
+ const reloaded: string[] = []
415
+
416
+ if (name) {
417
+ const instance = this.instances[name]
418
+ if (!instance) {
419
+ throw new Error(
420
+ `No active assistant "${name}" to reload. Active: ${Object.keys(this.instances).join(', ') || '(none)'}`
421
+ )
422
+ }
423
+ instance.reload()
424
+ reloaded.push(name)
425
+ } else {
426
+ for (const [key, instance] of Object.entries(this.instances)) {
427
+ instance.reload()
428
+ reloaded.push(key)
429
+ }
430
+ }
431
+
432
+ return { reloaded }
433
+ }
434
+
435
+ /**
436
+ * Build the thread prefix for a given assistant name, matching the
437
+ * convention used by the Assistant class: `name:cwdHash:`.
438
+ * This allows history lookups without an active instance.
439
+ *
440
+ * @param {string} assistantId - The assistant name
441
+ * @returns {string} The thread prefix
442
+ */
443
+ threadPrefixFor(assistantId: string): string {
444
+ const cwdHash = hashObject(this.container.cwd).slice(0, 8)
445
+ return `${assistantId}:${cwdHash}:`
446
+ }
447
+
448
+ /**
449
+ * Load conversation history for an assistant. Works whether or not the
450
+ * assistant is currently instantiated — uses the thread prefix convention
451
+ * to query the conversationHistory feature directly.
452
+ *
453
+ * @param {string} assistantId - The assistant name (e.g. 'researcher')
454
+ * @param {object} [options] - Query options
455
+ * @param {number} [options.limit] - Maximum number of records to return
456
+ * @param {boolean} [options.includeMessages] - Load full records with messages (default: false, returns metadata only)
457
+ * @param {string} [options.thread] - Load a specific thread ID instead of all threads for this assistant
458
+ * @returns {Promise<ConversationMeta[] | ConversationRecord[]>} Metadata or full records, newest first
459
+ *
460
+ * @example
461
+ * ```typescript
462
+ * // List recent sessions (metadata only)
463
+ * const sessions = await manager.loadAssistantHistory('researcher', { limit: 5 })
464
+ *
465
+ * // Load full records with messages
466
+ * const full = await manager.loadAssistantHistory('researcher', { includeMessages: true, limit: 3 })
467
+ *
468
+ * // Load a specific thread
469
+ * const thread = await manager.loadAssistantHistory('researcher', { thread: 'researcher:abc12345:2026-04-12' })
470
+ * ```
471
+ */
472
+ async loadAssistantHistory(
473
+ assistantId: string,
474
+ options?: { limit?: number; includeMessages?: boolean; thread?: string },
475
+ ): Promise<ConversationMeta[] | ConversationRecord[]> {
476
+ const history = this.container.feature('conversationHistory') as ConversationHistory
477
+
478
+ if (options?.thread) {
479
+ const record = await history.findByThread(options.thread)
480
+ return record ? [record] : []
481
+ }
482
+
483
+ const prefix = this.threadPrefixFor(assistantId)
484
+ const metas = await history.findByThreadPrefix(prefix)
485
+ const limited = options?.limit ? metas.slice(0, options.limit) : metas
486
+
487
+ if (!options?.includeMessages) return limited
488
+
489
+ const records: ConversationRecord[] = []
490
+ for (const meta of limited) {
491
+ const record = await history.load(meta.id)
492
+ if (record) records.push(record)
493
+ }
494
+ return records
495
+ }
496
+
381
497
  /**
382
498
  * Returns a previously created assistant instance by name.
383
499
  *
@@ -4,7 +4,7 @@ import { Feature } from '../feature.js'
4
4
  import type { Assistant } from './assistant.js'
5
5
  import type { ToolCallCtx } from '../lib/interceptor-chain.js'
6
6
 
7
- declare module '@soederpop/luca/feature' {
7
+ declare module 'luca/feature' {
8
8
  interface AvailableFeatures {
9
9
  autoAssistant: typeof AutonomousAssistant
10
10
  }
@@ -3,7 +3,7 @@ import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '.
3
3
  import { Feature } from '../feature.js'
4
4
  import type { Helper } from '../../helper.js'
5
5
 
6
- declare module '@soederpop/luca/feature' {
6
+ declare module 'luca/feature' {
7
7
  interface AvailableFeatures {
8
8
  browserUse: typeof BrowserUse
9
9
  }
@@ -220,6 +220,12 @@ export class BrowserUse extends Feature<BrowserUseState, BrowserUseOptions> {
220
220
  description: 'List all active browser sessions.',
221
221
  schema: z.object({}).describe('List all currently active browser sessions with their names and status.'),
222
222
  },
223
+ browserSetHeaded: {
224
+ description: 'Toggle browser visibility. Use headed mode to show the browser window, or headless mode to hide it.',
225
+ schema: z.object({
226
+ headed: z.boolean().describe('true to show the browser window (headed), false to hide it (headless)'),
227
+ }).describe('Toggle the browser between headed (visible window) and headless mode. When the user asks to see the browser or hide it, use this tool.'),
228
+ },
223
229
  }
224
230
 
225
231
  static { Feature.register(this, 'browserUse') }
@@ -490,6 +496,15 @@ export class BrowserUse extends Feature<BrowserUseState, BrowserUseOptions> {
490
496
  return this.exec('sessions')
491
497
  }
492
498
 
499
+ /**
500
+ * Toggle headed/headless mode
501
+ * @param headed - true for visible browser window, false for headless
502
+ */
503
+ setHeaded(headed: boolean): { success: boolean; headed: boolean } {
504
+ this.state.set('headed', headed)
505
+ return { success: true, headed }
506
+ }
507
+
493
508
  /**
494
509
  * Hover over an element
495
510
  * @param index - Element index
@@ -648,6 +663,10 @@ export class BrowserUse extends Feature<BrowserUseState, BrowserUseOptions> {
648
663
  async browserGetAttributes(options: { index: string }) {
649
664
  return this.getAttributes(options.index)
650
665
  }
666
+
667
+ async browserSetHeaded(options: { headed: boolean }) {
668
+ return this.setHeaded(options.headed)
669
+ }
651
670
  }
652
671
 
653
672
  export default BrowserUse
@@ -1,10 +1,10 @@
1
1
  // @ts-nocheck
2
2
  import { z } from 'zod'
3
3
  import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
4
- import { type AvailableFeatures } from '@soederpop/luca/feature'
4
+ import { type AvailableFeatures } from 'luca/feature'
5
5
  import { Feature } from '../feature.js'
6
6
 
7
- declare module '@soederpop/luca/feature' {
7
+ declare module 'luca/feature' {
8
8
  interface AvailableFeatures {
9
9
  claudeCode: typeof ClaudeCode
10
10
  }
@@ -184,6 +184,12 @@ export const ClaudeCodeOptionsSchema = FeatureOptionsSchema.extend({
184
184
  skillsFolders: z.array(z.string()).optional().describe('Directories containing Claude Code skills to load into sessions'),
185
185
  /** Launch Claude Code with a Chrome browser tool. */
186
186
  chrome: z.boolean().optional().describe('Launch Claude Code with a Chrome browser tool'),
187
+ /** Base URL for the Anthropic API. Injected as ANTHROPIC_BASE_URL env var. */
188
+ baseURL: z.string().optional().describe('Base URL for the Anthropic API, injected as ANTHROPIC_BASE_URL'),
189
+ /** Auth token for the Anthropic API. Injected as ANTHROPIC_AUTH_TOKEN env var. */
190
+ authToken: z.string().optional().describe('Auth token for the Anthropic API, injected as ANTHROPIC_AUTH_TOKEN'),
191
+ /** Use local models. Sets baseURL and model from LOCAL_CHAT_ENDPOINT and LOCAL_CODER_MODEL env vars. */
192
+ local: z.boolean().optional().describe('Use local models, sets baseURL to LOCAL_CHAT_ENDPOINT and model to LOCAL_CODER_MODEL'),
187
193
  })
188
194
 
189
195
  export const ClaudeCodeEventsSchema = FeatureEventsSchema.extend({
@@ -269,6 +275,12 @@ export interface RunOptions {
269
275
  settingsFile?: string
270
276
  /** Launch Claude Code with a Chrome browser tool. */
271
277
  chrome?: boolean
278
+ /** Base URL for the Anthropic API. Injected as ANTHROPIC_BASE_URL in the subprocess env. */
279
+ baseURL?: string
280
+ /** Auth token for the Anthropic API. Injected as ANTHROPIC_AUTH_TOKEN in the subprocess env. */
281
+ authToken?: string
282
+ /** Use local models. Sets baseURL to LOCAL_CHAT_ENDPOINT (or http://localhost:1234) and model to LOCAL_CODER_MODEL (or qwen/qwen3.6-27b). */
283
+ local?: boolean
272
284
  }
273
285
 
274
286
  /**
@@ -506,7 +518,8 @@ export class ClaudeCode extends Feature<ClaudeCodeState, ClaudeCodeOptions> {
506
518
  args.push('--include-partial-messages')
507
519
  }
508
520
 
509
- const model = options.model ?? this.options.model
521
+ const isLocal = options.local ?? this.options.local
522
+ const model = options.model ?? this.options.model ?? (isLocal ? (process.env.LOCAL_CODER_MODEL || 'qwen/qwen3.6-27b') : undefined)
510
523
  if (model) args.push('--model', model)
511
524
 
512
525
  const systemPrompt = options.systemPrompt ?? this.options.systemPrompt
@@ -613,6 +626,39 @@ export class ClaudeCode extends Feature<ClaudeCodeState, ClaudeCodeOptions> {
613
626
  return args
614
627
  }
615
628
 
629
+ /**
630
+ * Build the environment object for a claude CLI invocation.
631
+ * Injects ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN when baseURL/authToken are set,
632
+ * or when local mode is enabled.
633
+ *
634
+ * @param {RunOptions} options - Session options
635
+ * @returns {Record<string, string>} Environment variables
636
+ */
637
+ private buildEnv(options: RunOptions = {}): Record<string, string> {
638
+ const env = { ...process.env }
639
+ const isLocal = options.local ?? this.options.local
640
+
641
+ if (isLocal) {
642
+ const baseURL = process.env.LOCAL_CHAT_ENDPOINT || 'http://localhost:1234'
643
+ env.ANTHROPIC_BASE_URL = baseURL
644
+ if (!options.authToken) {
645
+ env.ANTHROPIC_AUTH_TOKEN = process.env.LOCAL_CHAT_AUTH_TOKEN || 'sk-anticropic-00000000000000000000001'
646
+ }
647
+ }
648
+
649
+ const baseURL = options.baseURL
650
+ if (baseURL) {
651
+ env.ANTHROPIC_BASE_URL = baseURL
652
+ }
653
+
654
+ const authToken = options.authToken
655
+ if (authToken) {
656
+ env.ANTHROPIC_AUTH_TOKEN = authToken
657
+ }
658
+
659
+ return env
660
+ }
661
+
616
662
  /**
617
663
  * Create a unique session ID.
618
664
  *
@@ -780,7 +826,7 @@ export class ClaudeCode extends Feature<ClaudeCodeState, ClaudeCodeOptions> {
780
826
  stdout: 'pipe',
781
827
  stderr: 'pipe',
782
828
  stdin: Buffer.from(prompt),
783
- environment: { ...process.env },
829
+ environment: this.buildEnv(options),
784
830
  })
785
831
 
786
832
  this.updateSession(id, { process: proc })
@@ -842,7 +888,7 @@ export class ClaudeCode extends Feature<ClaudeCodeState, ClaudeCodeOptions> {
842
888
  stdout: 'pipe',
843
889
  stderr: 'pipe',
844
890
  stdin: Buffer.from(prompt),
845
- environment: { ...process.env },
891
+ environment: this.buildEnv(options),
846
892
  })
847
893
 
848
894
  this.updateSession(id, { process: proc })
@@ -4,7 +4,7 @@ import { Feature } from '../feature.js'
4
4
  import type { Helper } from '../../helper.js'
5
5
  import type { ChildProcess } from '../../node/features/proc.js'
6
6
 
7
- declare module '@soederpop/luca/feature' {
7
+ declare module 'luca/feature' {
8
8
  interface AvailableFeatures {
9
9
  codingTools: typeof CodingTools
10
10
  }
@@ -1,16 +1,31 @@
1
1
  import { z } from 'zod'
2
2
  import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
3
- import { type AvailableFeatures } from '@soederpop/luca/feature'
3
+ import { type AvailableFeatures } from 'luca/feature'
4
4
  import { Feature } from '../feature.js'
5
- import type { DiskCache } from '@soederpop/luca/node/container'
5
+ import type { DiskCache } from 'luca/node/container'
6
+ import type { OpenAIClient } from '../../clients/openai'
6
7
  import type { Message } from './conversation'
7
8
 
8
- declare module '@soederpop/luca/feature' {
9
+ declare module 'luca/feature' {
9
10
  interface AvailableFeatures {
10
11
  conversationHistory: typeof ConversationHistory
11
12
  }
12
13
  }
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
+
14
29
  export interface ConversationRecord {
15
30
  id: string
16
31
  title: string
@@ -21,6 +36,8 @@ export interface ConversationRecord {
21
36
  createdAt: string
22
37
  updatedAt: string
23
38
  messageCount: number
39
+ tokenUsage?: TokenUsage
40
+ cost?: CostInfo
24
41
  metadata: Record<string, any>
25
42
  }
26
43
 
@@ -160,12 +177,15 @@ export class ConversationHistory extends Feature<ConversationHistoryState, Conve
160
177
  messages: Message[]
161
178
  tags?: string[]
162
179
  thread?: string
180
+ tokenUsage?: TokenUsage
181
+ cost?: CostInfo
163
182
  metadata?: Record<string, any>
164
183
  }): Promise<ConversationRecord> {
165
184
  const now = new Date().toISOString()
185
+ const title = opts.title || await this.autoTitle(opts.messages)
166
186
  const record: ConversationRecord = {
167
187
  id: opts.id || crypto.randomUUID(),
168
- title: opts.title || 'Untitled',
188
+ title,
169
189
  model: opts.model || 'unknown',
170
190
  messages: opts.messages,
171
191
  tags: opts.tags || [],
@@ -173,6 +193,8 @@ export class ConversationHistory extends Feature<ConversationHistoryState, Conve
173
193
  createdAt: now,
174
194
  updatedAt: now,
175
195
  messageCount: opts.messages.length,
196
+ tokenUsage: opts.tokenUsage,
197
+ cost: opts.cost,
176
198
  metadata: opts.metadata || {},
177
199
  }
178
200
 
@@ -408,6 +430,161 @@ export class ConversationHistory extends Feature<ConversationHistoryState, Conve
408
430
  return count
409
431
  }
410
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
+
411
588
  // -- index management --
412
589
 
413
590
  private async getIndex(): Promise<string[]> {