@k2b/cloud 0.7.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 (182) hide show
  1. package/package.json +5 -4
  2. package/scripts/README.md +10 -0
  3. package/scripts/build.ts +6 -1
  4. package/scripts/preload.ts +7 -0
  5. package/scripts/sync-recovery-smoke.ts +21 -6
  6. package/src/_internal/build-metadata.ts +8 -1
  7. package/src/_internal/capabilities.ts +35 -12
  8. package/src/_internal/define-app.ts +28 -11
  9. package/src/_internal/page-responses.ts +1 -1
  10. package/src/_internal/readiness.ts +4 -1
  11. package/src/_internal/runtime-context.ts +1 -1
  12. package/src/access/PermissionEditor.tsx +15 -9
  13. package/src/access/PrincipalPicker.tsx +9 -3
  14. package/src/access/ResourceApiKeys.tsx +1 -1
  15. package/src/access/ui.ts +1 -2
  16. package/src/ai/admin.ts +2 -2
  17. package/src/ai/assistant-models.ts +1 -1
  18. package/src/ai/audio-tool.ts +1 -1
  19. package/src/ai/browser-code-contracts.ts +174 -52
  20. package/src/ai/browser.ts +4 -2
  21. package/src/ai/capabilities.ts +3 -3
  22. package/src/ai/chat/blocks.tsx +127 -50
  23. package/src/ai/chat/builtin-tools.tsx +2 -4
  24. package/src/ai/chat/capability-table.tsx +1 -1
  25. package/src/ai/chat/composer-adapter.ts +48 -13
  26. package/src/ai/chat/message-utils.ts +18 -5
  27. package/src/ai/chat/presentation.tsx +24 -11
  28. package/src/ai/chat/tool-disclosure.tsx +6 -2
  29. package/src/ai/chat/tool-groups.ts +8 -2
  30. package/src/ai/chat-quotas.ts +2 -2
  31. package/src/ai/chat-task-contracts.ts +9 -13
  32. package/src/ai/chat-tasks.ts +39 -18
  33. package/src/ai/client/controller.ts +87 -38
  34. package/src/ai/client/projection.ts +13 -6
  35. package/src/ai/client/transport.ts +4 -2
  36. package/src/ai/code-capability-routes.ts +22 -27
  37. package/src/ai/code-capability-transport.ts +2 -1
  38. package/src/ai/code-execution.ts +53 -0
  39. package/src/ai/code-mode-skill.ts +9 -5
  40. package/src/ai/code-runtime-tools.ts +16 -3
  41. package/src/ai/code-source-contracts.ts +100 -32
  42. package/src/ai/code-source-tools.ts +49 -6
  43. package/src/ai/compaction.ts +3 -3
  44. package/src/ai/default-tools.ts +87 -14
  45. package/src/ai/dictation-runtime.ts +1 -1
  46. package/src/ai/draft-content.ts +27 -12
  47. package/src/ai/enrich.ts +1 -1
  48. package/src/ai/executor.ts +8 -12
  49. package/src/ai/fetch-file-tool.ts +2 -2
  50. package/src/ai/file-content-version.ts +6 -1
  51. package/src/ai/file-tools.ts +8 -2
  52. package/src/ai/files-store.ts +55 -17
  53. package/src/ai/grids-skill.ts +2 -0
  54. package/src/ai/http.ts +18 -7
  55. package/src/ai/index.ts +24 -25
  56. package/src/ai/inference-calls.ts +3 -4
  57. package/src/ai/live.ts +1 -1
  58. package/src/ai/markdown-pdf-tool.ts +2 -2
  59. package/src/ai/memories.ts +30 -27
  60. package/src/ai/memory-learning-runs.ts +1 -1
  61. package/src/ai/memory-learning.ts +5 -5
  62. package/src/ai/memory-tool.ts +1 -5
  63. package/src/ai/memory-workflow-evidence.ts +9 -4
  64. package/src/ai/message-queue.ts +23 -12
  65. package/src/ai/migrate.ts +6 -4
  66. package/src/ai/model-pricing.ts +4 -4
  67. package/src/ai/pdf-render-worker.ts +3 -2
  68. package/src/ai/projects-routes.ts +15 -40
  69. package/src/ai/projects.ts +18 -13
  70. package/src/ai/quota-provider.ts +6 -5
  71. package/src/ai/quota-report.ts +5 -5
  72. package/src/ai/quotas.ts +3 -3
  73. package/src/ai/routes.ts +60 -34
  74. package/src/ai/run-timeout.ts +3 -1
  75. package/src/ai/runtime-tools.ts +6 -7
  76. package/src/ai/runtime.ts +38 -20
  77. package/src/ai/settings.ts +1 -1
  78. package/src/ai/short-id.ts +4 -5
  79. package/src/ai/skill-search.ts +21 -10
  80. package/src/ai/skill-seeds.ts +16 -7
  81. package/src/ai/skill-tool.ts +6 -1
  82. package/src/ai/skills-routes.ts +32 -15
  83. package/src/ai/skills.ts +54 -28
  84. package/src/ai/store.ts +80 -47
  85. package/src/ai/structured.ts +6 -2
  86. package/src/ai/system-prompt.ts +7 -5
  87. package/src/ai/task-contracts.ts +84 -0
  88. package/src/ai/task-execution.ts +92 -0
  89. package/src/ai/todo-contracts.ts +24 -12
  90. package/src/ai/todo-tool.ts +16 -13
  91. package/src/ai/tools.ts +1 -1
  92. package/src/ai/transcription.ts +4 -4
  93. package/src/ai/turn-timing.ts +1 -1
  94. package/src/ai/ui.tsx +2 -1
  95. package/src/ai/usage.ts +1 -1
  96. package/src/ai/vision-tool.ts +38 -17
  97. package/src/api/admin-ai-quotas.ts +14 -8
  98. package/src/api/admin-ai-skills.ts +3 -3
  99. package/src/api/admin-linux-identities.ts +10 -4
  100. package/src/api/admin-rail.ts +2 -2
  101. package/src/api/app-approval.ts +2 -2
  102. package/src/api/auth.ts +1 -1
  103. package/src/api/capabilities.ts +45 -13
  104. package/src/api/index.ts +5 -5
  105. package/src/api/mcp.ts +5 -12
  106. package/src/browser/CloudResourceSearch.tsx +1 -1
  107. package/src/browser/command-bridge.ts +1 -1
  108. package/src/browser/command-shortcuts.ts +1 -1
  109. package/src/browser/commands.ts +8 -7
  110. package/src/browser/testing.ts +2 -1
  111. package/src/capabilities/client.ts +1 -1
  112. package/src/capabilities/command-link.ts +1 -1
  113. package/src/capabilities/server.ts +2 -1
  114. package/src/cli/admin/ai-quotas.ts +5 -5
  115. package/src/cli/admin/app-credentials.ts +7 -9
  116. package/src/cli/admin/index.ts +3 -3
  117. package/src/cli/admin/instance.ts +2 -2
  118. package/src/cli/admin/linux.ts +4 -2
  119. package/src/cli/admin/sync.ts +1 -1
  120. package/src/cli/capabilities.ts +42 -29
  121. package/src/config/define-env.ts +97 -0
  122. package/src/config/env.ts +147 -54
  123. package/src/config/index.ts +3 -1
  124. package/src/contracts/capabilities.ts +8 -1
  125. package/src/contracts/index.ts +5 -7
  126. package/src/contracts/registry.ts +2 -0
  127. package/src/contracts/shared.ts +12 -3
  128. package/src/desktop/index.ts +1 -0
  129. package/src/desktop/solid.tsx +1 -1
  130. package/src/index.ts +8 -2
  131. package/src/server/index.ts +2 -2
  132. package/src/server/ratelimit.ts +1 -1
  133. package/src/server/services/access-revision.ts +3 -3
  134. package/src/server/services/access.ts +3 -1
  135. package/src/services/accounts/notification-sender.ts +12 -2
  136. package/src/services/announcements/index.ts +4 -4
  137. package/src/services/app-approval.ts +11 -10
  138. package/src/services/identity/key-config.ts +6 -4
  139. package/src/services/identity/runtime-config.ts +6 -5
  140. package/src/services/ipa/posix.ts +1 -1
  141. package/src/services/ipa/sync.ts +1 -1
  142. package/src/services/logging/index.ts +1 -1
  143. package/src/services/logging/trace.ts +1 -1
  144. package/src/services/mandates/index.ts +5 -2
  145. package/src/services/mandates/policy.ts +106 -6
  146. package/src/services/notifications/batches.ts +1 -0
  147. package/src/services/pdf/gotenberg.ts +50 -14
  148. package/src/services/pdf/index.ts +11 -11
  149. package/src/services/public-http.ts +1 -1
  150. package/src/services/rail-shortcuts.ts +1 -1
  151. package/src/services/rail-snapshot.ts +1 -1
  152. package/src/services/request-cache-redis.ts +2 -1
  153. package/src/services/session/user.ts +1 -1
  154. package/src/services/settings/app.ts +5 -1
  155. package/src/services/settings/core-settings.ts +6 -8
  156. package/src/services/settings/index.ts +3 -3
  157. package/src/services/settings/store.ts +3 -3
  158. package/src/services/webauthn.ts +1 -1
  159. package/src/shared/index.ts +3 -6
  160. package/src/shared/markdown/extensions/info-blocks.ts +1 -1
  161. package/src/ssr/AdminSidebar.tsx +2 -2
  162. package/src/ssr/AppLaunchpad.island.tsx +12 -2
  163. package/src/ssr/AppLaunchpadPanel.tsx +4 -4
  164. package/src/ssr/GlobalSearchDialog.tsx +6 -6
  165. package/src/ssr/GlobalSearchTrigger.island.tsx +2 -2
  166. package/src/ssr/Layout.tsx +2 -2
  167. package/src/ssr/LayoutHeader.tsx +1 -1
  168. package/src/ssr/LayoutHelp.tsx +4 -6
  169. package/src/ssr/LayoutHelpTrigger.island.tsx +2 -2
  170. package/src/ssr/MinimalLayout.tsx +1 -1
  171. package/src/ssr/MobileNavigation.tsx +2 -2
  172. package/src/ssr/ProfilePreferences.island.tsx +1 -1
  173. package/src/ssr/admin-navigation.ts +6 -2
  174. package/src/ssr/islands/index.ts +2 -2
  175. package/src/ssr/mobile-menu-history.ts +1 -1
  176. package/src/workflows/ai/index.ts +2 -2
  177. package/src/workflows/ai/runtime.ts +16 -91
  178. package/src/workflows/ai/store.ts +1 -1
  179. package/src/workflows/ai/types.ts +6 -82
  180. package/src/workflows/store/index.ts +2 -2
  181. package/src/server/services/freeipa/test-certificates.ts +0 -44
  182. package/src/services/session/test-fixture.ts +0 -18
@@ -1,54 +1,166 @@
1
1
  import { z } from "zod";
2
2
 
3
- export const CodeResourceId = z.string().regex(/^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz]{6}$/).describe("Public resource short ID returned by code_create or code_list.");
3
+ export const CodeResourceId = z
4
+ .string()
5
+ .regex(/^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz]{6}$/)
6
+ .describe("Public resource short ID returned by code_create or code_list.");
4
7
  const id = CodeResourceId;
5
8
  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();
9
+ export const CODE_RUNTIME_TOOL_NAMES = [
10
+ "code_run",
11
+ "code_action",
12
+ "code_inspect",
13
+ "code_interact",
14
+ "code_stop",
15
+ "code_open",
16
+ "code_export",
17
+ "code_present",
18
+ "code_secret",
19
+ ] as const;
20
+ export const CodeRunInput = z
21
+ .object({
22
+ id: id.optional(),
23
+ resourceId: id
24
+ .optional()
25
+ .describe(
26
+ "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.",
27
+ ),
28
+ version: z
29
+ .number()
30
+ .int()
31
+ .positive()
32
+ .optional()
33
+ .describe("Published version to run; resource admins only. Omit for the current accessible source."),
34
+ code: z
35
+ .string()
36
+ .min(1)
37
+ .max(1024 * 1024)
38
+ .optional()
39
+ .describe("One-off JavaScript/TypeScript entry exporting one function. Use code OR a saved resource id; no title or icon needed."),
40
+ inputPaths: z
41
+ .array(z.string().min(1).max(500))
42
+ .max(64)
43
+ .default([])
44
+ .describe(
45
+ "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.",
46
+ ),
47
+ })
48
+ .strict()
49
+ .refine((input) => Number(input.id !== undefined) + Number(input.code !== undefined) === 1, "Provide exactly one of id or code")
50
+ .refine((input) => input.version === undefined || input.id !== undefined, "A published version requires a saved resource id")
51
+ .refine((input) => input.resourceId === undefined || input.code !== undefined, "resourceId requires one-off code");
52
+ export const CodeActionInput = z
53
+ .object({
54
+ id,
55
+ action: z
56
+ .string()
57
+ .regex(/^[a-z][a-zA-Z0-9_]*$/)
58
+ .max(80)
59
+ .describe("Exact name returned by code_actions."),
60
+ publishedVersion: z
61
+ .number()
62
+ .int()
63
+ .positive()
64
+ .optional()
65
+ .describe(
66
+ "Exact publication returned by code_actions; a changed publication requires fresh discovery. Supply this OR draft revision.",
67
+ ),
68
+ revision: z
69
+ .number()
70
+ .int()
71
+ .positive()
72
+ .optional()
73
+ .describe("Exact draft revision returned by code_actions({draft:true}); Manage required. Supply this OR publishedVersion."),
74
+ input: z.json().describe("Action input matching the discovered inputSchema."),
75
+ })
76
+ .strict()
77
+ .refine(
78
+ (input) => Number(input.publishedVersion !== undefined) + Number(input.revision !== undefined) === 1,
79
+ "Supply publishedVersion OR draft revision",
80
+ );
81
+ export const CodeInspectInput = z
82
+ .object({
83
+ runId,
84
+ waitMs: z
85
+ .number()
86
+ .int()
87
+ .min(0)
88
+ .max(30000)
89
+ .default(0)
90
+ .describe("Wait up to this duration for background work to finish before returning its real state; never restarts work."),
91
+ nodeId: z.string().min(1).max(80).optional(),
92
+ offset: z.number().int().min(0).default(0),
93
+ limit: z.number().int().min(1).max(100).default(20),
94
+ })
95
+ .strict();
96
+ const CodeInteraction = z
97
+ .object({
98
+ id: z.string().min(1).max(80).describe("Control ID or pending modal ID returned by the snapshot."),
99
+ event: z
100
+ .discriminatedUnion("type", [
101
+ z
102
+ .object({
103
+ type: z.literal("change"),
104
+ value: z.union([
105
+ z.string(),
106
+ z.number().finite(),
107
+ z.boolean(),
108
+ z.null(),
109
+ z.array(z.string()),
110
+ z.object({ start: z.iso.date().nullable(), end: z.iso.date().nullable() }).strict(),
111
+ ]),
112
+ })
113
+ .strict(),
114
+ z.object({ type: z.literal("select"), key: z.string().nullable() }).strict(),
115
+ z.object({ type: z.literal("view"), value: z.enum(["chart", "table"]) }).strict(),
116
+ z.object({ type: z.literal("refresh") }).strict(),
117
+ z.object({ type: z.literal("request"), request: z.record(z.string(), z.json()) }).strict(),
118
+ ])
119
+ .optional()
120
+ .describe(
121
+ "An event object, never a JSON-encoded string. Omit to activate a button. Example: {type:'change',value:['Sued']} or {type:'view',value:'table'}.",
122
+ ),
123
+ answer: z.json().optional().describe("Only for the pending modal ID: the modal answer, or null to cancel. Do not use for controls."),
124
+ })
125
+ .strict();
33
126
  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.");
127
+ export const CodeInteractInput = CodeInteraction.partial({ id: true })
128
+ .extend({
129
+ runId,
130
+ // Three normal 15-second callback budgets share the existing 45-second call budget.
131
+ steps: z
132
+ .array(CodeInteraction.refine(validInteraction, "Supply event OR answer"))
133
+ .min(1)
134
+ .max(3)
135
+ .optional()
136
+ .describe(
137
+ "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.",
138
+ ),
139
+ })
140
+ .strict()
141
+ .refine(
142
+ (input) =>
143
+ input.steps
144
+ ? input.id === undefined && input.event === undefined && input.answer === undefined
145
+ : input.id !== undefined && (input.event === undefined || input.answer === undefined),
146
+ "Supply steps OR one id with event/answer.",
147
+ );
40
148
  export const CodeStopInput = z.object({ runId }).strict();
41
149
  export const CodeOpenInput = z.object({ id }).strict();
42
150
  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();
151
+ export const CodeExportInput = z
152
+ .object({ runId, name: z.string().min(1).max(180).describe("Captured output filename returned by the snapshot.") })
153
+ .strict();
44
154
 
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();
155
+ export const CodeSecretInput = z
156
+ .object({
157
+ resourceId: id.optional().describe("Scope to this app/script; omit to configure the current chat only."),
158
+ name: z.string().regex(/^[a-zA-Z][a-zA-Z0-9_-]{0,79}$/),
159
+ origin: z.string().url().max(2000).describe("Exact HTTPS origin allowed to receive the secret."),
160
+ header: z.string().min(1).max(80).default("authorization"),
161
+ prefix: z.string().max(160).default("Bearer "),
162
+ })
163
+ .strict();
52
164
 
53
165
  // Internal bridge envelope. Each model-facing tool receives only its own flat schema.
54
166
  export const CodeRuntimeInput = z.discriminatedUnion("operation", [
@@ -65,15 +177,25 @@ export const CodeRuntimeInput = z.discriminatedUnion("operation", [
65
177
  export type CodeRuntimeInput = z.infer<typeof CodeRuntimeInput>;
66
178
  export function parseCodeToolInput(name: string, args: unknown): CodeRuntimeInput {
67
179
  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");
180
+ case "code_secret":
181
+ return { operation: "secret", ...CodeSecretInput.parse(args) };
182
+ case "code_action":
183
+ return { operation: "action", ...CodeActionInput.parse(args) };
184
+ case "code_run":
185
+ return { operation: "run", ...CodeRunInput.parse(args) };
186
+ case "code_inspect":
187
+ return { operation: "inspect", ...CodeInspectInput.parse(args) };
188
+ case "code_interact":
189
+ return { operation: "interact", ...CodeInteractInput.parse(args) };
190
+ case "code_stop":
191
+ return { operation: "stop", ...CodeStopInput.parse(args) };
192
+ case "code_open":
193
+ return { operation: "open", ...CodeOpenInput.parse(args) };
194
+ case "code_present":
195
+ return { operation: "present", ...CodePresentInput.parse(args) };
196
+ case "code_export":
197
+ return { operation: "export", ...CodeExportInput.parse(args) };
198
+ default:
199
+ throw new Error("Unknown code tool");
78
200
  }
79
201
  }
package/src/ai/browser.ts CHANGED
@@ -1,5 +1,7 @@
1
- export { parseAiTodoPlan, type AiTodoPlan } from "./todo-contracts";
2
- export { CodeResourceId, CodeActionInput, CODE_RUNTIME_TOOL_NAMES, CodeRuntimeInput, parseCodeToolInput } from "./browser-code-contracts";
1
+ export { CODE_RUNTIME_TOOL_NAMES, CodeActionInput, CodeResourceId, CodeRuntimeInput, parseCodeToolInput } from "./browser-code-contracts";
2
+ export { type AiTaskRequest, type AiTaskRequestInput, AiTaskRequestSchema } from "./task-contracts";
3
+ export { type AiTodoPlan, parseAiTodoPlan } from "./todo-contracts";
4
+
3
5
  import { AI_TURN_ATTACHMENT_MAX_ITEMS } from "./limits";
4
6
  import type { AiConversation, AiDraftContentPart } from "./types";
5
7
 
@@ -1,6 +1,3 @@
1
- import { createHelpReader, type HelpReaderFactory } from "../services/help";
2
- import { createCodeSourceTool } from "./code-source-tools";
3
- import { CODE_SOURCE_TOOLS } from "./code-source-contracts";
4
1
  import { createHash } from "node:crypto";
5
2
  import type { Tool, ToolContext, ToolResolver } from "@k2b/nessi";
6
3
  import { z } from "zod";
@@ -15,7 +12,10 @@ import {
15
12
  } from "../contracts/capabilities";
16
13
  import type { CapabilityRegistryEntry } from "../contracts/registry";
17
14
  import type { RequestActor } from "../server";
15
+ import { createHelpReader, type HelpReaderFactory } from "../services/help";
18
16
  import { recordRejectedAiCapability } from "./capability-execution";
17
+ import { CODE_SOURCE_TOOLS } from "./code-source-contracts";
18
+ import { createCodeSourceTool } from "./code-source-tools";
19
19
  import { CLOUD_AI_DEFERRED_BUILTIN_TOOL_NAMES } from "./default-tools";
20
20
  import { type AiToolPreparationContext, defineAiTool, type PreparedAiTools, prepareAiTools } from "./tools";
21
21
  import type { AiConversationService, AiRuntimeTool, AiToolPresentation } from "./types";
@@ -1,14 +1,13 @@
1
- import { hasCapabilityTable } from "./capability-result";
2
1
  import { dates } from "@k2b/stdlib";
3
2
  import { mutation } from "@k2b/stdlib/solid";
4
- import { Button, ButtonLink, Chat, MarkdownView, isStructuredDataValue, SplitButton, StructuredDataPreview, useLocale } from "@k2b/ui";
3
+ import { Button, ButtonLink, Chat, isStructuredDataValue, MarkdownView, SplitButton, StructuredDataPreview, useLocale } from "@k2b/ui";
5
4
  import { createMemo, createSignal, For, type JSX, Match, Show, Switch } from "solid-js";
6
5
  import type { CapabilityActionReview } from "../../contracts/capabilities";
7
6
  import { markdown } from "../../shared";
8
- import { groupToolBlocks, isFailedTool, summarizeToolGroup } from "./tool-groups";
9
7
  import type { AiTurnBlock } from "../protocol";
10
8
  import { isRenderableTurnBlock } from "../protocol";
11
9
  import { hasSpecializedBuiltinToolView, SpecializedBuiltinToolBlock } from "./builtin-tools";
10
+ import { hasCapabilityTable } from "./capability-result";
12
11
  import { CapabilityTablePreview } from "./capability-table";
13
12
  import { PresentToolBlock } from "./file-tools";
14
13
  import { useAiChatActions } from "./message-actions";
@@ -27,6 +26,7 @@ import {
27
26
  } from "./message-utils";
28
27
  import { AssistantMarkdownBlock } from "./primitives";
29
28
  import { AiToolActivity, AiToolDisclosureProvider, type AiToolDisclosureState, createAiToolDisclosureState } from "./tool-disclosure";
29
+ import { groupToolBlocks, isFailedTool, summarizeToolGroup } from "./tool-groups";
30
30
  import { CloudCardBlock, CloudSurveyBlock, CloudSurveyResultBlock, CloudTextEditorBlock, CloudTextEditorResultBlock } from "./visual-tools";
31
31
  import { FetchFileToolBlock, WebExtractToolBlock, WebSearchToolBlock } from "./web-tools";
32
32
 
@@ -655,7 +655,9 @@ function ToolBlockView(props: { turnId: string; block: ToolBlock; active?: boole
655
655
  <Match when={props.block.presentation?.kind === "capability"}>
656
656
  <CapabilityToolView block={props.block} />
657
657
  </Match>
658
- <Match when={actions.renderCodePresentation && props.block.name === "code_present" && status() === "completed" && !props.block.isError}>
658
+ <Match
659
+ when={actions.renderCodePresentation && props.block.name === "code_present" && status() === "completed" && !props.block.isError}
660
+ >
659
661
  {actions.renderCodePresentation?.(props.block.result)}
660
662
  </Match>
661
663
  <Match when={props.block.name === "present" && !props.block.isError}>
@@ -673,7 +675,9 @@ function ToolBlockView(props: { turnId: string; block: ToolBlock; active?: boole
673
675
  <Match when={props.block.name === "memory"}>
674
676
  <MemoryToolView block={props.block} />
675
677
  </Match>
676
- <Match when={hasSpecializedBuiltinToolView(props.block.name, props.block.result) && status() === "completed" && !props.block.isError}>
678
+ <Match
679
+ when={hasSpecializedBuiltinToolView(props.block.name, props.block.result) && status() === "completed" && !props.block.isError}
680
+ >
677
681
  <SpecializedBuiltinToolBlock block={props.block} />
678
682
  </Match>
679
683
  <Match when={isCardToolName(props.block.name) && !props.block.isError}>
@@ -702,20 +706,18 @@ export function AiTurnBlockView(props: { block: AiTurnBlock; turnId: string; str
702
706
  return (
703
707
  <Switch>
704
708
  <Match when={props.block.kind === "text" && props.block}>
705
- {block => <AssistantMarkdownBlock html={markdown.renderSync(block().text)} />}
709
+ {(block) => <AssistantMarkdownBlock html={markdown.renderSync(block().text)} />}
706
710
  </Match>
707
711
  <Match when={props.block.kind === "thinking" && props.block}>
708
- {block => <ThinkingBlockView text={block().text} streaming={props.streaming} />}
712
+ {(block) => <ThinkingBlockView text={block().text} streaming={props.streaming} />}
709
713
  </Match>
710
714
  <Match when={props.block.kind === "steer_applied"}>
711
715
  <Chat.Activity label="Conversation steered" icon="ti ti-route" tone="ai" />
712
716
  </Match>
713
717
  <Match when={props.block.kind === "tool" && props.block}>
714
- {block => <ToolBlockView turnId={props.turnId} block={block()} active={props.active} />}
715
- </Match>
716
- <Match when={props.block.kind === "compaction" && props.block}>
717
- {block => <CompactionBlockView block={block()} />}
718
+ {(block) => <ToolBlockView turnId={props.turnId} block={block()} active={props.active} />}
718
719
  </Match>
720
+ <Match when={props.block.kind === "compaction" && props.block}>{(block) => <CompactionBlockView block={block()} />}</Match>
719
721
  </Switch>
720
722
  );
721
723
  }
@@ -723,18 +725,37 @@ export function AiTurnBlockView(props: { block: AiTurnBlock; turnId: string; str
723
725
  function CompactToolRow(props: { block: ToolBlock; busy?: boolean }) {
724
726
  const locale = useLocale();
725
727
  const de = () => locale().startsWith("de");
726
- const args = () => isRecord(props.block.args) ? props.block.args : {};
728
+ const args = () => (isRecord(props.block.args) ? props.block.args : {});
727
729
  const detail = () => [args().path, args().name, args().query].find((value) => typeof value === "string");
728
730
  const waiting = () => props.block.status === "awaiting_client";
729
- return <AiToolActivity blockId={props.block.id} label={props.block.presentation?.title ?? displayToolName(props.block.name)}
730
- description={[props.block.status === "running" ? props.block.progress ?? "" : "", typeof detail() === "string" ? String(detail()) : "", waiting() ? (de() ? "Wartet" : "Waiting") : isFailedTool(props.block) ? (de() ? "Fehlgeschlagen" : "Failed") : ""].filter(Boolean).join(" · ")}
731
- icon={waiting() ? "ti ti-clock" : aiToolIcon(props.block.name, props.block.presentation?.appIcon)}
732
- tone={isFailedTool(props.block) ? "danger" : "neutral"} busy={props.busy && !waiting()} bodyInset={false} renderBody={() =>
733
- <div class="max-h-72 overflow-auto overscroll-contain rounded-md bg-zinc-100 p-3 dark:bg-zinc-950" tabIndex={0} role="region" aria-label={de() ? "Werkzeug-Eingabe und Ausgabe" : "Tool input and output"}>
734
- <ToolDetail title={de() ? "Eingabe" : "Input"} toolName={props.block.name} value={props.block.args} />
735
- <ToolDetail title={de() ? "Ausgabe" : "Output"} toolName={props.block.name} value={props.block.result} />
736
- </div>}
737
- />;
731
+ return (
732
+ <AiToolActivity
733
+ blockId={props.block.id}
734
+ label={props.block.presentation?.title ?? displayToolName(props.block.name)}
735
+ description={[
736
+ props.block.status === "running" ? (props.block.progress ?? "") : "",
737
+ typeof detail() === "string" ? String(detail()) : "",
738
+ waiting() ? (de() ? "Wartet" : "Waiting") : isFailedTool(props.block) ? (de() ? "Fehlgeschlagen" : "Failed") : "",
739
+ ]
740
+ .filter(Boolean)
741
+ .join(" · ")}
742
+ icon={waiting() ? "ti ti-clock" : aiToolIcon(props.block.name, props.block.presentation?.appIcon)}
743
+ tone={isFailedTool(props.block) ? "danger" : "neutral"}
744
+ busy={props.busy && !waiting()}
745
+ bodyInset={false}
746
+ renderBody={() => (
747
+ <div
748
+ class="max-h-72 overflow-auto overscroll-contain rounded-md bg-zinc-100 p-3 dark:bg-zinc-950"
749
+ tabIndex={0}
750
+ role="region"
751
+ aria-label={de() ? "Werkzeug-Eingabe und Ausgabe" : "Tool input and output"}
752
+ >
753
+ <ToolDetail title={de() ? "Eingabe" : "Input"} toolName={props.block.name} value={props.block.args} />
754
+ <ToolDetail title={de() ? "Ausgabe" : "Output"} toolName={props.block.name} value={props.block.result} />
755
+ </div>
756
+ )}
757
+ />
758
+ );
738
759
  }
739
760
 
740
761
  function ToolGroupView(props: { blocks: ToolBlock[]; active: boolean }) {
@@ -744,20 +765,51 @@ function ToolGroupView(props: { blocks: ToolBlock[]; active: boolean }) {
744
765
  const multiple = () => props.blocks.length > 1;
745
766
  const detail = () => {
746
767
  const args = last().args;
747
- return isRecord(args) ? [args.path,args.name,args.query].find(value=>typeof value==="string") : undefined;
768
+ return isRecord(args) ? [args.path, args.name, args.query].find((value) => typeof value === "string") : undefined;
748
769
  };
749
- return <AiToolActivity blockId={`group:${props.blocks[0]!.id}`} bodyInset={false}
750
- label={props.active || !multiple() ? (last().presentation?.title ?? displayToolName(last().name)) : summarizeToolGroup(props.blocks, locale())}
751
- description={waiting() ? (locale().startsWith("de") ? "Wartet" : "Waiting") : last().status === "running" ? last().progress : !multiple() && typeof detail()==="string" ? String(detail()) : undefined}
752
- icon={props.active || !multiple() ? (waiting() ? "ti ti-clock" : aiToolIcon(last().name)) : "ti ti-stack"}
753
- tone={!multiple() && isFailedTool(last()) ? "danger" : "neutral"}
754
- busy={props.active && !waiting()} renderBody={() => <Show when={multiple()} fallback={
755
- <div class="max-h-72 overflow-auto overscroll-contain rounded-md bg-zinc-100 p-3 dark:bg-zinc-950" tabIndex={0} role="region" aria-label={locale().startsWith("de") ? "Werkzeug-Eingabe und Ausgabe" : "Tool input and output"}>
756
- <ToolDetail title={locale().startsWith("de") ? "Eingabe" : "Input"} toolName={last().name} value={last().args} />
757
- <ToolDetail title={locale().startsWith("de") ? "Ausgabe" : "Output"} toolName={last().name} value={last().result} />
758
- </div>
759
- }><For each={props.blocks}>{(block) => <CompactToolRow block={block} />}</For></Show>}
760
- />;
770
+ return (
771
+ <AiToolActivity
772
+ blockId={`group:${props.blocks[0]!.id}`}
773
+ bodyInset={false}
774
+ label={
775
+ props.active || !multiple()
776
+ ? (last().presentation?.title ?? displayToolName(last().name))
777
+ : summarizeToolGroup(props.blocks, locale())
778
+ }
779
+ description={
780
+ waiting()
781
+ ? locale().startsWith("de")
782
+ ? "Wartet"
783
+ : "Waiting"
784
+ : last().status === "running"
785
+ ? last().progress
786
+ : !multiple() && typeof detail() === "string"
787
+ ? String(detail())
788
+ : undefined
789
+ }
790
+ icon={props.active || !multiple() ? (waiting() ? "ti ti-clock" : aiToolIcon(last().name)) : "ti ti-stack"}
791
+ tone={!multiple() && isFailedTool(last()) ? "danger" : "neutral"}
792
+ busy={props.active && !waiting()}
793
+ renderBody={() => (
794
+ <Show
795
+ when={multiple()}
796
+ fallback={
797
+ <div
798
+ class="max-h-72 overflow-auto overscroll-contain rounded-md bg-zinc-100 p-3 dark:bg-zinc-950"
799
+ tabIndex={0}
800
+ role="region"
801
+ aria-label={locale().startsWith("de") ? "Werkzeug-Eingabe und Ausgabe" : "Tool input and output"}
802
+ >
803
+ <ToolDetail title={locale().startsWith("de") ? "Eingabe" : "Input"} toolName={last().name} value={last().args} />
804
+ <ToolDetail title={locale().startsWith("de") ? "Ausgabe" : "Output"} toolName={last().name} value={last().result} />
805
+ </div>
806
+ }
807
+ >
808
+ <For each={props.blocks}>{(block) => <CompactToolRow block={block} />}</For>
809
+ </Show>
810
+ )}
811
+ />
812
+ );
761
813
  }
762
814
 
763
815
  export function AiTurnBlockList(props: {
@@ -769,24 +821,49 @@ export function AiTurnBlockList(props: {
769
821
  disclosureState?: AiToolDisclosureState;
770
822
  }) {
771
823
  const groups = createMemo(() => groupToolBlocks(props.blocks.filter(isRenderableTurnBlock)));
772
- const keyedGroups = createMemo(() => new Map(groups().map(group => [group.kind === "tools" ? `tools:${group.blocks[0]!.id}` : `block:${group.block.id}`, group])));
824
+ const keyedGroups = createMemo(
825
+ () => new Map(groups().map((group) => [group.kind === "tools" ? `tools:${group.blocks[0]!.id}` : `block:${group.block.id}`, group])),
826
+ );
773
827
  const keys = createMemo(() => [...keyedGroups().keys()]);
774
828
  const disclosureState = props.disclosureState ?? createAiToolDisclosureState();
775
829
  return (
776
- <Show when={groups().length > 0}><AiToolDisclosureProvider state={disclosureState}>
777
- <div class={`flex flex-col ${props.compact ? "gap-1" : "gap-2"}`}>
778
- <For each={keys()}>{(key, index) => {
779
- const group = () => keyedGroups().get(key)!;
780
- return <Switch>
781
- <Match when={(() => { const value = group(); return value.kind === "tools" ? value : undefined; })()}>
782
- {(tools) => <ToolGroupView blocks={tools().blocks} active={Boolean(props.active && index() === keys().length - 1)} />}
783
- </Match>
784
- <Match when={(() => { const value = group(); return value.kind === "block" ? value : undefined; })()}>
785
- {(item) => <AiTurnBlockView block={item().block} turnId={props.turnId} streaming={props.streaming && index() === keys().length - 1} active={props.active} />}
786
- </Match>
787
- </Switch>;
788
- }}</For>
789
- </div>
790
- </AiToolDisclosureProvider></Show>
830
+ <Show when={groups().length > 0}>
831
+ <AiToolDisclosureProvider state={disclosureState}>
832
+ <div class={`flex flex-col ${props.compact ? "gap-1" : "gap-2"}`}>
833
+ <For each={keys()}>
834
+ {(key, index) => {
835
+ const group = () => keyedGroups().get(key)!;
836
+ return (
837
+ <Switch>
838
+ <Match
839
+ when={(() => {
840
+ const value = group();
841
+ return value.kind === "tools" ? value : undefined;
842
+ })()}
843
+ >
844
+ {(tools) => <ToolGroupView blocks={tools().blocks} active={Boolean(props.active && index() === keys().length - 1)} />}
845
+ </Match>
846
+ <Match
847
+ when={(() => {
848
+ const value = group();
849
+ return value.kind === "block" ? value : undefined;
850
+ })()}
851
+ >
852
+ {(item) => (
853
+ <AiTurnBlockView
854
+ block={item().block}
855
+ turnId={props.turnId}
856
+ streaming={props.streaming && index() === keys().length - 1}
857
+ active={props.active}
858
+ />
859
+ )}
860
+ </Match>
861
+ </Switch>
862
+ );
863
+ }}
864
+ </For>
865
+ </div>
866
+ </AiToolDisclosureProvider>
867
+ </Show>
791
868
  );
792
869
  }
@@ -4,8 +4,8 @@ import { For, type JSX, Show } from "solid-js";
4
4
  import { formatAiFileSize } from "../attachments";
5
5
  import type { AiTurnBlock } from "../protocol";
6
6
  import { aiToolIcon, displayToolName, formatToolDetailText, isRecord } from "./message-utils";
7
- import { AiToolActivity } from "./tool-disclosure";
8
7
  import { aiChatMessages } from "./messages";
8
+ import { AiToolActivity } from "./tool-disclosure";
9
9
 
10
10
  type ToolBlock = Extract<AiTurnBlock, { kind: "tool" }>;
11
11
 
@@ -325,9 +325,7 @@ function ReadFileView(props: { block: ToolBlock }) {
325
325
  const start = number(result().offset);
326
326
  const end = number(result().nextOffset);
327
327
  const t = aiChatMessages(locale());
328
- return end > start
329
- ? t.byteRange({ start: start.toLocaleString(locale()), end: end.toLocaleString(locale()) })
330
- : t.read;
328
+ return end > start ? t.byteRange({ start: start.toLocaleString(locale()), end: end.toLocaleString(locale()) }) : t.read;
331
329
  };
332
330
  return (
333
331
  <CompletedActivity
@@ -1,7 +1,7 @@
1
- import { capabilityTable } from "./capability-result";
2
1
  import { ButtonLink, DataTable, type DataTableColumn, useLocale } from "@k2b/ui";
3
2
  import { createMemo, For, Show } from "solid-js";
4
3
  import { CapabilitySemanticLinkSchema, capabilityDataAtPath } from "../../contracts/capabilities";
4
+ import { capabilityTable } from "./capability-result";
5
5
  import { aiChatMessages } from "./messages";
6
6
 
7
7
  export const capabilityCellText = (value: unknown): string => {