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 +53 -2
- package/package.json +1 -1
- package/src/client/index.ts +49 -2
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)
|
|
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", {
|
|
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.
|
|
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",
|
package/src/client/index.ts
CHANGED
|
@@ -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', {
|
|
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', {
|