@dsh-suite/plugin-deus 0.3.0 → 0.4.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 +2 -1
- package/lib/client.js +268 -1
- package/lib/index.js +148 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
- 对话页 dock 出现 `⚓ 锚定维持中` 状态 chip;**漂移时变红**,点击发送重锚提示词(可开「漂移自动重锚」);重锚轮日志以 `reanchor` 模式独立记录,便于对比重锚前后维持率。
|
|
29
29
|
6. **未锚定引导(v0.3)** — 当前会话不在 deus/minimal 系 preset 时,dock 显示灰态 `◌ 非神版会话` chip,点击展开切换指引(把「25 工具会话注入≈0%」的实测结论落地为产品引导)。
|
|
30
30
|
7. **统计导出与分享(v0.3)** — 面板一键导出 CSV、**复制 Markdown 摘要**(Wilson CI 表格 + 诚实声明 + 包链接,可直接发帖),并内嵌近期神版率迷你趋势图(最近 40 条日志分桶折线)。
|
|
31
|
+
8. **提示词 A/B 实验台(v0.4)** — 设计文档的「纯噪音退路」转正:对话页 dock 的 🧪 **实验台**里输入**任意**提示词变体 A/B,各跑 N 次**真实会话注入**(每次试验自动点「新建会话」隔离上下文),宿主按提示词文本队列配对判定,产出**指纹分布对比表**——每类的 Wilson 95% CI、Δ(A−B)、两比例 z 检验与显著性标记。Settings 面板可回看/切换历史实验。deus 由此从「神模工具」升级为「提示词实验台」,神模只是第一个被测假设。
|
|
31
32
|
|
|
32
33
|
## 安装 / Install
|
|
33
34
|
|
|
@@ -52,7 +53,7 @@ npx @deepseek-ai/dsh plugin --profile web add ./plugin-deus # 在其父目录
|
|
|
52
53
|
|
|
53
54
|
- 日志只写本地 `$DSH_HOME/deus-mode/log.jsonl`,不上传、可一键清空、可导出 CSV。
|
|
54
55
|
- 全部是用户正常交互(prompt 注入 + 回复观测),无越权、无绕过。
|
|
55
|
-
- 若实验证明无差异:本插件的身份即**通用极简提示词 A/B 测试台**,"神模"
|
|
56
|
+
- 若实验证明无差异:本插件的身份即**通用极简提示词 A/B 测试台**,"神模"只是第一个被测假设。(v0.4 起实验台已内建:🧪 dock 芯片直接开跑。)
|
|
56
57
|
|
|
57
58
|
## 实测依据 / Measured basis
|
|
58
59
|
|
package/lib/client.js
CHANGED
|
@@ -45,6 +45,20 @@ window.__ModuleLoader__.load({
|
|
|
45
45
|
unanchored: '◌ 非神版会话', unanchoredTitle: '当前会话不是锚定 preset——实测 25 工具会话注入触发率≈0%。点击查看怎么切换',
|
|
46
46
|
guideText: '实测结论:standard(25 工具)会话里文本注入几乎无法触发神版(0%)。想用神版:新建会话时在 Agent preset 选择器选「神模扳机 · 窄锚 / 宽锚」(实测触发率 ~90% / ~65%)。',
|
|
47
47
|
copyMd: '复制 Markdown 摘要', mdCopied: '已复制 ✓ 去发帖吧', trend: '近期神版率趋势',
|
|
48
|
+
bench: '实验台',
|
|
49
|
+
benchHint: '提示词 A/B 实验台:任意两个提示词变体各跑 N 次真实会话注入(每次自动开新会话隔离上下文),对比起手式指纹分布。Wilson 95% CI + 两比例 z 检验;小样本只作倾向信号。神模只是第一个被测假设。',
|
|
50
|
+
benchA: '变体 A 提示词', benchB: '变体 B 提示词', benchTimes: '每变体次数 N',
|
|
51
|
+
benchRun: '▶ 开始实验', benchAbort: '⏹ 中止', benchRunning: '实验进行中',
|
|
52
|
+
benchProgress: '进度', benchWaiting: '等待回复…', benchDone: '实验完成',
|
|
53
|
+
benchIsolationWarn: '⚠ 未找到「新建会话」按钮,本轮在同会话内连跑,上下文未隔离',
|
|
54
|
+
benchTimeout: '等待回复超时(240s),实验中止',
|
|
55
|
+
benchEmpty: '请先填写变体 A 与变体 B 的提示词',
|
|
56
|
+
benchReportTitle: '指纹分布对比', benchRunId: '实验编号', benchStarted: '开始', benchFinished: '结束',
|
|
57
|
+
colFp: '指纹', colDelta: 'Δ(A−B)', colZ: 'z', colP: 'p', colSig: '显著',
|
|
58
|
+
benchNone: '还没有实验记录。在对话页输入框上方的 ⚗ 芯片行打开「实验台」跑一轮。',
|
|
59
|
+
benchPickRun: '选择实验', benchRefresh: '刷新',
|
|
60
|
+
benchDefA: '用三句话向聪明的初中生解释「熵」。',
|
|
61
|
+
benchDefB: '你是一位世界顶尖科普作家,回答前先在脑内推演三遍。用三句话向聪明的初中生解释「熵」。',
|
|
48
62
|
}
|
|
49
63
|
const en = {
|
|
50
64
|
nav: 'Deus Trigger', sub: 'Minimal-prompt trigger bench · inject → classify → stats',
|
|
@@ -72,6 +86,20 @@ window.__ModuleLoader__.load({
|
|
|
72
86
|
unanchored: '◌ Not a deus session', unanchoredTitle: 'This session is not on an anchored preset — injection measured ≈0% effective on 25-tool sessions. Click for how to switch',
|
|
73
87
|
guideText: 'Measured: text injection barely triggers god-mode in standard (25-tool) sessions (0%). To get it: start a new session and pick "Deus Trigger · narrow / wide anchor" in the Agent preset picker (~90% / ~65% measured trigger rate).',
|
|
74
88
|
copyMd: 'Copy Markdown summary', mdCopied: 'Copied ✓ go post it', trend: 'Recent god-rate trend',
|
|
89
|
+
bench: 'A/B Bench',
|
|
90
|
+
benchHint: 'Prompt A/B bench: run any two prompt variants N times each via real session injection (a fresh session per trial isolates context), then compare opener-fingerprint distributions. Wilson 95% CI + two-proportion z-test; small samples are directional only. God-mode is just the first hypothesis under test.',
|
|
91
|
+
benchA: 'Variant A prompt', benchB: 'Variant B prompt', benchTimes: 'Trials per variant N',
|
|
92
|
+
benchRun: '▶ Run experiment', benchAbort: '⏹ Abort', benchRunning: 'Running',
|
|
93
|
+
benchProgress: 'Progress', benchWaiting: 'waiting for reply…', benchDone: 'Experiment done',
|
|
94
|
+
benchIsolationWarn: '⚠ "New session" button not found; trials ran in the same session without context isolation',
|
|
95
|
+
benchTimeout: 'Timed out waiting for reply (240s); aborted',
|
|
96
|
+
benchEmpty: 'Fill in both variant A and variant B prompts first',
|
|
97
|
+
benchReportTitle: 'Fingerprint distribution comparison', benchRunId: 'Run', benchStarted: 'Started', benchFinished: 'Finished',
|
|
98
|
+
colFp: 'fingerprint', colDelta: 'Δ(A−B)', colZ: 'z', colP: 'p', colSig: 'sig',
|
|
99
|
+
benchNone: 'No experiments yet. Open the "A/B Bench" from the ⚗ chip row above the composer on a conversation page.',
|
|
100
|
+
benchPickRun: 'Pick run', benchRefresh: 'Refresh',
|
|
101
|
+
benchDefA: 'Explain "entropy" to a bright middle-schooler in three sentences.',
|
|
102
|
+
benchDefB: 'You are a world-class science writer; rehearse the answer three times in your head before speaking. Explain "entropy" to a bright middle-schooler in three sentences.',
|
|
75
103
|
}
|
|
76
104
|
|
|
77
105
|
const S = {
|
|
@@ -90,6 +118,11 @@ window.__ModuleLoader__.load({
|
|
|
90
118
|
td: { padding: '6px 8px', borderBottom: '1px solid #21262d' },
|
|
91
119
|
input: { background: '#0d1117', border: '1px solid #30363d', borderRadius: '6px', color: '#e6edf3', padding: '6px 8px', fontSize: '12px', width: '100%', boxSizing: 'border-box' },
|
|
92
120
|
mono: { fontFamily: 'monospace', fontSize: '12px', color: '#c9d1d9' },
|
|
121
|
+
benchWrap: { width: '100%', background: '#0d1117', border: '1px solid #30363d', borderRadius: '10px', padding: '10px 12px', marginBottom: '6px' },
|
|
122
|
+
benchTa: { width: '100%', minHeight: '52px', background: '#161b22', border: '1px solid #30363d', borderRadius: '8px', color: '#c9d1d9', fontSize: '12px', padding: '6px 8px', boxSizing: 'border-box', resize: 'vertical' },
|
|
123
|
+
benchLabel: { fontSize: '11px', color: '#8b949e', margin: '6px 0 3px' },
|
|
124
|
+
benchTable: { width: '100%', borderCollapse: 'collapse', fontSize: '11px', marginTop: '6px' },
|
|
125
|
+
benchCell: { border: '1px solid #21262d', padding: '3px 6px', textAlign: 'right', whiteSpace: 'nowrap' },
|
|
93
126
|
dock: { display: 'flex', gap: '6px', alignItems: 'center', flexWrap: 'wrap', padding: '4px 0' },
|
|
94
127
|
dockChip: { background: '#161b22', border: '1px solid #30363d', borderRadius: '16px', color: '#8b949e', padding: '3px 12px', fontSize: '12px', cursor: 'pointer' },
|
|
95
128
|
}
|
|
@@ -97,6 +130,56 @@ window.__ModuleLoader__.load({
|
|
|
97
130
|
const pct = (ci) => `${(ci.rate * 100).toFixed(0)}% [${(ci.low * 100).toFixed(0)}–${(ci.high * 100).toFixed(0)}]`
|
|
98
131
|
|
|
99
132
|
// ── tier A: settings section panel ──────────────────────────────────────
|
|
133
|
+
// ── v0.4 实验台:报告拉取与 A/B 对比表(Dock 控制台与设置面板共用)─────
|
|
134
|
+
function fetchBenchReport(runId) {
|
|
135
|
+
const url = runId ? '/deus/bench/report?runId=' + encodeURIComponent(runId) : '/deus/bench/report'
|
|
136
|
+
return fetch(url).then((r) => r.json())
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const BENCH_CLS_COLORS = { god: '#d2a8ff', med: '#3fb950', pure: '#79c0ff', unknown: '#8b949e' }
|
|
140
|
+
|
|
141
|
+
function benchCellFmt(g, n) {
|
|
142
|
+
if (!g || !n) return '—'
|
|
143
|
+
return g.n + '/' + n + ' · ' + Math.round(100 * g.rate) + '% [' + Math.round(100 * g.low) + '–' + Math.round(100 * g.high) + ']'
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function BenchTable(props) {
|
|
147
|
+
const report = props.report
|
|
148
|
+
const t = props.t
|
|
149
|
+
if (!report) return null
|
|
150
|
+
const A = report.order[0]
|
|
151
|
+
const B = report.order[1] || 'B'
|
|
152
|
+
const vA = report.variants[A]
|
|
153
|
+
const vB = report.variants[B]
|
|
154
|
+
const sig = (p) => (p == null ? '' : p < 0.01 ? '✱✱' : p < 0.05 ? '✱' : '')
|
|
155
|
+
return h('div', { 'data-deus': 'bench-report', style: { marginTop: '8px' } },
|
|
156
|
+
h('div', { style: S.status },
|
|
157
|
+
t('benchRunId') + ': ' + report.runId + ' · n=' + report.n +
|
|
158
|
+
' · ' + t('benchStarted') + ' ' + String(report.startedAt).slice(11, 19) +
|
|
159
|
+
' · ' + t('benchFinished') + ' ' + String(report.finishedAt).slice(11, 19)),
|
|
160
|
+
h('table', { style: S.benchTable },
|
|
161
|
+
h('thead', null, h('tr', null,
|
|
162
|
+
h('th', { style: { ...S.benchCell, textAlign: 'left' } }, t('colFp')),
|
|
163
|
+
h('th', { style: S.benchCell }, 'A (n=' + (vA ? vA.n : 0) + ')'),
|
|
164
|
+
h('th', { style: S.benchCell }, 'B (n=' + (vB ? vB.n : 0) + ')'),
|
|
165
|
+
h('th', { style: S.benchCell }, t('colDelta')),
|
|
166
|
+
h('th', { style: S.benchCell }, t('colZ')),
|
|
167
|
+
h('th', { style: S.benchCell }, t('colP')),
|
|
168
|
+
h('th', { style: S.benchCell }, t('colSig')))),
|
|
169
|
+
h('tbody', null, (report.comparison || []).map((row) => h('tr', { key: row.cls },
|
|
170
|
+
h('td', { style: { ...S.benchCell, textAlign: 'left', color: BENCH_CLS_COLORS[row.cls] || '#8b949e' } }, t('detected_' + row.cls)),
|
|
171
|
+
h('td', { style: S.benchCell }, benchCellFmt(row.a, vA && vA.n)),
|
|
172
|
+
h('td', { style: S.benchCell }, benchCellFmt(row.b, vB && vB.n)),
|
|
173
|
+
h('td', { style: S.benchCell }, row.delta == null ? '—' : (row.delta >= 0 ? '+' : '') + Math.round(row.delta * 100) + 'pp'),
|
|
174
|
+
h('td', { style: S.benchCell }, row.z == null ? '—' : Number(row.z).toFixed(2)),
|
|
175
|
+
h('td', { style: S.benchCell }, row.p == null ? '—' : row.p < 0.001 ? '<0.001' : Number(row.p).toFixed(3)),
|
|
176
|
+
h('td', { style: { ...S.benchCell, color: '#d29922' } }, sig(row.p)),
|
|
177
|
+
)))),
|
|
178
|
+
vA && vA.prompt ? h('div', { style: { ...S.status, marginTop: '6px' } }, 'A: ' + JSON.stringify(vA.prompt)) : null,
|
|
179
|
+
vB && vB.prompt ? h('div', { style: S.status }, 'B: ' + JSON.stringify(vB.prompt)) : null,
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
100
183
|
function Panel(props) {
|
|
101
184
|
const t = props.t
|
|
102
185
|
const [presets, setPresets] = useState([])
|
|
@@ -107,6 +190,12 @@ window.__ModuleLoader__.load({
|
|
|
107
190
|
const [err, setErr] = useState('')
|
|
108
191
|
const [copied, setCopied] = useState('')
|
|
109
192
|
const [editing, setEditing] = useState(null) // {id, label_zh, prompt} draft
|
|
193
|
+
const [benchData, setBenchData] = useState(null) // { ok, report, runs }
|
|
194
|
+
const [benchRunId, setBenchRunId] = useState('')
|
|
195
|
+
|
|
196
|
+
function refreshBench(runId) {
|
|
197
|
+
fetchBenchReport(runId || undefined).then((d) => setBenchData(d)).catch(() => {})
|
|
198
|
+
}
|
|
110
199
|
|
|
111
200
|
function refresh() {
|
|
112
201
|
fetch('/deus/presets').then((r) => r.json()).then((d) => setPresets(d.presets || [])).catch(() => setErr(t('loadFail')))
|
|
@@ -114,6 +203,7 @@ window.__ModuleLoader__.load({
|
|
|
114
203
|
fetch('/deus/log').then((r) => r.json()).then((d) => setEntries(d.entries || [])).catch(() => {})
|
|
115
204
|
fetch('/deus/version').then((r) => r.json()).then(setVer).catch(() => {})
|
|
116
205
|
fetch('/deus/anchor').then((r) => r.json()).then(setAnchor).catch(() => {})
|
|
206
|
+
refreshBench(benchRunId)
|
|
117
207
|
}
|
|
118
208
|
useEffect(() => { refresh() }, [])
|
|
119
209
|
|
|
@@ -256,6 +346,23 @@ window.__ModuleLoader__.load({
|
|
|
256
346
|
)
|
|
257
347
|
: h('div', { style: S.status }, t('anchorNone')),
|
|
258
348
|
|
|
349
|
+
h('div', { style: S.h2 }, '🧪 ' + t('bench')),
|
|
350
|
+
h('div', { style: S.status }, t('benchHint')),
|
|
351
|
+
h('div', { style: S.row },
|
|
352
|
+
h('button', { style: S.btn, 'data-deus': 'bench-refresh', onClick: () => refreshBench(benchRunId) }, '🔄 ' + t('benchRefresh')),
|
|
353
|
+
benchData && benchData.runs && benchData.runs.length > 0 ? h('select', {
|
|
354
|
+
style: { ...S.input, width: 'auto' }, 'data-deus': 'bench-runs',
|
|
355
|
+
value: benchRunId,
|
|
356
|
+
onChange: (e) => { setBenchRunId(e.target.value); refreshBench(e.target.value) },
|
|
357
|
+
},
|
|
358
|
+
h('option', { value: '' }, t('benchPickRun')),
|
|
359
|
+
benchData.runs.map((r) => h('option', { key: r.runId, value: r.runId }, r.runId + ' · n=' + r.n)),
|
|
360
|
+
) : null,
|
|
361
|
+
),
|
|
362
|
+
benchData && benchData.report
|
|
363
|
+
? h(BenchTable, { report: benchData.report, t })
|
|
364
|
+
: h('div', { style: S.status, 'data-deus': 'bench-empty' }, t('benchNone')),
|
|
365
|
+
|
|
259
366
|
h('div', { style: S.h2 }, t('recent')),
|
|
260
367
|
h('div', { style: S.status }, t('recentHint')),
|
|
261
368
|
entries.length === 0 ? h('div', { style: S.status }, t('noData'))
|
|
@@ -297,6 +404,129 @@ window.__ModuleLoader__.load({
|
|
|
297
404
|
const [sessPreset, setSessPreset] = useState(null)
|
|
298
405
|
const [showGuide, setShowGuide] = useState(false)
|
|
299
406
|
const lastReanchorKey = useRef('')
|
|
407
|
+
|
|
408
|
+
// ── v0.4 实验台(A/B bench)──────────────────────────────────────────
|
|
409
|
+
const [benchOpen, setBenchOpen] = useState(false)
|
|
410
|
+
const [benchA, setBenchA] = useState(() => {
|
|
411
|
+
try { return window.localStorage.getItem('deus.benchA') || t('benchDefA') } catch { return t('benchDefA') }
|
|
412
|
+
})
|
|
413
|
+
const [benchB, setBenchB] = useState(() => {
|
|
414
|
+
try { return window.localStorage.getItem('deus.benchB') || t('benchDefB') } catch { return t('benchDefB') }
|
|
415
|
+
})
|
|
416
|
+
const [benchN, setBenchN] = useState(3)
|
|
417
|
+
const [benchRunning, setBenchRunning] = useState(false)
|
|
418
|
+
const [benchProg, setBenchProg] = useState(null) // { a, b, n, label, waiting }
|
|
419
|
+
const [benchReport, setBenchReport] = useState(null)
|
|
420
|
+
const [benchNote, setBenchNote] = useState('')
|
|
421
|
+
const benchAbort = useRef(false)
|
|
422
|
+
const benchRunIdRef = useRef('')
|
|
423
|
+
const sidRef = useRef(sessionId)
|
|
424
|
+
const actionsRef = useRef(actions)
|
|
425
|
+
useEffect(() => { sidRef.current = sessionId; actionsRef.current = actions })
|
|
426
|
+
|
|
427
|
+
function sleep(ms) { return new Promise((r) => setTimeout(r, ms)) }
|
|
428
|
+
|
|
429
|
+
// 每次试验开一个新会话以隔离上下文:点侧栏「新建会话」按钮,
|
|
430
|
+
// 然后等 dock 的 sessionId prop 变化(运行时重渲染本会带回新 id)。
|
|
431
|
+
async function benchNewSession(prevSid) {
|
|
432
|
+
const sels = [
|
|
433
|
+
'button[aria-label="新建会话"]', 'button[aria-label="New session"]',
|
|
434
|
+
'[role="button"][aria-label="新建会话"]', '[role="button"][aria-label="New session"]',
|
|
435
|
+
]
|
|
436
|
+
let btn = null
|
|
437
|
+
for (const sel of sels) { btn = document.querySelector(sel); if (btn) break }
|
|
438
|
+
if (!btn) {
|
|
439
|
+
btn = Array.from(document.querySelectorAll('button, [role="button"]'))
|
|
440
|
+
.find((b) => /新建会话|New session/i.test((b.getAttribute('aria-label') || '') + ' ' + (b.title || '')))
|
|
441
|
+
}
|
|
442
|
+
if (!btn) return false
|
|
443
|
+
btn.click()
|
|
444
|
+
const t0 = Date.now()
|
|
445
|
+
while (Date.now() - t0 < 10000) {
|
|
446
|
+
await sleep(300)
|
|
447
|
+
if (String(sidRef.current || '') !== String(prevSid || '')) return true
|
|
448
|
+
}
|
|
449
|
+
return false
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function abortBench() {
|
|
453
|
+
benchAbort.current = true
|
|
454
|
+
try {
|
|
455
|
+
await fetch('/deus/bench/clear', {
|
|
456
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
457
|
+
body: JSON.stringify({ runId: benchRunIdRef.current }),
|
|
458
|
+
})
|
|
459
|
+
} catch { /* best-effort */ }
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async function runBench() {
|
|
463
|
+
const aText = benchA.trim()
|
|
464
|
+
const bText = benchB.trim()
|
|
465
|
+
if (!aText || !bText) { setBenchNote(t('benchEmpty')); return }
|
|
466
|
+
setBenchRunning(true); setBenchNote(''); setBenchReport(null)
|
|
467
|
+
benchAbort.current = false
|
|
468
|
+
const runId = 'bench-' + Date.now().toString(36)
|
|
469
|
+
benchRunIdRef.current = runId
|
|
470
|
+
const n = Math.max(1, Math.min(20, Math.floor(Number(benchN) || 1)))
|
|
471
|
+
const trials = []
|
|
472
|
+
for (let i = 0; i < n; i++) { trials.push(['A', aText, i + 1]); trials.push(['B', bText, i + 1]) }
|
|
473
|
+
let doneA = 0
|
|
474
|
+
let doneB = 0
|
|
475
|
+
const total = trials.length
|
|
476
|
+
try { window.__deusBench = { running: true, runId, done: 0, total } } catch { /* ignore */ }
|
|
477
|
+
try {
|
|
478
|
+
for (const [variant, text, idx] of trials) {
|
|
479
|
+
if (benchAbort.current) break
|
|
480
|
+
setBenchProg({ a: doneA, b: doneB, n, label: variant + '#' + idx, waiting: false })
|
|
481
|
+
// 1) 新会话隔离(失败则降级为同会话连跑并亮警告)
|
|
482
|
+
const prevSid = sidRef.current
|
|
483
|
+
const switched = await benchNewSession(prevSid)
|
|
484
|
+
if (!switched) { setBenchNote(t('benchIsolationWarn')); await sleep(400) }
|
|
485
|
+
if (benchAbort.current) break
|
|
486
|
+
// 2) 等 composer actions 就绪
|
|
487
|
+
const w0 = Date.now()
|
|
488
|
+
while (!(actionsRef.current && typeof actionsRef.current.setDraft === 'function')) {
|
|
489
|
+
if (Date.now() - w0 > 8000) break
|
|
490
|
+
await sleep(250)
|
|
491
|
+
}
|
|
492
|
+
// 3) 宿主标记(文本队列配对)→ 注入并发送
|
|
493
|
+
try {
|
|
494
|
+
await fetch('/deus/bench/mark', {
|
|
495
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
496
|
+
body: JSON.stringify({ runId, variant, prompt: text }),
|
|
497
|
+
})
|
|
498
|
+
} catch { /* marking best-effort */ }
|
|
499
|
+
const acts = actionsRef.current
|
|
500
|
+
if (!acts || typeof acts.setDraft !== 'function' || typeof acts.submit !== 'function') {
|
|
501
|
+
setBenchNote(t('benchTimeout')); break
|
|
502
|
+
}
|
|
503
|
+
acts.setDraft(text)
|
|
504
|
+
await sleep(200)
|
|
505
|
+
acts.submit()
|
|
506
|
+
// 4) 轮询报告计数,等宿主把这轮的指纹判定写进日志
|
|
507
|
+
setBenchProg({ a: doneA, b: doneB, n, label: variant + '#' + idx, waiting: true })
|
|
508
|
+
const doneSoFar = doneA + doneB
|
|
509
|
+
const t0 = Date.now()
|
|
510
|
+
let arrived = false
|
|
511
|
+
while (Date.now() - t0 < 240000) {
|
|
512
|
+
if (benchAbort.current) break
|
|
513
|
+
await sleep(2000)
|
|
514
|
+
const rep = await fetchBenchReport(runId).catch(() => null)
|
|
515
|
+
if (rep && rep.report && rep.report.n >= doneSoFar + 1) { arrived = true; break }
|
|
516
|
+
}
|
|
517
|
+
if (!arrived && !benchAbort.current) { setBenchNote(t('benchTimeout')); break }
|
|
518
|
+
if (variant === 'A') doneA++; else doneB++
|
|
519
|
+
try { window.__deusBench = { running: true, runId, done: doneA + doneB, total } } catch { /* ignore */ }
|
|
520
|
+
await sleep(600)
|
|
521
|
+
}
|
|
522
|
+
} finally {
|
|
523
|
+
setBenchRunning(false)
|
|
524
|
+
setBenchProg(null)
|
|
525
|
+
try { window.__deusBench = { running: false, runId, done: doneA + doneB, total } } catch { /* ignore */ }
|
|
526
|
+
}
|
|
527
|
+
const fin = await fetchBenchReport(runId).catch(() => null)
|
|
528
|
+
if (fin && fin.report) setBenchReport(fin.report)
|
|
529
|
+
}
|
|
300
530
|
const REANCHOR = 'We need to continue working on this together. Let us pick up where we left off. 我们继续协作,接着上一步往下做。'
|
|
301
531
|
|
|
302
532
|
useEffect(() => {
|
|
@@ -374,7 +604,37 @@ window.__ModuleLoader__.load({
|
|
|
374
604
|
const UNANCHORED_PRESET_RE = /^(deus-|minimal)/
|
|
375
605
|
const unanchored = !st && sessPreset && !UNANCHORED_PRESET_RE.test(sessPreset)
|
|
376
606
|
|
|
377
|
-
|
|
607
|
+
const benchPanel = benchOpen ? h('div', { style: S.benchWrap, 'data-deus': 'bench-panel' },
|
|
608
|
+
h('div', { style: { fontSize: '12px', fontWeight: 600, color: '#e6edf3' } }, '🧪 ' + t('bench')),
|
|
609
|
+
h('div', { style: S.status }, t('benchHint')),
|
|
610
|
+
h('div', { style: S.benchLabel }, t('benchA')),
|
|
611
|
+
h('textarea', {
|
|
612
|
+
style: S.benchTa, 'data-deus': 'bench-a', value: benchA, disabled: benchRunning,
|
|
613
|
+
onChange: (e) => { setBenchA(e.target.value); try { window.localStorage.setItem('deus.benchA', e.target.value) } catch { /* ignore */ } },
|
|
614
|
+
}),
|
|
615
|
+
h('div', { style: S.benchLabel }, t('benchB')),
|
|
616
|
+
h('textarea', {
|
|
617
|
+
style: S.benchTa, 'data-deus': 'bench-b', value: benchB, disabled: benchRunning,
|
|
618
|
+
onChange: (e) => { setBenchB(e.target.value); try { window.localStorage.setItem('deus.benchB', e.target.value) } catch { /* ignore */ } },
|
|
619
|
+
}),
|
|
620
|
+
h('div', { style: { ...S.row, marginTop: '8px', alignItems: 'center' } },
|
|
621
|
+
h('span', { style: { ...S.benchLabel, margin: 0 } }, t('benchTimes')),
|
|
622
|
+
h('input', {
|
|
623
|
+
type: 'number', min: 1, max: 20, 'data-deus': 'bench-n', value: benchN, disabled: benchRunning,
|
|
624
|
+
style: { background: '#161b22', border: '1px solid #30363d', borderRadius: '8px', color: '#c9d1d9', fontSize: '12px', padding: '4px 6px', width: '60px' },
|
|
625
|
+
onChange: (e) => setBenchN(e.target.value),
|
|
626
|
+
}),
|
|
627
|
+
benchRunning
|
|
628
|
+
? h('button', { style: { ...S.btn, color: '#f85149', borderColor: '#f85149' }, 'data-deus': 'bench-abort', onClick: abortBench }, t('benchAbort'))
|
|
629
|
+
: h('button', { style: S.btnActive, 'data-deus': 'bench-run', onClick: runBench }, t('benchRun')),
|
|
630
|
+
),
|
|
631
|
+
benchProg ? h('div', { style: S.status, 'data-deus': 'bench-progress' },
|
|
632
|
+
t('benchRunning') + ' · ' + t('benchProgress') + ': A ' + benchProg.a + '/' + benchProg.n + ' · B ' + benchProg.b + '/' + benchProg.n + ' · ' + benchProg.label + (benchProg.waiting ? ' ' + t('benchWaiting') : '')) : null,
|
|
633
|
+
benchNote ? h('div', { style: { ...S.status, color: '#d29922' }, 'data-deus': 'bench-note' }, benchNote) : null,
|
|
634
|
+
benchReport ? h(BenchTable, { report: benchReport, t }) : null,
|
|
635
|
+
) : null
|
|
636
|
+
|
|
637
|
+
const dockRow = h('div', { style: S.dock, title: t('dockHint') },
|
|
378
638
|
h('span', { style: { fontSize: '11px', color: '#8b949e' } }, '⚗'),
|
|
379
639
|
presets.map((p) => h('button', {
|
|
380
640
|
key: p.id, style: S.dockChip, title: t('dockHint'),
|
|
@@ -401,7 +661,13 @@ window.__ModuleLoader__.load({
|
|
|
401
661
|
(autoSend ? '☑ ' : '☐ ') + t('autoSend')),
|
|
402
662
|
st ? h('button', { style: { ...S.dockChip, borderStyle: 'dashed' }, title: t('autoReanchor'), onClick: toggleAutoReanchor },
|
|
403
663
|
(autoReanchor ? '☑ ' : '☐ ') + t('autoReanchor')) : null,
|
|
664
|
+
h('button', {
|
|
665
|
+
style: { ...S.dockChip, borderStyle: 'dashed', color: benchOpen ? '#d2a8ff' : '#8b949e', borderColor: benchOpen ? '#d2a8ff' : '#30363d' },
|
|
666
|
+
title: t('bench'), 'data-deus': 'bench-chip',
|
|
667
|
+
onClick: () => setBenchOpen((v) => !v),
|
|
668
|
+
}, '🧪 ' + t('bench')),
|
|
404
669
|
)
|
|
670
|
+
return h('div', { style: { width: '100%' } }, benchPanel, dockRow)
|
|
405
671
|
}
|
|
406
672
|
|
|
407
673
|
// v0.3: 触发率迷你趋势(最近 40 条日志分 10 桶的 god 率折线)
|
|
@@ -453,6 +719,7 @@ window.__ModuleLoader__.load({
|
|
|
453
719
|
inject: ['slots', 'locale'],
|
|
454
720
|
apply(ctx) {
|
|
455
721
|
try {
|
|
722
|
+
try { window.__deusClient = '0.4.0' } catch { /* non-browser */ }
|
|
456
723
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'deus-mode: dicts')
|
|
457
724
|
const t = ctx.locale.bind(NS)
|
|
458
725
|
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
package/lib/index.js
CHANGED
|
@@ -78,8 +78,27 @@ function wilson(k, n) {
|
|
|
78
78
|
return { rate: p, low: Math.max(0, center - half), high: Math.min(1, center + half) }
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
// ── 两比例 z 检验(v0.4 实验台 A/B 对比用)─────────────────────────────────
|
|
82
|
+
// 标准正态 CDF(Abramowitz–Stegun 7.1.26 近似,|ε| < 7.5e-8)
|
|
83
|
+
function normalCdf(x) {
|
|
84
|
+
const t = 1 / (1 + 0.2316419 * Math.abs(x))
|
|
85
|
+
const d = 0.3989423 * Math.exp((-x * x) / 2)
|
|
86
|
+
const p = d * t * (0.3193815 + t * (-0.3565638 + t * (1.781478 + t * (-1.821256 + t * 1.330274))))
|
|
87
|
+
return x > 0 ? 1 - p : p
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** 两比例 z 检验(双侧)。任一样本为空 → null。 */
|
|
91
|
+
function twoPropZ(kA, nA, kB, nB) {
|
|
92
|
+
if (!nA || !nB) return null
|
|
93
|
+
const pooled = (kA + kB) / (nA + nB)
|
|
94
|
+
const se = Math.sqrt(pooled * (1 - pooled) * (1 / nA + 1 / nB))
|
|
95
|
+
if (!se) return { z: 0, p: 1 }
|
|
96
|
+
const z = (kA / nA - kB / nB) / se
|
|
97
|
+
return { z, p: 2 * (1 - normalCdf(Math.abs(z))) }
|
|
98
|
+
}
|
|
99
|
+
|
|
81
100
|
// ── shipped agent presets (v0.2 锚定维持的静态层) ───────────────────────────
|
|
82
|
-
const PLUGIN_VERSION = '0.
|
|
101
|
+
const PLUGIN_VERSION = '0.4.0'
|
|
83
102
|
// 实测依据(research/deus-mode-matrix.md §8/§9):神版触发需要 Minimal persona
|
|
84
103
|
// ∧ 小工具目录;且本 harness 没有竞品的 promoteOn 机制——preset 构成全程恒定,
|
|
85
104
|
// 所以只要会话跑在锚定 preset 上,注入剥离/工具裁剪自动延续到每一轮。
|
|
@@ -300,6 +319,8 @@ function statsFrom(entries) {
|
|
|
300
319
|
const byMode = new Map()
|
|
301
320
|
for (const e of entries) {
|
|
302
321
|
const mode = e.prompt_mode || 'unknown'
|
|
322
|
+
if (mode === 'bench') continue // v0.4: 实验台样本有独立的 A/B 报告,不混入神模统计
|
|
323
|
+
|
|
303
324
|
if (!byMode.has(mode)) byMode.set(mode, { mode, n: 0, pure: 0, med: 0, god: 0, unknown: 0 })
|
|
304
325
|
const row = byMode.get(mode)
|
|
305
326
|
row.n += 1
|
|
@@ -320,13 +341,85 @@ function statsFrom(entries) {
|
|
|
320
341
|
}
|
|
321
342
|
|
|
322
343
|
function toCsv(entries) {
|
|
323
|
-
const head = 'ts,sessionId,prompt_mode,prompt_text,detected,source,turn,first_sentence,model'
|
|
344
|
+
const head = 'ts,sessionId,prompt_mode,prompt_text,detected,source,turn,first_sentence,model,bench_run,bench_variant'
|
|
324
345
|
const esc = (v) => `"${String(v ?? '').replace(/"/g, '""')}"`
|
|
325
346
|
const rows = entries.map((e) =>
|
|
326
|
-
[e.ts, e.sessionId, e.prompt_mode, e.prompt_text, e.detected, e.source, e.turn, e.first_sentence, e.model].map(esc).join(','))
|
|
347
|
+
[e.ts, e.sessionId, e.prompt_mode, e.prompt_text, e.detected, e.source, e.turn, e.first_sentence, e.model, e.bench_run, e.bench_variant].map(esc).join(','))
|
|
327
348
|
return [head, ...rows].join('\n') + '\n'
|
|
328
349
|
}
|
|
329
350
|
|
|
351
|
+
// ── v0.4 实验台:A/B 报告(指纹分布对比 + Wilson CI + 两比例 z 检验)────────
|
|
352
|
+
// 设计文档「纯噪音退路」转正:面板输入任意提示词变体 A/B,各跑 N 次真实会话
|
|
353
|
+
// 注入,这里把 log 里 prompt_mode='bench' 的样本按 bench_run/bench_variant
|
|
354
|
+
// 分组,输出每类的 Wilson 95% CI 与 A−B 差异检验。神模只是第一个被测假设。
|
|
355
|
+
const BENCH_CLASSES = ['god', 'med', 'pure', 'unknown']
|
|
356
|
+
|
|
357
|
+
export function benchReportFrom(entries, runId) {
|
|
358
|
+
const bench = entries.filter((e) => e.prompt_mode === 'bench' && e.bench_run)
|
|
359
|
+
const runMap = new Map()
|
|
360
|
+
for (const e of bench) {
|
|
361
|
+
let r = runMap.get(e.bench_run)
|
|
362
|
+
if (!r) {
|
|
363
|
+
r = { runId: e.bench_run, n: 0, firstTs: e.ts, lastTs: e.ts, variants: new Set() }
|
|
364
|
+
runMap.set(e.bench_run, r)
|
|
365
|
+
}
|
|
366
|
+
r.n += 1
|
|
367
|
+
if (e.ts < r.firstTs) r.firstTs = e.ts
|
|
368
|
+
if (e.ts > r.lastTs) r.lastTs = e.ts
|
|
369
|
+
if (e.bench_variant) r.variants.add(e.bench_variant)
|
|
370
|
+
}
|
|
371
|
+
const runs = [...runMap.values()]
|
|
372
|
+
.map((r) => ({ runId: r.runId, n: r.n, firstTs: r.firstTs, lastTs: r.lastTs, variants: [...r.variants].sort() }))
|
|
373
|
+
.sort((a, b) => (a.lastTs < b.lastTs ? 1 : -1))
|
|
374
|
+
const picked = runId ? runs.find((r) => r.runId === runId) : runs[0]
|
|
375
|
+
if (!picked) return { report: null, runs }
|
|
376
|
+
const rows = bench.filter((e) => e.bench_run === picked.runId)
|
|
377
|
+
const byVariant = {}
|
|
378
|
+
for (const e of rows) {
|
|
379
|
+
const v = String(e.bench_variant || '?')
|
|
380
|
+
const g = (byVariant[v] ||= { variant: v, n: 0, counts: { god: 0, med: 0, pure: 0, unknown: 0 }, prompt: null })
|
|
381
|
+
g.n += 1
|
|
382
|
+
if (g.counts[e.detected] !== undefined) g.counts[e.detected] += 1
|
|
383
|
+
else g.counts.unknown += 1
|
|
384
|
+
if (!g.prompt && e.prompt_text) g.prompt = e.prompt_text
|
|
385
|
+
}
|
|
386
|
+
for (const g of Object.values(byVariant)) {
|
|
387
|
+
g.classes = {}
|
|
388
|
+
for (const c of BENCH_CLASSES) {
|
|
389
|
+
g.classes[c] = { n: g.counts[c], ...wilson(g.counts[c], g.n) }
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const order = Object.keys(byVariant).sort()
|
|
393
|
+
const comparison = []
|
|
394
|
+
if (order.length >= 2) {
|
|
395
|
+
const A = byVariant[order[0]]
|
|
396
|
+
const B = byVariant[order[1]]
|
|
397
|
+
for (const c of BENCH_CLASSES) {
|
|
398
|
+
const t = twoPropZ(A.counts[c], A.n, B.counts[c], B.n)
|
|
399
|
+
comparison.push({
|
|
400
|
+
cls: c,
|
|
401
|
+
a: A.classes[c],
|
|
402
|
+
b: B.classes[c],
|
|
403
|
+
delta: A.n && B.n ? A.counts[c] / A.n - B.counts[c] / B.n : null,
|
|
404
|
+
z: t ? t.z : null,
|
|
405
|
+
p: t ? t.p : null,
|
|
406
|
+
})
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return {
|
|
410
|
+
report: {
|
|
411
|
+
runId: picked.runId,
|
|
412
|
+
n: rows.length,
|
|
413
|
+
variants: byVariant,
|
|
414
|
+
order,
|
|
415
|
+
comparison,
|
|
416
|
+
startedAt: picked.firstTs,
|
|
417
|
+
finishedAt: picked.lastTs,
|
|
418
|
+
},
|
|
419
|
+
runs,
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
330
423
|
// ── version auto-detect (differentiator: no manual checking) ────────────────
|
|
331
424
|
function detectDshVersion() {
|
|
332
425
|
if (process.env.DSH_VERSION) return process.env.DSH_VERSION
|
|
@@ -386,6 +479,10 @@ export function apply(ctx) {
|
|
|
386
479
|
|
|
387
480
|
// Pending trigger marks: sessionId -> { mode, prompt_text, ts, auto }
|
|
388
481
|
const pending = new Map()
|
|
482
|
+
// v0.4 实验台:待配对的 bench 样本队列 [{ runId, variant, prompt, ts }]
|
|
483
|
+
// (文本精确匹配配对,见 /deus/bench/mark 路由注释)
|
|
484
|
+
const benchQueue = []
|
|
485
|
+
const BENCH_QUEUE_TTL = 10 * 60 * 1000
|
|
389
486
|
// Chunk accumulation while a trigger is pending: sessionId -> { turn, step, reasoning, text }
|
|
390
487
|
// (实测校准:指纹在 reasoning-delta 流里,text-delta 兜底;见 detectMode 注释)
|
|
391
488
|
const buffers = new Map()
|
|
@@ -466,6 +563,9 @@ export function apply(ctx) {
|
|
|
466
563
|
source,
|
|
467
564
|
first_sentence: firstSentenceOf(classifyOn),
|
|
468
565
|
model: mark.model || undefined,
|
|
566
|
+
// v0.4 实验台:A/B 样本归属(仅 bench 标记的注入轮带这两个字段)
|
|
567
|
+
bench_run: mark.benchRun || undefined,
|
|
568
|
+
bench_variant: mark.benchVariant || undefined,
|
|
469
569
|
})
|
|
470
570
|
}
|
|
471
571
|
|
|
@@ -486,6 +586,15 @@ export function apply(ctx) {
|
|
|
486
586
|
if (event.type === 'user/message') {
|
|
487
587
|
const text = textOf(event.data && event.data.content).trim()
|
|
488
588
|
if (text !== '' && !pending.has(sessionId)) {
|
|
589
|
+
// v0.4 实验台:bench 队列优先于 preset exact-match(显式实验意图优先)
|
|
590
|
+
const now = Date.now()
|
|
591
|
+
const bi = benchQueue.findIndex((q) => q.prompt === text && now - q.ts < BENCH_QUEUE_TTL)
|
|
592
|
+
if (bi >= 0) {
|
|
593
|
+
const q = benchQueue.splice(bi, 1)[0]
|
|
594
|
+
buffers.delete(sessionId)
|
|
595
|
+
pending.set(sessionId, { mode: 'bench', prompt_text: q.prompt, benchRun: q.runId, benchVariant: q.variant, ts: now, auto: false })
|
|
596
|
+
return
|
|
597
|
+
}
|
|
489
598
|
const hit = loadPresets().find((p) => p.prompt !== null && p.prompt.trim() === text)
|
|
490
599
|
if (hit) {
|
|
491
600
|
buffers.delete(sessionId) // 防止上轮残留 buffer 污染注入轮判定
|
|
@@ -666,6 +775,42 @@ export function apply(ctx) {
|
|
|
666
775
|
if (!sessionId) { json({ ok: false, error: 'missing sessionId' }, 400)(req, res); return }
|
|
667
776
|
json({ sessionId, preset: readSessionPreset(sessionId) })(req, res)
|
|
668
777
|
} }),
|
|
778
|
+
// v0.4 实验台:A/B 变体注入标记。与 /deus/trigger 的 sessionId 预标记不同,
|
|
779
|
+
// 这里走「提示词文本队列」配对(与档 A 的 exact-match 同思路):客户端每次
|
|
780
|
+
// 试验前把 (runId, variant, prompt) 入队,user/message 到达时按文本精确
|
|
781
|
+
// 匹配出队并标记该会话注入轮。这样 bench 不依赖「新会话 id 何时出现」
|
|
782
|
+
// 的时序——新建空白会话在首条消息前可能没有稳定 id。
|
|
783
|
+
ctx.webServer.register({ kind: 'exact', path: '/deus/bench/mark', handler: (req, res) => {
|
|
784
|
+
readBody(req, (body) => {
|
|
785
|
+
const runId = String(body.runId || '').slice(0, 64)
|
|
786
|
+
const variant = String(body.variant || '').slice(0, 16)
|
|
787
|
+
const prompt = String(body.prompt || '').slice(0, 4000)
|
|
788
|
+
if (!runId || !variant || !prompt) {
|
|
789
|
+
json({ ok: false, error: 'missing runId / variant / prompt' }, 400)(req, res)
|
|
790
|
+
return
|
|
791
|
+
}
|
|
792
|
+
benchQueue.push({ runId, variant, prompt, ts: Date.now() })
|
|
793
|
+
if (benchQueue.length > 200) benchQueue.splice(0, benchQueue.length - 200)
|
|
794
|
+
json({ ok: true, queued: benchQueue.length })(req, res)
|
|
795
|
+
})
|
|
796
|
+
} }),
|
|
797
|
+
// 中止/清理:丢弃某个 run(或全部)未消耗的队列项
|
|
798
|
+
ctx.webServer.register({ kind: 'exact', path: '/deus/bench/clear', handler: (req, res) => {
|
|
799
|
+
readBody(req, (body) => {
|
|
800
|
+
const runId = String(body.runId || '')
|
|
801
|
+
const before = benchQueue.length
|
|
802
|
+
for (let i = benchQueue.length - 1; i >= 0; i--) {
|
|
803
|
+
if (!runId || benchQueue[i].runId === runId) benchQueue.splice(i, 1)
|
|
804
|
+
}
|
|
805
|
+
json({ ok: true, dropped: before - benchQueue.length })(req, res)
|
|
806
|
+
})
|
|
807
|
+
} }),
|
|
808
|
+
// v0.4 实验台:A/B 报告(?runId= 指定,缺省取最近一次 run;附全部 run 列表)
|
|
809
|
+
ctx.webServer.register({ kind: 'exact', path: '/deus/bench/report', handler: (req, res) => {
|
|
810
|
+
const u = new URL(String(req.url || ''), 'http://localhost')
|
|
811
|
+
const runId = String(u.searchParams.get('runId') || '')
|
|
812
|
+
json({ ok: true, ...benchReportFrom(readLog(10000), runId || undefined) })(req, res)
|
|
813
|
+
} }),
|
|
669
814
|
ctx.webServer.register({ kind: 'exact', path: '/deus/version', handler: (req, res) => {
|
|
670
815
|
let dsh = 'unknown'
|
|
671
816
|
try { dsh = detectDshVersion() } catch (e) { ctx.logger.warn('[plugin-deus] version detect failed:', e) }
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-suite/plugin-deus",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "神模扳机 Deus Trigger —
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "神模扳机 Deus Trigger — 提示词 A/B 实验台:任意提示词变体各跑 N 次真实会话注入,指纹分布对比(Wilson CI + 两比例 z 检验);预设库 + 一键注入 + 起手识别器 + 触发率统计。社区观察未获官方证实,本插件不承诺解锁任何隐藏能力。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|