@k2b/cloud 0.6.0-rc.0 → 0.7.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 (315) hide show
  1. package/README.md +2 -2
  2. package/package.json +30 -17
  3. package/scripts/browser-performance.ts +14 -0
  4. package/scripts/build-pdf-renderer.ts +39 -0
  5. package/scripts/build.ts +15 -35
  6. package/scripts/preload.ts +9 -12
  7. package/scripts/sync-recovery-smoke.ts +2 -1
  8. package/src/_internal/app-presentation.ts +14 -2
  9. package/src/_internal/capabilities.ts +108 -7
  10. package/src/_internal/capability-streams.ts +81 -0
  11. package/src/_internal/define-app.ts +62 -36
  12. package/src/_internal/heartbeat.ts +4 -0
  13. package/src/_internal/help-catalog.ts +27 -172
  14. package/src/_internal/help.ts +13 -76
  15. package/src/_internal/page-responses.ts +2 -0
  16. package/src/_internal/postgres-application-name.ts +30 -0
  17. package/src/_internal/process-identity.ts +15 -0
  18. package/src/_internal/process-sync.ts +1 -1
  19. package/src/_internal/registry-validation.ts +29 -25
  20. package/src/_internal/registry.ts +2 -53
  21. package/src/_internal/runtime-context.ts +5 -16
  22. package/src/_internal/server-timing.ts +13 -0
  23. package/src/_internal/status-preserving-ssr.ts +18 -6
  24. package/src/_internal/web-vitals-asset.ts +10 -0
  25. package/src/access/PermissionEditor.tsx +14 -153
  26. package/src/access/PrincipalPicker.tsx +104 -0
  27. package/src/access/ui.ts +2 -0
  28. package/src/ai/admin.ts +14 -13
  29. package/src/ai/assistant-models.ts +12 -1
  30. package/src/ai/audio-format.ts +34 -0
  31. package/src/ai/audio-tool.ts +90 -0
  32. package/src/ai/browser-code-contracts.ts +79 -0
  33. package/src/ai/browser.ts +2 -0
  34. package/src/ai/capabilities.ts +55 -36
  35. package/src/ai/capability-execution.ts +47 -12
  36. package/src/ai/chat/blocks.tsx +157 -95
  37. package/src/ai/chat/capability-result.ts +15 -0
  38. package/src/ai/chat/capability-table.tsx +2 -10
  39. package/src/ai/chat/composer-adapter.ts +48 -3
  40. package/src/ai/chat/message-actions.tsx +8 -7
  41. package/src/ai/chat/message-utils.ts +12 -1
  42. package/src/ai/chat/messages.ts +2 -0
  43. package/src/ai/chat/presentation.tsx +19 -48
  44. package/src/ai/chat/primitives.tsx +43 -1
  45. package/src/ai/chat/tool-disclosure.tsx +17 -2
  46. package/src/ai/chat/tool-groups.ts +52 -0
  47. package/src/ai/chat-quotas.ts +25 -0
  48. package/src/ai/chat-task-contracts.ts +9 -1
  49. package/src/ai/chat-tasks.ts +276 -43
  50. package/src/ai/client/controller.ts +110 -33
  51. package/src/ai/client/file-source.ts +12 -6
  52. package/src/ai/client/live-connection.ts +1 -0
  53. package/src/ai/client/projection.ts +22 -11
  54. package/src/ai/client/transport.ts +4 -0
  55. package/src/ai/code-capability-routes.ts +110 -0
  56. package/src/ai/code-capability-transport.ts +38 -0
  57. package/src/ai/code-mode-skill.ts +114 -0
  58. package/src/ai/code-runtime-tools.ts +166 -0
  59. package/src/ai/code-source-contracts.ts +202 -0
  60. package/src/ai/code-source-tools.ts +145 -0
  61. package/src/ai/compaction.ts +27 -6
  62. package/src/ai/data-analysis-skill.ts +10 -0
  63. package/src/ai/default-tools.ts +25 -2
  64. package/src/ai/dictation-runtime.ts +171 -0
  65. package/src/ai/dictations.ts +188 -0
  66. package/src/ai/draft-content.ts +34 -0
  67. package/src/ai/enrich.ts +6 -0
  68. package/src/ai/executor.ts +191 -31
  69. package/src/ai/file-content-version.ts +22 -0
  70. package/src/ai/file-context.ts +2 -1
  71. package/src/ai/file-media-type.ts +12 -0
  72. package/src/ai/file-reference-contracts.ts +15 -0
  73. package/src/ai/file-tools.ts +2 -1
  74. package/src/ai/files-store.ts +174 -71
  75. package/src/ai/fixtures/vision-encrypted.pdf +0 -0
  76. package/src/ai/grids-skill.ts +40 -10
  77. package/src/ai/http.ts +15 -8
  78. package/src/ai/index.ts +26 -2
  79. package/src/ai/inference-calls.ts +198 -0
  80. package/src/ai/live-events.ts +5 -1
  81. package/src/ai/memory-learning.ts +60 -28
  82. package/src/ai/message-queue.ts +167 -0
  83. package/src/ai/migrate.ts +187 -11
  84. package/src/ai/model-pricing.ts +43 -0
  85. package/src/ai/pdf-render-worker.ts +74 -0
  86. package/src/ai/pdf-render.fixture.ts +28 -0
  87. package/src/ai/pdf-render.ts +77 -0
  88. package/src/ai/projects-routes.ts +3 -3
  89. package/src/ai/projects.ts +20 -6
  90. package/src/ai/protocol.ts +3 -0
  91. package/src/ai/provider.ts +1 -8
  92. package/src/ai/quota-provider.ts +191 -0
  93. package/src/ai/quota-report.ts +152 -0
  94. package/src/ai/quotas-migrate.ts +45 -0
  95. package/src/ai/quotas.ts +168 -0
  96. package/src/ai/routes.ts +215 -11
  97. package/src/ai/run-timeout.ts +10 -0
  98. package/src/ai/runtime-tools.ts +3 -0
  99. package/src/ai/runtime.ts +55 -16
  100. package/src/ai/settings.ts +36 -8
  101. package/src/ai/short-id.ts +2 -2
  102. package/src/ai/skill-catalog.ts +0 -10
  103. package/src/ai/skill-search.ts +47 -0
  104. package/src/ai/skill-seeds.ts +105 -20
  105. package/src/ai/skill-tool.ts +16 -5
  106. package/src/ai/skills-routes.ts +32 -4
  107. package/src/ai/skills.ts +324 -155
  108. package/src/ai/store.ts +373 -191
  109. package/src/ai/stream.ts +5 -0
  110. package/src/ai/structured-runs.ts +4 -3
  111. package/src/ai/structured.ts +8 -1
  112. package/src/ai/system-prompt.ts +12 -2
  113. package/src/ai/timeline.ts +2 -5
  114. package/src/ai/todo-contracts.ts +20 -0
  115. package/src/ai/todo-tool.ts +16 -0
  116. package/src/ai/tool-audit.ts +30 -40
  117. package/src/ai/tools.ts +6 -0
  118. package/src/ai/transcription.ts +202 -0
  119. package/src/ai/turn-timing.ts +114 -0
  120. package/src/ai/types.ts +58 -10
  121. package/src/ai/ui.tsx +2 -0
  122. package/src/ai/usage.ts +118 -118
  123. package/src/ai/vision-tool.ts +39 -10
  124. package/src/api/admin-ai-projects.ts +2 -2
  125. package/src/api/admin-ai-quotas.ts +74 -0
  126. package/src/api/admin-ai-skills.ts +48 -5
  127. package/src/api/admin-ai-usage.ts +2 -2
  128. package/src/api/admin-core-settings.ts +25 -0
  129. package/src/api/admin-identity.ts +91 -51
  130. package/src/api/admin-rail.ts +58 -0
  131. package/src/api/announcements.ts +14 -0
  132. package/src/api/capabilities.ts +292 -43
  133. package/src/api/capability-streams.ts +262 -0
  134. package/src/api/help.ts +8 -44
  135. package/src/api/index.ts +6 -0
  136. package/src/api/mcp.ts +44 -26
  137. package/src/api/me-web-vitals.ts +35 -0
  138. package/src/api/me.ts +2 -0
  139. package/src/api/search/schemas.ts +26 -15
  140. package/src/api/search.bench.ts +1 -1
  141. package/src/api/search.ts +54 -17
  142. package/src/browser/CloudResourceSearch.tsx +640 -702
  143. package/src/browser/app-approval-vault.ts +19 -7
  144. package/src/browser/command-bridge.ts +109 -0
  145. package/src/browser/command-shortcuts.ts +76 -0
  146. package/src/browser/commands.ts +66 -0
  147. package/src/browser/navigation-search.ts +48 -0
  148. package/src/browser/notifications.ts +1 -1
  149. package/src/browser/resource-picker.tsx +5 -18
  150. package/src/browser/resource-search-dialog.ts +9 -0
  151. package/src/browser/resource-search-input.ts +46 -0
  152. package/src/browser/resource-search-messages.ts +76 -40
  153. package/src/browser/resource-search.ts +7 -0
  154. package/src/browser/search-bridge.ts +102 -0
  155. package/src/browser/search-commands.ts +59 -0
  156. package/src/browser/search.ts +13 -0
  157. package/src/browser/spotlight-position.ts +208 -0
  158. package/src/browser/testing.ts +5 -0
  159. package/src/browser/web-vitals.ts +25 -0
  160. package/src/capabilities/claims.ts +179 -0
  161. package/src/capabilities/client.ts +34 -0
  162. package/src/capabilities/command-link.ts +16 -0
  163. package/src/capabilities/executions.ts +298 -0
  164. package/src/capabilities/migrate.ts +81 -0
  165. package/src/capabilities/server.ts +114 -34
  166. package/src/capabilities/stream-body.ts +16 -0
  167. package/src/capabilities/streams.ts +28 -0
  168. package/src/cli/admin/ai-quotas.ts +161 -0
  169. package/src/cli/admin/ai-skills.ts +64 -0
  170. package/src/cli/admin/ai-usage.ts +53 -30
  171. package/src/cli/admin/app-credentials.ts +79 -0
  172. package/src/cli/admin/data.ts +17 -0
  173. package/src/cli/admin/index.ts +9 -0
  174. package/src/cli/capabilities.ts +29 -0
  175. package/src/cli/capability-streams.ts +25 -0
  176. package/src/cli/index.ts +3 -2
  177. package/src/config/env.ts +11 -8
  178. package/src/contracts/app.ts +23 -3
  179. package/src/contracts/capabilities.ts +68 -5
  180. package/src/contracts/capability-streams.ts +46 -0
  181. package/src/contracts/commands.ts +49 -0
  182. package/src/contracts/index.ts +6 -1
  183. package/src/contracts/rail-admin.ts +38 -0
  184. package/src/contracts/rail-preferences.ts +16 -0
  185. package/src/contracts/registry.ts +3 -27
  186. package/src/contracts/settings-types.ts +1 -1
  187. package/src/contracts/shared.ts +9 -5
  188. package/src/contracts/web-vitals.ts +27 -0
  189. package/src/desktop/solid.tsx +3 -3
  190. package/src/index.ts +0 -4
  191. package/src/server/index.ts +3 -0
  192. package/src/server/middleware/auth.ts +3 -2
  193. package/src/server/middleware/route-template.ts +9 -3
  194. package/src/server/middleware/runtime.ts +2 -1
  195. package/src/server/middleware/settings.ts +17 -11
  196. package/src/server/services/access-revision.ts +10 -0
  197. package/src/server/services/access.ts +1 -1
  198. package/src/server/services/index.ts +1 -0
  199. package/src/services/account-category-policy.ts +9 -0
  200. package/src/services/account-lifecycle/audit.ts +1 -1
  201. package/src/services/accounts/app.ts +12 -2
  202. package/src/services/accounts/email-write.ts +23 -0
  203. package/src/services/accounts/groups.ts +15 -4
  204. package/src/services/accounts/identities.ts +249 -0
  205. package/src/services/accounts/identity-reconciliation.ts +87 -0
  206. package/src/services/accounts/ipa-data.ts +2 -2
  207. package/src/services/accounts/local-groups.ts +2 -2
  208. package/src/services/accounts/posix.ts +45 -29
  209. package/src/services/accounts/users.ts +2 -2
  210. package/src/services/announcements/index.ts +69 -8
  211. package/src/services/audit/index.ts +1 -1
  212. package/src/services/cache-fill.ts +35 -0
  213. package/src/services/help/index.ts +109 -0
  214. package/src/services/help/maintenance.ts +25 -0
  215. package/src/services/help/store.ts +180 -0
  216. package/src/services/help/types.ts +46 -0
  217. package/src/services/identity/invocation-actor.ts +2 -2
  218. package/src/services/identity/key-config.ts +2 -1
  219. package/src/services/identity/key-ring.ts +1 -1
  220. package/src/services/index.ts +15 -0
  221. package/src/services/ipa/users.ts +268 -221
  222. package/src/services/legal-consent.ts +1 -1
  223. package/src/services/logging/index.ts +15 -13
  224. package/src/services/logging/trace.ts +2 -1
  225. package/src/services/logging/web-vitals.ts +81 -0
  226. package/src/services/mandates/index.ts +18 -4
  227. package/src/services/mandates/policy.ts +65 -2
  228. package/src/services/notifications/batches.ts +124 -57
  229. package/src/services/notifications/browser.ts +12 -0
  230. package/src/services/notifications/index.ts +0 -2
  231. package/src/services/notifications/platform.ts +0 -4
  232. package/src/services/oauth-tokens.ts +3 -3
  233. package/src/services/pdf/gotenberg.ts +107 -84
  234. package/src/services/pdf/index.ts +3 -0
  235. package/src/services/providers/local/users.ts +25 -16
  236. package/src/services/public-http.ts +101 -0
  237. package/src/services/rail-shortcuts.ts +95 -0
  238. package/src/services/rail-snapshot.ts +53 -0
  239. package/src/services/request-cache-redis.ts +28 -0
  240. package/src/services/session/index.ts +15 -2
  241. package/src/services/session/user.ts +62 -37
  242. package/src/services/settings/core-settings.ts +21 -28
  243. package/src/services/settings/defaults.ts +5 -0
  244. package/src/services/settings/store.ts +58 -84
  245. package/src/shared/ai-costs.ts +42 -0
  246. package/src/shared/ai-platform-prompt.ts +1 -1
  247. package/src/shared/ai-quotas.ts +136 -0
  248. package/src/shared/ai-usage.ts +6 -16
  249. package/src/shared/app-presentation.ts +21 -15
  250. package/src/shared/capability-messages.ts +14 -0
  251. package/src/shared/help.ts +6 -42
  252. package/src/shared/index.ts +6 -0
  253. package/src/ssr/AdminLayout.tsx +7 -5
  254. package/src/ssr/AdminSidebar.tsx +29 -15
  255. package/src/ssr/AppLaunchpad.island.tsx +24 -137
  256. package/src/ssr/AppLaunchpadPanel.tsx +181 -0
  257. package/src/ssr/BrowserPushRegistration.island.tsx +12 -0
  258. package/src/ssr/GlobalSearchDialog.tsx +167 -20
  259. package/src/ssr/GlobalSearchTrigger.island.tsx +33 -22
  260. package/src/ssr/Layout.tsx +17 -6
  261. package/src/ssr/LayoutBreadcrumbs.island.tsx +3 -5
  262. package/src/ssr/LayoutFooter.tsx +1 -1
  263. package/src/ssr/LayoutHeader.tsx +46 -14
  264. package/src/ssr/LayoutHelp.tsx +23 -22
  265. package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
  266. package/src/ssr/LayoutRail.tsx +17 -9
  267. package/src/ssr/MobileNavigation.tsx +71 -0
  268. package/src/ssr/MobileProfileActions.tsx +49 -0
  269. package/src/ssr/ProfilePreferences.island.tsx +3 -36
  270. package/src/ssr/RailApps.island.tsx +2 -2
  271. package/src/ssr/RailEditor.tsx +17 -2
  272. package/src/ssr/WorkspaceNavigation.island.tsx +7 -0
  273. package/src/ssr/WorkspaceNavigationProvider.tsx +16 -0
  274. package/src/ssr/admin-navigation.ts +7 -1
  275. package/src/ssr/help.ts +29 -0
  276. package/src/ssr/islands/SearchBar.tsx +67 -0
  277. package/src/ssr/islands/index.ts +3 -1
  278. package/src/ssr/layout-context.ts +2 -2
  279. package/src/ssr/layout-help-search.ts +15 -0
  280. package/src/ssr/mobile-menu-history.ts +70 -0
  281. package/src/ssr/platform-messages.ts +16 -8
  282. package/src/ssr/profile-actions.ts +42 -0
  283. package/src/ssr/rail-context.ts +8 -4
  284. package/src/ssr/rail-messages.ts +2 -0
  285. package/src/ssr/rail-navigation.ts +3 -3
  286. package/src/ssr/workspace-navigation.ts +75 -0
  287. package/src/styles/effects.css +6 -6
  288. package/src/styles/global.css +54 -0
  289. package/src/styles/input.css +43 -41
  290. package/src/styles/resource-search.css +505 -0
  291. package/src/styles/utilities-feedback.css +13 -0
  292. package/src/styles/utilities-layout.css +1 -5
  293. package/src/styles/utilities-navigation.css +3 -39
  294. package/src/types/ambient.d.ts +5 -0
  295. package/src/workflows/ai/runtime.ts +5 -1
  296. package/src/workflows/ai/store.ts +2 -0
  297. package/src/workflows/ai-actions.ts +16 -13
  298. package/src/workflows/definition.ts +10 -5
  299. package/src/workflows/store/actions.ts +44 -20
  300. package/src/workflows/store/index.ts +2 -0
  301. package/src/workflows/store/runs.ts +36 -0
  302. package/src/workflows/store/worker-pool.ts +55 -0
  303. package/src/workflows/store/worker-runtime.ts +90 -0
  304. package/scripts/app-favicon.test.ts +0 -49
  305. package/src/ai/chat/blocks.render.test.tsx +0 -1108
  306. package/src/ai/kit-skill.ts +0 -22
  307. package/src/ai/usage-migrate.ts +0 -81
  308. package/src/browser/CloudResourceSearch.behavior.test.tsx +0 -74
  309. package/src/browser/notification-target.ts +0 -27
  310. package/src/contracts/notification-live.ts +0 -66
  311. package/src/services/notifications/live.ts +0 -50
  312. package/src/ssr/BrowserNotifications.island.tsx +0 -135
  313. package/src/ssr/GlobalSearchDialog.behavior.test.tsx +0 -96
  314. package/src/ssr/Layout.render.test.tsx +0 -148
  315. package/src/ssr/islands/SearchBar.island.tsx +0 -80
@@ -1,4 +1,4 @@
1
- import { Chat, type ChatTimelineItem, useLocale } from "@k2b/ui";
1
+ import { Button, Chat, type ChatTimelineItem, useLocale } from "@k2b/ui";
2
2
  import { type Accessor, createEffect, createMemo, createSignal, type JSX, onCleanup, Show } from "solid-js";
3
3
  import type { AiActiveTurn } from "../client/projection";
4
4
  import { type AiActiveTurnSegment, isRenderableTurnBlock, splitActiveTurnBlocks } from "../protocol";
@@ -6,7 +6,7 @@ import { type AiAssistantTimelineItem, buildAiMessageTimeline, copyTextFromAssis
6
6
  import type { AiConversationTimelineEntry, AiStoredMessage } from "../types";
7
7
  import { AiTurnBlockList } from "./blocks";
8
8
  import { type AiChatActions, AiChatActionsProvider, createAssistantMessageActions, useAiChatActions } from "./message-actions";
9
- import { formatWorkedDuration, isCardToolName, isRecord, isSurveyToolName, textFromMessage } from "./message-utils";
9
+ import { formatWorkedDuration, isRecord, isSurveyToolName, textFromMessage } from "./message-utils";
10
10
  import { type AiToolDisclosureState, createAiToolDisclosureState } from "./tool-disclosure";
11
11
  import { aiChatMessages } from "./messages";
12
12
  import { CloudSurveyResultBlock } from "./visual-tools";
@@ -63,53 +63,17 @@ const surveyItem = (block: SurveyResultBlock, turnId: string): ChatTimelineItem
63
63
  content: <CloudSurveyResultBlock args={block.args} result={block.result} />,
64
64
  });
65
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
66
  export function AiAssistantContent(props: {
83
67
  item: AiAssistantTimelineItem;
84
68
  disclosureState?: AiToolDisclosureState;
85
69
  segmentId?: string;
86
70
  }): JSX.Element {
87
71
  const locale = useLocale();
88
- const blocks = createMemo(() => partitionCompletedAssistantBlocks(props.item.blocks));
89
72
  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
- );
73
+ return <div class="flex flex-col gap-2">
74
+ <Chat.Activity icon="ti ti-route" label={`${locale().startsWith("de") ? "Gearbeitet für" : "Worked for"} ${formatWorkedDuration(props.item.workedMs)}`} />
75
+ <AiTurnBlockList blocks={props.item.blocks} turnId={turnId()} disclosureState={props.disclosureState} />
76
+ </div>;
113
77
  }
114
78
 
115
79
  const storedItems = (
@@ -206,11 +170,18 @@ const storedItems = (
206
170
  createdAt: actionEntry?.createdAt ?? item.entries.at(-1)?.createdAt,
207
171
  class: segment.blocks.some(isWideBlock) ? "ai-chat-message-wide" : undefined,
208
172
  content: (
209
- <AiAssistantContent
210
- item={{ ...item, blocks: segment.blocks }}
211
- segmentId={segments.length > 1 ? `${item.id}:${index}` : undefined}
212
- disclosureState={disclosureState}
213
- />
173
+ <>
174
+ <Show when={index === 0 && item.entries.find(entry => entry.meta?.scheduledTask)?.meta?.scheduledTask}>
175
+ {task => <Button variant="ghost" size="xs" class="mb-2" onClick={() => actions.onOpenScheduledTaskRun?.(task().taskId, task().occurrenceId)} disabled={!actions.onOpenScheduledTaskRun}>
176
+ <i class="ti ti-calendar-time" aria-hidden="true" /> {aiChatMessages(locale).backgroundRun}
177
+ </Button>}
178
+ </Show>
179
+ <AiAssistantContent
180
+ item={{ ...item, blocks: segment.blocks }}
181
+ segmentId={segments.length > 1 ? `${item.id}:${index}` : undefined}
182
+ disclosureState={disclosureState}
183
+ />
184
+ </>
214
185
  ),
215
186
  actions:
216
187
  actionEntry && index === lastAssistant
@@ -268,7 +239,7 @@ const activeItems = (turn: AiActiveTurn | null, actions: AiChatActions, disclosu
268
239
  blocks={blocks}
269
240
  turnId={turn.turnId}
270
241
  streaming={turn.status === "running" && index === segments.length - 1}
271
- active
242
+ active={index === segments.length - 1}
272
243
  disclosureState={disclosureState}
273
244
  />
274
245
  ),
@@ -1,3 +1,45 @@
1
+ import { createEffect, onCleanup, onMount } from "solid-js";
2
+ import { useAiChatActions } from "./message-actions";
3
+
1
4
  export function AssistantMarkdownBlock(props: { html: string }) {
2
- return <div class="assistant-markdown-block" innerHTML={props.html} />;
5
+ const actions = useAiChatActions();
6
+ let element!: HTMLDivElement;
7
+ const originals = new WeakMap<HTMLAnchorElement, { href: string; target: string | null }>();
8
+ const files = new WeakMap<HTMLAnchorElement, string>();
9
+ createEffect(() => {
10
+ props.html;
11
+ for (const link of Array.from(element.querySelectorAll<HTMLAnchorElement>("a[href]"))) {
12
+ const original = originals.get(link) ?? { href: link.getAttribute("href")!, target: link.getAttribute("target") };
13
+ originals.set(link, original);
14
+ const file = actions.resolveFileLink?.(original.href);
15
+ link.setAttribute("href", file?.href ?? original.href);
16
+ const target = file ? null : original.target;
17
+ if (target) link.setAttribute("target", target);
18
+ else link.removeAttribute("target");
19
+ if (file) files.set(link, file.path);
20
+ else files.delete(link);
21
+ }
22
+ });
23
+ onMount(() => {
24
+ const handleClick = (event: MouseEvent) => {
25
+ if (
26
+ event.defaultPrevented ||
27
+ event.button !== 0 ||
28
+ event.metaKey ||
29
+ event.ctrlKey ||
30
+ event.shiftKey ||
31
+ event.altKey ||
32
+ !actions.onOpenFile
33
+ )
34
+ return;
35
+ const link = event.target instanceof Element ? event.target.closest("a") : null;
36
+ const path = link ? files.get(link) : undefined;
37
+ if (!path) return;
38
+ event.preventDefault();
39
+ actions.onOpenFile(path);
40
+ };
41
+ element.addEventListener("click", handleClick);
42
+ onCleanup(() => element.removeEventListener("click", handleClick));
43
+ });
44
+ return <div ref={element} class="assistant-markdown-block" innerHTML={props.html} />;
3
45
  }
@@ -8,9 +8,24 @@ export type AiToolDisclosureState = {
8
8
 
9
9
  export const createAiToolDisclosureState = (): AiToolDisclosureState => {
10
10
  const openByBlockId = new Map<string, boolean>();
11
+ const storageKey = (blockId: string) => `cloud.ai.tool-disclosure:${blockId}`;
11
12
  return {
12
- get: (blockId) => openByBlockId.get(blockId),
13
- set: (blockId, open) => openByBlockId.set(blockId, open),
13
+ get: (blockId) => {
14
+ if (!openByBlockId.has(blockId)) {
15
+ try {
16
+ const saved = window.sessionStorage.getItem(storageKey(blockId));
17
+ if (saved === "open") openByBlockId.set(blockId, true);
18
+ } catch { /* SSR and disabled storage keep the in-memory state. */ }
19
+ }
20
+ return openByBlockId.get(blockId);
21
+ },
22
+ set: (blockId, open) => {
23
+ openByBlockId.set(blockId, open);
24
+ try {
25
+ if (open) window.sessionStorage.setItem(storageKey(blockId), "open");
26
+ else window.sessionStorage.removeItem(storageKey(blockId));
27
+ } catch { /* Storage limits must not prevent expanding a tool. */ }
28
+ },
14
29
  };
15
30
  };
16
31
 
@@ -0,0 +1,52 @@
1
+ import { hasCapabilityTable } from "./capability-result";
2
+ import type { AiTurnBlock } from "../protocol";
3
+ import { isCardToolName, isSurveyToolName, isTextEditorToolName } from "./message-utils";
4
+
5
+ type Tool = Extract<AiTurnBlock, { kind: "tool" }>;
6
+ type Group = { kind: "tools"; blocks: Tool[] } | { kind: "block"; block: AiTurnBlock };
7
+
8
+ export function isFailedTool(tool: Tool): boolean {
9
+ if (tool.isError || tool.status === "failed" || tool.status === "rejected") return true;
10
+ const result = tool.result;
11
+ return ["code_run","code_inspect","code_interact"].includes(tool.name) && result !== null && typeof result === "object" && "status" in result && result.status === "error";
12
+ }
13
+
14
+ /** Visible content and interactive decisions are boundaries; model request boundaries are not. */
15
+ export function groupToolBlocks(blocks: readonly AiTurnBlock[]): Group[] {
16
+ const groups: Group[] = [];
17
+ for (const block of blocks) {
18
+ const ordinary =
19
+ block.kind === "tool" &&
20
+ !block.approval &&
21
+ block.status !== "awaiting_approval" &&
22
+ !["present", "code_present", "view_image", "code_secret"].includes(block.name) &&
23
+ !isCardToolName(block.name) &&
24
+ !isSurveyToolName(block.name) &&
25
+ !isTextEditorToolName(block.name) &&
26
+ !hasCapabilityTable(block);
27
+ if (ordinary) {
28
+ const last = groups.at(-1);
29
+ if (last?.kind === "tools") last.blocks.push(block);
30
+ else groups.push({ kind: "tools", blocks: [block] });
31
+ } else groups.push({ kind: "block", block });
32
+ }
33
+ return groups;
34
+ }
35
+
36
+ export function summarizeToolGroup(tools: readonly Tool[], locale: string): string {
37
+ const de = locale.startsWith("de");
38
+ const categories = tools.map((tool) => {
39
+ if (tool.name === "todo_write") return de ? "Arbeitsplan aktualisiert" : "Updated working plan";
40
+ if (tool.name.startsWith("code_")) return de ? "Mit Code gearbeitet" : "Worked with code";
41
+ if (tool.presentation?.kind === "capability" || tool.name === "read_cloud_resource")
42
+ return de ? "Cloud-Integration verwendet" : "Used Cloud integration";
43
+ if (["read_file", "list_files", "view_image"].includes(tool.name)) return de ? "Dateien gelesen" : "Read files";
44
+ if (tool.name === "write_file") return de ? "Dateien geschrieben" : "Wrote files";
45
+ if (["load_skill", "load_tools", "search_tools"].includes(tool.name))
46
+ return de ? "Werkzeuge und Wissen geladen" : "Loaded tools and guidance";
47
+ if (tool.name.startsWith("web_") || tool.name === "fetch_file") return de ? "Im Web recherchiert" : "Searched the web";
48
+ return de ? "Werkzeuge verwendet" : "Used tools";
49
+ });
50
+ const errors = tools.filter(isFailedTool).length;
51
+ return [...new Set(categories)].join(", ") + (errors ? ` · ${errors} ${de ? "fehlgeschlagen" : "failed"}` : "");
52
+ }
@@ -0,0 +1,25 @@
1
+ import { hasBillableAiPricing } from "../shared/ai-costs";
2
+ import { readAiSettingsState } from "./settings";
3
+ import type { AccessSubject } from "../server/services/access";
4
+ import type { AiChatQuotaSnapshot } from "../shared/ai-quotas";
5
+ import { listAssistantAiModels } from "./assistant-models";
6
+ import { aiQuotas } from "./quotas";
7
+
8
+ /** Own allowances only. Grant identities and unavailable model profiles stay private. */
9
+ export async function getAiChatQuotas(subject: AccessSubject): Promise<AiChatQuotaSnapshot> {
10
+ const config = await aiQuotas.config();
11
+ if (!config.enabled) return { enabled: false, balances: [] };
12
+ const models = await listAssistantAiModels(subject);
13
+ const snapshot = await aiQuotas.snapshot(subject);
14
+ const profiles = (await readAiSettingsState()).profiles;
15
+ return {
16
+ enabled: snapshot.enabled,
17
+ unit: config.unit ?? "EUR",
18
+ unlimitedModels: models
19
+ .filter((model) => !hasBillableAiPricing(profiles.find((profile) => profile.id === model.id)?.pricing))
20
+ .map((model) => model.id),
21
+ balances: snapshot.balances
22
+ .filter((b) => b.scope === "*" || models.some((m) => m.id === b.scope))
23
+ .map(({ sources: _sources, sourceDetails: _sourceDetails, ...balance }) => balance),
24
+ };
25
+ }
@@ -1,3 +1,5 @@
1
+ import { CapabilityGrantsSchema, type CapabilityGrant } from "../services/mandates";
2
+ export { CapabilityGrantsSchema as ChatTaskGrantsSchema };
1
3
  import { dates } from "@k2b/stdlib";
2
4
  import {
3
5
  type AiChatTask,
@@ -16,9 +18,11 @@ export const ChatTaskOccurrenceIdSchema = z.string().regex(AI_SHORT_ID_PATTERN).
16
18
  export const AiConversationIdSchema = z.string().regex(AI_SHORT_ID_PATTERN).describe("Readable six-character AI conversation ID.");
17
19
 
18
20
  export type AiChatTaskView = {
21
+ nextRunAt?: string | null;
19
22
  id: string;
20
23
  chatId: string;
21
24
  chatTitle: string;
25
+ grants: CapabilityGrant[];
22
26
  prompt: string;
23
27
  schedule: AiChatTaskSchedule;
24
28
  timezone: string;
@@ -35,6 +39,7 @@ export type AiChatTaskOccurrenceView = {
35
39
  trigger: "scheduled" | "manual";
36
40
  state: AiChatTaskOccurrenceState;
37
41
  error: string | null;
42
+ resultText: string | null;
38
43
  createdAt: string;
39
44
  startedAt: string | null;
40
45
  completedAt: string | null;
@@ -44,6 +49,7 @@ export const toAiChatTaskView = (task: AiChatTask): AiChatTaskView => ({
44
49
  id: task.shortId,
45
50
  chatId: task.chatId,
46
51
  chatTitle: task.chatTitle,
52
+ grants: task.grants,
47
53
  prompt: task.prompt,
48
54
  schedule: task.schedule,
49
55
  timezone: task.timezone,
@@ -60,6 +66,7 @@ export const toAiChatTaskOccurrenceView = (occurrence: AiChatTaskOccurrence, tas
60
66
  trigger: occurrence.trigger,
61
67
  state: occurrence.state,
62
68
  error: occurrence.error,
69
+ resultText: occurrence.resultText,
63
70
  createdAt: occurrence.createdAt,
64
71
  startedAt: occurrence.startedAt,
65
72
  completedAt: occurrence.completedAt,
@@ -92,9 +99,10 @@ export const chatTaskCreateFingerprint = (input: {
92
99
  prompt: string;
93
100
  schedule: z.infer<typeof ChatTaskScheduleInputSchema>;
94
101
  timezone?: string;
102
+ grants?: CapabilityGrant[];
95
103
  }): string =>
96
104
  new Bun.CryptoHasher("sha256")
97
- .update(JSON.stringify([input.chatId, input.prompt.trim(), input.schedule, input.timezone ?? null]))
105
+ .update(JSON.stringify([input.chatId, input.prompt.trim(), input.schedule, input.timezone ?? null, input.grants ?? []]))
98
106
  .digest("hex");
99
107
 
100
108
  export const normalizeChatTaskSchedule = async (