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,13 +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 { OpenAIClient } from '../../clients/openai';
6
6
  import type OpenAI from 'openai';
7
7
  import type { ConversationHistory } from './conversation-history';
8
- import { countMessageTokens, getContextWindow } from '../lib/token-counter.js';
8
+ import { countMessageTokens, getContextWindow, calculateCost } from '../lib/token-counter.js';
9
9
 
10
- declare module '@soederpop/luca/feature' {
10
+ declare module 'luca/feature' {
11
11
  interface AvailableFeatures {
12
12
  conversation: typeof Conversation
13
13
  }
@@ -37,6 +37,20 @@ export interface ConversationMCPServer {
37
37
  }
38
38
  }
39
39
 
40
+ const INPUT_TOKEN_SIZES: Record<string, number> = {
41
+ tiny: 8_000,
42
+ small: 16_000,
43
+ medium: 32_000,
44
+ large: 64_000,
45
+ xlarge: 256_000,
46
+ }
47
+
48
+ function resolveMaxInputTokens(value: number | string | undefined): number | undefined {
49
+ if (value == null) return undefined
50
+ if (typeof value === 'number') return value
51
+ return INPUT_TOKEN_SIZES[value]
52
+ }
53
+
40
54
  export const ConversationOptionsSchema = FeatureOptionsSchema.extend({
41
55
  /** A unique identifier for the conversation */
42
56
  id: z.string().optional().describe('A unique identifier for the conversation'),
@@ -87,6 +101,12 @@ export const ConversationOptionsSchema = FeatureOptionsSchema.extend({
87
101
  contextWindow: z.number().optional().describe('Override the inferred context window size for this model'),
88
102
  /** Number of recent messages to preserve after compaction (default 4) */
89
103
  compactKeepRecent: z.number().optional().describe('Number of recent messages to preserve after compaction (default 4)'),
104
+
105
+ /** Maximum input tokens to send to the API. When set, older messages are trimmed to stay within this budget, keeping the system prompt and most recent messages. Useful for avoiding long-context pricing tiers. Accepts a number or a named size: tiny (8k), small (16k), medium (32k), large (64k), xlarge (256k — max before long-context pricing). */
106
+ maxInputTokens: z.union([
107
+ z.number(),
108
+ z.enum(['tiny', 'small', 'medium', 'large', 'xlarge']),
109
+ ]).default('large').describe('Maximum input tokens. Accepts a number or a named size: tiny (8k), small (16k), medium (32k), large (64k), xlarge (256k). Defaults to large (64k)'),
90
110
  })
91
111
 
92
112
  export const ConversationStateSchema = FeatureStateSchema.extend({
@@ -103,7 +123,14 @@ export const ConversationStateSchema = FeatureStateSchema.extend({
103
123
  prompt: z.number().describe('Total prompt tokens consumed'),
104
124
  completion: z.number().describe('Total completion tokens consumed'),
105
125
  total: z.number().describe('Total tokens consumed'),
106
- }).describe('Cumulative token usage statistics'),
126
+ cachedTokens: z.number().describe('Input tokens served from cache (billed at reduced rate)'),
127
+ reasoningTokens: z.number().describe('Output tokens used for reasoning (o-series models)'),
128
+ }).describe('Cumulative token usage statistics including detail breakdowns from the API'),
129
+ cost: z.object({
130
+ inputCost: z.number().describe('Estimated cost in dollars for input tokens'),
131
+ outputCost: z.number().describe('Estimated cost in dollars for output tokens'),
132
+ totalCost: z.number().describe('Estimated total cost in dollars'),
133
+ }).describe('Running cost estimate based on cumulative token usage and model pricing'),
107
134
  estimatedInputTokens: z.number().describe('Estimated input token count for the current messages array'),
108
135
  compactionCount: z.number().describe('Number of times compact() has been called'),
109
136
  contextWindow: z.number().describe('The context window size for the current model'),
@@ -267,7 +294,8 @@ export class Conversation extends Feature<ConversationState, ConversationOptions
267
294
  toolCalls: 0,
268
295
  api: this.apiMode,
269
296
  lastResponseId: null,
270
- tokenUsage: { prompt: 0, completion: 0, total: 0 },
297
+ tokenUsage: { prompt: 0, completion: 0, total: 0, cachedTokens: 0, reasoningTokens: 0 },
298
+ cost: { inputCost: 0, outputCost: 0, totalCost: 0 },
271
299
  estimatedInputTokens: 0,
272
300
  compactionCount: 0,
273
301
  contextWindow: this.options.contextWindow || getContextWindow(this.options.model || 'gpt-5'),
@@ -598,7 +626,15 @@ export class Conversation extends Feature<ConversationState, ConversationOptions
598
626
  ? messages[0]
599
627
  : null
600
628
 
601
- const recentMessages = messages.slice(-keepRecent)
629
+ let sliceStart = messages.length - keepRecent
630
+ // Walk back to avoid splitting a tool call group — if we'd start on a tool message,
631
+ // include the preceding assistant message (and its full tool response block)
632
+ if (sliceStart > 0) {
633
+ while (sliceStart > 0 && messages[sliceStart]?.role === 'tool') {
634
+ sliceStart--
635
+ }
636
+ }
637
+ const recentMessages = messages.slice(sliceStart)
602
638
 
603
639
  const newMessages: Message[] = []
604
640
  if (systemMessage) newMessages.push(systemMessage)
@@ -724,16 +760,20 @@ export class Conversation extends Feature<ConversationState, ConversationOptions
724
760
  let raw: string
725
761
 
726
762
  if (this.apiMode === 'responses') {
727
- const previousResponseId = this.state.get('lastResponseId') || undefined
763
+ // When maxInputTokens is set, skip previous_response_id continuation
764
+ // so we control exactly how many tokens the API processes (server-side
765
+ // context from previous_response_id would accumulate unbounded).
766
+ const canChain = !this.options.maxInputTokens
767
+ const previousResponseId = canChain ? (this.state.get('lastResponseId') || undefined) : undefined
728
768
  let input: OpenAI.Responses.ResponseInput
729
769
 
730
770
  if (previousResponseId) {
731
771
  // Can chain via previous_response_id — only send the new user message
732
772
  input = [this.toResponsesUserMessage(content)]
733
773
  } else {
734
- // No previous response ID (first call or resumed from disk).
735
- // Convert full message history to Responses API input so the model has context.
736
- input = this.messagesToResponsesInput()
774
+ // No previous response ID (first call, resumed from disk, or maxInputTokens active).
775
+ // Convert (possibly trimmed) message history to Responses API input.
776
+ input = this.messagesToResponsesInput(this.getMessagesWithinBudget())
737
777
  }
738
778
 
739
779
  raw = await this.runResponsesLoop({
@@ -816,10 +856,10 @@ export class Conversation extends Feature<ConversationState, ConversationOptions
816
856
  * Convert the full Chat Completions message history into Responses API input items.
817
857
  * Used when resuming a conversation without a previous_response_id.
818
858
  */
819
- private messagesToResponsesInput(): OpenAI.Responses.ResponseInput {
859
+ private messagesToResponsesInput(messages?: Message[]): OpenAI.Responses.ResponseInput {
820
860
  const input: OpenAI.Responses.ResponseInput = []
821
861
 
822
- for (const msg of this.messages) {
862
+ for (const msg of (messages || this.messages)) {
823
863
  if (msg.role === 'system' || msg.role === 'developer') {
824
864
  // System/developer messages are handled via the instructions parameter
825
865
  continue
@@ -917,9 +957,15 @@ export class Conversation extends Feature<ConversationState, ConversationOptions
917
957
  const lastResponseId = this.state.get('lastResponseId')
918
958
  const responseMeta = lastResponseId ? { lastResponseId } : {}
919
959
 
960
+ // Grab the live token usage and cost from state
961
+ const tokenUsage = this.state.get('tokenUsage')!
962
+ const cost = this.state.get('cost')!
963
+
920
964
  if (existing) {
921
965
  existing.messages = this.messages
922
966
  existing.model = this.model
967
+ existing.tokenUsage = tokenUsage
968
+ existing.cost = cost
923
969
  if (opts?.title) existing.title = opts.title
924
970
  if (opts?.tags) existing.tags = opts.tags
925
971
  if (opts?.thread) existing.thread = opts.thread
@@ -930,11 +976,13 @@ export class Conversation extends Feature<ConversationState, ConversationOptions
930
976
 
931
977
  return this.history.create({
932
978
  id,
933
- title: opts?.title || this.options.title || 'Untitled',
979
+ title: opts?.title || this.options.title,
934
980
  model: this.model,
935
981
  messages: this.messages,
936
982
  tags: opts?.tags || this.options.tags || [],
937
983
  thread: opts?.thread || this.options.thread || this.state.get('thread'),
984
+ tokenUsage,
985
+ cost,
938
986
  metadata: { ...responseMeta, ...(opts?.metadata || this.options.metadata || {}) },
939
987
  })
940
988
  }
@@ -1163,6 +1211,16 @@ export class Conversation extends Feature<ConversationState, ConversationOptions
1163
1211
  return accumulated || finalText
1164
1212
  }
1165
1213
 
1214
+ /** Recalculate the running cost estimate from current token usage and update state. */
1215
+ private updateCost() {
1216
+ const tokenUsage = this.state.get('tokenUsage')!
1217
+ const { inputCost, outputCost, totalCost } = calculateCost(this.model, tokenUsage.prompt, tokenUsage.completion, {
1218
+ cachedTokens: tokenUsage.cachedTokens,
1219
+ reasoningTokens: tokenUsage.reasoningTokens,
1220
+ })
1221
+ this.state.set('cost', { inputCost, outputCost, totalCost })
1222
+ }
1223
+
1166
1224
  /** Apply Responses API usage stats to this conversation's token usage counters. */
1167
1225
  private applyResponsesUsage(usage?: OpenAI.Responses.ResponseUsage) {
1168
1226
  if (!usage) return
@@ -1171,7 +1229,10 @@ export class Conversation extends Feature<ConversationState, ConversationOptions
1171
1229
  prompt: prev.prompt + (usage.input_tokens || 0),
1172
1230
  completion: prev.completion + (usage.output_tokens || 0),
1173
1231
  total: prev.total + (usage.total_tokens || 0),
1232
+ cachedTokens: prev.cachedTokens + (usage.input_tokens_details?.cached_tokens || 0),
1233
+ reasoningTokens: prev.reasoningTokens + (usage.output_tokens_details?.reasoning_tokens || 0),
1174
1234
  })
1235
+ this.updateCost()
1175
1236
  }
1176
1237
 
1177
1238
  /**
@@ -1208,7 +1269,7 @@ export class Conversation extends Feature<ConversationState, ConversationOptions
1208
1269
  try {
1209
1270
  const stream = await this.openai.raw.chat.completions.create({
1210
1271
  model: this.model,
1211
- messages: this.messages,
1272
+ messages: this.sanitizeMessages(this.getMessagesWithinBudget()),
1212
1273
  stream: true,
1213
1274
  ...(toolsParam ? { tools: toolsParam, tool_choice: 'auto' } : {}),
1214
1275
  ...(this.maxTokens ? { [this.maxTokensParam]: this.maxTokens } : {}),
@@ -1258,8 +1319,11 @@ export class Conversation extends Feature<ConversationState, ConversationOptions
1258
1319
  this.state.set('tokenUsage', {
1259
1320
  prompt: prev.prompt + (chunk.usage.prompt_tokens || 0),
1260
1321
  completion: prev.completion + (chunk.usage.completion_tokens || 0),
1261
- total: prev.total + (chunk.usage.total_tokens || 0)
1322
+ total: prev.total + (chunk.usage.total_tokens || 0),
1323
+ cachedTokens: prev.cachedTokens + (chunk.usage.prompt_tokens_details?.cached_tokens || 0),
1324
+ reasoningTokens: prev.reasoningTokens + (chunk.usage.completion_tokens_details?.reasoning_tokens || 0),
1262
1325
  })
1326
+ this.updateCost()
1263
1327
  }
1264
1328
  }
1265
1329
  } finally {
@@ -1310,6 +1374,113 @@ export class Conversation extends Feature<ConversationState, ConversationOptions
1310
1374
  return accumulated
1311
1375
  }
1312
1376
 
1377
+ /**
1378
+ * Returns the messages array trimmed to fit within the maxInputTokens budget.
1379
+ * Keeps the system/developer message and drops oldest atomic groups first.
1380
+ *
1381
+ * Messages are grouped into atomic units so tool call/response pairs are never
1382
+ * split (which would cause a 400 from OpenAI):
1383
+ * - assistant with tool_calls + its subsequent tool response messages = one group
1384
+ * - standalone user, assistant (no tools), system = one group each
1385
+ *
1386
+ * If no maxInputTokens is set, returns messages as-is.
1387
+ */
1388
+ private getMessagesWithinBudget(): Message[] {
1389
+ const budget = resolveMaxInputTokens(this.options.maxInputTokens)
1390
+ if (!budget) return this.messages
1391
+
1392
+ const messages = this.messages
1393
+ if (messages.length === 0) return messages
1394
+
1395
+ // Check if the full history already fits
1396
+ const fullCount = countMessageTokens(messages, this.model)
1397
+ if (fullCount <= budget) return messages
1398
+
1399
+ // Separate system prompt from the rest
1400
+ const systemMsg = (messages[0]?.role === 'system' || messages[0]?.role === 'developer')
1401
+ ? messages[0]
1402
+ : null
1403
+ const nonSystem = systemMsg ? messages.slice(1) : [...messages]
1404
+
1405
+ // Group messages into atomic units.
1406
+ // An assistant message with tool_calls and its subsequent tool responses form one group.
1407
+ type MessageGroup = Message[]
1408
+ const groups: MessageGroup[] = []
1409
+ let i = 0
1410
+ while (i < nonSystem.length) {
1411
+ const msg = nonSystem[i]!
1412
+ if (msg.role === 'assistant' && (msg as any).tool_calls?.length) {
1413
+ // Collect the assistant + all following tool responses that belong to it
1414
+ const expectedIds = new Set(((msg as any).tool_calls as any[]).map((tc: any) => tc.id))
1415
+ const group: Message[] = [msg]
1416
+ let j = i + 1
1417
+ while (j < nonSystem.length && nonSystem[j]!.role === 'tool' && expectedIds.has((nonSystem[j] as any).tool_call_id)) {
1418
+ group.push(nonSystem[j]!)
1419
+ j++
1420
+ }
1421
+ groups.push(group)
1422
+ i = j
1423
+ } else {
1424
+ groups.push([msg])
1425
+ i++
1426
+ }
1427
+ }
1428
+
1429
+ // Walk backwards through groups, accumulating tokens until we exceed the budget
1430
+ const systemTokens = systemMsg ? countMessageTokens([systemMsg], this.model) : 0
1431
+ let running = systemTokens
1432
+ let cutoff = groups.length // start with nothing included
1433
+
1434
+ for (let g = groups.length - 1; g >= 0; g--) {
1435
+ const groupTokens = countMessageTokens(groups[g]!, this.model)
1436
+ if (running + groupTokens > budget) break
1437
+ running += groupTokens
1438
+ cutoff = g
1439
+ }
1440
+
1441
+ const kept = groups.slice(cutoff).flat()
1442
+ return systemMsg ? [systemMsg, ...kept] : kept
1443
+ }
1444
+
1445
+ private sanitizeMessages(messages: Message[]): Message[] {
1446
+ const result: Message[] = []
1447
+
1448
+ for (let i = 0; i < messages.length; i++) {
1449
+ const msg = messages[i]!
1450
+ result.push(msg)
1451
+
1452
+ // Check if this is an assistant message with tool_calls
1453
+ if (msg.role === 'assistant' && (msg as any).tool_calls?.length) {
1454
+ const toolCalls: Array<{ id: string }> = (msg as any).tool_calls
1455
+ const expectedIds = new Set(toolCalls.map(tc => tc.id))
1456
+
1457
+ // Scan forward for matching tool responses
1458
+ const foundIds = new Set<string>()
1459
+ for (let j = i + 1; j < messages.length; j++) {
1460
+ const next = messages[j]!
1461
+ if (next.role === 'tool' && expectedIds.has((next as any).tool_call_id)) {
1462
+ foundIds.add((next as any).tool_call_id)
1463
+ } else if (next.role !== 'tool') {
1464
+ break
1465
+ }
1466
+ }
1467
+
1468
+ // Add stub responses for any missing tool_call_ids
1469
+ for (const id of expectedIds) {
1470
+ if (!foundIds.has(id)) {
1471
+ result.push({
1472
+ role: 'tool',
1473
+ tool_call_id: id,
1474
+ content: '[tool execution was interrupted]',
1475
+ } as any)
1476
+ }
1477
+ }
1478
+ }
1479
+ }
1480
+
1481
+ return result
1482
+ }
1483
+
1313
1484
  /**
1314
1485
  * Append a message to the conversation state.
1315
1486
  *
@@ -1,11 +1,11 @@
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 { ContentDb } from '@/node.js'
6
6
  import type Assistant from './assistant.js'
7
7
 
8
- declare module '@soederpop/luca/feature' {
8
+ declare module 'luca/feature' {
9
9
  interface AvailableFeatures {
10
10
  docsReader: typeof DocsReader
11
11
  }
@@ -5,14 +5,17 @@ import type { FS } from '../../node/features/fs.js'
5
5
  import type { Grep, GrepMatch } from '../../node/features/grep.js'
6
6
  import type { Helper } from '../../helper.js'
7
7
 
8
- declare module '@soederpop/luca/feature' {
8
+ declare module 'luca/feature' {
9
9
  interface AvailableFeatures {
10
10
  fileTools: typeof FileTools
11
11
  }
12
12
  }
13
13
 
14
14
  export const FileToolsStateSchema = FeatureStateSchema.extend({})
15
- export const FileToolsOptionsSchema = FeatureOptionsSchema.extend({})
15
+ export const FileToolsOptionsSchema = FeatureOptionsSchema.extend({
16
+ lockToFolder: z.string().optional().describe('When set, all file operations are restricted to this folder. Paths outside it are rejected.'),
17
+ forbid: z.array(z.union([z.string(), z.instanceof(RegExp)])).optional().describe('Patterns (strings or RegExps) that block access to any matching path.'),
18
+ })
16
19
 
17
20
  /**
18
21
  * Curated file-system and code-search tools for AI assistants.
@@ -133,11 +136,43 @@ export class FileTools extends Feature {
133
136
  return this.container.feature('grep') as unknown as Grep
134
137
  }
135
138
 
139
+ /**
140
+ * Resolve a user-supplied path to an absolute path and validate it against
141
+ * `lockToFolder` and `forbid` constraints. Throws if the path is blocked.
142
+ */
143
+ private validatePath(inputPath: string): string {
144
+ const resolved = this.container.paths.resolve(inputPath)
145
+
146
+ const { lockToFolder, forbid } = this.options as { lockToFolder?: string; forbid?: (string | RegExp)[] }
147
+
148
+ if (lockToFolder) {
149
+ const folder = this.container.paths.resolve(lockToFolder)
150
+ // The resolved path must be inside the locked folder (or be the folder itself)
151
+ if (resolved !== folder && !resolved.startsWith(folder + '/')) {
152
+ throw new Error(`Access denied: "${inputPath}" is outside the allowed folder "${lockToFolder}"`)
153
+ }
154
+ }
155
+
156
+ if (forbid && forbid.length) {
157
+ for (const pattern of forbid) {
158
+ const matches = pattern instanceof RegExp
159
+ ? pattern.test(resolved)
160
+ : resolved.includes(pattern)
161
+ if (matches) {
162
+ throw new Error(`Access denied: "${inputPath}" matches forbidden pattern "${pattern}"`)
163
+ }
164
+ }
165
+ }
166
+
167
+ return resolved
168
+ }
169
+
136
170
  // -------------------------------------------------------------------------
137
171
  // Tool implementations — each matches a static tools key by name
138
172
  // -------------------------------------------------------------------------
139
173
 
140
174
  async readFile(args: { path: string; offset?: number; limit?: number }): Promise<string> {
175
+ this.validatePath(args.path)
141
176
  const content = await this.fs.readFileAsync(args.path) as string
142
177
 
143
178
  if (args.offset || args.limit) {
@@ -151,12 +186,14 @@ export class FileTools extends Feature {
151
186
  }
152
187
 
153
188
  async writeFile(args: { path: string; content: string }): Promise<string> {
189
+ this.validatePath(args.path)
154
190
  await this.fs.ensureFolderAsync(args.path.includes('/') ? args.path.split('/').slice(0, -1).join('/') : '.')
155
191
  await this.fs.writeFileAsync(args.path, args.content)
156
192
  return `Wrote ${args.content.length} bytes to ${args.path}`
157
193
  }
158
194
 
159
195
  async editFile(args: { path: string; oldString: string; newString: string; replaceAll?: boolean }): Promise<string> {
196
+ this.validatePath(args.path)
160
197
  const content = await this.fs.readFileAsync(args.path) as string
161
198
 
162
199
  if (args.replaceAll) {
@@ -183,6 +220,7 @@ export class FileTools extends Feature {
183
220
 
184
221
  async listDirectory(args: { path?: string; recursive?: boolean; include?: string; exclude?: string }): Promise<string> {
185
222
  const dir = args.path || '.'
223
+ this.validatePath(dir)
186
224
  const result = await this.fs.walkAsync(dir, {
187
225
  files: true,
188
226
  directories: true,
@@ -201,6 +239,7 @@ export class FileTools extends Feature {
201
239
  }
202
240
 
203
241
  async searchFiles(args: { pattern: string; path?: string; include?: string; exclude?: string; ignoreCase?: boolean; maxResults?: number }): Promise<string> {
242
+ if (args.path) this.validatePath(args.path)
204
243
  const results: GrepMatch[] = await this.grep.search({
205
244
  pattern: args.pattern,
206
245
  path: args.path,
@@ -219,6 +258,7 @@ export class FileTools extends Feature {
219
258
 
220
259
  async findFiles(args: { pattern: string; path?: string; exclude?: string }): Promise<string> {
221
260
  const dir = args.path || '.'
261
+ this.validatePath(dir)
222
262
  const result = await this.fs.walkAsync(dir, {
223
263
  files: true,
224
264
  directories: false,
@@ -230,6 +270,7 @@ export class FileTools extends Feature {
230
270
  }
231
271
 
232
272
  async fileInfo(args: { path: string }): Promise<string> {
273
+ this.validatePath(args.path)
233
274
  const exists = await this.fs.existsAsync(args.path)
234
275
  if (!exists) return JSON.stringify({ exists: false })
235
276
 
@@ -244,21 +285,27 @@ export class FileTools extends Feature {
244
285
  }
245
286
 
246
287
  async createDirectory(args: { path: string }): Promise<string> {
288
+ this.validatePath(args.path)
247
289
  await this.fs.ensureFolderAsync(args.path)
248
290
  return `Created ${args.path}`
249
291
  }
250
292
 
251
293
  async moveFile(args: { source: string; destination: string }): Promise<string> {
294
+ this.validatePath(args.source)
295
+ this.validatePath(args.destination)
252
296
  await this.fs.moveAsync(args.source, args.destination)
253
297
  return `Moved ${args.source} → ${args.destination}`
254
298
  }
255
299
 
256
300
  async copyFile(args: { source: string; destination: string }): Promise<string> {
301
+ this.validatePath(args.source)
302
+ this.validatePath(args.destination)
257
303
  await this.fs.copyAsync(args.source, args.destination)
258
304
  return `Copied ${args.source} → ${args.destination}`
259
305
  }
260
306
 
261
307
  async deleteFile(args: { path: string }): Promise<string> {
308
+ this.validatePath(args.path)
262
309
  const isDir = await this.fs.isDirectoryAsync(args.path)
263
310
  if (isDir) return `Error: "${args.path}" is a directory. Use deleteFile only for files.`
264
311
  await this.fs.rm(args.path)
@@ -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
  lucaCoder: typeof LucaCoder
10
10
  }
@@ -104,8 +104,8 @@ export const LucaCoderOptionsSchema = FeatureOptionsSchema.extend({
104
104
  /** Skills to auto-load into the system prompt context. If not specified, auto-detects luca-framework. */
105
105
  skills: z.array(z.string()).optional().describe('Skill names to auto-load into the system prompt'),
106
106
 
107
- /** Whether to auto-detect and load the luca-framework skill. Defaults to true. */
108
- autoLoadLucaSkill: z.boolean().default(true).describe('Auto-load luca-framework skill if found in .claude/skills path'),
107
+ /** Whether to auto-detect and load the luca-framework skill from conventional agent skill folders. Defaults to true. */
108
+ autoLoadLucaSkill: z.boolean().default(true).describe('Auto-load luca-framework skill if found in agent skill folders (.claude/skills or .agents/skills)'),
109
109
  })
110
110
 
111
111
  export type LucaCoderState = z.infer<typeof LucaCoderStateSchema>
@@ -116,7 +116,7 @@ export type LucaCoderOptions = z.infer<typeof LucaCoderOptionsSchema>
116
116
  * gates all tool calls through a permission system.
117
117
  *
118
118
  * Comes with built-in Bash tool (via proc.execAndCapture) and auto-loads
119
- * the luca-framework skill when found in .claude/skills paths.
119
+ * the luca-framework skill when found in conventional agent skill folders (.claude/skills or .agents/skills).
120
120
  *
121
121
  * Tools are stacked from feature bundles (fileTools, etc.)
122
122
  * and each tool can be set to 'allow' (runs immediately), 'ask' (blocks
@@ -415,11 +415,13 @@ export class LucaCoder extends Feature<LucaCoderState, LucaCoderOptions> {
415
415
  const skillContent: string[] = []
416
416
  const loadedSkills: string[] = []
417
417
 
418
- // Check for luca-framework skill in known locations
418
+ // Check for luca-framework skill in conventional agent skill folders
419
419
  if (this.options.autoLoadLucaSkill !== false) {
420
420
  const skillLocations = [
421
421
  paths.resolve(this.container.cwd, '.claude', 'skills', 'luca-framework', 'SKILL.md'),
422
+ paths.resolve(this.container.cwd, '.agents', 'skills', 'luca-framework', 'SKILL.md'),
422
423
  paths.resolve(os.homedir, '.claude', 'skills', 'luca-framework', 'SKILL.md'),
424
+ paths.resolve(os.homedir, '.agents', 'skills', 'luca-framework', 'SKILL.md'),
423
425
  ]
424
426
 
425
427
  for (const skillPath of skillLocations) {