reigncode-app 1.3.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 (300) hide show
  1. package/AGENTS.md +30 -0
  2. package/Dockerfile +21 -0
  3. package/README.md +51 -0
  4. package/bunfig.toml +3 -0
  5. package/create-effect-simplification-spec.md +515 -0
  6. package/e2e/AGENTS.md +226 -0
  7. package/e2e/actions.ts +1018 -0
  8. package/e2e/app/home.spec.ts +24 -0
  9. package/e2e/app/navigation.spec.ts +10 -0
  10. package/e2e/app/palette.spec.ts +20 -0
  11. package/e2e/app/server-default.spec.ts +58 -0
  12. package/e2e/app/session.spec.ts +16 -0
  13. package/e2e/app/titlebar-history.spec.ts +120 -0
  14. package/e2e/commands/input-focus.spec.ts +15 -0
  15. package/e2e/commands/panels.spec.ts +33 -0
  16. package/e2e/commands/tab-close.spec.ts +32 -0
  17. package/e2e/files/file-open.spec.ts +31 -0
  18. package/e2e/files/file-tree.spec.ts +56 -0
  19. package/e2e/files/file-viewer.spec.ts +156 -0
  20. package/e2e/fixtures.ts +154 -0
  21. package/e2e/models/model-picker.spec.ts +48 -0
  22. package/e2e/models/models-visibility.spec.ts +61 -0
  23. package/e2e/projects/project-edit.spec.ts +43 -0
  24. package/e2e/projects/projects-close.spec.ts +54 -0
  25. package/e2e/projects/projects-switch.spec.ts +116 -0
  26. package/e2e/projects/workspace-new-session.spec.ts +94 -0
  27. package/e2e/projects/workspaces.spec.ts +375 -0
  28. package/e2e/prompt/context.spec.ts +95 -0
  29. package/e2e/prompt/prompt-async.spec.ts +76 -0
  30. package/e2e/prompt/prompt-drop-file-uri.spec.ts +22 -0
  31. package/e2e/prompt/prompt-drop-file.spec.ts +30 -0
  32. package/e2e/prompt/prompt-history.spec.ts +184 -0
  33. package/e2e/prompt/prompt-mention.spec.ts +26 -0
  34. package/e2e/prompt/prompt-multiline.spec.ts +24 -0
  35. package/e2e/prompt/prompt-shell.spec.ts +62 -0
  36. package/e2e/prompt/prompt-slash-open.spec.ts +22 -0
  37. package/e2e/prompt/prompt-slash-share.spec.ts +64 -0
  38. package/e2e/prompt/prompt-slash-terminal.spec.ts +18 -0
  39. package/e2e/prompt/prompt.spec.ts +55 -0
  40. package/e2e/selectors.ts +75 -0
  41. package/e2e/session/session-child-navigation.spec.ts +37 -0
  42. package/e2e/session/session-composer-dock.spec.ts +530 -0
  43. package/e2e/session/session-model-persistence.spec.ts +359 -0
  44. package/e2e/session/session-review.spec.ts +426 -0
  45. package/e2e/session/session-undo-redo.spec.ts +233 -0
  46. package/e2e/session/session.spec.ts +174 -0
  47. package/e2e/settings/settings-keybinds.spec.ts +389 -0
  48. package/e2e/settings/settings-models.spec.ts +122 -0
  49. package/e2e/settings/settings-providers.spec.ts +136 -0
  50. package/e2e/settings/settings.spec.ts +519 -0
  51. package/e2e/sidebar/sidebar-popover-actions.spec.ts +118 -0
  52. package/e2e/sidebar/sidebar-session-links.spec.ts +30 -0
  53. package/e2e/sidebar/sidebar.spec.ts +40 -0
  54. package/e2e/status/status-popover.spec.ts +94 -0
  55. package/e2e/terminal/terminal-init.spec.ts +28 -0
  56. package/e2e/terminal/terminal-reconnect.spec.ts +46 -0
  57. package/e2e/terminal/terminal-tabs.spec.ts +168 -0
  58. package/e2e/terminal/terminal.spec.ts +18 -0
  59. package/e2e/thinking-level.spec.ts +25 -0
  60. package/e2e/tsconfig.json +9 -0
  61. package/e2e/utils.ts +63 -0
  62. package/happydom.ts +75 -0
  63. package/index.html +23 -0
  64. package/package.json +77 -0
  65. package/playwright.config.ts +45 -0
  66. package/public/_headers +17 -0
  67. package/public/oc-theme-preload.js +35 -0
  68. package/script/e2e-local.ts +180 -0
  69. package/src/addons/serialize.test.ts +319 -0
  70. package/src/addons/serialize.ts +634 -0
  71. package/src/app.tsx +308 -0
  72. package/src/components/debug-bar.tsx +443 -0
  73. package/src/components/dialog-connect-provider.tsx +617 -0
  74. package/src/components/dialog-custom-provider-form.ts +158 -0
  75. package/src/components/dialog-custom-provider.test.ts +80 -0
  76. package/src/components/dialog-custom-provider.tsx +329 -0
  77. package/src/components/dialog-edit-project.tsx +255 -0
  78. package/src/components/dialog-fork.tsx +108 -0
  79. package/src/components/dialog-manage-models.tsx +101 -0
  80. package/src/components/dialog-release-notes.tsx +144 -0
  81. package/src/components/dialog-select-directory.tsx +392 -0
  82. package/src/components/dialog-select-file.tsx +466 -0
  83. package/src/components/dialog-select-mcp.tsx +107 -0
  84. package/src/components/dialog-select-model-unpaid.tsx +137 -0
  85. package/src/components/dialog-select-model.tsx +220 -0
  86. package/src/components/dialog-select-provider.tsx +86 -0
  87. package/src/components/dialog-select-server.tsx +649 -0
  88. package/src/components/dialog-settings.tsx +73 -0
  89. package/src/components/file-tree.test.ts +78 -0
  90. package/src/components/file-tree.tsx +507 -0
  91. package/src/components/link.tsx +26 -0
  92. package/src/components/model-tooltip.tsx +91 -0
  93. package/src/components/prompt-input/attachments.test.ts +44 -0
  94. package/src/components/prompt-input/attachments.ts +201 -0
  95. package/src/components/prompt-input/build-request-parts.test.ts +312 -0
  96. package/src/components/prompt-input/build-request-parts.ts +175 -0
  97. package/src/components/prompt-input/context-items.tsx +88 -0
  98. package/src/components/prompt-input/drag-overlay.tsx +25 -0
  99. package/src/components/prompt-input/editor-dom.test.ts +99 -0
  100. package/src/components/prompt-input/editor-dom.ts +148 -0
  101. package/src/components/prompt-input/files.ts +66 -0
  102. package/src/components/prompt-input/history.test.ts +153 -0
  103. package/src/components/prompt-input/history.ts +256 -0
  104. package/src/components/prompt-input/image-attachments.tsx +58 -0
  105. package/src/components/prompt-input/paste.ts +24 -0
  106. package/src/components/prompt-input/placeholder.test.ts +48 -0
  107. package/src/components/prompt-input/placeholder.ts +15 -0
  108. package/src/components/prompt-input/slash-popover.tsx +141 -0
  109. package/src/components/prompt-input/submit.test.ts +346 -0
  110. package/src/components/prompt-input/submit.ts +579 -0
  111. package/src/components/prompt-input.tsx +1595 -0
  112. package/src/components/server/server-row.tsx +130 -0
  113. package/src/components/session/index.ts +5 -0
  114. package/src/components/session/session-context-breakdown.test.ts +61 -0
  115. package/src/components/session/session-context-breakdown.ts +132 -0
  116. package/src/components/session/session-context-format.ts +20 -0
  117. package/src/components/session/session-context-metrics.test.ts +101 -0
  118. package/src/components/session/session-context-metrics.ts +82 -0
  119. package/src/components/session/session-context-tab.tsx +339 -0
  120. package/src/components/session/session-header.tsx +486 -0
  121. package/src/components/session/session-new-view.tsx +91 -0
  122. package/src/components/session/session-sortable-tab.tsx +70 -0
  123. package/src/components/session/session-sortable-terminal-tab.tsx +193 -0
  124. package/src/components/session-context-usage.tsx +122 -0
  125. package/src/components/settings-general.tsx +585 -0
  126. package/src/components/settings-keybinds.tsx +453 -0
  127. package/src/components/settings-list.tsx +5 -0
  128. package/src/components/settings-models.tsx +137 -0
  129. package/src/components/settings-providers.tsx +251 -0
  130. package/src/components/status-popover.tsx +419 -0
  131. package/src/components/terminal.tsx +653 -0
  132. package/src/components/titlebar-history.test.ts +63 -0
  133. package/src/components/titlebar-history.ts +57 -0
  134. package/src/components/titlebar.tsx +312 -0
  135. package/src/constants/file-picker.ts +89 -0
  136. package/src/context/command-keybind.test.ts +69 -0
  137. package/src/context/command.test.ts +25 -0
  138. package/src/context/command.tsx +437 -0
  139. package/src/context/comments.test.ts +186 -0
  140. package/src/context/comments.tsx +243 -0
  141. package/src/context/file/content-cache.ts +88 -0
  142. package/src/context/file/path.test.ts +360 -0
  143. package/src/context/file/path.ts +151 -0
  144. package/src/context/file/tree-store.ts +170 -0
  145. package/src/context/file/types.ts +41 -0
  146. package/src/context/file/view-cache.ts +146 -0
  147. package/src/context/file/watcher.test.ts +149 -0
  148. package/src/context/file/watcher.ts +53 -0
  149. package/src/context/file-content-eviction-accounting.test.ts +65 -0
  150. package/src/context/file.tsx +280 -0
  151. package/src/context/global-sdk.tsx +232 -0
  152. package/src/context/global-sync/bootstrap.ts +206 -0
  153. package/src/context/global-sync/child-store.test.ts +38 -0
  154. package/src/context/global-sync/child-store.ts +281 -0
  155. package/src/context/global-sync/event-reducer.test.ts +552 -0
  156. package/src/context/global-sync/event-reducer.ts +359 -0
  157. package/src/context/global-sync/eviction.ts +28 -0
  158. package/src/context/global-sync/queue.ts +83 -0
  159. package/src/context/global-sync/session-cache.test.ts +102 -0
  160. package/src/context/global-sync/session-cache.ts +62 -0
  161. package/src/context/global-sync/session-load.ts +25 -0
  162. package/src/context/global-sync/session-prefetch.test.ts +96 -0
  163. package/src/context/global-sync/session-prefetch.ts +100 -0
  164. package/src/context/global-sync/session-trim.test.ts +59 -0
  165. package/src/context/global-sync/session-trim.ts +56 -0
  166. package/src/context/global-sync/types.ts +133 -0
  167. package/src/context/global-sync/utils.ts +25 -0
  168. package/src/context/global-sync.test.ts +122 -0
  169. package/src/context/global-sync.tsx +408 -0
  170. package/src/context/highlights.tsx +233 -0
  171. package/src/context/language.tsx +248 -0
  172. package/src/context/layout-scroll.test.ts +64 -0
  173. package/src/context/layout-scroll.ts +126 -0
  174. package/src/context/layout.test.ts +69 -0
  175. package/src/context/layout.tsx +937 -0
  176. package/src/context/local.tsx +422 -0
  177. package/src/context/model-variant.test.ts +86 -0
  178. package/src/context/model-variant.ts +52 -0
  179. package/src/context/models.tsx +163 -0
  180. package/src/context/notification.tsx +373 -0
  181. package/src/context/permission-auto-respond.test.ts +102 -0
  182. package/src/context/permission-auto-respond.ts +51 -0
  183. package/src/context/permission.tsx +277 -0
  184. package/src/context/platform.tsx +99 -0
  185. package/src/context/prompt.tsx +297 -0
  186. package/src/context/sdk.tsx +49 -0
  187. package/src/context/server.tsx +295 -0
  188. package/src/context/settings.tsx +241 -0
  189. package/src/context/sync-optimistic.test.ts +123 -0
  190. package/src/context/sync.tsx +618 -0
  191. package/src/context/terminal-title.ts +51 -0
  192. package/src/context/terminal.test.ts +82 -0
  193. package/src/context/terminal.tsx +437 -0
  194. package/src/entry.tsx +144 -0
  195. package/src/env.d.ts +18 -0
  196. package/src/hooks/use-providers.ts +44 -0
  197. package/src/i18n/ar.ts +855 -0
  198. package/src/i18n/br.ts +867 -0
  199. package/src/i18n/bs.ts +943 -0
  200. package/src/i18n/da.ts +937 -0
  201. package/src/i18n/de.ts +879 -0
  202. package/src/i18n/en.ts +948 -0
  203. package/src/i18n/es.ts +950 -0
  204. package/src/i18n/fr.ts +878 -0
  205. package/src/i18n/ja.ts +861 -0
  206. package/src/i18n/ko.ts +860 -0
  207. package/src/i18n/no.ts +944 -0
  208. package/src/i18n/parity.test.ts +32 -0
  209. package/src/i18n/pl.ts +865 -0
  210. package/src/i18n/ru.ts +946 -0
  211. package/src/i18n/th.ts +933 -0
  212. package/src/i18n/tr.ts +952 -0
  213. package/src/i18n/zh.ts +930 -0
  214. package/src/i18n/zht.ts +925 -0
  215. package/src/index.css +29 -0
  216. package/src/index.ts +6 -0
  217. package/src/pages/directory-layout.tsx +88 -0
  218. package/src/pages/error.tsx +327 -0
  219. package/src/pages/home.tsx +131 -0
  220. package/src/pages/layout/deep-links.ts +50 -0
  221. package/src/pages/layout/helpers.test.ts +211 -0
  222. package/src/pages/layout/helpers.ts +98 -0
  223. package/src/pages/layout/inline-editor.tsx +126 -0
  224. package/src/pages/layout/sidebar-items.tsx +437 -0
  225. package/src/pages/layout/sidebar-project.tsx +384 -0
  226. package/src/pages/layout/sidebar-shell.tsx +125 -0
  227. package/src/pages/layout/sidebar-workspace.tsx +504 -0
  228. package/src/pages/layout.tsx +2509 -0
  229. package/src/pages/session/composer/index.ts +2 -0
  230. package/src/pages/session/composer/session-composer-region.tsx +255 -0
  231. package/src/pages/session/composer/session-composer-state.test.ts +128 -0
  232. package/src/pages/session/composer/session-composer-state.ts +249 -0
  233. package/src/pages/session/composer/session-followup-dock.tsx +109 -0
  234. package/src/pages/session/composer/session-permission-dock.tsx +74 -0
  235. package/src/pages/session/composer/session-question-dock.tsx +449 -0
  236. package/src/pages/session/composer/session-request-tree.ts +52 -0
  237. package/src/pages/session/composer/session-revert-dock.tsx +99 -0
  238. package/src/pages/session/composer/session-todo-dock.tsx +330 -0
  239. package/src/pages/session/file-tab-scroll.test.ts +40 -0
  240. package/src/pages/session/file-tab-scroll.ts +67 -0
  241. package/src/pages/session/file-tabs.tsx +456 -0
  242. package/src/pages/session/handoff.ts +36 -0
  243. package/src/pages/session/helpers.test.ts +181 -0
  244. package/src/pages/session/helpers.ts +198 -0
  245. package/src/pages/session/message-gesture.test.ts +62 -0
  246. package/src/pages/session/message-gesture.ts +21 -0
  247. package/src/pages/session/message-id-from-hash.ts +6 -0
  248. package/src/pages/session/message-timeline.tsx +1013 -0
  249. package/src/pages/session/review-tab.tsx +170 -0
  250. package/src/pages/session/session-layout.ts +20 -0
  251. package/src/pages/session/session-model-helpers.test.ts +51 -0
  252. package/src/pages/session/session-model-helpers.ts +16 -0
  253. package/src/pages/session/session-side-panel.tsx +453 -0
  254. package/src/pages/session/terminal-label.ts +16 -0
  255. package/src/pages/session/terminal-panel.test.ts +25 -0
  256. package/src/pages/session/terminal-panel.tsx +326 -0
  257. package/src/pages/session/use-session-commands.tsx +495 -0
  258. package/src/pages/session/use-session-hash-scroll.test.ts +16 -0
  259. package/src/pages/session/use-session-hash-scroll.ts +197 -0
  260. package/src/pages/session.tsx +1841 -0
  261. package/src/sst-env.d.ts +12 -0
  262. package/src/testing/model-selection.ts +80 -0
  263. package/src/testing/prompt.ts +56 -0
  264. package/src/testing/session-composer.ts +84 -0
  265. package/src/testing/terminal.ts +118 -0
  266. package/src/theme-preload.test.ts +46 -0
  267. package/src/utils/agent.ts +23 -0
  268. package/src/utils/aim.ts +138 -0
  269. package/src/utils/base64.ts +10 -0
  270. package/src/utils/comment-note.ts +88 -0
  271. package/src/utils/id.ts +99 -0
  272. package/src/utils/notification-click.test.ts +27 -0
  273. package/src/utils/notification-click.ts +13 -0
  274. package/src/utils/persist.test.ts +115 -0
  275. package/src/utils/persist.ts +476 -0
  276. package/src/utils/prompt.test.ts +44 -0
  277. package/src/utils/prompt.ts +203 -0
  278. package/src/utils/runtime-adapters.test.ts +62 -0
  279. package/src/utils/runtime-adapters.ts +39 -0
  280. package/src/utils/same.ts +6 -0
  281. package/src/utils/scoped-cache.test.ts +69 -0
  282. package/src/utils/scoped-cache.ts +104 -0
  283. package/src/utils/server-errors.test.ts +131 -0
  284. package/src/utils/server-errors.ts +80 -0
  285. package/src/utils/server-health.test.ts +123 -0
  286. package/src/utils/server-health.ts +91 -0
  287. package/src/utils/server.ts +22 -0
  288. package/src/utils/solid-dnd.tsx +49 -0
  289. package/src/utils/sound.ts +117 -0
  290. package/src/utils/terminal-writer.test.ts +64 -0
  291. package/src/utils/terminal-writer.ts +65 -0
  292. package/src/utils/time.ts +22 -0
  293. package/src/utils/uuid.test.ts +78 -0
  294. package/src/utils/uuid.ts +12 -0
  295. package/src/utils/worktree.test.ts +46 -0
  296. package/src/utils/worktree.ts +73 -0
  297. package/sst-env.d.ts +10 -0
  298. package/tsconfig.json +26 -0
  299. package/vite.config.ts +15 -0
  300. package/vite.js +26 -0
@@ -0,0 +1,12 @@
1
+ /* This file is auto-generated by SST. Do not edit. */
2
+ /* tslint:disable */
3
+ /* eslint-disable */
4
+ /* biome-ignore-all lint: auto-generated */
5
+
6
+ /// <reference types="vite/client" />
7
+ interface ImportMetaEnv {
8
+
9
+ }
10
+ interface ImportMeta {
11
+ readonly env: ImportMetaEnv
12
+ }
@@ -0,0 +1,80 @@
1
+ type ModelKey = {
2
+ providerID: string
3
+ modelID: string
4
+ }
5
+
6
+ type State = {
7
+ agent?: string
8
+ model?: ModelKey | null
9
+ variant?: string | null
10
+ }
11
+
12
+ export type ModelProbeState = {
13
+ dir?: string
14
+ sessionID?: string
15
+ last?: {
16
+ type: "agent" | "model" | "variant"
17
+ agent?: string
18
+ model?: ModelKey | null
19
+ variant?: string | null
20
+ }
21
+ agent?: string
22
+ model?: (ModelKey & { name?: string }) | undefined
23
+ variant?: string | null
24
+ selected?: string | null
25
+ configured?: string
26
+ pick?: State
27
+ base?: State
28
+ current?: string
29
+ }
30
+
31
+ export type ModelWindow = Window & {
32
+ __opencode_e2e?: {
33
+ model?: {
34
+ enabled?: boolean
35
+ current?: ModelProbeState
36
+ }
37
+ }
38
+ }
39
+
40
+ const clone = (state?: State) => {
41
+ if (!state) return undefined
42
+ return {
43
+ ...state,
44
+ model: state.model ? { ...state.model } : state.model,
45
+ }
46
+ }
47
+
48
+ export const modelEnabled = () => {
49
+ if (typeof window === "undefined") return false
50
+ return (window as ModelWindow).__opencode_e2e?.model?.enabled === true
51
+ }
52
+
53
+ const root = () => {
54
+ if (!modelEnabled()) return
55
+ return (window as ModelWindow).__opencode_e2e?.model
56
+ }
57
+
58
+ export const modelProbe = {
59
+ set(input: ModelProbeState) {
60
+ const state = root()
61
+ if (!state) return
62
+ state.current = {
63
+ ...input,
64
+ model: input.model ? { ...input.model } : undefined,
65
+ last: input.last
66
+ ? {
67
+ ...input.last,
68
+ model: input.last.model ? { ...input.last.model } : input.last.model,
69
+ }
70
+ : undefined,
71
+ pick: clone(input.pick),
72
+ base: clone(input.base),
73
+ }
74
+ },
75
+ clear() {
76
+ const state = root()
77
+ if (!state) return
78
+ state.current = undefined
79
+ },
80
+ }
@@ -0,0 +1,56 @@
1
+ import type { E2EWindow } from "./terminal"
2
+
3
+ export type PromptProbeState = {
4
+ popover: "at" | "slash" | null
5
+ slash: {
6
+ active: string | null
7
+ ids: string[]
8
+ }
9
+ selected: string | null
10
+ selects: number
11
+ }
12
+
13
+ export const promptEnabled = () => {
14
+ if (typeof window === "undefined") return false
15
+ return (window as E2EWindow).__opencode_e2e?.prompt?.enabled === true
16
+ }
17
+
18
+ const root = () => {
19
+ if (!promptEnabled()) return
20
+ return (window as E2EWindow).__opencode_e2e?.prompt
21
+ }
22
+
23
+ export const promptProbe = {
24
+ set(input: Omit<PromptProbeState, "selected" | "selects">) {
25
+ const state = root()
26
+ if (!state) return
27
+ state.current = {
28
+ popover: input.popover,
29
+ slash: {
30
+ active: input.slash.active,
31
+ ids: [...input.slash.ids],
32
+ },
33
+ selected: state.current?.selected ?? null,
34
+ selects: state.current?.selects ?? 0,
35
+ }
36
+ },
37
+ select(id: string) {
38
+ const state = root()
39
+ if (!state) return
40
+ const prev = state.current
41
+ state.current = {
42
+ popover: prev?.popover ?? null,
43
+ slash: {
44
+ active: prev?.slash.active ?? null,
45
+ ids: [...(prev?.slash.ids ?? [])],
46
+ },
47
+ selected: id,
48
+ selects: (prev?.selects ?? 0) + 1,
49
+ }
50
+ },
51
+ clear() {
52
+ const state = root()
53
+ if (!state) return
54
+ state.current = undefined
55
+ },
56
+ }
@@ -0,0 +1,84 @@
1
+ import type { Todo } from "@reign-labs/sdk/v2"
2
+
3
+ export const composerEvent = "opencode:e2e:composer"
4
+
5
+ export type ComposerDriverState = {
6
+ live?: boolean
7
+ todos?: Array<Pick<Todo, "content" | "status" | "priority">>
8
+ }
9
+
10
+ export type ComposerProbeState = {
11
+ mounted: boolean
12
+ collapsed: boolean
13
+ hidden: boolean
14
+ count: number
15
+ states: Todo["status"][]
16
+ }
17
+
18
+ type ComposerState = {
19
+ driver?: ComposerDriverState
20
+ probe?: ComposerProbeState
21
+ }
22
+
23
+ export type ComposerWindow = Window & {
24
+ __opencode_e2e?: {
25
+ composer?: {
26
+ enabled?: boolean
27
+ sessions?: Record<string, ComposerState>
28
+ }
29
+ }
30
+ }
31
+
32
+ const clone = (driver: ComposerDriverState) => ({
33
+ live: driver.live,
34
+ todos: driver.todos?.map((todo) => ({ ...todo })),
35
+ })
36
+
37
+ export const composerEnabled = () => {
38
+ if (typeof window === "undefined") return false
39
+ return (window as ComposerWindow).__opencode_e2e?.composer?.enabled === true
40
+ }
41
+
42
+ const root = () => {
43
+ if (!composerEnabled()) return
44
+ const state = (window as ComposerWindow).__opencode_e2e?.composer
45
+ if (!state) return
46
+ state.sessions ??= {}
47
+ return state.sessions
48
+ }
49
+
50
+ export const composerDriver = (sessionID?: string) => {
51
+ if (!sessionID) return
52
+ const state = root()?.[sessionID]?.driver
53
+ if (!state) return
54
+ return clone(state)
55
+ }
56
+
57
+ export const composerProbe = (sessionID?: string) => {
58
+ const set = (next: ComposerProbeState) => {
59
+ if (!sessionID) return
60
+ const sessions = root()
61
+ if (!sessions) return
62
+ const prev = sessions[sessionID] ?? {}
63
+ sessions[sessionID] = {
64
+ ...prev,
65
+ probe: {
66
+ ...next,
67
+ states: [...next.states],
68
+ },
69
+ }
70
+ }
71
+
72
+ return {
73
+ set,
74
+ drop() {
75
+ set({
76
+ mounted: false,
77
+ collapsed: false,
78
+ hidden: true,
79
+ count: 0,
80
+ states: [],
81
+ })
82
+ },
83
+ }
84
+ }
@@ -0,0 +1,118 @@
1
+ import type { ModelProbeState } from "./model-selection"
2
+
3
+ export const terminalAttr = "data-pty-id"
4
+
5
+ export type TerminalProbeState = {
6
+ connected: boolean
7
+ connects: number
8
+ rendered: string
9
+ settled: number
10
+ focusing: number
11
+ }
12
+
13
+ type TerminalProbeControl = {
14
+ disconnect?: VoidFunction
15
+ }
16
+
17
+ export type E2EWindow = Window & {
18
+ __opencode_e2e?: {
19
+ model?: {
20
+ enabled?: boolean
21
+ current?: ModelProbeState
22
+ }
23
+ prompt?: {
24
+ enabled?: boolean
25
+ current?: import("./prompt").PromptProbeState
26
+ }
27
+ terminal?: {
28
+ enabled?: boolean
29
+ terminals?: Record<string, TerminalProbeState>
30
+ controls?: Record<string, TerminalProbeControl>
31
+ }
32
+ }
33
+ }
34
+
35
+ const seed = (): TerminalProbeState => ({
36
+ connected: false,
37
+ connects: 0,
38
+ rendered: "",
39
+ settled: 0,
40
+ focusing: 0,
41
+ })
42
+
43
+ const root = () => {
44
+ if (typeof window === "undefined") return
45
+ const state = (window as E2EWindow).__opencode_e2e?.terminal
46
+ if (!state?.enabled) return
47
+ return state
48
+ }
49
+
50
+ const terms = () => {
51
+ const state = root()
52
+ if (!state) return
53
+ state.terminals ??= {}
54
+ return state.terminals
55
+ }
56
+
57
+ const controls = () => {
58
+ const state = root()
59
+ if (!state) return
60
+ state.controls ??= {}
61
+ return state.controls
62
+ }
63
+
64
+ export const terminalProbe = (id: string) => {
65
+ const set = (next: Partial<TerminalProbeState>) => {
66
+ const state = terms()
67
+ if (!state) return
68
+ state[id] = { ...(state[id] ?? seed()), ...next }
69
+ }
70
+
71
+ return {
72
+ init() {
73
+ set(seed())
74
+ },
75
+ connect() {
76
+ const state = terms()
77
+ if (!state) return
78
+ const prev = state[id] ?? seed()
79
+ state[id] = {
80
+ ...prev,
81
+ connected: true,
82
+ connects: prev.connects + 1,
83
+ }
84
+ },
85
+ render(data: string) {
86
+ const state = terms()
87
+ if (!state) return
88
+ const prev = state[id] ?? seed()
89
+ state[id] = { ...prev, rendered: prev.rendered + data }
90
+ },
91
+ settle() {
92
+ const state = terms()
93
+ if (!state) return
94
+ const prev = state[id] ?? seed()
95
+ state[id] = { ...prev, settled: prev.settled + 1 }
96
+ },
97
+ focus(count: number) {
98
+ set({ focusing: Math.max(0, count) })
99
+ },
100
+ step() {
101
+ const state = terms()
102
+ if (!state) return
103
+ const prev = state[id] ?? seed()
104
+ state[id] = { ...prev, focusing: Math.max(0, prev.focusing - 1) }
105
+ },
106
+ control(next: Partial<TerminalProbeControl>) {
107
+ const state = controls()
108
+ if (!state) return
109
+ state[id] = { ...(state[id] ?? {}), ...next }
110
+ },
111
+ drop() {
112
+ const state = terms()
113
+ if (state) delete state[id]
114
+ const control = controls()
115
+ if (control) delete control[id]
116
+ },
117
+ }
118
+ }
@@ -0,0 +1,46 @@
1
+ import { beforeEach, describe, expect, test } from "bun:test"
2
+
3
+ const src = await Bun.file(new URL("../public/oc-theme-preload.js", import.meta.url)).text()
4
+
5
+ const run = () => Function(src)()
6
+
7
+ beforeEach(() => {
8
+ document.head.innerHTML = ""
9
+ document.documentElement.removeAttribute("data-theme")
10
+ document.documentElement.removeAttribute("data-color-scheme")
11
+ localStorage.clear()
12
+ Object.defineProperty(window, "matchMedia", {
13
+ value: () =>
14
+ ({
15
+ matches: false,
16
+ }) as MediaQueryList,
17
+ configurable: true,
18
+ })
19
+ })
20
+
21
+ describe("theme preload", () => {
22
+ test("migrates legacy oc-1 to oc-2 before mount", () => {
23
+ localStorage.setItem("opencode-theme-id", "oc-1")
24
+ localStorage.setItem("opencode-theme-css-light", "--background-base:#fff;")
25
+ localStorage.setItem("opencode-theme-css-dark", "--background-base:#000;")
26
+
27
+ run()
28
+
29
+ expect(document.documentElement.dataset.theme).toBe("oc-2")
30
+ expect(document.documentElement.dataset.colorScheme).toBe("light")
31
+ expect(localStorage.getItem("opencode-theme-id")).toBe("oc-2")
32
+ expect(localStorage.getItem("opencode-theme-css-light")).toBeNull()
33
+ expect(localStorage.getItem("opencode-theme-css-dark")).toBeNull()
34
+ expect(document.getElementById("oc-theme-preload")).toBeNull()
35
+ })
36
+
37
+ test("keeps cached css for non-default themes", () => {
38
+ localStorage.setItem("opencode-theme-id", "nightowl")
39
+ localStorage.setItem("opencode-theme-css-light", "--background-base:#fff;")
40
+
41
+ run()
42
+
43
+ expect(document.documentElement.dataset.theme).toBe("nightowl")
44
+ expect(document.getElementById("oc-theme-preload")?.textContent).toContain("--background-base:#fff;")
45
+ })
46
+ })
@@ -0,0 +1,23 @@
1
+ const defaults: Record<string, string> = {
2
+ ask: "var(--icon-agent-ask-base)",
3
+ build: "var(--icon-agent-build-base)",
4
+ docs: "var(--icon-agent-docs-base)",
5
+ plan: "var(--icon-agent-plan-base)",
6
+ }
7
+
8
+ export function agentColor(name: string, custom?: string) {
9
+ if (custom) return custom
10
+ return defaults[name] ?? defaults[name.toLowerCase()]
11
+ }
12
+
13
+ export function messageAgentColor(
14
+ list: readonly { role: string; agent?: string }[] | undefined,
15
+ agents: readonly { name: string; color?: string }[],
16
+ ) {
17
+ if (!list) return undefined
18
+ for (let i = list.length - 1; i >= 0; i--) {
19
+ const item = list[i]
20
+ if (item.role !== "user" || !item.agent) continue
21
+ return agentColor(item.agent, agents.find((agent) => agent.name === item.agent)?.color)
22
+ }
23
+ }
@@ -0,0 +1,138 @@
1
+ type Point = { x: number; y: number }
2
+
3
+ export function createAim(props: {
4
+ enabled: () => boolean
5
+ active: () => string | undefined
6
+ el: () => HTMLElement | undefined
7
+ onActivate: (id: string) => void
8
+ delay?: number
9
+ max?: number
10
+ tolerance?: number
11
+ edge?: number
12
+ }) {
13
+ const state = {
14
+ locs: [] as Point[],
15
+ timer: undefined as number | undefined,
16
+ pending: undefined as string | undefined,
17
+ over: undefined as string | undefined,
18
+ last: undefined as Point | undefined,
19
+ }
20
+
21
+ const delay = props.delay ?? 250
22
+ const max = props.max ?? 4
23
+ const tolerance = props.tolerance ?? 80
24
+ const edge = props.edge ?? 18
25
+
26
+ const cancel = () => {
27
+ if (state.timer !== undefined) clearTimeout(state.timer)
28
+ state.timer = undefined
29
+ state.pending = undefined
30
+ }
31
+
32
+ const reset = () => {
33
+ cancel()
34
+ state.over = undefined
35
+ state.last = undefined
36
+ state.locs.length = 0
37
+ }
38
+
39
+ const move = (event: MouseEvent) => {
40
+ if (!props.enabled()) return
41
+ const el = props.el()
42
+ if (!el) return
43
+
44
+ const rect = el.getBoundingClientRect()
45
+ const x = event.clientX
46
+ const y = event.clientY
47
+ if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) return
48
+
49
+ state.locs.push({ x, y })
50
+ if (state.locs.length > max) state.locs.shift()
51
+ }
52
+
53
+ const wait = () => {
54
+ if (!props.enabled()) return 0
55
+ if (!props.active()) return 0
56
+
57
+ const el = props.el()
58
+ if (!el) return 0
59
+ if (state.locs.length < 2) return 0
60
+
61
+ const rect = el.getBoundingClientRect()
62
+ const loc = state.locs[state.locs.length - 1]
63
+ if (!loc) return 0
64
+
65
+ const prev = state.locs[0] ?? loc
66
+ if (prev.x < rect.left || prev.x > rect.right || prev.y < rect.top || prev.y > rect.bottom) return 0
67
+ if (state.last && loc.x === state.last.x && loc.y === state.last.y) return 0
68
+
69
+ if (rect.right - loc.x <= edge) {
70
+ state.last = loc
71
+ return delay
72
+ }
73
+
74
+ const upper = { x: rect.right, y: rect.top - tolerance }
75
+ const lower = { x: rect.right, y: rect.bottom + tolerance }
76
+ const slope = (a: Point, b: Point) => (b.y - a.y) / (b.x - a.x)
77
+
78
+ const decreasing = slope(loc, upper)
79
+ const increasing = slope(loc, lower)
80
+ const prevDecreasing = slope(prev, upper)
81
+ const prevIncreasing = slope(prev, lower)
82
+
83
+ if (decreasing < prevDecreasing && increasing > prevIncreasing) {
84
+ state.last = loc
85
+ return delay
86
+ }
87
+
88
+ state.last = undefined
89
+ return 0
90
+ }
91
+
92
+ const activate = (id: string) => {
93
+ cancel()
94
+ props.onActivate(id)
95
+ }
96
+
97
+ const request = (id: string) => {
98
+ if (!id) return
99
+ if (props.active() === id) return
100
+
101
+ if (!props.active()) {
102
+ activate(id)
103
+ return
104
+ }
105
+
106
+ const ms = wait()
107
+ if (ms === 0) {
108
+ activate(id)
109
+ return
110
+ }
111
+
112
+ cancel()
113
+ state.pending = id
114
+ state.timer = window.setTimeout(() => {
115
+ state.timer = undefined
116
+ if (state.pending !== id) return
117
+ state.pending = undefined
118
+ if (!props.enabled()) return
119
+ if (!props.active()) return
120
+ if (state.over !== id) return
121
+ props.onActivate(id)
122
+ }, ms)
123
+ }
124
+
125
+ const enter = (id: string, event: MouseEvent) => {
126
+ if (!props.enabled()) return
127
+ state.over = id
128
+ move(event)
129
+ request(id)
130
+ }
131
+
132
+ const leave = (id: string) => {
133
+ if (state.over === id) state.over = undefined
134
+ if (state.pending === id) cancel()
135
+ }
136
+
137
+ return { move, enter, leave, activate, request, cancel, reset }
138
+ }
@@ -0,0 +1,10 @@
1
+ import { base64Decode } from "@reign-labs/util/encode"
2
+
3
+ export function decode64(value: string | undefined) {
4
+ if (value === undefined) return
5
+ try {
6
+ return base64Decode(value)
7
+ } catch {
8
+ return
9
+ }
10
+ }
@@ -0,0 +1,88 @@
1
+ import type { FileSelection } from "@/context/file"
2
+
3
+ export type PromptComment = {
4
+ path: string
5
+ selection?: FileSelection
6
+ comment: string
7
+ preview?: string
8
+ origin?: "review" | "file"
9
+ }
10
+
11
+ function selection(selection: unknown) {
12
+ if (!selection || typeof selection !== "object") return undefined
13
+ const startLine = Number((selection as FileSelection).startLine)
14
+ const startChar = Number((selection as FileSelection).startChar)
15
+ const endLine = Number((selection as FileSelection).endLine)
16
+ const endChar = Number((selection as FileSelection).endChar)
17
+ if (![startLine, startChar, endLine, endChar].every(Number.isFinite)) return undefined
18
+ return {
19
+ startLine,
20
+ startChar,
21
+ endLine,
22
+ endChar,
23
+ } satisfies FileSelection
24
+ }
25
+
26
+ export function createCommentMetadata(input: PromptComment) {
27
+ return {
28
+ opencodeComment: {
29
+ path: input.path,
30
+ selection: input.selection,
31
+ comment: input.comment,
32
+ preview: input.preview,
33
+ origin: input.origin,
34
+ },
35
+ }
36
+ }
37
+
38
+ export function readCommentMetadata(value: unknown) {
39
+ if (!value || typeof value !== "object") return
40
+ const meta = (value as { opencodeComment?: unknown }).opencodeComment
41
+ if (!meta || typeof meta !== "object") return
42
+ const path = (meta as { path?: unknown }).path
43
+ const comment = (meta as { comment?: unknown }).comment
44
+ if (typeof path !== "string" || typeof comment !== "string") return
45
+ const preview = (meta as { preview?: unknown }).preview
46
+ const origin = (meta as { origin?: unknown }).origin
47
+ return {
48
+ path,
49
+ selection: selection((meta as { selection?: unknown }).selection),
50
+ comment,
51
+ preview: typeof preview === "string" ? preview : undefined,
52
+ origin: origin === "review" || origin === "file" ? origin : undefined,
53
+ } satisfies PromptComment
54
+ }
55
+
56
+ export function formatCommentNote(input: { path: string; selection?: FileSelection; comment: string }) {
57
+ const start = input.selection ? Math.min(input.selection.startLine, input.selection.endLine) : undefined
58
+ const end = input.selection ? Math.max(input.selection.startLine, input.selection.endLine) : undefined
59
+ const range =
60
+ start === undefined || end === undefined
61
+ ? "this file"
62
+ : start === end
63
+ ? `line ${start}`
64
+ : `lines ${start} through ${end}`
65
+ return `The user made the following comment regarding ${range} of ${input.path}: ${input.comment}`
66
+ }
67
+
68
+ export function parseCommentNote(text: string) {
69
+ const match = text.match(
70
+ /^The user made the following comment regarding (this file|line (\d+)|lines (\d+) through (\d+)) of (.+?): ([\s\S]+)$/,
71
+ )
72
+ if (!match) return
73
+ const start = match[2] ? Number(match[2]) : match[3] ? Number(match[3]) : undefined
74
+ const end = match[2] ? Number(match[2]) : match[4] ? Number(match[4]) : undefined
75
+ return {
76
+ path: match[5],
77
+ selection:
78
+ start !== undefined && end !== undefined
79
+ ? {
80
+ startLine: start,
81
+ startChar: 0,
82
+ endLine: end,
83
+ endChar: 0,
84
+ }
85
+ : undefined,
86
+ comment: match[6],
87
+ } satisfies PromptComment
88
+ }