iterate-plugin 2.10.0 → 2.12.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 +42 -2
- package/README.zh-CN.md +40 -2
- package/dist/approval-gate.js +92 -0
- package/dist/config-loader.js +18 -3
- package/dist/config-write.js +7 -4
- package/dist/evidence.js +67 -1
- package/dist/git-scope.js +35 -6
- package/dist/index.js +15 -5
- package/dist/live.js +155 -0
- package/dist/meta-review.js +19 -5
- package/dist/method-scope.js +5 -1
- package/dist/paths.js +4 -0
- package/dist/review-scope.js +12 -8
- package/dist/review.js +76 -24
- package/dist/session-hooks.js +89 -0
- package/dist/skill-prompt.js +101 -19
- package/dist/tools/checkpoint.js +10 -3
- package/dist/tools/context.js +16 -4
- package/dist/tools/decision-log.js +29 -9
- package/dist/tools/fix.js +120 -3
- package/dist/tools/prune.js +16 -9
- package/dist/tools/review.js +4 -1
- package/dist/tools/transcript.js +324 -0
- package/dist/tools/triage.js +9 -6
- package/dist/tools/validate.js +5 -2
- package/dist/transcript.js +421 -0
- package/lib/client.js +966 -80
- package/lib/parse.js +302 -17
- package/package.json +1 -1
- package/src/approval-gate.ts +119 -0
- package/src/client/index.ts +807 -62
- package/src/config-loader.ts +16 -2
- package/src/config-write.ts +6 -4
- package/src/evidence.ts +69 -1
- package/src/git-scope.ts +34 -6
- package/src/index.ts +17 -6
- package/src/live.ts +185 -0
- package/src/meta-review.ts +24 -10
- package/src/method-scope.ts +5 -1
- package/src/paths.ts +5 -0
- package/src/review-scope.ts +11 -7
- package/src/review.ts +82 -25
- package/src/session-hooks.ts +90 -0
- package/src/skill-prompt.ts +101 -19
- package/src/tools/checkpoint.ts +10 -3
- package/src/tools/context.ts +14 -3
- package/src/tools/decision-log.ts +27 -10
- package/src/tools/fix.ts +114 -3
- package/src/tools/prune.ts +14 -11
- package/src/tools/review.ts +5 -2
- package/src/tools/transcript.ts +334 -0
- package/src/tools/triage.ts +9 -6
- package/src/tools/validate.ts +5 -2
- package/src/transcript.ts +475 -0
- package/src/types.ts +129 -0
package/lib/client.js
CHANGED
|
@@ -40,7 +40,10 @@ var SEVERITY_LABEL = {
|
|
|
40
40
|
var SEVERITY_COLOR = {
|
|
41
41
|
critical: "#ef4444",
|
|
42
42
|
high: "#f97316",
|
|
43
|
-
medium
|
|
43
|
+
// medium is used both for dots/fills and as TEXT (stat numbers, table
|
|
44
|
+
// headers); #eab308 is illegible as text on light backgrounds (~1.6:1).
|
|
45
|
+
// amber-600 (#d97706) ~3.2:1 — still short of AA; go darker for legibility.
|
|
46
|
+
medium: "#b45309",
|
|
44
47
|
low: "#6b7280"
|
|
45
48
|
};
|
|
46
49
|
function safeGet(o, key) {
|
|
@@ -194,8 +197,6 @@ function findReportInObject(obj, seen, maxDepth = 20) {
|
|
|
194
197
|
}
|
|
195
198
|
function scanSessionForReport(session) {
|
|
196
199
|
if (!session || typeof session !== "object") return null;
|
|
197
|
-
const direct = findReportInObject(session);
|
|
198
|
-
if (direct) return direct;
|
|
199
200
|
const s = (
|
|
200
201
|
/** @type {Record<string, unknown>} */
|
|
201
202
|
session
|
|
@@ -263,6 +264,213 @@ function scanSessionForReport(session) {
|
|
|
263
264
|
}
|
|
264
265
|
return null;
|
|
265
266
|
}
|
|
267
|
+
function isTranscriptManifest(obj) {
|
|
268
|
+
if (!obj || typeof obj !== "object") return false;
|
|
269
|
+
const o = (
|
|
270
|
+
/** @type {Record<string, unknown>} */
|
|
271
|
+
obj
|
|
272
|
+
);
|
|
273
|
+
return typeof safeGet(o, "version") === "number" && Array.isArray(safeGet(o, "rounds")) && Array.isArray(safeGet(o, "convergence"));
|
|
274
|
+
}
|
|
275
|
+
function attachLive(manifest, source) {
|
|
276
|
+
if (!manifest || typeof manifest !== "object") return manifest;
|
|
277
|
+
const live = source && typeof source === "object" ? safeGet(source, "live") : void 0;
|
|
278
|
+
if (!Array.isArray(live)) return manifest;
|
|
279
|
+
const m = (
|
|
280
|
+
/** @type {Record<string, unknown>} */
|
|
281
|
+
manifest
|
|
282
|
+
);
|
|
283
|
+
if (safeGet(m, "live") !== void 0) return manifest;
|
|
284
|
+
const copy = (
|
|
285
|
+
/** @type {Record<string, unknown>} */
|
|
286
|
+
{}
|
|
287
|
+
);
|
|
288
|
+
for (const k of safeKeys(m)) copy[k] = safeGet(m, k);
|
|
289
|
+
copy.live = live;
|
|
290
|
+
return copy;
|
|
291
|
+
}
|
|
292
|
+
function extractTranscript(obj) {
|
|
293
|
+
if (typeof obj === "string") {
|
|
294
|
+
try {
|
|
295
|
+
const parsed = JSON.parse(obj);
|
|
296
|
+
return extractTranscript(parsed);
|
|
297
|
+
} catch {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (!obj || typeof obj !== "object") return null;
|
|
302
|
+
if (isTranscriptManifest(obj)) return (
|
|
303
|
+
/** @type {Record<string, unknown>} */
|
|
304
|
+
obj
|
|
305
|
+
);
|
|
306
|
+
const o = (
|
|
307
|
+
/** @type {Record<string, unknown>} */
|
|
308
|
+
obj
|
|
309
|
+
);
|
|
310
|
+
if (safeGet(o, "operation") === "capture") {
|
|
311
|
+
const t = safeGet(o, "transcript");
|
|
312
|
+
if (t && typeof t === "object" && isTranscriptManifest(t)) {
|
|
313
|
+
return attachLive(
|
|
314
|
+
/** @type {Record<string, unknown>} */
|
|
315
|
+
t,
|
|
316
|
+
o
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
for (const key of ["message", "result", "content"]) {
|
|
321
|
+
const val = safeGet(o, key);
|
|
322
|
+
if (val !== void 0) {
|
|
323
|
+
if (Array.isArray(val)) {
|
|
324
|
+
for (const item of val) {
|
|
325
|
+
const found = extractTranscript(item);
|
|
326
|
+
if (found) return attachLive(found, o);
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
const found = extractTranscript(val);
|
|
330
|
+
if (found) return attachLive(found, o);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return attachLive(findTranscriptInObject(o), o);
|
|
335
|
+
}
|
|
336
|
+
function findTranscriptInObject(obj, seen, maxDepth = 20) {
|
|
337
|
+
if (maxDepth <= 0) return null;
|
|
338
|
+
if (!obj || typeof obj !== "object") return null;
|
|
339
|
+
const s = seen || /* @__PURE__ */ new Set();
|
|
340
|
+
if (s.has(obj)) return null;
|
|
341
|
+
s.add(obj);
|
|
342
|
+
if (isTranscriptManifest(obj)) return (
|
|
343
|
+
/** @type {Record<string, unknown>} */
|
|
344
|
+
obj
|
|
345
|
+
);
|
|
346
|
+
if (Array.isArray(obj)) {
|
|
347
|
+
for (const item of obj) {
|
|
348
|
+
const found = findTranscriptInObject(item, s, maxDepth - 1);
|
|
349
|
+
if (found) return found;
|
|
350
|
+
}
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
const o = (
|
|
354
|
+
/** @type {Record<string, unknown>} */
|
|
355
|
+
obj
|
|
356
|
+
);
|
|
357
|
+
for (const key of safeKeys(o)) {
|
|
358
|
+
const val = safeGet(o, key);
|
|
359
|
+
if (val && typeof val === "object") {
|
|
360
|
+
const found = findTranscriptInObject(val, s, maxDepth - 1);
|
|
361
|
+
if (found) return found;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
function scanSessionForTranscript(session) {
|
|
367
|
+
if (!session || typeof session !== "object") return null;
|
|
368
|
+
const s = (
|
|
369
|
+
/** @type {Record<string, unknown>} */
|
|
370
|
+
session
|
|
371
|
+
);
|
|
372
|
+
const toolCalls = safeGet(s, "toolCalls");
|
|
373
|
+
if (Array.isArray(toolCalls)) {
|
|
374
|
+
const calls = (
|
|
375
|
+
/** @type {Array<Record<string, unknown>>} */
|
|
376
|
+
toolCalls
|
|
377
|
+
);
|
|
378
|
+
for (let i = calls.length - 1; i >= 0; i--) {
|
|
379
|
+
const call = calls[i];
|
|
380
|
+
if (!call) continue;
|
|
381
|
+
const tool = String(safeGet(call, "tool") ?? "");
|
|
382
|
+
if (tool !== "iterate_transcript" && !tool.endsWith("iterate_transcript")) continue;
|
|
383
|
+
const found = extractTranscript(safeGet(call, "result")) || extractTranscript(safeGet(call, "message"));
|
|
384
|
+
if (found) return found;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const messages = safeGet(s, "messages");
|
|
388
|
+
if (Array.isArray(messages)) {
|
|
389
|
+
const msgs = (
|
|
390
|
+
/** @type {Array<Record<string, unknown>>} */
|
|
391
|
+
messages
|
|
392
|
+
);
|
|
393
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
394
|
+
const msg = msgs[i];
|
|
395
|
+
if (!msg) continue;
|
|
396
|
+
const calls = safeGet(msg, "tool_calls");
|
|
397
|
+
if (Array.isArray(calls)) {
|
|
398
|
+
for (const call of calls) {
|
|
399
|
+
if (!call) continue;
|
|
400
|
+
const args = safeGet(call, "arguments");
|
|
401
|
+
if (typeof args === "string") {
|
|
402
|
+
const found2 = extractTranscript(args);
|
|
403
|
+
if (found2) return found2;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const found = extractTranscript(safeGet(msg, "content"));
|
|
408
|
+
if (found) return found;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
function normalizeTranscript(manifest) {
|
|
414
|
+
const src = manifest && typeof manifest === "object" ? (
|
|
415
|
+
/** @type {Record<string, unknown>} */
|
|
416
|
+
manifest
|
|
417
|
+
) : {};
|
|
418
|
+
const asNum = (v) => typeof v === "number" ? v : 0;
|
|
419
|
+
const asStr = (v) => typeof v === "string" ? v : "";
|
|
420
|
+
const asBool = (v) => v === true;
|
|
421
|
+
const asCount = (v) => typeof v === "number" ? v : 0;
|
|
422
|
+
const asArray = (v) => Array.isArray(v) ? (
|
|
423
|
+
/** @type {Array<Record<string, unknown>>} */
|
|
424
|
+
v
|
|
425
|
+
) : [];
|
|
426
|
+
const rounds = asArray(safeGet(src, "rounds")).map((r) => ({
|
|
427
|
+
round: asNum(safeGet(r, "round")),
|
|
428
|
+
threads: asArray(safeGet(r, "threads")).map((t) => ({
|
|
429
|
+
dimension: asStr(safeGet(t, "dimension")),
|
|
430
|
+
attempt: asNum(safeGet(t, "attempt")),
|
|
431
|
+
messages: asArray(safeGet(t, "messages")).map((m) => typeof m === "string" ? m : ""),
|
|
432
|
+
readFiles: asArray(safeGet(t, "readFiles")).map((f) => typeof f === "string" ? f : ""),
|
|
433
|
+
findings: asArray(safeGet(t, "findings")).map((f) => ({ ...f }))
|
|
434
|
+
}))
|
|
435
|
+
}));
|
|
436
|
+
const cp = safeGet(src, "checkpoint");
|
|
437
|
+
const checkpoint = cp && typeof cp === "object" ? {
|
|
438
|
+
mode: asStr(safeGet(cp, "mode")),
|
|
439
|
+
round: asNum(safeGet(cp, "round")),
|
|
440
|
+
maxRounds: asNum(safeGet(cp, "maxRounds")),
|
|
441
|
+
fixedCount: asNum(safeGet(cp, "fixedCount")),
|
|
442
|
+
resumeCount: asNum(safeGet(cp, "resumeCount")),
|
|
443
|
+
updatedAt: asStr(safeGet(cp, "updatedAt"))
|
|
444
|
+
} : null;
|
|
445
|
+
const ng = safeGet(src, "nudge");
|
|
446
|
+
const nudge = ng && typeof ng === "object" ? { timestamp: asStr(safeGet(ng, "timestamp")), text: asStr(safeGet(ng, "text")) } : null;
|
|
447
|
+
const ap = safeGet(src, "approval");
|
|
448
|
+
return {
|
|
449
|
+
version: asNum(safeGet(src, "version")),
|
|
450
|
+
project: asStr(safeGet(src, "project")),
|
|
451
|
+
updatedAt: asStr(safeGet(src, "updatedAt")),
|
|
452
|
+
active: asBool(safeGet(src, "active")),
|
|
453
|
+
mode: asStr(safeGet(src, "mode")) || null,
|
|
454
|
+
goal: asStr(safeGet(src, "goal")),
|
|
455
|
+
phases: asArray(safeGet(src, "phases")).map((p) => typeof p === "string" ? p : ""),
|
|
456
|
+
round: asNum(safeGet(src, "round")),
|
|
457
|
+
maxRounds: asNum(safeGet(src, "maxRounds")),
|
|
458
|
+
rounds,
|
|
459
|
+
convergence: asArray(safeGet(src, "convergence")).map((n) => asCount(n)),
|
|
460
|
+
findings: asArray(safeGet(src, "findings")).map((f) => ({ ...f })),
|
|
461
|
+
fixes: asArray(safeGet(src, "fixes")).map((f) => ({ ...f })),
|
|
462
|
+
live: asArray(safeGet(src, "live")).map((e) => ({
|
|
463
|
+
ts: typeof safeGet(e, "ts") === "number" ? String(safeGet(e, "ts")) : asStr(safeGet(e, "ts")),
|
|
464
|
+
type: asStr(safeGet(e, "type")),
|
|
465
|
+
tool: asStr(safeGet(e, "tool")),
|
|
466
|
+
target: asStr(safeGet(e, "target"))
|
|
467
|
+
})),
|
|
468
|
+
checkpoint,
|
|
469
|
+
timeline: asArray(safeGet(src, "timeline")).map((t) => ({ ...t })),
|
|
470
|
+
nudge,
|
|
471
|
+
approval: ap && typeof ap === "object" ? { active: asBool(safeGet(ap, "active")), policy: asStr(safeGet(ap, "policy")) || "ask" } : { active: false, policy: "ask" }
|
|
472
|
+
};
|
|
473
|
+
}
|
|
266
474
|
function isRunSummary(obj) {
|
|
267
475
|
if (!obj || typeof obj !== "object") return false;
|
|
268
476
|
const o = (
|
|
@@ -304,8 +512,6 @@ function findRunSummaryInObject(obj, seen, maxDepth = 20) {
|
|
|
304
512
|
}
|
|
305
513
|
function scanSessionForRunSummary(session) {
|
|
306
514
|
if (!session || typeof session !== "object") return null;
|
|
307
|
-
const direct = findRunSummaryInObject(session);
|
|
308
|
-
if (direct) return direct;
|
|
309
515
|
const s = (
|
|
310
516
|
/** @type {Record<string, unknown>} */
|
|
311
517
|
session
|
|
@@ -503,22 +709,24 @@ function buildTriageState(report) {
|
|
|
503
709
|
return state;
|
|
504
710
|
}
|
|
505
711
|
function hashReport(report) {
|
|
506
|
-
const
|
|
507
|
-
/** @type {Record<string, unknown>} */
|
|
508
|
-
report.convergence ?? {}
|
|
509
|
-
);
|
|
510
|
-
const totalRounds = String(convergence.totalRounds ?? "");
|
|
511
|
-
const findingsCount = String(
|
|
512
|
-
/** @type {Array<unknown>} */
|
|
513
|
-
(report.findings ?? []).length
|
|
514
|
-
);
|
|
515
|
-
const firstFinding = (
|
|
712
|
+
const findings = (
|
|
516
713
|
/** @type {Array<Record<string, unknown>>} */
|
|
517
|
-
|
|
714
|
+
report.findings ?? []
|
|
518
715
|
);
|
|
519
|
-
|
|
520
|
-
const
|
|
521
|
-
|
|
716
|
+
let h = 2166136261;
|
|
717
|
+
const mix = (s) => {
|
|
718
|
+
for (let i = 0; i < s.length; i++) {
|
|
719
|
+
h ^= s.charCodeAt(i);
|
|
720
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
mix(`${String(report.mode ?? "")}|`);
|
|
724
|
+
for (const f of findings) {
|
|
725
|
+
if (!f || typeof f !== "object") continue;
|
|
726
|
+
mix(`${String(f.file ?? "")}|${typeof f.line === "number" ? f.line : 0}|${String(f.dimension ?? "")}|${String(f.summary ?? "")}
|
|
727
|
+
`);
|
|
728
|
+
}
|
|
729
|
+
return `iterate-triage-${h.toString(36)}`;
|
|
522
730
|
}
|
|
523
731
|
function toKnownIntentionalYaml(entries) {
|
|
524
732
|
if (!entries || entries.length === 0) return "";
|
|
@@ -955,6 +1163,27 @@ var ITERATE_CSS = `
|
|
|
955
1163
|
.iterate-switch[data-on] { background: var(--dsw-alias-brand-primary); border-color: var(--dsw-alias-brand-primary); }
|
|
956
1164
|
.iterate-switch[data-on] .iterate-switch-knob { transform: translateX(18px); background: #FFFFFF; }
|
|
957
1165
|
|
|
1166
|
+
/* Shared keyboard focus ring for every iterate interactive control */
|
|
1167
|
+
.iterate-btn:focus-visible, .iterate-vbtn:focus-visible, .iterate-batch-btn:focus-visible,
|
|
1168
|
+
.iterate-filter-select:focus-visible, .iterate-filter-search:focus-visible,
|
|
1169
|
+
.iterate-finding:focus-visible, .iterate-switch:focus-visible {
|
|
1170
|
+
outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/* Dashboard empty/onboarding state */
|
|
1174
|
+
.iterate-dashboard-empty { opacity: 0.75; }
|
|
1175
|
+
.iterate-empty-hint { font-size: 12px; color: var(--dsw-alias-label-secondary); }
|
|
1176
|
+
|
|
1177
|
+
/* Convergence-completed progress fill */
|
|
1178
|
+
.iterate-progress-fill-done { background: var(--dsw-alias-state-success-primary); }
|
|
1179
|
+
|
|
1180
|
+
/* Batch scope segmented control */
|
|
1181
|
+
.iterate-batch-scope { opacity: 0.6; }
|
|
1182
|
+
.iterate-batch-scope-on { opacity: 1; border-color: var(--dsw-alias-brand-primary); color: var(--dsw-alias-label-primary); }
|
|
1183
|
+
|
|
1184
|
+
/* Overflow dimension chip */
|
|
1185
|
+
.iterate-dim-more { opacity: 0.7; font-style: italic; }
|
|
1186
|
+
|
|
958
1187
|
/* Button variants */
|
|
959
1188
|
.iterate-btn[data-ghost] { background: transparent; }
|
|
960
1189
|
.iterate-btn[data-danger] { border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 45%, transparent); color: var(--dsw-alias-state-error-primary); background: transparent; }
|
|
@@ -965,6 +1194,38 @@ var ITERATE_CSS = `
|
|
|
965
1194
|
.iterate-guide { margin-top: 4px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; overflow: hidden; background: var(--dsw-alias-bg-layer-2); }
|
|
966
1195
|
.iterate-guide-bar { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 12px; background: var(--dsw-alias-bg-layer-2); border-bottom: 1px solid var(--dsw-alias-border-l1); font-size: 12px; color: var(--dsw-alias-label-secondary); }
|
|
967
1196
|
.iterate-guide-body { padding: 12px 14px; font-family: var(--dsw-font-mono, ui-monospace, monospace); font-size: 11.5px; line-height: 1.75; white-space: pre-wrap; color: var(--dsw-alias-label-primary); max-height: 260px; overflow: auto; }
|
|
1197
|
+
|
|
1198
|
+
/* \u2500\u2500 Runtime observatory panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
1199
|
+
.iterate-obs { margin: 6px 0; border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px; background: var(--dsw-alias-bg-layer-1); overflow: hidden; font-size: 12px; color: var(--dsw-alias-label-primary); }
|
|
1200
|
+
.iterate-obs-head { display: flex; align-items: center; gap: 10px; padding: 8px 14px; cursor: pointer; border-bottom: 1px solid var(--dsw-alias-border-l1); flex-wrap: wrap; }
|
|
1201
|
+
.iterate-obs-head[data-closed] { border-bottom: none; }
|
|
1202
|
+
.iterate-obs-title { font-weight: 650; }
|
|
1203
|
+
.iterate-obs-badge { display: inline-flex; align-items: center; gap: 6px; padding: 2px 10px; border-radius: 999px; background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent); color: var(--dsw-alias-brand-primary); font-size: 11px; font-weight: 600; white-space: nowrap; }
|
|
1204
|
+
.iterate-obs-badge[data-live] { color: var(--dsw-alias-state-success-primary); background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent); }
|
|
1205
|
+
.iterate-obs-head-meta { margin-left: auto; font-size: 11px; color: var(--dsw-alias-label-secondary); white-space: nowrap; }
|
|
1206
|
+
.iterate-obs-tabs { display: flex; gap: 4px; padding: 8px 14px 0; flex-wrap: wrap; border-bottom: 1px solid var(--dsw-alias-border-l1); }
|
|
1207
|
+
.iterate-obs-tab { padding: 4px 10px; border-radius: 7px; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-secondary); font-size: 11px; cursor: pointer; }
|
|
1208
|
+
.iterate-obs-tab[data-active] { border-color: var(--dsw-alias-brand-primary); color: var(--dsw-alias-brand-primary); }
|
|
1209
|
+
.iterate-obs-body { padding: 12px 14px; max-height: 460px; overflow: auto; }
|
|
1210
|
+
.iterate-obs-block { border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; overflow: hidden; margin-bottom: 8px; }
|
|
1211
|
+
.iterate-obs-block:last-child { margin-bottom: 0; }
|
|
1212
|
+
.iterate-obs-block-head { display: flex; align-items: center; gap: 8px; padding: 7px 10px; background: var(--dsw-alias-bg-layer-2); border-bottom: 1px solid var(--dsw-alias-border-l1); font-size: 11px; color: var(--dsw-alias-label-primary); flex-wrap: wrap; }
|
|
1213
|
+
.iterate-obs-block-head[data-click] { cursor: pointer; }
|
|
1214
|
+
.iterate-obs-block-body { padding: 8px 10px; }
|
|
1215
|
+
.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; }
|
|
1216
|
+
.iterate-obs-msg { padding: 2px 0; color: var(--dsw-alias-label-secondary); line-height: 1.5; font-size: 11px; word-break: break-word; }
|
|
1217
|
+
.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; }
|
|
1218
|
+
.iterate-obs-bar { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--dsw-alias-label-secondary); flex-wrap: wrap; }
|
|
1219
|
+
.iterate-obs-bar b { color: var(--dsw-alias-label-primary); font-weight: 600; }
|
|
1220
|
+
.iterate-obs-mono { font-family: var(--dsw-font-mono, ui-monospace, monospace); }
|
|
1221
|
+
.iterate-obs-code { padding: 6px 8px; margin-top: 4px; border-radius: 6px; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); font-family: var(--dsw-font-mono, ui-monospace, monospace); font-size: 10.5px; color: var(--dsw-alias-label-secondary); white-space: pre-wrap; word-break: break-all; }
|
|
1222
|
+
.iterate-obs-input { width: 100%; 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-size: 11px; resize: vertical; }
|
|
1223
|
+
.iterate-obs-input:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
|
|
1224
|
+
.iterate-obs-input::placeholder { color: var(--dsw-alias-label-secondary); }
|
|
1225
|
+
.iterate-obs-empty { font-size: 11px; color: var(--dsw-alias-label-secondary); padding: 8px 0; }
|
|
1226
|
+
.iterate-obs-row { display: flex; align-items: center; gap: 8px; padding: 5px 0; border-bottom: 1px solid var(--dsw-alias-border-l1); font-size: 11px; color: var(--dsw-alias-label-secondary); flex-wrap: wrap; }
|
|
1227
|
+
.iterate-obs-row:last-child { border-bottom: none; }
|
|
1228
|
+
.iterate-obs-livebadge { display: inline-flex; align-items: center; gap: 4px; padding: 1px 7px; border-radius: 6px; border: 1px solid currentColor; background: color-mix(in srgb, currentColor 10%, transparent); font-size: 10.5px; font-weight: 600; white-space: nowrap; }
|
|
968
1229
|
`;
|
|
969
1230
|
function log(...args) {
|
|
970
1231
|
if (typeof console !== "undefined" && typeof console.error === "function") {
|
|
@@ -1025,13 +1286,12 @@ function removeStorageByPrefix(prefix) {
|
|
|
1025
1286
|
}
|
|
1026
1287
|
function copyText(text) {
|
|
1027
1288
|
if (typeof navigator !== "undefined" && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
|
|
1028
|
-
navigator.clipboard.writeText(text).then(
|
|
1289
|
+
return navigator.clipboard.writeText(text).then(
|
|
1029
1290
|
() => true,
|
|
1030
1291
|
() => false
|
|
1031
1292
|
);
|
|
1032
|
-
return true;
|
|
1033
1293
|
}
|
|
1034
|
-
return false;
|
|
1294
|
+
return Promise.resolve(false);
|
|
1035
1295
|
}
|
|
1036
1296
|
var SEVERITY_KEYS = ["critical", "high", "medium", "low"];
|
|
1037
1297
|
function coerceSeverity(severity) {
|
|
@@ -1111,7 +1371,12 @@ function TrendChart({ points }) {
|
|
|
1111
1371
|
style: { height: `${Math.max(4, Math.round(p.count / max * 24))}px` }
|
|
1112
1372
|
})
|
|
1113
1373
|
);
|
|
1114
|
-
|
|
1374
|
+
const summary = points.map((p) => `Round ${p.round}: ${p.count}`).join(", ");
|
|
1375
|
+
return React.createElement("div", {
|
|
1376
|
+
className: "iterate-trend",
|
|
1377
|
+
role: "img",
|
|
1378
|
+
"aria-label": `\u5404\u8F6E\u53D1\u73B0\u6570\u91CF\u8D8B\u52BF\uFF1A${summary}`
|
|
1379
|
+
}, ...bars);
|
|
1115
1380
|
}
|
|
1116
1381
|
function ConvergenceDashboard(props) {
|
|
1117
1382
|
const [pulseKey, setPulseKey] = React.useState(0);
|
|
@@ -1124,7 +1389,14 @@ function ConvergenceDashboard(props) {
|
|
|
1124
1389
|
emitRoundPulse(cur, conv?.converged === true);
|
|
1125
1390
|
setPulseKey((k) => k + 1);
|
|
1126
1391
|
}, [report && hashReport(report) + ":" + getCurrentRound(report)]);
|
|
1127
|
-
if (!report)
|
|
1392
|
+
if (!report) {
|
|
1393
|
+
return React.createElement(
|
|
1394
|
+
"div",
|
|
1395
|
+
{ "data-iterate-root": "", "data-iterate": "dashboard", className: "iterate-dashboard iterate-dashboard-empty" },
|
|
1396
|
+
React.createElement("span", { className: "iterate-round-badge" }, "iterate"),
|
|
1397
|
+
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\u8BD5\u8BD5\u300Creview this project\u300D\u6216\u300C/iterate review-only\u300D")
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1128
1400
|
const round = getCurrentRound(report);
|
|
1129
1401
|
const total = getTotalRounds(report);
|
|
1130
1402
|
const progress = computeConvergenceProgress(report);
|
|
@@ -1143,13 +1415,24 @@ function ConvergenceDashboard(props) {
|
|
|
1143
1415
|
key: "images",
|
|
1144
1416
|
title: "\u4F1A\u8BDD\u4E2D\u68C0\u6D4B\u5230\u7528\u6237\u9644\u5E26\u7684\u56FE\u7247\uFF0C\u8BC4\u5BA1\u5C06\u4F5C\u4E3A\u89C6\u89C9\u8BC1\u636E\u53C2\u8003"
|
|
1145
1417
|
}, `\u9644\u4EF6\u56FE\u7247 ${String(imageCount)}`) : null;
|
|
1146
|
-
const
|
|
1418
|
+
const dimNames = Object.keys(dims);
|
|
1419
|
+
const dimBadges = dimNames.slice(0, 6).map(
|
|
1147
1420
|
(dim) => React.createElement(
|
|
1148
1421
|
"span",
|
|
1149
1422
|
{ key: dim, className: "iterate-dim-badge" },
|
|
1150
1423
|
`${dim} \xB7 ${dims[dim]?.length ?? 0}`
|
|
1151
1424
|
)
|
|
1152
1425
|
);
|
|
1426
|
+
const overflow = dimNames.length - 6;
|
|
1427
|
+
if (overflow > 0) {
|
|
1428
|
+
dimBadges.push(
|
|
1429
|
+
React.createElement(
|
|
1430
|
+
"span",
|
|
1431
|
+
{ key: "+more", className: "iterate-dim-badge iterate-dim-more", title: dimNames.slice(6).join(", ") },
|
|
1432
|
+
`+${overflow} \u66F4\u591A`
|
|
1433
|
+
)
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1153
1436
|
const mode = report.mode;
|
|
1154
1437
|
const summary = report.summary;
|
|
1155
1438
|
const isNormal = mode === "normal";
|
|
@@ -1159,6 +1442,18 @@ function ConvergenceDashboard(props) {
|
|
|
1159
1442
|
key: "fixes",
|
|
1160
1443
|
title: "\u672C\u8F6E\u5DF2\u5E94\u7528\u7684\u539F\u5B50\u4FEE\u590D\u6570\uFF08\u6B63\u5E38\u6A21\u5F0F\uFF09"
|
|
1161
1444
|
}, `${String(fixCount)} fixes`) : null;
|
|
1445
|
+
const converged = report.convergence && report.convergence.converged === true;
|
|
1446
|
+
const convChip = converged ? React.createElement("span", {
|
|
1447
|
+
className: "iterate-chip-resume",
|
|
1448
|
+
key: "converged",
|
|
1449
|
+
title: "\u5BA1\u67E5\u5DF2\u6536\u655B\uFF1A\u6700\u540E\u4E00\u8F6E\u672A\u53D1\u73B0\u65B0\u95EE\u9898"
|
|
1450
|
+
}, "\u2713 \u5DF2\u6536\u655B") : null;
|
|
1451
|
+
const sevMetric = (key, label) => React.createElement(
|
|
1452
|
+
"span",
|
|
1453
|
+
{ className: "iterate-metric", key, title: label },
|
|
1454
|
+
React.createElement("span", { className: "iterate-sev-dot", style: { background: SEVERITY_COLOR[key] } }),
|
|
1455
|
+
`${label} ${String(stats[key])}`
|
|
1456
|
+
);
|
|
1162
1457
|
return React.createElement(
|
|
1163
1458
|
"div",
|
|
1164
1459
|
{ "data-iterate-root": "", "data-iterate": "dashboard", className: "iterate-dashboard" },
|
|
@@ -1170,26 +1465,16 @@ function ConvergenceDashboard(props) {
|
|
|
1170
1465
|
React.createElement(
|
|
1171
1466
|
"div",
|
|
1172
1467
|
{ className: "iterate-progress" },
|
|
1173
|
-
React.createElement("div", {
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
{ className: "iterate-metric" },
|
|
1178
|
-
React.createElement("span", { className: "iterate-sev-dot", style: { background: SEVERITY_COLOR.critical } }),
|
|
1179
|
-
stats.critical
|
|
1180
|
-
),
|
|
1181
|
-
React.createElement(
|
|
1182
|
-
"span",
|
|
1183
|
-
{ className: "iterate-metric" },
|
|
1184
|
-
React.createElement("span", { className: "iterate-sev-dot", style: { background: SEVERITY_COLOR.high } }),
|
|
1185
|
-
stats.high
|
|
1186
|
-
),
|
|
1187
|
-
React.createElement(
|
|
1188
|
-
"span",
|
|
1189
|
-
{ className: "iterate-metric" },
|
|
1190
|
-
React.createElement("span", { className: "iterate-sev-dot", style: { background: SEVERITY_COLOR.medium } }),
|
|
1191
|
-
stats.medium
|
|
1468
|
+
React.createElement("div", {
|
|
1469
|
+
className: converged ? "iterate-progress-fill iterate-progress-fill-done" : "iterate-progress-fill",
|
|
1470
|
+
style: { width: `${progress}%` }
|
|
1471
|
+
})
|
|
1192
1472
|
),
|
|
1473
|
+
convChip,
|
|
1474
|
+
sevMetric("critical", "CRIT"),
|
|
1475
|
+
sevMetric("high", "HIGH"),
|
|
1476
|
+
sevMetric("medium", "MED"),
|
|
1477
|
+
sevMetric("low", "LOW"),
|
|
1193
1478
|
fixBadge,
|
|
1194
1479
|
resumeChip,
|
|
1195
1480
|
imageChip,
|
|
@@ -1313,12 +1598,11 @@ function TriagePanel(props) {
|
|
|
1313
1598
|
const [filter, setFilter] = React.useState({ severities: [], dimensions: [], search: "" });
|
|
1314
1599
|
const [selected, setSelected] = React.useState(null);
|
|
1315
1600
|
const [selectAll, setSelectAll] = React.useState(false);
|
|
1316
|
-
|
|
1317
|
-
if (storage) storage.set(storageKey, JSON.stringify(
|
|
1318
|
-
|
|
1319
|
-
};
|
|
1601
|
+
React.useEffect(() => {
|
|
1602
|
+
if (storage) storage.set(storageKey, JSON.stringify(verdicts));
|
|
1603
|
+
}, [storageKey, verdicts]);
|
|
1320
1604
|
const setVerdict = (index, verdict) => {
|
|
1321
|
-
setVerdicts((prev) =>
|
|
1605
|
+
setVerdicts((prev) => ({ ...prev, [String(index)]: verdict }));
|
|
1322
1606
|
};
|
|
1323
1607
|
const { filtered, indices } = filterFindingsWithIndices(findings, filter);
|
|
1324
1608
|
const indicesKey = indices.join(",");
|
|
@@ -1331,21 +1615,20 @@ function TriagePanel(props) {
|
|
|
1331
1615
|
const allIndices = allVerdictKeys(verdicts);
|
|
1332
1616
|
const batchTarget = selectAll ? allIndices : indices;
|
|
1333
1617
|
const applyBatch = (verdict) => {
|
|
1334
|
-
setVerdicts((prev) =>
|
|
1335
|
-
};
|
|
1336
|
-
const applyBatchAll = (verdict) => {
|
|
1337
|
-
setVerdicts((prev) => persistVerdicts(setAllVerdicts(prev, verdict)));
|
|
1618
|
+
setVerdicts((prev) => batchSetVerdict(prev, batchTarget, verdict));
|
|
1338
1619
|
};
|
|
1339
1620
|
const doResetVerdicts = () => {
|
|
1340
|
-
setVerdicts((prev) =>
|
|
1621
|
+
setVerdicts((prev) => setAllVerdicts(prev, "keep"));
|
|
1341
1622
|
setSelectAll(false);
|
|
1342
1623
|
};
|
|
1343
1624
|
React.useEffect(() => {
|
|
1344
1625
|
const doc = typeof document !== "undefined" ? document : null;
|
|
1345
1626
|
if (!doc) return;
|
|
1346
1627
|
const onKeyDown = (ev) => {
|
|
1628
|
+
if (ev.metaKey || ev.ctrlKey || ev.altKey) return;
|
|
1347
1629
|
const t = ev.target;
|
|
1348
1630
|
if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT")) return;
|
|
1631
|
+
if (t && typeof t.isContentEditable === "boolean" && t.isContentEditable) return;
|
|
1349
1632
|
const verdict = keyToVerdict(ev.key);
|
|
1350
1633
|
if (verdict && selected !== null && indices.includes(selected)) {
|
|
1351
1634
|
ev.preventDefault();
|
|
@@ -1376,10 +1659,14 @@ function TriagePanel(props) {
|
|
|
1376
1659
|
const doCopyYaml = () => {
|
|
1377
1660
|
const yaml = toKnownIntentionalYaml(ignored);
|
|
1378
1661
|
if (!yaml) return;
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1662
|
+
copyText(yaml).then((ok) => {
|
|
1663
|
+
if (ok) {
|
|
1664
|
+
setCopied(true);
|
|
1665
|
+
setTimeout(() => setCopied(false), 1600);
|
|
1666
|
+
} else {
|
|
1667
|
+
setPayload(yaml);
|
|
1668
|
+
}
|
|
1669
|
+
});
|
|
1383
1670
|
};
|
|
1384
1671
|
const doBuildInstruction = () => {
|
|
1385
1672
|
const text = buildApplyInstruction(ignored);
|
|
@@ -1408,7 +1695,17 @@ function TriagePanel(props) {
|
|
|
1408
1695
|
key: String(index),
|
|
1409
1696
|
className: "iterate-finding",
|
|
1410
1697
|
"data-selected": isSelected ? "" : void 0,
|
|
1411
|
-
|
|
1698
|
+
role: "option",
|
|
1699
|
+
"aria-selected": isSelected,
|
|
1700
|
+
tabIndex: 0,
|
|
1701
|
+
onClick: () => setSelected(index),
|
|
1702
|
+
onFocus: () => setSelected(index),
|
|
1703
|
+
onKeyDown: (e) => {
|
|
1704
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1705
|
+
e.preventDefault();
|
|
1706
|
+
setSelected(index);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1412
1709
|
},
|
|
1413
1710
|
React.createElement(
|
|
1414
1711
|
"div",
|
|
@@ -1435,7 +1732,7 @@ function TriagePanel(props) {
|
|
|
1435
1732
|
React.createElement(
|
|
1436
1733
|
"div",
|
|
1437
1734
|
{ className: "iterate-triage-head" },
|
|
1438
|
-
React.createElement("span", {}, `Iterate \xB7 Findings \u5206\u8BCA (${filtered.length}/${findings.length})`),
|
|
1735
|
+
React.createElement("span", { role: "heading", "aria-level": 3 }, `Iterate \xB7 Findings \u5206\u8BCA (${filtered.length}/${findings.length})`),
|
|
1439
1736
|
React.createElement("span", { className: "iterate-triage-hint" }, "y=\u4FEE\u590D \xB7 n=\u8DF3\u8FC7 \xB7 a=\u5DF2\u77E5\u6709\u610F \xB7 \u2191/\u2193 \u9009\u62E9")
|
|
1440
1737
|
),
|
|
1441
1738
|
React.createElement(
|
|
@@ -1443,7 +1740,7 @@ function TriagePanel(props) {
|
|
|
1443
1740
|
{ className: "iterate-filter" },
|
|
1444
1741
|
React.createElement(
|
|
1445
1742
|
"select",
|
|
1446
|
-
{ className: "iterate-filter-select", value: filter.severities[0] || "", onChange: (e) => setSeverityFilter(e.target.value),
|
|
1743
|
+
{ className: "iterate-filter-select", value: filter.severities[0] || "", onChange: (e) => setSeverityFilter(e.target.value), "aria-label": "\u6309\u4E25\u91CD\u5EA6\u7B5B\u9009" },
|
|
1447
1744
|
React.createElement("option", { value: "" }, "\u5168\u90E8\u4E25\u91CD\u5EA6"),
|
|
1448
1745
|
...options.severities.map(
|
|
1449
1746
|
(s) => React.createElement("option", { key: s.value, value: s.value }, `${severityLabel(s.value)} (${s.count})`)
|
|
@@ -1451,7 +1748,7 @@ function TriagePanel(props) {
|
|
|
1451
1748
|
),
|
|
1452
1749
|
React.createElement(
|
|
1453
1750
|
"select",
|
|
1454
|
-
{ className: "iterate-filter-select", value: filter.dimensions[0] || "", onChange: (e) => setDimensionFilter(e.target.value),
|
|
1751
|
+
{ className: "iterate-filter-select", value: filter.dimensions[0] || "", onChange: (e) => setDimensionFilter(e.target.value), "aria-label": "\u6309\u7EF4\u5EA6\u7B5B\u9009" },
|
|
1455
1752
|
React.createElement("option", { value: "" }, "\u5168\u90E8\u7EF4\u5EA6"),
|
|
1456
1753
|
...options.dimensions.map(
|
|
1457
1754
|
(d) => React.createElement("option", { key: d.value, value: d.value }, `${d.value} (${d.count})`)
|
|
@@ -1461,6 +1758,7 @@ function TriagePanel(props) {
|
|
|
1461
1758
|
className: "iterate-filter-search",
|
|
1462
1759
|
type: "search",
|
|
1463
1760
|
placeholder: "\u641C\u7D22\u6587\u4EF6 / \u6458\u8981\u2026",
|
|
1761
|
+
"aria-label": "\u641C\u7D22\u6587\u4EF6\u6216\u6458\u8981",
|
|
1464
1762
|
value: filter.search,
|
|
1465
1763
|
onChange: (e) => setSearchFilter(e.target.value)
|
|
1466
1764
|
}),
|
|
@@ -1473,20 +1771,20 @@ function TriagePanel(props) {
|
|
|
1473
1771
|
React.createElement(
|
|
1474
1772
|
"div",
|
|
1475
1773
|
{ className: "iterate-batch" },
|
|
1476
|
-
React.createElement("span", { className: "iterate-batch-label" }, "\u6279\u91CF\uFF1A"),
|
|
1477
|
-
React.createElement(
|
|
1478
|
-
"
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
React.createElement("button", { className: "iterate-batch-btn", onClick: () =>
|
|
1488
|
-
React.createElement("button", { className: "iterate-batch-btn", onClick: () =>
|
|
1489
|
-
React.createElement("button", { className: "iterate-batch-btn", onClick: () =>
|
|
1774
|
+
React.createElement("span", { className: "iterate-batch-label" }, "\u6279\u91CF\u4F5C\u7528\u4E8E\uFF1A"),
|
|
1775
|
+
React.createElement("button", {
|
|
1776
|
+
className: selectAll ? "iterate-batch-btn iterate-batch-scope" : "iterate-batch-btn iterate-batch-scope iterate-batch-scope-on",
|
|
1777
|
+
onClick: () => setSelectAll(false),
|
|
1778
|
+
title: "\u6279\u91CF\u6309\u94AE\u4EC5\u4F5C\u7528\u4E8E\u5F53\u524D\u7B5B\u9009\u53EF\u89C1\u7684 findings"
|
|
1779
|
+
}, `\u53EF\u89C1 ${indices.length}`),
|
|
1780
|
+
React.createElement("button", {
|
|
1781
|
+
className: selectAll ? "iterate-batch-btn iterate-batch-scope iterate-batch-scope-on" : "iterate-batch-btn iterate-batch-scope",
|
|
1782
|
+
onClick: () => setSelectAll(true),
|
|
1783
|
+
title: "\u6279\u91CF\u6309\u94AE\u4F5C\u7528\u4E8E\u5168\u90E8 findings"
|
|
1784
|
+
}, `\u5168\u90E8 ${allIndices.length}`),
|
|
1785
|
+
React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("keep") }, "y"),
|
|
1786
|
+
React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("skip") }, "n"),
|
|
1787
|
+
React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("ignore") }, "a"),
|
|
1490
1788
|
React.createElement("button", { className: "iterate-batch-btn", onClick: doResetVerdicts, title: "\u628A\u6240\u6709\u5224\u5B9A\u6062\u590D\u4E3A\u9ED8\u8BA4 y\uFF08\u4FEE\u590D\uFF09" }, "\u91CD\u7F6E")
|
|
1491
1789
|
),
|
|
1492
1790
|
...rows,
|
|
@@ -1497,8 +1795,20 @@ function TriagePanel(props) {
|
|
|
1497
1795
|
React.createElement(
|
|
1498
1796
|
"span",
|
|
1499
1797
|
{ style: { display: "flex", gap: 6 } },
|
|
1500
|
-
React.createElement("button", {
|
|
1501
|
-
|
|
1798
|
+
React.createElement("button", {
|
|
1799
|
+
className: "iterate-btn",
|
|
1800
|
+
"data-primary": "",
|
|
1801
|
+
"data-copied": copied ? "" : void 0,
|
|
1802
|
+
onClick: doCopyYaml,
|
|
1803
|
+
disabled: ignoredCount === 0,
|
|
1804
|
+
title: ignoredCount === 0 ? "\u5F53\u524D\u6CA1\u6709\u6807\u8BB0\u4E3A\u300C\u5DF2\u77E5\u6709\u610F\u300D\u7684 finding" : "\u590D\u5236 known_intentional YAML"
|
|
1805
|
+
}, copied ? "\u5DF2\u590D\u5236" : `\u590D\u5236 known_intentional${ignoredCount > 0 ? `\uFF08${ignoredCount}\uFF09` : ""}`),
|
|
1806
|
+
React.createElement("button", {
|
|
1807
|
+
className: "iterate-btn",
|
|
1808
|
+
onClick: doBuildInstruction,
|
|
1809
|
+
disabled: ignoredCount === 0,
|
|
1810
|
+
title: ignoredCount === 0 ? "\u5F53\u524D\u6CA1\u6709\u6807\u8BB0\u4E3A\u300C\u5DF2\u77E5\u6709\u610F\u300D\u7684 finding" : "\u751F\u6210 iterate_triage \u5E94\u7528\u6307\u4EE4"
|
|
1811
|
+
}, "\u751F\u6210\u5E94\u7528\u6307\u4EE4")
|
|
1502
1812
|
)
|
|
1503
1813
|
),
|
|
1504
1814
|
payload ? React.createElement("div", { className: "iterate-payload" }, payload) : null
|
|
@@ -1607,8 +1917,9 @@ function SettingsPanel(_props) {
|
|
|
1607
1917
|
setTimeout(() => setter(false), 1600);
|
|
1608
1918
|
};
|
|
1609
1919
|
const doCopy = (text, slot) => {
|
|
1610
|
-
copyText(text)
|
|
1611
|
-
|
|
1920
|
+
copyText(text).then((ok) => {
|
|
1921
|
+
if (ok) flashCopied(slot);
|
|
1922
|
+
});
|
|
1612
1923
|
};
|
|
1613
1924
|
const requestClear = () => {
|
|
1614
1925
|
if (confirming) {
|
|
@@ -1768,6 +2079,574 @@ function SettingsPanel(_props) {
|
|
|
1768
2079
|
statusCard
|
|
1769
2080
|
);
|
|
1770
2081
|
}
|
|
2082
|
+
var OBS_TABS = [
|
|
2083
|
+
{ key: "live", label: "\u5B9E\u65F6\u6D3B\u52A8\u6D41" },
|
|
2084
|
+
{ key: "f1", label: "\u5BA1\u67E5\u7EBF\u7A0B" },
|
|
2085
|
+
{ key: "f2", label: "\u6536\u655B\u8D8B\u52BF" },
|
|
2086
|
+
{ key: "f3", label: "\u53D1\u73B0\u5B9A\u4F4D" },
|
|
2087
|
+
{ key: "f4", label: "\u4FEE\u590D\u4E0E\u56DE\u6EDA" },
|
|
2088
|
+
{ key: "f5", label: "\u65AD\u70B9\u6062\u590D" },
|
|
2089
|
+
{ key: "f6", label: "\u8FD0\u884C\u63A7\u5236\u53F0" },
|
|
2090
|
+
{ key: "f7", label: "\u51B3\u7B56\u65F6\u95F4\u7EBF" }
|
|
2091
|
+
];
|
|
2092
|
+
var OBS_LIVE_META = {
|
|
2093
|
+
read: { label: "\u9605\u8BFB", color: "var(--dsw-alias-brand-primary)" },
|
|
2094
|
+
fix: { label: "\u4FEE\u590D", color: "var(--dsw-alias-state-success-primary)" },
|
|
2095
|
+
rollback: { label: "\u56DE\u6EDA", color: "var(--dsw-alias-state-error-primary)" },
|
|
2096
|
+
diff: { label: "\u5DEE\u5F02", color: "var(--dsw-alias-state-warn-primary)" },
|
|
2097
|
+
review: { label: "\u5BA1\u67E5", color: "var(--dsw-alias-brand-primary)" },
|
|
2098
|
+
triage: { label: "\u5206\u6D41", color: "var(--dsw-alias-state-warn-primary)" },
|
|
2099
|
+
checkpoint: { label: "\u65AD\u70B9", color: "var(--dsw-alias-brand-primary)" },
|
|
2100
|
+
validate: { label: "\u6821\u9A8C", color: "var(--dsw-alias-state-success-primary)" },
|
|
2101
|
+
log: { label: "\u8BB0\u5F55", color: "var(--dsw-alias-label-secondary)" },
|
|
2102
|
+
prune: { label: "\u6E05\u7406", color: "var(--dsw-alias-state-warn-primary)" },
|
|
2103
|
+
info: { label: "\u4FE1\u606F", color: "var(--dsw-alias-label-secondary)" }
|
|
2104
|
+
};
|
|
2105
|
+
var OBS_LIVE_FALLBACK_COLOR = "var(--dsw-alias-label-secondary)";
|
|
2106
|
+
function formatObsLiveTime(ts) {
|
|
2107
|
+
if (!ts) return "";
|
|
2108
|
+
let date = null;
|
|
2109
|
+
const num = Number(ts);
|
|
2110
|
+
if (typeof ts === "string" && ts !== "" && !Number.isNaN(num) && /^\d+$/.test(ts)) {
|
|
2111
|
+
date = new Date(num);
|
|
2112
|
+
} else {
|
|
2113
|
+
const d = new Date(ts);
|
|
2114
|
+
date = Number.isNaN(d.getTime()) ? null : d;
|
|
2115
|
+
}
|
|
2116
|
+
if (!date) return String(ts);
|
|
2117
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
2118
|
+
const h = p(date.getHours());
|
|
2119
|
+
const mi = p(date.getMinutes());
|
|
2120
|
+
const s = p(date.getSeconds());
|
|
2121
|
+
return `${p(date.getDate())}/${p(date.getMonth() + 1)} ${h}:${mi}:${s}`;
|
|
2122
|
+
}
|
|
2123
|
+
function latestTranscript(session) {
|
|
2124
|
+
if (!session) return null;
|
|
2125
|
+
const raw = scanSessionForTranscript(session);
|
|
2126
|
+
return raw ? normalizeTranscript(raw) : null;
|
|
2127
|
+
}
|
|
2128
|
+
function buildObsFixInstruction(f) {
|
|
2129
|
+
const payload = JSON.stringify({
|
|
2130
|
+
file: String(f.file || ""),
|
|
2131
|
+
...typeof f.line === "number" && f.line > 0 ? { line: f.line } : {},
|
|
2132
|
+
dimension: String(f.dimension || ""),
|
|
2133
|
+
summary: String(f.summary || ""),
|
|
2134
|
+
...f.suggested_fix ? { suggested_fix: String(f.suggested_fix) } : {}
|
|
2135
|
+
}, null, 2);
|
|
2136
|
+
return `\u8BF7\u8C03\u7528 \`iterate_fix\` \u4FEE\u590D\u4EE5\u4E0B finding\uFF1A
|
|
2137
|
+
|
|
2138
|
+
\`\`\`json
|
|
2139
|
+
${payload}
|
|
2140
|
+
\`\`\``;
|
|
2141
|
+
}
|
|
2142
|
+
function ObservatoryPanel(props) {
|
|
2143
|
+
const session = props && props.session ? props.session : null;
|
|
2144
|
+
const manifest = latestTranscript(session);
|
|
2145
|
+
const [open, setOpen] = React.useState(false);
|
|
2146
|
+
const [tab, setTab] = React.useState("live");
|
|
2147
|
+
const [expandedThreads, setExpandedThreads] = React.useState(/* @__PURE__ */ new Set());
|
|
2148
|
+
const [copiedKey, setCopiedKey] = React.useState(null);
|
|
2149
|
+
const [nudgeText, setNudgeText] = React.useState("");
|
|
2150
|
+
const [timelineType, setTimelineType] = React.useState("");
|
|
2151
|
+
const [timelineSearch, setTimelineSearch] = React.useState("");
|
|
2152
|
+
const copyTimer = React.useRef(null);
|
|
2153
|
+
React.useEffect(() => () => {
|
|
2154
|
+
if (copyTimer.current) clearTimeout(copyTimer.current);
|
|
2155
|
+
}, []);
|
|
2156
|
+
const copyInstruction = (key, text) => {
|
|
2157
|
+
if (!text) return;
|
|
2158
|
+
copyText(text).then((ok) => {
|
|
2159
|
+
if (!ok) return;
|
|
2160
|
+
setCopiedKey(key);
|
|
2161
|
+
if (copyTimer.current) clearTimeout(copyTimer.current);
|
|
2162
|
+
copyTimer.current = setTimeout(() => setCopiedKey((cur) => cur === key ? null : cur), 1600);
|
|
2163
|
+
});
|
|
2164
|
+
};
|
|
2165
|
+
const toggleThread = (key) => {
|
|
2166
|
+
setExpandedThreads((prev) => {
|
|
2167
|
+
const next = new Set(prev);
|
|
2168
|
+
if (next.has(key)) next.delete(key);
|
|
2169
|
+
else next.add(key);
|
|
2170
|
+
return next;
|
|
2171
|
+
});
|
|
2172
|
+
};
|
|
2173
|
+
if (!manifest) {
|
|
2174
|
+
return React.createElement(
|
|
2175
|
+
"div",
|
|
2176
|
+
{ "data-iterate-root": "", "data-iterate": "obs", className: "iterate-obs" },
|
|
2177
|
+
React.createElement(
|
|
2178
|
+
"div",
|
|
2179
|
+
{ className: "iterate-obs-head", "data-closed": "" },
|
|
2180
|
+
React.createElement("span", { className: "iterate-obs-title" }, "iterate \u89C2\u6D4B\u53F0"),
|
|
2181
|
+
React.createElement("span", { className: "iterate-obs-head-meta" }, "\u6682\u65E0\u8FD0\u884C\u65F6\u89C2\u6D4B\u6570\u636E")
|
|
2182
|
+
)
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
const live = manifest.active === true;
|
|
2186
|
+
const roundMeta = typeof manifest.round === "number" ? typeof manifest.maxRounds === "number" ? `Round ${manifest.round}/${manifest.maxRounds}` : `Round ${manifest.round}` : "";
|
|
2187
|
+
const headMeta = [manifest.mode || "", roundMeta, manifest.updatedAt || ""].filter(Boolean).join(" \xB7 ");
|
|
2188
|
+
const threadFindingPill = (f, keyBase) => {
|
|
2189
|
+
const loc = `${String(f.file || "?")}${typeof f.line === "number" && f.line > 0 ? `:${f.line}` : ""}`;
|
|
2190
|
+
return React.createElement(
|
|
2191
|
+
"div",
|
|
2192
|
+
{ key: keyBase, className: "iterate-obs-row" },
|
|
2193
|
+
React.createElement("span", { className: "iterate-sev-dot", style: { background: severityColor(f.severity) } }),
|
|
2194
|
+
React.createElement("span", {}, severityLabel(f.severity)),
|
|
2195
|
+
React.createElement("span", { className: "iterate-obs-file" }, loc),
|
|
2196
|
+
React.createElement("span", {}, String(f.dimension || "")),
|
|
2197
|
+
React.createElement("span", { className: "iterate-obs-msg" }, String(f.summary || ""))
|
|
2198
|
+
);
|
|
2199
|
+
};
|
|
2200
|
+
const renderThreads = () => {
|
|
2201
|
+
const rounds = manifest.rounds || [];
|
|
2202
|
+
if (rounds.length === 0) {
|
|
2203
|
+
return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u5BA1\u67E5\u7EBF\u7A0B");
|
|
2204
|
+
}
|
|
2205
|
+
return React.createElement("div", {}, ...rounds.map((r, ri) => {
|
|
2206
|
+
const threads = r.threads || [];
|
|
2207
|
+
const fCount = threads.reduce((sum, t) => sum + (t.findings ? t.findings.length : 0), 0);
|
|
2208
|
+
const threadBlocks = threads.length === 0 ? [React.createElement("div", { key: "none", className: "iterate-obs-empty" }, "\u672C\u8F6E\u65E0\u7EBF\u7A0B")] : threads.map((t, ti) => {
|
|
2209
|
+
const key = `${ri}-${ti}`;
|
|
2210
|
+
const expanded = expandedThreads.has(key);
|
|
2211
|
+
const dim = t.dimension || "\u672A\u547D\u540D\u7EF4\u5EA6";
|
|
2212
|
+
const tFindings = t.findings || [];
|
|
2213
|
+
const files = t.readFiles || [];
|
|
2214
|
+
const msgs = t.messages || [];
|
|
2215
|
+
return React.createElement(
|
|
2216
|
+
"div",
|
|
2217
|
+
{ key, className: "iterate-obs-block" },
|
|
2218
|
+
React.createElement(
|
|
2219
|
+
"div",
|
|
2220
|
+
{ className: "iterate-obs-block-head", "data-click": "", onClick: () => toggleThread(key), title: expanded ? "\u6536\u8D77\u7EBF\u7A0B" : "\u5C55\u5F00\u7EBF\u7A0B" },
|
|
2221
|
+
React.createElement("span", {}, expanded ? "\u2212" : "+"),
|
|
2222
|
+
React.createElement("b", {}, dim),
|
|
2223
|
+
React.createElement("span", { className: "iterate-obs-chip" }, `attempt ${typeof t.attempt === "number" ? t.attempt : "?"}`),
|
|
2224
|
+
React.createElement("span", { className: "iterate-obs-chip" }, `${tFindings.length} findings`)
|
|
2225
|
+
),
|
|
2226
|
+
expanded ? React.createElement(
|
|
2227
|
+
"div",
|
|
2228
|
+
{ className: "iterate-obs-block-body" },
|
|
2229
|
+
React.createElement(
|
|
2230
|
+
"div",
|
|
2231
|
+
{ className: "iterate-obs-bar" },
|
|
2232
|
+
React.createElement("b", {}, "\u53D9\u8FF0"),
|
|
2233
|
+
React.createElement("span", { className: "iterate-obs-head-meta" }, `${msgs.length} \u6761`)
|
|
2234
|
+
),
|
|
2235
|
+
...msgs.length === 0 ? [React.createElement("div", { key: "msg-none", className: "iterate-obs-empty" }, "\u65E0\u7EBF\u7D22\u53D9\u8FF0")] : msgs.map((m, mi) => React.createElement("div", { key: mi, className: "iterate-obs-msg" }, String(m))),
|
|
2236
|
+
...files.length > 0 ? [
|
|
2237
|
+
React.createElement(
|
|
2238
|
+
"div",
|
|
2239
|
+
{ key: "files-h", className: "iterate-obs-bar", style: { marginTop: 6 } },
|
|
2240
|
+
React.createElement("b", {}, "\u8BFB\u53D6\u6587\u4EF6")
|
|
2241
|
+
),
|
|
2242
|
+
...files.map(
|
|
2243
|
+
(f, fi) => React.createElement("div", { key: `f${fi}`, className: "iterate-obs-file" }, String(f))
|
|
2244
|
+
)
|
|
2245
|
+
] : [],
|
|
2246
|
+
React.createElement(
|
|
2247
|
+
"div",
|
|
2248
|
+
{ className: "iterate-obs-bar", style: { marginTop: 6 } },
|
|
2249
|
+
React.createElement("b", {}, "\u53D1\u73B0")
|
|
2250
|
+
),
|
|
2251
|
+
...tFindings.length === 0 ? [React.createElement("div", { key: "finding-none", className: "iterate-obs-empty" }, "\u672C\u7EBF\u7A0B\u65E0\u53D1\u73B0")] : tFindings.map((f, fi) => threadFindingPill(f, `tf${fi}`))
|
|
2252
|
+
) : null
|
|
2253
|
+
);
|
|
2254
|
+
});
|
|
2255
|
+
return React.createElement(
|
|
2256
|
+
"div",
|
|
2257
|
+
{ key: ri, className: "iterate-obs-block" },
|
|
2258
|
+
React.createElement(
|
|
2259
|
+
"div",
|
|
2260
|
+
{ className: "iterate-obs-block-head" },
|
|
2261
|
+
React.createElement("b", {}, `Round ${typeof r.round === "number" ? r.round : ri + 1}`),
|
|
2262
|
+
React.createElement("span", { className: "iterate-obs-chip" }, `${threads.length} \u7EBF\u7A0B`),
|
|
2263
|
+
React.createElement("span", { className: "iterate-obs-head-meta" }, `${fCount} findings`)
|
|
2264
|
+
),
|
|
2265
|
+
React.createElement("div", { className: "iterate-obs-block-body" }, ...threadBlocks)
|
|
2266
|
+
);
|
|
2267
|
+
}));
|
|
2268
|
+
};
|
|
2269
|
+
const renderTrend = () => {
|
|
2270
|
+
const conv = manifest.convergence || [];
|
|
2271
|
+
if (conv.length === 0) {
|
|
2272
|
+
return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u6536\u655B\u6570\u636E");
|
|
2273
|
+
}
|
|
2274
|
+
const points = conv.map((n, i) => ({ round: i + 1, count: n }));
|
|
2275
|
+
return React.createElement(
|
|
2276
|
+
"div",
|
|
2277
|
+
{},
|
|
2278
|
+
React.createElement(TrendChart, { points }),
|
|
2279
|
+
React.createElement(
|
|
2280
|
+
"div",
|
|
2281
|
+
{ className: "iterate-obs-bar", style: { marginTop: 6 } },
|
|
2282
|
+
`\u5404\u8F6E\u53D1\u73B0\u6570\u91CF\uFF1A${conv.join(" \u2192 ")}${typeof manifest.round === "number" ? ` \xB7 \u5F53\u524D Round ${manifest.round}` : ""}`
|
|
2283
|
+
)
|
|
2284
|
+
);
|
|
2285
|
+
};
|
|
2286
|
+
const renderFindings = () => {
|
|
2287
|
+
const findings = manifest.findings || [];
|
|
2288
|
+
if (findings.length === 0) {
|
|
2289
|
+
return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u53D1\u73B0");
|
|
2290
|
+
}
|
|
2291
|
+
return React.createElement("div", {}, ...findings.map((f, i) => {
|
|
2292
|
+
const k = `f3-${i}`;
|
|
2293
|
+
const loc = `${String(f.file || "?")}${typeof f.line === "number" && f.line > 0 ? `:${f.line}` : ""}`;
|
|
2294
|
+
const entry = {
|
|
2295
|
+
file: String(f.file || ""),
|
|
2296
|
+
...typeof f.line === "number" && f.line > 0 ? { line: f.line } : {},
|
|
2297
|
+
dimension: String(f.dimension || ""),
|
|
2298
|
+
reason: String(f.summary || "")
|
|
2299
|
+
};
|
|
2300
|
+
const knownInstruction = buildApplyInstruction([entry]);
|
|
2301
|
+
const fixInstruction = buildObsFixInstruction(f);
|
|
2302
|
+
return React.createElement(
|
|
2303
|
+
"div",
|
|
2304
|
+
{ key: k, className: "iterate-obs-block" },
|
|
2305
|
+
React.createElement(
|
|
2306
|
+
"div",
|
|
2307
|
+
{ className: "iterate-obs-block-head" },
|
|
2308
|
+
React.createElement("span", { className: "iterate-sev-dot", style: { background: severityColor(f.severity) } }),
|
|
2309
|
+
React.createElement("span", {}, severityLabel(f.severity)),
|
|
2310
|
+
React.createElement("span", { className: "iterate-obs-file" }, loc),
|
|
2311
|
+
React.createElement("span", {}, String(f.dimension || "")),
|
|
2312
|
+
f.acknowledged === true ? React.createElement("span", { className: "iterate-obs-chip" }, "\u5DF2\u786E\u8BA4") : null
|
|
2313
|
+
),
|
|
2314
|
+
React.createElement(
|
|
2315
|
+
"div",
|
|
2316
|
+
{ className: "iterate-obs-block-body" },
|
|
2317
|
+
React.createElement("div", { className: "iterate-obs-msg" }, String(f.summary || "")),
|
|
2318
|
+
React.createElement(
|
|
2319
|
+
"div",
|
|
2320
|
+
{ className: "iterate-obs-bar", style: { marginTop: 8 } },
|
|
2321
|
+
React.createElement("button", {
|
|
2322
|
+
className: "iterate-btn",
|
|
2323
|
+
"data-copied": copiedKey === `${k}-fix` ? "" : void 0,
|
|
2324
|
+
onClick: () => copyInstruction(`${k}-fix`, String(f.suggested_fix || "")),
|
|
2325
|
+
title: "\u590D\u5236\u8BE5 finding \u7684\u5EFA\u8BAE\u4FEE\u590D\u6587\u672C"
|
|
2326
|
+
}, copiedKey === `${k}-fix` ? "\u5DF2\u590D\u5236" : "\u590D\u5236 suggested_fix"),
|
|
2327
|
+
React.createElement("button", {
|
|
2328
|
+
className: "iterate-btn",
|
|
2329
|
+
"data-copied": copiedKey === `${k}-known` ? "" : void 0,
|
|
2330
|
+
onClick: () => copyInstruction(`${k}-known`, knownInstruction),
|
|
2331
|
+
title: "\u590D\u5236 iterate_triage \u6307\u4EE4\u6587\u672C\uFF08\u6807\u8BB0\u4E3A known_intentional\uFF09"
|
|
2332
|
+
}, copiedKey === `${k}-known` ? "\u5DF2\u590D\u5236" : "known_intentional"),
|
|
2333
|
+
React.createElement("button", {
|
|
2334
|
+
className: "iterate-btn",
|
|
2335
|
+
"data-primary": "",
|
|
2336
|
+
"data-copied": copiedKey === `${k}-repair` ? "" : void 0,
|
|
2337
|
+
onClick: () => copyInstruction(`${k}-repair`, fixInstruction),
|
|
2338
|
+
title: "\u590D\u5236 iterate_fix \u6307\u4EE4\u6587\u672C"
|
|
2339
|
+
}, copiedKey === `${k}-repair` ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u4FEE\u590D\u6307\u4EE4")
|
|
2340
|
+
)
|
|
2341
|
+
)
|
|
2342
|
+
);
|
|
2343
|
+
}));
|
|
2344
|
+
};
|
|
2345
|
+
const renderFixes = () => {
|
|
2346
|
+
const fixes = manifest.fixes || [];
|
|
2347
|
+
if (fixes.length === 0) {
|
|
2348
|
+
return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u4FEE\u590D\u8BB0\u5F55");
|
|
2349
|
+
}
|
|
2350
|
+
return React.createElement("div", {}, ...fixes.map((f, i) => {
|
|
2351
|
+
const k = `f4-${i}`;
|
|
2352
|
+
const id = String(f.id || `fix#${i + 1}`);
|
|
2353
|
+
const rollbackText = `\u8BF7\u8C03\u7528 \`iterate_rollback\` \u56DE\u6EDA\u4EE5\u4E0B\u4FEE\u590D\uFF1A
|
|
2354
|
+
|
|
2355
|
+
\`\`\`json
|
|
2356
|
+
${JSON.stringify({ id }, null, 2)}
|
|
2357
|
+
\`\`\``;
|
|
2358
|
+
const added = typeof f.linesAdded === "number" ? f.linesAdded : 0;
|
|
2359
|
+
const removed = typeof f.linesRemoved === "number" ? f.linesRemoved : 0;
|
|
2360
|
+
return React.createElement(
|
|
2361
|
+
"div",
|
|
2362
|
+
{ key: k, className: "iterate-obs-block" },
|
|
2363
|
+
React.createElement(
|
|
2364
|
+
"div",
|
|
2365
|
+
{ className: "iterate-obs-block-head" },
|
|
2366
|
+
React.createElement("b", {}, id),
|
|
2367
|
+
React.createElement("span", { className: "iterate-obs-file" }, String(f.file || "?")),
|
|
2368
|
+
React.createElement("span", { className: "iterate-obs-chip" }, f.success === true ? "\u6210\u529F" : "\u5931\u8D25"),
|
|
2369
|
+
typeof f.round === "number" ? React.createElement("span", { className: "iterate-obs-head-meta" }, `Round ${f.round}`) : null
|
|
2370
|
+
),
|
|
2371
|
+
React.createElement(
|
|
2372
|
+
"div",
|
|
2373
|
+
{ className: "iterate-obs-block-body" },
|
|
2374
|
+
React.createElement("div", { className: "iterate-obs-msg" }, String(f.summary || "")),
|
|
2375
|
+
React.createElement(
|
|
2376
|
+
"div",
|
|
2377
|
+
{ className: "iterate-obs-bar", style: { marginTop: 8 } },
|
|
2378
|
+
React.createElement("span", { className: "iterate-obs-chip" }, `+${added}`),
|
|
2379
|
+
React.createElement("span", { className: "iterate-obs-chip" }, `\u2212${removed}`),
|
|
2380
|
+
React.createElement("button", {
|
|
2381
|
+
className: "iterate-btn",
|
|
2382
|
+
"data-danger": "",
|
|
2383
|
+
"data-copied": copiedKey === `${k}-rb` ? "" : void 0,
|
|
2384
|
+
onClick: () => copyInstruction(`${k}-rb`, rollbackText),
|
|
2385
|
+
title: "\u590D\u5236 iterate_rollback \u6307\u4EE4\u6587\u672C"
|
|
2386
|
+
}, copiedKey === `${k}-rb` ? "\u5DF2\u590D\u5236" : "\u56DE\u6EDA")
|
|
2387
|
+
)
|
|
2388
|
+
)
|
|
2389
|
+
);
|
|
2390
|
+
}));
|
|
2391
|
+
};
|
|
2392
|
+
const renderCheckpoint = () => {
|
|
2393
|
+
const cp = manifest.checkpoint;
|
|
2394
|
+
if (!cp) {
|
|
2395
|
+
return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u65AD\u70B9");
|
|
2396
|
+
}
|
|
2397
|
+
const resumeText = `\u8BF7\u8C03\u7528 \`iterate_checkpoint\` \u4ECE\u65AD\u70B9\u6062\u590D\u8FED\u4EE3\uFF1A
|
|
2398
|
+
|
|
2399
|
+
\`\`\`json
|
|
2400
|
+
${JSON.stringify({
|
|
2401
|
+
operation: "resume",
|
|
2402
|
+
mode: String(cp.mode || "normal"),
|
|
2403
|
+
maxRounds: typeof cp.maxRounds === "number" ? cp.maxRounds : null
|
|
2404
|
+
}, null, 2)}
|
|
2405
|
+
\`\`\``;
|
|
2406
|
+
const item = (label, value) => React.createElement("span", { className: "iterate-obs-chip" }, `${label} ${String(value ?? "?")}`);
|
|
2407
|
+
return React.createElement(
|
|
2408
|
+
"div",
|
|
2409
|
+
{ className: "iterate-obs-block" },
|
|
2410
|
+
React.createElement(
|
|
2411
|
+
"div",
|
|
2412
|
+
{ className: "iterate-obs-block-head" },
|
|
2413
|
+
React.createElement("b", {}, "\u65AD\u70B9"),
|
|
2414
|
+
React.createElement("span", { className: "iterate-obs-head-meta" }, String(cp.updatedAt || ""))
|
|
2415
|
+
),
|
|
2416
|
+
React.createElement(
|
|
2417
|
+
"div",
|
|
2418
|
+
{ className: "iterate-obs-block-body" },
|
|
2419
|
+
React.createElement(
|
|
2420
|
+
"div",
|
|
2421
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8 } },
|
|
2422
|
+
item("mode", cp.mode || "normal"),
|
|
2423
|
+
item("round", cp.round),
|
|
2424
|
+
item("maxRounds", cp.maxRounds),
|
|
2425
|
+
item("fixed", cp.fixedCount),
|
|
2426
|
+
item("resume", cp.resumeCount)
|
|
2427
|
+
),
|
|
2428
|
+
React.createElement("button", {
|
|
2429
|
+
className: "iterate-btn",
|
|
2430
|
+
"data-primary": "",
|
|
2431
|
+
"data-copied": copiedKey === "cp-resume" ? "" : void 0,
|
|
2432
|
+
onClick: () => copyInstruction("cp-resume", resumeText),
|
|
2433
|
+
title: "\u590D\u5236 iterate_checkpoint \u7EFC/\u6062\u590D\u6307\u4EE4\u6587\u672C"
|
|
2434
|
+
}, copiedKey === "cp-resume" ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u6062\u590D\u6307\u4EE4")
|
|
2435
|
+
)
|
|
2436
|
+
);
|
|
2437
|
+
};
|
|
2438
|
+
const renderConsole = () => {
|
|
2439
|
+
const approval = manifest.approval || { policy: "ask" };
|
|
2440
|
+
const policyLabelMap = { ask: "\u8BE2\u95EE\u7528\u6237", deny: "\u62D2\u7EDD", allow: "\u76F4\u63A5\u6267\u884C" };
|
|
2441
|
+
const policyLabel = policyLabelMap[String(approval.policy || "ask")] || String(approval.policy || "ask");
|
|
2442
|
+
const nudge = manifest.nudge || null;
|
|
2443
|
+
const present = Boolean(nudge && nudge.text);
|
|
2444
|
+
const activeNudgeText = nudge && nudge.text ? nudge.text : "";
|
|
2445
|
+
const nudgeInstruction = `\u8BF7\u8C03\u7528 \`iterate_transcript\` \u5199\u5165 nudge \u6307\u4EE4\uFF1A
|
|
2446
|
+
|
|
2447
|
+
\`\`\`json
|
|
2448
|
+
${JSON.stringify({ operation: "nudge", text: nudgeText }, null, 2)}
|
|
2449
|
+
\`\`\``;
|
|
2450
|
+
return React.createElement(
|
|
2451
|
+
"div",
|
|
2452
|
+
{ className: "iterate-obs-block" },
|
|
2453
|
+
React.createElement(
|
|
2454
|
+
"div",
|
|
2455
|
+
{ className: "iterate-obs-block-head" },
|
|
2456
|
+
React.createElement("span", {}, "\u8FD0\u884C\u63A7\u5236\u53F0"),
|
|
2457
|
+
React.createElement("span", { className: "iterate-obs-badge", "data-live": live ? "" : void 0 }, live ? "\u8FD0\u884C\u4E2D" : "\u5DF2\u7ED3\u675F"),
|
|
2458
|
+
React.createElement("span", { className: "iterate-obs-head-meta" }, `\u6279\u51C6\u7B56\u7565\uFF1A${policyLabel}`)
|
|
2459
|
+
),
|
|
2460
|
+
React.createElement(
|
|
2461
|
+
"div",
|
|
2462
|
+
{ className: "iterate-obs-block-body" },
|
|
2463
|
+
present ? React.createElement(
|
|
2464
|
+
"div",
|
|
2465
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 6 } },
|
|
2466
|
+
React.createElement("b", {}, "\u5F53\u524D nudge"),
|
|
2467
|
+
React.createElement("span", { className: "iterate-obs-msg" }, activeNudgeText),
|
|
2468
|
+
React.createElement("button", { className: "iterate-btn", onClick: () => setNudgeText("") }, "\u6E05\u9664")
|
|
2469
|
+
) : null,
|
|
2470
|
+
React.createElement("textarea", {
|
|
2471
|
+
className: "iterate-obs-input iterate-obs-mono",
|
|
2472
|
+
rows: 3,
|
|
2473
|
+
placeholder: "\u8D77\u8349\u4E00\u6761 nudge \u65B9\u5411\u6307\u4EE4\u2026",
|
|
2474
|
+
value: nudgeText,
|
|
2475
|
+
"aria-label": "nudge \u8349\u7A3F",
|
|
2476
|
+
onChange: (e) => setNudgeText(e.target.value)
|
|
2477
|
+
}),
|
|
2478
|
+
React.createElement(
|
|
2479
|
+
"div",
|
|
2480
|
+
{ className: "iterate-obs-bar", style: { marginTop: 6 } },
|
|
2481
|
+
React.createElement("button", {
|
|
2482
|
+
className: "iterate-btn",
|
|
2483
|
+
"data-primary": "",
|
|
2484
|
+
"data-copied": copiedKey === "nudge" ? "" : void 0,
|
|
2485
|
+
disabled: !nudgeText,
|
|
2486
|
+
onClick: () => copyInstruction("nudge", nudgeInstruction),
|
|
2487
|
+
title: "\u590D\u5236 iterate_transcript nudge \u6307\u4EE4\u6587\u672C"
|
|
2488
|
+
}, copiedKey === "nudge" ? "\u5DF2\u590D\u5236" : "\u590D\u5236 nudge \u6307\u4EE4")
|
|
2489
|
+
)
|
|
2490
|
+
)
|
|
2491
|
+
);
|
|
2492
|
+
};
|
|
2493
|
+
const renderTimeline = () => {
|
|
2494
|
+
const entries = manifest.timeline || [];
|
|
2495
|
+
if (entries.length === 0) {
|
|
2496
|
+
return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u65F6\u95F4\u7EBF\u6761\u76EE");
|
|
2497
|
+
}
|
|
2498
|
+
const types = Array.from(new Set(entries.map((t) => String(t.type || "unknown")).filter(Boolean))).sort();
|
|
2499
|
+
const q = timelineSearch.trim().toLowerCase();
|
|
2500
|
+
const filtered = entries.filter((t) => {
|
|
2501
|
+
if (timelineType && String(t.type || "") !== timelineType) return false;
|
|
2502
|
+
if (!q) return true;
|
|
2503
|
+
const hay = [String(t.type || ""), String(t.round ?? ""), JSON.stringify(t.data || {})].join(" ").toLowerCase();
|
|
2504
|
+
return hay.indexOf(q) >= 0;
|
|
2505
|
+
});
|
|
2506
|
+
const sorted = filtered.slice().sort((a, b) => String(b.timestamp || "").localeCompare(String(a.timestamp || "")));
|
|
2507
|
+
const rows = sorted.map((t, i) => {
|
|
2508
|
+
const k = `f7-${i}`;
|
|
2509
|
+
const dataText = t.data && typeof t.data === "object" ? JSON.stringify(t.data) : "";
|
|
2510
|
+
return React.createElement(
|
|
2511
|
+
"div",
|
|
2512
|
+
{ key: k, className: "iterate-obs-row", style: { flexDirection: "column", alignItems: "flex-start" } },
|
|
2513
|
+
React.createElement(
|
|
2514
|
+
"div",
|
|
2515
|
+
{ className: "iterate-obs-bar", style: { width: "100%" } },
|
|
2516
|
+
React.createElement("span", { className: "iterate-obs-chip" }, String(t.type || "?")),
|
|
2517
|
+
React.createElement(
|
|
2518
|
+
"span",
|
|
2519
|
+
{ className: "iterate-obs-head-meta" },
|
|
2520
|
+
`${typeof t.round === "number" ? `Round ${t.round} \xB7 ` : ""}${String(t.timestamp || "")}`
|
|
2521
|
+
)
|
|
2522
|
+
),
|
|
2523
|
+
dataText ? React.createElement("div", { className: "iterate-obs-code" }, dataText) : null
|
|
2524
|
+
);
|
|
2525
|
+
});
|
|
2526
|
+
if (rows.length === 0) {
|
|
2527
|
+
return React.createElement("div", { className: "iterate-obs-empty" }, "\u65E0\u5339\u914D\u7684\u65F6\u95F4\u7EBF\u6761\u76EE");
|
|
2528
|
+
}
|
|
2529
|
+
return React.createElement(
|
|
2530
|
+
"div",
|
|
2531
|
+
{},
|
|
2532
|
+
React.createElement(
|
|
2533
|
+
"div",
|
|
2534
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8 } },
|
|
2535
|
+
React.createElement(
|
|
2536
|
+
"select",
|
|
2537
|
+
{
|
|
2538
|
+
className: "iterate-filter-select",
|
|
2539
|
+
value: timelineType,
|
|
2540
|
+
"aria-label": "\u6309\u65F6\u95F4\u7EBF\u7C7B\u578B\u7B5B\u9009",
|
|
2541
|
+
onChange: (e) => setTimelineType(e.target.value)
|
|
2542
|
+
},
|
|
2543
|
+
React.createElement("option", { value: "" }, "\u5168\u90E8\u7C7B\u578B"),
|
|
2544
|
+
...types.map((t) => React.createElement("option", { key: t, value: t }, t))
|
|
2545
|
+
),
|
|
2546
|
+
React.createElement("input", {
|
|
2547
|
+
className: "iterate-filter-search",
|
|
2548
|
+
type: "search",
|
|
2549
|
+
placeholder: "\u641C\u7D22\u65F6\u95F4\u7EBF\u2026",
|
|
2550
|
+
"aria-label": "\u641C\u7D22\u65F6\u95F4\u7EBF",
|
|
2551
|
+
value: timelineSearch,
|
|
2552
|
+
onChange: (e) => setTimelineSearch(e.target.value)
|
|
2553
|
+
}),
|
|
2554
|
+
React.createElement("span", { className: "iterate-filter-count" }, `${filtered.length}/${entries.length}`)
|
|
2555
|
+
),
|
|
2556
|
+
...rows
|
|
2557
|
+
);
|
|
2558
|
+
};
|
|
2559
|
+
const renderLive = () => {
|
|
2560
|
+
const liveEntries = manifest.live || [];
|
|
2561
|
+
if (liveEntries.length === 0) {
|
|
2562
|
+
return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u5B9E\u65F6\u6D3B\u52A8");
|
|
2563
|
+
}
|
|
2564
|
+
const rows = liveEntries.map((e, i) => {
|
|
2565
|
+
const type = String(e.type || "unknown");
|
|
2566
|
+
const meta = OBS_LIVE_META[type] || { label: String(e.type || "?"), color: OBS_LIVE_FALLBACK_COLOR };
|
|
2567
|
+
const tool = String(e.tool || "");
|
|
2568
|
+
const target = String(e.target || "");
|
|
2569
|
+
const time = formatObsLiveTime(e.ts);
|
|
2570
|
+
return React.createElement(
|
|
2571
|
+
"div",
|
|
2572
|
+
{ key: `live-${i}`, className: "iterate-obs-row" },
|
|
2573
|
+
React.createElement("span", { className: "iterate-obs-livebadge", style: { color: meta.color } }, meta.label),
|
|
2574
|
+
tool ? React.createElement("span", { className: "iterate-obs-chip" }, tool) : null,
|
|
2575
|
+
React.createElement("span", { className: "iterate-obs-msg" }, target || "\u2014"),
|
|
2576
|
+
React.createElement("span", { className: "iterate-obs-head-meta" }, time)
|
|
2577
|
+
);
|
|
2578
|
+
});
|
|
2579
|
+
return React.createElement(
|
|
2580
|
+
"div",
|
|
2581
|
+
{},
|
|
2582
|
+
React.createElement(
|
|
2583
|
+
"div",
|
|
2584
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8 } },
|
|
2585
|
+
React.createElement("b", {}, "\u5B9E\u65F6\u6D3B\u52A8"),
|
|
2586
|
+
React.createElement("span", { className: "iterate-obs-chip" }, `${liveEntries.length} \u9879`)
|
|
2587
|
+
),
|
|
2588
|
+
...rows
|
|
2589
|
+
);
|
|
2590
|
+
};
|
|
2591
|
+
const renderBody = () => {
|
|
2592
|
+
switch (tab) {
|
|
2593
|
+
case "live":
|
|
2594
|
+
return renderLive();
|
|
2595
|
+
case "f1":
|
|
2596
|
+
return renderThreads();
|
|
2597
|
+
case "f2":
|
|
2598
|
+
return renderTrend();
|
|
2599
|
+
case "f3":
|
|
2600
|
+
return renderFindings();
|
|
2601
|
+
case "f4":
|
|
2602
|
+
return renderFixes();
|
|
2603
|
+
case "f5":
|
|
2604
|
+
return renderCheckpoint();
|
|
2605
|
+
case "f6":
|
|
2606
|
+
return renderConsole();
|
|
2607
|
+
case "f7":
|
|
2608
|
+
return renderTimeline();
|
|
2609
|
+
default:
|
|
2610
|
+
return null;
|
|
2611
|
+
}
|
|
2612
|
+
};
|
|
2613
|
+
return React.createElement(
|
|
2614
|
+
"div",
|
|
2615
|
+
{ "data-iterate-root": "", "data-iterate": "obs", className: "iterate-obs" },
|
|
2616
|
+
React.createElement(
|
|
2617
|
+
"div",
|
|
2618
|
+
{ className: "iterate-obs-head", "data-closed": open ? void 0 : "", onClick: () => setOpen((v) => !v) },
|
|
2619
|
+
React.createElement("span", { className: "iterate-obs-title" }, "iterate \u89C2\u6D4B\u53F0"),
|
|
2620
|
+
React.createElement("span", { className: "iterate-obs-badge", "data-live": live ? "" : void 0 }, live ? "\u8FD0\u884C\u4E2D" : "\u5DF2\u7ED3\u675F"),
|
|
2621
|
+
React.createElement("span", { className: "iterate-obs-head-meta" }, headMeta || "runtime"),
|
|
2622
|
+
React.createElement("button", {
|
|
2623
|
+
className: "iterate-btn",
|
|
2624
|
+
"data-ghost": "",
|
|
2625
|
+
onClick: (e) => {
|
|
2626
|
+
e.stopPropagation();
|
|
2627
|
+
setOpen((v) => !v);
|
|
2628
|
+
}
|
|
2629
|
+
}, open ? "\u6536\u8D77" : "\u5C55\u5F00")
|
|
2630
|
+
),
|
|
2631
|
+
open ? React.createElement(
|
|
2632
|
+
"div",
|
|
2633
|
+
{},
|
|
2634
|
+
React.createElement(
|
|
2635
|
+
"div",
|
|
2636
|
+
{ className: "iterate-obs-tabs" },
|
|
2637
|
+
...OBS_TABS.map(
|
|
2638
|
+
(t) => React.createElement("button", {
|
|
2639
|
+
key: t.key,
|
|
2640
|
+
className: "iterate-obs-tab",
|
|
2641
|
+
"data-active": tab === t.key ? "" : void 0,
|
|
2642
|
+
onClick: () => setTab(t.key)
|
|
2643
|
+
}, t.label)
|
|
2644
|
+
)
|
|
2645
|
+
),
|
|
2646
|
+
React.createElement("div", { className: "iterate-obs-body" }, renderBody())
|
|
2647
|
+
) : null
|
|
2648
|
+
);
|
|
2649
|
+
}
|
|
1771
2650
|
function selectTurnTail(owner) {
|
|
1772
2651
|
if (!owner) return null;
|
|
1773
2652
|
if (findReportInObject(owner.turn, void 0, 24) || scanSessionForReport(owner.turn)) return { matched: true };
|
|
@@ -1808,6 +2687,13 @@ function apply(ctx) {
|
|
|
1808
2687
|
(props) => React.createElement(ConvergenceDashboard, props)
|
|
1809
2688
|
)
|
|
1810
2689
|
);
|
|
2690
|
+
slotsSvc.inject(
|
|
2691
|
+
"conversation.input.dock",
|
|
2692
|
+
() => slotsSvc?.register(
|
|
2693
|
+
{ name: "conversation.input.dock", id: "iterate-observatory", order: 91 },
|
|
2694
|
+
(props) => React.createElement(ObservatoryPanel, props)
|
|
2695
|
+
)
|
|
2696
|
+
);
|
|
1811
2697
|
slotsSvc.inject(
|
|
1812
2698
|
"conversation.chat.turnTail",
|
|
1813
2699
|
() => slotsSvc?.register(
|