@ggui-ai/ui-gen 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/adapters/generation-dispatch.d.ts +1 -1
  2. package/dist/adapters/generation-dispatch.js +198 -59
  3. package/dist/adapters/generation-dispatch.js.map +1 -1
  4. package/dist/adapters/index.js +35 -10
  5. package/dist/adapters/index.js.map +1 -1
  6. package/dist/advanced/index.d.ts +2 -2
  7. package/dist/advanced/index.js +381 -58
  8. package/dist/advanced/index.js.map +1 -1
  9. package/dist/boilerplate.js.map +1 -1
  10. package/dist/check/index.d.ts +1 -1
  11. package/dist/check/index.js.map +1 -1
  12. package/dist/{create-ui-generator-D7kOXNjJ.d.ts → create-ui-generator-BEkzEtNz.d.ts} +1 -1
  13. package/dist/{dispatch-DGvhmARp.d.ts → dispatch-DJNpQ7DI.d.ts} +1 -1
  14. package/dist/evaluation/axis-checks/index.d.ts +3 -3
  15. package/dist/evaluation/axis-checks/index.js.map +1 -1
  16. package/dist/evaluation/axis-checks/registry.d.ts +1 -1
  17. package/dist/evaluation/index.d.ts +3 -3
  18. package/dist/evaluation/index.js +37 -20
  19. package/dist/evaluation/index.js.map +1 -1
  20. package/dist/harness/check/runtime-render/index.d.ts +1 -1
  21. package/dist/harness/index.d.ts +1 -1
  22. package/dist/harness/index.js +34 -9
  23. package/dist/harness/index.js.map +1 -1
  24. package/dist/harness/prompts.js +1 -1
  25. package/dist/harness/prompts.js.map +1 -1
  26. package/dist/harness/result-types.d.ts +15 -2
  27. package/dist/harness/result-types.js.map +1 -1
  28. package/dist/harness/runtime.d.ts +1 -1
  29. package/dist/harness/runtime.js.map +1 -1
  30. package/dist/harness/types-public.d.ts +1 -1
  31. package/dist/harness/types-public.js.map +1 -1
  32. package/dist/harness/validator-trace-sink.d.ts +1 -1
  33. package/dist/index.d.ts +22 -6
  34. package/dist/index.js +381 -58
  35. package/dist/index.js.map +1 -1
  36. package/dist/patch.d.ts +1 -1
  37. package/dist/{types-public-CzYqZclY.d.ts → types-public-mM_-1Yk3.d.ts} +16 -1
  38. package/dist/workflows.d.ts +1 -1
  39. package/package.json +12 -12
  40. package/src/adapters/claude/claude-code-login.ts +62 -7
  41. package/src/adapters/claude/sdk.ts +6 -5
  42. package/src/boilerplate/templates/layouts/chat-mobile.tsx.tmpl +7 -3
  43. package/src/boilerplate/templates/layouts/chat-universal.tsx.tmpl +7 -3
  44. package/src/contract-context.ts +1 -1
  45. package/src/create-ui-generator.ts +30 -0
  46. package/src/evaluation/index.ts +1 -0
  47. package/src/evaluation/llm-evaluator.ts +40 -13
  48. package/src/evaluation/types-public.ts +16 -0
  49. package/src/evaluation/visual-evaluator.ts +9 -6
  50. package/src/generator-build.ts +158 -0
  51. package/src/harness/claude-code-login-query.ts +5 -4
  52. package/src/harness/coding/assemble-result.ts +8 -1
  53. package/src/harness/coding/generate-task-runner.ts +75 -36
  54. package/src/harness/coding/run-coding-turn.ts +9 -0
  55. package/src/harness/coding/run-eval-round.ts +160 -5
  56. package/src/harness/result-types.ts +14 -0
  57. package/src/index.ts +2 -1
package/dist/index.js CHANGED
@@ -4,10 +4,10 @@ import { build } from 'esbuild';
4
4
  import Anthropic from '@anthropic-ai/sdk';
5
5
  import { getCssTokens, fillFitRule, FORBIDDEN_IMPORT_SPECIFIERS } from '@ggui-ai/design/rendering';
6
6
  import { classifyUi, MODEL_REGISTRY, listContractGadgets, HOOK_NAME_RE, modelRefOfRoute, isGeneratorId, DANGEROUS_PATTERNS as DANGEROUS_PATTERNS$1, APP_GENERATION_PROFILE_BOUNDS, STDLIB_GADGETS_PACKAGE, resolveAppGadgets, anthropicRejectsForcedToolChoice, normalizeAnthropicModelId, anthropicRejectsSamplingParams, isRecord } from '@ggui-ai/protocol';
7
- import fs, { existsSync, mkdirSync, writeFileSync, unlinkSync, readFileSync, readdirSync, statSync } from 'fs';
7
+ import fs, { existsSync, mkdtempSync, writeFileSync, rmSync, readFileSync, readdirSync, statSync } from 'fs';
8
8
  import { contentHash } from '@ggui-ai/protocol/content-hash';
9
9
  import ts4 from 'typescript';
10
- import path, { dirname, resolve, relative } from 'path';
10
+ import path, { dirname, resolve, relative, join } from 'path';
11
11
  import { createRequire } from 'module';
12
12
  import { Linter } from 'eslint';
13
13
  import { consumedTokenManifest } from '@ggui-ai/design/themes';
@@ -539,18 +539,17 @@ var init_tool_bridge2 = __esm({
539
539
  });
540
540
 
541
541
  // src/adapters/claude/claude-code-login.ts
542
- function stripProviderKeyEnv(env) {
542
+ function loginChildEnv(env) {
543
543
  const out = {};
544
- const drop = new Set(PROVIDER_KEY_ENV_NAMES);
545
544
  for (const [k, v] of Object.entries(env)) {
546
- if (v !== void 0 && !drop.has(k)) out[k] = v;
545
+ if (v !== void 0 && (LOGIN_CHILD_ENV.has(k) || k.startsWith("LC_"))) out[k] = v;
547
546
  }
548
547
  return out;
549
548
  }
550
549
  function claudeCodeLoginQueryOptions() {
551
550
  return { tools: [], settingSources: [] };
552
551
  }
553
- var CLAUDE_CODE_LOGIN_CREDENTIAL, PROVIDER_KEY_ENV_NAMES;
552
+ var CLAUDE_CODE_LOGIN_CREDENTIAL, PROVIDER_KEY_ENV_NAMES, LOGIN_CHILD_ENV_NAMES, LOGIN_CHILD_ENV;
554
553
  var init_claude_code_login = __esm({
555
554
  "src/adapters/claude/claude-code-login.ts"() {
556
555
  CLAUDE_CODE_LOGIN_CREDENTIAL = "claude-code-login";
@@ -560,6 +559,38 @@ var init_claude_code_login = __esm({
560
559
  "ANTHROPIC_AUTH_TOKEN",
561
560
  "ANTHROPIC_BASE_URL"
562
561
  ];
562
+ LOGIN_CHILD_ENV_NAMES = [
563
+ "PATH",
564
+ "HOME",
565
+ "USER",
566
+ "LOGNAME",
567
+ "SHELL",
568
+ "TMPDIR",
569
+ "TMP",
570
+ "TEMP",
571
+ "TERM",
572
+ "TZ",
573
+ "LANG",
574
+ "CLAUDE_CONFIG_DIR",
575
+ "CLAUDE_CODE_OAUTH_TOKEN",
576
+ "XDG_CONFIG_HOME",
577
+ "XDG_DATA_HOME",
578
+ "XDG_CACHE_HOME",
579
+ "XDG_STATE_HOME",
580
+ "XDG_RUNTIME_DIR",
581
+ "HTTPS_PROXY",
582
+ "https_proxy",
583
+ "HTTP_PROXY",
584
+ "http_proxy",
585
+ "NO_PROXY",
586
+ "no_proxy",
587
+ "ALL_PROXY",
588
+ "all_proxy",
589
+ "NODE_EXTRA_CA_CERTS",
590
+ "SSL_CERT_FILE",
591
+ "SSL_CERT_DIR"
592
+ ];
593
+ LOGIN_CHILD_ENV = new Set(LOGIN_CHILD_ENV_NAMES);
563
594
  }
564
595
  });
565
596
 
@@ -608,7 +639,7 @@ var init_sdk = __esm({
608
639
  const cfg = this.sdkConfig;
609
640
  const { query } = await import('@anthropic-ai/claude-agent-sdk');
610
641
  const login = cfg.claudeCodeLogin === true;
611
- const env = login ? stripProviderKeyEnv(buildEnv(cfg.env)) : buildEnv(cfg.env);
642
+ const env = login ? loginChildEnv(buildEnv(cfg.env)) : buildEnv(cfg.env);
612
643
  const artifacts = createArtifacts();
613
644
  const onProgress = cfg.onProgress;
614
645
  let mcpServers = cfg.mcpServers;
@@ -4512,7 +4543,7 @@ function buildLoginQueryOptions(params) {
4512
4543
  systemPrompt: params.systemPrompt,
4513
4544
  maxTurns: 1,
4514
4545
  thinking: { type: "disabled" },
4515
- env: stripProviderKeyEnv(params.env ?? process.env),
4546
+ env: loginChildEnv(params.env ?? process.env),
4516
4547
  ...claudeCodeLoginQueryOptions(),
4517
4548
  ...params.tools !== void 0 ? {
4518
4549
  mcpServers: { [MCP_SERVER_NAME]: params.tools.server },
@@ -7217,6 +7248,14 @@ function getDefaultEvalModel(provider) {
7217
7248
  return "claude-haiku-4-5-20251001";
7218
7249
  }
7219
7250
  }
7251
+ function callTokens(response) {
7252
+ return {
7253
+ in: response.inputTokens,
7254
+ out: response.outputTokens,
7255
+ ...response.cacheReadTokens !== void 0 ? { cacheRead: response.cacheReadTokens } : {},
7256
+ ...response.cacheCreationTokens !== void 0 ? { cacheCreation: response.cacheCreationTokens } : {}
7257
+ };
7258
+ }
7220
7259
  async function evalCriterion(criterion, tier, motherPrompt, numberedSource, agent, model, designMode) {
7221
7260
  const start = Date.now();
7222
7261
  const tool = tier === 1 ? makeTier1Tool(criterion) : makeTier2Tool(criterion);
@@ -7240,7 +7279,7 @@ async function evalCriterion(criterion, tier, motherPrompt, numberedSource, agen
7240
7279
  pass: true,
7241
7280
  coverage: "skipped",
7242
7281
  skipReason: "no tool call returned",
7243
- tokens: { in: response.inputTokens, out: response.outputTokens },
7282
+ tokens: callTokens(response),
7244
7283
  durationMs
7245
7284
  };
7246
7285
  }
@@ -7293,7 +7332,7 @@ async function evalCriterion(criterion, tier, motherPrompt, numberedSource, agen
7293
7332
  issues,
7294
7333
  pass,
7295
7334
  coverage: "ran",
7296
- tokens: { in: response.inputTokens, out: response.outputTokens },
7335
+ tokens: callTokens(response),
7297
7336
  durationMs
7298
7337
  };
7299
7338
  } catch (e) {
@@ -7376,7 +7415,8 @@ async function runLLMEvaluation(context, config, preWarmedContext) {
7376
7415
  const passCategories = [];
7377
7416
  let totalIn = 0;
7378
7417
  let totalOut = 0;
7379
- let cachedTokensEstimate = 0;
7418
+ let cacheRead;
7419
+ let cacheCreation;
7380
7420
  for (const r of results) {
7381
7421
  allIssues.push(...r.issues);
7382
7422
  if (r.pass) {
@@ -7384,10 +7424,8 @@ async function runLLMEvaluation(context, config, preWarmedContext) {
7384
7424
  }
7385
7425
  totalIn += r.tokens.in;
7386
7426
  totalOut += r.tokens.out;
7387
- }
7388
- const firstResult = results[0];
7389
- if (firstResult && firstResult.tokens.in > 0) {
7390
- cachedTokensEstimate = firstResult.tokens.in * (totalCriteriaCount - 1);
7427
+ if (r.tokens.cacheRead !== void 0) cacheRead = (cacheRead ?? 0) + r.tokens.cacheRead;
7428
+ if (r.tokens.cacheCreation !== void 0) cacheCreation = (cacheCreation ?? 0) + r.tokens.cacheCreation;
7391
7429
  }
7392
7430
  const wallMs = Date.now() - evalStart;
7393
7431
  const criteriaCoverage = results.map((r) => ({
@@ -7404,14 +7442,16 @@ async function runLLMEvaluation(context, config, preWarmedContext) {
7404
7442
  console.log(`[eval] ${r.criterion}: ${status}${issueLabel} ${(r.durationMs / 1e3).toFixed(1)}s`);
7405
7443
  }
7406
7444
  console.log(
7407
- `[eval] total: ${(wallMs / 1e3).toFixed(1)}s wall (${totalCriteriaCount} parallel) | in=${totalIn} out=${totalOut} cached~=${cachedTokensEstimate} | issues=${allIssues.length} pass=${passCategories.length}` + (skippedCount > 0 ? ` skipped=${skippedCount}` : "")
7445
+ `[eval] total: ${(wallMs / 1e3).toFixed(1)}s wall (${totalCriteriaCount} parallel) | in=${totalIn} out=${totalOut} cacheRead=${cacheRead ?? "unreported"} cacheWrite=${cacheCreation ?? "unreported"} | issues=${allIssues.length} pass=${passCategories.length}` + (skippedCount > 0 ? ` skipped=${skippedCount}` : "")
7408
7446
  );
7409
7447
  return {
7410
7448
  issues: allIssues,
7411
7449
  pass: passCategories,
7412
7450
  criteriaCoverage,
7413
7451
  inputTokens: totalIn,
7414
- outputTokens: totalOut
7452
+ outputTokens: totalOut,
7453
+ ...cacheRead !== void 0 ? { cacheReadTokens: cacheRead } : {},
7454
+ ...cacheCreation !== void 0 ? { cacheCreationTokens: cacheCreation } : {}
7415
7455
  };
7416
7456
  }
7417
7457
  var FREE_STYLING_RULES, CONSTRAINED_STYLING_RULES, CONSTRAINED_VISUAL_REFERENCE, FREE_VISUAL_REFERENCE, CONSTRAINED_RAW_CLICK_TARGET_GAP, FREE_RAW_CLICK_TARGET_GAP, TIER_1_CRITERIA, TIER_2_CRITERIA, TIER_1_CATEGORIES, TIER_2_CATEGORIES, VALID_CATEGORIES;
@@ -7665,8 +7705,7 @@ function resolveDesignPackageDir() {
7665
7705
  }
7666
7706
  }
7667
7707
  async function bundleForRendering(compiledCode, sampleProps, designSrc = resolve(resolveDesignPackageDir(), "src")) {
7668
- const tmpDir = resolve(tmpdir(), "ggui-visual-eval-" + Date.now());
7669
- mkdirSync(tmpDir, { recursive: true });
7708
+ const tmpDir = mkdtempSync(resolve(tmpdir(), "ggui-visual-eval-"));
7670
7709
  const componentFile = resolve(tmpDir, "component.tsx");
7671
7710
  const entryFile = resolve(tmpDir, "entry.tsx");
7672
7711
  const designPkgDir = resolve(designSrc, "..");
@@ -7741,14 +7780,7 @@ try {
7741
7780
  });
7742
7781
  return result.outputFiles[0]?.text ?? "";
7743
7782
  } finally {
7744
- try {
7745
- unlinkSync(entryFile);
7746
- } catch {
7747
- }
7748
- try {
7749
- unlinkSync(componentFile);
7750
- } catch {
7751
- }
7783
+ rmSync(tmpDir, { recursive: true, force: true });
7752
7784
  }
7753
7785
  }
7754
7786
  function buildRenderHTML(bundledCode, cssTokens, fit) {
@@ -16352,7 +16384,11 @@ async function assembleGenerationResult(input) {
16352
16384
  generationTimeMs: totalMs,
16353
16385
  turnsUsed: telemetry.turnsUsed,
16354
16386
  passesUsed: 1,
16355
- evalResult: telemetry.evalResult,
16387
+ // ggui#1261 — the contract-feedback round's record rides the result the
16388
+ // generation ends with (whose probe stamp is the post-round verdict).
16389
+ evalResult: telemetry.evalResult !== void 0 && telemetry.contractFeedback !== void 0 ? { ...telemetry.evalResult, contractFeedback: telemetry.contractFeedback } : telemetry.evalResult,
16390
+ // ggui#404 — present only when the same-exchange guard ended the run.
16391
+ ...telemetry.sameExchangeBreak !== void 0 ? { sameExchangeBreak: telemetry.sameExchangeBreak } : {},
16356
16392
  needsBackgroundImprovement,
16357
16393
  selfCheckPassed: telemetry.selfCheckPassed,
16358
16394
  timing,
@@ -17107,7 +17143,8 @@ ${iconNamesCache}`;
17107
17143
  lastDiffFailed: false,
17108
17144
  isEvalFeedback: false,
17109
17145
  iconNamesCache,
17110
- preWarmedContext
17146
+ preWarmedContext,
17147
+ sameExchangeBreak: { tool: call.name, repeats: iconRepeats }
17111
17148
  };
17112
17149
  }
17113
17150
  return {
@@ -17307,7 +17344,8 @@ Repeated error: ${errText}
17307
17344
  lastDiffFailed: false,
17308
17345
  isEvalFeedback: false,
17309
17346
  iconNamesCache,
17310
- preWarmedContext
17347
+ preWarmedContext,
17348
+ sameExchangeBreak: { tool: call.name, repeats: sameExchangeRepeats }
17311
17349
  };
17312
17350
  }
17313
17351
  const turnDiffFailed = !!result.error && result.result.includes("DIFF");
@@ -17496,6 +17534,20 @@ function formatRuntimeProbeFeedback(issue) {
17496
17534
  return `[runtime] ${issue.category}${subcat}: ${issue.description}
17497
17535
  Fix: ${issue.fix ?? ""}`;
17498
17536
  }
17537
+ var FEEDBACK_PROBE_CHECKS = ["prop-sensitivity"];
17538
+ var CONTRACT_FEEDBACK_BONUS = 1;
17539
+ function isFeedbackProbeFail(issue) {
17540
+ const sub = issue.subcategory;
17541
+ if (issue.result !== "fail" || typeof sub !== "string") return false;
17542
+ return FEEDBACK_PROBE_CHECKS.some((check) => sub === `runtime:${check}` || sub.startsWith(`runtime:${check}:`));
17543
+ }
17544
+ function contractFeedbackRecord(fails, sourceBefore) {
17545
+ return { firedOn: fails.flatMap((i) => i.subcategory !== void 0 ? [i.subcategory] : []), sourceBefore };
17546
+ }
17547
+ function contractFeedbackFails(exitProbe, alreadyFedBack) {
17548
+ if (!exitProbe.fired) return [];
17549
+ return exitProbe.probeIssues.filter((i) => isFeedbackProbeFail(i) && !alreadyFedBack.has(fingerprintFail(i)));
17550
+ }
17499
17551
  async function runEvalRound(ctx, input) {
17500
17552
  const {
17501
17553
  workspace,
@@ -17685,7 +17737,9 @@ async function runEvalRound(ctx, input) {
17685
17737
  costTracker.record(evaluationAgent.model, llmResult.inputTokens, llmResult.outputTokens);
17686
17738
  evalTokens = {
17687
17739
  input: llmResult.inputTokens,
17688
- output: llmResult.outputTokens
17740
+ output: llmResult.outputTokens,
17741
+ ...llmResult.cacheReadTokens !== void 0 ? { cacheRead: llmResult.cacheReadTokens } : {},
17742
+ ...llmResult.cacheCreationTokens !== void 0 ? { cacheCreation: llmResult.cacheCreationTokens } : {}
17689
17743
  };
17690
17744
  }
17691
17745
  const allIssues = [
@@ -17770,6 +17824,34 @@ async function runEvalRound(ctx, input) {
17770
17824
  lastDiffFailed: false
17771
17825
  };
17772
17826
  }
17827
+ const contractFails = contractFeedbackFails(exitProbe, prevFailFingerprints);
17828
+ if (contractFails.length > 0 && evalRoundsUsed < maxEvalRounds + CONTRACT_FEEDBACK_BONUS) {
17829
+ const lines = contractFails.slice(0, MAX_FEEDBACK_ISSUES).map(formatRuntimeProbeFeedback);
17830
+ evalResult = {
17831
+ ...evalResult,
17832
+ issues: [...allIssues, ...exitProbe.probeIssues],
17833
+ pass: allPass,
17834
+ runtimeProbe: exitProbe.meta
17835
+ };
17836
+ console.log(
17837
+ `[simple] eval round ${evalRoundsUsed}: tier-1/2 clean BUT contract probe fail \u2014 granting +1 turn (ggui#1261; ${contractFails.map((i) => i.subcategory).join(", ")})`
17838
+ );
17839
+ return {
17840
+ control: "feedback",
17841
+ evalDone: false,
17842
+ evalResult,
17843
+ evalRoundsUsed,
17844
+ prevModeSubcats: updatedPrevModeSubcats,
17845
+ prevFailFingerprints: /* @__PURE__ */ new Set([...currFailFingerprints, ...contractFails.map(fingerprintFail)]),
17846
+ preWarmedContext,
17847
+ evalTokens,
17848
+ evalLlmMs,
17849
+ lastResultText: lines.join("\n\n"),
17850
+ isEvalFeedback: true,
17851
+ lastDiffFailed: false,
17852
+ contractFeedback: contractFeedbackRecord(contractFails, currentSource)
17853
+ };
17854
+ }
17773
17855
  evalResult = {
17774
17856
  ...evalResult,
17775
17857
  issues: [...allIssues, ...exitProbe.probeIssues],
@@ -17809,6 +17891,28 @@ async function runEvalRound(ctx, input) {
17809
17891
  issues: [...evalResult.issues, ...stuckExitProbe.probeIssues],
17810
17892
  runtimeProbe: stuckExitProbe.meta
17811
17893
  };
17894
+ const stuckContractFails = contractFeedbackFails(stuckExitProbe, prevFailFingerprints);
17895
+ if (stuckContractFails.length > 0 && evalRoundsUsed < maxEvalRounds + CONTRACT_FEEDBACK_BONUS) {
17896
+ const lines = stuckContractFails.slice(0, MAX_FEEDBACK_ISSUES).map(formatRuntimeProbeFeedback);
17897
+ console.log(
17898
+ `[simple] eval round ${evalRoundsUsed}: stuck BUT contract probe fail never delivered \u2014 granting +1 turn (ggui#1261; ${stuckContractFails.map((i) => i.subcategory).join(", ")})`
17899
+ );
17900
+ return {
17901
+ control: "feedback",
17902
+ evalDone: false,
17903
+ evalResult,
17904
+ evalRoundsUsed,
17905
+ prevModeSubcats: updatedPrevModeSubcats,
17906
+ prevFailFingerprints: /* @__PURE__ */ new Set([...currFailFingerprints, ...stuckContractFails.map(fingerprintFail)]),
17907
+ preWarmedContext,
17908
+ evalTokens,
17909
+ evalLlmMs,
17910
+ lastResultText: lines.join("\n\n"),
17911
+ isEvalFeedback: true,
17912
+ lastDiffFailed: false,
17913
+ contractFeedback: contractFeedbackRecord(stuckContractFails, currentSource)
17914
+ };
17915
+ }
17812
17916
  return {
17813
17917
  control: "break",
17814
17918
  evalDone: false,
@@ -17863,6 +17967,33 @@ async function runEvalRound(ctx, input) {
17863
17967
  lastDiffFailed: false
17864
17968
  };
17865
17969
  }
17970
+ const capContractFails = allowRuntimeExtension ? [] : contractFeedbackFails(exitProbe, prevFailFingerprints);
17971
+ if (capContractFails.length > 0 && evalRoundsUsed < maxEvalRounds + CONTRACT_FEEDBACK_BONUS) {
17972
+ const contractLines = capContractFails.slice(0, MAX_FEEDBACK_ISSUES).map(formatRuntimeProbeFeedback);
17973
+ evalResult = {
17974
+ ...evalResult,
17975
+ issues: [...evalResult.issues, ...exitProbe.probeIssues],
17976
+ runtimeProbe: exitProbe.meta
17977
+ };
17978
+ console.log(
17979
+ `[simple] eval round ${evalRoundsUsed}: contract probe fail active at cap \u2014 granting +1 retry (ggui#1261; ${capContractFails.map((i) => i.subcategory).join(", ")})`
17980
+ );
17981
+ return {
17982
+ control: "feedback",
17983
+ evalDone: false,
17984
+ evalResult,
17985
+ evalRoundsUsed,
17986
+ prevModeSubcats: updatedPrevModeSubcats,
17987
+ prevFailFingerprints: /* @__PURE__ */ new Set([...currFailFingerprints, ...capContractFails.map(fingerprintFail)]),
17988
+ preWarmedContext,
17989
+ evalTokens,
17990
+ evalLlmMs,
17991
+ lastResultText: contractLines.join("\n\n"),
17992
+ isEvalFeedback: true,
17993
+ lastDiffFailed: false,
17994
+ contractFeedback: contractFeedbackRecord(capContractFails, currentSource)
17995
+ };
17996
+ }
17866
17997
  if (!allowRuntimeExtension) {
17867
17998
  evalResult = {
17868
17999
  ...evalResult,
@@ -18005,6 +18136,8 @@ function createTelemetry() {
18005
18136
  totalIn: 0,
18006
18137
  totalOut: 0,
18007
18138
  evalResult: void 0,
18139
+ contractFeedback: void 0,
18140
+ sameExchangeBreak: void 0,
18008
18141
  compiledCode: "",
18009
18142
  pairedSource: "",
18010
18143
  selfCheckPassed: false,
@@ -18013,6 +18146,40 @@ function createTelemetry() {
18013
18146
  codingMs: 0
18014
18147
  };
18015
18148
  }
18149
+ function absorbTokens(telemetry, usage) {
18150
+ telemetry.totalIn += usage.input;
18151
+ telemetry.totalOut += usage.output;
18152
+ if (usage.cacheRead !== void 0) {
18153
+ telemetry.cacheReadTokens = (telemetry.cacheReadTokens ?? 0) + usage.cacheRead;
18154
+ }
18155
+ if (usage.cacheCreation !== void 0) {
18156
+ telemetry.cacheCreationTokens = (telemetry.cacheCreationTokens ?? 0) + usage.cacheCreation;
18157
+ }
18158
+ }
18159
+ function absorbTurn(telemetry, turn) {
18160
+ absorbTokens(telemetry, {
18161
+ input: turn.tokens.input,
18162
+ output: turn.tokens.output,
18163
+ ...turn.cacheReadTokens !== void 0 ? { cacheRead: turn.cacheReadTokens } : {},
18164
+ ...turn.cacheCreationTokens !== void 0 ? { cacheCreation: turn.cacheCreationTokens } : {}
18165
+ });
18166
+ telemetry.cumulativeLlmMs += turn.llmMs;
18167
+ telemetry.cumulativeToolMs += turn.toolMs;
18168
+ if (turn.phase) {
18169
+ if (turn.phase === "scaffold") telemetry.counters.phases.scaffold++;
18170
+ else if (turn.phase === "fill") telemetry.counters.phases.fill++;
18171
+ else if (turn.phase === "impl") telemetry.counters.phases.impl++;
18172
+ else if (turn.phase === "eval-fix") telemetry.counters.phases.evalFix++;
18173
+ else telemetry.counters.phases.patch++;
18174
+ }
18175
+ if (turn.outcome) {
18176
+ if (turn.outcome === "PASS") telemetry.counters.outcomes.pass++;
18177
+ else if (turn.outcome === "PATCH_INVALID") telemetry.counters.outcomes.patchInvalid++;
18178
+ else if (turn.outcome === "DIFF_FAIL") telemetry.counters.outcomes.diffFail++;
18179
+ else telemetry.counters.outcomes.selfCheckFail++;
18180
+ }
18181
+ if (turn.sameExchangeBreak !== void 0) telemetry.sameExchangeBreak = turn.sameExchangeBreak;
18182
+ }
18016
18183
  function createGenerateTaskRunner(input) {
18017
18184
  const { session, params, classification, telemetry } = input;
18018
18185
  const maxTurns = input.maxTurns ?? 30;
@@ -18065,31 +18232,9 @@ function createGenerateTaskRunner(input) {
18065
18232
  preWarmPromise: session.preWarmPromise
18066
18233
  }
18067
18234
  );
18068
- telemetry.totalIn += turn.tokens.input;
18069
- telemetry.totalOut += turn.tokens.output;
18070
- if (turn.cacheReadTokens !== void 0) {
18071
- telemetry.cacheReadTokens = (telemetry.cacheReadTokens ?? 0) + turn.cacheReadTokens;
18072
- }
18073
- if (turn.cacheCreationTokens !== void 0) {
18074
- telemetry.cacheCreationTokens = (telemetry.cacheCreationTokens ?? 0) + turn.cacheCreationTokens;
18075
- }
18076
- telemetry.cumulativeLlmMs += turn.llmMs;
18077
- telemetry.cumulativeToolMs += turn.toolMs;
18235
+ absorbTurn(telemetry, turn);
18078
18236
  iconNamesCache = turn.iconNamesCache;
18079
18237
  preWarmedContext = turn.preWarmedContext;
18080
- if (turn.phase) {
18081
- if (turn.phase === "scaffold") telemetry.counters.phases.scaffold++;
18082
- else if (turn.phase === "fill") telemetry.counters.phases.fill++;
18083
- else if (turn.phase === "impl") telemetry.counters.phases.impl++;
18084
- else if (turn.phase === "eval-fix") telemetry.counters.phases.evalFix++;
18085
- else telemetry.counters.phases.patch++;
18086
- }
18087
- if (turn.outcome) {
18088
- if (turn.outcome === "PASS") telemetry.counters.outcomes.pass++;
18089
- else if (turn.outcome === "PATCH_INVALID") telemetry.counters.outcomes.patchInvalid++;
18090
- else if (turn.outcome === "DIFF_FAIL") telemetry.counters.outcomes.diffFail++;
18091
- else telemetry.counters.outcomes.selfCheckFail++;
18092
- }
18093
18238
  if (turn.control === "break") break;
18094
18239
  if (turn.control === "continue") {
18095
18240
  lastResultText = turn.lastResultText;
@@ -18165,9 +18310,9 @@ function createGenerateTaskRunner(input) {
18165
18310
  prevFailFingerprints = round.prevFailFingerprints;
18166
18311
  preWarmedContext = round.preWarmedContext;
18167
18312
  if (round.evalResult) telemetry.evalResult = round.evalResult;
18313
+ if (round.contractFeedback) telemetry.contractFeedback = round.contractFeedback;
18168
18314
  evalDone = round.evalDone;
18169
- telemetry.totalIn += round.evalTokens.input;
18170
- telemetry.totalOut += round.evalTokens.output;
18315
+ absorbTokens(telemetry, round.evalTokens);
18171
18316
  if (round.control === "break") break;
18172
18317
  lastResultText = round.lastResultText;
18173
18318
  isEvalFeedback = round.isEvalFeedback;
@@ -18701,6 +18846,166 @@ function withArmRecord(result, params) {
18701
18846
  };
18702
18847
  }
18703
18848
 
18849
+ // src/pin-fixtures.ts
18850
+ var PIN_FIXTURES = [
18851
+ {
18852
+ userRequest: "A todo list where each item can be toggled done",
18853
+ shellType: "chat",
18854
+ screen: "mobile",
18855
+ contract: {
18856
+ propsSpec: {
18857
+ properties: {
18858
+ todos: {
18859
+ required: true,
18860
+ description: "The todos to show",
18861
+ schema: {
18862
+ type: "array",
18863
+ items: {
18864
+ type: "object",
18865
+ properties: {
18866
+ id: { type: "string" },
18867
+ title: { type: "string" },
18868
+ done: { type: "boolean" }
18869
+ }
18870
+ }
18871
+ }
18872
+ }
18873
+ }
18874
+ },
18875
+ actionSpec: {
18876
+ toggleTodo: {
18877
+ label: "Toggle",
18878
+ description: "Flip the done state of one todo",
18879
+ schema: {
18880
+ type: "object",
18881
+ properties: { id: { type: "string" } },
18882
+ required: ["id"]
18883
+ }
18884
+ }
18885
+ }
18886
+ }
18887
+ },
18888
+ {
18889
+ userRequest: "A kanban board with live task updates and a selected card",
18890
+ shellType: "fullscreen",
18891
+ screen: "desktop",
18892
+ contract: {
18893
+ propsSpec: {
18894
+ properties: {
18895
+ columns: {
18896
+ required: true,
18897
+ schema: { type: "array", items: { type: "string" } }
18898
+ },
18899
+ tasks: {
18900
+ required: false,
18901
+ schema: {
18902
+ type: "array",
18903
+ items: {
18904
+ type: "object",
18905
+ properties: {
18906
+ id: { type: "string" },
18907
+ column: { type: "string" },
18908
+ title: { type: "string" }
18909
+ }
18910
+ }
18911
+ }
18912
+ }
18913
+ }
18914
+ },
18915
+ streamSpec: {
18916
+ taskUpdates: {
18917
+ description: "Task CRUD feed",
18918
+ schema: {
18919
+ type: "object",
18920
+ properties: {
18921
+ action: { type: "string", enum: ["create", "move", "edit", "delete"] },
18922
+ id: { type: "string" }
18923
+ }
18924
+ }
18925
+ }
18926
+ },
18927
+ contextSpec: {
18928
+ selectedId: { schema: { type: "string", nullable: true } }
18929
+ }
18930
+ }
18931
+ }
18932
+ ];
18933
+
18934
+ // src/generator-build.ts
18935
+ var TEMPLATE_FIXTURE_BOUNDARY = "\n<<<pin-fixture-boundary>>>\n";
18936
+ function templateSha256(text) {
18937
+ return createHash("sha256").update(text, "utf8").digest("hex");
18938
+ }
18939
+ function renderPromptTemplates(designMode) {
18940
+ return PIN_FIXTURES.map((f) => {
18941
+ switch (designMode) {
18942
+ case "constrained":
18943
+ return buildSystemPrompt2(f.userRequest, f.shellType, f.screen);
18944
+ case "free":
18945
+ return buildSystemPrompt2(f.userRequest, f.shellType, f.screen, void 0, void 0, void 0, "free");
18946
+ }
18947
+ }).join(TEMPLATE_FIXTURE_BOUNDARY);
18948
+ }
18949
+ function renderBoilerplateTemplates(designMode) {
18950
+ return PIN_FIXTURES.map((f) => {
18951
+ switch (designMode) {
18952
+ case "constrained":
18953
+ return generateBoilerplate(f.userRequest, f.contract, f.shellType, f.screen);
18954
+ case "free":
18955
+ return generateBoilerplate(f.userRequest, f.contract, f.shellType, f.screen, void 0, void 0, "free");
18956
+ }
18957
+ }).join(TEMPLATE_FIXTURE_BOUNDARY);
18958
+ }
18959
+ var PACKAGE_NAME = "@ggui-ai/ui-gen";
18960
+ var MAX_PACKAGE_DEPTH = 6;
18961
+ function parsePackageJson(text) {
18962
+ try {
18963
+ const parsed = JSON.parse(text);
18964
+ return parsed;
18965
+ } catch (err) {
18966
+ if (err instanceof SyntaxError) return void 0;
18967
+ throw err;
18968
+ }
18969
+ }
18970
+ function findPackageVersion(startDir = dirname(fileURLToPath(import.meta.url))) {
18971
+ let dir = startDir;
18972
+ for (let i = 0; i < MAX_PACKAGE_DEPTH; i++) {
18973
+ const candidate = join(dir, "package.json");
18974
+ const pkg = existsSync(candidate) ? parsePackageJson(readFileSync(candidate, "utf8")) : void 0;
18975
+ if (pkg !== void 0) {
18976
+ if (typeof pkg === "object" && pkg !== null && "name" in pkg && pkg.name === PACKAGE_NAME && "version" in pkg && typeof pkg.version === "string") {
18977
+ return pkg.version;
18978
+ }
18979
+ }
18980
+ const parent = dirname(dir);
18981
+ if (parent === dir) break;
18982
+ dir = parent;
18983
+ }
18984
+ return void 0;
18985
+ }
18986
+ var versionRead = false;
18987
+ var packageVersion;
18988
+ var identities = /* @__PURE__ */ new Map();
18989
+ function generatorBuild(designMode) {
18990
+ const key = `${designMode}|${process.env.GGUI_PITFALLS ?? ""}|${process.env.GGUI_NEW_PITFALLS ?? ""}`;
18991
+ const known = identities.get(key);
18992
+ if (known !== void 0) return known;
18993
+ if (!versionRead) {
18994
+ packageVersion = findPackageVersion();
18995
+ versionRead = true;
18996
+ }
18997
+ const identity = {
18998
+ ...packageVersion !== void 0 ? { version: packageVersion } : {},
18999
+ mode: designMode,
19000
+ digests: {
19001
+ promptTemplateSha256: templateSha256(renderPromptTemplates(designMode)),
19002
+ boilerplateTemplateSha256: templateSha256(renderBoilerplateTemplates(designMode))
19003
+ }
19004
+ };
19005
+ identities.set(key, identity);
19006
+ return identity;
19007
+ }
19008
+
18704
19009
  // src/create-ui-generator.ts
18705
19010
  init_styling_profile();
18706
19011
 
@@ -18729,7 +19034,7 @@ var SHELL_HINTS = {
18729
19034
  - Padding: use var(--ggui-spacing-2) for inner elements, var(--ggui-spacing-4) for sections. Tight.
18730
19035
  - Scrolling: parent scrolls (chat feed) \u2014 component should NOT have internal scroll
18731
19036
  - Layout: single column, compact. No sidebar. Stack everything vertically.
18732
- - Width: width: 100% (fills parent bubble). Do NOT set max-width.`,
19037
+ - Width: the root is width: 100% (fills the bubble) \u2014 never cap the ROOT. A prose column inside it carries the reading cap (<Container maxWidth="sm">), which is inert at bubble width.`,
18733
19038
  fullscreen: `**Fullscreen Shell \u2014 Takes over entire viewport**
18734
19039
  - Container: the whole viewport, edge to edge \u2014 the frame stretches your root to its full height (you never set a viewport height yourself)
18735
19040
  - Chrome: NONE \u2014 your component IS the entire UI. You own all visual chrome.
@@ -18970,6 +19275,7 @@ function injectVariance(userPrompt, variance, options) {
18970
19275
  }
18971
19276
 
18972
19277
  // src/create-ui-generator.ts
19278
+ init_design_mode();
18973
19279
  init_provider_router();
18974
19280
  var DEFAULT_TIER = "default";
18975
19281
  var DEFAULT_MODEL = "anthropic/claude-haiku-4-5";
@@ -19037,6 +19343,7 @@ function createUiGenerator(options = {}) {
19037
19343
  const rendering = mapRendering(input.rendering);
19038
19344
  const promptWithRendering = rendering ? injectRenderingContext(input.request.prompt, rendering) : input.request.prompt;
19039
19345
  const canvas = rendering ? canvasForRenderingContext(rendering) : void 0;
19346
+ const shellType = harnessShellForRendering(rendering);
19040
19347
  const promptWithVariance = injectVariance(
19041
19348
  promptWithRendering,
19042
19349
  input.variance,
@@ -19076,6 +19383,8 @@ function createUiGenerator(options = {}) {
19076
19383
  ...onRetry !== void 0 ? { onRetry } : {},
19077
19384
  ...designMode !== void 0 ? { designMode } : {},
19078
19385
  ...canvas !== void 0 ? { canvas } : {},
19386
+ // ggui#1279 — the shell the declared rendering composes for (chat only; see harnessShellForRendering).
19387
+ ...shellType !== void 0 ? { shellType } : {},
19079
19388
  ...input.profile !== void 0 ? { profile: input.profile } : {},
19080
19389
  // Fixture props reach the in-loop probe + visual round only (never the prompt, never identity).
19081
19390
  ...input.fixtureProps !== void 0 ? { fixtureProps: input.fixtureProps } : {}
@@ -19090,6 +19399,8 @@ function createUiGenerator(options = {}) {
19090
19399
  latencyMs: Date.now() - startedAt,
19091
19400
  cacheHit: false,
19092
19401
  attempts: result.turnsUsed,
19402
+ // ggui#1280 — the build that made this generation (template keys, never per-request).
19403
+ build: generatorBuild(designMode ?? DEFAULT_DESIGN_MODE),
19093
19404
  // Prompt-cache counters are provider-specific (Claude reports
19094
19405
  // them; others omit). Pass through truthfully — absent on the
19095
19406
  // adapter result stays absent here, never defaulted to 0.
@@ -19124,7 +19435,9 @@ function createUiGenerator(options = {}) {
19124
19435
  inputTokens: 0,
19125
19436
  outputTokens: 0,
19126
19437
  latencyMs: Date.now() - startedAt,
19127
- cacheHit: false
19438
+ cacheHit: false,
19439
+ // ggui#1280 — a failed generation still names the build that failed.
19440
+ build: generatorBuild(designMode ?? DEFAULT_DESIGN_MODE)
19128
19441
  }
19129
19442
  };
19130
19443
  } finally {
@@ -19176,6 +19489,16 @@ function mapLlmProviderToDispatchProvider(provider) {
19176
19489
  return "openrouter";
19177
19490
  }
19178
19491
  }
19492
+ function harnessShellForRendering(rendering) {
19493
+ if (rendering === void 0) return void 0;
19494
+ switch (rendering.shell) {
19495
+ case "chat":
19496
+ return "chat";
19497
+ case "fullscreen":
19498
+ case "partial":
19499
+ return void 0;
19500
+ }
19501
+ }
19179
19502
  function mapRendering(rendering) {
19180
19503
  if (!rendering) return void 0;
19181
19504
  return {