@di-code/coding-agent 0.1.7 → 0.1.9

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 (333) hide show
  1. package/README.md +54 -567
  2. package/compositions/base.yml +54 -0
  3. package/compositions/interactive.yml +14 -0
  4. package/compositions/json.yml +6 -0
  5. package/compositions/print.yml +5 -0
  6. package/compositions/rpc.yml +12 -0
  7. package/compositions/webui.yml +9 -0
  8. package/dist/cli.d.ts +22 -1
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cli.js +97 -3
  11. package/dist/cli.js.map +1 -1
  12. package/dist/composition-loader-entry.d.ts +11 -0
  13. package/dist/composition-loader-entry.d.ts.map +1 -0
  14. package/dist/composition-loader-entry.js +12 -0
  15. package/dist/composition-loader-entry.js.map +1 -0
  16. package/dist/compositions.d.ts +32 -0
  17. package/dist/compositions.d.ts.map +1 -0
  18. package/dist/compositions.js +173 -0
  19. package/dist/compositions.js.map +1 -0
  20. package/dist/core/session/session-manager.d.ts +9 -2
  21. package/dist/core/session/session-manager.d.ts.map +1 -1
  22. package/dist/core/session/session-manager.js +28 -0
  23. package/dist/core/session/session-manager.js.map +1 -1
  24. package/dist/core/session/session-storage.d.ts.map +1 -1
  25. package/dist/core/session/session-storage.js +20 -3
  26. package/dist/core/session/session-storage.js.map +1 -1
  27. package/dist/core/session/types.d.ts +11 -2
  28. package/dist/core/session/types.d.ts.map +1 -1
  29. package/dist/core/session/types.js.map +1 -1
  30. package/dist/core/session-tools.d.ts +5 -0
  31. package/dist/core/session-tools.d.ts.map +1 -0
  32. package/dist/core/session-tools.js +17 -0
  33. package/dist/core/session-tools.js.map +1 -0
  34. package/dist/core/session.d.ts +19 -7
  35. package/dist/core/session.d.ts.map +1 -1
  36. package/dist/core/session.js +71 -39
  37. package/dist/core/session.js.map +1 -1
  38. package/dist/core/tools/bash.d.ts +1 -38
  39. package/dist/core/tools/bash.d.ts.map +1 -1
  40. package/dist/core/tools/bash.js +1 -207
  41. package/dist/core/tools/bash.js.map +1 -1
  42. package/dist/core/tools/edit-diff.d.ts +1 -24
  43. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  44. package/dist/core/tools/edit-diff.js +1 -135
  45. package/dist/core/tools/edit-diff.js.map +1 -1
  46. package/dist/core/tools/edit.d.ts +1 -26
  47. package/dist/core/tools/edit.d.ts.map +1 -1
  48. package/dist/core/tools/edit.js +1 -140
  49. package/dist/core/tools/edit.js.map +1 -1
  50. package/dist/core/tools/file-mutation-queue.d.ts +1 -1
  51. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  52. package/dist/core/tools/file-mutation-queue.js +1 -23
  53. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  54. package/dist/core/tools/file-search.d.ts +1 -13
  55. package/dist/core/tools/file-search.d.ts.map +1 -1
  56. package/dist/core/tools/file-search.js +1 -98
  57. package/dist/core/tools/file-search.js.map +1 -1
  58. package/dist/core/tools/glob.d.ts +1 -10
  59. package/dist/core/tools/glob.d.ts.map +1 -1
  60. package/dist/core/tools/glob.js +1 -43
  61. package/dist/core/tools/glob.js.map +1 -1
  62. package/dist/core/tools/grep.d.ts +1 -12
  63. package/dist/core/tools/grep.d.ts.map +1 -1
  64. package/dist/core/tools/grep.js +1 -66
  65. package/dist/core/tools/grep.js.map +1 -1
  66. package/dist/core/tools/load-skill.d.ts +1 -9
  67. package/dist/core/tools/load-skill.d.ts.map +1 -1
  68. package/dist/core/tools/load-skill.js +1 -21
  69. package/dist/core/tools/load-skill.js.map +1 -1
  70. package/dist/core/tools/path-boundary.d.ts +1 -4
  71. package/dist/core/tools/path-boundary.d.ts.map +1 -1
  72. package/dist/core/tools/path-boundary.js +1 -65
  73. package/dist/core/tools/path-boundary.js.map +1 -1
  74. package/dist/core/tools/read.d.ts +1 -16
  75. package/dist/core/tools/read.d.ts.map +1 -1
  76. package/dist/core/tools/read.js +1 -131
  77. package/dist/core/tools/read.js.map +1 -1
  78. package/dist/core/tools/write.d.ts +1 -16
  79. package/dist/core/tools/write.d.ts.map +1 -1
  80. package/dist/core/tools/write.js +1 -44
  81. package/dist/core/tools/write.js.map +1 -1
  82. package/dist/entry.js +30 -28
  83. package/dist/entry.js.map +1 -1
  84. package/dist/index.d.ts +6 -3
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +6 -3
  87. package/dist/index.js.map +1 -1
  88. package/dist/interactive/builtin.d.ts +17 -0
  89. package/dist/interactive/builtin.d.ts.map +1 -0
  90. package/dist/interactive/builtin.js +47 -0
  91. package/dist/interactive/builtin.js.map +1 -0
  92. package/dist/interactive/controller.d.ts +85 -0
  93. package/dist/interactive/controller.d.ts.map +1 -0
  94. package/dist/interactive/controller.js +211 -0
  95. package/dist/interactive/controller.js.map +1 -0
  96. package/dist/interactive/index.d.ts +3 -0
  97. package/dist/interactive/index.d.ts.map +1 -0
  98. package/dist/interactive/index.js +3 -0
  99. package/dist/interactive/index.js.map +1 -0
  100. package/dist/interactive-host-entry.d.ts +7 -0
  101. package/dist/interactive-host-entry.d.ts.map +1 -0
  102. package/dist/interactive-host-entry.js +123 -0
  103. package/dist/interactive-host-entry.js.map +1 -0
  104. package/dist/interactive-profile.d.ts +18 -0
  105. package/dist/interactive-profile.d.ts.map +1 -0
  106. package/dist/interactive-profile.js +290 -0
  107. package/dist/interactive-profile.js.map +1 -0
  108. package/dist/interactive-resources-entry.d.ts +7 -0
  109. package/dist/interactive-resources-entry.d.ts.map +1 -0
  110. package/dist/interactive-resources-entry.js +42 -0
  111. package/dist/interactive-resources-entry.js.map +1 -0
  112. package/dist/legacy-main.d.ts +39 -0
  113. package/dist/legacy-main.d.ts.map +1 -0
  114. package/dist/legacy-main.js +611 -0
  115. package/dist/legacy-main.js.map +1 -0
  116. package/dist/main.d.ts +15 -34
  117. package/dist/main.d.ts.map +1 -1
  118. package/dist/main.js +130 -518
  119. package/dist/main.js.map +1 -1
  120. package/dist/mcp/entries.d.ts +42 -0
  121. package/dist/mcp/entries.d.ts.map +1 -0
  122. package/dist/mcp/entries.js +98 -0
  123. package/dist/mcp/entries.js.map +1 -0
  124. package/dist/mcp-client-entry.d.ts +5 -0
  125. package/dist/mcp-client-entry.d.ts.map +1 -0
  126. package/dist/mcp-client-entry.js +6 -0
  127. package/dist/mcp-client-entry.js.map +1 -0
  128. package/dist/mcp-config-entry.d.ts +5 -0
  129. package/dist/mcp-config-entry.d.ts.map +1 -0
  130. package/dist/mcp-config-entry.js +6 -0
  131. package/dist/mcp-config-entry.js.map +1 -0
  132. package/dist/mcp-tools-entry.d.ts +5 -0
  133. package/dist/mcp-tools-entry.d.ts.map +1 -0
  134. package/dist/mcp-tools-entry.js +6 -0
  135. package/dist/mcp-tools-entry.js.map +1 -0
  136. package/dist/mcp-transport-entry.d.ts +5 -0
  137. package/dist/mcp-transport-entry.d.ts.map +1 -0
  138. package/dist/mcp-transport-entry.js +6 -0
  139. package/dist/mcp-transport-entry.js.map +1 -0
  140. package/dist/modes/interactive-entry.d.ts +18 -0
  141. package/dist/modes/interactive-entry.d.ts.map +1 -0
  142. package/dist/modes/interactive-entry.js +21 -0
  143. package/dist/modes/interactive-entry.js.map +1 -0
  144. package/dist/modes/interactive.d.ts +16 -6
  145. package/dist/modes/interactive.d.ts.map +1 -1
  146. package/dist/modes/interactive.js +81 -32
  147. package/dist/modes/interactive.js.map +1 -1
  148. package/dist/modes/json-entry.d.ts +13 -0
  149. package/dist/modes/json-entry.d.ts.map +1 -0
  150. package/dist/modes/json-entry.js +14 -0
  151. package/dist/modes/json-entry.js.map +1 -0
  152. package/dist/modes/json.d.ts +4 -1
  153. package/dist/modes/json.d.ts.map +1 -1
  154. package/dist/modes/json.js +3 -3
  155. package/dist/modes/json.js.map +1 -1
  156. package/dist/modes/print-entry.d.ts +10 -0
  157. package/dist/modes/print-entry.d.ts.map +1 -0
  158. package/dist/modes/print-entry.js +11 -0
  159. package/dist/modes/print-entry.js.map +1 -0
  160. package/dist/modes/tree-selector.d.ts.map +1 -1
  161. package/dist/modes/tree-selector.js +4 -0
  162. package/dist/modes/tree-selector.js.map +1 -1
  163. package/dist/plugin-dump-composition.d.ts +6 -0
  164. package/dist/plugin-dump-composition.d.ts.map +1 -0
  165. package/dist/plugin-dump-composition.js +7 -0
  166. package/dist/plugin-dump-composition.js.map +1 -0
  167. package/dist/plugin-inventory.d.ts +7 -0
  168. package/dist/plugin-inventory.d.ts.map +1 -0
  169. package/dist/plugin-inventory.js +8 -0
  170. package/dist/plugin-inventory.js.map +1 -0
  171. package/dist/plugin-manager.d.ts +6 -0
  172. package/dist/plugin-manager.d.ts.map +1 -0
  173. package/dist/plugin-manager.js +7 -0
  174. package/dist/plugin-manager.js.map +1 -0
  175. package/dist/plugin-trace.d.ts +6 -0
  176. package/dist/plugin-trace.d.ts.map +1 -0
  177. package/dist/plugin-trace.js +7 -0
  178. package/dist/plugin-trace.js.map +1 -0
  179. package/dist/plugins/dynamic-broker.d.ts +76 -0
  180. package/dist/plugins/dynamic-broker.d.ts.map +1 -0
  181. package/dist/plugins/dynamic-broker.js +852 -0
  182. package/dist/plugins/dynamic-broker.js.map +1 -0
  183. package/dist/plugins/profile.d.ts +15 -0
  184. package/dist/plugins/profile.d.ts.map +1 -0
  185. package/dist/plugins/profile.js +34 -0
  186. package/dist/plugins/profile.js.map +1 -0
  187. package/dist/plugins/runtime-host.d.ts +37 -0
  188. package/dist/plugins/runtime-host.d.ts.map +1 -0
  189. package/dist/plugins/runtime-host.js +95 -0
  190. package/dist/plugins/runtime-host.js.map +1 -0
  191. package/dist/plugins/trust.d.ts +8 -0
  192. package/dist/plugins/trust.d.ts.map +1 -0
  193. package/dist/plugins/trust.js +59 -0
  194. package/dist/plugins/trust.js.map +1 -0
  195. package/dist/project-trust-entry.d.ts +15 -0
  196. package/dist/project-trust-entry.d.ts.map +1 -0
  197. package/dist/project-trust-entry.js +15 -0
  198. package/dist/project-trust-entry.js.map +1 -0
  199. package/dist/rpc/client.d.ts +78 -6
  200. package/dist/rpc/client.d.ts.map +1 -1
  201. package/dist/rpc/client.js +172 -4
  202. package/dist/rpc/client.js.map +1 -1
  203. package/dist/rpc/dispatcher.d.ts +121 -0
  204. package/dist/rpc/dispatcher.d.ts.map +1 -0
  205. package/dist/rpc/dispatcher.js +994 -0
  206. package/dist/rpc/dispatcher.js.map +1 -0
  207. package/dist/rpc/index.d.ts +2 -0
  208. package/dist/rpc/index.d.ts.map +1 -1
  209. package/dist/rpc/index.js +2 -0
  210. package/dist/rpc/index.js.map +1 -1
  211. package/dist/rpc/lifecycle.d.ts +6 -0
  212. package/dist/rpc/lifecycle.d.ts.map +1 -0
  213. package/dist/rpc/lifecycle.js +24 -0
  214. package/dist/rpc/lifecycle.js.map +1 -0
  215. package/dist/rpc/protocol.d.ts +112 -31
  216. package/dist/rpc/protocol.d.ts.map +1 -1
  217. package/dist/rpc/protocol.js +569 -117
  218. package/dist/rpc/protocol.js.map +1 -1
  219. package/dist/rpc/server-service.d.ts +7 -0
  220. package/dist/rpc/server-service.d.ts.map +1 -0
  221. package/dist/rpc/server-service.js +3 -0
  222. package/dist/rpc/server-service.js.map +1 -0
  223. package/dist/rpc/server.d.ts +16 -19
  224. package/dist/rpc/server.d.ts.map +1 -1
  225. package/dist/rpc/server.js +59 -115
  226. package/dist/rpc/server.js.map +1 -1
  227. package/dist/rpc-client-sdk-entry.d.ts +12 -0
  228. package/dist/rpc-client-sdk-entry.d.ts.map +1 -0
  229. package/dist/rpc-client-sdk-entry.js +11 -0
  230. package/dist/rpc-client-sdk-entry.js.map +1 -0
  231. package/dist/rpc-entry.js +50 -18
  232. package/dist/rpc-entry.js.map +1 -1
  233. package/dist/rpc-server-entry.d.ts +8 -0
  234. package/dist/rpc-server-entry.d.ts.map +1 -0
  235. package/dist/rpc-server-entry.js +67 -0
  236. package/dist/rpc-server-entry.js.map +1 -0
  237. package/dist/runtime/attachment-store.d.ts +13 -0
  238. package/dist/runtime/attachment-store.d.ts.map +1 -0
  239. package/dist/runtime/attachment-store.js +93 -0
  240. package/dist/runtime/attachment-store.js.map +1 -0
  241. package/dist/runtime/bootstrap.d.ts +14 -0
  242. package/dist/runtime/bootstrap.d.ts.map +1 -0
  243. package/dist/runtime/bootstrap.js +29 -0
  244. package/dist/runtime/bootstrap.js.map +1 -0
  245. package/dist/runtime/interactive-host-service.d.ts +22 -0
  246. package/dist/runtime/interactive-host-service.d.ts.map +1 -0
  247. package/dist/runtime/interactive-host-service.js +2 -0
  248. package/dist/runtime/interactive-host-service.js.map +1 -0
  249. package/dist/runtime/interactive-resource-service.d.ts +11 -0
  250. package/dist/runtime/interactive-resource-service.d.ts.map +1 -0
  251. package/dist/runtime/interactive-resource-service.js +3 -0
  252. package/dist/runtime/interactive-resource-service.js.map +1 -0
  253. package/dist/runtime/plugin-inventory-entry.d.ts +5 -0
  254. package/dist/runtime/plugin-inventory-entry.d.ts.map +1 -0
  255. package/dist/runtime/plugin-inventory-entry.js +18 -0
  256. package/dist/runtime/plugin-inventory-entry.js.map +1 -0
  257. package/dist/runtime/plugin-inventory-service.d.ts +8 -0
  258. package/dist/runtime/plugin-inventory-service.d.ts.map +1 -0
  259. package/dist/runtime/plugin-inventory-service.js +3 -0
  260. package/dist/runtime/plugin-inventory-service.js.map +1 -0
  261. package/dist/runtime/plugin-manager-entry.d.ts +20 -0
  262. package/dist/runtime/plugin-manager-entry.d.ts.map +1 -0
  263. package/dist/runtime/plugin-manager-entry.js +96 -0
  264. package/dist/runtime/plugin-manager-entry.js.map +1 -0
  265. package/dist/runtime/plugin-observability-entry.d.ts +12 -0
  266. package/dist/runtime/plugin-observability-entry.d.ts.map +1 -0
  267. package/dist/runtime/plugin-observability-entry.js +73 -0
  268. package/dist/runtime/plugin-observability-entry.js.map +1 -0
  269. package/dist/runtime/product-host.d.ts +91 -0
  270. package/dist/runtime/product-host.d.ts.map +1 -0
  271. package/dist/runtime/product-host.js +529 -0
  272. package/dist/runtime/product-host.js.map +1 -0
  273. package/dist/runtime/session-factory.d.ts +17 -0
  274. package/dist/runtime/session-factory.d.ts.map +1 -0
  275. package/dist/runtime/session-factory.js +75 -0
  276. package/dist/runtime/session-factory.js.map +1 -0
  277. package/dist/runtime/session-host.d.ts +152 -0
  278. package/dist/runtime/session-host.d.ts.map +1 -0
  279. package/dist/runtime/session-host.js +895 -0
  280. package/dist/runtime/session-host.js.map +1 -0
  281. package/dist/runtime-core-entry.d.ts +11 -0
  282. package/dist/runtime-core-entry.d.ts.map +1 -0
  283. package/dist/runtime-core-entry.js +9 -0
  284. package/dist/runtime-core-entry.js.map +1 -0
  285. package/dist/session-factory-entry.d.ts +7 -0
  286. package/dist/session-factory-entry.d.ts.map +1 -0
  287. package/dist/session-factory-entry.js +9 -0
  288. package/dist/session-factory-entry.js.map +1 -0
  289. package/dist/session-store-jsonl-entry.d.ts +7 -0
  290. package/dist/session-store-jsonl-entry.d.ts.map +1 -0
  291. package/dist/session-store-jsonl-entry.js +33 -0
  292. package/dist/session-store-jsonl-entry.js.map +1 -0
  293. package/dist/startup.d.ts +12 -1
  294. package/dist/startup.d.ts.map +1 -1
  295. package/dist/startup.js +106 -15
  296. package/dist/startup.js.map +1 -1
  297. package/dist/subagents/index.d.ts +2 -0
  298. package/dist/subagents/index.d.ts.map +1 -0
  299. package/dist/subagents/index.js +2 -0
  300. package/dist/subagents/index.js.map +1 -0
  301. package/dist/subagents/service.d.ts +50 -0
  302. package/dist/subagents/service.d.ts.map +1 -0
  303. package/dist/subagents/service.js +521 -0
  304. package/dist/subagents/service.js.map +1 -0
  305. package/dist/ui-host.d.ts +125 -0
  306. package/dist/ui-host.d.ts.map +1 -0
  307. package/dist/ui-host.js +275 -0
  308. package/dist/ui-host.js.map +1 -0
  309. package/dist/web/assets/index-BwfuGMvv.css +1 -0
  310. package/dist/web/assets/index-DPUaJqFJ.js +41 -0
  311. package/dist/web/assets/index-DPUaJqFJ.js.map +1 -0
  312. package/dist/web/index.html +14 -0
  313. package/dist/web-command.d.ts +11 -0
  314. package/dist/web-command.d.ts.map +1 -0
  315. package/dist/web-command.js +114 -0
  316. package/dist/web-command.js.map +1 -0
  317. package/dist/web-frontend.d.ts +73 -0
  318. package/dist/web-frontend.d.ts.map +1 -0
  319. package/dist/web-frontend.js +249 -0
  320. package/dist/web-frontend.js.map +1 -0
  321. package/dist/web.d.ts +254 -0
  322. package/dist/web.d.ts.map +1 -0
  323. package/dist/web.js +740 -0
  324. package/dist/web.js.map +1 -0
  325. package/dist/webui-entry.d.ts +3 -0
  326. package/dist/webui-entry.d.ts.map +1 -0
  327. package/dist/webui-entry.js +73 -0
  328. package/dist/webui-entry.js.map +1 -0
  329. package/dist/webui.d.ts +91 -0
  330. package/dist/webui.d.ts.map +1 -0
  331. package/dist/webui.js +882 -0
  332. package/dist/webui.js.map +1 -0
  333. package/package.json +89 -8
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="color-scheme" content="dark light" />
7
+ <title>di-code</title>
8
+ <script type="module" crossorigin src="/assets/index-DPUaJqFJ.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-BwfuGMvv.css">
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ </body>
14
+ </html>
@@ -0,0 +1,11 @@
1
+ import type { CliCommand } from "./cli.ts";
2
+ import { type StartupConfiguration, type StartupRuntime } from "./startup.ts";
3
+ type WebCommand = Extract<CliCommand, {
4
+ readonly kind: "web";
5
+ }>;
6
+ /** Starts the Web settings flow with Faux when no configured runtime can be created yet. */
7
+ export declare function resolveWebRuntime(configuration: StartupConfiguration): StartupRuntime;
8
+ /** Starts the local same-origin SPA, HTTP/SSE RPC transport, boot route, and health endpoint. */
9
+ export declare function runWebCommand(command: WebCommand): Promise<number>;
10
+ export {};
11
+ //# sourceMappingURL=web-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-command.d.ts","sourceRoot":"","sources":["../src/web-command.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAQ3C,OAAO,EAGN,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACnB,MAAM,cAAc,CAAC;AAGtB,KAAK,UAAU,GAAG,OAAO,CAAC,UAAU,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,CAAC;AAEhE,4FAA4F;AAC5F,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,oBAAoB,GAAG,cAAc,CAMrF;AAgCD,iGAAiG;AACjG,wBAAsB,aAAa,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAiDxE"}
@@ -0,0 +1,114 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { access } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { workspaceCapabilityKey } from "@di-code/builtins";
7
+ import { createCompositionLoader } from "@di-code/plugin-loader";
8
+ import { createRootContext } from "@di-code/plugin-runtime";
9
+ import { importCompositionModule, resolveCompositionEntries, resolveManagedCompositionEntries, } from "./compositions.js";
10
+ import { createProjectTrustStore } from "./project-trust-entry.js";
11
+ import { disposeRpcComposition } from "./rpc/lifecycle.js";
12
+ import { loadStartupConfiguration, resolveStartupRuntime, } from "./startup.js";
13
+ import { WebUiServer } from "./webui.js";
14
+ /** Starts the Web settings flow with Faux when no configured runtime can be created yet. */
15
+ export function resolveWebRuntime(configuration) {
16
+ try {
17
+ return resolveStartupRuntime(configuration.environment, configuration.providers, configuration.defaults);
18
+ }
19
+ catch {
20
+ return resolveStartupRuntime({ DI_CODE_PROVIDER: "faux" }, []);
21
+ }
22
+ }
23
+ async function webAssetRoot() {
24
+ if (process.env.DI_CODE_WEB_STATIC_ROOT)
25
+ return await existingDirectory(process.env.DI_CODE_WEB_STATIC_ROOT);
26
+ const moduleDirectory = dirname(fileURLToPath(import.meta.url));
27
+ for (const candidate of [join(moduleDirectory, "web"), resolve(moduleDirectory, "../dist/web")]) {
28
+ try {
29
+ return await existingDirectory(candidate);
30
+ }
31
+ catch { }
32
+ }
33
+ throw new Error("Web assets are missing. Reinstall @di-code/coding-agent or run npm run build.");
34
+ }
35
+ async function existingDirectory(path) {
36
+ await access(resolve(path));
37
+ return resolve(path);
38
+ }
39
+ function developmentOrigin() {
40
+ const value = process.env.DI_CODE_WEB_DEV_ORIGIN;
41
+ if (!value)
42
+ return undefined;
43
+ let origin;
44
+ try {
45
+ origin = new URL(value);
46
+ }
47
+ catch {
48
+ throw new Error("DI_CODE_WEB_DEV_ORIGIN must be an http loopback origin.");
49
+ }
50
+ if (origin.protocol !== "http:" || (origin.hostname !== "127.0.0.1" && origin.hostname !== "::1"))
51
+ throw new Error("DI_CODE_WEB_DEV_ORIGIN must be an http loopback origin.");
52
+ return origin.origin;
53
+ }
54
+ /** Starts the local same-origin SPA, HTTP/SSE RPC transport, boot route, and health endpoint. */
55
+ export async function runWebCommand(command) {
56
+ const allowedRoot = resolve(process.cwd());
57
+ const agentDir = resolve(join(homedir(), ".di-code"));
58
+ const projectTrusted = (await createProjectTrustStore(join(agentDir, "trust.json")).get(allowedRoot)) === true;
59
+ const context = createRootContext({ id: "web-profile", mode: "webui", trustedProject: projectTrusted });
60
+ const loader = createCompositionLoader({
61
+ context,
62
+ entries: [
63
+ ...(await resolveCompositionEntries("webui", {
64
+ cwd: allowedRoot,
65
+ agentDir,
66
+ includeProjectComposition: projectTrusted,
67
+ allowedRoot,
68
+ })),
69
+ ...(await resolveManagedCompositionEntries(agentDir)),
70
+ ],
71
+ importModule: importCompositionModule,
72
+ projectTrusted,
73
+ });
74
+ let server;
75
+ try {
76
+ await loader.load();
77
+ const configuration = await loadStartupConfiguration(allowedRoot, process.env, agentDir);
78
+ const runtime = resolveWebRuntime(configuration);
79
+ server = new WebUiServer({
80
+ context,
81
+ allowedRoot: context.require(workspaceCapabilityKey).allowedRoot,
82
+ allowedWorkspaces: command.workspaces,
83
+ agentDir,
84
+ workspaceRegistryDir: resolve(join(homedir(), ".di-code")),
85
+ provider: runtime.provider,
86
+ model: runtime.model,
87
+ projectTrusted,
88
+ port: command.port,
89
+ token: randomBytes(32).toString("base64url"),
90
+ staticRoot: await webAssetRoot(),
91
+ developmentOrigin: developmentOrigin(),
92
+ });
93
+ const address = await server.listen();
94
+ process.stdout.write(`Web server listening at http://${address.host}:${address.port}\n`);
95
+ await waitForSignal();
96
+ return 0;
97
+ }
98
+ finally {
99
+ await server?.close();
100
+ await disposeRpcComposition(() => loader.dispose(), () => context.dispose());
101
+ }
102
+ }
103
+ function waitForSignal() {
104
+ return new Promise((resolveSignal) => {
105
+ const done = () => {
106
+ process.off("SIGINT", done);
107
+ process.off("SIGTERM", done);
108
+ resolveSignal();
109
+ };
110
+ process.once("SIGINT", done);
111
+ process.once("SIGTERM", done);
112
+ });
113
+ }
114
+ //# sourceMappingURL=web-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-command.js","sourceRoot":"","sources":["../src/web-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { access } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { workspaceCapabilityKey } from \"@di-code/builtins\";\nimport { createCompositionLoader } from \"@di-code/plugin-loader\";\nimport { createRootContext } from \"@di-code/plugin-runtime\";\nimport type { CliCommand } from \"./cli.ts\";\nimport {\n\timportCompositionModule,\n\tresolveCompositionEntries,\n\tresolveManagedCompositionEntries,\n} from \"./compositions.ts\";\nimport { createProjectTrustStore } from \"./project-trust-entry.ts\";\nimport { disposeRpcComposition } from \"./rpc/lifecycle.ts\";\nimport {\n\tloadStartupConfiguration,\n\tresolveStartupRuntime,\n\ttype StartupConfiguration,\n\ttype StartupRuntime,\n} from \"./startup.ts\";\nimport { WebUiServer } from \"./webui.ts\";\n\ntype WebCommand = Extract<CliCommand, { readonly kind: \"web\" }>;\n\n/** Starts the Web settings flow with Faux when no configured runtime can be created yet. */\nexport function resolveWebRuntime(configuration: StartupConfiguration): StartupRuntime {\n\ttry {\n\t\treturn resolveStartupRuntime(configuration.environment, configuration.providers, configuration.defaults);\n\t} catch {\n\t\treturn resolveStartupRuntime({ DI_CODE_PROVIDER: \"faux\" }, []);\n\t}\n}\n\nasync function webAssetRoot(): Promise<string> {\n\tif (process.env.DI_CODE_WEB_STATIC_ROOT) return await existingDirectory(process.env.DI_CODE_WEB_STATIC_ROOT);\n\tconst moduleDirectory = dirname(fileURLToPath(import.meta.url));\n\tfor (const candidate of [join(moduleDirectory, \"web\"), resolve(moduleDirectory, \"../dist/web\")]) {\n\t\ttry {\n\t\t\treturn await existingDirectory(candidate);\n\t\t} catch {}\n\t}\n\tthrow new Error(\"Web assets are missing. Reinstall @di-code/coding-agent or run npm run build.\");\n}\n\nasync function existingDirectory(path: string): Promise<string> {\n\tawait access(resolve(path));\n\treturn resolve(path);\n}\n\nfunction developmentOrigin(): string | undefined {\n\tconst value = process.env.DI_CODE_WEB_DEV_ORIGIN;\n\tif (!value) return undefined;\n\tlet origin: URL;\n\ttry {\n\t\torigin = new URL(value);\n\t} catch {\n\t\tthrow new Error(\"DI_CODE_WEB_DEV_ORIGIN must be an http loopback origin.\");\n\t}\n\tif (origin.protocol !== \"http:\" || (origin.hostname !== \"127.0.0.1\" && origin.hostname !== \"::1\"))\n\t\tthrow new Error(\"DI_CODE_WEB_DEV_ORIGIN must be an http loopback origin.\");\n\treturn origin.origin;\n}\n\n/** Starts the local same-origin SPA, HTTP/SSE RPC transport, boot route, and health endpoint. */\nexport async function runWebCommand(command: WebCommand): Promise<number> {\n\tconst allowedRoot = resolve(process.cwd());\n\tconst agentDir = resolve(join(homedir(), \".di-code\"));\n\tconst projectTrusted = (await createProjectTrustStore(join(agentDir, \"trust.json\")).get(allowedRoot)) === true;\n\tconst context = createRootContext({ id: \"web-profile\", mode: \"webui\", trustedProject: projectTrusted });\n\tconst loader = createCompositionLoader({\n\t\tcontext,\n\t\tentries: [\n\t\t\t...(await resolveCompositionEntries(\"webui\", {\n\t\t\t\tcwd: allowedRoot,\n\t\t\t\tagentDir,\n\t\t\t\tincludeProjectComposition: projectTrusted,\n\t\t\t\tallowedRoot,\n\t\t\t})),\n\t\t\t...(await resolveManagedCompositionEntries(agentDir)),\n\t\t],\n\t\timportModule: importCompositionModule,\n\t\tprojectTrusted,\n\t});\n\tlet server: WebUiServer | undefined;\n\ttry {\n\t\tawait loader.load();\n\t\tconst configuration = await loadStartupConfiguration(allowedRoot, process.env, agentDir);\n\t\tconst runtime = resolveWebRuntime(configuration);\n\t\tserver = new WebUiServer({\n\t\t\tcontext,\n\t\t\tallowedRoot: context.require(workspaceCapabilityKey).allowedRoot,\n\t\t\tallowedWorkspaces: command.workspaces,\n\t\t\tagentDir,\n\t\t\tworkspaceRegistryDir: resolve(join(homedir(), \".di-code\")),\n\t\t\tprovider: runtime.provider,\n\t\t\tmodel: runtime.model,\n\t\t\tprojectTrusted,\n\t\t\tport: command.port,\n\t\t\ttoken: randomBytes(32).toString(\"base64url\"),\n\t\t\tstaticRoot: await webAssetRoot(),\n\t\t\tdevelopmentOrigin: developmentOrigin(),\n\t\t});\n\t\tconst address = await server.listen();\n\t\tprocess.stdout.write(`Web server listening at http://${address.host}:${address.port}\\n`);\n\t\tawait waitForSignal();\n\t\treturn 0;\n\t} finally {\n\t\tawait server?.close();\n\t\tawait disposeRpcComposition(\n\t\t\t() => loader.dispose(),\n\t\t\t() => context.dispose(),\n\t\t);\n\t}\n}\n\nfunction waitForSignal(): Promise<void> {\n\treturn new Promise((resolveSignal) => {\n\t\tconst done = (): void => {\n\t\t\tprocess.off(\"SIGINT\", done);\n\t\t\tprocess.off(\"SIGTERM\", done);\n\t\t\tresolveSignal();\n\t\t};\n\t\tprocess.once(\"SIGINT\", done);\n\t\tprocess.once(\"SIGTERM\", done);\n\t});\n}\n"]}
@@ -0,0 +1,73 @@
1
+ import type { InteractiveControllerState } from "./interactive/controller.ts";
2
+ import { type WebAction, WebClient, type WebServerHello, type WebSocketConstructor, type WebTransport } from "./web.ts";
3
+ /** Small DOM surface so the browser frontend remains usable with SSR and DOM test doubles. */
4
+ export interface WebElementLike {
5
+ className: string;
6
+ textContent: string | null;
7
+ value?: string;
8
+ disabled?: boolean;
9
+ setAttribute(name: string, value: string): void;
10
+ append(...children: WebElementLike[]): void;
11
+ replaceChildren(...children: WebElementLike[]): void;
12
+ addEventListener(type: string, listener: (event: unknown) => void): void;
13
+ }
14
+ export interface WebDocumentLike {
15
+ readonly body: WebElementLike;
16
+ createElement(tagName: string): WebElementLike;
17
+ }
18
+ export interface WebFrontendOptions {
19
+ readonly client: WebClient;
20
+ readonly token: string;
21
+ readonly frontendId: string;
22
+ readonly document?: WebDocumentLike;
23
+ readonly root?: WebElementLike;
24
+ readonly title?: string;
25
+ }
26
+ export interface WebFrontendConnectionOptions {
27
+ readonly url: string;
28
+ readonly token: string;
29
+ readonly frontendId: string;
30
+ readonly document?: WebDocumentLike;
31
+ readonly root?: WebElementLike;
32
+ readonly title?: string;
33
+ readonly protocols?: string | string[];
34
+ readonly WebSocket?: WebSocketConstructor;
35
+ }
36
+ /** A complete browser UI for the versioned Web projection. It owns DOM and client state, never the Host. */
37
+ export declare class WebFrontendApp {
38
+ private readonly client;
39
+ private readonly token;
40
+ private readonly frontendId;
41
+ private readonly document?;
42
+ private readonly root?;
43
+ private readonly title;
44
+ private unsubscribe?;
45
+ private mounted;
46
+ private disposed;
47
+ private status;
48
+ private currentState;
49
+ private input?;
50
+ private transcript?;
51
+ private process?;
52
+ private error?;
53
+ private statusNode?;
54
+ private actionButtons;
55
+ constructor(options: WebFrontendOptions);
56
+ get state(): InteractiveControllerState | undefined;
57
+ /** Mounts the responsive UI and performs the initial authenticated handshake. */
58
+ start(): Promise<WebServerHello>;
59
+ /** Reconnects the browser client, replaying from the last event known by this page. */
60
+ reconnect(transport: WebTransport): Promise<WebServerHello>;
61
+ dispose(): void;
62
+ private mount;
63
+ private accept;
64
+ private submit;
65
+ private steer;
66
+ private runAction;
67
+ private setError;
68
+ private render;
69
+ }
70
+ /** Convenience factory for a browser page that owns a WebSocket client and DOM frontend. */
71
+ export declare function createWebFrontendApp(options: WebFrontendConnectionOptions): Promise<WebFrontendApp>;
72
+ export type WebFrontendAction = WebAction;
73
+ //# sourceMappingURL=web-frontend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-frontend.d.ts","sourceRoot":"","sources":["../src/web-frontend.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,249 @@
1
+ import { connectWebSocketTransport, WebClient, } from "./web.js";
2
+ const WEB_FRONTEND_STYLE = `
3
+ .di-web-app{box-sizing:border-box;min-height:100%;display:flex;flex-direction:column;background:#f6f7f9;color:#17202a;font:15px/1.5 system-ui,-apple-system,Segoe UI,sans-serif}
4
+ .di-web-app *{box-sizing:border-box}.di-web-header{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:14px 20px;border-bottom:1px solid #d8dde5;background:#fff}
5
+ .di-web-title{margin:0;font-size:18px;font-weight:650}.di-web-status{font-size:13px;color:#596579}.di-web-status[data-state=error]{color:#b42318}
6
+ .di-web-main{width:min(960px,100%);margin:0 auto;padding:24px 20px 16px;flex:1}.di-web-transcript{display:flex;flex-direction:column;gap:12px;white-space:pre-wrap;overflow-wrap:anywhere}
7
+ .di-web-message{padding:12px 14px;border:1px solid #d8dde5;border-radius:6px;background:#fff}.di-web-message[data-role=user]{border-left:3px solid #5b6b85}.di-web-message[data-role=assistant]{border-left:3px solid #147d64}.di-web-process{margin:16px 0;color:#596579;font-size:13px}.di-web-error{margin:12px 0;padding:10px 12px;border:1px solid #efb4ae;border-radius:6px;background:#fff5f4;color:#8e2117}
8
+ .di-web-composer{border-top:1px solid #d8dde5;background:#fff;padding:14px 20px;position:sticky;bottom:0}.di-web-composer-inner{width:min(960px,100%);margin:0 auto;display:flex;flex-direction:column;gap:10px}
9
+ .di-web-input{width:100%;min-height:72px;max-height:240px;resize:vertical;padding:10px 12px;border:1px solid #aeb8c6;border-radius:5px;font:inherit;color:inherit;background:#fff}.di-web-input:focus{outline:2px solid #6d8fc7;outline-offset:1px}
10
+ .di-web-actions{display:flex;flex-wrap:wrap;gap:8px}.di-web-button{border:1px solid #aeb8c6;border-radius:5px;padding:7px 12px;background:#fff;color:#17202a;font:inherit;cursor:pointer}.di-web-button:hover:not(:disabled){background:#f0f3f7}.di-web-button[data-primary=true]{border-color:#174ea6;background:#174ea6;color:#fff}.di-web-button:disabled{cursor:not-allowed;opacity:.5}
11
+ @media (max-width:600px){.di-web-header{padding:12px 14px}.di-web-main{padding:16px 14px 12px}.di-web-composer{padding:12px 14px}.di-web-button{flex:1 1 auto}}
12
+ `;
13
+ function element(document, tag, className, text) {
14
+ const node = document.createElement(tag);
15
+ node.className = className;
16
+ if (text !== undefined)
17
+ node.textContent = text;
18
+ return node;
19
+ }
20
+ function stateFrom(value) {
21
+ if (typeof value !== "object" || value === null)
22
+ return undefined;
23
+ return value;
24
+ }
25
+ /** A complete browser UI for the versioned Web projection. It owns DOM and client state, never the Host. */
26
+ export class WebFrontendApp {
27
+ client;
28
+ token;
29
+ frontendId;
30
+ document;
31
+ root;
32
+ title;
33
+ unsubscribe;
34
+ mounted = false;
35
+ disposed = false;
36
+ status = "Disconnected";
37
+ currentState;
38
+ input;
39
+ transcript;
40
+ process;
41
+ error;
42
+ statusNode;
43
+ actionButtons = new Map();
44
+ constructor(options) {
45
+ this.client = options.client;
46
+ this.token = options.token;
47
+ this.frontendId = options.frontendId;
48
+ this.document = options.document;
49
+ this.root = options.root;
50
+ this.title = options.title ?? "di-code";
51
+ }
52
+ get state() {
53
+ return this.currentState;
54
+ }
55
+ /** Mounts the responsive UI and performs the initial authenticated handshake. */
56
+ async start() {
57
+ if (this.disposed)
58
+ throw new Error("Web frontend is disposed.");
59
+ this.mount();
60
+ this.unsubscribe ??= this.client.subscribe((event) => this.accept(event));
61
+ try {
62
+ const hello = await this.client.connect(this.token, this.frontendId, this.client.eventId);
63
+ this.currentState = stateFrom(hello.state);
64
+ this.status = "Connected";
65
+ this.render();
66
+ return hello;
67
+ }
68
+ catch (cause) {
69
+ this.setError(cause instanceof Error ? cause.message : String(cause));
70
+ throw cause;
71
+ }
72
+ }
73
+ /** Reconnects the browser client, replaying from the last event known by this page. */
74
+ async reconnect(transport) {
75
+ if (this.disposed)
76
+ throw new Error("Web frontend is disposed.");
77
+ this.status = "Reconnecting";
78
+ this.render();
79
+ try {
80
+ const hello = await this.client.reconnect(transport, this.token, this.frontendId);
81
+ this.currentState = stateFrom(hello.state);
82
+ this.status = hello.resyncRequired ? "Connected (synced)" : "Connected";
83
+ this.render();
84
+ return hello;
85
+ }
86
+ catch (cause) {
87
+ this.setError(cause instanceof Error ? cause.message : String(cause));
88
+ throw cause;
89
+ }
90
+ }
91
+ dispose() {
92
+ if (this.disposed)
93
+ return;
94
+ this.disposed = true;
95
+ this.unsubscribe?.();
96
+ this.unsubscribe = undefined;
97
+ this.client.close();
98
+ }
99
+ mount() {
100
+ if (this.mounted)
101
+ return;
102
+ this.mounted = true;
103
+ if (!this.document || !this.root)
104
+ return;
105
+ const style = element(this.document, "style", "");
106
+ style.textContent = WEB_FRONTEND_STYLE;
107
+ const app = element(this.document, "div", "di-web-app");
108
+ const header = element(this.document, "header", "di-web-header");
109
+ header.setAttribute("role", "banner");
110
+ header.append(element(this.document, "h1", "di-web-title", this.title));
111
+ this.statusNode = element(this.document, "span", "di-web-status", this.status);
112
+ this.statusNode.setAttribute("aria-live", "polite");
113
+ header.append(this.statusNode);
114
+ const main = element(this.document, "main", "di-web-main");
115
+ this.transcript = element(this.document, "section", "di-web-transcript");
116
+ this.transcript.setAttribute("aria-live", "polite");
117
+ this.transcript.setAttribute("aria-label", "Conversation");
118
+ this.process = element(this.document, "div", "di-web-process");
119
+ this.error = element(this.document, "div", "di-web-error");
120
+ this.error.setAttribute("role", "alert");
121
+ main.append(this.transcript, this.process, this.error);
122
+ const composer = element(this.document, "form", "di-web-composer");
123
+ composer.addEventListener("submit", (event) => {
124
+ if (typeof event === "object" && event !== null && "preventDefault" in event) {
125
+ event.preventDefault();
126
+ }
127
+ void this.submit();
128
+ });
129
+ const composerInner = element(this.document, "div", "di-web-composer-inner");
130
+ this.input = element(this.document, "textarea", "di-web-input");
131
+ this.input.setAttribute("aria-label", "Message");
132
+ this.input.setAttribute("placeholder", "Send a message");
133
+ composerInner.append(this.input);
134
+ const actions = element(this.document, "div", "di-web-actions");
135
+ for (const [key, label, handler, primary] of [
136
+ ["submit", "Send", () => void this.submit(), true],
137
+ ["steer", "Steer", () => void this.steer(), false],
138
+ ["cancel", "Cancel", () => this.client.action({ type: "cancel" }), false],
139
+ ["retry", "Retry", () => void this.client.action({ type: "retry" }), false],
140
+ ["compact", "Compact", () => void this.client.action({ type: "compact" }), false],
141
+ ["create_session", "New session", () => void this.client.action({ type: "create_session" }), false],
142
+ ]) {
143
+ const button = element(this.document, "button", "di-web-button", label);
144
+ button.setAttribute("type", key === "submit" ? "submit" : "button");
145
+ button.setAttribute("data-primary", String(primary));
146
+ if (key !== "submit")
147
+ button.addEventListener("click", () => void this.runAction(handler));
148
+ actions.append(button);
149
+ this.actionButtons.set(key, button);
150
+ }
151
+ composerInner.append(actions);
152
+ composer.append(composerInner);
153
+ app.append(header, main, composer);
154
+ this.root.replaceChildren(style, app);
155
+ }
156
+ accept(event) {
157
+ if (event.type === "hello") {
158
+ this.currentState = stateFrom(event.message.state);
159
+ this.status = event.message.resyncRequired ? "Connected (synced)" : "Connected";
160
+ }
161
+ else if (event.type === "event") {
162
+ if (event.message.event.type === "state")
163
+ this.currentState = event.message.event.state;
164
+ }
165
+ else if (event.type === "error") {
166
+ this.status = "Connection error";
167
+ this.setError(event.message);
168
+ }
169
+ else if (event.type === "closed")
170
+ this.status = "Disconnected";
171
+ this.render();
172
+ }
173
+ async submit() {
174
+ const text = this.input?.value?.trim() ?? "";
175
+ if (!text)
176
+ return;
177
+ if (this.input)
178
+ this.input.value = "";
179
+ await this.runAction(() => this.client.action({ type: "submit", input: text }));
180
+ }
181
+ async steer() {
182
+ const text = this.input?.value?.trim() ?? "";
183
+ if (!text)
184
+ return;
185
+ if (this.input)
186
+ this.input.value = "";
187
+ await this.runAction(() => this.client.action({ type: "steer", input: text }));
188
+ }
189
+ async runAction(action) {
190
+ try {
191
+ await action();
192
+ }
193
+ catch (cause) {
194
+ this.setError(cause instanceof Error ? cause.message : String(cause));
195
+ }
196
+ }
197
+ setError(message) {
198
+ if (this.error)
199
+ this.error.textContent = message;
200
+ this.status = "Connection error";
201
+ this.render();
202
+ }
203
+ render() {
204
+ if (this.statusNode) {
205
+ this.statusNode.textContent = this.status;
206
+ this.statusNode.setAttribute("data-state", this.status.includes("error") ? "error" : "ok");
207
+ }
208
+ if (!this.transcript || !this.process || !this.document)
209
+ return;
210
+ const state = this.currentState;
211
+ this.transcript.replaceChildren();
212
+ for (const item of state?.messageItems ?? []) {
213
+ if (item.role !== "user" && item.role !== "assistant")
214
+ continue;
215
+ const message = element(this.document, "article", "di-web-message", item.text);
216
+ message.setAttribute("data-role", item.role);
217
+ this.transcript.append(message);
218
+ }
219
+ this.process.textContent = [
220
+ ...(state?.toolStatus ?? []),
221
+ ...(state?.streamingText ? [state.streamingText] : []),
222
+ ...(state?.queue?.length ? [`Queued: ${state.queue.length}`] : []),
223
+ ].join("\n");
224
+ if (this.error && !state?.error && this.status !== "Connection error")
225
+ this.error.textContent = "";
226
+ if (state?.error && this.error)
227
+ this.error.textContent = state.error;
228
+ for (const [key, button] of this.actionButtons) {
229
+ button.disabled = Boolean(state?.busy) && ["submit", "retry", "compact", "create_session"].includes(key);
230
+ }
231
+ }
232
+ }
233
+ /** Convenience factory for a browser page that owns a WebSocket client and DOM frontend. */
234
+ export async function createWebFrontendApp(options) {
235
+ const transport = await connectWebSocketTransport(options.url, {
236
+ protocols: options.protocols,
237
+ WebSocket: options.WebSocket,
238
+ });
239
+ const app = new WebFrontendApp({ ...options, client: new WebClient(transport) });
240
+ try {
241
+ await app.start();
242
+ return app;
243
+ }
244
+ catch (cause) {
245
+ app.dispose();
246
+ throw cause;
247
+ }
248
+ }
249
+ //# sourceMappingURL=web-frontend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-frontend.js","sourceRoot":"","sources":["../src/web-frontend.ts"],"names":[],"mappings":"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","sourcesContent":["import type { InteractiveControllerState } from \"./interactive/controller.ts\";\nimport {\n\tconnectWebSocketTransport,\n\ttype WebAction,\n\tWebClient,\n\ttype WebClientEvent,\n\ttype WebServerHello,\n\ttype WebSocketConstructor,\n\ttype WebTransport,\n} from \"./web.ts\";\n\n/** Small DOM surface so the browser frontend remains usable with SSR and DOM test doubles. */\nexport interface WebElementLike {\n\tclassName: string;\n\ttextContent: string | null;\n\tvalue?: string;\n\tdisabled?: boolean;\n\tsetAttribute(name: string, value: string): void;\n\tappend(...children: WebElementLike[]): void;\n\treplaceChildren(...children: WebElementLike[]): void;\n\taddEventListener(type: string, listener: (event: unknown) => void): void;\n}\n\nexport interface WebDocumentLike {\n\treadonly body: WebElementLike;\n\tcreateElement(tagName: string): WebElementLike;\n}\n\nexport interface WebFrontendOptions {\n\treadonly client: WebClient;\n\treadonly token: string;\n\treadonly frontendId: string;\n\treadonly document?: WebDocumentLike;\n\treadonly root?: WebElementLike;\n\treadonly title?: string;\n}\n\nexport interface WebFrontendConnectionOptions {\n\treadonly url: string;\n\treadonly token: string;\n\treadonly frontendId: string;\n\treadonly document?: WebDocumentLike;\n\treadonly root?: WebElementLike;\n\treadonly title?: string;\n\treadonly protocols?: string | string[];\n\treadonly WebSocket?: WebSocketConstructor;\n}\n\nconst WEB_FRONTEND_STYLE = `\n.di-web-app{box-sizing:border-box;min-height:100%;display:flex;flex-direction:column;background:#f6f7f9;color:#17202a;font:15px/1.5 system-ui,-apple-system,Segoe UI,sans-serif}\n.di-web-app *{box-sizing:border-box}.di-web-header{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:14px 20px;border-bottom:1px solid #d8dde5;background:#fff}\n.di-web-title{margin:0;font-size:18px;font-weight:650}.di-web-status{font-size:13px;color:#596579}.di-web-status[data-state=error]{color:#b42318}\n.di-web-main{width:min(960px,100%);margin:0 auto;padding:24px 20px 16px;flex:1}.di-web-transcript{display:flex;flex-direction:column;gap:12px;white-space:pre-wrap;overflow-wrap:anywhere}\n.di-web-message{padding:12px 14px;border:1px solid #d8dde5;border-radius:6px;background:#fff}.di-web-message[data-role=user]{border-left:3px solid #5b6b85}.di-web-message[data-role=assistant]{border-left:3px solid #147d64}.di-web-process{margin:16px 0;color:#596579;font-size:13px}.di-web-error{margin:12px 0;padding:10px 12px;border:1px solid #efb4ae;border-radius:6px;background:#fff5f4;color:#8e2117}\n.di-web-composer{border-top:1px solid #d8dde5;background:#fff;padding:14px 20px;position:sticky;bottom:0}.di-web-composer-inner{width:min(960px,100%);margin:0 auto;display:flex;flex-direction:column;gap:10px}\n.di-web-input{width:100%;min-height:72px;max-height:240px;resize:vertical;padding:10px 12px;border:1px solid #aeb8c6;border-radius:5px;font:inherit;color:inherit;background:#fff}.di-web-input:focus{outline:2px solid #6d8fc7;outline-offset:1px}\n.di-web-actions{display:flex;flex-wrap:wrap;gap:8px}.di-web-button{border:1px solid #aeb8c6;border-radius:5px;padding:7px 12px;background:#fff;color:#17202a;font:inherit;cursor:pointer}.di-web-button:hover:not(:disabled){background:#f0f3f7}.di-web-button[data-primary=true]{border-color:#174ea6;background:#174ea6;color:#fff}.di-web-button:disabled{cursor:not-allowed;opacity:.5}\n@media (max-width:600px){.di-web-header{padding:12px 14px}.di-web-main{padding:16px 14px 12px}.di-web-composer{padding:12px 14px}.di-web-button{flex:1 1 auto}}\n`;\n\nfunction element(document: WebDocumentLike, tag: string, className: string, text?: string): WebElementLike {\n\tconst node = document.createElement(tag);\n\tnode.className = className;\n\tif (text !== undefined) node.textContent = text;\n\treturn node;\n}\n\nfunction stateFrom(value: unknown): InteractiveControllerState | undefined {\n\tif (typeof value !== \"object\" || value === null) return undefined;\n\treturn value as InteractiveControllerState;\n}\n\n/** A complete browser UI for the versioned Web projection. It owns DOM and client state, never the Host. */\nexport class WebFrontendApp {\n\tprivate readonly client: WebClient;\n\tprivate readonly token: string;\n\tprivate readonly frontendId: string;\n\tprivate readonly document?: WebDocumentLike;\n\tprivate readonly root?: WebElementLike;\n\tprivate readonly title: string;\n\tprivate unsubscribe?: () => void;\n\tprivate mounted = false;\n\tprivate disposed = false;\n\tprivate status = \"Disconnected\";\n\tprivate currentState: InteractiveControllerState | undefined;\n\tprivate input?: WebElementLike;\n\tprivate transcript?: WebElementLike;\n\tprivate process?: WebElementLike;\n\tprivate error?: WebElementLike;\n\tprivate statusNode?: WebElementLike;\n\tprivate actionButtons = new Map<string, WebElementLike>();\n\n\tconstructor(options: WebFrontendOptions) {\n\t\tthis.client = options.client;\n\t\tthis.token = options.token;\n\t\tthis.frontendId = options.frontendId;\n\t\tthis.document = options.document;\n\t\tthis.root = options.root;\n\t\tthis.title = options.title ?? \"di-code\";\n\t}\n\n\tget state(): InteractiveControllerState | undefined {\n\t\treturn this.currentState;\n\t}\n\n\t/** Mounts the responsive UI and performs the initial authenticated handshake. */\n\tasync start(): Promise<WebServerHello> {\n\t\tif (this.disposed) throw new Error(\"Web frontend is disposed.\");\n\t\tthis.mount();\n\t\tthis.unsubscribe ??= this.client.subscribe((event) => this.accept(event));\n\t\ttry {\n\t\t\tconst hello = await this.client.connect(this.token, this.frontendId, this.client.eventId);\n\t\t\tthis.currentState = stateFrom(hello.state);\n\t\t\tthis.status = \"Connected\";\n\t\t\tthis.render();\n\t\t\treturn hello;\n\t\t} catch (cause) {\n\t\t\tthis.setError(cause instanceof Error ? cause.message : String(cause));\n\t\t\tthrow cause;\n\t\t}\n\t}\n\n\t/** Reconnects the browser client, replaying from the last event known by this page. */\n\tasync reconnect(transport: WebTransport): Promise<WebServerHello> {\n\t\tif (this.disposed) throw new Error(\"Web frontend is disposed.\");\n\t\tthis.status = \"Reconnecting\";\n\t\tthis.render();\n\t\ttry {\n\t\t\tconst hello = await this.client.reconnect(transport, this.token, this.frontendId);\n\t\t\tthis.currentState = stateFrom(hello.state);\n\t\t\tthis.status = hello.resyncRequired ? \"Connected (synced)\" : \"Connected\";\n\t\t\tthis.render();\n\t\t\treturn hello;\n\t\t} catch (cause) {\n\t\t\tthis.setError(cause instanceof Error ? cause.message : String(cause));\n\t\t\tthrow cause;\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\t\tthis.unsubscribe?.();\n\t\tthis.unsubscribe = undefined;\n\t\tthis.client.close();\n\t}\n\n\tprivate mount(): void {\n\t\tif (this.mounted) return;\n\t\tthis.mounted = true;\n\t\tif (!this.document || !this.root) return;\n\t\tconst style = element(this.document, \"style\", \"\");\n\t\tstyle.textContent = WEB_FRONTEND_STYLE;\n\t\tconst app = element(this.document, \"div\", \"di-web-app\");\n\t\tconst header = element(this.document, \"header\", \"di-web-header\");\n\t\theader.setAttribute(\"role\", \"banner\");\n\t\theader.append(element(this.document, \"h1\", \"di-web-title\", this.title));\n\t\tthis.statusNode = element(this.document, \"span\", \"di-web-status\", this.status);\n\t\tthis.statusNode.setAttribute(\"aria-live\", \"polite\");\n\t\theader.append(this.statusNode);\n\t\tconst main = element(this.document, \"main\", \"di-web-main\");\n\t\tthis.transcript = element(this.document, \"section\", \"di-web-transcript\");\n\t\tthis.transcript.setAttribute(\"aria-live\", \"polite\");\n\t\tthis.transcript.setAttribute(\"aria-label\", \"Conversation\");\n\t\tthis.process = element(this.document, \"div\", \"di-web-process\");\n\t\tthis.error = element(this.document, \"div\", \"di-web-error\");\n\t\tthis.error.setAttribute(\"role\", \"alert\");\n\t\tmain.append(this.transcript, this.process, this.error);\n\t\tconst composer = element(this.document, \"form\", \"di-web-composer\");\n\t\tcomposer.addEventListener(\"submit\", (event) => {\n\t\t\tif (typeof event === \"object\" && event !== null && \"preventDefault\" in event) {\n\t\t\t\t(event as { preventDefault(): void }).preventDefault();\n\t\t\t}\n\t\t\tvoid this.submit();\n\t\t});\n\t\tconst composerInner = element(this.document, \"div\", \"di-web-composer-inner\");\n\t\tthis.input = element(this.document, \"textarea\", \"di-web-input\");\n\t\tthis.input.setAttribute(\"aria-label\", \"Message\");\n\t\tthis.input.setAttribute(\"placeholder\", \"Send a message\");\n\t\tcomposerInner.append(this.input);\n\t\tconst actions = element(this.document, \"div\", \"di-web-actions\");\n\t\tfor (const [key, label, handler, primary] of [\n\t\t\t[\"submit\", \"Send\", () => void this.submit(), true],\n\t\t\t[\"steer\", \"Steer\", () => void this.steer(), false],\n\t\t\t[\"cancel\", \"Cancel\", () => this.client.action({ type: \"cancel\" }), false],\n\t\t\t[\"retry\", \"Retry\", () => void this.client.action({ type: \"retry\" }), false],\n\t\t\t[\"compact\", \"Compact\", () => void this.client.action({ type: \"compact\" }), false],\n\t\t\t[\"create_session\", \"New session\", () => void this.client.action({ type: \"create_session\" }), false],\n\t\t] as const) {\n\t\t\tconst button = element(this.document, \"button\", \"di-web-button\", label);\n\t\t\tbutton.setAttribute(\"type\", key === \"submit\" ? \"submit\" : \"button\");\n\t\t\tbutton.setAttribute(\"data-primary\", String(primary));\n\t\t\tif (key !== \"submit\") button.addEventListener(\"click\", () => void this.runAction(handler));\n\t\t\tactions.append(button);\n\t\t\tthis.actionButtons.set(key, button);\n\t\t}\n\t\tcomposerInner.append(actions);\n\t\tcomposer.append(composerInner);\n\t\tapp.append(header, main, composer);\n\t\tthis.root.replaceChildren(style, app);\n\t}\n\n\tprivate accept(event: WebClientEvent): void {\n\t\tif (event.type === \"hello\") {\n\t\t\tthis.currentState = stateFrom(event.message.state);\n\t\t\tthis.status = event.message.resyncRequired ? \"Connected (synced)\" : \"Connected\";\n\t\t} else if (event.type === \"event\") {\n\t\t\tif (event.message.event.type === \"state\") this.currentState = event.message.event.state;\n\t\t} else if (event.type === \"error\") {\n\t\t\tthis.status = \"Connection error\";\n\t\t\tthis.setError(event.message);\n\t\t} else if (event.type === \"closed\") this.status = \"Disconnected\";\n\t\tthis.render();\n\t}\n\n\tprivate async submit(): Promise<void> {\n\t\tconst text = this.input?.value?.trim() ?? \"\";\n\t\tif (!text) return;\n\t\tif (this.input) this.input.value = \"\";\n\t\tawait this.runAction(() => this.client.action({ type: \"submit\", input: text }));\n\t}\n\n\tprivate async steer(): Promise<void> {\n\t\tconst text = this.input?.value?.trim() ?? \"\";\n\t\tif (!text) return;\n\t\tif (this.input) this.input.value = \"\";\n\t\tawait this.runAction(() => this.client.action({ type: \"steer\", input: text }));\n\t}\n\n\tprivate async runAction(action: () => Promise<unknown> | unknown): Promise<void> {\n\t\ttry {\n\t\t\tawait action();\n\t\t} catch (cause) {\n\t\t\tthis.setError(cause instanceof Error ? cause.message : String(cause));\n\t\t}\n\t}\n\n\tprivate setError(message: string): void {\n\t\tif (this.error) this.error.textContent = message;\n\t\tthis.status = \"Connection error\";\n\t\tthis.render();\n\t}\n\n\tprivate render(): void {\n\t\tif (this.statusNode) {\n\t\t\tthis.statusNode.textContent = this.status;\n\t\t\tthis.statusNode.setAttribute(\"data-state\", this.status.includes(\"error\") ? \"error\" : \"ok\");\n\t\t}\n\t\tif (!this.transcript || !this.process || !this.document) return;\n\t\tconst state = this.currentState;\n\t\tthis.transcript.replaceChildren();\n\t\tfor (const item of state?.messageItems ?? []) {\n\t\t\tif (item.role !== \"user\" && item.role !== \"assistant\") continue;\n\t\t\tconst message = element(this.document, \"article\", \"di-web-message\", item.text);\n\t\t\tmessage.setAttribute(\"data-role\", item.role);\n\t\t\tthis.transcript.append(message);\n\t\t}\n\t\tthis.process.textContent = [\n\t\t\t...(state?.toolStatus ?? []),\n\t\t\t...(state?.streamingText ? [state.streamingText] : []),\n\t\t\t...(state?.queue?.length ? [`Queued: ${state.queue.length}`] : []),\n\t\t].join(\"\\n\");\n\t\tif (this.error && !state?.error && this.status !== \"Connection error\") this.error.textContent = \"\";\n\t\tif (state?.error && this.error) this.error.textContent = state.error;\n\t\tfor (const [key, button] of this.actionButtons) {\n\t\t\tbutton.disabled = Boolean(state?.busy) && [\"submit\", \"retry\", \"compact\", \"create_session\"].includes(key);\n\t\t}\n\t}\n}\n\n/** Convenience factory for a browser page that owns a WebSocket client and DOM frontend. */\nexport async function createWebFrontendApp(options: WebFrontendConnectionOptions): Promise<WebFrontendApp> {\n\tconst transport = await connectWebSocketTransport(options.url, {\n\t\tprotocols: options.protocols,\n\t\tWebSocket: options.WebSocket,\n\t});\n\tconst app = new WebFrontendApp({ ...options, client: new WebClient(transport) });\n\ttry {\n\t\tawait app.start();\n\t\treturn app;\n\t} catch (cause) {\n\t\tapp.dispose();\n\t\tthrow cause;\n\t}\n}\n\nexport type WebFrontendAction = WebAction;\n"]}