@k2b/cloud 0.27.0 → 0.29.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 (155) hide show
  1. package/package.json +7 -5
  2. package/src/_internal/cli-plugins.ts +18 -0
  3. package/src/_internal/define-app.ts +21 -5
  4. package/src/_internal/registry-validation.ts +30 -1
  5. package/src/_internal/runtime-context.ts +1 -0
  6. package/src/access/PermissionEditor.tsx +11 -4
  7. package/src/ai/assistant-models.ts +25 -4
  8. package/src/ai/audio-tool.ts +3 -10
  9. package/src/ai/browser-code-contracts.ts +33 -63
  10. package/src/ai/browser.ts +1 -0
  11. package/src/ai/capabilities.ts +35 -9
  12. package/src/ai/chat/blocks.tsx +113 -24
  13. package/src/ai/chat/builtin-tools.tsx +3 -1
  14. package/src/ai/chat/composer-adapter.ts +8 -0
  15. package/src/ai/chat/live-turn.browser-harness.tsx +15 -3
  16. package/src/ai/chat/message-actions.tsx +124 -102
  17. package/src/ai/chat/message-utils.ts +57 -6
  18. package/src/ai/chat/messages.ts +214 -0
  19. package/src/ai/chat/presentation.tsx +67 -28
  20. package/src/ai/chat/tool-groups.ts +1 -1
  21. package/src/ai/chat/tool-names.ts +157 -0
  22. package/src/ai/chat/turn-error.ts +21 -0
  23. package/src/ai/chat/turn-layout.ts +12 -0
  24. package/src/ai/chat/turn-view.tsx +69 -10
  25. package/src/ai/chat/user-message.tsx +19 -14
  26. package/src/ai/chat/visual-tools.tsx +1 -1
  27. package/src/ai/chat-tasks.ts +16 -3
  28. package/src/ai/client/controller.ts +23 -6
  29. package/src/ai/code-mode-skill.ts +21 -25
  30. package/src/ai/code-runtime-tools.ts +5 -1
  31. package/src/ai/code-source-contracts.ts +2 -2
  32. package/src/ai/compaction.ts +22 -3
  33. package/src/ai/data-analysis-skill.ts +3 -3
  34. package/src/ai/default-tools.ts +23 -17
  35. package/src/ai/executor.ts +262 -99
  36. package/src/ai/file-media-type.ts +4 -0
  37. package/src/ai/files-store.ts +17 -10
  38. package/src/ai/http.ts +11 -10
  39. package/src/ai/index.ts +3 -0
  40. package/src/ai/memories.ts +2 -0
  41. package/src/ai/memory-learning.ts +59 -5
  42. package/src/ai/memory-workflow-evidence.ts +35 -10
  43. package/src/ai/open-tool-calls.ts +87 -0
  44. package/src/ai/projects-routes.ts +5 -1
  45. package/src/ai/projects.ts +5 -1
  46. package/src/ai/protocol.ts +34 -1
  47. package/src/ai/provider.ts +16 -1
  48. package/src/ai/quota-provider.ts +4 -1
  49. package/src/ai/routes.ts +26 -10
  50. package/src/ai/run-timeout.ts +4 -5
  51. package/src/ai/runtime.ts +8 -0
  52. package/src/ai/settings.ts +54 -17
  53. package/src/ai/skill-seeds.ts +14 -6
  54. package/src/ai/store.ts +102 -34
  55. package/src/ai/structured.ts +2 -1
  56. package/src/ai/system-prompt.ts +9 -22
  57. package/src/ai/transcription.ts +58 -13
  58. package/src/ai/turn-failure.ts +104 -0
  59. package/src/ai/turn-guidance.ts +505 -0
  60. package/src/ai/turn-policy.ts +2 -1
  61. package/src/ai/types.ts +29 -1
  62. package/src/ai/ui.tsx +1 -0
  63. package/src/api/admin-core-settings.ts +16 -1
  64. package/src/api/admin-outgoing-mail.ts +185 -5
  65. package/src/api/me.ts +42 -1
  66. package/src/browser/FileChooser.tsx +82 -295
  67. package/src/browser/FileSaver.tsx +407 -0
  68. package/src/browser/app-badges.ts +16 -0
  69. package/src/browser/choose-files.browser-harness.tsx +22 -6
  70. package/src/browser/choose-files.tsx +23 -36
  71. package/src/browser/file-chooser-messages.ts +66 -0
  72. package/src/browser/file-providers.ts +258 -8
  73. package/src/browser/files.tsx +26 -3
  74. package/src/browser/notifications.ts +37 -1
  75. package/src/browser/provider-folder.tsx +301 -0
  76. package/src/browser/provider-list.ts +39 -0
  77. package/src/browser/save-files.browser-harness.tsx +35 -0
  78. package/src/browser/save-files.tsx +107 -0
  79. package/src/cli/admin/index.ts +3 -1
  80. package/src/cli/admin/notifications.ts +6 -0
  81. package/src/cli/admin/outgoing-mail.ts +104 -7
  82. package/src/contracts/app.ts +10 -0
  83. package/src/contracts/commands.ts +3 -1
  84. package/src/contracts/notification-group.ts +6 -0
  85. package/src/contracts/notification-types.ts +6 -0
  86. package/src/contracts/outgoing-mail.ts +189 -1
  87. package/src/contracts/registry.ts +1 -0
  88. package/src/contracts/user-notifications.ts +74 -0
  89. package/src/server/middleware/request-logger.ts +11 -2
  90. package/src/services/accounts/avatar.ts +5 -2
  91. package/src/services/help/store.ts +2 -1
  92. package/src/services/image-metadata.ts +438 -0
  93. package/src/services/index.ts +12 -1
  94. package/src/services/logging/redaction.ts +50 -0
  95. package/src/services/mandates/index.ts +4 -2
  96. package/src/services/mandates/policy.ts +20 -0
  97. package/src/services/notifications/batches.ts +139 -79
  98. package/src/services/notifications/browser.ts +48 -15
  99. package/src/services/notifications/channels.ts +33 -13
  100. package/src/services/notifications/dispatcher.ts +88 -6
  101. package/src/services/notifications/email-frame.fixture.html +51 -0
  102. package/src/services/notifications/{email.ts → email-frame.ts} +12 -35
  103. package/src/services/notifications/email-mail.ts +101 -0
  104. package/src/services/notifications/index.ts +49 -36
  105. package/src/services/notifications/observability.ts +9 -1
  106. package/src/services/notifications/platform.ts +33 -5
  107. package/src/services/notifications/preview.ts +23 -0
  108. package/src/services/notifications/quiet.ts +153 -0
  109. package/src/services/notifications/runtime.ts +9 -3
  110. package/src/services/notifications/user.ts +6 -0
  111. package/src/services/outgoing-mail/admin.ts +84 -0
  112. package/src/services/outgoing-mail/attachments.ts +135 -0
  113. package/src/services/outgoing-mail/bulk.ts +11 -0
  114. package/src/services/outgoing-mail/dispatcher.ts +231 -0
  115. package/src/services/outgoing-mail/drain.ts +60 -0
  116. package/src/services/outgoing-mail/enqueue.ts +180 -0
  117. package/src/services/outgoing-mail/index.ts +103 -10
  118. package/src/services/outgoing-mail/message.ts +14 -0
  119. package/src/services/outgoing-mail/messages.ts +431 -0
  120. package/src/services/outgoing-mail/retention.ts +48 -0
  121. package/src/services/outgoing-mail/runtime.ts +72 -0
  122. package/src/services/outgoing-mail/send.ts +130 -0
  123. package/src/services/outgoing-mail/store.ts +115 -4
  124. package/src/services/outgoing-mail/sync.ts +39 -0
  125. package/src/services/outgoing-mail/transport.ts +5 -1
  126. package/src/services/pdf/markdown.ts +22 -4
  127. package/src/services/pdf/template-preview.ts +2 -1
  128. package/src/services/postgres.ts +15 -0
  129. package/src/services/settings/app.ts +3 -0
  130. package/src/services/settings/core-settings.ts +18 -0
  131. package/src/services/settings/store.ts +4 -1
  132. package/src/services/weather/geo.ts +6 -2
  133. package/src/shared/ai-platform-prompt.ts +48 -4
  134. package/src/shared/markdown/extensions/links.ts +28 -20
  135. package/src/shared/markdown/index.ts +14 -5
  136. package/src/shared/markdown/shared.ts +0 -7
  137. package/src/shared/redirect.ts +2 -1
  138. package/src/shared/template-rendering.ts +120 -7
  139. package/src/ssr/AppBadge.tsx +30 -0
  140. package/src/ssr/AppLaunchpadPanel.tsx +47 -26
  141. package/src/ssr/GlobalAnnouncements.island.tsx +1 -1
  142. package/src/ssr/IslandError.tsx +33 -0
  143. package/src/ssr/Layout.tsx +3 -0
  144. package/src/ssr/LayoutHelp.tsx +4 -2
  145. package/src/ssr/RailApps.island.tsx +9 -2
  146. package/src/ssr/app-badges.ts +93 -0
  147. package/src/ssr/platform-messages.ts +1 -1
  148. package/src/ssr/rail-messages.ts +4 -0
  149. package/src/ssr/rail-navigation.ts +2 -0
  150. package/src/styles/effects.css +38 -17
  151. package/src/styles/file-chooser.css +25 -0
  152. package/src/styles/tokens.css +2 -0
  153. package/src/styles/utilities-markdown-editor.css +4 -39
  154. package/src/styles/utilities-markdown-table.css +14 -17
  155. package/src/styles/utilities-navigation.css +41 -0
@@ -100,7 +100,11 @@ export const runManagedCodeTool =
100
100
  const headers = new Headers({ authorization: `Bearer ${signed.token}`, "content-type": "application/json" });
101
101
  headers.set(CODE_CAPABILITY_TOKEN_HEADER, callback.token);
102
102
  if (context.locale) headers.set(LOCALE_HEADER, context.locale);
103
- if (context.timeZone) headers.set("cookie", `${TIMEZONE_COOKIE}=${encodeURIComponent(context.timeZone)}`);
103
+ const timeZone = runConfig.timeZone ?? context.timeZone;
104
+ headers.set(
105
+ "cookie",
106
+ [`theme=${runConfig.theme ?? "light"}`, ...(timeZone ? [`${TIMEZONE_COOKIE}=${encodeURIComponent(timeZone)}`] : [])].join("; "),
107
+ );
104
108
  const response = await fetch(new URL(`/_internal/assistant/tools/${name}`, app.baseUrl), {
105
109
  method: "POST",
106
110
  headers,
@@ -41,7 +41,7 @@ const DatabaseSql = z.object({
41
41
 
42
42
  const CodeWriteInput = Id.extend({
43
43
  expectedRevision: z.number().int().positive(),
44
- entry: ArtifactPath.optional(),
44
+ entry: ArtifactPath.optional().describe("index.html for an app with an interface, or the script that code_run executes."),
45
45
  files: z
46
46
  .array(z.union([ArtifactFile, z.object({ path: ArtifactPath, fromFile: AiFileReference }).strict()]))
47
47
  .min(1)
@@ -302,7 +302,7 @@ export const CODE_SOURCE_TOOLS = {
302
302
  },
303
303
  code_create: {
304
304
  description:
305
- "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.",
305
+ "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
306
  input: ArtifactCreate.pick({ title: true, description: true, icon: true }),
307
307
  },
308
308
  code_write: {
@@ -1,8 +1,10 @@
1
- import type { CompactFn, Message, StoreEntry } from "@k2b/nessi";
1
+ import type { CompactFn, Message, StoreEntry, Usage } from "@k2b/nessi";
2
2
  import { truncateMiddle } from "@k2b/nessi";
3
+ import { taskReasoningEffort } from "./provider";
3
4
  import { aiConversations } from "./store";
4
5
  import { safelyRecordStructuredRun } from "./structured-runs";
5
6
  import { buildAiTaskPrompt } from "./task-prompt";
7
+ import { AiTurnFailure } from "./turn-failure";
6
8
 
7
9
  /**
8
10
  * Structured handoff prompt, modeled on the compaction prompts of the big
@@ -158,6 +160,7 @@ export const createCloudCompactFn = (input: {
158
160
  modelProfileId: input.modelProfileId,
159
161
  providerModel: ctx.provider.model,
160
162
  };
163
+ let usage: Usage | undefined;
161
164
  const result = await ctx.provider
162
165
  .complete({
163
166
  systemPrompt: prompt.systemPrompt,
@@ -165,19 +168,35 @@ export const createCloudCompactFn = (input: {
165
168
  tools: [],
166
169
  maxOutputTokens: input.maxOutputTokens,
167
170
  signal: input.signal,
168
- disableReasoning: true,
171
+ reasoningEffort: taskReasoningEffort(ctx.provider),
172
+ })
173
+ .then((result) => {
174
+ usage = result.usage;
175
+ if (result.finishReason === "error" || result.finishReason === "interrupted" || result.finishReason === "aborted") {
176
+ throw new AiTurnFailure(
177
+ result.finishReason === "error" ? "provider_stopped" : "interrupted",
178
+ `AI compaction ended with ${result.finishReason}.`,
179
+ );
180
+ }
181
+ return result;
169
182
  })
170
183
  .catch(async (error: unknown) => {
171
184
  await safelyRecordStructuredRun({
172
185
  ...accounting,
173
186
  status: "failed",
174
187
  durationMs: Date.now() - startedAt,
188
+ usage,
175
189
  error: error instanceof Error ? error.message : String(error),
176
190
  });
177
191
  throw error;
178
192
  });
179
193
  await safelyRecordStructuredRun({ ...accounting, status: "ok", durationMs: Date.now() - startedAt, usage: result.usage });
180
- const summaryText = textFromAssistant(result.message).trim();
194
+ const summaryText = result.message.content
195
+ .filter((block) => block.type === "text")
196
+ .map((block) => block.text)
197
+ .filter(Boolean)
198
+ .join("\n\n")
199
+ .trim();
181
200
  if (!summaryText) return;
182
201
 
183
202
  await aiConversations.compactMessages({
@@ -2,9 +2,9 @@
2
2
  import type { AiSkillTemplate } from "./skills";
3
3
  export const ASSISTANT_DATA_ANALYSIS_SKILL = {
4
4
  "key": "assistant:data-analysis",
5
- "version": 7,
5
+ "version": 8,
6
6
  "name": "assistant-data-analysis",
7
- "description": "Analyze source data, explain metrics and comparisons, and build evidence-backed reports or interactive dashboards in Assistant Code Mode. Use for multi-step analysis, data exploration, and dashboards; a simple chart only needs the Code Mode charts reference.",
8
- "instructions": "# Analyze data and deliver an inspectable result\n\nStart with the question the reader needs to answer. Choose a direct answer,\nan interactive visualization in this chat, an exported file, or a reusable Studio app accordingly.\nFor a one-time visual analysis, prefer a chat visualization; filters and buttons\ndo not by themselves require a Studio App.\nLoad `assistant-code-mode` for execution and read its `references/analytics.md`\nfor the built-in UI. Loading this skill does not install a library or grant access.\n\n## Keep a working plan\n\nFor work with several real steps, use `todo_write` to keep a short chat plan.\nReplace the full `todos` list each time; give each item a stable `id`, actionable\n`content`, and `status` (`pending`, `in_progress`, `completed`, or `cancelled`).\nAt most one step is active. Update as work changes, including user corrections;\nmark a step completed only after doing and checking it. Preserve exact commands\nwhen they matter. Skip this tool for a simple calculation or conversational reply.\n\nFor analysis, useful steps are reconcile source data, build the analysis, and\nverify totals, filters and conclusions. A rendered dashboard alone is not proof\nthat its numbers are correct. Keep blocked source work open and explain why.\n\n## Establish the data\n\nIdentify the actual source, unit of observation, time window, timezone, and\nlatest complete period. Read files or discover the relevant Cloud capabilities\nbefore selecting fields. External APIs use `cloud.http.fetch`; credentials are entered\nonly through the trusted `code_secret` dialog.\n\nInspect a bounded sample, missing values, duplicate keys, types, and coverage.\nReconcile totals and join cardinalities before drawing conclusions. Keep raw\nnumbers separate from display formatting. Distinguish zero from unavailable data;\nstate exclusions and denominator choices. Do not substitute fixture data for a\nblocked source unless the user requested a mockup.\n\nKeep the source query or transformation in the saved source or an accompanying\nfile so another run can reproduce the result. Preserve the source identity and\nretrieval timestamp, without credentials or credential-bearing URLs.\n\n## Build one consistent analysis\n\nDerive charts, metrics, and tables from the same reviewed data. Aggregate large\ninputs before crossing the UI bridge. Chart Explorer rows identify selectable\nentities with stable string keys; derived bins and groups need explicit mark\nmappings. Shared keys across charts mean the same entity, not equal values.\n\nChoose the simplest chart that answers the question. Use tables for exact\nrecords, bars for category comparisons, lines for ordered trends, and distributions\nfor spread. State units, comparison windows, and whether a percent is a fraction\nor an already scaled number. Do not imply causation from correlation.\n\nLead reports with the finding, then supporting evidence and limitations.\nLead dashboards with the important measurements, then trends and diagnostic\nbreakdowns. Use shared filters only when they affect all claimed views. Keep the\ninitial view useful without interaction. Avoid unrelated metrics added merely to\nfill a grid.\n\nAttach source context to Explorer data: `mode`, `asOf`, `sources`, and relevant\n`status`/`note`. A timestamp records when the data was retrieved; it does not prove\nthat the upstream source is complete. Label partial or fixture data visibly.\n\n## Validate before delivery\n\nRun the actual source with `code_run`. Inspect the result; use `code_interact`\nwith structured UI events to test filters, chart/table switching, selection,\nempty results, and recovery from a failed load. Reconcile displayed values with\nthe reviewed totals and check that filters describe the data actually displayed.\nA successful schema validation does not establish analytical correctness.\n\nValidate the actual presented source (the saved revision for Apps), rather than pasting its formulas into a\nsecond test script. Keep an independent expectation from the input data: row\ncounts, unmatched joins, totals and representative boundary cases. For targets,\nstate their grain (for example month × region) and aggregate each target once;\nmultiple selected regions must sum their distinct targets. Compare inspected raw\nKPI values and plotted series with independent expectations. A formatted value\nmatching after rounding does not validate the raw ratio. Never round fractions\nbefore passing them to percent-formatted controls. Preserve precision\nuntil display formatting. Test reset, one/multiple/all selections, empty results,\nand complete versus partial periods. A newly generated timestamp is not source\nfreshness: keep the real retrieval or file-snapshot timestamp stable.\n\nFor a Studio App data snapshot, export the validated dataset with `cloud.download` and\n`code_export`, then copy its exact path/version into the resource with\n`code_write({id,expectedRevision,files:[{path:\"data.json\",fromFile:reference}]})`.\nObtain the exact reference with `code_file_stat`; importing private files into\nApp source receives fresh review.\nImport that file in the app. Never rebuild a truncated dataset by copying tool\noutput. Keep transformations and source identity alongside the snapshot.\n\nHuman approval and uncertain HTTP outcomes follow the Code Mode HTTP contract.\nDo not replay an external mutation to refresh a chart. Separate local filtering\nfrom external loading; an Apply button can avoid a request for every slider move.\n\n## Save, share, and hand off\n\nFor chat visualizations, test `code_run` and relevant `code_interact` controls,\nthen deliver with `code_present({runId,title})`. A successful run alone is not\nvisible to the user. Retain source identity, reviewed input data and their real\nretrieval timestamp. Put writes and external reloads behind explicit buttons;\nopening an old result must not repeat earlier actions. The user can download the\ncurrent view as PDF/HTML or an individual chart as SVG.\n\nFor reusable Studio Apps, reuse one Cloud resource for later revisions of the same report or dashboard.\nSave its source, test that revision, and use the normal Code Mode publication\nworkflow when publication is requested. Publishing a version and granting reader\naccess are separate operations. Preserve existing access; a dashboard request\ndoes not authorize broadening it. Personal secrets are never copied to readers.\n\nA published source version is not automatically a frozen data snapshot. A frozen\nreport must retain the reviewed data explicitly in its authorized resource or\noutput file. A live app must implement its loader and display the retrieval time;\nloading once is not continuous monitoring. No background refresh exists unless\nimplemented through an appropriate supported workflow.\n\nPresent the chat visualization, resource link or exported file, the data timestamp, and material\ncoverage limitations. Say whether it is a snapshot or reloads from its sources.\nIf publication fails, retain the tested resource and report the failed stage;\ndo not claim success or create a different public destination. Sites-specific\nhosting, access policies, and editor storage are not part of this Cloud workflow.",
7
+ "description": "Analyze source data, explain metrics and comparisons, and build evidence-backed reports or HTML dashboards in Assistant Code Mode. Use for multi-step analysis, data exploration, and dashboards; a simple chart only needs the Code Mode charts reference.",
8
+ "instructions": "# Analyze data and deliver an inspectable result\n\nStart with the question the reader needs to answer. Choose a direct answer,\nan app shown in this chat, an exported file, or a reusable Studio app accordingly.\nFor a one-time visual analysis, prefer a chat app; filters and buttons do not by\nthemselves require a Studio App. Load `assistant-code-mode` for execution and read\nits `references/apps.md` and `references/charts.md` for interfaces and charts.\nLoading this skill does not install a library or grant access.\n\n## Keep a working plan\n\nFor work with several real steps, use `todo_write` to keep a short chat plan.\nReplace the full `todos` list each time; give each item a stable `id`, actionable\n`content`, and `status` (`pending`, `in_progress`, `completed`, or `cancelled`).\nAt most one step is active. Update as work changes, including user corrections;\nmark a step completed only after doing and checking it. Preserve exact commands\nwhen they matter. Skip this tool for a simple calculation or conversational reply.\n\nFor analysis, useful steps are reconcile source data, build the analysis, and\nverify totals, filters and conclusions. A rendered dashboard alone is not proof\nthat its numbers are correct. Keep blocked source work open and explain why.\n\n## Establish the data\n\nIdentify the actual source, unit of observation, time window, timezone, and\nlatest complete period. Read files or discover the relevant Cloud capabilities\nbefore selecting fields. External APIs use `cloud.http.fetch`; credentials are entered\nonly through the trusted `code_secret` dialog.\n\nInspect a bounded sample, missing values, duplicate keys, types, and coverage.\nReconcile totals and join cardinalities before drawing conclusions. Keep raw\nnumbers separate from display formatting. Distinguish zero from unavailable data;\nstate exclusions and denominator choices. Do not substitute fixture data for a\nblocked source unless the user requested a mockup.\n\nKeep the source query or transformation in the saved source or an accompanying\nfile so another run can reproduce the result. Preserve the source identity and\nretrieval timestamp, without credentials or credential-bearing URLs.\n\n## Build one consistent analysis\n\nDerive charts, metrics, and tables from the same reviewed data. Aggregate large\ninputs in a script before they reach an app; an app shows aggregates, not raw\nexports. Give exact values a table, for example in a `<details>` element under\nthe chart.\n\nChoose the simplest chart that answers the question. Use tables for exact\nrecords, bars for category comparisons, lines for ordered trends, and distributions\nfor spread. State units, comparison windows, and whether a percent is a fraction\nor an already scaled number. Do not imply causation from correlation.\n\nLead reports with the finding, then supporting evidence and limitations.\nLead dashboards with the important measurements, then trends and diagnostic\nbreakdowns. Use shared filters only when they affect all claimed views. Keep the\ninitial view useful without interaction. Avoid unrelated metrics added merely to\nfill a grid.\n\nShow the source context in the app: whether it is a snapshot, when the data was\nretrieved, its sources, and relevant notes. A timestamp records when the data\nwas retrieved; it does not prove that the upstream source is complete. Label\npartial or fixture data visibly.\n\n## Validate before delivery\n\nCompute in a script with `code_run` and inspect the result. Put the filter and\naggregation logic of an app into a module the script can import too\n(`lib/totals.js`), and test it there with filters, empty results and failed\nloads. Reconcile the values the app will show with the reviewed totals and check\nthat filters describe the data actually displayed. A successful schema\nvalidation does not establish analytical correctness. Apps are not rendered in\na test yet; read the static findings of `code_write` and `code_present`, and say\nwhat the person should check.\n\nValidate the logic the app actually uses, rather than pasting its formulas into a\nsecond test script. Keep an independent expectation from the input data: row\ncounts, unmatched joins, totals and representative boundary cases. For targets,\nstate their grain (for example month × region) and aggregate each target once;\nmultiple selected regions must sum their distinct targets. Compare inspected raw\nKPI values and plotted series with independent expectations. A formatted value\nmatching after rounding does not validate the raw ratio. Never round fractions before formatting them as percent. Preserve precision\nuntil display formatting. Test reset, one/multiple/all selections, empty results,\nand complete versus partial periods. A newly generated timestamp is not source\nfreshness: keep the real retrieval or file-snapshot timestamp stable.\n\nFor a Studio App data snapshot, export the validated dataset with `cloud.download` and\n`code_export`, then copy its exact path/version into the App's files with\n`code_file_copy` (see the Code Mode file transfers reference); importing private\nfiles into an App receives fresh review. Read it in the app with\n`JSON.parse(await (await cloud.files.read(\"data.json\")).text())`. Never rebuild a\ntruncated dataset by copying tool output. Keep transformations and source\nidentity alongside the snapshot.\n\nHuman approval and uncertain HTTP outcomes follow the Code Mode HTTP contract.\nDo not replay an external mutation to refresh a chart. Separate local filtering\nfrom external loading; an Apply button can avoid a request for every slider move.\n\n## Save, share, and hand off\n\nFor chat apps, test the numbers with `code_run`, then deliver with\n`code_present({title, files})`. A successful run alone is not visible to the\nuser. Retain source identity, reviewed input data and their real retrieval\ntimestamp. Put writes and external reloads behind explicit buttons; opening an\nold result must not repeat earlier actions. The user can download the current\nview as PDF or static HTML.\n\nFor reusable Studio Apps, reuse one Cloud resource for later revisions of the same report or dashboard.\nSave its source, test that revision, and use the normal Code Mode publication\nworkflow when publication is requested. Publishing a version and granting reader\naccess are separate operations. Preserve existing access; a dashboard request\ndoes not authorize broadening it. Personal secrets are never copied to readers.\n\nA published source version is not automatically a frozen data snapshot. A frozen\nreport must retain the reviewed data explicitly in its authorized resource or\noutput file. A live app must implement its loader and display the retrieval time;\nloading once is not continuous monitoring. No background refresh exists unless\nimplemented through an appropriate supported workflow.\n\nPresent the chat app, resource link or exported file, the data timestamp, and material\ncoverage limitations. Say whether it is a snapshot or reloads from its sources.\nIf publication fails, retain the tested resource and report the failed stage;\ndo not claim success or create a different public destination. Sites-specific\nhosting, access policies, and editor storage are not part of this Cloud workflow.",
9
9
  "extraFrontmatter": {}
10
10
  } satisfies AiSkillTemplate;
@@ -1,11 +1,13 @@
1
1
  import { z } from "zod";
2
+ import type { AccessSubject } from "../server";
3
+ import { resolveAssistantAudioModel } from "./assistant-models";
2
4
  import { createCloudAiTranscribeAudioTool } from "./audio-tool";
3
5
  import {
4
6
  CODE_RUNTIME_TOOL_NAMES,
5
7
  CodeActionInput,
8
+ CodeCheckInput,
6
9
  CodeExportInput,
7
10
  CodeInspectInput,
8
- CodeInteractInput,
9
11
  CodeOpenInput,
10
12
  CodePresentInput,
11
13
  CodeRunInput,
@@ -110,7 +112,7 @@ export const createCloudAiCodeTools = () => [
110
112
  defineAiTool({
111
113
  name: "code_secret",
112
114
  description:
113
- "Open a trusted Secret input dialog. The user enters the value directly into encrypted Assistant storage; only configured/name returns. Never ask for a credential in chat, survey, app controls or code_interact. Personal secrets are scoped to this chat or resource and bound to the exact HTTPS origin, header and prefix. Use cloud.http.secret(name,{prefix}) in cloud.http.fetch headers; load assistant-code-mode for HTTP details. Web UI required for secret entry; stored secrets also work from CLI.",
115
+ "Open a trusted Secret input dialog. The user enters the value directly into encrypted Assistant storage; only configured/name returns. Never ask for a credential in chat, survey or app fields. Personal secrets are scoped to this chat or resource and bound to the exact HTTPS origin, header and prefix. Use cloud.http.secret(name,{prefix}) in cloud.http.fetch headers; load assistant-code-mode for HTTP details. Web UI required for secret entry; stored secrets also work from CLI.",
114
116
  inputSchema: CodeSecretInput,
115
117
  outputSchema: z.object({ configured: z.boolean(), name: z.string() }),
116
118
  approval: "never",
@@ -128,27 +130,18 @@ export const createCloudAiCodeTools = () => [
128
130
  promptHint:
129
131
  "For file analysis, data transformations or combining Cloud data, run a short one-off script with code_run. Load assistant-code-mode for its runtime APIs; no saved app is required.",
130
132
  description:
131
- "Run a saved resource id OR one-off code in the isolated worker. Optional resourceId binds one-off code to existing app data (Manage required), without editing its source. Includes cloud.capabilities.run(name,input) to chain Cloud capabilities in JavaScript; load assistant-code-mode for its runtime APIs. Scripts accept current chat inputPaths; app test runs use them only as explicit picker fixtures. Returns output, logs and UI state for agent inspection only; use code_present to show a one-off visualization to the user. Test runs use the app’s real shared and personal data; database writes and capability effects keep normal permissions and approvals.",
133
+ "Run one-off script code OR a saved script resource in the isolated worker. Optional resourceId binds one-off code to existing app data (Manage required), without editing its source. Includes cloud.capabilities.run(name,input) to chain Cloud capabilities in JavaScript; load assistant-code-mode for its runtime APIs. Scripts read explicit chat inputPaths. Returns output, logs and captured files for agent inspection only. Apps with an index.html interface do not run here: show them with code_open or code_present. Runs use real shared and personal data; database writes and capability effects keep normal permissions and approvals.",
132
134
  inputSchema: CodeRunInput,
133
135
  outputSchema: z.json(),
134
136
  approval: "never",
135
137
  }).server(runManagedCodeTool("code_run")),
136
138
  defineAiTool({
137
139
  name: "code_inspect",
138
- description:
139
- "Inspect a test run: errors, logs, output, controls and pending modal. Use nodeId to inspect table rows or list items; follow pagination only as needed.",
140
+ description: "Inspect a script run: status, progress, errors, logs, output and captured files. Use waitMs to wait for background work.",
140
141
  inputSchema: CodeInspectInput,
141
142
  outputSchema: z.json(),
142
143
  approval: "never",
143
144
  }).server(runManagedCodeTool("code_inspect")),
144
- defineAiTool({
145
- name: "code_interact",
146
- description:
147
- "Operate a control or answer a pending modal using its exact snapshot ID. Buttons need only id; controls use event:{type:change,value:...} or event:{type:select,key:...}. Use answer for modal replies, or answer:null to cancel. Copy an interactions example from code_inspect; never send JSON as a string. Use steps:[{id,event?},...] for up to three known sequential interactions; stops on errors, modals or background work. Returns one resulting state with completedSteps and nextStep.",
148
- inputSchema: CodeInteractInput,
149
- outputSchema: z.json(),
150
- approval: "never",
151
- }).server(runManagedCodeTool("code_interact")),
152
145
  defineAiTool({
153
146
  name: "code_stop",
154
147
  description: "Stop and release an isolated test run.",
@@ -158,15 +151,24 @@ export const createCloudAiCodeTools = () => [
158
151
  }).server(runManagedCodeTool("code_stop")),
159
152
  defineAiTool({
160
153
  name: "code_open",
161
- description: "Show the app beside the chat without starting it. For one-off calculations, return the result instead of opening an app.",
154
+ description:
155
+ "Show the app beside the chat without starting it; HTML apps require a passing code_check for the current files and tables. For one-off calculations, return the result instead of opening an app.",
162
156
  inputSchema: CodeOpenInput,
163
157
  outputSchema: z.json(),
164
158
  approval: "never",
165
159
  }).client(),
160
+ defineAiTool({
161
+ name: "code_check",
162
+ description:
163
+ "Mandatory HTML app self-test: write files and steps.json (at most 20 main-flow steps, ending in the filled main state), call code_check({id} OR {files}), fix every error and layout warning, call view_image({path, prompt: review.prompt}) for EVERY path in review.paths (screenshots and PDFs), fix what it finds and check again, then code_open/code_present/code_publish. Runs desktop 1280×800 and phone 390×844 in opposite themes on separate throwaway copies of database, shared KV/files and only your own KV.user. Steps run in both views; reload keeps the copy. Match accessible role/name, label or text exactly, then case-insensitively, then by substring; ambiguous targets fail with candidates, placeholders are never names. Fill numbers with a dot. Upload file is an app-relative source path or chat file path/name. AI runs for real; HTTP, capability effects and approvals are unavailable; read-only granted capabilities run except in background turns. Returns passed, content hash (files, steps and tables), height, issues, calls, captured downloads, three whole-page PNG chat paths (up to 2000 px tall; cropped marks a longer page), review and an untrusted aria tree (4 KiB). Passing means not broken; misaligned rows, also in PDFs, and broken values only warn. Self-test is a workflow guard, not a security mechanism. Aborting closes pages and discards copies. Bounded to 45 seconds, 1000 copied rows/16 MiB (schema only beyond), 64 downloads/250 MiB total/50 MiB per file.",
164
+ inputSchema: CodeCheckInput,
165
+ outputSchema: z.json(),
166
+ approval: "never",
167
+ }).server(runManagedCodeTool("code_check")),
166
168
  defineAiTool({
167
169
  name: "code_present",
168
170
  description:
169
- "Present a successful one-off run as a persistent interactive visualization in this chat. Requires code_run with code, without resourceId or saved app id. Saves source, selected inputs and UI preview. Users can activate controls and download the current view. Test runs are not visible until this succeeds. For reusable apps use code_open instead.",
171
+ "Show an HTML app as a card in this chat after a passing code_check for exactly these files and table definitions. Pass files (index.html plus optional style.css and app.js) for a one-off app without saved data, or the id of a saved app to run it live with its data. The person starts the card with a click. Static problems such as CDN scripts, inline handlers or a missing index.html are rejected before anything is saved. For a reusable app beside the chat use code_open.",
170
172
  inputSchema: CodePresentInput,
171
173
  outputSchema: z.json(),
172
174
  approval: "never",
@@ -193,12 +195,12 @@ export const CLOUD_AI_DEFERRED_BUILTIN_TOOL_NAMES = new Set<string>([
193
195
  "markdown_to_pdf",
194
196
  "html_to_pdf",
195
197
  "present",
196
- "calculate",
197
198
  "read_cloud_resource",
198
199
  ]);
199
200
 
200
201
  export const createConfiguredDefaultCloudAiTools = async (config?: {
201
202
  firecrawlApiKey?: string | null;
203
+ accessSubject?: AccessSubject | null;
202
204
  fetch?: typeof fetch;
203
205
  allowedDataBoundaries?: AiDataBoundary[];
204
206
  }) => {
@@ -214,8 +216,12 @@ export const createConfiguredDefaultCloudAiTools = async (config?: {
214
216
  createCloudAiPresentTool(),
215
217
  createCloudAiCalculateTool(),
216
218
  createCloudAiViewImageTool(),
217
- createCloudAiTranscribeAudioTool(),
218
219
  ];
220
+ const audioModelConfigured = await resolveAssistantAudioModel(config?.accessSubject ?? null, config?.allowedDataBoundaries).then(
221
+ () => true,
222
+ () => false,
223
+ );
224
+ if (audioModelConfigured) tools.push(createCloudAiTranscribeAudioTool());
219
225
  const firecrawlConfigured =
220
226
  config && "firecrawlApiKey" in config ? Boolean(config.firecrawlApiKey?.trim()) : await isCloudAiFirecrawlConfigured();
221
227
  if (firecrawlConfigured) {