@k2b/cloud 0.29.1 → 0.31.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.
- package/package.json +4 -2
- package/src/_internal/app-presentation.ts +22 -1
- package/src/_internal/app-roles.ts +14 -0
- package/src/_internal/capabilities.ts +113 -5
- package/src/_internal/capability-sentences.ts +413 -0
- package/src/_internal/define-app.ts +24 -5
- package/src/_internal/registry-validation.ts +5 -0
- package/src/_internal/registry.ts +50 -6
- package/src/_internal/widget-declarations.ts +53 -0
- package/src/access/PermissionEditor.tsx +43 -24
- package/src/ai/app-skill-store.ts +298 -0
- package/src/ai/app-skills.ts +105 -0
- package/src/ai/approval-routes.ts +40 -4
- package/src/ai/approvals.ts +68 -10
- package/src/ai/browser.ts +10 -0
- package/src/ai/capabilities.ts +68 -9
- package/src/ai/chart-block.ts +380 -0
- package/src/ai/chat/action-sentences.ts +49 -0
- package/src/ai/chat/blocks.tsx +97 -40
- package/src/ai/chat/chart-block.tsx +79 -0
- package/src/ai/chat/live-turn.browser-harness.tsx +4 -1
- package/src/ai/chat/message-actions.tsx +3 -1
- package/src/ai/chat/message-utils.ts +3 -0
- package/src/ai/chat/messages.ts +26 -2
- package/src/ai/chat/tool-groups.ts +66 -12
- package/src/ai/chat/tool-names.ts +6 -2
- package/src/ai/chat/turn-layout.ts +10 -2
- package/src/ai/chat/turn-view.tsx +123 -36
- package/src/ai/chat/web-tools.tsx +95 -1
- package/src/ai/client/controller.ts +6 -3
- package/src/ai/code-runtime-tools.ts +109 -61
- package/src/ai/code-source-contracts.ts +25 -21
- package/src/ai/code-source-tools.ts +3 -1
- package/src/ai/default-tool-contracts.ts +198 -0
- package/src/ai/default-tools.ts +25 -1
- package/src/ai/executor.ts +189 -35
- package/src/ai/fetch-file-tool.ts +41 -4
- package/src/ai/firecrawl-tools.ts +87 -4
- package/src/ai/index.ts +17 -1
- package/src/ai/migrate.ts +172 -14
- package/src/ai/protocol.ts +22 -1
- package/src/ai/routes.ts +11 -0
- package/src/ai/runtime-tools.ts +1 -0
- package/src/ai/runtime.ts +46 -7
- package/src/ai/skill-content.ts +54 -0
- package/src/ai/skills-routes.ts +22 -13
- package/src/ai/skills.ts +177 -231
- package/src/ai/store.ts +66 -1
- package/src/ai/tool-audit.ts +11 -1
- package/src/ai/tool-sentences.ts +38 -0
- package/src/ai/tools.ts +91 -3
- package/src/ai/types.ts +47 -1
- package/src/ai/web-provenance.ts +103 -0
- package/src/api/admin-ai-skills.ts +35 -16
- package/src/api/capabilities.ts +3 -2
- package/src/api/fanout.ts +128 -0
- package/src/api/help.ts +8 -3
- package/src/api/mcp.ts +4 -1
- package/src/api/search.ts +31 -106
- package/src/api/widgets.ts +291 -101
- package/src/browser/CloudResourceSearch.browser-harness.tsx +1 -1
- package/src/browser/CloudResourceSearch.tsx +1 -1
- package/src/browser/command-shortcuts.ts +21 -6
- package/src/browser/ndjson.ts +34 -0
- package/src/browser/reload.ts +46 -0
- package/src/browser/search-stream.ts +4 -26
- package/src/browser/spotlight-position.ts +4 -1
- package/src/browser/widgets.ts +30 -0
- package/src/capabilities/executions.ts +3 -0
- package/src/capabilities/server.ts +20 -2
- package/src/cli/access.ts +48 -14
- package/src/cli/admin/ai-skills.ts +59 -39
- package/src/cli/commands.ts +20 -0
- package/src/cli/index.ts +16 -0
- package/src/cli/plugin.ts +30 -0
- package/src/contracts/announcement-cookie.ts +62 -0
- package/src/contracts/announcements.ts +14 -52
- package/src/contracts/app.ts +34 -2
- package/src/contracts/capabilities.ts +24 -0
- package/src/contracts/pwa-paths.ts +7 -2
- package/src/contracts/pwa.ts +2 -7
- package/src/contracts/registry.ts +10 -1
- package/src/contracts/shared.ts +5 -0
- package/src/contracts/widgets.ts +67 -2
- package/src/desktop/solid.tsx +6 -7
- package/src/index.ts +1 -0
- package/src/server/help.ts +8 -1
- package/src/server/index.ts +1 -0
- package/src/server/widget-request.ts +27 -0
- package/src/services/accounts/users.ts +15 -0
- package/src/services/approvals/index.ts +225 -0
- package/src/services/approvals/migrate.ts +33 -0
- package/src/services/help/index.ts +18 -5
- package/src/services/help/types.ts +3 -2
- package/src/services/identity/constants.ts +2 -0
- package/src/services/identity/key-ring.ts +123 -42
- package/src/services/identity/runtime-config.ts +13 -2
- package/src/shared/app-presentation.ts +11 -0
- package/src/shared/email-html.ts +43 -8
- package/src/ssr/AdminLayout.tsx +4 -7
- package/src/ssr/AppLaunchpad.island.tsx +1 -1
- package/src/ssr/AppLaunchpadPanel.tsx +12 -3
- package/src/ssr/GlobalAnnouncements.island.tsx +2 -2
- package/src/ssr/GlobalSearchDialog.tsx +1 -43
- package/src/ssr/GlobalSearchTrigger.island.tsx +12 -3
- package/src/ssr/Layout.tsx +38 -14
- package/src/ssr/LayoutFooter.tsx +1 -1
- package/src/ssr/LayoutHeader.tsx +17 -20
- package/src/ssr/LayoutHelp.tsx +1 -1
- package/src/ssr/LayoutHelpTrigger.island.tsx +1 -1
- package/src/ssr/LayoutRail.tsx +1 -2
- package/src/ssr/PageTrail.tsx +23 -0
- package/src/ssr/PageTrailRow.island.tsx +20 -0
- package/src/ssr/PageTrailRuntime.island.tsx +26 -0
- package/src/ssr/ProfilePreferences.island.tsx +7 -3
- package/src/ssr/PwaRuntime.island.tsx +1 -1
- package/src/ssr/app-navigation.ts +5 -7
- package/src/ssr/global-search-host.tsx +55 -0
- package/src/ssr/help.ts +3 -3
- package/src/ssr/islands/index.ts +1 -0
- package/src/ssr/layout-runtime.ts +60 -0
- package/src/ssr/mobile-menu-history.ts +1 -1
- package/src/ssr/page-trail-runtime.ts +313 -0
- package/src/ssr/page-trail.browser-harness.tsx +216 -0
- package/src/ssr/page-trail.ts +59 -0
- package/src/ssr/platform-messages.ts +4 -0
- package/src/ssr/rail-context.ts +4 -3
- package/src/ssr/runtime.ts +14 -1
- package/src/styles/effects.css +21 -4
- package/src/styles/global.css +35 -2
- package/src/styles/utilities-layout.css +24 -1
- package/src/styles/utilities-navigation.css +6 -18
- package/src/ai/code-mode-skill.ts +0 -114
- package/src/ai/data-analysis-skill.ts +0 -10
- package/src/ai/grids-skill.ts +0 -158
- package/src/ai/skill-seeds.ts +0 -560
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@k2b/cloud",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Application platform library for independently deployed Hono and SolidJS services behind a dynamic gateway.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"repository": {
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"./browser/resource-clipboard": "./src/browser/resource-clipboard.ts",
|
|
39
39
|
"./browser/commands": "./src/browser/commands.ts",
|
|
40
40
|
"./browser/search": "./src/browser/search.ts",
|
|
41
|
+
"./browser/widgets": "./src/browser/widgets.ts",
|
|
41
42
|
"./browser/testing": "./src/browser/testing.ts",
|
|
42
43
|
"./browser/resource-picker": "./src/browser/resource-picker.tsx",
|
|
43
44
|
"./browser/files": "./src/browser/files.tsx",
|
|
@@ -72,6 +73,7 @@
|
|
|
72
73
|
"./ssr": "./src/ssr/index.ts",
|
|
73
74
|
"./ssr/islands": "./src/ssr/islands/index.ts",
|
|
74
75
|
"./ssr/WorkspaceNavigation.island": "./src/ssr/WorkspaceNavigation.island.tsx",
|
|
76
|
+
"./ssr/layout-runtime": "./src/ssr/layout-runtime.ts",
|
|
75
77
|
"./ssr/*": "./src/ssr/*",
|
|
76
78
|
"./config": "./src/config/index.ts",
|
|
77
79
|
"./config/*": "./src/config/*",
|
|
@@ -103,7 +105,7 @@
|
|
|
103
105
|
"@tailwindcss/typography": "0.5.20",
|
|
104
106
|
"@k2b/nessi": "0.17.0",
|
|
105
107
|
"@k2b/ssr": "0.15.0",
|
|
106
|
-
"@k2b/ui": "0.
|
|
108
|
+
"@k2b/ui": "0.22.0",
|
|
107
109
|
"@k2b/stdlib": "0.28.0",
|
|
108
110
|
"@k2b/sync": "7.0.0",
|
|
109
111
|
"@nats-io/jetstream": "3.4.0",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { AppMeta, AppPresentationCatalog, AppPresentationTranslation } from "../contracts/app";
|
|
1
|
+
import type { AppMeta, AppPresentationCatalog, AppPresentationTranslation, AppWidgetTranslation } from "../contracts/app";
|
|
2
|
+
import { WIDGET_DESCRIPTION_MAX_LENGTH, WIDGET_TITLE_MAX_LENGTH } from "../contracts/widgets";
|
|
2
3
|
import { canonicalLocale } from "../shared/locale";
|
|
3
4
|
|
|
4
5
|
export const APP_PRESENTATION_MAX_BYTES = 64 * 1024;
|
|
@@ -21,6 +22,24 @@ const labels = (values: Readonly<Record<string, string>> | undefined, allowed: S
|
|
|
21
22
|
return normalized;
|
|
22
23
|
};
|
|
23
24
|
|
|
25
|
+
const widgetTexts = (
|
|
26
|
+
values: Readonly<Record<string, AppWidgetTranslation>>,
|
|
27
|
+
allowed: Set<string>,
|
|
28
|
+
label: string,
|
|
29
|
+
): Record<string, AppWidgetTranslation> => {
|
|
30
|
+
const normalized: Record<string, AppWidgetTranslation> = {};
|
|
31
|
+
for (const [id, value] of Object.entries(values)) {
|
|
32
|
+
if (!allowed.has(id)) throw new Error(`${label} contains unknown key ${JSON.stringify(id)}`);
|
|
33
|
+
normalized[id] = {
|
|
34
|
+
...(value.title !== undefined ? { title: text(value.title, `${label}.${id}.title`, WIDGET_TITLE_MAX_LENGTH) } : {}),
|
|
35
|
+
...(value.description !== undefined
|
|
36
|
+
? { description: text(value.description, `${label}.${id}.description`, WIDGET_DESCRIPTION_MAX_LENGTH) }
|
|
37
|
+
: {}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return normalized;
|
|
41
|
+
};
|
|
42
|
+
|
|
24
43
|
/** Validate and canonicalize one static app-presentation catalog before it enters the registry. */
|
|
25
44
|
export const compileAppPresentation = (app: AppMeta, catalog: AppPresentationCatalog | undefined): AppPresentationCatalog | undefined => {
|
|
26
45
|
if (!catalog) return undefined;
|
|
@@ -36,6 +55,7 @@ export const compileAppPresentation = (app: AppMeta, catalog: AppPresentationCat
|
|
|
36
55
|
const adminHrefs = new Set((app.adminNav ?? []).flatMap((group) => group.links.map((link) => link.href)));
|
|
37
56
|
const searchHrefs = new Set((app.searchLinks ?? []).map((link) => link.href));
|
|
38
57
|
const legalHrefs = new Set((app.legalLinks ?? []).map((link) => link.href));
|
|
58
|
+
const widgetIds = new Set((app.widgets ?? []).map((widget) => widget.id));
|
|
39
59
|
const translations: Record<string, AppPresentationTranslation> = {};
|
|
40
60
|
|
|
41
61
|
for (const [locale, translation] of Object.entries(catalog.translations)) {
|
|
@@ -62,6 +82,7 @@ export const compileAppPresentation = (app: AppMeta, catalog: AppPresentationCat
|
|
|
62
82
|
}
|
|
63
83
|
: {}),
|
|
64
84
|
...(translation.legalLinks ? { legalLinks: labels(translation.legalLinks, legalHrefs, `${canonical}.legalLinks`) } : {}),
|
|
85
|
+
...(translation.widgets ? { widgets: widgetTexts(translation.widgets, widgetIds, `${canonical}.widgets`) } : {}),
|
|
65
86
|
};
|
|
66
87
|
}
|
|
67
88
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Types only: the shell evaluates this on every page and must not load the account schemas.
|
|
2
|
+
import type { AppRegistryNav } from "../contracts/registry";
|
|
3
|
+
import type { Role, User } from "../contracts/shared";
|
|
4
|
+
|
|
5
|
+
/** An app's declared roles: absent roles admit everyone, and `guest` matches the guest profile. */
|
|
6
|
+
export const hasAnyAppRole = (user: User | undefined, roles: readonly Role[] | undefined): boolean =>
|
|
7
|
+
!roles || (!!user && roles.some((role) => (role === "guest" ? user.profile === "guest" : user.roles.includes(role))));
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Who may see an app's Help and Skills: its navigation roles, or administrators for an app reached only through the
|
|
11
|
+
* admin area (`adminHref` without its own `href`). `undefined` admits every signed-in user.
|
|
12
|
+
*/
|
|
13
|
+
export const appAudienceRoles = (nav: AppRegistryNav | undefined): readonly Role[] | undefined =>
|
|
14
|
+
nav?.requiresRoles ?? (nav && !nav.href && nav.adminHref ? ["admin"] : undefined);
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type CapabilityActionManifest,
|
|
11
11
|
type CapabilityActionReviewResult,
|
|
12
12
|
CapabilityActionReviewSchema,
|
|
13
|
+
type CapabilityActionSentences,
|
|
13
14
|
type CapabilityCommandDefinition,
|
|
14
15
|
type CapabilityCommandManifest,
|
|
15
16
|
CapabilityCommandManifestSchema,
|
|
@@ -38,6 +39,14 @@ import {
|
|
|
38
39
|
} from "../contracts/capabilities";
|
|
39
40
|
import { fileProviderIssues } from "../contracts/file-provider";
|
|
40
41
|
import { canonicalLocale, localeFallbackChain, normalizeLocale } from "../shared/locale";
|
|
42
|
+
import {
|
|
43
|
+
CAPABILITY_SENTENCE_KEYS,
|
|
44
|
+
CAPABILITY_SENTENCE_MAX_CHARS,
|
|
45
|
+
type CapabilityActionWording,
|
|
46
|
+
capabilitySentenceFields,
|
|
47
|
+
parseCapabilitySentence,
|
|
48
|
+
resolveCapabilityActionSentences,
|
|
49
|
+
} from "./capability-sentences";
|
|
41
50
|
|
|
42
51
|
type JsonSchema = Record<string, unknown>;
|
|
43
52
|
const MAX_CAPABILITY_MANIFEST_BYTES = 256 * 1024;
|
|
@@ -274,6 +283,56 @@ const compileSchemaPresentation = (
|
|
|
274
283
|
return Object.fromEntries(entries);
|
|
275
284
|
};
|
|
276
285
|
|
|
286
|
+
const isAction = (
|
|
287
|
+
operation: CapabilityQueryManifest | CapabilityActionManifest | CapabilityCommandManifest,
|
|
288
|
+
): operation is CapabilityActionManifest => "idempotency" in operation;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Checks one Action's sentences against its schemas: every placeholder names a field the Action declares,
|
|
292
|
+
* and only `done` reads result data, because the other sentences are shown before or instead of a result.
|
|
293
|
+
*/
|
|
294
|
+
export const compileActionSentences = (
|
|
295
|
+
value: unknown,
|
|
296
|
+
operation: Pick<CapabilityActionManifest, "inputSchema" | "dataSchema">,
|
|
297
|
+
label: string,
|
|
298
|
+
strict = true,
|
|
299
|
+
): CapabilityActionSentences => {
|
|
300
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
|
|
301
|
+
const copy = value as Record<string, unknown>;
|
|
302
|
+
const extra = Object.keys(copy).find((key) => !(CAPABILITY_SENTENCE_KEYS as readonly string[]).includes(key));
|
|
303
|
+
if (strict && extra) throw new Error(`${label} contains unsupported field "${extra}"`);
|
|
304
|
+
const paths = { input: schemaFieldPaths(operation.inputSchema), data: schemaFieldPaths(operation.dataSchema) };
|
|
305
|
+
const sentences: Record<string, string> = {};
|
|
306
|
+
for (const key of CAPABILITY_SENTENCE_KEYS) {
|
|
307
|
+
const template = copy[key];
|
|
308
|
+
if (template === undefined) continue;
|
|
309
|
+
if (typeof template !== "string") throw new Error(`${label}.${key} must be text`);
|
|
310
|
+
assertText(template, `${label}.${key}`, CAPABILITY_SENTENCE_MAX_CHARS);
|
|
311
|
+
const parsed = parseCapabilitySentence(template.trim());
|
|
312
|
+
if ("error" in parsed) throw new Error(`${label}.${key} ${parsed.error}`);
|
|
313
|
+
for (const part of parsed.parts) {
|
|
314
|
+
if (typeof part === "string") continue;
|
|
315
|
+
if (part.source === "data" && key !== "done") throw new Error(`${label}.${key} may read {data.${part.path}} only in done`);
|
|
316
|
+
if (!paths[part.source].has(part.path)) {
|
|
317
|
+
throw new Error(`${label}.${key} placeholder {${part.source}.${part.path}} does not exist in the projected ${part.source} schema`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
sentences[key] = template.trim();
|
|
321
|
+
}
|
|
322
|
+
if (Object.keys(sentences).length === 0) throw new Error(`${label} needs at least one sentence`);
|
|
323
|
+
return sentences;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
/** A reader keeps an entry's other copy when only its sentences come from a newer or broken producer. */
|
|
327
|
+
const readActionSentences = (value: unknown, operation: CapabilityActionManifest, label: string, strict: boolean) => {
|
|
328
|
+
if (strict) return compileActionSentences(value, operation, label, strict);
|
|
329
|
+
try {
|
|
330
|
+
return compileActionSentences(value, operation, label, strict);
|
|
331
|
+
} catch {
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
277
336
|
const compileOperationPresentation = (
|
|
278
337
|
value: unknown,
|
|
279
338
|
operation: CapabilityQueryManifest | CapabilityActionManifest | CapabilityCommandManifest,
|
|
@@ -283,7 +342,9 @@ const compileOperationPresentation = (
|
|
|
283
342
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
|
|
284
343
|
const translation = value as Record<string, unknown>;
|
|
285
344
|
const allowed = new Set(
|
|
286
|
-
"path" in operation
|
|
345
|
+
"path" in operation
|
|
346
|
+
? ["title", "description", "input"]
|
|
347
|
+
: ["title", "description", "input", "data", "searchTags", ...(isAction(operation) ? ["sentences"] : [])],
|
|
287
348
|
);
|
|
288
349
|
const extra = Object.keys(translation).find((key) => !allowed.has(key));
|
|
289
350
|
if (strict && extra) throw new Error(`${label} contains unsupported field "${extra}"`);
|
|
@@ -330,12 +391,17 @@ const compileOperationPresentation = (
|
|
|
330
391
|
}
|
|
331
392
|
const input = compileSchemaPresentation(translation.input, operation.inputSchema, `${label}.input`);
|
|
332
393
|
const data = "dataSchema" in operation ? compileSchemaPresentation(translation.data, operation.dataSchema, `${label}.data`) : undefined;
|
|
394
|
+
const sentences =
|
|
395
|
+
translation.sentences !== undefined && isAction(operation)
|
|
396
|
+
? readActionSentences(translation.sentences, operation, `${label}.sentences`, strict)
|
|
397
|
+
: undefined;
|
|
333
398
|
return {
|
|
334
399
|
...(typeof translation.title === "string" ? { title: translation.title.trim() } : {}),
|
|
335
400
|
...(typeof translation.description === "string" ? { description: translation.description.trim() } : {}),
|
|
336
401
|
...(input ? { input } : {}),
|
|
337
402
|
...(data ? { data } : {}),
|
|
338
403
|
...(searchTags ? { searchTags } : {}),
|
|
404
|
+
...(sentences ? { sentences } : {}),
|
|
339
405
|
};
|
|
340
406
|
};
|
|
341
407
|
|
|
@@ -353,7 +419,7 @@ export const compileCapabilityPresentation = (
|
|
|
353
419
|
if (value === undefined) return undefined;
|
|
354
420
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Capability presentation must be an object");
|
|
355
421
|
const catalog = value as Record<string, unknown>;
|
|
356
|
-
if (strict && Object.keys(catalog).some((key) => key !== "baseLocale" && key !== "translations")) {
|
|
422
|
+
if (strict && Object.keys(catalog).some((key) => key !== "baseLocale" && key !== "sentences" && key !== "translations")) {
|
|
357
423
|
throw new Error("Capability presentation contains unsupported fields");
|
|
358
424
|
}
|
|
359
425
|
const baseLocale = typeof catalog.baseLocale === "string" ? canonicalLocale(catalog.baseLocale) : undefined;
|
|
@@ -365,6 +431,23 @@ export const compileCapabilityPresentation = (
|
|
|
365
431
|
const queries = new Map(manifest.queries.map((operation) => [operation.localId, operation]));
|
|
366
432
|
const actions = new Map(manifest.actions.map((operation) => [operation.localId, operation]));
|
|
367
433
|
const commands = new Map(manifest.commands.map((operation) => [operation.localId, operation]));
|
|
434
|
+
let sentences: Record<string, CapabilityActionSentences> | undefined;
|
|
435
|
+
if (catalog.sentences !== undefined) {
|
|
436
|
+
if (!catalog.sentences || typeof catalog.sentences !== "object" || Array.isArray(catalog.sentences)) {
|
|
437
|
+
throw new Error("Capability presentation sentences must be an object");
|
|
438
|
+
}
|
|
439
|
+
sentences = Object.fromEntries(
|
|
440
|
+
Object.entries(catalog.sentences as Record<string, unknown>).flatMap(([localId, entry]) => {
|
|
441
|
+
const action = actions.get(localId);
|
|
442
|
+
if (!action) {
|
|
443
|
+
if (strict) throw new Error(`Action sentences reference unknown localId "${localId}"`);
|
|
444
|
+
return [];
|
|
445
|
+
}
|
|
446
|
+
const compiled = readActionSentences(entry, action, `Action ${localId}.sentences`, strict);
|
|
447
|
+
return compiled ? [[localId, compiled]] : [];
|
|
448
|
+
}),
|
|
449
|
+
);
|
|
450
|
+
}
|
|
368
451
|
const translations: Record<string, CapabilityPresentationTranslation> = {};
|
|
369
452
|
for (const [locale, rawTranslation] of Object.entries(catalog.translations as Record<string, unknown>)) {
|
|
370
453
|
const canonical = canonicalLocale(locale);
|
|
@@ -431,7 +514,7 @@ export const compileCapabilityPresentation = (
|
|
|
431
514
|
...(translatedCommands ? { commands: translatedCommands } : {}),
|
|
432
515
|
};
|
|
433
516
|
}
|
|
434
|
-
return { baseLocale, translations };
|
|
517
|
+
return { baseLocale, ...(sentences && Object.keys(sentences).length > 0 ? { sentences } : {}), translations };
|
|
435
518
|
};
|
|
436
519
|
|
|
437
520
|
const projectSchema = (schema: z.ZodType, label: string, io: "input" | "output"): JsonSchema => {
|
|
@@ -798,6 +881,31 @@ export const resolveCapabilityOperationTitle = (
|
|
|
798
881
|
operation.title,
|
|
799
882
|
);
|
|
800
883
|
|
|
884
|
+
/**
|
|
885
|
+
* Everything a reader needs to word calls of one Action in the requested locale: its title, the app's
|
|
886
|
+
* sentences, and the labelled fields of the generic sentence. Derived only from the live manifest and
|
|
887
|
+
* presentation catalog, the same way for every app.
|
|
888
|
+
*/
|
|
889
|
+
export const resolveCapabilityActionWording = (
|
|
890
|
+
operation: CapabilityActionManifest,
|
|
891
|
+
catalog: CapabilityPresentationCatalog | undefined,
|
|
892
|
+
requestedLocale: string,
|
|
893
|
+
): CapabilityActionWording => {
|
|
894
|
+
const overlays = catalog ? presentationOverlays(catalog, requestedLocale) : [];
|
|
895
|
+
const inputDescriptions = Object.assign({}, ...overlays.map((translation) => translation.actions?.[operation.localId]?.input ?? {}));
|
|
896
|
+
const sentences = resolveCapabilityActionSentences(operation.localId, catalog, requestedLocale);
|
|
897
|
+
return {
|
|
898
|
+
title: resolveCapabilityOperationTitle(operation, "actions", catalog, requestedLocale),
|
|
899
|
+
...(sentences ? { sentences } : {}),
|
|
900
|
+
fields: capabilitySentenceFields({
|
|
901
|
+
inputSchema: operation.inputSchema,
|
|
902
|
+
dataSchema: operation.dataSchema,
|
|
903
|
+
sentences,
|
|
904
|
+
inputDescriptions,
|
|
905
|
+
}),
|
|
906
|
+
};
|
|
907
|
+
};
|
|
908
|
+
|
|
801
909
|
/** Resolve only human presentation; stable IDs, flags, tags, aliases, and data shapes stay untouched. */
|
|
802
910
|
export const resolveCapabilityManifestPresentation = (
|
|
803
911
|
manifest: CapabilityManifest,
|
|
@@ -1361,9 +1469,9 @@ export const reviewCompiledCapability = async (params: {
|
|
|
1361
1469
|
return normalizeProviderError(reviewed.error, "Capability review returned an invalid error", params.onUnexpectedError);
|
|
1362
1470
|
}
|
|
1363
1471
|
const parsed = CapabilityActionReviewSchema.safeParse(reviewed.data);
|
|
1472
|
+
// A rememberable Action may leave out its scope for arguments that should ask every time; any other Action has none.
|
|
1364
1473
|
const approvalScopeIsValid =
|
|
1365
|
-
parsed.success &&
|
|
1366
|
-
(operation.manifest.approval === "rememberable" ? parsed.data.approvalScope !== undefined : parsed.data.approvalScope === undefined);
|
|
1474
|
+
parsed.success && (operation.manifest.approval === "rememberable" || parsed.data.approvalScope === undefined);
|
|
1367
1475
|
if (!parsed.success || !approvalScopeIsValid) {
|
|
1368
1476
|
try {
|
|
1369
1477
|
params.onUnexpectedError?.(
|