@ggui-ai/negotiator 0.1.0-rc.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.
- package/LICENSE +201 -0
- package/README.md +49 -0
- package/dist/contract-hash.d.ts +54 -0
- package/dist/contract-hash.d.ts.map +1 -0
- package/dist/contract-hash.js +96 -0
- package/dist/contract-validators.d.ts +171 -0
- package/dist/contract-validators.d.ts.map +1 -0
- package/dist/contract-validators.js +478 -0
- package/dist/decision-input.d.ts +48 -0
- package/dist/decision-input.d.ts.map +1 -0
- package/dist/decision-input.js +14 -0
- package/dist/decision.d.ts +54 -0
- package/dist/decision.d.ts.map +1 -0
- package/dist/decision.js +500 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/intent.d.ts +22 -0
- package/dist/intent.d.ts.map +1 -0
- package/dist/intent.js +28 -0
- package/dist/llm-caller.d.ts +70 -0
- package/dist/llm-caller.d.ts.map +1 -0
- package/dist/llm-caller.js +38 -0
- package/dist/llm-rerank.d.ts +101 -0
- package/dist/llm-rerank.d.ts.map +1 -0
- package/dist/llm-rerank.js +178 -0
- package/dist/negotiate.d.ts +141 -0
- package/dist/negotiate.d.ts.map +1 -0
- package/dist/negotiate.js +161 -0
- package/dist/normalize-schema.d.ts +22 -0
- package/dist/normalize-schema.d.ts.map +1 -0
- package/dist/normalize-schema.js +191 -0
- package/dist/pure.d.ts +30 -0
- package/dist/pure.d.ts.map +1 -0
- package/dist/pure.js +43 -0
- package/dist/rag-search.d.ts +73 -0
- package/dist/rag-search.d.ts.map +1 -0
- package/dist/rag-search.js +192 -0
- package/dist/rerank-eval/pairs.d.ts +28 -0
- package/dist/rerank-eval/pairs.d.ts.map +1 -0
- package/dist/rerank-eval/pairs.js +531 -0
- package/dist/rerank-eval/run-probe-cli.d.ts +3 -0
- package/dist/rerank-eval/run-probe-cli.d.ts.map +1 -0
- package/dist/rerank-eval/run-probe-cli.js +146 -0
- package/dist/rerank-eval/run-probe.d.ts +68 -0
- package/dist/rerank-eval/run-probe.d.ts.map +1 -0
- package/dist/rerank-eval/run-probe.js +113 -0
- package/dist/session.d.ts +42 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +21 -0
- package/dist/suggestion.d.ts +38 -0
- package/dist/suggestion.d.ts.map +1 -0
- package/dist/suggestion.js +47 -0
- package/dist/synth-bench/corpus.d.ts +106 -0
- package/dist/synth-bench/corpus.d.ts.map +1 -0
- package/dist/synth-bench/corpus.js +994 -0
- package/dist/synth-bench/run-bench-cli.d.ts +3 -0
- package/dist/synth-bench/run-bench-cli.d.ts.map +1 -0
- package/dist/synth-bench/run-bench-cli.js +181 -0
- package/dist/synth-bench/run-bench.d.ts +101 -0
- package/dist/synth-bench/run-bench.d.ts.map +1 -0
- package/dist/synth-bench/run-bench.js +374 -0
- package/dist/synthesize-contract.d.ts +131 -0
- package/dist/synthesize-contract.d.ts.map +1 -0
- package/dist/synthesize-contract.js +948 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/package.json +74 -0
- package/src/contract-hash.ts +102 -0
- package/src/contract-validators.ts +604 -0
- package/src/decision-input.ts +49 -0
- package/src/decision.ts +581 -0
- package/src/index.ts +63 -0
- package/src/intent.ts +37 -0
- package/src/llm-caller.ts +82 -0
- package/src/llm-rerank.ts +280 -0
- package/src/negotiate.ts +312 -0
- package/src/normalize-schema.ts +193 -0
- package/src/pure.ts +46 -0
- package/src/rag-search.ts +274 -0
- package/src/rerank-eval/pairs.ts +624 -0
- package/src/rerank-eval/run-probe-cli.ts +197 -0
- package/src/rerank-eval/run-probe.ts +198 -0
- package/src/session.ts +41 -0
- package/src/suggestion.ts +73 -0
- package/src/synth-bench/corpus.ts +1126 -0
- package/src/synth-bench/run-bench-cli.ts +237 -0
- package/src/synth-bench/run-bench.ts +525 -0
- package/src/synthesize-contract.ts +1161 -0
- package/src/types.ts +31 -0
|
@@ -0,0 +1,994 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-curated synthesizer bench corpus.
|
|
3
|
+
*
|
|
4
|
+
* Each entry pairs a natural-language intent with the structural
|
|
5
|
+
* contract shape we expect the synthesizer to produce. The shape is
|
|
6
|
+
* intentionally coarse — we assert what specs MUST appear, what specs
|
|
7
|
+
* MUST be absent, and (when applicable) which action / slot names are
|
|
8
|
+
* acceptable. Exact action labels and schema details are out of scope
|
|
9
|
+
* because LLM phrasing varies.
|
|
10
|
+
*
|
|
11
|
+
* Entries carry no hand-labeled archetype — the protocol thinks in
|
|
12
|
+
* contract SHAPE, not interaction-pattern categories. The bench
|
|
13
|
+
* report groups entries by `contractShape(entry.expected)`, derived
|
|
14
|
+
* from which specs the expected contract declares: `props-only`,
|
|
15
|
+
* `context-only`, `context+action`, `stream`, `with-gadgets`.
|
|
16
|
+
*
|
|
17
|
+
* Used by:
|
|
18
|
+
* - run-synth-bench.ts (live LLM probe — opt-in, costs ~$0.001/entry)
|
|
19
|
+
* - structure-bench.test.ts (deterministic validator-only check)
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Contract-shape bucket of an expected contract — the grouping the
|
|
23
|
+
* bench report rolls precision up by, replacing the retired
|
|
24
|
+
* interaction-archetype categories. Priority-ordered so each entry
|
|
25
|
+
* lands in exactly one bucket: the most distinctive declared spec
|
|
26
|
+
* wins (a gadget-bearing contract is `with-gadgets` regardless of
|
|
27
|
+
* what else it declares; a stream-bearing one is `stream`; …).
|
|
28
|
+
*/
|
|
29
|
+
export function contractShape(expected) {
|
|
30
|
+
if (expected.hasClientCapabilities)
|
|
31
|
+
return 'with-gadgets';
|
|
32
|
+
if (expected.hasStreamSpec)
|
|
33
|
+
return 'stream';
|
|
34
|
+
if (expected.hasActionSpec)
|
|
35
|
+
return 'context+action';
|
|
36
|
+
if (expected.hasContextSpec)
|
|
37
|
+
return 'context-only';
|
|
38
|
+
if (expected.hasProps)
|
|
39
|
+
return 'props-only';
|
|
40
|
+
return 'empty';
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Canonical registered-package descriptor for the gadget-bearing
|
|
44
|
+
* corpus cases. Mirrors what `@ggui-samples/gadget-leaflet` ships —
|
|
45
|
+
* a single COMPONENT export (`<LeafletMap>`). The bench keeps the
|
|
46
|
+
* descriptor inline so the corpus is self-contained — no workspace
|
|
47
|
+
* cross-dependency.
|
|
48
|
+
*/
|
|
49
|
+
const LEAFLET_DESCRIPTOR = {
|
|
50
|
+
package: '@ggui-samples/gadget-leaflet',
|
|
51
|
+
version: '0.0.1',
|
|
52
|
+
exports: [
|
|
53
|
+
{
|
|
54
|
+
component: 'LeafletMap',
|
|
55
|
+
description: 'Render an interactive Leaflet map with a tile layer, pan/zoom controls, and markers. The component owns the container, sizing, and lifecycle.',
|
|
56
|
+
usage: 'Render `<LeafletMap center={[lat, lng]} zoom={2..20} />` when the intent names a rendered map (location browsing, route preview, delivery tracking, points-of-interest grid).',
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Registered mixed-package descriptor for the `capability-plugin` HOOK
|
|
62
|
+
* case. Mirrors `@ggui-samples/gadget-chart` — a `Chart` COMPONENT
|
|
63
|
+
* export plus a `useChartTheme` companion HOOK export. Exercises
|
|
64
|
+
* whether synth declares a registered third-party HOOK (not just a
|
|
65
|
+
* component) under `clientCapabilities.gadgets` — the dual of the
|
|
66
|
+
* Leaflet (component-only) cases.
|
|
67
|
+
*/
|
|
68
|
+
const CHART_DESCRIPTOR = {
|
|
69
|
+
package: '@ggui-samples/gadget-chart',
|
|
70
|
+
version: '0.0.1',
|
|
71
|
+
exports: [
|
|
72
|
+
{
|
|
73
|
+
component: 'Chart',
|
|
74
|
+
description: 'Render a responsive SVG bar chart from labelled magnitudes.',
|
|
75
|
+
usage: 'Render `<Chart data={[{ label, value }]} />` when the intent names a bar chart or a metric breakdown.',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
hook: 'useChartTheme',
|
|
79
|
+
description: 'Read the active ggui theme and return resolved chart colors — a categorical palette plus axis / label / grid colors.',
|
|
80
|
+
usage: 'Call `const theme = useChartTheme();` when a charted visualization should track the operator-selected app theme; pass `theme.palette[*]` as the series colors.',
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
export const BENCH_CORPUS = [
|
|
85
|
+
// ─── mutator-only: contextSpec mirror is the wire ─────────────────
|
|
86
|
+
{
|
|
87
|
+
id: 'mut-counter-1',
|
|
88
|
+
intent: 'a simple counter widget with increment, decrement, and reset',
|
|
89
|
+
expected: {
|
|
90
|
+
hasActionSpec: false,
|
|
91
|
+
hasContextSpec: true,
|
|
92
|
+
hasStreamSpec: false,
|
|
93
|
+
hasProps: false,
|
|
94
|
+
contextSlots: ['count'],
|
|
95
|
+
notes: 'load-bearing case. Slot mirror IS the wire; declaring increment/decrement/reset actions creates a parallel wire path the generator wires up incorrectly.',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'mut-counter-2',
|
|
100
|
+
intent: 'make me a counter that goes up and down',
|
|
101
|
+
expected: {
|
|
102
|
+
hasActionSpec: false,
|
|
103
|
+
hasContextSpec: true,
|
|
104
|
+
hasStreamSpec: false,
|
|
105
|
+
hasProps: false,
|
|
106
|
+
contextSlots: ['count'],
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 'mut-counter-3',
|
|
111
|
+
intent: 'a labeled counter with a custom step size',
|
|
112
|
+
expected: {
|
|
113
|
+
hasActionSpec: false,
|
|
114
|
+
hasContextSpec: true,
|
|
115
|
+
hasStreamSpec: false,
|
|
116
|
+
hasProps: false,
|
|
117
|
+
contextSlots: ['count', 'step', 'label'],
|
|
118
|
+
tolerateEitherShape: true,
|
|
119
|
+
notes: 'the live `count` is contextSpec; the static `label` + `step` are a genuine props-or-context judgment call — either shape is accepted.',
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: 'mut-toggle-1',
|
|
124
|
+
intent: 'a dark-mode toggle switch',
|
|
125
|
+
expected: {
|
|
126
|
+
hasActionSpec: false,
|
|
127
|
+
hasContextSpec: true,
|
|
128
|
+
hasStreamSpec: false,
|
|
129
|
+
hasProps: false,
|
|
130
|
+
contextSlots: ['darkMode', 'enabled', 'mode'],
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 'mut-toggle-2',
|
|
135
|
+
intent: 'an on/off switch for notifications',
|
|
136
|
+
expected: {
|
|
137
|
+
hasActionSpec: false,
|
|
138
|
+
hasContextSpec: true,
|
|
139
|
+
hasStreamSpec: false,
|
|
140
|
+
hasProps: false,
|
|
141
|
+
contextSlots: ['enabled', 'notifications', 'on'],
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: 'mut-slider-1',
|
|
146
|
+
intent: 'a volume slider that goes from 0 to 100',
|
|
147
|
+
expected: {
|
|
148
|
+
hasActionSpec: false,
|
|
149
|
+
hasContextSpec: true,
|
|
150
|
+
hasStreamSpec: false,
|
|
151
|
+
hasProps: false,
|
|
152
|
+
contextSlots: ['volume', 'value'],
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: 'mut-slider-2',
|
|
157
|
+
intent: 'a brightness control slider',
|
|
158
|
+
expected: {
|
|
159
|
+
hasActionSpec: false,
|
|
160
|
+
hasContextSpec: true,
|
|
161
|
+
hasStreamSpec: false,
|
|
162
|
+
hasProps: false,
|
|
163
|
+
contextSlots: ['brightness', 'value'],
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: 'mut-picker-1',
|
|
168
|
+
intent: 'a tab picker with home, profile, and settings tabs',
|
|
169
|
+
expected: {
|
|
170
|
+
hasActionSpec: false,
|
|
171
|
+
hasContextSpec: true,
|
|
172
|
+
hasStreamSpec: false,
|
|
173
|
+
hasProps: false,
|
|
174
|
+
contextSlots: ['selectedTab', 'tab', 'activeTab'],
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: 'mut-picker-2',
|
|
179
|
+
intent: 'a color picker with red, green, blue, and yellow swatches',
|
|
180
|
+
expected: {
|
|
181
|
+
hasActionSpec: false,
|
|
182
|
+
hasContextSpec: true,
|
|
183
|
+
hasStreamSpec: false,
|
|
184
|
+
hasProps: false,
|
|
185
|
+
contextSlots: ['color', 'selectedColor'],
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: 'mut-search-livetype',
|
|
190
|
+
intent: 'a search box that filters results live as the user types',
|
|
191
|
+
expected: {
|
|
192
|
+
hasActionSpec: false,
|
|
193
|
+
hasContextSpec: true,
|
|
194
|
+
hasStreamSpec: false,
|
|
195
|
+
hasProps: false,
|
|
196
|
+
contextSlots: ['query', 'searchText'],
|
|
197
|
+
notes: 'live-filter UIs do not need a discrete search action — the agent watches the query slot stream.',
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
// ─── form: contextSpec for draft + actionSpec for submit ──────────
|
|
201
|
+
{
|
|
202
|
+
id: 'form-feedback-1',
|
|
203
|
+
intent: 'a feedback form with a 5-star rating and a free-text comment',
|
|
204
|
+
expected: {
|
|
205
|
+
hasActionSpec: true,
|
|
206
|
+
hasContextSpec: true,
|
|
207
|
+
hasStreamSpec: false,
|
|
208
|
+
hasProps: false,
|
|
209
|
+
actionNames: ['submit', 'submitFeedback', 'send'],
|
|
210
|
+
contextSlots: ['rating', 'comment', 'draft', 'feedback'],
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: 'form-contact-1',
|
|
215
|
+
intent: 'a contact form with name, email, and message fields',
|
|
216
|
+
expected: {
|
|
217
|
+
hasActionSpec: true,
|
|
218
|
+
hasContextSpec: true,
|
|
219
|
+
hasStreamSpec: false,
|
|
220
|
+
hasProps: false,
|
|
221
|
+
actionNames: ['submit', 'send', 'submitContact'],
|
|
222
|
+
contextSlots: ['name', 'email', 'message', 'draft', 'formData'],
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
id: 'form-login-1',
|
|
227
|
+
intent: 'a login form with username and password and a sign-in button',
|
|
228
|
+
expected: {
|
|
229
|
+
hasActionSpec: true,
|
|
230
|
+
hasContextSpec: true,
|
|
231
|
+
hasStreamSpec: false,
|
|
232
|
+
hasProps: false,
|
|
233
|
+
actionNames: ['signIn', 'login', 'submit', 'authenticate'],
|
|
234
|
+
contextSlots: ['username', 'password', 'draft'],
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
id: 'form-notepad-save',
|
|
239
|
+
intent: 'a notepad I can save',
|
|
240
|
+
expected: {
|
|
241
|
+
hasActionSpec: true,
|
|
242
|
+
hasContextSpec: true,
|
|
243
|
+
hasStreamSpec: false,
|
|
244
|
+
hasProps: false,
|
|
245
|
+
actionNames: ['save', 'saveNote'],
|
|
246
|
+
contextSlots: ['noteText', 'note', 'text', 'draft'],
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
id: 'form-confirm-1',
|
|
251
|
+
intent: 'a confirmation dialog with confirm and cancel buttons',
|
|
252
|
+
expected: {
|
|
253
|
+
hasActionSpec: true,
|
|
254
|
+
hasContextSpec: false,
|
|
255
|
+
hasStreamSpec: false,
|
|
256
|
+
hasProps: false,
|
|
257
|
+
actionNames: ['confirm', 'cancel'],
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: 'form-search-button',
|
|
262
|
+
intent: 'a search box with a search button that runs on click',
|
|
263
|
+
expected: {
|
|
264
|
+
hasActionSpec: true,
|
|
265
|
+
hasContextSpec: true,
|
|
266
|
+
hasStreamSpec: false,
|
|
267
|
+
hasProps: false,
|
|
268
|
+
actionNames: ['search', 'submitSearch', 'submit'],
|
|
269
|
+
contextSlots: ['query', 'searchText'],
|
|
270
|
+
notes: 'explicit search button — discrete event with payload. Distinguish from live-filter (mut-search-livetype).',
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
id: 'form-survey-1',
|
|
275
|
+
intent: 'a quick survey with three multiple-choice questions and a submit button',
|
|
276
|
+
expected: {
|
|
277
|
+
hasActionSpec: true,
|
|
278
|
+
hasContextSpec: true,
|
|
279
|
+
hasStreamSpec: false,
|
|
280
|
+
hasProps: false,
|
|
281
|
+
actionNames: ['submit', 'submitSurvey', 'send'],
|
|
282
|
+
notes: 'no contextSlots assertion — a survey decomposes into per-question slots (question1, question2, …) or one aggregate; the names are intent-specific and cannot be enumerated.',
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
id: 'form-newsletter-1',
|
|
287
|
+
intent: 'a newsletter signup with an email field and subscribe button',
|
|
288
|
+
expected: {
|
|
289
|
+
hasActionSpec: true,
|
|
290
|
+
hasContextSpec: true,
|
|
291
|
+
hasStreamSpec: false,
|
|
292
|
+
hasProps: false,
|
|
293
|
+
actionNames: ['subscribe', 'submit', 'signUp'],
|
|
294
|
+
contextSlots: ['email', 'draft'],
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
// ─── list: many local-only lists need NO actionSpec ───────────────
|
|
298
|
+
{
|
|
299
|
+
id: 'list-todo-local',
|
|
300
|
+
intent: 'a todo list where I can add and remove items',
|
|
301
|
+
expected: {
|
|
302
|
+
hasActionSpec: false,
|
|
303
|
+
hasContextSpec: true,
|
|
304
|
+
hasStreamSpec: false,
|
|
305
|
+
hasProps: false,
|
|
306
|
+
contextSlots: ['todos', 'items'],
|
|
307
|
+
tolerateEitherShape: true,
|
|
308
|
+
notes: 'local-only todos need NO actionSpec. Synthesizer often emits add/delete actions here — the validator flags them as redundant-action. Tolerated either way; preferred shape is contextSpec only.',
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
id: 'list-shopping-local',
|
|
313
|
+
intent: 'a shopping list',
|
|
314
|
+
expected: {
|
|
315
|
+
hasActionSpec: false,
|
|
316
|
+
hasContextSpec: true,
|
|
317
|
+
hasStreamSpec: false,
|
|
318
|
+
hasProps: false,
|
|
319
|
+
contextSlots: ['items', 'list'],
|
|
320
|
+
tolerateEitherShape: true,
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
id: 'list-todo-agent-backed',
|
|
325
|
+
intent: 'an agent-backed todo list that persists across sessions',
|
|
326
|
+
expected: {
|
|
327
|
+
hasActionSpec: true,
|
|
328
|
+
hasContextSpec: true,
|
|
329
|
+
hasStreamSpec: false,
|
|
330
|
+
hasProps: false,
|
|
331
|
+
actionNames: ['addTodo', 'deleteTodo', 'add', 'delete', 'create', 'remove'],
|
|
332
|
+
contextSlots: ['todos', 'items'],
|
|
333
|
+
notes: 'agent-backed = explicit persistence = each add/delete IS a discrete event the agent must witness.',
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
id: 'list-message-thread',
|
|
338
|
+
intent: 'a message thread showing a list of messages',
|
|
339
|
+
expected: {
|
|
340
|
+
hasActionSpec: false,
|
|
341
|
+
hasContextSpec: false,
|
|
342
|
+
hasStreamSpec: false,
|
|
343
|
+
hasProps: true,
|
|
344
|
+
tolerateEitherShape: true,
|
|
345
|
+
notes: 'static thread — props with messages array. Live thread would be broadcast/converse.',
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
id: 'list-file-browser',
|
|
350
|
+
intent: 'a file browser showing the contents of a directory',
|
|
351
|
+
expected: {
|
|
352
|
+
hasActionSpec: false,
|
|
353
|
+
hasContextSpec: false,
|
|
354
|
+
hasStreamSpec: false,
|
|
355
|
+
hasProps: true,
|
|
356
|
+
tolerateEitherShape: true,
|
|
357
|
+
notes: 'agent passes file list as props; user reads.',
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
id: 'list-checklist-local',
|
|
362
|
+
intent: 'a checklist where I tick off items',
|
|
363
|
+
expected: {
|
|
364
|
+
hasActionSpec: false,
|
|
365
|
+
hasContextSpec: true,
|
|
366
|
+
hasStreamSpec: false,
|
|
367
|
+
hasProps: false,
|
|
368
|
+
contextSlots: ['items', 'checked', 'completed'],
|
|
369
|
+
tolerateEitherShape: true,
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
// ─── display: static props-only ───────────────────────────────────
|
|
373
|
+
{
|
|
374
|
+
id: 'display-weather-1',
|
|
375
|
+
intent: 'a weather card for Tokyo with temperature and an icon',
|
|
376
|
+
expected: {
|
|
377
|
+
hasActionSpec: false,
|
|
378
|
+
hasContextSpec: false,
|
|
379
|
+
hasStreamSpec: false,
|
|
380
|
+
hasProps: true,
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
id: 'display-weather-2',
|
|
385
|
+
intent: 'a weather widget showing today and tomorrow',
|
|
386
|
+
expected: {
|
|
387
|
+
hasActionSpec: false,
|
|
388
|
+
hasContextSpec: false,
|
|
389
|
+
hasStreamSpec: false,
|
|
390
|
+
hasProps: true,
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
id: 'display-profile-1',
|
|
395
|
+
intent: 'a profile card with name, avatar, and bio',
|
|
396
|
+
expected: {
|
|
397
|
+
hasActionSpec: false,
|
|
398
|
+
hasContextSpec: false,
|
|
399
|
+
hasStreamSpec: false,
|
|
400
|
+
hasProps: true,
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
id: 'display-stat-1',
|
|
405
|
+
intent: 'a single big number stat for revenue this month',
|
|
406
|
+
expected: {
|
|
407
|
+
hasActionSpec: false,
|
|
408
|
+
hasContextSpec: false,
|
|
409
|
+
hasStreamSpec: false,
|
|
410
|
+
hasProps: true,
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
id: 'display-status-1',
|
|
415
|
+
intent: 'a deployment status panel showing service name and current state',
|
|
416
|
+
expected: {
|
|
417
|
+
hasActionSpec: false,
|
|
418
|
+
hasContextSpec: false,
|
|
419
|
+
hasStreamSpec: false,
|
|
420
|
+
hasProps: true,
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
id: 'display-quote-1',
|
|
425
|
+
intent: 'a quote card with the quote text and the author',
|
|
426
|
+
expected: {
|
|
427
|
+
hasActionSpec: false,
|
|
428
|
+
hasContextSpec: false,
|
|
429
|
+
hasStreamSpec: false,
|
|
430
|
+
hasProps: true,
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
id: 'display-recipe-1',
|
|
435
|
+
intent: 'a recipe card with title, ingredients, and steps',
|
|
436
|
+
expected: {
|
|
437
|
+
hasActionSpec: false,
|
|
438
|
+
hasContextSpec: false,
|
|
439
|
+
hasStreamSpec: false,
|
|
440
|
+
hasProps: true,
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
id: 'display-event-1',
|
|
445
|
+
intent: 'an event ticket card with date, location, and seat',
|
|
446
|
+
expected: {
|
|
447
|
+
hasActionSpec: false,
|
|
448
|
+
hasContextSpec: false,
|
|
449
|
+
hasStreamSpec: false,
|
|
450
|
+
hasProps: true,
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
// ─── broadcast: agent → UI live ───────────────────────────────────
|
|
454
|
+
{
|
|
455
|
+
id: 'bcast-clock-1',
|
|
456
|
+
intent: 'a live clock that updates every second',
|
|
457
|
+
expected: {
|
|
458
|
+
hasActionSpec: false,
|
|
459
|
+
hasContextSpec: false,
|
|
460
|
+
hasStreamSpec: true,
|
|
461
|
+
hasProps: false,
|
|
462
|
+
},
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
id: 'bcast-ticker-1',
|
|
466
|
+
intent: 'a stock ticker showing live price updates',
|
|
467
|
+
expected: {
|
|
468
|
+
hasActionSpec: false,
|
|
469
|
+
hasContextSpec: false,
|
|
470
|
+
hasStreamSpec: true,
|
|
471
|
+
hasProps: false,
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
id: 'bcast-dashboard-1',
|
|
476
|
+
intent: 'a live system dashboard with CPU, memory, and request-rate metrics streaming in',
|
|
477
|
+
expected: {
|
|
478
|
+
hasActionSpec: false,
|
|
479
|
+
hasContextSpec: false,
|
|
480
|
+
hasStreamSpec: true,
|
|
481
|
+
hasProps: false,
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
id: 'bcast-feed-1',
|
|
486
|
+
intent: 'a live notifications feed showing alerts as they arrive',
|
|
487
|
+
expected: {
|
|
488
|
+
hasActionSpec: false,
|
|
489
|
+
hasContextSpec: false,
|
|
490
|
+
hasStreamSpec: true,
|
|
491
|
+
hasProps: false,
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
id: 'bcast-progress-1',
|
|
496
|
+
intent: 'a progress bar showing live build progress from the agent',
|
|
497
|
+
expected: {
|
|
498
|
+
hasActionSpec: false,
|
|
499
|
+
hasContextSpec: false,
|
|
500
|
+
hasStreamSpec: true,
|
|
501
|
+
hasProps: false,
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
// ─── converse: bidirectional ──────────────────────────────────────
|
|
505
|
+
{
|
|
506
|
+
id: 'conv-chat-1',
|
|
507
|
+
intent: 'a chat with the agent showing live messages',
|
|
508
|
+
expected: {
|
|
509
|
+
hasActionSpec: true,
|
|
510
|
+
hasContextSpec: false,
|
|
511
|
+
hasStreamSpec: true,
|
|
512
|
+
hasProps: false,
|
|
513
|
+
actionNames: ['sendMessage', 'send', 'submit'],
|
|
514
|
+
tolerateEitherShape: true,
|
|
515
|
+
notes: 'contextSpec for draftText is optional but valid.',
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
id: 'conv-copilot-1',
|
|
520
|
+
intent: 'a coding copilot session where I send prompts and see streamed responses',
|
|
521
|
+
expected: {
|
|
522
|
+
hasActionSpec: true,
|
|
523
|
+
hasContextSpec: false,
|
|
524
|
+
hasStreamSpec: true,
|
|
525
|
+
hasProps: false,
|
|
526
|
+
actionNames: ['sendMessage', 'sendPrompt', 'submit', 'send'],
|
|
527
|
+
tolerateEitherShape: true,
|
|
528
|
+
},
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
id: 'conv-support-1',
|
|
532
|
+
intent: 'a customer support chat with an AI agent',
|
|
533
|
+
expected: {
|
|
534
|
+
hasActionSpec: true,
|
|
535
|
+
hasContextSpec: false,
|
|
536
|
+
hasStreamSpec: true,
|
|
537
|
+
hasProps: false,
|
|
538
|
+
actionNames: ['sendMessage', 'send', 'submit'],
|
|
539
|
+
tolerateEitherShape: true,
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
// ─── flow: multi-step wizards ─────────────────────────────────────
|
|
543
|
+
{
|
|
544
|
+
id: 'flow-onboarding-1',
|
|
545
|
+
intent: 'a 3-step onboarding wizard for setting up a profile',
|
|
546
|
+
expected: {
|
|
547
|
+
hasActionSpec: true,
|
|
548
|
+
hasContextSpec: true,
|
|
549
|
+
hasStreamSpec: false,
|
|
550
|
+
hasProps: false,
|
|
551
|
+
actionNames: ['next', 'back', 'finish', 'submit', 'previous'],
|
|
552
|
+
contextSlots: ['step', 'currentStep', 'draft', 'profile'],
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
id: 'flow-checkout-1',
|
|
557
|
+
intent: 'a checkout flow with shipping, payment, and review steps',
|
|
558
|
+
expected: {
|
|
559
|
+
hasActionSpec: true,
|
|
560
|
+
hasContextSpec: true,
|
|
561
|
+
hasStreamSpec: false,
|
|
562
|
+
hasProps: false,
|
|
563
|
+
contextSlots: ['step', 'currentStep', 'draft', 'order'],
|
|
564
|
+
notes: 'no actionNames assertion — a checkout-completion action has an unbounded valid vocabulary (finish / complete / completePurchase / placeOrder / …). Spec-presence + placement verify the shape; an exact-name allow-list only generates false negatives.',
|
|
565
|
+
},
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
id: 'flow-survey-multistep',
|
|
569
|
+
intent: 'a multi-step survey where each section is its own page',
|
|
570
|
+
expected: {
|
|
571
|
+
hasActionSpec: true,
|
|
572
|
+
hasContextSpec: true,
|
|
573
|
+
hasStreamSpec: false,
|
|
574
|
+
hasProps: false,
|
|
575
|
+
actionNames: ['next', 'back', 'finish', 'submit', 'previous'],
|
|
576
|
+
contextSlots: ['step', 'currentStep', 'answers', 'draft', 'responses'],
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
id: 'flow-tutorial-1',
|
|
581
|
+
intent: 'a tutorial wizard that walks through the app features',
|
|
582
|
+
expected: {
|
|
583
|
+
hasActionSpec: true,
|
|
584
|
+
hasContextSpec: true,
|
|
585
|
+
hasStreamSpec: false,
|
|
586
|
+
hasProps: false,
|
|
587
|
+
actionNames: ['next', 'back', 'finish', 'skip', 'previous'],
|
|
588
|
+
contextSlots: ['step', 'currentStep'],
|
|
589
|
+
},
|
|
590
|
+
},
|
|
591
|
+
// ─── modal / settings / navigation (collect with discrete events) ─
|
|
592
|
+
{
|
|
593
|
+
id: 'misc-settings-1',
|
|
594
|
+
intent: 'a settings panel with several toggles and a save button',
|
|
595
|
+
expected: {
|
|
596
|
+
hasActionSpec: true,
|
|
597
|
+
hasContextSpec: true,
|
|
598
|
+
hasStreamSpec: false,
|
|
599
|
+
hasProps: false,
|
|
600
|
+
actionNames: ['save', 'submit', 'apply'],
|
|
601
|
+
notes: 'no contextSlots assertion — one context slot per toggle is the idiomatic shape, and the exact per-toggle names are intent-specific (notificationsEnabled, darkMode, …) so cannot be enumerated.',
|
|
602
|
+
},
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
id: 'misc-modal-confirm',
|
|
606
|
+
intent: 'a delete-confirmation modal with confirm and cancel actions',
|
|
607
|
+
expected: {
|
|
608
|
+
hasActionSpec: true,
|
|
609
|
+
hasContextSpec: false,
|
|
610
|
+
hasStreamSpec: false,
|
|
611
|
+
hasProps: false,
|
|
612
|
+
actionNames: ['confirm', 'cancel', 'delete'],
|
|
613
|
+
},
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
id: 'misc-nav-1',
|
|
617
|
+
intent: 'a navigation drawer with home, search, and inbox links',
|
|
618
|
+
expected: {
|
|
619
|
+
hasActionSpec: false,
|
|
620
|
+
hasContextSpec: true,
|
|
621
|
+
hasStreamSpec: false,
|
|
622
|
+
hasProps: false,
|
|
623
|
+
contextSlots: [
|
|
624
|
+
'route',
|
|
625
|
+
'currentRoute',
|
|
626
|
+
'page',
|
|
627
|
+
'selectedRoute',
|
|
628
|
+
'activeLink',
|
|
629
|
+
'active',
|
|
630
|
+
'link',
|
|
631
|
+
],
|
|
632
|
+
tolerateEitherShape: true,
|
|
633
|
+
notes: 'navigation is route mutation; the slot mirror IS the wire. Tolerated either way because some intents read "navigate" as a discrete event. The active-nav slot has many reasonable names (route / activeLink / …).',
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
id: 'misc-share-button',
|
|
638
|
+
intent: 'an article view with a share button',
|
|
639
|
+
expected: {
|
|
640
|
+
hasActionSpec: true,
|
|
641
|
+
hasContextSpec: false,
|
|
642
|
+
hasStreamSpec: false,
|
|
643
|
+
hasProps: true,
|
|
644
|
+
actionNames: ['share'],
|
|
645
|
+
notes: 'share IS a discrete event the agent must witness.',
|
|
646
|
+
},
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
id: 'misc-publish-button',
|
|
650
|
+
intent: 'a draft post editor with a publish button',
|
|
651
|
+
expected: {
|
|
652
|
+
hasActionSpec: true,
|
|
653
|
+
hasContextSpec: true,
|
|
654
|
+
hasStreamSpec: false,
|
|
655
|
+
hasProps: false,
|
|
656
|
+
actionNames: ['publish', 'submit', 'send'],
|
|
657
|
+
notes: 'no contextSlots assertion — a draft-text slot has an unbounded valid vocabulary (draft / body / content / postText / …). hasContextSpec verifies the slot exists; an exact-name allow-list only generates false negatives.',
|
|
658
|
+
},
|
|
659
|
+
},
|
|
660
|
+
// ─── hard cases: tolerate either author or refuse ────────────────
|
|
661
|
+
{
|
|
662
|
+
id: 'hard-color-stream',
|
|
663
|
+
intent: 'a color picker that streams the chosen color back to the agent in real time',
|
|
664
|
+
expected: {
|
|
665
|
+
hasActionSpec: false,
|
|
666
|
+
hasContextSpec: true,
|
|
667
|
+
hasStreamSpec: false,
|
|
668
|
+
hasProps: false,
|
|
669
|
+
contextSlots: ['color', 'selectedColor'],
|
|
670
|
+
tolerateEitherShape: true,
|
|
671
|
+
notes: 'streaming back to the agent IS the contextSpec mirror. Synth often misreads "streams" as needing streamSpec — bench tolerates both.',
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
id: 'hard-multi-user-chat',
|
|
676
|
+
intent: 'a multi-user chat room with three participants',
|
|
677
|
+
expected: {
|
|
678
|
+
hasActionSpec: true,
|
|
679
|
+
hasContextSpec: false,
|
|
680
|
+
hasStreamSpec: true,
|
|
681
|
+
hasProps: false,
|
|
682
|
+
tolerateEitherShape: true,
|
|
683
|
+
notes: 'multi-user is outside the agent-as-counterparty model. Synth may shape it as converse with sendMessage; that is acceptable.',
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
id: 'hard-collaborative-doc',
|
|
688
|
+
intent: 'a collaborative document where multiple users edit at once',
|
|
689
|
+
expected: {
|
|
690
|
+
hasActionSpec: false,
|
|
691
|
+
hasContextSpec: true,
|
|
692
|
+
hasStreamSpec: false,
|
|
693
|
+
hasProps: false,
|
|
694
|
+
tolerateEitherShape: true,
|
|
695
|
+
notes: 'CRDT semantics are out of scope. Single-user shape (contextSpec.docText) is acceptable.',
|
|
696
|
+
},
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
id: 'hard-game-controller',
|
|
700
|
+
intent: 'a game controller with directional pad and action buttons',
|
|
701
|
+
expected: {
|
|
702
|
+
hasActionSpec: true,
|
|
703
|
+
hasContextSpec: true,
|
|
704
|
+
hasStreamSpec: false,
|
|
705
|
+
hasProps: false,
|
|
706
|
+
tolerateEitherShape: true,
|
|
707
|
+
notes: 'each button press IS a discrete event (jump, fire) — actionSpec valid. Direction may be slot OR per-press action.',
|
|
708
|
+
},
|
|
709
|
+
},
|
|
710
|
+
{
|
|
711
|
+
id: 'hard-file-upload',
|
|
712
|
+
intent: 'a file uploader with drag-and-drop',
|
|
713
|
+
expected: {
|
|
714
|
+
hasActionSpec: true,
|
|
715
|
+
hasContextSpec: true,
|
|
716
|
+
hasStreamSpec: false,
|
|
717
|
+
hasProps: false,
|
|
718
|
+
actionNames: ['upload', 'submit', 'addFile', 'send', 'filesSelected', 'select'],
|
|
719
|
+
contextSlots: ['files', 'queue', 'draft'],
|
|
720
|
+
tolerateEitherShape: true,
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
id: 'hard-realtime-quiz',
|
|
725
|
+
intent: 'a real-time multiplayer quiz with a leaderboard that updates live',
|
|
726
|
+
expected: {
|
|
727
|
+
hasActionSpec: true,
|
|
728
|
+
hasContextSpec: false,
|
|
729
|
+
hasStreamSpec: true,
|
|
730
|
+
hasProps: false,
|
|
731
|
+
tolerateEitherShape: true,
|
|
732
|
+
notes: 'leaderboard is broadcast; answer submission is action. Synth may shape as converse or broadcast — both acceptable.',
|
|
733
|
+
},
|
|
734
|
+
},
|
|
735
|
+
// ─── capability: browser-capability hook declarations (EE-runtime) ───
|
|
736
|
+
// The capability output is a UI-side side effect that surfaces to the
|
|
737
|
+
// agent ONLY when threaded into a contextSpec slot or actionSpec
|
|
738
|
+
// payload. The synth output is expected to declare
|
|
739
|
+
// `clientCapabilities.gadgets[X].hook` for the named hook from
|
|
740
|
+
// `@ggui-ai/gadgets`. `tolerateEitherShape` is set on every
|
|
741
|
+
// entry — synth prompts haven't been retrained on these yet (Slice
|
|
742
|
+
// EE-runtime triad subtask 5f), so the corpus records the EXPECTED
|
|
743
|
+
// shape without failing legacy synth runs.
|
|
744
|
+
{
|
|
745
|
+
id: 'cap-location-1',
|
|
746
|
+
intent: 'show my current location on a map',
|
|
747
|
+
expected: {
|
|
748
|
+
hasActionSpec: false,
|
|
749
|
+
hasContextSpec: true,
|
|
750
|
+
hasStreamSpec: false,
|
|
751
|
+
hasProps: false,
|
|
752
|
+
hasClientCapabilities: true,
|
|
753
|
+
capabilityHooks: ['useGeolocation'],
|
|
754
|
+
contextSlots: ['location', 'coords', 'position'],
|
|
755
|
+
tolerateEitherShape: true,
|
|
756
|
+
notes: 'geolocation read is a one-shot capability; the resolved coords land on a contextSpec slot the map renders from.',
|
|
757
|
+
},
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
id: 'cap-voice-1',
|
|
761
|
+
intent: 'let me record a voice memo and send it',
|
|
762
|
+
expected: {
|
|
763
|
+
hasActionSpec: true,
|
|
764
|
+
hasContextSpec: true,
|
|
765
|
+
hasStreamSpec: false,
|
|
766
|
+
hasProps: false,
|
|
767
|
+
hasClientCapabilities: true,
|
|
768
|
+
capabilityHooks: ['useMicrophone'],
|
|
769
|
+
actionNames: ['send', 'submit', 'record'],
|
|
770
|
+
tolerateEitherShape: true,
|
|
771
|
+
notes: 'mic capture is a UI-owned lifecycle; the resulting Blob URL surfaces via contextSpec, sending is a discrete action.',
|
|
772
|
+
},
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
id: 'cap-photo-1',
|
|
776
|
+
intent: 'take a selfie and let me caption it before posting',
|
|
777
|
+
expected: {
|
|
778
|
+
hasActionSpec: true,
|
|
779
|
+
hasContextSpec: true,
|
|
780
|
+
hasStreamSpec: false,
|
|
781
|
+
hasProps: false,
|
|
782
|
+
hasClientCapabilities: true,
|
|
783
|
+
capabilityHooks: ['useCamera'],
|
|
784
|
+
actionNames: ['post', 'submit'],
|
|
785
|
+
contextSlots: ['photo', 'caption', 'image', 'draft'],
|
|
786
|
+
tolerateEitherShape: true,
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
{
|
|
790
|
+
id: 'cap-clipboard-write-1',
|
|
791
|
+
intent: 'a copy-to-clipboard button for a generated code snippet',
|
|
792
|
+
expected: {
|
|
793
|
+
hasActionSpec: true,
|
|
794
|
+
hasContextSpec: false,
|
|
795
|
+
hasStreamSpec: false,
|
|
796
|
+
hasProps: true,
|
|
797
|
+
hasClientCapabilities: true,
|
|
798
|
+
capabilityHooks: ['useClipboardWrite'],
|
|
799
|
+
actionNames: ['copy', 'copied'],
|
|
800
|
+
tolerateEitherShape: true,
|
|
801
|
+
notes: 'clipboard write is component-only mechanic; the agent observes only that the user pressed "copy" (actionSpec.copy).',
|
|
802
|
+
},
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
id: 'cap-clipboard-paste-1',
|
|
806
|
+
intent: 'paste my saved password into the login form',
|
|
807
|
+
expected: {
|
|
808
|
+
hasActionSpec: false,
|
|
809
|
+
hasContextSpec: true,
|
|
810
|
+
hasStreamSpec: false,
|
|
811
|
+
hasProps: false,
|
|
812
|
+
hasClientCapabilities: true,
|
|
813
|
+
capabilityHooks: ['useClipboardPaste'],
|
|
814
|
+
contextSlots: ['password', 'pastedText', 'value'],
|
|
815
|
+
tolerateEitherShape: true,
|
|
816
|
+
},
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
id: 'cap-file-1',
|
|
820
|
+
intent: 'upload a PDF for me to review',
|
|
821
|
+
expected: {
|
|
822
|
+
hasActionSpec: true,
|
|
823
|
+
hasContextSpec: true,
|
|
824
|
+
hasStreamSpec: false,
|
|
825
|
+
hasProps: false,
|
|
826
|
+
hasClientCapabilities: true,
|
|
827
|
+
capabilityHooks: ['useFilePicker'],
|
|
828
|
+
actionNames: ['upload', 'submit'],
|
|
829
|
+
contextSlots: ['file', 'files', 'attachment'],
|
|
830
|
+
tolerateEitherShape: true,
|
|
831
|
+
},
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
id: 'cap-notifications-1',
|
|
835
|
+
intent: 'send me a system notification when my long-running task finishes',
|
|
836
|
+
expected: {
|
|
837
|
+
hasActionSpec: false,
|
|
838
|
+
hasContextSpec: true,
|
|
839
|
+
hasStreamSpec: false,
|
|
840
|
+
hasProps: false,
|
|
841
|
+
hasClientCapabilities: true,
|
|
842
|
+
capabilityHooks: ['useNotifications'],
|
|
843
|
+
notes: 'no contextSlots assertion — the slot tracking the watched task (taskStatus, …) is intent-specific; capabilityHooks pins the real check (the useNotifications gadget).',
|
|
844
|
+
tolerateEitherShape: true,
|
|
845
|
+
},
|
|
846
|
+
},
|
|
847
|
+
// ─── source-fed-stream: agentCapabilities-driven live channels (EE-runtime) ─
|
|
848
|
+
// The synth should declare `streamSpec[X].source = {tool, args?}` and
|
|
849
|
+
// a matching `agentCapabilities.tools[<source.tool>]` catalog entry. The
|
|
850
|
+
// runtime decides transport (WebSocket subscribe vs polling); the
|
|
851
|
+
// contract doesn't care. Tolerated until the prompt revision (subtask
|
|
852
|
+
// 5e) lands.
|
|
853
|
+
{
|
|
854
|
+
id: 'src-stream-ticker-1',
|
|
855
|
+
intent: 'a live-refreshing AAPL stock ticker',
|
|
856
|
+
expected: {
|
|
857
|
+
hasActionSpec: false,
|
|
858
|
+
hasContextSpec: false,
|
|
859
|
+
hasStreamSpec: true,
|
|
860
|
+
hasProps: false,
|
|
861
|
+
hasAgentTools: true,
|
|
862
|
+
agentToolNames: ['fetch_quote', 'get_quote', 'list_quotes', 'quote'],
|
|
863
|
+
tolerateEitherShape: true,
|
|
864
|
+
notes: 'ticker streams price updates; the agent-side tool feeds the channel. No actionSpec because nothing the user does drives a turn.',
|
|
865
|
+
},
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
id: 'src-stream-dashboard-1',
|
|
869
|
+
intent: 'a real-time dashboard of incoming chat messages',
|
|
870
|
+
expected: {
|
|
871
|
+
hasActionSpec: false,
|
|
872
|
+
hasContextSpec: false,
|
|
873
|
+
hasStreamSpec: true,
|
|
874
|
+
hasProps: false,
|
|
875
|
+
hasAgentTools: true,
|
|
876
|
+
agentToolNames: ['list_messages', 'fetch_messages', 'messages'],
|
|
877
|
+
tolerateEitherShape: true,
|
|
878
|
+
},
|
|
879
|
+
},
|
|
880
|
+
{
|
|
881
|
+
id: 'src-stream-polling-table-1',
|
|
882
|
+
intent: "a polling table of pending jobs that refreshes every few seconds",
|
|
883
|
+
expected: {
|
|
884
|
+
hasActionSpec: false,
|
|
885
|
+
hasContextSpec: false,
|
|
886
|
+
hasStreamSpec: true,
|
|
887
|
+
hasProps: false,
|
|
888
|
+
hasAgentTools: true,
|
|
889
|
+
agentToolNames: ['list_jobs', 'fetch_jobs', 'jobs'],
|
|
890
|
+
tolerateEitherShape: true,
|
|
891
|
+
notes: 'polling table needs a source-fed stream; agent-tool name varies by intent vocabulary.',
|
|
892
|
+
},
|
|
893
|
+
},
|
|
894
|
+
// ─── capability-plugin: operator-registered 3rd-party wrappers ──────────────
|
|
895
|
+
// STDLIB capability cases above test the 7 first-party hooks the
|
|
896
|
+
// synth's static system prompt always knows about. These cases test
|
|
897
|
+
// the per-app `appGadgets` channel — does the synth READ the
|
|
898
|
+
// "AVAILABLE GADGETS" prompt block emitted by
|
|
899
|
+
// `composeAvailableGadgetsSection` when an operator has registered
|
|
900
|
+
// a wrapper at `App.gadgets`? Three shapes:
|
|
901
|
+
//
|
|
902
|
+
// - **positive**: intent matches the registered wrapper → expect
|
|
903
|
+
// `clientCapabilities.gadgets[*].hook` to reference it.
|
|
904
|
+
// - **negative**: intent doesn't justify a map at all → expect NO
|
|
905
|
+
// `clientCapabilities.gadgets` (registry shouldn't trigger
|
|
906
|
+
// attaching a wrapper that doesn't fit).
|
|
907
|
+
// - **distractor**: intent could go either way → forbid the
|
|
908
|
+
// wrapper unless its semantics are an exact match. Catches the
|
|
909
|
+
// "LLM attaches everything in the catalog regardless" failure.
|
|
910
|
+
//
|
|
911
|
+
// `tolerateEitherShape: true` on all entries — the synth's catalog-
|
|
912
|
+
// reading behavior is relatively new, so these cases would fail
|
|
913
|
+
// strictly until it is fully tuned. The bench captures the baseline
|
|
914
|
+
// + regression signal without breaking CI.
|
|
915
|
+
{
|
|
916
|
+
id: 'capplug-leaflet-positive-1',
|
|
917
|
+
intent: 'show my running route from this morning on an interactive map with the start and end pinned',
|
|
918
|
+
expected: {
|
|
919
|
+
hasActionSpec: false,
|
|
920
|
+
hasContextSpec: false,
|
|
921
|
+
hasStreamSpec: false,
|
|
922
|
+
hasProps: true,
|
|
923
|
+
hasClientCapabilities: true,
|
|
924
|
+
capabilityHooks: ['LeafletMap'],
|
|
925
|
+
tolerateEitherShape: true,
|
|
926
|
+
notes: 'positive: registered Leaflet wrapper should be referenced when the intent names an interactive map render.',
|
|
927
|
+
},
|
|
928
|
+
appGadgets: [LEAFLET_DESCRIPTOR],
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
id: 'capplug-leaflet-positive-2',
|
|
932
|
+
intent: 'plot the delivery destinations for the day with a marker per stop on a map',
|
|
933
|
+
expected: {
|
|
934
|
+
hasActionSpec: false,
|
|
935
|
+
hasContextSpec: false,
|
|
936
|
+
hasStreamSpec: false,
|
|
937
|
+
hasProps: true,
|
|
938
|
+
hasClientCapabilities: true,
|
|
939
|
+
capabilityHooks: ['LeafletMap'],
|
|
940
|
+
tolerateEitherShape: true,
|
|
941
|
+
},
|
|
942
|
+
appGadgets: [LEAFLET_DESCRIPTOR],
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
id: 'capplug-leaflet-negative-1',
|
|
946
|
+
intent: 'let me edit my profile name, email, and avatar in a simple form',
|
|
947
|
+
expected: {
|
|
948
|
+
hasActionSpec: true,
|
|
949
|
+
hasContextSpec: true,
|
|
950
|
+
hasStreamSpec: false,
|
|
951
|
+
hasProps: false,
|
|
952
|
+
hasClientCapabilities: false,
|
|
953
|
+
forbiddenCapabilityHooks: ['LeafletMap'],
|
|
954
|
+
tolerateEitherShape: true,
|
|
955
|
+
notes: 'negative: form-editing intent has no map surface; LLM must NOT attach the registered wrapper just because it appears in the catalog.',
|
|
956
|
+
},
|
|
957
|
+
appGadgets: [LEAFLET_DESCRIPTOR],
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
id: 'capplug-leaflet-distractor-1',
|
|
961
|
+
intent: 'show me the GPS coordinates of my current position as a labelled lat/lng pair',
|
|
962
|
+
expected: {
|
|
963
|
+
hasActionSpec: false,
|
|
964
|
+
hasContextSpec: true,
|
|
965
|
+
hasStreamSpec: false,
|
|
966
|
+
hasProps: false,
|
|
967
|
+
hasClientCapabilities: true,
|
|
968
|
+
capabilityHooks: ['useGeolocation'],
|
|
969
|
+
forbiddenCapabilityHooks: ['LeafletMap'],
|
|
970
|
+
contextSlots: ['coords', 'position', 'location', 'latitude', 'longitude'],
|
|
971
|
+
tolerateEitherShape: true,
|
|
972
|
+
notes: 'distractor: GPS-coords intent needs `useGeolocation` (STDLIB) but does NOT need the registered Leaflet wrapper — render is text, not map.',
|
|
973
|
+
},
|
|
974
|
+
appGadgets: [LEAFLET_DESCRIPTOR],
|
|
975
|
+
},
|
|
976
|
+
{
|
|
977
|
+
id: 'capplug-chart-hook-positive-1',
|
|
978
|
+
intent: 'a bar chart of quarterly revenue whose bar colors follow the current app theme',
|
|
979
|
+
expected: {
|
|
980
|
+
hasActionSpec: false,
|
|
981
|
+
hasContextSpec: false,
|
|
982
|
+
hasStreamSpec: false,
|
|
983
|
+
hasProps: true,
|
|
984
|
+
hasClientCapabilities: true,
|
|
985
|
+
// Registered third-party HOOK export — the dual of the Leaflet
|
|
986
|
+
// component cases. The `Chart` component may also be declared;
|
|
987
|
+
// this case pins that the registered HOOK is reached for.
|
|
988
|
+
capabilityHooks: ['useChartTheme'],
|
|
989
|
+
tolerateEitherShape: true,
|
|
990
|
+
notes: 'positive: registered third-party HOOK gadget. A theme-aware chart intent should pull the `useChartTheme` hook export from the registered chart package — the component-only Leaflet cases never exercise a registered hook.',
|
|
991
|
+
},
|
|
992
|
+
appGadgets: [CHART_DESCRIPTOR],
|
|
993
|
+
},
|
|
994
|
+
];
|