@noodleseed/agent-kit 0.16.0 → 0.18.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 +49 -49
- package/package.json +1 -1
- package/skills/claude-code/SKILL.md +1 -1
- package/skills/claude-code/examples/acme-bistro/README.md +1 -1
- package/skills/claude-code/examples/acme-bistro/design/UX-Document.md +4 -4
- package/skills/claude-code/examples/acme-bistro/design/api-contract.md +3 -3
- package/skills/claude-code/examples/acme-bistro/design/wireframe.html +3 -3
- package/skills/claude-code/examples/acme-bistro/src/server.ts +8 -6
- package/skills/claude-code/examples/acme-discovery/README.md +1 -1
- package/skills/claude-code/examples/acme-discovery/design/UX-Document.md +3 -3
- package/skills/claude-code/examples/acme-discovery/design/wireframe.html +2 -2
- package/skills/claude-code/examples/acme-discovery/src/server.ts +6 -5
- package/skills/claude-code/examples/acme-tasks/README.md +30 -1
- package/skills/claude-code/examples/acme-tasks/design/UX-Document.md +2 -2
- package/skills/claude-code/examples/acme-tasks/design/wireframe.html +6 -6
- package/skills/claude-code/examples/acme-tasks/src/server.ts +6 -5
- package/skills/claude-code/examples/food-ordering/src/server.ts +46 -56
- package/skills/claude-code/examples/weather/src/server.ts +29 -41
- package/skills/claude-code/references/authoring-workflow.md +15 -10
- package/skills/claude-code/references/cli-commands.md +1 -0
- package/skills/claude-code/references/compile-errors.md +2 -2
- package/skills/claude-code/references/connect-an-api.md +2 -2
- package/skills/claude-code/references/experience-design.md +1 -1
- package/skills/claude-code/references/sdk-surface.md +16 -16
- package/skills/claude-code/references/troubleshooting.md +2 -2
- package/skills/claude-code/references/widgets-and-apps.md +9 -8
- package/skills/codex/SKILL.md +1 -1
- package/skills/codex/examples/acme-bistro/README.md +1 -1
- package/skills/codex/examples/acme-bistro/design/UX-Document.md +4 -4
- package/skills/codex/examples/acme-bistro/design/api-contract.md +3 -3
- package/skills/codex/examples/acme-bistro/design/wireframe.html +3 -3
- package/skills/codex/examples/acme-bistro/src/server.ts +8 -6
- package/skills/codex/examples/acme-discovery/README.md +1 -1
- package/skills/codex/examples/acme-discovery/design/UX-Document.md +3 -3
- package/skills/codex/examples/acme-discovery/design/wireframe.html +2 -2
- package/skills/codex/examples/acme-discovery/src/server.ts +6 -5
- package/skills/codex/examples/acme-tasks/README.md +30 -1
- package/skills/codex/examples/acme-tasks/design/UX-Document.md +2 -2
- package/skills/codex/examples/acme-tasks/design/wireframe.html +6 -6
- package/skills/codex/examples/acme-tasks/src/server.ts +6 -5
- package/skills/codex/examples/food-ordering/src/server.ts +46 -56
- package/skills/codex/examples/weather/src/server.ts +29 -41
- package/skills/codex/references/authoring-workflow.md +15 -10
- package/skills/codex/references/cli-commands.md +1 -0
- package/skills/codex/references/compile-errors.md +2 -2
- package/skills/codex/references/connect-an-api.md +2 -2
- package/skills/codex/references/experience-design.md +1 -1
- package/skills/codex/references/sdk-surface.md +16 -16
- package/skills/codex/references/troubleshooting.md +2 -2
- package/skills/codex/references/widgets-and-apps.md +9 -8
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
## Tools and views
|
|
14
14
|
|
|
15
|
-
Use `
|
|
15
|
+
Use `tool(name, { description, input, output, fulfil, view })` for a model-visible tool that renders a widget, and the same `tool(name, { ..., visibility: ["app"] })` for an app-only helper hidden from the model. A `view` is `{ component: "name", entry: "./views/name.tsx" }` — a React component the compiler bundles at validate/deploy time.
|
|
16
16
|
|
|
17
17
|
## React hook surface
|
|
18
18
|
|
|
@@ -21,7 +21,7 @@ Author views as React components. `generateHelpers<ServerDefinition>()` (from `@
|
|
|
21
21
|
| Hook | Use for |
|
|
22
22
|
| :-- | :-- |
|
|
23
23
|
| `useToolInfo` | Read the invoking tool result; `structuredContent` is the widget’s typed data payload. |
|
|
24
|
-
| `useCallTool` | Call a tool from the widget — returns `{ status, callTool, callToolAsync, data, structuredContent, error, reset }`; target a model-visible tool or a hidden `
|
|
24
|
+
| `useCallTool` | Call a tool from the widget — returns `{ status, callTool, callToolAsync, data, structuredContent, error, reset }`; target a model-visible tool or a hidden `tool` helper. |
|
|
25
25
|
| `useViewState` | Persist per-widget UI state across re-renders and restores: `const [value, setValue] = useViewState("key", initial)`. |
|
|
26
26
|
| `useLayout` | Read host layout: `{ theme, displayMode, locale?, host?, supports? }` (`displayMode` is `"inline"`/`"pip"`/`"fullscreen"`) — adapt styling to the host theme and mode. |
|
|
27
27
|
| `useRequestDisplayMode` | Request a host-mediated layout change such as fullscreen; treat it as best-effort and keep inline rendering useful. |
|
|
@@ -94,10 +94,10 @@ export default function OrderStatus() {
|
|
|
94
94
|
|
|
95
95
|
### 2. The tool declaration
|
|
96
96
|
|
|
97
|
-
`
|
|
97
|
+
`tool` declares both the model-visible tool that renders the view and app-only helpers the view calls. Put `visibility: ["app"]` on each helper. Wire `view: { component, entry }`, `csp`, a widget `domain`, and a real `output` schema so non-Apps hosts still receive structured data. Inside `fulfil`, `input` is a symbolic ref recorded into a flow — reference it in output/template strings, but never use it as an object key or `if` condition.
|
|
98
98
|
|
|
99
99
|
```ts
|
|
100
|
-
import { annotations, server,
|
|
100
|
+
import { annotations, server, tool, z } from '@noodleseed/one';
|
|
101
101
|
|
|
102
102
|
const item = z.enum(['falafel_wrap', 'lentil_soup', 'mint_lemonade']).default('falafel_wrap');
|
|
103
103
|
const checkoutUrl = (customer: string) =>
|
|
@@ -112,7 +112,7 @@ export default server(
|
|
|
112
112
|
handoff: { allowedDomains: ['https://orders.example.com'] },
|
|
113
113
|
},
|
|
114
114
|
[
|
|
115
|
-
|
|
115
|
+
tool('show_order', {
|
|
116
116
|
description: 'Show the pickup order and render the ordering widget.',
|
|
117
117
|
// Declare tool annotations — a ChatGPT-submission requirement. A read → `readOnly()`.
|
|
118
118
|
annotations: annotations.readOnly(),
|
|
@@ -129,8 +129,8 @@ export default server(
|
|
|
129
129
|
total: 12,
|
|
130
130
|
checkoutUrl: checkoutUrl(input.customer),
|
|
131
131
|
}),
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
viewTitle: 'Pickup order',
|
|
133
|
+
viewDescription: 'Pick an item and place a pickup order.',
|
|
134
134
|
// A ChatGPT App is this widget + a domain: one https origin per app.
|
|
135
135
|
domain: 'https://pickup.example.com',
|
|
136
136
|
view: { component: 'order-status', entry: './views/order-status.tsx' },
|
|
@@ -142,7 +142,8 @@ export default server(
|
|
|
142
142
|
},
|
|
143
143
|
}),
|
|
144
144
|
// Widget-only helper the view calls with useCallTool('place_order'); hidden from the model.
|
|
145
|
-
|
|
145
|
+
tool('place_order', {
|
|
146
|
+
visibility: ['app'],
|
|
146
147
|
description: 'Place a pickup order from the widget.',
|
|
147
148
|
// A write that reaches the outside world → `action()` (not read-only, not destructive).
|
|
148
149
|
annotations: annotations.action(),
|
package/skills/codex/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
A Noodle MCP App for **Acme Bistro**, a fictional restaurant. It is the flagship for the **end-to-end
|
|
4
4
|
transaction** pattern: the customer browses the menu and builds the order *in chat*, and the order
|
|
5
5
|
completes in chat — only **payment** hands off, via a signed checkout deep link (the card never touches
|
|
6
|
-
the app). It pairs a `
|
|
6
|
+
the app). It pairs a view-backed `tool` menu/cart with app-only `tool` cart helpers and a model-visible
|
|
7
7
|
`create_checkout` tool backed by `handoff.allowedDomains`.
|
|
8
8
|
|
|
9
9
|
Capability slot: **end-to-end in-chat transaction + payment-only handoff**, plus a worked **design-first**
|
|
@@ -204,7 +204,7 @@ APP: [tool: create_checkout {customer:"Guest",
|
|
|
204
204
|
|
|
205
205
|
## 5. UI Widget Specifications
|
|
206
206
|
|
|
207
|
-
> The widget is authored as a Noodle Seed React `view` (`
|
|
207
|
+
> The widget is authored as a Noodle Seed React `view` (`tool` → `MenuCart`), styled with **vanilla CSS cascade layers** so it inherits the host's light/dark theme and adapts to ChatGPT's surface. Compliance is verified with `noodle check --target chatgpt`.
|
|
208
208
|
|
|
209
209
|
### 5.1 Design System Compliance
|
|
210
210
|
|
|
@@ -250,20 +250,20 @@ Modes deliberately **not** used: no Carousel (a single flat menu doesn't need on
|
|
|
250
250
|
|
|
251
251
|
## 6. Tool Definitions (App Backend)
|
|
252
252
|
|
|
253
|
-
### ★ Tool 1: `show_menu` — `
|
|
253
|
+
### ★ Tool 1: `show_menu` — `tool`
|
|
254
254
|
**Input:** `{ customer?: string = "Guest" }`
|
|
255
255
|
**Output:** `{ status, customer, items[] }` where each item is `{ id, name, price, kind }`.
|
|
256
256
|
**Renders:** the `MenuCart` widget.
|
|
257
257
|
**Annotations:** read-only.
|
|
258
258
|
**Triggers:** any menu / ordering intent ("show me Acme's menu", "order from Acme").
|
|
259
259
|
|
|
260
|
-
### Tool 2: `add_to_cart` — `
|
|
260
|
+
### Tool 2: `add_to_cart` — `tool`
|
|
261
261
|
**Input:** `{ customer?, item: <menu id> = "stone_pizza", quantity?: int ≥1 = 1, notes?: string }`
|
|
262
262
|
**Output:** `{ status, item, quantity, notes }`.
|
|
263
263
|
**Annotations:** local write (non-destructive).
|
|
264
264
|
**Triggers:** natural-language additions ("add two margheritas", "and a lemon tart"). Widget-facing helper — reflects NL selections into the visible cart.
|
|
265
265
|
|
|
266
|
-
### Tool 3: `remove_from_cart` — `
|
|
266
|
+
### Tool 3: `remove_from_cart` — `tool`
|
|
267
267
|
**Input:** `{ customer?, item: <menu id> = "stone_pizza" }`
|
|
268
268
|
**Output:** `{ status, item }`.
|
|
269
269
|
**Annotations:** local write (non-destructive).
|
|
@@ -8,9 +8,9 @@ The app maps to four tools:
|
|
|
8
8
|
|
|
9
9
|
| Tool | Kind | Job |
|
|
10
10
|
|------|------|-----|
|
|
11
|
-
| `show_menu` | `
|
|
12
|
-
| `add_to_cart` | `
|
|
13
|
-
| `remove_from_cart` | `
|
|
11
|
+
| `show_menu` | `tool` (read-only) | Return the menu + render the `MenuCart` widget |
|
|
12
|
+
| `add_to_cart` | `tool` (local write) | Reflect a natural-language addition into the visible cart |
|
|
13
|
+
| `remove_from_cart` | `tool` (local write) | Remove one unit of an item |
|
|
14
14
|
| `create_checkout` | model-visible `tool` (open-link) | Mint the signed, expiring payment link |
|
|
15
15
|
|
|
16
16
|
Menu item IDs are the stable enum: `stone_pizza`, `roast_bowl`, `house_salad`, `lemon_tart`, `sparkling`.
|
|
@@ -496,13 +496,13 @@
|
|
|
496
496
|
<div class="api-panel-header">Browse & Build (Steps 1–3)</div>
|
|
497
497
|
<div class="api-step">
|
|
498
498
|
<div class="api-step-label">Step 1 — Show the menu</div>
|
|
499
|
-
<div class="api-endpoint"><span class="kind read">READ</span><span class="path">show_menu</span><span class="desc">—
|
|
499
|
+
<div class="api-endpoint"><span class="kind read">READ</span><span class="path">show_menu</span><span class="desc">— tool + view. Returns the 5-item menu and renders MenuCart.</span></div>
|
|
500
500
|
<div class="api-note"><strong>Design intent:</strong> the menu is small and static, so it ships in one read — no pagination, no follow-up call. The <code>items[]</code> array is the model's only source of dish names and prices.</div>
|
|
501
501
|
</div>
|
|
502
502
|
<div class="api-step">
|
|
503
503
|
<div class="api-step-label">Steps 2–3 — Build & edit the cart</div>
|
|
504
|
-
<div class="api-endpoint"><span class="kind write">WRITE</span><span class="path">add_to_cart</span><span class="desc">—
|
|
505
|
-
<div class="api-endpoint"><span class="kind write">WRITE</span><span class="path">remove_from_cart</span><span class="desc">—
|
|
504
|
+
<div class="api-endpoint"><span class="kind write">WRITE</span><span class="path">add_to_cart</span><span class="desc">— tool + app visibility. Reflects a natural-language addition (item + quantity) into the visible cart.</span></div>
|
|
505
|
+
<div class="api-endpoint"><span class="kind write">WRITE</span><span class="path">remove_from_cart</span><span class="desc">— tool + app visibility. Removes one unit; the widget's − button calls the same tool.</span></div>
|
|
506
506
|
<div class="api-note"><strong>Natural-language parsing:</strong> "two margheritas and a lemon tart" becomes <code>add_to_cart{item:"stone_pizza",quantity:2}</code> + <code>add_to_cart{item:"lemon_tart",quantity:1}</code> — the model fills the fields; no UI round-trip. The running total is summed live in the widget (React), not in a tool.</div>
|
|
507
507
|
</div>
|
|
508
508
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { annotations, server, tool,
|
|
1
|
+
import { annotations, server, tool, z } from '@noodleseed/one';
|
|
2
2
|
|
|
3
3
|
// Acme Bistro is a fictional restaurant. This app is deliberately END-TO-END: the customer browses the
|
|
4
4
|
// menu and builds the order in chat, and the order completes in chat — only *payment* hands off, via a
|
|
@@ -52,7 +52,7 @@ export default server(
|
|
|
52
52
|
},
|
|
53
53
|
},
|
|
54
54
|
[
|
|
55
|
-
|
|
55
|
+
tool('show_menu', {
|
|
56
56
|
description: 'Show the Acme Bistro menu and render the ordering widget.',
|
|
57
57
|
annotations: readOnly,
|
|
58
58
|
input: z.object({ customer: z.string().default('Guest') }),
|
|
@@ -66,8 +66,8 @@ export default server(
|
|
|
66
66
|
customer: input.customer,
|
|
67
67
|
items: menu,
|
|
68
68
|
}),
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
viewTitle: 'Order at Acme Bistro',
|
|
70
|
+
viewDescription: 'Browse the menu, build an order in chat, and hand off to pay.',
|
|
71
71
|
invoking: 'Loading the menu…',
|
|
72
72
|
invoked: 'Menu ready',
|
|
73
73
|
domain: 'https://order.acme.example',
|
|
@@ -82,7 +82,8 @@ export default server(
|
|
|
82
82
|
},
|
|
83
83
|
}),
|
|
84
84
|
// Widget-only cart edits — the model fills the item from natural language ("add two margheritas").
|
|
85
|
-
|
|
85
|
+
tool('add_to_cart', {
|
|
86
|
+
visibility: ['app'],
|
|
86
87
|
description: 'Add a menu item to the Acme Bistro order from the widget.',
|
|
87
88
|
annotations: localWrite,
|
|
88
89
|
input: z.object({
|
|
@@ -104,7 +105,8 @@ export default server(
|
|
|
104
105
|
notes: input.notes,
|
|
105
106
|
}),
|
|
106
107
|
}),
|
|
107
|
-
|
|
108
|
+
tool('remove_from_cart', {
|
|
109
|
+
visibility: ['app'],
|
|
108
110
|
description: 'Remove a menu item from the Acme Bistro order.',
|
|
109
111
|
annotations: localWrite,
|
|
110
112
|
input: z.object({
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
A Noodle MCP App for **Acme Getaways**, a fictional travel brand. It is the flagship for the
|
|
4
4
|
**top-of-funnel discovery → handoff** pattern: discovery and configuration happen inside ChatGPT; the
|
|
5
5
|
booking/transaction happens off-app on Acme's own site, reached through a signed, attributable handoff
|
|
6
|
-
deep link. It pairs a `
|
|
6
|
+
deep link. It pairs a `tool` discovery carousel with a model-visible `create_handoff` tool and
|
|
7
7
|
server-level `handoff.allowedDomains`.
|
|
8
8
|
|
|
9
9
|
Capability slots: top-of-funnel funnel discipline, discovery carousel widget, `create_handoff` deep-link
|
|
@@ -202,7 +202,7 @@ Inside the widget, **Continue on Acme** calls `create_handoff(destination="coral
|
|
|
202
202
|
|
|
203
203
|
## 5. UI Widget Specifications (OpenAI Apps SDK Compliant)
|
|
204
204
|
|
|
205
|
-
> The app has **one** widget, `DiscoveryCarousel`, authored as a React `view` on the `discover_getaways` `
|
|
205
|
+
> The app has **one** widget, `DiscoveryCarousel`, authored as a React `view` on the `discover_getaways` `tool`. It follows the OpenAI Apps SDK UI Guidelines (system fonts, monochrome outlined icons, WCAG AA, ≤2 actions per card, no nested scroll). Styling uses Noodle Seed's server-level `branding` tokens and CSS cascade layers — not app-specific global CSS. Acme's accent teal is restricted to the primary CTA, the logo mark, and the "Shortlisted" state only.
|
|
206
206
|
|
|
207
207
|
### 5.1 Design System Compliance
|
|
208
208
|
|
|
@@ -378,8 +378,8 @@ The demo's arc: *a vague mood → four real, priced, reasoned getaways → one s
|
|
|
378
378
|
```
|
|
379
379
|
ChatGPT ──tool calls──► Noodle Seed runtime (server 'acme_discovery')
|
|
380
380
|
│ app-owned curated catalog (static data)
|
|
381
|
-
├──► discover_getaways (
|
|
382
|
-
├──► shortlist_getaway (
|
|
381
|
+
├──► discover_getaways (tool → DiscoveryCarousel)
|
|
382
|
+
├──► shortlist_getaway (tool, local write)
|
|
383
383
|
├──► create_handoff (open-link → signed Acme deep link)
|
|
384
384
|
└──► React view bundle (DiscoveryCarousel, branding tokens, CSP)
|
|
385
385
|
│
|
|
@@ -585,14 +585,14 @@
|
|
|
585
585
|
<div class="api-step">
|
|
586
586
|
<div class="an">1</div>
|
|
587
587
|
<div class="ac">
|
|
588
|
-
<div class="tool">discover_getaways<span class="kind">
|
|
588
|
+
<div class="tool">discover_getaways<span class="kind">tool + view</span></div>
|
|
589
589
|
<div class="api-note">In <code>{ vibe, month, travelers }</code> → out <code>{ status, vibe, month, travelers, options[] }</code> and renders <code>DiscoveryCarousel</code>. Returns the <strong>full curated catalog verbatim</strong> (4 destinations); the model narrates fit. Host status: "Finding getaways…" → "Getaways ready". <code>read-only</code>.</div>
|
|
590
590
|
</div>
|
|
591
591
|
</div>
|
|
592
592
|
<div class="api-step">
|
|
593
593
|
<div class="an">2</div>
|
|
594
594
|
<div class="ac">
|
|
595
|
-
<div class="tool">shortlist_getaway<span class="kind">
|
|
595
|
+
<div class="tool">shortlist_getaway<span class="kind">tool + app visibility</span></div>
|
|
596
596
|
<div class="api-note">In <code>{ destination, note }</code> → out <code>{ status, destination, note }</code>. Called from inside the carousel when a card's <strong>Shortlist</strong> is tapped; updates widget view-state. <code>local-action</code>, non-destructive — not a booking.</div>
|
|
597
597
|
</div>
|
|
598
598
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { annotations, server, tool,
|
|
1
|
+
import { annotations, server, tool, z } from '@noodleseed/one';
|
|
2
2
|
|
|
3
3
|
// Acme Getaways is a fictional travel brand. This app is deliberately top-of-funnel: discovery and
|
|
4
4
|
// configuration happen inside ChatGPT; the booking/transaction happens off-app on Acme's own site,
|
|
@@ -115,7 +115,7 @@ export default server(
|
|
|
115
115
|
},
|
|
116
116
|
},
|
|
117
117
|
[
|
|
118
|
-
|
|
118
|
+
tool('discover_getaways', {
|
|
119
119
|
description:
|
|
120
120
|
'Suggest Acme Getaways destinations for a vibe and month and render a discovery carousel.',
|
|
121
121
|
annotations: readOnly,
|
|
@@ -136,7 +136,7 @@ export default server(
|
|
|
136
136
|
travelers: input.travelers,
|
|
137
137
|
options: catalog,
|
|
138
138
|
}),
|
|
139
|
-
|
|
139
|
+
viewTitle: 'Discover getaways',
|
|
140
140
|
// ChatGPT host status copy (openai/toolInvocation/*) — required for widget-opening tools.
|
|
141
141
|
invoking: 'Finding getaways…',
|
|
142
142
|
invoked: 'Getaways ready',
|
|
@@ -145,7 +145,7 @@ export default server(
|
|
|
145
145
|
component: 'discovery-carousel',
|
|
146
146
|
entry: './views/discovery-carousel.tsx',
|
|
147
147
|
},
|
|
148
|
-
|
|
148
|
+
viewDescription:
|
|
149
149
|
'A top-of-funnel discovery carousel: pick a destination, then hand off to Acme to book.',
|
|
150
150
|
csp: {
|
|
151
151
|
connectDomains: ['https://acme.example'],
|
|
@@ -180,7 +180,8 @@ export default server(
|
|
|
180
180
|
handoffUrl: `https://book.acme.example/plan?dest=${input.destination}&month=${input.month}&pax=${input.travelers}&src=chatgpt`,
|
|
181
181
|
}),
|
|
182
182
|
}),
|
|
183
|
-
|
|
183
|
+
tool('shortlist_getaway', {
|
|
184
|
+
visibility: ['app'],
|
|
184
185
|
description: 'Record the traveler’s shortlisted destination from the discovery widget.',
|
|
185
186
|
annotations: localWrite,
|
|
186
187
|
input: z.object({
|
|
@@ -7,7 +7,7 @@ in one `TaskList` widget. It shows the "identify and prioritize the flows first,
|
|
|
7
7
|
the `noodle-seed` skill's `references/experience-design.md` teaches.
|
|
8
8
|
|
|
9
9
|
Capability slots: prioritized multi-flow app design, a two-way (read + write) in-chat pattern, a task-list
|
|
10
|
-
widget with `
|
|
10
|
+
widget with `tool` helpers, and a worked **design-first** artifact (the flow spec + wireframe
|
|
11
11
|
below). A real deployment would connect the user's account with the end-user auth pattern — see
|
|
12
12
|
[`../customer-auth/README.md`](../customer-auth/README.md); this example seeds a list so the focus stays
|
|
13
13
|
on the flows.
|
|
@@ -76,5 +76,34 @@ noodle deploy --access owner-only
|
|
|
76
76
|
noodle open
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
## Optional in-product assistant
|
|
80
|
+
|
|
81
|
+
The same task tools can also sit inside Acme's authenticated web app. Add an `assistant` option to this
|
|
82
|
+
server without creating a second tool set:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
assistant: embeddedAssistant({
|
|
86
|
+
model: openAICompatible({
|
|
87
|
+
baseUrl: variable('ASSISTANT_MODEL_BASE_URL'),
|
|
88
|
+
model: variable('ASSISTANT_MODEL'),
|
|
89
|
+
apiKey: secret('ASSISTANT_MODEL_API_KEY'),
|
|
90
|
+
}),
|
|
91
|
+
allowedOrigins: ['https://tasks.example.com'],
|
|
92
|
+
appearance: {
|
|
93
|
+
brand: { name: 'Acme Tasks Assistant' },
|
|
94
|
+
theme: {
|
|
95
|
+
defaultMode: 'auto',
|
|
96
|
+
light: { accent: '#3157D5' },
|
|
97
|
+
dark: { accent: '#9FB4FF' },
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The customer backend exchanges its authenticated user through `@noodleseed/assistant/server`; the browser
|
|
104
|
+
uses the Web Component or React wrapper and never receives the embed client or model secret. Validate with
|
|
105
|
+
`noodle check --target embedded-assistant`, then create the backend credential with
|
|
106
|
+
`noodle assistant clients create` after deployment.
|
|
107
|
+
|
|
79
108
|
This example has no connector secrets and does not include tokens, caller-key mechanisms, or
|
|
80
109
|
`.env.noodle` values. All tasks are fictional seed data.
|
|
@@ -155,7 +155,7 @@ All tools are atomic, model-fillable from natural language, and each returns a s
|
|
|
155
155
|
**★ `list_today`** *(read-only · renders `TaskList`)*
|
|
156
156
|
- **Input:** `{ focus: string = "today" }`
|
|
157
157
|
- **Output:** `{ status, focus, tasks: [{ id, title, priority, done }] }`
|
|
158
|
-
- **Notes:** `
|
|
158
|
+
- **Notes:** `tool` — the one tool that opens the widget. Host status copy: invoking "Loading your tasks…", invoked "Tasks ready".
|
|
159
159
|
|
|
160
160
|
**★ `add_task`** *(local write, non-destructive · model-visible)*
|
|
161
161
|
- **Input:** `{ title: string, priority: "high"|"medium"|"low" = "medium" }`
|
|
@@ -170,7 +170,7 @@ All tools are atomic, model-fillable from natural language, and each returns a s
|
|
|
170
170
|
**`set_priority`** *(local write, non-destructive · widget-only)*
|
|
171
171
|
- **Input:** `{ task: string (id), priority: "high"|"medium"|"low" }`
|
|
172
172
|
- **Output:** `{ status, task, priority }`
|
|
173
|
-
- **Notes:** `
|
|
173
|
+
- **Notes:** `tool` — hidden from the model; the `<select>` in `TaskList` is its only caller, keeping the model's tool surface to the three it should reason about.
|
|
174
174
|
|
|
175
175
|
---
|
|
176
176
|
|
|
@@ -217,7 +217,7 @@
|
|
|
217
217
|
<div class="rationale">
|
|
218
218
|
<h4>How to read these wireframes</h4>
|
|
219
219
|
<p>Solid-border phones are the <strong>in-ChatGPT app</strong>. Acme Tasks is a <strong>two-way (read + write) app</strong>, not a discovery funnel: every widget reflects the user's real list, and each action — capture, re-prioritize, complete — is <strong>recorded through a tool call</strong> and confirmed in-chat. The single dashed phone is the <strong>one-time account link</strong> (<span class="mono">customerAuth</span>); there is no transactional off-app step.</p>
|
|
220
|
-
<p>Widgets are built with the Noodle Seed authoring SDK — <span class="mono">
|
|
220
|
+
<p>Widgets are built with the Noodle Seed authoring SDK — <span class="mono">tool + view</span> renders <span class="mono">TaskList</span>; <span class="mono">tool + app visibility</span> powers the in-widget re-prioritize. Styling uses host/Noodle Seed semantic tokens via CSS cascade layers; the brand accent (<span class="mono">#7C3AED</span>) is reserved for the logo, checks, primary CTA and <em>high</em> emphasis. Priority uses fixed semantic colors: high red · medium amber · low slate.</p>
|
|
221
221
|
<p><span class="r-tag ux">UX</span> flow rationale <span class="r-tag ui">UI</span> interface rationale <span class="r-tag acme">ACME</span> product-model fit <span class="r-tag trust">TRUST</span> write-safety / grounding guardrail</p>
|
|
222
222
|
</div>
|
|
223
223
|
</div>
|
|
@@ -401,11 +401,11 @@
|
|
|
401
401
|
<div class="section" id="prioritize">
|
|
402
402
|
<span class="section-label">Flow 2 · the hub</span>
|
|
403
403
|
<h2 class="section-title">Prioritize — read the day, then re-order it in place</h2>
|
|
404
|
-
<p class="section-subtitle"><span class="mono">list_today</span> renders the grounded list, highest priority first; the per-row priority control (a <span class="mono">
|
|
404
|
+
<p class="section-subtitle"><span class="mono">list_today</span> renders the grounded list, highest priority first; the per-row priority control (a <span class="mono">tool + app visibility</span> helper hidden from the model) lets the user re-rank without describing the task twice. This is the widget the other two flows write into.</p>
|
|
405
405
|
<div class="rationale">
|
|
406
406
|
<h4>Why it's built this way</h4>
|
|
407
407
|
<p><span class="r-tag ux">UX</span> "What's on my plate?" wants an answer <em>and</em> a next move. The list is grouped high → low so the triager sees the real order at a glance and adjusts with one tap.</p>
|
|
408
|
-
<p><span class="r-tag acme">ACME</span> Re-prioritize is <span class="mono">set_priority</span> as <span class="mono">
|
|
408
|
+
<p><span class="r-tag acme">ACME</span> Re-prioritize is <span class="mono">set_priority</span> as <span class="mono">tool + app visibility</span> — <strong>widget-only</strong>, hidden from the model's tool surface. The model reasons about three tools (list, add, complete); the fourth is pure UI plumbing, keeping the model's choices clean.</p>
|
|
409
409
|
<p><span class="r-tag trust">TRUST</span> The list only ever shows tasks that exist — the app never invents a task or a priority. Every re-rank echoes a confirmation ("Set review_pr to high priority").</p>
|
|
410
410
|
</div>
|
|
411
411
|
<div class="phones-row">
|
|
@@ -543,7 +543,7 @@
|
|
|
543
543
|
<div class="section" id="api">
|
|
544
544
|
<span class="section-label">Technical appendix</span>
|
|
545
545
|
<h2 class="section-title">MCP tools & call sequence</h2>
|
|
546
|
-
<p class="section-subtitle">The four tools behind the flows. <span class="mono">list_today</span> is the only widget-opening tool (<span class="mono">
|
|
546
|
+
<p class="section-subtitle">The four tools behind the flows. <span class="mono">list_today</span> is the only widget-opening tool (<span class="mono">tool + view</span>); <span class="mono">set_priority</span> is widget-only (<span class="mono">tool + app visibility</span>, hidden from the model); <span class="mono">add_task</span> and <span class="mono">complete_task</span> are model-visible.</p>
|
|
547
547
|
|
|
548
548
|
<div class="api-panel">
|
|
549
549
|
<h4>Read & render (Flow 2 · the hub)</h4>
|
|
@@ -559,7 +559,7 @@
|
|
|
559
559
|
|
|
560
560
|
<div class="api-panel">
|
|
561
561
|
<h4>Prioritize (Flow 2 helper) & Complete (Flow 3)</h4>
|
|
562
|
-
<div class="api-step"><span class="verb">WRITE</span><span><span class="tname">set_priority</span> — returns { status, task, priority } ·
|
|
562
|
+
<div class="api-step"><span class="verb">WRITE</span><span><span class="tname">set_priority</span> — returns { status, task, priority } · tool + app visibility, hidden from the model</span></div>
|
|
563
563
|
<div class="api-note">Input <span class="mono">{ task:id, priority }</span>. Called only by the priority ▾ in TaskList — keeps the model's tool surface to the three it should reason about.</div>
|
|
564
564
|
<div class="api-step"><span class="verb">WRITE</span><span><span class="tname">complete_task</span> — returns { status, task } · model-visible</span></div>
|
|
565
565
|
<div class="api-note">Input <span class="mono">{ task:id, title:"" }</span>. Model-visible so "I finished X" completes directly; local non-destructive write, confirmed with the remaining count.</div>
|
|
@@ -596,7 +596,7 @@
|
|
|
596
596
|
|
|
597
597
|
<div class="footer">
|
|
598
598
|
Acme Tasks × ChatGPT — Two-Way App Wireframes · Noodle Seed · v1 · July 2026<br>
|
|
599
|
-
Illustrative wireframes for a fictional app. Built with the Noodle Seed authoring SDK (
|
|
599
|
+
Illustrative wireframes for a fictional app. Built with the Noodle Seed authoring SDK (tool + view / tool + app visibility · customerAuth). This flagship seeds the task list; a production deployment connects the user's account. Task data shown is sample content.
|
|
600
600
|
</div>
|
|
601
601
|
|
|
602
602
|
</body>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { annotations, server, tool,
|
|
1
|
+
import { annotations, server, tool, z } from '@noodleseed/one';
|
|
2
2
|
|
|
3
3
|
// Acme Tasks is a fictional productivity app. It is a two-way (read + write) experience rather than a
|
|
4
4
|
// top-of-funnel handoff: the top-3 prioritized user flows all complete in chat — Capture, Prioritize,
|
|
@@ -52,7 +52,7 @@ export default server(
|
|
|
52
52
|
},
|
|
53
53
|
[
|
|
54
54
|
// Flow 2 — Prioritize / Today: render the list so the human triages and the model can speak to it.
|
|
55
|
-
|
|
55
|
+
tool('list_today', {
|
|
56
56
|
description: 'Show today’s Acme Tasks and render the task-list widget.',
|
|
57
57
|
annotations: readOnly,
|
|
58
58
|
input: z.object({ focus: z.string().default('today') }),
|
|
@@ -66,8 +66,8 @@ export default server(
|
|
|
66
66
|
focus: input.focus,
|
|
67
67
|
tasks: today,
|
|
68
68
|
}),
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
viewTitle: 'Today’s tasks',
|
|
70
|
+
viewDescription: 'A prioritized task list: capture, re-prioritize, and complete in place.',
|
|
71
71
|
// ChatGPT host status copy (openai/toolInvocation/*) — required for widget-opening tools.
|
|
72
72
|
invoking: 'Loading your tasks…',
|
|
73
73
|
invoked: 'Tasks ready',
|
|
@@ -119,7 +119,8 @@ export default server(
|
|
|
119
119
|
}),
|
|
120
120
|
}),
|
|
121
121
|
// Flow 2 helper (widget-only): re-prioritize a task from the list widget.
|
|
122
|
-
|
|
122
|
+
tool('set_priority', {
|
|
123
|
+
visibility: ['app'],
|
|
123
124
|
description: 'Re-prioritize a task from the list widget.',
|
|
124
125
|
annotations: localWrite,
|
|
125
126
|
input: z.object({
|
|
@@ -1,15 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
annotations,
|
|
3
|
-
asset,
|
|
4
|
-
connector,
|
|
5
|
-
resource,
|
|
6
|
-
server,
|
|
7
|
-
tool,
|
|
8
|
-
toolForWidget,
|
|
9
|
-
toolWithWidget,
|
|
10
|
-
widget,
|
|
11
|
-
z,
|
|
12
|
-
} from '@noodleseed/one';
|
|
1
|
+
import { annotations, asset, connector, resource, server, tool, z } from '@noodleseed/one';
|
|
13
2
|
|
|
14
3
|
const heroImage = asset('assets/noodle-bowl.jpg');
|
|
15
4
|
|
|
@@ -17,28 +6,28 @@ const state = connector('noodle_state')
|
|
|
17
6
|
.version('1.0.0')
|
|
18
7
|
.operation('read_state', {
|
|
19
8
|
type: 'read',
|
|
20
|
-
input: {
|
|
21
|
-
handle:
|
|
22
|
-
key:
|
|
23
|
-
},
|
|
24
|
-
output: {
|
|
25
|
-
value:
|
|
26
|
-
revision:
|
|
27
|
-
status:
|
|
28
|
-
},
|
|
9
|
+
input: z.object({
|
|
10
|
+
handle: z.string(),
|
|
11
|
+
key: z.string().optional(),
|
|
12
|
+
}),
|
|
13
|
+
output: z.object({
|
|
14
|
+
value: z.record(z.string(), z.unknown()),
|
|
15
|
+
revision: z.number().int(),
|
|
16
|
+
status: z.string(),
|
|
17
|
+
}),
|
|
29
18
|
})
|
|
30
19
|
.operation('patch_state', {
|
|
31
20
|
type: 'action',
|
|
32
|
-
input: {
|
|
33
|
-
handle:
|
|
34
|
-
expectedRevision:
|
|
35
|
-
value:
|
|
36
|
-
},
|
|
37
|
-
output: {
|
|
38
|
-
value:
|
|
39
|
-
revision:
|
|
40
|
-
status:
|
|
41
|
-
},
|
|
21
|
+
input: z.object({
|
|
22
|
+
handle: z.string(),
|
|
23
|
+
expectedRevision: z.number().int(),
|
|
24
|
+
value: z.record(z.string(), z.unknown()),
|
|
25
|
+
}),
|
|
26
|
+
output: z.object({
|
|
27
|
+
value: z.record(z.string(), z.unknown()),
|
|
28
|
+
revision: z.number().int(),
|
|
29
|
+
status: z.string(),
|
|
30
|
+
}),
|
|
42
31
|
});
|
|
43
32
|
|
|
44
33
|
const stores = [
|
|
@@ -235,7 +224,7 @@ export default server(
|
|
|
235
224
|
},
|
|
236
225
|
},
|
|
237
226
|
[
|
|
238
|
-
|
|
227
|
+
tool('open_ordering', {
|
|
239
228
|
description:
|
|
240
229
|
'Open a complete food-ordering widget with store discovery, menu browsing, cart review, and checkout handoff.',
|
|
241
230
|
annotations: readOnly,
|
|
@@ -257,13 +246,13 @@ export default server(
|
|
|
257
246
|
featuredItems: menu,
|
|
258
247
|
fallback: 'Open stores: Harbor Noodles (Noodles), Garden Wraps (Vegetarian).',
|
|
259
248
|
}),
|
|
260
|
-
|
|
249
|
+
viewTitle: 'Food ordering',
|
|
261
250
|
domain: 'https://orders.example.com',
|
|
262
251
|
view: {
|
|
263
252
|
component: 'ordering-flow',
|
|
264
253
|
entry: './views/ordering-flow.tsx',
|
|
265
254
|
},
|
|
266
|
-
|
|
255
|
+
viewDescription:
|
|
267
256
|
'A complete consumer ordering surface with app-only helper tools, cart state, and checkout handoff.',
|
|
268
257
|
csp: {
|
|
269
258
|
connectDomains: ['https://orders.example.com'],
|
|
@@ -272,7 +261,8 @@ export default server(
|
|
|
272
261
|
},
|
|
273
262
|
permissions: { clipboardWrite: {} },
|
|
274
263
|
}),
|
|
275
|
-
|
|
264
|
+
tool('search_stores', {
|
|
265
|
+
visibility: ['app'],
|
|
276
266
|
description: 'Filter synthetic restaurants for the ordering widget.',
|
|
277
267
|
annotations: readOnly,
|
|
278
268
|
input: z.object({
|
|
@@ -282,7 +272,8 @@ export default server(
|
|
|
282
272
|
output: z.object({ stores: z.array(storeShape) }),
|
|
283
273
|
fulfil: () => ({ stores }),
|
|
284
274
|
}),
|
|
285
|
-
|
|
275
|
+
tool('load_menu', {
|
|
276
|
+
visibility: ['app'],
|
|
286
277
|
description: 'Load synthetic menu categories and items for one store.',
|
|
287
278
|
annotations: readOnly,
|
|
288
279
|
input: z.object({ storeId: z.string() }),
|
|
@@ -293,14 +284,16 @@ export default server(
|
|
|
293
284
|
}),
|
|
294
285
|
fulfil: ({ input }) => ({ storeId: input.storeId, stores, items: menu }),
|
|
295
286
|
}),
|
|
296
|
-
|
|
287
|
+
tool('load_item', {
|
|
288
|
+
visibility: ['app'],
|
|
297
289
|
description: 'Load item details and modifier options for the ordering widget.',
|
|
298
290
|
annotations: readOnly,
|
|
299
291
|
input: z.object({ itemId: z.string() }),
|
|
300
292
|
output: z.object({ itemId: z.string(), items: z.array(menuItemShape) }),
|
|
301
293
|
fulfil: ({ input }) => ({ itemId: input.itemId, items: menu }),
|
|
302
294
|
}),
|
|
303
|
-
|
|
295
|
+
tool('read_cart', {
|
|
296
|
+
visibility: ['app'],
|
|
304
297
|
description: 'Read the caller-scoped ordering cart state.',
|
|
305
298
|
annotations: readOnly,
|
|
306
299
|
input: z.object({}),
|
|
@@ -314,7 +307,8 @@ export default server(
|
|
|
314
307
|
return { value: state.value, revision: state.revision, status: state.status };
|
|
315
308
|
},
|
|
316
309
|
}),
|
|
317
|
-
|
|
310
|
+
tool('sync_cart', {
|
|
311
|
+
visibility: ['app'],
|
|
318
312
|
description: 'Patch the caller-scoped ordering cart with the widget cart mirror.',
|
|
319
313
|
annotations: action,
|
|
320
314
|
input: cartInput,
|
|
@@ -333,7 +327,8 @@ export default server(
|
|
|
333
327
|
return { cart, revision: state.revision, status: state.status };
|
|
334
328
|
},
|
|
335
329
|
}),
|
|
336
|
-
|
|
330
|
+
tool('prepare_checkout', {
|
|
331
|
+
visibility: ['app'],
|
|
337
332
|
description: 'Prepare the caller-scoped cart for checkout handoff.',
|
|
338
333
|
annotations: action,
|
|
339
334
|
input: cartInput,
|
|
@@ -375,6 +370,17 @@ export default server(
|
|
|
375
370
|
status: 'Food Ordering widget capabilities are ready.',
|
|
376
371
|
note: 'Standalone preview covers React views, helper tools, cart state, handoff, CSP, and permissions.',
|
|
377
372
|
}),
|
|
373
|
+
viewName: 'capabilities_card',
|
|
374
|
+
viewTitle: 'Food Ordering capabilities',
|
|
375
|
+
viewDescription: 'Standalone widget resource for previewing the ordering capability surface.',
|
|
376
|
+
domain: 'https://orders.example.com',
|
|
377
|
+
view: { component: 'capabilities-card', entry: './views/capabilities-card.tsx' },
|
|
378
|
+
csp: {
|
|
379
|
+
connectDomains: ['https://orders.example.com'],
|
|
380
|
+
resourceDomains: ['https://orders.example.com'],
|
|
381
|
+
frameDomains: ['https://orders.example.com'],
|
|
382
|
+
},
|
|
383
|
+
permissions: { clipboardWrite: {} },
|
|
378
384
|
}),
|
|
379
385
|
resource('food_ordering_guide', {
|
|
380
386
|
uri: 'docs://food-ordering',
|
|
@@ -392,21 +398,5 @@ export default server(
|
|
|
392
398
|
'- Checkout opens an allowlisted example URL; payment and final ordering remain out of scope.',
|
|
393
399
|
].join('\n'),
|
|
394
400
|
}),
|
|
395
|
-
widget('capabilities_card', {
|
|
396
|
-
tool: 'show_capabilities',
|
|
397
|
-
title: 'Food Ordering capabilities',
|
|
398
|
-
domain: 'https://orders.example.com',
|
|
399
|
-
description: 'Standalone widget resource for previewing the ordering capability surface.',
|
|
400
|
-
view: {
|
|
401
|
-
component: 'capabilities-card',
|
|
402
|
-
entry: './views/capabilities-card.tsx',
|
|
403
|
-
},
|
|
404
|
-
csp: {
|
|
405
|
-
connectDomains: ['https://orders.example.com'],
|
|
406
|
-
resourceDomains: ['https://orders.example.com'],
|
|
407
|
-
frameDomains: ['https://orders.example.com'],
|
|
408
|
-
},
|
|
409
|
-
permissions: { clipboardWrite: {} },
|
|
410
|
-
}),
|
|
411
401
|
],
|
|
412
402
|
);
|