@ggui-ai/ui-gen 0.25.0 → 0.27.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.
Files changed (117) hide show
  1. package/README.md +2 -2
  2. package/dist/{adapter-Cg_rkpGq.d.ts → adapter-h4_V69LI.d.ts} +49 -1
  3. package/dist/adapters/base.d.ts +6 -1
  4. package/dist/adapters/base.js.map +1 -1
  5. package/dist/adapters/generation-dispatch.d.ts +8 -5
  6. package/dist/adapters/generation-dispatch.js +1174 -198
  7. package/dist/adapters/generation-dispatch.js.map +1 -1
  8. package/dist/adapters/index.js +59 -16
  9. package/dist/adapters/index.js.map +1 -1
  10. package/dist/advanced/index.d.ts +10 -9
  11. package/dist/advanced/index.js +1215 -216
  12. package/dist/advanced/index.js.map +1 -1
  13. package/dist/blueprint-validator.js +416 -92
  14. package/dist/blueprint-validator.js.map +1 -1
  15. package/dist/boilerplate.d.ts +2 -2
  16. package/dist/boilerplate.js +10 -5
  17. package/dist/boilerplate.js.map +1 -1
  18. package/dist/check/index.d.ts +3 -8
  19. package/dist/check/index.js +55 -2
  20. package/dist/check/index.js.map +1 -1
  21. package/dist/{create-ui-generator-DFnnArrv.d.ts → create-ui-generator-BrZHF0cf.d.ts} +3 -4
  22. package/dist/{design-mode-DLufhrNp.d.ts → design-mode-DGWN_Gkm.d.ts} +1 -1
  23. package/dist/{dispatch-CStl9eAw.d.ts → dispatch-B07TI30d.d.ts} +2 -2
  24. package/dist/evaluation/axis-checks/index.d.ts +5 -4
  25. package/dist/evaluation/axis-checks/index.js.map +1 -1
  26. package/dist/evaluation/axis-checks/registry.d.ts +4 -3
  27. package/dist/evaluation/index.d.ts +124 -546
  28. package/dist/evaluation/index.js +588 -65
  29. package/dist/evaluation/index.js.map +1 -1
  30. package/dist/evaluation/types.d.ts +17 -0
  31. package/dist/evaluation/types.js.map +1 -1
  32. package/dist/{generate-9IeNT4ZY.d.ts → generate-tSO_shQ0.d.ts} +1 -1
  33. package/dist/harness/check/runtime-render/index.d.ts +5 -5
  34. package/dist/harness/check/runtime-render/index.js +416 -92
  35. package/dist/harness/check/runtime-render/index.js.map +1 -1
  36. package/dist/harness/check/runtime-render/render-check-worker.js +393 -88
  37. package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
  38. package/dist/harness/index.d.ts +4 -5
  39. package/dist/harness/index.js +36 -7
  40. package/dist/harness/index.js.map +1 -1
  41. package/dist/harness/prompts.d.ts +2 -2
  42. package/dist/harness/prompts.js +7 -1
  43. package/dist/harness/prompts.js.map +1 -1
  44. package/dist/harness/result-types.d.ts +17 -3
  45. package/dist/harness/result-types.js.map +1 -1
  46. package/dist/harness/runtime.d.ts +14 -6
  47. package/dist/harness/runtime.js +13 -7
  48. package/dist/harness/runtime.js.map +1 -1
  49. package/dist/harness/types-public.d.ts +4 -2
  50. package/dist/harness/types-public.js +10 -5
  51. package/dist/harness/types-public.js.map +1 -1
  52. package/dist/harness/validator-trace-sink.d.ts +4 -3
  53. package/dist/index.d.ts +46 -11
  54. package/dist/index.js +1217 -214
  55. package/dist/index.js.map +1 -1
  56. package/dist/patch.d.ts +3 -3
  57. package/dist/{prompts-YU9piOgs.d.ts → prompts-BhNUuRoZ.d.ts} +1 -1
  58. package/dist/tools/render-check-worker.js +8 -1
  59. package/dist/tools/render-check-worker.js.map +1 -1
  60. package/dist/{types-public-BJTLpzWn.d.ts → types-public-h8IMnYuh.d.ts} +388 -3
  61. package/dist/validation/index.d.ts +1 -1
  62. package/dist/validation/index.js +1 -1
  63. package/dist/validation/index.js.map +1 -1
  64. package/dist/visual-evaluator-C2strxqD.d.ts +639 -0
  65. package/dist/workflows.d.ts +3 -3
  66. package/package.json +12 -12
  67. package/src/adapters/base.ts +6 -1
  68. package/src/adapters/claude/message-parser.ts +5 -6
  69. package/src/adapters/claude/sdk-tool-pins.ts +44 -0
  70. package/src/adapters/claude/sdk.ts +3 -3
  71. package/src/adapters/claude/source-capture.ts +20 -0
  72. package/src/adapters/generation-dispatch.ts +2 -0
  73. package/src/boilerplate/free-design-prompt.ts +3 -3
  74. package/src/boilerplate/hard-sections.ts +1 -1
  75. package/src/boilerplate/system-prompt.ts +3 -2
  76. package/src/boilerplate/templates/layouts/chat-mobile.tsx.tmpl +3 -1
  77. package/src/boilerplate/templates/layouts/chat-universal.tsx.tmpl +3 -1
  78. package/src/boilerplate/templates/layouts/fullscreen-desktop.tsx.tmpl +3 -1
  79. package/src/boilerplate/templates/layouts/fullscreen-universal.tsx.tmpl +3 -1
  80. package/src/check/run-tier0.ts +67 -3
  81. package/src/contract-context.ts +1 -1
  82. package/src/design-system-docs.ts +2 -1
  83. package/src/evaluation/cost-tracker.ts +54 -6
  84. package/src/evaluation/criteria/edge-probe.ts +286 -0
  85. package/src/evaluation/criteria/edge.ts +202 -0
  86. package/src/evaluation/criteria/resolve.ts +74 -8
  87. package/src/evaluation/index.ts +5 -1
  88. package/src/evaluation/judge-spend.ts +48 -0
  89. package/src/evaluation/loop.ts +25 -4
  90. package/src/evaluation/message-parsing.ts +11 -12
  91. package/src/evaluation/types-public.ts +95 -0
  92. package/src/evaluation/types.ts +10 -0
  93. package/src/evaluation/visual-evaluator.ts +236 -40
  94. package/src/harness/check/runtime-render/adapter.ts +18 -0
  95. package/src/harness/check/runtime-render/probe-pending.ts +111 -0
  96. package/src/harness/check/runtime-render/render-check-host.ts +10 -6
  97. package/src/harness/check/runtime-render/render-check-worker.ts +6 -3
  98. package/src/harness/check/runtime-render/render-check.ts +417 -133
  99. package/src/harness/check/runtime-render/walk.ts +200 -0
  100. package/src/harness/coding/assemble-result.ts +2 -0
  101. package/src/harness/coding/generate-task-runner.ts +27 -0
  102. package/src/harness/coding/init-session.ts +5 -3
  103. package/src/harness/coding/run-coding-turn.ts +5 -1
  104. package/src/harness/coding/run-eval-round.ts +63 -3
  105. package/src/harness/in-loop-evaluation.ts +39 -0
  106. package/src/harness/llm-router.ts +58 -1
  107. package/src/harness/result-types.ts +9 -0
  108. package/src/harness/runtime.ts +9 -0
  109. package/src/harness/types-public.ts +10 -1
  110. package/src/index.ts +5 -0
  111. package/src/internal/verdict-line.ts +33 -0
  112. package/src/taught-tokens.ts +31 -0
  113. package/src/tools/render-check-worker.ts +2 -1
  114. package/src/tools/render-check.ts +8 -7
  115. package/src/validation/primitives.ts +1 -1
  116. package/dist/llm-router-nfO7wPec.d.ts +0 -267
  117. package/dist/{bank-CT4eEkta.d.ts → bank-Ctdp3FUA.d.ts} +3 -3
@@ -129,6 +129,7 @@ export type {
129
129
  ScreenshotAttempt,
130
130
  CanvasVisualResult,
131
131
  CriteriaCallTokens,
132
+ VisualLegTokens,
132
133
  CanvasFitPolicy,
133
134
  CaptureMode,
134
135
  ScreenshotDeps,
@@ -168,6 +169,9 @@ export { designTreeSha256, judgeDesignIdentity, resetJudgeDesignIdentityCache, t
168
169
  export { bankRows, criteriaBankSchema, parseCriteriaBank, type BankRow, type CriteriaBank, type CriteriaScope } from './criteria/bank.js';
169
170
  export { CRITERIA_SELECTOR_VERSION, canonicalCriteriaContext, criteriaSetIdFor, selectCriteria, type CriteriaSelectionResult } from './criteria/select.js';
170
171
  export { chromaOfCssTokens, criteriaContextFor, type CriteriaContextInput, type CriteriaContextSources } from './criteria/context.js';
171
- export { INSTRUMENT_BY_ID, buildCriteriaJudgeBlock, resolveCriteriaBlock, type CriteriaMeasurements } from './criteria/resolve.js';
172
+ export { INSTRUMENT_BESIDE_JUDGE, INSTRUMENT_BY_ID, buildCriteriaJudgeBlock, resolveCriteriaBlock, type CriteriaMeasurements } from './criteria/resolve.js';
173
+ export { EDGE_TOLERANCE_PX, judgeEdges, type EdgeBlock, type EdgeOffset, type EdgeVerdict } from './criteria/edge.js';
174
+ export { EDGE_BLOCKS_EXPRESSION, parseEdgeProbe, type EdgeProbe } from './criteria/edge-probe.js';
172
175
  export { judgeStoredCapture, type StoredCaptureInput, type StoredCaptureOutcome, type StoredCaptureVerdict } from './visual-evaluator.js';
176
+ export { judgedFrameTokens, type JudgeSpend, type JudgedTokens } from './judge-spend.js';
173
177
 
@@ -0,0 +1,48 @@
1
+ /**
2
+ * ggui#1640 — one judged frame's (or one visual evaluation's) spend, summed over EVERY leg the judge reports.
3
+ *
4
+ * The judge reports its legs apart: the scoring calls' `inputTokens` / `outputTokens`, and, since ggui#1436, the
5
+ * report-only criteria call's `criteriaTokens`. A caller that sums only the first leg under-counts, and a dollar
6
+ * ceiling built on that sum stops late. {@link judgedFrameTokens} is the one sum. The two `satisfies` tables below list
7
+ * every `*Tokens` member of the results it reads, so a leg added to either result type fails the build here until the
8
+ * sum counts it.
9
+ */
10
+ import type { CriteriaCallTokens, StoredCaptureVerdict, VisualEvaluationResult } from './visual-evaluator.js';
11
+
12
+ /** A spend in tokens: input and output, over every leg. */
13
+ export interface JudgeSpend {
14
+ readonly inputTokens: number;
15
+ readonly outputTokens: number;
16
+ }
17
+
18
+ /** The members of `T` whose names end in `Tokens`. */
19
+ type TokenMember<T> = { [K in keyof T]-?: K extends `${string}Tokens` ? K : never }[keyof T];
20
+
21
+ /** Which leg each `*Tokens` member belongs to. A member missing here, or listed and gone, is a compile error. */
22
+ const STORED_VERDICT_LEGS = {
23
+ inputTokens: 'scoring',
24
+ outputTokens: 'scoring',
25
+ criteriaTokens: 'criteria',
26
+ } as const satisfies Record<TokenMember<StoredCaptureVerdict>, 'scoring' | 'criteria'>;
27
+ const VISUAL_RESULT_LEGS = {
28
+ inputTokens: 'scoring',
29
+ outputTokens: 'scoring',
30
+ criteriaTokens: 'criteria',
31
+ } as const satisfies Record<TokenMember<VisualEvaluationResult>, 'scoring' | 'criteria'>;
32
+ void STORED_VERDICT_LEGS;
33
+ void VISUAL_RESULT_LEGS;
34
+
35
+ /** The legs {@link judgedFrameTokens} reads, as both result types carry them. */
36
+ export interface JudgedTokens {
37
+ readonly inputTokens?: number;
38
+ readonly outputTokens?: number;
39
+ readonly criteriaTokens?: CriteriaCallTokens;
40
+ }
41
+
42
+ /** A judged frame's spend over every leg: the scoring calls plus the report-only criteria call, when one ran. */
43
+ export function judgedFrameTokens(result: JudgedTokens): JudgeSpend {
44
+ return {
45
+ inputTokens: (result.inputTokens ?? 0) + (result.criteriaTokens?.inputTokens ?? 0),
46
+ outputTokens: (result.outputTokens ?? 0) + (result.criteriaTokens?.outputTokens ?? 0),
47
+ };
48
+ }
@@ -1,6 +1,7 @@
1
1
  // packages/ui-gen/src/evaluation/loop.ts
2
2
 
3
3
  import { query, type McpServerConfig } from '@anthropic-ai/claude-agent-sdk';
4
+ import { sdkToolPins } from '../adapters/claude/sdk-tool-pins';
4
5
  import { runEvaluation } from './evaluator';
5
6
  import {
6
7
  extractCompiledCodeFromMessage,
@@ -39,8 +40,13 @@ export interface EvaluationLoopOptions {
39
40
  cwd?: string;
40
41
  /** MCP servers available to the fix agent */
41
42
  mcpServers?: Record<string, McpServerConfig>;
42
- /** Tools the fix agent is allowed to call */
43
+ /** Tool names to approve. Absent → every tool of every server in `mcpServers` (ggui#1714). */
43
44
  allowedTools?: string[];
45
+ /**
46
+ * CLI built-in tools to offer the fix agent (e.g. `['Write']`). Default: none.
47
+ * An explicit opt-in: a built-in that writes or executes acts on the host (ggui#1714).
48
+ */
49
+ builtinTools?: readonly string[];
44
50
  /** LLM model for fix rounds */
45
51
  model?: string;
46
52
  /** Environment variables (includes BYOK credentials) */
@@ -90,6 +96,18 @@ export async function runEvaluationLoop(
90
96
  let round = 0;
91
97
  const maxRounds = Math.min(config.maxRounds ?? 3, MAX_EVAL_ROUNDS_HARD_LIMIT);
92
98
 
99
+ // ggui#1714 — a fix round runs with no CLI built-ins unless asked for. With no
100
+ // MCP server and no built-in either, the fix agent has nothing that can return
101
+ // code, and every fix round would silently hand back the original: refuse.
102
+ const offersATool =
103
+ Object.keys(generatorOptions?.mcpServers ?? {}).length > 0 || (generatorOptions?.builtinTools?.length ?? 0) > 0;
104
+ if (maxRounds > 1 && !offersATool) {
105
+ throw new Error(
106
+ 'runEvaluationLoop: a fix round needs a tool that can return code — pass generatorOptions.mcpServers ' +
107
+ "(e.g. a server with compile_component) or opt into a built-in with generatorOptions.builtinTools (e.g. ['Write']).",
108
+ );
109
+ }
110
+
93
111
  while (round < maxRounds) {
94
112
  round++;
95
113
 
@@ -145,12 +163,15 @@ export async function runEvaluationLoop(
145
163
  maxTurns: 15,
146
164
  maxBudgetUsd: config.maxBudgetPerFix,
147
165
  env,
148
- permissionMode: 'bypassPermissions',
149
- allowDangerouslySkipPermissions: true,
150
166
  ...(cliPath && { pathToClaudeCodeExecutable: cliPath }),
151
167
  ...(generatorOptions?.cwd && { cwd: generatorOptions.cwd }),
152
168
  ...(generatorOptions?.mcpServers && { mcpServers: generatorOptions.mcpServers }),
153
- ...(generatorOptions?.allowedTools && { allowedTools: generatorOptions.allowedTools }),
169
+ // ggui#1714 — no CLI built-ins unless asked for, no host settings, no permission bypass.
170
+ ...sdkToolPins({
171
+ mcpServers: generatorOptions?.mcpServers,
172
+ allowedTools: generatorOptions?.allowedTools,
173
+ builtinTools: generatorOptions?.builtinTools,
174
+ }),
154
175
  ...(generatorOptions?.model && { model: generatorOptions.model }),
155
176
  ...(generatorOptions?.stderr && { stderr: generatorOptions.stderr }),
156
177
  },
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  import type { EvaluationResult } from './types';
13
+ import { sourceCodeFromToolUse } from '../adapters/claude/source-capture';
13
14
 
14
15
  // ── SDK message type aliases ────────────────────────────────────────
15
16
 
@@ -137,13 +138,14 @@ export function extractCompiledCodeFromMessage(message: SdkMessage): string | un
137
138
  // ── sourceCode extraction ───────────────────────────────────────────
138
139
 
139
140
  /**
140
- * Extract source code from an assistant's Write tool_use message.
141
+ * Extract source code from an assistant message's tool_use blocks.
141
142
  *
142
- * Looks for `tool_use` blocks with `name === 'Write'` and returns
143
- * the `input.content` string. If multiple Write calls exist, returns
144
- * the last one (the final version).
143
+ * Reads the bridged compile tool (`*__compile_component`, `input.code`)
144
+ * and the CLI's `Write` (`input.content`, offered only by opt-in), by the
145
+ * one rule in `adapters/claude/source-capture.ts` (ggui#1714). If several
146
+ * calls carry source, returns the last one (the final version).
145
147
  *
146
- * Used by: loop.ts, generator.ts
148
+ * Used by: loop.ts
147
149
  */
148
150
  export function extractSourceCodeFromMessage(message: SdkMessage): string | undefined {
149
151
  if ((message.type as string) !== 'assistant') return undefined;
@@ -155,12 +157,9 @@ export function extractSourceCodeFromMessage(message: SdkMessage): string | unde
155
157
  if (!Array.isArray(content)) return undefined;
156
158
 
157
159
  for (const block of content) {
158
- if (block.type === 'tool_use' && block.name === 'Write') {
159
- const input = block.input as { content?: string } | undefined;
160
- if (input?.content) {
161
- sourceCode = input.content;
162
- }
163
- }
160
+ if (block.type !== 'tool_use') continue;
161
+ const value = sourceCodeFromToolUse(block.name, block.input);
162
+ if (value !== undefined) sourceCode = value;
164
163
  }
165
164
 
166
165
  return sourceCode;
@@ -183,7 +182,7 @@ export function extractCompiledCode(messages: SdkMessage[]): string | undefined
183
182
  }
184
183
 
185
184
  /**
186
- * Scan all messages for the last sourceCode from Write tool_use.
185
+ * Scan all messages for the last sourceCode a tool_use carried.
187
186
  *
188
187
  * Convenience wrapper for tests and one-shot extraction.
189
188
  */
@@ -20,6 +20,7 @@
20
20
 
21
21
  export type Priority = "P0" | "P1" | "P2";
22
22
 
23
+ import type { VisionFinishReason } from '../harness/llm-router.js';
23
24
  import type { GenerationRuntimeProbeOutcome, GenerationRuntimeProbeStatus } from "@ggui-ai/mcp-server-core";
24
25
  import type { CanvasClass, DesignMode } from "../design-mode.js";
25
26
 
@@ -61,6 +62,11 @@ export interface EvalIssue {
61
62
  description: string;
62
63
  fix: string;
63
64
  line?: number;
65
+ /**
66
+ * The visual leg's provenance (ggui#1545): `judge` (the model's finding) or `instrument` (a deterministic
67
+ * measurement). Absent on every other leg's issues. `visualEvaluation.actOn` acts on `judge` only.
68
+ */
69
+ origin?: "judge" | "instrument";
64
70
  }
65
71
 
66
72
  /**
@@ -100,6 +106,23 @@ export interface ProbeHostLoad {
100
106
  readonly cores: number;
101
107
  }
102
108
 
109
+ /**
110
+ * ggui#1398 — whether the controls the probe clicked showed they were
111
+ * working once their action was pending. Report-only: it is read by the
112
+ * benchmark and never counted into the probe's warnings or fed back to
113
+ * the coding loop.
114
+ */
115
+ export interface ProbePendingAffordance {
116
+ /** Controls the pending phase marked pending: those whose click dispatched their action (click or submit wiring) and that were still in the page. `visible + missing.length === dispatched`. */
117
+ readonly dispatched: number;
118
+ /** Of those, how many visibly changed while pending (disabled, busy, text, or replaced). */
119
+ readonly visible: number;
120
+ /** The actions whose control did not visibly change, including one whose card threw while pending. */
121
+ readonly missing: readonly string[];
122
+ /** Actions whose control was no longer in the page when its turn came (the card replaced it, or blanked after a throw): never marked, not counted as dispatched. */
123
+ readonly gone: readonly string[];
124
+ }
125
+
103
126
  /**
104
127
  * The engine's own fields on a probe's meta, beside the outcome a
105
128
  * generation's metadata reports: why a probe did not finish, its render
@@ -116,6 +139,11 @@ export interface RuntimeProbeMetaDetail {
116
139
  readonly hostLoad?: ProbeHostLoad;
117
140
  /** Time the check waited for a probe slot before it started, ms; present only when > 0 (ggui#1380 C2b). */
118
141
  readonly queuedMs?: number;
142
+ /**
143
+ * `ran` only: the pending-affordance walk (ggui#1398). Absent when no
144
+ * action walk ran, so "no data" and "0 of 0" read differently.
145
+ */
146
+ readonly pendingAffordance?: ProbePendingAffordance;
119
147
  }
120
148
 
121
149
  /**
@@ -334,6 +362,58 @@ export interface CanvasJudgeRecord {
334
362
  readonly sigma: number;
335
363
  /** The judge's per-canvas critique per sample — the words beside a swing. */
336
364
  readonly notes: string[];
365
+ /**
366
+ * ggui#1127 — the cause of each scoring sample whose answer failed to parse and was SALVAGED: its four dimensions
367
+ * were recovered from the closed prefix, its issues kept only as far as they closed, its critique lost. One entry
368
+ * per salvaged sample; absent when every sample parsed. The salvaged sample's score still counts.
369
+ */
370
+ readonly salvaged?: readonly JudgeSalvageCause[];
371
+ /**
372
+ * ggui#1127 — the criteria call's answer was salvaged. A salvage keeps no criteria, so that frame's judge rows read
373
+ * "not answered" with this cause rather than a clean `n/a`. Absent when the call parsed or was not asked.
374
+ */
375
+ readonly criteriaSalvaged?: JudgeSalvageCause;
376
+ /**
377
+ * ggui#1687 — the model asked for, and every model the provider said answered this canvas's calls (scoring and
378
+ * criteria), deduplicated. `served` is empty when no provider named one: unknown, never "the requested model".
379
+ * When the request names an alias and the provider echoes the alias (measured on a hosted route, 125 of 125
380
+ * canvases), `served` confirms the alias was honoured but cannot tell two snapshots behind it apart. A judge that
381
+ * must is pinned to a dated snapshot id instead. With the alias, a snapshot change shows only as a step on a date in
382
+ * the judge's pooled within-call spread, read by week. That is a read someone runs, not a monitor.
383
+ */
384
+ readonly models?: { readonly requested: string; readonly served: readonly string[] };
385
+ }
386
+
387
+ /**
388
+ * ggui#1687 — what the criteria call's answer held, before resolution. A row the judge left unanswered reads `n/a`
389
+ * either way; this says whether the call came back empty, cut, or with entries the parser had to drop.
390
+ */
391
+ export interface CriteriaAnswerCounts {
392
+ /** The answer carried a `criteria` array. `false`: absent or not an array, so no row was read from it. */
393
+ readonly array: boolean;
394
+ /** Entries in the array. */
395
+ readonly received: number;
396
+ /** Entries kept: an object with a string id and a verdict of pass, fail or n/a. */
397
+ readonly kept: number;
398
+ /** Entries dropped, by why. */
399
+ readonly dropped: { readonly notObject: number; readonly badId: number; readonly badVerdict: number };
400
+ }
401
+
402
+ /**
403
+ * ggui#1127 — why a judge answer that failed to parse had to be salvaged. `output-cap`: the provider reported it
404
+ * stopped at the output cap. `malformed`: the provider reported a normal stop, so the answer ended but did not
405
+ * parse. `stopped`: the provider stopped it for another reason it named (a safety stop, or one it reports as
406
+ * other), so it is cut, not malformed. `unknown`: the provider or route reported no stop reason. Never inferred
407
+ * from the answer's length.
408
+ */
409
+ export type JudgeSalvageCause = "output-cap" | "malformed" | "stopped" | "unknown";
410
+
411
+ /** ggui#1127 — salvaged judge answers across a canvas run, per leg, as N of M. */
412
+ export interface JudgeSalvageCounts {
413
+ /** Scoring answers: `answers` = samples that parsed or were salvaged, `salvaged` = those salvaged. */
414
+ readonly scoring: { readonly salvaged: number; readonly answers: number };
415
+ /** Criteria calls: `answered` = calls that parsed or were salvaged, `salvaged` = those salvaged (criteria lost). */
416
+ readonly criteria: { readonly salvaged: number; readonly answered: number };
337
417
  }
338
418
 
339
419
  // ─── Visual criteria (ggui#1436) ──────────────────────────────────────────
@@ -393,6 +473,19 @@ export interface CriterionVerdict {
393
473
  source: CriteriaSource;
394
474
  verdict: CriteriaVerdict;
395
475
  evidence: string;
476
+ /**
477
+ * ggui#1663 — a measured reading beside a judge row, REPORT-ONLY: the row's `verdict` stays the judge's, so this
478
+ * changes no verdict, score or bar. Present only on rows an instrument answers beside the judge (`space.edge`).
479
+ */
480
+ instrument?: { verdict: CriteriaVerdict; evidence: string };
481
+ }
482
+ /** ggui#1687 — the criteria call as it came back: its answer's counts, ids it answered outside the bank, its stop. */
483
+ export interface CriteriaCallRecord {
484
+ readonly answer: CriteriaAnswerCounts;
485
+ /** Kept entries whose id names no row of the bank. */
486
+ readonly unknownIds: number;
487
+ /** The provider's normalized stop reason (`length` = the output cap); absent when it reported none. */
488
+ readonly finishReason?: VisionFinishReason;
396
489
  }
397
490
  export interface CriteriaBlock {
398
491
  /** sha256(bank version | selector version | canonical context), 16 hex — the block's own digest. */
@@ -402,6 +495,8 @@ export interface CriteriaBlock {
402
495
  context: CriteriaContext;
403
496
  selection: CriteriaSelection[];
404
497
  verdicts: CriterionVerdict[];
498
+ /** ggui#1687 — the call that answered this block, when one did. Absent for a block resolved with no call. */
499
+ call?: CriteriaCallRecord;
405
500
  }
406
501
  /** The roll-up across canvases; a `must` at `n/a` is listed, never counted as a pass. */
407
502
  export interface CriteriaRollup {
@@ -1,3 +1,4 @@
1
+ import type { CriteriaAnswerCounts } from './types-public.js';
1
2
  // packages/ui-gen/src/evaluation/types.ts
2
3
 
3
4
  /**
@@ -28,6 +29,13 @@ export interface EvaluationIssue {
28
29
  severity: 'critical' | 'major' | 'minor';
29
30
  /** Suggested fix for the agent to apply */
30
31
  fix: string;
32
+ /**
33
+ * Who produced it, on the visual leg (ggui#1545): `judge` for the model's own findings, `instrument` for a
34
+ * deterministic measurement (fit, overflow, blank). Absent on issues from other evaluators. The loop's
35
+ * `visualEvaluation.actOn` acts on `judge` findings only, so a new instrument check never joins the treatment
36
+ * by accident: it has to say it is a judge finding, and absence reads as "not acted on".
37
+ */
38
+ origin?: 'judge' | 'instrument';
31
39
  }
32
40
 
33
41
  /**
@@ -57,6 +65,8 @@ export interface EvaluationResult {
57
65
  outputTokens?: number;
58
66
  /** ggui#1436 — the judge's criteria answers, when the prompt asked for them; never part of the score. */
59
67
  criteriaAnswers?: CriteriaAnswer[];
68
+ /** ggui#1687 — what the answer's criteria array held and what the parser dropped; set whenever the answer parsed. */
69
+ criteriaCounts?: CriteriaAnswerCounts;
60
70
  }
61
71
 
62
72
  /**