iterate-plugin 2.12.0 → 2.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -1629,6 +1629,11 @@ function TriagePanel(props) {
1629
1629
  const t = ev.target;
1630
1630
  if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT")) return;
1631
1631
  if (t && typeof t.isContentEditable === "boolean" && t.isContentEditable) return;
1632
+ const rootEl = doc.querySelector('[data-iterate="triage"]');
1633
+ if (!rootEl) return;
1634
+ const activeEl = doc.activeElement;
1635
+ if (!activeEl) return;
1636
+ if (activeEl !== rootEl && !(typeof rootEl.contains === "function" && rootEl.contains(activeEl))) return;
1632
1637
  const verdict = keyToVerdict(ev.key);
1633
1638
  if (verdict && selected !== null && indices.includes(selected)) {
1634
1639
  ev.preventDefault();
@@ -2146,6 +2151,7 @@ function ObservatoryPanel(props) {
2146
2151
  const [tab, setTab] = React.useState("live");
2147
2152
  const [expandedThreads, setExpandedThreads] = React.useState(/* @__PURE__ */ new Set());
2148
2153
  const [copiedKey, setCopiedKey] = React.useState(null);
2154
+ const [copyFailText, setCopyFailText] = React.useState(null);
2149
2155
  const [nudgeText, setNudgeText] = React.useState("");
2150
2156
  const [timelineType, setTimelineType] = React.useState("");
2151
2157
  const [timelineSearch, setTimelineSearch] = React.useState("");
@@ -2156,7 +2162,10 @@ function ObservatoryPanel(props) {
2156
2162
  const copyInstruction = (key, text) => {
2157
2163
  if (!text) return;
2158
2164
  copyText(text).then((ok) => {
2159
- if (!ok) return;
2165
+ if (!ok) {
2166
+ setCopyFailText(text);
2167
+ return;
2168
+ }
2160
2169
  setCopiedKey(key);
2161
2170
  if (copyTimer.current) clearTimeout(copyTimer.current);
2162
2171
  copyTimer.current = setTimeout(() => setCopiedKey((cur) => cur === key ? null : cur), 1600);
@@ -2446,6 +2455,11 @@ ${JSON.stringify({
2446
2455
 
2447
2456
  \`\`\`json
2448
2457
  ${JSON.stringify({ operation: "nudge", text: nudgeText }, null, 2)}
2458
+ \`\`\``;
2459
+ const activeClearInstruction = `\u8BF7\u8C03\u7528 \`iterate_transcript\` \u6E05\u9664\u5F53\u524D nudge\uFF1A
2460
+
2461
+ \`\`\`json
2462
+ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
2449
2463
  \`\`\``;
2450
2464
  return React.createElement(
2451
2465
  "div",
@@ -2465,7 +2479,12 @@ ${JSON.stringify({ operation: "nudge", text: nudgeText }, null, 2)}
2465
2479
  { className: "iterate-obs-bar", style: { marginBottom: 6 } },
2466
2480
  React.createElement("b", {}, "\u5F53\u524D nudge"),
2467
2481
  React.createElement("span", { className: "iterate-obs-msg" }, activeNudgeText),
2468
- React.createElement("button", { className: "iterate-btn", onClick: () => setNudgeText("") }, "\u6E05\u9664")
2482
+ React.createElement("button", {
2483
+ className: "iterate-btn",
2484
+ "data-copied": copiedKey === "nudge-clear" ? "" : void 0,
2485
+ onClick: () => copyInstruction("nudge-clear", activeClearInstruction),
2486
+ title: "\u590D\u5236\u6E05\u9664 nudge \u6307\u4EE4\uFF08\u5199 text:null\uFF09\uFF0C\u5728\u8FD0\u884C\u63A7\u5236\u53F0\u63D0\u793A\u6E05\u9664\u5DF2\u6301\u4E45\u5316\u7684 nudge"
2487
+ }, copiedKey === "nudge-clear" ? "\u5DF2\u590D\u5236\u6E05\u9664\u6307\u4EE4" : "\u590D\u5236\u6E05\u9664\u6307\u4EE4")
2469
2488
  ) : null,
2470
2489
  React.createElement("textarea", {
2471
2490
  className: "iterate-obs-input iterate-obs-mono",
@@ -2559,6 +2578,21 @@ ${JSON.stringify({ operation: "nudge", text: nudgeText }, null, 2)}
2559
2578
  const renderLive = () => {
2560
2579
  const liveEntries = manifest.live || [];
2561
2580
  if (liveEntries.length === 0) {
2581
+ const hasThreads = (manifest.rounds || []).length > 0;
2582
+ const hasFindings = (manifest.findings || []).length > 0;
2583
+ if (hasThreads || hasFindings) {
2584
+ return React.createElement(
2585
+ "div",
2586
+ { className: "iterate-obs-empty" },
2587
+ React.createElement("div", {}, "\u5B9E\u65F6\u6D3B\u52A8\u6D41\u4EC5\u5728\u8FD0\u884C\u671F\u95F4\u8BB0\u5F55\uFF0C\u5F53\u524D\u5DF2\u5B8C\u6210\u3002"),
2588
+ React.createElement(
2589
+ "div",
2590
+ { className: "iterate-obs-bar", style: { marginTop: 8 } },
2591
+ React.createElement("button", { className: "iterate-btn", onClick: () => setTab("f1") }, "\u67E5\u770B\u5BA1\u67E5\u7EBF\u7A0B"),
2592
+ React.createElement("button", { className: "iterate-btn", onClick: () => setTab("f3") }, "\u67E5\u770B\u53D1\u73B0")
2593
+ )
2594
+ );
2595
+ }
2562
2596
  return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u5B9E\u65F6\u6D3B\u52A8");
2563
2597
  }
2564
2598
  const rows = liveEntries.map((e, i) => {
@@ -2631,6 +2665,23 @@ ${JSON.stringify({ operation: "nudge", text: nudgeText }, null, 2)}
2631
2665
  open ? React.createElement(
2632
2666
  "div",
2633
2667
  {},
2668
+ copyFailText ? React.createElement(
2669
+ "div",
2670
+ { className: "iterate-obs-block" },
2671
+ React.createElement(
2672
+ "div",
2673
+ { className: "iterate-obs-block-head" },
2674
+ React.createElement("b", {}, "\u590D\u5236\u672A\u6210\u529F"),
2675
+ React.createElement("span", { className: "iterate-obs-head-meta" }, "\u526A\u8D34\u677F\u5199\u5165\u88AB\u62D2\u7EDD\uFF0C\u8BF7\u624B\u52A8\u9009\u4E2D\u4E0B\u65B9\u6307\u4EE4\u590D\u5236"),
2676
+ React.createElement("button", {
2677
+ className: "iterate-btn",
2678
+ "data-ghost": "",
2679
+ "aria-label": "\u5173\u95ED\u590D\u5236\u5931\u8D25\u63D0\u793A",
2680
+ onClick: () => setCopyFailText(null)
2681
+ }, "\u5173\u95ED")
2682
+ ),
2683
+ React.createElement("div", { className: "iterate-obs-code" }, copyFailText)
2684
+ ) : null,
2634
2685
  React.createElement(
2635
2686
  "div",
2636
2687
  { className: "iterate-obs-tabs" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iterate-plugin",
3
- "version": "2.12.0",
3
+ "version": "2.12.1",
4
4
  "description": "dsh plugin that turns the iterate skill into an autonomous closed-loop harness: plan -> parallel review xN -> atomic fixes -> validate -> loop -> auto-stop, plus a dry-run pure-review mode with multi-round convergence and a meta-review that audits the report and emits a final review report.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -967,6 +967,14 @@ function TriagePanel(props: SlotProps) {
967
967
  const t = ev.target as HTMLElement | null
968
968
  if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.tagName === 'SELECT')) return
969
969
  if (t && typeof t.isContentEditable === 'boolean' && t.isContentEditable) return
970
+ // Only act when focus is INSIDE the triage panel. Everything else keeps
971
+ // standard behavior (↑/↓ page scroll, typing in the composer), so we do
972
+ // not hijack document-level keys just because a report is mounted.
973
+ const rootEl = doc.querySelector('[data-iterate="triage"]')
974
+ if (!rootEl) return
975
+ const activeEl = doc.activeElement as HTMLElement | null
976
+ if (!activeEl) return
977
+ if (activeEl !== rootEl && !(typeof rootEl.contains === 'function' && rootEl.contains(activeEl))) return
970
978
  const verdict = keyToVerdict(ev.key)
971
979
  if (verdict && selected !== null && indices.includes(selected)) {
972
980
  ev.preventDefault()
@@ -1466,6 +1474,10 @@ function ObservatoryPanel(props: SlotProps) {
1466
1474
  const [tab, setTab] = React.useState('live')
1467
1475
  const [expandedThreads, setExpandedThreads] = React.useState<Set<string>>(new Set())
1468
1476
  const [copiedKey, setCopiedKey] = React.useState<string | null>(null)
1477
+ // When a clipboard write is blocked (permissions/unsupported), reveal the
1478
+ // raw instruction text so the user can copy it manually instead of silently
1479
+ // dropping the action (mirrors the triage/settings fallback).
1480
+ const [copyFailText, setCopyFailText] = React.useState<string | null>(null)
1469
1481
  const [nudgeText, setNudgeText] = React.useState('')
1470
1482
  const [timelineType, setTimelineType] = React.useState('')
1471
1483
  const [timelineSearch, setTimelineSearch] = React.useState('')
@@ -1478,7 +1490,7 @@ function ObservatoryPanel(props: SlotProps) {
1478
1490
  const copyInstruction = (key: string, text: string) => {
1479
1491
  if (!text) return
1480
1492
  copyText(text).then((ok) => {
1481
- if (!ok) return
1493
+ if (!ok) { setCopyFailText(text); return }
1482
1494
  setCopiedKey(key)
1483
1495
  if (copyTimer.current) clearTimeout(copyTimer.current)
1484
1496
  copyTimer.current = setTimeout(() => setCopiedKey((cur) => (cur === key ? null : cur)), 1600)
@@ -1732,6 +1744,10 @@ function ObservatoryPanel(props: SlotProps) {
1732
1744
  const present = Boolean(nudge && nudge.text)
1733
1745
  const activeNudgeText = nudge && nudge.text ? nudge.text : ''
1734
1746
  const nudgeInstruction = `请调用 \`iterate_transcript\` 写入 nudge 指令:\n\n\`\`\`json\n${JSON.stringify({ operation: 'nudge', text: nudgeText }, null, 2)}\n\`\`\``
1747
+ // Clearing the PERSISTED nudge (as opposed to the draft) requires
1748
+ // text:null. Copied as a paste-able instruction, mirroring the panel's
1749
+ // copy-to-command pattern — it cannot be cleared from the client directly.
1750
+ const activeClearInstruction = `请调用 \`iterate_transcript\` 清除当前 nudge:\n\n\`\`\`json\n${JSON.stringify({ operation: 'nudge', text: null }, null, 2)}\n\`\`\``
1735
1751
  return React.createElement('div', { className: 'iterate-obs-block' },
1736
1752
  React.createElement('div', { className: 'iterate-obs-block-head' },
1737
1753
  React.createElement('span', {}, '运行控制台'),
@@ -1743,7 +1759,11 @@ function ObservatoryPanel(props: SlotProps) {
1743
1759
  ? React.createElement('div', { className: 'iterate-obs-bar', style: { marginBottom: 6 } },
1744
1760
  React.createElement('b', {}, '当前 nudge'),
1745
1761
  React.createElement('span', { className: 'iterate-obs-msg' }, activeNudgeText),
1746
- React.createElement('button', { className: 'iterate-btn', onClick: () => setNudgeText('') }, '清除'),
1762
+ React.createElement('button', {
1763
+ className: 'iterate-btn', 'data-copied': copiedKey === 'nudge-clear' ? '' : undefined,
1764
+ onClick: () => copyInstruction('nudge-clear', activeClearInstruction),
1765
+ title: '复制清除 nudge 指令(写 text:null),在运行控制台提示清除已持久化的 nudge',
1766
+ }, copiedKey === 'nudge-clear' ? '已复制清除指令' : '复制清除指令'),
1747
1767
  )
1748
1768
  : null,
1749
1769
  React.createElement('textarea', {
@@ -1828,6 +1848,20 @@ function ObservatoryPanel(props: SlotProps) {
1828
1848
  const renderLive = () => {
1829
1849
  const liveEntries = manifest.live || []
1830
1850
  if (liveEntries.length === 0) {
1851
+ // Live entries only accumulate during an ACTIVE run. For a completed run
1852
+ // (or before any capture) this tab is empty — guide the user to the
1853
+ // populated summary tabs instead of leaving a dead-end "暂无实时活动" state.
1854
+ const hasThreads = (manifest.rounds || []).length > 0
1855
+ const hasFindings = (manifest.findings || []).length > 0
1856
+ if (hasThreads || hasFindings) {
1857
+ return React.createElement('div', { className: 'iterate-obs-empty' },
1858
+ React.createElement('div', {}, '实时活动流仅在运行期间记录,当前已完成。'),
1859
+ React.createElement('div', { className: 'iterate-obs-bar', style: { marginTop: 8 } },
1860
+ React.createElement('button', { className: 'iterate-btn', onClick: () => setTab('f1') }, '查看审查线程'),
1861
+ React.createElement('button', { className: 'iterate-btn', onClick: () => setTab('f3') }, '查看发现'),
1862
+ ),
1863
+ )
1864
+ }
1831
1865
  return React.createElement('div', { className: 'iterate-obs-empty' }, '暂无实时活动')
1832
1866
  }
1833
1867
  const rows = liveEntries.map((e, i) => {
@@ -1880,6 +1914,19 @@ function ObservatoryPanel(props: SlotProps) {
1880
1914
  ),
1881
1915
  open
1882
1916
  ? React.createElement('div', {},
1917
+ copyFailText
1918
+ ? React.createElement('div', { className: 'iterate-obs-block' },
1919
+ React.createElement('div', { className: 'iterate-obs-block-head' },
1920
+ React.createElement('b', {}, '复制未成功'),
1921
+ React.createElement('span', { className: 'iterate-obs-head-meta' }, '剪贴板写入被拒绝,请手动选中下方指令复制'),
1922
+ React.createElement('button', {
1923
+ className: 'iterate-btn', 'data-ghost': '', 'aria-label': '关闭复制失败提示',
1924
+ onClick: () => setCopyFailText(null),
1925
+ }, '关闭'),
1926
+ ),
1927
+ React.createElement('div', { className: 'iterate-obs-code' }, copyFailText),
1928
+ )
1929
+ : null,
1883
1930
  React.createElement('div', { className: 'iterate-obs-tabs' },
1884
1931
  ...OBS_TABS.map((t) =>
1885
1932
  React.createElement('button', {