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.
Files changed (55) hide show
  1. package/README.md +42 -2
  2. package/README.zh-CN.md +40 -2
  3. package/dist/approval-gate.js +92 -0
  4. package/dist/config-loader.js +18 -3
  5. package/dist/config-write.js +7 -4
  6. package/dist/evidence.js +67 -1
  7. package/dist/git-scope.js +35 -6
  8. package/dist/index.js +15 -5
  9. package/dist/live.js +155 -0
  10. package/dist/meta-review.js +19 -5
  11. package/dist/method-scope.js +5 -1
  12. package/dist/paths.js +4 -0
  13. package/dist/review-scope.js +12 -8
  14. package/dist/review.js +76 -24
  15. package/dist/session-hooks.js +89 -0
  16. package/dist/skill-prompt.js +101 -19
  17. package/dist/tools/checkpoint.js +10 -3
  18. package/dist/tools/context.js +16 -4
  19. package/dist/tools/decision-log.js +29 -9
  20. package/dist/tools/fix.js +120 -3
  21. package/dist/tools/prune.js +16 -9
  22. package/dist/tools/review.js +4 -1
  23. package/dist/tools/transcript.js +324 -0
  24. package/dist/tools/triage.js +9 -6
  25. package/dist/tools/validate.js +5 -2
  26. package/dist/transcript.js +421 -0
  27. package/lib/client.js +966 -80
  28. package/lib/parse.js +302 -17
  29. package/package.json +1 -1
  30. package/src/approval-gate.ts +119 -0
  31. package/src/client/index.ts +807 -62
  32. package/src/config-loader.ts +16 -2
  33. package/src/config-write.ts +6 -4
  34. package/src/evidence.ts +69 -1
  35. package/src/git-scope.ts +34 -6
  36. package/src/index.ts +17 -6
  37. package/src/live.ts +185 -0
  38. package/src/meta-review.ts +24 -10
  39. package/src/method-scope.ts +5 -1
  40. package/src/paths.ts +5 -0
  41. package/src/review-scope.ts +11 -7
  42. package/src/review.ts +82 -25
  43. package/src/session-hooks.ts +90 -0
  44. package/src/skill-prompt.ts +101 -19
  45. package/src/tools/checkpoint.ts +10 -3
  46. package/src/tools/context.ts +14 -3
  47. package/src/tools/decision-log.ts +27 -10
  48. package/src/tools/fix.ts +114 -3
  49. package/src/tools/prune.ts +14 -11
  50. package/src/tools/review.ts +5 -2
  51. package/src/tools/transcript.ts +334 -0
  52. package/src/tools/triage.ts +9 -6
  53. package/src/tools/validate.ts +5 -2
  54. package/src/transcript.ts +475 -0
  55. 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: "#eab308",
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 convergence = (
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
- (report.findings ?? [])[0]
714
+ report.findings ?? []
518
715
  );
519
- const firstSummary = firstFinding ? String(firstFinding.summary ?? "") : "";
520
- const mode = String(report.mode ?? "");
521
- return `iterate-triage-${mode}-${totalRounds}-${findingsCount}-${firstSummary.slice(0, 20)}`;
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
- return React.createElement("div", { className: "iterate-trend", title: "\u5404\u8F6E\u53D1\u73B0\u6570\u91CF\u8D8B\u52BF" }, ...bars);
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) return null;
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 dimBadges = Object.keys(dims).slice(0, 6).map(
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", { className: "iterate-progress-fill", style: { width: `${progress}%` } })
1174
- ),
1175
- React.createElement(
1176
- "span",
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
- const persistVerdicts = (next) => {
1317
- if (storage) storage.set(storageKey, JSON.stringify(next));
1318
- return next;
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) => persistVerdicts({ ...prev, [String(index)]: verdict }));
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) => persistVerdicts(batchSetVerdict(prev, batchTarget, verdict)));
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) => persistVerdicts(setAllVerdicts(prev, "keep")));
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
- const ok = copyText(yaml);
1380
- setCopied(true);
1381
- setTimeout(() => setCopied(false), 1600);
1382
- if (!ok) setPayload(yaml);
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
- onClick: () => setSelected(index)
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), title: "\u6309\u4E25\u91CD\u5EA6\u7B5B\u9009" },
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), title: "\u6309\u7EF4\u5EA6\u7B5B\u9009" },
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
- "label",
1479
- { className: "iterate-batch-check", title: "\u52FE\u9009\u540E\u6279\u91CF\u6309\u94AE\u4F5C\u7528\u4E8E\u5168\u90E8 findings\uFF0C\u5426\u5219\u4EC5\u5F53\u524D\u53EF\u89C1" },
1480
- React.createElement("input", { type: "checkbox", checked: selectAll, onChange: (e) => setSelectAll(e.target.checked) }),
1481
- selectAll ? `\u5168\u90E8 ${allIndices.length}` : "\u5168\u9009"
1482
- ),
1483
- React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("keep") }, "\u5168\u90E8 y"),
1484
- React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("skip") }, "\u5168\u90E8 n"),
1485
- React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("ignore") }, "\u5168\u90E8 a"),
1486
- React.createElement("span", { className: "iterate-batch-label", style: { marginLeft: 8 } }, "\u5168\u90E8\uFF1A"),
1487
- React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatchAll("keep") }, "y"),
1488
- React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatchAll("skip") }, "n"),
1489
- React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatchAll("ignore") }, "a"),
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", { className: "iterate-btn", "data-primary": "", "data-copied": copied ? "" : void 0, onClick: doCopyYaml }, copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236 known_intentional"),
1501
- React.createElement("button", { className: "iterate-btn", onClick: doBuildInstruction }, "\u751F\u6210\u5E94\u7528\u6307\u4EE4")
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
- flashCopied(slot);
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(