@noodleseed/agent-kit 0.90.0 → 0.92.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 +707 -275
- 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 +7 -5
- 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 +259 -67
- 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 +7 -5
- 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 +259 -67
- 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
|
@@ -35,9 +35,10 @@ access: [
|
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
There is no second tool set and no second app — one `server.ts`, projected onto its front doors.
|
|
38
|
-
The flagship selects `model: noodleManaged()`, so
|
|
39
|
-
endpoint, name, or key and its artifact remains provider-neutral.
|
|
40
|
-
|
|
38
|
+
The flagship selects `model: noodleManaged()`, so a billing-attributed hosted deployment needs no customer
|
|
39
|
+
model endpoint, name, or key and its artifact remains provider-neutral. The sponsored beta is available by
|
|
40
|
+
default with pooled billing-account and platform-wide daily limits; `openAICompatible(...)` remains the BYO
|
|
41
|
+
alternative.
|
|
41
42
|
The surface `instructions` add only the website-specific voice and goal; shared product truth stays in
|
|
42
43
|
`server.instructions`. This public guidance is injected into that surface's assistant turns, never MCP
|
|
43
44
|
`initialize` or another assistant surface.
|
|
@@ -152,5 +153,6 @@ noodle open
|
|
|
152
153
|
```
|
|
153
154
|
|
|
154
155
|
This example has no connector or model secrets and does not include tokens, caller-key mechanisms, or
|
|
155
|
-
`.env.noodle` values. Hosted `noodleManaged()` inference
|
|
156
|
-
|
|
156
|
+
`.env.noodle` values. Hosted `noodleManaged()` inference is available by default to billing-attributed
|
|
157
|
+
deployments and remains subject to sponsored daily limits; local validation and tool calls do not use the
|
|
158
|
+
hosted model. All destinations, prices, and URLs are fictional.
|
|
@@ -367,6 +367,13 @@ and passes `routing: { endpoints: { customer_api: cluster.apiBaseUrl } }` to
|
|
|
367
367
|
`createAssistantSession`. Noodle validates and privately stores that route; it is not returned to the
|
|
368
368
|
browser. Do not copy the route into page context, session claims, tool input, or model instructions.
|
|
369
369
|
|
|
370
|
+
The authenticated assistant surface also declares `accountTier` as a model-visible session claim. Pass it
|
|
371
|
+
from the same backend-owned account record as `claims: { accountTier: account.tier }`; undeclared claims are
|
|
372
|
+
dropped. This is personalization context, not authorization: the verified roles/scopes beside each tool and
|
|
373
|
+
the server-owned customer route remain the enforcement boundaries. The public
|
|
374
|
+
[runtime guide](https://docs.noodleseed.dev/docs/guides/product-agent-guides#use-the-guide-at-runtime)
|
|
375
|
+
explains how verified session claims constrain the guide content available to the model.
|
|
376
|
+
|
|
370
377
|
```bash
|
|
371
378
|
noodle variables set ASSISTANT_ORIGIN https://app.example.com --scope env
|
|
372
379
|
noodle variables set ASSISTANT_MODEL_BASE_URL https://model.example.com/v1 --scope env
|
|
@@ -391,7 +398,7 @@ set the bounded surface to `glass` only when translucency is intentional.
|
|
|
391
398
|
These TypeScript values remain the reusable developer defaults. After deployment, an environment operator
|
|
392
399
|
can adjust theme, logo, launcher style, position, and the bounded color palette from the Console's
|
|
393
400
|
**Assistant** tab or `noodle assistant appearance` without changing the customer's embed code. See the
|
|
394
|
-
[embedded assistant guide](https://docs.noodleseed.dev/guides/embedded-assistant) for precedence and reset
|
|
401
|
+
[embedded assistant guide](https://docs.noodleseed.dev/docs/guides/embedded-assistant) for precedence and reset
|
|
395
402
|
behavior.
|
|
396
403
|
|
|
397
404
|
Create the backend credential after deployment. The CLI writes it to a mode-0600 file and never prints the
|
|
@@ -425,8 +432,12 @@ import { NoodleAssistant } from '@noodleseed/assistant/react';
|
|
|
425
432
|
`resolvedTheme` is the application's current `'light' | 'dark'` value. Use `theme="auto"` only when the
|
|
426
433
|
browser operating-system preference is intentionally authoritative.
|
|
427
434
|
|
|
428
|
-
|
|
429
|
-
|
|
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
|
|
438
|
+
minimal skeleton keeps the canonical client and App host, but intentionally refuses confirmation and input
|
|
439
|
+
acceptance until the application implements their complete schema-aware presentation. Start with the managed
|
|
440
|
+
renderer unless the application accepts every obligation below.
|
|
430
441
|
|
|
431
442
|
```tsx
|
|
432
443
|
'use client';
|
|
@@ -438,12 +449,20 @@ import { useNoodleAssistant } from '@noodleseed/assistant/react/client';
|
|
|
438
449
|
export function CustomerAssistant({
|
|
439
450
|
principalKey,
|
|
440
451
|
resolvedTheme,
|
|
452
|
+
onSignInRequested,
|
|
441
453
|
}: {
|
|
442
454
|
principalKey: string;
|
|
443
455
|
resolvedTheme: 'light' | 'dark';
|
|
456
|
+
onSignInRequested: (request: {
|
|
457
|
+
signInTicket: string;
|
|
458
|
+
expiresAt: string;
|
|
459
|
+
}) => Promise<'started' | 'cancelled'>;
|
|
444
460
|
}) {
|
|
445
461
|
const [draft, setDraft] = useState('');
|
|
446
|
-
const
|
|
462
|
+
const [sessionNotice, setSessionNotice] = useState('');
|
|
463
|
+
const [turnNotice, setTurnNotice] = useState('');
|
|
464
|
+
const [pendingSignInTicket, setPendingSignInTicket] = useState<string>();
|
|
465
|
+
const { client, messages, suggestions, status, error } = useNoodleAssistant({
|
|
447
466
|
sessionEndpoint: '/api/noodle-assistant/session',
|
|
448
467
|
principalKey,
|
|
449
468
|
});
|
|
@@ -453,6 +472,16 @@ export function CustomerAssistant({
|
|
|
453
472
|
// The hook exposes this same structured failure through `error`.
|
|
454
473
|
});
|
|
455
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
|
+
);
|
|
456
485
|
|
|
457
486
|
return (
|
|
458
487
|
<section aria-label="Assistant" aria-busy={busy}>
|
|
@@ -466,20 +495,18 @@ export function CustomerAssistant({
|
|
|
466
495
|
<section key={review.id} aria-label="Review proposed action">
|
|
467
496
|
<h3>{review.title ?? 'Review proposed action'}</h3>
|
|
468
497
|
{review.description ? <p>{review.description}</p> : null}
|
|
469
|
-
<
|
|
470
|
-
{JSON.stringify(review.arguments ?? {}, null, 2)}
|
|
471
|
-
</pre>
|
|
498
|
+
<p>This custom renderer has not implemented a complete schema-aware review.</p>
|
|
472
499
|
<button
|
|
473
500
|
disabled={busy || review.status !== 'pending'}
|
|
474
|
-
onClick={() => settle(client.respond(review.id, { action: '
|
|
501
|
+
onClick={() => settle(client.respond(review.id, { action: 'decline' }))}
|
|
475
502
|
>
|
|
476
|
-
|
|
503
|
+
Don't proceed
|
|
477
504
|
</button>
|
|
478
505
|
<button
|
|
479
506
|
disabled={busy || review.status !== 'pending'}
|
|
480
|
-
onClick={() => settle(client.respond(review.id, { action: '
|
|
507
|
+
onClick={() => settle(client.respond(review.id, { action: 'cancel' }))}
|
|
481
508
|
>
|
|
482
|
-
|
|
509
|
+
Cancel
|
|
483
510
|
</button>
|
|
484
511
|
</section>
|
|
485
512
|
);
|
|
@@ -489,21 +516,28 @@ export function CustomerAssistant({
|
|
|
489
516
|
return (
|
|
490
517
|
<section key={request.id} aria-label="Assistant needs input">
|
|
491
518
|
<p>{request.message}</p>
|
|
492
|
-
|
|
519
|
+
{/* request.requestedSchema is the sole input-form contract. */}
|
|
520
|
+
<p>This custom renderer has not implemented the requested schema form.</p>
|
|
493
521
|
<button
|
|
494
522
|
disabled={busy || request.status !== 'pending'}
|
|
495
523
|
onClick={() => settle(client.respond(request.id, { action: 'decline' }))}
|
|
496
524
|
>
|
|
497
|
-
|
|
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
|
|
498
532
|
</button>
|
|
499
533
|
</section>
|
|
500
534
|
);
|
|
501
535
|
}
|
|
502
536
|
if (part.type === 'data-tool-result') {
|
|
503
537
|
return (
|
|
504
|
-
<
|
|
505
|
-
|
|
506
|
-
</
|
|
538
|
+
<p key={part.data.id} role="status">
|
|
539
|
+
A result is available, but this renderer has no trusted presentation for it.
|
|
540
|
+
</p>
|
|
507
541
|
);
|
|
508
542
|
}
|
|
509
543
|
if (part.type === 'data-view') {
|
|
@@ -516,17 +550,67 @@ export function CustomerAssistant({
|
|
|
516
550
|
/>
|
|
517
551
|
);
|
|
518
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
|
+
}
|
|
519
582
|
return <p key={index}>Unsupported assistant content.</p>;
|
|
520
583
|
})}
|
|
521
584
|
</article>
|
|
522
585
|
))}
|
|
523
|
-
|
|
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}
|
|
524
607
|
<form
|
|
525
608
|
onSubmit={(event) => {
|
|
526
609
|
event.preventDefault();
|
|
527
610
|
const message = draft.trim();
|
|
528
611
|
if (!message) return;
|
|
529
612
|
setDraft('');
|
|
613
|
+
setTurnNotice('');
|
|
530
614
|
settle(client.sendMessage(message));
|
|
531
615
|
}}
|
|
532
616
|
>
|
|
@@ -536,7 +620,13 @@ export function CustomerAssistant({
|
|
|
536
620
|
onChange={(event) => setDraft(event.currentTarget.value)}
|
|
537
621
|
/>
|
|
538
622
|
{busy ? (
|
|
539
|
-
<button
|
|
623
|
+
<button
|
|
624
|
+
type="button"
|
|
625
|
+
onClick={() => {
|
|
626
|
+
client.abort();
|
|
627
|
+
setTurnNotice('Response stopped. This does not undo a started action.');
|
|
628
|
+
}}
|
|
629
|
+
>
|
|
540
630
|
Stop
|
|
541
631
|
</button>
|
|
542
632
|
) : (
|
|
@@ -549,14 +639,26 @@ export function CustomerAssistant({
|
|
|
549
639
|
```
|
|
550
640
|
|
|
551
641
|
`principalKey` stays in the browser. Change it whenever the authenticated user or tenant changes; the hook
|
|
552
|
-
then aborts and clears the prior session and transcript. The sample
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
642
|
+
then aborts and clears the prior session and transcript. The sample does not render Confirm until the host
|
|
643
|
+
implements a complete schema-aware review, and it does not accept elicitation until a portable form covers
|
|
644
|
+
`requestedSchema`; use the managed renderer instead of shipping either unsupported branch. Suggestions are
|
|
645
|
+
hook-owned and submit ordinary messages through `client.sendMessage`.
|
|
646
|
+
|
|
647
|
+
`data-sign-in` has no status and is never passed to `client.respond`. Bind `signInTicket` to the host's
|
|
648
|
+
short-lived login transaction without putting it in URLs, logs, analytics, or durable browser storage. Resolve
|
|
649
|
+
the callback as `started` only after the host owns one active transaction; return `cancelled` or reject when no
|
|
650
|
+
transaction started so the renderer restores the sign-in affordance. A
|
|
651
|
+
mixed renderer uses separate public and authenticated clients: the public shell uses `embedId`, `serviceUrl`,
|
|
652
|
+
and a visitor principal key; after login, the destination mounts a new client against the same-origin session
|
|
653
|
+
endpoint under the user/tenant principal key. Never pass both source options or mutate the public client's
|
|
654
|
+
source in place. `session_expired` is observational: the client owns its single safe pre-execution re-exchange.
|
|
655
|
+
Never add a generic Retry button or replay an interaction decision automatically.
|
|
656
|
+
|
|
657
|
+
For `data-tool-result`, do not expose technical tool names or raw JSON. Prefer the linked `data-view`;
|
|
658
|
+
otherwise map a known bounded result to application-trusted UI or keep the explicit unsupported state. For
|
|
659
|
+
`data-view`, use `<noodle-app-view>` or its React `NoodleAppView` adapter. The element's semantic lifecycle
|
|
660
|
+
identity is the client plus `view.id` plus `view.resourceUri`, so payload/callback rerenders keep the iframe
|
|
661
|
+
and only a different view, disconnect, or App teardown request retires the bridge.
|
|
560
662
|
App views remain inline by default: the host advertises only inline presentation and rejects a widget's
|
|
561
663
|
fullscreen request. A customer-owned renderer may opt in explicitly with `allowFullscreen` on
|
|
562
664
|
`NoodleAppView` or `allow-fullscreen` on `<noodle-app-view>` only when fullscreen is part of its intended
|
|
@@ -578,6 +680,13 @@ secret allowlist; regenerate existing framework-owned environment binding types
|
|
|
578
680
|
Devtools/model exercises to synthetic data, and obtain approval before sending real connector data to an
|
|
579
681
|
external model.
|
|
580
682
|
|
|
683
|
+
Read `evidence.levels` in order: static host, local contract, hosted session, production browser, then
|
|
684
|
+
operations. Stop at `evidence.firstUnproven`. A static result may be `passed`, `partial`, or `failed`; even
|
|
685
|
+
top-level `ready: true` means only that no static blocker was detected. It never proves the local session
|
|
686
|
+
contract or a production-browser flow. Value-free diagnostics call out an MCP endpoint used as the service
|
|
687
|
+
URL, an HTML redirect risk, an SSR mount risk, and a cross-origin session endpoint. The command has no live
|
|
688
|
+
or browser flag; its `postDeployProbes` are next actions, not executed evidence.
|
|
689
|
+
|
|
581
690
|
After deployment, use the assistant doctor to verify the embed client, exact model transport, and static
|
|
582
691
|
session boundary:
|
|
583
692
|
|
|
@@ -591,21 +700,10 @@ application-specific customer route. Prove routed assistant tools by
|
|
|
591
700
|
having the authenticated embedding backend pass the user's server-verified endpoint during session
|
|
592
701
|
exchange, then invoke one representative safe read.
|
|
593
702
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
```tsx
|
|
599
|
-
useEffect(() => {
|
|
600
|
-
let active = true;
|
|
601
|
-
queueMicrotask(() => {
|
|
602
|
-
if (active) settle(client.sendMessage(initialMessage));
|
|
603
|
-
});
|
|
604
|
-
return () => {
|
|
605
|
-
active = false;
|
|
606
|
-
};
|
|
607
|
-
}, [client, initialMessage]);
|
|
608
|
-
```
|
|
703
|
+
Do not send a first turn on mount by default. React effect cleanup can suppress one provisional Strict Mode
|
|
704
|
+
effect, but it cannot make a remount, dependency change, or client replacement idempotent. Require an
|
|
705
|
+
explicit user action unless the host owns durable one-shot state and an application idempotency key that
|
|
706
|
+
makes repeated sends safe.
|
|
609
707
|
|
|
610
708
|
For a chat-first custom host, raw `tool_started` supplies the direct call `id` and technical tool name. Map
|
|
611
709
|
known tools to concise application copy and use a neutral fallback. Reserve a stable `role="status"` region
|
|
@@ -617,39 +715,81 @@ Use `${view.id}:${view.resourceUri}` as transport identity. Different call IDs a
|
|
|
617
715
|
must not be deduplicated generically. If this application intentionally owns one current panel for a known
|
|
618
716
|
resource, declare an application-owned slot for that resource and replace only that slot.
|
|
619
717
|
|
|
620
|
-
|
|
718
|
+
### Framework-neutral DOM client
|
|
719
|
+
|
|
720
|
+
Subscribe to the DOM-free client directly without a component wrapper and use the isolated App host.
|
|
621
721
|
It exposes the same conversation as headless AI SDK `UIMessage` state, including typed confirmation, input,
|
|
622
|
-
tool-result, and linked-view parts
|
|
722
|
+
tool-result, and linked-view parts:
|
|
623
723
|
|
|
624
724
|
```html
|
|
625
|
-
<
|
|
725
|
+
<div id="assistant-app-views"></div>
|
|
626
726
|
```
|
|
627
727
|
|
|
628
728
|
```ts
|
|
629
729
|
import '@noodleseed/assistant/app-view';
|
|
730
|
+
import {
|
|
731
|
+
type AssistantViewAvailableDetail,
|
|
732
|
+
type NoodleAppViewElement,
|
|
733
|
+
} from '@noodleseed/assistant/app-view';
|
|
630
734
|
import { createAssistantClient } from '@noodleseed/assistant/client';
|
|
631
735
|
|
|
632
736
|
const assistant = createAssistantClient({
|
|
633
737
|
sessionEndpoint: '/api/noodle-assistant/session',
|
|
634
738
|
});
|
|
635
|
-
const
|
|
636
|
-
if (!
|
|
637
|
-
|
|
638
|
-
|
|
739
|
+
const appViews = document.querySelector('#assistant-app-views');
|
|
740
|
+
if (!appViews) throw new Error('Missing App views host');
|
|
741
|
+
const mountedViews = new Map<string, NoodleAppViewElement>();
|
|
742
|
+
const readResolvedTheme = (): 'light' | 'dark' =>
|
|
743
|
+
document.documentElement.classList.contains('dark') ? 'dark' : 'light';
|
|
744
|
+
let resolvedTheme: 'light' | 'dark' = readResolvedTheme();
|
|
745
|
+
const syncResolvedTheme = () => {
|
|
746
|
+
resolvedTheme = readResolvedTheme();
|
|
747
|
+
for (const mountedView of mountedViews.values()) mountedView.theme = resolvedTheme;
|
|
748
|
+
};
|
|
749
|
+
new MutationObserver(syncResolvedTheme).observe(document.documentElement, {
|
|
750
|
+
attributes: true,
|
|
751
|
+
attributeFilter: ['class'],
|
|
752
|
+
});
|
|
753
|
+
const appViewFor = (view: AssistantViewAvailableDetail) => {
|
|
754
|
+
const key = `${view.id}:${view.resourceUri}`;
|
|
755
|
+
let appView = mountedViews.get(key);
|
|
756
|
+
if (!appView) {
|
|
757
|
+
appView = document.createElement('noodle-app-view') as NoodleAppViewElement;
|
|
758
|
+
appView.client = assistant;
|
|
759
|
+
mountedViews.set(key, appView);
|
|
760
|
+
appViews.append(appView);
|
|
761
|
+
}
|
|
762
|
+
appView.theme = resolvedTheme;
|
|
763
|
+
return appView;
|
|
764
|
+
};
|
|
639
765
|
|
|
640
766
|
assistant.subscribeChat((state) => {
|
|
641
|
-
renderUIMessageState(state
|
|
767
|
+
renderUIMessageState(state, {
|
|
768
|
+
respond: (id, response) => assistant.respond(id, response),
|
|
769
|
+
});
|
|
770
|
+
const activeViewKeys = new Set<string>();
|
|
642
771
|
for (const message of state.messages) {
|
|
643
772
|
for (const part of message.parts) {
|
|
644
|
-
if (part.type === 'data-
|
|
645
|
-
|
|
773
|
+
if (part.type === 'data-view') {
|
|
774
|
+
const key = `${part.data.id}:${part.data.resourceUri}`;
|
|
775
|
+
activeViewKeys.add(key);
|
|
776
|
+
appViewFor(part.data).view = part.data;
|
|
646
777
|
}
|
|
647
|
-
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
for (const [key, mountedView] of mountedViews) {
|
|
781
|
+
if (!activeViewKeys.has(key)) {
|
|
782
|
+
mountedView.remove();
|
|
783
|
+
mountedViews.delete(key);
|
|
648
784
|
}
|
|
649
785
|
}
|
|
650
786
|
});
|
|
651
787
|
```
|
|
652
788
|
|
|
789
|
+
`renderUIMessageState` is application code. It must present a complete schema-aware confirmation or input
|
|
790
|
+
form and require an explicit user gesture before using `respond`; never call `respond` while scanning a
|
|
791
|
+
transcript snapshot.
|
|
792
|
+
|
|
653
793
|
`theme="auto"` follows the operating-system preference, not a SaaS-owned toggle. Pass the resolved
|
|
654
794
|
`light`/`dark` theme to `NoodleAssistant` and `<noodle-app-view>`/`NoodleAppView`; updates reach mounted MCP Apps without a
|
|
655
795
|
remount. CSS custom properties inherit through the host, and documented `--ns-assistant-*` variables remain
|
|
@@ -666,6 +806,14 @@ deployment variables, and connector arguments do not select the tenant route.
|
|
|
666
806
|
endpoint. They are not customer API bearer tokens. The exchange endpoint verifies the platform-signed
|
|
667
807
|
subject assertion and mints a short-lived token scoped to the signed-in user and route binding.
|
|
668
808
|
|
|
809
|
+
## Launch and qualified-usage proof
|
|
810
|
+
|
|
811
|
+
Use the [embedded assistant guide](https://docs.noodleseed.dev/docs/guides/embedded-assistant) for the complete
|
|
812
|
+
pre-launch, browser-proof, qualified-usage, recovery, measurement, and operator procedure; do not copy those
|
|
813
|
+
commands into this example. Keep deployed capability, production-browser proof, qualified usage, and measured
|
|
814
|
+
outcome separate; raw turn volume is utilization rather than outcome. Platform-owned completion events still
|
|
815
|
+
require a separate human-approved analytics and customer-data contract.
|
|
816
|
+
|
|
669
817
|
## Deploy customer-protected to Noodle Seed Cloud
|
|
670
818
|
|
|
671
819
|
```bash
|
|
@@ -173,6 +173,9 @@ export default server(
|
|
|
173
173
|
// Production origins are exact HTTPS; http://localhost:<port> is allowed for local development.
|
|
174
174
|
access: authenticatedWebsite({
|
|
175
175
|
origins: [assistantOrigin, 'https://dev.noodleseed.com', 'http://localhost:3000'],
|
|
176
|
+
sessionClaims: {
|
|
177
|
+
accountTier: { exposeToModel: true },
|
|
178
|
+
},
|
|
176
179
|
}),
|
|
177
180
|
theme: 'auto',
|
|
178
181
|
layout: { mode: 'floating', position: 'bottom-center', panelWidth: 970 },
|
|
@@ -19,6 +19,9 @@ describe('customer-auth example', () => {
|
|
|
19
19
|
'https://dev.noodleseed.com',
|
|
20
20
|
'http://localhost:3000',
|
|
21
21
|
]);
|
|
22
|
+
expect(manifest.server.assistant?.sessionClaims).toEqual({
|
|
23
|
+
accountTier: { exposeToModel: true },
|
|
24
|
+
});
|
|
22
25
|
expect(manifest.server.branding).toMatchObject({
|
|
23
26
|
name: 'Noodle Seed Assistant',
|
|
24
27
|
colorScheme: 'auto',
|
|
@@ -100,5 +103,8 @@ describe('customer-auth example', () => {
|
|
|
100
103
|
confirm: true,
|
|
101
104
|
},
|
|
102
105
|
});
|
|
106
|
+
expect(
|
|
107
|
+
manifest.tools.find((tool) => tool.name === 'list_org_apps')?.annotations?.readOnlyHint,
|
|
108
|
+
).toBe(true);
|
|
103
109
|
});
|
|
104
110
|
});
|
|
@@ -17,7 +17,7 @@ private customer data.
|
|
|
17
17
|
| Public entry tool | `open_ordering` returns structured fallback content and renders the React widget |
|
|
18
18
|
| Product and distribution projections | `agentGuide` supplies grounded cross-capability guidance; `distribution` supplies listing, publisher, legal, image, and review facts separately from the runtime manifest |
|
|
19
19
|
| App-only helper tools | `search_stores`, `load_menu`, `load_item`, `read_cart`, `sync_cart`, `prepare_checkout`; mutating widget-owned helpers use `confirm: false` (equivalent to omission) and execute directly because action hints alone never gate |
|
|
20
|
-
| Durable cart state | `server(..., { state: { handles: { cart } }, use: { state } })` with caller scope
|
|
20
|
+
| Durable cart state | `server(..., { state: { handles: { cart } }, use: { state } })` with caller scope, revision checks, and explicit ticket-bound adoption when an anonymous visitor authenticates |
|
|
21
21
|
| React app runtime kit | `@noodleseed/one/react` supplies app flow, shell/nav/view, async state, form, quantity, choice, and handoff primitives |
|
|
22
22
|
| Multi-step widget flow | One React shell navigates stores, menu, item customization, cart, review, and handoff views through `useAppFlow` |
|
|
23
23
|
| Invocation context | `server.context` sets locale/time-zone defaults, derives an ambient service area/date, and exposes optional host-supplied coordinates to tools and the reserved `noodle_context` MCP adapter; location is an untrusted convenience hint, never an authorization signal or a substitute for explicit input |
|
|
@@ -3,7 +3,7 @@ name: executing-noodle-plans
|
|
|
3
3
|
description: "Use when the user asks to execute an approved, decision-complete implementation plan for a Noodle Seed project task by task with test-first changes, review, recovery, and final verification."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.92.0 hash:6a9f132ddb79352e -->
|
|
7
7
|
|
|
8
8
|
# Execute a Noodle Seed implementation plan
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: publishing-mcp-integrations
|
|
|
3
3
|
description: "Use when preparing, reviewing, or submitting a Noodle Seed MCP integration to a host or app directory."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.92.0 hash:0ccafb222038f553 -->
|
|
7
7
|
|
|
8
8
|
# publishing-mcp-integrations
|
|
9
9
|
|
|
@@ -27,8 +27,8 @@ Produce complete submission evidence with host-review uncertainty stated explici
|
|
|
27
27
|
|
|
28
28
|
## Workflow
|
|
29
29
|
|
|
30
|
-
Read and follow the canonical playbook
|
|
31
|
-
Load
|
|
30
|
+
Read and follow the canonical playbook at `references/publishing.md`. It owns the workflow; do not recreate it here or load the command catalog speculatively.
|
|
31
|
+
Load the supporting reference at `references/app-directory-compliance.md` only when the playbook or observed evidence names that concern.
|
|
32
32
|
|
|
33
33
|
## Verification evidence
|
|
34
34
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# App directory compliance (pre-submission)
|
|
2
|
+
|
|
3
|
+
Use this shared checklist against the built integration before preparing a directory submission. It
|
|
4
|
+
covers evidence common to app and connector directories without assuming a particular host, review
|
|
5
|
+
portal, client framework, or vendor policy.
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
- Validation evidence
|
|
10
|
+
- Capability and interaction quality
|
|
11
|
+
- Safety, privacy, and data handling
|
|
12
|
+
- Reliability and accessibility
|
|
13
|
+
- Directory-specific delta
|
|
14
|
+
|
|
15
|
+
## Validation evidence
|
|
16
|
+
|
|
17
|
+
A clean local validation result proves only the checks that actually ran. Record server validation,
|
|
18
|
+
behavior tests, protocol conformance, production reachability, and interactive rendering as separate
|
|
19
|
+
evidence levels. Never treat metadata readiness as proof of host rendering or directory acceptance.
|
|
20
|
+
|
|
21
|
+
## Capability and interaction quality
|
|
22
|
+
|
|
23
|
+
1. **User value** — each exposed capability solves a concrete user job and cites built behavior rather
|
|
24
|
+
than an aspiration.
|
|
25
|
+
2. **Grounded capability** — knowledge, actions, and presentation come from authoritative application
|
|
26
|
+
data or bounded operations instead of invented state.
|
|
27
|
+
3. **Atomic interfaces** — every action has a focused purpose, explicit input and output schemas, honest
|
|
28
|
+
effect annotations, and useful failure output.
|
|
29
|
+
4. **Helpful UI only** — every interactive surface earns its place and preserves a useful text or
|
|
30
|
+
structured fallback when rendering is unavailable.
|
|
31
|
+
5. **Meaningful completion** — the user can complete the promised task within the declared boundary,
|
|
32
|
+
with any external handoff clearly identified.
|
|
33
|
+
|
|
34
|
+
## Safety, privacy, and data handling
|
|
35
|
+
|
|
36
|
+
- Minimize model-visible and UI-visible data; remove secrets, internal identifiers, unnecessary personal
|
|
37
|
+
data, and continuation credentials from results and logs.
|
|
38
|
+
- Document authentication, authorization scopes, retention, deletion, subprocessors, and external
|
|
39
|
+
handoffs accurately in the public privacy and support material.
|
|
40
|
+
- Make mutations explicit, bounded, and confirmation-aware. Never imply that a read or preparation
|
|
41
|
+
request authorizes a write.
|
|
42
|
+
- For regulated or consequential workflows, show source provenance, uncertainty, cautions, and the
|
|
43
|
+
boundary between information and a professional decision.
|
|
44
|
+
|
|
45
|
+
## Reliability and accessibility
|
|
46
|
+
|
|
47
|
+
- Exercise representative positive, negative, empty, loading, error, and recovery cases against the
|
|
48
|
+
production-shaped endpoint.
|
|
49
|
+
- Preserve keyboard access, readable contrast, responsive layout, concise status feedback, and graceful
|
|
50
|
+
degradation when an interactive surface is unsupported.
|
|
51
|
+
- State latency, availability, rate-limit, and support expectations using observed evidence rather than
|
|
52
|
+
unverified claims.
|
|
53
|
+
|
|
54
|
+
## Directory-specific delta
|
|
55
|
+
|
|
56
|
+
After the shared checklist passes, read the selected directory’s current official documentation and add
|
|
57
|
+
only its verified requirements. Keep directory-specific metadata, screenshots, test accounts, policy
|
|
58
|
+
statements, and review procedures in that submission evidence—not in this shared skill reference. Mark
|
|
59
|
+
unknown or untested requirements explicitly, and never reuse another directory’s checklist as a proxy.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Publish to app directories
|
|
2
|
+
|
|
3
|
+
> Preparation is read-only unless the current user request explicitly authorizes the exact deploy, access change, host write, or submission target. A request to prepare must report missing readiness work and stop before mutation.
|
|
4
|
+
|
|
5
|
+
Directory requirements evolve. Identify the requested directory first and verify its current official requirements before preparing directory-specific evidence.
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
- Shared readiness gate
|
|
10
|
+
- Distribution metadata source
|
|
11
|
+
- Hosted immutable distribution
|
|
12
|
+
- Directory-specific evidence
|
|
13
|
+
- Submission boundary
|
|
14
|
+
|
|
15
|
+
## Shared readiness gate
|
|
16
|
+
|
|
17
|
+
Before any submission:
|
|
18
|
+
|
|
19
|
+
Use `references/app-directory-compliance.md` as this route’s canonical shared compliance checklist.
|
|
20
|
+
|
|
21
|
+
Prepare evidence for a reachable production MCP endpoint, accurate capability descriptions and schemas, useful fallback behavior, realistic positive and negative tests, data minimization, privacy disclosures, support ownership, and any interactive surface the directory will review.
|
|
22
|
+
|
|
23
|
+
## Distribution metadata source
|
|
24
|
+
|
|
25
|
+
When the user explicitly prepares host packaging, author the host-neutral `distribution` option in the same `server.ts`; do not add it during an ordinary build that has no distribution goal. It contains listing, publisher, support, legal, assets, and review facts that cannot be derived safely from MCP capability descriptions.
|
|
26
|
+
|
|
27
|
+
Reference real packaged images with `asset(...)`, write useful alt text, and include realistic positive and negative review scenarios. Name the exact expected MCP tools in each positive scenario’s `tools` array. A negative scenario is a non-invocation case: set `shouldInvoke: false` and do not define `tools`. For each MCP App screenshot, add the separate user `prompt` that produces that exact state. Capture only the rendered MCP App response—never the enclosing website, Devtools shell, host conversation, or an unrelated product photo—and meet the selected directory’s current format, dimension, and count limits. Keep reviewer credentials, tokens, secrets, personal data, and test-account passwords out of metadata and source control; supply any authorized reviewer credential out of band.
|
|
28
|
+
|
|
29
|
+
`distribution` is projected separately. It leaves the canonical App Package and Runtime Artifact unchanged, so editing listing copy cannot change deployment execution or product-skill identity. A product package still needs the separately judged `agentGuide`; do not duplicate capability schemas or workflow truth in listing metadata.
|
|
30
|
+
|
|
31
|
+
The shared framework can validate metadata and resolved image bytes, run an available target adapter, and create a reproducible archive. Target-specific availability and exact flags live in `references/cli-commands.md` and the live command catalog (`noodle commands --json`); never invent an unlisted target, bundle, filename, or acceptance claim.
|
|
32
|
+
|
|
33
|
+
When a directory has separate installable-plugin and remote-connector submission projections, generate each with its own exact live-catalog command. Never combine their archives or describe an operator dossier as directly portal-uploadable.
|
|
34
|
+
|
|
35
|
+
Local or repository testing and public-directory submission are distinct packaging states with distinct required inputs. An export command only compiles local source and writes the requested archive. It does not deploy, register, upload, submit, review, or publish the package.
|
|
36
|
+
|
|
37
|
+
When an export reports `uploadArtifacts`, treat its output archive as an outer review kit. Extract it, follow the generated instructions, and upload only the named inner artifacts to their matching fields. Never substitute the outer kit for a nested single-purpose upload.
|
|
38
|
+
|
|
39
|
+
## Hosted immutable distribution
|
|
40
|
+
|
|
41
|
+
Publishing a deployment-bound archive is a hosted mutation. Run it only when the current request explicitly authorizes that exact deployment and target: `noodle distributions publish <deployment-id> [server.ts] --target <target>`. The command compiles local TypeScript and requires its package snapshot to exactly match the selected deployment before it uploads anything. It uses the endpoint and package identity returned by the service; never substitute a local URL or a different deployment.
|
|
42
|
+
|
|
43
|
+
Use `noodle distributions list <deployment-id>` to discover immutable versions, `noodle distributions inspect <distribution-id>` to inspect one, and `noodle distributions download <distribution-id> --output <archive.zip>` to retrieve its exact archive. Download verifies the service length and digest before an atomic local write; a failed verification must leave no output file.
|
|
44
|
+
|
|
45
|
+
Lifecycle and delivery are separate mutations. Run `readiness`, `review`, `release`, `rollback`, `deprecate`, `revoke`, or `grant` only when the request explicitly authorizes that exact distribution and action. Inspect first when the active state or version is not already known.
|
|
46
|
+
|
|
47
|
+
Set readiness from evidence you can verify. Record `review` only from a real human-observed host status; never infer submission, approval, or publication from a generated archive or a successful Noodle command, and never put reviewer credentials or secrets in feedback.
|
|
48
|
+
|
|
49
|
+
`release --visibility private` creates or advances a stable Noodle channel without anonymous discovery; `release --visibility public` enables Noodle public delivery only. Both still require the underlying MCP deployment to use exact public access. Neither action publishes to an external directory.
|
|
50
|
+
|
|
51
|
+
`grant` returns one short-lived, exact-version bearer URL. Treat the complete URL as a secret, disclose it only to the authorized reviewer, and do not paste it into source, logs, issues, or durable docs. `rollback` moves only the channel pointer to an older ready version; `deprecate` stops delivery and `revoke` is terminal.
|
|
52
|
+
|
|
53
|
+
A hosted archive is still a submission candidate. Creating, listing, inspecting, downloading, releasing, or granting it does not submit it to an external directory, satisfy review, or publish a host listing.
|
|
54
|
+
|
|
55
|
+
## Directory-specific evidence
|
|
56
|
+
|
|
57
|
+
Read the selected directory’s current official submission documentation at review time. Record each additional requirement separately from the shared checklist, including listing fields, identity verification, test credentials, screenshots, policy declarations, review limits, and appeal or resubmission steps. Never project one directory’s requirements onto another.
|
|
58
|
+
|
|
59
|
+
When a requirement cannot be verified from the selected directory’s current documentation or direct review evidence, mark it unknown instead of borrowing a rule from another host.
|
|
60
|
+
|
|
61
|
+
## Submission boundary
|
|
62
|
+
|
|
63
|
+
Preparation is read-only. Reverify current official requirements immediately before public submission. Deployment, access changes, directory registration, credential entry, final submission, and publication each remain separate human-operated mutations that require explicit authorization for the exact target. Report remaining evidence gaps and stop when that authority or required directory access is absent.
|