@noodleseed/agent-kit 0.91.0 → 0.93.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/manifest.json +743 -279
- package/package.json +1 -1
- package/skills/claude-code/SKILL.md +1 -1
- package/skills/claude-code/authoring-mcp-servers/SKILL.md +4 -4
- package/skills/claude-code/authoring-mcp-servers/references/authoring-workflow.md +451 -0
- package/skills/claude-code/authoring-mcp-servers/references/build-an-mcp-server.md +56 -0
- package/skills/claude-code/authoring-mcp-servers/references/sdk-surface.md +258 -0
- package/skills/claude-code/building-mcp-apps/SKILL.md +4 -4
- package/skills/claude-code/building-mcp-apps/references/build-an-mcp-app.md +53 -0
- package/skills/claude-code/building-mcp-apps/references/experience-design.md +155 -0
- package/skills/claude-code/building-mcp-apps/references/widgets-and-apps.md +277 -0
- package/skills/claude-code/connecting-apis-to-mcp/SKILL.md +3 -3
- package/skills/claude-code/connecting-apis-to-mcp/references/authoring-workflow.md +451 -0
- package/skills/claude-code/connecting-apis-to-mcp/references/connect-an-api.md +245 -0
- package/skills/claude-code/creating-product-agent-guides/SKILL.md +2 -2
- package/skills/claude-code/creating-product-agent-guides/references/product-agent-guides.md +55 -0
- package/skills/claude-code/debugging-mcp-delivery/SKILL.md +4 -4
- package/skills/claude-code/debugging-mcp-delivery/references/inspect-hosted.md +26 -0
- package/skills/claude-code/debugging-mcp-delivery/references/troubleshooting.md +39 -0
- package/skills/claude-code/debugging-mcp-delivery/references/verify-and-recover.md +67 -0
- package/skills/claude-code/deploying-mcp-services/SKILL.md +3 -3
- package/skills/claude-code/deploying-mcp-services/references/cli-commands.md +106 -0
- package/skills/claude-code/deploying-mcp-services/references/deploy-and-ops.md +25 -0
- package/skills/claude-code/designing-mcp-products/SKILL.md +3 -3
- package/skills/claude-code/designing-mcp-products/references/authoring-workflow.md +451 -0
- package/skills/claude-code/designing-mcp-products/references/experience-design.md +155 -0
- package/skills/claude-code/embedding-mcp-assistants/SKILL.md +11 -10
- package/skills/claude-code/embedding-mcp-assistants/references/authoring-workflow.md +451 -0
- package/skills/claude-code/embedding-mcp-assistants/references/embedded-assistant.md +876 -0
- package/skills/claude-code/examples/acme-discovery/README.md +39 -1
- package/skills/claude-code/examples/acme-discovery/site/index.html +189 -0
- package/skills/claude-code/examples/acme-discovery/src/server.ts +5 -0
- package/skills/claude-code/examples/acme-discovery/test/server.test.ts +18 -0
- package/skills/claude-code/examples/acme-discovery/test/site-page.test.ts +50 -0
- package/skills/claude-code/examples/customer-auth/README.md +200 -52
- package/skills/claude-code/examples/customer-auth/src/server.ts +3 -0
- package/skills/claude-code/examples/customer-auth/test/server.test.ts +6 -0
- package/skills/claude-code/examples/customer-auth/vitest.config.ts +6 -0
- package/skills/claude-code/examples/food-ordering/README.md +1 -1
- package/skills/claude-code/examples/food-ordering/src/server.ts +1 -0
- package/skills/claude-code/executing-noodle-plans/SKILL.md +1 -1
- package/skills/claude-code/publishing-mcp-integrations/SKILL.md +3 -3
- package/skills/claude-code/publishing-mcp-integrations/references/app-directory-compliance.md +59 -0
- package/skills/claude-code/publishing-mcp-integrations/references/publishing.md +63 -0
- package/skills/claude-code/references/authoring-workflow.md +8 -0
- package/skills/claude-code/references/embedded-assistant.md +258 -66
- package/skills/claude-code/references/sdk-surface.md +1 -1
- package/skills/claude-code/reporting-noodle-feedback/SKILL.md +2 -2
- package/skills/claude-code/reporting-noodle-feedback/references/feedback.md +80 -0
- package/skills/claude-code/verifying-mcp-delivery/SKILL.md +3 -3
- package/skills/claude-code/verifying-mcp-delivery/references/test-in-hosts.md +39 -0
- package/skills/claude-code/verifying-mcp-delivery/references/verify-and-recover.md +67 -0
- package/skills/claude-code/wrapping-existing-applications/SKILL.md +4 -4
- package/skills/claude-code/wrapping-existing-applications/references/authoring-workflow.md +451 -0
- package/skills/claude-code/wrapping-existing-applications/references/tool-design.md +107 -0
- package/skills/claude-code/wrapping-existing-applications/references/wrap-existing-app.md +65 -0
- package/skills/codex/SKILL.md +1 -1
- package/skills/codex/authoring-mcp-servers/SKILL.md +4 -4
- package/skills/codex/authoring-mcp-servers/references/authoring-workflow.md +451 -0
- package/skills/codex/authoring-mcp-servers/references/build-an-mcp-server.md +56 -0
- package/skills/codex/authoring-mcp-servers/references/sdk-surface.md +258 -0
- package/skills/codex/building-mcp-apps/SKILL.md +4 -4
- package/skills/codex/building-mcp-apps/references/build-an-mcp-app.md +53 -0
- package/skills/codex/building-mcp-apps/references/experience-design.md +155 -0
- package/skills/codex/building-mcp-apps/references/widgets-and-apps.md +277 -0
- package/skills/codex/connecting-apis-to-mcp/SKILL.md +3 -3
- package/skills/codex/connecting-apis-to-mcp/references/authoring-workflow.md +451 -0
- package/skills/codex/connecting-apis-to-mcp/references/connect-an-api.md +245 -0
- package/skills/codex/creating-product-agent-guides/SKILL.md +2 -2
- package/skills/codex/creating-product-agent-guides/references/product-agent-guides.md +55 -0
- package/skills/codex/debugging-mcp-delivery/SKILL.md +4 -4
- package/skills/codex/debugging-mcp-delivery/references/inspect-hosted.md +26 -0
- package/skills/codex/debugging-mcp-delivery/references/troubleshooting.md +39 -0
- package/skills/codex/debugging-mcp-delivery/references/verify-and-recover.md +67 -0
- package/skills/codex/deploying-mcp-services/SKILL.md +3 -3
- package/skills/codex/deploying-mcp-services/references/cli-commands.md +106 -0
- package/skills/codex/deploying-mcp-services/references/deploy-and-ops.md +25 -0
- package/skills/codex/designing-mcp-products/SKILL.md +3 -3
- package/skills/codex/designing-mcp-products/references/authoring-workflow.md +451 -0
- package/skills/codex/designing-mcp-products/references/experience-design.md +155 -0
- package/skills/codex/embedding-mcp-assistants/SKILL.md +11 -10
- package/skills/codex/embedding-mcp-assistants/references/authoring-workflow.md +451 -0
- package/skills/codex/embedding-mcp-assistants/references/embedded-assistant.md +876 -0
- package/skills/codex/examples/acme-discovery/README.md +39 -1
- package/skills/codex/examples/acme-discovery/site/index.html +189 -0
- package/skills/codex/examples/acme-discovery/src/server.ts +5 -0
- package/skills/codex/examples/acme-discovery/test/server.test.ts +18 -0
- package/skills/codex/examples/acme-discovery/test/site-page.test.ts +50 -0
- package/skills/codex/examples/customer-auth/README.md +200 -52
- package/skills/codex/examples/customer-auth/src/server.ts +3 -0
- package/skills/codex/examples/customer-auth/test/server.test.ts +6 -0
- package/skills/codex/examples/customer-auth/vitest.config.ts +6 -0
- package/skills/codex/examples/food-ordering/README.md +1 -1
- package/skills/codex/examples/food-ordering/src/server.ts +1 -0
- package/skills/codex/executing-noodle-plans/SKILL.md +1 -1
- package/skills/codex/publishing-mcp-integrations/SKILL.md +3 -3
- package/skills/codex/publishing-mcp-integrations/references/app-directory-compliance.md +59 -0
- package/skills/codex/publishing-mcp-integrations/references/publishing.md +63 -0
- package/skills/codex/references/authoring-workflow.md +8 -0
- package/skills/codex/references/embedded-assistant.md +258 -66
- package/skills/codex/references/sdk-surface.md +1 -1
- package/skills/codex/reporting-noodle-feedback/SKILL.md +2 -2
- package/skills/codex/reporting-noodle-feedback/references/feedback.md +80 -0
- package/skills/codex/verifying-mcp-delivery/SKILL.md +3 -3
- package/skills/codex/verifying-mcp-delivery/references/test-in-hosts.md +39 -0
- package/skills/codex/verifying-mcp-delivery/references/verify-and-recover.md +67 -0
- package/skills/codex/wrapping-existing-applications/SKILL.md +4 -4
- package/skills/codex/wrapping-existing-applications/references/authoring-workflow.md +451 -0
- package/skills/codex/wrapping-existing-applications/references/tool-design.md +107 -0
- package/skills/codex/wrapping-existing-applications/references/wrap-existing-app.md +65 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
## Contents
|
|
4
4
|
|
|
5
5
|
- Architecture
|
|
6
|
-
-
|
|
6
|
+
- Select the architecture before code
|
|
7
7
|
- Author and validate
|
|
8
8
|
- Product workflow guidance
|
|
9
9
|
- Customize the presentation
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
- The session response
|
|
17
17
|
- Choose a browser renderer
|
|
18
18
|
- Host readiness and promotion
|
|
19
|
+
- Launch and qualified-usage proof
|
|
19
20
|
- Toolchain requirements
|
|
20
21
|
- Verify the boundary
|
|
21
22
|
- Troubleshooting: symptom to diagnosis
|
|
@@ -35,9 +36,32 @@ Keep every credential and identity layer separate:
|
|
|
35
36
|
| 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 |
|
|
36
37
|
| Browser | Short-lived assistant session only | In memory; never a client secret, model key, connector credential, or raw application session |
|
|
37
38
|
|
|
38
|
-
##
|
|
39
|
+
## Select the architecture before code
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
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.
|
|
42
|
+
|
|
43
|
+
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.
|
|
44
|
+
|
|
45
|
+
Treat the MCP application and embedding application as two code ownership roots, usually two repositories and sometimes two explicitly separate directories in one repository:
|
|
46
|
+
|
|
47
|
+
| Code owner | Owns | Must not own |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| 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 |
|
|
50
|
+
| 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 |
|
|
51
|
+
|
|
52
|
+
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.
|
|
53
|
+
|
|
54
|
+
Select exactly one recommended topology; do not return an unranked menu:
|
|
55
|
+
|
|
56
|
+
| User boundary | Recommended topology | Identity and routing consequence |
|
|
57
|
+
| --- | --- | --- |
|
|
58
|
+
| 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. |
|
|
59
|
+
| 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. |
|
|
60
|
+
| 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. |
|
|
61
|
+
|
|
62
|
+
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.
|
|
63
|
+
|
|
64
|
+
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.
|
|
41
65
|
|
|
42
66
|
## Author and validate
|
|
43
67
|
|
|
@@ -117,11 +141,17 @@ labels: {
|
|
|
117
141
|
|
|
118
142
|
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).
|
|
119
143
|
|
|
120
|
-
|
|
144
|
+
#### Complete mixed-mode handoff
|
|
145
|
+
|
|
146
|
+
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.
|
|
147
|
+
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.
|
|
148
|
+
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.
|
|
149
|
+
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.
|
|
150
|
+
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.
|
|
121
151
|
|
|
122
|
-
|
|
152
|
+
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.
|
|
123
153
|
|
|
124
|
-
|
|
154
|
+
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.
|
|
125
155
|
|
|
126
156
|
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.
|
|
127
157
|
|
|
@@ -216,6 +246,10 @@ Read the customer repository lockfile or `packageManager` field and install `@no
|
|
|
216
246
|
|
|
217
247
|
Create an authenticated same-origin backend route:
|
|
218
248
|
|
|
249
|
+
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.
|
|
250
|
+
|
|
251
|
+
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.
|
|
252
|
+
|
|
219
253
|
```ts
|
|
220
254
|
import { createAssistantSession } from "@noodleseed/assistant/server";
|
|
221
255
|
|
|
@@ -393,7 +427,9 @@ That custom element is the complete managed assistant in Vue, Angular, or plain
|
|
|
393
427
|
|
|
394
428
|
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`.
|
|
395
429
|
|
|
396
|
-
|
|
430
|
+
### Minimal fail-closed custom renderer skeleton
|
|
431
|
+
|
|
432
|
+
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.
|
|
397
433
|
|
|
398
434
|
```tsx
|
|
399
435
|
"use client";
|
|
@@ -402,9 +438,22 @@ import { useEffect, useState } from "react";
|
|
|
402
438
|
import { NoodleAppView } from "@noodleseed/assistant/react";
|
|
403
439
|
import { useNoodleAssistant } from "@noodleseed/assistant/react/client";
|
|
404
440
|
|
|
405
|
-
|
|
441
|
+
type SignInRequest = { readonly signInTicket: string; readonly expiresAt: string };
|
|
442
|
+
|
|
443
|
+
export function CustomAssistant({
|
|
444
|
+
principalKey,
|
|
445
|
+
resolvedTheme,
|
|
446
|
+
onSignInRequested,
|
|
447
|
+
}: {
|
|
448
|
+
principalKey: string;
|
|
449
|
+
resolvedTheme: "light" | "dark";
|
|
450
|
+
onSignInRequested: (request: SignInRequest) => Promise<"started" | "cancelled">;
|
|
451
|
+
}) {
|
|
406
452
|
const [draft, setDraft] = useState("");
|
|
407
|
-
const
|
|
453
|
+
const [sessionNotice, setSessionNotice] = useState("");
|
|
454
|
+
const [turnNotice, setTurnNotice] = useState("");
|
|
455
|
+
const [pendingSignInTicket, setPendingSignInTicket] = useState<string>();
|
|
456
|
+
const { client, messages, suggestions, status, error } = useNoodleAssistant({
|
|
408
457
|
sessionEndpoint: "/api/assistant/session",
|
|
409
458
|
principalKey,
|
|
410
459
|
clientContext: () => ({
|
|
@@ -418,6 +467,16 @@ export function CustomAssistant({ principalKey, resolvedTheme }: { principalKey:
|
|
|
418
467
|
// The hook exposes this same structured failure through `error`.
|
|
419
468
|
});
|
|
420
469
|
};
|
|
470
|
+
useEffect(
|
|
471
|
+
() =>
|
|
472
|
+
client.subscribe((event) => {
|
|
473
|
+
if (event.event === "session_expired") setSessionNotice("Session expired.");
|
|
474
|
+
if (event.event === "session_started" || event.event === "session_reset") {
|
|
475
|
+
setSessionNotice("");
|
|
476
|
+
}
|
|
477
|
+
}),
|
|
478
|
+
[client],
|
|
479
|
+
);
|
|
421
480
|
|
|
422
481
|
return (
|
|
423
482
|
<section aria-label="Assistant" aria-busy={busy}>
|
|
@@ -431,20 +490,18 @@ export function CustomAssistant({ principalKey, resolvedTheme }: { principalKey:
|
|
|
431
490
|
<section key={review.id} aria-label="Review proposed action">
|
|
432
491
|
<h3>{review.title ?? "Review proposed action"}</h3>
|
|
433
492
|
{review.description ? <p>{review.description}</p> : null}
|
|
434
|
-
<
|
|
435
|
-
{JSON.stringify(review.arguments ?? {}, null, 2)}
|
|
436
|
-
</pre>
|
|
493
|
+
<p>This custom renderer has not implemented a complete schema-aware review.</p>
|
|
437
494
|
<button
|
|
438
495
|
disabled={busy || review.status !== "pending"}
|
|
439
|
-
onClick={() => settle(client.respond(review.id, { action: "
|
|
496
|
+
onClick={() => settle(client.respond(review.id, { action: "decline" }))}
|
|
440
497
|
>
|
|
441
|
-
|
|
498
|
+
Don't proceed
|
|
442
499
|
</button>
|
|
443
500
|
<button
|
|
444
501
|
disabled={busy || review.status !== "pending"}
|
|
445
|
-
onClick={() => settle(client.respond(review.id, { action: "
|
|
502
|
+
onClick={() => settle(client.respond(review.id, { action: "cancel" }))}
|
|
446
503
|
>
|
|
447
|
-
|
|
504
|
+
Cancel
|
|
448
505
|
</button>
|
|
449
506
|
</section>
|
|
450
507
|
);
|
|
@@ -454,21 +511,28 @@ export function CustomAssistant({ principalKey, resolvedTheme }: { principalKey:
|
|
|
454
511
|
return (
|
|
455
512
|
<section key={request.id} aria-label="Assistant needs input">
|
|
456
513
|
<p>{request.message}</p>
|
|
457
|
-
|
|
514
|
+
{/* request.requestedSchema is the sole input-form contract. */}
|
|
515
|
+
<p>This custom renderer has not implemented the requested schema form.</p>
|
|
458
516
|
<button
|
|
459
517
|
disabled={busy || request.status !== "pending"}
|
|
460
518
|
onClick={() => settle(client.respond(request.id, { action: "decline" }))}
|
|
461
519
|
>
|
|
462
|
-
|
|
520
|
+
Don't proceed
|
|
521
|
+
</button>
|
|
522
|
+
<button
|
|
523
|
+
disabled={busy || request.status !== "pending"}
|
|
524
|
+
onClick={() => settle(client.respond(request.id, { action: "cancel" }))}
|
|
525
|
+
>
|
|
526
|
+
Cancel
|
|
463
527
|
</button>
|
|
464
528
|
</section>
|
|
465
529
|
);
|
|
466
530
|
}
|
|
467
531
|
if (part.type === "data-tool-result") {
|
|
468
532
|
return (
|
|
469
|
-
<
|
|
470
|
-
|
|
471
|
-
</
|
|
533
|
+
<p key={part.data.id} role="status">
|
|
534
|
+
A result is available, but this renderer has no trusted presentation for it.
|
|
535
|
+
</p>
|
|
472
536
|
);
|
|
473
537
|
}
|
|
474
538
|
if (part.type === "data-view") {
|
|
@@ -481,17 +545,67 @@ export function CustomAssistant({ principalKey, resolvedTheme }: { principalKey:
|
|
|
481
545
|
/>
|
|
482
546
|
);
|
|
483
547
|
}
|
|
548
|
+
if (part.type === "data-sign-in") {
|
|
549
|
+
const request = part.data;
|
|
550
|
+
return (
|
|
551
|
+
<section key={request.id} aria-label="Sign in required">
|
|
552
|
+
<p>Continue with your account to use this capability.</p>
|
|
553
|
+
<button
|
|
554
|
+
disabled={busy || pendingSignInTicket !== undefined}
|
|
555
|
+
onClick={() => {
|
|
556
|
+
setPendingSignInTicket(request.signInTicket);
|
|
557
|
+
void Promise.resolve()
|
|
558
|
+
.then(() =>
|
|
559
|
+
onSignInRequested({
|
|
560
|
+
signInTicket: request.signInTicket,
|
|
561
|
+
expiresAt: request.expiresAt,
|
|
562
|
+
}),
|
|
563
|
+
)
|
|
564
|
+
.then(
|
|
565
|
+
(result) => {
|
|
566
|
+
if (result === "cancelled") setPendingSignInTicket(undefined);
|
|
567
|
+
},
|
|
568
|
+
() => setPendingSignInTicket(undefined),
|
|
569
|
+
);
|
|
570
|
+
}}
|
|
571
|
+
>
|
|
572
|
+
Sign in
|
|
573
|
+
</button>
|
|
574
|
+
</section>
|
|
575
|
+
);
|
|
576
|
+
}
|
|
484
577
|
return <p key={index}>Unsupported assistant content.</p>;
|
|
485
578
|
})}
|
|
486
579
|
</article>
|
|
487
580
|
))}
|
|
488
|
-
|
|
581
|
+
<p role="status" aria-live="polite">
|
|
582
|
+
{sessionNotice || turnNotice || (busy ? "Assistant is working" : "")}
|
|
583
|
+
</p>
|
|
584
|
+
{suggestions?.prompts.length ? (
|
|
585
|
+
<nav aria-label="Suggested messages">
|
|
586
|
+
{suggestions.prompts.map((prompt) => (
|
|
587
|
+
<button
|
|
588
|
+
key={prompt}
|
|
589
|
+
type="button"
|
|
590
|
+
disabled={busy}
|
|
591
|
+
onClick={() => {
|
|
592
|
+
setTurnNotice("");
|
|
593
|
+
settle(client.sendMessage(prompt));
|
|
594
|
+
}}
|
|
595
|
+
>
|
|
596
|
+
{prompt}
|
|
597
|
+
</button>
|
|
598
|
+
))}
|
|
599
|
+
</nav>
|
|
600
|
+
) : null}
|
|
601
|
+
{error ? <p role="alert">The assistant could not complete that request.</p> : null}
|
|
489
602
|
<form
|
|
490
603
|
onSubmit={(event) => {
|
|
491
604
|
event.preventDefault();
|
|
492
605
|
const message = draft.trim();
|
|
493
606
|
if (!message) return;
|
|
494
607
|
setDraft("");
|
|
608
|
+
setTurnNotice("");
|
|
495
609
|
settle(client.sendMessage(message));
|
|
496
610
|
}}
|
|
497
611
|
>
|
|
@@ -501,7 +615,15 @@ export function CustomAssistant({ principalKey, resolvedTheme }: { principalKey:
|
|
|
501
615
|
onChange={(event) => setDraft(event.currentTarget.value)}
|
|
502
616
|
/>
|
|
503
617
|
{busy ? (
|
|
504
|
-
<button
|
|
618
|
+
<button
|
|
619
|
+
type="button"
|
|
620
|
+
onClick={() => {
|
|
621
|
+
client.abort();
|
|
622
|
+
setTurnNotice("Response stopped. This does not undo a started action.");
|
|
623
|
+
}}
|
|
624
|
+
>
|
|
625
|
+
Stop
|
|
626
|
+
</button>
|
|
505
627
|
) : (
|
|
506
628
|
<button type="submit">Send</button>
|
|
507
629
|
)}
|
|
@@ -511,37 +633,37 @@ export function CustomAssistant({ principalKey, resolvedTheme }: { principalKey:
|
|
|
511
633
|
}
|
|
512
634
|
```
|
|
513
635
|
|
|
514
|
-
`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.
|
|
636
|
+
`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.
|
|
515
637
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
```tsx
|
|
519
|
-
useEffect(() => {
|
|
520
|
-
let active = true;
|
|
521
|
-
queueMicrotask(() => {
|
|
522
|
-
if (active) settle(client.sendMessage(initialMessage));
|
|
523
|
-
});
|
|
524
|
-
return () => {
|
|
525
|
-
active = false;
|
|
526
|
-
};
|
|
527
|
-
}, [client, initialMessage]);
|
|
528
|
-
```
|
|
638
|
+
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.
|
|
529
639
|
|
|
530
640
|
`settle` must await or catch the command promise; the same structured failure also appears in the hook `error` state.
|
|
531
641
|
|
|
532
|
-
|
|
642
|
+
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.
|
|
643
|
+
|
|
644
|
+
`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.
|
|
645
|
+
|
|
646
|
+
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.
|
|
647
|
+
|
|
648
|
+
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.
|
|
533
649
|
|
|
534
650
|
`<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`.
|
|
535
651
|
|
|
536
|
-
|
|
652
|
+
### Framework-neutral DOM client
|
|
653
|
+
|
|
654
|
+
Use the same DOM-free client directly without a component wrapper, and import the isolated App-view entry only when rendering linked Apps. The client keeps the session token in memory, and the element owns only App presentation:
|
|
537
655
|
|
|
538
656
|
```html
|
|
539
|
-
<
|
|
657
|
+
<div id="assistant-app-views"></div>
|
|
540
658
|
```
|
|
541
659
|
|
|
542
660
|
```ts
|
|
543
661
|
import { createAssistantClient } from "@noodleseed/assistant/client";
|
|
544
662
|
import "@noodleseed/assistant/app-view";
|
|
663
|
+
import {
|
|
664
|
+
type AssistantViewAvailableDetail,
|
|
665
|
+
type NoodleAppViewElement,
|
|
666
|
+
} from "@noodleseed/assistant/app-view";
|
|
545
667
|
|
|
546
668
|
const assistant = createAssistantClient({
|
|
547
669
|
sessionEndpoint: "/api/assistant/session",
|
|
@@ -551,47 +673,62 @@ const assistant = createAssistantClient({
|
|
|
551
673
|
}),
|
|
552
674
|
});
|
|
553
675
|
|
|
554
|
-
const
|
|
555
|
-
if (!
|
|
556
|
-
|
|
557
|
-
|
|
676
|
+
const appViews = document.querySelector("#assistant-app-views");
|
|
677
|
+
if (!appViews) throw new Error("Missing App views host");
|
|
678
|
+
const mountedViews = new Map<string, NoodleAppViewElement>();
|
|
679
|
+
const readResolvedTheme = (): "light" | "dark" =>
|
|
680
|
+
document.documentElement.classList.contains("dark") ? "dark" : "light";
|
|
681
|
+
let resolvedTheme: "light" | "dark" = readResolvedTheme();
|
|
682
|
+
const syncResolvedTheme = () => {
|
|
683
|
+
resolvedTheme = readResolvedTheme();
|
|
684
|
+
for (const mountedView of mountedViews.values()) mountedView.theme = resolvedTheme;
|
|
685
|
+
};
|
|
686
|
+
new MutationObserver(syncResolvedTheme).observe(document.documentElement, {
|
|
687
|
+
attributes: true,
|
|
688
|
+
attributeFilter: ["class"],
|
|
689
|
+
});
|
|
690
|
+
const appViewFor = (view: AssistantViewAvailableDetail) => {
|
|
691
|
+
const key = `${view.id}:${view.resourceUri}`;
|
|
692
|
+
let appView = mountedViews.get(key);
|
|
693
|
+
if (!appView) {
|
|
694
|
+
appView = document.createElement("noodle-app-view") as NoodleAppViewElement;
|
|
695
|
+
appView.client = assistant;
|
|
696
|
+
mountedViews.set(key, appView);
|
|
697
|
+
appViews.append(appView);
|
|
698
|
+
}
|
|
699
|
+
appView.theme = resolvedTheme;
|
|
700
|
+
return appView;
|
|
701
|
+
};
|
|
558
702
|
|
|
559
703
|
assistant.updateModelContext({
|
|
560
704
|
content: [{ type: 'text', text: 'The time-off form is mounted.' }],
|
|
561
705
|
structuredContent: { widget: { name: 'time-off', lifecycle: 'mounted' } },
|
|
562
706
|
});
|
|
563
707
|
|
|
564
|
-
let pending: { id: string; requestedSchema?: Readonly<Record<string, unknown>> } | undefined;
|
|
565
708
|
assistant.subscribeChat((state) => {
|
|
566
|
-
renderUIMessageState(state
|
|
567
|
-
|
|
709
|
+
renderUIMessageState(state, {
|
|
710
|
+
respond: (id, response) => assistant.respond(id, response),
|
|
711
|
+
});
|
|
712
|
+
const activeViewKeys = new Set<string>();
|
|
568
713
|
for (const message of state.messages) {
|
|
569
714
|
for (const part of message.parts) {
|
|
570
|
-
if (part.type === 'data-confirmation' && part.data.status === 'pending') {
|
|
571
|
-
pending = { id: part.data.id };
|
|
572
|
-
}
|
|
573
|
-
if (part.type === 'data-input-request' && part.data.status === 'pending') {
|
|
574
|
-
pending = { id: part.data.id, requestedSchema: part.data.requestedSchema };
|
|
575
|
-
}
|
|
576
715
|
if (part.type === 'data-view') {
|
|
577
|
-
|
|
716
|
+
const key = `${part.data.id}:${part.data.resourceUri}`;
|
|
717
|
+
activeViewKeys.add(key);
|
|
718
|
+
appViewFor(part.data).view = part.data;
|
|
578
719
|
}
|
|
579
720
|
}
|
|
580
721
|
}
|
|
722
|
+
for (const [key, mountedView] of mountedViews) {
|
|
723
|
+
if (!activeViewKeys.has(key)) {
|
|
724
|
+
mountedView.remove();
|
|
725
|
+
mountedViews.delete(key);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
581
728
|
});
|
|
582
|
-
|
|
583
|
-
await assistant.sendMessage("Book next Thursday and Friday off");
|
|
584
|
-
if (pending) {
|
|
585
|
-
const requestedSchema = pending.requestedSchema;
|
|
586
|
-
const resolution = requestedSchema
|
|
587
|
-
? { action: 'accept' as const, content: await renderPortableForm(requestedSchema) }
|
|
588
|
-
: { action: 'accept' as const };
|
|
589
|
-
await assistant.respond(pending.id, resolution);
|
|
590
|
-
}
|
|
591
|
-
// The same pending id also accepts { action: 'decline' } or { action: 'cancel' }.
|
|
592
729
|
```
|
|
593
730
|
|
|
594
|
-
`subscribeChat` immediately emits a detached `{ messages, status, error? }` snapshot and then emits as `UIMessage.parts` change. Text uses `text`; Noodle confirmations, input requests, tool results, and linked views use `data-confirmation`, `data-input-request`, `data-tool-result`, and `data-view`. Interaction data moves through pending/submitting/accepted/declined/cancelled. Use raw `subscribe(...)` only for transport/session lifecycle events that are not transcript content.
|
|
731
|
+
`subscribeChat` immediately emits a detached `{ messages, status, error? }` snapshot and then emits as `UIMessage.parts` change. Text uses `text`; Noodle confirmations, input requests, sign-in requests, tool results, and linked views use `data-confirmation`, `data-input-request`, `data-sign-in`, `data-tool-result`, and `data-view`. `renderUIMessageState` is application code: it must show a complete schema-aware review or requested form and require an explicit user gesture before calling the supplied `respond`. Never call `respond` while scanning a transcript snapshot. Interaction data moves through pending/submitting/accepted/declined/cancelled. Use raw `subscribe(...)` only for transport/session lifecycle events that are not transcript content.
|
|
595
732
|
|
|
596
733
|
For a chat-first custom host, raw `tool_started` carries the direct invocation call `id` and technical `tool` name before execution. Map known tools through a finite application-owned label table and use a neutral fallback such as "Working"; never mechanically expose an internal identifier as customer copy. Reserve one stable region with `role="status"` and `aria-live="polite"` for thinking, the mapped activity label, and the linked-view skeleton. `view_available` makes the view ready; raw `error` or chat `error` replaces it with `role="alert"`. Mark decorative skeleton shapes `aria-hidden="true"` and disable shimmer/transitions under `@media (prefers-reduced-motion: reduce)`.
|
|
597
734
|
|
|
@@ -613,7 +750,7 @@ assistant.subscribe((event) => {
|
|
|
613
750
|
});
|
|
614
751
|
```
|
|
615
752
|
|
|
616
|
-
`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
|
|
753
|
+
`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 another component framework 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.
|
|
617
754
|
|
|
618
755
|
`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.
|
|
619
756
|
|
|
@@ -628,12 +765,67 @@ noodle assistant embed --check --json --require-env EXAMPLE_DELEG_CLIENT_SECRET
|
|
|
628
765
|
|
|
629
766
|
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.
|
|
630
767
|
|
|
768
|
+
Read `evidence.levels` in order and stop at `evidence.firstUnproven`:
|
|
769
|
+
|
|
770
|
+
| Level | What can prove it |
|
|
771
|
+
| --- | --- |
|
|
772
|
+
| 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 |
|
|
773
|
+
| 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 |
|
|
774
|
+
| Hosted session | `assistant doctor` plus one representative routed safe read against the exact deployment |
|
|
775
|
+
| Production browser | A real deployed browser completing keyboard submission, session exchange, one streamed turn, one interaction, and one linked App with console/network inspection |
|
|
776
|
+
| Operations | The launch, rollback, rotation, budget, usage, and qualified-outcome checks below |
|
|
777
|
+
|
|
778
|
+
`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.
|
|
779
|
+
|
|
780
|
+
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.
|
|
781
|
+
|
|
782
|
+
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.
|
|
783
|
+
|
|
631
784
|
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.
|
|
632
785
|
|
|
633
786
|
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.
|
|
634
787
|
|
|
635
788
|
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.
|
|
636
789
|
|
|
790
|
+
## Launch and qualified-usage proof
|
|
791
|
+
|
|
792
|
+
Treat deployed capability, production-browser proof, actual qualified usage, and measured customer outcome as four separate claims. Passing one never proves the next.
|
|
793
|
+
|
|
794
|
+
### Pre-launch checklist
|
|
795
|
+
|
|
796
|
+
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.
|
|
797
|
+
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.
|
|
798
|
+
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.
|
|
799
|
+
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.
|
|
800
|
+
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.
|
|
801
|
+
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.
|
|
802
|
+
7. Have the customer workflow owner execute and sign off the behavioral acceptance fixture below against the production-equivalent environment.
|
|
803
|
+
|
|
804
|
+
### Customer-owned behavioral acceptance fixture
|
|
805
|
+
|
|
806
|
+
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.
|
|
807
|
+
|
|
808
|
+
| Case | Customer-owned setup | Expected observable outcome |
|
|
809
|
+
| --- | --- | --- |
|
|
810
|
+
| normal | One common, authorized job with known data | Completes the named task and presents the business result or linked App clearly |
|
|
811
|
+
| ambiguous | A request with two materially different interpretations | Asks the smallest useful clarifying question before action |
|
|
812
|
+
| 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 |
|
|
813
|
+
| unauthorized | Use a real test role lacking the required scope | Refuses or withholds the capability without leaking its result, arguments, or internal tool name |
|
|
814
|
+
| 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 |
|
|
815
|
+
| 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 |
|
|
816
|
+
| 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 |
|
|
817
|
+
|
|
818
|
+
### Post-launch checklist
|
|
819
|
+
|
|
820
|
+
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.
|
|
821
|
+
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.
|
|
822
|
+
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.
|
|
823
|
+
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.
|
|
824
|
+
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.
|
|
825
|
+
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.
|
|
826
|
+
|
|
827
|
+
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.
|
|
828
|
+
|
|
637
829
|
## Toolchain requirements
|
|
638
830
|
|
|
639
831
|
- Node.js 20+ for `@noodleseed/assistant/server`.
|
|
@@ -79,7 +79,7 @@ Platform helper connectors are explicit subpath imports from `@noodleseed/one/pl
|
|
|
79
79
|
- `resource(name, { uri, description?, mimeType?, fulfil })` and `prompt(name, { description?, arguments?, fulfil })` expose MCP resources/prompts.
|
|
80
80
|
- View metadata (`viewTitle`, `viewDescription`, `csp`, `domain`, `permissions`) belongs on the tool that renders it; `asset("./path")` packages local files.
|
|
81
81
|
- `customerAuth.*(...)` belongs in `server` options when deployed customer callers need verified identity; inspect `examples/customer-auth` or `examples/sharepoint` before using it.
|
|
82
|
-
- `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.
|
|
82
|
+
- `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.
|
|
83
83
|
|
|
84
84
|
## Recipes
|
|
85
85
|
|
|
@@ -3,7 +3,7 @@ name: reporting-noodle-feedback
|
|
|
3
3
|
description: "Use when a Noodle Seed bug, misleading instruction, missing capability, or concrete product improvement should be proposed to the user."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.93.0 hash:660cc6ad9469d90f -->
|
|
7
7
|
|
|
8
8
|
# reporting-noodle-feedback
|
|
9
9
|
|
|
@@ -27,7 +27,7 @@ Preview one sanitized feedback proposal and submit it once only after informed e
|
|
|
27
27
|
|
|
28
28
|
## Workflow
|
|
29
29
|
|
|
30
|
-
Read and follow the canonical playbook
|
|
30
|
+
Read and follow the canonical playbook at `references/feedback.md`. It owns the workflow; do not recreate it here or load the command catalog speculatively.
|
|
31
31
|
|
|
32
32
|
## Verification evidence
|
|
33
33
|
|