@k2b/cloud 0.6.0 → 0.8.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 (349) hide show
  1. package/README.md +2 -2
  2. package/package.json +22 -11
  3. package/scripts/README.md +10 -0
  4. package/scripts/browser-performance.ts +14 -0
  5. package/scripts/build-pdf-renderer.ts +39 -0
  6. package/scripts/build.ts +20 -35
  7. package/scripts/preload.ts +16 -12
  8. package/scripts/sync-recovery-smoke.ts +22 -6
  9. package/src/_internal/app-presentation.ts +14 -2
  10. package/src/_internal/build-metadata.ts +8 -1
  11. package/src/_internal/capabilities.ts +131 -7
  12. package/src/_internal/capability-streams.ts +81 -0
  13. package/src/_internal/define-app.ts +80 -37
  14. package/src/_internal/heartbeat.ts +4 -0
  15. package/src/_internal/help-catalog.ts +27 -172
  16. package/src/_internal/help.ts +13 -76
  17. package/src/_internal/page-responses.ts +2 -0
  18. package/src/_internal/postgres-application-name.ts +30 -0
  19. package/src/_internal/process-identity.ts +15 -0
  20. package/src/_internal/process-sync.ts +1 -1
  21. package/src/_internal/readiness.ts +4 -1
  22. package/src/_internal/registry-validation.ts +29 -25
  23. package/src/_internal/registry.ts +2 -53
  24. package/src/_internal/runtime-context.ts +5 -16
  25. package/src/_internal/server-timing.ts +13 -0
  26. package/src/_internal/status-preserving-ssr.ts +18 -6
  27. package/src/_internal/web-vitals-asset.ts +10 -0
  28. package/src/access/PermissionEditor.tsx +18 -151
  29. package/src/access/PrincipalPicker.tsx +110 -0
  30. package/src/access/ResourceApiKeys.tsx +1 -1
  31. package/src/access/ui.ts +1 -0
  32. package/src/ai/admin.ts +14 -13
  33. package/src/ai/assistant-models.ts +12 -1
  34. package/src/ai/audio-format.ts +34 -0
  35. package/src/ai/audio-tool.ts +90 -0
  36. package/src/ai/browser-code-contracts.ts +201 -0
  37. package/src/ai/browser.ts +4 -0
  38. package/src/ai/capabilities.ts +55 -36
  39. package/src/ai/capability-execution.ts +47 -12
  40. package/src/ai/chat/blocks.tsx +229 -90
  41. package/src/ai/chat/builtin-tools.tsx +2 -4
  42. package/src/ai/chat/capability-result.ts +15 -0
  43. package/src/ai/chat/capability-table.tsx +2 -10
  44. package/src/ai/chat/composer-adapter.ts +83 -3
  45. package/src/ai/chat/message-actions.tsx +8 -7
  46. package/src/ai/chat/message-utils.ts +25 -1
  47. package/src/ai/chat/messages.ts +2 -0
  48. package/src/ai/chat/presentation.tsx +31 -47
  49. package/src/ai/chat/primitives.tsx +43 -1
  50. package/src/ai/chat/tool-disclosure.tsx +21 -2
  51. package/src/ai/chat/tool-groups.ts +58 -0
  52. package/src/ai/chat-quotas.ts +25 -0
  53. package/src/ai/chat-task-contracts.ts +14 -10
  54. package/src/ai/chat-tasks.ts +297 -43
  55. package/src/ai/client/controller.ts +165 -39
  56. package/src/ai/client/file-source.ts +12 -6
  57. package/src/ai/client/live-connection.ts +1 -0
  58. package/src/ai/client/projection.ts +29 -11
  59. package/src/ai/client/transport.ts +6 -0
  60. package/src/ai/code-capability-routes.ts +105 -0
  61. package/src/ai/code-capability-transport.ts +39 -0
  62. package/src/ai/code-execution.ts +53 -0
  63. package/src/ai/code-mode-skill.ts +118 -0
  64. package/src/ai/code-runtime-tools.ts +179 -0
  65. package/src/ai/code-source-contracts.ts +270 -0
  66. package/src/ai/code-source-tools.ts +188 -0
  67. package/src/ai/compaction.ts +27 -6
  68. package/src/ai/data-analysis-skill.ts +10 -0
  69. package/src/ai/default-tools.ts +99 -3
  70. package/src/ai/dictation-runtime.ts +171 -0
  71. package/src/ai/dictations.ts +188 -0
  72. package/src/ai/draft-content.ts +49 -0
  73. package/src/ai/enrich.ts +6 -0
  74. package/src/ai/executor.ts +189 -33
  75. package/src/ai/fetch-file-tool.ts +2 -2
  76. package/src/ai/file-content-version.ts +27 -0
  77. package/src/ai/file-context.ts +2 -1
  78. package/src/ai/file-media-type.ts +12 -0
  79. package/src/ai/file-reference-contracts.ts +15 -0
  80. package/src/ai/file-tools.ts +8 -1
  81. package/src/ai/files-store.ts +212 -71
  82. package/src/ai/fixtures/vision-encrypted.pdf +0 -0
  83. package/src/ai/grids-skill.ts +42 -10
  84. package/src/ai/http.ts +26 -8
  85. package/src/ai/index.ts +26 -3
  86. package/src/ai/inference-calls.ts +197 -0
  87. package/src/ai/live-events.ts +5 -1
  88. package/src/ai/live.ts +1 -1
  89. package/src/ai/markdown-pdf-tool.ts +2 -2
  90. package/src/ai/memories.ts +30 -27
  91. package/src/ai/memory-learning-runs.ts +1 -1
  92. package/src/ai/memory-learning.ts +64 -32
  93. package/src/ai/memory-tool.ts +1 -5
  94. package/src/ai/memory-workflow-evidence.ts +9 -4
  95. package/src/ai/message-queue.ts +178 -0
  96. package/src/ai/migrate.ts +189 -11
  97. package/src/ai/model-pricing.ts +43 -0
  98. package/src/ai/pdf-render-worker.ts +75 -0
  99. package/src/ai/pdf-render.fixture.ts +28 -0
  100. package/src/ai/pdf-render.ts +77 -0
  101. package/src/ai/projects-routes.ts +18 -43
  102. package/src/ai/projects.ts +33 -14
  103. package/src/ai/protocol.ts +3 -0
  104. package/src/ai/provider.ts +1 -8
  105. package/src/ai/quota-provider.ts +192 -0
  106. package/src/ai/quota-report.ts +152 -0
  107. package/src/ai/quotas-migrate.ts +45 -0
  108. package/src/ai/quotas.ts +168 -0
  109. package/src/ai/routes.ts +241 -11
  110. package/src/ai/run-timeout.ts +12 -0
  111. package/src/ai/runtime-tools.ts +7 -5
  112. package/src/ai/runtime.ts +74 -17
  113. package/src/ai/settings.ts +36 -8
  114. package/src/ai/short-id.ts +4 -5
  115. package/src/ai/skill-catalog.ts +0 -10
  116. package/src/ai/skill-search.ts +58 -0
  117. package/src/ai/skill-seeds.ts +114 -20
  118. package/src/ai/skill-tool.ts +21 -5
  119. package/src/ai/skills-routes.ts +49 -4
  120. package/src/ai/skills.ts +358 -163
  121. package/src/ai/store.ts +416 -201
  122. package/src/ai/stream.ts +5 -0
  123. package/src/ai/structured-runs.ts +4 -3
  124. package/src/ai/structured.ts +12 -1
  125. package/src/ai/system-prompt.ts +14 -2
  126. package/src/ai/task-contracts.ts +84 -0
  127. package/src/ai/task-execution.ts +92 -0
  128. package/src/ai/timeline.ts +2 -5
  129. package/src/ai/todo-contracts.ts +32 -0
  130. package/src/ai/todo-tool.ts +19 -0
  131. package/src/ai/tool-audit.ts +30 -40
  132. package/src/ai/tools.ts +6 -0
  133. package/src/ai/transcription.ts +202 -0
  134. package/src/ai/turn-timing.ts +114 -0
  135. package/src/ai/types.ts +58 -10
  136. package/src/ai/ui.tsx +3 -0
  137. package/src/ai/usage.ts +118 -118
  138. package/src/ai/vision-tool.ts +60 -10
  139. package/src/api/admin-ai-projects.ts +2 -2
  140. package/src/api/admin-ai-quotas.ts +80 -0
  141. package/src/api/admin-ai-skills.ts +48 -5
  142. package/src/api/admin-ai-usage.ts +2 -2
  143. package/src/api/admin-core-settings.ts +25 -0
  144. package/src/api/admin-identity.ts +91 -51
  145. package/src/api/admin-linux-identities.ts +10 -4
  146. package/src/api/admin-rail.ts +58 -0
  147. package/src/api/announcements.ts +14 -0
  148. package/src/api/app-approval.ts +2 -2
  149. package/src/api/auth.ts +1 -1
  150. package/src/api/capabilities.ts +324 -43
  151. package/src/api/capability-streams.ts +262 -0
  152. package/src/api/help.ts +8 -44
  153. package/src/api/index.ts +8 -2
  154. package/src/api/mcp.ts +45 -34
  155. package/src/api/me-web-vitals.ts +35 -0
  156. package/src/api/me.ts +2 -0
  157. package/src/api/search/schemas.ts +26 -15
  158. package/src/api/search.bench.ts +1 -1
  159. package/src/api/search.ts +54 -17
  160. package/src/browser/CloudResourceSearch.tsx +640 -702
  161. package/src/browser/app-approval-vault.ts +19 -7
  162. package/src/browser/command-bridge.ts +109 -0
  163. package/src/browser/command-shortcuts.ts +76 -0
  164. package/src/browser/commands.ts +67 -0
  165. package/src/browser/navigation-search.ts +48 -0
  166. package/src/browser/notifications.ts +1 -1
  167. package/src/browser/resource-picker.tsx +5 -18
  168. package/src/browser/resource-search-dialog.ts +9 -0
  169. package/src/browser/resource-search-input.ts +46 -0
  170. package/src/browser/resource-search-messages.ts +76 -40
  171. package/src/browser/resource-search.ts +7 -0
  172. package/src/browser/search-bridge.ts +102 -0
  173. package/src/browser/search-commands.ts +59 -0
  174. package/src/browser/search.ts +13 -0
  175. package/src/browser/spotlight-position.ts +208 -0
  176. package/src/browser/testing.ts +6 -0
  177. package/src/browser/web-vitals.ts +25 -0
  178. package/src/capabilities/claims.ts +179 -0
  179. package/src/capabilities/client.ts +34 -0
  180. package/src/capabilities/command-link.ts +16 -0
  181. package/src/capabilities/executions.ts +298 -0
  182. package/src/capabilities/migrate.ts +81 -0
  183. package/src/capabilities/server.ts +116 -35
  184. package/src/capabilities/stream-body.ts +16 -0
  185. package/src/capabilities/streams.ts +28 -0
  186. package/src/cli/admin/ai-quotas.ts +161 -0
  187. package/src/cli/admin/ai-skills.ts +64 -0
  188. package/src/cli/admin/ai-usage.ts +53 -30
  189. package/src/cli/admin/app-credentials.ts +77 -0
  190. package/src/cli/admin/data.ts +17 -0
  191. package/src/cli/admin/index.ts +10 -1
  192. package/src/cli/admin/instance.ts +2 -2
  193. package/src/cli/admin/linux.ts +4 -2
  194. package/src/cli/admin/sync.ts +1 -1
  195. package/src/cli/capabilities.ts +42 -0
  196. package/src/cli/capability-streams.ts +25 -0
  197. package/src/cli/index.ts +3 -2
  198. package/src/config/define-env.ts +97 -0
  199. package/src/config/env.ts +147 -51
  200. package/src/config/index.ts +3 -1
  201. package/src/contracts/app.ts +23 -3
  202. package/src/contracts/capabilities.ts +75 -5
  203. package/src/contracts/capability-streams.ts +46 -0
  204. package/src/contracts/commands.ts +49 -0
  205. package/src/contracts/index.ts +5 -2
  206. package/src/contracts/rail-admin.ts +38 -0
  207. package/src/contracts/rail-preferences.ts +16 -0
  208. package/src/contracts/registry.ts +5 -27
  209. package/src/contracts/settings-types.ts +1 -1
  210. package/src/contracts/shared.ts +18 -5
  211. package/src/contracts/web-vitals.ts +27 -0
  212. package/src/desktop/index.ts +1 -0
  213. package/src/desktop/solid.tsx +1 -1
  214. package/src/index.ts +8 -6
  215. package/src/server/index.ts +3 -0
  216. package/src/server/middleware/auth.ts +3 -2
  217. package/src/server/middleware/route-template.ts +9 -3
  218. package/src/server/middleware/runtime.ts +2 -1
  219. package/src/server/middleware/settings.ts +17 -11
  220. package/src/server/ratelimit.ts +1 -1
  221. package/src/server/services/access-revision.ts +10 -0
  222. package/src/server/services/access.ts +3 -1
  223. package/src/server/services/index.ts +1 -0
  224. package/src/services/account-category-policy.ts +9 -0
  225. package/src/services/account-lifecycle/audit.ts +1 -1
  226. package/src/services/accounts/app.ts +12 -2
  227. package/src/services/accounts/email-write.ts +23 -0
  228. package/src/services/accounts/groups.ts +15 -4
  229. package/src/services/accounts/identities.ts +249 -0
  230. package/src/services/accounts/identity-reconciliation.ts +87 -0
  231. package/src/services/accounts/ipa-data.ts +2 -2
  232. package/src/services/accounts/local-groups.ts +2 -2
  233. package/src/services/accounts/notification-sender.ts +12 -2
  234. package/src/services/accounts/posix.ts +45 -29
  235. package/src/services/accounts/users.ts +2 -2
  236. package/src/services/announcements/index.ts +69 -8
  237. package/src/services/app-approval.ts +11 -10
  238. package/src/services/audit/index.ts +1 -1
  239. package/src/services/cache-fill.ts +35 -0
  240. package/src/services/help/index.ts +109 -0
  241. package/src/services/help/maintenance.ts +25 -0
  242. package/src/services/help/store.ts +180 -0
  243. package/src/services/help/types.ts +46 -0
  244. package/src/services/identity/invocation-actor.ts +2 -2
  245. package/src/services/identity/key-config.ts +8 -5
  246. package/src/services/identity/key-ring.ts +1 -1
  247. package/src/services/identity/runtime-config.ts +6 -5
  248. package/src/services/index.ts +15 -0
  249. package/src/services/ipa/posix.ts +1 -1
  250. package/src/services/ipa/sync.ts +1 -1
  251. package/src/services/ipa/users.ts +268 -221
  252. package/src/services/legal-consent.ts +1 -1
  253. package/src/services/logging/index.ts +15 -13
  254. package/src/services/logging/trace.ts +2 -1
  255. package/src/services/logging/web-vitals.ts +81 -0
  256. package/src/services/mandates/index.ts +21 -4
  257. package/src/services/mandates/policy.ts +165 -2
  258. package/src/services/notifications/batches.ts +125 -57
  259. package/src/services/notifications/browser.ts +12 -0
  260. package/src/services/notifications/index.ts +0 -2
  261. package/src/services/notifications/platform.ts +0 -4
  262. package/src/services/oauth-tokens.ts +3 -3
  263. package/src/services/pdf/gotenberg.ts +131 -72
  264. package/src/services/pdf/index.ts +11 -8
  265. package/src/services/providers/local/users.ts +25 -16
  266. package/src/services/public-http.ts +101 -0
  267. package/src/services/rail-shortcuts.ts +95 -0
  268. package/src/services/rail-snapshot.ts +53 -0
  269. package/src/services/request-cache-redis.ts +29 -0
  270. package/src/services/session/index.ts +15 -2
  271. package/src/services/session/user.ts +62 -37
  272. package/src/services/settings/app.ts +5 -1
  273. package/src/services/settings/core-settings.ts +26 -35
  274. package/src/services/settings/defaults.ts +5 -0
  275. package/src/services/settings/index.ts +3 -3
  276. package/src/services/settings/store.ts +58 -84
  277. package/src/services/webauthn.ts +1 -1
  278. package/src/shared/ai-costs.ts +42 -0
  279. package/src/shared/ai-platform-prompt.ts +1 -1
  280. package/src/shared/ai-quotas.ts +136 -0
  281. package/src/shared/ai-usage.ts +6 -16
  282. package/src/shared/app-presentation.ts +21 -15
  283. package/src/shared/capability-messages.ts +14 -0
  284. package/src/shared/help.ts +6 -42
  285. package/src/shared/index.ts +3 -0
  286. package/src/shared/markdown/extensions/info-blocks.ts +1 -1
  287. package/src/ssr/AdminLayout.tsx +7 -5
  288. package/src/ssr/AdminSidebar.tsx +30 -16
  289. package/src/ssr/AppLaunchpad.island.tsx +33 -136
  290. package/src/ssr/AppLaunchpadPanel.tsx +181 -0
  291. package/src/ssr/BrowserPushRegistration.island.tsx +12 -0
  292. package/src/ssr/GlobalSearchDialog.tsx +167 -20
  293. package/src/ssr/GlobalSearchTrigger.island.tsx +33 -22
  294. package/src/ssr/Layout.tsx +17 -6
  295. package/src/ssr/LayoutBreadcrumbs.island.tsx +3 -5
  296. package/src/ssr/LayoutFooter.tsx +1 -1
  297. package/src/ssr/LayoutHeader.tsx +46 -14
  298. package/src/ssr/LayoutHelp.tsx +22 -23
  299. package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
  300. package/src/ssr/LayoutRail.tsx +17 -9
  301. package/src/ssr/MinimalLayout.tsx +1 -1
  302. package/src/ssr/MobileNavigation.tsx +71 -0
  303. package/src/ssr/MobileProfileActions.tsx +49 -0
  304. package/src/ssr/ProfilePreferences.island.tsx +3 -36
  305. package/src/ssr/RailApps.island.tsx +2 -2
  306. package/src/ssr/RailEditor.tsx +17 -2
  307. package/src/ssr/WorkspaceNavigation.island.tsx +7 -0
  308. package/src/ssr/WorkspaceNavigationProvider.tsx +16 -0
  309. package/src/ssr/admin-navigation.ts +11 -1
  310. package/src/ssr/help.ts +29 -0
  311. package/src/ssr/islands/SearchBar.tsx +67 -0
  312. package/src/ssr/islands/index.ts +3 -1
  313. package/src/ssr/layout-context.ts +2 -2
  314. package/src/ssr/layout-help-search.ts +15 -0
  315. package/src/ssr/mobile-menu-history.ts +70 -0
  316. package/src/ssr/platform-messages.ts +16 -8
  317. package/src/ssr/profile-actions.ts +42 -0
  318. package/src/ssr/rail-context.ts +8 -4
  319. package/src/ssr/rail-messages.ts +2 -0
  320. package/src/ssr/rail-navigation.ts +3 -3
  321. package/src/ssr/workspace-navigation.ts +75 -0
  322. package/src/styles/effects.css +6 -6
  323. package/src/styles/global.css +54 -0
  324. package/src/styles/input.css +43 -41
  325. package/src/styles/resource-search.css +505 -0
  326. package/src/styles/utilities-feedback.css +13 -0
  327. package/src/styles/utilities-layout.css +1 -5
  328. package/src/styles/utilities-navigation.css +3 -39
  329. package/src/types/ambient.d.ts +5 -0
  330. package/src/workflows/ai/index.ts +2 -2
  331. package/src/workflows/ai/runtime.ts +19 -90
  332. package/src/workflows/ai/store.ts +2 -0
  333. package/src/workflows/ai/types.ts +6 -82
  334. package/src/workflows/ai-actions.ts +16 -13
  335. package/src/workflows/definition.ts +10 -5
  336. package/src/workflows/store/actions.ts +44 -20
  337. package/src/workflows/store/index.ts +4 -2
  338. package/src/workflows/store/runs.ts +36 -0
  339. package/src/workflows/store/worker-pool.ts +55 -0
  340. package/src/workflows/store/worker-runtime.ts +90 -0
  341. package/src/ai/kit-skill.ts +0 -22
  342. package/src/ai/usage-migrate.ts +0 -81
  343. package/src/browser/notification-target.ts +0 -27
  344. package/src/contracts/notification-live.ts +0 -66
  345. package/src/server/services/freeipa/test-certificates.ts +0 -44
  346. package/src/services/notifications/live.ts +0 -50
  347. package/src/services/session/test-fixture.ts +0 -18
  348. package/src/ssr/BrowserNotifications.island.tsx +0 -135
  349. package/src/ssr/islands/SearchBar.island.tsx +0 -80
@@ -0,0 +1,179 @@
1
+ import type { ToolContext } from "@k2b/nessi";
2
+ import { z } from "zod";
3
+ import { readBoundedJson } from "../_internal/bounded-json";
4
+ import { getApp } from "../_internal/registry";
5
+ import type { RequestActor } from "../server";
6
+ import { signInvocationToken } from "../services/identity/invocation-token";
7
+ import { withActiveIdentitySigner } from "../services/identity/key-ring";
8
+ import { LOCALE_HEADER } from "../shared/locale";
9
+ import { resolveAiCapabilityActor } from "./capability-execution";
10
+ import { CODE_CAPABILITY_TOKEN_HEADER, codeCapabilityOperation } from "./code-capability-transport";
11
+ import { authorizeCodeExecution } from "./code-execution";
12
+ import { aiConversations } from "./store";
13
+
14
+ const Reply = z.object({
15
+ ok: z.literal(true),
16
+ data: z.object({
17
+ status: z.enum(["running", "busy", "done", "lost"]),
18
+ phase: z.enum(["starting", "running", "busy", "waiting_for_user"]).optional(),
19
+ result: z.unknown().optional(),
20
+ approvals: z.array(z.object({ id: z.uuid(), message: z.string(), decision: z.boolean().nullable() })),
21
+ }),
22
+ });
23
+ type Context = ToolContext & {
24
+ actor: RequestActor;
25
+ conversationId?: string;
26
+ turnId?: string;
27
+ locale?: string;
28
+ reportProgress?: (message: string) => Promise<void>;
29
+ };
30
+
31
+ /** The tab only renders progress and approvals; server calls own all execution and resumption. */
32
+ export const runManagedCodeTool =
33
+ (name: string) =>
34
+ async (args: unknown, context: Context): Promise<z.infer<ReturnType<typeof z.json>>> => {
35
+ if (!context.conversationId || !context.turnId) throw new Error("Code execution requires an active Assistant turn");
36
+ const runConfig = await aiConversations.getTurnRunConfig({ conversationId: context.conversationId, turnId: context.turnId });
37
+ if (!runConfig || runConfig.kind === "compact" || Boolean(runConfig.background) !== Boolean(runConfig.mandate)) {
38
+ throw new Error("Code execution requires a valid turn and task-scoped background authority.");
39
+ }
40
+ const { actor } = await resolveAiCapabilityActor({
41
+ conversationId: context.conversationId,
42
+ persistedActor: context.actor,
43
+ store: aiConversations,
44
+ });
45
+ await authorizeCodeExecution(context.conversationId, context.turnId, actor.user.id);
46
+ await context.reportProgress?.(context.locale?.startsWith("de") ? "Ausführungshost verbinden" : "Connecting execution host");
47
+ const app = await getApp("assistant");
48
+ if (!app) throw new Error("Assistant code host is unavailable");
49
+ let callback: Awaited<ReturnType<typeof signInvocationToken>> | undefined;
50
+ const request = async (decision?: { id: string; approved: boolean }) => {
51
+ context.signal.throwIfAborted();
52
+ await authorizeCodeExecution(context.conversationId!, context.turnId!, actor.user.id);
53
+ const signed = await withActiveIdentitySigner(
54
+ "invocation",
55
+ (signer) =>
56
+ signInvocationToken({
57
+ targetAppId: "assistant",
58
+ callingAppId: "core",
59
+ operation: `tool:${name}`,
60
+ schemaHash: null,
61
+ authority: {
62
+ sub: actor.user.id,
63
+ principal_type: "user",
64
+ access_subject_type: "user",
65
+ access_subject_id: actor.user.id,
66
+ credential_kind: "session",
67
+ scopes: [],
68
+ },
69
+ signer,
70
+ issuer: signer.issuer,
71
+ }),
72
+ { signal: context.signal, timeoutMs: 5000 },
73
+ );
74
+ if (!callback || callback.claims.exp * 1000 - Date.now() < 10_000) {
75
+ callback = await withActiveIdentitySigner(
76
+ "invocation",
77
+ (signer) =>
78
+ signInvocationToken({
79
+ targetAppId: "core",
80
+ callingAppId: "assistant",
81
+ operation: codeCapabilityOperation(context.conversationId!, context.turnId!),
82
+ schemaHash: null,
83
+ authority: {
84
+ sub: actor.user.id,
85
+ principal_type: "user",
86
+ access_subject_type: "user",
87
+ access_subject_id: actor.user.id,
88
+ credential_kind: "session",
89
+ scopes: [],
90
+ },
91
+ signer,
92
+ issuer: signer.issuer,
93
+ }),
94
+ { signal: context.signal, timeoutMs: 5000 },
95
+ );
96
+ }
97
+ const headers = new Headers({ authorization: `Bearer ${signed.token}`, "content-type": "application/json" });
98
+ headers.set(CODE_CAPABILITY_TOKEN_HEADER, callback.token);
99
+ if (context.locale) headers.set(LOCALE_HEADER, context.locale);
100
+ const response = await fetch(new URL(`/_internal/assistant/tools/${name}`, app.baseUrl), {
101
+ method: "POST",
102
+ headers,
103
+ redirect: "manual",
104
+ signal: context.signal,
105
+ body: JSON.stringify({
106
+ conversationId: context.conversationId,
107
+ input: { turnId: context.turnId, callId: context.callId, name, args, ...(decision ? { decision } : {}) },
108
+ }),
109
+ });
110
+ const body = await readBoundedJson(response, 256 * 1024);
111
+ if (!body.ok || !response.ok)
112
+ throw new Error("Code host request failed; inspect the existing call before starting another execution");
113
+ return Reply.parse(body.data).data;
114
+ };
115
+ return waitForManagedCodeCall(
116
+ request,
117
+ runConfig.background
118
+ ? {
119
+ ...context,
120
+ requestApproval: async () => {
121
+ throw new Error("Background code cannot request interactive approval. Update task grants in the normal chat.");
122
+ },
123
+ }
124
+ : context,
125
+ );
126
+ };
127
+
128
+ /** Ordered reviews are replayed through Nessi before consuming a durable result. */
129
+ export async function waitForManagedCodeCall(
130
+ request: (decision?: { id: string; approved: boolean }) => Promise<z.infer<typeof Reply>["data"]>,
131
+ context: Pick<ToolContext, "signal" | "requestApproval"> & { locale?: string; reportProgress?: (message: string) => Promise<void> },
132
+ ): Promise<z.infer<ReturnType<typeof z.json>>> {
133
+ const seen = new Set<string>();
134
+ let lastPhase: string | undefined;
135
+ while (true) {
136
+ context.signal.throwIfAborted();
137
+ const state = await request();
138
+ if (state.phase && state.phase !== lastPhase) {
139
+ lastPhase = state.phase;
140
+ const labels = context.locale?.startsWith("de")
141
+ ? {
142
+ starting: "Ausführungshost startet",
143
+ running: "Code wird ausgeführt",
144
+ busy: "Wartet auf laufende Ausführung",
145
+ waiting_for_user: "Wartet auf Freigabe",
146
+ }
147
+ : {
148
+ starting: "Starting execution host",
149
+ running: "Executing code",
150
+ busy: "Waiting for current execution",
151
+ waiting_for_user: "Waiting for approval",
152
+ };
153
+ await context.reportProgress?.(labels[state.phase]);
154
+ }
155
+ // Replay the same ordered approval history when Nessi resumes this server tool.
156
+ // Skipping already resolved reviews would shift Nessi's child action IDs.
157
+ for (const approval of state.approvals) {
158
+ if (seen.has(approval.id)) continue;
159
+ const approved = await context.requestApproval(approval.message);
160
+ seen.add(approval.id);
161
+ if (approval.decision === null) await request({ id: approval.id, approved });
162
+ }
163
+ if (state.status === "done") return z.json().parse(state.result);
164
+ if (state.status === "lost")
165
+ throw new Error("The isolated code host was lost. The call was not replayed; inspect saved data before starting a new run.");
166
+ await new Promise<void>((resolve, reject) => {
167
+ const aborted = () => {
168
+ clearTimeout(timer);
169
+ reject(context.signal.reason);
170
+ };
171
+ const timer = setTimeout(() => {
172
+ context.signal.removeEventListener("abort", aborted);
173
+ resolve();
174
+ }, 250);
175
+ context.signal.addEventListener("abort", aborted, { once: true });
176
+ if (context.signal.aborted) aborted();
177
+ });
178
+ }
179
+ }
@@ -0,0 +1,270 @@
1
+ import { z } from "zod";
2
+ import { PrincipalSchema } from "../contracts/shared";
3
+ import { CodeResourceId } from "./browser-code-contracts";
4
+ import { AiFileLocation, AiFileReference } from "./file-reference-contracts";
5
+
6
+ // Flat Assistant tool inputs. The owning service also validates its domain contracts.
7
+ const Id = z.object({ id: CodeResourceId }).strict();
8
+ const Page = z.number().int().min(1).max(100000).default(1);
9
+ const ArtifactPath = z
10
+ .string()
11
+ .min(1)
12
+ .max(180)
13
+ .regex(/^[a-zA-Z0-9_.-]+(?:\/[a-zA-Z0-9_.-]+)*$/)
14
+ .refine((path) => path.split("/").every((part) => part !== "." && part !== ".."));
15
+ const ArtifactFile = z
16
+ .object({
17
+ path: ArtifactPath,
18
+ content: z
19
+ .string()
20
+ .max(1024 * 1024)
21
+ .describe("Complete source file content; read existing content before replacing it."),
22
+ })
23
+ .strict();
24
+ const Icon = z
25
+ .string()
26
+ .regex(/^ti ti-[a-z0-9-]+$/)
27
+ .max(80);
28
+ const ArtifactMetadata = z
29
+ .object({
30
+ title: z.string().trim().min(1).max(120).optional(),
31
+ description: z.string().trim().max(500).optional(),
32
+ icon: Icon.optional(),
33
+ })
34
+ .strict();
35
+ const ArtifactCreate = ArtifactMetadata.required({ title: true });
36
+ const PublicationNote = z.string().trim().min(1).max(1000);
37
+ const DatabaseSql = z.object({
38
+ sql: z.string().trim().min(1).max(16000).describe("Read-only SELECT; no writes, CTEs or comments. Use LIMIT."),
39
+ params: z.array(z.json()).max(1000).default([]),
40
+ });
41
+
42
+ const CodeWriteInput = Id.extend({
43
+ expectedRevision: z.number().int().positive(),
44
+ entry: ArtifactPath.optional(),
45
+ files: z
46
+ .array(z.union([ArtifactFile, z.object({ path: ArtifactPath, fromFile: AiFileReference }).strict()]))
47
+ .min(1)
48
+ .max(64),
49
+ });
50
+
51
+ export const CODE_SOURCE_TOOLS = {
52
+ code_files: {
53
+ description:
54
+ "List authorized files in one explicit chat, Project or App shared store. Returns locations, metadata and nextAfter. Use code_file_stat to obtain the opaque version before copying; does not load bytes into model context.",
55
+ input: AiFileLocation.omit({ path: true }).extend({
56
+ after: z.string().max(500).default(""),
57
+ limit: z.number().int().min(1).max(1000).default(100),
58
+ }),
59
+ },
60
+ code_file_stat: {
61
+ description:
62
+ "Inspect one authorized file location. Returns exists plus its exact versioned reference, size and mediaType, never its bytes. App files require Use, Project files Read, chats ownership.",
63
+ input: z.object({ file: AiFileLocation }).strict(),
64
+ },
65
+ code_file_copy: {
66
+ description:
67
+ "Copy one exact file version between chat, Project and App shared stores with fresh review. Source bytes never enter tool output. App destination requires Use, Project destination Write, chat destination ownership. Set expectedVersion null only for a new destination; otherwise use its version from code_file_stat. Source/version, target rights, overwrite and byte limits are rechecked. This grants no access to other files.",
68
+ review: true,
69
+ input: z
70
+ .object({ source: AiFileReference, destination: AiFileLocation, expectedVersion: z.string().min(1).max(128).nullable() })
71
+ .strict(),
72
+ },
73
+ code_database_export: {
74
+ description:
75
+ "Export an App database backup into the current chat as a file. Manage required. Returns the actual path/version; no backup bytes enter tool output. Never overwrites an existing file. The chat file-size/storage limits apply. This is a Studio backup operation, not access to vendor APIs.",
76
+ input: Id.extend({
77
+ path: z
78
+ .string()
79
+ .startsWith("/")
80
+ .max(240)
81
+ .default("/database.sqlite")
82
+ .describe("New chat filename; an existing path is rejected. Choose another name after a conflict."),
83
+ }),
84
+ },
85
+ code_manage_read: {
86
+ description:
87
+ "Read an App management snapshot before deletion: title, source revision, publication, file/JSON counts, database connection and managementRevision. Manage required. Does not expose another user's credentials or database server internals.",
88
+ input: Id,
89
+ },
90
+ code_delete: {
91
+ description:
92
+ "Permanently delete an App, all source history, publications, grants and server data with fresh review. Read code_manage_read first. Database physical cleanup is queued, not claimed finished. Cannot delete an App that changed since review.",
93
+ review: true,
94
+ input: Id.extend({ expectedManagementRevision: z.string().regex(/^[a-f0-9]{64}$/) }),
95
+ },
96
+ code_database_read: {
97
+ description:
98
+ "Read App database connection status, generation, dataRevision and aggregate overview. Manage required; does not create a database. These are Studio contracts, not access to the backing service API.",
99
+ input: Id,
100
+ },
101
+ code_database_clear: {
102
+ description:
103
+ "Permanently clear all table rows with fresh review while preserving schema, source, files and JSON storage. Manage required. Read code_database_read first. Returns completed and clearedTables; partial failure is explicit and cannot be blindly retried.",
104
+ review: true,
105
+ input: Id.extend({ expectedGeneration: z.string().regex(/^[a-f0-9]{64}$/), expectedDataRevision: z.uuid() }),
106
+ },
107
+ code_database_reset: {
108
+ description:
109
+ "Disconnect and permanently discard an App database, including schema, with fresh review. Preserves source, publications and files/JSON storage. Next explicit connection creates an empty database; physical deletion is queued. Read code_database_read first. Never use as a routine query fix.",
110
+ review: true,
111
+ input: Id.extend({
112
+ expectedGeneration: z
113
+ .string()
114
+ .regex(/^[a-f0-9]{64}$/)
115
+ .nullable(),
116
+ expectedDataRevision: z.uuid().nullable(),
117
+ }),
118
+ },
119
+ code_storage_list: {
120
+ description:
121
+ "Inspect an App's shared files or JSON keys for administration. Manage required. Returns storageRevision, counts and a page of key/bytes/mediaType/version metadata; no file contents. Follow nextAfter.",
122
+ input: Id.extend({
123
+ area: z.enum(["files", "kv"]),
124
+ after: z.string().max(240).default(""),
125
+ limit: z.number().int().min(1).max(1000).default(100),
126
+ }),
127
+ },
128
+ code_storage_delete: {
129
+ description:
130
+ "Delete one shared file/JSON key, or clear an explicit storage area, with fresh user review. Manage required. Source, publications and database are preserved. Read code_storage_list first.",
131
+ review: true,
132
+ input: Id.extend({
133
+ area: z.enum(["files", "kv", "all"]),
134
+ key: z.string().min(1).max(240).optional().describe("Delete this exact key; omit to clear the entire area. Not valid with area all."),
135
+ expectedStorageRevision: z
136
+ .number()
137
+ .int()
138
+ .positive()
139
+ .describe("storageRevision from code_storage_list; concurrent writes invalidate the review."),
140
+ }).refine((input) => input.area !== "all" || input.key === undefined, "all clears both areas and cannot select a key"),
141
+ },
142
+ code_access_read: {
143
+ description:
144
+ "Read an App's current grants and accessRevision before changing access. Manage required. App Use is read; Manage is admin. Skill access is separate.",
145
+ input: Id,
146
+ },
147
+ code_access_change: {
148
+ description:
149
+ "Grant, change or revoke one App permission with fresh user review. Read code_access_read first. Sharing a referenced Skill is a separate operation.",
150
+ review: true,
151
+ input: Id.extend({
152
+ expectedAccessRevision: z
153
+ .string()
154
+ .regex(/^[a-f0-9]{64}$/)
155
+ .describe("Exact accessRevision returned by code_access_read."),
156
+ principal: PrincipalSchema.refine(
157
+ (principal) => principal.type !== "service_account",
158
+ "Studio Apps support user, group, authenticated or public grants.",
159
+ )
160
+ .optional()
161
+ .describe(
162
+ "New user or group from core.entities.search, authenticated, or public (read/run only, no server data). Service accounts are unsupported. Supply principal OR accessId.",
163
+ ),
164
+ accessId: z.uuid().optional().describe("Existing grant ID from code_access_read; supply accessId OR principal."),
165
+ permission: z.enum(["read", "admin"]).nullable().describe("read means Use, admin means Manage; null removes an existing accessId."),
166
+ })
167
+ .refine(
168
+ (input) =>
169
+ Number(input.principal !== undefined) + Number(input.accessId !== undefined) === 1 &&
170
+ (input.principal === undefined || input.permission !== null),
171
+ "Supply principal with permission OR accessId with permission (null removes)",
172
+ )
173
+ .refine((input) => input.principal?.type !== "public" || input.permission === "read", "Public Apps only support read/run access"),
174
+ },
175
+ code_actions: {
176
+ description:
177
+ "Discover a visible App's currently published actions and their complete input/output JSON Schemas without running code. Returns publishedVersion for code_action. Requires Use; no draft source or management tools are loaded.",
178
+ input: Id.extend({
179
+ draft: z.boolean().default(false).describe("Inspect current draft actions instead of the publication; Manage required."),
180
+ }),
181
+ },
182
+ code_sql: {
183
+ description:
184
+ "Run a read-only SELECT directly against an App database without writing code. Supply its id and parameterized SQL. Does not create or connect a database. Requires current resource access; Project access applies only in that Project chat. Narrow columns and add LIMIT for large results.",
185
+ input: Id.extend(DatabaseSql.shape),
186
+ },
187
+ code_versions: {
188
+ description: "List numbered published versions with notes, author and date. Admin only. Separate from automatic file-save history.",
189
+ input: Id.extend({ page: Page }),
190
+ },
191
+ code_list: {
192
+ description:
193
+ "Find accessible Apps, including published Project Apps in the current authorized Project chat. Reuse the intended resource instead of creating duplicates.",
194
+ input: z
195
+ .object({
196
+ page: Page,
197
+ q: z.string().max(120).optional().describe("Find by title or description; matches only metadata you may access."),
198
+ })
199
+ .strict(),
200
+ },
201
+ code_read: {
202
+ description:
203
+ "Read the current file, or omit path for the directory. Follow nextOffset for long files. revision is optional and only needed to inspect history.",
204
+ input: Id.extend({
205
+ path: ArtifactPath.optional().describe("Exact relative file path. Omit to list files."),
206
+ offset: z.number().int().min(0).default(0).describe("UTF-16 offset; follow nextOffset for the remaining content."),
207
+ revision: z.number().int().positive().optional().describe("Historical revision from code_history. Omit for current files."),
208
+ }),
209
+ },
210
+ code_history: {
211
+ description: "List previous saved versions. Use code_read with a returned revision only when historical source is needed.",
212
+ input: Id.extend({ page: Page }),
213
+ },
214
+ code_fork: {
215
+ description:
216
+ "Create a private editable copy of an accessible publication. Source is copied; shared data, database and Project associations are not. Project-only access does not permit forks.",
217
+ input: Id,
218
+ },
219
+ code_publish: {
220
+ description:
221
+ "Publish tested working source as the next numbered version. A concise change note is required. Never changes sharing. Read current revision first; a concurrent edit causes a conflict.",
222
+ input: Id.extend({
223
+ expectedRevision: z
224
+ .number()
225
+ .int()
226
+ .positive()
227
+ .describe("Current working revision from code_read; prevents conflicting publication or restore."),
228
+ note: PublicationNote,
229
+ }),
230
+ },
231
+ code_unpublish: {
232
+ description:
233
+ "Withdraw the current App publication with fresh review. Use code_manage_read first. Preserves source, publication history and data; ordinary Use-level callers can no longer start this App or its actions.",
234
+ review: true,
235
+ input: Id.extend({ expectedPublishedVersion: z.number().int().positive() }),
236
+ },
237
+ code_restore: {
238
+ description:
239
+ "Restore a published version including title, description and icon as the working source and a new latest publication, atomically. Automatically records Restore version X. Preserves publication history and user data; do not call code_publish again.",
240
+ input: Id.extend({
241
+ version: z.number().int().positive().describe("Publication number returned by code_versions."),
242
+ expectedRevision: z
243
+ .number()
244
+ .int()
245
+ .positive()
246
+ .describe("Current working revision from code_read; prevents conflicting publication or restore."),
247
+ }),
248
+ },
249
+ code_update: {
250
+ description: "Change working title, description or Tabler icon. Does not publish. Use e.g. ti ti-calculator or ti ti-chart-bar.",
251
+ input: Id.extend(ArtifactMetadata.shape),
252
+ },
253
+ code_create: {
254
+ description:
255
+ "Create a private reusable App with an optional UI, published actions and optional persistence. For one-off analysis, use code_run with code instead. Returns id and entry path; write source with code_write. Optional icon uses the complete class, e.g. ti ti-chart-bar; omit it when unsure. Does not run or share anything.",
256
+ input: ArtifactCreate.pick({ title: true, description: true, icon: true }),
257
+ },
258
+ code_write: {
259
+ description:
260
+ "Atomically save complete source files against expectedRevision from code_read/create. Other files stay unchanged. Returns revision and compiler diagnostics; never runs. fromFile imports one exact chat/Project/App file reference as UTF-8 source without printing bytes. File imports receive fresh review because source may be shared or published. Use code_file_stat first; source file and bundle byte limits apply.",
261
+ review: (input: unknown) => CodeWriteInput.parse(input).files.some((file) => "fromFile" in file),
262
+ input: CodeWriteInput,
263
+ },
264
+ code_remove: {
265
+ description:
266
+ "Remove one source file, saving immediately. Source history is retained for recovery; does not delete the app. Removing an absent file is harmless. Returns diagnostics for any broken imports.",
267
+ input: Id.extend({ path: ArtifactPath.describe("Exact relative path of the file to remove.") }),
268
+ },
269
+ } as const;
270
+ export type CodeSourceToolName = keyof typeof CODE_SOURCE_TOOLS;
@@ -0,0 +1,188 @@
1
+ import { z } from "zod";
2
+ import { readBoundedJson } from "../_internal/bounded-json";
3
+ import { getApp } from "../_internal/registry";
4
+ import {
5
+ capabilityIdempotencyKeyHash,
6
+ capabilityRequestHash,
7
+ claimCapabilityIdempotency,
8
+ completeCapabilityClaim,
9
+ markCapabilityClaimUncertain,
10
+ } from "../capabilities/claims";
11
+ import { signInvocationToken } from "../services/identity/invocation-token";
12
+ import { withActiveIdentitySigner } from "../services/identity/key-ring";
13
+ import { LOCALE_HEADER } from "../shared/locale";
14
+ import { resolveAiCapabilityActor } from "./capability-execution";
15
+ import { aiChatTasks } from "./chat-tasks";
16
+ import { authorizeCodeExecution } from "./code-execution";
17
+ import { CODE_SOURCE_TOOLS, type CodeSourceToolName } from "./code-source-contracts";
18
+ import { aiConversations } from "./store";
19
+ import { defineAiTool } from "./tools";
20
+
21
+ const WRITE_TOOLS = new Set([
22
+ "code_create",
23
+ "code_write",
24
+ "code_remove",
25
+ "code_update",
26
+ "code_fork",
27
+ "code_publish",
28
+ "code_restore",
29
+ "code_database_export",
30
+ ...Object.entries(CODE_SOURCE_TOOLS)
31
+ .filter(([, definition]) => "review" in definition && definition.review)
32
+ .map(([name]) => name),
33
+ ]);
34
+
35
+ const Reply = z.discriminatedUnion("ok", [
36
+ z.object({ ok: z.literal(true), data: z.unknown() }),
37
+ z.object({ ok: z.literal(false), error: z.object({ code: z.string(), message: z.string() }) }),
38
+ ]);
39
+
40
+ /** Direct server tools: discovery exposes code_*; Assistant owns the resource services. */
41
+ export function createCodeSourceTool(name: CodeSourceToolName) {
42
+ const definition = CODE_SOURCE_TOOLS[name];
43
+ return defineAiTool({
44
+ name,
45
+ description:
46
+ definition.description +
47
+ (WRITE_TOOLS.has(name) ? " If a write has an unknown outcome, inspect current state before attempting another write." : ""),
48
+ inputSchema: definition.input,
49
+ outputSchema: z.unknown(),
50
+ approval: "never",
51
+ }).server(async (input, context) => {
52
+ if (!context.conversationId) throw new Error("Code tools require an Assistant conversation.");
53
+ const { actor } = await resolveAiCapabilityActor({
54
+ conversationId: context.conversationId,
55
+ persistedActor: context.actor,
56
+ store: aiConversations,
57
+ });
58
+ const databaseOperations: Partial<Record<CodeSourceToolName, string>> = {
59
+ code_sql: "query",
60
+ code_database_read: "status",
61
+ code_database_export: "export",
62
+ code_database_clear: "clear",
63
+ code_database_reset: "reset",
64
+ };
65
+ const databaseOperation = databaseOperations[name];
66
+ let authorizeDatabase: (() => Promise<void>) | undefined;
67
+ if (context.turnId && databaseOperation) {
68
+ const config = await aiConversations.getTurnRunConfig({ conversationId: context.conversationId, turnId: context.turnId });
69
+ if (config?.kind !== "compact" && config?.background) {
70
+ const { id } = z.object({ id: z.string() }).parse(input);
71
+ const conversationId = context.conversationId,
72
+ turnId = context.turnId;
73
+ authorizeDatabase = async () => {
74
+ const { config } = await authorizeCodeExecution(conversationId, turnId, actor.user.id);
75
+ if (!config.mandate) throw new Error("Background database access requires task grants");
76
+ await aiChatTasks.authorizeRuntime({
77
+ mandate: config.mandate,
78
+ kind: "database",
79
+ input: { resourceId: id, operation: databaseOperation },
80
+ });
81
+ };
82
+ }
83
+ }
84
+ await authorizeDatabase?.();
85
+ const app = await getApp("assistant");
86
+ if (!app) throw new Error("Assistant is unavailable.");
87
+ const signal = context.signal;
88
+ const send = async (review = false) => {
89
+ await authorizeDatabase?.();
90
+ const signed = await withActiveIdentitySigner(
91
+ "invocation",
92
+ (signer) =>
93
+ signInvocationToken({
94
+ targetAppId: "assistant",
95
+ callingAppId: "core",
96
+ operation: `tool:${name}`,
97
+ schemaHash: null,
98
+ authority: {
99
+ sub: actor.user.id,
100
+ principal_type: "user",
101
+ access_subject_type: "user",
102
+ access_subject_id: actor.user.id,
103
+ credential_kind: "session",
104
+ scopes: [],
105
+ },
106
+ signer,
107
+ issuer: signer.issuer,
108
+ }),
109
+ { signal, timeoutMs: 5000 },
110
+ );
111
+ const headers = new Headers({ authorization: `Bearer ${signed.token}`, "content-type": "application/json" });
112
+ if (context.locale) headers.set(LOCALE_HEADER, context.locale);
113
+ const response = await fetch(new URL(`/_internal/assistant/tools/${name}`, app.baseUrl), {
114
+ method: "POST",
115
+ headers,
116
+ redirect: "manual",
117
+ signal,
118
+ body: JSON.stringify({ input, conversationId: context.conversationId, ...(review ? { review: true } : {}) }),
119
+ });
120
+ const parsed = await readBoundedJson(response, 256 * 1024);
121
+ if (!parsed.ok) throw new Error("Invalid Assistant tool response. A write may have completed; inspect its state before retrying.");
122
+ const reply = Reply.safeParse(parsed.data);
123
+ if (!reply.success) throw new Error("Assistant tool request failed. Inspect resource state before retrying a write.");
124
+ if (!reply.data.ok) throw new Error(`${reply.data.error.code}: ${reply.data.error.message}`);
125
+ if (!response.ok) throw new Error("Assistant tool request failed.");
126
+ return { data: reply.data.data, status: response.status };
127
+ };
128
+ if ("review" in definition && (typeof definition.review === "function" ? definition.review(input) : definition.review)) {
129
+ const preview = z.object({ data: z.object({ message: z.string().min(1) }) }).parse((await send(true)).data).data;
130
+ if (!authorizeDatabase && !(await context.requestApproval(preview.message)))
131
+ throw new Error("The user declined this change. No mutation was sent.");
132
+ }
133
+ // Reuse the platform replay guard; these tools are not registered capabilities.
134
+ const claim = WRITE_TOOLS.has(name)
135
+ ? {
136
+ appId: "assistant",
137
+ capability: `tool:${name}`,
138
+ principal: `user:${actor.user.id}`,
139
+ keyHash: capabilityIdempotencyKeyHash(`${context.conversationId}:${context.callId}`),
140
+ }
141
+ : null;
142
+ if (claim) {
143
+ if (!context.callId) throw new Error("A write requires a tool call ID.");
144
+ const state = await claimCapabilityIdempotency(claim, capabilityRequestHash(input));
145
+ if (state.state === "replay")
146
+ throw new Error("This write already completed. Use code_list or code_read to inspect current state; do not repeat the write.");
147
+ if (state.state !== "claimed")
148
+ throw new Error(`Write outcome ${state.state}; inspect current state before attempting another write.`);
149
+ }
150
+ try {
151
+ const response = await send();
152
+ if (claim) await completeCapabilityClaim(claim, response.status, response.data);
153
+ return response.data;
154
+ } catch (error) {
155
+ if (claim) await markCapabilityClaimUncertain(claim);
156
+ throw error;
157
+ }
158
+ });
159
+ }
160
+
161
+ export const createCodeSourceTools = () => [
162
+ createCodeSourceTool("code_files"),
163
+ createCodeSourceTool("code_file_stat"),
164
+ createCodeSourceTool("code_file_copy"),
165
+ createCodeSourceTool("code_database_export"),
166
+ createCodeSourceTool("code_manage_read"),
167
+ createCodeSourceTool("code_delete"),
168
+ createCodeSourceTool("code_database_read"),
169
+ createCodeSourceTool("code_database_clear"),
170
+ createCodeSourceTool("code_database_reset"),
171
+ createCodeSourceTool("code_storage_list"),
172
+ createCodeSourceTool("code_storage_delete"),
173
+ createCodeSourceTool("code_access_read"),
174
+ createCodeSourceTool("code_access_change"),
175
+ createCodeSourceTool("code_actions"),
176
+ createCodeSourceTool("code_sql"),
177
+ createCodeSourceTool("code_versions"),
178
+ createCodeSourceTool("code_list"),
179
+ createCodeSourceTool("code_read"),
180
+ createCodeSourceTool("code_history"),
181
+ createCodeSourceTool("code_fork"),
182
+ createCodeSourceTool("code_publish"),
183
+ createCodeSourceTool("code_restore"),
184
+ createCodeSourceTool("code_update"),
185
+ createCodeSourceTool("code_create"),
186
+ createCodeSourceTool("code_write"),
187
+ createCodeSourceTool("code_remove"),
188
+ ];