@ggui-ai/ui-gen 0.25.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-Cg_rkpGq.d.ts → adapter-BJwn74rx.d.ts} +49 -1
- package/dist/adapters/generation-dispatch.d.ts +8 -5
- package/dist/adapters/generation-dispatch.js +1118 -193
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +27 -6
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +10 -9
- package/dist/advanced/index.js +1126 -202
- package/dist/advanced/index.js.map +1 -1
- 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 +10 -3
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +3 -2
- package/dist/check/index.js +9 -2
- package/dist/check/index.js.map +1 -1
- package/dist/{create-ui-generator-DFnnArrv.d.ts → create-ui-generator-N4gRXpcL.d.ts} +3 -4
- package/dist/{design-mode-DLufhrNp.d.ts → design-mode-DGWN_Gkm.d.ts} +1 -1
- package/dist/{dispatch-CStl9eAw.d.ts → dispatch-BUoXRj_r.d.ts} +2 -2
- package/dist/evaluation/axis-checks/index.d.ts +5 -4
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +4 -3
- package/dist/evaluation/index.d.ts +107 -539
- package/dist/evaluation/index.js +552 -55
- package/dist/evaluation/index.js.map +1 -1
- package/dist/evaluation/types.d.ts +17 -0
- package/dist/evaluation/types.js.map +1 -1
- package/dist/{generate-9IeNT4ZY.d.ts → generate-tSO_shQ0.d.ts} +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +5 -5
- 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 +4 -5
- package/dist/harness/index.js +36 -5
- 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 +17 -3
- package/dist/harness/result-types.js.map +1 -1
- package/dist/harness/runtime.d.ts +14 -6
- package/dist/harness/runtime.js +11 -4
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +4 -2
- package/dist/harness/types-public.js +10 -3
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +4 -3
- package/dist/index.d.ts +46 -11
- package/dist/index.js +1128 -200
- package/dist/index.js.map +1 -1
- package/dist/patch.d.ts +3 -3
- package/dist/{prompts-YU9piOgs.d.ts → prompts-BhNUuRoZ.d.ts} +1 -1
- package/dist/tools/render-check-worker.js +8 -1
- package/dist/tools/render-check-worker.js.map +1 -1
- package/dist/{types-public-BJTLpzWn.d.ts → types-public-B7wn4ce5.d.ts} +384 -3
- 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 +3 -3
- package/package.json +12 -12
- package/src/adapters/generation-dispatch.ts +2 -0
- package/src/boilerplate/free-design-prompt.ts +3 -3
- package/src/boilerplate/hard-sections.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/run-tier0.ts +6 -3
- package/src/contract-context.ts +1 -1
- package/src/evaluation/cost-tracker.ts +54 -6
- package/src/evaluation/criteria/edge-probe.ts +286 -0
- package/src/evaluation/criteria/edge.ts +202 -0
- package/src/evaluation/criteria/resolve.ts +55 -7
- package/src/evaluation/index.ts +5 -1
- package/src/evaluation/judge-spend.ts +48 -0
- package/src/evaluation/types-public.ts +91 -0
- package/src/evaluation/types.ts +10 -0
- package/src/evaluation/visual-evaluator.ts +236 -40
- 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 +27 -0
- package/src/harness/coding/init-session.ts +5 -3
- package/src/harness/coding/run-coding-turn.ts +5 -1
- package/src/harness/coding/run-eval-round.ts +63 -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 +9 -0
- package/src/harness/types-public.ts +10 -1
- package/src/index.ts +5 -0
- package/src/internal/verdict-line.ts +33 -0
- package/src/taught-tokens.ts +32 -0
- package/src/tools/render-check-worker.ts +2 -1
- package/src/tools/render-check.ts +8 -7
- package/src/validation/primitives.ts +1 -1
- package/dist/llm-router-nfO7wPec.d.ts +0 -267
- package/dist/{bank-CT4eEkta.d.ts → bank-Ctdp3FUA.d.ts} +3 -3
|
@@ -161,6 +161,50 @@ export interface LLMResponse {
|
|
|
161
161
|
* requested".
|
|
162
162
|
*/
|
|
163
163
|
sampling?: AppliedSampling;
|
|
164
|
+
/**
|
|
165
|
+
* Why the provider stopped, normalized (ggui#1127): `length` = it hit the output cap, `stop` = it ended its
|
|
166
|
+
* answer, `content-filter` = a safety stop, `other` = anything else it reported. Set by the vision calls of
|
|
167
|
+
* providers that report one (Anthropic `stop_reason`, Google `finishReason`); absent when the provider or route
|
|
168
|
+
* said nothing — read absence as unknown, never as `stop`.
|
|
169
|
+
*/
|
|
170
|
+
finishReason?: VisionFinishReason;
|
|
171
|
+
/**
|
|
172
|
+
* The model that answered, in the provider's own words (ggui#1687): Anthropic's `model`, Google's `modelVersion`.
|
|
173
|
+
* The request names an alias or a resolved id; this is what the provider says served it. Absent when it said
|
|
174
|
+
* nothing — read absence as unknown, never as the requested model.
|
|
175
|
+
*/
|
|
176
|
+
servedModel?: string;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** The `servedModel` field to spread into an {@link LLMResponse}: present only when the provider named one. */
|
|
180
|
+
export function servedModelOf(name: string | null | undefined): { servedModel?: string } {
|
|
181
|
+
return typeof name === 'string' && name.length > 0 ? { servedModel: name } : {};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** The normalized stop reason a vision call reports (ggui#1127). */
|
|
185
|
+
export type VisionFinishReason = 'stop' | 'length' | 'content-filter' | 'other';
|
|
186
|
+
|
|
187
|
+
/** Anthropic's `stop_reason`, normalized; absent stays absent. */
|
|
188
|
+
export function anthropicFinishReason(stopReason: string | null | undefined): VisionFinishReason | undefined {
|
|
189
|
+
if (stopReason === null || stopReason === undefined) return undefined;
|
|
190
|
+
if (stopReason === 'end_turn' || stopReason === 'stop_sequence') return 'stop';
|
|
191
|
+
if (stopReason === 'max_tokens') return 'length';
|
|
192
|
+
if (stopReason === 'refusal') return 'content-filter';
|
|
193
|
+
return 'other';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Gemini's `finishReason`, normalized; absent stays absent. */
|
|
197
|
+
export function googleFinishReason(finishReason: string | null | undefined): VisionFinishReason | undefined {
|
|
198
|
+
if (finishReason === null || finishReason === undefined) return undefined;
|
|
199
|
+
if (finishReason === 'STOP') return 'stop';
|
|
200
|
+
if (finishReason === 'MAX_TOKENS') return 'length';
|
|
201
|
+
if (['SAFETY', 'RECITATION', 'BLOCKLIST', 'PROHIBITED_CONTENT', 'SPII', 'IMAGE_SAFETY'].includes(finishReason)) return 'content-filter';
|
|
202
|
+
return 'other';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** The `finishReason` field to spread into an {@link LLMResponse}: present only when the provider said. */
|
|
206
|
+
function visionFinish(finishReason: VisionFinishReason | undefined): { finishReason?: VisionFinishReason } {
|
|
207
|
+
return finishReason !== undefined ? { finishReason } : {};
|
|
164
208
|
}
|
|
165
209
|
|
|
166
210
|
/**
|
|
@@ -806,6 +850,8 @@ export class AnthropicAgent extends LLMAgent {
|
|
|
806
850
|
text,
|
|
807
851
|
inputTokens: response.usage.input_tokens,
|
|
808
852
|
outputTokens: response.usage.output_tokens,
|
|
853
|
+
...visionFinish(anthropicFinishReason(response.stop_reason)),
|
|
854
|
+
...servedModelOf(response.model),
|
|
809
855
|
};
|
|
810
856
|
} catch (e) {
|
|
811
857
|
const endedAt = Date.now();
|
|
@@ -1575,6 +1621,15 @@ export class OpenAIAgent extends LLMAgent {
|
|
|
1575
1621
|
}
|
|
1576
1622
|
|
|
1577
1623
|
// =============================================================================
|
|
1624
|
+
/**
|
|
1625
|
+
* `Interactions.GenerationConfig` as the Interactions API accepts it (ggui#1579). The SDK's type (@google/genai 2.15.0)
|
|
1626
|
+
* omits `temperature`, but the API takes `generation_config.temperature` and forwards it to the request's own: it
|
|
1627
|
+
* range-checks the value (2.5 is refused with "GenerateContentRequest.generation_config.temperature: temperature must be
|
|
1628
|
+
* in the range [0.0, 2.0]", 0 is accepted), while a key the API does not know is refused as "Unknown parameter". The
|
|
1629
|
+
* field is declared here so the call is built against a type that carries it, not left to a spread of an undeclared key.
|
|
1630
|
+
*/
|
|
1631
|
+
type InteractionsGenerationConfig = Interactions.GenerationConfig & { readonly temperature?: number };
|
|
1632
|
+
|
|
1578
1633
|
// GoogleAgent — uses Interactions API (server-side state, automatic caching)
|
|
1579
1634
|
// =============================================================================
|
|
1580
1635
|
|
|
@@ -1666,7 +1721,7 @@ export class GoogleAgent extends LLMAgent {
|
|
|
1666
1721
|
): Promise<LLMResponse> {
|
|
1667
1722
|
const client = await this.getClient<GoogleGenAI>();
|
|
1668
1723
|
|
|
1669
|
-
const generationConfig:
|
|
1724
|
+
const generationConfig: InteractionsGenerationConfig = {
|
|
1670
1725
|
...(maxTokens ? { max_output_tokens: maxTokens } : {}),
|
|
1671
1726
|
...(temperature !== undefined ? { temperature } : {}),
|
|
1672
1727
|
};
|
|
@@ -1730,6 +1785,8 @@ export class GoogleAgent extends LLMAgent {
|
|
|
1730
1785
|
// ggui#1186 (third site): `promptTokenCount` includes `cachedContentTokenCount` — report the non-cached input.
|
|
1731
1786
|
inputTokens: splitCacheInclusiveUsage(usage?.promptTokenCount ?? 0, usage?.candidatesTokenCount ?? 0, usage?.cachedContentTokenCount ?? 0).tokens.input,
|
|
1732
1787
|
outputTokens: usage?.candidatesTokenCount ?? 0,
|
|
1788
|
+
...visionFinish(googleFinishReason(response.candidates?.[0]?.finishReason)),
|
|
1789
|
+
...servedModelOf(response.modelVersion),
|
|
1733
1790
|
};
|
|
1734
1791
|
|
|
1735
1792
|
const endedAt = Date.now();
|
|
@@ -13,6 +13,7 @@ import type { DataContract, JsonObject } from "@ggui-ai/protocol";
|
|
|
13
13
|
import type { AdapterResult } from "../adapters/types.js";
|
|
14
14
|
import type { RenderingContext } from "../contract-context.js";
|
|
15
15
|
import type { EvalResult } from "../evaluation/types-public.js";
|
|
16
|
+
import type { VisualLegTokens } from "../evaluation/visual-evaluator.js";
|
|
16
17
|
import type { CanvasClass, DesignMode } from "../design-mode.js";
|
|
17
18
|
|
|
18
19
|
// =============================================================================
|
|
@@ -66,6 +67,14 @@ export interface GenerationResult extends AdapterResult {
|
|
|
66
67
|
passesUsed: number;
|
|
67
68
|
/** Three-tier evaluation result (tier 0 + LLM tier 1+2 + visual) */
|
|
68
69
|
evalResult?: EvalResult;
|
|
70
|
+
/**
|
|
71
|
+
* ggui#1522 — what the IN-LOOP visual judge spent during this generation (its scoring calls, and its report-only
|
|
72
|
+
* criteria calls apart), priced at the visual agent's model. NOT inside `tokens`, which covers the coding turns
|
|
73
|
+
* and the text evaluator. And NOT the same calls as a post-generation judge's own visual pass (a benchmark
|
|
74
|
+
* report's `meta.costs.visualUsd`): a cost rule that adds both is correct, one read as the other double-counts.
|
|
75
|
+
* Absent when no in-loop visual leg ran or it reported no tokens.
|
|
76
|
+
*/
|
|
77
|
+
inLoopVisualTokens?: VisualLegTokens;
|
|
69
78
|
/** Set only when the same-exchange guard ended the run (ggui#404); no key otherwise. */
|
|
70
79
|
sameExchangeBreak?: SameExchangeBreak;
|
|
71
80
|
/** Whether background improvement should be spawned (auto-improve mode) */
|
package/src/harness/runtime.ts
CHANGED
|
@@ -108,6 +108,15 @@ export interface SingleComponentParams {
|
|
|
108
108
|
criteriaBank?: CriteriaBank;
|
|
109
109
|
/** ggui#1436 — the caller's item kind for the selector (a bootstrap mint's `hello`); absent = not gated by kind. */
|
|
110
110
|
criteriaKind?: string;
|
|
111
|
+
/**
|
|
112
|
+
* ggui#1542 — which of the VISUAL leg's own findings may start another eval round when the quality mode is `fast`.
|
|
113
|
+
* `'critical'` (the default, today's behaviour): only a critical finding, which the harness makes a blocking fail.
|
|
114
|
+
* `'major'`: the visual JUDGE's major findings (tier-2 warns) too. The fit measurement's own warn-level overflow is
|
|
115
|
+
* not among them (fit runs with or without a judge, so it is its own feedback source), and neither are the text
|
|
116
|
+
* evaluator's warns. The other quality modes already act on every warn, so the setting changes nothing there. The
|
|
117
|
+
* fix turn is fed as any continuing round is (fails first, then warns).
|
|
118
|
+
*/
|
|
119
|
+
actOn?: "critical" | "major";
|
|
111
120
|
};
|
|
112
121
|
onProgress?: (event: unknown) => void;
|
|
113
122
|
onInitialResult?: (result: {
|
|
@@ -21,7 +21,14 @@
|
|
|
21
21
|
import type { GenerationProfileInput } from "../boilerplate/styling-profile.js";
|
|
22
22
|
import type { GadgetDescriptor, DataContract, JsonValue } from "@ggui-ai/protocol";
|
|
23
23
|
import type { Classification } from "../classifier/axes.js";
|
|
24
|
-
import type {
|
|
24
|
+
import type {
|
|
25
|
+
EvalIssue,
|
|
26
|
+
EvalTier,
|
|
27
|
+
AxisCheck,
|
|
28
|
+
ProbeHostLoad,
|
|
29
|
+
ProbePendingAffordance,
|
|
30
|
+
RuntimeProbeStatus,
|
|
31
|
+
} from "../evaluation/types-public.js";
|
|
25
32
|
import type { LLMToolDef } from "../llm.js";
|
|
26
33
|
import type { CacheTier, HarnessFragment } from "../fragments/index.js";
|
|
27
34
|
import type { HarnessPolicy, ProcessMode } from "../policy.js";
|
|
@@ -160,6 +167,8 @@ export interface RuntimeRenderOutcome {
|
|
|
160
167
|
readonly hostLoad?: ProbeHostLoad;
|
|
161
168
|
/** Time the check waited for a probe slot before it started, ms; present only when > 0 (ggui#1380 C2b). */
|
|
162
169
|
readonly queuedMs?: number;
|
|
170
|
+
/** `ran` only: the pending-affordance walk (ggui#1398), report-only; absent when no action walk ran. */
|
|
171
|
+
readonly pendingAffordance?: ProbePendingAffordance;
|
|
163
172
|
}
|
|
164
173
|
|
|
165
174
|
export interface RuntimeRenderCheck {
|
package/src/index.ts
CHANGED
|
@@ -28,6 +28,11 @@ export type { GenerationResult, SameExchangeBreak } from './harness/result-types
|
|
|
28
28
|
// either design mode — so a harness definition can carry both modes' digests
|
|
29
29
|
// while a run's metadata carries only the mode that ran.
|
|
30
30
|
export { generatorBuild } from './generator-build.js';
|
|
31
|
+
// ggui#1513 — which in-loop evaluation legs a generation runs: the harness's
|
|
32
|
+
// own gate, for a host that needs to know whether a generation's token
|
|
33
|
+
// counts can include evaluation calls.
|
|
34
|
+
export { inLoopEvaluation, runsInLoopEvaluation } from './harness/in-loop-evaluation.js';
|
|
35
|
+
export type { InLoopEvaluationSwitches } from './harness/in-loop-evaluation.js';
|
|
31
36
|
export type { UiGenBuild } from './generator-build.js';
|
|
32
37
|
|
|
33
38
|
// Generator registry seam re-export. The interface + slug helpers live
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How an isolated worker that runs generated component code (the
|
|
3
|
+
* runtime-render check's, the render smoke test's) hands its verdict to its
|
|
4
|
+
* host.
|
|
5
|
+
*
|
|
6
|
+
* The worker's stdout is shared with the component: card code runs in the
|
|
7
|
+
* worker's own realm with Node's `console`, so a card that logs even one line
|
|
8
|
+
* would corrupt a verdict read as "the whole of stdout". The verdict
|
|
9
|
+
* therefore goes out on one line carrying {@link VERDICT_PREFIX}, and the
|
|
10
|
+
* host reads the LAST such line, ignoring everything else.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** The prefix of the verdict line. Not a string a card would print by accident. */
|
|
14
|
+
export const VERDICT_PREFIX = '@@ggui-render-check-verdict@@';
|
|
15
|
+
|
|
16
|
+
/** The worker's verdict line, newline-terminated. */
|
|
17
|
+
export function formatVerdictLine(value: unknown): string {
|
|
18
|
+
return `${VERDICT_PREFIX} ${JSON.stringify(value)}\n`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The value on the last verdict line in `stdout`, or `undefined` when no
|
|
23
|
+
* line carries the prefix. A prefixed line that is not JSON throws, so the
|
|
24
|
+
* host can report it as such.
|
|
25
|
+
*/
|
|
26
|
+
export function parseVerdictLine<Verdict>(stdout: string): Verdict | undefined {
|
|
27
|
+
const lines = stdout.split('\n');
|
|
28
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
29
|
+
const line = lines[i]!;
|
|
30
|
+
if (line.startsWith(`${VERDICT_PREFIX} `)) return JSON.parse(line.slice(VERDICT_PREFIX.length + 1)) as Verdict;
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// The consumed-token names the generator may TEACH — the manifest minus the names a card may not
|
|
2
|
+
// reference yet.
|
|
3
|
+
//
|
|
4
|
+
// A card is minted by one release and rendered by whichever runtime serves it: during a rolling
|
|
5
|
+
// deploy, or after a rollback, that runtime can be the previous release, which neither declares
|
|
6
|
+
// nor completes a key derived for the first time in this one. A bare `var(--new-key)` then
|
|
7
|
+
// resolves to nothing — a `border` shorthand drops whole — and the no-fallback rule leaves the
|
|
8
|
+
// card nothing to fall back on. So a new derived key ships in two steps: the renderers that
|
|
9
|
+
// declare and complete it serve first (the design package's own primitives read it behind a
|
|
10
|
+
// fallback), and the release AFTER takes it out of this set, in the same change as the prompt
|
|
11
|
+
// guidance that teaches it.
|
|
12
|
+
|
|
13
|
+
import { consumedTokenManifest } from "@ggui-ai/design/themes";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Manifest names held back from every surface that teaches the model a name — the free prompt's
|
|
17
|
+
* colour vocabulary and token reference, and the self-check's nearest-name suggestions. The
|
|
18
|
+
* closed-set check still admits them: a card that references one is valid wherever this release
|
|
19
|
+
* renders it.
|
|
20
|
+
*
|
|
21
|
+
* - ggui#1494 — the control edges (`controlOutline`, `controlAccentOutline`): derived and completed
|
|
22
|
+
* from this release; taught from the next.
|
|
23
|
+
*/
|
|
24
|
+
export const NOT_YET_TAUGHT_TOKENS: ReadonlySet<string> = new Set([
|
|
25
|
+
"--ggui-color-controlAccentOutline",
|
|
26
|
+
"--ggui-color-controlOutline",
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
/** The manifest as the model is taught it. */
|
|
30
|
+
export const taughtTokenManifest: readonly string[] = consumedTokenManifest.filter(
|
|
31
|
+
(token) => !NOT_YET_TAUGHT_TOKENS.has(token),
|
|
32
|
+
);
|
|
@@ -43,6 +43,7 @@ import * as esbuild from 'esbuild';
|
|
|
43
43
|
import type { JsonObject } from '@ggui-ai/protocol';
|
|
44
44
|
import { evaluateComponentSource } from '../internal/evaluate-component-source.js';
|
|
45
45
|
import { hostGlobals } from '../internal/open-record.js';
|
|
46
|
+
import { formatVerdictLine } from '../internal/verdict-line.js';
|
|
46
47
|
|
|
47
48
|
interface WorkerInput {
|
|
48
49
|
readonly sourceCode: string;
|
|
@@ -212,7 +213,7 @@ async function renderOnce(input: WorkerInput): Promise<WorkerOutput> {
|
|
|
212
213
|
}
|
|
213
214
|
|
|
214
215
|
function emit(output: WorkerOutput): void {
|
|
215
|
-
process.stdout.write(
|
|
216
|
+
process.stdout.write(formatVerdictLine(output));
|
|
216
217
|
}
|
|
217
218
|
|
|
218
219
|
async function readAllStdin(): Promise<string> {
|
|
@@ -28,6 +28,7 @@ import { existsSync } from 'node:fs';
|
|
|
28
28
|
import { createRequire } from 'node:module';
|
|
29
29
|
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
30
30
|
import { runSandboxed } from '@ggui-ai/sandbox';
|
|
31
|
+
import { parseVerdictLine } from '../internal/verdict-line.js';
|
|
31
32
|
import type {
|
|
32
33
|
JsonObject,
|
|
33
34
|
} from '@ggui-ai/protocol';
|
|
@@ -168,20 +169,20 @@ export async function tryRender(
|
|
|
168
169
|
return `Render error: worker exited ${result.exitCode}${tail ? ` — ${tail}` : ''}`;
|
|
169
170
|
}
|
|
170
171
|
|
|
171
|
-
const stdout = result.stdout.trim();
|
|
172
|
-
if (stdout.length === 0) {
|
|
173
|
-
return 'Render error: worker exited without producing a verdict.';
|
|
174
|
-
}
|
|
175
|
-
|
|
176
172
|
type Verdict = { ok: true } | { ok: false; error: string };
|
|
177
|
-
let verdict: Verdict;
|
|
173
|
+
let verdict: Verdict | undefined;
|
|
178
174
|
try {
|
|
179
|
-
verdict
|
|
175
|
+
// The verdict rides its own prefixed line, so a component that logs to
|
|
176
|
+
// stdout while it renders cannot corrupt it.
|
|
177
|
+
verdict = parseVerdictLine<Verdict>(result.stdout);
|
|
180
178
|
} catch (err) {
|
|
181
179
|
return `Render error: malformed worker verdict — ${
|
|
182
180
|
err instanceof Error ? err.message : String(err)
|
|
183
181
|
}`;
|
|
184
182
|
}
|
|
183
|
+
if (verdict === undefined) {
|
|
184
|
+
return 'Render error: worker exited without producing a verdict.';
|
|
185
|
+
}
|
|
185
186
|
|
|
186
187
|
return verdict.ok ? null : verdict.error;
|
|
187
188
|
}
|