dsh-super-code 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +162 -0
- package/client/index.js +920 -0
- package/cordis.patch.yml +21 -0
- package/lib/core/delegation.d.ts +59 -0
- package/lib/core/delegation.d.ts.map +1 -0
- package/lib/core/delegation.js +41 -0
- package/lib/core/delegation.js.map +1 -0
- package/lib/core/evaluation.d.ts +123 -0
- package/lib/core/evaluation.d.ts.map +1 -0
- package/lib/core/evaluation.js +180 -0
- package/lib/core/evaluation.js.map +1 -0
- package/lib/core/experiment-recipes.d.ts +5 -0
- package/lib/core/experiment-recipes.d.ts.map +1 -0
- package/lib/core/experiment-recipes.js +67 -0
- package/lib/core/experiment-recipes.js.map +1 -0
- package/lib/core/guidance.d.ts +10 -0
- package/lib/core/guidance.d.ts.map +1 -0
- package/lib/core/guidance.js +16 -0
- package/lib/core/guidance.js.map +1 -0
- package/lib/core/session-scan.d.ts +8 -0
- package/lib/core/session-scan.d.ts.map +1 -0
- package/lib/core/session-scan.js +21 -0
- package/lib/core/session-scan.js.map +1 -0
- package/lib/core/task-memory.d.ts +81 -0
- package/lib/core/task-memory.d.ts.map +1 -0
- package/lib/core/task-memory.js +158 -0
- package/lib/core/task-memory.js.map +1 -0
- package/lib/core/teams.d.ts +19 -0
- package/lib/core/teams.d.ts.map +1 -0
- package/lib/core/teams.js +40 -0
- package/lib/core/teams.js.map +1 -0
- package/lib/core/tool-evidence.d.ts +57 -0
- package/lib/core/tool-evidence.d.ts.map +1 -0
- package/lib/core/tool-evidence.js +97 -0
- package/lib/core/tool-evidence.js.map +1 -0
- package/lib/dsh/index.d.ts +6 -0
- package/lib/dsh/index.d.ts.map +1 -0
- package/lib/dsh/index.js +11 -0
- package/lib/dsh/index.js.map +1 -0
- package/lib/dsh/super-code.d.ts +22 -0
- package/lib/dsh/super-code.d.ts.map +1 -0
- package/lib/dsh/super-code.js +247 -0
- package/lib/dsh/super-code.js.map +1 -0
- package/lib/evaluate.d.ts +3 -0
- package/lib/evaluate.d.ts.map +1 -0
- package/lib/evaluate.js +40 -0
- package/lib/evaluate.js.map +1 -0
- package/lib/experiments.d.ts +77 -0
- package/lib/experiments.d.ts.map +1 -0
- package/lib/experiments.js +196 -0
- package/lib/experiments.js.map +1 -0
- package/lib/index.d.ts +13 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +14 -0
- package/lib/index.js.map +1 -0
- package/lib/super-agent-usage.d.ts +44 -0
- package/lib/super-agent-usage.d.ts.map +1 -0
- package/lib/super-agent-usage.js +203 -0
- package/lib/super-agent-usage.js.map +1 -0
- package/lib/task-memory-projection.d.ts +43 -0
- package/lib/task-memory-projection.d.ts.map +1 -0
- package/lib/task-memory-projection.js +48 -0
- package/lib/task-memory-projection.js.map +1 -0
- package/lib/task-memory-store.d.ts +35 -0
- package/lib/task-memory-store.d.ts.map +1 -0
- package/lib/task-memory-store.js +69 -0
- package/lib/task-memory-store.js.map +1 -0
- package/package.json +123 -0
- package/presets/super-code/agent.cordis.yml +50 -0
- package/presets/super-code/preset.yml +3 -0
package/client/index.js
ADDED
|
@@ -0,0 +1,920 @@
|
|
|
1
|
+
/* Browser entry for dsh-super-code. It deliberately uses the Harness public
|
|
2
|
+
* slot and Remote faces; no DOM selectors or private React internals are used.
|
|
3
|
+
*
|
|
4
|
+
* The Harness client module table serves classic scripts and expects every
|
|
5
|
+
* plugin artifact to register a closure factory. Keeping the small bundle
|
|
6
|
+
* self-contained here also makes the published package usable outside the
|
|
7
|
+
* Harness monorepo's tsdown workspace.
|
|
8
|
+
*/
|
|
9
|
+
;(globalThis.window || globalThis).__ModuleLoader__.load({ id: 'dsh-super-code', factory: (require) => {
|
|
10
|
+
const React = require('react')
|
|
11
|
+
const { useEffect, useLayoutEffect, useRef, useMemo, useState, useSyncExternalStore } = React
|
|
12
|
+
const { IconRefreshOutline16 } = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
13
|
+
const { SessionEventStream } = require('@deepseek-ai/dsh-api-session-controller')
|
|
14
|
+
|
|
15
|
+
function TaskMemoryPanel({ state }) {
|
|
16
|
+
const tasks = state?.tasks || []
|
|
17
|
+
if (!tasks.length) return null
|
|
18
|
+
const current = state.current?.kind === 'task' ? state.current : undefined
|
|
19
|
+
const labels = { active: '进行中', paused: '已暂停', completed: '已完成', cancelled: '已取消' }
|
|
20
|
+
return React.createElement('section', { className: 'dsh-super-code-task-memory', 'aria-label': '任务档案' },
|
|
21
|
+
current && React.createElement(React.Fragment, null,
|
|
22
|
+
React.createElement('div', { className: 'dsh-super-code-task-heading' },
|
|
23
|
+
React.createElement('strong', null, current.title),
|
|
24
|
+
React.createElement('span', null, labels[current.status] || current.status)),
|
|
25
|
+
current.goal && React.createElement('p', null, current.goal),
|
|
26
|
+
current.next && React.createElement('p', null, '下一步:', current.next)),
|
|
27
|
+
tasks.some(task => task.id !== current?.id) && React.createElement('details', null,
|
|
28
|
+
React.createElement('summary', null, current ? '其他任务' : '任务记录'),
|
|
29
|
+
React.createElement('ul', null, tasks.filter(task => task.id !== current?.id).map(task => React.createElement('li', { key: task.id }, `${task.title} · ${labels[task.status] || task.status}`)))),
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Join host catalogs without leaking agents from unrelated conversations. */
|
|
34
|
+
function buildAgentView(state) {
|
|
35
|
+
const nodes = new Map(Object.values(state.byId || {}).map(node => [node.id, { ...node }]))
|
|
36
|
+
for (const [parentId, catalog] of Object.entries(state.subagentsByParent || {})) {
|
|
37
|
+
// Navigation rows omit timestamps; the parent's public projection retains them.
|
|
38
|
+
const created = new Map((nodes.get(parentId)?.projectionValues?.subagentCatalog || []).map(entry => [entry.id, entry.createdAt]))
|
|
39
|
+
for (const entry of catalog?.entries || []) {
|
|
40
|
+
const summary = nodes.get(entry.id)
|
|
41
|
+
nodes.set(entry.id, { ...summary, id: entry.id, parentId,
|
|
42
|
+
title: entry.label || summary?.title || summary?.displayTitle || entry.id,
|
|
43
|
+
mode: entry.mode, hasChildren: entry.hasChildren, createdAt: created.get(entry.id),
|
|
44
|
+
running: summary?.running ?? entry.activity === 'running',
|
|
45
|
+
unavailable: entry.kind === 'diagnostic',
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (!state.current) return { root: '', nodes: new Map(), children: new Map(), ancestors: new Set() }
|
|
50
|
+
if (state.currentAddress && !nodes.get(state.current)?.parentId) {
|
|
51
|
+
nodes.set(state.current, { ...nodes.get(state.current), id: state.current,
|
|
52
|
+
parentId: state.currentAddress.parentSessionId, mode: state.currentAddress.mode })
|
|
53
|
+
}
|
|
54
|
+
let root = state.current
|
|
55
|
+
const ancestors = new Set()
|
|
56
|
+
while (!ancestors.has(root)) {
|
|
57
|
+
ancestors.add(root)
|
|
58
|
+
if (!nodes.has(root)) nodes.set(root, { id: root, title: root })
|
|
59
|
+
const parent = nodes.get(root).parentId
|
|
60
|
+
if (!parent || ancestors.has(parent)) break
|
|
61
|
+
root = parent
|
|
62
|
+
}
|
|
63
|
+
const children = new Map()
|
|
64
|
+
for (const node of nodes.values()) {
|
|
65
|
+
if (!children.has(node.parentId)) children.set(node.parentId, [])
|
|
66
|
+
children.get(node.parentId).push(node.id)
|
|
67
|
+
}
|
|
68
|
+
// The public catalog is ordered by creation time; summary insertion order is not.
|
|
69
|
+
for (const [parent, catalog] of Object.entries(state.subagentsByParent || {})) {
|
|
70
|
+
const rank = new Map((catalog.entries || []).map((entry, index) => [entry.id, index]))
|
|
71
|
+
children.get(parent)?.sort((a, b) => (rank.get(a) ?? -1) - (rank.get(b) ?? -1))
|
|
72
|
+
}
|
|
73
|
+
const reachable = new Map(), pending = [root]
|
|
74
|
+
while (pending.length) {
|
|
75
|
+
const id = pending.pop()
|
|
76
|
+
if (reachable.has(id)) continue
|
|
77
|
+
reachable.set(id, nodes.get(id))
|
|
78
|
+
for (const child of children.get(id) || []) pending.push(child)
|
|
79
|
+
}
|
|
80
|
+
return { root, nodes: reachable, children, ancestors }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Iterative traversal bounds rendered rows even for long-running sessions. */
|
|
84
|
+
function visibleAgentRows(view, expanded, limit) {
|
|
85
|
+
const rows = [], seen = new Set(), stack = view.root ? [{ id: view.root, level: 1 }] : []
|
|
86
|
+
while (stack.length && rows.length < limit) {
|
|
87
|
+
const row = stack.pop()
|
|
88
|
+
if (seen.has(row.id)) continue
|
|
89
|
+
seen.add(row.id)
|
|
90
|
+
rows.push(row)
|
|
91
|
+
if (expanded(row.id)) {
|
|
92
|
+
const children = view.children.get(row.id) || []
|
|
93
|
+
for (let i = children.length - 1; i >= 0; i--) stack.push({ id: children[i], level: row.level + 1 })
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return { rows, more: stack.length > 0 }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Presentation only: never archive sessions or discard usage/history. */
|
|
100
|
+
function agentTreeDisplay(view, catalogs = {}, options = {}) {
|
|
101
|
+
const expanded = options.expanded || new Set(), collapsed = options.collapsed || new Set()
|
|
102
|
+
const recent = options.compact ? 2 : 4
|
|
103
|
+
const all = visibleAgentRows(view, () => true, view.nodes.size).rows
|
|
104
|
+
const levels = new Map(all.map(row => [row.id, row.level])), required = new Set(view.ancestors)
|
|
105
|
+
if (options.focused) required.add(options.focused)
|
|
106
|
+
const children = new Map()
|
|
107
|
+
for (let index = all.length - 1; index >= 0; index--) {
|
|
108
|
+
const { id, level } = all[index], node = view.nodes.get(id)
|
|
109
|
+
const original = (view.children.get(id) || []).filter(child => levels.get(child) === level + 1)
|
|
110
|
+
const unknownChildren = node.hasChildren && catalogs[id]?.state !== 'ready'
|
|
111
|
+
if (node.running !== false || node.unavailable || unknownChildren || original.some(child => required.has(child))) required.add(id)
|
|
112
|
+
const deep = level >= 2 && !expanded.has(id)
|
|
113
|
+
const keep = collapsed.has(id) || deep ? new Set() : new Set(original.slice(-recent))
|
|
114
|
+
const visible = original.filter(child => required.has(child) || (!collapsed.has(id) && (expanded.has(id) || keep.has(child))))
|
|
115
|
+
children.set(id, visible)
|
|
116
|
+
}
|
|
117
|
+
const display = { ...view, children }
|
|
118
|
+
const shown = new Set(visibleAgentRows(display, () => true, view.nodes.size).rows.map(row => row.id))
|
|
119
|
+
return { view: display, hidden: all.filter(row => !shown.has(row.id)).map(row => row.id) }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function agentTreeTotals(view, catalogs = {}) {
|
|
123
|
+
const totals = new Map(), order = [], parents = new Map(), seen = new Set(), pending = view.root ? [view.root] : []
|
|
124
|
+
while (pending.length) {
|
|
125
|
+
const id = pending.pop()
|
|
126
|
+
if (seen.has(id)) continue
|
|
127
|
+
seen.add(id); order.push(id)
|
|
128
|
+
for (const child of view.children.get(id) || []) {
|
|
129
|
+
if (!seen.has(child) && !parents.has(child)) { parents.set(child, id); pending.push(child) }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
133
|
+
const id = order[i], node = view.nodes.get(id), projections = node.projectionValues || {}
|
|
134
|
+
const usage = projections.superAgentUsage?.totals || projections.tokenUsage
|
|
135
|
+
const input = usage ? (usage.uncachedInputTokens || 0) + (usage.cacheReadTokens || 0) + (usage.cacheWriteTokens || 0) : 0
|
|
136
|
+
const total = totals.get(id) || { agents: 0, tokens: 0, cache: 0, input: 0, known: 0, partial: false }
|
|
137
|
+
total.agents++; total.tokens += input + (usage?.outputTokens || 0); total.cache += usage?.cacheReadTokens || 0; total.input += input
|
|
138
|
+
if (usage) total.known++
|
|
139
|
+
if (node.unavailable || ((id === view.root || node.hasChildren) && catalogs[id]?.state !== 'ready')) total.partial = true
|
|
140
|
+
totals.set(id, total)
|
|
141
|
+
const parent = parents.get(id)
|
|
142
|
+
if (parent && parent !== id) {
|
|
143
|
+
const sum = totals.get(parent) || { agents: 0, tokens: 0, cache: 0, input: 0, known: 0, partial: false }
|
|
144
|
+
for (const key of ['agents', 'tokens', 'cache', 'input', 'known']) sum[key] += total[key]
|
|
145
|
+
sum.partial ||= total.partial
|
|
146
|
+
totals.set(parent, sum)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return totals
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const historyRowHeight = 56
|
|
153
|
+
const historyCollator = new Intl.Collator('zh-CN', { numeric: true, sensitivity: 'base' })
|
|
154
|
+
const historyDateFormat = new Intl.DateTimeFormat('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
|
|
155
|
+
|
|
156
|
+
function agentHistoryRange(range, from = '', to = '', now = Date.now()) {
|
|
157
|
+
const today = new Date(now)
|
|
158
|
+
today.setHours(0, 0, 0, 0)
|
|
159
|
+
const parse = value => {
|
|
160
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return NaN
|
|
161
|
+
const [year, month, day] = value.split('-').map(Number), date = new Date(year, month - 1, day)
|
|
162
|
+
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day ? date.getTime() : NaN
|
|
163
|
+
}
|
|
164
|
+
let start = -Infinity, end = Infinity
|
|
165
|
+
if (range === 'custom') {
|
|
166
|
+
start = from ? parse(from) : -Infinity
|
|
167
|
+
if (to) { const date = new Date(parse(to)); date.setDate(date.getDate() + 1); end = date.getTime() }
|
|
168
|
+
} else if (range !== 'all') {
|
|
169
|
+
const tomorrow = new Date(today)
|
|
170
|
+
tomorrow.setDate(tomorrow.getDate() + 1)
|
|
171
|
+
today.setDate(today.getDate() - ({ today: 0, week: 6, month: 29 }[range] ?? 0))
|
|
172
|
+
start = today.getTime(); end = tomorrow.getTime()
|
|
173
|
+
}
|
|
174
|
+
return { start, end, valid: !Number.isNaN(start) && !Number.isNaN(end) && start < end }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function agentHistoryRows(view, hidden, totals, options = {}) {
|
|
178
|
+
const query = (options.query || '').trim().toLocaleLowerCase()
|
|
179
|
+
const range = agentHistoryRange(options.range || 'all', options.from, options.to, options.now)
|
|
180
|
+
if (!range.valid) return []
|
|
181
|
+
const rows = []
|
|
182
|
+
for (const id of hidden) {
|
|
183
|
+
const node = view.nodes.get(id), title = node.title || node.displayTitle || id
|
|
184
|
+
const parent = view.nodes.get(node.parentId)?.title || '主 Agent', usage = totals.get(id)
|
|
185
|
+
const time = Number.isFinite(node.createdAt) && node.createdAt >= 0 && node.createdAt <= 8640000000000000 ? node.createdAt : -1
|
|
186
|
+
if (query && !`${title} ${parent}`.toLocaleLowerCase().includes(query)) continue
|
|
187
|
+
if (options.range && options.range !== 'all' && (time < 0 || time < range.start || time >= range.end)) continue
|
|
188
|
+
rows.push({ id, node, title, parent, time, tokens: usage?.known ? usage.tokens : -1, partial: !!usage && (usage.partial || usage.known < usage.agents) })
|
|
189
|
+
}
|
|
190
|
+
const [field, direction] = (options.sort || 'time-desc').split('-'), sign = direction === 'asc' ? 1 : -1
|
|
191
|
+
return rows.sort((a, b) => {
|
|
192
|
+
let comparison
|
|
193
|
+
if (field === 'title') comparison = historyCollator.compare(a.title, b.title)
|
|
194
|
+
else {
|
|
195
|
+
const left = field === 'tokens' ? a.tokens : a.time, right = field === 'tokens' ? b.tokens : b.time
|
|
196
|
+
// Missing metadata stays last in either direction, never masquerades as zero.
|
|
197
|
+
if ((left < 0) !== (right < 0)) return left < 0 ? 1 : -1
|
|
198
|
+
comparison = left - right
|
|
199
|
+
}
|
|
200
|
+
return comparison * sign || historyCollator.compare(a.title, b.title) || a.id.localeCompare(b.id)
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function agentHistoryWindow(count, scrollTop, height) {
|
|
205
|
+
const top = Math.max(0, Math.min(scrollTop, Math.max(0, count * historyRowHeight - height)))
|
|
206
|
+
return { top, start: Math.max(0, Math.floor(top / historyRowHeight) - 3),
|
|
207
|
+
end: Math.min(count, Math.ceil((top + height) / historyRowHeight) + 3), height: count * historyRowHeight }
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Local scroll state keeps the graph out of the scrolling render path. */
|
|
211
|
+
function AgentHistory({ view, hidden, totals, open }) {
|
|
212
|
+
const [filters, setFilters] = useState({ query: '', range: 'all', from: '', to: '', sort: 'time-desc' })
|
|
213
|
+
const [scroll, setScroll] = useState({ top: 0, height: 168 })
|
|
214
|
+
const viewport = useRef(null), focusTarget = useRef('')
|
|
215
|
+
const records = useMemo(() => agentHistoryRows(view, hidden, totals, filters), [view, hidden, totals, filters])
|
|
216
|
+
const window = agentHistoryWindow(records.length, scroll.top, scroll.height)
|
|
217
|
+
const range = agentHistoryRange(filters.range, filters.from, filters.to)
|
|
218
|
+
const change = (key, value) => setFilters(previous => ({ ...previous, [key]: value }))
|
|
219
|
+
useLayoutEffect(() => {
|
|
220
|
+
viewport.current.scrollTop = 0
|
|
221
|
+
setScroll(previous => ({ ...previous, top: 0 }))
|
|
222
|
+
}, [filters])
|
|
223
|
+
useLayoutEffect(() => {
|
|
224
|
+
const element = viewport.current
|
|
225
|
+
const measure = () => setScroll(previous => previous.top === element.scrollTop && previous.height === element.clientHeight
|
|
226
|
+
? previous : { top: element.scrollTop, height: element.clientHeight })
|
|
227
|
+
measure()
|
|
228
|
+
const observer = new ResizeObserver(measure)
|
|
229
|
+
observer.observe(element)
|
|
230
|
+
return () => observer.disconnect()
|
|
231
|
+
}, [])
|
|
232
|
+
useLayoutEffect(() => {
|
|
233
|
+
viewport.current.scrollTop = window.top
|
|
234
|
+
if (focusTarget.current) {
|
|
235
|
+
const target = Array.from(viewport.current.querySelectorAll('button')).find(button => button.dataset.historyId === focusTarget.current)
|
|
236
|
+
if (target) { target.focus({ preventScroll: true }); focusTarget.current = '' }
|
|
237
|
+
}
|
|
238
|
+
}, [window.top, window.start, window.end, records, scroll])
|
|
239
|
+
const keyDown = (event, index) => {
|
|
240
|
+
const next = { ArrowDown: index + 1, ArrowUp: index - 1, Home: 0, End: records.length - 1 }[event.key]
|
|
241
|
+
if (next === undefined) return
|
|
242
|
+
event.preventDefault()
|
|
243
|
+
const target = Math.max(0, Math.min(records.length - 1, next)), element = viewport.current
|
|
244
|
+
focusTarget.current = records[target].id
|
|
245
|
+
if (target * historyRowHeight < element.scrollTop) element.scrollTop = target * historyRowHeight
|
|
246
|
+
else if ((target + 1) * historyRowHeight > element.scrollTop + element.clientHeight) element.scrollTop = (target + 1) * historyRowHeight - element.clientHeight
|
|
247
|
+
setScroll({ top: element.scrollTop, height: element.clientHeight })
|
|
248
|
+
}
|
|
249
|
+
const select = (key, label, choices) => React.createElement('label', null, label,
|
|
250
|
+
React.createElement('select', { value: filters[key], 'aria-label': label, onChange: event => change(key, event.target.value) },
|
|
251
|
+
choices.map(([value, text]) => React.createElement('option', { key: value, value }, text))))
|
|
252
|
+
return React.createElement('section', { className: 'dsh-super-code-history', 'aria-label': '已收起的 Agent' },
|
|
253
|
+
React.createElement('input', { type: 'search', value: filters.query, 'aria-label': '查找收起的 Agent', placeholder: '搜索 Agent 或上级任务', onChange: event => change('query', event.target.value) }),
|
|
254
|
+
React.createElement('div', { className: 'dsh-super-code-history-filters' },
|
|
255
|
+
select('range', '创建时间', [['all', '全部时间'], ['today', '今天'], ['week', '近 7 天'], ['month', '近 30 天'], ['custom', '自定义']]),
|
|
256
|
+
select('sort', '排序', [['time-desc', '最新创建'], ['time-asc', '最早创建'], ['tokens-desc', 'Token 从多到少'], ['tokens-asc', 'Token 从少到多'], ['title-asc', '标题正序'], ['title-desc', '标题倒序']])),
|
|
257
|
+
filters.range === 'custom' && React.createElement('div', { className: 'dsh-super-code-history-filters' },
|
|
258
|
+
['from', 'to'].map((key, index) => React.createElement('label', { key }, index ? '结束日期' : '开始日期',
|
|
259
|
+
React.createElement('input', { type: 'date', value: filters[key], 'aria-label': index ? '结束日期' : '开始日期', onChange: event => change(key, event.target.value) })))),
|
|
260
|
+
React.createElement('p', { className: 'dsh-super-code-history-summary', role: 'status' }, range.valid ? `${records.length} / ${hidden.length} 个已收起节点 · Token 含下级` : '请选择有效的起止日期'),
|
|
261
|
+
React.createElement('div', { ref: viewport, className: 'dsh-super-code-history-list', role: 'list', 'aria-label': '收起节点列表',
|
|
262
|
+
onScroll: event => { const top = event.currentTarget.scrollTop; setScroll(previous => previous.top === top ? previous : { ...previous, top }) } },
|
|
263
|
+
React.createElement('div', { style: { height: window.height, position: 'relative' } }, records.slice(window.start, window.end).map((record, offset) => {
|
|
264
|
+
const index = window.start + offset, status = agentExecutionStatus(record.node, false)
|
|
265
|
+
const date = record.time < 0 ? '时间未知' : historyDateFormat.format(record.time)
|
|
266
|
+
return React.createElement('div', { key: record.id, role: 'listitem', 'aria-posinset': index + 1, 'aria-setsize': records.length,
|
|
267
|
+
style: { position: 'absolute', top: index * historyRowHeight, height: historyRowHeight, width: '100%' } },
|
|
268
|
+
React.createElement('button', { type: 'button', 'data-history-id': record.id, onClick: () => open(record.node), onKeyDown: event => keyDown(event, index),
|
|
269
|
+
title: `${record.title} · ${record.parent} · ${record.time < 0 ? date : new Date(record.time).toLocaleString('zh-CN')} · ${record.tokens < 0 ? '用量未知' : record.tokens + ' Token' + (record.partial ? '(部分)' : '')}`,
|
|
270
|
+
'aria-label': `查看 ${record.title} 的详情` },
|
|
271
|
+
React.createElement('i', { className: 'dsh-super-code-circle ' + status.tone, 'aria-hidden': true }),
|
|
272
|
+
React.createElement('span', null, React.createElement('strong', null, record.title), React.createElement('small', null, `${status.label} · ${date} · ${record.parent}`)),
|
|
273
|
+
React.createElement('small', null, record.tokens < 0 ? '—' : `${record.partial ? '≈' : ''}${record.tokens.toLocaleString('zh-CN', { notation: 'compact', maximumFractionDigits: 1 })} tok`)))
|
|
274
|
+
})),
|
|
275
|
+
!records.length && React.createElement('p', { className: 'dsh-super-code-history-empty' }, '没有匹配的节点')))
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function AgentTree({ useSessions, useTabInfo, treeStates, openDetail, refresh, watchCatalog }) {
|
|
279
|
+
const state = useSessions(s => s)
|
|
280
|
+
const { tab } = useTabInfo()
|
|
281
|
+
const view = useMemo(() => buildAgentView(state), [state.byId, state.subagentsByParent, state.current, state.currentAddress])
|
|
282
|
+
const saved = useMemo(() => {
|
|
283
|
+
let value = treeStates.get(tab.signal)
|
|
284
|
+
if (!value || value.root !== view.root) {
|
|
285
|
+
value = { root: view.root, limit: 200, focused: state.current, expanded: new Set(), collapsed: new Set() }
|
|
286
|
+
treeStates.set(tab.signal, value)
|
|
287
|
+
}
|
|
288
|
+
return value
|
|
289
|
+
}, [treeStates, tab.signal, view.root])
|
|
290
|
+
return React.createElement(AgentTreeBody, { key: view.root, state, view, saved, openDetail, refresh, watchCatalog })
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function AgentCatalogWatch({ id, watchCatalog }) {
|
|
294
|
+
useEffect(() => {
|
|
295
|
+
watchCatalog(id, true)
|
|
296
|
+
return () => watchCatalog(id, false)
|
|
297
|
+
}, [id, watchCatalog])
|
|
298
|
+
return null
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function agentTreeLayout(view, limit) {
|
|
302
|
+
const { rows, more } = visibleAgentRows(view, () => true, limit)
|
|
303
|
+
const positions = new Map(), edges = []
|
|
304
|
+
let leaves = 0
|
|
305
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
306
|
+
const row = rows[i]
|
|
307
|
+
const children = (view.children.get(row.id) || []).filter(id => positions.has(id) && positions.get(id).level === row.level + 1)
|
|
308
|
+
const x = children.length ? children.reduce((sum, id) => sum + positions.get(id).x, 0) / children.length : 70 + leaves++ * 140
|
|
309
|
+
positions.set(row.id, { ...row, x, y: 18 + (row.level - 1) * 128 })
|
|
310
|
+
for (const child of children) edges.push({ parent: row.id, child })
|
|
311
|
+
}
|
|
312
|
+
const width = Math.max(140, leaves * 140)
|
|
313
|
+
// Reverse the postorder leaf allocation to preserve catalog order.
|
|
314
|
+
for (const position of positions.values()) position.x = width - position.x
|
|
315
|
+
return { rows: rows.map(row => positions.get(row.id)), edges, positions, more, width,
|
|
316
|
+
height: Math.max(140, ...rows.map(row => row.level * 128)) }
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function agentExecutionStatus(node, isRoot) {
|
|
320
|
+
if (!node || node.unavailable || typeof node.running !== 'boolean') return { tone: 'unavailable', label: '记录不可用' }
|
|
321
|
+
if (node.running) return { tone: 'active', label: '运行中' }
|
|
322
|
+
return isRoot || node.mode === 'continuable' ? { tone: 'idle', label: '待命' } : { tone: 'stopped', label: '已停止' }
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function AgentTreeBody({ state, view, saved, openDetail, refresh, watchCatalog }) {
|
|
326
|
+
const [pan, setPan] = useState(saved.pan || { x: 0, y: 0 })
|
|
327
|
+
const [zoom, setZoom] = useState(saved.zoom || 1)
|
|
328
|
+
const viewport = useRef(null), graph = useRef(null)
|
|
329
|
+
const autoFit = useRef(!saved.pan)
|
|
330
|
+
const camera = useRef({ pan, zoom })
|
|
331
|
+
camera.current = { pan, zoom }
|
|
332
|
+
const changeCamera = (nextPan, nextZoom) => {
|
|
333
|
+
saved.pan = nextPan; saved.zoom = nextZoom
|
|
334
|
+
camera.current = { pan: nextPan, zoom: nextZoom }
|
|
335
|
+
setPan(nextPan); setZoom(nextZoom)
|
|
336
|
+
}
|
|
337
|
+
const zoomAt = (factor, x, y) => {
|
|
338
|
+
autoFit.current = false
|
|
339
|
+
const current = camera.current, rect = graph.current.getBoundingClientRect()
|
|
340
|
+
const next = Math.max(.4, Math.min(2, current.zoom * factor))
|
|
341
|
+
const dx = (x - rect.left) / current.zoom, dy = (y - rect.top) / current.zoom
|
|
342
|
+
changeCamera({ x: current.pan.x - dx * (next - current.zoom), y: current.pan.y - dy * (next - current.zoom) }, next)
|
|
343
|
+
}
|
|
344
|
+
useEffect(() => {
|
|
345
|
+
const element = viewport.current
|
|
346
|
+
const wheel = event => { event.preventDefault(); zoomAt(Math.exp(-event.deltaY * (event.deltaMode === 1 ? .04 : .002)), event.clientX, event.clientY) }
|
|
347
|
+
element.addEventListener('wheel', wheel, { passive: false })
|
|
348
|
+
return () => element.removeEventListener('wheel', wheel)
|
|
349
|
+
}, [])
|
|
350
|
+
const gesture = useRef({ moved: false })
|
|
351
|
+
const finishDrag = event => {
|
|
352
|
+
const drag = gesture.current
|
|
353
|
+
if (drag.id !== event.pointerId) return
|
|
354
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId)
|
|
355
|
+
drag.id = undefined
|
|
356
|
+
event.currentTarget.classList.remove('dragging')
|
|
357
|
+
}
|
|
358
|
+
const [limit, setLimit] = useState(saved.limit)
|
|
359
|
+
const [error, setError] = useState('')
|
|
360
|
+
const [busy, setBusy] = useState(false)
|
|
361
|
+
const [focused, setFocused] = useState(saved.focused)
|
|
362
|
+
const [expanded, setExpanded] = useState(saved.expanded || new Set())
|
|
363
|
+
const [collapsed, setCollapsed] = useState(saved.collapsed || new Set())
|
|
364
|
+
const [historyOpen, setHistoryOpen] = useState(false)
|
|
365
|
+
const [compact, setCompact] = useState(false)
|
|
366
|
+
useEffect(() => { Object.assign(saved, { limit, focused, expanded, collapsed }) }, [saved, limit, focused, expanded, collapsed])
|
|
367
|
+
const catalogs = state.subagentsByParent || {}
|
|
368
|
+
const display = useMemo(() => agentTreeDisplay(view, catalogs, { expanded, collapsed, focused, compact }), [view, catalogs, expanded, collapsed, focused, compact])
|
|
369
|
+
const { rows, more, edges, positions, width, height } = useMemo(() => agentTreeLayout(display.view, limit), [display, limit])
|
|
370
|
+
const toggleBranch = id => {
|
|
371
|
+
autoFit.current = true
|
|
372
|
+
const show = collapsed.has(id) || !(display.view.children.get(id) || []).length
|
|
373
|
+
const nextExpanded = new Set(expanded), nextCollapsed = new Set(collapsed)
|
|
374
|
+
nextExpanded.delete(id); nextCollapsed.delete(id)
|
|
375
|
+
if (show) nextExpanded.add(id); else nextCollapsed.add(id)
|
|
376
|
+
setExpanded(nextExpanded); setCollapsed(nextCollapsed)
|
|
377
|
+
}
|
|
378
|
+
const fit = () => {
|
|
379
|
+
autoFit.current = true
|
|
380
|
+
const element = viewport.current
|
|
381
|
+
const next = Math.max(.4, Math.min(1.25, (element.clientWidth - 24) / width, (element.clientHeight - 24) / height))
|
|
382
|
+
changeCamera({ x: (element.clientWidth - 24 - width * next) / 2, y: 8 }, next)
|
|
383
|
+
element.scrollLeft = 0; element.scrollTop = 0
|
|
384
|
+
}
|
|
385
|
+
useLayoutEffect(() => {
|
|
386
|
+
const resize = () => {
|
|
387
|
+
setCompact(viewport.current.clientWidth < 400)
|
|
388
|
+
if (autoFit.current) fit()
|
|
389
|
+
}
|
|
390
|
+
resize()
|
|
391
|
+
const observer = new ResizeObserver(resize)
|
|
392
|
+
observer.observe(viewport.current)
|
|
393
|
+
return () => observer.disconnect()
|
|
394
|
+
}, [width, height])
|
|
395
|
+
const zoomButton = factor => {
|
|
396
|
+
const rect = viewport.current.getBoundingClientRect()
|
|
397
|
+
zoomAt(factor, rect.left + rect.width / 2, rect.top + rect.height / 2)
|
|
398
|
+
}
|
|
399
|
+
const totals = useMemo(() => agentTreeTotals(view, catalogs), [view, catalogs])
|
|
400
|
+
// Retain already-loaded branch subscriptions so a hidden worker can become visible.
|
|
401
|
+
const watched = [...new Set([...rows.filter(({ id }) => id === view.root || view.nodes.get(id)?.hasChildren).map(row => row.id),
|
|
402
|
+
...Object.keys(catalogs).filter(id => view.nodes.has(id))])]
|
|
403
|
+
const open = node => {
|
|
404
|
+
setError('')
|
|
405
|
+
Promise.resolve().then(() => openDetail(node)).catch(() => setError('无法打开详情,请重试'))
|
|
406
|
+
}
|
|
407
|
+
const reload = async () => {
|
|
408
|
+
setBusy(true); setError('')
|
|
409
|
+
try { await Promise.all([...new Set([view.root, ...watched])].filter(Boolean).map(id => refresh(id))) }
|
|
410
|
+
catch { setError('执行记录刷新失败') }
|
|
411
|
+
finally { setBusy(false) }
|
|
412
|
+
}
|
|
413
|
+
const keyDown = (event, row) => {
|
|
414
|
+
const index = rows.findIndex(item => item.id === row.id)
|
|
415
|
+
let target
|
|
416
|
+
if (event.key === 'ArrowDown') target = rows[Math.min(rows.length - 1, index + 1)]?.id
|
|
417
|
+
else if (event.key === 'ArrowUp') target = rows[Math.max(0, index - 1)]?.id
|
|
418
|
+
else if (event.key === 'Home') target = rows[0]?.id
|
|
419
|
+
else if (event.key === 'End') target = rows.at(-1)?.id
|
|
420
|
+
else if (event.key === 'ArrowRight') {
|
|
421
|
+
target = rows[index + 1]?.level > row.level ? rows[index + 1].id : row.id
|
|
422
|
+
} else if (event.key === 'ArrowLeft') {
|
|
423
|
+
target = view.nodes.get(row.id).parentId
|
|
424
|
+
} else if (event.key === 'Enter' || event.key === ' ') {
|
|
425
|
+
if (!view.nodes.get(row.id).unavailable) open(view.nodes.get(row.id))
|
|
426
|
+
} else return
|
|
427
|
+
event.preventDefault()
|
|
428
|
+
if (target) {
|
|
429
|
+
setFocused(target)
|
|
430
|
+
const tree = event.currentTarget.closest('[role="tree"]')
|
|
431
|
+
Array.from(tree.querySelectorAll('[role="treeitem"]')).find(item => item.dataset.agentId === target)?.focus()
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const focusId = rows.some(row => row.id === focused) ? focused : view.root
|
|
435
|
+
const running = [...view.nodes.values()].filter(node => node.running).length
|
|
436
|
+
const catalogError = watched.some(id => catalogs[id]?.state === 'error')
|
|
437
|
+
return React.createElement('section', { className: 'dsh-super-code-tree', 'data-super-agent-tree': true },
|
|
438
|
+
watched.map(id => React.createElement(AgentCatalogWatch, { key: id, id, watchCatalog })),
|
|
439
|
+
React.createElement('header', { className: 'dsh-super-code-tree-header' },
|
|
440
|
+
React.createElement('div', null,
|
|
441
|
+
React.createElement('strong', null, '协作执行'),
|
|
442
|
+
React.createElement('span', { className: 'dsh-super-code-tree-count' }, `显示 ${rows.length} / ${view.nodes.size} · 运行中 ${running}`)),
|
|
443
|
+
React.createElement('button', { type: 'button', className: 'dsh-super-code-icon-button', disabled: busy || !view.root, onClick: reload, title: '刷新执行记录', 'aria-label': '刷新执行记录' }, React.createElement(IconRefreshOutline16))),
|
|
444
|
+
(error || catalogError) && React.createElement('p', { role: 'alert', className: 'dsh-super-code-tree-error' }, error || '部分执行记录读取失败,请刷新重试'),
|
|
445
|
+
React.createElement('div', { className: 'dsh-super-code-graph-legend', 'aria-label': '状态颜色' },
|
|
446
|
+
['运行中', '待命', '已停止', '记录不可用'].map((label, index) => React.createElement('span', { key: label },
|
|
447
|
+
React.createElement('i', { className: 'dsh-super-code-circle ' + ['active', 'idle', 'stopped', 'unavailable'][index], 'aria-hidden': true }), label))),
|
|
448
|
+
!view.root && React.createElement('p', { className: 'dsh-super-code-tree-empty' }, '暂无会话'),
|
|
449
|
+
React.createElement('div', { className: 'dsh-super-code-graph-controls', 'aria-label': '画布操作' },
|
|
450
|
+
React.createElement('button', { type: 'button', 'aria-label': '缩小节点图', disabled: zoom <= .4, onClick: () => zoomButton(1 / 1.2) }, '−'),
|
|
451
|
+
React.createElement('span', { 'aria-live': 'polite' }, `${Math.round(zoom * 100)}%`),
|
|
452
|
+
React.createElement('button', { type: 'button', 'aria-label': '放大节点图', disabled: zoom >= 2, onClick: () => zoomButton(1.2) }, '+'),
|
|
453
|
+
React.createElement('button', { type: 'button', onClick: fit }, '适应窗口')),
|
|
454
|
+
(display.hidden.length > 0 || expanded.size > 0 || collapsed.size > 0) && React.createElement('div', { className: 'dsh-super-code-history-controls' },
|
|
455
|
+
React.createElement('button', { type: 'button', 'aria-expanded': historyOpen, onClick: () => setHistoryOpen(!historyOpen) }, `已收起 ${display.hidden.length}`),
|
|
456
|
+
(expanded.size > 0 || collapsed.size > 0) && React.createElement('button', { type: 'button', onClick: () => { autoFit.current = true; setExpanded(new Set()); setCollapsed(new Set()); setFocused(state.current) } }, '自动整理')),
|
|
457
|
+
historyOpen && React.createElement(AgentHistory, { view, hidden: display.hidden, totals, open }),
|
|
458
|
+
React.createElement('div', { ref: viewport, className: 'dsh-super-code-graph-scroll',
|
|
459
|
+
onPointerDown: event => {
|
|
460
|
+
if (!event.isPrimary || event.button !== 0) return
|
|
461
|
+
gesture.current = { id: event.pointerId, x: event.clientX, y: event.clientY, origin: pan, moved: false }
|
|
462
|
+
},
|
|
463
|
+
onPointerMove: event => {
|
|
464
|
+
const drag = gesture.current
|
|
465
|
+
if (drag.id !== event.pointerId) return
|
|
466
|
+
const dx = event.clientX - drag.x, dy = event.clientY - drag.y
|
|
467
|
+
if (!drag.moved && Math.hypot(dx, dy) < 6) return
|
|
468
|
+
autoFit.current = false
|
|
469
|
+
if (!drag.moved) { drag.moved = true; event.currentTarget.setPointerCapture(event.pointerId) }
|
|
470
|
+
event.currentTarget.classList.add('dragging')
|
|
471
|
+
const next = { x: drag.origin.x + dx, y: drag.origin.y + dy }
|
|
472
|
+
changeCamera(next, camera.current.zoom)
|
|
473
|
+
},
|
|
474
|
+
onPointerUp: finishDrag, onPointerCancel: finishDrag, onLostPointerCapture: finishDrag,
|
|
475
|
+
onClickCapture: event => { if (gesture.current.moved && event.detail !== 0) { event.preventDefault(); event.stopPropagation() } },
|
|
476
|
+
onDoubleClick: event => { if (!event.target.closest('[role="treeitem"]')) fit() },
|
|
477
|
+
title: '拖动平移,滚轮缩放,双击空白适应窗口',
|
|
478
|
+
},
|
|
479
|
+
React.createElement('div', { ref: graph, role: 'tree', 'aria-label': 'Agent 执行树', className: 'dsh-super-code-graph', style: { width, height, transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})` } },
|
|
480
|
+
React.createElement('svg', { width, height, className: 'dsh-super-code-graph-edges', 'aria-hidden': true }, edges.map(edge => {
|
|
481
|
+
const parent = positions.get(edge.parent), child = positions.get(edge.child)
|
|
482
|
+
return React.createElement('path', { key: edge.child, d: `M ${parent.x} ${parent.y + 105} V ${child.y - 12} H ${child.x} V ${child.y}`, fill: 'none' })
|
|
483
|
+
})),
|
|
484
|
+
rows.map(row => {
|
|
485
|
+
const node = view.nodes.get(row.id)
|
|
486
|
+
const title = node.title || node.displayTitle || '主 Agent'
|
|
487
|
+
const { label: status, tone } = agentExecutionStatus(node, row.id === view.root)
|
|
488
|
+
const usage = totals.get(row.id)
|
|
489
|
+
const partial = usage.partial || usage.known < usage.agents
|
|
490
|
+
const number = value => value.toLocaleString('zh-CN', { notation: 'compact', maximumFractionDigits: 1 })
|
|
491
|
+
const hasChildren = (view.children.get(row.id) || []).length > 0
|
|
492
|
+
const isExpanded = (display.view.children.get(row.id) || []).length > 0
|
|
493
|
+
return React.createElement(React.Fragment, { key: row.id }, React.createElement('button', {
|
|
494
|
+
type: 'button',
|
|
495
|
+
role: 'treeitem', tabIndex: row.id === focusId ? 0 : -1, 'data-agent-id': row.id,
|
|
496
|
+
'aria-level': row.level, 'aria-selected': row.id === state.current,
|
|
497
|
+
'aria-expanded': hasChildren ? isExpanded : undefined,
|
|
498
|
+
'aria-disabled': node.unavailable || undefined, 'aria-label': `${title},${status}`,
|
|
499
|
+
className: `dsh-super-code-graph-node${row.id === state.current ? ' selected' : ''}${node.running ? ' running' : ''}`,
|
|
500
|
+
style: { left: row.x - 64, top: row.y }, title: `${title} · ${status} · 点击查看详情`,
|
|
501
|
+
onFocus: () => setFocused(row.id), onClick: () => { if (!node.unavailable) open(node) },
|
|
502
|
+
onKeyDown: event => { if (event.target === event.currentTarget) keyDown(event, row) },
|
|
503
|
+
},
|
|
504
|
+
React.createElement('span', { className: 'dsh-super-code-circle ' + tone, 'aria-hidden': true }, row.id === view.root ? '主' : String(rows.findIndex(item => item.id === row.id))),
|
|
505
|
+
React.createElement('span', { className: 'dsh-super-code-tree-content' },
|
|
506
|
+
React.createElement('span', { className: 'dsh-super-code-tree-title' }, title),
|
|
507
|
+
React.createElement('span', { className: 'dsh-super-code-tree-usage', title: `本节点及所有已知下级合计,包含本节点。${partial ? '部分记录或用量尚不可用;仅显示已知值。' : ''}Token ${usage.tokens.toLocaleString('zh-CN')};缓存读取 ${usage.cache.toLocaleString('zh-CN')};${usage.agents} 个 Agent` },
|
|
508
|
+
React.createElement('span', null, usage.known ? `${number(usage.tokens)} token` : '用量 —'),
|
|
509
|
+
React.createElement('span', null, usage.known ? `缓存 ${usage.input ? Math.round(usage.cache / usage.input * 100) : 0}%${partial ? ' · 部分' : ''}` : '缓存 —')))),
|
|
510
|
+
hasChildren && React.createElement('button', { type: 'button', className: 'dsh-super-code-branch-toggle',
|
|
511
|
+
style: { left: row.x + 21, top: row.y + 25 }, 'aria-label': `${collapsed.has(row.id) || !isExpanded ? '展开' : '收起'} ${title} 的下级`,
|
|
512
|
+
title: '整理下级节点;运行中的分支始终保留', onClick: () => toggleBranch(row.id) },
|
|
513
|
+
React.createElement('svg', { width: 12, height: 12, viewBox: '0 0 12 12', 'aria-hidden': true, style: { transform: collapsed.has(row.id) || !isExpanded ? 'rotate(-90deg)' : undefined } },
|
|
514
|
+
React.createElement('path', { d: 'M3 4.5 6 7.5 9 4.5', fill: 'none', stroke: 'currentColor', strokeWidth: 1.5 }))))
|
|
515
|
+
}))),
|
|
516
|
+
more && React.createElement('button', { type: 'button', className: 'dsh-super-code-tree-more', onClick: () => setLimit(limit + 200) }, '显示更多'))
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function agentDetailAddress(node) {
|
|
520
|
+
const address = new URL('dsh-resource://super-agent/' + encodeURIComponent(node.id))
|
|
521
|
+
if (node.parentId && node.mode) {
|
|
522
|
+
address.searchParams.set('parent', node.parentId)
|
|
523
|
+
address.searchParams.set('mode', node.mode)
|
|
524
|
+
}
|
|
525
|
+
return address.href
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function agentDetailTarget(address) {
|
|
529
|
+
const url = new URL(address)
|
|
530
|
+
if (url.protocol !== 'dsh-resource:' || url.hostname !== 'super-agent') throw new Error('Invalid Agent address')
|
|
531
|
+
const id = decodeURIComponent(url.pathname.slice(1))
|
|
532
|
+
if (!id) throw new Error('Missing Agent identity')
|
|
533
|
+
const parent = url.searchParams.get('parent'), mode = url.searchParams.get('mode')
|
|
534
|
+
if (parent && (mode === 'one-shot' || mode === 'continuable')) return { kind: 'subagent', parentSessionId: parent, childSessionId: id, mode }
|
|
535
|
+
return { kind: 'session', sessionId: id }
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function agentPath(view, id) {
|
|
539
|
+
const path = [], seen = new Set()
|
|
540
|
+
while (id && !seen.has(id)) {
|
|
541
|
+
seen.add(id)
|
|
542
|
+
const node = view.nodes.get(id)
|
|
543
|
+
path.unshift({ id, title: node?.title || node?.displayTitle || id })
|
|
544
|
+
id = node?.parentId
|
|
545
|
+
}
|
|
546
|
+
return path
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function contentText(blocks) {
|
|
550
|
+
return (blocks || []).map(block => {
|
|
551
|
+
if (block.type === 'text' && typeof block.text === 'string') return block.text
|
|
552
|
+
if (block.type === 'image') return '[图片]'
|
|
553
|
+
return ''
|
|
554
|
+
}).filter(Boolean).join('\n\n')
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function detailRecord(entry) {
|
|
558
|
+
const event = entry.event, data = event?.data
|
|
559
|
+
if (!data) return { kind: 'skip' }
|
|
560
|
+
const base = { seq: event.seq, time: event.time }
|
|
561
|
+
if (event.type === 'user/message') {
|
|
562
|
+
if (data.source?.kind !== 'user') return { kind: 'skip' }
|
|
563
|
+
return { ...base, kind: 'message', label: '任务', text: contentText(data.content) }
|
|
564
|
+
}
|
|
565
|
+
if (event.type === 'assistant/message') return { ...base, kind: 'message', label: data.interrupted ? 'Agent · 已中断' : 'Agent', text: contentText(data.message?.content) }
|
|
566
|
+
if (event.type === 'turn/error') return { ...base, kind: 'error', label: '执行错误', text: data.message || '执行未完成,请稍后重试。' }
|
|
567
|
+
return { kind: 'skip' }
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// The host journal owns decoding, cursor validation and reconnects. This view
|
|
571
|
+
// keeps a bounded display window and never stages or sends to another session.
|
|
572
|
+
function createAgentInspector(createStream, address, readPage, initial) {
|
|
573
|
+
let snapshot = initial || { status: 'loading', items: [], hasMore: false, earlier: false, truncated: false, projections: {}, error: '', firstSeq: 0, cursor: -1 }
|
|
574
|
+
let cursor = snapshot.cursor
|
|
575
|
+
let disposed = false, stream
|
|
576
|
+
const listeners = new Set()
|
|
577
|
+
const publish = patch => {
|
|
578
|
+
if (disposed) return
|
|
579
|
+
if (patch.cursor !== undefined) cursor = patch.cursor
|
|
580
|
+
snapshot = { ...snapshot, cursor, ...patch }
|
|
581
|
+
listeners.forEach(listener => listener())
|
|
582
|
+
}
|
|
583
|
+
const itemsOf = entries => entries.map(detailRecord).filter(item => item.kind !== 'skip' && item.text)
|
|
584
|
+
stream = createStream(agentDetailTarget(address), {
|
|
585
|
+
publish: change => {
|
|
586
|
+
if (disposed) return
|
|
587
|
+
if (change.type === 'replace') {
|
|
588
|
+
if (snapshot.earlier) {
|
|
589
|
+
publish({ status: 'ready', projections: change.page.projections?.values || {}, error: '' })
|
|
590
|
+
return
|
|
591
|
+
}
|
|
592
|
+
const fresh = itemsOf(change.entries)
|
|
593
|
+
const sequences = new Set(fresh.map(item => item.seq))
|
|
594
|
+
const overlaps = snapshot.items.some(item => sequences.has(item.seq))
|
|
595
|
+
// A resumed tail page can start later than the user's reading window.
|
|
596
|
+
// Immutable journal records can be merged without moving that window.
|
|
597
|
+
const items = overlaps ? [...new Map([...snapshot.items, ...fresh].map(item => [item.seq, item])).values()].sort((a, b) => a.seq - b.seq) : fresh
|
|
598
|
+
publish({ status: 'ready', items: items.slice(-200), hasMore: (overlaps ? snapshot.hasMore : change.hasMore) || items.length > 200, earlier: false, truncated: items.length > 200,
|
|
599
|
+
firstSeq: items.length > 200 ? items.at(-200).seq : overlaps ? snapshot.firstSeq : change.entries[0]?.event.seq ?? 0,
|
|
600
|
+
cursor: change.entries.at(-1)?.event.seq ?? -1,
|
|
601
|
+
projections: change.page.projections?.values || {}, error: '' })
|
|
602
|
+
} else if (change.type === 'append' && !snapshot.earlier) {
|
|
603
|
+
const item = detailRecord(change.entry)
|
|
604
|
+
cursor = change.entry.event.seq
|
|
605
|
+
if (item.kind !== 'skip' && item.text) {
|
|
606
|
+
const items = [...snapshot.items, item]
|
|
607
|
+
publish({ items: items.slice(-200), cursor, truncated: snapshot.truncated || items.length > 200,
|
|
608
|
+
firstSeq: items.length > 200 ? items.at(-200).seq : snapshot.firstSeq, hasMore: snapshot.hasMore || items.length > 200 })
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
},
|
|
612
|
+
carrierFailed: () => publish({ error: '连接中断,正在重连' }),
|
|
613
|
+
failed: () => publish({ status: 'error', error: '对话详情读取失败' }),
|
|
614
|
+
})
|
|
615
|
+
return {
|
|
616
|
+
getSnapshot: () => snapshot,
|
|
617
|
+
subscribe: listener => { listeners.add(listener); return () => listeners.delete(listener) },
|
|
618
|
+
start: () => { stream.open({ maxMessages: 40 }).catch(() => publish({ status: 'error', error: '对话详情读取失败' })) },
|
|
619
|
+
older: async () => {
|
|
620
|
+
if (snapshot.status === 'paging' || !snapshot.hasMore) return
|
|
621
|
+
publish({ status: 'paging', error: '' })
|
|
622
|
+
const throughSeq = cursor
|
|
623
|
+
try {
|
|
624
|
+
const result = await readPage({ address: agentDetailTarget(address), throughSeq, beforeSeq: snapshot.firstSeq, maxMessages: 40 }, stream.signal)
|
|
625
|
+
if (!result.ok) throw new Error('History unavailable')
|
|
626
|
+
const items = itemsOf(result.value.records), visible = items.slice(-200)
|
|
627
|
+
publish({ status: 'ready', items: visible, earlier: true, cursor: throughSeq, truncated: items.length > 200,
|
|
628
|
+
firstSeq: items.length > 200 ? visible[0].seq : result.value.records[0]?.event.seq ?? 0,
|
|
629
|
+
hasMore: result.value.hasMore || items.length > 200 })
|
|
630
|
+
}
|
|
631
|
+
catch { publish({ status: 'error', error: '历史记录读取失败' }) }
|
|
632
|
+
},
|
|
633
|
+
dispose: () => { disposed = true; listeners.clear(); void stream.dispose() },
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function DetailText({ text }) {
|
|
638
|
+
const [full, setFull] = useState(false)
|
|
639
|
+
return React.createElement(React.Fragment, null,
|
|
640
|
+
React.createElement('pre', null, full ? text : text.slice(0, 12000)),
|
|
641
|
+
!full && text.length > 12000 && React.createElement('button', { type: 'button', onClick: () => setFull(true) }, '展开全文'))
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function AgentDetail({ useTabInfo, useSessions, createInspector, detailStates }) {
|
|
645
|
+
const { tab } = useTabInfo()
|
|
646
|
+
const state = useSessions(s => s)
|
|
647
|
+
const saved = useMemo(() => {
|
|
648
|
+
if (!detailStates.has(tab.signal)) detailStates.set(tab.signal, { top: 0, following: true, open: new Set(), snapshot: undefined })
|
|
649
|
+
return detailStates.get(tab.signal)
|
|
650
|
+
}, [detailStates, tab.signal])
|
|
651
|
+
const log = useRef(null)
|
|
652
|
+
const restored = useRef(false)
|
|
653
|
+
const previous = useRef(saved.snapshot)
|
|
654
|
+
const [unread, setUnread] = useState(saved.unread || false)
|
|
655
|
+
const markUnread = value => { saved.unread = value; setUnread(value) }
|
|
656
|
+
const [revision, setRevision] = useState(0)
|
|
657
|
+
const inspector = useMemo(() => createInspector(tab.contentId, saved.snapshot), [createInspector, tab.contentId, tab.visible, revision])
|
|
658
|
+
const snapshot = useSyncExternalStore(inspector.subscribe, inspector.getSnapshot, inspector.getSnapshot)
|
|
659
|
+
const latest = () => {
|
|
660
|
+
saved.snapshot = undefined; saved.following = true; saved.top = 0
|
|
661
|
+
restored.current = false; previous.current = undefined; markUnread(false)
|
|
662
|
+
setRevision(value => value + 1)
|
|
663
|
+
}
|
|
664
|
+
useLayoutEffect(() => {
|
|
665
|
+
const element = log.current
|
|
666
|
+
if (!element || snapshot.status === 'loading') return
|
|
667
|
+
if (!restored.current) {
|
|
668
|
+
element.scrollTop = saved.following && !snapshot.earlier ? element.scrollHeight : saved.top
|
|
669
|
+
restored.current = true
|
|
670
|
+
} else if (snapshot.items !== previous.current?.items) {
|
|
671
|
+
if (snapshot.earlier && snapshot.firstSeq !== previous.current?.firstSeq) {
|
|
672
|
+
element.scrollTop = 0; saved.following = false
|
|
673
|
+
} else if (saved.following && !snapshot.earlier) element.scrollTop = element.scrollHeight
|
|
674
|
+
else if (snapshot.items.at(-1)?.seq > (previous.current?.items.at(-1)?.seq ?? -1)) markUnread(true)
|
|
675
|
+
}
|
|
676
|
+
saved.top = element.scrollTop; saved.snapshot = snapshot; previous.current = snapshot
|
|
677
|
+
const visible = new Set(snapshot.items.map(item => item.seq))
|
|
678
|
+
for (const key of saved.open) if (key !== 'usage' && !visible.has(key)) saved.open.delete(key)
|
|
679
|
+
}, [snapshot, saved])
|
|
680
|
+
useEffect(() => {
|
|
681
|
+
if (tab.visible) inspector.start()
|
|
682
|
+
return () => inspector.dispose()
|
|
683
|
+
}, [inspector, tab.visible])
|
|
684
|
+
const target = agentDetailTarget(tab.contentId), id = target.childSessionId || target.sessionId
|
|
685
|
+
const view = buildAgentView(state), node = view.nodes.get(id)
|
|
686
|
+
const path = agentPath(view, id)
|
|
687
|
+
const projections = state.byId[id]?.projectionValues || snapshot.projections
|
|
688
|
+
const totals = projections.superAgentUsage?.totals || projections.tokenUsage
|
|
689
|
+
const input = totals ? (totals.uncachedInputTokens || 0) + (totals.cacheReadTokens || 0) + (totals.cacheWriteTokens || 0) : 0
|
|
690
|
+
const format = value => value === undefined ? '未提供' : value.toLocaleString('zh-CN')
|
|
691
|
+
const tasks = projections.superCodeTasks
|
|
692
|
+
const task = tasks?.current?.kind === 'task' ? tasks.current : undefined
|
|
693
|
+
const title = node?.title || node?.displayTitle || 'Agent 详情'
|
|
694
|
+
const metrics = [
|
|
695
|
+
['输入', totals ? input : undefined], ['输出', totals?.outputTokens],
|
|
696
|
+
['缓存读取', totals?.cacheReadTokens], ['缓存写入', totals?.cacheWriteTokens],
|
|
697
|
+
]
|
|
698
|
+
return React.createElement('section', { className: 'dsh-super-code-detail', 'data-agent-detail': id },
|
|
699
|
+
React.createElement('header', { className: 'dsh-super-code-detail-header' },
|
|
700
|
+
React.createElement('nav', { 'aria-label': 'Agent 路径' }, path.map((part, index) => React.createElement('span', { key: part.id }, index > 0 ? ' / ' : '', part.title))),
|
|
701
|
+
React.createElement('div', { className: 'dsh-super-code-detail-heading' },
|
|
702
|
+
React.createElement('div', null,
|
|
703
|
+
React.createElement('h2', null, title),
|
|
704
|
+
React.createElement('span', { className: 'dsh-super-code-detail-status' }, agentExecutionStatus(node, id === view.root).label)),
|
|
705
|
+
React.createElement('button', { type: 'button', className: 'dsh-super-code-icon-button', title: '刷新对话详情', 'aria-label': '刷新对话详情', onClick: latest }, React.createElement(IconRefreshOutline16))),
|
|
706
|
+
id !== view.root && task?.goal && task.goal !== title && React.createElement('p', { className: 'dsh-super-code-detail-task' }, task.goal),
|
|
707
|
+
id === view.root && React.createElement(TaskMemoryPanel, { state: tasks }),
|
|
708
|
+
React.createElement('dl', { className: 'dsh-super-code-detail-metrics' },
|
|
709
|
+
React.createElement('div', null, React.createElement('dt', null, 'Token 总量'), React.createElement('dd', null, format(totals ? input + (totals.outputTokens || 0) : undefined))),
|
|
710
|
+
React.createElement('div', null, React.createElement('dt', null, '缓存命中率'), React.createElement('dd', null, totals ? (input ? Math.round((totals.cacheReadTokens || 0) / input * 100) : 0) + '%' : '未提供'))),
|
|
711
|
+
React.createElement('details', { className: 'dsh-super-code-usage-details', open: saved.open.has('usage'), onToggle: event => { if (event.currentTarget.open) saved.open.add('usage'); else saved.open.delete('usage') } },
|
|
712
|
+
React.createElement('summary', null, '用量详情'),
|
|
713
|
+
React.createElement('dl', { className: 'dsh-super-code-detail-metrics' }, metrics.map(([label, value]) => React.createElement('div', { key: label }, React.createElement('dt', null, label), React.createElement('dd', null, format(value))))))),
|
|
714
|
+
React.createElement('div', { ref: log, className: 'dsh-super-code-detail-log', 'aria-label': 'Agent 执行内容', onScroll: event => {
|
|
715
|
+
const element = event.currentTarget
|
|
716
|
+
saved.top = element.scrollTop
|
|
717
|
+
saved.following = !snapshot.earlier && element.scrollHeight - element.clientHeight - element.scrollTop < 32
|
|
718
|
+
if (saved.following) markUnread(false)
|
|
719
|
+
} },
|
|
720
|
+
snapshot.error && React.createElement('p', { role: 'alert' }, snapshot.error),
|
|
721
|
+
snapshot.status === 'loading' && React.createElement('p', { role: 'status' }, '正在读取对话…'),
|
|
722
|
+
snapshot.hasMore && React.createElement('button', { type: 'button', disabled: snapshot.status === 'paging', onClick: inspector.older }, snapshot.status === 'paging' ? '读取中…' : '加载更早记录'),
|
|
723
|
+
(snapshot.earlier || snapshot.truncated) && React.createElement('button', { type: 'button', onClick: latest }, '回到最新'),
|
|
724
|
+
snapshot.status === 'ready' && !snapshot.items.length && React.createElement('p', null, '暂无执行内容'),
|
|
725
|
+
snapshot.items.map(item => React.createElement('article', { key: item.seq, className: 'dsh-super-code-detail-entry ' + item.kind },
|
|
726
|
+
item.collapsed ? React.createElement('details', { open: saved.open.has(item.seq), onToggle: event => { if (event.currentTarget.open) saved.open.add(item.seq); else saved.open.delete(item.seq) } },
|
|
727
|
+
React.createElement('summary', null, item.label),
|
|
728
|
+
React.createElement(DetailText, { text: item.text }))
|
|
729
|
+
: React.createElement(React.Fragment, null,
|
|
730
|
+
React.createElement('div', { className: 'dsh-super-code-detail-entry-label' }, item.label,
|
|
731
|
+
React.createElement('time', null, new Date(item.time).toLocaleTimeString('zh-CN', { hour12: false }))),
|
|
732
|
+
React.createElement(DetailText, { text: item.text })))) ),
|
|
733
|
+
unread && React.createElement('button', { type: 'button', className: 'dsh-super-code-new-content', onClick: () => {
|
|
734
|
+
saved.following = true; log.current.scrollTop = log.current.scrollHeight; markUnread(false)
|
|
735
|
+
} }, '有新内容 · 查看最新'))
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const inject = [
|
|
739
|
+
'slots', 'remote', 'remote.session', 'sessions', 'locale',
|
|
740
|
+
'sidebarRightTabs', 'sidebarRight',
|
|
741
|
+
]
|
|
742
|
+
|
|
743
|
+
function apply(ctx) {
|
|
744
|
+
ctx.effect(() => {
|
|
745
|
+
const style = document.createElement('style')
|
|
746
|
+
style.dataset.dshSuperCode = 'true'
|
|
747
|
+
style.textContent = `
|
|
748
|
+
.dsh-super-code-tree{--agent-muted:var(--dsw-alias-label-secondary,#70757d);--agent-line:var(--dsw-alias-border-l1,#dedfe3);height:100%;min-width:0;min-height:0;display:flex;flex-direction:column;overflow:auto;font-size:13px;letter-spacing:0;color:inherit}
|
|
749
|
+
.dsh-super-code-tree>header,.dsh-super-code-tree>.dsh-super-code-graph-controls,.dsh-super-code-tree>.dsh-super-code-graph-legend,.dsh-super-code-tree>.dsh-super-code-history-controls{flex-shrink:0}
|
|
750
|
+
.dsh-super-code-tree-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:20px 18px 16px}
|
|
751
|
+
.dsh-super-code-tree-header strong{display:block;font-size:14px;font-weight:600}
|
|
752
|
+
.dsh-super-code-tree-count{display:block;margin-top:5px;color:var(--agent-muted);font-size:12px}
|
|
753
|
+
.dsh-super-code-graph-controls{display:flex;align-items:center;gap:6px;padding:2px 18px 12px;color:var(--agent-muted);font-size:11px}
|
|
754
|
+
.dsh-super-code-graph-controls button{border:1px solid var(--agent-line);border-radius:5px;background:transparent;color:inherit;padding:3px 8px;min-height:28px;cursor:pointer;font:inherit}
|
|
755
|
+
.dsh-super-code-graph-controls button:disabled{opacity:.4;cursor:default}
|
|
756
|
+
.dsh-super-code-graph-controls span{min-width:38px;text-align:center;font-variant-numeric:tabular-nums}
|
|
757
|
+
.dsh-super-code-history-controls{display:flex;gap:8px;padding:0 18px 10px}
|
|
758
|
+
.dsh-super-code-history-controls button{border:1px solid var(--agent-line);border-radius:14px;background:transparent;color:var(--agent-muted);padding:5px 10px;font:inherit;font-size:11px;cursor:pointer}
|
|
759
|
+
.dsh-super-code-history-controls button[aria-expanded=true]{color:var(--dsw-color-primary,#3276dc);background:color-mix(in srgb,currentColor 5%,transparent)}
|
|
760
|
+
.dsh-super-code-history{display:flex;flex-direction:column;flex-shrink:0;box-sizing:border-box;height:46%;min-height:220px;max-height:400px;margin:0 12px 12px;padding:12px;border:1px solid var(--agent-line);border-radius:12px;background:color-mix(in srgb,var(--agent-muted) 3%,transparent)}
|
|
761
|
+
.dsh-super-code-history>p,.dsh-super-code-history>input,.dsh-super-code-history-filters{flex-shrink:0}
|
|
762
|
+
.dsh-super-code-history p{font-size:11px;color:var(--agent-muted);margin:0 0 10px;line-height:1.6}
|
|
763
|
+
.dsh-super-code-history input,.dsh-super-code-history select{box-sizing:border-box;min-width:0;width:100%;border:1px solid var(--agent-line);border-radius:7px;background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-text-primary,inherit);font:inherit;font-size:12px;padding:7px 8px}
|
|
764
|
+
.dsh-super-code-history>input{margin-bottom:8px}
|
|
765
|
+
.dsh-super-code-history-filters{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:8px;margin-bottom:8px}
|
|
766
|
+
.dsh-super-code-history-filters label{display:flex;flex-direction:column;gap:4px;min-width:0;font-size:10px;color:var(--agent-muted)}
|
|
767
|
+
.dsh-super-code-history-filters select{cursor:pointer}
|
|
768
|
+
.dsh-super-code-history input:focus-visible,.dsh-super-code-history select:focus-visible,.dsh-super-code-history-list button:focus-visible{outline:2px solid var(--dsw-color-primary,#3276dc);outline-offset:-2px}
|
|
769
|
+
.dsh-super-code-history p.dsh-super-code-history-summary{font-size:10px;margin-bottom:6px}
|
|
770
|
+
.dsh-super-code-history-list{flex:1;min-height:56px;overflow:auto;overflow-anchor:none;overscroll-behavior:contain;scrollbar-gutter:stable}
|
|
771
|
+
.dsh-super-code-history-list button{display:flex;align-items:center;box-sizing:border-box;gap:9px;width:100%;height:56px;text-align:left;border:0;border-radius:7px;padding:9px 5px;background:transparent;color:inherit;cursor:pointer;font:inherit}
|
|
772
|
+
.dsh-super-code-history-list button:hover{background:color-mix(in srgb,currentColor 5%,transparent)}
|
|
773
|
+
.dsh-super-code-history-list button>span{flex:1;min-width:0}.dsh-super-code-history-list strong{display:block;font-size:12px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
774
|
+
.dsh-super-code-history-list small{font-size:10px;color:var(--agent-muted);line-height:1.6}.dsh-super-code-history-list button>small{white-space:nowrap;font-variant-numeric:tabular-nums}
|
|
775
|
+
.dsh-super-code-history-list button>span>small{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
776
|
+
.dsh-super-code-history-list .dsh-super-code-circle{width:7px;height:7px;box-shadow:none}
|
|
777
|
+
.dsh-super-code-history-empty{padding:14px 5px}
|
|
778
|
+
.dsh-super-code-branch-toggle{position:absolute;display:grid;place-items:center;width:22px;height:22px;padding:0;border:1px solid var(--agent-line);border-radius:50%;background:var(--dsw-alias-bg-base,#fff);color:var(--agent-muted);cursor:pointer;box-shadow:0 1px 3px #0000000a}
|
|
779
|
+
.dsh-super-code-branch-toggle:hover{color:var(--dsw-color-primary,#3276dc);border-color:currentColor}
|
|
780
|
+
.dsh-super-code-graph-scroll{overflow:hidden;padding:0 12px;min-height:180px;flex:1;touch-action:none;cursor:grab;user-select:none}
|
|
781
|
+
.dsh-super-code-graph-scroll.dragging,.dsh-super-code-graph-scroll.dragging *{cursor:grabbing!important}
|
|
782
|
+
.dsh-super-code-graph-legend{display:flex;flex-wrap:wrap;gap:6px 12px;padding:0 18px 8px;color:var(--agent-muted);font-size:10px}
|
|
783
|
+
.dsh-super-code-graph-legend>span{display:flex;align-items:center;gap:5px}
|
|
784
|
+
.dsh-super-code-graph-legend .dsh-super-code-circle{width:7px;height:7px;box-shadow:none}
|
|
785
|
+
.dsh-super-code-graph{position:relative;transform-origin:0 0}
|
|
786
|
+
.dsh-super-code-graph-edges{position:absolute;inset:0;pointer-events:none;stroke:var(--agent-line);stroke-width:1.5}
|
|
787
|
+
.dsh-super-code-graph-node{position:absolute;width:128px;min-height:100px;display:flex;flex-direction:column;align-items:center;gap:6px;border:0;padding:0 4px;background:transparent;color:inherit;font:inherit;cursor:pointer;border-radius:8px}
|
|
788
|
+
.dsh-super-code-circle{position:relative;display:flex;align-items:center;justify-content:center;flex:none;width:40px;height:40px;border-radius:50%!important;corner-shape:round!important;font-size:13px;font-weight:600;background:var(--state-color);color:#fff}
|
|
789
|
+
.dsh-super-code-circle.active{--state-color:#008b78}.dsh-super-code-circle.idle{--state-color:#3971de}.dsh-super-code-circle.stopped{--state-color:#777d87}.dsh-super-code-circle.unavailable{--state-color:#b87716}
|
|
790
|
+
.dsh-super-code-graph-node .dsh-super-code-circle{background:linear-gradient(145deg,color-mix(in srgb,var(--state-color) 83%,white),var(--state-color));box-shadow:inset 0 1px 1px #ffffff40,0 2px 6px #00000012}
|
|
791
|
+
.dsh-super-code-graph-node .dsh-super-code-circle:after{content:"";position:absolute;inset:-5px;border:1px solid color-mix(in srgb,var(--state-color) 22%,transparent);border-radius:50%;corner-shape:round;pointer-events:none}
|
|
792
|
+
.dsh-super-code-graph-node .dsh-super-code-circle.idle:after{animation:super-agent-breathe 3s ease-in-out infinite}
|
|
793
|
+
.dsh-super-code-graph-node .dsh-super-code-circle.active:after{border-width:2px;border-top-color:var(--state-color);border-right-color:var(--state-color);animation:super-agent-process 1.6s linear infinite}
|
|
794
|
+
.dsh-super-code-graph-node .dsh-super-code-circle.stopped{background:linear-gradient(145deg,#9297a0,#747a84);box-shadow:inset 0 1px 1px #ffffff30}
|
|
795
|
+
.dsh-super-code-graph-node .dsh-super-code-circle.stopped:after{border-color:color-mix(in srgb,var(--state-color) 14%,transparent)}
|
|
796
|
+
.dsh-super-code-graph-node .dsh-super-code-circle.unavailable:after{border-style:dashed;border-color:var(--state-color)}
|
|
797
|
+
.dsh-super-code-graph-node:hover .dsh-super-code-circle{box-shadow:inset 0 1px 1px #ffffff40,0 3px 10px color-mix(in srgb,var(--state-color) 25%,transparent)}
|
|
798
|
+
@keyframes super-agent-breathe{0%,100%{transform:scale(1);opacity:.35}50%{transform:scale(1.12);opacity:.85}}
|
|
799
|
+
@keyframes super-agent-process{to{transform:rotate(360deg)}}
|
|
800
|
+
.dsh-super-code-graph-node:focus-visible{outline:2px solid var(--dsw-color-primary,#3276dc);outline-offset:4px}
|
|
801
|
+
.dsh-super-code-graph-node[aria-disabled]{cursor:default;opacity:.65}
|
|
802
|
+
.dsh-super-code-graph-node .dsh-super-code-tree-content{width:100%;text-align:center}
|
|
803
|
+
.dsh-super-code-graph-node .dsh-super-code-tree-title{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:18px;height:18px;font-size:12px}
|
|
804
|
+
.dsh-super-code-graph-node .dsh-super-code-tree-usage{justify-content:center;gap:1px 7px;font-size:10px}
|
|
805
|
+
.dsh-super-code-icon-button,.dsh-super-code-tree-toggle{display:inline-flex;align-items:center;justify-content:center;flex:none;width:28px;height:28px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--agent-muted);cursor:pointer}
|
|
806
|
+
.dsh-super-code-icon-button:hover,.dsh-super-code-tree-toggle:hover{background:color-mix(in srgb,currentColor 9%,transparent)}
|
|
807
|
+
.dsh-super-code-icon-button:disabled{opacity:.45;cursor:default}
|
|
808
|
+
.dsh-super-code-view-button{flex:none;border:0;border-radius:4px;padding:6px 8px;background:transparent;color:var(--agent-muted);font:inherit;font-size:12px;cursor:pointer}
|
|
809
|
+
.dsh-super-code-view-button:hover{background:color-mix(in srgb,currentColor 7%,transparent);color:inherit}
|
|
810
|
+
.dsh-super-code-view-button:disabled{opacity:.45;cursor:default}
|
|
811
|
+
.dsh-super-code-tree-rows{padding:0 12px 20px}
|
|
812
|
+
.dsh-super-code-tree-row{position:relative;display:flex;align-items:center;gap:6px;min-width:0;min-height:64px;padding:10px 8px 10px calc(4px + var(--agent-depth)*14px);border-radius:6px;cursor:default;box-sizing:border-box}
|
|
813
|
+
.dsh-super-code-tree-row[aria-expanded]{cursor:pointer}
|
|
814
|
+
.dsh-super-code-tree-row[aria-level]:not([aria-level="1"]):before{content:"";position:absolute;left:15px;width:calc(var(--agent-depth)*14px);height:100%;top:0;background:repeating-linear-gradient(to right,var(--agent-line) 0,var(--agent-line) 1px,transparent 1px,transparent 14px);pointer-events:none}
|
|
815
|
+
.dsh-super-code-tree-row:hover{background:color-mix(in srgb,currentColor 5%,transparent)}
|
|
816
|
+
.dsh-super-code-tree-row.selected{background:color-mix(in srgb,var(--dsw-color-primary,#3276dc) 6%,transparent)}
|
|
817
|
+
.dsh-super-code-tree-row:focus-visible,.dsh-super-code-tree button:focus-visible{outline:2px solid var(--dsw-color-primary,#3276dc);outline-offset:-2px}
|
|
818
|
+
.dsh-super-code-tree-row[aria-disabled]{cursor:default;opacity:.7}
|
|
819
|
+
.dsh-super-code-tree-toggle{width:22px;height:26px;z-index:1}
|
|
820
|
+
.dsh-super-code-tree-toggle svg{transition:transform .12s}.dsh-super-code-tree-toggle.expanded svg{transform:rotate(90deg)}
|
|
821
|
+
.dsh-super-code-tree-connector{width:22px;flex:none}
|
|
822
|
+
.dsh-super-code-tree-content{display:grid;gap:4px;flex:1;min-width:0}
|
|
823
|
+
.dsh-super-code-tree-title{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;overflow-wrap:anywhere;line-height:1.4}
|
|
824
|
+
.dsh-super-code-tree-meta{color:var(--agent-muted);font-size:11px;line-height:1.4;overflow-wrap:anywhere}
|
|
825
|
+
.dsh-super-code-tree-usage{display:flex;flex-wrap:wrap;gap:3px 10px;font-size:10px;line-height:1.6;color:var(--agent-muted);font-variant-numeric:tabular-nums}
|
|
826
|
+
.dsh-super-code-tree-usage>span{white-space:nowrap}
|
|
827
|
+
.dsh-super-code-state{display:inline-flex;align-items:center;gap:5px;padding:2px 6px;border-radius:5px;color:var(--state-color);background:color-mix(in srgb,var(--state-color) 9%,transparent)}
|
|
828
|
+
.dsh-super-code-state:before{content:"";width:5px;height:5px;border-radius:50%;background:currentColor}
|
|
829
|
+
.dsh-super-code-state.active{--state-color:#16877d}
|
|
830
|
+
.dsh-super-code-state.idle{--state-color:#5471bb}
|
|
831
|
+
.dsh-super-code-state.stopped{--state-color:#827299}
|
|
832
|
+
.dsh-super-code-state.unavailable{--state-color:#b77b29}
|
|
833
|
+
@media(prefers-color-scheme:dark){.dsh-super-code-state.active{--state-color:#6bc7b6}.dsh-super-code-state.idle{--state-color:#91b0f4}.dsh-super-code-state.stopped{--state-color:#b9a9cf}.dsh-super-code-state.unavailable{--state-color:#e8b971}}
|
|
834
|
+
.dsh-super-code-tree-status{width:6px;height:6px;border-radius:50%;background:var(--agent-line);flex:none}
|
|
835
|
+
.dsh-super-code-tree-row.running .dsh-super-code-tree-status{background:#168c78}
|
|
836
|
+
.dsh-super-code-tree-status.unavailable{background:#c17e1d}
|
|
837
|
+
.dsh-super-code-tree-note,.dsh-super-code-tree-empty{margin:10px 18px;color:var(--agent-muted);font-size:12px}
|
|
838
|
+
.dsh-super-code-tree-error{margin:12px 14px;color:var(--dsw-color-text-error,#bd423b);font-size:12px;overflow-wrap:anywhere}
|
|
839
|
+
.dsh-super-code-tree-more{display:block;margin:4px auto 14px;padding:6px 12px;border:1px solid var(--agent-line);border-radius:4px;background:transparent;color:inherit;cursor:pointer}
|
|
840
|
+
.dsh-super-code-detail{height:100%;min-width:0;display:flex;flex-direction:column;font-size:13px;letter-spacing:0;--agent-muted:var(--dsw-alias-label-secondary,#70757d);--agent-line:var(--dsw-alias-border-l1,#dedfe3)}
|
|
841
|
+
.dsh-super-code-detail-header{padding:20px 20px 18px;border-bottom:1px solid var(--agent-line);flex:none;max-height:45%;overflow:auto}
|
|
842
|
+
.dsh-super-code-detail-header nav{font-size:11px;line-height:1.5;color:var(--agent-muted);overflow-wrap:anywhere}
|
|
843
|
+
.dsh-super-code-detail-heading{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-top:14px}
|
|
844
|
+
.dsh-super-code-detail-heading>div{min-width:0}
|
|
845
|
+
.dsh-super-code-detail h2{font-size:16px;line-height:1.5;font-weight:600;margin:0;overflow-wrap:anywhere}
|
|
846
|
+
.dsh-super-code-detail-task{font-size:13px;line-height:1.7;margin:12px 0 0;overflow-wrap:anywhere}
|
|
847
|
+
.dsh-super-code-task-memory{margin-top:16px;padding-top:14px;border-top:1px solid var(--agent-line);font-size:12px;line-height:1.7;overflow-wrap:anywhere}
|
|
848
|
+
.dsh-super-code-task-heading{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 12px}
|
|
849
|
+
.dsh-super-code-task-heading strong{font-weight:500;color:inherit}
|
|
850
|
+
.dsh-super-code-task-heading span{color:var(--agent-muted);font-size:11px}
|
|
851
|
+
.dsh-super-code-task-memory p{margin:6px 0 0}
|
|
852
|
+
.dsh-super-code-task-memory details{margin-top:8px;color:var(--agent-muted)}
|
|
853
|
+
.dsh-super-code-task-memory summary{cursor:pointer}
|
|
854
|
+
.dsh-super-code-detail-metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px 16px;margin:16px 0 0}
|
|
855
|
+
.dsh-super-code-usage-details{margin-top:14px;font-size:12px;color:var(--agent-muted)}
|
|
856
|
+
.dsh-super-code-usage-details summary{cursor:pointer}
|
|
857
|
+
.dsh-super-code-new-content{align-self:center;flex:none;margin:8px 16px 12px;padding:7px 12px;border:1px solid var(--agent-line);border-radius:6px;background:transparent;color:inherit;font:inherit;font-size:12px;cursor:pointer}
|
|
858
|
+
.dsh-super-code-detail-metrics dt{font-size:11px;color:var(--agent-muted)}
|
|
859
|
+
.dsh-super-code-detail-metrics dd{margin:5px 0 0;font-variant-numeric:tabular-nums;font-size:14px;line-height:1.4;overflow-wrap:anywhere}
|
|
860
|
+
.dsh-super-code-detail-metrics>div:first-child dd{font-weight:600}
|
|
861
|
+
.dsh-super-code-detail-status{display:block;margin-top:5px;font-size:11px;color:var(--agent-muted)}
|
|
862
|
+
.dsh-super-code-detail-log{flex:1;min-height:0;overflow:auto;padding:4px 20px 24px}
|
|
863
|
+
.dsh-super-code-detail-log>button,.dsh-super-code-detail-entry button{border:1px solid var(--agent-line);border-radius:4px;background:transparent;color:inherit;padding:5px 8px;margin:10px 8px 4px 0;font-size:12px;cursor:pointer}
|
|
864
|
+
.dsh-super-code-detail-entry{padding:20px 0;min-width:0}
|
|
865
|
+
.dsh-super-code-detail-entry:has(>details){padding:10px 0;color:var(--agent-muted)}
|
|
866
|
+
.dsh-super-code-detail-entry details[open]{color:inherit;padding-bottom:8px}
|
|
867
|
+
.dsh-super-code-detail-entry-label{display:flex;justify-content:space-between;gap:10px;font-size:12px;font-weight:600}
|
|
868
|
+
.dsh-super-code-detail-entry time{color:var(--agent-muted);font-size:11px;font-weight:400;flex:none}
|
|
869
|
+
.dsh-super-code-detail-entry pre{font:inherit;line-height:1.7;white-space:pre-wrap;overflow-wrap:anywhere;margin:8px 0 0}
|
|
870
|
+
.dsh-super-code-detail-entry summary{cursor:pointer;font-size:12px;overflow-wrap:anywhere}
|
|
871
|
+
.dsh-super-code-detail-entry.context{color:var(--agent-muted)}
|
|
872
|
+
.dsh-super-code-detail-entry.error,.dsh-super-code-detail [role=alert]{color:var(--dsw-color-text-error,#bd423b)}
|
|
873
|
+
.dsh-super-code-detail button:focus-visible,.dsh-super-code-detail summary:focus-visible{outline:2px solid var(--dsw-color-primary,#3276dc);outline-offset:3px}
|
|
874
|
+
.dsh-super-code-detail-entry summary:hover{color:var(--dsw-alias-label-primary,inherit)}
|
|
875
|
+
@media(prefers-reduced-motion:reduce){.dsh-super-code-tree-toggle svg{transition:none}.dsh-super-code-graph-node .dsh-super-code-circle:after{animation:none!important}}
|
|
876
|
+
`
|
|
877
|
+
document.head.appendChild(style)
|
|
878
|
+
return () => { style.remove() }
|
|
879
|
+
}, 'super-code: browser styles')
|
|
880
|
+
// Both the tree and read-only details stay in the current conversation's tabs.
|
|
881
|
+
const treeId = 'dsh-super-code'
|
|
882
|
+
const treeKind = 'super-agent-agents'
|
|
883
|
+
const disposeTreeType = ctx.sidebarRightTabs.register({
|
|
884
|
+
id: treeId, kind: treeKind,
|
|
885
|
+
title: () => 'Agent 执行树',
|
|
886
|
+
guide: [{ order: 40, title: () => 'Agent 执行树', description: () => '查看 Agent 层级和执行对话' }],
|
|
887
|
+
})
|
|
888
|
+
const treeActions = {
|
|
889
|
+
// The host unmounts hidden bodies; tab signals live until close/unload.
|
|
890
|
+
treeStates: new WeakMap(),
|
|
891
|
+
refresh: id => ctx.sessions.refreshSubagents(id),
|
|
892
|
+
watchCatalog: (id, open) => ctx.sessions.setSubagentCatalogOpen(id, open),
|
|
893
|
+
openDetail: child => ctx.sidebarRight.openResource(agentDetailAddress(child), { kind: 'super-agent-detail' }),
|
|
894
|
+
}
|
|
895
|
+
const disposeDetailType = ctx.sidebarRightTabs.register({
|
|
896
|
+
id: 'dsh-super-code-detail', kind: 'super-agent-detail', patterns: ['dsh-resource://super-agent/**'],
|
|
897
|
+
title: address => {
|
|
898
|
+
const target = agentDetailTarget(address), state = ctx.sessions.list.getSnapshot()
|
|
899
|
+
const node = buildAgentView(state).nodes.get(target.childSessionId || target.sessionId)
|
|
900
|
+
return node?.title || node?.displayTitle || 'Agent 详情'
|
|
901
|
+
},
|
|
902
|
+
})
|
|
903
|
+
const detailActions = { detailStates: new WeakMap(), createInspector: (address, initial) => createAgentInspector(
|
|
904
|
+
(target, options) => new SessionEventStream(ctx.remote, target, options), address,
|
|
905
|
+
(request, signal) => ctx.remote.session.page(request, signal), initial) }
|
|
906
|
+
ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({
|
|
907
|
+
name: 'sidebar.right.pane.tab', key: 'dsh-super-code-detail', inject: () => detailActions,
|
|
908
|
+
}, AgentDetail))
|
|
909
|
+
ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({
|
|
910
|
+
// Keyed tab bodies dispatch by the definition id, while `kind` is the
|
|
911
|
+
// page/content discriminator used by the Sidebar controller.
|
|
912
|
+
name: 'sidebar.right.pane.tab', key: treeId,
|
|
913
|
+
inject: () => treeActions,
|
|
914
|
+
}, AgentTree))
|
|
915
|
+
ctx.effect(() => disposeTreeType, 'super-code: Agent tree tab')
|
|
916
|
+
ctx.effect(() => disposeDetailType, 'super-code: Agent detail tab')
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
return { inject, apply, buildAgentView, visibleAgentRows, agentTreeDisplay, agentTreeTotals, agentTreeLayout, agentExecutionStatus, agentDetailAddress, agentDetailTarget, agentPath, detailRecord, createAgentInspector, agentHistoryRange, agentHistoryRows, agentHistoryWindow }
|
|
920
|
+
} })
|