@zzusp/dingtalk-dsh-observer 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +3 -1
  2. package/web-client.js +243 -95
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zzusp/dingtalk-dsh-observer",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "DingTalk task board client for DeepSeek Harness",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -28,12 +28,14 @@
28
28
  "inject": [
29
29
  "@deepseek-ai/dsh-client-runtime",
30
30
  "@deepseek-ai/dsh-client-ui-layout",
31
+ "@deepseek-ai/dsh-client-ui-primitives",
31
32
  "@deepseek-ai/dsh-client-ui-sidebar"
32
33
  ],
33
34
  "platform": "web"
34
35
  }
35
36
  },
36
37
  "dependencies": {
38
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.1-rc.2",
37
39
  "react": "^18.2.0"
38
40
  }
39
41
  }
package/web-client.js CHANGED
@@ -3,7 +3,8 @@ window.__ModuleLoader__.load({
3
3
  factory: (require) => {
4
4
  const module = { exports: {} }
5
5
  const React = require('react')
6
- const { useCallback, useEffect, useState } = React
6
+ const { Button, Menu, Pill, StateDot } = require('@deepseek-ai/dsh-client-ui-primitives')
7
+ const { useCallback, useEffect, useLayoutEffect, useRef, useState } = React
7
8
  const ENDPOINT = 'http://127.0.0.1:18998'
8
9
  const colors = {
9
10
  surface: 'var(--dsw-alias-bg-base, #fff)',
@@ -16,17 +17,71 @@ window.__ModuleLoader__.load({
16
17
  warning: 'var(--dsw-alias-state-warn-primary, #a56500)'
17
18
  }
18
19
  let open = false
19
- const listeners = new Set()
20
- const setOpen = (value) => { open = value; for (const listener of listeners) listener(value) }
20
+ let selectedSessionSnapshot
21
+ const openListeners = new Set()
22
+ const clearSessionSelection = () => {
23
+ const element = document.querySelector('[role="treeitem"][aria-selected="true"]')
24
+ if (!element) return
25
+ const selectedClasses = [...element.classList].filter((name) => name.endsWith('_selected'))
26
+ selectedSessionSnapshot = { element, selectedClasses }
27
+ document.documentElement.style.setProperty('--dingtalk-dsh-observer-selected-bg', getComputedStyle(element).backgroundColor)
28
+ element.setAttribute('aria-selected', 'false')
29
+ for (const name of selectedClasses) element.classList.remove(name)
30
+ }
31
+ const restoreSessionSelection = () => {
32
+ const snapshot = selectedSessionSnapshot
33
+ selectedSessionSnapshot = undefined
34
+ if (!snapshot?.element?.isConnected) return
35
+ snapshot.element.setAttribute('aria-selected', 'true')
36
+ for (const name of snapshot.selectedClasses) snapshot.element.classList.add(name)
37
+ }
38
+ const setOpen = (value) => {
39
+ if (value === open) return
40
+ if (value) clearSessionSelection()
41
+ else restoreSessionSelection()
42
+ open = value
43
+ for (const listener of openListeners) listener(value)
44
+ }
21
45
  const useOpen = () => {
22
46
  const [value, setValue] = useState(open)
23
- useEffect(() => { listeners.add(setValue); return () => listeners.delete(setValue) }, [])
24
- return [value, setOpen]
47
+ useEffect(() => { openListeners.add(setValue); return () => openListeners.delete(setValue) }, [])
48
+ return value
25
49
  }
26
50
  const fmt = (value) => value ? new Date(value).toLocaleString('zh-CN', { hour12: false }) : '—'
51
+ const fmtTime = (value) => value ? new Date(value).toLocaleTimeString('zh-CN', { hour12: false }) : '—'
27
52
  const short = (value) => value ? String(value).replace(/^session-/, '').slice(0, 14) : '—'
28
53
  const pill = (tone) => ({ display: 'inline-flex', alignItems: 'center', borderRadius: 999, padding: '3px 8px', fontSize: 12, color: tone, background: `color-mix(in srgb, ${tone} 12%, transparent)` })
29
54
  const card = { border: `1px solid ${colors.border}`, borderRadius: 12, background: colors.cardSurface, padding: 16, boxShadow: '0 4px 16px rgba(15,23,42,.07)' }
55
+ const tableFrame = { border: `1px solid ${colors.border}`, borderRadius: 12, background: colors.cardSurface, overflow: 'hidden', boxShadow: 'var(--dsw-shadow-card, 0 1px 2px rgba(0,0,0,.06))' }
56
+ const tableHeadCell = { height: 40, boxSizing: 'border-box', padding: '0 12px', color: colors.muted, fontSize: 11, fontWeight: 500, verticalAlign: 'middle' }
57
+ const tableBodyCell = { padding: '12px', borderTop: `1px solid ${colors.border}`, fontSize: 12, lineHeight: 1.55, verticalAlign: 'top' }
58
+ const tableFooter = { minHeight: 48, boxSizing: 'border-box', display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 8, padding: '8px 12px', borderTop: `1px solid ${colors.border}`, background: colors.surface2 }
59
+ const toolbar = { minHeight: 48, boxSizing: 'border-box', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, padding: '8px 12px', borderBottom: `1px solid ${colors.border}`, background: colors.cardSurface }
60
+ const statusTone = {
61
+ done: 'var(--dsw-alias-state-success-primary, #248a3d)',
62
+ warning: 'var(--dsw-alias-state-warn-primary, #a56500)',
63
+ ongoing: 'var(--dsw-alias-brand-primary, #4d6bfe)',
64
+ error: 'var(--dsw-alias-state-error-primary, #c33)'
65
+ }
66
+ const statusTag = (label, state) => {
67
+ const tone = statusTone[state] || colors.muted
68
+ return React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 6, minHeight: 24, boxSizing: 'border-box', border: `1px solid color-mix(in srgb, ${tone} 28%, transparent)`, borderRadius: 999, background: `color-mix(in srgb, ${tone} 10%, transparent)`, color: tone, padding: '2px 8px', fontSize: 11, fontWeight: 500, lineHeight: 1.4, whiteSpace: 'nowrap' } }, React.createElement(StateDot, { state, size: 7 }), label)
69
+ }
70
+ const authorizationTag = (label, state) => {
71
+ const tone = statusTone[state] || colors.muted
72
+ return React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 5, minWidth: 58, minHeight: 22, boxSizing: 'border-box', border: `1px solid color-mix(in srgb, ${tone} 18%, transparent)`, borderRadius: 6, background: `color-mix(in srgb, ${tone} 7%, transparent)`, color: tone, padding: '2px 7px', fontSize: 11, fontWeight: 500, lineHeight: 1.35, whiteSpace: 'nowrap' } }, React.createElement(StateDot, { state, size: 6 }), label)
73
+ }
74
+ function SelectMenu({ label, value, options, onChange, minWidth = 150 }) {
75
+ const [open, setOpen] = useState(false)
76
+ const selected = options.find((item) => item.id === value)
77
+ const anchor = React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', 'aria-label': label, 'aria-expanded': open, onClick: () => setOpen((current) => !current), style: { minWidth, justifyContent: 'space-between', gap: 16, fontWeight: 400 } }, React.createElement('span', { style: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, selected?.label || value), React.createElement('span', { 'aria-hidden': true, style: { color: colors.muted, fontSize: 9, transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .15s ease' } }, '▼'))
78
+ return React.createElement(Menu, { open, anchor, items: options, selectedId: value, onSelect: (id) => { onChange(id); setOpen(false) }, onClose: () => setOpen(false), align: 'end', portal: true, dense: true, compact: true })
79
+ }
80
+ const pageHeader = (title, description, count) => React.createElement('div', { style: { minHeight: 44, display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 20, marginBottom: 16 } },
81
+ React.createElement('div', { style: { minWidth: 0 } }, React.createElement('h1', { style: { margin: 0, fontSize: 18, lineHeight: 1.45, fontWeight: 600 } }, title), React.createElement('p', { style: { margin: '4px 0 0', color: colors.muted, fontSize: 12, lineHeight: 1.5 } }, description)),
82
+ count === undefined ? null : React.createElement(Pill, null, String(count)))
83
+ const sectionHeader = (title, meta) => React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, marginBottom: 12 } }, React.createElement('h2', { style: { margin: 0, fontSize: 15, lineHeight: 1.45, fontWeight: 600 } }, title), meta ? React.createElement('span', { style: { color: colors.muted, fontSize: 11 } }, meta) : null)
84
+ const emptyState = (text) => React.createElement('div', { style: { minHeight: 112, display: 'grid', placeItems: 'center', color: colors.muted, fontSize: 12, border: `1px dashed ${colors.border}`, borderRadius: 10, background: colors.surface2 } }, text)
30
85
  async function get(path) {
31
86
  const response = await fetch(`${ENDPOINT}${path}`, { method: 'GET', headers: { accept: 'application/json' } })
32
87
  const value = await response.json()
@@ -45,17 +100,16 @@ window.__ModuleLoader__.load({
45
100
  ])
46
101
  return { health, groups, tasks, activities, alerts, authorizations }
47
102
  }
48
- function FooterAction({ wide }) {
49
- const [isOpen] = useOpen()
50
- const [hovered, setHovered] = useState(false)
103
+ function SidebarAction({ wide }) {
104
+ const isOpen = useOpen()
51
105
  const icon = React.createElement('svg', { width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true },
52
106
  React.createElement('rect', { x: 3, y: 3, width: 18, height: 18, rx: 3 }),
53
107
  React.createElement('path', { d: 'M7 15v2M12 11v6M17 7v10' }))
54
108
  return React.createElement('button', {
55
- type: 'button', title: '钉钉群聊运行看板', 'aria-label': '钉钉群聊运行看板', onClick: () => setOpen(!isOpen), onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false),
109
+ type: 'button', title: '钉钉群聊运行看板', 'aria-label': '钉钉群聊运行看板', onClick: () => setOpen(true),
56
110
  style: wide
57
- ? { boxSizing: 'border-box', cursor: 'pointer', width: 'calc(100% + 4px)', height: 42, color: 'var(--dsw-alias-label-primary)', background: isOpen ? 'color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent)' : hovered ? 'var(--dsw-alias-interactive-bg-hover)' : 'transparent', border: 0, borderRadius: 12, flex: '0 0 auto', alignItems: 'center', gap: 8, margin: '4px -2px', padding: '0 10px 0 8px', fontFamily: 'inherit', fontSize: 14, lineHeight: '22px', display: 'flex', overflow: 'hidden' }
58
- : { boxSizing: 'border-box', cursor: 'pointer', width: 36, height: 36, color: 'var(--dsw-alias-label-primary)', background: isOpen ? 'color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent)' : hovered ? 'var(--dsw-alias-interactive-bg-hover)' : 'transparent', border: 0, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '8px 0 10px', padding: 0 }
111
+ ? { boxSizing: 'border-box', cursor: 'pointer', width: 'calc(100% + 4px)', height: 42, color: 'var(--dsw-alias-label-primary)', background: isOpen ? 'var(--dingtalk-dsh-observer-selected-bg, var(--dsw-alias-interactive-bg-selected, var(--dsw-alias-interactive-bg-hover)))' : 'transparent', border: 0, outline: 'none', boxShadow: 'none', borderRadius: 12, flex: '0 0 auto', alignItems: 'center', gap: 8, margin: '4px -2px', padding: '0 10px 0 8px', fontFamily: 'inherit', fontSize: 14, lineHeight: '22px', display: 'flex', overflow: 'hidden' }
112
+ : { boxSizing: 'border-box', cursor: 'pointer', width: 36, height: 36, color: 'var(--dsw-alias-label-primary)', background: isOpen ? 'var(--dingtalk-dsh-observer-selected-bg, var(--dsw-alias-interactive-bg-selected, var(--dsw-alias-interactive-bg-hover)))' : 'transparent', border: 0, outline: 'none', boxShadow: 'none', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '8px 0 10px', padding: 0 }
59
113
  }, icon, wide ? React.createElement('span', { style: { whiteSpace: 'nowrap', overflow: 'hidden' } }, '运行看板') : null)
60
114
  }
61
115
  const taskBuckets = [
@@ -66,7 +120,7 @@ window.__ModuleLoader__.load({
66
120
  ]
67
121
  const pages = [{ id: 'groups', label: '群聊会话' }, { id: 'tasks', label: '任务看板' }, { id: 'authorizations', label: '授权审批' }, { id: 'archive', label: '归档任务' }, { id: 'alerts', label: '告警' }]
68
122
  function ObserverOverlay({ openSession }) {
69
- const [isOpen] = useOpen()
123
+ const isOpen = useOpen()
70
124
  const [activePage, setActivePage] = useState('groups')
71
125
  const [data, setData] = useState()
72
126
  const [error, setError] = useState()
@@ -74,25 +128,83 @@ window.__ModuleLoader__.load({
74
128
  const [updatedAt, setUpdatedAt] = useState()
75
129
  const [selectedGroupId, setSelectedGroupId] = useState('')
76
130
  const [messagePage, setMessagePage] = useState(1)
131
+ const [outboxPage, setOutboxPage] = useState(1)
132
+ const [groupTableView, setGroupTableView] = useState('messages')
133
+ const [messageDeliveryFilter, setMessageDeliveryFilter] = useState('all')
134
+ const [outboxStatusFilter, setOutboxStatusFilter] = useState('all')
77
135
  const [hoveredTaskId, setHoveredTaskId] = useState('')
78
136
  const [copiedId, setCopiedId] = useState('')
79
137
  const [navigatingSessionId, setNavigatingSessionId] = useState('')
80
138
  const [alertType, setAlertType] = useState('all')
139
+ const [alertView, setAlertView] = useState('active')
81
140
  const [resolvedAlertPage, setResolvedAlertPage] = useState(1)
82
141
  const [authorizationComments, setAuthorizationComments] = useState({})
83
142
  const [decidingAuthorizationId, setDecidingAuthorizationId] = useState('')
84
143
  const [authorizationPage, setAuthorizationPage] = useState(1)
144
+ const [authorizationFilter, setAuthorizationFilter] = useState('all')
85
145
  const [selectedAuthorizationId, setSelectedAuthorizationId] = useState('')
146
+ const [sidebarWidth, setSidebarWidth] = useState(280)
147
+ const overlayRef = useRef()
86
148
  const refresh = useCallback(async () => {
87
149
  try { setData(await load()); setUpdatedAt(new Date()); setError(undefined) }
88
150
  catch (cause) { setError(cause instanceof Error ? cause.message : String(cause)) }
89
151
  }, [])
90
- useEffect(() => {
152
+ useLayoutEffect(() => {
91
153
  if (!isOpen) return undefined
92
154
  refresh()
93
155
  const timer = window.setInterval(refresh, 5000)
94
156
  return () => window.clearInterval(timer)
95
157
  }, [isOpen, refresh])
158
+ useEffect(() => {
159
+ if (!isOpen) return undefined
160
+ const entry = document.querySelector('[aria-label="钉钉群聊运行看板"]')
161
+ let sidebar = entry?.parentElement
162
+ while (sidebar?.parentElement) {
163
+ const rect = sidebar.getBoundingClientRect()
164
+ if (rect.left === 0 && rect.width >= 40 && rect.width <= 400 && rect.height >= window.innerHeight * .8) break
165
+ sidebar = sidebar.parentElement
166
+ }
167
+ const update = () => {
168
+ const right = sidebar?.getBoundingClientRect().right
169
+ if (Number.isFinite(right) && right >= 40 && right <= 400) {
170
+ const roundedRight = Math.round(right)
171
+ if (overlayRef.current) overlayRef.current.style.paddingLeft = `${roundedRight}px`
172
+ setSidebarWidth((current) => current === roundedRight ? current : roundedRight)
173
+ }
174
+ }
175
+ let animationFrame
176
+ let trackingUntil = 0
177
+ const trackTransition = () => {
178
+ trackingUntil = performance.now() + 700
179
+ if (animationFrame) return
180
+ const track = () => {
181
+ update()
182
+ if (performance.now() < trackingUntil) animationFrame = window.requestAnimationFrame(track)
183
+ else animationFrame = undefined
184
+ }
185
+ animationFrame = window.requestAnimationFrame(track)
186
+ }
187
+ update()
188
+ const observer = sidebar && typeof ResizeObserver === 'function' ? new ResizeObserver(update) : undefined
189
+ if (sidebar && observer) observer.observe(sidebar)
190
+ sidebar?.addEventListener('transitionrun', trackTransition)
191
+ document.addEventListener('click', trackTransition, true)
192
+ window.addEventListener('resize', update)
193
+ return () => {
194
+ observer?.disconnect()
195
+ sidebar?.removeEventListener('transitionrun', trackTransition)
196
+ document.removeEventListener('click', trackTransition, true)
197
+ window.removeEventListener('resize', update)
198
+ if (animationFrame) window.cancelAnimationFrame(animationFrame)
199
+ }
200
+ }, [isOpen])
201
+ useEffect(() => {
202
+ const closeForSession = (event) => {
203
+ if (event.target instanceof Element && event.target.closest('[role="treeitem"]')) setOpen(false)
204
+ }
205
+ document.addEventListener('click', closeForSession, true)
206
+ return () => document.removeEventListener('click', closeForSession, true)
207
+ }, [])
96
208
  if (!isOpen) return null
97
209
  const activities = new Map((data?.activities || []).map((item) => [item.taskId, item]))
98
210
  const groupsById = new Map((data?.groups || []).map((group) => [group.groupId, group]))
@@ -121,32 +233,52 @@ window.__ModuleLoader__.load({
121
233
  setCopiedId(value)
122
234
  window.setTimeout(() => setCopiedId((current) => current === value ? '' : current), 1500)
123
235
  }
124
- const copyButton = (value, label) => React.createElement('button', { type: 'button', disabled: !value, onClick: (event) => { event.stopPropagation(); copyId(value).catch((cause) => setNavigationError(cause instanceof Error ? cause.message : String(cause))) }, style: { border: `1px solid ${colors.border}`, borderRadius: 7, background: colors.surface2, color: copiedId === value ? 'var(--dsw-alias-state-success-primary, #248a3d)' : colors.muted, padding: '3px 7px', cursor: value ? 'pointer' : 'default', fontFamily: 'inherit', fontSize: 10.5 } }, copiedId === value ? '已复制' : `复制${label}`)
125
- const groupCards = (data?.groups || []).map((group) => React.createElement('div', { key: group.groupId, style: { ...card, display: 'grid', gap: 7, minWidth: 280, padding: '12px 16px', boxShadow: '0 4px 16px rgba(15,23,42,.07)' } },
126
- React.createElement('button', { type: 'button', onClick: () => navigate(group.residentSessionId), style: { border: 0, padding: 0, background: 'transparent', color: 'inherit', cursor: 'pointer', textAlign: 'left', display: 'grid', gap: 5, fontFamily: 'inherit' } },
127
- React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16 } }, React.createElement('strong', null, group.name || '未命名群'), React.createElement('span', { style: { flex: '0 0 auto', fontSize: 10.5, color: colors.muted } }, fmt(group.messages?.at?.(-1)?.occurredAt || group.updatedAt))),
128
- React.createElement('div', { style: { fontSize: 11, color: colors.muted, overflowWrap: 'anywhere' } }, group.groupId)),
129
- React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } }, React.createElement('code', { title: group.residentSessionId || '', style: { flex: 1, minWidth: 0, fontSize: 12, overflow: 'hidden', textOverflow: 'ellipsis' } }, group.residentSessionId || '尚未建立'), copyButton(group.residentSessionId, '会话 ID'))))
236
+ const copyButton = (value, label) => React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: !value, onClick: (event) => { event.stopPropagation(); copyId(value).catch((cause) => setNavigationError(cause instanceof Error ? cause.message : String(cause))) }, style: { color: copiedId === value ? 'var(--dsw-alias-state-success-primary, #248a3d)' : undefined, cursor: value ? 'pointer' : 'default', fontSize: 10.5 } }, copiedId === value ? '已复制' : `复制${label}`)
130
237
  const selectedGroup = groupsById.get(selectedGroupId) || (data?.groups || [])[0]
238
+ const selectedGroupSummary = selectedGroup ? React.createElement('div', { style: { minHeight: 64, boxSizing: 'border-box', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 20, padding: '10px 12px', borderBottom: `1px solid ${colors.border}`, background: colors.surface2 } },
239
+ React.createElement('div', { style: { minWidth: 0, display: 'grid', gap: 5 } }, React.createElement('div', { style: { display: 'flex', alignItems: 'baseline', gap: 10 } }, React.createElement('button', { type: 'button', onClick: () => navigate(selectedGroup.residentSessionId), style: { minWidth: 0, border: 0, padding: 0, background: 'transparent', color: 'inherit', cursor: 'pointer', textAlign: 'left', fontFamily: 'inherit', fontSize: 13, fontWeight: 600 } }, selectedGroup.name || '未命名群'), React.createElement('span', { style: { color: colors.muted, fontSize: 10.5 } }, `${selectedGroup.messages?.length || 0} 条消息`), React.createElement('span', { style: { color: colors.muted, fontSize: 10.5 } }, `更新于 ${fmt(selectedGroup.messages?.at?.(-1)?.occurredAt || selectedGroup.updatedAt)}`)), React.createElement('div', { style: { minWidth: 0, display: 'flex', alignItems: 'center', gap: 8, color: colors.muted, fontSize: 10.5 } }, React.createElement('span', null, '常驻 Session'), React.createElement('code', { title: selectedGroup.residentSessionId || '', style: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', color: 'inherit', fontSize: 10.5 } }, selectedGroup.residentSessionId || '尚未建立'))),
240
+ copyButton(selectedGroup.residentSessionId, '会话 ID')) : emptyState('暂无常驻群')
131
241
  const selectedMessages = [...(selectedGroup?.messages || [])].sort((left, right) => Number(right.sequence || 0) - Number(left.sequence || 0))
242
+ const filteredMessages = selectedMessages.filter((message) => messageDeliveryFilter === 'all' || (message.agentDeliveryStatus || 'unknown') === messageDeliveryFilter)
132
243
  const pageSize = 10
133
- const pageCount = Math.max(1, Math.ceil(selectedMessages.length / pageSize))
244
+ const pageCount = Math.max(1, Math.ceil(filteredMessages.length / pageSize))
134
245
  const currentMessagePage = Math.min(messagePage, pageCount)
135
- const visibleMessages = selectedMessages.slice((currentMessagePage - 1) * pageSize, currentMessagePage * pageSize)
246
+ const visibleMessages = filteredMessages.slice((currentMessagePage - 1) * pageSize, currentMessagePage * pageSize)
136
247
  const delivery = {
137
- delivered: { label: '已投递', tone: 'var(--dsw-alias-state-success-primary, #248a3d)' },
138
- failed: { label: '投递失败', tone: colors.danger },
139
- pending: { label: '投递中', tone: colors.warning },
140
- skipped: { label: '历史补拉·未投递', tone: colors.muted },
141
- unknown: { label: '历史状态未知', tone: colors.muted },
248
+ delivered: { label: '已投递', state: 'done' },
249
+ failed: { label: '投递失败', state: 'error' },
250
+ pending: { label: '投递中', state: 'ongoing' },
251
+ skipped: { label: '历史补拉·未投递', state: 'warning' },
252
+ unknown: { label: '历史状态未知', state: 'warning' },
142
253
  }
143
254
  const messageRows = visibleMessages.map((message) => {
144
255
  const status = delivery[message.agentDeliveryStatus] || delivery.unknown
145
256
  return React.createElement('tr', { key: message.messageId },
146
- React.createElement('td', { style: { padding: '11px 12px', borderTop: `1px solid ${colors.border}`, width: 150, verticalAlign: 'top' } }, React.createElement('strong', { style: { fontSize: 12 } }, message.senderName || message.senderOpenDingTalkId || '发送人未记录'), React.createElement('div', { style: { marginTop: 4, fontSize: 10.5, color: colors.muted } }, fmt(message.occurredAt))),
147
- React.createElement('td', { title: message.text, style: { padding: '11px 12px', borderTop: `1px solid ${colors.border}`, fontSize: 12, lineHeight: 1.55, overflowWrap: 'anywhere' } }, message.text || '(空消息)'),
148
- React.createElement('td', { style: { padding: '11px 12px', borderTop: `1px solid ${colors.border}`, width: 150, verticalAlign: 'top' } }, React.createElement('span', { title: message.agentDeliveryError || '', style: pill(status.tone) }, status.label)),
149
- React.createElement('td', { style: { padding: '11px 12px', borderTop: `1px solid ${colors.border}`, width: 150, verticalAlign: 'top' } }, React.createElement('code', { title: message.messageId, style: { fontSize: 10, color: colors.muted } }, `#${message.sequence ?? '—'} · ${short(message.messageId)}`)))
257
+ React.createElement('td', { style: { ...tableBodyCell, width: 150 } }, React.createElement('strong', { style: { fontSize: 12 } }, message.senderName || message.senderOpenDingTalkId || '发送人未记录'), React.createElement('div', { style: { marginTop: 4, fontSize: 10.5, color: colors.muted } }, fmt(message.occurredAt))),
258
+ React.createElement('td', { title: message.text, style: { ...tableBodyCell, overflowWrap: 'anywhere' } }, message.text || '(空消息)'),
259
+ React.createElement('td', { style: { ...tableBodyCell, width: 150 }, title: message.agentDeliveryError || '' }, statusTag(status.label, status.state)),
260
+ React.createElement('td', { style: { ...tableBodyCell, width: 150 } }, React.createElement('code', { title: message.messageId, style: { fontSize: 10, color: colors.muted } }, `#${message.sequence ?? '—'} · ${short(message.messageId)}`)))
261
+ })
262
+ const selectedOutbox = [...(selectedGroup?.outbox || [])].reverse()
263
+ const filteredOutbox = selectedOutbox.filter((message) => outboxStatusFilter === 'all' || (outboxStatusFilter === 'waiting' ? message.status === 'pending' && message.readbackRequired === true : !(message.status === 'pending' && message.readbackRequired === true)))
264
+ const outboxPageSize = 10
265
+ const outboxPageCount = Math.max(1, Math.ceil(filteredOutbox.length / outboxPageSize))
266
+ const currentOutboxPage = Math.min(outboxPage, outboxPageCount)
267
+ const visibleOutbox = filteredOutbox.slice((currentOutboxPage - 1) * outboxPageSize, currentOutboxPage * outboxPageSize)
268
+ const outboundStatus = {
269
+ sent: { label: '已回读确认', state: 'done' },
270
+ pending: { label: '已回读确认', state: 'done' },
271
+ }
272
+ const outboxRows = visibleOutbox.map((message) => {
273
+ const status = message.status === 'pending' && message.readbackRequired === true ? { label: '待回读确认', state: 'warning' } : outboundStatus[message.status] || outboundStatus.sent
274
+ return React.createElement('tr', { key: message.outboundId },
275
+ React.createElement('td', { style: { ...tableBodyCell, width: 130 } }, statusTag(status.label, status.state)),
276
+ React.createElement('td', { title: message.text, style: { ...tableBodyCell, overflowWrap: 'anywhere' } }, message.text || '(空消息)'),
277
+ React.createElement('td', { style: { ...tableBodyCell, width: 180 } },
278
+ React.createElement('div', null, React.createElement('code', { title: message.sourceMessageId, style: { fontSize: 10, color: colors.muted } }, short(message.sourceMessageId))),
279
+ message.replyToMessageId ? React.createElement('div', { style: { marginTop: 5, fontSize: 10.5, color: colors.muted } }, '回复 ', React.createElement('code', { title: message.replyToMessageId }, short(message.replyToMessageId))) : null),
280
+ React.createElement('td', { style: { ...tableBodyCell, width: 170 } }, React.createElement('code', { title: message.deliveredMessageId || '', style: { fontSize: 10, color: colors.muted } }, message.deliveredMessageId ? short(message.deliveredMessageId) : '历史未记录')),
281
+ React.createElement('td', { style: { ...tableBodyCell, width: 150 } }, React.createElement('code', { title: message.outboundId, style: { fontSize: 10, color: colors.muted } }, short(message.outboundId))))
150
282
  })
151
283
  const renderTaskCard = (task) => {
152
284
  const group = groupsById.get(task.groupId)
@@ -157,7 +289,7 @@ window.__ModuleLoader__.load({
157
289
  const navigating = navigatingSessionId === task.childSessionId
158
290
  return React.createElement('div', { key: task.taskId, onMouseEnter: () => setHoveredTaskId(task.taskId), onMouseLeave: () => setHoveredTaskId(''), style: { width: '100%', minWidth: 0, boxSizing: 'border-box', border: `1px solid ${colors.border}`, borderRadius: 12, background: colors.cardSurface, boxShadow: hovered ? '0 8px 20px rgba(15,23,42,.08), 0 2px 6px rgba(15,23,42,.05)' : 'var(--dsw-shadow-card, 0 1px 2px rgba(0,0,0,.08))', padding: '11px 13px', color: 'inherit', display: 'grid', gap: 6, opacity: queued ? 0.72 : 1, transition: 'box-shadow 180ms ease' } },
159
291
  React.createElement('button', { type: 'button', disabled: queued || navigating, onClick: () => navigate(task.childSessionId, group?.residentSessionId), style: { width: '100%', minWidth: 0, boxSizing: 'border-box', border: 0, padding: 0, background: 'transparent', color: 'inherit', cursor: queued || navigating ? 'default' : 'pointer', textAlign: 'left', display: 'grid', gap: 6, fontFamily: 'inherit' } },
160
- React.createElement('strong', { title: task.objective, style: { fontSize: 13, lineHeight: 1.45, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', overflowWrap: 'anywhere' } }, task.objective),
292
+ React.createElement('strong', { title: task.title || task.objective, style: { fontSize: 13, lineHeight: 1.45, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', overflowWrap: 'anywhere' } }, task.title || task.objective),
161
293
  React.createElement('div', { style: { display: 'flex', alignItems: 'center', minWidth: 0 } }, React.createElement('span', { title: group?.name || task.groupId, style: { ...pill(colors.muted), maxWidth: '100%', padding: '2px 7px', fontSize: 10.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, group?.name || task.groupId)),
162
294
  summary ? React.createElement('div', { title: String(summary), style: { minWidth: 0, fontSize: 11, lineHeight: 1.55, color: task.waitingReason ? colors.warning : colors.muted, display: '-webkit-box', WebkitLineClamp: 4, WebkitBoxOrient: 'vertical', overflow: 'hidden', overflowWrap: 'anywhere', wordBreak: 'break-word' } }, summary) : null,
163
295
  React.createElement('div', { style: { minWidth: 0, marginTop: 3, paddingTop: 9, borderTop: `1px solid ${colors.border}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 } },
@@ -184,7 +316,7 @@ window.__ModuleLoader__.load({
184
316
  const active = alert.status !== 'resolved'
185
317
  const category = classifyAlert(alert)
186
318
  return React.createElement('div', { key: alert.alertId || alert.fingerprint || index, style: { padding: '12px 0', borderTop: `1px solid ${colors.border}` } },
187
- React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', gap: 12 } }, React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } }, React.createElement('strong', { style: { color: active ? colors.danger : 'var(--dsw-alias-state-success-primary, #248a3d)' } }, active ? '当前异常' : '已恢复'), React.createElement('span', { style: pill(colors.muted) }, category.label)), React.createElement('span', { style: { fontSize: 12, color: colors.muted } }, fmt(active ? alert.lastSeenAt : alert.resolvedAt || alert.lastSeenAt))),
319
+ React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', gap: 12 } }, React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } }, statusTag(active ? '当前异常' : '已恢复', active ? 'error' : 'done'), React.createElement(Pill, null, category.label)), React.createElement('span', { style: { fontSize: 12, color: colors.muted } }, fmt(active ? alert.lastSeenAt : alert.resolvedAt || alert.lastSeenAt))),
188
320
  React.createElement('div', { style: { marginTop: 5, fontSize: 12 } }, alert.message || alert.detail || alert.reason || JSON.stringify(alert)),
189
321
  React.createElement('code', { style: { display: 'block', marginTop: 6, fontSize: 10.5, color: colors.muted } }, `${alert.taskId} · ${alert.fingerprint} · ${alert.count || 1} 次`))
190
322
  }
@@ -200,38 +332,53 @@ window.__ModuleLoader__.load({
200
332
  const bucketColumns = taskBuckets.map((bucket) => {
201
333
  const tasks = (data?.tasks || []).filter((task) => task.state === bucket.state && !task.archivedAt).sort((left, right) => String(right.updatedAt).localeCompare(String(left.updatedAt)))
202
334
  const taskCards = tasks.length ? tasks.map(renderTaskCard) : [React.createElement('div', { key: 'empty', style: { border: `1px dashed ${colors.border}`, borderRadius: 10, minHeight: 90, display: 'grid', placeItems: 'center', color: colors.muted, fontSize: 12 } }, '暂无任务')]
203
- return React.createElement('div', { key: bucket.state, style: { boxSizing: 'border-box', borderRadius: 14, background: bucket.background, boxShadow: '0 4px 16px rgba(15,23,42,.07)', padding: 10, display: 'flex', flexDirection: 'column', height: 'max(1000px, calc(100dvh - 120px))', minHeight: 'max(1000px, calc(100dvh - 120px))', maxHeight: 'max(1000px, calc(100dvh - 120px))', overflow: 'hidden' } },
335
+ return React.createElement('div', { key: bucket.state, style: { boxSizing: 'border-box', borderRadius: 14, background: bucket.background, boxShadow: '0 4px 16px rgba(15,23,42,.07)', padding: 10, display: 'flex', flexDirection: 'column', height: 'calc(100dvh - 238px)', minHeight: 420, maxHeight: 'calc(100dvh - 238px)', overflow: 'hidden' } },
204
336
  React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, padding: '4px 6px 10px' } }, React.createElement('strong', { style: { display: 'inline-flex', alignItems: 'center', borderRadius: 999, padding: '3px 9px', fontSize: 11.5, fontWeight: 650, lineHeight: 1.4, color: 'var(--dsw-alias-label-primary-inverted, #fff)', background: bucket.tone } }, bucket.label), React.createElement('span', { style: { borderRadius: 999, background: colors.cardSurface, padding: '2px 7px', fontSize: 10.5, color: colors.muted } }, tasks.length)),
205
337
  React.createElement('div', { style: { flex: 1, minHeight: 0, padding: '8px 12px 16px 8px', display: 'flex', flexDirection: 'column', gap: 10, overflowY: 'scroll', scrollbarGutter: 'stable' } }, ...taskCards))
206
338
  })
207
- const menu = React.createElement('nav', { 'aria-label': '运行看板视图', style: { minWidth: 0, display: 'flex', justifyContent: 'center', gap: 4, overflowX: 'auto' } },
208
- ...pages.map((page) => React.createElement('button', { key: page.id, type: 'button', onClick: () => setActivePage(page.id), 'aria-current': activePage === page.id ? 'page' : undefined, style: { border: 0, borderRadius: 8, padding: '8px 14px', cursor: 'pointer', font: 'inherit', fontSize: 13, fontWeight: activePage === page.id ? 650 : 400, color: activePage === page.id ? colors.accent : 'inherit', background: activePage === page.id ? 'color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent)' : 'transparent' } }, page.label)))
209
- const header = React.createElement('header', { style: { position: 'sticky', top: 0, zIndex: 2, display: 'grid', gridTemplateColumns: 'minmax(220px, auto) minmax(300px, 1fr) auto', alignItems: 'center', gap: 18, padding: '10px 24px', borderBottom: `1px solid ${colors.border}`, background: colors.surface } },
210
- React.createElement('div', { style: { minWidth: 0 } }, React.createElement('strong', { style: { fontSize: 18, whiteSpace: 'nowrap' } }, '钉钉群聊运行看板'), React.createElement('span', { style: { marginLeft: 12, fontSize: 12, color: colors.muted, whiteSpace: 'nowrap' } }, `运行态 · ${updatedAt ? fmt(updatedAt) : '正在连接'}`)),
211
- menu,
212
- React.createElement('div', { style: { display: 'flex', gap: 8 } }, React.createElement('button', { type: 'button', onClick: refresh, style: { border: `1px solid ${colors.border}`, borderRadius: 8, background: 'transparent', color: 'inherit', padding: '7px 12px', cursor: 'pointer' } }, '刷新'), React.createElement('button', { type: 'button', onClick: () => setOpen(false), style: { border: 0, borderRadius: 8, background: colors.surface2, color: 'inherit', padding: '7px 12px', cursor: 'pointer' } }, '关闭')))
339
+ const menu = React.createElement('nav', { 'aria-label': '运行看板视图', style: { minWidth: 0, height: 38, display: 'flex', alignItems: 'stretch', gap: 28, overflowX: 'auto', padding: '0 24px' } },
340
+ ...pages.map((page) => React.createElement('button', { key: page.id, type: 'button', onClick: () => setActivePage(page.id), 'aria-current': activePage === page.id ? 'page' : undefined, style: { position: 'relative', border: 0, borderBottom: activePage === page.id ? '2px solid #4d6bfe' : '2px solid transparent', borderRadius: 0, padding: '0 0 8px', cursor: 'pointer', font: 'inherit', fontSize: 13, fontWeight: 400, color: activePage === page.id ? '#4d6bfe' : colors.muted, background: 'transparent', outline: 'none', whiteSpace: 'nowrap' } }, page.label)))
341
+ const refreshIcon = React.createElement('svg', { width: 14, height: 14, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true }, React.createElement('path', { d: 'M20 11a8 8 0 1 0-2.34 5.66' }), React.createElement('path', { d: 'M20 4v7h-7' }))
342
+ const header = React.createElement('header', { style: { position: 'sticky', top: 0, zIndex: 2, borderBottom: `1px solid ${colors.border}`, background: colors.surface, pointerEvents: 'auto' } },
343
+ React.createElement('div', { style: { height: 52, boxSizing: 'border-box', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 24, padding: '0 24px' } },
344
+ React.createElement('div', { style: { minWidth: 0, display: 'flex', alignItems: 'center', gap: 14 } },
345
+ React.createElement('span', { style: { fontSize: 15, fontWeight: 400, whiteSpace: 'nowrap' } }, '运行看板'),
346
+ React.createElement('div', { 'aria-label': updatedAt ? `运行正常,最近更新 ${fmt(updatedAt)}` : '正在连接运行时', style: { display: 'inline-flex', alignItems: 'center', gap: 7, minWidth: 0, color: colors.muted, fontSize: 11.5, whiteSpace: 'nowrap' } },
347
+ React.createElement(StateDot, { state: updatedAt ? 'done' : 'ongoing', size: 7 }),
348
+ React.createElement('span', { style: { color: updatedAt ? statusTone.done : colors.muted } }, updatedAt ? '运行正常' : '正在连接'),
349
+ updatedAt ? React.createElement('span', { 'aria-hidden': true, style: { width: 1, height: 12, background: colors.border } }) : null,
350
+ updatedAt ? React.createElement('span', { title: fmt(updatedAt) }, `更新于 ${fmtTime(updatedAt)}`) : null)),
351
+ React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', onClick: refresh, style: { gap: 6 } }, '刷新', refreshIcon)),
352
+ menu)
353
+ const dataViewTabs = React.createElement('div', { role: 'tablist', 'aria-label': '群聊数据视图', style: { display: 'flex', alignSelf: 'stretch', gap: 20 } },
354
+ ...[{ id: 'messages', label: `群消息 ${selectedMessages.length}` }, { id: 'outbox', label: `Outbox ${selectedOutbox.length}` }].map((item) => React.createElement('button', { key: item.id, role: 'tab', 'aria-selected': groupTableView === item.id, type: 'button', onClick: () => setGroupTableView(item.id), style: { border: 0, borderBottom: groupTableView === item.id ? `2px solid ${colors.accent}` : '2px solid transparent', background: 'transparent', color: groupTableView === item.id ? colors.accent : colors.muted, padding: '0 2px', fontFamily: 'inherit', fontSize: 12, cursor: 'pointer' } }, item.label)))
355
+ const groupTableToolbar = React.createElement('div', { style: toolbar },
356
+ dataViewTabs,
357
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 8, flexWrap: 'wrap' } },
358
+ (data?.groups || []).length > 1 ? React.createElement(SelectMenu, { label: '选择群聊会话', value: selectedGroup?.groupId || '', options: (data?.groups || []).map((group) => ({ id: group.groupId, label: group.name || group.groupId })), onChange: (value) => { setSelectedGroupId(value); setMessagePage(1); setOutboxPage(1) }, minWidth: 220 }) : null,
359
+ groupTableView === 'messages'
360
+ ? React.createElement(SelectMenu, { label: '筛选投递状态', value: messageDeliveryFilter, options: [{ id: 'all', label: '全部投递状态' }, { id: 'delivered', label: '已投递' }, { id: 'failed', label: '投递失败' }, { id: 'pending', label: '投递中' }, { id: 'skipped', label: '历史未投递' }, { id: 'unknown', label: '状态未知' }], onChange: (value) => { setMessageDeliveryFilter(value); setMessagePage(1) } })
361
+ : React.createElement(SelectMenu, { label: '筛选发件状态', value: outboxStatusFilter, options: [{ id: 'all', label: '全部发件状态' }, { id: 'confirmed', label: '已回读确认' }, { id: 'waiting', label: '待回读确认' }], onChange: (value) => { setOutboxStatusFilter(value); setOutboxPage(1) } })))
362
+ const messagesTable = React.createElement(React.Fragment, null,
363
+ React.createElement('div', { style: { overflowX: 'auto' } }, React.createElement('table', { style: { width: '100%', minWidth: 820, borderCollapse: 'collapse', tableLayout: 'fixed' } },
364
+ React.createElement('thead', null, React.createElement('tr', { style: { background: colors.surface2, textAlign: 'left' } }, React.createElement('th', { style: { ...tableHeadCell, width: 150 } }, '发送人 / 时间'), React.createElement('th', { style: tableHeadCell }, '消息内容'), React.createElement('th', { style: { ...tableHeadCell, width: 150 } }, 'Agent 投递'), React.createElement('th', { style: { ...tableHeadCell, width: 150 } }, '消息'))),
365
+ React.createElement('tbody', null, ...(messageRows.length ? messageRows : [React.createElement('tr', { key: 'empty' }, React.createElement('td', { colSpan: 4, style: { ...tableBodyCell, padding: 36, textAlign: 'center', color: colors.muted } }, '暂无符合条件的群聊消息'))])))),
366
+ React.createElement('div', { style: tableFooter }, React.createElement('span', { style: { marginRight: 'auto', fontSize: 11, color: colors.muted } }, `${filteredMessages.length} 条 · 每页 ${pageSize} 条`), React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: currentMessagePage <= 1, onClick: () => setMessagePage((page) => Math.max(1, page - 1)) }, '上一页'), React.createElement('span', { style: { fontSize: 11, color: colors.muted } }, `${currentMessagePage} / ${pageCount}`), React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: currentMessagePage >= pageCount, onClick: () => setMessagePage((page) => Math.min(pageCount, page + 1)) }, '下一页')))
367
+ const outboxTable = React.createElement(React.Fragment, null,
368
+ React.createElement('div', { style: { overflowX: 'auto' } }, React.createElement('table', { style: { width: '100%', minWidth: 900, borderCollapse: 'collapse', tableLayout: 'fixed' } },
369
+ React.createElement('thead', null, React.createElement('tr', { style: { background: colors.surface2, textAlign: 'left' } }, React.createElement('th', { style: { ...tableHeadCell, width: 130 } }, '发送状态'), React.createElement('th', { style: tableHeadCell }, '消息内容'), React.createElement('th', { style: { ...tableHeadCell, width: 180 } }, '来源 / 回复目标'), React.createElement('th', { style: { ...tableHeadCell, width: 170 } }, '投递消息 ID'), React.createElement('th', { style: { ...tableHeadCell, width: 150 } }, 'Outbox ID'))),
370
+ React.createElement('tbody', null, ...(outboxRows.length ? outboxRows : [React.createElement('tr', { key: 'empty' }, React.createElement('td', { colSpan: 5, style: { ...tableBodyCell, padding: 36, textAlign: 'center', color: colors.muted } }, '暂无符合条件的 Agent 发件记录'))])))),
371
+ React.createElement('div', { style: tableFooter }, React.createElement('span', { style: { marginRight: 'auto', fontSize: 11, color: colors.muted } }, `${filteredOutbox.length} 条 · 每页 ${outboxPageSize} 条`), React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: currentOutboxPage <= 1, onClick: () => setOutboxPage((page) => Math.max(1, page - 1)) }, '上一页'), React.createElement('span', { style: { fontSize: 11, color: colors.muted } }, `${currentOutboxPage} / ${outboxPageCount}`), React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: currentOutboxPage >= outboxPageCount, onClick: () => setOutboxPage((page) => Math.min(outboxPageCount, page + 1)) }, '下一页')))
213
372
  const groupsPage = React.createElement(React.Fragment, null,
214
- React.createElement('section', null, React.createElement('h2', { style: { margin: '0 0 12px', fontSize: 16 } }, '群聊常驻会话'), React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: 12 } }, ...(groupCards.length ? groupCards : [React.createElement('div', { key: 'empty', style: card }, '暂无常驻群')]))),
215
- React.createElement('section', null,
216
- React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 12, marginBottom: 12 } },
217
- React.createElement('h2', { style: { margin: 0, fontSize: 16 } }, '群聊消息'),
218
- React.createElement('select', { 'aria-label': '选择群聊会话', value: selectedGroup?.groupId || '', onChange: (event) => { setSelectedGroupId(event.target.value); setMessagePage(1) }, style: { minWidth: 240, border: `1px solid ${colors.border}`, borderRadius: 8, background: colors.cardSurface, color: 'inherit', padding: '7px 10px', fontFamily: 'inherit', fontSize: 12 } }, ...(data?.groups || []).map((group) => React.createElement('option', { key: group.groupId, value: group.groupId }, group.name || group.groupId))),
219
- React.createElement('span', { style: { fontSize: 11, color: colors.muted } }, `${selectedMessages.length} 条 · 每页 ${pageSize} 条`)),
220
- React.createElement('div', { style: { ...card, padding: 0, overflow: 'hidden', boxShadow: '0 4px 16px rgba(15,23,42,.07)' } },
221
- React.createElement('div', { style: { overflowX: 'auto' } }, React.createElement('table', { style: { width: '100%', minWidth: 820, borderCollapse: 'collapse', tableLayout: 'fixed' } },
222
- React.createElement('thead', null, React.createElement('tr', { style: { background: colors.surface2, textAlign: 'left', fontSize: 11, color: colors.muted } }, React.createElement('th', { style: { padding: '14px 12px', width: 150 } }, '发送人 / 时间'), React.createElement('th', { style: { padding: '14px 12px' } }, '消息内容'), React.createElement('th', { style: { padding: '14px 12px', width: 150 } }, 'Agent 投递'), React.createElement('th', { style: { padding: '14px 12px', width: 150 } }, '消息'))),
223
- React.createElement('tbody', null, ...(messageRows.length ? messageRows : [React.createElement('tr', { key: 'empty' }, React.createElement('td', { colSpan: 4, style: { padding: 36, textAlign: 'center', color: colors.muted, fontSize: 12 } }, '暂无群聊消息'))])))),
224
- React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 8, padding: '10px 12px' } },
225
- React.createElement('button', { type: 'button', disabled: currentMessagePage <= 1, onClick: () => setMessagePage((page) => Math.max(1, page - 1)), style: { border: `1px solid ${colors.border}`, borderRadius: 8, background: colors.cardSurface, color: 'inherit', padding: '5px 10px', cursor: currentMessagePage <= 1 ? 'default' : 'pointer', opacity: currentMessagePage <= 1 ? 0.45 : 1 } }, '上一页'),
226
- React.createElement('span', { style: { fontSize: 11, color: colors.muted } }, `${currentMessagePage} / ${pageCount}`),
227
- React.createElement('button', { type: 'button', disabled: currentMessagePage >= pageCount, onClick: () => setMessagePage((page) => Math.min(pageCount, page + 1)), style: { border: `1px solid ${colors.border}`, borderRadius: 8, background: colors.cardSurface, color: 'inherit', padding: '5px 10px', cursor: currentMessagePage >= pageCount ? 'default' : 'pointer', opacity: currentMessagePage >= pageCount ? 0.45 : 1 } }, '下一页')))))
228
- const tasksPage = React.createElement('section', null, React.createElement('h2', { style: { margin: '0 0 12px', fontSize: 16 } }, '任务'), React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(4, minmax(320px, 1fr))', gap: 16, overflowX: 'auto', alignItems: 'start', paddingBottom: 8 } }, ...bucketColumns))
373
+ pageHeader('群聊会话', '查看常驻会话、群消息接收状态与 Agent 发件记录。', (data?.groups || []).length),
374
+ React.createElement('section', null, sectionHeader('消息记录', '群消息与 Agent 发件记录'), React.createElement('div', { style: tableFrame }, selectedGroupSummary, groupTableToolbar, groupTableView === 'messages' ? messagesTable : outboxTable)))
375
+ const tasksPage = React.createElement(React.Fragment, null, pageHeader('任务看板', '按执行阶段查看任务、叶子会话和目标进展。', (data?.tasks || []).filter((task) => !task.archivedAt).length), React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(4, minmax(320px, 1fr))', gap: 16, overflowX: 'auto', alignItems: 'start', paddingBottom: 8 } }, ...bucketColumns))
229
376
  const authorizationStatus = {
230
- 'pending-send': { label: '待审批', tone: colors.warning },
231
- 'waiting-reply': { label: '待审批', tone: colors.warning },
232
- answered: { label: '已处理', tone: colors.muted },
377
+ 'pending-send': { label: '待发送', state: 'warning' },
378
+ 'waiting-reply': { label: '待批复', state: 'warning' },
379
+ answered: { label: '已处理', state: 'done' },
233
380
  }
234
- const authorizationDecision = { approved: { label: '已批准', tone: 'var(--dsw-alias-state-success-primary, #248a3d)' }, rejected: { label: '已拒绝', tone: colors.danger } }
381
+ const authorizationDecision = { approved: { label: '已批准', state: 'done' }, rejected: { label: '已拒绝', state: 'error' } }
235
382
  const decideAuthorization = async (requestId, decision) => {
236
383
  try {
237
384
  setNavigationError(undefined); setDecidingAuthorizationId(requestId)
@@ -244,10 +391,11 @@ window.__ModuleLoader__.load({
244
391
  const leftPending = left.status === 'answered' ? 0 : 1; const rightPending = right.status === 'answered' ? 0 : 1
245
392
  return rightPending - leftPending || String(right.createdAt || '').localeCompare(String(left.createdAt || ''))
246
393
  })
394
+ const filteredAuthorizationItems = authorizationItems.filter((item) => authorizationFilter === 'all' || (authorizationFilter === 'pending' ? item.status !== 'answered' : item.decision === authorizationFilter))
247
395
  const authorizationPageSize = 10
248
- const authorizationPageCount = Math.max(1, Math.ceil(authorizationItems.length / authorizationPageSize))
396
+ const authorizationPageCount = Math.max(1, Math.ceil(filteredAuthorizationItems.length / authorizationPageSize))
249
397
  const currentAuthorizationPage = Math.min(authorizationPage, authorizationPageCount)
250
- const visibleAuthorizationItems = authorizationItems.slice((currentAuthorizationPage - 1) * authorizationPageSize, currentAuthorizationPage * authorizationPageSize)
398
+ const visibleAuthorizationItems = filteredAuthorizationItems.slice((currentAuthorizationPage - 1) * authorizationPageSize, currentAuthorizationPage * authorizationPageSize)
251
399
  const selectedAuthorization = authorizationItems.find((item) => item.requestId === selectedAuthorizationId)
252
400
  const authorizationDetailField = (label, value) => React.createElement('div', { style: { display: 'grid', gap: 5 } },
253
401
  React.createElement('strong', { style: { fontSize: 11, color: colors.muted } }, label),
@@ -257,66 +405,66 @@ window.__ModuleLoader__.load({
257
405
  const status = pending ? authorizationStatus[item.status] : authorizationDecision[item.decision] || authorizationStatus.answered
258
406
  const group = groupsById.get(item.groupId)
259
407
  return React.createElement('div', { key: item.requestId, style: { width: '100%', minWidth: 0, boxSizing: 'border-box', display: 'grid', gridTemplateColumns: '88px minmax(260px, 1.1fr) minmax(300px, 1.5fr) 130px 44px', alignItems: 'center', gap: 18, borderTop: `1px solid ${colors.border}`, background: 'transparent', color: 'inherit', padding: '15px 18px' } },
260
- React.createElement('span', { style: { ...pill(status.tone), justifySelf: 'start', width: 'max-content', whiteSpace: 'nowrap', fontSize: 11, lineHeight: 1.4 } }, status.label),
408
+ authorizationTag(status.label, status.state),
261
409
  React.createElement('span', { style: { minWidth: 0 } }, React.createElement('strong', { title: item.objective, style: { display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', fontSize: 12.5, lineHeight: 1.5 } }, item.objective), React.createElement('span', { style: { display: 'block', marginTop: 5, color: colors.muted, fontSize: 10.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, group?.name || item.groupId)),
262
410
  React.createElement('span', { style: { minWidth: 0 } }, React.createElement('span', { title: item.requestedAction, style: { display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', fontSize: 11.5, lineHeight: 1.55, color: colors.muted } }, item.requestedAction), item.risk && item.risk !== '未单独说明' ? React.createElement('span', { style: { display: 'block', marginTop: 6, color: colors.danger, fontSize: 10.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, `风险 · ${item.risk}`) : null),
263
411
  React.createElement('span', { style: { color: colors.muted, fontSize: 10.5, lineHeight: 1.5 } }, fmt(item.createdAt)),
264
- React.createElement('button', { type: 'button', onClick: () => setSelectedAuthorizationId(item.requestId), style: { border: 0, background: 'transparent', color: colors.accent, padding: '5px 0', cursor: 'pointer', fontFamily: 'inherit', fontSize: 11.5, fontWeight: 600 } }, '查看'))
412
+ React.createElement(Button, { variant: 'ghost', size: 'sm', type: 'button', onClick: () => setSelectedAuthorizationId(item.requestId) }, '查看'))
265
413
  })
266
414
  const authorizationDetail = selectedAuthorization ? React.createElement('div', { role: 'presentation', onMouseDown: (event) => { if (event.target === event.currentTarget) setSelectedAuthorizationId('') }, style: { position: 'fixed', inset: 0, zIndex: 1100, display: 'flex', justifyContent: 'flex-end', background: 'rgba(0,0,0,.28)', backdropFilter: 'blur(2px)' } },
267
415
  React.createElement('section', { role: 'dialog', 'aria-modal': true, 'aria-label': '授权申请单详情', style: { width: 'min(640px, 100%)', height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden', borderLeft: `1px solid ${colors.border}`, background: colors.surface, boxShadow: '-20px 0 60px rgba(0,0,0,.2)' } },
268
- React.createElement('header', { style: { flex: '0 0 auto', display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 20, padding: '20px 24px 18px', borderBottom: `1px solid ${colors.border}`, background: colors.surface } },
269
- React.createElement('div', { style: { minWidth: 0 } }, React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 9, marginBottom: 9 } }, React.createElement('span', { style: pill(selectedAuthorization.status === 'answered' ? (authorizationDecision[selectedAuthorization.decision]?.tone || colors.muted) : colors.warning) }, selectedAuthorization.status === 'answered' ? (authorizationDecision[selectedAuthorization.decision]?.label || '已处理') : '待审批'), React.createElement('span', { style: { color: colors.muted, fontSize: 11.5 } }, fmt(selectedAuthorization.createdAt))), React.createElement('h3', { style: { margin: 0, fontSize: 19, lineHeight: 1.45 } }, selectedAuthorization.objective || '授权申请'), React.createElement('code', { style: { display: 'block', marginTop: 8, color: colors.muted, fontSize: 10.5 } }, selectedAuthorization.requestId)),
270
- React.createElement('button', { type: 'button', 'aria-label': '关闭申请单', onClick: () => setSelectedAuthorizationId(''), style: { width: 32, height: 32, border: `1px solid ${colors.border}`, borderRadius: 8, background: colors.surface2, color: 'inherit', cursor: 'pointer', fontSize: 18 } }, '×')),
271
- React.createElement('div', { style: { flex: '1 1 auto', overflowY: 'auto', padding: '0 24px' } },
272
- React.createElement('section', { style: { padding: '20px 0', borderBottom: `1px solid ${colors.border}` } }, authorizationDetailField('申请范围', selectedAuthorization.requestedAction)),
273
- React.createElement('section', { style: { padding: '20px 0', borderBottom: `1px solid ${colors.border}` } }, authorizationDetailField('风险', selectedAuthorization.risk || '未单独说明')),
274
- React.createElement('section', { style: { padding: '20px 0', borderBottom: `1px solid ${colors.border}` } }, authorizationDetailField('阻塞原因', selectedAuthorization.waitingReason)),
416
+ React.createElement('header', { style: { flex: '0 0 auto', display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 24, padding: '24px 24px 20px', borderBottom: `1px solid ${colors.border}`, background: colors.surface } },
417
+ React.createElement('div', { style: { minWidth: 0 } }, React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 } }, authorizationTag(selectedAuthorization.status === 'answered' ? (authorizationDecision[selectedAuthorization.decision]?.label || '已处理') : (authorizationStatus[selectedAuthorization.status]?.label || '待批复'), selectedAuthorization.status === 'answered' ? (authorizationDecision[selectedAuthorization.decision]?.state || 'done') : 'warning'), React.createElement('span', { style: { color: colors.muted, fontSize: 11.5 } }, fmt(selectedAuthorization.createdAt))), React.createElement('h3', { style: { margin: 0, fontSize: 18, fontWeight: 600, lineHeight: 1.5 } }, selectedAuthorization.objective || '授权申请'), React.createElement('code', { style: { display: 'block', marginTop: 10, color: colors.muted, fontSize: 10.5 } }, selectedAuthorization.requestId)),
418
+ React.createElement(Button, { variant: 'ghost', size: 'sm', type: 'button', 'aria-label': '关闭申请单', onClick: () => setSelectedAuthorizationId(''), style: { flex: '0 0 auto', minWidth: 52, whiteSpace: 'nowrap' } }, '关闭')),
419
+ React.createElement('div', { style: { flex: '1 1 auto', overflowY: 'auto', padding: '0 24px 24px' } },
420
+ React.createElement('section', { style: { padding: '24px 0', borderBottom: `1px solid ${colors.border}` } }, authorizationDetailField('申请范围', selectedAuthorization.requestedAction)),
421
+ React.createElement('section', { style: { padding: '24px 0', borderBottom: `1px solid ${colors.border}` } }, authorizationDetailField('风险', selectedAuthorization.risk || '未单独说明')),
422
+ React.createElement('section', { style: { padding: '24px 0', borderBottom: `1px solid ${colors.border}` } }, authorizationDetailField('阻塞原因', selectedAuthorization.waitingReason)),
275
423
  React.createElement('details', { open: true, style: { padding: '18px 0', borderBottom: `1px solid ${colors.border}` } }, React.createElement('summary', { style: { cursor: 'pointer', fontSize: 12, fontWeight: 600 } }, `现场证据 · ${(selectedAuthorization.evidence || []).length}`), React.createElement('div', { style: { marginTop: 14 } }, authorizationDetailField('', (selectedAuthorization.evidence || []).map((value, index) => `${index + 1}. ${value}`).join('\n')))),
276
424
  React.createElement('details', { style: { padding: '18px 0', borderBottom: `1px solid ${colors.border}` } }, React.createElement('summary', { style: { cursor: 'pointer', fontSize: 12, fontWeight: 600 } }, `已尝试 · ${(selectedAuthorization.attemptedActions || []).length}`), React.createElement('div', { style: { marginTop: 14 } }, authorizationDetailField('', (selectedAuthorization.attemptedActions || []).map((value, index) => `${index + 1}. ${value}`).join('\n')))),
277
425
  React.createElement('section', { style: { padding: '20px 0', borderBottom: `1px solid ${colors.border}` } }, authorizationDetailField('关联信息', `Task ID:${selectedAuthorization.taskId || '—'}\n群聊:${groupsById.get(selectedAuthorization.groupId)?.name || selectedAuthorization.groupId || '—'}`)),
278
426
  selectedAuthorization.status === 'answered' ? React.createElement('section', { style: { padding: '20px 0' } }, authorizationDetailField('批复结果', `${selectedAuthorization.decision === 'approved' ? '批准' : selectedAuthorization.decision === 'rejected' ? '拒绝' : selectedAuthorization.decision || '已处理'}\n批复渠道:${selectedAuthorization.decisionSource === 'web' ? '运行看板' : selectedAuthorization.decisionSource === 'dingtalk' ? '钉钉私聊' : selectedAuthorization.decisionSource || '历史迁移'}\n批复时间:${fmt(selectedAuthorization.decidedAt)}\n批复内容:${selectedAuthorization.reply || '—'}`)) : null),
279
- selectedAuthorization.status !== 'answered' ? React.createElement('footer', { style: { flex: '0 0 auto', display: 'grid', gridTemplateColumns: '1fr auto auto', gap: 10, alignItems: 'center', padding: '14px 24px', borderTop: `1px solid ${colors.border}`, background: colors.surface } },
427
+ selectedAuthorization.status !== 'answered' ? React.createElement('footer', { style: { flex: '0 0 auto', display: 'grid', gridTemplateColumns: '1fr auto auto', gap: 10, alignItems: 'center', padding: '16px 24px', borderTop: `1px solid ${colors.border}`, background: colors.surface2 } },
280
428
  React.createElement('input', { 'aria-label': `审批意见 ${selectedAuthorization.requestId}`, value: authorizationComments[selectedAuthorization.requestId] || '', onChange: (event) => setAuthorizationComments((current) => ({ ...current, [selectedAuthorization.requestId]: event.target.value })), placeholder: '审批意见(可选)', style: { minWidth: 0, border: `1px solid ${colors.border}`, borderRadius: 9, background: colors.surface2, color: 'inherit', padding: '9px 11px', fontFamily: 'inherit', fontSize: 12 } }),
281
- React.createElement('button', { type: 'button', disabled: decidingAuthorizationId === selectedAuthorization.requestId, onClick: () => decideAuthorization(selectedAuthorization.requestId, 'rejected'), style: { minWidth: 76, border: `1px solid ${colors.danger}`, borderRadius: 9, background: 'transparent', color: colors.danger, padding: '9px 14px', cursor: 'pointer', fontWeight: 600 } }, '拒绝'),
282
- React.createElement('button', { type: 'button', disabled: decidingAuthorizationId === selectedAuthorization.requestId, onClick: () => decideAuthorization(selectedAuthorization.requestId, 'approved'), style: { minWidth: 76, border: 0, borderRadius: 9, background: colors.accent, color: '#fff', padding: '10px 14px', cursor: 'pointer', fontWeight: 600 } }, decidingAuthorizationId === selectedAuthorization.requestId ? '处理中…' : '批准')) : null)) : null
283
- const authorizationsPage = React.createElement(React.Fragment, null, React.createElement('section', null,
284
- React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 } }, React.createElement('h2', { style: { margin: 0, fontSize: 16 } }, '授权审批'), React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } }, React.createElement('span', { style: pill(colors.warning) }, `${authorizationItems.filter((item) => item.status !== 'answered').length} 个待审批`), React.createElement('span', { style: { fontSize: 11, color: colors.muted } }, `${authorizationItems.length} 条 · 每页 ${authorizationPageSize} 条`))),
285
- React.createElement('div', { style: { ...card, padding: 0, overflow: 'hidden' } },
286
- React.createElement('div', { style: { minHeight: 44, boxSizing: 'border-box', display: 'grid', gridTemplateColumns: '88px minmax(260px, 1.1fr) minmax(300px, 1.5fr) 130px 44px', alignItems: 'center', gap: 18, padding: '0 18px', background: colors.surface2, color: colors.muted, fontSize: 10.5 } }, React.createElement('span', null, '状态'), React.createElement('span', null, '任务'), React.createElement('span', null, '申请摘要'), React.createElement('span', null, '申请时间'), React.createElement('span', null, '操作')),
429
+ React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: decidingAuthorizationId === selectedAuthorization.requestId, onClick: () => decideAuthorization(selectedAuthorization.requestId, 'rejected'), style: { color: colors.danger } }, '拒绝'),
430
+ React.createElement(Button, { variant: 'primary', size: 'sm', type: 'button', disabled: decidingAuthorizationId === selectedAuthorization.requestId, onClick: () => decideAuthorization(selectedAuthorization.requestId, 'approved') }, decidingAuthorizationId === selectedAuthorization.requestId ? '处理中…' : '批准')) : null)) : null
431
+ const authorizationsPage = React.createElement(React.Fragment, null, pageHeader('授权审批', '处理需要人工确认的操作范围,并保留风险、证据和批复记录。', authorizationItems.filter((item) => item.status !== 'answered').length), React.createElement('section', null,
432
+ sectionHeader('审批记录', `${authorizationItems.length} 条 · 每页 ${authorizationPageSize} 条`),
433
+ React.createElement('div', { style: tableFrame },
434
+ React.createElement('div', { style: toolbar }, React.createElement('span', { style: { fontSize: 12, color: colors.muted } }, `当前显示 ${filteredAuthorizationItems.length} 条`), React.createElement(SelectMenu, { label: '筛选审批状态', value: authorizationFilter, options: [{ id: 'all', label: '全部审批状态' }, { id: 'pending', label: '待批复' }, { id: 'approved', label: '已批准' }, { id: 'rejected', label: '已拒绝' }], onChange: (value) => { setAuthorizationFilter(value); setAuthorizationPage(1) } })),
435
+ React.createElement('div', { style: { height: 40, boxSizing: 'border-box', display: 'grid', gridTemplateColumns: '88px minmax(260px, 1.1fr) minmax(300px, 1.5fr) 130px 44px', alignItems: 'center', gap: 18, padding: '0 18px', background: colors.surface2, color: colors.muted, fontSize: 11, fontWeight: 500 } }, React.createElement('span', null, '状态'), React.createElement('span', null, '任务'), React.createElement('span', null, '申请摘要'), React.createElement('span', null, '申请时间'), React.createElement('span', null, '操作')),
287
436
  React.createElement('div', null, ...(authorizationRows.length ? authorizationRows : [React.createElement('div', { key: 'empty', style: { padding: 36, textAlign: 'center', color: colors.muted, fontSize: 12 } }, '暂无授权申请')])),
288
- React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 8, padding: '10px 12px' } },
289
- React.createElement('button', { type: 'button', disabled: currentAuthorizationPage <= 1, onClick: () => setAuthorizationPage((page) => Math.max(1, page - 1)), style: { border: `1px solid ${colors.border}`, borderRadius: 8, background: colors.cardSurface, color: 'inherit', padding: '5px 10px', cursor: currentAuthorizationPage <= 1 ? 'default' : 'pointer', opacity: currentAuthorizationPage <= 1 ? 0.45 : 1 } }, '上一页'),
437
+ React.createElement('div', { style: tableFooter },
438
+ React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: currentAuthorizationPage <= 1, onClick: () => setAuthorizationPage((page) => Math.max(1, page - 1)) }, '上一页'),
290
439
  React.createElement('span', { style: { fontSize: 11, color: colors.muted } }, `${currentAuthorizationPage} / ${authorizationPageCount}`),
291
- React.createElement('button', { type: 'button', disabled: currentAuthorizationPage >= authorizationPageCount, onClick: () => setAuthorizationPage((page) => Math.min(authorizationPageCount, page + 1)), style: { border: `1px solid ${colors.border}`, borderRadius: 8, background: colors.cardSurface, color: 'inherit', padding: '5px 10px', cursor: currentAuthorizationPage >= authorizationPageCount ? 'default' : 'pointer', opacity: currentAuthorizationPage >= authorizationPageCount ? 0.45 : 1 } }, '下一页')))
440
+ React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: currentAuthorizationPage >= authorizationPageCount, onClick: () => setAuthorizationPage((page) => Math.min(authorizationPageCount, page + 1)) }, '下一页')))
292
441
  ), authorizationDetail)
293
442
  const archivedTasks = (data?.tasks || []).filter((task) => task.state === 'completed' && task.archivedAt).sort((left, right) => String(right.archivedAt).localeCompare(String(left.archivedAt)))
294
443
  const archivePage = React.createElement('section', null,
295
- React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 } }, React.createElement('h2', { style: { margin: 0, fontSize: 16 } }, '归档任务'), React.createElement('span', { style: pill('var(--dsw-alias-state-success-primary, #248a3d)') }, archivedTasks.length)),
296
- React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: 12 } }, ...(archivedTasks.length ? archivedTasks.map(renderTaskCard) : [React.createElement('div', { key: 'empty', style: { ...card, color: colors.muted } }, '暂无归档任务')]))
444
+ pageHeader('归档任务', '归档只影响看板展示,Task、Session、Goal 与历史证据仍完整保留。', archivedTasks.length),
445
+ React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: 12 } }, ...(archivedTasks.length ? archivedTasks.map(renderTaskCard) : [React.createElement(React.Fragment, { key: 'empty' }, emptyState('暂无归档任务'))]))
297
446
  )
298
447
  const alertsPage = React.createElement(React.Fragment, null,
299
- React.createElement('section', { style: { ...card, display: 'flex', alignItems: 'center', gap: 12 } }, React.createElement('strong', { style: { fontSize: 13 } }, '告警类型'), React.createElement('select', { 'aria-label': '筛选告警类型', value: alertType, onChange: (event) => { setAlertType(event.target.value); setResolvedAlertPage(1) }, style: { minWidth: 160, border: `1px solid ${colors.border}`, borderRadius: 8, background: colors.cardSurface, color: 'inherit', padding: '7px 10px', fontFamily: 'inherit', fontSize: 12 } }, ...alertCategories.map((category) => React.createElement('option', { key: category.id, value: category.id }, category.label)))),
300
- React.createElement('section', { style: card }, React.createElement('h2', { style: { margin: '0 0 14px', fontSize: 16 } }, `当前异常 · ${filteredActiveAlerts.length}`), ...(filteredActiveAlerts.length ? filteredActiveAlerts.map(renderAlert) : [React.createElement('div', { key: 'empty', style: { color: colors.muted } }, '当前没有此类型异常')])),
301
- React.createElement('section', { style: card },
302
- React.createElement('h2', { style: { margin: '0 0 14px', fontSize: 16 } }, `已恢复历史 · ${filteredResolvedAlerts.length}`),
303
- ...(visibleResolvedAlerts.length ? visibleResolvedAlerts.map(renderAlert) : [React.createElement('div', { key: 'empty', style: { color: colors.muted } }, '暂无此类型恢复记录')]),
304
- React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 8, paddingTop: 12 } },
305
- React.createElement('button', { type: 'button', disabled: currentResolvedAlertPage <= 1, onClick: () => setResolvedAlertPage((page) => Math.max(1, page - 1)), style: { border: `1px solid ${colors.border}`, borderRadius: 8, background: colors.cardSurface, color: 'inherit', padding: '5px 10px', cursor: currentResolvedAlertPage <= 1 ? 'default' : 'pointer', opacity: currentResolvedAlertPage <= 1 ? 0.45 : 1 } }, '上一页'),
306
- React.createElement('span', { style: { fontSize: 11, color: colors.muted } }, `${currentResolvedAlertPage} / ${resolvedAlertPageCount} · 每页 ${resolvedAlertPageSize} 条`),
307
- React.createElement('button', { type: 'button', disabled: currentResolvedAlertPage >= resolvedAlertPageCount, onClick: () => setResolvedAlertPage((page) => Math.min(resolvedAlertPageCount, page + 1)), style: { border: `1px solid ${colors.border}`, borderRadius: 8, background: colors.cardSurface, color: 'inherit', padding: '5px 10px', cursor: currentResolvedAlertPage >= resolvedAlertPageCount ? 'default' : 'pointer', opacity: currentResolvedAlertPage >= resolvedAlertPageCount ? 0.45 : 1 } }, '下一页')))
448
+ pageHeader('告警', '集中查看消息通道、叶子会话、任务目标和 Runtime 的当前异常与恢复历史。', filteredActiveAlerts.length),
449
+ React.createElement('section', null, sectionHeader('告警记录', '按状态和类型筛选'), React.createElement('div', { style: tableFrame },
450
+ React.createElement('div', { style: toolbar },
451
+ React.createElement('div', { role: 'tablist', 'aria-label': '告警状态视图', style: { display: 'flex', alignSelf: 'stretch', gap: 20 } }, ...[{ id: 'active', label: `当前异常 ${filteredActiveAlerts.length}` }, { id: 'resolved', label: `恢复历史 ${filteredResolvedAlerts.length}` }].map((item) => React.createElement('button', { key: item.id, role: 'tab', 'aria-selected': alertView === item.id, type: 'button', onClick: () => setAlertView(item.id), style: { border: 0, borderBottom: alertView === item.id ? `2px solid ${colors.accent}` : '2px solid transparent', background: 'transparent', color: alertView === item.id ? colors.accent : colors.muted, padding: '0 2px', fontFamily: 'inherit', fontSize: 12, cursor: 'pointer' } }, item.label))),
452
+ React.createElement(SelectMenu, { label: '筛选告警类型', value: alertType, options: alertCategories.map((category) => ({ id: category.id, label: category.label })), onChange: (value) => { setAlertType(value); setResolvedAlertPage(1) } })),
453
+ React.createElement('div', { style: { padding: '0 16px' } }, ...(alertView === 'active' ? (filteredActiveAlerts.length ? filteredActiveAlerts.map(renderAlert) : [React.createElement('div', { key: 'empty', style: { padding: '36px 0', textAlign: 'center', color: colors.muted, fontSize: 12 } }, '当前没有此类型异常')]) : (visibleResolvedAlerts.length ? visibleResolvedAlerts.map(renderAlert) : [React.createElement('div', { key: 'empty', style: { padding: '36px 0', textAlign: 'center', color: colors.muted, fontSize: 12 } }, '暂无此类型恢复记录')]))),
454
+ alertView === 'resolved' ? React.createElement('div', { style: tableFooter }, React.createElement('span', { style: { marginRight: 'auto', fontSize: 11, color: colors.muted } }, `${filteredResolvedAlerts.length} 条 · 每页 ${resolvedAlertPageSize} 条`), React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: currentResolvedAlertPage <= 1, onClick: () => setResolvedAlertPage((page) => Math.max(1, page - 1)) }, '上一页'), React.createElement('span', { style: { fontSize: 11, color: colors.muted } }, `${currentResolvedAlertPage} / ${resolvedAlertPageCount}`), React.createElement(Button, { variant: 'outline', size: 'sm', type: 'button', disabled: currentResolvedAlertPage >= resolvedAlertPageCount, onClick: () => setResolvedAlertPage((page) => Math.min(resolvedAlertPageCount, page + 1)) }, '下一页')) : null))
308
455
  )
309
456
  const pageContent = activePage === 'tasks' ? tasksPage : activePage === 'authorizations' ? authorizationsPage : activePage === 'archive' ? archivePage : activePage === 'alerts' ? alertsPage : groupsPage
310
- const main = React.createElement('main', { style: { maxWidth: activePage === 'tasks' ? 1480 : 1240, margin: '0 auto', padding: 24, display: 'grid', gap: 20 } },
457
+ const pageViewport = React.createElement('div', { style: { minHeight: 'calc(100dvh - 138px)', boxSizing: 'border-box' } }, pageContent)
458
+ const main = React.createElement('main', { style: { maxWidth: activePage === 'tasks' ? 1480 : 1240, minHeight: 'calc(100dvh - 90px)', boxSizing: 'border-box', margin: '0 auto', padding: '24px 24px 32px', display: 'grid', alignContent: 'start', gap: 20, pointerEvents: 'auto' } },
311
459
  error ? React.createElement('div', { style: { ...card, borderColor: colors.danger, color: colors.danger } }, `无法连接 resident 插件:${error}`) : null,
312
460
  navigationError ? React.createElement('div', { style: { ...card, borderColor: colors.danger, color: colors.danger } }, `无法打开 DSH Session:${navigationError}`) : null,
313
- pageContent
461
+ pageViewport
314
462
  )
315
- return React.createElement('div', { style: { pointerEvents: 'auto', position: 'fixed', inset: 0, zIndex: 1000, background: colors.surface, color: 'inherit', overflow: 'auto' } }, header, main)
463
+ return React.createElement('div', { ref: overlayRef, style: { position: 'fixed', inset: 0, paddingLeft: sidebarWidth, boxSizing: 'border-box', zIndex: 900, minWidth: 0, background: colors.surface, color: 'inherit', overflow: 'auto', pointerEvents: 'none' } }, header, main)
316
464
  }
317
465
  function apply(ctx) {
318
- ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({ name: 'sidebar.footer.action', id: 'dingtalk-dsh-observer-entry', order: 5, inject: () => ({}) }, FooterAction))
319
- ctx.slots.inject('shell.overlay', () => ctx.slots.register({ name: 'shell.overlay', id: 'dingtalk-dsh-observer-panel', order: 20, inject: () => ({ openSession: async (sessionId, parentSessionId) => {
466
+ ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({ name: 'sidebar.footer.action', id: 'dingtalk-dsh-observer-entry', order: 5, inject: () => ({}) }, SidebarAction))
467
+ ctx.slots.inject('shell.overlay', () => ctx.slots.register({ name: 'shell.overlay', id: 'dingtalk-dsh-observer', order: 20, inject: () => ({ openSession: async (sessionId, parentSessionId) => {
320
468
  if (parentSessionId) {
321
469
  let address = ctx.sessions.subagentAddress(sessionId)
322
470
  if (!address) { await ctx.sessions.refreshSubagents(parentSessionId); address = ctx.sessions.subagentAddress(sessionId) }