iterate-plugin 3.5.7 → 3.6.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 +64 -20
- package/README.zh-CN.md +53 -17
- package/dist/approval-gate.js +143 -23
- package/dist/atomic-fs.js +148 -17
- package/dist/config-loader.js +509 -34
- package/dist/config-write.js +83 -22
- package/dist/evidence.js +141 -87
- package/dist/file-lock.js +165 -0
- package/dist/index.js +54 -28
- package/dist/jobs.js +56 -11
- package/dist/live.js +52 -13
- package/dist/meta-review.js +132 -28
- package/dist/method-scope.js +31 -8
- package/dist/review-scope.js +32 -9
- package/dist/review.js +96 -17
- package/dist/session-hooks.js +55 -13
- package/dist/skill-prompt.js +396 -66
- package/dist/tools/checkpoint.js +85 -19
- package/dist/tools/config.js +44 -2
- package/dist/tools/context.js +22 -5
- package/dist/tools/decision-log.js +111 -27
- package/dist/tools/defense-events.js +90 -31
- package/dist/tools/defense-store.js +28 -3
- package/dist/tools/experience-bank.js +108 -43
- package/dist/tools/experience-store.js +54 -4
- package/dist/tools/fix.js +347 -119
- package/dist/tools/history.js +68 -14
- package/dist/tools/present.js +80 -0
- package/dist/tools/prune.js +150 -53
- package/dist/tools/quality-gate.js +52 -2
- package/dist/tools/quality-store.js +17 -1
- package/dist/tools/review.js +158 -7
- package/dist/tools/transcript.js +173 -36
- package/dist/tools/triage.js +67 -12
- package/dist/tools/validate.js +115 -24
- package/dist/transcript.js +267 -4
- package/lib/client.js +945 -181
- package/lib/parse.js +760 -57
- package/package.json +14 -12
- package/src/approval-gate.ts +160 -23
- package/src/atomic-fs.ts +152 -17
- package/src/client/index.ts +600 -129
- package/src/config-loader.ts +542 -35
- package/src/config-write.ts +108 -26
- package/src/evidence.ts +194 -92
- package/src/file-lock.ts +184 -0
- package/src/index.ts +53 -29
- package/src/jobs.ts +63 -12
- package/src/live.ts +48 -11
- package/src/meta-review.ts +151 -34
- package/src/method-scope.ts +31 -8
- package/src/review-scope.ts +33 -10
- package/src/review.ts +96 -17
- package/src/session-hooks.ts +54 -12
- package/src/skill-prompt.ts +396 -66
- package/src/tools/checkpoint.ts +81 -19
- package/src/tools/config.ts +44 -2
- package/src/tools/context.ts +25 -5
- package/src/tools/decision-log.ts +115 -22
- package/src/tools/defense-events.ts +87 -31
- package/src/tools/defense-store.ts +30 -3
- package/src/tools/experience-bank.ts +112 -44
- package/src/tools/experience-store.ts +54 -5
- package/src/tools/fix.ts +258 -20
- package/src/tools/history.ts +74 -15
- package/src/tools/present.ts +89 -0
- package/src/tools/prune.ts +154 -51
- package/src/tools/quality-gate.ts +57 -2
- package/src/tools/quality-store.ts +18 -1
- package/src/tools/review.ts +155 -6
- package/src/tools/transcript.ts +178 -36
- package/src/tools/triage.ts +70 -13
- package/src/tools/validate.ts +138 -32
- package/src/transcript.ts +279 -4
- package/src/types.ts +29 -1
package/lib/client.js
CHANGED
|
@@ -60,6 +60,16 @@ function safeKeys(o) {
|
|
|
60
60
|
return [];
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
+
function coerceJsonNode(v) {
|
|
64
|
+
if (typeof v !== "string") return v;
|
|
65
|
+
const t = v.trim();
|
|
66
|
+
if (!t.startsWith("{") && !t.startsWith("[")) return v;
|
|
67
|
+
try {
|
|
68
|
+
return JSON.parse(t);
|
|
69
|
+
} catch {
|
|
70
|
+
return v;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
63
73
|
function scanSessionForResume(obj, seen, maxDepth = 20) {
|
|
64
74
|
if (maxDepth <= 0) return 0;
|
|
65
75
|
if (!obj || typeof obj !== "object") return 0;
|
|
@@ -213,19 +223,16 @@ function scanSessionForReport(session) {
|
|
|
213
223
|
const call = calls[i];
|
|
214
224
|
if (!call) continue;
|
|
215
225
|
if (safeGet(call, "tool") === "iterate_review" || String(safeGet(call, "tool") ?? "").endsWith("iterate_review")) {
|
|
216
|
-
const result = safeGet(call, "result");
|
|
226
|
+
const result = coerceJsonNode(safeGet(call, "result"));
|
|
217
227
|
if (result && typeof result === "object") {
|
|
218
228
|
const r = (
|
|
219
229
|
/** @type {Record<string, unknown>} */
|
|
220
230
|
result
|
|
221
231
|
);
|
|
222
232
|
const report = safeGet(r, "report");
|
|
223
|
-
if (report
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
report
|
|
227
|
-
);
|
|
228
|
-
}
|
|
233
|
+
if (isReviewReport(report)) return report;
|
|
234
|
+
const found = findReportInObject(result);
|
|
235
|
+
if (found) return found;
|
|
229
236
|
}
|
|
230
237
|
}
|
|
231
238
|
}
|
|
@@ -244,8 +251,24 @@ function scanSessionForReport(session) {
|
|
|
244
251
|
/** @type {Array<Record<string, unknown>>} */
|
|
245
252
|
msgCalls
|
|
246
253
|
);
|
|
247
|
-
for (
|
|
254
|
+
for (let j = calls.length - 1; j >= 0; j--) {
|
|
255
|
+
const call = calls[j];
|
|
248
256
|
if (!call) continue;
|
|
257
|
+
const callName = String(safeGet(call, "name") ?? safeGet(call, "tool") ?? "");
|
|
258
|
+
if (callName === "iterate_review" || callName.endsWith("iterate_review")) {
|
|
259
|
+
for (const key of ["result", "response", "message"]) {
|
|
260
|
+
const node = coerceJsonNode(safeGet(call, key));
|
|
261
|
+
if (!node || typeof node !== "object") continue;
|
|
262
|
+
const r = (
|
|
263
|
+
/** @type {Record<string, unknown>} */
|
|
264
|
+
node
|
|
265
|
+
);
|
|
266
|
+
const report = safeGet(r, "report");
|
|
267
|
+
if (isReviewReport(report)) return report;
|
|
268
|
+
const found = findReportInObject(node);
|
|
269
|
+
if (found) return found;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
249
272
|
const fn = safeGet(call, "function");
|
|
250
273
|
if (fn && typeof fn === "object") {
|
|
251
274
|
const f = (
|
|
@@ -402,10 +425,29 @@ function scanSessionForTranscript(session) {
|
|
|
402
425
|
if (!msg) continue;
|
|
403
426
|
const calls = safeGet(msg, "tool_calls");
|
|
404
427
|
if (Array.isArray(calls)) {
|
|
405
|
-
|
|
428
|
+
const callList = (
|
|
429
|
+
/** @type {Array<Record<string, unknown>>} */
|
|
430
|
+
calls
|
|
431
|
+
);
|
|
432
|
+
for (let j = callList.length - 1; j >= 0; j--) {
|
|
433
|
+
const call = callList[j];
|
|
406
434
|
if (!call) continue;
|
|
407
|
-
const
|
|
408
|
-
|
|
435
|
+
const fn = safeGet(call, "function");
|
|
436
|
+
const candidates = [
|
|
437
|
+
// Result surface first: `{ name|tool, result }` message tool calls
|
|
438
|
+
// (the same shape scanSessionForQualityGate already accepts).
|
|
439
|
+
safeGet(call, "result"),
|
|
440
|
+
safeGet(call, "response"),
|
|
441
|
+
safeGet(call, "message"),
|
|
442
|
+
safeGet(call, "arguments"),
|
|
443
|
+
fn && typeof fn === "object" ? safeGet(
|
|
444
|
+
/** @type {Record<string, unknown>} */
|
|
445
|
+
fn,
|
|
446
|
+
"arguments"
|
|
447
|
+
) : void 0
|
|
448
|
+
];
|
|
449
|
+
for (const args of candidates) {
|
|
450
|
+
if (args === void 0 || args === null) continue;
|
|
409
451
|
const found2 = extractTranscript(args);
|
|
410
452
|
if (found2) return found2;
|
|
411
453
|
}
|
|
@@ -417,6 +459,34 @@ function scanSessionForTranscript(session) {
|
|
|
417
459
|
}
|
|
418
460
|
return null;
|
|
419
461
|
}
|
|
462
|
+
function normalizeValidations(raw) {
|
|
463
|
+
if (!Array.isArray(raw)) return [];
|
|
464
|
+
const out = [];
|
|
465
|
+
for (
|
|
466
|
+
const v of
|
|
467
|
+
/** @type {unknown[]} */
|
|
468
|
+
raw
|
|
469
|
+
) {
|
|
470
|
+
if (!v || typeof v !== "object") continue;
|
|
471
|
+
const rec = (
|
|
472
|
+
/** @type {Record<string, unknown>} */
|
|
473
|
+
v
|
|
474
|
+
);
|
|
475
|
+
const roundRaw = Number(rec.round);
|
|
476
|
+
const exitRaw = rec.exitCode;
|
|
477
|
+
const command = typeof rec.command === "string" ? rec.command : String(rec.command ?? "");
|
|
478
|
+
if (!command) continue;
|
|
479
|
+
const rejectReason = typeof rec.rejectReason === "string" ? rec.rejectReason : "";
|
|
480
|
+
out.push({
|
|
481
|
+
round: Number.isFinite(roundRaw) && roundRaw > 0 ? Math.floor(roundRaw) : 0,
|
|
482
|
+
command,
|
|
483
|
+
exitCode: typeof exitRaw === "number" && Number.isFinite(exitRaw) ? exitRaw : null,
|
|
484
|
+
allowed: rec.allowed === true,
|
|
485
|
+
...rejectReason ? { rejectReason } : {}
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
return out;
|
|
489
|
+
}
|
|
420
490
|
function normalizeTranscript(manifest) {
|
|
421
491
|
const src = manifest && typeof manifest === "object" ? (
|
|
422
492
|
/** @type {Record<string, unknown>} */
|
|
@@ -481,6 +551,9 @@ function normalizeTranscript(manifest) {
|
|
|
481
551
|
})),
|
|
482
552
|
checkpoint,
|
|
483
553
|
timeline: asArray(safeGet(src, "timeline")).map((t) => ({ ...t })),
|
|
554
|
+
// #6: per-round validation rows (command / exitCode / allowed), captured
|
|
555
|
+
// by the workflow and persisted through `iterate_transcript capture`.
|
|
556
|
+
validations: normalizeValidations(safeGet(src, "validations")),
|
|
484
557
|
nudge,
|
|
485
558
|
approval: ap && typeof ap === "object" ? { active: asBool(safeGet(ap, "active")), policy: asStr(safeGet(ap, "policy")) || "ask" } : { active: false, policy: "ask" }
|
|
486
559
|
};
|
|
@@ -515,6 +588,11 @@ function isDefenseEventsResult(obj) {
|
|
|
515
588
|
}
|
|
516
589
|
function findFirstInObject(obj, predicate, seen, maxDepth = 20) {
|
|
517
590
|
if (maxDepth <= 0) return null;
|
|
591
|
+
if (typeof obj === "string") {
|
|
592
|
+
const parsed = coerceJsonNode(obj);
|
|
593
|
+
if (parsed === obj) return null;
|
|
594
|
+
return findFirstInObject(parsed, predicate, seen, maxDepth - 1);
|
|
595
|
+
}
|
|
518
596
|
if (!obj || typeof obj !== "object") return null;
|
|
519
597
|
const s = seen || /* @__PURE__ */ new Set();
|
|
520
598
|
if (s.has(obj)) return null;
|
|
@@ -551,7 +629,7 @@ function findExperienceResultInObject(obj) {
|
|
|
551
629
|
function findDefenseResultInObject(obj) {
|
|
552
630
|
return findFirstInObject(obj, (o) => isDefenseEventsResult(o));
|
|
553
631
|
}
|
|
554
|
-
function latestToolResultNode(session, toolName) {
|
|
632
|
+
function latestToolResultNode(session, toolName, find) {
|
|
555
633
|
if (!session || typeof session !== "object") return null;
|
|
556
634
|
const s = (
|
|
557
635
|
/** @type {Record<string, unknown>} */
|
|
@@ -569,9 +647,9 @@ function latestToolResultNode(session, toolName) {
|
|
|
569
647
|
const tool = String(safeGet(call, "tool") ?? "");
|
|
570
648
|
if (tool !== toolName && !tool.endsWith(toolName)) continue;
|
|
571
649
|
const result = safeGet(call, "result");
|
|
572
|
-
if (result !== void 0 && result !== null) return result;
|
|
650
|
+
if (result !== void 0 && result !== null && find(result)) return result;
|
|
573
651
|
const message = safeGet(call, "message");
|
|
574
|
-
if (message !== void 0 && message !== null) return message;
|
|
652
|
+
if (message !== void 0 && message !== null && find(message)) return message;
|
|
575
653
|
}
|
|
576
654
|
}
|
|
577
655
|
const messages = safeGet(s, "messages");
|
|
@@ -595,14 +673,15 @@ function latestToolResultNode(session, toolName) {
|
|
|
595
673
|
const name2 = String(safeGet(call, "name") ?? safeGet(call, "tool") ?? "");
|
|
596
674
|
if (name2 !== toolName && !name2.endsWith(toolName)) continue;
|
|
597
675
|
const result = safeGet(call, "result") ?? safeGet(call, "response") ?? safeGet(call, "message");
|
|
598
|
-
if (result !== void 0 && result !== null) return result;
|
|
676
|
+
if (result !== void 0 && result !== null && find(result)) return result;
|
|
599
677
|
}
|
|
600
678
|
}
|
|
601
679
|
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
602
680
|
const msg = msgs[i];
|
|
603
681
|
if (!msg) continue;
|
|
604
682
|
const content = safeGet(msg, "content");
|
|
605
|
-
if (content
|
|
683
|
+
if (content === void 0 || content === null) continue;
|
|
684
|
+
if (find(content)) return content;
|
|
606
685
|
}
|
|
607
686
|
}
|
|
608
687
|
return null;
|
|
@@ -651,7 +730,7 @@ function normalizeQualityGateSnapshot(raw) {
|
|
|
651
730
|
};
|
|
652
731
|
}
|
|
653
732
|
function scanSessionForQualityGate(session) {
|
|
654
|
-
const node = latestToolResultNode(session, "iterate_quality_gate");
|
|
733
|
+
const node = latestToolResultNode(session, "iterate_quality_gate", findQualityGateInObject);
|
|
655
734
|
if (node === null) return null;
|
|
656
735
|
const found = findQualityGateInObject(node);
|
|
657
736
|
if (!found) return null;
|
|
@@ -701,7 +780,7 @@ function normalizeExperienceBankResult(raw) {
|
|
|
701
780
|
};
|
|
702
781
|
}
|
|
703
782
|
function scanSessionForExperienceBank(session) {
|
|
704
|
-
const node = latestToolResultNode(session, "iterate_experience");
|
|
783
|
+
const node = latestToolResultNode(session, "iterate_experience", findExperienceResultInObject);
|
|
705
784
|
if (node === null) return null;
|
|
706
785
|
const found = findExperienceResultInObject(node);
|
|
707
786
|
if (!found) return null;
|
|
@@ -757,7 +836,7 @@ function normalizeDefenseEventsResult(raw) {
|
|
|
757
836
|
};
|
|
758
837
|
}
|
|
759
838
|
function scanSessionForDefenseEvents(session) {
|
|
760
|
-
const node = latestToolResultNode(session, "iterate_defense_events");
|
|
839
|
+
const node = latestToolResultNode(session, "iterate_defense_events", findDefenseResultInObject);
|
|
761
840
|
if (node === null) return null;
|
|
762
841
|
const found = findDefenseResultInObject(node);
|
|
763
842
|
if (!found) return null;
|
|
@@ -871,13 +950,14 @@ function normalizeReport(report) {
|
|
|
871
950
|
);
|
|
872
951
|
const rounds = (
|
|
873
952
|
/** @type {Array<unknown>} */
|
|
874
|
-
report.rounds
|
|
953
|
+
Array.isArray(report.rounds) ? report.rounds : []
|
|
875
954
|
);
|
|
876
955
|
const findings = (
|
|
877
956
|
/** @type {Array<Record<string, unknown>>} */
|
|
878
|
-
report.findings
|
|
957
|
+
Array.isArray(report.findings) ? report.findings : []
|
|
879
958
|
);
|
|
880
959
|
const totalRounds = typeof convergence.totalRounds === "number" ? convergence.totalRounds : rounds.length;
|
|
960
|
+
const explicitReason = typeof convergence.stoppedReason === "string" && convergence.stoppedReason.trim() ? convergence.stoppedReason : null;
|
|
881
961
|
const normalizedConvergence = {
|
|
882
962
|
totalRounds,
|
|
883
963
|
findingsByRound: Array.isArray(convergence.findingsByRound) ? convergence.findingsByRound : rounds.map((r) => {
|
|
@@ -888,7 +968,7 @@ function normalizeReport(report) {
|
|
|
888
968
|
return Array.isArray(rr?.findings) ? rr.findings.length : 0;
|
|
889
969
|
}),
|
|
890
970
|
converged: convergence.converged === true,
|
|
891
|
-
stoppedReason:
|
|
971
|
+
stoppedReason: explicitReason ?? (convergence.converged === true ? "converged" : rounds.length >= totalRounds ? "max_rounds_reached" : null)
|
|
892
972
|
};
|
|
893
973
|
let summary = report.summary;
|
|
894
974
|
if (!summary || typeof summary !== "object") {
|
|
@@ -921,17 +1001,18 @@ function normalizeReport(report) {
|
|
|
921
1001
|
};
|
|
922
1002
|
}
|
|
923
1003
|
function computeSummaryFromFindings(findings) {
|
|
1004
|
+
const list = Array.isArray(findings) ? findings : [];
|
|
924
1005
|
const counts = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
925
|
-
const byDimension =
|
|
926
|
-
for (const f of
|
|
1006
|
+
const byDimension = /* @__PURE__ */ Object.create(null);
|
|
1007
|
+
for (const f of list) {
|
|
927
1008
|
if (!f || typeof f !== "object") continue;
|
|
928
1009
|
const sev = String(f.severity ?? "low");
|
|
929
|
-
if (sev
|
|
1010
|
+
if (Object.prototype.hasOwnProperty.call(counts, sev)) counts[sev]++;
|
|
930
1011
|
const dim = String(f.dimension ?? "unknown");
|
|
931
1012
|
byDimension[dim] = (byDimension[dim] ?? 0) + 1;
|
|
932
1013
|
}
|
|
933
1014
|
return {
|
|
934
|
-
totalFindings:
|
|
1015
|
+
totalFindings: list.length,
|
|
935
1016
|
critical: counts.critical,
|
|
936
1017
|
high: counts.high,
|
|
937
1018
|
medium: counts.medium,
|
|
@@ -952,7 +1033,7 @@ function computeConvergenceProgress(report) {
|
|
|
952
1033
|
function currentRoundNumber(report) {
|
|
953
1034
|
const rounds = (
|
|
954
1035
|
/** @type {Array<Record<string, unknown>>} */
|
|
955
|
-
report.rounds
|
|
1036
|
+
Array.isArray(report.rounds) ? report.rounds : []
|
|
956
1037
|
);
|
|
957
1038
|
let max = 0;
|
|
958
1039
|
for (const r of rounds) {
|
|
@@ -975,34 +1056,38 @@ function getTotalRounds(report) {
|
|
|
975
1056
|
function severityStats(report) {
|
|
976
1057
|
const findings = (
|
|
977
1058
|
/** @type {Array<Record<string, unknown>>} */
|
|
978
|
-
report.findings
|
|
1059
|
+
Array.isArray(report.findings) ? report.findings : []
|
|
979
1060
|
);
|
|
980
1061
|
const counts = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
981
1062
|
for (const f of findings) {
|
|
982
1063
|
if (!f || typeof f !== "object") continue;
|
|
983
1064
|
const sev = String(f.severity ?? "low");
|
|
984
|
-
if (sev
|
|
1065
|
+
if (Object.prototype.hasOwnProperty.call(counts, sev)) counts[sev]++;
|
|
985
1066
|
}
|
|
986
1067
|
return counts;
|
|
987
1068
|
}
|
|
988
1069
|
function groupByDimension(report) {
|
|
989
1070
|
const findings = (
|
|
990
1071
|
/** @type {Array<Record<string, unknown>>} */
|
|
991
|
-
report.findings
|
|
1072
|
+
Array.isArray(report.findings) ? report.findings : []
|
|
992
1073
|
);
|
|
993
|
-
const groups =
|
|
1074
|
+
const groups = /* @__PURE__ */ Object.create(null);
|
|
994
1075
|
for (const f of findings) {
|
|
995
1076
|
if (!f || typeof f !== "object") continue;
|
|
996
1077
|
const dim = String(f.dimension ?? "unknown");
|
|
997
|
-
if (!groups
|
|
1078
|
+
if (!Object.prototype.hasOwnProperty.call(groups, dim)) groups[dim] = [];
|
|
998
1079
|
groups[dim].push(f);
|
|
999
1080
|
}
|
|
1000
1081
|
return groups;
|
|
1001
1082
|
}
|
|
1083
|
+
var TRIAGE_VERDICTS = (
|
|
1084
|
+
/** @type {const} */
|
|
1085
|
+
["keep", "skip", "ignore"]
|
|
1086
|
+
);
|
|
1002
1087
|
function buildTriageState(report) {
|
|
1003
1088
|
const findings = (
|
|
1004
1089
|
/** @type {Array<unknown>} */
|
|
1005
|
-
report.findings
|
|
1090
|
+
Array.isArray(report.findings) ? report.findings : []
|
|
1006
1091
|
);
|
|
1007
1092
|
const state = {};
|
|
1008
1093
|
for (let i = 0; i < findings.length; i++) {
|
|
@@ -1013,7 +1098,7 @@ function buildTriageState(report) {
|
|
|
1013
1098
|
function hashReport(report) {
|
|
1014
1099
|
const findings = (
|
|
1015
1100
|
/** @type {Array<Record<string, unknown>>} */
|
|
1016
|
-
report.findings
|
|
1101
|
+
Array.isArray(report.findings) ? report.findings : []
|
|
1017
1102
|
);
|
|
1018
1103
|
let h = 2166136261;
|
|
1019
1104
|
const mix = (s) => {
|
|
@@ -1058,17 +1143,27 @@ function buildApplyInstruction(entries) {
|
|
|
1058
1143
|
null,
|
|
1059
1144
|
2
|
|
1060
1145
|
);
|
|
1061
|
-
return
|
|
1146
|
+
return `\u8BF7\u8C03\u7528 \`iterate_triage\` \u5E94\u7528\u4E0B\u9762\u7684 known_intentional \u6761\u76EE\uFF08\u7C98\u8D34\u540E\u7531\u6A21\u578B\u8C03\u7528 \`iterate_triage\` \u7684 \`operation: "apply"\` \u5199\u5165\u914D\u7F6E\uFF1A\u81EA\u52A8\u53BB\u91CD\u3001\u5199\u5165\u524D\u5907\u4EFD\u3001\u5931\u8D25\u81EA\u52A8\u56DE\u6EDA\uFF09\uFF1A
|
|
1062
1147
|
|
|
1063
1148
|
\`\`\`json
|
|
1064
1149
|
${payload}
|
|
1150
|
+
\`\`\`
|
|
1151
|
+
|
|
1152
|
+
\u5199\u5165\u5B8C\u6210\u540E\u8BF7\u590D\u5236\u9762\u677F\u7684\u300C\u56DE\u8BFB\u5DF2\u5199\u5165\u6761\u76EE\u300D\u6307\u4EE4\uFF08\`iterate_triage\` \`operation: "list"\`\uFF09\u6838\u5BF9\u7ED3\u679C\u3002`;
|
|
1153
|
+
}
|
|
1154
|
+
function buildTriageReadbackInstruction() {
|
|
1155
|
+
return `\u8BF7\u8C03\u7528 \`iterate_triage\` \u56DE\u8BFB\u5F53\u524D\u5DF2\u5199\u5165\u914D\u7F6E\u7684 known_intentional \u6761\u76EE\uFF0C\u5E76\u628A\u6BCF\u6761\u7684 file / line / dimension / reason \u539F\u6837\u5217\u51FA\u6765\u4F9B\u6211\u6838\u5BF9\uFF1A
|
|
1156
|
+
|
|
1157
|
+
\`\`\`json
|
|
1158
|
+
${JSON.stringify({ operation: "list" }, null, 2)}
|
|
1065
1159
|
\`\`\``;
|
|
1066
1160
|
}
|
|
1067
1161
|
function collectIgnoredEntries(triageState, findings) {
|
|
1162
|
+
const list = Array.isArray(findings) ? findings : [];
|
|
1068
1163
|
const entries = [];
|
|
1069
1164
|
for (const [idx, verdict] of Object.entries(triageState)) {
|
|
1070
1165
|
if (verdict !== "ignore") continue;
|
|
1071
|
-
const finding =
|
|
1166
|
+
const finding = list[Number(idx)];
|
|
1072
1167
|
if (!finding) continue;
|
|
1073
1168
|
entries.push({
|
|
1074
1169
|
file: String(finding.file ?? ""),
|
|
@@ -1079,6 +1174,204 @@ function collectIgnoredEntries(triageState, findings) {
|
|
|
1079
1174
|
}
|
|
1080
1175
|
return entries;
|
|
1081
1176
|
}
|
|
1177
|
+
var CONTENT_PLACEHOLDER = "<\u7531\u6A21\u578B\u586B\u5199\uFF1A\u4FEE\u590D\u540E\u7684\u5B8C\u6574\u6587\u4EF6\u5185\u5BB9>";
|
|
1178
|
+
var ROUND_PLACEHOLDER = "<\u7531\u6A21\u578B\u586B\u5199\uFF1A\u5F53\u524D\u8FED\u4EE3\u8F6E\u6B21\uFF08\u22651 \u7684\u6574\u6570\uFF09>";
|
|
1179
|
+
var START_INSTRUCTION_FULL = '\u5F00\u59CB\u4E00\u6B21\u5B8C\u6574\u8FED\u4EE3\uFF08\u5BA1\u67E5 \u2192 \u4FEE\u590D \u2192 \u9A8C\u8BC1 \u2192 \u590D\u76D8\uFF09\uFF1A\u8BF7\u8C03\u7528 `workflow` \u5DE5\u5177\u8FD0\u884C iterate \u5DE5\u4F5C\u6D41\uFF0Cmode: "normal"\uFF0C\u6309\u7CFB\u7EDF\u63D0\u793A\u7684 Iterate Workflow \u6267\u884C plan \u2192 \u591A\u7EBF\u7A0B\u5E76\u884C\u8BC4\u5BA1 \u2192 \u5206\u8BCA \u2192 \u539F\u5B50\u4FEE\u590D \u2192 \u6BCF\u8F6E\u9A8C\u8BC1 \u2192 \u5FAA\u73AF\u76F4\u81F3\u6536\u655B\u6216\u8FBE\u5230 max_rounds \u2192 \u7EC8\u62A5\u5E76 `iterate_transcript` capture\u3002\u5F00\u8DD1\u524D\u5148 `iterate_config({operation:"read"})` \u68C0\u67E5 validation.commands\uFF1A\u82E5\u4E3A\u7A7A\uFF0C\u5148\u505C\u4E0B\u6765\u63D0\u793A\u6211\u7528 `iterate_config` \u5199\u5165\u9A8C\u8BC1\u547D\u4EE4\uFF0C\u5426\u5219\u672C\u8F6E\u8FED\u4EE3\u4E0D\u53D7\u4EFB\u4F55\u6D4B\u8BD5\u4FDD\u62A4\u3002';
|
|
1180
|
+
var START_INSTRUCTION_REVIEW_ONLY = '\u5F00\u59CB\u4E00\u6B21\u4EC5\u8BC4\u5BA1\uFF08dry-run\uFF0C\u4E0D\u4FEE\u6539\u4EFB\u4F55\u6587\u4EF6\uFF09\uFF1A\u8BF7\u8C03\u7528 `workflow` \u5DE5\u5177\u8FD0\u884C iterate \u5DE5\u4F5C\u6D41\uFF0Cmode: "dry-run"\uFF0C\u6309\u7CFB\u7EDF\u63D0\u793A\u7684 Iterate Workflow \u6267\u884C plan \u2192 \u591A\u7EBF\u7A0B\u5E76\u884C\u8BC4\u5BA1 \u2192 \u5206\u8BCA \u2192 `iterate_review aggregate` / `meta-review` \u7EC8\u62A5\u5E76 `iterate_transcript` capture\uFF1B\u5168\u7A0B\u4E0D\u8981\u8C03\u7528 `iterate_fix` / `iterate_rollback`\u3002';
|
|
1181
|
+
var START_INSTRUCTIONS = {
|
|
1182
|
+
full: START_INSTRUCTION_FULL,
|
|
1183
|
+
reviewOnly: START_INSTRUCTION_REVIEW_ONLY
|
|
1184
|
+
};
|
|
1185
|
+
function fixFindingPayload(finding) {
|
|
1186
|
+
const f = finding && typeof finding === "object" ? finding : {};
|
|
1187
|
+
const file = String(f.file ?? "");
|
|
1188
|
+
return {
|
|
1189
|
+
file,
|
|
1190
|
+
...typeof f.line === "number" && f.line > 0 ? { line: f.line } : {},
|
|
1191
|
+
dimension: String(f.dimension ?? ""),
|
|
1192
|
+
severity: String(f.severity ?? ""),
|
|
1193
|
+
summary: String(f.summary ?? ""),
|
|
1194
|
+
...f.failure_scenario ? { failure_scenario: String(f.failure_scenario) } : {},
|
|
1195
|
+
...f.suggested_fix ? { suggested_fix: String(f.suggested_fix) } : {}
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
function buildFixInstruction(finding, opts) {
|
|
1199
|
+
const payload = fixFindingPayload(finding);
|
|
1200
|
+
if (!payload.file) return "";
|
|
1201
|
+
const round = opts && Number.isInteger(opts.round) ? opts.round : null;
|
|
1202
|
+
const call = {
|
|
1203
|
+
file: payload.file,
|
|
1204
|
+
content: CONTENT_PLACEHOLDER,
|
|
1205
|
+
finding: payload,
|
|
1206
|
+
round: round !== null ? round : ROUND_PLACEHOLDER
|
|
1207
|
+
};
|
|
1208
|
+
return [
|
|
1209
|
+
"\u8BF7\u4FEE\u590D\u4E0B\u9762\u8FD9\u4E00\u4E2A finding\u3002`iterate_fix` \u4E00\u6B21\u53EA\u63A5\u53D7\u4E00\u4E2A finding\uFF0C\u4E14 file / content / finding / round \u56DB\u4E2A\u53C2\u6570\u5168\u90E8\u5FC5\u586B\uFF1A",
|
|
1210
|
+
`\u6B65\u9AA4 1\uFF1A\u8BFB\u53D6 \`${payload.file}\`\uFF0C\u6309 finding \u751F\u6210\u4FEE\u590D\u540E\u7684\u3010\u5B8C\u6574\u6587\u4EF6\u5185\u5BB9\u3011\uFF08content \u5360\u4F4D\u5FC5\u987B\u6362\u6210\u771F\u5B9E\u6587\u4EF6\u5185\u5BB9\uFF09\u3002`,
|
|
1211
|
+
`\u6B65\u9AA4 2\uFF1A\u8C03\u7528 \`iterate_fix\`\uFF0C\u628A content \u4E0E round \u586B\u6210\u771F\u5B9E\u503C${round !== null ? `\uFF08\u672C\u8F6E round = ${round}\uFF09` : ""}\uFF1A`,
|
|
1212
|
+
"```json",
|
|
1213
|
+
JSON.stringify(call, null, 2),
|
|
1214
|
+
"```",
|
|
1215
|
+
'\u9700\u8981\u8D85\u8FC7 atomic \u884C\u6570\u4E0A\u9650\u65F6\uFF0C\u53E6\u52A0 "force": true\u3002'
|
|
1216
|
+
].join("\n");
|
|
1217
|
+
}
|
|
1218
|
+
function buildAssignFixesInstruction(findings) {
|
|
1219
|
+
const list = (Array.isArray(findings) ? findings : []).filter((f) => f && typeof f === "object");
|
|
1220
|
+
if (list.length === 0) return "";
|
|
1221
|
+
const payload = list.map((f) => fixFindingPayload(f)).filter((p) => p.file);
|
|
1222
|
+
if (payload.length === 0) return "";
|
|
1223
|
+
return [
|
|
1224
|
+
`\u8BF7\u4FEE\u590D\u4E0B\u9762\u8FD9 ${payload.length} \u4E2A findings\u3002\u6CE8\u610F\uFF1A\`iterate_fix\` \u6BCF\u6B21\u53EA\u5904\u7406\u3010\u4E00\u4E2A\u3011finding\uFF08\u6CA1\u6709\u6570\u7EC4\u5165\u53C2\uFF09\uFF0C\u8BF7\u9010\u4E2A\u8C03\u7528 iterate_fix\u2014\u2014\u4E00\u6B21\u4E00\u4E2A finding\uFF0C\u5904\u7406\u5B8C\u4E00\u4E2A\u518D\u53D1\u8D77\u4E0B\u4E00\u4E2A\uFF0C\u7EDD\u4E0D\u8981\u628A\u6574\u6279\u6570\u7EC4\u5F53\u4F5C\u53C2\u6570\u4F20\u5165\u3002`,
|
|
1225
|
+
"\u6BCF\u4E2A finding \u90FD\u8D70\u540C\u6837\u7684\u4E24\u6B65\uFF1A\u2460 \u8BFB\u53D6\u6587\u4EF6\u751F\u6210\u4FEE\u590D\u540E\u7684\u5B8C\u6574 content\uFF1B\u2461 \u8C03\u7528 iterate_fix\uFF08file / content / finding / round \u5FC5\u586B\uFF0Ccontent \u4E0E round \u7531\u4F60\u586B\u5199\uFF09\u3002",
|
|
1226
|
+
"\u5F85\u4FEE\u590D\u6E05\u5355\uFF1A",
|
|
1227
|
+
"```json",
|
|
1228
|
+
JSON.stringify(payload, null, 2),
|
|
1229
|
+
"```"
|
|
1230
|
+
].join("\n");
|
|
1231
|
+
}
|
|
1232
|
+
function buildArchitecturalFixInstruction(findings) {
|
|
1233
|
+
const list = (Array.isArray(findings) ? findings : []).filter((f) => f && typeof f === "object");
|
|
1234
|
+
const lines = [
|
|
1235
|
+
'\u8BF7\u6279\u51C6\u5E76\u6267\u884C\u67B6\u6784\u578B\u4FEE\u590D\uFF08\u5141\u8BB8\u8D85\u8FC7 atomic \u884C\u6570\u4E0A\u9650\uFF09\uFF1A\u5BF9\u6BCF\u4E2A\u67B6\u6784\u578B finding \u9010\u4E2A\u8C03\u7528 `iterate_fix`\uFF0C\u4E00\u6B21\u4E00\u4E2A finding\uFF0C\u5E76\u663E\u5F0F\u8BBE\u7F6E `"force": true`\uFF1B',
|
|
1236
|
+
"file / content / finding / round \u56DB\u4E2A\u5FC5\u586B\u53C2\u6570\u4E00\u4E2A\u90FD\u4E0D\u80FD\u5C11\uFF08content = \u4FEE\u590D\u540E\u7684\u5B8C\u6574\u6587\u4EF6\u5185\u5BB9\uFF0Cround = \u5F53\u524D\u8FED\u4EE3\u8F6E\u6B21\uFF0C\u4E24\u8005\u7531\u4F60\u586B\u5199\uFF09\u3002"
|
|
1237
|
+
];
|
|
1238
|
+
if (list.length > 0) {
|
|
1239
|
+
lines.push("\u672C\u9879\u76EE\u5269\u4F59\u67B6\u6784\u578B finding\uFF1A", "```json", JSON.stringify(list.map((f) => fixFindingPayload(f)), null, 2), "```");
|
|
1240
|
+
}
|
|
1241
|
+
return lines.join("\n");
|
|
1242
|
+
}
|
|
1243
|
+
function buildCheckpointResumeInstruction() {
|
|
1244
|
+
return `\u8BF7\u8C03\u7528 \`iterate_checkpoint\` \u4ECE\u65AD\u70B9\u6062\u590D\u8FED\u4EE3\uFF08\u52A0\u8F7D\u65AD\u70B9\u5E76\u628A resumeCount +1\uFF09\uFF1A
|
|
1245
|
+
|
|
1246
|
+
\`\`\`json
|
|
1247
|
+
${JSON.stringify({ operation: "resume" }, null, 2)}
|
|
1248
|
+
\`\`\``;
|
|
1249
|
+
}
|
|
1250
|
+
function buildCheckpointClearInstruction() {
|
|
1251
|
+
return `\u8BF7\u8C03\u7528 \`iterate_checkpoint\` \u6E05\u9664\u5F53\u524D\u65AD\u70B9\uFF1A
|
|
1252
|
+
|
|
1253
|
+
\`\`\`json
|
|
1254
|
+
${JSON.stringify({ operation: "clear" }, null, 2)}
|
|
1255
|
+
\`\`\``;
|
|
1256
|
+
}
|
|
1257
|
+
function buildQualityGateQueryInstruction() {
|
|
1258
|
+
return `\u8BF7\u8C03\u7528 \`iterate_quality_gate\` \u67E5\u8BE2\u5F53\u524D\u8D28\u91CF\u95E8\u7981\u72B6\u6001\uFF08\u8BFB\u53D6\u78C1\u76D8\u4E0A\u6301\u4E45\u5316\u7684\u8BC1\u4E66\uFF09\uFF1A
|
|
1259
|
+
|
|
1260
|
+
\`\`\`json
|
|
1261
|
+
${JSON.stringify({ operation: "read" }, null, 2)}
|
|
1262
|
+
\`\`\``;
|
|
1263
|
+
}
|
|
1264
|
+
function buildQualityGateClearInstruction() {
|
|
1265
|
+
return `\u8BF7\u8C03\u7528 \`iterate_quality_gate\` \u6E05\u9664\u5F53\u524D\u8D28\u91CF\u95E8\u7981\u8BC1\u4E66\uFF1A
|
|
1266
|
+
|
|
1267
|
+
\`\`\`json
|
|
1268
|
+
${JSON.stringify({ operation: "clear" }, null, 2)}
|
|
1269
|
+
\`\`\``;
|
|
1270
|
+
}
|
|
1271
|
+
function buildRollbackInstruction(id) {
|
|
1272
|
+
return `\u8BF7\u8C03\u7528 \`iterate_rollback\` \u56DE\u6EDA\u4EE5\u4E0B\u4FEE\u590D\uFF1A
|
|
1273
|
+
|
|
1274
|
+
\`\`\`json
|
|
1275
|
+
${JSON.stringify({ id: String(id || "") }, null, 2)}
|
|
1276
|
+
\`\`\``;
|
|
1277
|
+
}
|
|
1278
|
+
function buildExperienceListInstruction() {
|
|
1279
|
+
return `\u8BF7\u8C03\u7528 \`iterate_experience\` \u5217\u51FA\u7ECF\u9A8C\u94F6\u884C\u4E2D\u7684\u6240\u6709\u7ECF\u9A8C\uFF0C\u5E76\u628A\u5B8C\u6574\u8FD4\u56DE\u7ED3\u679C\u56DE\u663E\u5230\u4F1A\u8BDD\u91CC\uFF1A
|
|
1280
|
+
|
|
1281
|
+
\`\`\`json
|
|
1282
|
+
${JSON.stringify({ operation: "list" }, null, 2)}
|
|
1283
|
+
\`\`\``;
|
|
1284
|
+
}
|
|
1285
|
+
function buildDefenseEventsListInstruction() {
|
|
1286
|
+
return `\u8BF7\u8C03\u7528 \`iterate_defense_events\` \u5217\u51FA\u5168\u90E8\u9632\u5FA1\u4E8B\u4EF6\uFF0C\u5E76\u628A\u5B8C\u6574\u8FD4\u56DE\u7ED3\u679C\u56DE\u663E\u5230\u4F1A\u8BDD\u91CC\uFF1A
|
|
1287
|
+
|
|
1288
|
+
\`\`\`json
|
|
1289
|
+
${JSON.stringify({ operation: "list" }, null, 2)}
|
|
1290
|
+
\`\`\``;
|
|
1291
|
+
}
|
|
1292
|
+
function buildDiskSnapshotInstruction() {
|
|
1293
|
+
const lines = [
|
|
1294
|
+
"\u8BF7\u4ECE\u78C1\u76D8\u62C9\u53D6 iterate \u5FEB\u7167\uFF1A\u9010\u4E2A\u8C03\u7528\u4E0B\u9762\u7684\u53EA\u8BFB\u5DE5\u5177\uFF0C\u5E76\u628A\u6BCF\u4E2A\u5DE5\u5177\u7684\u3010\u5B8C\u6574\u8FD4\u56DE\u7ED3\u679C\u3011\u539F\u6837\u56DE\u663E\u5230\u4F1A\u8BDD\u91CC\uFF08\u4E0D\u8981\u53EA\u7ED9\u6458\u8981\u2014\u2014\u63D2\u4EF6\u9762\u677F\u9760\u8FD9\u4E9B\u56DE\u663E\u586B\u5145\uFF09\uFF1A"
|
|
1295
|
+
];
|
|
1296
|
+
DISK_SNAPSHOT_SOURCES.forEach((s, i) => {
|
|
1297
|
+
lines.push(`${i + 1}. \`${s.tool}\`\uFF08${s.label}\uFF09\uFF1A${JSON.stringify(s.args)}`);
|
|
1298
|
+
});
|
|
1299
|
+
lines.push("\u56DE\u663E\u540E\uFF0C\u89C2\u6D4B\u53F0\u5B9E\u65F6\u6D41 / F2 \u8DE8\u8F6E\u5BF9\u6BD4 / F5 \u65AD\u70B9 / F8 \u8D28\u91CF\u95E8\u7981 / F9 \u7ECF\u9A8C\u94F6\u884C / F10 \u9632\u5FA1\u4E8B\u4EF6\u4F1A\u81EA\u52A8\u8BFB\u53D6\u8FD9\u4E9B\u7ED3\u679C\u3002");
|
|
1300
|
+
return lines.join("\n");
|
|
1301
|
+
}
|
|
1302
|
+
var DISK_SNAPSHOT_SOURCES = [
|
|
1303
|
+
{ tool: "iterate_status", args: {}, label: "\u8FD0\u884C\u72B6\u6001\u6C47\u603B" },
|
|
1304
|
+
{ tool: "iterate_transcript", args: { operation: "read" }, label: "transcript \u89C2\u6D4B\u6E05\u5355" },
|
|
1305
|
+
{ tool: "iterate_quality_gate", args: { operation: "read" }, label: "\u8D28\u91CF\u95E8\u7981\u8BC1\u4E66" },
|
|
1306
|
+
{ tool: "iterate_experience", args: { operation: "list" }, label: "\u7ECF\u9A8C\u94F6\u884C" },
|
|
1307
|
+
{ tool: "iterate_defense_events", args: { operation: "list" }, label: "\u9632\u5FA1\u4E8B\u4EF6\u6D41" },
|
|
1308
|
+
{ tool: "iterate_history", args: {}, label: "\u51B3\u7B56\u65E5\u5FD7\u4E0E\u4FEE\u590D\u6CE8\u518C\u8868\uFF08\u8DE8\u8F6E/\u8DE8\u4F1A\u8BDD\u5BF9\u6BD4\uFF09" }
|
|
1309
|
+
];
|
|
1310
|
+
function toolCalledInSession(session, toolName) {
|
|
1311
|
+
if (!session || typeof session !== "object" || typeof toolName !== "string" || !toolName) return false;
|
|
1312
|
+
const s = (
|
|
1313
|
+
/** @type {Record<string, unknown>} */
|
|
1314
|
+
session
|
|
1315
|
+
);
|
|
1316
|
+
const toolCalls = safeGet(s, "toolCalls");
|
|
1317
|
+
if (Array.isArray(toolCalls)) {
|
|
1318
|
+
for (
|
|
1319
|
+
const call of
|
|
1320
|
+
/** @type {unknown[]} */
|
|
1321
|
+
toolCalls
|
|
1322
|
+
) {
|
|
1323
|
+
if (!call || typeof call !== "object") continue;
|
|
1324
|
+
const t = String(safeGet(
|
|
1325
|
+
/** @type {Record<string, unknown>} */
|
|
1326
|
+
call,
|
|
1327
|
+
"tool"
|
|
1328
|
+
) ?? "");
|
|
1329
|
+
if (t === toolName || t.endsWith(toolName)) return true;
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
const messages = safeGet(s, "messages");
|
|
1333
|
+
if (Array.isArray(messages)) {
|
|
1334
|
+
for (
|
|
1335
|
+
const msg of
|
|
1336
|
+
/** @type {unknown[]} */
|
|
1337
|
+
messages
|
|
1338
|
+
) {
|
|
1339
|
+
if (!msg || typeof msg !== "object") continue;
|
|
1340
|
+
const calls = safeGet(
|
|
1341
|
+
/** @type {Record<string, unknown>} */
|
|
1342
|
+
msg,
|
|
1343
|
+
"tool_calls"
|
|
1344
|
+
);
|
|
1345
|
+
if (!Array.isArray(calls)) continue;
|
|
1346
|
+
for (
|
|
1347
|
+
const call of
|
|
1348
|
+
/** @type {unknown[]} */
|
|
1349
|
+
calls
|
|
1350
|
+
) {
|
|
1351
|
+
if (!call || typeof call !== "object") continue;
|
|
1352
|
+
const c = (
|
|
1353
|
+
/** @type {Record<string, unknown>} */
|
|
1354
|
+
call
|
|
1355
|
+
);
|
|
1356
|
+
const name2 = String(safeGet(c, "name") ?? safeGet(c, "tool") ?? "");
|
|
1357
|
+
if (name2 === toolName || name2.endsWith(toolName)) return true;
|
|
1358
|
+
const fn = safeGet(c, "function");
|
|
1359
|
+
if (fn && typeof fn === "object" && String(safeGet(
|
|
1360
|
+
/** @type {Record<string, unknown>} */
|
|
1361
|
+
fn,
|
|
1362
|
+
"name"
|
|
1363
|
+
) ?? "") === toolName) {
|
|
1364
|
+
return true;
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
return false;
|
|
1370
|
+
}
|
|
1371
|
+
function diskEmptyStateText(pulled, topic) {
|
|
1372
|
+
const subject = topic || "\u6570\u636E";
|
|
1373
|
+
return pulled ? `\u5DF2\u4ECE\u78C1\u76D8\u62C9\u53D6\uFF1A${subject}\u6682\u65E0\u8BB0\u5F55\uFF08\u78C1\u76D8\u65E0\u6570\u636E\uFF09\u3002` : `\u5C1A\u672A\u4ECE\u78C1\u76D8\u62C9\u53D6${subject}\uFF1A\u6570\u636E\u5B58\u653E\u5728\u9879\u76EE .iterate/ \u4E0B\uFF0C\u70B9\u300C\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u300D\u590D\u5236\u6307\u4EE4\u53D1\u56DE\u4F1A\u8BDD\u5373\u53EF\u56DE\u586B\u3002`;
|
|
1374
|
+
}
|
|
1082
1375
|
function normalizeFindingFilter(filter) {
|
|
1083
1376
|
const f = filter && typeof filter === "object" ? filter : {};
|
|
1084
1377
|
const severities = Array.isArray(f.severities) ? f.severities.filter((s) => SEVERITY_ORDER.includes(String(s))) : [];
|
|
@@ -1122,6 +1415,7 @@ function buildFilterOptions(findings) {
|
|
|
1122
1415
|
const severities = SEVERITY_ORDER.map((value) => ({ value, count: 0 }));
|
|
1123
1416
|
const dimCounts = {};
|
|
1124
1417
|
for (const f of list) {
|
|
1418
|
+
if (!f || typeof f !== "object") continue;
|
|
1125
1419
|
const sev = String(f.severity ?? "low");
|
|
1126
1420
|
const sv = severities.find((s) => s.value === sev);
|
|
1127
1421
|
if (sv) sv.count++;
|
|
@@ -1141,7 +1435,7 @@ function countVerdicts(triageState) {
|
|
|
1141
1435
|
return counts;
|
|
1142
1436
|
}
|
|
1143
1437
|
function batchSetVerdict(triageState, indices, verdict) {
|
|
1144
|
-
if (verdict
|
|
1438
|
+
if (!TRIAGE_VERDICTS.includes(verdict)) return triageState;
|
|
1145
1439
|
if (!Array.isArray(indices) || indices.length === 0) return triageState;
|
|
1146
1440
|
const next = { ...triageState };
|
|
1147
1441
|
for (const idx of indices) {
|
|
@@ -1250,6 +1544,21 @@ var CONFIG_EDIT_FIELDS = [
|
|
|
1250
1544
|
{ key: "max_rounds", label: "\u6700\u5927\u8F6E\u6570", hint: "\u6B63\u6574\u6570" },
|
|
1251
1545
|
{ key: "review.scope", label: "\u5BA1\u67E5\u8303\u56F4", hint: '"full" \u6216 "changed-only"' },
|
|
1252
1546
|
{ key: "reasoning_effort", label: "\u5BA1\u67E5\u63A8\u7406\u5F3A\u5EA6", hint: '"low" / "medium" / "high"\uFF0C\u7F3A\u7701\u8DDF\u968F\u6A21\u578B\u9ED8\u8BA4' },
|
|
1547
|
+
{
|
|
1548
|
+
key: "validation.commands",
|
|
1549
|
+
label: "\u9A8C\u8BC1\u547D\u4EE4",
|
|
1550
|
+
hint: '\u6570\u7EC4\uFF0C\u6BCF\u9879\u662F\u4E00\u6761\u767D\u540D\u5355\u547D\u4EE4\u5B57\u7B26\u4E32\uFF0C\u5982 ["npm test","npm run lint"]\uFF1B\u6BCF\u8F6E\u9A8C\u8BC1\u9010\u6761\u6267\u884C\u3002\u7559\u7A7A\u6570\u7EC4 = \u4E0D\u9A8C\u8BC1\uFF08\u672C\u8F6E\u8FED\u4EE3\u4E0D\u53D7\u4EFB\u4F55\u6D4B\u8BD5\u4FDD\u62A4\uFF09'
|
|
1551
|
+
},
|
|
1552
|
+
{
|
|
1553
|
+
key: "language",
|
|
1554
|
+
label: "\u754C\u9762\u8BED\u8A00",
|
|
1555
|
+
hint: '"zh" \u6216 "en"\uFF08\u9ED8\u8BA4 "en"\uFF09\uFF1A\u63A7\u5236\u5BA1\u6279\u7406\u7531\u3001\u9632\u5FA1\u4E8B\u4EF6\u6807\u7B7E\u7B49\u9762\u5411\u4EBA\u6587\u6848\u7684\u8BED\u8A00'
|
|
1556
|
+
},
|
|
1557
|
+
{
|
|
1558
|
+
key: "personalization.known_intentional",
|
|
1559
|
+
label: "\u5DF2\u77E5\u6709\u610F\u95EE\u9898",
|
|
1560
|
+
hint: "\u6570\u7EC4\uFF0C\u6BCF\u9879 {file, line?, dimension, reason}\uFF1B\u547D\u4E2D\u6761\u76EE\u5728\u8BC4\u5BA1\u4E2D\u88AB\u8DF3\u8FC7\u3002\u53EF\u7528\u5206\u8BCA\u9762\u677F\u7684\u300C\u56DE\u8BFB\u5DF2\u5199\u5165\u6761\u76EE\u300D\u6838\u5BF9"
|
|
1561
|
+
},
|
|
1253
1562
|
{ key: "atomic.max_lines", label: "\u539F\u5B50\u4FEE\u590D\u4E0A\u9650\u884C\u6570", hint: "\u6B63\u6574\u6570" },
|
|
1254
1563
|
{ key: "git.push_per_round", label: "\u6BCF\u8F6E\u63A8\u9001", hint: "true / false" }
|
|
1255
1564
|
];
|
|
@@ -1259,16 +1568,50 @@ function buildConfigEditGuide() {
|
|
|
1259
1568
|
"---------------------",
|
|
1260
1569
|
"\u914D\u7F6E\u6587\u4EF6\uFF1A\u9879\u76EE\u6839\u76EE\u5F55 iterate.config.yaml\u3002",
|
|
1261
1570
|
"",
|
|
1571
|
+
"\u26A0 \u672A\u914D\u7F6E validation.commands \u65F6\u8FED\u4EE3\u4E0D\u88AB\u4FDD\u62A4\uFF1A\u6BCF\u8F6E validate \u62FF\u5230\u7A7A\u7ED3\u679C\u4F1A\u88AB\u5F53\u4F5C",
|
|
1572
|
+
" \u201C\u9A8C\u8BC1\u901A\u8FC7\u201D\uFF0C\u7B49\u4E8E\u5728\u6CA1\u6709\u4EFB\u4F55\u6D4B\u8BD5\u4FDD\u62A4\u7684\u60C5\u51B5\u4E0B\u7EE7\u7EED\u6536\u655B\u3002\u7B2C\u4E00\u6B21\u542F\u52A8\u8FED\u4EE3\u524D\uFF0C",
|
|
1573
|
+
" \u8BF7\u5148\u7ED9 validation.commands \u914D\u4E0A\u81F3\u5C11\u4E00\u6761\u771F\u5B9E\u547D\u4EE4\uFF08\u5982 npm test\uFF09\u3002",
|
|
1574
|
+
"",
|
|
1262
1575
|
"\u53EF\u7F16\u8F91\u5B57\u6BB5\uFF1A",
|
|
1263
1576
|
...CONFIG_EDIT_FIELDS.map((f) => `- ${f.key}\uFF08${f.label}\uFF09\uFF1A${f.hint}`),
|
|
1264
1577
|
"",
|
|
1265
1578
|
"\u8BA9\u6A21\u578B\u5E2E\u4F60\u6539\uFF1A",
|
|
1266
1579
|
'1. \u8C03\u7528 iterate_config({ operation: "read" }) \u67E5\u770B\u5F53\u524D\u914D\u7F6E\uFF1B',
|
|
1267
|
-
"2. \u8BF4\u660E\u60F3\u6539\u7684\u5B57\u6BB5\uFF0C\u4F8B\u5982\u300C\u628A max_rounds \u6539\u6210 5\
|
|
1268
|
-
'3. \u6A21\u578B\u4F1A\u8C03\u7528 iterate_config({ operation: "write", updates: {...} }) \u5199\u5165\uFF0C\u5199\u5165\u524D\u81EA\u52A8\u5907\u4EFD\uFF0C\u5931\u8D25\u81EA\u52A8\u56DE\u6EDA\u3002'
|
|
1580
|
+
"2. \u8BF4\u660E\u60F3\u6539\u7684\u5B57\u6BB5\uFF0C\u4F8B\u5982\u300C\u628A max_rounds \u6539\u6210 5\uFF0Cvalidation.commands \u52A0\u4E0A npm test\u300D\uFF1B",
|
|
1581
|
+
'3. \u6A21\u578B\u4F1A\u8C03\u7528 iterate_config({ operation: "write", updates: {...} }) \u5199\u5165\uFF0C\u5199\u5165\u524D\u81EA\u52A8\u5907\u4EFD\uFF0C\u5931\u8D25\u81EA\u52A8\u56DE\u6EDA\u3002',
|
|
1582
|
+
"",
|
|
1583
|
+
"\u4E5F\u53EF\u4EE5\u7528\u8BBE\u7F6E\u9875\u7684\u201C\u9009\u5B57\u6BB5 \u2192 \u751F\u6210\u5199\u5165\u6307\u4EE4\u201D\u76F4\u63A5\u590D\u5236\u7B2C 3 \u6B65\u7684\u6307\u4EE4\u7C98\u8D34\u7ED9\u6A21\u578B\u3002"
|
|
1269
1584
|
];
|
|
1270
1585
|
return lines.join("\n");
|
|
1271
1586
|
}
|
|
1587
|
+
function buildConfigEditInstruction(desiredChanges) {
|
|
1588
|
+
const payload = JSON.stringify({ operation: "write", updates: desiredChanges }, null, 2);
|
|
1589
|
+
return `\u8BF7\u8C03\u7528 \`iterate_config\` \u5199\u5165\u4EE5\u4E0B\u914D\u7F6E\u66F4\u65B0\uFF1A
|
|
1590
|
+
|
|
1591
|
+
\`\`\`json
|
|
1592
|
+
${payload}
|
|
1593
|
+
\`\`\``;
|
|
1594
|
+
}
|
|
1595
|
+
var CONFIG_VALUE_PLACEHOLDER = "<\u7531\u6A21\u578B\u586B\u5199\uFF1A\u8BE5\u5B57\u6BB5\u7684\u65B0\u503C>";
|
|
1596
|
+
function configFieldByKey(key) {
|
|
1597
|
+
if (typeof key !== "string" || !key) return null;
|
|
1598
|
+
return CONFIG_EDIT_FIELDS.find((f) => f.key === key) ?? null;
|
|
1599
|
+
}
|
|
1600
|
+
function buildConfigFieldInstruction(key, value) {
|
|
1601
|
+
const field = configFieldByKey(key);
|
|
1602
|
+
const label = field ? field.label : key;
|
|
1603
|
+
const hint = field ? field.hint : "\u89C1 iterate.config.yaml";
|
|
1604
|
+
const updates = { [key]: value === void 0 ? CONFIG_VALUE_PLACEHOLDER : value };
|
|
1605
|
+
const payload = buildConfigEditInstruction(updates);
|
|
1606
|
+
return [
|
|
1607
|
+
`\u914D\u7F6E\u5B57\u6BB5 \xB7 ${label}\uFF08${key}\uFF09`,
|
|
1608
|
+
`\u5B57\u6BB5\u8BF4\u660E\uFF1A${hint}`,
|
|
1609
|
+
"",
|
|
1610
|
+
payload,
|
|
1611
|
+
"",
|
|
1612
|
+
'\u5199\u5165\u524D\u8BF7\u5148 `operation: "read"` \u5C55\u793A\u5F53\u524D\u503C\uFF1B\u5199\u5165\u7531\u5DE5\u5177\u81EA\u52A8\u5907\u4EFD\uFF0C\u5931\u8D25\u81EA\u52A8\u56DE\u6EDA\u3002'
|
|
1613
|
+
].join("\n");
|
|
1614
|
+
}
|
|
1272
1615
|
var VERDICT_SHORTCUTS = {
|
|
1273
1616
|
y: "keep",
|
|
1274
1617
|
Y: "keep",
|
|
@@ -1278,7 +1621,8 @@ var VERDICT_SHORTCUTS = {
|
|
|
1278
1621
|
A: "ignore"
|
|
1279
1622
|
};
|
|
1280
1623
|
function keyToVerdict(key) {
|
|
1281
|
-
|
|
1624
|
+
const verdict = VERDICT_SHORTCUTS[key];
|
|
1625
|
+
return verdict !== void 0 && TRIAGE_VERDICTS.includes(verdict) ? verdict : null;
|
|
1282
1626
|
}
|
|
1283
1627
|
function allVerdictKeys(triageState) {
|
|
1284
1628
|
const state = triageState && typeof triageState === "object" ? triageState : {};
|
|
@@ -1352,11 +1696,12 @@ function filterTimelineEntries(entries, opts) {
|
|
|
1352
1696
|
});
|
|
1353
1697
|
return filtered.slice().sort((a, b) => String(b.timestamp ?? "").localeCompare(String(a.timestamp ?? "")));
|
|
1354
1698
|
}
|
|
1355
|
-
function serializeObservatoryExport(manifest, live) {
|
|
1699
|
+
function serializeObservatoryExport(manifest, live, extra) {
|
|
1356
1700
|
const payload = {
|
|
1357
1701
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1358
1702
|
manifest: manifest && typeof manifest === "object" ? manifest : null,
|
|
1359
|
-
live: Array.isArray(live) ? live : []
|
|
1703
|
+
live: Array.isArray(live) ? live : [],
|
|
1704
|
+
...pickExportExtras(extra)
|
|
1360
1705
|
};
|
|
1361
1706
|
try {
|
|
1362
1707
|
return JSON.stringify(payload, null, 2);
|
|
@@ -1364,6 +1709,62 @@ function serializeObservatoryExport(manifest, live) {
|
|
|
1364
1709
|
return JSON.stringify({ exportedAt: payload.exportedAt, manifest: null, live: [] }, null, 2);
|
|
1365
1710
|
}
|
|
1366
1711
|
}
|
|
1712
|
+
var EXPORT_EXTRA_KEYS = ["qualityGate", "experienceBank", "defenseEvents", "report"];
|
|
1713
|
+
function pickExportExtras(extra) {
|
|
1714
|
+
const out = {};
|
|
1715
|
+
if (!extra || typeof extra !== "object") return out;
|
|
1716
|
+
for (const key of EXPORT_EXTRA_KEYS) {
|
|
1717
|
+
if (!Object.prototype.hasOwnProperty.call(extra, key)) continue;
|
|
1718
|
+
const value = extra[key];
|
|
1719
|
+
if (value === void 0) continue;
|
|
1720
|
+
out[key] = value;
|
|
1721
|
+
}
|
|
1722
|
+
return out;
|
|
1723
|
+
}
|
|
1724
|
+
function buildRoundComparison(report, validations) {
|
|
1725
|
+
const rounds = /* @__PURE__ */ new Map();
|
|
1726
|
+
const rowsFor = (round) => {
|
|
1727
|
+
const n = Number(round);
|
|
1728
|
+
const key = Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
|
|
1729
|
+
if (!rounds.has(key)) {
|
|
1730
|
+
rounds.set(key, { round: key, findings: 0, fixed: 0, severities: /* @__PURE__ */ Object.create(null), validations: [] });
|
|
1731
|
+
}
|
|
1732
|
+
return rounds.get(key);
|
|
1733
|
+
};
|
|
1734
|
+
const list = report && Array.isArray(report.findings) ? report.findings : [];
|
|
1735
|
+
for (const f of list) {
|
|
1736
|
+
if (!f || typeof f !== "object") continue;
|
|
1737
|
+
const row = rowsFor(f.round);
|
|
1738
|
+
row.findings += 1;
|
|
1739
|
+
const sev = typeof f.severity === "string" && f.severity ? f.severity : "unknown";
|
|
1740
|
+
row.severities[sev] = (row.severities[sev] || 0) + 1;
|
|
1741
|
+
if (f.status === "fixed") row.fixed += 1;
|
|
1742
|
+
}
|
|
1743
|
+
const vals = Array.isArray(validations) ? validations : [];
|
|
1744
|
+
for (const v of vals) {
|
|
1745
|
+
if (!v || typeof v !== "object") continue;
|
|
1746
|
+
const row = rowsFor(v.round);
|
|
1747
|
+
row.validations.push({
|
|
1748
|
+
command: typeof v.command === "string" ? v.command : "",
|
|
1749
|
+
exitCode: typeof v.exitCode === "number" ? v.exitCode : null,
|
|
1750
|
+
allowed: v.allowed === true
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
return [...rounds.values()].filter((r) => r.round > 0).sort((a, b) => a.round - b.round).map((r) => ({ ...r, severities: { ...r.severities } }));
|
|
1754
|
+
}
|
|
1755
|
+
function dashboardRunState(transcript) {
|
|
1756
|
+
const t = transcript && typeof transcript === "object" ? transcript : null;
|
|
1757
|
+
if (!t) return { state: "empty", round: 0, phase: "", stoppedReason: "" };
|
|
1758
|
+
const rawRound = Number(t.round);
|
|
1759
|
+
const round = Number.isFinite(rawRound) && rawRound > 0 ? Math.floor(rawRound) : 1;
|
|
1760
|
+
const stoppedReason = typeof t.stoppedReason === "string" ? t.stoppedReason.trim() : "";
|
|
1761
|
+
return {
|
|
1762
|
+
state: t.active === true ? "running" : "done",
|
|
1763
|
+
round,
|
|
1764
|
+
phase: latestPhase(t.phases),
|
|
1765
|
+
stoppedReason
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1367
1768
|
function latestPhase(phases) {
|
|
1368
1769
|
const list = Array.isArray(phases) ? phases : [];
|
|
1369
1770
|
let latest = "";
|
|
@@ -1453,6 +1854,7 @@ var ITERATE_CSS = `
|
|
|
1453
1854
|
}
|
|
1454
1855
|
.iterate-btn[data-primary] { border-color: var(--dsw-alias-brand-primary); color: var(--dsw-alias-brand-primary); }
|
|
1455
1856
|
.iterate-btn[data-copied] { border-color: var(--dsw-alias-state-success-primary); color: var(--dsw-alias-state-success-primary); }
|
|
1857
|
+
.iterate-btn[data-failed], .iterate-cmd[data-failed] { border-color: var(--dsw-alias-state-error-primary); color: var(--dsw-alias-state-error-primary); }
|
|
1456
1858
|
.iterate-payload { width: 100%; margin-top: 8px; padding: 8px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); font-family: var(--dsw-font-mono, ui-monospace, monospace); font-size: 11px; white-space: pre-wrap; }
|
|
1457
1859
|
|
|
1458
1860
|
.iterate-stats { margin: 10px 0; border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px; background: var(--dsw-alias-bg-layer-1); padding: 12px 14px; }
|
|
@@ -1637,6 +2039,16 @@ var ITERATE_CSS = `
|
|
|
1637
2039
|
.iterate-obs-block-head[data-click] { cursor: pointer; }
|
|
1638
2040
|
.iterate-obs-block-body { padding: 8px 10px; }
|
|
1639
2041
|
.iterate-obs-chip { display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px; border-radius: 6px; background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l1); font-size: 11px; color: var(--dsw-alias-label-secondary); white-space: nowrap; }
|
|
2042
|
+
/* Gap #6: validation / gate status colors (pass=green, fail=red). */
|
|
2043
|
+
.iterate-obs-chip[data-status="pass"], .iterate-obs-badge[data-status="pass"] { color: var(--dsw-alias-state-success-primary); background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent); border-color: color-mix(in srgb, var(--dsw-alias-state-success-primary) 28%, transparent); }
|
|
2044
|
+
.iterate-obs-chip[data-status="fail"], .iterate-obs-badge[data-status="fail"] { color: var(--dsw-alias-state-error-primary); background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent); border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 28%, transparent); }
|
|
2045
|
+
.iterate-obs-row[data-status="fail"] { color: var(--dsw-alias-state-error-primary); }
|
|
2046
|
+
.iterate-obs-row[data-highlight] { background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent); border-radius: 6px; padding-left: 6px; padding-right: 6px; border-bottom-color: transparent; font-weight: 600; }
|
|
2047
|
+
/* Gap #13: cross-round comparison table. */
|
|
2048
|
+
.iterate-obs-table { width: 100%; border-collapse: collapse; font-size: 11px; }
|
|
2049
|
+
.iterate-obs-table th, .iterate-obs-table td { text-align: left; padding: 5px 8px; border-bottom: 1px solid var(--dsw-alias-border-l1); white-space: nowrap; }
|
|
2050
|
+
.iterate-obs-table th { color: var(--dsw-alias-label-secondary); font-weight: 600; }
|
|
2051
|
+
.iterate-obs-table td { color: var(--dsw-alias-label-primary); }
|
|
1640
2052
|
.iterate-obs-msg { padding: 2px 0; color: var(--dsw-alias-label-secondary); line-height: 1.5; font-size: 11px; word-break: break-word; }
|
|
1641
2053
|
.iterate-obs-file { font-family: var(--dsw-font-mono, ui-monospace, monospace); font-size: 10.5px; color: var(--dsw-alias-label-secondary); word-break: break-all; }
|
|
1642
2054
|
.iterate-obs-bar { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--dsw-alias-label-secondary); flex-wrap: wrap; }
|
|
@@ -1853,11 +2265,25 @@ function TrendChart({ points }) {
|
|
|
1853
2265
|
}
|
|
1854
2266
|
function StartIterationButton() {
|
|
1855
2267
|
const [copied, setCopied] = React.useState(null);
|
|
2268
|
+
const [failed, setFailed] = React.useState(null);
|
|
2269
|
+
const timers = React.useRef([]);
|
|
2270
|
+
React.useEffect(
|
|
2271
|
+
() => () => {
|
|
2272
|
+
for (const t of timers.current) clearTimeout(t);
|
|
2273
|
+
timers.current = [];
|
|
2274
|
+
},
|
|
2275
|
+
[]
|
|
2276
|
+
);
|
|
1856
2277
|
const copy = (key, text) => {
|
|
1857
2278
|
copyText(text).then((ok) => {
|
|
1858
2279
|
if (ok) {
|
|
2280
|
+
setFailed(null);
|
|
1859
2281
|
setCopied(key);
|
|
1860
|
-
setTimeout(() => setCopied((cur) => cur === key ? null : cur), 1600);
|
|
2282
|
+
const t = window.setTimeout(() => setCopied((cur) => cur === key ? null : cur), 1600);
|
|
2283
|
+
timers.current.push(t);
|
|
2284
|
+
} else {
|
|
2285
|
+
setCopied(null);
|
|
2286
|
+
setFailed(key);
|
|
1861
2287
|
}
|
|
1862
2288
|
});
|
|
1863
2289
|
};
|
|
@@ -1866,14 +2292,15 @@ function StartIterationButton() {
|
|
|
1866
2292
|
className: "iterate-cmd",
|
|
1867
2293
|
"data-primary": primary ? "" : void 0,
|
|
1868
2294
|
"data-copied": copied === key ? "" : void 0,
|
|
2295
|
+
"data-failed": failed === key ? "" : void 0,
|
|
1869
2296
|
onClick: () => copy(key, command),
|
|
1870
2297
|
title
|
|
1871
|
-
}, copied === key ? "\u5DF2\u590D\u5236" : label);
|
|
2298
|
+
}, copied === key ? "\u5DF2\u590D\u5236" : failed === key ? "\u590D\u5236\u5931\u8D25" : label);
|
|
1872
2299
|
return React.createElement(
|
|
1873
2300
|
"span",
|
|
1874
2301
|
{ className: "iterate-dashboard-launch" },
|
|
1875
|
-
btn("start-full", "\u5B8C\u6574\u8FED\u4EE3",
|
|
1876
|
-
btn("start-review", "\u4EC5\u8BC4\u5BA1",
|
|
2302
|
+
btn("start-full", "\u5B8C\u6574\u8FED\u4EE3", START_INSTRUCTIONS.full, true, "\u590D\u5236\u542F\u52A8\u6307\u4EE4\uFF1A\u5B8C\u6574\u300C\u5BA1\u67E5 \u2192 \u4FEE\u590D \u2192 \u9A8C\u8BC1 \u2192 \u590D\u76D8\u300D\u95ED\u73AF\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\u5373\u53EF\uFF09"),
|
|
2303
|
+
btn("start-review", "\u4EC5\u8BC4\u5BA1", START_INSTRUCTIONS.reviewOnly, false, "\u590D\u5236\u542F\u52A8\u6307\u4EE4\uFF1A\u53EA\u5BA1\u67E5\u4E0D\u4FEE\u6539\uFF08dry-run\uFF09\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\u5373\u53EF\uFF09")
|
|
1877
2304
|
);
|
|
1878
2305
|
}
|
|
1879
2306
|
function ConvergenceDashboard(props) {
|
|
@@ -1888,9 +2315,36 @@ function ConvergenceDashboard(props) {
|
|
|
1888
2315
|
setPulseKey((k) => k + 1);
|
|
1889
2316
|
}, [report && hashReport(report) + ":" + getCurrentRound(report)]);
|
|
1890
2317
|
if (!report) {
|
|
2318
|
+
const runState = dashboardRunState(latestTranscript(session));
|
|
2319
|
+
if (runState.state === "running") {
|
|
2320
|
+
return React.createElement(
|
|
2321
|
+
"div",
|
|
2322
|
+
{ "data-iterate-root": "", "data-iterate": "dashboard", "data-run-state": "running", className: "iterate-dashboard iterate-dashboard-empty" },
|
|
2323
|
+
React.createElement("span", { className: "iterate-round-badge", "data-live": "" }, "\u8FD0\u884C\u4E2D"),
|
|
2324
|
+
React.createElement(
|
|
2325
|
+
"span",
|
|
2326
|
+
{ className: "iterate-empty-hint" },
|
|
2327
|
+
`\u7B2C ${String(runState.round)} \u8F6E\u8FDB\u884C\u4E2D${runState.phase ? ` \xB7 \u9636\u6BB5\u300C${runState.phase}\u300D` : ""}\u2014\u2014\u9996\u8F6E\u6C47\u603B\u62A5\u544A\u843D\u5730\u540E\uFF0C\u8FD9\u91CC\u4F1A\u663E\u793A\u6536\u655B\u8FDB\u5EA6\u4E0E\u53D1\u73B0\u7EDF\u8BA1\u3002`
|
|
2328
|
+
)
|
|
2329
|
+
// No start button while a run is active.
|
|
2330
|
+
);
|
|
2331
|
+
}
|
|
2332
|
+
if (runState.state === "done") {
|
|
2333
|
+
return React.createElement(
|
|
2334
|
+
"div",
|
|
2335
|
+
{ "data-iterate-root": "", "data-iterate": "dashboard", "data-run-state": "done", className: "iterate-dashboard iterate-dashboard-empty" },
|
|
2336
|
+
React.createElement("span", { className: "iterate-round-badge" }, "\u5DF2\u7ED3\u675F"),
|
|
2337
|
+
React.createElement(
|
|
2338
|
+
"span",
|
|
2339
|
+
{ className: "iterate-empty-hint" },
|
|
2340
|
+
`\u4E0A\u6B21\u8FED\u4EE3\u5728\u7B2C ${String(runState.round)} \u8F6E\u540E\u505C\u6B62${runState.stoppedReason ? `\uFF08${stoppedReasonLabel(runState.stoppedReason)}\uFF09` : ""}\uFF0C\u4F46\u6CA1\u6709\u53EF\u5C55\u793A\u7684\u6C47\u603B\u62A5\u544A\u3002`
|
|
2341
|
+
),
|
|
2342
|
+
React.createElement(StartIterationButton, null)
|
|
2343
|
+
);
|
|
2344
|
+
}
|
|
1891
2345
|
return React.createElement(
|
|
1892
2346
|
"div",
|
|
1893
|
-
{ "data-iterate-root": "", "data-iterate": "dashboard", className: "iterate-dashboard iterate-dashboard-empty" },
|
|
2347
|
+
{ "data-iterate-root": "", "data-iterate": "dashboard", "data-run-state": "empty", className: "iterate-dashboard iterate-dashboard-empty" },
|
|
1894
2348
|
React.createElement("span", { className: "iterate-round-badge" }, "iterate"),
|
|
1895
2349
|
React.createElement("span", { className: "iterate-empty-hint" }, "\u8FD0\u884C\u4E00\u6B21\u8BC4\u5BA1\u540E\uFF0C\u8FD9\u91CC\u4F1A\u663E\u793A\u6536\u655B\u8FDB\u5EA6\u4E0E\u53D1\u73B0\u7EDF\u8BA1\u3002"),
|
|
1896
2350
|
React.createElement(StartIterationButton, null)
|
|
@@ -2116,6 +2570,25 @@ function TriagePanel(props) {
|
|
|
2116
2570
|
const [selected, setSelected] = React.useState(null);
|
|
2117
2571
|
const [selectAll, setSelectAll] = React.useState(false);
|
|
2118
2572
|
const [cmdCopied, setCmdCopied] = React.useState(null);
|
|
2573
|
+
const [cmdFailed, setCmdFailed] = React.useState(null);
|
|
2574
|
+
const cmdTimers = React.useRef([]);
|
|
2575
|
+
React.useEffect(() => () => {
|
|
2576
|
+
for (const t of cmdTimers.current) clearTimeout(t);
|
|
2577
|
+
cmdTimers.current = [];
|
|
2578
|
+
}, []);
|
|
2579
|
+
const copyWithFeedback = (text, key) => {
|
|
2580
|
+
copyText(text).then((ok) => {
|
|
2581
|
+
if (ok) {
|
|
2582
|
+
setCmdFailed(null);
|
|
2583
|
+
setCmdCopied(key);
|
|
2584
|
+
cmdTimers.current.push(window.setTimeout(() => setCmdCopied((cur) => cur === key ? null : cur), 1600));
|
|
2585
|
+
} else {
|
|
2586
|
+
setCmdCopied(null);
|
|
2587
|
+
setCmdFailed(key);
|
|
2588
|
+
setPayload(text);
|
|
2589
|
+
}
|
|
2590
|
+
});
|
|
2591
|
+
};
|
|
2119
2592
|
React.useEffect(() => {
|
|
2120
2593
|
if (storage) storage.set(storageKey, JSON.stringify(verdicts));
|
|
2121
2594
|
}, [storageKey, verdicts]);
|
|
@@ -2180,55 +2653,26 @@ function TriagePanel(props) {
|
|
|
2180
2653
|
const ignoredCount = ignored.length;
|
|
2181
2654
|
const counts = countVerdicts(verdicts);
|
|
2182
2655
|
const doApproveArchitecturalFix = () => {
|
|
2183
|
-
const cmd =
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
setCmdCopied("approve-arch");
|
|
2187
|
-
setTimeout(() => setCmdCopied(null), 1600);
|
|
2188
|
-
}
|
|
2189
|
-
});
|
|
2656
|
+
const cmd = buildArchitecturalFixInstruction(findings);
|
|
2657
|
+
if (!cmd) return;
|
|
2658
|
+
copyWithFeedback(cmd, "approve-arch");
|
|
2190
2659
|
};
|
|
2191
2660
|
const doTriggerNewRound = () => {
|
|
2192
2661
|
const cmd = "\u8BF7\u7EE7\u7EED\u6267\u884C\u4E0B\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5";
|
|
2193
|
-
|
|
2194
|
-
if (ok) {
|
|
2195
|
-
setCmdCopied("new-round");
|
|
2196
|
-
setTimeout(() => setCmdCopied(null), 1600);
|
|
2197
|
-
}
|
|
2198
|
-
});
|
|
2662
|
+
copyWithFeedback(cmd, "new-round");
|
|
2199
2663
|
};
|
|
2200
2664
|
const doRollbackToCheckpoint = () => {
|
|
2201
2665
|
const cmd = "\u8BF7\u56DE\u6EDA\u4E0A\u4E00\u8F6E\u8FED\u4EE3\u7684\u4FEE\u590D\uFF1A\u8C03\u7528 `iterate_history` \u67E5\u770B\u672C\u8F6E fix id\uFF0C\u518D\u7528 `iterate_rollback` \u9010\u4E2A\u64A4\u9500\u8FD9\u4E9B\u4FEE\u590D";
|
|
2202
|
-
|
|
2203
|
-
if (ok) {
|
|
2204
|
-
setCmdCopied("rollback");
|
|
2205
|
-
setTimeout(() => setCmdCopied(null), 1600);
|
|
2206
|
-
}
|
|
2207
|
-
});
|
|
2666
|
+
copyWithFeedback(cmd, "rollback");
|
|
2208
2667
|
};
|
|
2209
2668
|
const doAssignFindings = () => {
|
|
2210
2669
|
const targetAll = selectAll;
|
|
2211
2670
|
const scopeIndices = targetAll ? allIndices : indices;
|
|
2212
|
-
const scopeFindings = findings.filter((f, i) => scopeIndices.includes(i))
|
|
2213
|
-
file: String(f.file || ""),
|
|
2214
|
-
...typeof f.line === "number" && f.line > 0 ? { line: f.line } : {},
|
|
2215
|
-
dimension: String(f.dimension || ""),
|
|
2216
|
-
severity: String(f.severity || ""),
|
|
2217
|
-
summary: String(f.summary || ""),
|
|
2218
|
-
...f.suggested_fix ? { suggested_fix: String(f.suggested_fix) } : {}
|
|
2219
|
-
}));
|
|
2671
|
+
const scopeFindings = findings.filter((f, i) => scopeIndices.includes(i));
|
|
2220
2672
|
if (scopeFindings.length === 0) return;
|
|
2221
|
-
const cmd =
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
${JSON.stringify(scopeFindings, null, 2)}
|
|
2225
|
-
\`\`\``;
|
|
2226
|
-
copyText(cmd).then((ok) => {
|
|
2227
|
-
if (ok) {
|
|
2228
|
-
setCmdCopied("assign");
|
|
2229
|
-
setTimeout(() => setCmdCopied(null), 1600);
|
|
2230
|
-
}
|
|
2231
|
-
});
|
|
2673
|
+
const cmd = buildAssignFixesInstruction(scopeFindings);
|
|
2674
|
+
if (!cmd) return;
|
|
2675
|
+
copyWithFeedback(cmd, "assign");
|
|
2232
2676
|
};
|
|
2233
2677
|
const doCopyYaml = () => {
|
|
2234
2678
|
const yaml = toKnownIntentionalYaml(ignored);
|
|
@@ -2236,19 +2680,31 @@ ${JSON.stringify(scopeFindings, null, 2)}
|
|
|
2236
2680
|
copyText(yaml).then((ok) => {
|
|
2237
2681
|
if (ok) {
|
|
2238
2682
|
setCopied(true);
|
|
2239
|
-
setTimeout(() => setCopied(false), 1600);
|
|
2683
|
+
cmdTimers.current.push(window.setTimeout(() => setCopied(false), 1600));
|
|
2240
2684
|
} else {
|
|
2241
2685
|
setPayload(yaml);
|
|
2686
|
+
setCmdFailed("yaml");
|
|
2242
2687
|
}
|
|
2243
2688
|
});
|
|
2244
2689
|
};
|
|
2245
2690
|
const doBuildInstruction = () => {
|
|
2246
2691
|
const text = buildApplyInstruction(ignored);
|
|
2692
|
+
if (!text) return;
|
|
2247
2693
|
setPayload(text);
|
|
2248
|
-
|
|
2694
|
+
copyWithFeedback(text, "apply");
|
|
2249
2695
|
};
|
|
2250
|
-
const
|
|
2251
|
-
const
|
|
2696
|
+
const doReadback = () => {
|
|
2697
|
+
const text = buildTriageReadbackInstruction();
|
|
2698
|
+
setPayload(text);
|
|
2699
|
+
copyWithFeedback(text, "readback");
|
|
2700
|
+
};
|
|
2701
|
+
const rowPairs = [];
|
|
2702
|
+
for (let i = 0; i < filtered.length; i++) {
|
|
2703
|
+
const idx = indices[i];
|
|
2704
|
+
const f = filtered[i];
|
|
2705
|
+
if (typeof idx === "number" && f) rowPairs.push({ index: idx, finding: f });
|
|
2706
|
+
}
|
|
2707
|
+
const rows = rowPairs.map(({ index, finding }) => {
|
|
2252
2708
|
const severity = finding.severity || "low";
|
|
2253
2709
|
const verdict = verdicts[String(index)] || "keep";
|
|
2254
2710
|
const isSelected = selected === index;
|
|
@@ -2368,22 +2824,35 @@ ${JSON.stringify(scopeFindings, null, 2)}
|
|
|
2368
2824
|
React.createElement("span", {}, `y ${counts.keep} \xB7 n ${counts.skip} \xB7 a ${counts.ignore} \xB7 \u5F85\u5199\u56DE known_intentional\uFF1A${ignoredCount} \u6761`),
|
|
2369
2825
|
React.createElement(
|
|
2370
2826
|
"span",
|
|
2371
|
-
{ style: { display: "flex", gap: 6 } },
|
|
2827
|
+
{ style: { display: "flex", gap: 6, flexWrap: "wrap" } },
|
|
2828
|
+
// Gap #7: the apply instruction (safe path) is PRIMARY; the raw YAML
|
|
2829
|
+
// is demoted to an explicit "just the snippet" escape hatch.
|
|
2372
2830
|
React.createElement("button", {
|
|
2373
2831
|
className: "iterate-btn",
|
|
2374
2832
|
"data-primary": "",
|
|
2833
|
+
"data-copied": cmdCopied === "apply" ? "" : void 0,
|
|
2834
|
+
"data-failed": cmdFailed === "apply" ? "" : void 0,
|
|
2835
|
+
onClick: doBuildInstruction,
|
|
2836
|
+
disabled: ignoredCount === 0,
|
|
2837
|
+
title: ignoredCount === 0 ? "\u5F53\u524D\u6CA1\u6709\u6807\u8BB0\u4E3A\u300C\u5DF2\u77E5\u6709\u610F\u300D\u7684 finding" : "\u590D\u5236\u5E94\u7528\u6307\u4EE4\uFF1A\u7C98\u8D34\u540E\u6A21\u578B\u4F1A\u8C03\u7528 iterate_triage apply\uFF08\u6821\u9A8C\u3001\u53BB\u91CD\u3001\u5199\u5165\u524D\u5907\u4EFD\u3001\u5931\u8D25\u81EA\u52A8\u56DE\u6EDA\uFF09"
|
|
2838
|
+
}, cmdCopied === "apply" ? "\u5DF2\u590D\u5236" : cmdFailed === "apply" ? "\u590D\u5236\u5931\u8D25" : `\u751F\u6210\u5E94\u7528\u6307\u4EE4${ignoredCount > 0 ? `\uFF08${ignoredCount}\uFF09` : ""}`),
|
|
2839
|
+
React.createElement("button", {
|
|
2840
|
+
className: "iterate-btn",
|
|
2375
2841
|
"data-copied": copied ? "" : void 0,
|
|
2842
|
+
"data-failed": cmdFailed === "yaml" ? "" : void 0,
|
|
2376
2843
|
onClick: doCopyYaml,
|
|
2377
2844
|
disabled: ignoredCount === 0,
|
|
2378
|
-
title: ignoredCount === 0 ? "\u5F53\u524D\u6CA1\u6709\u6807\u8BB0\u4E3A\u300C\u5DF2\u77E5\u6709\u610F\u300D\u7684 finding" : "\u590D\u5236 known_intentional YAML"
|
|
2379
|
-
}, copied ? "\u5DF2\u590D\u5236" :
|
|
2845
|
+
title: ignoredCount === 0 ? "\u5F53\u524D\u6CA1\u6709\u6807\u8BB0\u4E3A\u300C\u5DF2\u77E5\u6709\u610F\u300D\u7684 finding" : "\u53EA\u590D\u5236 known_intentional YAML \u7247\u6BB5\uFF08\u9700\u81EA\u884C\u7C98\u5230 iterate.config.yaml \u7684 personalization.known_intentional \u4E0B\uFF09"
|
|
2846
|
+
}, copied ? "\u5DF2\u590D\u5236" : cmdFailed === "yaml" ? "\u590D\u5236\u5931\u8D25" : "\u4EC5\u590D\u5236 YAML \u7247\u6BB5"),
|
|
2380
2847
|
React.createElement("button", {
|
|
2381
2848
|
className: "iterate-btn",
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2849
|
+
"data-copied": cmdCopied === "readback" ? "" : void 0,
|
|
2850
|
+
"data-failed": cmdFailed === "readback" ? "" : void 0,
|
|
2851
|
+
onClick: doReadback,
|
|
2852
|
+
title: "\u590D\u5236 iterate_triage list \u6307\u4EE4\uFF0C\u6838\u5BF9\u5DF2\u7ECF\u5199\u5165\u7684 known_intentional \u6761\u76EE\uFF08\u5199\u56DE\u95ED\u73AF\u786E\u8BA4\uFF09"
|
|
2853
|
+
}, cmdCopied === "readback" ? "\u5DF2\u590D\u5236" : cmdFailed === "readback" ? "\u590D\u5236\u5931\u8D25" : "\u56DE\u8BFB\u5DF2\u5199\u5165\u6761\u76EE")
|
|
2854
|
+
),
|
|
2855
|
+
cmdCopied === "apply" ? React.createElement("span", { className: "iterate-triage-hint", role: "status" }, "\u5DF2\u590D\u5236\u5E94\u7528\u6307\u4EE4\u2014\u2014\u7C98\u8D34\u56DE\u4F1A\u8BDD\uFF0C\u6A21\u578B\u5C06\u8C03\u7528 iterate_triage apply \u5E76\u56DE\u62A5\u5199\u5165\u6761\u6570\u3002") : cmdCopied === "readback" ? React.createElement("span", { className: "iterate-triage-hint", role: "status" }, "\u5DF2\u590D\u5236\u56DE\u8BFB\u6307\u4EE4\u2014\u2014\u7C98\u8D34\u56DE\u4F1A\u8BDD\u53EF\u5217\u51FA\u5F53\u524D\u5DF2\u5199\u5165\u7684 known_intentional\u3002") : null
|
|
2387
2856
|
),
|
|
2388
2857
|
// v3.0: Command buttons for native actions (§8)
|
|
2389
2858
|
React.createElement(
|
|
@@ -2392,35 +2861,39 @@ ${JSON.stringify(scopeFindings, null, 2)}
|
|
|
2392
2861
|
React.createElement("span", { style: { fontSize: 11, fontWeight: 600, color: "var(--dsw-alias-label-secondary)" } }, "\u6307\u6325\u64CD\u4F5C"),
|
|
2393
2862
|
React.createElement(
|
|
2394
2863
|
"span",
|
|
2395
|
-
{ style: { display: "flex", gap: 6 } },
|
|
2864
|
+
{ style: { display: "flex", gap: 6, flexWrap: "wrap" } },
|
|
2396
2865
|
React.createElement("button", {
|
|
2397
2866
|
className: "iterate-cmd",
|
|
2398
2867
|
"data-primary": "",
|
|
2399
2868
|
"data-copied": cmdCopied === "approve-arch" ? "" : void 0,
|
|
2869
|
+
"data-failed": cmdFailed === "approve-arch" ? "" : void 0,
|
|
2400
2870
|
onClick: doApproveArchitecturalFix,
|
|
2401
|
-
title: "\u6279\u51C6\u67B6\u6784\u4FEE\u590D\uFF08\u590D\u5236\u6307\u4EE4\u6587\u672C\uFF09"
|
|
2402
|
-
}, cmdCopied === "approve-arch" ? "\u5DF2\u590D\u5236" : "\u6279\u51C6\u67B6\u6784\u4FEE\u590D"),
|
|
2871
|
+
title: "\u6279\u51C6\u67B6\u6784\u4FEE\u590D\uFF08\u590D\u5236\u6307\u4EE4\u6587\u672C\uFF1Aiterate_fix force:true\uFF0C\u542B\u5168\u90E8\u5FC5\u586B\u53C2\u6570\uFF09"
|
|
2872
|
+
}, cmdCopied === "approve-arch" ? "\u5DF2\u590D\u5236" : cmdFailed === "approve-arch" ? "\u590D\u5236\u5931\u8D25" : "\u6279\u51C6\u67B6\u6784\u4FEE\u590D"),
|
|
2403
2873
|
React.createElement("button", {
|
|
2404
2874
|
className: "iterate-cmd",
|
|
2405
2875
|
"data-copied": cmdCopied === "new-round" ? "" : void 0,
|
|
2876
|
+
"data-failed": cmdFailed === "new-round" ? "" : void 0,
|
|
2406
2877
|
onClick: doTriggerNewRound,
|
|
2407
2878
|
title: "\u89E6\u53D1\u65B0\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5"
|
|
2408
|
-
}, cmdCopied === "new-round" ? "\u5DF2\u590D\u5236" : "\u89E6\u53D1\u65B0\u4E00\u8F6E"),
|
|
2879
|
+
}, cmdCopied === "new-round" ? "\u5DF2\u590D\u5236" : cmdFailed === "new-round" ? "\u590D\u5236\u5931\u8D25" : "\u89E6\u53D1\u65B0\u4E00\u8F6E"),
|
|
2409
2880
|
React.createElement("button", {
|
|
2410
2881
|
className: "iterate-cmd",
|
|
2411
2882
|
"data-primary": "",
|
|
2412
2883
|
"data-copied": cmdCopied === "assign" ? "" : void 0,
|
|
2884
|
+
"data-failed": cmdFailed === "assign" ? "" : void 0,
|
|
2413
2885
|
onClick: doAssignFindings,
|
|
2414
2886
|
disabled: (selectAll ? allIndices : indices).length === 0,
|
|
2415
|
-
title: selectAll ? `\u6307\u6D3E\u5168\u90E8 ${allIndices.length} \u4E2A findings \u4FEE\u590D` : `\u6307\u6D3E\u5F53\u524D\u53EF\u89C1 ${indices.length} \u4E2A findings \u4FEE\u590D`
|
|
2416
|
-
}, cmdCopied === "assign" ? "\u5DF2\u590D\u5236" : "\u6307\u6D3E\u4FEE\u590D"),
|
|
2887
|
+
title: selectAll ? `\u6307\u6D3E\u5168\u90E8 ${allIndices.length} \u4E2A findings \u4FEE\u590D\uFF08\u9010\u4E2A iterate_fix\uFF09` : `\u6307\u6D3E\u5F53\u524D\u53EF\u89C1 ${indices.length} \u4E2A findings \u4FEE\u590D\uFF08\u9010\u4E2A iterate_fix\uFF09`
|
|
2888
|
+
}, cmdCopied === "assign" ? "\u5DF2\u590D\u5236" : cmdFailed === "assign" ? "\u590D\u5236\u5931\u8D25" : "\u6307\u6D3E\u4FEE\u590D"),
|
|
2417
2889
|
React.createElement("button", {
|
|
2418
2890
|
className: "iterate-cmd",
|
|
2419
2891
|
"data-danger": "",
|
|
2420
2892
|
"data-copied": cmdCopied === "rollback" ? "" : void 0,
|
|
2893
|
+
"data-failed": cmdFailed === "rollback" ? "" : void 0,
|
|
2421
2894
|
onClick: doRollbackToCheckpoint,
|
|
2422
2895
|
title: "\u56DE\u6EDA\u5230\u4E0A\u4E00\u4E2A\u68C0\u67E5\u70B9"
|
|
2423
|
-
}, cmdCopied === "rollback" ? "\u5DF2\u590D\u5236" : "\u56DE\u6EDA\u68C0\u67E5\u70B9")
|
|
2896
|
+
}, cmdCopied === "rollback" ? "\u5DF2\u590D\u5236" : cmdFailed === "rollback" ? "\u590D\u5236\u5931\u8D25" : "\u56DE\u6EDA\u68C0\u67E5\u70B9")
|
|
2424
2897
|
)
|
|
2425
2898
|
),
|
|
2426
2899
|
payload ? React.createElement("div", { className: "iterate-payload" }, payload) : null
|
|
@@ -2522,33 +2995,57 @@ function SettingsPanel(_props) {
|
|
|
2522
2995
|
const [showStatus, setShowStatus] = React.useState(false);
|
|
2523
2996
|
const [confirming, setConfirming] = React.useState(false);
|
|
2524
2997
|
const [clearedInfo, setClearedInfo] = React.useState(null);
|
|
2998
|
+
const [cfgField, setCfgField] = React.useState(
|
|
2999
|
+
() => CONFIG_EDIT_FIELDS.some((f) => f.key === "validation.commands") ? "validation.commands" : CONFIG_EDIT_FIELDS[0]?.key ?? "goal"
|
|
3000
|
+
);
|
|
3001
|
+
const [cfgCopied, setCfgCopied] = React.useState(false);
|
|
3002
|
+
const [cfgFailed, setCfgFailed] = React.useState(false);
|
|
3003
|
+
const [cfgText, setCfgText] = React.useState(null);
|
|
3004
|
+
const [pullCopied, setPullCopied] = React.useState(false);
|
|
3005
|
+
const [copyError, setCopyError] = React.useState(null);
|
|
2525
3006
|
const guide = buildConfigEditGuide();
|
|
2526
3007
|
const statusGuide = buildRuntimeStatusGuide();
|
|
3008
|
+
const cfgTimers = React.useRef([]);
|
|
3009
|
+
React.useEffect(() => () => {
|
|
3010
|
+
for (const t of cfgTimers.current) clearTimeout(t);
|
|
3011
|
+
cfgTimers.current = [];
|
|
3012
|
+
}, []);
|
|
2527
3013
|
const toggleTheme = () => {
|
|
2528
3014
|
const next = !enabled;
|
|
2529
3015
|
setEnabled(next);
|
|
2530
3016
|
setThemeEnabled(next);
|
|
2531
3017
|
};
|
|
2532
3018
|
const flashCopied = (slot) => {
|
|
2533
|
-
const setter = slot === "guide" ? setGuideCopied : setStatusCopied;
|
|
3019
|
+
const setter = slot === "guide" ? setGuideCopied : slot === "status" ? setStatusCopied : setPullCopied;
|
|
2534
3020
|
setter(true);
|
|
2535
|
-
setTimeout(() => setter(false), 1600);
|
|
3021
|
+
cfgTimers.current.push(window.setTimeout(() => setter(false), 1600));
|
|
2536
3022
|
};
|
|
2537
3023
|
const doCopy = (text, slot) => {
|
|
2538
3024
|
copyText(text).then((ok) => {
|
|
3025
|
+
setCopyError(ok ? null : slot);
|
|
2539
3026
|
if (ok) flashCopied(slot);
|
|
2540
3027
|
});
|
|
2541
3028
|
};
|
|
3029
|
+
const doBuildConfigInstruction = () => {
|
|
3030
|
+
const text = buildConfigFieldInstruction(cfgField);
|
|
3031
|
+
setCfgText(text);
|
|
3032
|
+
copyText(text).then((ok) => {
|
|
3033
|
+
setCfgFailed(!ok);
|
|
3034
|
+
setCfgCopied(ok);
|
|
3035
|
+
cfgTimers.current.push(window.setTimeout(() => setCfgCopied(false), 1600));
|
|
3036
|
+
if (!ok) return;
|
|
3037
|
+
});
|
|
3038
|
+
};
|
|
2542
3039
|
const requestClear = () => {
|
|
2543
3040
|
if (confirming) {
|
|
2544
3041
|
const count = removeStorageByPrefix(TRIAGE_STORAGE_PREFIX);
|
|
2545
3042
|
setClearedInfo(count);
|
|
2546
3043
|
setConfirming(false);
|
|
2547
|
-
setTimeout(() => setClearedInfo(null), 3e3);
|
|
3044
|
+
cfgTimers.current.push(window.setTimeout(() => setClearedInfo(null), 3e3));
|
|
2548
3045
|
return;
|
|
2549
3046
|
}
|
|
2550
3047
|
setConfirming(true);
|
|
2551
|
-
setTimeout(() => setConfirming(false), 3e3);
|
|
3048
|
+
cfgTimers.current.push(window.setTimeout(() => setConfirming(false), 3e3));
|
|
2552
3049
|
};
|
|
2553
3050
|
const clearButton = clearedInfo !== null ? React.createElement("button", { className: "iterate-btn", "data-copied": "", disabled: true }, `\u5DF2\u6E05\u9664 ${clearedInfo} \u6761`) : React.createElement("button", {
|
|
2554
3051
|
className: "iterate-btn",
|
|
@@ -2614,7 +3111,7 @@ function SettingsPanel(_props) {
|
|
|
2614
3111
|
"div",
|
|
2615
3112
|
{},
|
|
2616
3113
|
React.createElement("div", { className: "iterate-settings-title" }, "\u914D\u7F6E\u7BA1\u7406"),
|
|
2617
|
-
React.createElement("div", { className: "iterate-settings-desc" }, "\u76EE\u6807 / \u7EF4\u5EA6 / \u6700\u5927\u8F6E\u6570\u5199\u5728\u9879\u76EE\u7684 iterate.config.yaml\uFF0C\u590D\u5236\u6307\u5F15\u53EF\u8BA9\u6A21\u578B\u6309\u9700\u8C03\u6574\u3002")
|
|
3114
|
+
React.createElement("div", { className: "iterate-settings-desc" }, "\u76EE\u6807 / \u7EF4\u5EA6 / \u6700\u5927\u8F6E\u6570 / validation.commands \u5199\u5728\u9879\u76EE\u7684 iterate.config.yaml\uFF0C\u590D\u5236\u6307\u5F15\u53EF\u8BA9\u6A21\u578B\u6309\u9700\u8C03\u6574\u3002")
|
|
2618
3115
|
),
|
|
2619
3116
|
React.createElement(
|
|
2620
3117
|
"div",
|
|
@@ -2623,6 +3120,50 @@ function SettingsPanel(_props) {
|
|
|
2623
3120
|
React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => setShowGuide((v) => !v) }, showGuide ? "\u6536\u8D77" : "\u5C55\u5F00")
|
|
2624
3121
|
)
|
|
2625
3122
|
),
|
|
3123
|
+
// Gap #8: wire up the previously-dead `buildConfigFieldInstruction` — pick
|
|
3124
|
+
// a field, generate the `iterate_config` write payload for it, paste it to
|
|
3125
|
+
// the model. `validation.commands` is the default selection.
|
|
3126
|
+
React.createElement(
|
|
3127
|
+
"div",
|
|
3128
|
+
{ className: "iterate-guide-bar", style: { marginTop: 8, flexWrap: "wrap" } },
|
|
3129
|
+
React.createElement(
|
|
3130
|
+
"select",
|
|
3131
|
+
{
|
|
3132
|
+
className: "iterate-filter-select",
|
|
3133
|
+
value: cfgField,
|
|
3134
|
+
"aria-label": "\u9009\u62E9\u8981\u4FEE\u6539\u7684\u914D\u7F6E\u5B57\u6BB5",
|
|
3135
|
+
onChange: (e) => setCfgField(e.target.value)
|
|
3136
|
+
},
|
|
3137
|
+
...CONFIG_EDIT_FIELDS.map(
|
|
3138
|
+
(f) => React.createElement("option", { key: f.key, value: f.key }, `${f.label}\uFF08${f.key}\uFF09`)
|
|
3139
|
+
)
|
|
3140
|
+
),
|
|
3141
|
+
React.createElement(
|
|
3142
|
+
"span",
|
|
3143
|
+
{ className: "iterate-settings-desc" },
|
|
3144
|
+
configFieldByKey(cfgField)?.hint ?? ""
|
|
3145
|
+
),
|
|
3146
|
+
React.createElement("button", {
|
|
3147
|
+
className: "iterate-btn",
|
|
3148
|
+
"data-primary": "",
|
|
3149
|
+
"data-copied": cfgCopied ? "" : void 0,
|
|
3150
|
+
"data-failed": cfgFailed ? "" : void 0,
|
|
3151
|
+
onClick: doBuildConfigInstruction,
|
|
3152
|
+
title: "\u751F\u6210\u5E76\u590D\u5236\u8BE5\u5B57\u6BB5\u7684 iterate_config \u5199\u5165\u6307\u4EE4\uFF08\u5199\u5165\u524D\u81EA\u52A8\u5907\u4EFD\u3001\u5931\u8D25\u81EA\u52A8\u56DE\u6EDA\uFF09"
|
|
3153
|
+
}, cfgCopied ? "\u5DF2\u590D\u5236" : cfgFailed ? "\u590D\u5236\u5931\u8D25" : "\u751F\u6210\u5199\u5165\u6307\u4EE4"),
|
|
3154
|
+
cfgFailed ? React.createElement("span", { className: "iterate-settings-desc" }, "\u526A\u8D34\u677F\u88AB\u62D2\u7EDD\u2014\u2014\u6307\u4EE4\u5DF2\u5C55\u793A\u5728\u4E0B\u65B9\uFF0C\u8BF7\u624B\u52A8\u590D\u5236\u3002") : null
|
|
3155
|
+
),
|
|
3156
|
+
cfgText ? React.createElement(
|
|
3157
|
+
"div",
|
|
3158
|
+
{ className: "iterate-guide" },
|
|
3159
|
+
React.createElement(
|
|
3160
|
+
"div",
|
|
3161
|
+
{ className: "iterate-guide-bar" },
|
|
3162
|
+
React.createElement("span", {}, "\u5199\u5165\u6307\u4EE4\uFF08\u7C98\u8D34\u7ED9\u6A21\u578B\uFF09"),
|
|
3163
|
+
React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => doCopy(cfgText, "guide") }, "\u590D\u5236")
|
|
3164
|
+
),
|
|
3165
|
+
React.createElement("div", { className: "iterate-guide-body" }, cfgText)
|
|
3166
|
+
) : null,
|
|
2626
3167
|
showGuide ? React.createElement(
|
|
2627
3168
|
"div",
|
|
2628
3169
|
{ className: "iterate-guide" },
|
|
@@ -2651,6 +3192,14 @@ function SettingsPanel(_props) {
|
|
|
2651
3192
|
"div",
|
|
2652
3193
|
{ className: "iterate-scard-actions" },
|
|
2653
3194
|
React.createElement("button", { className: "iterate-btn", "data-primary": "", "data-copied": statusCopied ? "" : void 0, onClick: () => doCopy(statusGuide, "status") }, statusCopied ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u6307\u5F15"),
|
|
3195
|
+
// Gap #4: RUNTIME_ARTIFACTS stops being documentation-only — this
|
|
3196
|
+
// copies the instruction that makes the model echo those artifacts
|
|
3197
|
+
// into the session so the observatory panels can read them.
|
|
3198
|
+
React.createElement("button", {
|
|
3199
|
+
className: "iterate-btn",
|
|
3200
|
+
"data-copied": pullCopied ? "" : void 0,
|
|
3201
|
+
onClick: () => doCopy(buildDiskSnapshotInstruction(), "pull")
|
|
3202
|
+
}, pullCopied ? "\u5DF2\u590D\u5236" : "\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167"),
|
|
2654
3203
|
React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => setShowStatus((v) => !v) }, showStatus ? "\u6536\u8D77" : "\u5C55\u5F00")
|
|
2655
3204
|
)
|
|
2656
3205
|
),
|
|
@@ -2691,6 +3240,15 @@ function SettingsPanel(_props) {
|
|
|
2691
3240
|
{ "data-iterate-root": "", "data-iterate": "settings", className: "iterate-settings" },
|
|
2692
3241
|
React.createElement("div", { className: "iterate-settings-title", style: { fontSize: 15, fontWeight: 700 } }, "iterate \u8BBE\u7F6E"),
|
|
2693
3242
|
banner,
|
|
3243
|
+
copyError ? React.createElement(
|
|
3244
|
+
"div",
|
|
3245
|
+
{ key: "copy-err", className: "iterate-scard", role: "alert" },
|
|
3246
|
+
React.createElement(
|
|
3247
|
+
"div",
|
|
3248
|
+
{ className: "iterate-settings-desc" },
|
|
3249
|
+
"\u526A\u8D34\u677F\u5199\u5165\u88AB\u62D2\u7EDD\uFF0C\u590D\u5236\u672A\u751F\u6548\u2014\u2014\u8BF7\u5C55\u5F00\u5BF9\u5E94\u533A\u5757\u624B\u52A8\u9009\u4E2D\u6587\u672C\u590D\u5236\u3002"
|
|
3250
|
+
)
|
|
3251
|
+
) : null,
|
|
2694
3252
|
themeCard,
|
|
2695
3253
|
dataCard,
|
|
2696
3254
|
guideCard,
|
|
@@ -2698,19 +3256,37 @@ function SettingsPanel(_props) {
|
|
|
2698
3256
|
);
|
|
2699
3257
|
}
|
|
2700
3258
|
var OBS_TABS = [
|
|
2701
|
-
{ key: "live", label: "\u5B9E\u65F6\u6D3B\u52A8\u6D41" },
|
|
2702
|
-
{ key: "f1", label: "\u5BA1\u67E5\u7EBF\u7A0B" },
|
|
2703
|
-
{ key: "f2", label: "\u6536\u655B\u8D8B\u52BF" },
|
|
2704
|
-
{ key: "f3", label: "\u53D1\u73B0\u5B9A\u4F4D" },
|
|
2705
|
-
{ key: "f4", label: "\u4FEE\u590D\u4E0E\u56DE\u6EDA" },
|
|
2706
|
-
{ key: "f5", label: "\u65AD\u70B9\u6062\u590D" },
|
|
2707
|
-
{ key: "f6", label: "\u8FD0\u884C\u63A7\u5236\u53F0" },
|
|
2708
|
-
{ key: "f7", label: "\u51B3\u7B56\u65F6\u95F4\u7EBF" },
|
|
3259
|
+
{ key: "live", label: "\u5B9E\u65F6\u6D3B\u52A8\u6D41", hotkey: "" },
|
|
3260
|
+
{ key: "f1", label: "\u5BA1\u67E5\u7EBF\u7A0B", hotkey: "1" },
|
|
3261
|
+
{ key: "f2", label: "\u6536\u655B\u8D8B\u52BF", hotkey: "2" },
|
|
3262
|
+
{ key: "f3", label: "\u53D1\u73B0\u5B9A\u4F4D", hotkey: "3" },
|
|
3263
|
+
{ key: "f4", label: "\u4FEE\u590D\u4E0E\u56DE\u6EDA", hotkey: "4" },
|
|
3264
|
+
{ key: "f5", label: "\u65AD\u70B9\u6062\u590D", hotkey: "5" },
|
|
3265
|
+
{ key: "f6", label: "\u8FD0\u884C\u63A7\u5236\u53F0", hotkey: "6" },
|
|
3266
|
+
{ key: "f7", label: "\u51B3\u7B56\u65F6\u95F4\u7EBF", hotkey: "7" },
|
|
2709
3267
|
// v3.0: Quality Command Center tabs
|
|
2710
|
-
{ key: "f8", label: "\u8D28\u91CF\u95E8\u7981" },
|
|
2711
|
-
{ key: "f9", label: "\u7ECF\u9A8C\u94F6\u884C" },
|
|
2712
|
-
{ key: "f10", label: "\u9632\u5FA1\u4E8B\u4EF6" }
|
|
3268
|
+
{ key: "f8", label: "\u8D28\u91CF\u95E8\u7981", hotkey: "8" },
|
|
3269
|
+
{ key: "f9", label: "\u7ECF\u9A8C\u94F6\u884C", hotkey: "9" },
|
|
3270
|
+
{ key: "f10", label: "\u9632\u5FA1\u4E8B\u4EF6", hotkey: "0" }
|
|
2713
3271
|
];
|
|
3272
|
+
var OBS_HOTKEY_TABS = {
|
|
3273
|
+
1: "f1",
|
|
3274
|
+
2: "f2",
|
|
3275
|
+
3: "f3",
|
|
3276
|
+
4: "f4",
|
|
3277
|
+
5: "f5",
|
|
3278
|
+
6: "f6",
|
|
3279
|
+
7: "f7",
|
|
3280
|
+
8: "f8",
|
|
3281
|
+
9: "f9",
|
|
3282
|
+
0: "f10",
|
|
3283
|
+
"-": "live"
|
|
3284
|
+
};
|
|
3285
|
+
function obsTabLabel(t) {
|
|
3286
|
+
if (t.key === "live") return t.label;
|
|
3287
|
+
return `${t.key.toUpperCase()} ${t.label}`;
|
|
3288
|
+
}
|
|
3289
|
+
var OBS_HOTKEY_HINT = `${String(OBS_TABS.length)} \u9875 \xB7 \u6309 1-9/0 \u5207 Fn\u3001- \u56DE\u5B9E\u65F6\u6D41`;
|
|
2714
3290
|
var OBS_LIVE_META = {
|
|
2715
3291
|
read: { label: "\u9605\u8BFB", color: "var(--dsw-alias-brand-primary)" },
|
|
2716
3292
|
fix: { label: "\u4FEE\u590D", color: "var(--dsw-alias-state-success-primary)" },
|
|
@@ -2760,18 +3336,7 @@ function latestDefenseEvents(session) {
|
|
|
2760
3336
|
return scanSessionForDefenseEvents(session);
|
|
2761
3337
|
}
|
|
2762
3338
|
function buildObsFixInstruction(f) {
|
|
2763
|
-
|
|
2764
|
-
file: String(f.file || ""),
|
|
2765
|
-
...typeof f.line === "number" && f.line > 0 ? { line: f.line } : {},
|
|
2766
|
-
dimension: String(f.dimension || ""),
|
|
2767
|
-
summary: String(f.summary || ""),
|
|
2768
|
-
...f.suggested_fix ? { suggested_fix: String(f.suggested_fix) } : {}
|
|
2769
|
-
}, null, 2);
|
|
2770
|
-
return `\u8BF7\u8C03\u7528 \`iterate_fix\` \u4FEE\u590D\u4EE5\u4E0B finding\uFF1A
|
|
2771
|
-
|
|
2772
|
-
\`\`\`json
|
|
2773
|
-
${payload}
|
|
2774
|
-
\`\`\``;
|
|
3339
|
+
return buildFixInstruction(f);
|
|
2775
3340
|
}
|
|
2776
3341
|
function ObservatoryPanel(props) {
|
|
2777
3342
|
const session = props && props.session ? props.session : null;
|
|
@@ -2799,6 +3364,28 @@ function ObservatoryPanel(props) {
|
|
|
2799
3364
|
React.useEffect(() => () => {
|
|
2800
3365
|
if (copyTimer.current) clearTimeout(copyTimer.current);
|
|
2801
3366
|
}, []);
|
|
3367
|
+
React.useEffect(() => {
|
|
3368
|
+
const doc = typeof document !== "undefined" ? document : null;
|
|
3369
|
+
if (!doc || !open) return;
|
|
3370
|
+
const onKeyDown = (ev) => {
|
|
3371
|
+
if (ev.metaKey || ev.ctrlKey || ev.altKey || ev.shiftKey) return;
|
|
3372
|
+
const t = ev.target;
|
|
3373
|
+
if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT")) return;
|
|
3374
|
+
if (t && typeof t.isContentEditable === "boolean" && t.isContentEditable) return;
|
|
3375
|
+
const rootEl = doc.querySelector('[data-iterate="obs"]');
|
|
3376
|
+
if (!rootEl) return;
|
|
3377
|
+
const activeEl = doc.activeElement;
|
|
3378
|
+
const inPanel = activeEl === rootEl || activeEl !== null && typeof rootEl.contains === "function" && rootEl.contains(activeEl);
|
|
3379
|
+
const idle = activeEl === null || activeEl === doc.body;
|
|
3380
|
+
if (!inPanel && !idle) return;
|
|
3381
|
+
const next = OBS_HOTKEY_TABS[ev.key];
|
|
3382
|
+
if (!next) return;
|
|
3383
|
+
ev.preventDefault();
|
|
3384
|
+
setTab(next);
|
|
3385
|
+
};
|
|
3386
|
+
doc.addEventListener("keydown", onKeyDown);
|
|
3387
|
+
return () => doc.removeEventListener("keydown", onKeyDown);
|
|
3388
|
+
}, [open]);
|
|
2802
3389
|
const copyInstruction = (key, text) => {
|
|
2803
3390
|
if (!text) return;
|
|
2804
3391
|
copyText(text).then((ok) => {
|
|
@@ -2811,6 +3398,8 @@ function ObservatoryPanel(props) {
|
|
|
2811
3398
|
copyTimer.current = setTimeout(() => setCopiedKey((cur) => cur === key ? null : cur), 1600);
|
|
2812
3399
|
});
|
|
2813
3400
|
};
|
|
3401
|
+
const pullInstruction = buildDiskSnapshotInstruction();
|
|
3402
|
+
const pullTitle = `\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u8BA9\u6A21\u578B\u9010\u4E2A\u8C03\u7528 ${DISK_SNAPSHOT_SOURCES.map((s) => s.tool).join(" / ")} \u5E76\u56DE\u663E\u5B8C\u6574\u7ED3\u679C`;
|
|
2814
3403
|
const toggleThread = (key) => {
|
|
2815
3404
|
setThreadMode("auto");
|
|
2816
3405
|
setExpandedThreads((prev) => {
|
|
@@ -2822,7 +3411,12 @@ function ObservatoryPanel(props) {
|
|
|
2822
3411
|
};
|
|
2823
3412
|
const exportObservatory = () => {
|
|
2824
3413
|
if (!manifest) return;
|
|
2825
|
-
const json = serializeObservatoryExport(manifest, manifest.live || []
|
|
3414
|
+
const json = serializeObservatoryExport(manifest, manifest.live || [], {
|
|
3415
|
+
qualityGate,
|
|
3416
|
+
experienceBank,
|
|
3417
|
+
defenseEvents,
|
|
3418
|
+
report: latestReport(session)
|
|
3419
|
+
});
|
|
2826
3420
|
const filename = `iterate-observatory-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`;
|
|
2827
3421
|
const flash = () => {
|
|
2828
3422
|
setCopiedKey("export");
|
|
@@ -2858,7 +3452,16 @@ function ObservatoryPanel(props) {
|
|
|
2858
3452
|
"div",
|
|
2859
3453
|
{ className: "iterate-obs-head", "data-closed": "" },
|
|
2860
3454
|
React.createElement("span", { className: "iterate-obs-title" }, "iterate \u89C2\u6D4B\u53F0"),
|
|
2861
|
-
React.createElement("span", { className: "iterate-obs-head-meta" },
|
|
3455
|
+
React.createElement("span", { className: "iterate-obs-head-meta" }, `\u6682\u65E0\u8FD0\u884C\u65F6\u89C2\u6D4B\u6570\u636E \xB7 ${OBS_HOTKEY_HINT}`),
|
|
3456
|
+
React.createElement("button", {
|
|
3457
|
+
className: "iterate-btn",
|
|
3458
|
+
"data-copied": copiedKey === "pull-snapshot" ? "" : void 0,
|
|
3459
|
+
onClick: (e) => {
|
|
3460
|
+
e.stopPropagation();
|
|
3461
|
+
copyInstruction("pull-snapshot", pullInstruction);
|
|
3462
|
+
},
|
|
3463
|
+
title: "\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u78C1\u76D8\u4E0A\u53EF\u80FD\u5DF2\u6709\u5386\u53F2\u4EA7\u7269\uFF0C\u56DE\u663E\u8FDB\u4F1A\u8BDD\u540E\u89C2\u6D4B\u53F0\u5373\u53EF\u586B\u5145"
|
|
3464
|
+
}, copiedKey === "pull-snapshot" ? "\u5DF2\u590D\u5236" : "\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167")
|
|
2862
3465
|
)
|
|
2863
3466
|
);
|
|
2864
3467
|
}
|
|
@@ -2968,6 +3571,45 @@ function ObservatoryPanel(props) {
|
|
|
2968
3571
|
return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u6536\u655B\u6570\u636E");
|
|
2969
3572
|
}
|
|
2970
3573
|
const points = conv.map((n, i) => ({ round: i + 1, count: n }));
|
|
3574
|
+
const report = latestReport(session);
|
|
3575
|
+
const comparison = buildRoundComparison(report, manifest.validations || []);
|
|
3576
|
+
const comparisonTable = comparison.length === 0 ? null : React.createElement(
|
|
3577
|
+
"table",
|
|
3578
|
+
{ className: "iterate-obs-table" },
|
|
3579
|
+
React.createElement(
|
|
3580
|
+
"thead",
|
|
3581
|
+
{},
|
|
3582
|
+
React.createElement(
|
|
3583
|
+
"tr",
|
|
3584
|
+
{},
|
|
3585
|
+
React.createElement("th", {}, "\u8F6E\u6B21"),
|
|
3586
|
+
React.createElement("th", {}, "\u53D1\u73B0"),
|
|
3587
|
+
React.createElement("th", {}, "\u5DF2\u4FEE\u590D"),
|
|
3588
|
+
React.createElement("th", {}, "\u4E25\u91CD\u5EA6\u5206\u5E03"),
|
|
3589
|
+
React.createElement("th", {}, "\u9A8C\u8BC1")
|
|
3590
|
+
)
|
|
3591
|
+
),
|
|
3592
|
+
React.createElement(
|
|
3593
|
+
"tbody",
|
|
3594
|
+
{},
|
|
3595
|
+
...comparison.map((row) => {
|
|
3596
|
+
const sevText = Object.entries(row.severities).map(([k, v]) => `${k}:${v}`).join(" \xB7 ");
|
|
3597
|
+
const anyFailed = row.validations.some((v) => v.allowed !== true);
|
|
3598
|
+
const valText = row.validations.length === 0 ? "\u2014" : `${row.validations.filter((v) => v.allowed === true).length}/${row.validations.length} \u901A\u8FC7${anyFailed ? "\uFF08\u542B\u5931\u8D25\uFF09" : ""}`;
|
|
3599
|
+
return React.createElement(
|
|
3600
|
+
"tr",
|
|
3601
|
+
{ key: `cmp-${row.round}` },
|
|
3602
|
+
React.createElement("td", {}, `Round ${row.round}`),
|
|
3603
|
+
React.createElement("td", {}, String(row.findings)),
|
|
3604
|
+
React.createElement("td", {}, String(row.fixed)),
|
|
3605
|
+
React.createElement("td", {}, sevText || "\u2014"),
|
|
3606
|
+
React.createElement("td", {
|
|
3607
|
+
style: anyFailed ? { color: "var(--dsw-alias-state-error-primary)", fontWeight: 600 } : row.validations.length > 0 ? { color: "var(--dsw-alias-state-success-primary)" } : void 0
|
|
3608
|
+
}, valText)
|
|
3609
|
+
);
|
|
3610
|
+
})
|
|
3611
|
+
)
|
|
3612
|
+
);
|
|
2971
3613
|
return React.createElement(
|
|
2972
3614
|
"div",
|
|
2973
3615
|
{},
|
|
@@ -2976,7 +3618,22 @@ function ObservatoryPanel(props) {
|
|
|
2976
3618
|
"div",
|
|
2977
3619
|
{ className: "iterate-obs-bar", style: { marginTop: 6 } },
|
|
2978
3620
|
`\u5404\u8F6E\u53D1\u73B0\u6570\u91CF\uFF1A${conv.join(" \u2192 ")}${typeof manifest.round === "number" ? ` \xB7 \u5F53\u524D Round ${manifest.round}` : ""}`
|
|
2979
|
-
)
|
|
3621
|
+
),
|
|
3622
|
+
comparisonTable ? React.createElement(
|
|
3623
|
+
"div",
|
|
3624
|
+
{ className: "iterate-obs-block", style: { marginTop: 8 } },
|
|
3625
|
+
React.createElement(
|
|
3626
|
+
"div",
|
|
3627
|
+
{ className: "iterate-obs-block-head" },
|
|
3628
|
+
React.createElement("span", {}, "\u8DE8\u8F6E\u5BF9\u6BD4\uFF08\u672C\u4F1A\u8BDD\uFF09"),
|
|
3629
|
+
React.createElement(
|
|
3630
|
+
"span",
|
|
3631
|
+
{ className: "iterate-obs-head-meta" },
|
|
3632
|
+
"\u8DE8\u4F1A\u8BDD\u5BF9\u6BD4\uFF1A\u672C\u8868\u53EA\u8986\u76D6\u5F53\u524D\u4F1A\u8BDD\uFF1B\u70B9\u300C\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u300D\u8BA9\u6A21\u578B\u56DE\u663E iterate_history\uFF0C\u518D\u7528\u300C\u5BFC\u51FA JSON\u300D\u5E76\u6392\u6BD4\u8F83\u4E24\u6B21\u8FD0\u884C"
|
|
3633
|
+
)
|
|
3634
|
+
),
|
|
3635
|
+
React.createElement("div", { className: "iterate-obs-block-body" }, comparisonTable)
|
|
3636
|
+
) : null
|
|
2980
3637
|
);
|
|
2981
3638
|
};
|
|
2982
3639
|
const renderFindings = () => {
|
|
@@ -3109,11 +3766,7 @@ function ObservatoryPanel(props) {
|
|
|
3109
3766
|
return React.createElement("div", {}, ...fixes.map((f, i) => {
|
|
3110
3767
|
const k = `f4-${i}`;
|
|
3111
3768
|
const id = String(f.id || `fix#${i + 1}`);
|
|
3112
|
-
const rollbackText =
|
|
3113
|
-
|
|
3114
|
-
\`\`\`json
|
|
3115
|
-
${JSON.stringify({ id }, null, 2)}
|
|
3116
|
-
\`\`\``;
|
|
3769
|
+
const rollbackText = buildRollbackInstruction(id);
|
|
3117
3770
|
const added = typeof f.linesAdded === "number" ? f.linesAdded : 0;
|
|
3118
3771
|
const removed = typeof f.linesRemoved === "number" ? f.linesRemoved : 0;
|
|
3119
3772
|
return React.createElement(
|
|
@@ -3151,22 +3804,14 @@ ${JSON.stringify({ id }, null, 2)}
|
|
|
3151
3804
|
const renderCheckpoint = () => {
|
|
3152
3805
|
const cp = manifest.checkpoint;
|
|
3153
3806
|
if (!cp) {
|
|
3154
|
-
return React.createElement(
|
|
3807
|
+
return React.createElement(
|
|
3808
|
+
"div",
|
|
3809
|
+
{ className: "iterate-obs-empty" },
|
|
3810
|
+
diskEmptyStateText(toolCalledInSession(session, "iterate_transcript"), "\u65AD\u70B9")
|
|
3811
|
+
);
|
|
3155
3812
|
}
|
|
3156
|
-
const resumeText =
|
|
3157
|
-
|
|
3158
|
-
\`\`\`json
|
|
3159
|
-
${JSON.stringify({
|
|
3160
|
-
operation: "resume",
|
|
3161
|
-
mode: String(cp.mode || "normal"),
|
|
3162
|
-
maxRounds: typeof cp.maxRounds === "number" ? cp.maxRounds : null
|
|
3163
|
-
}, null, 2)}
|
|
3164
|
-
\`\`\``;
|
|
3165
|
-
const clearText = `\u8BF7\u8C03\u7528 \`iterate_checkpoint\` \u6E05\u9664\u5F53\u524D\u65AD\u70B9\uFF1A
|
|
3166
|
-
|
|
3167
|
-
\`\`\`json
|
|
3168
|
-
${JSON.stringify({ operation: "clear" }, null, 2)}
|
|
3169
|
-
\`\`\``;
|
|
3813
|
+
const resumeText = buildCheckpointResumeInstruction();
|
|
3814
|
+
const clearText = buildCheckpointClearInstruction();
|
|
3170
3815
|
const item = (label, value) => React.createElement("span", { className: "iterate-obs-chip" }, `${label} ${String(value ?? "?")}`);
|
|
3171
3816
|
return React.createElement(
|
|
3172
3817
|
"div",
|
|
@@ -3227,6 +3872,53 @@ ${JSON.stringify({ operation: "nudge", text: nudgeText }, null, 2)}
|
|
|
3227
3872
|
\`\`\`json
|
|
3228
3873
|
${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
|
|
3229
3874
|
\`\`\``;
|
|
3875
|
+
const validations = Array.isArray(manifest.validations) ? manifest.validations : [];
|
|
3876
|
+
const abortedByValidation = String(manifest.stoppedReason || "") === "aborted_by_validation";
|
|
3877
|
+
const validationRows = validations.map((v, i) => {
|
|
3878
|
+
const allowed = v.allowed === true;
|
|
3879
|
+
const failed = !allowed;
|
|
3880
|
+
const highlight = failed && abortedByValidation;
|
|
3881
|
+
const exit = typeof v.exitCode === "number" ? `exit ${v.exitCode}` : "exit \u2014";
|
|
3882
|
+
return React.createElement(
|
|
3883
|
+
"div",
|
|
3884
|
+
{
|
|
3885
|
+
key: `val-${i}`,
|
|
3886
|
+
className: "iterate-obs-row",
|
|
3887
|
+
"data-status": allowed ? "pass" : "fail",
|
|
3888
|
+
"data-highlight": highlight ? "" : void 0,
|
|
3889
|
+
title: v.rejectReason ? String(v.rejectReason) : void 0
|
|
3890
|
+
},
|
|
3891
|
+
React.createElement("span", { className: "iterate-obs-head-meta" }, `Round ${typeof v.round === "number" ? v.round : "?"}`),
|
|
3892
|
+
React.createElement("code", { className: "iterate-obs-mono" }, String(v.command || "")),
|
|
3893
|
+
React.createElement(
|
|
3894
|
+
"span",
|
|
3895
|
+
{ className: "iterate-obs-chip", "data-status": allowed ? "pass" : "fail" },
|
|
3896
|
+
allowed ? "\u901A\u8FC7" : "\u5931\u8D25"
|
|
3897
|
+
),
|
|
3898
|
+
React.createElement("span", { className: "iterate-obs-head-meta" }, exit),
|
|
3899
|
+
highlight ? React.createElement("span", { className: "iterate-obs-chip", "data-status": "fail" }, "\u5BFC\u81F4\u4E2D\u6B62") : null
|
|
3900
|
+
);
|
|
3901
|
+
});
|
|
3902
|
+
const validationsBlock = React.createElement(
|
|
3903
|
+
"div",
|
|
3904
|
+
{ className: "iterate-obs-block", style: { marginTop: 8 } },
|
|
3905
|
+
React.createElement(
|
|
3906
|
+
"div",
|
|
3907
|
+
{ className: "iterate-obs-block-head" },
|
|
3908
|
+
React.createElement("span", {}, "\u9A8C\u8BC1\u7ED3\u679C"),
|
|
3909
|
+
validations.length === 0 ? React.createElement("span", { className: "iterate-obs-head-meta" }, "\u672C\u8F6E\u5C1A\u65E0\u9A8C\u8BC1\u8BB0\u5F55") : React.createElement("span", { className: "iterate-obs-head-meta" }, `${validations.length} \u6761\u547D\u4EE4`),
|
|
3910
|
+
abortedByValidation ? React.createElement("span", { className: "iterate-obs-badge", "data-status": "fail" }, "\u56E0\u9A8C\u8BC1\u5931\u8D25\u4E2D\u6B62") : null
|
|
3911
|
+
),
|
|
3912
|
+
React.createElement(
|
|
3913
|
+
"div",
|
|
3914
|
+
{ className: "iterate-obs-block-body" },
|
|
3915
|
+
validations.length === 0 ? React.createElement(
|
|
3916
|
+
"div",
|
|
3917
|
+
{ className: "iterate-obs-empty" },
|
|
3918
|
+
"\u5C1A\u672A\u62C9\u53D6\u9A8C\u8BC1\u7ED3\u679C\uFF1A\u6570\u636E\u5728\u78C1\u76D8 transcript \u91CC\uFF0C\u70B9\u300C\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u300D\u590D\u5236\u6307\u4EE4\u53D1\u56DE\u4F1A\u8BDD\u5373\u53EF\u56DE\u586B\u3002"
|
|
3919
|
+
) : React.createElement(React.Fragment, null, ...validationRows)
|
|
3920
|
+
)
|
|
3921
|
+
);
|
|
3230
3922
|
return React.createElement(
|
|
3231
3923
|
"div",
|
|
3232
3924
|
{ className: "iterate-obs-block" },
|
|
@@ -3271,7 +3963,8 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
|
|
|
3271
3963
|
onClick: () => copyInstruction("nudge", nudgeInstruction),
|
|
3272
3964
|
title: "\u590D\u5236 iterate_transcript nudge \u6307\u4EE4\u6587\u672C"
|
|
3273
3965
|
}, copiedKey === "nudge" ? "\u5DF2\u590D\u5236" : "\u590D\u5236 nudge \u6307\u4EE4")
|
|
3274
|
-
)
|
|
3966
|
+
),
|
|
3967
|
+
validationsBlock
|
|
3275
3968
|
)
|
|
3276
3969
|
);
|
|
3277
3970
|
};
|
|
@@ -3374,18 +4067,15 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
|
|
|
3374
4067
|
return React.createElement("div", {}, filterBar, ...rows);
|
|
3375
4068
|
};
|
|
3376
4069
|
const renderQualityGate = () => {
|
|
3377
|
-
const gateInstruction =
|
|
3378
|
-
const gateClearInstruction =
|
|
3379
|
-
|
|
3380
|
-
\`\`\`json
|
|
3381
|
-
${JSON.stringify({ operation: "clear" }, null, 2)}
|
|
3382
|
-
\`\`\``;
|
|
4070
|
+
const gateInstruction = buildQualityGateQueryInstruction();
|
|
4071
|
+
const gateClearInstruction = buildQualityGateClearInstruction();
|
|
3383
4072
|
const gate = qualityGate;
|
|
3384
4073
|
const dims = gate && gate.dimensions ? gate.dimensions : [];
|
|
3385
4074
|
const hasGate = gate !== null && Boolean(gate.overallStatus || gate.overallScore != null || dims.length > 0);
|
|
3386
4075
|
const status = gate && gate.overallStatus ? String(gate.overallStatus) : "pending";
|
|
3387
4076
|
const statusLabel = status === "pass" ? "PASS" : status === "fail" ? "FAIL" : "PENDING";
|
|
3388
4077
|
const num = (v) => Number(v != null ? v : 0);
|
|
4078
|
+
const pulled = toolCalledInSession(session, "iterate_quality_gate");
|
|
3389
4079
|
const headerBar = React.createElement(
|
|
3390
4080
|
"div",
|
|
3391
4081
|
{ className: "iterate-obs-bar", style: { marginBottom: 8 } },
|
|
@@ -3395,7 +4085,7 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
|
|
|
3395
4085
|
"data-primary": "",
|
|
3396
4086
|
"data-copied": copiedKey === "qgate" ? "" : void 0,
|
|
3397
4087
|
onClick: () => copyInstruction("qgate", gateInstruction),
|
|
3398
|
-
title: "\u590D\u5236 iterate_quality_gate \u67E5\u8BE2\u6307\u4EE4"
|
|
4088
|
+
title: "\u590D\u5236 iterate_quality_gate \u67E5\u8BE2\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09"
|
|
3399
4089
|
}, copiedKey === "qgate" ? "\u5DF2\u590D\u5236" : "\u67E5\u8BE2\u95E8\u7981"),
|
|
3400
4090
|
React.createElement("button", {
|
|
3401
4091
|
className: "iterate-btn",
|
|
@@ -3403,7 +4093,15 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
|
|
|
3403
4093
|
"data-copied": copiedKey === "qgate-clear" ? "" : void 0,
|
|
3404
4094
|
onClick: () => copyInstruction("qgate-clear", gateClearInstruction),
|
|
3405
4095
|
title: "\u590D\u5236 iterate_quality_gate clear \u6307\u4EE4\u6587\u672C\uFF08\u91CD\u7F6E\u9648\u65E7\u7684 FAIL \u8BC1\u4E66\uFF09"
|
|
3406
|
-
}, copiedKey === "qgate-clear" ? "\u5DF2\u590D\u5236" : "\u6E05\u9664\u95E8\u7981")
|
|
4096
|
+
}, copiedKey === "qgate-clear" ? "\u5DF2\u590D\u5236" : "\u6E05\u9664\u95E8\u7981"),
|
|
4097
|
+
// Gap #4: the browser cannot read `.iterate/` directly — one click
|
|
4098
|
+
// copies the pull instruction that makes the model echo it into the stream.
|
|
4099
|
+
React.createElement("button", {
|
|
4100
|
+
className: "iterate-btn",
|
|
4101
|
+
"data-copied": copiedKey === "disk-snapshot" ? "" : void 0,
|
|
4102
|
+
onClick: () => copyInstruction("disk-snapshot", pullInstruction),
|
|
4103
|
+
title: "\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u8BA9\u6A21\u578B\u56DE\u663E .iterate/ \u4E0B\u7684\u95E8\u7981\u3001\u7ECF\u9A8C\u3001\u9632\u5FA1\u4E8B\u4EF6\u4E0E transcript"
|
|
4104
|
+
}, copiedKey === "disk-snapshot" ? "\u5DF2\u590D\u5236" : "\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167")
|
|
3407
4105
|
);
|
|
3408
4106
|
if (!hasGate) {
|
|
3409
4107
|
return React.createElement(
|
|
@@ -3422,7 +4120,18 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
|
|
|
3422
4120
|
React.createElement(
|
|
3423
4121
|
"div",
|
|
3424
4122
|
{ className: "iterate-obs-block-body" },
|
|
3425
|
-
React.createElement(
|
|
4123
|
+
React.createElement(
|
|
4124
|
+
"div",
|
|
4125
|
+
{ className: "iterate-obs-msg" },
|
|
4126
|
+
// Gap #4: distinguish "we pulled it and it is empty" from "we
|
|
4127
|
+
// never asked the model to read the disk".
|
|
4128
|
+
diskEmptyStateText(pulled, "\u8D28\u91CF\u95E8\u7981\u8BC1\u4E66")
|
|
4129
|
+
),
|
|
4130
|
+
React.createElement(
|
|
4131
|
+
"div",
|
|
4132
|
+
{ className: "iterate-obs-msg", style: { marginTop: 6 } },
|
|
4133
|
+
"\u8D28\u91CF\u95E8\u7981\u663E\u793A\u5404\u7EF4\u5EA6\u6536\u655B\u5EA6\u3001\u9A8C\u8BC1\u901A\u8FC7\u7387\u548C\u6574\u4F53 PASS/FAIL \u72B6\u6001\u3002"
|
|
4134
|
+
),
|
|
3426
4135
|
React.createElement(
|
|
3427
4136
|
"div",
|
|
3428
4137
|
{ className: "iterate-obs-bar", style: { marginTop: 8 } },
|
|
@@ -3431,7 +4140,13 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
|
|
|
3431
4140
|
"data-copied": copiedKey === "qgate-instr" ? "" : void 0,
|
|
3432
4141
|
onClick: () => copyInstruction("qgate-instr", gateInstruction),
|
|
3433
4142
|
title: "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4"
|
|
3434
|
-
}, copiedKey === "qgate-instr" ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4")
|
|
4143
|
+
}, copiedKey === "qgate-instr" ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4"),
|
|
4144
|
+
React.createElement("button", {
|
|
4145
|
+
className: "iterate-btn",
|
|
4146
|
+
"data-copied": copiedKey === "disk-snapshot-2" ? "" : void 0,
|
|
4147
|
+
onClick: () => copyInstruction("disk-snapshot-2", pullInstruction),
|
|
4148
|
+
title: "\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4"
|
|
4149
|
+
}, copiedKey === "disk-snapshot-2" ? "\u5DF2\u590D\u5236" : "\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167")
|
|
3435
4150
|
)
|
|
3436
4151
|
)
|
|
3437
4152
|
)
|
|
@@ -3500,12 +4215,13 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
|
|
|
3500
4215
|
|
|
3501
4216
|
\`\`\`json
|
|
3502
4217
|
${JSON.stringify({ operation: "search", query: expSearch }, null, 2)}
|
|
3503
|
-
\`\`\`` :
|
|
4218
|
+
\`\`\`` : buildExperienceListInstruction();
|
|
3504
4219
|
const expEntries = experienceBank && experienceBank.entries ? experienceBank.entries : [];
|
|
3505
4220
|
const query = expSearch.trim().toLowerCase();
|
|
3506
4221
|
const visible = query ? expEntries.filter(
|
|
3507
4222
|
(e) => [e.pattern, e.description, e.id, e.dimension, e.severity, ...e.tags || []].filter(Boolean).some((t) => String(t).toLowerCase().includes(query))
|
|
3508
4223
|
) : expEntries;
|
|
4224
|
+
const pulled = toolCalledInSession(session, "iterate_experience");
|
|
3509
4225
|
const resultRows = visible.map((e, i) => {
|
|
3510
4226
|
const adoptInstruction = `\u8BF7\u8C03\u7528 \`iterate_experience\` \u91C7\u7EB3\u7ECF\u9A8C\u5E76\u5E94\u7528\u5DF2\u9A8C\u8BC1\u4FEE\u6CD5\uFF1A
|
|
3511
4227
|
|
|
@@ -3552,9 +4268,15 @@ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
|
|
|
3552
4268
|
className: "iterate-btn",
|
|
3553
4269
|
"data-primary": "",
|
|
3554
4270
|
"data-copied": copiedKey === "exp-list" ? "" : void 0,
|
|
3555
|
-
onClick: () => copyInstruction("exp-list",
|
|
3556
|
-
title: "\u590D\u5236\u5217\u51FA\u7ECF\u9A8C\u6307\u4EE4"
|
|
3557
|
-
}, copiedKey === "exp-list" ? "\u5DF2\u590D\u5236" : "\u5217\u51FA\u7ECF\u9A8C")
|
|
4271
|
+
onClick: () => copyInstruction("exp-list", buildExperienceListInstruction()),
|
|
4272
|
+
title: "\u590D\u5236\u5217\u51FA\u7ECF\u9A8C\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09"
|
|
4273
|
+
}, copiedKey === "exp-list" ? "\u5DF2\u590D\u5236" : "\u5217\u51FA\u7ECF\u9A8C"),
|
|
4274
|
+
React.createElement("button", {
|
|
4275
|
+
className: "iterate-btn",
|
|
4276
|
+
"data-copied": copiedKey === "exp-snapshot" ? "" : void 0,
|
|
4277
|
+
onClick: () => copyInstruction("exp-snapshot", pullInstruction),
|
|
4278
|
+
title: "\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u628A .iterate/ \u4E0B\u7684\u7ECF\u9A8C\u94F6\u884C\u7B49\u4EA7\u7269\u56DE\u663E\u8FDB\u4F1A\u8BDD"
|
|
4279
|
+
}, copiedKey === "exp-snapshot" ? "\u5DF2\u590D\u5236" : "\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167")
|
|
3558
4280
|
),
|
|
3559
4281
|
React.createElement(
|
|
3560
4282
|
"div",
|
|
@@ -3575,7 +4297,13 @@ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
|
|
|
3575
4297
|
title: "\u590D\u5236\u641C\u7D22\u6307\u4EE4"
|
|
3576
4298
|
}, copiedKey === "exp-search" ? "\u5DF2\u590D\u5236" : "\u641C\u7D22")
|
|
3577
4299
|
),
|
|
3578
|
-
resultRows.length > 0 ? React.createElement("div", {}, ...resultRows) : expEntries.length > 0 ? React.createElement("div", { className: "iterate-obs-empty" }, "\u6CA1\u6709\u5339\u914D\u300C" + expSearch + "\u300D\u7684\u7ECF\u9A8C") : React.createElement(
|
|
4300
|
+
resultRows.length > 0 ? React.createElement("div", {}, ...resultRows) : expEntries.length > 0 ? React.createElement("div", { className: "iterate-obs-empty" }, "\u6CA1\u6709\u5339\u914D\u300C" + expSearch + "\u300D\u7684\u7ECF\u9A8C") : React.createElement(
|
|
4301
|
+
"div",
|
|
4302
|
+
{},
|
|
4303
|
+
// Gap #4: separate "pulled and empty" from "never pulled".
|
|
4304
|
+
React.createElement("div", { className: "iterate-obs-empty" }, diskEmptyStateText(pulled, "\u7ECF\u9A8C\u94F6\u884C")),
|
|
4305
|
+
React.createElement("div", { className: "iterate-obs-empty" }, "\u4E5F\u53EF\u70B9\u300C\u5217\u51FA\u7ECF\u9A8C\u300D\u67E5\u8BE2\u78C1\u76D8\u4E0A\u7684\u5168\u90E8\u5386\u53F2\u7ECF\u9A8C\u3002")
|
|
4306
|
+
)
|
|
3579
4307
|
);
|
|
3580
4308
|
};
|
|
3581
4309
|
const renderDefenseEvents = () => {
|
|
@@ -3583,7 +4311,7 @@ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
|
|
|
3583
4311
|
|
|
3584
4312
|
\`\`\`json
|
|
3585
4313
|
${JSON.stringify({ operation: "list", type: defenseFilter || void 0 }, null, 2)}
|
|
3586
|
-
\`\`\`` :
|
|
4314
|
+
\`\`\`` : buildDefenseEventsListInstruction();
|
|
3587
4315
|
const typeOptions = [
|
|
3588
4316
|
{ value: "", label: "\u5168\u90E8\u7C7B\u578B" },
|
|
3589
4317
|
{ value: "precondition_failed", label: "\u524D\u7F6E\u6821\u9A8C\u5931\u8D25" },
|
|
@@ -3610,14 +4338,24 @@ ${JSON.stringify({ operation: "list", type: defenseFilter || void 0 }, null, 2)}
|
|
|
3610
4338
|
className: "iterate-btn",
|
|
3611
4339
|
"data-primary": "",
|
|
3612
4340
|
"data-copied": copiedKey === "defense-list" ? "" : void 0,
|
|
3613
|
-
onClick: () => copyInstruction("defense-list",
|
|
3614
|
-
title: "\u590D\u5236\u5217\u51FA\u9632\u5FA1\u4E8B\u4EF6\u6307\u4EE4"
|
|
4341
|
+
onClick: () => copyInstruction("defense-list", buildDefenseEventsListInstruction()),
|
|
4342
|
+
title: "\u590D\u5236\u5217\u51FA\u9632\u5FA1\u4E8B\u4EF6\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09"
|
|
3615
4343
|
}, copiedKey === "defense-list" ? "\u5DF2\u590D\u5236" : "\u5217\u51FA\u4E8B\u4EF6"),
|
|
4344
|
+
React.createElement("button", {
|
|
4345
|
+
className: "iterate-btn",
|
|
4346
|
+
"data-copied": copiedKey === "defense-snapshot" ? "" : void 0,
|
|
4347
|
+
onClick: () => copyInstruction("defense-snapshot", pullInstruction),
|
|
4348
|
+
title: "\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u628A .iterate/ \u4E0B\u7684\u9632\u5FA1\u4E8B\u4EF6\u6D41\u56DE\u663E\u8FDB\u4F1A\u8BDD"
|
|
4349
|
+
}, copiedKey === "defense-snapshot" ? "\u5DF2\u590D\u5236" : "\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167"),
|
|
3616
4350
|
React.createElement("button", {
|
|
3617
4351
|
className: "iterate-btn",
|
|
3618
4352
|
"data-copied": copiedKey === "defense-counts" ? "" : void 0,
|
|
3619
|
-
onClick: () => copyInstruction("defense-counts",
|
|
3620
|
-
|
|
4353
|
+
onClick: () => copyInstruction("defense-counts", `\u8BF7\u8C03\u7528 \`iterate_defense_events\` \u67E5\u8BE2\u4E8B\u4EF6\u7EDF\u8BA1\uFF1A
|
|
4354
|
+
|
|
4355
|
+
\`\`\`json
|
|
4356
|
+
${JSON.stringify({ operation: "counts" }, null, 2)}
|
|
4357
|
+
\`\`\``),
|
|
4358
|
+
title: "\u590D\u5236\u7EDF\u8BA1\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09"
|
|
3621
4359
|
}, copiedKey === "defense-counts" ? "\u5DF2\u590D\u5236" : "\u7EDF\u8BA1"),
|
|
3622
4360
|
React.createElement("button", {
|
|
3623
4361
|
className: "iterate-btn",
|
|
@@ -3648,7 +4386,16 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
|
|
|
3648
4386
|
React.createElement(
|
|
3649
4387
|
"div",
|
|
3650
4388
|
{ className: "iterate-obs-block-body" },
|
|
3651
|
-
React.createElement(
|
|
4389
|
+
React.createElement(
|
|
4390
|
+
"div",
|
|
4391
|
+
{ className: "iterate-obs-msg" },
|
|
4392
|
+
diskEmptyStateText(toolCalledInSession(session, "iterate_defense_events"), "\u9632\u5FA1\u4E8B\u4EF6\u6D41")
|
|
4393
|
+
),
|
|
4394
|
+
React.createElement(
|
|
4395
|
+
"div",
|
|
4396
|
+
{ className: "iterate-obs-msg", style: { marginTop: 6 } },
|
|
4397
|
+
"\u9632\u5FA1\u4E8B\u4EF6\u5305\u62EC\uFF1A\u524D\u7F6E\u6821\u9A8C\u5931\u8D25\u3001\u56DE\u6EDA\u3001\u4E0D\u53D8\u91CF\u8FDD\u53CD\u3001\u5047\u8BBE\u88AB\u8BC1\u4F2A\u3002"
|
|
4398
|
+
),
|
|
3652
4399
|
React.createElement(
|
|
3653
4400
|
"div",
|
|
3654
4401
|
{ className: "iterate-obs-bar", style: { marginTop: 8 } },
|
|
@@ -3832,6 +4579,11 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
|
|
|
3832
4579
|
React.createElement("span", { className: "iterate-obs-title" }, "iterate \u89C2\u6D4B\u53F0"),
|
|
3833
4580
|
React.createElement("span", { className: "iterate-obs-badge", "data-live": live ? "" : void 0 }, runStatusText(live, manifest)),
|
|
3834
4581
|
React.createElement("span", { className: "iterate-obs-head-meta" }, headMeta || "runtime"),
|
|
4582
|
+
// Gap #14: make the tab system discoverable without expanding it.
|
|
4583
|
+
React.createElement("span", {
|
|
4584
|
+
className: "iterate-obs-head-meta",
|
|
4585
|
+
title: OBS_HOTKEY_HINT
|
|
4586
|
+
}, OBS_HOTKEY_HINT),
|
|
3835
4587
|
React.createElement("button", {
|
|
3836
4588
|
className: "iterate-btn",
|
|
3837
4589
|
"data-ghost": "",
|
|
@@ -3875,18 +4627,30 @@ ${JSON.stringify({ operation: "clear" }, null, 2)}
|
|
|
3875
4627
|
"data-copied": copiedKey === "export" ? "" : void 0,
|
|
3876
4628
|
onClick: exportObservatory,
|
|
3877
4629
|
title: "\u5C06\u89C2\u6D4B\u53F0\u5168\u90E8\u6570\u636E\u5BFC\u51FA\u4E3A JSON\uFF08\u4F18\u5148\u4E0B\u8F7D\uFF0C\u5931\u8D25\u5219\u590D\u5236\uFF09"
|
|
3878
|
-
}, copiedKey === "export" ? "\u5DF2\u5BFC\u51FA" : "\u5BFC\u51FA JSON")
|
|
4630
|
+
}, copiedKey === "export" ? "\u5DF2\u5BFC\u51FA" : "\u5BFC\u51FA JSON"),
|
|
4631
|
+
// Gap #4: `.iterate/` artifacts are unreachable from the browser;
|
|
4632
|
+
// this copies the one instruction that makes the model echo them
|
|
4633
|
+
// into the session stream (which is what every panel scans).
|
|
4634
|
+
React.createElement("button", {
|
|
4635
|
+
className: "iterate-btn",
|
|
4636
|
+
"data-copied": copiedKey === "pull-snapshot" ? "" : void 0,
|
|
4637
|
+
onClick: () => copyInstruction("pull-snapshot", pullInstruction),
|
|
4638
|
+
title: pullTitle
|
|
4639
|
+
}, copiedKey === "pull-snapshot" ? "\u5DF2\u590D\u5236" : "\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167")
|
|
3879
4640
|
),
|
|
3880
4641
|
React.createElement(
|
|
3881
4642
|
"div",
|
|
3882
|
-
{ className: "iterate-obs-tabs" },
|
|
4643
|
+
{ className: "iterate-obs-tabs", role: "tablist", "aria-label": "\u89C2\u6D4B\u53F0\u9875\u7B7E\uFF08\u5FEB\u6377\u952E 1-9 / 0 / -\uFF09" },
|
|
3883
4644
|
...OBS_TABS.map(
|
|
3884
4645
|
(t) => React.createElement("button", {
|
|
3885
4646
|
key: t.key,
|
|
3886
4647
|
className: "iterate-obs-tab",
|
|
4648
|
+
role: "tab",
|
|
4649
|
+
"aria-selected": tab === t.key,
|
|
3887
4650
|
"data-active": tab === t.key ? "" : void 0,
|
|
3888
|
-
onClick: () => setTab(t.key)
|
|
3889
|
-
|
|
4651
|
+
onClick: () => setTab(t.key),
|
|
4652
|
+
title: t.hotkey ? `${obsTabLabel(t)}\uFF08\u6309 ${t.hotkey} \u5207\u6362\uFF09` : `${obsTabLabel(t)}\uFF08\u6309 - \u5207\u6362\uFF09`
|
|
4653
|
+
}, obsTabLabel(t))
|
|
3890
4654
|
)
|
|
3891
4655
|
),
|
|
3892
4656
|
React.createElement("div", { className: "iterate-obs-body" }, renderBody())
|