iterate-plugin 3.6.0 → 3.8.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/lib/client.js CHANGED
@@ -1,3 +1,4 @@
1
+ /* iterate-plugin client source-digest: fc3f99c3c9a74c891ce54564ac49e7a336441432dfafe0cb21775ee4afe8f411 */
1
2
  window.__ModuleLoader__.load({ id: "iterate-plugin", factory: (require) => {
2
3
  var module = { exports: {} };
3
4
  var exports = module.exports;
@@ -62,10 +63,10 @@ function safeKeys(o) {
62
63
  }
63
64
  function coerceJsonNode(v) {
64
65
  if (typeof v !== "string") return v;
65
- const t = v.trim();
66
- if (!t.startsWith("{") && !t.startsWith("[")) return v;
66
+ const t2 = v.trim();
67
+ if (!t2.startsWith("{") && !t2.startsWith("[")) return v;
67
68
  try {
68
- return JSON.parse(t);
69
+ return JSON.parse(t2);
69
70
  } catch {
70
71
  return v;
71
72
  }
@@ -175,7 +176,8 @@ function isReviewReport(obj) {
175
176
  obj
176
177
  );
177
178
  const convergence = safeGet(o, "convergence");
178
- return typeof convergence === "object" && convergence !== null && Array.isArray(safeGet(o, "findings")) && Array.isArray(safeGet(o, "rounds"));
179
+ if (!convergence || typeof convergence !== "object" || Array.isArray(convergence)) return false;
180
+ return Array.isArray(safeGet(o, "findings")) && Array.isArray(safeGet(o, "rounds"));
179
181
  }
180
182
  function findReportInObject(obj, seen, maxDepth = 20) {
181
183
  if (maxDepth <= 0) return null;
@@ -338,11 +340,11 @@ function extractTranscript(obj, seen, depth) {
338
340
  obj
339
341
  );
340
342
  if (safeGet(o, "operation") === "capture") {
341
- const t = safeGet(o, "transcript");
342
- if (t && typeof t === "object" && isTranscriptManifest(t)) {
343
+ const t2 = safeGet(o, "transcript");
344
+ if (t2 && typeof t2 === "object" && isTranscriptManifest(t2)) {
343
345
  return attachLive(
344
346
  /** @type {Record<string, unknown>} */
345
- t,
347
+ t2,
346
348
  o
347
349
  );
348
350
  }
@@ -457,7 +459,11 @@ function scanSessionForTranscript(session) {
457
459
  if (found) return found;
458
460
  }
459
461
  }
460
- return null;
462
+ const loose = findFirstInObject(session, isTranscriptManifest);
463
+ return loose ? (
464
+ /** @type {Record<string, unknown>} */
465
+ loose
466
+ ) : null;
461
467
  }
462
468
  function normalizeValidations(raw) {
463
469
  if (!Array.isArray(raw)) return [];
@@ -502,12 +508,12 @@ function normalizeTranscript(manifest) {
502
508
  ) : [];
503
509
  const rounds = asArray(safeGet(src, "rounds")).map((r) => ({
504
510
  round: asNum(safeGet(r, "round")),
505
- threads: asArray(safeGet(r, "threads")).map((t) => ({
506
- dimension: asStr(safeGet(t, "dimension")),
507
- attempt: asNum(safeGet(t, "attempt")),
508
- messages: asArray(safeGet(t, "messages")).map((m) => typeof m === "string" ? m : ""),
509
- readFiles: asArray(safeGet(t, "readFiles")).map((f) => typeof f === "string" ? f : ""),
510
- findings: asArray(safeGet(t, "findings")).map((f) => ({ ...f }))
511
+ threads: asArray(safeGet(r, "threads")).map((t2) => ({
512
+ dimension: asStr(safeGet(t2, "dimension")),
513
+ attempt: asNum(safeGet(t2, "attempt")),
514
+ messages: asArray(safeGet(t2, "messages")).map((m) => typeof m === "string" ? m : ""),
515
+ readFiles: asArray(safeGet(t2, "readFiles")).map((f) => typeof f === "string" ? f : ""),
516
+ findings: asArray(safeGet(t2, "findings")).map((f) => ({ ...f }))
511
517
  }))
512
518
  }));
513
519
  const cp = safeGet(src, "checkpoint");
@@ -550,7 +556,7 @@ function normalizeTranscript(manifest) {
550
556
  target: asStr(safeGet(e, "target"))
551
557
  })),
552
558
  checkpoint,
553
- timeline: asArray(safeGet(src, "timeline")).map((t) => ({ ...t })),
559
+ timeline: asArray(safeGet(src, "timeline")).map((t2) => ({ ...t2 })),
554
560
  // #6: per-round validation rows (command / exitCode / allowed), captured
555
561
  // by the workflow and persisted through `iterate_transcript capture`.
556
562
  validations: normalizeValidations(safeGet(src, "validations")),
@@ -731,10 +737,12 @@ function normalizeQualityGateSnapshot(raw) {
731
737
  }
732
738
  function scanSessionForQualityGate(session) {
733
739
  const node = latestToolResultNode(session, "iterate_quality_gate", findQualityGateInObject);
734
- if (node === null) return null;
735
- const found = findQualityGateInObject(node);
736
- if (!found) return null;
737
- return normalizeQualityGateSnapshot(found);
740
+ if (node !== null) {
741
+ const found = findQualityGateInObject(node);
742
+ if (found) return normalizeQualityGateSnapshot(found);
743
+ }
744
+ const loose = findFirstInObject(session, isQualityGateSnapshot);
745
+ return loose ? normalizeQualityGateSnapshot(loose) : null;
738
746
  }
739
747
  function normalizeExperienceBankResult(raw) {
740
748
  const src = raw && typeof raw === "object" ? (
@@ -774,17 +782,19 @@ function normalizeExperienceBankResult(raw) {
774
782
  hitCount: asNum(safeGet(rec, "hitCount")),
775
783
  lastHitAt: asStr(safeGet(rec, "lastHitAt")) || null,
776
784
  files: asArr(safeGet(rec, "files")).map((f) => typeof f === "string" ? f : ""),
777
- tags: asArr(safeGet(rec, "tags")).map((t) => typeof t === "string" ? t : "")
785
+ tags: asArr(safeGet(rec, "tags")).map((t2) => typeof t2 === "string" ? t2 : "")
778
786
  };
779
787
  })
780
788
  };
781
789
  }
782
790
  function scanSessionForExperienceBank(session) {
783
791
  const node = latestToolResultNode(session, "iterate_experience", findExperienceResultInObject);
784
- if (node === null) return null;
785
- const found = findExperienceResultInObject(node);
786
- if (!found) return null;
787
- return normalizeExperienceBankResult(found);
792
+ if (node !== null) {
793
+ const found = findExperienceResultInObject(node);
794
+ if (found) return normalizeExperienceBankResult(found);
795
+ }
796
+ const loose = findFirstInObject(session, isExperienceBankResult);
797
+ return loose ? normalizeExperienceBankResult(loose) : null;
788
798
  }
789
799
  function normalizeDefenseEventsResult(raw) {
790
800
  const src = raw && typeof raw === "object" ? (
@@ -837,10 +847,12 @@ function normalizeDefenseEventsResult(raw) {
837
847
  }
838
848
  function scanSessionForDefenseEvents(session) {
839
849
  const node = latestToolResultNode(session, "iterate_defense_events", findDefenseResultInObject);
840
- if (node === null) return null;
841
- const found = findDefenseResultInObject(node);
842
- if (!found) return null;
843
- return normalizeDefenseEventsResult(found);
850
+ if (node !== null) {
851
+ const found = findDefenseResultInObject(node);
852
+ if (found) return normalizeDefenseEventsResult(found);
853
+ }
854
+ const loose = findFirstInObject(session, isDefenseEventsResult);
855
+ return loose ? normalizeDefenseEventsResult(loose) : null;
844
856
  }
845
857
  function isRunSummary(obj) {
846
858
  if (!obj || typeof obj !== "object") return false;
@@ -1321,12 +1333,12 @@ function toolCalledInSession(session, toolName) {
1321
1333
  toolCalls
1322
1334
  ) {
1323
1335
  if (!call || typeof call !== "object") continue;
1324
- const t = String(safeGet(
1336
+ const t2 = String(safeGet(
1325
1337
  /** @type {Record<string, unknown>} */
1326
1338
  call,
1327
1339
  "tool"
1328
1340
  ) ?? "");
1329
- if (t === toolName || t.endsWith(toolName)) return true;
1341
+ if (t2 === toolName || t2.endsWith(toolName)) return true;
1330
1342
  }
1331
1343
  }
1332
1344
  const messages = safeGet(s, "messages");
@@ -1368,8 +1380,11 @@ function toolCalledInSession(session, toolName) {
1368
1380
  }
1369
1381
  return false;
1370
1382
  }
1371
- function diskEmptyStateText(pulled, topic) {
1372
- const subject = topic || "\u6570\u636E";
1383
+ function diskEmptyStateText(pulled, topic, lang = "zh") {
1384
+ const subject = topic || (lang === "en" ? "data" : "\u6570\u636E");
1385
+ if (lang === "en") {
1386
+ return pulled ? `Pulled from disk: ${subject} has no records yet (the disk is empty).` : `Not pulled from disk yet: ${subject} lives under the project's .iterate/ \u2014 copy the "Pull disk snapshot" instruction back into the session and it will fill in.`;
1387
+ }
1373
1388
  return pulled ? `\u5DF2\u4ECE\u78C1\u76D8\u62C9\u53D6\uFF1A${subject}\u6682\u65E0\u8BB0\u5F55\uFF08\u78C1\u76D8\u65E0\u6570\u636E\uFF09\u3002` : `\u5C1A\u672A\u4ECE\u78C1\u76D8\u62C9\u53D6${subject}\uFF1A\u6570\u636E\u5B58\u653E\u5728\u9879\u76EE .iterate/ \u4E0B\uFF0C\u70B9\u300C\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u300D\u590D\u5236\u6307\u4EE4\u53D1\u56DE\u4F1A\u8BDD\u5373\u53EF\u56DE\u586B\u3002`;
1374
1389
  }
1375
1390
  function normalizeFindingFilter(filter) {
@@ -1505,27 +1520,32 @@ function trendMax(points) {
1505
1520
  }
1506
1521
  return max;
1507
1522
  }
1508
- function stoppedReasonLabel(reason) {
1509
- switch (reason) {
1510
- case "converged":
1511
- return "\u5DF2\u7ED3\u675F \xB7 \u5DF2\u6536\u655B\uFF08\u65E0\u65B0\u53D1\u73B0\uFF09";
1512
- case "max_rounds_reached":
1513
- return "\u5DF2\u7ED3\u675F \xB7 \u8FBE\u5230\u8F6E\u6570\u4E0A\u9650";
1514
- case "aborted_by_validation":
1515
- return "\u5DF2\u7ED3\u675F \xB7 \u9A8C\u8BC1\u5931\u8D25\u540E\u56DE\u6EDA\u505C\u6B62";
1516
- case "aborted_by_config":
1517
- return "\u5DF2\u7ED3\u675F \xB7 \u9A8C\u8BC1\u547D\u4EE4\u4E0D\u5728\u767D\u540D\u5355\uFF08\u914D\u7F6E\u9700\u4FEE\u590D\uFF09";
1518
- case "inconclusive":
1519
- return "\u5DF2\u7ED3\u675F \xB7 \u8F6E\u6B21\u7ED3\u8BBA\u4E0D\u660E\uFF08\u5BA1\u67E5\u8F93\u51FA\u65E0\u6548\uFF09";
1520
- case "schema_invalid":
1521
- return "\u5DF2\u7ED3\u675F \xB7 \u5BA1\u67E5\u8F93\u51FA\u8FDE\u7EED schema \u6821\u9A8C\u5931\u8D25";
1522
- case "no_usable_reviewer_output":
1523
- return "\u5DF2\u7ED3\u675F \xB7 \u65E0\u53EF\u7528\u5BA1\u67E5\u8F93\u51FA";
1524
- default:
1525
- return reason ? `\u5DF2\u7ED3\u675F \xB7 ${reason}` : "\u5DF2\u7ED3\u675F";
1526
- }
1523
+ function stoppedReasonLabel(reason, lang = "zh") {
1524
+ const zh = {
1525
+ converged: "\u5DF2\u7ED3\u675F \xB7 \u5DF2\u6536\u655B\uFF08\u65E0\u65B0\u53D1\u73B0\uFF09",
1526
+ max_rounds_reached: "\u5DF2\u7ED3\u675F \xB7 \u8FBE\u5230\u8F6E\u6570\u4E0A\u9650",
1527
+ aborted_by_validation: "\u5DF2\u7ED3\u675F \xB7 \u9A8C\u8BC1\u5931\u8D25\u540E\u56DE\u6EDA\u505C\u6B62",
1528
+ aborted_by_config: "\u5DF2\u7ED3\u675F \xB7 \u9A8C\u8BC1\u547D\u4EE4\u4E0D\u5728\u767D\u540D\u5355\uFF08\u914D\u7F6E\u9700\u4FEE\u590D\uFF09",
1529
+ inconclusive: "\u5DF2\u7ED3\u675F \xB7 \u8F6E\u6B21\u7ED3\u8BBA\u4E0D\u660E\uFF08\u5BA1\u67E5\u8F93\u51FA\u65E0\u6548\uFF09",
1530
+ schema_invalid: "\u5DF2\u7ED3\u675F \xB7 \u5BA1\u67E5\u8F93\u51FA\u8FDE\u7EED schema \u6821\u9A8C\u5931\u8D25",
1531
+ no_usable_reviewer_output: "\u5DF2\u7ED3\u675F \xB7 \u65E0\u53EF\u7528\u5BA1\u67E5\u8F93\u51FA"
1532
+ };
1533
+ const en = {
1534
+ converged: "Done \xB7 converged (no new findings)",
1535
+ max_rounds_reached: "Done \xB7 reached the round limit",
1536
+ aborted_by_validation: "Done \xB7 rolled back after a validation failure",
1537
+ aborted_by_config: "Done \xB7 validation command not allow-listed (config needs fixing)",
1538
+ inconclusive: "Done \xB7 inconclusive rounds (invalid review output)",
1539
+ schema_invalid: "Done \xB7 repeated schema validation failures in review output",
1540
+ no_usable_reviewer_output: "Done \xB7 no usable review output"
1541
+ };
1542
+ const table = lang === "en" ? en : zh;
1543
+ if (typeof reason === "string" && Object.hasOwn(table, reason)) return table[reason];
1544
+ const text = typeof reason === "string" && reason !== "" ? reason : "";
1545
+ if (lang === "en") return text ? `Done \xB7 ${text}` : "Done";
1546
+ return text ? `\u5DF2\u7ED3\u675F \xB7 ${text}` : "\u5DF2\u7ED3\u675F";
1527
1547
  }
1528
- function buildCompletionSummary(report) {
1548
+ function buildCompletionSummary(report, lang = "zh") {
1529
1549
  const conv = (
1530
1550
  /** @type {Record<string, unknown>} */
1531
1551
  report.convergence ?? {}
@@ -1534,33 +1554,74 @@ function buildCompletionSummary(report) {
1534
1554
  const total = getTotalRounds(report);
1535
1555
  const stats = severityStats(report);
1536
1556
  const converged = conv.converged === true;
1537
- const reason = converged ? "\u5DF2\u6536\u655B" : `\u5DF2\u8FBE\u6700\u5927\u8F6E\u6570 ${total}`;
1538
1557
  const totalFindings = stats.critical + stats.high + stats.medium + stats.low;
1558
+ if (lang === "en") {
1559
+ const reason2 = converged ? "converged" : `reached the round limit ${total}`;
1560
+ return `iterate review complete \xB7 ${rounds}/${total} rounds \xB7 ${totalFindings} findings \xB7 ${reason2}`;
1561
+ }
1562
+ const reason = converged ? "\u5DF2\u6536\u655B" : `\u5DF2\u8FBE\u6700\u5927\u8F6E\u6570 ${total}`;
1539
1563
  return `iterate \u8BC4\u5BA1\u5B8C\u6210 \xB7 ${rounds}/${total} \u8F6E \xB7 ${totalFindings} \u9879\u53D1\u73B0 \xB7 ${reason}`;
1540
1564
  }
1541
1565
  var CONFIG_EDIT_FIELDS = [
1542
- { key: "goal", label: "\u76EE\u6807", hint: "\u4E00\u53E5\u8BDD\u63CF\u8FF0\u672C\u6B21\u8FED\u4EE3\u76EE\u6807\uFF08\u5B57\u7B26\u4E32\uFF09" },
1543
- { key: "dimensions", label: "\u5BA1\u67E5\u7EF4\u5EA6", hint: '\u6570\u7EC4\uFF0C\u5982 ["correctness","security"]' },
1544
- { key: "max_rounds", label: "\u6700\u5927\u8F6E\u6570", hint: "\u6B63\u6574\u6570" },
1545
- { key: "review.scope", label: "\u5BA1\u67E5\u8303\u56F4", hint: '"full" \u6216 "changed-only"' },
1546
- { key: "reasoning_effort", label: "\u5BA1\u67E5\u63A8\u7406\u5F3A\u5EA6", hint: '"low" / "medium" / "high"\uFF0C\u7F3A\u7701\u8DDF\u968F\u6A21\u578B\u9ED8\u8BA4' },
1566
+ {
1567
+ key: "goal",
1568
+ label: { zh: "\u76EE\u6807", en: "Goal" },
1569
+ hint: { zh: "\u4E00\u53E5\u8BDD\u63CF\u8FF0\u672C\u6B21\u8FED\u4EE3\u76EE\u6807\uFF08\u5B57\u7B26\u4E32\uFF09", en: "One-sentence description of this iteration's goal (a string)" }
1570
+ },
1571
+ {
1572
+ key: "dimensions",
1573
+ label: { zh: "\u5BA1\u67E5\u7EF4\u5EA6", en: "Review dimensions" },
1574
+ hint: { zh: '\u6570\u7EC4\uFF0C\u5982 ["correctness","security"]', en: 'Array, e.g. ["correctness","security"]' }
1575
+ },
1576
+ {
1577
+ key: "max_rounds",
1578
+ label: { zh: "\u6700\u5927\u8F6E\u6570", en: "Max rounds" },
1579
+ hint: { zh: "\u6B63\u6574\u6570", en: "Positive integer" }
1580
+ },
1581
+ {
1582
+ key: "review.scope",
1583
+ label: { zh: "\u5BA1\u67E5\u8303\u56F4", en: "Review scope" },
1584
+ hint: { zh: '"full" \u6216 "changed-only"', en: '"full" or "changed-only"' }
1585
+ },
1586
+ {
1587
+ key: "reasoning_effort",
1588
+ label: { zh: "\u5BA1\u67E5\u63A8\u7406\u5F3A\u5EA6", en: "Review reasoning effort" },
1589
+ hint: { zh: '"low" / "medium" / "high"\uFF0C\u7F3A\u7701\u8DDF\u968F\u6A21\u578B\u9ED8\u8BA4', en: '"low" / "medium" / "high"; omitted = follow the model default' }
1590
+ },
1547
1591
  {
1548
1592
  key: "validation.commands",
1549
- label: "\u9A8C\u8BC1\u547D\u4EE4",
1550
- hint: '\u6570\u7EC4\uFF0C\u6BCF\u9879\u662F\u4E00\u6761\u767D\u540D\u5355\u547D\u4EE4\u5B57\u7B26\u4E32\uFF0C\u5982 ["npm test","npm run lint"]\uFF1B\u6BCF\u8F6E\u9A8C\u8BC1\u9010\u6761\u6267\u884C\u3002\u7559\u7A7A\u6570\u7EC4 = \u4E0D\u9A8C\u8BC1\uFF08\u672C\u8F6E\u8FED\u4EE3\u4E0D\u53D7\u4EFB\u4F55\u6D4B\u8BD5\u4FDD\u62A4\uFF09'
1593
+ label: { zh: "\u9A8C\u8BC1\u547D\u4EE4", en: "Validation commands" },
1594
+ hint: {
1595
+ zh: '\u6570\u7EC4\uFF0C\u6BCF\u9879\u662F\u4E00\u6761\u767D\u540D\u5355\u547D\u4EE4\u5B57\u7B26\u4E32\uFF0C\u5982 ["npm test","npm run lint"]\uFF1B\u6BCF\u8F6E\u9A8C\u8BC1\u9010\u6761\u6267\u884C\u3002\u7559\u7A7A\u6570\u7EC4 = \u4E0D\u9A8C\u8BC1\uFF08\u672C\u8F6E\u8FED\u4EE3\u4E0D\u53D7\u4EFB\u4F55\u6D4B\u8BD5\u4FDD\u62A4\uFF09',
1596
+ en: 'Array of allow-listed command strings, e.g. ["npm test","npm run lint"]; run one by one every round. An empty array = no verification (this iteration runs with no test protection)'
1597
+ }
1551
1598
  },
1552
1599
  {
1553
1600
  key: "language",
1554
- label: "\u754C\u9762\u8BED\u8A00",
1555
- hint: '"zh" \u6216 "en"\uFF08\u9ED8\u8BA4 "en"\uFF09\uFF1A\u63A7\u5236\u5BA1\u6279\u7406\u7531\u3001\u9632\u5FA1\u4E8B\u4EF6\u6807\u7B7E\u7B49\u9762\u5411\u4EBA\u6587\u6848\u7684\u8BED\u8A00'
1601
+ label: { zh: "\u754C\u9762\u8BED\u8A00", en: "Interface language" },
1602
+ hint: {
1603
+ zh: '"zh" \u6216 "en"\uFF08\u9ED8\u8BA4 "en"\uFF09\uFF1A\u63A7\u5236\u5BA1\u6279\u7406\u7531\u3001\u9632\u5FA1\u4E8B\u4EF6\u6807\u7B7E\u7B49\u9762\u5411\u4EBA\u6587\u6848\u7684\u8BED\u8A00',
1604
+ en: '"zh" or "en" (default "en"): language of human-facing copy such as approval rationales and defense-event labels'
1605
+ }
1556
1606
  },
1557
1607
  {
1558
1608
  key: "personalization.known_intentional",
1559
- label: "\u5DF2\u77E5\u6709\u610F\u95EE\u9898",
1560
- hint: "\u6570\u7EC4\uFF0C\u6BCF\u9879 {file, line?, dimension, reason}\uFF1B\u547D\u4E2D\u6761\u76EE\u5728\u8BC4\u5BA1\u4E2D\u88AB\u8DF3\u8FC7\u3002\u53EF\u7528\u5206\u8BCA\u9762\u677F\u7684\u300C\u56DE\u8BFB\u5DF2\u5199\u5165\u6761\u76EE\u300D\u6838\u5BF9"
1609
+ label: { zh: "\u5DF2\u77E5\u6709\u610F\u95EE\u9898", en: "Known intentional issues" },
1610
+ hint: {
1611
+ zh: "\u6570\u7EC4\uFF0C\u6BCF\u9879 {file, line?, dimension, reason}\uFF1B\u547D\u4E2D\u6761\u76EE\u5728\u8BC4\u5BA1\u4E2D\u88AB\u8DF3\u8FC7\u3002\u53EF\u7528\u5206\u8BCA\u9762\u677F\u7684\u300C\u56DE\u8BFB\u5DF2\u5199\u5165\u6761\u76EE\u300D\u6838\u5BF9",
1612
+ en: `Array of {file, line?, dimension, reason}; findings hitting an entry are skipped in review. Verify via the triage panel's "read back written entries"`
1613
+ }
1614
+ },
1615
+ {
1616
+ key: "atomic.max_lines",
1617
+ label: { zh: "\u539F\u5B50\u4FEE\u590D\u4E0A\u9650\u884C\u6570", en: "Atomic fix line cap" },
1618
+ hint: { zh: "\u6B63\u6574\u6570", en: "Positive integer" }
1561
1619
  },
1562
- { key: "atomic.max_lines", label: "\u539F\u5B50\u4FEE\u590D\u4E0A\u9650\u884C\u6570", hint: "\u6B63\u6574\u6570" },
1563
- { key: "git.push_per_round", label: "\u6BCF\u8F6E\u63A8\u9001", hint: "true / false" }
1620
+ {
1621
+ key: "git.push_per_round",
1622
+ label: { zh: "\u6BCF\u8F6E\u63A8\u9001", en: "Push per round" },
1623
+ hint: { zh: "true / false", en: "true / false" }
1624
+ }
1564
1625
  ];
1565
1626
  function buildConfigEditGuide() {
1566
1627
  const lines = [
@@ -1568,12 +1629,13 @@ function buildConfigEditGuide() {
1568
1629
  "---------------------",
1569
1630
  "\u914D\u7F6E\u6587\u4EF6\uFF1A\u9879\u76EE\u6839\u76EE\u5F55 iterate.config.yaml\u3002",
1570
1631
  "",
1571
- "\u26A0 \u672A\u914D\u7F6E validation.commands \u65F6\u8FED\u4EE3\u4E0D\u88AB\u4FDD\u62A4\uFF1A\u6BCF\u8F6E validate \u62FF\u5230\u7A7A\u7ED3\u679C\u4F1A\u88AB\u5F53\u4F5C",
1572
- " \u201C\u9A8C\u8BC1\u901A\u8FC7\u201D\uFF0C\u7B49\u4E8E\u5728\u6CA1\u6709\u4EFB\u4F55\u6D4B\u8BD5\u4FDD\u62A4\u7684\u60C5\u51B5\u4E0B\u7EE7\u7EED\u6536\u655B\u3002\u7B2C\u4E00\u6B21\u542F\u52A8\u8FED\u4EE3\u524D\uFF0C",
1632
+ "\u26A0 \u672A\u914D\u7F6E validation.commands \u65F6\u8FED\u4EE3\u4E0D\u88AB\u4FDD\u62A4\uFF1A\u8BE5\u8F6E\u4F1A\u76F4\u63A5\u4E2D\u6B62\uFF08fail-closed\uFF09\uFF0C",
1633
+ " \u7A7A\u9A8C\u8BC1\u7ED3\u679C/\u9A8C\u8BC1\u672A\u6267\u884C\u7EDD\u4E0D\u5F53\u4F5C\u201C\u9A8C\u8BC1\u901A\u8FC7\u201D\uFF0C\u8FD0\u884C\u4EE5 converged_unverified /",
1634
+ " aborted_by_config \u6536\u5C3E\u800C\u4E0D\u662F plain converged\u3002\u7B2C\u4E00\u6B21\u542F\u52A8\u8FED\u4EE3\u524D\uFF0C",
1573
1635
  " \u8BF7\u5148\u7ED9 validation.commands \u914D\u4E0A\u81F3\u5C11\u4E00\u6761\u771F\u5B9E\u547D\u4EE4\uFF08\u5982 npm test\uFF09\u3002",
1574
1636
  "",
1575
1637
  "\u53EF\u7F16\u8F91\u5B57\u6BB5\uFF1A",
1576
- ...CONFIG_EDIT_FIELDS.map((f) => `- ${f.key}\uFF08${f.label}\uFF09\uFF1A${f.hint}`),
1638
+ ...CONFIG_EDIT_FIELDS.map((f) => `- ${f.key}\uFF08${f.label.zh}\uFF09\uFF1A${f.hint.zh}`),
1577
1639
  "",
1578
1640
  "\u8BA9\u6A21\u578B\u5E2E\u4F60\u6539\uFF1A",
1579
1641
  '1. \u8C03\u7528 iterate_config({ operation: "read" }) \u67E5\u770B\u5F53\u524D\u914D\u7F6E\uFF1B',
@@ -1599,8 +1661,8 @@ function configFieldByKey(key) {
1599
1661
  }
1600
1662
  function buildConfigFieldInstruction(key, value) {
1601
1663
  const field = configFieldByKey(key);
1602
- const label = field ? field.label : key;
1603
- const hint = field ? field.hint : "\u89C1 iterate.config.yaml";
1664
+ const label = field ? field.label.zh : key;
1665
+ const hint = field ? field.hint.zh : "\u89C1 iterate.config.yaml";
1604
1666
  const updates = { [key]: value === void 0 ? CONFIG_VALUE_PLACEHOLDER : value };
1605
1667
  const payload = buildConfigEditInstruction(updates);
1606
1668
  return [
@@ -1612,6 +1674,71 @@ function buildConfigFieldInstruction(key, value) {
1612
1674
  '\u5199\u5165\u524D\u8BF7\u5148 `operation: "read"` \u5C55\u793A\u5F53\u524D\u503C\uFF1B\u5199\u5165\u7531\u5DE5\u5177\u81EA\u52A8\u5907\u4EFD\uFF0C\u5931\u8D25\u81EA\u52A8\u56DE\u6EDA\u3002'
1613
1675
  ].join("\n");
1614
1676
  }
1677
+ function buildConfigReadInstruction() {
1678
+ return `\u8BF7\u8C03\u7528 \`iterate_config\` \u56DE\u8BFB\u5F53\u524D\u751F\u6548\u914D\u7F6E\uFF0C\u5E76\u628A\u5B8C\u6574\u7ED3\u679C\uFF08\u542B config \u5B57\u6BB5\uFF09\u539F\u6837\u56DE\u663E\uFF0C\u4F9B\u6211\u6838\u5BF9\u754C\u9762\u4E0A\u5C55\u793A\u7684\u5F53\u524D\u503C\uFF1A
1679
+
1680
+ \`\`\`json
1681
+ ${JSON.stringify({ operation: "read" }, null, 2)}
1682
+ \`\`\``;
1683
+ }
1684
+ var CONFIG_TOP_LEVEL_KEYS = [...new Set(CONFIG_EDIT_FIELDS.map((f) => f.key.split(".")[0]))];
1685
+ function isEffectiveConfig(v) {
1686
+ if (!v || typeof v !== "object" || Array.isArray(v)) return false;
1687
+ const rec = (
1688
+ /** @type {Record<string, unknown>} */
1689
+ v
1690
+ );
1691
+ let hits = 0;
1692
+ for (const key of CONFIG_TOP_LEVEL_KEYS) {
1693
+ if (Object.hasOwn(rec, key)) hits += 1;
1694
+ if (hits >= 3) return true;
1695
+ }
1696
+ return false;
1697
+ }
1698
+ function findConfigInResult(v) {
1699
+ if (!v || typeof v !== "object" || Array.isArray(v)) return null;
1700
+ const rec = (
1701
+ /** @type {Record<string, unknown>} */
1702
+ v
1703
+ );
1704
+ const cfg = safeGet(rec, "config");
1705
+ if (isEffectiveConfig(cfg)) return (
1706
+ /** @type {Record<string, unknown>} */
1707
+ cfg
1708
+ );
1709
+ return isEffectiveConfig(v) ? rec : null;
1710
+ }
1711
+ function scanSessionForConfigRead(session) {
1712
+ const node = latestToolResultNode(session, "iterate_config", findConfigInResult);
1713
+ if (node !== null) {
1714
+ const cfg = findConfigInResult(node);
1715
+ if (cfg) return cfg;
1716
+ }
1717
+ const loose = findFirstInObject(session, isEffectiveConfig);
1718
+ return loose ? (
1719
+ /** @type {Record<string, unknown>} */
1720
+ loose
1721
+ ) : null;
1722
+ }
1723
+ function configValueByPath(config, key) {
1724
+ if (!config || typeof config !== "object" || typeof key !== "string" || !key) {
1725
+ return { resolved: false, value: void 0 };
1726
+ }
1727
+ let node = (
1728
+ /** @type {unknown} */
1729
+ config
1730
+ );
1731
+ for (const part of key.split(".")) {
1732
+ if (!node || typeof node !== "object" || Array.isArray(node)) return { resolved: false, value: void 0 };
1733
+ const rec = (
1734
+ /** @type {Record<string, unknown>} */
1735
+ node
1736
+ );
1737
+ if (!Object.hasOwn(rec, part)) return { resolved: false, value: void 0 };
1738
+ node = rec[part];
1739
+ }
1740
+ return { resolved: true, value: node };
1741
+ }
1615
1742
  var VERDICT_SHORTCUTS = {
1616
1743
  y: "keep",
1617
1744
  Y: "keep",
@@ -1668,9 +1795,9 @@ function buildRuntimeStatusGuide() {
1668
1795
  }
1669
1796
  function filterLiveEntries(entries, type) {
1670
1797
  const list = Array.isArray(entries) ? entries : [];
1671
- const t = typeof type === "string" ? type.trim() : "";
1672
- if (!t) return list.slice();
1673
- return list.filter((e) => e && typeof e === "object" && String(e.type ?? "") === t);
1798
+ const t2 = typeof type === "string" ? type.trim() : "";
1799
+ if (!t2) return list.slice();
1800
+ return list.filter((e) => e && typeof e === "object" && String(e.type ?? "") === t2);
1674
1801
  }
1675
1802
  function filterTimelineEntries(entries, opts) {
1676
1803
  const list = Array.isArray(entries) ? entries : [];
@@ -1678,18 +1805,18 @@ function filterTimelineEntries(entries, opts) {
1678
1805
  const type = typeof o.type === "string" ? o.type : "";
1679
1806
  const round = typeof o.round === "string" ? o.round : "";
1680
1807
  const q = typeof o.search === "string" ? o.search.trim().toLowerCase() : "";
1681
- const filtered = list.filter((t) => {
1682
- if (!t || typeof t !== "object") return false;
1683
- if (type && String(t.type ?? "") !== type) return false;
1684
- if (round && String(t.round ?? "") !== round) return false;
1808
+ const filtered = list.filter((t2) => {
1809
+ if (!t2 || typeof t2 !== "object") return false;
1810
+ if (type && String(t2.type ?? "") !== type) return false;
1811
+ if (round && String(t2.round ?? "") !== round) return false;
1685
1812
  if (q) {
1686
1813
  let dataText = "";
1687
1814
  try {
1688
- dataText = JSON.stringify(t.data ?? {});
1815
+ dataText = JSON.stringify(t2.data ?? {});
1689
1816
  } catch {
1690
- dataText = t.data === void 0 ? "{}" : String(t.data);
1817
+ dataText = t2.data === void 0 ? "{}" : String(t2.data);
1691
1818
  }
1692
- const hay = [String(t.type ?? ""), String(t.round ?? ""), dataText].join(" ").toLowerCase();
1819
+ const hay = [String(t2.type ?? ""), String(t2.round ?? ""), dataText].join(" ").toLowerCase();
1693
1820
  if (hay.indexOf(q) < 0) return false;
1694
1821
  }
1695
1822
  return true;
@@ -1721,7 +1848,7 @@ function pickExportExtras(extra) {
1721
1848
  }
1722
1849
  return out;
1723
1850
  }
1724
- function buildRoundComparison(report, validations) {
1851
+ function buildRoundComparison(report, validations, fixes) {
1725
1852
  const rounds = /* @__PURE__ */ new Map();
1726
1853
  const rowsFor = (round) => {
1727
1854
  const n = Number(round);
@@ -1738,7 +1865,18 @@ function buildRoundComparison(report, validations) {
1738
1865
  row.findings += 1;
1739
1866
  const sev = typeof f.severity === "string" && f.severity ? f.severity : "unknown";
1740
1867
  row.severities[sev] = (row.severities[sev] || 0) + 1;
1741
- if (f.status === "fixed") row.fixed += 1;
1868
+ }
1869
+ const fixList = Array.isArray(fixes) ? fixes : [];
1870
+ for (const fx of fixList) {
1871
+ if (!fx || typeof fx !== "object") continue;
1872
+ if (
1873
+ /** @type {Record<string, unknown>} */
1874
+ fx.success !== true
1875
+ ) continue;
1876
+ rowsFor(
1877
+ /** @type {Record<string, unknown>} */
1878
+ fx.round
1879
+ ).fixed += 1;
1742
1880
  }
1743
1881
  const vals = Array.isArray(validations) ? validations : [];
1744
1882
  for (const v of vals) {
@@ -1753,15 +1891,15 @@ function buildRoundComparison(report, validations) {
1753
1891
  return [...rounds.values()].filter((r) => r.round > 0).sort((a, b) => a.round - b.round).map((r) => ({ ...r, severities: { ...r.severities } }));
1754
1892
  }
1755
1893
  function dashboardRunState(transcript) {
1756
- const t = transcript && typeof transcript === "object" ? transcript : null;
1757
- if (!t) return { state: "empty", round: 0, phase: "", stoppedReason: "" };
1758
- const rawRound = Number(t.round);
1894
+ const t2 = transcript && typeof transcript === "object" ? transcript : null;
1895
+ if (!t2) return { state: "empty", round: 0, phase: "", stoppedReason: "" };
1896
+ const rawRound = Number(t2.round);
1759
1897
  const round = Number.isFinite(rawRound) && rawRound > 0 ? Math.floor(rawRound) : 1;
1760
- const stoppedReason = typeof t.stoppedReason === "string" ? t.stoppedReason.trim() : "";
1898
+ const stoppedReason = typeof t2.stoppedReason === "string" ? t2.stoppedReason.trim() : "";
1761
1899
  return {
1762
- state: t.active === true ? "running" : "done",
1900
+ state: t2.active === true ? "running" : "done",
1763
1901
  round,
1764
- phase: latestPhase(t.phases),
1902
+ phase: latestPhase(t2.phases),
1765
1903
  stoppedReason
1766
1904
  };
1767
1905
  }
@@ -1774,6 +1912,387 @@ function latestPhase(phases) {
1774
1912
  return latest;
1775
1913
  }
1776
1914
 
1915
+ // src/client/i18n.ts
1916
+ var LANG_STORAGE_KEY = "iterate-ui-lang";
1917
+ var LANG_NAMES = {
1918
+ zh: "\u7B80\u4F53\u4E2D\u6587",
1919
+ en: "English"
1920
+ };
1921
+ var EN = {
1922
+ // ── run state / dashboard ────────────────────────────────────────────────
1923
+ \u8FD0\u884C\u4E2D: "Running",
1924
+ \u5DF2\u7ED3\u675F: "Finished",
1925
+ "Round {a0}\uFF1A{a1} \u9879": "Round {a0}: {a1} findings",
1926
+ "\u5404\u8F6E\u53D1\u73B0\u6570\u91CF\u8D8B\u52BF\uFF1A{a0}": "Findings per round: {a0}",
1927
+ \u5B8C\u6574\u8FED\u4EE3: "Full iteration",
1928
+ "\u590D\u5236\u542F\u52A8\u6307\u4EE4\uFF1A\u5B8C\u6574\u300C\u5BA1\u67E5 \u2192 \u4FEE\u590D \u2192 \u9A8C\u8BC1 \u2192 \u590D\u76D8\u300D\u95ED\u73AF\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\u5373\u53EF\uFF09": 'Copy the start instruction: the full "review \u2192 fix \u2192 validate \u2192 debrief" loop (paste it to the model)',
1929
+ \u4EC5\u8BC4\u5BA1: "Review only",
1930
+ "\u590D\u5236\u542F\u52A8\u6307\u4EE4\uFF1A\u53EA\u5BA1\u67E5\u4E0D\u4FEE\u6539\uFF08dry-run\uFF09\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\u5373\u53EF\uFF09": "Copy the start instruction: review without changing anything (dry-run) \u2014 paste it to the model",
1931
+ "\u7B2C {a0} \u8F6E\u8FDB\u884C\u4E2D{a1}\u2014\u2014\u9996\u8F6E\u6C47\u603B\u62A5\u544A\u843D\u5730\u540E\uFF0C\u8FD9\u91CC\u4F1A\u663E\u793A\u6536\u655B\u8FDB\u5EA6\u4E0E\u53D1\u73B0\u7EDF\u8BA1\u3002": "Round {a0} in progress{a1} \u2014 convergence progress and finding stats appear here once the first aggregated report lands.",
1932
+ " \xB7 \u9636\u6BB5\u300C{a0}\u300D": ' \xB7 phase "{a0}"',
1933
+ "\u4E0A\u6B21\u8FED\u4EE3\u5728\u7B2C {a0} \u8F6E\u540E\u505C\u6B62{a1}\uFF0C\u4F46\u6CA1\u6709\u53EF\u5C55\u793A\u7684\u6C47\u603B\u62A5\u544A\u3002": "The last iteration stopped at round {a0}{a1}, but there is no report to show.",
1934
+ "\u8FD0\u884C\u4E00\u6B21\u8BC4\u5BA1\u540E\uFF0C\u8FD9\u91CC\u4F1A\u663E\u793A\u6536\u655B\u8FDB\u5EA6\u4E0E\u53D1\u73B0\u7EDF\u8BA1\u3002": "Run a review and convergence progress and finding stats will show up here.",
1935
+ \u672C\u6B21\u8FED\u4EE3\u4ECE\u4E0A\u4E00\u6B21\u4E2D\u65AD\u7684\u65AD\u70B9\u7EE7\u7EED\u6267\u884C: "This run resumed from the previous interrupted checkpoint",
1936
+ "\u5DF2\u4E2D\u65AD\u6062\u590D \xD7{a0}": "Resumed \xD7{a0}",
1937
+ "\u4F1A\u8BDD\u4E2D\u68C0\u6D4B\u5230\u7528\u6237\u9644\u5E26\u7684\u56FE\u7247\uFF0C\u8BC4\u5BA1\u5C06\u4F5C\u4E3A\u89C6\u89C9\u8BC1\u636E\u53C2\u8003": "Images attached by the user were detected in this session; the review treats them as visual evidence",
1938
+ "\u9644\u4EF6\u56FE\u7247 {a0}": "Attached images {a0}",
1939
+ "\u5F53\u524D\u5904\u4E8E\u300C{a0}\u300D\u9636\u6BB5\uFF08\u8FD0\u884C\u4E2D\uFF09": 'Currently in the "{a0}" phase (running)',
1940
+ "\u6700\u8FD1\u4E00\u6B21\u6267\u884C\u505C\u7559\u5728\u300C{a0}\u300D\u9636\u6BB5\uFF08\u5DF2\u7ED3\u675F\uFF09": 'The last run stopped in the "{a0}" phase (finished)',
1941
+ "\u4EE3\u7801\u6A21\u5F0F\uFF1A\u76F4\u63A5\u4FEE\u6539\u6587\u4EF6": "Code mode: edits files directly",
1942
+ "\u8FED\u4EE3\u6A21\u5F0F\uFF1A\u5BA1\u67E5-\u4FEE\u590D\u5FAA\u73AF": "Iterate mode: review \u2192 fix loop",
1943
+ "+{a0} \u66F4\u591A": "+{a0} more",
1944
+ "\u672C\u8F6E\u5DF2\u5E94\u7528\u7684\u539F\u5B50\u4FEE\u590D\u6570\uFF08\u6B63\u5E38\u6A21\u5F0F\uFF09": "Atomic fixes applied this round (normal mode)",
1945
+ "\u5BA1\u67E5\u5DF2\u6536\u655B\uFF1A\u6700\u540E\u4E00\u8F6E\u672A\u53D1\u73B0\u65B0\u95EE\u9898": "Review converged: the final round found nothing new",
1946
+ "\u2713 \u5DF2\u6536\u655B": "\u2713 Converged",
1947
+ "\u9996\u8F6E {a0} \u2192 \u672B\u8F6E {a1} \u9879\uFF0C\u964D\u5E45 {a2}%{a3}": "Round {a0} \u2192 round {a1} findings, down {a2}%{a3}",
1948
+ "\uFF0C\u5DF2\u6536\u655B": ", converged",
1949
+ "Iterate \xB7 \u6536\u655B\u7EDF\u8BA1": "Iterate \xB7 Convergence stats",
1950
+ \u6536\u8D77\u5386\u53F2: "Collapse history",
1951
+ "\u5386\u53F2 / \u8D8B\u52BF": "History / trend",
1952
+ \u8F6E\u6B21: "Round",
1953
+ \u53D1\u73B0: "Findings",
1954
+ \u8BF7\u7EE7\u7EED\u6267\u884C\u4E0B\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5: "Please continue with the next review round",
1955
+ "\u8BF7\u56DE\u6EDA\u4E0A\u4E00\u8F6E\u8FED\u4EE3\u7684\u4FEE\u590D\uFF1A\u8C03\u7528 `iterate_history` \u67E5\u770B\u672C\u8F6E fix id\uFF0C\u518D\u7528 `iterate_rollback` \u9010\u4E2A\u64A4\u9500\u8FD9\u4E9B\u4FEE\u590D": "Please roll back the fixes from the last round: call `iterate_history` for this round\u2019s fix ids, then `iterate_rollback` to undo them one by one",
1956
+ "y \u4FEE\u590D": "y fix",
1957
+ "\u4FDD\u7559\u8BE5 finding\uFF0C\u8FDB\u5165\u4FEE\u590D": "Keep this finding and move it to fixing",
1958
+ "n \u8DF3\u8FC7": "n skip",
1959
+ "\u8DF3\u8FC7\u8BE5 finding": "Skip this finding",
1960
+ "a \u5DF2\u77E5\u6709\u610F": "a known-intentional",
1961
+ "\u6807\u8BB0\u4E3A\u5DF2\u77E5\u6709\u610F\u5E76\u5199\u5165 known_intentional": "Mark as known-intentional and write it into known_intentional",
1962
+ // ── triage panel ─────────────────────────────────────────────────────────
1963
+ "Iterate \xB7 Findings \u5206\u8BCA ({a0}/{a1})": "Iterate \xB7 Findings triage ({a0}/{a1})",
1964
+ "y=\u4FEE\u590D \xB7 n=\u8DF3\u8FC7 \xB7 a=\u5DF2\u77E5\u6709\u610F \xB7 \u2191/\u2193 \u9009\u62E9": "y=fix \xB7 n=skip \xB7 a=known-intentional \xB7 \u2191/\u2193 to move",
1965
+ \u6309\u4E25\u91CD\u5EA6\u7B5B\u9009: "Filter by severity",
1966
+ \u5168\u90E8\u4E25\u91CD\u5EA6: "All severities",
1967
+ \u6309\u7EF4\u5EA6\u7B5B\u9009: "Filter by dimension",
1968
+ \u5168\u90E8\u7EF4\u5EA6: "All dimensions",
1969
+ "\u641C\u7D22\u6587\u4EF6 / \u6458\u8981\u2026": "Search file / summary\u2026",
1970
+ \u641C\u7D22\u6587\u4EF6\u6216\u6458\u8981: "Search file or summary",
1971
+ "\u6E05\u9664\u7B5B\u9009\uFF08\u663E\u793A {a0}\uFF09": "Clear filters (showing {a0})",
1972
+ "\u5171 {a0} \u9879": "{a0} in total",
1973
+ "\u6279\u91CF\u4F5C\u7528\u4E8E\uFF1A": "Batch applies to:",
1974
+ "\u6279\u91CF\u6309\u94AE\u4EC5\u4F5C\u7528\u4E8E\u5F53\u524D\u7B5B\u9009\u53EF\u89C1\u7684 findings": "Batch buttons only apply to the findings visible under the current filter",
1975
+ "\u53EF\u89C1 {a0}": "Visible {a0}",
1976
+ "\u6279\u91CF\u6309\u94AE\u4F5C\u7528\u4E8E\u5168\u90E8 findings": "Batch buttons apply to every finding",
1977
+ "\u5168\u90E8 {a0}": "All {a0}",
1978
+ "\u628A\u6240\u6709\u5224\u5B9A\u6062\u590D\u4E3A\u9ED8\u8BA4 y\uFF08\u4FEE\u590D\uFF09": "Reset every verdict to the default y (fix)",
1979
+ \u91CD\u7F6E: "Reset",
1980
+ "y {a0} \xB7 n {a1} \xB7 a {a2} \xB7 \u5F85\u5199\u56DE known_intentional\uFF1A{a3} \u6761": "y {a0} \xB7 n {a1} \xB7 a {a2} \xB7 pending known_intentional write-back: {a3}",
1981
+ "\u5F53\u524D\u6CA1\u6709\u6807\u8BB0\u4E3A\u300C\u5DF2\u77E5\u6709\u610F\u300D\u7684 finding": "No finding is marked known-intentional yet",
1982
+ "\u590D\u5236\u5E94\u7528\u6307\u4EE4\uFF1A\u7C98\u8D34\u540E\u6A21\u578B\u4F1A\u8C03\u7528 iterate_triage apply\uFF08\u6821\u9A8C\u3001\u53BB\u91CD\u3001\u5199\u5165\u524D\u5907\u4EFD\u3001\u5931\u8D25\u81EA\u52A8\u56DE\u6EDA\uFF09": "Copy the apply instruction: once pasted, the model calls iterate_triage apply (validates, dedupes, backs up before writing, rolls back on failure)",
1983
+ "\u751F\u6210\u5E94\u7528\u6307\u4EE4{a0}": "Generate apply instruction{a0}",
1984
+ "\u53EA\u590D\u5236 known_intentional YAML \u7247\u6BB5\uFF08\u9700\u81EA\u884C\u7C98\u5230 iterate.config.yaml \u7684 personalization.known_intentional \u4E0B\uFF09": "Copy only the known_intentional YAML fragment (you must paste it under personalization.known_intentional in iterate.config.yaml yourself)",
1985
+ "\u4EC5\u590D\u5236 YAML \u7247\u6BB5": "Copy YAML fragment only",
1986
+ "\u590D\u5236 iterate_triage list \u6307\u4EE4\uFF0C\u6838\u5BF9\u5DF2\u7ECF\u5199\u5165\u7684 known_intentional \u6761\u76EE\uFF08\u5199\u56DE\u95ED\u73AF\u786E\u8BA4\uFF09": "Copy the iterate_triage list instruction to check which known_intentional entries actually landed (write-back \u2192 readback)",
1987
+ \u56DE\u8BFB\u5DF2\u5199\u5165\u6761\u76EE: "Read back written entries",
1988
+ "\u5DF2\u590D\u5236\u5E94\u7528\u6307\u4EE4\u2014\u2014\u7C98\u8D34\u56DE\u4F1A\u8BDD\uFF0C\u6A21\u578B\u5C06\u8C03\u7528 iterate_triage apply \u5E76\u56DE\u62A5\u5199\u5165\u6761\u6570\u3002": "Apply instruction copied \u2014 paste it back into the chat and the model will call iterate_triage apply and report how many entries were written.",
1989
+ "\u5DF2\u590D\u5236\u56DE\u8BFB\u6307\u4EE4\u2014\u2014\u7C98\u8D34\u56DE\u4F1A\u8BDD\u53EF\u5217\u51FA\u5F53\u524D\u5DF2\u5199\u5165\u7684 known_intentional\u3002": "Readback instruction copied \u2014 paste it back to list the known_intentional entries currently written.",
1990
+ // ── command actions ──────────────────────────────────────────────────────
1991
+ \u6307\u6325\u64CD\u4F5C: "Command actions",
1992
+ "\u6279\u51C6\u67B6\u6784\u4FEE\u590D\uFF08\u590D\u5236\u6307\u4EE4\u6587\u672C\uFF1Aiterate_fix force:true\uFF0C\u542B\u5168\u90E8\u5FC5\u586B\u53C2\u6570\uFF09": "Approve the architectural fix (copies the instruction text: iterate_fix with force:true and every required argument)",
1993
+ \u6279\u51C6\u67B6\u6784\u4FEE\u590D: "Approve architectural fix",
1994
+ \u89E6\u53D1\u65B0\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5: "Trigger the next review round",
1995
+ \u89E6\u53D1\u65B0\u4E00\u8F6E: "New round",
1996
+ "\u6307\u6D3E\u5168\u90E8 {a0} \u4E2A findings \u4FEE\u590D\uFF08\u9010\u4E2A iterate_fix\uFF09": "Assign all {a0} findings for fixing (one iterate_fix each)",
1997
+ "\u6307\u6D3E\u5F53\u524D\u53EF\u89C1 {a0} \u4E2A findings \u4FEE\u590D\uFF08\u9010\u4E2A iterate_fix\uFF09": "Assign the {a0} visible findings for fixing (one iterate_fix each)",
1998
+ \u6307\u6D3E\u4FEE\u590D: "Assign fixes",
1999
+ \u56DE\u6EDA\u5230\u4E0A\u4E00\u4E2A\u68C0\u67E5\u70B9: "Roll back to the previous checkpoint",
2000
+ \u56DE\u6EDA\u68C0\u67E5\u70B9: "Rollback to checkpoint",
2001
+ // ── verdict banner / stats card ──────────────────────────────────────────
2002
+ \u62A5\u544A\u5DF2\u6279\u51C6: "Report approved",
2003
+ \u62A5\u544A\u9700\u4FEE\u8BA2: "Report needs revision",
2004
+ \u9879\u53D1\u73B0: " findings",
2005
+ \u8F6E: " rounds",
2006
+ \u9879\u5BA1\u67E5: " reviews",
2007
+ \u62A5\u544A\u901A\u8FC7\u5168\u90E8\u4E00\u81F4\u6027\u68C0\u67E5: "The report passed every consistency check",
2008
+ \u5904\u62A5\u544A\u7F3A\u9677: " report defects found",
2009
+ \u5DF2\u6536\u655B: "Converged",
2010
+ \u672A\u6536\u655B: "Not converged",
2011
+ "Round {a0} \u5B8C\u6210 \xB7 \u5DF2\u6536\u655B": "Round {a0} complete \xB7 converged",
2012
+ "Round {a0} \u5B8C\u6210": "Round {a0} complete",
2013
+ // ── settings ─────────────────────────────────────────────────────────────
2014
+ "\u5DF2\u6E05\u9664 {a0} \u6761": "Cleared {a0}",
2015
+ \u518D\u6B21\u70B9\u51FB\u4EE5\u786E\u8BA4\u6E05\u9664\u5168\u90E8\u5224\u5B9A: "Click again to confirm clearing every verdict",
2016
+ "\u6E05\u9664\u6240\u6709\u5206\u8BCA\u5224\u5B9A\u8BB0\u5F55\uFF08\u9700\u8981\u4E8C\u6B21\u786E\u8BA4\uFF09": "Clear every triage verdict (requires a second click)",
2017
+ "\u786E\u8BA4\u6E05\u9664\uFF1F": "Confirm clear?",
2018
+ \u6E05\u9664\u5206\u8BCA: "Clear triage",
2019
+ \u8FED\u4EE3\u4E3B\u9898: "Iterate theme",
2020
+ "\u542F\u7528\u6696\u7425\u73C0\u914D\u8272\u7684 iterate \u4E13\u5C5E\u76AE\u80A4\uFF0C\u8986\u76D6 dsh \u9ED8\u8BA4\u4E3B\u9898\u4EE4\u724C\u3002": "Enables the warm-amber iterate skin, overriding the default dsh theme tokens.",
2021
+ \u5DF2\u542F\u7528: "Enabled",
2022
+ \u5DF2\u5173\u95ED: "Off",
2023
+ "\u5F00\u5173 iterate \u4E3B\u9898": "Toggle the iterate theme",
2024
+ \u5206\u8BCA\u6301\u4E45\u5316: "Triage persistence",
2025
+ "y / n / a \u5224\u5B9A\u4FDD\u5B58\u5728\u672C\u5730\u6D4F\u89C8\u5668\uFF08localStorage\uFF09\uFF0C\u5237\u65B0\u4F1A\u8BDD\u540E\u4ECD\u4FDD\u7559\u3002": "y / n / a verdicts are stored in your browser (localStorage) and survive a session refresh.",
2026
+ \u672C\u5730\u4FDD\u5B58: "Stored locally",
2027
+ \u914D\u7F6E\u7BA1\u7406: "Configuration",
2028
+ "\u76EE\u6807 / \u7EF4\u5EA6 / \u6700\u5927\u8F6E\u6570 / validation.commands \u5199\u5728\u9879\u76EE\u7684 iterate.config.yaml\uFF0C\u590D\u5236\u6307\u5F15\u53EF\u8BA9\u6A21\u578B\u6309\u9700\u8C03\u6574\u3002": "Goal / dimensions / max rounds / validation.commands live in the project\u2019s iterate.config.yaml; copy the guide to let the model adjust them on demand.",
2029
+ \u590D\u5236\u6307\u5F15: "Copy guide",
2030
+ \u6536\u8D77: "Collapse",
2031
+ \u5C55\u5F00: "Expand",
2032
+ \u9009\u62E9\u8981\u4FEE\u6539\u7684\u914D\u7F6E\u5B57\u6BB5: "Pick the config field to edit",
2033
+ "\u751F\u6210\u5E76\u590D\u5236\u8BE5\u5B57\u6BB5\u7684 iterate_config \u5199\u5165\u6307\u4EE4\uFF08\u5199\u5165\u524D\u81EA\u52A8\u5907\u4EFD\u3001\u5931\u8D25\u81EA\u52A8\u56DE\u6EDA\uFF09": "Build and copy the iterate_config write instruction for this field (backs up first, rolls back on failure)",
2034
+ \u751F\u6210\u5199\u5165\u6307\u4EE4: "Build write instruction",
2035
+ "\u526A\u8D34\u677F\u88AB\u62D2\u7EDD\u2014\u2014\u6307\u4EE4\u5DF2\u5C55\u793A\u5728\u4E0B\u65B9\uFF0C\u8BF7\u624B\u52A8\u590D\u5236\u3002": "The clipboard write was refused \u2014 the instruction is shown below, copy it manually.",
2036
+ "\u5199\u5165\u6307\u4EE4\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\uFF09": "Write instruction (paste to the model)",
2037
+ "\u5C1A\u672A\u56DE\u8BFB\u5F53\u524D\u914D\u7F6E\u2014\u2014\u70B9\u300C\u56DE\u8BFB\u5F53\u524D\u914D\u7F6E\u300D\u590D\u5236\u6307\u4EE4\u53D1\u7ED9\u6A21\u578B\uFF0C\u56DE\u663E\u540E\u5373\u53EF\u5728\u6B64\u770B\u5230\u5F53\u524D\u503C\u3002": "No config read back yet \u2014 press \u201CRead back current config\u201D, send the instruction to the model, and the current value shows up here.",
2038
+ "\u5F53\u524D\u503C {a0}\uFF1A{a1}": "Current value for {a0}: {a1}",
2039
+ "\u56DE\u8BFB\u7ED3\u679C\u4E2D\u6CA1\u6709 {a0}\uFF08\u8BE5\u5B57\u6BB5\u53EF\u80FD\u5C1A\u672A\u5199\u5165\u914D\u7F6E\u6587\u4EF6\uFF09\u3002": "The read-back does not contain {a0} (it may not have been written to the config file yet).",
2040
+ '\u590D\u5236\u56DE\u8BFB\u6307\u4EE4\uFF1A\u8BA9\u6A21\u578B\u8C03\u7528 iterate_config(operation:"read") \u5E76\u56DE\u663E\u7ED3\u679C': 'Copy the read-back instruction: have the model call iterate_config(operation:"read") and echo the result',
2041
+ \u56DE\u8BFB\u5F53\u524D\u914D\u7F6E: "Read back current config",
2042
+ "\u56DE\u8BFB\u6307\u4EE4\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\uFF09": "Read-back instruction (paste to the model)",
2043
+ \u590D\u5236: "Copy",
2044
+ "iterate.config.yaml \u53EF\u7F16\u8F91\u5B57\u6BB5": "Editable iterate.config.yaml fields",
2045
+ "\u4E2D\u6587\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\u7684\u5951\u7EA6\u6587\u6848\uFF09": "Chinese (contract text to paste to the model)",
2046
+ \u72B6\u6001\u6982\u89C8: "Runtime status",
2047
+ "\u67E5\u770B\u8FD0\u884C\u65F6\u4EA7\u7269\u5E03\u5C40\u4E0E\u6E05\u7406\u6307\u5F15\u3002iterate_status / iterate_history / iterate_prune \u5DE5\u5177\u7528\u4E8E\u67E5\u770B\u548C\u7BA1\u7406\u3002": "The runtime artifact layout and cleanup guide. Use iterate_status / iterate_history / iterate_prune to inspect and manage them.",
2048
+ \u62C9\u53D6\u78C1\u76D8\u5FEB\u7167: "Pull disk snapshot",
2049
+ \u8FD0\u884C\u65F6\u5E03\u5C40\u4E0E\u6E05\u7406: "Runtime layout & cleanup",
2050
+ "\u4E3A\u6BCF\u6B21\u4EE3\u7801\u8BC4\u5BA1\u751F\u6210 Review \u62A5\u544A\u4E0E\u4EA4\u4E92\u5F0F\u5206\u8BCA\u89C6\u56FE\uFF0C\u4E13\u6CE8 AI \u81EA\u6211\u5BA1\u67E5\u4E0E\u4FEE\u6B63\u3002": "Generates a review report and an interactive triage view for every code review, focused on AI self-review and correction.",
2051
+ \u5C31\u7EEA: "Ready",
2052
+ "iterate \u8BBE\u7F6E": "iterate settings",
2053
+ "\u526A\u8D34\u677F\u5199\u5165\u88AB\u62D2\u7EDD\uFF0C\u590D\u5236\u672A\u751F\u6548\u2014\u2014\u8BF7\u5C55\u5F00\u5BF9\u5E94\u533A\u5757\u624B\u52A8\u9009\u4E2D\u6587\u672C\u590D\u5236\u3002": "The clipboard write was refused, so nothing was copied \u2014 expand the section and copy the text manually.",
2054
+ "\u754C\u9762\u8BED\u8A00": "Interface language",
2055
+ "\u9762\u677F\u6587\u6848\u7684\u663E\u793A\u8BED\u8A00\uFF1B\u590D\u5236\u7ED9\u6A21\u578B\u7684\u6307\u4EE4\u4FDD\u6301\u4E2D\u6587\uFF0C\u4E0E SKILL.md \u7684\u5DE5\u4F5C\u6D41\u5951\u7EA6\u4E00\u81F4\u3002": "The language of the panel copy. Instructions copied for the model stay Chinese \u2014 they mirror the SKILL.md workflow contract.",
2056
+ "\u5207\u6362\u754C\u9762\u8BED\u8A00": "Switch the interface language",
2057
+ // ── observatory shell / live feed ────────────────────────────────────────
2058
+ "{a0} \u9875 \xB7 \u6309 1-9/0 \u5207 Fn\u3001- \u56DE\u5B9E\u65F6\u6D41": "{a0} tabs \xB7 keys 1-9/0 switch Fn, - returns to the live feed",
2059
+ "\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u8BA9\u6A21\u578B\u9010\u4E2A\u8C03\u7528 {a0} \u5E76\u56DE\u663E\u5B8C\u6574\u7ED3\u679C": "Copy the disk-snapshot instruction: have the model call {a0} and echo the full results",
2060
+ "iterate \u89C2\u6D4B\u53F0": "iterate observatory",
2061
+ "\u6682\u65E0\u8FD0\u884C\u65F6\u89C2\u6D4B\u6570\u636E \xB7 {a0}": "No runtime observations yet \xB7 {a0}",
2062
+ "\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u78C1\u76D8\u4E0A\u53EF\u80FD\u5DF2\u6709\u5386\u53F2\u4EA7\u7269\uFF0C\u56DE\u663E\u8FDB\u4F1A\u8BDD\u540E\u89C2\u6D4B\u53F0\u5373\u53EF\u586B\u5145": "Copy the disk-snapshot instruction: older artifacts may already be on disk; once echoed back into the chat the observatory fills up",
2063
+ "\u5B9E\u65F6\u6D3B\u52A8\u6D41\u4EC5\u5728\u8FD0\u884C\u671F\u95F4\u8BB0\u5F55\uFF0C\u5F53\u524D\u5DF2\u5B8C\u6210\u3002": "The live activity stream is only recorded while a run is active; this run has finished.",
2064
+ \u67E5\u770B\u5BA1\u67E5\u7EBF\u7A0B: "View review threads",
2065
+ \u67E5\u770B\u53D1\u73B0: "View findings",
2066
+ \u6682\u65E0\u5B9E\u65F6\u6D3B\u52A8: "No live activity yet",
2067
+ \u5B9E\u65F6\u6D3B\u52A8: "Live activity",
2068
+ \u6309\u6D3B\u52A8\u7C7B\u578B\u7B5B\u9009: "Filter by activity type",
2069
+ \u5168\u90E8\u6D3B\u52A8: "All activity",
2070
+ \u65E0\u5339\u914D\u7684\u5B9E\u65F6\u6D3B\u52A8: "No matching live activity",
2071
+ \u590D\u5236\u672A\u6210\u529F: "Copy failed",
2072
+ "\u526A\u8D34\u677F\u5199\u5165\u88AB\u62D2\u7EDD\uFF0C\u8BF7\u624B\u52A8\u9009\u4E2D\u4E0B\u65B9\u6307\u4EE4\u590D\u5236": "The clipboard write was refused \u2014 select the instruction below and copy it manually",
2073
+ \u5173\u95ED\u590D\u5236\u5931\u8D25\u63D0\u793A: "Dismiss the copy-failure notice",
2074
+ \u5173\u95ED: "Dismiss",
2075
+ "\u5C06\u89C2\u6D4B\u53F0\u5168\u90E8\u6570\u636E\u5BFC\u51FA\u4E3A JSON\uFF08\u4F18\u5148\u4E0B\u8F7D\uFF0C\u5931\u8D25\u5219\u590D\u5236\uFF09": "Export every observatory panel as JSON (downloads first, falls back to copying)",
2076
+ \u5DF2\u5BFC\u51FA: "Exported",
2077
+ "\u5BFC\u51FA JSON": "Export JSON",
2078
+ "\u89C2\u6D4B\u53F0\u9875\u7B7E\uFF08\u5FEB\u6377\u952E 1-9 / 0 / -\uFF09": "Observatory tabs (keys 1-9 / 0 / -)",
2079
+ "{a0}\uFF08\u6309 {a1} \u5207\u6362\uFF09": "{a0} (press {a1})",
2080
+ "{a0}\uFF08\u6309 - \u5207\u6362\uFF09": "{a0} (press -)",
2081
+ // ── F1 review threads ────────────────────────────────────────────────────
2082
+ \u6682\u65E0\u5BA1\u67E5\u7EBF\u7A0B: "No review threads yet",
2083
+ \u5BA1\u67E5\u7EBF\u7A0B: "Review threads",
2084
+ \u5C55\u5F00\u5168\u90E8\u7EBF\u7A0B: "Expand all threads",
2085
+ \u5168\u90E8\u5C55\u5F00: "Expand all",
2086
+ \u6536\u8D77\u5168\u90E8\u7EBF\u7A0B: "Collapse all threads",
2087
+ \u5168\u90E8\u6536\u8D77: "Collapse all",
2088
+ "{a0} \xB7 {a1} \u8F6E": "{a0} \xB7 {a1} rounds",
2089
+ \u81EA\u7531\u5207\u6362: "Mixed",
2090
+ \u672C\u8F6E\u65E0\u7EBF\u7A0B: "No threads in this round",
2091
+ \u672A\u547D\u540D\u7EF4\u5EA6: "Unnamed dimension",
2092
+ \u6536\u8D77\u7EBF\u7A0B: "Collapse thread",
2093
+ \u5C55\u5F00\u7EBF\u7A0B: "Expand thread",
2094
+ \u53D9\u8FF0: "Narration",
2095
+ "{a0} \u6761": "{a0}",
2096
+ \u65E0\u7EBF\u7D22\u53D9\u8FF0: "No narration recorded",
2097
+ \u8BFB\u53D6\u6587\u4EF6: "Files read",
2098
+ \u672C\u7EBF\u7A0B\u65E0\u53D1\u73B0: "This thread found nothing",
2099
+ "{a0} \u7EBF\u7A0B": "{a0} threads",
2100
+ // ── F2 convergence trend ─────────────────────────────────────────────────
2101
+ \u6682\u65E0\u6536\u655B\u6570\u636E: "No convergence data yet",
2102
+ \u5DF2\u4FEE\u590D: "Fixed",
2103
+ \u4E25\u91CD\u5EA6\u5206\u5E03: "Severity mix",
2104
+ \u9A8C\u8BC1: "Validation",
2105
+ "{a0}/{a1} \u901A\u8FC7{a2}": "{a0}/{a1} passed{a2}",
2106
+ "\uFF08\u542B\u5931\u8D25\uFF09": " (failures included)",
2107
+ "\u5404\u8F6E\u53D1\u73B0\u6570\u91CF\uFF1A{a0}{a1}": "Findings per round: {a0}{a1}",
2108
+ " \xB7 \u5F53\u524D Round {a0}": " \xB7 current round {a0}",
2109
+ "\u8DE8\u8F6E\u5BF9\u6BD4\uFF08\u672C\u4F1A\u8BDD\uFF09": "Cross-round comparison (this session)",
2110
+ "\u8DE8\u4F1A\u8BDD\u5BF9\u6BD4\uFF1A\u672C\u8868\u53EA\u8986\u76D6\u5F53\u524D\u4F1A\u8BDD\uFF1B\u70B9\u300C\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u300D\u8BA9\u6A21\u578B\u56DE\u663E iterate_history\uFF0C\u518D\u7528\u300C\u5BFC\u51FA JSON\u300D\u5E76\u6392\u6BD4\u8F83\u4E24\u6B21\u8FD0\u884C": 'Cross-session comparison: this table only covers the current session \u2014 click "Pull disk snapshot" to have the model echo iterate_history, then use "Export JSON" to compare two runs side by side',
2111
+ // ── F3 finding locations ─────────────────────────────────────────────────
2112
+ \u6682\u65E0\u53D1\u73B0: "No findings",
2113
+ "{a0}\uFF08{a1} \u9879\uFF09": "{a0} ({a1} findings)",
2114
+ "\u641C\u7D22\u53D1\u73B0\u2026": "Search findings\u2026",
2115
+ \u641C\u7D22\u53D1\u73B0: "Search findings",
2116
+ \u6E05\u9664\u5168\u90E8\u7B5B\u9009: "Clear all filters",
2117
+ \u6E05\u9664\u7B5B\u9009: "Clear filters",
2118
+ \u5DF2\u786E\u8BA4: "Acknowledged",
2119
+ "\u590D\u5236\u8BE5 finding \u7684\u5EFA\u8BAE\u4FEE\u590D\u6587\u672C": "Copy this finding\u2019s suggested fix text",
2120
+ "\u590D\u5236 suggested_fix": "Copy suggested_fix",
2121
+ "\u590D\u5236 iterate_triage \u6307\u4EE4\u6587\u672C\uFF08\u6807\u8BB0\u4E3A known_intentional\uFF09": "Copy the iterate_triage instruction text (mark as known_intentional)",
2122
+ "\u590D\u5236 iterate_fix \u6307\u4EE4\u6587\u672C": "Copy the iterate_fix instruction text",
2123
+ \u590D\u5236\u4FEE\u590D\u6307\u4EE4: "Copy fix instruction",
2124
+ \u65E0\u5339\u914D\u7684\u53D1\u73B0: "No matching findings",
2125
+ // ── F4 fixes & rollback ──────────────────────────────────────────────────
2126
+ \u6682\u65E0\u4FEE\u590D\u8BB0\u5F55: "No fixes recorded yet",
2127
+ \u6210\u529F: "Success",
2128
+ \u5931\u8D25: "Failed",
2129
+ "\u590D\u5236 iterate_rollback \u6307\u4EE4\u6587\u672C": "Copy the iterate_rollback instruction text",
2130
+ \u56DE\u6EDA: "Rollback",
2131
+ // ── F5 checkpoint ────────────────────────────────────────────────────────
2132
+ \u65AD\u70B9: "Checkpoint",
2133
+ "\u590D\u5236 iterate_checkpoint resume \u6307\u4EE4\u6587\u672C\uFF08\u52A0\u8F7D\u65AD\u70B9\u5E76\u8BA1\u6570\u4E00\u6B21\u6062\u590D\uFF09": "Copy the iterate_checkpoint resume instruction (loads the checkpoint and counts one resume)",
2134
+ \u590D\u5236\u6062\u590D\u6307\u4EE4: "Copy resume instruction",
2135
+ "\u590D\u5236 iterate_checkpoint clear \u6307\u4EE4\u6587\u672C\uFF08\u6E05\u9664\u5F53\u524D\u65AD\u70B9\uFF0C\u4F9B\u91CD\u65B0\u5F00\u59CB\uFF09": "Copy the iterate_checkpoint clear instruction (drops the current checkpoint so you can start over)",
2136
+ \u6E05\u9664\u65AD\u70B9: "Clear checkpoint",
2137
+ // ── F6 run console ───────────────────────────────────────────────────────
2138
+ \u8BE2\u95EE\u7528\u6237: "Ask the user",
2139
+ \u62D2\u7EDD: "Deny",
2140
+ \u76F4\u63A5\u6267\u884C: "Run directly",
2141
+ "\u8BF7\u8C03\u7528 `iterate_transcript` \u5199\u5165 nudge \u6307\u4EE4\uFF1A\n\n```json\n{a0}\n```": "Please call `iterate_transcript` to write this nudge instruction:\n\n```json\n{a0}\n```",
2142
+ "\u8BF7\u8C03\u7528 `iterate_transcript` \u6E05\u9664\u5F53\u524D nudge\uFF1A\n\n```json\n{a0}\n```": "Please call `iterate_transcript` to clear the current nudge:\n\n```json\n{a0}\n```",
2143
+ \u901A\u8FC7: "Passed",
2144
+ \u5BFC\u81F4\u4E2D\u6B62: " aborted the run",
2145
+ \u9A8C\u8BC1\u7ED3\u679C: "Validation results",
2146
+ \u672C\u8F6E\u5C1A\u65E0\u9A8C\u8BC1\u8BB0\u5F55: "No validation was recorded in this round",
2147
+ "{a0} \u6761\u547D\u4EE4": "{a0} commands",
2148
+ \u56E0\u9A8C\u8BC1\u5931\u8D25\u4E2D\u6B62: "Stopped because validation failed",
2149
+ "\u5C1A\u672A\u62C9\u53D6\u9A8C\u8BC1\u7ED3\u679C\uFF1A\u6570\u636E\u5728\u78C1\u76D8 transcript \u91CC\uFF0C\u70B9\u300C\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u300D\u590D\u5236\u6307\u4EE4\u53D1\u56DE\u4F1A\u8BDD\u5373\u53EF\u56DE\u586B\u3002": 'Validation results have not been pulled yet: they live in the transcript on disk \u2014 click "Pull disk snapshot", paste the instruction back into the chat, and this panel fills.',
2150
+ \u8FD0\u884C\u63A7\u5236\u53F0: "Run console",
2151
+ "\u6279\u51C6\u7B56\u7565\uFF1A{a0}": "Approval policy: {a0}",
2152
+ "\u5F53\u524D nudge": "Current nudge",
2153
+ "\u590D\u5236\u6E05\u9664 nudge \u6307\u4EE4\uFF08\u5199 text:null\uFF09\uFF0C\u5728\u8FD0\u884C\u63A7\u5236\u53F0\u63D0\u793A\u6E05\u9664\u5DF2\u6301\u4E45\u5316\u7684 nudge": "Copy the clear-nudge instruction (writes text:null) to ask the model to drop the persisted nudge",
2154
+ \u5DF2\u590D\u5236\u6E05\u9664\u6307\u4EE4: "Clear instruction copied",
2155
+ \u590D\u5236\u6E05\u9664\u6307\u4EE4: "Copy clear instruction",
2156
+ "\u8D77\u8349\u4E00\u6761 nudge \u65B9\u5411\u6307\u4EE4\u2026": "Draft a nudge to steer the run\u2026",
2157
+ "nudge \u8349\u7A3F": "Nudge draft",
2158
+ "\u590D\u5236 iterate_transcript nudge \u6307\u4EE4\u6587\u672C": "Copy the iterate_transcript nudge instruction text",
2159
+ "\u590D\u5236 nudge \u6307\u4EE4": "Copy nudge instruction",
2160
+ // ── F7 decision timeline ─────────────────────────────────────────────────
2161
+ \u6682\u65E0\u65F6\u95F4\u7EBF\u6761\u76EE: "No timeline entries yet",
2162
+ \u65F6\u95F4\u7EBF: "Timeline",
2163
+ \u6309\u65F6\u95F4\u7EBF\u7C7B\u578B\u7B5B\u9009: "Filter the timeline by type",
2164
+ \u5168\u90E8\u7C7B\u578B: "All types",
2165
+ \u6309\u8F6E\u6B21\u7B5B\u9009: "Filter by round",
2166
+ \u5168\u90E8\u8F6E\u6B21: "All rounds",
2167
+ "\u641C\u7D22\u65F6\u95F4\u7EBF\u2026": "Search timeline\u2026",
2168
+ \u641C\u7D22\u65F6\u95F4\u7EBF: "Search timeline",
2169
+ \u6E05\u9664: "Clear",
2170
+ \u65E0\u5339\u914D\u7684\u65F6\u95F4\u7EBF\u6761\u76EE: "No matching timeline entries",
2171
+ // ── F8 quality gate ──────────────────────────────────────────────────────
2172
+ \u8D28\u91CF\u95E8\u7981: "Quality gate",
2173
+ "\u590D\u5236 iterate_quality_gate \u67E5\u8BE2\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09": "Copy the iterate_quality_gate query instruction (JSON arguments included)",
2174
+ \u67E5\u8BE2\u95E8\u7981: "Query gate",
2175
+ "\u590D\u5236 iterate_quality_gate clear \u6307\u4EE4\u6587\u672C\uFF08\u91CD\u7F6E\u9648\u65E7\u7684 FAIL \u8BC1\u4E66\uFF09": "Copy the iterate_quality_gate clear instruction (resets a stale FAIL certificate)",
2176
+ \u6E05\u9664\u95E8\u7981: "Clear gate",
2177
+ "\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u8BA9\u6A21\u578B\u56DE\u663E .iterate/ \u4E0B\u7684\u95E8\u7981\u3001\u7ECF\u9A8C\u3001\u9632\u5FA1\u4E8B\u4EF6\u4E0E transcript": "Copy the disk-snapshot instruction: have the model echo the gate, experience, defense events and transcript artifacts under .iterate/",
2178
+ \u8D28\u91CF\u95E8\u7981\u89C6\u56FE: "Quality gate view",
2179
+ \u8D28\u91CF\u95E8\u7981\u8BC1\u4E66: "Quality gate certificate",
2180
+ "\u8D28\u91CF\u95E8\u7981\u663E\u793A\u5404\u7EF4\u5EA6\u6536\u655B\u5EA6\u3001\u9A8C\u8BC1\u901A\u8FC7\u7387\u548C\u6574\u4F53 PASS/FAIL \u72B6\u6001\u3002": "The quality gate shows per-dimension convergence, the validation pass rate and the overall PASS/FAIL state.",
2181
+ \u590D\u5236\u67E5\u8BE2\u6307\u4EE4: "Copy query instruction",
2182
+ \u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4: "Copy disk-snapshot instruction",
2183
+ "\u6574\u4F53\u8BC4\u5206 {a0} \xB7 \u9A8C\u8BC1 {a1}/{a2} \xB7 \u901A\u8FC7\u7387 {a3}%": "Overall score {a0} \xB7 validation {a1}/{a2} \xB7 pass rate {a3}%",
2184
+ "{a0} \xB7 \u6536\u655B {a1}% \xB7 \u8BC4\u5206 {a2}": "{a0} \xB7 convergence {a1}% \xB7 score {a2}",
2185
+ "\u8BC4\u5206 {a0} \xB7 \u53D1\u73B0 {a1} \xB7 \u5DF2\u4FEE\u590D {a2}": "Score {a0} \xB7 findings {a1} \xB7 fixed {a2}",
2186
+ "\u5931\u8D25\u539F\u56E0\uFF1A{a0}": "Failure reason: {a0}",
2187
+ // ── F9 experience bank ───────────────────────────────────────────────────
2188
+ "\u8BF7\u8C03\u7528 `iterate_experience` \u641C\u7D22\u7ECF\u9A8C\uFF1A\n\n```json\n{a0}\n```": "Please call `iterate_experience` to search the experience bank:\n\n```json\n{a0}\n```",
2189
+ "\u8BF7\u8C03\u7528 `iterate_experience` \u91C7\u7EB3\u7ECF\u9A8C\u5E76\u5E94\u7528\u5DF2\u9A8C\u8BC1\u4FEE\u6CD5\uFF1A\n\n```json\n{a0}\n```": "Please call `iterate_experience` to adopt this entry and apply its verified fix:\n\n```json\n{a0}\n```",
2190
+ "\u547D\u4E2D {a0} \u6B21": "Hit {a0} times",
2191
+ "\u590D\u5236\u91C7\u7EB3\u6307\u4EE4\uFF08\u67E5\u8BE2\u8BE5\u7ECF\u9A8C\u5E76\u5E94\u7528\u5230\u5F53\u524D\u4E0A\u4E0B\u6587\uFF09": "Copy the adopt instruction (queries this entry and applies it to the current context)",
2192
+ \u91C7\u7EB3: "Adopt",
2193
+ \u7ECF\u9A8C\u94F6\u884C: "Experience bank",
2194
+ "\u4F1A\u8BDD\u5185 {a0} \u6761": "{a0} in this session",
2195
+ "\u590D\u5236\u5217\u51FA\u7ECF\u9A8C\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09": "Copy the list-experience instruction (JSON arguments included)",
2196
+ \u5217\u51FA\u7ECF\u9A8C: "List experiences",
2197
+ "\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u628A .iterate/ \u4E0B\u7684\u7ECF\u9A8C\u94F6\u884C\u7B49\u4EA7\u7269\u56DE\u663E\u8FDB\u4F1A\u8BDD": "Copy the disk-snapshot instruction: echo the experience bank and other .iterate/ artifacts back into the chat",
2198
+ "\u641C\u7D22\u7ECF\u9A8C\u6A21\u5F0F\u2026": "Search experience patterns\u2026",
2199
+ \u641C\u7D22\u7ECF\u9A8C: "Search experiences",
2200
+ \u590D\u5236\u641C\u7D22\u6307\u4EE4: "Copy search instruction",
2201
+ \u641C\u7D22: "Search",
2202
+ "\u6CA1\u6709\u5339\u914D\u300C": "No experience matches \u201C",
2203
+ "\u300D\u7684\u7ECF\u9A8C": "\u201D",
2204
+ "\u4E5F\u53EF\u70B9\u300C\u5217\u51FA\u7ECF\u9A8C\u300D\u67E5\u8BE2\u78C1\u76D8\u4E0A\u7684\u5168\u90E8\u5386\u53F2\u7ECF\u9A8C\u3002": 'You can also click "List experiences" to query every historical entry on disk.',
2205
+ // ── F10 defense events ───────────────────────────────────────────────────
2206
+ "\u8BF7\u8C03\u7528 `iterate_defense_events` \u67E5\u8BE2\u9632\u5FA1\u4E8B\u4EF6\uFF1A\n\n```json\n{a0}\n```": "Please call `iterate_defense_events` to query defense events:\n\n```json\n{a0}\n```",
2207
+ \u524D\u7F6E\u6821\u9A8C\u5931\u8D25: "Precondition failed",
2208
+ \u4E0D\u53D8\u91CF\u8FDD\u53CD: "Invariant violated",
2209
+ \u5047\u8BBE\u88AB\u8BC1\u4F2A: "Assumption falsified",
2210
+ \u9632\u5FA1\u4E8B\u4EF6: "Defense events",
2211
+ "\u4F1A\u8BDD\u5185 {a0} \u6761 / \u7D2F\u8BA1 {a1}": "{a0} in this session / {a1} total",
2212
+ "\u590D\u5236\u5217\u51FA\u9632\u5FA1\u4E8B\u4EF6\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09": "Copy the list-defense-events instruction (JSON arguments included)",
2213
+ \u5217\u51FA\u4E8B\u4EF6: "List events",
2214
+ "\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u628A .iterate/ \u4E0B\u7684\u9632\u5FA1\u4E8B\u4EF6\u6D41\u56DE\u663E\u8FDB\u4F1A\u8BDD": "Copy the disk-snapshot instruction: echo the defense event stream under .iterate/ back into the chat",
2215
+ "\u8BF7\u8C03\u7528 `iterate_defense_events` \u67E5\u8BE2\u4E8B\u4EF6\u7EDF\u8BA1\uFF1A\n\n```json\n{a0}\n```": "Please call `iterate_defense_events` for the event counts:\n\n```json\n{a0}\n```",
2216
+ "\u590D\u5236\u7EDF\u8BA1\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09": "Copy the counts instruction (JSON arguments included)",
2217
+ \u7EDF\u8BA1: "Counts",
2218
+ "\u8BF7\u8C03\u7528 `iterate_defense_events` \u6E05\u9664\u9632\u5FA1\u4E8B\u4EF6\u6D41\uFF1A\n\n```json\n{a0}\n```": "Please call `iterate_defense_events` to clear the defense event stream:\n\n```json\n{a0}\n```",
2219
+ "\u590D\u5236 iterate_defense_events clear \u6307\u4EE4\u6587\u672C\uFF08\u91CD\u7F6E\u9648\u65E7\u4E8B\u4EF6\u6D41\uFF09": "Copy the iterate_defense_events clear instruction (resets a stale event stream)",
2220
+ \u6E05\u9664\u4E8B\u4EF6: "Clear events",
2221
+ \u9632\u5FA1\u4E8B\u4EF6\u6D41: "Defense event stream",
2222
+ "\u9632\u5FA1\u4E8B\u4EF6\u5305\u62EC\uFF1A\u524D\u7F6E\u6821\u9A8C\u5931\u8D25\u3001\u56DE\u6EDA\u3001\u4E0D\u53D8\u91CF\u8FDD\u53CD\u3001\u5047\u8BBE\u88AB\u8BC1\u4F2A\u3002": "Defense events cover failed preconditions, rollbacks, violated invariants and falsified assumptions.",
2223
+ \u6309\u4E8B\u4EF6\u7C7B\u578B\u7B5B\u9009: "Filter by event type",
2224
+ \u6E05\u9664\u7C7B\u578B\u7B5B\u9009: "Clear type filter",
2225
+ "\u9632\u7EBF\uFF1A{a0}": "Defense: {a0}",
2226
+ \u6CA1\u6709\u5339\u914D\u8BE5\u7C7B\u578B\u7684\u9632\u5FA1\u4E8B\u4EF6: "No defense events of this type",
2227
+ // ── copy feedback ────────────────────────────────────────────────────────
2228
+ \u5DF2\u590D\u5236: "Copied",
2229
+ \u590D\u5236\u5931\u8D25: "Copy failed"
2230
+ };
2231
+ var current = "zh";
2232
+ var storage = null;
2233
+ var listeners = /* @__PURE__ */ new Set();
2234
+ function getLang() {
2235
+ return current;
2236
+ }
2237
+ function setLang(next, persist = true) {
2238
+ if (next !== current) {
2239
+ current = next;
2240
+ if (persist) {
2241
+ try {
2242
+ storage?.set(LANG_STORAGE_KEY, next);
2243
+ } catch {
2244
+ }
2245
+ }
2246
+ for (const listener of [...listeners]) {
2247
+ try {
2248
+ listener(next);
2249
+ } catch {
2250
+ }
2251
+ }
2252
+ }
2253
+ }
2254
+ function subscribeLang(listener) {
2255
+ listeners.add(listener);
2256
+ return () => {
2257
+ listeners.delete(listener);
2258
+ };
2259
+ }
2260
+ function configureLangStorage(next) {
2261
+ storage = next;
2262
+ }
2263
+ function detectLang(stored, navigatorLanguage) {
2264
+ if (stored === "en" || stored === "zh") return stored;
2265
+ return /^zh\b/i.test(navigatorLanguage ?? "") ? "zh" : "en";
2266
+ }
2267
+ function t(key) {
2268
+ return current === "en" ? EN[key] ?? key : key;
2269
+ }
2270
+ function tf(template, params = {}) {
2271
+ const translated = current === "en" ? EN[template] ?? template : template;
2272
+ return translated.replace(
2273
+ /\{([A-Za-z0-9_]+)\}/g,
2274
+ (whole, name2) => Object.prototype.hasOwnProperty.call(params, name2) ? String(params[name2]) : whole
2275
+ );
2276
+ }
2277
+
2278
+ // src/client/session-cache.ts
2279
+ var current2 = null;
2280
+ var listeners2 = /* @__PURE__ */ new Set();
2281
+ function noteSession(session) {
2282
+ if (Object.is(session, current2)) return;
2283
+ current2 = session;
2284
+ for (const listener of [...listeners2]) listener();
2285
+ }
2286
+ function readSession() {
2287
+ return current2;
2288
+ }
2289
+ function subscribeSession(listener) {
2290
+ listeners2.add(listener);
2291
+ return () => {
2292
+ listeners2.delete(listener);
2293
+ };
2294
+ }
2295
+
1777
2296
  // src/client/index.ts
1778
2297
  var React = require("react");
1779
2298
  var name = "iterate-plugin";
@@ -2106,12 +2625,12 @@ function createStorage() {
2106
2625
  }
2107
2626
  }
2108
2627
  function removeStorageByPrefix(prefix) {
2109
- if (!storage) return 0;
2628
+ if (!storage2) return 0;
2110
2629
  let removed = 0;
2111
2630
  try {
2112
- for (const key of storage.keys()) {
2631
+ for (const key of storage2.keys()) {
2113
2632
  if (key.startsWith(prefix)) {
2114
- storage.remove(key);
2633
+ storage2.remove(key);
2115
2634
  removed++;
2116
2635
  }
2117
2636
  }
@@ -2120,6 +2639,20 @@ function removeStorageByPrefix(prefix) {
2120
2639
  }
2121
2640
  return removed;
2122
2641
  }
2642
+ function useLang() {
2643
+ const [lang, set] = React.useState(getLang());
2644
+ React.useEffect(() => subscribeLang(set), []);
2645
+ return lang;
2646
+ }
2647
+ function useSessionSnapshot() {
2648
+ return React.useSyncExternalStore(subscribeSession, readSession, readSession);
2649
+ }
2650
+ function withLang(Component) {
2651
+ return function LangScoped(props) {
2652
+ useLang();
2653
+ return React.createElement(Component, props);
2654
+ };
2655
+ }
2123
2656
  function copyText(text) {
2124
2657
  if (typeof navigator !== "undefined" && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
2125
2658
  return navigator.clipboard.writeText(text).then(
@@ -2157,10 +2690,23 @@ function severityColor(severity) {
2157
2690
  function severityLabel(severity) {
2158
2691
  return SEVERITY_LABEL[coerceSeverity(severity)];
2159
2692
  }
2693
+ function formatConfigValue(value) {
2694
+ if (typeof value === "string") return value;
2695
+ if (value === void 0) return "\u2014";
2696
+ if (value === null) return "null";
2697
+ try {
2698
+ return JSON.stringify(value) ?? String(value);
2699
+ } catch {
2700
+ return String(value);
2701
+ }
2702
+ }
2703
+ function parenthesize(label) {
2704
+ return getLang() === "en" ? ` (${label})` : `\uFF08${label}\uFF09`;
2705
+ }
2160
2706
  function runStatusText(live, manifest) {
2161
- if (live) return "\u8FD0\u884C\u4E2D";
2707
+ if (live) return t("\u8FD0\u884C\u4E2D");
2162
2708
  const reason = manifest && typeof manifest.stoppedReason === "string" ? manifest.stoppedReason : "";
2163
- return reason ? stoppedReasonLabel(reason) : "\u5DF2\u7ED3\u675F";
2709
+ return reason ? stoppedReasonLabel(reason, getLang()) : t("\u5DF2\u7ED3\u675F");
2164
2710
  }
2165
2711
  function readSlots(ctx) {
2166
2712
  if (ctx && ctx.slots && typeof ctx.slots.inject === "function" && typeof ctx.slots.register === "function") {
@@ -2184,7 +2730,7 @@ function readTheme(ctx) {
2184
2730
  }
2185
2731
  var slotsSvc = void 0;
2186
2732
  var themeSvc = void 0;
2187
- var storage = null;
2733
+ var storage2 = null;
2188
2734
  var themeDisposer = null;
2189
2735
  var themeListener = null;
2190
2736
  var themeEnabled = true;
@@ -2235,7 +2781,7 @@ function registerThemeListener(ctx) {
2235
2781
  }
2236
2782
  function setThemeEnabled(enabled) {
2237
2783
  themeEnabled = enabled;
2238
- if (storage) storage.set(THEME_STORAGE_KEY, enabled ? "1" : "0");
2784
+ if (storage2) storage2.set(THEME_STORAGE_KEY, enabled ? "1" : "0");
2239
2785
  if (enabled) applyThemeSkin();
2240
2786
  else clearThemeSkin();
2241
2787
  }
@@ -2252,7 +2798,7 @@ function TrendChart({ points }) {
2252
2798
  key: p.round,
2253
2799
  className: "iterate-trend-bar",
2254
2800
  "data-hot": p.count > 0 ? "" : void 0,
2255
- title: `Round ${p.round}\uFF1A${p.count} \u9879`,
2801
+ title: tf(`Round {a0}\uFF1A{a1} \u9879`, { a0: p.round, a1: p.count }),
2256
2802
  style: { height: `${Math.max(4, Math.round(p.count / max * 24))}px` }
2257
2803
  })
2258
2804
  );
@@ -2260,7 +2806,7 @@ function TrendChart({ points }) {
2260
2806
  return React.createElement("div", {
2261
2807
  className: "iterate-trend",
2262
2808
  role: "img",
2263
- "aria-label": `\u5404\u8F6E\u53D1\u73B0\u6570\u91CF\u8D8B\u52BF\uFF1A${summary}`
2809
+ "aria-label": tf(`\u5404\u8F6E\u53D1\u73B0\u6570\u91CF\u8D8B\u52BF\uFF1A{a0}`, { a0: summary })
2264
2810
  }, ...bars);
2265
2811
  }
2266
2812
  function StartIterationButton() {
@@ -2269,7 +2815,7 @@ function StartIterationButton() {
2269
2815
  const timers = React.useRef([]);
2270
2816
  React.useEffect(
2271
2817
  () => () => {
2272
- for (const t of timers.current) clearTimeout(t);
2818
+ for (const t2 of timers.current) clearTimeout(t2);
2273
2819
  timers.current = [];
2274
2820
  },
2275
2821
  []
@@ -2279,8 +2825,8 @@ function StartIterationButton() {
2279
2825
  if (ok) {
2280
2826
  setFailed(null);
2281
2827
  setCopied(key);
2282
- const t = window.setTimeout(() => setCopied((cur) => cur === key ? null : cur), 1600);
2283
- timers.current.push(t);
2828
+ const t2 = window.setTimeout(() => setCopied((cur) => cur === key ? null : cur), 1600);
2829
+ timers.current.push(t2);
2284
2830
  } else {
2285
2831
  setCopied(null);
2286
2832
  setFailed(key);
@@ -2295,21 +2841,32 @@ function StartIterationButton() {
2295
2841
  "data-failed": failed === key ? "" : void 0,
2296
2842
  onClick: () => copy(key, command),
2297
2843
  title
2298
- }, copied === key ? "\u5DF2\u590D\u5236" : failed === key ? "\u590D\u5236\u5931\u8D25" : label);
2844
+ }, copied === key ? t("\u5DF2\u590D\u5236") : failed === key ? t("\u590D\u5236\u5931\u8D25") : label);
2299
2845
  return React.createElement(
2300
2846
  "span",
2301
2847
  { className: "iterate-dashboard-launch" },
2302
- btn("start-full", "\u5B8C\u6574\u8FED\u4EE3", START_INSTRUCTIONS.full, true, "\u590D\u5236\u542F\u52A8\u6307\u4EE4\uFF1A\u5B8C\u6574\u300C\u5BA1\u67E5 \u2192 \u4FEE\u590D \u2192 \u9A8C\u8BC1 \u2192 \u590D\u76D8\u300D\u95ED\u73AF\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\u5373\u53EF\uFF09"),
2303
- btn("start-review", "\u4EC5\u8BC4\u5BA1", START_INSTRUCTIONS.reviewOnly, false, "\u590D\u5236\u542F\u52A8\u6307\u4EE4\uFF1A\u53EA\u5BA1\u67E5\u4E0D\u4FEE\u6539\uFF08dry-run\uFF09\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\u5373\u53EF\uFF09")
2848
+ btn("start-full", t("\u5B8C\u6574\u8FED\u4EE3"), START_INSTRUCTIONS.full, true, t("\u590D\u5236\u542F\u52A8\u6307\u4EE4\uFF1A\u5B8C\u6574\u300C\u5BA1\u67E5 \u2192 \u4FEE\u590D \u2192 \u9A8C\u8BC1 \u2192 \u590D\u76D8\u300D\u95ED\u73AF\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\u5373\u53EF\uFF09")),
2849
+ btn("start-review", t("\u4EC5\u8BC4\u5BA1"), START_INSTRUCTIONS.reviewOnly, false, t("\u590D\u5236\u542F\u52A8\u6307\u4EE4\uFF1A\u53EA\u5BA1\u67E5\u4E0D\u4FEE\u6539\uFF08dry-run\uFF09\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\u5373\u53EF\uFF09"))
2304
2850
  );
2305
2851
  }
2306
2852
  function ConvergenceDashboard(props) {
2307
2853
  const [pulseKey, setPulseKey] = React.useState(0);
2308
2854
  const session = props && props.session ? props.session : null;
2855
+ React.useEffect(() => {
2856
+ noteSession(session);
2857
+ }, [session]);
2309
2858
  const report = latestReport(session);
2859
+ const pulseSeenRef = React.useRef(null);
2310
2860
  React.useEffect(() => {
2311
2861
  if (!report) return;
2312
2862
  const cur = getCurrentRound(report);
2863
+ const key = hashReport(report) + ":" + String(cur);
2864
+ if (pulseSeenRef.current === null) {
2865
+ pulseSeenRef.current = key;
2866
+ return;
2867
+ }
2868
+ if (pulseSeenRef.current === key) return;
2869
+ pulseSeenRef.current = key;
2313
2870
  const conv = report.convergence;
2314
2871
  emitRoundPulse(cur, conv?.converged === true);
2315
2872
  setPulseKey((k) => k + 1);
@@ -2320,11 +2877,11 @@ function ConvergenceDashboard(props) {
2320
2877
  return React.createElement(
2321
2878
  "div",
2322
2879
  { "data-iterate-root": "", "data-iterate": "dashboard", "data-run-state": "running", className: "iterate-dashboard iterate-dashboard-empty" },
2323
- React.createElement("span", { className: "iterate-round-badge", "data-live": "" }, "\u8FD0\u884C\u4E2D"),
2880
+ React.createElement("span", { className: "iterate-round-badge", "data-live": "" }, t("\u8FD0\u884C\u4E2D")),
2324
2881
  React.createElement(
2325
2882
  "span",
2326
2883
  { className: "iterate-empty-hint" },
2327
- `\u7B2C ${String(runState.round)} \u8F6E\u8FDB\u884C\u4E2D${runState.phase ? ` \xB7 \u9636\u6BB5\u300C${runState.phase}\u300D` : ""}\u2014\u2014\u9996\u8F6E\u6C47\u603B\u62A5\u544A\u843D\u5730\u540E\uFF0C\u8FD9\u91CC\u4F1A\u663E\u793A\u6536\u655B\u8FDB\u5EA6\u4E0E\u53D1\u73B0\u7EDF\u8BA1\u3002`
2884
+ tf(`\u7B2C {a0} \u8F6E\u8FDB\u884C\u4E2D{a1}\u2014\u2014\u9996\u8F6E\u6C47\u603B\u62A5\u544A\u843D\u5730\u540E\uFF0C\u8FD9\u91CC\u4F1A\u663E\u793A\u6536\u655B\u8FDB\u5EA6\u4E0E\u53D1\u73B0\u7EDF\u8BA1\u3002`, { a0: String(runState.round), a1: runState.phase ? tf(` \xB7 \u9636\u6BB5\u300C{a0}\u300D`, { a0: runState.phase }) : "" })
2328
2885
  )
2329
2886
  // No start button while a run is active.
2330
2887
  );
@@ -2333,11 +2890,11 @@ function ConvergenceDashboard(props) {
2333
2890
  return React.createElement(
2334
2891
  "div",
2335
2892
  { "data-iterate-root": "", "data-iterate": "dashboard", "data-run-state": "done", className: "iterate-dashboard iterate-dashboard-empty" },
2336
- React.createElement("span", { className: "iterate-round-badge" }, "\u5DF2\u7ED3\u675F"),
2893
+ React.createElement("span", { className: "iterate-round-badge" }, t("\u5DF2\u7ED3\u675F")),
2337
2894
  React.createElement(
2338
2895
  "span",
2339
2896
  { className: "iterate-empty-hint" },
2340
- `\u4E0A\u6B21\u8FED\u4EE3\u5728\u7B2C ${String(runState.round)} \u8F6E\u540E\u505C\u6B62${runState.stoppedReason ? `\uFF08${stoppedReasonLabel(runState.stoppedReason)}\uFF09` : ""}\uFF0C\u4F46\u6CA1\u6709\u53EF\u5C55\u793A\u7684\u6C47\u603B\u62A5\u544A\u3002`
2897
+ tf(`\u4E0A\u6B21\u8FED\u4EE3\u5728\u7B2C {a0} \u8F6E\u540E\u505C\u6B62{a1}\uFF0C\u4F46\u6CA1\u6709\u53EF\u5C55\u793A\u7684\u6C47\u603B\u62A5\u544A\u3002`, { a0: String(runState.round), a1: runState.stoppedReason ? parenthesize(stoppedReasonLabel(runState.stoppedReason, getLang())) : "" })
2341
2898
  ),
2342
2899
  React.createElement(StartIterationButton, null)
2343
2900
  );
@@ -2346,7 +2903,7 @@ function ConvergenceDashboard(props) {
2346
2903
  "div",
2347
2904
  { "data-iterate-root": "", "data-iterate": "dashboard", "data-run-state": "empty", className: "iterate-dashboard iterate-dashboard-empty" },
2348
2905
  React.createElement("span", { className: "iterate-round-badge" }, "iterate"),
2349
- React.createElement("span", { className: "iterate-empty-hint" }, "\u8FD0\u884C\u4E00\u6B21\u8BC4\u5BA1\u540E\uFF0C\u8FD9\u91CC\u4F1A\u663E\u793A\u6536\u655B\u8FDB\u5EA6\u4E0E\u53D1\u73B0\u7EDF\u8BA1\u3002"),
2906
+ React.createElement("span", { className: "iterate-empty-hint" }, t("\u8FD0\u884C\u4E00\u6B21\u8BC4\u5BA1\u540E\uFF0C\u8FD9\u91CC\u4F1A\u663E\u793A\u6536\u655B\u8FDB\u5EA6\u4E0E\u53D1\u73B0\u7EDF\u8BA1\u3002")),
2350
2907
  React.createElement(StartIterationButton, null)
2351
2908
  );
2352
2909
  }
@@ -2361,20 +2918,20 @@ function ConvergenceDashboard(props) {
2361
2918
  const resumeChip = resumeCount > 0 ? React.createElement("span", {
2362
2919
  className: "iterate-chip-resume",
2363
2920
  key: "resume",
2364
- title: "\u672C\u6B21\u8FED\u4EE3\u4ECE\u4E0A\u4E00\u6B21\u4E2D\u65AD\u7684\u65AD\u70B9\u7EE7\u7EED\u6267\u884C"
2365
- }, `\u5DF2\u4E2D\u65AD\u6062\u590D \xD7${String(resumeCount)}`) : null;
2921
+ title: t("\u672C\u6B21\u8FED\u4EE3\u4ECE\u4E0A\u4E00\u6B21\u4E2D\u65AD\u7684\u65AD\u70B9\u7EE7\u7EED\u6267\u884C")
2922
+ }, tf(`\u5DF2\u4E2D\u65AD\u6062\u590D \xD7{a0}`, { a0: String(resumeCount) })) : null;
2366
2923
  const imageChip = imageCount > 0 ? React.createElement("span", {
2367
2924
  className: "iterate-chip-images",
2368
2925
  key: "images",
2369
- title: "\u4F1A\u8BDD\u4E2D\u68C0\u6D4B\u5230\u7528\u6237\u9644\u5E26\u7684\u56FE\u7247\uFF0C\u8BC4\u5BA1\u5C06\u4F5C\u4E3A\u89C6\u89C9\u8BC1\u636E\u53C2\u8003"
2370
- }, `\u9644\u4EF6\u56FE\u7247 ${String(imageCount)}`) : null;
2926
+ title: t("\u4F1A\u8BDD\u4E2D\u68C0\u6D4B\u5230\u7528\u6237\u9644\u5E26\u7684\u56FE\u7247\uFF0C\u8BC4\u5BA1\u5C06\u4F5C\u4E3A\u89C6\u89C9\u8BC1\u636E\u53C2\u8003")
2927
+ }, tf(`\u9644\u4EF6\u56FE\u7247 {a0}`, { a0: String(imageCount) })) : null;
2371
2928
  const transcript = latestTranscript(session);
2372
2929
  const phase = transcript ? latestPhase(transcript.phases) : "";
2373
2930
  const liveChip = transcript && phase ? React.createElement("span", {
2374
2931
  className: "iterate-chip-phase",
2375
2932
  key: "phase",
2376
2933
  "data-live": transcript.active === true ? "" : void 0,
2377
- title: transcript.active === true ? `\u5F53\u524D\u5904\u4E8E\u300C${phase}\u300D\u9636\u6BB5\uFF08\u8FD0\u884C\u4E2D\uFF09` : `\u6700\u8FD1\u4E00\u6B21\u6267\u884C\u505C\u7559\u5728\u300C${phase}\u300D\u9636\u6BB5\uFF08\u5DF2\u7ED3\u675F\uFF09`
2934
+ title: transcript.active === true ? tf(`\u5F53\u524D\u5904\u4E8E\u300C{a0}\u300D\u9636\u6BB5\uFF08\u8FD0\u884C\u4E2D\uFF09`, { a0: phase }) : tf(`\u6700\u8FD1\u4E00\u6B21\u6267\u884C\u505C\u7559\u5728\u300C{a0}\u300D\u9636\u6BB5\uFF08\u5DF2\u7ED3\u675F\uFF09`, { a0: phase })
2378
2935
  }, `${phase} \xB7 ${runStatusText(transcript.active === true, transcript)}`) : null;
2379
2936
  const taskModeTranscript = latestTranscript(session);
2380
2937
  const taskMode = taskModeTranscript?.taskMode;
@@ -2382,7 +2939,7 @@ function ConvergenceDashboard(props) {
2382
2939
  className: "iterate-chip-taskmode",
2383
2940
  key: "taskmode",
2384
2941
  "data-mode": taskMode,
2385
- title: taskMode === "code" ? "\u4EE3\u7801\u6A21\u5F0F\uFF1A\u76F4\u63A5\u4FEE\u6539\u6587\u4EF6" : "\u8FED\u4EE3\u6A21\u5F0F\uFF1A\u5BA1\u67E5-\u4FEE\u590D\u5FAA\u73AF"
2942
+ title: taskMode === "code" ? t("\u4EE3\u7801\u6A21\u5F0F\uFF1A\u76F4\u63A5\u4FEE\u6539\u6587\u4EF6") : t("\u8FED\u4EE3\u6A21\u5F0F\uFF1A\u5BA1\u67E5-\u4FEE\u590D\u5FAA\u73AF")
2386
2943
  }, taskMode === "code" ? "\u25CF code" : "\u25CF iterate") : null;
2387
2944
  const dimNames = Object.keys(dims);
2388
2945
  const dimBadges = dimNames.slice(0, 6).map(
@@ -2398,7 +2955,7 @@ function ConvergenceDashboard(props) {
2398
2955
  React.createElement(
2399
2956
  "span",
2400
2957
  { key: "+more", className: "iterate-dim-badge iterate-dim-more", title: dimNames.slice(6).join(", ") },
2401
- `+${overflow} \u66F4\u591A`
2958
+ tf(`+{a0} \u66F4\u591A`, { a0: overflow })
2402
2959
  )
2403
2960
  );
2404
2961
  }
@@ -2409,14 +2966,14 @@ function ConvergenceDashboard(props) {
2409
2966
  const fixBadge = fixCount !== null ? React.createElement("span", {
2410
2967
  className: "iterate-metric",
2411
2968
  key: "fixes",
2412
- title: "\u672C\u8F6E\u5DF2\u5E94\u7528\u7684\u539F\u5B50\u4FEE\u590D\u6570\uFF08\u6B63\u5E38\u6A21\u5F0F\uFF09"
2969
+ title: t("\u672C\u8F6E\u5DF2\u5E94\u7528\u7684\u539F\u5B50\u4FEE\u590D\u6570\uFF08\u6B63\u5E38\u6A21\u5F0F\uFF09")
2413
2970
  }, `${String(fixCount)} fixes`) : null;
2414
2971
  const converged = report.convergence && report.convergence.converged === true;
2415
2972
  const convChip = converged ? React.createElement("span", {
2416
2973
  className: "iterate-chip-resume",
2417
2974
  key: "converged",
2418
- title: "\u5BA1\u67E5\u5DF2\u6536\u655B\uFF1A\u6700\u540E\u4E00\u8F6E\u672A\u53D1\u73B0\u65B0\u95EE\u9898"
2419
- }, "\u2713 \u5DF2\u6536\u655B") : null;
2975
+ title: t("\u5BA1\u67E5\u5DF2\u6536\u655B\uFF1A\u6700\u540E\u4E00\u8F6E\u672A\u53D1\u73B0\u65B0\u95EE\u9898")
2976
+ }, t("\u2713 \u5DF2\u6536\u655B")) : null;
2420
2977
  const sevMetric = (key, label) => React.createElement(
2421
2978
  "span",
2422
2979
  { className: "iterate-metric", key, title: label },
@@ -2485,19 +3042,19 @@ function StatsCard(props) {
2485
3042
  React.createElement("td", { style: { color: SEVERITY_COLOR.low } }, String(h.low))
2486
3043
  )
2487
3044
  );
2488
- const trendLine = `\u9996\u8F6E ${trend.firstRound} \u2192 \u672B\u8F6E ${trend.lastRound} \u9879\uFF0C\u964D\u5E45 ${trend.reductionPercent}%${trend.converged ? "\uFF0C\u5DF2\u6536\u655B" : ""}`;
2489
- const completion = buildCompletionSummary(report);
3045
+ const trendLine = tf(`\u9996\u8F6E {a0} \u2192 \u672B\u8F6E {a1} \u9879\uFF0C\u964D\u5E45 {a2}%{a3}`, { a0: trend.firstRound, a1: trend.lastRound, a2: trend.reductionPercent, a3: trend.converged ? t("\uFF0C\u5DF2\u6536\u655B") : "" });
3046
+ const completion = buildCompletionSummary(report, getLang());
2490
3047
  return React.createElement(
2491
3048
  "div",
2492
3049
  { "data-iterate-root": "", "data-iterate": "stats", className: "iterate-stats" },
2493
3050
  React.createElement(
2494
3051
  "div",
2495
3052
  { className: "iterate-triage-head" },
2496
- React.createElement("span", {}, "Iterate \xB7 \u6536\u655B\u7EDF\u8BA1"),
3053
+ React.createElement("span", {}, t("Iterate \xB7 \u6536\u655B\u7EDF\u8BA1")),
2497
3054
  React.createElement(
2498
3055
  "span",
2499
3056
  { className: "iterate-triage-hint" },
2500
- `Round ${getCurrentRound(report)}/${getTotalRounds(report)} \xB7 ${total} findings \xB7 ${report.convergence && report.convergence.converged ? "\u5DF2\u6536\u655B" : "\u672A\u6536\u655B"}`
3057
+ `Round ${getCurrentRound(report)}/${getTotalRounds(report)} \xB7 ${total} findings \xB7 ${report.convergence && report.convergence.converged ? t("\u5DF2\u6536\u655B") : t("\u672A\u6536\u655B")}`
2501
3058
  )
2502
3059
  ),
2503
3060
  React.createElement("div", { className: "iterate-stats-grid" }, ...rows),
@@ -2510,7 +3067,7 @@ function StatsCard(props) {
2510
3067
  className: "iterate-btn",
2511
3068
  "data-primary": showHistory ? "" : void 0,
2512
3069
  onClick: () => setShowHistory((v) => !v)
2513
- }, showHistory ? "\u6536\u8D77\u5386\u53F2" : "\u5386\u53F2 / \u8D8B\u52BF")
3070
+ }, showHistory ? t("\u6536\u8D77\u5386\u53F2") : t("\u5386\u53F2 / \u8D8B\u52BF"))
2514
3071
  ),
2515
3072
  showHistory ? React.createElement(
2516
3073
  "div",
@@ -2524,8 +3081,8 @@ function StatsCard(props) {
2524
3081
  React.createElement(
2525
3082
  "tr",
2526
3083
  {},
2527
- React.createElement("th", {}, "\u8F6E\u6B21"),
2528
- React.createElement("th", {}, "\u53D1\u73B0"),
3084
+ React.createElement("th", {}, t("\u8F6E\u6B21")),
3085
+ React.createElement("th", {}, t("\u53D1\u73B0")),
2529
3086
  React.createElement("th", { style: { color: SEVERITY_COLOR.critical } }, "CRIT"),
2530
3087
  React.createElement("th", { style: { color: SEVERITY_COLOR.high } }, "HIGH"),
2531
3088
  React.createElement("th", { style: { color: SEVERITY_COLOR.medium } }, "MED"),
@@ -2548,7 +3105,7 @@ function TriagePanel(props) {
2548
3105
  const storageKey = TRIAGE_STORAGE_PREFIX + hashReport(report);
2549
3106
  const [verdicts, setVerdicts] = React.useState(() => {
2550
3107
  const initial = buildTriageState(report);
2551
- const saved = storage ? storage.get(storageKey) : null;
3108
+ const saved = storage2 ? storage2.get(storageKey) : null;
2552
3109
  if (saved) {
2553
3110
  try {
2554
3111
  const parsed = JSON.parse(saved);
@@ -2573,7 +3130,7 @@ function TriagePanel(props) {
2573
3130
  const [cmdFailed, setCmdFailed] = React.useState(null);
2574
3131
  const cmdTimers = React.useRef([]);
2575
3132
  React.useEffect(() => () => {
2576
- for (const t of cmdTimers.current) clearTimeout(t);
3133
+ for (const t2 of cmdTimers.current) clearTimeout(t2);
2577
3134
  cmdTimers.current = [];
2578
3135
  }, []);
2579
3136
  const copyWithFeedback = (text, key) => {
@@ -2590,7 +3147,7 @@ function TriagePanel(props) {
2590
3147
  });
2591
3148
  };
2592
3149
  React.useEffect(() => {
2593
- if (storage) storage.set(storageKey, JSON.stringify(verdicts));
3150
+ if (storage2) storage2.set(storageKey, JSON.stringify(verdicts));
2594
3151
  }, [storageKey, verdicts]);
2595
3152
  const setVerdict = (index, verdict) => {
2596
3153
  setVerdicts((prev) => ({ ...prev, [String(index)]: verdict }));
@@ -2612,15 +3169,16 @@ function TriagePanel(props) {
2612
3169
  setVerdicts((prev) => setAllVerdicts(prev, "keep"));
2613
3170
  setSelectAll(false);
2614
3171
  };
3172
+ const rootRef = React.useRef(null);
2615
3173
  React.useEffect(() => {
2616
3174
  const doc = typeof document !== "undefined" ? document : null;
2617
3175
  if (!doc) return;
2618
3176
  const onKeyDown = (ev) => {
2619
3177
  if (ev.metaKey || ev.ctrlKey || ev.altKey) return;
2620
- const t = ev.target;
2621
- if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT")) return;
2622
- if (t && typeof t.isContentEditable === "boolean" && t.isContentEditable) return;
2623
- const rootEl = doc.querySelector('[data-iterate="triage"]');
3178
+ const t2 = ev.target;
3179
+ if (t2 && (t2.tagName === "INPUT" || t2.tagName === "TEXTAREA" || t2.tagName === "SELECT")) return;
3180
+ if (t2 && typeof t2.isContentEditable === "boolean" && t2.isContentEditable) return;
3181
+ const rootEl = rootRef.current;
2624
3182
  if (!rootEl) return;
2625
3183
  const activeEl = doc.activeElement;
2626
3184
  if (!activeEl) return;
@@ -2658,11 +3216,11 @@ function TriagePanel(props) {
2658
3216
  copyWithFeedback(cmd, "approve-arch");
2659
3217
  };
2660
3218
  const doTriggerNewRound = () => {
2661
- const cmd = "\u8BF7\u7EE7\u7EED\u6267\u884C\u4E0B\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5";
3219
+ const cmd = t("\u8BF7\u7EE7\u7EED\u6267\u884C\u4E0B\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5");
2662
3220
  copyWithFeedback(cmd, "new-round");
2663
3221
  };
2664
3222
  const doRollbackToCheckpoint = () => {
2665
- const cmd = "\u8BF7\u56DE\u6EDA\u4E0A\u4E00\u8F6E\u8FED\u4EE3\u7684\u4FEE\u590D\uFF1A\u8C03\u7528 `iterate_history` \u67E5\u770B\u672C\u8F6E fix id\uFF0C\u518D\u7528 `iterate_rollback` \u9010\u4E2A\u64A4\u9500\u8FD9\u4E9B\u4FEE\u590D";
3223
+ const cmd = t("\u8BF7\u56DE\u6EDA\u4E0A\u4E00\u8F6E\u8FED\u4EE3\u7684\u4FEE\u590D\uFF1A\u8C03\u7528 `iterate_history` \u67E5\u770B\u672C\u8F6E fix id\uFF0C\u518D\u7528 `iterate_rollback` \u9010\u4E2A\u64A4\u9500\u8FD9\u4E9B\u4FEE\u590D");
2666
3224
  copyWithFeedback(cmd, "rollback");
2667
3225
  };
2668
3226
  const doAssignFindings = () => {
@@ -2750,36 +3308,36 @@ function TriagePanel(props) {
2750
3308
  React.createElement(
2751
3309
  "div",
2752
3310
  { className: "iterate-finding-actions" },
2753
- btn("y \u4FEE\u590D", "keep", "\u4FDD\u7559\u8BE5 finding\uFF0C\u8FDB\u5165\u4FEE\u590D"),
2754
- btn("n \u8DF3\u8FC7", "skip", "\u8DF3\u8FC7\u8BE5 finding"),
2755
- btn("a \u5DF2\u77E5\u6709\u610F", "ignore", "\u6807\u8BB0\u4E3A\u5DF2\u77E5\u6709\u610F\u5E76\u5199\u5165 known_intentional")
3311
+ btn(t("y \u4FEE\u590D"), "keep", t("\u4FDD\u7559\u8BE5 finding\uFF0C\u8FDB\u5165\u4FEE\u590D")),
3312
+ btn(t("n \u8DF3\u8FC7"), "skip", t("\u8DF3\u8FC7\u8BE5 finding")),
3313
+ btn(t("a \u5DF2\u77E5\u6709\u610F"), "ignore", t("\u6807\u8BB0\u4E3A\u5DF2\u77E5\u6709\u610F\u5E76\u5199\u5165 known_intentional"))
2756
3314
  )
2757
3315
  );
2758
3316
  });
2759
3317
  return React.createElement(
2760
3318
  "div",
2761
- { "data-iterate-root": "", "data-iterate": "triage", className: "iterate-triage" },
3319
+ { ref: rootRef, "data-iterate-root": "", "data-iterate": "triage", className: "iterate-triage" },
2762
3320
  React.createElement(
2763
3321
  "div",
2764
3322
  { className: "iterate-triage-head" },
2765
- React.createElement("span", { role: "heading", "aria-level": 3 }, `Iterate \xB7 Findings \u5206\u8BCA (${filtered.length}/${findings.length})`),
2766
- React.createElement("span", { className: "iterate-triage-hint" }, "y=\u4FEE\u590D \xB7 n=\u8DF3\u8FC7 \xB7 a=\u5DF2\u77E5\u6709\u610F \xB7 \u2191/\u2193 \u9009\u62E9")
3323
+ React.createElement("span", { role: "heading", "aria-level": 3 }, tf(`Iterate \xB7 Findings \u5206\u8BCA ({a0}/{a1})`, { a0: filtered.length, a1: findings.length })),
3324
+ React.createElement("span", { className: "iterate-triage-hint" }, t("y=\u4FEE\u590D \xB7 n=\u8DF3\u8FC7 \xB7 a=\u5DF2\u77E5\u6709\u610F \xB7 \u2191/\u2193 \u9009\u62E9"))
2767
3325
  ),
2768
3326
  React.createElement(
2769
3327
  "div",
2770
3328
  { className: "iterate-filter" },
2771
3329
  React.createElement(
2772
3330
  "select",
2773
- { className: "iterate-filter-select", value: filter.severities[0] || "", onChange: (e) => setSeverityFilter(e.target.value), "aria-label": "\u6309\u4E25\u91CD\u5EA6\u7B5B\u9009" },
2774
- React.createElement("option", { value: "" }, "\u5168\u90E8\u4E25\u91CD\u5EA6"),
3331
+ { className: "iterate-filter-select", value: filter.severities[0] || "", onChange: (e) => setSeverityFilter(e.target.value), "aria-label": t("\u6309\u4E25\u91CD\u5EA6\u7B5B\u9009") },
3332
+ React.createElement("option", { value: "" }, t("\u5168\u90E8\u4E25\u91CD\u5EA6")),
2775
3333
  ...options.severities.map(
2776
3334
  (s) => React.createElement("option", { key: s.value, value: s.value }, `${severityLabel(s.value)} (${s.count})`)
2777
3335
  )
2778
3336
  ),
2779
3337
  React.createElement(
2780
3338
  "select",
2781
- { className: "iterate-filter-select", value: filter.dimensions[0] || "", onChange: (e) => setDimensionFilter(e.target.value), "aria-label": "\u6309\u7EF4\u5EA6\u7B5B\u9009" },
2782
- React.createElement("option", { value: "" }, "\u5168\u90E8\u7EF4\u5EA6"),
3339
+ { className: "iterate-filter-select", value: filter.dimensions[0] || "", onChange: (e) => setDimensionFilter(e.target.value), "aria-label": t("\u6309\u7EF4\u5EA6\u7B5B\u9009") },
3340
+ React.createElement("option", { value: "" }, t("\u5168\u90E8\u7EF4\u5EA6")),
2783
3341
  ...options.dimensions.map(
2784
3342
  (d) => React.createElement("option", { key: d.value, value: d.value }, `${d.value} (${d.count})`)
2785
3343
  )
@@ -2787,41 +3345,41 @@ function TriagePanel(props) {
2787
3345
  React.createElement("input", {
2788
3346
  className: "iterate-filter-search",
2789
3347
  type: "search",
2790
- placeholder: "\u641C\u7D22\u6587\u4EF6 / \u6458\u8981\u2026",
2791
- "aria-label": "\u641C\u7D22\u6587\u4EF6\u6216\u6458\u8981",
3348
+ placeholder: t("\u641C\u7D22\u6587\u4EF6 / \u6458\u8981\u2026"),
3349
+ "aria-label": t("\u641C\u7D22\u6587\u4EF6\u6216\u6458\u8981"),
2792
3350
  value: filter.search,
2793
3351
  onChange: (e) => setSearchFilter(e.target.value)
2794
3352
  }),
2795
3353
  React.createElement(
2796
3354
  "span",
2797
3355
  { className: "iterate-filter-count" },
2798
- isFilterActive ? React.createElement("button", { className: "iterate-batch-btn", onClick: clearFilter }, `\u6E05\u9664\u7B5B\u9009\uFF08\u663E\u793A ${filtered.length}\uFF09`) : `\u5171 ${findings.length} \u9879`
3356
+ isFilterActive ? React.createElement("button", { className: "iterate-batch-btn", onClick: clearFilter }, tf(`\u6E05\u9664\u7B5B\u9009\uFF08\u663E\u793A {a0}\uFF09`, { a0: filtered.length })) : tf(`\u5171 {a0} \u9879`, { a0: findings.length })
2799
3357
  )
2800
3358
  ),
2801
3359
  React.createElement(
2802
3360
  "div",
2803
3361
  { className: "iterate-batch" },
2804
- React.createElement("span", { className: "iterate-batch-label" }, "\u6279\u91CF\u4F5C\u7528\u4E8E\uFF1A"),
3362
+ React.createElement("span", { className: "iterate-batch-label" }, t("\u6279\u91CF\u4F5C\u7528\u4E8E\uFF1A")),
2805
3363
  React.createElement("button", {
2806
3364
  className: selectAll ? "iterate-batch-btn iterate-batch-scope" : "iterate-batch-btn iterate-batch-scope iterate-batch-scope-on",
2807
3365
  onClick: () => setSelectAll(false),
2808
- title: "\u6279\u91CF\u6309\u94AE\u4EC5\u4F5C\u7528\u4E8E\u5F53\u524D\u7B5B\u9009\u53EF\u89C1\u7684 findings"
2809
- }, `\u53EF\u89C1 ${indices.length}`),
3366
+ title: t("\u6279\u91CF\u6309\u94AE\u4EC5\u4F5C\u7528\u4E8E\u5F53\u524D\u7B5B\u9009\u53EF\u89C1\u7684 findings")
3367
+ }, tf(`\u53EF\u89C1 {a0}`, { a0: indices.length })),
2810
3368
  React.createElement("button", {
2811
3369
  className: selectAll ? "iterate-batch-btn iterate-batch-scope iterate-batch-scope-on" : "iterate-batch-btn iterate-batch-scope",
2812
3370
  onClick: () => setSelectAll(true),
2813
- title: "\u6279\u91CF\u6309\u94AE\u4F5C\u7528\u4E8E\u5168\u90E8 findings"
2814
- }, `\u5168\u90E8 ${allIndices.length}`),
3371
+ title: t("\u6279\u91CF\u6309\u94AE\u4F5C\u7528\u4E8E\u5168\u90E8 findings")
3372
+ }, tf(`\u5168\u90E8 {a0}`, { a0: allIndices.length })),
2815
3373
  React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("keep") }, "y"),
2816
3374
  React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("skip") }, "n"),
2817
3375
  React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("ignore") }, "a"),
2818
- React.createElement("button", { className: "iterate-batch-btn", onClick: doResetVerdicts, title: "\u628A\u6240\u6709\u5224\u5B9A\u6062\u590D\u4E3A\u9ED8\u8BA4 y\uFF08\u4FEE\u590D\uFF09" }, "\u91CD\u7F6E")
3376
+ React.createElement("button", { className: "iterate-batch-btn", onClick: doResetVerdicts, title: t("\u628A\u6240\u6709\u5224\u5B9A\u6062\u590D\u4E3A\u9ED8\u8BA4 y\uFF08\u4FEE\u590D\uFF09") }, t("\u91CD\u7F6E"))
2819
3377
  ),
2820
3378
  ...rows,
2821
3379
  React.createElement(
2822
3380
  "div",
2823
3381
  { className: "iterate-triage-foot" },
2824
- React.createElement("span", {}, `y ${counts.keep} \xB7 n ${counts.skip} \xB7 a ${counts.ignore} \xB7 \u5F85\u5199\u56DE known_intentional\uFF1A${ignoredCount} \u6761`),
3382
+ React.createElement("span", {}, tf(`y {a0} \xB7 n {a1} \xB7 a {a2} \xB7 \u5F85\u5199\u56DE known_intentional\uFF1A{a3} \u6761`, { a0: counts.keep, a1: counts.skip, a2: counts.ignore, a3: ignoredCount })),
2825
3383
  React.createElement(
2826
3384
  "span",
2827
3385
  { style: { display: "flex", gap: 6, flexWrap: "wrap" } },
@@ -2834,31 +3392,31 @@ function TriagePanel(props) {
2834
3392
  "data-failed": cmdFailed === "apply" ? "" : void 0,
2835
3393
  onClick: doBuildInstruction,
2836
3394
  disabled: ignoredCount === 0,
2837
- title: ignoredCount === 0 ? "\u5F53\u524D\u6CA1\u6709\u6807\u8BB0\u4E3A\u300C\u5DF2\u77E5\u6709\u610F\u300D\u7684 finding" : "\u590D\u5236\u5E94\u7528\u6307\u4EE4\uFF1A\u7C98\u8D34\u540E\u6A21\u578B\u4F1A\u8C03\u7528 iterate_triage apply\uFF08\u6821\u9A8C\u3001\u53BB\u91CD\u3001\u5199\u5165\u524D\u5907\u4EFD\u3001\u5931\u8D25\u81EA\u52A8\u56DE\u6EDA\uFF09"
2838
- }, cmdCopied === "apply" ? "\u5DF2\u590D\u5236" : cmdFailed === "apply" ? "\u590D\u5236\u5931\u8D25" : `\u751F\u6210\u5E94\u7528\u6307\u4EE4${ignoredCount > 0 ? `\uFF08${ignoredCount}\uFF09` : ""}`),
3395
+ title: ignoredCount === 0 ? t("\u5F53\u524D\u6CA1\u6709\u6807\u8BB0\u4E3A\u300C\u5DF2\u77E5\u6709\u610F\u300D\u7684 finding") : t("\u590D\u5236\u5E94\u7528\u6307\u4EE4\uFF1A\u7C98\u8D34\u540E\u6A21\u578B\u4F1A\u8C03\u7528 iterate_triage apply\uFF08\u6821\u9A8C\u3001\u53BB\u91CD\u3001\u5199\u5165\u524D\u5907\u4EFD\u3001\u5931\u8D25\u81EA\u52A8\u56DE\u6EDA\uFF09")
3396
+ }, cmdCopied === "apply" ? t("\u5DF2\u590D\u5236") : cmdFailed === "apply" ? t("\u590D\u5236\u5931\u8D25") : tf(`\u751F\u6210\u5E94\u7528\u6307\u4EE4{a0}`, { a0: ignoredCount > 0 ? `\uFF08${ignoredCount}\uFF09` : "" })),
2839
3397
  React.createElement("button", {
2840
3398
  className: "iterate-btn",
2841
3399
  "data-copied": copied ? "" : void 0,
2842
3400
  "data-failed": cmdFailed === "yaml" ? "" : void 0,
2843
3401
  onClick: doCopyYaml,
2844
3402
  disabled: ignoredCount === 0,
2845
- title: ignoredCount === 0 ? "\u5F53\u524D\u6CA1\u6709\u6807\u8BB0\u4E3A\u300C\u5DF2\u77E5\u6709\u610F\u300D\u7684 finding" : "\u53EA\u590D\u5236 known_intentional YAML \u7247\u6BB5\uFF08\u9700\u81EA\u884C\u7C98\u5230 iterate.config.yaml \u7684 personalization.known_intentional \u4E0B\uFF09"
2846
- }, copied ? "\u5DF2\u590D\u5236" : cmdFailed === "yaml" ? "\u590D\u5236\u5931\u8D25" : "\u4EC5\u590D\u5236 YAML \u7247\u6BB5"),
3403
+ title: ignoredCount === 0 ? t("\u5F53\u524D\u6CA1\u6709\u6807\u8BB0\u4E3A\u300C\u5DF2\u77E5\u6709\u610F\u300D\u7684 finding") : t("\u53EA\u590D\u5236 known_intentional YAML \u7247\u6BB5\uFF08\u9700\u81EA\u884C\u7C98\u5230 iterate.config.yaml \u7684 personalization.known_intentional \u4E0B\uFF09")
3404
+ }, copied ? t("\u5DF2\u590D\u5236") : cmdFailed === "yaml" ? t("\u590D\u5236\u5931\u8D25") : t("\u4EC5\u590D\u5236 YAML \u7247\u6BB5")),
2847
3405
  React.createElement("button", {
2848
3406
  className: "iterate-btn",
2849
3407
  "data-copied": cmdCopied === "readback" ? "" : void 0,
2850
3408
  "data-failed": cmdFailed === "readback" ? "" : void 0,
2851
3409
  onClick: doReadback,
2852
- title: "\u590D\u5236 iterate_triage list \u6307\u4EE4\uFF0C\u6838\u5BF9\u5DF2\u7ECF\u5199\u5165\u7684 known_intentional \u6761\u76EE\uFF08\u5199\u56DE\u95ED\u73AF\u786E\u8BA4\uFF09"
2853
- }, cmdCopied === "readback" ? "\u5DF2\u590D\u5236" : cmdFailed === "readback" ? "\u590D\u5236\u5931\u8D25" : "\u56DE\u8BFB\u5DF2\u5199\u5165\u6761\u76EE")
3410
+ title: t("\u590D\u5236 iterate_triage list \u6307\u4EE4\uFF0C\u6838\u5BF9\u5DF2\u7ECF\u5199\u5165\u7684 known_intentional \u6761\u76EE\uFF08\u5199\u56DE\u95ED\u73AF\u786E\u8BA4\uFF09")
3411
+ }, cmdCopied === "readback" ? t("\u5DF2\u590D\u5236") : cmdFailed === "readback" ? t("\u590D\u5236\u5931\u8D25") : t("\u56DE\u8BFB\u5DF2\u5199\u5165\u6761\u76EE"))
2854
3412
  ),
2855
- cmdCopied === "apply" ? React.createElement("span", { className: "iterate-triage-hint", role: "status" }, "\u5DF2\u590D\u5236\u5E94\u7528\u6307\u4EE4\u2014\u2014\u7C98\u8D34\u56DE\u4F1A\u8BDD\uFF0C\u6A21\u578B\u5C06\u8C03\u7528 iterate_triage apply \u5E76\u56DE\u62A5\u5199\u5165\u6761\u6570\u3002") : cmdCopied === "readback" ? React.createElement("span", { className: "iterate-triage-hint", role: "status" }, "\u5DF2\u590D\u5236\u56DE\u8BFB\u6307\u4EE4\u2014\u2014\u7C98\u8D34\u56DE\u4F1A\u8BDD\u53EF\u5217\u51FA\u5F53\u524D\u5DF2\u5199\u5165\u7684 known_intentional\u3002") : null
3413
+ cmdCopied === "apply" ? React.createElement("span", { className: "iterate-triage-hint", role: "status" }, t("\u5DF2\u590D\u5236\u5E94\u7528\u6307\u4EE4\u2014\u2014\u7C98\u8D34\u56DE\u4F1A\u8BDD\uFF0C\u6A21\u578B\u5C06\u8C03\u7528 iterate_triage apply \u5E76\u56DE\u62A5\u5199\u5165\u6761\u6570\u3002")) : cmdCopied === "readback" ? React.createElement("span", { className: "iterate-triage-hint", role: "status" }, t("\u5DF2\u590D\u5236\u56DE\u8BFB\u6307\u4EE4\u2014\u2014\u7C98\u8D34\u56DE\u4F1A\u8BDD\u53EF\u5217\u51FA\u5F53\u524D\u5DF2\u5199\u5165\u7684 known_intentional\u3002")) : null
2856
3414
  ),
2857
3415
  // v3.0: Command buttons for native actions (§8)
2858
3416
  React.createElement(
2859
3417
  "div",
2860
3418
  { className: "iterate-triage-foot", style: { borderTop: "1px solid var(--dsw-alias-border-l1)" } },
2861
- React.createElement("span", { style: { fontSize: 11, fontWeight: 600, color: "var(--dsw-alias-label-secondary)" } }, "\u6307\u6325\u64CD\u4F5C"),
3419
+ React.createElement("span", { style: { fontSize: 11, fontWeight: 600, color: "var(--dsw-alias-label-secondary)" } }, t("\u6307\u6325\u64CD\u4F5C")),
2862
3420
  React.createElement(
2863
3421
  "span",
2864
3422
  { style: { display: "flex", gap: 6, flexWrap: "wrap" } },
@@ -2868,15 +3426,15 @@ function TriagePanel(props) {
2868
3426
  "data-copied": cmdCopied === "approve-arch" ? "" : void 0,
2869
3427
  "data-failed": cmdFailed === "approve-arch" ? "" : void 0,
2870
3428
  onClick: doApproveArchitecturalFix,
2871
- title: "\u6279\u51C6\u67B6\u6784\u4FEE\u590D\uFF08\u590D\u5236\u6307\u4EE4\u6587\u672C\uFF1Aiterate_fix force:true\uFF0C\u542B\u5168\u90E8\u5FC5\u586B\u53C2\u6570\uFF09"
2872
- }, cmdCopied === "approve-arch" ? "\u5DF2\u590D\u5236" : cmdFailed === "approve-arch" ? "\u590D\u5236\u5931\u8D25" : "\u6279\u51C6\u67B6\u6784\u4FEE\u590D"),
3429
+ title: t("\u6279\u51C6\u67B6\u6784\u4FEE\u590D\uFF08\u590D\u5236\u6307\u4EE4\u6587\u672C\uFF1Aiterate_fix force:true\uFF0C\u542B\u5168\u90E8\u5FC5\u586B\u53C2\u6570\uFF09")
3430
+ }, cmdCopied === "approve-arch" ? t("\u5DF2\u590D\u5236") : cmdFailed === "approve-arch" ? t("\u590D\u5236\u5931\u8D25") : t("\u6279\u51C6\u67B6\u6784\u4FEE\u590D")),
2873
3431
  React.createElement("button", {
2874
3432
  className: "iterate-cmd",
2875
3433
  "data-copied": cmdCopied === "new-round" ? "" : void 0,
2876
3434
  "data-failed": cmdFailed === "new-round" ? "" : void 0,
2877
3435
  onClick: doTriggerNewRound,
2878
- title: "\u89E6\u53D1\u65B0\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5"
2879
- }, cmdCopied === "new-round" ? "\u5DF2\u590D\u5236" : cmdFailed === "new-round" ? "\u590D\u5236\u5931\u8D25" : "\u89E6\u53D1\u65B0\u4E00\u8F6E"),
3436
+ title: t("\u89E6\u53D1\u65B0\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5")
3437
+ }, cmdCopied === "new-round" ? t("\u5DF2\u590D\u5236") : cmdFailed === "new-round" ? t("\u590D\u5236\u5931\u8D25") : t("\u89E6\u53D1\u65B0\u4E00\u8F6E")),
2880
3438
  React.createElement("button", {
2881
3439
  className: "iterate-cmd",
2882
3440
  "data-primary": "",
@@ -2884,16 +3442,16 @@ function TriagePanel(props) {
2884
3442
  "data-failed": cmdFailed === "assign" ? "" : void 0,
2885
3443
  onClick: doAssignFindings,
2886
3444
  disabled: (selectAll ? allIndices : indices).length === 0,
2887
- title: selectAll ? `\u6307\u6D3E\u5168\u90E8 ${allIndices.length} \u4E2A findings \u4FEE\u590D\uFF08\u9010\u4E2A iterate_fix\uFF09` : `\u6307\u6D3E\u5F53\u524D\u53EF\u89C1 ${indices.length} \u4E2A findings \u4FEE\u590D\uFF08\u9010\u4E2A iterate_fix\uFF09`
2888
- }, cmdCopied === "assign" ? "\u5DF2\u590D\u5236" : cmdFailed === "assign" ? "\u590D\u5236\u5931\u8D25" : "\u6307\u6D3E\u4FEE\u590D"),
3445
+ title: selectAll ? tf(`\u6307\u6D3E\u5168\u90E8 {a0} \u4E2A findings \u4FEE\u590D\uFF08\u9010\u4E2A iterate_fix\uFF09`, { a0: allIndices.length }) : tf(`\u6307\u6D3E\u5F53\u524D\u53EF\u89C1 {a0} \u4E2A findings \u4FEE\u590D\uFF08\u9010\u4E2A iterate_fix\uFF09`, { a0: indices.length })
3446
+ }, cmdCopied === "assign" ? t("\u5DF2\u590D\u5236") : cmdFailed === "assign" ? t("\u590D\u5236\u5931\u8D25") : t("\u6307\u6D3E\u4FEE\u590D")),
2889
3447
  React.createElement("button", {
2890
3448
  className: "iterate-cmd",
2891
3449
  "data-danger": "",
2892
3450
  "data-copied": cmdCopied === "rollback" ? "" : void 0,
2893
3451
  "data-failed": cmdFailed === "rollback" ? "" : void 0,
2894
3452
  onClick: doRollbackToCheckpoint,
2895
- title: "\u56DE\u6EDA\u5230\u4E0A\u4E00\u4E2A\u68C0\u67E5\u70B9"
2896
- }, cmdCopied === "rollback" ? "\u5DF2\u590D\u5236" : cmdFailed === "rollback" ? "\u590D\u5236\u5931\u8D25" : "\u56DE\u6EDA\u68C0\u67E5\u70B9")
3453
+ title: t("\u56DE\u6EDA\u5230\u4E0A\u4E00\u4E2A\u68C0\u67E5\u70B9")
3454
+ }, cmdCopied === "rollback" ? t("\u5DF2\u590D\u5236") : cmdFailed === "rollback" ? t("\u590D\u5236\u5931\u8D25") : t("\u56DE\u6EDA\u68C0\u67E5\u70B9"))
2897
3455
  )
2898
3456
  ),
2899
3457
  payload ? React.createElement("div", { className: "iterate-payload" }, payload) : null
@@ -2916,16 +3474,16 @@ function VerdictBanner(props) {
2916
3474
  React.createElement(
2917
3475
  "span",
2918
3476
  { className: "iterate-verdict-tag", "data-ok": ok ? "" : void 0, "data-warn": ok ? void 0 : "" },
2919
- ok ? "\u62A5\u544A\u5DF2\u6279\u51C6" : "\u62A5\u544A\u9700\u4FEE\u8BA2"
3477
+ ok ? t("\u62A5\u544A\u5DF2\u6279\u51C6") : t("\u62A5\u544A\u9700\u4FEE\u8BA2")
2920
3478
  ),
2921
3479
  React.createElement(
2922
3480
  "span",
2923
3481
  { className: "iterate-verdict-detail" },
2924
- item(verdict.totalFindings, "\u9879\u53D1\u73B0"),
2925
- item(verdict.totalRounds, "\u8F6E"),
2926
- item(verdict.checksRun, "\u9879\u5BA1\u67E5"),
2927
- ok ? phrase("\u62A5\u544A\u901A\u8FC7\u5168\u90E8\u4E00\u81F4\u6027\u68C0\u67E5") : item(verdict.reportIssues, "\u5904\u62A5\u544A\u7F3A\u9677"),
2928
- phrase(verdict.converged ? "\u5DF2\u6536\u655B" : "\u672A\u6536\u655B")
3482
+ item(verdict.totalFindings, t("\u9879\u53D1\u73B0")),
3483
+ item(verdict.totalRounds, t("\u8F6E")),
3484
+ item(verdict.checksRun, t("\u9879\u5BA1\u67E5")),
3485
+ ok ? phrase(t("\u62A5\u544A\u901A\u8FC7\u5168\u90E8\u4E00\u81F4\u6027\u68C0\u67E5")) : item(verdict.reportIssues, t("\u5904\u62A5\u544A\u7F3A\u9677")),
3486
+ phrase(verdict.converged ? t("\u5DF2\u6536\u655B") : t("\u672A\u6536\u655B"))
2929
3487
  )
2930
3488
  );
2931
3489
  }
@@ -2967,7 +3525,7 @@ function ProgressCapsule() {
2967
3525
  const listener = (payload) => {
2968
3526
  const converged = payload && payload.converged === true;
2969
3527
  const round = payload && typeof payload.round === "number" ? payload.round : "?";
2970
- setInfo({ text: converged ? `Round ${round} \u5B8C\u6210 \xB7 \u5DF2\u6536\u655B` : `Round ${round} \u5B8C\u6210`, ok: converged });
3528
+ setInfo({ text: converged ? tf(`Round {a0} \u5B8C\u6210 \xB7 \u5DF2\u6536\u655B`, { a0: round }) : tf(`Round {a0} \u5B8C\u6210`, { a0: round }), ok: converged });
2971
3529
  if (timer) clearTimeout(timer);
2972
3530
  timer = setTimeout(() => setInfo(null), converged ? 3600 : 2400);
2973
3531
  };
@@ -2987,7 +3545,10 @@ function ProgressCapsule() {
2987
3545
  "aria-live": "polite"
2988
3546
  }, info.text);
2989
3547
  }
2990
- function SettingsPanel(_props) {
3548
+ function SettingsPanel(props) {
3549
+ const lang = useLang();
3550
+ const cachedSession = useSessionSnapshot();
3551
+ const session = props && props.session ? props.session : cachedSession;
2991
3552
  const [enabled, setEnabled] = React.useState(themeEnabled);
2992
3553
  const [guideCopied, setGuideCopied] = React.useState(false);
2993
3554
  const [statusCopied, setStatusCopied] = React.useState(false);
@@ -3001,13 +3562,19 @@ function SettingsPanel(_props) {
3001
3562
  const [cfgCopied, setCfgCopied] = React.useState(false);
3002
3563
  const [cfgFailed, setCfgFailed] = React.useState(false);
3003
3564
  const [cfgText, setCfgText] = React.useState(null);
3565
+ const [cfgReadCopied, setCfgReadCopied] = React.useState(false);
3566
+ const [cfgReadText, setCfgReadText] = React.useState(null);
3004
3567
  const [pullCopied, setPullCopied] = React.useState(false);
3005
3568
  const [copyError, setCopyError] = React.useState(null);
3006
3569
  const guide = buildConfigEditGuide();
3007
3570
  const statusGuide = buildRuntimeStatusGuide();
3571
+ const cfgMeta = configFieldByKey(cfgField);
3572
+ const cfgRead = scanSessionForConfigRead(session);
3573
+ const cfgCurrent = cfgRead ? configValueByPath(cfgRead, cfgField) : null;
3574
+ const cfgFieldLabel = cfgMeta ? cfgMeta.label[getLang()] : cfgField;
3008
3575
  const cfgTimers = React.useRef([]);
3009
3576
  React.useEffect(() => () => {
3010
- for (const t of cfgTimers.current) clearTimeout(t);
3577
+ for (const t2 of cfgTimers.current) clearTimeout(t2);
3011
3578
  cfgTimers.current = [];
3012
3579
  }, []);
3013
3580
  const toggleTheme = () => {
@@ -3016,7 +3583,7 @@ function SettingsPanel(_props) {
3016
3583
  setThemeEnabled(next);
3017
3584
  };
3018
3585
  const flashCopied = (slot) => {
3019
- const setter = slot === "guide" ? setGuideCopied : slot === "status" ? setStatusCopied : setPullCopied;
3586
+ const setter = slot === "guide" ? setGuideCopied : slot === "status" ? setStatusCopied : slot === "pull" ? setPullCopied : setCfgReadCopied;
3020
3587
  setter(true);
3021
3588
  cfgTimers.current.push(window.setTimeout(() => setter(false), 1600));
3022
3589
  };
@@ -3036,6 +3603,17 @@ function SettingsPanel(_props) {
3036
3603
  if (!ok) return;
3037
3604
  });
3038
3605
  };
3606
+ const doCopyConfigRead = () => {
3607
+ const text = buildConfigReadInstruction();
3608
+ copyText(text).then((ok) => {
3609
+ setCopyError(ok ? null : "cfgRead");
3610
+ if (ok) {
3611
+ flashCopied("cfgRead");
3612
+ return;
3613
+ }
3614
+ setCfgReadText(text);
3615
+ });
3616
+ };
3039
3617
  const requestClear = () => {
3040
3618
  if (confirming) {
3041
3619
  const count = removeStorageByPrefix(TRIAGE_STORAGE_PREFIX);
@@ -3047,13 +3625,13 @@ function SettingsPanel(_props) {
3047
3625
  setConfirming(true);
3048
3626
  cfgTimers.current.push(window.setTimeout(() => setConfirming(false), 3e3));
3049
3627
  };
3050
- const clearButton = clearedInfo !== null ? React.createElement("button", { className: "iterate-btn", "data-copied": "", disabled: true }, `\u5DF2\u6E05\u9664 ${clearedInfo} \u6761`) : React.createElement("button", {
3628
+ const clearButton = clearedInfo !== null ? React.createElement("button", { className: "iterate-btn", "data-copied": "", disabled: true }, tf(`\u5DF2\u6E05\u9664 {a0} \u6761`, { a0: clearedInfo })) : React.createElement("button", {
3051
3629
  className: "iterate-btn",
3052
3630
  "data-danger": confirming ? void 0 : "",
3053
3631
  "data-confirm": confirming ? "" : void 0,
3054
3632
  onClick: requestClear,
3055
- title: confirming ? "\u518D\u6B21\u70B9\u51FB\u4EE5\u786E\u8BA4\u6E05\u9664\u5168\u90E8\u5224\u5B9A" : "\u6E05\u9664\u6240\u6709\u5206\u8BCA\u5224\u5B9A\u8BB0\u5F55\uFF08\u9700\u8981\u4E8C\u6B21\u786E\u8BA4\uFF09"
3056
- }, confirming ? "\u786E\u8BA4\u6E05\u9664\uFF1F" : "\u6E05\u9664\u5206\u8BCA");
3633
+ title: confirming ? t("\u518D\u6B21\u70B9\u51FB\u4EE5\u786E\u8BA4\u6E05\u9664\u5168\u90E8\u5224\u5B9A") : t("\u6E05\u9664\u6240\u6709\u5206\u8BCA\u5224\u5B9A\u8BB0\u5F55\uFF08\u9700\u8981\u4E8C\u6B21\u786E\u8BA4\uFF09")
3634
+ }, confirming ? t("\u786E\u8BA4\u6E05\u9664\uFF1F") : t("\u6E05\u9664\u5206\u8BCA"));
3057
3635
  const themeCard = React.createElement(
3058
3636
  "div",
3059
3637
  { key: "theme", className: "iterate-scard" },
@@ -3063,17 +3641,17 @@ function SettingsPanel(_props) {
3063
3641
  React.createElement(
3064
3642
  "div",
3065
3643
  {},
3066
- React.createElement("div", { className: "iterate-settings-title" }, "\u8FED\u4EE3\u4E3B\u9898"),
3067
- React.createElement("div", { className: "iterate-settings-desc" }, "\u542F\u7528\u6696\u7425\u73C0\u914D\u8272\u7684 iterate \u4E13\u5C5E\u76AE\u80A4\uFF0C\u8986\u76D6 dsh \u9ED8\u8BA4\u4E3B\u9898\u4EE4\u724C\u3002")
3644
+ React.createElement("div", { className: "iterate-settings-title" }, t("\u8FED\u4EE3\u4E3B\u9898")),
3645
+ React.createElement("div", { className: "iterate-settings-desc" }, t("\u542F\u7528\u6696\u7425\u73C0\u914D\u8272\u7684 iterate \u4E13\u5C5E\u76AE\u80A4\uFF0C\u8986\u76D6 dsh \u9ED8\u8BA4\u4E3B\u9898\u4EE4\u724C\u3002"))
3068
3646
  ),
3069
3647
  React.createElement(
3070
3648
  "div",
3071
3649
  { className: "iterate-scard-actions" },
3072
- React.createElement("span", { className: "iterate-chip", "data-ok": enabled ? "" : void 0 }, enabled ? "\u5DF2\u542F\u7528" : "\u5DF2\u5173\u95ED"),
3650
+ React.createElement("span", { className: "iterate-chip", "data-ok": enabled ? "" : void 0 }, enabled ? t("\u5DF2\u542F\u7528") : t("\u5DF2\u5173\u95ED")),
3073
3651
  React.createElement("button", {
3074
3652
  role: "switch",
3075
3653
  "aria-checked": enabled,
3076
- "aria-label": "\u5F00\u5173 iterate \u4E3B\u9898",
3654
+ "aria-label": t("\u5F00\u5173 iterate \u4E3B\u9898"),
3077
3655
  className: "iterate-switch",
3078
3656
  "data-on": enabled ? "" : void 0,
3079
3657
  onClick: toggleTheme
@@ -3081,6 +3659,37 @@ function SettingsPanel(_props) {
3081
3659
  )
3082
3660
  )
3083
3661
  );
3662
+ const langCard = React.createElement(
3663
+ "div",
3664
+ { key: "lang", className: "iterate-scard" },
3665
+ React.createElement(
3666
+ "div",
3667
+ { className: "iterate-scard-head" },
3668
+ React.createElement(
3669
+ "div",
3670
+ {},
3671
+ React.createElement("div", { className: "iterate-settings-title" }, t("\u754C\u9762\u8BED\u8A00")),
3672
+ React.createElement(
3673
+ "div",
3674
+ { className: "iterate-settings-desc" },
3675
+ t("\u9762\u677F\u6587\u6848\u7684\u663E\u793A\u8BED\u8A00\uFF1B\u590D\u5236\u7ED9\u6A21\u578B\u7684\u6307\u4EE4\u4FDD\u6301\u4E2D\u6587\uFF0C\u4E0E SKILL.md \u7684\u5DE5\u4F5C\u6D41\u5951\u7EA6\u4E00\u81F4\u3002")
3676
+ )
3677
+ ),
3678
+ React.createElement(
3679
+ "div",
3680
+ { className: "iterate-scard-actions" },
3681
+ React.createElement("span", { className: "iterate-chip", "data-ok": "" }, LANG_NAMES[lang]),
3682
+ React.createElement("button", {
3683
+ role: "switch",
3684
+ "aria-checked": lang === "en",
3685
+ "aria-label": t("\u5207\u6362\u754C\u9762\u8BED\u8A00"),
3686
+ className: "iterate-switch",
3687
+ "data-on": lang === "en" ? "" : void 0,
3688
+ onClick: () => setLang(lang === "en" ? "zh" : "en")
3689
+ }, React.createElement("span", { className: "iterate-switch-knob" }))
3690
+ )
3691
+ )
3692
+ );
3084
3693
  const dataCard = React.createElement(
3085
3694
  "div",
3086
3695
  { key: "data", className: "iterate-scard" },
@@ -3090,13 +3699,13 @@ function SettingsPanel(_props) {
3090
3699
  React.createElement(
3091
3700
  "div",
3092
3701
  {},
3093
- React.createElement("div", { className: "iterate-settings-title" }, "\u5206\u8BCA\u6301\u4E45\u5316"),
3094
- React.createElement("div", { className: "iterate-settings-desc" }, "y / n / a \u5224\u5B9A\u4FDD\u5B58\u5728\u672C\u5730\u6D4F\u89C8\u5668\uFF08localStorage\uFF09\uFF0C\u5237\u65B0\u4F1A\u8BDD\u540E\u4ECD\u4FDD\u7559\u3002")
3702
+ React.createElement("div", { className: "iterate-settings-title" }, t("\u5206\u8BCA\u6301\u4E45\u5316")),
3703
+ React.createElement("div", { className: "iterate-settings-desc" }, t("y / n / a \u5224\u5B9A\u4FDD\u5B58\u5728\u672C\u5730\u6D4F\u89C8\u5668\uFF08localStorage\uFF09\uFF0C\u5237\u65B0\u4F1A\u8BDD\u540E\u4ECD\u4FDD\u7559\u3002"))
3095
3704
  ),
3096
3705
  React.createElement(
3097
3706
  "div",
3098
3707
  { className: "iterate-scard-actions" },
3099
- React.createElement("span", { className: "iterate-chip" }, "\u672C\u5730\u4FDD\u5B58"),
3708
+ React.createElement("span", { className: "iterate-chip" }, t("\u672C\u5730\u4FDD\u5B58")),
3100
3709
  clearButton
3101
3710
  )
3102
3711
  )
@@ -3110,14 +3719,14 @@ function SettingsPanel(_props) {
3110
3719
  React.createElement(
3111
3720
  "div",
3112
3721
  {},
3113
- React.createElement("div", { className: "iterate-settings-title" }, "\u914D\u7F6E\u7BA1\u7406"),
3114
- React.createElement("div", { className: "iterate-settings-desc" }, "\u76EE\u6807 / \u7EF4\u5EA6 / \u6700\u5927\u8F6E\u6570 / validation.commands \u5199\u5728\u9879\u76EE\u7684 iterate.config.yaml\uFF0C\u590D\u5236\u6307\u5F15\u53EF\u8BA9\u6A21\u578B\u6309\u9700\u8C03\u6574\u3002")
3722
+ React.createElement("div", { className: "iterate-settings-title" }, t("\u914D\u7F6E\u7BA1\u7406")),
3723
+ React.createElement("div", { className: "iterate-settings-desc" }, t("\u76EE\u6807 / \u7EF4\u5EA6 / \u6700\u5927\u8F6E\u6570 / validation.commands \u5199\u5728\u9879\u76EE\u7684 iterate.config.yaml\uFF0C\u590D\u5236\u6307\u5F15\u53EF\u8BA9\u6A21\u578B\u6309\u9700\u8C03\u6574\u3002"))
3115
3724
  ),
3116
3725
  React.createElement(
3117
3726
  "div",
3118
3727
  { className: "iterate-scard-actions" },
3119
- React.createElement("button", { className: "iterate-btn", "data-primary": "", "data-copied": guideCopied ? "" : void 0, onClick: () => doCopy(guide, "guide") }, guideCopied ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u6307\u5F15"),
3120
- React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => setShowGuide((v) => !v) }, showGuide ? "\u6536\u8D77" : "\u5C55\u5F00")
3728
+ React.createElement("button", { className: "iterate-btn", "data-primary": "", "data-copied": guideCopied ? "" : void 0, onClick: () => doCopy(guide, "guide") }, guideCopied ? t("\u5DF2\u590D\u5236") : t("\u590D\u5236\u6307\u5F15")),
3729
+ React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => setShowGuide((v) => !v) }, showGuide ? t("\u6536\u8D77") : t("\u5C55\u5F00"))
3121
3730
  )
3122
3731
  ),
3123
3732
  // Gap #8: wire up the previously-dead `buildConfigFieldInstruction` — pick
@@ -3131,17 +3740,17 @@ function SettingsPanel(_props) {
3131
3740
  {
3132
3741
  className: "iterate-filter-select",
3133
3742
  value: cfgField,
3134
- "aria-label": "\u9009\u62E9\u8981\u4FEE\u6539\u7684\u914D\u7F6E\u5B57\u6BB5",
3743
+ "aria-label": t("\u9009\u62E9\u8981\u4FEE\u6539\u7684\u914D\u7F6E\u5B57\u6BB5"),
3135
3744
  onChange: (e) => setCfgField(e.target.value)
3136
3745
  },
3137
3746
  ...CONFIG_EDIT_FIELDS.map(
3138
- (f) => React.createElement("option", { key: f.key, value: f.key }, `${f.label}\uFF08${f.key}\uFF09`)
3747
+ (f) => React.createElement("option", { key: f.key, value: f.key }, getLang() === "en" ? `${f.label.en} (${f.key})` : `${f.label.zh}\uFF08${f.key}\uFF09`)
3139
3748
  )
3140
3749
  ),
3141
3750
  React.createElement(
3142
3751
  "span",
3143
3752
  { className: "iterate-settings-desc" },
3144
- configFieldByKey(cfgField)?.hint ?? ""
3753
+ cfgMeta?.hint[getLang()] ?? ""
3145
3754
  ),
3146
3755
  React.createElement("button", {
3147
3756
  className: "iterate-btn",
@@ -3149,18 +3758,48 @@ function SettingsPanel(_props) {
3149
3758
  "data-copied": cfgCopied ? "" : void 0,
3150
3759
  "data-failed": cfgFailed ? "" : void 0,
3151
3760
  onClick: doBuildConfigInstruction,
3152
- title: "\u751F\u6210\u5E76\u590D\u5236\u8BE5\u5B57\u6BB5\u7684 iterate_config \u5199\u5165\u6307\u4EE4\uFF08\u5199\u5165\u524D\u81EA\u52A8\u5907\u4EFD\u3001\u5931\u8D25\u81EA\u52A8\u56DE\u6EDA\uFF09"
3153
- }, cfgCopied ? "\u5DF2\u590D\u5236" : cfgFailed ? "\u590D\u5236\u5931\u8D25" : "\u751F\u6210\u5199\u5165\u6307\u4EE4"),
3154
- cfgFailed ? React.createElement("span", { className: "iterate-settings-desc" }, "\u526A\u8D34\u677F\u88AB\u62D2\u7EDD\u2014\u2014\u6307\u4EE4\u5DF2\u5C55\u793A\u5728\u4E0B\u65B9\uFF0C\u8BF7\u624B\u52A8\u590D\u5236\u3002") : null
3761
+ title: t("\u751F\u6210\u5E76\u590D\u5236\u8BE5\u5B57\u6BB5\u7684 iterate_config \u5199\u5165\u6307\u4EE4\uFF08\u5199\u5165\u524D\u81EA\u52A8\u5907\u4EFD\u3001\u5931\u8D25\u81EA\u52A8\u56DE\u6EDA\uFF09")
3762
+ }, cfgCopied ? t("\u5DF2\u590D\u5236") : cfgFailed ? t("\u590D\u5236\u5931\u8D25") : t("\u751F\u6210\u5199\u5165\u6307\u4EE4")),
3763
+ cfgFailed ? React.createElement("span", { className: "iterate-settings-desc" }, t("\u526A\u8D34\u677F\u88AB\u62D2\u7EDD\u2014\u2014\u6307\u4EE4\u5DF2\u5C55\u793A\u5728\u4E0B\u65B9\uFF0C\u8BF7\u624B\u52A8\u590D\u5236\u3002")) : null
3155
3764
  ),
3765
+ // Gap #1 (3.7.0): close the write -> read-back loop. Three honest states:
3766
+ // nothing echoed yet, the value the model read back, or a read-back that
3767
+ // never contained this field (defaults are in play — say so, don't blank).
3768
+ React.createElement(
3769
+ "div",
3770
+ { className: "iterate-guide-bar", style: { marginTop: 8, flexWrap: "wrap" } },
3771
+ React.createElement(
3772
+ "span",
3773
+ { className: "iterate-settings-desc" },
3774
+ !cfgRead ? t("\u5C1A\u672A\u56DE\u8BFB\u5F53\u524D\u914D\u7F6E\u2014\u2014\u70B9\u300C\u56DE\u8BFB\u5F53\u524D\u914D\u7F6E\u300D\u590D\u5236\u6307\u4EE4\u53D1\u7ED9\u6A21\u578B\uFF0C\u56DE\u663E\u540E\u5373\u53EF\u5728\u6B64\u770B\u5230\u5F53\u524D\u503C\u3002") : cfgCurrent && cfgCurrent.resolved ? tf("\u5F53\u524D\u503C {a0}\uFF1A{a1}", { a0: cfgFieldLabel, a1: formatConfigValue(cfgCurrent.value) }) : tf("\u56DE\u8BFB\u7ED3\u679C\u4E2D\u6CA1\u6709 {a0}\uFF08\u8BE5\u5B57\u6BB5\u53EF\u80FD\u5C1A\u672A\u5199\u5165\u914D\u7F6E\u6587\u4EF6\uFF09\u3002", { a0: cfgFieldLabel })
3775
+ ),
3776
+ React.createElement("button", {
3777
+ className: "iterate-btn",
3778
+ "data-primary": "",
3779
+ "data-copied": cfgReadCopied ? "" : void 0,
3780
+ onClick: doCopyConfigRead,
3781
+ title: t('\u590D\u5236\u56DE\u8BFB\u6307\u4EE4\uFF1A\u8BA9\u6A21\u578B\u8C03\u7528 iterate_config(operation:"read") \u5E76\u56DE\u663E\u7ED3\u679C')
3782
+ }, cfgReadCopied ? t("\u5DF2\u590D\u5236") : t("\u56DE\u8BFB\u5F53\u524D\u914D\u7F6E"))
3783
+ ),
3784
+ cfgReadText ? React.createElement(
3785
+ "div",
3786
+ { className: "iterate-guide" },
3787
+ React.createElement(
3788
+ "div",
3789
+ { className: "iterate-guide-bar" },
3790
+ React.createElement("span", {}, t("\u56DE\u8BFB\u6307\u4EE4\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\uFF09")),
3791
+ React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => doCopy(cfgReadText, "cfgRead") }, t("\u590D\u5236"))
3792
+ ),
3793
+ React.createElement("div", { className: "iterate-guide-body" }, cfgReadText)
3794
+ ) : null,
3156
3795
  cfgText ? React.createElement(
3157
3796
  "div",
3158
3797
  { className: "iterate-guide" },
3159
3798
  React.createElement(
3160
3799
  "div",
3161
3800
  { className: "iterate-guide-bar" },
3162
- React.createElement("span", {}, "\u5199\u5165\u6307\u4EE4\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\uFF09"),
3163
- React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => doCopy(cfgText, "guide") }, "\u590D\u5236")
3801
+ React.createElement("span", {}, t("\u5199\u5165\u6307\u4EE4\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\uFF09")),
3802
+ React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => doCopy(cfgText, "guide") }, t("\u590D\u5236"))
3164
3803
  ),
3165
3804
  React.createElement("div", { className: "iterate-guide-body" }, cfgText)
3166
3805
  ) : null,
@@ -3170,8 +3809,9 @@ function SettingsPanel(_props) {
3170
3809
  React.createElement(
3171
3810
  "div",
3172
3811
  { className: "iterate-guide-bar" },
3173
- React.createElement("span", {}, "iterate.config.yaml \u53EF\u7F16\u8F91\u5B57\u6BB5"),
3174
- React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => doCopy(guide, "guide") }, "\u590D\u5236")
3812
+ React.createElement("span", {}, t("iterate.config.yaml \u53EF\u7F16\u8F91\u5B57\u6BB5")),
3813
+ React.createElement("span", { className: "iterate-chip" }, t("\u4E2D\u6587\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\u7684\u5951\u7EA6\u6587\u6848\uFF09")),
3814
+ React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => doCopy(guide, "guide") }, t("\u590D\u5236"))
3175
3815
  ),
3176
3816
  React.createElement("div", { className: "iterate-guide-body" }, guide)
3177
3817
  ) : null
@@ -3185,13 +3825,13 @@ function SettingsPanel(_props) {
3185
3825
  React.createElement(
3186
3826
  "div",
3187
3827
  {},
3188
- React.createElement("div", { className: "iterate-settings-title" }, "\u72B6\u6001\u6982\u89C8"),
3189
- React.createElement("div", { className: "iterate-settings-desc" }, "\u67E5\u770B\u8FD0\u884C\u65F6\u4EA7\u7269\u5E03\u5C40\u4E0E\u6E05\u7406\u6307\u5F15\u3002iterate_status / iterate_history / iterate_prune \u5DE5\u5177\u7528\u4E8E\u67E5\u770B\u548C\u7BA1\u7406\u3002")
3828
+ React.createElement("div", { className: "iterate-settings-title" }, t("\u72B6\u6001\u6982\u89C8")),
3829
+ React.createElement("div", { className: "iterate-settings-desc" }, t("\u67E5\u770B\u8FD0\u884C\u65F6\u4EA7\u7269\u5E03\u5C40\u4E0E\u6E05\u7406\u6307\u5F15\u3002iterate_status / iterate_history / iterate_prune \u5DE5\u5177\u7528\u4E8E\u67E5\u770B\u548C\u7BA1\u7406\u3002"))
3190
3830
  ),
3191
3831
  React.createElement(
3192
3832
  "div",
3193
3833
  { className: "iterate-scard-actions" },
3194
- React.createElement("button", { className: "iterate-btn", "data-primary": "", "data-copied": statusCopied ? "" : void 0, onClick: () => doCopy(statusGuide, "status") }, statusCopied ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u6307\u5F15"),
3834
+ React.createElement("button", { className: "iterate-btn", "data-primary": "", "data-copied": statusCopied ? "" : void 0, onClick: () => doCopy(statusGuide, "status") }, statusCopied ? t("\u5DF2\u590D\u5236") : t("\u590D\u5236\u6307\u5F15")),
3195
3835
  // Gap #4: RUNTIME_ARTIFACTS stops being documentation-only — this
3196
3836
  // copies the instruction that makes the model echo those artifacts
3197
3837
  // into the session so the observatory panels can read them.
@@ -3199,8 +3839,8 @@ function SettingsPanel(_props) {
3199
3839
  className: "iterate-btn",
3200
3840
  "data-copied": pullCopied ? "" : void 0,
3201
3841
  onClick: () => doCopy(buildDiskSnapshotInstruction(), "pull")
3202
- }, pullCopied ? "\u5DF2\u590D\u5236" : "\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167"),
3203
- React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => setShowStatus((v) => !v) }, showStatus ? "\u6536\u8D77" : "\u5C55\u5F00")
3842
+ }, pullCopied ? t("\u5DF2\u590D\u5236") : t("\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167")),
3843
+ React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => setShowStatus((v) => !v) }, showStatus ? t("\u6536\u8D77") : t("\u5C55\u5F00"))
3204
3844
  )
3205
3845
  ),
3206
3846
  showStatus ? React.createElement(
@@ -3209,8 +3849,9 @@ function SettingsPanel(_props) {
3209
3849
  React.createElement(
3210
3850
  "div",
3211
3851
  { className: "iterate-guide-bar" },
3212
- React.createElement("span", {}, "\u8FD0\u884C\u65F6\u5E03\u5C40\u4E0E\u6E05\u7406"),
3213
- React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => doCopy(statusGuide, "status") }, "\u590D\u5236")
3852
+ React.createElement("span", {}, t("\u8FD0\u884C\u65F6\u5E03\u5C40\u4E0E\u6E05\u7406")),
3853
+ React.createElement("span", { className: "iterate-chip" }, t("\u4E2D\u6587\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\u7684\u5951\u7EA6\u6587\u6848\uFF09")),
3854
+ React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => doCopy(statusGuide, "status") }, t("\u590D\u5236"))
3214
3855
  ),
3215
3856
  React.createElement("div", { className: "iterate-guide-body" }, statusGuide)
3216
3857
  ) : null
@@ -3225,20 +3866,20 @@ function SettingsPanel(_props) {
3225
3866
  "div",
3226
3867
  {},
3227
3868
  React.createElement("div", { className: "iterate-settings-title" }, "iterate"),
3228
- React.createElement("div", { className: "iterate-settings-desc" }, "\u4E3A\u6BCF\u6B21\u4EE3\u7801\u8BC4\u5BA1\u751F\u6210 Review \u62A5\u544A\u4E0E\u4EA4\u4E92\u5F0F\u5206\u8BCA\u89C6\u56FE\uFF0C\u4E13\u6CE8 AI \u81EA\u6211\u5BA1\u67E5\u4E0E\u4FEE\u6B63\u3002")
3869
+ React.createElement("div", { className: "iterate-settings-desc" }, t("\u4E3A\u6BCF\u6B21\u4EE3\u7801\u8BC4\u5BA1\u751F\u6210 Review \u62A5\u544A\u4E0E\u4EA4\u4E92\u5F0F\u5206\u8BCA\u89C6\u56FE\uFF0C\u4E13\u6CE8 AI \u81EA\u6211\u5BA1\u67E5\u4E0E\u4FEE\u6B63\u3002"))
3229
3870
  ),
3230
3871
  React.createElement(
3231
3872
  "span",
3232
3873
  { className: "iterate-pill" },
3233
3874
  React.createElement("span", { className: "iterate-pill-dot" }),
3234
- "\u5C31\u7EEA"
3875
+ t("\u5C31\u7EEA")
3235
3876
  )
3236
3877
  )
3237
3878
  );
3238
3879
  return React.createElement(
3239
3880
  "div",
3240
3881
  { "data-iterate-root": "", "data-iterate": "settings", className: "iterate-settings" },
3241
- React.createElement("div", { className: "iterate-settings-title", style: { fontSize: 15, fontWeight: 700 } }, "iterate \u8BBE\u7F6E"),
3882
+ React.createElement("div", { className: "iterate-settings-title", style: { fontSize: 15, fontWeight: 700 } }, t("iterate \u8BBE\u7F6E")),
3242
3883
  banner,
3243
3884
  copyError ? React.createElement(
3244
3885
  "div",
@@ -3246,9 +3887,10 @@ function SettingsPanel(_props) {
3246
3887
  React.createElement(
3247
3888
  "div",
3248
3889
  { className: "iterate-settings-desc" },
3249
- "\u526A\u8D34\u677F\u5199\u5165\u88AB\u62D2\u7EDD\uFF0C\u590D\u5236\u672A\u751F\u6548\u2014\u2014\u8BF7\u5C55\u5F00\u5BF9\u5E94\u533A\u5757\u624B\u52A8\u9009\u4E2D\u6587\u672C\u590D\u5236\u3002"
3890
+ t("\u526A\u8D34\u677F\u5199\u5165\u88AB\u62D2\u7EDD\uFF0C\u590D\u5236\u672A\u751F\u6548\u2014\u2014\u8BF7\u5C55\u5F00\u5BF9\u5E94\u533A\u5757\u624B\u52A8\u9009\u4E2D\u6587\u672C\u590D\u5236\u3002")
3250
3891
  )
3251
3892
  ) : null,
3893
+ langCard,
3252
3894
  themeCard,
3253
3895
  dataCard,
3254
3896
  guideCard,
@@ -3256,18 +3898,18 @@ function SettingsPanel(_props) {
3256
3898
  );
3257
3899
  }
3258
3900
  var OBS_TABS = [
3259
- { key: "live", label: "\u5B9E\u65F6\u6D3B\u52A8\u6D41", hotkey: "" },
3260
- { key: "f1", label: "\u5BA1\u67E5\u7EBF\u7A0B", hotkey: "1" },
3261
- { key: "f2", label: "\u6536\u655B\u8D8B\u52BF", hotkey: "2" },
3262
- { key: "f3", label: "\u53D1\u73B0\u5B9A\u4F4D", hotkey: "3" },
3263
- { key: "f4", label: "\u4FEE\u590D\u4E0E\u56DE\u6EDA", hotkey: "4" },
3264
- { key: "f5", label: "\u65AD\u70B9\u6062\u590D", hotkey: "5" },
3265
- { key: "f6", label: "\u8FD0\u884C\u63A7\u5236\u53F0", hotkey: "6" },
3266
- { key: "f7", label: "\u51B3\u7B56\u65F6\u95F4\u7EBF", hotkey: "7" },
3901
+ { key: "live", label: { zh: "\u5B9E\u65F6\u6D3B\u52A8\u6D41", en: "Live activity" }, hotkey: "" },
3902
+ { key: "f1", label: { zh: "\u5BA1\u67E5\u7EBF\u7A0B", en: "Review threads" }, hotkey: "1" },
3903
+ { key: "f2", label: { zh: "\u6536\u655B\u8D8B\u52BF", en: "Convergence trend" }, hotkey: "2" },
3904
+ { key: "f3", label: { zh: "\u53D1\u73B0\u5B9A\u4F4D", en: "Finding locations" }, hotkey: "3" },
3905
+ { key: "f4", label: { zh: "\u4FEE\u590D\u4E0E\u56DE\u6EDA", en: "Fixes & rollback" }, hotkey: "4" },
3906
+ { key: "f5", label: { zh: "\u65AD\u70B9\u6062\u590D", en: "Checkpoint resume" }, hotkey: "5" },
3907
+ { key: "f6", label: { zh: "\u8FD0\u884C\u63A7\u5236\u53F0", en: "Run console" }, hotkey: "6" },
3908
+ { key: "f7", label: { zh: "\u51B3\u7B56\u65F6\u95F4\u7EBF", en: "Decision timeline" }, hotkey: "7" },
3267
3909
  // v3.0: Quality Command Center tabs
3268
- { key: "f8", label: "\u8D28\u91CF\u95E8\u7981", hotkey: "8" },
3269
- { key: "f9", label: "\u7ECF\u9A8C\u94F6\u884C", hotkey: "9" },
3270
- { key: "f10", label: "\u9632\u5FA1\u4E8B\u4EF6", hotkey: "0" }
3910
+ { key: "f8", label: { zh: "\u8D28\u91CF\u95E8\u7981", en: "Quality gate" }, hotkey: "8" },
3911
+ { key: "f9", label: { zh: "\u7ECF\u9A8C\u94F6\u884C", en: "Experience bank" }, hotkey: "9" },
3912
+ { key: "f10", label: { zh: "\u9632\u5FA1\u4E8B\u4EF6", en: "Defense events" }, hotkey: "0" }
3271
3913
  ];
3272
3914
  var OBS_HOTKEY_TABS = {
3273
3915
  1: "f1",
@@ -3282,23 +3924,26 @@ var OBS_HOTKEY_TABS = {
3282
3924
  0: "f10",
3283
3925
  "-": "live"
3284
3926
  };
3285
- function obsTabLabel(t) {
3286
- if (t.key === "live") return t.label;
3287
- return `${t.key.toUpperCase()} ${t.label}`;
3927
+ function obsTabLabel(t2) {
3928
+ const label = t2.label[getLang()];
3929
+ if (t2.key === "live") return label;
3930
+ return `${t2.key.toUpperCase()} ${label}`;
3931
+ }
3932
+ function obsHotkeyHint() {
3933
+ return getLang() === "en" ? `${String(OBS_TABS.length)} tabs \xB7 keys 1-9/0 switch Fn, - returns to the live feed` : tf(`{a0} \u9875 \xB7 \u6309 1-9/0 \u5207 Fn\u3001- \u56DE\u5B9E\u65F6\u6D41`, { a0: String(OBS_TABS.length) });
3288
3934
  }
3289
- var OBS_HOTKEY_HINT = `${String(OBS_TABS.length)} \u9875 \xB7 \u6309 1-9/0 \u5207 Fn\u3001- \u56DE\u5B9E\u65F6\u6D41`;
3290
3935
  var OBS_LIVE_META = {
3291
- read: { label: "\u9605\u8BFB", color: "var(--dsw-alias-brand-primary)" },
3292
- fix: { label: "\u4FEE\u590D", color: "var(--dsw-alias-state-success-primary)" },
3293
- rollback: { label: "\u56DE\u6EDA", color: "var(--dsw-alias-state-error-primary)" },
3294
- diff: { label: "\u5DEE\u5F02", color: "var(--dsw-alias-state-warn-primary)" },
3295
- review: { label: "\u5BA1\u67E5", color: "var(--dsw-alias-brand-primary)" },
3296
- triage: { label: "\u5206\u6D41", color: "var(--dsw-alias-state-warn-primary)" },
3297
- checkpoint: { label: "\u65AD\u70B9", color: "var(--dsw-alias-brand-primary)" },
3298
- validate: { label: "\u6821\u9A8C", color: "var(--dsw-alias-state-success-primary)" },
3299
- log: { label: "\u8BB0\u5F55", color: "var(--dsw-alias-label-secondary)" },
3300
- prune: { label: "\u6E05\u7406", color: "var(--dsw-alias-state-warn-primary)" },
3301
- info: { label: "\u4FE1\u606F", color: "var(--dsw-alias-label-secondary)" }
3936
+ read: { label: { zh: "\u9605\u8BFB", en: "Read" }, color: "var(--dsw-alias-brand-primary)" },
3937
+ fix: { label: { zh: "\u4FEE\u590D", en: "Fix" }, color: "var(--dsw-alias-state-success-primary)" },
3938
+ rollback: { label: { zh: "\u56DE\u6EDA", en: "Rollback" }, color: "var(--dsw-alias-state-error-primary)" },
3939
+ diff: { label: { zh: "\u5DEE\u5F02", en: "Diff" }, color: "var(--dsw-alias-state-warn-primary)" },
3940
+ review: { label: { zh: "\u5BA1\u67E5", en: "Review" }, color: "var(--dsw-alias-brand-primary)" },
3941
+ triage: { label: { zh: "\u5206\u6D41", en: "Triage" }, color: "var(--dsw-alias-state-warn-primary)" },
3942
+ checkpoint: { label: { zh: "\u65AD\u70B9", en: "Checkpoint" }, color: "var(--dsw-alias-brand-primary)" },
3943
+ validate: { label: { zh: "\u6821\u9A8C", en: "Validate" }, color: "var(--dsw-alias-state-success-primary)" },
3944
+ log: { label: { zh: "\u8BB0\u5F55", en: "Log" }, color: "var(--dsw-alias-label-secondary)" },
3945
+ prune: { label: { zh: "\u6E05\u7406", en: "Prune" }, color: "var(--dsw-alias-state-warn-primary)" },
3946
+ info: { label: { zh: "\u4FE1\u606F", en: "Info" }, color: "var(--dsw-alias-label-secondary)" }
3302
3947
  };
3303
3948
  var OBS_LIVE_FALLBACK_COLOR = "var(--dsw-alias-label-secondary)";
3304
3949
  function formatObsLiveTime(ts) {
@@ -3340,6 +3985,9 @@ function buildObsFixInstruction(f) {
3340
3985
  }
3341
3986
  function ObservatoryPanel(props) {
3342
3987
  const session = props && props.session ? props.session : null;
3988
+ React.useEffect(() => {
3989
+ noteSession(session);
3990
+ }, [session]);
3343
3991
  const manifest = latestTranscript(session);
3344
3992
  const qualityGate = latestQualityGate(session);
3345
3993
  const experienceBank = latestExperienceBank(session);
@@ -3369,9 +4017,9 @@ function ObservatoryPanel(props) {
3369
4017
  if (!doc || !open) return;
3370
4018
  const onKeyDown = (ev) => {
3371
4019
  if (ev.metaKey || ev.ctrlKey || ev.altKey || ev.shiftKey) return;
3372
- const t = ev.target;
3373
- if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT")) return;
3374
- if (t && typeof t.isContentEditable === "boolean" && t.isContentEditable) return;
4020
+ const t2 = ev.target;
4021
+ if (t2 && (t2.tagName === "INPUT" || t2.tagName === "TEXTAREA" || t2.tagName === "SELECT")) return;
4022
+ if (t2 && typeof t2.isContentEditable === "boolean" && t2.isContentEditable) return;
3375
4023
  const rootEl = doc.querySelector('[data-iterate="obs"]');
3376
4024
  if (!rootEl) return;
3377
4025
  const activeEl = doc.activeElement;
@@ -3399,7 +4047,7 @@ function ObservatoryPanel(props) {
3399
4047
  });
3400
4048
  };
3401
4049
  const pullInstruction = buildDiskSnapshotInstruction();
3402
- const pullTitle = `\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u8BA9\u6A21\u578B\u9010\u4E2A\u8C03\u7528 ${DISK_SNAPSHOT_SOURCES.map((s) => s.tool).join(" / ")} \u5E76\u56DE\u663E\u5B8C\u6574\u7ED3\u679C`;
4050
+ const pullTitle = tf(`\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u8BA9\u6A21\u578B\u9010\u4E2A\u8C03\u7528 {a0} \u5E76\u56DE\u663E\u5B8C\u6574\u7ED3\u679C`, { a0: DISK_SNAPSHOT_SOURCES.map((s) => s.tool).join(" / ") });
3403
4051
  const toggleThread = (key) => {
3404
4052
  setThreadMode("auto");
3405
4053
  setExpandedThreads((prev) => {
@@ -3451,8 +4099,8 @@ function ObservatoryPanel(props) {
3451
4099
  React.createElement(
3452
4100
  "div",
3453
4101
  { className: "iterate-obs-head", "data-closed": "" },
3454
- React.createElement("span", { className: "iterate-obs-title" }, "iterate \u89C2\u6D4B\u53F0"),
3455
- React.createElement("span", { className: "iterate-obs-head-meta" }, `\u6682\u65E0\u8FD0\u884C\u65F6\u89C2\u6D4B\u6570\u636E \xB7 ${OBS_HOTKEY_HINT}`),
4102
+ React.createElement("span", { className: "iterate-obs-title" }, t("iterate \u89C2\u6D4B\u53F0")),
4103
+ React.createElement("span", { className: "iterate-obs-head-meta" }, tf(`\u6682\u65E0\u8FD0\u884C\u65F6\u89C2\u6D4B\u6570\u636E \xB7 {a0}`, { a0: obsHotkeyHint() })),
3456
4104
  React.createElement("button", {
3457
4105
  className: "iterate-btn",
3458
4106
  "data-copied": copiedKey === "pull-snapshot" ? "" : void 0,
@@ -3460,8 +4108,8 @@ function ObservatoryPanel(props) {
3460
4108
  e.stopPropagation();
3461
4109
  copyInstruction("pull-snapshot", pullInstruction);
3462
4110
  },
3463
- title: "\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u78C1\u76D8\u4E0A\u53EF\u80FD\u5DF2\u6709\u5386\u53F2\u4EA7\u7269\uFF0C\u56DE\u663E\u8FDB\u4F1A\u8BDD\u540E\u89C2\u6D4B\u53F0\u5373\u53EF\u586B\u5145"
3464
- }, copiedKey === "pull-snapshot" ? "\u5DF2\u590D\u5236" : "\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167")
4111
+ title: t("\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u78C1\u76D8\u4E0A\u53EF\u80FD\u5DF2\u6709\u5386\u53F2\u4EA7\u7269\uFF0C\u56DE\u663E\u8FDB\u4F1A\u8BDD\u540E\u89C2\u6D4B\u53F0\u5373\u53EF\u586B\u5145")
4112
+ }, copiedKey === "pull-snapshot" ? t("\u5DF2\u590D\u5236") : t("\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167"))
3465
4113
  )
3466
4114
  );
3467
4115
  }
@@ -3483,7 +4131,7 @@ function ObservatoryPanel(props) {
3483
4131
  const renderThreads = () => {
3484
4132
  const rounds = manifest.rounds || [];
3485
4133
  if (rounds.length === 0) {
3486
- return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u5BA1\u67E5\u7EBF\u7A0B");
4134
+ return React.createElement("div", { className: "iterate-obs-empty" }, t("\u6682\u65E0\u5BA1\u67E5\u7EBF\u7A0B"));
3487
4135
  }
3488
4136
  return React.createElement(
3489
4137
  "div",
@@ -3491,34 +4139,34 @@ function ObservatoryPanel(props) {
3491
4139
  React.createElement(
3492
4140
  "div",
3493
4141
  { className: "iterate-obs-bar", style: { marginBottom: 8, flexWrap: "wrap" } },
3494
- React.createElement("b", {}, "\u5BA1\u67E5\u7EBF\u7A0B"),
3495
- React.createElement("button", { className: "iterate-btn", onClick: () => setAllThreads("all"), title: "\u5C55\u5F00\u5168\u90E8\u7EBF\u7A0B" }, "\u5168\u90E8\u5C55\u5F00"),
3496
- React.createElement("button", { className: "iterate-btn", onClick: () => setAllThreads("none"), title: "\u6536\u8D77\u5168\u90E8\u7EBF\u7A0B" }, "\u5168\u90E8\u6536\u8D77"),
4142
+ React.createElement("b", {}, t("\u5BA1\u67E5\u7EBF\u7A0B")),
4143
+ React.createElement("button", { className: "iterate-btn", onClick: () => setAllThreads("all"), title: t("\u5C55\u5F00\u5168\u90E8\u7EBF\u7A0B") }, t("\u5168\u90E8\u5C55\u5F00")),
4144
+ React.createElement("button", { className: "iterate-btn", onClick: () => setAllThreads("none"), title: t("\u6536\u8D77\u5168\u90E8\u7EBF\u7A0B") }, t("\u5168\u90E8\u6536\u8D77")),
3497
4145
  React.createElement(
3498
4146
  "span",
3499
4147
  { className: "iterate-filter-count" },
3500
- `${threadMode === "all" ? "\u5168\u90E8\u5C55\u5F00" : threadMode === "none" ? "\u5168\u90E8\u6536\u8D77" : "\u81EA\u7531\u5207\u6362"} \xB7 ${rounds.length} \u8F6E`
4148
+ tf(`{a0} \xB7 {a1} \u8F6E`, { a0: threadMode === "all" ? t("\u5168\u90E8\u5C55\u5F00") : threadMode === "none" ? t("\u5168\u90E8\u6536\u8D77") : t("\u81EA\u7531\u5207\u6362"), a1: rounds.length })
3501
4149
  )
3502
4150
  ),
3503
4151
  ...rounds.map((r, ri) => {
3504
4152
  const threads = r.threads || [];
3505
- const fCount = threads.reduce((sum, t) => sum + (t.findings ? t.findings.length : 0), 0);
3506
- const threadBlocks = threads.length === 0 ? [React.createElement("div", { key: "none", className: "iterate-obs-empty" }, "\u672C\u8F6E\u65E0\u7EBF\u7A0B")] : threads.map((t, ti) => {
4153
+ const fCount = threads.reduce((sum, t2) => sum + (t2.findings ? t2.findings.length : 0), 0);
4154
+ const threadBlocks = threads.length === 0 ? [React.createElement("div", { key: "none", className: "iterate-obs-empty" }, t("\u672C\u8F6E\u65E0\u7EBF\u7A0B"))] : threads.map((th, ti) => {
3507
4155
  const key = `${ri}-${ti}`;
3508
4156
  const expanded = isThreadExpanded(key);
3509
- const dim = t.dimension || "\u672A\u547D\u540D\u7EF4\u5EA6";
3510
- const tFindings = t.findings || [];
3511
- const files = t.readFiles || [];
3512
- const msgs = t.messages || [];
4157
+ const dim = th.dimension || t("\u672A\u547D\u540D\u7EF4\u5EA6");
4158
+ const tFindings = th.findings || [];
4159
+ const files = th.readFiles || [];
4160
+ const msgs = th.messages || [];
3513
4161
  return React.createElement(
3514
4162
  "div",
3515
4163
  { key, className: "iterate-obs-block" },
3516
4164
  React.createElement(
3517
4165
  "div",
3518
- { className: "iterate-obs-block-head", "data-click": "", onClick: () => toggleThread(key), title: expanded ? "\u6536\u8D77\u7EBF\u7A0B" : "\u5C55\u5F00\u7EBF\u7A0B" },
4166
+ { className: "iterate-obs-block-head", "data-click": "", onClick: () => toggleThread(key), title: expanded ? t("\u6536\u8D77\u7EBF\u7A0B") : t("\u5C55\u5F00\u7EBF\u7A0B") },
3519
4167
  React.createElement("span", {}, expanded ? "\u2212" : "+"),
3520
4168
  React.createElement("b", {}, dim),
3521
- React.createElement("span", { className: "iterate-obs-chip" }, `attempt ${typeof t.attempt === "number" ? t.attempt : "?"}`),
4169
+ React.createElement("span", { className: "iterate-obs-chip" }, `attempt ${typeof th.attempt === "number" ? th.attempt : "?"}`),
3522
4170
  React.createElement("span", { className: "iterate-obs-chip" }, `${tFindings.length} findings`)
3523
4171
  ),
3524
4172
  expanded ? React.createElement(
@@ -3527,15 +4175,15 @@ function ObservatoryPanel(props) {
3527
4175
  React.createElement(
3528
4176
  "div",
3529
4177
  { className: "iterate-obs-bar" },
3530
- React.createElement("b", {}, "\u53D9\u8FF0"),
3531
- React.createElement("span", { className: "iterate-obs-head-meta" }, `${msgs.length} \u6761`)
4178
+ React.createElement("b", {}, t("\u53D9\u8FF0")),
4179
+ React.createElement("span", { className: "iterate-obs-head-meta" }, tf(`{a0} \u6761`, { a0: msgs.length }))
3532
4180
  ),
3533
- ...msgs.length === 0 ? [React.createElement("div", { key: "msg-none", className: "iterate-obs-empty" }, "\u65E0\u7EBF\u7D22\u53D9\u8FF0")] : msgs.map((m, mi) => React.createElement("div", { key: mi, className: "iterate-obs-msg" }, String(m))),
4181
+ ...msgs.length === 0 ? [React.createElement("div", { key: "msg-none", className: "iterate-obs-empty" }, t("\u65E0\u7EBF\u7D22\u53D9\u8FF0"))] : msgs.map((m, mi) => React.createElement("div", { key: mi, className: "iterate-obs-msg" }, String(m))),
3534
4182
  ...files.length > 0 ? [
3535
4183
  React.createElement(
3536
4184
  "div",
3537
4185
  { key: "files-h", className: "iterate-obs-bar", style: { marginTop: 6 } },
3538
- React.createElement("b", {}, "\u8BFB\u53D6\u6587\u4EF6")
4186
+ React.createElement("b", {}, t("\u8BFB\u53D6\u6587\u4EF6"))
3539
4187
  ),
3540
4188
  ...files.map(
3541
4189
  (f, fi) => React.createElement("div", { key: `f${fi}`, className: "iterate-obs-file" }, String(f))
@@ -3544,9 +4192,9 @@ function ObservatoryPanel(props) {
3544
4192
  React.createElement(
3545
4193
  "div",
3546
4194
  { className: "iterate-obs-bar", style: { marginTop: 6 } },
3547
- React.createElement("b", {}, "\u53D1\u73B0")
4195
+ React.createElement("b", {}, t("\u53D1\u73B0"))
3548
4196
  ),
3549
- ...tFindings.length === 0 ? [React.createElement("div", { key: "finding-none", className: "iterate-obs-empty" }, "\u672C\u7EBF\u7A0B\u65E0\u53D1\u73B0")] : tFindings.map((f, fi) => threadFindingPill(f, `tf${fi}`))
4197
+ ...tFindings.length === 0 ? [React.createElement("div", { key: "finding-none", className: "iterate-obs-empty" }, t("\u672C\u7EBF\u7A0B\u65E0\u53D1\u73B0"))] : tFindings.map((f, fi) => threadFindingPill(f, `tf${fi}`))
3550
4198
  ) : null
3551
4199
  );
3552
4200
  });
@@ -3557,7 +4205,7 @@ function ObservatoryPanel(props) {
3557
4205
  "div",
3558
4206
  { className: "iterate-obs-block-head" },
3559
4207
  React.createElement("b", {}, `Round ${typeof r.round === "number" ? r.round : ri + 1}`),
3560
- React.createElement("span", { className: "iterate-obs-chip" }, `${threads.length} \u7EBF\u7A0B`),
4208
+ React.createElement("span", { className: "iterate-obs-chip" }, tf(`{a0} \u7EBF\u7A0B`, { a0: threads.length })),
3561
4209
  React.createElement("span", { className: "iterate-obs-head-meta" }, `${fCount} findings`)
3562
4210
  ),
3563
4211
  React.createElement("div", { className: "iterate-obs-block-body" }, ...threadBlocks)
@@ -3568,11 +4216,11 @@ function ObservatoryPanel(props) {
3568
4216
  const renderTrend = () => {
3569
4217
  const conv = manifest.convergence || [];
3570
4218
  if (conv.length === 0) {
3571
- return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u6536\u655B\u6570\u636E");
4219
+ return React.createElement("div", { className: "iterate-obs-empty" }, t("\u6682\u65E0\u6536\u655B\u6570\u636E"));
3572
4220
  }
3573
4221
  const points = conv.map((n, i) => ({ round: i + 1, count: n }));
3574
4222
  const report = latestReport(session);
3575
- const comparison = buildRoundComparison(report, manifest.validations || []);
4223
+ const comparison = buildRoundComparison(report, manifest.validations || [], manifest.fixes || []);
3576
4224
  const comparisonTable = comparison.length === 0 ? null : React.createElement(
3577
4225
  "table",
3578
4226
  { className: "iterate-obs-table" },
@@ -3582,11 +4230,11 @@ function ObservatoryPanel(props) {
3582
4230
  React.createElement(
3583
4231
  "tr",
3584
4232
  {},
3585
- React.createElement("th", {}, "\u8F6E\u6B21"),
3586
- React.createElement("th", {}, "\u53D1\u73B0"),
3587
- React.createElement("th", {}, "\u5DF2\u4FEE\u590D"),
3588
- React.createElement("th", {}, "\u4E25\u91CD\u5EA6\u5206\u5E03"),
3589
- React.createElement("th", {}, "\u9A8C\u8BC1")
4233
+ React.createElement("th", {}, t("\u8F6E\u6B21")),
4234
+ React.createElement("th", {}, t("\u53D1\u73B0")),
4235
+ React.createElement("th", {}, t("\u5DF2\u4FEE\u590D")),
4236
+ React.createElement("th", {}, t("\u4E25\u91CD\u5EA6\u5206\u5E03")),
4237
+ React.createElement("th", {}, t("\u9A8C\u8BC1"))
3590
4238
  )
3591
4239
  ),
3592
4240
  React.createElement(
@@ -3595,7 +4243,7 @@ function ObservatoryPanel(props) {
3595
4243
  ...comparison.map((row) => {
3596
4244
  const sevText = Object.entries(row.severities).map(([k, v]) => `${k}:${v}`).join(" \xB7 ");
3597
4245
  const anyFailed = row.validations.some((v) => v.allowed !== true);
3598
- const valText = row.validations.length === 0 ? "\u2014" : `${row.validations.filter((v) => v.allowed === true).length}/${row.validations.length} \u901A\u8FC7${anyFailed ? "\uFF08\u542B\u5931\u8D25\uFF09" : ""}`;
4246
+ const valText = row.validations.length === 0 ? "\u2014" : tf(`{a0}/{a1} \u901A\u8FC7{a2}`, { a0: row.validations.filter((v) => v.allowed === true).length, a1: row.validations.length, a2: anyFailed ? t("\uFF08\u542B\u5931\u8D25\uFF09") : "" });
3599
4247
  return React.createElement(
3600
4248
  "tr",
3601
4249
  { key: `cmp-${row.round}` },
@@ -3617,7 +4265,7 @@ function ObservatoryPanel(props) {
3617
4265
  React.createElement(
3618
4266
  "div",
3619
4267
  { className: "iterate-obs-bar", style: { marginTop: 6 } },
3620
- `\u5404\u8F6E\u53D1\u73B0\u6570\u91CF\uFF1A${conv.join(" \u2192 ")}${typeof manifest.round === "number" ? ` \xB7 \u5F53\u524D Round ${manifest.round}` : ""}`
4268
+ tf(`\u5404\u8F6E\u53D1\u73B0\u6570\u91CF\uFF1A{a0}{a1}`, { a0: conv.join(" \u2192 "), a1: typeof manifest.round === "number" ? tf(` \xB7 \u5F53\u524D Round {a0}`, { a0: manifest.round }) : "" })
3621
4269
  ),
3622
4270
  comparisonTable ? React.createElement(
3623
4271
  "div",
@@ -3625,11 +4273,11 @@ function ObservatoryPanel(props) {
3625
4273
  React.createElement(
3626
4274
  "div",
3627
4275
  { className: "iterate-obs-block-head" },
3628
- React.createElement("span", {}, "\u8DE8\u8F6E\u5BF9\u6BD4\uFF08\u672C\u4F1A\u8BDD\uFF09"),
4276
+ React.createElement("span", {}, t("\u8DE8\u8F6E\u5BF9\u6BD4\uFF08\u672C\u4F1A\u8BDD\uFF09")),
3629
4277
  React.createElement(
3630
4278
  "span",
3631
4279
  { className: "iterate-obs-head-meta" },
3632
- "\u8DE8\u4F1A\u8BDD\u5BF9\u6BD4\uFF1A\u672C\u8868\u53EA\u8986\u76D6\u5F53\u524D\u4F1A\u8BDD\uFF1B\u70B9\u300C\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u300D\u8BA9\u6A21\u578B\u56DE\u663E iterate_history\uFF0C\u518D\u7528\u300C\u5BFC\u51FA JSON\u300D\u5E76\u6392\u6BD4\u8F83\u4E24\u6B21\u8FD0\u884C"
4280
+ t("\u8DE8\u4F1A\u8BDD\u5BF9\u6BD4\uFF1A\u672C\u8868\u53EA\u8986\u76D6\u5F53\u524D\u4F1A\u8BDD\uFF1B\u70B9\u300C\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u300D\u8BA9\u6A21\u578B\u56DE\u663E iterate_history\uFF0C\u518D\u7528\u300C\u5BFC\u51FA JSON\u300D\u5E76\u6392\u6BD4\u8F83\u4E24\u6B21\u8FD0\u884C")
3633
4281
  )
3634
4282
  ),
3635
4283
  React.createElement("div", { className: "iterate-obs-block-body" }, comparisonTable)
@@ -3639,7 +4287,7 @@ function ObservatoryPanel(props) {
3639
4287
  const renderFindings = () => {
3640
4288
  const findings = manifest.findings || [];
3641
4289
  if (findings.length === 0) {
3642
- return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u53D1\u73B0");
4290
+ return React.createElement("div", { className: "iterate-obs-empty" }, t("\u6682\u65E0\u53D1\u73B0"));
3643
4291
  }
3644
4292
  const opts = buildFilterOptions(findings);
3645
4293
  const { filtered } = filterFindingsWithIndices(findings, f3Filter);
@@ -3653,13 +4301,13 @@ function ObservatoryPanel(props) {
3653
4301
  const filterBar = React.createElement(
3654
4302
  "div",
3655
4303
  { className: "iterate-obs-bar", style: { marginBottom: 8, flexWrap: "wrap" } },
3656
- React.createElement("b", {}, "\u53D1\u73B0"),
4304
+ React.createElement("b", {}, t("\u53D1\u73B0")),
3657
4305
  ...opts.severities.map(
3658
4306
  (s) => React.createElement("button", {
3659
4307
  key: `sev-${s.value}`,
3660
4308
  className: "iterate-filter-chip",
3661
4309
  "data-active": f3Filter.severities.includes(s.value) ? "" : void 0,
3662
- title: `${s.value}\uFF08${s.count} \u9879\uFF09`,
4310
+ title: tf(`{a0}\uFF08{a1} \u9879\uFF09`, { a0: s.value, a1: s.count }),
3663
4311
  onClick: () => toggleSeverity(s.value)
3664
4312
  }, `${s.value} ${s.count}`)
3665
4313
  ),
@@ -3668,13 +4316,13 @@ function ObservatoryPanel(props) {
3668
4316
  {
3669
4317
  className: "iterate-filter-select",
3670
4318
  value: f3Filter.dimensions[0] || "",
3671
- "aria-label": "\u6309\u7EF4\u5EA6\u7B5B\u9009",
4319
+ "aria-label": t("\u6309\u7EF4\u5EA6\u7B5B\u9009"),
3672
4320
  onChange: (e) => {
3673
4321
  const v = e.target.value;
3674
4322
  setF3Filter((prev) => ({ ...prev, dimensions: v ? [v] : [] }));
3675
4323
  }
3676
4324
  },
3677
- React.createElement("option", { value: "" }, "\u5168\u90E8\u7EF4\u5EA6"),
4325
+ React.createElement("option", { value: "" }, t("\u5168\u90E8\u7EF4\u5EA6")),
3678
4326
  ...opts.dimensions.map(
3679
4327
  (d) => React.createElement("option", { key: d.value, value: d.value }, `${d.value} (${d.count})`)
3680
4328
  )
@@ -3682,16 +4330,16 @@ function ObservatoryPanel(props) {
3682
4330
  React.createElement("input", {
3683
4331
  className: "iterate-filter-search",
3684
4332
  type: "search",
3685
- placeholder: "\u641C\u7D22\u53D1\u73B0\u2026",
3686
- "aria-label": "\u641C\u7D22\u53D1\u73B0",
4333
+ placeholder: t("\u641C\u7D22\u53D1\u73B0\u2026"),
4334
+ "aria-label": t("\u641C\u7D22\u53D1\u73B0"),
3687
4335
  value: f3Filter.search,
3688
4336
  onChange: (e) => setF3Filter((prev) => ({ ...prev, search: e.target.value }))
3689
4337
  }),
3690
4338
  filterActive ? React.createElement("button", {
3691
4339
  className: "iterate-filter-clear",
3692
4340
  onClick: () => setF3Filter({ severities: [], dimensions: [], search: "" }),
3693
- title: "\u6E05\u9664\u5168\u90E8\u7B5B\u9009"
3694
- }, "\u6E05\u9664\u7B5B\u9009") : null,
4341
+ title: t("\u6E05\u9664\u5168\u90E8\u7B5B\u9009")
4342
+ }, t("\u6E05\u9664\u7B5B\u9009")) : null,
3695
4343
  React.createElement("span", { className: "iterate-filter-count" }, `${filtered.length}/${findings.length}`)
3696
4344
  );
3697
4345
  const findingBlocks = filtered.map((f, i) => {
@@ -3716,7 +4364,7 @@ function ObservatoryPanel(props) {
3716
4364
  React.createElement("span", {}, severityLabel(finding.severity)),
3717
4365
  React.createElement("span", { className: "iterate-obs-file" }, loc),
3718
4366
  React.createElement("span", {}, String(finding.dimension || "")),
3719
- finding.acknowledged === true ? React.createElement("span", { className: "iterate-obs-chip" }, "\u5DF2\u786E\u8BA4") : null
4367
+ finding.acknowledged === true ? React.createElement("span", { className: "iterate-obs-chip" }, t("\u5DF2\u786E\u8BA4")) : null
3720
4368
  ),
3721
4369
  React.createElement(
3722
4370
  "div",
@@ -3729,21 +4377,21 @@ function ObservatoryPanel(props) {
3729
4377
  className: "iterate-btn",
3730
4378
  "data-copied": copiedKey === `${k}-fix` ? "" : void 0,
3731
4379
  onClick: () => copyInstruction(`${k}-fix`, String(finding.suggested_fix || "")),
3732
- title: "\u590D\u5236\u8BE5 finding \u7684\u5EFA\u8BAE\u4FEE\u590D\u6587\u672C"
3733
- }, copiedKey === `${k}-fix` ? "\u5DF2\u590D\u5236" : "\u590D\u5236 suggested_fix"),
4380
+ title: t("\u590D\u5236\u8BE5 finding \u7684\u5EFA\u8BAE\u4FEE\u590D\u6587\u672C")
4381
+ }, copiedKey === `${k}-fix` ? t("\u5DF2\u590D\u5236") : t("\u590D\u5236 suggested_fix")),
3734
4382
  React.createElement("button", {
3735
4383
  className: "iterate-btn",
3736
4384
  "data-copied": copiedKey === `${k}-known` ? "" : void 0,
3737
4385
  onClick: () => copyInstruction(`${k}-known`, knownInstruction),
3738
- title: "\u590D\u5236 iterate_triage \u6307\u4EE4\u6587\u672C\uFF08\u6807\u8BB0\u4E3A known_intentional\uFF09"
3739
- }, copiedKey === `${k}-known` ? "\u5DF2\u590D\u5236" : "known_intentional"),
4386
+ title: t("\u590D\u5236 iterate_triage \u6307\u4EE4\u6587\u672C\uFF08\u6807\u8BB0\u4E3A known_intentional\uFF09")
4387
+ }, copiedKey === `${k}-known` ? t("\u5DF2\u590D\u5236") : "known_intentional"),
3740
4388
  React.createElement("button", {
3741
4389
  className: "iterate-btn",
3742
4390
  "data-primary": "",
3743
4391
  "data-copied": copiedKey === `${k}-repair` ? "" : void 0,
3744
4392
  onClick: () => copyInstruction(`${k}-repair`, fixInstruction),
3745
- title: "\u590D\u5236 iterate_fix \u6307\u4EE4\u6587\u672C"
3746
- }, copiedKey === `${k}-repair` ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u4FEE\u590D\u6307\u4EE4")
4393
+ title: t("\u590D\u5236 iterate_fix \u6307\u4EE4\u6587\u672C")
4394
+ }, copiedKey === `${k}-repair` ? t("\u5DF2\u590D\u5236") : t("\u590D\u5236\u4FEE\u590D\u6307\u4EE4"))
3747
4395
  )
3748
4396
  )
3749
4397
  );
@@ -3753,7 +4401,7 @@ function ObservatoryPanel(props) {
3753
4401
  "div",
3754
4402
  {},
3755
4403
  filterBar,
3756
- React.createElement("div", { className: "iterate-obs-empty" }, "\u65E0\u5339\u914D\u7684\u53D1\u73B0")
4404
+ React.createElement("div", { className: "iterate-obs-empty" }, t("\u65E0\u5339\u914D\u7684\u53D1\u73B0"))
3757
4405
  );
3758
4406
  }
3759
4407
  return React.createElement("div", {}, filterBar, ...findingBlocks);
@@ -3761,7 +4409,7 @@ function ObservatoryPanel(props) {
3761
4409
  const renderFixes = () => {
3762
4410
  const fixes = manifest.fixes || [];
3763
4411
  if (fixes.length === 0) {
3764
- return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u4FEE\u590D\u8BB0\u5F55");
4412
+ return React.createElement("div", { className: "iterate-obs-empty" }, t("\u6682\u65E0\u4FEE\u590D\u8BB0\u5F55"));
3765
4413
  }
3766
4414
  return React.createElement("div", {}, ...fixes.map((f, i) => {
3767
4415
  const k = `f4-${i}`;
@@ -3777,7 +4425,7 @@ function ObservatoryPanel(props) {
3777
4425
  { className: "iterate-obs-block-head" },
3778
4426
  React.createElement("b", {}, id),
3779
4427
  React.createElement("span", { className: "iterate-obs-file" }, String(f.file || "?")),
3780
- React.createElement("span", { className: "iterate-obs-chip" }, f.success === true ? "\u6210\u529F" : "\u5931\u8D25"),
4428
+ React.createElement("span", { className: "iterate-obs-chip" }, f.success === true ? t("\u6210\u529F") : t("\u5931\u8D25")),
3781
4429
  typeof f.round === "number" ? React.createElement("span", { className: "iterate-obs-head-meta" }, `Round ${f.round}`) : null
3782
4430
  ),
3783
4431
  React.createElement(
@@ -3794,8 +4442,8 @@ function ObservatoryPanel(props) {
3794
4442
  "data-danger": "",
3795
4443
  "data-copied": copiedKey === `${k}-rb` ? "" : void 0,
3796
4444
  onClick: () => copyInstruction(`${k}-rb`, rollbackText),
3797
- title: "\u590D\u5236 iterate_rollback \u6307\u4EE4\u6587\u672C"
3798
- }, copiedKey === `${k}-rb` ? "\u5DF2\u590D\u5236" : "\u56DE\u6EDA")
4445
+ title: t("\u590D\u5236 iterate_rollback \u6307\u4EE4\u6587\u672C")
4446
+ }, copiedKey === `${k}-rb` ? t("\u5DF2\u590D\u5236") : t("\u56DE\u6EDA"))
3799
4447
  )
3800
4448
  )
3801
4449
  );
@@ -3807,7 +4455,7 @@ function ObservatoryPanel(props) {
3807
4455
  return React.createElement(
3808
4456
  "div",
3809
4457
  { className: "iterate-obs-empty" },
3810
- diskEmptyStateText(toolCalledInSession(session, "iterate_transcript"), "\u65AD\u70B9")
4458
+ diskEmptyStateText(toolCalledInSession(session, "iterate_transcript"), t("\u65AD\u70B9"), getLang())
3811
4459
  );
3812
4460
  }
3813
4461
  const resumeText = buildCheckpointResumeInstruction();
@@ -3819,7 +4467,7 @@ function ObservatoryPanel(props) {
3819
4467
  React.createElement(
3820
4468
  "div",
3821
4469
  { className: "iterate-obs-block-head" },
3822
- React.createElement("b", {}, "\u65AD\u70B9"),
4470
+ React.createElement("b", {}, t("\u65AD\u70B9")),
3823
4471
  React.createElement("span", { className: "iterate-obs-head-meta" }, String(cp.updatedAt || ""))
3824
4472
  ),
3825
4473
  React.createElement(
@@ -3842,36 +4490,36 @@ function ObservatoryPanel(props) {
3842
4490
  "data-primary": "",
3843
4491
  "data-copied": copiedKey === "cp-resume" ? "" : void 0,
3844
4492
  onClick: () => copyInstruction("cp-resume", resumeText),
3845
- title: "\u590D\u5236 iterate_checkpoint resume \u6307\u4EE4\u6587\u672C\uFF08\u52A0\u8F7D\u65AD\u70B9\u5E76\u8BA1\u6570\u4E00\u6B21\u6062\u590D\uFF09"
3846
- }, copiedKey === "cp-resume" ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u6062\u590D\u6307\u4EE4"),
4493
+ title: t("\u590D\u5236 iterate_checkpoint resume \u6307\u4EE4\u6587\u672C\uFF08\u52A0\u8F7D\u65AD\u70B9\u5E76\u8BA1\u6570\u4E00\u6B21\u6062\u590D\uFF09")
4494
+ }, copiedKey === "cp-resume" ? t("\u5DF2\u590D\u5236") : t("\u590D\u5236\u6062\u590D\u6307\u4EE4")),
3847
4495
  React.createElement("button", {
3848
4496
  className: "iterate-btn",
3849
4497
  "data-danger": "",
3850
4498
  "data-copied": copiedKey === "cp-clear" ? "" : void 0,
3851
4499
  onClick: () => copyInstruction("cp-clear", clearText),
3852
- title: "\u590D\u5236 iterate_checkpoint clear \u6307\u4EE4\u6587\u672C\uFF08\u6E05\u9664\u5F53\u524D\u65AD\u70B9\uFF0C\u4F9B\u91CD\u65B0\u5F00\u59CB\uFF09"
3853
- }, copiedKey === "cp-clear" ? "\u5DF2\u590D\u5236" : "\u6E05\u9664\u65AD\u70B9")
4500
+ title: t("\u590D\u5236 iterate_checkpoint clear \u6307\u4EE4\u6587\u672C\uFF08\u6E05\u9664\u5F53\u524D\u65AD\u70B9\uFF0C\u4F9B\u91CD\u65B0\u5F00\u59CB\uFF09")
4501
+ }, copiedKey === "cp-clear" ? t("\u5DF2\u590D\u5236") : t("\u6E05\u9664\u65AD\u70B9"))
3854
4502
  )
3855
4503
  )
3856
4504
  );
3857
4505
  };
3858
4506
  const renderConsole = () => {
3859
4507
  const approval = manifest.approval || { policy: "ask" };
3860
- const policyLabelMap = { ask: "\u8BE2\u95EE\u7528\u6237", deny: "\u62D2\u7EDD", allow: "\u76F4\u63A5\u6267\u884C" };
4508
+ const policyLabelMap = { ask: t("\u8BE2\u95EE\u7528\u6237"), deny: t("\u62D2\u7EDD"), allow: t("\u76F4\u63A5\u6267\u884C") };
3861
4509
  const policyLabel = policyLabelMap[String(approval.policy || "ask")] || String(approval.policy || "ask");
3862
4510
  const nudge = manifest.nudge || null;
3863
4511
  const present = Boolean(nudge && nudge.text);
3864
4512
  const activeNudgeText = nudge && nudge.text ? nudge.text : "";
3865
- const nudgeInstruction = `\u8BF7\u8C03\u7528 \`iterate_transcript\` \u5199\u5165 nudge \u6307\u4EE4\uFF1A
4513
+ const nudgeInstruction = tf(`\u8BF7\u8C03\u7528 \`iterate_transcript\` \u5199\u5165 nudge \u6307\u4EE4\uFF1A
3866
4514
 
3867
4515
  \`\`\`json
3868
- ${JSON.stringify({ operation: "nudge", text: nudgeText }, null, 2)}
3869
- \`\`\``;
3870
- const activeClearInstruction = `\u8BF7\u8C03\u7528 \`iterate_transcript\` \u6E05\u9664\u5F53\u524D nudge\uFF1A
4516
+ {a0}
4517
+ \`\`\``, { a0: JSON.stringify({ operation: "nudge", text: nudgeText }, null, 2) });
4518
+ const activeClearInstruction = tf(`\u8BF7\u8C03\u7528 \`iterate_transcript\` \u6E05\u9664\u5F53\u524D nudge\uFF1A
3871
4519
 
3872
4520
  \`\`\`json
3873
- ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3874
- \`\`\``;
4521
+ {a0}
4522
+ \`\`\``, { a0: JSON.stringify({ operation: "nudge", text: null }, null, 2) });
3875
4523
  const validations = Array.isArray(manifest.validations) ? manifest.validations : [];
3876
4524
  const abortedByValidation = String(manifest.stoppedReason || "") === "aborted_by_validation";
3877
4525
  const validationRows = validations.map((v, i) => {
@@ -3893,10 +4541,10 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3893
4541
  React.createElement(
3894
4542
  "span",
3895
4543
  { className: "iterate-obs-chip", "data-status": allowed ? "pass" : "fail" },
3896
- allowed ? "\u901A\u8FC7" : "\u5931\u8D25"
4544
+ allowed ? t("\u901A\u8FC7") : t("\u5931\u8D25")
3897
4545
  ),
3898
4546
  React.createElement("span", { className: "iterate-obs-head-meta" }, exit),
3899
- highlight ? React.createElement("span", { className: "iterate-obs-chip", "data-status": "fail" }, "\u5BFC\u81F4\u4E2D\u6B62") : null
4547
+ highlight ? React.createElement("span", { className: "iterate-obs-chip", "data-status": "fail" }, t("\u5BFC\u81F4\u4E2D\u6B62")) : null
3900
4548
  );
3901
4549
  });
3902
4550
  const validationsBlock = React.createElement(
@@ -3905,9 +4553,9 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3905
4553
  React.createElement(
3906
4554
  "div",
3907
4555
  { className: "iterate-obs-block-head" },
3908
- React.createElement("span", {}, "\u9A8C\u8BC1\u7ED3\u679C"),
3909
- validations.length === 0 ? React.createElement("span", { className: "iterate-obs-head-meta" }, "\u672C\u8F6E\u5C1A\u65E0\u9A8C\u8BC1\u8BB0\u5F55") : React.createElement("span", { className: "iterate-obs-head-meta" }, `${validations.length} \u6761\u547D\u4EE4`),
3910
- abortedByValidation ? React.createElement("span", { className: "iterate-obs-badge", "data-status": "fail" }, "\u56E0\u9A8C\u8BC1\u5931\u8D25\u4E2D\u6B62") : null
4556
+ React.createElement("span", {}, t("\u9A8C\u8BC1\u7ED3\u679C")),
4557
+ validations.length === 0 ? React.createElement("span", { className: "iterate-obs-head-meta" }, t("\u672C\u8F6E\u5C1A\u65E0\u9A8C\u8BC1\u8BB0\u5F55")) : React.createElement("span", { className: "iterate-obs-head-meta" }, tf(`{a0} \u6761\u547D\u4EE4`, { a0: validations.length })),
4558
+ abortedByValidation ? React.createElement("span", { className: "iterate-obs-badge", "data-status": "fail" }, t("\u56E0\u9A8C\u8BC1\u5931\u8D25\u4E2D\u6B62")) : null
3911
4559
  ),
3912
4560
  React.createElement(
3913
4561
  "div",
@@ -3915,7 +4563,7 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3915
4563
  validations.length === 0 ? React.createElement(
3916
4564
  "div",
3917
4565
  { className: "iterate-obs-empty" },
3918
- "\u5C1A\u672A\u62C9\u53D6\u9A8C\u8BC1\u7ED3\u679C\uFF1A\u6570\u636E\u5728\u78C1\u76D8 transcript \u91CC\uFF0C\u70B9\u300C\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u300D\u590D\u5236\u6307\u4EE4\u53D1\u56DE\u4F1A\u8BDD\u5373\u53EF\u56DE\u586B\u3002"
4566
+ t("\u5C1A\u672A\u62C9\u53D6\u9A8C\u8BC1\u7ED3\u679C\uFF1A\u6570\u636E\u5728\u78C1\u76D8 transcript \u91CC\uFF0C\u70B9\u300C\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u300D\u590D\u5236\u6307\u4EE4\u53D1\u56DE\u4F1A\u8BDD\u5373\u53EF\u56DE\u586B\u3002")
3919
4567
  ) : React.createElement(React.Fragment, null, ...validationRows)
3920
4568
  )
3921
4569
  );
@@ -3925,9 +4573,9 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3925
4573
  React.createElement(
3926
4574
  "div",
3927
4575
  { className: "iterate-obs-block-head" },
3928
- React.createElement("span", {}, "\u8FD0\u884C\u63A7\u5236\u53F0"),
4576
+ React.createElement("span", {}, t("\u8FD0\u884C\u63A7\u5236\u53F0")),
3929
4577
  React.createElement("span", { className: "iterate-obs-badge", "data-live": live ? "" : void 0 }, runStatusText(live, manifest)),
3930
- React.createElement("span", { className: "iterate-obs-head-meta" }, `\u6279\u51C6\u7B56\u7565\uFF1A${policyLabel}`)
4578
+ React.createElement("span", { className: "iterate-obs-head-meta" }, tf(`\u6279\u51C6\u7B56\u7565\uFF1A{a0}`, { a0: policyLabel }))
3931
4579
  ),
3932
4580
  React.createElement(
3933
4581
  "div",
@@ -3935,21 +4583,21 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3935
4583
  present ? React.createElement(
3936
4584
  "div",
3937
4585
  { className: "iterate-obs-bar", style: { marginBottom: 6 } },
3938
- React.createElement("b", {}, "\u5F53\u524D nudge"),
4586
+ React.createElement("b", {}, t("\u5F53\u524D nudge")),
3939
4587
  React.createElement("span", { className: "iterate-obs-msg" }, activeNudgeText),
3940
4588
  React.createElement("button", {
3941
4589
  className: "iterate-btn",
3942
4590
  "data-copied": copiedKey === "nudge-clear" ? "" : void 0,
3943
4591
  onClick: () => copyInstruction("nudge-clear", activeClearInstruction),
3944
- title: "\u590D\u5236\u6E05\u9664 nudge \u6307\u4EE4\uFF08\u5199 text:null\uFF09\uFF0C\u5728\u8FD0\u884C\u63A7\u5236\u53F0\u63D0\u793A\u6E05\u9664\u5DF2\u6301\u4E45\u5316\u7684 nudge"
3945
- }, copiedKey === "nudge-clear" ? "\u5DF2\u590D\u5236\u6E05\u9664\u6307\u4EE4" : "\u590D\u5236\u6E05\u9664\u6307\u4EE4")
4592
+ title: t("\u590D\u5236\u6E05\u9664 nudge \u6307\u4EE4\uFF08\u5199 text:null\uFF09\uFF0C\u5728\u8FD0\u884C\u63A7\u5236\u53F0\u63D0\u793A\u6E05\u9664\u5DF2\u6301\u4E45\u5316\u7684 nudge")
4593
+ }, copiedKey === "nudge-clear" ? t("\u5DF2\u590D\u5236\u6E05\u9664\u6307\u4EE4") : t("\u590D\u5236\u6E05\u9664\u6307\u4EE4"))
3946
4594
  ) : null,
3947
4595
  React.createElement("textarea", {
3948
4596
  className: "iterate-obs-input iterate-obs-mono",
3949
4597
  rows: 3,
3950
- placeholder: "\u8D77\u8349\u4E00\u6761 nudge \u65B9\u5411\u6307\u4EE4\u2026",
4598
+ placeholder: t("\u8D77\u8349\u4E00\u6761 nudge \u65B9\u5411\u6307\u4EE4\u2026"),
3951
4599
  value: nudgeText,
3952
- "aria-label": "nudge \u8349\u7A3F",
4600
+ "aria-label": t("nudge \u8349\u7A3F"),
3953
4601
  onChange: (e) => setNudgeText(e.target.value)
3954
4602
  }),
3955
4603
  React.createElement(
@@ -3961,8 +4609,8 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3961
4609
  "data-copied": copiedKey === "nudge" ? "" : void 0,
3962
4610
  disabled: !nudgeText,
3963
4611
  onClick: () => copyInstruction("nudge", nudgeInstruction),
3964
- title: "\u590D\u5236 iterate_transcript nudge \u6307\u4EE4\u6587\u672C"
3965
- }, copiedKey === "nudge" ? "\u5DF2\u590D\u5236" : "\u590D\u5236 nudge \u6307\u4EE4")
4612
+ title: t("\u590D\u5236 iterate_transcript nudge \u6307\u4EE4\u6587\u672C")
4613
+ }, copiedKey === "nudge" ? t("\u5DF2\u590D\u5236") : t("\u590D\u5236 nudge \u6307\u4EE4"))
3966
4614
  ),
3967
4615
  validationsBlock
3968
4616
  )
@@ -3971,11 +4619,11 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3971
4619
  const renderTimeline = () => {
3972
4620
  const entries = manifest.timeline || [];
3973
4621
  if (entries.length === 0) {
3974
- return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u65F6\u95F4\u7EBF\u6761\u76EE");
4622
+ return React.createElement("div", { className: "iterate-obs-empty" }, t("\u6682\u65E0\u65F6\u95F4\u7EBF\u6761\u76EE"));
3975
4623
  }
3976
- const types = Array.from(new Set(entries.map((t) => String(t.type || "unknown")).filter(Boolean))).sort();
4624
+ const types = Array.from(new Set(entries.map((t2) => String(t2.type || "unknown")).filter(Boolean))).sort();
3977
4625
  const rounds = Array.from(
3978
- new Set(entries.map((t) => typeof t.round === "number" ? String(t.round) : "").filter(Boolean))
4626
+ new Set(entries.map((t2) => typeof t2.round === "number" ? String(t2.round) : "").filter(Boolean))
3979
4627
  ).sort((a, b) => Number(a) - Number(b));
3980
4628
  const filtered = filterTimelineEntries(entries, {
3981
4629
  type: timelineType,
@@ -3989,14 +4637,14 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3989
4637
  setTimelineSearch("");
3990
4638
  };
3991
4639
  const sorted = filtered.slice();
3992
- const rows = sorted.map((t, i) => {
4640
+ const rows = sorted.map((t2, i) => {
3993
4641
  const k = `f7-${i}`;
3994
4642
  let dataText = "";
3995
- if (t.data && typeof t.data === "object") {
4643
+ if (t2.data && typeof t2.data === "object") {
3996
4644
  try {
3997
- dataText = JSON.stringify(t.data);
4645
+ dataText = JSON.stringify(t2.data);
3998
4646
  } catch {
3999
- dataText = String(t.data);
4647
+ dataText = String(t2.data);
4000
4648
  }
4001
4649
  }
4002
4650
  return React.createElement(
@@ -4005,11 +4653,11 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4005
4653
  React.createElement(
4006
4654
  "div",
4007
4655
  { className: "iterate-obs-bar", style: { width: "100%" } },
4008
- React.createElement("span", { className: "iterate-obs-chip" }, String(t.type || "?")),
4656
+ React.createElement("span", { className: "iterate-obs-chip" }, String(t2.type || "?")),
4009
4657
  React.createElement(
4010
4658
  "span",
4011
4659
  { className: "iterate-obs-head-meta" },
4012
- `${typeof t.round === "number" ? `Round ${t.round} \xB7 ` : ""}${String(t.timestamp || "")}`
4660
+ `${typeof t2.round === "number" ? `Round ${t2.round} \xB7 ` : ""}${String(t2.timestamp || "")}`
4013
4661
  )
4014
4662
  ),
4015
4663
  dataText ? React.createElement("div", { className: "iterate-obs-code" }, dataText) : null
@@ -4018,42 +4666,42 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4018
4666
  const filterBar = React.createElement(
4019
4667
  "div",
4020
4668
  { className: "iterate-obs-bar", style: { marginBottom: 8 } },
4021
- React.createElement("b", {}, "\u65F6\u95F4\u7EBF"),
4669
+ React.createElement("b", {}, t("\u65F6\u95F4\u7EBF")),
4022
4670
  React.createElement(
4023
4671
  "select",
4024
4672
  {
4025
4673
  className: "iterate-filter-select",
4026
4674
  value: timelineType,
4027
- "aria-label": "\u6309\u65F6\u95F4\u7EBF\u7C7B\u578B\u7B5B\u9009",
4675
+ "aria-label": t("\u6309\u65F6\u95F4\u7EBF\u7C7B\u578B\u7B5B\u9009"),
4028
4676
  onChange: (e) => setTimelineType(e.target.value)
4029
4677
  },
4030
- React.createElement("option", { value: "" }, "\u5168\u90E8\u7C7B\u578B"),
4031
- ...types.map((t) => React.createElement("option", { key: t, value: t }, t))
4678
+ React.createElement("option", { value: "" }, t("\u5168\u90E8\u7C7B\u578B")),
4679
+ ...types.map((t2) => React.createElement("option", { key: t2, value: t2 }, t2))
4032
4680
  ),
4033
4681
  React.createElement(
4034
4682
  "select",
4035
4683
  {
4036
4684
  className: "iterate-filter-select",
4037
4685
  value: timelineRound,
4038
- "aria-label": "\u6309\u8F6E\u6B21\u7B5B\u9009",
4686
+ "aria-label": t("\u6309\u8F6E\u6B21\u7B5B\u9009"),
4039
4687
  onChange: (e) => setTimelineRound(e.target.value)
4040
4688
  },
4041
- React.createElement("option", { value: "" }, "\u5168\u90E8\u8F6E\u6B21"),
4689
+ React.createElement("option", { value: "" }, t("\u5168\u90E8\u8F6E\u6B21")),
4042
4690
  ...rounds.map((r) => React.createElement("option", { key: r, value: r }, `Round ${r}`))
4043
4691
  ),
4044
4692
  React.createElement("input", {
4045
4693
  className: "iterate-filter-search",
4046
4694
  type: "search",
4047
- placeholder: "\u641C\u7D22\u65F6\u95F4\u7EBF\u2026",
4048
- "aria-label": "\u641C\u7D22\u65F6\u95F4\u7EBF",
4695
+ placeholder: t("\u641C\u7D22\u65F6\u95F4\u7EBF\u2026"),
4696
+ "aria-label": t("\u641C\u7D22\u65F6\u95F4\u7EBF"),
4049
4697
  value: timelineSearch,
4050
4698
  onChange: (e) => setTimelineSearch(e.target.value)
4051
4699
  }),
4052
4700
  filterActive ? React.createElement("button", {
4053
4701
  className: "iterate-filter-clear",
4054
4702
  onClick: clearTimelineFilter,
4055
- title: "\u6E05\u9664\u5168\u90E8\u7B5B\u9009"
4056
- }, "\u6E05\u9664") : null,
4703
+ title: t("\u6E05\u9664\u5168\u90E8\u7B5B\u9009")
4704
+ }, t("\u6E05\u9664")) : null,
4057
4705
  React.createElement("span", { className: "iterate-filter-count" }, `${filtered.length}/${entries.length}`)
4058
4706
  );
4059
4707
  if (rows.length === 0) {
@@ -4061,7 +4709,7 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4061
4709
  "div",
4062
4710
  {},
4063
4711
  filterBar,
4064
- React.createElement("div", { className: "iterate-obs-empty" }, "\u65E0\u5339\u914D\u7684\u65F6\u95F4\u7EBF\u6761\u76EE")
4712
+ React.createElement("div", { className: "iterate-obs-empty" }, t("\u65E0\u5339\u914D\u7684\u65F6\u95F4\u7EBF\u6761\u76EE"))
4065
4713
  );
4066
4714
  }
4067
4715
  return React.createElement("div", {}, filterBar, ...rows);
@@ -4079,29 +4727,29 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4079
4727
  const headerBar = React.createElement(
4080
4728
  "div",
4081
4729
  { className: "iterate-obs-bar", style: { marginBottom: 8 } },
4082
- React.createElement("b", {}, "\u8D28\u91CF\u95E8\u7981"),
4730
+ React.createElement("b", {}, t("\u8D28\u91CF\u95E8\u7981")),
4083
4731
  React.createElement("button", {
4084
4732
  className: "iterate-btn",
4085
4733
  "data-primary": "",
4086
4734
  "data-copied": copiedKey === "qgate" ? "" : void 0,
4087
4735
  onClick: () => copyInstruction("qgate", gateInstruction),
4088
- title: "\u590D\u5236 iterate_quality_gate \u67E5\u8BE2\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09"
4089
- }, copiedKey === "qgate" ? "\u5DF2\u590D\u5236" : "\u67E5\u8BE2\u95E8\u7981"),
4736
+ title: t("\u590D\u5236 iterate_quality_gate \u67E5\u8BE2\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09")
4737
+ }, copiedKey === "qgate" ? t("\u5DF2\u590D\u5236") : t("\u67E5\u8BE2\u95E8\u7981")),
4090
4738
  React.createElement("button", {
4091
4739
  className: "iterate-btn",
4092
4740
  "data-danger": "",
4093
4741
  "data-copied": copiedKey === "qgate-clear" ? "" : void 0,
4094
4742
  onClick: () => copyInstruction("qgate-clear", gateClearInstruction),
4095
- title: "\u590D\u5236 iterate_quality_gate clear \u6307\u4EE4\u6587\u672C\uFF08\u91CD\u7F6E\u9648\u65E7\u7684 FAIL \u8BC1\u4E66\uFF09"
4096
- }, copiedKey === "qgate-clear" ? "\u5DF2\u590D\u5236" : "\u6E05\u9664\u95E8\u7981"),
4743
+ title: t("\u590D\u5236 iterate_quality_gate clear \u6307\u4EE4\u6587\u672C\uFF08\u91CD\u7F6E\u9648\u65E7\u7684 FAIL \u8BC1\u4E66\uFF09")
4744
+ }, copiedKey === "qgate-clear" ? t("\u5DF2\u590D\u5236") : t("\u6E05\u9664\u95E8\u7981")),
4097
4745
  // Gap #4: the browser cannot read `.iterate/` directly — one click
4098
4746
  // copies the pull instruction that makes the model echo it into the stream.
4099
4747
  React.createElement("button", {
4100
4748
  className: "iterate-btn",
4101
4749
  "data-copied": copiedKey === "disk-snapshot" ? "" : void 0,
4102
4750
  onClick: () => copyInstruction("disk-snapshot", pullInstruction),
4103
- title: "\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u8BA9\u6A21\u578B\u56DE\u663E .iterate/ \u4E0B\u7684\u95E8\u7981\u3001\u7ECF\u9A8C\u3001\u9632\u5FA1\u4E8B\u4EF6\u4E0E transcript"
4104
- }, copiedKey === "disk-snapshot" ? "\u5DF2\u590D\u5236" : "\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167")
4751
+ title: t("\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u8BA9\u6A21\u578B\u56DE\u663E .iterate/ \u4E0B\u7684\u95E8\u7981\u3001\u7ECF\u9A8C\u3001\u9632\u5FA1\u4E8B\u4EF6\u4E0E transcript")
4752
+ }, copiedKey === "disk-snapshot" ? t("\u5DF2\u590D\u5236") : t("\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167"))
4105
4753
  );
4106
4754
  if (!hasGate) {
4107
4755
  return React.createElement(
@@ -4114,7 +4762,7 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4114
4762
  React.createElement(
4115
4763
  "div",
4116
4764
  { className: "iterate-obs-block-head" },
4117
- React.createElement("span", {}, "\u8D28\u91CF\u95E8\u7981\u89C6\u56FE"),
4765
+ React.createElement("span", {}, t("\u8D28\u91CF\u95E8\u7981\u89C6\u56FE")),
4118
4766
  React.createElement("span", { className: "iterate-obs-badge" }, "v3.0")
4119
4767
  ),
4120
4768
  React.createElement(
@@ -4125,12 +4773,12 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4125
4773
  { className: "iterate-obs-msg" },
4126
4774
  // Gap #4: distinguish "we pulled it and it is empty" from "we
4127
4775
  // never asked the model to read the disk".
4128
- diskEmptyStateText(pulled, "\u8D28\u91CF\u95E8\u7981\u8BC1\u4E66")
4776
+ diskEmptyStateText(pulled, t("\u8D28\u91CF\u95E8\u7981\u8BC1\u4E66"), getLang())
4129
4777
  ),
4130
4778
  React.createElement(
4131
4779
  "div",
4132
4780
  { className: "iterate-obs-msg", style: { marginTop: 6 } },
4133
- "\u8D28\u91CF\u95E8\u7981\u663E\u793A\u5404\u7EF4\u5EA6\u6536\u655B\u5EA6\u3001\u9A8C\u8BC1\u901A\u8FC7\u7387\u548C\u6574\u4F53 PASS/FAIL \u72B6\u6001\u3002"
4781
+ t("\u8D28\u91CF\u95E8\u7981\u663E\u793A\u5404\u7EF4\u5EA6\u6536\u655B\u5EA6\u3001\u9A8C\u8BC1\u901A\u8FC7\u7387\u548C\u6574\u4F53 PASS/FAIL \u72B6\u6001\u3002")
4134
4782
  ),
4135
4783
  React.createElement(
4136
4784
  "div",
@@ -4139,14 +4787,14 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4139
4787
  className: "iterate-btn",
4140
4788
  "data-copied": copiedKey === "qgate-instr" ? "" : void 0,
4141
4789
  onClick: () => copyInstruction("qgate-instr", gateInstruction),
4142
- title: "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4"
4143
- }, copiedKey === "qgate-instr" ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4"),
4790
+ title: t("\u590D\u5236\u67E5\u8BE2\u6307\u4EE4")
4791
+ }, copiedKey === "qgate-instr" ? t("\u5DF2\u590D\u5236") : t("\u590D\u5236\u67E5\u8BE2\u6307\u4EE4")),
4144
4792
  React.createElement("button", {
4145
4793
  className: "iterate-btn",
4146
4794
  "data-copied": copiedKey === "disk-snapshot-2" ? "" : void 0,
4147
4795
  onClick: () => copyInstruction("disk-snapshot-2", pullInstruction),
4148
- title: "\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4"
4149
- }, copiedKey === "disk-snapshot-2" ? "\u5DF2\u590D\u5236" : "\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167")
4796
+ title: t("\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4")
4797
+ }, copiedKey === "disk-snapshot-2" ? t("\u5DF2\u590D\u5236") : t("\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167"))
4150
4798
  )
4151
4799
  )
4152
4800
  )
@@ -4166,7 +4814,7 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4166
4814
  React.createElement(
4167
4815
  "span",
4168
4816
  { className: "iterate-obs-head-meta" },
4169
- `\u6574\u4F53\u8BC4\u5206 ${num(gate && gate.overallScore)} \xB7 \u9A8C\u8BC1 ${num(gate && gate.passedChecks)}/${num(gate && gate.totalChecks)} \xB7 \u901A\u8FC7\u7387 ${num(gate && gate.verificationPassRate)}%`
4817
+ tf(`\u6574\u4F53\u8BC4\u5206 {a0} \xB7 \u9A8C\u8BC1 {a1}/{a2} \xB7 \u901A\u8FC7\u7387 {a3}%`, { a0: num(gate && gate.overallScore), a1: num(gate && gate.passedChecks), a2: num(gate && gate.totalChecks), a3: num(gate && gate.verificationPassRate) })
4170
4818
  )
4171
4819
  ),
4172
4820
  React.createElement(
@@ -4179,7 +4827,7 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4179
4827
  (d, i) => React.createElement(
4180
4828
  "span",
4181
4829
  { key: `gdim-${i}`, className: "iterate-obs-chip" },
4182
- `${String(d.dimension || "?")} \xB7 \u6536\u655B ${num(d.convergenceRate)}% \xB7 \u8BC4\u5206 ${num(d.score)}`
4830
+ tf(`{a0} \xB7 \u6536\u655B {a1}% \xB7 \u8BC4\u5206 {a2}`, { a0: String(d.dimension || "?"), a1: num(d.convergenceRate), a2: num(d.score) })
4183
4831
  )
4184
4832
  )
4185
4833
  ) : null,
@@ -4201,33 +4849,33 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4201
4849
  style: { width: `${fill}%` }
4202
4850
  })
4203
4851
  ),
4204
- React.createElement("span", {}, `\u8BC4\u5206 ${num(d.score)} \xB7 \u53D1\u73B0 ${num(d.findingsCount)} \xB7 \u5DF2\u4FEE\u590D ${num(d.fixedCount)}`)
4852
+ React.createElement("span", {}, tf(`\u8BC4\u5206 {a0} \xB7 \u53D1\u73B0 {a1} \xB7 \u5DF2\u4FEE\u590D {a2}`, { a0: num(d.score), a1: num(d.findingsCount), a2: num(d.fixedCount) }))
4205
4853
  );
4206
4854
  })
4207
4855
  ),
4208
- gate && gate.failReason ? React.createElement("div", { className: "iterate-obs-msg", style: { marginTop: 6 } }, `\u5931\u8D25\u539F\u56E0\uFF1A${String(gate.failReason)}`) : null
4856
+ gate && gate.failReason ? React.createElement("div", { className: "iterate-obs-msg", style: { marginTop: 6 } }, tf(`\u5931\u8D25\u539F\u56E0\uFF1A{a0}`, { a0: String(gate.failReason) })) : null
4209
4857
  )
4210
4858
  )
4211
4859
  );
4212
4860
  };
4213
4861
  const renderExperienceBank = () => {
4214
- const expInstruction = expSearch ? `\u8BF7\u8C03\u7528 \`iterate_experience\` \u641C\u7D22\u7ECF\u9A8C\uFF1A
4862
+ const expInstruction = expSearch ? tf(`\u8BF7\u8C03\u7528 \`iterate_experience\` \u641C\u7D22\u7ECF\u9A8C\uFF1A
4215
4863
 
4216
4864
  \`\`\`json
4217
- ${JSON.stringify({ operation: "search", query: expSearch }, null, 2)}
4218
- \`\`\`` : buildExperienceListInstruction();
4865
+ {a0}
4866
+ \`\`\``, { a0: JSON.stringify({ operation: "search", query: expSearch }, null, 2) }) : buildExperienceListInstruction();
4219
4867
  const expEntries = experienceBank && experienceBank.entries ? experienceBank.entries : [];
4220
4868
  const query = expSearch.trim().toLowerCase();
4221
4869
  const visible = query ? expEntries.filter(
4222
- (e) => [e.pattern, e.description, e.id, e.dimension, e.severity, ...e.tags || []].filter(Boolean).some((t) => String(t).toLowerCase().includes(query))
4870
+ (e) => [e.pattern, e.description, e.id, e.dimension, e.severity, ...e.tags || []].filter(Boolean).some((t2) => String(t2).toLowerCase().includes(query))
4223
4871
  ) : expEntries;
4224
4872
  const pulled = toolCalledInSession(session, "iterate_experience");
4225
4873
  const resultRows = visible.map((e, i) => {
4226
- const adoptInstruction = `\u8BF7\u8C03\u7528 \`iterate_experience\` \u91C7\u7EB3\u7ECF\u9A8C\u5E76\u5E94\u7528\u5DF2\u9A8C\u8BC1\u4FEE\u6CD5\uFF1A
4874
+ const adoptInstruction = tf(`\u8BF7\u8C03\u7528 \`iterate_experience\` \u91C7\u7EB3\u7ECF\u9A8C\u5E76\u5E94\u7528\u5DF2\u9A8C\u8BC1\u4FEE\u6CD5\uFF1A
4227
4875
 
4228
4876
  \`\`\`json
4229
- ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
4230
- \`\`\``;
4877
+ {a0}
4878
+ \`\`\``, { a0: JSON.stringify({ operation: "get", id: e.id }, null, 2) });
4231
4879
  return React.createElement(
4232
4880
  "div",
4233
4881
  { key: `exp-${i}`, className: "iterate-obs-block" },
@@ -4236,7 +4884,7 @@ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
4236
4884
  { className: "iterate-obs-block-head" },
4237
4885
  React.createElement("span", { className: "iterate-obs-chip" }, String(e.dimension || "")),
4238
4886
  React.createElement("span", {}, String(e.pattern || "?")),
4239
- React.createElement("span", { className: "iterate-exp-hit" }, `\u547D\u4E2D ${e.hitCount ?? 0} \u6B21`)
4887
+ React.createElement("span", { className: "iterate-exp-hit" }, tf(`\u547D\u4E2D {a0} \u6B21`, { a0: e.hitCount ?? 0 }))
4240
4888
  ),
4241
4889
  React.createElement(
4242
4890
  "div",
@@ -4250,8 +4898,8 @@ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
4250
4898
  "data-primary": "",
4251
4899
  "data-copied": copiedKey === `exp-adopt-${i}` ? "" : void 0,
4252
4900
  onClick: () => copyInstruction(`exp-adopt-${i}`, adoptInstruction),
4253
- title: "\u590D\u5236\u91C7\u7EB3\u6307\u4EE4\uFF08\u67E5\u8BE2\u8BE5\u7ECF\u9A8C\u5E76\u5E94\u7528\u5230\u5F53\u524D\u4E0A\u4E0B\u6587\uFF09"
4254
- }, copiedKey === `exp-adopt-${i}` ? "\u5DF2\u590D\u5236" : "\u91C7\u7EB3") : null
4901
+ title: t("\u590D\u5236\u91C7\u7EB3\u6307\u4EE4\uFF08\u67E5\u8BE2\u8BE5\u7ECF\u9A8C\u5E76\u5E94\u7528\u5230\u5F53\u524D\u4E0A\u4E0B\u6587\uFF09")
4902
+ }, copiedKey === `exp-adopt-${i}` ? t("\u5DF2\u590D\u5236") : t("\u91C7\u7EB3")) : null
4255
4903
  )
4256
4904
  )
4257
4905
  );
@@ -4262,21 +4910,21 @@ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
4262
4910
  React.createElement(
4263
4911
  "div",
4264
4912
  { className: "iterate-obs-bar", style: { marginBottom: 8 } },
4265
- React.createElement("b", {}, "\u7ECF\u9A8C\u94F6\u884C"),
4266
- React.createElement("span", { className: "iterate-obs-head-meta" }, `\u4F1A\u8BDD\u5185 ${expEntries.length} \u6761`),
4913
+ React.createElement("b", {}, t("\u7ECF\u9A8C\u94F6\u884C")),
4914
+ React.createElement("span", { className: "iterate-obs-head-meta" }, tf(`\u4F1A\u8BDD\u5185 {a0} \u6761`, { a0: expEntries.length })),
4267
4915
  React.createElement("button", {
4268
4916
  className: "iterate-btn",
4269
4917
  "data-primary": "",
4270
4918
  "data-copied": copiedKey === "exp-list" ? "" : void 0,
4271
4919
  onClick: () => copyInstruction("exp-list", buildExperienceListInstruction()),
4272
- title: "\u590D\u5236\u5217\u51FA\u7ECF\u9A8C\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09"
4273
- }, copiedKey === "exp-list" ? "\u5DF2\u590D\u5236" : "\u5217\u51FA\u7ECF\u9A8C"),
4920
+ title: t("\u590D\u5236\u5217\u51FA\u7ECF\u9A8C\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09")
4921
+ }, copiedKey === "exp-list" ? t("\u5DF2\u590D\u5236") : t("\u5217\u51FA\u7ECF\u9A8C")),
4274
4922
  React.createElement("button", {
4275
4923
  className: "iterate-btn",
4276
4924
  "data-copied": copiedKey === "exp-snapshot" ? "" : void 0,
4277
4925
  onClick: () => copyInstruction("exp-snapshot", pullInstruction),
4278
- title: "\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u628A .iterate/ \u4E0B\u7684\u7ECF\u9A8C\u94F6\u884C\u7B49\u4EA7\u7269\u56DE\u663E\u8FDB\u4F1A\u8BDD"
4279
- }, copiedKey === "exp-snapshot" ? "\u5DF2\u590D\u5236" : "\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167")
4926
+ title: t("\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u628A .iterate/ \u4E0B\u7684\u7ECF\u9A8C\u94F6\u884C\u7B49\u4EA7\u7269\u56DE\u663E\u8FDB\u4F1A\u8BDD")
4927
+ }, copiedKey === "exp-snapshot" ? t("\u5DF2\u590D\u5236") : t("\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167"))
4280
4928
  ),
4281
4929
  React.createElement(
4282
4930
  "div",
@@ -4284,8 +4932,8 @@ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
4284
4932
  React.createElement("input", {
4285
4933
  className: "iterate-filter-search",
4286
4934
  type: "search",
4287
- placeholder: "\u641C\u7D22\u7ECF\u9A8C\u6A21\u5F0F\u2026",
4288
- "aria-label": "\u641C\u7D22\u7ECF\u9A8C",
4935
+ placeholder: t("\u641C\u7D22\u7ECF\u9A8C\u6A21\u5F0F\u2026"),
4936
+ "aria-label": t("\u641C\u7D22\u7ECF\u9A8C"),
4289
4937
  value: expSearch,
4290
4938
  onChange: (e) => setExpSearch(e.target.value)
4291
4939
  }),
@@ -4294,36 +4942,36 @@ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
4294
4942
  "data-copied": copiedKey === "exp-search" ? "" : void 0,
4295
4943
  disabled: !expSearch,
4296
4944
  onClick: () => copyInstruction("exp-search", expInstruction),
4297
- title: "\u590D\u5236\u641C\u7D22\u6307\u4EE4"
4298
- }, copiedKey === "exp-search" ? "\u5DF2\u590D\u5236" : "\u641C\u7D22")
4945
+ title: t("\u590D\u5236\u641C\u7D22\u6307\u4EE4")
4946
+ }, copiedKey === "exp-search" ? t("\u5DF2\u590D\u5236") : t("\u641C\u7D22"))
4299
4947
  ),
4300
- resultRows.length > 0 ? React.createElement("div", {}, ...resultRows) : expEntries.length > 0 ? React.createElement("div", { className: "iterate-obs-empty" }, "\u6CA1\u6709\u5339\u914D\u300C" + expSearch + "\u300D\u7684\u7ECF\u9A8C") : React.createElement(
4948
+ resultRows.length > 0 ? React.createElement("div", {}, ...resultRows) : expEntries.length > 0 ? React.createElement("div", { className: "iterate-obs-empty" }, t("\u6CA1\u6709\u5339\u914D\u300C") + expSearch + t("\u300D\u7684\u7ECF\u9A8C")) : React.createElement(
4301
4949
  "div",
4302
4950
  {},
4303
4951
  // Gap #4: separate "pulled and empty" from "never pulled".
4304
- React.createElement("div", { className: "iterate-obs-empty" }, diskEmptyStateText(pulled, "\u7ECF\u9A8C\u94F6\u884C")),
4305
- React.createElement("div", { className: "iterate-obs-empty" }, "\u4E5F\u53EF\u70B9\u300C\u5217\u51FA\u7ECF\u9A8C\u300D\u67E5\u8BE2\u78C1\u76D8\u4E0A\u7684\u5168\u90E8\u5386\u53F2\u7ECF\u9A8C\u3002")
4952
+ React.createElement("div", { className: "iterate-obs-empty" }, diskEmptyStateText(pulled, t("\u7ECF\u9A8C\u94F6\u884C"), getLang())),
4953
+ React.createElement("div", { className: "iterate-obs-empty" }, t("\u4E5F\u53EF\u70B9\u300C\u5217\u51FA\u7ECF\u9A8C\u300D\u67E5\u8BE2\u78C1\u76D8\u4E0A\u7684\u5168\u90E8\u5386\u53F2\u7ECF\u9A8C\u3002"))
4306
4954
  )
4307
4955
  );
4308
4956
  };
4309
4957
  const renderDefenseEvents = () => {
4310
- const defenseInstruction = defenseFilter ? `\u8BF7\u8C03\u7528 \`iterate_defense_events\` \u67E5\u8BE2\u9632\u5FA1\u4E8B\u4EF6\uFF1A
4958
+ const defenseInstruction = defenseFilter ? tf(`\u8BF7\u8C03\u7528 \`iterate_defense_events\` \u67E5\u8BE2\u9632\u5FA1\u4E8B\u4EF6\uFF1A
4311
4959
 
4312
4960
  \`\`\`json
4313
- ${JSON.stringify({ operation: "list", type: defenseFilter || void 0 }, null, 2)}
4314
- \`\`\`` : buildDefenseEventsListInstruction();
4961
+ {a0}
4962
+ \`\`\``, { a0: JSON.stringify({ operation: "list", type: defenseFilter || void 0 }, null, 2) }) : buildDefenseEventsListInstruction();
4315
4963
  const typeOptions = [
4316
- { value: "", label: "\u5168\u90E8\u7C7B\u578B" },
4317
- { value: "precondition_failed", label: "\u524D\u7F6E\u6821\u9A8C\u5931\u8D25" },
4318
- { value: "rollback", label: "\u56DE\u6EDA" },
4319
- { value: "invariant_violated", label: "\u4E0D\u53D8\u91CF\u8FDD\u53CD" },
4320
- { value: "assumption_falsified", label: "\u5047\u8BBE\u88AB\u8BC1\u4F2A" }
4964
+ { value: "", label: t("\u5168\u90E8\u7C7B\u578B") },
4965
+ { value: "precondition_failed", label: t("\u524D\u7F6E\u6821\u9A8C\u5931\u8D25") },
4966
+ { value: "rollback", label: t("\u56DE\u6EDA") },
4967
+ { value: "invariant_violated", label: t("\u4E0D\u53D8\u91CF\u8FDD\u53CD") },
4968
+ { value: "assumption_falsified", label: t("\u5047\u8BBE\u88AB\u8BC1\u4F2A") }
4321
4969
  ];
4322
4970
  const TYPE_LABEL = {
4323
- precondition_failed: "\u524D\u7F6E\u6821\u9A8C\u5931\u8D25",
4324
- rollback: "\u56DE\u6EDA",
4325
- invariant_violated: "\u4E0D\u53D8\u91CF\u8FDD\u53CD",
4326
- assumption_falsified: "\u5047\u8BBE\u88AB\u8BC1\u4F2A"
4971
+ precondition_failed: t("\u524D\u7F6E\u6821\u9A8C\u5931\u8D25"),
4972
+ rollback: t("\u56DE\u6EDA"),
4973
+ invariant_violated: t("\u4E0D\u53D8\u91CF\u8FDD\u53CD"),
4974
+ assumption_falsified: t("\u5047\u8BBE\u88AB\u8BC1\u4F2A")
4327
4975
  };
4328
4976
  const counts = defenseEvents && defenseEvents.counts || {};
4329
4977
  const allEvents = defenseEvents && defenseEvents.events ? defenseEvents.events : [];
@@ -4332,42 +4980,42 @@ ${JSON.stringify({ operation: "list", type: defenseFilter || void 0 }, null, 2)}
4332
4980
  const headerBar = React.createElement(
4333
4981
  "div",
4334
4982
  { className: "iterate-obs-bar", style: { marginBottom: 8 } },
4335
- React.createElement("b", {}, "\u9632\u5FA1\u4E8B\u4EF6"),
4336
- React.createElement("span", { className: "iterate-obs-head-meta" }, `\u4F1A\u8BDD\u5185 ${allEvents.length} \u6761 / \u7D2F\u8BA1 ${totalCount}`),
4983
+ React.createElement("b", {}, t("\u9632\u5FA1\u4E8B\u4EF6")),
4984
+ React.createElement("span", { className: "iterate-obs-head-meta" }, tf(`\u4F1A\u8BDD\u5185 {a0} \u6761 / \u7D2F\u8BA1 {a1}`, { a0: allEvents.length, a1: totalCount })),
4337
4985
  React.createElement("button", {
4338
4986
  className: "iterate-btn",
4339
4987
  "data-primary": "",
4340
4988
  "data-copied": copiedKey === "defense-list" ? "" : void 0,
4341
4989
  onClick: () => copyInstruction("defense-list", buildDefenseEventsListInstruction()),
4342
- title: "\u590D\u5236\u5217\u51FA\u9632\u5FA1\u4E8B\u4EF6\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09"
4343
- }, copiedKey === "defense-list" ? "\u5DF2\u590D\u5236" : "\u5217\u51FA\u4E8B\u4EF6"),
4990
+ title: t("\u590D\u5236\u5217\u51FA\u9632\u5FA1\u4E8B\u4EF6\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09")
4991
+ }, copiedKey === "defense-list" ? t("\u5DF2\u590D\u5236") : t("\u5217\u51FA\u4E8B\u4EF6")),
4344
4992
  React.createElement("button", {
4345
4993
  className: "iterate-btn",
4346
4994
  "data-copied": copiedKey === "defense-snapshot" ? "" : void 0,
4347
4995
  onClick: () => copyInstruction("defense-snapshot", pullInstruction),
4348
- title: "\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u628A .iterate/ \u4E0B\u7684\u9632\u5FA1\u4E8B\u4EF6\u6D41\u56DE\u663E\u8FDB\u4F1A\u8BDD"
4349
- }, copiedKey === "defense-snapshot" ? "\u5DF2\u590D\u5236" : "\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167"),
4996
+ title: t("\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u628A .iterate/ \u4E0B\u7684\u9632\u5FA1\u4E8B\u4EF6\u6D41\u56DE\u663E\u8FDB\u4F1A\u8BDD")
4997
+ }, copiedKey === "defense-snapshot" ? t("\u5DF2\u590D\u5236") : t("\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167")),
4350
4998
  React.createElement("button", {
4351
4999
  className: "iterate-btn",
4352
5000
  "data-copied": copiedKey === "defense-counts" ? "" : void 0,
4353
- onClick: () => copyInstruction("defense-counts", `\u8BF7\u8C03\u7528 \`iterate_defense_events\` \u67E5\u8BE2\u4E8B\u4EF6\u7EDF\u8BA1\uFF1A
5001
+ onClick: () => copyInstruction("defense-counts", tf(`\u8BF7\u8C03\u7528 \`iterate_defense_events\` \u67E5\u8BE2\u4E8B\u4EF6\u7EDF\u8BA1\uFF1A
4354
5002
 
4355
5003
  \`\`\`json
4356
- ${JSON.stringify({ operation: "counts" }, null, 2)}
4357
- \`\`\``),
4358
- title: "\u590D\u5236\u7EDF\u8BA1\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09"
4359
- }, copiedKey === "defense-counts" ? "\u5DF2\u590D\u5236" : "\u7EDF\u8BA1"),
5004
+ {a0}
5005
+ \`\`\``, { a0: JSON.stringify({ operation: "counts" }, null, 2) })),
5006
+ title: t("\u590D\u5236\u7EDF\u8BA1\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09")
5007
+ }, copiedKey === "defense-counts" ? t("\u5DF2\u590D\u5236") : t("\u7EDF\u8BA1")),
4360
5008
  React.createElement("button", {
4361
5009
  className: "iterate-btn",
4362
5010
  "data-danger": "",
4363
5011
  "data-copied": copiedKey === "defense-clear" ? "" : void 0,
4364
- onClick: () => copyInstruction("defense-clear", `\u8BF7\u8C03\u7528 \`iterate_defense_events\` \u6E05\u9664\u9632\u5FA1\u4E8B\u4EF6\u6D41\uFF1A
5012
+ onClick: () => copyInstruction("defense-clear", tf(`\u8BF7\u8C03\u7528 \`iterate_defense_events\` \u6E05\u9664\u9632\u5FA1\u4E8B\u4EF6\u6D41\uFF1A
4365
5013
 
4366
5014
  \`\`\`json
4367
- ${JSON.stringify({ operation: "clear" }, null, 2)}
4368
- \`\`\``),
4369
- title: "\u590D\u5236 iterate_defense_events clear \u6307\u4EE4\u6587\u672C\uFF08\u91CD\u7F6E\u9648\u65E7\u4E8B\u4EF6\u6D41\uFF09"
4370
- }, copiedKey === "defense-clear" ? "\u5DF2\u590D\u5236" : "\u6E05\u9664\u4E8B\u4EF6")
5015
+ {a0}
5016
+ \`\`\``, { a0: JSON.stringify({ operation: "clear" }, null, 2) })),
5017
+ title: t("\u590D\u5236 iterate_defense_events clear \u6307\u4EE4\u6587\u672C\uFF08\u91CD\u7F6E\u9648\u65E7\u4E8B\u4EF6\u6D41\uFF09")
5018
+ }, copiedKey === "defense-clear" ? t("\u5DF2\u590D\u5236") : t("\u6E05\u9664\u4E8B\u4EF6"))
4371
5019
  );
4372
5020
  if (allEvents.length === 0 && totalCount === 0) {
4373
5021
  return React.createElement(
@@ -4380,7 +5028,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4380
5028
  React.createElement(
4381
5029
  "div",
4382
5030
  { className: "iterate-obs-block-head" },
4383
- React.createElement("span", {}, "\u9632\u5FA1\u4E8B\u4EF6\u6D41"),
5031
+ React.createElement("span", {}, t("\u9632\u5FA1\u4E8B\u4EF6\u6D41")),
4384
5032
  React.createElement("span", { className: "iterate-obs-badge" }, "v3.0")
4385
5033
  ),
4386
5034
  React.createElement(
@@ -4389,12 +5037,12 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4389
5037
  React.createElement(
4390
5038
  "div",
4391
5039
  { className: "iterate-obs-msg" },
4392
- diskEmptyStateText(toolCalledInSession(session, "iterate_defense_events"), "\u9632\u5FA1\u4E8B\u4EF6\u6D41")
5040
+ diskEmptyStateText(toolCalledInSession(session, "iterate_defense_events"), t("\u9632\u5FA1\u4E8B\u4EF6\u6D41"), getLang())
4393
5041
  ),
4394
5042
  React.createElement(
4395
5043
  "div",
4396
5044
  { className: "iterate-obs-msg", style: { marginTop: 6 } },
4397
- "\u9632\u5FA1\u4E8B\u4EF6\u5305\u62EC\uFF1A\u524D\u7F6E\u6821\u9A8C\u5931\u8D25\u3001\u56DE\u6EDA\u3001\u4E0D\u53D8\u91CF\u8FDD\u53CD\u3001\u5047\u8BBE\u88AB\u8BC1\u4F2A\u3002"
5045
+ t("\u9632\u5FA1\u4E8B\u4EF6\u5305\u62EC\uFF1A\u524D\u7F6E\u6821\u9A8C\u5931\u8D25\u3001\u56DE\u6EDA\u3001\u4E0D\u53D8\u91CF\u8FDD\u53CD\u3001\u5047\u8BBE\u88AB\u8BC1\u4F2A\u3002")
4398
5046
  ),
4399
5047
  React.createElement(
4400
5048
  "div",
@@ -4403,8 +5051,8 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4403
5051
  className: "iterate-btn",
4404
5052
  "data-copied": copiedKey === "defense-instr" ? "" : void 0,
4405
5053
  onClick: () => copyInstruction("defense-instr", defenseInstruction),
4406
- title: "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4"
4407
- }, copiedKey === "defense-instr" ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4")
5054
+ title: t("\u590D\u5236\u67E5\u8BE2\u6307\u4EE4")
5055
+ }, copiedKey === "defense-instr" ? t("\u5DF2\u590D\u5236") : t("\u590D\u5236\u67E5\u8BE2\u6307\u4EE4"))
4408
5056
  )
4409
5057
  )
4410
5058
  )
@@ -4418,7 +5066,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4418
5066
  {
4419
5067
  className: "iterate-filter-select",
4420
5068
  value: defenseFilter,
4421
- "aria-label": "\u6309\u4E8B\u4EF6\u7C7B\u578B\u7B5B\u9009",
5069
+ "aria-label": t("\u6309\u4E8B\u4EF6\u7C7B\u578B\u7B5B\u9009"),
4422
5070
  onChange: (e) => setDefenseFilter(e.target.value)
4423
5071
  },
4424
5072
  ...typeOptions.map((opt) => React.createElement("option", { key: opt.value, value: opt.value }, opt.label))
@@ -4426,8 +5074,8 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4426
5074
  defenseFilter ? React.createElement("button", {
4427
5075
  className: "iterate-filter-clear",
4428
5076
  onClick: () => setDefenseFilter(""),
4429
- title: "\u6E05\u9664\u7C7B\u578B\u7B5B\u9009"
4430
- }, "\u6E05\u9664") : null
5077
+ title: t("\u6E05\u9664\u7C7B\u578B\u7B5B\u9009")
5078
+ }, t("\u6E05\u9664")) : null
4431
5079
  );
4432
5080
  let eventRows = [];
4433
5081
  if (visibleEvents.length > 0) {
@@ -4448,7 +5096,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4448
5096
  "div",
4449
5097
  { className: "iterate-obs-block-body" },
4450
5098
  React.createElement("div", { className: "iterate-obs-msg" }, String(e.description || "")),
4451
- e.defense ? React.createElement("div", { className: "iterate-obs-msg", style: { marginTop: 4 } }, `\u9632\u7EBF\uFF1A${String(e.defense)}`) : null
5099
+ e.defense ? React.createElement("div", { className: "iterate-obs-msg", style: { marginTop: 4 } }, tf(`\u9632\u7EBF\uFF1A{a0}`, { a0: String(e.defense) })) : null
4452
5100
  )
4453
5101
  );
4454
5102
  });
@@ -4469,7 +5117,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4469
5117
  )
4470
5118
  )
4471
5119
  ),
4472
- eventRows.length > 0 ? React.createElement("div", {}, ...eventRows) : React.createElement("div", { className: "iterate-obs-empty" }, "\u6CA1\u6709\u5339\u914D\u8BE5\u7C7B\u578B\u7684\u9632\u5FA1\u4E8B\u4EF6")
5120
+ eventRows.length > 0 ? React.createElement("div", {}, ...eventRows) : React.createElement("div", { className: "iterate-obs-empty" }, t("\u6CA1\u6709\u5339\u914D\u8BE5\u7C7B\u578B\u7684\u9632\u5FA1\u4E8B\u4EF6"))
4473
5121
  );
4474
5122
  };
4475
5123
  const renderLive = () => {
@@ -4481,29 +5129,31 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4481
5129
  return React.createElement(
4482
5130
  "div",
4483
5131
  { className: "iterate-obs-empty" },
4484
- React.createElement("div", {}, "\u5B9E\u65F6\u6D3B\u52A8\u6D41\u4EC5\u5728\u8FD0\u884C\u671F\u95F4\u8BB0\u5F55\uFF0C\u5F53\u524D\u5DF2\u5B8C\u6210\u3002"),
5132
+ React.createElement("div", {}, t("\u5B9E\u65F6\u6D3B\u52A8\u6D41\u4EC5\u5728\u8FD0\u884C\u671F\u95F4\u8BB0\u5F55\uFF0C\u5F53\u524D\u5DF2\u5B8C\u6210\u3002")),
4485
5133
  React.createElement(
4486
5134
  "div",
4487
5135
  { className: "iterate-obs-bar", style: { marginTop: 8 } },
4488
- React.createElement("button", { className: "iterate-btn", onClick: () => setTab("f1") }, "\u67E5\u770B\u5BA1\u67E5\u7EBF\u7A0B"),
4489
- React.createElement("button", { className: "iterate-btn", onClick: () => setTab("f3") }, "\u67E5\u770B\u53D1\u73B0")
5136
+ React.createElement("button", { className: "iterate-btn", onClick: () => setTab("f1") }, t("\u67E5\u770B\u5BA1\u67E5\u7EBF\u7A0B")),
5137
+ React.createElement("button", { className: "iterate-btn", onClick: () => setTab("f3") }, t("\u67E5\u770B\u53D1\u73B0"))
4490
5138
  )
4491
5139
  );
4492
5140
  }
4493
- return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u5B9E\u65F6\u6D3B\u52A8");
5141
+ return React.createElement("div", { className: "iterate-obs-empty" }, t("\u6682\u65E0\u5B9E\u65F6\u6D3B\u52A8"));
4494
5142
  }
4495
5143
  const types = Array.from(new Set(liveEntries.map((e) => String(e.type || "unknown")).filter(Boolean))).sort();
4496
5144
  const filtered = filterLiveEntries(liveEntries, liveType);
4497
5145
  const rows = filtered.map((e, i) => {
4498
5146
  const type = String(e.type || "unknown");
4499
- const meta = OBS_LIVE_META[type] || { label: String(e.type || "?"), color: OBS_LIVE_FALLBACK_COLOR };
5147
+ const meta = OBS_LIVE_META[type];
5148
+ const typeLabel = meta ? meta.label[getLang()] : String(e.type || "?");
5149
+ const typeColor = meta ? meta.color : OBS_LIVE_FALLBACK_COLOR;
4500
5150
  const tool = String(e.tool || "");
4501
5151
  const target = String(e.target || "");
4502
5152
  const time = formatObsLiveTime(String(e.ts || ""));
4503
5153
  return React.createElement(
4504
5154
  "div",
4505
5155
  { key: `live-${i}`, className: "iterate-obs-row" },
4506
- React.createElement("span", { className: "iterate-obs-livebadge", style: { color: meta.color } }, meta.label),
5156
+ React.createElement("span", { className: "iterate-obs-livebadge", style: { color: typeColor } }, typeLabel),
4507
5157
  tool ? React.createElement("span", { className: "iterate-obs-chip" }, tool) : null,
4508
5158
  React.createElement("span", { className: "iterate-obs-msg" }, target || "\u2014"),
4509
5159
  React.createElement("span", { className: "iterate-obs-head-meta" }, time)
@@ -4512,23 +5162,23 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4512
5162
  const filterBar = React.createElement(
4513
5163
  "div",
4514
5164
  { className: "iterate-obs-bar", style: { marginBottom: 8 } },
4515
- React.createElement("b", {}, "\u5B9E\u65F6\u6D3B\u52A8"),
5165
+ React.createElement("b", {}, t("\u5B9E\u65F6\u6D3B\u52A8")),
4516
5166
  React.createElement(
4517
5167
  "select",
4518
5168
  {
4519
5169
  className: "iterate-filter-select",
4520
5170
  value: liveType,
4521
- "aria-label": "\u6309\u6D3B\u52A8\u7C7B\u578B\u7B5B\u9009",
5171
+ "aria-label": t("\u6309\u6D3B\u52A8\u7C7B\u578B\u7B5B\u9009"),
4522
5172
  onChange: (e) => setLiveType(e.target.value)
4523
5173
  },
4524
- React.createElement("option", { value: "" }, "\u5168\u90E8\u6D3B\u52A8"),
4525
- ...types.map((t) => React.createElement("option", { key: t, value: t }, t))
5174
+ React.createElement("option", { value: "" }, t("\u5168\u90E8\u6D3B\u52A8")),
5175
+ ...types.map((t2) => React.createElement("option", { key: t2, value: t2 }, t2))
4526
5176
  ),
4527
5177
  liveType ? React.createElement("button", {
4528
5178
  className: "iterate-filter-clear",
4529
5179
  onClick: () => setLiveType(""),
4530
- title: "\u6E05\u9664\u7C7B\u578B\u7B5B\u9009"
4531
- }, "\u6E05\u9664") : null,
5180
+ title: t("\u6E05\u9664\u7C7B\u578B\u7B5B\u9009")
5181
+ }, t("\u6E05\u9664")) : null,
4532
5182
  React.createElement("span", { className: "iterate-filter-count" }, `${filtered.length}/${liveEntries.length}`)
4533
5183
  );
4534
5184
  if (rows.length === 0) {
@@ -4536,7 +5186,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4536
5186
  "div",
4537
5187
  {},
4538
5188
  filterBar,
4539
- React.createElement("div", { className: "iterate-obs-empty" }, "\u65E0\u5339\u914D\u7684\u5B9E\u65F6\u6D3B\u52A8")
5189
+ React.createElement("div", { className: "iterate-obs-empty" }, t("\u65E0\u5339\u914D\u7684\u5B9E\u65F6\u6D3B\u52A8"))
4540
5190
  );
4541
5191
  }
4542
5192
  return React.createElement("div", {}, filterBar, ...rows);
@@ -4576,14 +5226,14 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4576
5226
  React.createElement(
4577
5227
  "div",
4578
5228
  { className: "iterate-obs-head", "data-closed": open ? void 0 : "", onClick: () => setOpen((v) => !v) },
4579
- React.createElement("span", { className: "iterate-obs-title" }, "iterate \u89C2\u6D4B\u53F0"),
5229
+ React.createElement("span", { className: "iterate-obs-title" }, t("iterate \u89C2\u6D4B\u53F0")),
4580
5230
  React.createElement("span", { className: "iterate-obs-badge", "data-live": live ? "" : void 0 }, runStatusText(live, manifest)),
4581
5231
  React.createElement("span", { className: "iterate-obs-head-meta" }, headMeta || "runtime"),
4582
5232
  // Gap #14: make the tab system discoverable without expanding it.
4583
5233
  React.createElement("span", {
4584
5234
  className: "iterate-obs-head-meta",
4585
- title: OBS_HOTKEY_HINT
4586
- }, OBS_HOTKEY_HINT),
5235
+ title: obsHotkeyHint()
5236
+ }, obsHotkeyHint()),
4587
5237
  React.createElement("button", {
4588
5238
  className: "iterate-btn",
4589
5239
  "data-ghost": "",
@@ -4591,7 +5241,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4591
5241
  e.stopPropagation();
4592
5242
  setOpen((v) => !v);
4593
5243
  }
4594
- }, open ? "\u6536\u8D77" : "\u5C55\u5F00")
5244
+ }, open ? t("\u6536\u8D77") : t("\u5C55\u5F00"))
4595
5245
  ),
4596
5246
  open ? React.createElement(
4597
5247
  "div",
@@ -4602,14 +5252,14 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4602
5252
  React.createElement(
4603
5253
  "div",
4604
5254
  { className: "iterate-obs-block-head" },
4605
- React.createElement("b", {}, "\u590D\u5236\u672A\u6210\u529F"),
4606
- React.createElement("span", { className: "iterate-obs-head-meta" }, "\u526A\u8D34\u677F\u5199\u5165\u88AB\u62D2\u7EDD\uFF0C\u8BF7\u624B\u52A8\u9009\u4E2D\u4E0B\u65B9\u6307\u4EE4\u590D\u5236"),
5255
+ React.createElement("b", {}, t("\u590D\u5236\u672A\u6210\u529F")),
5256
+ React.createElement("span", { className: "iterate-obs-head-meta" }, t("\u526A\u8D34\u677F\u5199\u5165\u88AB\u62D2\u7EDD\uFF0C\u8BF7\u624B\u52A8\u9009\u4E2D\u4E0B\u65B9\u6307\u4EE4\u590D\u5236")),
4607
5257
  React.createElement("button", {
4608
5258
  className: "iterate-btn",
4609
5259
  "data-ghost": "",
4610
- "aria-label": "\u5173\u95ED\u590D\u5236\u5931\u8D25\u63D0\u793A",
5260
+ "aria-label": t("\u5173\u95ED\u590D\u5236\u5931\u8D25\u63D0\u793A"),
4611
5261
  onClick: () => setCopyFailText(null)
4612
- }, "\u5173\u95ED")
5262
+ }, t("\u5173\u95ED"))
4613
5263
  ),
4614
5264
  React.createElement("div", { className: "iterate-obs-code" }, copyFailText)
4615
5265
  ) : null,
@@ -4626,8 +5276,8 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4626
5276
  "data-primary": "",
4627
5277
  "data-copied": copiedKey === "export" ? "" : void 0,
4628
5278
  onClick: exportObservatory,
4629
- title: "\u5C06\u89C2\u6D4B\u53F0\u5168\u90E8\u6570\u636E\u5BFC\u51FA\u4E3A JSON\uFF08\u4F18\u5148\u4E0B\u8F7D\uFF0C\u5931\u8D25\u5219\u590D\u5236\uFF09"
4630
- }, copiedKey === "export" ? "\u5DF2\u5BFC\u51FA" : "\u5BFC\u51FA JSON"),
5279
+ title: t("\u5C06\u89C2\u6D4B\u53F0\u5168\u90E8\u6570\u636E\u5BFC\u51FA\u4E3A JSON\uFF08\u4F18\u5148\u4E0B\u8F7D\uFF0C\u5931\u8D25\u5219\u590D\u5236\uFF09")
5280
+ }, copiedKey === "export" ? t("\u5DF2\u5BFC\u51FA") : t("\u5BFC\u51FA JSON")),
4631
5281
  // Gap #4: `.iterate/` artifacts are unreachable from the browser;
4632
5282
  // this copies the one instruction that makes the model echo them
4633
5283
  // into the session stream (which is what every panel scans).
@@ -4636,21 +5286,21 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4636
5286
  "data-copied": copiedKey === "pull-snapshot" ? "" : void 0,
4637
5287
  onClick: () => copyInstruction("pull-snapshot", pullInstruction),
4638
5288
  title: pullTitle
4639
- }, copiedKey === "pull-snapshot" ? "\u5DF2\u590D\u5236" : "\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167")
5289
+ }, copiedKey === "pull-snapshot" ? t("\u5DF2\u590D\u5236") : t("\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167"))
4640
5290
  ),
4641
5291
  React.createElement(
4642
5292
  "div",
4643
- { className: "iterate-obs-tabs", role: "tablist", "aria-label": "\u89C2\u6D4B\u53F0\u9875\u7B7E\uFF08\u5FEB\u6377\u952E 1-9 / 0 / -\uFF09" },
5293
+ { className: "iterate-obs-tabs", role: "tablist", "aria-label": t("\u89C2\u6D4B\u53F0\u9875\u7B7E\uFF08\u5FEB\u6377\u952E 1-9 / 0 / -\uFF09") },
4644
5294
  ...OBS_TABS.map(
4645
- (t) => React.createElement("button", {
4646
- key: t.key,
5295
+ (t2) => React.createElement("button", {
5296
+ key: t2.key,
4647
5297
  className: "iterate-obs-tab",
4648
5298
  role: "tab",
4649
- "aria-selected": tab === t.key,
4650
- "data-active": tab === t.key ? "" : void 0,
4651
- onClick: () => setTab(t.key),
4652
- title: t.hotkey ? `${obsTabLabel(t)}\uFF08\u6309 ${t.hotkey} \u5207\u6362\uFF09` : `${obsTabLabel(t)}\uFF08\u6309 - \u5207\u6362\uFF09`
4653
- }, obsTabLabel(t))
5299
+ "aria-selected": tab === t2.key,
5300
+ "data-active": tab === t2.key ? "" : void 0,
5301
+ onClick: () => setTab(t2.key),
5302
+ title: t2.hotkey ? tf(`{a0}\uFF08\u6309 {a1} \u5207\u6362\uFF09`, { a0: obsTabLabel(t2), a1: t2.hotkey }) : tf(`{a0}\uFF08\u6309 - \u5207\u6362\uFF09`, { a0: obsTabLabel(t2) })
5303
+ }, obsTabLabel(t2))
4654
5304
  )
4655
5305
  ),
4656
5306
  React.createElement("div", { className: "iterate-obs-body" }, renderBody())
@@ -4663,11 +5313,21 @@ function selectTurnTail(owner) {
4663
5313
  if (scanSessionForRunSummary(owner.turn)) return { matched: true };
4664
5314
  return null;
4665
5315
  }
5316
+ var LangDashboard = withLang(ConvergenceDashboard);
5317
+ var LangObservatory = withLang(ObservatoryPanel);
5318
+ var LangTurnTail = withLang(TurnTailEntry);
5319
+ var LangProgress = withLang(ProgressCapsule);
5320
+ var LangSettings = withLang(SettingsPanel);
4666
5321
  function apply(ctx) {
4667
5322
  slotsSvc = readSlots(ctx);
4668
5323
  themeSvc = readTheme(ctx);
4669
- storage = createStorage();
4670
- const savedTheme = storage.get(THEME_STORAGE_KEY);
5324
+ storage2 = createStorage();
5325
+ configureLangStorage(storage2);
5326
+ setLang(
5327
+ detectLang(storage2.get(LANG_STORAGE_KEY), typeof navigator !== "undefined" ? navigator.language : ""),
5328
+ false
5329
+ );
5330
+ const savedTheme = storage2.get(THEME_STORAGE_KEY);
4671
5331
  themeEnabled = savedTheme === null ? true : savedTheme === "1";
4672
5332
  if (themeEnabled) applyThemeSkin();
4673
5333
  if (typeof document !== "undefined" && document.createElement && document.head) {
@@ -4697,35 +5357,35 @@ function apply(ctx) {
4697
5357
  "conversation.input.dock",
4698
5358
  () => slotsSvc?.register(
4699
5359
  { name: "conversation.input.dock", id: "iterate-dashboard", order: 90 },
4700
- (props) => React.createElement(ConvergenceDashboard, props)
5360
+ (props) => React.createElement(LangDashboard, props)
4701
5361
  )
4702
5362
  );
4703
5363
  slotsSvc.inject(
4704
5364
  "conversation.input.dock",
4705
5365
  () => slotsSvc?.register(
4706
5366
  { name: "conversation.input.dock", id: "iterate-observatory", order: 91 },
4707
- (props) => React.createElement(ObservatoryPanel, props)
5367
+ (props) => React.createElement(LangObservatory, props)
4708
5368
  )
4709
5369
  );
4710
5370
  slotsSvc.inject(
4711
5371
  "conversation.chat.turnTail",
4712
5372
  () => slotsSvc?.register(
4713
5373
  { name: "conversation.chat.turnTail", id: "iterate-turn-tail", select: selectTurnTail },
4714
- (props) => React.createElement(TurnTailEntry, props)
5374
+ (props) => React.createElement(LangTurnTail, props)
4715
5375
  )
4716
5376
  );
4717
5377
  slotsSvc.inject(
4718
5378
  "shell.overlay",
4719
5379
  () => slotsSvc?.register(
4720
5380
  { name: "shell.overlay", id: "iterate-progress", order: 0 },
4721
- (props) => React.createElement(ProgressCapsule, props)
5381
+ (props) => React.createElement(LangProgress, props)
4722
5382
  )
4723
5383
  );
4724
5384
  slotsSvc.inject(
4725
5385
  "settings.section",
4726
5386
  () => slotsSvc?.register(
4727
5387
  { name: "settings.section", id: "iterate-settings", order: 30, label: () => "iterate" },
4728
- (props) => React.createElement(SettingsPanel, props)
5388
+ (props) => React.createElement(LangSettings, props)
4729
5389
  )
4730
5390
  );
4731
5391
  }