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/README.md +124 -76
- package/README.zh-CN.md +129 -80
- package/dist/approval-gate.js +16 -19
- package/dist/config-loader.js +5 -0
- package/dist/index.js +16 -6
- package/dist/session-hooks.js +36 -11
- package/dist/skill-prompt.js +3 -0
- package/dist/tools/checkpoint.js +22 -3
- package/dist/tools/decision-log.js +10 -1
- package/dist/tools/defense-events.js +263 -0
- package/dist/tools/defense-store.js +103 -0
- package/dist/tools/experience-bank.js +251 -0
- package/dist/tools/experience-store.js +138 -0
- package/dist/tools/quality-gate.js +183 -0
- package/dist/tools/quality-store.js +180 -0
- package/dist/tools/transcript.js +8 -1
- package/dist/transcript.js +10 -0
- package/lib/client.js +779 -15
- package/lib/parse.js +352 -0
- package/package.json +6 -6
- package/src/approval-gate.ts +14 -28
- package/src/client/index.ts +564 -16
- package/src/config-loader.ts +5 -0
- package/src/index.ts +16 -6
- package/src/session-hooks.ts +33 -11
- package/src/skill-prompt.ts +3 -0
- package/src/tools/checkpoint.ts +22 -3
- package/src/tools/decision-log.ts +10 -1
- package/src/tools/defense-events.ts +298 -0
- package/src/tools/defense-store.ts +122 -0
- package/src/tools/experience-bank.ts +267 -0
- package/src/tools/experience-store.ts +169 -0
- package/src/tools/quality-gate.ts +196 -0
- package/src/tools/quality-store.ts +208 -0
- package/src/tools/transcript.ts +8 -1
- package/src/transcript.ts +11 -0
- package/src/types.ts +118 -0
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", {
|
|
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
|
|
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(
|
|
2489
|
-
...typeof
|
|
2490
|
-
dimension: String(
|
|
2491
|
-
reason: String(
|
|
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(
|
|
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(
|
|
2502
|
-
React.createElement("span", {}, severityLabel(
|
|
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(
|
|
2505
|
-
|
|
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(
|
|
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(
|
|
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
|
}
|