@zzusp/dingtalk-dsh-observer 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: dingtalk-dsh-observer
3
+ name: '@zzusp/dingtalk-dsh-observer'
package/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export const name = 'dingtalk-dsh-observer-web'
2
+ export function apply() {}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@zzusp/dingtalk-dsh-observer",
3
+ "version": "0.4.0",
4
+ "description": "DingTalk task board client for DeepSeek Harness",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "main": "index.js",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/HiQ-AI/dingtalk-dsh-assistant.git",
11
+ "directory": "packages/dingtalk-dsh-observer"
12
+ },
13
+ "publishConfig": {
14
+ "access": "public",
15
+ "registry": "https://registry.npmjs.org/"
16
+ },
17
+ "exports": {
18
+ ".": "./index.js",
19
+ "./client": "./web-client.js",
20
+ "./cordis.patch.yml": "./cordis.patch.yml",
21
+ "./package.json": "./package.json"
22
+ },
23
+ "dsh": {
24
+ "bundle": {
25
+ "patch": "./cordis.patch.yml"
26
+ },
27
+ "client": {
28
+ "inject": [
29
+ "@deepseek-ai/dsh-client-runtime",
30
+ "@deepseek-ai/dsh-client-ui-layout",
31
+ "@deepseek-ai/dsh-client-ui-sidebar"
32
+ ],
33
+ "platform": "web"
34
+ }
35
+ },
36
+ "dependencies": {
37
+ "react": "^18.2.0"
38
+ }
39
+ }
package/web-client.js ADDED
@@ -0,0 +1,332 @@
1
+ window.__ModuleLoader__.load({
2
+ id: '@zzusp/dingtalk-dsh-observer',
3
+ factory: (require) => {
4
+ const module = { exports: {} }
5
+ const React = require('react')
6
+ const { useCallback, useEffect, useState } = React
7
+ const ENDPOINT = 'http://127.0.0.1:18998'
8
+ const colors = {
9
+ surface: 'var(--dsw-alias-bg-base, #fff)',
10
+ surface2: 'var(--dsw-alias-bg-layer-1, #f7f7f8)',
11
+ cardSurface: 'var(--dsw-alias-bg-layer-1, #fff)',
12
+ border: 'var(--dsw-alias-border-l2, rgba(127,127,127,.25))',
13
+ muted: 'var(--dsw-alias-label-secondary, #737373)',
14
+ accent: 'var(--dsw-alias-brand-primary, #4d6bfe)',
15
+ danger: 'var(--dsw-alias-state-error-primary, #c33)',
16
+ warning: 'var(--dsw-alias-state-warn-primary, #a56500)'
17
+ }
18
+ let open = false
19
+ const listeners = new Set()
20
+ const setOpen = (value) => { open = value; for (const listener of listeners) listener(value) }
21
+ const useOpen = () => {
22
+ const [value, setValue] = useState(open)
23
+ useEffect(() => { listeners.add(setValue); return () => listeners.delete(setValue) }, [])
24
+ return [value, setOpen]
25
+ }
26
+ const fmt = (value) => value ? new Date(value).toLocaleString('zh-CN', { hour12: false }) : '—'
27
+ const short = (value) => value ? String(value).replace(/^session-/, '').slice(0, 14) : '—'
28
+ 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
+ const card = { border: `1px solid ${colors.border}`, borderRadius: 12, background: colors.cardSurface, padding: 16, boxShadow: '0 4px 16px rgba(15,23,42,.07)' }
30
+ async function get(path) {
31
+ const response = await fetch(`${ENDPOINT}${path}`, { method: 'GET', headers: { accept: 'application/json' } })
32
+ const value = await response.json()
33
+ if (!response.ok) throw new Error(value.error || `HTTP ${response.status}`)
34
+ return value
35
+ }
36
+ async function post(path, body = {}) {
37
+ const response = await fetch(`${ENDPOINT}${path}`, { method: 'POST', headers: { accept: 'application/json', 'content-type': 'application/json' }, body: JSON.stringify(body) })
38
+ const value = await response.json()
39
+ if (!response.ok) throw new Error(value.error || `HTTP ${response.status}`)
40
+ return value
41
+ }
42
+ async function load() {
43
+ const [health, groups, tasks, activities, alerts, authorizations] = await Promise.all([
44
+ get('/health'), get('/state/groups'), get('/state/tasks'), get('/state/activities'), get('/state/supervisor/alerts'), get('/state/authorizations')
45
+ ])
46
+ return { health, groups, tasks, activities, alerts, authorizations }
47
+ }
48
+ function FooterAction({ wide }) {
49
+ const [isOpen] = useOpen()
50
+ const [hovered, setHovered] = useState(false)
51
+ 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
+ React.createElement('rect', { x: 3, y: 3, width: 18, height: 18, rx: 3 }),
53
+ React.createElement('path', { d: 'M7 15v2M12 11v6M17 7v10' }))
54
+ return React.createElement('button', {
55
+ type: 'button', title: '钉钉群聊运行看板', 'aria-label': '钉钉群聊运行看板', onClick: () => setOpen(!isOpen), onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false),
56
+ 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 }
59
+ }, icon, wide ? React.createElement('span', { style: { whiteSpace: 'nowrap', overflow: 'hidden' } }, '运行看板') : null)
60
+ }
61
+ const taskBuckets = [
62
+ { state: 'queued', label: '待执行', tone: 'var(--dsw-alias-label-secondary, #64748b)', background: 'color-mix(in srgb, var(--dsw-alias-label-secondary, #64748b) 8%, var(--dsw-alias-bg-layer-1, #fff))' },
63
+ { state: 'running', label: '执行中', tone: colors.accent, background: 'color-mix(in srgb, var(--dsw-alias-brand-primary, #4d6bfe) 8%, var(--dsw-alias-bg-layer-1, #fff))' },
64
+ { state: 'waiting', label: '等待中', tone: colors.warning, background: 'color-mix(in srgb, var(--dsw-alias-state-warn-primary, #a56500) 8%, var(--dsw-alias-bg-layer-1, #fff))' },
65
+ { state: 'completed', label: '已完成', tone: 'var(--dsw-alias-state-success-primary, #248a3d)', background: 'color-mix(in srgb, var(--dsw-alias-state-success-primary, #248a3d) 8%, var(--dsw-alias-bg-layer-1, #fff))' }
66
+ ]
67
+ const pages = [{ id: 'groups', label: '群聊会话' }, { id: 'tasks', label: '任务看板' }, { id: 'authorizations', label: '授权审批' }, { id: 'archive', label: '归档任务' }, { id: 'alerts', label: '告警' }]
68
+ function ObserverOverlay({ openSession }) {
69
+ const [isOpen] = useOpen()
70
+ const [activePage, setActivePage] = useState('groups')
71
+ const [data, setData] = useState()
72
+ const [error, setError] = useState()
73
+ const [navigationError, setNavigationError] = useState()
74
+ const [updatedAt, setUpdatedAt] = useState()
75
+ const [selectedGroupId, setSelectedGroupId] = useState('')
76
+ const [messagePage, setMessagePage] = useState(1)
77
+ const [hoveredTaskId, setHoveredTaskId] = useState('')
78
+ const [copiedId, setCopiedId] = useState('')
79
+ const [navigatingSessionId, setNavigatingSessionId] = useState('')
80
+ const [alertType, setAlertType] = useState('all')
81
+ const [resolvedAlertPage, setResolvedAlertPage] = useState(1)
82
+ const [authorizationComments, setAuthorizationComments] = useState({})
83
+ const [decidingAuthorizationId, setDecidingAuthorizationId] = useState('')
84
+ const [authorizationPage, setAuthorizationPage] = useState(1)
85
+ const [selectedAuthorizationId, setSelectedAuthorizationId] = useState('')
86
+ const refresh = useCallback(async () => {
87
+ try { setData(await load()); setUpdatedAt(new Date()); setError(undefined) }
88
+ catch (cause) { setError(cause instanceof Error ? cause.message : String(cause)) }
89
+ }, [])
90
+ useEffect(() => {
91
+ if (!isOpen) return undefined
92
+ refresh()
93
+ const timer = window.setInterval(refresh, 5000)
94
+ return () => window.clearInterval(timer)
95
+ }, [isOpen, refresh])
96
+ if (!isOpen) return null
97
+ const activities = new Map((data?.activities || []).map((item) => [item.taskId, item]))
98
+ const groupsById = new Map((data?.groups || []).map((group) => [group.groupId, group]))
99
+ const navigate = async (sessionId, parentSessionId) => {
100
+ try { setNavigationError(undefined); setNavigatingSessionId(sessionId); await openSession(sessionId, parentSessionId); setOpen(false) }
101
+ catch (cause) { setNavigationError(cause instanceof Error ? cause.message : String(cause)) }
102
+ finally { setNavigatingSessionId('') }
103
+ }
104
+ const copyId = async (value) => {
105
+ let copied = false
106
+ try {
107
+ if (globalThis.navigator?.clipboard?.writeText) { await globalThis.navigator.clipboard.writeText(value); copied = true }
108
+ } catch {}
109
+ if (!copied) {
110
+ const input = document.createElement('textarea')
111
+ input.value = value
112
+ input.setAttribute('readonly', '')
113
+ input.style.position = 'fixed'
114
+ input.style.opacity = '0'
115
+ document.body.appendChild(input)
116
+ input.select()
117
+ copied = document.execCommand('copy')
118
+ input.remove()
119
+ }
120
+ if (!copied) throw new Error('当前浏览器未允许复制,请手动选择 ID')
121
+ setCopiedId(value)
122
+ window.setTimeout(() => setCopiedId((current) => current === value ? '' : current), 1500)
123
+ }
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'))))
130
+ const selectedGroup = groupsById.get(selectedGroupId) || (data?.groups || [])[0]
131
+ const selectedMessages = [...(selectedGroup?.messages || [])].sort((left, right) => Number(right.sequence || 0) - Number(left.sequence || 0))
132
+ const pageSize = 10
133
+ const pageCount = Math.max(1, Math.ceil(selectedMessages.length / pageSize))
134
+ const currentMessagePage = Math.min(messagePage, pageCount)
135
+ const visibleMessages = selectedMessages.slice((currentMessagePage - 1) * pageSize, currentMessagePage * pageSize)
136
+ 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 },
142
+ }
143
+ const messageRows = visibleMessages.map((message) => {
144
+ const status = delivery[message.agentDeliveryStatus] || delivery.unknown
145
+ 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)}`)))
150
+ })
151
+ const renderTaskCard = (task) => {
152
+ const group = groupsById.get(task.groupId)
153
+ const activity = activities.get(task.taskId)
154
+ const queued = task.state === 'queued'
155
+ const summary = task.waitingReason || task.completion || activity?.type || activity?.eventType
156
+ const hovered = hoveredTaskId === task.taskId
157
+ const navigating = navigatingSessionId === task.childSessionId
158
+ 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
+ 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),
161
+ 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
+ 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
+ React.createElement('div', { style: { minWidth: 0, marginTop: 3, paddingTop: 9, borderTop: `1px solid ${colors.border}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 } },
164
+ React.createElement('span', { title: task.updatedAt, style: { minWidth: 0, fontSize: 10.5, color: colors.muted, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, `最后活动 ${fmt(task.updatedAt)}`),
165
+ React.createElement('div', { style: { flex: '0 0 auto', display: 'flex', alignItems: 'center', gap: 5 } }, copyButton(task.taskId, ' Task'), task.state === 'completed' && !task.archivedAt ? React.createElement('button', { type: 'button', onClick: async (event) => { event.stopPropagation(); try { await post(`/tasks/${encodeURIComponent(task.taskId)}/archive`); await refresh() } catch (cause) { setNavigationError(cause instanceof Error ? cause.message : String(cause)) } }, style: { flex: '0 0 auto', border: `1px solid ${colors.border}`, borderRadius: 7, background: colors.surface2, color: colors.muted, padding: '3px 7px', cursor: 'pointer', fontFamily: 'inherit', fontSize: 10.5 } }, '归档') : null)),
166
+ navigating ? React.createElement('div', { role: 'status', style: { fontSize: 11.5, color: colors.accent } }, '正在打开会话…') : queued ? React.createElement('div', { style: { fontSize: 11.5, color: colors.muted } }, '等待执行,尚无对话和轨迹') : null),
167
+ null)
168
+ }
169
+ const alertCategories = [
170
+ { id: 'all', label: '全部类型' },
171
+ { id: 'message-channel', label: '消息通道' },
172
+ { id: 'leaf-session', label: '叶子会话' },
173
+ { id: 'goal', label: '任务目标' },
174
+ { id: 'runtime', label: '运行时' }
175
+ ]
176
+ const classifyAlert = (alert) => {
177
+ const fingerprint = String(alert.fingerprint || '')
178
+ if (fingerprint.startsWith('dws-') || fingerprint.includes('consumer')) return alertCategories[1]
179
+ if (fingerprint.startsWith('leaf-session') || fingerprint.startsWith('leaf-paused')) return alertCategories[2]
180
+ if (fingerprint.startsWith('leaf-goal')) return alertCategories[3]
181
+ return alertCategories[4]
182
+ }
183
+ const renderAlert = (alert, index) => {
184
+ const active = alert.status !== 'resolved'
185
+ const category = classifyAlert(alert)
186
+ 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))),
188
+ React.createElement('div', { style: { marginTop: 5, fontSize: 12 } }, alert.message || alert.detail || alert.reason || JSON.stringify(alert)),
189
+ React.createElement('code', { style: { display: 'block', marginTop: 6, fontSize: 10.5, color: colors.muted } }, `${alert.taskId} · ${alert.fingerprint} · ${alert.count || 1} 次`))
190
+ }
191
+ const activeAlerts = (data?.alerts || []).filter((alert) => alert.status !== 'resolved').sort((left, right) => String(right.lastSeenAt).localeCompare(String(left.lastSeenAt)))
192
+ const resolvedAlerts = (data?.alerts || []).filter((alert) => alert.status === 'resolved').sort((left, right) => String(right.resolvedAt || right.lastSeenAt).localeCompare(String(left.resolvedAt || left.lastSeenAt)))
193
+ const matchesAlertType = (alert) => alertType === 'all' || classifyAlert(alert).id === alertType
194
+ const filteredActiveAlerts = activeAlerts.filter(matchesAlertType)
195
+ const filteredResolvedAlerts = resolvedAlerts.filter(matchesAlertType)
196
+ const resolvedAlertPageSize = 10
197
+ const resolvedAlertPageCount = Math.max(1, Math.ceil(filteredResolvedAlerts.length / resolvedAlertPageSize))
198
+ const currentResolvedAlertPage = Math.min(resolvedAlertPage, resolvedAlertPageCount)
199
+ const visibleResolvedAlerts = filteredResolvedAlerts.slice((currentResolvedAlertPage - 1) * resolvedAlertPageSize, currentResolvedAlertPage * resolvedAlertPageSize)
200
+ const bucketColumns = taskBuckets.map((bucket) => {
201
+ const tasks = (data?.tasks || []).filter((task) => task.state === bucket.state && !task.archivedAt).sort((left, right) => String(right.updatedAt).localeCompare(String(left.updatedAt)))
202
+ 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' } },
204
+ 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
+ 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
+ })
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' } }, '关闭')))
213
+ 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))
229
+ const authorizationStatus = {
230
+ 'pending-send': { label: '待审批', tone: colors.warning },
231
+ 'waiting-reply': { label: '待审批', tone: colors.warning },
232
+ answered: { label: '已处理', tone: colors.muted },
233
+ }
234
+ const authorizationDecision = { approved: { label: '已批准', tone: 'var(--dsw-alias-state-success-primary, #248a3d)' }, rejected: { label: '已拒绝', tone: colors.danger } }
235
+ const decideAuthorization = async (requestId, decision) => {
236
+ try {
237
+ setNavigationError(undefined); setDecidingAuthorizationId(requestId)
238
+ await post(`/authorizations/${encodeURIComponent(requestId)}/decision`, { decision, comment: authorizationComments[requestId] || '' })
239
+ await refresh()
240
+ } catch (cause) { setNavigationError(cause instanceof Error ? cause.message : String(cause)) }
241
+ finally { setDecidingAuthorizationId('') }
242
+ }
243
+ const authorizationItems = [...(data?.authorizations || [])].sort((left, right) => {
244
+ const leftPending = left.status === 'answered' ? 0 : 1; const rightPending = right.status === 'answered' ? 0 : 1
245
+ return rightPending - leftPending || String(right.createdAt || '').localeCompare(String(left.createdAt || ''))
246
+ })
247
+ const authorizationPageSize = 10
248
+ const authorizationPageCount = Math.max(1, Math.ceil(authorizationItems.length / authorizationPageSize))
249
+ const currentAuthorizationPage = Math.min(authorizationPage, authorizationPageCount)
250
+ const visibleAuthorizationItems = authorizationItems.slice((currentAuthorizationPage - 1) * authorizationPageSize, currentAuthorizationPage * authorizationPageSize)
251
+ const selectedAuthorization = authorizationItems.find((item) => item.requestId === selectedAuthorizationId)
252
+ const authorizationDetailField = (label, value) => React.createElement('div', { style: { display: 'grid', gap: 5 } },
253
+ React.createElement('strong', { style: { fontSize: 11, color: colors.muted } }, label),
254
+ React.createElement('div', { style: { fontSize: 13, lineHeight: 1.65, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } }, value || '—'))
255
+ const authorizationRows = visibleAuthorizationItems.map((item) => {
256
+ const pending = item.status !== 'answered'
257
+ const status = pending ? authorizationStatus[item.status] : authorizationDecision[item.decision] || authorizationStatus.answered
258
+ const group = groupsById.get(item.groupId)
259
+ 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),
261
+ 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
+ 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
+ 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 } }, '查看'))
265
+ })
266
+ 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
+ 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)),
275
+ 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
+ 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
+ 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
+ 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 } },
280
+ 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, '操作')),
287
+ 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 } }, '上一页'),
290
+ 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 } }, '下一页')))
292
+ ), authorizationDetail)
293
+ const archivedTasks = (data?.tasks || []).filter((task) => task.state === 'completed' && task.archivedAt).sort((left, right) => String(right.archivedAt).localeCompare(String(left.archivedAt)))
294
+ 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 } }, '暂无归档任务')]))
297
+ )
298
+ 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 } }, '下一页')))
308
+ )
309
+ 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 } },
311
+ error ? React.createElement('div', { style: { ...card, borderColor: colors.danger, color: colors.danger } }, `无法连接 resident 插件:${error}`) : null,
312
+ navigationError ? React.createElement('div', { style: { ...card, borderColor: colors.danger, color: colors.danger } }, `无法打开 DSH Session:${navigationError}`) : null,
313
+ pageContent
314
+ )
315
+ return React.createElement('div', { style: { pointerEvents: 'auto', position: 'fixed', inset: 0, zIndex: 1000, background: colors.surface, color: 'inherit', overflow: 'auto' } }, header, main)
316
+ }
317
+ 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) => {
320
+ if (parentSessionId) {
321
+ let address = ctx.sessions.subagentAddress(sessionId)
322
+ if (!address) { await ctx.sessions.refreshSubagents(parentSessionId); address = ctx.sessions.subagentAddress(sessionId) }
323
+ if (address) { ctx.sessions.openSubagent(address); return }
324
+ }
325
+ await ctx.sessions.refresh()
326
+ ctx.sessions.open(sessionId)
327
+ } }) }, ObserverOverlay))
328
+ }
329
+ module.exports = { name: 'dingtalk-dsh-observer-client', inject: ['slots', 'sessions'], apply }
330
+ return module.exports
331
+ }
332
+ })