@ggui-ai/ui-gen 0.21.0 → 0.23.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 (87) hide show
  1. package/dist/adapters/generation-dispatch.d.ts +1 -1
  2. package/dist/adapters/generation-dispatch.js +253 -76
  3. package/dist/adapters/generation-dispatch.js.map +1 -1
  4. package/dist/adapters/index.js +36 -11
  5. package/dist/adapters/index.js.map +1 -1
  6. package/dist/advanced/index.d.ts +3 -3
  7. package/dist/advanced/index.js +447 -76
  8. package/dist/advanced/index.js.map +1 -1
  9. package/dist/blueprint-validator.js +37 -7
  10. package/dist/blueprint-validator.js.map +1 -1
  11. package/dist/boilerplate.js +1 -0
  12. package/dist/boilerplate.js.map +1 -1
  13. package/dist/check/index.d.ts +1 -1
  14. package/dist/check/index.js.map +1 -1
  15. package/dist/{create-ui-generator-D7kOXNjJ.d.ts → create-ui-generator-D0kbWwln.d.ts} +1 -1
  16. package/dist/{dispatch-DGvhmARp.d.ts → dispatch-wxaIMwZt.d.ts} +1 -1
  17. package/dist/evaluation/axis-checks/index.d.ts +3 -3
  18. package/dist/evaluation/axis-checks/index.js +19 -4
  19. package/dist/evaluation/axis-checks/index.js.map +1 -1
  20. package/dist/evaluation/axis-checks/registry.d.ts +1 -1
  21. package/dist/evaluation/axis-checks/registry.js +19 -4
  22. package/dist/evaluation/axis-checks/registry.js.map +1 -1
  23. package/dist/evaluation/index.d.ts +3 -3
  24. package/dist/evaluation/index.js +58 -26
  25. package/dist/evaluation/index.js.map +1 -1
  26. package/dist/harness/check/runtime-render/index.d.ts +4 -4
  27. package/dist/harness/check/runtime-render/index.js +26 -6
  28. package/dist/harness/check/runtime-render/index.js.map +1 -1
  29. package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
  30. package/dist/harness/index.d.ts +1 -1
  31. package/dist/harness/index.js +37 -11
  32. package/dist/harness/index.js.map +1 -1
  33. package/dist/harness/prompts.js +1 -1
  34. package/dist/harness/prompts.js.map +1 -1
  35. package/dist/harness/result-types.d.ts +15 -2
  36. package/dist/harness/result-types.js.map +1 -1
  37. package/dist/harness/runtime.d.ts +1 -1
  38. package/dist/harness/runtime.js +2 -1
  39. package/dist/harness/runtime.js.map +1 -1
  40. package/dist/harness/types-public.d.ts +9 -5
  41. package/dist/harness/types-public.js +1 -0
  42. package/dist/harness/types-public.js.map +1 -1
  43. package/dist/harness/validator-trace-sink.d.ts +1 -1
  44. package/dist/index.d.ts +22 -6
  45. package/dist/index.js +436 -75
  46. package/dist/index.js.map +1 -1
  47. package/dist/patch.d.ts +1 -1
  48. package/dist/{render-check-C0CqVceF.d.ts → render-check-Blwj8gB6.d.ts} +18 -0
  49. package/dist/tools/get-primitives-ts.d.ts +1 -1
  50. package/dist/tools/get-primitives-ts.js +1 -1
  51. package/dist/tools/get-primitives-ts.js.map +1 -1
  52. package/dist/{types-public-CzYqZclY.d.ts → types-public-Ow-tNtFE.d.ts} +41 -3
  53. package/dist/validation/index.d.ts +1 -1
  54. package/dist/validation/index.js +1 -1
  55. package/dist/validation/index.js.map +1 -1
  56. package/dist/workflows.d.ts +1 -1
  57. package/package.json +12 -12
  58. package/src/adapters/claude/claude-code-login.ts +62 -7
  59. package/src/adapters/claude/sdk.ts +6 -5
  60. package/src/advanced/generator.ts +16 -1
  61. package/src/blueprint-validator.ts +21 -6
  62. package/src/boilerplate/hard-sections.ts +1 -0
  63. package/src/boilerplate/templates/layouts/chat-mobile.tsx.tmpl +7 -3
  64. package/src/boilerplate/templates/layouts/chat-universal.tsx.tmpl +7 -3
  65. package/src/coding-agent/tools.ts +6 -1
  66. package/src/contract-context.ts +1 -1
  67. package/src/create-ui-generator.ts +30 -0
  68. package/src/evaluation/axis-checks/checks/state-payload.ts +29 -4
  69. package/src/evaluation/index.ts +1 -0
  70. package/src/evaluation/llm-evaluator.ts +40 -13
  71. package/src/evaluation/types-public.ts +42 -2
  72. package/src/evaluation/visual-evaluator.ts +9 -6
  73. package/src/generator-build.ts +158 -0
  74. package/src/harness/check/runtime-render/adapter.ts +19 -0
  75. package/src/harness/check/runtime-render/render-check-host.ts +31 -9
  76. package/src/harness/check/runtime-render/render-check.ts +19 -0
  77. package/src/harness/claude-code-login-query.ts +5 -4
  78. package/src/harness/coding/assemble-result.ts +8 -1
  79. package/src/harness/coding/generate-task-runner.ts +75 -36
  80. package/src/harness/coding/run-coding-turn.ts +9 -0
  81. package/src/harness/coding/run-eval-round.ts +162 -5
  82. package/src/harness/llm-router.ts +3 -3
  83. package/src/harness/result-types.ts +14 -0
  84. package/src/harness/types-public.ts +9 -5
  85. package/src/index.ts +2 -1
  86. package/src/tools/get-primitives-ts.ts +1 -1
  87. package/src/validation/primitives.ts +1 -1
@@ -22,7 +22,7 @@ import { listContractGadgets } from "@ggui-ai/protocol";
22
22
  import type { Classification } from "../../classifier/index.js";
23
23
  import type { AgentWorkspace } from "../../coding-agent/workspace.js";
24
24
  import type { CostTracker } from "../../evaluation/cost-tracker.js";
25
- import type { EvalIssue, EvalResult, RuntimeProbeMeta, VisualEvalSummary, VisualCoverage } from "../../evaluation/types-public.js";
25
+ import type { ContractFeedbackRecord, EvalIssue, EvalResult, RuntimeProbeMeta, VisualEvalSummary, VisualCoverage } from "../../evaluation/types-public.js";
26
26
  import { notApplicableCoverage } from "../../evaluation/types-public.js";
27
27
  import { mapProviderForEvaluator, visionJudgeProvider } from "../enforced-coding.js";
28
28
  import { runCheck } from "../index.js";
@@ -40,6 +40,7 @@ import type {
40
40
  type PreWarmedEvalContext =
41
41
  import("../../evaluation/llm-evaluator.js").PreWarmedEvalContext;
42
42
  type LlmEvalMod = typeof import("../../evaluation/llm-evaluator.js");
43
+ type LLMEvalTokenUsage = import("../../evaluation/llm-evaluator.js").LLMEvalTokenUsage;
43
44
  type VisualEvalMod = typeof import("../../evaluation/visual-evaluator.js");
44
45
 
45
46
  /**
@@ -100,6 +101,20 @@ export interface EvalRoundInput {
100
101
  */
101
102
  export type EvalRoundControl = "break" | "feedback";
102
103
 
104
+ /**
105
+ * One call's (or one round's) token accounting. `input` is the NON-cached
106
+ * input; the cache counts are present only when the provider reported them —
107
+ * absent means unreported, never zero. The runner folds every coding turn and
108
+ * every eval round through the same rule (`absorbTokens`), so the published
109
+ * `tokens.total` holds one convention across both (ggui#1281, #1186).
110
+ */
111
+ export interface TokenUsage {
112
+ readonly input: number;
113
+ readonly output: number;
114
+ readonly cacheRead?: number;
115
+ readonly cacheCreation?: number;
116
+ }
117
+
103
118
  export interface EvalRoundResult {
104
119
  readonly control: EvalRoundControl;
105
120
  /** True only on the low-risk bypass + clean-pass paths. */
@@ -115,7 +130,7 @@ export interface EvalRoundResult {
115
130
  readonly prevFailFingerprints: Set<string>;
116
131
  readonly preWarmedContext: PreWarmedEvalContext | null | undefined;
117
132
  /** Tokens added during this round — caller adds to its own totals. */
118
- readonly evalTokens: { input: number; output: number };
133
+ readonly evalTokens: TokenUsage;
119
134
  /** Wall-clock of the parallel LLM + visual eval block (zero on the
120
135
  * low-risk bypass path, which exits before `Promise.all`). Caller
121
136
  * accumulates into its `cumulativeEvalLlmMs`. */
@@ -124,6 +139,13 @@ export interface EvalRoundResult {
124
139
  readonly lastResultText: string;
125
140
  readonly isEvalFeedback: boolean;
126
141
  readonly lastDiffFailed: boolean;
142
+ /**
143
+ * ggui#1261 — set ONLY on the round that granted a contract-feedback round:
144
+ * what bought it and the source it was fed back on. The caller keeps it past
145
+ * this round (the next round rebuilds `evalResult`) and stamps it on the
146
+ * result the generation ends with.
147
+ */
148
+ readonly contractFeedback?: ContractFeedbackRecord;
127
149
  }
128
150
 
129
151
  /** Cap feedback to N issues per eval-fix turn so the LLM stays surgical. */
@@ -337,6 +359,8 @@ async function runProbeAtExit(input: {
337
359
  const meta: RuntimeProbeMeta = {
338
360
  status: outcome.status,
339
361
  ...(outcome.reason !== undefined ? { reason: outcome.reason } : {}),
362
+ ...(outcome.elapsedMs !== undefined ? { elapsedMs: outcome.elapsedMs } : {}),
363
+ ...(outcome.hostLoad !== undefined ? { hostLoad: outcome.hostLoad } : {}),
340
364
  };
341
365
  if (outcome.status !== "ran") {
342
366
  return { fired: false, recoverableFail: false, probeIssues: [], meta };
@@ -368,6 +392,42 @@ function formatRuntimeProbeFeedback(issue: EvalIssue): string {
368
392
  );
369
393
  }
370
394
 
395
+ /**
396
+ * Exit-probe checks whose FAIL buys ONE feedback round although it is not a
397
+ * crash: a contract defect the probe diagnoses deterministically, with a fix
398
+ * text the model can act on in a turn. Without the round the loop recorded the
399
+ * finding and shipped it — its own evaluator named the defect and the model
400
+ * never heard. Named, and grown only with evidence: `prop-sensitivity` (a
401
+ * declared prop ignored, a literal baked in its place — "You" for
402
+ * `currentUser`) is the class measured failing across models (ggui#1261).
403
+ */
404
+ const FEEDBACK_PROBE_CHECKS: readonly string[] = ["prop-sensitivity"];
405
+
406
+ /** Rounds past the cap the contract-feedback round may use: one, as the fit round (ggui#1195). */
407
+ const CONTRACT_FEEDBACK_BONUS = 1;
408
+
409
+ /** `runtime:<check>` or `runtime:<check>:<subject>` for a named feedback check, FAIL only. */
410
+ function isFeedbackProbeFail(issue: EvalIssue): boolean {
411
+ const sub = issue.subcategory;
412
+ if (issue.result !== "fail" || typeof sub !== "string") return false;
413
+ return FEEDBACK_PROBE_CHECKS.some((check) => sub === `runtime:${check}` || sub.startsWith(`runtime:${check}:`));
414
+ }
415
+
416
+ /**
417
+ * The exit probe's feedback-worthy FAILs this generation has not been told
418
+ * about yet. A finding whose fingerprint already rode a feedback round and
419
+ * recurred is left out — one attempt per finding, then it ships as recorded.
420
+ */
421
+ /** The round's record: the findings that bought it, and the source they were found on. */
422
+ function contractFeedbackRecord(fails: readonly EvalIssue[], sourceBefore: string): ContractFeedbackRecord {
423
+ return { firedOn: fails.flatMap((i) => (i.subcategory !== undefined ? [i.subcategory] : [])), sourceBefore };
424
+ }
425
+
426
+ function contractFeedbackFails(exitProbe: ProbeAtExitResult, alreadyFedBack: ReadonlySet<string>): EvalIssue[] {
427
+ if (!exitProbe.fired) return [];
428
+ return exitProbe.probeIssues.filter((i) => isFeedbackProbeFail(i) && !alreadyFedBack.has(fingerprintFail(i)));
429
+ }
430
+
371
431
  /**
372
432
  * Run one eval round. Mutates `ctx.costTracker` (records LLM eval tokens)
373
433
  * and `ctx.workspace` is read-only from this function's POV. Returns
@@ -404,7 +464,7 @@ export async function runEvalRound(
404
464
  const currentSource = workspace.read() ?? "";
405
465
 
406
466
  let evalResult: EvalResult | undefined;
407
- let evalTokens = { input: 0, output: 0 };
467
+ let evalTokens: TokenUsage = { input: 0, output: 0 };
408
468
  let evalLlmMs = 0;
409
469
 
410
470
  try {
@@ -528,7 +588,7 @@ export async function runEvalRound(
528
588
  }
529
589
 
530
590
  // ── Tier 1+2: LLM evaluation + Visual eval in parallel ──
531
- let llmResult: (EvalResult & { inputTokens: number; outputTokens: number }) | null = null;
591
+ let llmResult: (EvalResult & LLMEvalTokenUsage) | null = null;
532
592
  let visualIssues: EvalIssue[] | null = null;
533
593
 
534
594
  // The visual leg's page and frame — ONE value each, read by the judge and
@@ -629,6 +689,8 @@ export async function runEvalRound(
629
689
  evalTokens = {
630
690
  input: llmResult.inputTokens,
631
691
  output: llmResult.outputTokens,
692
+ ...(llmResult.cacheReadTokens !== undefined ? { cacheRead: llmResult.cacheReadTokens } : {}),
693
+ ...(llmResult.cacheCreationTokens !== undefined ? { cacheCreation: llmResult.cacheCreationTokens } : {}),
632
694
  };
633
695
  }
634
696
 
@@ -748,6 +810,40 @@ export async function runEvalRound(
748
810
  lastDiffFailed: false,
749
811
  };
750
812
  }
813
+ // ggui#1261 — a contract defect the probe diagnosed (a named check,
814
+ // FAIL) that this generation has not been told about: ONE feedback
815
+ // round with the probe's own diagnosis and fix. The next round re-runs
816
+ // the probe at its exit, so the result this generation ends with
817
+ // carries the post-fix stamp, never this one.
818
+ const contractFails = contractFeedbackFails(exitProbe, prevFailFingerprints);
819
+ if (contractFails.length > 0 && evalRoundsUsed < maxEvalRounds + CONTRACT_FEEDBACK_BONUS) {
820
+ const lines = contractFails.slice(0, MAX_FEEDBACK_ISSUES).map(formatRuntimeProbeFeedback);
821
+ evalResult = {
822
+ ...evalResult,
823
+ issues: [...allIssues, ...exitProbe.probeIssues],
824
+ pass: allPass,
825
+ runtimeProbe: exitProbe.meta,
826
+ };
827
+ console.log(
828
+ `[simple] eval round ${evalRoundsUsed}: tier-1/2 clean BUT contract probe fail — granting +1 turn ` +
829
+ `(ggui#1261; ${contractFails.map((i) => i.subcategory).join(", ")})`,
830
+ );
831
+ return {
832
+ control: "feedback",
833
+ evalDone: false,
834
+ evalResult,
835
+ evalRoundsUsed,
836
+ prevModeSubcats: updatedPrevModeSubcats,
837
+ prevFailFingerprints: new Set([...currFailFingerprints, ...contractFails.map(fingerprintFail)]),
838
+ preWarmedContext,
839
+ evalTokens,
840
+ evalLlmMs,
841
+ lastResultText: lines.join("\n\n"),
842
+ isEvalFeedback: true,
843
+ lastDiffFailed: false,
844
+ contractFeedback: contractFeedbackRecord(contractFails, currentSource),
845
+ };
846
+ }
751
847
  // Probe didn't trip a recoverable fail — fold its issues (if any)
752
848
  // into the merged result and ALWAYS stamp its execution meta, so a
753
849
  // did-not-run probe is visible downstream even on this clean exit.
@@ -811,7 +907,8 @@ export async function runEvalRound(
811
907
  // the runtimeProbe stamp lands (Exp 48 found this exit surfacing
812
908
  // as "SKIP — no runtimeProbe stamp" in the bench: honest, but a
813
909
  // probe here is cheap and the shipped code deserves the signal).
814
- // No feedback is granted — stuck means the LLM stopped converging.
910
+ // The RECURRING fails get no more feedback — stuck means the model
911
+ // saw them and stopped converging on them.
815
912
  const stuckExitProbe = await runProbeAtExit({
816
913
  harness,
817
914
  sourceCode: currentSource,
@@ -824,6 +921,36 @@ export async function runEvalRound(
824
921
  issues: [...evalResult.issues, ...stuckExitProbe.probeIssues],
825
922
  runtimeProbe: stuckExitProbe.meta,
826
923
  };
924
+ // ggui#1261 — a contract defect the exit probe diagnosed here was never
925
+ // DELIVERED: the stuck set is non-runtime by construction (runtime-only
926
+ // sets are exempt above), and the stuck premise is "the model saw these
927
+ // and did not resolve them". So it gets the same one round as at the
928
+ // clean-PASS and cap exits — the probe's diagnosis only, never the
929
+ // recurring fail; one attempt per finding; bounded at the cap + 1 (Exp
930
+ // 011, candidate r5: the one FAIL that shipped without a round).
931
+ const stuckContractFails = contractFeedbackFails(stuckExitProbe, prevFailFingerprints);
932
+ if (stuckContractFails.length > 0 && evalRoundsUsed < maxEvalRounds + CONTRACT_FEEDBACK_BONUS) {
933
+ const lines = stuckContractFails.slice(0, MAX_FEEDBACK_ISSUES).map(formatRuntimeProbeFeedback);
934
+ console.log(
935
+ `[simple] eval round ${evalRoundsUsed}: stuck BUT contract probe fail never delivered — granting +1 turn ` +
936
+ `(ggui#1261; ${stuckContractFails.map((i) => i.subcategory).join(", ")})`,
937
+ );
938
+ return {
939
+ control: "feedback",
940
+ evalDone: false,
941
+ evalResult,
942
+ evalRoundsUsed,
943
+ prevModeSubcats: updatedPrevModeSubcats,
944
+ prevFailFingerprints: new Set([...currFailFingerprints, ...stuckContractFails.map(fingerprintFail)]),
945
+ preWarmedContext,
946
+ evalTokens,
947
+ evalLlmMs,
948
+ lastResultText: lines.join("\n\n"),
949
+ isEvalFeedback: true,
950
+ lastDiffFailed: false,
951
+ contractFeedback: contractFeedbackRecord(stuckContractFails, currentSource),
952
+ };
953
+ }
827
954
  return {
828
955
  control: "break",
829
956
  evalDone: false,
@@ -909,6 +1036,36 @@ export async function runEvalRound(
909
1036
  lastDiffFailed: false,
910
1037
  };
911
1038
  }
1039
+ // ggui#1261 — a named contract FAIL from the exit probe active at the
1040
+ // cap: the same one round as at the clean exit, bounded at the cap + 1.
1041
+ const capContractFails = allowRuntimeExtension ? [] : contractFeedbackFails(exitProbe, prevFailFingerprints);
1042
+ if (capContractFails.length > 0 && evalRoundsUsed < maxEvalRounds + CONTRACT_FEEDBACK_BONUS) {
1043
+ const contractLines = capContractFails.slice(0, MAX_FEEDBACK_ISSUES).map(formatRuntimeProbeFeedback);
1044
+ evalResult = {
1045
+ ...evalResult,
1046
+ issues: [...evalResult.issues, ...exitProbe.probeIssues],
1047
+ runtimeProbe: exitProbe.meta,
1048
+ };
1049
+ console.log(
1050
+ `[simple] eval round ${evalRoundsUsed}: contract probe fail active at cap — granting +1 retry ` +
1051
+ `(ggui#1261; ${capContractFails.map((i) => i.subcategory).join(", ")})`,
1052
+ );
1053
+ return {
1054
+ control: "feedback",
1055
+ evalDone: false,
1056
+ evalResult,
1057
+ evalRoundsUsed,
1058
+ prevModeSubcats: updatedPrevModeSubcats,
1059
+ prevFailFingerprints: new Set([...currFailFingerprints, ...capContractFails.map(fingerprintFail)]),
1060
+ preWarmedContext,
1061
+ evalTokens,
1062
+ evalLlmMs,
1063
+ lastResultText: contractLines.join("\n\n"),
1064
+ isEvalFeedback: true,
1065
+ lastDiffFailed: false,
1066
+ contractFeedback: contractFeedbackRecord(capContractFails, currentSource),
1067
+ };
1068
+ }
912
1069
  if (!allowRuntimeExtension) {
913
1070
  // Fold probe issues (if any) into evalResult for telemetry and
914
1071
  // ALWAYS stamp the probe's execution meta, even though we're not
@@ -875,8 +875,8 @@ export class AnthropicAgent extends LLMAgent {
875
875
  : toolChoice;
876
876
  if (appliedToolChoice !== toolChoice) {
877
877
  console.warn(
878
- `[anthropic] callTools: ${normalizeAnthropicModelId(resolvedModel)} rejects forced tool_choice (HTTP 400); ` +
879
- `caller's 'required' downgraded to 'auto'`,
878
+ `[anthropic] callTools: ${normalizeAnthropicModelId(resolvedModel)} would reject a forced tool_choice (the API answers 400); ` +
879
+ `caller's 'required' downgraded to 'auto' before sending`,
880
880
  );
881
881
  }
882
882
  const response = await this.apiCall(() =>
@@ -2128,7 +2128,7 @@ export class OpenRouterAgent extends LLMAgent {
2128
2128
  requested === 'required' && anthropicRejectsForcedToolChoice(resolved) ? 'auto' : requested;
2129
2129
  if (appliedToolChoice !== requested) {
2130
2130
  console.warn(
2131
- `[openrouter] callTools: ${resolved} rejects forced tool_choice (HTTP 400); caller's 'required' downgraded to 'auto'`,
2131
+ `[openrouter] callTools: ${resolved} would reject a forced tool_choice (the API answers 400); caller's 'required' downgraded to 'auto' before sending`,
2132
2132
  );
2133
2133
  }
2134
2134
 
@@ -49,11 +49,25 @@ export type ModelRoles = Partial<Record<ModelRole, string>>;
49
49
  // Generation Result
50
50
  // =============================================================================
51
51
 
52
+ /**
53
+ * The same-exchange guard (ggui#404) ended this generation: the coding model
54
+ * made one identical tool exchange (same tool, same input, same result)
55
+ * `repeats` times, so the loop stopped instead of burning its remaining turns.
56
+ * Present ONLY when that guard ended the run — whether anything shipped is
57
+ * the compiled code's to say (the loop falls back to an earlier build).
58
+ */
59
+ export interface SameExchangeBreak {
60
+ readonly tool: string;
61
+ readonly repeats: number;
62
+ }
63
+
52
64
  export interface GenerationResult extends AdapterResult {
53
65
  /** Number of generation passes the harness performed */
54
66
  passesUsed: number;
55
67
  /** Three-tier evaluation result (tier 0 + LLM tier 1+2 + visual) */
56
68
  evalResult?: EvalResult;
69
+ /** Set only when the same-exchange guard ended the run (ggui#404); no key otherwise. */
70
+ sameExchangeBreak?: SameExchangeBreak;
57
71
  /** Whether background improvement should be spawned (auto-improve mode) */
58
72
  needsBackgroundImprovement?: boolean;
59
73
  /** Whether at least one commit passed the self-check (compile + type check + lint) */
@@ -21,7 +21,7 @@
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 { EvalIssue, EvalTier, AxisCheck, RuntimeProbeStatus } from "../evaluation/types-public.js";
24
+ import type { EvalIssue, EvalTier, AxisCheck, ProbeHostLoad, RuntimeProbeStatus } from "../evaluation/types-public.js";
25
25
  import type { LLMToolDef } from "../llm.js";
26
26
  import type { CacheTier, HarnessFragment } from "../fragments/index.js";
27
27
  import type { HarnessPolicy, ProcessMode } from "../policy.js";
@@ -140,14 +140,18 @@ export interface LLMEvaluator {
140
140
  export interface RuntimeRenderOutcome {
141
141
  /**
142
142
  * Whether the probe actually executed. `issues` is only probe
143
- * evidence when `status === "ran"` — an `infra-skipped` or
144
- * `not-applicable` outcome carries zero evidence and MUST NOT be
145
- * scored as a pass by any consumer.
143
+ * evidence when `status === "ran"` — an `infra-skipped`,
144
+ * `not-applicable` or `timed-out` outcome carries zero evidence and
145
+ * MUST NOT be scored as a pass by any consumer.
146
146
  */
147
147
  readonly status: RuntimeProbeStatus;
148
148
  readonly issues: readonly EvalIssue[];
149
- /** Populated for `infra-skipped` / `not-applicable` — why the probe didn't run. */
149
+ /** Populated for `infra-skipped` / `not-applicable` / `timed-out` — why the probe didn't finish. */
150
150
  readonly reason?: string;
151
+ /** `timed-out` only: how long the probe ran before it was stopped. */
152
+ readonly elapsedMs?: number;
153
+ /** Host load around the probe, when it ran isolated (absent for an in-process probe). */
154
+ readonly hostLoad?: ProbeHostLoad;
151
155
  }
152
156
 
153
157
  export interface RuntimeRenderCheck {
package/src/index.ts CHANGED
@@ -21,7 +21,8 @@ export {
21
21
  } from './create-ui-generator.js';
22
22
  export type { CreateUiGeneratorOptions } from './create-ui-generator.js';
23
23
  export type { ProviderRetryInfo } from './harness/llm-router.js';
24
- export type { GenerationResult } from './harness/result-types.js';
24
+ export type { GenerationResult, SameExchangeBreak } from './harness/result-types.js';
25
+ export type { UiGenBuild } from './generator-build.js';
25
26
 
26
27
  // Generator registry seam re-export. The interface + slug helpers live
27
28
  // in `@ggui-ai/mcp-server-core`; the in-memory factory lives in