@neurocode-ai/core 1.18.8

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 (490) hide show
  1. package/bin/neurocode +199 -0
  2. package/bunfig.toml +2 -0
  3. package/drizzle.config.ts +10 -0
  4. package/package.json +131 -0
  5. package/schema.json +2071 -0
  6. package/script/fix-node-pty.ts +28 -0
  7. package/script/migration.ts +196 -0
  8. package/src/account/sql.ts +39 -0
  9. package/src/account.ts +101 -0
  10. package/src/agent.ts +111 -0
  11. package/src/aisdk.ts +235 -0
  12. package/src/background-job.ts +365 -0
  13. package/src/catalog.ts +301 -0
  14. package/src/command.ts +64 -0
  15. package/src/config/agent.ts +25 -0
  16. package/src/config/attachments.ts +15 -0
  17. package/src/config/command.ts +12 -0
  18. package/src/config/compaction.ts +15 -0
  19. package/src/config/experimental.ts +18 -0
  20. package/src/config/formatter.ts +12 -0
  21. package/src/config/lsp.ts +18 -0
  22. package/src/config/markdown.ts +36 -0
  23. package/src/config/mcp.ts +48 -0
  24. package/src/config/plugin/agent.ts +179 -0
  25. package/src/config/plugin/command.ts +83 -0
  26. package/src/config/plugin/external.ts +91 -0
  27. package/src/config/plugin/provider.ts +113 -0
  28. package/src/config/plugin/reference.ts +70 -0
  29. package/src/config/plugin/skill.ts +50 -0
  30. package/src/config/plugin.ts +13 -0
  31. package/src/config/provider.ts +71 -0
  32. package/src/config/reference.ts +22 -0
  33. package/src/config/tool-output.ts +9 -0
  34. package/src/config/watcher.ts +7 -0
  35. package/src/config.ts +227 -0
  36. package/src/control-plane/move-session.ts +148 -0
  37. package/src/control-plane/workspace.sql.ts +20 -0
  38. package/src/credential/sql.ts +14 -0
  39. package/src/credential.ts +138 -0
  40. package/src/cross-spawn-spawner.ts +507 -0
  41. package/src/data-migration.sql.ts +6 -0
  42. package/src/database/database.ts +57 -0
  43. package/src/database/migration/20260127222353_familiar_lady_ursula.ts +107 -0
  44. package/src/database/migration/20260211171708_add_project_commands.ts +11 -0
  45. package/src/database/migration/20260213144116_wakeful_the_professor.ts +23 -0
  46. package/src/database/migration/20260225215848_workspace.ts +19 -0
  47. package/src/database/migration/20260227213759_add_session_workspace_id.ts +12 -0
  48. package/src/database/migration/20260228203230_blue_harpoon.ts +30 -0
  49. package/src/database/migration/20260303231226_add_workspace_fields.ts +15 -0
  50. package/src/database/migration/20260309230000_move_org_to_state.ts +15 -0
  51. package/src/database/migration/20260312043431_session_message_cursor.ts +16 -0
  52. package/src/database/migration/20260323234822_events.ts +26 -0
  53. package/src/database/migration/20260410174513_workspace-name.ts +29 -0
  54. package/src/database/migration/20260413175956_chief_energizer.ts +24 -0
  55. package/src/database/migration/20260423070820_add_icon_url_override.ts +14 -0
  56. package/src/database/migration/20260427172553_slow_nightmare.ts +30 -0
  57. package/src/database/migration/20260428004200_add_session_path.ts +11 -0
  58. package/src/database/migration/20260501142318_next_venus.ts +12 -0
  59. package/src/database/migration/20260504145000_add_sync_owner.ts +11 -0
  60. package/src/database/migration/20260507164347_add_workspace_time.ts +11 -0
  61. package/src/database/migration/20260510033149_session_usage.ts +56 -0
  62. package/src/database/migration/20260511000411_data_migration_state.ts +16 -0
  63. package/src/database/migration/20260511173437_session-metadata.ts +16 -0
  64. package/src/database/migration/20260601010001_normalize_storage_paths.ts +22 -0
  65. package/src/database/migration/20260601202201_amazing_prowler.ts +11 -0
  66. package/src/database/migration/20260602002951_lowly_union_jack.ts +24 -0
  67. package/src/database/migration/20260602182828_add_project_directories.ts +20 -0
  68. package/src/database/migration/20260603001617_session_message_projection_indexes.ts +19 -0
  69. package/src/database/migration/20260603040000_session_message_projection_order.ts +19 -0
  70. package/src/database/migration/20260603141458_session_input_inbox.ts +25 -0
  71. package/src/database/migration/20260603160727_jittery_ezekiel_stane.ts +20 -0
  72. package/src/database/migration/20260604172448_event_sourced_session_input.ts +47 -0
  73. package/src/database/migration/20260605003541_add_session_context_snapshot.ts +21 -0
  74. package/src/database/migration/20260605042240_add_context_epoch_agent.ts +11 -0
  75. package/src/database/migration/20260611035744_credential.ts +25 -0
  76. package/src/database/migration/20260611192811_lush_chimera.ts +25 -0
  77. package/src/database/migration/20260612174303_project_dir_strategy.ts +29 -0
  78. package/src/database/migration/20260622142730_simplify_session_context_epoch.ts +13 -0
  79. package/src/database/migration/20260622170816_reset_v2_session_state.ts +17 -0
  80. package/src/database/migration/20260622202450_simplify_session_input.ts +17 -0
  81. package/src/database/migration.gen.ts +44 -0
  82. package/src/database/migration.ts +81 -0
  83. package/src/database/path.ts +91 -0
  84. package/src/database/schema.gen.ts +274 -0
  85. package/src/database/schema.sql.ts +10 -0
  86. package/src/database/sqlite.bun.ts +183 -0
  87. package/src/database/sqlite.node.ts +178 -0
  88. package/src/database/sqlite.ts +8 -0
  89. package/src/effect/app-node-builder.ts +23 -0
  90. package/src/effect/app-node-platform.ts +18 -0
  91. package/src/effect/app-node.ts +14 -0
  92. package/src/effect/dfdf +1 -0
  93. package/src/effect/keyed-mutex.ts +45 -0
  94. package/src/effect/layer-node.ts +333 -0
  95. package/src/effect/memo-map.ts +3 -0
  96. package/src/effect/runtime.ts +21 -0
  97. package/src/effect/service-use.ts +43 -0
  98. package/src/event/sql.ts +25 -0
  99. package/src/event.ts +638 -0
  100. package/src/file-mutation.ts +207 -0
  101. package/src/file.ts +6 -0
  102. package/src/filesystem/fff.bun.ts +140 -0
  103. package/src/filesystem/fff.node.ts +138 -0
  104. package/src/filesystem/ignore.ts +67 -0
  105. package/src/filesystem/protected.ts +53 -0
  106. package/src/filesystem/search.ts +239 -0
  107. package/src/filesystem/watcher.ts +140 -0
  108. package/src/filesystem.ts +118 -0
  109. package/src/flag/flag.ts +78 -0
  110. package/src/fs-util.ts +274 -0
  111. package/src/git.ts +987 -0
  112. package/src/github-copilot/README.md +5 -0
  113. package/src/github-copilot/chat/convert-to-openai-compatible-chat-messages.ts +170 -0
  114. package/src/github-copilot/chat/get-response-metadata.ts +15 -0
  115. package/src/github-copilot/chat/map-openai-compatible-finish-reason.ts +19 -0
  116. package/src/github-copilot/chat/openai-compatible-api-types.ts +64 -0
  117. package/src/github-copilot/chat/openai-compatible-chat-language-model.ts +815 -0
  118. package/src/github-copilot/chat/openai-compatible-chat-options.ts +28 -0
  119. package/src/github-copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
  120. package/src/github-copilot/chat/openai-compatible-prepare-tools.ts +83 -0
  121. package/src/github-copilot/copilot-provider.ts +100 -0
  122. package/src/github-copilot/openai-compatible-error.ts +27 -0
  123. package/src/github-copilot/responses/convert-to-openai-responses-input.ts +335 -0
  124. package/src/github-copilot/responses/map-openai-responses-finish-reason.ts +22 -0
  125. package/src/github-copilot/responses/openai-config.ts +18 -0
  126. package/src/github-copilot/responses/openai-error.ts +22 -0
  127. package/src/github-copilot/responses/openai-responses-api-types.ts +214 -0
  128. package/src/github-copilot/responses/openai-responses-language-model.ts +1770 -0
  129. package/src/github-copilot/responses/openai-responses-prepare-tools.ts +173 -0
  130. package/src/github-copilot/responses/openai-responses-settings.ts +1 -0
  131. package/src/github-copilot/responses/tool/code-interpreter.ts +87 -0
  132. package/src/github-copilot/responses/tool/file-search.ts +127 -0
  133. package/src/github-copilot/responses/tool/image-generation.ts +114 -0
  134. package/src/github-copilot/responses/tool/local-shell.ts +64 -0
  135. package/src/github-copilot/responses/tool/web-search-preview.ts +103 -0
  136. package/src/github-copilot/responses/tool/web-search.ts +102 -0
  137. package/src/global.ts +87 -0
  138. package/src/id/id.ts +47 -0
  139. package/src/image/photon.ts +94 -0
  140. package/src/image.ts +81 -0
  141. package/src/installation/version.ts +8 -0
  142. package/src/instruction-context.ts +101 -0
  143. package/src/integration/connection.ts +12 -0
  144. package/src/integration.ts +520 -0
  145. package/src/location-mutation.ts +162 -0
  146. package/src/location-service-map.ts +18 -0
  147. package/src/location-services.ts +115 -0
  148. package/src/location.ts +39 -0
  149. package/src/markdown.d.ts +4 -0
  150. package/src/model.ts +41 -0
  151. package/src/models-dev.ts +260 -0
  152. package/src/npm-config.ts +40 -0
  153. package/src/npm.ts +269 -0
  154. package/src/oauth/page.ts +276 -0
  155. package/src/observability/logging.ts +71 -0
  156. package/src/observability/otlp.ts +79 -0
  157. package/src/observability/shared.ts +1 -0
  158. package/src/observability.ts +24 -0
  159. package/src/patch.ts +197 -0
  160. package/src/permission/saved.ts +79 -0
  161. package/src/permission/sql.ts +20 -0
  162. package/src/permission.ts +310 -0
  163. package/src/plugin/agent.ts +206 -0
  164. package/src/plugin/command/initialize.txt +65 -0
  165. package/src/plugin/command/review.txt +100 -0
  166. package/src/plugin/command.ts +25 -0
  167. package/src/plugin/host.ts +219 -0
  168. package/src/plugin/internal.ts +153 -0
  169. package/src/plugin/layer-map.example.ts +94 -0
  170. package/src/plugin/models-dev.ts +183 -0
  171. package/src/plugin/promise.ts +93 -0
  172. package/src/plugin/provider/alibaba.ts +15 -0
  173. package/src/plugin/provider/amazon-bedrock.ts +126 -0
  174. package/src/plugin/provider/anthropic.ts +27 -0
  175. package/src/plugin/provider/azure.ts +82 -0
  176. package/src/plugin/provider/cerebras.ts +26 -0
  177. package/src/plugin/provider/cloudflare-ai-gateway.ts +81 -0
  178. package/src/plugin/provider/cloudflare-workers-ai.ts +81 -0
  179. package/src/plugin/provider/cohere.ts +15 -0
  180. package/src/plugin/provider/deepinfra.ts +15 -0
  181. package/src/plugin/provider/dynamic.ts +31 -0
  182. package/src/plugin/provider/gateway.ts +15 -0
  183. package/src/plugin/provider/github-copilot.ts +52 -0
  184. package/src/plugin/provider/gitlab.ts +65 -0
  185. package/src/plugin/provider/google-vertex.ts +173 -0
  186. package/src/plugin/provider/google.ts +15 -0
  187. package/src/plugin/provider/groq.ts +15 -0
  188. package/src/plugin/provider/kilo.ts +21 -0
  189. package/src/plugin/provider/llmgateway.ts +26 -0
  190. package/src/plugin/provider/mistral.ts +15 -0
  191. package/src/plugin/provider/nvidia.ts +22 -0
  192. package/src/plugin/provider/openai-compatible.ts +17 -0
  193. package/src/plugin/provider/openai.ts +292 -0
  194. package/src/plugin/provider/opencode.ts +311 -0
  195. package/src/plugin/provider/openrouter.ts +36 -0
  196. package/src/plugin/provider/perplexity.ts +15 -0
  197. package/src/plugin/provider/sap-ai-core.ts +46 -0
  198. package/src/plugin/provider/snowflake-cortex.ts +89 -0
  199. package/src/plugin/provider/togetherai.ts +15 -0
  200. package/src/plugin/provider/venice.ts +15 -0
  201. package/src/plugin/provider/vercel.ts +27 -0
  202. package/src/plugin/provider/xai.ts +22 -0
  203. package/src/plugin/provider/zenmux.ts +21 -0
  204. package/src/plugin/provider.ts +71 -0
  205. package/src/plugin/skill/customize-neurocode.md +452 -0
  206. package/src/plugin/skill.ts +31 -0
  207. package/src/plugin/variant.ts +39 -0
  208. package/src/plugin.ts +167 -0
  209. package/src/policy.ts +49 -0
  210. package/src/process.ts +261 -0
  211. package/src/project/copy-strategies.ts +35 -0
  212. package/src/project/copy.ts +292 -0
  213. package/src/project/directories.ts +158 -0
  214. package/src/project/schema.ts +16 -0
  215. package/src/project/sql.ts +35 -0
  216. package/src/project.ts +136 -0
  217. package/src/provider.ts +25 -0
  218. package/src/pty/protocol.ts +37 -0
  219. package/src/pty/pty.bun.ts +26 -0
  220. package/src/pty/pty.node.ts +29 -0
  221. package/src/pty/pty.ts +25 -0
  222. package/src/pty/schema.ts +1 -0
  223. package/src/pty/ticket.ts +56 -0
  224. package/src/pty.ts +318 -0
  225. package/src/public-event-manifest.ts +7 -0
  226. package/src/question.ts +153 -0
  227. package/src/reference/guidance.ts +69 -0
  228. package/src/reference.ts +125 -0
  229. package/src/repository-cache.ts +259 -0
  230. package/src/repository.ts +214 -0
  231. package/src/ripgrep/binary.ts +132 -0
  232. package/src/ripgrep.ts +281 -0
  233. package/src/schema.ts +79 -0
  234. package/src/session/compaction.ts +241 -0
  235. package/src/session/context-epoch.ts +174 -0
  236. package/src/session/error.ts +24 -0
  237. package/src/session/event.ts +2 -0
  238. package/src/session/execution/local.ts +46 -0
  239. package/src/session/execution.ts +34 -0
  240. package/src/session/history.ts +101 -0
  241. package/src/session/info.ts +50 -0
  242. package/src/session/input.ts +288 -0
  243. package/src/session/message-updater.ts +397 -0
  244. package/src/session/message.ts +2 -0
  245. package/src/session/projector.ts +458 -0
  246. package/src/session/prompt.ts +1 -0
  247. package/src/session/revert.ts +121 -0
  248. package/src/session/run-coordinator.ts +104 -0
  249. package/src/session/runner/index.ts +28 -0
  250. package/src/session/runner/llm.ts +432 -0
  251. package/src/session/runner/max-steps.ts +16 -0
  252. package/src/session/runner/model.ts +218 -0
  253. package/src/session/runner/publish-llm-event.ts +423 -0
  254. package/src/session/runner/to-llm-message.ts +171 -0
  255. package/src/session/schema.ts +9 -0
  256. package/src/session/sql.ts +176 -0
  257. package/src/session/store.ts +63 -0
  258. package/src/session/todo.ts +78 -0
  259. package/src/session.ts +486 -0
  260. package/src/share/sql.ts +13 -0
  261. package/src/shell.ts +226 -0
  262. package/src/skill/discovery.ts +213 -0
  263. package/src/skill/guidance.ts +76 -0
  264. package/src/skill.ts +132 -0
  265. package/src/snapshot.ts +266 -0
  266. package/src/state.ts +128 -0
  267. package/src/system-context/builtins.ts +50 -0
  268. package/src/system-context/index.ts +320 -0
  269. package/src/system-context/registry.ts +49 -0
  270. package/src/tool/AGENTS.md +59 -0
  271. package/src/tool/application-tools.ts +57 -0
  272. package/src/tool/apply-patch.ts +219 -0
  273. package/src/tool/bash.ts +207 -0
  274. package/src/tool/builtins.ts +48 -0
  275. package/src/tool/edit.ts +223 -0
  276. package/src/tool/glob.ts +105 -0
  277. package/src/tool/grep.ts +137 -0
  278. package/src/tool/http-body.ts +30 -0
  279. package/src/tool/question.ts +94 -0
  280. package/src/tool/read-filesystem.ts +366 -0
  281. package/src/tool/read.ts +117 -0
  282. package/src/tool/registry.ts +147 -0
  283. package/src/tool/skill.ts +109 -0
  284. package/src/tool/todowrite.ts +62 -0
  285. package/src/tool/tool.ts +162 -0
  286. package/src/tool/tools.ts +13 -0
  287. package/src/tool/webfetch.ts +218 -0
  288. package/src/tool/websearch.ts +260 -0
  289. package/src/tool/write.ts +101 -0
  290. package/src/tool-output-store.ts +211 -0
  291. package/src/util/array.ts +10 -0
  292. package/src/util/binary.ts +41 -0
  293. package/src/util/effect-flock.ts +284 -0
  294. package/src/util/encode.ts +51 -0
  295. package/src/util/error.ts +70 -0
  296. package/src/util/flock.ts +358 -0
  297. package/src/util/glob.ts +34 -0
  298. package/src/util/hash.ts +11 -0
  299. package/src/util/identifier.ts +1 -0
  300. package/src/util/iife.ts +3 -0
  301. package/src/util/lazy.ts +11 -0
  302. package/src/util/module.ts +10 -0
  303. package/src/util/path.ts +37 -0
  304. package/src/util/retry.ts +42 -0
  305. package/src/util/slug.ts +74 -0
  306. package/src/util/token.ts +5 -0
  307. package/src/util/which.ts +14 -0
  308. package/src/util/wildcard.ts +14 -0
  309. package/src/v1/config/agent.ts +89 -0
  310. package/src/v1/config/attachment.ts +25 -0
  311. package/src/v1/config/command.ts +13 -0
  312. package/src/v1/config/config.ts +192 -0
  313. package/src/v1/config/console-state.ts +16 -0
  314. package/src/v1/config/error.ts +39 -0
  315. package/src/v1/config/formatter.ts +13 -0
  316. package/src/v1/config/layout.ts +6 -0
  317. package/src/v1/config/lsp.ts +80 -0
  318. package/src/v1/config/mcp.ts +63 -0
  319. package/src/v1/config/migrate.ts +254 -0
  320. package/src/v1/config/permission.ts +50 -0
  321. package/src/v1/config/plugin.ts +9 -0
  322. package/src/v1/config/provider-options.ts +227 -0
  323. package/src/v1/config/provider.ts +121 -0
  324. package/src/v1/config/server.ts +19 -0
  325. package/src/v1/config/skills.ts +13 -0
  326. package/src/v1/permission.ts +33 -0
  327. package/src/v1/session.ts +68 -0
  328. package/src/v2-schema.ts +3 -0
  329. package/src/workspace.ts +6 -0
  330. package/sst-env.d.ts +10 -0
  331. package/test/agent.test.ts +131 -0
  332. package/test/application-tools.test.ts +287 -0
  333. package/test/background-job.test.ts +106 -0
  334. package/test/catalog.test.ts +353 -0
  335. package/test/command.test.ts +56 -0
  336. package/test/config/agent.test.ts +349 -0
  337. package/test/config/command.test.ts +83 -0
  338. package/test/config/config.test.ts +798 -0
  339. package/test/config/fixtures/plugin/directory-plugin.ts +13 -0
  340. package/test/config/plugin.test.ts +248 -0
  341. package/test/config/provider-options.test.ts +224 -0
  342. package/test/config/provider.test.ts +269 -0
  343. package/test/config/skill.test.ts +80 -0
  344. package/test/credential.test.ts +36 -0
  345. package/test/database-migration.test.ts +644 -0
  346. package/test/effect/cross-spawn-spawner.test.ts +426 -0
  347. package/test/effect/keyed-mutex.test.ts +73 -0
  348. package/test/effect/layer-node/layer-node-types.test.ts +143 -0
  349. package/test/effect/layer-node/layer-node.test.ts +261 -0
  350. package/test/effect/layer-node/node-build.test.ts +125 -0
  351. package/test/effect/observability.test.ts +109 -0
  352. package/test/event.test.ts +1124 -0
  353. package/test/file-mutation.test.ts +368 -0
  354. package/test/filesystem/filesystem.test.ts +387 -0
  355. package/test/filesystem/ignore.test.ts +10 -0
  356. package/test/filesystem/search.test.ts +44 -0
  357. package/test/filesystem/watcher.test.ts +266 -0
  358. package/test/fixture/effect-flock-worker.ts +60 -0
  359. package/test/fixture/flock-worker.ts +72 -0
  360. package/test/fixture/git.ts +49 -0
  361. package/test/fixture/location.ts +26 -0
  362. package/test/fixture/tmpdir.ts +25 -0
  363. package/test/fixtures/recordings/session-runner/openai-chat-streams-text.json +27 -0
  364. package/test/git.test.ts +164 -0
  365. package/test/github-copilot/convert-to-copilot-messages.test.ts +523 -0
  366. package/test/github-copilot/copilot-chat-model.test.ts +592 -0
  367. package/test/github-copilot/openai-responses-language-model.test.ts +206 -0
  368. package/test/global.test.ts +16 -0
  369. package/test/instruction-context.test.ts +323 -0
  370. package/test/integration.test.ts +349 -0
  371. package/test/legacy-event-schema.test.ts +16 -0
  372. package/test/lib/effect.ts +53 -0
  373. package/test/lib/tool.ts +20 -0
  374. package/test/location-filesystem.test.ts +69 -0
  375. package/test/location-layer.test.ts +226 -0
  376. package/test/location-mutation.test.ts +178 -0
  377. package/test/location.test.ts +42 -0
  378. package/test/model.test.ts +23 -0
  379. package/test/models.test.ts +290 -0
  380. package/test/move-session.test.ts +235 -0
  381. package/test/npm-config.test.ts +51 -0
  382. package/test/npm.test.ts +84 -0
  383. package/test/oauth-page.test.ts +15 -0
  384. package/test/patch.test.ts +68 -0
  385. package/test/permission.test.ts +315 -0
  386. package/test/plugin/command.test.ts +47 -0
  387. package/test/plugin/fixture.ts +52 -0
  388. package/test/plugin/fixtures/config-effect-plugin.ts +15 -0
  389. package/test/plugin/fixtures/config-promise-plugin.ts +13 -0
  390. package/test/plugin/fixtures/invalid-plugin.ts +1 -0
  391. package/test/plugin/fixtures/models-dev.json +14 -0
  392. package/test/plugin/fixtures/provider-factory.ts +9 -0
  393. package/test/plugin/host.ts +303 -0
  394. package/test/plugin/models-dev.test.ts +170 -0
  395. package/test/plugin/promise.test.ts +67 -0
  396. package/test/plugin/provider-alibaba.test.ts +92 -0
  397. package/test/plugin/provider-amazon-bedrock.test.ts +622 -0
  398. package/test/plugin/provider-anthropic.test.ts +94 -0
  399. package/test/plugin/provider-azure-cognitive-services.test.ts +199 -0
  400. package/test/plugin/provider-azure.test.ts +293 -0
  401. package/test/plugin/provider-cerebras.test.ts +136 -0
  402. package/test/plugin/provider-cloudflare-ai-gateway.test.ts +426 -0
  403. package/test/plugin/provider-cloudflare-workers-ai.test.ts +279 -0
  404. package/test/plugin/provider-cohere.test.ts +123 -0
  405. package/test/plugin/provider-deepinfra.test.ts +159 -0
  406. package/test/plugin/provider-dynamic.test.ts +184 -0
  407. package/test/plugin/provider-gateway.test.ts +115 -0
  408. package/test/plugin/provider-github-copilot.test.ts +278 -0
  409. package/test/plugin/provider-gitlab.test.ts +308 -0
  410. package/test/plugin/provider-google-vertex-anthropic.test.ts +264 -0
  411. package/test/plugin/provider-google-vertex.test.ts +387 -0
  412. package/test/plugin/provider-google.test.ts +80 -0
  413. package/test/plugin/provider-groq.test.ts +121 -0
  414. package/test/plugin/provider-kilo.test.ts +99 -0
  415. package/test/plugin/provider-llmgateway.test.ts +80 -0
  416. package/test/plugin/provider-mistral.test.ts +129 -0
  417. package/test/plugin/provider-nvidia.test.ts +97 -0
  418. package/test/plugin/provider-openai-compatible.test.ts +107 -0
  419. package/test/plugin/provider-openai.test.ts +176 -0
  420. package/test/plugin/provider-opencode.test.ts +423 -0
  421. package/test/plugin/provider-openrouter.test.ts +113 -0
  422. package/test/plugin/provider-perplexity.test.ts +122 -0
  423. package/test/plugin/provider-sap-ai-core.test.ts +159 -0
  424. package/test/plugin/provider-snowflake-cortex.test.ts +261 -0
  425. package/test/plugin/provider-togetherai.test.ts +129 -0
  426. package/test/plugin/provider-venice.test.ts +115 -0
  427. package/test/plugin/provider-vercel.test.ts +83 -0
  428. package/test/plugin/provider-xai.test.ts +125 -0
  429. package/test/plugin/provider-zenmux.test.ts +110 -0
  430. package/test/plugin/skill.test.ts +25 -0
  431. package/test/plugin/variant.test.ts +67 -0
  432. package/test/plugin.test.ts +71 -0
  433. package/test/policy.test.ts +85 -0
  434. package/test/preload.ts +5 -0
  435. package/test/process/process.test.ts +368 -0
  436. package/test/project-copy.test.ts +400 -0
  437. package/test/project-directories.test.ts +61 -0
  438. package/test/project.test.ts +221 -0
  439. package/test/provider-mistral.test.ts +282 -0
  440. package/test/provider-xai-responses.test.ts +32 -0
  441. package/test/pty/info-schema.test.ts +32 -0
  442. package/test/pty/protocol.test.ts +27 -0
  443. package/test/pty/pty-session.test.ts +240 -0
  444. package/test/pty/ticket.test.ts +62 -0
  445. package/test/question.test.ts +114 -0
  446. package/test/reference-guidance.test.ts +76 -0
  447. package/test/reference.test.ts +78 -0
  448. package/test/repository-cache.test.ts +153 -0
  449. package/test/repository.test.ts +71 -0
  450. package/test/ripgrep.test.ts +65 -0
  451. package/test/session-compaction.test.ts +27 -0
  452. package/test/session-create.test.ts +425 -0
  453. package/test/session-history.test.ts +165 -0
  454. package/test/session-projector.test.ts +533 -0
  455. package/test/session-prompt.test.ts +584 -0
  456. package/test/session-run-coordinator.test.ts +418 -0
  457. package/test/session-runner-message.test.ts +501 -0
  458. package/test/session-runner-model.test.ts +347 -0
  459. package/test/session-runner-recorded.test.ts +193 -0
  460. package/test/session-runner-tool-events.test.ts +136 -0
  461. package/test/session-runner-tool-registry.test.ts +452 -0
  462. package/test/session-runner.test.ts +3365 -0
  463. package/test/session-todo.test.ts +94 -0
  464. package/test/session-tool-progress.test.ts +158 -0
  465. package/test/shared-schema.test.ts +206 -0
  466. package/test/shell.test.ts +108 -0
  467. package/test/skill/guidance.test.ts +144 -0
  468. package/test/skill-discovery.test.ts +165 -0
  469. package/test/skill.test.ts +125 -0
  470. package/test/snapshot.test.ts +178 -0
  471. package/test/state.test.ts +115 -0
  472. package/test/system-context/builtins.test.ts +129 -0
  473. package/test/system-context/index.test.ts +307 -0
  474. package/test/system-context/registry.test.ts +114 -0
  475. package/test/tool-apply-patch.test.ts +436 -0
  476. package/test/tool-bash.test.ts +437 -0
  477. package/test/tool-edit.test.ts +434 -0
  478. package/test/tool-output-store.test.ts +247 -0
  479. package/test/tool-question.test.ts +162 -0
  480. package/test/tool-read-filesystem.test.ts +118 -0
  481. package/test/tool-read.test.ts +674 -0
  482. package/test/tool-skill.test.ts +149 -0
  483. package/test/tool-todowrite.test.ts +125 -0
  484. package/test/tool-webfetch.test.ts +281 -0
  485. package/test/tool-websearch.test.ts +316 -0
  486. package/test/tool-write.test.ts +305 -0
  487. package/test/util/effect-flock.test.ts +392 -0
  488. package/test/util/flock.test.ts +428 -0
  489. package/test/util/which.test.ts +100 -0
  490. package/tsconfig.json +7 -0
package/src/shell.ts ADDED
@@ -0,0 +1,226 @@
1
+ export * as Shell from "./shell"
2
+
3
+ import path from "path"
4
+ import { spawn, type ChildProcess } from "child_process"
5
+ import { readFile } from "fs/promises"
6
+ import { statSync } from "fs"
7
+ import { setTimeout as sleep } from "node:timers/promises"
8
+ import { Flag } from "./flag/flag"
9
+ import { FSUtil } from "./fs-util"
10
+ import { which } from "./util/which"
11
+
12
+ const SIGKILL_TIMEOUT_MS = 200
13
+ const META: Record<string, { deny?: boolean; login?: boolean; posix?: boolean; ps?: boolean }> = {
14
+ bash: { login: true, posix: true },
15
+ dash: { login: true, posix: true },
16
+ fish: { deny: true, login: true },
17
+ ksh: { login: true, posix: true },
18
+ nu: { deny: true },
19
+ powershell: { ps: true },
20
+ pwsh: { ps: true },
21
+ sh: { login: true, posix: true },
22
+ zsh: { login: true, posix: true },
23
+ }
24
+
25
+ export type Item = {
26
+ path: string
27
+ name: string
28
+ acceptable: boolean
29
+ }
30
+
31
+ export async function killTree(proc: ChildProcess, opts?: { exited?: () => boolean }): Promise<void> {
32
+ const pid = proc.pid
33
+ if (!pid || opts?.exited?.()) return
34
+
35
+ if (process.platform === "win32") {
36
+ await new Promise<void>((resolve) => {
37
+ const killer = spawn("taskkill", ["/pid", String(pid), "/f", "/t"], {
38
+ stdio: "ignore",
39
+ windowsHide: true,
40
+ })
41
+ killer.once("exit", () => resolve())
42
+ killer.once("error", () => resolve())
43
+ })
44
+ return
45
+ }
46
+
47
+ try {
48
+ process.kill(-pid, "SIGTERM")
49
+ await sleep(SIGKILL_TIMEOUT_MS)
50
+ if (!opts?.exited?.()) {
51
+ process.kill(-pid, "SIGKILL")
52
+ }
53
+ } catch {
54
+ proc.kill("SIGTERM")
55
+ await sleep(SIGKILL_TIMEOUT_MS)
56
+ if (!opts?.exited?.()) {
57
+ proc.kill("SIGKILL")
58
+ }
59
+ }
60
+ }
61
+
62
+ function stat(file: string) {
63
+ return statSync(file, { throwIfNoEntry: false }) ?? undefined
64
+ }
65
+
66
+ function full(file: string) {
67
+ if (process.platform !== "win32") return file
68
+ const shell = FSUtil.windowsPath(file)
69
+ if (path.win32.dirname(shell) !== ".") {
70
+ if (shell.startsWith("/") && name(shell) === "bash") return gitbash() || shell
71
+ return shell
72
+ }
73
+ if (name(shell) === "bash") return gitbash() || which(shell) || shell
74
+ return which(shell) || shell
75
+ }
76
+
77
+ function meta(file: string) {
78
+ return META[name(file)]
79
+ }
80
+
81
+ function ok(file: string) {
82
+ return meta(file)?.deny !== true
83
+ }
84
+
85
+ function rooted(file: string) {
86
+ return path.isAbsolute(FSUtil.windowsPath(file))
87
+ }
88
+
89
+ function resolve(file: string) {
90
+ const shell = full(file)
91
+ if (rooted(shell)) {
92
+ if (stat(shell)?.isFile()) return shell
93
+ return
94
+ }
95
+ return which(shell) ?? undefined
96
+ }
97
+
98
+ function win() {
99
+ return Array.from(
100
+ new Set(
101
+ [which("pwsh"), which("powershell"), gitbash(), process.env.COMSPEC || "cmd.exe"]
102
+ .filter((item): item is string => Boolean(item))
103
+ .map(full),
104
+ ),
105
+ )
106
+ }
107
+
108
+ async function unix() {
109
+ const text = await readFile("/etc/shells", "utf8").catch(() => "")
110
+ if (text) return Array.from(new Set(text.split("\n").filter((line) => line.trim() && !line.startsWith("#"))))
111
+ return ["/bin/bash", "/bin/zsh", "/bin/sh"]
112
+ }
113
+
114
+ function select(file: string | undefined, opts?: { acceptable?: boolean }) {
115
+ if (file && (!opts?.acceptable || ok(file))) {
116
+ const shell = resolve(file)
117
+ if (shell) return shell
118
+ }
119
+ if (process.platform === "win32") return win()[0]
120
+ return fallback()
121
+ }
122
+
123
+ export function gitbash() {
124
+ if (process.platform !== "win32") return
125
+ if (Flag.NEUROCODE_GIT_BASH_PATH) return Flag.NEUROCODE_GIT_BASH_PATH
126
+ const git = which("git")
127
+ if (!git) return
128
+ const file = path.join(git, "..", "..", "bin", "bash.exe")
129
+ if (stat(file)?.size) return file
130
+ }
131
+
132
+ function fallback() {
133
+ if (process.platform === "darwin") return "/bin/zsh"
134
+ const bash = which("bash")
135
+ if (bash) return bash
136
+ return "/bin/sh"
137
+ }
138
+
139
+ export function name(file: string) {
140
+ if (process.platform === "win32") return path.win32.parse(FSUtil.windowsPath(file)).name.toLowerCase()
141
+ return path.basename(file).toLowerCase()
142
+ }
143
+
144
+ export function login(file: string) {
145
+ return meta(file)?.login === true
146
+ }
147
+
148
+ export function posix(file: string) {
149
+ return meta(file)?.posix === true
150
+ }
151
+
152
+ export function ps(file: string) {
153
+ return meta(file)?.ps === true
154
+ }
155
+
156
+ function info(file: string): Item {
157
+ const item = full(file)
158
+ const n = name(item)
159
+ return {
160
+ path: item,
161
+ name: resolve(n) ? n : item,
162
+ acceptable: ok(item),
163
+ }
164
+ }
165
+
166
+ export function args(file: string, command: string, cwd: string) {
167
+ const n = name(file)
168
+ if (n === "nu" || n === "fish") return ["-c", command]
169
+ if (n === "zsh") {
170
+ return [
171
+ "-l",
172
+ "-c",
173
+ `
174
+ [[ -f ~/.zshenv ]] && source ~/.zshenv >/dev/null 2>&1 || true
175
+ [[ -f "\${ZDOTDIR:-$HOME}/.zshrc" ]] && source "\${ZDOTDIR:-$HOME}/.zshrc" >/dev/null 2>&1 || true
176
+ cd -- "$1"
177
+ eval ${JSON.stringify(command)}
178
+ `,
179
+ "neurocode",
180
+ cwd,
181
+ ]
182
+ }
183
+ if (n === "bash") {
184
+ return [
185
+ "-l",
186
+ "-c",
187
+ `
188
+ shopt -s expand_aliases
189
+ [[ -f ~/.bashrc ]] && source ~/.bashrc >/dev/null 2>&1 || true
190
+ cd -- "$1"
191
+ eval ${JSON.stringify(command)}
192
+ `,
193
+ "neurocode",
194
+ cwd,
195
+ ]
196
+ }
197
+ if (n === "cmd") return ["/c", command]
198
+ if (ps(file)) return ["-NoProfile", "-Command", command]
199
+ return ["-c", command]
200
+ }
201
+
202
+ let defaultPreferred: string | undefined
203
+ let defaultAcceptable: string | undefined
204
+
205
+ export function preferred(configShell?: string) {
206
+ if (configShell) return select(configShell)
207
+ defaultPreferred ??= select(process.env.SHELL)
208
+ return defaultPreferred
209
+ }
210
+ preferred.reset = () => {
211
+ defaultPreferred = undefined
212
+ }
213
+
214
+ export function acceptable(configShell?: string) {
215
+ if (configShell) return select(configShell, { acceptable: true })
216
+ defaultAcceptable ??= select(process.env.SHELL, { acceptable: true })
217
+ return defaultAcceptable
218
+ }
219
+ acceptable.reset = () => {
220
+ defaultAcceptable = undefined
221
+ }
222
+
223
+ export async function list(): Promise<Item[]> {
224
+ const shells = process.platform === "win32" ? win() : await unix()
225
+ return shells.filter((s) => resolve(s)).map(info)
226
+ }
@@ -0,0 +1,213 @@
1
+ export * as SkillDiscovery from "./discovery"
2
+
3
+ import path from "path"
4
+ import { Context, Effect, Layer, Schedule, Schema } from "effect"
5
+ import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
6
+ import { FSUtil } from "../fs-util"
7
+ import { Global } from "../global"
8
+ import { makeGlobalNode } from "../effect/app-node"
9
+ import { httpClient } from "../effect/app-node-platform"
10
+ import { AbsolutePath } from "../schema"
11
+
12
+ const skillConcurrency = 4
13
+ const fileConcurrency = 8
14
+
15
+ function isSafeSegment(value: string) {
16
+ return (
17
+ value.length > 0 &&
18
+ value !== "." &&
19
+ value !== ".." &&
20
+ !value.includes("/") &&
21
+ !value.includes("\\") &&
22
+ !value.includes("\0")
23
+ )
24
+ }
25
+
26
+ function isSafeRelativePath(value: string) {
27
+ const segments = value.split("/")
28
+ return (
29
+ value.length > 0 &&
30
+ !value.includes("\\") &&
31
+ !value.includes("\0") &&
32
+ !value.includes("?") &&
33
+ !value.includes("#") &&
34
+ !URL.canParse(value) &&
35
+ !path.posix.isAbsolute(value) &&
36
+ !path.win32.isAbsolute(value) &&
37
+ segments.every((segment) => {
38
+ try {
39
+ const decoded = decodeURIComponent(segment)
40
+ return (
41
+ decoded.length > 0 &&
42
+ decoded !== "." &&
43
+ decoded !== ".." &&
44
+ !decoded.includes("/") &&
45
+ !decoded.includes("\\") &&
46
+ !decoded.includes("\0")
47
+ )
48
+ } catch {
49
+ return false
50
+ }
51
+ })
52
+ )
53
+ }
54
+
55
+ class IndexSkill extends Schema.Class<IndexSkill>("SkillDiscovery.IndexSkill")({
56
+ name: Schema.String,
57
+ version: Schema.optional(Schema.String),
58
+ files: Schema.Array(Schema.String),
59
+ }) {}
60
+
61
+ class Index extends Schema.Class<Index>("SkillDiscovery.Index")({
62
+ skills: Schema.Array(IndexSkill),
63
+ }) {}
64
+
65
+ export interface Interface {
66
+ readonly pull: (url: string) => Effect.Effect<AbsolutePath[]>
67
+ }
68
+
69
+ export class Service extends Context.Service<Service, Interface>()("@neurocode/v2/SkillDiscovery") {}
70
+
71
+ const layer = Layer.effect(
72
+ Service,
73
+ Effect.gen(function* () {
74
+ const fs = yield* FSUtil.Service
75
+ const global = yield* Global.Service
76
+ const http = (yield* HttpClient.HttpClient).pipe(
77
+ HttpClient.retryTransient({
78
+ retryOn: "errors-and-responses",
79
+ times: 2,
80
+ schedule: Schedule.exponential(200).pipe(Schedule.jittered),
81
+ }),
82
+ HttpClient.filterStatusOk,
83
+ )
84
+
85
+ const download = Effect.fn("SkillDiscovery.download")(function* (url: string, destination: string) {
86
+ if (yield* fs.exists(destination).pipe(Effect.orDie)) return true
87
+ return yield* HttpClientRequest.get(url).pipe(
88
+ http.execute,
89
+ Effect.flatMap((response) => response.arrayBuffer),
90
+ Effect.flatMap((body) => fs.writeWithDirs(destination, new Uint8Array(body))),
91
+ Effect.as(true),
92
+ Effect.catch((error) =>
93
+ Effect.logError("failed to download skill file", { url, error }).pipe(Effect.as(false)),
94
+ ),
95
+ )
96
+ })
97
+
98
+ return Service.of({
99
+ pull: Effect.fn("SkillDiscovery.pull")(function* (url) {
100
+ const base = url.endsWith("/") ? url : `${url}/`
101
+ const source = new URL(base)
102
+ const index = new URL("index.json", source).href
103
+ const data = yield* HttpClientRequest.get(index).pipe(
104
+ HttpClientRequest.acceptJson,
105
+ http.execute,
106
+ Effect.flatMap(HttpClientResponse.schemaBodyJson(Index)),
107
+ Effect.catch((error) =>
108
+ Effect.logError("failed to fetch skill index", { url: index, error }).pipe(Effect.as(undefined)),
109
+ ),
110
+ )
111
+ if (!data) return []
112
+
113
+ const sourceRoot = path.resolve(global.cache, "skills", Bun.hash(base).toString(16))
114
+ return yield* Effect.forEach(
115
+ data.skills.flatMap((skill) => {
116
+ if (!isSafeSegment(skill.name)) {
117
+ return []
118
+ }
119
+ if (!skill.files.includes("SKILL.md") && !skill.files.includes(`${skill.name}.md`)) {
120
+ return []
121
+ }
122
+
123
+ const root = path.resolve(sourceRoot, skill.name)
124
+ if (!FSUtil.contains(sourceRoot, root) || root === sourceRoot) {
125
+ return []
126
+ }
127
+
128
+ const skillUrl = new URL(`${encodeURIComponent(skill.name)}/`, source)
129
+ const versionFile = path.join(root, ".neurocode-version")
130
+ const files = skill.files.map((file) => {
131
+ if (!isSafeRelativePath(file)) return undefined
132
+ let resource: URL
133
+ try {
134
+ resource = new URL(file, skillUrl)
135
+ } catch {
136
+ return undefined
137
+ }
138
+ if (resource.origin !== source.origin) return undefined
139
+
140
+ const destination = path.resolve(root, file)
141
+ if (!FSUtil.contains(root, destination) || destination === root) return undefined
142
+ return {
143
+ url: resource.href,
144
+ destination,
145
+ file,
146
+ }
147
+ })
148
+ if (files.some((file) => file === undefined)) {
149
+ return []
150
+ }
151
+ return [{ skill, root, versionFile, files: files as { url: string; destination: string; file: string }[] }]
152
+ }),
153
+ ({ skill, root, versionFile, files }) =>
154
+ Effect.gen(function* () {
155
+ const version = skill.version
156
+ const current =
157
+ version === undefined
158
+ ? undefined
159
+ : yield* fs.readFileStringSafe(versionFile).pipe(Effect.catch(() => Effect.succeed(undefined)))
160
+ if (version === undefined || current === version) {
161
+ yield* Effect.forEach(files, (file) => download(file.url, file.destination), {
162
+ concurrency: fileConcurrency,
163
+ discard: true,
164
+ })
165
+ } else {
166
+ const token = crypto.randomUUID()
167
+ const staging = `${root}.tmp-${token}`
168
+ const backup = `${root}.old-${token}`
169
+ yield* Effect.gen(function* () {
170
+ const downloaded = yield* Effect.forEach(
171
+ files,
172
+ (file) => download(file.url, path.resolve(staging, file.file)),
173
+ { concurrency: fileConcurrency },
174
+ )
175
+ if (!downloaded.every(Boolean)) return
176
+ const exists =
177
+ (yield* fs.exists(path.join(staging, "SKILL.md")).pipe(Effect.orDie)) ||
178
+ (yield* fs.exists(path.join(staging, `${skill.name}.md`)).pipe(Effect.orDie))
179
+ if (!exists) return
180
+ yield* fs.writeFileString(path.join(staging, ".neurocode-version"), version)
181
+ yield* Effect.uninterruptible(
182
+ Effect.gen(function* () {
183
+ const cached = yield* fs.exists(root).pipe(Effect.orDie)
184
+ if (cached) yield* fs.rename(root, backup)
185
+ yield* fs.rename(staging, root).pipe(
186
+ Effect.catch((error) =>
187
+ Effect.gen(function* () {
188
+ if (cached) yield* fs.rename(backup, root).pipe(Effect.ignore)
189
+ return yield* Effect.fail(error)
190
+ }),
191
+ ),
192
+ )
193
+ if (cached) yield* fs.remove(backup, { recursive: true, force: true }).pipe(Effect.ignore)
194
+ }),
195
+ )
196
+ }).pipe(
197
+ Effect.catch((error) => Effect.logError("failed to refresh skill", { skill: skill.name, error })),
198
+ Effect.ensuring(fs.remove(staging, { recursive: true, force: true }).pipe(Effect.ignore)),
199
+ )
200
+ }
201
+ const exists =
202
+ (yield* fs.exists(path.join(root, "SKILL.md")).pipe(Effect.orDie)) ||
203
+ (yield* fs.exists(path.join(root, `${skill.name}.md`)).pipe(Effect.orDie))
204
+ return exists ? [AbsolutePath.make(root)] : []
205
+ }),
206
+ { concurrency: skillConcurrency },
207
+ ).pipe(Effect.map((directories) => directories.flat()))
208
+ }),
209
+ })
210
+ }),
211
+ )
212
+
213
+ export const node = makeGlobalNode({ service: Service, layer, deps: [httpClient, FSUtil.node, Global.node] })
@@ -0,0 +1,76 @@
1
+ export * as SkillGuidance from "./guidance"
2
+
3
+ import { makeLocationNode } from "../effect/app-node"
4
+ import { Context, Effect, Layer, Schema } from "effect"
5
+ import { AgentV2 } from "../agent"
6
+ import { PermissionV2 } from "../permission"
7
+ import { SkillV2 } from "../skill"
8
+ import { SystemContext } from "../system-context/index"
9
+
10
+ const Summary = Schema.Struct({
11
+ name: Schema.String,
12
+ description: Schema.String,
13
+ })
14
+ type Summary = typeof Summary.Type
15
+
16
+ const render = (skills: ReadonlyArray<Summary>) =>
17
+ [
18
+ "Skills provide specialized instructions and workflows for specific tasks.",
19
+ "Use the skill tool to load a skill when a task matches its description.",
20
+ ...(skills.length === 0
21
+ ? ["No skills are currently available."]
22
+ : [
23
+ "<available_skills>",
24
+ ...skills.flatMap((skill) => [
25
+ " <skill>",
26
+ ` <name>${skill.name}</name>`,
27
+ ` <description>${skill.description}</description>`,
28
+ " </skill>",
29
+ ]),
30
+ "</available_skills>",
31
+ ]),
32
+ ].join("\n")
33
+
34
+ export interface Interface {
35
+ readonly load: (agent: AgentV2.Selection) => Effect.Effect<SystemContext.SystemContext>
36
+ }
37
+
38
+ export class Service extends Context.Service<Service, Interface>()("@neurocode/v2/SkillGuidance") {}
39
+
40
+ const layer = Layer.effect(
41
+ Service,
42
+ Effect.gen(function* () {
43
+ const skills = yield* SkillV2.Service
44
+
45
+ return Service.of({
46
+ load: Effect.fn("SkillGuidance.load")(function* (selection) {
47
+ const agent = selection.info
48
+ if (!agent) return SystemContext.empty
49
+ const permitted = SkillV2.available(yield* skills.list(), agent)
50
+ if (permitted.length === 0 && PermissionV2.evaluate("skill", "*", agent.permissions).effect === "deny")
51
+ return SystemContext.empty
52
+ const available = permitted
53
+ .flatMap((skill) =>
54
+ skill.description === undefined ? [] : [{ name: skill.name, description: skill.description }],
55
+ )
56
+ .toSorted((a, b) => a.name.localeCompare(b.name))
57
+ return SystemContext.make({
58
+ key: SystemContext.Key.make("core/skill-guidance"),
59
+ codec: Schema.toCodecJson(Schema.Array(Summary)),
60
+ load: Effect.succeed(available),
61
+ baseline: render,
62
+ update: (_previous, current) =>
63
+ [
64
+ "The available skills have changed. This list supersedes the previous available skills list.",
65
+ render(current),
66
+ ].join("\n"),
67
+ removed: () => "Skill guidance is no longer available. Do not use any previously listed skill.",
68
+ })
69
+ }),
70
+ })
71
+ }),
72
+ )
73
+
74
+ export const locationLayer = layer
75
+
76
+ export const node = makeLocationNode({ service: Service, layer, deps: [SkillV2.node] })
package/src/skill.ts ADDED
@@ -0,0 +1,132 @@
1
+ export * as SkillV2 from "./skill"
2
+
3
+ import { makeLocationNode } from "./effect/app-node"
4
+ import path from "path"
5
+ import { Context, Effect, Layer, Schema, Types } from "effect"
6
+ import { Skill } from "@neurocode-ai/schema/skill"
7
+ import { AgentV2 } from "./agent"
8
+ import { ConfigMarkdown } from "./config/markdown"
9
+ import { FSUtil } from "./fs-util"
10
+ import { PermissionV2 } from "./permission"
11
+ import { AbsolutePath } from "./schema"
12
+ import { SkillDiscovery } from "./skill/discovery"
13
+ import { State } from "./state"
14
+
15
+ export const DirectorySource = Skill.DirectorySource
16
+ export type DirectorySource = Skill.DirectorySource
17
+
18
+ export const UrlSource = Skill.UrlSource
19
+ export type UrlSource = Skill.UrlSource
20
+
21
+ export const EmbeddedSource = Skill.EmbeddedSource
22
+ export type EmbeddedSource = Skill.EmbeddedSource
23
+
24
+ export const Source = Skill.Source
25
+ export type Source = typeof Source.Type
26
+
27
+ export const Info = Skill.Info
28
+ export type Info = Skill.Info
29
+
30
+ export const available = (skills: ReadonlyArray<Info>, agent: AgentV2.Info) =>
31
+ skills.filter((skill) => PermissionV2.evaluate("skill", skill.name, agent.permissions).effect !== "deny")
32
+
33
+ const Frontmatter = Schema.Struct({
34
+ name: Schema.String.pipe(Schema.optional),
35
+ description: Schema.String.pipe(Schema.optional),
36
+ slash: Schema.Boolean.pipe(Schema.optional),
37
+ })
38
+ const decodeFrontmatter = Schema.decodeUnknownOption(Frontmatter)
39
+
40
+ export type Data = {
41
+ sources: Types.DeepMutable<Source>[]
42
+ }
43
+
44
+ export type Draft = {
45
+ source: (source: Source) => void
46
+ list: () => readonly Source[]
47
+ }
48
+
49
+ export interface Interface extends State.Transformable<Draft> {
50
+ readonly sources: () => Effect.Effect<Source[]>
51
+ readonly list: () => Effect.Effect<Info[]>
52
+ }
53
+
54
+ export class Service extends Context.Service<Service, Interface>()("@neurocode/v2/Skill") {}
55
+
56
+ const layer = Layer.effect(
57
+ Service,
58
+ Effect.gen(function* () {
59
+ const discovery = yield* SkillDiscovery.Service
60
+ const fs = yield* FSUtil.Service
61
+
62
+ const state = State.create<Data, Draft>({
63
+ initial: () => ({ sources: [] }),
64
+ draft: (draft) => ({
65
+ source: (source) => {
66
+ if (draft.sources.some((item) => Source.equals(item, source))) return
67
+ draft.sources.push(source as Types.DeepMutable<Source>)
68
+ },
69
+ list: () => draft.sources as Source[],
70
+ }),
71
+ })
72
+
73
+ const load = Effect.fn("SkillV2.load")(function* (source: Source) {
74
+ const skills: Info[] = []
75
+ if (source.type === "embedded") return [source.skill]
76
+ const directories = source.type === "directory" ? [source.path] : yield* discovery.pull(source.url)
77
+ for (const directory of directories) {
78
+ const files = yield* fs
79
+ .glob("{*.md,**/SKILL.md}", { cwd: directory, absolute: true, include: "file", symlink: true, dot: true })
80
+ .pipe(Effect.catch(() => Effect.succeed([] as string[])))
81
+ for (const filepath of files.toSorted()) {
82
+ const content = yield* fs.readFileStringSafe(filepath).pipe(Effect.catch(() => Effect.succeed(undefined)))
83
+ if (!content) continue
84
+ const markdown = ConfigMarkdown.parseOption(content)
85
+ if (!markdown) continue
86
+ const frontmatter = decodeFrontmatter(markdown.data).valueOrUndefined
87
+ if (!frontmatter) continue
88
+ const name =
89
+ frontmatter.name !== undefined
90
+ ? frontmatter.name
91
+ : path.dirname(filepath) === directory
92
+ ? path.basename(filepath, ".md")
93
+ : undefined
94
+ if (!name) continue
95
+ skills.push({
96
+ name,
97
+ description: frontmatter.description,
98
+ slash: frontmatter.slash,
99
+ location: AbsolutePath.make(filepath),
100
+ content: markdown.content,
101
+ })
102
+ }
103
+ }
104
+ return skills
105
+ })
106
+
107
+ // QUESTION(Dax): Should local skill sources invalidate on filesystem watch
108
+ // events, following the reload policy chosen for other context sources?
109
+ const cache = new Map<string, Info[]>()
110
+ const list = Effect.fn("SkillV2.list")(function* () {
111
+ const skills = new Map<string, Info>()
112
+ for (const source of state.get().sources) {
113
+ const key = Source.key(source)
114
+ const loaded = cache.get(key) ?? (yield* load(source))
115
+ cache.set(key, loaded)
116
+ for (const skill of loaded) skills.set(skill.name, skill)
117
+ }
118
+ return Array.from(skills.values())
119
+ })
120
+
121
+ return Service.of({
122
+ transform: state.transform,
123
+ reload: state.reload,
124
+ sources: Effect.fn("SkillV2.sources")(function* () {
125
+ return state.get().sources
126
+ }),
127
+ list,
128
+ })
129
+ }),
130
+ )
131
+
132
+ export const node = makeLocationNode({ service: Service, layer, deps: [SkillDiscovery.node, FSUtil.node] })