@ggui-ai/ui-gen 0.26.0 → 0.28.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-BJwn74rx.d.ts → adapter-h4_V69LI.d.ts} +1 -1
- package/dist/adapters/base.d.ts +6 -1
- package/dist/adapters/base.js.map +1 -1
- package/dist/adapters/generation-dispatch.d.ts +2 -2
- package/dist/adapters/generation-dispatch.js +233 -17
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +36 -14
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +4 -4
- package/dist/advanced/index.js +268 -28
- package/dist/advanced/index.js.map +1 -1
- package/dist/boilerplate.js +4 -6
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +1 -7
- package/dist/check/index.js +50 -4
- package/dist/check/index.js.map +1 -1
- package/dist/{create-ui-generator-N4gRXpcL.d.ts → create-ui-generator-BrZHF0cf.d.ts} +2 -2
- package/dist/{dispatch-BUoXRj_r.d.ts → dispatch-B07TI30d.d.ts} +1 -1
- package/dist/evaluation/axis-checks/index.d.ts +3 -3
- package/dist/evaluation/axis-checks/index.js +97 -1
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +1 -1
- package/dist/evaluation/axis-checks/registry.js +97 -1
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +22 -12
- package/dist/evaluation/index.js +138 -14
- package/dist/evaluation/index.js.map +1 -1
- package/dist/evaluation/types.d.ts +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +3 -3
- package/dist/harness/index.d.ts +1 -1
- package/dist/harness/index.js +6 -6
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/prompts.js +1 -4
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +14 -3
- package/dist/harness/result-types.js.map +1 -1
- package/dist/harness/runtime.d.ts +1 -1
- package/dist/harness/runtime.js +6 -7
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +1 -1
- package/dist/harness/types-public.js +4 -6
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.js +268 -28
- package/dist/index.js.map +1 -1
- package/dist/patch.d.ts +1 -1
- package/dist/{types-public-B7wn4ce5.d.ts → types-public-h8IMnYuh.d.ts} +4 -0
- package/dist/{visual-evaluator-C9L-o6ap.d.ts → visual-evaluator-C2strxqD.d.ts} +1 -1
- package/dist/workflows.d.ts +1 -1
- package/package.json +13 -13
- package/src/adapters/base.ts +6 -1
- package/src/adapters/claude/message-parser.ts +5 -6
- package/src/adapters/claude/sdk-tool-pins.ts +44 -0
- package/src/adapters/claude/sdk.ts +3 -3
- package/src/adapters/claude/source-capture.ts +20 -0
- package/src/adapters/provider-router.ts +1 -0
- package/src/boilerplate/system-prompt.ts +3 -2
- package/src/check/run-tier0.ts +61 -0
- package/src/create-ui-generator.ts +4 -2
- package/src/design-system-docs.ts +2 -1
- package/src/evaluation/axis-checks/checks/state-merge.ts +1 -1
- package/src/evaluation/axis-checks/helpers.ts +125 -0
- package/src/evaluation/criteria/resolve.ts +19 -1
- package/src/evaluation/evaluator.ts +2 -2
- package/src/evaluation/llm-evaluator.ts +2 -2
- package/src/evaluation/loop.ts +25 -4
- package/src/evaluation/message-parsing.ts +11 -12
- package/src/evaluation/types-public.ts +4 -0
- package/src/evaluation/visual-evaluator.ts +1 -1
- package/src/harness/coding/assemble-result.ts +1 -0
- package/src/harness/coding/dupe-break.ts +70 -0
- package/src/harness/coding/generate-task-runner.ts +5 -1
- package/src/harness/coding/run-coding-turn.ts +56 -1
- package/src/harness/result-types.ts +12 -0
- package/src/index.ts +1 -1
- package/src/taught-tokens.ts +5 -6
package/src/evaluation/loop.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// packages/ui-gen/src/evaluation/loop.ts
|
|
2
2
|
|
|
3
3
|
import { query, type McpServerConfig } from '@anthropic-ai/claude-agent-sdk';
|
|
4
|
+
import { sdkToolPins } from '../adapters/claude/sdk-tool-pins';
|
|
4
5
|
import { runEvaluation } from './evaluator';
|
|
5
6
|
import {
|
|
6
7
|
extractCompiledCodeFromMessage,
|
|
@@ -39,8 +40,13 @@ export interface EvaluationLoopOptions {
|
|
|
39
40
|
cwd?: string;
|
|
40
41
|
/** MCP servers available to the fix agent */
|
|
41
42
|
mcpServers?: Record<string, McpServerConfig>;
|
|
42
|
-
/**
|
|
43
|
+
/** Tool names to approve. Absent → every tool of every server in `mcpServers` (ggui#1714). */
|
|
43
44
|
allowedTools?: string[];
|
|
45
|
+
/**
|
|
46
|
+
* CLI built-in tools to offer the fix agent (e.g. `['Write']`). Default: none.
|
|
47
|
+
* An explicit opt-in: a built-in that writes or executes acts on the host (ggui#1714).
|
|
48
|
+
*/
|
|
49
|
+
builtinTools?: readonly string[];
|
|
44
50
|
/** LLM model for fix rounds */
|
|
45
51
|
model?: string;
|
|
46
52
|
/** Environment variables (includes BYOK credentials) */
|
|
@@ -90,6 +96,18 @@ export async function runEvaluationLoop(
|
|
|
90
96
|
let round = 0;
|
|
91
97
|
const maxRounds = Math.min(config.maxRounds ?? 3, MAX_EVAL_ROUNDS_HARD_LIMIT);
|
|
92
98
|
|
|
99
|
+
// ggui#1714 — a fix round runs with no CLI built-ins unless asked for. With no
|
|
100
|
+
// MCP server and no built-in either, the fix agent has nothing that can return
|
|
101
|
+
// code, and every fix round would silently hand back the original: refuse.
|
|
102
|
+
const offersATool =
|
|
103
|
+
Object.keys(generatorOptions?.mcpServers ?? {}).length > 0 || (generatorOptions?.builtinTools?.length ?? 0) > 0;
|
|
104
|
+
if (maxRounds > 1 && !offersATool) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
'runEvaluationLoop: a fix round needs a tool that can return code — pass generatorOptions.mcpServers ' +
|
|
107
|
+
"(e.g. a server with compile_component) or opt into a built-in with generatorOptions.builtinTools (e.g. ['Write']).",
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
93
111
|
while (round < maxRounds) {
|
|
94
112
|
round++;
|
|
95
113
|
|
|
@@ -145,12 +163,15 @@ export async function runEvaluationLoop(
|
|
|
145
163
|
maxTurns: 15,
|
|
146
164
|
maxBudgetUsd: config.maxBudgetPerFix,
|
|
147
165
|
env,
|
|
148
|
-
permissionMode: 'bypassPermissions',
|
|
149
|
-
allowDangerouslySkipPermissions: true,
|
|
150
166
|
...(cliPath && { pathToClaudeCodeExecutable: cliPath }),
|
|
151
167
|
...(generatorOptions?.cwd && { cwd: generatorOptions.cwd }),
|
|
152
168
|
...(generatorOptions?.mcpServers && { mcpServers: generatorOptions.mcpServers }),
|
|
153
|
-
|
|
169
|
+
// ggui#1714 — no CLI built-ins unless asked for, no host settings, no permission bypass.
|
|
170
|
+
...sdkToolPins({
|
|
171
|
+
mcpServers: generatorOptions?.mcpServers,
|
|
172
|
+
allowedTools: generatorOptions?.allowedTools,
|
|
173
|
+
builtinTools: generatorOptions?.builtinTools,
|
|
174
|
+
}),
|
|
154
175
|
...(generatorOptions?.model && { model: generatorOptions.model }),
|
|
155
176
|
...(generatorOptions?.stderr && { stderr: generatorOptions.stderr }),
|
|
156
177
|
},
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { EvaluationResult } from './types';
|
|
13
|
+
import { sourceCodeFromToolUse } from '../adapters/claude/source-capture';
|
|
13
14
|
|
|
14
15
|
// ── SDK message type aliases ────────────────────────────────────────
|
|
15
16
|
|
|
@@ -137,13 +138,14 @@ export function extractCompiledCodeFromMessage(message: SdkMessage): string | un
|
|
|
137
138
|
// ── sourceCode extraction ───────────────────────────────────────────
|
|
138
139
|
|
|
139
140
|
/**
|
|
140
|
-
* Extract source code from an assistant's
|
|
141
|
+
* Extract source code from an assistant message's tool_use blocks.
|
|
141
142
|
*
|
|
142
|
-
*
|
|
143
|
-
* the `input.content
|
|
144
|
-
*
|
|
143
|
+
* Reads the bridged compile tool (`*__compile_component`, `input.code`)
|
|
144
|
+
* and the CLI's `Write` (`input.content`, offered only by opt-in), by the
|
|
145
|
+
* one rule in `adapters/claude/source-capture.ts` (ggui#1714). If several
|
|
146
|
+
* calls carry source, returns the last one (the final version).
|
|
145
147
|
*
|
|
146
|
-
* Used by: loop.ts
|
|
148
|
+
* Used by: loop.ts
|
|
147
149
|
*/
|
|
148
150
|
export function extractSourceCodeFromMessage(message: SdkMessage): string | undefined {
|
|
149
151
|
if ((message.type as string) !== 'assistant') return undefined;
|
|
@@ -155,12 +157,9 @@ export function extractSourceCodeFromMessage(message: SdkMessage): string | unde
|
|
|
155
157
|
if (!Array.isArray(content)) return undefined;
|
|
156
158
|
|
|
157
159
|
for (const block of content) {
|
|
158
|
-
if (block.type
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
sourceCode = input.content;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
160
|
+
if (block.type !== 'tool_use') continue;
|
|
161
|
+
const value = sourceCodeFromToolUse(block.name, block.input);
|
|
162
|
+
if (value !== undefined) sourceCode = value;
|
|
164
163
|
}
|
|
165
164
|
|
|
166
165
|
return sourceCode;
|
|
@@ -183,7 +182,7 @@ export function extractCompiledCode(messages: SdkMessage[]): string | undefined
|
|
|
183
182
|
}
|
|
184
183
|
|
|
185
184
|
/**
|
|
186
|
-
* Scan all messages for the last sourceCode
|
|
185
|
+
* Scan all messages for the last sourceCode a tool_use carried.
|
|
187
186
|
*
|
|
188
187
|
* Convenience wrapper for tests and one-shot extraction.
|
|
189
188
|
*/
|
|
@@ -376,6 +376,10 @@ export interface CanvasJudgeRecord {
|
|
|
376
376
|
/**
|
|
377
377
|
* ggui#1687 — the model asked for, and every model the provider said answered this canvas's calls (scoring and
|
|
378
378
|
* criteria), deduplicated. `served` is empty when no provider named one: unknown, never "the requested model".
|
|
379
|
+
* When the request names an alias and the provider echoes the alias (measured on a hosted route, 125 of 125
|
|
380
|
+
* canvases), `served` confirms the alias was honoured but cannot tell two snapshots behind it apart. A judge that
|
|
381
|
+
* must is pinned to a dated snapshot id instead. With the alias, a snapshot change shows only as a step on a date in
|
|
382
|
+
* the judge's pooled within-call spread, read by week. That is a read someone runs, not a monitor.
|
|
379
383
|
*/
|
|
380
384
|
readonly models?: { readonly requested: string; readonly served: readonly string[] };
|
|
381
385
|
}
|
|
@@ -2004,7 +2004,7 @@ function parseCriteriaAnswers(raw: unknown): { answers?: CriteriaAnswer[]; count
|
|
|
2004
2004
|
|
|
2005
2005
|
function getDefaultVisualModel(provider: 'claude' | 'google'): string {
|
|
2006
2006
|
switch (provider) {
|
|
2007
|
-
case 'claude': return 'claude-haiku-
|
|
2007
|
+
case 'claude': return 'claude-haiku-5-5';
|
|
2008
2008
|
case 'google': return 'gemini-3-flash-preview';
|
|
2009
2009
|
}
|
|
2010
2010
|
}
|
|
@@ -132,6 +132,7 @@ export async function assembleGenerationResult(
|
|
|
132
132
|
: telemetry.evalResult,
|
|
133
133
|
// ggui#404 — present only when the same-exchange guard ended the run.
|
|
134
134
|
...(telemetry.sameExchangeBreak !== undefined ? { sameExchangeBreak: telemetry.sameExchangeBreak } : {}),
|
|
135
|
+
...(telemetry.sameFailureBreak !== undefined ? { sameFailureBreak: telemetry.sameFailureBreak } : {}),
|
|
135
136
|
needsBackgroundImprovement,
|
|
136
137
|
selfCheckPassed: telemetry.selfCheckPassed,
|
|
137
138
|
timing,
|
|
@@ -102,6 +102,11 @@ export interface DupeBreakState {
|
|
|
102
102
|
* information-free — the model learned nothing it didn't already
|
|
103
103
|
* have. 1 = first occurrence; 2 = one repeat (nudge); 3 = break. */
|
|
104
104
|
consecutiveIdenticalExchanges: number;
|
|
105
|
+
/** ggui#1791 — for each failure signature ({@link failureSignatures}), how
|
|
106
|
+
* many failed writes (`apply_changes` / `rewrite`) have reported it since
|
|
107
|
+
* the last write that passed, whatever the patch and whatever failed in
|
|
108
|
+
* between. Cleared when a write passes. */
|
|
109
|
+
failureCounts: Map<string, number>;
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
/**
|
|
@@ -147,6 +152,70 @@ export const SAME_EXCHANGE_NUDGE =
|
|
|
147
152
|
"you already have and emit an apply_changes (or scoped) call now; one " +
|
|
148
153
|
"more identical repeat terminates the run.";
|
|
149
154
|
|
|
155
|
+
/**
|
|
156
|
+
* ggui#1791 — the failures a failed write reported, one signature per failing
|
|
157
|
+
* check, with what changes between two identical failures removed: every
|
|
158
|
+
* digit (line and column numbers, counts) and runs of whitespace.
|
|
159
|
+
*
|
|
160
|
+
* A self-check result lists its violations as `[category] description` lines;
|
|
161
|
+
* each one is its own signature, so a check that fails on every turn reads as
|
|
162
|
+
* the same failure whether or not another check fails beside it. A result with
|
|
163
|
+
* no violation line (a build error, a rejected patch) is one signature: its
|
|
164
|
+
* text without the `Committed <oid>: "<message>"` line, which carries a fresh
|
|
165
|
+
* oid and the model's own message every turn.
|
|
166
|
+
*
|
|
167
|
+
* The patch fingerprint above keys on the PATCH, so the same failure under a
|
|
168
|
+
* different patch each turn (a reworded comment, a moved line) never reads as
|
|
169
|
+
* stuck, and a run goes to the turn cap. These key on the FAILURE.
|
|
170
|
+
*/
|
|
171
|
+
export function failureSignatures(resultText: string): string[] {
|
|
172
|
+
const norm = (text: string): string => text.replace(/[0-9]+/g, "#").replace(/\s+/g, " ").trim();
|
|
173
|
+
const violations = [...resultText.matchAll(/^\[[^\]\n]+\] [^\n]+/gm)].map((m) => norm(m[0]));
|
|
174
|
+
if (violations.length > 0) return [...new Set(violations)];
|
|
175
|
+
const rest = norm(resultText.replace(/^Committed [^\n]*\n/, ""));
|
|
176
|
+
return rest.length > 0 ? [rest] : [];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** The failure a write has reported most often since the last pass, and how often. */
|
|
180
|
+
export interface RepeatedFailure {
|
|
181
|
+
readonly signature: string;
|
|
182
|
+
readonly repeats: number;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* ggui#1791 — record one failed write: count each of its signatures once, and
|
|
187
|
+
* return the one now reported most often since the last passing write
|
|
188
|
+
* (undefined when the result carried no recognisable failure).
|
|
189
|
+
*/
|
|
190
|
+
export function recordWriteFailure(state: DupeBreakState, resultText: string): RepeatedFailure | undefined {
|
|
191
|
+
let top: RepeatedFailure | undefined;
|
|
192
|
+
for (const signature of failureSignatures(resultText)) {
|
|
193
|
+
const repeats = (state.failureCounts.get(signature) ?? 0) + 1;
|
|
194
|
+
state.failureCounts.set(signature, repeats);
|
|
195
|
+
if (top === undefined || repeats > top.repeats) top = { signature, repeats };
|
|
196
|
+
}
|
|
197
|
+
return top;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** ggui#1791 — a write passed: every count starts over. */
|
|
201
|
+
export function clearWriteFailures(state: DupeBreakState): void {
|
|
202
|
+
state.failureCounts.clear();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** A failure reported for the third time since the last pass ⇒ one explicit nudge. */
|
|
206
|
+
export const SAME_FAILURE_NUDGE_AT = 3;
|
|
207
|
+
|
|
208
|
+
/** A failure reported for the fifth time since the last pass ⇒ the loop is not
|
|
209
|
+
* converging; the caller ends the run, which ships what the turn cap would
|
|
210
|
+
* have shipped. */
|
|
211
|
+
export const SAME_FAILURE_BREAK_AT = 5;
|
|
212
|
+
|
|
213
|
+
/** Appended to the tool result of the {@link SAME_FAILURE_NUDGE_AT}th identical failure. */
|
|
214
|
+
export const SAME_FAILURE_NUDGE =
|
|
215
|
+
"\n\n[harness] This same failure has now been reported for three of your " +
|
|
216
|
+
"patches. Changing other code does not clear it: re-read that check above and " +
|
|
217
|
+
"change exactly what it names. Two more reports of it end the run.";
|
|
218
|
+
|
|
150
219
|
export function createDupeBreakState(): DupeBreakState {
|
|
151
220
|
return {
|
|
152
221
|
lastFailedPatchFingerprint: null,
|
|
@@ -164,6 +233,7 @@ export function createDupeBreakState(): DupeBreakState {
|
|
|
164
233
|
consecutiveNoToolCalls: 0,
|
|
165
234
|
lastExchangeKey: null,
|
|
166
235
|
consecutiveIdenticalExchanges: 0,
|
|
236
|
+
failureCounts: new Map(),
|
|
167
237
|
};
|
|
168
238
|
}
|
|
169
239
|
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
import { runEvalRound } from "./run-eval-round.js";
|
|
26
26
|
import type { TokenUsage } from "./run-eval-round.js";
|
|
27
27
|
import type { VisualLegTokens } from "../../evaluation/visual-evaluator.js";
|
|
28
|
-
import type { SameExchangeBreak } from "../result-types.js";
|
|
28
|
+
import type { SameExchangeBreak, SameFailureBreak } from "../result-types.js";
|
|
29
29
|
|
|
30
30
|
type PreWarmedEvalContext =
|
|
31
31
|
import("../../evaluation/llm-evaluator.js").PreWarmedEvalContext;
|
|
@@ -105,6 +105,8 @@ export interface GenerateTelemetry {
|
|
|
105
105
|
* undefined unless that guard is what stopped the loop.
|
|
106
106
|
*/
|
|
107
107
|
sameExchangeBreak: SameExchangeBreak | undefined;
|
|
108
|
+
/** ggui#1791 — set when the same-failure guard ended the run. */
|
|
109
|
+
sameFailureBreak: SameFailureBreak | undefined;
|
|
108
110
|
/** Latest compiled code — empty string until the first self-check pass. */
|
|
109
111
|
compiledCode: string;
|
|
110
112
|
/**
|
|
@@ -147,6 +149,7 @@ export function createTelemetry(): GenerateTelemetry {
|
|
|
147
149
|
probeRepairTrigger: undefined,
|
|
148
150
|
contractFeedback: undefined,
|
|
149
151
|
sameExchangeBreak: undefined,
|
|
152
|
+
sameFailureBreak: undefined,
|
|
150
153
|
compiledCode: "",
|
|
151
154
|
pairedSource: "",
|
|
152
155
|
selfCheckPassed: false,
|
|
@@ -223,6 +226,7 @@ export function absorbTurn(telemetry: GenerateTelemetry, turn: CodingTurnResult)
|
|
|
223
226
|
else telemetry.counters.outcomes.selfCheckFail++;
|
|
224
227
|
}
|
|
225
228
|
if (turn.sameExchangeBreak !== undefined) telemetry.sameExchangeBreak = turn.sameExchangeBreak;
|
|
229
|
+
if (turn.sameFailureBreak !== undefined) telemetry.sameFailureBreak = turn.sameFailureBreak;
|
|
226
230
|
}
|
|
227
231
|
|
|
228
232
|
export interface CreateGenerateRunnerInput {
|
|
@@ -21,7 +21,7 @@ import { executeTool } from "../../coding-agent/tools.js";
|
|
|
21
21
|
import type { CostTracker } from "../../evaluation/cost-tracker.js";
|
|
22
22
|
import type { LLMAgent } from "../llm-router.js";
|
|
23
23
|
import type { Harness } from "../types-public.js";
|
|
24
|
-
import type { SameExchangeBreak } from "../result-types.js";
|
|
24
|
+
import type { SameExchangeBreak, SameFailureBreak } from "../result-types.js";
|
|
25
25
|
import {
|
|
26
26
|
APPLY_CHANGES_HASHLINE_TOOL,
|
|
27
27
|
APPLY_CHANGES_HASHLINE_TOOL_FLAT,
|
|
@@ -38,10 +38,16 @@ import {
|
|
|
38
38
|
import { LUCIDE_ICON_NAMES } from "@ggui-ai/design";
|
|
39
39
|
import { formatWithHashlines } from "../hashline.js";
|
|
40
40
|
import {
|
|
41
|
+
clearWriteFailures,
|
|
41
42
|
recordToolExchange,
|
|
43
|
+
recordWriteFailure,
|
|
42
44
|
SAME_EXCHANGE_BREAK_AT,
|
|
43
45
|
SAME_EXCHANGE_NUDGE,
|
|
46
|
+
SAME_FAILURE_BREAK_AT,
|
|
47
|
+
SAME_FAILURE_NUDGE,
|
|
48
|
+
SAME_FAILURE_NUDGE_AT,
|
|
44
49
|
} from "./dupe-break.js";
|
|
50
|
+
import type { RepeatedFailure } from "./dupe-break.js";
|
|
45
51
|
|
|
46
52
|
/** Staged process-mode phase machine — also defined in runtime.ts. */
|
|
47
53
|
export type A1Phase = "scaffold" | "fill" | "post";
|
|
@@ -254,6 +260,11 @@ export interface CodingTurnResult {
|
|
|
254
260
|
* control value many exits share. No key on any other turn.
|
|
255
261
|
*/
|
|
256
262
|
readonly sameExchangeBreak?: SameExchangeBreak;
|
|
263
|
+
/**
|
|
264
|
+
* Set only on the `break` the same-failure guard returns (ggui#1791). No
|
|
265
|
+
* key on any other turn.
|
|
266
|
+
*/
|
|
267
|
+
readonly sameFailureBreak?: SameFailureBreak;
|
|
257
268
|
}
|
|
258
269
|
|
|
259
270
|
/**
|
|
@@ -952,6 +963,26 @@ ${closingInstruction}`;
|
|
|
952
963
|
}
|
|
953
964
|
}
|
|
954
965
|
|
|
966
|
+
// ggui#1791 same-failure guard — the patch fingerprint above keys on the
|
|
967
|
+
// PATCH, so the same failure under a different patch each turn (a reworded
|
|
968
|
+
// comment, a moved line) never counts as stuck. This keys on the FAILURE:
|
|
969
|
+
// each failing check a write reports is counted since the last passing
|
|
970
|
+
// write, whatever failed in between. The third report of one gets a nudge,
|
|
971
|
+
// the fifth ends the run. A passing write clears the counts; a non-write
|
|
972
|
+
// tool (cat, icons) changes nothing and leaves them as they were.
|
|
973
|
+
const isWrite = call.name === "apply_changes" || call.name === "rewrite";
|
|
974
|
+
let repeatedFailure: RepeatedFailure | undefined;
|
|
975
|
+
if (ctx.dupeBreak && isWrite) {
|
|
976
|
+
if (result.done) {
|
|
977
|
+
clearWriteFailures(ctx.dupeBreak);
|
|
978
|
+
} else {
|
|
979
|
+
repeatedFailure = recordWriteFailure(ctx.dupeBreak, result.result);
|
|
980
|
+
if (repeatedFailure?.repeats === SAME_FAILURE_NUDGE_AT) {
|
|
981
|
+
augmentedResultText += SAME_FAILURE_NUDGE;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
955
986
|
// Send tool result back to close the API contract (Google/OpenAI/OpenRouter)
|
|
956
987
|
// This enables proper session chaining on the next callTools() invocation.
|
|
957
988
|
await codingAgent.sendToolResult([{
|
|
@@ -984,6 +1015,30 @@ ${closingInstruction}`;
|
|
|
984
1015
|
};
|
|
985
1016
|
}
|
|
986
1017
|
|
|
1018
|
+
if (repeatedFailure !== undefined && repeatedFailure.repeats >= SAME_FAILURE_BREAK_AT) {
|
|
1019
|
+
const signature = repeatedFailure.signature.slice(0, 160);
|
|
1020
|
+
console.log(
|
|
1021
|
+
`[coding-agent] SAME_FAILURE_BREAK | tool=${call.name} | repeats=${repeatedFailure.repeats} | failure=${JSON.stringify(signature.slice(0, 80))} — terminating run (ggui#1791)`,
|
|
1022
|
+
);
|
|
1023
|
+
return {
|
|
1024
|
+
control: "break",
|
|
1025
|
+
compiledCode: "",
|
|
1026
|
+
selfCheckPassed: false,
|
|
1027
|
+
outcome: undefined,
|
|
1028
|
+
phase: undefined,
|
|
1029
|
+
tokens,
|
|
1030
|
+
...cacheTokens,
|
|
1031
|
+
llmMs,
|
|
1032
|
+
toolMs,
|
|
1033
|
+
lastResultText: augmentedResultText,
|
|
1034
|
+
lastDiffFailed: false,
|
|
1035
|
+
isEvalFeedback: false,
|
|
1036
|
+
iconNamesCache,
|
|
1037
|
+
preWarmedContext,
|
|
1038
|
+
sameFailureBreak: { signature, repeats: repeatedFailure.repeats },
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
|
|
987
1042
|
// Track whether the diff itself failed to apply (vs applied but had violations)
|
|
988
1043
|
const turnDiffFailed = !!result.error && result.result.includes("DIFF");
|
|
989
1044
|
|
|
@@ -62,6 +62,16 @@ export interface SameExchangeBreak {
|
|
|
62
62
|
readonly repeats: number;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* ggui#1791 — the same-failure guard ended the run: one failing check
|
|
67
|
+
* (`signature`, its first 160 characters) was reported by this many failed
|
|
68
|
+
* writes since the last passing one, whatever the patch.
|
|
69
|
+
*/
|
|
70
|
+
export interface SameFailureBreak {
|
|
71
|
+
readonly signature: string;
|
|
72
|
+
readonly repeats: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
65
75
|
export interface GenerationResult extends AdapterResult {
|
|
66
76
|
/** Number of generation passes the harness performed */
|
|
67
77
|
passesUsed: number;
|
|
@@ -77,6 +87,8 @@ export interface GenerationResult extends AdapterResult {
|
|
|
77
87
|
inLoopVisualTokens?: VisualLegTokens;
|
|
78
88
|
/** Set only when the same-exchange guard ended the run (ggui#404); no key otherwise. */
|
|
79
89
|
sameExchangeBreak?: SameExchangeBreak;
|
|
90
|
+
/** ggui#1791 — set only when the same-failure guard ended the run. */
|
|
91
|
+
sameFailureBreak?: SameFailureBreak;
|
|
80
92
|
/** Whether background improvement should be spawned (auto-improve mode) */
|
|
81
93
|
needsBackgroundImprovement?: boolean;
|
|
82
94
|
/** Whether at least one commit passed the self-check (compile + type check + lint) */
|
package/src/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ export {
|
|
|
22
22
|
export type { CreateUiGeneratorOptions } from './create-ui-generator.js';
|
|
23
23
|
export type { RuntimeRenderProbeConfig } from './harness/check/runtime-render/adapter.js';
|
|
24
24
|
export type { ProviderRetryInfo } from './harness/llm-router.js';
|
|
25
|
-
export type { GenerationResult, SameExchangeBreak } from './harness/result-types.js';
|
|
25
|
+
export type { GenerationResult, SameExchangeBreak, SameFailureBreak } from './harness/result-types.js';
|
|
26
26
|
// The build identity a stamp outside this package can name: the same
|
|
27
27
|
// digests `createUiGenerator` puts on every result's `metadata.build`, for
|
|
28
28
|
// either design mode — so a harness definition can carry both modes' digests
|
package/src/taught-tokens.ts
CHANGED
|
@@ -18,13 +18,12 @@ import { consumedTokenManifest } from "@ggui-ai/design/themes";
|
|
|
18
18
|
* closed-set check still admits them: a card that references one is valid wherever this release
|
|
19
19
|
* renders it.
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* Empty today. The control edges (`controlOutline`, `controlAccentOutline`, ggui#1494) were held here
|
|
22
|
+
* until every serving and rollback runtime declared them; the derivation (`10a0c00f2`) is in every
|
|
23
|
+
* release from tag 17 on, so they are taught from ggui#1697, in the same change as the prompt rule that
|
|
24
|
+
* teaches a control's edge.
|
|
23
25
|
*/
|
|
24
|
-
export const NOT_YET_TAUGHT_TOKENS: ReadonlySet<string> = new Set([
|
|
25
|
-
"--ggui-color-controlAccentOutline",
|
|
26
|
-
"--ggui-color-controlOutline",
|
|
27
|
-
]);
|
|
26
|
+
export const NOT_YET_TAUGHT_TOKENS: ReadonlySet<string> = new Set<string>([]);
|
|
28
27
|
|
|
29
28
|
/** The manifest as the model is taught it. */
|
|
30
29
|
export const taughtTokenManifest: readonly string[] = consumedTokenManifest.filter(
|