@ggui-ai/ui-gen 0.9.0 → 0.11.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/adapters/generation-dispatch.d.ts +9 -2
- package/dist/adapters/generation-dispatch.js +693 -89
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +28 -11
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +5 -3
- package/dist/advanced/index.js +714 -101
- package/dist/advanced/index.js.map +1 -1
- package/dist/blueprint-validator.js +267 -3
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.js +14 -2
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +1 -1
- package/dist/check/index.js +11 -1
- package/dist/check/index.js.map +1 -1
- package/dist/{create-ui-generator-B9kV4U9h.d.ts → create-ui-generator-CF0EL0Li.d.ts} +13 -1
- package/dist/{dispatch-Deo9qzrG.d.ts → dispatch-D75M-OfZ.d.ts} +1 -1
- package/dist/evaluation/axis-checks/index.d.ts +3 -3
- package/dist/evaluation/axis-checks/index.js +101 -0
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +1 -1
- package/dist/evaluation/axis-checks/registry.js +101 -0
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +46 -3
- package/dist/evaluation/index.js +358 -17
- package/dist/evaluation/index.js.map +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +2 -2
- package/dist/harness/check/runtime-render/index.js +268 -3
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/check/runtime-render/render-check-worker.d.ts +2 -0
- package/dist/harness/check/runtime-render/render-check-worker.js +1332 -0
- package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -0
- package/dist/harness/index.d.ts +2 -2
- package/dist/harness/index.js +263 -18
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/llm-trace-sink.d.ts +1 -1
- package/dist/harness/llm-trace-sink.js.map +1 -1
- package/dist/harness/prompts.js +17 -3
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +1 -1
- package/dist/harness/runtime.d.ts +9 -2
- package/dist/harness/runtime.js +20 -6
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +1 -1
- package/dist/harness/types-public.js +14 -2
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +708 -95
- package/dist/index.js.map +1 -1
- package/dist/{llm-router-1TyI0_TV.d.ts → llm-router-BEVtbAb9.d.ts} +61 -4
- package/dist/patch.d.ts +1 -1
- package/dist/provider-adapter.d.ts +16 -6
- package/dist/provider-adapter.js.map +1 -1
- package/dist/providers/index.d.ts +19 -6
- package/dist/providers/index.js.map +1 -1
- package/dist/{render-check-CNZjHD5g.d.ts → render-check-BZoIE8-u.d.ts} +21 -1
- package/dist/{types-public-C36k1q6s.d.ts → types-public-DIpjC-OA.d.ts} +1 -1
- package/dist/workflows.d.ts +1 -1
- package/package.json +12 -12
- package/src/adapters/generation-dispatch.ts +9 -0
- package/src/adapters/openrouter/client.ts +2 -2
- package/src/adapters/openrouter/types.ts +10 -0
- package/src/boilerplate/generate.ts +10 -2
- package/src/boilerplate/json-schema-ts.ts +22 -2
- package/src/boilerplate/system-prompt.ts +1 -0
- package/src/compile.ts +1 -1
- package/src/contract-context.ts +10 -2
- package/src/create-ui-generator.ts +14 -0
- package/src/design-system-docs.ts +6 -4
- package/src/evaluation/axis-checks/checks/state-ui-affordance.ts +162 -0
- package/src/evaluation/axis-checks/registry.ts +2 -0
- package/src/evaluation/index.ts +11 -0
- package/src/evaluation/llm-evaluator.ts +14 -1
- package/src/evaluation/visual-evaluator.ts +44 -65
- package/src/harness/check/runtime-render/adapter.ts +18 -0
- package/src/harness/check/runtime-render/index.ts +8 -0
- package/src/harness/check/runtime-render/prepare-mockup.ts +14 -0
- package/src/harness/check/runtime-render/production-act-shim.ts +158 -0
- package/src/harness/check/runtime-render/render-check-host.ts +201 -0
- package/src/harness/check/runtime-render/render-check-worker.ts +80 -0
- package/src/harness/check/runtime-render/render-check.ts +112 -1
- package/src/harness/coding/run-eval-round.ts +61 -5
- package/src/harness/index.ts +1 -1
- package/src/harness/llm-router.ts +397 -18
- package/src/harness/llm-trace-sink.ts +2 -1
- package/src/harness/runtime.ts +7 -0
- package/src/index.ts +5 -5
- package/src/provider-adapter.ts +16 -6
- package/src/providers/bedrock.ts +5 -4
- package/src/providers/index.ts +16 -4
- package/src/tools/render-check.ts +19 -7
|
@@ -3,11 +3,13 @@ import 'zod';
|
|
|
3
3
|
import { classifyUi, MODEL_REGISTRY, DANGEROUS_PATTERNS as DANGEROUS_PATTERNS$1, listContractGadgets, HOOK_NAME_RE, STDLIB_GADGETS_PACKAGE, resolveAppGadgets } from '@ggui-ai/protocol';
|
|
4
4
|
import * as esbuild from 'esbuild';
|
|
5
5
|
import { build } from 'esbuild';
|
|
6
|
-
import fs, { mkdirSync, writeFileSync, unlinkSync,
|
|
6
|
+
import fs, { mkdirSync, writeFileSync, unlinkSync, existsSync, readFileSync, rm, createReadStream, createWriteStream } from 'fs';
|
|
7
7
|
import { contentHash } from '@ggui-ai/protocol/content-hash';
|
|
8
|
+
import { createRequire } from 'module';
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
10
|
+
import { runSandboxed } from '@ggui-ai/sandbox';
|
|
8
11
|
import ts2 from 'typescript';
|
|
9
12
|
import path, { dirname, resolve, join, basename } from 'path';
|
|
10
|
-
import { createRequire } from 'module';
|
|
11
13
|
import { Linter } from 'eslint';
|
|
12
14
|
import { createHash } from 'crypto';
|
|
13
15
|
import 'fs/promises';
|
|
@@ -15,8 +17,6 @@ import { tmpdir } from 'os';
|
|
|
15
17
|
import { Readable } from 'stream';
|
|
16
18
|
import { pipeline } from 'stream/promises';
|
|
17
19
|
import { createBrotliDecompress, createUnzip } from 'zlib';
|
|
18
|
-
import { fileURLToPath } from 'url';
|
|
19
|
-
import '@ggui-ai/sandbox';
|
|
20
20
|
import git from 'isomorphic-git';
|
|
21
21
|
import { Volume, createFsFromVolume } from 'memfs';
|
|
22
22
|
import { createPatch, applyPatch } from 'diff';
|
|
@@ -82,6 +82,15 @@ function jsonSchemaTypeToTs(schema) {
|
|
|
82
82
|
if (schema.enum?.length) {
|
|
83
83
|
return schema.enum.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ");
|
|
84
84
|
}
|
|
85
|
+
if (Array.isArray(schema.type)) {
|
|
86
|
+
const { nullable: _nullable, ...rest } = schema;
|
|
87
|
+
const members = [...new Set(schema.type)].map(
|
|
88
|
+
(member) => jsonSchemaTypeToTs({ ...rest, type: member })
|
|
89
|
+
);
|
|
90
|
+
const unique = [...new Set(members)];
|
|
91
|
+
if (schema.nullable && !unique.includes("null")) unique.push("null");
|
|
92
|
+
return unique.join(" | ");
|
|
93
|
+
}
|
|
85
94
|
let result;
|
|
86
95
|
switch (schema.type) {
|
|
87
96
|
case "string":
|
|
@@ -99,7 +108,8 @@ function jsonSchemaTypeToTs(schema) {
|
|
|
99
108
|
case "array": {
|
|
100
109
|
if (schema.items) {
|
|
101
110
|
const itemType = jsonSchemaTypeToTs(schema.items);
|
|
102
|
-
|
|
111
|
+
const itemsDeclareObject = Array.isArray(schema.items.type) ? schema.items.type.includes("object") : schema.items.type === "object";
|
|
112
|
+
result = itemsDeclareObject && schema.items.properties ? `Array<${itemType}>` : itemType.includes("|") ? `(${itemType})[]` : `${itemType}[]`;
|
|
103
113
|
} else {
|
|
104
114
|
result = "unknown[]";
|
|
105
115
|
}
|
|
@@ -552,14 +562,16 @@ var OpenRouterError;
|
|
|
552
562
|
var init_types = __esm({
|
|
553
563
|
"src/adapters/openrouter/types.ts"() {
|
|
554
564
|
OpenRouterError = class extends Error {
|
|
555
|
-
constructor(message, status, code) {
|
|
565
|
+
constructor(message, status, code, headers) {
|
|
556
566
|
super(message);
|
|
557
567
|
this.status = status;
|
|
558
568
|
this.code = code;
|
|
569
|
+
this.headers = headers;
|
|
559
570
|
this.name = "OpenRouterError";
|
|
560
571
|
}
|
|
561
572
|
status;
|
|
562
573
|
code;
|
|
574
|
+
headers;
|
|
563
575
|
get isTransient() {
|
|
564
576
|
return this.status === 408 || this.status === 429 || this.status === 502 || this.status === 503;
|
|
565
577
|
}
|
|
@@ -604,7 +616,7 @@ var init_client2 = __esm({
|
|
|
604
616
|
if (!response.ok) {
|
|
605
617
|
const errorBody = await response.json().catch(() => ({ error: { message: response.statusText } }));
|
|
606
618
|
const message = errorBody.error?.message ?? response.statusText;
|
|
607
|
-
throw new OpenRouterError(message, response.status);
|
|
619
|
+
throw new OpenRouterError(message, response.status, void 0, response.headers);
|
|
608
620
|
}
|
|
609
621
|
return response.json();
|
|
610
622
|
}
|
|
@@ -622,7 +634,7 @@ var init_client2 = __esm({
|
|
|
622
634
|
if (!response.ok) {
|
|
623
635
|
const errorBody = await response.json().catch(() => ({ error: { message: response.statusText } }));
|
|
624
636
|
const message = errorBody.error?.message ?? response.statusText;
|
|
625
|
-
throw new OpenRouterError(message, response.status);
|
|
637
|
+
throw new OpenRouterError(message, response.status, void 0, response.headers);
|
|
626
638
|
}
|
|
627
639
|
if (!response.body) {
|
|
628
640
|
throw new OpenRouterError("No response body for streaming request", 500);
|
|
@@ -690,6 +702,29 @@ function errorSummary(e) {
|
|
|
690
702
|
}
|
|
691
703
|
return `${e.constructor.name}${status}${code}: ${e.message.slice(0, 120)}${body}`;
|
|
692
704
|
}
|
|
705
|
+
function isRateLimited(e) {
|
|
706
|
+
if (!(e instanceof Error) || !("status" in e)) return false;
|
|
707
|
+
return e.status === 429;
|
|
708
|
+
}
|
|
709
|
+
function extractStatus(e) {
|
|
710
|
+
if (!(e instanceof Error) || !("status" in e)) return void 0;
|
|
711
|
+
const status = e.status;
|
|
712
|
+
return typeof status === "number" ? status : void 0;
|
|
713
|
+
}
|
|
714
|
+
function extractRetryAfterSec(e) {
|
|
715
|
+
if (!(e instanceof Error) || !("headers" in e)) return void 0;
|
|
716
|
+
const headers = e.headers;
|
|
717
|
+
if (headers === null || typeof headers !== "object" || !("get" in headers) || typeof headers.get !== "function") {
|
|
718
|
+
return void 0;
|
|
719
|
+
}
|
|
720
|
+
const raw = headers.get("retry-after");
|
|
721
|
+
if (raw === null) return void 0;
|
|
722
|
+
const parsed = Number(raw);
|
|
723
|
+
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : void 0;
|
|
724
|
+
}
|
|
725
|
+
function sleep(ms) {
|
|
726
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
727
|
+
}
|
|
693
728
|
function interactionText(interaction) {
|
|
694
729
|
let text = "";
|
|
695
730
|
for (const step of interaction.steps ?? []) {
|
|
@@ -714,24 +749,38 @@ function toUserInputStep(text) {
|
|
|
714
749
|
function createAgent(providerOrConfig) {
|
|
715
750
|
const provider = typeof providerOrConfig === "string" ? providerOrConfig : providerOrConfig.provider;
|
|
716
751
|
const routeOverride = typeof providerOrConfig === "string" ? void 0 : providerOrConfig.routeOverride;
|
|
752
|
+
const onRetry = typeof providerOrConfig === "string" ? void 0 : providerOrConfig.onRetry;
|
|
717
753
|
switch (provider) {
|
|
718
754
|
case "anthropic":
|
|
719
|
-
return new AnthropicAgent(routeOverride);
|
|
755
|
+
return new AnthropicAgent(routeOverride, onRetry);
|
|
720
756
|
case "openai":
|
|
721
|
-
return new OpenAIAgent(routeOverride);
|
|
757
|
+
return new OpenAIAgent(routeOverride, onRetry);
|
|
722
758
|
case "google":
|
|
723
|
-
return new GoogleAgent(routeOverride);
|
|
759
|
+
return new GoogleAgent(routeOverride, onRetry);
|
|
724
760
|
case "openrouter":
|
|
725
|
-
return new OpenRouterAgent(routeOverride);
|
|
761
|
+
return new OpenRouterAgent(routeOverride, onRetry);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
function createVisionAgent(config) {
|
|
765
|
+
switch (config.provider) {
|
|
766
|
+
case "anthropic":
|
|
767
|
+
return new AnthropicAgent(config.routeOverride, config.onRetry);
|
|
768
|
+
case "google":
|
|
769
|
+
return new GoogleAgent(config.routeOverride, config.onRetry);
|
|
726
770
|
}
|
|
727
771
|
}
|
|
728
|
-
var LLMAgent, AnthropicAgent, OpenAIAgent, GoogleAgent, OpenRouterAgent;
|
|
772
|
+
var MAX_RETRY_ATTEMPTS, RETRY_AFTER_CAP_SEC, DEFAULT_BACKOFF_BASE_MS, DEFAULT_BACKOFF_MAX_MS, MAX_TOTAL_RETRY_DELAY_MS, LLMAgent, AnthropicAgent, OpenAIAgent, GoogleAgent, OpenRouterAgent;
|
|
729
773
|
var init_llm_router = __esm({
|
|
730
774
|
"src/harness/llm-router.ts"() {
|
|
731
775
|
init_client();
|
|
732
776
|
init_provider_router();
|
|
733
777
|
init_tool_bridge();
|
|
734
778
|
init_llm_trace_sink();
|
|
779
|
+
MAX_RETRY_ATTEMPTS = 2;
|
|
780
|
+
RETRY_AFTER_CAP_SEC = 15;
|
|
781
|
+
DEFAULT_BACKOFF_BASE_MS = 1e3;
|
|
782
|
+
DEFAULT_BACKOFF_MAX_MS = 8e3;
|
|
783
|
+
MAX_TOTAL_RETRY_DELAY_MS = 2e4;
|
|
735
784
|
LLMAgent = class {
|
|
736
785
|
client = null;
|
|
737
786
|
// ── Session state for server-side chaining ──────────────
|
|
@@ -746,8 +795,11 @@ var init_llm_router = __esm({
|
|
|
746
795
|
* back to `process.env` exactly as before.
|
|
747
796
|
*/
|
|
748
797
|
routeOverride;
|
|
749
|
-
|
|
798
|
+
/** See `AgentConfig.onRetry` (#489). Absent is a no-op. */
|
|
799
|
+
onRetry;
|
|
800
|
+
constructor(routeOverride, onRetry) {
|
|
750
801
|
this.routeOverride = routeOverride;
|
|
802
|
+
this.onRetry = onRetry;
|
|
751
803
|
}
|
|
752
804
|
async getClient() {
|
|
753
805
|
if (!this.client) {
|
|
@@ -782,17 +834,83 @@ var init_llm_router = __esm({
|
|
|
782
834
|
async sendToolResult(_results) {
|
|
783
835
|
}
|
|
784
836
|
/**
|
|
785
|
-
* Execute an API call
|
|
786
|
-
*
|
|
837
|
+
* Execute an API call, retrying once or twice on HTTP 429
|
|
838
|
+
* (rate-limited) before giving up (#489).
|
|
839
|
+
*
|
|
840
|
+
* Policy: on a 429, honor the provider's `Retry-After` header when
|
|
841
|
+
* present (capped at `RETRY_AFTER_CAP_SEC` — a provider asking for a
|
|
842
|
+
* longer wait is treated as "don't retry", not "wait longer": making
|
|
843
|
+
* the caller sit through 15+ more seconds on a single failed call is
|
|
844
|
+
* a bad experience regardless of what's driving the request);
|
|
845
|
+
* otherwise fall back to exponential backoff with jitter, capped at
|
|
846
|
+
* `DEFAULT_BACKOFF_MAX_MS` per attempt. Stops after
|
|
847
|
+
* `MAX_RETRY_ATTEMPTS` retries OR once the cumulative delay would
|
|
848
|
+
* exceed `MAX_TOTAL_RETRY_DELAY_MS`, whichever comes first. Any
|
|
849
|
+
* non-429 error, or a 429 with no attempts left, is logged and
|
|
850
|
+
* re-thrown immediately — unchanged from the pre-#489 behavior.
|
|
851
|
+
*
|
|
852
|
+
* **Budget scope (final-review correction):** `MAX_TOTAL_RETRY_DELAY_MS`
|
|
853
|
+
* bounds a single `apiCall()` invocation, not a whole generation. A
|
|
854
|
+
* generation issues many sequential `apiCall()` calls (coding turns,
|
|
855
|
+
* tool-result round-trips, eval rounds); under sustained throttling
|
|
856
|
+
* each one can independently add up to `MAX_TOTAL_RETRY_DELAY_MS`, so
|
|
857
|
+
* a generation with k rate-limited calls can add up to roughly `k *
|
|
858
|
+
* MAX_TOTAL_RETRY_DELAY_MS` to how long it holds its caller's
|
|
859
|
+
* resources (e.g. the caller's concurrency slot) — not a flat 20s
|
|
860
|
+
* ceiling. No shared per-generation budget is threaded here by
|
|
861
|
+
* design (accepted ruling, not an oversight): the caller's own
|
|
862
|
+
* queue/admission backstop is what bounds how long other
|
|
863
|
+
* queued work waits, independent of any one generation's retry
|
|
864
|
+
* total.
|
|
787
865
|
*/
|
|
788
866
|
async apiCall(fn) {
|
|
789
867
|
const start = Date.now();
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
868
|
+
let totalDelayMs = 0;
|
|
869
|
+
for (let attempt = 0; ; attempt++) {
|
|
870
|
+
try {
|
|
871
|
+
return await fn();
|
|
872
|
+
} catch (e) {
|
|
873
|
+
const ms = Date.now() - start;
|
|
874
|
+
if (attempt >= MAX_RETRY_ATTEMPTS || !isRateLimited(e)) {
|
|
875
|
+
console.error(`[${this.provider}] API error after ${ms}ms: ${errorSummary(e)}`);
|
|
876
|
+
throw e;
|
|
877
|
+
}
|
|
878
|
+
const retryAfterSec = extractRetryAfterSec(e);
|
|
879
|
+
if (retryAfterSec !== void 0 && retryAfterSec > RETRY_AFTER_CAP_SEC) {
|
|
880
|
+
console.error(
|
|
881
|
+
`[${this.provider}] API error after ${ms}ms (retry-after ${retryAfterSec}s exceeds ${RETRY_AFTER_CAP_SEC}s cap, not retrying): ${errorSummary(e)}`
|
|
882
|
+
);
|
|
883
|
+
throw e;
|
|
884
|
+
}
|
|
885
|
+
const delayMs = retryAfterSec !== void 0 ? retryAfterSec * 1e3 : Math.min(DEFAULT_BACKOFF_BASE_MS * 2 ** attempt, DEFAULT_BACKOFF_MAX_MS) + Math.floor(Math.random() * 250);
|
|
886
|
+
if (totalDelayMs + delayMs > MAX_TOTAL_RETRY_DELAY_MS) {
|
|
887
|
+
console.error(
|
|
888
|
+
`[${this.provider}] API error after ${ms}ms (retry budget exhausted, not retrying): ${errorSummary(e)}`
|
|
889
|
+
);
|
|
890
|
+
throw e;
|
|
891
|
+
}
|
|
892
|
+
totalDelayMs += delayMs;
|
|
893
|
+
const info = {
|
|
894
|
+
provider: this.provider,
|
|
895
|
+
attempt: attempt + 1,
|
|
896
|
+
maxAttempts: MAX_RETRY_ATTEMPTS,
|
|
897
|
+
status: extractStatus(e),
|
|
898
|
+
retryAfterSec,
|
|
899
|
+
delayMs,
|
|
900
|
+
message: errorSummary(e)
|
|
901
|
+
};
|
|
902
|
+
console.warn(
|
|
903
|
+
`[${this.provider}] rate-limited (attempt ${info.attempt}/${info.maxAttempts}), retrying in ${delayMs}ms`
|
|
904
|
+
);
|
|
905
|
+
try {
|
|
906
|
+
this.onRetry?.(info);
|
|
907
|
+
} catch (observerError) {
|
|
908
|
+
console.warn(
|
|
909
|
+
`[${this.provider}] onRetry observer threw (ignored, retry proceeds): ${errorSummary(observerError)}`
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
await sleep(delayMs);
|
|
913
|
+
}
|
|
796
914
|
}
|
|
797
915
|
}
|
|
798
916
|
};
|
|
@@ -885,6 +1003,73 @@ var init_llm_router = __esm({
|
|
|
885
1003
|
throw e;
|
|
886
1004
|
}
|
|
887
1005
|
}
|
|
1006
|
+
async callVision(model, systemPrompt, userPrompt, image, maxTokens) {
|
|
1007
|
+
const client = await this.getClient();
|
|
1008
|
+
const traceId = newLlmTraceId();
|
|
1009
|
+
const startedAt = Date.now();
|
|
1010
|
+
const resolvedModel = this.resolveModel(model);
|
|
1011
|
+
const imageBlock = {
|
|
1012
|
+
type: "image",
|
|
1013
|
+
source: {
|
|
1014
|
+
type: "base64",
|
|
1015
|
+
media_type: image.mediaType,
|
|
1016
|
+
data: image.base64
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
try {
|
|
1020
|
+
const response = await this.apiCall(
|
|
1021
|
+
() => client.messages.create({
|
|
1022
|
+
model: resolvedModel,
|
|
1023
|
+
max_tokens: maxTokens ?? 1500,
|
|
1024
|
+
system: systemPrompt,
|
|
1025
|
+
messages: [
|
|
1026
|
+
{
|
|
1027
|
+
role: "user",
|
|
1028
|
+
content: [imageBlock, { type: "text", text: userPrompt }]
|
|
1029
|
+
}
|
|
1030
|
+
]
|
|
1031
|
+
})
|
|
1032
|
+
);
|
|
1033
|
+
const text = response.content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
1034
|
+
const endedAt = Date.now();
|
|
1035
|
+
emitLlmTraceEvent({
|
|
1036
|
+
id: traceId,
|
|
1037
|
+
at: startedAt,
|
|
1038
|
+
endedAt,
|
|
1039
|
+
durationMs: endedAt - startedAt,
|
|
1040
|
+
provider: "anthropic",
|
|
1041
|
+
model: resolvedModel,
|
|
1042
|
+
kind: "callVision",
|
|
1043
|
+
systemPrompt,
|
|
1044
|
+
userPrompt,
|
|
1045
|
+
result: {
|
|
1046
|
+
inputTokens: response.usage.input_tokens,
|
|
1047
|
+
outputTokens: response.usage.output_tokens,
|
|
1048
|
+
text
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
return {
|
|
1052
|
+
text,
|
|
1053
|
+
inputTokens: response.usage.input_tokens,
|
|
1054
|
+
outputTokens: response.usage.output_tokens
|
|
1055
|
+
};
|
|
1056
|
+
} catch (e) {
|
|
1057
|
+
const endedAt = Date.now();
|
|
1058
|
+
emitLlmTraceEvent({
|
|
1059
|
+
id: traceId,
|
|
1060
|
+
at: startedAt,
|
|
1061
|
+
endedAt,
|
|
1062
|
+
durationMs: endedAt - startedAt,
|
|
1063
|
+
provider: "anthropic",
|
|
1064
|
+
model: resolvedModel,
|
|
1065
|
+
kind: "callVision",
|
|
1066
|
+
systemPrompt,
|
|
1067
|
+
userPrompt,
|
|
1068
|
+
error: { message: e instanceof Error ? e.message : String(e) }
|
|
1069
|
+
});
|
|
1070
|
+
throw e;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
888
1073
|
async callTools(model, systemPrompt, userPrompt, tools, toolChoice = "required", _scopedTools) {
|
|
889
1074
|
const client = await this.getClient();
|
|
890
1075
|
const traceId = newLlmTraceId();
|
|
@@ -1259,6 +1444,72 @@ var init_llm_router = __esm({
|
|
|
1259
1444
|
outputTokens: interaction.usage?.total_output_tokens ?? 0
|
|
1260
1445
|
};
|
|
1261
1446
|
}
|
|
1447
|
+
async callVision(model, systemPrompt, userPrompt, image, maxTokens) {
|
|
1448
|
+
const client = await this.getClient();
|
|
1449
|
+
const traceId = newLlmTraceId();
|
|
1450
|
+
const startedAt = Date.now();
|
|
1451
|
+
const resolvedModel = this.resolveModel(model);
|
|
1452
|
+
try {
|
|
1453
|
+
const response = await this.apiCall(
|
|
1454
|
+
() => client.models.generateContent({
|
|
1455
|
+
model: resolvedModel,
|
|
1456
|
+
contents: [
|
|
1457
|
+
{
|
|
1458
|
+
role: "user",
|
|
1459
|
+
parts: [
|
|
1460
|
+
{ inlineData: { mimeType: image.mediaType, data: image.base64 } },
|
|
1461
|
+
{ text: userPrompt }
|
|
1462
|
+
]
|
|
1463
|
+
}
|
|
1464
|
+
],
|
|
1465
|
+
config: {
|
|
1466
|
+
systemInstruction: systemPrompt,
|
|
1467
|
+
...maxTokens ? { maxOutputTokens: maxTokens } : {}
|
|
1468
|
+
}
|
|
1469
|
+
})
|
|
1470
|
+
);
|
|
1471
|
+
const text = response.candidates?.[0]?.content?.parts?.map((p) => p.text || "").join("") || "";
|
|
1472
|
+
const usage = response.usageMetadata;
|
|
1473
|
+
const result = {
|
|
1474
|
+
text,
|
|
1475
|
+
inputTokens: usage?.promptTokenCount ?? 0,
|
|
1476
|
+
outputTokens: usage?.candidatesTokenCount ?? 0
|
|
1477
|
+
};
|
|
1478
|
+
const endedAt = Date.now();
|
|
1479
|
+
emitLlmTraceEvent({
|
|
1480
|
+
id: traceId,
|
|
1481
|
+
at: startedAt,
|
|
1482
|
+
endedAt,
|
|
1483
|
+
durationMs: endedAt - startedAt,
|
|
1484
|
+
provider: "google",
|
|
1485
|
+
model: resolvedModel,
|
|
1486
|
+
kind: "callVision",
|
|
1487
|
+
systemPrompt,
|
|
1488
|
+
userPrompt,
|
|
1489
|
+
result: {
|
|
1490
|
+
inputTokens: result.inputTokens,
|
|
1491
|
+
outputTokens: result.outputTokens,
|
|
1492
|
+
text
|
|
1493
|
+
}
|
|
1494
|
+
});
|
|
1495
|
+
return result;
|
|
1496
|
+
} catch (e) {
|
|
1497
|
+
const endedAt = Date.now();
|
|
1498
|
+
emitLlmTraceEvent({
|
|
1499
|
+
id: traceId,
|
|
1500
|
+
at: startedAt,
|
|
1501
|
+
endedAt,
|
|
1502
|
+
durationMs: endedAt - startedAt,
|
|
1503
|
+
provider: "google",
|
|
1504
|
+
model: resolvedModel,
|
|
1505
|
+
kind: "callVision",
|
|
1506
|
+
systemPrompt,
|
|
1507
|
+
userPrompt,
|
|
1508
|
+
error: { message: e instanceof Error ? e.message : String(e) }
|
|
1509
|
+
});
|
|
1510
|
+
throw e;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1262
1513
|
async callTools(model, systemPrompt, userPrompt, tools, _toolChoice = "required", scopedTools) {
|
|
1263
1514
|
const client = await this.getClient();
|
|
1264
1515
|
const interactionTools = tools.map((t) => ({
|
|
@@ -2304,6 +2555,140 @@ var init_validation = __esm({
|
|
|
2304
2555
|
init_ui_compiler();
|
|
2305
2556
|
}
|
|
2306
2557
|
});
|
|
2558
|
+
|
|
2559
|
+
// src/harness/check/runtime-render/render-check-host.ts
|
|
2560
|
+
var render_check_host_exports = {};
|
|
2561
|
+
__export(render_check_host_exports, {
|
|
2562
|
+
mapSandboxResultToCheckResult: () => mapSandboxResultToCheckResult,
|
|
2563
|
+
runRenderCheckViaWorker: () => runRenderCheckViaWorker
|
|
2564
|
+
});
|
|
2565
|
+
function resolveWorkerSpawn() {
|
|
2566
|
+
const jsCandidates = [
|
|
2567
|
+
// Sibling — when import.meta.url is already .../harness/check/runtime-render/.
|
|
2568
|
+
new URL(`./${WORKER_BASENAME}.js`, import.meta.url),
|
|
2569
|
+
// From dist/index.js.
|
|
2570
|
+
new URL(`./harness/check/runtime-render/${WORKER_BASENAME}.js`, import.meta.url),
|
|
2571
|
+
// From dist/harness/index.js.
|
|
2572
|
+
new URL(`./check/runtime-render/${WORKER_BASENAME}.js`, import.meta.url),
|
|
2573
|
+
// From dist/check/index.js, dist/advanced/index.js, dist/<flat>.js.
|
|
2574
|
+
new URL(`../harness/check/runtime-render/${WORKER_BASENAME}.js`, import.meta.url)
|
|
2575
|
+
];
|
|
2576
|
+
for (const jsUrl of jsCandidates) {
|
|
2577
|
+
const jsPath = fileURLToPath(jsUrl);
|
|
2578
|
+
if (existsSync(jsPath)) {
|
|
2579
|
+
return { command: process.execPath, args: [jsPath] };
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
const tsCandidates = [
|
|
2583
|
+
new URL(`./${WORKER_BASENAME}.ts`, import.meta.url),
|
|
2584
|
+
new URL(`../harness/check/runtime-render/${WORKER_BASENAME}.ts`, import.meta.url)
|
|
2585
|
+
];
|
|
2586
|
+
for (const tsUrl of tsCandidates) {
|
|
2587
|
+
const tsPath = fileURLToPath(tsUrl);
|
|
2588
|
+
if (existsSync(tsPath)) {
|
|
2589
|
+
const require_ = createRequire(import.meta.url);
|
|
2590
|
+
const tsxLoader = pathToFileURL(require_.resolve("tsx")).href;
|
|
2591
|
+
return { command: process.execPath, args: ["--import", tsxLoader, tsPath] };
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
const tried = [...jsCandidates, ...tsCandidates].map((u) => fileURLToPath(u)).join(", ");
|
|
2595
|
+
throw new Error(
|
|
2596
|
+
`render-check: isolated worker not found at any of: ${tried}. Did \`pnpm build\` run?`
|
|
2597
|
+
);
|
|
2598
|
+
}
|
|
2599
|
+
function failed(reason, t0) {
|
|
2600
|
+
return {
|
|
2601
|
+
ok: false,
|
|
2602
|
+
issues: [{ check: "render-no-throw", outcome: "failed", reason }],
|
|
2603
|
+
stats: { actionsChecked: 0, streamsChecked: 0, renderMs: Date.now() - t0 }
|
|
2604
|
+
};
|
|
2605
|
+
}
|
|
2606
|
+
function unverified(reason, t0) {
|
|
2607
|
+
return {
|
|
2608
|
+
ok: false,
|
|
2609
|
+
issues: [{ check: "render-no-throw", outcome: "unverified", reason }],
|
|
2610
|
+
stats: { actionsChecked: 0, streamsChecked: 0, renderMs: Date.now() - t0 }
|
|
2611
|
+
};
|
|
2612
|
+
}
|
|
2613
|
+
function mapSandboxResultToCheckResult(result, t0) {
|
|
2614
|
+
if (result.outcome === "timeout") {
|
|
2615
|
+
return failed(
|
|
2616
|
+
`render check timed out after ${CHECK_TIMEOUT_MS}ms in the isolated worker (likely infinite loop or runaway effect in the component).`,
|
|
2617
|
+
t0
|
|
2618
|
+
);
|
|
2619
|
+
}
|
|
2620
|
+
if (result.outcome === "overflow-stdout" || result.outcome === "overflow-stderr") {
|
|
2621
|
+
return failed(
|
|
2622
|
+
`render check worker produced excessive output (${result.outcome}) \u2014 the component is likely in a pathological logging or error loop.`,
|
|
2623
|
+
t0
|
|
2624
|
+
);
|
|
2625
|
+
}
|
|
2626
|
+
if (result.outcome === "spawn-error") {
|
|
2627
|
+
return unverified(
|
|
2628
|
+
`render check worker failed to start \u2014 ${result.errorMessage}`,
|
|
2629
|
+
t0
|
|
2630
|
+
);
|
|
2631
|
+
}
|
|
2632
|
+
if (result.outcome === "canceled") {
|
|
2633
|
+
return unverified("render check worker was canceled before completing.", t0);
|
|
2634
|
+
}
|
|
2635
|
+
if (result.outcome !== "exit") {
|
|
2636
|
+
return unverified(
|
|
2637
|
+
`render check worker ended with unexpected sandbox outcome '${result.outcome}'.`,
|
|
2638
|
+
t0
|
|
2639
|
+
);
|
|
2640
|
+
}
|
|
2641
|
+
if (result.exitCode !== 0) {
|
|
2642
|
+
const tail = result.stderr.trim() || result.stdout.trim();
|
|
2643
|
+
return unverified(
|
|
2644
|
+
`render check worker exited ${result.exitCode}${tail ? ` \u2014 ${tail.slice(0, 600)}` : ""}`,
|
|
2645
|
+
t0
|
|
2646
|
+
);
|
|
2647
|
+
}
|
|
2648
|
+
const stdout = result.stdout.trim();
|
|
2649
|
+
if (stdout.length === 0) {
|
|
2650
|
+
return unverified("render check worker exited without producing a verdict.", t0);
|
|
2651
|
+
}
|
|
2652
|
+
try {
|
|
2653
|
+
return JSON.parse(stdout);
|
|
2654
|
+
} catch (err) {
|
|
2655
|
+
return unverified(
|
|
2656
|
+
`render check worker verdict was not valid JSON \u2014 ${err instanceof Error ? err.message : String(err)}`,
|
|
2657
|
+
t0
|
|
2658
|
+
);
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
async function runRenderCheckViaWorker(input) {
|
|
2662
|
+
const t0 = Date.now();
|
|
2663
|
+
let spawn;
|
|
2664
|
+
try {
|
|
2665
|
+
spawn = resolveWorkerSpawn();
|
|
2666
|
+
} catch (err) {
|
|
2667
|
+
return unverified(err instanceof Error ? err.message : String(err), t0);
|
|
2668
|
+
}
|
|
2669
|
+
const result = await runSandboxed({
|
|
2670
|
+
command: spawn.command,
|
|
2671
|
+
args: spawn.args,
|
|
2672
|
+
timeoutMs: CHECK_TIMEOUT_MS,
|
|
2673
|
+
maxStdoutBytes: CHECK_STDOUT_CAP,
|
|
2674
|
+
nodeHeapMb: CHECK_NODE_HEAP_MB,
|
|
2675
|
+
stdin: JSON.stringify(input),
|
|
2676
|
+
// NODE_ENV steers React's production vs development build — keep
|
|
2677
|
+
// parity with the caller; everything else stays on the sandbox's
|
|
2678
|
+
// default allowlist.
|
|
2679
|
+
env: { NODE_ENV: process.env.NODE_ENV ?? "production" }
|
|
2680
|
+
});
|
|
2681
|
+
return mapSandboxResultToCheckResult(result, t0);
|
|
2682
|
+
}
|
|
2683
|
+
var CHECK_TIMEOUT_MS, CHECK_STDOUT_CAP, CHECK_NODE_HEAP_MB, WORKER_BASENAME;
|
|
2684
|
+
var init_render_check_host = __esm({
|
|
2685
|
+
"src/harness/check/runtime-render/render-check-host.ts"() {
|
|
2686
|
+
CHECK_TIMEOUT_MS = 3e4;
|
|
2687
|
+
CHECK_STDOUT_CAP = 2 * 1024 * 1024;
|
|
2688
|
+
CHECK_NODE_HEAP_MB = 512;
|
|
2689
|
+
WORKER_BASENAME = "render-check-worker";
|
|
2690
|
+
}
|
|
2691
|
+
});
|
|
2307
2692
|
function extractWireCallSites(code) {
|
|
2308
2693
|
const sf = ts2.createSourceFile(
|
|
2309
2694
|
"component.tsx",
|
|
@@ -4730,7 +5115,11 @@ async function runLLMEvaluation(context, config, preWarmedContext) {
|
|
|
4730
5115
|
designSystemSummary: context.designContext
|
|
4731
5116
|
});
|
|
4732
5117
|
const numberedSource = context.sourceCode.split("\n").map((line, i) => `${String(i + 1).padStart(4)} | ${line}`).join("\n");
|
|
4733
|
-
const agent = createAgent({
|
|
5118
|
+
const agent = createAgent({
|
|
5119
|
+
provider: routerProvider,
|
|
5120
|
+
routeOverride: config.routeOverride,
|
|
5121
|
+
onRetry: config.onRetry
|
|
5122
|
+
});
|
|
4734
5123
|
const evalStart = Date.now();
|
|
4735
5124
|
const totalCriteriaCount = TIER_1_CRITERIA.length + TIER_2_CRITERIA.length;
|
|
4736
5125
|
const results = await Promise.all([
|
|
@@ -8098,6 +8487,7 @@ var visual_evaluator_exports = {};
|
|
|
8098
8487
|
__export(visual_evaluator_exports, {
|
|
8099
8488
|
buildRenderHTML: () => buildRenderHTML,
|
|
8100
8489
|
bundleForRendering: () => bundleForRendering,
|
|
8490
|
+
callMultimodalLLM: () => callMultimodalLLM,
|
|
8101
8491
|
runVisualEval: () => runVisualEval,
|
|
8102
8492
|
runVisualEvaluation: () => runVisualEvaluation
|
|
8103
8493
|
});
|
|
@@ -8222,63 +8612,23 @@ async function captureScreenshot(html, viewport = { width: 1280, height: 800 })
|
|
|
8222
8612
|
return null;
|
|
8223
8613
|
}
|
|
8224
8614
|
}
|
|
8225
|
-
async function callMultimodalLLM(
|
|
8615
|
+
async function callMultimodalLLM(config, model, prompt, screenshot, originalPrompt) {
|
|
8226
8616
|
const userPrompt = `## Original Request
|
|
8227
8617
|
${originalPrompt}
|
|
8228
8618
|
|
|
8229
8619
|
Evaluate the screenshot of the generated component.`;
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
baseURL: "https://api.anthropic.com"
|
|
8235
|
-
});
|
|
8236
|
-
const response2 = await client2.messages.create({
|
|
8237
|
-
model: model.startsWith("anthropic/") ? model.slice(10) : model,
|
|
8238
|
-
max_tokens: 1500,
|
|
8239
|
-
system: prompt,
|
|
8240
|
-
messages: [{
|
|
8241
|
-
role: "user",
|
|
8242
|
-
content: [
|
|
8243
|
-
{
|
|
8244
|
-
type: "image",
|
|
8245
|
-
source: {
|
|
8246
|
-
type: "base64",
|
|
8247
|
-
media_type: "image/png",
|
|
8248
|
-
data: screenshot.toString("base64")
|
|
8249
|
-
}
|
|
8250
|
-
},
|
|
8251
|
-
{ type: "text", text: userPrompt }
|
|
8252
|
-
]
|
|
8253
|
-
}]
|
|
8254
|
-
});
|
|
8255
|
-
return {
|
|
8256
|
-
text: response2.content.filter((b) => b.type === "text").map((b) => b.text).join(""),
|
|
8257
|
-
inputTokens: response2.usage.input_tokens,
|
|
8258
|
-
outputTokens: response2.usage.output_tokens
|
|
8259
|
-
};
|
|
8260
|
-
}
|
|
8261
|
-
const { GoogleGenAI } = await import('@google/genai');
|
|
8262
|
-
const client = new GoogleGenAI({
|
|
8263
|
-
apiKey: process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY
|
|
8620
|
+
const agent = createVisionAgent({
|
|
8621
|
+
provider: config.provider === "claude" ? "anthropic" : config.provider,
|
|
8622
|
+
routeOverride: config.routeOverride,
|
|
8623
|
+
onRetry: config.onRetry
|
|
8264
8624
|
});
|
|
8265
|
-
|
|
8266
|
-
model
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
]
|
|
8273
|
-
}],
|
|
8274
|
-
config: { systemInstruction: prompt }
|
|
8275
|
-
});
|
|
8276
|
-
const usage = response.usageMetadata;
|
|
8277
|
-
return {
|
|
8278
|
-
text: response.candidates?.[0]?.content?.parts?.map((p) => p.text || "").join("") || "",
|
|
8279
|
-
inputTokens: usage?.promptTokenCount ?? 0,
|
|
8280
|
-
outputTokens: usage?.candidatesTokenCount ?? 0
|
|
8281
|
-
};
|
|
8625
|
+
return agent.callVision(
|
|
8626
|
+
model,
|
|
8627
|
+
prompt,
|
|
8628
|
+
userPrompt,
|
|
8629
|
+
{ mediaType: "image/png", base64: screenshot.toString("base64") },
|
|
8630
|
+
1500
|
|
8631
|
+
);
|
|
8282
8632
|
}
|
|
8283
8633
|
async function runVisualEvaluation(context, config) {
|
|
8284
8634
|
const startTime = Date.now();
|
|
@@ -8296,7 +8646,7 @@ async function runVisualEvaluation(context, config) {
|
|
|
8296
8646
|
console.log(`[visual-eval] screenshot: ${screenshot.length}B (${Date.now() - startTime}ms)`);
|
|
8297
8647
|
const model = config.model ?? getDefaultVisualModel(config.provider);
|
|
8298
8648
|
const response = await callMultimodalLLM(
|
|
8299
|
-
config
|
|
8649
|
+
config,
|
|
8300
8650
|
model,
|
|
8301
8651
|
VISUAL_EVAL_PROMPT,
|
|
8302
8652
|
screenshot,
|
|
@@ -8370,6 +8720,7 @@ async function runVisualEval(context, config) {
|
|
|
8370
8720
|
var VISUAL_EVAL_PROMPT;
|
|
8371
8721
|
var init_visual_evaluator = __esm({
|
|
8372
8722
|
"src/evaluation/visual-evaluator.ts"() {
|
|
8723
|
+
init_llm_router();
|
|
8373
8724
|
VISUAL_EVAL_PROMPT = `You are a visual quality evaluator for ggui \u2014 a platform where AI agents generate React UI components on demand.
|
|
8374
8725
|
|
|
8375
8726
|
## Context
|
|
@@ -10064,13 +10415,14 @@ function generateBoilerplate(_userPrompt, contract, shellType, screen, composedS
|
|
|
10064
10415
|
const propsFields = [];
|
|
10065
10416
|
const propsProperties = contract?.propsSpec?.properties ?? {};
|
|
10066
10417
|
for (const [key, entry] of Object.entries(propsProperties)) {
|
|
10067
|
-
const required = entry.required
|
|
10418
|
+
const required = entry.required === true;
|
|
10068
10419
|
const nullable = entry.schema.nullable === true;
|
|
10069
10420
|
const tsType = jsonSchemaTypeToTs(entry.schema);
|
|
10070
10421
|
const fullType = nullable ? `${tsType} | null` : tsType;
|
|
10071
10422
|
const parts = [];
|
|
10072
10423
|
if (entry.description) parts.push(entry.description);
|
|
10073
10424
|
if (entry.default !== void 0) parts.push(`(default: ${JSON.stringify(entry.default)})`);
|
|
10425
|
+
if (!required) parts.push("(optional \u2014 the agent may omit it; guard or fall back)");
|
|
10074
10426
|
const desc = parts.length > 0 ? ` // ${parts.join(" ")}` : "";
|
|
10075
10427
|
propsFields.push(` ${key}${required ? "" : "?"}: ${fullType};${desc}`);
|
|
10076
10428
|
}
|
|
@@ -10629,6 +10981,7 @@ A \`DataContract\` declares everything a GguiSession exchanges with the outside
|
|
|
10629
10981
|
|
|
10630
10982
|
Props arrive at mount and may be partial on first render; later prop changes reach the SAME mounted component via \`ggui_amend\` (in-place repaint) on the live channel. Stream channels start empty and fill over time. Context slots start at their declared default (often \`null\`). **Never assume a field exists before you read it.**
|
|
10631
10983
|
|
|
10984
|
+
- **Optional contract props**: a \`Props\` field marked \`?\` in the boilerplate may be omitted by the agent ENTIRELY \u2014 the wire gate only enforces \`required: true\` entries, and the harness verifies your component with those props stripped. Guard them (\`?? default\`, \`?.\`, or an empty-state branch) from the FIRST render.
|
|
10632
10985
|
- **Array iteration**: always default to \`[]\` before \`.map\`/\`.filter\`/\`.length\`. Use \`(props.items ?? []).map(...)\` not \`props.items.map(...)\`. Same for stream.all, stream.latest, etc.
|
|
10633
10986
|
- **Object access**: optional-chain through nested fields. \`props.user?.name ?? 'Anonymous'\` not \`props.user.name\`.
|
|
10634
10987
|
- **Number ops**: default before arithmetic. \`(props.count ?? 0) + 1\` not \`props.count + 1\`.
|
|
@@ -11570,10 +11923,12 @@ These tokens adapt automatically to any theme (light, dark, branded). **ALWAYS u
|
|
|
11570
11923
|
- Borders/dividers \u2192 \`var(--ggui-color-outline)\` or \`var(--ggui-color-outlineVariant)\`
|
|
11571
11924
|
|
|
11572
11925
|
### State Colors
|
|
11573
|
-
|
|
11574
|
-
- var(--ggui-color-
|
|
11575
|
-
- var(--ggui-color-
|
|
11576
|
-
- var(--ggui-color-
|
|
11926
|
+
Semantic families ship as SCALES; the \`500\` stop is the role color (per-mode brightness), \`50\` the tint, \`800\` the on-tint text:
|
|
11927
|
+
- var(--ggui-color-success-500) \u2014 success states (tint bg: success-50, text on tint: success-800)
|
|
11928
|
+
- var(--ggui-color-warning-500) \u2014 warning states (warning-50 / warning-800)
|
|
11929
|
+
- var(--ggui-color-error-500) \u2014 error states, destructive actions (error-50 / error-800)
|
|
11930
|
+
- var(--ggui-color-info-500) \u2014 informational (info-50 / info-800)
|
|
11931
|
+
Prefer the primitives' \`tone="success|warning|error|info"\` prop over hand-written vars \u2014 it resolves to the same stops.
|
|
11577
11932
|
|
|
11578
11933
|
### IMPORTANT: Color Rules
|
|
11579
11934
|
- **NEVER** use hardcoded hex colors. ONLY use var(--ggui-color-*) tokens.
|
|
@@ -12365,6 +12720,106 @@ var STATE_MERGE_CHECKS = [
|
|
|
12365
12720
|
}
|
|
12366
12721
|
];
|
|
12367
12722
|
|
|
12723
|
+
// src/evaluation/axis-checks/checks/state-ui-affordance.ts
|
|
12724
|
+
var STATE_ARIA_OR_ROLE = /aria-(checked|pressed|selected|expanded|current)\s*=|role\s*=/;
|
|
12725
|
+
var STATE_PRIMITIVE_IN_SUBTREE = /<(Checkbox|Toggle|Tabs|Accordion)[\s/>]/;
|
|
12726
|
+
var SEMANTIC_PRIMITIVES = /* @__PURE__ */ new Set(["Button", "Checkbox", "Toggle", "Link", "MenuItem"]);
|
|
12727
|
+
function collectSetters(src) {
|
|
12728
|
+
const setters = /* @__PURE__ */ new Set();
|
|
12729
|
+
for (const m of src.matchAll(/const\s*\[\s*\w+\s*,\s*(set\w+)\s*\]\s*=\s*useState/g)) {
|
|
12730
|
+
setters.add(m[1]);
|
|
12731
|
+
}
|
|
12732
|
+
return setters;
|
|
12733
|
+
}
|
|
12734
|
+
function collectHandlers(src, setters) {
|
|
12735
|
+
const handlers = /* @__PURE__ */ new Set();
|
|
12736
|
+
for (const m of src.matchAll(/const\s+(\w+)\s*=\s*(?:\([^)]*\)|\w+)\s*=>\s*\{?/g)) {
|
|
12737
|
+
const body = src.slice(m.index + m[0].length, m.index + m[0].length + 400);
|
|
12738
|
+
for (const s of setters) {
|
|
12739
|
+
if (body.includes(s)) {
|
|
12740
|
+
handlers.add(m[1]);
|
|
12741
|
+
break;
|
|
12742
|
+
}
|
|
12743
|
+
}
|
|
12744
|
+
}
|
|
12745
|
+
return handlers;
|
|
12746
|
+
}
|
|
12747
|
+
function attrBlock(src, start) {
|
|
12748
|
+
let depth = 0;
|
|
12749
|
+
for (let i = start; i < src.length; i++) {
|
|
12750
|
+
const c = src[i];
|
|
12751
|
+
if (c === "{") depth++;
|
|
12752
|
+
else if (c === "}") depth--;
|
|
12753
|
+
else if (c === ">" && depth === 0) return { block: src.slice(start, i), end: i };
|
|
12754
|
+
}
|
|
12755
|
+
return { block: src.slice(start, start + 900), end: start + 900 };
|
|
12756
|
+
}
|
|
12757
|
+
function onClickValue(block) {
|
|
12758
|
+
const m = block.match(/onClick\s*=\s*\{/);
|
|
12759
|
+
if (!m || m.index === void 0) return null;
|
|
12760
|
+
const open = m.index + m[0].length - 1;
|
|
12761
|
+
let depth = 0;
|
|
12762
|
+
for (let i = open; i < block.length; i++) {
|
|
12763
|
+
if (block[i] === "{") depth++;
|
|
12764
|
+
else if (block[i] === "}") {
|
|
12765
|
+
depth--;
|
|
12766
|
+
if (depth === 0) return block.slice(open + 1, i);
|
|
12767
|
+
}
|
|
12768
|
+
}
|
|
12769
|
+
return block.slice(open + 1);
|
|
12770
|
+
}
|
|
12771
|
+
function subtree(src, pos, tag) {
|
|
12772
|
+
const close = src.indexOf(`</${tag}>`, pos);
|
|
12773
|
+
return src.slice(pos, close === -1 ? pos + 700 : close);
|
|
12774
|
+
}
|
|
12775
|
+
function runStateAriaPresent(input) {
|
|
12776
|
+
if (input.compiledCode === null) return [];
|
|
12777
|
+
const src = input.sourceCode;
|
|
12778
|
+
const setters = collectSetters(src);
|
|
12779
|
+
const handlers = collectHandlers(src, setters);
|
|
12780
|
+
const issues = [];
|
|
12781
|
+
const flaggedLines = /* @__PURE__ */ new Set();
|
|
12782
|
+
const localComponents = /* @__PURE__ */ new Set();
|
|
12783
|
+
for (const m of src.matchAll(/(?:function\s+([A-Z]\w*)\s*\(|const\s+([A-Z]\w*)\s*=)/g)) {
|
|
12784
|
+
localComponents.add(m[1] ?? m[2]);
|
|
12785
|
+
}
|
|
12786
|
+
for (const m of src.matchAll(/<([A-Za-z][A-Za-z0-9]*)\b/g)) {
|
|
12787
|
+
const tag = m[1];
|
|
12788
|
+
if (m.index === void 0) continue;
|
|
12789
|
+
if (localComponents.has(tag)) continue;
|
|
12790
|
+
const { block, end } = attrBlock(src, m.index + m[0].length);
|
|
12791
|
+
const clickValue = onClickValue(block);
|
|
12792
|
+
if (clickValue === null) continue;
|
|
12793
|
+
const mutatesAffordance = [...setters].some((s) => clickValue.includes(s)) || [...handlers].some((h) => new RegExp(`\\b${h}\\b`).test(clickValue)) || // Extracted-subcomponent shape: the toggle handler arrives as an
|
|
12794
|
+
// `on[A-Z]…` callback prop and is invoked from onClick — inside a
|
|
12795
|
+
// ui-affordance component that IS the affordance mutation.
|
|
12796
|
+
/\bon[A-Z]\w*\b/.test(clickValue);
|
|
12797
|
+
if (!mutatesAffordance) continue;
|
|
12798
|
+
if (SEMANTIC_PRIMITIVES.has(tag)) continue;
|
|
12799
|
+
if (STATE_ARIA_OR_ROLE.test(block)) continue;
|
|
12800
|
+
if (STATE_PRIMITIVE_IN_SUBTREE.test(subtree(src, end, tag))) continue;
|
|
12801
|
+
const line = src.slice(0, m.index).split("\n").length;
|
|
12802
|
+
if (flaggedLines.has(line)) continue;
|
|
12803
|
+
flaggedLines.add(line);
|
|
12804
|
+
issues.push(
|
|
12805
|
+
mkIssue(
|
|
12806
|
+
"state.ui_affordance.state_aria_present",
|
|
12807
|
+
`Line ${line}: <${tag}> toggles the UI affordance from its onClick but exposes no state ARIA \u2014 its checked/selected/expanded state is invisible to assistive technology (styling is the only carrier).`,
|
|
12808
|
+
`Add the matching state attribute to the SAME element that carries the onClick: \`role="checkbox"\` + \`aria-checked={\u2026}\` for a toggle row, \`aria-pressed\` for a toggle button, \`aria-selected\` for a selection, \`aria-expanded\` for an open/close affordance \u2014 or replace the hand-rolled control with the state-carrying primitive (Checkbox / Toggle / Tabs / Accordion). \`as={Clickable}\` adds button + keyboard semantics but NOT state.`
|
|
12809
|
+
)
|
|
12810
|
+
);
|
|
12811
|
+
}
|
|
12812
|
+
return issues;
|
|
12813
|
+
}
|
|
12814
|
+
var STATE_UI_AFFORDANCE_CHECKS = [
|
|
12815
|
+
{
|
|
12816
|
+
id: "state.ui_affordance.state_aria_present",
|
|
12817
|
+
axis: "state",
|
|
12818
|
+
values: ["ui-affordance"],
|
|
12819
|
+
run: runStateAriaPresent
|
|
12820
|
+
}
|
|
12821
|
+
];
|
|
12822
|
+
|
|
12368
12823
|
// src/evaluation/axis-checks/checks/realtime.ts
|
|
12369
12824
|
var REALTIME_ACTIVE = ["merge", "append", "status", "presence", "mixed"];
|
|
12370
12825
|
function runStreamHandlerPerEvent(input) {
|
|
@@ -13054,6 +13509,7 @@ var EXTRA_CHECKS = [
|
|
|
13054
13509
|
var REGISTRY = [
|
|
13055
13510
|
...UNIVERSAL_CHECKS,
|
|
13056
13511
|
...STATE_MERGE_CHECKS,
|
|
13512
|
+
...STATE_UI_AFFORDANCE_CHECKS,
|
|
13057
13513
|
...REALTIME_CHECKS,
|
|
13058
13514
|
...WRITES_CHECKS,
|
|
13059
13515
|
...STATE_PAYLOAD_CHECKS,
|
|
@@ -13224,8 +13680,8 @@ ${cjsResult.code}
|
|
|
13224
13680
|
// src/harness/check/runtime-render/load-component.ts
|
|
13225
13681
|
async function loadComponent(input) {
|
|
13226
13682
|
const { sourceCode, moduleResolutions = {} } = input;
|
|
13227
|
-
const { createRequire:
|
|
13228
|
-
const require_ =
|
|
13683
|
+
const { createRequire: createRequire5 } = await import('module');
|
|
13684
|
+
const require_ = createRequire5(import.meta.url);
|
|
13229
13685
|
const sandboxRequire = (id) => {
|
|
13230
13686
|
if (id in moduleResolutions) return moduleResolutions[id];
|
|
13231
13687
|
if (id === "react/jsx-runtime" || id === "react/jsx-dev-runtime") {
|
|
@@ -13481,6 +13937,47 @@ function wrapDetection(kind, jsx, native, custom) {
|
|
|
13481
13937
|
function dedupe(arr) {
|
|
13482
13938
|
return Array.from(new Set(arr));
|
|
13483
13939
|
}
|
|
13940
|
+
var installed = false;
|
|
13941
|
+
function installProductionActShim() {
|
|
13942
|
+
if (installed) return;
|
|
13943
|
+
installed = true;
|
|
13944
|
+
const require_ = createRequire(import.meta.url);
|
|
13945
|
+
let reactCjs;
|
|
13946
|
+
try {
|
|
13947
|
+
reactCjs = require_("react");
|
|
13948
|
+
} catch {
|
|
13949
|
+
return;
|
|
13950
|
+
}
|
|
13951
|
+
if (typeof reactCjs.act === "function") {
|
|
13952
|
+
return;
|
|
13953
|
+
}
|
|
13954
|
+
try {
|
|
13955
|
+
const testUtils = require_("react-dom/test-utils");
|
|
13956
|
+
const reactDom = require_("react-dom");
|
|
13957
|
+
testUtils.act = createFlushSyncAct(reactDom.flushSync);
|
|
13958
|
+
} catch {
|
|
13959
|
+
}
|
|
13960
|
+
}
|
|
13961
|
+
function createFlushSyncAct(flushSync) {
|
|
13962
|
+
return (callback) => {
|
|
13963
|
+
let callbackResult;
|
|
13964
|
+
flushSync(() => {
|
|
13965
|
+
callbackResult = callback();
|
|
13966
|
+
});
|
|
13967
|
+
if (isThenable(callbackResult)) {
|
|
13968
|
+
const pending = callbackResult;
|
|
13969
|
+
return new Promise((resolve3, reject) => {
|
|
13970
|
+
pending.then(() => resolve3(), reject);
|
|
13971
|
+
});
|
|
13972
|
+
}
|
|
13973
|
+
return new Promise((resolve3) => {
|
|
13974
|
+
queueMicrotask(resolve3);
|
|
13975
|
+
});
|
|
13976
|
+
};
|
|
13977
|
+
}
|
|
13978
|
+
function isThenable(value) {
|
|
13979
|
+
return typeof value === "object" && value !== null && typeof value.then === "function";
|
|
13980
|
+
}
|
|
13484
13981
|
|
|
13485
13982
|
// src/internal/open-record.ts
|
|
13486
13983
|
function openRecord(o) {
|
|
@@ -13543,6 +14040,14 @@ function parentLink(node) {
|
|
|
13543
14040
|
|
|
13544
14041
|
// src/harness/check/runtime-render/render-check.ts
|
|
13545
14042
|
async function runRenderCheck(input) {
|
|
14043
|
+
const g = hostGlobals();
|
|
14044
|
+
if ("window" in g && "document" in g) {
|
|
14045
|
+
return runRenderCheckInProcess(input);
|
|
14046
|
+
}
|
|
14047
|
+
const { runRenderCheckViaWorker: runRenderCheckViaWorker2 } = await Promise.resolve().then(() => (init_render_check_host(), render_check_host_exports));
|
|
14048
|
+
return runRenderCheckViaWorker2(input);
|
|
14049
|
+
}
|
|
14050
|
+
async function runRenderCheckInProcess(input) {
|
|
13546
14051
|
const t0 = Date.now();
|
|
13547
14052
|
const issues = [];
|
|
13548
14053
|
const teardown = await setupHappyDom();
|
|
@@ -13598,6 +14103,7 @@ async function runRenderCheck(input) {
|
|
|
13598
14103
|
const probe = createProbe();
|
|
13599
14104
|
const wireConfig = createProbeWireConfig(probe);
|
|
13600
14105
|
uninstallSpy = probe.installPostMessageSpy();
|
|
14106
|
+
installProductionActShim();
|
|
13601
14107
|
const { render, cleanup } = await import('@testing-library/react');
|
|
13602
14108
|
const userEventModule = await import('@testing-library/user-event');
|
|
13603
14109
|
const userEvent = userEventModule.default ?? userEventModule;
|
|
@@ -13753,6 +14259,50 @@ async function runRenderCheck(input) {
|
|
|
13753
14259
|
if (issue) issues.push(issue);
|
|
13754
14260
|
}
|
|
13755
14261
|
}
|
|
14262
|
+
const declaredProps = input.contract?.propsSpec?.properties;
|
|
14263
|
+
if (declaredProps) {
|
|
14264
|
+
const optionalFilled = Object.entries(declaredProps).filter(
|
|
14265
|
+
([name, entry]) => entry.required !== true && input.mockupProps[name] !== void 0
|
|
14266
|
+
).map(([name]) => name);
|
|
14267
|
+
if (optionalFilled.length > 0) {
|
|
14268
|
+
const requiredOnlyProps = { ...input.mockupProps };
|
|
14269
|
+
for (const name of optionalFilled) delete requiredOnlyProps[name];
|
|
14270
|
+
const omitBoundaryRef = {};
|
|
14271
|
+
class OmitErrorBoundary extends React.Component {
|
|
14272
|
+
state = { caught: false };
|
|
14273
|
+
static getDerivedStateFromError() {
|
|
14274
|
+
return { caught: true };
|
|
14275
|
+
}
|
|
14276
|
+
componentDidCatch(error) {
|
|
14277
|
+
omitBoundaryRef.error ??= error;
|
|
14278
|
+
}
|
|
14279
|
+
render() {
|
|
14280
|
+
if (this.state.caught) return null;
|
|
14281
|
+
return this.props.children;
|
|
14282
|
+
}
|
|
14283
|
+
}
|
|
14284
|
+
try {
|
|
14285
|
+
render(
|
|
14286
|
+
React.createElement(GguiWireProvider, {
|
|
14287
|
+
config: wireConfig,
|
|
14288
|
+
children: React.createElement(OmitErrorBoundary, {
|
|
14289
|
+
children: React.createElement(Component, requiredOnlyProps)
|
|
14290
|
+
})
|
|
14291
|
+
})
|
|
14292
|
+
);
|
|
14293
|
+
} catch (e) {
|
|
14294
|
+
omitBoundaryRef.error ??= e instanceof Error ? e : new Error(String(e));
|
|
14295
|
+
}
|
|
14296
|
+
if (omitBoundaryRef.error) {
|
|
14297
|
+
issues.push({
|
|
14298
|
+
check: "optional-props-omitted",
|
|
14299
|
+
outcome: "failed",
|
|
14300
|
+
subject: optionalFilled.join(", "),
|
|
14301
|
+
reason: `Render crashed when the optional prop(s) ${optionalFilled.map((n) => `'${n}'`).join(", ")} were omitted: ${omitBoundaryRef.error.message}. The contract marks them optional (no \`required: true\`), so a legitimate render may leave them out.`
|
|
14302
|
+
});
|
|
14303
|
+
}
|
|
14304
|
+
}
|
|
14305
|
+
}
|
|
13756
14306
|
} finally {
|
|
13757
14307
|
cleanup();
|
|
13758
14308
|
}
|
|
@@ -14324,6 +14874,15 @@ function synthesizeFromSchema(schema, hint, warnings, depth) {
|
|
|
14324
14874
|
if (schema.anyOf && schema.anyOf.length > 0) {
|
|
14325
14875
|
return synthesizeFromSchema(schema.anyOf[0], hint, warnings, depth + 1);
|
|
14326
14876
|
}
|
|
14877
|
+
if (Array.isArray(schema.type)) {
|
|
14878
|
+
const primary = schema.type.find((t) => t !== "null") ?? "null";
|
|
14879
|
+
return synthesizeFromSchema(
|
|
14880
|
+
{ ...schema, type: primary },
|
|
14881
|
+
hint,
|
|
14882
|
+
warnings,
|
|
14883
|
+
depth + 1
|
|
14884
|
+
);
|
|
14885
|
+
}
|
|
14327
14886
|
switch (schema.type) {
|
|
14328
14887
|
case "string": {
|
|
14329
14888
|
switch (schema.format) {
|
|
@@ -14494,6 +15053,16 @@ function toEvalIssue(issue) {
|
|
|
14494
15053
|
description: `Component crashed at runtime: ${issue.reason}`,
|
|
14495
15054
|
fix: classifyRenderCrashFix(issue.reason)
|
|
14496
15055
|
};
|
|
15056
|
+
case "optional-props-omitted":
|
|
15057
|
+
return {
|
|
15058
|
+
tier: 0,
|
|
15059
|
+
result,
|
|
15060
|
+
category: "crash",
|
|
15061
|
+
subcategory,
|
|
15062
|
+
severity: "critical",
|
|
15063
|
+
description: `Component crashed when optional props were omitted: ${issue.reason}`,
|
|
15064
|
+
fix: `Guard every optional prop before member access (props.${subject.split(",")[0]?.trim() ?? "x"}?.map(...), a default via destructuring, or an early empty-state return). Optional props (no \`required: true\` in the contract) may be absent at runtime even though the harness fills them.`
|
|
15065
|
+
};
|
|
14497
15066
|
case "action-wiring": {
|
|
14498
15067
|
const fix = issue.outcome === "unverified" ? `Source shows the action callback flowing into a non-native or custom-component prop. If wiring is intentional (e.g., Dropdown.onChange, drag-drop), this warn is informational \u2014 manual/browser verification is required to confirm. Otherwise wire to a native onClick={() => ${subject}(payload)} on <button> or design-system <Button>.` : `Wire ${subject}() to a native event prop. Common fix: <Button onClick={() => ${subject}(payload)}>Label</Button>. Source-AST analysis didn't find this wiring in your JSX.`;
|
|
14499
15068
|
return {
|
|
@@ -14540,6 +15109,7 @@ async function warmupRuntimeRenderProbe() {
|
|
|
14540
15109
|
missing += 1;
|
|
14541
15110
|
}
|
|
14542
15111
|
};
|
|
15112
|
+
installProductionActShim();
|
|
14543
15113
|
await Promise.all([
|
|
14544
15114
|
tryLoad("happy-dom"),
|
|
14545
15115
|
tryLoad("@testing-library/react"),
|
|
@@ -15624,6 +16194,14 @@ function mapProviderForEvaluator(provider) {
|
|
|
15624
16194
|
|
|
15625
16195
|
// src/harness/coding/run-eval-round.ts
|
|
15626
16196
|
var MAX_FEEDBACK_ISSUES = 3;
|
|
16197
|
+
function selectActionableFeedback(issues) {
|
|
16198
|
+
const pool = issues.filter((i) => i.result === "fail" || i.result === "warn");
|
|
16199
|
+
const reserved = pool.find((i) => i.tier === 0 && i.result === "fail");
|
|
16200
|
+
return [
|
|
16201
|
+
...reserved ? [reserved] : [],
|
|
16202
|
+
...pool.filter((i) => i !== reserved).sort((a, b) => (a.result === "fail" ? 0 : 1) - (b.result === "fail" ? 0 : 1))
|
|
16203
|
+
].slice(0, MAX_FEEDBACK_ISSUES);
|
|
16204
|
+
}
|
|
15627
16205
|
function fingerprintFail(issue) {
|
|
15628
16206
|
const cat = issue.category ?? "";
|
|
15629
16207
|
const sub = issue.subcategory ?? "";
|
|
@@ -15798,7 +16376,14 @@ async function runEvalRound(ctx, input) {
|
|
|
15798
16376
|
// back to `process.env` either (the eval calls run
|
|
15799
16377
|
// inside the same concurrent generation, so the race is
|
|
15800
16378
|
// real here too, not just on the coding turn).
|
|
15801
|
-
routeOverride: evaluationAgent.routeOverride
|
|
16379
|
+
routeOverride: evaluationAgent.routeOverride,
|
|
16380
|
+
// #489 — thread the same provider-429-retry observer.
|
|
16381
|
+
// Without this, a rate-limited retry inside the
|
|
16382
|
+
// evaluation agent's apiCall() happens (the base-class
|
|
16383
|
+
// retry loop is unconditional) but never reaches the
|
|
16384
|
+
// caller, silently missing the host application's
|
|
16385
|
+
// structured log.
|
|
16386
|
+
onRetry: evaluationAgent.onRetry
|
|
15802
16387
|
},
|
|
15803
16388
|
preWarmedContext
|
|
15804
16389
|
) : null,
|
|
@@ -15809,7 +16394,15 @@ async function runEvalRound(ctx, input) {
|
|
|
15809
16394
|
model: visualEvalAgent.model,
|
|
15810
16395
|
passThreshold: visualThreshold,
|
|
15811
16396
|
sampleProps: visualEvaluation?.sampleProps,
|
|
15812
|
-
viewport: visualEvaluation?.viewport
|
|
16397
|
+
viewport: visualEvaluation?.viewport,
|
|
16398
|
+
// #484/#489 — same threading as the LLM-eval leg above:
|
|
16399
|
+
// the visual eval's multimodal call runs inside the same
|
|
16400
|
+
// concurrent generation, so it needs the same routing
|
|
16401
|
+
// override (never fall back to `process.env`) and the
|
|
16402
|
+
// same provider-429-retry observer (retries reach the
|
|
16403
|
+
// host application's structured log).
|
|
16404
|
+
routeOverride: visualEvalAgent.routeOverride,
|
|
16405
|
+
onRetry: visualEvalAgent.onRetry
|
|
15813
16406
|
}
|
|
15814
16407
|
) : null
|
|
15815
16408
|
]);
|
|
@@ -16028,7 +16621,7 @@ async function runEvalRound(ctx, input) {
|
|
|
16028
16621
|
lastDiffFailed: false
|
|
16029
16622
|
};
|
|
16030
16623
|
}
|
|
16031
|
-
const allActionable = evalResult.issues
|
|
16624
|
+
const allActionable = selectActionableFeedback(evalResult.issues);
|
|
16032
16625
|
const issueLines = allActionable.map((issue) => {
|
|
16033
16626
|
const tag = issue.result === "fail" ? "FAIL" : "WARN";
|
|
16034
16627
|
return `[${tag}] ${issue.category}${issue.subcategory ? "/" + issue.subcategory : ""}: ${issue.description}
|
|
@@ -16043,7 +16636,16 @@ async function runEvalRound(ctx, input) {
|
|
|
16043
16636
|
evalResult,
|
|
16044
16637
|
evalRoundsUsed,
|
|
16045
16638
|
prevModeSubcats: updatedPrevModeSubcats,
|
|
16046
|
-
|
|
16639
|
+
// DELIVERED fails only (Exp 54 P1 addendum, proposal 2): the
|
|
16640
|
+
// stuck detector's premise is "the LLM saw these and did not
|
|
16641
|
+
// resolve them" — recording fails that the MAX_FEEDBACK_ISSUES
|
|
16642
|
+
// cap cut from the feedback turns truncation into a premature
|
|
16643
|
+
// stuck-exit (the model never saw the issue it is blamed for
|
|
16644
|
+
// not fixing). An undelivered fail recurring next round now
|
|
16645
|
+
// reads as NEW, gets delivered, and earns its fix round.
|
|
16646
|
+
prevFailFingerprints: new Set(
|
|
16647
|
+
allActionable.filter((i) => i.result === "fail").map(fingerprintFail)
|
|
16648
|
+
),
|
|
16047
16649
|
preWarmedContext,
|
|
16048
16650
|
evalTokens,
|
|
16049
16651
|
evalLlmMs,
|
|
@@ -16538,12 +17140,14 @@ async function dispatchGeneration(params) {
|
|
|
16538
17140
|
const codingAgent = {
|
|
16539
17141
|
provider: routerProvider,
|
|
16540
17142
|
model: resolvedModel,
|
|
16541
|
-
routeOverride: params.routeOverride
|
|
17143
|
+
routeOverride: params.routeOverride,
|
|
17144
|
+
onRetry: params.onRetry
|
|
16542
17145
|
};
|
|
16543
17146
|
const evaluationAgent = params.models?.evaluation ? {
|
|
16544
17147
|
provider: routerProvider,
|
|
16545
17148
|
model: params.models.evaluation,
|
|
16546
|
-
routeOverride: params.routeOverride
|
|
17149
|
+
routeOverride: params.routeOverride,
|
|
17150
|
+
onRetry: params.onRetry
|
|
16547
17151
|
} : void 0;
|
|
16548
17152
|
const effectiveContract = params.contract;
|
|
16549
17153
|
const sessionParams = {
|