soraacode 0.1.7 → 0.2.1

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 (352) hide show
  1. package/LICENSE +21 -0
  2. package/bin/soraacode.exe +10 -0
  3. package/package.json +17 -114
  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/specs/effect/error-boundaries-plan.md +0 -235
  28. package/specs/effect/errors.md +0 -207
  29. package/specs/effect/facades.md +0 -218
  30. package/specs/effect/guide.md +0 -247
  31. package/specs/effect/instance-context.md +0 -13
  32. package/specs/effect/loose-ends.md +0 -30
  33. package/specs/effect/migration.md +0 -62
  34. package/specs/effect/routes.md +0 -61
  35. package/specs/effect/schema.md +0 -88
  36. package/specs/effect/server-package.md +0 -58
  37. package/specs/effect/todo.md +0 -241
  38. package/specs/effect/tools.md +0 -88
  39. package/specs/openapi-translation-cleanup.md +0 -204
  40. package/specs/tui-plugins.md +0 -544
  41. package/specs/v2/api.ts +0 -67
  42. package/specs/v2/message-shape.md +0 -136
  43. package/specs/v2/notifications.md +0 -13
  44. package/specs/v2/tui-command-shim.md +0 -67
  45. package/src/account/account.ts +0 -481
  46. package/src/account/repo.ts +0 -171
  47. package/src/account/schema.ts +0 -99
  48. package/src/account/url.ts +0 -8
  49. package/src/acp/agent.ts +0 -95
  50. package/src/acp/config-option.ts +0 -206
  51. package/src/acp/content.ts +0 -269
  52. package/src/acp/directory.ts +0 -212
  53. package/src/acp/error.ts +0 -97
  54. package/src/acp/event.ts +0 -421
  55. package/src/acp/permission.ts +0 -254
  56. package/src/acp/profile.ts +0 -42
  57. package/src/acp/service.ts +0 -1226
  58. package/src/acp/session.ts +0 -232
  59. package/src/acp/tool.ts +0 -364
  60. package/src/acp/usage.ts +0 -243
  61. package/src/agent/agent.ts +0 -450
  62. package/src/agent/generate.txt +0 -75
  63. package/src/agent/prompt/compaction.txt +0 -5
  64. package/src/agent/prompt/explore.txt +0 -18
  65. package/src/agent/prompt/summary.txt +0 -11
  66. package/src/agent/prompt/title.txt +0 -44
  67. package/src/agent/subagent-permissions.ts +0 -27
  68. package/src/audio.d.ts +0 -14
  69. package/src/auth/index.ts +0 -97
  70. package/src/background/job.ts +0 -37
  71. package/src/bus/global.ts +0 -22
  72. package/src/cli/bootstrap.ts +0 -11
  73. package/src/cli/cmd/cmd.ts +0 -7
  74. package/src/cli/cmd/serve.ts +0 -24
  75. package/src/cli/effect/prompt.ts +0 -37
  76. package/src/cli/effect-cmd.ts +0 -96
  77. package/src/cli/error.ts +0 -130
  78. package/src/cli/heap.ts +0 -45
  79. package/src/cli/logo.ts +0 -2
  80. package/src/cli/network.ts +0 -80
  81. package/src/cli/ui.ts +0 -132
  82. package/src/command/index.ts +0 -177
  83. package/src/command/template/initialize.txt +0 -66
  84. package/src/command/template/review.txt +0 -101
  85. package/src/config/agent.ts +0 -59
  86. package/src/config/command.ts +0 -39
  87. package/src/config/config.ts +0 -707
  88. package/src/config/entry-name.ts +0 -19
  89. package/src/config/managed.ts +0 -69
  90. package/src/config/markdown.ts +0 -36
  91. package/src/config/parse.ts +0 -61
  92. package/src/config/paths.ts +0 -45
  93. package/src/config/plugin.ts +0 -79
  94. package/src/config/v2-compat.ts +0 -449
  95. package/src/config/variable.ts +0 -91
  96. package/src/control-plane/adapters/index.ts +0 -41
  97. package/src/control-plane/adapters/worktree.ts +0 -96
  98. package/src/control-plane/dev/README.md +0 -19
  99. package/src/control-plane/dev/debug-workspace-plugin.ts +0 -73
  100. package/src/control-plane/types.ts +0 -59
  101. package/src/control-plane/util.ts +0 -39
  102. package/src/control-plane/workspace-adapter-runtime.ts +0 -51
  103. package/src/control-plane/workspace-context.ts +0 -26
  104. package/src/control-plane/workspace.ts +0 -966
  105. package/src/effect/app-node-builder-v1.ts +0 -12
  106. package/src/effect/app-runtime.ts +0 -135
  107. package/src/effect/bootstrap-runtime.ts +0 -19
  108. package/src/effect/bridge.ts +0 -84
  109. package/src/effect/config-service.ts +0 -67
  110. package/src/effect/instance-ref.ts +0 -11
  111. package/src/effect/instance-registry.ts +0 -12
  112. package/src/effect/instance-state.ts +0 -69
  113. package/src/effect/promise.ts +0 -17
  114. package/src/effect/run-service.ts +0 -47
  115. package/src/effect/runner.ts +0 -217
  116. package/src/effect/runtime-flags.ts +0 -78
  117. package/src/env/index.ts +0 -41
  118. package/src/event-manifest.ts +0 -3
  119. package/src/event-v2-bridge.ts +0 -71
  120. package/src/format/formatter.ts +0 -404
  121. package/src/format/index.ts +0 -203
  122. package/src/git/index.ts +0 -348
  123. package/src/id/id.ts +0 -80
  124. package/src/ide/index.ts +0 -54
  125. package/src/image/image.ts +0 -172
  126. package/src/index-headless.ts +0 -89
  127. package/src/index.ts +0 -93
  128. package/src/installation/index.ts +0 -336
  129. package/src/lsp/client.ts +0 -650
  130. package/src/lsp/diagnostic.ts +0 -29
  131. package/src/lsp/language.ts +0 -121
  132. package/src/lsp/launch.ts +0 -21
  133. package/src/lsp/lsp.ts +0 -507
  134. package/src/lsp/server.ts +0 -1983
  135. package/src/markdown.d.ts +0 -4
  136. package/src/mcp/auth.ts +0 -163
  137. package/src/mcp/browser.ts +0 -37
  138. package/src/mcp/catalog.ts +0 -170
  139. package/src/mcp/index.ts +0 -1004
  140. package/src/mcp/oauth-callback.ts +0 -194
  141. package/src/mcp/oauth-provider.ts +0 -259
  142. package/src/node.ts +0 -4
  143. package/src/patch/index.ts +0 -686
  144. package/src/permission/arity.ts +0 -163
  145. package/src/permission/evaluate.ts +0 -1
  146. package/src/permission/index.ts +0 -223
  147. package/src/plugin/github-copilot/copilot.ts +0 -415
  148. package/src/plugin/github-copilot/models.ts +0 -261
  149. package/src/plugin/index.ts +0 -298
  150. package/src/plugin/install.ts +0 -439
  151. package/src/plugin/loader.ts +0 -237
  152. package/src/plugin/meta.ts +0 -188
  153. package/src/plugin/openai/README.md +0 -31
  154. package/src/plugin/openai/codex.ts +0 -575
  155. package/src/plugin/openai/ws-pool.ts +0 -271
  156. package/src/plugin/openai/ws.ts +0 -383
  157. package/src/plugin/pty-environment.ts +0 -24
  158. package/src/plugin/shared.ts +0 -323
  159. package/src/project/bootstrap-service.ts +0 -9
  160. package/src/project/bootstrap.ts +0 -58
  161. package/src/project/instance-context.ts +0 -24
  162. package/src/project/instance-runtime.ts +0 -16
  163. package/src/project/instance-store.ts +0 -213
  164. package/src/project/project.ts +0 -483
  165. package/src/project/vcs.ts +0 -423
  166. package/src/provider/auth.ts +0 -229
  167. package/src/provider/error.ts +0 -195
  168. package/src/provider/model-status.ts +0 -8
  169. package/src/provider/provider.ts +0 -1955
  170. package/src/provider/transform.ts +0 -1923
  171. package/src/question/index.ts +0 -161
  172. package/src/question/schema.ts +0 -4
  173. package/src/server/auth.ts +0 -48
  174. package/src/server/event.ts +0 -10
  175. package/src/server/global-lifecycle.ts +0 -28
  176. package/src/server/mdns.ts +0 -47
  177. package/src/server/proxy-util.ts +0 -48
  178. package/src/server/routes/instance/httpapi/AGENTS.md +0 -39
  179. package/src/server/routes/instance/httpapi/api.ts +0 -97
  180. package/src/server/routes/instance/httpapi/errors.ts +0 -193
  181. package/src/server/routes/instance/httpapi/groups/config.ts +0 -65
  182. package/src/server/routes/instance/httpapi/groups/control-plane.ts +0 -35
  183. package/src/server/routes/instance/httpapi/groups/control.ts +0 -76
  184. package/src/server/routes/instance/httpapi/groups/event.ts +0 -29
  185. package/src/server/routes/instance/httpapi/groups/experimental.ts +0 -275
  186. package/src/server/routes/instance/httpapi/groups/file.ts +0 -185
  187. package/src/server/routes/instance/httpapi/groups/global.ts +0 -139
  188. package/src/server/routes/instance/httpapi/groups/instance.ts +0 -206
  189. package/src/server/routes/instance/httpapi/groups/mcp.ts +0 -156
  190. package/src/server/routes/instance/httpapi/groups/metadata.ts +0 -18
  191. package/src/server/routes/instance/httpapi/groups/permission.ts +0 -61
  192. package/src/server/routes/instance/httpapi/groups/project-copy.ts +0 -32
  193. package/src/server/routes/instance/httpapi/groups/project.ts +0 -93
  194. package/src/server/routes/instance/httpapi/groups/provider.ts +0 -101
  195. package/src/server/routes/instance/httpapi/groups/pty.ts +0 -172
  196. package/src/server/routes/instance/httpapi/groups/query.ts +0 -12
  197. package/src/server/routes/instance/httpapi/groups/question.ts +0 -74
  198. package/src/server/routes/instance/httpapi/groups/session.ts +0 -462
  199. package/src/server/routes/instance/httpapi/groups/sync.ts +0 -113
  200. package/src/server/routes/instance/httpapi/groups/tui.ts +0 -208
  201. package/src/server/routes/instance/httpapi/groups/workspace.ts +0 -141
  202. package/src/server/routes/instance/httpapi/handlers/config.ts +0 -34
  203. package/src/server/routes/instance/httpapi/handlers/control-plane.ts +0 -37
  204. package/src/server/routes/instance/httpapi/handlers/control.ts +0 -43
  205. package/src/server/routes/instance/httpapi/handlers/event.ts +0 -99
  206. package/src/server/routes/instance/httpapi/handlers/experimental.ts +0 -193
  207. package/src/server/routes/instance/httpapi/handlers/file.ts +0 -139
  208. package/src/server/routes/instance/httpapi/handlers/global.ts +0 -126
  209. package/src/server/routes/instance/httpapi/handlers/instance.ts +0 -110
  210. package/src/server/routes/instance/httpapi/handlers/mcp.ts +0 -111
  211. package/src/server/routes/instance/httpapi/handlers/permission.ts +0 -41
  212. package/src/server/routes/instance/httpapi/handlers/project-copy.ts +0 -83
  213. package/src/server/routes/instance/httpapi/handlers/project.ts +0 -63
  214. package/src/server/routes/instance/httpapi/handlers/provider.ts +0 -116
  215. package/src/server/routes/instance/httpapi/handlers/pty.ts +0 -273
  216. package/src/server/routes/instance/httpapi/handlers/question.ts +0 -54
  217. package/src/server/routes/instance/httpapi/handlers/session-errors.ts +0 -21
  218. package/src/server/routes/instance/httpapi/handlers/session.ts +0 -442
  219. package/src/server/routes/instance/httpapi/handlers/sync.ts +0 -89
  220. package/src/server/routes/instance/httpapi/handlers/tui.ts +0 -131
  221. package/src/server/routes/instance/httpapi/handlers/workspace.ts +0 -102
  222. package/src/server/routes/instance/httpapi/lifecycle.ts +0 -54
  223. package/src/server/routes/instance/httpapi/middleware/authorization.ts +0 -150
  224. package/src/server/routes/instance/httpapi/middleware/compression.ts +0 -64
  225. package/src/server/routes/instance/httpapi/middleware/cors-vary.ts +0 -29
  226. package/src/server/routes/instance/httpapi/middleware/error.ts +0 -44
  227. package/src/server/routes/instance/httpapi/middleware/fence.ts +0 -25
  228. package/src/server/routes/instance/httpapi/middleware/instance-context.ts +0 -43
  229. package/src/server/routes/instance/httpapi/middleware/proxy.ts +0 -131
  230. package/src/server/routes/instance/httpapi/middleware/schema-error.ts +0 -41
  231. package/src/server/routes/instance/httpapi/middleware/workspace-routing.ts +0 -250
  232. package/src/server/routes/instance/httpapi/public.ts +0 -537
  233. package/src/server/routes/instance/httpapi/server.ts +0 -325
  234. package/src/server/routes/instance/httpapi/websocket-tracker.ts +0 -60
  235. package/src/server/server.ts +0 -224
  236. package/src/server/shared/fence.ts +0 -60
  237. package/src/server/shared/pty-ticket.ts +0 -15
  238. package/src/server/shared/public-ui.ts +0 -12
  239. package/src/server/shared/tui-control.ts +0 -28
  240. package/src/server/shared/ui.ts +0 -108
  241. package/src/server/shared/workspace-routing.ts +0 -45
  242. package/src/server/tui-event.ts +0 -1
  243. package/src/session/compaction.ts +0 -608
  244. package/src/session/instruction.ts +0 -237
  245. package/src/session/llm/AGENTS.md +0 -90
  246. package/src/session/llm/ai-sdk.ts +0 -291
  247. package/src/session/llm/native-request.ts +0 -196
  248. package/src/session/llm/native-runtime.ts +0 -195
  249. package/src/session/llm/request.ts +0 -226
  250. package/src/session/llm.ts +0 -390
  251. package/src/session/message-error.ts +0 -14
  252. package/src/session/message-v2.ts +0 -742
  253. package/src/session/message.ts +0 -148
  254. package/src/session/overflow.ts +0 -34
  255. package/src/session/processor.ts +0 -732
  256. package/src/session/prompt/build-switch.txt +0 -5
  257. package/src/session/prompt/default.txt +0 -35
  258. package/src/session/prompt/plan-mode.txt +0 -70
  259. package/src/session/prompt/plan.txt +0 -26
  260. package/src/session/prompt.ts +0 -1631
  261. package/src/session/reminders.ts +0 -92
  262. package/src/session/retry.ts +0 -209
  263. package/src/session/revert.ts +0 -136
  264. package/src/session/run-state.ts +0 -151
  265. package/src/session/schema.ts +0 -26
  266. package/src/session/session.ts +0 -1016
  267. package/src/session/status.ts +0 -56
  268. package/src/session/summary.ts +0 -160
  269. package/src/session/system.ts +0 -123
  270. package/src/session/todo.ts +0 -74
  271. package/src/session/tools.ts +0 -590
  272. package/src/share/session.ts +0 -58
  273. package/src/share/share-next.ts +0 -371
  274. package/src/skill/discovery.ts +0 -140
  275. package/src/skill/index.ts +0 -354
  276. package/src/snapshot/index.ts +0 -807
  277. package/src/sql.d.ts +0 -4
  278. package/src/storage/schema.ts +0 -5
  279. package/src/storage/storage.ts +0 -327
  280. package/src/sync/README.md +0 -179
  281. package/src/sync/schema.ts +0 -11
  282. package/src/temporary.ts +0 -31
  283. package/src/tool/apply_patch.ts +0 -313
  284. package/src/tool/apply_patch.txt +0 -14
  285. package/src/tool/code-mode.ts +0 -310
  286. package/src/tool/edit.ts +0 -737
  287. package/src/tool/edit.txt +0 -13
  288. package/src/tool/external-directory.ts +0 -49
  289. package/src/tool/glob.ts +0 -76
  290. package/src/tool/glob.txt +0 -7
  291. package/src/tool/grep.ts +0 -115
  292. package/src/tool/grep.txt +0 -7
  293. package/src/tool/invalid.ts +0 -21
  294. package/src/tool/json-schema.ts +0 -164
  295. package/src/tool/lsp.ts +0 -113
  296. package/src/tool/lsp.txt +0 -1
  297. package/src/tool/mcp-websearch.ts +0 -96
  298. package/src/tool/plan-enter.txt +0 -1
  299. package/src/tool/plan-exit.txt +0 -1
  300. package/src/tool/plan.ts +0 -79
  301. package/src/tool/question.ts +0 -44
  302. package/src/tool/question.txt +0 -1
  303. package/src/tool/read.ts +0 -386
  304. package/src/tool/read.txt +0 -13
  305. package/src/tool/registry.ts +0 -455
  306. package/src/tool/schema.ts +0 -14
  307. package/src/tool/shell/id.ts +0 -19
  308. package/src/tool/shell/prompt.ts +0 -293
  309. package/src/tool/shell/shell.txt +0 -21
  310. package/src/tool/shell.ts +0 -645
  311. package/src/tool/skill.ts +0 -70
  312. package/src/tool/skill.txt +0 -1
  313. package/src/tool/task.ts +0 -371
  314. package/src/tool/task.txt +0 -11
  315. package/src/tool/todo.ts +0 -46
  316. package/src/tool/todowrite.txt +0 -11
  317. package/src/tool/tool.ts +0 -183
  318. package/src/tool/truncate.ts +0 -156
  319. package/src/tool/truncation-dir.ts +0 -4
  320. package/src/tool/webfetch.ts +0 -192
  321. package/src/tool/webfetch.txt +0 -9
  322. package/src/tool/websearch.ts +0 -143
  323. package/src/tool/websearch.txt +0 -9
  324. package/src/tool/write.ts +0 -104
  325. package/src/tool/write.txt +0 -11
  326. package/src/util/archive.ts +0 -17
  327. package/src/util/bom.ts +0 -27
  328. package/src/util/data-url.ts +0 -9
  329. package/src/util/defer.ts +0 -10
  330. package/src/util/effect-http-client.ts +0 -11
  331. package/src/util/error.ts +0 -16
  332. package/src/util/filesystem.ts +0 -251
  333. package/src/util/html.ts +0 -8
  334. package/src/util/iife.ts +0 -3
  335. package/src/util/lazy.ts +0 -20
  336. package/src/util/local-context.ts +0 -25
  337. package/src/util/locale.ts +0 -4
  338. package/src/util/media.ts +0 -26
  339. package/src/util/process.ts +0 -177
  340. package/src/util/proxy-env.ts +0 -72
  341. package/src/util/queue.ts +0 -32
  342. package/src/util/record.ts +0 -3
  343. package/src/util/repository.ts +0 -232
  344. package/src/util/rpc.ts +0 -66
  345. package/src/util/signal.ts +0 -12
  346. package/src/util/timeout.ts +0 -13
  347. package/src/util/token.ts +0 -1
  348. package/src/util/wildcard.ts +0 -59
  349. package/src/worktree/index.ts +0 -623
  350. package/sst-env.d.ts +0 -10
  351. package/tsconfig.headless.json +0 -13
  352. 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"