iterate-plugin 2.12.3 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -445,12 +445,15 @@ function normalizeTranscript(manifest) {
445
445
  const ng = safeGet(src, "nudge");
446
446
  const nudge = ng && typeof ng === "object" ? { timestamp: asStr(safeGet(ng, "timestamp")), text: asStr(safeGet(ng, "text")) } : null;
447
447
  const ap = safeGet(src, "approval");
448
+ const tm = safeGet(src, "taskMode");
448
449
  return {
449
450
  version: asNum(safeGet(src, "version")),
450
451
  project: asStr(safeGet(src, "project")),
451
452
  updatedAt: asStr(safeGet(src, "updatedAt")),
452
453
  active: asBool(safeGet(src, "active")),
453
454
  mode: asStr(safeGet(src, "mode")) || null,
455
+ // v3.0: harness task_mode (code/iterate), tolerated when absent.
456
+ taskMode: tm === "code" || tm === "iterate" ? tm : null,
454
457
  goal: asStr(safeGet(src, "goal")),
455
458
  phases: asArray(safeGet(src, "phases")).map((p) => typeof p === "string" ? p : ""),
456
459
  round: asNum(safeGet(src, "round")),
@@ -471,6 +474,266 @@ function normalizeTranscript(manifest) {
471
474
  approval: ap && typeof ap === "object" ? { active: asBool(safeGet(ap, "active")), policy: asStr(safeGet(ap, "policy")) || "ask" } : { active: false, policy: "ask" }
472
475
  };
473
476
  }
477
+ var DEFENSE_EVENT_TYPES = ["precondition_failed", "rollback", "invariant_violated", "assumption_falsified"];
478
+ function isQualityGateSnapshot(obj) {
479
+ if (!obj || typeof obj !== "object") return false;
480
+ const o = (
481
+ /** @type {Record<string, unknown>} */
482
+ obj
483
+ );
484
+ const status = safeGet(o, "overallStatus");
485
+ return (status === "pass" || status === "fail" || status === "pending") && typeof safeGet(o, "overallScore") === "number" && Array.isArray(safeGet(o, "dimensions"));
486
+ }
487
+ function isExperienceBankResult(obj) {
488
+ if (!obj || typeof obj !== "object") return false;
489
+ const o = (
490
+ /** @type {Record<string, unknown>} */
491
+ obj
492
+ );
493
+ const entry = safeGet(o, "entry");
494
+ return Array.isArray(safeGet(o, "entries")) && typeof safeGet(o, "count") === "number" || !!entry && typeof entry === "object" && typeof safeGet(o, "operation") === "string";
495
+ }
496
+ function isDefenseEventsResult(obj) {
497
+ if (!obj || typeof obj !== "object") return false;
498
+ const o = (
499
+ /** @type {Record<string, unknown>} */
500
+ obj
501
+ );
502
+ const counts = safeGet(o, "counts");
503
+ return Array.isArray(safeGet(o, "events")) && typeof safeGet(o, "count") === "number" || !!counts && typeof counts === "object" && typeof safeGet(o, "operation") === "string";
504
+ }
505
+ function findFirstInObject(obj, predicate, seen, maxDepth = 20) {
506
+ if (maxDepth <= 0) return null;
507
+ if (!obj || typeof obj !== "object") return null;
508
+ const s = seen || /* @__PURE__ */ new Set();
509
+ if (s.has(obj)) return null;
510
+ s.add(obj);
511
+ if (Array.isArray(obj)) {
512
+ for (const item of obj) {
513
+ const found = findFirstInObject(item, predicate, s, maxDepth - 1);
514
+ if (found) return found;
515
+ }
516
+ return null;
517
+ }
518
+ if (typeof obj === "object") {
519
+ const o = (
520
+ /** @type {Record<string, unknown>} */
521
+ obj
522
+ );
523
+ if (predicate(o)) return o;
524
+ for (const key of safeKeys(o)) {
525
+ const val = safeGet(o, key);
526
+ if (val && typeof val === "object") {
527
+ const found = findFirstInObject(val, predicate, s, maxDepth - 1);
528
+ if (found) return found;
529
+ }
530
+ }
531
+ }
532
+ return null;
533
+ }
534
+ function findQualityGateInObject(obj) {
535
+ return findFirstInObject(obj, (o) => isQualityGateSnapshot(o));
536
+ }
537
+ function findExperienceResultInObject(obj) {
538
+ return findFirstInObject(obj, (o) => isExperienceBankResult(o));
539
+ }
540
+ function findDefenseResultInObject(obj) {
541
+ return findFirstInObject(obj, (o) => isDefenseEventsResult(o));
542
+ }
543
+ function latestToolResultNode(session, toolName) {
544
+ if (!session || typeof session !== "object") return null;
545
+ const s = (
546
+ /** @type {Record<string, unknown>} */
547
+ session
548
+ );
549
+ const toolCalls = safeGet(s, "toolCalls");
550
+ if (Array.isArray(toolCalls)) {
551
+ const calls = (
552
+ /** @type {Array<Record<string, unknown>>} */
553
+ toolCalls
554
+ );
555
+ for (let i = calls.length - 1; i >= 0; i--) {
556
+ const call = calls[i];
557
+ if (!call) continue;
558
+ const tool = String(safeGet(call, "tool") ?? "");
559
+ if (tool !== toolName && !tool.endsWith(toolName)) continue;
560
+ const result = safeGet(call, "result");
561
+ if (result !== void 0 && result !== null) return result;
562
+ const message = safeGet(call, "message");
563
+ if (message !== void 0 && message !== null) return message;
564
+ }
565
+ }
566
+ const messages = safeGet(s, "messages");
567
+ if (Array.isArray(messages)) {
568
+ const msgs = (
569
+ /** @type {Array<Record<string, unknown>>} */
570
+ messages
571
+ );
572
+ for (let i = msgs.length - 1; i >= 0; i--) {
573
+ const msg = msgs[i];
574
+ if (!msg) continue;
575
+ const content = safeGet(msg, "content");
576
+ if (content !== void 0 && content !== null) return content;
577
+ }
578
+ }
579
+ return null;
580
+ }
581
+ function normalizeQualityGateSnapshot(raw) {
582
+ const src = raw && typeof raw === "object" ? (
583
+ /** @type {Record<string, unknown>} */
584
+ raw
585
+ ) : {};
586
+ const asNum = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
587
+ const asStr = (v) => typeof v === "string" ? v : "";
588
+ const status = safeGet(src, "overallStatus");
589
+ const dims = Array.isArray(safeGet(src, "dimensions")) ? safeGet(src, "dimensions") : [];
590
+ return {
591
+ timestamp: asStr(safeGet(src, "timestamp")),
592
+ overallStatus: status === "pass" || status === "fail" || status === "pending" ? status : "pending",
593
+ overallScore: asNum(safeGet(src, "overallScore")),
594
+ verificationPassRate: asNum(safeGet(src, "verificationPassRate")),
595
+ totalChecks: asNum(safeGet(src, "totalChecks")),
596
+ passedChecks: asNum(safeGet(src, "passedChecks")),
597
+ failedChecks: asNum(safeGet(src, "failedChecks")),
598
+ failReason: asStr(safeGet(src, "failReason")) || null,
599
+ totalFindings: asNum(safeGet(src, "totalFindings")),
600
+ criticalCount: asNum(safeGet(src, "criticalCount")),
601
+ highCount: asNum(safeGet(src, "highCount")),
602
+ mediumCount: asNum(safeGet(src, "mediumCount")),
603
+ lowCount: asNum(safeGet(src, "lowCount")),
604
+ dimensions: (
605
+ /** @type {unknown[]} */
606
+ dims.map((d) => {
607
+ const rec = (
608
+ /** @type {Record<string, unknown>} */
609
+ d && typeof d === "object" ? d : {}
610
+ );
611
+ const dimStatus = safeGet(rec, "status");
612
+ return {
613
+ dimension: asStr(safeGet(rec, "dimension")),
614
+ convergenceRate: asNum(safeGet(rec, "convergenceRate")),
615
+ findingsCount: asNum(safeGet(rec, "findingsCount")),
616
+ fixedCount: asNum(safeGet(rec, "fixedCount")),
617
+ score: asNum(safeGet(rec, "score")),
618
+ status: dimStatus === "pass" || dimStatus === "warn" || dimStatus === "fail" ? dimStatus : "warn"
619
+ };
620
+ })
621
+ )
622
+ };
623
+ }
624
+ function scanSessionForQualityGate(session) {
625
+ const node = latestToolResultNode(session, "iterate_quality_gate");
626
+ if (node === null) return null;
627
+ const found = findQualityGateInObject(node);
628
+ if (!found) return null;
629
+ return normalizeQualityGateSnapshot(found);
630
+ }
631
+ function normalizeExperienceBankResult(raw) {
632
+ const src = raw && typeof raw === "object" ? (
633
+ /** @type {Record<string, unknown>} */
634
+ raw
635
+ ) : {};
636
+ const asNum = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
637
+ const asStr = (v) => typeof v === "string" ? v : "";
638
+ const asArr = (v) => Array.isArray(v) ? (
639
+ /** @type {unknown[]} */
640
+ v
641
+ ) : [];
642
+ const entry = safeGet(src, "entry");
643
+ const rawEntries = Array.isArray(safeGet(src, "entries")) ? (
644
+ /** @type {unknown[]} */
645
+ safeGet(src, "entries")
646
+ ) : entry && typeof entry === "object" ? [entry] : [];
647
+ return {
648
+ operation: asStr(safeGet(src, "operation")),
649
+ count: asNum(safeGet(src, "count")),
650
+ totalHits: asNum(safeGet(src, "totalHits")),
651
+ added: safeGet(src, "added") === true,
652
+ entries: rawEntries.map((e) => {
653
+ const rec = (
654
+ /** @type {Record<string, unknown>} */
655
+ e && typeof e === "object" ? e : {}
656
+ );
657
+ return {
658
+ id: asStr(safeGet(rec, "id")),
659
+ timestamp: asStr(safeGet(rec, "timestamp")),
660
+ dimension: asStr(safeGet(rec, "dimension")),
661
+ pattern: asStr(safeGet(rec, "pattern")),
662
+ description: asStr(safeGet(rec, "description")),
663
+ verifiedFix: asStr(safeGet(rec, "verifiedFix")),
664
+ findingSummary: asStr(safeGet(rec, "findingSummary")),
665
+ severity: asStr(safeGet(rec, "severity")),
666
+ hitCount: asNum(safeGet(rec, "hitCount")),
667
+ lastHitAt: asStr(safeGet(rec, "lastHitAt")) || null,
668
+ files: asArr(safeGet(rec, "files")).map((f) => typeof f === "string" ? f : ""),
669
+ tags: asArr(safeGet(rec, "tags")).map((t) => typeof t === "string" ? t : "")
670
+ };
671
+ })
672
+ };
673
+ }
674
+ function scanSessionForExperienceBank(session) {
675
+ const node = latestToolResultNode(session, "iterate_experience");
676
+ if (node === null) return null;
677
+ const found = findExperienceResultInObject(node);
678
+ if (!found) return null;
679
+ return normalizeExperienceBankResult(found);
680
+ }
681
+ function normalizeDefenseEventsResult(raw) {
682
+ const src = raw && typeof raw === "object" ? (
683
+ /** @type {Record<string, unknown>} */
684
+ raw
685
+ ) : {};
686
+ const asNum = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
687
+ const asStr = (v) => typeof v === "string" ? v : "";
688
+ const countsRaw = safeGet(src, "counts") && typeof safeGet(src, "counts") === "object" ? (
689
+ /** @type {Record<string, unknown>} */
690
+ safeGet(src, "counts")
691
+ ) : {};
692
+ const counts = (
693
+ /** @type {Record<string, number>} */
694
+ {}
695
+ );
696
+ for (const type of DEFENSE_EVENT_TYPES) {
697
+ const n = safeGet(countsRaw, type);
698
+ counts[type] = typeof n === "number" && Number.isFinite(n) && n >= 0 ? n : 0;
699
+ }
700
+ const event = safeGet(src, "event");
701
+ const rawEvents = Array.isArray(safeGet(src, "events")) ? (
702
+ /** @type {unknown[]} */
703
+ safeGet(src, "events")
704
+ ) : event && typeof event === "object" ? [event] : [];
705
+ return {
706
+ operation: asStr(safeGet(src, "operation")),
707
+ count: asNum(safeGet(src, "count")),
708
+ language: asStr(safeGet(src, "language")),
709
+ counts,
710
+ events: rawEvents.map((e) => {
711
+ const rec = (
712
+ /** @type {Record<string, unknown>} */
713
+ e && typeof e === "object" ? e : {}
714
+ );
715
+ return {
716
+ id: asStr(safeGet(rec, "id")),
717
+ timestamp: asStr(safeGet(rec, "timestamp")),
718
+ round: asNum(safeGet(rec, "round")),
719
+ type: asStr(safeGet(rec, "type")),
720
+ description: asStr(safeGet(rec, "description")),
721
+ defense: asStr(safeGet(rec, "defense")),
722
+ outcome: asStr(safeGet(rec, "outcome")),
723
+ file: asStr(safeGet(rec, "file")) || null,
724
+ line: asNum(safeGet(rec, "line")) || null,
725
+ severity: asStr(safeGet(rec, "severity"))
726
+ };
727
+ })
728
+ };
729
+ }
730
+ function scanSessionForDefenseEvents(session) {
731
+ const node = latestToolResultNode(session, "iterate_defense_events");
732
+ if (node === null) return null;
733
+ const found = findDefenseResultInObject(node);
734
+ if (!found) return null;
735
+ return normalizeDefenseEventsResult(found);
736
+ }
474
737
  function isRunSummary(obj) {
475
738
  if (!obj || typeof obj !== "object") return false;
476
739
  const o = (
@@ -1208,6 +1471,43 @@ var ITERATE_CSS = `
1208
1471
  .iterate-chip-phase { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px; font-size: 11px; font-weight: 600; white-space: nowrap; color: var(--dsw-alias-label-primary); background: color-mix(in srgb, var(--dsw-alias-label-secondary) 14%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-label-secondary) 26%, transparent); }
1209
1472
  .iterate-chip-phase[data-live] { 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); }
1210
1473
 
1474
+ /* v3.0: task_mode indicator chip */
1475
+ .iterate-chip-taskmode { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px; font-size: 11px; font-weight: 600; white-space: nowrap; }
1476
+ .iterate-chip-taskmode[data-mode="code"] { color: var(--dsw-alias-brand-primary); background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 28%, transparent); }
1477
+ .iterate-chip-taskmode[data-mode="iterate"] { color: #B45309; background: color-mix(in srgb, #B45309 12%, transparent); border: 1px solid color-mix(in srgb, #B45309 28%, transparent); }
1478
+
1479
+ /* v3.0: Quality gate status */
1480
+ .iterate-gate { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px; font-size: 11px; font-weight: 600; white-space: nowrap; }
1481
+ .iterate-gate[data-status="pass"] { color: var(--dsw-alias-state-success-primary); background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 28%, transparent); }
1482
+ .iterate-gate[data-status="fail"] { color: var(--dsw-alias-state-error-primary); background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 28%, transparent); }
1483
+ .iterate-gate[data-status="pending"] { color: var(--dsw-alias-label-secondary); background: color-mix(in srgb, var(--dsw-alias-label-secondary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-label-secondary) 28%, transparent); }
1484
+
1485
+ /* v3.0: Experience bank hit highlight */
1486
+ .iterate-exp-hit { display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px; border-radius: 6px; background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 28%, transparent); font-size: 11px; color: var(--dsw-alias-state-success-primary); }
1487
+ .iterate-exp-hit b { font-weight: 600; }
1488
+
1489
+ /* v3.0: Defense event type badges */
1490
+ .iterate-defense-badge { display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px; border-radius: 6px; font-size: 11px; font-weight: 600; white-space: nowrap; }
1491
+ .iterate-defense-badge[data-type="precondition_failed"] { color: var(--dsw-alias-state-error-primary); background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 28%, transparent); }
1492
+ .iterate-defense-badge[data-type="rollback"] { color: var(--dsw-alias-state-warn-primary); background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 28%, transparent); }
1493
+ .iterate-defense-badge[data-type="invariant_violated"] { color: var(--dsw-alias-state-error-primary); background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 28%, transparent); }
1494
+ .iterate-defense-badge[data-type="assumption_falsified"] { color: var(--dsw-alias-brand-primary); background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 28%, transparent); }
1495
+
1496
+ /* v3.0: Command buttons */
1497
+ .iterate-cmd { padding: 4px 10px; border-radius: 7px; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font-size: 11px; cursor: pointer; font-weight: 500; }
1498
+ .iterate-cmd:hover { background: var(--dsw-alias-bg-layer-2); }
1499
+ .iterate-cmd[data-primary] { border-color: var(--dsw-alias-brand-primary); color: var(--dsw-alias-brand-primary); }
1500
+ .iterate-cmd[data-danger] { border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 45%, transparent); color: var(--dsw-alias-state-error-primary); }
1501
+ .iterate-cmd:disabled { opacity: 0.5; cursor: not-allowed; }
1502
+
1503
+ /* v3.0: Dimension score bar */
1504
+ .iterate-dim-score { display: flex; align-items: center; gap: 6px; font-size: 11px; }
1505
+ .iterate-dim-score-bar { width: 60px; height: 6px; border-radius: 3px; background: var(--dsw-alias-bg-layer-2); overflow: hidden; }
1506
+ .iterate-dim-score-fill { height: 100%; border-radius: 3px; transition: width 300ms ease; }
1507
+ .iterate-dim-score-fill[data-status="pass"] { background: var(--dsw-alias-state-success-primary); }
1508
+ .iterate-dim-score-fill[data-status="warn"] { background: var(--dsw-alias-state-warn-primary); }
1509
+ .iterate-dim-score-fill[data-status="fail"] { background: var(--dsw-alias-state-error-primary); }
1510
+
1211
1511
  /* Accessibility-switch toggle */
1212
1512
  .iterate-switch { position: relative; width: 42px; height: 24px; border-radius: 999px; padding: 0; cursor: pointer; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); transition: background-color 160ms ease, border-color 160ms ease; }
1213
1513
  .iterate-switch:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
@@ -1493,6 +1793,14 @@ function ConvergenceDashboard(props) {
1493
1793
  "data-live": transcript.active === true ? "" : void 0,
1494
1794
  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`
1495
1795
  }, `${phase} \xB7 ${transcript.active === true ? "\u8FD0\u884C\u4E2D" : "\u5DF2\u7ED3\u675F"}`) : null;
1796
+ const taskModeTranscript = latestTranscript(session);
1797
+ const taskMode = taskModeTranscript?.taskMode;
1798
+ const taskModeChip = taskMode ? React.createElement("span", {
1799
+ className: "iterate-chip-taskmode",
1800
+ key: "taskmode",
1801
+ "data-mode": taskMode,
1802
+ 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"
1803
+ }, taskMode === "code" ? "\u25CF code" : "\u25CF iterate") : null;
1496
1804
  const dimNames = Object.keys(dims);
1497
1805
  const dimBadges = dimNames.slice(0, 6).map(
1498
1806
  (dim) => React.createElement(
@@ -1557,6 +1865,7 @@ function ConvergenceDashboard(props) {
1557
1865
  resumeChip,
1558
1866
  imageChip,
1559
1867
  liveChip,
1868
+ taskModeChip,
1560
1869
  React.createElement(TrendChart, { points: trend.points }),
1561
1870
  ...dimBadges
1562
1871
  );
@@ -1677,6 +1986,7 @@ function TriagePanel(props) {
1677
1986
  const [filter, setFilter] = React.useState({ severities: [], dimensions: [], search: "" });
1678
1987
  const [selected, setSelected] = React.useState(null);
1679
1988
  const [selectAll, setSelectAll] = React.useState(false);
1989
+ const [cmdCopied, setCmdCopied] = React.useState(null);
1680
1990
  React.useEffect(() => {
1681
1991
  if (storage) storage.set(storageKey, JSON.stringify(verdicts));
1682
1992
  }, [storageKey, verdicts]);
@@ -1740,6 +2050,57 @@ function TriagePanel(props) {
1740
2050
  const ignored = collectIgnoredEntries(verdicts, findings);
1741
2051
  const ignoredCount = ignored.length;
1742
2052
  const counts = countVerdicts(verdicts);
2053
+ const doApproveArchitecturalFix = () => {
2054
+ const cmd = "\u8BF7\u8C03\u7528 `iterate_fix` \u6279\u51C6\u67B6\u6784\u4FEE\u590D\uFF08\u8BBE\u7F6E is_architectural: true\uFF09";
2055
+ copyText(cmd).then((ok) => {
2056
+ if (ok) {
2057
+ setCmdCopied("approve-arch");
2058
+ setTimeout(() => setCmdCopied(null), 1600);
2059
+ }
2060
+ });
2061
+ };
2062
+ const doTriggerNewRound = () => {
2063
+ const cmd = "\u8BF7\u7EE7\u7EED\u6267\u884C\u4E0B\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5";
2064
+ copyText(cmd).then((ok) => {
2065
+ if (ok) {
2066
+ setCmdCopied("new-round");
2067
+ setTimeout(() => setCmdCopied(null), 1600);
2068
+ }
2069
+ });
2070
+ };
2071
+ const doRollbackToCheckpoint = () => {
2072
+ const cmd = "\u8BF7\u8C03\u7528 `iterate_checkpoint` \u56DE\u6EDA\u5230\u4E0A\u4E00\u4E2A\u68C0\u67E5\u70B9";
2073
+ copyText(cmd).then((ok) => {
2074
+ if (ok) {
2075
+ setCmdCopied("rollback");
2076
+ setTimeout(() => setCmdCopied(null), 1600);
2077
+ }
2078
+ });
2079
+ };
2080
+ const doAssignFindings = () => {
2081
+ const targetAll = selectAll;
2082
+ const scopeIndices = targetAll ? allIndices : indices;
2083
+ const scopeFindings = findings.filter((f, i) => scopeIndices.includes(i)).map((f) => ({
2084
+ file: String(f.file || ""),
2085
+ ...typeof f.line === "number" && f.line > 0 ? { line: f.line } : {},
2086
+ dimension: String(f.dimension || ""),
2087
+ severity: String(f.severity || ""),
2088
+ summary: String(f.summary || ""),
2089
+ ...f.suggested_fix ? { suggested_fix: String(f.suggested_fix) } : {}
2090
+ }));
2091
+ if (scopeFindings.length === 0) return;
2092
+ const cmd = `\u8BF7\u8C03\u7528 \`iterate_fix\` \u6307\u6D3E\u5E76\u4FEE\u590D\u4EE5\u4E0B findings\uFF1A
2093
+
2094
+ \`\`\`json
2095
+ ${JSON.stringify(scopeFindings, null, 2)}
2096
+ \`\`\``;
2097
+ copyText(cmd).then((ok) => {
2098
+ if (ok) {
2099
+ setCmdCopied("assign");
2100
+ setTimeout(() => setCmdCopied(null), 1600);
2101
+ }
2102
+ });
2103
+ };
1743
2104
  const doCopyYaml = () => {
1744
2105
  const yaml = toKnownIntentionalYaml(ignored);
1745
2106
  if (!yaml) return;
@@ -1895,6 +2256,44 @@ function TriagePanel(props) {
1895
2256
  }, "\u751F\u6210\u5E94\u7528\u6307\u4EE4")
1896
2257
  )
1897
2258
  ),
2259
+ // v3.0: Command buttons for native actions (§8)
2260
+ React.createElement(
2261
+ "div",
2262
+ { className: "iterate-triage-foot", style: { borderTop: "1px solid var(--dsw-alias-border-l1)" } },
2263
+ React.createElement("span", { style: { fontSize: 11, fontWeight: 600, color: "var(--dsw-alias-label-secondary)" } }, "\u6307\u6325\u64CD\u4F5C"),
2264
+ React.createElement(
2265
+ "span",
2266
+ { style: { display: "flex", gap: 6 } },
2267
+ React.createElement("button", {
2268
+ className: "iterate-cmd",
2269
+ "data-primary": "",
2270
+ "data-copied": cmdCopied === "approve-arch" ? "" : void 0,
2271
+ onClick: doApproveArchitecturalFix,
2272
+ title: "\u6279\u51C6\u67B6\u6784\u4FEE\u590D\uFF08\u590D\u5236\u6307\u4EE4\u6587\u672C\uFF09"
2273
+ }, cmdCopied === "approve-arch" ? "\u5DF2\u590D\u5236" : "\u6279\u51C6\u67B6\u6784\u4FEE\u590D"),
2274
+ React.createElement("button", {
2275
+ className: "iterate-cmd",
2276
+ "data-copied": cmdCopied === "new-round" ? "" : void 0,
2277
+ onClick: doTriggerNewRound,
2278
+ title: "\u89E6\u53D1\u65B0\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5"
2279
+ }, cmdCopied === "new-round" ? "\u5DF2\u590D\u5236" : "\u89E6\u53D1\u65B0\u4E00\u8F6E"),
2280
+ React.createElement("button", {
2281
+ className: "iterate-cmd",
2282
+ "data-primary": "",
2283
+ "data-copied": cmdCopied === "assign" ? "" : void 0,
2284
+ onClick: doAssignFindings,
2285
+ disabled: (selectAll ? allIndices : indices).length === 0,
2286
+ title: selectAll ? `\u6307\u6D3E\u5168\u90E8 ${allIndices.length} \u4E2A findings \u4FEE\u590D` : `\u6307\u6D3E\u5F53\u524D\u53EF\u89C1 ${indices.length} \u4E2A findings \u4FEE\u590D`
2287
+ }, cmdCopied === "assign" ? "\u5DF2\u590D\u5236" : "\u6307\u6D3E\u4FEE\u590D"),
2288
+ React.createElement("button", {
2289
+ className: "iterate-cmd",
2290
+ "data-danger": "",
2291
+ "data-copied": cmdCopied === "rollback" ? "" : void 0,
2292
+ onClick: doRollbackToCheckpoint,
2293
+ title: "\u56DE\u6EDA\u5230\u4E0A\u4E00\u4E2A\u68C0\u67E5\u70B9"
2294
+ }, cmdCopied === "rollback" ? "\u5DF2\u590D\u5236" : "\u56DE\u6EDA\u68C0\u67E5\u70B9")
2295
+ )
2296
+ ),
1898
2297
  payload ? React.createElement("div", { className: "iterate-payload" }, payload) : null
1899
2298
  );
1900
2299
  }
@@ -1978,7 +2377,13 @@ function ProgressCapsule() {
1978
2377
  };
1979
2378
  }, []);
1980
2379
  if (!info) return null;
1981
- return React.createElement("div", { "data-iterate-root": "", className: "iterate-capsule", "data-ok": info.ok ? "" : void 0 }, info.text);
2380
+ return React.createElement("div", {
2381
+ "data-iterate-root": "",
2382
+ className: "iterate-capsule",
2383
+ "data-ok": info.ok ? "" : void 0,
2384
+ role: "status",
2385
+ "aria-live": "polite"
2386
+ }, info.text);
1982
2387
  }
1983
2388
  function SettingsPanel(_props) {
1984
2389
  const [enabled, setEnabled] = React.useState(themeEnabled);
@@ -2171,7 +2576,11 @@ var OBS_TABS = [
2171
2576
  { key: "f4", label: "\u4FEE\u590D\u4E0E\u56DE\u6EDA" },
2172
2577
  { key: "f5", label: "\u65AD\u70B9\u6062\u590D" },
2173
2578
  { key: "f6", label: "\u8FD0\u884C\u63A7\u5236\u53F0" },
2174
- { key: "f7", label: "\u51B3\u7B56\u65F6\u95F4\u7EBF" }
2579
+ { key: "f7", label: "\u51B3\u7B56\u65F6\u95F4\u7EBF" },
2580
+ // v3.0: Quality Command Center tabs
2581
+ { key: "f8", label: "\u8D28\u91CF\u95E8\u7981" },
2582
+ { key: "f9", label: "\u7ECF\u9A8C\u94F6\u884C" },
2583
+ { key: "f10", label: "\u9632\u5FA1\u4E8B\u4EF6" }
2175
2584
  ];
2176
2585
  var OBS_LIVE_META = {
2177
2586
  read: { label: "\u9605\u8BFB", color: "var(--dsw-alias-brand-primary)" },
@@ -2209,6 +2618,18 @@ function latestTranscript(session) {
2209
2618
  const raw = scanSessionForTranscript(session);
2210
2619
  return raw ? normalizeTranscript(raw) : null;
2211
2620
  }
2621
+ function latestQualityGate(session) {
2622
+ if (!session) return null;
2623
+ return scanSessionForQualityGate(session);
2624
+ }
2625
+ function latestExperienceBank(session) {
2626
+ if (!session) return null;
2627
+ return scanSessionForExperienceBank(session);
2628
+ }
2629
+ function latestDefenseEvents(session) {
2630
+ if (!session) return null;
2631
+ return scanSessionForDefenseEvents(session);
2632
+ }
2212
2633
  function buildObsFixInstruction(f) {
2213
2634
  const payload = JSON.stringify({
2214
2635
  file: String(f.file || ""),
@@ -2226,6 +2647,9 @@ ${payload}
2226
2647
  function ObservatoryPanel(props) {
2227
2648
  const session = props && props.session ? props.session : null;
2228
2649
  const manifest = latestTranscript(session);
2650
+ const qualityGate = latestQualityGate(session);
2651
+ const experienceBank = latestExperienceBank(session);
2652
+ const defenseEvents = latestDefenseEvents(session);
2229
2653
  const [open, setOpen] = React.useState(false);
2230
2654
  const [tab, setTab] = React.useState("live");
2231
2655
  const [expandedThreads, setExpandedThreads] = React.useState(/* @__PURE__ */ new Set());
@@ -2240,6 +2664,8 @@ function ObservatoryPanel(props) {
2240
2664
  const [f3Filter, setF3Filter] = React.useState(
2241
2665
  { severities: [], dimensions: [], search: "" }
2242
2666
  );
2667
+ const [expSearch, setExpSearch] = React.useState("");
2668
+ const [defenseFilter, setDefenseFilter] = React.useState("");
2243
2669
  const copyTimer = React.useRef(null);
2244
2670
  React.useEffect(() => () => {
2245
2671
  if (copyTimer.current) clearTimeout(copyTimer.current);
@@ -2266,6 +2692,7 @@ function ObservatoryPanel(props) {
2266
2692
  });
2267
2693
  };
2268
2694
  const exportObservatory = () => {
2695
+ if (!manifest) return;
2269
2696
  const json = serializeObservatoryExport(manifest, manifest.live || []);
2270
2697
  const filename = `iterate-observatory-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`;
2271
2698
  const flash = () => {
@@ -2483,38 +2910,39 @@ function ObservatoryPanel(props) {
2483
2910
  );
2484
2911
  const findingBlocks = filtered.map((f, i) => {
2485
2912
  const k = `f3-${i}`;
2486
- const loc = `${String(f.file || "?")}${typeof f.line === "number" && f.line > 0 ? `:${f.line}` : ""}`;
2913
+ const finding = f;
2914
+ const loc = `${String(finding.file || "?")}${typeof finding.line === "number" && finding.line > 0 ? `:${finding.line}` : ""}`;
2487
2915
  const entry = {
2488
- file: String(f.file || ""),
2489
- ...typeof f.line === "number" && f.line > 0 ? { line: f.line } : {},
2490
- dimension: String(f.dimension || ""),
2491
- reason: String(f.summary || "")
2916
+ file: String(finding.file || ""),
2917
+ ...typeof finding.line === "number" && finding.line > 0 ? { line: finding.line } : {},
2918
+ dimension: String(finding.dimension || ""),
2919
+ reason: String(finding.summary || "")
2492
2920
  };
2493
2921
  const knownInstruction = buildApplyInstruction([entry]);
2494
- const fixInstruction = buildObsFixInstruction(f);
2922
+ const fixInstruction = buildObsFixInstruction(finding);
2495
2923
  return React.createElement(
2496
2924
  "div",
2497
2925
  { key: k, className: "iterate-obs-block" },
2498
2926
  React.createElement(
2499
2927
  "div",
2500
2928
  { className: "iterate-obs-block-head" },
2501
- React.createElement("span", { className: "iterate-sev-dot", style: { background: severityColor(f.severity) } }),
2502
- React.createElement("span", {}, severityLabel(f.severity)),
2929
+ React.createElement("span", { className: "iterate-sev-dot", style: { background: severityColor(finding.severity) } }),
2930
+ React.createElement("span", {}, severityLabel(finding.severity)),
2503
2931
  React.createElement("span", { className: "iterate-obs-file" }, loc),
2504
- React.createElement("span", {}, String(f.dimension || "")),
2505
- f.acknowledged === true ? React.createElement("span", { className: "iterate-obs-chip" }, "\u5DF2\u786E\u8BA4") : null
2932
+ React.createElement("span", {}, String(finding.dimension || "")),
2933
+ finding.acknowledged === true ? React.createElement("span", { className: "iterate-obs-chip" }, "\u5DF2\u786E\u8BA4") : null
2506
2934
  ),
2507
2935
  React.createElement(
2508
2936
  "div",
2509
2937
  { className: "iterate-obs-block-body" },
2510
- React.createElement("div", { className: "iterate-obs-msg" }, String(f.summary || "")),
2938
+ React.createElement("div", { className: "iterate-obs-msg" }, String(finding.summary || "")),
2511
2939
  React.createElement(
2512
2940
  "div",
2513
2941
  { className: "iterate-obs-bar", style: { marginTop: 8 } },
2514
2942
  React.createElement("button", {
2515
2943
  className: "iterate-btn",
2516
2944
  "data-copied": copiedKey === `${k}-fix` ? "" : void 0,
2517
- onClick: () => copyInstruction(`${k}-fix`, String(f.suggested_fix || "")),
2945
+ onClick: () => copyInstruction(`${k}-fix`, String(finding.suggested_fix || "")),
2518
2946
  title: "\u590D\u5236\u8BE5 finding \u7684\u5EFA\u8BAE\u4FEE\u590D\u6587\u672C"
2519
2947
  }, copiedKey === `${k}-fix` ? "\u5DF2\u590D\u5236" : "\u590D\u5236 suggested_fix"),
2520
2948
  React.createElement("button", {
@@ -2793,6 +3221,335 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
2793
3221
  }
2794
3222
  return React.createElement("div", {}, filterBar, ...rows);
2795
3223
  };
3224
+ const renderQualityGate = () => {
3225
+ const gateInstruction = "\u8BF7\u8C03\u7528 `iterate_quality_gate` \u67E5\u8BE2\u5F53\u524D\u8D28\u91CF\u95E8\u7981\u72B6\u6001";
3226
+ const gate = qualityGate;
3227
+ const dims = gate && gate.dimensions ? gate.dimensions : [];
3228
+ const hasGate = gate !== null && Boolean(gate.overallStatus || gate.overallScore != null || dims.length > 0);
3229
+ const status = gate && gate.overallStatus ? String(gate.overallStatus) : "pending";
3230
+ const statusLabel = status === "pass" ? "PASS" : status === "fail" ? "FAIL" : "PENDING";
3231
+ const num = (v) => Number(v != null ? v : 0);
3232
+ const headerBar = React.createElement(
3233
+ "div",
3234
+ { className: "iterate-obs-bar", style: { marginBottom: 8 } },
3235
+ React.createElement("b", {}, "\u8D28\u91CF\u95E8\u7981"),
3236
+ React.createElement("button", {
3237
+ className: "iterate-btn",
3238
+ "data-primary": "",
3239
+ "data-copied": copiedKey === "qgate" ? "" : void 0,
3240
+ onClick: () => copyInstruction("qgate", gateInstruction),
3241
+ title: "\u590D\u5236 iterate_quality_gate \u67E5\u8BE2\u6307\u4EE4"
3242
+ }, copiedKey === "qgate" ? "\u5DF2\u590D\u5236" : "\u67E5\u8BE2\u95E8\u7981")
3243
+ );
3244
+ if (!hasGate) {
3245
+ return React.createElement(
3246
+ "div",
3247
+ {},
3248
+ headerBar,
3249
+ React.createElement(
3250
+ "div",
3251
+ { className: "iterate-obs-block" },
3252
+ React.createElement(
3253
+ "div",
3254
+ { className: "iterate-obs-block-head" },
3255
+ React.createElement("span", {}, "\u8D28\u91CF\u95E8\u7981\u89C6\u56FE"),
3256
+ React.createElement("span", { className: "iterate-obs-badge" }, "v3.0")
3257
+ ),
3258
+ React.createElement(
3259
+ "div",
3260
+ { className: "iterate-obs-block-body" },
3261
+ 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"),
3262
+ React.createElement(
3263
+ "div",
3264
+ { className: "iterate-obs-bar", style: { marginTop: 8 } },
3265
+ React.createElement("button", {
3266
+ className: "iterate-btn",
3267
+ "data-copied": copiedKey === "qgate-instr" ? "" : void 0,
3268
+ onClick: () => copyInstruction("qgate-instr", gateInstruction),
3269
+ title: "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4"
3270
+ }, copiedKey === "qgate-instr" ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4")
3271
+ )
3272
+ )
3273
+ )
3274
+ );
3275
+ }
3276
+ return React.createElement(
3277
+ "div",
3278
+ {},
3279
+ headerBar,
3280
+ React.createElement(
3281
+ "div",
3282
+ { className: "iterate-obs-block" },
3283
+ React.createElement(
3284
+ "div",
3285
+ { className: "iterate-obs-block-head" },
3286
+ React.createElement("span", { className: "iterate-gate", "data-status": status }, statusLabel),
3287
+ React.createElement(
3288
+ "span",
3289
+ { className: "iterate-obs-head-meta" },
3290
+ `\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)}%`
3291
+ )
3292
+ ),
3293
+ React.createElement(
3294
+ "div",
3295
+ { className: "iterate-obs-block-body" },
3296
+ dims.length > 0 ? React.createElement(
3297
+ "div",
3298
+ { className: "iterate-obs-bar", style: { flexWrap: "wrap", gap: 6 } },
3299
+ ...dims.map(
3300
+ (d, i) => React.createElement(
3301
+ "span",
3302
+ { key: `gdim-${i}`, className: "iterate-obs-chip" },
3303
+ `${String(d.dimension || "?")} \xB7 \u6536\u655B ${num(d.convergenceRate)}% \xB7 \u8BC4\u5206 ${num(d.score)}`
3304
+ )
3305
+ )
3306
+ ) : null,
3307
+ React.createElement(
3308
+ "div",
3309
+ { className: "iterate-obs-bar", style: { flexWrap: "wrap" } },
3310
+ ...dims.map((d, i) => {
3311
+ const fill = Math.min(100, Math.max(0, num(d.score)));
3312
+ return React.createElement(
3313
+ "div",
3314
+ { key: `gbar-${i}`, className: "iterate-dim-score" },
3315
+ React.createElement("span", {}, String(d.dimension || "?")),
3316
+ React.createElement(
3317
+ "div",
3318
+ { className: "iterate-dim-score-bar" },
3319
+ React.createElement("div", {
3320
+ className: "iterate-dim-score-fill",
3321
+ "data-status": String(d.status || "warn"),
3322
+ style: { width: `${fill}%` }
3323
+ })
3324
+ ),
3325
+ React.createElement("span", {}, `\u8BC4\u5206 ${num(d.score)} \xB7 \u53D1\u73B0 ${num(d.findingsCount)} \xB7 \u5DF2\u4FEE\u590D ${num(d.fixedCount)}`)
3326
+ );
3327
+ })
3328
+ ),
3329
+ gate && gate.failReason ? React.createElement("div", { className: "iterate-obs-msg", style: { marginTop: 6 } }, `\u5931\u8D25\u539F\u56E0\uFF1A${String(gate.failReason)}`) : null
3330
+ )
3331
+ )
3332
+ );
3333
+ };
3334
+ const renderExperienceBank = () => {
3335
+ const expInstruction = expSearch ? `\u8BF7\u8C03\u7528 \`iterate_experience\` \u641C\u7D22\u7ECF\u9A8C\uFF1A
3336
+
3337
+ \`\`\`json
3338
+ ${JSON.stringify({ operation: "search", query: expSearch }, null, 2)}
3339
+ \`\`\`` : "\u8BF7\u8C03\u7528 `iterate_experience` \u5217\u51FA\u6240\u6709\u7ECF\u9A8C";
3340
+ const expEntries = experienceBank && experienceBank.entries ? experienceBank.entries : [];
3341
+ const query = expSearch.trim().toLowerCase();
3342
+ const visible = query ? expEntries.filter(
3343
+ (e) => [e.pattern, e.description, e.id, e.dimension, e.severity, ...e.tags || []].filter(Boolean).some((t) => String(t).toLowerCase().includes(query))
3344
+ ) : expEntries;
3345
+ const resultRows = visible.map((e, i) => {
3346
+ const adoptInstruction = `\u8BF7\u8C03\u7528 \`iterate_experience\` \u91C7\u7EB3\u7ECF\u9A8C\u5E76\u5E94\u7528\u5DF2\u9A8C\u8BC1\u4FEE\u6CD5\uFF1A
3347
+
3348
+ \`\`\`json
3349
+ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
3350
+ \`\`\``;
3351
+ return React.createElement(
3352
+ "div",
3353
+ { key: `exp-${i}`, className: "iterate-obs-block" },
3354
+ React.createElement(
3355
+ "div",
3356
+ { className: "iterate-obs-block-head" },
3357
+ React.createElement("span", { className: "iterate-obs-chip" }, String(e.dimension || "")),
3358
+ React.createElement("span", {}, String(e.pattern || "?")),
3359
+ React.createElement("span", { className: "iterate-exp-hit" }, `\u547D\u4E2D ${e.hitCount ?? 0} \u6B21`)
3360
+ ),
3361
+ React.createElement(
3362
+ "div",
3363
+ { className: "iterate-obs-block-body" },
3364
+ React.createElement("div", { className: "iterate-obs-msg" }, String(e.description || "")),
3365
+ React.createElement(
3366
+ "div",
3367
+ { className: "iterate-obs-bar", style: { marginTop: 6 } },
3368
+ e.verifiedFix ? React.createElement("button", {
3369
+ className: "iterate-btn",
3370
+ "data-primary": "",
3371
+ "data-copied": copiedKey === `exp-adopt-${i}` ? "" : void 0,
3372
+ onClick: () => copyInstruction(`exp-adopt-${i}`, adoptInstruction),
3373
+ title: "\u590D\u5236\u91C7\u7EB3\u6307\u4EE4\uFF08\u67E5\u8BE2\u8BE5\u7ECF\u9A8C\u5E76\u5E94\u7528\u5230\u5F53\u524D\u4E0A\u4E0B\u6587\uFF09"
3374
+ }, copiedKey === `exp-adopt-${i}` ? "\u5DF2\u590D\u5236" : "\u91C7\u7EB3") : null
3375
+ )
3376
+ )
3377
+ );
3378
+ });
3379
+ return React.createElement(
3380
+ "div",
3381
+ {},
3382
+ React.createElement(
3383
+ "div",
3384
+ { className: "iterate-obs-bar", style: { marginBottom: 8 } },
3385
+ React.createElement("b", {}, "\u7ECF\u9A8C\u94F6\u884C"),
3386
+ React.createElement("span", { className: "iterate-obs-head-meta" }, `\u4F1A\u8BDD\u5185 ${expEntries.length} \u6761`),
3387
+ React.createElement("button", {
3388
+ className: "iterate-btn",
3389
+ "data-primary": "",
3390
+ "data-copied": copiedKey === "exp-list" ? "" : void 0,
3391
+ onClick: () => copyInstruction("exp-list", "\u8BF7\u8C03\u7528 `iterate_experience` \u5217\u51FA\u6240\u6709\u7ECF\u9A8C"),
3392
+ title: "\u590D\u5236\u5217\u51FA\u7ECF\u9A8C\u6307\u4EE4"
3393
+ }, copiedKey === "exp-list" ? "\u5DF2\u590D\u5236" : "\u5217\u51FA\u7ECF\u9A8C")
3394
+ ),
3395
+ React.createElement(
3396
+ "div",
3397
+ { className: "iterate-obs-bar", style: { marginBottom: 8 } },
3398
+ React.createElement("input", {
3399
+ className: "iterate-filter-search",
3400
+ type: "search",
3401
+ placeholder: "\u641C\u7D22\u7ECF\u9A8C\u6A21\u5F0F\u2026",
3402
+ "aria-label": "\u641C\u7D22\u7ECF\u9A8C",
3403
+ value: expSearch,
3404
+ onChange: (e) => setExpSearch(e.target.value)
3405
+ }),
3406
+ React.createElement("button", {
3407
+ className: "iterate-btn",
3408
+ "data-copied": copiedKey === "exp-search" ? "" : void 0,
3409
+ disabled: !expSearch,
3410
+ onClick: () => copyInstruction("exp-search", expInstruction),
3411
+ title: "\u590D\u5236\u641C\u7D22\u6307\u4EE4"
3412
+ }, copiedKey === "exp-search" ? "\u5DF2\u590D\u5236" : "\u641C\u7D22")
3413
+ ),
3414
+ 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")
3415
+ );
3416
+ };
3417
+ const renderDefenseEvents = () => {
3418
+ const defenseInstruction = defenseFilter ? `\u8BF7\u8C03\u7528 \`iterate_defense_events\` \u67E5\u8BE2\u9632\u5FA1\u4E8B\u4EF6\uFF1A
3419
+
3420
+ \`\`\`json
3421
+ ${JSON.stringify({ operation: "list", type: defenseFilter || void 0 }, null, 2)}
3422
+ \`\`\`` : "\u8BF7\u8C03\u7528 `iterate_defense_events` \u5217\u51FA\u6240\u6709\u9632\u5FA1\u4E8B\u4EF6";
3423
+ const typeOptions = [
3424
+ { value: "", label: "\u5168\u90E8\u7C7B\u578B" },
3425
+ { value: "precondition_failed", label: "\u524D\u7F6E\u6821\u9A8C\u5931\u8D25" },
3426
+ { value: "rollback", label: "\u56DE\u6EDA" },
3427
+ { value: "invariant_violated", label: "\u4E0D\u53D8\u91CF\u8FDD\u53CD" },
3428
+ { value: "assumption_falsified", label: "\u5047\u8BBE\u88AB\u8BC1\u4F2A" }
3429
+ ];
3430
+ const TYPE_LABEL = {
3431
+ precondition_failed: "\u524D\u7F6E\u6821\u9A8C\u5931\u8D25",
3432
+ rollback: "\u56DE\u6EDA",
3433
+ invariant_violated: "\u4E0D\u53D8\u91CF\u8FDD\u53CD",
3434
+ assumption_falsified: "\u5047\u8BBE\u88AB\u8BC1\u4F2A"
3435
+ };
3436
+ const counts = defenseEvents && defenseEvents.counts || {};
3437
+ const allEvents = defenseEvents && defenseEvents.events ? defenseEvents.events : [];
3438
+ const totalCount = typeOptions.slice(1).reduce((acc, o) => acc + (counts[o.value] || 0), 0);
3439
+ const visibleEvents = defenseFilter ? allEvents.filter((e) => e.type === defenseFilter) : allEvents;
3440
+ const headerBar = React.createElement(
3441
+ "div",
3442
+ { className: "iterate-obs-bar", style: { marginBottom: 8 } },
3443
+ React.createElement("b", {}, "\u9632\u5FA1\u4E8B\u4EF6"),
3444
+ React.createElement("span", { className: "iterate-obs-head-meta" }, `\u4F1A\u8BDD\u5185 ${allEvents.length} \u6761 / \u7D2F\u8BA1 ${totalCount}`),
3445
+ React.createElement("button", {
3446
+ className: "iterate-btn",
3447
+ "data-primary": "",
3448
+ "data-copied": copiedKey === "defense-list" ? "" : void 0,
3449
+ onClick: () => copyInstruction("defense-list", "\u8BF7\u8C03\u7528 `iterate_defense_events` \u5217\u51FA\u6240\u6709\u9632\u5FA1\u4E8B\u4EF6"),
3450
+ title: "\u590D\u5236\u5217\u51FA\u9632\u5FA1\u4E8B\u4EF6\u6307\u4EE4"
3451
+ }, copiedKey === "defense-list" ? "\u5DF2\u590D\u5236" : "\u5217\u51FA\u4E8B\u4EF6"),
3452
+ React.createElement("button", {
3453
+ className: "iterate-btn",
3454
+ "data-copied": copiedKey === "defense-counts" ? "" : void 0,
3455
+ onClick: () => copyInstruction("defense-counts", "\u8BF7\u8C03\u7528 `iterate_defense_events` \u67E5\u8BE2\u4E8B\u4EF6\u7EDF\u8BA1"),
3456
+ title: "\u590D\u5236\u7EDF\u8BA1\u6307\u4EE4"
3457
+ }, copiedKey === "defense-counts" ? "\u5DF2\u590D\u5236" : "\u7EDF\u8BA1")
3458
+ );
3459
+ if (allEvents.length === 0 && totalCount === 0) {
3460
+ return React.createElement(
3461
+ "div",
3462
+ {},
3463
+ headerBar,
3464
+ React.createElement(
3465
+ "div",
3466
+ { className: "iterate-obs-block" },
3467
+ React.createElement(
3468
+ "div",
3469
+ { className: "iterate-obs-block-head" },
3470
+ React.createElement("span", {}, "\u9632\u5FA1\u4E8B\u4EF6\u6D41"),
3471
+ React.createElement("span", { className: "iterate-obs-badge" }, "v3.0")
3472
+ ),
3473
+ React.createElement(
3474
+ "div",
3475
+ { className: "iterate-obs-block-body" },
3476
+ 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"),
3477
+ React.createElement(
3478
+ "div",
3479
+ { className: "iterate-obs-bar", style: { marginTop: 8 } },
3480
+ React.createElement("button", {
3481
+ className: "iterate-btn",
3482
+ "data-copied": copiedKey === "defense-instr" ? "" : void 0,
3483
+ onClick: () => copyInstruction("defense-instr", defenseInstruction),
3484
+ title: "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4"
3485
+ }, copiedKey === "defense-instr" ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4")
3486
+ )
3487
+ )
3488
+ )
3489
+ );
3490
+ }
3491
+ const filterBar = React.createElement(
3492
+ "div",
3493
+ { className: "iterate-obs-bar", style: { marginBottom: 8 } },
3494
+ React.createElement(
3495
+ "select",
3496
+ {
3497
+ className: "iterate-filter-select",
3498
+ value: defenseFilter,
3499
+ "aria-label": "\u6309\u4E8B\u4EF6\u7C7B\u578B\u7B5B\u9009",
3500
+ onChange: (e) => setDefenseFilter(e.target.value)
3501
+ },
3502
+ ...typeOptions.map((opt) => React.createElement("option", { key: opt.value, value: opt.value }, opt.label))
3503
+ ),
3504
+ defenseFilter ? React.createElement("button", {
3505
+ className: "iterate-filter-clear",
3506
+ onClick: () => setDefenseFilter(""),
3507
+ title: "\u6E05\u9664\u7C7B\u578B\u7B5B\u9009"
3508
+ }, "\u6E05\u9664") : null
3509
+ );
3510
+ let eventRows = [];
3511
+ if (visibleEvents.length > 0) {
3512
+ eventRows = visibleEvents.map((e, i) => {
3513
+ const type = String(e.type || "unknown");
3514
+ const loc = `${String(e.file || "?")}${typeof e.line === "number" && e.line > 0 ? `:${e.line}` : ""}`;
3515
+ return React.createElement(
3516
+ "div",
3517
+ { key: `def-${i}`, className: "iterate-obs-block" },
3518
+ React.createElement(
3519
+ "div",
3520
+ { className: "iterate-obs-block-head" },
3521
+ React.createElement("span", { className: "iterate-defense-badge", "data-type": type }, TYPE_LABEL[type] || type),
3522
+ typeof e.round === "number" ? React.createElement("span", { className: "iterate-obs-head-meta" }, `Round ${e.round}`) : null,
3523
+ React.createElement("span", { className: "iterate-obs-head-meta" }, loc)
3524
+ ),
3525
+ React.createElement(
3526
+ "div",
3527
+ { className: "iterate-obs-block-body" },
3528
+ React.createElement("div", { className: "iterate-obs-msg" }, String(e.description || "")),
3529
+ e.defense ? React.createElement("div", { className: "iterate-obs-msg", style: { marginTop: 4 } }, `\u9632\u7EBF\uFF1A${String(e.defense)}`) : null
3530
+ )
3531
+ );
3532
+ });
3533
+ }
3534
+ return React.createElement(
3535
+ "div",
3536
+ {},
3537
+ headerBar,
3538
+ filterBar,
3539
+ React.createElement(
3540
+ "div",
3541
+ { className: "iterate-obs-bar", style: { flexWrap: "wrap", gap: 6, marginBottom: 8 } },
3542
+ ...typeOptions.slice(1).map(
3543
+ (opt) => React.createElement(
3544
+ "span",
3545
+ { key: `cnt-${opt.value}`, className: "iterate-defense-badge", "data-type": opt.value },
3546
+ `${opt.label} ${counts[opt.value] || 0}`
3547
+ )
3548
+ )
3549
+ ),
3550
+ 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")
3551
+ );
3552
+ };
2796
3553
  const renderLive = () => {
2797
3554
  const liveEntries = manifest.live || [];
2798
3555
  if (liveEntries.length === 0) {
@@ -2820,7 +3577,7 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
2820
3577
  const meta = OBS_LIVE_META[type] || { label: String(e.type || "?"), color: OBS_LIVE_FALLBACK_COLOR };
2821
3578
  const tool = String(e.tool || "");
2822
3579
  const target = String(e.target || "");
2823
- const time = formatObsLiveTime(e.ts);
3580
+ const time = formatObsLiveTime(String(e.ts || ""));
2824
3581
  return React.createElement(
2825
3582
  "div",
2826
3583
  { key: `live-${i}`, className: "iterate-obs-row" },
@@ -2880,6 +3637,13 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
2880
3637
  return renderConsole();
2881
3638
  case "f7":
2882
3639
  return renderTimeline();
3640
+ // v3.0: Quality Command Center tabs
3641
+ case "f8":
3642
+ return renderQualityGate();
3643
+ case "f9":
3644
+ return renderExperienceBank();
3645
+ case "f10":
3646
+ return renderDefenseEvents();
2883
3647
  default:
2884
3648
  return null;
2885
3649
  }