@noodleseed/one 0.141.2 → 0.142.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 (52) hide show
  1. package/dist/commands/assistant-appearance-ops.d.ts +3 -0
  2. package/dist/commands/assistant-appearance-ops.d.ts.map +1 -0
  3. package/dist/commands/assistant-appearance-ops.js +148 -0
  4. package/dist/commands/assistant-appearance-ops.js.map +1 -0
  5. package/dist/commands/assistant-ops.d.ts.map +1 -1
  6. package/dist/commands/assistant-ops.js +4 -1
  7. package/dist/commands/assistant-ops.js.map +1 -1
  8. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +3 -3
  9. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +25 -18
  10. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-appearance-store.d.ts +34 -0
  11. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-appearance-store.js +12 -0
  12. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-appearance.d.ts +18 -0
  13. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-appearance.js +228 -0
  14. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-configuration.d.ts +31 -0
  15. package/node_modules/@noodle-borg/{service/dist/routes → assistant-gateway/dist}/assistant-configuration.js +12 -1
  16. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-delegated-projection.d.ts +12 -0
  17. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.d.ts +1 -1
  18. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.js +1 -0
  19. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-view-availability.d.ts +38 -0
  20. package/node_modules/@noodle-borg/assistant-gateway/dist/embed-store.d.ts +3 -0
  21. package/node_modules/@noodle-borg/assistant-gateway/dist/embed-store.js +4 -0
  22. package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-assistant-appearance-store.d.ts +8 -0
  23. package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-assistant-appearance-store.js +26 -0
  24. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +7 -0
  25. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +7 -0
  26. package/node_modules/@noodle-borg/assistant-gateway/dist/public-configuration.d.ts +22 -0
  27. package/node_modules/@noodle-borg/assistant-gateway/dist/public-configuration.js +25 -0
  28. package/node_modules/@noodle-borg/assistant-gateway/package.json +3 -1
  29. package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +6 -2
  30. package/node_modules/@noodle-borg/authoring/dist/react/components.d.ts +3 -3
  31. package/node_modules/@noodle-borg/authoring/dist/react/native-components.d.ts +2 -2
  32. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-assistant.js +38 -0
  33. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +56 -4
  34. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +25 -47
  35. package/node_modules/@noodle-borg/service/dist/credential-broker.js +1 -1
  36. package/node_modules/@noodle-borg/service/dist/invocation-context.js +1 -2
  37. package/node_modules/@noodle-borg/service/dist/local-options.d.ts +1 -0
  38. package/node_modules/@noodle-borg/service/dist/registry-compile.js +1 -1
  39. package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +1 -3
  40. package/node_modules/@noodle-borg/service/dist/routes/assistant-appearance.js +104 -0
  41. package/node_modules/@noodle-borg/service/dist/routes/assistant-dispatch.js +23 -0
  42. package/node_modules/@noodle-borg/service/dist/routes/assistant-interaction-replay.js +1 -2
  43. package/node_modules/@noodle-borg/service/dist/routes/assistant-interactions.js +1 -2
  44. package/node_modules/@noodle-borg/service/dist/routes/assistant-public-configuration.js +41 -0
  45. package/node_modules/@noodle-borg/service/dist/routes/assistant-public-session.js +6 -11
  46. package/node_modules/@noodle-borg/service/dist/routes/assistant.js +2 -3
  47. package/node_modules/@noodle-borg/service/dist/service.js +3 -1
  48. package/node_modules/@noodleseed/assistant/README.md +54 -20
  49. package/node_modules/@noodleseed/assistant/package.json +1 -1
  50. package/package.json +1 -1
  51. /package/node_modules/@noodle-borg/{service → assistant-gateway}/dist/assistant-delegated-projection.js +0 -0
  52. /package/node_modules/@noodle-borg/{service → assistant-gateway}/dist/assistant-view-availability.js +0 -0
@@ -56,7 +56,7 @@ export function renderEmbeddedAssistantReference() {
56
56
  ' access: authenticatedWebsite({',
57
57
  ' origins: ["http://localhost:3000", "https://app.example.com"],',
58
58
  ' }),',
59
- ' layout: { mode: "floating", position: "bottom-right" },',
59
+ ' layout: { mode: "floating", position: "bottom-center" },',
60
60
  '}),',
61
61
  '```',
62
62
  '',
@@ -145,13 +145,15 @@ export function renderEmbeddedAssistantReference() {
145
145
  '',
146
146
  '```ts',
147
147
  'assistant: embeddedAssistant({',
148
- ' model, allowedOrigins,',
149
- ' layout: { panelWidth: 520, panelMinHeight: 540, panelMaxHeight: 740, edgeOffset: 24 },',
150
- ' behavior: { showTimestamps: true },',
151
- ' labels: { composerPlaceholder: "Message Acme Support…", sessionReady: "Acme support is online" },',
148
+ ' model,',
149
+ ' access: authenticatedWebsite({ origins: ["https://app.example.com"] }),',
150
+ ' theme: "invert",',
151
+ ' layout: { position: "bottom-right", panelWidth: 520, panelMinHeight: 540, panelMaxHeight: 740, edgeOffset: 24 },',
152
+ ' behavior: { showTimestamps: true, showPoweredBy: false },',
153
+ ' labels: { launcherPlaceholder: "Ask Acme anything", composerPlaceholder: "Message Acme Support…", sessionReady: "Acme support is online" },',
152
154
  ' presentation: {',
153
155
  ' panel: { surface: "solid", elevation: "dramatic", border: "strong", radius: 20 },',
154
- ' launcher: { icon: "chat", size: "lg", status: "session", effect: "pulse" },',
156
+ ' launcher: { style: "bubble", icon: "chat", size: "lg", status: "session", effect: "pulse" },',
155
157
  ' header: {',
156
158
  ' mark: "status",',
157
159
  ' badge: { text: "Online", tone: "success", indicator: true },',
@@ -162,9 +164,9 @@ export function renderEmbeddedAssistantReference() {
162
164
  '}),',
163
165
  '```',
164
166
  '',
165
- 'The Atlas-style product treatment above is the maximum deployment-configurable presentation. The bounded surface covers panel treatment, launcher icon/size/session pulse, header mark/status badge, composer controls, and message treatment; it does not accept custom header actions, structured empty-state layouts, footers, spectacle variants/effects, or tenant code.',
167
+ 'The Atlas-style product treatment above is the maximum deployment-configurable presentation. The bounded surface covers panel treatment, pill/bubble launcher style plus icon/size/session pulse, header mark/status badge, composer controls, and message treatment; it does not accept custom header actions, structured empty-state layouts, footers, tenant-defined launcher variants/effects, or tenant code.',
166
168
  '',
167
- 'Omitted fields retain the quiet premium baseline. For exact application-owned color roles, pass the typed React `appearance={{ light: { panel: { surface, text, border }, composer: {...}, confirmation: {...}, primaryButton: {...} }, dark: {...} }}` prop or assign the same object to `element.appearance`. CSS custom properties inherit through the assistant host, so those values may reuse existing application tokens such as `surface: "var(--app-surface)"` without copying literals. The appearance surface covers canvas, panel, header, messages, composer, suggestions, confirmation, buttons, launcher, code, and the MCP App frame; the package README publishes the complete role-to-`--ns-assistant-*` map. Exact parseable literal colors are preserved and low contrast emits `assistant-appearance-warning`; contrast for unresolved CSS references remains host-owned. Precedence is host appearance object, host slots/public variables, deployed semantic presentation, then defaults. Prefer `server.ts` configuration first so every embedding app receives the same assistant after redeploy.',
169
+ 'Omitted UI fields retain the complete managed baseline: a bottom-center frosted prompt pill, 970px outer desktop shell with 20px side padding, 85vh/1025px height bounds, 24px panel with built-in `#F8F8F8` light and `#0C0A09` dark surfaces, bottom prompt chips and pill composer, plain assistant messages, 85%-wide user bubbles, Noodle Seed attribution, and mobile fullscreen. The pill morphs into an input before opening; `launcher.style: "bubble"` opens directly, while `panel.surface: "glass"` remains an explicit translucent alternative. `theme: "auto"` follows the host page and `"invert"` selects its opposite. The generic suggestions are defaults; an authored list, including `[]`, replaces them. The ChatGPT promotion is intentionally absent. For exact application-owned color roles, pass the typed React `appearance={{ light: { panel: { surface, text, border }, composer: {...}, confirmation: {...}, primaryButton: {...} }, dark: {...} }}` prop or assign the same object to `element.appearance`. CSS custom properties inherit through the assistant host, so those values may reuse existing application tokens such as `surface: "var(--app-surface)"` without copying literals. The appearance surface covers canvas, panel, header, messages, composer, suggestions, confirmation, buttons, launcher, code, and the MCP App frame; the package README publishes the complete role-to-`--ns-assistant-*` map. Exact parseable literal colors are preserved and low contrast emits `assistant-appearance-warning`; contrast for unresolved CSS references remains host-owned. Precedence is host appearance object, host slots/public variables, saved environment operator override, deployed semantic presentation, then defaults. Prefer reusable `server.ts` defaults; use the Console Assistant tab or `noodle assistant appearance show|apply|reset` for environment-owned changes that should reach existing embeds without a redeploy.',
168
170
  '',
169
171
  'Give every business action a portable `tool(..., { title: "Complete task", description: "This will mark the task complete for everyone.", input: z.object({ task: z.string().meta({ title: "Task" }) }) })` title. The standard confirmation uses the tool title/description plus schema field `title`, `description`, and `format`; it shows Confirm and Don\'t proceed and keeps technical action details secondary. Do not put JSON or implementation names in business-facing copy.',
170
172
  '',
@@ -322,12 +324,15 @@ export function renderEmbeddedAssistantReference() {
322
324
  '',
323
325
  '```ts',
324
326
  'assistant: embeddedAssistant({',
325
- ' model, allowedOrigins,',
326
- ' sessionClaims: {',
327
- ' displayName: { exposeToModel: true },',
328
- ' accountTier: { exposeToModel: true },',
329
- ' region: {}, // tools only, never in the prompt',
330
- ' },',
327
+ ' model,',
328
+ ' access: authenticatedWebsite({',
329
+ ' origins: ["https://app.example.com"],',
330
+ ' sessionClaims: {',
331
+ ' displayName: { exposeToModel: true },',
332
+ ' accountTier: { exposeToModel: true },',
333
+ ' region: {}, // tools only, never in the prompt',
334
+ ' },',
335
+ ' }),',
331
336
  '}),',
332
337
  '```',
333
338
  '',
@@ -383,7 +388,9 @@ export function renderEmbeddedAssistantReference() {
383
388
  '',
384
389
  'Or import the package root once and mount `<noodle-assistant session-endpoint="/api/assistant/session" theme="auto"></noodle-assistant>`. Mount only inside the authenticated application surface.',
385
390
  '',
386
- '`theme="auto"` follows the browser operating-system preference. If the SaaS application owns a theme toggle, obtain its resolved application theme (`"light"` or `"dark"`), pass `theme={resolvedTheme}` to `NoodleAssistant`, and update the custom element\'s `theme` attribute when that value changes.',
391
+ 'That custom element is the complete managed assistant in Vue, Angular, or plain DOM; it has no React runtime requirement. Configure the framework to accept `noodle-assistant` as a custom element. If the session exchange needs an authenticated fetch wrapper, create the element imperatively, assign `element.fetch` and then `element.sessionEndpoint`, and append it only after both properties are set.',
392
+ '',
393
+ '`theme="auto"` follows an explicit host-page light/dark class or data attribute, then the browser operating-system preference; `theme="invert"` selects the opposite. If the SaaS application owns a theme toggle, obtain its resolved application theme (`"light"` or `"dark"`), pass `theme={resolvedTheme}` to `NoodleAssistant`, and update the custom element\'s `theme` attribute when that value changes.',
387
394
  '',
388
395
  'The component renders a custom element and must mount client-side. In a Next.js App Router tree, put the mount in a `"use client"` component; from a server component or the Pages Router, load it with `next/dynamic` and `ssr: false`.',
389
396
  '',
@@ -638,7 +645,7 @@ export function renderEmbeddedAssistantReference() {
638
645
  '',
639
646
  '- Signed-out session exchange returns `401`.',
640
647
  '- The browser network/DOM/storage contains no client secret or model key.',
641
- '- The local and production origins match `allowedOrigins` character-for-character.',
648
+ '- The local and production origins match the authored `access.origins` character-for-character.',
642
649
  '- At the manifest/runtime boundary and in TypeScript action helpers, only `confirm: true` enables confirmation; omitted or `false` preserves direct execution. Action hints alone never enforce approval; `annotations.action({ confirm: false })` is equivalent to omission.',
643
650
  '- An expired turn re-exchanges once; interaction decisions never auto-retry. An explicit same-decision repeat returns the stored outcome without executing again.',
644
651
  '- Accept, decline, and cancel are single-use. Only accept executes; the server ignores replacement tool arguments.',
@@ -656,9 +663,9 @@ export function renderEmbeddedAssistantReference() {
656
663
  '| `assistant-error` with code `invalid_response` | The turn endpoint returned HTML or non-SSE content (auth redirect, proxy page) | Check the backend session route path and any middleware/rewrites on the embedding app |',
657
664
  '| Build error `Package path ./react is not exported` | Outdated package version with import-only export conditions | Update `@noodleseed/assistant`; do not add webpack aliases or type shims |',
658
665
  '| Deploy fails with `server_auth_required` | `--access customers` without `server.auth` | Add direct/federated OIDC or a built-in Firebase/Microsoft adapter |',
659
- '| Validate rejects an origin | Non-loopback HTTP origin in `allowedOrigins` | Use the exact HTTPS production origin; HTTP is only for `localhost`/`127.0.0.1` |',
666
+ '| Validate rejects an origin | Non-loopback HTTP origin in `access.origins` | Use the exact HTTPS production origin; HTTP is only for `localhost`/`127.0.0.1` |',
660
667
  '| Session exchange returns 404 | `serviceUrl` points at the deployment MCP endpoint | Use the control-plane service URL printed by `noodle assistant clients create` |',
661
- '| Session exchange returns 403 `origin is not allowed` | Request origin differs from `allowedOrigins` character-for-character | Align the exact scheme/host/port on both sides and redeploy |',
668
+ '| Session exchange returns 403 `origin is not allowed` | Request origin differs from the authored `access.origins` character-for-character | Align the exact scheme/host/port on both sides and redeploy |',
662
669
  '| Session exchange returns `400` with `invalid assistant routing` | The authenticated backend supplied an unknown endpoint name or a malformed/policy-disallowed URL | Resolve the route from server-owned membership, use the exact authored endpoint name, and ensure the canonical HTTPS URL satisfies its active `customerEndpoint` policy; the error never reflects the URL |',
663
670
  '| A routed assistant tool returns `connector_route_unavailable` | The authenticated backend omitted that endpoint during session exchange | Pass the server-verified route as `routing.endpoints.<name>` when minting a new session; keep it out of browser input |',
664
671
  '| Host session 503 | A required backend environment name is absent or mapped into the wrong deployment environment | Run `noodle assistant embed --check --json`, repair the host CI mapping, then probe the session route again |',
@@ -0,0 +1,34 @@
1
+ import type { AssistantAppearanceOverride } from './assistant-appearance.js';
2
+ import type { TenantRef } from './tenant-ref.js';
3
+ export interface AssistantAppearanceSettingsRecord {
4
+ readonly tenant: TenantRef;
5
+ /** Undefined is a durable reset tombstone, not a missing row. */
6
+ readonly override?: AssistantAppearanceOverride;
7
+ readonly revision: number;
8
+ readonly updatedAt: Date;
9
+ readonly updatedBy: string;
10
+ }
11
+ export type AssistantAppearanceReplaceResult = {
12
+ readonly ok: true;
13
+ readonly record: AssistantAppearanceSettingsRecord;
14
+ } | {
15
+ readonly ok: false;
16
+ readonly currentRevision: number;
17
+ };
18
+ export interface AssistantAppearanceSettingsStore {
19
+ get(tenant: TenantRef): Promise<AssistantAppearanceSettingsRecord | undefined>;
20
+ /**
21
+ * Full replacement with compare-and-swap. Revision zero means no row has ever been written;
22
+ * `override: undefined` records a reset without reopening stale-write races.
23
+ */
24
+ replace(input: {
25
+ readonly tenant: TenantRef;
26
+ readonly expectedRevision: number;
27
+ readonly override: AssistantAppearanceOverride | undefined;
28
+ readonly updatedAt: Date;
29
+ readonly updatedBy: string;
30
+ }): Promise<AssistantAppearanceReplaceResult>;
31
+ }
32
+ export declare function appearanceTenantKey(tenant: TenantRef): string;
33
+ export declare function cloneAssistantAppearanceSettingsRecord(record: AssistantAppearanceSettingsRecord): AssistantAppearanceSettingsRecord;
34
+ //# sourceMappingURL=assistant-appearance-store.d.ts.map
@@ -0,0 +1,12 @@
1
+ export function appearanceTenantKey(tenant) {
2
+ return `${tenant.org}/${tenant.app}/${tenant.env}`;
3
+ }
4
+ export function cloneAssistantAppearanceSettingsRecord(record) {
5
+ return {
6
+ ...record,
7
+ tenant: { ...record.tenant },
8
+ ...(record.override === undefined ? {} : { override: structuredClone(record.override) }),
9
+ updatedAt: new Date(record.updatedAt),
10
+ };
11
+ }
12
+ //# sourceMappingURL=assistant-appearance-store.js.map
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod';
2
+ import type { AssistantSessionRecord } from './assistant-store.js';
3
+ /**
4
+ * Browser-safe environment override. It deliberately has no model, origin, capability, claim, HTML,
5
+ * CSS, callback, or arbitrary-token field: appearance cannot grow assistant authority.
6
+ */
7
+ export type AssistantAppearanceOverride = NonNullable<AssistantSessionRecord['configuration']>;
8
+ export declare const assistantAppearanceOverrideSchema: z.ZodType<AssistantAppearanceOverride>;
9
+ export type AssistantAppearanceProvenance = Readonly<Record<string, 'developer' | 'operator'>>;
10
+ export interface ResolvedAssistantAppearanceConfiguration {
11
+ readonly effective: AssistantAppearanceOverride | undefined;
12
+ readonly fallback: 'halo';
13
+ /** Dot paths not present here inherit the renderer-owned built-in baseline. */
14
+ readonly provenance: AssistantAppearanceProvenance;
15
+ }
16
+ /** Operator values win leaf-by-leaf; arrays replace and omitted fields retain developer intent. */
17
+ export declare function resolveAssistantAppearanceConfiguration(developer: AssistantAppearanceOverride | undefined, operator: AssistantAppearanceOverride | undefined): ResolvedAssistantAppearanceConfiguration;
18
+ //# sourceMappingURL=assistant-appearance.d.ts.map
@@ -0,0 +1,228 @@
1
+ import { z } from 'zod';
2
+ const hexColorSchema = z.string().regex(/^#[0-9A-Fa-f]{6}$/, 'must be a 6-digit hex color');
3
+ const browserAssetUrlSchema = z
4
+ .string()
5
+ .trim()
6
+ .url()
7
+ .refine(isSecureBrowserAssetUrl, 'must use https (http is allowed only for loopback testing)');
8
+ const brandAssetSchema = z
9
+ .object({
10
+ uri: browserAssetUrlSchema,
11
+ darkUri: browserAssetUrlSchema.optional(),
12
+ alt: z.string().trim().min(1).max(240),
13
+ })
14
+ .strict();
15
+ const brandThemeSchema = z
16
+ .object({
17
+ surface: hexColorSchema.optional(),
18
+ surfaceRaised: hexColorSchema.optional(),
19
+ surfaceMuted: hexColorSchema.optional(),
20
+ text: hexColorSchema.optional(),
21
+ textMuted: hexColorSchema.optional(),
22
+ accent: hexColorSchema.optional(),
23
+ accentText: hexColorSchema.optional(),
24
+ link: hexColorSchema.optional(),
25
+ border: hexColorSchema.optional(),
26
+ borderStrong: hexColorSchema.optional(),
27
+ focus: hexColorSchema.optional(),
28
+ success: hexColorSchema.optional(),
29
+ warning: hexColorSchema.optional(),
30
+ danger: hexColorSchema.optional(),
31
+ code: hexColorSchema.optional(),
32
+ })
33
+ .strict();
34
+ const brandingSchema = z
35
+ .object({
36
+ name: z.string().trim().min(1).max(160).optional(),
37
+ accent: hexColorSchema.optional(),
38
+ surface: hexColorSchema.optional(),
39
+ surfaceDark: hexColorSchema.optional(),
40
+ logo: brandAssetSchema.optional(),
41
+ mark: brandAssetSchema.optional(),
42
+ avatar: brandAssetSchema.optional(),
43
+ theme: z
44
+ .object({ light: brandThemeSchema.optional(), dark: brandThemeSchema.optional() })
45
+ .strict()
46
+ .optional(),
47
+ radius: z.enum(['none', 'sm', 'md', 'lg']).optional(),
48
+ density: z.enum(['compact', 'comfortable']).optional(),
49
+ typography: z.enum(['system', 'serif', 'mono']).optional(),
50
+ colorScheme: z.enum(['auto', 'light', 'dark']).optional(),
51
+ })
52
+ .strict();
53
+ const labelsSchema = z
54
+ .object({
55
+ welcomeHeading: z.string().trim().min(1).max(160).optional(),
56
+ welcomeMessage: z.string().max(1000).optional(),
57
+ launcherPlaceholder: z.string().trim().min(1).max(160).optional(),
58
+ composerPlaceholder: z.string().trim().min(1).max(160).optional(),
59
+ thinking: z.string().trim().min(1).max(80).optional(),
60
+ send: z.string().trim().min(1).max(80).optional(),
61
+ stop: z.string().trim().min(1).max(80).optional(),
62
+ close: z.string().trim().min(1).max(80).optional(),
63
+ open: z.string().trim().min(1).max(80).optional(),
64
+ confirm: z.string().trim().min(1).max(80).optional(),
65
+ decline: z.string().trim().min(1).max(80).optional(),
66
+ cancel: z.string().trim().min(1).max(80).optional(),
67
+ confirmationHeading: z.string().trim().min(1).max(120).optional(),
68
+ additionalDetails: z.string().trim().min(1).max(120).optional(),
69
+ redacted: z.string().trim().min(1).max(120).optional(),
70
+ completed: z.string().trim().min(1).max(80).optional(),
71
+ stopped: z.string().trim().min(1).max(80).optional(),
72
+ copy: z.string().trim().min(1).max(80).optional(),
73
+ newMessages: z.string().trim().min(1).max(80).optional(),
74
+ reconnect: z.string().trim().min(1).max(80).optional(),
75
+ retry: z.string().trim().min(1).max(80).optional(),
76
+ unavailable: z.string().trim().min(1).max(240).optional(),
77
+ sessionExpired: z.string().trim().min(1).max(240).optional(),
78
+ sessionIdle: z.string().trim().min(1).max(120).optional(),
79
+ sessionLoading: z.string().trim().min(1).max(120).optional(),
80
+ sessionReady: z.string().trim().min(1).max(120).optional(),
81
+ sessionError: z.string().trim().min(1).max(120).optional(),
82
+ })
83
+ .strict();
84
+ const presentationSchema = z
85
+ .object({
86
+ panel: z
87
+ .object({
88
+ surface: z.enum(['solid', 'glass']).optional(),
89
+ elevation: z.enum(['soft', 'dramatic']).optional(),
90
+ border: z.enum(['subtle', 'strong']).optional(),
91
+ radius: z.number().int().min(0).max(64).optional(),
92
+ })
93
+ .strict()
94
+ .optional(),
95
+ launcher: z
96
+ .object({
97
+ style: z.enum(['pill', 'bubble']).optional(),
98
+ icon: z.enum(['brand-mark', 'chat', 'none']).optional(),
99
+ size: z.enum(['md', 'lg']).optional(),
100
+ status: z.enum(['none', 'session']).optional(),
101
+ effect: z.enum(['none', 'pulse']).optional(),
102
+ })
103
+ .strict()
104
+ .optional(),
105
+ header: z
106
+ .object({
107
+ mark: z.enum(['none', 'brand-mark', 'status']).optional(),
108
+ badge: z
109
+ .object({
110
+ text: z.string().trim().min(1).max(80),
111
+ tone: z.enum(['neutral', 'success', 'warning', 'danger']).optional(),
112
+ indicator: z.boolean().optional(),
113
+ })
114
+ .strict()
115
+ .optional(),
116
+ })
117
+ .strict()
118
+ .optional(),
119
+ composer: z
120
+ .object({
121
+ leadingIcon: z.enum(['none', 'brand-mark']).optional(),
122
+ sendIcon: z.enum(['arrow-up', 'paper-plane']).optional(),
123
+ shape: z.enum(['rounded', 'pill']).optional(),
124
+ })
125
+ .strict()
126
+ .optional(),
127
+ messages: z
128
+ .object({
129
+ userStyle: z.enum(['bubble', 'accent']).optional(),
130
+ assistantStyle: z.enum(['plain', 'bubble']).optional(),
131
+ })
132
+ .strict()
133
+ .optional(),
134
+ })
135
+ .strict();
136
+ const assistantUiSchema = z
137
+ .object({
138
+ theme: z.enum(['auto', 'light', 'dark', 'invert']).optional(),
139
+ layout: z
140
+ .object({
141
+ mode: z.enum(['floating', 'inline', 'drawer']).optional(),
142
+ position: z.enum(['bottom-left', 'bottom-center', 'bottom-right']).optional(),
143
+ panelWidth: z.number().int().min(280).max(1200).optional(),
144
+ panelMinHeight: z.number().int().min(240).max(1600).optional(),
145
+ panelMaxHeight: z.number().int().min(320).max(2400).optional(),
146
+ edgeOffset: z.number().int().min(0).max(96).optional(),
147
+ zIndex: z.number().int().min(0).max(2_147_483_647).optional(),
148
+ density: z.enum(['compact', 'comfortable']).optional(),
149
+ mobileFullscreen: z.boolean().optional(),
150
+ })
151
+ .strict()
152
+ .optional(),
153
+ behavior: z
154
+ .object({
155
+ startOpen: z.boolean().optional(),
156
+ closeOnEscape: z.boolean().optional(),
157
+ closeOnOutsideClick: z.boolean().optional(),
158
+ showLauncher: z.boolean().optional(),
159
+ showHeader: z.boolean().optional(),
160
+ showAvatars: z.boolean().optional(),
161
+ showTimestamps: z.boolean().optional(),
162
+ showPoweredBy: z.boolean().optional(),
163
+ })
164
+ .strict()
165
+ .optional(),
166
+ labels: labelsSchema.optional(),
167
+ presentation: presentationSchema.optional(),
168
+ suggestedPrompts: z.array(z.string().trim().min(1).max(240)).max(8).optional(),
169
+ privacyUrl: z.string().url().refine(isHttpsUrl, 'must use https').optional(),
170
+ termsUrl: z.string().url().refine(isHttpsUrl, 'must use https').optional(),
171
+ locale: z.string().trim().min(2).max(35).optional(),
172
+ direction: z.enum(['ltr', 'rtl', 'auto']).optional(),
173
+ })
174
+ .strict();
175
+ export const assistantAppearanceOverrideSchema = z
176
+ .object({ branding: brandingSchema.optional(), assistant: assistantUiSchema.optional() })
177
+ .strict()
178
+ .refine(hasLeafValue, 'appearance override must contain at least one setting');
179
+ /** Operator values win leaf-by-leaf; arrays replace and omitted fields retain developer intent. */
180
+ export function resolveAssistantAppearanceConfiguration(developer, operator) {
181
+ if (developer === undefined && operator === undefined) {
182
+ return { effective: undefined, fallback: 'halo', provenance: {} };
183
+ }
184
+ const effective = mergeObjects(developer ?? {}, operator ?? {});
185
+ const provenance = {};
186
+ collectLeafPaths(developer, '', 'developer', provenance);
187
+ collectLeafPaths(operator, '', 'operator', provenance);
188
+ return { effective, fallback: 'halo', provenance };
189
+ }
190
+ function mergeObjects(base, override) {
191
+ const merged = structuredClone(base);
192
+ for (const [key, value] of Object.entries(override)) {
193
+ const current = merged[key];
194
+ merged[key] =
195
+ isPlainObject(current) && isPlainObject(value)
196
+ ? mergeObjects(current, value)
197
+ : structuredClone(value);
198
+ }
199
+ return merged;
200
+ }
201
+ function collectLeafPaths(value, path, source, output) {
202
+ if (!isPlainObject(value)) {
203
+ if (path)
204
+ output[path] = source;
205
+ return;
206
+ }
207
+ for (const [key, child] of Object.entries(value)) {
208
+ collectLeafPaths(child, path ? `${path}.${key}` : key, source, output);
209
+ }
210
+ }
211
+ function isPlainObject(value) {
212
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
213
+ }
214
+ function hasLeafValue(value) {
215
+ return Object.values(value).some((child) => isPlainObject(child) ? hasLeafValue(child) : child !== undefined);
216
+ }
217
+ function isHttpsUrl(value) {
218
+ return new URL(value).protocol === 'https:';
219
+ }
220
+ function isSecureBrowserAssetUrl(value) {
221
+ const url = new URL(value);
222
+ if (url.protocol === 'https:')
223
+ return true;
224
+ if (url.protocol !== 'http:')
225
+ return false;
226
+ return url.hostname === 'localhost' || url.hostname === '127.0.0.1' || url.hostname === '[::1]';
227
+ }
228
+ //# sourceMappingURL=assistant-appearance.js.map
@@ -0,0 +1,31 @@
1
+ import type { ArtifactServer } from '@noodle-borg/compiler';
2
+ import { type AssistantAppearanceOverride } from './assistant-appearance.js';
3
+ import type { AssistantAppearanceSettingsStore } from './assistant-appearance-store.js';
4
+ import { ASSISTANT_BROWSER_UI_FIELDS } from './assistant-store.js';
5
+ import type { TenantRef } from './tenant-ref.js';
6
+ /**
7
+ * What the browser is allowed to see.
8
+ *
9
+ * The session response's `configuration` crosses to the browser, and its wire schema is
10
+ * `z.record(z.unknown())` — it validates nothing. This was previously "everything on `server.assistant`
11
+ * except `model` and `allowedOrigins`", which is a denylist: it already shipped `sessionClaims` key
12
+ * names to every embed, and it would ship a public page the server's internal capability allowlist.
13
+ *
14
+ * An explicit allowlist inverts the default. A field reaches a customer's page because it is named
15
+ * in `ASSISTANT_BROWSER_UI_FIELDS`, not because nobody remembered to exclude it — so growth in the
16
+ * manifest cannot silently grow the browser payload. That list is owned by the gateway, beside the
17
+ * record type it produces, so this projection and the type it satisfies cannot disagree.
18
+ */
19
+ export declare function assistantBrowserConfiguration(server: ArtifactServer): AssistantAppearanceOverride | undefined;
20
+ /** Resolve environment operator state above the immutable deployment projection. */
21
+ export declare function effectiveAssistantBrowserConfiguration(server: ArtifactServer, tenant: TenantRef, store: AssistantAppearanceSettingsStore | undefined): Promise<{
22
+ effective: AssistantAppearanceOverride | undefined;
23
+ fallback: "halo";
24
+ provenance: import("./assistant-appearance.js").AssistantAppearanceProvenance;
25
+ developer: {
26
+ readonly branding?: ArtifactServer["branding"];
27
+ readonly assistant?: Pick<NonNullable<ArtifactServer["assistant"]>, (typeof ASSISTANT_BROWSER_UI_FIELDS)[number]>;
28
+ } | undefined;
29
+ record: import("./assistant-appearance-store.js").AssistantAppearanceSettingsRecord | undefined;
30
+ }>;
31
+ //# sourceMappingURL=assistant-configuration.d.ts.map
@@ -1,4 +1,5 @@
1
- import { ASSISTANT_BROWSER_UI_FIELDS, } from '@noodle-borg/assistant-gateway/portable';
1
+ import { resolveAssistantAppearanceConfiguration, } from './assistant-appearance.js';
2
+ import { ASSISTANT_BROWSER_UI_FIELDS } from './assistant-store.js';
2
3
  /**
3
4
  * What the browser is allowed to see.
4
5
  *
@@ -28,4 +29,14 @@ export function assistantBrowserConfiguration(server) {
28
29
  ? configuration
29
30
  : undefined;
30
31
  }
32
+ /** Resolve environment operator state above the immutable deployment projection. */
33
+ export async function effectiveAssistantBrowserConfiguration(server, tenant, store) {
34
+ const developer = assistantBrowserConfiguration(server);
35
+ const record = await store?.get(tenant);
36
+ return {
37
+ developer,
38
+ record,
39
+ ...resolveAssistantAppearanceConfiguration(developer, record?.override),
40
+ };
41
+ }
31
42
  //# sourceMappingURL=assistant-configuration.js.map
@@ -0,0 +1,12 @@
1
+ import type { ArtifactTool, RuntimeArtifact } from '@noodle-borg/compiler';
2
+ import type { ConnectorCompileError, SecretBinding } from '@noodle-borg/connector-defs';
3
+ /** Join keys for caller-derived bindings: `connectorId|operation`, `connectorId|*` for defaults. */
4
+ export declare function delegatedAuthOperationKeys(bindings: readonly SecretBinding[]): ReadonlySet<string>;
5
+ /** Whether any operation this tool fulfils through is bound to caller-derived connector auth. */
6
+ export declare function toolTouchesDelegatedAuth(tool: Pick<ArtifactTool, 'fulfilment'>, keys: ReadonlySet<string>): boolean;
7
+ /**
8
+ * Reject delegated-auth tools projected to `public` surfaces. A `mixed` surface deliberately
9
+ * projects them — reaching one raises the sign-in card — so only pure `public` is an error.
10
+ */
11
+ export declare function publicSurfaceDelegatedAuthErrors(artifact: Pick<RuntimeArtifact, 'tools' | 'server'>, bindings: readonly SecretBinding[]): readonly ConnectorCompileError[];
12
+ //# sourceMappingURL=assistant-delegated-projection.d.ts.map
@@ -14,7 +14,7 @@ export declare const ASSISTANT_SESSION_IDLE_MS: number;
14
14
  * that lies — it once admitted `sessionClaims` key names, and would now admit the public surface's
15
15
  * capability allowlist — so growth in the manifest must not be able to grow the browser payload.
16
16
  */
17
- export declare const ASSISTANT_BROWSER_UI_FIELDS: readonly ["layout", "behavior", "labels", "presentation", "suggestedPrompts", "privacyUrl", "termsUrl", "locale", "direction"];
17
+ export declare const ASSISTANT_BROWSER_UI_FIELDS: readonly ["theme", "layout", "behavior", "labels", "presentation", "suggestedPrompts", "privacyUrl", "termsUrl", "locale", "direction"];
18
18
  export interface AssistantClientRecord {
19
19
  readonly id: string;
20
20
  readonly name: string;
@@ -12,6 +12,7 @@ export const ASSISTANT_SESSION_IDLE_MS = 30 * 60 * 1000;
12
12
  * capability allowlist — so growth in the manifest must not be able to grow the browser payload.
13
13
  */
14
14
  export const ASSISTANT_BROWSER_UI_FIELDS = [
15
+ 'theme',
15
16
  'layout',
16
17
  'behavior',
17
18
  'labels',
@@ -0,0 +1,38 @@
1
+ import type { RuntimeArtifact } from '@noodle-borg/compiler';
2
+ /** Safe renderer-facing identity for a completed tool that has a linked MCP App resource. */
3
+ export interface AssistantViewAvailableData {
4
+ readonly id: string;
5
+ readonly tool: string;
6
+ readonly resourceUri: string;
7
+ readonly title?: string;
8
+ /** Already bounded/redacted public tool output. Never pass a raw runtime result here. */
9
+ readonly result: unknown;
10
+ readonly arguments?: unknown;
11
+ /** Self-contained MCP App document, with the standard bridge injected by the protocol runtime. */
12
+ readonly html: string;
13
+ readonly resourceMeta?: Readonly<Record<string, unknown>>;
14
+ readonly allowedOpenDomains?: readonly string[];
15
+ readonly replayed?: true;
16
+ }
17
+ /** A declared widget the completed tool could not surface — reported so the drop is never silent. */
18
+ export interface AssistantViewUnresolved {
19
+ readonly tool: string;
20
+ readonly resourceUri: string;
21
+ readonly reason: 'resource_not_found' | 'resource_not_renderable';
22
+ }
23
+ /**
24
+ * Resolve a self-contained, statically compiled MCP App document for the embedded host. The linked
25
+ * resource must be present in the same compiled artifact and carry the MCP App MIME type; arbitrary
26
+ * tool metadata therefore cannot turn this event into a browser navigation. A tool with no declared
27
+ * `ui.resourceUri` resolves to `undefined` silently (no widget intended); a *declared* widget that
28
+ * cannot be resolved additionally invokes `onUnresolved` so callers can log the drop — a blank or
29
+ * missing widget must never be signal-free.
30
+ */
31
+ export declare function assistantViewAvailableData(artifact: RuntimeArtifact, input: {
32
+ readonly id: string;
33
+ readonly tool: string;
34
+ readonly result: unknown;
35
+ readonly arguments?: unknown;
36
+ readonly replayed?: true;
37
+ }, onUnresolved?: (failure: AssistantViewUnresolved) => void): AssistantViewAvailableData | undefined;
38
+ //# sourceMappingURL=assistant-view-availability.d.ts.map
@@ -32,6 +32,8 @@ export interface PublicEmbedRecord {
32
32
  readonly turnsPerDay?: number;
33
33
  readonly mintsPerDay?: number;
34
34
  }
35
+ /** Project the non-secret embed coordinate onto the shared assistant tenant shape. */
36
+ export declare function publicEmbedTenant(embed: PublicEmbedRecord): TenantRef;
35
37
  /** What an operator may change about a surface. An omitted field leaves that cap as it was. */
36
38
  export interface PublicEmbedBudget {
37
39
  readonly turnsPerDay?: number;
@@ -70,4 +72,5 @@ export interface PublicEmbedStore {
70
72
  */
71
73
  export declare const PUBLIC_EMBED_ID_PREFIX = "pub_";
72
74
  export declare function isPublicEmbedId(value: string): boolean;
75
+ import type { TenantRef } from './tenant-ref.js';
73
76
  //# sourceMappingURL=embed-store.d.ts.map
@@ -11,6 +11,10 @@
11
11
  * projection underneath a page that never changes. Origins, capabilities, and UI therefore come from the
12
12
  * *active* deployment at mint time, never from the embed record.
13
13
  */
14
+ /** Project the non-secret embed coordinate onto the shared assistant tenant shape. */
15
+ export function publicEmbedTenant(embed) {
16
+ return { org: embed.org, app: embed.app, env: embed.env };
17
+ }
14
18
  /**
15
19
  * `pub_` prefixed so it is self-describing in page source, logs, and support threads — someone reading a
16
20
  * customer's HTML can tell at a glance that it is not a secret that leaked.
@@ -0,0 +1,8 @@
1
+ import { type AssistantAppearanceReplaceResult, type AssistantAppearanceSettingsRecord, type AssistantAppearanceSettingsStore } from './assistant-appearance-store.js';
2
+ /** Process-local appearance settings for local development and tests. */
3
+ export declare class InMemoryAssistantAppearanceSettingsStore implements AssistantAppearanceSettingsStore {
4
+ #private;
5
+ get(tenant: AssistantAppearanceSettingsRecord['tenant']): Promise<AssistantAppearanceSettingsRecord | undefined>;
6
+ replace(input: Parameters<AssistantAppearanceSettingsStore['replace']>[0]): Promise<AssistantAppearanceReplaceResult>;
7
+ }
8
+ //# sourceMappingURL=in-memory-assistant-appearance-store.d.ts.map
@@ -0,0 +1,26 @@
1
+ import { appearanceTenantKey, cloneAssistantAppearanceSettingsRecord, } from './assistant-appearance-store.js';
2
+ /** Process-local appearance settings for local development and tests. */
3
+ export class InMemoryAssistantAppearanceSettingsStore {
4
+ #records = new Map();
5
+ async get(tenant) {
6
+ const record = this.#records.get(appearanceTenantKey(tenant));
7
+ return record === undefined ? undefined : cloneAssistantAppearanceSettingsRecord(record);
8
+ }
9
+ async replace(input) {
10
+ const key = appearanceTenantKey(input.tenant);
11
+ const current = this.#records.get(key);
12
+ const currentRevision = current?.revision ?? 0;
13
+ if (currentRevision !== input.expectedRevision)
14
+ return { ok: false, currentRevision };
15
+ const record = {
16
+ tenant: { ...input.tenant },
17
+ ...(input.override === undefined ? {} : { override: structuredClone(input.override) }),
18
+ revision: currentRevision + 1,
19
+ updatedAt: new Date(input.updatedAt),
20
+ updatedBy: input.updatedBy,
21
+ };
22
+ this.#records.set(key, record);
23
+ return { ok: true, record: cloneAssistantAppearanceSettingsRecord(record) };
24
+ }
25
+ }
26
+ //# sourceMappingURL=in-memory-assistant-appearance-store.js.map
@@ -1,8 +1,12 @@
1
1
  /** Assistant gateway contracts and in-memory adapters safe for the published CLI runtime. */
2
2
  export * from './artifact-projection.js';
3
+ export * from './assistant-appearance.js';
4
+ export * from './assistant-appearance-store.js';
5
+ export * from './assistant-configuration.js';
3
6
  export * from './assistant-context.js';
4
7
  export * from './assistant-customer-issuer.js';
5
8
  export * from './assistant-customer-routing.js';
9
+ export * from './assistant-delegated-projection.js';
6
10
  export * from './assistant-guide.js';
7
11
  export * from './assistant-interaction-state.js';
8
12
  export * from './assistant-interactive.js';
@@ -10,12 +14,15 @@ export * from './assistant-presentation.js';
10
14
  export * from './assistant-sensitive-values.js';
11
15
  export * from './assistant-store.js';
12
16
  export * from './assistant-transcript.js';
17
+ export * from './assistant-view-availability.js';
13
18
  export * from './elevation.js';
14
19
  export * from './elevation-store.js';
15
20
  export * from './embed-script.js';
16
21
  export * from './embed-store.js';
22
+ export * from './in-memory-assistant-appearance-store.js';
17
23
  export * from './in-memory-elevation-store.js';
18
24
  export * from './in-memory-embed-store.js';
25
+ export * from './public-configuration.js';
19
26
  export * from './public-session.js';
20
27
  export * from './public-surface.js';
21
28
  export * from './public-turn.js';