@ggui-ai/ui-gen 0.24.0 → 0.26.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/README.md +2 -2
- package/dist/{adapter-Du7P_5Fx.d.ts → adapter-BJwn74rx.d.ts} +49 -1
- package/dist/adapters/generation-dispatch.d.ts +32 -7
- package/dist/adapters/generation-dispatch.js +1997 -216
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +29 -6
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +15 -12
- package/dist/advanced/index.js +2013 -270
- package/dist/advanced/index.js.map +1 -1
- package/dist/{axes-CzLEMDeB.d.ts → axes-CKVCzR_x.d.ts} +1 -1
- package/dist/bank-Ctdp3FUA.d.ts +118 -0
- package/dist/blueprint-validator.js +416 -92
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.d.ts +2 -2
- package/dist/boilerplate.js +34 -15
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +13 -5
- package/dist/check/index.js +56 -3
- package/dist/check/index.js.map +1 -1
- package/dist/classifier/index.d.ts +2 -2
- package/dist/compose.d.ts +2 -2
- package/dist/{create-ui-generator-GIhzx08x.d.ts → create-ui-generator-N4gRXpcL.d.ts} +11 -4
- package/dist/{design-mode-DLufhrNp.d.ts → design-mode-DGWN_Gkm.d.ts} +1 -1
- package/dist/{dispatch-BnAH56p4.d.ts → dispatch-BUoXRj_r.d.ts} +6 -4
- package/dist/evaluation/axis-checks/index.d.ts +7 -6
- package/dist/evaluation/axis-checks/index.js +90 -2
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +6 -5
- package/dist/evaluation/axis-checks/registry.js +88 -1
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +189 -344
- package/dist/evaluation/index.js +1272 -59
- package/dist/evaluation/index.js.map +1 -1
- package/dist/evaluation/types.d.ts +26 -1
- package/dist/evaluation/types.js.map +1 -1
- package/dist/fragments/index.d.ts +3 -3
- package/dist/{generate-9IeNT4ZY.d.ts → generate-tSO_shQ0.d.ts} +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +7 -7
- package/dist/harness/check/runtime-render/index.js +416 -92
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/check/runtime-render/render-check-worker.js +393 -88
- package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
- package/dist/harness/index.d.ts +5 -6
- package/dist/harness/index.js +69 -20
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/prompts.d.ts +2 -2
- package/dist/harness/prompts.js +10 -1
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +19 -5
- package/dist/harness/result-types.js.map +1 -1
- package/dist/harness/runtime.d.ts +21 -7
- package/dist/harness/runtime.js +36 -17
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +6 -4
- package/dist/harness/types-public.js +43 -18
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +6 -5
- package/dist/hash.d.ts +1 -1
- package/dist/index.d.ts +64 -13
- package/dist/index.js +2023 -270
- package/dist/index.js.map +1 -1
- package/dist/patch.d.ts +5 -5
- package/dist/policy.d.ts +1 -1
- package/dist/{prompts-YU9piOgs.d.ts → prompts-BhNUuRoZ.d.ts} +1 -1
- package/dist/tools/get-primitives-ts.d.ts +1 -1
- package/dist/tools/get-primitives-ts.js +1 -1
- package/dist/tools/get-primitives-ts.js.map +1 -1
- package/dist/tools/render-check-worker.js +8 -1
- package/dist/tools/render-check-worker.js.map +1 -1
- package/dist/{types-BOvHNG7K.d.ts → types-Dh1s4K9j.d.ts} +1 -1
- package/dist/types-public-B7wn4ce5.d.ts +959 -0
- package/dist/validation/index.d.ts +1 -1
- package/dist/validation/index.js +1 -1
- package/dist/validation/index.js.map +1 -1
- package/dist/visual-evaluator-C9L-o6ap.d.ts +639 -0
- package/dist/workflows.d.ts +5 -5
- package/package.json +12 -12
- package/src/adapters/generation-dispatch.ts +38 -2
- package/src/adapters/tools.ts +2 -1
- package/src/boilerplate/free-design-prompt.ts +4 -4
- package/src/boilerplate/generate.ts +26 -1
- package/src/boilerplate/hard-sections.ts +14 -14
- package/src/boilerplate/system-prompt.ts +1 -1
- package/src/boilerplate/templates/layouts/chat-mobile.tsx.tmpl +3 -1
- package/src/boilerplate/templates/layouts/chat-universal.tsx.tmpl +3 -1
- package/src/boilerplate/templates/layouts/fullscreen-desktop.tsx.tmpl +3 -1
- package/src/boilerplate/templates/layouts/fullscreen-universal.tsx.tmpl +3 -1
- package/src/check/index.ts +1 -0
- package/src/check/run-tier0.ts +6 -3
- package/src/check/sample-props.ts +63 -0
- package/src/coding-agent/tools.ts +4 -1
- package/src/contract-context.ts +1 -1
- package/src/create-harness.ts +3 -3
- package/src/create-ui-generator.ts +20 -1
- package/src/evaluation/__fixtures__/png.ts +100 -0
- package/src/evaluation/axis-checks/checks/state-merge.ts +1 -1
- package/src/evaluation/axis-checks/checks/universal.ts +111 -0
- package/src/evaluation/axis-checks/dispatch.ts +4 -1
- package/src/evaluation/cost-tracker.ts +54 -6
- package/src/evaluation/criteria/bank.ts +85 -0
- package/src/evaluation/criteria/context.ts +52 -0
- package/src/evaluation/criteria/edge-probe.ts +286 -0
- package/src/evaluation/criteria/edge.ts +202 -0
- package/src/evaluation/criteria/resolve.ts +207 -0
- package/src/evaluation/criteria/select.ts +79 -0
- package/src/evaluation/index.ts +27 -0
- package/src/evaluation/ink-extent.ts +200 -0
- package/src/evaluation/judge-spend.ts +48 -0
- package/src/evaluation/theme-css.ts +12 -5
- package/src/evaluation/types-public.ts +221 -3
- package/src/evaluation/types.ts +19 -0
- package/src/evaluation/visual-evaluator.ts +853 -51
- package/src/harness/check/runtime-render/adapter.ts +18 -0
- package/src/harness/check/runtime-render/probe-pending.ts +111 -0
- package/src/harness/check/runtime-render/render-check-host.ts +10 -6
- package/src/harness/check/runtime-render/render-check-worker.ts +6 -3
- package/src/harness/check/runtime-render/render-check.ts +417 -133
- package/src/harness/check/runtime-render/walk.ts +200 -0
- package/src/harness/coding/assemble-result.ts +2 -0
- package/src/harness/coding/generate-task-runner.ts +30 -0
- package/src/harness/coding/init-session.ts +5 -3
- package/src/harness/coding/run-coding-turn.ts +12 -4
- package/src/harness/coding/run-eval-round.ts +82 -3
- package/src/harness/in-loop-evaluation.ts +39 -0
- package/src/harness/llm-router.ts +58 -1
- package/src/harness/result-types.ts +9 -0
- package/src/harness/runtime.ts +14 -0
- package/src/harness/types-public.ts +10 -1
- package/src/harness-tool-names.ts +38 -0
- package/src/index.ts +17 -0
- package/src/internal/verdict-line.ts +33 -0
- package/src/run-check.ts +2 -0
- package/src/taught-tokens.ts +32 -0
- package/src/tools/get-primitives-ts.ts +1 -1
- package/src/tools/get-wire.ts +1 -1
- package/src/tools/render-check-worker.ts +2 -1
- package/src/tools/render-check.ts +8 -67
- package/src/tools.ts +11 -1
- package/src/validation/primitives.ts +1 -1
- package/dist/llm-router-DwwUK1bz.d.ts +0 -265
- package/dist/types-public-Bk6d1WLN.d.ts +0 -451
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/ui-gen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "UI generation harness for ggui — the LLM system prompt, multi-turn coding loop, and evaluation engine that turn a DataContract into a React component. Implements the UiGenerator contract from @ggui-ai/mcp-server-core; consumed by @ggui-ai/mcp-server.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"keywords": [
|
|
@@ -189,9 +189,9 @@
|
|
|
189
189
|
"puppeteer-core": "^24.40.0 || ^25.0.0",
|
|
190
190
|
"react": "^18.0.0 || ^19.0.0",
|
|
191
191
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
192
|
-
"@ggui-ai/
|
|
193
|
-
"@ggui-ai/protocol": "0.
|
|
194
|
-
"@ggui-ai/
|
|
192
|
+
"@ggui-ai/mcp-server-core": "0.26.0",
|
|
193
|
+
"@ggui-ai/protocol": "0.26.0",
|
|
194
|
+
"@ggui-ai/project-config": "0.26.0"
|
|
195
195
|
},
|
|
196
196
|
"peerDependenciesMeta": {
|
|
197
197
|
"@google/adk": {
|
|
@@ -233,11 +233,11 @@
|
|
|
233
233
|
"openai": "^7.9.0",
|
|
234
234
|
"typescript": "^5.0.0",
|
|
235
235
|
"zod": "^4.3.6",
|
|
236
|
-
"@ggui-ai/design": "0.
|
|
237
|
-
"@ggui-ai/gadgets": "0.
|
|
238
|
-
"@ggui-ai/
|
|
239
|
-
"@ggui-ai/
|
|
240
|
-
"@ggui-ai/wire": "0.
|
|
236
|
+
"@ggui-ai/design": "0.26.0",
|
|
237
|
+
"@ggui-ai/gadgets": "0.26.0",
|
|
238
|
+
"@ggui-ai/ui-visual-tester": "0.26.0",
|
|
239
|
+
"@ggui-ai/sandbox": "0.26.0",
|
|
240
|
+
"@ggui-ai/wire": "0.26.0"
|
|
241
241
|
},
|
|
242
242
|
"devDependencies": {
|
|
243
243
|
"@google/adk": "^2.0.0",
|
|
@@ -252,9 +252,9 @@
|
|
|
252
252
|
"react-dom": "^19.0.0",
|
|
253
253
|
"tsup": "^8.5.0",
|
|
254
254
|
"vitest": "^3.2.6",
|
|
255
|
-
"@ggui-ai/
|
|
256
|
-
"@ggui-ai/
|
|
257
|
-
"@ggui-ai/protocol": "0.
|
|
255
|
+
"@ggui-ai/project-config": "0.26.0",
|
|
256
|
+
"@ggui-ai/mcp-server-core": "0.26.0",
|
|
257
|
+
"@ggui-ai/protocol": "0.26.0"
|
|
258
258
|
},
|
|
259
259
|
"engines": {
|
|
260
260
|
"node": ">=22.0.0"
|
|
@@ -60,8 +60,22 @@ import type { QualityConfig } from "../evaluation/types-public.js";
|
|
|
60
60
|
import type { AgentConfig } from "../harness/llm-router.js";
|
|
61
61
|
import { type GadgetDescriptor, type JsonObject } from "@ggui-ai/protocol";
|
|
62
62
|
import { canvasForRendering, type CanvasClass, type DesignMode } from "../design-mode.js";
|
|
63
|
+
import type { VisionProvider } from "../harness/llm-router.js";
|
|
64
|
+
import type { CriteriaBank } from "../evaluation/criteria/bank.js";
|
|
63
65
|
import type { GenerationProfileInput } from "../boilerplate/styling-profile.js";
|
|
64
66
|
|
|
67
|
+
/**
|
|
68
|
+
* ggui#1250 — the in-loop VISUAL judge as its own agent: a vision provider (typed so an OpenAI judge cannot
|
|
69
|
+
* be written), a model, and — when the caller resolves provider keys itself — that provider's key, which
|
|
70
|
+
* becomes the agent's own `routeOverride`. Never the coding agent's route: where this exists (an OpenAI
|
|
71
|
+
* coding lane), the coding key is another provider's.
|
|
72
|
+
*/
|
|
73
|
+
export interface VisualEvalAgentSpec {
|
|
74
|
+
readonly provider: VisionProvider;
|
|
75
|
+
readonly model: string;
|
|
76
|
+
readonly apiKey?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
65
79
|
export interface GenerationDispatchParams {
|
|
66
80
|
provider: ProviderName;
|
|
67
81
|
model: string;
|
|
@@ -109,6 +123,11 @@ export interface GenerationDispatchParams {
|
|
|
109
123
|
* the same box. See `VisualEvalConfig.canvasViewports`.
|
|
110
124
|
*/
|
|
111
125
|
canvasViewports?: Partial<Readonly<Record<CanvasClass, { width: number; height: number }>>>;
|
|
126
|
+
/** ggui#1436 — see `SingleComponentParams.visualEvaluation.criteriaBank`. */
|
|
127
|
+
criteriaBank?: CriteriaBank;
|
|
128
|
+
criteriaKind?: string;
|
|
129
|
+
/** ggui#1542 — see `SingleComponentParams.visualEvaluation.actOn`. */
|
|
130
|
+
actOn?: "critical" | "major";
|
|
112
131
|
};
|
|
113
132
|
/** Quality config controlling eval tiers and improvement behavior */
|
|
114
133
|
qualityConfig?: QualityConfig;
|
|
@@ -190,6 +209,13 @@ export interface GenerationDispatchParams {
|
|
|
190
209
|
* existing behavior for callers that don't set it.
|
|
191
210
|
*/
|
|
192
211
|
routeOverride?: AgentConfig["routeOverride"];
|
|
212
|
+
/**
|
|
213
|
+
* ggui#1250 — the in-loop visual judge named as its own agent (see {@link VisualEvalAgentSpec}).
|
|
214
|
+
* Absent (default, every existing caller): today's chain, `visualEval ← evaluation ← coding`, which on
|
|
215
|
+
* a lane without a vision path records the score half as skipped (#1248). Off by default; a measurement
|
|
216
|
+
* knob first, a product knob only by ruling.
|
|
217
|
+
*/
|
|
218
|
+
visualEvalAgent?: VisualEvalAgentSpec;
|
|
193
219
|
/**
|
|
194
220
|
* Retry observer (#489) — see `AgentConfig.onRetry`. Threaded onto
|
|
195
221
|
* the coding + evaluation agent specs so a rate-limited retry inside
|
|
@@ -268,8 +294,17 @@ export async function dispatchGeneration(
|
|
|
268
294
|
onRetry: params.onRetry,
|
|
269
295
|
}
|
|
270
296
|
: undefined;
|
|
271
|
-
//
|
|
272
|
-
//
|
|
297
|
+
// ggui#1250 — the in-loop visual judge as its own agent when named: its own provider and its own key
|
|
298
|
+
// route, never the coding agent's. Absent: today's chain, visualEval ← evaluation ← coding, inside
|
|
299
|
+
// resolveSessionAgents (ModelRoles has no visualEval slot — a role is a model, and this needs a provider).
|
|
300
|
+
const visualEvalAgent = params.visualEvalAgent
|
|
301
|
+
? {
|
|
302
|
+
provider: params.visualEvalAgent.provider,
|
|
303
|
+
model: params.visualEvalAgent.model,
|
|
304
|
+
...(params.visualEvalAgent.apiKey !== undefined ? { routeOverride: { apiKey: params.visualEvalAgent.apiKey } } : {}),
|
|
305
|
+
onRetry: params.onRetry,
|
|
306
|
+
}
|
|
307
|
+
: undefined;
|
|
273
308
|
|
|
274
309
|
// ── Contract enrichment ──────────────────────────────────────────────
|
|
275
310
|
// The contract is used as-is: `clientCapabilities.gadgets`
|
|
@@ -426,6 +461,7 @@ export async function dispatchGeneration(
|
|
|
426
461
|
const agents = resolveSessionAgents({
|
|
427
462
|
codingAgent,
|
|
428
463
|
evaluationAgent,
|
|
464
|
+
...(visualEvalAgent !== undefined ? { visualEvalAgent } : {}),
|
|
429
465
|
});
|
|
430
466
|
|
|
431
467
|
// ── Runtime policy resolution ──
|
package/src/adapters/tools.ts
CHANGED
|
@@ -20,7 +20,8 @@ import { validateAllContracts } from '../check/index.js';
|
|
|
20
20
|
import { typecheck } from '../check/index.js';
|
|
21
21
|
import { lintReactHooks } from '../check/index.js';
|
|
22
22
|
import ts from 'typescript';
|
|
23
|
-
import { tryRender
|
|
23
|
+
import { tryRender } from '../tools/render-check.js';
|
|
24
|
+
import { generateSampleProps } from '../check/sample-props.js';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Context for creating generator tools.
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// for raw elements · the one-gesture-surface invariant restated for raw
|
|
19
19
|
// elements · a regenerated quality checklist.
|
|
20
20
|
|
|
21
|
-
import {
|
|
21
|
+
import { taughtTokenManifest } from "../taught-tokens.js";
|
|
22
22
|
import { describeCanvas, type CanvasClass } from "../design-mode.js";
|
|
23
23
|
import {
|
|
24
24
|
ANTI_PATTERNS,
|
|
@@ -136,7 +136,7 @@ function renderRamp(r: ColorRamp): string {
|
|
|
136
136
|
* every role the prose names exists on `:root`; nothing is implied by a
|
|
137
137
|
* `50…800` range that the manifest does not carry step for step.
|
|
138
138
|
*/
|
|
139
|
-
export function renderFreeColorRule(manifest: readonly string[] =
|
|
139
|
+
export function renderFreeColorRule(manifest: readonly string[] = taughtTokenManifest): string {
|
|
140
140
|
const { ramps, roles } = colorVocabularyFromManifest(manifest);
|
|
141
141
|
const brand = ramps.filter((r) => r.family === "primary" || r.family === "neutral");
|
|
142
142
|
const state = ramps.filter((r) => r.family !== "primary" && r.family !== "neutral");
|
|
@@ -212,7 +212,7 @@ With raw elements YOU own the semantics — nothing is accessible by constructio
|
|
|
212
212
|
3. **Images have \`alt\`** (\`alt=""\` for decorative). Icon-only buttons need \`aria-label\`; decorative icons and emoji are \`aria-hidden="true"\`.
|
|
213
213
|
4. **Live & streaming data** (a \`useStream\` \`.latest\` value, counters, clocks, flashing prices) sits in an \`aria-live="polite"\` (or \`role="status"\`) region so screen readers announce the change.
|
|
214
214
|
5. **Headings nest**: one \`<h1>\` per screen, \`<h2>\` for sections, \`<h3>\` below — never skipped or inverted.
|
|
215
|
-
6. **Busy state is announced**: \`<button disabled={
|
|
215
|
+
6. **Busy state is announced**: every control that dispatches an action (a button, a select whose change dispatches, an input that sends on Enter) stays pending from \`useActionPending('<action>')\` until the agent's answer repaints the card — \`<button disabled={sendPending} aria-busy={sendPending}>{sendPending ? 'Sending…' : 'Send'}</button>\`. Only a control that dispatches the action carries its busy look; while it is pending, other controls of the same record may be disabled — nothing more (controls of other records, creation controls and the action's own cancel stay enabled); busy state the card owns (\`isLoading\`) the same way.
|
|
216
216
|
7. **Stateful controls announce state**: \`role="checkbox"\` + \`aria-checked\` on a toggleable row, \`aria-pressed\` on a toggle button, \`aria-selected\` on the chosen item, \`aria-expanded\` on an open/close affordance. Styling (strikethrough, color, a check mark) is never the only carrier of state.
|
|
217
217
|
8. **Focus is visible** — style \`:focus-visible\` with a token color; never \`outline: none\` without a replacement.`;
|
|
218
218
|
|
|
@@ -260,7 +260,7 @@ const TOKEN_GROUPS: readonly TokenGroup[] = [
|
|
|
260
260
|
* does not define. Grouped by prefix; anything outside the known
|
|
261
261
|
* prefixes lands under "Other".
|
|
262
262
|
*/
|
|
263
|
-
export function renderTokenVocabulary(manifest: readonly string[] =
|
|
263
|
+
export function renderTokenVocabulary(manifest: readonly string[] = taughtTokenManifest): string {
|
|
264
264
|
const sorted = [...manifest].sort();
|
|
265
265
|
const claimed = new Set<string>();
|
|
266
266
|
const lines: string[] = [
|
|
@@ -230,6 +230,7 @@ export function generateBoilerplate(
|
|
|
230
230
|
const actionTypeAliases: string[] = [];
|
|
231
231
|
const actionHookCalls: string[] = [];
|
|
232
232
|
const actionReturnFields: string[] = [];
|
|
233
|
+
let hasOneShotAction = false;
|
|
233
234
|
const actionsMap: ActionSpec = contract?.actionSpec ?? {};
|
|
234
235
|
for (const [key, entry] of Object.entries(actionsMap)) {
|
|
235
236
|
const label = entry.label ?? key;
|
|
@@ -274,7 +275,29 @@ export function generateBoilerplate(
|
|
|
274
275
|
// instruction for every action. So it stays, ALONE on the hook line — what
|
|
275
276
|
// sits next to it is part of what it says.
|
|
276
277
|
const onceHint = " — if this is meant ONCE, disable its control after it fires";
|
|
277
|
-
|
|
278
|
+
// ggui#1223 (rnd reliability/013, arm C): an action the contract DECLARES
|
|
279
|
+
// `oneShot` gets its spent state as data, pre-bound beside the dispatcher —
|
|
280
|
+
// WHAT to code, beside HOW — and the generic once-hint leaves ITS line only:
|
|
281
|
+
// left there, the hint steered the model to a local `useState(false)` guard
|
|
282
|
+
// against the prompt's block (arm B: 0 of 6 declared cells used the hook).
|
|
283
|
+
// The generic hint stays verbatim on every undeclared action.
|
|
284
|
+
if (entry.oneShot === true) {
|
|
285
|
+
hasOneShotAction = true;
|
|
286
|
+
actionHookCalls.push(
|
|
287
|
+
` const ${key} = useAction<${typeName}>('${key}'); // ${callSig}${toolHint} — declared oneShot: the runtime drops a second dispatch`,
|
|
288
|
+
);
|
|
289
|
+
actionHookCalls.push(
|
|
290
|
+
` const ${key}Spent = useActionSpent('${key}'); // true once fired, also after a reload — render the control from THIS: disabled={${key}Spent} and a spent label`,
|
|
291
|
+
);
|
|
292
|
+
} else {
|
|
293
|
+
actionHookCalls.push(` const ${key} = useAction<${typeName}>('${key}'); // ${callSig}${toolHint}${onceHint}`);
|
|
294
|
+
}
|
|
295
|
+
// ggui#1398 — the dispatch's in-flight state, bound at the site the model
|
|
296
|
+
// writes the control (reliability/013: a rule alone reached 1/6, the
|
|
297
|
+
// binding 6/6).
|
|
298
|
+
actionHookCalls.push(
|
|
299
|
+
` const ${key}Pending = useActionPending('${key}'); // true from the tap until the agent's answer repaints the card — render EVERY control that dispatches '${key}' (a Button, a Select's onChange, an Input's Enter) from THIS: disabled={${key}Pending} aria-busy={${key}Pending}, and a working label on a button; no other control carries its aria-busy`,
|
|
300
|
+
);
|
|
278
301
|
actionReturnFields.push(key);
|
|
279
302
|
}
|
|
280
303
|
|
|
@@ -507,6 +530,8 @@ export function generateBoilerplate(
|
|
|
507
530
|
|
|
508
531
|
const wireHooks: string[] = [];
|
|
509
532
|
if (hasActions) wireHooks.push("useAction");
|
|
533
|
+
if (hasOneShotAction) wireHooks.push("useActionSpent");
|
|
534
|
+
if (hasActions) wireHooks.push("useActionPending");
|
|
510
535
|
if (hasStream) wireHooks.push("useStream");
|
|
511
536
|
if (hasContext) wireHooks.push("useGguiContext");
|
|
512
537
|
const wireImport = hasAnyWireFromWire
|
|
@@ -214,29 +214,29 @@ Rules:
|
|
|
214
214
|
2. Layout and styling are universal. Colors, spacing, structure — these are the reusable part.
|
|
215
215
|
3. Default values come from the current request — so the component renders correctly standalone.
|
|
216
216
|
4. Copy comes from the props. NEVER invent display text the contract does not carry — no eyebrow / kicker / overline (a \`<Text caps>\` label), no helper line, no status text, no tagline. **Do NOT add a caps label above the heading** — not \`<Text caps>LET'S BEGIN</Text>\`, not \`WELCOME\`, not a section kicker like \`GETTING STARTED\` or \`OVERVIEW\` — UNLESS a prop supplies that exact text (\`<Text caps>{props.section}</Text>\`). When the request asks for a heading and a line, render the heading and the line and NOTHING above the heading. A heading's fallback is the request's own words, never a slogan of your own. Text the agent cannot change is chatter on every render, and a caps eyebrow you wrote is the most common form of it. An action's control shows the contract's \`label\` VERBATIM — every character, including \`&\`, \`/\`, \`–\` and quotes — never paraphrased, shortened or re-cased (\`"Confirm & Schedule"\` is not \`"Confirm Schedule"\`).
|
|
217
|
-
5. Props interface must be typed and exported
|
|
217
|
+
5. Props interface must be typed and exported.
|
|
218
|
+
6. Keys on a mapped list come from the item's own identity: \`key={item.id}\` when the contract gives an id; when the items carry none (KPI tiles \`{label, value}\`, forecast days \`{day, high}\`), key by the field that names each one — \`key={item.label}\`, \`key={day.day}\` — never by the map index: a reorder or a stream merge re-keys by index and re-mounts the wrong rows, and the self-check refuses an index key.`;
|
|
218
219
|
|
|
219
220
|
/**
|
|
220
221
|
* A control that fires an action the user would not mean to repeat must not
|
|
221
|
-
* stay armed (ggui#1108
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
222
|
+
* stay armed (ggui#1108). One-shot-ness is DECLARED on the action
|
|
223
|
+
* (`actionSpec.<name>.oneShot`), enforced by the runtime, and readable by the
|
|
224
|
+
* component through `useActionSpent` (ggui#1223) — so a declared action's
|
|
225
|
+
* control renders from data that survives a reload, and the local-state guard
|
|
226
|
+
* remains only as the fallback for an action the contract failed to declare.
|
|
227
|
+
* Both arms, one HARD section: the contract says which actions are once; where
|
|
228
|
+
* it does not, the request may, and no static rule of ours can infer it from a
|
|
229
|
+
* verb.
|
|
225
230
|
*/
|
|
226
231
|
export const TERMINAL_ACTIONS = `## Actions the user means once
|
|
227
232
|
|
|
228
233
|
A second press is a mistake only when it would make a SECOND THING HAPPEN IN THE WORLD — a second booking, a second charge, a second order, a second application sent. If pressing again would only write the SAME STATE AGAIN — saving, updating, renaming, setting a preference, toggling — nothing is duplicated and the control stays armed.
|
|
229
234
|
|
|
230
|
-
|
|
235
|
+
**The contract says which actions are once.** An action the contract declares \`oneShot: true\` (in the Contract block above) is guarded by the runtime — a second dispatch is dropped — and its spent state is DATA you read, never state you hold: render its control from \`const spent = useActionSpent('<name>')\`, \`disabled={spent}\`, and say so in the label or beside it ("Submitted", "Request sent") — a control that is disabled without explanation reads as broken. The hook's answer survives a reload and a second tab; \`useState(false)\` does not, so a reloaded card built on local state re-arms a control that already fired.
|
|
231
236
|
|
|
232
|
-
**
|
|
237
|
+
**An action the contract does not declare \`oneShot\` stays armed.** Never guard one from a feeling about the verb — "confirm", "submit", "place" alone are not evidence. Guard it only where the request itself names a one-time commitment the contract failed to declare ("submit the application", "place the order"), and then with local state as the fallback: \`const [submitted, setSubmitted] = useState(false)\` set in the handler, \`disabled={submitted}\` with a label — knowing that this state lives with the component, not with the request, and starts fresh on a re-render with new props.
|
|
233
238
|
|
|
234
|
-
When
|
|
235
|
-
- Hold a small piece of state for it and set it in the handler: \`const [submitted, setSubmitted] = useState(false)\`.
|
|
236
|
-
- \`disabled={submitted}\` on the control that fires it, and say so in the label or beside it ("Submitted", "Request sent") — a control that is disabled without explanation reads as broken.
|
|
237
|
-
- A card re-rendered with new props starts fresh; the state above lives with the component, not with the request.
|
|
238
|
-
|
|
239
|
-
Nothing in the wire DECLARES one-shot-ness yet, which is why the test is what a second press would DO — not how final the word sounds.`;
|
|
239
|
+
**When in doubt, leave it armed.** A control the user cannot press a second time has lost them something that worked — save, edit, save again is ordinary. A control left armed is only what every interface does by default.`;
|
|
240
240
|
|
|
241
241
|
/**
|
|
242
242
|
* The frame owns the height — the component never sizes itself to the
|
|
@@ -257,7 +257,7 @@ The width works the same way, and the mistake is the mirror image: **your outerm
|
|
|
257
257
|
|
|
258
258
|
- Never put \`maxWidth\` (or a \`max-w-*\` class) on the element you return at the top. On a wide canvas that turns your card into a narrow strip with the frame's ground on either side of it, and on a narrow one it does nothing — the same composition reading differently at two sizes.
|
|
259
259
|
- DO cap the reading measure inside: a column of prose is easiest to read at 60–70 characters, so wrap the TEXT in a \`<Container maxWidth="sm">\` (or your own \`max-width\`) and leave the root filling. A hero band, a toolbar, a table or an image then still uses the whole width, which is what makes a wide card look composed rather than padded.
|
|
260
|
-
- Centre that inner column with the container's own centring, not by shrinking the root.
|
|
260
|
+
- Centre that inner column with the container's own centring, not by shrinking the root — and give the card ONE column: every block of the reading flow (a heading, its text, and the chips or buttons that answer it) sits in a \`<Container>\` of the same \`maxWidth\`, so their left edges line up even where a full-width band runs between them.
|
|
261
261
|
- **They are two different elements.** Even when the whole card is one column of text, the root still fills and the column sits INSIDE it — \`<Box padding="lg"><Container maxWidth="sm">…</Container></Box>\`, never a root that is itself the container. A centred card is centring inside a filled frame, not a shrunken frame.`;
|
|
262
262
|
|
|
263
263
|
/** Helper-component structure guidance (JSX depth 3–5, helpers above `Component`). Identical in both modes. */
|
|
@@ -469,7 +469,7 @@ The design-system primitives are accessible by construction — they emit their
|
|
|
469
469
|
3. **Icons are decorative by default** — \`<Icon name="check" />\` is hidden from screen readers, which is correct for an icon sitting next to text. Add \`aria-label\` ONLY for a standalone, meaning-bearing icon with no adjacent text. Icon-only \`Button\`s still need \`aria-label\` on the **Button** itself.
|
|
470
470
|
4. **Live & streaming data** — wrap any region whose content updates on its own (a \`useStream\` \`.latest\` value, a live clock, an "N new" counter, a flashing price) in an element with \`aria-live="polite"\` so screen readers announce the change.
|
|
471
471
|
5. **Headings nest** — one \`<Heading level={1}>\` per screen, \`level={2}\` for sections, \`level={3}\` for subsections. Never skip or invert levels.
|
|
472
|
-
6. **Buttons** — descriptive text content; icon-only buttons need \`aria-label\`. Announce busy state: \`<Button disabled={
|
|
472
|
+
6. **Buttons** — descriptive text content; icon-only buttons need \`aria-label\`. Announce busy state. EVERY control that dispatches an action — a Button, a Select whose change dispatches, an Input that sends on Enter — stays pending from that action's \`useActionPending\` until the agent's answer repaints the card: with \`const sendPending = useActionPending('send');\`, write \`<Button disabled={sendPending} aria-busy={sendPending} onClick={() => send({ text })}>{sendPending ? 'Sending…' : 'Send'}</Button>\`, and give a dispatching Select or Input the same \`disabled={sendPending} aria-busy={sendPending}\`. Only a control that dispatches the action carries its \`aria-busy\` and a working label. While it is pending, other controls of the SAME record may be \`disabled\` — nothing more; controls of other records, creation controls and the action's own cancel stay enabled. Busy state the card owns (a local \`isLoading\`) is announced the same way.
|
|
473
473
|
7. **Stateful controls announce their state.** Toggleable / selectable / expandable things use the primitive that carries the semantics (\`Checkbox\`, \`Toggle\`, \`Tabs\`, \`Accordion\`) — never a styled \`Box\`/\`Icon\` that only *looks* checked. When you hand-roll one with \`as={Clickable}\`, \`Clickable\` adds button + keyboard semantics but NOT state — add it yourself: \`role="checkbox"\` + \`aria-checked={done}\` on a toggleable row, \`aria-pressed\` on a toggle button, \`aria-selected\` on the chosen item, \`aria-expanded\` on an open/close affordance. Styling (strikethrough, color, a check icon) must never be the only carrier of state.
|
|
474
474
|
|
|
475
475
|
## Design System Usage (CRITICAL)
|
|
@@ -3,5 +3,7 @@
|
|
|
3
3
|
provides the chrome, so add no shadow or outer radius of your own.
|
|
4
4
|
Compact and natural height; stack vertically. This root fills the
|
|
5
5
|
bubble — wrap PROSE in <Container maxWidth="sm"> to cap the reading
|
|
6
|
-
measure (inert at bubble width, composed on a wider frame)
|
|
6
|
+
measure (inert at bubble width, composed on a wider frame), and put
|
|
7
|
+
the chips or buttons that answer it in a Container of the same
|
|
8
|
+
maxWidth, so every block keeps one left edge. */}
|
|
7
9
|
</Box>
|
|
@@ -3,5 +3,7 @@
|
|
|
3
3
|
provides the chrome, so add no shadow or outer radius of your own.
|
|
4
4
|
Compact and natural height; stack vertically. This root fills the
|
|
5
5
|
bubble — wrap PROSE in <Container maxWidth="sm"> to cap the reading
|
|
6
|
-
measure (inert at bubble width, composed on a wider frame)
|
|
6
|
+
measure (inert at bubble width, composed on a wider frame), and put
|
|
7
|
+
the chips or buttons that answer it in a Container of the same
|
|
8
|
+
maxWidth, so every block keeps one left edge. */}
|
|
7
9
|
</Box>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<Box padding="lg">
|
|
2
2
|
{/* implement your UI here — this root FILLS the frame.
|
|
3
3
|
Bands, toolbars, tables and images use the whole width;
|
|
4
|
-
wrap PROSE in <Container maxWidth="sm"> to cap the reading measure
|
|
4
|
+
wrap PROSE in <Container maxWidth="sm"> to cap the reading measure,
|
|
5
|
+
and put the chips or buttons that answer it in a Container of the
|
|
6
|
+
same maxWidth, so every block keeps one left edge. */}
|
|
5
7
|
</Box>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<Box padding="lg">
|
|
2
2
|
{/* implement your UI here — this root FILLS the frame.
|
|
3
3
|
Bands, toolbars, tables and images use the whole width;
|
|
4
|
-
wrap PROSE in <Container maxWidth="sm"> to cap the reading measure
|
|
4
|
+
wrap PROSE in <Container maxWidth="sm"> to cap the reading measure,
|
|
5
|
+
and put the chips or buttons that answer it in a Container of the
|
|
6
|
+
same maxWidth, so every block keeps one left edge. */}
|
|
5
7
|
</Box>
|
package/src/check/index.ts
CHANGED
package/src/check/run-tier0.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type { DataContract } from '@ggui-ai/protocol';
|
|
|
17
17
|
import { HOOK_NAME_RE, listContractGadgets } from '@ggui-ai/protocol';
|
|
18
18
|
import { consumedTokenManifest } from '@ggui-ai/design/themes';
|
|
19
19
|
import { DEFAULT_DESIGN_MODE, type DesignMode } from '../design-mode.js';
|
|
20
|
+
import { taughtTokenManifest } from '../taught-tokens.js';
|
|
20
21
|
import { validateAllContracts, type ContractIssue } from './contract-validation.js';
|
|
21
22
|
import { typecheck } from './type-checker.js';
|
|
22
23
|
import { lintReactHooks, type ReactLintDiagnostic } from './react-linter.js';
|
|
@@ -315,7 +316,9 @@ const RETIRED_TOKEN_SUCCESSORS: ReadonlyMap<string, string> = new Map([
|
|
|
315
316
|
* (`--ggui-motion-duration-normal` → fast, base, slow); otherwise the names
|
|
316
317
|
* sharing any of its segments of four letters or more
|
|
317
318
|
* (`--ggui-duration-slow` → the motion durations). Empty when nothing is
|
|
318
|
-
* near, so the hint falls back to the manifest's general shape.
|
|
319
|
+
* near, so the hint falls back to the manifest's general shape. A hint
|
|
320
|
+
* teaches, so it draws only on the names the model may be taught
|
|
321
|
+
* (`taughtTokenManifest`); the closed-set check above admits the full manifest.
|
|
319
322
|
*/
|
|
320
323
|
export function suggestManifestTokens(name: string): readonly string[] {
|
|
321
324
|
const successor = RETIRED_TOKEN_SUCCESSORS.get(name);
|
|
@@ -328,7 +331,7 @@ export function suggestManifestTokens(name: string): readonly string[] {
|
|
|
328
331
|
const own = name.endsWith('-') ? segments.length : segments.length - 1;
|
|
329
332
|
for (let n = own; n >= 2; n--) {
|
|
330
333
|
const prefix = `--${segments.slice(0, n).join('-')}-`;
|
|
331
|
-
const family =
|
|
334
|
+
const family = taughtTokenManifest.filter((token) => token.startsWith(prefix));
|
|
332
335
|
if (family.length > 0) return wordHitsFirst(name, rolesFirst(family));
|
|
333
336
|
}
|
|
334
337
|
const words = new Set(
|
|
@@ -338,7 +341,7 @@ export function suggestManifestTokens(name: string): readonly string[] {
|
|
|
338
341
|
.map((segment) => segment.toLowerCase()),
|
|
339
342
|
);
|
|
340
343
|
return rolesFirst(
|
|
341
|
-
|
|
344
|
+
taughtTokenManifest.filter((token) =>
|
|
342
345
|
token.split('-').some((segment) => words.has(segment.toLowerCase())),
|
|
343
346
|
),
|
|
344
347
|
);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Sample props from a PropsSpec — `example` first, then `default`, then a type-driven fill from the JSON
|
|
2
|
+
// Schema. Pure: protocol types only, no sandbox, no node modules, so `@ggui-ai/ui-gen/check` can export it and a
|
|
3
|
+
// caller outside the harness (an experiment's judge input, ggui#1386) synthesizes the SAME props the coding
|
|
4
|
+
// agent's own render check falls back to. Moved verbatim out of `tools/render-check.ts` (2026-09-26).
|
|
5
|
+
import type { JsonObject, JsonSchema, JsonValue, PropsSpec } from '@ggui-ai/protocol';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generate sample props from a PropsSpec contract.
|
|
9
|
+
* Uses `example` values from each PropEntry when available,
|
|
10
|
+
* otherwise synthesizes plausible defaults from the JSON Schema.
|
|
11
|
+
*/
|
|
12
|
+
export function generateSampleProps(spec: PropsSpec): JsonObject {
|
|
13
|
+
const props: JsonObject = {};
|
|
14
|
+
|
|
15
|
+
for (const [name, entry] of Object.entries(spec.properties)) {
|
|
16
|
+
// Prefer explicit example value
|
|
17
|
+
if (entry.example !== undefined) {
|
|
18
|
+
props[name] = entry.example;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Prefer explicit default value
|
|
23
|
+
if (entry.default !== undefined) {
|
|
24
|
+
props[name] = entry.default;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Synthesize from JSON Schema
|
|
29
|
+
props[name] = synthesizeFromSchema(entry.schema);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return props;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function synthesizeFromSchema(schema: JsonSchema): JsonValue | undefined {
|
|
36
|
+
// Draft-07 type arrays (`['string','null']`) synthesize from the
|
|
37
|
+
// first non-null member — the canonical nullability form the
|
|
38
|
+
// enforced-props-schema emission produces.
|
|
39
|
+
const declared = Array.isArray(schema.type)
|
|
40
|
+
? schema.type.find((t) => t !== 'null')
|
|
41
|
+
: schema.type;
|
|
42
|
+
switch (declared) {
|
|
43
|
+
case 'string': return 'sample';
|
|
44
|
+
case 'number':
|
|
45
|
+
case 'integer': return 0;
|
|
46
|
+
case 'boolean': return false;
|
|
47
|
+
case 'null': return null;
|
|
48
|
+
case 'array': {
|
|
49
|
+
if (schema.items) return [synthesizeFromSchema(schema.items) ?? null];
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
case 'object': {
|
|
53
|
+
const obj: JsonObject = {};
|
|
54
|
+
if (schema.properties) {
|
|
55
|
+
for (const [k, v] of Object.entries(schema.properties)) {
|
|
56
|
+
obj[k] = synthesizeFromSchema(v);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return obj;
|
|
60
|
+
}
|
|
61
|
+
default: return undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -17,7 +17,7 @@ import type { AxisCheck, AxisCheckInput } from '../evaluation/types-public.js';
|
|
|
17
17
|
import type { Classification } from '../classifier/axes.js';
|
|
18
18
|
import type { CanvasClass } from '../design-mode.js';
|
|
19
19
|
import { PRIMITIVES_DOCUMENTATION } from '../validation/index.js';
|
|
20
|
-
import type { DataContract } from '@ggui-ai/protocol';
|
|
20
|
+
import type { DataContract, JsonObject } from '@ggui-ai/protocol';
|
|
21
21
|
import type {
|
|
22
22
|
ToolResult,
|
|
23
23
|
CommitMetadata,
|
|
@@ -64,6 +64,8 @@ export interface AutoCommitAxisChecks {
|
|
|
64
64
|
readonly originalPrompt: string;
|
|
65
65
|
/** The canvas the harness was built for (ggui#1117); absent = no rendering context, never a default. */
|
|
66
66
|
readonly canvas?: CanvasClass;
|
|
67
|
+
/** The props the probe and judge render with (ggui#1285); absent = the fixture-echo check stands down. */
|
|
68
|
+
readonly fixtureProps?: JsonObject;
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
async function autoCommit(
|
|
@@ -161,6 +163,7 @@ async function autoCommit(
|
|
|
161
163
|
classification: axis.classification,
|
|
162
164
|
designMode,
|
|
163
165
|
...(axis.canvas !== undefined ? { canvas: axis.canvas } : {}),
|
|
166
|
+
...(axis.fixtureProps !== undefined ? { fixtureProps: axis.fixtureProps } : {}),
|
|
164
167
|
}
|
|
165
168
|
: undefined;
|
|
166
169
|
if (axisFacts !== undefined && !buildSuccess) traceAxisChecksSkipped(axisFacts, 'build failed — no check ran');
|
package/src/contract-context.ts
CHANGED
|
@@ -59,7 +59,7 @@ const SHELL_HINTS: Record<string, string> = {
|
|
|
59
59
|
- Padding: use var(--ggui-spacing-2) for inner elements, var(--ggui-spacing-4) for sections. Tight.
|
|
60
60
|
- Scrolling: parent scrolls (chat feed) — component should NOT have internal scroll
|
|
61
61
|
- Layout: single column, compact. No sidebar. Stack everything vertically.
|
|
62
|
-
- Width: the root is width: 100% (fills the bubble) — never cap the ROOT. A prose column inside it carries the reading cap (<Container maxWidth="sm">), which is inert at bubble width.`,
|
|
62
|
+
- Width: the root is width: 100% (fills the bubble) — never cap the ROOT. A prose column inside it carries the reading cap (<Container maxWidth="sm">), which is inert at bubble width; the chips or buttons that answer the prose sit in a Container of the same maxWidth, so they share its left edge.`,
|
|
63
63
|
|
|
64
64
|
fullscreen: `**Fullscreen Shell — Takes over entire viewport**
|
|
65
65
|
- Container: the whole viewport, edge to edge — the frame stretches your root to its full height (you never set a viewport height yourself)
|
package/src/create-harness.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { hasProfile } from "./boilerplate/styling-profile.js";
|
|
|
16
16
|
import type { CacheTier } from "./fragments/index.js";
|
|
17
17
|
import { compose } from "./compose.js";
|
|
18
18
|
import { buildSystemPrompt, generateBoilerplate } from "./boilerplate.js";
|
|
19
|
-
import {
|
|
19
|
+
import { HARNESS_CODING_TOOLS, HARNESS_SCOPED_TOOLS } from "./harness-tool-names.js";
|
|
20
20
|
import { defaultApplyPatch } from "./patch.js";
|
|
21
21
|
import { computeHarnessId, computeHarnessName, hashClassification } from "./hash.js";
|
|
22
22
|
import { pickWorkflow } from "./workflows.js";
|
|
@@ -128,8 +128,8 @@ export function createHarness(input: CreateHarnessInput): Harness {
|
|
|
128
128
|
const baseWhat: WhatLeg = {
|
|
129
129
|
boilerplate,
|
|
130
130
|
fragments: whatFragments,
|
|
131
|
-
codingTools: [
|
|
132
|
-
scopedTools: [
|
|
131
|
+
codingTools: [...HARNESS_CODING_TOOLS],
|
|
132
|
+
scopedTools: [...HARNESS_SCOPED_TOOLS],
|
|
133
133
|
applyPatch: defaultPatchFn,
|
|
134
134
|
// Registered gadget catalog — drives the system prompt's gadget
|
|
135
135
|
// table + the boilerplate's direct-import emission. Omitted when no
|
|
@@ -38,6 +38,7 @@ import type {
|
|
|
38
38
|
JsonObject,
|
|
39
39
|
GeneratorId,
|
|
40
40
|
ModelRef,
|
|
41
|
+
AppGenerationProfileEffort,
|
|
41
42
|
} from "@ggui-ai/protocol";
|
|
42
43
|
import { isGeneratorId, modelRefOfRoute } from "@ggui-ai/protocol";
|
|
43
44
|
import type { GadgetCatalogAdapter } from "@ggui-ai/gadgets";
|
|
@@ -58,7 +59,7 @@ import {
|
|
|
58
59
|
parseGeneratorSlug,
|
|
59
60
|
} from "@ggui-ai/mcp-server-core";
|
|
60
61
|
import { createGeneratorTools } from "./adapters/index.js";
|
|
61
|
-
import { dispatchGeneration } from "./adapters/generation-dispatch.js";
|
|
62
|
+
import { dispatchGeneration, type VisualEvalAgentSpec } from "./adapters/generation-dispatch.js";
|
|
62
63
|
import {
|
|
63
64
|
createRuntimeRenderCheck,
|
|
64
65
|
type RuntimeRenderProbeConfig,
|
|
@@ -139,6 +140,13 @@ export interface CreateUiGeneratorOptions {
|
|
|
139
140
|
* prompt or the identity. Default: none — the round runs as today.
|
|
140
141
|
*/
|
|
141
142
|
readonly visualEvaluation?: SingleComponentParams["visualEvaluation"];
|
|
143
|
+
/**
|
|
144
|
+
* ggui#1250 — the in-loop VISUAL judge as its own agent (a vision provider, a model, and that
|
|
145
|
+
* provider's key when the caller resolves keys itself). Absent (default): the judge is the
|
|
146
|
+
* evaluation agent's provider, which on a lane without a vision path records the score half as
|
|
147
|
+
* skipped (#1248). Forwarded verbatim to the dispatch; never part of the prompt or the identity.
|
|
148
|
+
*/
|
|
149
|
+
readonly visualEvalAgent?: VisualEvalAgentSpec;
|
|
142
150
|
/** Quality config controlling eval tiers + improvement behavior. */
|
|
143
151
|
readonly qualityConfig?: QualityConfig;
|
|
144
152
|
/**
|
|
@@ -259,6 +267,7 @@ export function createUiGenerator(options: CreateUiGeneratorOptions = {}): UiGen
|
|
|
259
267
|
maxEvalRounds,
|
|
260
268
|
evaluation,
|
|
261
269
|
visualEvaluation,
|
|
270
|
+
visualEvalAgent,
|
|
262
271
|
qualityConfig,
|
|
263
272
|
gadgetCatalog,
|
|
264
273
|
disableEnvMutation = false,
|
|
@@ -354,6 +363,9 @@ export function createUiGenerator(options: CreateUiGeneratorOptions = {}): UiGen
|
|
|
354
363
|
}
|
|
355
364
|
: undefined;
|
|
356
365
|
|
|
366
|
+
// ggui#1459 — the named level whose dials this generation applied; set
|
|
367
|
+
// where the dials are resolved, read by both metadata arms below.
|
|
368
|
+
let effortRan: AppGenerationProfileEffort | undefined;
|
|
357
369
|
try {
|
|
358
370
|
const tools = createGeneratorTools({
|
|
359
371
|
...(input.contract ? { contract: input.contract } : {}),
|
|
@@ -400,6 +412,7 @@ export function createUiGenerator(options: CreateUiGeneratorOptions = {}): UiGen
|
|
|
400
412
|
// deployment's options for THIS generation; absent ⇒ untouched.
|
|
401
413
|
// Prompt bytes never move with it (dials only).
|
|
402
414
|
const dials = effortDials(input.profile?.effort);
|
|
415
|
+
if (dials !== undefined) effortRan = input.profile?.effort;
|
|
403
416
|
const effectiveMaxAttempts = dials?.maxAttempts ?? maxAttempts;
|
|
404
417
|
const effectiveMaxEvalRounds = dials?.maxEvalRounds ?? maxEvalRounds;
|
|
405
418
|
const effectiveEvaluation =
|
|
@@ -442,6 +455,8 @@ export function createUiGenerator(options: CreateUiGeneratorOptions = {}): UiGen
|
|
|
442
455
|
// ggui#1279 — the shell the declared rendering composes for (chat only; see harnessShellForRendering).
|
|
443
456
|
...(shellType !== undefined ? { shellType } : {}),
|
|
444
457
|
...(input.profile !== undefined ? { profile: input.profile } : {}),
|
|
458
|
+
// ggui#1250 — the named in-loop visual judge, when this generator was built with one.
|
|
459
|
+
...(visualEvalAgent !== undefined ? { visualEvalAgent } : {}),
|
|
445
460
|
// Fixture props reach the in-loop probe + visual round only (never the prompt, never identity).
|
|
446
461
|
...(input.fixtureProps !== undefined ? { fixtureProps: input.fixtureProps } : {}),
|
|
447
462
|
});
|
|
@@ -461,6 +476,8 @@ export function createUiGenerator(options: CreateUiGeneratorOptions = {}): UiGen
|
|
|
461
476
|
attempts: result.turnsUsed,
|
|
462
477
|
// ggui#1280 — the build that made this generation (template keys, never per-request).
|
|
463
478
|
build: generatorBuild(designMode ?? DEFAULT_DESIGN_MODE),
|
|
479
|
+
// ggui#1459 — the level that ran; absent when none was applied.
|
|
480
|
+
...(effortRan !== undefined ? { effort: effortRan } : {}),
|
|
464
481
|
// Prompt-cache counters are provider-specific (Claude reports
|
|
465
482
|
// them; others omit). Pass through truthfully — absent on the
|
|
466
483
|
// adapter result stays absent here, never defaulted to 0.
|
|
@@ -507,6 +524,8 @@ export function createUiGenerator(options: CreateUiGeneratorOptions = {}): UiGen
|
|
|
507
524
|
cacheHit: false,
|
|
508
525
|
// ggui#1280 — a failed generation still names the build that failed.
|
|
509
526
|
build: generatorBuild(designMode ?? DEFAULT_DESIGN_MODE),
|
|
527
|
+
// ggui#1459 — and the level it ran, when one was applied.
|
|
528
|
+
...(effortRan !== undefined ? { effort: effortRan } : {}),
|
|
510
529
|
},
|
|
511
530
|
};
|
|
512
531
|
} finally {
|