@k2b/cloud 0.6.0-rc.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 (540) hide show
  1. package/README.md +98 -0
  2. package/package.json +128 -0
  3. package/public/logo.svg +1 -0
  4. package/scripts/app-favicon.test.ts +49 -0
  5. package/scripts/app-favicon.ts +51 -0
  6. package/scripts/build.ts +205 -0
  7. package/scripts/preload.ts +164 -0
  8. package/scripts/sync-recovery-smoke.ts +200 -0
  9. package/src/_internal/app-favicon.ts +2 -0
  10. package/src/_internal/app-presentation.ts +61 -0
  11. package/src/_internal/app-workspace-state.ts +24 -0
  12. package/src/_internal/bounded-json.ts +47 -0
  13. package/src/_internal/build-metadata.ts +9 -0
  14. package/src/_internal/capabilities.ts +1118 -0
  15. package/src/_internal/define-app.ts +801 -0
  16. package/src/_internal/heartbeat.ts +167 -0
  17. package/src/_internal/help-catalog.ts +241 -0
  18. package/src/_internal/help.ts +95 -0
  19. package/src/_internal/nats-connection.ts +34 -0
  20. package/src/_internal/page-responses.ts +40 -0
  21. package/src/_internal/process-sync.ts +117 -0
  22. package/src/_internal/readiness.ts +3 -0
  23. package/src/_internal/registry-validation.ts +136 -0
  24. package/src/_internal/registry-watch.ts +52 -0
  25. package/src/_internal/registry.ts +338 -0
  26. package/src/_internal/runtime-compatibility.ts +64 -0
  27. package/src/_internal/runtime-context.ts +84 -0
  28. package/src/_internal/runtime-watcher.ts +78 -0
  29. package/src/_internal/static-assets.ts +86 -0
  30. package/src/_internal/status-preserving-ssr.ts +49 -0
  31. package/src/access/PermissionEditor.tsx +486 -0
  32. package/src/access/ResourceApiKeys.tsx +271 -0
  33. package/src/access/messages.ts +121 -0
  34. package/src/access/ui.ts +16 -0
  35. package/src/account/Avatar.tsx +28 -0
  36. package/src/account/EntitySearch.tsx +363 -0
  37. package/src/account/avatar-upload-dialog.tsx +197 -0
  38. package/src/account/avatar-upload.ts +149 -0
  39. package/src/account/ui.ts +3 -0
  40. package/src/ai/admin.ts +47 -0
  41. package/src/ai/approval-routes.ts +130 -0
  42. package/src/ai/approvals.ts +139 -0
  43. package/src/ai/assistant-models.ts +61 -0
  44. package/src/ai/attachments.ts +40 -0
  45. package/src/ai/browser.ts +92 -0
  46. package/src/ai/capabilities.ts +714 -0
  47. package/src/ai/capability-execution.ts +151 -0
  48. package/src/ai/chat/blocks.render.test.tsx +1108 -0
  49. package/src/ai/chat/blocks.tsx +730 -0
  50. package/src/ai/chat/builtin-tools.tsx +428 -0
  51. package/src/ai/chat/capability-table.tsx +87 -0
  52. package/src/ai/chat/composer-adapter.ts +169 -0
  53. package/src/ai/chat/file-tools.tsx +81 -0
  54. package/src/ai/chat/message-actions.tsx +370 -0
  55. package/src/ai/chat/message-utils.ts +479 -0
  56. package/src/ai/chat/messages.ts +43 -0
  57. package/src/ai/chat/presentation.tsx +395 -0
  58. package/src/ai/chat/primitives.tsx +3 -0
  59. package/src/ai/chat/tool-disclosure.tsx +41 -0
  60. package/src/ai/chat/turn-navigator-utils.ts +28 -0
  61. package/src/ai/chat/turn-navigator.tsx +164 -0
  62. package/src/ai/chat/user-message.tsx +149 -0
  63. package/src/ai/chat/visual-tools.tsx +435 -0
  64. package/src/ai/chat/web-tools.tsx +202 -0
  65. package/src/ai/chat-task-contracts.ts +113 -0
  66. package/src/ai/chat-tasks.ts +952 -0
  67. package/src/ai/client/controller.ts +1211 -0
  68. package/src/ai/client/file-source.ts +95 -0
  69. package/src/ai/client/live-connection.ts +140 -0
  70. package/src/ai/client/projection.ts +178 -0
  71. package/src/ai/client/transport.ts +111 -0
  72. package/src/ai/compaction.ts +182 -0
  73. package/src/ai/credentials.ts +124 -0
  74. package/src/ai/default-tool-contracts.ts +108 -0
  75. package/src/ai/default-tools.ts +127 -0
  76. package/src/ai/enrich.ts +280 -0
  77. package/src/ai/executor.ts +1624 -0
  78. package/src/ai/fetch-file-tool.ts +313 -0
  79. package/src/ai/file-context.ts +92 -0
  80. package/src/ai/file-media-type.ts +28 -0
  81. package/src/ai/file-mount.ts +16 -0
  82. package/src/ai/file-tools.ts +566 -0
  83. package/src/ai/files-store.ts +390 -0
  84. package/src/ai/firecrawl-tools.ts +305 -0
  85. package/src/ai/grids-skill.ts +126 -0
  86. package/src/ai/http.ts +265 -0
  87. package/src/ai/index.ts +337 -0
  88. package/src/ai/kit-skill.ts +22 -0
  89. package/src/ai/limits.ts +5 -0
  90. package/src/ai/live-events.ts +289 -0
  91. package/src/ai/live-messages.ts +45 -0
  92. package/src/ai/live-outbox.ts +102 -0
  93. package/src/ai/live-routes.ts +412 -0
  94. package/src/ai/live.ts +2 -0
  95. package/src/ai/maintenance.ts +302 -0
  96. package/src/ai/markdown-pdf-tool.ts +95 -0
  97. package/src/ai/memories.ts +605 -0
  98. package/src/ai/memory-learning-runs.ts +222 -0
  99. package/src/ai/memory-learning.ts +619 -0
  100. package/src/ai/memory-tool.ts +120 -0
  101. package/src/ai/memory-workflow-evidence.ts +158 -0
  102. package/src/ai/migrate.ts +2082 -0
  103. package/src/ai/model-access-migrate.ts +30 -0
  104. package/src/ai/model-access.ts +191 -0
  105. package/src/ai/personal-agent.ts +6 -0
  106. package/src/ai/prefs.ts +72 -0
  107. package/src/ai/project-tool.ts +94 -0
  108. package/src/ai/projects-routes.ts +277 -0
  109. package/src/ai/projects.ts +902 -0
  110. package/src/ai/protocol.ts +279 -0
  111. package/src/ai/provider.ts +73 -0
  112. package/src/ai/public-projection.ts +25 -0
  113. package/src/ai/resource-markers.ts +31 -0
  114. package/src/ai/resource-refs.ts +70 -0
  115. package/src/ai/routes.ts +1131 -0
  116. package/src/ai/runtime-tools.ts +39 -0
  117. package/src/ai/runtime.ts +544 -0
  118. package/src/ai/settings.ts +529 -0
  119. package/src/ai/short-id.ts +45 -0
  120. package/src/ai/skill-catalog.ts +73 -0
  121. package/src/ai/skill-format.ts +145 -0
  122. package/src/ai/skill-seeds.ts +430 -0
  123. package/src/ai/skill-tool.ts +81 -0
  124. package/src/ai/skills-routes.ts +143 -0
  125. package/src/ai/skills.ts +934 -0
  126. package/src/ai/solid.ts +27 -0
  127. package/src/ai/store.ts +3113 -0
  128. package/src/ai/stream.ts +308 -0
  129. package/src/ai/structured-runs.ts +66 -0
  130. package/src/ai/structured.ts +186 -0
  131. package/src/ai/system-prompt.ts +170 -0
  132. package/src/ai/task-prompt.ts +22 -0
  133. package/src/ai/timeline.ts +148 -0
  134. package/src/ai/tool-audit.ts +177 -0
  135. package/src/ai/tool-result-budget.ts +16 -0
  136. package/src/ai/tools.ts +155 -0
  137. package/src/ai/types.ts +967 -0
  138. package/src/ai/ui.tsx +39 -0
  139. package/src/ai/usage-migrate.ts +81 -0
  140. package/src/ai/usage.ts +315 -0
  141. package/src/ai/validate.ts +54 -0
  142. package/src/ai/vision-tool.ts +79 -0
  143. package/src/api/accounts-entities.ts +174 -0
  144. package/src/api/admin-ai-projects.ts +74 -0
  145. package/src/api/admin-ai-skills.ts +76 -0
  146. package/src/api/admin-ai-usage.ts +42 -0
  147. package/src/api/admin-core-settings.ts +508 -0
  148. package/src/api/admin-identity.ts +148 -0
  149. package/src/api/admin-lifecycle.ts +206 -0
  150. package/src/api/admin-linux-identities.ts +66 -0
  151. package/src/api/announcements.ts +131 -0
  152. package/src/api/app-approval.ts +129 -0
  153. package/src/api/apps.ts +51 -0
  154. package/src/api/auth/schemas.ts +54 -0
  155. package/src/api/auth.ts +370 -0
  156. package/src/api/capabilities.ts +658 -0
  157. package/src/api/help-messages.ts +19 -0
  158. package/src/api/help.ts +78 -0
  159. package/src/api/index.ts +108 -0
  160. package/src/api/mcp.ts +744 -0
  161. package/src/api/me-rail.ts +45 -0
  162. package/src/api/me.ts +753 -0
  163. package/src/api/search/schemas.ts +79 -0
  164. package/src/api/search.bench.ts +185 -0
  165. package/src/api/search.ts +407 -0
  166. package/src/api/sync-ops.ts +79 -0
  167. package/src/api/widgets.ts +143 -0
  168. package/src/browser/CloudResourceSearch.behavior.test.tsx +74 -0
  169. package/src/browser/CloudResourceSearch.tsx +791 -0
  170. package/src/browser/app-approval-vault.ts +305 -0
  171. package/src/browser/app-approval.ts +234 -0
  172. package/src/browser/live-websocket.ts +268 -0
  173. package/src/browser/locale-preference.ts +25 -0
  174. package/src/browser/notification-target.ts +27 -0
  175. package/src/browser/notification-timeout.ts +14 -0
  176. package/src/browser/notifications.ts +181 -0
  177. package/src/browser/resource-clipboard.ts +70 -0
  178. package/src/browser/resource-picker.tsx +39 -0
  179. package/src/browser/resource-search-messages.ts +55 -0
  180. package/src/browser/resource-search.ts +27 -0
  181. package/src/capabilities/client.ts +149 -0
  182. package/src/capabilities/response.ts +39 -0
  183. package/src/capabilities/server.ts +215 -0
  184. package/src/capabilities/signals.ts +4 -0
  185. package/src/capabilities/testing.ts +12 -0
  186. package/src/capabilities/types.ts +32 -0
  187. package/src/cli/access.ts +499 -0
  188. package/src/cli/account.ts +382 -0
  189. package/src/cli/admin/account-administration.ts +45 -0
  190. package/src/cli/admin/account-categories.ts +49 -0
  191. package/src/cli/admin/ai-usage.ts +118 -0
  192. package/src/cli/admin/app-sign-in.ts +49 -0
  193. package/src/cli/admin/data.ts +343 -0
  194. package/src/cli/admin/documentation.ts +34 -0
  195. package/src/cli/admin/gateway.ts +155 -0
  196. package/src/cli/admin/index.ts +61 -0
  197. package/src/cli/admin/instance.ts +250 -0
  198. package/src/cli/admin/jobs.ts +223 -0
  199. package/src/cli/admin/legal.ts +122 -0
  200. package/src/cli/admin/linux.ts +53 -0
  201. package/src/cli/admin/logs.ts +324 -0
  202. package/src/cli/admin/metrics.ts +142 -0
  203. package/src/cli/admin/nats.ts +167 -0
  204. package/src/cli/admin/notifications.ts +516 -0
  205. package/src/cli/admin/shared.ts +113 -0
  206. package/src/cli/admin/sync.ts +188 -0
  207. package/src/cli/admin/telemetry.ts +314 -0
  208. package/src/cli/admin/webhooks.ts +222 -0
  209. package/src/cli/admin/workflows.ts +367 -0
  210. package/src/cli/apps.ts +40 -0
  211. package/src/cli/capabilities.ts +239 -0
  212. package/src/cli/commands.ts +604 -0
  213. package/src/cli/index.ts +61 -0
  214. package/src/cli/locale.ts +31 -0
  215. package/src/cli/output.ts +58 -0
  216. package/src/clients/core.ts +20 -0
  217. package/src/config/env.ts +54 -0
  218. package/src/config/index.ts +6 -0
  219. package/src/contracts/account-categories.ts +62 -0
  220. package/src/contracts/announcements.ts +121 -0
  221. package/src/contracts/app-approval.ts +197 -0
  222. package/src/contracts/app.ts +162 -0
  223. package/src/contracts/capabilities.ts +484 -0
  224. package/src/contracts/index.ts +14 -0
  225. package/src/contracts/notification-live.ts +66 -0
  226. package/src/contracts/notification-types.ts +235 -0
  227. package/src/contracts/posix.ts +69 -0
  228. package/src/contracts/profile.ts +85 -0
  229. package/src/contracts/rail-preferences.ts +53 -0
  230. package/src/contracts/registry.ts +110 -0
  231. package/src/contracts/resource-clipboard.ts +33 -0
  232. package/src/contracts/settings-types.ts +98 -0
  233. package/src/contracts/shared.ts +408 -0
  234. package/src/contracts/user-notifications.ts +101 -0
  235. package/src/contracts/widgets.ts +183 -0
  236. package/src/desktop/index.ts +704 -0
  237. package/src/desktop/solid.tsx +938 -0
  238. package/src/index.ts +58 -0
  239. package/src/server/actor.ts +41 -0
  240. package/src/server/api/index.ts +2 -0
  241. package/src/server/api/respond.ts +96 -0
  242. package/src/server/api-client.ts +54 -0
  243. package/src/server/app-context.ts +39 -0
  244. package/src/server/help.ts +187 -0
  245. package/src/server/index.ts +89 -0
  246. package/src/server/locale.ts +55 -0
  247. package/src/server/middleware/auth.ts +386 -0
  248. package/src/server/middleware/index.ts +28 -0
  249. package/src/server/middleware/invocation.ts +55 -0
  250. package/src/server/middleware/middleware.ts +65 -0
  251. package/src/server/middleware/openapi.ts +126 -0
  252. package/src/server/middleware/rate-limit.ts +125 -0
  253. package/src/server/middleware/request-logger.ts +41 -0
  254. package/src/server/middleware/route-template.ts +46 -0
  255. package/src/server/middleware/runtime.ts +22 -0
  256. package/src/server/middleware/settings.ts +55 -0
  257. package/src/server/middleware/validator.ts +49 -0
  258. package/src/server/middleware/workload.ts +22 -0
  259. package/src/server/ratelimit.ts +151 -0
  260. package/src/server/request-cookies.ts +18 -0
  261. package/src/server/services/access.ts +644 -0
  262. package/src/server/services/freeipa/client.ts +137 -0
  263. package/src/server/services/freeipa/index.ts +9 -0
  264. package/src/server/services/freeipa/session.ts +123 -0
  265. package/src/server/services/freeipa/test-certificates.ts +44 -0
  266. package/src/server/services/freeipa/tls.ts +44 -0
  267. package/src/server/services/freeipa/transport.ts +116 -0
  268. package/src/server/services/freeipa/util.ts +59 -0
  269. package/src/server/services/geo.ts +198 -0
  270. package/src/server/services/index.ts +48 -0
  271. package/src/server/services/pagination.ts +22 -0
  272. package/src/server/services/services.ts +13 -0
  273. package/src/server/time.ts +33 -0
  274. package/src/services/account-category-policy.ts +51 -0
  275. package/src/services/account-lifecycle/audit.ts +41 -0
  276. package/src/services/account-lifecycle/index.ts +940 -0
  277. package/src/services/account-lifecycle/ipa-backfill.ts +152 -0
  278. package/src/services/account-lifecycle/notification-sender.ts +13 -0
  279. package/src/services/account-lifecycle/scheduler.ts +506 -0
  280. package/src/services/account-model.ts +22 -0
  281. package/src/services/accounts/app.ts +1671 -0
  282. package/src/services/accounts/authz.ts +44 -0
  283. package/src/services/accounts/avatar.ts +120 -0
  284. package/src/services/accounts/base-group.ts +11 -0
  285. package/src/services/accounts/base-user.ts +46 -0
  286. package/src/services/accounts/duplicate-emails.ts +82 -0
  287. package/src/services/accounts/entities.ts +622 -0
  288. package/src/services/accounts/group-sql.ts +106 -0
  289. package/src/services/accounts/groups.ts +272 -0
  290. package/src/services/accounts/index.ts +14 -0
  291. package/src/services/accounts/ipa-data.ts +66 -0
  292. package/src/services/accounts/lifecycle.ts +2 -0
  293. package/src/services/accounts/local-groups.ts +499 -0
  294. package/src/services/accounts/model.ts +129 -0
  295. package/src/services/accounts/notification-sender.ts +31 -0
  296. package/src/services/accounts/posix.ts +386 -0
  297. package/src/services/accounts/request-policy.ts +11 -0
  298. package/src/services/accounts/switching.ts +126 -0
  299. package/src/services/accounts/users.ts +745 -0
  300. package/src/services/announcements/index.ts +224 -0
  301. package/src/services/app-approval.ts +498 -0
  302. package/src/services/audit/index.ts +438 -0
  303. package/src/services/auth-flows/index.ts +15 -0
  304. package/src/services/auth-flows/ipa.ts +197 -0
  305. package/src/services/auth-flows/magic-link.ts +185 -0
  306. package/src/services/auth-flows/notification-sender.ts +10 -0
  307. package/src/services/auth-flows/password-reset.ts +268 -0
  308. package/src/services/auth-flows/proxy-return.ts +49 -0
  309. package/src/services/document-extraction.ts +148 -0
  310. package/src/services/freeipa-config.ts +179 -0
  311. package/src/services/gateway.ts +186 -0
  312. package/src/services/identity/constants.ts +18 -0
  313. package/src/services/identity/index.ts +52 -0
  314. package/src/services/identity/invocation-actor.ts +120 -0
  315. package/src/services/identity/invocation-authority.ts +53 -0
  316. package/src/services/identity/invocation-operations.ts +15 -0
  317. package/src/services/identity/invocation-token.ts +276 -0
  318. package/src/services/identity/key-config.ts +41 -0
  319. package/src/services/identity/key-ring.ts +530 -0
  320. package/src/services/identity/metrics.ts +29 -0
  321. package/src/services/identity/routes.ts +18 -0
  322. package/src/services/identity/runtime-config.ts +88 -0
  323. package/src/services/identity/session-token.ts +152 -0
  324. package/src/services/identity/signer-refresh.ts +44 -0
  325. package/src/services/identity/workload-auth.ts +64 -0
  326. package/src/services/index.ts +187 -0
  327. package/src/services/ipa/auth.ts +216 -0
  328. package/src/services/ipa/connection.ts +57 -0
  329. package/src/services/ipa/effective-groups.ts +70 -0
  330. package/src/services/ipa/groups.ts +722 -0
  331. package/src/services/ipa/guard.ts +17 -0
  332. package/src/services/ipa/index.ts +17 -0
  333. package/src/services/ipa/posix.ts +19 -0
  334. package/src/services/ipa/profile.ts +132 -0
  335. package/src/services/ipa/runtime.ts +28 -0
  336. package/src/services/ipa/search.ts +154 -0
  337. package/src/services/ipa/service-account.ts +15 -0
  338. package/src/services/ipa/sync-planning.ts +100 -0
  339. package/src/services/ipa/sync.ts +862 -0
  340. package/src/services/ipa/users.ts +806 -0
  341. package/src/services/legal-consent.ts +76 -0
  342. package/src/services/logging/index.ts +477 -0
  343. package/src/services/logging/redaction.ts +20 -0
  344. package/src/services/logging/trace.ts +1231 -0
  345. package/src/services/mandates/index.ts +855 -0
  346. package/src/services/mandates/policy.ts +85 -0
  347. package/src/services/network-security.ts +31 -0
  348. package/src/services/notifications/batches.ts +722 -0
  349. package/src/services/notifications/browser.ts +252 -0
  350. package/src/services/notifications/catalog.ts +113 -0
  351. package/src/services/notifications/channels.ts +104 -0
  352. package/src/services/notifications/dispatcher.ts +139 -0
  353. package/src/services/notifications/email.ts +118 -0
  354. package/src/services/notifications/index.ts +633 -0
  355. package/src/services/notifications/live.ts +50 -0
  356. package/src/services/notifications/observability.ts +407 -0
  357. package/src/services/notifications/platform.ts +421 -0
  358. package/src/services/notifications/runtime.ts +120 -0
  359. package/src/services/notifications/user.ts +248 -0
  360. package/src/services/notifications/web-push-transport.ts +135 -0
  361. package/src/services/oauth-tokens.ts +209 -0
  362. package/src/services/outbox.ts +187 -0
  363. package/src/services/pdf/gotenberg.ts +290 -0
  364. package/src/services/pdf/index.ts +43 -0
  365. package/src/services/pdf/markdown.ts +209 -0
  366. package/src/services/pdf/template-preview.ts +122 -0
  367. package/src/services/postgres.ts +76 -0
  368. package/src/services/providers/index.ts +27 -0
  369. package/src/services/providers/local/auth.ts +51 -0
  370. package/src/services/providers/local/index.ts +4 -0
  371. package/src/services/providers/local/users.ts +263 -0
  372. package/src/services/rail-preferences.ts +27 -0
  373. package/src/services/runtime-lifecycle.ts +191 -0
  374. package/src/services/secrets.ts +10 -0
  375. package/src/services/service-account-credentials.ts +891 -0
  376. package/src/services/service-accounts.ts +184 -0
  377. package/src/services/session/index.ts +236 -0
  378. package/src/services/session/test-fixture.ts +18 -0
  379. package/src/services/session/user.ts +144 -0
  380. package/src/services/settings/api.ts +61 -0
  381. package/src/services/settings/app.ts +86 -0
  382. package/src/services/settings/core-settings.ts +796 -0
  383. package/src/services/settings/crypto.ts +69 -0
  384. package/src/services/settings/defaults.ts +374 -0
  385. package/src/services/settings/index.ts +215 -0
  386. package/src/services/settings/namespace.ts +9 -0
  387. package/src/services/settings/snapshot.ts +49 -0
  388. package/src/services/settings/store.ts +241 -0
  389. package/src/services/settings/templates.ts +4 -0
  390. package/src/services/sync-ops.ts +403 -0
  391. package/src/services/topic-cursor.ts +22 -0
  392. package/src/services/weather/forecast.ts +323 -0
  393. package/src/services/weather/geo.ts +119 -0
  394. package/src/services/weather/index.ts +67 -0
  395. package/src/services/weather/location.ts +24 -0
  396. package/src/services/weather/locations.ts +133 -0
  397. package/src/services/weather/migrate.ts +45 -0
  398. package/src/services/weather/settings.ts +66 -0
  399. package/src/services/weather/short-id.ts +57 -0
  400. package/src/services/weather/types.ts +61 -0
  401. package/src/services/weather/ui.ts +50 -0
  402. package/src/services/webauthn.ts +385 -0
  403. package/src/shared/account-action-notice.ts +70 -0
  404. package/src/shared/account-display.ts +15 -0
  405. package/src/shared/account-session.ts +15 -0
  406. package/src/shared/ai-platform-prompt.ts +134 -0
  407. package/src/shared/ai-usage.ts +49 -0
  408. package/src/shared/app-presentation.ts +48 -0
  409. package/src/shared/app-url.ts +12 -0
  410. package/src/shared/branding.ts +14 -0
  411. package/src/shared/capability-messages.ts +70 -0
  412. package/src/shared/email-html.ts +54 -0
  413. package/src/shared/format.ts +91 -0
  414. package/src/shared/help.ts +68 -0
  415. package/src/shared/icons.ts +400 -0
  416. package/src/shared/index.ts +54 -0
  417. package/src/shared/locale.ts +48 -0
  418. package/src/shared/login-method.ts +44 -0
  419. package/src/shared/markdown/client.ts +130 -0
  420. package/src/shared/markdown/extensions/code.ts +105 -0
  421. package/src/shared/markdown/extensions/guided-help.ts +100 -0
  422. package/src/shared/markdown/extensions/images.ts +79 -0
  423. package/src/shared/markdown/extensions/info-blocks.ts +97 -0
  424. package/src/shared/markdown/extensions/katex.ts +120 -0
  425. package/src/shared/markdown/extensions/links.ts +43 -0
  426. package/src/shared/markdown/extensions/mark.ts +48 -0
  427. package/src/shared/markdown/extensions/sub-sup.ts +60 -0
  428. package/src/shared/markdown/extensions/tables.ts +109 -0
  429. package/src/shared/markdown/extensions/task-list.ts +53 -0
  430. package/src/shared/markdown/formula.ts +1187 -0
  431. package/src/shared/markdown/index.ts +193 -0
  432. package/src/shared/markdown/shared.ts +36 -0
  433. package/src/shared/mock-cover.ts +130 -0
  434. package/src/shared/network-address.ts +106 -0
  435. package/src/shared/redirect.ts +56 -0
  436. package/src/shared/template-rendering.ts +140 -0
  437. package/src/shared/theme.ts +68 -0
  438. package/src/shared/time.ts +13 -0
  439. package/src/ssr/AdminLayout.tsx +53 -0
  440. package/src/ssr/AdminSidebar.tsx +50 -0
  441. package/src/ssr/AppLaunchpad.island.tsx +236 -0
  442. package/src/ssr/AppWorkspaceController.island.tsx +21 -0
  443. package/src/ssr/BrowserNotifications.island.tsx +135 -0
  444. package/src/ssr/GlobalAnnouncements.island.tsx +142 -0
  445. package/src/ssr/GlobalSearchDialog.behavior.test.tsx +96 -0
  446. package/src/ssr/GlobalSearchDialog.tsx +35 -0
  447. package/src/ssr/GlobalSearchHelpDialog.tsx +104 -0
  448. package/src/ssr/GlobalSearchTrigger.island.tsx +55 -0
  449. package/src/ssr/HotkeysHelpRail.island.tsx +55 -0
  450. package/src/ssr/Layout.render.test.tsx +148 -0
  451. package/src/ssr/Layout.tsx +292 -0
  452. package/src/ssr/LayoutBreadcrumbs.island.tsx +44 -0
  453. package/src/ssr/LayoutFooter.tsx +22 -0
  454. package/src/ssr/LayoutHeader.tsx +81 -0
  455. package/src/ssr/LayoutHelp.tsx +869 -0
  456. package/src/ssr/LayoutPreferences.island.tsx +36 -0
  457. package/src/ssr/LayoutRail.tsx +58 -0
  458. package/src/ssr/MinimalLayout.tsx +46 -0
  459. package/src/ssr/PageError.tsx +29 -0
  460. package/src/ssr/ProfilePreferences.island.tsx +92 -0
  461. package/src/ssr/RailApps.island.tsx +61 -0
  462. package/src/ssr/RailEditor.tsx +293 -0
  463. package/src/ssr/RegisteredHelpDocuments.island.tsx +3 -0
  464. package/src/ssr/TimezoneCookie.island.tsx +23 -0
  465. package/src/ssr/admin-active-link.ts +47 -0
  466. package/src/ssr/admin-navigation.ts +78 -0
  467. package/src/ssr/app-appearance.ts +23 -0
  468. package/src/ssr/app-navigation.ts +41 -0
  469. package/src/ssr/help-messages.ts +104 -0
  470. package/src/ssr/index.ts +14 -0
  471. package/src/ssr/islands/SearchBar.island.tsx +80 -0
  472. package/src/ssr/islands/index.ts +16 -0
  473. package/src/ssr/layout-context.ts +17 -0
  474. package/src/ssr/layout-help-markdown.ts +9 -0
  475. package/src/ssr/layout-help-navigation.ts +24 -0
  476. package/src/ssr/layout-help-url.ts +5 -0
  477. package/src/ssr/layout-runtime.ts +22 -0
  478. package/src/ssr/page-error-messages.ts +25 -0
  479. package/src/ssr/platform-messages.ts +135 -0
  480. package/src/ssr/preference-controller.ts +44 -0
  481. package/src/ssr/profile-preferences-messages.ts +29 -0
  482. package/src/ssr/rail-context.ts +21 -0
  483. package/src/ssr/rail-messages.ts +66 -0
  484. package/src/ssr/rail-navigation.ts +60 -0
  485. package/src/ssr/runtime.ts +31 -0
  486. package/src/ssr/url-filter.ts +127 -0
  487. package/src/styles/base-popover.css +36 -0
  488. package/src/styles/effects.css +851 -0
  489. package/src/styles/global.css +268 -0
  490. package/src/styles/input.css +58 -0
  491. package/src/styles/tokens.css +230 -0
  492. package/src/styles/utilities-buttons.css +405 -0
  493. package/src/styles/utilities-data.css +33 -0
  494. package/src/styles/utilities-feedback.css +232 -0
  495. package/src/styles/utilities-layout.css +603 -0
  496. package/src/styles/utilities-markdown-editor.css +525 -0
  497. package/src/styles/utilities-markdown-table.css +234 -0
  498. package/src/styles/utilities-navigation.css +1054 -0
  499. package/src/types/ambient.d.ts +24 -0
  500. package/src/workflows/ai/index.ts +17 -0
  501. package/src/workflows/ai/runtime.ts +286 -0
  502. package/src/workflows/ai/store.ts +314 -0
  503. package/src/workflows/ai/types.ts +111 -0
  504. package/src/workflows/ai-actions.ts +269 -0
  505. package/src/workflows/authoring.ts +88 -0
  506. package/src/workflows/builtins.ts +193 -0
  507. package/src/workflows/contracts.ts +280 -0
  508. package/src/workflows/definition.ts +334 -0
  509. package/src/workflows/editor.ts +113 -0
  510. package/src/workflows/index.ts +71 -0
  511. package/src/workflows/language/binder.ts +45 -0
  512. package/src/workflows/language/canonical.ts +29 -0
  513. package/src/workflows/language/compiler.ts +396 -0
  514. package/src/workflows/language/expressions.ts +34 -0
  515. package/src/workflows/language/index.ts +20 -0
  516. package/src/workflows/language/references.ts +55 -0
  517. package/src/workflows/language/schema.ts +134 -0
  518. package/src/workflows/language/strict-yaml.ts +131 -0
  519. package/src/workflows/manifest.ts +24 -0
  520. package/src/workflows/module.ts +12 -0
  521. package/src/workflows/runtime/coordinator.ts +115 -0
  522. package/src/workflows/runtime/executor.ts +1003 -0
  523. package/src/workflows/runtime/index.ts +60 -0
  524. package/src/workflows/runtime/instant.ts +8 -0
  525. package/src/workflows/runtime/ports.ts +213 -0
  526. package/src/workflows/runtime/schedule.ts +203 -0
  527. package/src/workflows/runtime/trigger.ts +37 -0
  528. package/src/workflows/runtime/wake.ts +13 -0
  529. package/src/workflows/store/actions.ts +438 -0
  530. package/src/workflows/store/budget.ts +145 -0
  531. package/src/workflows/store/definitions.ts +345 -0
  532. package/src/workflows/store/events.ts +383 -0
  533. package/src/workflows/store/index.ts +107 -0
  534. package/src/workflows/store/observability.ts +594 -0
  535. package/src/workflows/store/runs.ts +975 -0
  536. package/src/workflows/store/transaction.ts +23 -0
  537. package/src/workflows/store/worker.ts +439 -0
  538. package/src/workflows/testing/conformance.ts +72 -0
  539. package/src/workflows/testing/index.ts +11 -0
  540. package/src/workflows/testing/process-fixtures.ts +275 -0
@@ -0,0 +1,95 @@
1
+ /**
2
+ * FileSource adapter over a conversation's VFS routes (/conversations/:id/files).
3
+ * Files share one flat conversation namespace. Origin metadata preserves who
4
+ * created a file without encoding policy in its path. Browser-safe module.
5
+ */
6
+ import type { FileSource, FileTreeEntry, FileViewContent } from "@k2b/ui";
7
+ import type { AiFileStat } from "../files-store";
8
+
9
+ const readError = async (response: Response, fallback: string): Promise<string> => {
10
+ const body = await response.json().catch(() => null);
11
+ return body && typeof body === "object" && "message" in body && typeof body.message === "string" ? body.message : fallback;
12
+ };
13
+
14
+ const TEXT_MEDIA_TYPES = new Set([
15
+ "application/json",
16
+ "application/yaml",
17
+ "application/xml",
18
+ "image/svg+xml",
19
+ "text/javascript",
20
+ "text/typescript",
21
+ ]);
22
+ const isTextMediaType = (mediaType: string): boolean => mediaType.startsWith("text/") || TEXT_MEDIA_TYPES.has(mediaType);
23
+
24
+ const bytesToBase64 = (bytes: Uint8Array): string => {
25
+ let binary = "";
26
+ const CHUNK = 0x8000;
27
+ for (let index = 0; index < bytes.length; index += CHUNK) {
28
+ binary += String.fromCharCode(...bytes.subarray(index, index + CHUNK));
29
+ }
30
+ return btoa(binary);
31
+ };
32
+
33
+ export const conversationFileSource = (baseUrl: string, conversationId: string): FileSource => {
34
+ const filesUrl = (suffix = "", params?: Record<string, string>) => {
35
+ const query = params ? `?${new URLSearchParams(params).toString()}` : "";
36
+ return `${baseUrl}/conversations/${conversationId}/files${suffix}${query}`;
37
+ };
38
+
39
+ const request = async <T>(url: string, init: RequestInit, fallback: string): Promise<T> => {
40
+ const response = await fetch(url, {
41
+ ...init,
42
+ headers: init.body instanceof FormData ? undefined : { "Content-Type": "application/json" },
43
+ });
44
+ if (!response.ok) throw new Error(await readError(response, fallback));
45
+ return (await response.json()) as T;
46
+ };
47
+
48
+ return {
49
+ async list(): Promise<FileTreeEntry[]> {
50
+ const result = await request<{ files: AiFileStat[] }>(filesUrl(), { method: "GET" }, "Failed to load files");
51
+ return result.files.map((file) => ({
52
+ path: file.path,
53
+ size: file.size,
54
+ mediaType: file.mediaType,
55
+ updatedAt: file.updatedAt,
56
+ badge: file.origin === "user" ? "upload" : undefined,
57
+ }));
58
+ },
59
+
60
+ async read(path: string): Promise<FileViewContent> {
61
+ const response = await fetch(filesUrl("/content", { path }));
62
+ if (!response.ok) throw new Error(await readError(response, "Failed to load file"));
63
+ const mediaType = response.headers.get("Content-Type")?.split(";")[0]?.trim() || "application/octet-stream";
64
+ const bytes = new Uint8Array(await response.arrayBuffer());
65
+ return isTextMediaType(mediaType)
66
+ ? { encoding: "utf8", content: new TextDecoder().decode(bytes), mediaType }
67
+ : { encoding: "base64", content: bytesToBase64(bytes), mediaType };
68
+ },
69
+
70
+ async write(path, content, encoding = "utf8") {
71
+ await request(filesUrl("/content"), { method: "PUT", body: JSON.stringify({ path, content, encoding }) }, "Failed to save file");
72
+ },
73
+
74
+ async remove(path) {
75
+ await request(filesUrl("", { path }), { method: "DELETE" }, "Failed to delete file");
76
+ },
77
+
78
+ async rename(from, to) {
79
+ await request(filesUrl("/rename"), { method: "POST", body: JSON.stringify({ from, to }) }, "Failed to rename file");
80
+ },
81
+
82
+ async upload(_dirPath, files) {
83
+ for (const file of files) {
84
+ const form = new FormData();
85
+ form.append("file", file);
86
+ const response = await fetch(filesUrl(), { method: "POST", body: form });
87
+ if (!response.ok) throw new Error(await readError(response, `Failed to upload ${file.name}`));
88
+ }
89
+ },
90
+
91
+ downloadHref: (path) => filesUrl("/content", { path }),
92
+
93
+ isReadOnly: () => false,
94
+ };
95
+ };
@@ -0,0 +1,140 @@
1
+ import {
2
+ createLiveWebSocket,
3
+ type LiveWebSocket,
4
+ type LiveWebSocketClose,
5
+ type LiveWebSocketError,
6
+ type LiveWebSocketStatus,
7
+ } from "../../browser/live-websocket";
8
+ import { AI_LIVE_WS_TYPE, type AiLiveServerMessage, parseAiLiveServerMessage } from "../live-events";
9
+ import type { AiConversationStreamTransport, AiStreamConnectionStatus } from "./transport";
10
+
11
+ type TurnSubscription = {
12
+ conversationId: string;
13
+ generation: number;
14
+ onEvent: Parameters<AiConversationStreamTransport["subscribe"]>[0]["onEvent"];
15
+ onStatus?: Parameters<AiConversationStreamTransport["subscribe"]>[0]["onStatus"];
16
+ onError?: Parameters<AiConversationStreamTransport["subscribe"]>[0]["onError"];
17
+ };
18
+
19
+ export type AiLiveConnection = {
20
+ connect: () => void;
21
+ markApplied: (cursor: string | null | undefined) => void;
22
+ streamTransport: AiConversationStreamTransport;
23
+ dispose: () => void;
24
+ };
25
+
26
+ export type CreateAiLiveConnectionOptions = {
27
+ url?: string;
28
+ initialCursor: string;
29
+ onLiveMessage: (message: AiLiveServerMessage) => void;
30
+ onLiveStatus?: (status: LiveWebSocketStatus) => void;
31
+ onFatal?: (error: LiveWebSocketError) => void;
32
+ };
33
+
34
+ const reconnectableAiClose = (close: LiveWebSocketClose): LiveWebSocketError | null =>
35
+ close.code === 1008 ? { code: close.reason || "access_denied", message: "Live access changed or expired." } : null;
36
+
37
+ const streamStatus = (status: LiveWebSocketStatus): AiStreamConnectionStatus | null => {
38
+ if (status === "connecting" || status === "reconnecting") return status;
39
+ if (status === "paused") return "reconnecting";
40
+ return null;
41
+ };
42
+
43
+ /** One browser connection for AI invalidations and the currently visible turn. */
44
+ export const createAiLiveConnection = (options: CreateAiLiveConnectionOptions): AiLiveConnection => {
45
+ let socket: LiveWebSocket;
46
+ let turn: TurnSubscription | null = null;
47
+ let turnGeneration = 0;
48
+ let connected = false;
49
+ let disposed = false;
50
+ let liveSubscribeCount = 0;
51
+
52
+ const sendTurnSubscription = () => {
53
+ if (!connected || !turn) return;
54
+ if (
55
+ !socket.send({
56
+ type: AI_LIVE_WS_TYPE.turnSubscribe,
57
+ payload: { conversationId: turn.conversationId },
58
+ })
59
+ ) {
60
+ turn.onStatus?.("reconnecting");
61
+ }
62
+ };
63
+
64
+ socket = createLiveWebSocket<AiLiveServerMessage>({
65
+ url: options.url ?? "/api/ai/live",
66
+ initialCursor: options.initialCursor,
67
+ subscribe: (cursor) => ({
68
+ type: AI_LIVE_WS_TYPE.subscribe,
69
+ payload: { fromCursor: cursor, recover: liveSubscribeCount++ > 0 },
70
+ }),
71
+ parse: parseAiLiveServerMessage,
72
+ classifyClose: reconnectableAiClose,
73
+ onOpen: sendTurnSubscription,
74
+ onStatus: (status) => {
75
+ connected = status === "open";
76
+ options.onLiveStatus?.(status);
77
+ const next = streamStatus(status);
78
+ if (next) turn?.onStatus?.(next);
79
+ },
80
+ onMessage: (message) => {
81
+ if (message.type === AI_LIVE_WS_TYPE.turnEvent) {
82
+ if (!turn || turn.conversationId !== message.payload.conversationId) return;
83
+ if (message.payload.event.type === "state") turn.onStatus?.("open");
84
+ turn.onEvent(message.payload.event);
85
+ return;
86
+ }
87
+ if (message.type === AI_LIVE_WS_TYPE.turnError) {
88
+ if (!turn || turn.conversationId !== message.payload.conversationId) return;
89
+ const current = turn;
90
+ turn = null;
91
+ current.onError?.(new Error(message.payload.message));
92
+ return;
93
+ }
94
+ options.onLiveMessage(message);
95
+ },
96
+ onFatal: (error) => {
97
+ connected = false;
98
+ const current = turn;
99
+ turn = null;
100
+ current?.onError?.(new Error(error.message));
101
+ options.onFatal?.(error);
102
+ },
103
+ });
104
+
105
+ const streamTransport: AiConversationStreamTransport = {
106
+ subscribe: ({ conversationId, onEvent, onStatus, onError }) => {
107
+ const generation = ++turnGeneration;
108
+ const subscription: TurnSubscription = { conversationId, generation, onEvent, onStatus, onError };
109
+ turn = subscription;
110
+ onStatus?.("connecting");
111
+ sendTurnSubscription();
112
+ return {
113
+ close: () => {
114
+ if (turn?.generation !== generation) return;
115
+ turn = null;
116
+ if (connected) {
117
+ socket.send({
118
+ type: AI_LIVE_WS_TYPE.turnUnsubscribe,
119
+ payload: { conversationId },
120
+ });
121
+ }
122
+ },
123
+ };
124
+ },
125
+ };
126
+
127
+ return {
128
+ connect: () => {
129
+ if (!disposed) socket.connect();
130
+ },
131
+ markApplied: (cursor) => socket.markApplied(cursor),
132
+ streamTransport,
133
+ dispose: () => {
134
+ if (disposed) return;
135
+ disposed = true;
136
+ turn = null;
137
+ socket.dispose();
138
+ },
139
+ };
140
+ };
@@ -0,0 +1,178 @@
1
+ import type { AiStreamEvent, AiTurnBlock, AiTurnSnapshot, AiWireEvent } from "../protocol";
2
+ import { applyWireEventToBlocks, isNewerWireEvent, reconcileResolvedTurnActions, toolBlockId } from "../protocol";
3
+ import type { AiConversation, AiStoredMessage } from "../types";
4
+
5
+ export type AiActiveTurn = {
6
+ turnId: string;
7
+ attempt: number;
8
+ seq: number;
9
+ status: "running" | "waiting_for_action";
10
+ blocks: AiTurnBlock[];
11
+ modelProfileId: string | null;
12
+ };
13
+
14
+ export type AiChatProjection = {
15
+ conversation: AiConversation | null;
16
+ messages: AiStoredMessage[];
17
+ activeTurn: AiActiveTurn | null;
18
+ };
19
+
20
+ export const emptyProjection = (conversation: AiConversation | null = null): AiChatProjection => ({
21
+ conversation,
22
+ messages: [],
23
+ activeTurn: null,
24
+ });
25
+
26
+ const mergeMessages = (existing: AiStoredMessage[], incoming: AiStoredMessage[]): AiStoredMessage[] => {
27
+ if (incoming.length === 0) return existing;
28
+ const byId = new Map(existing.map((message) => [message.id, message]));
29
+ for (const message of incoming) byId.set(message.id, message);
30
+ return [...byId.values()].sort((a, b) => a.seq - b.seq);
31
+ };
32
+
33
+ /** A turn is "waiting" when any of its tool blocks is awaiting a user action. */
34
+ const deriveStatus = (blocks: AiTurnBlock[]): "running" | "waiting_for_action" =>
35
+ blocks.some((block) => block.kind === "tool" && (block.status === "awaiting_approval" || block.status === "awaiting_client"))
36
+ ? "waiting_for_action"
37
+ : "running";
38
+
39
+ const overlayBlocks = (base: AiTurnBlock[], overlay: AiTurnBlock[]): AiTurnBlock[] => {
40
+ const next = [...base];
41
+ const indexById = new Map(next.map((block, index) => [block.id, index]));
42
+ for (const block of overlay) {
43
+ const index = indexById.get(block.id);
44
+ if (index === undefined) {
45
+ indexById.set(block.id, next.length);
46
+ next.push(block);
47
+ } else {
48
+ next[index] = block;
49
+ }
50
+ }
51
+ return next;
52
+ };
53
+
54
+ const normalizeCustomApprovalBlocks = (blocks: AiTurnBlock[]): AiTurnBlock[] => {
55
+ const normalized: AiTurnBlock[] = [];
56
+ const indexById = new Map<string, number>();
57
+ for (const block of blocks) {
58
+ const parentCallId =
59
+ block.kind === "tool" && block.status === "awaiting_approval" ? /^(.*)-approval-\d+$/.exec(block.callId)?.[1] : undefined;
60
+ const next = parentCallId ? { ...block, id: toolBlockId(parentCallId) } : block;
61
+ const index = indexById.get(next.id);
62
+ if (index === undefined) {
63
+ indexById.set(next.id, normalized.length);
64
+ normalized.push(next);
65
+ } else {
66
+ normalized[index] = next;
67
+ }
68
+ }
69
+ return normalized;
70
+ };
71
+
72
+ /** Never let a same-turn DB/SSE snapshot remove blocks the browser already observed. */
73
+ export const mergeActiveTurn = (previous: AiActiveTurn | null, incoming: AiActiveTurn | null): AiActiveTurn | null => {
74
+ if (!previous || !incoming || previous.turnId !== incoming.turnId) return incoming;
75
+ if (previous.attempt !== incoming.attempt) return incoming.attempt > previous.attempt ? incoming : previous;
76
+
77
+ const incomingIsNewer = incoming.seq >= previous.seq;
78
+ const blocks = incomingIsNewer ? overlayBlocks(previous.blocks, incoming.blocks) : overlayBlocks(incoming.blocks, previous.blocks);
79
+ return {
80
+ ...(incomingIsNewer ? incoming : previous),
81
+ seq: Math.max(previous.seq, incoming.seq),
82
+ blocks,
83
+ status: deriveStatus(blocks),
84
+ };
85
+ };
86
+
87
+ export const reconcileActiveTurnActions = (
88
+ turn: AiActiveTurn,
89
+ actions: Parameters<typeof reconcileResolvedTurnActions>[1],
90
+ ): AiActiveTurn => {
91
+ const blocks = reconcileResolvedTurnActions(turn.blocks, actions);
92
+ return blocks === turn.blocks ? turn : { ...turn, blocks, status: deriveStatus(blocks) };
93
+ };
94
+
95
+ /** Convert a server turn snapshot into a live active-turn (queued turns count as running). */
96
+ export const activeTurnFromSnapshot = (snapshot: AiTurnSnapshot | null): AiActiveTurn | null => {
97
+ if (!snapshot) return null;
98
+ if (snapshot.status === "completed" || snapshot.status === "failed" || snapshot.status === "aborted") return null;
99
+ return {
100
+ turnId: snapshot.turnId,
101
+ attempt: snapshot.attempt,
102
+ seq: snapshot.seq,
103
+ status: snapshot.status === "waiting_for_action" ? "waiting_for_action" : "running",
104
+ blocks: normalizeCustomApprovalBlocks(snapshot.blocks),
105
+ modelProfileId: snapshot.modelProfileId,
106
+ };
107
+ };
108
+
109
+ /**
110
+ * Fold one stream event into the projection. Pure and total: the client and any
111
+ * test converge on the same state for the same ordered event sequence.
112
+ *
113
+ * Rules:
114
+ * - `state` replaces everything (reconnect baseline).
115
+ * - `turn_started` atomically installs the attempt's ordered block baseline;
116
+ * stale attempts are ignored. Older senders without a baseline reset to only
117
+ * locally pending steering blocks.
118
+ * - block events apply only when strictly newer than the active turn's cursor.
119
+ * - `turn_finished` folds the turn's persisted messages in and clears the active turn.
120
+ */
121
+ export const reduceProjection = (state: AiChatProjection, event: AiStreamEvent): AiChatProjection => {
122
+ if (event.type === "state") {
123
+ // The snapshot only carries the newest window. History the client already
124
+ // paged in (older than the window) must survive a reconnect — losing the
125
+ // scrollback under the reader would be unpredictable.
126
+ const windowOldest = event.messages[0]?.seq;
127
+ const sameConversation = state.conversation?.id === event.conversation.id;
128
+ const preservedOlder =
129
+ sameConversation && windowOldest !== undefined ? state.messages.filter((message) => message.seq < windowOldest) : [];
130
+ return {
131
+ conversation: event.conversation,
132
+ messages: [...preservedOlder, ...event.messages],
133
+ activeTurn: mergeActiveTurn(sameConversation ? state.activeTurn : null, activeTurnFromSnapshot(event.activeTurn)),
134
+ };
135
+ }
136
+
137
+ return reduceWireEvent(state, event);
138
+ };
139
+
140
+ export const reduceWireEvent = (state: AiChatProjection, event: AiWireEvent): AiChatProjection => {
141
+ const active = state.activeTurn;
142
+
143
+ if (event.type === "turn_started") {
144
+ if (active && active.turnId === event.turnId && event.attempt < active.attempt) return state;
145
+ const pendingSteers =
146
+ active?.turnId === event.turnId ? active.blocks.filter((block) => block.kind === "steer_message" && block.status !== "consumed") : [];
147
+ const blocks = event.blocks ? overlayBlocks(normalizeCustomApprovalBlocks(event.blocks), pendingSteers) : pendingSteers;
148
+ return {
149
+ ...state,
150
+ activeTurn: {
151
+ turnId: event.turnId,
152
+ attempt: event.attempt,
153
+ seq: event.seq,
154
+ status: deriveStatus(blocks),
155
+ blocks,
156
+ modelProfileId: event.modelProfileId,
157
+ },
158
+ };
159
+ }
160
+
161
+ if (event.type === "turn_finished") {
162
+ if (!active || active.turnId !== event.turnId) return state;
163
+ return { ...state, messages: mergeMessages(state.messages, event.messages ?? []), activeTurn: null };
164
+ }
165
+
166
+ // block_set / block_delta
167
+ if (!active || active.turnId !== event.turnId) return state;
168
+ if (!isNewerWireEvent(event, active)) return state;
169
+ const blocks = applyWireEventToBlocks(active.blocks, event);
170
+ return { ...state, activeTurn: { ...active, attempt: event.attempt, seq: event.seq, blocks, status: deriveStatus(blocks) } };
171
+ };
172
+
173
+ /** Assistant/tool messages of the active turn are represented by live blocks; hide them. */
174
+ export const visibleMessages = (state: AiChatProjection): AiStoredMessage[] => {
175
+ const activeTurnId = state.activeTurn?.turnId;
176
+ if (!activeTurnId) return state.messages;
177
+ return state.messages.filter((message) => message.loopId !== activeTurnId || (message.message.role === "user" && !message.meta?.steerId));
178
+ };
@@ -0,0 +1,111 @@
1
+ import type { AiStreamEvent } from "../protocol";
2
+
3
+ export type AiStreamHandle = { close: () => void };
4
+ export type AiStreamFetch = (url: string, init: RequestInit) => Promise<Response>;
5
+ export type AiStreamConnectionStatus = "connecting" | "open" | "reconnecting";
6
+ export type AiConversationStreamTransport = {
7
+ subscribe: (input: {
8
+ conversationId: string;
9
+ url: string;
10
+ onEvent: (event: AiStreamEvent) => void;
11
+ onStatus?: (status: AiStreamConnectionStatus) => void;
12
+ onError?: (error: Error) => void;
13
+ }) => AiStreamHandle;
14
+ };
15
+
16
+ const RECONNECT_BASE_MS = 500;
17
+ const RECONNECT_MAX_MS = 5_000;
18
+ const CONNECT_TIMEOUT_MS = 10_000;
19
+
20
+ /** Parse an SSE byte stream into decoded data payloads. */
21
+ export async function* parseAiSse(response: Response, signal: AbortSignal): AsyncGenerator<AiStreamEvent> {
22
+ const reader = response.body?.getReader();
23
+ if (!reader) return;
24
+ const decoder = new TextDecoder();
25
+ let buffer = "";
26
+ try {
27
+ while (!signal.aborted) {
28
+ const { value, done } = await reader.read();
29
+ if (done) break;
30
+ buffer += decoder.decode(value, { stream: true });
31
+ const chunks = buffer.split("\n\n");
32
+ buffer = chunks.pop() ?? "";
33
+ for (const chunk of chunks) {
34
+ const data = chunk
35
+ .split("\n")
36
+ .filter((line) => line.startsWith("data:"))
37
+ .map((line) => line.slice(5).trimStart())
38
+ .join("\n");
39
+ if (data) yield JSON.parse(data) as AiStreamEvent;
40
+ }
41
+ }
42
+ } finally {
43
+ await reader.cancel().catch(() => undefined);
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Subscribe to a conversation's SSE stream with automatic reconnect. Each
49
+ * (re)connect starts with a fresh `state` event, so the projection self-heals on
50
+ * every reconnect without cursor bookkeeping.
51
+ */
52
+ export const subscribeAiStream = (input: {
53
+ url: string;
54
+ onEvent: (event: AiStreamEvent) => void;
55
+ onStatus?: (status: AiStreamConnectionStatus) => void;
56
+ fetch?: AiStreamFetch;
57
+ }): AiStreamHandle => {
58
+ const fetchStream: AiStreamFetch = input.fetch ?? fetch;
59
+ let reconnectDelay = RECONNECT_BASE_MS;
60
+ let stopped = false;
61
+ let activeAttempt: AbortController | null = null;
62
+ let connectTimer: ReturnType<typeof setTimeout> | null = null;
63
+
64
+ const clearConnectTimer = () => {
65
+ if (connectTimer !== null) clearTimeout(connectTimer);
66
+ connectTimer = null;
67
+ };
68
+
69
+ const loop = async () => {
70
+ while (!stopped) {
71
+ const attempt = new AbortController();
72
+ activeAttempt = attempt;
73
+ connectTimer = setTimeout(() => attempt.abort(), CONNECT_TIMEOUT_MS);
74
+ try {
75
+ input.onStatus?.(reconnectDelay === RECONNECT_BASE_MS ? "connecting" : "reconnecting");
76
+ const response = await fetchStream(input.url, { signal: attempt.signal, headers: { Accept: "text/event-stream" } });
77
+ clearConnectTimer();
78
+ if (stopped) return;
79
+ if (!response.ok || !response.body) throw new Error(`AI stream failed: ${response.status}`);
80
+ input.onStatus?.("open");
81
+ reconnectDelay = RECONNECT_BASE_MS;
82
+ for await (const event of parseAiSse(response, attempt.signal)) {
83
+ if (stopped) break;
84
+ input.onEvent(event);
85
+ }
86
+ } catch {
87
+ if (stopped) return;
88
+ } finally {
89
+ clearConnectTimer();
90
+ if (activeAttempt === attempt) activeAttempt = null;
91
+ }
92
+ if (stopped) return;
93
+ await new Promise((resolve) => setTimeout(resolve, reconnectDelay));
94
+ reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_MS);
95
+ }
96
+ };
97
+
98
+ void loop();
99
+ return {
100
+ close: () => {
101
+ stopped = true;
102
+ clearConnectTimer();
103
+ activeAttempt?.abort();
104
+ activeAttempt = null;
105
+ },
106
+ };
107
+ };
108
+
109
+ export const aiSseConversationStreamTransport: AiConversationStreamTransport = {
110
+ subscribe: ({ url, onEvent, onStatus }) => subscribeAiStream({ url, onEvent, onStatus }),
111
+ };