kcg-code 0.0.0-stage → 0.1.0

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 (286) hide show
  1. package/README.md +228 -2
  2. package/bin/kcgcode.ts +13 -0
  3. package/bun-env.d.ts +19 -0
  4. package/kcg-code.config.example.json +3 -0
  5. package/package.json +83 -4
  6. package/public/logo.svg +1 -0
  7. package/public/manifest.json +18 -0
  8. package/public/sw.js +49 -0
  9. package/src/App.tsx +39 -0
  10. package/src/__tests__/manifest.test.ts +61 -0
  11. package/src/build-config.ts +9 -0
  12. package/src/cli/__tests__/dashboard.test.ts +62 -0
  13. package/src/cli/args.ts +123 -0
  14. package/src/cli/commands.ts +174 -0
  15. package/src/cli/dashboard.tsx +343 -0
  16. package/src/cli/index.ts +37 -0
  17. package/src/cli/theme.ts +100 -0
  18. package/src/components/auth/AuthGate.tsx +182 -0
  19. package/src/components/auth/AvatarEditor.tsx +236 -0
  20. package/src/components/auth/LockScreen.tsx +714 -0
  21. package/src/components/auth/ProfileAvatar.tsx +60 -0
  22. package/src/components/auth/WelcomeScreen.tsx +672 -0
  23. package/src/components/auth/__tests__/WelcomeScreen.test.tsx +42 -0
  24. package/src/components/auth/avatar-icons.tsx +114 -0
  25. package/src/components/layout/AppShell.tsx +142 -0
  26. package/src/components/layout/AppSidebar.tsx +419 -0
  27. package/src/components/layout/SearchDialog.tsx +189 -0
  28. package/src/components/layout/SidebarAccount.tsx +111 -0
  29. package/src/components/layout/SidebarSessionMenu.tsx +163 -0
  30. package/src/components/layout/ThemeToggle.tsx +67 -0
  31. package/src/components/projects/FolderBrowser.tsx +142 -0
  32. package/src/components/projects/HomeChat.tsx +250 -0
  33. package/src/components/projects/NewProjectDialog.tsx +249 -0
  34. package/src/components/projects/PanelSection.tsx +102 -0
  35. package/src/components/projects/ProjectInstructions.tsx +229 -0
  36. package/src/components/projects/ProjectRow.tsx +58 -0
  37. package/src/components/projects/ProjectSidePanel.tsx +448 -0
  38. package/src/components/sessions/AgentModeList.tsx +265 -0
  39. package/src/components/sessions/AgentPicker.tsx +123 -0
  40. package/src/components/sessions/AssistantTurn.tsx +145 -0
  41. package/src/components/sessions/AttachmentTile.tsx +161 -0
  42. package/src/components/sessions/CodeBlock.tsx +107 -0
  43. package/src/components/sessions/ComposerActionSheet.tsx +96 -0
  44. package/src/components/sessions/ConfirmDeleteProjectDialog.tsx +80 -0
  45. package/src/components/sessions/ConfirmSessionDeleteDialog.tsx +72 -0
  46. package/src/components/sessions/ContextPanel.tsx +561 -0
  47. package/src/components/sessions/DropPlate.tsx +165 -0
  48. package/src/components/sessions/FileDropOverlay.tsx +232 -0
  49. package/src/components/sessions/FileMentionDropdown.tsx +47 -0
  50. package/src/components/sessions/ImageLightbox.tsx +173 -0
  51. package/src/components/sessions/MarkdownContent.tsx +187 -0
  52. package/src/components/sessions/Mascot.tsx +47 -0
  53. package/src/components/sessions/MessageActions.tsx +60 -0
  54. package/src/components/sessions/MessageRail.tsx +121 -0
  55. package/src/components/sessions/ModelPicker.tsx +209 -0
  56. package/src/components/sessions/ModelSearchList.tsx +151 -0
  57. package/src/components/sessions/NewSessionDialog.tsx +189 -0
  58. package/src/components/sessions/PendingAttachments.tsx +127 -0
  59. package/src/components/sessions/PromptCard.tsx +1085 -0
  60. package/src/components/sessions/PromptPanel.tsx +112 -0
  61. package/src/components/sessions/SessionComposer.tsx +640 -0
  62. package/src/components/sessions/SessionHeader.tsx +175 -0
  63. package/src/components/sessions/SessionList.tsx +396 -0
  64. package/src/components/sessions/SessionRow.tsx +137 -0
  65. package/src/components/sessions/SessionTimeline.tsx +275 -0
  66. package/src/components/sessions/SessionTimelineSkeleton.tsx +83 -0
  67. package/src/components/sessions/SessionView.tsx +236 -0
  68. package/src/components/sessions/ThoughtProcessBlock.tsx +199 -0
  69. package/src/components/sessions/UserMessage.tsx +177 -0
  70. package/src/components/sessions/__tests__/ContextPanel.test.tsx +220 -0
  71. package/src/components/sessions/__tests__/MarkdownContent.test.tsx +89 -0
  72. package/src/components/sessions/__tests__/MessageRail.test.tsx +40 -0
  73. package/src/components/sessions/__tests__/PermissionCard.test.tsx +79 -0
  74. package/src/components/sessions/__tests__/PromptCard.test.ts +166 -0
  75. package/src/components/sessions/__tests__/QuestionCard.test.tsx +166 -0
  76. package/src/components/sessions/__tests__/SessionTimelineSkeleton.test.tsx +32 -0
  77. package/src/components/sessions/__tests__/SessionView.test.ts +386 -0
  78. package/src/components/settings/AboutGroup.tsx +170 -0
  79. package/src/components/settings/AccountGroup.tsx +122 -0
  80. package/src/components/settings/DevicesGroup.tsx +151 -0
  81. package/src/components/settings/LhrRemoteAccess.tsx +262 -0
  82. package/src/components/settings/PasskeysGroup.tsx +551 -0
  83. package/src/components/settings/RemoteAccessGroup.tsx +1226 -0
  84. package/src/components/settings/SecurityGroup.tsx +566 -0
  85. package/src/components/settings/WorkspaceGroup.tsx +370 -0
  86. package/src/components/settings/__tests__/AboutGroup.test.tsx +47 -0
  87. package/src/components/settings/__tests__/PasskeysGroup.test.tsx +109 -0
  88. package/src/components/settings/__tests__/WorkspaceGroup.test.tsx +49 -0
  89. package/src/components/settings/prefs.tsx +265 -0
  90. package/src/components/skills/InstallNotifications.tsx +325 -0
  91. package/src/components/skills/InstallSkillDialog.tsx +233 -0
  92. package/src/components/skills/SkillCards.tsx +210 -0
  93. package/src/components/skills/SkillsProjectPicker.tsx +178 -0
  94. package/src/components/ui/alert-dialog.tsx +179 -0
  95. package/src/components/ui/alert.tsx +60 -0
  96. package/src/components/ui/badge.tsx +46 -0
  97. package/src/components/ui/bubble.tsx +125 -0
  98. package/src/components/ui/button.tsx +62 -0
  99. package/src/components/ui/card.tsx +75 -0
  100. package/src/components/ui/collapsible.tsx +19 -0
  101. package/src/components/ui/dialog.tsx +142 -0
  102. package/src/components/ui/dropdown-menu.tsx +225 -0
  103. package/src/components/ui/empty.tsx +94 -0
  104. package/src/components/ui/field.tsx +234 -0
  105. package/src/components/ui/input-group.tsx +161 -0
  106. package/src/components/ui/input.tsx +21 -0
  107. package/src/components/ui/label.tsx +19 -0
  108. package/src/components/ui/message-scroller.tsx +131 -0
  109. package/src/components/ui/message.tsx +85 -0
  110. package/src/components/ui/popover.tsx +46 -0
  111. package/src/components/ui/select.tsx +172 -0
  112. package/src/components/ui/separator.tsx +28 -0
  113. package/src/components/ui/sheet.tsx +138 -0
  114. package/src/components/ui/sidebar.tsx +699 -0
  115. package/src/components/ui/skeleton.tsx +15 -0
  116. package/src/components/ui/sonner.tsx +45 -0
  117. package/src/components/ui/spinner.tsx +16 -0
  118. package/src/components/ui/switch.tsx +32 -0
  119. package/src/components/ui/textarea.tsx +18 -0
  120. package/src/components/ui/tooltip.tsx +51 -0
  121. package/src/config.ts +85 -0
  122. package/src/db.ts +146 -0
  123. package/src/frontend.tsx +51 -0
  124. package/src/hooks/__tests__/useMention.test.ts +79 -0
  125. package/src/hooks/__tests__/useResyncOnReconnect.test.ts +128 -0
  126. package/src/hooks/__tests__/useRouter.test.ts +17 -0
  127. package/src/hooks/__tests__/useSmoothText.react.test.ts +194 -0
  128. package/src/hooks/__tests__/useSmoothText.test.ts +61 -0
  129. package/src/hooks/__tests__/useTheme.test.ts +118 -0
  130. package/src/hooks/use-mobile.ts +16 -0
  131. package/src/hooks/useAgentPicker.ts +97 -0
  132. package/src/hooks/useMediaQuery.ts +49 -0
  133. package/src/hooks/useMention.ts +236 -0
  134. package/src/hooks/useProjects.ts +88 -0
  135. package/src/hooks/useResyncOnReconnect.ts +28 -0
  136. package/src/hooks/useRouter.ts +92 -0
  137. package/src/hooks/useSessionChat.ts +427 -0
  138. package/src/hooks/useSessionContext.ts +106 -0
  139. package/src/hooks/useSessionList.ts +234 -0
  140. package/src/hooks/useSidebarData.ts +108 -0
  141. package/src/hooks/useSmoothText.ts +122 -0
  142. package/src/hooks/useTheme.ts +164 -0
  143. package/src/hooks/useWebSocket.ts +196 -0
  144. package/src/index.css +27 -0
  145. package/src/index.html +14 -0
  146. package/src/index.ts +49 -0
  147. package/src/lib/__tests__/attachments.test.ts +58 -0
  148. package/src/lib/__tests__/avatar-presets.test.ts +25 -0
  149. package/src/lib/__tests__/collapsible.test.ts +101 -0
  150. package/src/lib/__tests__/composer.test.ts +118 -0
  151. package/src/lib/__tests__/highlight.test.ts +42 -0
  152. package/src/lib/__tests__/home-chat.test.ts +64 -0
  153. package/src/lib/__tests__/lock-screen.test.ts +64 -0
  154. package/src/lib/__tests__/model-picker.test.ts +205 -0
  155. package/src/lib/__tests__/onboarding.test.ts +41 -0
  156. package/src/lib/__tests__/permission-info.test.ts +61 -0
  157. package/src/lib/__tests__/project-extensions.test.ts +52 -0
  158. package/src/lib/__tests__/project-overview.test.ts +173 -0
  159. package/src/lib/__tests__/references.test.ts +121 -0
  160. package/src/lib/__tests__/routes.test.ts +134 -0
  161. package/src/lib/__tests__/session-summary.test.ts +115 -0
  162. package/src/lib/__tests__/session-title.test.ts +58 -0
  163. package/src/lib/__tests__/sidebar-groups.test.ts +100 -0
  164. package/src/lib/__tests__/skill-installs.test.ts +134 -0
  165. package/src/lib/__tests__/skills-catalog.test.ts +96 -0
  166. package/src/lib/__tests__/tone.test.ts +37 -0
  167. package/src/lib/__tests__/turns-delta.test.ts +55 -0
  168. package/src/lib/api.ts +306 -0
  169. package/src/lib/attachments.ts +203 -0
  170. package/src/lib/auth.ts +92 -0
  171. package/src/lib/avatar-presets.ts +31 -0
  172. package/src/lib/collapsible.ts +75 -0
  173. package/src/lib/composer.ts +89 -0
  174. package/src/lib/data-events.ts +17 -0
  175. package/src/lib/headlines.ts +30 -0
  176. package/src/lib/highlight.ts +186 -0
  177. package/src/lib/layout.ts +20 -0
  178. package/src/lib/lock-screen.ts +54 -0
  179. package/src/lib/model-picker.ts +133 -0
  180. package/src/lib/onboarding.ts +38 -0
  181. package/src/lib/passkeys.ts +113 -0
  182. package/src/lib/pending-prompt.ts +53 -0
  183. package/src/lib/permission-info.ts +101 -0
  184. package/src/lib/project-extensions.ts +74 -0
  185. package/src/lib/project-overview.ts +117 -0
  186. package/src/lib/prompts.ts +87 -0
  187. package/src/lib/references.ts +106 -0
  188. package/src/lib/routes.ts +115 -0
  189. package/src/lib/session-handoff.ts +25 -0
  190. package/src/lib/session-status.ts +45 -0
  191. package/src/lib/session-summary.ts +68 -0
  192. package/src/lib/session-title.ts +63 -0
  193. package/src/lib/sidebar-groups.ts +78 -0
  194. package/src/lib/skill-installs.ts +360 -0
  195. package/src/lib/skills-catalog.ts +99 -0
  196. package/src/lib/time.ts +22 -0
  197. package/src/lib/tone.ts +80 -0
  198. package/src/lib/turns.ts +320 -0
  199. package/src/lib/utils.ts +6 -0
  200. package/src/logo.svg +31 -0
  201. package/src/pages/projects/ProjectDetailPage.tsx +130 -0
  202. package/src/pages/projects/ProjectsPage.tsx +97 -0
  203. package/src/pages/sessions/SessionPage.tsx +122 -0
  204. package/src/pages/settings/SettingsPage.tsx +299 -0
  205. package/src/pages/settings/__tests__/SettingsPage.test.ts +9 -0
  206. package/src/pages/skills/SkillsPage.tsx +701 -0
  207. package/src/paths.ts +121 -0
  208. package/src/runtime.ts +24 -0
  209. package/src/server/__tests__/app.test.ts +1031 -0
  210. package/src/server/__tests__/auth.test.ts +370 -0
  211. package/src/server/__tests__/config.test.ts +101 -0
  212. package/src/server/__tests__/db.test.ts +485 -0
  213. package/src/server/__tests__/host.test.ts +14 -0
  214. package/src/server/__tests__/tunnel-config.test.ts +38 -0
  215. package/src/server/app.ts +415 -0
  216. package/src/server/db/auth.ts +311 -0
  217. package/src/server/db/messages.ts +50 -0
  218. package/src/server/db/projects.ts +106 -0
  219. package/src/server/db/prompts.ts +78 -0
  220. package/src/server/db/rows.ts +150 -0
  221. package/src/server/db/schema.ts +221 -0
  222. package/src/server/db/sessions.ts +239 -0
  223. package/src/server/db/tunnel.ts +124 -0
  224. package/src/server/host.ts +12 -0
  225. package/src/server/result.ts +15 -0
  226. package/src/server/routes/auth-guard.ts +156 -0
  227. package/src/server/routes/auth.routes.ts +287 -0
  228. package/src/server/routes/helpers.ts +165 -0
  229. package/src/server/routes/meta.routes.ts +24 -0
  230. package/src/server/routes/passkeys.routes.ts +161 -0
  231. package/src/server/routes/projects.routes.ts +157 -0
  232. package/src/server/routes/sessions.routes.ts +197 -0
  233. package/src/server/routes/skills.routes.ts +117 -0
  234. package/src/server/routes/tunnel.routes.ts +132 -0
  235. package/src/server/routes/types.ts +35 -0
  236. package/src/server/routes/uploads.routes.ts +54 -0
  237. package/src/server/routes/workspace.routes.ts +127 -0
  238. package/src/server/services/__tests__/app-info.test.ts +18 -0
  239. package/src/server/services/__tests__/attachments.test.ts +159 -0
  240. package/src/server/services/__tests__/auth-passkeys.test.ts +358 -0
  241. package/src/server/services/__tests__/auth.test.ts +341 -0
  242. package/src/server/services/__tests__/frpc-installer.test.ts +83 -0
  243. package/src/server/services/__tests__/git-info.test.ts +74 -0
  244. package/src/server/services/__tests__/opencode.test.ts +320 -0
  245. package/src/server/services/__tests__/project-manager.test.ts +190 -0
  246. package/src/server/services/__tests__/sandbox.test.ts +141 -0
  247. package/src/server/services/__tests__/session-manager.test.ts +3469 -0
  248. package/src/server/services/__tests__/skill-install-jobs.test.ts +226 -0
  249. package/src/server/services/__tests__/skills-registry.test.ts +359 -0
  250. package/src/server/services/__tests__/tunnel-lhr.test.ts +234 -0
  251. package/src/server/services/__tests__/tunnel.test.ts +367 -0
  252. package/src/server/services/__tests__/websocket-gateway.test.ts +677 -0
  253. package/src/server/services/__tests__/workspace.test.ts +249 -0
  254. package/src/server/services/app-info.ts +29 -0
  255. package/src/server/services/attachments.ts +193 -0
  256. package/src/server/services/auth-passkeys.ts +459 -0
  257. package/src/server/services/auth.ts +633 -0
  258. package/src/server/services/frpc-installer.ts +183 -0
  259. package/src/server/services/git-info.ts +87 -0
  260. package/src/server/services/opencode-client.ts +714 -0
  261. package/src/server/services/opencode-server.ts +319 -0
  262. package/src/server/services/opencode-sse.ts +82 -0
  263. package/src/server/services/project-manager.ts +147 -0
  264. package/src/server/services/sandbox.ts +83 -0
  265. package/src/server/services/session-events.ts +256 -0
  266. package/src/server/services/session-manager.ts +1374 -0
  267. package/src/server/services/skill-install-jobs.ts +262 -0
  268. package/src/server/services/skills-registry.ts +468 -0
  269. package/src/server/services/tunnel-api.ts +207 -0
  270. package/src/server/services/tunnel-config.ts +45 -0
  271. package/src/server/services/tunnel-lhr.ts +387 -0
  272. package/src/server/services/tunnel.ts +633 -0
  273. package/src/server/services/turn-stream.ts +339 -0
  274. package/src/server/services/websocket-gateway.ts +374 -0
  275. package/src/server/services/workspace.ts +223 -0
  276. package/src/server/services/ws-transport.ts +121 -0
  277. package/src/types/context.ts +69 -0
  278. package/src/types/index.ts +13 -0
  279. package/src/types/message.ts +58 -0
  280. package/src/types/project.ts +16 -0
  281. package/src/types/prompt.ts +118 -0
  282. package/src/types/session.ts +52 -0
  283. package/src/types/turns.ts +47 -0
  284. package/src/ws-protocol.ts +93 -0
  285. package/styles/globals.css +311 -0
  286. package/tsconfig.json +37 -0
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Transport WebSocket — adaptor koneksi + dispatch pesan Client -> Gateway.
3
+ *
4
+ * Dipisah dari `websocket-gateway.ts` (yang mengurus registrasi & broadcast)
5
+ * agar tiap berkas satu tanggung jawab:
6
+ * - `bunWsSubscriber` menyamarkan `ServerWebSocket` Bun menjadi `Subscriber`.
7
+ * - `dispatchClientMessage` mem-parsing teks JSON dari Client lalu meneruskan
8
+ * ke gateway sesuai `type` (attach/input/prompt_response/stop/interrupt).
9
+ */
10
+ import type { ServerWebSocket } from "bun";
11
+ import type { PromptResponse } from "../../types";
12
+ import type { Subscriber, WebSocketGateway } from "./websocket-gateway";
13
+
14
+ /** Adaptor ServerWebSocket Bun ke `Subscriber` (untuk wiring di `app.ts`). */
15
+ export function bunWsSubscriber<T>(ws: ServerWebSocket<T>): Subscriber {
16
+ return {
17
+ send(msg) {
18
+ ws.send(JSON.stringify(msg));
19
+ },
20
+ close() {
21
+ ws.close(1000);
22
+ },
23
+ };
24
+ }
25
+
26
+ function isPromptResponse(r: unknown): r is PromptResponse {
27
+ if (r === "approve" || r === "always" || r === "deny" || r === "cancel") return true;
28
+ if (typeof r !== "object" || r === null) return false;
29
+ // Single choice `{ option }`.
30
+ if (typeof (r as { option?: unknown }).option === "string") return true;
31
+ // Multi-select `{ options: string[] }`.
32
+ const options = (r as { options?: unknown }).options;
33
+ if (Array.isArray(options) && options.every((s) => typeof s === "string")) return true;
34
+ // Multi-question `{ answers: string[][] }` — satu array label per pertanyaan.
35
+ const answers = (r as { answers?: unknown }).answers;
36
+ if (
37
+ Array.isArray(answers) &&
38
+ answers.every((a) => Array.isArray(a) && (a as unknown[]).every((s) => typeof s === "string"))
39
+ ) {
40
+ return true;
41
+ }
42
+ return false;
43
+ }
44
+
45
+ function invalidMessage(sub: Subscriber, detail: string): void {
46
+ sub.send({ type: "error", code: "INVALID_MESSAGE", message: detail });
47
+ }
48
+
49
+ /**
50
+ * Mem-parsing pesan Client dan meneruskan ke gateway sesuai `type`.
51
+ * Pesan JSON rusak / payload salah bentuk -> `error` INVALID_MESSAGE.
52
+ */
53
+ export function dispatchClientMessage(
54
+ gateway: WebSocketGateway,
55
+ sub: Subscriber,
56
+ raw: string,
57
+ ): void {
58
+ let parsed: unknown;
59
+ try {
60
+ parsed = JSON.parse(raw);
61
+ } catch {
62
+ invalidMessage(sub, "Pesan JSON tidak valid");
63
+ return;
64
+ }
65
+ if (typeof parsed !== "object" || parsed === null) {
66
+ invalidMessage(sub, "Pesan harus berupa objek JSON");
67
+ return;
68
+ }
69
+ const msg = parsed as Record<string, unknown>;
70
+ switch (msg.type) {
71
+ case "attach":
72
+ if (typeof msg.sessionId === "string") gateway.attach(sub, msg.sessionId);
73
+ else invalidMessage(sub, "attach membutuhkan sessionId string");
74
+ break;
75
+ case "input":
76
+ if (
77
+ typeof msg.sessionId === "string" &&
78
+ typeof msg.text === "string" &&
79
+ (msg.files === undefined ||
80
+ (Array.isArray(msg.files) && msg.files.every((f) => typeof f === "string"))) &&
81
+ (msg.images === undefined ||
82
+ (Array.isArray(msg.images) && msg.images.every((f) => typeof f === "string")))
83
+ ) {
84
+ gateway.input(sub, msg.sessionId, msg.text, msg.files, msg.images);
85
+ } else {
86
+ invalidMessage(sub, "input membutuhkan sessionId dan text string");
87
+ }
88
+ break;
89
+ case "prompt_response":
90
+ if (
91
+ typeof msg.sessionId === "string" &&
92
+ typeof msg.promptId === "string" &&
93
+ isPromptResponse(msg.response)
94
+ ) {
95
+ gateway.promptResponse(sub, msg.sessionId, msg.promptId, msg.response);
96
+ } else {
97
+ invalidMessage(sub, "prompt_response membutuhkan sessionId, promptId, dan response valid");
98
+ }
99
+ break;
100
+ case "stop":
101
+ if (typeof msg.sessionId === "string") {
102
+ gateway.stop(sub, msg.sessionId);
103
+ } else {
104
+ invalidMessage(sub, "stop membutuhkan sessionId string");
105
+ }
106
+ break;
107
+ case "interrupt":
108
+ if (typeof msg.sessionId === "string") {
109
+ gateway.interrupt(sub, msg.sessionId);
110
+ } else {
111
+ invalidMessage(sub, "interrupt membutuhkan sessionId string");
112
+ }
113
+ break;
114
+ default:
115
+ sub.send({
116
+ type: "error",
117
+ code: "UNKNOWN_MESSAGE_TYPE",
118
+ message: "Tipe pesan tidak dikenal",
119
+ });
120
+ }
121
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Konteks kerja Session untuk panel kanan ("Environment / Task list /
3
+ * Reference"). Dipakai server (`sessionContext`) maupun UI, jadi bentuknya
4
+ * hanya didefinisikan di sini.
5
+ *
6
+ * Sumber datanya tiga hal yang berbeda:
7
+ * - `changes` + `git` : keadaan worktree project (opencode `GET /file/status`
8
+ * dan perintah `git` baca di cwd Session).
9
+ * - `todos` : task list agent (`todowrite`, `GET /session/{id}/todo`).
10
+ * - `mcp` : MCP server Project & status koneksinya (`GET /mcp`).
11
+ * - Reference (file yang dibuka tool) TIDAK ada di sini: itu diturunkan di
12
+ * klien dari parts pesan yang sudah dimiliki timeline (`lib/references.ts`),
13
+ * sehingga tidak perlu request tambahan.
14
+ */
15
+
16
+ /** Satu file yang berubah di worktree. */
17
+ export interface FileChange {
18
+ path: string;
19
+ added: number;
20
+ removed: number;
21
+ status: "added" | "deleted" | "modified";
22
+ }
23
+
24
+ /** Satu item task list agent. */
25
+ export interface TodoItem {
26
+ content: string;
27
+ /** `pending` | `in_progress` | `completed` | `cancelled` (string bebas). */
28
+ status: string;
29
+ /** `high` | `medium` | `low` (string bebas). */
30
+ priority: string;
31
+ }
32
+
33
+ /** Keadaan Git worktree Session. */
34
+ export interface GitState {
35
+ /** Cabang aktif; null = detached HEAD / bukan repo git / belum ada commit. */
36
+ branch: string | null;
37
+ /** Commit lokal yang belum dipush; null = cabang tanpa upstream. */
38
+ ahead: number | null;
39
+ /** Commit upstream yang belum ditarik; null = cabang tanpa upstream. */
40
+ behind: number | null;
41
+ hasRemote: boolean;
42
+ }
43
+
44
+ /** Satu MCP server Project (subset status opencode `GET /mcp`). */
45
+ export interface McpServerState {
46
+ name: string;
47
+ status:
48
+ | "connected"
49
+ | "disabled"
50
+ | "failed"
51
+ | "needs_auth"
52
+ | "needs_client_registration"
53
+ | "unknown";
54
+ /** Pesan error bila `status === "failed"`. */
55
+ error: string | null;
56
+ }
57
+
58
+ export interface SessionContext {
59
+ git: GitState;
60
+ changes: FileChange[];
61
+ todos: TodoItem[];
62
+ /** MCP server Project beserta status koneksinya. */
63
+ mcp: McpServerState[];
64
+ /**
65
+ * Server opencode Session sedang hidup. `false` -> `todos`/`changes` kosong
66
+ * karena belum bisa dibaca, BUKAN karena memang tidak ada.
67
+ */
68
+ live: boolean;
69
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Tipe domain bersama KCG Code — satu file per domain (pola `src/types/`
3
+ * kcgcode) dengan barrel agar `@/types` tetap menjadi satu pintu import.
4
+ *
5
+ * Khusus server (bukan domain FE): `Result<T>` / `SimpleResult` hidup di
6
+ * `src/server/result.ts` — jangan diimpor dari sini.
7
+ */
8
+ export * from "./context";
9
+ export * from "./message";
10
+ export * from "./project";
11
+ export * from "./prompt";
12
+ export * from "./session";
13
+ export * from "./turns";
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Tipe pesan percakapan terstruktur (headless mode).
3
+ *
4
+ * Output_Stream chunk PTY/TUI digantikan `SessionMessage` (role + parts)
5
+ * yang berasal dari API headless opencode.
6
+ */
7
+ /**
8
+ * Satu part pesan opencode (subset skema `Part` OpenAPI): `text`, `reasoning`,
9
+ * `tool`, `step-start`, `step-finish`, `file`, `agent`, `subtask`, dll.
10
+ * Dibiarkan toleran (indeks dinamis) agar renderer frontend tahan terhadap
11
+ * bentuk part baru dari opencode. Field `state` (tool call) dipakai renderer
12
+ * untuk label target tool (mis. `state.input.filePath`).
13
+ *
14
+ * Field `file` (mime/filename/url/attachmentId) dipakai untuk part `file`:
15
+ * echo `@file` teks maupun gambar yang di-upload. `attachmentId` adalah id
16
+ * lampiran di Attachment_Store KCG (gambar), dipakai renderer untuk memuat
17
+ * bytes lewat route HTTP `/api/uploads/...`.
18
+ */
19
+ export interface MessagePart {
20
+ type: string;
21
+ id?: string;
22
+ text?: string;
23
+ tool?: string;
24
+ /** MIME part `file` (mis. `text/plain` untuk @file, `image/png` untuk gambar). */
25
+ mime?: string;
26
+ /** Nama file part `file` (nama asli / path relatif project). */
27
+ filename?: string;
28
+ /** URL file part (`file:///abs/path` dibaca opencode, bukan browser). */
29
+ url?: string;
30
+ /** Id lampiran gambar di Attachment_Store (gambar upload dari perangkat). */
31
+ attachmentId?: string;
32
+ /**
33
+ * State tool call dari opencode: `status` (pending/running/error/done),
34
+ * `input` (argumen tool, mis. `filePath` untuk read/edit), dan `output`
35
+ * (hasil). Dipakai renderer untuk menampilkan target tiap tool call.
36
+ */
37
+ state?: { input?: Record<string, unknown>; output?: unknown };
38
+ /**
39
+ * Timing asli dari opencode (ReasoningPart, TextPart, ToolState).
40
+ * `start`/`end` adalah Unix ms. Hadir di part yang sudah selesai di-stream;
41
+ * saat masih di-stream `end` bisa undefined.
42
+ */
43
+ time?: { start?: number; end?: number; created?: number };
44
+ [k: string]: unknown;
45
+ }
46
+
47
+ /** Satu pesan percakapan terstruktur (user atau assistant). */
48
+ export interface SessionMessage {
49
+ /** Id pesan (dari opencode `msg_...` atau id lokal untuk echo user). */
50
+ id: string;
51
+ /** Id Session lokal KCG Code. */
52
+ sessionId: string;
53
+ role: "user" | "assistant";
54
+ parts: MessagePart[];
55
+ createdAt: number;
56
+ /** Pesan sementara yang masih di-stream dari server headless (SSE). */
57
+ streaming?: boolean;
58
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Tipe domain Project — direpresentasikan sebagai direktori kerja di dalam
3
+ * Sandbox_Root (Requirement 10).
4
+ */
5
+ export interface Project {
6
+ id: string;
7
+ name: string;
8
+ path: string;
9
+ createdAt: number;
10
+ /**
11
+ * Custom instruction Project — dikirim sebagai `system` di setiap prompt
12
+ * ke opencode (ditambahkan di atas AGENTS.md / instruksi bawaan).
13
+ * `null`/kosong = tanpa instruksi tambahan.
14
+ */
15
+ instructions?: string | null;
16
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Satu pertanyaan dalam request tool `question` opencode. Satu request bisa
3
+ * membaca BEBERAPA pertanyaan (multi-question); pertanyaan pertama tetap
4
+ * disalin ke field legacy `title`/`options`/`multiple`/`custom` di
5
+ * `InteractivePrompt` agar kode lama tetap bekerja.
6
+ */
7
+ export interface PromptQuestion {
8
+ /** Teks pertanyaan yang tampil ke user. */
9
+ question: string | null;
10
+ /** Label pendek (header) dari skema opencode — opsional. */
11
+ header?: string | null;
12
+ options: PromptOption[] | null;
13
+ /** Boleh pilih lebih dari satu opsi (default false). */
14
+ multiple?: boolean;
15
+ /** Boleh jawab bebas selain opsi terdaftar (default true). */
16
+ custom?: boolean;
17
+ }
18
+
19
+ /**
20
+ * Tipe Interactive_Prompt (permission/question).
21
+ *
22
+ * `InteractivePrompt` bersumber dari event terstruktur opencode
23
+ * (`permission.asked` / `question.asked`) lewat field `kind` — bukan hasil
24
+ * regex atas teks TUI (`prompt-detector.ts` dihapus).
25
+ */
26
+ export type PromptType = "confirmation" | "menu";
27
+
28
+ /**
29
+ * Satu opsi question opencode: label yang dikirim balik sebagai jawaban
30
+ * plus keterangan tampilan (skema tool `question`: `{label, description?}`).
31
+ * String polos (data lama / varian event tanpa `description`) dinormalisasi
32
+ * jadi `{ label }` — lihat `normalizePromptOptions` di bawah.
33
+ */
34
+ export interface PromptOption {
35
+ label: string;
36
+ description: string | null;
37
+ }
38
+
39
+ /**
40
+ * Normalisasi opsi question ke `PromptOption[]`:
41
+ * - `{ label, description? }` (skema question opencode) -> dipakai apa adanya.
42
+ * - string polos (DB lama) -> `{ label, description: null }`.
43
+ * Entri tanpa label valid di-skip.
44
+ */
45
+ export function normalizePromptOptions(raw: unknown): PromptOption[] {
46
+ if (!Array.isArray(raw)) return [];
47
+ const out: PromptOption[] = [];
48
+ for (const o of raw) {
49
+ if (typeof o === "string") {
50
+ if (o.trim() !== "") out.push({ label: o, description: null });
51
+ } else if (typeof o === "object" && o !== null) {
52
+ const label = (o as { label?: unknown }).label;
53
+ if (typeof label === "string" && label.trim() !== "") {
54
+ const description = (o as { description?: unknown }).description;
55
+ out.push({
56
+ label,
57
+ description:
58
+ typeof description === "string" && description.trim() !== "" ? description : null,
59
+ });
60
+ }
61
+ }
62
+ }
63
+ return out;
64
+ }
65
+
66
+ export type PromptStatus = "pending" | "resolved";
67
+
68
+ /** Sumber Interactive_Prompt: izin tool (permission) atau pertanyaan (question). */
69
+ export type PromptKind = "permission" | "question";
70
+
71
+ export interface InteractivePrompt {
72
+ /** Id request opencode (`per_...` / `que_...`) — dipakai untuk reply. */
73
+ id: string;
74
+ sessionId: string;
75
+ kind: PromptKind;
76
+ type: PromptType;
77
+ /** Teks pertanyaan (question) atau deskripsi izin (permission). */
78
+ title: string | null;
79
+ /** Opsi question (menu). null = bukan menu / tanpa opsi. */
80
+ options: PromptOption[] | null;
81
+ /**
82
+ * Question saja: boleh pilih LEBIH dari satu opsi (skema tool `question`
83
+ * opencode, default false — single choice). Jawaban tetap dikirim sebagai
84
+ * array label via reply API.
85
+ */
86
+ multiple?: boolean;
87
+ /**
88
+ * Question saja: SELURUH pertanyaan dalam request (multi-question).
89
+ * undefined/[] = data lama — pertanyaan tunggal ada di field legacy di
90
+ * atas (title/options/multiple/custom).
91
+ */
92
+ questions?: PromptQuestion[];
93
+ /**
94
+ * Question saja: user boleh mengetik jawaban bebas selain opsi yang
95
+ * terdaftar (flag `custom` di skema question opencode, default true).
96
+ */
97
+ custom?: boolean;
98
+ status: PromptStatus;
99
+ createdAt: number;
100
+ resolvedAt: number | null;
101
+ }
102
+
103
+ /**
104
+ * Respon user atas Interactive_Prompt.
105
+ * - question single choice: `{ option }`.
106
+ * - question multi-select: `{ options: string[] }` (label terpilih, urut
107
+ * sesuai tampilan; minimal satu).
108
+ * - question multi-question: `{ answers: string[][] }` — SATU array label per
109
+ * pertanyaan, urut sesuai `questions` (jawaban bebas masuk sebagai label).
110
+ */
111
+ export type PromptResponse =
112
+ | "approve"
113
+ | "always"
114
+ | "deny"
115
+ | "cancel"
116
+ | { option: string }
117
+ | { options: string[] }
118
+ | { answers: string[][] };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Tipe domain Session (headless mode).
3
+ *
4
+ * `Session.ocSessionId` menyimpan id Session di server headless opencode
5
+ * (`ses_...`) sebagai pemetaan ke id Session lokal — perubahan dari versi
6
+ * PTY/TUI.
7
+ */
8
+ export type AgentType = "opencode" | "claude-code";
9
+
10
+ export type SessionStatus = "running" | "stopped" | "crashed";
11
+
12
+ /**
13
+ * Model LLM pilihan untuk Session (dipakai saat mengirim prompt).
14
+ * `null` = biarkan opencode memakai model default-nya.
15
+ */
16
+ export interface SessionModel {
17
+ providerID: string;
18
+ modelID: string;
19
+ }
20
+
21
+ /**
22
+ * Agent (mode) opencode pilihan untuk Session — dibaca dari `GET /agent`
23
+ * server headless, dipakai saat mengirim prompt (body `agent`).
24
+ * `null` = biarkan opencode memakai agent default-nya (biasanya `build`).
25
+ */
26
+ export interface SessionAgent {
27
+ name: string;
28
+ mode: "primary" | "subagent" | "all";
29
+ description: string | null;
30
+ }
31
+
32
+ export interface Session {
33
+ id: string;
34
+ projectId: string;
35
+ agentType: AgentType;
36
+ cwd: string;
37
+ status: SessionStatus;
38
+ /** Id Session di server headless opencode (`ses_...`); null sebelum dibuat. */
39
+ ocSessionId: string | null;
40
+ /**
41
+ * Judul Session — dibuat otomatis oleh opencode setelah prompt pertama
42
+ * (summarization), diterima via SSE `session.updated`. null = belum ada
43
+ * (UI menampilkan fallback "New session").
44
+ */
45
+ title: string | null;
46
+ /** Model LLM pilihan; null = model default opencode. */
47
+ model: SessionModel | null;
48
+ /** Agent (mode) pilihan; null = agent default opencode (build). */
49
+ agent: string | null;
50
+ createdAt: number;
51
+ updatedAt: number;
52
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Model tampilan percakapan (timeline SessionView).
3
+ *
4
+ * Tipe-tipe ini adalah hasil transformasi `SessionMessage` menjadi segmen /
5
+ * kelompok yang siap dirender — transformasinya (fungsi murni, teruji lewat
6
+ * `bun test`) hidup di `src/lib/turns.ts`, sedangkan konsumen render berada
7
+ * di `src/components/sessions/`.
8
+ */
9
+ import type { MessagePart, SessionMessage } from "./message";
10
+
11
+ /** Satu segmen render turn assistant (urut sesuai alur kerja model). */
12
+ export type TurnSegment =
13
+ | { kind: "reasoning"; key: string; part: MessagePart }
14
+ | { kind: "tool"; key: string; part: MessagePart }
15
+ | { kind: "error"; key: string; part: MessagePart }
16
+ | {
17
+ kind: "text";
18
+ key: string;
19
+ text: string /** Jawaban akhir turn (bukan interim). */;
20
+ final: boolean;
21
+ };
22
+
23
+ /**
24
+ * Kelompokkan segmen satu turn menjadi blok berurutan untuk render in-order.
25
+ * Setiap blok berisi kluster steps (reasoning/tool) opsional diikuti teks/error
26
+ * opsional. Ini memungkinkan pola multi-round:
27
+ * [thinking] → [text] → [thinking] → [text]
28
+ * dirender sebagai beberapa "Thought process" terpisah, bukan satu blok di atas.
29
+ */
30
+ export interface TurnBlock {
31
+ /** Key unik blok (dari key segmen pertama). */
32
+ key: string;
33
+ steps: Extract<TurnSegment, { kind: "reasoning" | "tool" }>[];
34
+ /** Teks atau error setelah kluster steps ini (bisa null kalau blok masih streaming). */
35
+ content: Extract<TurnSegment, { kind: "text" | "error" }> | null;
36
+ }
37
+
38
+ /** Sekelompok pesan assistant berurutan dari satu turn balasan model. */
39
+ export interface AssistantTurnGroup {
40
+ kind: "assistant";
41
+ /** Id pesan pertama — dipakai sebagai key React & key collapsible turn. */
42
+ id: string;
43
+ messages: SessionMessage[];
44
+ }
45
+
46
+ /** Kelompok pesan untuk render timeline: user selalu sendiri, assistant satu turn. */
47
+ export type MessageGroup = { kind: "user"; message: SessionMessage } | AssistantTurnGroup;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Protokol pesan WebSocket bersama Client <-> Server (versi headless).
3
+ *
4
+ * Perubahan dari versi PTY/TUI:
5
+ * - `history` kini membawa `messages` (SessionMessage terstruktur) dan
6
+ * `prompts` (pending) — bukan chunk Output_Stream.
7
+ * - `output`/`resize` dihapus; digantikan `message`, `stop` (hentikan Session)
8
+ * dan `interrupt` (hentikan balasan model saja — Session tetap berjalan).
9
+ * - `turn_active` memberi tahu Client kapan model sedang merespon (turn
10
+ * streaming aktif) sehingga tombol stop/interrupt bisa ditampilkan.
11
+ */
12
+ import type {
13
+ InteractivePrompt,
14
+ MessagePart,
15
+ PromptResponse,
16
+ SessionMessage,
17
+ SessionStatus,
18
+ } from "./types";
19
+
20
+ /** Pesan Client -> Server. */
21
+ export type ClientMessage =
22
+ | { type: "attach"; sessionId: string }
23
+ | {
24
+ type: "input";
25
+ sessionId: string;
26
+ text: string;
27
+ /** Path relatif project untuk referensi `@file` (dibaca sebagai teks). */
28
+ files?: string[];
29
+ /** Id lampiran gambar di Attachment_Store KCG (upload dari perangkat). */
30
+ images?: string[];
31
+ }
32
+ | { type: "prompt_response"; sessionId: string; promptId: string; response: PromptResponse }
33
+ | { type: "stop"; sessionId: string }
34
+ /**
35
+ * Hentikan balasan model yang sedang berlangsung (interrupt ala opencode):
36
+ * turn di-abort tapi Session tetap `running` sehingga bisa langsung kirim
37
+ * pesan baru. Beda dari `stop` yang menonaktifkan Session.
38
+ */
39
+ | { type: "interrupt"; sessionId: string };
40
+
41
+ /** Pesan Server -> Client. */
42
+ export type ServerMessage =
43
+ | {
44
+ type: "history";
45
+ sessionId: string;
46
+ messages: SessionMessage[];
47
+ prompts: InteractivePrompt[];
48
+ }
49
+ | { type: "message"; sessionId: string; message: SessionMessage }
50
+ /** Part pesan yang sedang di-stream (SSE `message.part.updated`). */
51
+ | { type: "message_part"; sessionId: string; messageId: string; part: MessagePart }
52
+ /**
53
+ * Potongan teks baru untuk satu part (SSE `message.part.delta`): tambahkan
54
+ * `delta` ke `part[field]` (biasanya `text`). Token model tiba lewat sini.
55
+ */
56
+ | {
57
+ type: "message_part_delta";
58
+ sessionId: string;
59
+ messageId: string;
60
+ partId: string;
61
+ field: string;
62
+ delta: string;
63
+ }
64
+ | { type: "prompt"; sessionId: string; prompt: InteractivePrompt }
65
+ | { type: "prompt_resolved"; sessionId: string; promptId: string }
66
+ | { type: "session_status"; sessionId: string; status: SessionStatus }
67
+ /** Judul Session baru hasil generate opencode — daftar Session ikut terbarui. */
68
+ | { type: "session_title"; sessionId: string; title: string }
69
+ /** Status turn: `active: true` = model sedang merespon; `false` = berhenti. */
70
+ | { type: "turn_active"; sessionId: string; active: boolean }
71
+ /** Session dihapus permanen — subscriber harus meninggalkan halamannya. */
72
+ | { type: "session_deleted"; sessionId: string }
73
+ /**
74
+ * Daftar Project/Session berubah (dibuat / dihapus) dari perangkat mana
75
+ * pun — dikirim ke semua koneksi; sidebar & daftar memuat ulang datanya.
76
+ */
77
+ | { type: "data_changed" }
78
+ | { type: "error"; code: string; message: string };
79
+
80
+ /** Kode error umum pada pesan `error`. */
81
+ export const ErrorCodes = {
82
+ SESSION_NOT_FOUND: "SESSION_NOT_FOUND",
83
+ PROMPT_NOT_FOUND: "PROMPT_NOT_FOUND",
84
+ PROMPT_ALREADY_RESOLVED: "PROMPT_ALREADY_RESOLVED",
85
+ /** Reply prompt gagal diproses server opencode (mis. reply 404/400). */
86
+ PROMPT_FAILED: "PROMPT_FAILED",
87
+ /** Agent tidak lagi menunggu jawaban; prompt ditutup & kartu dihapus. */
88
+ PROMPT_EXPIRED: "PROMPT_EXPIRED",
89
+ INVALID_RESPONSE: "INVALID_RESPONSE",
90
+ SESSION_NOT_RUNNING: "SESSION_NOT_RUNNING",
91
+ INVALID_TEXT: "INVALID_TEXT",
92
+ ATTACHMENT_NOT_FOUND: "ATTACHMENT_NOT_FOUND",
93
+ } as const;