@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
@@ -0,0 +1,183 @@
1
+ import { Database } from "bun:sqlite"
2
+ import { drizzle } from "drizzle-orm/bun-sqlite"
3
+ import * as Context from "effect/Context"
4
+ import * as Effect from "effect/Effect"
5
+ import * as Fiber from "effect/Fiber"
6
+ import { identity } from "effect/Function"
7
+ import * as Layer from "effect/Layer"
8
+ import * as Scope from "effect/Scope"
9
+ import * as Semaphore from "effect/Semaphore"
10
+ import * as Stream from "effect/Stream"
11
+ import * as Reactivity from "effect/unstable/reactivity/Reactivity"
12
+ import * as Client from "effect/unstable/sql/SqlClient"
13
+ import type { Connection } from "effect/unstable/sql/SqlConnection"
14
+ import { classifySqliteError, SqlError } from "effect/unstable/sql/SqlError"
15
+ import * as Statement from "effect/unstable/sql/Statement"
16
+ import { Sqlite } from "./sqlite"
17
+
18
+ const ATTR_DB_SYSTEM_NAME = "db.system.name"
19
+
20
+ const TypeId = "~@neurocode-ai/core/database/SqliteBun" as const
21
+ type TypeId = typeof TypeId
22
+
23
+ interface SqliteClient extends Client.SqlClient {
24
+ readonly [TypeId]: TypeId
25
+ readonly config: Config
26
+ readonly export: Effect.Effect<Uint8Array, SqlError>
27
+ readonly loadExtension: (path: string) => Effect.Effect<void, SqlError>
28
+ readonly updateValues: never
29
+ }
30
+
31
+ interface Config {
32
+ readonly filename: string
33
+ readonly readonly?: boolean
34
+ readonly create?: boolean
35
+ readonly readwrite?: boolean
36
+ readonly disableWAL?: boolean
37
+ readonly spanAttributes?: Record<string, unknown>
38
+ readonly transformResultNames?: (str: string) => string
39
+ readonly transformQueryNames?: (str: string) => string
40
+ }
41
+
42
+ interface SqliteConnection extends Connection {
43
+ readonly export: Effect.Effect<Uint8Array, SqlError>
44
+ readonly loadExtension: (path: string) => Effect.Effect<void, SqlError>
45
+ }
46
+
47
+ const make = (options: Config) =>
48
+ Effect.gen(function* () {
49
+ const native = (yield* Sqlite.Native) as Database
50
+
51
+ const compiler = Statement.makeCompilerSqlite(options.transformQueryNames)
52
+ const transformRows = options.transformResultNames
53
+ ? Statement.defaultTransforms(options.transformResultNames).array
54
+ : undefined
55
+
56
+ const run = (query: string, params: ReadonlyArray<unknown> = []) =>
57
+ Effect.withFiber<Array<Record<string, unknown>>, SqlError>((fiber) => {
58
+ const statement = native.query(query)
59
+ // @ts-ignore bun-types missing safeIntegers method, fixed in https://github.com/oven-sh/bun/pull/26627
60
+ statement.safeIntegers(Context.get(fiber.context, Client.SafeIntegers))
61
+ try {
62
+ return Effect.succeed((statement.all(...(params as any)) ?? []) as Array<Record<string, unknown>>)
63
+ } catch (cause) {
64
+ return Effect.fail(
65
+ new SqlError({
66
+ reason: classifySqliteError(cause, { message: "Failed to execute statement", operation: "execute" }),
67
+ }),
68
+ )
69
+ }
70
+ })
71
+
72
+ const runValues = (query: string, params: ReadonlyArray<unknown> = []) =>
73
+ Effect.withFiber<Array<unknown[]>, SqlError>((fiber) => {
74
+ const statement = native.query(query)
75
+ // @ts-ignore bun-types missing safeIntegers method, fixed in https://github.com/oven-sh/bun/pull/26627
76
+ statement.safeIntegers(Context.get(fiber.context, Client.SafeIntegers))
77
+ try {
78
+ return Effect.succeed((statement.values(...(params as any)) ?? []) as Array<unknown[]>)
79
+ } catch (cause) {
80
+ return Effect.fail(
81
+ new SqlError({
82
+ reason: classifySqliteError(cause, { message: "Failed to execute statement", operation: "execute" }),
83
+ }),
84
+ )
85
+ }
86
+ })
87
+
88
+ const connection = identity<SqliteConnection>({
89
+ execute(query, params, transformRows) {
90
+ return transformRows ? Effect.map(run(query, params), transformRows) : run(query, params)
91
+ },
92
+ executeRaw(query, params) {
93
+ return run(query, params)
94
+ },
95
+ executeValues(query, params) {
96
+ return runValues(query, params)
97
+ },
98
+ executeUnprepared(query, params, transformRows) {
99
+ return this.execute(query, params, transformRows)
100
+ },
101
+ executeStream() {
102
+ return Stream.die("executeStream not implemented")
103
+ },
104
+ export: Effect.try({
105
+ try: () => native.serialize(),
106
+ catch: (cause) =>
107
+ new SqlError({
108
+ reason: classifySqliteError(cause, { message: "Failed to export database", operation: "export" }),
109
+ }),
110
+ }),
111
+ loadExtension: (path) =>
112
+ Effect.try({
113
+ try: () => native.loadExtension(path),
114
+ catch: (cause) =>
115
+ new SqlError({
116
+ reason: classifySqliteError(cause, { message: "Failed to load extension", operation: "loadExtension" }),
117
+ }),
118
+ }),
119
+ })
120
+
121
+ const semaphore = yield* Semaphore.make(1)
122
+ const acquirer = semaphore.withPermits(1)(Effect.succeed(connection))
123
+ const transactionAcquirer = Effect.uninterruptibleMask((restore) => {
124
+ const fiber = Fiber.getCurrent()!
125
+ const scope = Context.getUnsafe(fiber.context, Scope.Scope)
126
+ return Effect.as(
127
+ Effect.tap(restore(semaphore.take(1)), () => Scope.addFinalizer(scope, semaphore.release(1))),
128
+ connection,
129
+ )
130
+ })
131
+
132
+ const client = Object.assign(
133
+ (yield* Client.make({
134
+ acquirer,
135
+ compiler,
136
+ transactionAcquirer,
137
+ spanAttributes: [
138
+ ...(options.spanAttributes ? Object.entries(options.spanAttributes) : []),
139
+ [ATTR_DB_SYSTEM_NAME, "sqlite"],
140
+ ],
141
+ transformRows,
142
+ })) as SqliteClient,
143
+ {
144
+ [TypeId]: TypeId,
145
+ config: options,
146
+ export: Effect.flatMap(acquirer, (_) => _.export),
147
+ loadExtension: (path: string) => Effect.flatMap(acquirer, (_) => _.loadExtension(path)),
148
+ },
149
+ )
150
+
151
+ return client
152
+ })
153
+
154
+ const nativeLayer = (config: Config) =>
155
+ Layer.effect(
156
+ Sqlite.Native,
157
+ Effect.gen(function* () {
158
+ const native = new Database(config.filename, {
159
+ readonly: config.readonly,
160
+ readwrite: config.readwrite ?? true,
161
+ create: config.create ?? true,
162
+ })
163
+ yield* Effect.addFinalizer(() => Effect.sync(() => native.close()))
164
+ if (config.disableWAL !== true) native.run("PRAGMA journal_mode = WAL;")
165
+ return native
166
+ }),
167
+ )
168
+
169
+ const sqliteLayer = (config: Config) => Layer.effect(Client.SqlClient, make(config))
170
+
171
+ const drizzleLayer = Layer.effect(
172
+ Sqlite.Drizzle,
173
+ Effect.gen(function* () {
174
+ return drizzle({ client: (yield* Sqlite.Native) as Database })
175
+ }),
176
+ )
177
+
178
+ export const layer = (config: Config) => {
179
+ const native = nativeLayer(config)
180
+ return Layer.merge(native, Layer.merge(sqliteLayer(config), drizzleLayer).pipe(Layer.provide(native))).pipe(
181
+ Layer.provide(Reactivity.layer),
182
+ )
183
+ }
@@ -0,0 +1,178 @@
1
+ import { DatabaseSync, type SQLInputValue } from "node:sqlite"
2
+ import { drizzle } from "drizzle-orm/node-sqlite"
3
+ import * as Context from "effect/Context"
4
+ import * as Effect from "effect/Effect"
5
+ import * as Fiber from "effect/Fiber"
6
+ import { identity } from "effect/Function"
7
+ import * as Layer from "effect/Layer"
8
+ import * as Scope from "effect/Scope"
9
+ import * as Semaphore from "effect/Semaphore"
10
+ import * as Stream from "effect/Stream"
11
+ import * as Reactivity from "effect/unstable/reactivity/Reactivity"
12
+ import * as Client from "effect/unstable/sql/SqlClient"
13
+ import type { Connection } from "effect/unstable/sql/SqlConnection"
14
+ import { classifySqliteError, SqlError } from "effect/unstable/sql/SqlError"
15
+ import * as Statement from "effect/unstable/sql/Statement"
16
+ import { Sqlite } from "./sqlite"
17
+
18
+ const ATTR_DB_SYSTEM_NAME = "db.system.name"
19
+
20
+ const TypeId = "~@neurocode-ai/core/database/SqliteNode" as const
21
+ type TypeId = typeof TypeId
22
+
23
+ interface SqliteClient extends Client.SqlClient {
24
+ readonly [TypeId]: TypeId
25
+ readonly config: Config
26
+ readonly loadExtension: (path: string) => Effect.Effect<void, SqlError>
27
+ readonly updateValues: never
28
+ }
29
+
30
+ interface Config {
31
+ readonly filename: string
32
+ readonly readonly?: boolean
33
+ readonly create?: boolean
34
+ readonly readwrite?: boolean
35
+ readonly disableWAL?: boolean
36
+ readonly timeout?: number
37
+ readonly allowExtension?: boolean
38
+ readonly spanAttributes?: Record<string, unknown>
39
+ readonly transformResultNames?: (str: string) => string
40
+ readonly transformQueryNames?: (str: string) => string
41
+ }
42
+
43
+ interface SqliteConnection extends Connection {
44
+ readonly loadExtension: (path: string) => Effect.Effect<void, SqlError>
45
+ }
46
+
47
+ const make = (options: Config) =>
48
+ Effect.gen(function* () {
49
+ const native = (yield* Sqlite.Native) as DatabaseSync
50
+
51
+ const compiler = Statement.makeCompilerSqlite(options.transformQueryNames)
52
+ const transformRows = options.transformResultNames
53
+ ? Statement.defaultTransforms(options.transformResultNames).array
54
+ : undefined
55
+
56
+ const run = (query: string, params: ReadonlyArray<unknown> = []) =>
57
+ Effect.withFiber<Array<Record<string, unknown>>, SqlError>((fiber) => {
58
+ const statement = native.prepare(query)
59
+ statement.setReadBigInts(Context.get(fiber.context, Client.SafeIntegers))
60
+ try {
61
+ return Effect.succeed(statement.all(...(params as SQLInputValue[])) as Array<Record<string, unknown>>)
62
+ } catch (cause) {
63
+ return Effect.fail(
64
+ new SqlError({
65
+ reason: classifySqliteError(cause, { message: "Failed to execute statement", operation: "execute" }),
66
+ }),
67
+ )
68
+ }
69
+ })
70
+
71
+ const runValues = (query: string, params: ReadonlyArray<unknown> = []) =>
72
+ Effect.withFiber<ReadonlyArray<ReadonlyArray<unknown>>, SqlError>((fiber) => {
73
+ const statement = native.prepare(query)
74
+ statement.setReadBigInts(Context.get(fiber.context, Client.SafeIntegers))
75
+ statement.setReturnArrays(true)
76
+ try {
77
+ return Effect.succeed(
78
+ statement.all(...(params as SQLInputValue[])) as unknown as ReadonlyArray<ReadonlyArray<unknown>>,
79
+ )
80
+ } catch (cause) {
81
+ return Effect.fail(
82
+ new SqlError({
83
+ reason: classifySqliteError(cause, { message: "Failed to execute statement", operation: "execute" }),
84
+ }),
85
+ )
86
+ }
87
+ })
88
+
89
+ const connection = identity<SqliteConnection>({
90
+ execute(query, params, transformRows) {
91
+ return transformRows ? Effect.map(run(query, params), transformRows) : run(query, params)
92
+ },
93
+ executeRaw(query, params) {
94
+ return run(query, params)
95
+ },
96
+ executeValues(query, params) {
97
+ return runValues(query, params)
98
+ },
99
+ executeUnprepared(query, params, transformRows) {
100
+ return this.execute(query, params, transformRows)
101
+ },
102
+ executeStream() {
103
+ return Stream.die("executeStream not implemented")
104
+ },
105
+ loadExtension: (path) =>
106
+ Effect.try({
107
+ try: () => native.loadExtension(path),
108
+ catch: (cause) =>
109
+ new SqlError({
110
+ reason: classifySqliteError(cause, { message: "Failed to load extension", operation: "loadExtension" }),
111
+ }),
112
+ }),
113
+ })
114
+
115
+ const semaphore = yield* Semaphore.make(1)
116
+ const acquirer = semaphore.withPermits(1)(Effect.succeed(connection))
117
+ const transactionAcquirer = Effect.uninterruptibleMask((restore) => {
118
+ const fiber = Fiber.getCurrent()!
119
+ const scope = Context.getUnsafe(fiber.context, Scope.Scope)
120
+ return Effect.as(
121
+ Effect.tap(restore(semaphore.take(1)), () => Scope.addFinalizer(scope, semaphore.release(1))),
122
+ connection,
123
+ )
124
+ })
125
+
126
+ const client = Object.assign(
127
+ (yield* Client.make({
128
+ acquirer,
129
+ compiler,
130
+ transactionAcquirer,
131
+ spanAttributes: [
132
+ ...(options.spanAttributes ? Object.entries(options.spanAttributes) : []),
133
+ [ATTR_DB_SYSTEM_NAME, "sqlite"],
134
+ ],
135
+ transformRows,
136
+ })) as SqliteClient,
137
+ {
138
+ [TypeId]: TypeId,
139
+ config: options,
140
+ loadExtension: (path: string) => Effect.flatMap(acquirer, (_) => _.loadExtension(path)),
141
+ },
142
+ )
143
+
144
+ return client
145
+ })
146
+
147
+ const nativeLayer = (config: Config) =>
148
+ Layer.effect(
149
+ Sqlite.Native,
150
+ Effect.gen(function* () {
151
+ const native = new DatabaseSync(config.filename, {
152
+ readOnly: config.readonly,
153
+ timeout: config.timeout,
154
+ allowExtension: config.allowExtension,
155
+ enableForeignKeyConstraints: true,
156
+ open: true,
157
+ })
158
+ yield* Effect.addFinalizer(() => Effect.sync(() => native.close()))
159
+ if (config.disableWAL !== true && config.readonly !== true) native.exec("PRAGMA journal_mode = WAL;")
160
+ return native
161
+ }),
162
+ )
163
+
164
+ const sqliteLayer = (config: Config) => Layer.effect(Client.SqlClient, make(config))
165
+
166
+ const drizzleLayer = Layer.effect(
167
+ Sqlite.Drizzle,
168
+ Effect.gen(function* () {
169
+ return drizzle({ client: (yield* Sqlite.Native) as DatabaseSync }) as unknown as Sqlite.DrizzleClient
170
+ }),
171
+ )
172
+
173
+ export const layer = (config: Config) => {
174
+ const native = nativeLayer(config)
175
+ return Layer.merge(native, Layer.merge(sqliteLayer(config), drizzleLayer).pipe(Layer.provide(native))).pipe(
176
+ Layer.provide(Reactivity.layer),
177
+ )
178
+ }
@@ -0,0 +1,8 @@
1
+ export * as Sqlite from "./sqlite"
2
+
3
+ import { Context } from "effect"
4
+ import type { drizzle } from "drizzle-orm/bun-sqlite"
5
+
6
+ export type DrizzleClient = ReturnType<typeof drizzle>
7
+ export class Native extends Context.Service<Native, unknown>()("@neurocode-ai/core/database/SqliteNative") {}
8
+ export class Drizzle extends Context.Service<Drizzle, DrizzleClient>()("@neurocode-ai/core/database/SqliteDrizzle") {}
@@ -0,0 +1,23 @@
1
+ import { buildLocationServiceMap } from "../location-services"
2
+ import { LocationServiceMap } from "../location-service-map"
3
+ import { LayerNode } from "./layer-node"
4
+ import { makeGlobalNode } from "./app-node"
5
+
6
+ export function build<A, E>(root: LayerNode.Node<A, E, any>, replacements: LayerNode.Replacements = []) {
7
+ let allReplacements = replacements
8
+
9
+ // Only build the location service map if it's actually needed
10
+ if (LayerNode.hasUnbound(root, LocationServiceMap.node) && !hasReplacement(replacements, LocationServiceMap.node)) {
11
+ const locationMap = buildLocationServiceMap(replacements)
12
+ const locationMapNode = makeGlobalNode({ service: LocationServiceMap.Service, layer: locationMap, deps: [] })
13
+ allReplacements = replacements.concat([[LocationServiceMap.node, locationMapNode]])
14
+ }
15
+
16
+ return LayerNode.compile(root, allReplacements)
17
+ }
18
+
19
+ function hasReplacement(replacements: LayerNode.Replacements, node: LayerNode.Node<unknown, unknown, any>) {
20
+ return replacements.some(([source]) => source.name === node.name)
21
+ }
22
+
23
+ export * as AppNodeBuilder from "./app-node-builder"
@@ -0,0 +1,18 @@
1
+ import { NodeFileSystem, NodePath } from "@effect/platform-node"
2
+ import { LLMClient, RequestExecutor } from "@neurocode-ai/llm/route"
3
+ import { FileSystem, Path } from "effect"
4
+ import { FetchHttpClient } from "effect/unstable/http"
5
+ import { HttpClient } from "effect/unstable/http"
6
+ import { makeGlobalNode } from "./app-node"
7
+
8
+ export const filesystem = makeGlobalNode({ service: FileSystem.FileSystem, layer: NodeFileSystem.layer, deps: [] })
9
+ export const path = makeGlobalNode({ service: Path.Path, layer: NodePath.layer, deps: [] })
10
+ export const httpClient = makeGlobalNode({ service: HttpClient.HttpClient, layer: FetchHttpClient.layer, deps: [] })
11
+ export const requestExecutor = makeGlobalNode({
12
+ service: RequestExecutor.Service,
13
+ layer: RequestExecutor.layer,
14
+ deps: [httpClient],
15
+ })
16
+ export const llmClient = makeGlobalNode({ service: LLMClient.Service, layer: LLMClient.layer, deps: [requestExecutor] })
17
+
18
+ export * as LayerNodePlatform from "./app-node-platform"
@@ -0,0 +1,14 @@
1
+ import { LayerNode } from "./layer-node"
2
+
3
+ export const tags = LayerNode.tags({
4
+ location: ["global"],
5
+ global: [],
6
+ })
7
+
8
+ export type GlobalNode<A, E = never> = LayerNode.Node<A, E, (typeof tags.values)["global"]>
9
+ export type LocationNode<A, E = never> = LayerNode.Node<A, E, (typeof tags.values)["location"]>
10
+
11
+ export const makeGlobalNode = tags.make("global")
12
+ export const makeLocationNode = tags.make("location")
13
+
14
+ export * as Node from "./app-node"
@@ -0,0 +1 @@
1
+ File to save in: ~/.local/share/opencode/worktree/012780/location-layer-tiers/packages/core/src/effect/
@@ -0,0 +1,45 @@
1
+ export * as KeyedMutex from "./keyed-mutex"
2
+
3
+ import { Effect, Semaphore } from "effect"
4
+
5
+ export interface KeyedMutex<in Key> {
6
+ readonly size: Effect.Effect<number>
7
+ readonly withLock: (key: Key) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
8
+ }
9
+
10
+ /**
11
+ * Creates an in-memory mutex with one lock per key. Entries are removed when no
12
+ * holder or waiter remains.
13
+ *
14
+ * same key -> queue
15
+ * different key -> run independently
16
+ *
17
+ * `users` counts holders and waiters so an entry is not removed while a waiter
18
+ * will reuse it.
19
+ */
20
+ export const makeUnsafe = <Key>(): KeyedMutex<Key> => {
21
+ const locks = new Map<Key, { readonly semaphore: Semaphore.Semaphore; users: number }>()
22
+
23
+ const withLock =
24
+ (key: Key) =>
25
+ <A, E, R>(effect: Effect.Effect<A, E, R>) =>
26
+ Effect.suspend(() => {
27
+ const current = locks.get(key)
28
+ const entry = current ?? { semaphore: Semaphore.makeUnsafe(1), users: 0 }
29
+ if (!current) locks.set(key, entry)
30
+ entry.users++
31
+ return entry.semaphore.withPermit(effect).pipe(
32
+ Effect.ensuring(
33
+ Effect.sync(() => {
34
+ entry.users--
35
+ if (entry.users === 0) locks.delete(key)
36
+ }),
37
+ ),
38
+ )
39
+ })
40
+
41
+ return { size: Effect.sync(() => locks.size), withLock }
42
+ }
43
+
44
+ /** Creates an in-memory keyed mutex inside an Effect workflow. */
45
+ export const make = <Key>(): Effect.Effect<KeyedMutex<Key>> => Effect.sync(makeUnsafe<Key>)