iterate-plugin 3.5.7 → 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.
Files changed (79) hide show
  1. package/README.md +97 -22
  2. package/README.zh-CN.md +79 -19
  3. package/dist/approval-gate.js +143 -23
  4. package/dist/atomic-fs.js +148 -17
  5. package/dist/config-loader.js +516 -35
  6. package/dist/config-write.js +83 -22
  7. package/dist/evidence.js +141 -87
  8. package/dist/file-lock.js +193 -0
  9. package/dist/index.js +61 -28
  10. package/dist/jobs.js +56 -11
  11. package/dist/live.js +54 -14
  12. package/dist/meta-review.js +132 -28
  13. package/dist/method-scope.js +31 -8
  14. package/dist/review-scope.js +32 -9
  15. package/dist/review.js +96 -17
  16. package/dist/session-hooks.js +61 -14
  17. package/dist/skill-prompt.js +396 -66
  18. package/dist/tools/checkpoint.js +85 -19
  19. package/dist/tools/config.js +44 -2
  20. package/dist/tools/context.js +22 -5
  21. package/dist/tools/decision-log.js +106 -28
  22. package/dist/tools/defense-events.js +90 -31
  23. package/dist/tools/defense-store.js +28 -3
  24. package/dist/tools/experience-bank.js +108 -43
  25. package/dist/tools/experience-store.js +54 -4
  26. package/dist/tools/fix.js +347 -119
  27. package/dist/tools/history.js +68 -14
  28. package/dist/tools/present.js +80 -0
  29. package/dist/tools/prune.js +150 -53
  30. package/dist/tools/quality-gate.js +52 -2
  31. package/dist/tools/quality-store.js +17 -1
  32. package/dist/tools/review.js +158 -7
  33. package/dist/tools/transcript.js +173 -36
  34. package/dist/tools/triage.js +67 -12
  35. package/dist/tools/validate.js +115 -24
  36. package/dist/transcript.js +267 -4
  37. package/dist/working-directory.js +114 -0
  38. package/lib/client.js +1912 -512
  39. package/lib/parse.js +1001 -103
  40. package/package.json +16 -9
  41. package/src/approval-gate.ts +160 -23
  42. package/src/atomic-fs.ts +152 -17
  43. package/src/client/i18n.ts +517 -0
  44. package/src/client/index.ts +1031 -369
  45. package/src/client/session-cache.ts +54 -0
  46. package/src/config-loader.ts +549 -36
  47. package/src/config-write.ts +108 -26
  48. package/src/evidence.ts +194 -92
  49. package/src/file-lock.ts +211 -0
  50. package/src/index.ts +60 -29
  51. package/src/jobs.ts +63 -12
  52. package/src/live.ts +50 -12
  53. package/src/meta-review.ts +151 -34
  54. package/src/method-scope.ts +31 -8
  55. package/src/review-scope.ts +33 -10
  56. package/src/review.ts +96 -17
  57. package/src/session-hooks.ts +60 -13
  58. package/src/skill-prompt.ts +396 -66
  59. package/src/tools/checkpoint.ts +81 -19
  60. package/src/tools/config.ts +44 -2
  61. package/src/tools/context.ts +25 -5
  62. package/src/tools/decision-log.ts +114 -23
  63. package/src/tools/defense-events.ts +87 -31
  64. package/src/tools/defense-store.ts +30 -3
  65. package/src/tools/experience-bank.ts +112 -44
  66. package/src/tools/experience-store.ts +54 -5
  67. package/src/tools/fix.ts +258 -20
  68. package/src/tools/history.ts +74 -15
  69. package/src/tools/present.ts +89 -0
  70. package/src/tools/prune.ts +154 -51
  71. package/src/tools/quality-gate.ts +57 -2
  72. package/src/tools/quality-store.ts +18 -1
  73. package/src/tools/review.ts +155 -6
  74. package/src/tools/transcript.ts +178 -36
  75. package/src/tools/triage.ts +70 -13
  76. package/src/tools/validate.ts +138 -32
  77. package/src/transcript.ts +279 -4
  78. package/src/types.ts +29 -1
  79. package/src/working-directory.ts +126 -0
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;
@@ -60,6 +61,16 @@ function safeKeys(o) {
60
61
  return [];
61
62
  }
62
63
  }
64
+ function coerceJsonNode(v) {
65
+ if (typeof v !== "string") return v;
66
+ const t2 = v.trim();
67
+ if (!t2.startsWith("{") && !t2.startsWith("[")) return v;
68
+ try {
69
+ return JSON.parse(t2);
70
+ } catch {
71
+ return v;
72
+ }
73
+ }
63
74
  function scanSessionForResume(obj, seen, maxDepth = 20) {
64
75
  if (maxDepth <= 0) return 0;
65
76
  if (!obj || typeof obj !== "object") return 0;
@@ -165,7 +176,8 @@ function isReviewReport(obj) {
165
176
  obj
166
177
  );
167
178
  const convergence = safeGet(o, "convergence");
168
- 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"));
169
181
  }
170
182
  function findReportInObject(obj, seen, maxDepth = 20) {
171
183
  if (maxDepth <= 0) return null;
@@ -213,19 +225,16 @@ function scanSessionForReport(session) {
213
225
  const call = calls[i];
214
226
  if (!call) continue;
215
227
  if (safeGet(call, "tool") === "iterate_review" || String(safeGet(call, "tool") ?? "").endsWith("iterate_review")) {
216
- const result = safeGet(call, "result");
228
+ const result = coerceJsonNode(safeGet(call, "result"));
217
229
  if (result && typeof result === "object") {
218
230
  const r = (
219
231
  /** @type {Record<string, unknown>} */
220
232
  result
221
233
  );
222
234
  const report = safeGet(r, "report");
223
- if (report && typeof report === "object") {
224
- return (
225
- /** @type {Record<string, unknown>} */
226
- report
227
- );
228
- }
235
+ if (isReviewReport(report)) return report;
236
+ const found = findReportInObject(result);
237
+ if (found) return found;
229
238
  }
230
239
  }
231
240
  }
@@ -244,8 +253,24 @@ function scanSessionForReport(session) {
244
253
  /** @type {Array<Record<string, unknown>>} */
245
254
  msgCalls
246
255
  );
247
- for (const call of calls) {
256
+ for (let j = calls.length - 1; j >= 0; j--) {
257
+ const call = calls[j];
248
258
  if (!call) continue;
259
+ const callName = String(safeGet(call, "name") ?? safeGet(call, "tool") ?? "");
260
+ if (callName === "iterate_review" || callName.endsWith("iterate_review")) {
261
+ for (const key of ["result", "response", "message"]) {
262
+ const node = coerceJsonNode(safeGet(call, key));
263
+ if (!node || typeof node !== "object") continue;
264
+ const r = (
265
+ /** @type {Record<string, unknown>} */
266
+ node
267
+ );
268
+ const report = safeGet(r, "report");
269
+ if (isReviewReport(report)) return report;
270
+ const found = findReportInObject(node);
271
+ if (found) return found;
272
+ }
273
+ }
249
274
  const fn = safeGet(call, "function");
250
275
  if (fn && typeof fn === "object") {
251
276
  const f = (
@@ -315,11 +340,11 @@ function extractTranscript(obj, seen, depth) {
315
340
  obj
316
341
  );
317
342
  if (safeGet(o, "operation") === "capture") {
318
- const t = safeGet(o, "transcript");
319
- if (t && typeof t === "object" && isTranscriptManifest(t)) {
343
+ const t2 = safeGet(o, "transcript");
344
+ if (t2 && typeof t2 === "object" && isTranscriptManifest(t2)) {
320
345
  return attachLive(
321
346
  /** @type {Record<string, unknown>} */
322
- t,
347
+ t2,
323
348
  o
324
349
  );
325
350
  }
@@ -402,10 +427,29 @@ function scanSessionForTranscript(session) {
402
427
  if (!msg) continue;
403
428
  const calls = safeGet(msg, "tool_calls");
404
429
  if (Array.isArray(calls)) {
405
- for (const call of calls) {
430
+ const callList = (
431
+ /** @type {Array<Record<string, unknown>>} */
432
+ calls
433
+ );
434
+ for (let j = callList.length - 1; j >= 0; j--) {
435
+ const call = callList[j];
406
436
  if (!call) continue;
407
- const args = safeGet(call, "arguments");
408
- if (typeof args === "string") {
437
+ const fn = safeGet(call, "function");
438
+ const candidates = [
439
+ // Result surface first: `{ name|tool, result }` message tool calls
440
+ // (the same shape scanSessionForQualityGate already accepts).
441
+ safeGet(call, "result"),
442
+ safeGet(call, "response"),
443
+ safeGet(call, "message"),
444
+ safeGet(call, "arguments"),
445
+ fn && typeof fn === "object" ? safeGet(
446
+ /** @type {Record<string, unknown>} */
447
+ fn,
448
+ "arguments"
449
+ ) : void 0
450
+ ];
451
+ for (const args of candidates) {
452
+ if (args === void 0 || args === null) continue;
409
453
  const found2 = extractTranscript(args);
410
454
  if (found2) return found2;
411
455
  }
@@ -415,7 +459,39 @@ function scanSessionForTranscript(session) {
415
459
  if (found) return found;
416
460
  }
417
461
  }
418
- return null;
462
+ const loose = findFirstInObject(session, isTranscriptManifest);
463
+ return loose ? (
464
+ /** @type {Record<string, unknown>} */
465
+ loose
466
+ ) : null;
467
+ }
468
+ function normalizeValidations(raw) {
469
+ if (!Array.isArray(raw)) return [];
470
+ const out = [];
471
+ for (
472
+ const v of
473
+ /** @type {unknown[]} */
474
+ raw
475
+ ) {
476
+ if (!v || typeof v !== "object") continue;
477
+ const rec = (
478
+ /** @type {Record<string, unknown>} */
479
+ v
480
+ );
481
+ const roundRaw = Number(rec.round);
482
+ const exitRaw = rec.exitCode;
483
+ const command = typeof rec.command === "string" ? rec.command : String(rec.command ?? "");
484
+ if (!command) continue;
485
+ const rejectReason = typeof rec.rejectReason === "string" ? rec.rejectReason : "";
486
+ out.push({
487
+ round: Number.isFinite(roundRaw) && roundRaw > 0 ? Math.floor(roundRaw) : 0,
488
+ command,
489
+ exitCode: typeof exitRaw === "number" && Number.isFinite(exitRaw) ? exitRaw : null,
490
+ allowed: rec.allowed === true,
491
+ ...rejectReason ? { rejectReason } : {}
492
+ });
493
+ }
494
+ return out;
419
495
  }
420
496
  function normalizeTranscript(manifest) {
421
497
  const src = manifest && typeof manifest === "object" ? (
@@ -432,12 +508,12 @@ function normalizeTranscript(manifest) {
432
508
  ) : [];
433
509
  const rounds = asArray(safeGet(src, "rounds")).map((r) => ({
434
510
  round: asNum(safeGet(r, "round")),
435
- threads: asArray(safeGet(r, "threads")).map((t) => ({
436
- dimension: asStr(safeGet(t, "dimension")),
437
- attempt: asNum(safeGet(t, "attempt")),
438
- messages: asArray(safeGet(t, "messages")).map((m) => typeof m === "string" ? m : ""),
439
- readFiles: asArray(safeGet(t, "readFiles")).map((f) => typeof f === "string" ? f : ""),
440
- 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 }))
441
517
  }))
442
518
  }));
443
519
  const cp = safeGet(src, "checkpoint");
@@ -480,7 +556,10 @@ function normalizeTranscript(manifest) {
480
556
  target: asStr(safeGet(e, "target"))
481
557
  })),
482
558
  checkpoint,
483
- timeline: asArray(safeGet(src, "timeline")).map((t) => ({ ...t })),
559
+ timeline: asArray(safeGet(src, "timeline")).map((t2) => ({ ...t2 })),
560
+ // #6: per-round validation rows (command / exitCode / allowed), captured
561
+ // by the workflow and persisted through `iterate_transcript capture`.
562
+ validations: normalizeValidations(safeGet(src, "validations")),
484
563
  nudge,
485
564
  approval: ap && typeof ap === "object" ? { active: asBool(safeGet(ap, "active")), policy: asStr(safeGet(ap, "policy")) || "ask" } : { active: false, policy: "ask" }
486
565
  };
@@ -515,6 +594,11 @@ function isDefenseEventsResult(obj) {
515
594
  }
516
595
  function findFirstInObject(obj, predicate, seen, maxDepth = 20) {
517
596
  if (maxDepth <= 0) return null;
597
+ if (typeof obj === "string") {
598
+ const parsed = coerceJsonNode(obj);
599
+ if (parsed === obj) return null;
600
+ return findFirstInObject(parsed, predicate, seen, maxDepth - 1);
601
+ }
518
602
  if (!obj || typeof obj !== "object") return null;
519
603
  const s = seen || /* @__PURE__ */ new Set();
520
604
  if (s.has(obj)) return null;
@@ -551,7 +635,7 @@ function findExperienceResultInObject(obj) {
551
635
  function findDefenseResultInObject(obj) {
552
636
  return findFirstInObject(obj, (o) => isDefenseEventsResult(o));
553
637
  }
554
- function latestToolResultNode(session, toolName) {
638
+ function latestToolResultNode(session, toolName, find) {
555
639
  if (!session || typeof session !== "object") return null;
556
640
  const s = (
557
641
  /** @type {Record<string, unknown>} */
@@ -569,9 +653,9 @@ function latestToolResultNode(session, toolName) {
569
653
  const tool = String(safeGet(call, "tool") ?? "");
570
654
  if (tool !== toolName && !tool.endsWith(toolName)) continue;
571
655
  const result = safeGet(call, "result");
572
- if (result !== void 0 && result !== null) return result;
656
+ if (result !== void 0 && result !== null && find(result)) return result;
573
657
  const message = safeGet(call, "message");
574
- if (message !== void 0 && message !== null) return message;
658
+ if (message !== void 0 && message !== null && find(message)) return message;
575
659
  }
576
660
  }
577
661
  const messages = safeGet(s, "messages");
@@ -595,14 +679,15 @@ function latestToolResultNode(session, toolName) {
595
679
  const name2 = String(safeGet(call, "name") ?? safeGet(call, "tool") ?? "");
596
680
  if (name2 !== toolName && !name2.endsWith(toolName)) continue;
597
681
  const result = safeGet(call, "result") ?? safeGet(call, "response") ?? safeGet(call, "message");
598
- if (result !== void 0 && result !== null) return result;
682
+ if (result !== void 0 && result !== null && find(result)) return result;
599
683
  }
600
684
  }
601
685
  for (let i = msgs.length - 1; i >= 0; i--) {
602
686
  const msg = msgs[i];
603
687
  if (!msg) continue;
604
688
  const content = safeGet(msg, "content");
605
- if (content !== void 0 && content !== null) return content;
689
+ if (content === void 0 || content === null) continue;
690
+ if (find(content)) return content;
606
691
  }
607
692
  }
608
693
  return null;
@@ -651,11 +736,13 @@ function normalizeQualityGateSnapshot(raw) {
651
736
  };
652
737
  }
653
738
  function scanSessionForQualityGate(session) {
654
- const node = latestToolResultNode(session, "iterate_quality_gate");
655
- if (node === null) return null;
656
- const found = findQualityGateInObject(node);
657
- if (!found) return null;
658
- return normalizeQualityGateSnapshot(found);
739
+ const node = latestToolResultNode(session, "iterate_quality_gate", findQualityGateInObject);
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;
659
746
  }
660
747
  function normalizeExperienceBankResult(raw) {
661
748
  const src = raw && typeof raw === "object" ? (
@@ -695,17 +782,19 @@ function normalizeExperienceBankResult(raw) {
695
782
  hitCount: asNum(safeGet(rec, "hitCount")),
696
783
  lastHitAt: asStr(safeGet(rec, "lastHitAt")) || null,
697
784
  files: asArr(safeGet(rec, "files")).map((f) => typeof f === "string" ? f : ""),
698
- tags: asArr(safeGet(rec, "tags")).map((t) => typeof t === "string" ? t : "")
785
+ tags: asArr(safeGet(rec, "tags")).map((t2) => typeof t2 === "string" ? t2 : "")
699
786
  };
700
787
  })
701
788
  };
702
789
  }
703
790
  function scanSessionForExperienceBank(session) {
704
- const node = latestToolResultNode(session, "iterate_experience");
705
- if (node === null) return null;
706
- const found = findExperienceResultInObject(node);
707
- if (!found) return null;
708
- return normalizeExperienceBankResult(found);
791
+ const node = latestToolResultNode(session, "iterate_experience", findExperienceResultInObject);
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;
709
798
  }
710
799
  function normalizeDefenseEventsResult(raw) {
711
800
  const src = raw && typeof raw === "object" ? (
@@ -757,11 +846,13 @@ function normalizeDefenseEventsResult(raw) {
757
846
  };
758
847
  }
759
848
  function scanSessionForDefenseEvents(session) {
760
- const node = latestToolResultNode(session, "iterate_defense_events");
761
- if (node === null) return null;
762
- const found = findDefenseResultInObject(node);
763
- if (!found) return null;
764
- return normalizeDefenseEventsResult(found);
849
+ const node = latestToolResultNode(session, "iterate_defense_events", findDefenseResultInObject);
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;
765
856
  }
766
857
  function isRunSummary(obj) {
767
858
  if (!obj || typeof obj !== "object") return false;
@@ -871,13 +962,14 @@ function normalizeReport(report) {
871
962
  );
872
963
  const rounds = (
873
964
  /** @type {Array<unknown>} */
874
- report.rounds ?? []
965
+ Array.isArray(report.rounds) ? report.rounds : []
875
966
  );
876
967
  const findings = (
877
968
  /** @type {Array<Record<string, unknown>>} */
878
- report.findings ?? []
969
+ Array.isArray(report.findings) ? report.findings : []
879
970
  );
880
971
  const totalRounds = typeof convergence.totalRounds === "number" ? convergence.totalRounds : rounds.length;
972
+ const explicitReason = typeof convergence.stoppedReason === "string" && convergence.stoppedReason.trim() ? convergence.stoppedReason : null;
881
973
  const normalizedConvergence = {
882
974
  totalRounds,
883
975
  findingsByRound: Array.isArray(convergence.findingsByRound) ? convergence.findingsByRound : rounds.map((r) => {
@@ -888,7 +980,7 @@ function normalizeReport(report) {
888
980
  return Array.isArray(rr?.findings) ? rr.findings.length : 0;
889
981
  }),
890
982
  converged: convergence.converged === true,
891
- stoppedReason: convergence.stoppedReason ?? (rounds.length < totalRounds ? "converged" : "max_rounds_reached")
983
+ stoppedReason: explicitReason ?? (convergence.converged === true ? "converged" : rounds.length >= totalRounds ? "max_rounds_reached" : null)
892
984
  };
893
985
  let summary = report.summary;
894
986
  if (!summary || typeof summary !== "object") {
@@ -921,17 +1013,18 @@ function normalizeReport(report) {
921
1013
  };
922
1014
  }
923
1015
  function computeSummaryFromFindings(findings) {
1016
+ const list = Array.isArray(findings) ? findings : [];
924
1017
  const counts = { critical: 0, high: 0, medium: 0, low: 0 };
925
- const byDimension = {};
926
- for (const f of findings) {
1018
+ const byDimension = /* @__PURE__ */ Object.create(null);
1019
+ for (const f of list) {
927
1020
  if (!f || typeof f !== "object") continue;
928
1021
  const sev = String(f.severity ?? "low");
929
- if (sev in counts) counts[sev]++;
1022
+ if (Object.prototype.hasOwnProperty.call(counts, sev)) counts[sev]++;
930
1023
  const dim = String(f.dimension ?? "unknown");
931
1024
  byDimension[dim] = (byDimension[dim] ?? 0) + 1;
932
1025
  }
933
1026
  return {
934
- totalFindings: findings.length,
1027
+ totalFindings: list.length,
935
1028
  critical: counts.critical,
936
1029
  high: counts.high,
937
1030
  medium: counts.medium,
@@ -952,7 +1045,7 @@ function computeConvergenceProgress(report) {
952
1045
  function currentRoundNumber(report) {
953
1046
  const rounds = (
954
1047
  /** @type {Array<Record<string, unknown>>} */
955
- report.rounds ?? []
1048
+ Array.isArray(report.rounds) ? report.rounds : []
956
1049
  );
957
1050
  let max = 0;
958
1051
  for (const r of rounds) {
@@ -975,34 +1068,38 @@ function getTotalRounds(report) {
975
1068
  function severityStats(report) {
976
1069
  const findings = (
977
1070
  /** @type {Array<Record<string, unknown>>} */
978
- report.findings ?? []
1071
+ Array.isArray(report.findings) ? report.findings : []
979
1072
  );
980
1073
  const counts = { critical: 0, high: 0, medium: 0, low: 0 };
981
1074
  for (const f of findings) {
982
1075
  if (!f || typeof f !== "object") continue;
983
1076
  const sev = String(f.severity ?? "low");
984
- if (sev in counts) counts[sev]++;
1077
+ if (Object.prototype.hasOwnProperty.call(counts, sev)) counts[sev]++;
985
1078
  }
986
1079
  return counts;
987
1080
  }
988
1081
  function groupByDimension(report) {
989
1082
  const findings = (
990
1083
  /** @type {Array<Record<string, unknown>>} */
991
- report.findings ?? []
1084
+ Array.isArray(report.findings) ? report.findings : []
992
1085
  );
993
- const groups = {};
1086
+ const groups = /* @__PURE__ */ Object.create(null);
994
1087
  for (const f of findings) {
995
1088
  if (!f || typeof f !== "object") continue;
996
1089
  const dim = String(f.dimension ?? "unknown");
997
- if (!groups[dim]) groups[dim] = [];
1090
+ if (!Object.prototype.hasOwnProperty.call(groups, dim)) groups[dim] = [];
998
1091
  groups[dim].push(f);
999
1092
  }
1000
1093
  return groups;
1001
1094
  }
1095
+ var TRIAGE_VERDICTS = (
1096
+ /** @type {const} */
1097
+ ["keep", "skip", "ignore"]
1098
+ );
1002
1099
  function buildTriageState(report) {
1003
1100
  const findings = (
1004
1101
  /** @type {Array<unknown>} */
1005
- report.findings ?? []
1102
+ Array.isArray(report.findings) ? report.findings : []
1006
1103
  );
1007
1104
  const state = {};
1008
1105
  for (let i = 0; i < findings.length; i++) {
@@ -1013,7 +1110,7 @@ function buildTriageState(report) {
1013
1110
  function hashReport(report) {
1014
1111
  const findings = (
1015
1112
  /** @type {Array<Record<string, unknown>>} */
1016
- report.findings ?? []
1113
+ Array.isArray(report.findings) ? report.findings : []
1017
1114
  );
1018
1115
  let h = 2166136261;
1019
1116
  const mix = (s) => {
@@ -1058,17 +1155,27 @@ function buildApplyInstruction(entries) {
1058
1155
  null,
1059
1156
  2
1060
1157
  );
1061
- return `Please call \`iterate_triage\` with the following payload to apply the triage verdicts:
1158
+ return `\u8BF7\u8C03\u7528 \`iterate_triage\` \u5E94\u7528\u4E0B\u9762\u7684 known_intentional \u6761\u76EE\uFF08\u7C98\u8D34\u540E\u7531\u6A21\u578B\u8C03\u7528 \`iterate_triage\` \u7684 \`operation: "apply"\` \u5199\u5165\u914D\u7F6E\uFF1A\u81EA\u52A8\u53BB\u91CD\u3001\u5199\u5165\u524D\u5907\u4EFD\u3001\u5931\u8D25\u81EA\u52A8\u56DE\u6EDA\uFF09\uFF1A
1062
1159
 
1063
1160
  \`\`\`json
1064
1161
  ${payload}
1162
+ \`\`\`
1163
+
1164
+ \u5199\u5165\u5B8C\u6210\u540E\u8BF7\u590D\u5236\u9762\u677F\u7684\u300C\u56DE\u8BFB\u5DF2\u5199\u5165\u6761\u76EE\u300D\u6307\u4EE4\uFF08\`iterate_triage\` \`operation: "list"\`\uFF09\u6838\u5BF9\u7ED3\u679C\u3002`;
1165
+ }
1166
+ function buildTriageReadbackInstruction() {
1167
+ return `\u8BF7\u8C03\u7528 \`iterate_triage\` \u56DE\u8BFB\u5F53\u524D\u5DF2\u5199\u5165\u914D\u7F6E\u7684 known_intentional \u6761\u76EE\uFF0C\u5E76\u628A\u6BCF\u6761\u7684 file / line / dimension / reason \u539F\u6837\u5217\u51FA\u6765\u4F9B\u6211\u6838\u5BF9\uFF1A
1168
+
1169
+ \`\`\`json
1170
+ ${JSON.stringify({ operation: "list" }, null, 2)}
1065
1171
  \`\`\``;
1066
1172
  }
1067
1173
  function collectIgnoredEntries(triageState, findings) {
1174
+ const list = Array.isArray(findings) ? findings : [];
1068
1175
  const entries = [];
1069
1176
  for (const [idx, verdict] of Object.entries(triageState)) {
1070
1177
  if (verdict !== "ignore") continue;
1071
- const finding = findings[Number(idx)];
1178
+ const finding = list[Number(idx)];
1072
1179
  if (!finding) continue;
1073
1180
  entries.push({
1074
1181
  file: String(finding.file ?? ""),
@@ -1079,6 +1186,207 @@ function collectIgnoredEntries(triageState, findings) {
1079
1186
  }
1080
1187
  return entries;
1081
1188
  }
1189
+ var CONTENT_PLACEHOLDER = "<\u7531\u6A21\u578B\u586B\u5199\uFF1A\u4FEE\u590D\u540E\u7684\u5B8C\u6574\u6587\u4EF6\u5185\u5BB9>";
1190
+ var ROUND_PLACEHOLDER = "<\u7531\u6A21\u578B\u586B\u5199\uFF1A\u5F53\u524D\u8FED\u4EE3\u8F6E\u6B21\uFF08\u22651 \u7684\u6574\u6570\uFF09>";
1191
+ var START_INSTRUCTION_FULL = '\u5F00\u59CB\u4E00\u6B21\u5B8C\u6574\u8FED\u4EE3\uFF08\u5BA1\u67E5 \u2192 \u4FEE\u590D \u2192 \u9A8C\u8BC1 \u2192 \u590D\u76D8\uFF09\uFF1A\u8BF7\u8C03\u7528 `workflow` \u5DE5\u5177\u8FD0\u884C iterate \u5DE5\u4F5C\u6D41\uFF0Cmode: "normal"\uFF0C\u6309\u7CFB\u7EDF\u63D0\u793A\u7684 Iterate Workflow \u6267\u884C plan \u2192 \u591A\u7EBF\u7A0B\u5E76\u884C\u8BC4\u5BA1 \u2192 \u5206\u8BCA \u2192 \u539F\u5B50\u4FEE\u590D \u2192 \u6BCF\u8F6E\u9A8C\u8BC1 \u2192 \u5FAA\u73AF\u76F4\u81F3\u6536\u655B\u6216\u8FBE\u5230 max_rounds \u2192 \u7EC8\u62A5\u5E76 `iterate_transcript` capture\u3002\u5F00\u8DD1\u524D\u5148 `iterate_config({operation:"read"})` \u68C0\u67E5 validation.commands\uFF1A\u82E5\u4E3A\u7A7A\uFF0C\u5148\u505C\u4E0B\u6765\u63D0\u793A\u6211\u7528 `iterate_config` \u5199\u5165\u9A8C\u8BC1\u547D\u4EE4\uFF0C\u5426\u5219\u672C\u8F6E\u8FED\u4EE3\u4E0D\u53D7\u4EFB\u4F55\u6D4B\u8BD5\u4FDD\u62A4\u3002';
1192
+ var START_INSTRUCTION_REVIEW_ONLY = '\u5F00\u59CB\u4E00\u6B21\u4EC5\u8BC4\u5BA1\uFF08dry-run\uFF0C\u4E0D\u4FEE\u6539\u4EFB\u4F55\u6587\u4EF6\uFF09\uFF1A\u8BF7\u8C03\u7528 `workflow` \u5DE5\u5177\u8FD0\u884C iterate \u5DE5\u4F5C\u6D41\uFF0Cmode: "dry-run"\uFF0C\u6309\u7CFB\u7EDF\u63D0\u793A\u7684 Iterate Workflow \u6267\u884C plan \u2192 \u591A\u7EBF\u7A0B\u5E76\u884C\u8BC4\u5BA1 \u2192 \u5206\u8BCA \u2192 `iterate_review aggregate` / `meta-review` \u7EC8\u62A5\u5E76 `iterate_transcript` capture\uFF1B\u5168\u7A0B\u4E0D\u8981\u8C03\u7528 `iterate_fix` / `iterate_rollback`\u3002';
1193
+ var START_INSTRUCTIONS = {
1194
+ full: START_INSTRUCTION_FULL,
1195
+ reviewOnly: START_INSTRUCTION_REVIEW_ONLY
1196
+ };
1197
+ function fixFindingPayload(finding) {
1198
+ const f = finding && typeof finding === "object" ? finding : {};
1199
+ const file = String(f.file ?? "");
1200
+ return {
1201
+ file,
1202
+ ...typeof f.line === "number" && f.line > 0 ? { line: f.line } : {},
1203
+ dimension: String(f.dimension ?? ""),
1204
+ severity: String(f.severity ?? ""),
1205
+ summary: String(f.summary ?? ""),
1206
+ ...f.failure_scenario ? { failure_scenario: String(f.failure_scenario) } : {},
1207
+ ...f.suggested_fix ? { suggested_fix: String(f.suggested_fix) } : {}
1208
+ };
1209
+ }
1210
+ function buildFixInstruction(finding, opts) {
1211
+ const payload = fixFindingPayload(finding);
1212
+ if (!payload.file) return "";
1213
+ const round = opts && Number.isInteger(opts.round) ? opts.round : null;
1214
+ const call = {
1215
+ file: payload.file,
1216
+ content: CONTENT_PLACEHOLDER,
1217
+ finding: payload,
1218
+ round: round !== null ? round : ROUND_PLACEHOLDER
1219
+ };
1220
+ return [
1221
+ "\u8BF7\u4FEE\u590D\u4E0B\u9762\u8FD9\u4E00\u4E2A finding\u3002`iterate_fix` \u4E00\u6B21\u53EA\u63A5\u53D7\u4E00\u4E2A finding\uFF0C\u4E14 file / content / finding / round \u56DB\u4E2A\u53C2\u6570\u5168\u90E8\u5FC5\u586B\uFF1A",
1222
+ `\u6B65\u9AA4 1\uFF1A\u8BFB\u53D6 \`${payload.file}\`\uFF0C\u6309 finding \u751F\u6210\u4FEE\u590D\u540E\u7684\u3010\u5B8C\u6574\u6587\u4EF6\u5185\u5BB9\u3011\uFF08content \u5360\u4F4D\u5FC5\u987B\u6362\u6210\u771F\u5B9E\u6587\u4EF6\u5185\u5BB9\uFF09\u3002`,
1223
+ `\u6B65\u9AA4 2\uFF1A\u8C03\u7528 \`iterate_fix\`\uFF0C\u628A content \u4E0E round \u586B\u6210\u771F\u5B9E\u503C${round !== null ? `\uFF08\u672C\u8F6E round = ${round}\uFF09` : ""}\uFF1A`,
1224
+ "```json",
1225
+ JSON.stringify(call, null, 2),
1226
+ "```",
1227
+ '\u9700\u8981\u8D85\u8FC7 atomic \u884C\u6570\u4E0A\u9650\u65F6\uFF0C\u53E6\u52A0 "force": true\u3002'
1228
+ ].join("\n");
1229
+ }
1230
+ function buildAssignFixesInstruction(findings) {
1231
+ const list = (Array.isArray(findings) ? findings : []).filter((f) => f && typeof f === "object");
1232
+ if (list.length === 0) return "";
1233
+ const payload = list.map((f) => fixFindingPayload(f)).filter((p) => p.file);
1234
+ if (payload.length === 0) return "";
1235
+ return [
1236
+ `\u8BF7\u4FEE\u590D\u4E0B\u9762\u8FD9 ${payload.length} \u4E2A findings\u3002\u6CE8\u610F\uFF1A\`iterate_fix\` \u6BCF\u6B21\u53EA\u5904\u7406\u3010\u4E00\u4E2A\u3011finding\uFF08\u6CA1\u6709\u6570\u7EC4\u5165\u53C2\uFF09\uFF0C\u8BF7\u9010\u4E2A\u8C03\u7528 iterate_fix\u2014\u2014\u4E00\u6B21\u4E00\u4E2A finding\uFF0C\u5904\u7406\u5B8C\u4E00\u4E2A\u518D\u53D1\u8D77\u4E0B\u4E00\u4E2A\uFF0C\u7EDD\u4E0D\u8981\u628A\u6574\u6279\u6570\u7EC4\u5F53\u4F5C\u53C2\u6570\u4F20\u5165\u3002`,
1237
+ "\u6BCF\u4E2A finding \u90FD\u8D70\u540C\u6837\u7684\u4E24\u6B65\uFF1A\u2460 \u8BFB\u53D6\u6587\u4EF6\u751F\u6210\u4FEE\u590D\u540E\u7684\u5B8C\u6574 content\uFF1B\u2461 \u8C03\u7528 iterate_fix\uFF08file / content / finding / round \u5FC5\u586B\uFF0Ccontent \u4E0E round \u7531\u4F60\u586B\u5199\uFF09\u3002",
1238
+ "\u5F85\u4FEE\u590D\u6E05\u5355\uFF1A",
1239
+ "```json",
1240
+ JSON.stringify(payload, null, 2),
1241
+ "```"
1242
+ ].join("\n");
1243
+ }
1244
+ function buildArchitecturalFixInstruction(findings) {
1245
+ const list = (Array.isArray(findings) ? findings : []).filter((f) => f && typeof f === "object");
1246
+ const lines = [
1247
+ '\u8BF7\u6279\u51C6\u5E76\u6267\u884C\u67B6\u6784\u578B\u4FEE\u590D\uFF08\u5141\u8BB8\u8D85\u8FC7 atomic \u884C\u6570\u4E0A\u9650\uFF09\uFF1A\u5BF9\u6BCF\u4E2A\u67B6\u6784\u578B finding \u9010\u4E2A\u8C03\u7528 `iterate_fix`\uFF0C\u4E00\u6B21\u4E00\u4E2A finding\uFF0C\u5E76\u663E\u5F0F\u8BBE\u7F6E `"force": true`\uFF1B',
1248
+ "file / content / finding / round \u56DB\u4E2A\u5FC5\u586B\u53C2\u6570\u4E00\u4E2A\u90FD\u4E0D\u80FD\u5C11\uFF08content = \u4FEE\u590D\u540E\u7684\u5B8C\u6574\u6587\u4EF6\u5185\u5BB9\uFF0Cround = \u5F53\u524D\u8FED\u4EE3\u8F6E\u6B21\uFF0C\u4E24\u8005\u7531\u4F60\u586B\u5199\uFF09\u3002"
1249
+ ];
1250
+ if (list.length > 0) {
1251
+ lines.push("\u672C\u9879\u76EE\u5269\u4F59\u67B6\u6784\u578B finding\uFF1A", "```json", JSON.stringify(list.map((f) => fixFindingPayload(f)), null, 2), "```");
1252
+ }
1253
+ return lines.join("\n");
1254
+ }
1255
+ function buildCheckpointResumeInstruction() {
1256
+ return `\u8BF7\u8C03\u7528 \`iterate_checkpoint\` \u4ECE\u65AD\u70B9\u6062\u590D\u8FED\u4EE3\uFF08\u52A0\u8F7D\u65AD\u70B9\u5E76\u628A resumeCount +1\uFF09\uFF1A
1257
+
1258
+ \`\`\`json
1259
+ ${JSON.stringify({ operation: "resume" }, null, 2)}
1260
+ \`\`\``;
1261
+ }
1262
+ function buildCheckpointClearInstruction() {
1263
+ return `\u8BF7\u8C03\u7528 \`iterate_checkpoint\` \u6E05\u9664\u5F53\u524D\u65AD\u70B9\uFF1A
1264
+
1265
+ \`\`\`json
1266
+ ${JSON.stringify({ operation: "clear" }, null, 2)}
1267
+ \`\`\``;
1268
+ }
1269
+ function buildQualityGateQueryInstruction() {
1270
+ return `\u8BF7\u8C03\u7528 \`iterate_quality_gate\` \u67E5\u8BE2\u5F53\u524D\u8D28\u91CF\u95E8\u7981\u72B6\u6001\uFF08\u8BFB\u53D6\u78C1\u76D8\u4E0A\u6301\u4E45\u5316\u7684\u8BC1\u4E66\uFF09\uFF1A
1271
+
1272
+ \`\`\`json
1273
+ ${JSON.stringify({ operation: "read" }, null, 2)}
1274
+ \`\`\``;
1275
+ }
1276
+ function buildQualityGateClearInstruction() {
1277
+ return `\u8BF7\u8C03\u7528 \`iterate_quality_gate\` \u6E05\u9664\u5F53\u524D\u8D28\u91CF\u95E8\u7981\u8BC1\u4E66\uFF1A
1278
+
1279
+ \`\`\`json
1280
+ ${JSON.stringify({ operation: "clear" }, null, 2)}
1281
+ \`\`\``;
1282
+ }
1283
+ function buildRollbackInstruction(id) {
1284
+ return `\u8BF7\u8C03\u7528 \`iterate_rollback\` \u56DE\u6EDA\u4EE5\u4E0B\u4FEE\u590D\uFF1A
1285
+
1286
+ \`\`\`json
1287
+ ${JSON.stringify({ id: String(id || "") }, null, 2)}
1288
+ \`\`\``;
1289
+ }
1290
+ function buildExperienceListInstruction() {
1291
+ return `\u8BF7\u8C03\u7528 \`iterate_experience\` \u5217\u51FA\u7ECF\u9A8C\u94F6\u884C\u4E2D\u7684\u6240\u6709\u7ECF\u9A8C\uFF0C\u5E76\u628A\u5B8C\u6574\u8FD4\u56DE\u7ED3\u679C\u56DE\u663E\u5230\u4F1A\u8BDD\u91CC\uFF1A
1292
+
1293
+ \`\`\`json
1294
+ ${JSON.stringify({ operation: "list" }, null, 2)}
1295
+ \`\`\``;
1296
+ }
1297
+ function buildDefenseEventsListInstruction() {
1298
+ return `\u8BF7\u8C03\u7528 \`iterate_defense_events\` \u5217\u51FA\u5168\u90E8\u9632\u5FA1\u4E8B\u4EF6\uFF0C\u5E76\u628A\u5B8C\u6574\u8FD4\u56DE\u7ED3\u679C\u56DE\u663E\u5230\u4F1A\u8BDD\u91CC\uFF1A
1299
+
1300
+ \`\`\`json
1301
+ ${JSON.stringify({ operation: "list" }, null, 2)}
1302
+ \`\`\``;
1303
+ }
1304
+ function buildDiskSnapshotInstruction() {
1305
+ const lines = [
1306
+ "\u8BF7\u4ECE\u78C1\u76D8\u62C9\u53D6 iterate \u5FEB\u7167\uFF1A\u9010\u4E2A\u8C03\u7528\u4E0B\u9762\u7684\u53EA\u8BFB\u5DE5\u5177\uFF0C\u5E76\u628A\u6BCF\u4E2A\u5DE5\u5177\u7684\u3010\u5B8C\u6574\u8FD4\u56DE\u7ED3\u679C\u3011\u539F\u6837\u56DE\u663E\u5230\u4F1A\u8BDD\u91CC\uFF08\u4E0D\u8981\u53EA\u7ED9\u6458\u8981\u2014\u2014\u63D2\u4EF6\u9762\u677F\u9760\u8FD9\u4E9B\u56DE\u663E\u586B\u5145\uFF09\uFF1A"
1307
+ ];
1308
+ DISK_SNAPSHOT_SOURCES.forEach((s, i) => {
1309
+ lines.push(`${i + 1}. \`${s.tool}\`\uFF08${s.label}\uFF09\uFF1A${JSON.stringify(s.args)}`);
1310
+ });
1311
+ lines.push("\u56DE\u663E\u540E\uFF0C\u89C2\u6D4B\u53F0\u5B9E\u65F6\u6D41 / F2 \u8DE8\u8F6E\u5BF9\u6BD4 / F5 \u65AD\u70B9 / F8 \u8D28\u91CF\u95E8\u7981 / F9 \u7ECF\u9A8C\u94F6\u884C / F10 \u9632\u5FA1\u4E8B\u4EF6\u4F1A\u81EA\u52A8\u8BFB\u53D6\u8FD9\u4E9B\u7ED3\u679C\u3002");
1312
+ return lines.join("\n");
1313
+ }
1314
+ var DISK_SNAPSHOT_SOURCES = [
1315
+ { tool: "iterate_status", args: {}, label: "\u8FD0\u884C\u72B6\u6001\u6C47\u603B" },
1316
+ { tool: "iterate_transcript", args: { operation: "read" }, label: "transcript \u89C2\u6D4B\u6E05\u5355" },
1317
+ { tool: "iterate_quality_gate", args: { operation: "read" }, label: "\u8D28\u91CF\u95E8\u7981\u8BC1\u4E66" },
1318
+ { tool: "iterate_experience", args: { operation: "list" }, label: "\u7ECF\u9A8C\u94F6\u884C" },
1319
+ { tool: "iterate_defense_events", args: { operation: "list" }, label: "\u9632\u5FA1\u4E8B\u4EF6\u6D41" },
1320
+ { tool: "iterate_history", args: {}, label: "\u51B3\u7B56\u65E5\u5FD7\u4E0E\u4FEE\u590D\u6CE8\u518C\u8868\uFF08\u8DE8\u8F6E/\u8DE8\u4F1A\u8BDD\u5BF9\u6BD4\uFF09" }
1321
+ ];
1322
+ function toolCalledInSession(session, toolName) {
1323
+ if (!session || typeof session !== "object" || typeof toolName !== "string" || !toolName) return false;
1324
+ const s = (
1325
+ /** @type {Record<string, unknown>} */
1326
+ session
1327
+ );
1328
+ const toolCalls = safeGet(s, "toolCalls");
1329
+ if (Array.isArray(toolCalls)) {
1330
+ for (
1331
+ const call of
1332
+ /** @type {unknown[]} */
1333
+ toolCalls
1334
+ ) {
1335
+ if (!call || typeof call !== "object") continue;
1336
+ const t2 = String(safeGet(
1337
+ /** @type {Record<string, unknown>} */
1338
+ call,
1339
+ "tool"
1340
+ ) ?? "");
1341
+ if (t2 === toolName || t2.endsWith(toolName)) return true;
1342
+ }
1343
+ }
1344
+ const messages = safeGet(s, "messages");
1345
+ if (Array.isArray(messages)) {
1346
+ for (
1347
+ const msg of
1348
+ /** @type {unknown[]} */
1349
+ messages
1350
+ ) {
1351
+ if (!msg || typeof msg !== "object") continue;
1352
+ const calls = safeGet(
1353
+ /** @type {Record<string, unknown>} */
1354
+ msg,
1355
+ "tool_calls"
1356
+ );
1357
+ if (!Array.isArray(calls)) continue;
1358
+ for (
1359
+ const call of
1360
+ /** @type {unknown[]} */
1361
+ calls
1362
+ ) {
1363
+ if (!call || typeof call !== "object") continue;
1364
+ const c = (
1365
+ /** @type {Record<string, unknown>} */
1366
+ call
1367
+ );
1368
+ const name2 = String(safeGet(c, "name") ?? safeGet(c, "tool") ?? "");
1369
+ if (name2 === toolName || name2.endsWith(toolName)) return true;
1370
+ const fn = safeGet(c, "function");
1371
+ if (fn && typeof fn === "object" && String(safeGet(
1372
+ /** @type {Record<string, unknown>} */
1373
+ fn,
1374
+ "name"
1375
+ ) ?? "") === toolName) {
1376
+ return true;
1377
+ }
1378
+ }
1379
+ }
1380
+ }
1381
+ return false;
1382
+ }
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
+ }
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`;
1389
+ }
1082
1390
  function normalizeFindingFilter(filter) {
1083
1391
  const f = filter && typeof filter === "object" ? filter : {};
1084
1392
  const severities = Array.isArray(f.severities) ? f.severities.filter((s) => SEVERITY_ORDER.includes(String(s))) : [];
@@ -1122,6 +1430,7 @@ function buildFilterOptions(findings) {
1122
1430
  const severities = SEVERITY_ORDER.map((value) => ({ value, count: 0 }));
1123
1431
  const dimCounts = {};
1124
1432
  for (const f of list) {
1433
+ if (!f || typeof f !== "object") continue;
1125
1434
  const sev = String(f.severity ?? "low");
1126
1435
  const sv = severities.find((s) => s.value === sev);
1127
1436
  if (sv) sv.count++;
@@ -1141,7 +1450,7 @@ function countVerdicts(triageState) {
1141
1450
  return counts;
1142
1451
  }
1143
1452
  function batchSetVerdict(triageState, indices, verdict) {
1144
- if (verdict !== "keep" && verdict !== "skip" && verdict !== "ignore") return triageState;
1453
+ if (!TRIAGE_VERDICTS.includes(verdict)) return triageState;
1145
1454
  if (!Array.isArray(indices) || indices.length === 0) return triageState;
1146
1455
  const next = { ...triageState };
1147
1456
  for (const idx of indices) {
@@ -1211,27 +1520,29 @@ function trendMax(points) {
1211
1520
  }
1212
1521
  return max;
1213
1522
  }
1214
- function stoppedReasonLabel(reason) {
1215
- switch (reason) {
1216
- case "converged":
1217
- return "\u5DF2\u7ED3\u675F \xB7 \u5DF2\u6536\u655B\uFF08\u65E0\u65B0\u53D1\u73B0\uFF09";
1218
- case "max_rounds_reached":
1219
- return "\u5DF2\u7ED3\u675F \xB7 \u8FBE\u5230\u8F6E\u6570\u4E0A\u9650";
1220
- case "aborted_by_validation":
1221
- return "\u5DF2\u7ED3\u675F \xB7 \u9A8C\u8BC1\u5931\u8D25\u540E\u56DE\u6EDA\u505C\u6B62";
1222
- case "aborted_by_config":
1223
- return "\u5DF2\u7ED3\u675F \xB7 \u9A8C\u8BC1\u547D\u4EE4\u4E0D\u5728\u767D\u540D\u5355\uFF08\u914D\u7F6E\u9700\u4FEE\u590D\uFF09";
1224
- case "inconclusive":
1225
- return "\u5DF2\u7ED3\u675F \xB7 \u8F6E\u6B21\u7ED3\u8BBA\u4E0D\u660E\uFF08\u5BA1\u67E5\u8F93\u51FA\u65E0\u6548\uFF09";
1226
- case "schema_invalid":
1227
- return "\u5DF2\u7ED3\u675F \xB7 \u5BA1\u67E5\u8F93\u51FA\u8FDE\u7EED schema \u6821\u9A8C\u5931\u8D25";
1228
- case "no_usable_reviewer_output":
1229
- return "\u5DF2\u7ED3\u675F \xB7 \u65E0\u53EF\u7528\u5BA1\u67E5\u8F93\u51FA";
1230
- default:
1231
- return reason ? `\u5DF2\u7ED3\u675F \xB7 ${reason}` : "\u5DF2\u7ED3\u675F";
1232
- }
1233
- }
1234
- function buildCompletionSummary(report) {
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");
1544
+ }
1545
+ function buildCompletionSummary(report, lang = "zh") {
1235
1546
  const conv = (
1236
1547
  /** @type {Record<string, unknown>} */
1237
1548
  report.convergence ?? {}
@@ -1240,18 +1551,74 @@ function buildCompletionSummary(report) {
1240
1551
  const total = getTotalRounds(report);
1241
1552
  const stats = severityStats(report);
1242
1553
  const converged = conv.converged === true;
1243
- const reason = converged ? "\u5DF2\u6536\u655B" : `\u5DF2\u8FBE\u6700\u5927\u8F6E\u6570 ${total}`;
1244
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}`;
1245
1560
  return `iterate \u8BC4\u5BA1\u5B8C\u6210 \xB7 ${rounds}/${total} \u8F6E \xB7 ${totalFindings} \u9879\u53D1\u73B0 \xB7 ${reason}`;
1246
1561
  }
1247
1562
  var CONFIG_EDIT_FIELDS = [
1248
- { key: "goal", label: "\u76EE\u6807", hint: "\u4E00\u53E5\u8BDD\u63CF\u8FF0\u672C\u6B21\u8FED\u4EE3\u76EE\u6807\uFF08\u5B57\u7B26\u4E32\uFF09" },
1249
- { key: "dimensions", label: "\u5BA1\u67E5\u7EF4\u5EA6", hint: '\u6570\u7EC4\uFF0C\u5982 ["correctness","security"]' },
1250
- { key: "max_rounds", label: "\u6700\u5927\u8F6E\u6570", hint: "\u6B63\u6574\u6570" },
1251
- { key: "review.scope", label: "\u5BA1\u67E5\u8303\u56F4", hint: '"full" \u6216 "changed-only"' },
1252
- { key: "reasoning_effort", label: "\u5BA1\u67E5\u63A8\u7406\u5F3A\u5EA6", hint: '"low" / "medium" / "high"\uFF0C\u7F3A\u7701\u8DDF\u968F\u6A21\u578B\u9ED8\u8BA4' },
1253
- { key: "atomic.max_lines", label: "\u539F\u5B50\u4FEE\u590D\u4E0A\u9650\u884C\u6570", hint: "\u6B63\u6574\u6570" },
1254
- { key: "git.push_per_round", label: "\u6BCF\u8F6E\u63A8\u9001", hint: "true / false" }
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
+ },
1588
+ {
1589
+ key: "validation.commands",
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
+ }
1595
+ },
1596
+ {
1597
+ key: "language",
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
+ }
1603
+ },
1604
+ {
1605
+ key: "personalization.known_intentional",
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" }
1616
+ },
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
+ }
1255
1622
  ];
1256
1623
  function buildConfigEditGuide() {
1257
1624
  const lines = [
@@ -1259,16 +1626,115 @@ function buildConfigEditGuide() {
1259
1626
  "---------------------",
1260
1627
  "\u914D\u7F6E\u6587\u4EF6\uFF1A\u9879\u76EE\u6839\u76EE\u5F55 iterate.config.yaml\u3002",
1261
1628
  "",
1629
+ "\u26A0 \u672A\u914D\u7F6E validation.commands \u65F6\u8FED\u4EE3\u4E0D\u88AB\u4FDD\u62A4\uFF1A\u6BCF\u8F6E validate \u62FF\u5230\u7A7A\u7ED3\u679C\u4F1A\u88AB\u5F53\u4F5C",
1630
+ " \u201C\u9A8C\u8BC1\u901A\u8FC7\u201D\uFF0C\u7B49\u4E8E\u5728\u6CA1\u6709\u4EFB\u4F55\u6D4B\u8BD5\u4FDD\u62A4\u7684\u60C5\u51B5\u4E0B\u7EE7\u7EED\u6536\u655B\u3002\u7B2C\u4E00\u6B21\u542F\u52A8\u8FED\u4EE3\u524D\uFF0C",
1631
+ " \u8BF7\u5148\u7ED9 validation.commands \u914D\u4E0A\u81F3\u5C11\u4E00\u6761\u771F\u5B9E\u547D\u4EE4\uFF08\u5982 npm test\uFF09\u3002",
1632
+ "",
1262
1633
  "\u53EF\u7F16\u8F91\u5B57\u6BB5\uFF1A",
1263
- ...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}`),
1264
1635
  "",
1265
1636
  "\u8BA9\u6A21\u578B\u5E2E\u4F60\u6539\uFF1A",
1266
1637
  '1. \u8C03\u7528 iterate_config({ operation: "read" }) \u67E5\u770B\u5F53\u524D\u914D\u7F6E\uFF1B',
1267
- "2. \u8BF4\u660E\u60F3\u6539\u7684\u5B57\u6BB5\uFF0C\u4F8B\u5982\u300C\u628A max_rounds \u6539\u6210 5\uFF0Cdimensions \u53EA\u4FDD\u7559 correctness \u548C security\u300D\uFF1B",
1268
- '3. \u6A21\u578B\u4F1A\u8C03\u7528 iterate_config({ operation: "write", updates: {...} }) \u5199\u5165\uFF0C\u5199\u5165\u524D\u81EA\u52A8\u5907\u4EFD\uFF0C\u5931\u8D25\u81EA\u52A8\u56DE\u6EDA\u3002'
1638
+ "2. \u8BF4\u660E\u60F3\u6539\u7684\u5B57\u6BB5\uFF0C\u4F8B\u5982\u300C\u628A max_rounds \u6539\u6210 5\uFF0Cvalidation.commands \u52A0\u4E0A npm test\u300D\uFF1B",
1639
+ '3. \u6A21\u578B\u4F1A\u8C03\u7528 iterate_config({ operation: "write", updates: {...} }) \u5199\u5165\uFF0C\u5199\u5165\u524D\u81EA\u52A8\u5907\u4EFD\uFF0C\u5931\u8D25\u81EA\u52A8\u56DE\u6EDA\u3002',
1640
+ "",
1641
+ "\u4E5F\u53EF\u4EE5\u7528\u8BBE\u7F6E\u9875\u7684\u201C\u9009\u5B57\u6BB5 \u2192 \u751F\u6210\u5199\u5165\u6307\u4EE4\u201D\u76F4\u63A5\u590D\u5236\u7B2C 3 \u6B65\u7684\u6307\u4EE4\u7C98\u8D34\u7ED9\u6A21\u578B\u3002"
1269
1642
  ];
1270
1643
  return lines.join("\n");
1271
1644
  }
1645
+ function buildConfigEditInstruction(desiredChanges) {
1646
+ const payload = JSON.stringify({ operation: "write", updates: desiredChanges }, null, 2);
1647
+ return `\u8BF7\u8C03\u7528 \`iterate_config\` \u5199\u5165\u4EE5\u4E0B\u914D\u7F6E\u66F4\u65B0\uFF1A
1648
+
1649
+ \`\`\`json
1650
+ ${payload}
1651
+ \`\`\``;
1652
+ }
1653
+ var CONFIG_VALUE_PLACEHOLDER = "<\u7531\u6A21\u578B\u586B\u5199\uFF1A\u8BE5\u5B57\u6BB5\u7684\u65B0\u503C>";
1654
+ function configFieldByKey(key) {
1655
+ if (typeof key !== "string" || !key) return null;
1656
+ return CONFIG_EDIT_FIELDS.find((f) => f.key === key) ?? null;
1657
+ }
1658
+ function buildConfigFieldInstruction(key, value) {
1659
+ const field = configFieldByKey(key);
1660
+ const label = field ? field.label.zh : key;
1661
+ const hint = field ? field.hint.zh : "\u89C1 iterate.config.yaml";
1662
+ const updates = { [key]: value === void 0 ? CONFIG_VALUE_PLACEHOLDER : value };
1663
+ const payload = buildConfigEditInstruction(updates);
1664
+ return [
1665
+ `\u914D\u7F6E\u5B57\u6BB5 \xB7 ${label}\uFF08${key}\uFF09`,
1666
+ `\u5B57\u6BB5\u8BF4\u660E\uFF1A${hint}`,
1667
+ "",
1668
+ payload,
1669
+ "",
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'
1671
+ ].join("\n");
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
+ }
1272
1738
  var VERDICT_SHORTCUTS = {
1273
1739
  y: "keep",
1274
1740
  Y: "keep",
@@ -1278,7 +1744,8 @@ var VERDICT_SHORTCUTS = {
1278
1744
  A: "ignore"
1279
1745
  };
1280
1746
  function keyToVerdict(key) {
1281
- return VERDICT_SHORTCUTS[key] ?? null;
1747
+ const verdict = VERDICT_SHORTCUTS[key];
1748
+ return verdict !== void 0 && TRIAGE_VERDICTS.includes(verdict) ? verdict : null;
1282
1749
  }
1283
1750
  function allVerdictKeys(triageState) {
1284
1751
  const state = triageState && typeof triageState === "object" ? triageState : {};
@@ -1324,9 +1791,9 @@ function buildRuntimeStatusGuide() {
1324
1791
  }
1325
1792
  function filterLiveEntries(entries, type) {
1326
1793
  const list = Array.isArray(entries) ? entries : [];
1327
- const t = typeof type === "string" ? type.trim() : "";
1328
- if (!t) return list.slice();
1329
- 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);
1330
1797
  }
1331
1798
  function filterTimelineEntries(entries, opts) {
1332
1799
  const list = Array.isArray(entries) ? entries : [];
@@ -1334,29 +1801,30 @@ function filterTimelineEntries(entries, opts) {
1334
1801
  const type = typeof o.type === "string" ? o.type : "";
1335
1802
  const round = typeof o.round === "string" ? o.round : "";
1336
1803
  const q = typeof o.search === "string" ? o.search.trim().toLowerCase() : "";
1337
- const filtered = list.filter((t) => {
1338
- if (!t || typeof t !== "object") return false;
1339
- if (type && String(t.type ?? "") !== type) return false;
1340
- 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;
1341
1808
  if (q) {
1342
1809
  let dataText = "";
1343
1810
  try {
1344
- dataText = JSON.stringify(t.data ?? {});
1811
+ dataText = JSON.stringify(t2.data ?? {});
1345
1812
  } catch {
1346
- dataText = t.data === void 0 ? "{}" : String(t.data);
1813
+ dataText = t2.data === void 0 ? "{}" : String(t2.data);
1347
1814
  }
1348
- const hay = [String(t.type ?? ""), String(t.round ?? ""), dataText].join(" ").toLowerCase();
1815
+ const hay = [String(t2.type ?? ""), String(t2.round ?? ""), dataText].join(" ").toLowerCase();
1349
1816
  if (hay.indexOf(q) < 0) return false;
1350
1817
  }
1351
1818
  return true;
1352
1819
  });
1353
1820
  return filtered.slice().sort((a, b) => String(b.timestamp ?? "").localeCompare(String(a.timestamp ?? "")));
1354
1821
  }
1355
- function serializeObservatoryExport(manifest, live) {
1822
+ function serializeObservatoryExport(manifest, live, extra) {
1356
1823
  const payload = {
1357
1824
  exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
1358
1825
  manifest: manifest && typeof manifest === "object" ? manifest : null,
1359
- live: Array.isArray(live) ? live : []
1826
+ live: Array.isArray(live) ? live : [],
1827
+ ...pickExportExtras(extra)
1360
1828
  };
1361
1829
  try {
1362
1830
  return JSON.stringify(payload, null, 2);
@@ -1364,6 +1832,62 @@ function serializeObservatoryExport(manifest, live) {
1364
1832
  return JSON.stringify({ exportedAt: payload.exportedAt, manifest: null, live: [] }, null, 2);
1365
1833
  }
1366
1834
  }
1835
+ var EXPORT_EXTRA_KEYS = ["qualityGate", "experienceBank", "defenseEvents", "report"];
1836
+ function pickExportExtras(extra) {
1837
+ const out = {};
1838
+ if (!extra || typeof extra !== "object") return out;
1839
+ for (const key of EXPORT_EXTRA_KEYS) {
1840
+ if (!Object.prototype.hasOwnProperty.call(extra, key)) continue;
1841
+ const value = extra[key];
1842
+ if (value === void 0) continue;
1843
+ out[key] = value;
1844
+ }
1845
+ return out;
1846
+ }
1847
+ function buildRoundComparison(report, validations) {
1848
+ const rounds = /* @__PURE__ */ new Map();
1849
+ const rowsFor = (round) => {
1850
+ const n = Number(round);
1851
+ const key = Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
1852
+ if (!rounds.has(key)) {
1853
+ rounds.set(key, { round: key, findings: 0, fixed: 0, severities: /* @__PURE__ */ Object.create(null), validations: [] });
1854
+ }
1855
+ return rounds.get(key);
1856
+ };
1857
+ const list = report && Array.isArray(report.findings) ? report.findings : [];
1858
+ for (const f of list) {
1859
+ if (!f || typeof f !== "object") continue;
1860
+ const row = rowsFor(f.round);
1861
+ row.findings += 1;
1862
+ const sev = typeof f.severity === "string" && f.severity ? f.severity : "unknown";
1863
+ row.severities[sev] = (row.severities[sev] || 0) + 1;
1864
+ if (f.status === "fixed") row.fixed += 1;
1865
+ }
1866
+ const vals = Array.isArray(validations) ? validations : [];
1867
+ for (const v of vals) {
1868
+ if (!v || typeof v !== "object") continue;
1869
+ const row = rowsFor(v.round);
1870
+ row.validations.push({
1871
+ command: typeof v.command === "string" ? v.command : "",
1872
+ exitCode: typeof v.exitCode === "number" ? v.exitCode : null,
1873
+ allowed: v.allowed === true
1874
+ });
1875
+ }
1876
+ return [...rounds.values()].filter((r) => r.round > 0).sort((a, b) => a.round - b.round).map((r) => ({ ...r, severities: { ...r.severities } }));
1877
+ }
1878
+ function dashboardRunState(transcript) {
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);
1882
+ const round = Number.isFinite(rawRound) && rawRound > 0 ? Math.floor(rawRound) : 1;
1883
+ const stoppedReason = typeof t2.stoppedReason === "string" ? t2.stoppedReason.trim() : "";
1884
+ return {
1885
+ state: t2.active === true ? "running" : "done",
1886
+ round,
1887
+ phase: latestPhase(t2.phases),
1888
+ stoppedReason
1889
+ };
1890
+ }
1367
1891
  function latestPhase(phases) {
1368
1892
  const list = Array.isArray(phases) ? phases : [];
1369
1893
  let latest = "";
@@ -1373,6 +1897,387 @@ function latestPhase(phases) {
1373
1897
  return latest;
1374
1898
  }
1375
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
+
1376
2281
  // src/client/index.ts
1377
2282
  var React = require("react");
1378
2283
  var name = "iterate-plugin";
@@ -1453,6 +2358,7 @@ var ITERATE_CSS = `
1453
2358
  }
1454
2359
  .iterate-btn[data-primary] { border-color: var(--dsw-alias-brand-primary); color: var(--dsw-alias-brand-primary); }
1455
2360
  .iterate-btn[data-copied] { border-color: var(--dsw-alias-state-success-primary); color: var(--dsw-alias-state-success-primary); }
2361
+ .iterate-btn[data-failed], .iterate-cmd[data-failed] { border-color: var(--dsw-alias-state-error-primary); color: var(--dsw-alias-state-error-primary); }
1456
2362
  .iterate-payload { width: 100%; margin-top: 8px; padding: 8px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); font-family: var(--dsw-font-mono, ui-monospace, monospace); font-size: 11px; white-space: pre-wrap; }
1457
2363
 
1458
2364
  .iterate-stats { margin: 10px 0; border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px; background: var(--dsw-alias-bg-layer-1); padding: 12px 14px; }
@@ -1637,6 +2543,16 @@ var ITERATE_CSS = `
1637
2543
  .iterate-obs-block-head[data-click] { cursor: pointer; }
1638
2544
  .iterate-obs-block-body { padding: 8px 10px; }
1639
2545
  .iterate-obs-chip { display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px; border-radius: 6px; background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l1); font-size: 11px; color: var(--dsw-alias-label-secondary); white-space: nowrap; }
2546
+ /* Gap #6: validation / gate status colors (pass=green, fail=red). */
2547
+ .iterate-obs-chip[data-status="pass"], .iterate-obs-badge[data-status="pass"] { color: var(--dsw-alias-state-success-primary); background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent); border-color: color-mix(in srgb, var(--dsw-alias-state-success-primary) 28%, transparent); }
2548
+ .iterate-obs-chip[data-status="fail"], .iterate-obs-badge[data-status="fail"] { color: var(--dsw-alias-state-error-primary); background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent); border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 28%, transparent); }
2549
+ .iterate-obs-row[data-status="fail"] { color: var(--dsw-alias-state-error-primary); }
2550
+ .iterate-obs-row[data-highlight] { background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent); border-radius: 6px; padding-left: 6px; padding-right: 6px; border-bottom-color: transparent; font-weight: 600; }
2551
+ /* Gap #13: cross-round comparison table. */
2552
+ .iterate-obs-table { width: 100%; border-collapse: collapse; font-size: 11px; }
2553
+ .iterate-obs-table th, .iterate-obs-table td { text-align: left; padding: 5px 8px; border-bottom: 1px solid var(--dsw-alias-border-l1); white-space: nowrap; }
2554
+ .iterate-obs-table th { color: var(--dsw-alias-label-secondary); font-weight: 600; }
2555
+ .iterate-obs-table td { color: var(--dsw-alias-label-primary); }
1640
2556
  .iterate-obs-msg { padding: 2px 0; color: var(--dsw-alias-label-secondary); line-height: 1.5; font-size: 11px; word-break: break-word; }
1641
2557
  .iterate-obs-file { font-family: var(--dsw-font-mono, ui-monospace, monospace); font-size: 10.5px; color: var(--dsw-alias-label-secondary); word-break: break-all; }
1642
2558
  .iterate-obs-bar { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--dsw-alias-label-secondary); flex-wrap: wrap; }
@@ -1694,12 +2610,12 @@ function createStorage() {
1694
2610
  }
1695
2611
  }
1696
2612
  function removeStorageByPrefix(prefix) {
1697
- if (!storage) return 0;
2613
+ if (!storage2) return 0;
1698
2614
  let removed = 0;
1699
2615
  try {
1700
- for (const key of storage.keys()) {
2616
+ for (const key of storage2.keys()) {
1701
2617
  if (key.startsWith(prefix)) {
1702
- storage.remove(key);
2618
+ storage2.remove(key);
1703
2619
  removed++;
1704
2620
  }
1705
2621
  }
@@ -1708,6 +2624,20 @@ function removeStorageByPrefix(prefix) {
1708
2624
  }
1709
2625
  return removed;
1710
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
+ }
1711
2641
  function copyText(text) {
1712
2642
  if (typeof navigator !== "undefined" && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
1713
2643
  return navigator.clipboard.writeText(text).then(
@@ -1745,10 +2675,23 @@ function severityColor(severity) {
1745
2675
  function severityLabel(severity) {
1746
2676
  return SEVERITY_LABEL[coerceSeverity(severity)];
1747
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
+ }
1748
2691
  function runStatusText(live, manifest) {
1749
- if (live) return "\u8FD0\u884C\u4E2D";
2692
+ if (live) return t("\u8FD0\u884C\u4E2D");
1750
2693
  const reason = manifest && typeof manifest.stoppedReason === "string" ? manifest.stoppedReason : "";
1751
- return reason ? stoppedReasonLabel(reason) : "\u5DF2\u7ED3\u675F";
2694
+ return reason ? stoppedReasonLabel(reason, getLang()) : t("\u5DF2\u7ED3\u675F");
1752
2695
  }
1753
2696
  function readSlots(ctx) {
1754
2697
  if (ctx && ctx.slots && typeof ctx.slots.inject === "function" && typeof ctx.slots.register === "function") {
@@ -1772,7 +2715,7 @@ function readTheme(ctx) {
1772
2715
  }
1773
2716
  var slotsSvc = void 0;
1774
2717
  var themeSvc = void 0;
1775
- var storage = null;
2718
+ var storage2 = null;
1776
2719
  var themeDisposer = null;
1777
2720
  var themeListener = null;
1778
2721
  var themeEnabled = true;
@@ -1823,7 +2766,7 @@ function registerThemeListener(ctx) {
1823
2766
  }
1824
2767
  function setThemeEnabled(enabled) {
1825
2768
  themeEnabled = enabled;
1826
- if (storage) storage.set(THEME_STORAGE_KEY, enabled ? "1" : "0");
2769
+ if (storage2) storage2.set(THEME_STORAGE_KEY, enabled ? "1" : "0");
1827
2770
  if (enabled) applyThemeSkin();
1828
2771
  else clearThemeSkin();
1829
2772
  }
@@ -1840,7 +2783,7 @@ function TrendChart({ points }) {
1840
2783
  key: p.round,
1841
2784
  className: "iterate-trend-bar",
1842
2785
  "data-hot": p.count > 0 ? "" : void 0,
1843
- title: `Round ${p.round}\uFF1A${p.count} \u9879`,
2786
+ title: tf(`Round {a0}\uFF1A{a1} \u9879`, { a0: p.round, a1: p.count }),
1844
2787
  style: { height: `${Math.max(4, Math.round(p.count / max * 24))}px` }
1845
2788
  })
1846
2789
  );
@@ -1848,16 +2791,30 @@ function TrendChart({ points }) {
1848
2791
  return React.createElement("div", {
1849
2792
  className: "iterate-trend",
1850
2793
  role: "img",
1851
- "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 })
1852
2795
  }, ...bars);
1853
2796
  }
1854
2797
  function StartIterationButton() {
1855
2798
  const [copied, setCopied] = React.useState(null);
2799
+ const [failed, setFailed] = React.useState(null);
2800
+ const timers = React.useRef([]);
2801
+ React.useEffect(
2802
+ () => () => {
2803
+ for (const t2 of timers.current) clearTimeout(t2);
2804
+ timers.current = [];
2805
+ },
2806
+ []
2807
+ );
1856
2808
  const copy = (key, text) => {
1857
2809
  copyText(text).then((ok) => {
1858
2810
  if (ok) {
2811
+ setFailed(null);
1859
2812
  setCopied(key);
1860
- setTimeout(() => setCopied((cur) => cur === key ? null : cur), 1600);
2813
+ const t2 = window.setTimeout(() => setCopied((cur) => cur === key ? null : cur), 1600);
2814
+ timers.current.push(t2);
2815
+ } else {
2816
+ setCopied(null);
2817
+ setFailed(key);
1861
2818
  }
1862
2819
  });
1863
2820
  };
@@ -1866,19 +2823,23 @@ function StartIterationButton() {
1866
2823
  className: "iterate-cmd",
1867
2824
  "data-primary": primary ? "" : void 0,
1868
2825
  "data-copied": copied === key ? "" : void 0,
2826
+ "data-failed": failed === key ? "" : void 0,
1869
2827
  onClick: () => copy(key, command),
1870
2828
  title
1871
- }, copied === key ? "\u5DF2\u590D\u5236" : label);
2829
+ }, copied === key ? t("\u5DF2\u590D\u5236") : failed === key ? t("\u590D\u5236\u5931\u8D25") : label);
1872
2830
  return React.createElement(
1873
2831
  "span",
1874
2832
  { className: "iterate-dashboard-launch" },
1875
- btn("start-full", "\u5B8C\u6574\u8FED\u4EE3", "/iterate", true, "\u590D\u5236\u542F\u52A8\u547D\u4EE4\uFF1A\u5B8C\u6574\u300C\u5BA1\u67E5 \u2192 \u4FEE\u590D \u2192 \u9A8C\u8BC1\u300D\u95ED\u73AF"),
1876
- btn("start-review", "\u4EC5\u8BC4\u5BA1", "/iterate review-only", false, "\u590D\u5236\u542F\u52A8\u547D\u4EE4\uFF1A\u53EA\u5BA1\u67E5\u4E0D\u4FEE\u6539\uFF08dry-run\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"))
1877
2835
  );
1878
2836
  }
1879
2837
  function ConvergenceDashboard(props) {
1880
2838
  const [pulseKey, setPulseKey] = React.useState(0);
1881
2839
  const session = props && props.session ? props.session : null;
2840
+ React.useEffect(() => {
2841
+ noteSession(session);
2842
+ }, [session]);
1882
2843
  const report = latestReport(session);
1883
2844
  React.useEffect(() => {
1884
2845
  if (!report) return;
@@ -1888,11 +2849,38 @@ function ConvergenceDashboard(props) {
1888
2849
  setPulseKey((k) => k + 1);
1889
2850
  }, [report && hashReport(report) + ":" + getCurrentRound(report)]);
1890
2851
  if (!report) {
2852
+ const runState = dashboardRunState(latestTranscript(session));
2853
+ if (runState.state === "running") {
2854
+ return React.createElement(
2855
+ "div",
2856
+ { "data-iterate-root": "", "data-iterate": "dashboard", "data-run-state": "running", className: "iterate-dashboard iterate-dashboard-empty" },
2857
+ React.createElement("span", { className: "iterate-round-badge", "data-live": "" }, t("\u8FD0\u884C\u4E2D")),
2858
+ React.createElement(
2859
+ "span",
2860
+ { className: "iterate-empty-hint" },
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 }) : "" })
2862
+ )
2863
+ // No start button while a run is active.
2864
+ );
2865
+ }
2866
+ if (runState.state === "done") {
2867
+ return React.createElement(
2868
+ "div",
2869
+ { "data-iterate-root": "", "data-iterate": "dashboard", "data-run-state": "done", className: "iterate-dashboard iterate-dashboard-empty" },
2870
+ React.createElement("span", { className: "iterate-round-badge" }, t("\u5DF2\u7ED3\u675F")),
2871
+ React.createElement(
2872
+ "span",
2873
+ { className: "iterate-empty-hint" },
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())) : "" })
2875
+ ),
2876
+ React.createElement(StartIterationButton, null)
2877
+ );
2878
+ }
1891
2879
  return React.createElement(
1892
2880
  "div",
1893
- { "data-iterate-root": "", "data-iterate": "dashboard", className: "iterate-dashboard iterate-dashboard-empty" },
2881
+ { "data-iterate-root": "", "data-iterate": "dashboard", "data-run-state": "empty", className: "iterate-dashboard iterate-dashboard-empty" },
1894
2882
  React.createElement("span", { className: "iterate-round-badge" }, "iterate"),
1895
- 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")),
1896
2884
  React.createElement(StartIterationButton, null)
1897
2885
  );
1898
2886
  }
@@ -1907,20 +2895,20 @@ function ConvergenceDashboard(props) {
1907
2895
  const resumeChip = resumeCount > 0 ? React.createElement("span", {
1908
2896
  className: "iterate-chip-resume",
1909
2897
  key: "resume",
1910
- title: "\u672C\u6B21\u8FED\u4EE3\u4ECE\u4E0A\u4E00\u6B21\u4E2D\u65AD\u7684\u65AD\u70B9\u7EE7\u7EED\u6267\u884C"
1911
- }, `\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;
1912
2900
  const imageChip = imageCount > 0 ? React.createElement("span", {
1913
2901
  className: "iterate-chip-images",
1914
2902
  key: "images",
1915
- 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"
1916
- }, `\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;
1917
2905
  const transcript = latestTranscript(session);
1918
2906
  const phase = transcript ? latestPhase(transcript.phases) : "";
1919
2907
  const liveChip = transcript && phase ? React.createElement("span", {
1920
2908
  className: "iterate-chip-phase",
1921
2909
  key: "phase",
1922
2910
  "data-live": transcript.active === true ? "" : void 0,
1923
- 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 })
1924
2912
  }, `${phase} \xB7 ${runStatusText(transcript.active === true, transcript)}`) : null;
1925
2913
  const taskModeTranscript = latestTranscript(session);
1926
2914
  const taskMode = taskModeTranscript?.taskMode;
@@ -1928,7 +2916,7 @@ function ConvergenceDashboard(props) {
1928
2916
  className: "iterate-chip-taskmode",
1929
2917
  key: "taskmode",
1930
2918
  "data-mode": taskMode,
1931
- 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")
1932
2920
  }, taskMode === "code" ? "\u25CF code" : "\u25CF iterate") : null;
1933
2921
  const dimNames = Object.keys(dims);
1934
2922
  const dimBadges = dimNames.slice(0, 6).map(
@@ -1944,7 +2932,7 @@ function ConvergenceDashboard(props) {
1944
2932
  React.createElement(
1945
2933
  "span",
1946
2934
  { key: "+more", className: "iterate-dim-badge iterate-dim-more", title: dimNames.slice(6).join(", ") },
1947
- `+${overflow} \u66F4\u591A`
2935
+ tf(`+{a0} \u66F4\u591A`, { a0: overflow })
1948
2936
  )
1949
2937
  );
1950
2938
  }
@@ -1955,14 +2943,14 @@ function ConvergenceDashboard(props) {
1955
2943
  const fixBadge = fixCount !== null ? React.createElement("span", {
1956
2944
  className: "iterate-metric",
1957
2945
  key: "fixes",
1958
- 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")
1959
2947
  }, `${String(fixCount)} fixes`) : null;
1960
2948
  const converged = report.convergence && report.convergence.converged === true;
1961
2949
  const convChip = converged ? React.createElement("span", {
1962
2950
  className: "iterate-chip-resume",
1963
2951
  key: "converged",
1964
- title: "\u5BA1\u67E5\u5DF2\u6536\u655B\uFF1A\u6700\u540E\u4E00\u8F6E\u672A\u53D1\u73B0\u65B0\u95EE\u9898"
1965
- }, "\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;
1966
2954
  const sevMetric = (key, label) => React.createElement(
1967
2955
  "span",
1968
2956
  { className: "iterate-metric", key, title: label },
@@ -2031,19 +3019,19 @@ function StatsCard(props) {
2031
3019
  React.createElement("td", { style: { color: SEVERITY_COLOR.low } }, String(h.low))
2032
3020
  )
2033
3021
  );
2034
- const trendLine = `\u9996\u8F6E ${trend.firstRound} \u2192 \u672B\u8F6E ${trend.lastRound} \u9879\uFF0C\u964D\u5E45 ${trend.reductionPercent}%${trend.converged ? "\uFF0C\u5DF2\u6536\u655B" : ""}`;
2035
- 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());
2036
3024
  return React.createElement(
2037
3025
  "div",
2038
3026
  { "data-iterate-root": "", "data-iterate": "stats", className: "iterate-stats" },
2039
3027
  React.createElement(
2040
3028
  "div",
2041
3029
  { className: "iterate-triage-head" },
2042
- React.createElement("span", {}, "Iterate \xB7 \u6536\u655B\u7EDF\u8BA1"),
3030
+ React.createElement("span", {}, t("Iterate \xB7 \u6536\u655B\u7EDF\u8BA1")),
2043
3031
  React.createElement(
2044
3032
  "span",
2045
3033
  { className: "iterate-triage-hint" },
2046
- `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")}`
2047
3035
  )
2048
3036
  ),
2049
3037
  React.createElement("div", { className: "iterate-stats-grid" }, ...rows),
@@ -2056,7 +3044,7 @@ function StatsCard(props) {
2056
3044
  className: "iterate-btn",
2057
3045
  "data-primary": showHistory ? "" : void 0,
2058
3046
  onClick: () => setShowHistory((v) => !v)
2059
- }, showHistory ? "\u6536\u8D77\u5386\u53F2" : "\u5386\u53F2 / \u8D8B\u52BF")
3047
+ }, showHistory ? t("\u6536\u8D77\u5386\u53F2") : t("\u5386\u53F2 / \u8D8B\u52BF"))
2060
3048
  ),
2061
3049
  showHistory ? React.createElement(
2062
3050
  "div",
@@ -2070,8 +3058,8 @@ function StatsCard(props) {
2070
3058
  React.createElement(
2071
3059
  "tr",
2072
3060
  {},
2073
- React.createElement("th", {}, "\u8F6E\u6B21"),
2074
- React.createElement("th", {}, "\u53D1\u73B0"),
3061
+ React.createElement("th", {}, t("\u8F6E\u6B21")),
3062
+ React.createElement("th", {}, t("\u53D1\u73B0")),
2075
3063
  React.createElement("th", { style: { color: SEVERITY_COLOR.critical } }, "CRIT"),
2076
3064
  React.createElement("th", { style: { color: SEVERITY_COLOR.high } }, "HIGH"),
2077
3065
  React.createElement("th", { style: { color: SEVERITY_COLOR.medium } }, "MED"),
@@ -2094,7 +3082,7 @@ function TriagePanel(props) {
2094
3082
  const storageKey = TRIAGE_STORAGE_PREFIX + hashReport(report);
2095
3083
  const [verdicts, setVerdicts] = React.useState(() => {
2096
3084
  const initial = buildTriageState(report);
2097
- const saved = storage ? storage.get(storageKey) : null;
3085
+ const saved = storage2 ? storage2.get(storageKey) : null;
2098
3086
  if (saved) {
2099
3087
  try {
2100
3088
  const parsed = JSON.parse(saved);
@@ -2116,8 +3104,27 @@ function TriagePanel(props) {
2116
3104
  const [selected, setSelected] = React.useState(null);
2117
3105
  const [selectAll, setSelectAll] = React.useState(false);
2118
3106
  const [cmdCopied, setCmdCopied] = React.useState(null);
3107
+ const [cmdFailed, setCmdFailed] = React.useState(null);
3108
+ const cmdTimers = React.useRef([]);
3109
+ React.useEffect(() => () => {
3110
+ for (const t2 of cmdTimers.current) clearTimeout(t2);
3111
+ cmdTimers.current = [];
3112
+ }, []);
3113
+ const copyWithFeedback = (text, key) => {
3114
+ copyText(text).then((ok) => {
3115
+ if (ok) {
3116
+ setCmdFailed(null);
3117
+ setCmdCopied(key);
3118
+ cmdTimers.current.push(window.setTimeout(() => setCmdCopied((cur) => cur === key ? null : cur), 1600));
3119
+ } else {
3120
+ setCmdCopied(null);
3121
+ setCmdFailed(key);
3122
+ setPayload(text);
3123
+ }
3124
+ });
3125
+ };
2119
3126
  React.useEffect(() => {
2120
- if (storage) storage.set(storageKey, JSON.stringify(verdicts));
3127
+ if (storage2) storage2.set(storageKey, JSON.stringify(verdicts));
2121
3128
  }, [storageKey, verdicts]);
2122
3129
  const setVerdict = (index, verdict) => {
2123
3130
  setVerdicts((prev) => ({ ...prev, [String(index)]: verdict }));
@@ -2144,9 +3151,9 @@ function TriagePanel(props) {
2144
3151
  if (!doc) return;
2145
3152
  const onKeyDown = (ev) => {
2146
3153
  if (ev.metaKey || ev.ctrlKey || ev.altKey) return;
2147
- const t = ev.target;
2148
- if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT")) return;
2149
- 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;
2150
3157
  const rootEl = doc.querySelector('[data-iterate="triage"]');
2151
3158
  if (!rootEl) return;
2152
3159
  const activeEl = doc.activeElement;
@@ -2180,55 +3187,26 @@ function TriagePanel(props) {
2180
3187
  const ignoredCount = ignored.length;
2181
3188
  const counts = countVerdicts(verdicts);
2182
3189
  const doApproveArchitecturalFix = () => {
2183
- const cmd = "\u8BF7\u8C03\u7528 `iterate_fix`\uFF0C\u5BF9\u672C\u9879\u76EE\u5269\u4F59\u67B6\u6784\u578B finding \u751F\u6210\u4FEE\u590D\u5185\u5BB9\u5E76\u8BBE\u7F6E force: true \u5E94\u7528\uFF08\u67B6\u6784\u4FEE\u590D\u5141\u8BB8\u8D85\u8FC7 atomic \u9608\u503C\uFF09";
2184
- copyText(cmd).then((ok) => {
2185
- if (ok) {
2186
- setCmdCopied("approve-arch");
2187
- setTimeout(() => setCmdCopied(null), 1600);
2188
- }
2189
- });
3190
+ const cmd = buildArchitecturalFixInstruction(findings);
3191
+ if (!cmd) return;
3192
+ copyWithFeedback(cmd, "approve-arch");
2190
3193
  };
2191
3194
  const doTriggerNewRound = () => {
2192
- const cmd = "\u8BF7\u7EE7\u7EED\u6267\u884C\u4E0B\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5";
2193
- copyText(cmd).then((ok) => {
2194
- if (ok) {
2195
- setCmdCopied("new-round");
2196
- setTimeout(() => setCmdCopied(null), 1600);
2197
- }
2198
- });
3195
+ const cmd = t("\u8BF7\u7EE7\u7EED\u6267\u884C\u4E0B\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5");
3196
+ copyWithFeedback(cmd, "new-round");
2199
3197
  };
2200
3198
  const doRollbackToCheckpoint = () => {
2201
- 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";
2202
- copyText(cmd).then((ok) => {
2203
- if (ok) {
2204
- setCmdCopied("rollback");
2205
- setTimeout(() => setCmdCopied(null), 1600);
2206
- }
2207
- });
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");
3200
+ copyWithFeedback(cmd, "rollback");
2208
3201
  };
2209
3202
  const doAssignFindings = () => {
2210
3203
  const targetAll = selectAll;
2211
3204
  const scopeIndices = targetAll ? allIndices : indices;
2212
- const scopeFindings = findings.filter((f, i) => scopeIndices.includes(i)).map((f) => ({
2213
- file: String(f.file || ""),
2214
- ...typeof f.line === "number" && f.line > 0 ? { line: f.line } : {},
2215
- dimension: String(f.dimension || ""),
2216
- severity: String(f.severity || ""),
2217
- summary: String(f.summary || ""),
2218
- ...f.suggested_fix ? { suggested_fix: String(f.suggested_fix) } : {}
2219
- }));
3205
+ const scopeFindings = findings.filter((f, i) => scopeIndices.includes(i));
2220
3206
  if (scopeFindings.length === 0) return;
2221
- const cmd = `\u8BF7\u8C03\u7528 \`iterate_fix\` \u6307\u6D3E\u5E76\u4FEE\u590D\u4EE5\u4E0B findings\uFF1A
2222
-
2223
- \`\`\`json
2224
- ${JSON.stringify(scopeFindings, null, 2)}
2225
- \`\`\``;
2226
- copyText(cmd).then((ok) => {
2227
- if (ok) {
2228
- setCmdCopied("assign");
2229
- setTimeout(() => setCmdCopied(null), 1600);
2230
- }
2231
- });
3207
+ const cmd = buildAssignFixesInstruction(scopeFindings);
3208
+ if (!cmd) return;
3209
+ copyWithFeedback(cmd, "assign");
2232
3210
  };
2233
3211
  const doCopyYaml = () => {
2234
3212
  const yaml = toKnownIntentionalYaml(ignored);
@@ -2236,19 +3214,31 @@ ${JSON.stringify(scopeFindings, null, 2)}
2236
3214
  copyText(yaml).then((ok) => {
2237
3215
  if (ok) {
2238
3216
  setCopied(true);
2239
- setTimeout(() => setCopied(false), 1600);
3217
+ cmdTimers.current.push(window.setTimeout(() => setCopied(false), 1600));
2240
3218
  } else {
2241
3219
  setPayload(yaml);
3220
+ setCmdFailed("yaml");
2242
3221
  }
2243
3222
  });
2244
3223
  };
2245
3224
  const doBuildInstruction = () => {
2246
3225
  const text = buildApplyInstruction(ignored);
3226
+ if (!text) return;
2247
3227
  setPayload(text);
2248
- if (text) copyText(text);
3228
+ copyWithFeedback(text, "apply");
2249
3229
  };
2250
- const rows = filtered.map((finding, i) => {
2251
- const index = indices[i];
3230
+ const doReadback = () => {
3231
+ const text = buildTriageReadbackInstruction();
3232
+ setPayload(text);
3233
+ copyWithFeedback(text, "readback");
3234
+ };
3235
+ const rowPairs = [];
3236
+ for (let i = 0; i < filtered.length; i++) {
3237
+ const idx = indices[i];
3238
+ const f = filtered[i];
3239
+ if (typeof idx === "number" && f) rowPairs.push({ index: idx, finding: f });
3240
+ }
3241
+ const rows = rowPairs.map(({ index, finding }) => {
2252
3242
  const severity = finding.severity || "low";
2253
3243
  const verdict = verdicts[String(index)] || "keep";
2254
3244
  const isSelected = selected === index;
@@ -2294,9 +3284,9 @@ ${JSON.stringify(scopeFindings, null, 2)}
2294
3284
  React.createElement(
2295
3285
  "div",
2296
3286
  { className: "iterate-finding-actions" },
2297
- btn("y \u4FEE\u590D", "keep", "\u4FDD\u7559\u8BE5 finding\uFF0C\u8FDB\u5165\u4FEE\u590D"),
2298
- btn("n \u8DF3\u8FC7", "skip", "\u8DF3\u8FC7\u8BE5 finding"),
2299
- 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"))
2300
3290
  )
2301
3291
  );
2302
3292
  });
@@ -2306,24 +3296,24 @@ ${JSON.stringify(scopeFindings, null, 2)}
2306
3296
  React.createElement(
2307
3297
  "div",
2308
3298
  { className: "iterate-triage-head" },
2309
- React.createElement("span", { role: "heading", "aria-level": 3 }, `Iterate \xB7 Findings \u5206\u8BCA (${filtered.length}/${findings.length})`),
2310
- 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"))
2311
3301
  ),
2312
3302
  React.createElement(
2313
3303
  "div",
2314
3304
  { className: "iterate-filter" },
2315
3305
  React.createElement(
2316
3306
  "select",
2317
- { className: "iterate-filter-select", value: filter.severities[0] || "", onChange: (e) => setSeverityFilter(e.target.value), "aria-label": "\u6309\u4E25\u91CD\u5EA6\u7B5B\u9009" },
2318
- 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")),
2319
3309
  ...options.severities.map(
2320
3310
  (s) => React.createElement("option", { key: s.value, value: s.value }, `${severityLabel(s.value)} (${s.count})`)
2321
3311
  )
2322
3312
  ),
2323
3313
  React.createElement(
2324
3314
  "select",
2325
- { className: "iterate-filter-select", value: filter.dimensions[0] || "", onChange: (e) => setDimensionFilter(e.target.value), "aria-label": "\u6309\u7EF4\u5EA6\u7B5B\u9009" },
2326
- 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")),
2327
3317
  ...options.dimensions.map(
2328
3318
  (d) => React.createElement("option", { key: d.value, value: d.value }, `${d.value} (${d.count})`)
2329
3319
  )
@@ -2331,96 +3321,113 @@ ${JSON.stringify(scopeFindings, null, 2)}
2331
3321
  React.createElement("input", {
2332
3322
  className: "iterate-filter-search",
2333
3323
  type: "search",
2334
- placeholder: "\u641C\u7D22\u6587\u4EF6 / \u6458\u8981\u2026",
2335
- "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"),
2336
3326
  value: filter.search,
2337
3327
  onChange: (e) => setSearchFilter(e.target.value)
2338
3328
  }),
2339
3329
  React.createElement(
2340
3330
  "span",
2341
3331
  { className: "iterate-filter-count" },
2342
- 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 })
2343
3333
  )
2344
3334
  ),
2345
3335
  React.createElement(
2346
3336
  "div",
2347
3337
  { className: "iterate-batch" },
2348
- 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")),
2349
3339
  React.createElement("button", {
2350
3340
  className: selectAll ? "iterate-batch-btn iterate-batch-scope" : "iterate-batch-btn iterate-batch-scope iterate-batch-scope-on",
2351
3341
  onClick: () => setSelectAll(false),
2352
- title: "\u6279\u91CF\u6309\u94AE\u4EC5\u4F5C\u7528\u4E8E\u5F53\u524D\u7B5B\u9009\u53EF\u89C1\u7684 findings"
2353
- }, `\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 })),
2354
3344
  React.createElement("button", {
2355
3345
  className: selectAll ? "iterate-batch-btn iterate-batch-scope iterate-batch-scope-on" : "iterate-batch-btn iterate-batch-scope",
2356
3346
  onClick: () => setSelectAll(true),
2357
- title: "\u6279\u91CF\u6309\u94AE\u4F5C\u7528\u4E8E\u5168\u90E8 findings"
2358
- }, `\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 })),
2359
3349
  React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("keep") }, "y"),
2360
3350
  React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("skip") }, "n"),
2361
3351
  React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("ignore") }, "a"),
2362
- 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"))
2363
3353
  ),
2364
3354
  ...rows,
2365
3355
  React.createElement(
2366
3356
  "div",
2367
3357
  { className: "iterate-triage-foot" },
2368
- 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 })),
2369
3359
  React.createElement(
2370
3360
  "span",
2371
- { style: { display: "flex", gap: 6 } },
3361
+ { style: { display: "flex", gap: 6, flexWrap: "wrap" } },
3362
+ // Gap #7: the apply instruction (safe path) is PRIMARY; the raw YAML
3363
+ // is demoted to an explicit "just the snippet" escape hatch.
2372
3364
  React.createElement("button", {
2373
3365
  className: "iterate-btn",
2374
3366
  "data-primary": "",
3367
+ "data-copied": cmdCopied === "apply" ? "" : void 0,
3368
+ "data-failed": cmdFailed === "apply" ? "" : void 0,
3369
+ onClick: doBuildInstruction,
3370
+ disabled: ignoredCount === 0,
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` : "" })),
3373
+ React.createElement("button", {
3374
+ className: "iterate-btn",
2375
3375
  "data-copied": copied ? "" : void 0,
3376
+ "data-failed": cmdFailed === "yaml" ? "" : void 0,
2376
3377
  onClick: doCopyYaml,
2377
3378
  disabled: ignoredCount === 0,
2378
- title: ignoredCount === 0 ? "\u5F53\u524D\u6CA1\u6709\u6807\u8BB0\u4E3A\u300C\u5DF2\u77E5\u6709\u610F\u300D\u7684 finding" : "\u590D\u5236 known_intentional YAML"
2379
- }, copied ? "\u5DF2\u590D\u5236" : `\u590D\u5236 known_intentional${ignoredCount > 0 ? `\uFF08${ignoredCount}\uFF09` : ""}`),
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")),
2380
3381
  React.createElement("button", {
2381
3382
  className: "iterate-btn",
2382
- onClick: doBuildInstruction,
2383
- disabled: ignoredCount === 0,
2384
- title: ignoredCount === 0 ? "\u5F53\u524D\u6CA1\u6709\u6807\u8BB0\u4E3A\u300C\u5DF2\u77E5\u6709\u610F\u300D\u7684 finding" : "\u751F\u6210 iterate_triage \u5E94\u7528\u6307\u4EE4"
2385
- }, "\u751F\u6210\u5E94\u7528\u6307\u4EE4")
2386
- )
3383
+ "data-copied": cmdCopied === "readback" ? "" : void 0,
3384
+ "data-failed": cmdFailed === "readback" ? "" : void 0,
3385
+ onClick: doReadback,
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"))
3388
+ ),
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
2387
3390
  ),
2388
3391
  // v3.0: Command buttons for native actions (§8)
2389
3392
  React.createElement(
2390
3393
  "div",
2391
3394
  { className: "iterate-triage-foot", style: { borderTop: "1px solid var(--dsw-alias-border-l1)" } },
2392
- 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")),
2393
3396
  React.createElement(
2394
3397
  "span",
2395
- { style: { display: "flex", gap: 6 } },
3398
+ { style: { display: "flex", gap: 6, flexWrap: "wrap" } },
2396
3399
  React.createElement("button", {
2397
3400
  className: "iterate-cmd",
2398
3401
  "data-primary": "",
2399
3402
  "data-copied": cmdCopied === "approve-arch" ? "" : void 0,
3403
+ "data-failed": cmdFailed === "approve-arch" ? "" : void 0,
2400
3404
  onClick: doApproveArchitecturalFix,
2401
- title: "\u6279\u51C6\u67B6\u6784\u4FEE\u590D\uFF08\u590D\u5236\u6307\u4EE4\u6587\u672C\uFF09"
2402
- }, cmdCopied === "approve-arch" ? "\u5DF2\u590D\u5236" : "\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")),
2403
3407
  React.createElement("button", {
2404
3408
  className: "iterate-cmd",
2405
3409
  "data-copied": cmdCopied === "new-round" ? "" : void 0,
3410
+ "data-failed": cmdFailed === "new-round" ? "" : void 0,
2406
3411
  onClick: doTriggerNewRound,
2407
- title: "\u89E6\u53D1\u65B0\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5"
2408
- }, cmdCopied === "new-round" ? "\u5DF2\u590D\u5236" : "\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")),
2409
3414
  React.createElement("button", {
2410
3415
  className: "iterate-cmd",
2411
3416
  "data-primary": "",
2412
3417
  "data-copied": cmdCopied === "assign" ? "" : void 0,
3418
+ "data-failed": cmdFailed === "assign" ? "" : void 0,
2413
3419
  onClick: doAssignFindings,
2414
3420
  disabled: (selectAll ? allIndices : indices).length === 0,
2415
- title: selectAll ? `\u6307\u6D3E\u5168\u90E8 ${allIndices.length} \u4E2A findings \u4FEE\u590D` : `\u6307\u6D3E\u5F53\u524D\u53EF\u89C1 ${indices.length} \u4E2A findings \u4FEE\u590D`
2416
- }, cmdCopied === "assign" ? "\u5DF2\u590D\u5236" : "\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")),
2417
3423
  React.createElement("button", {
2418
3424
  className: "iterate-cmd",
2419
3425
  "data-danger": "",
2420
3426
  "data-copied": cmdCopied === "rollback" ? "" : void 0,
3427
+ "data-failed": cmdFailed === "rollback" ? "" : void 0,
2421
3428
  onClick: doRollbackToCheckpoint,
2422
- title: "\u56DE\u6EDA\u5230\u4E0A\u4E00\u4E2A\u68C0\u67E5\u70B9"
2423
- }, cmdCopied === "rollback" ? "\u5DF2\u590D\u5236" : "\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"))
2424
3431
  )
2425
3432
  ),
2426
3433
  payload ? React.createElement("div", { className: "iterate-payload" }, payload) : null
@@ -2443,16 +3450,16 @@ function VerdictBanner(props) {
2443
3450
  React.createElement(
2444
3451
  "span",
2445
3452
  { className: "iterate-verdict-tag", "data-ok": ok ? "" : void 0, "data-warn": ok ? void 0 : "" },
2446
- 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")
2447
3454
  ),
2448
3455
  React.createElement(
2449
3456
  "span",
2450
3457
  { className: "iterate-verdict-detail" },
2451
- item(verdict.totalFindings, "\u9879\u53D1\u73B0"),
2452
- item(verdict.totalRounds, "\u8F6E"),
2453
- item(verdict.checksRun, "\u9879\u5BA1\u67E5"),
2454
- ok ? phrase("\u62A5\u544A\u901A\u8FC7\u5168\u90E8\u4E00\u81F4\u6027\u68C0\u67E5") : item(verdict.reportIssues, "\u5904\u62A5\u544A\u7F3A\u9677"),
2455
- 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"))
2456
3463
  )
2457
3464
  );
2458
3465
  }
@@ -2494,7 +3501,7 @@ function ProgressCapsule() {
2494
3501
  const listener = (payload) => {
2495
3502
  const converged = payload && payload.converged === true;
2496
3503
  const round = payload && typeof payload.round === "number" ? payload.round : "?";
2497
- 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 });
2498
3505
  if (timer) clearTimeout(timer);
2499
3506
  timer = setTimeout(() => setInfo(null), converged ? 3600 : 2400);
2500
3507
  };
@@ -2514,7 +3521,10 @@ function ProgressCapsule() {
2514
3521
  "aria-live": "polite"
2515
3522
  }, info.text);
2516
3523
  }
2517
- function SettingsPanel(_props) {
3524
+ function SettingsPanel(props) {
3525
+ const lang = useLang();
3526
+ const cachedSession = useSessionSnapshot();
3527
+ const session = props && props.session ? props.session : cachedSession;
2518
3528
  const [enabled, setEnabled] = React.useState(themeEnabled);
2519
3529
  const [guideCopied, setGuideCopied] = React.useState(false);
2520
3530
  const [statusCopied, setStatusCopied] = React.useState(false);
@@ -2522,41 +3532,82 @@ function SettingsPanel(_props) {
2522
3532
  const [showStatus, setShowStatus] = React.useState(false);
2523
3533
  const [confirming, setConfirming] = React.useState(false);
2524
3534
  const [clearedInfo, setClearedInfo] = React.useState(null);
3535
+ const [cfgField, setCfgField] = React.useState(
3536
+ () => CONFIG_EDIT_FIELDS.some((f) => f.key === "validation.commands") ? "validation.commands" : CONFIG_EDIT_FIELDS[0]?.key ?? "goal"
3537
+ );
3538
+ const [cfgCopied, setCfgCopied] = React.useState(false);
3539
+ const [cfgFailed, setCfgFailed] = React.useState(false);
3540
+ const [cfgText, setCfgText] = React.useState(null);
3541
+ const [cfgReadCopied, setCfgReadCopied] = React.useState(false);
3542
+ const [cfgReadText, setCfgReadText] = React.useState(null);
3543
+ const [pullCopied, setPullCopied] = React.useState(false);
3544
+ const [copyError, setCopyError] = React.useState(null);
2525
3545
  const guide = buildConfigEditGuide();
2526
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;
3551
+ const cfgTimers = React.useRef([]);
3552
+ React.useEffect(() => () => {
3553
+ for (const t2 of cfgTimers.current) clearTimeout(t2);
3554
+ cfgTimers.current = [];
3555
+ }, []);
2527
3556
  const toggleTheme = () => {
2528
3557
  const next = !enabled;
2529
3558
  setEnabled(next);
2530
3559
  setThemeEnabled(next);
2531
3560
  };
2532
3561
  const flashCopied = (slot) => {
2533
- const setter = slot === "guide" ? setGuideCopied : setStatusCopied;
3562
+ const setter = slot === "guide" ? setGuideCopied : slot === "status" ? setStatusCopied : slot === "pull" ? setPullCopied : setCfgReadCopied;
2534
3563
  setter(true);
2535
- setTimeout(() => setter(false), 1600);
3564
+ cfgTimers.current.push(window.setTimeout(() => setter(false), 1600));
2536
3565
  };
2537
3566
  const doCopy = (text, slot) => {
2538
3567
  copyText(text).then((ok) => {
3568
+ setCopyError(ok ? null : slot);
2539
3569
  if (ok) flashCopied(slot);
2540
3570
  });
2541
3571
  };
3572
+ const doBuildConfigInstruction = () => {
3573
+ const text = buildConfigFieldInstruction(cfgField);
3574
+ setCfgText(text);
3575
+ copyText(text).then((ok) => {
3576
+ setCfgFailed(!ok);
3577
+ setCfgCopied(ok);
3578
+ cfgTimers.current.push(window.setTimeout(() => setCfgCopied(false), 1600));
3579
+ if (!ok) return;
3580
+ });
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
+ };
2542
3593
  const requestClear = () => {
2543
3594
  if (confirming) {
2544
3595
  const count = removeStorageByPrefix(TRIAGE_STORAGE_PREFIX);
2545
3596
  setClearedInfo(count);
2546
3597
  setConfirming(false);
2547
- setTimeout(() => setClearedInfo(null), 3e3);
3598
+ cfgTimers.current.push(window.setTimeout(() => setClearedInfo(null), 3e3));
2548
3599
  return;
2549
3600
  }
2550
3601
  setConfirming(true);
2551
- setTimeout(() => setConfirming(false), 3e3);
3602
+ cfgTimers.current.push(window.setTimeout(() => setConfirming(false), 3e3));
2552
3603
  };
2553
- 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", {
2554
3605
  className: "iterate-btn",
2555
3606
  "data-danger": confirming ? void 0 : "",
2556
3607
  "data-confirm": confirming ? "" : void 0,
2557
3608
  onClick: requestClear,
2558
- 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"
2559
- }, 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"));
2560
3611
  const themeCard = React.createElement(
2561
3612
  "div",
2562
3613
  { key: "theme", className: "iterate-scard" },
@@ -2566,17 +3617,17 @@ function SettingsPanel(_props) {
2566
3617
  React.createElement(
2567
3618
  "div",
2568
3619
  {},
2569
- React.createElement("div", { className: "iterate-settings-title" }, "\u8FED\u4EE3\u4E3B\u9898"),
2570
- 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"))
2571
3622
  ),
2572
3623
  React.createElement(
2573
3624
  "div",
2574
3625
  { className: "iterate-scard-actions" },
2575
- 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")),
2576
3627
  React.createElement("button", {
2577
3628
  role: "switch",
2578
3629
  "aria-checked": enabled,
2579
- "aria-label": "\u5F00\u5173 iterate \u4E3B\u9898",
3630
+ "aria-label": t("\u5F00\u5173 iterate \u4E3B\u9898"),
2580
3631
  className: "iterate-switch",
2581
3632
  "data-on": enabled ? "" : void 0,
2582
3633
  onClick: toggleTheme
@@ -2584,6 +3635,37 @@ function SettingsPanel(_props) {
2584
3635
  )
2585
3636
  )
2586
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
+ );
2587
3669
  const dataCard = React.createElement(
2588
3670
  "div",
2589
3671
  { key: "data", className: "iterate-scard" },
@@ -2593,13 +3675,13 @@ function SettingsPanel(_props) {
2593
3675
  React.createElement(
2594
3676
  "div",
2595
3677
  {},
2596
- React.createElement("div", { className: "iterate-settings-title" }, "\u5206\u8BCA\u6301\u4E45\u5316"),
2597
- 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"))
2598
3680
  ),
2599
3681
  React.createElement(
2600
3682
  "div",
2601
3683
  { className: "iterate-scard-actions" },
2602
- React.createElement("span", { className: "iterate-chip" }, "\u672C\u5730\u4FDD\u5B58"),
3684
+ React.createElement("span", { className: "iterate-chip" }, t("\u672C\u5730\u4FDD\u5B58")),
2603
3685
  clearButton
2604
3686
  )
2605
3687
  )
@@ -2613,24 +3695,99 @@ function SettingsPanel(_props) {
2613
3695
  React.createElement(
2614
3696
  "div",
2615
3697
  {},
2616
- React.createElement("div", { className: "iterate-settings-title" }, "\u914D\u7F6E\u7BA1\u7406"),
2617
- React.createElement("div", { className: "iterate-settings-desc" }, "\u76EE\u6807 / \u7EF4\u5EA6 / \u6700\u5927\u8F6E\u6570\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"))
2618
3700
  ),
2619
3701
  React.createElement(
2620
3702
  "div",
2621
3703
  { className: "iterate-scard-actions" },
2622
- 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"),
2623
- 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"))
2624
3706
  )
2625
3707
  ),
3708
+ // Gap #8: wire up the previously-dead `buildConfigFieldInstruction` — pick
3709
+ // a field, generate the `iterate_config` write payload for it, paste it to
3710
+ // the model. `validation.commands` is the default selection.
3711
+ React.createElement(
3712
+ "div",
3713
+ { className: "iterate-guide-bar", style: { marginTop: 8, flexWrap: "wrap" } },
3714
+ React.createElement(
3715
+ "select",
3716
+ {
3717
+ className: "iterate-filter-select",
3718
+ value: cfgField,
3719
+ "aria-label": t("\u9009\u62E9\u8981\u4FEE\u6539\u7684\u914D\u7F6E\u5B57\u6BB5"),
3720
+ onChange: (e) => setCfgField(e.target.value)
3721
+ },
3722
+ ...CONFIG_EDIT_FIELDS.map(
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`)
3724
+ )
3725
+ ),
3726
+ React.createElement(
3727
+ "span",
3728
+ { className: "iterate-settings-desc" },
3729
+ cfgMeta?.hint[getLang()] ?? ""
3730
+ ),
3731
+ React.createElement("button", {
3732
+ className: "iterate-btn",
3733
+ "data-primary": "",
3734
+ "data-copied": cfgCopied ? "" : void 0,
3735
+ "data-failed": cfgFailed ? "" : void 0,
3736
+ onClick: doBuildConfigInstruction,
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
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,
3771
+ cfgText ? React.createElement(
3772
+ "div",
3773
+ { className: "iterate-guide" },
3774
+ React.createElement(
3775
+ "div",
3776
+ { className: "iterate-guide-bar" },
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"))
3779
+ ),
3780
+ React.createElement("div", { className: "iterate-guide-body" }, cfgText)
3781
+ ) : null,
2626
3782
  showGuide ? React.createElement(
2627
3783
  "div",
2628
3784
  { className: "iterate-guide" },
2629
3785
  React.createElement(
2630
3786
  "div",
2631
3787
  { className: "iterate-guide-bar" },
2632
- React.createElement("span", {}, "iterate.config.yaml \u53EF\u7F16\u8F91\u5B57\u6BB5"),
2633
- 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"))
2634
3791
  ),
2635
3792
  React.createElement("div", { className: "iterate-guide-body" }, guide)
2636
3793
  ) : null
@@ -2644,14 +3801,22 @@ function SettingsPanel(_props) {
2644
3801
  React.createElement(
2645
3802
  "div",
2646
3803
  {},
2647
- React.createElement("div", { className: "iterate-settings-title" }, "\u72B6\u6001\u6982\u89C8"),
2648
- 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"))
2649
3806
  ),
2650
3807
  React.createElement(
2651
3808
  "div",
2652
3809
  { className: "iterate-scard-actions" },
2653
- 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"),
2654
- React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => setShowStatus((v) => !v) }, showStatus ? "\u6536\u8D77" : "\u5C55\u5F00")
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")),
3811
+ // Gap #4: RUNTIME_ARTIFACTS stops being documentation-only — this
3812
+ // copies the instruction that makes the model echo those artifacts
3813
+ // into the session so the observatory panels can read them.
3814
+ React.createElement("button", {
3815
+ className: "iterate-btn",
3816
+ "data-copied": pullCopied ? "" : void 0,
3817
+ onClick: () => doCopy(buildDiskSnapshotInstruction(), "pull")
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"))
2655
3820
  )
2656
3821
  ),
2657
3822
  showStatus ? React.createElement(
@@ -2660,8 +3825,9 @@ function SettingsPanel(_props) {
2660
3825
  React.createElement(
2661
3826
  "div",
2662
3827
  { className: "iterate-guide-bar" },
2663
- React.createElement("span", {}, "\u8FD0\u884C\u65F6\u5E03\u5C40\u4E0E\u6E05\u7406"),
2664
- 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"))
2665
3831
  ),
2666
3832
  React.createElement("div", { className: "iterate-guide-body" }, statusGuide)
2667
3833
  ) : null
@@ -2676,21 +3842,31 @@ function SettingsPanel(_props) {
2676
3842
  "div",
2677
3843
  {},
2678
3844
  React.createElement("div", { className: "iterate-settings-title" }, "iterate"),
2679
- 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"))
2680
3846
  ),
2681
3847
  React.createElement(
2682
3848
  "span",
2683
3849
  { className: "iterate-pill" },
2684
3850
  React.createElement("span", { className: "iterate-pill-dot" }),
2685
- "\u5C31\u7EEA"
3851
+ t("\u5C31\u7EEA")
2686
3852
  )
2687
3853
  )
2688
3854
  );
2689
3855
  return React.createElement(
2690
3856
  "div",
2691
3857
  { "data-iterate-root": "", "data-iterate": "settings", className: "iterate-settings" },
2692
- 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")),
2693
3859
  banner,
3860
+ copyError ? React.createElement(
3861
+ "div",
3862
+ { key: "copy-err", className: "iterate-scard", role: "alert" },
3863
+ React.createElement(
3864
+ "div",
3865
+ { className: "iterate-settings-desc" },
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")
3867
+ )
3868
+ ) : null,
3869
+ langCard,
2694
3870
  themeCard,
2695
3871
  dataCard,
2696
3872
  guideCard,
@@ -2698,31 +3874,52 @@ function SettingsPanel(_props) {
2698
3874
  );
2699
3875
  }
2700
3876
  var OBS_TABS = [
2701
- { key: "live", label: "\u5B9E\u65F6\u6D3B\u52A8\u6D41" },
2702
- { key: "f1", label: "\u5BA1\u67E5\u7EBF\u7A0B" },
2703
- { key: "f2", label: "\u6536\u655B\u8D8B\u52BF" },
2704
- { key: "f3", label: "\u53D1\u73B0\u5B9A\u4F4D" },
2705
- { key: "f4", label: "\u4FEE\u590D\u4E0E\u56DE\u6EDA" },
2706
- { key: "f5", label: "\u65AD\u70B9\u6062\u590D" },
2707
- { key: "f6", label: "\u8FD0\u884C\u63A7\u5236\u53F0" },
2708
- { key: "f7", label: "\u51B3\u7B56\u65F6\u95F4\u7EBF" },
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" },
2709
3885
  // v3.0: Quality Command Center tabs
2710
- { key: "f8", label: "\u8D28\u91CF\u95E8\u7981" },
2711
- { key: "f9", label: "\u7ECF\u9A8C\u94F6\u884C" },
2712
- { key: "f10", label: "\u9632\u5FA1\u4E8B\u4EF6" }
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" }
2713
3889
  ];
3890
+ var OBS_HOTKEY_TABS = {
3891
+ 1: "f1",
3892
+ 2: "f2",
3893
+ 3: "f3",
3894
+ 4: "f4",
3895
+ 5: "f5",
3896
+ 6: "f6",
3897
+ 7: "f7",
3898
+ 8: "f8",
3899
+ 9: "f9",
3900
+ 0: "f10",
3901
+ "-": "live"
3902
+ };
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) });
3910
+ }
2714
3911
  var OBS_LIVE_META = {
2715
- read: { label: "\u9605\u8BFB", color: "var(--dsw-alias-brand-primary)" },
2716
- fix: { label: "\u4FEE\u590D", color: "var(--dsw-alias-state-success-primary)" },
2717
- rollback: { label: "\u56DE\u6EDA", color: "var(--dsw-alias-state-error-primary)" },
2718
- diff: { label: "\u5DEE\u5F02", color: "var(--dsw-alias-state-warn-primary)" },
2719
- review: { label: "\u5BA1\u67E5", color: "var(--dsw-alias-brand-primary)" },
2720
- triage: { label: "\u5206\u6D41", color: "var(--dsw-alias-state-warn-primary)" },
2721
- checkpoint: { label: "\u65AD\u70B9", color: "var(--dsw-alias-brand-primary)" },
2722
- validate: { label: "\u6821\u9A8C", color: "var(--dsw-alias-state-success-primary)" },
2723
- log: { label: "\u8BB0\u5F55", color: "var(--dsw-alias-label-secondary)" },
2724
- prune: { label: "\u6E05\u7406", color: "var(--dsw-alias-state-warn-primary)" },
2725
- 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)" }
2726
3923
  };
2727
3924
  var OBS_LIVE_FALLBACK_COLOR = "var(--dsw-alias-label-secondary)";
2728
3925
  function formatObsLiveTime(ts) {
@@ -2760,21 +3957,13 @@ function latestDefenseEvents(session) {
2760
3957
  return scanSessionForDefenseEvents(session);
2761
3958
  }
2762
3959
  function buildObsFixInstruction(f) {
2763
- const payload = JSON.stringify({
2764
- file: String(f.file || ""),
2765
- ...typeof f.line === "number" && f.line > 0 ? { line: f.line } : {},
2766
- dimension: String(f.dimension || ""),
2767
- summary: String(f.summary || ""),
2768
- ...f.suggested_fix ? { suggested_fix: String(f.suggested_fix) } : {}
2769
- }, null, 2);
2770
- return `\u8BF7\u8C03\u7528 \`iterate_fix\` \u4FEE\u590D\u4EE5\u4E0B finding\uFF1A
2771
-
2772
- \`\`\`json
2773
- ${payload}
2774
- \`\`\``;
3960
+ return buildFixInstruction(f);
2775
3961
  }
2776
3962
  function ObservatoryPanel(props) {
2777
3963
  const session = props && props.session ? props.session : null;
3964
+ React.useEffect(() => {
3965
+ noteSession(session);
3966
+ }, [session]);
2778
3967
  const manifest = latestTranscript(session);
2779
3968
  const qualityGate = latestQualityGate(session);
2780
3969
  const experienceBank = latestExperienceBank(session);
@@ -2799,6 +3988,28 @@ function ObservatoryPanel(props) {
2799
3988
  React.useEffect(() => () => {
2800
3989
  if (copyTimer.current) clearTimeout(copyTimer.current);
2801
3990
  }, []);
3991
+ React.useEffect(() => {
3992
+ const doc = typeof document !== "undefined" ? document : null;
3993
+ if (!doc || !open) return;
3994
+ const onKeyDown = (ev) => {
3995
+ if (ev.metaKey || ev.ctrlKey || ev.altKey || ev.shiftKey) 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;
3999
+ const rootEl = doc.querySelector('[data-iterate="obs"]');
4000
+ if (!rootEl) return;
4001
+ const activeEl = doc.activeElement;
4002
+ const inPanel = activeEl === rootEl || activeEl !== null && typeof rootEl.contains === "function" && rootEl.contains(activeEl);
4003
+ const idle = activeEl === null || activeEl === doc.body;
4004
+ if (!inPanel && !idle) return;
4005
+ const next = OBS_HOTKEY_TABS[ev.key];
4006
+ if (!next) return;
4007
+ ev.preventDefault();
4008
+ setTab(next);
4009
+ };
4010
+ doc.addEventListener("keydown", onKeyDown);
4011
+ return () => doc.removeEventListener("keydown", onKeyDown);
4012
+ }, [open]);
2802
4013
  const copyInstruction = (key, text) => {
2803
4014
  if (!text) return;
2804
4015
  copyText(text).then((ok) => {
@@ -2811,6 +4022,8 @@ function ObservatoryPanel(props) {
2811
4022
  copyTimer.current = setTimeout(() => setCopiedKey((cur) => cur === key ? null : cur), 1600);
2812
4023
  });
2813
4024
  };
4025
+ const pullInstruction = buildDiskSnapshotInstruction();
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(" / ") });
2814
4027
  const toggleThread = (key) => {
2815
4028
  setThreadMode("auto");
2816
4029
  setExpandedThreads((prev) => {
@@ -2822,7 +4035,12 @@ function ObservatoryPanel(props) {
2822
4035
  };
2823
4036
  const exportObservatory = () => {
2824
4037
  if (!manifest) return;
2825
- const json = serializeObservatoryExport(manifest, manifest.live || []);
4038
+ const json = serializeObservatoryExport(manifest, manifest.live || [], {
4039
+ qualityGate,
4040
+ experienceBank,
4041
+ defenseEvents,
4042
+ report: latestReport(session)
4043
+ });
2826
4044
  const filename = `iterate-observatory-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`;
2827
4045
  const flash = () => {
2828
4046
  setCopiedKey("export");
@@ -2857,8 +4075,17 @@ function ObservatoryPanel(props) {
2857
4075
  React.createElement(
2858
4076
  "div",
2859
4077
  { className: "iterate-obs-head", "data-closed": "" },
2860
- React.createElement("span", { className: "iterate-obs-title" }, "iterate \u89C2\u6D4B\u53F0"),
2861
- React.createElement("span", { className: "iterate-obs-head-meta" }, "\u6682\u65E0\u8FD0\u884C\u65F6\u89C2\u6D4B\u6570\u636E")
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() })),
4080
+ React.createElement("button", {
4081
+ className: "iterate-btn",
4082
+ "data-copied": copiedKey === "pull-snapshot" ? "" : void 0,
4083
+ onClick: (e) => {
4084
+ e.stopPropagation();
4085
+ copyInstruction("pull-snapshot", pullInstruction);
4086
+ },
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"))
2862
4089
  )
2863
4090
  );
2864
4091
  }
@@ -2880,7 +4107,7 @@ function ObservatoryPanel(props) {
2880
4107
  const renderThreads = () => {
2881
4108
  const rounds = manifest.rounds || [];
2882
4109
  if (rounds.length === 0) {
2883
- 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"));
2884
4111
  }
2885
4112
  return React.createElement(
2886
4113
  "div",
@@ -2888,34 +4115,34 @@ function ObservatoryPanel(props) {
2888
4115
  React.createElement(
2889
4116
  "div",
2890
4117
  { className: "iterate-obs-bar", style: { marginBottom: 8, flexWrap: "wrap" } },
2891
- React.createElement("b", {}, "\u5BA1\u67E5\u7EBF\u7A0B"),
2892
- React.createElement("button", { className: "iterate-btn", onClick: () => setAllThreads("all"), title: "\u5C55\u5F00\u5168\u90E8\u7EBF\u7A0B" }, "\u5168\u90E8\u5C55\u5F00"),
2893
- 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")),
2894
4121
  React.createElement(
2895
4122
  "span",
2896
4123
  { className: "iterate-filter-count" },
2897
- `${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 })
2898
4125
  )
2899
4126
  ),
2900
4127
  ...rounds.map((r, ri) => {
2901
4128
  const threads = r.threads || [];
2902
- const fCount = threads.reduce((sum, t) => sum + (t.findings ? t.findings.length : 0), 0);
2903
- 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) => {
2904
4131
  const key = `${ri}-${ti}`;
2905
4132
  const expanded = isThreadExpanded(key);
2906
- const dim = t.dimension || "\u672A\u547D\u540D\u7EF4\u5EA6";
2907
- const tFindings = t.findings || [];
2908
- const files = t.readFiles || [];
2909
- 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 || [];
2910
4137
  return React.createElement(
2911
4138
  "div",
2912
4139
  { key, className: "iterate-obs-block" },
2913
4140
  React.createElement(
2914
4141
  "div",
2915
- { 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") },
2916
4143
  React.createElement("span", {}, expanded ? "\u2212" : "+"),
2917
4144
  React.createElement("b", {}, dim),
2918
- 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 : "?"}`),
2919
4146
  React.createElement("span", { className: "iterate-obs-chip" }, `${tFindings.length} findings`)
2920
4147
  ),
2921
4148
  expanded ? React.createElement(
@@ -2924,15 +4151,15 @@ function ObservatoryPanel(props) {
2924
4151
  React.createElement(
2925
4152
  "div",
2926
4153
  { className: "iterate-obs-bar" },
2927
- React.createElement("b", {}, "\u53D9\u8FF0"),
2928
- 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 }))
2929
4156
  ),
2930
- ...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))),
2931
4158
  ...files.length > 0 ? [
2932
4159
  React.createElement(
2933
4160
  "div",
2934
4161
  { key: "files-h", className: "iterate-obs-bar", style: { marginTop: 6 } },
2935
- React.createElement("b", {}, "\u8BFB\u53D6\u6587\u4EF6")
4162
+ React.createElement("b", {}, t("\u8BFB\u53D6\u6587\u4EF6"))
2936
4163
  ),
2937
4164
  ...files.map(
2938
4165
  (f, fi) => React.createElement("div", { key: `f${fi}`, className: "iterate-obs-file" }, String(f))
@@ -2941,9 +4168,9 @@ function ObservatoryPanel(props) {
2941
4168
  React.createElement(
2942
4169
  "div",
2943
4170
  { className: "iterate-obs-bar", style: { marginTop: 6 } },
2944
- React.createElement("b", {}, "\u53D1\u73B0")
4171
+ React.createElement("b", {}, t("\u53D1\u73B0"))
2945
4172
  ),
2946
- ...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}`))
2947
4174
  ) : null
2948
4175
  );
2949
4176
  });
@@ -2954,7 +4181,7 @@ function ObservatoryPanel(props) {
2954
4181
  "div",
2955
4182
  { className: "iterate-obs-block-head" },
2956
4183
  React.createElement("b", {}, `Round ${typeof r.round === "number" ? r.round : ri + 1}`),
2957
- 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 })),
2958
4185
  React.createElement("span", { className: "iterate-obs-head-meta" }, `${fCount} findings`)
2959
4186
  ),
2960
4187
  React.createElement("div", { className: "iterate-obs-block-body" }, ...threadBlocks)
@@ -2965,9 +4192,48 @@ function ObservatoryPanel(props) {
2965
4192
  const renderTrend = () => {
2966
4193
  const conv = manifest.convergence || [];
2967
4194
  if (conv.length === 0) {
2968
- 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"));
2969
4196
  }
2970
4197
  const points = conv.map((n, i) => ({ round: i + 1, count: n }));
4198
+ const report = latestReport(session);
4199
+ const comparison = buildRoundComparison(report, manifest.validations || []);
4200
+ const comparisonTable = comparison.length === 0 ? null : React.createElement(
4201
+ "table",
4202
+ { className: "iterate-obs-table" },
4203
+ React.createElement(
4204
+ "thead",
4205
+ {},
4206
+ React.createElement(
4207
+ "tr",
4208
+ {},
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"))
4214
+ )
4215
+ ),
4216
+ React.createElement(
4217
+ "tbody",
4218
+ {},
4219
+ ...comparison.map((row) => {
4220
+ const sevText = Object.entries(row.severities).map(([k, v]) => `${k}:${v}`).join(" \xB7 ");
4221
+ const anyFailed = row.validations.some((v) => v.allowed !== true);
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") : "" });
4223
+ return React.createElement(
4224
+ "tr",
4225
+ { key: `cmp-${row.round}` },
4226
+ React.createElement("td", {}, `Round ${row.round}`),
4227
+ React.createElement("td", {}, String(row.findings)),
4228
+ React.createElement("td", {}, String(row.fixed)),
4229
+ React.createElement("td", {}, sevText || "\u2014"),
4230
+ React.createElement("td", {
4231
+ style: anyFailed ? { color: "var(--dsw-alias-state-error-primary)", fontWeight: 600 } : row.validations.length > 0 ? { color: "var(--dsw-alias-state-success-primary)" } : void 0
4232
+ }, valText)
4233
+ );
4234
+ })
4235
+ )
4236
+ );
2971
4237
  return React.createElement(
2972
4238
  "div",
2973
4239
  {},
@@ -2975,14 +4241,29 @@ function ObservatoryPanel(props) {
2975
4241
  React.createElement(
2976
4242
  "div",
2977
4243
  { className: "iterate-obs-bar", style: { marginTop: 6 } },
2978
- `\u5404\u8F6E\u53D1\u73B0\u6570\u91CF\uFF1A${conv.join(" \u2192 ")}${typeof manifest.round === "number" ? ` \xB7 \u5F53\u524D Round ${manifest.round}` : ""}`
2979
- )
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 }) : "" })
4245
+ ),
4246
+ comparisonTable ? React.createElement(
4247
+ "div",
4248
+ { className: "iterate-obs-block", style: { marginTop: 8 } },
4249
+ React.createElement(
4250
+ "div",
4251
+ { className: "iterate-obs-block-head" },
4252
+ React.createElement("span", {}, t("\u8DE8\u8F6E\u5BF9\u6BD4\uFF08\u672C\u4F1A\u8BDD\uFF09")),
4253
+ React.createElement(
4254
+ "span",
4255
+ { className: "iterate-obs-head-meta" },
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")
4257
+ )
4258
+ ),
4259
+ React.createElement("div", { className: "iterate-obs-block-body" }, comparisonTable)
4260
+ ) : null
2980
4261
  );
2981
4262
  };
2982
4263
  const renderFindings = () => {
2983
4264
  const findings = manifest.findings || [];
2984
4265
  if (findings.length === 0) {
2985
- 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"));
2986
4267
  }
2987
4268
  const opts = buildFilterOptions(findings);
2988
4269
  const { filtered } = filterFindingsWithIndices(findings, f3Filter);
@@ -2996,13 +4277,13 @@ function ObservatoryPanel(props) {
2996
4277
  const filterBar = React.createElement(
2997
4278
  "div",
2998
4279
  { className: "iterate-obs-bar", style: { marginBottom: 8, flexWrap: "wrap" } },
2999
- React.createElement("b", {}, "\u53D1\u73B0"),
4280
+ React.createElement("b", {}, t("\u53D1\u73B0")),
3000
4281
  ...opts.severities.map(
3001
4282
  (s) => React.createElement("button", {
3002
4283
  key: `sev-${s.value}`,
3003
4284
  className: "iterate-filter-chip",
3004
4285
  "data-active": f3Filter.severities.includes(s.value) ? "" : void 0,
3005
- title: `${s.value}\uFF08${s.count} \u9879\uFF09`,
4286
+ title: tf(`{a0}\uFF08{a1} \u9879\uFF09`, { a0: s.value, a1: s.count }),
3006
4287
  onClick: () => toggleSeverity(s.value)
3007
4288
  }, `${s.value} ${s.count}`)
3008
4289
  ),
@@ -3011,13 +4292,13 @@ function ObservatoryPanel(props) {
3011
4292
  {
3012
4293
  className: "iterate-filter-select",
3013
4294
  value: f3Filter.dimensions[0] || "",
3014
- "aria-label": "\u6309\u7EF4\u5EA6\u7B5B\u9009",
4295
+ "aria-label": t("\u6309\u7EF4\u5EA6\u7B5B\u9009"),
3015
4296
  onChange: (e) => {
3016
4297
  const v = e.target.value;
3017
4298
  setF3Filter((prev) => ({ ...prev, dimensions: v ? [v] : [] }));
3018
4299
  }
3019
4300
  },
3020
- React.createElement("option", { value: "" }, "\u5168\u90E8\u7EF4\u5EA6"),
4301
+ React.createElement("option", { value: "" }, t("\u5168\u90E8\u7EF4\u5EA6")),
3021
4302
  ...opts.dimensions.map(
3022
4303
  (d) => React.createElement("option", { key: d.value, value: d.value }, `${d.value} (${d.count})`)
3023
4304
  )
@@ -3025,16 +4306,16 @@ function ObservatoryPanel(props) {
3025
4306
  React.createElement("input", {
3026
4307
  className: "iterate-filter-search",
3027
4308
  type: "search",
3028
- placeholder: "\u641C\u7D22\u53D1\u73B0\u2026",
3029
- "aria-label": "\u641C\u7D22\u53D1\u73B0",
4309
+ placeholder: t("\u641C\u7D22\u53D1\u73B0\u2026"),
4310
+ "aria-label": t("\u641C\u7D22\u53D1\u73B0"),
3030
4311
  value: f3Filter.search,
3031
4312
  onChange: (e) => setF3Filter((prev) => ({ ...prev, search: e.target.value }))
3032
4313
  }),
3033
4314
  filterActive ? React.createElement("button", {
3034
4315
  className: "iterate-filter-clear",
3035
4316
  onClick: () => setF3Filter({ severities: [], dimensions: [], search: "" }),
3036
- title: "\u6E05\u9664\u5168\u90E8\u7B5B\u9009"
3037
- }, "\u6E05\u9664\u7B5B\u9009") : null,
4317
+ title: t("\u6E05\u9664\u5168\u90E8\u7B5B\u9009")
4318
+ }, t("\u6E05\u9664\u7B5B\u9009")) : null,
3038
4319
  React.createElement("span", { className: "iterate-filter-count" }, `${filtered.length}/${findings.length}`)
3039
4320
  );
3040
4321
  const findingBlocks = filtered.map((f, i) => {
@@ -3059,7 +4340,7 @@ function ObservatoryPanel(props) {
3059
4340
  React.createElement("span", {}, severityLabel(finding.severity)),
3060
4341
  React.createElement("span", { className: "iterate-obs-file" }, loc),
3061
4342
  React.createElement("span", {}, String(finding.dimension || "")),
3062
- 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
3063
4344
  ),
3064
4345
  React.createElement(
3065
4346
  "div",
@@ -3072,21 +4353,21 @@ function ObservatoryPanel(props) {
3072
4353
  className: "iterate-btn",
3073
4354
  "data-copied": copiedKey === `${k}-fix` ? "" : void 0,
3074
4355
  onClick: () => copyInstruction(`${k}-fix`, String(finding.suggested_fix || "")),
3075
- title: "\u590D\u5236\u8BE5 finding \u7684\u5EFA\u8BAE\u4FEE\u590D\u6587\u672C"
3076
- }, 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")),
3077
4358
  React.createElement("button", {
3078
4359
  className: "iterate-btn",
3079
4360
  "data-copied": copiedKey === `${k}-known` ? "" : void 0,
3080
4361
  onClick: () => copyInstruction(`${k}-known`, knownInstruction),
3081
- title: "\u590D\u5236 iterate_triage \u6307\u4EE4\u6587\u672C\uFF08\u6807\u8BB0\u4E3A known_intentional\uFF09"
3082
- }, 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"),
3083
4364
  React.createElement("button", {
3084
4365
  className: "iterate-btn",
3085
4366
  "data-primary": "",
3086
4367
  "data-copied": copiedKey === `${k}-repair` ? "" : void 0,
3087
4368
  onClick: () => copyInstruction(`${k}-repair`, fixInstruction),
3088
- title: "\u590D\u5236 iterate_fix \u6307\u4EE4\u6587\u672C"
3089
- }, 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"))
3090
4371
  )
3091
4372
  )
3092
4373
  );
@@ -3096,7 +4377,7 @@ function ObservatoryPanel(props) {
3096
4377
  "div",
3097
4378
  {},
3098
4379
  filterBar,
3099
- 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"))
3100
4381
  );
3101
4382
  }
3102
4383
  return React.createElement("div", {}, filterBar, ...findingBlocks);
@@ -3104,16 +4385,12 @@ function ObservatoryPanel(props) {
3104
4385
  const renderFixes = () => {
3105
4386
  const fixes = manifest.fixes || [];
3106
4387
  if (fixes.length === 0) {
3107
- 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"));
3108
4389
  }
3109
4390
  return React.createElement("div", {}, ...fixes.map((f, i) => {
3110
4391
  const k = `f4-${i}`;
3111
4392
  const id = String(f.id || `fix#${i + 1}`);
3112
- const rollbackText = `\u8BF7\u8C03\u7528 \`iterate_rollback\` \u56DE\u6EDA\u4EE5\u4E0B\u4FEE\u590D\uFF1A
3113
-
3114
- \`\`\`json
3115
- ${JSON.stringify({ id }, null, 2)}
3116
- \`\`\``;
4393
+ const rollbackText = buildRollbackInstruction(id);
3117
4394
  const added = typeof f.linesAdded === "number" ? f.linesAdded : 0;
3118
4395
  const removed = typeof f.linesRemoved === "number" ? f.linesRemoved : 0;
3119
4396
  return React.createElement(
@@ -3124,7 +4401,7 @@ ${JSON.stringify({ id }, null, 2)}
3124
4401
  { className: "iterate-obs-block-head" },
3125
4402
  React.createElement("b", {}, id),
3126
4403
  React.createElement("span", { className: "iterate-obs-file" }, String(f.file || "?")),
3127
- 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")),
3128
4405
  typeof f.round === "number" ? React.createElement("span", { className: "iterate-obs-head-meta" }, `Round ${f.round}`) : null
3129
4406
  ),
3130
4407
  React.createElement(
@@ -3141,8 +4418,8 @@ ${JSON.stringify({ id }, null, 2)}
3141
4418
  "data-danger": "",
3142
4419
  "data-copied": copiedKey === `${k}-rb` ? "" : void 0,
3143
4420
  onClick: () => copyInstruction(`${k}-rb`, rollbackText),
3144
- title: "\u590D\u5236 iterate_rollback \u6307\u4EE4\u6587\u672C"
3145
- }, 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"))
3146
4423
  )
3147
4424
  )
3148
4425
  );
@@ -3151,22 +4428,14 @@ ${JSON.stringify({ id }, null, 2)}
3151
4428
  const renderCheckpoint = () => {
3152
4429
  const cp = manifest.checkpoint;
3153
4430
  if (!cp) {
3154
- return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u65AD\u70B9");
4431
+ return React.createElement(
4432
+ "div",
4433
+ { className: "iterate-obs-empty" },
4434
+ diskEmptyStateText(toolCalledInSession(session, "iterate_transcript"), t("\u65AD\u70B9"), getLang())
4435
+ );
3155
4436
  }
3156
- const resumeText = `\u8BF7\u8C03\u7528 \`iterate_checkpoint\` \u4ECE\u65AD\u70B9\u6062\u590D\u8FED\u4EE3\uFF1A
3157
-
3158
- \`\`\`json
3159
- ${JSON.stringify({
3160
- operation: "resume",
3161
- mode: String(cp.mode || "normal"),
3162
- maxRounds: typeof cp.maxRounds === "number" ? cp.maxRounds : null
3163
- }, null, 2)}
3164
- \`\`\``;
3165
- const clearText = `\u8BF7\u8C03\u7528 \`iterate_checkpoint\` \u6E05\u9664\u5F53\u524D\u65AD\u70B9\uFF1A
3166
-
3167
- \`\`\`json
3168
- ${JSON.stringify({ operation: "clear" }, null, 2)}
3169
- \`\`\``;
4437
+ const resumeText = buildCheckpointResumeInstruction();
4438
+ const clearText = buildCheckpointClearInstruction();
3170
4439
  const item = (label, value) => React.createElement("span", { className: "iterate-obs-chip" }, `${label} ${String(value ?? "?")}`);
3171
4440
  return React.createElement(
3172
4441
  "div",
@@ -3174,7 +4443,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3174
4443
  React.createElement(
3175
4444
  "div",
3176
4445
  { className: "iterate-obs-block-head" },
3177
- React.createElement("b", {}, "\u65AD\u70B9"),
4446
+ React.createElement("b", {}, t("\u65AD\u70B9")),
3178
4447
  React.createElement("span", { className: "iterate-obs-head-meta" }, String(cp.updatedAt || ""))
3179
4448
  ),
3180
4449
  React.createElement(
@@ -3197,45 +4466,92 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3197
4466
  "data-primary": "",
3198
4467
  "data-copied": copiedKey === "cp-resume" ? "" : void 0,
3199
4468
  onClick: () => copyInstruction("cp-resume", resumeText),
3200
- title: "\u590D\u5236 iterate_checkpoint resume \u6307\u4EE4\u6587\u672C\uFF08\u52A0\u8F7D\u65AD\u70B9\u5E76\u8BA1\u6570\u4E00\u6B21\u6062\u590D\uFF09"
3201
- }, 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")),
3202
4471
  React.createElement("button", {
3203
4472
  className: "iterate-btn",
3204
4473
  "data-danger": "",
3205
4474
  "data-copied": copiedKey === "cp-clear" ? "" : void 0,
3206
4475
  onClick: () => copyInstruction("cp-clear", clearText),
3207
- title: "\u590D\u5236 iterate_checkpoint clear \u6307\u4EE4\u6587\u672C\uFF08\u6E05\u9664\u5F53\u524D\u65AD\u70B9\uFF0C\u4F9B\u91CD\u65B0\u5F00\u59CB\uFF09"
3208
- }, 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"))
3209
4478
  )
3210
4479
  )
3211
4480
  );
3212
4481
  };
3213
4482
  const renderConsole = () => {
3214
4483
  const approval = manifest.approval || { policy: "ask" };
3215
- 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") };
3216
4485
  const policyLabel = policyLabelMap[String(approval.policy || "ask")] || String(approval.policy || "ask");
3217
4486
  const nudge = manifest.nudge || null;
3218
4487
  const present = Boolean(nudge && nudge.text);
3219
4488
  const activeNudgeText = nudge && nudge.text ? nudge.text : "";
3220
- 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
3221
4490
 
3222
4491
  \`\`\`json
3223
- ${JSON.stringify({ operation: "nudge", text: nudgeText }, null, 2)}
3224
- \`\`\``;
3225
- 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
3226
4495
 
3227
4496
  \`\`\`json
3228
- ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3229
- \`\`\``;
4497
+ {a0}
4498
+ \`\`\``, { a0: JSON.stringify({ operation: "nudge", text: null }, null, 2) });
4499
+ const validations = Array.isArray(manifest.validations) ? manifest.validations : [];
4500
+ const abortedByValidation = String(manifest.stoppedReason || "") === "aborted_by_validation";
4501
+ const validationRows = validations.map((v, i) => {
4502
+ const allowed = v.allowed === true;
4503
+ const failed = !allowed;
4504
+ const highlight = failed && abortedByValidation;
4505
+ const exit = typeof v.exitCode === "number" ? `exit ${v.exitCode}` : "exit \u2014";
4506
+ return React.createElement(
4507
+ "div",
4508
+ {
4509
+ key: `val-${i}`,
4510
+ className: "iterate-obs-row",
4511
+ "data-status": allowed ? "pass" : "fail",
4512
+ "data-highlight": highlight ? "" : void 0,
4513
+ title: v.rejectReason ? String(v.rejectReason) : void 0
4514
+ },
4515
+ React.createElement("span", { className: "iterate-obs-head-meta" }, `Round ${typeof v.round === "number" ? v.round : "?"}`),
4516
+ React.createElement("code", { className: "iterate-obs-mono" }, String(v.command || "")),
4517
+ React.createElement(
4518
+ "span",
4519
+ { className: "iterate-obs-chip", "data-status": allowed ? "pass" : "fail" },
4520
+ allowed ? t("\u901A\u8FC7") : t("\u5931\u8D25")
4521
+ ),
4522
+ React.createElement("span", { className: "iterate-obs-head-meta" }, exit),
4523
+ highlight ? React.createElement("span", { className: "iterate-obs-chip", "data-status": "fail" }, t("\u5BFC\u81F4\u4E2D\u6B62")) : null
4524
+ );
4525
+ });
4526
+ const validationsBlock = React.createElement(
4527
+ "div",
4528
+ { className: "iterate-obs-block", style: { marginTop: 8 } },
4529
+ React.createElement(
4530
+ "div",
4531
+ { className: "iterate-obs-block-head" },
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
4535
+ ),
4536
+ React.createElement(
4537
+ "div",
4538
+ { className: "iterate-obs-block-body" },
4539
+ validations.length === 0 ? React.createElement(
4540
+ "div",
4541
+ { className: "iterate-obs-empty" },
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")
4543
+ ) : React.createElement(React.Fragment, null, ...validationRows)
4544
+ )
4545
+ );
3230
4546
  return React.createElement(
3231
4547
  "div",
3232
4548
  { className: "iterate-obs-block" },
3233
4549
  React.createElement(
3234
4550
  "div",
3235
4551
  { className: "iterate-obs-block-head" },
3236
- React.createElement("span", {}, "\u8FD0\u884C\u63A7\u5236\u53F0"),
4552
+ React.createElement("span", {}, t("\u8FD0\u884C\u63A7\u5236\u53F0")),
3237
4553
  React.createElement("span", { className: "iterate-obs-badge", "data-live": live ? "" : void 0 }, runStatusText(live, manifest)),
3238
- 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 }))
3239
4555
  ),
3240
4556
  React.createElement(
3241
4557
  "div",
@@ -3243,21 +4559,21 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3243
4559
  present ? React.createElement(
3244
4560
  "div",
3245
4561
  { className: "iterate-obs-bar", style: { marginBottom: 6 } },
3246
- React.createElement("b", {}, "\u5F53\u524D nudge"),
4562
+ React.createElement("b", {}, t("\u5F53\u524D nudge")),
3247
4563
  React.createElement("span", { className: "iterate-obs-msg" }, activeNudgeText),
3248
4564
  React.createElement("button", {
3249
4565
  className: "iterate-btn",
3250
4566
  "data-copied": copiedKey === "nudge-clear" ? "" : void 0,
3251
4567
  onClick: () => copyInstruction("nudge-clear", activeClearInstruction),
3252
- 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"
3253
- }, 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"))
3254
4570
  ) : null,
3255
4571
  React.createElement("textarea", {
3256
4572
  className: "iterate-obs-input iterate-obs-mono",
3257
4573
  rows: 3,
3258
- placeholder: "\u8D77\u8349\u4E00\u6761 nudge \u65B9\u5411\u6307\u4EE4\u2026",
4574
+ placeholder: t("\u8D77\u8349\u4E00\u6761 nudge \u65B9\u5411\u6307\u4EE4\u2026"),
3259
4575
  value: nudgeText,
3260
- "aria-label": "nudge \u8349\u7A3F",
4576
+ "aria-label": t("nudge \u8349\u7A3F"),
3261
4577
  onChange: (e) => setNudgeText(e.target.value)
3262
4578
  }),
3263
4579
  React.createElement(
@@ -3269,20 +4585,21 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3269
4585
  "data-copied": copiedKey === "nudge" ? "" : void 0,
3270
4586
  disabled: !nudgeText,
3271
4587
  onClick: () => copyInstruction("nudge", nudgeInstruction),
3272
- title: "\u590D\u5236 iterate_transcript nudge \u6307\u4EE4\u6587\u672C"
3273
- }, copiedKey === "nudge" ? "\u5DF2\u590D\u5236" : "\u590D\u5236 nudge \u6307\u4EE4")
3274
- )
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"))
4590
+ ),
4591
+ validationsBlock
3275
4592
  )
3276
4593
  );
3277
4594
  };
3278
4595
  const renderTimeline = () => {
3279
4596
  const entries = manifest.timeline || [];
3280
4597
  if (entries.length === 0) {
3281
- 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"));
3282
4599
  }
3283
- 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();
3284
4601
  const rounds = Array.from(
3285
- 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))
3286
4603
  ).sort((a, b) => Number(a) - Number(b));
3287
4604
  const filtered = filterTimelineEntries(entries, {
3288
4605
  type: timelineType,
@@ -3296,14 +4613,14 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3296
4613
  setTimelineSearch("");
3297
4614
  };
3298
4615
  const sorted = filtered.slice();
3299
- const rows = sorted.map((t, i) => {
4616
+ const rows = sorted.map((t2, i) => {
3300
4617
  const k = `f7-${i}`;
3301
4618
  let dataText = "";
3302
- if (t.data && typeof t.data === "object") {
4619
+ if (t2.data && typeof t2.data === "object") {
3303
4620
  try {
3304
- dataText = JSON.stringify(t.data);
4621
+ dataText = JSON.stringify(t2.data);
3305
4622
  } catch {
3306
- dataText = String(t.data);
4623
+ dataText = String(t2.data);
3307
4624
  }
3308
4625
  }
3309
4626
  return React.createElement(
@@ -3312,11 +4629,11 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3312
4629
  React.createElement(
3313
4630
  "div",
3314
4631
  { className: "iterate-obs-bar", style: { width: "100%" } },
3315
- React.createElement("span", { className: "iterate-obs-chip" }, String(t.type || "?")),
4632
+ React.createElement("span", { className: "iterate-obs-chip" }, String(t2.type || "?")),
3316
4633
  React.createElement(
3317
4634
  "span",
3318
4635
  { className: "iterate-obs-head-meta" },
3319
- `${typeof t.round === "number" ? `Round ${t.round} \xB7 ` : ""}${String(t.timestamp || "")}`
4636
+ `${typeof t2.round === "number" ? `Round ${t2.round} \xB7 ` : ""}${String(t2.timestamp || "")}`
3320
4637
  )
3321
4638
  ),
3322
4639
  dataText ? React.createElement("div", { className: "iterate-obs-code" }, dataText) : null
@@ -3325,42 +4642,42 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3325
4642
  const filterBar = React.createElement(
3326
4643
  "div",
3327
4644
  { className: "iterate-obs-bar", style: { marginBottom: 8 } },
3328
- React.createElement("b", {}, "\u65F6\u95F4\u7EBF"),
4645
+ React.createElement("b", {}, t("\u65F6\u95F4\u7EBF")),
3329
4646
  React.createElement(
3330
4647
  "select",
3331
4648
  {
3332
4649
  className: "iterate-filter-select",
3333
4650
  value: timelineType,
3334
- "aria-label": "\u6309\u65F6\u95F4\u7EBF\u7C7B\u578B\u7B5B\u9009",
4651
+ "aria-label": t("\u6309\u65F6\u95F4\u7EBF\u7C7B\u578B\u7B5B\u9009"),
3335
4652
  onChange: (e) => setTimelineType(e.target.value)
3336
4653
  },
3337
- React.createElement("option", { value: "" }, "\u5168\u90E8\u7C7B\u578B"),
3338
- ...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))
3339
4656
  ),
3340
4657
  React.createElement(
3341
4658
  "select",
3342
4659
  {
3343
4660
  className: "iterate-filter-select",
3344
4661
  value: timelineRound,
3345
- "aria-label": "\u6309\u8F6E\u6B21\u7B5B\u9009",
4662
+ "aria-label": t("\u6309\u8F6E\u6B21\u7B5B\u9009"),
3346
4663
  onChange: (e) => setTimelineRound(e.target.value)
3347
4664
  },
3348
- React.createElement("option", { value: "" }, "\u5168\u90E8\u8F6E\u6B21"),
4665
+ React.createElement("option", { value: "" }, t("\u5168\u90E8\u8F6E\u6B21")),
3349
4666
  ...rounds.map((r) => React.createElement("option", { key: r, value: r }, `Round ${r}`))
3350
4667
  ),
3351
4668
  React.createElement("input", {
3352
4669
  className: "iterate-filter-search",
3353
4670
  type: "search",
3354
- placeholder: "\u641C\u7D22\u65F6\u95F4\u7EBF\u2026",
3355
- "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"),
3356
4673
  value: timelineSearch,
3357
4674
  onChange: (e) => setTimelineSearch(e.target.value)
3358
4675
  }),
3359
4676
  filterActive ? React.createElement("button", {
3360
4677
  className: "iterate-filter-clear",
3361
4678
  onClick: clearTimelineFilter,
3362
- title: "\u6E05\u9664\u5168\u90E8\u7B5B\u9009"
3363
- }, "\u6E05\u9664") : null,
4679
+ title: t("\u6E05\u9664\u5168\u90E8\u7B5B\u9009")
4680
+ }, t("\u6E05\u9664")) : null,
3364
4681
  React.createElement("span", { className: "iterate-filter-count" }, `${filtered.length}/${entries.length}`)
3365
4682
  );
3366
4683
  if (rows.length === 0) {
@@ -3368,42 +4685,47 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
3368
4685
  "div",
3369
4686
  {},
3370
4687
  filterBar,
3371
- 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"))
3372
4689
  );
3373
4690
  }
3374
4691
  return React.createElement("div", {}, filterBar, ...rows);
3375
4692
  };
3376
4693
  const renderQualityGate = () => {
3377
- const gateInstruction = "\u8BF7\u8C03\u7528 `iterate_quality_gate` \u67E5\u8BE2\u5F53\u524D\u8D28\u91CF\u95E8\u7981\u72B6\u6001";
3378
- const gateClearInstruction = `\u8BF7\u8C03\u7528 \`iterate_quality_gate\` \u6E05\u9664\u5F53\u524D\u8D28\u91CF\u95E8\u7981\u8BC1\u4E66\uFF1A
3379
-
3380
- \`\`\`json
3381
- ${JSON.stringify({ operation: "clear" }, null, 2)}
3382
- \`\`\``;
4694
+ const gateInstruction = buildQualityGateQueryInstruction();
4695
+ const gateClearInstruction = buildQualityGateClearInstruction();
3383
4696
  const gate = qualityGate;
3384
4697
  const dims = gate && gate.dimensions ? gate.dimensions : [];
3385
4698
  const hasGate = gate !== null && Boolean(gate.overallStatus || gate.overallScore != null || dims.length > 0);
3386
4699
  const status = gate && gate.overallStatus ? String(gate.overallStatus) : "pending";
3387
4700
  const statusLabel = status === "pass" ? "PASS" : status === "fail" ? "FAIL" : "PENDING";
3388
4701
  const num = (v) => Number(v != null ? v : 0);
4702
+ const pulled = toolCalledInSession(session, "iterate_quality_gate");
3389
4703
  const headerBar = React.createElement(
3390
4704
  "div",
3391
4705
  { className: "iterate-obs-bar", style: { marginBottom: 8 } },
3392
- React.createElement("b", {}, "\u8D28\u91CF\u95E8\u7981"),
4706
+ React.createElement("b", {}, t("\u8D28\u91CF\u95E8\u7981")),
3393
4707
  React.createElement("button", {
3394
4708
  className: "iterate-btn",
3395
4709
  "data-primary": "",
3396
4710
  "data-copied": copiedKey === "qgate" ? "" : void 0,
3397
4711
  onClick: () => copyInstruction("qgate", gateInstruction),
3398
- title: "\u590D\u5236 iterate_quality_gate \u67E5\u8BE2\u6307\u4EE4"
3399
- }, 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")),
3400
4714
  React.createElement("button", {
3401
4715
  className: "iterate-btn",
3402
4716
  "data-danger": "",
3403
4717
  "data-copied": copiedKey === "qgate-clear" ? "" : void 0,
3404
4718
  onClick: () => copyInstruction("qgate-clear", gateClearInstruction),
3405
- title: "\u590D\u5236 iterate_quality_gate clear \u6307\u4EE4\u6587\u672C\uFF08\u91CD\u7F6E\u9648\u65E7\u7684 FAIL \u8BC1\u4E66\uFF09"
3406
- }, 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")),
4721
+ // Gap #4: the browser cannot read `.iterate/` directly — one click
4722
+ // copies the pull instruction that makes the model echo it into the stream.
4723
+ React.createElement("button", {
4724
+ className: "iterate-btn",
4725
+ "data-copied": copiedKey === "disk-snapshot" ? "" : void 0,
4726
+ onClick: () => copyInstruction("disk-snapshot", pullInstruction),
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"))
3407
4729
  );
3408
4730
  if (!hasGate) {
3409
4731
  return React.createElement(
@@ -3416,13 +4738,24 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3416
4738
  React.createElement(
3417
4739
  "div",
3418
4740
  { className: "iterate-obs-block-head" },
3419
- React.createElement("span", {}, "\u8D28\u91CF\u95E8\u7981\u89C6\u56FE"),
4741
+ React.createElement("span", {}, t("\u8D28\u91CF\u95E8\u7981\u89C6\u56FE")),
3420
4742
  React.createElement("span", { className: "iterate-obs-badge" }, "v3.0")
3421
4743
  ),
3422
4744
  React.createElement(
3423
4745
  "div",
3424
4746
  { className: "iterate-obs-block-body" },
3425
- React.createElement("div", { className: "iterate-obs-msg" }, "\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"),
4747
+ React.createElement(
4748
+ "div",
4749
+ { className: "iterate-obs-msg" },
4750
+ // Gap #4: distinguish "we pulled it and it is empty" from "we
4751
+ // never asked the model to read the disk".
4752
+ diskEmptyStateText(pulled, t("\u8D28\u91CF\u95E8\u7981\u8BC1\u4E66"), getLang())
4753
+ ),
4754
+ React.createElement(
4755
+ "div",
4756
+ { className: "iterate-obs-msg", style: { marginTop: 6 } },
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")
4758
+ ),
3426
4759
  React.createElement(
3427
4760
  "div",
3428
4761
  { className: "iterate-obs-bar", style: { marginTop: 8 } },
@@ -3430,8 +4763,14 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3430
4763
  className: "iterate-btn",
3431
4764
  "data-copied": copiedKey === "qgate-instr" ? "" : void 0,
3432
4765
  onClick: () => copyInstruction("qgate-instr", gateInstruction),
3433
- title: "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4"
3434
- }, 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")),
4768
+ React.createElement("button", {
4769
+ className: "iterate-btn",
4770
+ "data-copied": copiedKey === "disk-snapshot-2" ? "" : void 0,
4771
+ onClick: () => copyInstruction("disk-snapshot-2", pullInstruction),
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"))
3435
4774
  )
3436
4775
  )
3437
4776
  )
@@ -3451,7 +4790,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3451
4790
  React.createElement(
3452
4791
  "span",
3453
4792
  { className: "iterate-obs-head-meta" },
3454
- `\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) })
3455
4794
  )
3456
4795
  ),
3457
4796
  React.createElement(
@@ -3464,7 +4803,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3464
4803
  (d, i) => React.createElement(
3465
4804
  "span",
3466
4805
  { key: `gdim-${i}`, className: "iterate-obs-chip" },
3467
- `${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) })
3468
4807
  )
3469
4808
  )
3470
4809
  ) : null,
@@ -3486,32 +4825,33 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3486
4825
  style: { width: `${fill}%` }
3487
4826
  })
3488
4827
  ),
3489
- 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) }))
3490
4829
  );
3491
4830
  })
3492
4831
  ),
3493
- 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
3494
4833
  )
3495
4834
  )
3496
4835
  );
3497
4836
  };
3498
4837
  const renderExperienceBank = () => {
3499
- 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
3500
4839
 
3501
4840
  \`\`\`json
3502
- ${JSON.stringify({ operation: "search", query: expSearch }, null, 2)}
3503
- \`\`\`` : "\u8BF7\u8C03\u7528 `iterate_experience` \u5217\u51FA\u6240\u6709\u7ECF\u9A8C";
4841
+ {a0}
4842
+ \`\`\``, { a0: JSON.stringify({ operation: "search", query: expSearch }, null, 2) }) : buildExperienceListInstruction();
3504
4843
  const expEntries = experienceBank && experienceBank.entries ? experienceBank.entries : [];
3505
4844
  const query = expSearch.trim().toLowerCase();
3506
4845
  const visible = query ? expEntries.filter(
3507
- (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))
3508
4847
  ) : expEntries;
4848
+ const pulled = toolCalledInSession(session, "iterate_experience");
3509
4849
  const resultRows = visible.map((e, i) => {
3510
- 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
3511
4851
 
3512
4852
  \`\`\`json
3513
- ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
3514
- \`\`\``;
4853
+ {a0}
4854
+ \`\`\``, { a0: JSON.stringify({ operation: "get", id: e.id }, null, 2) });
3515
4855
  return React.createElement(
3516
4856
  "div",
3517
4857
  { key: `exp-${i}`, className: "iterate-obs-block" },
@@ -3520,7 +4860,7 @@ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
3520
4860
  { className: "iterate-obs-block-head" },
3521
4861
  React.createElement("span", { className: "iterate-obs-chip" }, String(e.dimension || "")),
3522
4862
  React.createElement("span", {}, String(e.pattern || "?")),
3523
- 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 }))
3524
4864
  ),
3525
4865
  React.createElement(
3526
4866
  "div",
@@ -3534,8 +4874,8 @@ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
3534
4874
  "data-primary": "",
3535
4875
  "data-copied": copiedKey === `exp-adopt-${i}` ? "" : void 0,
3536
4876
  onClick: () => copyInstruction(`exp-adopt-${i}`, adoptInstruction),
3537
- title: "\u590D\u5236\u91C7\u7EB3\u6307\u4EE4\uFF08\u67E5\u8BE2\u8BE5\u7ECF\u9A8C\u5E76\u5E94\u7528\u5230\u5F53\u524D\u4E0A\u4E0B\u6587\uFF09"
3538
- }, 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
3539
4879
  )
3540
4880
  )
3541
4881
  );
@@ -3546,15 +4886,21 @@ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
3546
4886
  React.createElement(
3547
4887
  "div",
3548
4888
  { className: "iterate-obs-bar", style: { marginBottom: 8 } },
3549
- React.createElement("b", {}, "\u7ECF\u9A8C\u94F6\u884C"),
3550
- 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 })),
3551
4891
  React.createElement("button", {
3552
4892
  className: "iterate-btn",
3553
4893
  "data-primary": "",
3554
4894
  "data-copied": copiedKey === "exp-list" ? "" : void 0,
3555
- onClick: () => copyInstruction("exp-list", "\u8BF7\u8C03\u7528 `iterate_experience` \u5217\u51FA\u6240\u6709\u7ECF\u9A8C"),
3556
- title: "\u590D\u5236\u5217\u51FA\u7ECF\u9A8C\u6307\u4EE4"
3557
- }, copiedKey === "exp-list" ? "\u5DF2\u590D\u5236" : "\u5217\u51FA\u7ECF\u9A8C")
4895
+ onClick: () => copyInstruction("exp-list", buildExperienceListInstruction()),
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")),
4898
+ React.createElement("button", {
4899
+ className: "iterate-btn",
4900
+ "data-copied": copiedKey === "exp-snapshot" ? "" : void 0,
4901
+ onClick: () => copyInstruction("exp-snapshot", pullInstruction),
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"))
3558
4904
  ),
3559
4905
  React.createElement(
3560
4906
  "div",
@@ -3562,8 +4908,8 @@ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
3562
4908
  React.createElement("input", {
3563
4909
  className: "iterate-filter-search",
3564
4910
  type: "search",
3565
- placeholder: "\u641C\u7D22\u7ECF\u9A8C\u6A21\u5F0F\u2026",
3566
- "aria-label": "\u641C\u7D22\u7ECF\u9A8C",
4911
+ placeholder: t("\u641C\u7D22\u7ECF\u9A8C\u6A21\u5F0F\u2026"),
4912
+ "aria-label": t("\u641C\u7D22\u7ECF\u9A8C"),
3567
4913
  value: expSearch,
3568
4914
  onChange: (e) => setExpSearch(e.target.value)
3569
4915
  }),
@@ -3572,30 +4918,36 @@ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
3572
4918
  "data-copied": copiedKey === "exp-search" ? "" : void 0,
3573
4919
  disabled: !expSearch,
3574
4920
  onClick: () => copyInstruction("exp-search", expInstruction),
3575
- title: "\u590D\u5236\u641C\u7D22\u6307\u4EE4"
3576
- }, 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"))
3577
4923
  ),
3578
- 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("div", { className: "iterate-obs-empty" }, "\u672C\u6B21\u4F1A\u8BDD\u6682\u65E0\u7ECF\u9A8C\u8BB0\u5F55\uFF0C\u53EF\u70B9\u51FB\u300C\u5217\u51FA\u7ECF\u9A8C\u300D\u67E5\u8BE2\u5386\u53F2\u7ECF\u9A8C")
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(
4925
+ "div",
4926
+ {},
4927
+ // Gap #4: separate "pulled and empty" from "never pulled".
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"))
4930
+ )
3579
4931
  );
3580
4932
  };
3581
4933
  const renderDefenseEvents = () => {
3582
- 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
3583
4935
 
3584
4936
  \`\`\`json
3585
- ${JSON.stringify({ operation: "list", type: defenseFilter || void 0 }, null, 2)}
3586
- \`\`\`` : "\u8BF7\u8C03\u7528 `iterate_defense_events` \u5217\u51FA\u6240\u6709\u9632\u5FA1\u4E8B\u4EF6";
4937
+ {a0}
4938
+ \`\`\``, { a0: JSON.stringify({ operation: "list", type: defenseFilter || void 0 }, null, 2) }) : buildDefenseEventsListInstruction();
3587
4939
  const typeOptions = [
3588
- { value: "", label: "\u5168\u90E8\u7C7B\u578B" },
3589
- { value: "precondition_failed", label: "\u524D\u7F6E\u6821\u9A8C\u5931\u8D25" },
3590
- { value: "rollback", label: "\u56DE\u6EDA" },
3591
- { value: "invariant_violated", label: "\u4E0D\u53D8\u91CF\u8FDD\u53CD" },
3592
- { 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") }
3593
4945
  ];
3594
4946
  const TYPE_LABEL = {
3595
- precondition_failed: "\u524D\u7F6E\u6821\u9A8C\u5931\u8D25",
3596
- rollback: "\u56DE\u6EDA",
3597
- invariant_violated: "\u4E0D\u53D8\u91CF\u8FDD\u53CD",
3598
- 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")
3599
4951
  };
3600
4952
  const counts = defenseEvents && defenseEvents.counts || {};
3601
4953
  const allEvents = defenseEvents && defenseEvents.events ? defenseEvents.events : [];
@@ -3604,32 +4956,42 @@ ${JSON.stringify({ operation: "list", type: defenseFilter || void 0 }, null, 2)}
3604
4956
  const headerBar = React.createElement(
3605
4957
  "div",
3606
4958
  { className: "iterate-obs-bar", style: { marginBottom: 8 } },
3607
- React.createElement("b", {}, "\u9632\u5FA1\u4E8B\u4EF6"),
3608
- 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 })),
3609
4961
  React.createElement("button", {
3610
4962
  className: "iterate-btn",
3611
4963
  "data-primary": "",
3612
4964
  "data-copied": copiedKey === "defense-list" ? "" : void 0,
3613
- onClick: () => copyInstruction("defense-list", "\u8BF7\u8C03\u7528 `iterate_defense_events` \u5217\u51FA\u6240\u6709\u9632\u5FA1\u4E8B\u4EF6"),
3614
- title: "\u590D\u5236\u5217\u51FA\u9632\u5FA1\u4E8B\u4EF6\u6307\u4EE4"
3615
- }, copiedKey === "defense-list" ? "\u5DF2\u590D\u5236" : "\u5217\u51FA\u4E8B\u4EF6"),
4965
+ onClick: () => copyInstruction("defense-list", buildDefenseEventsListInstruction()),
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")),
4968
+ React.createElement("button", {
4969
+ className: "iterate-btn",
4970
+ "data-copied": copiedKey === "defense-snapshot" ? "" : void 0,
4971
+ onClick: () => copyInstruction("defense-snapshot", pullInstruction),
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")),
3616
4974
  React.createElement("button", {
3617
4975
  className: "iterate-btn",
3618
4976
  "data-copied": copiedKey === "defense-counts" ? "" : void 0,
3619
- onClick: () => copyInstruction("defense-counts", "\u8BF7\u8C03\u7528 `iterate_defense_events` \u67E5\u8BE2\u4E8B\u4EF6\u7EDF\u8BA1"),
3620
- title: "\u590D\u5236\u7EDF\u8BA1\u6307\u4EE4"
3621
- }, copiedKey === "defense-counts" ? "\u5DF2\u590D\u5236" : "\u7EDF\u8BA1"),
4977
+ onClick: () => copyInstruction("defense-counts", tf(`\u8BF7\u8C03\u7528 \`iterate_defense_events\` \u67E5\u8BE2\u4E8B\u4EF6\u7EDF\u8BA1\uFF1A
4978
+
4979
+ \`\`\`json
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")),
3622
4984
  React.createElement("button", {
3623
4985
  className: "iterate-btn",
3624
4986
  "data-danger": "",
3625
4987
  "data-copied": copiedKey === "defense-clear" ? "" : void 0,
3626
- 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
3627
4989
 
3628
4990
  \`\`\`json
3629
- ${JSON.stringify({ operation: "clear" }, null, 2)}
3630
- \`\`\``),
3631
- title: "\u590D\u5236 iterate_defense_events clear \u6307\u4EE4\u6587\u672C\uFF08\u91CD\u7F6E\u9648\u65E7\u4E8B\u4EF6\u6D41\uFF09"
3632
- }, 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"))
3633
4995
  );
3634
4996
  if (allEvents.length === 0 && totalCount === 0) {
3635
4997
  return React.createElement(
@@ -3642,13 +5004,22 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3642
5004
  React.createElement(
3643
5005
  "div",
3644
5006
  { className: "iterate-obs-block-head" },
3645
- React.createElement("span", {}, "\u9632\u5FA1\u4E8B\u4EF6\u6D41"),
5007
+ React.createElement("span", {}, t("\u9632\u5FA1\u4E8B\u4EF6\u6D41")),
3646
5008
  React.createElement("span", { className: "iterate-obs-badge" }, "v3.0")
3647
5009
  ),
3648
5010
  React.createElement(
3649
5011
  "div",
3650
5012
  { className: "iterate-obs-block-body" },
3651
- React.createElement("div", { className: "iterate-obs-msg" }, "\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"),
5013
+ React.createElement(
5014
+ "div",
5015
+ { className: "iterate-obs-msg" },
5016
+ diskEmptyStateText(toolCalledInSession(session, "iterate_defense_events"), t("\u9632\u5FA1\u4E8B\u4EF6\u6D41"), getLang())
5017
+ ),
5018
+ React.createElement(
5019
+ "div",
5020
+ { className: "iterate-obs-msg", style: { marginTop: 6 } },
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")
5022
+ ),
3652
5023
  React.createElement(
3653
5024
  "div",
3654
5025
  { className: "iterate-obs-bar", style: { marginTop: 8 } },
@@ -3656,8 +5027,8 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3656
5027
  className: "iterate-btn",
3657
5028
  "data-copied": copiedKey === "defense-instr" ? "" : void 0,
3658
5029
  onClick: () => copyInstruction("defense-instr", defenseInstruction),
3659
- title: "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4"
3660
- }, 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"))
3661
5032
  )
3662
5033
  )
3663
5034
  )
@@ -3671,7 +5042,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3671
5042
  {
3672
5043
  className: "iterate-filter-select",
3673
5044
  value: defenseFilter,
3674
- "aria-label": "\u6309\u4E8B\u4EF6\u7C7B\u578B\u7B5B\u9009",
5045
+ "aria-label": t("\u6309\u4E8B\u4EF6\u7C7B\u578B\u7B5B\u9009"),
3675
5046
  onChange: (e) => setDefenseFilter(e.target.value)
3676
5047
  },
3677
5048
  ...typeOptions.map((opt) => React.createElement("option", { key: opt.value, value: opt.value }, opt.label))
@@ -3679,8 +5050,8 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3679
5050
  defenseFilter ? React.createElement("button", {
3680
5051
  className: "iterate-filter-clear",
3681
5052
  onClick: () => setDefenseFilter(""),
3682
- title: "\u6E05\u9664\u7C7B\u578B\u7B5B\u9009"
3683
- }, "\u6E05\u9664") : null
5053
+ title: t("\u6E05\u9664\u7C7B\u578B\u7B5B\u9009")
5054
+ }, t("\u6E05\u9664")) : null
3684
5055
  );
3685
5056
  let eventRows = [];
3686
5057
  if (visibleEvents.length > 0) {
@@ -3701,7 +5072,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3701
5072
  "div",
3702
5073
  { className: "iterate-obs-block-body" },
3703
5074
  React.createElement("div", { className: "iterate-obs-msg" }, String(e.description || "")),
3704
- 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
3705
5076
  )
3706
5077
  );
3707
5078
  });
@@ -3722,7 +5093,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3722
5093
  )
3723
5094
  )
3724
5095
  ),
3725
- 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"))
3726
5097
  );
3727
5098
  };
3728
5099
  const renderLive = () => {
@@ -3734,29 +5105,31 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3734
5105
  return React.createElement(
3735
5106
  "div",
3736
5107
  { className: "iterate-obs-empty" },
3737
- 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")),
3738
5109
  React.createElement(
3739
5110
  "div",
3740
5111
  { className: "iterate-obs-bar", style: { marginTop: 8 } },
3741
- React.createElement("button", { className: "iterate-btn", onClick: () => setTab("f1") }, "\u67E5\u770B\u5BA1\u67E5\u7EBF\u7A0B"),
3742
- 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"))
3743
5114
  )
3744
5115
  );
3745
5116
  }
3746
- 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"));
3747
5118
  }
3748
5119
  const types = Array.from(new Set(liveEntries.map((e) => String(e.type || "unknown")).filter(Boolean))).sort();
3749
5120
  const filtered = filterLiveEntries(liveEntries, liveType);
3750
5121
  const rows = filtered.map((e, i) => {
3751
5122
  const type = String(e.type || "unknown");
3752
- 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;
3753
5126
  const tool = String(e.tool || "");
3754
5127
  const target = String(e.target || "");
3755
5128
  const time = formatObsLiveTime(String(e.ts || ""));
3756
5129
  return React.createElement(
3757
5130
  "div",
3758
5131
  { key: `live-${i}`, className: "iterate-obs-row" },
3759
- 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),
3760
5133
  tool ? React.createElement("span", { className: "iterate-obs-chip" }, tool) : null,
3761
5134
  React.createElement("span", { className: "iterate-obs-msg" }, target || "\u2014"),
3762
5135
  React.createElement("span", { className: "iterate-obs-head-meta" }, time)
@@ -3765,23 +5138,23 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3765
5138
  const filterBar = React.createElement(
3766
5139
  "div",
3767
5140
  { className: "iterate-obs-bar", style: { marginBottom: 8 } },
3768
- React.createElement("b", {}, "\u5B9E\u65F6\u6D3B\u52A8"),
5141
+ React.createElement("b", {}, t("\u5B9E\u65F6\u6D3B\u52A8")),
3769
5142
  React.createElement(
3770
5143
  "select",
3771
5144
  {
3772
5145
  className: "iterate-filter-select",
3773
5146
  value: liveType,
3774
- "aria-label": "\u6309\u6D3B\u52A8\u7C7B\u578B\u7B5B\u9009",
5147
+ "aria-label": t("\u6309\u6D3B\u52A8\u7C7B\u578B\u7B5B\u9009"),
3775
5148
  onChange: (e) => setLiveType(e.target.value)
3776
5149
  },
3777
- React.createElement("option", { value: "" }, "\u5168\u90E8\u6D3B\u52A8"),
3778
- ...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))
3779
5152
  ),
3780
5153
  liveType ? React.createElement("button", {
3781
5154
  className: "iterate-filter-clear",
3782
5155
  onClick: () => setLiveType(""),
3783
- title: "\u6E05\u9664\u7C7B\u578B\u7B5B\u9009"
3784
- }, "\u6E05\u9664") : null,
5156
+ title: t("\u6E05\u9664\u7C7B\u578B\u7B5B\u9009")
5157
+ }, t("\u6E05\u9664")) : null,
3785
5158
  React.createElement("span", { className: "iterate-filter-count" }, `${filtered.length}/${liveEntries.length}`)
3786
5159
  );
3787
5160
  if (rows.length === 0) {
@@ -3789,7 +5162,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3789
5162
  "div",
3790
5163
  {},
3791
5164
  filterBar,
3792
- 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"))
3793
5166
  );
3794
5167
  }
3795
5168
  return React.createElement("div", {}, filterBar, ...rows);
@@ -3829,9 +5202,14 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3829
5202
  React.createElement(
3830
5203
  "div",
3831
5204
  { className: "iterate-obs-head", "data-closed": open ? void 0 : "", onClick: () => setOpen((v) => !v) },
3832
- React.createElement("span", { className: "iterate-obs-title" }, "iterate \u89C2\u6D4B\u53F0"),
5205
+ React.createElement("span", { className: "iterate-obs-title" }, t("iterate \u89C2\u6D4B\u53F0")),
3833
5206
  React.createElement("span", { className: "iterate-obs-badge", "data-live": live ? "" : void 0 }, runStatusText(live, manifest)),
3834
5207
  React.createElement("span", { className: "iterate-obs-head-meta" }, headMeta || "runtime"),
5208
+ // Gap #14: make the tab system discoverable without expanding it.
5209
+ React.createElement("span", {
5210
+ className: "iterate-obs-head-meta",
5211
+ title: obsHotkeyHint()
5212
+ }, obsHotkeyHint()),
3835
5213
  React.createElement("button", {
3836
5214
  className: "iterate-btn",
3837
5215
  "data-ghost": "",
@@ -3839,7 +5217,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3839
5217
  e.stopPropagation();
3840
5218
  setOpen((v) => !v);
3841
5219
  }
3842
- }, open ? "\u6536\u8D77" : "\u5C55\u5F00")
5220
+ }, open ? t("\u6536\u8D77") : t("\u5C55\u5F00"))
3843
5221
  ),
3844
5222
  open ? React.createElement(
3845
5223
  "div",
@@ -3850,14 +5228,14 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3850
5228
  React.createElement(
3851
5229
  "div",
3852
5230
  { className: "iterate-obs-block-head" },
3853
- React.createElement("b", {}, "\u590D\u5236\u672A\u6210\u529F"),
3854
- 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")),
3855
5233
  React.createElement("button", {
3856
5234
  className: "iterate-btn",
3857
5235
  "data-ghost": "",
3858
- "aria-label": "\u5173\u95ED\u590D\u5236\u5931\u8D25\u63D0\u793A",
5236
+ "aria-label": t("\u5173\u95ED\u590D\u5236\u5931\u8D25\u63D0\u793A"),
3859
5237
  onClick: () => setCopyFailText(null)
3860
- }, "\u5173\u95ED")
5238
+ }, t("\u5173\u95ED"))
3861
5239
  ),
3862
5240
  React.createElement("div", { className: "iterate-obs-code" }, copyFailText)
3863
5241
  ) : null,
@@ -3874,19 +5252,31 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
3874
5252
  "data-primary": "",
3875
5253
  "data-copied": copiedKey === "export" ? "" : void 0,
3876
5254
  onClick: exportObservatory,
3877
- 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"
3878
- }, 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")),
5257
+ // Gap #4: `.iterate/` artifacts are unreachable from the browser;
5258
+ // this copies the one instruction that makes the model echo them
5259
+ // into the session stream (which is what every panel scans).
5260
+ React.createElement("button", {
5261
+ className: "iterate-btn",
5262
+ "data-copied": copiedKey === "pull-snapshot" ? "" : void 0,
5263
+ onClick: () => copyInstruction("pull-snapshot", pullInstruction),
5264
+ title: pullTitle
5265
+ }, copiedKey === "pull-snapshot" ? t("\u5DF2\u590D\u5236") : t("\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167"))
3879
5266
  ),
3880
5267
  React.createElement(
3881
5268
  "div",
3882
- { className: "iterate-obs-tabs" },
5269
+ { className: "iterate-obs-tabs", role: "tablist", "aria-label": t("\u89C2\u6D4B\u53F0\u9875\u7B7E\uFF08\u5FEB\u6377\u952E 1-9 / 0 / -\uFF09") },
3883
5270
  ...OBS_TABS.map(
3884
- (t) => React.createElement("button", {
3885
- key: t.key,
5271
+ (t2) => React.createElement("button", {
5272
+ key: t2.key,
3886
5273
  className: "iterate-obs-tab",
3887
- "data-active": tab === t.key ? "" : void 0,
3888
- onClick: () => setTab(t.key)
3889
- }, t.label)
5274
+ role: "tab",
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))
3890
5280
  )
3891
5281
  ),
3892
5282
  React.createElement("div", { className: "iterate-obs-body" }, renderBody())
@@ -3899,11 +5289,21 @@ function selectTurnTail(owner) {
3899
5289
  if (scanSessionForRunSummary(owner.turn)) return { matched: true };
3900
5290
  return null;
3901
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);
3902
5297
  function apply(ctx) {
3903
5298
  slotsSvc = readSlots(ctx);
3904
5299
  themeSvc = readTheme(ctx);
3905
- storage = createStorage();
3906
- 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);
3907
5307
  themeEnabled = savedTheme === null ? true : savedTheme === "1";
3908
5308
  if (themeEnabled) applyThemeSkin();
3909
5309
  if (typeof document !== "undefined" && document.createElement && document.head) {
@@ -3933,35 +5333,35 @@ function apply(ctx) {
3933
5333
  "conversation.input.dock",
3934
5334
  () => slotsSvc?.register(
3935
5335
  { name: "conversation.input.dock", id: "iterate-dashboard", order: 90 },
3936
- (props) => React.createElement(ConvergenceDashboard, props)
5336
+ (props) => React.createElement(LangDashboard, props)
3937
5337
  )
3938
5338
  );
3939
5339
  slotsSvc.inject(
3940
5340
  "conversation.input.dock",
3941
5341
  () => slotsSvc?.register(
3942
5342
  { name: "conversation.input.dock", id: "iterate-observatory", order: 91 },
3943
- (props) => React.createElement(ObservatoryPanel, props)
5343
+ (props) => React.createElement(LangObservatory, props)
3944
5344
  )
3945
5345
  );
3946
5346
  slotsSvc.inject(
3947
5347
  "conversation.chat.turnTail",
3948
5348
  () => slotsSvc?.register(
3949
5349
  { name: "conversation.chat.turnTail", id: "iterate-turn-tail", select: selectTurnTail },
3950
- (props) => React.createElement(TurnTailEntry, props)
5350
+ (props) => React.createElement(LangTurnTail, props)
3951
5351
  )
3952
5352
  );
3953
5353
  slotsSvc.inject(
3954
5354
  "shell.overlay",
3955
5355
  () => slotsSvc?.register(
3956
5356
  { name: "shell.overlay", id: "iterate-progress", order: 0 },
3957
- (props) => React.createElement(ProgressCapsule, props)
5357
+ (props) => React.createElement(LangProgress, props)
3958
5358
  )
3959
5359
  );
3960
5360
  slotsSvc.inject(
3961
5361
  "settings.section",
3962
5362
  () => slotsSvc?.register(
3963
5363
  { name: "settings.section", id: "iterate-settings", order: 30, label: () => "iterate" },
3964
- (props) => React.createElement(SettingsPanel, props)
5364
+ (props) => React.createElement(LangSettings, props)
3965
5365
  )
3966
5366
  );
3967
5367
  }