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,136 +0,0 @@
1
- # Message Shape
2
-
3
- Problem:
4
-
5
- - stored messages need enough data to replay and resume a session later
6
- - prompt hooks often just want to append a synthetic user/assistant message
7
- - today that means faking ids, timestamps, and request metadata
8
-
9
- ## Option 1: Two Message Shapes
10
-
11
- Keep `User` / `Assistant` for stored history, but clean them up.
12
-
13
- ```ts
14
- type User = {
15
- role: "user"
16
- time: { created: number }
17
- request: {
18
- agent: string
19
- model: ModelRef
20
- variant?: string
21
- format?: OutputFormat
22
- system?: string
23
- tools?: Record<string, boolean>
24
- }
25
- }
26
-
27
- type Assistant = {
28
- role: "assistant"
29
- run: { agent: string; model: ModelRef; path: { cwd: string; root: string } }
30
- usage: { cost: number; tokens: Tokens }
31
- result: { finish?: string; error?: Error; structured?: unknown; kind: "reply" | "summary" }
32
- }
33
- ```
34
-
35
- Add a separate transient `PromptMessage` for prompt surgery.
36
-
37
- ```ts
38
- type PromptMessage = {
39
- role: "user" | "assistant"
40
- parts: PromptPart[]
41
- }
42
- ```
43
-
44
- Plugin hook example:
45
-
46
- ```ts
47
- prompt.push({
48
- role: "user",
49
- parts: [{ type: "text", text: "Summarize the tool output above and continue." }],
50
- })
51
- ```
52
-
53
- Tradeoff: prompt hooks get easy lightweight messages, but there are now two message shapes.
54
-
55
- ## Option 2: Prompt Mutators
56
-
57
- Keep `User` / `Assistant` as the stored history model.
58
-
59
- Prompt hooks do not build messages directly. The runtime gives them prompt mutators.
60
-
61
- ```ts
62
- type PromptEditor = {
63
- append(input: { role: "user" | "assistant"; parts: PromptPart[] }): void
64
- prepend(input: { role: "user" | "assistant"; parts: PromptPart[] }): void
65
- appendTo(target: "last-user" | "last-assistant", parts: PromptPart[]): void
66
- insertAfter(messageID: string, input: { role: "user" | "assistant"; parts: PromptPart[] }): void
67
- insertBefore(messageID: string, input: { role: "user" | "assistant"; parts: PromptPart[] }): void
68
- }
69
- ```
70
-
71
- Plugin hook examples:
72
-
73
- ```ts
74
- prompt.append({
75
- role: "user",
76
- parts: [{ type: "text", text: "Summarize the tool output above and continue." }],
77
- })
78
- ```
79
-
80
- ```ts
81
- prompt.appendTo("last-user", [{ type: "text", text: BUILD_SWITCH }])
82
- ```
83
-
84
- Tradeoff: avoids a second full message type and avoids fake ids/timestamps, but moves more magic into the hook API.
85
-
86
- ## Option 3: Separate Turn State
87
-
88
- Move execution settings out of `User` and into a separate turn/request object.
89
-
90
- ```ts
91
- type Turn = {
92
- id: string
93
- request: {
94
- agent: string
95
- model: ModelRef
96
- variant?: string
97
- format?: OutputFormat
98
- system?: string
99
- tools?: Record<string, boolean>
100
- }
101
- }
102
-
103
- type User = {
104
- role: "user"
105
- turnID: string
106
- time: { created: number }
107
- }
108
-
109
- type Assistant = {
110
- role: "assistant"
111
- turnID: string
112
- usage: { cost: number; tokens: Tokens }
113
- result: { finish?: string; error?: Error; structured?: unknown; kind: "reply" | "summary" }
114
- }
115
- ```
116
-
117
- Examples:
118
-
119
- ```ts
120
- const turn = {
121
- request: {
122
- agent: "build",
123
- model: { providerID: "openai", modelID: "gpt-5" },
124
- },
125
- }
126
- ```
127
-
128
- ```ts
129
- const msg = {
130
- role: "user",
131
- turnID: turn.id,
132
- parts: [{ type: "text", text: "Summarize the tool output above and continue." }],
133
- }
134
- ```
135
-
136
- Tradeoff: stored messages get much smaller and cleaner, but replay now has to join messages with turn state and prompt hooks still need a way to pick which turn they belong to.
@@ -1,13 +0,0 @@
1
- # TUI Notifications Default
2
-
3
- Problem:
4
-
5
- - v1 defaults `attention.enabled` to `false`
6
- - users can opt in with `attention.enabled = true`
7
- - v2 should make core TUI notifications a default behavior
8
-
9
- ## v2 Target
10
-
11
- Flip `attention.enabled` to `true` by default in v2.
12
-
13
- Keep `attention.enabled = false` as the explicit opt-out.
@@ -1,67 +0,0 @@
1
- # TUI Command Shim Removal
2
-
3
- Problem:
4
-
5
- - v1 keeps a deprecated `api.command` TUI plugin shim so older plugins do not fail during initialization
6
- - v2 should expose only the keymap command API
7
- - tests and fixtures should not encode legacy command behavior as expected behavior
8
-
9
- ## Remove Public Types
10
-
11
- In `packages/plugin/src/tui.ts`, remove:
12
-
13
- - `TuiCommand`
14
- - `TuiCommandApi`
15
- - `TuiPluginApi.command`
16
-
17
- Keep `api.keymap` as the only TUI command registration and execution surface.
18
-
19
- ## Remove Runtime Shim
20
-
21
- Delete `packages/opencode/src/cli/cmd/tui/plugin/command-shim.ts`.
22
-
23
- In `packages/opencode/src/cli/cmd/tui/plugin/api.tsx`, remove:
24
-
25
- - the `createCommandShim` import
26
- - the `command: createCommandShim(...)` field from `createTuiApi(...)`
27
-
28
- In `packages/opencode/src/cli/cmd/tui/plugin/runtime.ts`, remove:
29
-
30
- - the `createCommandShim` import
31
- - the `command: createCommandShim(...)` field from `pluginApi(...)`
32
-
33
- ## Migration Target
34
-
35
- Plugin authors should replace old calls with keymap calls:
36
-
37
- ```ts
38
- api.keymap.registerLayer({
39
- commands: [
40
- {
41
- name: "plugin.command",
42
- title: "Plugin Command",
43
- namespace: "palette",
44
- slashName: "plugin",
45
- run() {
46
- api.ui.dialog.clear()
47
- },
48
- },
49
- ],
50
- bindings: [{ key: "ctrl+shift+p", cmd: "plugin.command" }],
51
- })
52
- ```
53
-
54
- Direct replacements:
55
-
56
- - `api.command.register(cb)` -> `api.keymap.registerLayer({ commands, bindings })`
57
- - `api.command.trigger(name)` -> `api.keymap.dispatchCommand(name)`
58
- - `api.command.show()` -> `api.keymap.dispatchCommand("command.palette.show")`
59
- - `onSelect(dialog)` -> use `api.ui.dialog` from the plugin API closure
60
-
61
- ## Verification
62
-
63
- After removal, run from package directories:
64
-
65
- - `bun typecheck` in `packages/plugin`
66
- - `bun typecheck` in `packages/opencode`
67
- - TUI plugin loader tests in `packages/opencode` if runtime plugin API wiring changed
@@ -1,481 +0,0 @@
1
- import { LayerNode } from "@soraacode/core/effect/layer-node"
2
- import { httpClient } from "@soraacode/core/effect/app-node-platform"
3
- import { Cache, Clock, Duration, Effect, Layer, Option, Schema, SchemaGetter, Context } from "effect"
4
- import { serviceUse } from "@soraacode/core/effect/service-use"
5
- import {
6
- FetchHttpClient,
7
- HttpClient,
8
- HttpClientError,
9
- HttpClientRequest,
10
- HttpClientResponse,
11
- } from "effect/unstable/http"
12
-
13
- import { withTransientReadRetry } from "@/util/effect-http-client"
14
- import { AccountRepo, type AccountRow } from "./repo"
15
- import { normalizeServerUrl } from "./url"
16
- import {
17
- type AccountError,
18
- AccessToken,
19
- AccountID,
20
- DeviceCode,
21
- Info,
22
- RefreshToken,
23
- AccountServiceError,
24
- AccountTransportError,
25
- Login,
26
- Org,
27
- OrgID,
28
- PollDenied,
29
- PollError,
30
- PollExpired,
31
- PollPending,
32
- type PollResult,
33
- PollSlow,
34
- PollSuccess,
35
- UserCode,
36
- } from "./schema"
37
-
38
- export {
39
- AccountID,
40
- type AccountError,
41
- AccountRepoError,
42
- AccountServiceError,
43
- AccountTransportError,
44
- AccessToken,
45
- RefreshToken,
46
- DeviceCode,
47
- UserCode,
48
- Info,
49
- Org,
50
- OrgID,
51
- Login,
52
- PollSuccess,
53
- PollPending,
54
- PollSlow,
55
- PollExpired,
56
- PollDenied,
57
- PollError,
58
- PollResult,
59
- } from "./schema"
60
-
61
- export type AccountOrgs = {
62
- account: Info
63
- orgs: readonly Org[]
64
- }
65
-
66
- export type ActiveOrg = {
67
- account: Info
68
- org: Org
69
- }
70
-
71
- class RemoteConfig extends Schema.Class<RemoteConfig>("RemoteConfig")({
72
- config: Schema.Record(Schema.String, Schema.Json),
73
- }) {}
74
-
75
- const DurationFromSeconds = Schema.Number.pipe(
76
- Schema.decodeTo(Schema.Duration, {
77
- decode: SchemaGetter.transform((n) => Duration.seconds(n)),
78
- encode: SchemaGetter.transform((d) => Duration.toSeconds(d)),
79
- }),
80
- )
81
-
82
- class TokenRefresh extends Schema.Class<TokenRefresh>("TokenRefresh")({
83
- access_token: AccessToken,
84
- refresh_token: RefreshToken,
85
- expires_in: DurationFromSeconds,
86
- }) {}
87
-
88
- class DeviceAuth extends Schema.Class<DeviceAuth>("DeviceAuth")({
89
- device_code: DeviceCode,
90
- user_code: UserCode,
91
- verification_uri_complete: Schema.String,
92
- expires_in: DurationFromSeconds,
93
- interval: DurationFromSeconds,
94
- }) {}
95
-
96
- class DeviceTokenSuccess extends Schema.Class<DeviceTokenSuccess>("DeviceTokenSuccess")({
97
- access_token: AccessToken,
98
- refresh_token: RefreshToken,
99
- token_type: Schema.Literal("Bearer"),
100
- expires_in: DurationFromSeconds,
101
- }) {}
102
-
103
- class DeviceTokenError extends Schema.Class<DeviceTokenError>("DeviceTokenError")({
104
- error: Schema.String,
105
- error_description: Schema.String,
106
- }) {
107
- toPollResult(): PollResult {
108
- if (this.error === "authorization_pending") return new PollPending()
109
- if (this.error === "slow_down") return new PollSlow()
110
- if (this.error === "expired_token") return new PollExpired()
111
- if (this.error === "access_denied") return new PollDenied()
112
- return new PollError({ cause: this.error })
113
- }
114
- }
115
-
116
- const DeviceToken = Schema.Union([DeviceTokenSuccess, DeviceTokenError])
117
-
118
- class User extends Schema.Class<User>("User")({
119
- id: AccountID,
120
- email: Schema.String,
121
- }) {}
122
-
123
- class ClientId extends Schema.Class<ClientId>("ClientId")({ client_id: Schema.String }) {}
124
-
125
- class DeviceTokenRequest extends Schema.Class<DeviceTokenRequest>("DeviceTokenRequest")({
126
- grant_type: Schema.String,
127
- device_code: DeviceCode,
128
- client_id: Schema.String,
129
- }) {}
130
-
131
- class TokenRefreshRequest extends Schema.Class<TokenRefreshRequest>("TokenRefreshRequest")({
132
- grant_type: Schema.String,
133
- refresh_token: RefreshToken,
134
- client_id: Schema.String,
135
- }) {}
136
-
137
- const clientId = "opencode-cli"
138
- const eagerRefreshThreshold = Duration.minutes(5)
139
- const eagerRefreshThresholdMs = Duration.toMillis(eagerRefreshThreshold)
140
-
141
- const isTokenFresh = (tokenExpiry: number | null, now: number) =>
142
- tokenExpiry != null && tokenExpiry > now + eagerRefreshThresholdMs
143
-
144
- const mapAccountServiceError =
145
- (message = "Account service operation failed") =>
146
- <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, AccountError, R> =>
147
- effect.pipe(Effect.mapError((cause) => accountErrorFromCause(cause, message)))
148
-
149
- const accountErrorFromCause = (cause: unknown, message: string): AccountError => {
150
- if (cause instanceof AccountServiceError || cause instanceof AccountTransportError) {
151
- return cause
152
- }
153
-
154
- if (HttpClientError.isHttpClientError(cause)) {
155
- switch (cause.reason._tag) {
156
- case "TransportError": {
157
- return AccountTransportError.fromHttpClientError(cause.reason)
158
- }
159
- default: {
160
- return new AccountServiceError({ message, cause })
161
- }
162
- }
163
- }
164
-
165
- return new AccountServiceError({ message, cause })
166
- }
167
-
168
- export interface Interface {
169
- readonly active: () => Effect.Effect<Option.Option<Info>, AccountError>
170
- readonly activeOrg: () => Effect.Effect<Option.Option<ActiveOrg>, AccountError>
171
- readonly list: () => Effect.Effect<Info[], AccountError>
172
- readonly orgsByAccount: () => Effect.Effect<readonly AccountOrgs[], AccountError>
173
- readonly remove: (accountID: AccountID) => Effect.Effect<void, AccountError>
174
- readonly use: (accountID: AccountID, orgID: Option.Option<OrgID>) => Effect.Effect<void, AccountError>
175
- readonly orgs: (accountID: AccountID) => Effect.Effect<readonly Org[], AccountError>
176
- readonly config: (
177
- accountID: AccountID,
178
- orgID: OrgID,
179
- ) => Effect.Effect<Option.Option<Record<string, unknown>>, AccountError>
180
- readonly token: (accountID: AccountID) => Effect.Effect<Option.Option<AccessToken>, AccountError>
181
- readonly login: (url: string) => Effect.Effect<Login, AccountError>
182
- readonly poll: (input: Login) => Effect.Effect<PollResult, AccountError>
183
- }
184
-
185
- export class Service extends Context.Service<Service, Interface>()("@opencode/Account") {}
186
-
187
- export const use = serviceUse(Service)
188
-
189
- const layer: Layer.Layer<Service, never, AccountRepo.Service | HttpClient.HttpClient> = Layer.effect(
190
- Service,
191
- Effect.gen(function* () {
192
- const repo = yield* AccountRepo.Service
193
- const http = yield* HttpClient.HttpClient
194
- const httpRead = withTransientReadRetry(http)
195
- const httpOk = HttpClient.filterStatusOk(http)
196
- const httpReadOk = HttpClient.filterStatusOk(httpRead)
197
-
198
- const executeRead = (request: HttpClientRequest.HttpClientRequest) =>
199
- httpRead.execute(request).pipe(mapAccountServiceError("HTTP request failed"))
200
-
201
- const executeReadOk = (request: HttpClientRequest.HttpClientRequest) =>
202
- httpReadOk.execute(request).pipe(mapAccountServiceError("HTTP request failed"))
203
-
204
- const executeEffectOk = <E>(request: Effect.Effect<HttpClientRequest.HttpClientRequest, E>) =>
205
- request.pipe(
206
- Effect.flatMap((req) => httpOk.execute(req)),
207
- mapAccountServiceError("HTTP request failed"),
208
- )
209
-
210
- const executeEffect = <E>(request: Effect.Effect<HttpClientRequest.HttpClientRequest, E>) =>
211
- request.pipe(
212
- Effect.flatMap((req) => http.execute(req)),
213
- mapAccountServiceError("HTTP request failed"),
214
- )
215
-
216
- const refreshToken = Effect.fnUntraced(function* (row: AccountRow) {
217
- const now = yield* Clock.currentTimeMillis
218
-
219
- const response = yield* executeEffectOk(
220
- HttpClientRequest.post(`${row.url}/auth/device/token`).pipe(
221
- HttpClientRequest.acceptJson,
222
- HttpClientRequest.schemaBodyJson(TokenRefreshRequest)(
223
- new TokenRefreshRequest({
224
- grant_type: "refresh_token",
225
- refresh_token: row.refresh_token,
226
- client_id: clientId,
227
- }),
228
- ),
229
- ),
230
- )
231
-
232
- const parsed = yield* HttpClientResponse.schemaBodyJson(TokenRefresh)(response).pipe(
233
- mapAccountServiceError("Failed to decode response"),
234
- )
235
-
236
- const expiry = Option.some(now + Duration.toMillis(parsed.expires_in))
237
-
238
- yield* repo.persistToken({
239
- accountID: row.id,
240
- accessToken: parsed.access_token,
241
- refreshToken: parsed.refresh_token,
242
- expiry,
243
- })
244
-
245
- return parsed.access_token
246
- })
247
-
248
- const refreshTokenCache = yield* Cache.make<AccountID, AccessToken, AccountError>({
249
- capacity: Number.POSITIVE_INFINITY,
250
- timeToLive: Duration.zero,
251
- lookup: Effect.fnUntraced(function* (accountID) {
252
- const maybeAccount = yield* repo.getRow(accountID)
253
- if (Option.isNone(maybeAccount)) {
254
- return yield* Effect.fail(new AccountServiceError({ message: "Account not found during token refresh" }))
255
- }
256
-
257
- const account = maybeAccount.value
258
- const now = yield* Clock.currentTimeMillis
259
- if (isTokenFresh(account.token_expiry, now)) {
260
- return account.access_token
261
- }
262
-
263
- return yield* refreshToken(account)
264
- }),
265
- })
266
-
267
- const resolveToken = Effect.fnUntraced(function* (row: AccountRow) {
268
- const now = yield* Clock.currentTimeMillis
269
- if (isTokenFresh(row.token_expiry, now)) {
270
- return row.access_token
271
- }
272
-
273
- return yield* Cache.get(refreshTokenCache, row.id)
274
- })
275
-
276
- const resolveAccess = Effect.fnUntraced(function* (accountID: AccountID) {
277
- const maybeAccount = yield* repo.getRow(accountID)
278
- if (Option.isNone(maybeAccount)) return Option.none()
279
-
280
- const account = maybeAccount.value
281
- const accessToken = yield* resolveToken(account)
282
- return Option.some({ account, accessToken })
283
- })
284
-
285
- const fetchOrgs = Effect.fnUntraced(function* (url: string, accessToken: AccessToken) {
286
- const response = yield* executeReadOk(
287
- HttpClientRequest.get(`${url}/api/orgs`).pipe(
288
- HttpClientRequest.acceptJson,
289
- HttpClientRequest.bearerToken(accessToken),
290
- ),
291
- )
292
-
293
- return yield* HttpClientResponse.schemaBodyJson(Schema.Array(Org))(response).pipe(
294
- mapAccountServiceError("Failed to decode response"),
295
- )
296
- })
297
-
298
- const fetchUser = Effect.fnUntraced(function* (url: string, accessToken: AccessToken) {
299
- const response = yield* executeReadOk(
300
- HttpClientRequest.get(`${url}/api/user`).pipe(
301
- HttpClientRequest.acceptJson,
302
- HttpClientRequest.bearerToken(accessToken),
303
- ),
304
- )
305
-
306
- return yield* HttpClientResponse.schemaBodyJson(User)(response).pipe(
307
- mapAccountServiceError("Failed to decode response"),
308
- )
309
- })
310
-
311
- const token = Effect.fn("Account.token")((accountID: AccountID) =>
312
- resolveAccess(accountID).pipe(Effect.map(Option.map((r) => r.accessToken))),
313
- )
314
-
315
- const activeOrg = Effect.fn("Account.activeOrg")(function* () {
316
- const activeAccount = yield* repo.active()
317
- if (Option.isNone(activeAccount)) return Option.none<ActiveOrg>()
318
-
319
- const account = activeAccount.value
320
- if (!account.active_org_id) return Option.none<ActiveOrg>()
321
-
322
- const accountOrgs = yield* orgs(account.id)
323
- const org = accountOrgs.find((item) => item.id === account.active_org_id)
324
- if (!org) return Option.none<ActiveOrg>()
325
-
326
- return Option.some({ account, org })
327
- })
328
-
329
- const orgsByAccount = Effect.fn("Account.orgsByAccount")(function* () {
330
- const accounts = yield* repo.list()
331
- return yield* Effect.forEach(
332
- accounts,
333
- (account) =>
334
- orgs(account.id).pipe(
335
- Effect.catch(() => Effect.succeed([] as readonly Org[])),
336
- Effect.map((orgs) => ({ account, orgs })),
337
- ),
338
- { concurrency: 3 },
339
- )
340
- })
341
-
342
- const orgs = Effect.fn("Account.orgs")(function* (accountID: AccountID) {
343
- const resolved = yield* resolveAccess(accountID)
344
- if (Option.isNone(resolved)) return []
345
-
346
- const { account, accessToken } = resolved.value
347
-
348
- return yield* fetchOrgs(account.url, accessToken)
349
- })
350
-
351
- const remove = Effect.fn("Account.remove")(function* (accountID: AccountID) {
352
- const active = yield* repo.active()
353
- yield* repo.remove(accountID)
354
- if (Option.isNone(active) || active.value.id !== accountID) return
355
-
356
- const next = (yield* orgsByAccount()).flatMap((group) =>
357
- group.orgs.map((org) => ({ accountID: group.account.id, orgID: org.id })),
358
- )[0]
359
- if (!next) return
360
- yield* repo.use(next.accountID, Option.some(next.orgID))
361
- })
362
-
363
- const config = Effect.fn("Account.config")(function* (accountID: AccountID, orgID: OrgID) {
364
- const resolved = yield* resolveAccess(accountID)
365
- if (Option.isNone(resolved)) return Option.none()
366
-
367
- const { account, accessToken } = resolved.value
368
-
369
- const response = yield* executeRead(
370
- HttpClientRequest.get(`${account.url}/api/config`).pipe(
371
- HttpClientRequest.acceptJson,
372
- HttpClientRequest.bearerToken(accessToken),
373
- HttpClientRequest.setHeaders({ "x-org-id": orgID }),
374
- ),
375
- )
376
-
377
- if (response.status === 404) return Option.none()
378
-
379
- const ok = yield* HttpClientResponse.filterStatusOk(response).pipe(mapAccountServiceError())
380
-
381
- const parsed = yield* HttpClientResponse.schemaBodyJson(RemoteConfig)(ok).pipe(
382
- mapAccountServiceError("Failed to decode response"),
383
- )
384
- return Option.some(parsed.config)
385
- })
386
-
387
- const login = Effect.fn("Account.login")(function* (server: string) {
388
- const normalizedServer = normalizeServerUrl(server)
389
- const response = yield* executeEffectOk(
390
- HttpClientRequest.post(`${normalizedServer}/auth/device/code`).pipe(
391
- HttpClientRequest.acceptJson,
392
- HttpClientRequest.schemaBodyJson(ClientId)(new ClientId({ client_id: clientId })),
393
- ),
394
- )
395
-
396
- const parsed = yield* HttpClientResponse.schemaBodyJson(DeviceAuth)(response).pipe(
397
- mapAccountServiceError("Failed to decode response"),
398
- )
399
- const verification = yield* Effect.try({
400
- try: () => {
401
- const url = new URL(parsed.verification_uri_complete, `${normalizedServer}/`)
402
- if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("expected HTTP(S)")
403
- return url.href
404
- },
405
- catch: (cause) => new AccountServiceError({ message: "Invalid device verification URL", cause }),
406
- })
407
- return new Login({
408
- code: parsed.device_code,
409
- user: parsed.user_code,
410
- url: verification,
411
- server: normalizedServer,
412
- expiry: parsed.expires_in,
413
- interval: parsed.interval,
414
- })
415
- })
416
-
417
- const poll = Effect.fn("Account.poll")(function* (input: Login) {
418
- const response = yield* executeEffect(
419
- HttpClientRequest.post(`${input.server}/auth/device/token`).pipe(
420
- HttpClientRequest.acceptJson,
421
- HttpClientRequest.schemaBodyJson(DeviceTokenRequest)(
422
- new DeviceTokenRequest({
423
- grant_type: "urn:ietf:params:oauth:grant-type:device_code",
424
- device_code: input.code,
425
- client_id: clientId,
426
- }),
427
- ),
428
- ),
429
- )
430
-
431
- const parsed = yield* HttpClientResponse.schemaBodyJson(DeviceToken)(response).pipe(
432
- mapAccountServiceError("Failed to decode response"),
433
- )
434
-
435
- if (parsed instanceof DeviceTokenError) return parsed.toPollResult()
436
- const accessToken = parsed.access_token
437
-
438
- const user = fetchUser(input.server, accessToken)
439
- const orgs = fetchOrgs(input.server, accessToken)
440
-
441
- const [account, remoteOrgs] = yield* Effect.all([user, orgs], { concurrency: 2 })
442
-
443
- // TODO: When there are multiple orgs, let the user choose
444
- const firstOrgID = remoteOrgs.length > 0 ? Option.some(remoteOrgs[0].id) : Option.none<OrgID>()
445
-
446
- const now = yield* Clock.currentTimeMillis
447
- const expiry = now + Duration.toMillis(parsed.expires_in)
448
- const refreshToken = parsed.refresh_token
449
-
450
- yield* repo.persistAccount({
451
- id: account.id,
452
- email: account.email,
453
- url: input.server,
454
- accessToken,
455
- refreshToken,
456
- expiry,
457
- orgID: firstOrgID,
458
- })
459
-
460
- return new PollSuccess({ email: account.email })
461
- })
462
-
463
- return Service.of({
464
- active: repo.active,
465
- activeOrg,
466
- list: repo.list,
467
- orgsByAccount,
468
- remove,
469
- use: repo.use,
470
- orgs,
471
- config,
472
- token,
473
- login,
474
- poll,
475
- })
476
- }),
477
- )
478
-
479
- export const node = LayerNode.make({ service: Service, layer: layer, deps: [AccountRepo.node, httpClient] })
480
-
481
- export * as Account from "./account"