iterate-plugin 3.6.0 → 3.7.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: 7da1ef5af84458a763f4b0ad8cd75ba2de3fb16b9831e00c0a9767bc16fece65 */
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,29 @@ 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
+ if (lang === "en") return en[reason] ?? (reason ? `Done \xB7 ${reason}` : "Done");
1543
+ return zh[reason] ?? (reason ? `\u5DF2\u7ED3\u675F \xB7 ${reason}` : "\u5DF2\u7ED3\u675F");
1527
1544
  }
1528
- function buildCompletionSummary(report) {
1545
+ function buildCompletionSummary(report, lang = "zh") {
1529
1546
  const conv = (
1530
1547
  /** @type {Record<string, unknown>} */
1531
1548
  report.convergence ?? {}
@@ -1534,33 +1551,74 @@ function buildCompletionSummary(report) {
1534
1551
  const total = getTotalRounds(report);
1535
1552
  const stats = severityStats(report);
1536
1553
  const converged = conv.converged === true;
1537
- const reason = converged ? "\u5DF2\u6536\u655B" : `\u5DF2\u8FBE\u6700\u5927\u8F6E\u6570 ${total}`;
1538
1554
  const totalFindings = stats.critical + stats.high + stats.medium + stats.low;
1555
+ if (lang === "en") {
1556
+ const reason2 = converged ? "converged" : `reached the round limit ${total}`;
1557
+ return `iterate review complete \xB7 ${rounds}/${total} rounds \xB7 ${totalFindings} findings \xB7 ${reason2}`;
1558
+ }
1559
+ const reason = converged ? "\u5DF2\u6536\u655B" : `\u5DF2\u8FBE\u6700\u5927\u8F6E\u6570 ${total}`;
1539
1560
  return `iterate \u8BC4\u5BA1\u5B8C\u6210 \xB7 ${rounds}/${total} \u8F6E \xB7 ${totalFindings} \u9879\u53D1\u73B0 \xB7 ${reason}`;
1540
1561
  }
1541
1562
  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' },
1563
+ {
1564
+ key: "goal",
1565
+ label: { zh: "\u76EE\u6807", en: "Goal" },
1566
+ 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)" }
1567
+ },
1568
+ {
1569
+ key: "dimensions",
1570
+ label: { zh: "\u5BA1\u67E5\u7EF4\u5EA6", en: "Review dimensions" },
1571
+ hint: { zh: '\u6570\u7EC4\uFF0C\u5982 ["correctness","security"]', en: 'Array, e.g. ["correctness","security"]' }
1572
+ },
1573
+ {
1574
+ key: "max_rounds",
1575
+ label: { zh: "\u6700\u5927\u8F6E\u6570", en: "Max rounds" },
1576
+ hint: { zh: "\u6B63\u6574\u6570", en: "Positive integer" }
1577
+ },
1578
+ {
1579
+ key: "review.scope",
1580
+ label: { zh: "\u5BA1\u67E5\u8303\u56F4", en: "Review scope" },
1581
+ hint: { zh: '"full" \u6216 "changed-only"', en: '"full" or "changed-only"' }
1582
+ },
1583
+ {
1584
+ key: "reasoning_effort",
1585
+ label: { zh: "\u5BA1\u67E5\u63A8\u7406\u5F3A\u5EA6", en: "Review reasoning effort" },
1586
+ hint: { zh: '"low" / "medium" / "high"\uFF0C\u7F3A\u7701\u8DDF\u968F\u6A21\u578B\u9ED8\u8BA4', en: '"low" / "medium" / "high"; omitted = follow the model default' }
1587
+ },
1547
1588
  {
1548
1589
  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'
1590
+ label: { zh: "\u9A8C\u8BC1\u547D\u4EE4", en: "Validation commands" },
1591
+ hint: {
1592
+ 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',
1593
+ 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)'
1594
+ }
1551
1595
  },
1552
1596
  {
1553
1597
  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'
1598
+ label: { zh: "\u754C\u9762\u8BED\u8A00", en: "Interface language" },
1599
+ hint: {
1600
+ 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',
1601
+ en: '"zh" or "en" (default "en"): language of human-facing copy such as approval rationales and defense-event labels'
1602
+ }
1556
1603
  },
1557
1604
  {
1558
1605
  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"
1606
+ label: { zh: "\u5DF2\u77E5\u6709\u610F\u95EE\u9898", en: "Known intentional issues" },
1607
+ hint: {
1608
+ 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",
1609
+ 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"`
1610
+ }
1611
+ },
1612
+ {
1613
+ key: "atomic.max_lines",
1614
+ label: { zh: "\u539F\u5B50\u4FEE\u590D\u4E0A\u9650\u884C\u6570", en: "Atomic fix line cap" },
1615
+ hint: { zh: "\u6B63\u6574\u6570", en: "Positive integer" }
1561
1616
  },
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" }
1617
+ {
1618
+ key: "git.push_per_round",
1619
+ label: { zh: "\u6BCF\u8F6E\u63A8\u9001", en: "Push per round" },
1620
+ hint: { zh: "true / false", en: "true / false" }
1621
+ }
1564
1622
  ];
1565
1623
  function buildConfigEditGuide() {
1566
1624
  const lines = [
@@ -1573,7 +1631,7 @@ function buildConfigEditGuide() {
1573
1631
  " \u8BF7\u5148\u7ED9 validation.commands \u914D\u4E0A\u81F3\u5C11\u4E00\u6761\u771F\u5B9E\u547D\u4EE4\uFF08\u5982 npm test\uFF09\u3002",
1574
1632
  "",
1575
1633
  "\u53EF\u7F16\u8F91\u5B57\u6BB5\uFF1A",
1576
- ...CONFIG_EDIT_FIELDS.map((f) => `- ${f.key}\uFF08${f.label}\uFF09\uFF1A${f.hint}`),
1634
+ ...CONFIG_EDIT_FIELDS.map((f) => `- ${f.key}\uFF08${f.label.zh}\uFF09\uFF1A${f.hint.zh}`),
1577
1635
  "",
1578
1636
  "\u8BA9\u6A21\u578B\u5E2E\u4F60\u6539\uFF1A",
1579
1637
  '1. \u8C03\u7528 iterate_config({ operation: "read" }) \u67E5\u770B\u5F53\u524D\u914D\u7F6E\uFF1B',
@@ -1599,8 +1657,8 @@ function configFieldByKey(key) {
1599
1657
  }
1600
1658
  function buildConfigFieldInstruction(key, value) {
1601
1659
  const field = configFieldByKey(key);
1602
- const label = field ? field.label : key;
1603
- const hint = field ? field.hint : "\u89C1 iterate.config.yaml";
1660
+ const label = field ? field.label.zh : key;
1661
+ const hint = field ? field.hint.zh : "\u89C1 iterate.config.yaml";
1604
1662
  const updates = { [key]: value === void 0 ? CONFIG_VALUE_PLACEHOLDER : value };
1605
1663
  const payload = buildConfigEditInstruction(updates);
1606
1664
  return [
@@ -1612,6 +1670,71 @@ function buildConfigFieldInstruction(key, value) {
1612
1670
  '\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
1671
  ].join("\n");
1614
1672
  }
1673
+ function buildConfigReadInstruction() {
1674
+ 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
1675
+
1676
+ \`\`\`json
1677
+ ${JSON.stringify({ operation: "read" }, null, 2)}
1678
+ \`\`\``;
1679
+ }
1680
+ var CONFIG_TOP_LEVEL_KEYS = [...new Set(CONFIG_EDIT_FIELDS.map((f) => f.key.split(".")[0]))];
1681
+ function isEffectiveConfig(v) {
1682
+ if (!v || typeof v !== "object" || Array.isArray(v)) return false;
1683
+ const rec = (
1684
+ /** @type {Record<string, unknown>} */
1685
+ v
1686
+ );
1687
+ let hits = 0;
1688
+ for (const key of CONFIG_TOP_LEVEL_KEYS) {
1689
+ if (Object.hasOwn(rec, key)) hits += 1;
1690
+ if (hits >= 3) return true;
1691
+ }
1692
+ return false;
1693
+ }
1694
+ function findConfigInResult(v) {
1695
+ if (!v || typeof v !== "object" || Array.isArray(v)) return null;
1696
+ const rec = (
1697
+ /** @type {Record<string, unknown>} */
1698
+ v
1699
+ );
1700
+ const cfg = safeGet(rec, "config");
1701
+ if (isEffectiveConfig(cfg)) return (
1702
+ /** @type {Record<string, unknown>} */
1703
+ cfg
1704
+ );
1705
+ return isEffectiveConfig(v) ? rec : null;
1706
+ }
1707
+ function scanSessionForConfigRead(session) {
1708
+ const node = latestToolResultNode(session, "iterate_config", findConfigInResult);
1709
+ if (node !== null) {
1710
+ const cfg = findConfigInResult(node);
1711
+ if (cfg) return cfg;
1712
+ }
1713
+ const loose = findFirstInObject(session, isEffectiveConfig);
1714
+ return loose ? (
1715
+ /** @type {Record<string, unknown>} */
1716
+ loose
1717
+ ) : null;
1718
+ }
1719
+ function configValueByPath(config, key) {
1720
+ if (!config || typeof config !== "object" || typeof key !== "string" || !key) {
1721
+ return { resolved: false, value: void 0 };
1722
+ }
1723
+ let node = (
1724
+ /** @type {unknown} */
1725
+ config
1726
+ );
1727
+ for (const part of key.split(".")) {
1728
+ if (!node || typeof node !== "object" || Array.isArray(node)) return { resolved: false, value: void 0 };
1729
+ const rec = (
1730
+ /** @type {Record<string, unknown>} */
1731
+ node
1732
+ );
1733
+ if (!Object.hasOwn(rec, part)) return { resolved: false, value: void 0 };
1734
+ node = rec[part];
1735
+ }
1736
+ return { resolved: true, value: node };
1737
+ }
1615
1738
  var VERDICT_SHORTCUTS = {
1616
1739
  y: "keep",
1617
1740
  Y: "keep",
@@ -1668,9 +1791,9 @@ function buildRuntimeStatusGuide() {
1668
1791
  }
1669
1792
  function filterLiveEntries(entries, type) {
1670
1793
  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);
1794
+ const t2 = typeof type === "string" ? type.trim() : "";
1795
+ if (!t2) return list.slice();
1796
+ return list.filter((e) => e && typeof e === "object" && String(e.type ?? "") === t2);
1674
1797
  }
1675
1798
  function filterTimelineEntries(entries, opts) {
1676
1799
  const list = Array.isArray(entries) ? entries : [];
@@ -1678,18 +1801,18 @@ function filterTimelineEntries(entries, opts) {
1678
1801
  const type = typeof o.type === "string" ? o.type : "";
1679
1802
  const round = typeof o.round === "string" ? o.round : "";
1680
1803
  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;
1804
+ const filtered = list.filter((t2) => {
1805
+ if (!t2 || typeof t2 !== "object") return false;
1806
+ if (type && String(t2.type ?? "") !== type) return false;
1807
+ if (round && String(t2.round ?? "") !== round) return false;
1685
1808
  if (q) {
1686
1809
  let dataText = "";
1687
1810
  try {
1688
- dataText = JSON.stringify(t.data ?? {});
1811
+ dataText = JSON.stringify(t2.data ?? {});
1689
1812
  } catch {
1690
- dataText = t.data === void 0 ? "{}" : String(t.data);
1813
+ dataText = t2.data === void 0 ? "{}" : String(t2.data);
1691
1814
  }
1692
- const hay = [String(t.type ?? ""), String(t.round ?? ""), dataText].join(" ").toLowerCase();
1815
+ const hay = [String(t2.type ?? ""), String(t2.round ?? ""), dataText].join(" ").toLowerCase();
1693
1816
  if (hay.indexOf(q) < 0) return false;
1694
1817
  }
1695
1818
  return true;
@@ -1753,15 +1876,15 @@ function buildRoundComparison(report, validations) {
1753
1876
  return [...rounds.values()].filter((r) => r.round > 0).sort((a, b) => a.round - b.round).map((r) => ({ ...r, severities: { ...r.severities } }));
1754
1877
  }
1755
1878
  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);
1879
+ const t2 = transcript && typeof transcript === "object" ? transcript : null;
1880
+ if (!t2) return { state: "empty", round: 0, phase: "", stoppedReason: "" };
1881
+ const rawRound = Number(t2.round);
1759
1882
  const round = Number.isFinite(rawRound) && rawRound > 0 ? Math.floor(rawRound) : 1;
1760
- const stoppedReason = typeof t.stoppedReason === "string" ? t.stoppedReason.trim() : "";
1883
+ const stoppedReason = typeof t2.stoppedReason === "string" ? t2.stoppedReason.trim() : "";
1761
1884
  return {
1762
- state: t.active === true ? "running" : "done",
1885
+ state: t2.active === true ? "running" : "done",
1763
1886
  round,
1764
- phase: latestPhase(t.phases),
1887
+ phase: latestPhase(t2.phases),
1765
1888
  stoppedReason
1766
1889
  };
1767
1890
  }
@@ -1774,6 +1897,387 @@ function latestPhase(phases) {
1774
1897
  return latest;
1775
1898
  }
1776
1899
 
1900
+ // src/client/i18n.ts
1901
+ var LANG_STORAGE_KEY = "iterate-ui-lang";
1902
+ var LANG_NAMES = {
1903
+ zh: "\u7B80\u4F53\u4E2D\u6587",
1904
+ en: "English"
1905
+ };
1906
+ var EN = {
1907
+ // ── run state / dashboard ────────────────────────────────────────────────
1908
+ \u8FD0\u884C\u4E2D: "Running",
1909
+ \u5DF2\u7ED3\u675F: "Finished",
1910
+ "Round {a0}\uFF1A{a1} \u9879": "Round {a0}: {a1} findings",
1911
+ "\u5404\u8F6E\u53D1\u73B0\u6570\u91CF\u8D8B\u52BF\uFF1A{a0}": "Findings per round: {a0}",
1912
+ \u5B8C\u6574\u8FED\u4EE3: "Full iteration",
1913
+ "\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)',
1914
+ \u4EC5\u8BC4\u5BA1: "Review only",
1915
+ "\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",
1916
+ "\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.",
1917
+ " \xB7 \u9636\u6BB5\u300C{a0}\u300D": ' \xB7 phase "{a0}"',
1918
+ "\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.",
1919
+ "\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.",
1920
+ \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",
1921
+ "\u5DF2\u4E2D\u65AD\u6062\u590D \xD7{a0}": "Resumed \xD7{a0}",
1922
+ "\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",
1923
+ "\u9644\u4EF6\u56FE\u7247 {a0}": "Attached images {a0}",
1924
+ "\u5F53\u524D\u5904\u4E8E\u300C{a0}\u300D\u9636\u6BB5\uFF08\u8FD0\u884C\u4E2D\uFF09": 'Currently in the "{a0}" phase (running)',
1925
+ "\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)',
1926
+ "\u4EE3\u7801\u6A21\u5F0F\uFF1A\u76F4\u63A5\u4FEE\u6539\u6587\u4EF6": "Code mode: edits files directly",
1927
+ "\u8FED\u4EE3\u6A21\u5F0F\uFF1A\u5BA1\u67E5-\u4FEE\u590D\u5FAA\u73AF": "Iterate mode: review \u2192 fix loop",
1928
+ "+{a0} \u66F4\u591A": "+{a0} more",
1929
+ "\u672C\u8F6E\u5DF2\u5E94\u7528\u7684\u539F\u5B50\u4FEE\u590D\u6570\uFF08\u6B63\u5E38\u6A21\u5F0F\uFF09": "Atomic fixes applied this round (normal mode)",
1930
+ "\u5BA1\u67E5\u5DF2\u6536\u655B\uFF1A\u6700\u540E\u4E00\u8F6E\u672A\u53D1\u73B0\u65B0\u95EE\u9898": "Review converged: the final round found nothing new",
1931
+ "\u2713 \u5DF2\u6536\u655B": "\u2713 Converged",
1932
+ "\u9996\u8F6E {a0} \u2192 \u672B\u8F6E {a1} \u9879\uFF0C\u964D\u5E45 {a2}%{a3}": "Round {a0} \u2192 round {a1} findings, down {a2}%{a3}",
1933
+ "\uFF0C\u5DF2\u6536\u655B": ", converged",
1934
+ "Iterate \xB7 \u6536\u655B\u7EDF\u8BA1": "Iterate \xB7 Convergence stats",
1935
+ \u6536\u8D77\u5386\u53F2: "Collapse history",
1936
+ "\u5386\u53F2 / \u8D8B\u52BF": "History / trend",
1937
+ \u8F6E\u6B21: "Round",
1938
+ \u53D1\u73B0: "Findings",
1939
+ \u8BF7\u7EE7\u7EED\u6267\u884C\u4E0B\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5: "Please continue with the next review round",
1940
+ "\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",
1941
+ "y \u4FEE\u590D": "y fix",
1942
+ "\u4FDD\u7559\u8BE5 finding\uFF0C\u8FDB\u5165\u4FEE\u590D": "Keep this finding and move it to fixing",
1943
+ "n \u8DF3\u8FC7": "n skip",
1944
+ "\u8DF3\u8FC7\u8BE5 finding": "Skip this finding",
1945
+ "a \u5DF2\u77E5\u6709\u610F": "a known-intentional",
1946
+ "\u6807\u8BB0\u4E3A\u5DF2\u77E5\u6709\u610F\u5E76\u5199\u5165 known_intentional": "Mark as known-intentional and write it into known_intentional",
1947
+ // ── triage panel ─────────────────────────────────────────────────────────
1948
+ "Iterate \xB7 Findings \u5206\u8BCA ({a0}/{a1})": "Iterate \xB7 Findings triage ({a0}/{a1})",
1949
+ "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",
1950
+ \u6309\u4E25\u91CD\u5EA6\u7B5B\u9009: "Filter by severity",
1951
+ \u5168\u90E8\u4E25\u91CD\u5EA6: "All severities",
1952
+ \u6309\u7EF4\u5EA6\u7B5B\u9009: "Filter by dimension",
1953
+ \u5168\u90E8\u7EF4\u5EA6: "All dimensions",
1954
+ "\u641C\u7D22\u6587\u4EF6 / \u6458\u8981\u2026": "Search file / summary\u2026",
1955
+ \u641C\u7D22\u6587\u4EF6\u6216\u6458\u8981: "Search file or summary",
1956
+ "\u6E05\u9664\u7B5B\u9009\uFF08\u663E\u793A {a0}\uFF09": "Clear filters (showing {a0})",
1957
+ "\u5171 {a0} \u9879": "{a0} in total",
1958
+ "\u6279\u91CF\u4F5C\u7528\u4E8E\uFF1A": "Batch applies to:",
1959
+ "\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",
1960
+ "\u53EF\u89C1 {a0}": "Visible {a0}",
1961
+ "\u6279\u91CF\u6309\u94AE\u4F5C\u7528\u4E8E\u5168\u90E8 findings": "Batch buttons apply to every finding",
1962
+ "\u5168\u90E8 {a0}": "All {a0}",
1963
+ "\u628A\u6240\u6709\u5224\u5B9A\u6062\u590D\u4E3A\u9ED8\u8BA4 y\uFF08\u4FEE\u590D\uFF09": "Reset every verdict to the default y (fix)",
1964
+ \u91CD\u7F6E: "Reset",
1965
+ "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}",
1966
+ "\u5F53\u524D\u6CA1\u6709\u6807\u8BB0\u4E3A\u300C\u5DF2\u77E5\u6709\u610F\u300D\u7684 finding": "No finding is marked known-intentional yet",
1967
+ "\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)",
1968
+ "\u751F\u6210\u5E94\u7528\u6307\u4EE4{a0}": "Generate apply instruction{a0}",
1969
+ "\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)",
1970
+ "\u4EC5\u590D\u5236 YAML \u7247\u6BB5": "Copy YAML fragment only",
1971
+ "\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)",
1972
+ \u56DE\u8BFB\u5DF2\u5199\u5165\u6761\u76EE: "Read back written entries",
1973
+ "\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.",
1974
+ "\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.",
1975
+ // ── command actions ──────────────────────────────────────────────────────
1976
+ \u6307\u6325\u64CD\u4F5C: "Command actions",
1977
+ "\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)",
1978
+ \u6279\u51C6\u67B6\u6784\u4FEE\u590D: "Approve architectural fix",
1979
+ \u89E6\u53D1\u65B0\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5: "Trigger the next review round",
1980
+ \u89E6\u53D1\u65B0\u4E00\u8F6E: "New round",
1981
+ "\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)",
1982
+ "\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)",
1983
+ \u6307\u6D3E\u4FEE\u590D: "Assign fixes",
1984
+ \u56DE\u6EDA\u5230\u4E0A\u4E00\u4E2A\u68C0\u67E5\u70B9: "Roll back to the previous checkpoint",
1985
+ \u56DE\u6EDA\u68C0\u67E5\u70B9: "Rollback to checkpoint",
1986
+ // ── verdict banner / stats card ──────────────────────────────────────────
1987
+ \u62A5\u544A\u5DF2\u6279\u51C6: "Report approved",
1988
+ \u62A5\u544A\u9700\u4FEE\u8BA2: "Report needs revision",
1989
+ \u9879\u53D1\u73B0: " findings",
1990
+ \u8F6E: " rounds",
1991
+ \u9879\u5BA1\u67E5: " reviews",
1992
+ \u62A5\u544A\u901A\u8FC7\u5168\u90E8\u4E00\u81F4\u6027\u68C0\u67E5: "The report passed every consistency check",
1993
+ \u5904\u62A5\u544A\u7F3A\u9677: " report defects found",
1994
+ \u5DF2\u6536\u655B: "Converged",
1995
+ \u672A\u6536\u655B: "Not converged",
1996
+ "Round {a0} \u5B8C\u6210 \xB7 \u5DF2\u6536\u655B": "Round {a0} complete \xB7 converged",
1997
+ "Round {a0} \u5B8C\u6210": "Round {a0} complete",
1998
+ // ── settings ─────────────────────────────────────────────────────────────
1999
+ "\u5DF2\u6E05\u9664 {a0} \u6761": "Cleared {a0}",
2000
+ \u518D\u6B21\u70B9\u51FB\u4EE5\u786E\u8BA4\u6E05\u9664\u5168\u90E8\u5224\u5B9A: "Click again to confirm clearing every verdict",
2001
+ "\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)",
2002
+ "\u786E\u8BA4\u6E05\u9664\uFF1F": "Confirm clear?",
2003
+ \u6E05\u9664\u5206\u8BCA: "Clear triage",
2004
+ \u8FED\u4EE3\u4E3B\u9898: "Iterate theme",
2005
+ "\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.",
2006
+ \u5DF2\u542F\u7528: "Enabled",
2007
+ \u5DF2\u5173\u95ED: "Off",
2008
+ "\u5F00\u5173 iterate \u4E3B\u9898": "Toggle the iterate theme",
2009
+ \u5206\u8BCA\u6301\u4E45\u5316: "Triage persistence",
2010
+ "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.",
2011
+ \u672C\u5730\u4FDD\u5B58: "Stored locally",
2012
+ \u914D\u7F6E\u7BA1\u7406: "Configuration",
2013
+ "\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.",
2014
+ \u590D\u5236\u6307\u5F15: "Copy guide",
2015
+ \u6536\u8D77: "Collapse",
2016
+ \u5C55\u5F00: "Expand",
2017
+ \u9009\u62E9\u8981\u4FEE\u6539\u7684\u914D\u7F6E\u5B57\u6BB5: "Pick the config field to edit",
2018
+ "\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)",
2019
+ \u751F\u6210\u5199\u5165\u6307\u4EE4: "Build write instruction",
2020
+ "\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.",
2021
+ "\u5199\u5165\u6307\u4EE4\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\uFF09": "Write instruction (paste to the model)",
2022
+ "\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.",
2023
+ "\u5F53\u524D\u503C {a0}\uFF1A{a1}": "Current value for {a0}: {a1}",
2024
+ "\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).",
2025
+ '\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',
2026
+ \u56DE\u8BFB\u5F53\u524D\u914D\u7F6E: "Read back current config",
2027
+ "\u56DE\u8BFB\u6307\u4EE4\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\uFF09": "Read-back instruction (paste to the model)",
2028
+ \u590D\u5236: "Copy",
2029
+ "iterate.config.yaml \u53EF\u7F16\u8F91\u5B57\u6BB5": "Editable iterate.config.yaml fields",
2030
+ "\u4E2D\u6587\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\u7684\u5951\u7EA6\u6587\u6848\uFF09": "Chinese (contract text to paste to the model)",
2031
+ \u72B6\u6001\u6982\u89C8: "Runtime status",
2032
+ "\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.",
2033
+ \u62C9\u53D6\u78C1\u76D8\u5FEB\u7167: "Pull disk snapshot",
2034
+ \u8FD0\u884C\u65F6\u5E03\u5C40\u4E0E\u6E05\u7406: "Runtime layout & cleanup",
2035
+ "\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.",
2036
+ \u5C31\u7EEA: "Ready",
2037
+ "iterate \u8BBE\u7F6E": "iterate settings",
2038
+ "\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.",
2039
+ "\u754C\u9762\u8BED\u8A00": "Interface language",
2040
+ "\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.",
2041
+ "\u5207\u6362\u754C\u9762\u8BED\u8A00": "Switch the interface language",
2042
+ // ── observatory shell / live feed ────────────────────────────────────────
2043
+ "{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",
2044
+ "\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",
2045
+ "iterate \u89C2\u6D4B\u53F0": "iterate observatory",
2046
+ "\u6682\u65E0\u8FD0\u884C\u65F6\u89C2\u6D4B\u6570\u636E \xB7 {a0}": "No runtime observations yet \xB7 {a0}",
2047
+ "\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",
2048
+ "\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.",
2049
+ \u67E5\u770B\u5BA1\u67E5\u7EBF\u7A0B: "View review threads",
2050
+ \u67E5\u770B\u53D1\u73B0: "View findings",
2051
+ \u6682\u65E0\u5B9E\u65F6\u6D3B\u52A8: "No live activity yet",
2052
+ \u5B9E\u65F6\u6D3B\u52A8: "Live activity",
2053
+ \u6309\u6D3B\u52A8\u7C7B\u578B\u7B5B\u9009: "Filter by activity type",
2054
+ \u5168\u90E8\u6D3B\u52A8: "All activity",
2055
+ \u65E0\u5339\u914D\u7684\u5B9E\u65F6\u6D3B\u52A8: "No matching live activity",
2056
+ \u590D\u5236\u672A\u6210\u529F: "Copy failed",
2057
+ "\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",
2058
+ \u5173\u95ED\u590D\u5236\u5931\u8D25\u63D0\u793A: "Dismiss the copy-failure notice",
2059
+ \u5173\u95ED: "Dismiss",
2060
+ "\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)",
2061
+ \u5DF2\u5BFC\u51FA: "Exported",
2062
+ "\u5BFC\u51FA JSON": "Export JSON",
2063
+ "\u89C2\u6D4B\u53F0\u9875\u7B7E\uFF08\u5FEB\u6377\u952E 1-9 / 0 / -\uFF09": "Observatory tabs (keys 1-9 / 0 / -)",
2064
+ "{a0}\uFF08\u6309 {a1} \u5207\u6362\uFF09": "{a0} (press {a1})",
2065
+ "{a0}\uFF08\u6309 - \u5207\u6362\uFF09": "{a0} (press -)",
2066
+ // ── F1 review threads ────────────────────────────────────────────────────
2067
+ \u6682\u65E0\u5BA1\u67E5\u7EBF\u7A0B: "No review threads yet",
2068
+ \u5BA1\u67E5\u7EBF\u7A0B: "Review threads",
2069
+ \u5C55\u5F00\u5168\u90E8\u7EBF\u7A0B: "Expand all threads",
2070
+ \u5168\u90E8\u5C55\u5F00: "Expand all",
2071
+ \u6536\u8D77\u5168\u90E8\u7EBF\u7A0B: "Collapse all threads",
2072
+ \u5168\u90E8\u6536\u8D77: "Collapse all",
2073
+ "{a0} \xB7 {a1} \u8F6E": "{a0} \xB7 {a1} rounds",
2074
+ \u81EA\u7531\u5207\u6362: "Mixed",
2075
+ \u672C\u8F6E\u65E0\u7EBF\u7A0B: "No threads in this round",
2076
+ \u672A\u547D\u540D\u7EF4\u5EA6: "Unnamed dimension",
2077
+ \u6536\u8D77\u7EBF\u7A0B: "Collapse thread",
2078
+ \u5C55\u5F00\u7EBF\u7A0B: "Expand thread",
2079
+ \u53D9\u8FF0: "Narration",
2080
+ "{a0} \u6761": "{a0}",
2081
+ \u65E0\u7EBF\u7D22\u53D9\u8FF0: "No narration recorded",
2082
+ \u8BFB\u53D6\u6587\u4EF6: "Files read",
2083
+ \u672C\u7EBF\u7A0B\u65E0\u53D1\u73B0: "This thread found nothing",
2084
+ "{a0} \u7EBF\u7A0B": "{a0} threads",
2085
+ // ── F2 convergence trend ─────────────────────────────────────────────────
2086
+ \u6682\u65E0\u6536\u655B\u6570\u636E: "No convergence data yet",
2087
+ \u5DF2\u4FEE\u590D: "Fixed",
2088
+ \u4E25\u91CD\u5EA6\u5206\u5E03: "Severity mix",
2089
+ \u9A8C\u8BC1: "Validation",
2090
+ "{a0}/{a1} \u901A\u8FC7{a2}": "{a0}/{a1} passed{a2}",
2091
+ "\uFF08\u542B\u5931\u8D25\uFF09": " (failures included)",
2092
+ "\u5404\u8F6E\u53D1\u73B0\u6570\u91CF\uFF1A{a0}{a1}": "Findings per round: {a0}{a1}",
2093
+ " \xB7 \u5F53\u524D Round {a0}": " \xB7 current round {a0}",
2094
+ "\u8DE8\u8F6E\u5BF9\u6BD4\uFF08\u672C\u4F1A\u8BDD\uFF09": "Cross-round comparison (this session)",
2095
+ "\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',
2096
+ // ── F3 finding locations ─────────────────────────────────────────────────
2097
+ \u6682\u65E0\u53D1\u73B0: "No findings",
2098
+ "{a0}\uFF08{a1} \u9879\uFF09": "{a0} ({a1} findings)",
2099
+ "\u641C\u7D22\u53D1\u73B0\u2026": "Search findings\u2026",
2100
+ \u641C\u7D22\u53D1\u73B0: "Search findings",
2101
+ \u6E05\u9664\u5168\u90E8\u7B5B\u9009: "Clear all filters",
2102
+ \u6E05\u9664\u7B5B\u9009: "Clear filters",
2103
+ \u5DF2\u786E\u8BA4: "Acknowledged",
2104
+ "\u590D\u5236\u8BE5 finding \u7684\u5EFA\u8BAE\u4FEE\u590D\u6587\u672C": "Copy this finding\u2019s suggested fix text",
2105
+ "\u590D\u5236 suggested_fix": "Copy suggested_fix",
2106
+ "\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)",
2107
+ "\u590D\u5236 iterate_fix \u6307\u4EE4\u6587\u672C": "Copy the iterate_fix instruction text",
2108
+ \u590D\u5236\u4FEE\u590D\u6307\u4EE4: "Copy fix instruction",
2109
+ \u65E0\u5339\u914D\u7684\u53D1\u73B0: "No matching findings",
2110
+ // ── F4 fixes & rollback ──────────────────────────────────────────────────
2111
+ \u6682\u65E0\u4FEE\u590D\u8BB0\u5F55: "No fixes recorded yet",
2112
+ \u6210\u529F: "Success",
2113
+ \u5931\u8D25: "Failed",
2114
+ "\u590D\u5236 iterate_rollback \u6307\u4EE4\u6587\u672C": "Copy the iterate_rollback instruction text",
2115
+ \u56DE\u6EDA: "Rollback",
2116
+ // ── F5 checkpoint ────────────────────────────────────────────────────────
2117
+ \u65AD\u70B9: "Checkpoint",
2118
+ "\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)",
2119
+ \u590D\u5236\u6062\u590D\u6307\u4EE4: "Copy resume instruction",
2120
+ "\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)",
2121
+ \u6E05\u9664\u65AD\u70B9: "Clear checkpoint",
2122
+ // ── F6 run console ───────────────────────────────────────────────────────
2123
+ \u8BE2\u95EE\u7528\u6237: "Ask the user",
2124
+ \u62D2\u7EDD: "Deny",
2125
+ \u76F4\u63A5\u6267\u884C: "Run directly",
2126
+ "\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```",
2127
+ "\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```",
2128
+ \u901A\u8FC7: "Passed",
2129
+ \u5BFC\u81F4\u4E2D\u6B62: " aborted the run",
2130
+ \u9A8C\u8BC1\u7ED3\u679C: "Validation results",
2131
+ \u672C\u8F6E\u5C1A\u65E0\u9A8C\u8BC1\u8BB0\u5F55: "No validation was recorded in this round",
2132
+ "{a0} \u6761\u547D\u4EE4": "{a0} commands",
2133
+ \u56E0\u9A8C\u8BC1\u5931\u8D25\u4E2D\u6B62: "Stopped because validation failed",
2134
+ "\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.',
2135
+ \u8FD0\u884C\u63A7\u5236\u53F0: "Run console",
2136
+ "\u6279\u51C6\u7B56\u7565\uFF1A{a0}": "Approval policy: {a0}",
2137
+ "\u5F53\u524D nudge": "Current nudge",
2138
+ "\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",
2139
+ \u5DF2\u590D\u5236\u6E05\u9664\u6307\u4EE4: "Clear instruction copied",
2140
+ \u590D\u5236\u6E05\u9664\u6307\u4EE4: "Copy clear instruction",
2141
+ "\u8D77\u8349\u4E00\u6761 nudge \u65B9\u5411\u6307\u4EE4\u2026": "Draft a nudge to steer the run\u2026",
2142
+ "nudge \u8349\u7A3F": "Nudge draft",
2143
+ "\u590D\u5236 iterate_transcript nudge \u6307\u4EE4\u6587\u672C": "Copy the iterate_transcript nudge instruction text",
2144
+ "\u590D\u5236 nudge \u6307\u4EE4": "Copy nudge instruction",
2145
+ // ── F7 decision timeline ─────────────────────────────────────────────────
2146
+ \u6682\u65E0\u65F6\u95F4\u7EBF\u6761\u76EE: "No timeline entries yet",
2147
+ \u65F6\u95F4\u7EBF: "Timeline",
2148
+ \u6309\u65F6\u95F4\u7EBF\u7C7B\u578B\u7B5B\u9009: "Filter the timeline by type",
2149
+ \u5168\u90E8\u7C7B\u578B: "All types",
2150
+ \u6309\u8F6E\u6B21\u7B5B\u9009: "Filter by round",
2151
+ \u5168\u90E8\u8F6E\u6B21: "All rounds",
2152
+ "\u641C\u7D22\u65F6\u95F4\u7EBF\u2026": "Search timeline\u2026",
2153
+ \u641C\u7D22\u65F6\u95F4\u7EBF: "Search timeline",
2154
+ \u6E05\u9664: "Clear",
2155
+ \u65E0\u5339\u914D\u7684\u65F6\u95F4\u7EBF\u6761\u76EE: "No matching timeline entries",
2156
+ // ── F8 quality gate ──────────────────────────────────────────────────────
2157
+ \u8D28\u91CF\u95E8\u7981: "Quality gate",
2158
+ "\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)",
2159
+ \u67E5\u8BE2\u95E8\u7981: "Query gate",
2160
+ "\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)",
2161
+ \u6E05\u9664\u95E8\u7981: "Clear gate",
2162
+ "\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/",
2163
+ \u8D28\u91CF\u95E8\u7981\u89C6\u56FE: "Quality gate view",
2164
+ \u8D28\u91CF\u95E8\u7981\u8BC1\u4E66: "Quality gate certificate",
2165
+ "\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.",
2166
+ \u590D\u5236\u67E5\u8BE2\u6307\u4EE4: "Copy query instruction",
2167
+ \u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4: "Copy disk-snapshot instruction",
2168
+ "\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}%",
2169
+ "{a0} \xB7 \u6536\u655B {a1}% \xB7 \u8BC4\u5206 {a2}": "{a0} \xB7 convergence {a1}% \xB7 score {a2}",
2170
+ "\u8BC4\u5206 {a0} \xB7 \u53D1\u73B0 {a1} \xB7 \u5DF2\u4FEE\u590D {a2}": "Score {a0} \xB7 findings {a1} \xB7 fixed {a2}",
2171
+ "\u5931\u8D25\u539F\u56E0\uFF1A{a0}": "Failure reason: {a0}",
2172
+ // ── F9 experience bank ───────────────────────────────────────────────────
2173
+ "\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```",
2174
+ "\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```",
2175
+ "\u547D\u4E2D {a0} \u6B21": "Hit {a0} times",
2176
+ "\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)",
2177
+ \u91C7\u7EB3: "Adopt",
2178
+ \u7ECF\u9A8C\u94F6\u884C: "Experience bank",
2179
+ "\u4F1A\u8BDD\u5185 {a0} \u6761": "{a0} in this session",
2180
+ "\u590D\u5236\u5217\u51FA\u7ECF\u9A8C\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09": "Copy the list-experience instruction (JSON arguments included)",
2181
+ \u5217\u51FA\u7ECF\u9A8C: "List experiences",
2182
+ "\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",
2183
+ "\u641C\u7D22\u7ECF\u9A8C\u6A21\u5F0F\u2026": "Search experience patterns\u2026",
2184
+ \u641C\u7D22\u7ECF\u9A8C: "Search experiences",
2185
+ \u590D\u5236\u641C\u7D22\u6307\u4EE4: "Copy search instruction",
2186
+ \u641C\u7D22: "Search",
2187
+ "\u6CA1\u6709\u5339\u914D\u300C": "No experience matches \u201C",
2188
+ "\u300D\u7684\u7ECF\u9A8C": "\u201D",
2189
+ "\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.',
2190
+ // ── F10 defense events ───────────────────────────────────────────────────
2191
+ "\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```",
2192
+ \u524D\u7F6E\u6821\u9A8C\u5931\u8D25: "Precondition failed",
2193
+ \u4E0D\u53D8\u91CF\u8FDD\u53CD: "Invariant violated",
2194
+ \u5047\u8BBE\u88AB\u8BC1\u4F2A: "Assumption falsified",
2195
+ \u9632\u5FA1\u4E8B\u4EF6: "Defense events",
2196
+ "\u4F1A\u8BDD\u5185 {a0} \u6761 / \u7D2F\u8BA1 {a1}": "{a0} in this session / {a1} total",
2197
+ "\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)",
2198
+ \u5217\u51FA\u4E8B\u4EF6: "List events",
2199
+ "\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",
2200
+ "\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```",
2201
+ "\u590D\u5236\u7EDF\u8BA1\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09": "Copy the counts instruction (JSON arguments included)",
2202
+ \u7EDF\u8BA1: "Counts",
2203
+ "\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```",
2204
+ "\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)",
2205
+ \u6E05\u9664\u4E8B\u4EF6: "Clear events",
2206
+ \u9632\u5FA1\u4E8B\u4EF6\u6D41: "Defense event stream",
2207
+ "\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.",
2208
+ \u6309\u4E8B\u4EF6\u7C7B\u578B\u7B5B\u9009: "Filter by event type",
2209
+ \u6E05\u9664\u7C7B\u578B\u7B5B\u9009: "Clear type filter",
2210
+ "\u9632\u7EBF\uFF1A{a0}": "Defense: {a0}",
2211
+ \u6CA1\u6709\u5339\u914D\u8BE5\u7C7B\u578B\u7684\u9632\u5FA1\u4E8B\u4EF6: "No defense events of this type",
2212
+ // ── copy feedback ────────────────────────────────────────────────────────
2213
+ \u5DF2\u590D\u5236: "Copied",
2214
+ \u590D\u5236\u5931\u8D25: "Copy failed"
2215
+ };
2216
+ var current = "zh";
2217
+ var storage = null;
2218
+ var listeners = /* @__PURE__ */ new Set();
2219
+ function getLang() {
2220
+ return current;
2221
+ }
2222
+ function setLang(next, persist = true) {
2223
+ if (next !== current) {
2224
+ current = next;
2225
+ if (persist) {
2226
+ try {
2227
+ storage?.set(LANG_STORAGE_KEY, next);
2228
+ } catch {
2229
+ }
2230
+ }
2231
+ for (const listener of [...listeners]) {
2232
+ try {
2233
+ listener(next);
2234
+ } catch {
2235
+ }
2236
+ }
2237
+ }
2238
+ }
2239
+ function subscribeLang(listener) {
2240
+ listeners.add(listener);
2241
+ return () => {
2242
+ listeners.delete(listener);
2243
+ };
2244
+ }
2245
+ function configureLangStorage(next) {
2246
+ storage = next;
2247
+ }
2248
+ function detectLang(stored, navigatorLanguage) {
2249
+ if (stored === "en" || stored === "zh") return stored;
2250
+ return /^zh\b/i.test(navigatorLanguage ?? "") ? "zh" : "en";
2251
+ }
2252
+ function t(key) {
2253
+ return current === "en" ? EN[key] ?? key : key;
2254
+ }
2255
+ function tf(template, params = {}) {
2256
+ const translated = current === "en" ? EN[template] ?? template : template;
2257
+ return translated.replace(
2258
+ /\{([A-Za-z0-9_]+)\}/g,
2259
+ (whole, name2) => Object.prototype.hasOwnProperty.call(params, name2) ? String(params[name2]) : whole
2260
+ );
2261
+ }
2262
+
2263
+ // src/client/session-cache.ts
2264
+ var current2 = null;
2265
+ var listeners2 = /* @__PURE__ */ new Set();
2266
+ function noteSession(session) {
2267
+ if (Object.is(session, current2)) return;
2268
+ current2 = session;
2269
+ for (const listener of [...listeners2]) listener();
2270
+ }
2271
+ function readSession() {
2272
+ return current2;
2273
+ }
2274
+ function subscribeSession(listener) {
2275
+ listeners2.add(listener);
2276
+ return () => {
2277
+ listeners2.delete(listener);
2278
+ };
2279
+ }
2280
+
1777
2281
  // src/client/index.ts
1778
2282
  var React = require("react");
1779
2283
  var name = "iterate-plugin";
@@ -2106,12 +2610,12 @@ function createStorage() {
2106
2610
  }
2107
2611
  }
2108
2612
  function removeStorageByPrefix(prefix) {
2109
- if (!storage) return 0;
2613
+ if (!storage2) return 0;
2110
2614
  let removed = 0;
2111
2615
  try {
2112
- for (const key of storage.keys()) {
2616
+ for (const key of storage2.keys()) {
2113
2617
  if (key.startsWith(prefix)) {
2114
- storage.remove(key);
2618
+ storage2.remove(key);
2115
2619
  removed++;
2116
2620
  }
2117
2621
  }
@@ -2120,6 +2624,20 @@ function removeStorageByPrefix(prefix) {
2120
2624
  }
2121
2625
  return removed;
2122
2626
  }
2627
+ function useLang() {
2628
+ const [lang, set] = React.useState(getLang());
2629
+ React.useEffect(() => subscribeLang(set), []);
2630
+ return lang;
2631
+ }
2632
+ function useSessionSnapshot() {
2633
+ return React.useSyncExternalStore(subscribeSession, readSession, readSession);
2634
+ }
2635
+ function withLang(Component) {
2636
+ return function LangScoped(props) {
2637
+ useLang();
2638
+ return React.createElement(Component, props);
2639
+ };
2640
+ }
2123
2641
  function copyText(text) {
2124
2642
  if (typeof navigator !== "undefined" && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
2125
2643
  return navigator.clipboard.writeText(text).then(
@@ -2157,10 +2675,23 @@ function severityColor(severity) {
2157
2675
  function severityLabel(severity) {
2158
2676
  return SEVERITY_LABEL[coerceSeverity(severity)];
2159
2677
  }
2678
+ function formatConfigValue(value) {
2679
+ if (typeof value === "string") return value;
2680
+ if (value === void 0) return "\u2014";
2681
+ if (value === null) return "null";
2682
+ try {
2683
+ return JSON.stringify(value) ?? String(value);
2684
+ } catch {
2685
+ return String(value);
2686
+ }
2687
+ }
2688
+ function parenthesize(label) {
2689
+ return getLang() === "en" ? ` (${label})` : `\uFF08${label}\uFF09`;
2690
+ }
2160
2691
  function runStatusText(live, manifest) {
2161
- if (live) return "\u8FD0\u884C\u4E2D";
2692
+ if (live) return t("\u8FD0\u884C\u4E2D");
2162
2693
  const reason = manifest && typeof manifest.stoppedReason === "string" ? manifest.stoppedReason : "";
2163
- return reason ? stoppedReasonLabel(reason) : "\u5DF2\u7ED3\u675F";
2694
+ return reason ? stoppedReasonLabel(reason, getLang()) : t("\u5DF2\u7ED3\u675F");
2164
2695
  }
2165
2696
  function readSlots(ctx) {
2166
2697
  if (ctx && ctx.slots && typeof ctx.slots.inject === "function" && typeof ctx.slots.register === "function") {
@@ -2184,7 +2715,7 @@ function readTheme(ctx) {
2184
2715
  }
2185
2716
  var slotsSvc = void 0;
2186
2717
  var themeSvc = void 0;
2187
- var storage = null;
2718
+ var storage2 = null;
2188
2719
  var themeDisposer = null;
2189
2720
  var themeListener = null;
2190
2721
  var themeEnabled = true;
@@ -2235,7 +2766,7 @@ function registerThemeListener(ctx) {
2235
2766
  }
2236
2767
  function setThemeEnabled(enabled) {
2237
2768
  themeEnabled = enabled;
2238
- if (storage) storage.set(THEME_STORAGE_KEY, enabled ? "1" : "0");
2769
+ if (storage2) storage2.set(THEME_STORAGE_KEY, enabled ? "1" : "0");
2239
2770
  if (enabled) applyThemeSkin();
2240
2771
  else clearThemeSkin();
2241
2772
  }
@@ -2252,7 +2783,7 @@ function TrendChart({ points }) {
2252
2783
  key: p.round,
2253
2784
  className: "iterate-trend-bar",
2254
2785
  "data-hot": p.count > 0 ? "" : void 0,
2255
- title: `Round ${p.round}\uFF1A${p.count} \u9879`,
2786
+ title: tf(`Round {a0}\uFF1A{a1} \u9879`, { a0: p.round, a1: p.count }),
2256
2787
  style: { height: `${Math.max(4, Math.round(p.count / max * 24))}px` }
2257
2788
  })
2258
2789
  );
@@ -2260,7 +2791,7 @@ function TrendChart({ points }) {
2260
2791
  return React.createElement("div", {
2261
2792
  className: "iterate-trend",
2262
2793
  role: "img",
2263
- "aria-label": `\u5404\u8F6E\u53D1\u73B0\u6570\u91CF\u8D8B\u52BF\uFF1A${summary}`
2794
+ "aria-label": tf(`\u5404\u8F6E\u53D1\u73B0\u6570\u91CF\u8D8B\u52BF\uFF1A{a0}`, { a0: summary })
2264
2795
  }, ...bars);
2265
2796
  }
2266
2797
  function StartIterationButton() {
@@ -2269,7 +2800,7 @@ function StartIterationButton() {
2269
2800
  const timers = React.useRef([]);
2270
2801
  React.useEffect(
2271
2802
  () => () => {
2272
- for (const t of timers.current) clearTimeout(t);
2803
+ for (const t2 of timers.current) clearTimeout(t2);
2273
2804
  timers.current = [];
2274
2805
  },
2275
2806
  []
@@ -2279,8 +2810,8 @@ function StartIterationButton() {
2279
2810
  if (ok) {
2280
2811
  setFailed(null);
2281
2812
  setCopied(key);
2282
- const t = window.setTimeout(() => setCopied((cur) => cur === key ? null : cur), 1600);
2283
- timers.current.push(t);
2813
+ const t2 = window.setTimeout(() => setCopied((cur) => cur === key ? null : cur), 1600);
2814
+ timers.current.push(t2);
2284
2815
  } else {
2285
2816
  setCopied(null);
2286
2817
  setFailed(key);
@@ -2295,17 +2826,20 @@ function StartIterationButton() {
2295
2826
  "data-failed": failed === key ? "" : void 0,
2296
2827
  onClick: () => copy(key, command),
2297
2828
  title
2298
- }, copied === key ? "\u5DF2\u590D\u5236" : failed === key ? "\u590D\u5236\u5931\u8D25" : label);
2829
+ }, copied === key ? t("\u5DF2\u590D\u5236") : failed === key ? t("\u590D\u5236\u5931\u8D25") : label);
2299
2830
  return React.createElement(
2300
2831
  "span",
2301
2832
  { 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")
2833
+ 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")),
2834
+ 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
2835
  );
2305
2836
  }
2306
2837
  function ConvergenceDashboard(props) {
2307
2838
  const [pulseKey, setPulseKey] = React.useState(0);
2308
2839
  const session = props && props.session ? props.session : null;
2840
+ React.useEffect(() => {
2841
+ noteSession(session);
2842
+ }, [session]);
2309
2843
  const report = latestReport(session);
2310
2844
  React.useEffect(() => {
2311
2845
  if (!report) return;
@@ -2320,11 +2854,11 @@ function ConvergenceDashboard(props) {
2320
2854
  return React.createElement(
2321
2855
  "div",
2322
2856
  { "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"),
2857
+ React.createElement("span", { className: "iterate-round-badge", "data-live": "" }, t("\u8FD0\u884C\u4E2D")),
2324
2858
  React.createElement(
2325
2859
  "span",
2326
2860
  { 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`
2861
+ 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
2862
  )
2329
2863
  // No start button while a run is active.
2330
2864
  );
@@ -2333,11 +2867,11 @@ function ConvergenceDashboard(props) {
2333
2867
  return React.createElement(
2334
2868
  "div",
2335
2869
  { "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"),
2870
+ React.createElement("span", { className: "iterate-round-badge" }, t("\u5DF2\u7ED3\u675F")),
2337
2871
  React.createElement(
2338
2872
  "span",
2339
2873
  { 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`
2874
+ 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
2875
  ),
2342
2876
  React.createElement(StartIterationButton, null)
2343
2877
  );
@@ -2346,7 +2880,7 @@ function ConvergenceDashboard(props) {
2346
2880
  "div",
2347
2881
  { "data-iterate-root": "", "data-iterate": "dashboard", "data-run-state": "empty", className: "iterate-dashboard iterate-dashboard-empty" },
2348
2882
  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"),
2883
+ 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
2884
  React.createElement(StartIterationButton, null)
2351
2885
  );
2352
2886
  }
@@ -2361,20 +2895,20 @@ function ConvergenceDashboard(props) {
2361
2895
  const resumeChip = resumeCount > 0 ? React.createElement("span", {
2362
2896
  className: "iterate-chip-resume",
2363
2897
  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;
2898
+ title: t("\u672C\u6B21\u8FED\u4EE3\u4ECE\u4E0A\u4E00\u6B21\u4E2D\u65AD\u7684\u65AD\u70B9\u7EE7\u7EED\u6267\u884C")
2899
+ }, tf(`\u5DF2\u4E2D\u65AD\u6062\u590D \xD7{a0}`, { a0: String(resumeCount) })) : null;
2366
2900
  const imageChip = imageCount > 0 ? React.createElement("span", {
2367
2901
  className: "iterate-chip-images",
2368
2902
  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;
2903
+ 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")
2904
+ }, tf(`\u9644\u4EF6\u56FE\u7247 {a0}`, { a0: String(imageCount) })) : null;
2371
2905
  const transcript = latestTranscript(session);
2372
2906
  const phase = transcript ? latestPhase(transcript.phases) : "";
2373
2907
  const liveChip = transcript && phase ? React.createElement("span", {
2374
2908
  className: "iterate-chip-phase",
2375
2909
  key: "phase",
2376
2910
  "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`
2911
+ 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
2912
  }, `${phase} \xB7 ${runStatusText(transcript.active === true, transcript)}`) : null;
2379
2913
  const taskModeTranscript = latestTranscript(session);
2380
2914
  const taskMode = taskModeTranscript?.taskMode;
@@ -2382,7 +2916,7 @@ function ConvergenceDashboard(props) {
2382
2916
  className: "iterate-chip-taskmode",
2383
2917
  key: "taskmode",
2384
2918
  "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"
2919
+ 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
2920
  }, taskMode === "code" ? "\u25CF code" : "\u25CF iterate") : null;
2387
2921
  const dimNames = Object.keys(dims);
2388
2922
  const dimBadges = dimNames.slice(0, 6).map(
@@ -2398,7 +2932,7 @@ function ConvergenceDashboard(props) {
2398
2932
  React.createElement(
2399
2933
  "span",
2400
2934
  { key: "+more", className: "iterate-dim-badge iterate-dim-more", title: dimNames.slice(6).join(", ") },
2401
- `+${overflow} \u66F4\u591A`
2935
+ tf(`+{a0} \u66F4\u591A`, { a0: overflow })
2402
2936
  )
2403
2937
  );
2404
2938
  }
@@ -2409,14 +2943,14 @@ function ConvergenceDashboard(props) {
2409
2943
  const fixBadge = fixCount !== null ? React.createElement("span", {
2410
2944
  className: "iterate-metric",
2411
2945
  key: "fixes",
2412
- title: "\u672C\u8F6E\u5DF2\u5E94\u7528\u7684\u539F\u5B50\u4FEE\u590D\u6570\uFF08\u6B63\u5E38\u6A21\u5F0F\uFF09"
2946
+ title: t("\u672C\u8F6E\u5DF2\u5E94\u7528\u7684\u539F\u5B50\u4FEE\u590D\u6570\uFF08\u6B63\u5E38\u6A21\u5F0F\uFF09")
2413
2947
  }, `${String(fixCount)} fixes`) : null;
2414
2948
  const converged = report.convergence && report.convergence.converged === true;
2415
2949
  const convChip = converged ? React.createElement("span", {
2416
2950
  className: "iterate-chip-resume",
2417
2951
  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;
2952
+ title: t("\u5BA1\u67E5\u5DF2\u6536\u655B\uFF1A\u6700\u540E\u4E00\u8F6E\u672A\u53D1\u73B0\u65B0\u95EE\u9898")
2953
+ }, t("\u2713 \u5DF2\u6536\u655B")) : null;
2420
2954
  const sevMetric = (key, label) => React.createElement(
2421
2955
  "span",
2422
2956
  { className: "iterate-metric", key, title: label },
@@ -2485,19 +3019,19 @@ function StatsCard(props) {
2485
3019
  React.createElement("td", { style: { color: SEVERITY_COLOR.low } }, String(h.low))
2486
3020
  )
2487
3021
  );
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);
3022
+ 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") : "" });
3023
+ const completion = buildCompletionSummary(report, getLang());
2490
3024
  return React.createElement(
2491
3025
  "div",
2492
3026
  { "data-iterate-root": "", "data-iterate": "stats", className: "iterate-stats" },
2493
3027
  React.createElement(
2494
3028
  "div",
2495
3029
  { className: "iterate-triage-head" },
2496
- React.createElement("span", {}, "Iterate \xB7 \u6536\u655B\u7EDF\u8BA1"),
3030
+ React.createElement("span", {}, t("Iterate \xB7 \u6536\u655B\u7EDF\u8BA1")),
2497
3031
  React.createElement(
2498
3032
  "span",
2499
3033
  { 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"}`
3034
+ `Round ${getCurrentRound(report)}/${getTotalRounds(report)} \xB7 ${total} findings \xB7 ${report.convergence && report.convergence.converged ? t("\u5DF2\u6536\u655B") : t("\u672A\u6536\u655B")}`
2501
3035
  )
2502
3036
  ),
2503
3037
  React.createElement("div", { className: "iterate-stats-grid" }, ...rows),
@@ -2510,7 +3044,7 @@ function StatsCard(props) {
2510
3044
  className: "iterate-btn",
2511
3045
  "data-primary": showHistory ? "" : void 0,
2512
3046
  onClick: () => setShowHistory((v) => !v)
2513
- }, showHistory ? "\u6536\u8D77\u5386\u53F2" : "\u5386\u53F2 / \u8D8B\u52BF")
3047
+ }, showHistory ? t("\u6536\u8D77\u5386\u53F2") : t("\u5386\u53F2 / \u8D8B\u52BF"))
2514
3048
  ),
2515
3049
  showHistory ? React.createElement(
2516
3050
  "div",
@@ -2524,8 +3058,8 @@ function StatsCard(props) {
2524
3058
  React.createElement(
2525
3059
  "tr",
2526
3060
  {},
2527
- React.createElement("th", {}, "\u8F6E\u6B21"),
2528
- React.createElement("th", {}, "\u53D1\u73B0"),
3061
+ React.createElement("th", {}, t("\u8F6E\u6B21")),
3062
+ React.createElement("th", {}, t("\u53D1\u73B0")),
2529
3063
  React.createElement("th", { style: { color: SEVERITY_COLOR.critical } }, "CRIT"),
2530
3064
  React.createElement("th", { style: { color: SEVERITY_COLOR.high } }, "HIGH"),
2531
3065
  React.createElement("th", { style: { color: SEVERITY_COLOR.medium } }, "MED"),
@@ -2548,7 +3082,7 @@ function TriagePanel(props) {
2548
3082
  const storageKey = TRIAGE_STORAGE_PREFIX + hashReport(report);
2549
3083
  const [verdicts, setVerdicts] = React.useState(() => {
2550
3084
  const initial = buildTriageState(report);
2551
- const saved = storage ? storage.get(storageKey) : null;
3085
+ const saved = storage2 ? storage2.get(storageKey) : null;
2552
3086
  if (saved) {
2553
3087
  try {
2554
3088
  const parsed = JSON.parse(saved);
@@ -2573,7 +3107,7 @@ function TriagePanel(props) {
2573
3107
  const [cmdFailed, setCmdFailed] = React.useState(null);
2574
3108
  const cmdTimers = React.useRef([]);
2575
3109
  React.useEffect(() => () => {
2576
- for (const t of cmdTimers.current) clearTimeout(t);
3110
+ for (const t2 of cmdTimers.current) clearTimeout(t2);
2577
3111
  cmdTimers.current = [];
2578
3112
  }, []);
2579
3113
  const copyWithFeedback = (text, key) => {
@@ -2590,7 +3124,7 @@ function TriagePanel(props) {
2590
3124
  });
2591
3125
  };
2592
3126
  React.useEffect(() => {
2593
- if (storage) storage.set(storageKey, JSON.stringify(verdicts));
3127
+ if (storage2) storage2.set(storageKey, JSON.stringify(verdicts));
2594
3128
  }, [storageKey, verdicts]);
2595
3129
  const setVerdict = (index, verdict) => {
2596
3130
  setVerdicts((prev) => ({ ...prev, [String(index)]: verdict }));
@@ -2617,9 +3151,9 @@ function TriagePanel(props) {
2617
3151
  if (!doc) return;
2618
3152
  const onKeyDown = (ev) => {
2619
3153
  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;
3154
+ const t2 = ev.target;
3155
+ if (t2 && (t2.tagName === "INPUT" || t2.tagName === "TEXTAREA" || t2.tagName === "SELECT")) return;
3156
+ if (t2 && typeof t2.isContentEditable === "boolean" && t2.isContentEditable) return;
2623
3157
  const rootEl = doc.querySelector('[data-iterate="triage"]');
2624
3158
  if (!rootEl) return;
2625
3159
  const activeEl = doc.activeElement;
@@ -2658,11 +3192,11 @@ function TriagePanel(props) {
2658
3192
  copyWithFeedback(cmd, "approve-arch");
2659
3193
  };
2660
3194
  const doTriggerNewRound = () => {
2661
- const cmd = "\u8BF7\u7EE7\u7EED\u6267\u884C\u4E0B\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5";
3195
+ const cmd = t("\u8BF7\u7EE7\u7EED\u6267\u884C\u4E0B\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5");
2662
3196
  copyWithFeedback(cmd, "new-round");
2663
3197
  };
2664
3198
  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";
3199
+ 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
3200
  copyWithFeedback(cmd, "rollback");
2667
3201
  };
2668
3202
  const doAssignFindings = () => {
@@ -2750,9 +3284,9 @@ function TriagePanel(props) {
2750
3284
  React.createElement(
2751
3285
  "div",
2752
3286
  { 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")
3287
+ btn(t("y \u4FEE\u590D"), "keep", t("\u4FDD\u7559\u8BE5 finding\uFF0C\u8FDB\u5165\u4FEE\u590D")),
3288
+ btn(t("n \u8DF3\u8FC7"), "skip", t("\u8DF3\u8FC7\u8BE5 finding")),
3289
+ btn(t("a \u5DF2\u77E5\u6709\u610F"), "ignore", t("\u6807\u8BB0\u4E3A\u5DF2\u77E5\u6709\u610F\u5E76\u5199\u5165 known_intentional"))
2756
3290
  )
2757
3291
  );
2758
3292
  });
@@ -2762,24 +3296,24 @@ function TriagePanel(props) {
2762
3296
  React.createElement(
2763
3297
  "div",
2764
3298
  { 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")
3299
+ React.createElement("span", { role: "heading", "aria-level": 3 }, tf(`Iterate \xB7 Findings \u5206\u8BCA ({a0}/{a1})`, { a0: filtered.length, a1: findings.length })),
3300
+ 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
3301
  ),
2768
3302
  React.createElement(
2769
3303
  "div",
2770
3304
  { className: "iterate-filter" },
2771
3305
  React.createElement(
2772
3306
  "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"),
3307
+ { className: "iterate-filter-select", value: filter.severities[0] || "", onChange: (e) => setSeverityFilter(e.target.value), "aria-label": t("\u6309\u4E25\u91CD\u5EA6\u7B5B\u9009") },
3308
+ React.createElement("option", { value: "" }, t("\u5168\u90E8\u4E25\u91CD\u5EA6")),
2775
3309
  ...options.severities.map(
2776
3310
  (s) => React.createElement("option", { key: s.value, value: s.value }, `${severityLabel(s.value)} (${s.count})`)
2777
3311
  )
2778
3312
  ),
2779
3313
  React.createElement(
2780
3314
  "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"),
3315
+ { className: "iterate-filter-select", value: filter.dimensions[0] || "", onChange: (e) => setDimensionFilter(e.target.value), "aria-label": t("\u6309\u7EF4\u5EA6\u7B5B\u9009") },
3316
+ React.createElement("option", { value: "" }, t("\u5168\u90E8\u7EF4\u5EA6")),
2783
3317
  ...options.dimensions.map(
2784
3318
  (d) => React.createElement("option", { key: d.value, value: d.value }, `${d.value} (${d.count})`)
2785
3319
  )
@@ -2787,41 +3321,41 @@ function TriagePanel(props) {
2787
3321
  React.createElement("input", {
2788
3322
  className: "iterate-filter-search",
2789
3323
  type: "search",
2790
- placeholder: "\u641C\u7D22\u6587\u4EF6 / \u6458\u8981\u2026",
2791
- "aria-label": "\u641C\u7D22\u6587\u4EF6\u6216\u6458\u8981",
3324
+ placeholder: t("\u641C\u7D22\u6587\u4EF6 / \u6458\u8981\u2026"),
3325
+ "aria-label": t("\u641C\u7D22\u6587\u4EF6\u6216\u6458\u8981"),
2792
3326
  value: filter.search,
2793
3327
  onChange: (e) => setSearchFilter(e.target.value)
2794
3328
  }),
2795
3329
  React.createElement(
2796
3330
  "span",
2797
3331
  { 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`
3332
+ 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
3333
  )
2800
3334
  ),
2801
3335
  React.createElement(
2802
3336
  "div",
2803
3337
  { className: "iterate-batch" },
2804
- React.createElement("span", { className: "iterate-batch-label" }, "\u6279\u91CF\u4F5C\u7528\u4E8E\uFF1A"),
3338
+ React.createElement("span", { className: "iterate-batch-label" }, t("\u6279\u91CF\u4F5C\u7528\u4E8E\uFF1A")),
2805
3339
  React.createElement("button", {
2806
3340
  className: selectAll ? "iterate-batch-btn iterate-batch-scope" : "iterate-batch-btn iterate-batch-scope iterate-batch-scope-on",
2807
3341
  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}`),
3342
+ title: t("\u6279\u91CF\u6309\u94AE\u4EC5\u4F5C\u7528\u4E8E\u5F53\u524D\u7B5B\u9009\u53EF\u89C1\u7684 findings")
3343
+ }, tf(`\u53EF\u89C1 {a0}`, { a0: indices.length })),
2810
3344
  React.createElement("button", {
2811
3345
  className: selectAll ? "iterate-batch-btn iterate-batch-scope iterate-batch-scope-on" : "iterate-batch-btn iterate-batch-scope",
2812
3346
  onClick: () => setSelectAll(true),
2813
- title: "\u6279\u91CF\u6309\u94AE\u4F5C\u7528\u4E8E\u5168\u90E8 findings"
2814
- }, `\u5168\u90E8 ${allIndices.length}`),
3347
+ title: t("\u6279\u91CF\u6309\u94AE\u4F5C\u7528\u4E8E\u5168\u90E8 findings")
3348
+ }, tf(`\u5168\u90E8 {a0}`, { a0: allIndices.length })),
2815
3349
  React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("keep") }, "y"),
2816
3350
  React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("skip") }, "n"),
2817
3351
  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")
3352
+ 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
3353
  ),
2820
3354
  ...rows,
2821
3355
  React.createElement(
2822
3356
  "div",
2823
3357
  { 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`),
3358
+ 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
3359
  React.createElement(
2826
3360
  "span",
2827
3361
  { style: { display: "flex", gap: 6, flexWrap: "wrap" } },
@@ -2834,31 +3368,31 @@ function TriagePanel(props) {
2834
3368
  "data-failed": cmdFailed === "apply" ? "" : void 0,
2835
3369
  onClick: doBuildInstruction,
2836
3370
  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` : ""}`),
3371
+ 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")
3372
+ }, 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
3373
  React.createElement("button", {
2840
3374
  className: "iterate-btn",
2841
3375
  "data-copied": copied ? "" : void 0,
2842
3376
  "data-failed": cmdFailed === "yaml" ? "" : void 0,
2843
3377
  onClick: doCopyYaml,
2844
3378
  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"),
3379
+ 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")
3380
+ }, copied ? t("\u5DF2\u590D\u5236") : cmdFailed === "yaml" ? t("\u590D\u5236\u5931\u8D25") : t("\u4EC5\u590D\u5236 YAML \u7247\u6BB5")),
2847
3381
  React.createElement("button", {
2848
3382
  className: "iterate-btn",
2849
3383
  "data-copied": cmdCopied === "readback" ? "" : void 0,
2850
3384
  "data-failed": cmdFailed === "readback" ? "" : void 0,
2851
3385
  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")
3386
+ 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")
3387
+ }, cmdCopied === "readback" ? t("\u5DF2\u590D\u5236") : cmdFailed === "readback" ? t("\u590D\u5236\u5931\u8D25") : t("\u56DE\u8BFB\u5DF2\u5199\u5165\u6761\u76EE"))
2854
3388
  ),
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
3389
+ 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
3390
  ),
2857
3391
  // v3.0: Command buttons for native actions (§8)
2858
3392
  React.createElement(
2859
3393
  "div",
2860
3394
  { 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"),
3395
+ React.createElement("span", { style: { fontSize: 11, fontWeight: 600, color: "var(--dsw-alias-label-secondary)" } }, t("\u6307\u6325\u64CD\u4F5C")),
2862
3396
  React.createElement(
2863
3397
  "span",
2864
3398
  { style: { display: "flex", gap: 6, flexWrap: "wrap" } },
@@ -2868,15 +3402,15 @@ function TriagePanel(props) {
2868
3402
  "data-copied": cmdCopied === "approve-arch" ? "" : void 0,
2869
3403
  "data-failed": cmdFailed === "approve-arch" ? "" : void 0,
2870
3404
  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"),
3405
+ 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")
3406
+ }, cmdCopied === "approve-arch" ? t("\u5DF2\u590D\u5236") : cmdFailed === "approve-arch" ? t("\u590D\u5236\u5931\u8D25") : t("\u6279\u51C6\u67B6\u6784\u4FEE\u590D")),
2873
3407
  React.createElement("button", {
2874
3408
  className: "iterate-cmd",
2875
3409
  "data-copied": cmdCopied === "new-round" ? "" : void 0,
2876
3410
  "data-failed": cmdFailed === "new-round" ? "" : void 0,
2877
3411
  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"),
3412
+ title: t("\u89E6\u53D1\u65B0\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5")
3413
+ }, cmdCopied === "new-round" ? t("\u5DF2\u590D\u5236") : cmdFailed === "new-round" ? t("\u590D\u5236\u5931\u8D25") : t("\u89E6\u53D1\u65B0\u4E00\u8F6E")),
2880
3414
  React.createElement("button", {
2881
3415
  className: "iterate-cmd",
2882
3416
  "data-primary": "",
@@ -2884,16 +3418,16 @@ function TriagePanel(props) {
2884
3418
  "data-failed": cmdFailed === "assign" ? "" : void 0,
2885
3419
  onClick: doAssignFindings,
2886
3420
  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"),
3421
+ 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 })
3422
+ }, cmdCopied === "assign" ? t("\u5DF2\u590D\u5236") : cmdFailed === "assign" ? t("\u590D\u5236\u5931\u8D25") : t("\u6307\u6D3E\u4FEE\u590D")),
2889
3423
  React.createElement("button", {
2890
3424
  className: "iterate-cmd",
2891
3425
  "data-danger": "",
2892
3426
  "data-copied": cmdCopied === "rollback" ? "" : void 0,
2893
3427
  "data-failed": cmdFailed === "rollback" ? "" : void 0,
2894
3428
  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")
3429
+ title: t("\u56DE\u6EDA\u5230\u4E0A\u4E00\u4E2A\u68C0\u67E5\u70B9")
3430
+ }, cmdCopied === "rollback" ? t("\u5DF2\u590D\u5236") : cmdFailed === "rollback" ? t("\u590D\u5236\u5931\u8D25") : t("\u56DE\u6EDA\u68C0\u67E5\u70B9"))
2897
3431
  )
2898
3432
  ),
2899
3433
  payload ? React.createElement("div", { className: "iterate-payload" }, payload) : null
@@ -2916,16 +3450,16 @@ function VerdictBanner(props) {
2916
3450
  React.createElement(
2917
3451
  "span",
2918
3452
  { 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"
3453
+ ok ? t("\u62A5\u544A\u5DF2\u6279\u51C6") : t("\u62A5\u544A\u9700\u4FEE\u8BA2")
2920
3454
  ),
2921
3455
  React.createElement(
2922
3456
  "span",
2923
3457
  { 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")
3458
+ item(verdict.totalFindings, t("\u9879\u53D1\u73B0")),
3459
+ item(verdict.totalRounds, t("\u8F6E")),
3460
+ item(verdict.checksRun, t("\u9879\u5BA1\u67E5")),
3461
+ ok ? phrase(t("\u62A5\u544A\u901A\u8FC7\u5168\u90E8\u4E00\u81F4\u6027\u68C0\u67E5")) : item(verdict.reportIssues, t("\u5904\u62A5\u544A\u7F3A\u9677")),
3462
+ phrase(verdict.converged ? t("\u5DF2\u6536\u655B") : t("\u672A\u6536\u655B"))
2929
3463
  )
2930
3464
  );
2931
3465
  }
@@ -2967,7 +3501,7 @@ function ProgressCapsule() {
2967
3501
  const listener = (payload) => {
2968
3502
  const converged = payload && payload.converged === true;
2969
3503
  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 });
3504
+ setInfo({ text: converged ? tf(`Round {a0} \u5B8C\u6210 \xB7 \u5DF2\u6536\u655B`, { a0: round }) : tf(`Round {a0} \u5B8C\u6210`, { a0: round }), ok: converged });
2971
3505
  if (timer) clearTimeout(timer);
2972
3506
  timer = setTimeout(() => setInfo(null), converged ? 3600 : 2400);
2973
3507
  };
@@ -2987,7 +3521,10 @@ function ProgressCapsule() {
2987
3521
  "aria-live": "polite"
2988
3522
  }, info.text);
2989
3523
  }
2990
- function SettingsPanel(_props) {
3524
+ function SettingsPanel(props) {
3525
+ const lang = useLang();
3526
+ const cachedSession = useSessionSnapshot();
3527
+ const session = props && props.session ? props.session : cachedSession;
2991
3528
  const [enabled, setEnabled] = React.useState(themeEnabled);
2992
3529
  const [guideCopied, setGuideCopied] = React.useState(false);
2993
3530
  const [statusCopied, setStatusCopied] = React.useState(false);
@@ -3001,13 +3538,19 @@ function SettingsPanel(_props) {
3001
3538
  const [cfgCopied, setCfgCopied] = React.useState(false);
3002
3539
  const [cfgFailed, setCfgFailed] = React.useState(false);
3003
3540
  const [cfgText, setCfgText] = React.useState(null);
3541
+ const [cfgReadCopied, setCfgReadCopied] = React.useState(false);
3542
+ const [cfgReadText, setCfgReadText] = React.useState(null);
3004
3543
  const [pullCopied, setPullCopied] = React.useState(false);
3005
3544
  const [copyError, setCopyError] = React.useState(null);
3006
3545
  const guide = buildConfigEditGuide();
3007
3546
  const statusGuide = buildRuntimeStatusGuide();
3547
+ const cfgMeta = configFieldByKey(cfgField);
3548
+ const cfgRead = scanSessionForConfigRead(session);
3549
+ const cfgCurrent = cfgRead ? configValueByPath(cfgRead, cfgField) : null;
3550
+ const cfgFieldLabel = cfgMeta ? cfgMeta.label[getLang()] : cfgField;
3008
3551
  const cfgTimers = React.useRef([]);
3009
3552
  React.useEffect(() => () => {
3010
- for (const t of cfgTimers.current) clearTimeout(t);
3553
+ for (const t2 of cfgTimers.current) clearTimeout(t2);
3011
3554
  cfgTimers.current = [];
3012
3555
  }, []);
3013
3556
  const toggleTheme = () => {
@@ -3016,7 +3559,7 @@ function SettingsPanel(_props) {
3016
3559
  setThemeEnabled(next);
3017
3560
  };
3018
3561
  const flashCopied = (slot) => {
3019
- const setter = slot === "guide" ? setGuideCopied : slot === "status" ? setStatusCopied : setPullCopied;
3562
+ const setter = slot === "guide" ? setGuideCopied : slot === "status" ? setStatusCopied : slot === "pull" ? setPullCopied : setCfgReadCopied;
3020
3563
  setter(true);
3021
3564
  cfgTimers.current.push(window.setTimeout(() => setter(false), 1600));
3022
3565
  };
@@ -3036,6 +3579,17 @@ function SettingsPanel(_props) {
3036
3579
  if (!ok) return;
3037
3580
  });
3038
3581
  };
3582
+ const doCopyConfigRead = () => {
3583
+ const text = buildConfigReadInstruction();
3584
+ copyText(text).then((ok) => {
3585
+ setCopyError(ok ? null : "cfgRead");
3586
+ if (ok) {
3587
+ flashCopied("cfgRead");
3588
+ return;
3589
+ }
3590
+ setCfgReadText(text);
3591
+ });
3592
+ };
3039
3593
  const requestClear = () => {
3040
3594
  if (confirming) {
3041
3595
  const count = removeStorageByPrefix(TRIAGE_STORAGE_PREFIX);
@@ -3047,13 +3601,13 @@ function SettingsPanel(_props) {
3047
3601
  setConfirming(true);
3048
3602
  cfgTimers.current.push(window.setTimeout(() => setConfirming(false), 3e3));
3049
3603
  };
3050
- const clearButton = clearedInfo !== null ? React.createElement("button", { className: "iterate-btn", "data-copied": "", disabled: true }, `\u5DF2\u6E05\u9664 ${clearedInfo} \u6761`) : React.createElement("button", {
3604
+ 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
3605
  className: "iterate-btn",
3052
3606
  "data-danger": confirming ? void 0 : "",
3053
3607
  "data-confirm": confirming ? "" : void 0,
3054
3608
  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");
3609
+ 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")
3610
+ }, confirming ? t("\u786E\u8BA4\u6E05\u9664\uFF1F") : t("\u6E05\u9664\u5206\u8BCA"));
3057
3611
  const themeCard = React.createElement(
3058
3612
  "div",
3059
3613
  { key: "theme", className: "iterate-scard" },
@@ -3063,17 +3617,17 @@ function SettingsPanel(_props) {
3063
3617
  React.createElement(
3064
3618
  "div",
3065
3619
  {},
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")
3620
+ React.createElement("div", { className: "iterate-settings-title" }, t("\u8FED\u4EE3\u4E3B\u9898")),
3621
+ 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
3622
  ),
3069
3623
  React.createElement(
3070
3624
  "div",
3071
3625
  { className: "iterate-scard-actions" },
3072
- React.createElement("span", { className: "iterate-chip", "data-ok": enabled ? "" : void 0 }, enabled ? "\u5DF2\u542F\u7528" : "\u5DF2\u5173\u95ED"),
3626
+ React.createElement("span", { className: "iterate-chip", "data-ok": enabled ? "" : void 0 }, enabled ? t("\u5DF2\u542F\u7528") : t("\u5DF2\u5173\u95ED")),
3073
3627
  React.createElement("button", {
3074
3628
  role: "switch",
3075
3629
  "aria-checked": enabled,
3076
- "aria-label": "\u5F00\u5173 iterate \u4E3B\u9898",
3630
+ "aria-label": t("\u5F00\u5173 iterate \u4E3B\u9898"),
3077
3631
  className: "iterate-switch",
3078
3632
  "data-on": enabled ? "" : void 0,
3079
3633
  onClick: toggleTheme
@@ -3081,6 +3635,37 @@ function SettingsPanel(_props) {
3081
3635
  )
3082
3636
  )
3083
3637
  );
3638
+ const langCard = React.createElement(
3639
+ "div",
3640
+ { key: "lang", className: "iterate-scard" },
3641
+ React.createElement(
3642
+ "div",
3643
+ { className: "iterate-scard-head" },
3644
+ React.createElement(
3645
+ "div",
3646
+ {},
3647
+ React.createElement("div", { className: "iterate-settings-title" }, t("\u754C\u9762\u8BED\u8A00")),
3648
+ React.createElement(
3649
+ "div",
3650
+ { className: "iterate-settings-desc" },
3651
+ 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")
3652
+ )
3653
+ ),
3654
+ React.createElement(
3655
+ "div",
3656
+ { className: "iterate-scard-actions" },
3657
+ React.createElement("span", { className: "iterate-chip", "data-ok": "" }, LANG_NAMES[lang]),
3658
+ React.createElement("button", {
3659
+ role: "switch",
3660
+ "aria-checked": lang === "en",
3661
+ "aria-label": t("\u5207\u6362\u754C\u9762\u8BED\u8A00"),
3662
+ className: "iterate-switch",
3663
+ "data-on": lang === "en" ? "" : void 0,
3664
+ onClick: () => setLang(lang === "en" ? "zh" : "en")
3665
+ }, React.createElement("span", { className: "iterate-switch-knob" }))
3666
+ )
3667
+ )
3668
+ );
3084
3669
  const dataCard = React.createElement(
3085
3670
  "div",
3086
3671
  { key: "data", className: "iterate-scard" },
@@ -3090,13 +3675,13 @@ function SettingsPanel(_props) {
3090
3675
  React.createElement(
3091
3676
  "div",
3092
3677
  {},
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")
3678
+ React.createElement("div", { className: "iterate-settings-title" }, t("\u5206\u8BCA\u6301\u4E45\u5316")),
3679
+ 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
3680
  ),
3096
3681
  React.createElement(
3097
3682
  "div",
3098
3683
  { className: "iterate-scard-actions" },
3099
- React.createElement("span", { className: "iterate-chip" }, "\u672C\u5730\u4FDD\u5B58"),
3684
+ React.createElement("span", { className: "iterate-chip" }, t("\u672C\u5730\u4FDD\u5B58")),
3100
3685
  clearButton
3101
3686
  )
3102
3687
  )
@@ -3110,14 +3695,14 @@ function SettingsPanel(_props) {
3110
3695
  React.createElement(
3111
3696
  "div",
3112
3697
  {},
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")
3698
+ React.createElement("div", { className: "iterate-settings-title" }, t("\u914D\u7F6E\u7BA1\u7406")),
3699
+ 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
3700
  ),
3116
3701
  React.createElement(
3117
3702
  "div",
3118
3703
  { 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")
3704
+ 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")),
3705
+ React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => setShowGuide((v) => !v) }, showGuide ? t("\u6536\u8D77") : t("\u5C55\u5F00"))
3121
3706
  )
3122
3707
  ),
3123
3708
  // Gap #8: wire up the previously-dead `buildConfigFieldInstruction` — pick
@@ -3131,17 +3716,17 @@ function SettingsPanel(_props) {
3131
3716
  {
3132
3717
  className: "iterate-filter-select",
3133
3718
  value: cfgField,
3134
- "aria-label": "\u9009\u62E9\u8981\u4FEE\u6539\u7684\u914D\u7F6E\u5B57\u6BB5",
3719
+ "aria-label": t("\u9009\u62E9\u8981\u4FEE\u6539\u7684\u914D\u7F6E\u5B57\u6BB5"),
3135
3720
  onChange: (e) => setCfgField(e.target.value)
3136
3721
  },
3137
3722
  ...CONFIG_EDIT_FIELDS.map(
3138
- (f) => React.createElement("option", { key: f.key, value: f.key }, `${f.label}\uFF08${f.key}\uFF09`)
3723
+ (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
3724
  )
3140
3725
  ),
3141
3726
  React.createElement(
3142
3727
  "span",
3143
3728
  { className: "iterate-settings-desc" },
3144
- configFieldByKey(cfgField)?.hint ?? ""
3729
+ cfgMeta?.hint[getLang()] ?? ""
3145
3730
  ),
3146
3731
  React.createElement("button", {
3147
3732
  className: "iterate-btn",
@@ -3149,18 +3734,48 @@ function SettingsPanel(_props) {
3149
3734
  "data-copied": cfgCopied ? "" : void 0,
3150
3735
  "data-failed": cfgFailed ? "" : void 0,
3151
3736
  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
3737
+ 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")
3738
+ }, cfgCopied ? t("\u5DF2\u590D\u5236") : cfgFailed ? t("\u590D\u5236\u5931\u8D25") : t("\u751F\u6210\u5199\u5165\u6307\u4EE4")),
3739
+ 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
3740
  ),
3741
+ // Gap #1 (3.7.0): close the write -> read-back loop. Three honest states:
3742
+ // nothing echoed yet, the value the model read back, or a read-back that
3743
+ // never contained this field (defaults are in play — say so, don't blank).
3744
+ React.createElement(
3745
+ "div",
3746
+ { className: "iterate-guide-bar", style: { marginTop: 8, flexWrap: "wrap" } },
3747
+ React.createElement(
3748
+ "span",
3749
+ { className: "iterate-settings-desc" },
3750
+ !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 })
3751
+ ),
3752
+ React.createElement("button", {
3753
+ className: "iterate-btn",
3754
+ "data-primary": "",
3755
+ "data-copied": cfgReadCopied ? "" : void 0,
3756
+ onClick: doCopyConfigRead,
3757
+ title: t('\u590D\u5236\u56DE\u8BFB\u6307\u4EE4\uFF1A\u8BA9\u6A21\u578B\u8C03\u7528 iterate_config(operation:"read") \u5E76\u56DE\u663E\u7ED3\u679C')
3758
+ }, cfgReadCopied ? t("\u5DF2\u590D\u5236") : t("\u56DE\u8BFB\u5F53\u524D\u914D\u7F6E"))
3759
+ ),
3760
+ cfgReadText ? React.createElement(
3761
+ "div",
3762
+ { className: "iterate-guide" },
3763
+ React.createElement(
3764
+ "div",
3765
+ { className: "iterate-guide-bar" },
3766
+ React.createElement("span", {}, t("\u56DE\u8BFB\u6307\u4EE4\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\uFF09")),
3767
+ React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => doCopy(cfgReadText, "cfgRead") }, t("\u590D\u5236"))
3768
+ ),
3769
+ React.createElement("div", { className: "iterate-guide-body" }, cfgReadText)
3770
+ ) : null,
3156
3771
  cfgText ? React.createElement(
3157
3772
  "div",
3158
3773
  { className: "iterate-guide" },
3159
3774
  React.createElement(
3160
3775
  "div",
3161
3776
  { 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")
3777
+ React.createElement("span", {}, t("\u5199\u5165\u6307\u4EE4\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\uFF09")),
3778
+ React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => doCopy(cfgText, "guide") }, t("\u590D\u5236"))
3164
3779
  ),
3165
3780
  React.createElement("div", { className: "iterate-guide-body" }, cfgText)
3166
3781
  ) : null,
@@ -3170,8 +3785,9 @@ function SettingsPanel(_props) {
3170
3785
  React.createElement(
3171
3786
  "div",
3172
3787
  { 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")
3788
+ React.createElement("span", {}, t("iterate.config.yaml \u53EF\u7F16\u8F91\u5B57\u6BB5")),
3789
+ React.createElement("span", { className: "iterate-chip" }, t("\u4E2D\u6587\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\u7684\u5951\u7EA6\u6587\u6848\uFF09")),
3790
+ React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => doCopy(guide, "guide") }, t("\u590D\u5236"))
3175
3791
  ),
3176
3792
  React.createElement("div", { className: "iterate-guide-body" }, guide)
3177
3793
  ) : null
@@ -3185,13 +3801,13 @@ function SettingsPanel(_props) {
3185
3801
  React.createElement(
3186
3802
  "div",
3187
3803
  {},
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")
3804
+ React.createElement("div", { className: "iterate-settings-title" }, t("\u72B6\u6001\u6982\u89C8")),
3805
+ 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
3806
  ),
3191
3807
  React.createElement(
3192
3808
  "div",
3193
3809
  { 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"),
3810
+ 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
3811
  // Gap #4: RUNTIME_ARTIFACTS stops being documentation-only — this
3196
3812
  // copies the instruction that makes the model echo those artifacts
3197
3813
  // into the session so the observatory panels can read them.
@@ -3199,8 +3815,8 @@ function SettingsPanel(_props) {
3199
3815
  className: "iterate-btn",
3200
3816
  "data-copied": pullCopied ? "" : void 0,
3201
3817
  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")
3818
+ }, pullCopied ? t("\u5DF2\u590D\u5236") : t("\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167")),
3819
+ React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => setShowStatus((v) => !v) }, showStatus ? t("\u6536\u8D77") : t("\u5C55\u5F00"))
3204
3820
  )
3205
3821
  ),
3206
3822
  showStatus ? React.createElement(
@@ -3209,8 +3825,9 @@ function SettingsPanel(_props) {
3209
3825
  React.createElement(
3210
3826
  "div",
3211
3827
  { 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")
3828
+ React.createElement("span", {}, t("\u8FD0\u884C\u65F6\u5E03\u5C40\u4E0E\u6E05\u7406")),
3829
+ React.createElement("span", { className: "iterate-chip" }, t("\u4E2D\u6587\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\u7684\u5951\u7EA6\u6587\u6848\uFF09")),
3830
+ React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => doCopy(statusGuide, "status") }, t("\u590D\u5236"))
3214
3831
  ),
3215
3832
  React.createElement("div", { className: "iterate-guide-body" }, statusGuide)
3216
3833
  ) : null
@@ -3225,20 +3842,20 @@ function SettingsPanel(_props) {
3225
3842
  "div",
3226
3843
  {},
3227
3844
  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")
3845
+ 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
3846
  ),
3230
3847
  React.createElement(
3231
3848
  "span",
3232
3849
  { className: "iterate-pill" },
3233
3850
  React.createElement("span", { className: "iterate-pill-dot" }),
3234
- "\u5C31\u7EEA"
3851
+ t("\u5C31\u7EEA")
3235
3852
  )
3236
3853
  )
3237
3854
  );
3238
3855
  return React.createElement(
3239
3856
  "div",
3240
3857
  { "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"),
3858
+ React.createElement("div", { className: "iterate-settings-title", style: { fontSize: 15, fontWeight: 700 } }, t("iterate \u8BBE\u7F6E")),
3242
3859
  banner,
3243
3860
  copyError ? React.createElement(
3244
3861
  "div",
@@ -3246,9 +3863,10 @@ function SettingsPanel(_props) {
3246
3863
  React.createElement(
3247
3864
  "div",
3248
3865
  { 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"
3866
+ 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
3867
  )
3251
3868
  ) : null,
3869
+ langCard,
3252
3870
  themeCard,
3253
3871
  dataCard,
3254
3872
  guideCard,
@@ -3256,18 +3874,18 @@ function SettingsPanel(_props) {
3256
3874
  );
3257
3875
  }
3258
3876
  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" },
3877
+ { key: "live", label: { zh: "\u5B9E\u65F6\u6D3B\u52A8\u6D41", en: "Live activity" }, hotkey: "" },
3878
+ { key: "f1", label: { zh: "\u5BA1\u67E5\u7EBF\u7A0B", en: "Review threads" }, hotkey: "1" },
3879
+ { key: "f2", label: { zh: "\u6536\u655B\u8D8B\u52BF", en: "Convergence trend" }, hotkey: "2" },
3880
+ { key: "f3", label: { zh: "\u53D1\u73B0\u5B9A\u4F4D", en: "Finding locations" }, hotkey: "3" },
3881
+ { key: "f4", label: { zh: "\u4FEE\u590D\u4E0E\u56DE\u6EDA", en: "Fixes & rollback" }, hotkey: "4" },
3882
+ { key: "f5", label: { zh: "\u65AD\u70B9\u6062\u590D", en: "Checkpoint resume" }, hotkey: "5" },
3883
+ { key: "f6", label: { zh: "\u8FD0\u884C\u63A7\u5236\u53F0", en: "Run console" }, hotkey: "6" },
3884
+ { key: "f7", label: { zh: "\u51B3\u7B56\u65F6\u95F4\u7EBF", en: "Decision timeline" }, hotkey: "7" },
3267
3885
  // 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" }
3886
+ { key: "f8", label: { zh: "\u8D28\u91CF\u95E8\u7981", en: "Quality gate" }, hotkey: "8" },
3887
+ { key: "f9", label: { zh: "\u7ECF\u9A8C\u94F6\u884C", en: "Experience bank" }, hotkey: "9" },
3888
+ { key: "f10", label: { zh: "\u9632\u5FA1\u4E8B\u4EF6", en: "Defense events" }, hotkey: "0" }
3271
3889
  ];
3272
3890
  var OBS_HOTKEY_TABS = {
3273
3891
  1: "f1",
@@ -3282,23 +3900,26 @@ var OBS_HOTKEY_TABS = {
3282
3900
  0: "f10",
3283
3901
  "-": "live"
3284
3902
  };
3285
- function obsTabLabel(t) {
3286
- if (t.key === "live") return t.label;
3287
- return `${t.key.toUpperCase()} ${t.label}`;
3903
+ function obsTabLabel(t2) {
3904
+ const label = t2.label[getLang()];
3905
+ if (t2.key === "live") return label;
3906
+ return `${t2.key.toUpperCase()} ${label}`;
3907
+ }
3908
+ function obsHotkeyHint() {
3909
+ 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
3910
  }
3289
- var OBS_HOTKEY_HINT = `${String(OBS_TABS.length)} \u9875 \xB7 \u6309 1-9/0 \u5207 Fn\u3001- \u56DE\u5B9E\u65F6\u6D41`;
3290
3911
  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)" }
3912
+ read: { label: { zh: "\u9605\u8BFB", en: "Read" }, color: "var(--dsw-alias-brand-primary)" },
3913
+ fix: { label: { zh: "\u4FEE\u590D", en: "Fix" }, color: "var(--dsw-alias-state-success-primary)" },
3914
+ rollback: { label: { zh: "\u56DE\u6EDA", en: "Rollback" }, color: "var(--dsw-alias-state-error-primary)" },
3915
+ diff: { label: { zh: "\u5DEE\u5F02", en: "Diff" }, color: "var(--dsw-alias-state-warn-primary)" },
3916
+ review: { label: { zh: "\u5BA1\u67E5", en: "Review" }, color: "var(--dsw-alias-brand-primary)" },
3917
+ triage: { label: { zh: "\u5206\u6D41", en: "Triage" }, color: "var(--dsw-alias-state-warn-primary)" },
3918
+ checkpoint: { label: { zh: "\u65AD\u70B9", en: "Checkpoint" }, color: "var(--dsw-alias-brand-primary)" },
3919
+ validate: { label: { zh: "\u6821\u9A8C", en: "Validate" }, color: "var(--dsw-alias-state-success-primary)" },
3920
+ log: { label: { zh: "\u8BB0\u5F55", en: "Log" }, color: "var(--dsw-alias-label-secondary)" },
3921
+ prune: { label: { zh: "\u6E05\u7406", en: "Prune" }, color: "var(--dsw-alias-state-warn-primary)" },
3922
+ info: { label: { zh: "\u4FE1\u606F", en: "Info" }, color: "var(--dsw-alias-label-secondary)" }
3302
3923
  };
3303
3924
  var OBS_LIVE_FALLBACK_COLOR = "var(--dsw-alias-label-secondary)";
3304
3925
  function formatObsLiveTime(ts) {
@@ -3340,6 +3961,9 @@ function buildObsFixInstruction(f) {
3340
3961
  }
3341
3962
  function ObservatoryPanel(props) {
3342
3963
  const session = props && props.session ? props.session : null;
3964
+ React.useEffect(() => {
3965
+ noteSession(session);
3966
+ }, [session]);
3343
3967
  const manifest = latestTranscript(session);
3344
3968
  const qualityGate = latestQualityGate(session);
3345
3969
  const experienceBank = latestExperienceBank(session);
@@ -3369,9 +3993,9 @@ function ObservatoryPanel(props) {
3369
3993
  if (!doc || !open) return;
3370
3994
  const onKeyDown = (ev) => {
3371
3995
  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;
3996
+ const t2 = ev.target;
3997
+ if (t2 && (t2.tagName === "INPUT" || t2.tagName === "TEXTAREA" || t2.tagName === "SELECT")) return;
3998
+ if (t2 && typeof t2.isContentEditable === "boolean" && t2.isContentEditable) return;
3375
3999
  const rootEl = doc.querySelector('[data-iterate="obs"]');
3376
4000
  if (!rootEl) return;
3377
4001
  const activeEl = doc.activeElement;
@@ -3399,7 +4023,7 @@ function ObservatoryPanel(props) {
3399
4023
  });
3400
4024
  };
3401
4025
  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`;
4026
+ 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
4027
  const toggleThread = (key) => {
3404
4028
  setThreadMode("auto");
3405
4029
  setExpandedThreads((prev) => {
@@ -3451,8 +4075,8 @@ function ObservatoryPanel(props) {
3451
4075
  React.createElement(
3452
4076
  "div",
3453
4077
  { 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}`),
4078
+ React.createElement("span", { className: "iterate-obs-title" }, t("iterate \u89C2\u6D4B\u53F0")),
4079
+ React.createElement("span", { className: "iterate-obs-head-meta" }, tf(`\u6682\u65E0\u8FD0\u884C\u65F6\u89C2\u6D4B\u6570\u636E \xB7 {a0}`, { a0: obsHotkeyHint() })),
3456
4080
  React.createElement("button", {
3457
4081
  className: "iterate-btn",
3458
4082
  "data-copied": copiedKey === "pull-snapshot" ? "" : void 0,
@@ -3460,8 +4084,8 @@ function ObservatoryPanel(props) {
3460
4084
  e.stopPropagation();
3461
4085
  copyInstruction("pull-snapshot", pullInstruction);
3462
4086
  },
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")
4087
+ 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")
4088
+ }, copiedKey === "pull-snapshot" ? t("\u5DF2\u590D\u5236") : t("\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167"))
3465
4089
  )
3466
4090
  );
3467
4091
  }
@@ -3483,7 +4107,7 @@ function ObservatoryPanel(props) {
3483
4107
  const renderThreads = () => {
3484
4108
  const rounds = manifest.rounds || [];
3485
4109
  if (rounds.length === 0) {
3486
- return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u5BA1\u67E5\u7EBF\u7A0B");
4110
+ return React.createElement("div", { className: "iterate-obs-empty" }, t("\u6682\u65E0\u5BA1\u67E5\u7EBF\u7A0B"));
3487
4111
  }
3488
4112
  return React.createElement(
3489
4113
  "div",
@@ -3491,34 +4115,34 @@ function ObservatoryPanel(props) {
3491
4115
  React.createElement(
3492
4116
  "div",
3493
4117
  { 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"),
4118
+ React.createElement("b", {}, t("\u5BA1\u67E5\u7EBF\u7A0B")),
4119
+ React.createElement("button", { className: "iterate-btn", onClick: () => setAllThreads("all"), title: t("\u5C55\u5F00\u5168\u90E8\u7EBF\u7A0B") }, t("\u5168\u90E8\u5C55\u5F00")),
4120
+ React.createElement("button", { className: "iterate-btn", onClick: () => setAllThreads("none"), title: t("\u6536\u8D77\u5168\u90E8\u7EBF\u7A0B") }, t("\u5168\u90E8\u6536\u8D77")),
3497
4121
  React.createElement(
3498
4122
  "span",
3499
4123
  { 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`
4124
+ 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
4125
  )
3502
4126
  ),
3503
4127
  ...rounds.map((r, ri) => {
3504
4128
  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) => {
4129
+ const fCount = threads.reduce((sum, t2) => sum + (t2.findings ? t2.findings.length : 0), 0);
4130
+ 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
4131
  const key = `${ri}-${ti}`;
3508
4132
  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 || [];
4133
+ const dim = th.dimension || t("\u672A\u547D\u540D\u7EF4\u5EA6");
4134
+ const tFindings = th.findings || [];
4135
+ const files = th.readFiles || [];
4136
+ const msgs = th.messages || [];
3513
4137
  return React.createElement(
3514
4138
  "div",
3515
4139
  { key, className: "iterate-obs-block" },
3516
4140
  React.createElement(
3517
4141
  "div",
3518
- { className: "iterate-obs-block-head", "data-click": "", onClick: () => toggleThread(key), title: expanded ? "\u6536\u8D77\u7EBF\u7A0B" : "\u5C55\u5F00\u7EBF\u7A0B" },
4142
+ { className: "iterate-obs-block-head", "data-click": "", onClick: () => toggleThread(key), title: expanded ? t("\u6536\u8D77\u7EBF\u7A0B") : t("\u5C55\u5F00\u7EBF\u7A0B") },
3519
4143
  React.createElement("span", {}, expanded ? "\u2212" : "+"),
3520
4144
  React.createElement("b", {}, dim),
3521
- React.createElement("span", { className: "iterate-obs-chip" }, `attempt ${typeof t.attempt === "number" ? t.attempt : "?"}`),
4145
+ React.createElement("span", { className: "iterate-obs-chip" }, `attempt ${typeof th.attempt === "number" ? th.attempt : "?"}`),
3522
4146
  React.createElement("span", { className: "iterate-obs-chip" }, `${tFindings.length} findings`)
3523
4147
  ),
3524
4148
  expanded ? React.createElement(
@@ -3527,15 +4151,15 @@ function ObservatoryPanel(props) {
3527
4151
  React.createElement(
3528
4152
  "div",
3529
4153
  { className: "iterate-obs-bar" },
3530
- React.createElement("b", {}, "\u53D9\u8FF0"),
3531
- React.createElement("span", { className: "iterate-obs-head-meta" }, `${msgs.length} \u6761`)
4154
+ React.createElement("b", {}, t("\u53D9\u8FF0")),
4155
+ React.createElement("span", { className: "iterate-obs-head-meta" }, tf(`{a0} \u6761`, { a0: msgs.length }))
3532
4156
  ),
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))),
4157
+ ...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
4158
  ...files.length > 0 ? [
3535
4159
  React.createElement(
3536
4160
  "div",
3537
4161
  { key: "files-h", className: "iterate-obs-bar", style: { marginTop: 6 } },
3538
- React.createElement("b", {}, "\u8BFB\u53D6\u6587\u4EF6")
4162
+ React.createElement("b", {}, t("\u8BFB\u53D6\u6587\u4EF6"))
3539
4163
  ),
3540
4164
  ...files.map(
3541
4165
  (f, fi) => React.createElement("div", { key: `f${fi}`, className: "iterate-obs-file" }, String(f))
@@ -3544,9 +4168,9 @@ function ObservatoryPanel(props) {
3544
4168
  React.createElement(
3545
4169
  "div",
3546
4170
  { className: "iterate-obs-bar", style: { marginTop: 6 } },
3547
- React.createElement("b", {}, "\u53D1\u73B0")
4171
+ React.createElement("b", {}, t("\u53D1\u73B0"))
3548
4172
  ),
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}`))
4173
+ ...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
4174
  ) : null
3551
4175
  );
3552
4176
  });
@@ -3557,7 +4181,7 @@ function ObservatoryPanel(props) {
3557
4181
  "div",
3558
4182
  { className: "iterate-obs-block-head" },
3559
4183
  React.createElement("b", {}, `Round ${typeof r.round === "number" ? r.round : ri + 1}`),
3560
- React.createElement("span", { className: "iterate-obs-chip" }, `${threads.length} \u7EBF\u7A0B`),
4184
+ React.createElement("span", { className: "iterate-obs-chip" }, tf(`{a0} \u7EBF\u7A0B`, { a0: threads.length })),
3561
4185
  React.createElement("span", { className: "iterate-obs-head-meta" }, `${fCount} findings`)
3562
4186
  ),
3563
4187
  React.createElement("div", { className: "iterate-obs-block-body" }, ...threadBlocks)
@@ -3568,7 +4192,7 @@ function ObservatoryPanel(props) {
3568
4192
  const renderTrend = () => {
3569
4193
  const conv = manifest.convergence || [];
3570
4194
  if (conv.length === 0) {
3571
- return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u6536\u655B\u6570\u636E");
4195
+ return React.createElement("div", { className: "iterate-obs-empty" }, t("\u6682\u65E0\u6536\u655B\u6570\u636E"));
3572
4196
  }
3573
4197
  const points = conv.map((n, i) => ({ round: i + 1, count: n }));
3574
4198
  const report = latestReport(session);
@@ -3582,11 +4206,11 @@ function ObservatoryPanel(props) {
3582
4206
  React.createElement(
3583
4207
  "tr",
3584
4208
  {},
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")
4209
+ React.createElement("th", {}, t("\u8F6E\u6B21")),
4210
+ React.createElement("th", {}, t("\u53D1\u73B0")),
4211
+ React.createElement("th", {}, t("\u5DF2\u4FEE\u590D")),
4212
+ React.createElement("th", {}, t("\u4E25\u91CD\u5EA6\u5206\u5E03")),
4213
+ React.createElement("th", {}, t("\u9A8C\u8BC1"))
3590
4214
  )
3591
4215
  ),
3592
4216
  React.createElement(
@@ -3595,7 +4219,7 @@ function ObservatoryPanel(props) {
3595
4219
  ...comparison.map((row) => {
3596
4220
  const sevText = Object.entries(row.severities).map(([k, v]) => `${k}:${v}`).join(" \xB7 ");
3597
4221
  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" : ""}`;
4222
+ 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
4223
  return React.createElement(
3600
4224
  "tr",
3601
4225
  { key: `cmp-${row.round}` },
@@ -3617,7 +4241,7 @@ function ObservatoryPanel(props) {
3617
4241
  React.createElement(
3618
4242
  "div",
3619
4243
  { 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}` : ""}`
4244
+ 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
4245
  ),
3622
4246
  comparisonTable ? React.createElement(
3623
4247
  "div",
@@ -3625,11 +4249,11 @@ function ObservatoryPanel(props) {
3625
4249
  React.createElement(
3626
4250
  "div",
3627
4251
  { className: "iterate-obs-block-head" },
3628
- React.createElement("span", {}, "\u8DE8\u8F6E\u5BF9\u6BD4\uFF08\u672C\u4F1A\u8BDD\uFF09"),
4252
+ React.createElement("span", {}, t("\u8DE8\u8F6E\u5BF9\u6BD4\uFF08\u672C\u4F1A\u8BDD\uFF09")),
3629
4253
  React.createElement(
3630
4254
  "span",
3631
4255
  { 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"
4256
+ 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
4257
  )
3634
4258
  ),
3635
4259
  React.createElement("div", { className: "iterate-obs-block-body" }, comparisonTable)
@@ -3639,7 +4263,7 @@ function ObservatoryPanel(props) {
3639
4263
  const renderFindings = () => {
3640
4264
  const findings = manifest.findings || [];
3641
4265
  if (findings.length === 0) {
3642
- return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u53D1\u73B0");
4266
+ return React.createElement("div", { className: "iterate-obs-empty" }, t("\u6682\u65E0\u53D1\u73B0"));
3643
4267
  }
3644
4268
  const opts = buildFilterOptions(findings);
3645
4269
  const { filtered } = filterFindingsWithIndices(findings, f3Filter);
@@ -3653,13 +4277,13 @@ function ObservatoryPanel(props) {
3653
4277
  const filterBar = React.createElement(
3654
4278
  "div",
3655
4279
  { className: "iterate-obs-bar", style: { marginBottom: 8, flexWrap: "wrap" } },
3656
- React.createElement("b", {}, "\u53D1\u73B0"),
4280
+ React.createElement("b", {}, t("\u53D1\u73B0")),
3657
4281
  ...opts.severities.map(
3658
4282
  (s) => React.createElement("button", {
3659
4283
  key: `sev-${s.value}`,
3660
4284
  className: "iterate-filter-chip",
3661
4285
  "data-active": f3Filter.severities.includes(s.value) ? "" : void 0,
3662
- title: `${s.value}\uFF08${s.count} \u9879\uFF09`,
4286
+ title: tf(`{a0}\uFF08{a1} \u9879\uFF09`, { a0: s.value, a1: s.count }),
3663
4287
  onClick: () => toggleSeverity(s.value)
3664
4288
  }, `${s.value} ${s.count}`)
3665
4289
  ),
@@ -3668,13 +4292,13 @@ function ObservatoryPanel(props) {
3668
4292
  {
3669
4293
  className: "iterate-filter-select",
3670
4294
  value: f3Filter.dimensions[0] || "",
3671
- "aria-label": "\u6309\u7EF4\u5EA6\u7B5B\u9009",
4295
+ "aria-label": t("\u6309\u7EF4\u5EA6\u7B5B\u9009"),
3672
4296
  onChange: (e) => {
3673
4297
  const v = e.target.value;
3674
4298
  setF3Filter((prev) => ({ ...prev, dimensions: v ? [v] : [] }));
3675
4299
  }
3676
4300
  },
3677
- React.createElement("option", { value: "" }, "\u5168\u90E8\u7EF4\u5EA6"),
4301
+ React.createElement("option", { value: "" }, t("\u5168\u90E8\u7EF4\u5EA6")),
3678
4302
  ...opts.dimensions.map(
3679
4303
  (d) => React.createElement("option", { key: d.value, value: d.value }, `${d.value} (${d.count})`)
3680
4304
  )
@@ -3682,16 +4306,16 @@ function ObservatoryPanel(props) {
3682
4306
  React.createElement("input", {
3683
4307
  className: "iterate-filter-search",
3684
4308
  type: "search",
3685
- placeholder: "\u641C\u7D22\u53D1\u73B0\u2026",
3686
- "aria-label": "\u641C\u7D22\u53D1\u73B0",
4309
+ placeholder: t("\u641C\u7D22\u53D1\u73B0\u2026"),
4310
+ "aria-label": t("\u641C\u7D22\u53D1\u73B0"),
3687
4311
  value: f3Filter.search,
3688
4312
  onChange: (e) => setF3Filter((prev) => ({ ...prev, search: e.target.value }))
3689
4313
  }),
3690
4314
  filterActive ? React.createElement("button", {
3691
4315
  className: "iterate-filter-clear",
3692
4316
  onClick: () => setF3Filter({ severities: [], dimensions: [], search: "" }),
3693
- title: "\u6E05\u9664\u5168\u90E8\u7B5B\u9009"
3694
- }, "\u6E05\u9664\u7B5B\u9009") : null,
4317
+ title: t("\u6E05\u9664\u5168\u90E8\u7B5B\u9009")
4318
+ }, t("\u6E05\u9664\u7B5B\u9009")) : null,
3695
4319
  React.createElement("span", { className: "iterate-filter-count" }, `${filtered.length}/${findings.length}`)
3696
4320
  );
3697
4321
  const findingBlocks = filtered.map((f, i) => {
@@ -3716,7 +4340,7 @@ function ObservatoryPanel(props) {
3716
4340
  React.createElement("span", {}, severityLabel(finding.severity)),
3717
4341
  React.createElement("span", { className: "iterate-obs-file" }, loc),
3718
4342
  React.createElement("span", {}, String(finding.dimension || "")),
3719
- finding.acknowledged === true ? React.createElement("span", { className: "iterate-obs-chip" }, "\u5DF2\u786E\u8BA4") : null
4343
+ finding.acknowledged === true ? React.createElement("span", { className: "iterate-obs-chip" }, t("\u5DF2\u786E\u8BA4")) : null
3720
4344
  ),
3721
4345
  React.createElement(
3722
4346
  "div",
@@ -3729,21 +4353,21 @@ function ObservatoryPanel(props) {
3729
4353
  className: "iterate-btn",
3730
4354
  "data-copied": copiedKey === `${k}-fix` ? "" : void 0,
3731
4355
  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"),
4356
+ title: t("\u590D\u5236\u8BE5 finding \u7684\u5EFA\u8BAE\u4FEE\u590D\u6587\u672C")
4357
+ }, copiedKey === `${k}-fix` ? t("\u5DF2\u590D\u5236") : t("\u590D\u5236 suggested_fix")),
3734
4358
  React.createElement("button", {
3735
4359
  className: "iterate-btn",
3736
4360
  "data-copied": copiedKey === `${k}-known` ? "" : void 0,
3737
4361
  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"),
4362
+ title: t("\u590D\u5236 iterate_triage \u6307\u4EE4\u6587\u672C\uFF08\u6807\u8BB0\u4E3A known_intentional\uFF09")
4363
+ }, copiedKey === `${k}-known` ? t("\u5DF2\u590D\u5236") : "known_intentional"),
3740
4364
  React.createElement("button", {
3741
4365
  className: "iterate-btn",
3742
4366
  "data-primary": "",
3743
4367
  "data-copied": copiedKey === `${k}-repair` ? "" : void 0,
3744
4368
  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")
4369
+ title: t("\u590D\u5236 iterate_fix \u6307\u4EE4\u6587\u672C")
4370
+ }, copiedKey === `${k}-repair` ? t("\u5DF2\u590D\u5236") : t("\u590D\u5236\u4FEE\u590D\u6307\u4EE4"))
3747
4371
  )
3748
4372
  )
3749
4373
  );
@@ -3753,7 +4377,7 @@ function ObservatoryPanel(props) {
3753
4377
  "div",
3754
4378
  {},
3755
4379
  filterBar,
3756
- React.createElement("div", { className: "iterate-obs-empty" }, "\u65E0\u5339\u914D\u7684\u53D1\u73B0")
4380
+ React.createElement("div", { className: "iterate-obs-empty" }, t("\u65E0\u5339\u914D\u7684\u53D1\u73B0"))
3757
4381
  );
3758
4382
  }
3759
4383
  return React.createElement("div", {}, filterBar, ...findingBlocks);
@@ -3761,7 +4385,7 @@ function ObservatoryPanel(props) {
3761
4385
  const renderFixes = () => {
3762
4386
  const fixes = manifest.fixes || [];
3763
4387
  if (fixes.length === 0) {
3764
- return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u4FEE\u590D\u8BB0\u5F55");
4388
+ return React.createElement("div", { className: "iterate-obs-empty" }, t("\u6682\u65E0\u4FEE\u590D\u8BB0\u5F55"));
3765
4389
  }
3766
4390
  return React.createElement("div", {}, ...fixes.map((f, i) => {
3767
4391
  const k = `f4-${i}`;
@@ -3777,7 +4401,7 @@ function ObservatoryPanel(props) {
3777
4401
  { className: "iterate-obs-block-head" },
3778
4402
  React.createElement("b", {}, id),
3779
4403
  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"),
4404
+ React.createElement("span", { className: "iterate-obs-chip" }, f.success === true ? t("\u6210\u529F") : t("\u5931\u8D25")),
3781
4405
  typeof f.round === "number" ? React.createElement("span", { className: "iterate-obs-head-meta" }, `Round ${f.round}`) : null
3782
4406
  ),
3783
4407
  React.createElement(
@@ -3794,8 +4418,8 @@ function ObservatoryPanel(props) {
3794
4418
  "data-danger": "",
3795
4419
  "data-copied": copiedKey === `${k}-rb` ? "" : void 0,
3796
4420
  onClick: () => copyInstruction(`${k}-rb`, rollbackText),
3797
- title: "\u590D\u5236 iterate_rollback \u6307\u4EE4\u6587\u672C"
3798
- }, copiedKey === `${k}-rb` ? "\u5DF2\u590D\u5236" : "\u56DE\u6EDA")
4421
+ title: t("\u590D\u5236 iterate_rollback \u6307\u4EE4\u6587\u672C")
4422
+ }, copiedKey === `${k}-rb` ? t("\u5DF2\u590D\u5236") : t("\u56DE\u6EDA"))
3799
4423
  )
3800
4424
  )
3801
4425
  );
@@ -3807,7 +4431,7 @@ function ObservatoryPanel(props) {
3807
4431
  return React.createElement(
3808
4432
  "div",
3809
4433
  { className: "iterate-obs-empty" },
3810
- diskEmptyStateText(toolCalledInSession(session, "iterate_transcript"), "\u65AD\u70B9")
4434
+ diskEmptyStateText(toolCalledInSession(session, "iterate_transcript"), t("\u65AD\u70B9"), getLang())
3811
4435
  );
3812
4436
  }
3813
4437
  const resumeText = buildCheckpointResumeInstruction();
@@ -3819,7 +4443,7 @@ function ObservatoryPanel(props) {
3819
4443
  React.createElement(
3820
4444
  "div",
3821
4445
  { className: "iterate-obs-block-head" },
3822
- React.createElement("b", {}, "\u65AD\u70B9"),
4446
+ React.createElement("b", {}, t("\u65AD\u70B9")),
3823
4447
  React.createElement("span", { className: "iterate-obs-head-meta" }, String(cp.updatedAt || ""))
3824
4448
  ),
3825
4449
  React.createElement(
@@ -3842,36 +4466,36 @@ function ObservatoryPanel(props) {
3842
4466
  "data-primary": "",
3843
4467
  "data-copied": copiedKey === "cp-resume" ? "" : void 0,
3844
4468
  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"),
4469
+ title: t("\u590D\u5236 iterate_checkpoint resume \u6307\u4EE4\u6587\u672C\uFF08\u52A0\u8F7D\u65AD\u70B9\u5E76\u8BA1\u6570\u4E00\u6B21\u6062\u590D\uFF09")
4470
+ }, copiedKey === "cp-resume" ? t("\u5DF2\u590D\u5236") : t("\u590D\u5236\u6062\u590D\u6307\u4EE4")),
3847
4471
  React.createElement("button", {
3848
4472
  className: "iterate-btn",
3849
4473
  "data-danger": "",
3850
4474
  "data-copied": copiedKey === "cp-clear" ? "" : void 0,
3851
4475
  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")
4476
+ 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")
4477
+ }, copiedKey === "cp-clear" ? t("\u5DF2\u590D\u5236") : t("\u6E05\u9664\u65AD\u70B9"))
3854
4478
  )
3855
4479
  )
3856
4480
  );
3857
4481
  };
3858
4482
  const renderConsole = () => {
3859
4483
  const approval = manifest.approval || { policy: "ask" };
3860
- const policyLabelMap = { ask: "\u8BE2\u95EE\u7528\u6237", deny: "\u62D2\u7EDD", allow: "\u76F4\u63A5\u6267\u884C" };
4484
+ const policyLabelMap = { ask: t("\u8BE2\u95EE\u7528\u6237"), deny: t("\u62D2\u7EDD"), allow: t("\u76F4\u63A5\u6267\u884C") };
3861
4485
  const policyLabel = policyLabelMap[String(approval.policy || "ask")] || String(approval.policy || "ask");
3862
4486
  const nudge = manifest.nudge || null;
3863
4487
  const present = Boolean(nudge && nudge.text);
3864
4488
  const activeNudgeText = nudge && nudge.text ? nudge.text : "";
3865
- const nudgeInstruction = `\u8BF7\u8C03\u7528 \`iterate_transcript\` \u5199\u5165 nudge \u6307\u4EE4\uFF1A
4489
+ const nudgeInstruction = tf(`\u8BF7\u8C03\u7528 \`iterate_transcript\` \u5199\u5165 nudge \u6307\u4EE4\uFF1A
3866
4490
 
3867
4491
  \`\`\`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
4492
+ {a0}
4493
+ \`\`\``, { a0: JSON.stringify({ operation: "nudge", text: nudgeText }, null, 2) });
4494
+ const activeClearInstruction = tf(`\u8BF7\u8C03\u7528 \`iterate_transcript\` \u6E05\u9664\u5F53\u524D nudge\uFF1A
3871
4495
 
3872
4496
  \`\`\`json
3873
- ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3874
- \`\`\``;
4497
+ {a0}
4498
+ \`\`\``, { a0: JSON.stringify({ operation: "nudge", text: null }, null, 2) });
3875
4499
  const validations = Array.isArray(manifest.validations) ? manifest.validations : [];
3876
4500
  const abortedByValidation = String(manifest.stoppedReason || "") === "aborted_by_validation";
3877
4501
  const validationRows = validations.map((v, i) => {
@@ -3893,10 +4517,10 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3893
4517
  React.createElement(
3894
4518
  "span",
3895
4519
  { className: "iterate-obs-chip", "data-status": allowed ? "pass" : "fail" },
3896
- allowed ? "\u901A\u8FC7" : "\u5931\u8D25"
4520
+ allowed ? t("\u901A\u8FC7") : t("\u5931\u8D25")
3897
4521
  ),
3898
4522
  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
4523
+ highlight ? React.createElement("span", { className: "iterate-obs-chip", "data-status": "fail" }, t("\u5BFC\u81F4\u4E2D\u6B62")) : null
3900
4524
  );
3901
4525
  });
3902
4526
  const validationsBlock = React.createElement(
@@ -3905,9 +4529,9 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3905
4529
  React.createElement(
3906
4530
  "div",
3907
4531
  { 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
4532
+ React.createElement("span", {}, t("\u9A8C\u8BC1\u7ED3\u679C")),
4533
+ 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 })),
4534
+ abortedByValidation ? React.createElement("span", { className: "iterate-obs-badge", "data-status": "fail" }, t("\u56E0\u9A8C\u8BC1\u5931\u8D25\u4E2D\u6B62")) : null
3911
4535
  ),
3912
4536
  React.createElement(
3913
4537
  "div",
@@ -3915,7 +4539,7 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3915
4539
  validations.length === 0 ? React.createElement(
3916
4540
  "div",
3917
4541
  { 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"
4542
+ 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
4543
  ) : React.createElement(React.Fragment, null, ...validationRows)
3920
4544
  )
3921
4545
  );
@@ -3925,9 +4549,9 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3925
4549
  React.createElement(
3926
4550
  "div",
3927
4551
  { className: "iterate-obs-block-head" },
3928
- React.createElement("span", {}, "\u8FD0\u884C\u63A7\u5236\u53F0"),
4552
+ React.createElement("span", {}, t("\u8FD0\u884C\u63A7\u5236\u53F0")),
3929
4553
  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}`)
4554
+ React.createElement("span", { className: "iterate-obs-head-meta" }, tf(`\u6279\u51C6\u7B56\u7565\uFF1A{a0}`, { a0: policyLabel }))
3931
4555
  ),
3932
4556
  React.createElement(
3933
4557
  "div",
@@ -3935,21 +4559,21 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3935
4559
  present ? React.createElement(
3936
4560
  "div",
3937
4561
  { className: "iterate-obs-bar", style: { marginBottom: 6 } },
3938
- React.createElement("b", {}, "\u5F53\u524D nudge"),
4562
+ React.createElement("b", {}, t("\u5F53\u524D nudge")),
3939
4563
  React.createElement("span", { className: "iterate-obs-msg" }, activeNudgeText),
3940
4564
  React.createElement("button", {
3941
4565
  className: "iterate-btn",
3942
4566
  "data-copied": copiedKey === "nudge-clear" ? "" : void 0,
3943
4567
  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")
4568
+ 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")
4569
+ }, copiedKey === "nudge-clear" ? t("\u5DF2\u590D\u5236\u6E05\u9664\u6307\u4EE4") : t("\u590D\u5236\u6E05\u9664\u6307\u4EE4"))
3946
4570
  ) : null,
3947
4571
  React.createElement("textarea", {
3948
4572
  className: "iterate-obs-input iterate-obs-mono",
3949
4573
  rows: 3,
3950
- placeholder: "\u8D77\u8349\u4E00\u6761 nudge \u65B9\u5411\u6307\u4EE4\u2026",
4574
+ placeholder: t("\u8D77\u8349\u4E00\u6761 nudge \u65B9\u5411\u6307\u4EE4\u2026"),
3951
4575
  value: nudgeText,
3952
- "aria-label": "nudge \u8349\u7A3F",
4576
+ "aria-label": t("nudge \u8349\u7A3F"),
3953
4577
  onChange: (e) => setNudgeText(e.target.value)
3954
4578
  }),
3955
4579
  React.createElement(
@@ -3961,8 +4585,8 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3961
4585
  "data-copied": copiedKey === "nudge" ? "" : void 0,
3962
4586
  disabled: !nudgeText,
3963
4587
  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")
4588
+ title: t("\u590D\u5236 iterate_transcript nudge \u6307\u4EE4\u6587\u672C")
4589
+ }, copiedKey === "nudge" ? t("\u5DF2\u590D\u5236") : t("\u590D\u5236 nudge \u6307\u4EE4"))
3966
4590
  ),
3967
4591
  validationsBlock
3968
4592
  )
@@ -3971,11 +4595,11 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3971
4595
  const renderTimeline = () => {
3972
4596
  const entries = manifest.timeline || [];
3973
4597
  if (entries.length === 0) {
3974
- return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u65F6\u95F4\u7EBF\u6761\u76EE");
4598
+ return React.createElement("div", { className: "iterate-obs-empty" }, t("\u6682\u65E0\u65F6\u95F4\u7EBF\u6761\u76EE"));
3975
4599
  }
3976
- const types = Array.from(new Set(entries.map((t) => String(t.type || "unknown")).filter(Boolean))).sort();
4600
+ const types = Array.from(new Set(entries.map((t2) => String(t2.type || "unknown")).filter(Boolean))).sort();
3977
4601
  const rounds = Array.from(
3978
- new Set(entries.map((t) => typeof t.round === "number" ? String(t.round) : "").filter(Boolean))
4602
+ new Set(entries.map((t2) => typeof t2.round === "number" ? String(t2.round) : "").filter(Boolean))
3979
4603
  ).sort((a, b) => Number(a) - Number(b));
3980
4604
  const filtered = filterTimelineEntries(entries, {
3981
4605
  type: timelineType,
@@ -3989,14 +4613,14 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3989
4613
  setTimelineSearch("");
3990
4614
  };
3991
4615
  const sorted = filtered.slice();
3992
- const rows = sorted.map((t, i) => {
4616
+ const rows = sorted.map((t2, i) => {
3993
4617
  const k = `f7-${i}`;
3994
4618
  let dataText = "";
3995
- if (t.data && typeof t.data === "object") {
4619
+ if (t2.data && typeof t2.data === "object") {
3996
4620
  try {
3997
- dataText = JSON.stringify(t.data);
4621
+ dataText = JSON.stringify(t2.data);
3998
4622
  } catch {
3999
- dataText = String(t.data);
4623
+ dataText = String(t2.data);
4000
4624
  }
4001
4625
  }
4002
4626
  return React.createElement(
@@ -4005,11 +4629,11 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4005
4629
  React.createElement(
4006
4630
  "div",
4007
4631
  { className: "iterate-obs-bar", style: { width: "100%" } },
4008
- React.createElement("span", { className: "iterate-obs-chip" }, String(t.type || "?")),
4632
+ React.createElement("span", { className: "iterate-obs-chip" }, String(t2.type || "?")),
4009
4633
  React.createElement(
4010
4634
  "span",
4011
4635
  { className: "iterate-obs-head-meta" },
4012
- `${typeof t.round === "number" ? `Round ${t.round} \xB7 ` : ""}${String(t.timestamp || "")}`
4636
+ `${typeof t2.round === "number" ? `Round ${t2.round} \xB7 ` : ""}${String(t2.timestamp || "")}`
4013
4637
  )
4014
4638
  ),
4015
4639
  dataText ? React.createElement("div", { className: "iterate-obs-code" }, dataText) : null
@@ -4018,42 +4642,42 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4018
4642
  const filterBar = React.createElement(
4019
4643
  "div",
4020
4644
  { className: "iterate-obs-bar", style: { marginBottom: 8 } },
4021
- React.createElement("b", {}, "\u65F6\u95F4\u7EBF"),
4645
+ React.createElement("b", {}, t("\u65F6\u95F4\u7EBF")),
4022
4646
  React.createElement(
4023
4647
  "select",
4024
4648
  {
4025
4649
  className: "iterate-filter-select",
4026
4650
  value: timelineType,
4027
- "aria-label": "\u6309\u65F6\u95F4\u7EBF\u7C7B\u578B\u7B5B\u9009",
4651
+ "aria-label": t("\u6309\u65F6\u95F4\u7EBF\u7C7B\u578B\u7B5B\u9009"),
4028
4652
  onChange: (e) => setTimelineType(e.target.value)
4029
4653
  },
4030
- React.createElement("option", { value: "" }, "\u5168\u90E8\u7C7B\u578B"),
4031
- ...types.map((t) => React.createElement("option", { key: t, value: t }, t))
4654
+ React.createElement("option", { value: "" }, t("\u5168\u90E8\u7C7B\u578B")),
4655
+ ...types.map((t2) => React.createElement("option", { key: t2, value: t2 }, t2))
4032
4656
  ),
4033
4657
  React.createElement(
4034
4658
  "select",
4035
4659
  {
4036
4660
  className: "iterate-filter-select",
4037
4661
  value: timelineRound,
4038
- "aria-label": "\u6309\u8F6E\u6B21\u7B5B\u9009",
4662
+ "aria-label": t("\u6309\u8F6E\u6B21\u7B5B\u9009"),
4039
4663
  onChange: (e) => setTimelineRound(e.target.value)
4040
4664
  },
4041
- React.createElement("option", { value: "" }, "\u5168\u90E8\u8F6E\u6B21"),
4665
+ React.createElement("option", { value: "" }, t("\u5168\u90E8\u8F6E\u6B21")),
4042
4666
  ...rounds.map((r) => React.createElement("option", { key: r, value: r }, `Round ${r}`))
4043
4667
  ),
4044
4668
  React.createElement("input", {
4045
4669
  className: "iterate-filter-search",
4046
4670
  type: "search",
4047
- placeholder: "\u641C\u7D22\u65F6\u95F4\u7EBF\u2026",
4048
- "aria-label": "\u641C\u7D22\u65F6\u95F4\u7EBF",
4671
+ placeholder: t("\u641C\u7D22\u65F6\u95F4\u7EBF\u2026"),
4672
+ "aria-label": t("\u641C\u7D22\u65F6\u95F4\u7EBF"),
4049
4673
  value: timelineSearch,
4050
4674
  onChange: (e) => setTimelineSearch(e.target.value)
4051
4675
  }),
4052
4676
  filterActive ? React.createElement("button", {
4053
4677
  className: "iterate-filter-clear",
4054
4678
  onClick: clearTimelineFilter,
4055
- title: "\u6E05\u9664\u5168\u90E8\u7B5B\u9009"
4056
- }, "\u6E05\u9664") : null,
4679
+ title: t("\u6E05\u9664\u5168\u90E8\u7B5B\u9009")
4680
+ }, t("\u6E05\u9664")) : null,
4057
4681
  React.createElement("span", { className: "iterate-filter-count" }, `${filtered.length}/${entries.length}`)
4058
4682
  );
4059
4683
  if (rows.length === 0) {
@@ -4061,7 +4685,7 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4061
4685
  "div",
4062
4686
  {},
4063
4687
  filterBar,
4064
- React.createElement("div", { className: "iterate-obs-empty" }, "\u65E0\u5339\u914D\u7684\u65F6\u95F4\u7EBF\u6761\u76EE")
4688
+ React.createElement("div", { className: "iterate-obs-empty" }, t("\u65E0\u5339\u914D\u7684\u65F6\u95F4\u7EBF\u6761\u76EE"))
4065
4689
  );
4066
4690
  }
4067
4691
  return React.createElement("div", {}, filterBar, ...rows);
@@ -4079,29 +4703,29 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4079
4703
  const headerBar = React.createElement(
4080
4704
  "div",
4081
4705
  { className: "iterate-obs-bar", style: { marginBottom: 8 } },
4082
- React.createElement("b", {}, "\u8D28\u91CF\u95E8\u7981"),
4706
+ React.createElement("b", {}, t("\u8D28\u91CF\u95E8\u7981")),
4083
4707
  React.createElement("button", {
4084
4708
  className: "iterate-btn",
4085
4709
  "data-primary": "",
4086
4710
  "data-copied": copiedKey === "qgate" ? "" : void 0,
4087
4711
  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"),
4712
+ title: t("\u590D\u5236 iterate_quality_gate \u67E5\u8BE2\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09")
4713
+ }, copiedKey === "qgate" ? t("\u5DF2\u590D\u5236") : t("\u67E5\u8BE2\u95E8\u7981")),
4090
4714
  React.createElement("button", {
4091
4715
  className: "iterate-btn",
4092
4716
  "data-danger": "",
4093
4717
  "data-copied": copiedKey === "qgate-clear" ? "" : void 0,
4094
4718
  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"),
4719
+ title: t("\u590D\u5236 iterate_quality_gate clear \u6307\u4EE4\u6587\u672C\uFF08\u91CD\u7F6E\u9648\u65E7\u7684 FAIL \u8BC1\u4E66\uFF09")
4720
+ }, copiedKey === "qgate-clear" ? t("\u5DF2\u590D\u5236") : t("\u6E05\u9664\u95E8\u7981")),
4097
4721
  // Gap #4: the browser cannot read `.iterate/` directly — one click
4098
4722
  // copies the pull instruction that makes the model echo it into the stream.
4099
4723
  React.createElement("button", {
4100
4724
  className: "iterate-btn",
4101
4725
  "data-copied": copiedKey === "disk-snapshot" ? "" : void 0,
4102
4726
  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")
4727
+ 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")
4728
+ }, copiedKey === "disk-snapshot" ? t("\u5DF2\u590D\u5236") : t("\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167"))
4105
4729
  );
4106
4730
  if (!hasGate) {
4107
4731
  return React.createElement(
@@ -4114,7 +4738,7 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4114
4738
  React.createElement(
4115
4739
  "div",
4116
4740
  { className: "iterate-obs-block-head" },
4117
- React.createElement("span", {}, "\u8D28\u91CF\u95E8\u7981\u89C6\u56FE"),
4741
+ React.createElement("span", {}, t("\u8D28\u91CF\u95E8\u7981\u89C6\u56FE")),
4118
4742
  React.createElement("span", { className: "iterate-obs-badge" }, "v3.0")
4119
4743
  ),
4120
4744
  React.createElement(
@@ -4125,12 +4749,12 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4125
4749
  { className: "iterate-obs-msg" },
4126
4750
  // Gap #4: distinguish "we pulled it and it is empty" from "we
4127
4751
  // never asked the model to read the disk".
4128
- diskEmptyStateText(pulled, "\u8D28\u91CF\u95E8\u7981\u8BC1\u4E66")
4752
+ diskEmptyStateText(pulled, t("\u8D28\u91CF\u95E8\u7981\u8BC1\u4E66"), getLang())
4129
4753
  ),
4130
4754
  React.createElement(
4131
4755
  "div",
4132
4756
  { 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"
4757
+ 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
4758
  ),
4135
4759
  React.createElement(
4136
4760
  "div",
@@ -4139,14 +4763,14 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4139
4763
  className: "iterate-btn",
4140
4764
  "data-copied": copiedKey === "qgate-instr" ? "" : void 0,
4141
4765
  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"),
4766
+ title: t("\u590D\u5236\u67E5\u8BE2\u6307\u4EE4")
4767
+ }, copiedKey === "qgate-instr" ? t("\u5DF2\u590D\u5236") : t("\u590D\u5236\u67E5\u8BE2\u6307\u4EE4")),
4144
4768
  React.createElement("button", {
4145
4769
  className: "iterate-btn",
4146
4770
  "data-copied": copiedKey === "disk-snapshot-2" ? "" : void 0,
4147
4771
  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")
4772
+ title: t("\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4")
4773
+ }, copiedKey === "disk-snapshot-2" ? t("\u5DF2\u590D\u5236") : t("\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167"))
4150
4774
  )
4151
4775
  )
4152
4776
  )
@@ -4166,7 +4790,7 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4166
4790
  React.createElement(
4167
4791
  "span",
4168
4792
  { 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)}%`
4793
+ 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
4794
  )
4171
4795
  ),
4172
4796
  React.createElement(
@@ -4179,7 +4803,7 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4179
4803
  (d, i) => React.createElement(
4180
4804
  "span",
4181
4805
  { key: `gdim-${i}`, className: "iterate-obs-chip" },
4182
- `${String(d.dimension || "?")} \xB7 \u6536\u655B ${num(d.convergenceRate)}% \xB7 \u8BC4\u5206 ${num(d.score)}`
4806
+ tf(`{a0} \xB7 \u6536\u655B {a1}% \xB7 \u8BC4\u5206 {a2}`, { a0: String(d.dimension || "?"), a1: num(d.convergenceRate), a2: num(d.score) })
4183
4807
  )
4184
4808
  )
4185
4809
  ) : null,
@@ -4201,33 +4825,33 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
4201
4825
  style: { width: `${fill}%` }
4202
4826
  })
4203
4827
  ),
4204
- React.createElement("span", {}, `\u8BC4\u5206 ${num(d.score)} \xB7 \u53D1\u73B0 ${num(d.findingsCount)} \xB7 \u5DF2\u4FEE\u590D ${num(d.fixedCount)}`)
4828
+ 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
4829
  );
4206
4830
  })
4207
4831
  ),
4208
- gate && gate.failReason ? React.createElement("div", { className: "iterate-obs-msg", style: { marginTop: 6 } }, `\u5931\u8D25\u539F\u56E0\uFF1A${String(gate.failReason)}`) : null
4832
+ 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
4833
  )
4210
4834
  )
4211
4835
  );
4212
4836
  };
4213
4837
  const renderExperienceBank = () => {
4214
- const expInstruction = expSearch ? `\u8BF7\u8C03\u7528 \`iterate_experience\` \u641C\u7D22\u7ECF\u9A8C\uFF1A
4838
+ const expInstruction = expSearch ? tf(`\u8BF7\u8C03\u7528 \`iterate_experience\` \u641C\u7D22\u7ECF\u9A8C\uFF1A
4215
4839
 
4216
4840
  \`\`\`json
4217
- ${JSON.stringify({ operation: "search", query: expSearch }, null, 2)}
4218
- \`\`\`` : buildExperienceListInstruction();
4841
+ {a0}
4842
+ \`\`\``, { a0: JSON.stringify({ operation: "search", query: expSearch }, null, 2) }) : buildExperienceListInstruction();
4219
4843
  const expEntries = experienceBank && experienceBank.entries ? experienceBank.entries : [];
4220
4844
  const query = expSearch.trim().toLowerCase();
4221
4845
  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))
4846
+ (e) => [e.pattern, e.description, e.id, e.dimension, e.severity, ...e.tags || []].filter(Boolean).some((t2) => String(t2).toLowerCase().includes(query))
4223
4847
  ) : expEntries;
4224
4848
  const pulled = toolCalledInSession(session, "iterate_experience");
4225
4849
  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
4850
+ const adoptInstruction = tf(`\u8BF7\u8C03\u7528 \`iterate_experience\` \u91C7\u7EB3\u7ECF\u9A8C\u5E76\u5E94\u7528\u5DF2\u9A8C\u8BC1\u4FEE\u6CD5\uFF1A
4227
4851
 
4228
4852
  \`\`\`json
4229
- ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
4230
- \`\`\``;
4853
+ {a0}
4854
+ \`\`\``, { a0: JSON.stringify({ operation: "get", id: e.id }, null, 2) });
4231
4855
  return React.createElement(
4232
4856
  "div",
4233
4857
  { key: `exp-${i}`, className: "iterate-obs-block" },
@@ -4236,7 +4860,7 @@ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
4236
4860
  { className: "iterate-obs-block-head" },
4237
4861
  React.createElement("span", { className: "iterate-obs-chip" }, String(e.dimension || "")),
4238
4862
  React.createElement("span", {}, String(e.pattern || "?")),
4239
- React.createElement("span", { className: "iterate-exp-hit" }, `\u547D\u4E2D ${e.hitCount ?? 0} \u6B21`)
4863
+ React.createElement("span", { className: "iterate-exp-hit" }, tf(`\u547D\u4E2D {a0} \u6B21`, { a0: e.hitCount ?? 0 }))
4240
4864
  ),
4241
4865
  React.createElement(
4242
4866
  "div",
@@ -4250,8 +4874,8 @@ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
4250
4874
  "data-primary": "",
4251
4875
  "data-copied": copiedKey === `exp-adopt-${i}` ? "" : void 0,
4252
4876
  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
4877
+ title: t("\u590D\u5236\u91C7\u7EB3\u6307\u4EE4\uFF08\u67E5\u8BE2\u8BE5\u7ECF\u9A8C\u5E76\u5E94\u7528\u5230\u5F53\u524D\u4E0A\u4E0B\u6587\uFF09")
4878
+ }, copiedKey === `exp-adopt-${i}` ? t("\u5DF2\u590D\u5236") : t("\u91C7\u7EB3")) : null
4255
4879
  )
4256
4880
  )
4257
4881
  );
@@ -4262,21 +4886,21 @@ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
4262
4886
  React.createElement(
4263
4887
  "div",
4264
4888
  { 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`),
4889
+ React.createElement("b", {}, t("\u7ECF\u9A8C\u94F6\u884C")),
4890
+ React.createElement("span", { className: "iterate-obs-head-meta" }, tf(`\u4F1A\u8BDD\u5185 {a0} \u6761`, { a0: expEntries.length })),
4267
4891
  React.createElement("button", {
4268
4892
  className: "iterate-btn",
4269
4893
  "data-primary": "",
4270
4894
  "data-copied": copiedKey === "exp-list" ? "" : void 0,
4271
4895
  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"),
4896
+ title: t("\u590D\u5236\u5217\u51FA\u7ECF\u9A8C\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09")
4897
+ }, copiedKey === "exp-list" ? t("\u5DF2\u590D\u5236") : t("\u5217\u51FA\u7ECF\u9A8C")),
4274
4898
  React.createElement("button", {
4275
4899
  className: "iterate-btn",
4276
4900
  "data-copied": copiedKey === "exp-snapshot" ? "" : void 0,
4277
4901
  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")
4902
+ 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")
4903
+ }, copiedKey === "exp-snapshot" ? t("\u5DF2\u590D\u5236") : t("\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167"))
4280
4904
  ),
4281
4905
  React.createElement(
4282
4906
  "div",
@@ -4284,8 +4908,8 @@ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
4284
4908
  React.createElement("input", {
4285
4909
  className: "iterate-filter-search",
4286
4910
  type: "search",
4287
- placeholder: "\u641C\u7D22\u7ECF\u9A8C\u6A21\u5F0F\u2026",
4288
- "aria-label": "\u641C\u7D22\u7ECF\u9A8C",
4911
+ placeholder: t("\u641C\u7D22\u7ECF\u9A8C\u6A21\u5F0F\u2026"),
4912
+ "aria-label": t("\u641C\u7D22\u7ECF\u9A8C"),
4289
4913
  value: expSearch,
4290
4914
  onChange: (e) => setExpSearch(e.target.value)
4291
4915
  }),
@@ -4294,36 +4918,36 @@ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
4294
4918
  "data-copied": copiedKey === "exp-search" ? "" : void 0,
4295
4919
  disabled: !expSearch,
4296
4920
  onClick: () => copyInstruction("exp-search", expInstruction),
4297
- title: "\u590D\u5236\u641C\u7D22\u6307\u4EE4"
4298
- }, copiedKey === "exp-search" ? "\u5DF2\u590D\u5236" : "\u641C\u7D22")
4921
+ title: t("\u590D\u5236\u641C\u7D22\u6307\u4EE4")
4922
+ }, copiedKey === "exp-search" ? t("\u5DF2\u590D\u5236") : t("\u641C\u7D22"))
4299
4923
  ),
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(
4924
+ 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
4925
  "div",
4302
4926
  {},
4303
4927
  // 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")
4928
+ React.createElement("div", { className: "iterate-obs-empty" }, diskEmptyStateText(pulled, t("\u7ECF\u9A8C\u94F6\u884C"), getLang())),
4929
+ 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
4930
  )
4307
4931
  );
4308
4932
  };
4309
4933
  const renderDefenseEvents = () => {
4310
- const defenseInstruction = defenseFilter ? `\u8BF7\u8C03\u7528 \`iterate_defense_events\` \u67E5\u8BE2\u9632\u5FA1\u4E8B\u4EF6\uFF1A
4934
+ const defenseInstruction = defenseFilter ? tf(`\u8BF7\u8C03\u7528 \`iterate_defense_events\` \u67E5\u8BE2\u9632\u5FA1\u4E8B\u4EF6\uFF1A
4311
4935
 
4312
4936
  \`\`\`json
4313
- ${JSON.stringify({ operation: "list", type: defenseFilter || void 0 }, null, 2)}
4314
- \`\`\`` : buildDefenseEventsListInstruction();
4937
+ {a0}
4938
+ \`\`\``, { a0: JSON.stringify({ operation: "list", type: defenseFilter || void 0 }, null, 2) }) : buildDefenseEventsListInstruction();
4315
4939
  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" }
4940
+ { value: "", label: t("\u5168\u90E8\u7C7B\u578B") },
4941
+ { value: "precondition_failed", label: t("\u524D\u7F6E\u6821\u9A8C\u5931\u8D25") },
4942
+ { value: "rollback", label: t("\u56DE\u6EDA") },
4943
+ { value: "invariant_violated", label: t("\u4E0D\u53D8\u91CF\u8FDD\u53CD") },
4944
+ { value: "assumption_falsified", label: t("\u5047\u8BBE\u88AB\u8BC1\u4F2A") }
4321
4945
  ];
4322
4946
  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"
4947
+ precondition_failed: t("\u524D\u7F6E\u6821\u9A8C\u5931\u8D25"),
4948
+ rollback: t("\u56DE\u6EDA"),
4949
+ invariant_violated: t("\u4E0D\u53D8\u91CF\u8FDD\u53CD"),
4950
+ assumption_falsified: t("\u5047\u8BBE\u88AB\u8BC1\u4F2A")
4327
4951
  };
4328
4952
  const counts = defenseEvents && defenseEvents.counts || {};
4329
4953
  const allEvents = defenseEvents && defenseEvents.events ? defenseEvents.events : [];
@@ -4332,42 +4956,42 @@ ${JSON.stringify({ operation: "list", type: defenseFilter || void 0 }, null, 2)}
4332
4956
  const headerBar = React.createElement(
4333
4957
  "div",
4334
4958
  { 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}`),
4959
+ React.createElement("b", {}, t("\u9632\u5FA1\u4E8B\u4EF6")),
4960
+ React.createElement("span", { className: "iterate-obs-head-meta" }, tf(`\u4F1A\u8BDD\u5185 {a0} \u6761 / \u7D2F\u8BA1 {a1}`, { a0: allEvents.length, a1: totalCount })),
4337
4961
  React.createElement("button", {
4338
4962
  className: "iterate-btn",
4339
4963
  "data-primary": "",
4340
4964
  "data-copied": copiedKey === "defense-list" ? "" : void 0,
4341
4965
  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"),
4966
+ title: t("\u590D\u5236\u5217\u51FA\u9632\u5FA1\u4E8B\u4EF6\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09")
4967
+ }, copiedKey === "defense-list" ? t("\u5DF2\u590D\u5236") : t("\u5217\u51FA\u4E8B\u4EF6")),
4344
4968
  React.createElement("button", {
4345
4969
  className: "iterate-btn",
4346
4970
  "data-copied": copiedKey === "defense-snapshot" ? "" : void 0,
4347
4971
  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"),
4972
+ 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")
4973
+ }, copiedKey === "defense-snapshot" ? t("\u5DF2\u590D\u5236") : t("\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167")),
4350
4974
  React.createElement("button", {
4351
4975
  className: "iterate-btn",
4352
4976
  "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
4977
+ onClick: () => copyInstruction("defense-counts", tf(`\u8BF7\u8C03\u7528 \`iterate_defense_events\` \u67E5\u8BE2\u4E8B\u4EF6\u7EDF\u8BA1\uFF1A
4354
4978
 
4355
4979
  \`\`\`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"),
4980
+ {a0}
4981
+ \`\`\``, { a0: JSON.stringify({ operation: "counts" }, null, 2) })),
4982
+ title: t("\u590D\u5236\u7EDF\u8BA1\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09")
4983
+ }, copiedKey === "defense-counts" ? t("\u5DF2\u590D\u5236") : t("\u7EDF\u8BA1")),
4360
4984
  React.createElement("button", {
4361
4985
  className: "iterate-btn",
4362
4986
  "data-danger": "",
4363
4987
  "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
4988
+ onClick: () => copyInstruction("defense-clear", tf(`\u8BF7\u8C03\u7528 \`iterate_defense_events\` \u6E05\u9664\u9632\u5FA1\u4E8B\u4EF6\u6D41\uFF1A
4365
4989
 
4366
4990
  \`\`\`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")
4991
+ {a0}
4992
+ \`\`\``, { a0: JSON.stringify({ operation: "clear" }, null, 2) })),
4993
+ title: t("\u590D\u5236 iterate_defense_events clear \u6307\u4EE4\u6587\u672C\uFF08\u91CD\u7F6E\u9648\u65E7\u4E8B\u4EF6\u6D41\uFF09")
4994
+ }, copiedKey === "defense-clear" ? t("\u5DF2\u590D\u5236") : t("\u6E05\u9664\u4E8B\u4EF6"))
4371
4995
  );
4372
4996
  if (allEvents.length === 0 && totalCount === 0) {
4373
4997
  return React.createElement(
@@ -4380,7 +5004,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4380
5004
  React.createElement(
4381
5005
  "div",
4382
5006
  { className: "iterate-obs-block-head" },
4383
- React.createElement("span", {}, "\u9632\u5FA1\u4E8B\u4EF6\u6D41"),
5007
+ React.createElement("span", {}, t("\u9632\u5FA1\u4E8B\u4EF6\u6D41")),
4384
5008
  React.createElement("span", { className: "iterate-obs-badge" }, "v3.0")
4385
5009
  ),
4386
5010
  React.createElement(
@@ -4389,12 +5013,12 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4389
5013
  React.createElement(
4390
5014
  "div",
4391
5015
  { className: "iterate-obs-msg" },
4392
- diskEmptyStateText(toolCalledInSession(session, "iterate_defense_events"), "\u9632\u5FA1\u4E8B\u4EF6\u6D41")
5016
+ diskEmptyStateText(toolCalledInSession(session, "iterate_defense_events"), t("\u9632\u5FA1\u4E8B\u4EF6\u6D41"), getLang())
4393
5017
  ),
4394
5018
  React.createElement(
4395
5019
  "div",
4396
5020
  { 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"
5021
+ 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
5022
  ),
4399
5023
  React.createElement(
4400
5024
  "div",
@@ -4403,8 +5027,8 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4403
5027
  className: "iterate-btn",
4404
5028
  "data-copied": copiedKey === "defense-instr" ? "" : void 0,
4405
5029
  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")
5030
+ title: t("\u590D\u5236\u67E5\u8BE2\u6307\u4EE4")
5031
+ }, copiedKey === "defense-instr" ? t("\u5DF2\u590D\u5236") : t("\u590D\u5236\u67E5\u8BE2\u6307\u4EE4"))
4408
5032
  )
4409
5033
  )
4410
5034
  )
@@ -4418,7 +5042,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4418
5042
  {
4419
5043
  className: "iterate-filter-select",
4420
5044
  value: defenseFilter,
4421
- "aria-label": "\u6309\u4E8B\u4EF6\u7C7B\u578B\u7B5B\u9009",
5045
+ "aria-label": t("\u6309\u4E8B\u4EF6\u7C7B\u578B\u7B5B\u9009"),
4422
5046
  onChange: (e) => setDefenseFilter(e.target.value)
4423
5047
  },
4424
5048
  ...typeOptions.map((opt) => React.createElement("option", { key: opt.value, value: opt.value }, opt.label))
@@ -4426,8 +5050,8 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4426
5050
  defenseFilter ? React.createElement("button", {
4427
5051
  className: "iterate-filter-clear",
4428
5052
  onClick: () => setDefenseFilter(""),
4429
- title: "\u6E05\u9664\u7C7B\u578B\u7B5B\u9009"
4430
- }, "\u6E05\u9664") : null
5053
+ title: t("\u6E05\u9664\u7C7B\u578B\u7B5B\u9009")
5054
+ }, t("\u6E05\u9664")) : null
4431
5055
  );
4432
5056
  let eventRows = [];
4433
5057
  if (visibleEvents.length > 0) {
@@ -4448,7 +5072,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4448
5072
  "div",
4449
5073
  { className: "iterate-obs-block-body" },
4450
5074
  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
5075
+ e.defense ? React.createElement("div", { className: "iterate-obs-msg", style: { marginTop: 4 } }, tf(`\u9632\u7EBF\uFF1A{a0}`, { a0: String(e.defense) })) : null
4452
5076
  )
4453
5077
  );
4454
5078
  });
@@ -4469,7 +5093,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4469
5093
  )
4470
5094
  )
4471
5095
  ),
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")
5096
+ 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
5097
  );
4474
5098
  };
4475
5099
  const renderLive = () => {
@@ -4481,29 +5105,31 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4481
5105
  return React.createElement(
4482
5106
  "div",
4483
5107
  { 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"),
5108
+ 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
5109
  React.createElement(
4486
5110
  "div",
4487
5111
  { 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")
5112
+ React.createElement("button", { className: "iterate-btn", onClick: () => setTab("f1") }, t("\u67E5\u770B\u5BA1\u67E5\u7EBF\u7A0B")),
5113
+ React.createElement("button", { className: "iterate-btn", onClick: () => setTab("f3") }, t("\u67E5\u770B\u53D1\u73B0"))
4490
5114
  )
4491
5115
  );
4492
5116
  }
4493
- return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u5B9E\u65F6\u6D3B\u52A8");
5117
+ return React.createElement("div", { className: "iterate-obs-empty" }, t("\u6682\u65E0\u5B9E\u65F6\u6D3B\u52A8"));
4494
5118
  }
4495
5119
  const types = Array.from(new Set(liveEntries.map((e) => String(e.type || "unknown")).filter(Boolean))).sort();
4496
5120
  const filtered = filterLiveEntries(liveEntries, liveType);
4497
5121
  const rows = filtered.map((e, i) => {
4498
5122
  const type = String(e.type || "unknown");
4499
- const meta = OBS_LIVE_META[type] || { label: String(e.type || "?"), color: OBS_LIVE_FALLBACK_COLOR };
5123
+ const meta = OBS_LIVE_META[type];
5124
+ const typeLabel = meta ? meta.label[getLang()] : String(e.type || "?");
5125
+ const typeColor = meta ? meta.color : OBS_LIVE_FALLBACK_COLOR;
4500
5126
  const tool = String(e.tool || "");
4501
5127
  const target = String(e.target || "");
4502
5128
  const time = formatObsLiveTime(String(e.ts || ""));
4503
5129
  return React.createElement(
4504
5130
  "div",
4505
5131
  { key: `live-${i}`, className: "iterate-obs-row" },
4506
- React.createElement("span", { className: "iterate-obs-livebadge", style: { color: meta.color } }, meta.label),
5132
+ React.createElement("span", { className: "iterate-obs-livebadge", style: { color: typeColor } }, typeLabel),
4507
5133
  tool ? React.createElement("span", { className: "iterate-obs-chip" }, tool) : null,
4508
5134
  React.createElement("span", { className: "iterate-obs-msg" }, target || "\u2014"),
4509
5135
  React.createElement("span", { className: "iterate-obs-head-meta" }, time)
@@ -4512,23 +5138,23 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4512
5138
  const filterBar = React.createElement(
4513
5139
  "div",
4514
5140
  { className: "iterate-obs-bar", style: { marginBottom: 8 } },
4515
- React.createElement("b", {}, "\u5B9E\u65F6\u6D3B\u52A8"),
5141
+ React.createElement("b", {}, t("\u5B9E\u65F6\u6D3B\u52A8")),
4516
5142
  React.createElement(
4517
5143
  "select",
4518
5144
  {
4519
5145
  className: "iterate-filter-select",
4520
5146
  value: liveType,
4521
- "aria-label": "\u6309\u6D3B\u52A8\u7C7B\u578B\u7B5B\u9009",
5147
+ "aria-label": t("\u6309\u6D3B\u52A8\u7C7B\u578B\u7B5B\u9009"),
4522
5148
  onChange: (e) => setLiveType(e.target.value)
4523
5149
  },
4524
- React.createElement("option", { value: "" }, "\u5168\u90E8\u6D3B\u52A8"),
4525
- ...types.map((t) => React.createElement("option", { key: t, value: t }, t))
5150
+ React.createElement("option", { value: "" }, t("\u5168\u90E8\u6D3B\u52A8")),
5151
+ ...types.map((t2) => React.createElement("option", { key: t2, value: t2 }, t2))
4526
5152
  ),
4527
5153
  liveType ? React.createElement("button", {
4528
5154
  className: "iterate-filter-clear",
4529
5155
  onClick: () => setLiveType(""),
4530
- title: "\u6E05\u9664\u7C7B\u578B\u7B5B\u9009"
4531
- }, "\u6E05\u9664") : null,
5156
+ title: t("\u6E05\u9664\u7C7B\u578B\u7B5B\u9009")
5157
+ }, t("\u6E05\u9664")) : null,
4532
5158
  React.createElement("span", { className: "iterate-filter-count" }, `${filtered.length}/${liveEntries.length}`)
4533
5159
  );
4534
5160
  if (rows.length === 0) {
@@ -4536,7 +5162,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4536
5162
  "div",
4537
5163
  {},
4538
5164
  filterBar,
4539
- React.createElement("div", { className: "iterate-obs-empty" }, "\u65E0\u5339\u914D\u7684\u5B9E\u65F6\u6D3B\u52A8")
5165
+ React.createElement("div", { className: "iterate-obs-empty" }, t("\u65E0\u5339\u914D\u7684\u5B9E\u65F6\u6D3B\u52A8"))
4540
5166
  );
4541
5167
  }
4542
5168
  return React.createElement("div", {}, filterBar, ...rows);
@@ -4576,14 +5202,14 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4576
5202
  React.createElement(
4577
5203
  "div",
4578
5204
  { 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"),
5205
+ React.createElement("span", { className: "iterate-obs-title" }, t("iterate \u89C2\u6D4B\u53F0")),
4580
5206
  React.createElement("span", { className: "iterate-obs-badge", "data-live": live ? "" : void 0 }, runStatusText(live, manifest)),
4581
5207
  React.createElement("span", { className: "iterate-obs-head-meta" }, headMeta || "runtime"),
4582
5208
  // Gap #14: make the tab system discoverable without expanding it.
4583
5209
  React.createElement("span", {
4584
5210
  className: "iterate-obs-head-meta",
4585
- title: OBS_HOTKEY_HINT
4586
- }, OBS_HOTKEY_HINT),
5211
+ title: obsHotkeyHint()
5212
+ }, obsHotkeyHint()),
4587
5213
  React.createElement("button", {
4588
5214
  className: "iterate-btn",
4589
5215
  "data-ghost": "",
@@ -4591,7 +5217,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4591
5217
  e.stopPropagation();
4592
5218
  setOpen((v) => !v);
4593
5219
  }
4594
- }, open ? "\u6536\u8D77" : "\u5C55\u5F00")
5220
+ }, open ? t("\u6536\u8D77") : t("\u5C55\u5F00"))
4595
5221
  ),
4596
5222
  open ? React.createElement(
4597
5223
  "div",
@@ -4602,14 +5228,14 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4602
5228
  React.createElement(
4603
5229
  "div",
4604
5230
  { 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"),
5231
+ React.createElement("b", {}, t("\u590D\u5236\u672A\u6210\u529F")),
5232
+ 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
5233
  React.createElement("button", {
4608
5234
  className: "iterate-btn",
4609
5235
  "data-ghost": "",
4610
- "aria-label": "\u5173\u95ED\u590D\u5236\u5931\u8D25\u63D0\u793A",
5236
+ "aria-label": t("\u5173\u95ED\u590D\u5236\u5931\u8D25\u63D0\u793A"),
4611
5237
  onClick: () => setCopyFailText(null)
4612
- }, "\u5173\u95ED")
5238
+ }, t("\u5173\u95ED"))
4613
5239
  ),
4614
5240
  React.createElement("div", { className: "iterate-obs-code" }, copyFailText)
4615
5241
  ) : null,
@@ -4626,8 +5252,8 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4626
5252
  "data-primary": "",
4627
5253
  "data-copied": copiedKey === "export" ? "" : void 0,
4628
5254
  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"),
5255
+ 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")
5256
+ }, copiedKey === "export" ? t("\u5DF2\u5BFC\u51FA") : t("\u5BFC\u51FA JSON")),
4631
5257
  // Gap #4: `.iterate/` artifacts are unreachable from the browser;
4632
5258
  // this copies the one instruction that makes the model echo them
4633
5259
  // into the session stream (which is what every panel scans).
@@ -4636,21 +5262,21 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
4636
5262
  "data-copied": copiedKey === "pull-snapshot" ? "" : void 0,
4637
5263
  onClick: () => copyInstruction("pull-snapshot", pullInstruction),
4638
5264
  title: pullTitle
4639
- }, copiedKey === "pull-snapshot" ? "\u5DF2\u590D\u5236" : "\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167")
5265
+ }, copiedKey === "pull-snapshot" ? t("\u5DF2\u590D\u5236") : t("\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167"))
4640
5266
  ),
4641
5267
  React.createElement(
4642
5268
  "div",
4643
- { className: "iterate-obs-tabs", role: "tablist", "aria-label": "\u89C2\u6D4B\u53F0\u9875\u7B7E\uFF08\u5FEB\u6377\u952E 1-9 / 0 / -\uFF09" },
5269
+ { className: "iterate-obs-tabs", role: "tablist", "aria-label": t("\u89C2\u6D4B\u53F0\u9875\u7B7E\uFF08\u5FEB\u6377\u952E 1-9 / 0 / -\uFF09") },
4644
5270
  ...OBS_TABS.map(
4645
- (t) => React.createElement("button", {
4646
- key: t.key,
5271
+ (t2) => React.createElement("button", {
5272
+ key: t2.key,
4647
5273
  className: "iterate-obs-tab",
4648
5274
  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))
5275
+ "aria-selected": tab === t2.key,
5276
+ "data-active": tab === t2.key ? "" : void 0,
5277
+ onClick: () => setTab(t2.key),
5278
+ 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) })
5279
+ }, obsTabLabel(t2))
4654
5280
  )
4655
5281
  ),
4656
5282
  React.createElement("div", { className: "iterate-obs-body" }, renderBody())
@@ -4663,11 +5289,21 @@ function selectTurnTail(owner) {
4663
5289
  if (scanSessionForRunSummary(owner.turn)) return { matched: true };
4664
5290
  return null;
4665
5291
  }
5292
+ var LangDashboard = withLang(ConvergenceDashboard);
5293
+ var LangObservatory = withLang(ObservatoryPanel);
5294
+ var LangTurnTail = withLang(TurnTailEntry);
5295
+ var LangProgress = withLang(ProgressCapsule);
5296
+ var LangSettings = withLang(SettingsPanel);
4666
5297
  function apply(ctx) {
4667
5298
  slotsSvc = readSlots(ctx);
4668
5299
  themeSvc = readTheme(ctx);
4669
- storage = createStorage();
4670
- const savedTheme = storage.get(THEME_STORAGE_KEY);
5300
+ storage2 = createStorage();
5301
+ configureLangStorage(storage2);
5302
+ setLang(
5303
+ detectLang(storage2.get(LANG_STORAGE_KEY), typeof navigator !== "undefined" ? navigator.language : ""),
5304
+ false
5305
+ );
5306
+ const savedTheme = storage2.get(THEME_STORAGE_KEY);
4671
5307
  themeEnabled = savedTheme === null ? true : savedTheme === "1";
4672
5308
  if (themeEnabled) applyThemeSkin();
4673
5309
  if (typeof document !== "undefined" && document.createElement && document.head) {
@@ -4697,35 +5333,35 @@ function apply(ctx) {
4697
5333
  "conversation.input.dock",
4698
5334
  () => slotsSvc?.register(
4699
5335
  { name: "conversation.input.dock", id: "iterate-dashboard", order: 90 },
4700
- (props) => React.createElement(ConvergenceDashboard, props)
5336
+ (props) => React.createElement(LangDashboard, props)
4701
5337
  )
4702
5338
  );
4703
5339
  slotsSvc.inject(
4704
5340
  "conversation.input.dock",
4705
5341
  () => slotsSvc?.register(
4706
5342
  { name: "conversation.input.dock", id: "iterate-observatory", order: 91 },
4707
- (props) => React.createElement(ObservatoryPanel, props)
5343
+ (props) => React.createElement(LangObservatory, props)
4708
5344
  )
4709
5345
  );
4710
5346
  slotsSvc.inject(
4711
5347
  "conversation.chat.turnTail",
4712
5348
  () => slotsSvc?.register(
4713
5349
  { name: "conversation.chat.turnTail", id: "iterate-turn-tail", select: selectTurnTail },
4714
- (props) => React.createElement(TurnTailEntry, props)
5350
+ (props) => React.createElement(LangTurnTail, props)
4715
5351
  )
4716
5352
  );
4717
5353
  slotsSvc.inject(
4718
5354
  "shell.overlay",
4719
5355
  () => slotsSvc?.register(
4720
5356
  { name: "shell.overlay", id: "iterate-progress", order: 0 },
4721
- (props) => React.createElement(ProgressCapsule, props)
5357
+ (props) => React.createElement(LangProgress, props)
4722
5358
  )
4723
5359
  );
4724
5360
  slotsSvc.inject(
4725
5361
  "settings.section",
4726
5362
  () => slotsSvc?.register(
4727
5363
  { name: "settings.section", id: "iterate-settings", order: 30, label: () => "iterate" },
4728
- (props) => React.createElement(SettingsPanel, props)
5364
+ (props) => React.createElement(LangSettings, props)
4729
5365
  )
4730
5366
  );
4731
5367
  }