@k2b/cloud 0.30.0 → 0.32.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 (171) hide show
  1. package/package.json +7 -4
  2. package/src/_internal/app-presentation.ts +22 -1
  3. package/src/_internal/capabilities.ts +2 -2
  4. package/src/_internal/define-app.ts +17 -5
  5. package/src/_internal/registry.ts +50 -6
  6. package/src/_internal/widget-declarations.ts +53 -0
  7. package/src/ai/app-skills.ts +1 -7
  8. package/src/ai/approval-routes.ts +40 -4
  9. package/src/ai/approvals.ts +68 -10
  10. package/src/ai/audio-tool.ts +2 -1
  11. package/src/ai/browser.ts +109 -0
  12. package/src/ai/capabilities.ts +6 -2
  13. package/src/ai/chart-block.ts +431 -81
  14. package/src/ai/chat/action-sentences.ts +13 -7
  15. package/src/ai/chat/blocks.tsx +64 -39
  16. package/src/ai/chat/builtin-tools.tsx +9 -7
  17. package/src/ai/chat/chart-block.tsx +218 -44
  18. package/src/ai/chat/explorer-block.tsx +276 -0
  19. package/src/ai/chat/file-tools.tsx +40 -1
  20. package/src/ai/chat/live-turn.browser-harness.tsx +4 -1
  21. package/src/ai/chat/message-actions.tsx +9 -1
  22. package/src/ai/chat/message-utils.ts +7 -0
  23. package/src/ai/chat/messages.ts +34 -0
  24. package/src/ai/chat/presentation.tsx +13 -3
  25. package/src/ai/chat/reference-text.tsx +34 -0
  26. package/src/ai/chat/tool-groups.ts +67 -12
  27. package/src/ai/chat/tool-names.ts +18 -2
  28. package/src/ai/chat/turn-layout.ts +8 -0
  29. package/src/ai/chat/turn-view.tsx +111 -32
  30. package/src/ai/chat/view-block.browser-harness.tsx +35 -0
  31. package/src/ai/chat/view-block.tsx +417 -0
  32. package/src/ai/chat/visual-tools.tsx +32 -53
  33. package/src/ai/chat/web-tools.tsx +95 -1
  34. package/src/ai/client/controller.ts +6 -3
  35. package/src/ai/client-tool-results.ts +9 -0
  36. package/src/ai/code-runtime-tools.ts +50 -17
  37. package/src/ai/code-source-contracts.ts +25 -21
  38. package/src/ai/code-source-tools.ts +3 -1
  39. package/src/ai/default-tool-contracts.ts +334 -91
  40. package/src/ai/default-tools.ts +87 -11
  41. package/src/ai/executor.ts +343 -45
  42. package/src/ai/fetch-file-tool.ts +41 -4
  43. package/src/ai/file-error.ts +2 -0
  44. package/src/ai/file-tools.ts +27 -7
  45. package/src/ai/files-store.ts +60 -0
  46. package/src/ai/firecrawl-tools.ts +87 -4
  47. package/src/ai/html-pdf-tool.ts +3 -2
  48. package/src/ai/http.ts +37 -12
  49. package/src/ai/index.ts +98 -0
  50. package/src/ai/local-tool-contracts.ts +244 -0
  51. package/src/ai/malformed-tool-call-repair.ts +144 -0
  52. package/src/ai/markdown-pdf-tool.ts +2 -1
  53. package/src/ai/migrate.ts +120 -2
  54. package/src/ai/openai-api-messages.ts +53 -0
  55. package/src/ai/openai-api-slots.ts +40 -0
  56. package/src/ai/openai-api.ts +383 -0
  57. package/src/ai/protocol.ts +28 -3
  58. package/src/ai/provider.ts +16 -4
  59. package/src/ai/request-context.ts +33 -0
  60. package/src/ai/routes.ts +99 -49
  61. package/src/ai/runtime.ts +90 -15
  62. package/src/ai/settings.ts +5 -0
  63. package/src/ai/skill-catalog.ts +20 -5
  64. package/src/ai/skill-format.ts +17 -7
  65. package/src/ai/skill-tool.ts +15 -5
  66. package/src/ai/skills-routes.ts +20 -6
  67. package/src/ai/skills.ts +26 -7
  68. package/src/ai/store.ts +91 -1
  69. package/src/ai/system-prompt.ts +69 -10
  70. package/src/ai/timeline.ts +3 -1
  71. package/src/ai/tool-audit.ts +11 -1
  72. package/src/ai/tools.ts +94 -3
  73. package/src/ai/types.ts +48 -0
  74. package/src/ai/view-block.ts +941 -0
  75. package/src/ai/vision-tool.ts +2 -1
  76. package/src/ai/web-provenance.ts +103 -0
  77. package/src/api/admin-identity.ts +6 -1
  78. package/src/api/app-approval-actions.ts +36 -0
  79. package/src/api/app-approval.ts +10 -2
  80. package/src/api/approvals-loader.ts +15 -0
  81. package/src/api/approvals.ts +120 -0
  82. package/src/api/capabilities.ts +3 -2
  83. package/src/api/index.ts +3 -0
  84. package/src/api/me.ts +12 -3
  85. package/src/api/widgets.ts +36 -5
  86. package/src/approvals/Approvals.browser-harness.tsx +16 -0
  87. package/src/approvals/Approvals.tsx +621 -0
  88. package/src/approvals/ApprovalsNavStatus.tsx +20 -0
  89. package/src/approvals/messages.ts +95 -0
  90. package/src/approvals/ui.ts +8 -0
  91. package/src/browser/CloudResourceSearch.tsx +1 -1
  92. package/src/browser/app-approval.ts +32 -0
  93. package/src/browser/choose-files.tsx +26 -13
  94. package/src/browser/command-shortcuts.ts +21 -6
  95. package/src/browser/commands.ts +13 -4
  96. package/src/browser/file-provider-error.ts +11 -0
  97. package/src/browser/file-providers.ts +3 -12
  98. package/src/browser/files.tsx +1 -2
  99. package/src/browser/live-connection.ts +2 -3
  100. package/src/browser/provider-list.ts +2 -1
  101. package/src/browser/save-files.tsx +13 -2
  102. package/src/browser/spotlight-position.ts +4 -1
  103. package/src/browser/widgets.ts +7 -5
  104. package/src/capabilities/executions.ts +3 -0
  105. package/src/cli/account.ts +21 -2
  106. package/src/cli/commands.ts +24 -2
  107. package/src/cli/index.ts +16 -0
  108. package/src/cli/plugin.ts +30 -0
  109. package/src/contracts/app-approval.ts +120 -0
  110. package/src/contracts/app.ts +34 -2
  111. package/src/contracts/approval-paths.ts +4 -0
  112. package/src/contracts/approvals.ts +81 -0
  113. package/src/contracts/command-path.ts +17 -0
  114. package/src/contracts/commands.ts +5 -15
  115. package/src/contracts/registry.ts +9 -1
  116. package/src/contracts/shared.ts +5 -0
  117. package/src/contracts/widgets.ts +47 -2
  118. package/src/desktop/solid.tsx +6 -7
  119. package/src/events/live-protocol.ts +2 -1
  120. package/src/events/live-server-message.ts +40 -0
  121. package/src/events/testing.ts +88 -0
  122. package/src/server/index.ts +3 -0
  123. package/src/server/middleware/auth.ts +11 -1
  124. package/src/server/request-timeout.ts +22 -0
  125. package/src/server/services/access.ts +4 -3
  126. package/src/server/widget-request.ts +27 -0
  127. package/src/services/app-approval.ts +103 -15
  128. package/src/services/approvals/cursor.ts +15 -0
  129. package/src/services/approvals/device.ts +112 -0
  130. package/src/services/approvals/index.ts +241 -0
  131. package/src/services/approvals/list.ts +120 -0
  132. package/src/services/approvals/migrate.ts +41 -0
  133. package/src/services/identity/models-only-auth.ts +25 -0
  134. package/src/services/notifications/email-frame.ts +16 -3
  135. package/src/services/service-account-credentials.ts +22 -10
  136. package/src/services/settings/core-settings.ts +27 -0
  137. package/src/shared/ai-platform-prompt.ts +26 -1
  138. package/src/shared/app-presentation.ts +11 -0
  139. package/src/shared/email-html.ts +46 -7
  140. package/src/shared/index.ts +6 -1
  141. package/src/shared/markdown/extensions/code.ts +14 -0
  142. package/src/shared/markdown/index.ts +4 -4
  143. package/src/ssr/AdminLayout.tsx +4 -7
  144. package/src/ssr/AppBadge.tsx +4 -4
  145. package/src/ssr/AppLaunchpad.island.tsx +1 -1
  146. package/src/ssr/AppLaunchpadPanel.tsx +4 -4
  147. package/src/ssr/Layout.tsx +38 -14
  148. package/src/ssr/LayoutFooter.tsx +1 -1
  149. package/src/ssr/LayoutHeader.tsx +17 -20
  150. package/src/ssr/LayoutHelp.tsx +1 -1
  151. package/src/ssr/LayoutRail.tsx +1 -2
  152. package/src/ssr/PageTrail.tsx +23 -0
  153. package/src/ssr/PageTrailRow.island.tsx +20 -0
  154. package/src/ssr/PageTrailRuntime.island.tsx +26 -0
  155. package/src/ssr/ProfilePreferences.island.tsx +7 -3
  156. package/src/ssr/RailApps.island.tsx +2 -2
  157. package/src/ssr/app-badges.ts +50 -6
  158. package/src/ssr/islands/index.ts +1 -0
  159. package/src/ssr/layout-runtime.ts +60 -0
  160. package/src/ssr/mobile-menu-history.ts +1 -1
  161. package/src/ssr/page-trail-runtime.ts +313 -0
  162. package/src/ssr/page-trail.browser-harness.tsx +265 -0
  163. package/src/ssr/page-trail.ts +59 -0
  164. package/src/ssr/platform-messages.ts +4 -0
  165. package/src/ssr/rail-navigation.ts +5 -3
  166. package/src/ssr/runtime.ts +6 -1
  167. package/src/styles/approvals.css +390 -0
  168. package/src/styles/effects.css +21 -4
  169. package/src/styles/global.css +39 -3
  170. package/src/styles/utilities-layout.css +24 -1
  171. package/src/styles/utilities-navigation.css +6 -18
@@ -0,0 +1,9 @@
1
+ import type { z } from "zod";
2
+ import { createCloudAiLocalBashTool, createCloudAiLocalTools } from "./default-tools";
3
+
4
+ // Two 512 KiB Bash streams, fully JSON-escaped at six bytes per character, plus a 64 KiB envelope.
5
+ export const AI_CLIENT_TOOL_ACTION_MAX_BYTES = 2 * 512 * 1024 * 6 + 64 * 1024;
6
+
7
+ export const aiClientToolOutputSchemas = new Map<string, z.ZodType>(
8
+ [createCloudAiLocalBashTool(), ...createCloudAiLocalTools()].map((tool) => [tool.def.name, tool.def.outputSchema]),
9
+ );
@@ -14,6 +14,7 @@ import { resolveAiCapabilityActor } from "./capability-execution";
14
14
  import { CODE_CAPABILITY_TOKEN_HEADER, codeCapabilityOperation } from "./code-capability-transport";
15
15
  import { authorizeCodeExecution } from "./code-execution";
16
16
  import { aiConversations } from "./store";
17
+ import type { AiApprovalTarget, AiWebsiteReceipt } from "./types";
17
18
 
18
19
  const log = logger("ai:code-runtime");
19
20
  const ISSUANCE_TIMEOUT_MS = 5_000;
@@ -24,7 +25,20 @@ const Reply = z.object({
24
25
  status: z.enum(["running", "busy", "done", "lost"]),
25
26
  phase: z.enum(["starting", "running", "busy", "waiting_for_user"]).optional(),
26
27
  result: z.unknown().optional(),
27
- approvals: z.array(z.object({ id: z.uuid(), message: z.string(), decision: z.boolean().nullable() })),
28
+ approvals: z.array(
29
+ z.object({
30
+ id: z.uuid(),
31
+ approvalId: z.uuid().optional(),
32
+ message: z.string(),
33
+ decision: z.boolean().nullable(),
34
+ /** What the person may remember: the website or Action, derived by the host from the stored request. */
35
+ remember: z.object({ toolName: z.string(), approvalScope: z.string(), always: z.boolean() }).optional(),
36
+ }),
37
+ ),
38
+ /** The next requests a website approval for this chat let through without asking, after `receiptsAfter`. */
39
+ receipts: z.array(z.object({ method: z.string(), url: z.string() })).default([]),
40
+ /** More receipts follow; the answer then leaves out the result until the last one is read. */
41
+ moreReceipts: z.boolean().default(false),
28
42
  }),
29
43
  });
30
44
  type Context = ToolContext & {
@@ -34,6 +48,9 @@ type Context = ToolContext & {
34
48
  locale?: string;
35
49
  timeZone?: string;
36
50
  reportProgress?: (message: string) => Promise<void>;
51
+ /** Relays a decision; the managed host consumes the linked platform approval before execution. */
52
+ requestRelayedApproval?: (message: string, options: { approvalId?: string; target?: AiApprovalTarget }) => Promise<boolean>;
53
+ reportWebsiteReceipts?: (receipts: AiWebsiteReceipt[]) => Promise<void>;
37
54
  };
38
55
 
39
56
  /** The tab only renders progress and approvals; server calls own all execution and resumption. */
@@ -101,7 +118,7 @@ export const runManagedCodeTool =
101
118
  token && token.claims.exp * 1000 - Date.now() >= 10_000 ? token : undefined;
102
119
  let signed: Awaited<ReturnType<typeof signInvocationToken>> | undefined;
103
120
  let callback: Awaited<ReturnType<typeof signInvocationToken>> | undefined;
104
- const request = async (decision?: { id: string; approved: boolean }) => {
121
+ const request = async ({ decision, receiptsAfter }: { decision?: { id: string; approved: boolean }; receiptsAfter: number }) => {
105
122
  context.signal.throwIfAborted();
106
123
  await authorizeCodeExecution(context.conversationId!, context.turnId!, actor.user.id);
107
124
  signed = usable(signed) ?? (await issue({ targetAppId: "assistant", callingAppId: "core", operation: `tool:${name}` }));
@@ -127,7 +144,7 @@ export const runManagedCodeTool =
127
144
  signal: context.signal,
128
145
  body: JSON.stringify({
129
146
  conversationId: context.conversationId,
130
- input: { turnId: context.turnId, callId: context.callId, name, args, ...(decision ? { decision } : {}) },
147
+ input: { turnId: context.turnId, callId: context.callId, name, args, receiptsAfter, ...(decision ? { decision } : {}) },
131
148
  }),
132
149
  });
133
150
  const body = await readBoundedJson(response, 256 * 1024);
@@ -135,29 +152,41 @@ export const runManagedCodeTool =
135
152
  throw new Error("Code host request failed; inspect the existing call before starting another execution");
136
153
  return Reply.parse(body.data).data;
137
154
  };
155
+ const backgroundApproval = async (): Promise<boolean> => {
156
+ throw new Error("Background code cannot request interactive approval. Update task grants in the normal chat.");
157
+ };
138
158
  return waitForManagedCodeCall(
139
159
  request,
140
- runConfig.background
141
- ? {
142
- ...context,
143
- requestApproval: async () => {
144
- throw new Error("Background code cannot request interactive approval. Update task grants in the normal chat.");
145
- },
146
- }
147
- : context,
160
+ runConfig.background ? { ...context, requestApproval: backgroundApproval, requestRelayedApproval: backgroundApproval } : context,
148
161
  );
149
162
  };
150
163
 
151
- /** Ordered reviews are replayed through Nessi before consuming a durable result. */
164
+ /**
165
+ * Ordered reviews are replayed through Nessi before consuming a durable result. Each request a website approval let
166
+ * through is reported as a receipt as soon as the host names it, so the chat shows it with its full URL whatever the
167
+ * run's outcome. Receipts arrive page by page after the ones already reported.
168
+ */
152
169
  export async function waitForManagedCodeCall(
153
- request: (decision?: { id: string; approved: boolean }) => Promise<z.infer<typeof Reply>["data"]>,
154
- context: Pick<ToolContext, "signal" | "requestApproval"> & { locale?: string; reportProgress?: (message: string) => Promise<void> },
170
+ request: (input: { decision?: { id: string; approved: boolean }; receiptsAfter: number }) => Promise<z.input<typeof Reply>["data"]>,
171
+ context: Pick<ToolContext, "signal" | "requestApproval"> & {
172
+ locale?: string;
173
+ reportProgress?: (message: string) => Promise<void>;
174
+ /** Relays a decision; the managed host consumes the linked platform approval before execution. */
175
+ requestRelayedApproval?: (message: string, options: { approvalId?: string; target?: AiApprovalTarget }) => Promise<boolean>;
176
+ reportWebsiteReceipts?: (receipts: AiWebsiteReceipt[]) => Promise<void>;
177
+ },
155
178
  ): Promise<z.infer<ReturnType<typeof z.json>>> {
156
179
  const seen = new Set<string>();
157
180
  let lastPhase: string | undefined;
181
+ let receiptsAfter = 0;
158
182
  while (true) {
159
183
  context.signal.throwIfAborted();
160
- const state = await request();
184
+ const state = Reply.shape.data.parse(await request({ receiptsAfter }));
185
+ if (state.receipts.length) {
186
+ await context.reportWebsiteReceipts?.(state.receipts.map((receipt, index) => ({ index: receiptsAfter + index, ...receipt })));
187
+ receiptsAfter += state.receipts.length;
188
+ }
189
+ if (state.moreReceipts) continue;
161
190
  if (state.phase && state.phase !== lastPhase) {
162
191
  lastPhase = state.phase;
163
192
  const labels = context.locale?.startsWith("de")
@@ -179,9 +208,13 @@ export async function waitForManagedCodeCall(
179
208
  // Skipping already resolved reviews would shift Nessi's child action IDs.
180
209
  for (const approval of state.approvals) {
181
210
  if (seen.has(approval.id)) continue;
182
- const approved = await context.requestApproval(approval.message);
211
+ if (!context.requestRelayedApproval) throw new Error("Managed code approval requires a relay context");
212
+ const approved = await context.requestRelayedApproval(approval.message, {
213
+ approvalId: approval.approvalId,
214
+ target: approval.remember,
215
+ });
183
216
  seen.add(approval.id);
184
- if (approval.decision === null) await request({ id: approval.id, approved });
217
+ if (approval.decision === null) await request({ decision: { id: approval.id, approved }, receiptsAfter });
185
218
  }
186
219
  if (state.status === "done") {
187
220
  // Nessi records a thrown error as a failed tool result the model can act on.
@@ -12,15 +12,6 @@ const ArtifactPath = z
12
12
  .max(180)
13
13
  .regex(/^[a-zA-Z0-9_.-]+(?:\/[a-zA-Z0-9_.-]+)*$/)
14
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
15
  const Icon = z
25
16
  .string()
26
17
  .regex(/^ti ti-[a-z0-9-]+$/)
@@ -39,15 +30,6 @@ const DatabaseSql = z.object({
39
30
  params: z.array(z.json()).max(1000).default([]),
40
31
  });
41
32
 
42
- const CodeWriteInput = Id.extend({
43
- expectedRevision: z.number().int().positive(),
44
- entry: ArtifactPath.optional().describe("index.html for an app with an interface, or the script that code_run executes."),
45
- files: z
46
- .array(z.union([ArtifactFile, z.object({ path: ArtifactPath, fromFile: AiFileReference }).strict()]))
47
- .min(1)
48
- .max(64),
49
- });
50
-
51
33
  const DbName = z
52
34
  .string()
53
35
  .regex(/^[A-Za-z][A-Za-z0-9_]*$/)
@@ -305,11 +287,33 @@ export const CODE_SOURCE_TOOLS = {
305
287
  "Create a private reusable App: an index.html interface with optional style.css and app.js, published actions and optional persistence. Starts with a minimal index.html. 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.",
306
288
  input: ArtifactCreate.pick({ title: true, description: true, icon: true }),
307
289
  },
290
+ // Source text stays top-level because some providers corrupt strings nested in arrays.
291
+ // Imports carry only references, so they keep an array and one review per batch.
308
292
  code_write: {
309
293
  description:
310
- "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.",
311
- review: (input: unknown) => CodeWriteInput.parse(input).files.some((file) => "fromFile" in file),
312
- input: CodeWriteInput,
294
+ "Save one complete source file against expectedRevision from code_read, code_create or the previous write. Other files stay unchanged. Write a multi-file app with one call per file and wait for each result, passing its returned revision as the next expectedRevision; calls sent together in one response share a stale revision, so all but the first fail with CONFLICT. Returns revision, the lines and length of every file, and compiler diagnostics for the saved state; an import of a file not yet written is reported as missing until it is saved. Never runs. Copy exact chat, Project or App files with code_import.",
295
+ input: Id.extend({
296
+ expectedRevision: z.number().int().positive().describe("Revision from code_read, code_create or the previous write."),
297
+ path: ArtifactPath.describe("Relative path of the file to save."),
298
+ content: z
299
+ .string()
300
+ .max(1024 * 1024)
301
+ .describe("Complete file content as plain text; read existing content before replacing it."),
302
+ entry: ArtifactPath.optional().describe("index.html for an app with an interface, or the script that code_run executes."),
303
+ }),
304
+ },
305
+ code_import: {
306
+ description:
307
+ "Atomically import exact chat, Project or App file references as UTF-8 source files against expectedRevision, without printing their bytes. One fresh review covers the whole batch because source may be shared or published. Use code_file_stat first; source file and bundle byte limits apply. Returns revision, the lines and length of every file, imported and diagnostics. Write source text with code_write.",
308
+ review: true,
309
+ input: Id.extend({
310
+ expectedRevision: z.number().int().positive(),
311
+ entry: ArtifactPath.optional().describe("index.html for an app with an interface, or the script that code_run executes."),
312
+ files: z
313
+ .array(z.object({ path: ArtifactPath, fromFile: AiFileReference }).strict())
314
+ .min(1)
315
+ .max(64),
316
+ }),
313
317
  },
314
318
  code_remove: {
315
319
  description:
@@ -21,6 +21,7 @@ import { defineAiTool } from "./tools";
21
21
  const WRITE_TOOLS = new Set([
22
22
  "code_create",
23
23
  "code_write",
24
+ "code_import",
24
25
  "code_remove",
25
26
  "code_update",
26
27
  "code_fork",
@@ -131,7 +132,7 @@ export function createCodeSourceTool(name: CodeSourceToolName) {
131
132
  if (!response.ok) throw new Error("Assistant tool request failed.");
132
133
  return { data: reply.data.data, status: response.status };
133
134
  };
134
- if ("review" in definition && (typeof definition.review === "function" ? definition.review(input) : definition.review)) {
135
+ if ("review" in definition && definition.review) {
135
136
  const preview = z.object({ data: z.object({ message: z.string().min(1) }) }).parse((await send(true)).data).data;
136
137
  if (!authorizeDatabase && !(await context.requestApproval(preview.message)))
137
138
  throw new Error("The user declined this change. No mutation was sent.");
@@ -191,5 +192,6 @@ export const createCodeSourceTools = () => [
191
192
  createCodeSourceTool("code_update"),
192
193
  createCodeSourceTool("code_create"),
193
194
  createCodeSourceTool("code_write"),
195
+ createCodeSourceTool("code_import"),
194
196
  createCodeSourceTool("code_remove"),
195
197
  ];