soraacode 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/LICENSE +21 -0
  2. package/bin/soraacode.exe +10 -0
  3. package/package.json +17 -25
  4. package/{script/postinstall.mjs → postinstall.mjs} +5 -5
  5. package/AGENTS.md +0 -131
  6. package/Dockerfile +0 -18
  7. package/README.md +0 -15
  8. package/bin/opencode +0 -199
  9. package/bin/soraacode +0 -199
  10. package/bunfig.toml +0 -7
  11. package/git +0 -0
  12. package/migration/20260511173437_session-metadata/migration.sql +0 -1
  13. package/migration/20260511173437_session-metadata/snapshot.json +0 -1500
  14. package/parsers-config.ts +0 -1
  15. package/script/bench-search.ts +0 -94
  16. package/script/bench-test-suite.ts +0 -52
  17. package/script/build-headless.ts +0 -51
  18. package/script/build.ts +0 -245
  19. package/script/generate.ts +0 -14
  20. package/script/httpapi-exercise.ts +0 -1
  21. package/script/profile-test-files.ts +0 -42
  22. package/script/publish.ts +0 -213
  23. package/script/run-workspace-server +0 -106
  24. package/script/schema.ts +0 -77
  25. package/script/time.ts +0 -6
  26. package/script/trace-imports.ts +0 -153
  27. package/soraacode-0.1.7.tgz +0 -0
  28. package/specs/effect/error-boundaries-plan.md +0 -235
  29. package/specs/effect/errors.md +0 -207
  30. package/specs/effect/facades.md +0 -218
  31. package/specs/effect/guide.md +0 -247
  32. package/specs/effect/instance-context.md +0 -13
  33. package/specs/effect/loose-ends.md +0 -30
  34. package/specs/effect/migration.md +0 -62
  35. package/specs/effect/routes.md +0 -61
  36. package/specs/effect/schema.md +0 -88
  37. package/specs/effect/server-package.md +0 -58
  38. package/specs/effect/todo.md +0 -241
  39. package/specs/effect/tools.md +0 -88
  40. package/specs/openapi-translation-cleanup.md +0 -204
  41. package/specs/tui-plugins.md +0 -544
  42. package/specs/v2/api.ts +0 -67
  43. package/specs/v2/message-shape.md +0 -136
  44. package/specs/v2/notifications.md +0 -13
  45. package/specs/v2/tui-command-shim.md +0 -67
  46. package/src/account/account.ts +0 -481
  47. package/src/account/repo.ts +0 -171
  48. package/src/account/schema.ts +0 -99
  49. package/src/account/url.ts +0 -8
  50. package/src/acp/agent.ts +0 -95
  51. package/src/acp/config-option.ts +0 -206
  52. package/src/acp/content.ts +0 -269
  53. package/src/acp/directory.ts +0 -212
  54. package/src/acp/error.ts +0 -97
  55. package/src/acp/event.ts +0 -421
  56. package/src/acp/permission.ts +0 -254
  57. package/src/acp/profile.ts +0 -42
  58. package/src/acp/service.ts +0 -1226
  59. package/src/acp/session.ts +0 -232
  60. package/src/acp/tool.ts +0 -364
  61. package/src/acp/usage.ts +0 -243
  62. package/src/agent/agent.ts +0 -450
  63. package/src/agent/generate.txt +0 -75
  64. package/src/agent/prompt/compaction.txt +0 -5
  65. package/src/agent/prompt/explore.txt +0 -18
  66. package/src/agent/prompt/summary.txt +0 -11
  67. package/src/agent/prompt/title.txt +0 -44
  68. package/src/agent/subagent-permissions.ts +0 -27
  69. package/src/audio.d.ts +0 -14
  70. package/src/auth/index.ts +0 -97
  71. package/src/background/job.ts +0 -37
  72. package/src/bus/global.ts +0 -22
  73. package/src/cli/bootstrap.ts +0 -11
  74. package/src/cli/cmd/cmd.ts +0 -7
  75. package/src/cli/cmd/serve.ts +0 -24
  76. package/src/cli/effect/prompt.ts +0 -37
  77. package/src/cli/effect-cmd.ts +0 -96
  78. package/src/cli/error.ts +0 -130
  79. package/src/cli/heap.ts +0 -45
  80. package/src/cli/logo.ts +0 -2
  81. package/src/cli/network.ts +0 -80
  82. package/src/cli/ui.ts +0 -132
  83. package/src/command/index.ts +0 -177
  84. package/src/command/template/initialize.txt +0 -66
  85. package/src/command/template/review.txt +0 -101
  86. package/src/config/agent.ts +0 -59
  87. package/src/config/command.ts +0 -39
  88. package/src/config/config.ts +0 -707
  89. package/src/config/entry-name.ts +0 -19
  90. package/src/config/managed.ts +0 -69
  91. package/src/config/markdown.ts +0 -36
  92. package/src/config/parse.ts +0 -61
  93. package/src/config/paths.ts +0 -45
  94. package/src/config/plugin.ts +0 -79
  95. package/src/config/v2-compat.ts +0 -449
  96. package/src/config/variable.ts +0 -91
  97. package/src/control-plane/adapters/index.ts +0 -41
  98. package/src/control-plane/adapters/worktree.ts +0 -96
  99. package/src/control-plane/dev/README.md +0 -19
  100. package/src/control-plane/dev/debug-workspace-plugin.ts +0 -73
  101. package/src/control-plane/types.ts +0 -59
  102. package/src/control-plane/util.ts +0 -39
  103. package/src/control-plane/workspace-adapter-runtime.ts +0 -51
  104. package/src/control-plane/workspace-context.ts +0 -26
  105. package/src/control-plane/workspace.ts +0 -966
  106. package/src/effect/app-node-builder-v1.ts +0 -12
  107. package/src/effect/app-runtime.ts +0 -135
  108. package/src/effect/bootstrap-runtime.ts +0 -19
  109. package/src/effect/bridge.ts +0 -84
  110. package/src/effect/config-service.ts +0 -67
  111. package/src/effect/instance-ref.ts +0 -11
  112. package/src/effect/instance-registry.ts +0 -12
  113. package/src/effect/instance-state.ts +0 -69
  114. package/src/effect/promise.ts +0 -17
  115. package/src/effect/run-service.ts +0 -47
  116. package/src/effect/runner.ts +0 -217
  117. package/src/effect/runtime-flags.ts +0 -78
  118. package/src/env/index.ts +0 -41
  119. package/src/event-manifest.ts +0 -3
  120. package/src/event-v2-bridge.ts +0 -71
  121. package/src/format/formatter.ts +0 -404
  122. package/src/format/index.ts +0 -203
  123. package/src/git/index.ts +0 -348
  124. package/src/id/id.ts +0 -80
  125. package/src/ide/index.ts +0 -54
  126. package/src/image/image.ts +0 -172
  127. package/src/index-headless.ts +0 -89
  128. package/src/index.ts +0 -93
  129. package/src/installation/index.ts +0 -336
  130. package/src/lsp/client.ts +0 -650
  131. package/src/lsp/diagnostic.ts +0 -29
  132. package/src/lsp/language.ts +0 -121
  133. package/src/lsp/launch.ts +0 -21
  134. package/src/lsp/lsp.ts +0 -507
  135. package/src/lsp/server.ts +0 -1983
  136. package/src/markdown.d.ts +0 -4
  137. package/src/mcp/auth.ts +0 -163
  138. package/src/mcp/browser.ts +0 -37
  139. package/src/mcp/catalog.ts +0 -170
  140. package/src/mcp/index.ts +0 -1004
  141. package/src/mcp/oauth-callback.ts +0 -194
  142. package/src/mcp/oauth-provider.ts +0 -259
  143. package/src/node.ts +0 -4
  144. package/src/patch/index.ts +0 -686
  145. package/src/permission/arity.ts +0 -163
  146. package/src/permission/evaluate.ts +0 -1
  147. package/src/permission/index.ts +0 -223
  148. package/src/plugin/github-copilot/copilot.ts +0 -415
  149. package/src/plugin/github-copilot/models.ts +0 -261
  150. package/src/plugin/index.ts +0 -298
  151. package/src/plugin/install.ts +0 -439
  152. package/src/plugin/loader.ts +0 -237
  153. package/src/plugin/meta.ts +0 -188
  154. package/src/plugin/openai/README.md +0 -31
  155. package/src/plugin/openai/codex.ts +0 -575
  156. package/src/plugin/openai/ws-pool.ts +0 -271
  157. package/src/plugin/openai/ws.ts +0 -383
  158. package/src/plugin/pty-environment.ts +0 -24
  159. package/src/plugin/shared.ts +0 -323
  160. package/src/project/bootstrap-service.ts +0 -9
  161. package/src/project/bootstrap.ts +0 -58
  162. package/src/project/instance-context.ts +0 -24
  163. package/src/project/instance-runtime.ts +0 -16
  164. package/src/project/instance-store.ts +0 -213
  165. package/src/project/project.ts +0 -483
  166. package/src/project/vcs.ts +0 -423
  167. package/src/provider/auth.ts +0 -229
  168. package/src/provider/error.ts +0 -195
  169. package/src/provider/model-status.ts +0 -8
  170. package/src/provider/provider.ts +0 -1955
  171. package/src/provider/transform.ts +0 -1923
  172. package/src/question/index.ts +0 -161
  173. package/src/question/schema.ts +0 -4
  174. package/src/server/auth.ts +0 -48
  175. package/src/server/event.ts +0 -10
  176. package/src/server/global-lifecycle.ts +0 -28
  177. package/src/server/mdns.ts +0 -47
  178. package/src/server/proxy-util.ts +0 -48
  179. package/src/server/routes/instance/httpapi/AGENTS.md +0 -39
  180. package/src/server/routes/instance/httpapi/api.ts +0 -97
  181. package/src/server/routes/instance/httpapi/errors.ts +0 -193
  182. package/src/server/routes/instance/httpapi/groups/config.ts +0 -65
  183. package/src/server/routes/instance/httpapi/groups/control-plane.ts +0 -35
  184. package/src/server/routes/instance/httpapi/groups/control.ts +0 -76
  185. package/src/server/routes/instance/httpapi/groups/event.ts +0 -29
  186. package/src/server/routes/instance/httpapi/groups/experimental.ts +0 -275
  187. package/src/server/routes/instance/httpapi/groups/file.ts +0 -185
  188. package/src/server/routes/instance/httpapi/groups/global.ts +0 -139
  189. package/src/server/routes/instance/httpapi/groups/instance.ts +0 -206
  190. package/src/server/routes/instance/httpapi/groups/mcp.ts +0 -156
  191. package/src/server/routes/instance/httpapi/groups/metadata.ts +0 -18
  192. package/src/server/routes/instance/httpapi/groups/permission.ts +0 -61
  193. package/src/server/routes/instance/httpapi/groups/project-copy.ts +0 -32
  194. package/src/server/routes/instance/httpapi/groups/project.ts +0 -93
  195. package/src/server/routes/instance/httpapi/groups/provider.ts +0 -101
  196. package/src/server/routes/instance/httpapi/groups/pty.ts +0 -172
  197. package/src/server/routes/instance/httpapi/groups/query.ts +0 -12
  198. package/src/server/routes/instance/httpapi/groups/question.ts +0 -74
  199. package/src/server/routes/instance/httpapi/groups/session.ts +0 -462
  200. package/src/server/routes/instance/httpapi/groups/sync.ts +0 -113
  201. package/src/server/routes/instance/httpapi/groups/tui.ts +0 -208
  202. package/src/server/routes/instance/httpapi/groups/workspace.ts +0 -141
  203. package/src/server/routes/instance/httpapi/handlers/config.ts +0 -34
  204. package/src/server/routes/instance/httpapi/handlers/control-plane.ts +0 -37
  205. package/src/server/routes/instance/httpapi/handlers/control.ts +0 -43
  206. package/src/server/routes/instance/httpapi/handlers/event.ts +0 -99
  207. package/src/server/routes/instance/httpapi/handlers/experimental.ts +0 -193
  208. package/src/server/routes/instance/httpapi/handlers/file.ts +0 -139
  209. package/src/server/routes/instance/httpapi/handlers/global.ts +0 -126
  210. package/src/server/routes/instance/httpapi/handlers/instance.ts +0 -110
  211. package/src/server/routes/instance/httpapi/handlers/mcp.ts +0 -111
  212. package/src/server/routes/instance/httpapi/handlers/permission.ts +0 -41
  213. package/src/server/routes/instance/httpapi/handlers/project-copy.ts +0 -83
  214. package/src/server/routes/instance/httpapi/handlers/project.ts +0 -63
  215. package/src/server/routes/instance/httpapi/handlers/provider.ts +0 -116
  216. package/src/server/routes/instance/httpapi/handlers/pty.ts +0 -273
  217. package/src/server/routes/instance/httpapi/handlers/question.ts +0 -54
  218. package/src/server/routes/instance/httpapi/handlers/session-errors.ts +0 -21
  219. package/src/server/routes/instance/httpapi/handlers/session.ts +0 -442
  220. package/src/server/routes/instance/httpapi/handlers/sync.ts +0 -89
  221. package/src/server/routes/instance/httpapi/handlers/tui.ts +0 -131
  222. package/src/server/routes/instance/httpapi/handlers/workspace.ts +0 -102
  223. package/src/server/routes/instance/httpapi/lifecycle.ts +0 -54
  224. package/src/server/routes/instance/httpapi/middleware/authorization.ts +0 -150
  225. package/src/server/routes/instance/httpapi/middleware/compression.ts +0 -64
  226. package/src/server/routes/instance/httpapi/middleware/cors-vary.ts +0 -29
  227. package/src/server/routes/instance/httpapi/middleware/error.ts +0 -44
  228. package/src/server/routes/instance/httpapi/middleware/fence.ts +0 -25
  229. package/src/server/routes/instance/httpapi/middleware/instance-context.ts +0 -43
  230. package/src/server/routes/instance/httpapi/middleware/proxy.ts +0 -131
  231. package/src/server/routes/instance/httpapi/middleware/schema-error.ts +0 -41
  232. package/src/server/routes/instance/httpapi/middleware/workspace-routing.ts +0 -250
  233. package/src/server/routes/instance/httpapi/public.ts +0 -537
  234. package/src/server/routes/instance/httpapi/server.ts +0 -325
  235. package/src/server/routes/instance/httpapi/websocket-tracker.ts +0 -60
  236. package/src/server/server.ts +0 -224
  237. package/src/server/shared/fence.ts +0 -60
  238. package/src/server/shared/pty-ticket.ts +0 -15
  239. package/src/server/shared/public-ui.ts +0 -12
  240. package/src/server/shared/tui-control.ts +0 -28
  241. package/src/server/shared/ui.ts +0 -108
  242. package/src/server/shared/workspace-routing.ts +0 -45
  243. package/src/server/tui-event.ts +0 -1
  244. package/src/session/compaction.ts +0 -608
  245. package/src/session/instruction.ts +0 -237
  246. package/src/session/llm/AGENTS.md +0 -90
  247. package/src/session/llm/ai-sdk.ts +0 -291
  248. package/src/session/llm/native-request.ts +0 -196
  249. package/src/session/llm/native-runtime.ts +0 -195
  250. package/src/session/llm/request.ts +0 -226
  251. package/src/session/llm.ts +0 -390
  252. package/src/session/message-error.ts +0 -14
  253. package/src/session/message-v2.ts +0 -742
  254. package/src/session/message.ts +0 -148
  255. package/src/session/overflow.ts +0 -34
  256. package/src/session/processor.ts +0 -732
  257. package/src/session/prompt/build-switch.txt +0 -5
  258. package/src/session/prompt/default.txt +0 -35
  259. package/src/session/prompt/plan-mode.txt +0 -70
  260. package/src/session/prompt/plan.txt +0 -26
  261. package/src/session/prompt.ts +0 -1631
  262. package/src/session/reminders.ts +0 -92
  263. package/src/session/retry.ts +0 -209
  264. package/src/session/revert.ts +0 -136
  265. package/src/session/run-state.ts +0 -151
  266. package/src/session/schema.ts +0 -26
  267. package/src/session/session.ts +0 -1016
  268. package/src/session/status.ts +0 -56
  269. package/src/session/summary.ts +0 -160
  270. package/src/session/system.ts +0 -123
  271. package/src/session/todo.ts +0 -74
  272. package/src/session/tools.ts +0 -590
  273. package/src/share/session.ts +0 -58
  274. package/src/share/share-next.ts +0 -371
  275. package/src/skill/discovery.ts +0 -140
  276. package/src/skill/index.ts +0 -354
  277. package/src/snapshot/index.ts +0 -807
  278. package/src/sql.d.ts +0 -4
  279. package/src/storage/schema.ts +0 -5
  280. package/src/storage/storage.ts +0 -327
  281. package/src/sync/README.md +0 -179
  282. package/src/sync/schema.ts +0 -11
  283. package/src/temporary.ts +0 -31
  284. package/src/tool/apply_patch.ts +0 -313
  285. package/src/tool/apply_patch.txt +0 -14
  286. package/src/tool/code-mode.ts +0 -310
  287. package/src/tool/edit.ts +0 -737
  288. package/src/tool/edit.txt +0 -13
  289. package/src/tool/external-directory.ts +0 -49
  290. package/src/tool/glob.ts +0 -76
  291. package/src/tool/glob.txt +0 -7
  292. package/src/tool/grep.ts +0 -115
  293. package/src/tool/grep.txt +0 -7
  294. package/src/tool/invalid.ts +0 -21
  295. package/src/tool/json-schema.ts +0 -164
  296. package/src/tool/lsp.ts +0 -113
  297. package/src/tool/lsp.txt +0 -1
  298. package/src/tool/mcp-websearch.ts +0 -96
  299. package/src/tool/plan-enter.txt +0 -1
  300. package/src/tool/plan-exit.txt +0 -1
  301. package/src/tool/plan.ts +0 -79
  302. package/src/tool/question.ts +0 -44
  303. package/src/tool/question.txt +0 -1
  304. package/src/tool/read.ts +0 -386
  305. package/src/tool/read.txt +0 -13
  306. package/src/tool/registry.ts +0 -455
  307. package/src/tool/schema.ts +0 -14
  308. package/src/tool/shell/id.ts +0 -19
  309. package/src/tool/shell/prompt.ts +0 -293
  310. package/src/tool/shell/shell.txt +0 -21
  311. package/src/tool/shell.ts +0 -645
  312. package/src/tool/skill.ts +0 -70
  313. package/src/tool/skill.txt +0 -1
  314. package/src/tool/task.ts +0 -371
  315. package/src/tool/task.txt +0 -11
  316. package/src/tool/todo.ts +0 -46
  317. package/src/tool/todowrite.txt +0 -11
  318. package/src/tool/tool.ts +0 -183
  319. package/src/tool/truncate.ts +0 -156
  320. package/src/tool/truncation-dir.ts +0 -4
  321. package/src/tool/webfetch.ts +0 -192
  322. package/src/tool/webfetch.txt +0 -9
  323. package/src/tool/websearch.ts +0 -143
  324. package/src/tool/websearch.txt +0 -9
  325. package/src/tool/write.ts +0 -104
  326. package/src/tool/write.txt +0 -11
  327. package/src/util/archive.ts +0 -17
  328. package/src/util/bom.ts +0 -27
  329. package/src/util/data-url.ts +0 -9
  330. package/src/util/defer.ts +0 -10
  331. package/src/util/effect-http-client.ts +0 -11
  332. package/src/util/error.ts +0 -16
  333. package/src/util/filesystem.ts +0 -251
  334. package/src/util/html.ts +0 -8
  335. package/src/util/iife.ts +0 -3
  336. package/src/util/lazy.ts +0 -20
  337. package/src/util/local-context.ts +0 -25
  338. package/src/util/locale.ts +0 -4
  339. package/src/util/media.ts +0 -26
  340. package/src/util/process.ts +0 -177
  341. package/src/util/proxy-env.ts +0 -72
  342. package/src/util/queue.ts +0 -32
  343. package/src/util/record.ts +0 -3
  344. package/src/util/repository.ts +0 -232
  345. package/src/util/rpc.ts +0 -66
  346. package/src/util/signal.ts +0 -12
  347. package/src/util/timeout.ts +0 -13
  348. package/src/util/token.ts +0 -1
  349. package/src/util/wildcard.ts +0 -59
  350. package/src/worktree/index.ts +0 -623
  351. package/sst-env.d.ts +0 -10
  352. package/tsconfig.headless.json +0 -13
  353. package/tsconfig.json +0 -16
@@ -1,323 +0,0 @@
1
- import path from "path"
2
- import { fileURLToPath, pathToFileURL } from "url"
3
- import npa from "npm-package-arg"
4
- import semver from "semver"
5
- import { Filesystem } from "@/util/filesystem"
6
- import { isRecord } from "@/util/record"
7
- import { Npm } from "@soraacode/core/npm"
8
-
9
- // Old npm package names for plugins that are now built-in
10
- export const DEPRECATED_PLUGIN_PACKAGES = ["opencode-openai-codex-auth", "opencode-copilot-auth"]
11
-
12
- export function isDeprecatedPlugin(spec: string) {
13
- return DEPRECATED_PLUGIN_PACKAGES.some((pkg) => spec.includes(pkg))
14
- }
15
-
16
- function parse(spec: string) {
17
- try {
18
- return npa(spec)
19
- } catch {}
20
- }
21
-
22
- export function parsePluginSpecifier(spec: string) {
23
- const hit = parse(spec)
24
- if (hit?.type === "alias" && !hit.name) {
25
- const sub = (hit as npa.AliasResult).subSpec
26
- if (sub?.name) {
27
- const version = !sub.rawSpec || sub.rawSpec === "*" ? "latest" : sub.rawSpec
28
- return { pkg: sub.name, version }
29
- }
30
- }
31
- if (!hit?.name) return { pkg: spec, version: "" }
32
- if (hit.raw === hit.name) return { pkg: hit.name, version: "latest" }
33
- return { pkg: hit.name, version: hit.rawSpec }
34
- }
35
-
36
- export type PluginSource = "file" | "npm"
37
- export type PluginKind = "server" | "tui"
38
- type PluginMode = "strict" | "detect"
39
-
40
- export type PluginPackage = {
41
- dir: string
42
- pkg: string
43
- json: Record<string, unknown>
44
- }
45
-
46
- export type PluginEntry = {
47
- spec: string
48
- source: PluginSource
49
- target: string
50
- pkg?: PluginPackage
51
- entry?: string
52
- }
53
-
54
- const INDEX_FILES = ["index.ts", "index.tsx", "index.js", "index.mjs", "index.cjs"]
55
-
56
- export function pluginSource(spec: string): PluginSource {
57
- if (isPathPluginSpec(spec)) return "file"
58
- return "npm"
59
- }
60
-
61
- function resolveExportPath(raw: string, dir: string) {
62
- if (raw.startsWith("file://")) return fileURLToPath(raw)
63
- if (path.isAbsolute(raw)) return raw
64
- return path.resolve(dir, raw)
65
- }
66
-
67
- function isAbsolutePath(raw: string) {
68
- return path.isAbsolute(raw) || /^[A-Za-z]:[\\/]/.test(raw)
69
- }
70
-
71
- function extractExportValue(value: unknown): string | undefined {
72
- if (typeof value === "string") return value
73
- if (!isRecord(value)) return undefined
74
- for (const key of ["import", "default"]) {
75
- const nested = value[key]
76
- if (typeof nested === "string") return nested
77
- }
78
- return undefined
79
- }
80
-
81
- function packageMain(pkg: PluginPackage) {
82
- const value = pkg.json.main
83
- if (typeof value !== "string") return
84
- const next = value.trim()
85
- if (!next) return
86
- return next
87
- }
88
-
89
- function resolvePackageFile(spec: string, raw: string, kind: string, pkg: PluginPackage) {
90
- const resolved = resolveExportPath(raw, pkg.dir)
91
- const root = Filesystem.resolve(pkg.dir)
92
- const next = Filesystem.resolve(resolved)
93
- if (!Filesystem.contains(root, next)) {
94
- throw new Error(`Plugin ${spec} resolved ${kind} entry outside plugin directory`)
95
- }
96
- return next
97
- }
98
-
99
- function resolvePackagePath(spec: string, raw: string, kind: PluginKind, pkg: PluginPackage) {
100
- return pathToFileURL(resolvePackageFile(spec, raw, kind, pkg)).href
101
- }
102
-
103
- function resolvePackageEntrypoint(spec: string, kind: PluginKind, pkg: PluginPackage) {
104
- const exports = pkg.json.exports
105
- if (isRecord(exports)) {
106
- const raw = extractExportValue(exports[`./${kind}`])
107
- if (raw) return resolvePackagePath(spec, raw, kind, pkg)
108
- }
109
-
110
- if (kind !== "server") return
111
- const main = packageMain(pkg)
112
- if (!main) return
113
- return resolvePackagePath(spec, main, kind, pkg)
114
- }
115
-
116
- function targetPath(target: string) {
117
- if (target.startsWith("file://")) return fileURLToPath(target)
118
- if (path.isAbsolute(target)) return target
119
- }
120
-
121
- async function resolveDirectoryIndex(dir: string) {
122
- for (const name of INDEX_FILES) {
123
- const file = path.join(dir, name)
124
- if (await Filesystem.exists(file)) return file
125
- }
126
- }
127
-
128
- async function resolveTargetDirectory(target: string) {
129
- const file = targetPath(target)
130
- if (!file) return
131
- const stat = await Filesystem.statAsync(file)
132
- if (!stat?.isDirectory()) return
133
- return file
134
- }
135
-
136
- async function resolvePluginEntrypoint(spec: string, target: string, kind: PluginKind, pkg?: PluginPackage) {
137
- const source = pluginSource(spec)
138
- const hit =
139
- pkg ?? (source === "npm" ? await readPluginPackage(target) : await readPluginPackage(target).catch(() => undefined))
140
- if (!hit) return target
141
-
142
- const entry = resolvePackageEntrypoint(spec, kind, hit)
143
- if (entry) return entry
144
-
145
- const dir = await resolveTargetDirectory(target)
146
-
147
- if (kind === "tui") {
148
- if (source === "file" && dir) {
149
- const index = await resolveDirectoryIndex(dir)
150
- if (index) return pathToFileURL(index).href
151
- }
152
-
153
- if (source === "npm") return
154
- if (dir) return
155
-
156
- return target
157
- }
158
-
159
- if (dir && isRecord(hit.json.exports)) {
160
- if (source === "file") {
161
- const index = await resolveDirectoryIndex(dir)
162
- if (index) return pathToFileURL(index).href
163
- }
164
-
165
- return
166
- }
167
-
168
- return target
169
- }
170
-
171
- export function isPathPluginSpec(spec: string) {
172
- return spec.startsWith("file://") || spec.startsWith(".") || isAbsolutePath(spec)
173
- }
174
-
175
- export async function resolvePathPluginTarget(spec: string) {
176
- const raw = spec.startsWith("file://") ? fileURLToPath(spec) : spec
177
- const file = path.isAbsolute(raw) || /^[A-Za-z]:[\\/]/.test(raw) ? raw : path.resolve(raw)
178
- const stat = await Filesystem.statAsync(file)
179
- if (!stat?.isDirectory()) {
180
- if (spec.startsWith("file://")) return spec
181
- return pathToFileURL(file).href
182
- }
183
-
184
- if (await Filesystem.exists(path.join(file, "package.json"))) {
185
- return pathToFileURL(file).href
186
- }
187
-
188
- const index = await resolveDirectoryIndex(file)
189
- if (index) return pathToFileURL(index).href
190
-
191
- throw new Error(`Plugin directory ${file} is missing package.json or index file`)
192
- }
193
-
194
- export async function checkPluginCompatibility(target: string, opencodeVersion: string, pkg?: PluginPackage) {
195
- if (!semver.valid(opencodeVersion) || semver.major(opencodeVersion) === 0) return
196
- const hit = pkg ?? (await readPluginPackage(target).catch(() => undefined))
197
- if (!hit) return
198
- const engines = hit.json.engines
199
- if (!isRecord(engines)) return
200
- const range = engines.opencode
201
- if (typeof range !== "string") return
202
- if (!semver.satisfies(opencodeVersion, range)) {
203
- throw new Error(`Plugin requires opencode ${range} but running ${opencodeVersion}`)
204
- }
205
- }
206
-
207
- export async function resolvePluginTarget(spec: string) {
208
- if (isPathPluginSpec(spec)) return resolvePathPluginTarget(spec)
209
- const hit = parse(spec)
210
- const pkg = hit?.name && hit.raw === hit.name ? `${hit.name}@latest` : spec
211
- const result = await Npm.add(pkg)
212
- return result.directory
213
- }
214
-
215
- export async function readPluginPackage(target: string): Promise<PluginPackage> {
216
- const file = target.startsWith("file://") ? fileURLToPath(target) : target
217
- const stat = await Filesystem.statAsync(file)
218
- const dir = stat?.isDirectory() ? file : path.dirname(file)
219
- const pkg = path.join(dir, "package.json")
220
- const json = await Filesystem.readJson<Record<string, unknown>>(pkg)
221
- return { dir, pkg, json }
222
- }
223
-
224
- export async function createPluginEntry(spec: string, target: string, kind: PluginKind): Promise<PluginEntry> {
225
- const source = pluginSource(spec)
226
- const pkg =
227
- source === "npm" ? await readPluginPackage(target) : await readPluginPackage(target).catch(() => undefined)
228
- const entry = await resolvePluginEntrypoint(spec, target, kind, pkg)
229
- return {
230
- spec,
231
- source,
232
- target,
233
- pkg,
234
- entry,
235
- }
236
- }
237
-
238
- export function readPackageThemes(spec: string, pkg: PluginPackage) {
239
- const field = pkg.json["oc-themes"]
240
- if (field === undefined) return []
241
- if (!Array.isArray(field)) {
242
- throw new TypeError(`Plugin ${spec} has invalid oc-themes field`)
243
- }
244
-
245
- const list = field.map((item) => {
246
- if (typeof item !== "string") {
247
- throw new TypeError(`Plugin ${spec} has invalid oc-themes entry`)
248
- }
249
-
250
- const raw = item.trim()
251
- if (!raw) {
252
- throw new TypeError(`Plugin ${spec} has empty oc-themes entry`)
253
- }
254
- if (raw.startsWith("file://") || isAbsolutePath(raw)) {
255
- throw new TypeError(`Plugin ${spec} oc-themes entry must be relative: ${item}`)
256
- }
257
-
258
- return resolvePackageFile(spec, raw, "oc-themes", pkg)
259
- })
260
-
261
- return Array.from(new Set(list))
262
- }
263
-
264
- export function readPluginId(id: unknown, spec: string) {
265
- if (id === undefined) return
266
- if (typeof id !== "string") throw new TypeError(`Plugin ${spec} has invalid id type ${typeof id}`)
267
- const value = id.trim()
268
- if (!value) throw new TypeError(`Plugin ${spec} has an empty id`)
269
- return value
270
- }
271
-
272
- export function readV1Plugin(
273
- mod: Record<string, unknown>,
274
- spec: string,
275
- kind: PluginKind,
276
- mode: PluginMode = "strict",
277
- ) {
278
- const value = mod.default
279
- if (!isRecord(value)) {
280
- if (mode === "detect") return
281
- throw new TypeError(`Plugin ${spec} must default export an object with ${kind}()`)
282
- }
283
- if (mode === "detect" && !("id" in value) && !("server" in value) && !("tui" in value)) return
284
-
285
- const server = "server" in value ? value.server : undefined
286
- const tui = "tui" in value ? value.tui : undefined
287
- if (server !== undefined && typeof server !== "function") {
288
- throw new TypeError(`Plugin ${spec} has invalid server export`)
289
- }
290
- if (tui !== undefined && typeof tui !== "function") {
291
- throw new TypeError(`Plugin ${spec} has invalid tui export`)
292
- }
293
- if (server !== undefined && tui !== undefined) {
294
- throw new TypeError(`Plugin ${spec} must default export either server() or tui(), not both`)
295
- }
296
- if (kind === "server" && server === undefined) {
297
- throw new TypeError(`Plugin ${spec} must default export an object with server()`)
298
- }
299
- if (kind === "tui" && tui === undefined) {
300
- throw new TypeError(`Plugin ${spec} must default export an object with tui()`)
301
- }
302
-
303
- return value
304
- }
305
-
306
- export async function resolvePluginId(
307
- source: PluginSource,
308
- spec: string,
309
- target: string,
310
- id: string | undefined,
311
- pkg?: PluginPackage,
312
- ) {
313
- if (source === "file") {
314
- if (id) return id
315
- throw new TypeError(`Path plugin ${spec} must export id`)
316
- }
317
- if (id) return id
318
- const hit = pkg ?? (await readPluginPackage(target))
319
- if (typeof hit.json.name !== "string" || !hit.json.name.trim()) {
320
- throw new TypeError(`Plugin package ${hit.pkg} is missing name`)
321
- }
322
- return hit.json.name.trim()
323
- }
@@ -1,9 +0,0 @@
1
- import { Context, Effect } from "effect"
2
-
3
- export interface Interface {
4
- readonly run: Effect.Effect<void>
5
- }
6
-
7
- export class Service extends Context.Service<Service, Interface>()("@opencode/InstanceBootstrap") {}
8
-
9
- export * as InstanceBootstrap from "./bootstrap-service"
@@ -1,58 +0,0 @@
1
- import { makeGlobalNode } from "@soraacode/core/effect/app-node"
2
- import { Plugin } from "../plugin"
3
- import { Format } from "../format"
4
- import { LSP } from "@/lsp/lsp"
5
- import { Snapshot } from "../snapshot"
6
- import * as Project from "./project"
7
- import * as Vcs from "./vcs"
8
- import { InstanceState } from "@/effect/instance-state"
9
- import { ShareNext } from "@/share/share-next"
10
- import { Effect, Layer } from "effect"
11
- import { Config } from "@/config/config"
12
- import { Service } from "./bootstrap-service"
13
-
14
- export { Service } from "./bootstrap-service"
15
- export type { Interface } from "./bootstrap-service"
16
-
17
- const layer = Layer.effect(
18
- Service,
19
- Effect.gen(function* () {
20
- // Yield each bootstrap dep at layer init so `run` itself has R = never.
21
- // InstanceStore imports only the lightweight tag from bootstrap-service.ts,
22
- // so it can depend on bootstrap without importing this implementation graph.
23
- const config = yield* Config.Service
24
- const format = yield* Format.Service
25
- const lsp = yield* LSP.Service
26
- const plugin = yield* Plugin.Service
27
- const project = yield* Project.Service
28
- const shareNext = yield* ShareNext.Service
29
- const snapshot = yield* Snapshot.Service
30
- const vcs = yield* Vcs.Service
31
-
32
- const run = Effect.gen(function* () {
33
- const ctx = yield* InstanceState.context
34
- yield* Effect.logInfo("bootstrapping", { directory: ctx.directory })
35
- // everything depends on config so eager load it for nice traces
36
- yield* config.get()
37
- // Plugin can mutate config so it has to be initialized before anything else.
38
- yield* plugin.init()
39
- // Each service self-manages its own slow work via Effect.forkScoped against
40
- // its per-instance state scope. We just await materialization here.
41
- yield* Effect.forEach(
42
- [lsp, shareNext, format, vcs, snapshot, project],
43
- (s) => s.init().pipe(Effect.catchCause((cause) => Effect.logWarning("init failed", { cause }))),
44
- { concurrency: "unbounded", discard: true },
45
- ).pipe(Effect.withSpan("InstanceBootstrap.init"))
46
- }).pipe(Effect.withSpan("InstanceBootstrap"))
47
-
48
- return Service.of({ run })
49
- }),
50
- )
51
-
52
- export const node = makeGlobalNode({
53
- service: Service,
54
- layer: layer,
55
- deps: [Config.node, Format.node, LSP.node, Plugin.node, Project.node, ShareNext.node, Snapshot.node, Vcs.node],
56
- })
57
-
58
- export * as InstanceBootstrap from "./bootstrap"
@@ -1,24 +0,0 @@
1
- import { LocalContext } from "@/util/local-context"
2
- import { FSUtil } from "@soraacode/core/fs-util"
3
- import type * as Project from "./project"
4
-
5
- export interface InstanceContext {
6
- directory: string
7
- worktree: string
8
- project: Project.Info
9
- }
10
-
11
- export const context = LocalContext.create<InstanceContext>("instance")
12
-
13
- /**
14
- * Check if a path is within the project boundary.
15
- * Returns true if path is inside ctx.directory OR ctx.worktree.
16
- * Paths within the worktree but outside the working directory should not trigger external_directory permission.
17
- */
18
- export function containsPath(filepath: string, ctx: InstanceContext): boolean {
19
- if (FSUtil.contains(ctx.directory, filepath)) return true
20
- // Non-git projects set worktree to "/" which would match ANY absolute path.
21
- // Skip worktree check in this case to preserve external_directory permissions.
22
- if (ctx.worktree === "/") return false
23
- return FSUtil.contains(ctx.worktree, filepath)
24
- }
@@ -1,16 +0,0 @@
1
- import { AppRuntime } from "@/effect/app-runtime"
2
- import { type InstanceContext } from "./instance-context"
3
- import { InstanceStore, type LoadInput } from "./instance-store"
4
-
5
- // Bridge for Promise/ALS callers that cannot yet yield InstanceStore.Service.
6
- // Delete this module once those callers are migrated to Effect boundaries that
7
- // provide InstanceStore directly.
8
-
9
- export const load = (input: LoadInput) => AppRuntime.runPromise(InstanceStore.Service.use((store) => store.load(input)))
10
- export const disposeInstance = (ctx: InstanceContext) =>
11
- AppRuntime.runPromise(InstanceStore.Service.use((store) => store.dispose(ctx)))
12
- export const disposeAllInstances = () => AppRuntime.runPromise(InstanceStore.Service.use((store) => store.disposeAll()))
13
- export const reloadInstance = (input: LoadInput) =>
14
- AppRuntime.runPromise(InstanceStore.Service.use((store) => store.reload(input)))
15
-
16
- export * as InstanceRuntime from "./instance-runtime"
@@ -1,213 +0,0 @@
1
- import { LayerNode } from "@soraacode/core/effect/layer-node"
2
- import { makeGlobalNode, Node } from "@soraacode/core/effect/app-node"
3
- import { GlobalBus } from "@/bus/global"
4
- import { serviceUse } from "@soraacode/core/effect/service-use"
5
- import { WorkspaceContext } from "@/control-plane/workspace-context"
6
- import { InstanceRef } from "@/effect/instance-ref"
7
- import { disposeInstance as runDisposers } from "@/effect/instance-registry"
8
- import { FSUtil } from "@soraacode/core/fs-util"
9
- import { Context, Deferred, Duration, Effect, Exit, Layer, Scope } from "effect"
10
- import { type InstanceContext } from "./instance-context"
11
- import { InstanceBootstrap } from "./bootstrap-service"
12
- import * as Project from "./project"
13
-
14
- export interface LoadInput {
15
- directory: string
16
- worktree?: string
17
- project?: Project.Info
18
- }
19
-
20
- export interface Interface {
21
- readonly load: (input: LoadInput) => Effect.Effect<InstanceContext>
22
- readonly reload: (input: LoadInput) => Effect.Effect<InstanceContext>
23
- readonly dispose: (ctx: InstanceContext) => Effect.Effect<void>
24
- readonly disposeDirectory: (directory: string) => Effect.Effect<void>
25
- readonly disposeAll: () => Effect.Effect<void>
26
- readonly provide: <A, E, R>(input: LoadInput, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
27
- }
28
-
29
- export class Service extends Context.Service<Service, Interface>()("@opencode/InstanceStore") {}
30
-
31
- export const use = serviceUse(Service)
32
-
33
- interface Entry {
34
- readonly deferred: Deferred.Deferred<InstanceContext>
35
- }
36
-
37
- const layer: Layer.Layer<Service, never, Project.Service | InstanceBootstrap.Service> = Layer.effect(
38
- Service,
39
- Effect.gen(function* () {
40
- const project = yield* Project.Service
41
- const bootstrap = yield* InstanceBootstrap.Service
42
- const scope = yield* Scope.Scope
43
- const cache = new Map<string, Entry>()
44
-
45
- const boot = (input: LoadInput & { directory: string }) =>
46
- Effect.gen(function* () {
47
- const ctx: InstanceContext =
48
- input.project && input.worktree
49
- ? {
50
- directory: input.directory,
51
- worktree: input.worktree,
52
- project: input.project,
53
- }
54
- : yield* project.fromDirectory(input.directory).pipe(
55
- Effect.map((result) => ({
56
- directory: input.directory,
57
- worktree: result.sandbox,
58
- project: result.project,
59
- })),
60
- )
61
- yield* bootstrap.run.pipe(Effect.provideService(InstanceRef, ctx))
62
- return ctx
63
- }).pipe(Effect.withSpan("InstanceStore.boot"))
64
-
65
- const removeEntry = (directory: string, entry: Entry) =>
66
- Effect.sync(() => {
67
- if (cache.get(directory) !== entry) return false
68
- cache.delete(directory)
69
- return true
70
- })
71
-
72
- const completeLoad = (directory: string, input: LoadInput, entry: Entry) =>
73
- Effect.gen(function* () {
74
- const exit = yield* Effect.exit(boot({ ...input, directory }))
75
- if (Exit.isFailure(exit)) yield* removeEntry(directory, entry)
76
- yield* Deferred.done(entry.deferred, exit).pipe(Effect.asVoid)
77
- })
78
-
79
- const emitDisposed = (input: { directory: string; project?: string }) =>
80
- Effect.sync(() =>
81
- GlobalBus.emit("event", {
82
- directory: input.directory,
83
- project: input.project,
84
- workspace: WorkspaceContext.workspaceID,
85
- payload: {
86
- type: "server.instance.disposed",
87
- properties: {
88
- directory: input.directory,
89
- },
90
- },
91
- }),
92
- )
93
-
94
- const disposeContext = Effect.fn("InstanceStore.disposeContext")(function* (ctx: InstanceContext) {
95
- yield* Effect.logInfo("disposing instance", { directory: ctx.directory })
96
- yield* Effect.promise(() => runDisposers(ctx.directory))
97
- yield* emitDisposed({ directory: ctx.directory, project: ctx.project.id })
98
- })
99
-
100
- const disposeEntry = Effect.fnUntraced(function* (directory: string, entry: Entry, ctx: InstanceContext) {
101
- if (cache.get(directory) !== entry) return false
102
- yield* disposeContext(ctx)
103
- if (cache.get(directory) !== entry) return false
104
- cache.delete(directory)
105
- return true
106
- })
107
-
108
- const load = (input: LoadInput): Effect.Effect<InstanceContext> => {
109
- const directory = FSUtil.resolve(input.directory)
110
- return Effect.uninterruptibleMask((restore) =>
111
- Effect.gen(function* () {
112
- const existing = cache.get(directory)
113
- if (existing) return yield* restore(Deferred.await(existing.deferred))
114
-
115
- const entry: Entry = { deferred: Deferred.makeUnsafe<InstanceContext>() }
116
- cache.set(directory, entry)
117
- yield* Effect.gen(function* () {
118
- yield* Effect.logInfo("creating instance", { directory: directory })
119
- yield* completeLoad(directory, input, entry)
120
- }).pipe(Effect.forkIn(scope, { startImmediately: true }))
121
- return yield* restore(Deferred.await(entry.deferred))
122
- }),
123
- ).pipe(Effect.withSpan("InstanceStore.load"))
124
- }
125
-
126
- const reload = (input: LoadInput): Effect.Effect<InstanceContext> => {
127
- const directory = FSUtil.resolve(input.directory)
128
- return Effect.uninterruptibleMask((restore) =>
129
- Effect.gen(function* () {
130
- const previous = cache.get(directory)
131
- const entry: Entry = { deferred: Deferred.makeUnsafe<InstanceContext>() }
132
- cache.set(directory, entry)
133
- yield* Effect.gen(function* () {
134
- yield* Effect.logInfo("reloading instance", { directory: directory })
135
- if (previous) {
136
- yield* Deferred.await(previous.deferred).pipe(Effect.ignore)
137
- yield* Effect.promise(() => runDisposers(directory))
138
- yield* emitDisposed({ directory, project: input.project?.id })
139
- }
140
- yield* completeLoad(directory, input, entry)
141
- }).pipe(Effect.forkIn(scope, { startImmediately: true }))
142
- return yield* restore(Deferred.await(entry.deferred))
143
- }),
144
- ).pipe(Effect.withSpan("InstanceStore.reload"))
145
- }
146
-
147
- const dispose = Effect.fn("InstanceStore.dispose")(function* (ctx: InstanceContext) {
148
- const entry = cache.get(ctx.directory)
149
- if (!entry) return yield* disposeContext(ctx)
150
-
151
- const exit = yield* Deferred.await(entry.deferred).pipe(Effect.exit)
152
- if (Exit.isFailure(exit)) return yield* removeEntry(ctx.directory, entry).pipe(Effect.asVoid)
153
- if (exit.value !== ctx) return
154
- yield* disposeEntry(ctx.directory, entry, ctx).pipe(Effect.asVoid)
155
- })
156
-
157
- const disposeDirectory = Effect.fn("InstanceStore.disposeDirectory")(function* (input: string) {
158
- const directory = FSUtil.resolve(input)
159
- const entry = cache.get(directory)
160
- if (!entry) return
161
- const exit = yield* Deferred.await(entry.deferred).pipe(Effect.exit)
162
- if (Exit.isFailure(exit)) return yield* removeEntry(directory, entry).pipe(Effect.asVoid)
163
- yield* disposeEntry(directory, entry, exit.value).pipe(Effect.asVoid)
164
- })
165
-
166
- const disposeAllOnce = Effect.fnUntraced(function* () {
167
- yield* Effect.logInfo("disposing all instances")
168
- yield* Effect.forEach(
169
- [...cache.entries()],
170
- (item) =>
171
- Effect.gen(function* () {
172
- const exit = yield* Deferred.await(item[1].deferred).pipe(Effect.exit)
173
- if (Exit.isFailure(exit)) {
174
- yield* Effect.logWarning("instance dispose failed", { key: item[0], cause: exit.cause })
175
- yield* removeEntry(item[0], item[1])
176
- return
177
- }
178
- yield* disposeEntry(item[0], item[1], exit.value)
179
- }),
180
- { discard: true },
181
- )
182
- })
183
-
184
- const cachedDisposeAll = yield* Effect.cachedWithTTL(disposeAllOnce(), Duration.zero)
185
- const disposeAll = Effect.fn("InstanceStore.disposeAll")(function* () {
186
- return yield* cachedDisposeAll
187
- })
188
-
189
- const provide = <A, E, R>(input: LoadInput, effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
190
- load(input).pipe(Effect.flatMap((ctx) => effect.pipe(Effect.provideService(InstanceRef, ctx))))
191
-
192
- yield* Effect.addFinalizer(() => disposeAll().pipe(Effect.ignore))
193
-
194
- return Service.of({
195
- load,
196
- reload,
197
- dispose,
198
- disposeDirectory,
199
- disposeAll,
200
- provide,
201
- })
202
- }),
203
- )
204
-
205
- export const bootstrapNode = LayerNode.unbound(InstanceBootstrap.Service, Node.tags.values.global)
206
-
207
- export const node = makeGlobalNode({
208
- service: Service,
209
- layer: layer,
210
- deps: [Project.node, bootstrapNode],
211
- })
212
-
213
- export * as InstanceStore from "./instance-store"