@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
@@ -0,0 +1,79 @@
1
+ import { z } from "zod";
2
+
3
+ export const CodeResourceId = z.string().regex(/^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz]{6}$/).describe("Public resource short ID returned by code_create or code_list.");
4
+ const id = CodeResourceId;
5
+ const runId = z.string().min(1).max(180).describe("Run ID returned by code_run in this conversation execution host.");
6
+ export const CODE_RUNTIME_TOOL_NAMES = ["code_run", "code_action", "code_inspect", "code_interact", "code_stop", "code_open", "code_export", "code_present", "code_secret"] as const;
7
+ export const CodeRunInput = z.object({
8
+ id: id.optional(),
9
+ resourceId: id.optional().describe("Optional data context for one-off code. Requires Manage; uses this resource database and shared files/KV without changing its source. Local storage stays temporary."),
10
+ version: z.number().int().positive().optional().describe("Published version to run; resource admins only. Omit for the current accessible source."),
11
+ code: z.string().min(1).max(1024 * 1024).optional().describe("One-off JavaScript/TypeScript entry exporting one function. Use code OR a saved resource id; no title or icon needed."),
12
+ inputPaths: z.array(z.string().min(1).max(500)).max(64).default([]).describe("Explicit current-chat input files. Scripts can read them; app test runs expose them only through the simulated picker. User apps never receive chat files."),
13
+ }).strict().refine(input => Number(input.id !== undefined) + Number(input.code !== undefined) === 1, "Provide exactly one of id or code").refine(input => input.version === undefined || input.id !== undefined,"A published version requires a saved resource id").refine(input => input.resourceId === undefined || input.code !== undefined, "resourceId requires one-off code");
14
+ export const CodeActionInput = z.object({
15
+ id,
16
+ action: z.string().regex(/^[a-z][a-zA-Z0-9_]*$/).max(80).describe("Exact name returned by code_actions."),
17
+ publishedVersion: z.number().int().positive().optional().describe("Exact publication returned by code_actions; a changed publication requires fresh discovery. Supply this OR draft revision."),
18
+ revision: z.number().int().positive().optional().describe("Exact draft revision returned by code_actions({draft:true}); Manage required. Supply this OR publishedVersion."),
19
+ input: z.json().describe("Action input matching the discovered inputSchema."),
20
+ }).strict().refine(input => Number(input.publishedVersion !== undefined) + Number(input.revision !== undefined) === 1, "Supply publishedVersion OR draft revision");
21
+ export const CodeInspectInput = z.object({ runId, waitMs: z.number().int().min(0).max(30000).default(0).describe("Wait up to this duration for background work to finish before returning its real state; never restarts work."), nodeId: z.string().min(1).max(80).optional(), offset: z.number().int().min(0).default(0), limit: z.number().int().min(1).max(100).default(20) }).strict();
22
+ const CodeInteraction = z.object({
23
+ id: z.string().min(1).max(80).describe("Control ID or pending modal ID returned by the snapshot."),
24
+ event: z.discriminatedUnion("type", [
25
+ z.object({ type: z.literal("change"), value: z.union([z.string(), z.number().finite(), z.boolean(), z.null(), z.array(z.string()), z.object({ start: z.iso.date().nullable(), end: z.iso.date().nullable() }).strict()]) }).strict(),
26
+ z.object({ type: z.literal("select"), key: z.string().nullable() }).strict(),
27
+ z.object({ type: z.literal("view"), value: z.enum(["chart", "table"]) }).strict(),
28
+ z.object({ type: z.literal("refresh") }).strict(),
29
+ z.object({ type: z.literal("request"), request: z.record(z.string(), z.json()) }).strict(),
30
+ ]).optional().describe("An event object, never a JSON-encoded string. Omit to activate a button. Example: {type:'change',value:['Sued']} or {type:'view',value:'table'}."),
31
+ answer: z.json().optional().describe("Only for the pending modal ID: the modal answer, or null to cancel. Do not use for controls."),
32
+ }).strict();
33
+ const validInteraction = (input: z.infer<typeof CodeInteraction>) => input.event === undefined || input.answer === undefined;
34
+ export const CodeInteractInput = CodeInteraction.partial({id:true}).extend({
35
+ runId,
36
+ // Three normal 15-second callback budgets share the existing 45-second call budget.
37
+ steps:z.array(CodeInteraction.refine(validInteraction,"Supply event OR answer")).min(1).max(3).optional()
38
+ .describe("Up to three sequential interactions in one call. Use steps OR the top-level id/event/answer. Stops on an error, pending modal or background work; returns completedSteps and nextStep."),
39
+ }).strict().refine(input=>input.steps ? input.id===undefined && input.event===undefined && input.answer===undefined : input.id!==undefined && (input.event===undefined || input.answer===undefined),"Supply steps OR one id with event/answer.");
40
+ export const CodeStopInput = z.object({ runId }).strict();
41
+ export const CodeOpenInput = z.object({ id }).strict();
42
+ export const CodePresentInput = z.object({ runId, title: z.string().trim().min(1).max(120) }).strict();
43
+ export const CodeExportInput = z.object({ runId, name: z.string().min(1).max(180).describe("Captured output filename returned by the snapshot.") }).strict();
44
+
45
+ export const CodeSecretInput = z.object({
46
+ resourceId:id.optional().describe("Scope to this app/script; omit to configure the current chat only."),
47
+ name:z.string().regex(/^[a-zA-Z][a-zA-Z0-9_-]{0,79}$/),
48
+ origin:z.string().url().max(2000).describe("Exact HTTPS origin allowed to receive the secret."),
49
+ header:z.string().min(1).max(80).default("authorization"),
50
+ prefix:z.string().max(160).default("Bearer "),
51
+ }).strict();
52
+
53
+ // Internal bridge envelope. Each model-facing tool receives only its own flat schema.
54
+ export const CodeRuntimeInput = z.discriminatedUnion("operation", [
55
+ CodeRunInput.safeExtend({ operation: z.literal("run") }),
56
+ CodeActionInput.safeExtend({ operation: z.literal("action") }),
57
+ CodeInspectInput.extend({ operation: z.literal("inspect") }),
58
+ CodeInteractInput.safeExtend({ operation: z.literal("interact") }),
59
+ CodeStopInput.extend({ operation: z.literal("stop") }),
60
+ CodeOpenInput.extend({ operation: z.literal("open") }),
61
+ CodeSecretInput.extend({ operation: z.literal("secret") }),
62
+ CodePresentInput.extend({ operation: z.literal("present") }),
63
+ CodeExportInput.extend({ operation: z.literal("export") }),
64
+ ]);
65
+ export type CodeRuntimeInput = z.infer<typeof CodeRuntimeInput>;
66
+ export function parseCodeToolInput(name: string, args: unknown): CodeRuntimeInput {
67
+ switch (name) {
68
+ case "code_secret": return { operation: "secret", ...CodeSecretInput.parse(args) };
69
+ case "code_action": return { operation: "action", ...CodeActionInput.parse(args) };
70
+ case "code_run": return { operation: "run", ...CodeRunInput.parse(args) };
71
+ case "code_inspect": return { operation: "inspect", ...CodeInspectInput.parse(args) };
72
+ case "code_interact": return { operation: "interact", ...CodeInteractInput.parse(args) };
73
+ case "code_stop": return { operation: "stop", ...CodeStopInput.parse(args) };
74
+ case "code_open": return { operation: "open", ...CodeOpenInput.parse(args) };
75
+ case "code_present": return { operation: "present", ...CodePresentInput.parse(args) };
76
+ case "code_export": return { operation: "export", ...CodeExportInput.parse(args) };
77
+ default: throw new Error("Unknown code tool");
78
+ }
79
+ }
package/src/ai/browser.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export { parseAiTodoPlan, type AiTodoPlan } from "./todo-contracts";
2
+ export { CodeResourceId, CodeActionInput, CODE_RUNTIME_TOOL_NAMES, CodeRuntimeInput, parseCodeToolInput } from "./browser-code-contracts";
1
3
  import { AI_TURN_ATTACHMENT_MAX_ITEMS } from "./limits";
2
4
  import type { AiConversation, AiDraftContentPart } from "./types";
3
5
 
@@ -1,13 +1,10 @@
1
+ import { createHelpReader, type HelpReaderFactory } from "../services/help";
2
+ import { createCodeSourceTool } from "./code-source-tools";
3
+ import { CODE_SOURCE_TOOLS } from "./code-source-contracts";
1
4
  import { createHash } from "node:crypto";
2
5
  import type { Tool, ToolContext, ToolResolver } from "@k2b/nessi";
3
6
  import { z } from "zod";
4
- import {
5
- createHelpCatalog,
6
- HELP_READ_MAX_CHARS,
7
- HELP_SEARCH_MAX_LIMIT,
8
- readHelpCatalog,
9
- searchHelpCatalog,
10
- } from "../_internal/help-catalog";
7
+ import { HELP_READ_MAX_CHARS, HELP_SEARCH_MAX_LIMIT, readHelpArticle } from "../_internal/help-catalog";
11
8
  import {
12
9
  type CapabilityActionManifest,
13
10
  type CapabilityActionReview,
@@ -16,8 +13,9 @@ import {
16
13
  cloudResourceRefAppId,
17
14
  resolveCapabilityResourceReader,
18
15
  } from "../contracts/capabilities";
19
- import type { CapabilityRegistryEntry, HelpRegistryEntry } from "../contracts/registry";
16
+ import type { CapabilityRegistryEntry } from "../contracts/registry";
20
17
  import type { RequestActor } from "../server";
18
+ import { recordRejectedAiCapability } from "./capability-execution";
21
19
  import { CLOUD_AI_DEFERRED_BUILTIN_TOOL_NAMES } from "./default-tools";
22
20
  import { type AiToolPreparationContext, defineAiTool, type PreparedAiTools, prepareAiTools } from "./tools";
23
21
  import type { AiConversationService, AiRuntimeTool, AiToolPresentation } from "./types";
@@ -304,14 +302,14 @@ const AiHelpDocumentSchema = AiHelpCatalogItemSchema.extend({
304
302
  truncated: z.boolean(),
305
303
  }).strict();
306
304
 
307
- /** Search and read the live Help snapshot without loading one tool per article. */
308
- export const createAiHelpTools = (registry: readonly HelpRegistryEntry[], locale = "en"): AiRuntimeTool[] => {
309
- const documents = createHelpCatalog(registry, locale);
305
+ /** Search and read published Help without loading one tool per article. */
306
+ export const createAiHelpTools = (help: HelpReaderFactory = createHelpReader, locale = "en"): AiRuntimeTool[] => {
307
+ const reader = help(locale);
310
308
 
311
309
  const search = defineAiTool({
312
310
  name: "search_help",
313
311
  description:
314
- "Search installed Cloud app Help when product behavior, settings, workflows, permissions, or app errors are unclear. Use 1-3 concise English product terms and scope appId when known. Returns compact document ids for read_help; skip this tool for straightforward live-data requests.",
312
+ "Search installed Cloud app Help when product behavior, settings, workflows, permissions, or app errors are unclear. Use concise product terms in the request language and scope appId when known. Returns compact document ids for read_help; skip this tool for straightforward live-data requests.",
315
313
  inputSchema: z
316
314
  .object({
317
315
  query: z.string().trim().min(1).max(200).describe("Product task or concept to find."),
@@ -322,7 +320,7 @@ export const createAiHelpTools = (registry: readonly HelpRegistryEntry[], locale
322
320
  outputSchema: z.object({ documents: z.array(AiHelpCatalogItemSchema).max(HELP_SEARCH_MAX_LIMIT) }).strict(),
323
321
  approval: "never",
324
322
  }).server(async ({ query, appId, limit }) => ({
325
- documents: searchHelpCatalog(documents, { query, appId, limit: boundedLimit(limit, DEFAULT_SEARCH_LIMIT, HELP_SEARCH_MAX_LIMIT) }),
323
+ documents: await reader.search({ query, appId, limit: boundedLimit(limit, DEFAULT_SEARCH_LIMIT, HELP_SEARCH_MAX_LIMIT) }),
326
324
  }));
327
325
 
328
326
  const read = defineAiTool({
@@ -338,25 +336,14 @@ export const createAiHelpTools = (registry: readonly HelpRegistryEntry[], locale
338
336
  .strict(),
339
337
  outputSchema: z.object({ document: AiHelpDocumentSchema.nullable() }).strict(),
340
338
  approval: "never",
341
- }).server(async ({ appId, documentId, query }) => ({
342
- document: readHelpCatalog(documents, { appId, documentId, query }),
343
- }));
339
+ }).server(async ({ appId, documentId, query }) => {
340
+ const document = await reader.read({ appId, documentId });
341
+ return { document: document ? readHelpArticle(document, query) : null };
342
+ });
344
343
 
345
344
  return [search, read];
346
345
  };
347
346
 
348
- const resolveHelpRegistry = async (
349
- listRegistry: () => Promise<HelpRegistryEntry[]>,
350
- onError?: (error: unknown) => void,
351
- ): Promise<HelpRegistryEntry[]> => {
352
- try {
353
- return await listRegistry();
354
- } catch (error) {
355
- onError?.(error);
356
- return [];
357
- }
358
- };
359
-
360
347
  const resolveCapabilityRegistry = async (
361
348
  listRegistry: () => Promise<CapabilityRegistryEntry[]>,
362
349
  onError?: (error: unknown) => void,
@@ -554,7 +541,9 @@ export const createAiToolMetaTools = (input: {
554
541
  export const createLoadedAiCapabilityTools = (input: {
555
542
  catalog: readonly AiCapabilityCatalogEntry[];
556
543
  loadedNames: readonly string[];
544
+ actor: RequestActor;
557
545
  review?: (entry: AiCapabilityCatalogEntry, args: unknown, context: ToolContext) => Promise<CapabilityActionReview | null>;
546
+ authorizeBackground?: (entry: AiCapabilityCatalogEntry, args: unknown) => Promise<void>;
558
547
  onReview?: (callId: string, review: CapabilityActionReview) => void;
559
548
  execute: (entry: AiCapabilityCatalogEntry, args: unknown, context: ToolContext) => Promise<unknown>;
560
549
  }): AiRuntimeTool[] => {
@@ -573,12 +562,18 @@ export const createLoadedAiCapabilityTools = (input: {
573
562
  // live review has resolved. The review may supply an app-owned scope.
574
563
  approval: "never",
575
564
  }).server(async (args, context) => {
576
- if (entry.kind === "action") {
565
+ await input.authorizeBackground?.(entry, args);
566
+ if (!input.authorizeBackground && entry.kind === "action" && (entry.operation as CapabilityActionManifest).approval !== "none") {
577
567
  const review = (await input.review?.(entry, args, context)) ?? null;
578
568
  if (review && context.callId) input.onReview?.(context.callId, review);
579
569
  const message =
580
570
  review?.message ?? `${entry.appName}: ${entry.title}\nReview the validated arguments below before running this Action.`;
581
- if (!(await context.requestApproval(message))) throw new Error("Capability Action was rejected by the user.");
571
+ if (!(await context.requestApproval(message))) {
572
+ if (input.actor.kind === "user") {
573
+ await recordRejectedAiCapability({ entry, actor: input.actor, args }).catch(() => undefined);
574
+ }
575
+ throw new Error("Capability Action was rejected by the user.");
576
+ }
582
577
  }
583
578
  return input.execute(entry, args, context);
584
579
  }),
@@ -599,6 +594,11 @@ export const createAiResourceReaderTool = (input: {
599
594
  outputSchema: z.unknown(),
600
595
  approval: "never",
601
596
  }).server(async (ref, context) => {
597
+ if (ref.type === "assistant.artifact") {
598
+ const tool = createCodeSourceTool("code_read");
599
+ if (tool.location !== "server") throw new Error("Code reader must execute on the server.");
600
+ return tool.run(CODE_SOURCE_TOOLS.code_read.input.parse({ id: ref.id }), context);
601
+ }
602
602
  const appId = cloudResourceRefAppId(ref);
603
603
  const app = input.apps.find((candidate) => candidate.appId === appId);
604
604
  const reader = app ? resolveCapabilityResourceReader(app.manifest, ref) : null;
@@ -611,6 +611,24 @@ export const createAiResourceReaderTool = (input: {
611
611
  });
612
612
 
613
613
  /** Nessi resolver: one registry/load-state snapshot drives discovery, loading, schemas, and execution per model turn. */
614
+ /** Background discovery belongs to one run, never the interactive conversation. */
615
+ export const createRunToolStore = (initialNames: readonly string[]): Pick<AiConversationService, "getLoadedTools" | "loadTools"> => {
616
+ let current = [...new Set(initialNames)];
617
+ return {
618
+ getLoadedTools: async () => [...current],
619
+ loadTools: async ({ names, maxLoadedTools }) => {
620
+ const requested = [...new Set(names.map((name) => name.trim()).filter(Boolean))];
621
+ const alreadyLoaded = requested.filter((name) => current.includes(name));
622
+ const added = requested.filter((name) => !current.includes(name));
623
+ const combined = [...current, ...added];
624
+ const limit = Math.floor(maxLoadedTools ?? 0);
625
+ const evicted = limit > 0 ? combined.slice(0, Math.max(0, combined.length - limit)) : [];
626
+ current = combined.slice(evicted.length);
627
+ return { loaded: added.filter((name) => current.includes(name)), alreadyLoaded, evicted };
628
+ },
629
+ };
630
+ };
631
+
614
632
  export const createAiToolResolver =
615
633
  (input: {
616
634
  conversationId: string;
@@ -621,12 +639,12 @@ export const createAiToolResolver =
621
639
  store: Pick<AiConversationService, "getLoadedTools" | "loadTools">;
622
640
  listRegistry?: () => Promise<CapabilityRegistryEntry[]>;
623
641
  onCapabilityRegistryError?: (error: unknown) => void;
624
- listHelpRegistry?: () => Promise<HelpRegistryEntry[]>;
625
- onHelpRegistryError?: (error: unknown) => void;
642
+ help?: HelpReaderFactory;
626
643
  locale?: string;
627
644
  maxLoadedTools?: number;
628
645
  execute?: (entry: AiCapabilityCatalogEntry, args: unknown, context: ToolContext) => Promise<unknown>;
629
646
  review?: (entry: AiCapabilityCatalogEntry, args: unknown, context: ToolContext) => Promise<CapabilityActionReview | null>;
647
+ authorizeBackground?: (entry: AiCapabilityCatalogEntry, args: unknown) => Promise<void>;
630
648
  onReview?: (callId: string, review: CapabilityActionReview) => void;
631
649
  onPrepared?: (snapshot: {
632
650
  prepared: PreparedAiTools;
@@ -635,10 +653,9 @@ export const createAiToolResolver =
635
653
  }) => void;
636
654
  }): ToolResolver =>
637
655
  async (): Promise<Tool[]> => {
638
- const [registry, persistedLoadedNames, helpRegistry] = await Promise.all([
656
+ const [registry, persistedLoadedNames] = await Promise.all([
639
657
  input.listRegistry ? resolveCapabilityRegistry(input.listRegistry, input.onCapabilityRegistryError) : [],
640
658
  input.store.getLoadedTools({ conversationId: input.conversationId }),
641
- input.listHelpRegistry ? resolveHelpRegistry(input.listHelpRegistry, input.onHelpRegistryError) : [],
642
659
  ]);
643
660
  const configuredLimit = Math.floor(input.maxLoadedTools ?? 0);
644
661
  const loadedNames = configuredLimit > 0 ? persistedLoadedNames.slice(-configuredLimit) : persistedLoadedNames;
@@ -651,9 +668,9 @@ export const createAiToolResolver =
651
668
  }
652
669
  const allowed = input.allowedTools == null ? null : new Set(input.allowedTools);
653
670
  const capabilityCatalog = buildAiCapabilityCatalog(registry).filter((entry) => !allowed || allowed.has(entry.name));
654
- const helpTools = input.listHelpRegistry ? createAiHelpTools(helpRegistry, input.locale) : [];
671
+ const helpTools = input.help ? createAiHelpTools(input.help, input.locale) : [];
655
672
  const resourceTool =
656
- input.execute && capabilityCatalog.length > 0
673
+ input.execute && (capabilityCatalog.length > 0 || input.staticTools.some((tool) => tool.def.name === "code_read"))
657
674
  ? createAiResourceReaderTool({ apps: registry, catalog: capabilityCatalog, execute: input.execute })
658
675
  : null;
659
676
  const builtIns = [...input.staticTools, ...helpTools, ...(resourceTool ? [resourceTool] : [])].filter(
@@ -679,8 +696,10 @@ export const createAiToolResolver =
679
696
  ? createLoadedAiCapabilityTools({
680
697
  catalog: capabilityCatalog,
681
698
  loadedNames,
699
+ actor: input.actor,
682
700
  review: input.review,
683
701
  onReview: input.onReview,
702
+ authorizeBackground: input.authorizeBackground,
684
703
  execute: input.execute,
685
704
  })
686
705
  : []),
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import type { ToolContext } from "@k2b/nessi";
3
3
  import { type CapabilityDispatchDependencies, dispatchCapability } from "../api/capabilities";
4
+ import { capabilityValueMeta, recordCapabilityExecution } from "../capabilities/executions";
4
5
  import { type CapabilityActionManifest, type CapabilityActionReview, CapabilityActionReviewSchema } from "../contracts/capabilities";
5
6
  import type { RequestActor } from "../server";
6
7
  import type { AccessSubject } from "../server/services/access";
@@ -98,18 +99,24 @@ const dispatchAiCapability = async (input: AiCapabilityCall, review: boolean): P
98
99
  const headers = new Headers();
99
100
  if (input.locale) headers.set(LOCALE_HEADER, input.locale);
100
101
  const action = input.entry.kind === "action" ? (input.entry.operation as CapabilityActionManifest) : null;
101
- if (!review && action?.idempotency === "required" && input.context.callId) {
102
- const key = idempotencyKey(input.conversationId, input.context.callId);
103
- if (input.turnId) {
104
- await aiToolAudit.noteCapabilityDispatch({
105
- conversationId: input.conversationId,
106
- turnId: input.turnId,
107
- callId: input.context.callId,
108
- toolName: input.entry.name,
109
- idempotencyKey: key,
110
- });
111
- }
112
- headers.set("idempotency-key", key);
102
+ // One request id per dispatch links the tool call, the platform execution
103
+ // record, and the target app's own audit rows.
104
+ const requestId = crypto.randomUUID();
105
+ headers.set("x-request-id", requestId);
106
+ const key =
107
+ !review && action?.idempotency === "required" && input.context.callId
108
+ ? idempotencyKey(input.conversationId, input.context.callId)
109
+ : null;
110
+ if (key) headers.set("idempotency-key", key);
111
+ if (!review && input.turnId && input.context.callId) {
112
+ await aiToolAudit.noteCapabilityDispatch({
113
+ conversationId: input.conversationId,
114
+ turnId: input.turnId,
115
+ callId: input.context.callId,
116
+ toolName: input.entry.name,
117
+ requestId,
118
+ idempotencyKey: key,
119
+ });
113
120
  }
114
121
  const request = new Request("http://cloud.internal/api/ai/capability", {
115
122
  method: "POST",
@@ -120,6 +127,7 @@ const dispatchAiCapability = async (input: AiCapabilityCall, review: boolean): P
120
127
  await dispatch({
121
128
  request,
122
129
  kind: input.entry.kind === "query" ? "queries" : "actions",
130
+ origin: "assistant",
123
131
  review,
124
132
  appId: input.entry.appId,
125
133
  capabilityId: input.entry.operation.localId,
@@ -149,3 +157,30 @@ export const reviewAiCapability = async (input: AiCapabilityCall): Promise<Capab
149
157
  };
150
158
 
151
159
  export const executeAiCapability = (input: AiCapabilityCall): Promise<unknown> => dispatchAiCapability(input, false);
160
+
161
+ /**
162
+ * A rejected approval never reaches the dispatcher, so the assistant records
163
+ * the same execution row the dispatcher would have written for the attempt.
164
+ */
165
+ export const recordRejectedAiCapability = async (input: {
166
+ entry: Pick<AiCapabilityCatalogEntry, "appId" | "kind" | "name" | "operation">;
167
+ actor: CapabilityActor;
168
+ args: unknown;
169
+ }): Promise<void> => {
170
+ const at = new Date();
171
+ await recordCapabilityExecution({
172
+ requestId: crypto.randomUUID(),
173
+ origin: "assistant",
174
+ appId: input.entry.appId,
175
+ capability: input.entry.name,
176
+ kind: input.entry.kind,
177
+ destructive: "destructive" in input.entry.operation ? input.entry.operation.destructive : false,
178
+ actorKind: "user",
179
+ actorId: input.actor.user.id,
180
+ userId: input.actor.user.id,
181
+ status: "rejected",
182
+ inputMeta: capabilityValueMeta(input.args),
183
+ startedAt: at,
184
+ completedAt: at,
185
+ });
186
+ };