dsh-agent-board 1.7.4 → 1.7.5
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 +33 -13
- package/index.mjs +9 -1
- package/lib/client/dashboard.js +339 -52
- package/lib/client/kernel.js +9 -6
- package/lib/client/task-detail.js +14 -1
- package/lib/client.js +362 -59
- package/lib/core.mjs +102 -8
- package/lib/dispatch.mjs +38 -3
- package/lib/health.mjs +68 -1
- package/lib/rpc.mjs +54 -10
- package/lib/usage.mjs +532 -0
- package/package.json +2 -2
- package/test/core.test.mjs +352 -65
- package/test/headless-smoke.test.mjs +209 -0
- package/test/helpers/mock-ctx.mjs +327 -0
- package/test/integration.test.mjs +506 -0
- package/test/runtime-health.test.mjs +158 -0
- package/test/scoreboard-client.test.mjs +106 -0
- package/test/scoreboard-trends-client.test.mjs +104 -0
- package/test/scoreboard.test.mjs +271 -0
- package/test/scorecard-clusters.test.mjs +225 -0
- package/test/scorecard-data.test.mjs +214 -0
- package/test/usage-mainwindow.test.mjs +7 -5
- package/test/user-guide-client.test.mjs +79 -0
package/lib/client/kernel.js
CHANGED
|
@@ -219,7 +219,7 @@
|
|
|
219
219
|
|
|
220
220
|
var COLUMNS = ['draft', 'pending', 'in-progress', 'verifying', 'resolved', 'blocked']
|
|
221
221
|
var reqEpoch = 0 // 会话切换纪元:切会话时自增,旧会话在途响应按纪元丢弃
|
|
222
|
-
var state = { sessionId: null, tasks: [], boardMode: 'auto', teamMode: false, workMode: 'auto', minWorkers: 1, maxWorkers: 3, minVerifiers: 0, maxVerifiers: 2, workerModel: '', verifierModel: '', softTimeoutMin: 30, hardTimeoutMin: 120, feedbackEnabled: true, notifyDispatch: true, notifyDone: true, epicSplit: true, lessonPushed: {}, isRoot: true, isRootEverTrue: false, isRootFalseN: 0, isRootStable: true, open: false, detailId: null, children: [], dragOver: null, dragTask: null, dispatchInfo: '', view: 'board', layoutLeft: 280, layoutRight: 0, poolStatus: null, escalatedIds: [], filterQ: '', filterPrio: [], filterTag: '', selectMode: false, selected: {}, undoSnapshot: null, cardHover: '', archSort: 'time-desc', dateRange: { from: '', to: '' }, rfOpen: false, gboOpen: false, activity: {}, archived: [], archQ: '', globalBoards: [], createOpen: false, createFlash: '', usageSummary: null, childStats: {}, tasksErr: '', tasksHash: '' }
|
|
222
|
+
var state = { sessionId: null, tasks: [], boardMode: 'auto', teamMode: false, workMode: 'auto', minWorkers: 1, maxWorkers: 3, minVerifiers: 0, maxVerifiers: 2, workerModel: '', verifierModel: '', softTimeoutMin: 30, hardTimeoutMin: 120, feedbackEnabled: true, notifyDispatch: true, notifyDone: true, epicSplit: true, verifyUserGuide: true, lessonPushed: {}, isRoot: true, isRootEverTrue: false, isRootFalseN: 0, isRootStable: true, open: false, detailId: null, children: [], dragOver: null, dragTask: null, dispatchInfo: '', view: 'board', layoutLeft: 280, layoutRight: 0, poolStatus: null, escalatedIds: [], filterQ: '', filterPrio: [], filterTag: '', selectMode: false, selected: {}, undoSnapshot: null, cardHover: '', archSort: 'time-desc', dateRange: { from: '', to: '' }, rfOpen: false, gboOpen: false, activity: {}, archived: [], archQ: '', globalBoards: [], createOpen: false, createFlash: '', mpRole: '', mpSize: '', usageSummary: null, childStats: {}, tasksErr: '', tasksHash: '' }
|
|
223
223
|
|
|
224
224
|
// #16 快捷键:Esc 逐级关闭(详情→看板→面板);输入框聚焦时不劫持
|
|
225
225
|
try {
|
|
@@ -285,11 +285,11 @@
|
|
|
285
285
|
}
|
|
286
286
|
|
|
287
287
|
// ===== 设置开关权威纠偏(反馈:勾选几秒才同步,task-muw5uudk)=====
|
|
288
|
-
//
|
|
288
|
+
// 开关字段(学习反馈/派发回执/完成回执/史诗拆分/自测指南)在 get-tasks 里**无条件**照常赋值,但取值口径是
|
|
289
289
|
// 「缺字段/脏值=开,只有显式 false 才关」,与 host core.cfg 同口径——单点定义,避免纠偏把脏值当真值。
|
|
290
290
|
function cfgKnobOf(src, key) { return !(src && src[key] === false) }
|
|
291
291
|
// 变更检测:与 notify 同类的轻量浅比较(只看这几个布尔开关;settings 无嵌套对象)。
|
|
292
|
-
function cfgKnobsChanged(a, b) { return a.feedbackEnabled !== b.feedbackEnabled || a.notifyDispatch !== b.notifyDispatch || a.notifyDone !== b.notifyDone || a.epicSplit !== b.epicSplit }
|
|
292
|
+
function cfgKnobsChanged(a, b) { return a.feedbackEnabled !== b.feedbackEnabled || a.notifyDispatch !== b.notifyDispatch || a.notifyDone !== b.notifyDone || a.epicSplit !== b.epicSplit || a.verifyUserGuide !== b.verifyUserGuide }
|
|
293
293
|
|
|
294
294
|
function fetchTasks() {
|
|
295
295
|
if (!state.sessionId) return
|
|
@@ -320,7 +320,7 @@
|
|
|
320
320
|
// → 勾选框必须等下一次任意 notify 才翻面(安静板卡数秒)。这里在赋值前快照、赋值后比对,
|
|
321
321
|
// 有变化就补一次 notify:乐观更新(PoolCfgPopover)已让点击瞬时翻面,本兜底是服务端权威值纠偏
|
|
322
322
|
// (乐观值与服务端不一致时以服务端为准,失败回滚亦由此收敛)。
|
|
323
|
-
var cfgKnobs = { feedbackEnabled: state.feedbackEnabled, notifyDispatch: state.notifyDispatch, notifyDone: state.notifyDone, epicSplit: state.epicSplit }
|
|
323
|
+
var cfgKnobs = { feedbackEnabled: state.feedbackEnabled, notifyDispatch: state.notifyDispatch, notifyDone: state.notifyDone, epicSplit: state.epicSplit, verifyUserGuide: state.verifyUserGuide }
|
|
324
324
|
if (tasksChanged) {
|
|
325
325
|
state.tasksHash = newHash
|
|
326
326
|
state.tasks = (d && d.tasks) || []
|
|
@@ -364,6 +364,8 @@
|
|
|
364
364
|
state.notifyDone = cfgKnobOf(d, 'notifyDone')
|
|
365
365
|
// 史诗拆分总开关(设置区「功能」):同上——老 host 不返回 = 开(引导照旧),只有显式 false 才关
|
|
366
366
|
state.epicSplit = cfgKnobOf(d, 'epicSplit')
|
|
367
|
+
// 自测指南开关(设置区「验收」):同上——缺字段=开(详情块/报告清单段照常),只有显式 false 才关
|
|
368
|
+
state.verifyUserGuide = cfgKnobOf(d, 'verifyUserGuide')
|
|
367
369
|
// 开关有变 / Token 区聚合有变 → 补一次 notify(tasksChanged 分支已在上面 notify 过,
|
|
368
370
|
// 这里只管 hash 不变时被跳过的那两次:开关乐观更新纠偏 + 统计范围切换后的新聚合)
|
|
369
371
|
var cfgDelta = !tasksChanged && cfgKnobsChanged(cfgKnobs, state)
|
|
@@ -558,7 +560,8 @@
|
|
|
558
560
|
var _fbo = useState(state.feedbackEnabled), fbEnabled = _fbo[0], setFbEnabled = _fbo[1]
|
|
559
561
|
var _ndo = useState(state.notifyDispatch), ndOn = _ndo[0], setNdOn = _ndo[1]; var _nno = useState(state.notifyDone), nnOn = _nno[0], setNnOn = _nno[1]
|
|
560
562
|
var _eso = useState(state.epicSplit), esOn = _eso[0], setEsOn = _eso[1] // 史诗拆分总开关勾选态(设置区「功能」)
|
|
561
|
-
|
|
563
|
+
var _vug = useState(state.verifyUserGuide), vugOn = _vug[0], setVugOn = _vug[1] // 自测指南开关勾选态(设置区「验收」)
|
|
564
|
+
useEffect(function () { function update() { setOpen(state.open); setTasksState(state.tasks); setModeState(state.boardMode); setDetailId(state.detailId); setDragOver(state.dragOver); setDispatchInfo(state.dispatchInfo); setViewState(state.view); setLayL(state.layoutLeft); setLayR(state.layoutRight); setMinW(state.minWorkers); setMaxW(state.maxWorkers); setMinV(state.minVerifiers); setMaxV(state.maxVerifiers); setWorkerModel(state.workerModel); setVerifierModel(state.verifierModel); setTeamMode(state.teamMode); setWorkModeState(state.workMode); setDR(state.dateRange); setSoftT(state.softTimeoutMin); setHardT(state.hardTimeoutMin); setFbEnabled(state.feedbackEnabled); setNdOn(state.notifyDispatch); setNnOn(state.notifyDone); setEsOn(state.epicSplit); setVugOn(state.verifyUserGuide); setCreateOpen(state.createOpen); setCreateFlash(state.createFlash); setTasksErr(state.tasksErr) }; listeners.push(update); update(); return function () { var i = listeners.indexOf(update); if (i >= 0) listeners.splice(i, 1) } }, [])
|
|
562
565
|
if (!open) return null
|
|
563
566
|
if (!state.isRootStable) return null // 子代理会话不渲染看板面板(读 isRootStable:瞬态 false 不闪,见 isRoot 蝶变防抖)
|
|
564
567
|
var active = tasks.filter(function (t) { return t.status !== 'archived' }); var archived = tasks.filter(function (t) { return t.status === 'archived' })
|
|
@@ -584,7 +587,7 @@
|
|
|
584
587
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '6px 12px', borderBottom: '1px solid ' + C.border, flexShrink: 0, flexWrap: 'wrap', gap: 4 } },
|
|
585
588
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6 } }, React.createElement('span', { style: { fontWeight: 600, fontSize: 13, color: C.text, display: 'inline-flex', alignItems: 'center', gap: 5 } }, ic('clipboard-list', 15), '智能看板'), React.createElement(ViewTab, null), React.createElement(PoolStatus, null)),
|
|
586
589
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 4, flexWrap: 'wrap' } },
|
|
587
|
-
React.createElement(PoolCfgPopover, { minW: minW, maxW: maxW, minV: minV, maxV: maxV, workerModel: workerModel, verifierModel: verifierModel, softT: softT, hardT: hardT, feedbackEnabled: fbEnabled, notifyDispatch: ndOn, notifyDone: nnOn, epicSplit: esOn }),
|
|
590
|
+
React.createElement(PoolCfgPopover, { minW: minW, maxW: maxW, minV: minV, maxV: maxV, workerModel: workerModel, verifierModel: verifierModel, softT: softT, hardT: hardT, feedbackEnabled: fbEnabled, notifyDispatch: ndOn, notifyDone: nnOn, epicSplit: esOn, verifyUserGuide: vugOn }),
|
|
588
591
|
dispatchInfo ? React.createElement('span', { style: { fontSize: 9, color: C.brand, maxWidth: 120, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, title: dispatchInfo }, dispatchInfo) : null,
|
|
589
592
|
React.createElement('button', { onClick: function () { state.createOpen = true; notify() }, title: '新建任务(可存为草稿)', style: { fontSize: 11, padding: '3px 8px', border: '1px solid ' + C.border, borderRadius: 6, cursor: 'pointer', background: 'transparent', color: C.text2, display: 'inline-flex', alignItems: 'center', gap: 3 } }, ic('plus', 11), '新建任务'),
|
|
590
593
|
createFlash ? React.createElement('span', { style: { fontSize: 10, color: C.ok } }, createFlash) : null,
|
|
@@ -352,7 +352,20 @@
|
|
|
352
352
|
task.verification ? React.createElement('div', { style: { marginBottom: 8, padding: '6px 8px', border: '1px solid ' + (task.verification.verdict === 'approved' ? C.ok : C.err), borderRadius: 6, background: C.card } },
|
|
353
353
|
React.createElement('div', { style: { fontSize: 11, fontWeight: 700, color: task.verification.verdict === 'approved' ? C.ok : C.err, marginBottom: 4, display: 'flex', alignItems: 'center', gap: 4 } }, ic(task.verification.verdict === 'approved' ? 'clipboard-check' : 'clipboard-x', 12), (task.verification.verdict === 'approved' ? '验收通过' : '验收驳回') + ' · ' + (task.verification.by || '') + ' · ' + ago(task.verification.at)),
|
|
354
354
|
React.createElement('div', { style: { fontSize: 11, color: C.text, marginBottom: 4, whiteSpace: 'pre-wrap' } }, task.verification.summary || '(无测试概要)'),
|
|
355
|
-
task.verification.checks ? React.createElement('div', { style: { marginTop: 4 } }, React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2 } }, '核对项'), React.createElement('div', { style: { fontSize: 10, color: C.text2, whiteSpace: 'pre-wrap', maxHeight: 120, overflowY: 'auto' } }, task.verification.checks)) : null
|
|
355
|
+
task.verification.checks ? React.createElement('div', { style: { marginTop: 4 } }, React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2 } }, '核对项'), React.createElement('div', { style: { fontSize: 10, color: C.text2, whiteSpace: 'pre-wrap', maxHeight: 120, overflowY: 'auto' } }, task.verification.checks)) : null,
|
|
356
|
+
// ===== 自测指南块(task-muxyyvg0):双门禁——板级开关 verifyUserGuide 关 / 未挂 userTest 字段,
|
|
357
|
+
// 任一整块不渲染。tier 徽章三档配色:ui 绿(界面可操作)/ metric 蓝(看指标变化)/ internal 灰(纯内部)。
|
|
358
|
+
task.verification.userTest && state.verifyUserGuide !== false ? (function () {
|
|
359
|
+
var ut = task.verification.userTest
|
|
360
|
+
var tierMeta = ut.tier === 'ui' ? { c: C.ok, label: 'UI 可操作' } : (ut.tier === 'metric' ? { c: C.brand, label: '看指标' } : { c: C.text2, label: '纯内部' })
|
|
361
|
+
return React.createElement('div', { style: { marginTop: 6, padding: '5px 8px', background: C.nested, borderRadius: 4 } },
|
|
362
|
+
React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2, marginBottom: 3, display: 'flex', alignItems: 'center', gap: 5 } },
|
|
363
|
+
'📋 自测指南',
|
|
364
|
+
React.createElement('span', { style: { fontSize: 9, padding: '0 5px', borderRadius: 3, background: tierMeta.c, color: C_INV, fontWeight: 700 }, title: 'tier=' + (ut.tier || 'internal') + '(ui=界面可操作 / metric=看指标变化 / internal=纯内部无用户可感知面)' }, tierMeta.label)),
|
|
365
|
+
ut.gist ? React.createElement('div', { style: { fontSize: 11, color: C.text, marginBottom: 3 } }, ut.gist) : null,
|
|
366
|
+
ut.steps && ut.steps.length ? React.createElement('div', { style: { fontSize: 10, color: C.text, marginBottom: 3 } }, ut.steps.map(function (s, i) { return React.createElement('div', { key: i, style: { marginBottom: 1 } }, (i + 1) + '. ' + s) })) : null,
|
|
367
|
+
ut.expect ? React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '预期:' + ut.expect) : null)
|
|
368
|
+
})() : null) : null,
|
|
356
369
|
React.createElement(MsgThread, { messages: task.messages, taskId: task.id }),
|
|
357
370
|
Array.isArray(task.history) && task.history.length > 0 ? React.createElement('div', { style: { marginBottom: 8 } }, React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 3 } }, '流转轨迹'), React.createElement('div', { style: { fontSize: 10, color: C.text2, padding: '4px 6px', background: C.nested, borderRadius: 4 } }, task.history.map(function (h, i) { return React.createElement('div', { key: i, style: { marginBottom: 2 } }, React.createElement('span', { style: { color: C.brand } }, statusLabels[h.to] || h.to), ' · ' + ago(h.timestamp) + ' · ', React.createElement(ActorLink, { id: h.actor }), h.note ? ' · ' + h.note : '') }))) : null,
|
|
358
371
|
React.createElement('div', { style: { display: 'flex', gap: 4, flexWrap: 'wrap', marginTop: 6 } },
|