@ggui-ai/ui-gen 0.28.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapter-h4_V69LI.d.ts → adapter-DLQ-5COm.d.ts} +1 -1
- package/dist/adapters/base.js +2 -27
- package/dist/adapters/base.js.map +1 -1
- package/dist/adapters/generation-dispatch.d.ts +14 -6
- package/dist/adapters/generation-dispatch.js +40 -19260
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +25 -3561
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/types.js +2 -9
- package/dist/adapters/types.js.map +1 -1
- package/dist/advanced/index.d.ts +13 -12
- package/dist/advanced/index.js +63 -21954
- package/dist/advanced/index.js.map +1 -1
- package/dist/{bank-Ctdp3FUA.d.ts → bank-LWaiAtaA.d.ts} +1 -1
- package/dist/blueprint-validator.js +6 -2301
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.d.ts +3 -2
- package/dist/boilerplate.js +6 -1867
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +3 -2
- package/dist/check/index.js +6 -1923
- package/dist/check/index.js.map +1 -1
- package/dist/chunk-33B3BAWS.js +24 -0
- package/dist/chunk-33B3BAWS.js.map +1 -0
- package/dist/chunk-3HSGA6NI.js +591 -0
- package/dist/chunk-3HSGA6NI.js.map +1 -0
- package/dist/chunk-3NNNGNTO.js +1707 -0
- package/dist/chunk-3NNNGNTO.js.map +1 -0
- package/dist/chunk-3T2HMTON.js +79 -0
- package/dist/chunk-3T2HMTON.js.map +1 -0
- package/dist/chunk-3ZSSDV3L.js +123 -0
- package/dist/chunk-3ZSSDV3L.js.map +1 -0
- package/dist/chunk-4MBR5GA3.js +99 -0
- package/dist/chunk-4MBR5GA3.js.map +1 -0
- package/dist/chunk-56NJFD55.js +337 -0
- package/dist/chunk-56NJFD55.js.map +1 -0
- package/dist/chunk-5AGTY555.js +26 -0
- package/dist/chunk-5AGTY555.js.map +1 -0
- package/dist/chunk-5EJM7EGY.js +115 -0
- package/dist/chunk-5EJM7EGY.js.map +1 -0
- package/dist/chunk-5KXCQLLR.js +529 -0
- package/dist/chunk-5KXCQLLR.js.map +1 -0
- package/dist/chunk-5VBXHVEH.js +171 -0
- package/dist/chunk-5VBXHVEH.js.map +1 -0
- package/dist/chunk-6KDDEING.js +515 -0
- package/dist/chunk-6KDDEING.js.map +1 -0
- package/dist/chunk-6VDRWAP6.js +123 -0
- package/dist/chunk-6VDRWAP6.js.map +1 -0
- package/dist/chunk-732IBFEJ.js +2400 -0
- package/dist/chunk-732IBFEJ.js.map +1 -0
- package/dist/chunk-73AURS3I.js +46 -0
- package/dist/chunk-73AURS3I.js.map +1 -0
- package/dist/chunk-77H5SKLX.js +8 -0
- package/dist/chunk-77H5SKLX.js.map +1 -0
- package/dist/chunk-7QE3AHLE.js +35 -0
- package/dist/chunk-7QE3AHLE.js.map +1 -0
- package/dist/chunk-A375NNWN.js +11 -0
- package/dist/chunk-A375NNWN.js.map +1 -0
- package/dist/chunk-AOU33EFS.js +140 -0
- package/dist/chunk-AOU33EFS.js.map +1 -0
- package/dist/chunk-BA4QYA6E.js +461 -0
- package/dist/chunk-BA4QYA6E.js.map +1 -0
- package/dist/chunk-DBP764GL.js +3 -0
- package/dist/chunk-DBP764GL.js.map +1 -0
- package/dist/chunk-DUU3ZJDQ.js +41 -0
- package/dist/chunk-DUU3ZJDQ.js.map +1 -0
- package/dist/chunk-DXKRBXNU.js +30 -0
- package/dist/chunk-DXKRBXNU.js.map +1 -0
- package/dist/chunk-FB7OEEAH.js +1467 -0
- package/dist/chunk-FB7OEEAH.js.map +1 -0
- package/dist/chunk-FLRNMZV2.js +18 -0
- package/dist/chunk-FLRNMZV2.js.map +1 -0
- package/dist/chunk-FWJIMKFZ.js +2032 -0
- package/dist/chunk-FWJIMKFZ.js.map +1 -0
- package/dist/chunk-FXRHWYIQ.js +163 -0
- package/dist/chunk-FXRHWYIQ.js.map +1 -0
- package/dist/chunk-G3PMV62Z.js +33 -0
- package/dist/chunk-G3PMV62Z.js.map +1 -0
- package/dist/chunk-GAJI7Y3P.js +13 -0
- package/dist/chunk-GAJI7Y3P.js.map +1 -0
- package/dist/chunk-GYINWMPR.js +2052 -0
- package/dist/chunk-GYINWMPR.js.map +1 -0
- package/dist/chunk-HD6ABIK7.js +484 -0
- package/dist/chunk-HD6ABIK7.js.map +1 -0
- package/dist/chunk-HDQVHYO6.js +100 -0
- package/dist/chunk-HDQVHYO6.js.map +1 -0
- package/dist/chunk-IHUT3SIW.js +7 -0
- package/dist/chunk-IHUT3SIW.js.map +1 -0
- package/dist/chunk-JDH5EGNZ.js +126 -0
- package/dist/chunk-JDH5EGNZ.js.map +1 -0
- package/dist/chunk-JMD4RGSV.js +37 -0
- package/dist/chunk-JMD4RGSV.js.map +1 -0
- package/dist/chunk-KJWGZL5B.js +33 -0
- package/dist/chunk-KJWGZL5B.js.map +1 -0
- package/dist/chunk-MKMDQFZ7.js +86 -0
- package/dist/chunk-MKMDQFZ7.js.map +1 -0
- package/dist/chunk-NF6JUZWF.js +85 -0
- package/dist/chunk-NF6JUZWF.js.map +1 -0
- package/dist/chunk-O2YC6XDC.js +1933 -0
- package/dist/chunk-O2YC6XDC.js.map +1 -0
- package/dist/chunk-OVLH5ICV.js +29 -0
- package/dist/chunk-OVLH5ICV.js.map +1 -0
- package/dist/chunk-PBOYHGSD.js +106 -0
- package/dist/chunk-PBOYHGSD.js.map +1 -0
- package/dist/chunk-PJFH7QWA.js +81 -0
- package/dist/chunk-PJFH7QWA.js.map +1 -0
- package/dist/chunk-PUH4CXGZ.js +284 -0
- package/dist/chunk-PUH4CXGZ.js.map +1 -0
- package/dist/chunk-QLXXPGTV.js +18 -0
- package/dist/chunk-QLXXPGTV.js.map +1 -0
- package/dist/chunk-R5GIGPW2.js +2845 -0
- package/dist/chunk-R5GIGPW2.js.map +1 -0
- package/dist/chunk-RH4IUSTO.js +83 -0
- package/dist/chunk-RH4IUSTO.js.map +1 -0
- package/dist/chunk-S2GVXKKY.js +153 -0
- package/dist/chunk-S2GVXKKY.js.map +1 -0
- package/dist/chunk-TVHGKFJS.js +58 -0
- package/dist/chunk-TVHGKFJS.js.map +1 -0
- package/dist/chunk-U7GALWLG.js +66 -0
- package/dist/chunk-U7GALWLG.js.map +1 -0
- package/dist/chunk-VTC263LT.js +256 -0
- package/dist/chunk-VTC263LT.js.map +1 -0
- package/dist/chunk-WEOXFEVE.js +739 -0
- package/dist/chunk-WEOXFEVE.js.map +1 -0
- package/dist/chunk-WYHP46LT.js +28 -0
- package/dist/chunk-WYHP46LT.js.map +1 -0
- package/dist/chunk-XGLO5LGG.js +274 -0
- package/dist/chunk-XGLO5LGG.js.map +1 -0
- package/dist/chunk-XRBCRKA2.js +29 -0
- package/dist/chunk-XRBCRKA2.js.map +1 -0
- package/dist/chunk-XW7ADFFV.js +340 -0
- package/dist/chunk-XW7ADFFV.js.map +1 -0
- package/dist/chunk-YIQ42IUX.js +34 -0
- package/dist/chunk-YIQ42IUX.js.map +1 -0
- package/dist/chunk-YJZTUY7B.js +101 -0
- package/dist/chunk-YJZTUY7B.js.map +1 -0
- package/dist/chunk-Z3CXME3K.js +107 -0
- package/dist/chunk-Z3CXME3K.js.map +1 -0
- package/dist/classifier/index.js +2 -527
- package/dist/classifier/index.js.map +1 -1
- package/dist/client-JBPCXU7W.js +4 -0
- package/dist/client-JBPCXU7W.js.map +1 -0
- package/dist/compose.js +3 -367
- package/dist/compose.js.map +1 -1
- package/dist/cost-tracker-HHGQ4FTW.js +67 -0
- package/dist/cost-tracker-HHGQ4FTW.js.map +1 -0
- package/dist/{create-ui-generator-BrZHF0cf.d.ts → create-ui-generator-BY-WeaPi.d.ts} +3 -3
- package/dist/{design-mode-DGWN_Gkm.d.ts → design-mode-XyOQmzE0.d.ts} +4 -3
- package/dist/{dispatch-B07TI30d.d.ts → dispatch-8LzTddKw.d.ts} +3 -2
- package/dist/dist-UALN6ZVB.js +543 -0
- package/dist/dist-UALN6ZVB.js.map +1 -0
- package/dist/dupe-break-X6K7QFIS.js +4 -0
- package/dist/dupe-break-X6K7QFIS.js.map +1 -0
- package/dist/evaluation/axis-checks/index.d.ts +5 -4
- package/dist/evaluation/axis-checks/index.js +6 -1914
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +3 -2
- package/dist/evaluation/axis-checks/registry.js +3 -1844
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +49 -132
- package/dist/evaluation/index.js +302 -6466
- package/dist/evaluation/index.js.map +1 -1
- package/dist/evaluation/types.d.ts +3 -2
- package/dist/evaluation/types.js +2 -5
- package/dist/evaluation/types.js.map +1 -1
- package/dist/fragments/index.js +2 -338
- package/dist/fragments/index.js.map +1 -1
- package/dist/{generate-tSO_shQ0.d.ts → generate-DQ4ci90s.d.ts} +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +35 -15
- package/dist/harness/check/runtime-render/index.js +6 -2390
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/check/runtime-render/render-check-worker.js +4 -1739
- package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
- package/dist/harness/index.d.ts +3 -2
- package/dist/harness/index.js +23 -5126
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/llm-trace-sink.js +2 -24
- package/dist/harness/llm-trace-sink.js.map +1 -1
- package/dist/harness/prompts.d.ts +2 -2
- package/dist/harness/prompts.js +8 -324
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +6 -5
- package/dist/harness/result-types.js +2 -0
- package/dist/harness/result-types.js.map +1 -1
- package/dist/harness/runtime.d.ts +22 -5
- package/dist/harness/runtime.js +10 -2313
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +3 -2
- package/dist/harness/types-public.js +16 -2954
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +3 -2
- package/dist/harness/validator-trace-sink.js +2 -26
- package/dist/harness/validator-trace-sink.js.map +1 -1
- package/dist/hash.js +2 -39
- package/dist/hash.js.map +1 -1
- package/dist/hashline-5OKREIRM.js +4 -0
- package/dist/hashline-5OKREIRM.js.map +1 -0
- package/dist/index.d.ts +50 -11
- package/dist/index.js +57 -22015
- package/dist/index.js.map +1 -1
- package/dist/llm-evaluator-PN3WFCS4.js +653 -0
- package/dist/llm-evaluator-PN3WFCS4.js.map +1 -0
- package/dist/magic-string.es-2NZTC4GZ.js +1307 -0
- package/dist/magic-string.es-2NZTC4GZ.js.map +1 -0
- package/dist/patch.d.ts +3 -2
- package/dist/patch.js +2 -56
- package/dist/patch.js.map +1 -1
- package/dist/policy-VCYKGSAC.js +5 -0
- package/dist/policy-VCYKGSAC.js.map +1 -0
- package/dist/policy.js +2 -35
- package/dist/policy.js.map +1 -1
- package/dist/primitive-index-2OF6F4TZ.js +131 -0
- package/dist/primitive-index-2OF6F4TZ.js.map +1 -0
- package/dist/primitive-slice-WTMBLYFJ.js +235 -0
- package/dist/primitive-slice-WTMBLYFJ.js.map +1 -0
- package/dist/{prompts-BhNUuRoZ.d.ts → prompts-CLZGywMP.d.ts} +1 -1
- package/dist/provider-adapter-contract.js +10407 -14204
- package/dist/provider-adapter-contract.js.map +1 -1
- package/dist/provider-adapter.js +2 -44
- package/dist/provider-adapter.js.map +1 -1
- package/dist/providers/index.d.ts +89 -1
- package/dist/providers/index.js +115 -44
- package/dist/providers/index.js.map +1 -1
- package/dist/raw-C6KBH5I3.js +141 -0
- package/dist/raw-C6KBH5I3.js.map +1 -0
- package/dist/raw-FXSBXQTZ.js +7 -0
- package/dist/raw-FXSBXQTZ.js.map +1 -0
- package/dist/raw-MURDUPGK.js +8 -0
- package/dist/raw-MURDUPGK.js.map +1 -0
- package/dist/raw-T3GW3UVX.js +8 -0
- package/dist/raw-T3GW3UVX.js.map +1 -0
- package/dist/render-check-host-ZNIS6BJ5.js +5 -0
- package/dist/render-check-host-ZNIS6BJ5.js.map +1 -0
- package/dist/sdk-4JNSDNSR.js +6 -0
- package/dist/sdk-4JNSDNSR.js.map +1 -0
- package/dist/sdk-DIVJMHCX.js +7 -0
- package/dist/sdk-DIVJMHCX.js.map +1 -0
- package/dist/sdk-QRJOCRTS.js +7 -0
- package/dist/sdk-QRJOCRTS.js.map +1 -0
- package/dist/tag-balance-CTHCTRJ7.js +123 -0
- package/dist/tag-balance-CTHCTRJ7.js.map +1 -0
- package/dist/tools/get-primitives-ts.d.ts +1 -1
- package/dist/tools/get-primitives-ts.js +3 -1
- package/dist/tools/get-primitives-ts.js.map +1 -1
- package/dist/tools/render-check-worker.js +3 -40
- package/dist/tools/render-check-worker.js.map +1 -1
- package/dist/{types-public-h8IMnYuh.d.ts → types-public-hYlrerZh.d.ts} +35 -2
- package/dist/validation/index.d.ts +2 -2
- package/dist/validation/index.js +3 -498
- package/dist/validation/index.js.map +1 -1
- package/dist/{visual-evaluator-C2strxqD.d.ts → visual-evaluator-KskajxD5.d.ts} +14 -4
- package/dist/visual-evaluator-TGEXYQTP.js +12 -0
- package/dist/visual-evaluator-TGEXYQTP.js.map +1 -0
- package/dist/workflows.d.ts +3 -2
- package/dist/workflows.js +2 -151
- package/dist/workflows.js.map +1 -1
- package/package.json +20 -16
- package/src/adapters/claude/sdk.ts +4 -2
- package/src/adapters/claude/tool-bridge.ts +2 -1
- package/src/adapters/generation-dispatch.ts +9 -0
- package/src/adapters/openai/raw.ts +4 -0
- package/src/adapters/openai/sdk.ts +6 -1
- package/src/adapters/openai/tokens.ts +21 -7
- package/src/adapters/tool-bridge.ts +1 -1
- package/src/adapters/tools.ts +1 -1
- package/src/advanced/feedback.ts +1 -2
- package/src/advanced/generator.ts +4 -3
- package/src/boilerplate/free-design-prompt.ts +7 -0
- package/src/boilerplate/generate.ts +18 -5
- package/src/boilerplate/hard-sections.ts +8 -3
- package/src/boilerplate/react-hooks.ts +56 -0
- package/src/boilerplate/system-prompt.ts +12 -3
- package/src/check/run-tier0.ts +233 -8
- package/src/check/type-checker.ts +128 -16
- package/src/coding-agent/tools.ts +26 -3
- package/src/design-mode.ts +4 -9
- package/src/evaluation/axis-checks/checks/state-merge.ts +2 -4
- package/src/evaluation/axis-checks/checks/state-payload.ts +1 -28
- package/src/evaluation/axis-checks/checks/tint.ts +197 -0
- package/src/evaluation/axis-checks/helpers.ts +121 -0
- package/src/evaluation/axis-checks/registry.ts +2 -0
- package/src/evaluation/criteria/__fixtures__/edge-v2-draw2.json +4629 -0
- package/src/evaluation/criteria/bank.ts +1 -1
- package/src/evaluation/criteria/edge-instruments.ts +14 -0
- package/src/evaluation/criteria/edge-probe-v2.ts +352 -0
- package/src/evaluation/criteria/edge-v2.ts +242 -0
- package/src/evaluation/criteria/resolve.ts +24 -5
- package/src/evaluation/index.ts +3 -5
- package/src/evaluation/mcp-server.ts +0 -50
- package/src/evaluation/types-public.ts +6 -1
- package/src/evaluation/visual-evaluator.ts +161 -30
- package/src/harness/check/runtime-render/index.ts +28 -4
- package/src/harness/check/runtime-render/render-check.ts +20 -21
- package/src/harness/claude-agent-sdk-loader.ts +93 -0
- package/src/harness/claude-code-login-query.ts +1 -1
- package/src/harness/coding/generate-task-runner.ts +1 -0
- package/src/harness/coding/run-coding-turn.ts +3 -0
- package/src/harness/coding/run-eval-round.ts +9 -0
- package/src/harness/llm-router.ts +50 -2
- package/src/harness/runtime.ts +17 -0
- package/src/index.ts +9 -0
- package/src/providers/anthropic-tool-call.ts +263 -0
- package/src/providers/index.ts +13 -0
- package/src/tools/get-primitives-ts.ts +1 -1
- package/src/tools/render-check.ts +4 -4
- package/src/validation/primitives.ts +2 -1
- package/src/evaluation/loop.ts +0 -218
|
@@ -0,0 +1,2845 @@
|
|
|
1
|
+
import { createDupeBreakState, recordToolExchange, SAME_EXCHANGE_NUDGE, SAME_EXCHANGE_BREAK_AT, clearWriteFailures, recordWriteFailure, SAME_FAILURE_NUDGE_AT, SAME_FAILURE_BREAK_AT, SAME_FAILURE_NUDGE } from './chunk-RH4IUSTO.js';
|
|
2
|
+
import { resolveHarnessPolicy } from './chunk-5VBXHVEH.js';
|
|
3
|
+
import { formatWithHashlines } from './chunk-4MBR5GA3.js';
|
|
4
|
+
import { buildSystemPrompt } from './chunk-5EJM7EGY.js';
|
|
5
|
+
import { PRIMITIVES_DOCUMENTATION } from './chunk-HD6ABIK7.js';
|
|
6
|
+
import { createHarness, runHarness, APPLY_CHANGES_TOOL, GET_ICONS_TOOL, APPLY_CHANGES_TOOL_SCOPED, ICON_LIST_NO_MATCH_GUIDANCE, runCheck, REWRITE_TOOL, WRITE_PLAN_TOOL, GET_COMPONENTS_INFO_TOOL, APPLY_CHANGES_HASHLINE_TOOL_FLAT, APPLY_CHANGES_HASHLINE_TOOL, APPLY_CHANGES_TOOL_FLAT } from './chunk-WEOXFEVE.js';
|
|
7
|
+
import { runTier0Checks } from './chunk-FWJIMKFZ.js';
|
|
8
|
+
import { classifyAxes } from './chunk-5KXCQLLR.js';
|
|
9
|
+
import { criteriaContextFor } from './chunk-DXKRBXNU.js';
|
|
10
|
+
import { createAgent } from './chunk-O2YC6XDC.js';
|
|
11
|
+
import { traceAxisChecksSkipped, runGatedAxisChecks } from './chunk-U7GALWLG.js';
|
|
12
|
+
import { REGISTRY } from './chunk-GYINWMPR.js';
|
|
13
|
+
import { warmupRuntimeRenderProbe, DEFAULT_RUNTIME_RENDER_CHECK, isRecoverableRenderCrash, parseRuntimeSubcategory } from './chunk-BA4QYA6E.js';
|
|
14
|
+
import { RENDER_CHECK_KINDS } from './chunk-3NNNGNTO.js';
|
|
15
|
+
import { restoreReactImport } from './chunk-Z3CXME3K.js';
|
|
16
|
+
import { matches, getActionableIssues, notApplicableCoverage } from './chunk-PUH4CXGZ.js';
|
|
17
|
+
import { canvasForRendering, DEFAULT_DESIGN_MODE } from './chunk-3T2HMTON.js';
|
|
18
|
+
import { DEFAULT_HARNESS_POLICY } from './chunk-JMD4RGSV.js';
|
|
19
|
+
import * as esbuild from 'esbuild';
|
|
20
|
+
import { LUCIDE_ICON_NAMES } from '@ggui-ai/design';
|
|
21
|
+
import { listContractGadgets } from '@ggui-ai/protocol';
|
|
22
|
+
import git from 'isomorphic-git';
|
|
23
|
+
import { Volume, createFsFromVolume } from 'memfs';
|
|
24
|
+
import { createPatch, applyPatch } from 'diff';
|
|
25
|
+
|
|
26
|
+
// src/harness/coding/assemble-result.ts
|
|
27
|
+
async function assembleGenerationResult(input) {
|
|
28
|
+
const { session, telemetry, source } = input;
|
|
29
|
+
const a1Mode = session.harness.process.mode === "staged";
|
|
30
|
+
let needsBackgroundImprovement = false;
|
|
31
|
+
if (session.qualityMode === "auto-improve" && telemetry.evalResult && session.tiersMod) {
|
|
32
|
+
const remaining = getActionableIssues(
|
|
33
|
+
telemetry.evalResult,
|
|
34
|
+
"high-quality"
|
|
35
|
+
);
|
|
36
|
+
if (remaining.length > 0) {
|
|
37
|
+
needsBackgroundImprovement = true;
|
|
38
|
+
console.log(
|
|
39
|
+
`[simple] auto-improve: ${remaining.length} remaining issues \u2192 background improvement needed`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const totalMs = Date.now() - session.startedAtMs;
|
|
44
|
+
const evalWallMs = telemetry.cumulativeEvalWallMs;
|
|
45
|
+
const codingOnlyMs = Math.max(0, telemetry.codingMs - evalWallMs);
|
|
46
|
+
const setupMs = Math.max(0, totalMs - telemetry.codingMs);
|
|
47
|
+
const timing = {
|
|
48
|
+
codingMs: codingOnlyMs,
|
|
49
|
+
evalMs: evalWallMs,
|
|
50
|
+
setupMs,
|
|
51
|
+
totalMs
|
|
52
|
+
};
|
|
53
|
+
await session.codingAgent.cleanup();
|
|
54
|
+
console.log(
|
|
55
|
+
`[simple] timing: coding=${codingOnlyMs}ms eval=${evalWallMs}ms setup=${setupMs}ms total=${totalMs}ms`
|
|
56
|
+
);
|
|
57
|
+
console.log(
|
|
58
|
+
`[simple] DONE | ${totalMs}ms | turns=${telemetry.turnsUsed} | evalRounds=${telemetry.evalRoundsUsed} | tokens=${telemetry.totalIn + telemetry.totalOut} | compiled=${telemetry.compiledCode.length}B`
|
|
59
|
+
);
|
|
60
|
+
const { phases, outcomes } = telemetry.counters;
|
|
61
|
+
console.log(
|
|
62
|
+
`[simple] SUMMARY | turns=${telemetry.turnsUsed} impl=${phases.impl} patch=${phases.patch} evalFix=${phases.evalFix}` + (a1Mode ? ` scaffold=${phases.scaffold} fill=${phases.fill}` : "") + ` | pass=${outcomes.pass} patchInvalid=${outcomes.patchInvalid} selfCheckFail=${outcomes.selfCheckFail} diffFail=${outcomes.diffFail} | evalRounds=${telemetry.evalRoundsUsed} | codingLlmMs=${telemetry.cumulativeLlmMs} evalLlmMs=${telemetry.cumulativeEvalLlmMs} toolMs=${telemetry.cumulativeToolMs} evalWallMs=${evalWallMs}`
|
|
63
|
+
);
|
|
64
|
+
return {
|
|
65
|
+
compiledCode: telemetry.compiledCode,
|
|
66
|
+
sourceCode: source,
|
|
67
|
+
tokens: {
|
|
68
|
+
input: telemetry.totalIn,
|
|
69
|
+
output: telemetry.totalOut,
|
|
70
|
+
// ggui#1186: the FULL processed footprint — the adapters' convention
|
|
71
|
+
// (non-cached input + cache creation + cache reads + output) — so a
|
|
72
|
+
// row's cached count is `total − input − output` even where the cache
|
|
73
|
+
// fields are not published. `input + output` alone priced the cached
|
|
74
|
+
// prefix and hid it (a 36k-token luna turn published as 1.6k).
|
|
75
|
+
total: telemetry.totalIn + (telemetry.cacheCreationTokens ?? 0) + (telemetry.cacheReadTokens ?? 0) + telemetry.totalOut
|
|
76
|
+
},
|
|
77
|
+
// Prompt-cache counters are provider-specific — pass through truthfully.
|
|
78
|
+
// Absent on the telemetry (provider didn't report) stays absent here,
|
|
79
|
+
// never defaulted to 0, so the downstream observability ratio reflects
|
|
80
|
+
// real cache activity instead of a structural zero.
|
|
81
|
+
...telemetry.cacheReadTokens !== void 0 ? { cacheReadTokens: telemetry.cacheReadTokens } : {},
|
|
82
|
+
...telemetry.cacheCreationTokens !== void 0 ? { cacheCreationTokens: telemetry.cacheCreationTokens } : {},
|
|
83
|
+
// ggui#1522 — the in-loop visual judge's spend, beside `tokens` and never inside it.
|
|
84
|
+
...telemetry.inLoopVisualTokens !== void 0 ? { inLoopVisualTokens: telemetry.inLoopVisualTokens } : {},
|
|
85
|
+
generationTimeMs: totalMs,
|
|
86
|
+
turnsUsed: telemetry.turnsUsed,
|
|
87
|
+
passesUsed: 1,
|
|
88
|
+
// ggui#1261 — the contract-feedback round's record rides the result the
|
|
89
|
+
// generation ends with (whose probe stamp is the post-round verdict).
|
|
90
|
+
evalResult: telemetry.evalResult !== void 0 && telemetry.contractFeedback !== void 0 ? { ...telemetry.evalResult, contractFeedback: telemetry.contractFeedback } : telemetry.evalResult,
|
|
91
|
+
// ggui#404 — present only when the same-exchange guard ended the run.
|
|
92
|
+
...telemetry.sameExchangeBreak !== void 0 ? { sameExchangeBreak: telemetry.sameExchangeBreak } : {},
|
|
93
|
+
...telemetry.sameFailureBreak !== void 0 ? { sameFailureBreak: telemetry.sameFailureBreak } : {},
|
|
94
|
+
needsBackgroundImprovement,
|
|
95
|
+
selfCheckPassed: telemetry.selfCheckPassed,
|
|
96
|
+
timing,
|
|
97
|
+
breakdown: {
|
|
98
|
+
phases: { ...phases },
|
|
99
|
+
outcomes: { ...outcomes },
|
|
100
|
+
evalRounds: telemetry.evalRoundsUsed,
|
|
101
|
+
// `llmMs` retained as coding-turn LLM (stable semantics for existing
|
|
102
|
+
// consumers in benchmarks/multi-sdk/runner.ts:312); `evalLlmMs` is
|
|
103
|
+
// new, covers eval-round parallel LLM+visual wall-time.
|
|
104
|
+
llmMs: telemetry.cumulativeLlmMs,
|
|
105
|
+
evalLlmMs: telemetry.cumulativeEvalLlmMs,
|
|
106
|
+
toolMs: telemetry.cumulativeToolMs,
|
|
107
|
+
evalMs: evalWallMs,
|
|
108
|
+
codingMs: codingOnlyMs,
|
|
109
|
+
setupMs
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/coding-agent/self-check.ts
|
|
115
|
+
function getSoftWarnings(code) {
|
|
116
|
+
const warnings = [];
|
|
117
|
+
if (!/var\(--ggui-/.test(code)) {
|
|
118
|
+
warnings.push(
|
|
119
|
+
"uses_design_tokens: No CSS variables (var(--ggui-*)) found \u2014 consider using design system tokens"
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
if (!/aria-label/.test(code)) {
|
|
123
|
+
warnings.push(
|
|
124
|
+
"has_aria_labels: No aria-label attributes found \u2014 consider adding for accessibility"
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return warnings;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/coding-agent/tools.ts
|
|
131
|
+
function axisCountsWithIds(issues) {
|
|
132
|
+
const idOf = (i) => i.subcategory ?? i.category;
|
|
133
|
+
const warns = issues.filter((i) => i.result === "warn").map(idOf);
|
|
134
|
+
const fails = issues.filter((i) => i.result !== "warn").map(idOf);
|
|
135
|
+
const groups = [...fails.length > 0 ? [`F:${fails.join(",")}`] : [], ...warns.length > 0 ? [`W:${warns.join(",")}`] : []];
|
|
136
|
+
return `${fails.length}F/${warns.length}W${groups.length > 0 ? `[${groups.join(" ")}]` : ""}`;
|
|
137
|
+
}
|
|
138
|
+
function getComponentDocumentation(name) {
|
|
139
|
+
const marker = `### ${name}`;
|
|
140
|
+
const startIdx = PRIMITIVES_DOCUMENTATION.indexOf(marker);
|
|
141
|
+
if (startIdx === -1) {
|
|
142
|
+
return `Component "${name}" not found. Available primitives: Container, Card, Stack, Row, Box, Text, Heading, Button, Input, Select, Checkbox, Toggle, Badge, Alert, Progress, Image, Icon, Divider, Tabs, Accordion, Table, Tooltip, Spinner, Avatar, Link`;
|
|
143
|
+
}
|
|
144
|
+
const nextMarker = PRIMITIVES_DOCUMENTATION.indexOf("### ", startIdx + marker.length);
|
|
145
|
+
const section = nextMarker === -1 ? PRIMITIVES_DOCUMENTATION.slice(startIdx) : PRIMITIVES_DOCUMENTATION.slice(startIdx, nextMarker);
|
|
146
|
+
return section.replace(/\\n/g, "\n").replace(/\\"/g, '"').slice(0, 3e3);
|
|
147
|
+
}
|
|
148
|
+
async function autoCommit(workspace, commitMeta, message, contract, contextPolicy, gadgetTypes, designMode = DEFAULT_DESIGN_MODE, axis) {
|
|
149
|
+
const commitStart = Date.now();
|
|
150
|
+
const raw = workspace.read();
|
|
151
|
+
if (!raw && raw !== "") {
|
|
152
|
+
return { result: "FAILED: no file to compile", error: true };
|
|
153
|
+
}
|
|
154
|
+
const restore = restoreReactImport(raw);
|
|
155
|
+
if (restore.added.length > 0) {
|
|
156
|
+
workspace.write(restore.source);
|
|
157
|
+
console.log(`[coding-agent] react-import restored: ${restore.added.map((h) => `+${h}`).join(",")}`);
|
|
158
|
+
}
|
|
159
|
+
const formatted = restore.source;
|
|
160
|
+
const buildStart = Date.now();
|
|
161
|
+
let buildSuccess = false;
|
|
162
|
+
let compiledCode = "";
|
|
163
|
+
const buildErrors = [];
|
|
164
|
+
try {
|
|
165
|
+
const result = await esbuild.transform(formatted, {
|
|
166
|
+
loader: "tsx",
|
|
167
|
+
target: "es2020",
|
|
168
|
+
format: "esm",
|
|
169
|
+
jsx: "automatic",
|
|
170
|
+
jsxImportSource: "react",
|
|
171
|
+
minify: true,
|
|
172
|
+
keepNames: true
|
|
173
|
+
});
|
|
174
|
+
compiledCode = result.code;
|
|
175
|
+
buildSuccess = true;
|
|
176
|
+
} catch (e) {
|
|
177
|
+
buildErrors.push(e instanceof Error ? e.message : String(e));
|
|
178
|
+
}
|
|
179
|
+
const buildMs = Date.now() - buildStart;
|
|
180
|
+
const buildResult = {
|
|
181
|
+
success: buildSuccess,
|
|
182
|
+
compiledCode: buildSuccess ? compiledCode : void 0,
|
|
183
|
+
errors: buildErrors.length > 0 ? buildErrors : void 0
|
|
184
|
+
};
|
|
185
|
+
const selfCheckStart = Date.now();
|
|
186
|
+
const tier0Issues = await runTier0Checks(
|
|
187
|
+
formatted,
|
|
188
|
+
contract,
|
|
189
|
+
buildResult.errors,
|
|
190
|
+
gadgetTypes,
|
|
191
|
+
designMode
|
|
192
|
+
);
|
|
193
|
+
const axisFacts = axis !== void 0 ? {
|
|
194
|
+
sourceCode: formatted,
|
|
195
|
+
...contract !== void 0 ? { contract } : {},
|
|
196
|
+
originalPrompt: axis.originalPrompt,
|
|
197
|
+
classification: axis.classification,
|
|
198
|
+
designMode,
|
|
199
|
+
...axis.canvas !== void 0 ? { canvas: axis.canvas } : {},
|
|
200
|
+
...axis.fixtureProps !== void 0 ? { fixtureProps: axis.fixtureProps } : {}
|
|
201
|
+
} : void 0;
|
|
202
|
+
if (axisFacts !== void 0 && !buildSuccess) traceAxisChecksSkipped(axisFacts, "build failed \u2014 no check ran");
|
|
203
|
+
const axisRun = axis !== void 0 && axisFacts !== void 0 && buildSuccess ? runGatedAxisChecks(axis.checks, { ...axisFacts, compiledCode }) : void 0;
|
|
204
|
+
const axisIssues = axisRun?.issues ?? [];
|
|
205
|
+
const checkIssues = [...tier0Issues, ...axisIssues];
|
|
206
|
+
const tier0Fails = checkIssues.filter((i) => i.result === "fail");
|
|
207
|
+
const selfCheckPassed = tier0Fails.length === 0;
|
|
208
|
+
const violations = checkIssues.filter((i) => i.result === "fail").map((i) => contextPolicy?.labeledTier0 ? `[${i.priority ?? "P0"}-${i.category}] ${i.description}
|
|
209
|
+
Fix: ${i.fix}` : `[${i.category}] ${i.description}
|
|
210
|
+
Fix: ${i.fix}`);
|
|
211
|
+
const axisWarnings = axisIssues.filter((i) => i.result === "warn").map((i) => `[${i.subcategory}] ${i.description} Fix: ${i.fix}`);
|
|
212
|
+
const softWarnings = [...getSoftWarnings(raw), ...axisWarnings];
|
|
213
|
+
const selfCheckMs = Date.now() - selfCheckStart;
|
|
214
|
+
const gitStart = Date.now();
|
|
215
|
+
const oid = await workspace.commit(message);
|
|
216
|
+
commitMeta.set(oid, { build: buildResult, selfCheck: { passed: selfCheckPassed, violations } });
|
|
217
|
+
const gitMs = Date.now() - gitStart;
|
|
218
|
+
const status = buildSuccess && selfCheckPassed ? "PASS" : "FAIL";
|
|
219
|
+
console.log(
|
|
220
|
+
`[coding-agent] auto-commit: ${status} | build=${buildMs}ms self-check=${selfCheckMs}ms git=${gitMs}ms total=${Date.now() - commitStart}ms | violations=${violations.length} | axis=${axisRun === void 0 ? "off" : axisCountsWithIds(axisIssues)}`
|
|
221
|
+
);
|
|
222
|
+
if (violations.length > 0) {
|
|
223
|
+
for (const v of violations) {
|
|
224
|
+
console.log(`[coding-agent] \u2717 ${v}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (buildSuccess && selfCheckPassed) {
|
|
228
|
+
const warnStr = softWarnings.length > 0 ? `
|
|
229
|
+
Warnings (non-blocking): ${softWarnings.join("; ")}` : "";
|
|
230
|
+
return {
|
|
231
|
+
result: `Committed ${oid.slice(0, 7)}: "${message}"
|
|
232
|
+
Build: OK
|
|
233
|
+
Self-check: PASS${warnStr}`,
|
|
234
|
+
done: true,
|
|
235
|
+
compiledCode
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
const errors = [
|
|
239
|
+
...buildErrors.length > 0 ? [`Build errors:
|
|
240
|
+
${buildErrors.join("\n")}`] : [],
|
|
241
|
+
...!selfCheckPassed ? [`Self-check violations:
|
|
242
|
+
${violations.join("\n")}`] : []
|
|
243
|
+
];
|
|
244
|
+
return {
|
|
245
|
+
result: `Committed ${oid.slice(0, 7)}: "${message}"
|
|
246
|
+
${errors.join("\n")}
|
|
247
|
+
Fix the issues.`
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
async function executeTool(workspace, tool, input, commitMeta, contract, applyPatch2, contextPolicy, gadgetTypes, designMode = DEFAULT_DESIGN_MODE, axis) {
|
|
251
|
+
switch (tool) {
|
|
252
|
+
case "write":
|
|
253
|
+
case "rewrite": {
|
|
254
|
+
const code = input.code;
|
|
255
|
+
if (!code && code !== "") {
|
|
256
|
+
console.warn(
|
|
257
|
+
`[coding-agent] write: no "code" field. Keys: [${Object.keys(input).join(", ")}]`
|
|
258
|
+
);
|
|
259
|
+
return {
|
|
260
|
+
result: `FAILED: write requires a "code" field. Received: [${Object.keys(input).join(", ")}]`,
|
|
261
|
+
error: true
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
workspace.write(code);
|
|
265
|
+
await workspace.stage();
|
|
266
|
+
const lineCount = code.split("\n").length;
|
|
267
|
+
const message = input.commit_message || `write ${lineCount} lines`;
|
|
268
|
+
console.log(`[coding-agent] write: ${lineCount} lines \u2192 auto-commit`);
|
|
269
|
+
return autoCommit(workspace, commitMeta, message, contract, contextPolicy, gadgetTypes, designMode, axis);
|
|
270
|
+
}
|
|
271
|
+
case "apply_changes": {
|
|
272
|
+
const currentFile = workspace.read();
|
|
273
|
+
if (!currentFile && currentFile !== "") {
|
|
274
|
+
return { result: "FAILED: No file exists.", error: true };
|
|
275
|
+
}
|
|
276
|
+
const inputChanges = input.changes;
|
|
277
|
+
const rawChanges = inputChanges ? Array.isArray(inputChanges) ? inputChanges : [inputChanges] : [];
|
|
278
|
+
const allowBroken = input.allowBroken === true;
|
|
279
|
+
const { parseHashlineRef, validateHashlineRefs, formatHashlineStaleMessage } = await import('./hashline-5OKREIRM.js');
|
|
280
|
+
const normalizedChanges = [];
|
|
281
|
+
for (let i = 0; i < (rawChanges ?? []).length; i++) {
|
|
282
|
+
const c = rawChanges[i];
|
|
283
|
+
let startLine;
|
|
284
|
+
let expectedStartHash;
|
|
285
|
+
if (typeof c.startLine === "string") {
|
|
286
|
+
const parsed = parseHashlineRef(c.startLine);
|
|
287
|
+
if (!parsed) {
|
|
288
|
+
return {
|
|
289
|
+
result: `FAILED: change[${i}].startLine = ${JSON.stringify(c.startLine)} is not a valid hashline ref (expected "N:hh" format).`,
|
|
290
|
+
error: true
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
startLine = parsed.line;
|
|
294
|
+
expectedStartHash = parsed.expectedHash;
|
|
295
|
+
} else {
|
|
296
|
+
startLine = c.startLine;
|
|
297
|
+
}
|
|
298
|
+
let endLine;
|
|
299
|
+
let expectedEndHash;
|
|
300
|
+
if (typeof c.endLine === "string") {
|
|
301
|
+
const parsed = parseHashlineRef(c.endLine);
|
|
302
|
+
if (!parsed) {
|
|
303
|
+
return {
|
|
304
|
+
result: `FAILED: change[${i}].endLine = ${JSON.stringify(c.endLine)} is not a valid hashline ref (expected "N:hh" format).`,
|
|
305
|
+
error: true
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
endLine = parsed.line;
|
|
309
|
+
expectedEndHash = parsed.expectedHash;
|
|
310
|
+
} else {
|
|
311
|
+
endLine = c.endLine;
|
|
312
|
+
}
|
|
313
|
+
normalizedChanges.push({
|
|
314
|
+
startLine,
|
|
315
|
+
endLine,
|
|
316
|
+
code: Array.isArray(c.code) ? c.code : typeof c.code === "string" ? c.code.split("\n") : [],
|
|
317
|
+
description: c.description ?? `change ${i + 1}`,
|
|
318
|
+
expectedStartHash,
|
|
319
|
+
expectedEndHash
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
const hashlineIssues = validateHashlineRefs(currentFile, normalizedChanges);
|
|
323
|
+
if (hashlineIssues.length > 0) {
|
|
324
|
+
const ranges = normalizedChanges.map((c) => `${c.startLine}-${c.endLine}`).join(", ");
|
|
325
|
+
console.log(
|
|
326
|
+
`[coding-agent] apply_changes: HASHLINE_STALE | ranges=${ranges} | ${hashlineIssues.length} mismatch(es)`
|
|
327
|
+
);
|
|
328
|
+
return {
|
|
329
|
+
result: formatHashlineStaleMessage(hashlineIssues),
|
|
330
|
+
error: true
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
const gutterLine = normalizedChanges.flatMap((c) => c.code).find((l) => /^\s*\d+(?::[0-9a-f]{2})?│/.test(l));
|
|
334
|
+
if (gutterLine !== void 0) {
|
|
335
|
+
console.log(
|
|
336
|
+
`[coding-agent] apply_changes: GUTTER_TRANSCRIBED | ${JSON.stringify(gutterLine.slice(0, 60))}`
|
|
337
|
+
);
|
|
338
|
+
return {
|
|
339
|
+
result: `PATCH_INVALID: your \`code\` contains the line-number gutter from the Current File view (e.g. ${JSON.stringify(gutterLine.trim().slice(0, 40))}). The \`N\u2502\` prefix is display decoration \u2014 \`code\` must be raw file content only. Re-emit the patch without the gutter.`,
|
|
340
|
+
error: true
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
const { defaultApplyPatch } = await import('./patch.js');
|
|
344
|
+
const patcher = applyPatch2 ?? defaultApplyPatch;
|
|
345
|
+
const patchResult = await patcher({
|
|
346
|
+
sourceBefore: currentFile,
|
|
347
|
+
changes: normalizedChanges
|
|
348
|
+
});
|
|
349
|
+
if (!patchResult.ok) {
|
|
350
|
+
console.log(`[coding-agent] apply_changes: REJECTED | ${patchResult.error}`);
|
|
351
|
+
return { result: `PATCH_INVALID: ${patchResult.error}`, error: true };
|
|
352
|
+
}
|
|
353
|
+
const candidate = patchResult.sourceAfter ?? currentFile;
|
|
354
|
+
const changes = [...normalizedChanges].sort((a, b) => a.startLine - b.startLine);
|
|
355
|
+
for (const c of changes) {
|
|
356
|
+
console.log(
|
|
357
|
+
`[coding-agent] change: lines ${c.startLine}-${c.endLine} \u2192 ${c.code.length} lines | ${c.description}`
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
const resultLines = candidate.split("\n");
|
|
361
|
+
let tagImbalanceSummary = null;
|
|
362
|
+
{
|
|
363
|
+
const { checkPatchTagBalance } = await import('./tag-balance-CTHCTRJ7.js');
|
|
364
|
+
const imbalance = checkPatchTagBalance(currentFile, changes);
|
|
365
|
+
if (imbalance.imbalanced) {
|
|
366
|
+
const ranges = changes.map((c) => `${c.startLine}-${c.endLine}`).join(", ");
|
|
367
|
+
const totalsSummary = imbalance.totals.map((d) => `${d.tag}${d.netDelta >= 0 ? "+" : ""}${d.netDelta}`).join(" ");
|
|
368
|
+
console.log(
|
|
369
|
+
`[coding-agent] apply_changes: tag-balance-diag | ranges=${ranges} | ${totalsSummary}`
|
|
370
|
+
);
|
|
371
|
+
tagImbalanceSummary = totalsSummary;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
try {
|
|
375
|
+
await esbuild.transform(candidate, {
|
|
376
|
+
loader: "tsx",
|
|
377
|
+
target: "es2020",
|
|
378
|
+
format: "esm",
|
|
379
|
+
jsx: "automatic",
|
|
380
|
+
jsxImportSource: "react",
|
|
381
|
+
minify: true,
|
|
382
|
+
keepNames: true
|
|
383
|
+
});
|
|
384
|
+
} catch (e) {
|
|
385
|
+
const errMsg = e instanceof Error ? e.message : String(e);
|
|
386
|
+
const esErrs = e.errors ?? [];
|
|
387
|
+
const firstErr = esErrs[0];
|
|
388
|
+
const errLine = firstErr?.location?.line;
|
|
389
|
+
const errCol = firstErr?.location?.column;
|
|
390
|
+
const errText = firstErr?.text ?? errMsg.split("\n")[0];
|
|
391
|
+
const ranges = changes.map((c) => `${c.startLine}-${c.endLine}`).join(", ");
|
|
392
|
+
let sliceStart;
|
|
393
|
+
let sliceEnd;
|
|
394
|
+
let sliceLabel;
|
|
395
|
+
if (typeof errLine === "number" && errLine > 0) {
|
|
396
|
+
sliceStart = Math.max(0, errLine - 6);
|
|
397
|
+
sliceEnd = Math.min(resultLines.length, errLine + 5);
|
|
398
|
+
sliceLabel = `Candidate slice (\xB15 lines around esbuild error at line ${errLine}${typeof errCol === "number" ? `:${errCol}` : ""})`;
|
|
399
|
+
} else {
|
|
400
|
+
const first = changes[0];
|
|
401
|
+
sliceStart = Math.max(0, first.startLine - 6);
|
|
402
|
+
sliceEnd = Math.min(resultLines.length, first.endLine + 5);
|
|
403
|
+
sliceLabel = `Candidate slice (around first changed range)`;
|
|
404
|
+
}
|
|
405
|
+
const slice = resultLines.slice(sliceStart, sliceEnd).map((l, i) => {
|
|
406
|
+
const lineNum = sliceStart + i + 1;
|
|
407
|
+
const marker = lineNum === errLine ? " \u25C4\u2500\u2500 esbuild error here" : "";
|
|
408
|
+
return `${lineNum}\u2502${l}${marker}`;
|
|
409
|
+
}).join("\n");
|
|
410
|
+
workspace.write(candidate);
|
|
411
|
+
console.log(
|
|
412
|
+
`[coding-agent] apply_changes: APPLIED-BROKEN (preflight failed)${allowBroken ? " [allowBroken]" : ""} | ranges=${ranges} | line=${errLine ?? "?"} | ${errText}`
|
|
413
|
+
);
|
|
414
|
+
const preflightPrefix = contextPolicy?.labeledPreflight ? `[P0-compile] ` : ``;
|
|
415
|
+
const imbalanceHint = tagImbalanceSummary ? `
|
|
416
|
+
|
|
417
|
+
Patch tag-balance imbalance: ${tagImbalanceSummary}
|
|
418
|
+
(Net opens vs closes inside your edit ranges. If a tag has +N, you opened N more than you closed within the patch \u2014 verify each <Tag> has a matching </Tag> in your changes, or that the surrounding scaffold provides the closer.)` : "";
|
|
419
|
+
const contextAddendum = buildRangeContextAddendum(
|
|
420
|
+
errLine,
|
|
421
|
+
changes,
|
|
422
|
+
currentFile.split("\n")
|
|
423
|
+
);
|
|
424
|
+
if (contextAddendum.length > 0) {
|
|
425
|
+
console.log(
|
|
426
|
+
`[coding-agent] apply_changes: RANGE_CONTEXT_DIAG | line=${errLine} | ${JSON.stringify(contextAddendum.slice(0, 80))}`
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
result: `${preflightPrefix}PATCH_APPLIED_BROKEN: patch applied but file has a syntax error. Workspace updated; no git commit yet.
|
|
431
|
+
Changed ranges: ${ranges}
|
|
432
|
+
esbuild error: ${errText}${typeof errLine === "number" ? ` (line ${errLine}${typeof errCol === "number" ? `, col ${errCol}` : ""})` : ""}
|
|
433
|
+
|
|
434
|
+
${sliceLabel}:
|
|
435
|
+
${slice}
|
|
436
|
+
|
|
437
|
+
The file now reflects your latest patch. Submit a follow-up apply_changes targeting the error location above to converge toward a compilable file.${contextAddendum}${imbalanceHint}`,
|
|
438
|
+
error: false
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
workspace.write(candidate);
|
|
442
|
+
await workspace.stage();
|
|
443
|
+
const message = input.commit_message || "apply changes";
|
|
444
|
+
console.log(`[coding-agent] apply_changes: ${changes.length} changes applied \u2192 auto-commit`);
|
|
445
|
+
return autoCommit(workspace, commitMeta, message, contract, contextPolicy, gadgetTypes, designMode, axis);
|
|
446
|
+
}
|
|
447
|
+
case "get_components_info": {
|
|
448
|
+
const names = input.names ?? [];
|
|
449
|
+
const docs = names.map((name) => getComponentDocumentation(name));
|
|
450
|
+
return { result: docs.join("\n\n---\n\n") };
|
|
451
|
+
}
|
|
452
|
+
case "write_plan": {
|
|
453
|
+
const components = input.components ?? [];
|
|
454
|
+
const structure = input.structure ?? "";
|
|
455
|
+
const wiring = input.wiring ?? "";
|
|
456
|
+
const summary = `PLAN_COMMITTED
|
|
457
|
+
|
|
458
|
+
Components: ${components.join(", ")}
|
|
459
|
+
|
|
460
|
+
Structure: ${structure}
|
|
461
|
+
|
|
462
|
+
Wiring: ${wiring}
|
|
463
|
+
|
|
464
|
+
On the next turn, use apply_changes to write the code. Fetch more component docs only if needed.`;
|
|
465
|
+
return { result: summary };
|
|
466
|
+
}
|
|
467
|
+
// `cat` is NOT advertised on the bench's coding-turn tool list —
|
|
468
|
+
// `run-coding-turn.ts::selectTurnTools` omits it because every turn's
|
|
469
|
+
// prompt already injects the current file as a `## Current File`
|
|
470
|
+
// block, so no read-tool is needed there. The case stays for
|
|
471
|
+
// dev-agent workflows that don't auto-inject file content.
|
|
472
|
+
case "cat":
|
|
473
|
+
return {
|
|
474
|
+
result: workspace.cat(
|
|
475
|
+
input.start_line,
|
|
476
|
+
input.end_line
|
|
477
|
+
)
|
|
478
|
+
};
|
|
479
|
+
case "grep":
|
|
480
|
+
return {
|
|
481
|
+
result: workspace.grep(
|
|
482
|
+
input.pattern,
|
|
483
|
+
input.context
|
|
484
|
+
)
|
|
485
|
+
};
|
|
486
|
+
case "diff":
|
|
487
|
+
return { result: await workspace.diffWorking() };
|
|
488
|
+
case "log": {
|
|
489
|
+
const commits = await workspace.log(input.depth);
|
|
490
|
+
if (commits.length === 0) return { result: "(no commits)" };
|
|
491
|
+
const lines = commits.map((c) => {
|
|
492
|
+
const meta = commitMeta.get(c.oid);
|
|
493
|
+
const status = meta?.selfCheck.passed ? "PASS" : meta?.selfCheck ? "FAIL" : "\u2014";
|
|
494
|
+
return `${c.oid.slice(0, 7)} [${status}] ${c.commit.message.trim()}`;
|
|
495
|
+
});
|
|
496
|
+
return { result: lines.join("\n") };
|
|
497
|
+
}
|
|
498
|
+
case "show": {
|
|
499
|
+
const oidPrefix = input.oid;
|
|
500
|
+
const commits = await workspace.log();
|
|
501
|
+
const idx = commits.findIndex((c) => c.oid.startsWith(oidPrefix));
|
|
502
|
+
if (idx === -1) {
|
|
503
|
+
return { result: `Commit not found: ${oidPrefix}`, error: true };
|
|
504
|
+
}
|
|
505
|
+
const thisOid = commits[idx].oid;
|
|
506
|
+
const parentOid = idx + 1 < commits.length ? commits[idx + 1].oid : null;
|
|
507
|
+
if (!parentOid) {
|
|
508
|
+
const content = await workspace.readFileAtCommit(thisOid);
|
|
509
|
+
return { result: `(initial commit)
|
|
510
|
+
${content}` };
|
|
511
|
+
}
|
|
512
|
+
return { result: await workspace.diffBetween(parentOid, thisOid) };
|
|
513
|
+
}
|
|
514
|
+
case "revert": {
|
|
515
|
+
const oidPrefix = input.oid;
|
|
516
|
+
const commits = await workspace.log();
|
|
517
|
+
const match = commits.find((c) => c.oid.startsWith(oidPrefix));
|
|
518
|
+
if (!match) {
|
|
519
|
+
const available = commits.map((c) => c.oid.slice(0, 7)).join(", ");
|
|
520
|
+
return {
|
|
521
|
+
result: `OID not found: ${oidPrefix}. Available: ${available || "(none)"}`,
|
|
522
|
+
error: true
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
await workspace.checkout(match.oid);
|
|
526
|
+
return { result: `Reverted working copy to ${match.oid.slice(0, 7)}` };
|
|
527
|
+
}
|
|
528
|
+
default:
|
|
529
|
+
return { result: `Unknown tool: ${tool}`, error: true };
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function buildRangeContextAddendum(errLine, changes, originalLines) {
|
|
533
|
+
if (typeof errLine !== "number") return "";
|
|
534
|
+
const hit = changes.find((c) => c.startLine === errLine);
|
|
535
|
+
if (!hit) return "";
|
|
536
|
+
const firstPayloadLine = hit.code.find((l) => l.trim().length > 0) ?? "";
|
|
537
|
+
const declMatch = /^\s*(const|let|var|function|type|interface)\b/.exec(
|
|
538
|
+
firstPayloadLine
|
|
539
|
+
);
|
|
540
|
+
if (declMatch) {
|
|
541
|
+
for (let i = hit.startLine - 2; i >= 0; i--) {
|
|
542
|
+
const line = originalLines[i];
|
|
543
|
+
if (line === void 0 || line.trim().length === 0) continue;
|
|
544
|
+
if (/\breturn\s*\($/.test(line.trim())) {
|
|
545
|
+
return `
|
|
546
|
+
|
|
547
|
+
Range/context mismatch: line ${i + 1} is \`return (\` \u2014 your range starts at ${hit.startLine}, INSIDE the returned JSX expression, but your payload begins with a declaration (\`${declMatch[1]}\`). Declarations cannot live inside JSX: start the range at or before line ${i + 1} and re-emit \`return (\` after them.`;
|
|
548
|
+
}
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (hit.code.some((l) => l.includes("export default function"))) {
|
|
553
|
+
const existing = originalLines.findIndex(
|
|
554
|
+
(l) => l.includes("export default function")
|
|
555
|
+
);
|
|
556
|
+
if (existing >= 0 && existing + 1 < hit.startLine) {
|
|
557
|
+
return `
|
|
558
|
+
|
|
559
|
+
Range/context mismatch: your payload emits a second \`export default function\` but the range starts at line ${hit.startLine}, INSIDE the existing component that opens at line ${existing + 1}. Patch within the existing function body \u2014 do not re-emit the component definition.`;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return "";
|
|
563
|
+
}
|
|
564
|
+
function extractFailSignature(resultText) {
|
|
565
|
+
const esbuildMatch = resultText.match(/esbuild error: ([^\n]+)/);
|
|
566
|
+
if (esbuildMatch) return esbuildMatch[1];
|
|
567
|
+
const violationMatch = resultText.match(/^\[[A-Z][^\]]*\] [^\n]+/m);
|
|
568
|
+
if (violationMatch) return violationMatch[0];
|
|
569
|
+
const buildErrMatch = resultText.match(/Build errors:\s*\n([^\n]+)/);
|
|
570
|
+
if (buildErrMatch) return buildErrMatch[1];
|
|
571
|
+
return "";
|
|
572
|
+
}
|
|
573
|
+
function selectTurnTools(turnsUsed, forceEscape = false, consecutiveBrokenApplies = 0, hashlineMode = "off", primitiveIndexMode = "off", primitiveIndexForceFetch = false, primitiveIndexPlanTurn = false, planFirstTurn = false, codeFormat = "array") {
|
|
574
|
+
if (forceEscape) return [APPLY_CHANGES_TOOL_SCOPED];
|
|
575
|
+
if (consecutiveBrokenApplies >= 3) return [REWRITE_TOOL];
|
|
576
|
+
if (planFirstTurn && turnsUsed === 1) {
|
|
577
|
+
return [WRITE_PLAN_TOOL];
|
|
578
|
+
}
|
|
579
|
+
if (primitiveIndexForceFetch && primitiveIndexMode !== "off" && turnsUsed === 1) {
|
|
580
|
+
return [GET_COMPONENTS_INFO_TOOL];
|
|
581
|
+
}
|
|
582
|
+
if (primitiveIndexPlanTurn && primitiveIndexMode !== "off" && turnsUsed === 2) {
|
|
583
|
+
return [WRITE_PLAN_TOOL];
|
|
584
|
+
}
|
|
585
|
+
const applyTool = hashlineMode === "v2" ? codeFormat === "flat" ? APPLY_CHANGES_HASHLINE_TOOL_FLAT : APPLY_CHANGES_HASHLINE_TOOL : codeFormat === "flat" ? APPLY_CHANGES_TOOL_FLAT : APPLY_CHANGES_TOOL;
|
|
586
|
+
const tools = [applyTool, GET_ICONS_TOOL];
|
|
587
|
+
if (primitiveIndexMode !== "off") tools.push(GET_COMPONENTS_INFO_TOOL);
|
|
588
|
+
return tools;
|
|
589
|
+
}
|
|
590
|
+
async function runCodingTurn(ctx, input) {
|
|
591
|
+
const {
|
|
592
|
+
workspace,
|
|
593
|
+
codingAgent,
|
|
594
|
+
codingModel,
|
|
595
|
+
systemPrompt,
|
|
596
|
+
harness,
|
|
597
|
+
contract,
|
|
598
|
+
commitMeta,
|
|
599
|
+
originalProps,
|
|
600
|
+
costTracker
|
|
601
|
+
} = ctx;
|
|
602
|
+
const {
|
|
603
|
+
turnsUsed,
|
|
604
|
+
a1Phase,
|
|
605
|
+
lastResultText,
|
|
606
|
+
lastDiffFailed,
|
|
607
|
+
isEvalFeedback
|
|
608
|
+
} = input;
|
|
609
|
+
let { iconNamesCache, preWarmedContext } = input;
|
|
610
|
+
if (originalProps) {
|
|
611
|
+
const current = workspace.read() ?? "";
|
|
612
|
+
const currentProps = current.match(/interface Props \{[\s\S]*?\n\}/)?.[0];
|
|
613
|
+
if (currentProps && currentProps !== originalProps) {
|
|
614
|
+
workspace.write(current.replace(currentProps, originalProps));
|
|
615
|
+
await workspace.stage();
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
const hashlineMode = ctx.contextPolicy?.hashline ?? "off";
|
|
619
|
+
const currentFile = hashlineMode === "v2" ? formatWithHashlines(workspace.read() ?? "") : workspace.cat();
|
|
620
|
+
let userPrompt;
|
|
621
|
+
if (a1Phase === "scaffold") {
|
|
622
|
+
userPrompt = `Produce a COMPILEABLE structural first pass. This is turn 1 of 2 \u2014 you are NOT implementing the feature yet.
|
|
623
|
+
|
|
624
|
+
## Required this turn (non-negotiable)
|
|
625
|
+
|
|
626
|
+
1. Declare all hooks from the boilerplate: \`useState\` for local state you'll need, plus every \`useAction\` / \`useStream\` / \`useGguiContext\` from the contract, plus any capability hooks declared in \`clientCapabilities\` (from \`@ggui-ai/gadgets\`)
|
|
627
|
+
2. Build a minimal valid JSX skeleton \u2014 one top-level \`<Container>\` or \`<Card>\` with a \`<Stack>\` of empty-but-valid region containers (one per logical section of the UI)
|
|
628
|
+
3. Keep the \`export default function\` signature + \`Props\` interface exactly as in the boilerplate
|
|
629
|
+
|
|
630
|
+
## NOT required yet (next turn will fill these in)
|
|
631
|
+
|
|
632
|
+
- Rendering prop values or real content
|
|
633
|
+
- Wiring hook callbacks to UI elements (empty \`onClick={() => {}}\` is fine)
|
|
634
|
+
- Styling polish, icons, badges, variants
|
|
635
|
+
- Interactive flows and derived state
|
|
636
|
+
|
|
637
|
+
**Goal: compile + pass syntactic checks. Broken JSX wastes the whole round-trip \u2014 keep it minimal and valid.**
|
|
638
|
+
|
|
639
|
+
## Current File
|
|
640
|
+
\`\`\`tsx
|
|
641
|
+
${currentFile}
|
|
642
|
+
\`\`\``;
|
|
643
|
+
} else if (a1Phase === "fill") {
|
|
644
|
+
userPrompt = `The scaffold compiled. Now FILL in the details against the compiled skeleton. Make minimal, targeted patches \u2014 don't rewrite the whole file.
|
|
645
|
+
|
|
646
|
+
## Required this turn (non-negotiable)
|
|
647
|
+
|
|
648
|
+
1. Render every required prop from the Props interface in the appropriate region
|
|
649
|
+
2. Wire every hook to a real interactive element with the correct payload shape
|
|
650
|
+
3. Add real content \u2014 text, labels, icons, variants \u2014 so the UI matches the user's request
|
|
651
|
+
|
|
652
|
+
## Allowed to simplify (ONLY flourishes/polish)
|
|
653
|
+
|
|
654
|
+
- Elaborate animations \u2192 plain CSS transitions or static styles
|
|
655
|
+
- Rare empty states \u2192 skip the branch
|
|
656
|
+
- Fancy layout refinements \u2192 simpler grid/stack
|
|
657
|
+
|
|
658
|
+
**Preserve the scaffolded region containers wherever possible.** Patch surgically inside them instead of rewriting the tree. Broken JSX or type errors waste a whole round-trip.
|
|
659
|
+
|
|
660
|
+
## Current File (scaffolded, compiles)
|
|
661
|
+
\`\`\`tsx
|
|
662
|
+
${currentFile}
|
|
663
|
+
\`\`\``;
|
|
664
|
+
} else if (turnsUsed === 1) {
|
|
665
|
+
userPrompt = `Deliver a compileable, end-to-end first pass in one turn.
|
|
666
|
+
|
|
667
|
+
## Required this turn (non-negotiable)
|
|
668
|
+
|
|
669
|
+
1. JSX structure that parses \u2014 balanced tags, closed fragments, no stray braces
|
|
670
|
+
2. Render every required prop from the Props interface
|
|
671
|
+
3. Wire every \`useAction\` / \`useStream\` / \`useGguiContext\` hook from the boilerplate to a visible element. If the contract declared \`clientCapabilities\`, the boilerplate emits a capability-hook call for each (e.g., \`const loc = useGeolocation();\`); call \`.start()\` from a UI control and render the result.
|
|
672
|
+
|
|
673
|
+
## Allowed to simplify (ONLY flourishes/polish \u2014 NOT core work above)
|
|
674
|
+
|
|
675
|
+
- Elaborate animations \u2192 plain CSS transitions or static styles
|
|
676
|
+
- Rare empty states \u2192 skip the branch entirely
|
|
677
|
+
- Fancy layout refinements \u2192 simpler grid/stack
|
|
678
|
+
|
|
679
|
+
**If a flourish risks broken JSX or type errors, OMIT it rather than leaving a placeholder or partial structure.** No TODOs, no half-finished helpers, no stub components. Broken syntax or missing core work wastes a whole round-trip.
|
|
680
|
+
|
|
681
|
+
Read the boilerplate comments \u2014 each hook has its type and call signature inline. Follow P0 \u2192 P1 \u2192 P2 priority from the system prompt.
|
|
682
|
+
|
|
683
|
+
## Current File
|
|
684
|
+
\`\`\`tsx
|
|
685
|
+
${currentFile}
|
|
686
|
+
\`\`\``;
|
|
687
|
+
} else {
|
|
688
|
+
const issues = lastResultText;
|
|
689
|
+
const diffHint = lastDiffFailed ? `
|
|
690
|
+
**Your last diff did not apply.** Make sure context lines match the current file exactly. Use 3 lines of context around each change.
|
|
691
|
+
` : "";
|
|
692
|
+
if (!preWarmedContext && input.preWarmPromise) {
|
|
693
|
+
preWarmedContext = await Promise.race([
|
|
694
|
+
input.preWarmPromise,
|
|
695
|
+
new Promise((r) => setTimeout(r, 0, null))
|
|
696
|
+
]);
|
|
697
|
+
}
|
|
698
|
+
const issueHeader = isEvalFeedback ? "## Evaluation Issues (fix all)" : "## Issues (fix all)";
|
|
699
|
+
if (ctx.dupeBreak?.pendingDiagnosticTurn) {
|
|
700
|
+
ctx.dupeBreak.pendingDiagnosticTurn = false;
|
|
701
|
+
ctx.dupeBreak.awaitingDiagnosticOutcome = true;
|
|
702
|
+
const recents = ctx.dupeBreak.recentFailedPatches;
|
|
703
|
+
const head = recents[0];
|
|
704
|
+
const lines = currentFile.split("\n");
|
|
705
|
+
const firstRange = head?.rangesShort.split(",")[0]?.trim() ?? "";
|
|
706
|
+
const m = /^(\d+)-(\d+)$/.exec(firstRange);
|
|
707
|
+
const focusStart = m ? Math.max(1, parseInt(m[1], 10) - 15) : 1;
|
|
708
|
+
const focusEnd = m ? Math.min(lines.length, parseInt(m[2], 10) + 15) : lines.length;
|
|
709
|
+
const numbered = lines.slice(focusStart - 1, focusEnd).map((line, i) => `${String(focusStart + i).padStart(4, "0")}: ${line}`).join("\n");
|
|
710
|
+
const priorBlock = recents.map(
|
|
711
|
+
(p, i) => ` Attempt ${recents.length - i}: ranges=[${p.rangesShort}] error="${p.errClass}" fp=${p.fingerprint.slice(0, 12)}`
|
|
712
|
+
).join("\n");
|
|
713
|
+
const promptPolicy = ctx.contextPolicy ?? harness.policy.context;
|
|
714
|
+
const noForce = promptPolicy.dupeBreakAction === "diagnostic-noforce";
|
|
715
|
+
const step2 = noForce ? `2. Then call \`apply_changes\`. The information above (line numbers + prior fingerprints) should let you see exactly what's wrong. If your patch turns out to be the same one you tried before, that's fine \u2014 but make sure you've verified line-by-line against the focused excerpt that the patch is actually correct.` : `2. Then call \`apply_changes\` with a patch that **differs materially** from your prior failed attempt \u2014 different ranges OR meaningfully different code at the same range. Re-emitting the same patch is not allowed.`;
|
|
716
|
+
userPrompt = `[LOOP DETECTED] Your last patch was byte-identical to a prior failed patch and produced the same preflight error. You are stuck in a fixed point \u2014 apply_changes alone won't escape this loop unless you re-examine the file.
|
|
717
|
+
|
|
718
|
+
## Prior failed attempts (most recent first)
|
|
719
|
+
${priorBlock}
|
|
720
|
+
|
|
721
|
+
## Failing region \u2014 focused excerpt (lines ${focusStart}\u2013${focusEnd}, line-numbered)
|
|
722
|
+
\`\`\`tsx
|
|
723
|
+
${numbered}
|
|
724
|
+
\`\`\`
|
|
725
|
+
|
|
726
|
+
## What to emit on this turn
|
|
727
|
+
|
|
728
|
+
1. Set \`commit_message\` to a one-line structured diagnosis in this exact format:
|
|
729
|
+
\`DIAG: why=<why the prior patch failed, \u226480 chars> | next=<what you will change instead, \u226480 chars> | lines=<startLine-endLine you will target>\`
|
|
730
|
+
${step2}
|
|
731
|
+
|
|
732
|
+
## Issues from prior turn (full text \u2014 for context)
|
|
733
|
+
${issues}
|
|
734
|
+
`;
|
|
735
|
+
} else {
|
|
736
|
+
const batchFixForced = typeof process !== "undefined" && process.env?.GGUI_BATCH_FIX === "on";
|
|
737
|
+
const closingInstruction = batchFixForced ? `**Fix EVERY error listed above in THIS single \`apply_changes\` call.** Emit multiple \`changes[]\` entries \u2014 one per range.` : `Produce ONE \`apply_changes\` call targeting only the lines that need fixing.`;
|
|
738
|
+
userPrompt = `Fix the issues below. Make **minimal, targeted changes** \u2014 do NOT rewrite the whole file.${diffHint}
|
|
739
|
+
|
|
740
|
+
${issueHeader}
|
|
741
|
+
${issues}
|
|
742
|
+
|
|
743
|
+
## Current File
|
|
744
|
+
\`\`\`tsx
|
|
745
|
+
${currentFile}
|
|
746
|
+
\`\`\`
|
|
747
|
+
|
|
748
|
+
${closingInstruction}`;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
const forceEscape = ctx.dupeBreak?.forceEscapeNextTurn === true;
|
|
752
|
+
if (ctx.dupeBreak && forceEscape) ctx.dupeBreak.forceEscapeNextTurn = false;
|
|
753
|
+
if (ctx.dupeBreak && ctx.dupeBreak.cooldown > 0) ctx.dupeBreak.cooldown--;
|
|
754
|
+
const consecutiveBrokenApplies = ctx.dupeBreak?.consecutiveBrokenApplies ?? 0;
|
|
755
|
+
const primitiveIndexMode = ctx.contextPolicy?.primitiveIndex ?? "off";
|
|
756
|
+
const primitiveIndexForceFetch = ctx.contextPolicy?.primitiveIndexForceFetch ?? false;
|
|
757
|
+
const primitiveIndexPlanTurn = ctx.contextPolicy?.primitiveIndexPlanTurn ?? false;
|
|
758
|
+
const planFirstTurn = ctx.contextPolicy?.planFirstTurn ?? false;
|
|
759
|
+
const codeFormat = ctx.contextPolicy?.codeFormat ?? "array";
|
|
760
|
+
const tools = selectTurnTools(
|
|
761
|
+
turnsUsed,
|
|
762
|
+
forceEscape,
|
|
763
|
+
consecutiveBrokenApplies,
|
|
764
|
+
hashlineMode,
|
|
765
|
+
primitiveIndexMode,
|
|
766
|
+
primitiveIndexForceFetch,
|
|
767
|
+
primitiveIndexPlanTurn,
|
|
768
|
+
planFirstTurn,
|
|
769
|
+
codeFormat
|
|
770
|
+
);
|
|
771
|
+
ctx.onPrompt?.({ turn: turnsUsed, systemPrompt, userPrompt });
|
|
772
|
+
const llmStart = Date.now();
|
|
773
|
+
const response = await codingAgent.callTools(
|
|
774
|
+
codingModel,
|
|
775
|
+
systemPrompt,
|
|
776
|
+
userPrompt,
|
|
777
|
+
tools,
|
|
778
|
+
"required",
|
|
779
|
+
[APPLY_CHANGES_TOOL_SCOPED]
|
|
780
|
+
);
|
|
781
|
+
const llmMs = Date.now() - llmStart;
|
|
782
|
+
if (costTracker) {
|
|
783
|
+
costTracker.record(codingModel, response.inputTokens, response.outputTokens, {
|
|
784
|
+
...response.cacheReadTokens !== void 0 ? { read: response.cacheReadTokens } : {},
|
|
785
|
+
...response.cacheCreationTokens !== void 0 ? { write: response.cacheCreationTokens } : {}
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
const tokens = { input: response.inputTokens, output: response.outputTokens };
|
|
789
|
+
const cacheTokens = {
|
|
790
|
+
...response.cacheReadTokens !== void 0 ? { cacheReadTokens: response.cacheReadTokens } : {},
|
|
791
|
+
...response.cacheCreationTokens !== void 0 ? { cacheCreationTokens: response.cacheCreationTokens } : {}
|
|
792
|
+
};
|
|
793
|
+
const call = response.toolCalls[0];
|
|
794
|
+
if (!call) {
|
|
795
|
+
if (ctx.dupeBreak) {
|
|
796
|
+
ctx.dupeBreak.consecutiveNoToolCalls += 1;
|
|
797
|
+
}
|
|
798
|
+
const noToolStreak = ctx.dupeBreak?.consecutiveNoToolCalls ?? 1;
|
|
799
|
+
const shouldBreak = noToolStreak >= 2;
|
|
800
|
+
console.warn(
|
|
801
|
+
`[simple] turn ${turnsUsed}: no tool call` + (shouldBreak ? ` \u2014 breaking (streak=${noToolStreak})` : ` \u2014 retrying (streak=${noToolStreak})`)
|
|
802
|
+
);
|
|
803
|
+
const retryDirective = shouldBreak ? lastResultText : `${lastResultText ?? ""}
|
|
804
|
+
|
|
805
|
+
[harness] Your previous turn returned no tool call. You MUST issue an apply_changes (or scoped) tool call. Re-read the current file state and emit a corrected patch.`.trim();
|
|
806
|
+
return {
|
|
807
|
+
control: shouldBreak ? "break" : "continue",
|
|
808
|
+
compiledCode: "",
|
|
809
|
+
selfCheckPassed: false,
|
|
810
|
+
outcome: void 0,
|
|
811
|
+
phase: void 0,
|
|
812
|
+
tokens,
|
|
813
|
+
...cacheTokens,
|
|
814
|
+
llmMs,
|
|
815
|
+
toolMs: 0,
|
|
816
|
+
lastResultText: retryDirective,
|
|
817
|
+
lastDiffFailed,
|
|
818
|
+
isEvalFeedback,
|
|
819
|
+
iconNamesCache,
|
|
820
|
+
preWarmedContext
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
if (ctx.dupeBreak) {
|
|
824
|
+
ctx.dupeBreak.consecutiveNoToolCalls = 0;
|
|
825
|
+
}
|
|
826
|
+
if (call.name === "get_available_icons") {
|
|
827
|
+
if (!iconNamesCache) {
|
|
828
|
+
iconNamesCache = LUCIDE_ICON_NAMES.join(", ");
|
|
829
|
+
}
|
|
830
|
+
console.log(`[simple] turn ${turnsUsed}: get_available_icons`);
|
|
831
|
+
let iconResult = `Available Lucide icon names (use with <Icon name="...">):
|
|
832
|
+
${iconNamesCache}
|
|
833
|
+
|
|
834
|
+
${ICON_LIST_NO_MATCH_GUIDANCE}`;
|
|
835
|
+
let iconRepeats = 0;
|
|
836
|
+
if (ctx.dupeBreak) {
|
|
837
|
+
iconRepeats = recordToolExchange(ctx.dupeBreak, call.name, call.input, iconResult);
|
|
838
|
+
if (iconRepeats === 2) iconResult += SAME_EXCHANGE_NUDGE;
|
|
839
|
+
}
|
|
840
|
+
await codingAgent.sendToolResult([{ callId: call.id, name: call.name, result: iconResult }]);
|
|
841
|
+
if (iconRepeats >= SAME_EXCHANGE_BREAK_AT) {
|
|
842
|
+
console.log(
|
|
843
|
+
`[coding-agent] SAME_EXCHANGE_BREAK | tool=${call.name} | repeats=${iconRepeats} \u2014 terminating run (ggui#404)`
|
|
844
|
+
);
|
|
845
|
+
return {
|
|
846
|
+
control: "break",
|
|
847
|
+
compiledCode: "",
|
|
848
|
+
selfCheckPassed: false,
|
|
849
|
+
outcome: void 0,
|
|
850
|
+
phase: void 0,
|
|
851
|
+
tokens,
|
|
852
|
+
...cacheTokens,
|
|
853
|
+
llmMs,
|
|
854
|
+
toolMs: 0,
|
|
855
|
+
lastResultText: iconResult,
|
|
856
|
+
lastDiffFailed: false,
|
|
857
|
+
isEvalFeedback: false,
|
|
858
|
+
iconNamesCache,
|
|
859
|
+
preWarmedContext,
|
|
860
|
+
sameExchangeBreak: { tool: call.name, repeats: iconRepeats }
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
return {
|
|
864
|
+
control: "continue",
|
|
865
|
+
compiledCode: "",
|
|
866
|
+
selfCheckPassed: false,
|
|
867
|
+
outcome: void 0,
|
|
868
|
+
phase: void 0,
|
|
869
|
+
tokens,
|
|
870
|
+
...cacheTokens,
|
|
871
|
+
llmMs,
|
|
872
|
+
toolMs: 0,
|
|
873
|
+
lastResultText: iconResult,
|
|
874
|
+
lastDiffFailed: false,
|
|
875
|
+
isEvalFeedback: false,
|
|
876
|
+
iconNamesCache,
|
|
877
|
+
preWarmedContext
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
const toolStart = Date.now();
|
|
881
|
+
const result = await executeTool(
|
|
882
|
+
workspace,
|
|
883
|
+
call.name,
|
|
884
|
+
call.input,
|
|
885
|
+
commitMeta,
|
|
886
|
+
contract,
|
|
887
|
+
harness.what.applyPatch,
|
|
888
|
+
// Prefer the caller-threaded runtime-resolved policy
|
|
889
|
+
// (session.resolvedPolicy.context) so experimental profiles take
|
|
890
|
+
// effect. Falls back to the harness's static policy.
|
|
891
|
+
ctx.contextPolicy ?? harness.policy.context,
|
|
892
|
+
// Forward the third-party wrapper `.d.ts` map so autoCommit's
|
|
893
|
+
// typecheck overlays the real wrapper declarations and a generated
|
|
894
|
+
// direct gadget import gets strict option/return narrowing.
|
|
895
|
+
harness.what.gadgetTypes,
|
|
896
|
+
// Which tier-0 legs fire — the design-vocabulary legs stand down in
|
|
897
|
+
// `free` mode; every contract leg runs in both.
|
|
898
|
+
harness.designMode,
|
|
899
|
+
// ggui#1122: the harness's pre-filtered axis checks run at auto-commit — the serve path.
|
|
900
|
+
{
|
|
901
|
+
checks: harness.check.axisChecks,
|
|
902
|
+
classification: harness.classification,
|
|
903
|
+
originalPrompt: ctx.originalPrompt,
|
|
904
|
+
...harness.canvas !== void 0 ? { canvas: harness.canvas } : {},
|
|
905
|
+
...ctx.fixtureProps !== void 0 ? { fixtureProps: ctx.fixtureProps } : {}
|
|
906
|
+
}
|
|
907
|
+
);
|
|
908
|
+
const toolMs = Date.now() - toolStart;
|
|
909
|
+
if (forceEscape && ctx.dupeBreak && call.name === "apply_changes") {
|
|
910
|
+
const changes = call.input?.changes ?? [];
|
|
911
|
+
const scopedShape = changes.length === 1 && changes[0] !== void 0 && changes[0].endLine - changes[0].startLine <= 20;
|
|
912
|
+
if (scopedShape) {
|
|
913
|
+
ctx.dupeBreak.scopedEscapeUsedCount++;
|
|
914
|
+
console.log(
|
|
915
|
+
`[coding-agent] SCOPED_ESCAPE_USED | range=${changes[0]?.startLine}-${changes[0]?.endLine} | count=${ctx.dupeBreak.scopedEscapeUsedCount}`
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
const effectivePolicy = ctx.contextPolicy ?? harness.policy.context;
|
|
920
|
+
const isPatchInvalid = !!result.error && result.result.startsWith("PATCH_INVALID");
|
|
921
|
+
const isApplyChanges = call.name === "apply_changes";
|
|
922
|
+
const isFailedPatch = !result.done && isApplyChanges;
|
|
923
|
+
if (ctx.dupeBreak?.awaitingDiagnosticOutcome) {
|
|
924
|
+
ctx.dupeBreak.awaitingDiagnosticOutcome = false;
|
|
925
|
+
const commitMsg = typeof call.input?.commit_message === "string" ? call.input.commit_message : "";
|
|
926
|
+
const returnedDiag = /^\s*DIAG\s*:/i.test(commitMsg);
|
|
927
|
+
if (returnedDiag) {
|
|
928
|
+
ctx.dupeBreak.diagnosticReturnedCount++;
|
|
929
|
+
console.log(
|
|
930
|
+
`[coding-agent] DUPE_DIAGNOSTIC_RETURNED | msg=${JSON.stringify(commitMsg.slice(0, 120))}`
|
|
931
|
+
);
|
|
932
|
+
} else {
|
|
933
|
+
console.log(
|
|
934
|
+
`[coding-agent] DUPE_DIAGNOSTIC_RETURNED=false | msg=${JSON.stringify(commitMsg.slice(0, 120))}`
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
const dupeFp = ctx.dupeBreak.lastFailedPatchFingerprint;
|
|
938
|
+
let brokeLoop = false;
|
|
939
|
+
if (result.done) {
|
|
940
|
+
brokeLoop = true;
|
|
941
|
+
} else if (isApplyChanges && dupeFp) {
|
|
942
|
+
const { computePatchFingerprint } = await import('./dupe-break-X6K7QFIS.js');
|
|
943
|
+
const changes2 = call.input?.changes ?? [];
|
|
944
|
+
const errText2 = extractFailSignature(result.result);
|
|
945
|
+
const currentFp = computePatchFingerprint(changes2, errText2);
|
|
946
|
+
brokeLoop = currentFp !== dupeFp;
|
|
947
|
+
} else {
|
|
948
|
+
brokeLoop = true;
|
|
949
|
+
}
|
|
950
|
+
if (brokeLoop) {
|
|
951
|
+
ctx.dupeBreak.diagnosticBrokeLoopCount++;
|
|
952
|
+
console.log(`[coding-agent] DUPE_DIAGNOSTIC_BROKE_LOOP=true`);
|
|
953
|
+
} else {
|
|
954
|
+
console.log(`[coding-agent] DUPE_DIAGNOSTIC_BROKE_LOOP=false`);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
let augmentedResultText = result.result;
|
|
958
|
+
if (isFailedPatch && effectivePolicy?.breakDuplicatePatch && ctx.dupeBreak) {
|
|
959
|
+
const { computePatchFingerprint } = await import('./dupe-break-X6K7QFIS.js');
|
|
960
|
+
const changes = call.input?.changes ?? [];
|
|
961
|
+
const errText = extractFailSignature(result.result);
|
|
962
|
+
const fingerprint = computePatchFingerprint(changes, errText);
|
|
963
|
+
const priorPrint = ctx.dupeBreak.lastFailedPatchFingerprint;
|
|
964
|
+
const inCooldown = ctx.dupeBreak.cooldown > 0;
|
|
965
|
+
if (priorPrint && priorPrint === fingerprint && !inCooldown) {
|
|
966
|
+
ctx.dupeBreak.cooldown = 3;
|
|
967
|
+
ctx.dupeBreak.firedCount++;
|
|
968
|
+
const action = effectivePolicy.dupeBreakAction ?? "escape";
|
|
969
|
+
console.log(
|
|
970
|
+
`[coding-agent] DUPLICATE_PATCH_BREAK fired | action=${action} | fingerprint=${fingerprint.slice(0, 12)} error=${JSON.stringify(errText.slice(0, 48))}`
|
|
971
|
+
);
|
|
972
|
+
const codeHead = (() => {
|
|
973
|
+
const sorted = [...changes].sort(
|
|
974
|
+
(a, b) => a.startLine - b.startLine || a.endLine - b.endLine
|
|
975
|
+
);
|
|
976
|
+
const flat = sorted.map((c) => Array.isArray(c.code) ? c.code.join("\n") : String(c.code ?? "")).join("\n");
|
|
977
|
+
return flat.slice(0, 200);
|
|
978
|
+
})();
|
|
979
|
+
const rangesShort = [...changes].sort((a, b) => a.startLine - b.startLine || a.endLine - b.endLine).map((c) => `${c.startLine}-${c.endLine}`).join(", ");
|
|
980
|
+
const errClass = (errText || "unknown").toLowerCase().slice(0, 60);
|
|
981
|
+
const summary = { rangesShort, codeHead, errClass, fingerprint };
|
|
982
|
+
ctx.dupeBreak.recentFailedPatches = [
|
|
983
|
+
summary,
|
|
984
|
+
...ctx.dupeBreak.recentFailedPatches
|
|
985
|
+
].slice(0, 2);
|
|
986
|
+
if (action === "diagnostic" || action === "diagnostic-noforce") {
|
|
987
|
+
ctx.dupeBreak.pendingDiagnosticTurn = true;
|
|
988
|
+
ctx.dupeBreak.diagnosticFiredCount++;
|
|
989
|
+
augmentedResultText = `[DUPLICATE_PATCH] Identical failed patch detected.
|
|
990
|
+
Your previous patch was byte-identical to one you tried before and failed for the same reason. The next prompt will give you a focused excerpt of the failing region with line numbers and require a one-line structured diagnosis (in commit_message) before your next patch. Your next apply_changes MUST differ materially from the prior failed patch \u2014 different ranges OR meaningfully different code.
|
|
991
|
+
|
|
992
|
+
Repeated error: ${errText}
|
|
993
|
+
|
|
994
|
+
---
|
|
995
|
+
|
|
996
|
+
` + result.result;
|
|
997
|
+
} else {
|
|
998
|
+
ctx.dupeBreak.forceEscapeNextTurn = true;
|
|
999
|
+
augmentedResultText = `[DUPLICATE_PATCH] Identical failed patch detected.
|
|
1000
|
+
Your previous patch failed with the same error, and this retry is byte-identical.
|
|
1001
|
+
The next turn is FORCED to a scoped single-change apply_changes (one change, \u226420 lines). Target the minimal failing region \u2014 do not retry the full patch. The rest of the file is correct.
|
|
1002
|
+
|
|
1003
|
+
Repeated error: ${errText}
|
|
1004
|
+
|
|
1005
|
+
---
|
|
1006
|
+
|
|
1007
|
+
` + result.result;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
ctx.dupeBreak.lastFailedPatchFingerprint = fingerprint;
|
|
1011
|
+
}
|
|
1012
|
+
if (result.done && ctx.dupeBreak?.lastFailedPatchFingerprint) {
|
|
1013
|
+
ctx.dupeBreak.lastFailedPatchFingerprint = null;
|
|
1014
|
+
}
|
|
1015
|
+
if (ctx.dupeBreak && call.name === "apply_changes") {
|
|
1016
|
+
const wasBrokenApply = result.result.startsWith("PATCH_APPLIED_BROKEN");
|
|
1017
|
+
if (wasBrokenApply) {
|
|
1018
|
+
ctx.dupeBreak.consecutiveBrokenApplies++;
|
|
1019
|
+
} else if (!result.error) {
|
|
1020
|
+
ctx.dupeBreak.consecutiveBrokenApplies = 0;
|
|
1021
|
+
}
|
|
1022
|
+
} else if (ctx.dupeBreak && call.name === "rewrite") {
|
|
1023
|
+
if (!result.error) ctx.dupeBreak.consecutiveBrokenApplies = 0;
|
|
1024
|
+
}
|
|
1025
|
+
let sameExchangeRepeats = 0;
|
|
1026
|
+
if (ctx.dupeBreak) {
|
|
1027
|
+
sameExchangeRepeats = recordToolExchange(
|
|
1028
|
+
ctx.dupeBreak,
|
|
1029
|
+
call.name,
|
|
1030
|
+
call.input,
|
|
1031
|
+
result.result
|
|
1032
|
+
);
|
|
1033
|
+
if (sameExchangeRepeats === 2) {
|
|
1034
|
+
augmentedResultText += SAME_EXCHANGE_NUDGE;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
const isWrite = call.name === "apply_changes" || call.name === "rewrite";
|
|
1038
|
+
let repeatedFailure;
|
|
1039
|
+
if (ctx.dupeBreak && isWrite) {
|
|
1040
|
+
if (result.done) {
|
|
1041
|
+
clearWriteFailures(ctx.dupeBreak);
|
|
1042
|
+
} else {
|
|
1043
|
+
repeatedFailure = recordWriteFailure(ctx.dupeBreak, result.result);
|
|
1044
|
+
if (repeatedFailure?.repeats === SAME_FAILURE_NUDGE_AT) {
|
|
1045
|
+
augmentedResultText += SAME_FAILURE_NUDGE;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
await codingAgent.sendToolResult([{
|
|
1050
|
+
callId: call.id,
|
|
1051
|
+
name: call.name,
|
|
1052
|
+
result: augmentedResultText,
|
|
1053
|
+
isError: !!result.error
|
|
1054
|
+
}]);
|
|
1055
|
+
if (sameExchangeRepeats >= SAME_EXCHANGE_BREAK_AT) {
|
|
1056
|
+
console.log(
|
|
1057
|
+
`[coding-agent] SAME_EXCHANGE_BREAK | tool=${call.name} | repeats=${sameExchangeRepeats} \u2014 terminating run (ggui#404)`
|
|
1058
|
+
);
|
|
1059
|
+
return {
|
|
1060
|
+
control: "break",
|
|
1061
|
+
compiledCode: "",
|
|
1062
|
+
selfCheckPassed: false,
|
|
1063
|
+
outcome: void 0,
|
|
1064
|
+
phase: void 0,
|
|
1065
|
+
tokens,
|
|
1066
|
+
...cacheTokens,
|
|
1067
|
+
llmMs,
|
|
1068
|
+
toolMs,
|
|
1069
|
+
lastResultText: augmentedResultText,
|
|
1070
|
+
lastDiffFailed: false,
|
|
1071
|
+
isEvalFeedback: false,
|
|
1072
|
+
iconNamesCache,
|
|
1073
|
+
preWarmedContext,
|
|
1074
|
+
sameExchangeBreak: { tool: call.name, repeats: sameExchangeRepeats }
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
if (repeatedFailure !== void 0 && repeatedFailure.repeats >= SAME_FAILURE_BREAK_AT) {
|
|
1078
|
+
const signature = repeatedFailure.signature.slice(0, 160);
|
|
1079
|
+
console.log(
|
|
1080
|
+
`[coding-agent] SAME_FAILURE_BREAK | tool=${call.name} | repeats=${repeatedFailure.repeats} | failure=${JSON.stringify(signature.slice(0, 80))} \u2014 terminating run (ggui#1791)`
|
|
1081
|
+
);
|
|
1082
|
+
return {
|
|
1083
|
+
control: "break",
|
|
1084
|
+
compiledCode: "",
|
|
1085
|
+
selfCheckPassed: false,
|
|
1086
|
+
outcome: void 0,
|
|
1087
|
+
phase: void 0,
|
|
1088
|
+
tokens,
|
|
1089
|
+
...cacheTokens,
|
|
1090
|
+
llmMs,
|
|
1091
|
+
toolMs,
|
|
1092
|
+
lastResultText: augmentedResultText,
|
|
1093
|
+
lastDiffFailed: false,
|
|
1094
|
+
isEvalFeedback: false,
|
|
1095
|
+
iconNamesCache,
|
|
1096
|
+
preWarmedContext,
|
|
1097
|
+
sameFailureBreak: { signature, repeats: repeatedFailure.repeats }
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
const turnDiffFailed = !!result.error && result.result.includes("DIFF");
|
|
1101
|
+
const isDiffFail = turnDiffFailed && !isPatchInvalid;
|
|
1102
|
+
const outcome = result.done ? "PASS" : isPatchInvalid ? "PATCH_INVALID" : isDiffFail ? "DIFF_FAIL" : "SELF_CHECK_FAIL";
|
|
1103
|
+
const phase = a1Phase === "scaffold" ? "scaffold" : a1Phase === "fill" ? "fill" : turnsUsed === 1 ? "impl" : isEvalFeedback ? "eval-fix" : "patch";
|
|
1104
|
+
console.log(
|
|
1105
|
+
`[simple] turn ${turnsUsed} (${phase}): ${outcome} | llm=${llmMs}ms tool=${toolMs}ms | in=${response.inputTokens} out=${response.outputTokens}`
|
|
1106
|
+
);
|
|
1107
|
+
if (!result.done) {
|
|
1108
|
+
const preview = JSON.stringify(call.input).slice(0, 150);
|
|
1109
|
+
console.log(`[simple] turn ${turnsUsed} preview: ${preview}...`);
|
|
1110
|
+
}
|
|
1111
|
+
if (!result.done) {
|
|
1112
|
+
return {
|
|
1113
|
+
control: "continue",
|
|
1114
|
+
compiledCode: "",
|
|
1115
|
+
selfCheckPassed: false,
|
|
1116
|
+
outcome,
|
|
1117
|
+
phase,
|
|
1118
|
+
tokens,
|
|
1119
|
+
...cacheTokens,
|
|
1120
|
+
llmMs,
|
|
1121
|
+
toolMs,
|
|
1122
|
+
// Use the augmented text so the next turn's prompt gets the
|
|
1123
|
+
// [DUPLICATE_PATCH] preface when dupe-break fired (noop otherwise).
|
|
1124
|
+
lastResultText: augmentedResultText,
|
|
1125
|
+
lastDiffFailed: turnDiffFailed,
|
|
1126
|
+
isEvalFeedback: false,
|
|
1127
|
+
iconNamesCache,
|
|
1128
|
+
preWarmedContext
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
let compiledCode = "";
|
|
1132
|
+
let selfCheckPassed = false;
|
|
1133
|
+
for (const [, meta] of commitMeta) {
|
|
1134
|
+
if (meta.build.success && meta.selfCheck.passed && meta.build.compiledCode) {
|
|
1135
|
+
compiledCode = meta.build.compiledCode;
|
|
1136
|
+
selfCheckPassed = true;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
if (!compiledCode) {
|
|
1140
|
+
for (const [, meta] of commitMeta) {
|
|
1141
|
+
if (meta.build.success && meta.build.compiledCode) {
|
|
1142
|
+
compiledCode = meta.build.compiledCode;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return {
|
|
1147
|
+
control: "proceed",
|
|
1148
|
+
compiledCode,
|
|
1149
|
+
selfCheckPassed,
|
|
1150
|
+
outcome,
|
|
1151
|
+
phase,
|
|
1152
|
+
tokens,
|
|
1153
|
+
...cacheTokens,
|
|
1154
|
+
llmMs,
|
|
1155
|
+
toolMs,
|
|
1156
|
+
lastResultText,
|
|
1157
|
+
lastDiffFailed: turnDiffFailed,
|
|
1158
|
+
isEvalFeedback,
|
|
1159
|
+
iconNamesCache,
|
|
1160
|
+
preWarmedContext
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// src/harness/enforced-coding.ts
|
|
1165
|
+
function mapProviderForEvaluator(provider) {
|
|
1166
|
+
return provider === "anthropic" ? "claude" : provider;
|
|
1167
|
+
}
|
|
1168
|
+
function visionJudgeProvider(provider) {
|
|
1169
|
+
switch (provider) {
|
|
1170
|
+
case "anthropic":
|
|
1171
|
+
return "claude";
|
|
1172
|
+
case "google":
|
|
1173
|
+
return "google";
|
|
1174
|
+
case "openai":
|
|
1175
|
+
case "openrouter":
|
|
1176
|
+
return void 0;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// src/harness/coding/run-eval-round.ts
|
|
1181
|
+
var MAX_FEEDBACK_ISSUES = 3;
|
|
1182
|
+
function isFitFail(issue) {
|
|
1183
|
+
return issue.result === "fail" && issue.category === "visual" && issue.subcategory === "canvas-overflow";
|
|
1184
|
+
}
|
|
1185
|
+
var FIT_FINGERPRINT_PREFIX = "visual|canvas-overflow|";
|
|
1186
|
+
function formatFitFeedback(issue) {
|
|
1187
|
+
return `[fit] ${issue.category}/${issue.subcategory ?? ""}: ${issue.description}
|
|
1188
|
+
Fix: ${issue.fix ?? ""}`;
|
|
1189
|
+
}
|
|
1190
|
+
var EVAL_ROUND_THREW_REASON = "eval round threw";
|
|
1191
|
+
var PROBE_ONLY_CRITERIA_REASON = "probe-only round: no evaluator configured";
|
|
1192
|
+
var PROBE_ONLY_VISUAL_REASON = "probe-only round: no visual evaluator configured";
|
|
1193
|
+
var PROBE_ONLY_PASS = "probe-only";
|
|
1194
|
+
var COST_TRACKER_ABSENT_REASON = "cost tracker absent on an evaluation round";
|
|
1195
|
+
function requireCostTracker(costTracker) {
|
|
1196
|
+
if (costTracker === null) throw new Error(COST_TRACKER_ABSENT_REASON);
|
|
1197
|
+
return costTracker;
|
|
1198
|
+
}
|
|
1199
|
+
function runVisualLeg(visualEvalAgent, run, measureFit) {
|
|
1200
|
+
const provider = visionJudgeProvider(visualEvalAgent.provider);
|
|
1201
|
+
if (provider === void 0) {
|
|
1202
|
+
const noJudge = `no vision judge for provider '${visualEvalAgent.provider}' (model ${visualEvalAgent.model}) \u2014 the in-loop visual judge runs on a vision provider (anthropic, google)`;
|
|
1203
|
+
if (measureFit === void 0) {
|
|
1204
|
+
return Promise.resolve({ issues: [], coverage: { status: "skipped", reason: noJudge } });
|
|
1205
|
+
}
|
|
1206
|
+
const notMeasured = (why) => ({
|
|
1207
|
+
issues: [],
|
|
1208
|
+
coverage: { status: "skipped", reason: `${noJudge}; fit not measured: ${why}` }
|
|
1209
|
+
});
|
|
1210
|
+
return measureFit().then(
|
|
1211
|
+
(fit) => fit.status === "measured" ? {
|
|
1212
|
+
issues: fit.issues,
|
|
1213
|
+
coverage: { status: "skipped", reason: `${noJudge}; fit measured without the judge: ${describeFitReadings(fit.readings)}` }
|
|
1214
|
+
} : notMeasured(fit.reason),
|
|
1215
|
+
(e) => notMeasured(e instanceof Error ? e.message : String(e))
|
|
1216
|
+
);
|
|
1217
|
+
}
|
|
1218
|
+
return run(provider).catch((e) => ({
|
|
1219
|
+
issues: [],
|
|
1220
|
+
coverage: { status: "skipped", reason: `visual leg failed: ${e instanceof Error ? e.message : String(e)}` }
|
|
1221
|
+
}));
|
|
1222
|
+
}
|
|
1223
|
+
function describeFitReadings(readings) {
|
|
1224
|
+
return readings.map((r) => `${r.canvas} ${r.viewport.width}\xD7${r.viewport.height} content ${r.contentHeight ?? "?"}px${r.overflow ? " (overflow)" : ""}`).join(", ");
|
|
1225
|
+
}
|
|
1226
|
+
var LOW_RISK_BYPASS_REASON = "same-image low-risk bypass: axis checks clean, tier-1/2 evaluation not invoked";
|
|
1227
|
+
function selectActionableFeedback(issues) {
|
|
1228
|
+
const pool = issues.filter((i) => i.result === "fail" || i.result === "warn");
|
|
1229
|
+
const reserved = pool.find((i) => i.tier === 0 && i.result === "fail");
|
|
1230
|
+
return [
|
|
1231
|
+
...reserved ? [reserved] : [],
|
|
1232
|
+
...pool.filter((i) => i !== reserved).sort((a, b) => (a.result === "fail" ? 0 : 1) - (b.result === "fail" ? 0 : 1))
|
|
1233
|
+
].slice(0, MAX_FEEDBACK_ISSUES);
|
|
1234
|
+
}
|
|
1235
|
+
function fingerprintFail(issue) {
|
|
1236
|
+
const cat = issue.category ?? "";
|
|
1237
|
+
const sub = issue.subcategory ?? "";
|
|
1238
|
+
const desc = (issue.description ?? "").toLowerCase().replace(/line\s+\d+/g, "line N").replace(/\s+/g, " ").trim().slice(0, 40);
|
|
1239
|
+
return `${cat}|${sub}|${desc}`;
|
|
1240
|
+
}
|
|
1241
|
+
function failedCheckKinds(probeIssues) {
|
|
1242
|
+
const failed = /* @__PURE__ */ new Set();
|
|
1243
|
+
for (const issue of probeIssues) {
|
|
1244
|
+
if (issue.result !== "fail" || issue.subcategory === void 0) continue;
|
|
1245
|
+
const kind = parseRuntimeSubcategory(issue.subcategory);
|
|
1246
|
+
if (kind !== void 0) failed.add(kind);
|
|
1247
|
+
}
|
|
1248
|
+
return RENDER_CHECK_KINDS.filter((kind) => failed.has(kind));
|
|
1249
|
+
}
|
|
1250
|
+
function probeMetaOf(outcome, probeIssues) {
|
|
1251
|
+
const detail = {
|
|
1252
|
+
...outcome.reason !== void 0 ? { reason: outcome.reason } : {},
|
|
1253
|
+
...outcome.renderMs !== void 0 ? { renderMs: outcome.renderMs } : {},
|
|
1254
|
+
...outcome.hostLoad !== void 0 ? { hostLoad: outcome.hostLoad } : {},
|
|
1255
|
+
...outcome.queuedMs !== void 0 ? { queuedMs: outcome.queuedMs } : {},
|
|
1256
|
+
...outcome.pendingAffordance !== void 0 ? { pendingAffordance: outcome.pendingAffordance } : {}
|
|
1257
|
+
};
|
|
1258
|
+
const elapsed = outcome.elapsedMs !== void 0 ? { elapsedMs: outcome.elapsedMs } : {};
|
|
1259
|
+
if (outcome.status !== "ran") {
|
|
1260
|
+
return { status: outcome.status, ...elapsed, ...detail };
|
|
1261
|
+
}
|
|
1262
|
+
const [first, ...rest] = failedCheckKinds(probeIssues);
|
|
1263
|
+
if (first === void 0) {
|
|
1264
|
+
return { status: "ran", verdict: "pass", ...elapsed, ...detail };
|
|
1265
|
+
}
|
|
1266
|
+
return { status: "ran", verdict: "fail", failChecks: [first, ...rest], ...elapsed, ...detail };
|
|
1267
|
+
}
|
|
1268
|
+
function describeProbeMeta(meta) {
|
|
1269
|
+
if (meta.verdict === void 0) return meta.status;
|
|
1270
|
+
return meta.verdict === "pass" ? "ran, verdict pass" : `ran, verdict fail (${meta.failChecks.join(", ")})`;
|
|
1271
|
+
}
|
|
1272
|
+
async function runProbeAtExit(input) {
|
|
1273
|
+
const { harness, sourceCode, compiledCode, contract, fixtureProps } = input;
|
|
1274
|
+
const probe = harness.check.runtimeRender;
|
|
1275
|
+
if (!probe || !compiledCode) {
|
|
1276
|
+
return {
|
|
1277
|
+
fired: false,
|
|
1278
|
+
recoverableFail: false,
|
|
1279
|
+
probeIssues: [],
|
|
1280
|
+
meta: {
|
|
1281
|
+
status: "not-applicable",
|
|
1282
|
+
reason: probe ? "no compiled code" : "no runtimeRender check configured"
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
let outcome;
|
|
1287
|
+
try {
|
|
1288
|
+
outcome = await probe.run({
|
|
1289
|
+
sourceCode,
|
|
1290
|
+
compiledCode,
|
|
1291
|
+
contract,
|
|
1292
|
+
fixtureProps
|
|
1293
|
+
});
|
|
1294
|
+
} catch (e) {
|
|
1295
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1296
|
+
console.warn(`[simple] in-loop probe at exit: infra failure \u2014 ${message}`);
|
|
1297
|
+
return {
|
|
1298
|
+
fired: false,
|
|
1299
|
+
recoverableFail: false,
|
|
1300
|
+
probeIssues: [],
|
|
1301
|
+
meta: { status: "infra-skipped", reason: message }
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
const meta = probeMetaOf(outcome, outcome.issues);
|
|
1305
|
+
if (outcome.status !== "ran") {
|
|
1306
|
+
return { fired: false, recoverableFail: false, probeIssues: [], meta };
|
|
1307
|
+
}
|
|
1308
|
+
const probeIssues = outcome.issues;
|
|
1309
|
+
const recoverableFail = probeIssues.some(
|
|
1310
|
+
(i) => i.result === "fail" && typeof i.subcategory === "string" && i.subcategory.startsWith("runtime:render-no-throw") && typeof i.description === "string" && isRecoverableRenderCrash(i.description)
|
|
1311
|
+
);
|
|
1312
|
+
return { fired: true, recoverableFail, probeIssues, meta };
|
|
1313
|
+
}
|
|
1314
|
+
function formatRuntimeProbeFeedback(issue) {
|
|
1315
|
+
const subcat = issue.subcategory ? `/${issue.subcategory}` : "";
|
|
1316
|
+
return `[runtime] ${issue.category}${subcat}: ${issue.description}
|
|
1317
|
+
Fix: ${issue.fix ?? ""}`;
|
|
1318
|
+
}
|
|
1319
|
+
var FEEDBACK_PROBE_CHECKS = ["prop-sensitivity"];
|
|
1320
|
+
var CONTRACT_FEEDBACK_BONUS = 1;
|
|
1321
|
+
function isFeedbackProbeFail(issue) {
|
|
1322
|
+
const sub = issue.subcategory;
|
|
1323
|
+
if (issue.result !== "fail" || typeof sub !== "string") return false;
|
|
1324
|
+
return FEEDBACK_PROBE_CHECKS.some((check) => sub === `runtime:${check}` || sub.startsWith(`runtime:${check}:`));
|
|
1325
|
+
}
|
|
1326
|
+
function contractFeedbackRecord(fails, sourceBefore) {
|
|
1327
|
+
return { firedOn: fails.flatMap((i) => i.subcategory !== void 0 ? [i.subcategory] : []), sourceBefore };
|
|
1328
|
+
}
|
|
1329
|
+
function contractFeedbackFails(exitProbe, alreadyFedBack) {
|
|
1330
|
+
if (!exitProbe.fired) return [];
|
|
1331
|
+
return exitProbe.probeIssues.filter((i) => isFeedbackProbeFail(i) && !alreadyFedBack.has(fingerprintFail(i)));
|
|
1332
|
+
}
|
|
1333
|
+
async function runEvalRound(ctx, input) {
|
|
1334
|
+
const {
|
|
1335
|
+
workspace,
|
|
1336
|
+
harness,
|
|
1337
|
+
contract,
|
|
1338
|
+
userPrompt,
|
|
1339
|
+
fixtureProps,
|
|
1340
|
+
classification,
|
|
1341
|
+
evaluationAgent,
|
|
1342
|
+
visualEvalAgent,
|
|
1343
|
+
visualEvaluation,
|
|
1344
|
+
visualThreshold,
|
|
1345
|
+
qualityMode,
|
|
1346
|
+
maxEvalRounds,
|
|
1347
|
+
llmEvalMod,
|
|
1348
|
+
visualMod,
|
|
1349
|
+
preWarmPromise,
|
|
1350
|
+
onProgress,
|
|
1351
|
+
probeOnly,
|
|
1352
|
+
probeRepairUsed,
|
|
1353
|
+
probeRepairTrigger
|
|
1354
|
+
} = ctx;
|
|
1355
|
+
const { compiledCode, prevModeSubcats, prevFailFingerprints } = input;
|
|
1356
|
+
let { evalRoundsUsed, preWarmedContext } = input;
|
|
1357
|
+
evalRoundsUsed++;
|
|
1358
|
+
onProgress?.({ type: "generating", phase: "evaluating" });
|
|
1359
|
+
const currentSource = workspace.read() ?? "";
|
|
1360
|
+
let evalResult;
|
|
1361
|
+
let evalTokens = { input: 0, output: 0 };
|
|
1362
|
+
let inLoopVisualTokens = void 0;
|
|
1363
|
+
let evalLlmMs = 0;
|
|
1364
|
+
try {
|
|
1365
|
+
if (probeOnly) {
|
|
1366
|
+
const exitProbe = await runProbeAtExit({
|
|
1367
|
+
harness,
|
|
1368
|
+
sourceCode: currentSource,
|
|
1369
|
+
compiledCode,
|
|
1370
|
+
contract,
|
|
1371
|
+
fixtureProps
|
|
1372
|
+
});
|
|
1373
|
+
const probeOnlyResult = {
|
|
1374
|
+
issues: [...exitProbe.probeIssues],
|
|
1375
|
+
pass: [PROBE_ONLY_PASS],
|
|
1376
|
+
runtimeProbe: exitProbe.meta,
|
|
1377
|
+
criteriaCoverage: notApplicableCoverage(PROBE_ONLY_CRITERIA_REASON),
|
|
1378
|
+
visualCoverage: { status: "not-applicable", reason: PROBE_ONLY_VISUAL_REASON }
|
|
1379
|
+
};
|
|
1380
|
+
const recoverableFail = exitProbe.fired && exitProbe.recoverableFail;
|
|
1381
|
+
if (probeRepairUsed) {
|
|
1382
|
+
if (probeRepairTrigger === void 0) {
|
|
1383
|
+
throw new Error("harness defect: a probe-only re-probe round without the probe that bought the repair turn");
|
|
1384
|
+
}
|
|
1385
|
+
const repair = { attempted: true, compiled: true, trigger: probeRepairTrigger };
|
|
1386
|
+
evalResult = { ...probeOnlyResult, runtimeProbeRepair: repair };
|
|
1387
|
+
console.log(
|
|
1388
|
+
`[simple] eval round ${evalRoundsUsed}: probe-only re-probe after the repair turn \u2014 ${describeProbeMeta(exitProbe.meta)}${recoverableFail ? ", recoverable crash still present" : ""} \u2014 serving`
|
|
1389
|
+
);
|
|
1390
|
+
return {
|
|
1391
|
+
control: "break",
|
|
1392
|
+
evalDone: true,
|
|
1393
|
+
evalResult,
|
|
1394
|
+
evalRoundsUsed,
|
|
1395
|
+
prevModeSubcats,
|
|
1396
|
+
prevFailFingerprints,
|
|
1397
|
+
preWarmedContext,
|
|
1398
|
+
evalTokens,
|
|
1399
|
+
...inLoopVisualTokens !== void 0 ? { inLoopVisualTokens } : {},
|
|
1400
|
+
evalLlmMs,
|
|
1401
|
+
lastResultText: "",
|
|
1402
|
+
isEvalFeedback: false,
|
|
1403
|
+
lastDiffFailed: false
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
evalResult = probeOnlyResult;
|
|
1407
|
+
if (recoverableFail) {
|
|
1408
|
+
const runtimeFails = exitProbe.probeIssues.filter(
|
|
1409
|
+
(i) => i.result === "fail" && i.subcategory?.startsWith("runtime:")
|
|
1410
|
+
);
|
|
1411
|
+
const lines = runtimeFails.slice(0, MAX_FEEDBACK_ISSUES).map(formatRuntimeProbeFeedback);
|
|
1412
|
+
console.log(
|
|
1413
|
+
`[simple] eval round ${evalRoundsUsed}: probe-only round \u2014 runtime probe fail (recoverable) \u2014 granting the one repair turn`
|
|
1414
|
+
);
|
|
1415
|
+
return {
|
|
1416
|
+
control: "feedback",
|
|
1417
|
+
evalDone: false,
|
|
1418
|
+
evalResult,
|
|
1419
|
+
evalRoundsUsed,
|
|
1420
|
+
prevModeSubcats,
|
|
1421
|
+
prevFailFingerprints,
|
|
1422
|
+
preWarmedContext,
|
|
1423
|
+
evalTokens,
|
|
1424
|
+
...inLoopVisualTokens !== void 0 ? { inLoopVisualTokens } : {},
|
|
1425
|
+
evalLlmMs,
|
|
1426
|
+
lastResultText: lines.join("\n\n"),
|
|
1427
|
+
isEvalFeedback: true,
|
|
1428
|
+
lastDiffFailed: false
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
console.log(
|
|
1432
|
+
`[simple] eval round ${evalRoundsUsed}: probe-only round \u2014 probe ${describeProbeMeta(exitProbe.meta)} \u2014 serving`
|
|
1433
|
+
);
|
|
1434
|
+
return {
|
|
1435
|
+
control: "break",
|
|
1436
|
+
evalDone: true,
|
|
1437
|
+
evalResult,
|
|
1438
|
+
evalRoundsUsed,
|
|
1439
|
+
prevModeSubcats,
|
|
1440
|
+
prevFailFingerprints,
|
|
1441
|
+
preWarmedContext,
|
|
1442
|
+
evalTokens,
|
|
1443
|
+
...inLoopVisualTokens !== void 0 ? { inLoopVisualTokens } : {},
|
|
1444
|
+
evalLlmMs,
|
|
1445
|
+
lastResultText: "",
|
|
1446
|
+
isEvalFeedback: false,
|
|
1447
|
+
lastDiffFailed: false
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
const checkResult = await runCheck({
|
|
1451
|
+
harness,
|
|
1452
|
+
sourceCode: currentSource,
|
|
1453
|
+
compiledCode,
|
|
1454
|
+
contract,
|
|
1455
|
+
prompt: userPrompt,
|
|
1456
|
+
fixtureProps,
|
|
1457
|
+
skipRuntimeRender: true
|
|
1458
|
+
});
|
|
1459
|
+
const modeIssues = [...checkResult.issues];
|
|
1460
|
+
const modeHasBlocking = modeIssues.some((i) => i.result === "fail");
|
|
1461
|
+
const contractDeclaresGadgets = contract !== void 0 && listContractGadgets(contract).length > 0;
|
|
1462
|
+
if (classification.riskTier === "low" && !modeHasBlocking && !contractDeclaresGadgets) {
|
|
1463
|
+
const exitProbe = await runProbeAtExit({
|
|
1464
|
+
harness,
|
|
1465
|
+
sourceCode: currentSource,
|
|
1466
|
+
compiledCode,
|
|
1467
|
+
contract,
|
|
1468
|
+
fixtureProps
|
|
1469
|
+
});
|
|
1470
|
+
if (exitProbe.fired && exitProbe.recoverableFail) {
|
|
1471
|
+
const runtimeFails = exitProbe.probeIssues.filter(
|
|
1472
|
+
(i) => i.result === "fail" && i.subcategory?.startsWith("runtime:")
|
|
1473
|
+
);
|
|
1474
|
+
const lines = runtimeFails.slice(0, MAX_FEEDBACK_ISSUES).map(formatRuntimeProbeFeedback);
|
|
1475
|
+
const allIssues2 = [...modeIssues, ...exitProbe.probeIssues];
|
|
1476
|
+
evalResult = {
|
|
1477
|
+
issues: allIssues2,
|
|
1478
|
+
pass: ["axis.low-risk"],
|
|
1479
|
+
criteriaCoverage: notApplicableCoverage(LOW_RISK_BYPASS_REASON),
|
|
1480
|
+
runtimeProbe: exitProbe.meta
|
|
1481
|
+
};
|
|
1482
|
+
console.log(
|
|
1483
|
+
`[simple] eval round ${evalRoundsUsed}: low-risk bypass clean BUT runtime probe fail (recoverable) \u2014 granting +1 turn`
|
|
1484
|
+
);
|
|
1485
|
+
return {
|
|
1486
|
+
control: "feedback",
|
|
1487
|
+
evalDone: false,
|
|
1488
|
+
evalResult,
|
|
1489
|
+
evalRoundsUsed,
|
|
1490
|
+
prevModeSubcats,
|
|
1491
|
+
prevFailFingerprints,
|
|
1492
|
+
preWarmedContext,
|
|
1493
|
+
evalTokens,
|
|
1494
|
+
...inLoopVisualTokens !== void 0 ? { inLoopVisualTokens } : {},
|
|
1495
|
+
evalLlmMs,
|
|
1496
|
+
lastResultText: lines.join("\n\n"),
|
|
1497
|
+
isEvalFeedback: true,
|
|
1498
|
+
lastDiffFailed: false
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
evalResult = {
|
|
1502
|
+
issues: [...modeIssues, ...exitProbe.probeIssues],
|
|
1503
|
+
pass: ["axis.low-risk"],
|
|
1504
|
+
criteriaCoverage: notApplicableCoverage(LOW_RISK_BYPASS_REASON),
|
|
1505
|
+
runtimeProbe: exitProbe.meta
|
|
1506
|
+
};
|
|
1507
|
+
console.log(
|
|
1508
|
+
`[simple] eval round ${evalRoundsUsed}: low-risk bypass (axis-checks clean, tier-1/2 skipped${exitProbe.fired ? ", probe ran" : `, probe ${exitProbe.meta.status}`}) \u2014 PASS`
|
|
1509
|
+
);
|
|
1510
|
+
return {
|
|
1511
|
+
control: "break",
|
|
1512
|
+
evalDone: true,
|
|
1513
|
+
evalResult,
|
|
1514
|
+
evalRoundsUsed,
|
|
1515
|
+
prevModeSubcats,
|
|
1516
|
+
prevFailFingerprints,
|
|
1517
|
+
preWarmedContext,
|
|
1518
|
+
evalTokens,
|
|
1519
|
+
...inLoopVisualTokens !== void 0 ? { inLoopVisualTokens } : {},
|
|
1520
|
+
evalLlmMs,
|
|
1521
|
+
lastResultText: "",
|
|
1522
|
+
isEvalFeedback: false,
|
|
1523
|
+
lastDiffFailed: false
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
const costTracker = requireCostTracker(ctx.costTracker);
|
|
1527
|
+
if (evalRoundsUsed === 1 && !preWarmedContext && preWarmPromise) {
|
|
1528
|
+
preWarmedContext = await preWarmPromise;
|
|
1529
|
+
}
|
|
1530
|
+
let llmResult = null;
|
|
1531
|
+
let visualIssues = null;
|
|
1532
|
+
const visualContext = {
|
|
1533
|
+
compiledCode,
|
|
1534
|
+
originalPrompt: userPrompt,
|
|
1535
|
+
profile: harness.profile,
|
|
1536
|
+
// The caller's theme, when named — the round paints what the app paints.
|
|
1537
|
+
...visualEvaluation?.cssTokens !== void 0 ? { cssTokens: visualEvaluation.cssTokens } : {},
|
|
1538
|
+
// ggui#1436 — the criteria bank and the card's context; the judge selects per canvas, report-only.
|
|
1539
|
+
...visualEvaluation?.criteriaBank !== void 0 ? {
|
|
1540
|
+
criteria: {
|
|
1541
|
+
bank: visualEvaluation.criteriaBank,
|
|
1542
|
+
context: criteriaContextFor({
|
|
1543
|
+
classification,
|
|
1544
|
+
contract,
|
|
1545
|
+
cssTokens: visualEvaluation.cssTokens,
|
|
1546
|
+
profile: harness.profile,
|
|
1547
|
+
shell: ctx.shellType ?? "unknown",
|
|
1548
|
+
...visualEvaluation.criteriaKind !== void 0 ? { kind: visualEvaluation.criteriaKind } : {}
|
|
1549
|
+
})
|
|
1550
|
+
}
|
|
1551
|
+
} : {}
|
|
1552
|
+
};
|
|
1553
|
+
const visualFrame = {
|
|
1554
|
+
// The in-loop visual round renders the same sample the runtime probe
|
|
1555
|
+
// does (the caller's fixture) unless the config names its own.
|
|
1556
|
+
sampleProps: visualEvaluation?.sampleProps ?? fixtureProps,
|
|
1557
|
+
// Per-canvas judging (arm-neutral) — absent = single shot.
|
|
1558
|
+
...visualEvaluation?.canvases !== void 0 ? { canvases: visualEvaluation.canvases } : {},
|
|
1559
|
+
// ggui#1195 — the declared box per canvas (same-target with the composer).
|
|
1560
|
+
...visualEvaluation?.canvasViewports !== void 0 ? { canvasViewports: visualEvaluation.canvasViewports } : {}
|
|
1561
|
+
};
|
|
1562
|
+
const measureFit = visualMod && visualFrame.canvases !== void 0 && visualFrame.canvases.length > 0 ? () => visualMod.runVisualFit(visualContext, visualFrame) : void 0;
|
|
1563
|
+
const evalLlmStart = Date.now();
|
|
1564
|
+
const [llm, visual] = await Promise.all([
|
|
1565
|
+
llmEvalMod ? llmEvalMod.runLLMEvaluation(
|
|
1566
|
+
{
|
|
1567
|
+
sourceCode: currentSource,
|
|
1568
|
+
originalPrompt: userPrompt,
|
|
1569
|
+
contract,
|
|
1570
|
+
// Criteria + mother prompt follow the triad that produced
|
|
1571
|
+
// the source (free mode: color-only tokens, arm-neutral
|
|
1572
|
+
// visual judging).
|
|
1573
|
+
designMode: harness.designMode,
|
|
1574
|
+
profile: harness.profile
|
|
1575
|
+
},
|
|
1576
|
+
{
|
|
1577
|
+
provider: mapProviderForEvaluator(evaluationAgent.provider),
|
|
1578
|
+
model: evaluationAgent.model,
|
|
1579
|
+
// #484 — thread the coding-agent's routing override
|
|
1580
|
+
// through to the evaluator so its LLM call never falls
|
|
1581
|
+
// back to `process.env` either (the eval calls run
|
|
1582
|
+
// inside the same concurrent generation, so the race is
|
|
1583
|
+
// real here too, not just on the coding turn).
|
|
1584
|
+
routeOverride: evaluationAgent.routeOverride,
|
|
1585
|
+
// #489 — thread the same provider-429-retry observer.
|
|
1586
|
+
// Without this, a rate-limited retry inside the
|
|
1587
|
+
// evaluation agent's apiCall() happens (the base-class
|
|
1588
|
+
// retry loop is unconditional) but never reaches the
|
|
1589
|
+
// caller, silently missing the host application's
|
|
1590
|
+
// structured log.
|
|
1591
|
+
onRetry: evaluationAgent.onRetry
|
|
1592
|
+
},
|
|
1593
|
+
preWarmedContext
|
|
1594
|
+
) : null,
|
|
1595
|
+
visualMod ? runVisualLeg(
|
|
1596
|
+
visualEvalAgent,
|
|
1597
|
+
(provider) => visualMod.runVisualEval(visualContext, {
|
|
1598
|
+
provider,
|
|
1599
|
+
model: visualEvalAgent.model,
|
|
1600
|
+
passThreshold: visualThreshold,
|
|
1601
|
+
viewport: visualEvaluation?.viewport,
|
|
1602
|
+
...visualFrame,
|
|
1603
|
+
// #484/#489 — same threading as the LLM-eval leg above:
|
|
1604
|
+
// the visual eval's multimodal call runs inside the same
|
|
1605
|
+
// concurrent generation, so it needs the same routing
|
|
1606
|
+
// override (never fall back to `process.env`) and the
|
|
1607
|
+
// same provider-429-retry observer (retries reach the
|
|
1608
|
+
// host application's structured log).
|
|
1609
|
+
routeOverride: visualEvalAgent.routeOverride,
|
|
1610
|
+
onRetry: visualEvalAgent.onRetry
|
|
1611
|
+
}),
|
|
1612
|
+
measureFit
|
|
1613
|
+
) : null
|
|
1614
|
+
]);
|
|
1615
|
+
evalLlmMs = Date.now() - evalLlmStart;
|
|
1616
|
+
llmResult = llm;
|
|
1617
|
+
visualIssues = visual?.issues ?? null;
|
|
1618
|
+
inLoopVisualTokens = visual?.tokens;
|
|
1619
|
+
if (inLoopVisualTokens !== void 0) {
|
|
1620
|
+
costTracker.record(visualEvalAgent.model, inLoopVisualTokens.inputTokens, inLoopVisualTokens.outputTokens);
|
|
1621
|
+
const criteriaSpend = inLoopVisualTokens.criteria;
|
|
1622
|
+
if (criteriaSpend !== void 0) costTracker.record(visualEvalAgent.model, criteriaSpend.inputTokens, criteriaSpend.outputTokens);
|
|
1623
|
+
}
|
|
1624
|
+
const visualSummary = visual?.summary;
|
|
1625
|
+
const visualCoverage = visual?.coverage ?? { status: "not-applicable", reason: "visual leg not configured" };
|
|
1626
|
+
if (visualCoverage.status === "skipped") {
|
|
1627
|
+
console.log(`[eval] visual-judge: SKIPPED (${visualCoverage.reason ?? "no reason recorded"})`);
|
|
1628
|
+
}
|
|
1629
|
+
if (llmResult) {
|
|
1630
|
+
costTracker.record(evaluationAgent.model, llmResult.inputTokens, llmResult.outputTokens, {
|
|
1631
|
+
...llmResult.cacheReadTokens !== void 0 ? { read: llmResult.cacheReadTokens } : {},
|
|
1632
|
+
...llmResult.cacheCreationTokens !== void 0 ? { write: llmResult.cacheCreationTokens } : {}
|
|
1633
|
+
});
|
|
1634
|
+
evalTokens = {
|
|
1635
|
+
input: llmResult.inputTokens,
|
|
1636
|
+
output: llmResult.outputTokens,
|
|
1637
|
+
...llmResult.cacheReadTokens !== void 0 ? { cacheRead: llmResult.cacheReadTokens } : {},
|
|
1638
|
+
...llmResult.cacheCreationTokens !== void 0 ? { cacheCreation: llmResult.cacheCreationTokens } : {}
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
const allIssues = [
|
|
1642
|
+
...llmResult?.issues ?? [],
|
|
1643
|
+
...visualIssues ?? [],
|
|
1644
|
+
...modeIssues
|
|
1645
|
+
];
|
|
1646
|
+
const allPass = [...llmResult?.pass ?? []];
|
|
1647
|
+
evalResult = {
|
|
1648
|
+
issues: allIssues,
|
|
1649
|
+
pass: allPass,
|
|
1650
|
+
...llmResult?.criteriaCoverage !== void 0 ? { criteriaCoverage: llmResult.criteriaCoverage } : {},
|
|
1651
|
+
// Per-canvas visual summary — present only when canvases were
|
|
1652
|
+
// requested and the leg ran; every later re-stamp spreads this
|
|
1653
|
+
// base so it survives to the assembled result.
|
|
1654
|
+
...visualSummary !== void 0 ? { visual: visualSummary } : {},
|
|
1655
|
+
visualCoverage
|
|
1656
|
+
};
|
|
1657
|
+
const fails = allIssues.filter((i) => i.result === "fail");
|
|
1658
|
+
const warns = allIssues.filter((i) => i.result === "warn");
|
|
1659
|
+
const modeFails = modeIssues.filter((i) => i.result === "fail").length;
|
|
1660
|
+
const modeWarns = modeIssues.filter((i) => i.result === "warn").length;
|
|
1661
|
+
const modeSubcatCounts = modeIssues.reduce((acc, i) => {
|
|
1662
|
+
const key = i.subcategory ?? "mode";
|
|
1663
|
+
acc[key] = (acc[key] ?? 0) + 1;
|
|
1664
|
+
return acc;
|
|
1665
|
+
}, {});
|
|
1666
|
+
const modeBreakdown = Object.entries(modeSubcatCounts).map(([k, v]) => `${k}\xD7${v}`).join(", ");
|
|
1667
|
+
const currSubcats = new Set(Object.keys(modeSubcatCounts));
|
|
1668
|
+
let crossRound = "";
|
|
1669
|
+
if (evalRoundsUsed > 1 && (prevModeSubcats.size > 0 || currSubcats.size > 0)) {
|
|
1670
|
+
const unresolved = [...currSubcats].filter((s) => prevModeSubcats.has(s));
|
|
1671
|
+
const fixed = [...prevModeSubcats].filter((s) => !currSubcats.has(s));
|
|
1672
|
+
const fresh = [...currSubcats].filter((s) => !prevModeSubcats.has(s));
|
|
1673
|
+
const parts = [];
|
|
1674
|
+
if (unresolved.length) parts.push(`unresolved=[${unresolved.join(",")}]`);
|
|
1675
|
+
if (fixed.length) parts.push(`fixed=[${fixed.join(",")}]`);
|
|
1676
|
+
if (fresh.length) parts.push(`new=[${fresh.join(",")}]`);
|
|
1677
|
+
if (parts.length) crossRound = ` | ${parts.join(" ")}`;
|
|
1678
|
+
}
|
|
1679
|
+
const updatedPrevModeSubcats = currSubcats;
|
|
1680
|
+
console.log(
|
|
1681
|
+
`[simple] eval round ${evalRoundsUsed}: ${fails.length} fails, ${warns.length} warns | cost=$${costTracker.getTotal().toFixed(4)} | tiers: t1+2=${llmResult?.issues.length ?? 0} visual=${visualIssues?.length ?? 0} axis[${classification.riskTier}]=${modeFails}F/${modeWarns}W${modeBreakdown ? ` (${modeBreakdown})` : ""}${crossRound}`
|
|
1682
|
+
);
|
|
1683
|
+
const blocking = evalResult.issues.filter((i) => i.result === "fail");
|
|
1684
|
+
const visualMajorsActed = visualEvaluation?.actOn === "major" ? (visualIssues ?? []).filter((i) => i.origin === "judge" && i.result === "warn" && i.severity === "major") : [];
|
|
1685
|
+
const shouldContinue = qualityMode === "fast" ? blocking.length > 0 || visualMajorsActed.length > 0 : blocking.length > 0 || warns.length > 0;
|
|
1686
|
+
const currFailFingerprints = new Set(blocking.map(fingerprintFail));
|
|
1687
|
+
if (!shouldContinue) {
|
|
1688
|
+
const exitProbe = await runProbeAtExit({
|
|
1689
|
+
harness,
|
|
1690
|
+
sourceCode: currentSource,
|
|
1691
|
+
compiledCode,
|
|
1692
|
+
contract,
|
|
1693
|
+
fixtureProps
|
|
1694
|
+
});
|
|
1695
|
+
if (exitProbe.fired && exitProbe.recoverableFail) {
|
|
1696
|
+
const runtimeFails = exitProbe.probeIssues.filter(
|
|
1697
|
+
(i) => i.result === "fail" && i.subcategory?.startsWith("runtime:")
|
|
1698
|
+
);
|
|
1699
|
+
const lines = runtimeFails.slice(0, MAX_FEEDBACK_ISSUES).map(formatRuntimeProbeFeedback);
|
|
1700
|
+
const enrichedIssues = [...allIssues, ...exitProbe.probeIssues];
|
|
1701
|
+
evalResult = { ...evalResult, issues: enrichedIssues, pass: allPass, runtimeProbe: exitProbe.meta };
|
|
1702
|
+
const enrichedFingerprints = /* @__PURE__ */ new Set([
|
|
1703
|
+
...currFailFingerprints,
|
|
1704
|
+
...runtimeFails.map(fingerprintFail)
|
|
1705
|
+
]);
|
|
1706
|
+
console.log(
|
|
1707
|
+
`[simple] eval round ${evalRoundsUsed}: tier-1/2 clean BUT runtime probe fail (recoverable) \u2014 granting +1 turn`
|
|
1708
|
+
);
|
|
1709
|
+
return {
|
|
1710
|
+
control: "feedback",
|
|
1711
|
+
evalDone: false,
|
|
1712
|
+
evalResult,
|
|
1713
|
+
evalRoundsUsed,
|
|
1714
|
+
prevModeSubcats: updatedPrevModeSubcats,
|
|
1715
|
+
prevFailFingerprints: enrichedFingerprints,
|
|
1716
|
+
preWarmedContext,
|
|
1717
|
+
evalTokens,
|
|
1718
|
+
...inLoopVisualTokens !== void 0 ? { inLoopVisualTokens } : {},
|
|
1719
|
+
evalLlmMs,
|
|
1720
|
+
lastResultText: lines.join("\n\n"),
|
|
1721
|
+
isEvalFeedback: true,
|
|
1722
|
+
lastDiffFailed: false
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
const contractFails = contractFeedbackFails(exitProbe, prevFailFingerprints);
|
|
1726
|
+
if (contractFails.length > 0 && evalRoundsUsed < maxEvalRounds + CONTRACT_FEEDBACK_BONUS) {
|
|
1727
|
+
const lines = contractFails.slice(0, MAX_FEEDBACK_ISSUES).map(formatRuntimeProbeFeedback);
|
|
1728
|
+
evalResult = {
|
|
1729
|
+
...evalResult,
|
|
1730
|
+
issues: [...allIssues, ...exitProbe.probeIssues],
|
|
1731
|
+
pass: allPass,
|
|
1732
|
+
runtimeProbe: exitProbe.meta
|
|
1733
|
+
};
|
|
1734
|
+
console.log(
|
|
1735
|
+
`[simple] eval round ${evalRoundsUsed}: tier-1/2 clean BUT contract probe fail \u2014 granting +1 turn (ggui#1261; ${contractFails.map((i) => i.subcategory).join(", ")})`
|
|
1736
|
+
);
|
|
1737
|
+
return {
|
|
1738
|
+
control: "feedback",
|
|
1739
|
+
evalDone: false,
|
|
1740
|
+
evalResult,
|
|
1741
|
+
evalRoundsUsed,
|
|
1742
|
+
prevModeSubcats: updatedPrevModeSubcats,
|
|
1743
|
+
prevFailFingerprints: /* @__PURE__ */ new Set([...currFailFingerprints, ...contractFails.map(fingerprintFail)]),
|
|
1744
|
+
preWarmedContext,
|
|
1745
|
+
evalTokens,
|
|
1746
|
+
...inLoopVisualTokens !== void 0 ? { inLoopVisualTokens } : {},
|
|
1747
|
+
evalLlmMs,
|
|
1748
|
+
lastResultText: lines.join("\n\n"),
|
|
1749
|
+
isEvalFeedback: true,
|
|
1750
|
+
lastDiffFailed: false,
|
|
1751
|
+
contractFeedback: contractFeedbackRecord(contractFails, currentSource)
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
evalResult = {
|
|
1755
|
+
...evalResult,
|
|
1756
|
+
issues: [...allIssues, ...exitProbe.probeIssues],
|
|
1757
|
+
pass: allPass,
|
|
1758
|
+
runtimeProbe: exitProbe.meta
|
|
1759
|
+
};
|
|
1760
|
+
console.log(`[simple] eval round ${evalRoundsUsed}: no blocking issues \u2014 PASS`);
|
|
1761
|
+
return {
|
|
1762
|
+
control: "break",
|
|
1763
|
+
evalDone: true,
|
|
1764
|
+
evalResult,
|
|
1765
|
+
evalRoundsUsed,
|
|
1766
|
+
prevModeSubcats: updatedPrevModeSubcats,
|
|
1767
|
+
prevFailFingerprints: currFailFingerprints,
|
|
1768
|
+
preWarmedContext,
|
|
1769
|
+
evalTokens,
|
|
1770
|
+
...inLoopVisualTokens !== void 0 ? { inLoopVisualTokens } : {},
|
|
1771
|
+
evalLlmMs,
|
|
1772
|
+
lastResultText: "",
|
|
1773
|
+
isEvalFeedback: false,
|
|
1774
|
+
lastDiffFailed: false
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
const onlyExtensionExemptFails = currFailFingerprints.size > 0 && [...currFailFingerprints].every((fp) => fp.includes("|runtime:") || fp.startsWith(FIT_FINGERPRINT_PREFIX));
|
|
1778
|
+
if (evalRoundsUsed > 1 && currFailFingerprints.size > 0 && !onlyExtensionExemptFails && [...currFailFingerprints].every((fp) => prevFailFingerprints.has(fp))) {
|
|
1779
|
+
console.log(
|
|
1780
|
+
`[simple] eval round ${evalRoundsUsed}: stuck \u2014 all ${currFailFingerprints.size} fails recur from prior round, exiting eval-fix loop`
|
|
1781
|
+
);
|
|
1782
|
+
const stuckExitProbe = await runProbeAtExit({
|
|
1783
|
+
harness,
|
|
1784
|
+
sourceCode: currentSource,
|
|
1785
|
+
compiledCode,
|
|
1786
|
+
contract,
|
|
1787
|
+
fixtureProps
|
|
1788
|
+
});
|
|
1789
|
+
evalResult = {
|
|
1790
|
+
...evalResult,
|
|
1791
|
+
issues: [...evalResult.issues, ...stuckExitProbe.probeIssues],
|
|
1792
|
+
runtimeProbe: stuckExitProbe.meta
|
|
1793
|
+
};
|
|
1794
|
+
const stuckContractFails = contractFeedbackFails(stuckExitProbe, prevFailFingerprints);
|
|
1795
|
+
if (stuckContractFails.length > 0 && evalRoundsUsed < maxEvalRounds + CONTRACT_FEEDBACK_BONUS) {
|
|
1796
|
+
const lines = stuckContractFails.slice(0, MAX_FEEDBACK_ISSUES).map(formatRuntimeProbeFeedback);
|
|
1797
|
+
console.log(
|
|
1798
|
+
`[simple] eval round ${evalRoundsUsed}: stuck BUT contract probe fail never delivered \u2014 granting +1 turn (ggui#1261; ${stuckContractFails.map((i) => i.subcategory).join(", ")})`
|
|
1799
|
+
);
|
|
1800
|
+
return {
|
|
1801
|
+
control: "feedback",
|
|
1802
|
+
evalDone: false,
|
|
1803
|
+
evalResult,
|
|
1804
|
+
evalRoundsUsed,
|
|
1805
|
+
prevModeSubcats: updatedPrevModeSubcats,
|
|
1806
|
+
prevFailFingerprints: /* @__PURE__ */ new Set([...currFailFingerprints, ...stuckContractFails.map(fingerprintFail)]),
|
|
1807
|
+
preWarmedContext,
|
|
1808
|
+
evalTokens,
|
|
1809
|
+
...inLoopVisualTokens !== void 0 ? { inLoopVisualTokens } : {},
|
|
1810
|
+
evalLlmMs,
|
|
1811
|
+
lastResultText: lines.join("\n\n"),
|
|
1812
|
+
isEvalFeedback: true,
|
|
1813
|
+
lastDiffFailed: false,
|
|
1814
|
+
contractFeedback: contractFeedbackRecord(stuckContractFails, currentSource)
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
return {
|
|
1818
|
+
control: "break",
|
|
1819
|
+
evalDone: false,
|
|
1820
|
+
evalResult,
|
|
1821
|
+
evalRoundsUsed,
|
|
1822
|
+
prevModeSubcats: updatedPrevModeSubcats,
|
|
1823
|
+
prevFailFingerprints: currFailFingerprints,
|
|
1824
|
+
preWarmedContext,
|
|
1825
|
+
evalTokens,
|
|
1826
|
+
...inLoopVisualTokens !== void 0 ? { inLoopVisualTokens } : {},
|
|
1827
|
+
evalLlmMs,
|
|
1828
|
+
lastResultText: "",
|
|
1829
|
+
isEvalFeedback: false,
|
|
1830
|
+
lastDiffFailed: false
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
const RUNTIME_EXTENSION_BONUS = 2;
|
|
1834
|
+
const FIT_EXTENSION_BONUS = 1;
|
|
1835
|
+
if (evalRoundsUsed >= maxEvalRounds) {
|
|
1836
|
+
const exitProbe = await runProbeAtExit({
|
|
1837
|
+
harness,
|
|
1838
|
+
sourceCode: currentSource,
|
|
1839
|
+
compiledCode,
|
|
1840
|
+
contract,
|
|
1841
|
+
fixtureProps
|
|
1842
|
+
});
|
|
1843
|
+
const hasRuntimeFail = exitProbe.fired && exitProbe.recoverableFail;
|
|
1844
|
+
const allowRuntimeExtension = hasRuntimeFail && evalRoundsUsed < maxEvalRounds + RUNTIME_EXTENSION_BONUS;
|
|
1845
|
+
const fitFails = evalResult.issues.filter(isFitFail);
|
|
1846
|
+
const allowFitExtension = !allowRuntimeExtension && fitFails.length > 0 && evalRoundsUsed < maxEvalRounds + FIT_EXTENSION_BONUS;
|
|
1847
|
+
if (allowFitExtension) {
|
|
1848
|
+
const fitLines = fitFails.slice(0, MAX_FEEDBACK_ISSUES).map(formatFitFeedback);
|
|
1849
|
+
evalResult = {
|
|
1850
|
+
...evalResult,
|
|
1851
|
+
issues: [...evalResult.issues, ...exitProbe.probeIssues],
|
|
1852
|
+
runtimeProbe: exitProbe.meta
|
|
1853
|
+
};
|
|
1854
|
+
console.log(
|
|
1855
|
+
`[simple] eval round ${evalRoundsUsed}: fit fail active at cap \u2014 granting +1 retry (ggui#1195; ${fitFails.length} canvas-overflow)`
|
|
1856
|
+
);
|
|
1857
|
+
return {
|
|
1858
|
+
control: "feedback",
|
|
1859
|
+
evalDone: false,
|
|
1860
|
+
evalResult,
|
|
1861
|
+
evalRoundsUsed,
|
|
1862
|
+
prevModeSubcats: updatedPrevModeSubcats,
|
|
1863
|
+
prevFailFingerprints: /* @__PURE__ */ new Set([...currFailFingerprints, ...fitFails.map(fingerprintFail)]),
|
|
1864
|
+
preWarmedContext,
|
|
1865
|
+
evalTokens,
|
|
1866
|
+
...inLoopVisualTokens !== void 0 ? { inLoopVisualTokens } : {},
|
|
1867
|
+
evalLlmMs,
|
|
1868
|
+
lastResultText: fitLines.join("\n\n"),
|
|
1869
|
+
isEvalFeedback: true,
|
|
1870
|
+
lastDiffFailed: false
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
const capContractFails = allowRuntimeExtension ? [] : contractFeedbackFails(exitProbe, prevFailFingerprints);
|
|
1874
|
+
if (capContractFails.length > 0 && evalRoundsUsed < maxEvalRounds + CONTRACT_FEEDBACK_BONUS) {
|
|
1875
|
+
const contractLines = capContractFails.slice(0, MAX_FEEDBACK_ISSUES).map(formatRuntimeProbeFeedback);
|
|
1876
|
+
evalResult = {
|
|
1877
|
+
...evalResult,
|
|
1878
|
+
issues: [...evalResult.issues, ...exitProbe.probeIssues],
|
|
1879
|
+
runtimeProbe: exitProbe.meta
|
|
1880
|
+
};
|
|
1881
|
+
console.log(
|
|
1882
|
+
`[simple] eval round ${evalRoundsUsed}: contract probe fail active at cap \u2014 granting +1 retry (ggui#1261; ${capContractFails.map((i) => i.subcategory).join(", ")})`
|
|
1883
|
+
);
|
|
1884
|
+
return {
|
|
1885
|
+
control: "feedback",
|
|
1886
|
+
evalDone: false,
|
|
1887
|
+
evalResult,
|
|
1888
|
+
evalRoundsUsed,
|
|
1889
|
+
prevModeSubcats: updatedPrevModeSubcats,
|
|
1890
|
+
prevFailFingerprints: /* @__PURE__ */ new Set([...currFailFingerprints, ...capContractFails.map(fingerprintFail)]),
|
|
1891
|
+
preWarmedContext,
|
|
1892
|
+
evalTokens,
|
|
1893
|
+
...inLoopVisualTokens !== void 0 ? { inLoopVisualTokens } : {},
|
|
1894
|
+
evalLlmMs,
|
|
1895
|
+
lastResultText: contractLines.join("\n\n"),
|
|
1896
|
+
isEvalFeedback: true,
|
|
1897
|
+
lastDiffFailed: false,
|
|
1898
|
+
contractFeedback: contractFeedbackRecord(capContractFails, currentSource)
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
if (!allowRuntimeExtension) {
|
|
1902
|
+
evalResult = {
|
|
1903
|
+
...evalResult,
|
|
1904
|
+
issues: [...evalResult.issues, ...exitProbe.probeIssues],
|
|
1905
|
+
runtimeProbe: exitProbe.meta
|
|
1906
|
+
};
|
|
1907
|
+
console.log(`[simple] eval round ${evalRoundsUsed}: max eval rounds reached \u2014 stopping`);
|
|
1908
|
+
return {
|
|
1909
|
+
control: "break",
|
|
1910
|
+
evalDone: false,
|
|
1911
|
+
evalResult,
|
|
1912
|
+
evalRoundsUsed,
|
|
1913
|
+
prevModeSubcats: updatedPrevModeSubcats,
|
|
1914
|
+
prevFailFingerprints: currFailFingerprints,
|
|
1915
|
+
preWarmedContext,
|
|
1916
|
+
evalTokens,
|
|
1917
|
+
...inLoopVisualTokens !== void 0 ? { inLoopVisualTokens } : {},
|
|
1918
|
+
evalLlmMs,
|
|
1919
|
+
lastResultText: "",
|
|
1920
|
+
isEvalFeedback: false,
|
|
1921
|
+
lastDiffFailed: false
|
|
1922
|
+
};
|
|
1923
|
+
}
|
|
1924
|
+
const runtimeFails = exitProbe.probeIssues.filter(
|
|
1925
|
+
(i) => i.result === "fail" && i.subcategory?.startsWith("runtime:")
|
|
1926
|
+
);
|
|
1927
|
+
const probeLines = runtimeFails.slice(0, MAX_FEEDBACK_ISSUES).map(formatRuntimeProbeFeedback);
|
|
1928
|
+
const enrichedIssues = [...evalResult.issues, ...exitProbe.probeIssues];
|
|
1929
|
+
evalResult = { ...evalResult, issues: enrichedIssues, runtimeProbe: exitProbe.meta };
|
|
1930
|
+
console.log(
|
|
1931
|
+
`[simple] eval round ${evalRoundsUsed}: runtime-probe fail active at cap \u2014 granting +1 retry (${maxEvalRounds + RUNTIME_EXTENSION_BONUS - evalRoundsUsed} of ${RUNTIME_EXTENSION_BONUS} bonus rounds remain)`
|
|
1932
|
+
);
|
|
1933
|
+
return {
|
|
1934
|
+
control: "feedback",
|
|
1935
|
+
evalDone: false,
|
|
1936
|
+
evalResult,
|
|
1937
|
+
evalRoundsUsed,
|
|
1938
|
+
prevModeSubcats: updatedPrevModeSubcats,
|
|
1939
|
+
prevFailFingerprints: /* @__PURE__ */ new Set([
|
|
1940
|
+
...currFailFingerprints,
|
|
1941
|
+
...runtimeFails.map(fingerprintFail)
|
|
1942
|
+
]),
|
|
1943
|
+
preWarmedContext,
|
|
1944
|
+
evalTokens,
|
|
1945
|
+
...inLoopVisualTokens !== void 0 ? { inLoopVisualTokens } : {},
|
|
1946
|
+
evalLlmMs,
|
|
1947
|
+
lastResultText: probeLines.join("\n\n"),
|
|
1948
|
+
isEvalFeedback: true,
|
|
1949
|
+
lastDiffFailed: false
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
1952
|
+
if (!costTracker.canContinue()) {
|
|
1953
|
+
console.log(
|
|
1954
|
+
`[simple] eval: budget exhausted (spent=$${costTracker.getTotal().toFixed(4)}) \u2014 stopping`
|
|
1955
|
+
);
|
|
1956
|
+
return {
|
|
1957
|
+
control: "break",
|
|
1958
|
+
evalDone: false,
|
|
1959
|
+
evalResult,
|
|
1960
|
+
evalRoundsUsed,
|
|
1961
|
+
prevModeSubcats: updatedPrevModeSubcats,
|
|
1962
|
+
prevFailFingerprints: currFailFingerprints,
|
|
1963
|
+
preWarmedContext,
|
|
1964
|
+
evalTokens,
|
|
1965
|
+
...inLoopVisualTokens !== void 0 ? { inLoopVisualTokens } : {},
|
|
1966
|
+
evalLlmMs,
|
|
1967
|
+
lastResultText: "",
|
|
1968
|
+
isEvalFeedback: false,
|
|
1969
|
+
lastDiffFailed: false
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
const allActionable = selectActionableFeedback(evalResult.issues);
|
|
1973
|
+
const issueLines = allActionable.map((issue) => {
|
|
1974
|
+
const tag = issue.result === "fail" ? "FAIL" : "WARN";
|
|
1975
|
+
return `[${tag}] ${issue.category}${issue.subcategory ? "/" + issue.subcategory : ""}: ${issue.description}
|
|
1976
|
+
Fix: ${issue.fix}`;
|
|
1977
|
+
});
|
|
1978
|
+
console.log(
|
|
1979
|
+
`[simple] eval round ${evalRoundsUsed}: ${allActionable.length} issues (${blocking.length} fail, ${warns.length} warn) \u2192 feeding back to coding loop`
|
|
1980
|
+
);
|
|
1981
|
+
for (const issue of evalResult.issues) {
|
|
1982
|
+
if (issue.tier !== 0 || issue.result !== "fail" && issue.result !== "warn") continue;
|
|
1983
|
+
console.log(
|
|
1984
|
+
`[simple] eval round ${evalRoundsUsed}: tier0 ${issue.result} ${issue.subcategory ?? issue.category} delivered=${allActionable.includes(issue)}`
|
|
1985
|
+
);
|
|
1986
|
+
}
|
|
1987
|
+
if (visualEvaluation?.actOn !== void 0) {
|
|
1988
|
+
console.log(
|
|
1989
|
+
`[simple] eval round ${evalRoundsUsed}: continuing: fails=${blocking.length}, visual majors acted=${visualMajorsActed.length} (actOn=${visualEvaluation.actOn})`
|
|
1990
|
+
);
|
|
1991
|
+
}
|
|
1992
|
+
return {
|
|
1993
|
+
control: "feedback",
|
|
1994
|
+
evalDone: false,
|
|
1995
|
+
evalResult,
|
|
1996
|
+
evalRoundsUsed,
|
|
1997
|
+
prevModeSubcats: updatedPrevModeSubcats,
|
|
1998
|
+
// DELIVERED fails only (Exp 54 P1 addendum, proposal 2): the
|
|
1999
|
+
// stuck detector's premise is "the LLM saw these and did not
|
|
2000
|
+
// resolve them" — recording fails that the MAX_FEEDBACK_ISSUES
|
|
2001
|
+
// cap cut from the feedback turns truncation into a premature
|
|
2002
|
+
// stuck-exit (the model never saw the issue it is blamed for
|
|
2003
|
+
// not fixing). An undelivered fail recurring next round now
|
|
2004
|
+
// reads as NEW, gets delivered, and earns its fix round.
|
|
2005
|
+
prevFailFingerprints: new Set(
|
|
2006
|
+
allActionable.filter((i) => i.result === "fail").map(fingerprintFail)
|
|
2007
|
+
),
|
|
2008
|
+
preWarmedContext,
|
|
2009
|
+
evalTokens,
|
|
2010
|
+
...inLoopVisualTokens !== void 0 ? { inLoopVisualTokens } : {},
|
|
2011
|
+
evalLlmMs,
|
|
2012
|
+
lastResultText: issueLines.join("\n\n"),
|
|
2013
|
+
isEvalFeedback: true,
|
|
2014
|
+
lastDiffFailed: false
|
|
2015
|
+
};
|
|
2016
|
+
} catch (e) {
|
|
2017
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
2018
|
+
console.error("[simple] eval failed:", message);
|
|
2019
|
+
evalResult = evalResult ?? {
|
|
2020
|
+
issues: [],
|
|
2021
|
+
pass: [],
|
|
2022
|
+
criteriaCoverage: notApplicableCoverage(`${EVAL_ROUND_THREW_REASON}: ${message}`),
|
|
2023
|
+
runtimeProbe: { status: "infra-skipped", reason: `${EVAL_ROUND_THREW_REASON}: ${message}` }
|
|
2024
|
+
};
|
|
2025
|
+
return {
|
|
2026
|
+
control: "break",
|
|
2027
|
+
evalDone: false,
|
|
2028
|
+
evalResult,
|
|
2029
|
+
evalRoundsUsed,
|
|
2030
|
+
prevModeSubcats,
|
|
2031
|
+
prevFailFingerprints,
|
|
2032
|
+
preWarmedContext,
|
|
2033
|
+
evalTokens,
|
|
2034
|
+
...inLoopVisualTokens !== void 0 ? { inLoopVisualTokens } : {},
|
|
2035
|
+
evalLlmMs,
|
|
2036
|
+
lastResultText: "",
|
|
2037
|
+
isEvalFeedback: false,
|
|
2038
|
+
lastDiffFailed: false
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
// src/harness/coding/generate-task-runner.ts
|
|
2044
|
+
function createTelemetry() {
|
|
2045
|
+
return {
|
|
2046
|
+
counters: {
|
|
2047
|
+
phases: { impl: 0, patch: 0, evalFix: 0, scaffold: 0, fill: 0 },
|
|
2048
|
+
outcomes: { pass: 0, patchInvalid: 0, selfCheckFail: 0, diffFail: 0 }
|
|
2049
|
+
},
|
|
2050
|
+
turnsUsed: 0,
|
|
2051
|
+
evalRoundsUsed: 0,
|
|
2052
|
+
cumulativeLlmMs: 0,
|
|
2053
|
+
cumulativeToolMs: 0,
|
|
2054
|
+
cumulativeEvalWallMs: 0,
|
|
2055
|
+
cumulativeEvalLlmMs: 0,
|
|
2056
|
+
totalIn: 0,
|
|
2057
|
+
totalOut: 0,
|
|
2058
|
+
evalResult: void 0,
|
|
2059
|
+
probeRepairUsed: false,
|
|
2060
|
+
probeRepairTrigger: void 0,
|
|
2061
|
+
contractFeedback: void 0,
|
|
2062
|
+
sameExchangeBreak: void 0,
|
|
2063
|
+
sameFailureBreak: void 0,
|
|
2064
|
+
compiledCode: "",
|
|
2065
|
+
pairedSource: "",
|
|
2066
|
+
selfCheckPassed: false,
|
|
2067
|
+
initialResultDelivered: false,
|
|
2068
|
+
codingStartedAtMs: 0,
|
|
2069
|
+
codingMs: 0
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
function absorbTokens(telemetry, usage) {
|
|
2073
|
+
telemetry.totalIn += usage.input;
|
|
2074
|
+
telemetry.totalOut += usage.output;
|
|
2075
|
+
if (usage.cacheRead !== void 0) {
|
|
2076
|
+
telemetry.cacheReadTokens = (telemetry.cacheReadTokens ?? 0) + usage.cacheRead;
|
|
2077
|
+
}
|
|
2078
|
+
if (usage.cacheCreation !== void 0) {
|
|
2079
|
+
telemetry.cacheCreationTokens = (telemetry.cacheCreationTokens ?? 0) + usage.cacheCreation;
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
function absorbInLoopVisualTokens(telemetry, part) {
|
|
2083
|
+
if (part === void 0) return;
|
|
2084
|
+
const prev = telemetry.inLoopVisualTokens;
|
|
2085
|
+
const prevCriteria = prev?.criteria;
|
|
2086
|
+
const criteria = part.criteria === void 0 ? prevCriteria : {
|
|
2087
|
+
inputTokens: (prevCriteria?.inputTokens ?? 0) + part.criteria.inputTokens,
|
|
2088
|
+
outputTokens: (prevCriteria?.outputTokens ?? 0) + part.criteria.outputTokens
|
|
2089
|
+
};
|
|
2090
|
+
telemetry.inLoopVisualTokens = {
|
|
2091
|
+
inputTokens: (prev?.inputTokens ?? 0) + part.inputTokens,
|
|
2092
|
+
outputTokens: (prev?.outputTokens ?? 0) + part.outputTokens,
|
|
2093
|
+
...criteria !== void 0 ? { criteria } : {}
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
function absorbTurn(telemetry, turn) {
|
|
2097
|
+
absorbTokens(telemetry, {
|
|
2098
|
+
input: turn.tokens.input,
|
|
2099
|
+
output: turn.tokens.output,
|
|
2100
|
+
...turn.cacheReadTokens !== void 0 ? { cacheRead: turn.cacheReadTokens } : {},
|
|
2101
|
+
...turn.cacheCreationTokens !== void 0 ? { cacheCreation: turn.cacheCreationTokens } : {}
|
|
2102
|
+
});
|
|
2103
|
+
telemetry.cumulativeLlmMs += turn.llmMs;
|
|
2104
|
+
telemetry.cumulativeToolMs += turn.toolMs;
|
|
2105
|
+
if (turn.phase) {
|
|
2106
|
+
if (turn.phase === "scaffold") telemetry.counters.phases.scaffold++;
|
|
2107
|
+
else if (turn.phase === "fill") telemetry.counters.phases.fill++;
|
|
2108
|
+
else if (turn.phase === "impl") telemetry.counters.phases.impl++;
|
|
2109
|
+
else if (turn.phase === "eval-fix") telemetry.counters.phases.evalFix++;
|
|
2110
|
+
else telemetry.counters.phases.patch++;
|
|
2111
|
+
}
|
|
2112
|
+
if (turn.outcome) {
|
|
2113
|
+
if (turn.outcome === "PASS") telemetry.counters.outcomes.pass++;
|
|
2114
|
+
else if (turn.outcome === "PATCH_INVALID") telemetry.counters.outcomes.patchInvalid++;
|
|
2115
|
+
else if (turn.outcome === "DIFF_FAIL") telemetry.counters.outcomes.diffFail++;
|
|
2116
|
+
else telemetry.counters.outcomes.selfCheckFail++;
|
|
2117
|
+
}
|
|
2118
|
+
if (turn.sameExchangeBreak !== void 0) telemetry.sameExchangeBreak = turn.sameExchangeBreak;
|
|
2119
|
+
if (turn.sameFailureBreak !== void 0) telemetry.sameFailureBreak = turn.sameFailureBreak;
|
|
2120
|
+
}
|
|
2121
|
+
function createGenerateTaskRunner(input) {
|
|
2122
|
+
const { session, params, classification, telemetry } = input;
|
|
2123
|
+
const maxTurns = input.maxTurns ?? 30;
|
|
2124
|
+
const a1Mode = session.harness.process.mode === "staged";
|
|
2125
|
+
let a1Phase = a1Mode ? "scaffold" : "post";
|
|
2126
|
+
let lastResultText = "";
|
|
2127
|
+
let lastDiffFailed = false;
|
|
2128
|
+
let isEvalFeedback = false;
|
|
2129
|
+
let iconNamesCache = null;
|
|
2130
|
+
let preWarmedContext;
|
|
2131
|
+
let prevModeSubcats = /* @__PURE__ */ new Set();
|
|
2132
|
+
let prevFailFingerprints = /* @__PURE__ */ new Set();
|
|
2133
|
+
let evalDone = false;
|
|
2134
|
+
return async function generate(_task, _ctx) {
|
|
2135
|
+
if (telemetry.codingStartedAtMs === 0) {
|
|
2136
|
+
telemetry.codingStartedAtMs = Date.now();
|
|
2137
|
+
params.onProgress?.({ type: "generating", phase: "coding" });
|
|
2138
|
+
}
|
|
2139
|
+
do {
|
|
2140
|
+
telemetry.turnsUsed++;
|
|
2141
|
+
const turn = await runCodingTurn(
|
|
2142
|
+
{
|
|
2143
|
+
workspace: session.workspace,
|
|
2144
|
+
codingAgent: session.codingAgent,
|
|
2145
|
+
codingModel: session.agents.coding.model,
|
|
2146
|
+
systemPrompt: session.systemPrompt,
|
|
2147
|
+
harness: session.harness,
|
|
2148
|
+
contract: params.contract,
|
|
2149
|
+
originalPrompt: params.userPrompt,
|
|
2150
|
+
commitMeta: session.commitMeta,
|
|
2151
|
+
originalProps: session.originalProps,
|
|
2152
|
+
// ggui#1285: the fixture the probe and judge render with, for the echo check at auto-commit.
|
|
2153
|
+
...params.fixtureProps !== void 0 ? { fixtureProps: params.fixtureProps } : {},
|
|
2154
|
+
costTracker: session.costTracker,
|
|
2155
|
+
// Thread the dispatch-resolved policy so experimental
|
|
2156
|
+
// profiles take effect on both preflight and tier-0 retry
|
|
2157
|
+
// feedback.
|
|
2158
|
+
contextPolicy: session.resolvedPolicy.context,
|
|
2159
|
+
// Dupe-break state is mutable; runCodingTurn updates it in
|
|
2160
|
+
// place (fingerprint + cooldown + forceEscape +
|
|
2161
|
+
// scopedEscapeUsedCount).
|
|
2162
|
+
dupeBreak: session.dupeBreak,
|
|
2163
|
+
...params.onCodingTurnPrompt !== void 0 ? { onPrompt: params.onCodingTurnPrompt } : {}
|
|
2164
|
+
},
|
|
2165
|
+
{
|
|
2166
|
+
turnsUsed: telemetry.turnsUsed,
|
|
2167
|
+
a1Phase,
|
|
2168
|
+
lastResultText,
|
|
2169
|
+
lastDiffFailed,
|
|
2170
|
+
isEvalFeedback,
|
|
2171
|
+
iconNamesCache,
|
|
2172
|
+
preWarmedContext,
|
|
2173
|
+
preWarmPromise: session.preWarmPromise
|
|
2174
|
+
}
|
|
2175
|
+
);
|
|
2176
|
+
absorbTurn(telemetry, turn);
|
|
2177
|
+
iconNamesCache = turn.iconNamesCache;
|
|
2178
|
+
preWarmedContext = turn.preWarmedContext;
|
|
2179
|
+
if (turn.control !== "proceed" && session.probeOnlyEnabled && telemetry.probeRepairUsed) {
|
|
2180
|
+
if (telemetry.evalResult !== void 0) {
|
|
2181
|
+
telemetry.evalResult = { ...telemetry.evalResult, runtimeProbeRepair: { attempted: true, compiled: false } };
|
|
2182
|
+
}
|
|
2183
|
+
console.log(
|
|
2184
|
+
`[simple] probe-only repair turn did not compile (${turn.outcome ?? "no tool call"}) \u2014 serving the pre-repair card`
|
|
2185
|
+
);
|
|
2186
|
+
break;
|
|
2187
|
+
}
|
|
2188
|
+
if (turn.control === "break") break;
|
|
2189
|
+
if (turn.control === "continue") {
|
|
2190
|
+
lastResultText = turn.lastResultText;
|
|
2191
|
+
lastDiffFailed = turn.lastDiffFailed;
|
|
2192
|
+
isEvalFeedback = turn.isEvalFeedback;
|
|
2193
|
+
continue;
|
|
2194
|
+
}
|
|
2195
|
+
telemetry.compiledCode = turn.compiledCode;
|
|
2196
|
+
telemetry.pairedSource = session.workspace.read() ?? "";
|
|
2197
|
+
telemetry.selfCheckPassed = turn.selfCheckPassed;
|
|
2198
|
+
if (a1Mode && a1Phase === "scaffold" && telemetry.selfCheckPassed && telemetry.compiledCode) {
|
|
2199
|
+
console.log(
|
|
2200
|
+
`[simple] A1: scaffold compiled (${telemetry.compiledCode.length}B, turns=${telemetry.turnsUsed}) \u2192 entering fill phase`
|
|
2201
|
+
);
|
|
2202
|
+
a1Phase = "fill";
|
|
2203
|
+
telemetry.selfCheckPassed = false;
|
|
2204
|
+
telemetry.compiledCode = "";
|
|
2205
|
+
telemetry.pairedSource = "";
|
|
2206
|
+
lastResultText = "";
|
|
2207
|
+
isEvalFeedback = false;
|
|
2208
|
+
continue;
|
|
2209
|
+
}
|
|
2210
|
+
if (a1Mode && a1Phase === "fill" && telemetry.selfCheckPassed) {
|
|
2211
|
+
console.log(`[simple] A1: fill completed (turns=${telemetry.turnsUsed}) \u2192 post phase`);
|
|
2212
|
+
a1Phase = "post";
|
|
2213
|
+
}
|
|
2214
|
+
if (!telemetry.initialResultDelivered && telemetry.compiledCode) {
|
|
2215
|
+
const sourceCode = session.workspace.read() ?? "";
|
|
2216
|
+
console.log(`[simple] compiled: ${telemetry.compiledCode.length}B`);
|
|
2217
|
+
if (params.onInitialResult) {
|
|
2218
|
+
await params.onInitialResult({ componentCode: telemetry.compiledCode, sourceCode });
|
|
2219
|
+
}
|
|
2220
|
+
telemetry.initialResultDelivered = true;
|
|
2221
|
+
const codingDoneMs = Date.now() - telemetry.codingStartedAtMs;
|
|
2222
|
+
console.log(
|
|
2223
|
+
`[simple] initial result delivered | ${codingDoneMs}ms | turns=${telemetry.turnsUsed} | tokens=${telemetry.totalIn + telemetry.totalOut} | compiled=${telemetry.compiledCode.length}B`
|
|
2224
|
+
);
|
|
2225
|
+
}
|
|
2226
|
+
if (!evalDone && telemetry.compiledCode && (session.tiersMod && (session.codeEvalEnabled || session.visualEvalEnabled) || session.probeOnlyEnabled)) {
|
|
2227
|
+
const evalRoundStart = Date.now();
|
|
2228
|
+
const round = await runEvalRound(
|
|
2229
|
+
{
|
|
2230
|
+
workspace: session.workspace,
|
|
2231
|
+
harness: session.harness,
|
|
2232
|
+
contract: params.contract,
|
|
2233
|
+
userPrompt: params.userPrompt,
|
|
2234
|
+
fixtureProps: params.fixtureProps,
|
|
2235
|
+
classification,
|
|
2236
|
+
evaluationAgent: session.agents.evaluation,
|
|
2237
|
+
visualEvalAgent: session.agents.visualEval,
|
|
2238
|
+
visualEvaluation: params.visualEvaluation,
|
|
2239
|
+
shellType: params.shellType,
|
|
2240
|
+
visualThreshold: session.visualThreshold,
|
|
2241
|
+
qualityMode: session.qualityMode,
|
|
2242
|
+
maxEvalRounds: session.maxEvalRounds,
|
|
2243
|
+
costTracker: session.costTracker,
|
|
2244
|
+
llmEvalMod: session.llmEvalMod,
|
|
2245
|
+
visualMod: session.visualMod,
|
|
2246
|
+
preWarmPromise: session.preWarmPromise,
|
|
2247
|
+
onProgress: params.onProgress,
|
|
2248
|
+
probeOnly: session.probeOnlyEnabled,
|
|
2249
|
+
probeRepairUsed: telemetry.probeRepairUsed,
|
|
2250
|
+
...telemetry.probeRepairTrigger !== void 0 ? { probeRepairTrigger: telemetry.probeRepairTrigger } : {}
|
|
2251
|
+
},
|
|
2252
|
+
{
|
|
2253
|
+
compiledCode: telemetry.compiledCode,
|
|
2254
|
+
evalRoundsUsed: telemetry.evalRoundsUsed,
|
|
2255
|
+
preWarmedContext,
|
|
2256
|
+
prevModeSubcats,
|
|
2257
|
+
prevFailFingerprints
|
|
2258
|
+
}
|
|
2259
|
+
);
|
|
2260
|
+
telemetry.cumulativeEvalWallMs += Date.now() - evalRoundStart;
|
|
2261
|
+
telemetry.cumulativeEvalLlmMs += round.evalLlmMs;
|
|
2262
|
+
telemetry.evalRoundsUsed = round.evalRoundsUsed;
|
|
2263
|
+
prevModeSubcats = round.prevModeSubcats;
|
|
2264
|
+
prevFailFingerprints = round.prevFailFingerprints;
|
|
2265
|
+
preWarmedContext = round.preWarmedContext;
|
|
2266
|
+
if (round.evalResult) telemetry.evalResult = round.evalResult;
|
|
2267
|
+
if (round.contractFeedback) telemetry.contractFeedback = round.contractFeedback;
|
|
2268
|
+
evalDone = round.evalDone;
|
|
2269
|
+
absorbTokens(telemetry, round.evalTokens);
|
|
2270
|
+
absorbInLoopVisualTokens(telemetry, round.inLoopVisualTokens);
|
|
2271
|
+
if (round.control === "break") break;
|
|
2272
|
+
if (session.probeOnlyEnabled) {
|
|
2273
|
+
telemetry.probeRepairUsed = true;
|
|
2274
|
+
telemetry.probeRepairTrigger = round.evalResult?.runtimeProbe;
|
|
2275
|
+
}
|
|
2276
|
+
lastResultText = round.lastResultText;
|
|
2277
|
+
isEvalFeedback = round.isEvalFeedback;
|
|
2278
|
+
lastDiffFailed = round.lastDiffFailed;
|
|
2279
|
+
} else {
|
|
2280
|
+
break;
|
|
2281
|
+
}
|
|
2282
|
+
} while (telemetry.turnsUsed < maxTurns && !evalDone);
|
|
2283
|
+
if (session.probeOnlyEnabled && telemetry.probeRepairUsed && telemetry.evalResult !== void 0 && telemetry.evalResult.runtimeProbeRepair === void 0) {
|
|
2284
|
+
console.log(`[simple] probe-only repair turn not taken \u2014 turn cap (${maxTurns}) reached before it`);
|
|
2285
|
+
}
|
|
2286
|
+
telemetry.codingMs = Date.now() - telemetry.codingStartedAtMs;
|
|
2287
|
+
if (!telemetry.compiledCode) {
|
|
2288
|
+
for (const [, meta] of session.commitMeta) {
|
|
2289
|
+
if (meta.build.success && meta.selfCheck.passed && meta.build.compiledCode) {
|
|
2290
|
+
telemetry.compiledCode = meta.build.compiledCode;
|
|
2291
|
+
telemetry.selfCheckPassed = true;
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
if (!telemetry.compiledCode) {
|
|
2295
|
+
for (const [, meta] of session.commitMeta) {
|
|
2296
|
+
if (meta.build.success && meta.build.compiledCode) {
|
|
2297
|
+
telemetry.compiledCode = meta.build.compiledCode;
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
if (telemetry.compiledCode && !telemetry.pairedSource) {
|
|
2302
|
+
telemetry.pairedSource = session.workspace.read() ?? "";
|
|
2303
|
+
}
|
|
2304
|
+
console.log(`[simple] compiled (fallback): ${telemetry.compiledCode.length}B`);
|
|
2305
|
+
}
|
|
2306
|
+
if (!telemetry.initialResultDelivered && params.onInitialResult && telemetry.compiledCode) {
|
|
2307
|
+
await params.onInitialResult({
|
|
2308
|
+
componentCode: telemetry.compiledCode,
|
|
2309
|
+
sourceCode: telemetry.pairedSource
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2312
|
+
return telemetry.pairedSource || (session.workspace.read() ?? "");
|
|
2313
|
+
};
|
|
2314
|
+
}
|
|
2315
|
+
var FILE = "ui.tsx";
|
|
2316
|
+
var DIR = "/workspace";
|
|
2317
|
+
var FILEPATH = `${DIR}/${FILE}`;
|
|
2318
|
+
var AUTHOR = { name: "ggui-agent", email: "agent@ggui.ai" };
|
|
2319
|
+
var AgentWorkspace = class {
|
|
2320
|
+
vol;
|
|
2321
|
+
fs;
|
|
2322
|
+
constructor() {
|
|
2323
|
+
this.vol = new Volume();
|
|
2324
|
+
this.fs = createFsFromVolume(this.vol);
|
|
2325
|
+
}
|
|
2326
|
+
async init() {
|
|
2327
|
+
this.fs.mkdirSync(DIR, { recursive: true });
|
|
2328
|
+
await git.init({ fs: this.fs, dir: DIR });
|
|
2329
|
+
}
|
|
2330
|
+
// ── File Operations ────────────────────────────────
|
|
2331
|
+
read() {
|
|
2332
|
+
try {
|
|
2333
|
+
return this.fs.readFileSync(FILEPATH, "utf-8");
|
|
2334
|
+
} catch {
|
|
2335
|
+
return null;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
write(code) {
|
|
2339
|
+
this.fs.writeFileSync(FILEPATH, code);
|
|
2340
|
+
}
|
|
2341
|
+
cat(startLine, endLine) {
|
|
2342
|
+
const content = this.read();
|
|
2343
|
+
if (!content) return "(no file yet \u2014 use `write` to create it)";
|
|
2344
|
+
const lines = content.split("\n");
|
|
2345
|
+
const start = (startLine ?? 1) - 1;
|
|
2346
|
+
const end = endLine ?? lines.length;
|
|
2347
|
+
const padWidth = String(end).length;
|
|
2348
|
+
return lines.slice(start, end).map((line, i) => `${String(start + i + 1).padStart(padWidth)}\u2502 ${line}`).join("\n");
|
|
2349
|
+
}
|
|
2350
|
+
grep(pattern, contextLines = 0) {
|
|
2351
|
+
const content = this.read();
|
|
2352
|
+
if (!content) return "(no file)";
|
|
2353
|
+
const lines = content.split("\n");
|
|
2354
|
+
const matchedIndices = /* @__PURE__ */ new Set();
|
|
2355
|
+
const matchLines = /* @__PURE__ */ new Set();
|
|
2356
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2357
|
+
if (new RegExp(pattern, "gi").test(lines[i])) {
|
|
2358
|
+
matchLines.add(i);
|
|
2359
|
+
for (let j = Math.max(0, i - contextLines); j <= Math.min(lines.length - 1, i + contextLines); j++) {
|
|
2360
|
+
matchedIndices.add(j);
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
if (matchedIndices.size === 0) return "(no matches)";
|
|
2365
|
+
const padWidth = String(lines.length).length;
|
|
2366
|
+
return [...matchedIndices].sort((a, b) => a - b).map((i) => {
|
|
2367
|
+
const prefix = matchLines.has(i) ? ">" : " ";
|
|
2368
|
+
return `${prefix} ${String(i + 1).padStart(padWidth)}\u2502 ${lines[i]}`;
|
|
2369
|
+
}).join("\n");
|
|
2370
|
+
}
|
|
2371
|
+
// ── Git Operations ─────────────────────────────────
|
|
2372
|
+
async stage() {
|
|
2373
|
+
await git.add({ fs: this.fs, dir: DIR, filepath: FILE });
|
|
2374
|
+
}
|
|
2375
|
+
async commit(message) {
|
|
2376
|
+
await this.stage();
|
|
2377
|
+
return git.commit({
|
|
2378
|
+
fs: this.fs,
|
|
2379
|
+
dir: DIR,
|
|
2380
|
+
message,
|
|
2381
|
+
author: AUTHOR
|
|
2382
|
+
});
|
|
2383
|
+
}
|
|
2384
|
+
async log(depth) {
|
|
2385
|
+
try {
|
|
2386
|
+
return await git.log({ fs: this.fs, dir: DIR, depth: depth ?? 20 });
|
|
2387
|
+
} catch {
|
|
2388
|
+
return [];
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
async readFileAtCommit(oid) {
|
|
2392
|
+
const { blob } = await git.readBlob({
|
|
2393
|
+
fs: this.fs,
|
|
2394
|
+
dir: DIR,
|
|
2395
|
+
oid,
|
|
2396
|
+
filepath: FILE
|
|
2397
|
+
});
|
|
2398
|
+
return new TextDecoder().decode(blob);
|
|
2399
|
+
}
|
|
2400
|
+
async checkout(oid) {
|
|
2401
|
+
const content = await this.readFileAtCommit(oid);
|
|
2402
|
+
this.write(content);
|
|
2403
|
+
}
|
|
2404
|
+
// ── Diff Operations ────────────────────────────────
|
|
2405
|
+
async diffWorking() {
|
|
2406
|
+
const commits = await this.log(1);
|
|
2407
|
+
if (commits.length === 0) return "(no commits yet \u2014 new file)";
|
|
2408
|
+
const committed = await this.readFileAtCommit(commits[0].oid);
|
|
2409
|
+
const working = this.read() ?? "";
|
|
2410
|
+
return createPatch(FILE, committed, working, "committed", "working", {
|
|
2411
|
+
context: 3
|
|
2412
|
+
});
|
|
2413
|
+
}
|
|
2414
|
+
async diffBetween(oldOid, newOid) {
|
|
2415
|
+
const oldContent = await this.readFileAtCommit(oldOid);
|
|
2416
|
+
const newContent = await this.readFileAtCommit(newOid);
|
|
2417
|
+
return createPatch(
|
|
2418
|
+
FILE,
|
|
2419
|
+
oldContent,
|
|
2420
|
+
newContent,
|
|
2421
|
+
oldOid.slice(0, 7),
|
|
2422
|
+
newOid.slice(0, 7),
|
|
2423
|
+
{ context: 3 }
|
|
2424
|
+
);
|
|
2425
|
+
}
|
|
2426
|
+
applyDiff(patch) {
|
|
2427
|
+
const current = this.read() ?? "";
|
|
2428
|
+
if (!patch.includes("@@")) {
|
|
2429
|
+
return {
|
|
2430
|
+
success: false,
|
|
2431
|
+
error: "Invalid diff format \u2014 missing @@ hunk headers. Use standard unified diff format."
|
|
2432
|
+
};
|
|
2433
|
+
}
|
|
2434
|
+
const result = applyPatch(current, patch, {
|
|
2435
|
+
fuzzFactor: 2,
|
|
2436
|
+
compareLine: (_lineNum, line, _op, patchContent) => {
|
|
2437
|
+
return line.trimEnd() === patchContent.trimEnd();
|
|
2438
|
+
}
|
|
2439
|
+
});
|
|
2440
|
+
if (result === false) {
|
|
2441
|
+
return {
|
|
2442
|
+
success: false,
|
|
2443
|
+
error: "Patch failed to apply \u2014 context lines in your diff do not match the current file. Re-read the current file provided in the prompt and produce a corrected diff."
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
this.write(result);
|
|
2447
|
+
return { success: true };
|
|
2448
|
+
}
|
|
2449
|
+
};
|
|
2450
|
+
|
|
2451
|
+
// src/harness/in-loop-evaluation.ts
|
|
2452
|
+
function inLoopEvaluation(options) {
|
|
2453
|
+
return {
|
|
2454
|
+
text: options.evaluation?.enabled === true,
|
|
2455
|
+
visual: options.visualEvaluation?.enabled === true || options.qualityConfig?.visualEval === true
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
function runsInLoopEvaluation(options) {
|
|
2459
|
+
const legs = inLoopEvaluation(options);
|
|
2460
|
+
return legs.text || legs.visual;
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
// src/harness/coding/init-session.ts
|
|
2464
|
+
function resolveSessionAgents(config) {
|
|
2465
|
+
const coding = config.codingAgent;
|
|
2466
|
+
const evaluation = config.evaluationAgent ?? coding;
|
|
2467
|
+
const visualEval = config.visualEvalAgent ?? evaluation;
|
|
2468
|
+
return { coding, evaluation, visualEval };
|
|
2469
|
+
}
|
|
2470
|
+
async function initSession(input) {
|
|
2471
|
+
const { harness, params, agents } = input;
|
|
2472
|
+
const resolvedPolicy = input.resolvedPolicy ?? harness.policy ?? DEFAULT_HARNESS_POLICY;
|
|
2473
|
+
const startedAtMs = Date.now();
|
|
2474
|
+
const boilerplate = harness.what.boilerplate;
|
|
2475
|
+
console.log(
|
|
2476
|
+
`[simple] boilerplate: ${boilerplate.split("\n").length} lines, shell=${params.shellType ?? "fullscreen"}, screen=${params.screen ?? "universal"}`
|
|
2477
|
+
);
|
|
2478
|
+
const workspace = new AgentWorkspace();
|
|
2479
|
+
await workspace.init();
|
|
2480
|
+
const commitMeta = /* @__PURE__ */ new Map();
|
|
2481
|
+
workspace.write(boilerplate);
|
|
2482
|
+
await workspace.stage();
|
|
2483
|
+
await workspace.commit("scaffold: boilerplate");
|
|
2484
|
+
const systemPrompt = input.systemPromptOverride ?? harness.how.systemPrompt;
|
|
2485
|
+
const codingAgent = createAgent(agents.coding);
|
|
2486
|
+
await codingAgent.warmCache(
|
|
2487
|
+
agents.coding.model,
|
|
2488
|
+
systemPrompt,
|
|
2489
|
+
[APPLY_CHANGES_TOOL, GET_ICONS_TOOL],
|
|
2490
|
+
"required"
|
|
2491
|
+
);
|
|
2492
|
+
const { text: codeEvalEnabled, visual: visualEvalEnabled } = inLoopEvaluation(params);
|
|
2493
|
+
let preWarmPromise;
|
|
2494
|
+
if (codeEvalEnabled) {
|
|
2495
|
+
const evalSpec = agents.evaluation;
|
|
2496
|
+
preWarmPromise = import('./llm-evaluator-PN3WFCS4.js').then(
|
|
2497
|
+
(mod) => mod.preWarmEval(
|
|
2498
|
+
{
|
|
2499
|
+
sourceCode: "",
|
|
2500
|
+
originalPrompt: params.userPrompt,
|
|
2501
|
+
contract: params.contract,
|
|
2502
|
+
designContext: void 0,
|
|
2503
|
+
designMode: harness.designMode,
|
|
2504
|
+
profile: harness.profile
|
|
2505
|
+
},
|
|
2506
|
+
{
|
|
2507
|
+
provider: mapProviderForEvaluator(evalSpec.provider),
|
|
2508
|
+
model: evalSpec.model
|
|
2509
|
+
}
|
|
2510
|
+
)
|
|
2511
|
+
).catch((e) => {
|
|
2512
|
+
console.warn(`[simple] eval pre-warm failed: ${e instanceof Error ? e.message : e}`);
|
|
2513
|
+
return null;
|
|
2514
|
+
});
|
|
2515
|
+
}
|
|
2516
|
+
const propsMatch = boilerplate.match(/interface Props \{[\s\S]*?\n\}/);
|
|
2517
|
+
const originalProps = propsMatch?.[0];
|
|
2518
|
+
const qualityConfig = params.qualityConfig;
|
|
2519
|
+
const qualityMode = qualityConfig?.quality ?? "fast";
|
|
2520
|
+
const probeOnlyEnabled = harness.check.runtimeRender !== void 0 && !codeEvalEnabled && !visualEvalEnabled;
|
|
2521
|
+
const maxEvalRounds = qualityMode === "high-quality" ? params.evaluation?.maxRounds ?? 3 : params.evaluation?.maxRounds ?? 2;
|
|
2522
|
+
const visualThreshold = params.visualEvaluation?.passThreshold ?? 70;
|
|
2523
|
+
let tiersMod = null;
|
|
2524
|
+
let llmEvalMod = null;
|
|
2525
|
+
let visualMod = null;
|
|
2526
|
+
let costTracker = null;
|
|
2527
|
+
if (codeEvalEnabled || visualEvalEnabled) {
|
|
2528
|
+
tiersMod = await import('./check/index.js');
|
|
2529
|
+
const costTrackerMod = await import('./cost-tracker-HHGQ4FTW.js');
|
|
2530
|
+
costTracker = new costTrackerMod.CostTracker(
|
|
2531
|
+
qualityConfig?.maxCostPerGeneration ?? null
|
|
2532
|
+
);
|
|
2533
|
+
if (codeEvalEnabled) {
|
|
2534
|
+
llmEvalMod = await import('./llm-evaluator-PN3WFCS4.js');
|
|
2535
|
+
}
|
|
2536
|
+
if (visualEvalEnabled) {
|
|
2537
|
+
visualMod = await import('./visual-evaluator-TGEXYQTP.js').catch(() => null);
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
return {
|
|
2541
|
+
harness,
|
|
2542
|
+
workspace,
|
|
2543
|
+
codingAgent,
|
|
2544
|
+
agents,
|
|
2545
|
+
commitMeta,
|
|
2546
|
+
boilerplate,
|
|
2547
|
+
originalProps,
|
|
2548
|
+
systemPrompt,
|
|
2549
|
+
codeEvalEnabled,
|
|
2550
|
+
visualEvalEnabled,
|
|
2551
|
+
probeOnlyEnabled,
|
|
2552
|
+
maxEvalRounds,
|
|
2553
|
+
visualThreshold,
|
|
2554
|
+
qualityMode,
|
|
2555
|
+
tiersMod,
|
|
2556
|
+
llmEvalMod,
|
|
2557
|
+
visualMod,
|
|
2558
|
+
costTracker,
|
|
2559
|
+
preWarmPromise,
|
|
2560
|
+
resolvedPolicy,
|
|
2561
|
+
dupeBreak: createDupeBreakState(),
|
|
2562
|
+
startedAtMs
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
// src/adapters/generation-dispatch.ts
|
|
2567
|
+
function mapProviderForLLMRouter(provider) {
|
|
2568
|
+
return provider === "claude" ? "anthropic" : provider;
|
|
2569
|
+
}
|
|
2570
|
+
async function dispatchGeneration(params) {
|
|
2571
|
+
if ((params.enableRuntimeRender ?? true) === true) {
|
|
2572
|
+
void warmupRuntimeRenderProbe().catch(() => {
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2575
|
+
const routerProvider = mapProviderForLLMRouter(params.provider);
|
|
2576
|
+
const resolvedModel = params.models?.coding ?? params.models?.default ?? params.model;
|
|
2577
|
+
const codingAgent = {
|
|
2578
|
+
provider: routerProvider,
|
|
2579
|
+
model: resolvedModel,
|
|
2580
|
+
routeOverride: params.routeOverride,
|
|
2581
|
+
onRetry: params.onRetry
|
|
2582
|
+
};
|
|
2583
|
+
const evaluationAgent = params.models?.evaluation ? {
|
|
2584
|
+
provider: routerProvider,
|
|
2585
|
+
model: params.models.evaluation,
|
|
2586
|
+
routeOverride: params.routeOverride,
|
|
2587
|
+
onRetry: params.onRetry
|
|
2588
|
+
} : void 0;
|
|
2589
|
+
const visualEvalAgent = params.visualEvalAgent ? {
|
|
2590
|
+
provider: params.visualEvalAgent.provider,
|
|
2591
|
+
model: params.visualEvalAgent.model,
|
|
2592
|
+
...params.visualEvalAgent.apiKey !== void 0 ? { routeOverride: { apiKey: params.visualEvalAgent.apiKey } } : {},
|
|
2593
|
+
onRetry: params.onRetry
|
|
2594
|
+
} : void 0;
|
|
2595
|
+
const effectiveContract = params.contract;
|
|
2596
|
+
const sessionParams = {
|
|
2597
|
+
userPrompt: params.originalPrompt ?? params.userPrompt,
|
|
2598
|
+
contract: effectiveContract,
|
|
2599
|
+
shellType: params.shellType,
|
|
2600
|
+
screen: params.screen,
|
|
2601
|
+
evaluation: params.evaluation ? {
|
|
2602
|
+
enabled: params.evaluation.enabled,
|
|
2603
|
+
passThreshold: params.evaluation.passThreshold,
|
|
2604
|
+
maxRounds: params.evaluation.maxRounds ?? params.maxEvalRounds
|
|
2605
|
+
} : void 0,
|
|
2606
|
+
visualEvaluation: params.visualEvaluation,
|
|
2607
|
+
qualityConfig: params.qualityConfig,
|
|
2608
|
+
fixtureProps: params.fixtureProps,
|
|
2609
|
+
...params.onCodingTurnPrompt !== void 0 ? { onCodingTurnPrompt: params.onCodingTurnPrompt } : {}
|
|
2610
|
+
};
|
|
2611
|
+
const classification = classifyAxes({
|
|
2612
|
+
contract: effectiveContract ?? {},
|
|
2613
|
+
prompt: sessionParams.userPrompt
|
|
2614
|
+
});
|
|
2615
|
+
const harness = createHarness({
|
|
2616
|
+
classification,
|
|
2617
|
+
contract: effectiveContract ?? {},
|
|
2618
|
+
prompt: sessionParams.userPrompt,
|
|
2619
|
+
shellType: sessionParams.shellType,
|
|
2620
|
+
screen: sessionParams.screen,
|
|
2621
|
+
// Forward the registered catalog into createHarness so
|
|
2622
|
+
// `generateBoilerplate` emits correct `import { useLeafletMap }
|
|
2623
|
+
// from '<package>'` lines for thin contract refs. The closure
|
|
2624
|
+
// capture below (systemPromptBuilder) wires the SAME catalog
|
|
2625
|
+
// into the HOW leg.
|
|
2626
|
+
...params.appGadgets !== void 0 ? { appGadgets: params.appGadgets } : {},
|
|
2627
|
+
// Forward the third-party wrapper `.d.ts` map so
|
|
2628
|
+
// `WhatLeg.gadgetTypes` reaches the coding-agent's typecheck
|
|
2629
|
+
// overlay (autoCommit → runTier0Checks → typecheck). The
|
|
2630
|
+
// systemPromptBuilder closure below also captures `gadgetTypes`
|
|
2631
|
+
// from `params` directly for the prompt `Type:` lines.
|
|
2632
|
+
...params.gadgetTypes !== void 0 ? { gadgetTypes: params.gadgetTypes } : {},
|
|
2633
|
+
// Design mode + canvas — the harness carries them to every leg.
|
|
2634
|
+
...params.designMode !== void 0 ? { designMode: params.designMode } : {},
|
|
2635
|
+
// ggui#1117 — the canvas is ALWAYS carried: a stated one, else the class
|
|
2636
|
+
// shell × screen renders on. The axis checks judge for it
|
|
2637
|
+
// (`universal.root_width_cap` stands down without one — which is what every
|
|
2638
|
+
// served generation got, since the serving runtime states no canvas); the
|
|
2639
|
+
// constrained prompt ignores it (INVARIANT 1, `design-mode.pin.test.ts`),
|
|
2640
|
+
// so the prompt digests do not move; the free prompt renders it as before.
|
|
2641
|
+
canvas: params.canvas ?? canvasForRendering(params.shellType, params.screen),
|
|
2642
|
+
...params.profile !== void 0 ? { profile: params.profile } : {},
|
|
2643
|
+
// Policy is pre-resolved at the dispatch boundary so `createHarness`
|
|
2644
|
+
// stays free of experiment plumbing. `resolveHarnessPolicy` applies
|
|
2645
|
+
// the `GGUI_POLICY_PROFILE` experimental-profile layer and hands the
|
|
2646
|
+
// result to the constructor.
|
|
2647
|
+
policy: resolveHarnessPolicy(classification),
|
|
2648
|
+
// Fully-populated system-prompt builder. This `buildSystemPrompt`
|
|
2649
|
+
// wrapper pre-fills pitfalls + design-system docs + primitives +
|
|
2650
|
+
// wire content.
|
|
2651
|
+
//
|
|
2652
|
+
// `appGadgets` is captured from `params` at dispatch time and
|
|
2653
|
+
// threaded into the wrapper so the code-gen prompt's
|
|
2654
|
+
// `clientCapabilities — registered catalog` section renders the
|
|
2655
|
+
// operator-registered gadget set (Leaflet, Mapbox, …) instead of
|
|
2656
|
+
// falling back to STDLIB. When omitted, the wrapper defaults to
|
|
2657
|
+
// STDLIB-only.
|
|
2658
|
+
// `params.gadgetTypes` is captured into the closure so the code-gen
|
|
2659
|
+
// prompt's gadget catalog renders a `Type:` line per third-party
|
|
2660
|
+
// gadget (the LLM sees a wrapper's real call shape it otherwise
|
|
2661
|
+
// can't know).
|
|
2662
|
+
systemPromptBuilder: ({
|
|
2663
|
+
userRequest,
|
|
2664
|
+
shellType,
|
|
2665
|
+
screen,
|
|
2666
|
+
axisDelta,
|
|
2667
|
+
designMode,
|
|
2668
|
+
canvas,
|
|
2669
|
+
profile
|
|
2670
|
+
}) => buildSystemPrompt(
|
|
2671
|
+
userRequest,
|
|
2672
|
+
shellType,
|
|
2673
|
+
screen,
|
|
2674
|
+
axisDelta,
|
|
2675
|
+
params.appGadgets,
|
|
2676
|
+
params.gadgetTypes,
|
|
2677
|
+
designMode,
|
|
2678
|
+
canvas,
|
|
2679
|
+
profile
|
|
2680
|
+
),
|
|
2681
|
+
// Pre-filtered axis-check registry. The `matches()` predicate
|
|
2682
|
+
// selects the checks relevant to this generation's axis vector.
|
|
2683
|
+
axisChecks: REGISTRY.filter(
|
|
2684
|
+
(check) => matches(classification.vector, check)
|
|
2685
|
+
),
|
|
2686
|
+
// The runtime-render check, built under the caller's probe config.
|
|
2687
|
+
// Heavy deps (happy-dom + wire + design) stay on the dispatch side of
|
|
2688
|
+
// the boundary; the skeleton createHarness defaults this to undefined.
|
|
2689
|
+
//
|
|
2690
|
+
// Gated behind `enableRuntimeRender` (default true) so
|
|
2691
|
+
// benchmark/dev callers can opt out of in-loop probe execution and
|
|
2692
|
+
// run the probe externally as a final post-generation check.
|
|
2693
|
+
runtimeRender: params.enableRuntimeRender ?? true ? params.runtimeRender ?? DEFAULT_RUNTIME_RENDER_CHECK : void 0
|
|
2694
|
+
});
|
|
2695
|
+
console.log(
|
|
2696
|
+
`[harness] id=${harness.id} name=${harness.name} workflow=${harness.process.workflow.name} overrides=${harness.meta.overrides.join(",") || "none"}`
|
|
2697
|
+
);
|
|
2698
|
+
console.log(
|
|
2699
|
+
`[simple] axis-vector: ${JSON.stringify(classification.vector)} risk=${classification.riskTier}`
|
|
2700
|
+
);
|
|
2701
|
+
if (harness.process.workflow.name === "staged") {
|
|
2702
|
+
throw new Error(
|
|
2703
|
+
`[dispatch] STAGED workflow routed but architect/coder TaskRunners are not wired on the dispatch path. This is a misconfiguration (probably pickProcessMode returning WORKFLOWS.staged). For staged-like behaviour, set process.mode="staged" + workflow.name="single_pass" instead \u2014 the A1 scaffold\u2192fill phase machine lives inside the generate runner and fires without the workflow topology. See ralph #3 report (2026-04-14) for the full rationale.`
|
|
2704
|
+
);
|
|
2705
|
+
}
|
|
2706
|
+
const agents = resolveSessionAgents({
|
|
2707
|
+
codingAgent,
|
|
2708
|
+
evaluationAgent,
|
|
2709
|
+
...visualEvalAgent !== void 0 ? { visualEvalAgent } : {}
|
|
2710
|
+
});
|
|
2711
|
+
const { resolveRunPolicyForProfile } = await import('./policy-VCYKGSAC.js');
|
|
2712
|
+
const resolvedPolicy = resolveRunPolicyForProfile(process.env.GGUI_POLICY_PROFILE, harness, {
|
|
2713
|
+
provider: routerProvider,
|
|
2714
|
+
modelId: resolvedModel
|
|
2715
|
+
});
|
|
2716
|
+
let systemPromptOverride;
|
|
2717
|
+
{
|
|
2718
|
+
const { PRIMITIVES_DOCUMENTATION: PRIMITIVES_DOCUMENTATION2 } = await import('./validation/index.js');
|
|
2719
|
+
const fullBytes = Buffer.byteLength(PRIMITIVES_DOCUMENTATION2, "utf8");
|
|
2720
|
+
const docFormat = resolvedPolicy.context.primitiveDocFormat ?? "markdown";
|
|
2721
|
+
const indexMode = resolvedPolicy.context.primitiveIndex ?? "off";
|
|
2722
|
+
const mode = resolvedPolicy.context.primitiveDocSlice ?? "full";
|
|
2723
|
+
let doc = PRIMITIVES_DOCUMENTATION2;
|
|
2724
|
+
if (docFormat === "ts" && indexMode === "off") {
|
|
2725
|
+
const { PRIMITIVES_DOCUMENTATION_TS } = await import('./tools/get-primitives-ts.js');
|
|
2726
|
+
doc = PRIMITIVES_DOCUMENTATION_TS;
|
|
2727
|
+
const tsBytes = Buffer.byteLength(doc, "utf8");
|
|
2728
|
+
console.log(
|
|
2729
|
+
`[simple] primitive-doc-ts-bytes=${tsBytes} baseline=${fullBytes} drop=${((1 - tsBytes / fullBytes) * 100).toFixed(1)}%`
|
|
2730
|
+
);
|
|
2731
|
+
}
|
|
2732
|
+
const formatBytes = Buffer.byteLength(doc, "utf8");
|
|
2733
|
+
if (mode === "axis-keyed" || mode === "axis-keyed+contract") {
|
|
2734
|
+
const {
|
|
2735
|
+
computePrimitiveAllowlist,
|
|
2736
|
+
computeContractPrimitives,
|
|
2737
|
+
slicePrimitiveDocumentation,
|
|
2738
|
+
slicePrimitiveDocumentationTs
|
|
2739
|
+
} = await import('./primitive-slice-WTMBLYFJ.js');
|
|
2740
|
+
let allowlist = computePrimitiveAllowlist(classification);
|
|
2741
|
+
if (mode === "axis-keyed+contract") {
|
|
2742
|
+
const fromContract = computeContractPrimitives(effectiveContract);
|
|
2743
|
+
allowlist = [.../* @__PURE__ */ new Set([...allowlist, ...fromContract])].sort();
|
|
2744
|
+
}
|
|
2745
|
+
const excludes = resolvedPolicy.context.primitiveDocExcludes ?? [];
|
|
2746
|
+
if (excludes.length > 0) {
|
|
2747
|
+
const excludeSet = new Set(excludes);
|
|
2748
|
+
allowlist = allowlist.filter((p) => !excludeSet.has(p));
|
|
2749
|
+
}
|
|
2750
|
+
doc = docFormat === "ts" && indexMode === "off" ? slicePrimitiveDocumentationTs(doc, allowlist) : slicePrimitiveDocumentation(doc, allowlist);
|
|
2751
|
+
const slicedBytes = Buffer.byteLength(doc, "utf8");
|
|
2752
|
+
const excludeTag = excludes.length ? ` excludes=${excludes.join(",")}` : "";
|
|
2753
|
+
console.log(
|
|
2754
|
+
`[simple] primitive-doc-bytes=${slicedBytes} mode=${mode} baseline=${formatBytes} drop=${((1 - slicedBytes / formatBytes) * 100).toFixed(1)}% allowlist=${allowlist.length}/${allowlist.join(",")}${excludeTag}`
|
|
2755
|
+
);
|
|
2756
|
+
} else {
|
|
2757
|
+
console.log(
|
|
2758
|
+
`[simple] primitive-doc-bytes=${formatBytes} mode=full baseline=${formatBytes} drop=0.0%`
|
|
2759
|
+
);
|
|
2760
|
+
}
|
|
2761
|
+
if (indexMode !== "off") {
|
|
2762
|
+
const { buildPrimitiveIndex } = await import('./primitive-index-2OF6F4TZ.js');
|
|
2763
|
+
doc = buildPrimitiveIndex(PRIMITIVES_DOCUMENTATION2, indexMode);
|
|
2764
|
+
const indexBytes = Buffer.byteLength(doc, "utf8");
|
|
2765
|
+
console.log(
|
|
2766
|
+
`[simple] primitive-index-bytes=${indexBytes} mode=${indexMode} baseline=${fullBytes} drop=${((1 - indexBytes / fullBytes) * 100).toFixed(1)}%`
|
|
2767
|
+
);
|
|
2768
|
+
}
|
|
2769
|
+
if (doc !== PRIMITIVES_DOCUMENTATION2) {
|
|
2770
|
+
systemPromptOverride = harness.how.systemPrompt.replace(PRIMITIVES_DOCUMENTATION2, doc);
|
|
2771
|
+
}
|
|
2772
|
+
const sentPrompt = systemPromptOverride ?? harness.how.systemPrompt;
|
|
2773
|
+
console.log(
|
|
2774
|
+
`[simple] system-prompt-bytes=${Buffer.byteLength(sentPrompt, "utf8")} doc-format=${docFormat === "ts" && indexMode === "off" ? "ts" : docFormat} doc-bytes=${Buffer.byteLength(doc, "utf8")} mode=${indexMode !== "off" ? `index:${indexMode}` : mode}`
|
|
2775
|
+
);
|
|
2776
|
+
if (resolvedPolicy.context.planFirstTurn) {
|
|
2777
|
+
const phaseGuidance = `
|
|
2778
|
+
|
|
2779
|
+
## Generation Phases
|
|
2780
|
+
|
|
2781
|
+
This generation runs in TWO phases:
|
|
2782
|
+
|
|
2783
|
+
**Turn 1 \u2014 PLAN.** The harness only advertises the \`write_plan\` tool. Commit to:
|
|
2784
|
+
\u2022 \`components\`: which primitives/components you will use (from the reference above).
|
|
2785
|
+
\u2022 \`structure\`: a brief JSX outline showing nesting.
|
|
2786
|
+
\u2022 \`wiring\`: state/actions/streams you will use.
|
|
2787
|
+
Do NOT attempt to write code \u2014 \`apply_changes\` is not available on turn 1.
|
|
2788
|
+
|
|
2789
|
+
**Turn 2+ \u2014 EXECUTE.** Now write code via \`apply_changes\`. If your patches accumulate into a tangled broken state, \`rewrite\` is available as an escape. There is no fetch tool \u2014 the component reference above is complete.
|
|
2790
|
+
|
|
2791
|
+
Your plan is a commitment, not a design doc. Keep it tight. Do not over-plan; just enough to pick components and sketch structure before writing.`;
|
|
2792
|
+
systemPromptOverride = (systemPromptOverride ?? harness.how.systemPrompt) + phaseGuidance;
|
|
2793
|
+
console.log(`[simple] plan-first-turn=on`);
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
const session = await initSession({
|
|
2797
|
+
harness,
|
|
2798
|
+
params: sessionParams,
|
|
2799
|
+
agents,
|
|
2800
|
+
resolvedPolicy,
|
|
2801
|
+
systemPromptOverride
|
|
2802
|
+
});
|
|
2803
|
+
const telemetry = createTelemetry();
|
|
2804
|
+
const generate = createGenerateTaskRunner({
|
|
2805
|
+
session,
|
|
2806
|
+
params: sessionParams,
|
|
2807
|
+
classification,
|
|
2808
|
+
telemetry,
|
|
2809
|
+
maxTurns: params.maxAttempts ?? 8
|
|
2810
|
+
});
|
|
2811
|
+
const runResult = await runHarness({
|
|
2812
|
+
harness,
|
|
2813
|
+
prompt: sessionParams.userPrompt,
|
|
2814
|
+
contract: params.contract ?? { intent: "" },
|
|
2815
|
+
taskRunners: { generate },
|
|
2816
|
+
compile: async () => telemetry.compiledCode || null,
|
|
2817
|
+
passes: () => true,
|
|
2818
|
+
skipCheck: true,
|
|
2819
|
+
maxIterations: 1
|
|
2820
|
+
});
|
|
2821
|
+
if (!runResult.ok) {
|
|
2822
|
+
console.error(
|
|
2823
|
+
`[dispatch] runHarness terminated: ${runResult.reason} (source=${runResult.finalSource ? runResult.finalSource.length + "B" : "null"}, compiled=${runResult.finalCompiled ? runResult.finalCompiled.length + "B" : "null"})`
|
|
2824
|
+
);
|
|
2825
|
+
}
|
|
2826
|
+
const finalSource = runResult.finalSource ?? "";
|
|
2827
|
+
const result = await assembleGenerationResult({
|
|
2828
|
+
session,
|
|
2829
|
+
telemetry,
|
|
2830
|
+
source: finalSource
|
|
2831
|
+
});
|
|
2832
|
+
return withArmRecord(result, params);
|
|
2833
|
+
}
|
|
2834
|
+
function withArmRecord(result, params) {
|
|
2835
|
+
const recordedCanvas = params.canvas ?? (params.designMode === "free" ? canvasForRendering(params.shellType, params.screen) : void 0);
|
|
2836
|
+
return {
|
|
2837
|
+
...result,
|
|
2838
|
+
...params.designMode !== void 0 ? { designMode: params.designMode } : {},
|
|
2839
|
+
...recordedCanvas !== void 0 ? { canvas: recordedCanvas } : {}
|
|
2840
|
+
};
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
export { dispatchGeneration, inLoopEvaluation, runsInLoopEvaluation };
|
|
2844
|
+
//# sourceMappingURL=chunk-R5GIGPW2.js.map
|
|
2845
|
+
//# sourceMappingURL=chunk-R5GIGPW2.js.map
|