@noodleseed/agent-kit 0.20.0 → 0.21.1

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 (31) hide show
  1. package/README.md +2 -2
  2. package/manifest.json +37 -27
  3. package/package.json +2 -2
  4. package/skills/claude-code/SKILL.md +4 -3
  5. package/skills/claude-code/examples/acme-bistro/README.md +1 -1
  6. package/skills/claude-code/examples/acme-tasks/README.md +8 -3
  7. package/skills/claude-code/examples/food-ordering/README.md +8 -0
  8. package/skills/claude-code/examples/food-ordering/src/server.ts +3 -1
  9. package/skills/claude-code/examples/hello/README.md +4 -0
  10. package/skills/claude-code/examples/hello/src/server.ts +5 -2
  11. package/skills/claude-code/examples/hello/test/server.test.ts +11 -0
  12. package/skills/claude-code/references/authoring-workflow.md +1 -1
  13. package/skills/claude-code/references/compile-errors.md +1 -0
  14. package/skills/claude-code/references/deploy-and-ops.md +5 -0
  15. package/skills/claude-code/references/embedded-assistant.md +208 -0
  16. package/skills/claude-code/references/sdk-surface.md +2 -2
  17. package/skills/claude-code/references/widgets-and-apps.md +31 -21
  18. package/skills/codex/SKILL.md +4 -3
  19. package/skills/codex/examples/acme-bistro/README.md +1 -1
  20. package/skills/codex/examples/acme-tasks/README.md +8 -3
  21. package/skills/codex/examples/food-ordering/README.md +8 -0
  22. package/skills/codex/examples/food-ordering/src/server.ts +3 -1
  23. package/skills/codex/examples/hello/README.md +4 -0
  24. package/skills/codex/examples/hello/src/server.ts +5 -2
  25. package/skills/codex/examples/hello/test/server.test.ts +11 -0
  26. package/skills/codex/references/authoring-workflow.md +1 -1
  27. package/skills/codex/references/compile-errors.md +1 -0
  28. package/skills/codex/references/deploy-and-ops.md +5 -0
  29. package/skills/codex/references/embedded-assistant.md +208 -0
  30. package/skills/codex/references/sdk-surface.md +2 -2
  31. package/skills/codex/references/widgets-and-apps.md +31 -21
package/README.md CHANGED
@@ -15,7 +15,7 @@ into a project's `.agents/` and `.claude/` skill directories via `noodle agents
15
15
  - `skills/claude-code/` — the Claude Code skill, same hierarchical tree.
16
16
  - `manifest.json` — `{ packageVersion, files: [{ path, sha256, agentTarget }] }`. The CLI verifies every
17
17
  file's sha256 against this manifest before writing it into a user's project; a mismatch refuses the
18
- write. See [ADR 0102](https://github.com/NoodleSeed-com/noodle-borg/blob/main/docs/decisions/0102-self-checking-skills-via-npm.md).
18
+ write. The manifest and every generated skill file are available in the public source tree for inspection.
19
19
 
20
20
  ## How the CLI uses it
21
21
 
@@ -35,5 +35,5 @@ reference files.
35
35
  ## License
36
36
 
37
37
  Apache-2.0. This package is generated and published from the
38
- [`noodle-borg`](https://github.com/NoodleSeed-com/noodle-borg) repository; the renderer is the private
38
+ [`noodle-core`](https://github.com/NoodleSeed-com/noodle-core) repository; the renderer is the
39
39
  `@noodle-borg/agent-kit` workspace package.
package/manifest.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
- "packageVersion": "0.20.0",
2
+ "packageVersion": "0.21.1",
3
3
  "files": [
4
4
  {
5
5
  "path": "skills/codex/SKILL.md",
6
- "sha256": "a62843d66b5bde2d718f394b4d78ce25a7f5808884d8e29789480bc16f198406",
6
+ "sha256": "4a48b0a357c2dfab02ff191d68a9ebb47ad8396a63f476a0f60ba69cc7b943ad",
7
7
  "agentTarget": "codex"
8
8
  },
9
9
  {
10
10
  "path": "skills/codex/references/sdk-surface.md",
11
- "sha256": "f19560c3232558eb692dc4ff0cee74850ebca9029f95272233e734aa3854faba",
11
+ "sha256": "0916b1f4df01053c424b46eeb4e8ee0910709d66b956c4d19da77d5abc4b046b",
12
12
  "agentTarget": "codex"
13
13
  },
14
14
  {
@@ -23,12 +23,17 @@
23
23
  },
24
24
  {
25
25
  "path": "skills/codex/references/compile-errors.md",
26
- "sha256": "de43b81d6b9c1a065aa1905fdb469e518e50e80bf34306964593e608649f6868",
26
+ "sha256": "1039e245421e8c128cf1a81cf7a3ce809b0149478917040abebc124ffcb60423",
27
27
  "agentTarget": "codex"
28
28
  },
29
29
  {
30
30
  "path": "skills/codex/references/authoring-workflow.md",
31
- "sha256": "fd2a67d3ba67f640edddca6d30ed8d38f278f0f3e9ab1c1669ace2aef8797c01",
31
+ "sha256": "b4ca23d7545c8e96dbfee6a4cf2235d4eadfb27302e453fe631031041a17a8c6",
32
+ "agentTarget": "codex"
33
+ },
34
+ {
35
+ "path": "skills/codex/references/embedded-assistant.md",
36
+ "sha256": "687666010deb32bb605ccc23aeac8778fd1d02c53d7d8e232b6d0b8aeef77668",
32
37
  "agentTarget": "codex"
33
38
  },
34
39
  {
@@ -43,7 +48,7 @@
43
48
  },
44
49
  {
45
50
  "path": "skills/codex/references/widgets-and-apps.md",
46
- "sha256": "86bfc4d43a2d1226dcbce8c41fe0b1f36562a0cc7ac7964f4ee579bd91398d34",
51
+ "sha256": "69ff229945465793e6c9ae10e5185fc3b5e59b9fb508f03a81ab8d2a12b39efc",
47
52
  "agentTarget": "codex"
48
53
  },
49
54
  {
@@ -58,7 +63,7 @@
58
63
  },
59
64
  {
60
65
  "path": "skills/codex/references/deploy-and-ops.md",
61
- "sha256": "0175101939ca5ea6823a18ce83a32311f5d9937ef8469a431dd0576ad0997fe4",
66
+ "sha256": "1b359a78e76ad6a71003a3d235685019cd2e1e2bbabd95ec520761d9915a2259",
62
67
  "agentTarget": "codex"
63
68
  },
64
69
  {
@@ -78,7 +83,7 @@
78
83
  },
79
84
  {
80
85
  "path": "skills/codex/examples/acme-bistro/README.md",
81
- "sha256": "d94f67f7518208cba14920c26ed8aaa1546b496d96e55645e85b5d75a1a64127",
86
+ "sha256": "2bcf803ea2407479ddc70644a79ad503335880fd8439ed673b17c8d50e7cb59f",
82
87
  "agentTarget": "codex"
83
88
  },
84
89
  {
@@ -193,7 +198,7 @@
193
198
  },
194
199
  {
195
200
  "path": "skills/codex/examples/acme-tasks/README.md",
196
- "sha256": "1d65cde8d1b47ade9e895ef99a92a0aec9d9ffe0c578f0d2dc4bafa0bcccca26",
201
+ "sha256": "e1d654d1a98b721ef7ce4f5d5d3d868611079ed4dbf2126b766018adb95d4194",
197
202
  "agentTarget": "codex"
198
203
  },
199
204
  {
@@ -248,7 +253,7 @@
248
253
  },
249
254
  {
250
255
  "path": "skills/codex/examples/food-ordering/README.md",
251
- "sha256": "5524001b5f632f3b5329fbf4e408512d4b45ea29a27893516f8b0eb902bd8618",
256
+ "sha256": "4d42dfbaf46ee8003ae84b3ef79c9d2a8b716943b85e2e9329fa875007019524",
252
257
  "agentTarget": "codex"
253
258
  },
254
259
  {
@@ -268,7 +273,7 @@
268
273
  },
269
274
  {
270
275
  "path": "skills/codex/examples/food-ordering/src/server.ts",
271
- "sha256": "1b1329c99af4798072b69979f1f0861c3b3587b237df46d3cf7b923060f134cb",
276
+ "sha256": "6e8c4c17e6f3a7867232880e1a8d0e45428ebfd3938282d6bec7d8542ae7d712",
272
277
  "agentTarget": "codex"
273
278
  },
274
279
  {
@@ -298,7 +303,7 @@
298
303
  },
299
304
  {
300
305
  "path": "skills/codex/examples/hello/README.md",
301
- "sha256": "f66f3355bc8083fdd86f67b10a3c2d7977ce08369a2ecd62d3a68338f9ae5488",
306
+ "sha256": "c39b89f95c4ae1a2b78a71f42c00910507c32eaba628be36e5ec3ef32508aaa5",
302
307
  "agentTarget": "codex"
303
308
  },
304
309
  {
@@ -313,12 +318,12 @@
313
318
  },
314
319
  {
315
320
  "path": "skills/codex/examples/hello/src/server.ts",
316
- "sha256": "a387dd0a860852753c5aa009604c7b85352baa3d220d2f32e0ecec62b72b5597",
321
+ "sha256": "5b7dd35428128cfe0f506054e80b060d8a87ef157b03d2ce94d304b14df6e521",
317
322
  "agentTarget": "codex"
318
323
  },
319
324
  {
320
325
  "path": "skills/codex/examples/hello/test/server.test.ts",
321
- "sha256": "e6c5779402eb2974a59c48823d579b745f0406f6a4f51a67b4ba4b19ab6e9cd6",
326
+ "sha256": "4de4932d95a920f4e5fd78fc58f3583285aab3c3a97a3c1417f8a863ee05aa98",
322
327
  "agentTarget": "codex"
323
328
  },
324
329
  {
@@ -348,12 +353,12 @@
348
353
  },
349
354
  {
350
355
  "path": "skills/claude-code/SKILL.md",
351
- "sha256": "1fbf6a1f0649627017b0f296e9458764e345da62a2e326bb8c3238ac3d4a51ae",
356
+ "sha256": "23754f0386b77f112249ca8b948b479792895303f0257ab0961cbbb81a857693",
352
357
  "agentTarget": "claude-code"
353
358
  },
354
359
  {
355
360
  "path": "skills/claude-code/references/sdk-surface.md",
356
- "sha256": "f19560c3232558eb692dc4ff0cee74850ebca9029f95272233e734aa3854faba",
361
+ "sha256": "0916b1f4df01053c424b46eeb4e8ee0910709d66b956c4d19da77d5abc4b046b",
357
362
  "agentTarget": "claude-code"
358
363
  },
359
364
  {
@@ -368,12 +373,17 @@
368
373
  },
369
374
  {
370
375
  "path": "skills/claude-code/references/compile-errors.md",
371
- "sha256": "de43b81d6b9c1a065aa1905fdb469e518e50e80bf34306964593e608649f6868",
376
+ "sha256": "1039e245421e8c128cf1a81cf7a3ce809b0149478917040abebc124ffcb60423",
372
377
  "agentTarget": "claude-code"
373
378
  },
374
379
  {
375
380
  "path": "skills/claude-code/references/authoring-workflow.md",
376
- "sha256": "fd2a67d3ba67f640edddca6d30ed8d38f278f0f3e9ab1c1669ace2aef8797c01",
381
+ "sha256": "b4ca23d7545c8e96dbfee6a4cf2235d4eadfb27302e453fe631031041a17a8c6",
382
+ "agentTarget": "claude-code"
383
+ },
384
+ {
385
+ "path": "skills/claude-code/references/embedded-assistant.md",
386
+ "sha256": "687666010deb32bb605ccc23aeac8778fd1d02c53d7d8e232b6d0b8aeef77668",
377
387
  "agentTarget": "claude-code"
378
388
  },
379
389
  {
@@ -388,7 +398,7 @@
388
398
  },
389
399
  {
390
400
  "path": "skills/claude-code/references/widgets-and-apps.md",
391
- "sha256": "86bfc4d43a2d1226dcbce8c41fe0b1f36562a0cc7ac7964f4ee579bd91398d34",
401
+ "sha256": "69ff229945465793e6c9ae10e5185fc3b5e59b9fb508f03a81ab8d2a12b39efc",
392
402
  "agentTarget": "claude-code"
393
403
  },
394
404
  {
@@ -403,7 +413,7 @@
403
413
  },
404
414
  {
405
415
  "path": "skills/claude-code/references/deploy-and-ops.md",
406
- "sha256": "0175101939ca5ea6823a18ce83a32311f5d9937ef8469a431dd0576ad0997fe4",
416
+ "sha256": "1b359a78e76ad6a71003a3d235685019cd2e1e2bbabd95ec520761d9915a2259",
407
417
  "agentTarget": "claude-code"
408
418
  },
409
419
  {
@@ -423,7 +433,7 @@
423
433
  },
424
434
  {
425
435
  "path": "skills/claude-code/examples/acme-bistro/README.md",
426
- "sha256": "d94f67f7518208cba14920c26ed8aaa1546b496d96e55645e85b5d75a1a64127",
436
+ "sha256": "2bcf803ea2407479ddc70644a79ad503335880fd8439ed673b17c8d50e7cb59f",
427
437
  "agentTarget": "claude-code"
428
438
  },
429
439
  {
@@ -538,7 +548,7 @@
538
548
  },
539
549
  {
540
550
  "path": "skills/claude-code/examples/acme-tasks/README.md",
541
- "sha256": "1d65cde8d1b47ade9e895ef99a92a0aec9d9ffe0c578f0d2dc4bafa0bcccca26",
551
+ "sha256": "e1d654d1a98b721ef7ce4f5d5d3d868611079ed4dbf2126b766018adb95d4194",
542
552
  "agentTarget": "claude-code"
543
553
  },
544
554
  {
@@ -593,7 +603,7 @@
593
603
  },
594
604
  {
595
605
  "path": "skills/claude-code/examples/food-ordering/README.md",
596
- "sha256": "5524001b5f632f3b5329fbf4e408512d4b45ea29a27893516f8b0eb902bd8618",
606
+ "sha256": "4d42dfbaf46ee8003ae84b3ef79c9d2a8b716943b85e2e9329fa875007019524",
597
607
  "agentTarget": "claude-code"
598
608
  },
599
609
  {
@@ -613,7 +623,7 @@
613
623
  },
614
624
  {
615
625
  "path": "skills/claude-code/examples/food-ordering/src/server.ts",
616
- "sha256": "1b1329c99af4798072b69979f1f0861c3b3587b237df46d3cf7b923060f134cb",
626
+ "sha256": "6e8c4c17e6f3a7867232880e1a8d0e45428ebfd3938282d6bec7d8542ae7d712",
617
627
  "agentTarget": "claude-code"
618
628
  },
619
629
  {
@@ -643,7 +653,7 @@
643
653
  },
644
654
  {
645
655
  "path": "skills/claude-code/examples/hello/README.md",
646
- "sha256": "f66f3355bc8083fdd86f67b10a3c2d7977ce08369a2ecd62d3a68338f9ae5488",
656
+ "sha256": "c39b89f95c4ae1a2b78a71f42c00910507c32eaba628be36e5ec3ef32508aaa5",
647
657
  "agentTarget": "claude-code"
648
658
  },
649
659
  {
@@ -658,12 +668,12 @@
658
668
  },
659
669
  {
660
670
  "path": "skills/claude-code/examples/hello/src/server.ts",
661
- "sha256": "a387dd0a860852753c5aa009604c7b85352baa3d220d2f32e0ecec62b72b5597",
671
+ "sha256": "5b7dd35428128cfe0f506054e80b060d8a87ef157b03d2ce94d304b14df6e521",
662
672
  "agentTarget": "claude-code"
663
673
  },
664
674
  {
665
675
  "path": "skills/claude-code/examples/hello/test/server.test.ts",
666
- "sha256": "e6c5779402eb2974a59c48823d579b745f0406f6a4f51a67b4ba4b19ab6e9cd6",
676
+ "sha256": "4de4932d95a920f4e5fd78fc58f3583285aab3c3a97a3c1417f8a863ee05aa98",
667
677
  "agentTarget": "claude-code"
668
678
  },
669
679
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noodleseed/agent-kit",
3
- "version": "0.20.0",
3
+ "version": "0.21.1",
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",
@@ -24,7 +24,7 @@
24
24
  ],
25
25
  "repository": {
26
26
  "type": "git",
27
- "url": "https://github.com/NoodleSeed-com/noodle-borg",
27
+ "url": "https://github.com/NoodleSeed-com/noodle-core",
28
28
  "directory": "packages/agent-kit"
29
29
  },
30
30
  "author": "Noodle Seed",
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  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
- version: 0.20.0
5
- hash: b4952708b1c38f55
4
+ version: 0.21.1
5
+ hash: b4fc528d406e2149
6
6
  ---
7
7
 
8
8
  # Noodle Seed
@@ -25,7 +25,7 @@ Before authoring, design the experience — the funnel/handoff boundary, tools,
25
25
  4. **Repair** — fix each `error.errors[]` entry at its `path`, then re-run `noodle validate --json`; `noodle validate --fix-prompt` emits ready-to-apply repair prose. Never freeform re-edit (see `references/compile-errors.md`).
26
26
  5. **Smoke** — `noodle test --json`: local compile plus a loopback MCP smoke.
27
27
  6. **Prove real output** — `validate`/`test` prove a connector tool *compiles and registers*, not that its response mapping returns data. Set the secret at the scope your local `noodle dev` resolves (`noodle secrets set <NAME> --runtime local --scope org --org local --from-env <ENV>`; a secret set at the wrong scope leaves the loopback returning `-32600 "not found"` — see `references/connect-an-api.md`), then run a live read — `noodle tools call <read_tool> --args '{...}'` executes the connector against the real API in-process — and confirm the mapped fields are populated, not `undefined`, before trusting it. Only run a live write if it is safe/approved.
28
- 7. **Apps/widgets** — `noodle check --json` (add `--target chatgpt|claude`), then `noodle devtools` (see `references/widgets-and-apps.md`).
28
+ 7. **Apps/widgets/embed** — `noodle check --json` (add `--target chatgpt|claude|embedded-assistant`), then `noodle devtools`; use `references/widgets-and-apps.md` for MCP Apps and `references/embedded-assistant.md` for a SaaS embed.
29
29
  8. **Deploy** — `noodle deploy`; auth fails clean with `error.next` = `noodle login` (see `references/deploy-and-ops.md`).
30
30
  9. **Wire into a host** — `noodle connect <codex|claude-code|chatgpt>` (prove it in a real host per `references/test-in-hosts.md`; debug symptoms with `references/troubleshooting.md`).
31
31
  10. **Health** — `noodle metrics --agent-output`: a health verdict plus the exact next command per attention item.
@@ -39,6 +39,7 @@ Scan all of these during discovery; open in full the ones your task touches:
39
39
  - `references/cli-commands.md` — every `noodle` command, grouped by area.
40
40
  - `references/compile-errors.md` — fix `noodle validate` errors by code.
41
41
  - `references/authoring-workflow.md` — input paths (scrape / OpenAPI import / user interview), the fit check, the validate→test→dev repair loop, connectors, and secrets/variables.
42
+ - `references/embedded-assistant.md` — HTTPS origins, managed model config, deploy-before-client sequencing, backend session exchange, browser mounting, and credential boundaries.
42
43
  - `references/connect-an-api.md` — given an API key: secure it, probe the live API to learn the real shape, model the connector, and prove real output before building.
43
44
  - `references/experience-design.md` — design the app experience before authoring: funnel/handoff boundary, grounding, two-users, display modes, and the wireframe/UX spec.
44
45
  - `references/widgets-and-apps.md` — MCP Apps, React `view` widgets, the widget hook surface, output shaping, and CSP.
@@ -34,7 +34,7 @@ In another terminal:
34
34
  ```sh
35
35
  noodle tools list
36
36
  noodle tools call show_menu --args '{"customer":"Asha"}'
37
- noodle tools call create_checkout --args '{"customer":"Asha","cartToken":"stone_pizzax2-lemon_tartx1","total":36}'
37
+ noodle tools call create_checkout --args '{"customer":"Asha","cartToken":"cart_1","total":36}'
38
38
  noodle check --target chatgpt
39
39
  ```
40
40
 
@@ -97,13 +97,18 @@ assistant: embeddedAssistant({
97
97
  The assistant automatically inherits this server's existing `branding` block, so its name, accent,
98
98
  light/dark surfaces, density, and radius match the `TaskList` widget without a second brand declaration.
99
99
 
100
+ Every assistant origin is exact and HTTPS-only. If the customer web app needs local browser testing, it
101
+ must serve itself over development HTTPS and add its `https://localhost:<port>` origin; `noodle dev` does
102
+ not provide TLS for that separate SaaS app.
103
+
100
104
  The customer backend exchanges its authenticated user through `@noodleseed/assistant/server`; the browser
101
105
  uses the Web Component or React wrapper and never receives the embed client or model secret. Validate with
102
106
  `noodle check --target embedded-assistant`, then create the backend credential with
103
- `noodle assistant clients create` after deployment.
107
+ `noodle assistant clients create` after deployment. Model URL/name/key values stay in Noodle managed config;
108
+ only the Noodle service URL and assistant client ID/secret belong in the authenticated customer backend.
104
109
 
105
- Install the independently versioned embed SDK in the customer web application with
106
- `pnpm add @noodleseed/assistant`.
110
+ Install the independently versioned embed SDK with the customer web application's existing package manager;
111
+ do not introduce a second lockfile.
107
112
 
108
113
  This example has no connector secrets and does not include tokens, caller-key mechanisms, or
109
114
  `.env.noodle` values. All tasks are fictional seed data.
@@ -21,6 +21,14 @@ private customer data.
21
21
  | Handoff | `handoff.allowedDomains` allows only `https://orders.example.com` checkout URLs |
22
22
  | Progressive enhancement | Non-Apps hosts still receive stores, featured items, and a readable fallback summary |
23
23
 
24
+ The example is intentionally richer than the generated starter, but each inline view still follows the
25
+ same default: one immediate purpose, one primary action, at most one subordinate action, and progressive
26
+ disclosure for the rest. Preview it at 280px before adding navigation or local CSS; loading, empty, stale,
27
+ error/retry, and success states must remain readable without nested vertical scrolling.
28
+
29
+ Like the comprehensive default `noodle init my-app` scaffold, this flagship keeps the server feature-rich
30
+ while making each individual widget view focused; server capability breadth and screen density are separate.
31
+
24
32
  ## Local Author Loop
25
33
 
26
34
  ```sh
@@ -151,7 +151,9 @@ const cartStateSchema = z.object({
151
151
  customer: z.string(),
152
152
  notes: z.string().optional(),
153
153
  subtotal: z.number(),
154
- status: z.enum(['draft', 'review', 'handoff']),
154
+ // `.default()`/`.optional()` state fields are optional on write: a cart save that omits
155
+ // `status` still validates, and a fresh cart starts in `draft`.
156
+ status: z.enum(['draft', 'review', 'handoff']).default('draft'),
155
157
  checkoutUrl: z.string().optional(),
156
158
  });
157
159
 
@@ -10,4 +10,8 @@ noodle dev examples/hello/src/server.ts --app hello
10
10
  noodle deploy examples/hello/src/server.ts --org acme --app hello
11
11
  ```
12
12
 
13
+ `noodle export manifest examples/hello/src/server.ts` compiles the same entrypoint locally and prints
14
+ the portable, vendor-neutral manifest JSON — the eject path: your `server.ts` plus this manifest is
15
+ the whole app, yours to read, diff, and keep.
16
+
13
17
  It is also the fixture for `pnpm smoke:dev` and the e2e harness, so keep its tool surface stable.
@@ -1,4 +1,4 @@
1
- import { server, tool, z } from '@noodleseed/one';
1
+ import { annotations, server, tool, z } from '@noodleseed/one';
2
2
 
3
3
  export default server(
4
4
  'hello',
@@ -16,11 +16,14 @@ export default server(
16
16
  tool('greet', {
17
17
  description: 'Greet someone by name.',
18
18
  input: z.object({
19
- name: z.string(),
19
+ // Defaults are advertised to the model and applied at runtime when the argument is omitted.
20
+ name: z.string().default('world'),
20
21
  }),
21
22
  output: z.object({
22
23
  message: z.string(),
23
24
  }),
25
+ // Read-only, closed-world: assistant surfaces run this without a consent prompt.
26
+ annotations: annotations.readOnly(),
24
27
  fulfil: ({ input }) => {
25
28
  return { message: `Hello, ${input.name}!` };
26
29
  },
@@ -5,4 +5,15 @@ describe('hello example', () => {
5
5
  it('exports a Noodle server definition', () => {
6
6
  expect(typeof app.toManifest).toBe('function');
7
7
  });
8
+
9
+ it('advertises the greet default and keeps the argument optional', async () => {
10
+ const manifest = await app.toManifest();
11
+ const greet = manifest.tools?.find((tool) => tool.name === 'greet');
12
+ const schema = greet?.inputSchema as {
13
+ properties?: { name?: { default?: unknown } };
14
+ required?: string[];
15
+ };
16
+ expect(schema.properties?.name?.default).toBe('world');
17
+ expect(schema.required ?? []).not.toContain('name');
18
+ });
8
19
  });
@@ -200,7 +200,7 @@ Author managed config as `secret("NAME")` / `variable("NAME")` and operate it wi
200
200
 
201
201
  ## Embedded assistant
202
202
 
203
- To place the same server tools inside the SaaS web app, declare the shared identity once with `branding: { name: "Acme", accent: "#3157D5", surface: "#FFFFFF", surfaceDark: "#161B26", theme: { dark: { accent: "#9FB4FF" } } }`, then add `assistant: embeddedAssistant({ model: openAICompatible({ baseUrl: variable("ASSISTANT_MODEL_BASE_URL"), model: variable("ASSISTANT_MODEL"), apiKey: secret("ASSISTANT_MODEL_API_KEY") }), allowedOrigins: ["https://app.example.com"], layout: { mode: "floating", position: "bottom-right" } })`. Widgets and the embed inherit the same brand kit. Keep origins exact. Install the independently versioned embed SDK with `pnpm add @noodleseed/assistant`; the customer backend uses its `/server` export to exchange the already-authenticated user for a short-lived browser session, while the browser uses the package root or `/react` export. Client and model secrets never enter the browser. Run `noodle check --target embedded-assistant` before deploy, then manage backend credentials with `noodle assistant clients create|list|rotate|revoke`.
203
+ To place the same server tools inside a SaaS web app, declare `assistant: embeddedAssistant(...)` alongside the one server-level brand kit. Read `embedded-assistant.md` before integrating: it owns the HTTPS-origin rule, managed model configuration, required deploy-before-client sequence, customer-backend exchange, browser mount, and verification checklist.
204
204
 
205
205
  ## Boundaries
206
206
 
@@ -18,6 +18,7 @@ Run `noodle validate` (add `--json` for the machine-readable envelope, `--fix-pr
18
18
  | `invalid_name` | Rename the identifier to match the allowed pattern (lowercase, no spaces/reserved characters) cited at `path`. |
19
19
  | `duplicate_name` | Two tools/components share a name; give each a unique name at the cited `path`. |
20
20
  | `unsupported_manifest_version` | Update the SDK/CLI so the emitted manifest version is supported; do not pin an old manifest shape. |
21
+ | `reserved_for_future_version` | The verb at `path` (e.g. `compute`, `elicit`) is reserved for a future core version; express the step with `use` (a connector operation) or `map` (a pure mapping) instead. |
21
22
  | `invalid_operation_ref` | Fix the connector operation reference to `alias.operation` for an operation that exists on that connector. |
22
23
  | `external_ref` | Remove the external/remote `$ref`; schemas must be self-contained — inline the definition instead of dereferencing a URL. |
23
24
  | `invalid_schema_ref` | Correct the `$use` schema reference syntax at `path`; it does not name a resolvable local schema. |
@@ -5,6 +5,7 @@
5
5
  - Authenticate
6
6
  - Link and target
7
7
  - Deploy and inspect
8
+ - Eject path (portable manifest)
8
9
  - Connect into a host
9
10
  - Access modes
10
11
  - Org and members
@@ -24,6 +25,10 @@
24
25
 
25
26
  `noodle deploy` deploys the server. Then `noodle open` (latest URL), `noodle status`, `noodle inspect` (metadata, no secrets), `noodle smoke` (readiness diagnostics), and `noodle rollback <deploymentId>` to revert.
26
27
 
28
+ ## Eject path (portable manifest)
29
+
30
+ `noodle export manifest [--output <file>]` compiles the entrypoint locally and emits the portable, vendor-neutral manifest JSON — no service, no login, no account. A Noodle app is just `src/server.ts` plus this manifest: the user can read it, diff it, and keep it.
31
+
27
32
  ## Connect into a host
28
33
 
29
34
  Once deployed, register the server as a tool in a host with `noodle connect <host>` (`claude-code`, `codex`, `chatgpt`, `cursor`, `vscode`, `claude`, `inspector`) — it prints the exact config to paste.
@@ -0,0 +1,208 @@
1
+ # Embedded assistant
2
+
3
+ ## Contents
4
+
5
+ - Architecture
6
+ - Author and validate
7
+ - Configure and deploy
8
+ - Access modes and customer auth
9
+ - Create the backend client
10
+ - Integrate the customer backend
11
+ - Verified session context (identity and claims)
12
+ - The session response
13
+ - Mount the browser component
14
+ - Toolchain requirements
15
+ - Verify the boundary
16
+ - Troubleshooting: symptom to diagnosis
17
+
18
+ ## Architecture
19
+
20
+ The browser never receives a model key, assistant client secret, MCP token, or raw application session. The embedding SaaS authenticates its own user, its backend exchanges that verified identity through `@noodleseed/assistant/server`, and the browser receives only a short-lived assistant session.
21
+
22
+ Keep the two configuration sets separate:
23
+
24
+ | Owner | Values | Destination |
25
+ | --- | --- | --- |
26
+ | Noodle deployment | `ASSISTANT_MODEL_BASE_URL`, `ASSISTANT_MODEL`, `ASSISTANT_MODEL_API_KEY` | `noodle variables set` / `noodle secrets set`; never the SaaS environment |
27
+ | SaaS backend | `NOODLE_SERVICE_URL`, `NOODLE_ASSISTANT_CLIENT_ID`, `NOODLE_ASSISTANT_CLIENT_SECRET` | Backend-only environment or secret manager; never browser code or `NEXT_PUBLIC_*`/equivalent variables |
28
+
29
+ ## Author and validate
30
+
31
+ Use the same server tools in the embed; do not create a second tool set. Declare one server-level brand kit and an assistant configuration:
32
+
33
+ ```ts
34
+ branding: { name: "Acme", accent: "#3157D5" },
35
+ assistant: embeddedAssistant({
36
+ model: openAICompatible({
37
+ baseUrl: variable("ASSISTANT_MODEL_BASE_URL"),
38
+ model: variable("ASSISTANT_MODEL"),
39
+ apiKey: secret("ASSISTANT_MODEL_API_KEY"),
40
+ }),
41
+ allowedOrigins: ["http://localhost:3000", "https://app.example.com"],
42
+ layout: { mode: "floating", position: "bottom-right" },
43
+ }),
44
+ ```
45
+
46
+ `allowedOrigins` are exact origins: scheme, host, and optional port, with no path, trailing slash, or wildcard. Production origins must be HTTPS; plain HTTP is accepted only for loopback development origins (`http://localhost:<port>`, `http://127.0.0.1:<port>`). `noodle dev` serves the MCP project, not the embedding SaaS.
47
+
48
+ Run:
49
+
50
+ ```sh
51
+ noodle validate --json
52
+ noodle check --target embedded-assistant --json
53
+ ```
54
+
55
+ Use `noodle commands --json` before proposing command flags; do not invent flags from memory.
56
+
57
+ ## Configure and deploy
58
+
59
+ Local MCP authoring and tests need no account, but an external browser embed needs an active assistant-enabled deployment before a backend client can be created. Set the model values on the target org/app/env, then deploy:
60
+
61
+ ```sh
62
+ noodle variables set ASSISTANT_MODEL_BASE_URL --scope env --org <org> --app <app> --env <env> --value <https-model-base-url>
63
+ noodle variables set ASSISTANT_MODEL --scope env --org <org> --app <app> --env <env> --value <model>
64
+ noodle secrets set ASSISTANT_MODEL_API_KEY --scope env --org <org> --app <app> --env <env> --from-env ASSISTANT_MODEL_API_KEY
65
+ noodle deploy --org <org> --app <app> --env <env>
66
+ ```
67
+
68
+ Do not put these model values in the embedding SaaS environment. A production deployment may omit a local origin; include a loopback origin only when local browser integration is required.
69
+
70
+ ## Access modes and customer auth
71
+
72
+ Session exchange authenticates with the backend client credentials, so the embed works under any `--access` mode. Add `--access customers` only when verified end customers should also call the MCP endpoint directly. That mode requires `server.auth`; `noodle deploy` preflights the rule locally and fails with `server_auth_required` before contacting the service. Fix by adding auth to server options:
73
+
74
+ ```ts
75
+ auth: customerAuth.bridge({ verifyUrl: "https://app.example.com/api/auth/verify" }),
76
+ // or a concrete adapter: customerAuth.firebase({ projectId, apiKey })
77
+ ```
78
+
79
+ ## Create the backend client
80
+
81
+ After the deployment is active:
82
+
83
+ ```sh
84
+ noodle assistant clients create --name web --org <org> --app <app> --env <env>
85
+ ```
86
+
87
+ The CLI writes `{ clientId, clientSecret }` to a mode-`0600` file and prints only its path. Move the values into the SaaS backend secret manager without printing or committing them. Rotation invalidates the previous secret.
88
+
89
+ ## Integrate the customer backend
90
+
91
+ Read the customer repository lockfile or `packageManager` field and install `@noodleseed/assistant` with that existing package manager; never introduce a second lockfile.
92
+
93
+ Create an authenticated same-origin backend route:
94
+
95
+ ```ts
96
+ import { createAssistantSession } from "@noodleseed/assistant/server";
97
+
98
+ export async function POST(request: Request) {
99
+ const user = await requireCurrentUser(request);
100
+ const { context } = await request.json();
101
+ const session = await createAssistantSession({
102
+ serviceUrl: process.env.NOODLE_SERVICE_URL!,
103
+ clientId: process.env.NOODLE_ASSISTANT_CLIENT_ID!,
104
+ clientSecret: process.env.NOODLE_ASSISTANT_CLIENT_SECRET!,
105
+ origin: process.env.PUBLIC_APP_ORIGIN!,
106
+ user: { id: user.id, email: user.email, roles: user.roles },
107
+ context,
108
+ });
109
+ return Response.json(session);
110
+ }
111
+ ```
112
+
113
+ Authenticate before exchange. Source `origin` from trusted server configuration or strictly match the request origin against the same exact allowlist; never accept an arbitrary request header. Treat page context as untrusted model context, never authorization. Forward the helper response unchanged.
114
+
115
+ `serviceUrl` is the Noodle Seed control-plane base URL: the value `noodle assistant clients create` prints, also stored as `serviceUrl` in `deployment.json`. It is NOT the deployment MCP endpoint (`url`, which ends in `/v1/mcp` and rejects session exchange). Never probe or guess endpoints with real credentials.
116
+
117
+ ## Verified session context (identity and claims)
118
+
119
+ The embedding developer defines what authenticated session context the assistant receives. One mechanism, three hops:
120
+
121
+ 1. The authenticated backend passes standard identity and any verified claims at session exchange (flat scalars only):
122
+
123
+ ```ts
124
+ const session = await createAssistantSession({
125
+ serviceUrl, clientId, clientSecret, origin,
126
+ user: { id: user.id, email: user.email, name: user.name },
127
+ claims: { displayName: user.name, accountTier: account.tier, region: account.region },
128
+ });
129
+ ```
130
+
131
+ 2. The server author declares the allowlist in `server.ts` — undeclared claims are dropped at session exchange (never rejected, so backend and server deploys may skew safely):
132
+
133
+ ```ts
134
+ assistant: embeddedAssistant({
135
+ model, allowedOrigins,
136
+ sessionClaims: {
137
+ displayName: { exposeToModel: true },
138
+ accountTier: { exposeToModel: true },
139
+ region: {}, // tools only, never in the prompt
140
+ },
141
+ }),
142
+ ```
143
+
144
+ 3. Consumption. Tools read the verified identity and declared claims through the `user` scope:
145
+
146
+ ```ts
147
+ tool("greet", {
148
+ description: "Greet the signed-in user.",
149
+ input: z.object({}),
150
+ annotations: annotations.readOnly(),
151
+ fulfil: ({ user }) => ({ message: `Hello, ${user.name}!`, tier: user.claims.accountTier }),
152
+ });
153
+ ```
154
+
155
+ Manifest expressions use `${user.name}`, `${user.email}`, `${user.subject}`, `${user.claims.<key>}`. The model receives one platform identity line automatically: standard identity (name/email) whenever present, plus only the claims marked `exposeToModel: true` — so the assistant greets the actual user and can pass identity into tool arguments. `noodle check --target embedded-assistant` lists the declared claim contract.
156
+
157
+ Page `context` from the widget remains untrusted hint data; verified facts belong in `claims`, never in `context`.
158
+
159
+ ## The session response
160
+
161
+ The exchange returns the versioned Embedded Assistant v1 contract. `token`, `expiresAt`, and `endpoints.turns` / `endpoints.toolConfirmations` (absolute URLs) are always present; `configuration` is optional theming data. Forward the body unchanged; the widget posts turns to `endpoints.turns` itself. Do not rebuild, filter, or rewrite the response.
162
+
163
+ ## Mount the browser component
164
+
165
+ Use the React wrapper in React applications:
166
+
167
+ ```tsx
168
+ import { NoodleAssistant } from "@noodleseed/assistant/react";
169
+
170
+ <NoodleAssistant sessionEndpoint="/api/assistant/session" theme="auto" />;
171
+ ```
172
+
173
+ 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.
174
+
175
+ 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`.
176
+
177
+ ## Toolchain requirements
178
+
179
+ - Node.js 20+ for `@noodleseed/assistant/server`.
180
+ - 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.
181
+ - TypeScript `moduleResolution` `bundler` or `node16` recommended; classic `node` also resolves the `/react` and `/server` subpaths.
182
+
183
+ ## Verify the boundary
184
+
185
+ - Signed-out session exchange returns `401`.
186
+ - The browser network/DOM/storage contains no client secret or model key.
187
+ - The local and production origins match `allowedOrigins` character-for-character.
188
+ - Auto-run requires the full safe-read annotation (`annotations.readOnly()`: read-only, non-destructive, closed-world); unannotated or partially annotated tools always confirm. Writes require confirmation by design.
189
+ - An expired turn re-exchanges once; confirmations never replay.
190
+ - Wrong-origin and malformed-origin requests fail closed.
191
+
192
+ ## Troubleshooting: symptom to diagnosis
193
+
194
+ | Symptom | Diagnosis | Fix |
195
+ | --- | --- | --- |
196
+ | Widget renders but no reply arrives and model usage stays zero | Turns are not reaching the service: outdated `@noodleseed/assistant` package, or the session response was rebuilt/filtered by the backend route | Update the package to the latest version; forward the session response unchanged |
197
+ | `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 |
198
+ | 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 |
199
+ | Deploy fails with `server_auth_required` | `--access customers` without `server.auth` | Add `customerAuth.bridge(...)` or an adapter to server options |
200
+ | 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` |
201
+ | Session exchange returns 404 | `serviceUrl` points at the deployment MCP endpoint | Use the control-plane service URL printed by `noodle assistant clients create` |
202
+ | 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 |
203
+ | Hydration or `HTMLElement is not defined` errors | The component mounted during server rendering | Mount client-only (`"use client"` or `next/dynamic` with `ssr: false`) |
204
+ | A read-only tool still asks for confirmation | Its annotations fail the safe-read rule: auto-run requires `readOnlyHint: true`, `destructiveHint: false`, AND `openWorldHint: false` (use `annotations.readOnly()`; `readOnly({ openWorld: true })` confirm-gates) | Fix the annotations; `noodle check --target embedded-assistant` lists every confirm-gated tool |
205
+ | `${user.claims.<key>}` is empty | Claim not declared in `sessionClaims` (or key typo) — undeclared claims are dropped at exchange | Declare the key in `embeddedAssistant({ sessionClaims })` and redeploy |
206
+ | `${user.name}` is empty | Backend did not pass `user.name` to `createAssistantSession` | Pass the verified name from the authenticated backend session |
207
+ | The model does not know a claim you passed | Claim is tools-only | Mark it `exposeToModel: true` in `sessionClaims` |
208
+ | Behavior does not change after `noodle deploy` | Outdated platform: before the 2026-07 fix, clients were pinned to their creation-time deployment | Update the platform; sessions now follow the tenant's active deployment |