@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
@@ -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, DANGEROUS_PATTERNS as DANGEROUS_PATTERNS$1, listContractGadgets, HOOK_NAME_RE, modelRefOfRoute, isGeneratorId, 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;
@@ -4504,7 +4535,7 @@ function buildLoginQueryOptions(params) {
4504
4535
  systemPrompt: params.systemPrompt,
4505
4536
  maxTurns: 1,
4506
4537
  thinking: { type: "disabled" },
4507
- env: stripProviderKeyEnv(params.env ?? process.env),
4538
+ env: loginChildEnv(params.env ?? process.env),
4508
4539
  ...claudeCodeLoginQueryOptions(),
4509
4540
  ...params.tools !== void 0 ? {
4510
4541
  mcpServers: { [MCP_SERVER_NAME]: params.tools.server },
@@ -7209,6 +7240,14 @@ function getDefaultEvalModel(provider) {
7209
7240
  return "claude-haiku-4-5-20251001";
7210
7241
  }
7211
7242
  }
7243
+ function callTokens(response) {
7244
+ return {
7245
+ in: response.inputTokens,
7246
+ out: response.outputTokens,
7247
+ ...response.cacheReadTokens !== void 0 ? { cacheRead: response.cacheReadTokens } : {},
7248
+ ...response.cacheCreationTokens !== void 0 ? { cacheCreation: response.cacheCreationTokens } : {}
7249
+ };
7250
+ }
7212
7251
  async function evalCriterion(criterion, tier, motherPrompt, numberedSource, agent, model, designMode) {
7213
7252
  const start = Date.now();
7214
7253
  const tool = tier === 1 ? makeTier1Tool(criterion) : makeTier2Tool(criterion);
@@ -7232,7 +7271,7 @@ async function evalCriterion(criterion, tier, motherPrompt, numberedSource, agen
7232
7271
  pass: true,
7233
7272
  coverage: "skipped",
7234
7273
  skipReason: "no tool call returned",
7235
- tokens: { in: response.inputTokens, out: response.outputTokens },
7274
+ tokens: callTokens(response),
7236
7275
  durationMs
7237
7276
  };
7238
7277
  }
@@ -7285,7 +7324,7 @@ async function evalCriterion(criterion, tier, motherPrompt, numberedSource, agen
7285
7324
  issues,
7286
7325
  pass,
7287
7326
  coverage: "ran",
7288
- tokens: { in: response.inputTokens, out: response.outputTokens },
7327
+ tokens: callTokens(response),
7289
7328
  durationMs
7290
7329
  };
7291
7330
  } catch (e) {
@@ -7368,7 +7407,8 @@ async function runLLMEvaluation(context, config, preWarmedContext) {
7368
7407
  const passCategories = [];
7369
7408
  let totalIn = 0;
7370
7409
  let totalOut = 0;
7371
- let cachedTokensEstimate = 0;
7410
+ let cacheRead;
7411
+ let cacheCreation;
7372
7412
  for (const r of results) {
7373
7413
  allIssues.push(...r.issues);
7374
7414
  if (r.pass) {
@@ -7376,10 +7416,8 @@ async function runLLMEvaluation(context, config, preWarmedContext) {
7376
7416
  }
7377
7417
  totalIn += r.tokens.in;
7378
7418
  totalOut += r.tokens.out;
7379
- }
7380
- const firstResult = results[0];
7381
- if (firstResult && firstResult.tokens.in > 0) {
7382
- cachedTokensEstimate = firstResult.tokens.in * (totalCriteriaCount - 1);
7419
+ if (r.tokens.cacheRead !== void 0) cacheRead = (cacheRead ?? 0) + r.tokens.cacheRead;
7420
+ if (r.tokens.cacheCreation !== void 0) cacheCreation = (cacheCreation ?? 0) + r.tokens.cacheCreation;
7383
7421
  }
7384
7422
  const wallMs = Date.now() - evalStart;
7385
7423
  const criteriaCoverage = results.map((r) => ({
@@ -7396,14 +7434,16 @@ async function runLLMEvaluation(context, config, preWarmedContext) {
7396
7434
  console.log(`[eval] ${r.criterion}: ${status}${issueLabel} ${(r.durationMs / 1e3).toFixed(1)}s`);
7397
7435
  }
7398
7436
  console.log(
7399
- `[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}` : "")
7437
+ `[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}` : "")
7400
7438
  );
7401
7439
  return {
7402
7440
  issues: allIssues,
7403
7441
  pass: passCategories,
7404
7442
  criteriaCoverage,
7405
7443
  inputTokens: totalIn,
7406
- outputTokens: totalOut
7444
+ outputTokens: totalOut,
7445
+ ...cacheRead !== void 0 ? { cacheReadTokens: cacheRead } : {},
7446
+ ...cacheCreation !== void 0 ? { cacheCreationTokens: cacheCreation } : {}
7407
7447
  };
7408
7448
  }
7409
7449
  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;
@@ -7657,8 +7697,7 @@ function resolveDesignPackageDir() {
7657
7697
  }
7658
7698
  }
7659
7699
  async function bundleForRendering(compiledCode, sampleProps, designSrc = resolve(resolveDesignPackageDir(), "src")) {
7660
- const tmpDir = resolve(tmpdir(), "ggui-visual-eval-" + Date.now());
7661
- mkdirSync(tmpDir, { recursive: true });
7700
+ const tmpDir = mkdtempSync(resolve(tmpdir(), "ggui-visual-eval-"));
7662
7701
  const componentFile = resolve(tmpDir, "component.tsx");
7663
7702
  const entryFile = resolve(tmpDir, "entry.tsx");
7664
7703
  const designPkgDir = resolve(designSrc, "..");
@@ -7733,14 +7772,7 @@ try {
7733
7772
  });
7734
7773
  return result.outputFiles[0]?.text ?? "";
7735
7774
  } finally {
7736
- try {
7737
- unlinkSync(entryFile);
7738
- } catch {
7739
- }
7740
- try {
7741
- unlinkSync(componentFile);
7742
- } catch {
7743
- }
7775
+ rmSync(tmpDir, { recursive: true, force: true });
7744
7776
  }
7745
7777
  }
7746
7778
  function buildRenderHTML(bundledCode, cssTokens, fit) {
@@ -16344,7 +16376,11 @@ async function assembleGenerationResult(input) {
16344
16376
  generationTimeMs: totalMs,
16345
16377
  turnsUsed: telemetry.turnsUsed,
16346
16378
  passesUsed: 1,
16347
- evalResult: telemetry.evalResult,
16379
+ // ggui#1261 — the contract-feedback round's record rides the result the
16380
+ // generation ends with (whose probe stamp is the post-round verdict).
16381
+ evalResult: telemetry.evalResult !== void 0 && telemetry.contractFeedback !== void 0 ? { ...telemetry.evalResult, contractFeedback: telemetry.contractFeedback } : telemetry.evalResult,
16382
+ // ggui#404 — present only when the same-exchange guard ended the run.
16383
+ ...telemetry.sameExchangeBreak !== void 0 ? { sameExchangeBreak: telemetry.sameExchangeBreak } : {},
16348
16384
  needsBackgroundImprovement,
16349
16385
  selfCheckPassed: telemetry.selfCheckPassed,
16350
16386
  timing,
@@ -17099,7 +17135,8 @@ ${iconNamesCache}`;
17099
17135
  lastDiffFailed: false,
17100
17136
  isEvalFeedback: false,
17101
17137
  iconNamesCache,
17102
- preWarmedContext
17138
+ preWarmedContext,
17139
+ sameExchangeBreak: { tool: call.name, repeats: iconRepeats }
17103
17140
  };
17104
17141
  }
17105
17142
  return {
@@ -17299,7 +17336,8 @@ Repeated error: ${errText}
17299
17336
  lastDiffFailed: false,
17300
17337
  isEvalFeedback: false,
17301
17338
  iconNamesCache,
17302
- preWarmedContext
17339
+ preWarmedContext,
17340
+ sameExchangeBreak: { tool: call.name, repeats: sameExchangeRepeats }
17303
17341
  };
17304
17342
  }
17305
17343
  const turnDiffFailed = !!result.error && result.result.includes("DIFF");
@@ -17488,6 +17526,20 @@ function formatRuntimeProbeFeedback(issue) {
17488
17526
  return `[runtime] ${issue.category}${subcat}: ${issue.description}
17489
17527
  Fix: ${issue.fix ?? ""}`;
17490
17528
  }
17529
+ var FEEDBACK_PROBE_CHECKS = ["prop-sensitivity"];
17530
+ var CONTRACT_FEEDBACK_BONUS = 1;
17531
+ function isFeedbackProbeFail(issue) {
17532
+ const sub = issue.subcategory;
17533
+ if (issue.result !== "fail" || typeof sub !== "string") return false;
17534
+ return FEEDBACK_PROBE_CHECKS.some((check) => sub === `runtime:${check}` || sub.startsWith(`runtime:${check}:`));
17535
+ }
17536
+ function contractFeedbackRecord(fails, sourceBefore) {
17537
+ return { firedOn: fails.flatMap((i) => i.subcategory !== void 0 ? [i.subcategory] : []), sourceBefore };
17538
+ }
17539
+ function contractFeedbackFails(exitProbe, alreadyFedBack) {
17540
+ if (!exitProbe.fired) return [];
17541
+ return exitProbe.probeIssues.filter((i) => isFeedbackProbeFail(i) && !alreadyFedBack.has(fingerprintFail(i)));
17542
+ }
17491
17543
  async function runEvalRound(ctx, input) {
17492
17544
  const {
17493
17545
  workspace,
@@ -17677,7 +17729,9 @@ async function runEvalRound(ctx, input) {
17677
17729
  costTracker.record(evaluationAgent.model, llmResult.inputTokens, llmResult.outputTokens);
17678
17730
  evalTokens = {
17679
17731
  input: llmResult.inputTokens,
17680
- output: llmResult.outputTokens
17732
+ output: llmResult.outputTokens,
17733
+ ...llmResult.cacheReadTokens !== void 0 ? { cacheRead: llmResult.cacheReadTokens } : {},
17734
+ ...llmResult.cacheCreationTokens !== void 0 ? { cacheCreation: llmResult.cacheCreationTokens } : {}
17681
17735
  };
17682
17736
  }
17683
17737
  const allIssues = [
@@ -17762,6 +17816,34 @@ async function runEvalRound(ctx, input) {
17762
17816
  lastDiffFailed: false
17763
17817
  };
17764
17818
  }
17819
+ const contractFails = contractFeedbackFails(exitProbe, prevFailFingerprints);
17820
+ if (contractFails.length > 0 && evalRoundsUsed < maxEvalRounds + CONTRACT_FEEDBACK_BONUS) {
17821
+ const lines = contractFails.slice(0, MAX_FEEDBACK_ISSUES).map(formatRuntimeProbeFeedback);
17822
+ evalResult = {
17823
+ ...evalResult,
17824
+ issues: [...allIssues, ...exitProbe.probeIssues],
17825
+ pass: allPass,
17826
+ runtimeProbe: exitProbe.meta
17827
+ };
17828
+ console.log(
17829
+ `[simple] eval round ${evalRoundsUsed}: tier-1/2 clean BUT contract probe fail \u2014 granting +1 turn (ggui#1261; ${contractFails.map((i) => i.subcategory).join(", ")})`
17830
+ );
17831
+ return {
17832
+ control: "feedback",
17833
+ evalDone: false,
17834
+ evalResult,
17835
+ evalRoundsUsed,
17836
+ prevModeSubcats: updatedPrevModeSubcats,
17837
+ prevFailFingerprints: /* @__PURE__ */ new Set([...currFailFingerprints, ...contractFails.map(fingerprintFail)]),
17838
+ preWarmedContext,
17839
+ evalTokens,
17840
+ evalLlmMs,
17841
+ lastResultText: lines.join("\n\n"),
17842
+ isEvalFeedback: true,
17843
+ lastDiffFailed: false,
17844
+ contractFeedback: contractFeedbackRecord(contractFails, currentSource)
17845
+ };
17846
+ }
17765
17847
  evalResult = {
17766
17848
  ...evalResult,
17767
17849
  issues: [...allIssues, ...exitProbe.probeIssues],
@@ -17801,6 +17883,28 @@ async function runEvalRound(ctx, input) {
17801
17883
  issues: [...evalResult.issues, ...stuckExitProbe.probeIssues],
17802
17884
  runtimeProbe: stuckExitProbe.meta
17803
17885
  };
17886
+ const stuckContractFails = contractFeedbackFails(stuckExitProbe, prevFailFingerprints);
17887
+ if (stuckContractFails.length > 0 && evalRoundsUsed < maxEvalRounds + CONTRACT_FEEDBACK_BONUS) {
17888
+ const lines = stuckContractFails.slice(0, MAX_FEEDBACK_ISSUES).map(formatRuntimeProbeFeedback);
17889
+ console.log(
17890
+ `[simple] eval round ${evalRoundsUsed}: stuck BUT contract probe fail never delivered \u2014 granting +1 turn (ggui#1261; ${stuckContractFails.map((i) => i.subcategory).join(", ")})`
17891
+ );
17892
+ return {
17893
+ control: "feedback",
17894
+ evalDone: false,
17895
+ evalResult,
17896
+ evalRoundsUsed,
17897
+ prevModeSubcats: updatedPrevModeSubcats,
17898
+ prevFailFingerprints: /* @__PURE__ */ new Set([...currFailFingerprints, ...stuckContractFails.map(fingerprintFail)]),
17899
+ preWarmedContext,
17900
+ evalTokens,
17901
+ evalLlmMs,
17902
+ lastResultText: lines.join("\n\n"),
17903
+ isEvalFeedback: true,
17904
+ lastDiffFailed: false,
17905
+ contractFeedback: contractFeedbackRecord(stuckContractFails, currentSource)
17906
+ };
17907
+ }
17804
17908
  return {
17805
17909
  control: "break",
17806
17910
  evalDone: false,
@@ -17855,6 +17959,33 @@ async function runEvalRound(ctx, input) {
17855
17959
  lastDiffFailed: false
17856
17960
  };
17857
17961
  }
17962
+ const capContractFails = allowRuntimeExtension ? [] : contractFeedbackFails(exitProbe, prevFailFingerprints);
17963
+ if (capContractFails.length > 0 && evalRoundsUsed < maxEvalRounds + CONTRACT_FEEDBACK_BONUS) {
17964
+ const contractLines = capContractFails.slice(0, MAX_FEEDBACK_ISSUES).map(formatRuntimeProbeFeedback);
17965
+ evalResult = {
17966
+ ...evalResult,
17967
+ issues: [...evalResult.issues, ...exitProbe.probeIssues],
17968
+ runtimeProbe: exitProbe.meta
17969
+ };
17970
+ console.log(
17971
+ `[simple] eval round ${evalRoundsUsed}: contract probe fail active at cap \u2014 granting +1 retry (ggui#1261; ${capContractFails.map((i) => i.subcategory).join(", ")})`
17972
+ );
17973
+ return {
17974
+ control: "feedback",
17975
+ evalDone: false,
17976
+ evalResult,
17977
+ evalRoundsUsed,
17978
+ prevModeSubcats: updatedPrevModeSubcats,
17979
+ prevFailFingerprints: /* @__PURE__ */ new Set([...currFailFingerprints, ...capContractFails.map(fingerprintFail)]),
17980
+ preWarmedContext,
17981
+ evalTokens,
17982
+ evalLlmMs,
17983
+ lastResultText: contractLines.join("\n\n"),
17984
+ isEvalFeedback: true,
17985
+ lastDiffFailed: false,
17986
+ contractFeedback: contractFeedbackRecord(capContractFails, currentSource)
17987
+ };
17988
+ }
17858
17989
  if (!allowRuntimeExtension) {
17859
17990
  evalResult = {
17860
17991
  ...evalResult,
@@ -17997,6 +18128,8 @@ function createTelemetry() {
17997
18128
  totalIn: 0,
17998
18129
  totalOut: 0,
17999
18130
  evalResult: void 0,
18131
+ contractFeedback: void 0,
18132
+ sameExchangeBreak: void 0,
18000
18133
  compiledCode: "",
18001
18134
  pairedSource: "",
18002
18135
  selfCheckPassed: false,
@@ -18005,6 +18138,40 @@ function createTelemetry() {
18005
18138
  codingMs: 0
18006
18139
  };
18007
18140
  }
18141
+ function absorbTokens(telemetry, usage) {
18142
+ telemetry.totalIn += usage.input;
18143
+ telemetry.totalOut += usage.output;
18144
+ if (usage.cacheRead !== void 0) {
18145
+ telemetry.cacheReadTokens = (telemetry.cacheReadTokens ?? 0) + usage.cacheRead;
18146
+ }
18147
+ if (usage.cacheCreation !== void 0) {
18148
+ telemetry.cacheCreationTokens = (telemetry.cacheCreationTokens ?? 0) + usage.cacheCreation;
18149
+ }
18150
+ }
18151
+ function absorbTurn(telemetry, turn) {
18152
+ absorbTokens(telemetry, {
18153
+ input: turn.tokens.input,
18154
+ output: turn.tokens.output,
18155
+ ...turn.cacheReadTokens !== void 0 ? { cacheRead: turn.cacheReadTokens } : {},
18156
+ ...turn.cacheCreationTokens !== void 0 ? { cacheCreation: turn.cacheCreationTokens } : {}
18157
+ });
18158
+ telemetry.cumulativeLlmMs += turn.llmMs;
18159
+ telemetry.cumulativeToolMs += turn.toolMs;
18160
+ if (turn.phase) {
18161
+ if (turn.phase === "scaffold") telemetry.counters.phases.scaffold++;
18162
+ else if (turn.phase === "fill") telemetry.counters.phases.fill++;
18163
+ else if (turn.phase === "impl") telemetry.counters.phases.impl++;
18164
+ else if (turn.phase === "eval-fix") telemetry.counters.phases.evalFix++;
18165
+ else telemetry.counters.phases.patch++;
18166
+ }
18167
+ if (turn.outcome) {
18168
+ if (turn.outcome === "PASS") telemetry.counters.outcomes.pass++;
18169
+ else if (turn.outcome === "PATCH_INVALID") telemetry.counters.outcomes.patchInvalid++;
18170
+ else if (turn.outcome === "DIFF_FAIL") telemetry.counters.outcomes.diffFail++;
18171
+ else telemetry.counters.outcomes.selfCheckFail++;
18172
+ }
18173
+ if (turn.sameExchangeBreak !== void 0) telemetry.sameExchangeBreak = turn.sameExchangeBreak;
18174
+ }
18008
18175
  function createGenerateTaskRunner(input) {
18009
18176
  const { session, params, classification, telemetry } = input;
18010
18177
  const maxTurns = input.maxTurns ?? 30;
@@ -18057,31 +18224,9 @@ function createGenerateTaskRunner(input) {
18057
18224
  preWarmPromise: session.preWarmPromise
18058
18225
  }
18059
18226
  );
18060
- telemetry.totalIn += turn.tokens.input;
18061
- telemetry.totalOut += turn.tokens.output;
18062
- if (turn.cacheReadTokens !== void 0) {
18063
- telemetry.cacheReadTokens = (telemetry.cacheReadTokens ?? 0) + turn.cacheReadTokens;
18064
- }
18065
- if (turn.cacheCreationTokens !== void 0) {
18066
- telemetry.cacheCreationTokens = (telemetry.cacheCreationTokens ?? 0) + turn.cacheCreationTokens;
18067
- }
18068
- telemetry.cumulativeLlmMs += turn.llmMs;
18069
- telemetry.cumulativeToolMs += turn.toolMs;
18227
+ absorbTurn(telemetry, turn);
18070
18228
  iconNamesCache = turn.iconNamesCache;
18071
18229
  preWarmedContext = turn.preWarmedContext;
18072
- if (turn.phase) {
18073
- if (turn.phase === "scaffold") telemetry.counters.phases.scaffold++;
18074
- else if (turn.phase === "fill") telemetry.counters.phases.fill++;
18075
- else if (turn.phase === "impl") telemetry.counters.phases.impl++;
18076
- else if (turn.phase === "eval-fix") telemetry.counters.phases.evalFix++;
18077
- else telemetry.counters.phases.patch++;
18078
- }
18079
- if (turn.outcome) {
18080
- if (turn.outcome === "PASS") telemetry.counters.outcomes.pass++;
18081
- else if (turn.outcome === "PATCH_INVALID") telemetry.counters.outcomes.patchInvalid++;
18082
- else if (turn.outcome === "DIFF_FAIL") telemetry.counters.outcomes.diffFail++;
18083
- else telemetry.counters.outcomes.selfCheckFail++;
18084
- }
18085
18230
  if (turn.control === "break") break;
18086
18231
  if (turn.control === "continue") {
18087
18232
  lastResultText = turn.lastResultText;
@@ -18157,9 +18302,9 @@ function createGenerateTaskRunner(input) {
18157
18302
  prevFailFingerprints = round.prevFailFingerprints;
18158
18303
  preWarmedContext = round.preWarmedContext;
18159
18304
  if (round.evalResult) telemetry.evalResult = round.evalResult;
18305
+ if (round.contractFeedback) telemetry.contractFeedback = round.contractFeedback;
18160
18306
  evalDone = round.evalDone;
18161
- telemetry.totalIn += round.evalTokens.input;
18162
- telemetry.totalOut += round.evalTokens.output;
18307
+ absorbTokens(telemetry, round.evalTokens);
18163
18308
  if (round.control === "break") break;
18164
18309
  lastResultText = round.lastResultText;
18165
18310
  isEvalFeedback = round.isEvalFeedback;
@@ -18693,6 +18838,166 @@ function withArmRecord(result, params) {
18693
18838
  };
18694
18839
  }
18695
18840
 
18841
+ // src/pin-fixtures.ts
18842
+ var PIN_FIXTURES = [
18843
+ {
18844
+ userRequest: "A todo list where each item can be toggled done",
18845
+ shellType: "chat",
18846
+ screen: "mobile",
18847
+ contract: {
18848
+ propsSpec: {
18849
+ properties: {
18850
+ todos: {
18851
+ required: true,
18852
+ description: "The todos to show",
18853
+ schema: {
18854
+ type: "array",
18855
+ items: {
18856
+ type: "object",
18857
+ properties: {
18858
+ id: { type: "string" },
18859
+ title: { type: "string" },
18860
+ done: { type: "boolean" }
18861
+ }
18862
+ }
18863
+ }
18864
+ }
18865
+ }
18866
+ },
18867
+ actionSpec: {
18868
+ toggleTodo: {
18869
+ label: "Toggle",
18870
+ description: "Flip the done state of one todo",
18871
+ schema: {
18872
+ type: "object",
18873
+ properties: { id: { type: "string" } },
18874
+ required: ["id"]
18875
+ }
18876
+ }
18877
+ }
18878
+ }
18879
+ },
18880
+ {
18881
+ userRequest: "A kanban board with live task updates and a selected card",
18882
+ shellType: "fullscreen",
18883
+ screen: "desktop",
18884
+ contract: {
18885
+ propsSpec: {
18886
+ properties: {
18887
+ columns: {
18888
+ required: true,
18889
+ schema: { type: "array", items: { type: "string" } }
18890
+ },
18891
+ tasks: {
18892
+ required: false,
18893
+ schema: {
18894
+ type: "array",
18895
+ items: {
18896
+ type: "object",
18897
+ properties: {
18898
+ id: { type: "string" },
18899
+ column: { type: "string" },
18900
+ title: { type: "string" }
18901
+ }
18902
+ }
18903
+ }
18904
+ }
18905
+ }
18906
+ },
18907
+ streamSpec: {
18908
+ taskUpdates: {
18909
+ description: "Task CRUD feed",
18910
+ schema: {
18911
+ type: "object",
18912
+ properties: {
18913
+ action: { type: "string", enum: ["create", "move", "edit", "delete"] },
18914
+ id: { type: "string" }
18915
+ }
18916
+ }
18917
+ }
18918
+ },
18919
+ contextSpec: {
18920
+ selectedId: { schema: { type: "string", nullable: true } }
18921
+ }
18922
+ }
18923
+ }
18924
+ ];
18925
+
18926
+ // src/generator-build.ts
18927
+ var TEMPLATE_FIXTURE_BOUNDARY = "\n<<<pin-fixture-boundary>>>\n";
18928
+ function templateSha256(text) {
18929
+ return createHash("sha256").update(text, "utf8").digest("hex");
18930
+ }
18931
+ function renderPromptTemplates(designMode) {
18932
+ return PIN_FIXTURES.map((f) => {
18933
+ switch (designMode) {
18934
+ case "constrained":
18935
+ return buildSystemPrompt2(f.userRequest, f.shellType, f.screen);
18936
+ case "free":
18937
+ return buildSystemPrompt2(f.userRequest, f.shellType, f.screen, void 0, void 0, void 0, "free");
18938
+ }
18939
+ }).join(TEMPLATE_FIXTURE_BOUNDARY);
18940
+ }
18941
+ function renderBoilerplateTemplates(designMode) {
18942
+ return PIN_FIXTURES.map((f) => {
18943
+ switch (designMode) {
18944
+ case "constrained":
18945
+ return generateBoilerplate(f.userRequest, f.contract, f.shellType, f.screen);
18946
+ case "free":
18947
+ return generateBoilerplate(f.userRequest, f.contract, f.shellType, f.screen, void 0, void 0, "free");
18948
+ }
18949
+ }).join(TEMPLATE_FIXTURE_BOUNDARY);
18950
+ }
18951
+ var PACKAGE_NAME = "@ggui-ai/ui-gen";
18952
+ var MAX_PACKAGE_DEPTH = 6;
18953
+ function parsePackageJson(text) {
18954
+ try {
18955
+ const parsed = JSON.parse(text);
18956
+ return parsed;
18957
+ } catch (err) {
18958
+ if (err instanceof SyntaxError) return void 0;
18959
+ throw err;
18960
+ }
18961
+ }
18962
+ function findPackageVersion(startDir = dirname(fileURLToPath(import.meta.url))) {
18963
+ let dir = startDir;
18964
+ for (let i = 0; i < MAX_PACKAGE_DEPTH; i++) {
18965
+ const candidate = join(dir, "package.json");
18966
+ const pkg = existsSync(candidate) ? parsePackageJson(readFileSync(candidate, "utf8")) : void 0;
18967
+ if (pkg !== void 0) {
18968
+ if (typeof pkg === "object" && pkg !== null && "name" in pkg && pkg.name === PACKAGE_NAME && "version" in pkg && typeof pkg.version === "string") {
18969
+ return pkg.version;
18970
+ }
18971
+ }
18972
+ const parent = dirname(dir);
18973
+ if (parent === dir) break;
18974
+ dir = parent;
18975
+ }
18976
+ return void 0;
18977
+ }
18978
+ var versionRead = false;
18979
+ var packageVersion;
18980
+ var identities = /* @__PURE__ */ new Map();
18981
+ function generatorBuild(designMode) {
18982
+ const key = `${designMode}|${process.env.GGUI_PITFALLS ?? ""}|${process.env.GGUI_NEW_PITFALLS ?? ""}`;
18983
+ const known = identities.get(key);
18984
+ if (known !== void 0) return known;
18985
+ if (!versionRead) {
18986
+ packageVersion = findPackageVersion();
18987
+ versionRead = true;
18988
+ }
18989
+ const identity = {
18990
+ ...packageVersion !== void 0 ? { version: packageVersion } : {},
18991
+ mode: designMode,
18992
+ digests: {
18993
+ promptTemplateSha256: templateSha256(renderPromptTemplates(designMode)),
18994
+ boilerplateTemplateSha256: templateSha256(renderBoilerplateTemplates(designMode))
18995
+ }
18996
+ };
18997
+ identities.set(key, identity);
18998
+ return identity;
18999
+ }
19000
+
18696
19001
  // src/create-ui-generator.ts
18697
19002
  init_styling_profile();
18698
19003
 
@@ -18720,7 +19025,7 @@ var SHELL_HINTS = {
18720
19025
  - Padding: use var(--ggui-spacing-2) for inner elements, var(--ggui-spacing-4) for sections. Tight.
18721
19026
  - Scrolling: parent scrolls (chat feed) \u2014 component should NOT have internal scroll
18722
19027
  - Layout: single column, compact. No sidebar. Stack everything vertically.
18723
- - Width: width: 100% (fills parent bubble). Do NOT set max-width.`,
19028
+ - 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.`,
18724
19029
  fullscreen: `**Fullscreen Shell \u2014 Takes over entire viewport**
18725
19030
  - Container: the whole viewport, edge to edge \u2014 the frame stretches your root to its full height (you never set a viewport height yourself)
18726
19031
  - Chrome: NONE \u2014 your component IS the entire UI. You own all visual chrome.
@@ -18961,6 +19266,7 @@ function injectVariance(userPrompt, variance, options) {
18961
19266
  }
18962
19267
 
18963
19268
  // src/create-ui-generator.ts
19269
+ init_design_mode();
18964
19270
  init_provider_router();
18965
19271
  var DEFAULT_TIER = "default";
18966
19272
  var DEFAULT_MODEL = "anthropic/claude-haiku-4-5";
@@ -19028,6 +19334,7 @@ function createUiGenerator(options = {}) {
19028
19334
  const rendering = mapRendering(input.rendering);
19029
19335
  const promptWithRendering = rendering ? injectRenderingContext(input.request.prompt, rendering) : input.request.prompt;
19030
19336
  const canvas = rendering ? canvasForRenderingContext(rendering) : void 0;
19337
+ const shellType = harnessShellForRendering(rendering);
19031
19338
  const promptWithVariance = injectVariance(
19032
19339
  promptWithRendering,
19033
19340
  input.variance,
@@ -19067,6 +19374,8 @@ function createUiGenerator(options = {}) {
19067
19374
  ...onRetry !== void 0 ? { onRetry } : {},
19068
19375
  ...designMode !== void 0 ? { designMode } : {},
19069
19376
  ...canvas !== void 0 ? { canvas } : {},
19377
+ // ggui#1279 — the shell the declared rendering composes for (chat only; see harnessShellForRendering).
19378
+ ...shellType !== void 0 ? { shellType } : {},
19070
19379
  ...input.profile !== void 0 ? { profile: input.profile } : {},
19071
19380
  // Fixture props reach the in-loop probe + visual round only (never the prompt, never identity).
19072
19381
  ...input.fixtureProps !== void 0 ? { fixtureProps: input.fixtureProps } : {}
@@ -19081,6 +19390,8 @@ function createUiGenerator(options = {}) {
19081
19390
  latencyMs: Date.now() - startedAt,
19082
19391
  cacheHit: false,
19083
19392
  attempts: result.turnsUsed,
19393
+ // ggui#1280 — the build that made this generation (template keys, never per-request).
19394
+ build: generatorBuild(designMode ?? DEFAULT_DESIGN_MODE),
19084
19395
  // Prompt-cache counters are provider-specific (Claude reports
19085
19396
  // them; others omit). Pass through truthfully — absent on the
19086
19397
  // adapter result stays absent here, never defaulted to 0.
@@ -19115,7 +19426,9 @@ function createUiGenerator(options = {}) {
19115
19426
  inputTokens: 0,
19116
19427
  outputTokens: 0,
19117
19428
  latencyMs: Date.now() - startedAt,
19118
- cacheHit: false
19429
+ cacheHit: false,
19430
+ // ggui#1280 — a failed generation still names the build that failed.
19431
+ build: generatorBuild(designMode ?? DEFAULT_DESIGN_MODE)
19119
19432
  }
19120
19433
  };
19121
19434
  } finally {
@@ -19167,6 +19480,16 @@ function mapLlmProviderToDispatchProvider(provider) {
19167
19480
  return "openrouter";
19168
19481
  }
19169
19482
  }
19483
+ function harnessShellForRendering(rendering) {
19484
+ if (rendering === void 0) return void 0;
19485
+ switch (rendering.shell) {
19486
+ case "chat":
19487
+ return "chat";
19488
+ case "fullscreen":
19489
+ case "partial":
19490
+ return void 0;
19491
+ }
19492
+ }
19170
19493
  function mapRendering(rendering) {
19171
19494
  if (!rendering) return void 0;
19172
19495
  return {