dsh-agent-board 1.7.5 → 1.7.6
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 +7 -2
- package/index.mjs +1 -1
- package/lib/client/board-list.js +37 -26
- package/lib/client/dashboard.js +75 -22
- package/lib/client/kernel.js +14 -4
- package/lib/client/task-detail.js +7 -1
- package/lib/client.js +133 -53
- package/lib/core.mjs +100 -0
- package/lib/dispatch.mjs +16 -4
- package/lib/rpc.mjs +35 -9
- package/lib/usage.mjs +77 -1
- package/package.json +1 -1
- package/test/core.test.mjs +297 -6
- package/test/integration.test.mjs +37 -0
- package/test/quality-alert-digest.test.mjs +128 -0
- package/test/scorecard-clusters.test.mjs +2 -2
package/lib/client.js
CHANGED
|
@@ -231,6 +231,16 @@ function apply(ctx) {
|
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
var COLUMNS = ['draft', 'pending', 'in-progress', 'verifying', 'resolved', 'blocked']
|
|
234
|
+
// 六列列头 title + 空列引导(巡检 n-muyg4fl8tm6r 六列无说明 / n-muygwnxpwcsq 空列零引导):
|
|
235
|
+
// 黑话→人话(title 悬浮补一句),空列给下一步动作指引(灰字)。
|
|
236
|
+
var COLUMN_META = {
|
|
237
|
+
draft: { title: '草稿:已保存未发布(补完依赖/上下文后统一发布)', empty: '暂无草稿——「+ 新建任务」勾选存为草稿后会出现在这里' },
|
|
238
|
+
pending: { title: '待办:已发布待派发(等 Worker 领取,或等依赖/文件锁释放)', empty: '暂无待办——已发布待派发的任务会出现在这里' },
|
|
239
|
+
'in-progress': { title: '进行中:Worker 子代理正在执行', empty: '暂无执行中——Worker 领取任务后会出现在这里' },
|
|
240
|
+
verifying: { title: '验证中:Worker 已完成,独立 Verifier 复跑验收中', empty: '暂无卡片——Worker 提交验收后会出现在这里' },
|
|
241
|
+
resolved: { title: '已完成:Verifier 验收通过', empty: '暂无已完成——验收通过后会出现在这里' },
|
|
242
|
+
blocked: { title: '阻塞:卡住待处理(等依赖/等主窗口裁决)', empty: '暂无阻塞——任务卡住待裁决时会出现在这里' }
|
|
243
|
+
}
|
|
234
244
|
var reqEpoch = 0 // 会话切换纪元:切会话时自增,旧会话在途响应按纪元丢弃
|
|
235
245
|
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: '' }
|
|
236
246
|
|
|
@@ -519,7 +529,7 @@ function apply(ctx) {
|
|
|
519
529
|
// #18 依赖未满足判断(依赖不存在视为阻塞——创建时已校验,防御性兜底)
|
|
520
530
|
function depsBlocked(t) { if (!Array.isArray(t.dependsOn) || t.dependsOn.length === 0) return false; return t.dependsOn.some(function (id) { var d = getTask(id); return !d || (d.status !== 'resolved' && d.status !== 'archived') }) }
|
|
521
531
|
// #19 管线档位元数据
|
|
522
|
-
var pipeMeta = { full: { icon: 'flask-conical', label: '
|
|
532
|
+
var pipeMeta = { full: { icon: 'flask-conical', label: '全流程:Worker 执行 + Verifier 独立验收', short: '全流程' }, work: { icon: 'file-text', label: '免验证:Worker 只做,不验收', short: '免验' }, direct: { icon: 'message-square', label: '主窗口直办:不派 Worker,由当前对话直接处理', short: '直办' } }
|
|
523
533
|
// ===== 耗时口径三分离(task-mutdnitw):排队 ≠ 执行 ≠ 验收,不再把排队算进耗时 =====
|
|
524
534
|
// waitOf:⏳ 排队时长 = createdAt → claimedAt(未领取则统计到 now)——"等了多久"
|
|
525
535
|
// execOf:⏱ 执行时长 = claimedAt → resolvedAt/archivedAt(未定则统计到 now)——"干了多久"
|
|
@@ -559,7 +569,7 @@ function apply(ctx) {
|
|
|
559
569
|
return React.createElement('button', { onClick: function () { state.open = !state.open; notify() }, title: '智能看板' + (pendingCount > 0 ? '(' + pendingCount + ' 待办)' : '') + (escCount > 0 ? '(' + escCount + ' 待裁决)' : ''), style: { display: 'inline-flex', alignItems: 'center', gap: 4, padding: '3px 10px', border: '1px solid ' + (escCount > 0 ? C.err : C.border), borderRadius: 6, background: isOpen ? C.nested : 'transparent', color: C.text, cursor: 'pointer', fontSize: 12 } }, React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center' } }, ic('clipboard-list', 14)), React.createElement('span', null, '智能看板'), escCount > 0 ? React.createElement('span', { style: { minWidth: 16, height: 16, padding: '0 4px', borderRadius: 8, background: C.err, color: C_INV, fontSize: 10, fontWeight: 700, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 1, animation: 'tskb-pulse 1s ease-in-out infinite' }, title: escCount + ' 个任务待裁决' }, ic('alert-triangle', 10), escCount) : null, pendingCount > 0 ? React.createElement('span', { style: { minWidth: 16, height: 16, padding: '0 4px', borderRadius: 8, background: C.brand, color: C_INV, fontSize: 10, fontWeight: 600, display: 'inline-flex', alignItems: 'center', justifyContent: 'center' } }, String(pendingCount)) : null)
|
|
560
570
|
}
|
|
561
571
|
|
|
562
|
-
function ViewTab() { var btnBase = { fontSize: 11, padding: '3px 10px', border: 'none', cursor: 'pointer', fontWeight: 500, borderRadius: 5, transition: 'all .15s' }; function goArch() { state.view = 'archive'; notify(); fetchArchived() } return React.createElement('div', { style: { display: 'inline-flex', borderRadius: 6, border: '1px solid ' + C.border, overflow: 'hidden', background: C.card } }, React.createElement('button', { onClick: function () { state.view = 'board'; notify() }, style: Object.assign({}, btnBase, state.view === 'board' ? { background: C.brand, color: C_INV } : { background: 'transparent', color: C.text2 }) }, React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 4 } }, ic('clipboard-list', 12), '看板')), React.createElement('button', { onClick: function () { state.view = 'team'; notify() }, style: Object.assign({}, btnBase, state.view === 'team' ? { background: C.brand, color: C_INV } : { background: 'transparent', color: C.text2 }) }, icText('users', 12, '
|
|
572
|
+
function ViewTab() { var btnBase = { fontSize: 11, padding: '3px 10px', border: 'none', cursor: 'pointer', fontWeight: 500, borderRadius: 5, transition: 'all .15s' }; function goArch() { state.view = 'archive'; notify(); fetchArchived() } return React.createElement('div', { style: { display: 'inline-flex', borderRadius: 6, border: '1px solid ' + C.border, overflow: 'hidden', background: C.card } }, React.createElement('button', { onClick: function () { state.view = 'board'; notify() }, style: Object.assign({}, btnBase, state.view === 'board' ? { background: C.brand, color: C_INV } : { background: 'transparent', color: C.text2 }) }, React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 4 } }, ic('clipboard-list', 12), '看板')), React.createElement('button', { onClick: function () { state.view = 'team'; notify() }, title: 'Worker / Verifier 执行池(一次性子代理,随任务结算销毁)', style: Object.assign({}, btnBase, state.view === 'team' ? { background: C.brand, color: C_INV } : { background: 'transparent', color: C.text2 }) }, icText('users', 12, '执行池')), React.createElement('button', { onClick: function () { state.view = 'dashboard'; notify() }, style: Object.assign({}, btnBase, state.view === 'dashboard' ? { background: C.brand, color: C_INV } : { background: 'transparent', color: C.text2 }) }, icText('bar-chart-3', 12, '仪表盘')), React.createElement('button', { onClick: goArch, title: '已归档任务(可检索、可恢复)', style: Object.assign({}, btnBase, state.view === 'archive' ? { background: C.brand, color: C_INV } : { background: 'transparent', color: C.text2 }) }, icText('archive', 12, '归档'))) }
|
|
563
573
|
|
|
564
574
|
function TopPanel() {
|
|
565
575
|
var _R = React; var useState = _R.useState, useEffect = _R.useEffect
|
|
@@ -587,14 +597,14 @@ function apply(ctx) {
|
|
|
587
597
|
else if (detailId) { content = React.createElement(DetailView) }
|
|
588
598
|
else {
|
|
589
599
|
var prioRank = { critical: 4, high: 3, medium: 2, low: 1 }
|
|
590
|
-
var cols = COLUMNS.map(function (st) { var colTasks = active.filter(function (t) { return t.status === st }); colTasks.sort(function (a, b) { var e = (b.escalation ? 1 : 0) - (a.escalation ? 1 : 0); if (e) return e; var sk = (b.stuckSince ? 1 : 0) - (a.stuckSince ? 1 : 0); if (sk) return sk; var db = (depsBlocked(a) ? 1 : 0) - (depsBlocked(b) ? 1 : 0); if (db) return db; var p = (prioRank[b.priority] || 2) - (prioRank[a.priority] || 2); if (p) return p; return (a.createdAt || '').localeCompare(b.createdAt || '') }); var isOver = dragOver === st; return React.createElement('div', { key: st, onDragOver: function (e) { onColDragOver(e, st) }, onDragLeave: function () { onColDragLeave(st) }, onDrop: function (e) { onColDrop(e, st) }, style: { flex: '1 1 0', minWidth: 150, borderRadius: 8, padding: 6, background: isOver ? C.nested : 'transparent', border: isOver ? '1px dashed ' + C.brand : '1px dashed transparent', transition: 'background .15s, border .15s' } }, React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 4, padding: '0 2px' } }, (statusLabels[st] || st) + ' (' + colTasks.length + ')'), colTasks.map(function (t) { return React.createElement(Card, { key: t.id, task: t }) })) })
|
|
600
|
+
var cols = COLUMNS.map(function (st) { var colTasks = active.filter(function (t) { return t.status === st }); colTasks.sort(function (a, b) { var e = (b.escalation ? 1 : 0) - (a.escalation ? 1 : 0); if (e) return e; var sk = (b.stuckSince ? 1 : 0) - (a.stuckSince ? 1 : 0); if (sk) return sk; var db = (depsBlocked(a) ? 1 : 0) - (depsBlocked(b) ? 1 : 0); if (db) return db; var p = (prioRank[b.priority] || 2) - (prioRank[a.priority] || 2); if (p) return p; return (a.createdAt || '').localeCompare(b.createdAt || '') }); var isOver = dragOver === st; return React.createElement('div', { key: st, onDragOver: function (e) { onColDragOver(e, st) }, onDragLeave: function () { onColDragLeave(st) }, onDrop: function (e) { onColDrop(e, st) }, style: { flex: '1 1 0', minWidth: 150, borderRadius: 8, padding: 6, background: isOver ? C.nested : 'transparent', border: isOver ? '1px dashed ' + C.brand : '1px dashed transparent', transition: 'background .15s, border .15s' } }, React.createElement('div', { title: (COLUMN_META[st] || {}).title, style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 4, padding: '0 2px', cursor: 'help' } }, (statusLabels[st] || st) + ' (' + colTasks.length + ')'), colTasks.length === 0 ? React.createElement('div', { style: { fontSize: 10, color: C.text2, opacity: 0.7, padding: '2px 6px 8px', textAlign: 'center' } }, (COLUMN_META[st] || {}).empty) : colTasks.map(function (t) { return React.createElement(Card, { key: t.id, task: t }) })) })
|
|
591
601
|
// 归档区:排序选择器(默认归档时间降序)+ 纵向滚动列表(不再横向拉条)
|
|
592
602
|
var archSorted = archived.slice().sort(function (a, b) {
|
|
593
603
|
if (state.archSort === 'title') return (a.title || '').localeCompare(b.title || '')
|
|
594
604
|
var ta = a.archivedAt || a.resolvedAt || a.createdAt || '', tb = b.archivedAt || b.resolvedAt || b.createdAt || ''
|
|
595
605
|
return state.archSort === 'time-asc' ? ta.localeCompare(tb) : tb.localeCompare(ta)
|
|
596
606
|
})
|
|
597
|
-
content = React.createElement('div', null, React.createElement(FilterBar, null), hasFilter && active.length === 0 && archived.length === 0 ? React.createElement('div', { style: { textAlign: 'center', padding: 16, color: C.text2, fontSize: 11 } }, '无匹配任务') : null, React.createElement('div', { style: { fontSize: 10, color: C.text2, marginBottom: 6 } }, state.selectMode ? '多选模式:点击卡片勾选,底部批量操作' : '提示:拖拽卡片到目标列即可流转状态'), tasks.length === 0 ? React.createElement('div', { style: { textAlign: 'center', padding: '16px 8px 12px', color: C.text2, fontSize: 12 } }, '点击右上角', React.createElement('span', { style: { color: C.brand, fontWeight: 600 } }, '「+ 新建任务」'), '
|
|
607
|
+
content = React.createElement('div', null, React.createElement(FilterBar, null), hasFilter && active.length === 0 && archived.length === 0 ? React.createElement('div', { style: { textAlign: 'center', padding: 16, color: C.text2, fontSize: 11 } }, '无匹配任务') : null, React.createElement('div', { style: { fontSize: 10, color: C.text2, marginBottom: 6 } }, state.selectMode ? '多选模式:点击卡片勾选,底部批量操作' : '提示:拖拽卡片到目标列即可流转状态'), tasks.length === 0 ? React.createElement('div', { style: { textAlign: 'center', padding: '16px 8px 12px', color: C.text2, fontSize: 12 } }, '点击右上角', React.createElement('span', { style: { color: C.brand, fontWeight: 600 } }, '「+ 新建任务」'), '创建第一张卡,或直接在对话里说:帮我建一张任务卡') : null, React.createElement('div', { style: { display: 'flex', gap: 8, overflowX: 'auto', paddingBottom: 4 } }, cols), React.createElement(BatchBar, null))
|
|
598
608
|
}
|
|
599
609
|
return React.createElement('div', { style: { position: 'fixed', top: 44, left: (layL + 8) + 'px', right: (layR + 8) + 'px', maxHeight: '60vh', zIndex: 900, background: C.bg, border: '1px solid ' + C.border, borderRadius: '0 0 10px 10px', boxShadow: '0 8px 24px rgba(0,0,0,0.12)', display: 'flex', flexDirection: 'column', overflow: 'hidden' } },
|
|
600
610
|
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 } },
|
|
@@ -626,13 +636,14 @@ function apply(ctx) {
|
|
|
626
636
|
// #14 批量操作条(#16 带一步撤销:快照操作前的 priority/status;delete 是真删,明确不支持撤销)
|
|
627
637
|
function BatchBar() {
|
|
628
638
|
var _R = React; var useState = _R.useState, useEffect = _R.useEffect
|
|
629
|
-
var _a = useState(0), cnt = _a[0], setCnt = _a[1]; var _b = useState(false), on = _b[0], setOn = _b[1]; var _c = useState(''), msg = _c[0], setMsg = _c[1]; var _d = useState(null), undo = _d[0], setUndo = _d[1]
|
|
639
|
+
var _a = useState(0), cnt = _a[0], setCnt = _a[1]; var _b = useState(false), on = _b[0], setOn = _b[1]; var _c = useState(''), msg = _c[0], setMsg = _c[1]; var _d = useState(null), undo = _d[0], setUndo = _d[1]; var _e = useState([]), reasons = _e[0], setReasons = _e[1]
|
|
630
640
|
useEffect(function () { function update() { setCnt(Object.keys(state.selected).length); setOn(state.selectMode); setUndo(state.undoSnapshot) }; listeners.push(update); update(); return function () { var i = listeners.indexOf(update); if (i >= 0) listeners.splice(i, 1) } }, [])
|
|
631
641
|
if (!on) return null
|
|
632
642
|
var ids = Object.keys(state.selected)
|
|
633
643
|
function snapshot(op) { return { op: op, at: Date.now(), items: ids.map(function (id) { var t = getTask(id); return t ? { id: id, priority: t.priority, status: t.status } : null }).filter(Boolean) } }
|
|
634
|
-
//
|
|
635
|
-
|
|
644
|
+
// 跳过原因逐条渲染:host batch-op 返回 reasons 对象(id → 原因),转成「标题:原因」列表
|
|
645
|
+
// (巡检 n-muyj5p755l3c:批量归档跳过此前只显示「跳过 N」无原因)
|
|
646
|
+
function reasonRows(r) { var m = (r && r.reasons) || {}; return Object.keys(m).map(function (id) { var t = getTask(id); return { title: t ? t.title : shortId(id), reason: String(m[id]) } }) }
|
|
636
647
|
function run(op, value) {
|
|
637
648
|
// 只有可撤销的 op 才需要快照(delete 是真删,快照里的 status/priority 复活不回整张卡)
|
|
638
649
|
var snap = op === 'delete' ? null : snapshot(op)
|
|
@@ -641,8 +652,8 @@ function apply(ctx) {
|
|
|
641
652
|
state.selected = {}
|
|
642
653
|
// 删除无 undo:done>0 也不留撤销快照(否则「↩️ 撤销」点了恢复不了,纯粹误导);其他 op 维持一步撤销
|
|
643
654
|
state.undoSnapshot = (r && r.done > 0 && op !== 'delete') ? snap : null
|
|
644
|
-
|
|
645
|
-
setMsg('✅ 已处理 ' + (r && r.done || 0) + ' 个' + (r && r.skipped && r.skipped.length ? ',跳过 ' + r.skipped.length +
|
|
655
|
+
setReasons(reasonRows(r))
|
|
656
|
+
setMsg('✅ 已处理 ' + (r && r.done || 0) + ' 个' + (r && r.skipped && r.skipped.length ? ',跳过 ' + r.skipped.length + ' 个' : ''))
|
|
646
657
|
fetchTasks()
|
|
647
658
|
}).catch(function (e) { setMsg('⚠️ ' + String(e)) })
|
|
648
659
|
}
|
|
@@ -654,7 +665,7 @@ function apply(ctx) {
|
|
|
654
665
|
}
|
|
655
666
|
function doUndo() { if (!undo) return; rpc('batch-undo', { snapshot: undo }).then(function (r) { setMsg('↩️ 已撤销 ' + (r && r.done || 0) + ' 个'); state.undoSnapshot = null; fetchTasks() }).catch(function (e) { setMsg('⚠️ ' + String(e)) }) }
|
|
656
667
|
var btn = { fontSize: 10, padding: '3px 10px', borderRadius: 4, border: 'none', cursor: 'pointer', fontWeight: 600 }
|
|
657
|
-
return React.createElement('div', { style: { position: 'sticky', bottom: 0, display: 'flex', alignItems: 'center', gap: 6, padding: '6px 8px', marginTop: 8, background: C.nested, border: '1px solid ' + C.border, borderRadius: 6 } },
|
|
668
|
+
return React.createElement('div', { style: { position: 'sticky', bottom: 0, display: 'flex', alignItems: 'center', gap: 6, padding: '6px 8px', marginTop: 8, background: C.nested, border: '1px solid ' + C.border, borderRadius: 6, flexWrap: 'wrap' } },
|
|
658
669
|
React.createElement('span', { style: { fontSize: 11, color: C.text, fontWeight: 600 } }, '已选 ' + cnt + ' 项'),
|
|
659
670
|
React.createElement('button', { onClick: function () { run('archive') }, disabled: cnt === 0, style: Object.assign({}, btn, { background: C.text2, color: C_INV, display: 'inline-flex', alignItems: 'center', gap: 3 }) }, ic('archive', 11), '批量归档'),
|
|
660
671
|
['critical', 'high', 'medium', 'low'].map(function (p) { return React.createElement('button', { key: p, onClick: function () { run('set-priority', p) }, disabled: cnt === 0, style: Object.assign({}, btn, { background: prioColor[p], color: C_INV }) }, prioLabel[p]) }),
|
|
@@ -662,21 +673,26 @@ function apply(ctx) {
|
|
|
662
673
|
React.createElement('button', { onClick: batchDelete, disabled: cnt === 0, title: '删除选中任务(不可恢复;执行中/验证中的需先终止,已落定请用归档)', style: Object.assign({}, btn, { background: C.err, color: C_INV, display: 'inline-flex', alignItems: 'center', gap: 3 }) }, ic('trash-2', 11), '批量删除'),
|
|
663
674
|
undo ? React.createElement('button', { onClick: doUndo, title: '撤销最近一次批量操作(删除不可撤销)', style: Object.assign({}, btn, { background: C.brand, color: C_INV }) }, '↩️ 撤销') : null,
|
|
664
675
|
msg ? React.createElement('span', { style: { fontSize: 10, color: C.text2 } }, msg) : null,
|
|
665
|
-
React.createElement('button', { onClick: function () { state.selected = {}; notify() }, style: { marginLeft: 'auto', fontSize: 10, padding: '3px 8px', border: 'none', background: 'transparent', color: C.text2, cursor: 'pointer' } }, '清除选择')
|
|
676
|
+
React.createElement('button', { onClick: function () { state.selected = {}; notify() }, style: { marginLeft: 'auto', fontSize: 10, padding: '3px 8px', border: 'none', background: 'transparent', color: C.text2, cursor: 'pointer' } }, '清除选择'),
|
|
677
|
+
reasons.length > 0 ? React.createElement('div', { style: { flexBasis: '100%', marginTop: 4, padding: '4px 6px', background: C.card, border: '1px solid ' + C.border, borderRadius: 4, maxHeight: 96, overflowY: 'auto' } },
|
|
678
|
+
React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2, marginBottom: 2 } }, '跳过原因'),
|
|
679
|
+
reasons.map(function (it, i) { return React.createElement('div', { key: i, style: { fontSize: 10, color: C.text2, marginBottom: 1, wordBreak: 'break-word' } }, it.title + ':' + it.reason) })) : null)
|
|
666
680
|
}
|
|
667
681
|
|
|
668
682
|
function FilterBar() {
|
|
669
683
|
var _R = React; var useState = _R.useState, useEffect = _R.useEffect
|
|
670
|
-
|
|
671
|
-
|
|
684
|
+
// 优先级筛选单一事实源:chip 高亮与「清除」显隐统一读全局 state.filterPrio(消灭本地 fp 副本),
|
|
685
|
+
// 曾因本地态与全局 filterPrio 不同帧更新出现「清除按钮消失但高亮残留」的矛盾画面(巡检 n-muyimk1hpdxv)
|
|
686
|
+
var _a = useState(state.filterQ), q = _a[0], setQ = _a[1]; var _c = useState(state.filterTag), ft = _c[0], setFt = _c[1]
|
|
687
|
+
useEffect(function () { function update() { setQ(state.filterQ); setFt(state.filterTag) }; listeners.push(update); update(); return function () { var i = listeners.indexOf(update); if (i >= 0) listeners.splice(i, 1) } }, [])
|
|
672
688
|
function setQ2(v) { state.filterQ = v; notify() }
|
|
673
689
|
function togglePrio(p) { var i = state.filterPrio.indexOf(p); if (i >= 0) state.filterPrio.splice(i, 1); else state.filterPrio.push(p); notify() }
|
|
674
690
|
function clearAll() { state.filterQ = ''; state.filterPrio = []; state.filterTag = ''; notify() }
|
|
675
|
-
var hasFilter = q.trim() ||
|
|
691
|
+
var hasFilter = q.trim() || state.filterPrio.length > 0 || ft
|
|
676
692
|
var chipBase = { fontSize: 10, padding: '2px 8px', borderRadius: 10, border: '1px solid ' + C.border, cursor: 'pointer', background: 'transparent', color: C.text2, transition: 'all .15s' }
|
|
677
693
|
return React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 8, flexWrap: 'wrap' } },
|
|
678
694
|
React.createElement('input', { value: q, onChange: function (e) { setQ2(e.target.value) }, placeholder: '🔍 搜索标题/描述/ID…', style: { flex: '0 1 200px', fontSize: 11, padding: '3px 8px', border: '1px solid ' + C.border2, borderRadius: 4, background: C.card, color: C.text } }),
|
|
679
|
-
['critical', 'high', 'medium', 'low'].map(function (p) { var on =
|
|
695
|
+
['critical', 'high', 'medium', 'low'].map(function (p) { var on = state.filterPrio.indexOf(p) >= 0; return React.createElement('button', { key: p, onClick: function () { togglePrio(p) }, style: Object.assign({}, chipBase, on ? { background: prioColor[p], color: C_INV, borderColor: prioColor[p] } : {}) }, prioLabel[p]) }),
|
|
680
696
|
allTags().length > 0 ? React.createElement('select', { value: ft, onChange: function (e) { state.filterTag = e.target.value; notify() }, style: { fontSize: 10, padding: '2px 4px', border: '1px solid ' + C.border, borderRadius: 4, background: C.card, color: C.text } }, React.createElement('option', { value: '' }, '🏷 全部标签'), allTags().map(function (g) { return React.createElement('option', { key: g, value: g }, g) })) : null,
|
|
681
697
|
hasFilter ? React.createElement('button', { onClick: clearAll, style: { fontSize: 10, padding: '2px 8px', borderRadius: 10, border: 'none', background: C.nested, color: C.text2, cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 2 } }, ic('x', 9), '清除') : null)
|
|
682
698
|
}
|
|
@@ -781,8 +797,8 @@ function apply(ctx) {
|
|
|
781
797
|
(Array.isArray(t.waitingForTouches) && t.waitingForTouches.length) ? React.createElement('span', { title: '等文件锁释放:' + t.waitingForTouches.join('、') + '(touches 冲突,详情页可 force 越权派发)', style: { color: C.warn, fontWeight: 600 } }, '🔒 等文件释放') : null,
|
|
782
798
|
React.createElement('span', { title: pm.label, style: { fontSize: 9, padding: '0 4px', borderRadius: 2, background: C.nested, border: '1px solid ' + C.border } }, pm.short),
|
|
783
799
|
(t.retryCount || 0) + (t.rejectCount || 0) > 0 ? React.createElement('span', { title: '重试 ' + (t.retryCount || 0) + ' 次 / 驳回 ' + (t.rejectCount || 0) + ' 次', style: { color: C.warn, fontWeight: 600 } }, '⟳' + ((t.retryCount || 0) + (t.rejectCount || 0))) : null,
|
|
784
|
-
t.status === 'in-progress' && t.claimedBy ? React.createElement('span',
|
|
785
|
-
t.assignee ? React.createElement('span',
|
|
800
|
+
t.status === 'in-progress' && t.claimedBy ? React.createElement('span', { title: '执行该任务的子代理会话:' + t.claimedBy }, '⚡ ' + String(t.claimedBy).slice(0, 8) + '…') : null,
|
|
801
|
+
t.assignee ? React.createElement('span', { title: '指派给:' + t.assignee }, '👤→' + String(t.assignee).slice(0, 8) + '…') : null,
|
|
786
802
|
durB ? React.createElement('span', { title: durB.tip }, durB.txt) : null,
|
|
787
803
|
// 删除入口(仅草稿/待办/阻塞,多选模式下隐藏以免误触):hover 才由透明转红,平时不抢视觉
|
|
788
804
|
delOk ? React.createElement('span', {
|
|
@@ -801,22 +817,27 @@ function apply(ctx) {
|
|
|
801
817
|
t.status === 'verifying' && preview ? React.createElement('div', { style: { fontSize: 10, color: C.text2, marginTop: 3, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, '📝 ' + preview) : null, depBlock ? React.createElement('div', { style: { fontSize: 10, color: C.text2, marginTop: 3, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, title: '依赖全部完成后才会派发' }, '⛓ 等待「' + depWaitTitle + '」' + (depWaitN > 1 ? ' 等 ' + depWaitN + ' 个' : '')) : null) }
|
|
802
818
|
|
|
803
819
|
function ArchiveView() {
|
|
804
|
-
var _R = React; var useState = _R.useState
|
|
820
|
+
var _R = React; var useState = _R.useState
|
|
805
821
|
var _a = useState(state.archQ || ''), q = _a[0], setQ = _a[1]
|
|
806
|
-
|
|
807
|
-
|
|
822
|
+
// 归档数据源口径对齐仪表盘(反馈 n-muyg3e0m9z7i):直接读 state.tasks(get-tasks 全量含归档),
|
|
823
|
+
// 不再依赖独立 state.archived/fetchArchived——旧路径异步填库却未接入 TopPanel 的 notify 更新项,
|
|
824
|
+
// 归档 tab 打开后停在「暂无归档任务」空态,而仪表盘「已归档」计数来自 state.tasks(全量),二者对不上。
|
|
825
|
+
// 全量 + 按归档时间倒序(archivedAt 优先,回退 resolvedAt/createdAt)+ 顶部注明条数。
|
|
826
|
+
var archived = state.tasks.filter(function (t) { return t.status === 'archived' })
|
|
827
|
+
var list = archived.filter(function (t) {
|
|
808
828
|
if (!q) return true
|
|
809
829
|
var qq = q.toLowerCase()
|
|
810
830
|
return (t.title || '').toLowerCase().indexOf(qq) >= 0 || (t.id || '').toLowerCase().indexOf(qq) >= 0 || (t.tags || []).join(' ').toLowerCase().indexOf(qq) >= 0
|
|
811
831
|
}).sort(function (a, b) {
|
|
812
|
-
//
|
|
813
|
-
function
|
|
814
|
-
return (
|
|
832
|
+
// 归档时间倒序:archivedAt 优先,回退 resolvedAt/createdAt(老卡可能缺 archivedAt)
|
|
833
|
+
function archTs(t) { return t.archivedAt || t.resolvedAt || t.createdAt || '' }
|
|
834
|
+
return (archTs(b) || '').localeCompare(archTs(a) || '')
|
|
815
835
|
})
|
|
816
|
-
function restore(id) { rpc('update-task', { taskId: id, resetToPending: true }).then(function () {
|
|
836
|
+
function restore(id) { rpc('update-task', { taskId: id, resetToPending: true }).then(function () { fetchTasks() }).catch(function () {}) }
|
|
817
837
|
return React.createElement('div', null,
|
|
818
838
|
React.createElement('input', { value: q, onChange: function (e) { setQ(e.target.value); state.archQ = e.target.value }, placeholder: '检索标题 / ID / 标签…', style: { width: '100%', padding: '5px 8px', fontSize: 11, border: '1px solid ' + C.border2, borderRadius: 5, background: C.card, color: C.text, marginBottom: 8 } }),
|
|
819
|
-
|
|
839
|
+
React.createElement('div', { style: { fontSize: 10, color: C.text2, marginBottom: 6 } }, '共 ' + archived.length + ' 条归档(全量,与仪表盘「已归档」同口径)'),
|
|
840
|
+
archived.length === 0 ? React.createElement('div', { style: { fontSize: 11, color: C.text2, padding: 12, textAlign: 'center' } }, '暂无归档任务') :
|
|
820
841
|
list.length === 0 ? React.createElement('div', { style: { fontSize: 11, color: C.text2, padding: 12, textAlign: 'center' } }, '无匹配结果') :
|
|
821
842
|
list.map(function (t) {
|
|
822
843
|
return React.createElement('div', { key: t.id, style: { padding: '6px 8px', marginBottom: 5, background: C.card, border: '1px solid ' + C.border, borderRadius: 6 } },
|
|
@@ -887,7 +908,7 @@ function apply(ctx) {
|
|
|
887
908
|
}
|
|
888
909
|
var inp = { width: '100%', boxSizing: 'border-box', fontSize: 11, padding: '4px 6px', border: '1px solid ' + C.border, borderRadius: 4, background: C.card, color: C.text, fontFamily: 'inherit' }
|
|
889
910
|
var lblStyle = { fontSize: 10, color: C.text2, marginBottom: 3 }
|
|
890
|
-
function field(label, node) { return React.createElement('div', { style: { marginBottom: 8 } }, React.createElement('div', { style: lblStyle }, label), node) }
|
|
911
|
+
function field(label, node, hint) { return React.createElement('div', { style: { marginBottom: 8 } }, React.createElement('div', { style: lblStyle }, label), node, hint ? React.createElement('div', { style: { fontSize: 9, color: C.text2, marginTop: 2, lineHeight: 1.4 } }, hint) : null) }
|
|
891
912
|
var btnGhost = { fontSize: 11, padding: '4px 12px', border: '1px solid ' + C.border, borderRadius: 5, background: 'transparent', color: C.text2, cursor: 'pointer' }
|
|
892
913
|
var btnPrimary = { fontSize: 11, padding: '4px 14px', border: 'none', borderRadius: 5, background: C.brand, color: C_INV, fontWeight: 600, cursor: busy ? 'default' : 'pointer', opacity: busy ? 0.6 : 1 }
|
|
893
914
|
// 视口级 overlay(修裁切,反馈 n-mut9rzl4mxe4):fixed 全屏遮罩脱离看板抽屉(maxHeight 60vh + overflow hidden)
|
|
@@ -907,8 +928,8 @@ function apply(ctx) {
|
|
|
907
928
|
React.createElement('div', { style: { flex: '1 1 0', minWidth: 0 } }, field('管线', React.createElement('select', { value: pipe, onChange: function (e) { setPipe(e.target.value) }, style: inp },
|
|
908
929
|
React.createElement('option', { value: 'full' }, '全流程(执行+验证)'),
|
|
909
930
|
React.createElement('option', { value: 'work' }, '免验证(只做不验)'),
|
|
910
|
-
React.createElement('option', { value: 'direct' }, '主窗口处理'))))),
|
|
911
|
-
field('touches(文件/glob,逗号或换行分隔,可空)', React.createElement('input', { value: touchesRaw, onChange: function (e) { setTouchesRaw(e.target.value) }, placeholder: 'src/a.mjs, src/**', style: inp })),
|
|
931
|
+
React.createElement('option', { value: 'direct' }, '主窗口处理')), '管线 = 任务走哪种流程:全流程 = Worker 做 + Verifier 验;免验证 = 只做不验;主窗口处理 = 你在对话里直接办'))),
|
|
932
|
+
field('touches(文件/glob,逗号或换行分隔,可空)', React.createElement('input', { value: touchesRaw, onChange: function (e) { setTouchesRaw(e.target.value) }, placeholder: 'src/a.mjs, src/**', style: inp }), 'touches = 本卡要改动的文件(glob 匹配);声明后并行任务间防互踩(文件锁)'),
|
|
912
933
|
field('依赖(全部完成后才派发,可空)', cands.length === 0
|
|
913
934
|
? React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '当前无草稿/待办任务可选')
|
|
914
935
|
: React.createElement('div', { style: { maxHeight: 96, overflowY: 'auto', border: '1px solid ' + C.border, borderRadius: 4, padding: '4px 6px', background: C.card } }, cands.map(function (x) {
|
|
@@ -916,7 +937,7 @@ function apply(ctx) {
|
|
|
916
937
|
React.createElement('input', { type: 'checkbox', checked: !!depSel[x.id], onChange: function () { toggleDep(x.id) } }),
|
|
917
938
|
React.createElement('span', { style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, (statusLabels[x.status] || x.status) + ' · ' + (x.title || x.id)))
|
|
918
939
|
}))),
|
|
919
|
-
field('验收脚本(命令,可空)', React.createElement('input', { value: acc, onChange: function (e) { setAcc(e.target.value) }, placeholder: '如 node --test test/x.test.js', style: Object.assign({}, inp, { fontFamily: 'monospace' }) })),
|
|
940
|
+
field('验收脚本(命令,可空)', React.createElement('input', { value: acc, onChange: function (e) { setAcc(e.target.value) }, placeholder: '如 node --test test/x.test.js', style: Object.assign({}, inp, { fontFamily: 'monospace' }) }), '验收脚本 = 硬性验收命令:Worker 必须真实运行,Verifier 独立复跑'),
|
|
920
941
|
React.createElement('label', { style: { display: 'flex', alignItems: 'center', gap: 6, fontSize: 11, color: C.text, cursor: 'pointer', marginBottom: 6, flexWrap: 'wrap' } },
|
|
921
942
|
React.createElement('input', { type: 'checkbox', checked: asDraft, onChange: function (e) { setAsDraft(e.target.checked) } }),
|
|
922
943
|
'存为草稿',
|
|
@@ -928,7 +949,7 @@ function apply(ctx) {
|
|
|
928
949
|
err ? React.createElement('div', { style: { fontSize: 11, color: C.err, marginBottom: 6 } }, err) : null,
|
|
929
950
|
React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 6 } },
|
|
930
951
|
React.createElement('button', { onClick: close, style: btnGhost }, '取消'),
|
|
931
|
-
React.createElement('button', { onClick: submit, disabled: busy, title: asDraft ? '创建为草稿(不派发)' : '
|
|
952
|
+
React.createElement('button', { onClick: submit, disabled: busy, title: asDraft ? '创建为草稿(不派发)' : '创建并立即交给 Worker 执行', style: btnPrimary }, busy ? '创建中…' : (asDraft ? '存为草稿' : '创建并交给 Worker'))))))
|
|
932
953
|
}
|
|
933
954
|
|
|
934
955
|
// ============================================================================
|
|
@@ -1223,11 +1244,16 @@ function apply(ctx) {
|
|
|
1223
1244
|
React.createElement('div', { style: { fontSize: 10, display: 'flex', flexWrap: 'wrap', gap: 5 } }, task.waitingForTouches.map(function (id) { var t2 = getTask(id); return React.createElement('span', { key: id, onClick: function () { if (t2) { state.detailId = id; notify() } }, title: t2 ? (t2.title + ' (' + id + ')') : id, style: { padding: '1px 5px', borderRadius: 3, background: C.nested, border: '1px solid ' + C.border, color: t2 ? C.brand : C.text2, cursor: t2 ? 'pointer' : 'default' } }, (t2 ? t2.title.slice(0, 20) : shortId(id)) + ' · ' + shortId(id)) })),
|
|
1224
1245
|
Array.isArray(task.touches) && task.touches.length ? React.createElement('div', { style: { fontSize: 10, color: C.text2, marginTop: 4 } }, '本任务 touches: ' + task.touches.join('、')) : null) : null,
|
|
1225
1246
|
Array.isArray(task.touches) && task.touches.length ? React.createElement('div', { style: { fontSize: 11, color: C.text2, marginBottom: 6, padding: '4px 6px', background: C.nested, borderRadius: 4 } }, '🔒 文件级排他(touches): ' + task.touches.join('、')) : null,
|
|
1247
|
+
// 起草 lint(task.lintWarnings[],host 建卡/更新卡现算落盘,get-tasks 透出):
|
|
1248
|
+
// 四规则软警告(粒度过粗/双仓库歧义/缺验收/标题缺动词或描述空)随卡展示,缺字段/空数组整块不渲染(老卡零影响)。
|
|
1249
|
+
Array.isArray(task.lintWarnings) && task.lintWarnings.length ? React.createElement('div', { style: { fontSize: 11, color: C.text, marginBottom: 6, padding: '5px 8px', border: '1px solid ' + C.warn, borderRadius: 6, background: 'color-mix(in srgb, ' + C.warn + ' 8%, transparent)' } },
|
|
1250
|
+
task.lintWarnings.map(function (w, i) { return React.createElement('div', { key: i, style: { fontSize: 10, color: C.text2, lineHeight: 1.6, display: 'flex', alignItems: 'flex-start', gap: 4 } }, React.createElement('span', { style: { flexShrink: 0 } }, '⚠️'), React.createElement('span', null, w)) })) : null,
|
|
1226
1251
|
(task.status === 'in-progress' || task.status === 'verifying') && state.activity[task.id] ? React.createElement('div', { style: { fontSize: 10, color: C.brand, marginBottom: 8, padding: '4px 8px', background: C.nested, borderRadius: 4, display: 'flex', alignItems: 'center', gap: 4, animation: 'tskb-pulse 2s ease-in-out infinite' } }, ic('activity', 10), React.createElement('span', { style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, '当前动作: ' + state.activity[task.id])) : null,
|
|
1227
1252
|
task.status === 'in-progress' && task.claimedBy ? React.createElement('div', { style: { fontSize: 10, color: C.text2, marginBottom: 8, padding: '5px 8px', background: C.nested, borderRadius: 4, display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' } },
|
|
1228
1253
|
ic('activity', 10),
|
|
1229
1254
|
React.createElement('span', null, '已运行 ' + elapsedSince(task.claimedAt) + '(软超时 ' + state.softTimeoutMin + ' 分提醒 · 硬超时 ' + state.hardTimeoutMin + ' 分自动终止)'),
|
|
1230
1255
|
elapsedMin(task.claimedAt) > state.softTimeoutMin ? React.createElement('span', { style: { color: C.warn, fontWeight: 600 } }, '⏱ 已超软超时') : null,
|
|
1256
|
+
React.createElement('span', { title: '当前活跃 run 的子会话 id(continuable 即 childId);终止前先到子代理列表核对该 id 的同名条目,避免误杀别的合法 Worker', style: { fontSize: 10, color: C.text2, fontFamily: 'monospace', wordBreak: 'break-all', flexBasis: '100%' } }, '子会话 id: ' + task.claimedBy + '(对应子代理列表同名条目)'),
|
|
1231
1257
|
React.createElement('button', { onClick: doTerminate, title: '终止该执行 Agent,任务回 pending 重新排队', style: { fontSize: 10, padding: '2px 8px', border: '1px solid ' + C.err, borderRadius: 3, background: 'transparent', color: C.err, cursor: 'pointer', marginLeft: 'auto' } }, '⏹ 立即终止')) : null,
|
|
1232
1258
|
task.escalation ? React.createElement('div', { id: 'tskb-escalation', style: { marginBottom: 8, padding: '8px 10px', border: '1px solid ' + C.err, borderRadius: 6, background: 'color-mix(in srgb, ' + C.err + ' 8%, transparent)' } },
|
|
1233
1259
|
React.createElement('div', { style: { fontSize: 12, fontWeight: 700, color: C.err, marginBottom: 4, display: 'flex', alignItems: 'center', gap: 4 } }, ic('alert-triangle', 13), 'Worker 上报歧义 — 等待主窗口裁决'),
|
|
@@ -1302,7 +1328,8 @@ function apply(ctx) {
|
|
|
1302
1328
|
React.createElement(MsgThread, { messages: task.messages, taskId: task.id }),
|
|
1303
1329
|
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,
|
|
1304
1330
|
React.createElement('div', { style: { display: 'flex', gap: 4, flexWrap: 'wrap', marginTop: 6 } },
|
|
1305
|
-
|
|
1331
|
+
// 草稿态「保存并重置」实为 resetToPending 静默发布:按钮文案改「保存并发布」+ title 发布预警(巡检 n-muyi5yfec8m0)
|
|
1332
|
+
React.createElement('button', { onClick: saveEdit, disabled: saving, title: task.status === 'draft' ? '发布后按当前工作模式可能被立即派发' : '保存并重置为待办', style: { fontSize: 10, padding: '3px 8px', border: 'none', borderRadius: 3, background: C.brand, color: C_INV, cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 3 } }, saving ? '保存中…' : [ic('save', 10), task.status === 'draft' ? ' 保存并发布' : ' 保存并重置']),
|
|
1306
1333
|
task.status === 'draft' ? React.createElement('button', { onClick: function () { doAction(function () { return rpc('update-task', { taskId: task.id, publish: true }) }) }, style: { fontSize: 10, padding: '3px 8px', border: 'none', borderRadius: 3, background: C.ok, color: C_INV, cursor: 'pointer', fontWeight: 600, display: 'inline-flex', alignItems: 'center', gap: 3 } }, ic('rocket', 10), '发布(进入派发池)') : null,
|
|
1307
1334
|
(task.status === 'pending' || task.status === 'blocked') ? React.createElement('button', { onClick: function () { doAction(function () { return rpc('claim-task', { taskId: task.id }) }) }, style: { fontSize: 10, padding: '3px 8px', border: 'none', borderRadius: 3, background: C.brand, color: C_INV, cursor: 'pointer' } }, '领取') : null,
|
|
1308
1335
|
task.status === 'pending' ? React.createElement('button', { onClick: function () { doAction(function () { return doManualDispatch('worker') }) }, title: '手动派发给一次性 Worker 子代理', style: { fontSize: 10, padding: '3px 8px', border: 'none', borderRadius: 3, background: C.warn, color: C_INV, cursor: 'pointer', fontWeight: 600, display: 'inline-flex', alignItems: 'center', gap: 3 } }, ic('zap', 10), '派发') : null,
|
|
@@ -1550,8 +1577,59 @@ function apply(ctx) {
|
|
|
1550
1577
|
})) : null)
|
|
1551
1578
|
}
|
|
1552
1579
|
|
|
1580
|
+
// ===== 每日批次摘要(近 24h,异常驱动审视③):报告导出「批次一页纸」段的数据源 =====
|
|
1581
|
+
// 纯聚合现有字段(不新建状态、不重拉):完成/驳回/token/超时按 run 与消息的 at 滚 24h 窗口现算,
|
|
1582
|
+
// 幽灵回收读当前 healthHints 里的一次性 info(「本轮回收 N 个幽灵活跃表项」,无则 0);
|
|
1583
|
+
// 一句话质量趋势按近 24h 落定的 resolved 任务一次通过率(rejectCount=0 占比)。
|
|
1584
|
+
// 范围无关:报告顶部的批次摘要永远反映「最近 24h」实况,不受「统计范围」筛选影响。
|
|
1585
|
+
function batchDigest() {
|
|
1586
|
+
var now = Date.now(), since = now - 86400000
|
|
1587
|
+
var all = (Array.isArray(state.tasks) ? state.tasks : []).concat(Array.isArray(state.archived) ? state.archived : [])
|
|
1588
|
+
var resolved = 0, rejected = 0, tokTot = 0, tokEff = 0, timeouts = 0, trendResolved = 0, trendFirstPass = 0
|
|
1589
|
+
function in24(iso) { var d = Date.parse(iso); return isFinite(d) && d >= since && d <= now }
|
|
1590
|
+
function pos(v) { var n = Number(v); return isFinite(n) && n > 0 ? n : 0 }
|
|
1591
|
+
all.forEach(function (t) {
|
|
1592
|
+
if (!t) return
|
|
1593
|
+
var doneAt = t.resolvedAt || t.verifiedAt || ''
|
|
1594
|
+
if (doneAt && in24(doneAt)) {
|
|
1595
|
+
resolved++
|
|
1596
|
+
if (t.status === 'resolved') { trendResolved++; if (!(t.rejectCount > 0)) trendFirstPass++ }
|
|
1597
|
+
}
|
|
1598
|
+
if (Array.isArray(t.messages)) t.messages.forEach(function (m) { if (m && m.kind === 'rejection' && m.at && in24(m.at)) rejected++ })
|
|
1599
|
+
if (Array.isArray(t.runs)) t.runs.forEach(function (r) {
|
|
1600
|
+
if (!r || !r.at || !in24(r.at)) return
|
|
1601
|
+
var u = (r.usage && typeof r.usage === 'object') ? r.usage : {}
|
|
1602
|
+
var inp = pos(u.input), outp = pos(u.output), cr = pos(u.cacheRead), cw = pos(u.cacheWrite)
|
|
1603
|
+
var tot = pos(u.total) || (inp + outp + cr + cw)
|
|
1604
|
+
tokTot += tot; tokEff += inp + outp + cw
|
|
1605
|
+
if (r.outcome === 'timeout/error' || r.outcome === 'timeout') timeouts++
|
|
1606
|
+
})
|
|
1607
|
+
})
|
|
1608
|
+
var ghosts = 0
|
|
1609
|
+
var hints = Array.isArray(state.healthHints) ? state.healthHints : []
|
|
1610
|
+
hints.forEach(function (h) { var m = h && h.text ? String(h.text).match(/本轮回收 (\d+) 个幽灵活跃表项/) : null; if (m) ghosts += pos(m[1]) })
|
|
1611
|
+
var trend = trendResolved > 0 ? ('一次通过率 ' + Math.round(trendFirstPass / trendResolved * 100) + '%(近 24h 完成 ' + trendResolved + ' 张中 ' + trendFirstPass + ' 张零驳回)') : '近 24h 无完成记录,暂无质量趋势'
|
|
1612
|
+
var has = resolved > 0 || rejected > 0 || tokTot > 0 || timeouts > 0 || ghosts > 0 || trendResolved > 0
|
|
1613
|
+
return { has: has, resolved: resolved, rejected: rejected, tokTot: tokTot, tokEff: tokEff, timeouts: timeouts, ghosts: ghosts, trend: trend }
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1553
1616
|
function buildReport() {
|
|
1554
1617
|
var lines = ['# 任务看板报告', '', '生成时间: ' + new Date().toLocaleString(), '工作模式: ' + (workModeNames[state.workMode || (state.teamMode ? 'team' : (state.boardMode === 'auto' ? 'auto' : 'list'))] || '自动派发'), '', '统计范围: ' + rangeLabel()]
|
|
1618
|
+
// ===== 每日批次摘要(异常驱动审视③):报告顶部一页纸,近 24h 五字段 + 一句话质量趋势 =====
|
|
1619
|
+
var dg = batchDigest()
|
|
1620
|
+
if (dg.has) {
|
|
1621
|
+
lines.push('## 批次摘要(近 24 小时)')
|
|
1622
|
+
lines.push('- 完成:' + dg.resolved + ' 张')
|
|
1623
|
+
lines.push('- 驳回:' + dg.rejected + ' 次')
|
|
1624
|
+
lines.push('- Token:约 ' + fmtTokens(dg.tokTot) + (dg.tokEff > 0 ? '(其中有效 ' + fmtTokens(dg.tokEff) + ')' : ''))
|
|
1625
|
+
lines.push('- 异常事件:超时 ' + dg.timeouts + ' 次 · 幽灵回收 ' + dg.ghosts + ' 次')
|
|
1626
|
+
lines.push('- 质量趋势:' + dg.trend)
|
|
1627
|
+
lines.push('')
|
|
1628
|
+
} else {
|
|
1629
|
+
lines.push('## 批次摘要(近 24 小时)')
|
|
1630
|
+
lines.push('近 24 小时无活动记录(无完成 / 驳回 / 消耗 / 异常事件)')
|
|
1631
|
+
lines.push('')
|
|
1632
|
+
}
|
|
1555
1633
|
var groups = { inProgress: [], verifying: [], pending: [], blocked: [], resolved: [] }
|
|
1556
1634
|
var rg = activeRange()
|
|
1557
1635
|
state.tasks.forEach(function (t) {
|
|
@@ -1762,7 +1840,7 @@ function apply(ctx) {
|
|
|
1762
1840
|
// 范围说明(task-muwc7hjd):用户常把 RangeFilter 当成「整页过滤」,但今日大数字与近 7 天柱子
|
|
1763
1841
|
// 是自身固定口径(今日=本地今天、近 7 天=最近 7 个本地日)——不随范围变,必须在文案里讲清,
|
|
1764
1842
|
// 否则「选了范围数字没变」看起来像 bug。模型分布/Top8/累计才是范围生效的三处。
|
|
1765
|
-
React.createElement('div', { style: { fontSize: 9, color: C.text2, marginTop: 6, lineHeight: 1.5 } }, '口径:累计与 Top 8 仅看板派发的 Worker/Verifier run
|
|
1843
|
+
React.createElement('div', { style: { fontSize: 9, color: C.text2, marginTop: 6, lineHeight: 1.5 } }, '口径:累计与 Top 8 仅看板派发的 Worker/Verifier run(= 一次执行)消耗;主窗口行=本会话对话消耗,与看板派发口径并列不混入(单列一行 + 模型分布尾部一条,不进累计 / Top 8);主数字(今日 / 近 7 天 / 模型分布 / Top 8 / 主窗口行)均为总量口径(含缓存读);有效消耗(input+output+cacheWrite,不含缓存读)见悬浮 title'),
|
|
1766
1844
|
React.createElement('div', { style: { fontSize: 9, color: C.text2, marginTop: 2, lineHeight: 1.5 } }, '统计范围作用于按模型分布 / 任务消耗 Top 8 / 累计三分量(按 run 的本地日落点过滤);今日与「近 7 天」为固定口径,不随范围变化。'),
|
|
1767
1845
|
// 「→ 模型表现」滚动锚链接(task-muxhtgi2 卡3):一键滚到下方模型×场景七指标表
|
|
1768
1846
|
// (与详情页 goArbitration 同一 scrollIntoView 原语;目标区空态也挂锚 id,链接始终可达)
|
|
@@ -1913,8 +1991,8 @@ function apply(ctx) {
|
|
|
1913
1991
|
var days14 = lastNDays(14)
|
|
1914
1992
|
var todayKey = localDayKey()
|
|
1915
1993
|
// 块头:左块名 + 右侧今日/汇总值(fontWeight 400 小字)
|
|
1916
|
-
function blockHead(label, rightEl) {
|
|
1917
|
-
return React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2, marginBottom: 4, display: 'flex', alignItems: 'baseline', gap: 6 } }, label,
|
|
1994
|
+
function blockHead(label, rightEl, tip) {
|
|
1995
|
+
return React.createElement('div', { title: tip, style: { fontSize: 10, fontWeight: 600, color: C.text2, marginBottom: 4, display: 'flex', alignItems: 'baseline', gap: 6 } }, label,
|
|
1918
1996
|
rightEl ? React.createElement('span', { style: { marginLeft: 'auto', fontWeight: 400, fontSize: 9 } }, rightEl) : null)
|
|
1919
1997
|
}
|
|
1920
1998
|
// ===== ① 一次通过率趋势(近 14 天):当日率 = 落定 resolved 任务中零驳回(rejectCount=0)占比 =====
|
|
@@ -1927,14 +2005,14 @@ function apply(ctx) {
|
|
|
1927
2005
|
var fpHas = false; fpCells.forEach(function (c) { if (c.rate !== null) fpHas = true })
|
|
1928
2006
|
var fpToday = fpCells[fpCells.length - 1]
|
|
1929
2007
|
var block1 = React.createElement('div', { style: { flex: '1 1 260px', minWidth: 230 } },
|
|
1930
|
-
blockHead('一次通过率(近 14 天)', React.createElement('span', null, '今日 ', React.createElement('span', { style: { color: fpToday.rate !== null ? C.brand : C.text2, fontWeight: 600 } }, fpToday.rate !== null ? mpPct(fpToday.rate) : '—'))),
|
|
2008
|
+
blockHead('一次通过率(近 14 天)', React.createElement('span', null, '今日 ', React.createElement('span', { style: { color: fpToday.rate !== null ? C.brand : C.text2, fontWeight: 600 } }, fpToday.rate !== null ? mpPct(fpToday.rate) : '—')), '一次通过率 = 当天落定「已完成」任务里零驳回(rejectCount=0)的占比;驳回 = Verifier 验收判 reject'),
|
|
1931
2009
|
fpHas ? qtDayBars(fpCells, C.ok, todayKey)
|
|
1932
2010
|
: React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '近 14 天窗口内暂无 resolved 任务落定'))
|
|
1933
2011
|
// ===== ② 卡时长分布(四桶横条):创建 → 落定(verifiedAt||resolvedAt)墙钟,含排队/执行/验收全程 =====
|
|
1934
2012
|
var durMax = 1
|
|
1935
2013
|
if (durB) QT_DUR_BUCKETS.forEach(function (b) { var v = Number(durB[b.k]) || 0; if (v > durMax) durMax = v })
|
|
1936
2014
|
var block2 = React.createElement('div', { style: { flex: '1 1 180px', minWidth: 170 } },
|
|
1937
|
-
blockHead('卡时长分布', durSum > 0 ? React.createElement('span', null, React.createElement('span', { style: { color: C.text, fontWeight: 600 } }, String(durSum)), ' 张落定卡') : null),
|
|
2015
|
+
blockHead('卡时长分布', durSum > 0 ? React.createElement('span', null, React.createElement('span', { style: { color: C.text, fontWeight: 600 } }, String(durSum)), ' 张落定卡') : null, '卡时长 = 创建 → 落定的墙钟(含排队/执行/验收全程)'),
|
|
1938
2016
|
durSum === 0 ? React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '暂无时长样本')
|
|
1939
2017
|
: React.createElement('div', null, QT_DUR_BUCKETS.map(function (b) {
|
|
1940
2018
|
var v = Number(durB[b.k]) || 0
|
|
@@ -1955,13 +2033,13 @@ function apply(ctx) {
|
|
|
1955
2033
|
var toHas = false; toCells.forEach(function (c) { if (c.rate !== null) toHas = true })
|
|
1956
2034
|
var toToday = toCells[toCells.length - 1]
|
|
1957
2035
|
var block3 = React.createElement('div', { style: { flex: '1 1 260px', minWidth: 230 } },
|
|
1958
|
-
blockHead('超时率走势(近 14 天)', React.createElement('span', null, '今日 ', React.createElement('span', { style: { color: toToday.rate !== null ? C.warn : C.text2, fontWeight: 600 } }, toToday.rate !== null ? mpPct(toToday.rate) : '—'))),
|
|
2036
|
+
blockHead('超时率走势(近 14 天)', React.createElement('span', null, '今日 ', React.createElement('span', { style: { color: toToday.rate !== null ? C.warn : C.text2, fontWeight: 600 } }, toToday.rate !== null ? mpPct(toToday.rate) : '—')), '超时率 = 超时落定(timeout/error)的 run 占当天 run 的比例'),
|
|
1959
2037
|
toHas ? qtDayBars(toCells, C.warn, todayKey)
|
|
1960
2038
|
: React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '近 14 天窗口内暂无 run 落定'))
|
|
1961
2039
|
// ===== ④ 续跑成功率(范围总量口径,卡2出参无 byDay):resume:true 续跑记录中 completed 占比 =====
|
|
1962
2040
|
var rsmRate = (rsm && typeof rsm.rate === 'number' && isFinite(rsm.rate)) ? rsm.rate : null
|
|
1963
2041
|
var block4 = React.createElement('div', { style: { flex: '1 1 180px', minWidth: 170 } },
|
|
1964
|
-
blockHead('续跑成功率', rsmRuns > 0 ? React.createElement('span', null, React.createElement('span', { style: { color: C.text, fontWeight: 600 } }, String((rsm && rsm.completed) || 0) + '/' + rsmRuns), ' 续跑成功') : null),
|
|
2042
|
+
blockHead('续跑成功率', rsmRuns > 0 ? React.createElement('span', null, React.createElement('span', { style: { color: C.text, fontWeight: 600 } }, String((rsm && rsm.completed) || 0) + '/' + rsmRuns), ' 续跑成功') : null, '续跑成功率 = resume 续跑记录中 outcome=completed 的占比;续跑 = 前次超时/失败后继续跑'),
|
|
1965
2043
|
rsmRuns === 0 ? React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '暂无续跑记录')
|
|
1966
2044
|
: React.createElement('div', { title: '续跑成功率 = resume:true 续跑记录中 outcome=completed 的占比(范围总量口径,host 出参无逐日);续跑 = 前次 run 超时/失败后续跑' },
|
|
1967
2045
|
React.createElement('div', { style: { fontSize: 16, fontWeight: 700, color: rsmRate !== null ? C.ok : C.text2 } }, rsmRate !== null ? mpPct(rsmRate) : '—'),
|
|
@@ -2035,6 +2113,8 @@ function apply(ctx) {
|
|
|
2035
2113
|
var infoColor = 'var(--dsw-alias-state-business-primary)'
|
|
2036
2114
|
return React.createElement('div', { style: { padding: '8px 10px', background: C.card, border: '1px solid ' + C.border, borderRadius: 6, marginBottom: 12 } },
|
|
2037
2115
|
React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 4 } }, ic('activity', 11), '架构健康'),
|
|
2116
|
+
// 术语白话(巡检 n-muyg5m7o5r00):settle/touches/p90 等黑话第一屏无解,补一行术语说明
|
|
2117
|
+
React.createElement('div', { style: { fontSize: 9, color: C.text2, marginBottom: 5, lineHeight: 1.5 } }, '运行时自检告警,多为自动恢复的瞬时异常。术语:settle = 任务结算通道;touches = 任务声明的改动文件(文件锁);p90 = 执行时长第 90 百分位'),
|
|
2038
2118
|
hints.map(function (h, i) {
|
|
2039
2119
|
var warn = h.level === 'warn'
|
|
2040
2120
|
var col = warn ? C.warn : infoColor
|
|
@@ -2075,12 +2155,12 @@ function apply(ctx) {
|
|
|
2075
2155
|
function pick(m) { if (m === cur) return; rpc('set-work-mode', { mode: m }).then(fetchTasks).catch(function () {}) }
|
|
2076
2156
|
// 档位文案:名称 + 一句说明(说明常驻显示在按钮内,选中态高亮不可用 title 才看得到)
|
|
2077
2157
|
var opts = [
|
|
2078
|
-
{ k: 'list', name: '清单模式', desc: '
|
|
2079
|
-
{ k: 'auto', name: '自动派发', desc: '
|
|
2080
|
-
{ k: 'team', name: 'Team 托管', desc: '
|
|
2158
|
+
{ k: 'list', name: '清单模式', desc: '只读清单,不自动派发', icon: 'clipboard-list' },
|
|
2159
|
+
{ k: 'auto', name: '自动派发', desc: '建卡即派给后台子代理', icon: 'zap' },
|
|
2160
|
+
{ k: 'team', name: 'Team 托管', desc: '草稿起步,你裁决全流程', icon: 'users' }
|
|
2081
2161
|
]
|
|
2082
2162
|
var btnBase = { border: 'none', cursor: 'pointer', fontWeight: 500, textAlign: 'center', display: 'inline-flex', flexDirection: 'column', alignItems: 'center', gap: 1, padding: '3px 9px', transition: 'all .15s', lineHeight: 1.25 }
|
|
2083
|
-
return React.createElement('div', { title: '工作模式(清单 / 自动派发 / Team 托管)——歧义裁决、Verifier
|
|
2163
|
+
return React.createElement('div', { title: '工作模式(清单 / 自动派发 / Team 托管)——歧义裁决、Verifier 验收、文件锁(touches)排他在三档下全部生效', style: { display: 'inline-flex', borderRadius: 8, border: '1px solid ' + C.border, overflow: 'hidden', background: C.card } }, opts.map(function (o) {
|
|
2084
2164
|
var on = cur === o.k
|
|
2085
2165
|
return React.createElement('button', {
|
|
2086
2166
|
key: o.k, onClick: function () { pick(o.k) }, title: o.name + ':' + o.desc,
|
|
@@ -2119,13 +2199,13 @@ function apply(ctx) {
|
|
|
2119
2199
|
var ps = state.poolStatus
|
|
2120
2200
|
if (!ps) return null
|
|
2121
2201
|
var wActive = (ps.workers || []).filter(function (w) { return w.busy }).length
|
|
2122
|
-
var wTotal =
|
|
2202
|
+
var wTotal = state.maxWorkers // 分母=并发上限而非当前池实例数,空闲也显示 0/上限
|
|
2123
2203
|
var vActive = (ps.verifiers || []).filter(function (v) { return v.busy }).length
|
|
2124
|
-
var vTotal =
|
|
2204
|
+
var vTotal = state.maxVerifiers
|
|
2125
2205
|
return React.createElement('span', { style: { fontSize: 9, color: C.text2, display: 'inline-flex', gap: 4, alignItems: 'center' } },
|
|
2126
|
-
//
|
|
2127
|
-
React.createElement('span', { title: 'Worker
|
|
2128
|
-
React.createElement('span', { title: 'Verifier
|
|
2206
|
+
// 池水位标注(反馈 n-mut9rzoq3flu / n-muyg4wl9208c):裸数字第一屏无解,补可见文字标签 + title
|
|
2207
|
+
React.createElement('span', { title: '活跃 Worker 数/上限(⚡ = 在跑)', style: { display: 'inline-flex', alignItems: 'center', gap: 2 } }, ic('zap', 10), 'Worker ' + wActive + '/' + wTotal),
|
|
2208
|
+
React.createElement('span', { title: '活跃 Verifier 数/上限(✓ = 在跑)', style: { display: 'inline-flex', alignItems: 'center', gap: 2 } }, ic('check', 10), 'Verifier ' + vActive + '/' + vTotal))
|
|
2129
2209
|
}
|
|
2130
2210
|
|
|
2131
2211
|
// #11 头部减负:池配置收纳进 ⚙️ 弹出层(含 #17 verifier 异构模型设置)
|
|
@@ -2135,7 +2215,7 @@ function apply(ctx) {
|
|
|
2135
2215
|
var _b = useState(false), dirty = _b[0], setDirty = _b[1]
|
|
2136
2216
|
var _m = useState(null), models = _m[0], setModels = _m[1]
|
|
2137
2217
|
var cfgKey = props.cfgKey || 'verifierModel'
|
|
2138
|
-
var label = props.label || '
|
|
2218
|
+
var label = props.label || 'Verifier 模型'
|
|
2139
2219
|
// 下拉列出当前网关可用模型(host list-models RPC);枚举失败降级为文本输入
|
|
2140
2220
|
useEffect(function () {
|
|
2141
2221
|
var cancelled = false
|
|
@@ -2188,13 +2268,13 @@ function apply(ctx) {
|
|
|
2188
2268
|
open && pos ? React.createElement('div', { style: { position: 'fixed', top: pos.top + 'px', right: pos.right + 'px', padding: '8px 10px', background: C.card, border: '1px solid ' + C.border, borderRadius: 8, boxShadow: '0 4px 16px rgba(0,0,0,0.15)', zIndex: 1000, whiteSpace: 'nowrap', maxHeight: 'calc(100vh - ' + (pos.top + 12) + 'px)', overflowY: 'auto' } },
|
|
2189
2269
|
React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2, marginBottom: 5 } }, '并发上限(一次性派发,用完即销毁)'),
|
|
2190
2270
|
React.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center', marginBottom: 5 } },
|
|
2191
|
-
React.createElement(PoolCfg, { label: '
|
|
2192
|
-
React.createElement(PoolCfg, { label: '
|
|
2271
|
+
React.createElement(PoolCfg, { label: 'Worker 并发', cfgKey: 'maxWorkers', value: props.maxW }),
|
|
2272
|
+
React.createElement(PoolCfg, { label: 'Verifier 并发', cfgKey: 'maxVerifiers', value: props.maxV })),
|
|
2193
2273
|
React.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center', marginBottom: 5 } },
|
|
2194
|
-
React.createElement(ModelCfg, { label: '
|
|
2274
|
+
React.createElement(ModelCfg, { label: 'Worker 模型', cfgKey: 'workerModel', value: props.workerModel }),
|
|
2195
2275
|
React.createElement('span', { style: { fontSize: 9, color: C.text2 } }, '(Worker 执行模型)')),
|
|
2196
2276
|
React.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } },
|
|
2197
|
-
React.createElement(ModelCfg, { label: '
|
|
2277
|
+
React.createElement(ModelCfg, { label: 'Verifier 模型', cfgKey: 'verifierModel', value: props.verifierModel }),
|
|
2198
2278
|
React.createElement('span', { style: { fontSize: 9, color: C.text2 } }, '(Verifier 异构审查)')),
|
|
2199
2279
|
React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2, margin: '7px 0 5px' } }, '两级超时(软超时只提醒主窗口,由你决定继续等待或终止)'),
|
|
2200
2280
|
React.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } },
|
|
@@ -2245,7 +2325,7 @@ function apply(ctx) {
|
|
|
2245
2325
|
React.createElement('span', { style: { marginLeft: 'auto', fontSize: 9, padding: '1px 6px', borderRadius: 3, background: C.brand, color: C_INV } }, '忙碌')),
|
|
2246
2326
|
m.model ? React.createElement('div', { style: { fontSize: 9, color: C.warn, marginBottom: 3 }, title: '异构模型审查' }, '🧬 ' + m.model) : null,
|
|
2247
2327
|
curTask ? React.createElement('div', { onClick: function () { state.detailId = curTask.id; state.view = 'board'; notify() }, style: { fontSize: 10, color: C.brand, cursor: 'pointer', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, title: curTask.title }, '→ ' + curTask.title) : React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '→ ' + m.taskId),
|
|
2248
|
-
React.createElement('div', { style: { fontSize: 9, color: C.text2, marginTop: 3 }, title: '
|
|
2328
|
+
React.createElement('div', { style: { fontSize: 9, color: C.text2, marginTop: 3 }, title: '一次性子代理会话:' + (m.runId || '') }, '会话 ' + String(m.runId || '').slice(0, 8) + '…(用完即销毁)'))
|
|
2249
2329
|
}
|
|
2250
2330
|
var ws = ps.workers || [], vs = ps.verifiers || []
|
|
2251
2331
|
var _R2 = React; var _st = _R2.useState(''), editMsg = _st[0], setEditMsg = _st[1]
|