@noodleseed/agent-kit 0.40.0 → 0.42.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 +235 -235
- package/package.json +1 -1
- package/skills/claude-code/SKILL.md +2 -2
- package/skills/claude-code/authoring-mcp-servers/SKILL.md +1 -1
- package/skills/claude-code/building-mcp-apps/SKILL.md +1 -1
- package/skills/claude-code/connecting-apis-to-mcp/SKILL.md +1 -1
- package/skills/claude-code/debugging-mcp-delivery/SKILL.md +1 -1
- package/skills/claude-code/deploying-mcp-services/SKILL.md +1 -1
- package/skills/claude-code/designing-mcp-products/SKILL.md +1 -1
- package/skills/claude-code/embedding-mcp-assistants/SKILL.md +1 -1
- package/skills/claude-code/examples/customer-auth/README.md +143 -1
- package/skills/claude-code/examples/hello/README.md +3 -0
- package/skills/claude-code/publishing-mcp-integrations/SKILL.md +1 -1
- package/skills/claude-code/references/agent-contract.md +4 -3
- package/skills/claude-code/references/embedded-assistant.md +142 -15
- package/skills/claude-code/reporting-noodle-feedback/SKILL.md +1 -1
- package/skills/claude-code/verifying-mcp-delivery/SKILL.md +1 -1
- package/skills/codex/SKILL.md +2 -2
- package/skills/codex/authoring-mcp-servers/SKILL.md +1 -1
- package/skills/codex/building-mcp-apps/SKILL.md +1 -1
- package/skills/codex/connecting-apis-to-mcp/SKILL.md +1 -1
- package/skills/codex/debugging-mcp-delivery/SKILL.md +1 -1
- package/skills/codex/deploying-mcp-services/SKILL.md +1 -1
- package/skills/codex/designing-mcp-products/SKILL.md +1 -1
- package/skills/codex/embedding-mcp-assistants/SKILL.md +1 -1
- package/skills/codex/examples/customer-auth/README.md +143 -1
- package/skills/codex/examples/hello/README.md +3 -0
- package/skills/codex/publishing-mcp-integrations/SKILL.md +1 -1
- package/skills/codex/references/agent-contract.md +4 -3
- package/skills/codex/references/embedded-assistant.md +142 -15
- package/skills/codex/reporting-noodle-feedback/SKILL.md +1 -1
- package/skills/codex/verifying-mcp-delivery/SKILL.md +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noodleseed/agent-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Self-checking, self-updating agent skills for the Noodle Seed CLI. Authored in this repo by @noodle-borg/agent-kit; this is the published, independently-versioned canonical skills artifact the CLI fetches and verifies.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -3,7 +3,7 @@ name: noodle-seed
|
|
|
3
3
|
description: "Use when building, validating, testing, deploying, or operating a local or hosted Noodle Seed MCP server or app authored in TypeScript with the noodle CLI."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.42.0 hash:0c1ebd532b9c2499 -->
|
|
7
7
|
|
|
8
8
|
# Noodle Seed
|
|
9
9
|
|
|
@@ -39,7 +39,7 @@ Negative routing examples: “Inspect hosted logs/status” → `inspect-hosted`
|
|
|
39
39
|
|
|
40
40
|
## Common machine loop
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
Every `--json` command speaks the canonical envelope on stdout. Parse machine state instead of scraping human prose; `references/agent-contract.md` owns the envelope, streaming records, and exit codes.
|
|
43
43
|
|
|
44
44
|
1. **Discover** — use `noodle commands --json` when the required command or flags are uncertain; don't read CLI source.
|
|
45
45
|
2. **Author** — for build routes, edit the configured TypeScript entrypoint, usually `src/server.ts`.
|
|
@@ -3,7 +3,7 @@ name: authoring-mcp-servers
|
|
|
3
3
|
description: "Use when creating or extending a headless Noodle Seed MCP server, tool, resource, prompt, or typed model-facing capability."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.42.0 hash:0b2fd8c7e43fc69f -->
|
|
7
7
|
|
|
8
8
|
# authoring-mcp-servers
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: building-mcp-apps
|
|
|
3
3
|
description: "Use when a Noodle Seed MCP App, widget, interactive card, visual interaction, or host-visible UI is the primary requested outcome."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.42.0 hash:f7fa54992c8d7692 -->
|
|
7
7
|
|
|
8
8
|
# building-mcp-apps
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: connecting-apis-to-mcp
|
|
|
3
3
|
description: "Use when credentials, an API URL, an OpenAPI document, or an observed response must become real Noodle Seed MCP behavior."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.42.0 hash:1e86b8704f407bd3 -->
|
|
7
7
|
|
|
8
8
|
# connecting-apis-to-mcp
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: debugging-mcp-delivery
|
|
|
3
3
|
description: "Use when an existing Noodle Seed MCP project has a concrete validation, runtime, connector, App, host, deployment, or production failure."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.42.0 hash:aa715bae12041d7c -->
|
|
7
7
|
|
|
8
8
|
# debugging-mcp-delivery
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: deploying-mcp-services
|
|
|
3
3
|
description: "Use when the user explicitly requests a Noodle Seed hosted link, configuration write, deployment, access change, rollback, or connection write."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.42.0 hash:93e735b7ffb45df1 -->
|
|
7
7
|
|
|
8
8
|
# deploying-mcp-services
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: designing-mcp-products
|
|
|
3
3
|
description: "Use when a Noodle Seed MCP product idea needs conversational fit, user benefit, scope, interaction, or evidence design before implementation."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.42.0 hash:76cce86729cffbee -->
|
|
7
7
|
|
|
8
8
|
# designing-mcp-products
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: embedding-mcp-assistants
|
|
|
3
3
|
description: "Use when embedding a Noodle assistant into an existing SaaS or web application with browser, identity, session, and credential boundaries."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.42.0 hash:5d8f40f904d6ab4b -->
|
|
7
7
|
|
|
8
8
|
# embedding-mcp-assistants
|
|
9
9
|
|
|
@@ -169,7 +169,7 @@ customer backend. The model URL, model name, and model API key remain managed by
|
|
|
169
169
|
|
|
170
170
|
The customer's authenticated backend calls `createAssistantSession(...)` from
|
|
171
171
|
`@noodleseed/assistant/server`, passing the already-verified user and browser origin. The browser then uses
|
|
172
|
-
the returned short-lived session through the Web Component or
|
|
172
|
+
the returned short-lived session through the managed Web Component/React renderer or a customer-owned UI:
|
|
173
173
|
|
|
174
174
|
```bash
|
|
175
175
|
pnpm add @noodleseed/assistant
|
|
@@ -185,6 +185,148 @@ import { NoodleAssistant } from '@noodleseed/assistant/react';
|
|
|
185
185
|
/>;
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
+
For an entirely application-owned React renderer, use the renderer-free hook. It creates no custom element
|
|
189
|
+
and returns the AI SDK transcript plus the canonical client commands:
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
'use client';
|
|
193
|
+
|
|
194
|
+
import { useState } from 'react';
|
|
195
|
+
import { useNoodleAssistant } from '@noodleseed/assistant/react/client';
|
|
196
|
+
|
|
197
|
+
export function CustomerAssistant({ principalKey }: { principalKey: string }) {
|
|
198
|
+
const [draft, setDraft] = useState('');
|
|
199
|
+
const { client, messages, status, error } = useNoodleAssistant({
|
|
200
|
+
sessionEndpoint: '/api/noodle-assistant/session',
|
|
201
|
+
principalKey,
|
|
202
|
+
});
|
|
203
|
+
const busy = status === 'submitted' || status === 'streaming';
|
|
204
|
+
const settle = (operation: Promise<void>) => {
|
|
205
|
+
void operation.catch(() => {
|
|
206
|
+
// The hook exposes this same structured failure through `error`.
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<section aria-label="Assistant" aria-busy={busy}>
|
|
212
|
+
{messages.map((message) => (
|
|
213
|
+
<article key={message.id} data-role={message.role}>
|
|
214
|
+
{message.parts.map((part, index) => {
|
|
215
|
+
if (part.type === 'text') return <p key={index}>{part.text}</p>;
|
|
216
|
+
if (part.type === 'data-confirmation') {
|
|
217
|
+
const review = part.data;
|
|
218
|
+
return (
|
|
219
|
+
<section key={review.id} aria-label="Review proposed action">
|
|
220
|
+
<h3>{review.title ?? 'Review proposed action'}</h3>
|
|
221
|
+
{review.description ? <p>{review.description}</p> : null}
|
|
222
|
+
<pre aria-label="Proposed action arguments">
|
|
223
|
+
{JSON.stringify(review.arguments ?? {}, null, 2)}
|
|
224
|
+
</pre>
|
|
225
|
+
<button
|
|
226
|
+
disabled={busy || review.status !== 'pending'}
|
|
227
|
+
onClick={() => settle(client.respond(review.id, { action: 'accept' }))}
|
|
228
|
+
>
|
|
229
|
+
Confirm
|
|
230
|
+
</button>
|
|
231
|
+
<button
|
|
232
|
+
disabled={busy || review.status !== 'pending'}
|
|
233
|
+
onClick={() => settle(client.respond(review.id, { action: 'decline' }))}
|
|
234
|
+
>
|
|
235
|
+
Don't proceed
|
|
236
|
+
</button>
|
|
237
|
+
</section>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
if (part.type === 'data-input-request') {
|
|
241
|
+
const request = part.data;
|
|
242
|
+
return (
|
|
243
|
+
<section key={request.id} aria-label="Assistant needs input">
|
|
244
|
+
<p>{request.message}</p>
|
|
245
|
+
<p>This renderer has not implemented the requested form.</p>
|
|
246
|
+
<button
|
|
247
|
+
disabled={busy || request.status !== 'pending'}
|
|
248
|
+
onClick={() => settle(client.respond(request.id, { action: 'decline' }))}
|
|
249
|
+
>
|
|
250
|
+
Cancel request
|
|
251
|
+
</button>
|
|
252
|
+
</section>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (part.type === 'data-tool-result') {
|
|
256
|
+
return (
|
|
257
|
+
<pre key={part.data.id} aria-label={`${part.data.tool} result`}>
|
|
258
|
+
{JSON.stringify(part.data.result, null, 2)}
|
|
259
|
+
</pre>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
if (part.type === 'data-view') {
|
|
263
|
+
return (
|
|
264
|
+
<p key={part.data.id}>
|
|
265
|
+
Trusted app view available: {part.data.title ?? part.data.resourceUri}
|
|
266
|
+
</p>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
return <p key={index}>Unsupported assistant content.</p>;
|
|
270
|
+
})}
|
|
271
|
+
</article>
|
|
272
|
+
))}
|
|
273
|
+
{error ? <p role="alert">{error.message}</p> : null}
|
|
274
|
+
<form
|
|
275
|
+
onSubmit={(event) => {
|
|
276
|
+
event.preventDefault();
|
|
277
|
+
const message = draft.trim();
|
|
278
|
+
if (!message) return;
|
|
279
|
+
setDraft('');
|
|
280
|
+
settle(client.sendMessage(message));
|
|
281
|
+
}}
|
|
282
|
+
>
|
|
283
|
+
<input
|
|
284
|
+
aria-label="Message"
|
|
285
|
+
value={draft}
|
|
286
|
+
onChange={(event) => setDraft(event.currentTarget.value)}
|
|
287
|
+
/>
|
|
288
|
+
{busy ? (
|
|
289
|
+
<button type="button" onClick={() => client.abort()}>
|
|
290
|
+
Stop
|
|
291
|
+
</button>
|
|
292
|
+
) : (
|
|
293
|
+
<button type="submit">Send</button>
|
|
294
|
+
)}
|
|
295
|
+
</form>
|
|
296
|
+
</section>
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
`principalKey` stays in the browser. Change it whenever the authenticated user or tenant changes; the hook
|
|
302
|
+
then aborts and clears the prior session and transcript. The sample fails closed on input requests until its
|
|
303
|
+
fallback is replaced with a form generated from `requestedSchema`. A production renderer must show the
|
|
304
|
+
complete confirmation review and both decisions. For `data-view`, map `resourceUri` or `tool` and the
|
|
305
|
+
bounded/redacted result to a component already trusted by this application. Never inject `part.data.html`,
|
|
306
|
+
assign it to `srcdoc`, or fetch a `ui://` URI.
|
|
307
|
+
|
|
308
|
+
Outside React, subscribe to the DOM-free client directly. It exposes the same conversation as headless AI
|
|
309
|
+
SDK `UIMessage` state, including typed confirmation, input, tool-result, and linked-view parts:
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
import { createAssistantClient } from '@noodleseed/assistant/client';
|
|
313
|
+
|
|
314
|
+
const assistant = createAssistantClient({
|
|
315
|
+
sessionEndpoint: '/api/noodle-assistant/session',
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
assistant.subscribeChat((state) => {
|
|
319
|
+
renderUIMessageState(state);
|
|
320
|
+
for (const message of state.messages) {
|
|
321
|
+
for (const part of message.parts) {
|
|
322
|
+
if (part.type === 'data-confirmation' && part.data.status === 'pending') {
|
|
323
|
+
renderConfirmation(part.data, (response) => assistant.respond(part.data.id, response));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
```
|
|
329
|
+
|
|
188
330
|
`theme="auto"` follows the SaaS application. The server-level `branding` block is inherited by both MCP App
|
|
189
331
|
widgets and the assistant; documented `--ns-assistant-*` semantic CSS variables remain the final integration
|
|
190
332
|
escape hatch. There is no second assistant branding declaration.
|
|
@@ -12,6 +12,9 @@ guides and operates the validate, preview, deploy, inspect, and debug workflow.
|
|
|
12
12
|
If that agent discovers a Noodle Seed product gap while working, the installed skill prepares a
|
|
13
13
|
sanitized `noodle feedback` proposal, discovers current fields from `noodle commands --json`, and
|
|
14
14
|
submits it only after showing the exact command and receiving explicit user approval.
|
|
15
|
+
Every `--json` command writes its canonical success or failure envelope to stdout and leaves stderr
|
|
16
|
+
empty. One-shot commands write one envelope; streaming commands write NDJSON snapshot, event, and
|
|
17
|
+
terminal-failure envelopes so agents can parse each line independently.
|
|
15
18
|
|
|
16
19
|
```sh
|
|
17
20
|
noodle dev examples/hello/src/server.ts --app hello
|
|
@@ -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.42.0 hash:efffbf82007f935d -->
|
|
7
7
|
|
|
8
8
|
# publishing-mcp-integrations
|
|
9
9
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Agent contract: --json, exit codes, output modes
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Every `--json` command returns the canonical envelope below on stdout and keeps stderr empty. Decide what to do next by parsing machine state — do not scrape human prose.
|
|
4
4
|
|
|
5
5
|
## Contents
|
|
6
6
|
|
|
@@ -11,12 +11,13 @@ The cold-agent-path commands (`init`, `validate`, `test`, `check`, `tools`/`reso
|
|
|
11
11
|
|
|
12
12
|
## Response envelope
|
|
13
13
|
|
|
14
|
-
A `--json` command returns exactly one JSON object:
|
|
14
|
+
A one-shot `--json` command returns exactly one JSON object on stdout; stderr stays empty:
|
|
15
15
|
|
|
16
16
|
- **Success**: `{ ok: true, data, warnings? }` — `data` is the command payload; `warnings?` is an optional array of non-fatal notes.
|
|
17
|
-
- **Failure**: `{ ok: false, error: { code, message, cause?, fix, next, requestId? } }` — `code` is the stable machine code to branch on, `message` is human text, `cause?` is the underlying error, `fix` states the correction, `next` names the command to run next, `requestId?` correlates a hosted call.
|
|
17
|
+
- **Failure**: `{ ok: false, error: { code, message, cause?, fix, next, requestId?, retryable?, retryAfterSeconds? } }` — `code` is the stable machine code to branch on, `message` is human text, `cause?` is the underlying error, `fix` states the correction, `next` names the command to run next, `requestId?` correlates a hosted call, and retry metadata tells automation whether and when to retry.
|
|
18
18
|
- **Field errors** carry a dotted `path`: multi-error commands (e.g. `noodle validate`) nest them under `error.errors[]`, each `{ code, path, message }`. The top-level `error` still carries `code`/`message`/`fix`/`next`; the per-field `path`s live in `error.errors[]`.
|
|
19
19
|
- **Repair prose is isolated**: ready-to-apply repair text appears only under `error.fixPrompt` (surfaced by `--fix-prompt`), never mixed into `message` or `data`.
|
|
20
|
+
- **Streams are NDJSON envelopes**: the initial snapshot is `{ ok: true, data: { kind: "snapshot", snapshot } }`, subsequent records are `{ ok: true, data: { kind: "event", event } }`, and a terminal failure is the ordinary `{ ok: false, error }` envelope on its own line. Parse each line independently.
|
|
20
21
|
|
|
21
22
|
## Exit codes
|
|
22
23
|
|
|
@@ -248,7 +248,125 @@ Or import the package root once and mount `<noodle-assistant session-endpoint="/
|
|
|
248
248
|
|
|
249
249
|
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`.
|
|
250
250
|
|
|
251
|
-
For a customer-owned renderer, use the
|
|
251
|
+
For a customer-owned React renderer, use the renderer-free hook. It owns client lifetime and React subscription while `client` remains the one command surface:
|
|
252
|
+
|
|
253
|
+
```tsx
|
|
254
|
+
"use client";
|
|
255
|
+
|
|
256
|
+
import { useState } from "react";
|
|
257
|
+
import { useNoodleAssistant } from "@noodleseed/assistant/react/client";
|
|
258
|
+
|
|
259
|
+
export function CustomAssistant({ principalKey }: { principalKey: string }) {
|
|
260
|
+
const [draft, setDraft] = useState("");
|
|
261
|
+
const { client, messages, status, error } = useNoodleAssistant({
|
|
262
|
+
sessionEndpoint: "/api/assistant/session",
|
|
263
|
+
principalKey,
|
|
264
|
+
clientContext: () => ({
|
|
265
|
+
locale: navigator.language,
|
|
266
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
267
|
+
}),
|
|
268
|
+
});
|
|
269
|
+
const busy = status === "submitted" || status === "streaming";
|
|
270
|
+
const settle = (operation: Promise<void>) => {
|
|
271
|
+
void operation.catch(() => {
|
|
272
|
+
// The hook exposes this same structured failure through `error`.
|
|
273
|
+
});
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<section aria-label="Assistant" aria-busy={busy}>
|
|
278
|
+
{messages.map((message) => (
|
|
279
|
+
<article key={message.id} data-role={message.role}>
|
|
280
|
+
{message.parts.map((part, index) => {
|
|
281
|
+
if (part.type === "text") return <p key={index}>{part.text}</p>;
|
|
282
|
+
if (part.type === "data-confirmation") {
|
|
283
|
+
const review = part.data;
|
|
284
|
+
return (
|
|
285
|
+
<section key={review.id} aria-label="Review proposed action">
|
|
286
|
+
<h3>{review.title ?? "Review proposed action"}</h3>
|
|
287
|
+
{review.description ? <p>{review.description}</p> : null}
|
|
288
|
+
<pre aria-label="Proposed action arguments">
|
|
289
|
+
{JSON.stringify(review.arguments ?? {}, null, 2)}
|
|
290
|
+
</pre>
|
|
291
|
+
<button
|
|
292
|
+
disabled={busy || review.status !== "pending"}
|
|
293
|
+
onClick={() => settle(client.respond(review.id, { action: "accept" }))}
|
|
294
|
+
>
|
|
295
|
+
Confirm
|
|
296
|
+
</button>
|
|
297
|
+
<button
|
|
298
|
+
disabled={busy || review.status !== "pending"}
|
|
299
|
+
onClick={() => settle(client.respond(review.id, { action: "decline" }))}
|
|
300
|
+
>
|
|
301
|
+
Don't proceed
|
|
302
|
+
</button>
|
|
303
|
+
</section>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
if (part.type === "data-input-request") {
|
|
307
|
+
const request = part.data;
|
|
308
|
+
return (
|
|
309
|
+
<section key={request.id} aria-label="Assistant needs input">
|
|
310
|
+
<p>{request.message}</p>
|
|
311
|
+
<p>This renderer has not implemented the requested form.</p>
|
|
312
|
+
<button
|
|
313
|
+
disabled={busy || request.status !== "pending"}
|
|
314
|
+
onClick={() => settle(client.respond(request.id, { action: "decline" }))}
|
|
315
|
+
>
|
|
316
|
+
Cancel request
|
|
317
|
+
</button>
|
|
318
|
+
</section>
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
if (part.type === "data-tool-result") {
|
|
322
|
+
return (
|
|
323
|
+
<pre key={part.data.id} aria-label={`${part.data.tool} result`}>
|
|
324
|
+
{JSON.stringify(part.data.result, null, 2)}
|
|
325
|
+
</pre>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
if (part.type === "data-view") {
|
|
329
|
+
return (
|
|
330
|
+
<p key={part.data.id}>
|
|
331
|
+
Trusted app view available: {part.data.title ?? part.data.resourceUri}
|
|
332
|
+
</p>
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
return <p key={index}>Unsupported assistant content.</p>;
|
|
336
|
+
})}
|
|
337
|
+
</article>
|
|
338
|
+
))}
|
|
339
|
+
{error ? <p role="alert">{error.message}</p> : null}
|
|
340
|
+
<form
|
|
341
|
+
onSubmit={(event) => {
|
|
342
|
+
event.preventDefault();
|
|
343
|
+
const message = draft.trim();
|
|
344
|
+
if (!message) return;
|
|
345
|
+
setDraft("");
|
|
346
|
+
settle(client.sendMessage(message));
|
|
347
|
+
}}
|
|
348
|
+
>
|
|
349
|
+
<input
|
|
350
|
+
aria-label="Message"
|
|
351
|
+
value={draft}
|
|
352
|
+
onChange={(event) => setDraft(event.currentTarget.value)}
|
|
353
|
+
/>
|
|
354
|
+
{busy ? (
|
|
355
|
+
<button type="button" onClick={() => client.abort()}>Stop</button>
|
|
356
|
+
) : (
|
|
357
|
+
<button type="submit">Send</button>
|
|
358
|
+
)}
|
|
359
|
+
</form>
|
|
360
|
+
</section>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
`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.
|
|
366
|
+
|
|
367
|
+
The sample fails closed on input requests until you replace that branch with a form generated from `requestedSchema`. A custom renderer must show the complete confirmation review and both decisions, handle every part it supports, and surface an explicit unsupported state for the rest. For `data-view`, map `resourceUri` or `tool` plus the bounded/redacted `result` to a component already trusted by the application. Never inject `part.data.html`, assign it to `srcdoc`, or fetch a `ui://` URI; the managed element alone supplies Noodle’s sandbox host. Do not wrap this client in another chat transport or invent user messages for interaction continuations.
|
|
368
|
+
|
|
369
|
+
Outside React, use the same DOM-free client directly. It keeps the session token in memory, exposes a React-free `UIMessage` transcript with typed parts, and never registers a custom element:
|
|
252
370
|
|
|
253
371
|
```ts
|
|
254
372
|
import { createAssistantClient } from "@noodleseed/assistant/client";
|
|
@@ -266,30 +384,39 @@ assistant.updateModelContext({
|
|
|
266
384
|
structuredContent: { widget: { name: 'time-off', lifecycle: 'mounted' } },
|
|
267
385
|
});
|
|
268
386
|
|
|
269
|
-
let
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
387
|
+
let pending: { id: string; requestedSchema?: Readonly<Record<string, unknown>> } | undefined;
|
|
388
|
+
assistant.subscribeChat((state) => {
|
|
389
|
+
renderUIMessageState(state);
|
|
390
|
+
pending = undefined;
|
|
391
|
+
for (const message of state.messages) {
|
|
392
|
+
for (const part of message.parts) {
|
|
393
|
+
if (part.type === 'data-confirmation' && part.data.status === 'pending') {
|
|
394
|
+
pending = { id: part.data.id };
|
|
395
|
+
}
|
|
396
|
+
if (part.type === 'data-input-request' && part.data.status === 'pending') {
|
|
397
|
+
pending = { id: part.data.id, requestedSchema: part.data.requestedSchema };
|
|
398
|
+
}
|
|
399
|
+
if (part.type === 'data-view') {
|
|
400
|
+
renderRegisteredView(part.data.resourceUri, part.data.result);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
279
403
|
}
|
|
280
404
|
});
|
|
281
405
|
|
|
282
406
|
await assistant.sendMessage("Book next Thursday and Friday off");
|
|
283
|
-
if (
|
|
407
|
+
if (pending) {
|
|
408
|
+
const requestedSchema = pending.requestedSchema;
|
|
284
409
|
const resolution = requestedSchema
|
|
285
410
|
? { action: 'accept' as const, content: await renderPortableForm(requestedSchema) }
|
|
286
411
|
: { action: 'accept' as const };
|
|
287
|
-
await assistant.respond(
|
|
412
|
+
await assistant.respond(pending.id, resolution);
|
|
288
413
|
}
|
|
289
414
|
// The same pending id also accepts { action: 'decline' } or { action: 'cancel' }.
|
|
290
415
|
```
|
|
291
416
|
|
|
292
|
-
`
|
|
417
|
+
`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.
|
|
418
|
+
|
|
419
|
+
`data-view` means a completed tool has a linked MCP App view. It carries the call/interaction id, tool, `ui://` identity, optional title, bounded/redacted public result, and—on current services—the self-contained bridged document. The standard element is an MCP Apps host and mounts that document behind a double iframe; a customer renderer ignores it and maps the identity/result to an application-trusted component. The standard element supports lifecycle, app tool/resource calls, ui/message, ui/update-model-context, links, resize, and inline/fullscreen; sampling, tasks, downloads, and remote DOM are not advertised. It also dispatches `assistant-view-available` for a customer-owned renderer.
|
|
293
420
|
|
|
294
421
|
`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.
|
|
295
422
|
|
|
@@ -297,7 +424,7 @@ if (pendingId) {
|
|
|
297
424
|
|
|
298
425
|
- Node.js 20+ for `@noodleseed/assistant/server`.
|
|
299
426
|
- The package ships ESM and CommonJS with full export conditions; no bundler aliases, `transpilePackages`, or ambient type shims are needed. If resolution fails, the installed package version is outdated: update `@noodleseed/assistant` instead of adding workarounds.
|
|
300
|
-
- TypeScript `moduleResolution` `bundler` or `node16` recommended; classic `node` also resolves the `/client`, `/react`, and `/server` subpaths.
|
|
427
|
+
- TypeScript `moduleResolution` `bundler` or `node16` recommended; classic `node` also resolves the `/client`, `/react`, `/react/client`, and `/server` subpaths.
|
|
301
428
|
|
|
302
429
|
## Verify the boundary
|
|
303
430
|
|
|
@@ -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.42.0 hash:1e23938b39956ef8 -->
|
|
7
7
|
|
|
8
8
|
# reporting-noodle-feedback
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: verifying-mcp-delivery
|
|
|
3
3
|
description: "Use when proving a Noodle Seed MCP project works at a named compile, local, connector, App, host, deployment, or production evidence level."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.42.0 hash:6ef6ef551e26b78e -->
|
|
7
7
|
|
|
8
8
|
# verifying-mcp-delivery
|
|
9
9
|
|
package/skills/codex/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: noodle-seed
|
|
|
3
3
|
description: "Use when building, validating, testing, deploying, or operating a local or hosted Noodle Seed MCP server or app authored in TypeScript with the noodle CLI."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.42.0 hash:0c1ebd532b9c2499 -->
|
|
7
7
|
|
|
8
8
|
# Noodle Seed
|
|
9
9
|
|
|
@@ -39,7 +39,7 @@ Negative routing examples: “Inspect hosted logs/status” → `inspect-hosted`
|
|
|
39
39
|
|
|
40
40
|
## Common machine loop
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
Every `--json` command speaks the canonical envelope on stdout. Parse machine state instead of scraping human prose; `references/agent-contract.md` owns the envelope, streaming records, and exit codes.
|
|
43
43
|
|
|
44
44
|
1. **Discover** — use `noodle commands --json` when the required command or flags are uncertain; don't read CLI source.
|
|
45
45
|
2. **Author** — for build routes, edit the configured TypeScript entrypoint, usually `src/server.ts`.
|
|
@@ -3,7 +3,7 @@ name: authoring-mcp-servers
|
|
|
3
3
|
description: "Use when creating or extending a headless Noodle Seed MCP server, tool, resource, prompt, or typed model-facing capability."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.42.0 hash:0b2fd8c7e43fc69f -->
|
|
7
7
|
|
|
8
8
|
# authoring-mcp-servers
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: building-mcp-apps
|
|
|
3
3
|
description: "Use when a Noodle Seed MCP App, widget, interactive card, visual interaction, or host-visible UI is the primary requested outcome."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.42.0 hash:f7fa54992c8d7692 -->
|
|
7
7
|
|
|
8
8
|
# building-mcp-apps
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: connecting-apis-to-mcp
|
|
|
3
3
|
description: "Use when credentials, an API URL, an OpenAPI document, or an observed response must become real Noodle Seed MCP behavior."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.42.0 hash:1e86b8704f407bd3 -->
|
|
7
7
|
|
|
8
8
|
# connecting-apis-to-mcp
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: debugging-mcp-delivery
|
|
|
3
3
|
description: "Use when an existing Noodle Seed MCP project has a concrete validation, runtime, connector, App, host, deployment, or production failure."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.42.0 hash:aa715bae12041d7c -->
|
|
7
7
|
|
|
8
8
|
# debugging-mcp-delivery
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: deploying-mcp-services
|
|
|
3
3
|
description: "Use when the user explicitly requests a Noodle Seed hosted link, configuration write, deployment, access change, rollback, or connection write."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.42.0 hash:93e735b7ffb45df1 -->
|
|
7
7
|
|
|
8
8
|
# deploying-mcp-services
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: designing-mcp-products
|
|
|
3
3
|
description: "Use when a Noodle Seed MCP product idea needs conversational fit, user benefit, scope, interaction, or evidence design before implementation."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.42.0 hash:76cce86729cffbee -->
|
|
7
7
|
|
|
8
8
|
# designing-mcp-products
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ name: embedding-mcp-assistants
|
|
|
3
3
|
description: "Use when embedding a Noodle assistant into an existing SaaS or web application with browser, identity, session, and credential boundaries."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- noodle-skill version:0.
|
|
6
|
+
<!-- noodle-skill version:0.42.0 hash:5d8f40f904d6ab4b -->
|
|
7
7
|
|
|
8
8
|
# embedding-mcp-assistants
|
|
9
9
|
|