@noodleseed/one 0.151.0 → 0.152.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/dist/assistant-embed-scaffold-template.d.ts +1 -0
- package/dist/assistant-embed-scaffold-template.d.ts.map +1 -1
- package/dist/assistant-embed-scaffold-template.js +80 -12
- package/dist/assistant-embed-scaffold-template.js.map +1 -1
- package/dist/commands/assistant-embed-ops.d.ts.map +1 -1
- package/dist/commands/assistant-embed-ops.js +141 -4
- package/dist/commands/assistant-embed-ops.js.map +1 -1
- package/dist/commands/assistant-ops.js +2 -1
- package/dist/commands/assistant-ops.js.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/behavior-skills.js +10 -10
- package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +6 -5
- package/node_modules/@noodle-borg/agent-kit/dist/index.js +13 -1
- package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-refs.js +8 -0
- package/node_modules/@noodle-borg/agent-kit/dist/skill-content.js +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-operations-ref.js +78 -0
- package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +156 -135
- package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-renderer-ref.js +109 -0
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-browser-fields.d.ts +3 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-browser-fields.js +15 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-configuration.d.ts +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-configuration.js +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.d.ts +19 -11
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.js +24 -23
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-transcript-events.d.ts +24 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-transcript-events.js +39 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-transcript.d.ts +6 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-transcript.js +11 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-view-availability.d.ts +8 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-view-availability.js +8 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/elevation-store.d.ts +3 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/elevation.d.ts +7 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/elevation.js +1 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-elevation-store.d.ts +1 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-elevation-store.js +1 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/managed-spend.d.ts +29 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/managed-spend.js +27 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +1 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +1 -0
- package/node_modules/@noodle-borg/assistant-gateway/package.json +1 -0
- package/node_modules/@noodle-borg/authoring/dist/server.d.ts +2 -0
- package/node_modules/@noodle-borg/authoring/dist/server.js +3 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +1 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/version.d.ts +4 -1
- package/node_modules/@noodle-borg/compiler/dist/artifact/version.js +4 -1
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +4 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +20 -1
- package/node_modules/@noodle-borg/compiler/dist/state-handles.js +1 -0
- package/node_modules/@noodle-borg/runtime/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/runtime/dist/index.js +1 -0
- package/node_modules/@noodle-borg/runtime/dist/state-handle-ownership.d.ts +21 -0
- package/node_modules/@noodle-borg/runtime/dist/state-handle-ownership.js +10 -0
- package/node_modules/@noodle-borg/runtime/package.json +6 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +8 -3
- package/node_modules/@noodle-borg/service/dist/routes/assistant-elevation.js +14 -7
- package/node_modules/@noodle-borg/service/dist/routes/assistant-interaction-replay.js +4 -2
- package/node_modules/@noodle-borg/service/dist/routes/assistant-interactions.js +4 -2
- package/node_modules/@noodle-borg/service/dist/routes/assistant-transcript.js +48 -9
- package/node_modules/@noodle-borg/service/dist/service.js +3 -0
- package/node_modules/@noodle-borg/service/package.json +1 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/assistant.d.ts +19 -25
- package/node_modules/@noodle-borg/wire-contracts/dist/assistant.js +20 -34
- package/node_modules/@noodleseed/assistant/README.md +1 -1
- package/package.json +2 -2
|
@@ -228,6 +228,14 @@ export function renderAuthoringWorkflowReference() {
|
|
|
228
228
|
'',
|
|
229
229
|
'To place the same server tools inside a SaaS web app, declare `assistant: embeddedAssistant(...)` alongside the one server-level brand kit. Read `embedded-assistant.md` before integrating: it owns the HTTPS-origin rule, managed model configuration, required deploy-before-client sequence, customer-backend exchange, browser mount, and verification checklist.',
|
|
230
230
|
'',
|
|
231
|
+
'## Managed capability reuse',
|
|
232
|
+
'',
|
|
233
|
+
'Before authoring an HTTP connector for public-web search, page extraction, places/local search, routing, weather, translation, or media discovery, inspect the generated SDK surface reference. If the matching Noodle capability is actually exported, use its one documented declaration and generated tools directly—never wrap it, rename it, or build an assistant-only provider call. That single declaration is available to both the embedded assistant and external MCP agents under the same authorization and policy.',
|
|
234
|
+
'',
|
|
235
|
+
'Use `provider: noodleManaged()` in reusable capability intent and let the app/environment operator bind a supported BYO provider through managed configuration. Keep provider names, endpoints, and credentials out of `server.ts`. Live fares, inventory, bookings, pricing, orders, accounts, and payments remain connectors to authoritative systems. Source order is not truth: preserve citations, freshness, and rights evidence returned by the capability.',
|
|
236
|
+
'',
|
|
237
|
+
'A roadmap is not an SDK. If the capability is absent from the generated export list, do not invent an import or generic platform helper. With explicit developer agreement, a bounded application connector may prove the immediate use case; keep it application-specific and do not claim it as the canonical Noodle primitive.',
|
|
238
|
+
'',
|
|
231
239
|
'## Knowledge components',
|
|
232
240
|
'',
|
|
233
241
|
"Ground an assistant in controlled documents and the customer's live public site with one declaration — never a handwritten `search`/`fetch` tool pair, a provider name, a sync job, or an index manifest. Declare `knowledge(...)` with `file(...)` documents (UTF-8 `.md`/`.txt`, project-root relative, ≤100 files, ≤1 MiB each, ≤25 MiB per component) and `site(...)` live scopes (exact HTTPS origin plus positive path globs), pass the declaration in the server's `knowledge` array, and include it in a public website surface's `capabilities` to project the generated `search_<name>` capability:",
|
|
@@ -249,7 +249,7 @@ const SDK_AUTHORING_SIGNATURES = [
|
|
|
249
249
|
'- `resource(name, { uri, description?, mimeType?, fulfil })` and `prompt(name, { description?, arguments?, fulfil })` expose MCP resources/prompts.',
|
|
250
250
|
'- View metadata (`viewTitle`, `viewDescription`, `csp`, `domain`, `permissions`) belongs on the tool that renders it; `asset("./path")` packages local files.',
|
|
251
251
|
'- `customerAuth.*(...)` belongs in `server` options when deployed customer callers need verified identity; inspect `examples/customer-auth` or `examples/sharepoint` before using it.',
|
|
252
|
-
'- `state` defines durable widget state handles; handle schemas may use `.optional()`/`.default()` — defaulted fields are optional on write, so a save that omits them still validates. `handoff` declares allowed external domains for safe host handoff.',
|
|
252
|
+
'- `state` defines durable widget state handles; handle schemas may use `.optional()`/`.default()` — defaulted fields are optional on write, so a save that omits them still validates. Add `claimOnAuthentication: true` only to an explicitly caller-scoped handle with a finite TTL when a mixed public assistant should atomically adopt that expiring draft on sign-in-ticket spend. `handoff` declares allowed external domains for safe host handoff.',
|
|
253
253
|
];
|
|
254
254
|
export function renderSdkSurfaceReference() {
|
|
255
255
|
const remaining = new Set(SDK_EXPORTS);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** Evidence, launch, and qualified-usage guidance appended to the embedded-assistant reference. */
|
|
2
|
+
export function renderEmbeddedAssistantOperationsReference() {
|
|
3
|
+
return [
|
|
4
|
+
'',
|
|
5
|
+
'## Host readiness and promotion',
|
|
6
|
+
'',
|
|
7
|
+
'Run the non-mutating host preflight from the embedding application before its production build:',
|
|
8
|
+
'',
|
|
9
|
+
'```sh',
|
|
10
|
+
'noodle assistant embed --check --json',
|
|
11
|
+
'noodle assistant embed --check --json --require-env EXAMPLE_DELEG_CLIENT_SECRET',
|
|
12
|
+
'```',
|
|
13
|
+
'',
|
|
14
|
+
'The check reports only required and missing environment names, never their values. Pass `--surface public|mixed|authenticated` to match the deployment: `public` drops the backend client id/secret requirement (a public embed has neither), and `public`/`mixed` additionally require `script-src` — the one directive whose failure runs no widget code at all, so nothing can report it from inside the page. CSP directives verify against the service origin exactly, via the env placeholder, or through a covering wildcard (`https://*.example.com`); a dynamic expression is marked unverified instead of guessed. Additional `--require-env` names are application-owned; `--env-alias NAME=HOST_NAME` follows a host repo that names an env var differently.',
|
|
15
|
+
'',
|
|
16
|
+
'Read `evidence.levels` in order and stop at `evidence.firstUnproven`:',
|
|
17
|
+
'',
|
|
18
|
+
'| Level | What can prove it |',
|
|
19
|
+
'| --- | --- |',
|
|
20
|
+
'| Static host | `assistant embed --check`: environment-name presence, control-plane URL shape, canonical session-route/mount risks, same-origin endpoint shape, and statically visible CSP |',
|
|
21
|
+
"| Local contract | The host's existing local test/build: JSON signed-out `401`, signed-in session exchange, unchanged response, client-only mount, and cookie/CSRF behavior |",
|
|
22
|
+
'| Hosted session | `assistant doctor` plus one representative routed safe read against the exact deployment |',
|
|
23
|
+
'| Production browser | A real deployed browser completing keyboard submission, session exchange, one streamed turn, one interaction, and one linked App with console/network inspection |',
|
|
24
|
+
'| Operations | The launch, rollback, rotation, budget, usage, and qualified-outcome checks below |',
|
|
25
|
+
'',
|
|
26
|
+
'`static-host` is `passed` only when every canonical static diagnostic is ready or not applicable. It is `partial` when a custom or missing source location is not detected, and `failed` for a known blocker. A top-level `ready: true` means only that the check found no static blocker; static success is never local-contract, hosted-session, or production-browser proof.',
|
|
27
|
+
'',
|
|
28
|
+
'The value-free diagnostics distinguish `mcp-endpoint` (a deployment MCP URL was supplied where the control-plane service URL belongs), `html-redirect-risk` (the canonical API route appears to redirect instead of returning JSON), `ssr-risk` (the canonical mount is not provably client-only), and `cross-origin-risk` (the browser session endpoint would omit same-origin cookies). `not-detected` and `unverified` are evidence gaps, not passes. The listed `postDeployProbes` are next actions; this command does not execute them.',
|
|
29
|
+
'',
|
|
30
|
+
"There is no live or browser flag. Do not add one without human approval of its network, identity, data, timeout, and redaction contract; use the host's own test and browser tooling for later evidence levels.",
|
|
31
|
+
'',
|
|
32
|
+
'Inspect the host repository for generated environment bindings after adding names. Run its existing generator, review the diff, commit generated types only when that repository requires them, then run the production-equivalent host build. Do not invent a framework command or add a second generator.',
|
|
33
|
+
'',
|
|
34
|
+
'Promotion checklist: provision each environment in the backend secret manager; map names through the CI environment and any secret allowlist or secrets file; run the presence-only host preflight; run the canonical deploy so its configuration preflight completes before asset upload; run the post-deploy probes from the JSON contract; rotate the assistant client and delegated credential independently, then rerun the same checks.',
|
|
35
|
+
'',
|
|
36
|
+
"Devtools privacy gate: default model and connector exercises to synthetic or mock data. Before Devtools Chat sends real connector data to an external model, disclose the data flow and obtain the user's approval. A local validation pass is not that approval.",
|
|
37
|
+
'',
|
|
38
|
+
'## Launch and qualified-usage proof',
|
|
39
|
+
'',
|
|
40
|
+
'Treat deployed capability, production-browser proof, actual qualified usage, and measured customer outcome as four separate claims. Passing one never proves the next.',
|
|
41
|
+
'',
|
|
42
|
+
'### Pre-launch checklist',
|
|
43
|
+
'',
|
|
44
|
+
'1. Run `noodle assistant embed --check --json` in the host repository and stop at `evidence.firstUnproven`; complete the host-owned local contract and production-browser checks described above.',
|
|
45
|
+
'2. Run `noodle assistant doctor --user-id <real-test-user> --origin <exact-origin> --org <org> --app <app> --env <env> --json`, then exercise one representative routed safe read. Use an approved synthetic account unless real customer data use was explicitly approved.',
|
|
46
|
+
'3. Run `noodle assistant appearance show --org <org> --app <app> --env <env> --json` and have the customer owner verify the rendered brand, keyboard path, reduced motion, narrow viewport, confirmation, input, and linked App view in the deployed browser.',
|
|
47
|
+
'4. Run `noodle assistant clients list --org <org> --app <app> --env <env> --json`; name the active credential owner and rehearse the secret-manager update and verification sequence for `noodle assistant clients rotate <client-id> --org <org> --app <app> --env <env> --json`. Rotation returns a new secret once; never put it in the PR, shell history, logs, or analytics.',
|
|
48
|
+
'5. For a public or mixed surface, inspect its live caps with `noodle assistant embeds list --org <org> --app <app> --env <env> --json`. Record the authorized kill switch, `noodle assistant budget set --surface <embed-id> --turns-per-day 0 --org <org> --app <app> --env <env> --json`, but do not execute it during a rehearsal.',
|
|
49
|
+
'6. Record the current and previous deployment identifiers and the named rollback owner. The recovery command is `noodle rollback <previous-deployment-id> --org <org> --app <app> --env <env> --json`; do not run it merely to prove syntax.',
|
|
50
|
+
'7. Have the customer workflow owner execute and sign off the behavioral acceptance fixture below against the production-equivalent environment.',
|
|
51
|
+
'',
|
|
52
|
+
'### Customer-owned behavioral acceptance fixture',
|
|
53
|
+
'',
|
|
54
|
+
'Use customer-recognizable inputs and expected business outcomes. Record only the minimum identifiers needed to reproduce a failure; do not paste raw conversations, secrets, or customer records into issue trackers.',
|
|
55
|
+
'',
|
|
56
|
+
'| Case | Customer-owned setup | Expected observable outcome |',
|
|
57
|
+
'| --- | --- | --- |',
|
|
58
|
+
'| normal | One common, authorized job with known data | Completes the named task and presents the business result or linked App clearly |',
|
|
59
|
+
'| ambiguous | A request with two materially different interpretations | Asks the smallest useful clarifying question before action |',
|
|
60
|
+
'| missing-data | Omit one required business field or make the read return no record | Requests only the missing input or states that no data exists; never invents it |',
|
|
61
|
+
'| unauthorized | Use a real test role lacking the required scope | Refuses or withholds the capability without leaking its result, arguments, or internal tool name |',
|
|
62
|
+
'| tenant-switch | Change the signed-in organization while the panel is open | Aborts and clears the prior principal session; no prior-tenant transcript, route, or result survives |',
|
|
63
|
+
'| confirmation | Propose one consequential operation, then test decline, cancel, and accept separately | Shows the complete non-sensitive action review; only explicit accept executes, at most once |',
|
|
64
|
+
'| recovery | Expire a session before execution and lose one interaction response after submission | Safe pre-execution re-exchange succeeds once; same-id/same-decision reconciliation returns the stored outcome without re-execution |',
|
|
65
|
+
'',
|
|
66
|
+
'### Post-launch checklist',
|
|
67
|
+
'',
|
|
68
|
+
'1. Run `noodle assistant usage --window 7d --org <org> --app <app> --env <env> --json` on an agreed cadence. It reports traffic, depth, engagement, latency, and tokens; it does not establish customer success.',
|
|
69
|
+
'2. Re-run the customer-owned fixture with named external users after any tool, permission, model, identity, renderer, or routing change. Record results by case and deployment, not as a single green launch claim.',
|
|
70
|
+
'3. Report task completion, refusal correctness, repeat usage, safety, latency, and abandonment separately: completed qualified attempts / qualified attempts; correct refusals / refusal cases; distinct returning qualified users; confirmed cross-tenant, duplicate-effect, or secret-exposure incidents; p50/p95 end-to-end time for the named job; and started qualified attempts with no completion or explicit refusal.',
|
|
71
|
+
'4. Treat raw turn volume as utilization evidence, not an outcome. A user can generate many turns because the assistant is useful, confused, slow, or unsafe.',
|
|
72
|
+
'5. Recheck caps and credential age. Run the rehearsed `noodle assistant clients rotate <client-id>` sequence only under the named owner, update the backend secret manager, rerun doctor and one browser turn, then revoke the superseded credential after the overlap window.',
|
|
73
|
+
'6. When an agreed safety, cost, or correctness threshold is crossed, use the recorded public-surface budget kill switch or the recorded deployment rollback. Verify the restored state with doctor, the browser path, and the affected acceptance cases.',
|
|
74
|
+
'',
|
|
75
|
+
'Do not add a platform-owned completion event or infer completion from a final assistant message. Any such schema requires a separate human-approved analytics and customer-data decision covering event semantics, customer consent, retention, access, deletion, tenant isolation, and whether the customer application or Noodle is authoritative.',
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=skill-embedded-assistant-operations-ref.js.map
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { renderEmbeddedAssistantOperationsReference } from './skill-embedded-assistant-operations-ref.js';
|
|
2
|
+
import { renderEmbeddedAssistantRendererReference } from './skill-embedded-assistant-renderer-ref.js';
|
|
1
3
|
/** Curated external-developer workflow for embedding a deployed Noodle assistant in a SaaS app. */
|
|
2
4
|
export function renderEmbeddedAssistantReference() {
|
|
3
5
|
return [
|
|
@@ -6,7 +8,7 @@ export function renderEmbeddedAssistantReference() {
|
|
|
6
8
|
'## Contents',
|
|
7
9
|
'',
|
|
8
10
|
'- Architecture',
|
|
9
|
-
'-
|
|
11
|
+
'- Select the architecture before code',
|
|
10
12
|
'- Author and validate',
|
|
11
13
|
'- Product workflow guidance',
|
|
12
14
|
'- Customize the presentation',
|
|
@@ -19,6 +21,7 @@ export function renderEmbeddedAssistantReference() {
|
|
|
19
21
|
'- The session response',
|
|
20
22
|
'- Choose a browser renderer',
|
|
21
23
|
'- Host readiness and promotion',
|
|
24
|
+
'- Launch and qualified-usage proof',
|
|
22
25
|
'- Toolchain requirements',
|
|
23
26
|
'- Verify the boundary',
|
|
24
27
|
'- Troubleshooting: symptom to diagnosis',
|
|
@@ -38,9 +41,32 @@ export function renderEmbeddedAssistantReference() {
|
|
|
38
41
|
'| SaaS backend | `NOODLE_SERVICE_URL`, `NOODLE_ASSISTANT_CLIENT_ID`, `NOODLE_ASSISTANT_CLIENT_SECRET`, `PUBLIC_APP_ORIGIN` | Backend-only environment or secret manager; never browser code or public-prefixed variables |',
|
|
39
42
|
'| Browser | Short-lived assistant session only | In memory; never a client secret, model key, connector credential, or raw application session |',
|
|
40
43
|
'',
|
|
41
|
-
'##
|
|
44
|
+
'## Select the architecture before code',
|
|
42
45
|
'',
|
|
43
|
-
'
|
|
46
|
+
'Do not edit either codebase until one architecture brief is decision-complete. Name the external end user, the conversational job, one to three workflows, the exact access mode, renderer, existing host framework, identity/session boundary, server-owned routing authority, model owner, exact origin and mount point, and requested evidence level.',
|
|
47
|
+
'',
|
|
48
|
+
'If the external user, job, or workflow set is still vague, hand off to `designing-mcp-products` before code. Preserve its bounded product outcome and resume this workflow only when the value path and one representative success path are explicit.',
|
|
49
|
+
'',
|
|
50
|
+
'Treat the MCP application and embedding application as two code ownership roots, usually two repositories and sometimes two explicitly separate directories in one repository:',
|
|
51
|
+
'',
|
|
52
|
+
'| Code owner | Owns | Must not own |',
|
|
53
|
+
'| --- | --- | --- |',
|
|
54
|
+
'| MCP application | `server.ts`, tools, App views, access surfaces, portable branding defaults, model declaration, validation, and deployment | Host cookies, login UI, application middleware, or browser mounting |',
|
|
55
|
+
'| Embedding application | Existing framework and package manager, user authentication, same-origin session route when required, browser mount, CSP, cookies/CSRF, and production build | MCP tool semantics, model keys, assistant client secrets in browser code, or caller-selected tenant routing |',
|
|
56
|
+
'',
|
|
57
|
+
'Develop locally as two local processes: `noodle dev` serves the MCP application, while the embedding application runs its existing development command at the exact authored loopback origin. Do not replace either process with a second scaffold, package manager, chat transport, or copied flagship shell.',
|
|
58
|
+
'',
|
|
59
|
+
'Select exactly one recommended topology; do not return an unranked menu:',
|
|
60
|
+
'',
|
|
61
|
+
'| User boundary | Recommended topology | Identity and routing consequence |',
|
|
62
|
+
'| --- | --- | --- |',
|
|
63
|
+
'| Anonymous public visitor | `publicWebsite(...)` plus the hosted embed ID and managed renderer | No backend session route, `${user}`, roles/scopes, delegated credentials, or customer routing. Require the explicit capability allowlist and confirmation for side effects. |',
|
|
64
|
+
'| Signed-in application user | `authenticatedWebsite(...)` plus a same-origin backend session route and the managed renderer | The host backend authenticates the user, calls `createAssistantSession`, and supplies only verified identity, claims, preferences, and server-owned routing. |',
|
|
65
|
+
'| Visitor who may sign in mid-conversation | `publicWebsite({ signIn: true })` plus the hosted embed ID, host application login, and ticket elevation | Anonymous capabilities remain bounded; the host backend spends the ticket after login and continues through a same-origin session endpoint on an allowed origin. |',
|
|
66
|
+
'',
|
|
67
|
+
'Use the existing host framework and its package manager. Recommend the managed renderer unless the product names a concrete UI or integration benefit that requires the headless client; a custom renderer accepts the complete transcript-part obligation described below. Recommend `noodleManaged()` when Noodle owns the managed-cloud model for the exact eligible target; recommend `openAICompatible(...)` only when the operator or self-hosted deployment owns that model endpoint and credentials. Never present both as unfinished choices.',
|
|
68
|
+
'',
|
|
69
|
+
'The architecture brief must state the selected topology, why it fits the named user and job, the two code owners and two local processes, credential and identity flow, routing authority, model owner, files expected to change, and the first requested evidence boundary. If identity/session ownership, exact origin, tenant-routing authority, model ownership, or evidence target is unavailable, return a blocked architecture brief naming the missing owner or decision and do not edit either codebase.',
|
|
44
70
|
'',
|
|
45
71
|
'## Author and validate',
|
|
46
72
|
'',
|
|
@@ -120,11 +146,17 @@ export function renderEmbeddedAssistantReference() {
|
|
|
120
146
|
'',
|
|
121
147
|
'The sign-in card renders on the same themed chrome as every proposal card and follows the server `branding`. Authoring `signUpAction` adds a second button; leaving it out renders none. Both buttons raise the same `assistant-sign-in-requested` event with the same single-use ticket — the detail adds `intent: "sign-in" | "sign-up"` so the page routes `sign-up` to its registration page instead of its login. The ticket spend after account creation is identical to the one after sign-in; the service does not care which path produced the session. Headless renderers receive the same moment as a `data-sign-in` transcript part from `subscribeChat` (it has no status and is not respondable through `client.respond` — resolution is the elevated session).',
|
|
122
148
|
'',
|
|
123
|
-
'
|
|
149
|
+
'#### Complete mixed-mode handoff',
|
|
150
|
+
'',
|
|
151
|
+
'1. **Capture the ticket.** The widget raises `assistant-sign-in-requested` with a single-use `signInTicket`. POST it to the host application same-origin backend and bind it to the short-lived login transaction; keep it out of query strings, logs, analytics, and durable browser storage.',
|
|
152
|
+
'2. **Use the existing login.** Send the visitor through the host application full-page redirect for sign-in or sign-up. Noodle never becomes the customer identity provider.',
|
|
153
|
+
'3. **Elevate on the backend.** After authentication, recover the bound ticket and call `createAssistantSession({ ..., signInTicket })` with the verified user, server-owned routing, backend client credentials, and the exact **origin the conversation will continue on**. The allowed destination may differ from the anonymous marketing origin; successful elevation rebinds the session to it.',
|
|
154
|
+
'4. **Reattach on the destination origin.** Forward the helper response unchanged through the customer-owned same-origin session endpoint. The widget uses `credentials: "same-origin"`; pointing it at a cross-origin endpoint guarantees a cookie-less `401`. The elevated token replaces the anonymous token, which is dead.',
|
|
155
|
+
'5. **Resume and replay.** The widget repaints the bounded visible transcript from `endpoints.transcript`, emits one secure-continuation status, re-resolves the latest linked App view against the current surface, restores at most one live pending confirmation/input request, and then shows follow-up suggestions. Restoring an interaction suppresses auto-resume. Replay includes only rows the panel showed, never tool internals or a spent ticket, and never App HTML, a DOM snapshot, or exact scroll/focus. On an older service without that endpoint the panel starts visually fresh while the model still remembers, so promise only that "the assistant remembers". Pass `restoreConversation: false` when a privacy-sensitive application must suppress browser replay while retaining bounded server/model continuity.',
|
|
124
156
|
'',
|
|
125
|
-
'
|
|
157
|
+
'Possession of the ticket alone elevates nothing, and the service checks that the backend client tenant owns the conversation. A refused spend throws a typed `AssistantSessionExchangeError`: branch on `elevationRefusal` (`elevation_ticket_expired` re-prompt; `elevation_tenant_mismatch` alert and never retry; `elevation_state_conflict` means the ticket is spent, so open or restart the signed-in flow and never merge generic state). A refused origin does not burn the ticket. The ticket is not the server-held interaction continuation — that value never reaches browser code.',
|
|
126
158
|
'',
|
|
127
|
-
'
|
|
159
|
+
'By default the pending request completes itself: the service re-attempts the intercepted tool under the new principal and streams it as the elevated session first turn. This is one-shot, is mooted if the visitor types first, and stops at the confirmation card for a confirm-gated tool. Pass `resume: false` beside the ticket only when the application deliberately owns that affordance. Do not build client-side resume scaffolding or a second identity provider.',
|
|
128
160
|
'',
|
|
129
161
|
'A connector-backed side effect needs **two** independent declarations to be reachable from a public or mixed surface: inclusion in `capabilities` **and** `{ confirm: true }` on the operation. Signing in proves who the visitor is; it does not pre-authorize an effect, so confirmation still applies on a mixed surface. Confirmation is never authentication or business authorization — the customer backend still owns payload validation, abuse controls, and idempotency. Local or session-only widget state needs no confirmation.',
|
|
130
162
|
'',
|
|
@@ -219,6 +251,10 @@ export function renderEmbeddedAssistantReference() {
|
|
|
219
251
|
'',
|
|
220
252
|
'Create an authenticated same-origin backend route:',
|
|
221
253
|
'',
|
|
254
|
+
'The route must return a JSON `401` for a signed-out caller rather than an HTML login redirect, require JSON, and compare the request against the exact configured Origin before exchange. The managed renderer sends same-origin cookies but does not invent an application CSRF token. Treat JSON plus exact Origin as the minimum cookie/CSRF boundary; when the host keeps token-based CSRF middleware, use the DOM-free client with an injected `fetch` that supplies the token instead of exempting the route. Resolve claims and connector routes from the authenticated user and server-owned membership only.',
|
|
255
|
+
'',
|
|
256
|
+
'For the complete framework-neutral server-to-server HTTP exchange in a non-Node host, link to the [Django and Vue guide](https://docs.noodleseed.dev/docs/guides/embedded-assistant-django-vue); keep its framework-specific middleware and mounting mechanics there rather than copying them into agent instructions.',
|
|
257
|
+
'',
|
|
222
258
|
'```ts',
|
|
223
259
|
'import { createAssistantSession } from "@noodleseed/assistant/server";',
|
|
224
260
|
'',
|
|
@@ -396,7 +432,9 @@ export function renderEmbeddedAssistantReference() {
|
|
|
396
432
|
'',
|
|
397
433
|
'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`.',
|
|
398
434
|
'',
|
|
399
|
-
'
|
|
435
|
+
'### Minimal fail-closed custom renderer skeleton',
|
|
436
|
+
'',
|
|
437
|
+
'Use the renderer-free hook only when the product has a concrete reason to own the conversation UI. This is a minimal fail-closed skeleton, not a production-complete renderer: it keeps one client and App host, renders safe transcript content, and refuses interactions whose complete business presentation the host has not implemented. Start from the managed renderer unless the application accepts the full obligation checklist below.',
|
|
400
438
|
'',
|
|
401
439
|
'```tsx',
|
|
402
440
|
'"use client";',
|
|
@@ -405,9 +443,22 @@ export function renderEmbeddedAssistantReference() {
|
|
|
405
443
|
'import { NoodleAppView } from "@noodleseed/assistant/react";',
|
|
406
444
|
'import { useNoodleAssistant } from "@noodleseed/assistant/react/client";',
|
|
407
445
|
'',
|
|
408
|
-
'
|
|
446
|
+
'type SignInRequest = { readonly signInTicket: string; readonly expiresAt: string };',
|
|
447
|
+
'',
|
|
448
|
+
'export function CustomAssistant({',
|
|
449
|
+
' principalKey,',
|
|
450
|
+
' resolvedTheme,',
|
|
451
|
+
' onSignInRequested,',
|
|
452
|
+
'}: {',
|
|
453
|
+
' principalKey: string;',
|
|
454
|
+
' resolvedTheme: "light" | "dark";',
|
|
455
|
+
' onSignInRequested: (request: SignInRequest) => Promise<"started" | "cancelled">;',
|
|
456
|
+
'}) {',
|
|
409
457
|
' const [draft, setDraft] = useState("");',
|
|
410
|
-
' const
|
|
458
|
+
' const [sessionNotice, setSessionNotice] = useState("");',
|
|
459
|
+
' const [turnNotice, setTurnNotice] = useState("");',
|
|
460
|
+
' const [pendingSignInTicket, setPendingSignInTicket] = useState<string>();',
|
|
461
|
+
' const { client, messages, suggestions, status, error } = useNoodleAssistant({',
|
|
411
462
|
' sessionEndpoint: "/api/assistant/session",',
|
|
412
463
|
' principalKey,',
|
|
413
464
|
' clientContext: () => ({',
|
|
@@ -421,6 +472,16 @@ export function renderEmbeddedAssistantReference() {
|
|
|
421
472
|
' // The hook exposes this same structured failure through `error`.',
|
|
422
473
|
' });',
|
|
423
474
|
' };',
|
|
475
|
+
' useEffect(',
|
|
476
|
+
' () =>',
|
|
477
|
+
' client.subscribe((event) => {',
|
|
478
|
+
' if (event.event === "session_expired") setSessionNotice("Session expired.");',
|
|
479
|
+
' if (event.event === "session_started" || event.event === "session_reset") {',
|
|
480
|
+
' setSessionNotice("");',
|
|
481
|
+
' }',
|
|
482
|
+
' }),',
|
|
483
|
+
' [client],',
|
|
484
|
+
' );',
|
|
424
485
|
'',
|
|
425
486
|
' return (',
|
|
426
487
|
' <section aria-label="Assistant" aria-busy={busy}>',
|
|
@@ -434,20 +495,18 @@ export function renderEmbeddedAssistantReference() {
|
|
|
434
495
|
' <section key={review.id} aria-label="Review proposed action">',
|
|
435
496
|
' <h3>{review.title ?? "Review proposed action"}</h3>',
|
|
436
497
|
' {review.description ? <p>{review.description}</p> : null}',
|
|
437
|
-
' <
|
|
438
|
-
' {JSON.stringify(review.arguments ?? {}, null, 2)}',
|
|
439
|
-
' </pre>',
|
|
498
|
+
' <p>This custom renderer has not implemented a complete schema-aware review.</p>',
|
|
440
499
|
' <button',
|
|
441
500
|
' disabled={busy || review.status !== "pending"}',
|
|
442
|
-
' onClick={() => settle(client.respond(review.id, { action: "
|
|
501
|
+
' onClick={() => settle(client.respond(review.id, { action: "decline" }))}',
|
|
443
502
|
' >',
|
|
444
|
-
|
|
503
|
+
" Don't proceed",
|
|
445
504
|
' </button>',
|
|
446
505
|
' <button',
|
|
447
506
|
' disabled={busy || review.status !== "pending"}',
|
|
448
|
-
' onClick={() => settle(client.respond(review.id, { action: "
|
|
507
|
+
' onClick={() => settle(client.respond(review.id, { action: "cancel" }))}',
|
|
449
508
|
' >',
|
|
450
|
-
|
|
509
|
+
' Cancel',
|
|
451
510
|
' </button>',
|
|
452
511
|
' </section>',
|
|
453
512
|
' );',
|
|
@@ -457,21 +516,28 @@ export function renderEmbeddedAssistantReference() {
|
|
|
457
516
|
' return (',
|
|
458
517
|
' <section key={request.id} aria-label="Assistant needs input">',
|
|
459
518
|
' <p>{request.message}</p>',
|
|
460
|
-
'
|
|
519
|
+
' {/* request.requestedSchema is the sole input-form contract. */}',
|
|
520
|
+
' <p>This custom renderer has not implemented the requested schema form.</p>',
|
|
461
521
|
' <button',
|
|
462
522
|
' disabled={busy || request.status !== "pending"}',
|
|
463
523
|
' onClick={() => settle(client.respond(request.id, { action: "decline" }))}',
|
|
464
524
|
' >',
|
|
465
|
-
'
|
|
525
|
+
" Don't proceed",
|
|
526
|
+
' </button>',
|
|
527
|
+
' <button',
|
|
528
|
+
' disabled={busy || request.status !== "pending"}',
|
|
529
|
+
' onClick={() => settle(client.respond(request.id, { action: "cancel" }))}',
|
|
530
|
+
' >',
|
|
531
|
+
' Cancel',
|
|
466
532
|
' </button>',
|
|
467
533
|
' </section>',
|
|
468
534
|
' );',
|
|
469
535
|
' }',
|
|
470
536
|
' if (part.type === "data-tool-result") {',
|
|
471
537
|
' return (',
|
|
472
|
-
' <
|
|
473
|
-
'
|
|
474
|
-
' </
|
|
538
|
+
' <p key={part.data.id} role="status">',
|
|
539
|
+
' A result is available, but this renderer has no trusted presentation for it.',
|
|
540
|
+
' </p>',
|
|
475
541
|
' );',
|
|
476
542
|
' }',
|
|
477
543
|
' if (part.type === "data-view") {',
|
|
@@ -484,17 +550,67 @@ export function renderEmbeddedAssistantReference() {
|
|
|
484
550
|
' />',
|
|
485
551
|
' );',
|
|
486
552
|
' }',
|
|
553
|
+
' if (part.type === "data-sign-in") {',
|
|
554
|
+
' const request = part.data;',
|
|
555
|
+
' return (',
|
|
556
|
+
' <section key={request.id} aria-label="Sign in required">',
|
|
557
|
+
' <p>Continue with your account to use this capability.</p>',
|
|
558
|
+
' <button',
|
|
559
|
+
' disabled={busy || pendingSignInTicket !== undefined}',
|
|
560
|
+
' onClick={() => {',
|
|
561
|
+
' setPendingSignInTicket(request.signInTicket);',
|
|
562
|
+
' void Promise.resolve()',
|
|
563
|
+
' .then(() =>',
|
|
564
|
+
' onSignInRequested({',
|
|
565
|
+
' signInTicket: request.signInTicket,',
|
|
566
|
+
' expiresAt: request.expiresAt,',
|
|
567
|
+
' }),',
|
|
568
|
+
' )',
|
|
569
|
+
' .then(',
|
|
570
|
+
' (result) => {',
|
|
571
|
+
' if (result === "cancelled") setPendingSignInTicket(undefined);',
|
|
572
|
+
' },',
|
|
573
|
+
' () => setPendingSignInTicket(undefined),',
|
|
574
|
+
' );',
|
|
575
|
+
' }}',
|
|
576
|
+
' >',
|
|
577
|
+
' Sign in',
|
|
578
|
+
' </button>',
|
|
579
|
+
' </section>',
|
|
580
|
+
' );',
|
|
581
|
+
' }',
|
|
487
582
|
' return <p key={index}>Unsupported assistant content.</p>;',
|
|
488
583
|
' })}',
|
|
489
584
|
' </article>',
|
|
490
585
|
' ))}',
|
|
491
|
-
'
|
|
586
|
+
' <p role="status" aria-live="polite">',
|
|
587
|
+
' {sessionNotice || turnNotice || (busy ? "Assistant is working" : "")}',
|
|
588
|
+
' </p>',
|
|
589
|
+
' {suggestions?.prompts.length ? (',
|
|
590
|
+
' <nav aria-label="Suggested messages">',
|
|
591
|
+
' {suggestions.prompts.map((prompt) => (',
|
|
592
|
+
' <button',
|
|
593
|
+
' key={prompt}',
|
|
594
|
+
' type="button"',
|
|
595
|
+
' disabled={busy}',
|
|
596
|
+
' onClick={() => {',
|
|
597
|
+
' setTurnNotice("");',
|
|
598
|
+
' settle(client.sendMessage(prompt));',
|
|
599
|
+
' }}',
|
|
600
|
+
' >',
|
|
601
|
+
' {prompt}',
|
|
602
|
+
' </button>',
|
|
603
|
+
' ))}',
|
|
604
|
+
' </nav>',
|
|
605
|
+
' ) : null}',
|
|
606
|
+
' {error ? <p role="alert">The assistant could not complete that request.</p> : null}',
|
|
492
607
|
' <form',
|
|
493
608
|
' onSubmit={(event) => {',
|
|
494
609
|
' event.preventDefault();',
|
|
495
610
|
' const message = draft.trim();',
|
|
496
611
|
' if (!message) return;',
|
|
497
612
|
' setDraft("");',
|
|
613
|
+
' setTurnNotice("");',
|
|
498
614
|
' settle(client.sendMessage(message));',
|
|
499
615
|
' }}',
|
|
500
616
|
' >',
|
|
@@ -504,7 +620,15 @@ export function renderEmbeddedAssistantReference() {
|
|
|
504
620
|
' onChange={(event) => setDraft(event.currentTarget.value)}',
|
|
505
621
|
' />',
|
|
506
622
|
' {busy ? (',
|
|
507
|
-
' <button
|
|
623
|
+
' <button',
|
|
624
|
+
' type="button"',
|
|
625
|
+
' onClick={() => {',
|
|
626
|
+
' client.abort();',
|
|
627
|
+
' setTurnNotice("Response stopped. This does not undo a started action.");',
|
|
628
|
+
' }}',
|
|
629
|
+
' >',
|
|
630
|
+
' Stop',
|
|
631
|
+
' </button>',
|
|
508
632
|
' ) : (',
|
|
509
633
|
' <button type="submit">Send</button>',
|
|
510
634
|
' )}',
|
|
@@ -514,128 +638,25 @@ export function renderEmbeddedAssistantReference() {
|
|
|
514
638
|
'}',
|
|
515
639
|
'```',
|
|
516
640
|
'',
|
|
517
|
-
'`principalKey` is a browser-local identity for the authenticated user/tenant and is never sent to Noodle. Change it whenever that principal changes; the hook then aborts and clears the previous session and transcript. The hook does not register `<noodle-assistant>` or render Noodle markup.',
|
|
641
|
+
'`principalKey` is a browser-local identity for the authenticated user/tenant and is never sent to Noodle. Change it whenever that principal changes; the hook then aborts and clears the previous session and transcript. The hook does not register `<noodle-assistant>` or render Noodle markup. `suggestions` is hook-owned state: each prompt starts an ordinary message through the same guarded `client.sendMessage` path and disappears when the client clears it.',
|
|
518
642
|
'',
|
|
519
|
-
'
|
|
520
|
-
'',
|
|
521
|
-
'```tsx',
|
|
522
|
-
'useEffect(() => {',
|
|
523
|
-
' let active = true;',
|
|
524
|
-
' queueMicrotask(() => {',
|
|
525
|
-
' if (active) settle(client.sendMessage(initialMessage));',
|
|
526
|
-
' });',
|
|
527
|
-
' return () => {',
|
|
528
|
-
' active = false;',
|
|
529
|
-
' };',
|
|
530
|
-
'}, [client, initialMessage]);',
|
|
531
|
-
'```',
|
|
643
|
+
'Do not send a first turn on mount by default. React effect cleanup can suppress one provisional Strict Mode effect, but it cannot make a remount, dependency change, or client replacement idempotent. Require an explicit user action unless the host owns a durable one-shot state and an application idempotency key that makes repeated sends safe.',
|
|
532
644
|
'',
|
|
533
645
|
'`settle` must await or catch the command promise; the same structured failure also appears in the hook `error` state.',
|
|
534
646
|
'',
|
|
535
|
-
'
|
|
536
|
-
'',
|
|
537
|
-
'`<noodle-app-view>` owns one bridge for the semantic view identity: client + `view.id` + `view.resourceUri`; `NoodleAppView` delegates to it. The host retains the iframe across fresh payload/callback/theme rerenders, publishes later resolved-theme changes through MCP Apps host context, and sends standard App teardown when that semantic identity changes, the element disconnects, or the App requests teardown. App views remain inline by default; the host advertises only inline presentation and rejects widget fullscreen requests. Opt in with `allowFullscreen` on `NoodleAppView` or `allow-fullscreen` on `<noodle-app-view>` only when fullscreen is an intentional part of the customer-owned experience. When fullscreen is accepted, the shared host adds an accessible top-right exit control that returns the same mounted App to inline mode without losing its state. Pass the same resolved application theme used by the conversation shell. Do not key an ancestor by a view object or callback. If the embedding page sets Content-Security-Policy, include the Noodle service origin in both `connect-src` and `frame-src`.',
|
|
538
|
-
'',
|
|
539
|
-
'Outside React, use the same DOM-free client directly and import the isolated App-view entry only when rendering linked Apps. The client keeps the session token in memory, the transcript stays React-free, and the element owns only App presentation:',
|
|
540
|
-
'',
|
|
541
|
-
'```html',
|
|
542
|
-
'<noodle-app-view id="assistant-app-view"></noodle-app-view>',
|
|
543
|
-
'```',
|
|
544
|
-
'',
|
|
545
|
-
'```ts',
|
|
546
|
-
'import { createAssistantClient } from "@noodleseed/assistant/client";',
|
|
547
|
-
'import "@noodleseed/assistant/app-view";',
|
|
548
|
-
'',
|
|
549
|
-
'const assistant = createAssistantClient({',
|
|
550
|
-
' sessionEndpoint: "/api/assistant/session",',
|
|
551
|
-
' clientContext: () => ({',
|
|
552
|
-
' locale: navigator.language,',
|
|
553
|
-
' timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,',
|
|
554
|
-
' }),',
|
|
555
|
-
'});',
|
|
556
|
-
'',
|
|
557
|
-
'const appView = document.querySelector("#assistant-app-view");',
|
|
558
|
-
'if (!appView) throw new Error("Missing App view host");',
|
|
559
|
-
'appView.client = assistant;',
|
|
560
|
-
"appView.theme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';",
|
|
561
|
-
'',
|
|
562
|
-
'assistant.updateModelContext({',
|
|
563
|
-
" content: [{ type: 'text', text: 'The time-off form is mounted.' }],",
|
|
564
|
-
" structuredContent: { widget: { name: 'time-off', lifecycle: 'mounted' } },",
|
|
565
|
-
'});',
|
|
566
|
-
'',
|
|
567
|
-
'let pending: { id: string; requestedSchema?: Readonly<Record<string, unknown>> } | undefined;',
|
|
568
|
-
'assistant.subscribeChat((state) => {',
|
|
569
|
-
' renderUIMessageState(state);',
|
|
570
|
-
' pending = undefined;',
|
|
571
|
-
' for (const message of state.messages) {',
|
|
572
|
-
' for (const part of message.parts) {',
|
|
573
|
-
" if (part.type === 'data-confirmation' && part.data.status === 'pending') {",
|
|
574
|
-
' pending = { id: part.data.id };',
|
|
575
|
-
' }',
|
|
576
|
-
" if (part.type === 'data-input-request' && part.data.status === 'pending') {",
|
|
577
|
-
' pending = { id: part.data.id, requestedSchema: part.data.requestedSchema };',
|
|
578
|
-
' }',
|
|
579
|
-
" if (part.type === 'data-view') {",
|
|
580
|
-
' appView.view = part.data;',
|
|
581
|
-
' }',
|
|
582
|
-
' }',
|
|
583
|
-
' }',
|
|
584
|
-
'});',
|
|
647
|
+
'This sample deliberately does not render Confirm until the application supplies a complete schema-aware review of every non-sensitive business field. It also refuses accepted elicitation until a form covers the compiler-approved portable `requestedSchema` subset; decline and cancel remain available and distinct. Use the managed renderer instead of shipping either unsupported branch. A production custom renderer must handle text, suggestions, sign-in, confirmation, elicitation, tool results, linked views, streaming, cancellation, terminal interaction states, and unknown future parts without silently dropping content.',
|
|
585
648
|
'',
|
|
586
|
-
'
|
|
587
|
-
'if (pending) {',
|
|
588
|
-
' const requestedSchema = pending.requestedSchema;',
|
|
589
|
-
' const resolution = requestedSchema',
|
|
590
|
-
" ? { action: 'accept' as const, content: await renderPortableForm(requestedSchema) }",
|
|
591
|
-
" : { action: 'accept' as const };",
|
|
592
|
-
' await assistant.respond(pending.id, resolution);',
|
|
593
|
-
'}',
|
|
594
|
-
"// The same pending id also accepts { action: 'decline' } or { action: 'cancel' }.",
|
|
595
|
-
'```',
|
|
649
|
+
"`data-sign-in` is not an interaction and has no status: never pass its id to `client.respond`. Give `signInTicket` to an application callback that binds it to the host's existing short-lived login transaction; the authenticated backend spends it after login. Resolve the callback as `started` only after the host owns one active login transaction. Resolve it as `cancelled`, or reject it, when no transaction was started so the renderer can restore the sign-in affordance. A mixed custom renderer uses separate public and authenticated client mounts: the public shell starts with `embedId`, `serviceUrl`, and its visitor `principalKey`; after login, the authenticated destination mounts a new client against the same-origin `sessionEndpoint` with the user/tenant `principalKey`. Never pass both source options or mutate the public client source in place. The elevated session response owns bounded transcript repaint and optional resume. Never put the ticket in a query string, log, analytics event, or durable browser storage.",
|
|
596
650
|
'',
|
|
597
|
-
'`
|
|
651
|
+
'The `session_expired` notice is observational. The client owns its one pre-execution `401` re-exchange and message retry; never rebuild tokens or replay an interaction in renderer code. A retryable transport detail is diagnostic, not proof that resending an ordinary message or business action is safe, so the skeleton never offers a generic Retry button. Reconcile a lost interaction response only by repeating the same id and the same decision through an explicit application recovery affordance. Budget exhaustion and tenant-mismatch elevation never retry.',
|
|
598
652
|
'',
|
|
599
|
-
'For
|
|
653
|
+
'For `data-tool-result`, do not expose technical tool names or raw JSON as customer UI. Prefer the linked `data-view`; otherwise map a known bounded result to an application-trusted native component or keep the explicit unsupported-result state. For `data-view`, use the canonical `<noodle-app-view>` host (or its React `NoodleAppView` adapter). Never inject `part.data.html`, assign it to `srcdoc`, fetch a `ui://` URI, reproduce the bridge, wrap the client in another chat transport, or invent user messages for continuations.',
|
|
600
654
|
'',
|
|
601
|
-
'
|
|
602
|
-
'const activityByTool: Readonly<Record<string, string>> = {',
|
|
603
|
-
' list_open_items: "Loading open items",',
|
|
604
|
-
'};',
|
|
605
|
-
'const viewSlots = new Map([["ui://workspace/current", "current-workspace"]]);',
|
|
606
|
-
'',
|
|
607
|
-
'assistant.subscribe((event) => {',
|
|
608
|
-
' if (event.event === "tool_started") {',
|
|
609
|
-
' showActivity(event.data.id, activityByTool[event.data.tool] ?? "Working");',
|
|
610
|
-
' }',
|
|
611
|
-
' if (event.event === "view_available") {',
|
|
612
|
-
' const key = viewSlots.get(event.data.resourceUri) ?? `${event.data.id}:${event.data.resourceUri}`;',
|
|
613
|
-
' showReadyView(key, event.data);',
|
|
614
|
-
' }',
|
|
615
|
-
' if (event.event === "error") showActivityError(event.data.code);',
|
|
616
|
-
'});',
|
|
617
|
-
'```',
|
|
618
|
-
'',
|
|
619
|
-
'`data-view` means a completed tool has a linked MCP App view. Pass that typed part and the existing client to `<noodle-app-view>` in Vue, Angular, or plain DOM, or to its `NoodleAppView` React adapter. It retains one bridge for client + `view.id` + `view.resourceUri` and requests standard App teardown on semantic replacement, disconnect, or App request. That pair is transport identity: different call ids are distinct invocations and must not be deduplicated generically. If the product intentionally owns one current panel for a known resource, declare an application-owned slot map and replace only that slot. Deliberately map the bounded result to an application-trusted native component only when replacing the linked App UI.',
|
|
655
|
+
'`<noodle-app-view>` owns one bridge for the semantic view identity: client + `view.id` + `view.resourceUri`; `NoodleAppView` delegates to it. The host retains the iframe across fresh payload/callback/theme rerenders, publishes later resolved-theme changes through MCP Apps host context, and sends standard App teardown when that semantic identity changes, the element disconnects, or the App requests teardown. App views remain inline by default; the host advertises only inline presentation and rejects widget fullscreen requests. Opt in with `allowFullscreen` on `NoodleAppView` or `allow-fullscreen` on `<noodle-app-view>` only when fullscreen is an intentional part of the customer-owned experience. When fullscreen is accepted, the shared host adds an accessible top-right exit control that returns the same mounted App to inline mode without losing its state. Pass the same resolved application theme used by the conversation shell. Do not key an ancestor by a view object or callback. If the embedding page sets Content-Security-Policy, include the Noodle service origin in both `connect-src` and `frame-src`.',
|
|
620
656
|
'',
|
|
657
|
+
...renderEmbeddedAssistantRendererReference(),
|
|
621
658
|
'`clientContext` and typed `pageContext` are recomputed for each turn. `updateContext(...)` remains the legacy session-exchange context; `updatePageContext(...)` replaces the fresh per-turn application hint. `updateModelContext({ content, structuredContent })` publishes one cohesive renderer snapshot for later message turns without starting a turn; every call replaces the prior snapshot rather than merging fields. These are untrusted data, not conversation history or authorization input, and the boundaries reject credential-shaped or unbounded updates. A message may re-exchange once after a pre-execution `401`; the client never auto-retries interaction decisions. `tool_proposed.arguments` is a complete schema-aware review projection and, for connector-backed tools, names the sole exact connector version/operation/resolved arguments. Sensitive/write-only fields are redacted; truncating or omitting any non-sensitive action field fails closed. Accept is bound to the server-held action and claims at most one execution attempt—clients cannot replace it. Normal terminal outcomes scrub private arguments and continuations immediately; only an accepted action still executing retains them for the one-hour unknown-outcome recovery window, after which it records `interaction_outcome_unknown` and scrubs. Without downstream idempotency this is not an exactly-once business-effect guarantee. To reconcile a lost response, explicitly repeat the same id and decision: the service returns its durable stored outcome without re-execution.',
|
|
622
|
-
|
|
623
|
-
'## Host readiness and promotion',
|
|
624
|
-
'',
|
|
625
|
-
'Run the non-mutating host preflight from the embedding application before its production build:',
|
|
626
|
-
'',
|
|
627
|
-
'```sh',
|
|
628
|
-
'noodle assistant embed --check --json',
|
|
629
|
-
'noodle assistant embed --check --json --require-env EXAMPLE_DELEG_CLIENT_SECRET',
|
|
630
|
-
'```',
|
|
631
|
-
'',
|
|
632
|
-
'The check reports only required and missing environment names, never their values. Pass `--surface public|mixed|authenticated` to match the deployment: `public` drops the backend client id/secret requirement (a public embed has neither), and `public`/`mixed` additionally require `script-src` — the one directive whose failure runs no widget code at all, so nothing can report it from inside the page. CSP directives verify against the service origin exactly, via the env placeholder, or through a covering wildcard (`https://*.example.com`); a dynamic expression is marked unverified instead of guessed. Additional `--require-env` names are application-owned; `--env-alias NAME=HOST_NAME` follows a host repo that names an env var differently.',
|
|
633
|
-
'',
|
|
634
|
-
'Inspect the host repository for generated environment bindings after adding names. Run its existing generator, review the diff, commit generated types only when that repository requires them, then run the production-equivalent host build. Do not invent a framework command or add a second generator.',
|
|
635
|
-
'',
|
|
636
|
-
'Promotion checklist: provision each environment in the backend secret manager; map names through the CI environment and any secret allowlist or secrets file; run the presence-only host preflight; run the canonical deploy so its configuration preflight completes before asset upload; run the post-deploy probes from the JSON contract; rotate the assistant client and delegated credential independently, then rerun the same checks.',
|
|
637
|
-
'',
|
|
638
|
-
"Devtools privacy gate: default model and connector exercises to synthetic or mock data. Before Devtools Chat sends real connector data to an external model, disclose the data flow and obtain the user's approval. A local validation pass is not that approval.",
|
|
659
|
+
...renderEmbeddedAssistantOperationsReference(),
|
|
639
660
|
'',
|
|
640
661
|
'## Toolchain requirements',
|
|
641
662
|
'',
|