dsh-agent-board 1.0.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/lib/client.js ADDED
@@ -0,0 +1,595 @@
1
+ /* global window, document, fetch, getComputedStyle, MutationObserver, ResizeObserver */
2
+ // dsh-agent-board — Browser 侧 bundle(CJS 工厂,供 dsh web 客户端 ModuleLoader 注入)。
3
+ // 本文件即源码,直接维护(v68 起:变形层已拆除,不再从其他文件生成)。
4
+ window.__ModuleLoader__.load({
5
+ id: "dsh-agent-board",
6
+ factory: (require) => {
7
+ var module = { exports: {} };
8
+ var exports = module.exports;
9
+ 'use strict'
10
+ const React = require('react')
11
+
12
+ function apply(ctx) {
13
+ var slots = ctx.get('slots')
14
+ if (slots === undefined) return
15
+ var sessionsSvc = ctx.get('sessions')
16
+ var C = { bg: 'var(--dsw-alias-bg-base)', card: 'var(--dsw-alias-bg-layer-1)', nested: 'var(--dsw-alias-bg-layer-2)', border: 'var(--dsw-alias-border-l1)', brand: 'var(--dsw-alias-brand-primary)', text: 'var(--dsw-alias-label-primary)', text2: 'var(--dsw-alias-label-secondary)', err: 'var(--dsw-alias-state-error-primary)', ok: 'var(--dsw-alias-state-success-primary)', warn: 'var(--dsw-alias-state-warn-primary)' }
17
+ var prioColor = { critical: C.err, high: C.warn, medium: C.brand, low: C.text2 }
18
+ var prioLabel = { critical: '紧急', high: '高', medium: '中', low: '低' }
19
+ var statusLabels = { draft: '草稿', pending: '待办', 'in-progress': '进行中', verifying: '验证中', resolved: '已完成', blocked: '阻塞', cancelled: '已取消', archived: '已归档' }
20
+ var statusColors = { draft: C.text2, pending: C.text2, 'in-progress': C.brand, verifying: C.warn, resolved: C.ok, blocked: C.err, archived: C.text2 }
21
+ // ===== 图标收口(docs/icon-style-guide.md:Lucide 线性 SVG,currentColor 跟随主题)=====
22
+ // ICONS 存 [tag, attrs] 数组,用 createElement 逐个渲染(避免 dangerouslySetInnerHTML 受限)
23
+ var ICONS = {
24
+ 'clipboard-list': [
25
+ ['rect', { x: 8, y: 2, width: 8, height: 4, rx: 1 }],
26
+ ['path', { d: 'M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2' }],
27
+ ['path', { d: 'M12 11h4' }],
28
+ ['path', { d: 'M12 16h4' }],
29
+ ['path', { d: 'M8 11h.01' }],
30
+ ['path', { d: 'M8 16h.01' }]
31
+ ],
32
+ 'bar-chart-3': [
33
+ ['path', { d: 'M3 3v18h18' }],
34
+ ['path', { d: 'M18 17V9' }],
35
+ ['path', { d: 'M13 17V5' }],
36
+ ['path', { d: 'M8 17v-3' }]
37
+ ],
38
+ 'alert-triangle': [
39
+ ['path', { d: 'm21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3' }],
40
+ ['path', { d: 'M12 9v4' }],
41
+ ['path', { d: 'M12 17h.01' }]
42
+ ],
43
+ 'zap': [
44
+ ['polygon', { points: '13 2 3 14 12 14 11 22 21 10 12 10 13 2' }]
45
+ ],
46
+ 'check': [
47
+ ['path', { d: 'M20 6 9 17l-5-5' }]
48
+ ],
49
+ 'check-circle': [
50
+ ['circle', { cx: 12, cy: 12, r: 10 }],
51
+ ['path', { d: 'm9 12 2 2 4-4' }]
52
+ ],
53
+ 'x-circle': [
54
+ ['circle', { cx: 12, cy: 12, r: 10 }],
55
+ ['path', { d: 'm15 9-6 6' }],
56
+ ['path', { d: 'm9 9 6 6' }]
57
+ ],
58
+ 'x': [
59
+ ['path', { d: 'M18 6 6 18' }],
60
+ ['path', { d: 'm6 6 12 12' }]
61
+ ],
62
+ 'refresh-cw': [
63
+ ['path', { d: 'M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8' }],
64
+ ['path', { d: 'M21 3v5h-5' }],
65
+ ['path', { d: 'M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16' }],
66
+ ['path', { d: 'M8 16H3v5' }]
67
+ ],
68
+ 'save': [
69
+ ['path', { d: 'M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z' }],
70
+ ['path', { d: 'M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7' }],
71
+ ['path', { d: 'M7 3v4a1 1 0 0 0 1 1h7' }]
72
+ ],
73
+ 'archive': [
74
+ ['rect', { width: 20, height: 5, x: 2, y: 3, rx: 1 }],
75
+ ['path', { d: 'M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8' }],
76
+ ['path', { d: 'M10 12h4' }]
77
+ ],
78
+ 'package': [
79
+ ['path', { d: 'M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z' }],
80
+ ['path', { d: 'M12 22V12' }],
81
+ ['path', { d: 'm3.3 7 7.703 4.734a2 2 0 0 0 1.994 0L20.7 7' }],
82
+ ['path', { d: 'm7.5 4.27 9 5.15' }]
83
+ ],
84
+ 'clipboard-check': [
85
+ ['rect', { width: 8, height: 4, x: 8, y: 2, rx: 1, ry: 1 }],
86
+ ['path', { d: 'M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2' }],
87
+ ['path', { d: 'm9 14 2 2 4-4' }]
88
+ ],
89
+ 'clipboard-x': [
90
+ ['rect', { width: 8, height: 4, x: 8, y: 2, rx: 1, ry: 1 }],
91
+ ['path', { d: 'M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2' }],
92
+ ['path', { d: 'm15 11-6 6' }],
93
+ ['path', { d: 'm9 11 6 6' }]
94
+ ],
95
+ 'scale': [
96
+ ['path', { d: 'm16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z' }],
97
+ ['path', { d: 'm2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z' }],
98
+ ['path', { d: 'M7 21h10' }],
99
+ ['path', { d: 'M12 3v18' }],
100
+ ['path', { d: 'M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2' }]
101
+ ],
102
+ 'users': [
103
+ ['path', { d: 'M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2' }],
104
+ ['circle', { cx: 9, cy: 7, r: 4 }],
105
+ ['path', { d: 'M22 21v-2a4 4 0 0 0-3-3.87' }],
106
+ ['path', { d: 'M16 3.13a4 4 0 0 1 0 7.75' }]
107
+ ],
108
+ 'bot': [
109
+ ['path', { d: 'M12 8V4H8' }],
110
+ ['rect', { width: 16, height: 12, x: 4, y: 8, rx: 2 }],
111
+ ['path', { d: 'M2 14h2' }],
112
+ ['path', { d: 'M20 14h2' }],
113
+ ['path', { d: 'M15 13v2' }],
114
+ ['path', { d: 'M9 13v2' }]
115
+ ],
116
+ 'user': [
117
+ ['path', { d: 'M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2' }],
118
+ ['circle', { cx: 12, cy: 7, r: 4 }]
119
+ ],
120
+ 'chevron-down': [
121
+ ['path', { d: 'm6 9 6 6 6-6' }]
122
+ ],
123
+ 'chevron-right': [
124
+ ['path', { d: 'm9 18 6-6-6-6' }]
125
+ ],
126
+ 'settings': [
127
+ ['path', { d: 'M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z' }],
128
+ ['circle', { cx: 12, cy: 12, r: 3 }]
129
+ ],
130
+ 'rocket': [
131
+ ['path', { d: 'M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z' }],
132
+ ['path', { d: 'm12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z' }],
133
+ ['path', { d: 'M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0' }],
134
+ ['path', { d: 'M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5' }]
135
+ ],
136
+ 'flask-conical': [
137
+ ['path', { d: 'M10 2v7.527a2 2 0 0 1-.211.896L4.72 20.55a1 1 0 0 0 .9 1.45h12.76a1 1 0 0 0 .9-1.45l-5.069-10.127A2 2 0 0 1 14 9.527V2' }],
138
+ ['path', { d: 'M8.5 2h7' }],
139
+ ['path', { d: 'M7 16h10' }]
140
+ ],
141
+ 'file-text': [
142
+ ['path', { d: 'M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z' }],
143
+ ['path', { d: 'M14 2v4a2 2 0 0 0 2 2h4' }],
144
+ ['path', { d: 'M16 13H8' }],
145
+ ['path', { d: 'M16 17H8' }],
146
+ ['path', { d: 'M10 9H8' }]
147
+ ],
148
+ 'message-square': [
149
+ ['path', { d: 'M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z' }]
150
+ ],
151
+ 'square-check-big': [
152
+ ['path', { d: 'M21 10.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h12.5' }],
153
+ ['path', { d: 'm9 11 3 3L22 4' }]
154
+ ],
155
+ 'square': [
156
+ ['rect', { width: 18, height: 18, x: 3, y: 3, rx: 2 }]
157
+ ],
158
+ 'stop-circle': [
159
+ ['circle', { cx: 12, cy: 12, r: 10 }],
160
+ ['rect', { width: 6, height: 6, x: 9, y: 9 }]
161
+ ]
162
+ }
163
+ // 图标+文字内联组合的快捷渲染
164
+ function icText(name, size, text) { return React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 3 } }, ic(name, size), text) }
165
+ function ic(name, size) {
166
+ var def = ICONS[name]
167
+ if (!def) return null
168
+ return React.createElement('svg', {
169
+ width: size || 14, height: size || 14, viewBox: '0 0 24 24',
170
+ fill: 'none', stroke: 'currentColor', strokeWidth: 2,
171
+ strokeLinecap: 'round', strokeLinejoin: 'round',
172
+ style: { display: 'inline-block', verticalAlign: '-2px', flexShrink: 0 }
173
+ }, def.map(function (p, i) { return React.createElement(p[0], Object.assign({ key: i }, p[1])) }))
174
+ }
175
+ var COLUMNS = ['draft', 'pending', 'in-progress', 'verifying', 'resolved', 'blocked']
176
+ var state = { sessionId: null, tasks: [], boardMode: 'auto', teamMode: false, minWorkers: 1, maxWorkers: 3, minVerifiers: 0, maxVerifiers: 2, workerModel: '', verifierModel: '', isRoot: 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, archSort: 'time-desc' }
177
+ // #16 快捷键:Esc 逐级关闭(详情→看板→面板);输入框聚焦时不劫持
178
+ try {
179
+ var onKey = function (e) {
180
+ if (e.key !== 'Escape') return
181
+ var ae = document.activeElement
182
+ if (ae && (ae.tagName === 'INPUT' || ae.tagName === 'TEXTAREA' || ae.tagName === 'SELECT')) return
183
+ if (!state.open) return
184
+ if (state.detailId) { state.detailId = null; notify() }
185
+ else if (state.selectMode) { state.selectMode = false; state.selected = {}; notify() }
186
+ else { state.open = false; notify() }
187
+ e.stopPropagation()
188
+ }
189
+ document.addEventListener('keydown', onKey, true)
190
+ ctx.effect(function () { return function () { document.removeEventListener('keydown', onKey, true) } })
191
+ } catch (_) {}
192
+ // 注入 escalation 脉冲 + critical 辉光动画样式
193
+ try { var stEl = document.createElement('style'); stEl.textContent = '@keyframes tskb-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.55;transform:scale(1.12)}}@keyframes tskb-crit{0%,100%{box-shadow:0 0 0 0 rgba(239,68,68,.5)}50%{box-shadow:0 0 12px 2px rgba(239,68,68,.22)}}'; document.head.appendChild(stEl); ctx.effect(function () { return function () { try { stEl.remove() } catch (_) {} } }) } catch (_) {}
194
+ var listeners = []
195
+ function notify() { listeners.forEach(function (fn) { try { fn(state) } catch (_) {} }) }
196
+ function rpc(method, args) { var a = args || {}; a.sessionId = state.sessionId; return fetch('/dsh-agent-board', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ method: method, args: a }) }).then(function (r) { return r.json() }) }
197
+ function fetchTasks() {
198
+ if (!state.sessionId) return
199
+ rpc('get-tasks').then(function (d) {
200
+ state.tasks = (d && d.tasks) || []
201
+ state.boardMode = (d && d.boardMode) || 'auto'
202
+ state.teamMode = !!(d && d.teamMode)
203
+ state.minWorkers = (d && d.minWorkers) || 1
204
+ state.maxWorkers = (d && d.maxWorkers) || 3
205
+ state.minVerifiers = (d && d.minVerifiers) || 0
206
+ state.maxVerifiers = (d && d.maxVerifiers) || 2
207
+ state.verifierModel = (d && d.verifierModel) || ''
208
+ state.workerModel = (d && d.workerModel) || ''
209
+ state.poolStatus = (d && d.poolStatus) || null
210
+ state.isRoot = !d || d.isRoot !== false
211
+ if (state.isRoot === false && state.open) { state.open = false; state.detailId = null } // 子代理会话:强制收起看板
212
+ if (d && d.dispatchInfo) { state.dispatchInfo = d.dispatchInfo }
213
+ // escalation 一等公民:出现新的待裁决任务 → 面板自动弹开直达该任务详情
214
+ var newEsc = state.tasks.filter(function (t) { return t.escalation && state.escalatedIds.indexOf(t.id) < 0 })
215
+ state.escalatedIds = state.tasks.filter(function (t) { return t.escalation }).map(function (t) { return t.id })
216
+ if (newEsc.length > 0) { state.open = true; state.view = 'board'; state.detailId = newEsc[0].id }
217
+ notify()
218
+ }).catch(function () {})
219
+ }
220
+ function fetchChildren() { if (!state.sessionId) return; rpc('list-children').then(function (d) { state.children = (d && d.children) || []; notify() }).catch(function () {}) }
221
+ var __timer = ctx.get('timer')
222
+ // 轮询:get-tasks 已是纯读(v71),3s 开销低;面板关闭时也轮询以更新角标
223
+ if (__timer) ctx.effect(function () { return __timer.interval(fetchTasks, 3000) })
224
+ function readLayoutOffsets() {
225
+ try {
226
+ var frames = document.querySelectorAll('[data-sidebar-collapsed], [data-details-collapsed]')
227
+ for (var i = 0; i < frames.length; i++) {
228
+ var gtc = getComputedStyle(frames[i]).gridTemplateColumns
229
+ if (gtc) { var parts = gtc.split(/\s+/); return { sidebar: parseInt(parts[0]) || 0, details: parts.length >= 3 ? (parseInt(parts[parts.length - 1]) || 0) : 0 } }
230
+ }
231
+ var all = document.querySelectorAll('[style*="grid-template-columns"]')
232
+ for (var j = 0; j < all.length; j++) { var s = all[j].style.gridTemplateColumns; var m = s.match(/^(\d+)px/); var m2 = s.match(/(\d+)px$/); return { sidebar: m ? parseInt(m[1]) : 280, details: m2 ? parseInt(m2[1]) : 0 } }
233
+ } catch (_) {}
234
+ return { sidebar: 280, details: 0 }
235
+ }
236
+ function syncLayout() { var o = readLayoutOffsets(); if (o.sidebar !== state.layoutLeft || o.details !== state.layoutRight) { state.layoutLeft = o.sidebar; state.layoutRight = o.details; notify() } }
237
+ // 去抖:切会话时 body 子树 style 大面积变动会触发回调风暴,300ms 合并一次
238
+ var layoutTimer = null
239
+ function syncLayoutDebounced() { if (layoutTimer) return; layoutTimer = setTimeout(function () { layoutTimer = null; syncLayout() }, 300) }
240
+ ctx.effect(function () { return function () { if (layoutTimer) { clearTimeout(layoutTimer); layoutTimer = null } } })
241
+ try { var ro = new ResizeObserver(function () { syncLayoutDebounced() }); ro.observe(document.body); ctx.effect(function () { return function () { ro.disconnect() } }) } catch (_) {}
242
+ try { var mo = new MutationObserver(function () { syncLayoutDebounced() }); mo.observe(document.body, { attributes: true, attributeFilter: ['style'], subtree: true }); ctx.effect(function () { return function () { mo.disconnect() } }) } catch (_) {}
243
+ syncLayout()
244
+ function ago(iso) { if (!iso) return ''; var ms = Date.now() - new Date(iso).getTime(); if (ms < 60000) return '刚刚'; if (ms < 3600000) return Math.floor(ms / 60000) + ' 分钟前'; if (ms < 86400000) return Math.floor(ms / 3600000) + ' 小时前'; return Math.floor(ms / 86400000) + ' 天前' }
245
+ function fmtTime(iso) { if (!iso) return '-'; try { return new Date(iso).toLocaleString() } catch (_) { return iso } }
246
+ function getTask(id) { return state.tasks.find(function (t) { return t.id === id }) }
247
+ function shortId(sid) { return sid ? String(sid).slice(0, 14) : '' }
248
+ function jumpTo(actorId) { if (sessionsSvc && actorId && actorId !== 'system' && actorId !== 'unknown' && actorId !== 'auto-dispatch') sessionsSvc.open(actorId) }
249
+ function transition(taskId, from, to) {
250
+ if (from === to) return
251
+ var ok = function () { fetchTasks() }; var fail = function () { fetchTasks() }
252
+ if (to === 'pending' && from === 'draft') rpc('update-task', { taskId: taskId, publish: true }).then(ok).catch(fail)
253
+ else if (to === 'pending' && from === 'blocked') rpc('update-task', { taskId: taskId, resetToPending: true }).then(ok).catch(fail) // 阻塞任务拖回待办=重新投放
254
+ else if (to === 'in-progress' && (from === 'pending' || from === 'blocked')) rpc('claim-task', { taskId: taskId }).then(ok).catch(fail)
255
+ else if (to === 'verifying' && from === 'in-progress') { var res = window.prompt('提交验证 — 解决说明(必填):'); if (res) rpc('resolve-task', { taskId: taskId, status: 'verifying', resolution: res }).then(ok).catch(fail) }
256
+ else if (to === 'resolved' && from === 'verifying') rpc('verify-task', { taskId: taskId, verdict: 'approved' }).then(ok).catch(fail)
257
+ else if (to === 'in-progress' && from === 'verifying') { var c = window.prompt('驳回原因(可选):'); rpc('verify-task', { taskId: taskId, verdict: 'rejected', comment: c || '' }).then(ok).catch(fail) }
258
+ else if (to === 'blocked' && from === 'in-progress') { var r = window.prompt('阻塞原因(可选):') || ''; rpc('resolve-task', { taskId: taskId, status: 'blocked', resolution: r }).then(ok).catch(fail) }
259
+ else if (to === 'archived' && from === 'resolved') rpc('archive-task', { taskId: taskId }).then(ok).catch(fail)
260
+ }
261
+ function onDragStart(e, task) { e.dataTransfer.setData('text/plain', JSON.stringify({ id: task.id, status: task.status })); e.dataTransfer.effectAllowed = 'move'; state.dragTask = task.id; notify() }
262
+ function onDragEnd() { state.dragTask = null; state.dragOver = null; notify() }
263
+ function onColDragOver(e, st) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; if (state.dragOver !== st) { state.dragOver = st; notify() } }
264
+ function onColDragLeave(st) { if (state.dragOver === st) { state.dragOver = null; notify() } }
265
+ function onColDrop(e, st) { e.preventDefault(); try { var data = JSON.parse(e.dataTransfer.getData('text/plain')); state.dragOver = null; state.dragTask = null; transition(data.id, data.status, st); notify() } catch (_) {} }
266
+ function computeStats(tasks) {
267
+ var total = tasks.length, byStatus = {}, byPriority = {}, byAgent = {}, completionTimes = [], verifyTimes = [], todayDone = 0, todayStart = new Date(); todayStart.setHours(0, 0, 0, 0); var recentActivity = []
268
+ var dailyDone = [] // #15 近 7 天每日完成趋势
269
+ for (var di = 6; di >= 0; di--) { var dayStart = new Date(todayStart); dayStart.setDate(dayStart.getDate() - di); var dayEnd = new Date(dayStart); dayEnd.setDate(dayEnd.getDate() + 1); dailyDone.push({ label: (dayStart.getMonth() + 1) + '/' + dayStart.getDate(), count: 0, start: dayStart, end: dayEnd }) }
270
+ tasks.forEach(function (t) { byStatus[t.status] = (byStatus[t.status] || 0) + 1; byPriority[t.priority || 'medium'] = (byPriority[t.priority || 'medium'] || 0) + 1; if (t.claimedBy && (t.status === 'in-progress' || t.status === 'verifying')) { if (!byAgent[t.claimedBy]) byAgent[t.claimedBy] = 0; byAgent[t.claimedBy]++ } if (t.resolvedAt && t.createdAt) completionTimes.push(new Date(t.resolvedAt) - new Date(t.createdAt)); if (t.verifiedAt && t.resolvedAt) verifyTimes.push(new Date(t.verifiedAt) - new Date(t.resolvedAt)); if (t.resolvedAt) { var rd = new Date(t.resolvedAt); if (rd >= todayStart) todayDone++; for (var k = 0; k < dailyDone.length; k++) { if (rd >= dailyDone[k].start && rd < dailyDone[k].end) { dailyDone[k].count++; break } } } if (Array.isArray(t.history)) t.history.forEach(function (h) { recentActivity.push({ taskId: t.id, title: t.title, from: h.from, to: h.to, actor: h.actor, timestamp: h.timestamp, note: h.note }) }) })
271
+ recentActivity.sort(function (a, b) { return (b.timestamp || '').localeCompare(a.timestamp || '') }); recentActivity = recentActivity.slice(0, 10)
272
+ function avgMs(arr) { if (arr.length === 0) return null; var s = arr.reduce(function (a, b) { return a + b }, 0); return Math.round(s / arr.length) }
273
+ function fmtMs(ms) { if (!ms) return '-'; var m = Math.floor(ms / 60000); if (m < 60) return m + ' 分钟'; var h = Math.floor(m / 60); if (h < 24) return h + ' 小时 ' + (m % 60) + ' 分'; return Math.floor(h / 24) + ' 天 ' + (h % 24) + ' 时' }
274
+ return { total: total, byStatus: byStatus, byPriority: byPriority, byAgent: byAgent, avgCompletion: fmtMs(avgMs(completionTimes)), avgVerify: fmtMs(avgMs(verifyTimes)), todayDone: todayDone, dailyDone: dailyDone, recentActivity: recentActivity }
275
+ }
276
+ function TrendChart(props) {
277
+ var data = props.data || []
278
+ var max = 1; data.forEach(function (d) { if (d.count > max) max = d.count })
279
+ return React.createElement('div', { style: { padding: '8px 10px', background: C.card, border: '1px solid ' + C.border, borderRadius: 6, marginBottom: 12 } },
280
+ React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 6 } }, '近 7 天完成趋势'),
281
+ React.createElement('div', { style: { display: 'flex', alignItems: 'flex-end', gap: 4, height: 40 } },
282
+ data.map(function (d, i) {
283
+ var h = d.count > 0 ? Math.max(10, Math.round(d.count / max * 36)) : 3
284
+ return React.createElement('div', { key: i, style: { flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'flex-end', gap: 1 }, title: d.label + ': ' + d.count + ' 个完成' },
285
+ d.count > 0 ? React.createElement('span', { style: { fontSize: 8, color: C.text2 } }, String(d.count)) : null,
286
+ React.createElement('div', { style: { width: '100%', maxWidth: 28, height: h + 'px', borderRadius: 2, background: d.count > 0 ? C.ok : C.nested, transition: 'height .3s' } }),
287
+ React.createElement('span', { style: { fontSize: 8, color: C.text2 } }, d.label))
288
+ })))
289
+ }
290
+ function StatCard(props) { return React.createElement('div', { style: { flex: '1 1 0', minWidth: 80, padding: '8px 10px', background: C.card, border: '1px solid ' + C.border, borderRadius: 6, textAlign: 'center' } }, React.createElement('div', { style: { fontSize: 20, fontWeight: 700, color: props.color || C.text } }, String(props.value)), React.createElement('div', { style: { fontSize: 10, color: C.text2, marginTop: 2 } }, props.label)) }
291
+ function BarRow(props) { var pct = props.total > 0 ? (props.count / props.total * 100) : 0; return React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 } }, React.createElement('span', { style: { width: 40, fontSize: 10, color: C.text2, textAlign: 'right', flexShrink: 0 } }, props.label), React.createElement('div', { style: { flex: 1, height: 8, background: C.nested, borderRadius: 4, overflow: 'hidden' } }, React.createElement('div', { style: { height: '100%', width: pct + '%', background: props.color || C.brand, borderRadius: 4, transition: 'width .3s' } })), React.createElement('span', { style: { width: 24, fontSize: 10, color: C.text2, flexShrink: 0 } }, String(props.count))) }
292
+ function Dashboard() {
293
+ var stats = computeStats(state.tasks); var statusOrder = ['pending', 'in-progress', 'verifying', 'resolved', 'blocked', 'archived']; var prioOrder = ['critical', 'high', 'medium', 'low']
294
+ return React.createElement('div', null,
295
+ React.createElement('div', { style: { display: 'flex', gap: 6, marginBottom: 12, flexWrap: 'wrap' } }, React.createElement(StatCard, { label: '总任务', value: stats.total, color: C.text }), React.createElement(StatCard, { label: '待办', value: stats.byStatus['pending'] || 0, color: C.text2 }), React.createElement(StatCard, { label: '进行中', value: stats.byStatus['in-progress'] || 0, color: C.brand }), React.createElement(StatCard, { label: '验证中', value: stats.byStatus['verifying'] || 0, color: C.warn }), React.createElement(StatCard, { label: '已完成', value: stats.byStatus['resolved'] || 0, color: C.ok }), React.createElement(StatCard, { label: '已归档', value: stats.byStatus['archived'] || 0, color: C.text2 })),
296
+ React.createElement('div', { style: { display: 'flex', gap: 12, marginBottom: 12, flexWrap: 'wrap' } },
297
+ React.createElement('div', { style: { flex: '1 1 0', minWidth: 200, padding: '8px 10px', background: C.card, border: '1px solid ' + C.border, borderRadius: 6 } }, React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 6 } }, '按状态分布'), statusOrder.map(function (s) { return React.createElement(BarRow, { key: s, label: statusLabels[s] || s, count: stats.byStatus[s] || 0, total: stats.total, color: statusColors[s] || C.brand }) })),
298
+ React.createElement('div', { style: { flex: '1 1 0', minWidth: 200, padding: '8px 10px', background: C.card, border: '1px solid ' + C.border, borderRadius: 6 } }, React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 6 } }, '按优先级分布'), prioOrder.map(function (p) { return React.createElement(BarRow, { key: p, label: prioLabel[p] || p, count: stats.byPriority[p] || 0, total: stats.total, color: prioColor[p] || C.brand }) }))),
299
+ React.createElement('div', { style: { display: 'flex', gap: 6, marginBottom: 12, flexWrap: 'wrap' } }, React.createElement(StatCard, { label: '平均完成时间', value: stats.avgCompletion }), React.createElement(StatCard, { label: '平均验证时间', value: stats.avgVerify }), React.createElement(StatCard, { label: '今日完成', value: stats.todayDone, color: C.ok })),
300
+ React.createElement(TrendChart, { data: stats.dailyDone }),
301
+ Object.keys(stats.byAgent).length > 0 ? React.createElement('div', { style: { padding: '8px 10px', background: C.card, border: '1px solid ' + C.border, borderRadius: 6, marginBottom: 12 } }, React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 6 } }, 'Agent 负载(进行中+验证中)'), Object.keys(stats.byAgent).map(function (aid) { return React.createElement('div', { key: aid, style: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 3, fontSize: 11 } }, React.createElement(ActorLink, { id: aid }), React.createElement('span', { style: { color: C.text2 } }, stats.byAgent[aid] + ' 个任务')) })) : null,
302
+ React.createElement('div', { style: { padding: '8px 10px', background: C.card, border: '1px solid ' + C.border, borderRadius: 6 } }, React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 6 } }, '最近活跃'), stats.recentActivity.length === 0 ? React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '暂无记录') : stats.recentActivity.map(function (a, i) { return React.createElement('div', { key: i, style: { fontSize: 10, color: C.text2, marginBottom: 3, display: 'flex', gap: 4, alignItems: 'center' } }, React.createElement('span', { style: { color: statusColors[a.to] || C.brand, fontWeight: 600 } }, statusLabels[a.to] || a.to), React.createElement('span', { style: { color: C.text, maxWidth: 120, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, a.title), React.createElement(ActorLink, { id: a.actor }), React.createElement('span', null, ago(a.timestamp))) })))
303
+ }
304
+ function BoardButton(props) {
305
+ var _R = React; var useState = _R.useState, useEffect = _R.useEffect
306
+ var _a = useState(0), pendingCount = _a[0], setPendingCount = _a[1]; var _b = useState(false), isOpen = _b[0], setIsOpen = _b[1]; var _c = useState(0), escCount = _c[0], setEscCount = _c[1]; var _d2 = useState(true), isRoot = _d2[0], setIsRoot = _d2[1]
307
+ useEffect(function () { if (props && props.sessionId) { var sid = String(props.sessionId); if (state.sessionId !== sid) { state.sessionId = sid; fetchTasks(); fetchChildren() } } }, [props && props.sessionId])
308
+ useEffect(function () { function update() { var n = 0, e = 0; for (var i = 0; i < state.tasks.length; i++) { if (state.tasks[i].status === 'pending') n++; if (state.tasks[i].escalation) e++ }; setPendingCount(n); setEscCount(e); setIsOpen(state.open); setIsRoot(state.isRoot) }; listeners.push(update); update(); return function () { var i = listeners.indexOf(update); if (i >= 0) listeners.splice(i, 1) } }, [])
309
+ if (!isRoot) return null // 子代理会话不显示看板入口
310
+ 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: '#fff', 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: '#fff', fontSize: 10, fontWeight: 600, display: 'inline-flex', alignItems: 'center', justifyContent: 'center' } }, String(pendingCount)) : null)
311
+ }
312
+ function ModeSwitch(props) { var mode = props.mode; var teamOn = props.teamOn; function pick(m) { if (teamOn && m === 'manual') return; rpc('set-board-mode', { mode: m }).then(fetchTasks).catch(function () {}) } var btnBase = { fontSize: 12, padding: '4px 14px', border: 'none', cursor: 'pointer', fontWeight: 500, flex: 1, textAlign: 'center', borderRadius: 6, transition: 'all .15s' }; var manDis = teamOn; return React.createElement('div', { style: { display: 'inline-flex', borderRadius: 8, border: '1px solid ' + C.border, overflow: 'hidden', background: C.card } }, React.createElement('button', { onClick: function () { pick('auto') }, style: Object.assign({}, btnBase, mode === 'auto' ? { background: C.brand, color: '#fff' } : { background: 'transparent', color: C.text2 }) }, icText('bot', 12, '自动')), React.createElement('button', { onClick: function () { pick('manual') }, disabled: manDis, title: manDis ? 'Team 模式开启时不可切换到手动' : '', style: Object.assign({}, btnBase, mode === 'manual' ? { background: C.brand, color: '#fff' } : { background: 'transparent', color: manDis ? C.text2 + '88' : C.text2 }, manDis ? { cursor: 'not-allowed', opacity: 0.5 } : {}) }, icText('user', 12, '手动'))) }
313
+ function TeamSwitch(props) { var on = props.on; function toggle() { rpc('set-team-mode', { enabled: !on }).then(fetchTasks).catch(function () {}) } return React.createElement('button', { onClick: toggle, title: on ? 'Team 模式已开启:任务走看板派发,Worker 歧义自动上报主窗口裁决(不拦截主窗口编辑)' : '开启 Team 模式:任务全走看板,Worker 歧义上报主窗口裁决', style: { fontSize: 12, padding: '4px 10px', border: '1px solid ' + (on ? C.brand : C.border), borderRadius: 8, cursor: 'pointer', fontWeight: 500, background: on ? C.brand : 'transparent', color: on ? '#fff' : C.text2, transition: 'all .15s' } }, icText('users', 12, 'Team' + (on ? ' ON' : ''))) }
314
+ function ViewTab() { var btnBase = { fontSize: 11, padding: '3px 10px', border: 'none', cursor: 'pointer', fontWeight: 500, borderRadius: 5, transition: 'all .15s' }; 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: '#fff' } : { 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: '#fff' } : { 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: '#fff' } : { background: 'transparent', color: C.text2 }) }, icText('bar-chart-3', 12, '仪表盘'))) }
315
+ function PoolCfg(props) { var _R = React; var useState = _R.useState, useEffect = _R.useEffect; var _a = useState(String(props.value)), val = _a[0], setVal = _a[1]; var _b = useState(false), dirty = _b[0], setDirty = _b[1]; useEffect(function () { setVal(String(props.value)); setDirty(false) }, [props.value]); function commit(v) { var n = parseInt(v, 10); if (!isNaN(n) && n >= 0 && n <= 10) { setVal(String(n)); setDirty(false); rpc('set-board-config', { key: props.cfgKey, value: n }).then(fetchTasks).catch(function () {}) } } function step(d) { var n = parseInt(val, 10) || 0; commit(String(Math.max(0, Math.min(10, n + d)))) } var miniBtn = { fontSize: 9, padding: '0px 4px', border: '1px solid ' + C.border, borderRadius: 2, background: C.nested, color: C.text2, cursor: 'pointer', lineHeight: '14px' }; return React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 1, fontSize: 9, color: C.text2 } }, props.label, React.createElement('button', { onClick: function () { step(-1) }, title: '减 1', style: miniBtn }, '−'), React.createElement('input', { value: val, onChange: function (e) { setVal(e.target.value); setDirty(e.target.value !== String(props.value)) }, onBlur: function () { if (dirty) commit(val) }, onKeyDown: function (e) { if (e.key === 'Enter') commit(val) }, style: { width: 22, padding: '0px 2px', fontSize: 9, textAlign: 'center', border: '1px solid ' + (dirty ? C.brand : C.border), borderRadius: 2, background: C.card, color: C.text } }), React.createElement('button', { onClick: function () { step(1) }, title: '加 1', style: miniBtn }, '+'), dirty ? React.createElement('button', { onClick: function () { commit(val) }, title: '应用', style: { fontSize: 9, padding: '0px 5px', border: 'none', borderRadius: 2, background: C.brand, color: '#fff', cursor: 'pointer', lineHeight: '14px', fontWeight: 600, display: 'inline-flex', alignItems: 'center' } }, ic('check', 9)) : null) }
316
+ function PoolStatus() {
317
+ var ps = state.poolStatus
318
+ if (!ps) return null
319
+ var wActive = (ps.workers || []).filter(function (w) { return w.busy }).length
320
+ var wTotal = (ps.workers || []).length
321
+ var vActive = (ps.verifiers || []).filter(function (v) { return v.busy }).length
322
+ var vTotal = (ps.verifiers || []).length
323
+ return React.createElement('span', { style: { fontSize: 9, color: C.text2, display: 'inline-flex', gap: 4, alignItems: 'center' } },
324
+ React.createElement('span', { title: 'Worker 池', style: { display: 'inline-flex', alignItems: 'center', gap: 2 } }, ic('zap', 10), wActive + '/' + wTotal),
325
+ React.createElement('span', { title: 'Verifier 池', style: { display: 'inline-flex', alignItems: 'center', gap: 2 } }, ic('check', 10), vActive + '/' + vTotal))
326
+ }
327
+ // #11 头部减负:池配置收纳进 ⚙️ 弹出层(含 #17 verifier 异构模型设置)
328
+ function ModelCfg(props) {
329
+ var _R = React; var useState = _R.useState, useEffect = _R.useEffect
330
+ var _a = useState(props.value || ''), val = _a[0], setVal = _a[1]
331
+ var _b = useState(false), dirty = _b[0], setDirty = _b[1]
332
+ var _m = useState(null), models = _m[0], setModels = _m[1]
333
+ var cfgKey = props.cfgKey || 'verifierModel'
334
+ var label = props.label || 'V模型'
335
+ // 下拉列出当前网关可用模型(host list-models RPC);枚举失败降级为文本输入
336
+ useEffect(function () {
337
+ var cancelled = false
338
+ rpc('list-models').then(function (r) { if (!cancelled && r && r.ok && Array.isArray(r.models) && r.models.length) setModels(r.models) }).catch(function () {})
339
+ return function () { cancelled = true }
340
+ }, [])
341
+ useEffect(function () { setVal(props.value || ''); setDirty(false) }, [props.value])
342
+ function save() { setDirty(false); rpc('set-board-config', { key: cfgKey, value: (val || '').trim() }).then(fetchTasks).catch(function () {}) }
343
+ function pick(v) { setVal(v); setDirty(false); rpc('set-board-config', { key: cfgKey, value: v }).then(fetchTasks).catch(function () {}) }
344
+ if (models) {
345
+ return React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 3, fontSize: 9, color: C.text2 } }, label,
346
+ React.createElement('select', { value: val, onChange: function (e) { pick(e.target.value) }, title: label + '(空=继承父级)', style: { width: 150, padding: '0px 4px', fontSize: 9, border: '1px solid ' + C.border, borderRadius: 2, background: C.card, color: C.text } },
347
+ React.createElement('option', { value: '' }, '继承父级'),
348
+ models.map(function (m) { return React.createElement('option', { key: m.id, value: m.id }, m.name + ' (' + m.provider + ')') })))
349
+ }
350
+ // 降级:文本输入(模型枚举不可用时)
351
+ return React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 3, fontSize: 9, color: C.text2 } }, label,
352
+ React.createElement('input', { value: val, onChange: function (e) { setVal(e.target.value); setDirty(e.target.value !== (props.value || '')) }, onBlur: function () { if (dirty) save() }, onKeyDown: function (e) { if (e.key === 'Enter') save() }, placeholder: '空=同父级', style: { width: 110, padding: '0px 4px', fontSize: 9, border: '1px solid ' + (dirty ? C.brand : C.border), borderRadius: 2, background: C.card, color: C.text } }),
353
+ dirty ? React.createElement('button', { onClick: save, title: '应用', style: { fontSize: 9, padding: '0px 5px', border: 'none', borderRadius: 2, background: C.brand, color: '#fff', cursor: 'pointer', lineHeight: '14px', fontWeight: 600, display: 'inline-flex', alignItems: 'center' } }, ic('check', 9)) : null)
354
+ }
355
+ function PoolCfgPopover(props) {
356
+ var _R = React; var useState = _R.useState, useEffect = _R.useEffect, useRef = _R.useRef
357
+ var _a = useState(false), open = _a[0], setOpen = _a[1]
358
+ var ref = useRef(null)
359
+ useEffect(function () { if (!open) return; function onDown(e) { if (ref.current && !ref.current.contains(e.target)) setOpen(false) }; document.addEventListener('mousedown', onDown); return function () { document.removeEventListener('mousedown', onDown) } }, [open])
360
+ return React.createElement('span', { ref: ref, style: { position: 'relative', display: 'inline-flex' } },
361
+ React.createElement('button', { onClick: function () { setOpen(!open) }, title: '池配置', style: { fontSize: 12, padding: '3px 8px', border: '1px solid ' + (open ? C.brand : C.border), borderRadius: 6, cursor: 'pointer', background: open ? C.nested : 'transparent', color: C.text2, display: 'inline-flex', alignItems: 'center' } }, ic('settings', 13)),
362
+ open ? React.createElement('div', { style: { position: 'absolute', top: '100%', right: 0, marginTop: 4, padding: '8px 10px', background: C.card, border: '1px solid ' + C.border, borderRadius: 8, boxShadow: '0 4px 16px rgba(0,0,0,0.15)', zIndex: 10, whiteSpace: 'nowrap' } },
363
+ React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2, marginBottom: 5 } }, '并发上限(一次性派发,用完即销毁)'),
364
+ React.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center', marginBottom: 5 } },
365
+ React.createElement(PoolCfg, { label: 'W并发', cfgKey: 'maxWorkers', value: props.maxW }),
366
+ React.createElement(PoolCfg, { label: 'V并发', cfgKey: 'maxVerifiers', value: props.maxV })),
367
+ React.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center', marginBottom: 5 } },
368
+ React.createElement(ModelCfg, { label: 'W模型', cfgKey: 'workerModel', value: props.workerModel }),
369
+ React.createElement('span', { style: { fontSize: 9, color: C.text2 } }, '(Worker 执行模型)')),
370
+ React.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } },
371
+ React.createElement(ModelCfg, { label: 'V模型', cfgKey: 'verifierModel', value: props.verifierModel }),
372
+ React.createElement('span', { style: { fontSize: 9, color: C.text2 } }, '(Verifier 异构审查)'))) : null)
373
+ }
374
+ // #13 筛选:文本(标题/描述/ID)+ 优先级多选 + 标签
375
+ function passFilter(t) {
376
+ var q = state.filterQ.trim().toLowerCase()
377
+ if (q && (t.title + ' ' + (t.description || '') + ' ' + t.id).toLowerCase().indexOf(q) < 0) return false
378
+ if (state.filterPrio.length > 0 && state.filterPrio.indexOf(t.priority || 'medium') < 0) return false
379
+ if (state.filterTag && (t.tags || []).indexOf(state.filterTag) < 0) return false
380
+ return true
381
+ }
382
+ function allTags() { var s = {}; state.tasks.forEach(function (t) { (t.tags || []).forEach(function (g) { s[g] = 1 }) }); return Object.keys(s) }
383
+ // #18 依赖未满足判断(依赖不存在视为阻塞——创建时已校验,防御性兜底)
384
+ 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') }) }
385
+ // #19 管线档位元数据
386
+ var pipeMeta = { full: { icon: 'flask-conical', label: '全流程(执行+验证)' }, work: { icon: 'file-text', label: '免验证(只做不验)' }, direct: { icon: 'message-square', label: '主窗口直接处理' } }
387
+ function pipeOf(t) { return pipeMeta[t.pipeline] || pipeMeta.full }
388
+ // #14 批量操作条(#16 带一步撤销:快照操作前的 priority/status)
389
+ function BatchBar() {
390
+ var _R = React; var useState = _R.useState, useEffect = _R.useEffect
391
+ 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]
392
+ 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) } }, [])
393
+ if (!on) return null
394
+ var ids = Object.keys(state.selected)
395
+ 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) } }
396
+ function run(op, value) { var snap = snapshot(op); var payload = { ids: ids, op: op }; if (value !== undefined) payload.value = value; rpc('batch-op', payload).then(function (r) { state.selected = {}; state.undoSnapshot = (r && r.done > 0) ? snap : null; setMsg('✅ 已处理 ' + (r && r.done || 0) + ' 个' + (r && r.skipped && r.skipped.length ? ',跳过 ' + r.skipped.length : '')); fetchTasks() }).catch(function (e) { setMsg('⚠️ ' + String(e)) }) }
397
+ 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)) }) }
398
+ var btn = { fontSize: 10, padding: '3px 10px', borderRadius: 4, border: 'none', cursor: 'pointer', fontWeight: 600 }
399
+ 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 } },
400
+ React.createElement('span', { style: { fontSize: 11, color: C.text, fontWeight: 600 } }, '已选 ' + cnt + ' 项'),
401
+ React.createElement('button', { onClick: function () { run('archive') }, disabled: cnt === 0, style: Object.assign({}, btn, { background: C.text2, color: '#fff', display: 'inline-flex', alignItems: 'center', gap: 3 }) }, ic('archive', 11), '批量归档'),
402
+ ['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: '#fff' }) }, prioLabel[p]) }),
403
+ undo ? React.createElement('button', { onClick: doUndo, title: '撤销最近一次批量操作', style: Object.assign({}, btn, { background: C.brand, color: '#fff' }) }, '↩️ 撤销') : null,
404
+ msg ? React.createElement('span', { style: { fontSize: 10, color: C.text2 } }, msg) : null,
405
+ 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' } }, '清除选择'))
406
+ }
407
+ function FilterBar() {
408
+ var _R = React; var useState = _R.useState, useEffect = _R.useEffect
409
+ var _a = useState(state.filterQ), q = _a[0], setQ = _a[1]; var _b = useState(state.filterPrio), fp = _b[0], setFp = _b[1]; var _c = useState(state.filterTag), ft = _c[0], setFt = _c[1]
410
+ useEffect(function () { function update() { setQ(state.filterQ); setFp(state.filterPrio); setFt(state.filterTag) }; listeners.push(update); update(); return function () { var i = listeners.indexOf(update); if (i >= 0) listeners.splice(i, 1) } }, [])
411
+ function setQ2(v) { state.filterQ = v; notify() }
412
+ function togglePrio(p) { var i = state.filterPrio.indexOf(p); if (i >= 0) state.filterPrio.splice(i, 1); else state.filterPrio.push(p); notify() }
413
+ function clearAll() { state.filterQ = ''; state.filterPrio = []; state.filterTag = ''; notify() }
414
+ var hasFilter = q.trim() || fp.length > 0 || ft
415
+ var chipBase = { fontSize: 10, padding: '2px 8px', borderRadius: 10, border: '1px solid ' + C.border, cursor: 'pointer', background: 'transparent', color: C.text2, transition: 'all .15s' }
416
+ return React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 8, flexWrap: 'wrap' } },
417
+ 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.border, borderRadius: 4, background: C.card, color: C.text } }),
418
+ ['critical', 'high', 'medium', 'low'].map(function (p) { var on = fp.indexOf(p) >= 0; return React.createElement('button', { key: p, onClick: function () { togglePrio(p) }, style: Object.assign({}, chipBase, on ? { background: prioColor[p], color: '#fff', borderColor: prioColor[p] } : {}) }, prioLabel[p]) }),
419
+ 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,
420
+ 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)
421
+ }
422
+ function Card(props) { var t = props.task; var pc = prioColor[t.priority] || prioColor.low; var dragging = state.dragTask === t.id; var preview = (t.deliverable && t.deliverable.summary) || t.resolution; var critGlow = t.priority === 'critical' && !t.escalation; var sel = !!state.selected[t.id]; var pm = pipeOf(t); var depBlock = t.status === 'pending' && depsBlocked(t); return React.createElement('div', { draggable: !state.selectMode, onDragStart: function (e) { onDragStart(e, t) }, onDragEnd: onDragEnd, onClick: function () { if (state.selectMode) { if (state.selected[t.id]) delete state.selected[t.id]; else state.selected[t.id] = true; notify() } else { state.detailId = t.id; notify() } }, style: { border: '1px solid ' + (sel ? C.brand : (t.escalation ? C.err : (critGlow ? C.err : C.border))), borderRadius: 6, padding: '6px 8px', marginBottom: 6, background: sel ? C.nested : C.card, borderLeft: '3px solid ' + (t.escalation ? C.err : pc), cursor: state.selectMode ? 'pointer' : 'grab', fontSize: 12, opacity: dragging ? 0.4 : (depBlock ? 0.65 : 1), transition: 'opacity .15s', animation: critGlow ? 'tskb-crit 2s ease-in-out infinite' : 'none' } }, React.createElement('div', { style: { display: 'flex', alignItems: 'flex-start', gap: 4 } }, state.selectMode ? React.createElement('span', { style: { color: sel ? C.brand : C.text2, flexShrink: 0, marginTop: 1, display: 'inline-flex' } }, ic(sel ? 'square-check-big' : 'square', 12)) : null, React.createElement('div', { style: { fontWeight: 600, color: C.text, marginBottom: 2, wordBreak: 'break-word', flex: 1 } }, t.title), React.createElement('span', { style: { flexShrink: 0, marginTop: 1, display: 'inline-flex', color: C.text2 }, title: pm.label }, ic(pm.icon, 10)), React.createElement('span', { style: { fontSize: 9, padding: '1px 5px', borderRadius: 3, background: pc, color: '#fff', flexShrink: 0, marginTop: 1 } }, prioLabel[t.priority] || '中')), t.escalation ? React.createElement('div', { style: { fontSize: 10, color: C.err, fontWeight: 600, marginBottom: 2, display: 'flex', alignItems: 'center', gap: 3 } }, ic('alert-triangle', 10), '待裁决 — 点击查看疑问') : null, depBlock ? React.createElement('div', { style: { fontSize: 10, color: C.text2, marginBottom: 2 } }, '⛓ 被 ' + t.dependsOn.filter(function (id) { var d = getTask(id); return !d || (d.status !== 'resolved' && d.status !== 'archived') }).length + ' 个依赖阻塞') : null, t.stuckSince ? React.createElement('div', { style: { fontSize: 10, color: C.warn, fontWeight: 600, marginBottom: 2, animation: 'tskb-pulse 1.5s ease-in-out infinite' } }, '⏱ 疑似卡死 · ' + ago(t.stuckSince) + ' — 点击处理') : null, React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, t.status === 'in-progress' && t.claimedBy ? '⚡ ' + shortId(t.claimedBy) : '', t.assignee ? ' 👤→' + shortId(t.assignee) : '', ' ' + ago(t.createdAt)), t.status === 'verifying' && preview ? React.createElement('div', { style: { fontSize: 10, color: C.text2, marginTop: 3, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, '📝 ' + preview) : null) }
423
+ function ActorLink(props) { var id = props.id; if (!id || id === 'system' || id === 'unknown' || id === 'auto-dispatch') return React.createElement('span', { style: { color: C.text2 } }, id || '-'); return React.createElement('span', { onClick: function (e) { e.stopPropagation(); jumpTo(id) }, style: { color: C.brand, cursor: 'pointer', textDecoration: 'underline' }, title: '跳转到 ' + id }, shortId(id)) }
424
+ function MsgThread(props) {
425
+ var msgs = props.messages || []
426
+ if (msgs.length === 0) return null
427
+ var kindStyle = { escalation: { color: C.err, label: 'Worker 上报', icon: 'alert-triangle' }, arbitration: { color: C.brand, label: '主窗口裁决', icon: 'scale' }, intervention: { color: C.warn, label: '高优介入', icon: 'zap' } }
428
+ return React.createElement('div', { style: { marginBottom: 8 } },
429
+ React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 3 } }, '裁决对话 (' + msgs.length + ')'),
430
+ msgs.map(function (m, i) {
431
+ var ks = kindStyle[m.kind] || { color: C.text2, label: m.kind }
432
+ return React.createElement('div', { key: i, style: { marginBottom: 4, padding: '5px 8px', borderLeft: '2px solid ' + ks.color, background: C.nested, borderRadius: 4 } },
433
+ React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: ks.color, marginBottom: 2, display: 'inline-flex', alignItems: 'center', gap: 3 } }, ic(ks.icon, 10), ks.label + ' · ' + (m.by || '') + ' · ' + ago(m.at)),
434
+ React.createElement('div', { style: { fontSize: 10, color: C.text, whiteSpace: 'pre-wrap', maxHeight: 140, overflowY: 'auto' } }, m.text))
435
+ }))
436
+ }
437
+ function TeamView() {
438
+ var ps = state.poolStatus || { workers: [], verifiers: [] }
439
+ // v74 一次性模型:每个活跃卡片 = 一个在跑的 run(随任务结算销毁),没有常驻成员
440
+ function memberCard(m, role) {
441
+ var isW = role === 'worker'
442
+ var curTask = m.taskId ? getTask(m.taskId) : null
443
+ return React.createElement('div', { key: m.id, style: { minWidth: 170, padding: '8px 10px', background: C.card, border: '1px solid ' + C.border, borderRadius: 8, borderTop: '3px solid ' + (isW ? C.brand : C.warn) } },
444
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 4, marginBottom: 4 } },
445
+ React.createElement('span', { style: { display: 'inline-flex', color: isW ? C.brand : C.warn } }, ic(isW ? 'zap' : 'clipboard-check', 13)),
446
+ React.createElement('span', { style: { fontSize: 12, fontWeight: 700, color: C.text } }, (isW ? 'Worker' : 'Verifier') + ' · 执行中'),
447
+ React.createElement('span', { style: { marginLeft: 'auto', fontSize: 9, padding: '1px 6px', borderRadius: 3, background: C.brand, color: '#fff' } }, '忙碌')),
448
+ m.model ? React.createElement('div', { style: { fontSize: 9, color: C.warn, marginBottom: 3 }, title: '异构模型审查' }, '🧬 ' + m.model) : null,
449
+ 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),
450
+ React.createElement('div', { style: { fontSize: 9, color: C.text2, marginTop: 3 }, title: '一次性子代理会话' }, '会话 ' + String(m.runId || '').slice(0, 8) + '…(用完即销毁)'))
451
+ }
452
+ var ws = ps.workers || [], vs = ps.verifiers || []
453
+ return React.createElement('div', null,
454
+ React.createElement('div', { style: { fontSize: 12, fontWeight: 600, color: C.text, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 4 } }, ic('zap', 12), 'Worker 池 (' + ws.length + ')'),
455
+ ws.length === 0 ? React.createElement('div', { style: { fontSize: 10, color: C.text2, marginBottom: 10 } }, '暂无 Worker(有待办任务时自动扩容)') : React.createElement('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 12 } }, ws.map(function (m) { return memberCard(m, 'worker') })),
456
+ React.createElement('div', { style: { fontSize: 12, fontWeight: 600, color: C.text, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 4 } }, ic('clipboard-check', 12), 'Verifier 池 (' + vs.length + ')'),
457
+ vs.length === 0 ? React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '暂无 Verifier(有验证中任务时自动扩容)') : React.createElement('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } }, vs.map(function (m) { return memberCard(m, 'verifier') })))
458
+ }
459
+ // #18 依赖管理区块:列出依赖(状态+跳转+移除)+ 添加依赖下拉
460
+ function DepsSection(props) {
461
+ var task = props.task
462
+ var deps = Array.isArray(task.dependsOn) ? task.dependsOn : []
463
+ var candidates = state.tasks.filter(function (x) { return x.id !== task.id && x.status !== 'archived' && deps.indexOf(x.id) < 0 })
464
+ function saveDeps(next) { rpc('update-task', { taskId: task.id, dependsOn: next }).then(function (r) { if (r && r.ok === false) setMsg('⚠️ ' + (r.error || '失败')); fetchTasks() }).catch(function (e) { setMsg('⚠️ ' + String(e)) }) }
465
+ var _R = React; var useState = _R.useState
466
+ var _m = useState(''), msg = _m[0], setMsg = _m[1]
467
+ return React.createElement('div', { style: { marginBottom: 8, padding: '6px 8px', border: '1px solid ' + C.border, borderRadius: 6, background: C.card } },
468
+ React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 4 } }, '⛓ 依赖任务(全部完成后才会派发)' + (deps.length ? ' · ' + deps.length : '')),
469
+ deps.length === 0 ? React.createElement('div', { style: { fontSize: 10, color: C.text2, marginBottom: 4 } }, '无依赖 — 可立即派发') : deps.map(function (id) {
470
+ var dt = getTask(id)
471
+ var satisfied = dt && (dt.status === 'resolved' || dt.status === 'archived')
472
+ return React.createElement('div', { key: id, style: { display: 'flex', alignItems: 'center', gap: 5, marginBottom: 3, fontSize: 10 } },
473
+ React.createElement('span', { style: { padding: '0 5px', borderRadius: 3, fontSize: 9, background: satisfied ? C.ok : (statusColors[dt && dt.status] || C.text2), color: '#fff', flexShrink: 0 } }, dt ? (statusLabels[dt.status] || dt.status) : '不存在'),
474
+ React.createElement('span', { onClick: function () { if (dt) { state.detailId = id; notify() } }, style: { color: dt ? C.brand : C.text2, cursor: dt ? 'pointer' : 'default', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }, title: id }, dt ? dt.title : id),
475
+ React.createElement('span', { onClick: function () { saveDeps(deps.filter(function (x) { return x !== id })) }, title: '移除依赖', style: { cursor: 'pointer', color: C.text2, flexShrink: 0, display: 'inline-flex' } }, ic('x', 10)))
476
+ }),
477
+ candidates.length > 0 ? React.createElement('select', { value: '', onChange: function (e) { if (e.target.value) saveDeps(deps.concat([e.target.value])) }, style: { width: '100%', fontSize: 10, padding: '2px 4px', marginTop: 2, border: '1px solid ' + C.border, borderRadius: 4, background: C.card, color: C.text } },
478
+ React.createElement('option', { value: '' }, '+ 添加依赖…'),
479
+ candidates.map(function (x) { return React.createElement('option', { key: x.id, value: x.id }, x.title.slice(0, 36) + ' (' + (statusLabels[x.status] || x.status) + ')') })) : null,
480
+ msg ? React.createElement('div', { style: { fontSize: 10, color: C.err, marginTop: 3 } }, msg) : null)
481
+ }
482
+ function DetailView() {
483
+ var _R = React; var useState = _R.useState, useEffect = _R.useEffect; var task = getTask(state.detailId)
484
+ var _a = useState(task ? task.title : ''), editTitle = _a[0], setEditTitle = _a[1]; var _b = useState(task ? task.description || '' : ''), editDesc = _b[0], setEditDesc = _b[1]; var _c = useState(false), saving = _c[0], setSaving = _c[1]; var _d = useState(state.boardMode), mode = _d[0], setMode = _d[1]
485
+ var _e2 = useState(''), arbAnswer = _e2[0], setArbAnswer = _e2[1]; var _f2 = useState(''), interveneMsg = _f2[0], setInterveneMsg = _f2[1]; var _g2 = useState(''), actionMsg = _g2[0], setActionMsg = _g2[1]
486
+ useEffect(function () { function update() { setMode(state.boardMode) }; listeners.push(update); update(); return function () { var i = listeners.indexOf(update); if (i >= 0) listeners.splice(i, 1) } }, [])
487
+ if (!task) { state.detailId = null; return React.createElement('div', { style: { padding: 20, color: C.text2 } }, '任务不存在') }
488
+ function doAction(fn) { fn().then(fetchTasks).catch(function () {}) }
489
+ function saveEdit() { setSaving(true); rpc('update-task', { taskId: task.id, title: editTitle, description: editDesc, resetToPending: true }).then(function () { setSaving(false); fetchTasks() }).catch(function () { setSaving(false) }) }
490
+ function jumpToAgent() { if (sessionsSvc && task.claimedBy) sessionsSvc.open(task.claimedBy) }
491
+ function submitArbitration() { if (!arbAnswer.trim()) return; rpc('resolve-escalation', { taskId: task.id, answer: arbAnswer }).then(function (r) { setActionMsg(r && r.ok ? '✅ 裁决已转达给 Worker' : '⚠️ ' + ((r && r.error) || '失败')); setArbAnswer(''); fetchTasks() }).catch(function (e) { setActionMsg('⚠️ ' + String(e)) }) }
492
+ function doTerminate() { rpc('terminate-agent', { taskId: task.id }).then(function (r) { setActionMsg(r && r.ok ? '⏹ 已终止 ' + (r.terminated || '') + ',任务重新排队' : '⚠️ ' + ((r && r.error) || '无活动 Agent')); fetchTasks() }).catch(function (e) { setActionMsg('⚠️ ' + String(e)) }) }
493
+ function doDismiss() { rpc('dismiss-suspect', { taskId: task.id }).then(function () { setActionMsg('✅ 已清除卡死标记,继续观察'); fetchTasks() }).catch(function (e) { setActionMsg('⚠️ ' + String(e)) }) }
494
+ function submitIntervene() { if (!interveneMsg.trim()) return; rpc('intervene-agent', { taskId: task.id, message: interveneMsg }).then(function (r) { setActionMsg(r && r.ok ? '✅ 高优指令已插入 ' + shortId(r.agent) + ' 队首' : '⚠️ ' + ((r && r.error) || '无活动 Agent')); setInterveneMsg(''); fetchTasks() }).catch(function (e) { setActionMsg('⚠️ ' + String(e)) }) }
495
+ var isManual = task.assignMode === 'manual'; var canJump = (task.status === 'in-progress' || task.status === 'verifying') && task.claimedBy && task.claimedBy !== state.sessionId
496
+ var canIntervene = task.status === 'in-progress' || task.status === 'verifying'
497
+ return React.createElement('div', { style: { padding: '4px 2px' } },
498
+ React.createElement('div', { onClick: function () { state.detailId = null; notify() }, style: { fontSize: 11, color: C.brand, cursor: 'pointer', marginBottom: 8 } }, '← 返回看板'),
499
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 8 } }, React.createElement('span', { style: { fontSize: 10, padding: '1px 6px', borderRadius: 3, background: prioColor[task.priority] || prioColor.low, color: '#fff' } }, prioLabel[task.priority] || '中'), React.createElement('span', { style: { fontSize: 11, padding: '1px 8px', borderRadius: 3, background: C.nested, color: C.text } }, statusLabels[task.status] || task.status), React.createElement('select', { value: task.pipeline || 'full', onChange: function (e) { rpc('update-task', { taskId: task.id, pipeline: e.target.value }).then(fetchTasks).catch(function () {}) }, title: '管线档位', style: { fontSize: 10, padding: '1px 4px', border: '1px solid ' + C.border, borderRadius: 3, background: C.card, color: C.text2 } }, React.createElement('option', { value: 'full' }, '全流程(执行+验证)'), React.createElement('option', { value: 'work' }, '免验证(只做不验)'), React.createElement('option', { value: 'direct' }, '主窗口处理')), task.pipelineAuto ? React.createElement('span', { style: { fontSize: 9, color: C.text2 }, title: '由规则自动分类,可手动覆盖' }, 'auto') : null, isManual ? React.createElement('span', { style: { fontSize: 10, color: C.text2, display: 'inline-flex', alignItems: 'center', gap: 2 } }, ic('user', 10), '手动派发') : null),
500
+ task.escalation ? React.createElement('div', { style: { marginBottom: 8, padding: '8px 10px', border: '1px solid ' + C.err, borderRadius: 6, background: 'color-mix(in srgb, ' + C.err + ' 8%, transparent)' } },
501
+ React.createElement('div', { style: { fontSize: 12, fontWeight: 700, color: C.err, marginBottom: 4, display: 'flex', alignItems: 'center', gap: 4 } }, ic('alert-triangle', 13), 'Worker 上报歧义 — 等待主窗口裁决'),
502
+ React.createElement('div', { style: { fontSize: 11, color: C.text, marginBottom: 6, whiteSpace: 'pre-wrap', maxHeight: 160, overflowY: 'auto' } }, task.escalation.question),
503
+ React.createElement('div', { style: { fontSize: 10, color: C.text2, marginBottom: 6 } }, '上报于 ' + ago(task.escalation.at) + ' · ' + (task.escalation.by || '')),
504
+ React.createElement('textarea', { value: arbAnswer, onChange: function (e) { setArbAnswer(e.target.value) }, rows: 2, placeholder: '输入裁决指示,将直接转达给原 Worker(保有上下文)…', style: { width: '100%', fontSize: 11, padding: '5px 8px', marginBottom: 4, border: '1px solid ' + C.err, borderRadius: 4, background: C.card, color: C.text, resize: 'vertical', boxSizing: 'border-box', fontFamily: 'inherit' } }),
505
+ React.createElement('button', { onClick: submitArbitration, disabled: !arbAnswer.trim(), style: { fontSize: 11, padding: '4px 12px', border: 'none', borderRadius: 4, background: C.err, color: '#fff', cursor: 'pointer', fontWeight: 600, display: 'inline-flex', alignItems: 'center', gap: 4 } }, ic('scale', 12), '提交裁决')) : null,
506
+ actionMsg ? React.createElement('div', { style: { fontSize: 11, color: C.text2, marginBottom: 6 } }, actionMsg) : null,
507
+ task.stuckSince ? React.createElement('div', { style: { marginBottom: 8, padding: '8px 10px', border: '1px solid ' + C.warn, borderRadius: 6, background: 'color-mix(in srgb, ' + C.warn + ' 8%, transparent)' } },
508
+ React.createElement('div', { style: { fontSize: 12, fontWeight: 700, color: C.warn, marginBottom: 4, display: 'flex', alignItems: 'center', gap: 4 } }, ic('alert-triangle', 13), '执行 Agent 疑似卡死'),
509
+ React.createElement('div', { style: { fontSize: 11, color: C.text, marginBottom: 6 } }, '标记于 ' + ago(task.stuckSince) + '(运行超 5 分钟且事件流停滞超 1 分钟)。你可以查看其会话后决定:'),
510
+ React.createElement('div', { style: { display: 'flex', gap: 6 } },
511
+ React.createElement('button', { onClick: doTerminate, style: { fontSize: 11, padding: '4px 12px', border: 'none', borderRadius: 4, background: C.err, color: '#fff', cursor: 'pointer', fontWeight: 600, display: 'inline-flex', alignItems: 'center', gap: 4 } }, ic('stop-circle', 12), '终止任务(重新排队)'),
512
+ React.createElement('button', { onClick: doDismiss, style: { fontSize: 11, padding: '4px 12px', border: '1px solid ' + C.border, borderRadius: 4, background: 'transparent', color: C.text2, cursor: 'pointer' } }, '继续观察'),
513
+ canJump ? React.createElement('button', { onClick: jumpToAgent, style: { fontSize: 11, padding: '4px 12px', border: '1px solid ' + C.brand, borderRadius: 4, background: 'transparent', color: C.brand, cursor: 'pointer' } }, '→ 查看会话') : null)) : null,
514
+ React.createElement('input', { value: editTitle, onChange: function (e) { setEditTitle(e.target.value) }, style: { width: '100%', fontSize: 13, fontWeight: 600, padding: '4px 6px', marginBottom: 6, border: '1px solid ' + C.border, borderRadius: 4, background: C.card, color: C.text, boxSizing: 'border-box' } }),
515
+ React.createElement('div', { style: { fontSize: 10, color: C.text2, marginBottom: 8, lineHeight: 1.7 } }, React.createElement('div', null, 'ID: ' + task.id), React.createElement('div', null, '创建: ' + fmtTime(task.createdAt) + '(' + ago(task.createdAt) + ')'), task.claimedBy ? React.createElement('div', null, '领取人: ', React.createElement(ActorLink, { id: task.claimedBy }), ' · ' + ago(task.claimedAt)) : null, task.resolvedAt ? React.createElement('div', null, '提交: ' + fmtTime(task.resolvedAt)) : null, task.verifiedAt ? React.createElement('div', null, '验收: ' + fmtTime(task.verifiedAt) + ' by ', React.createElement(ActorLink, { id: task.verifiedBy })) : null),
516
+ React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 3 } }, '任务描述'),
517
+ React.createElement('textarea', { value: editDesc, onChange: function (e) { setEditDesc(e.target.value) }, rows: 3, style: { width: '100%', fontSize: 12, padding: '6px 8px', marginBottom: 6, border: '1px solid ' + C.border, borderRadius: 4, background: C.card, color: C.text, resize: 'vertical', boxSizing: 'border-box', fontFamily: 'inherit' } }),
518
+ task.context && task.context.instructions ? React.createElement('div', { style: { fontSize: 11, color: C.text2, marginBottom: 6, padding: '4px 6px', background: C.nested, borderRadius: 4 } }, '指引: ' + task.context.instructions) : null,
519
+ React.createElement(DepsSection, { task: task }),
520
+ task.acceptance ? React.createElement('div', { style: { fontSize: 11, color: C.text, marginBottom: 6, padding: '5px 8px', background: C.nested, borderRadius: 4, borderLeft: '2px solid ' + C.ok, fontFamily: 'monospace', display: 'flex', alignItems: 'center', gap: 4 } }, ic('flask-conical', 11), '硬性验收: ' + task.acceptance) : null,
521
+ task.resolution ? React.createElement('div', { style: { fontSize: 11, color: C.text, marginBottom: 6, padding: '5px 8px', background: C.nested, borderRadius: 4, borderLeft: '2px solid ' + C.warn, maxHeight: 120, overflowY: 'auto', whiteSpace: 'pre-wrap' } }, '📝 ' + task.resolution) : null,
522
+ task.deliverable ? React.createElement('div', { style: { marginBottom: 8, padding: '6px 8px', border: '1px solid ' + C.border, borderRadius: 6, background: C.card } },
523
+ React.createElement('div', { style: { fontSize: 11, fontWeight: 700, color: C.brand, marginBottom: 4, display: 'flex', alignItems: 'center', gap: 4 } }, ic('package', 12), '交付报告 · ' + (task.deliverable.by || '') + ' · ' + ago(task.deliverable.at)),
524
+ React.createElement('div', { style: { fontSize: 11, color: C.text, marginBottom: 4, whiteSpace: 'pre-wrap' } }, task.deliverable.summary || '(无开发描述)'),
525
+ task.deliverable.changes ? 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: 100, overflowY: 'auto' } }, task.deliverable.changes)) : null,
526
+ task.deliverable.selfTest ? 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: 100, overflowY: 'auto' } }, task.deliverable.selfTest)) : null) : null,
527
+ 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 } },
528
+ 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)),
529
+ React.createElement('div', { style: { fontSize: 11, color: C.text, marginBottom: 4, whiteSpace: 'pre-wrap' } }, task.verification.summary || '(无测试概要)'),
530
+ 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) : null,
531
+ React.createElement(MsgThread, { messages: task.messages }),
532
+ 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,
533
+ React.createElement('div', { style: { display: 'flex', gap: 4, flexWrap: 'wrap', marginTop: 6 } },
534
+ React.createElement('button', { onClick: saveEdit, disabled: saving, style: { fontSize: 10, padding: '3px 8px', border: 'none', borderRadius: 3, background: C.brand, color: '#fff', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 3 } }, saving ? '保存中…' : [ic('save', 10), ' 保存并重置']),
535
+ 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: '#fff', cursor: 'pointer', fontWeight: 600, display: 'inline-flex', alignItems: 'center', gap: 3 } }, ic('rocket', 10), '发布(进入派发池)') : null,
536
+ (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: '#fff', cursor: 'pointer' } }, '领取') : null,
537
+ task.status === 'pending' ? React.createElement('button', { onClick: function () { doAction(function () { return rpc('dispatch-task', { taskId: task.id, role: 'worker' }) }) }, title: '手动派发给一次性 Worker 子代理', style: { fontSize: 10, padding: '3px 8px', border: 'none', borderRadius: 3, background: C.warn, color: '#fff', cursor: 'pointer', fontWeight: 600, display: 'inline-flex', alignItems: 'center', gap: 3 } }, ic('zap', 10), '派发') : null,
538
+ task.status === 'verifying' && !task.escalation ? React.createElement('button', { onClick: function () { doAction(function () { return rpc('dispatch-task', { taskId: task.id, role: 'verifier' }) }) }, title: '手动派发给一次性 Verifier 子代理', style: { fontSize: 10, padding: '3px 8px', border: 'none', borderRadius: 3, background: C.warn, color: '#fff', cursor: 'pointer', fontWeight: 600, display: 'inline-flex', alignItems: 'center', gap: 3 } }, ic('zap', 10), '派发验收') : null,
539
+ task.status === 'verifying' ? React.createElement('button', { onClick: function () { doAction(function () { return rpc('verify-task', { taskId: task.id, verdict: 'approved' }) }) }, style: { fontSize: 10, padding: '3px 8px', border: 'none', borderRadius: 3, background: C.ok, color: '#fff', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 3 } }, ic('check-circle', 10), '通过') : null,
540
+ task.status === 'verifying' ? React.createElement('button', { onClick: function () { var r = window.prompt('驳回原因:'); doAction(function () { return rpc('verify-task', { taskId: task.id, verdict: 'rejected', comment: r || '' }) }) }, style: { fontSize: 10, padding: '3px 8px', border: 'none', borderRadius: 3, background: C.err, color: '#fff', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 3 } }, ic('x-circle', 10), '驳回') : null,
541
+ task.status === 'resolved' ? React.createElement('button', { onClick: function () { doAction(function () { return rpc('archive-task', { taskId: task.id }) }) }, style: { fontSize: 10, padding: '3px 8px', border: 'none', borderRadius: 3, background: C.text2, color: '#fff', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 3 } }, ic('archive', 10), '归档') : null,
542
+ canJump ? React.createElement('button', { onClick: jumpToAgent, style: { fontSize: 10, padding: '3px 8px', border: '1px solid ' + C.brand, borderRadius: 3, background: 'transparent', color: C.brand, cursor: 'pointer' } }, '→ 跳转执行会话') : null),
543
+ canIntervene ? React.createElement('div', { style: { marginTop: 8, padding: '6px 8px', border: '1px dashed ' + C.warn, borderRadius: 6 } },
544
+ React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.warn, marginBottom: 4, display: 'flex', alignItems: 'center', gap: 4 } }, ic('zap', 11), '高优先级介入(插入执行 Agent 队首)'),
545
+ React.createElement('div', { style: { display: 'flex', gap: 4 } },
546
+ React.createElement('input', { value: interveneMsg, onChange: function (e) { setInterveneMsg(e.target.value) }, onKeyDown: function (e) { if (e.key === 'Enter') submitIntervene() }, placeholder: '给执行中的 Agent 下达高优指令…', style: { flex: 1, fontSize: 11, padding: '4px 8px', border: '1px solid ' + C.border, borderRadius: 4, background: C.card, color: C.text } }),
547
+ React.createElement('button', { onClick: submitIntervene, disabled: !interveneMsg.trim(), style: { fontSize: 11, padding: '4px 10px', border: 'none', borderRadius: 4, background: C.warn, color: '#fff', cursor: 'pointer', fontWeight: 600 } }, '介入'))) : null)
548
+ }
549
+ function TopPanel() {
550
+ var _R = React; var useState = _R.useState, useEffect = _R.useEffect
551
+ var _a = useState(state.open), open = _a[0], setOpen = _a[1]; var _b = useState(state.tasks), tasks = _b[0], setTasksState = _b[1]; var _c = useState(state.boardMode), mode = _c[0], setModeState = _c[1]; var _d = useState(state.detailId), detailId = _d[0], setDetailId = _d[1]; var _e = useState(false), showArchived = _e[0], setShowArchived = _e[1]; var _f = useState(state.dragOver), dragOver = _f[0], setDragOver = _f[1]; var _g = useState(state.dispatchInfo), dispatchInfo = _g[0], setDispatchInfo = _g[1]; var _j = useState(state.view), view = _j[0], setViewState = _j[1]; var _k = useState(state.layoutLeft), layL = _k[0], setLayL = _k[1]; var _l = useState(state.layoutRight), layR = _l[0], setLayR = _l[1]
552
+ var _m = useState(state.minWorkers), minW = _m[0], setMinW = _m[1]; var _n = useState(state.maxWorkers), maxW = _n[0], setMaxW = _n[1]; var _o = useState(state.minVerifiers), minV = _o[0], setMinV = _o[1]; var _p = useState(state.maxVerifiers), maxV = _p[0], setMaxV = _p[1]; var _wm = useState(state.workerModel), workerModel = _wm[0], setWorkerModel = _wm[1]; var _vm = useState(state.verifierModel), verifierModel = _vm[0], setVerifierModel = _vm[1]
553
+ var _q = useState(state.teamMode), teamMode = _q[0], setTeamMode = _q[1]
554
+ 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) }; listeners.push(update); update(); return function () { var i = listeners.indexOf(update); if (i >= 0) listeners.splice(i, 1) } }, [])
555
+ if (!open) return null
556
+ if (state.isRoot === false) return null // 子代理会话不渲染看板面板
557
+ var active = tasks.filter(function (t) { return t.status !== 'archived' }); var archived = tasks.filter(function (t) { return t.status === 'archived' })
558
+ var hasFilter = state.filterQ.trim() || state.filterPrio.length > 0 || state.filterTag
559
+ if (hasFilter) { active = active.filter(passFilter); archived = archived.filter(passFilter) }
560
+ var content
561
+ if (view === 'dashboard') { content = React.createElement(Dashboard) }
562
+ else if (view === 'team') { content = React.createElement(TeamView) }
563
+ else if (detailId) { content = React.createElement(DetailView) }
564
+ else {
565
+ var prioRank = { critical: 4, high: 3, medium: 2, low: 1 }
566
+ 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 }) })) })
567
+ // 归档区:排序选择器(默认归档时间降序)+ 纵向滚动列表(不再横向拉条)
568
+ var archSorted = archived.slice().sort(function (a, b) {
569
+ if (state.archSort === 'title') return (a.title || '').localeCompare(b.title || '')
570
+ var ta = a.archivedAt || a.resolvedAt || a.createdAt || '', tb = b.archivedAt || b.resolvedAt || b.createdAt || ''
571
+ return state.archSort === 'time-asc' ? ta.localeCompare(tb) : tb.localeCompare(ta)
572
+ })
573
+ 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 ? '多选模式:点击卡片勾选,底部批量操作' : '提示:拖拽卡片到目标列即可流转状态'), React.createElement('div', { style: { display: 'flex', gap: 8, overflowX: 'auto', paddingBottom: 4 } }, cols), archived.length > 0 ? React.createElement('div', { style: { marginTop: 4 } }, React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6 } }, React.createElement('div', { onClick: function () { setShowArchived(!showArchived) }, style: { fontSize: 11, color: C.text2, cursor: 'pointer', userSelect: 'none', display: 'inline-flex', alignItems: 'center', gap: 3 } }, ic(showArchived ? 'chevron-down' : 'chevron-right', 11), '已归档 (' + archived.length + ')'), showArchived ? React.createElement('select', { value: state.archSort, onChange: function (e) { state.archSort = e.target.value; notify() }, title: '归档排序', style: { fontSize: 9, padding: '1px 4px', border: '1px solid ' + C.border, borderRadius: 3, background: C.card, color: C.text2 } }, React.createElement('option', { value: 'time-desc' }, '最新在前'), React.createElement('option', { value: 'time-asc' }, '最早在前'), React.createElement('option', { value: 'title' }, '按标题')) : null), showArchived ? React.createElement('div', { style: { maxHeight: '32vh', overflowY: 'auto', marginTop: 4, paddingRight: 2 } }, archSorted.map(function (t) { return React.createElement(Card, { key: t.id, task: t }) })) : null) : null, React.createElement(BatchBar, null))
574
+ }
575
+ 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' } },
576
+ 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 } },
577
+ 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)),
578
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 4, flexWrap: 'wrap' } },
579
+ React.createElement(PoolCfgPopover, { minW: minW, maxW: maxW, minV: minV, maxV: maxV, workerModel: workerModel, verifierModel: verifierModel }),
580
+ dispatchInfo ? React.createElement('span', { style: { fontSize: 9, color: C.brand, maxWidth: 120, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, title: dispatchInfo }, dispatchInfo) : null,
581
+ React.createElement('button', { onClick: function () { state.selectMode = !state.selectMode; if (!state.selectMode) state.selected = {}; notify() }, title: '多选批量操作', style: { fontSize: 11, padding: '3px 8px', border: '1px solid ' + (state.selectMode ? C.brand : C.border), borderRadius: 6, cursor: 'pointer', background: state.selectMode ? C.brand : 'transparent', color: state.selectMode ? '#fff' : C.text2, display: 'inline-flex', alignItems: 'center', gap: 3 } }, ic('square-check-big', 11), '多选'),
582
+ React.createElement(TeamSwitch, { on: teamMode }),
583
+ React.createElement(ModeSwitch, { mode: mode, teamOn: teamMode }),
584
+ React.createElement('button', { onClick: function () { fetchTasks(); fetchChildren() }, title: '刷新', style: { border: 'none', background: 'transparent', cursor: 'pointer', fontSize: 12, color: C.text2, display: 'inline-flex', alignItems: 'center' } }, ic('refresh-cw', 13)),
585
+ React.createElement('button', { onClick: function () { state.open = false; state.detailId = null; notify() }, title: '关闭', style: { border: 'none', background: 'transparent', cursor: 'pointer', fontSize: 14, color: C.text2, display: 'inline-flex', alignItems: 'center' } }, ic('x', 14)))),
586
+ React.createElement('div', { style: { flex: 1, overflowY: 'auto', padding: '10px 12px' } }, tasks.length === 0 ? React.createElement('div', { style: { textAlign: 'center', padding: 24, color: C.text2, fontSize: 12 } }, '🎉 暂无任务') : content))
587
+ }
588
+ slots.inject('conversation.session.header.actions', function () { return slots.register({ name: 'conversation.session.header.actions', id: 'task-board-btn', label: '任务看板', order: 30 }, function (props) { return React.createElement(BoardButton, props) }) })
589
+ slots.inject('shell.overlay', function () { return slots.register({ name: 'shell.overlay', id: 'task-board-top-panel' }, function () { return React.createElement(TopPanel) }) })
590
+ }
591
+
592
+ module.exports = { name: 'dsh-agent-board', apply: apply }
593
+ return module.exports
594
+ }
595
+ })