@noodleseed/agent-kit 0.84.1 → 0.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/manifest.json +271 -271
  2. package/package.json +1 -1
  3. package/skills/claude-code/SKILL.md +1 -1
  4. package/skills/claude-code/authoring-mcp-servers/SKILL.md +1 -1
  5. package/skills/claude-code/building-mcp-apps/SKILL.md +1 -1
  6. package/skills/claude-code/connecting-apis-to-mcp/SKILL.md +1 -1
  7. package/skills/claude-code/creating-product-agent-guides/SKILL.md +1 -1
  8. package/skills/claude-code/debugging-mcp-delivery/SKILL.md +1 -1
  9. package/skills/claude-code/deploying-mcp-services/SKILL.md +1 -1
  10. package/skills/claude-code/designing-mcp-products/SKILL.md +1 -1
  11. package/skills/claude-code/embedding-mcp-assistants/SKILL.md +1 -1
  12. package/skills/claude-code/examples/acme-discovery/README.md +58 -15
  13. package/skills/claude-code/examples/acme-discovery/src/server.ts +127 -13
  14. package/skills/claude-code/examples/acme-discovery/test/server.test.ts +39 -0
  15. package/skills/claude-code/examples/customer-auth/README.md +9 -1
  16. package/skills/claude-code/examples/customer-auth/src/server.ts +11 -3
  17. package/skills/claude-code/examples/customer-auth/test/server.test.ts +1 -1
  18. package/skills/claude-code/executing-noodle-plans/SKILL.md +1 -1
  19. package/skills/claude-code/publishing-mcp-integrations/SKILL.md +1 -1
  20. package/skills/claude-code/references/embedded-assistant.md +34 -19
  21. package/skills/claude-code/reporting-noodle-feedback/SKILL.md +1 -1
  22. package/skills/claude-code/verifying-mcp-delivery/SKILL.md +1 -1
  23. package/skills/claude-code/wrapping-existing-applications/SKILL.md +1 -1
  24. package/skills/codex/SKILL.md +1 -1
  25. package/skills/codex/authoring-mcp-servers/SKILL.md +1 -1
  26. package/skills/codex/building-mcp-apps/SKILL.md +1 -1
  27. package/skills/codex/connecting-apis-to-mcp/SKILL.md +1 -1
  28. package/skills/codex/creating-product-agent-guides/SKILL.md +1 -1
  29. package/skills/codex/debugging-mcp-delivery/SKILL.md +1 -1
  30. package/skills/codex/deploying-mcp-services/SKILL.md +1 -1
  31. package/skills/codex/designing-mcp-products/SKILL.md +1 -1
  32. package/skills/codex/embedding-mcp-assistants/SKILL.md +1 -1
  33. package/skills/codex/examples/acme-discovery/README.md +58 -15
  34. package/skills/codex/examples/acme-discovery/src/server.ts +127 -13
  35. package/skills/codex/examples/acme-discovery/test/server.test.ts +39 -0
  36. package/skills/codex/examples/customer-auth/README.md +9 -1
  37. package/skills/codex/examples/customer-auth/src/server.ts +11 -3
  38. package/skills/codex/examples/customer-auth/test/server.test.ts +1 -1
  39. package/skills/codex/executing-noodle-plans/SKILL.md +1 -1
  40. package/skills/codex/publishing-mcp-integrations/SKILL.md +1 -1
  41. package/skills/codex/references/embedded-assistant.md +34 -19
  42. package/skills/codex/reporting-noodle-feedback/SKILL.md +1 -1
  43. package/skills/codex/verifying-mcp-delivery/SKILL.md +1 -1
  44. package/skills/codex/wrapping-existing-applications/SKILL.md +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noodleseed/agent-kit",
3
- "version": "0.84.1",
3
+ "version": "0.86.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.84.1 hash:13ddce01769caae4 -->
6
+ <!-- noodle-skill version:0.86.0 hash:13ddce01769caae4 -->
7
7
 
8
8
  # Noodle Seed
9
9
 
@@ -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.84.1 hash:11523cb33b9473c0 -->
6
+ <!-- noodle-skill version:0.86.0 hash:11523cb33b9473c0 -->
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.84.1 hash:9fd67d4d24328e15 -->
6
+ <!-- noodle-skill version:0.86.0 hash:9fd67d4d24328e15 -->
7
7
 
8
8
  # building-mcp-apps
9
9
 
@@ -3,7 +3,7 @@ name: connecting-apis-to-mcp
3
3
  description: "Use when all four API-evidence inputs exist—and only then: API base URL, authentication scheme, representative safe read, and observed response."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.84.1 hash:21bbd3ec441ffd30 -->
6
+ <!-- noodle-skill version:0.86.0 hash:21bbd3ec441ffd30 -->
7
7
 
8
8
  # connecting-apis-to-mcp
9
9
 
@@ -3,7 +3,7 @@ name: creating-product-agent-guides
3
3
  description: "Use when a Noodle Seed MCP server needs a new or revised product agent guide, App Package skill, or explicit product-skill regeneration."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.84.1 hash:0fa48a82fe836cf0 -->
6
+ <!-- noodle-skill version:0.86.0 hash:0fa48a82fe836cf0 -->
7
7
 
8
8
  # creating-product-agent-guides
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.84.1 hash:aa715bae12041d7c -->
6
+ <!-- noodle-skill version:0.86.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.84.1 hash:93e735b7ffb45df1 -->
6
+ <!-- noodle-skill version:0.86.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.84.1 hash:78a6f181b61f92f1 -->
6
+ <!-- noodle-skill version:0.86.0 hash:78a6f181b61f92f1 -->
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.84.1 hash:cc54a67f21c0ecdb -->
6
+ <!-- noodle-skill version:0.86.0 hash:cc54a67f21c0ecdb -->
7
7
 
8
8
  # embedding-mcp-assistants
9
9
 
@@ -13,30 +13,73 @@ it" flow the `noodle-seed` skill's `references/experience-design.md` teaches.
13
13
 
14
14
  ## The same tools on Acme's own website
15
15
 
16
- The funnel does not only start in ChatGPT. The `assistant` block projects these same three tools onto
17
- Acme's marketing site for a visitor with **no account and no session backend**:
16
+ The funnel does not only start in ChatGPT. The `assistant` block projects these same tools onto
17
+ Acme's marketing site for a visitor with **no account and no session backend** — as a **mixed**
18
+ surface, so the visitor can also sign in mid-conversation:
18
19
 
19
20
  ```ts
20
- access: publicWebsite({
21
- origins: ['https://getaways.acme.example'],
22
- capabilities: [discoverGetaways, createHandoff, shortlistGetaway],
23
- instructions:
24
- 'Be a friendly, consultative travel guide, never pushy. Help visitors narrow a getaway before suggesting the next useful step.',
25
- }),
21
+ access: [
22
+ publicWebsite({
23
+ origins: ['https://getaways.acme.example'],
24
+ capabilities: [destinations, discoverGetaways, createHandoff, shortlistGetaway, captureLead, myTrips],
25
+ signIn: true, // my_trips reads ${user}; reaching it raises the sign-in card
26
+ instructions:
27
+ 'Be a friendly, consultative travel guide, never pushy. Help visitors narrow a getaway before suggesting the next useful step.',
28
+ }),
29
+ authenticatedWebsite({
30
+ origins: ['https://account.acme.example'],
31
+ capabilities: [destinations, discoverGetaways, createHandoff, myTrips],
32
+ instructions: 'The traveler is signed in. Help them plan from their saved trips.',
33
+ }),
34
+ ],
26
35
  ```
27
36
 
28
- There is no second tool set and no second app — one `server.ts`, projected onto another front door.
37
+ There is no second tool set and no second app — one `server.ts`, projected onto its front doors.
29
38
  The flagship selects `model: noodleManaged()`, so an enrolled hosted deployment needs no customer model
30
39
  endpoint, name, or key and its artifact remains provider-neutral. Sponsored beta enrollment is exact to the
31
40
  org/app/environment and fails closed outside that cohort; `openAICompatible(...)` remains the BYO alternative.
32
41
  The surface `instructions` add only the website-specific voice and goal; shared product truth stays in
33
- `server.instructions`. This public guidance is injected into public assistant turns, never MCP
42
+ `server.instructions`. This public guidance is injected into that surface's assistant turns, never MCP
34
43
  `initialize` or another assistant surface.
35
- `capabilities` is the entire externally reachable surface, so it stays short enough to review at a glance
36
- and closed by default: a tool added to this server later is unreachable from the website until someone
37
- lists it. A tool that needed a signed-in user could not be listed here at all (the compiler rejects it);
38
- serve those from `authenticatedWebsite({ origins })` instead, where Acme's own backend proves who the
39
- visitor is, or add `signIn: true` so visitors sign in mid-conversation through Acme own login.
44
+ `capabilities` is the entire externally reachable surface per front door, so it stays short enough to
45
+ review at a glance and closed by default: a tool added to this server later is unreachable from the
46
+ website until someone lists it.
47
+
48
+ ## Sign in mid-conversation, land in the account
49
+
50
+ `signIn: true` makes the marketing surface **mixed**: `my_trips` stays visible so the assistant can
51
+ offer it, and an anonymous visitor who reaches for it sees a branded card — *Sign in* plus, because
52
+ `labels.signUpAction` is authored, *Create free account*. Both raise `assistant-sign-in-requested`
53
+ with a single-use `signInTicket`; the detail's `intent` tells the page whether to route its login or
54
+ its registration. Acme's page signs the visitor in exactly as it already does, its backend spends the
55
+ ticket with `createAssistantSession({ ..., signInTicket })`, and the **same conversation continues**
56
+ on whichever origin the backend designates:
57
+
58
+ - landing back on `getaways.acme.example` keeps the mixed surface's projection with identity attached;
59
+ - landing on `account.acme.example` rebinds the conversation to the authenticated surface — its
60
+ capabilities and its voice — and the widget repaints the visible transcript and auto-answers the
61
+ intercepted `my_trips` ask under the new identity.
62
+
63
+ The ticket spend after account creation is identical to the one after sign-in; the service never
64
+ operates a login of its own.
65
+
66
+ ## The consultative sales gateway
67
+
68
+ When a visitor's plans firm up but they would rather not sign up, the assistant may — with explicit
69
+ confirmation — take their details and deliver them to Acme's own sink. The recipe is a composition of
70
+ existing primitives, not a platform feature:
71
+
72
+ - `capture_lead` is an ordinary tool with `annotations.action({ confirm: true })`: the confirmation
73
+ card, showing every field, is the visitor's consent moment.
74
+ - Delivery is a declarative HTTP connector whose endpoint is `variable('LEAD_SINK_URL')` and whose
75
+ credential is `secret('LEAD_SINK_TOKEN')` — the operator supplies values with
76
+ `noodle variables set` / `noodle secrets set`; the example stays credential-free and one authored
77
+ class serves any business.
78
+ - The request mapping sets `source: 'website-assistant'` itself, so Acme's sink can trust the
79
+ attribution; the model never supplies it.
80
+ - The lead rests only in Acme's own system. The platform stores no lead, and a vendor sink is just
81
+ different data: Resend/Postmark are `auth: { kind: 'apiKey', … }`, a HubSpot private app is
82
+ `auth: { kind: 'bearer', … }` — never a named vendor package.
40
83
 
41
84
  ## Design spec (write this before the code)
42
85
 
@@ -1,13 +1,17 @@
1
1
  import {
2
2
  annotations,
3
+ authenticatedWebsite,
4
+ connector,
3
5
  embeddedAssistant,
4
6
  file,
5
7
  knowledge,
6
8
  noodleManaged,
7
9
  publicWebsite,
10
+ secret,
8
11
  server,
9
12
  site,
10
13
  tool,
14
+ variable,
11
15
  z,
12
16
  } from '@noodleseed/one';
13
17
 
@@ -197,6 +201,90 @@ const shortlistGetaway = tool('shortlist_getaway', {
197
201
  }),
198
202
  });
199
203
 
204
+ // The consultative sales gateway (ADR 0214): when a visitor would rather not sign up, the assistant
205
+ // may — with explicit confirmation — take their details and deliver them to Acme's own sink. The
206
+ // recipe is a composition of existing primitives, not a platform feature: an ordinary confirm-gated
207
+ // action plus a declarative HTTP connector whose endpoint and credential are operator-managed
208
+ // (`noodle variables set LEAD_SINK_URL …`, `noodle secrets set LEAD_SINK_TOKEN …`). The payload
209
+ // rests only in Acme's own system; the platform stores no lead. A vendor sink is the same shape as
210
+ // data: Resend/Postmark take `auth: { kind: 'apiKey', … }`, a HubSpot private app takes
211
+ // `auth: { kind: 'bearer', … }` — never a named vendor package.
212
+ const leadSink = connector('lead_sink')
213
+ .version('1.0.0')
214
+ .http({
215
+ baseUrl: variable('LEAD_SINK_URL'),
216
+ allowedOrigins: ['https://acme.example'],
217
+ auth: { kind: 'bearer', secret: secret('LEAD_SINK_TOKEN') },
218
+ operations: {
219
+ submit_lead: {
220
+ type: 'action',
221
+ method: 'POST',
222
+ path: '/api/assistant-lead',
223
+ input: z.object({
224
+ name: z.string().trim().min(2).max(120),
225
+ workEmail: z.email().max(240),
226
+ company: z.string().trim().min(2).max(200),
227
+ note: z.string().max(500),
228
+ }),
229
+ output: z.object({ ok: z.boolean() }),
230
+ request: {
231
+ name: '${args.name}',
232
+ workEmail: '${args.workEmail}',
233
+ company: '${args.company}',
234
+ note: '${args.note}',
235
+ // Fixed attribution, set here rather than model-supplied: Acme's sink can trust it.
236
+ source: 'website-assistant',
237
+ },
238
+ response: { ok: '${response.ok}' },
239
+ },
240
+ },
241
+ });
242
+
243
+ const captureLead = tool('capture_lead', {
244
+ title: 'Send my details to Acme',
245
+ description:
246
+ 'Send the visitor’s contact details and trip interest to Acme Getaways so the team may follow ' +
247
+ 'up. Call only after the visitor explicitly agrees to be contacted; the confirmation card is ' +
248
+ 'their consent moment. After a confirmed success, say only that the details were sent — never ' +
249
+ 'promise response timing.',
250
+ annotations: annotations.action({ confirm: true }),
251
+ input: z.object({
252
+ name: z.string().trim().min(2).max(120).meta({ title: 'Your name' }),
253
+ workEmail: z.email().max(240).meta({ title: 'Work email' }),
254
+ company: z.string().trim().min(2).max(200).meta({ title: 'Company' }),
255
+ note: z.string().max(500).default('').meta({ title: 'What are you planning?' }),
256
+ }),
257
+ output: z.object({ ok: z.boolean() }),
258
+ fulfil: ({ input, connectors }) => {
259
+ const result = connectors.leads.submitLead({
260
+ name: input.name,
261
+ workEmail: input.workEmail,
262
+ company: input.company,
263
+ note: input.note,
264
+ });
265
+ return { ok: result.ok };
266
+ },
267
+ });
268
+
269
+ // The mixed surface's sign-in trigger (ADR 0201): reading `${user.id}` classifies this tool
270
+ // requires-identity, so an anonymous visitor who reaches for it sees the sign-in card instead of an
271
+ // error — and after signing in on Acme's account origin, the conversation continues under the
272
+ // authenticated surface below.
273
+ const myTrips = tool('my_trips', {
274
+ title: 'My saved trips',
275
+ description: 'Read the signed-in traveler’s saved trips and their booking status.',
276
+ annotations: readOnly,
277
+ input: z.object({}),
278
+ output: z.object({
279
+ traveler: z.string(),
280
+ status: z.string(),
281
+ }),
282
+ fulfil: ({ user }) => ({
283
+ traveler: user.id as string,
284
+ status: 'No trips booked yet — shortlist a getaway to start one.',
285
+ }),
286
+ });
287
+
200
288
  // Grounding beyond the catalog: two controlled files answer policy/pricing/support questions with
201
289
  // citations, and Acme's live public site is crawled on deploy and re-crawled on the declared
202
290
  // refresh cadence — no sync job, no handwritten search tool. One declaration, one generated
@@ -241,23 +329,49 @@ export default server(
241
329
  handoff: {
242
330
  allowedDomains: ['https://book.acme.example', 'https://acme.example'],
243
331
  },
244
- // The same three tools also serve Acme's own marketing site, with no second tool set and no
245
- // session backend: a visitor with no account gets the discovery carousel and the booking
246
- // handoff. `capabilities` is the whole externally reachable surface short enough to review in
247
- // one glance, and closed by default when a tool is added to the server later. The knowledge
248
- // component projects its generated search capability the same way a tool does.
332
+ use: { leads: leadSink },
333
+ // The same tools also serve Acme's own websites, with no second tool set. The marketing site is
334
+ // a **mixed** surface (`signIn: true`): a visitor with no account gets discovery, the booking
335
+ // handoff, and the confirm-gated lead capture and reaching for `my_trips` raises the sign-in
336
+ // card instead of an error, with `signUpAction` offering account creation through Acme's own
337
+ // registration. After the login redirect lands on the account origin, the same conversation
338
+ // continues under the authenticated surface's capabilities and instructions (ADR 0201).
339
+ // `capabilities` is the whole externally reachable surface per front door — short enough to
340
+ // review in one glance, and closed by default when a tool is added to the server later.
249
341
  assistant: embeddedAssistant({
250
342
  model: noodleManaged(),
251
- access: publicWebsite({
252
- origins: ['https://getaways.acme.example'],
253
- capabilities: [destinations, discoverGetaways, createHandoff, shortlistGetaway],
254
- instructions:
255
- 'Be a friendly, consultative travel guide, never pushy. Help visitors narrow a getaway before suggesting the next useful step. Ground recommendations in Acme knowledge, and clearly separate discovery from booking.',
256
- }),
343
+ access: [
344
+ publicWebsite({
345
+ origins: ['https://getaways.acme.example'],
346
+ capabilities: [
347
+ destinations,
348
+ discoverGetaways,
349
+ createHandoff,
350
+ shortlistGetaway,
351
+ captureLead,
352
+ myTrips,
353
+ ],
354
+ signIn: true,
355
+ instructions:
356
+ 'Be a friendly, consultative travel guide, never pushy. Help visitors narrow a getaway before suggesting the next useful step. Ground recommendations in Acme knowledge, and clearly separate discovery from booking. When a visitor’s plans firm up, invite them to sign in or create an account; if they would rather not, offer — once — to send their details to the Acme team instead.',
357
+ }),
358
+ authenticatedWebsite({
359
+ origins: ['https://account.acme.example'],
360
+ capabilities: [destinations, discoverGetaways, createHandoff, myTrips],
361
+ instructions:
362
+ 'The traveler is signed in. Help them plan from their saved trips, and keep booking on Acme’s own pages through the handoff.',
363
+ }),
364
+ ],
257
365
  layout: { mode: 'floating', position: 'bottom-right' },
258
- labels: { welcomeHeading: 'Where would you like to go?' },
366
+ labels: {
367
+ welcomeHeading: 'Where would you like to go?',
368
+ signInHeading: 'Continue with your Acme account',
369
+ signInBody: 'Saved trips need an account.',
370
+ signInAction: 'Sign in',
371
+ signUpAction: 'Create free account',
372
+ },
259
373
  }),
260
374
  knowledge: [destinations],
261
375
  },
262
- [discoverGetaways, createHandoff, shortlistGetaway],
376
+ [discoverGetaways, createHandoff, shortlistGetaway, captureLead, myTrips],
263
377
  );
@@ -30,6 +30,45 @@ describe('acme-discovery example', () => {
30
30
  );
31
31
  });
32
32
 
33
+ it('keeps the lead capture behind explicit confirmation and a managed customer sink', async () => {
34
+ const manifest = (await app.toManifest()) as {
35
+ tools: { name: string; annotations?: Record<string, unknown> }[];
36
+ };
37
+ const captureLead = manifest.tools.find((tool) => tool.name === 'capture_lead');
38
+ // The confirmation card is the visitor's consent moment (ADR 0214): a lead may never leave the
39
+ // conversation without it, and the sink endpoint/credential stay operator-managed data.
40
+ expect(captureLead?.annotations?.confirm).toBe(true);
41
+ const catalog = JSON.stringify(
42
+ (app as unknown as { toConnectorCatalog: () => unknown }).toConnectorCatalog(),
43
+ );
44
+ expect(catalog).toContain('${env.LEAD_SINK_URL}');
45
+ expect(catalog).toContain('LEAD_SINK_TOKEN');
46
+ // Fixed attribution set in the request mapping, never model-supplied; no named vendor host.
47
+ expect(catalog).toContain('website-assistant');
48
+ expect(catalog).not.toContain('api.resend.com');
49
+ expect(catalog).not.toContain('api.hubapi.com');
50
+ });
51
+
52
+ it('serves a mixed marketing surface and an authenticated account surface from one server', async () => {
53
+ const manifest = await app.toManifest();
54
+ const surfaces = manifest.server.assistant?.surfaces ?? [];
55
+ expect(surfaces.map((surface) => surface.mode)).toEqual(['mixed', 'authenticated']);
56
+ // The sign-in trigger is listed on the mixed surface so the assistant can offer it; the
57
+ // authenticated surface carries its own narrowed list and voice.
58
+ const capabilityNames = (surface: (typeof surfaces)[number]) =>
59
+ surface.capabilities?.map((capability) => capability.name) ?? [];
60
+ expect(capabilityNames(surfaces[0]!)).toContain('my_trips');
61
+ expect(capabilityNames(surfaces[0]!)).toContain('capture_lead');
62
+ expect(capabilityNames(surfaces[1]!)).toEqual([
63
+ 'destinations',
64
+ 'discover_getaways',
65
+ 'create_handoff',
66
+ 'my_trips',
67
+ ]);
68
+ // Authoring the sign-up label is the opt-in for the card's create-account button.
69
+ expect(manifest.server.assistant?.labels?.signUpAction).toBe('Create free account');
70
+ });
71
+
33
72
  it('declares the grounded knowledge component and its live site scope', async () => {
34
73
  const manifest = (await app.toManifest()) as { server: { knowledge?: unknown[] } };
35
74
  // One declaration: controlled files plus the live public site, compiled later into the
@@ -380,7 +380,15 @@ loopback development origins such as `http://localhost:3000`, `http://127.0.0.1:
380
380
 
381
381
  The bounded `presentation` object configures the panel, launcher, header, composer, and messages. Its
382
382
  primitives derive colors from shared server `branding`; raw HTML, CSS, inline SVG, renderer classes, and
383
- callbacks are not accepted.
383
+ callbacks are not accepted. This example omits `presentation.panel.surface`, so the renderer keeps the
384
+ opaque default panel treatment while the example's light/dark `branding` surfaces provide its customer colors;
385
+ set the bounded surface to `glass` only when translucency is intentional.
386
+
387
+ These TypeScript values remain the reusable developer defaults. After deployment, an environment operator
388
+ can adjust theme, logo, launcher style, position, and the bounded color palette from the Console's
389
+ **Assistant** tab or `noodle assistant appearance` without changing the customer's embed code. See the
390
+ [embedded assistant guide](https://docs.noodleseed.dev/guides/embedded-assistant) for precedence and reset
391
+ behavior.
384
392
 
385
393
  Create the backend credential after deployment. The CLI writes it to a mode-0600 file and never prints the
386
394
  secret:
@@ -173,14 +173,22 @@ export default server(
173
173
  access: authenticatedWebsite({
174
174
  origins: [assistantOrigin, 'https://dev.noodleseed.com', 'http://localhost:3000'],
175
175
  }),
176
- layout: { mode: 'floating', position: 'bottom-right', panelWidth: 420 },
176
+ theme: 'auto',
177
+ layout: { mode: 'floating', position: 'bottom-center', panelWidth: 970 },
178
+ behavior: { showPoweredBy: true },
177
179
  labels: {
178
180
  welcomeHeading: 'How can I help with Noodle Seed?',
181
+ launcherPlaceholder: 'Ask Noodle Seed anything',
179
182
  composerPlaceholder: 'Ask about your apps…',
180
183
  },
181
184
  presentation: {
182
- panel: { surface: 'glass', elevation: 'soft', border: 'subtle' },
183
- launcher: { icon: 'brand-mark', status: 'session', effect: 'pulse' },
185
+ panel: { elevation: 'soft', border: 'subtle' },
186
+ launcher: {
187
+ style: 'pill',
188
+ icon: 'brand-mark',
189
+ status: 'session',
190
+ effect: 'pulse',
191
+ },
184
192
  header: {
185
193
  mark: 'status',
186
194
  badge: { text: 'Workspace online', tone: 'success', indicator: true },
@@ -9,7 +9,7 @@ describe('customer-auth example', () => {
9
9
  model: { kind: 'openai-compatible', apiKey: 'ASSISTANT_MODEL_API_KEY' },
10
10
  layout: { mode: 'floating' },
11
11
  presentation: {
12
- panel: { surface: 'glass', elevation: 'soft', border: 'subtle' },
12
+ panel: { elevation: 'soft', border: 'subtle' },
13
13
  launcher: { icon: 'brand-mark', status: 'session', effect: 'pulse' },
14
14
  header: { mark: 'status', badge: { text: 'Workspace online', tone: 'success' } },
15
15
  },
@@ -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.84.1 hash:6a9f132ddb79352e -->
6
+ <!-- noodle-skill version:0.86.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.84.1 hash:efffbf82007f935d -->
6
+ <!-- noodle-skill version:0.86.0 hash:efffbf82007f935d -->
7
7
 
8
8
  # publishing-mcp-integrations
9
9
 
@@ -53,7 +53,7 @@ assistant: embeddedAssistant({
53
53
  access: authenticatedWebsite({
54
54
  origins: ["http://localhost:3000", "https://app.example.com"],
55
55
  }),
56
- layout: { mode: "floating", position: "bottom-right" },
56
+ layout: { mode: "floating", position: "bottom-center" },
57
57
  }),
58
58
  ```
59
59
 
@@ -107,11 +107,19 @@ access: publicWebsite({
107
107
  capabilities: [answerProductQuestion, requestDemo, myOrders],
108
108
  signIn: true, // `myOrders` reads ${user}; visitors sign in to reach it
109
109
  }),
110
+ labels: {
111
+ signInHeading: "Continue with your Acme account",
112
+ signInBody: "Order history needs an account.", // optional; empty hides the line
113
+ signInAction: "Sign in",
114
+ signUpAction: "Create free account", // authoring this label is the sign-up opt-in
115
+ },
110
116
  ```
111
117
 
118
+ 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
+
112
120
  Elevation runs through the **host application own login**, never a Noodle-operated one. The widget raises `assistant-sign-in-requested` with a single-use `signInTicket` in its detail; the page signs the visitor in as it already does, then its backend spends the ticket with `createAssistantSession({ ..., signInTicket })` from `@noodleseed/assistant/server` — the same session exchange, its own client credentials. A refused spend throws a typed `AssistantSessionExchangeError`: branch on `elevationRefusal` (`elevation_ticket_expired` re-prompt; `elevation_tenant_mismatch` alert, never retry). Possession of the ticket alone elevates nothing, and the service checks the client tenant owns that conversation. The ticket is not the server-held interaction continuation — that value never reaches browser code; this one exists to travel through the page.
113
121
 
114
- The conversation is kept server-side: same session, new token, the anonymous one dead. By default the pending request also completes itself: the service re-attempts the intercepted tool under the new principal and streams it as the elevated session first turn (one-shot; mooted if the visitor types first; confirm-gated tools stop at their confirmation card; pass `resume: false` beside the ticket to disable). Set honest expectations in UI copy the assistant **remembers** the conversation and finishes the pending request, but no transcript is replayed to the browser, so after a full-page navigation earlier messages do not repaint. Say "the assistant remembers", never "your conversation will reappear". Do not build a second identity provider or client-side resume scaffolding for this.
122
+ The conversation is kept server-side: same session, new token, the anonymous one dead. By default the pending request also completes itself: the service re-attempts the intercepted tool under the new principal and streams it as the elevated session first turn (one-shot; mooted if the visitor types first; confirm-gated tools stop at their confirmation card; pass `resume: false` beside the ticket to disable). On a backend-exchanged reattach the widget also repaints the bounded visible transcript (`endpoints.transcript`) before the resume runs only rows the panel actually showed replay, never tool internals or a spent ticket. On an older service without the endpoint the panel starts visually fresh while the model still remembers, so keep copy honest either way: "the assistant remembers". Do not build a second identity provider or client-side resume scaffolding for this.
115
123
 
116
124
  When the login lives on a different origin (marketing site + app), the flow is: the visitor signs in via full-page redirect as the site already does; the backend spends the ticket presenting the **origin the conversation will continue on** (any origin in the deployment allowlist — elevation re-pins the session there, and CORS follows); the token reaches the widget through the customer own **same-origin** session endpoint on that origin. The redirect handoff is mandatory, not stylistic: the widget calls the session endpoint with `credentials: "same-origin"`, so pointing a marketing page at a cross-origin endpoint is a guaranteed cookie-less 401. Persist the ticket across the login redirect (single-use, expires in minutes); a refused origin does not burn it.
117
125
 
@@ -134,13 +142,15 @@ Keep portable identity and semantic light/dark colors in the one server-level `b
134
142
 
135
143
  ```ts
136
144
  assistant: embeddedAssistant({
137
- model, allowedOrigins,
138
- layout: { panelWidth: 520, panelMinHeight: 540, panelMaxHeight: 740, edgeOffset: 24 },
139
- behavior: { showTimestamps: true },
140
- labels: { composerPlaceholder: "Message Acme Support…", sessionReady: "Acme support is online" },
145
+ model,
146
+ access: authenticatedWebsite({ origins: ["https://app.example.com"] }),
147
+ theme: "invert",
148
+ layout: { position: "bottom-right", panelWidth: 520, panelMinHeight: 540, panelMaxHeight: 740, edgeOffset: 24 },
149
+ behavior: { showTimestamps: true, showPoweredBy: false },
150
+ labels: { launcherPlaceholder: "Ask Acme anything", composerPlaceholder: "Message Acme Support…", sessionReady: "Acme support is online" },
141
151
  presentation: {
142
152
  panel: { surface: "solid", elevation: "dramatic", border: "strong", radius: 20 },
143
- launcher: { icon: "chat", size: "lg", status: "session", effect: "pulse" },
153
+ launcher: { style: "bubble", icon: "chat", size: "lg", status: "session", effect: "pulse" },
144
154
  header: {
145
155
  mark: "status",
146
156
  badge: { text: "Online", tone: "success", indicator: true },
@@ -151,9 +161,9 @@ assistant: embeddedAssistant({
151
161
  }),
152
162
  ```
153
163
 
154
- The Atlas-style product treatment above is the maximum deployment-configurable presentation. The bounded surface covers panel treatment, launcher icon/size/session pulse, header mark/status badge, composer controls, and message treatment; it does not accept custom header actions, structured empty-state layouts, footers, spectacle variants/effects, or tenant code.
164
+ The Atlas-style product treatment above is the maximum deployment-configurable presentation. The bounded surface covers panel treatment, pill/bubble launcher style plus icon/size/session pulse, header mark/status badge, composer controls, and message treatment; it does not accept custom header actions, structured empty-state layouts, footers, tenant-defined launcher variants/effects, or tenant code.
155
165
 
156
- Omitted fields retain the quiet premium baseline. For exact application-owned color roles, pass the typed React `appearance={{ light: { panel: { surface, text, border }, composer: {...}, confirmation: {...}, primaryButton: {...} }, dark: {...} }}` prop or assign the same object to `element.appearance`. CSS custom properties inherit through the assistant host, so those values may reuse existing application tokens such as `surface: "var(--app-surface)"` without copying literals. The appearance surface covers canvas, panel, header, messages, composer, suggestions, confirmation, buttons, launcher, code, and the MCP App frame; the package README publishes the complete role-to-`--ns-assistant-*` map. Exact parseable literal colors are preserved and low contrast emits `assistant-appearance-warning`; contrast for unresolved CSS references remains host-owned. Precedence is host appearance object, host slots/public variables, deployed semantic presentation, then defaults. Prefer `server.ts` configuration first so every embedding app receives the same assistant after redeploy.
166
+ Omitted UI fields retain the complete managed baseline: a bottom-center frosted prompt pill, 970px outer desktop shell with 20px side padding, 85vh/1025px height bounds, 24px panel with built-in `#F8F8F8` light and `#0C0A09` dark surfaces, bottom prompt chips and pill composer, plain assistant messages, 85%-wide user bubbles, Noodle Seed attribution, and mobile fullscreen. The pill morphs into an input before opening; `launcher.style: "bubble"` opens directly, while `panel.surface: "glass"` remains an explicit translucent alternative. `theme: "auto"` follows the host page and `"invert"` selects its opposite. The generic suggestions are defaults; an authored list, including `[]`, replaces them. The ChatGPT promotion is intentionally absent. For exact application-owned color roles, pass the typed React `appearance={{ light: { panel: { surface, text, border }, composer: {...}, confirmation: {...}, primaryButton: {...} }, dark: {...} }}` prop or assign the same object to `element.appearance`. CSS custom properties inherit through the assistant host, so those values may reuse existing application tokens such as `surface: "var(--app-surface)"` without copying literals. The appearance surface covers canvas, panel, header, messages, composer, suggestions, confirmation, buttons, launcher, code, and the MCP App frame; the package README publishes the complete role-to-`--ns-assistant-*` map. Exact parseable literal colors are preserved and low contrast emits `assistant-appearance-warning`; contrast for unresolved CSS references remains host-owned. Precedence is host appearance object, host slots/public variables, saved environment operator override, deployed semantic presentation, then defaults. Prefer reusable `server.ts` defaults; use the Console Assistant tab or `noodle assistant appearance show|apply|reset` for environment-owned changes that should reach existing embeds without a redeploy.
157
167
 
158
168
  Give every business action a portable `tool(..., { title: "Complete task", description: "This will mark the task complete for everyone.", input: z.object({ task: z.string().meta({ title: "Task" }) }) })` title. The standard confirmation uses the tool title/description plus schema field `title`, `description`, and `format`; it shows Confirm and Don't proceed and keeps technical action details secondary. Do not put JSON or implementation names in business-facing copy.
159
169
 
@@ -311,12 +321,15 @@ const session = await createAssistantSession({
311
321
 
312
322
  ```ts
313
323
  assistant: embeddedAssistant({
314
- model, allowedOrigins,
315
- sessionClaims: {
316
- displayName: { exposeToModel: true },
317
- accountTier: { exposeToModel: true },
318
- region: {}, // tools only, never in the prompt
319
- },
324
+ model,
325
+ access: authenticatedWebsite({
326
+ origins: ["https://app.example.com"],
327
+ sessionClaims: {
328
+ displayName: { exposeToModel: true },
329
+ accountTier: { exposeToModel: true },
330
+ region: {}, // tools only, never in the prompt
331
+ },
332
+ }),
320
333
  }),
321
334
  ```
322
335
 
@@ -372,7 +385,9 @@ import { NoodleAssistant } from "@noodleseed/assistant/react";
372
385
 
373
386
  Or import the package root once and mount `<noodle-assistant session-endpoint="/api/assistant/session" theme="auto"></noodle-assistant>`. Mount only inside the authenticated application surface.
374
387
 
375
- `theme="auto"` follows the browser operating-system preference. If the SaaS application owns a theme toggle, obtain its resolved application theme (`"light"` or `"dark"`), pass `theme={resolvedTheme}` to `NoodleAssistant`, and update the custom element's `theme` attribute when that value changes.
388
+ That custom element is the complete managed assistant in Vue, Angular, or plain DOM; it has no React runtime requirement. Configure the framework to accept `noodle-assistant` as a custom element. If the session exchange needs an authenticated fetch wrapper, create the element imperatively, assign `element.fetch` and then `element.sessionEndpoint`, and append it only after both properties are set.
389
+
390
+ `theme="auto"` follows an explicit host-page light/dark class or data attribute, then the browser operating-system preference; `theme="invert"` selects the opposite. If the SaaS application owns a theme toggle, obtain its resolved application theme (`"light"` or `"dark"`), pass `theme={resolvedTheme}` to `NoodleAssistant`, and update the custom element's `theme` attribute when that value changes.
376
391
 
377
392
  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`.
378
393
 
@@ -627,7 +642,7 @@ Devtools privacy gate: default model and connector exercises to synthetic or moc
627
642
 
628
643
  - Signed-out session exchange returns `401`.
629
644
  - The browser network/DOM/storage contains no client secret or model key.
630
- - The local and production origins match `allowedOrigins` character-for-character.
645
+ - The local and production origins match the authored `access.origins` character-for-character.
631
646
  - At the manifest/runtime boundary and in TypeScript action helpers, only `confirm: true` enables confirmation; omitted or `false` preserves direct execution. Action hints alone never enforce approval; `annotations.action({ confirm: false })` is equivalent to omission.
632
647
  - An expired turn re-exchanges once; interaction decisions never auto-retry. An explicit same-decision repeat returns the stored outcome without executing again.
633
648
  - Accept, decline, and cancel are single-use. Only accept executes; the server ignores replacement tool arguments.
@@ -645,9 +660,9 @@ Devtools privacy gate: default model and connector exercises to synthetic or moc
645
660
  | `assistant-error` with code `invalid_response` | The turn endpoint returned HTML or non-SSE content (auth redirect, proxy page) | Check the backend session route path and any middleware/rewrites on the embedding app |
646
661
  | Build error `Package path ./react is not exported` | Outdated package version with import-only export conditions | Update `@noodleseed/assistant`; do not add webpack aliases or type shims |
647
662
  | Deploy fails with `server_auth_required` | `--access customers` without `server.auth` | Add direct/federated OIDC or a built-in Firebase/Microsoft adapter |
648
- | Validate rejects an origin | Non-loopback HTTP origin in `allowedOrigins` | Use the exact HTTPS production origin; HTTP is only for `localhost`/`127.0.0.1` |
663
+ | Validate rejects an origin | Non-loopback HTTP origin in `access.origins` | Use the exact HTTPS production origin; HTTP is only for `localhost`/`127.0.0.1` |
649
664
  | Session exchange returns 404 | `serviceUrl` points at the deployment MCP endpoint | Use the control-plane service URL printed by `noodle assistant clients create` |
650
- | Session exchange returns 403 `origin is not allowed` | Request origin differs from `allowedOrigins` character-for-character | Align the exact scheme/host/port on both sides and redeploy |
665
+ | Session exchange returns 403 `origin is not allowed` | Request origin differs from the authored `access.origins` character-for-character | Align the exact scheme/host/port on both sides and redeploy |
651
666
  | Session exchange returns `400` with `invalid assistant routing` | The authenticated backend supplied an unknown endpoint name or a malformed/policy-disallowed URL | Resolve the route from server-owned membership, use the exact authored endpoint name, and ensure the canonical HTTPS URL satisfies its active `customerEndpoint` policy; the error never reflects the URL |
652
667
  | A routed assistant tool returns `connector_route_unavailable` | The authenticated backend omitted that endpoint during session exchange | Pass the server-verified route as `routing.endpoints.<name>` when minting a new session; keep it out of browser input |
653
668
  | Host session 503 | A required backend environment name is absent or mapped into the wrong deployment environment | Run `noodle assistant embed --check --json`, repair the host CI mapping, then probe the session route again |
@@ -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.84.1 hash:0f404109f4845683 -->
6
+ <!-- noodle-skill version:0.86.0 hash:0f404109f4845683 -->
7
7
 
8
8
  # reporting-noodle-feedback
9
9