@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,395 @@
1
+ import { Chat, type ChatTimelineItem, useLocale } from "@k2b/ui";
2
+ import { type Accessor, createEffect, createMemo, createSignal, type JSX, onCleanup, Show } from "solid-js";
3
+ import type { AiActiveTurn } from "../client/projection";
4
+ import { type AiActiveTurnSegment, isRenderableTurnBlock, splitActiveTurnBlocks } from "../protocol";
5
+ import { type AiAssistantTimelineItem, buildAiMessageTimeline, copyTextFromAssistantEntries } from "../timeline";
6
+ import type { AiConversationTimelineEntry, AiStoredMessage } from "../types";
7
+ import { AiTurnBlockList } from "./blocks";
8
+ import { type AiChatActions, AiChatActionsProvider, createAssistantMessageActions, useAiChatActions } from "./message-actions";
9
+ import { formatWorkedDuration, isCardToolName, isRecord, isSurveyToolName, textFromMessage } from "./message-utils";
10
+ import { type AiToolDisclosureState, createAiToolDisclosureState } from "./tool-disclosure";
11
+ import { aiChatMessages } from "./messages";
12
+ import { CloudSurveyResultBlock } from "./visual-tools";
13
+ import { TurnNavigator } from "./turn-navigator";
14
+ import { activeTimelineSeq } from "./turn-navigator-utils";
15
+ import {
16
+ aiSteerMessageText,
17
+ aiUserMessageAttachments,
18
+ aiUserMessageText,
19
+ createAiSteerMessageActions,
20
+ createAiUserMessageActions,
21
+ } from "./user-message";
22
+
23
+ export type AiChatTimelineSession = {
24
+ messages: readonly AiStoredMessage[];
25
+ activeTurn: AiActiveTurn | null;
26
+ };
27
+
28
+ export { type AiChatActions, AiChatActionsProvider };
29
+
30
+ const isWideBlock = (block: AiAssistantTimelineItem["blocks"][number]) => block.kind === "tool";
31
+
32
+ type AssistantBlock = AiAssistantTimelineItem["blocks"][number];
33
+
34
+ type SurveyResultBlock = Extract<AssistantBlock, { kind: "tool" }>;
35
+ type SurveySegment = { type: "assistant"; blocks: AssistantBlock[] } | { type: "survey"; block: SurveyResultBlock };
36
+
37
+ // Presentation only: the accepted answer remains a tool result in the protocol.
38
+ const splitSurveyResults = (blocks: AssistantBlock[]): SurveySegment[] => {
39
+ const segments: SurveySegment[] = [];
40
+ for (const block of blocks) {
41
+ if (
42
+ block.kind === "tool" &&
43
+ isSurveyToolName(block.name) &&
44
+ block.status === "completed" &&
45
+ !block.isError &&
46
+ isRecord(block.result) &&
47
+ block.result.submitted === true
48
+ ) {
49
+ segments.push({ type: "survey", block });
50
+ } else {
51
+ const previous = segments.at(-1);
52
+ if (previous?.type === "assistant") previous.blocks.push(block);
53
+ else segments.push({ type: "assistant", blocks: [block] });
54
+ }
55
+ }
56
+ return segments;
57
+ };
58
+
59
+ const surveyItem = (block: SurveyResultBlock, turnId: string): ChatTimelineItem => ({
60
+ kind: "message",
61
+ id: `survey-answer:${turnId}:${block.callId}`,
62
+ role: "user",
63
+ content: <CloudSurveyResultBlock args={block.args} result={block.result} />,
64
+ });
65
+
66
+ const isDirectCompletedResult = (block: AssistantBlock): boolean =>
67
+ block.kind === "tool" && block.status === "completed" && !block.isError && (isCardToolName(block.name) || block.name === "present");
68
+
69
+ const isFailedBlock = (block: AssistantBlock): boolean =>
70
+ (block.kind === "tool" && (block.isError || block.status === "failed" || block.status === "rejected")) ||
71
+ (block.kind === "compaction" && block.status === "failed");
72
+
73
+ export const partitionCompletedAssistantBlocks = (blocks: AssistantBlock[]) => {
74
+ const renderable = blocks.filter(isRenderableTurnBlock);
75
+ const finalTextIndex = renderable.findLastIndex((block) => block.kind === "text");
76
+ return {
77
+ worked: renderable.filter((block, index) => index !== finalTextIndex && !isDirectCompletedResult(block)),
78
+ visible: renderable.filter((block, index) => index === finalTextIndex || isDirectCompletedResult(block)),
79
+ };
80
+ };
81
+
82
+ export function AiAssistantContent(props: {
83
+ item: AiAssistantTimelineItem;
84
+ disclosureState?: AiToolDisclosureState;
85
+ segmentId?: string;
86
+ }): JSX.Element {
87
+ const locale = useLocale();
88
+ const blocks = createMemo(() => partitionCompletedAssistantBlocks(props.item.blocks));
89
+ const turnId = () => props.item.loopId ?? props.item.id;
90
+ const workedDisclosureId = () => `worked:${props.segmentId ?? turnId()}`;
91
+ const workedOpen = () => props.disclosureState?.get(workedDisclosureId());
92
+ const setWorkedOpen = (open: boolean) => props.disclosureState?.set(workedDisclosureId(), open);
93
+ const workedFailed = () => blocks().worked.some(isFailedBlock);
94
+
95
+ return (
96
+ <div class="flex flex-col gap-2">
97
+ <Show when={blocks().worked.length > 0}>
98
+ <Chat.Activity
99
+ icon="ti ti-route"
100
+ label={props.segmentId ? aiChatMessages(locale()).workSteps : `Worked for ${formatWorkedDuration(props.item.workedMs)}`}
101
+ tone={workedFailed() ? "danger" : undefined}
102
+ bodyInset={false}
103
+ defaultOpen={workedFailed()}
104
+ open={workedOpen()}
105
+ onOpenChange={setWorkedOpen}
106
+ >
107
+ <AiTurnBlockList blocks={blocks().worked} turnId={turnId()} compact disclosureState={props.disclosureState} />
108
+ </Chat.Activity>
109
+ </Show>
110
+ <AiTurnBlockList blocks={blocks().visible} turnId={turnId()} disclosureState={props.disclosureState} />
111
+ </div>
112
+ );
113
+ }
114
+
115
+ const storedItems = (
116
+ messages: readonly AiStoredMessage[],
117
+ actions: AiChatActions,
118
+ disclosureState: AiToolDisclosureState,
119
+ locale: string,
120
+ ): ChatTimelineItem[] =>
121
+ buildAiMessageTimeline([...messages]).flatMap((item): ChatTimelineItem | ChatTimelineItem[] => {
122
+ if (item.type === "user") {
123
+ const text = aiUserMessageText(item.entry);
124
+ const agentMessage = item.entry.meta?.agentMessage;
125
+ if (agentMessage) {
126
+ const separator = text.indexOf("\n\n");
127
+ const forwardedText = separator >= 0 ? text.slice(separator + 2) : text;
128
+ return {
129
+ kind: "message",
130
+ id: item.id,
131
+ role: "system",
132
+ label: `Message from Assistant chat ${agentMessage.sourceTitle}`,
133
+ createdAt: item.entry.createdAt,
134
+ content: (
135
+ <div class="flex flex-col gap-1">
136
+ <p class="text-xs font-medium text-dimmed">
137
+ <i class="ti ti-message-forward mr-1" aria-hidden="true" />
138
+ From{" "}
139
+ <Show
140
+ when={agentMessage.sourceHref}
141
+ fallback={
142
+ <span>
143
+ {agentMessage.sourceTitle} ({agentMessage.sourceChatId})
144
+ </span>
145
+ }
146
+ >
147
+ {(href) => (
148
+ <a class="text-link hover:underline" href={href()}>
149
+ {agentMessage.sourceTitle} ({agentMessage.sourceChatId})
150
+ </a>
151
+ )}
152
+ </Show>
153
+ <span class="font-normal"> · turn {agentMessage.sourceTurnId}</span>
154
+ </p>
155
+ {forwardedText ? <p class="whitespace-pre-wrap">{forwardedText}</p> : undefined}
156
+ </div>
157
+ ),
158
+ anchorId: item.entry.seq,
159
+ };
160
+ }
161
+ return {
162
+ kind: "message",
163
+ id: item.id,
164
+ role: "user",
165
+ createdAt: item.entry.createdAt,
166
+ content: text ? <p class="whitespace-pre-wrap">{text}</p> : undefined,
167
+ attachments: aiUserMessageAttachments(item.entry, actions),
168
+ actions: createAiUserMessageActions(item.entry, actions),
169
+ actionDisplay: "menu",
170
+ anchorId: item.entry.seq,
171
+ };
172
+ }
173
+
174
+ if (item.type === "summary") {
175
+ const count = item.entry.meta?.compactedCount;
176
+ const date = new Date(item.entry.createdAt).toLocaleDateString(locale);
177
+ return {
178
+ kind: "activity",
179
+ id: item.id,
180
+ label: "Context compacted",
181
+ description: count ? `${count} message${count === 1 ? "" : "s"} summarized · ${date}` : date,
182
+ icon: "ti ti-brain",
183
+ tone: "ai",
184
+ content: (
185
+ <div>
186
+ <p class="mb-1 text-[10px] font-medium uppercase tracking-wide text-dimmed">
187
+ The model now sees this summary instead of the messages above
188
+ </p>
189
+ <p class="whitespace-pre-wrap">{textFromMessage(item.entry.message) || "No visible content"}</p>
190
+ </div>
191
+ ),
192
+ };
193
+ }
194
+
195
+ const actionEntry = item.actionEntry?.compactedAt ? null : item.actionEntry;
196
+ const copyText = copyTextFromAssistantEntries(item.entries);
197
+ const segments = splitSurveyResults(item.blocks);
198
+ if (segments.length === 0) segments.push({ type: "assistant", blocks: [] });
199
+ const lastAssistant = segments.findLastIndex((segment) => segment.type === "assistant");
200
+ return segments.map((segment, index): ChatTimelineItem => {
201
+ if (segment.type === "survey") return surveyItem(segment.block, item.loopId ?? item.id);
202
+ return {
203
+ kind: "message",
204
+ id: index === 0 ? item.id : `${item.id}:${segment.blocks[0]!.id}`,
205
+ role: "assistant",
206
+ createdAt: actionEntry?.createdAt ?? item.entries.at(-1)?.createdAt,
207
+ class: segment.blocks.some(isWideBlock) ? "ai-chat-message-wide" : undefined,
208
+ content: (
209
+ <AiAssistantContent
210
+ item={{ ...item, blocks: segment.blocks }}
211
+ segmentId={segments.length > 1 ? `${item.id}:${index}` : undefined}
212
+ disclosureState={disclosureState}
213
+ />
214
+ ),
215
+ actions:
216
+ actionEntry && index === lastAssistant
217
+ ? createAssistantMessageActions({ entry: actionEntry, entries: item.entries, copyText, actions })
218
+ : undefined,
219
+ actionDisplay: "inline",
220
+ };
221
+ });
222
+ });
223
+
224
+ const activeItems = (turn: AiActiveTurn | null, actions: AiChatActions, disclosureState: AiToolDisclosureState): ChatTimelineItem[] => {
225
+ if (!turn) return [];
226
+ const segments = splitActiveTurnBlocks(turn.blocks).flatMap((segment): (AiActiveTurnSegment | SurveySegment)[] =>
227
+ segment.type === "steer" ? [segment] : splitSurveyResults(segment.blocks),
228
+ );
229
+ // Keep the shared assistant progress indicator after the accepted answer.
230
+ if (turn.status === "running" && segments.at(-1)?.type === "survey") segments.push({ type: "assistant", blocks: [] });
231
+ if (segments.length === 0) {
232
+ return [
233
+ {
234
+ kind: "message",
235
+ id: `${turn.turnId}-pending`,
236
+ role: "assistant",
237
+ status: "streaming",
238
+ anchorId: turn.seq,
239
+ },
240
+ ];
241
+ }
242
+
243
+ return segments.map((segment, index): ChatTimelineItem => {
244
+ if (segment.type === "survey") return surveyItem(segment.block, turn.turnId);
245
+ if (segment.type === "steer") {
246
+ const block = segment.block;
247
+ return {
248
+ kind: "message",
249
+ id: `${turn.turnId}-steer-${block.id}`,
250
+ role: "user",
251
+ status: block.status === "pending" ? "pending" : block.status === "failed" ? "error" : "complete",
252
+ content: <p class="whitespace-pre-wrap">{aiSteerMessageText(block)}</p>,
253
+ actions: createAiSteerMessageActions(block, actions),
254
+ actionDisplay: "menu",
255
+ anchorId: turn.seq,
256
+ };
257
+ }
258
+
259
+ const blocks = segment.blocks;
260
+ return {
261
+ kind: "message",
262
+ id: `${turn.turnId}-assistant-${index}`,
263
+ role: "assistant",
264
+ status: turn.status === "running" && index === segments.length - 1 ? "streaming" : "complete",
265
+ class: blocks.some(isWideBlock) ? "ai-chat-message-wide" : undefined,
266
+ content: (
267
+ <AiTurnBlockList
268
+ blocks={blocks}
269
+ turnId={turn.turnId}
270
+ streaming={turn.status === "running" && index === segments.length - 1}
271
+ active
272
+ disclosureState={disclosureState}
273
+ />
274
+ ),
275
+ };
276
+ });
277
+ };
278
+
279
+ export type AiChatTimelineSource = {
280
+ messages: Accessor<readonly AiStoredMessage[]>;
281
+ activeTurn: Accessor<AiActiveTurn | null>;
282
+ };
283
+
284
+ /**
285
+ * Adapts Cloud's stored/live conversation domain into portable chat items.
286
+ * Call below AiChatActionsProvider so rich tool blocks receive the current
287
+ * action context.
288
+ */
289
+ export function createAiChatTimeline(source: AiChatTimelineSource): Accessor<readonly ChatTimelineItem[]> {
290
+ const actions = useAiChatActions();
291
+ const locale = useLocale();
292
+ const disclosureState = createAiToolDisclosureState();
293
+ const stored = createMemo(() => storedItems(source.messages(), actions, disclosureState, locale()));
294
+ const active = createMemo(() => activeItems(source.activeTurn(), actions, disclosureState));
295
+ return createMemo(() => [...stored(), ...active()]);
296
+ }
297
+
298
+ export type AiChatTurnNavigatorProps = {
299
+ entries: readonly AiConversationTimelineEntry[];
300
+ loading?: boolean;
301
+ viewport: () => HTMLDivElement | undefined;
302
+ content: () => HTMLDivElement | undefined;
303
+ loadThrough: (seq: number) => Promise<boolean>;
304
+ };
305
+
306
+ export function AiChatTurnNavigator(props: AiChatTurnNavigatorProps): JSX.Element {
307
+ const [activeSeq, setActiveSeq] = createSignal<number | null>(null);
308
+ const [loadingSeq, setLoadingSeq] = createSignal<number | null>(null);
309
+ const [height, setHeight] = createSignal(0);
310
+ let navigationRevision = 0;
311
+ let updateFrame: number | undefined;
312
+
313
+ const update = () => {
314
+ const viewport = props.viewport();
315
+ const content = props.content();
316
+ if (!viewport || !content || props.entries.length === 0) {
317
+ setActiveSeq(null);
318
+ return;
319
+ }
320
+ setHeight(viewport.clientHeight);
321
+ if (viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight <= 8) {
322
+ setActiveSeq(props.entries.at(-1)?.seq ?? null);
323
+ return;
324
+ }
325
+ const anchors = Array.from(content.querySelectorAll<HTMLElement>("[data-chat-anchor]")).flatMap((node) => {
326
+ const seq = Number(node.dataset.chatAnchor);
327
+ return Number.isFinite(seq) ? [{ seq, top: node.getBoundingClientRect().top }] : [];
328
+ });
329
+ const rect = viewport.getBoundingClientRect();
330
+ setActiveSeq(activeTimelineSeq(anchors, rect.top, rect.height));
331
+ };
332
+
333
+ const scheduleUpdate = () => {
334
+ if (updateFrame !== undefined) return;
335
+ updateFrame = requestAnimationFrame(() => {
336
+ updateFrame = undefined;
337
+ update();
338
+ });
339
+ };
340
+
341
+ createEffect(() => {
342
+ const viewport = props.viewport();
343
+ const content = props.content();
344
+ if (!viewport || !content) return;
345
+ update();
346
+ viewport.addEventListener("scroll", scheduleUpdate, { passive: true });
347
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(scheduleUpdate);
348
+ observer?.observe(viewport);
349
+ observer?.observe(content);
350
+ onCleanup(() => {
351
+ viewport.removeEventListener("scroll", scheduleUpdate);
352
+ observer?.disconnect();
353
+ if (updateFrame !== undefined) cancelAnimationFrame(updateFrame);
354
+ updateFrame = undefined;
355
+ });
356
+ });
357
+
358
+ const select = async (entry: AiConversationTimelineEntry) => {
359
+ const revision = ++navigationRevision;
360
+ const content = props.content();
361
+ let anchor = content?.querySelector<HTMLElement>(`[data-chat-anchor="${entry.seq}"]`) ?? null;
362
+ if (!anchor) {
363
+ setLoadingSeq(entry.seq);
364
+ const loaded = await props.loadThrough(entry.seq);
365
+ if (!loaded || revision !== navigationRevision) {
366
+ setLoadingSeq(null);
367
+ return;
368
+ }
369
+ await new Promise<void>((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
370
+ anchor = props.content()?.querySelector<HTMLElement>(`[data-chat-anchor="${entry.seq}"]`) ?? null;
371
+ }
372
+ const viewport = props.viewport();
373
+ if (!viewport || !anchor || revision !== navigationRevision) return;
374
+ const viewportRect = viewport.getBoundingClientRect();
375
+ viewport.scrollTop = Math.max(0, viewport.scrollTop + anchor.getBoundingClientRect().top - viewportRect.top - 16);
376
+ setActiveSeq(entry.seq);
377
+ setLoadingSeq(null);
378
+ };
379
+
380
+ return (
381
+ <Show when={props.entries.length >= 5 && height() > 0 && !props.loading}>
382
+ <div class="ai-turn-navigator-shell pointer-events-none relative h-0">
383
+ <div class="absolute top-0" style={{ left: "max(0.5rem, calc(50% - 30rem))" }}>
384
+ <TurnNavigator
385
+ entries={[...props.entries]}
386
+ activeSeq={activeSeq()}
387
+ loadingSeq={loadingSeq()}
388
+ height={Math.max(120, height() - 16)}
389
+ onSelect={(entry) => void select(entry)}
390
+ />
391
+ </div>
392
+ </div>
393
+ </Show>
394
+ );
395
+ }
@@ -0,0 +1,3 @@
1
+ export function AssistantMarkdownBlock(props: { html: string }) {
2
+ return <div class="assistant-markdown-block" innerHTML={props.html} />;
3
+ }
@@ -0,0 +1,41 @@
1
+ import { Chat, type ChatActivityProps } from "@k2b/ui";
2
+ import { createContext, type JSX, splitProps, useContext } from "solid-js";
3
+
4
+ export type AiToolDisclosureState = {
5
+ get: (blockId: string) => boolean | undefined;
6
+ set: (blockId: string, open: boolean) => void;
7
+ };
8
+
9
+ export const createAiToolDisclosureState = (): AiToolDisclosureState => {
10
+ const openByBlockId = new Map<string, boolean>();
11
+ return {
12
+ get: (blockId) => openByBlockId.get(blockId),
13
+ set: (blockId, open) => openByBlockId.set(blockId, open),
14
+ };
15
+ };
16
+
17
+ const AiToolDisclosureContext = createContext<AiToolDisclosureState>();
18
+
19
+ export function AiToolDisclosureProvider(props: { state: AiToolDisclosureState; children: JSX.Element }): JSX.Element {
20
+ return <AiToolDisclosureContext.Provider value={props.state}>{props.children}</AiToolDisclosureContext.Provider>;
21
+ }
22
+
23
+ export function AiToolActivity(props: Omit<ChatActivityProps, "open" | "onOpenChange"> & { blockId: string }): JSX.Element {
24
+ const [local, activity] = splitProps(props, ["blockId"]);
25
+ const disclosure = useAiToolDisclosure(() => local.blockId);
26
+ return <Chat.Activity {...activity} open={disclosure.open()} onOpenChange={disclosure.onOpenChange} />;
27
+ }
28
+
29
+ export function useAiToolDisclosure(blockId: () => string | undefined) {
30
+ const state = useContext(AiToolDisclosureContext);
31
+ return {
32
+ open: () => {
33
+ const id = blockId();
34
+ return id ? state?.get(id) : undefined;
35
+ },
36
+ onOpenChange: (open: boolean) => {
37
+ const id = blockId();
38
+ if (id) state?.set(id, open);
39
+ },
40
+ };
41
+ }
@@ -0,0 +1,28 @@
1
+ import type { AiConversationTimelineEntry } from "../types";
2
+
3
+ export type TimelineAnchorPosition = { seq: number; top: number };
4
+
5
+ export const isTimelineRailScrollable = (entryCount: number, height: number): boolean => entryCount * 16 > height - 16;
6
+
7
+ export const activeTimelineSeq = (anchors: TimelineAnchorPosition[], viewportTop: number, viewportHeight: number): number | null => {
8
+ if (anchors.length === 0) return null;
9
+ const threshold = viewportTop + Math.min(180, Math.max(72, viewportHeight * 0.28));
10
+ let active = anchors[0]!.seq;
11
+ for (const anchor of anchors) {
12
+ if (anchor.top > threshold) break;
13
+ active = anchor.seq;
14
+ }
15
+ return active;
16
+ };
17
+
18
+ export const adjacentTimelineEntry = (
19
+ entries: readonly AiConversationTimelineEntry[],
20
+ currentSeq: number | null,
21
+ direction: -1 | 1,
22
+ ): AiConversationTimelineEntry | null => {
23
+ if (entries.length === 0) return null;
24
+ const currentIndex = currentSeq === null ? (direction > 0 ? -1 : entries.length) : entries.findIndex((entry) => entry.seq === currentSeq);
25
+ const fallbackIndex = direction > 0 ? 0 : entries.length - 1;
26
+ const nextIndex = currentIndex < 0 ? fallbackIndex : Math.min(entries.length - 1, Math.max(0, currentIndex + direction));
27
+ return entries[nextIndex] ?? null;
28
+ };
@@ -0,0 +1,164 @@
1
+ import { createEffect, createMemo, createSignal, For, Show } from "solid-js";
2
+ import type { AiConversationTimelineEntry } from "../types";
3
+ import { adjacentTimelineEntry, isTimelineRailScrollable } from "./turn-navigator-utils";
4
+
5
+ type TurnNavigatorProps = {
6
+ entries: AiConversationTimelineEntry[];
7
+ activeSeq: number | null;
8
+ loadingSeq: number | null;
9
+ height: number;
10
+ onSelect: (entry: AiConversationTimelineEntry) => void;
11
+ };
12
+
13
+ const countLabel = (count: number, singular: string, plural = `${singular}s`) => `${count} ${count === 1 ? singular : plural}`;
14
+
15
+ export function TurnNavigator(props: TurnNavigatorProps) {
16
+ let navRef: HTMLElement | undefined;
17
+ let markerViewportRef: HTMLDivElement | undefined;
18
+ const markerRefs = new Map<number, HTMLButtonElement>();
19
+ const [previewSeq, setPreviewSeq] = createSignal<number | null>(null);
20
+ const [previewTop, setPreviewTop] = createSignal(8);
21
+ const [railHovered, setRailHovered] = createSignal(false);
22
+ let wheelDelta = 0;
23
+ let wheelLockedUntil = 0;
24
+
25
+ const preview = createMemo(() => props.entries.find((entry) => entry.seq === previewSeq()) ?? null);
26
+ const scrollable = () => isTimelineRailScrollable(props.entries.length, props.height);
27
+ const revealMarker = (marker: HTMLButtonElement | undefined) => {
28
+ const viewport = markerViewportRef;
29
+ if (!marker || !viewport) return;
30
+ const viewportRect = viewport.getBoundingClientRect();
31
+ const markerRect = marker.getBoundingClientRect();
32
+ if (markerRect.top < viewportRect.top) viewport.scrollTop -= viewportRect.top - markerRect.top;
33
+ else if (markerRect.bottom > viewportRect.bottom) viewport.scrollTop += markerRect.bottom - viewportRect.bottom;
34
+ };
35
+ const showPreview = (entry: AiConversationTimelineEntry, marker: HTMLButtonElement) => {
36
+ setPreviewSeq(entry.seq);
37
+ const navRect = navRef?.getBoundingClientRect();
38
+ const markerRect = marker.getBoundingClientRect();
39
+ if (!navRect) return;
40
+ setPreviewTop(Math.max(8, Math.min(props.height - 176, markerRect.top - navRect.top - 68)));
41
+ };
42
+
43
+ createEffect(() => {
44
+ const active = props.activeSeq;
45
+ if (active === null || railHovered()) return;
46
+ revealMarker(markerRefs.get(active));
47
+ });
48
+
49
+ const selectAdjacent = (direction: -1 | 1) => {
50
+ const current = previewSeq() ?? props.activeSeq;
51
+ const next = adjacentTimelineEntry(props.entries, current, direction);
52
+ if (!next || next.seq === current) return;
53
+ const marker = markerRefs.get(next.seq);
54
+ if (marker) showPreview(next, marker);
55
+ props.onSelect(next);
56
+ };
57
+
58
+ const handleWheel = (event: WheelEvent) => {
59
+ event.preventDefault();
60
+ wheelDelta += event.deltaY;
61
+ const now = performance.now();
62
+ if (Math.abs(wheelDelta) < 28 || now < wheelLockedUntil) return;
63
+ const direction = wheelDelta > 0 ? 1 : -1;
64
+ wheelDelta = 0;
65
+ wheelLockedUntil = now + 90;
66
+ selectAdjacent(direction);
67
+ };
68
+
69
+ return (
70
+ <nav
71
+ ref={navRef}
72
+ aria-label="Conversation turns"
73
+ class="pointer-events-auto relative block w-8"
74
+ style={{ height: `${props.height}px` }}
75
+ onMouseEnter={() => setRailHovered(true)}
76
+ onMouseLeave={() => {
77
+ setRailHovered(false);
78
+ setPreviewSeq(null);
79
+ wheelDelta = 0;
80
+ }}
81
+ onWheel={handleWheel}
82
+ >
83
+ <div
84
+ ref={markerViewportRef}
85
+ class={`flex h-full w-8 flex-col items-center gap-1 overflow-y-auto py-2 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden ${
86
+ scrollable() ? "justify-start" : "justify-center"
87
+ }`}
88
+ >
89
+ <For each={props.entries}>
90
+ {(entry) => {
91
+ const active = () => entry.seq === props.activeSeq;
92
+ const loading = () => entry.seq === props.loadingSeq;
93
+ return (
94
+ <button
95
+ ref={(node) => markerRefs.set(entry.seq, node)}
96
+ type="button"
97
+ class="group/marker flex h-3 w-8 shrink-0 items-center justify-center focus-ui"
98
+ aria-label={`${entry.isSteer ? "Steering message" : "User message"}: ${entry.userPreview}`}
99
+ aria-current={active() ? "location" : undefined}
100
+ onMouseEnter={(event) => showPreview(entry, event.currentTarget)}
101
+ onFocus={(event) => showPreview(entry, event.currentTarget)}
102
+ onClick={() => props.onSelect(entry)}
103
+ >
104
+ <span
105
+ class={`block h-0.5 rounded-full transition-[width,background-color] ${
106
+ loading()
107
+ ? "w-5 animate-pulse bg-cyan-500"
108
+ : active()
109
+ ? "w-6 bg-zinc-700 dark:bg-zinc-100"
110
+ : entry.isSteer
111
+ ? "w-2 bg-zinc-300 group-hover/marker:w-4 group-hover/marker:bg-zinc-500 dark:bg-zinc-700 dark:group-hover/marker:bg-zinc-400"
112
+ : "w-3 bg-zinc-300 group-hover/marker:w-5 group-hover/marker:bg-zinc-500 dark:bg-zinc-700 dark:group-hover/marker:bg-zinc-400"
113
+ }`}
114
+ />
115
+ </button>
116
+ );
117
+ }}
118
+ </For>
119
+ </div>
120
+
121
+ <Show when={preview()}>
122
+ {(entry) => (
123
+ <div
124
+ class="pointer-events-none absolute left-10 z-30 w-80 max-w-[min(20rem,calc(100vw-6rem))] rounded-md border border-zinc-200 bg-white p-3 text-left shadow-lg dark:border-zinc-700 dark:bg-zinc-900"
125
+ style={{ top: `${previewTop()}px` }}
126
+ >
127
+ <p class="line-clamp-2 text-sm font-medium leading-5 text-primary">{entry().userPreview}</p>
128
+ <Show when={entry().assistantPreview}>
129
+ <p class="mt-1 line-clamp-3 text-xs leading-5 text-dimmed">{entry().assistantPreview}</p>
130
+ </Show>
131
+ <Show when={entry().isSteer || entry().inputFileCount > 0 || entry().outputFileCount > 0 || entry().toolCount > 0}>
132
+ <div class="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-dimmed">
133
+ <Show when={entry().isSteer}>
134
+ <span class="inline-flex items-center gap-1">
135
+ <i class="ti ti-route" aria-hidden="true" />
136
+ Steered
137
+ </span>
138
+ </Show>
139
+ <Show when={entry().inputFileCount > 0}>
140
+ <span class="inline-flex items-center gap-1">
141
+ <i class="ti ti-paperclip" aria-hidden="true" />
142
+ {countLabel(entry().inputFileCount, "file")}
143
+ </span>
144
+ </Show>
145
+ <Show when={entry().outputFileCount > 0}>
146
+ <span class="inline-flex items-center gap-1">
147
+ <i class="ti ti-file-export" aria-hidden="true" />
148
+ {countLabel(entry().outputFileCount, "output file")}
149
+ </span>
150
+ </Show>
151
+ <Show when={entry().toolCount > 0}>
152
+ <span class="inline-flex items-center gap-1">
153
+ <i class="ti ti-tool" aria-hidden="true" />
154
+ {countLabel(entry().toolCount, "tool")}
155
+ </span>
156
+ </Show>
157
+ </div>
158
+ </Show>
159
+ </div>
160
+ )}
161
+ </Show>
162
+ </nav>
163
+ );
164
+ }