@soederpop/luca 0.1.2 → 0.2.1

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 (381) hide show
  1. package/.github/workflows/release.yaml +167 -0
  2. package/CLAUDE.md +2 -0
  3. package/README.md +3 -0
  4. package/assistants/codingAssistant/ABOUT.md +3 -0
  5. package/assistants/codingAssistant/CORE.md +22 -17
  6. package/assistants/codingAssistant/hooks.ts +17 -4
  7. package/assistants/codingAssistant/tools.ts +1 -106
  8. package/assistants/inkbot/ABOUT.md +5 -0
  9. package/assistants/inkbot/CORE.md +71 -0
  10. package/assistants/inkbot/hooks.ts +14 -0
  11. package/assistants/inkbot/tools.ts +47 -0
  12. package/bun.lock +20 -4
  13. package/commands/inkbot.ts +353 -0
  14. package/commands/release.ts +75 -181
  15. package/dist/agi/container.server.d.ts +63 -0
  16. package/dist/agi/container.server.d.ts.map +1 -0
  17. package/dist/agi/endpoints/ask.d.ts +20 -0
  18. package/dist/agi/endpoints/ask.d.ts.map +1 -0
  19. package/dist/agi/endpoints/conversations/[id].d.ts +27 -0
  20. package/dist/agi/endpoints/conversations/[id].d.ts.map +1 -0
  21. package/dist/agi/endpoints/conversations.d.ts +18 -0
  22. package/dist/agi/endpoints/conversations.d.ts.map +1 -0
  23. package/dist/agi/endpoints/experts.d.ts +8 -0
  24. package/dist/agi/endpoints/experts.d.ts.map +1 -0
  25. package/dist/agi/feature.d.ts +9 -0
  26. package/dist/agi/feature.d.ts.map +1 -0
  27. package/dist/agi/features/assistant.d.ts +509 -0
  28. package/dist/agi/features/assistant.d.ts.map +1 -0
  29. package/dist/agi/features/assistants-manager.d.ts +236 -0
  30. package/dist/agi/features/assistants-manager.d.ts.map +1 -0
  31. package/dist/agi/features/autonomous-assistant.d.ts +281 -0
  32. package/dist/agi/features/autonomous-assistant.d.ts.map +1 -0
  33. package/dist/agi/features/browser-use.d.ts +479 -0
  34. package/dist/agi/features/browser-use.d.ts.map +1 -0
  35. package/dist/agi/features/claude-code.d.ts +824 -0
  36. package/dist/agi/features/claude-code.d.ts.map +1 -0
  37. package/dist/agi/features/conversation-history.d.ts +245 -0
  38. package/dist/agi/features/conversation-history.d.ts.map +1 -0
  39. package/dist/agi/features/conversation.d.ts +464 -0
  40. package/dist/agi/features/conversation.d.ts.map +1 -0
  41. package/dist/agi/features/docs-reader.d.ts +72 -0
  42. package/dist/agi/features/docs-reader.d.ts.map +1 -0
  43. package/dist/agi/features/file-tools.d.ts +110 -0
  44. package/dist/agi/features/file-tools.d.ts.map +1 -0
  45. package/dist/agi/features/luca-coder.d.ts +323 -0
  46. package/dist/agi/features/luca-coder.d.ts.map +1 -0
  47. package/dist/agi/features/openai-codex.d.ts +381 -0
  48. package/dist/agi/features/openai-codex.d.ts.map +1 -0
  49. package/dist/agi/features/openapi.d.ts +200 -0
  50. package/dist/agi/features/openapi.d.ts.map +1 -0
  51. package/dist/agi/features/skills-library.d.ts +167 -0
  52. package/dist/agi/features/skills-library.d.ts.map +1 -0
  53. package/dist/agi/index.d.ts +5 -0
  54. package/dist/agi/index.d.ts.map +1 -0
  55. package/dist/agi/lib/interceptor-chain.d.ts +44 -0
  56. package/dist/agi/lib/interceptor-chain.d.ts.map +1 -0
  57. package/dist/agi/lib/token-counter.d.ts +13 -0
  58. package/dist/agi/lib/token-counter.d.ts.map +1 -0
  59. package/dist/bootstrap/generated.d.ts +5 -0
  60. package/dist/bootstrap/generated.d.ts.map +1 -0
  61. package/dist/browser.d.ts +12 -0
  62. package/dist/browser.d.ts.map +1 -0
  63. package/dist/bus.d.ts +29 -0
  64. package/dist/bus.d.ts.map +1 -0
  65. package/dist/cli/build-info.d.ts +4 -0
  66. package/dist/cli/build-info.d.ts.map +1 -0
  67. package/dist/cli/cli.d.ts +3 -0
  68. package/dist/cli/cli.d.ts.map +1 -0
  69. package/dist/client.d.ts +60 -0
  70. package/dist/client.d.ts.map +1 -0
  71. package/dist/clients/civitai/index.d.ts +472 -0
  72. package/dist/clients/civitai/index.d.ts.map +1 -0
  73. package/dist/clients/client-template.d.ts +30 -0
  74. package/dist/clients/client-template.d.ts.map +1 -0
  75. package/dist/clients/comfyui/index.d.ts +281 -0
  76. package/dist/clients/comfyui/index.d.ts.map +1 -0
  77. package/dist/clients/elevenlabs/index.d.ts +197 -0
  78. package/dist/clients/elevenlabs/index.d.ts.map +1 -0
  79. package/dist/clients/graph.d.ts +64 -0
  80. package/dist/clients/graph.d.ts.map +1 -0
  81. package/dist/clients/openai/index.d.ts +247 -0
  82. package/dist/clients/openai/index.d.ts.map +1 -0
  83. package/dist/clients/rest.d.ts +92 -0
  84. package/dist/clients/rest.d.ts.map +1 -0
  85. package/dist/clients/supabase/index.d.ts +176 -0
  86. package/dist/clients/supabase/index.d.ts.map +1 -0
  87. package/dist/clients/websocket.d.ts +127 -0
  88. package/dist/clients/websocket.d.ts.map +1 -0
  89. package/dist/command.d.ts +163 -0
  90. package/dist/command.d.ts.map +1 -0
  91. package/dist/commands/bootstrap.d.ts +20 -0
  92. package/dist/commands/bootstrap.d.ts.map +1 -0
  93. package/dist/commands/chat.d.ts +37 -0
  94. package/dist/commands/chat.d.ts.map +1 -0
  95. package/dist/commands/code.d.ts +28 -0
  96. package/dist/commands/code.d.ts.map +1 -0
  97. package/dist/commands/console.d.ts +22 -0
  98. package/dist/commands/console.d.ts.map +1 -0
  99. package/dist/commands/describe.d.ts +50 -0
  100. package/dist/commands/describe.d.ts.map +1 -0
  101. package/dist/commands/eval.d.ts +23 -0
  102. package/dist/commands/eval.d.ts.map +1 -0
  103. package/dist/commands/help.d.ts +25 -0
  104. package/dist/commands/help.d.ts.map +1 -0
  105. package/dist/commands/index.d.ts +18 -0
  106. package/dist/commands/index.d.ts.map +1 -0
  107. package/dist/commands/introspect.d.ts +24 -0
  108. package/dist/commands/introspect.d.ts.map +1 -0
  109. package/dist/commands/mcp.d.ts +35 -0
  110. package/dist/commands/mcp.d.ts.map +1 -0
  111. package/dist/commands/prompt.d.ts +38 -0
  112. package/dist/commands/prompt.d.ts.map +1 -0
  113. package/dist/commands/run.d.ts +24 -0
  114. package/dist/commands/run.d.ts.map +1 -0
  115. package/dist/commands/sandbox-mcp.d.ts +34 -0
  116. package/dist/commands/sandbox-mcp.d.ts.map +1 -0
  117. package/dist/commands/save-api-docs.d.ts +21 -0
  118. package/dist/commands/save-api-docs.d.ts.map +1 -0
  119. package/dist/commands/scaffold.d.ts +24 -0
  120. package/dist/commands/scaffold.d.ts.map +1 -0
  121. package/dist/commands/select.d.ts +22 -0
  122. package/dist/commands/select.d.ts.map +1 -0
  123. package/dist/commands/serve.d.ts +29 -0
  124. package/dist/commands/serve.d.ts.map +1 -0
  125. package/dist/container-describer.d.ts +144 -0
  126. package/dist/container-describer.d.ts.map +1 -0
  127. package/dist/container.d.ts +451 -0
  128. package/dist/container.d.ts.map +1 -0
  129. package/dist/endpoint.d.ts +113 -0
  130. package/dist/endpoint.d.ts.map +1 -0
  131. package/dist/feature.d.ts +47 -0
  132. package/dist/feature.d.ts.map +1 -0
  133. package/dist/graft.d.ts +29 -0
  134. package/dist/graft.d.ts.map +1 -0
  135. package/dist/hash-object.d.ts +8 -0
  136. package/dist/hash-object.d.ts.map +1 -0
  137. package/dist/helper.d.ts +209 -0
  138. package/dist/helper.d.ts.map +1 -0
  139. package/dist/introspection/generated.node.d.ts +44623 -0
  140. package/dist/introspection/generated.node.d.ts.map +1 -0
  141. package/dist/introspection/generated.web.d.ts +1412 -0
  142. package/dist/introspection/generated.web.d.ts.map +1 -0
  143. package/dist/introspection/index.d.ts +156 -0
  144. package/dist/introspection/index.d.ts.map +1 -0
  145. package/dist/introspection/scan.d.ts +147 -0
  146. package/dist/introspection/scan.d.ts.map +1 -0
  147. package/dist/node/container.d.ts +256 -0
  148. package/dist/node/container.d.ts.map +1 -0
  149. package/dist/node/feature.d.ts +9 -0
  150. package/dist/node/feature.d.ts.map +1 -0
  151. package/dist/node/features/container-link.d.ts +213 -0
  152. package/dist/node/features/container-link.d.ts.map +1 -0
  153. package/dist/node/features/content-db.d.ts +354 -0
  154. package/dist/node/features/content-db.d.ts.map +1 -0
  155. package/dist/node/features/disk-cache.d.ts +236 -0
  156. package/dist/node/features/disk-cache.d.ts.map +1 -0
  157. package/dist/node/features/dns.d.ts +511 -0
  158. package/dist/node/features/dns.d.ts.map +1 -0
  159. package/dist/node/features/docker.d.ts +485 -0
  160. package/dist/node/features/docker.d.ts.map +1 -0
  161. package/dist/node/features/downloader.d.ts +73 -0
  162. package/dist/node/features/downloader.d.ts.map +1 -0
  163. package/dist/node/features/figlet-fonts.d.ts +4 -0
  164. package/dist/node/features/figlet-fonts.d.ts.map +1 -0
  165. package/dist/node/features/file-manager.d.ts +177 -0
  166. package/dist/node/features/file-manager.d.ts.map +1 -0
  167. package/dist/node/features/fs.d.ts +635 -0
  168. package/dist/node/features/fs.d.ts.map +1 -0
  169. package/dist/node/features/git.d.ts +329 -0
  170. package/dist/node/features/git.d.ts.map +1 -0
  171. package/dist/node/features/google-auth.d.ts +200 -0
  172. package/dist/node/features/google-auth.d.ts.map +1 -0
  173. package/dist/node/features/google-calendar.d.ts +194 -0
  174. package/dist/node/features/google-calendar.d.ts.map +1 -0
  175. package/dist/node/features/google-docs.d.ts +138 -0
  176. package/dist/node/features/google-docs.d.ts.map +1 -0
  177. package/dist/node/features/google-drive.d.ts +202 -0
  178. package/dist/node/features/google-drive.d.ts.map +1 -0
  179. package/dist/node/features/google-mail.d.ts +221 -0
  180. package/dist/node/features/google-mail.d.ts.map +1 -0
  181. package/dist/node/features/google-sheets.d.ts +157 -0
  182. package/dist/node/features/google-sheets.d.ts.map +1 -0
  183. package/dist/node/features/grep.d.ts +207 -0
  184. package/dist/node/features/grep.d.ts.map +1 -0
  185. package/dist/node/features/helpers.d.ts +236 -0
  186. package/dist/node/features/helpers.d.ts.map +1 -0
  187. package/dist/node/features/ink.d.ts +332 -0
  188. package/dist/node/features/ink.d.ts.map +1 -0
  189. package/dist/node/features/ipc-socket.d.ts +298 -0
  190. package/dist/node/features/ipc-socket.d.ts.map +1 -0
  191. package/dist/node/features/json-tree.d.ts +140 -0
  192. package/dist/node/features/json-tree.d.ts.map +1 -0
  193. package/dist/node/features/networking.d.ts +373 -0
  194. package/dist/node/features/networking.d.ts.map +1 -0
  195. package/dist/node/features/nlp.d.ts +125 -0
  196. package/dist/node/features/nlp.d.ts.map +1 -0
  197. package/dist/node/features/opener.d.ts +93 -0
  198. package/dist/node/features/opener.d.ts.map +1 -0
  199. package/dist/node/features/os.d.ts +168 -0
  200. package/dist/node/features/os.d.ts.map +1 -0
  201. package/dist/node/features/package-finder.d.ts +419 -0
  202. package/dist/node/features/package-finder.d.ts.map +1 -0
  203. package/dist/node/features/postgres.d.ts +173 -0
  204. package/dist/node/features/postgres.d.ts.map +1 -0
  205. package/dist/node/features/proc.d.ts +285 -0
  206. package/dist/node/features/proc.d.ts.map +1 -0
  207. package/dist/node/features/process-manager.d.ts +427 -0
  208. package/dist/node/features/process-manager.d.ts.map +1 -0
  209. package/dist/node/features/python.d.ts +477 -0
  210. package/dist/node/features/python.d.ts.map +1 -0
  211. package/dist/node/features/redis.d.ts +247 -0
  212. package/dist/node/features/redis.d.ts.map +1 -0
  213. package/dist/node/features/repl.d.ts +84 -0
  214. package/dist/node/features/repl.d.ts.map +1 -0
  215. package/dist/node/features/runpod.d.ts +527 -0
  216. package/dist/node/features/runpod.d.ts.map +1 -0
  217. package/dist/node/features/secure-shell.d.ts +145 -0
  218. package/dist/node/features/secure-shell.d.ts.map +1 -0
  219. package/dist/node/features/semantic-search.d.ts +207 -0
  220. package/dist/node/features/semantic-search.d.ts.map +1 -0
  221. package/dist/node/features/sqlite.d.ts +180 -0
  222. package/dist/node/features/sqlite.d.ts.map +1 -0
  223. package/dist/node/features/telegram.d.ts +173 -0
  224. package/dist/node/features/telegram.d.ts.map +1 -0
  225. package/dist/node/features/transpiler.d.ts +51 -0
  226. package/dist/node/features/transpiler.d.ts.map +1 -0
  227. package/dist/node/features/tts.d.ts +108 -0
  228. package/dist/node/features/tts.d.ts.map +1 -0
  229. package/dist/node/features/ui.d.ts +562 -0
  230. package/dist/node/features/ui.d.ts.map +1 -0
  231. package/dist/node/features/vault.d.ts +90 -0
  232. package/dist/node/features/vault.d.ts.map +1 -0
  233. package/dist/node/features/vm.d.ts +285 -0
  234. package/dist/node/features/vm.d.ts.map +1 -0
  235. package/dist/node/features/yaml-tree.d.ts +118 -0
  236. package/dist/node/features/yaml-tree.d.ts.map +1 -0
  237. package/dist/node/features/yaml.d.ts +127 -0
  238. package/dist/node/features/yaml.d.ts.map +1 -0
  239. package/dist/node.d.ts +67 -0
  240. package/dist/node.d.ts.map +1 -0
  241. package/dist/python/generated.d.ts +2 -0
  242. package/dist/python/generated.d.ts.map +1 -0
  243. package/dist/react/index.d.ts +36 -0
  244. package/dist/react/index.d.ts.map +1 -0
  245. package/dist/registry.d.ts +97 -0
  246. package/dist/registry.d.ts.map +1 -0
  247. package/dist/scaffolds/generated.d.ts +13 -0
  248. package/dist/scaffolds/generated.d.ts.map +1 -0
  249. package/dist/scaffolds/template.d.ts +11 -0
  250. package/dist/scaffolds/template.d.ts.map +1 -0
  251. package/dist/schemas/base.d.ts +254 -0
  252. package/dist/schemas/base.d.ts.map +1 -0
  253. package/dist/selector.d.ts +130 -0
  254. package/dist/selector.d.ts.map +1 -0
  255. package/dist/server.d.ts +89 -0
  256. package/dist/server.d.ts.map +1 -0
  257. package/dist/servers/express.d.ts +104 -0
  258. package/dist/servers/express.d.ts.map +1 -0
  259. package/dist/servers/mcp.d.ts +201 -0
  260. package/dist/servers/mcp.d.ts.map +1 -0
  261. package/dist/servers/socket.d.ts +121 -0
  262. package/dist/servers/socket.d.ts.map +1 -0
  263. package/dist/state.d.ts +24 -0
  264. package/dist/state.d.ts.map +1 -0
  265. package/dist/web/clients/socket.d.ts +37 -0
  266. package/dist/web/clients/socket.d.ts.map +1 -0
  267. package/dist/web/container.d.ts +55 -0
  268. package/dist/web/container.d.ts.map +1 -0
  269. package/dist/web/extension.d.ts +4 -0
  270. package/dist/web/extension.d.ts.map +1 -0
  271. package/dist/web/feature.d.ts +8 -0
  272. package/dist/web/feature.d.ts.map +1 -0
  273. package/dist/web/features/asset-loader.d.ts +35 -0
  274. package/dist/web/features/asset-loader.d.ts.map +1 -0
  275. package/dist/web/features/container-link.d.ts +167 -0
  276. package/dist/web/features/container-link.d.ts.map +1 -0
  277. package/dist/web/features/esbuild.d.ts +51 -0
  278. package/dist/web/features/esbuild.d.ts.map +1 -0
  279. package/dist/web/features/helpers.d.ts +140 -0
  280. package/dist/web/features/helpers.d.ts.map +1 -0
  281. package/dist/web/features/network.d.ts +69 -0
  282. package/dist/web/features/network.d.ts.map +1 -0
  283. package/dist/web/features/speech.d.ts +71 -0
  284. package/dist/web/features/speech.d.ts.map +1 -0
  285. package/dist/web/features/vault.d.ts +62 -0
  286. package/dist/web/features/vault.d.ts.map +1 -0
  287. package/dist/web/features/vm.d.ts +48 -0
  288. package/dist/web/features/vm.d.ts.map +1 -0
  289. package/dist/web/features/voice-recognition.d.ts +96 -0
  290. package/dist/web/features/voice-recognition.d.ts.map +1 -0
  291. package/dist/web/shims/isomorphic-vm.d.ts +22 -0
  292. package/dist/web/shims/isomorphic-vm.d.ts.map +1 -0
  293. package/docs/apis/features/agi/assistant.md +1 -0
  294. package/docs/apis/features/agi/assistants-manager.md +62 -2
  295. package/docs/apis/features/agi/auto-assistant.md +11 -109
  296. package/docs/apis/features/agi/claude-code.md +138 -0
  297. package/docs/apis/features/agi/conversation.md +60 -31
  298. package/docs/apis/features/agi/luca-coder.md +407 -0
  299. package/docs/apis/features/agi/openapi.md +2 -2
  300. package/docs/apis/features/agi/skills-library.md +12 -0
  301. package/docs/apis/features/node/python.md +81 -11
  302. package/docs/apis/features/node/transpiler.md +74 -0
  303. package/docs/apis/features/web/esbuild.md +0 -6
  304. package/docs/apis/servers/mcp.md +2 -2
  305. package/docs/examples/entity.md +124 -0
  306. package/docs/ideas/assistant-factory-pattern.md +142 -0
  307. package/package.json +74 -21
  308. package/src/agi/container.server.ts +10 -0
  309. package/src/agi/feature.ts +13 -0
  310. package/src/agi/features/agent-memory.ts +694 -0
  311. package/src/agi/features/assistant.ts +37 -26
  312. package/src/agi/features/assistants-manager.ts +95 -5
  313. package/src/agi/features/autonomous-assistant.ts +1 -5
  314. package/src/agi/features/browser-use.ts +32 -2
  315. package/src/agi/features/claude-code.ts +165 -1
  316. package/src/agi/features/coding-tools.ts +175 -0
  317. package/src/agi/features/conversation-history.ts +2 -6
  318. package/src/agi/features/conversation.ts +95 -3
  319. package/src/agi/features/docs-reader.ts +2 -1
  320. package/src/agi/features/file-tools.ts +35 -28
  321. package/src/agi/features/luca-coder.ts +1 -5
  322. package/src/agi/features/openai-codex.ts +1 -1
  323. package/src/agi/features/openapi.ts +3 -3
  324. package/src/agi/features/skills-library.ts +111 -13
  325. package/src/agi/lib/interceptor-chain.ts +10 -0
  326. package/src/agi/lib/token-counter.ts +1 -1
  327. package/src/bootstrap/generated.ts +126 -1
  328. package/src/bus.ts +27 -5
  329. package/src/cli/build-info.ts +2 -2
  330. package/src/client.ts +2 -2
  331. package/src/clients/elevenlabs/index.ts +5 -0
  332. package/src/clients/voicebox/index.ts +300 -0
  333. package/src/commands/bootstrap.ts +2 -1
  334. package/src/commands/chat.ts +1 -0
  335. package/src/commands/code.ts +4 -2
  336. package/src/commands/prompt.ts +34 -34
  337. package/src/commands/sandbox-mcp.ts +69 -163
  338. package/src/commands/save-api-docs.ts +10 -8
  339. package/src/commands/select.ts +8 -3
  340. package/src/container-describer.ts +70 -84
  341. package/src/container.ts +93 -3
  342. package/src/endpoint.ts +1 -1
  343. package/src/entity.ts +173 -0
  344. package/src/feature.ts +3 -3
  345. package/src/helper.ts +8 -4
  346. package/src/introspection/generated.agi.ts +3012 -1356
  347. package/src/introspection/generated.node.ts +179 -33
  348. package/src/introspection/generated.web.ts +95 -3
  349. package/src/introspection/scan.ts +1 -1
  350. package/src/node/container.ts +1 -1
  351. package/src/node/features/content-db.ts +57 -30
  352. package/src/node/features/file-manager.ts +10 -9
  353. package/src/node/features/git.ts +5 -5
  354. package/src/node/features/helpers.ts +1 -1
  355. package/src/node/features/json-tree.ts +1 -1
  356. package/src/node/features/os.ts +3 -3
  357. package/src/node/features/package-finder.ts +1 -1
  358. package/src/node/features/process-manager.ts +51 -18
  359. package/src/node/features/python.ts +3 -3
  360. package/src/node/features/redis.ts +1 -1
  361. package/src/node/features/repl.ts +2 -2
  362. package/src/node/features/transpiler.ts +2 -2
  363. package/src/node/features/ui.ts +1 -1
  364. package/src/node/features/vm.ts +3 -3
  365. package/src/node/features/yaml-tree.ts +1 -1
  366. package/src/node.ts +1 -0
  367. package/src/python/generated.ts +1 -1
  368. package/src/scaffolds/generated.ts +1 -1
  369. package/src/selector.ts +74 -4
  370. package/src/server.ts +2 -2
  371. package/src/servers/mcp.ts +6 -6
  372. package/src/web/features/helpers.ts +1 -1
  373. package/src/web/features/network.ts +1 -0
  374. package/test/assistant.test.ts +14 -5
  375. package/test/conversation.test.ts +220 -0
  376. package/test-integration/memory.test.ts +204 -0
  377. package/tsconfig.build.json +12 -0
  378. package/tsconfig.json +1 -1
  379. package/scripts/examples/telegram-ink-ui.ts +0 -302
  380. package/scripts/examples/using-openai-codex.ts +0 -23
  381. package/scripts/examples/vm-loading-esm-modules.ts +0 -16
@@ -1,9 +1,8 @@
1
1
  import { z } from 'zod'
2
2
  import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
3
3
  import { type AvailableFeatures } from '@soederpop/luca/feature'
4
- import { Feature } from '@soederpop/luca/feature'
4
+ import { Feature } from '../feature.js'
5
5
  import type { Conversation, ConversationTool, ContentPart, AskOptions, Message } from './conversation'
6
- import type { AGIContainer } from '../container.server.js'
7
6
  import type { ContentDb } from '@soederpop/luca/node'
8
7
  import type { ConversationHistory, ConversationMeta } from './conversation-history'
9
8
  import hashObject from '../../hash-object.js'
@@ -152,6 +151,10 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
152
151
  * @returns this, for chaining
153
152
  */
154
153
  intercept<K extends InterceptorPoint>(point: K, fn: InterceptorFn<InterceptorPoints[K]>): this {
154
+ if (!(point in this.interceptors)) {
155
+ const available = Object.keys(this.interceptors).join(', ')
156
+ throw new Error(`Unknown intercept point "${point}". Available points: ${available}`)
157
+ }
155
158
  this.interceptors[point].add(fn as any)
156
159
  return this
157
160
  }
@@ -176,12 +179,9 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
176
179
  }
177
180
  }
178
181
 
179
- override get container(): AGIContainer {
180
- return super.container as AGIContainer
181
- }
182
182
 
183
183
  get name() {
184
- return this.resolvedFolder.split('/').pop()
184
+ return this.options.name || this.resolvedFolder.split('/').pop()
185
185
  }
186
186
 
187
187
  /** The absolute resolved path to the assistant folder. */
@@ -213,11 +213,11 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
213
213
  get voiceConfig(): Record<string, any> | undefined {
214
214
  if (!this.hasVoice) return undefined
215
215
  const yaml = this.container.feature('yaml')
216
- return yaml.parse(this.container.fs.readFile(this.paths.resolve('voice.yaml')))
216
+ return yaml.parse(String(this.container.fs.readFile(this.paths.resolve('voice.yaml'))))
217
217
  }
218
218
 
219
219
  get resolvedDocsFolder() {
220
- const { docsFolder = this.options.docsFolder || 'docs' } = this.state.current
220
+ const { docsFolder = this.effectiveOptions.docsFolder || 'docs' } = this.state.current
221
221
 
222
222
  if (this.container.fs.exists(docsFolder)) {
223
223
  return this.container.paths.resolve(docsFolder)
@@ -261,24 +261,24 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
261
261
  // Bind hooks to events BEFORE emitting created so the created hook fires
262
262
  this.bindHooksToEvents()
263
263
 
264
- this.emit('created')
264
+ setTimeout(() => this.emit('created'), 1)
265
265
  }
266
266
 
267
267
  get conversation(): Conversation {
268
268
  let conv = this.state.get('conversation') as Conversation | null
269
269
  if (!conv) {
270
270
  conv = this.container.feature('conversation', {
271
- model: this.options.model || 'gpt-5.4',
272
- local: !!this.options.local,
271
+ model: this.effectiveOptions.model || 'gpt-5.4',
272
+ local: !!this.effectiveOptions.local,
273
273
  tools: this.tools,
274
274
  api: 'chat',
275
- ...(this.options.maxTokens ? { maxTokens: this.options.maxTokens } : {}),
276
- ...(this.options.temperature != null ? { temperature: this.options.temperature } : {}),
277
- ...(this.options.topP != null ? { topP: this.options.topP } : {}),
278
- ...(this.options.topK != null ? { topK: this.options.topK } : {}),
279
- ...(this.options.frequencyPenalty != null ? { frequencyPenalty: this.options.frequencyPenalty } : {}),
280
- ...(this.options.presencePenalty != null ? { presencePenalty: this.options.presencePenalty } : {}),
281
- ...(this.options.stop ? { stop: this.options.stop } : {}),
275
+ ...(this.effectiveOptions.maxTokens ? { maxTokens: this.effectiveOptions.maxTokens } : {}),
276
+ ...(this.effectiveOptions.temperature != null ? { temperature: this.effectiveOptions.temperature } : {}),
277
+ ...(this.effectiveOptions.topP != null ? { topP: this.effectiveOptions.topP } : {}),
278
+ ...(this.effectiveOptions.topK != null ? { topK: this.effectiveOptions.topK } : {}),
279
+ ...(this.effectiveOptions.frequencyPenalty != null ? { frequencyPenalty: this.effectiveOptions.frequencyPenalty } : {}),
280
+ ...(this.effectiveOptions.presencePenalty != null ? { presencePenalty: this.effectiveOptions.presencePenalty } : {}),
281
+ ...(this.effectiveOptions.stop ? { stop: this.effectiveOptions.stop } : {}),
282
282
  history: [
283
283
  { role: 'system', content: this.effectiveSystemPrompt },
284
284
  ],
@@ -372,7 +372,7 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
372
372
  * allowTools is applied first (strict allowlist), then forbidTools removes from whatever remains.
373
373
  */
374
374
  private applyToolFilters(tools: Record<string, ConversationTool>): Record<string, ConversationTool> {
375
- const { allowTools, forbidTools, toolNames } = this.options
375
+ const { allowTools, forbidTools, toolNames } = this.effectiveOptions
376
376
  if (!allowTools && !forbidTools && !toolNames) return tools
377
377
 
378
378
  let names = Object.keys(tools)
@@ -395,7 +395,8 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
395
395
 
396
396
  const result: Record<string, ConversationTool> = {}
397
397
  for (const n of names) {
398
- result[n] = tools[n]
398
+ const tool = tools[n]
399
+ if (tool) result[n] = tool
399
400
  }
400
401
  return result
401
402
  }
@@ -607,6 +608,15 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
607
608
  return (this.state.get('meta') || {}) as Record<string, any>
608
609
  }
609
610
 
611
+ /**
612
+ * Merged options where CORE.md frontmatter provides defaults and
613
+ * constructor options take precedence. Prefer this over `this.options`
614
+ * anywhere model parameters or runtime config is consumed.
615
+ */
616
+ get effectiveOptions(): AssistantOptions & Record<string, any> {
617
+ return { ...this.meta, ...this.options }
618
+ }
619
+
610
620
  /**
611
621
  * Load the system prompt from CORE.md, applying any prepend/append options.
612
622
  * YAML frontmatter (between --- fences) is stripped from the prompt and
@@ -831,8 +841,9 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
831
841
  return `${stamp} ${content}`
832
842
  }
833
843
 
834
- if (content.length > 0 && content[0].type === 'text') {
835
- return [{ type: 'text' as const, text: `${stamp} ${content[0].text}` }, ...content.slice(1)]
844
+ const firstPart = content[0]
845
+ if (firstPart && firstPart.type === 'text') {
846
+ return [{ type: 'text' as const, text: `${stamp} ${firstPart.text}` }, ...content.slice(1)]
836
847
  }
837
848
 
838
849
  return [{ type: 'text' as const, text: stamp }, ...content]
@@ -919,7 +930,7 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
919
930
  * Called from start() for non-lifecycle modes.
920
931
  */
921
932
  private async loadConversationHistory(): Promise<void> {
922
- const mode = this.options.historyMode || 'lifecycle'
933
+ const mode = this.effectiveOptions.historyMode || 'lifecycle'
923
934
  if (mode === 'lifecycle') return
924
935
 
925
936
  const threadId = (this.state.get('resumeThreadId') as string | undefined) || this.buildThreadId(mode)
@@ -1104,7 +1115,7 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
1104
1115
  await this.loadConversationHistory()
1105
1116
 
1106
1117
  // Enable autoCompact for modes that accumulate history
1107
- const mode = this.options.historyMode || 'lifecycle'
1118
+ const mode = this.effectiveOptions.historyMode || 'lifecycle'
1108
1119
  if (mode === 'daily' || mode === 'persistent') {
1109
1120
  (this.conversation.options as any).autoCompact = true
1110
1121
  }
@@ -1146,7 +1157,7 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
1146
1157
  const count = (this.state.get('conversationCount') || 0) + 1
1147
1158
  this.state.set('conversationCount', count)
1148
1159
 
1149
- if (this.options.injectTimestamps) {
1160
+ if (this.effectiveOptions.injectTimestamps) {
1150
1161
  question = this.prependTimestamp(question)
1151
1162
  }
1152
1163
 
@@ -1169,7 +1180,7 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
1169
1180
  }
1170
1181
 
1171
1182
  // Auto-save for non-lifecycle modes
1172
- if (this.options.historyMode !== 'lifecycle' && this.state.get('threadId')) {
1183
+ if (this.effectiveOptions.historyMode !== 'lifecycle' && this.state.get('threadId')) {
1173
1184
  await this.conversation.save({ thread: this.state.get('threadId') })
1174
1185
  }
1175
1186
 
@@ -1,9 +1,9 @@
1
1
  import { z } from 'zod'
2
2
  import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
3
3
  import { type AvailableFeatures } from '@soederpop/luca/feature'
4
- import { Feature } from '@soederpop/luca/feature'
5
- import type { AGIContainer } from '../container.server.js'
4
+ import { Feature } from '../feature.js'
6
5
  import type { Assistant } from './assistant.js'
6
+ import type { InterceptorFn, InterceptorPoint, InterceptorPoints } from '../lib/interceptor-chain.js'
7
7
 
8
8
  declare module '@soederpop/luca/feature' {
9
9
  interface AvailableFeatures {
@@ -27,6 +27,10 @@ export interface AssistantEntry {
27
27
  hasHooks: boolean
28
28
  /** Whether a voice.yaml configuration file exists. */
29
29
  hasVoice: boolean
30
+ /** Contents of ABOUT.md if present, undefined otherwise. */
31
+ about?: string
32
+ /** Frontmatter metadata parsed from CORE.md. */
33
+ meta?: Record<string, any>
30
34
  }
31
35
 
32
36
  export const AssistantsManagerEventsSchema = FeatureEventsSchema.extend({
@@ -47,6 +51,7 @@ export const AssistantsManagerStateSchema = FeatureStateSchema.extend({
47
51
  entries: z.record(z.string(), z.any()).describe('Discovered assistant entries keyed by name'),
48
52
  instances: z.record(z.string(), z.any()).describe('Active assistant instances keyed by name'),
49
53
  factories: z.record(z.string(), z.any()).describe('Registered factory functions keyed by name'),
54
+ extraFolders: z.array(z.string()).describe('Additional folders to scan during discovery'),
50
55
  })
51
56
 
52
57
  export const AssistantsManagerOptionsSchema = FeatureOptionsSchema.extend({})
@@ -91,12 +96,10 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
91
96
  entries: {},
92
97
  instances: {},
93
98
  factories: {},
99
+ extraFolders: [],
94
100
  }
95
101
  }
96
102
 
97
- override get container(): AGIContainer {
98
- return super.container as AGIContainer
99
- }
100
103
 
101
104
  /** Discovered assistant entries keyed by name. */
102
105
  get entries(): Record<string, AssistantEntry> {
@@ -113,12 +116,60 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
113
116
  return (this.state.get('factories') || {}) as Record<string, (options: Record<string, any>) => Assistant>
114
117
  }
115
118
 
119
+ /** Interceptor registrations to be applied to every assistant this manager creates. */
120
+ private _interceptors: Array<{ point: InterceptorPoint; fn: InterceptorFn<any> }> = []
121
+
122
+ /**
123
+ * Registers a pipeline interceptor that is applied to every assistant created by this manager.
124
+ * Interceptors are applied at the given interception point on each assistant at creation time.
125
+ * This mirrors the per-assistant `assistant.intercept(point, fn)` API, but scopes it globally
126
+ * across all assistants managed here — useful for cross-cutting concerns like logging, tracing,
127
+ * or policy enforcement.
128
+ *
129
+ * @param {InterceptorPoint} point - The interception point (beforeAsk, beforeTurn, beforeToolCall, afterToolCall, beforeResponse)
130
+ * @param {InterceptorFn<InterceptorPoints[K]>} fn - Middleware function receiving (ctx, next)
131
+ * @returns {this} This instance, for chaining
132
+ *
133
+ * @example
134
+ * ```typescript
135
+ * manager.intercept('beforeAsk', async (ctx, next) => {
136
+ * console.log(`[${ctx.assistant.name}] asking: ${ctx.message}`)
137
+ * await next()
138
+ * })
139
+ * ```
140
+ */
141
+ intercept<K extends InterceptorPoint>(point: K, fn: InterceptorFn<InterceptorPoints[K]>): this {
142
+ this._interceptors.push({ point, fn })
143
+ return this
144
+ }
145
+
116
146
  /**
117
147
  * Discovers assistants by listing subdirectories in ~/.luca/assistants/
118
148
  * and cwd/assistants/. Each subdirectory containing a CORE.md is an assistant.
119
149
  *
120
150
  * @returns {Promise<this>} This instance, for chaining
121
151
  */
152
+ /**
153
+ * Registers an additional folder to scan during assistant discovery and
154
+ * immediately triggers a new discovery pass.
155
+ *
156
+ * @param {string} folderPath - Absolute path to a folder containing assistant subdirectories
157
+ * @returns {Promise<this>} This instance, for chaining
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * await manager.addDiscoveryFolder('/path/to/more/assistants')
162
+ * console.log(manager.available) // includes assistants from the new folder
163
+ * ```
164
+ */
165
+ async addDiscoveryFolder(folderPath: string): Promise<this> {
166
+ const current = this.state.get('extraFolders') as string[]
167
+ if (!current.includes(folderPath)) {
168
+ this.state.set('extraFolders', [...current, folderPath])
169
+ }
170
+ return this.discover()
171
+ }
172
+
122
173
  async discover(): Promise<this> {
123
174
  const { fs, paths, os } = this.container
124
175
 
@@ -127,6 +178,7 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
127
178
  const locations = [
128
179
  `${os.homedir}/.luca/assistants`,
129
180
  paths.resolve('assistants'),
181
+ ...(this.state.get('extraFolders') as string[]),
130
182
  ]
131
183
 
132
184
  for (const location of locations) {
@@ -143,6 +195,25 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
143
195
 
144
196
  // Don't overwrite earlier entries (home takes precedence for same name)
145
197
  if (!discovered[entry]) {
198
+ const hasAbout = fs.exists(`${folder}/ABOUT.md`)
199
+ let about: string | undefined
200
+ let meta: Record<string, any> | undefined
201
+
202
+ if (hasAbout) {
203
+ about = fs.readFileSync(`${folder}/ABOUT.md`, 'utf8')
204
+ }
205
+
206
+ try {
207
+ const coreContent = fs.readFileSync(`${folder}/CORE.md`, 'utf8')
208
+ const fmMatch = coreContent.match(/^---\r?\n([\s\S]*?)\r?\n---/)
209
+ if (fmMatch) {
210
+ const yaml = this.container.feature('yaml')
211
+ meta = yaml.parse(fmMatch[1])
212
+ }
213
+ } catch {
214
+ // CORE.md exists but couldn't be parsed — skip meta
215
+ }
216
+
146
217
  discovered[entry] = {
147
218
  name: entry,
148
219
  folder,
@@ -150,6 +221,8 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
150
221
  hasTools: fs.exists(`${folder}/tools.ts`),
151
222
  hasHooks: fs.exists(`${folder}/hooks.ts`),
152
223
  hasVoice: fs.exists(`${folder}/voice.yaml`),
224
+ ...(about != null && { about }),
225
+ ...(meta != null && { meta }),
153
226
  }
154
227
  }
155
228
  }
@@ -262,6 +335,7 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
262
335
  const factory = this.factories[name]
263
336
  if (factory) {
264
337
  const instance = factory(options)
338
+ this._bindAssistant(instance)
265
339
  const updated = { ...this.instances, [name]: instance }
266
340
  this.state.setState({ instances: updated, activeCount: Object.keys(updated).length })
267
341
  this.emit('assistantCreated', name, instance)
@@ -281,6 +355,7 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
281
355
  ...options,
282
356
  })
283
357
 
358
+ this._bindAssistant(instance)
284
359
  const updated = { ...this.instances, [name]: instance }
285
360
  this.state.setState({ instances: updated, activeCount: Object.keys(updated).length })
286
361
  this.emit('assistantCreated', name, instance)
@@ -288,6 +363,21 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
288
363
  return instance
289
364
  }
290
365
 
366
+ /**
367
+ * Wires an assistant into the manager: bridges all assistant events up to the manager
368
+ * as `assistantEvent:<eventName>` with (assistant, ...originalArgs), and applies any
369
+ * globally registered interceptors.
370
+ */
371
+ private _bindAssistant(instance: Assistant): void {
372
+ instance.on('*', (event: string, ...args: any[]) => {
373
+ this.emit(`assistantEvent:${event}` as any, instance, ...args)
374
+ })
375
+
376
+ for (const { point, fn } of this._interceptors) {
377
+ instance.intercept(point, fn)
378
+ }
379
+ }
380
+
291
381
  /**
292
382
  * Returns a previously created assistant instance by name.
293
383
  *
@@ -1,7 +1,6 @@
1
1
  import { z } from 'zod'
2
2
  import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
3
- import { Feature } from '@soederpop/luca/feature'
4
- import type { AGIContainer } from '../container.server.js'
3
+ import { Feature } from '../feature.js'
5
4
  import type { Assistant } from './assistant.js'
6
5
  import type { ToolCallCtx } from '../lib/interceptor-chain.js'
7
6
 
@@ -158,9 +157,6 @@ export class AutonomousAssistant extends Feature<AutonomousAssistantState, Auton
158
157
  }
159
158
  }
160
159
 
161
- override get container(): AGIContainer {
162
- return super.container as AGIContainer
163
- }
164
160
 
165
161
  /** The inner assistant. Throws if not started. */
166
162
  get assistant(): Assistant {
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod'
2
2
  import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
3
- import { Feature } from '@soederpop/luca/feature'
3
+ import { Feature } from '../feature.js'
4
+ import type { Helper } from '../../helper.js'
4
5
 
5
6
  declare module '@soederpop/luca/feature' {
6
7
  interface AvailableFeatures {
@@ -61,7 +62,7 @@ export class BrowserUse extends Feature<BrowserUseState, BrowserUseOptions> {
61
62
  static override optionsSchema = BrowserUseOptionsSchema
62
63
  static override eventsSchema = BrowserUseEventsSchema
63
64
 
64
- static tools = {
65
+ static override tools = {
65
66
  browserOpen: {
66
67
  description: 'Navigate the browser to a URL. Call this first to open a page before any interaction.',
67
68
  schema: z.object({
@@ -223,6 +224,35 @@ export class BrowserUse extends Feature<BrowserUseState, BrowserUseOptions> {
223
224
 
224
225
  static { Feature.register(this, 'browserUse') }
225
226
 
227
+ /**
228
+ * When an assistant uses browserUse, inject system prompt guidance
229
+ * about the browser interaction loop.
230
+ */
231
+ override setupToolsConsumer(consumer: Helper) {
232
+ if (typeof (consumer as any).addSystemPromptExtension === 'function') {
233
+ (consumer as any).addSystemPromptExtension('browserUse', [
234
+ '## Browser Automation',
235
+ '',
236
+ '**The core loop:** `browserOpen` → `browserGetState` → interact → `browserGetState` again.',
237
+ '',
238
+ '`browserGetState` is your eyes. It returns all interactive elements with index numbers. You MUST call it:',
239
+ '- After every `browserOpen` or navigation',
240
+ '- After any action that changes the page (click, submit, scroll)',
241
+ '- Before any interaction — to get fresh element indices',
242
+ '',
243
+ 'Element indices change whenever the page updates. Never reuse indices from a previous `browserGetState` call after the page has changed.',
244
+ '',
245
+ '**Interacting with elements:** Use `browserInput` (click + type) for form fields. Use `browserClick` for buttons and links. Use `browserSelect` for dropdowns. All require an element index from `browserGetState`.',
246
+ '',
247
+ '**When things load asynchronously:** Use `browserWaitForSelector` or `browserWaitForText` after actions that trigger page updates (form submissions, AJAX). Then call `browserGetState` to see the updated page.',
248
+ '',
249
+ '**Debugging:** If an interaction doesn\'t work, take a `browserScreenshot` to see the actual page state. Check `browserGetState` to see what elements are available.',
250
+ '',
251
+ '**Cleanup:** Call `browserClose` when you\'re done to free resources.',
252
+ ].join('\n'))
253
+ }
254
+ }
255
+
226
256
  override async afterInitialize() {
227
257
  if (this.options.session) this.state.set('session', this.options.session)
228
258
  if (this.options.headed) this.state.set('headed', true)
@@ -2,7 +2,7 @@
2
2
  import { z } from 'zod'
3
3
  import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
4
4
  import { type AvailableFeatures } from '@soederpop/luca/feature'
5
- import { Feature } from '@soederpop/luca/feature'
5
+ import { Feature } from '../feature.js'
6
6
 
7
7
  declare module '@soederpop/luca/feature' {
8
8
  interface AvailableFeatures {
@@ -1349,6 +1349,170 @@ export class ClaudeCode extends Feature<ClaudeCodeState, ClaudeCodeOptions> {
1349
1349
  }
1350
1350
  }
1351
1351
 
1352
+ /**
1353
+ * List all Claude Code processes currently registered in ~/.claude/sessions/.
1354
+ * Returns each session's metadata along with whether the process is still alive.
1355
+ *
1356
+ * @returns {Promise<Array<{ pid: number; sessionId: string; cwd: string; startedAt: number; kind: string; entrypoint: string; alive: boolean }>>}
1357
+ *
1358
+ * @example
1359
+ * const sessions = await cc.listProcessSessions()
1360
+ * for (const s of sessions) {
1361
+ * console.log(`[${s.alive ? 'LIVE' : 'dead'}] PID ${s.pid} in ${s.cwd}`)
1362
+ * }
1363
+ */
1364
+ async listProcessSessions(): Promise<Array<{
1365
+ pid: number
1366
+ sessionId: string
1367
+ cwd: string
1368
+ startedAt: number
1369
+ kind: string
1370
+ entrypoint: string
1371
+ alive: boolean
1372
+ }>> {
1373
+ const fs = this.container.feature('fs')
1374
+ const proc = this.container.feature('proc')
1375
+ const home = process.env.HOME ?? '/tmp'
1376
+ const sessionsDir = `${home}/.claude/sessions`
1377
+
1378
+ let files: string[]
1379
+ try {
1380
+ files = await fs.readdir(sessionsDir)
1381
+ } catch {
1382
+ return []
1383
+ }
1384
+
1385
+ const jsonFiles = files.filter((f: string) => f.endsWith('.json'))
1386
+
1387
+ const results = await Promise.all(jsonFiles.map(async (file: string) => {
1388
+ try {
1389
+ const raw = await fs.readFile(`${sessionsDir}/${file}`, 'utf8')
1390
+ const data = JSON.parse(raw)
1391
+ let alive = false
1392
+ try {
1393
+ await proc.exec(`kill -0 ${data.pid}`)
1394
+ alive = true
1395
+ } catch {
1396
+ alive = false
1397
+ }
1398
+ return { ...data, alive }
1399
+ } catch {
1400
+ return null
1401
+ }
1402
+ }))
1403
+
1404
+ return results.filter(Boolean)
1405
+ }
1406
+
1407
+ /**
1408
+ * Read a single process session by PID from ~/.claude/sessions/<pid>.json.
1409
+ *
1410
+ * @param {number} pid - The process ID
1411
+ * @returns {Promise<{ pid: number; sessionId: string; cwd: string; startedAt: number; kind: string; entrypoint: string } | null>}
1412
+ *
1413
+ * @example
1414
+ * const session = await cc.getProcessSession(12345)
1415
+ * console.log(session?.cwd)
1416
+ */
1417
+ async getProcessSession(pid: number): Promise<{
1418
+ pid: number
1419
+ sessionId: string
1420
+ cwd: string
1421
+ startedAt: number
1422
+ kind: string
1423
+ entrypoint: string
1424
+ } | null> {
1425
+ const fs = this.container.feature('fs')
1426
+ const home = process.env.HOME ?? '/tmp'
1427
+ try {
1428
+ const raw = await fs.readFile(`${home}/.claude/sessions/${pid}.json`, 'utf8')
1429
+ return JSON.parse(raw)
1430
+ } catch {
1431
+ return null
1432
+ }
1433
+ }
1434
+
1435
+ /**
1436
+ * Read the conversation history for a Claude Code session from its JSONL file in
1437
+ * ~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl.
1438
+ *
1439
+ * Returns an array of parsed message objects (user, assistant, tool_use, tool_result).
1440
+ *
1441
+ * @param {string} sessionId - The Claude CLI session ID (from listProcessSessions or getProcessSession)
1442
+ * @param {string} cwd - The working directory of the session (used to locate the project folder)
1443
+ * @returns {Promise<any[]>} Array of parsed JSONL records
1444
+ *
1445
+ * @example
1446
+ * const sessions = await cc.listProcessSessions()
1447
+ * const s = sessions[0]
1448
+ * const history = await cc.getConversationHistory(s.sessionId, s.cwd)
1449
+ * console.log(history.length, 'turns')
1450
+ */
1451
+ async getConversationHistory(sessionId: string, cwd?: string): Promise<any[]> {
1452
+ const fs = this.container.feature('fs')
1453
+ const home = process.env.HOME ?? '/tmp'
1454
+ const resolvedCwd = cwd ?? this.options.cwd ?? (this.container as any).cwd
1455
+ const encodedCwd = resolvedCwd.replace(/\//g, '-').replace(/@/g, '-')
1456
+ const filePath = `${home}/.claude/projects/${encodedCwd}/${sessionId}.jsonl`
1457
+ try {
1458
+ const raw = await fs.readFile(filePath, 'utf8')
1459
+ return raw
1460
+ .split('\n')
1461
+ .filter((line: string) => line.trim())
1462
+ .map((line: string) => JSON.parse(line))
1463
+ } catch {
1464
+ return []
1465
+ }
1466
+ }
1467
+
1468
+ /**
1469
+ * List all conversation sessions stored for a given working directory.
1470
+ * Reads ~/.claude/projects/<encoded-cwd>/ and returns metadata for each .jsonl file.
1471
+ *
1472
+ * @param {string} cwd - The working directory path to look up
1473
+ * @returns {Promise<Array<{ sessionId: string; filePath: string; messageCount: number }>>}
1474
+ *
1475
+ * @example
1476
+ * const sessions = await cc.listSessionsForCwd('/Users/me/my-project')
1477
+ * for (const s of sessions) {
1478
+ * console.log(s.sessionId, s.messageCount, 'messages')
1479
+ * }
1480
+ */
1481
+ async listSessionsForCwd(cwd?: string): Promise<Array<{
1482
+ sessionId: string
1483
+ filePath: string
1484
+ messageCount: number
1485
+ }>> {
1486
+ const fs = this.container.feature('fs')
1487
+ const home = process.env.HOME ?? '/tmp'
1488
+ const resolvedCwd = cwd ?? this.options.cwd ?? (this.container as any).cwd
1489
+ const encodedCwd = resolvedCwd.replace(/\//g, '-').replace(/@/g, '-')
1490
+ const projectDir = `${home}/.claude/projects/${encodedCwd}`
1491
+
1492
+ let files: string[]
1493
+ try {
1494
+ files = await fs.readdir(projectDir)
1495
+ } catch {
1496
+ return []
1497
+ }
1498
+
1499
+ const jsonlFiles = files.filter((f: string) => f.endsWith('.jsonl'))
1500
+
1501
+ const results = await Promise.all(jsonlFiles.map(async (file: string) => {
1502
+ const sessionId = file.replace(/\.jsonl$/, '')
1503
+ const filePath = `${projectDir}/${file}`
1504
+ try {
1505
+ const raw = await fs.readFile(filePath, 'utf8')
1506
+ const messageCount = raw.split('\n').filter((l: string) => l.trim()).length
1507
+ return { sessionId, filePath, messageCount }
1508
+ } catch {
1509
+ return { sessionId, filePath, messageCount: 0 }
1510
+ }
1511
+ }))
1512
+
1513
+ return results
1514
+ }
1515
+
1352
1516
  /**
1353
1517
  * Clean up any temp MCP config files created during sessions.
1354
1518
  */