@ggui-ai/ui-gen 0.22.0 → 0.24.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/dist/adapter-Du7P_5Fx.d.ts +201 -0
- package/dist/adapters/generation-dispatch.d.ts +33 -12
- package/dist/adapters/generation-dispatch.js +549 -129
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +5 -3
- package/dist/advanced/index.js +601 -144
- package/dist/advanced/index.js.map +1 -1
- package/dist/blueprint-validator.js +178 -54
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.js +1 -0
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +2 -1
- package/dist/check/index.js +47 -1
- package/dist/check/index.js.map +1 -1
- package/dist/{create-ui-generator-BEkzEtNz.d.ts → create-ui-generator-GIhzx08x.d.ts} +34 -10
- package/dist/{dispatch-DJNpQ7DI.d.ts → dispatch-BnAH56p4.d.ts} +1 -1
- package/dist/evaluation/axis-checks/index.d.ts +4 -3
- package/dist/evaluation/axis-checks/index.js +19 -4
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +2 -1
- package/dist/evaluation/axis-checks/registry.js +19 -4
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +4 -3
- package/dist/evaluation/index.js +21 -6
- package/dist/evaluation/index.js.map +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +27 -8
- package/dist/harness/check/runtime-render/index.js +217 -54
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/check/runtime-render/render-check-worker.d.ts +8 -1
- package/dist/harness/check/runtime-render/render-check-worker.js +72 -32
- package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
- package/dist/harness/index.d.ts +2 -1
- package/dist/harness/index.js +3 -2
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +2 -1
- package/dist/harness/runtime.d.ts +2 -1
- package/dist/harness/runtime.js +2 -1
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +18 -5
- package/dist/harness/types-public.js +1 -0
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +2 -1
- package/dist/index.d.ts +38 -3
- package/dist/index.js +592 -144
- package/dist/index.js.map +1 -1
- package/dist/patch.d.ts +2 -1
- package/dist/policy.d.ts +7 -4
- package/dist/policy.js.map +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.d.ts +7 -1
- package/dist/tools/render-check-worker.js +55 -23
- package/dist/tools/render-check-worker.js.map +1 -1
- package/dist/{types-public-mM_-1Yk3.d.ts → types-public-Bk6d1WLN.d.ts} +98 -9
- 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/workflows.d.ts +2 -1
- package/package.json +12 -12
- package/src/adapters/generation-dispatch.ts +137 -96
- package/src/advanced/generator.ts +16 -1
- package/src/blueprint-validator.ts +21 -6
- package/src/boilerplate/hard-sections.ts +1 -0
- package/src/check/run-tier0.ts +80 -1
- package/src/coding-agent/tools.ts +6 -1
- package/src/create-ui-generator.ts +185 -95
- package/src/evaluation/axis-checks/checks/state-payload.ts +29 -4
- package/src/evaluation/types-public.ts +94 -8
- package/src/harness/check/runtime-render/adapter.ts +223 -58
- package/src/harness/check/runtime-render/index.ts +75 -2
- package/src/harness/check/runtime-render/render-check-host.ts +111 -26
- package/src/harness/check/runtime-render/render-check-worker.ts +83 -29
- package/src/harness/check/runtime-render/render-check.ts +173 -117
- package/src/harness/coding/generate-task-runner.ts +57 -4
- package/src/harness/coding/init-session.ts +13 -0
- package/src/harness/coding/run-eval-round.ts +238 -13
- package/src/harness/llm-router.ts +3 -3
- package/src/harness/policy.ts +11 -0
- package/src/harness/primitive-slice.ts +104 -0
- package/src/harness/types-public.ts +17 -5
- package/src/index.ts +4 -0
- package/src/policy.ts +7 -4
- package/src/tools/get-primitives-ts.ts +1 -1
- package/src/tools/render-check-worker.ts +74 -23
- package/src/validation/primitives.ts +1 -1
- package/dist/render-check-C0CqVceF.d.ts +0 -87
|
@@ -416,7 +416,12 @@ export async function executeTool(
|
|
|
416
416
|
changes: normalizedChanges,
|
|
417
417
|
});
|
|
418
418
|
if (!patchResult.ok) {
|
|
419
|
-
|
|
419
|
+
// ggui#1261: the engine refused the patch (overlap, line bounds, missing range) before
|
|
420
|
+
// anything applied. Say so in the log, and call it what it is — PATCH_INVALID, like every
|
|
421
|
+
// other structural refusal in this tool — so the turn is never counted as a self-check
|
|
422
|
+
// failure that did not run.
|
|
423
|
+
console.log(`[coding-agent] apply_changes: REJECTED | ${patchResult.error}`);
|
|
424
|
+
return { result: `PATCH_INVALID: ${patchResult.error}`, error: true };
|
|
420
425
|
}
|
|
421
426
|
const candidate = patchResult.sourceAfter ?? currentFile;
|
|
422
427
|
|
|
@@ -31,66 +31,96 @@
|
|
|
31
31
|
* Callers who want a lightweight single-shot path can build it
|
|
32
32
|
* themselves.
|
|
33
33
|
*/
|
|
34
|
-
import type { SingleComponentParams } from
|
|
34
|
+
import type { SingleComponentParams } from "./harness/runtime.js";
|
|
35
35
|
import type {
|
|
36
36
|
GadgetDescriptor,
|
|
37
37
|
GenerationError,
|
|
38
38
|
JsonObject,
|
|
39
39
|
GeneratorId,
|
|
40
40
|
ModelRef,
|
|
41
|
-
} from
|
|
42
|
-
import { isGeneratorId, modelRefOfRoute } from
|
|
43
|
-
import type { GadgetCatalogAdapter } from
|
|
41
|
+
} from "@ggui-ai/protocol";
|
|
42
|
+
import { isGeneratorId, modelRefOfRoute } from "@ggui-ai/protocol";
|
|
43
|
+
import type { GadgetCatalogAdapter } from "@ggui-ai/gadgets";
|
|
44
44
|
import type {
|
|
45
45
|
GenerationMetadata,
|
|
46
|
+
GenerationRuntimeProbe,
|
|
47
|
+
GenerationRuntimeProbeOutcome,
|
|
48
|
+
GenerationRuntimeProbeRepair,
|
|
46
49
|
GeneratorTier,
|
|
47
50
|
LlmProvider,
|
|
48
51
|
UiGenerateInput,
|
|
49
52
|
UiGenerateResult,
|
|
50
53
|
UiGenerator,
|
|
51
|
-
} from
|
|
54
|
+
} from "@ggui-ai/mcp-server-core";
|
|
52
55
|
import {
|
|
53
56
|
formatGeneratorSlug,
|
|
54
57
|
isValidGeneratorSlug,
|
|
55
58
|
parseGeneratorSlug,
|
|
56
|
-
} from
|
|
57
|
-
import { createGeneratorTools } from
|
|
58
|
-
import { dispatchGeneration } from
|
|
59
|
-
import {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
import
|
|
59
|
+
} from "@ggui-ai/mcp-server-core";
|
|
60
|
+
import { createGeneratorTools } from "./adapters/index.js";
|
|
61
|
+
import { dispatchGeneration } from "./adapters/generation-dispatch.js";
|
|
62
|
+
import {
|
|
63
|
+
createRuntimeRenderCheck,
|
|
64
|
+
type RuntimeRenderProbeConfig,
|
|
65
|
+
} from "./harness/check/runtime-render/adapter.js";
|
|
66
|
+
import { generatorBuild } from "./generator-build.js";
|
|
67
|
+
import { hasProfile } from "./boilerplate/styling-profile.js";
|
|
68
|
+
import { effortDials } from "./effort.js";
|
|
69
|
+
import type { ProviderName } from "./adapters/types.js";
|
|
70
|
+
import type { GenerationResult } from "./harness/result-types.js";
|
|
64
71
|
import {
|
|
65
72
|
canvasForRenderingContext,
|
|
66
73
|
injectContracts,
|
|
67
74
|
injectRenderingContext,
|
|
68
75
|
injectVariance,
|
|
69
|
-
} from
|
|
70
|
-
import type { RenderingContext } from
|
|
71
|
-
import type { DesignMode } from
|
|
72
|
-
import { DEFAULT_DESIGN_MODE } from
|
|
73
|
-
import { resolveRoute, applyRouteToEnv } from
|
|
74
|
-
import type { QualityConfig } from
|
|
75
|
-
import type { EvaluationConfig } from
|
|
76
|
-
import type { AgentConfig } from
|
|
76
|
+
} from "./contract-context.js";
|
|
77
|
+
import type { RenderingContext } from "./contract-context.js";
|
|
78
|
+
import type { DesignMode } from "./design-mode.js";
|
|
79
|
+
import { DEFAULT_DESIGN_MODE } from "./design-mode.js";
|
|
80
|
+
import { resolveRoute, applyRouteToEnv } from "./adapters/provider-router.js";
|
|
81
|
+
import type { QualityConfig, RuntimeProbeMeta, RuntimeProbeRepair } from "./evaluation/types-public.js";
|
|
82
|
+
import type { EvaluationConfig } from "./evaluation/types.js";
|
|
83
|
+
import type { AgentConfig } from "./harness/llm-router.js";
|
|
77
84
|
|
|
78
85
|
/** The slug for the OSS default seed generator. */
|
|
79
|
-
const DEFAULT_TIER: GeneratorTier =
|
|
80
|
-
const DEFAULT_MODEL: ModelRef =
|
|
86
|
+
const DEFAULT_TIER: GeneratorTier = "default";
|
|
87
|
+
const DEFAULT_MODEL: ModelRef = "anthropic/claude-haiku-4-5";
|
|
81
88
|
|
|
82
89
|
export interface CreateUiGeneratorOptions {
|
|
83
90
|
/**
|
|
84
|
-
* Wire
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
91
|
+
* Wire the runtime-render check into the harness's check leg — one
|
|
92
|
+
* instance per generator, built from `runtimeRenderProbe` (the default
|
|
93
|
+
* bounds when it is absent). When `true`, the runtime-render probe runs
|
|
94
|
+
* ONCE at the exit decision,
|
|
95
|
+
* after the coding turns, in an isolated subprocess: it mounts the
|
|
96
|
+
* compiled component against the contract and catches crash-class and
|
|
97
|
+
* contract-wiring bugs (a render that throws, a missing `useAction()`,
|
|
98
|
+
* a wrong stream channel name). It is never on the per-turn hot path.
|
|
99
|
+
*
|
|
100
|
+
* Without an `evaluation` config the round is observe + one repair turn
|
|
101
|
+
* (ggui#1380): a recoverable render crash buys exactly one repair turn
|
|
102
|
+
* and one re-probe, then the generation serves what it has; a probe that
|
|
103
|
+
* timed out, could not run, or had nothing to probe never blocks and
|
|
104
|
+
* never repairs. The probe's status and timing ride the result's
|
|
105
|
+
* metadata (`runtimeProbe`, `evalMs`). With an `evaluation` config the
|
|
106
|
+
* probe is the evaluation round's exit gate instead.
|
|
88
107
|
*
|
|
89
|
-
* Default: `false`
|
|
90
|
-
*
|
|
91
|
-
*
|
|
108
|
+
* Default: `false` — keeps cold-start light. The probe pulls happy-dom
|
|
109
|
+
* + @testing-library on first use (~700-1500ms cold). A serving
|
|
110
|
+
* deployment that wants the probe's verdict on every card sets it.
|
|
92
111
|
*/
|
|
93
112
|
readonly enableRuntimeRender?: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* How the runtime-render probe runs its isolated worker (ggui#1380). A
|
|
115
|
+
* serving deployment sets the probe's wall-clock bound, worker heap and
|
|
116
|
+
* concurrency here; the defaults are the evaluation lane's (30 s, 512 MB,
|
|
117
|
+
* no concurrency cap). The generator builds ONE check from it and hands
|
|
118
|
+
* that instance to every generation it dispatches — the concurrency cap
|
|
119
|
+
* lives on the instance, so it bounds the workers this generator has
|
|
120
|
+
* live at once. Only read when `enableRuntimeRender` is on. Never part
|
|
121
|
+
* of the prompt or the identity.
|
|
122
|
+
*/
|
|
123
|
+
readonly runtimeRenderProbe?: RuntimeRenderProbeConfig;
|
|
94
124
|
/** Maximum coding attempts per generation pass. */
|
|
95
125
|
readonly maxAttempts?: number;
|
|
96
126
|
/** Maximum evaluation rounds. */
|
|
@@ -108,7 +138,7 @@ export interface CreateUiGeneratorOptions {
|
|
|
108
138
|
* stylesheet to render under). Forwarded verbatim; never part of the
|
|
109
139
|
* prompt or the identity. Default: none — the round runs as today.
|
|
110
140
|
*/
|
|
111
|
-
readonly visualEvaluation?: SingleComponentParams[
|
|
141
|
+
readonly visualEvaluation?: SingleComponentParams["visualEvaluation"];
|
|
112
142
|
/** Quality config controlling eval tiers + improvement behavior. */
|
|
113
143
|
readonly qualityConfig?: QualityConfig;
|
|
114
144
|
/**
|
|
@@ -189,7 +219,7 @@ export interface CreateUiGeneratorOptions {
|
|
|
189
219
|
* request. Absent (default) is a no-op — retries still happen and
|
|
190
220
|
* still log via `console.warn`, just without a caller-side hook.
|
|
191
221
|
*/
|
|
192
|
-
readonly onRetry?: AgentConfig[
|
|
222
|
+
readonly onRetry?: AgentConfig["onRetry"];
|
|
193
223
|
/**
|
|
194
224
|
* Generation observer. Invoked once per successful `generate()` with
|
|
195
225
|
* the FULL harness result — source, compiled code, token counters,
|
|
@@ -221,11 +251,10 @@ export interface CreateUiGeneratorOptions {
|
|
|
221
251
|
readonly designMode?: DesignMode;
|
|
222
252
|
}
|
|
223
253
|
|
|
224
|
-
export function createUiGenerator(
|
|
225
|
-
options: CreateUiGeneratorOptions = {},
|
|
226
|
-
): UiGenerator {
|
|
254
|
+
export function createUiGenerator(options: CreateUiGeneratorOptions = {}): UiGenerator {
|
|
227
255
|
const {
|
|
228
256
|
enableRuntimeRender = false,
|
|
257
|
+
runtimeRenderProbe,
|
|
229
258
|
maxAttempts,
|
|
230
259
|
maxEvalRounds,
|
|
231
260
|
evaluation,
|
|
@@ -237,6 +266,11 @@ export function createUiGenerator(
|
|
|
237
266
|
onGenerated,
|
|
238
267
|
designMode,
|
|
239
268
|
} = options;
|
|
269
|
+
// ggui#1380 — ONE runtime-render check per generator: the instance owns
|
|
270
|
+
// the probe's concurrency cap, so it must outlive a single generation.
|
|
271
|
+
const runtimeRender = enableRuntimeRender
|
|
272
|
+
? createRuntimeRenderCheck(runtimeRenderProbe)
|
|
273
|
+
: undefined;
|
|
240
274
|
|
|
241
275
|
const identity = resolveIdentity(options);
|
|
242
276
|
|
|
@@ -273,9 +307,9 @@ export function createUiGenerator(
|
|
|
273
307
|
});
|
|
274
308
|
} catch (err) {
|
|
275
309
|
return failWithoutMetadata(input, identity.slug, startedAt, {
|
|
276
|
-
code:
|
|
310
|
+
code: "PRODUCTION_FAILED",
|
|
277
311
|
message: err instanceof Error ? err.message : String(err),
|
|
278
|
-
details: { kind:
|
|
312
|
+
details: { kind: "route-resolution-failed" },
|
|
279
313
|
});
|
|
280
314
|
}
|
|
281
315
|
|
|
@@ -307,16 +341,16 @@ export function createUiGenerator(
|
|
|
307
341
|
// flag is the only way the decision reaches `createAgent` /
|
|
308
342
|
// `createVisionAgent` (which select the login client on it).
|
|
309
343
|
const routeOverride =
|
|
310
|
-
route.auth ===
|
|
344
|
+
route.auth === "claude-code-login"
|
|
311
345
|
? { claudeCodeLogin: true }
|
|
312
346
|
: disableEnvMutation
|
|
313
347
|
? {
|
|
314
348
|
apiKey:
|
|
315
|
-
route.env[
|
|
316
|
-
route.env[
|
|
317
|
-
route.env[
|
|
318
|
-
route.env[
|
|
319
|
-
useBedrock: route.env[
|
|
349
|
+
route.env["ANTHROPIC_API_KEY"] ??
|
|
350
|
+
route.env["OPENAI_API_KEY"] ??
|
|
351
|
+
route.env["GEMINI_API_KEY"] ??
|
|
352
|
+
route.env["OPENROUTER_API_KEY"],
|
|
353
|
+
useBedrock: route.env["CLAUDE_CODE_USE_BEDROCK"] === "1",
|
|
320
354
|
}
|
|
321
355
|
: undefined;
|
|
322
356
|
|
|
@@ -342,11 +376,9 @@ export function createUiGenerator(
|
|
|
342
376
|
// descriptors stay byte-identical).
|
|
343
377
|
const canvas = rendering ? canvasForRenderingContext(rendering) : undefined;
|
|
344
378
|
const shellType = harnessShellForRendering(rendering);
|
|
345
|
-
const promptWithVariance = injectVariance(
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
{ profileDeclared: hasProfile(input.profile) },
|
|
349
|
-
);
|
|
379
|
+
const promptWithVariance = injectVariance(promptWithRendering, input.variance, {
|
|
380
|
+
profileDeclared: hasProfile(input.profile),
|
|
381
|
+
});
|
|
350
382
|
|
|
351
383
|
// Resolve appGadgets by precedence:
|
|
352
384
|
// 1. input.appGadgets (caller pre-fetched, handler path)
|
|
@@ -359,14 +391,10 @@ export function createUiGenerator(
|
|
|
359
391
|
input.appGadgets !== undefined
|
|
360
392
|
? input.appGadgets
|
|
361
393
|
: gadgetCatalog !== undefined && input.appId !== undefined
|
|
362
|
-
|
|
363
|
-
|
|
394
|
+
? await gadgetCatalog.list(input.appId)
|
|
395
|
+
: undefined;
|
|
364
396
|
|
|
365
|
-
const userPrompt = injectContracts(
|
|
366
|
-
promptWithVariance,
|
|
367
|
-
input.contract,
|
|
368
|
-
resolvedAppGadgets,
|
|
369
|
-
);
|
|
397
|
+
const userPrompt = injectContracts(promptWithVariance, input.contract, resolvedAppGadgets);
|
|
370
398
|
|
|
371
399
|
// `effort` (ggui#1059): a named level's dials override the
|
|
372
400
|
// deployment's options for THIS generation; absent ⇒ untouched.
|
|
@@ -375,7 +403,9 @@ export function createUiGenerator(
|
|
|
375
403
|
const effectiveMaxAttempts = dials?.maxAttempts ?? maxAttempts;
|
|
376
404
|
const effectiveMaxEvalRounds = dials?.maxEvalRounds ?? maxEvalRounds;
|
|
377
405
|
const effectiveEvaluation =
|
|
378
|
-
dials !== undefined && evaluation !== undefined
|
|
406
|
+
dials !== undefined && evaluation !== undefined
|
|
407
|
+
? { ...evaluation, maxRounds: dials.maxEvalRounds }
|
|
408
|
+
: evaluation;
|
|
379
409
|
const effectiveVisualEvaluation =
|
|
380
410
|
dials !== undefined && visualEvaluation !== undefined
|
|
381
411
|
? { ...visualEvaluation, passThreshold: dials.selfEvalPassThreshold }
|
|
@@ -389,21 +419,22 @@ export function createUiGenerator(
|
|
|
389
419
|
...(input.contract ? { contract: input.contract } : {}),
|
|
390
420
|
originalPrompt: input.request.prompt,
|
|
391
421
|
...(effectiveMaxAttempts !== undefined ? { maxAttempts: effectiveMaxAttempts } : {}),
|
|
392
|
-
...(effectiveMaxEvalRounds !== undefined
|
|
422
|
+
...(effectiveMaxEvalRounds !== undefined
|
|
423
|
+
? { maxEvalRounds: effectiveMaxEvalRounds }
|
|
424
|
+
: {}),
|
|
393
425
|
...(effectiveEvaluation !== undefined ? { evaluation: effectiveEvaluation } : {}),
|
|
394
|
-
...(effectiveVisualEvaluation !== undefined
|
|
395
|
-
|
|
396
|
-
...(resolvedAppGadgets !== undefined
|
|
397
|
-
? { appGadgets: resolvedAppGadgets }
|
|
426
|
+
...(effectiveVisualEvaluation !== undefined
|
|
427
|
+
? { visualEvaluation: effectiveVisualEvaluation }
|
|
398
428
|
: {}),
|
|
429
|
+
...(qualityConfig !== undefined ? { qualityConfig } : {}),
|
|
430
|
+
...(resolvedAppGadgets !== undefined ? { appGadgets: resolvedAppGadgets } : {}),
|
|
399
431
|
// Forward the third-party wrapper `.d.ts` map the render
|
|
400
432
|
// handler pre-fetched. Reaches both the code-gen prompt's
|
|
401
433
|
// `Type:` lines and the coding-agent typecheck overlay via
|
|
402
434
|
// `dispatchGeneration`.
|
|
403
|
-
...(input.gadgetTypes !== undefined
|
|
404
|
-
? { gadgetTypes: input.gadgetTypes }
|
|
405
|
-
: {}),
|
|
435
|
+
...(input.gadgetTypes !== undefined ? { gadgetTypes: input.gadgetTypes } : {}),
|
|
406
436
|
enableRuntimeRender,
|
|
437
|
+
...(runtimeRender !== undefined ? { runtimeRender } : {}),
|
|
407
438
|
...(routeOverride !== undefined ? { routeOverride } : {}),
|
|
408
439
|
...(onRetry !== undefined ? { onRetry } : {}),
|
|
409
440
|
...(designMode !== undefined ? { designMode } : {}),
|
|
@@ -417,6 +448,8 @@ export function createUiGenerator(
|
|
|
417
448
|
|
|
418
449
|
onGenerated?.(result);
|
|
419
450
|
|
|
451
|
+
const runtimeProbe = metadataRuntimeProbe(result);
|
|
452
|
+
const evalMs = metadataEvalMs(result);
|
|
420
453
|
const metadata: GenerationMetadata = {
|
|
421
454
|
provider: input.llm.provider,
|
|
422
455
|
generator: identity.slug,
|
|
@@ -437,6 +470,10 @@ export function createUiGenerator(
|
|
|
437
470
|
...(result.cacheCreationTokens !== undefined
|
|
438
471
|
? { cacheCreationTokens: result.cacheCreationTokens }
|
|
439
472
|
: {}),
|
|
473
|
+
// ggui#1380 — the runtime probe's record and the eval rounds' wall,
|
|
474
|
+
// when the harness ran a round; absent stays absent.
|
|
475
|
+
...(runtimeProbe !== undefined ? { runtimeProbe } : {}),
|
|
476
|
+
...(evalMs !== undefined ? { evalMs } : {}),
|
|
440
477
|
};
|
|
441
478
|
|
|
442
479
|
return {
|
|
@@ -451,12 +488,12 @@ export function createUiGenerator(
|
|
|
451
488
|
};
|
|
452
489
|
} catch (err) {
|
|
453
490
|
const message = err instanceof Error ? err.message : String(err);
|
|
454
|
-
const details: JsonObject = { kind:
|
|
455
|
-
if (err instanceof Error && err.stack) details[
|
|
491
|
+
const details: JsonObject = { kind: "harness-failed" };
|
|
492
|
+
if (err instanceof Error && err.stack) details["stack"] = err.stack;
|
|
456
493
|
return {
|
|
457
494
|
ok: false,
|
|
458
495
|
error: {
|
|
459
|
-
code:
|
|
496
|
+
code: "PRODUCTION_FAILED",
|
|
460
497
|
message,
|
|
461
498
|
details,
|
|
462
499
|
},
|
|
@@ -488,11 +525,9 @@ function resolveIdentity(opts: CreateUiGeneratorOptions): {
|
|
|
488
525
|
tier: GeneratorTier;
|
|
489
526
|
model: ModelRef;
|
|
490
527
|
} {
|
|
491
|
-
const hasSlug = typeof opts.slug ===
|
|
528
|
+
const hasSlug = typeof opts.slug === "string" && opts.slug.length > 0;
|
|
492
529
|
if (hasSlug && opts.tier !== undefined) {
|
|
493
|
-
throw new Error(
|
|
494
|
-
'createUiGenerator: pass either { slug } or { tier } — not both.',
|
|
495
|
-
);
|
|
530
|
+
throw new Error("createUiGenerator: pass either { slug } or { tier } — not both.");
|
|
496
531
|
}
|
|
497
532
|
const model = opts.model ?? DEFAULT_MODEL;
|
|
498
533
|
if (hasSlug) {
|
|
@@ -500,7 +535,7 @@ function resolveIdentity(opts: CreateUiGeneratorOptions): {
|
|
|
500
535
|
const parsed = parseGeneratorSlug(slug);
|
|
501
536
|
if (!parsed || !isGeneratorId(slug)) {
|
|
502
537
|
throw new Error(
|
|
503
|
-
`createUiGenerator: slug ${JSON.stringify(slug)} is not a valid ui-gen-<tier> identifier
|
|
538
|
+
`createUiGenerator: slug ${JSON.stringify(slug)} is not a valid ui-gen-<tier> identifier.`
|
|
504
539
|
);
|
|
505
540
|
}
|
|
506
541
|
return { slug, tier: parsed.tier, model };
|
|
@@ -512,23 +547,80 @@ function resolveIdentity(opts: CreateUiGeneratorOptions): {
|
|
|
512
547
|
// so a slug it returns must parse. Defensive check kept to surface
|
|
513
548
|
// any future drift between the two helpers.
|
|
514
549
|
throw new Error(
|
|
515
|
-
`createUiGenerator: formatted slug ${JSON.stringify(slug)} round-trips as invalid — formatGeneratorSlug/isValidGeneratorSlug drift
|
|
550
|
+
`createUiGenerator: formatted slug ${JSON.stringify(slug)} round-trips as invalid — formatGeneratorSlug/isValidGeneratorSlug drift.`
|
|
516
551
|
);
|
|
517
552
|
}
|
|
518
553
|
return { slug, tier, model };
|
|
519
554
|
}
|
|
520
555
|
|
|
556
|
+
/**
|
|
557
|
+
* ggui#1380 — one probe's meta, projected onto the metadata's outcome ARM BY
|
|
558
|
+
* ARM: the outcome fields (`status`, `verdict`, `failChecks`, `elapsedMs`)
|
|
559
|
+
* are copied; the engine's own detail (`reason`, `renderMs`, `hostLoad`)
|
|
560
|
+
* stays on the harness result. No cast — each arm is rebuilt as a literal of
|
|
561
|
+
* the target arm, so a field the metadata does not carry cannot ride along
|
|
562
|
+
* and a field the arm requires cannot be dropped. Absent stays absent.
|
|
563
|
+
*/
|
|
564
|
+
function metadataProbeOutcome(meta: RuntimeProbeMeta): GenerationRuntimeProbeOutcome {
|
|
565
|
+
const elapsed = {
|
|
566
|
+
...(meta.elapsedMs !== undefined ? { elapsedMs: meta.elapsedMs } : {}),
|
|
567
|
+
...(meta.queuedMs !== undefined ? { queuedMs: meta.queuedMs } : {}),
|
|
568
|
+
};
|
|
569
|
+
if (meta.status !== "ran") return { status: meta.status, ...elapsed };
|
|
570
|
+
if (meta.verdict === "pass") return { status: "ran", verdict: "pass", ...elapsed };
|
|
571
|
+
return { status: "ran", verdict: "fail", failChecks: meta.failChecks, ...elapsed };
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* ggui#1380 — the repair record, arm by arm: `compiled: false` is carried as
|
|
576
|
+
* is (no re-probe, so no after); `compiled: true` carries the re-probe's meta
|
|
577
|
+
* projected with the same function as the record's own outcome.
|
|
578
|
+
*/
|
|
579
|
+
function metadataProbeRepair(repair: RuntimeProbeRepair): GenerationRuntimeProbeRepair {
|
|
580
|
+
return repair.compiled
|
|
581
|
+
? { attempted: true, compiled: true, trigger: metadataProbeOutcome(repair.trigger) }
|
|
582
|
+
: { attempted: true, compiled: false };
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* ggui#1380 — the harness result's probe record, projected onto the metadata
|
|
587
|
+
* a host reads. The outcome fields are the LAST probe's (the re-probe's when
|
|
588
|
+
* the repair compiled, else the pre-repair probe's — exactly what the harness
|
|
589
|
+
* stamps on `evalResult.runtimeProbe`), and `repair` is present only when a
|
|
590
|
+
* repair turn was bought. Absent when no probe ran — never a default status.
|
|
591
|
+
*/
|
|
592
|
+
function metadataRuntimeProbe(result: GenerationResult): GenerationRuntimeProbe | undefined {
|
|
593
|
+
const probe = result.evalResult?.runtimeProbe;
|
|
594
|
+
if (probe === undefined) return undefined;
|
|
595
|
+
const repair = result.evalResult?.runtimeProbeRepair;
|
|
596
|
+
return {
|
|
597
|
+
...metadataProbeOutcome(probe),
|
|
598
|
+
...(repair !== undefined ? { repair: metadataProbeRepair(repair) } : {}),
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* ggui#1380 — the eval rounds' wall-clock, present only when a round ran:
|
|
604
|
+
* a generation with no round has nothing to time, and the field stays
|
|
605
|
+
* absent rather than reporting a structural 0.
|
|
606
|
+
*/
|
|
607
|
+
function metadataEvalMs(result: GenerationResult): number | undefined {
|
|
608
|
+
const breakdown = result.breakdown;
|
|
609
|
+
if (breakdown === undefined || breakdown.evalRounds === 0) return undefined;
|
|
610
|
+
return breakdown.evalMs;
|
|
611
|
+
}
|
|
612
|
+
|
|
521
613
|
function mapLlmProviderToDispatchProvider(provider: LlmProvider): ProviderName {
|
|
522
614
|
switch (provider) {
|
|
523
|
-
case
|
|
524
|
-
case
|
|
525
|
-
return
|
|
526
|
-
case
|
|
527
|
-
return
|
|
528
|
-
case
|
|
529
|
-
return
|
|
530
|
-
case
|
|
531
|
-
return
|
|
615
|
+
case "anthropic":
|
|
616
|
+
case "bedrock":
|
|
617
|
+
return "claude";
|
|
618
|
+
case "openai":
|
|
619
|
+
return "openai";
|
|
620
|
+
case "google":
|
|
621
|
+
return "google";
|
|
622
|
+
case "openrouter":
|
|
623
|
+
return "openrouter";
|
|
532
624
|
}
|
|
533
625
|
}
|
|
534
626
|
|
|
@@ -542,20 +634,18 @@ function mapLlmProviderToDispatchProvider(provider: LlmProvider): ProviderName {
|
|
|
542
634
|
* yet, so both pass no shell and those generations stay byte-identical — named
|
|
543
635
|
* here rather than guessed.
|
|
544
636
|
*/
|
|
545
|
-
function harnessShellForRendering(rendering: RenderingContext | undefined):
|
|
637
|
+
function harnessShellForRendering(rendering: RenderingContext | undefined): "chat" | undefined {
|
|
546
638
|
if (rendering === undefined) return undefined;
|
|
547
639
|
switch (rendering.shell) {
|
|
548
|
-
case
|
|
549
|
-
return
|
|
550
|
-
case
|
|
551
|
-
case
|
|
640
|
+
case "chat":
|
|
641
|
+
return "chat";
|
|
642
|
+
case "fullscreen":
|
|
643
|
+
case "partial":
|
|
552
644
|
return undefined;
|
|
553
645
|
}
|
|
554
646
|
}
|
|
555
647
|
|
|
556
|
-
function mapRendering(
|
|
557
|
-
rendering: UiGenerateInput['rendering'],
|
|
558
|
-
): RenderingContext | undefined {
|
|
648
|
+
function mapRendering(rendering: UiGenerateInput["rendering"]): RenderingContext | undefined {
|
|
559
649
|
if (!rendering) return undefined;
|
|
560
650
|
return {
|
|
561
651
|
device: rendering.device,
|
|
@@ -568,7 +658,7 @@ function failWithoutMetadata(
|
|
|
568
658
|
input: UiGenerateInput,
|
|
569
659
|
generatorSlug: GeneratorId,
|
|
570
660
|
startedAt: number,
|
|
571
|
-
error: GenerationError
|
|
661
|
+
error: GenerationError
|
|
572
662
|
): UiGenerateResult {
|
|
573
663
|
return {
|
|
574
664
|
ok: false,
|
|
@@ -600,18 +690,18 @@ export function extractComponentCode(raw: string): string {
|
|
|
600
690
|
const fences: Array<{ lang: string; body: string }> = [];
|
|
601
691
|
let match: RegExpExecArray | null;
|
|
602
692
|
while ((match = fencePattern.exec(raw)) !== null) {
|
|
603
|
-
const lang = (match[1] ??
|
|
604
|
-
const body = match[2] ??
|
|
693
|
+
const lang = (match[1] ?? "").toLowerCase();
|
|
694
|
+
const body = match[2] ?? "";
|
|
605
695
|
fences.push({ lang, body });
|
|
606
696
|
}
|
|
607
697
|
|
|
608
698
|
if (fences.length > 0) {
|
|
609
|
-
const priority = [
|
|
699
|
+
const priority = ["tsx", "jsx", "typescript", "ts", "javascript", "js", ""];
|
|
610
700
|
for (const preferred of priority) {
|
|
611
701
|
const hit = fences.find((f) => f.lang === preferred);
|
|
612
702
|
if (hit) return hit.body.trim();
|
|
613
703
|
}
|
|
614
|
-
const firstBody = fences[0]?.body ??
|
|
704
|
+
const firstBody = fences[0]?.body ?? "";
|
|
615
705
|
return firstBody.trim();
|
|
616
706
|
}
|
|
617
707
|
|
|
@@ -79,16 +79,41 @@ function runStateCoversPayload(input: AxisCheckInput): EvalIssue[] {
|
|
|
79
79
|
return issues;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Whether a `useState` initializer reads `props.<name>`: directly, or through a local bound from
|
|
84
|
+
* it (`const initial = normalize(props.initialProfile)` then `useState(initial.name)`, or a
|
|
85
|
+
* destructure `const { name } = props.initialProfile ?? {}` then `useState(name)`). The property
|
|
86
|
+
* this check guards is "form state is seeded from the prop", not a spelling: ggui#1261's Exp 011
|
|
87
|
+
* cell re-seeded correctly through a normalized local, was refused, and spent a turn restoring
|
|
88
|
+
* the literal form.
|
|
89
|
+
*/
|
|
90
|
+
function seedsFromProp(src: string, name: string): boolean {
|
|
91
|
+
const propRef = `props\\??\\.${name}\\b`;
|
|
92
|
+
const initializerReads = (ref: string): boolean =>
|
|
93
|
+
new RegExp(`useState(?:<[^>]*>)?\\s*\\([\\s\\S]{0,400}?${ref}`).test(src);
|
|
94
|
+
if (initializerReads(propRef)) return true;
|
|
95
|
+
const locals = new Set<string>();
|
|
96
|
+
for (const m of src.matchAll(new RegExp(`(?:const|let|var)\\s+(\\w+)\\s*(?::[^=]{1,80})?=\\s*[^;]{0,300}?${propRef}`, "g"))) {
|
|
97
|
+
const id = m[1];
|
|
98
|
+
if (id !== undefined) locals.add(id);
|
|
99
|
+
}
|
|
100
|
+
for (const m of src.matchAll(new RegExp(`(?:const|let|var)\\s*\\{([^}]{1,400})\\}\\s*=\\s*[^;]{0,300}?${propRef}`, "g"))) {
|
|
101
|
+
for (const part of (m[1] ?? "").split(",")) {
|
|
102
|
+
const id = part.split(":").pop()?.split("=")[0]?.trim();
|
|
103
|
+
if (id !== undefined && /^\w+$/.test(id)) locals.add(id);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
for (const id of locals) if (initializerReads(`\\b${id}\\b`)) return true;
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
82
110
|
function runInitialValuesSeeded(input: AxisCheckInput): EvalIssue[] {
|
|
83
111
|
if (input.compiledCode === null) return [];
|
|
84
112
|
const src = input.sourceCode;
|
|
85
113
|
const initialProps = getInitialValuePropNames(input.contract);
|
|
86
114
|
const issues: EvalIssue[] = [];
|
|
87
115
|
for (const name of initialProps) {
|
|
88
|
-
|
|
89
|
-
`useState(?:<[^>]*>)?\\s*\\([\\s\\S]{0,400}?props\\.${name}\\b`,
|
|
90
|
-
);
|
|
91
|
-
if (re.test(src)) continue;
|
|
116
|
+
if (seedsFromProp(src, name)) continue;
|
|
92
117
|
issues.push(
|
|
93
118
|
mkIssue(
|
|
94
119
|
"state.payload.initial_values_seeded",
|