@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,158 @@
1
+ export * as ProjectDirectories from "./directories"
2
+
3
+ import { and, asc, desc, eq, isNotNull, isNull, ne, or } from "drizzle-orm"
4
+ import { Context, Effect, Layer, Schema } from "effect"
5
+ import { Database } from "../database/database"
6
+ import { makeGlobalNode } from "../effect/app-node"
7
+ import { AbsolutePath, optional } from "../schema"
8
+ import { ProjectSchema } from "./schema"
9
+ import { ProjectDirectoryTable } from "./sql"
10
+ import type { EffectDrizzleSqlite } from "@neurocode-ai/effect-drizzle-sqlite"
11
+
12
+ export interface Directory {
13
+ readonly directory: AbsolutePath
14
+ readonly strategy?: string
15
+ }
16
+
17
+ export const CreateInput = Schema.Struct({
18
+ projectID: ProjectSchema.ID,
19
+ directory: AbsolutePath,
20
+ strategy: Schema.optional(Schema.String),
21
+ behavior: Schema.Literals(["ignore", "replace"]).pipe(Schema.optional),
22
+ })
23
+ export type CreateInput = typeof CreateInput.Type
24
+
25
+ export const RemoveInput = Schema.Struct({
26
+ projectID: ProjectSchema.ID,
27
+ directory: AbsolutePath,
28
+ })
29
+ export type RemoveInput = typeof RemoveInput.Type
30
+
31
+ type DatabaseClient = EffectDrizzleSqlite.EffectSQLiteDatabase
32
+ export type Transaction = Parameters<Parameters<DatabaseClient["transaction"]>[0]>[0]
33
+
34
+ export const ListInput = Schema.Struct({
35
+ projectID: ProjectSchema.ID,
36
+ }).annotate({ identifier: "Project.DirectoriesInput" })
37
+ export type ListInput = typeof ListInput.Type
38
+
39
+ export const ListOutput = Schema.Array(
40
+ Schema.Struct({
41
+ directory: AbsolutePath,
42
+ strategy: optional(Schema.String),
43
+ }),
44
+ ).annotate({ identifier: "Project.Directories" })
45
+ export type ListOutput = typeof ListOutput.Type
46
+
47
+ export interface Interface {
48
+ readonly list: (projectID: ProjectSchema.ID) => Effect.Effect<ReadonlyArray<Directory>>
49
+ readonly get: (input: {
50
+ projectID: ProjectSchema.ID
51
+ directory: AbsolutePath
52
+ }) => Effect.Effect<Directory | undefined>
53
+ readonly contains: (input: { projectID: ProjectSchema.ID; directory: AbsolutePath }) => Effect.Effect<boolean>
54
+ readonly create: (input: CreateInput, tx?: Transaction) => Effect.Effect<boolean>
55
+ readonly remove: (input: RemoveInput, tx?: Transaction) => Effect.Effect<boolean>
56
+ }
57
+
58
+ export class Service extends Context.Service<Service, Interface>()("@neurocode/ProjectDirectories") {}
59
+
60
+ const layer = Layer.effect(
61
+ Service,
62
+ Effect.gen(function* () {
63
+ const db = (yield* Database.Service).db
64
+
65
+ const create = Effect.fn("ProjectDirectories.create")(function* (input: CreateInput, tx?: Transaction) {
66
+ const insert = (tx ?? db)
67
+ .insert(ProjectDirectoryTable)
68
+ .values({ project_id: input.projectID, directory: input.directory, strategy: input.strategy })
69
+ const query =
70
+ input.behavior === "replace"
71
+ ? insert.onConflictDoUpdate({
72
+ target: [ProjectDirectoryTable.project_id, ProjectDirectoryTable.directory],
73
+ set: { strategy: input.strategy ?? null },
74
+ setWhere: input.strategy
75
+ ? or(isNull(ProjectDirectoryTable.strategy), ne(ProjectDirectoryTable.strategy, input.strategy))
76
+ : isNotNull(ProjectDirectoryTable.strategy),
77
+ })
78
+ : insert.onConflictDoNothing()
79
+ return (
80
+ (yield* query.returning({ directory: ProjectDirectoryTable.directory }).get().pipe(Effect.orDie)) !== undefined
81
+ )
82
+ })
83
+
84
+ const remove = Effect.fn("ProjectDirectories.remove")(function* (input: RemoveInput, tx?: Transaction) {
85
+ return (
86
+ (yield* (tx ?? db)
87
+ .delete(ProjectDirectoryTable)
88
+ .where(
89
+ and(
90
+ eq(ProjectDirectoryTable.project_id, input.projectID),
91
+ eq(ProjectDirectoryTable.directory, input.directory),
92
+ ),
93
+ )
94
+ .returning({ directory: ProjectDirectoryTable.directory })
95
+ .get()
96
+ .pipe(Effect.orDie)) !== undefined
97
+ )
98
+ })
99
+
100
+ const list = Effect.fn("ProjectDirectories.list")(function* (projectID: ProjectSchema.ID) {
101
+ const rows = yield* db
102
+ .select({ directory: ProjectDirectoryTable.directory, strategy: ProjectDirectoryTable.strategy })
103
+ .from(ProjectDirectoryTable)
104
+ .where(eq(ProjectDirectoryTable.project_id, projectID))
105
+ .orderBy(desc(ProjectDirectoryTable.time_created), asc(ProjectDirectoryTable.directory))
106
+ .all()
107
+ .pipe(Effect.orDie)
108
+ return rows.map((row) => ({ directory: row.directory, strategy: row.strategy ?? undefined }))
109
+ })
110
+
111
+ const contains = Effect.fn("ProjectDirectories.contains")(function* (input: {
112
+ projectID: ProjectSchema.ID
113
+ directory: AbsolutePath
114
+ }) {
115
+ return (
116
+ (yield* db
117
+ .select({ directory: ProjectDirectoryTable.directory })
118
+ .from(ProjectDirectoryTable)
119
+ .where(
120
+ and(
121
+ eq(ProjectDirectoryTable.project_id, input.projectID),
122
+ eq(ProjectDirectoryTable.directory, input.directory),
123
+ ),
124
+ )
125
+ .get()
126
+ .pipe(Effect.orDie)) !== undefined
127
+ )
128
+ })
129
+
130
+ const get = Effect.fn("ProjectDirectories.get")(function* (input: {
131
+ projectID: ProjectSchema.ID
132
+ directory: AbsolutePath
133
+ }) {
134
+ const row = yield* db
135
+ .select({ directory: ProjectDirectoryTable.directory, strategy: ProjectDirectoryTable.strategy })
136
+ .from(ProjectDirectoryTable)
137
+ .where(
138
+ and(
139
+ eq(ProjectDirectoryTable.project_id, input.projectID),
140
+ eq(ProjectDirectoryTable.directory, input.directory),
141
+ ),
142
+ )
143
+ .get()
144
+ .pipe(Effect.orDie)
145
+ return row ? { directory: row.directory, strategy: row.strategy ?? undefined } : undefined
146
+ })
147
+
148
+ return Service.of({
149
+ list,
150
+ get,
151
+ contains,
152
+ create,
153
+ remove,
154
+ })
155
+ }),
156
+ )
157
+
158
+ export const node = makeGlobalNode({ service: Service, layer: layer, deps: [Database.node] })
@@ -0,0 +1,16 @@
1
+ export * as ProjectSchema from "./schema"
2
+
3
+ import { Schema } from "effect"
4
+ import { Project } from "@neurocode-ai/schema/project"
5
+ import { AbsolutePath } from "../schema"
6
+
7
+ export const ID = Project.ID
8
+ export type ID = typeof ID.Type
9
+
10
+ export const Vcs = Schema.Union([
11
+ Schema.Struct({
12
+ type: Schema.Literal("git"),
13
+ store: AbsolutePath,
14
+ }),
15
+ ])
16
+ export type Vcs = typeof Vcs.Type
@@ -0,0 +1,35 @@
1
+ import { sqliteTable, text, integer, primaryKey } from "drizzle-orm/sqlite-core"
2
+ import * as DatabasePath from "../database/path"
3
+ import { Timestamps } from "../database/schema.sql"
4
+ import { ProjectSchema } from "./schema"
5
+
6
+ export const ProjectTable = sqliteTable("project", {
7
+ id: text().$type<ProjectSchema.ID>().primaryKey(),
8
+ worktree: DatabasePath.absoluteColumn().notNull(),
9
+ vcs: text(),
10
+ name: text(),
11
+ icon_url: text(),
12
+ icon_url_override: text(),
13
+ icon_color: text(),
14
+ ...Timestamps,
15
+ time_initialized: integer(),
16
+ sandboxes: DatabasePath.absoluteArrayColumn().notNull(),
17
+ commands: text({ mode: "json" }).$type<{ start?: string }>(),
18
+ })
19
+
20
+ export const ProjectDirectoryTable = sqliteTable(
21
+ "project_directory",
22
+ {
23
+ project_id: text()
24
+ .$type<ProjectSchema.ID>()
25
+ .notNull()
26
+ .references(() => ProjectTable.id, { onDelete: "cascade" }),
27
+ directory: DatabasePath.absoluteColumn().notNull(),
28
+ type: text().$type<"main" | "root" | "git_worktree">(),
29
+ strategy: text(),
30
+ time_created: integer()
31
+ .notNull()
32
+ .$default(() => Date.now()),
33
+ },
34
+ (table) => [primaryKey({ columns: [table.project_id, table.directory] })],
35
+ )
package/src/project.ts ADDED
@@ -0,0 +1,136 @@
1
+ export * as ProjectV2 from "./project"
2
+ export * as Project from "./project"
3
+
4
+ import { Context, Effect, Layer, Schema } from "effect"
5
+ import path from "path"
6
+ import { AbsolutePath } from "./schema"
7
+ import { FSUtil } from "./fs-util"
8
+ import { Git } from "./git"
9
+ import { makeGlobalNode } from "./effect/app-node"
10
+ import { Hash } from "./util/hash"
11
+ import { ProjectDirectories } from "./project/directories"
12
+ import { ProjectSchema } from "./project/schema"
13
+
14
+ export const ID = ProjectSchema.ID
15
+ export type ID = ProjectSchema.ID
16
+
17
+ export const Vcs = ProjectSchema.Vcs
18
+ export type Vcs = ProjectSchema.Vcs
19
+
20
+ export class Info extends Schema.Class<Info>("Project.Info")({
21
+ id: ID,
22
+ }) {}
23
+
24
+ export const DirectoriesInput = ProjectDirectories.ListInput
25
+ export type DirectoriesInput = typeof DirectoriesInput.Type
26
+
27
+ export const Directories = ProjectDirectories.ListOutput
28
+ export type Directories = typeof Directories.Type
29
+
30
+ export interface Resolved {
31
+ readonly previous?: ID
32
+ readonly id: ID
33
+ readonly directory: AbsolutePath
34
+ readonly vcs?: Vcs
35
+ }
36
+
37
+ export interface Interface {
38
+ readonly directories: (input: DirectoriesInput) => Effect.Effect<Directories>
39
+ readonly resolve: (input: AbsolutePath) => Effect.Effect<Resolved>
40
+ /**
41
+ * Temporary bridge method for writing the resolved project ID to the repo-local cache.
42
+ *
43
+ * This exists while the old neurocode project service and this core project
44
+ * service work together: core resolves the ID, while the old service still owns
45
+ * database migration and persistence. The old service should call this after it
46
+ * finishes migrating from `resolve().previous` to `resolve().id`; once project
47
+ * persistence moves into core, this separate bridge method can go away.
48
+ */
49
+ readonly commit: (input: { store: AbsolutePath; id: ID }) => Effect.Effect<void>
50
+ }
51
+
52
+ export class Service extends Context.Service<Service, Interface>()("@neurocode/ProjectV2") {}
53
+
54
+ const layer = Layer.effect(
55
+ Service,
56
+ Effect.gen(function* () {
57
+ const fs = yield* FSUtil.Service
58
+ const git = yield* Git.Service
59
+ const projectDirectories = yield* ProjectDirectories.Service
60
+
61
+ const directories = Effect.fn("Project.directories")(function* (input: DirectoriesInput) {
62
+ return yield* projectDirectories.list(input.projectID)
63
+ })
64
+
65
+ const cached = Effect.fnUntraced(function* (dir: string) {
66
+ return yield* fs.readFileString(path.join(dir, "neurocode")).pipe(
67
+ Effect.map((value) => value.trim()),
68
+ Effect.map((value) => (value ? ID.make(value) : undefined)),
69
+ Effect.catch(() => Effect.succeed(undefined)),
70
+ )
71
+ })
72
+
73
+ const remote = Effect.fnUntraced(function* (repo: Git.Repository) {
74
+ const origin = yield* git.remote.get(repo)
75
+ if (!origin) return undefined
76
+ const normalized = url(origin)
77
+ if (!normalized) return undefined
78
+ return ID.make(Hash.fast(`git-remote:${normalized}`))
79
+ })
80
+
81
+ function url(input: string) {
82
+ const value = input.trim()
83
+ if (!value) return undefined
84
+
85
+ try {
86
+ const parsed = new URL(value)
87
+ if (parsed.protocol === "file:") return undefined
88
+ return parts(parsed.hostname, parsed.pathname)
89
+ } catch {
90
+ const scp = value.match(/^([^@/:]+@)?([^/:]+):(.+)$/)
91
+ if (scp) return parts(scp[2], scp[3])
92
+ return undefined
93
+ }
94
+ }
95
+
96
+ function parts(host: string, name: string) {
97
+ const pathname = name
98
+ .replace(/^\/+/, "")
99
+ .replace(/\.git\/?$/, "")
100
+ .replace(/\/+$/, "")
101
+ if (!host || !pathname) return undefined
102
+ return `${host.toLowerCase()}/${pathname}`
103
+ }
104
+
105
+ const root = Effect.fnUntraced(function* (repo: Git.Repository) {
106
+ const root = (yield* git.history.rootCommits(repo))[0]
107
+ return root ? ID.make(root) : undefined
108
+ })
109
+
110
+ const resolve = Effect.fn("Project.resolve")(function* (input: AbsolutePath) {
111
+ const repo = yield* git.repo.discover(input)
112
+ if (!repo) return { id: ID.global, directory: AbsolutePath.make(path.parse(input).root), vcs: undefined }
113
+
114
+ const previous = yield* cached(repo.commonDirectory)
115
+ const id = (yield* remote(repo)) ?? previous ?? (yield* root(repo))
116
+ return {
117
+ previous,
118
+ id: id ?? ID.global,
119
+ directory: repo.worktree,
120
+ vcs: { type: "git" as const, store: repo.commonDirectory },
121
+ }
122
+ })
123
+
124
+ const commit = Effect.fn("Project.commit")(function* (input: { store: AbsolutePath; id: ID }) {
125
+ yield* fs.writeFileString(path.join(input.store, "neurocode"), input.id).pipe(Effect.ignore)
126
+ })
127
+
128
+ return Service.of({ directories, resolve, commit })
129
+ }),
130
+ )
131
+
132
+ export const node = makeGlobalNode({
133
+ service: Service,
134
+ layer: layer,
135
+ deps: [FSUtil.node, Git.node, ProjectDirectories.node],
136
+ })
@@ -0,0 +1,25 @@
1
+ export * as ProviderV2 from "./provider"
2
+
3
+ import { Types } from "effect"
4
+ import { Provider } from "@neurocode-ai/schema/provider"
5
+
6
+ export const ID = Provider.ID
7
+ export type ID = typeof ID.Type
8
+
9
+ export const AISDK = Provider.AISDK
10
+
11
+ export const Native = Provider.Native
12
+
13
+ export const Api = Provider.Api
14
+ export type Api = Provider.Api
15
+ export type MutableApi<T extends Api = Api> = T extends Api
16
+ ? Omit<Types.DeepMutable<T>, "settings"> & (undefined extends T["settings"] ? { settings?: any } : { settings: any })
17
+ : never
18
+
19
+ export const Request = Provider.Request
20
+ export type Request = Provider.Request
21
+
22
+ export const Info = Provider.Info
23
+ export type Info = Provider.Info
24
+
25
+ export type MutableInfo = Omit<Types.DeepMutable<Info>, "api"> & { api: MutableApi }
@@ -0,0 +1,37 @@
1
+ export * as PtyProtocol from "./protocol"
2
+
3
+ // Wire protocol for PTY websocket transports. The PTY domain service is transport-free; server
4
+ // routes adapt Pty.attach to websockets with these helpers so every surface speaks one protocol.
5
+ //
6
+ // Outbound frames are raw UTF-8 terminal chunks. One control frame — a 0x00 byte followed by
7
+ // UTF-8 JSON — carries the absolute output cursor after replay so clients can resume later.
8
+
9
+ const encoder = new TextEncoder()
10
+ const decoder = new TextDecoder("utf-8", { fatal: true })
11
+
12
+ // Replay can be megabytes; send it in bounded frames.
13
+ export const REPLAY_CHUNK = 64 * 1024
14
+
15
+ export function metaFrame(cursor: number) {
16
+ const bytes = encoder.encode(JSON.stringify({ cursor }))
17
+ const out = new Uint8Array(bytes.length + 1)
18
+ out[0] = 0
19
+ out.set(bytes, 1)
20
+ return out
21
+ }
22
+
23
+ export function chunks(data: string) {
24
+ const out: string[] = []
25
+ for (let i = 0; i < data.length; i += REPLAY_CHUNK) out.push(data.slice(i, i + REPLAY_CHUNK))
26
+ return out
27
+ }
28
+
29
+ // Inbound client frames are UTF-8 text or binary; invalid UTF-8 input is dropped.
30
+ export function decodeInput(message: string | Uint8Array | ArrayBuffer) {
31
+ if (typeof message === "string") return message
32
+ try {
33
+ return decoder.decode(message instanceof ArrayBuffer ? new Uint8Array(message) : message)
34
+ } catch {
35
+ return undefined
36
+ }
37
+ }
@@ -0,0 +1,26 @@
1
+ import { spawn as create } from "bun-pty"
2
+ import type { Opts, Proc } from "./pty"
3
+
4
+ export type { Disp, Exit, Opts, Proc } from "./pty"
5
+
6
+ export function spawn(file: string, args: string[], opts: Opts): Proc {
7
+ const pty = create(file, args, opts)
8
+ return {
9
+ pid: pty.pid,
10
+ onData(listener) {
11
+ return pty.onData(listener)
12
+ },
13
+ onExit(listener) {
14
+ return pty.onExit(listener)
15
+ },
16
+ write(data) {
17
+ pty.write(data)
18
+ },
19
+ resize(cols, rows) {
20
+ pty.resize(cols, rows)
21
+ },
22
+ kill(signal) {
23
+ pty.kill(signal)
24
+ },
25
+ }
26
+ }
@@ -0,0 +1,29 @@
1
+ import * as pty from "@lydell/node-pty"
2
+ import type { Opts, Proc } from "./pty"
3
+
4
+ export type { Disp, Exit, Opts, Proc } from "./pty"
5
+
6
+ export function spawn(file: string, args: string[], opts: Opts): Proc {
7
+ const proc = pty.spawn(file, args, {
8
+ ...opts,
9
+ ...(process.platform === "win32" ? { useConptyDll: true } : {}),
10
+ })
11
+ return {
12
+ pid: proc.pid,
13
+ onData(listener) {
14
+ return proc.onData(listener)
15
+ },
16
+ onExit(listener) {
17
+ return proc.onExit(listener)
18
+ },
19
+ write(data) {
20
+ proc.write(data)
21
+ },
22
+ resize(cols, rows) {
23
+ proc.resize(cols, rows)
24
+ },
25
+ kill(signal) {
26
+ proc.kill(signal)
27
+ },
28
+ }
29
+ }
package/src/pty/pty.ts ADDED
@@ -0,0 +1,25 @@
1
+ export type Disp = {
2
+ dispose(): void
3
+ }
4
+
5
+ export type Exit = {
6
+ exitCode: number
7
+ signal?: number | string
8
+ }
9
+
10
+ export type Opts = {
11
+ name: string
12
+ cols?: number
13
+ rows?: number
14
+ cwd?: string
15
+ env?: Record<string, string>
16
+ }
17
+
18
+ export type Proc = {
19
+ pid: number
20
+ onData(listener: (data: string) => void): Disp
21
+ onExit(listener: (event: Exit) => void): Disp
22
+ write(data: string): void
23
+ resize(cols: number, rows: number): void
24
+ kill(signal?: string): void
25
+ }
@@ -0,0 +1 @@
1
+ export { ID as PtyID } from "@neurocode-ai/schema/pty"
@@ -0,0 +1,56 @@
1
+ export * as PtyTicket from "./ticket"
2
+
3
+ import { WorkspaceV2 } from "../workspace"
4
+ import { PtyTicket } from "@neurocode-ai/schema/pty-ticket"
5
+ import { PtyID } from "./schema"
6
+ import { Cache, Context, Duration, Effect, Layer } from "effect"
7
+ import { makeGlobalNode } from "../effect/app-node"
8
+
9
+ const DEFAULT_TTL = Duration.seconds(60)
10
+ const CAPACITY = 10_000
11
+
12
+ export const ConnectToken = PtyTicket.ConnectToken
13
+
14
+ export type Scope = {
15
+ readonly ptyID: PtyID
16
+ readonly directory?: string
17
+ readonly workspaceID?: WorkspaceV2.ID
18
+ }
19
+
20
+ export interface Interface {
21
+ issue(input: Scope): Effect.Effect<typeof ConnectToken.Type>
22
+ consume(input: Scope & { readonly ticket: string }): Effect.Effect<boolean>
23
+ }
24
+
25
+ export class Service extends Context.Service<Service, Interface>()("@neurocode/PtyTicket") {}
26
+
27
+ function matches(record: Scope, input: Scope) {
28
+ return (
29
+ record.ptyID === input.ptyID && record.directory === input.directory && record.workspaceID === input.workspaceID
30
+ )
31
+ }
32
+
33
+ // Tickets are inserted via Cache.set and removed atomically via invalidateWhen. The lookup is
34
+ // never invoked; it dies if it ever is, which would signal a misuse of the Service interface.
35
+ const noLookup = () => Effect.die("PtyTicket cache must be used via set/invalidateWhen, never get")
36
+
37
+ // Visible for tests so the TTL can be shortened. Production uses `layer` with the default TTL.
38
+ export const make = (ttl: Duration.Input = DEFAULT_TTL) =>
39
+ Effect.gen(function* () {
40
+ const cache = yield* Cache.make<string, Scope>({ capacity: CAPACITY, lookup: noLookup, timeToLive: ttl })
41
+ const expiresIn = Math.max(1, Math.round(Duration.toSeconds(Duration.fromInputUnsafe(ttl))))
42
+ return Service.of({
43
+ issue: Effect.fn("PtyTicket.issue")(function* (input) {
44
+ const ticket = crypto.randomUUID()
45
+ yield* Cache.set(cache, ticket, input)
46
+ return { ticket, expires_in: expiresIn }
47
+ }),
48
+ consume: Effect.fn("PtyTicket.consume")(function* (input) {
49
+ return yield* Cache.invalidateWhen(cache, input.ticket, (stored) => matches(stored, input))
50
+ }),
51
+ })
52
+ })
53
+
54
+ const layer = Layer.effect(Service, make())
55
+
56
+ export const node = makeGlobalNode({ service: Service, layer: layer, deps: [] })