dsh-agentic-tool-catalog 0.0.0-stage → 0.3.2
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/CHANGELOG.md +63 -0
- package/LICENSE +21 -0
- package/README.md +173 -2
- package/README.zh.md +135 -0
- package/cordis.patch.yml +29 -0
- package/docs/assets/plugin-page.png +0 -0
- package/docs/assets/ui-dock.png +0 -0
- package/docs/assets/ui-panel.png +0 -0
- package/docs/benchmark.md +54 -0
- package/docs/data-card.md +96 -0
- package/docs/detail-benchmark.svg +30 -0
- package/docs/detail-hero.svg +57 -0
- package/docs/detail.md +33 -0
- package/docs/detail.zh.md +30 -0
- package/icon.svg +15 -0
- package/lib/catalog.js +208 -0
- package/lib/client.js +368 -0
- package/lib/generation.js +342 -0
- package/lib/index.js +795 -0
- package/lib/store.js +108 -0
- package/lib/subagent-compat.js +87 -0
- package/locale/en.json +7 -0
- package/locale/zh.json +7 -0
- package/package.json +90 -4
package/lib/client.js
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agentic Tool Catalog —— Client 半边(v3,对齐官方 ContextMeter)。
|
|
3
|
+
*
|
|
4
|
+
* 座位:官方 `conversation.composer.dock`。
|
|
5
|
+
* Trigger:glyph + 极小数字(按需工具数),无状态点;hover/展开使用官方
|
|
6
|
+
* `--dsw-alias-interactive-bg-hover`,尺寸/字体与官方 ContextMeter 同一套 token。
|
|
7
|
+
* 面板:portal 到 body 的 fixed 浮层,样式照官方 ContextMeter:264px、12px 内边距、
|
|
8
|
+
* `--dsw-specific-menu` + `--dsw-elevation-prominent`、12/20 字号;内容只有信号——
|
|
9
|
+
* 常驻/已加载/待加载三段条 + 三行图例 + 一行存储/更新时间;没有目录名堆叠、没有长文案。
|
|
10
|
+
*
|
|
11
|
+
* 数据:Host 只读路由 `/plugins/agentic-tool-catalog/snapshot?sessionId=<id>`。
|
|
12
|
+
*
|
|
13
|
+
* @module @local/dsh-agentic-tool-catalog/client
|
|
14
|
+
*/
|
|
15
|
+
window.__ModuleLoader__.load({
|
|
16
|
+
id: 'dsh-agentic-tool-catalog',
|
|
17
|
+
factory(require) {
|
|
18
|
+
const React = require('react')
|
|
19
|
+
const h = React.createElement
|
|
20
|
+
const ReactDOM = (() => {
|
|
21
|
+
try {
|
|
22
|
+
return require('react-dom')
|
|
23
|
+
} catch {
|
|
24
|
+
return undefined
|
|
25
|
+
}
|
|
26
|
+
})()
|
|
27
|
+
const primitives = (() => {
|
|
28
|
+
try {
|
|
29
|
+
return require('@deepseek-ai/dsh-client-ui-primitives')
|
|
30
|
+
} catch {
|
|
31
|
+
return undefined
|
|
32
|
+
}
|
|
33
|
+
})()
|
|
34
|
+
|
|
35
|
+
const NS = 'agentic-tool-catalog'
|
|
36
|
+
const SNAPSHOT_ROUTE = '/plugins/agentic-tool-catalog/snapshot'
|
|
37
|
+
const EVENTS_ROUTE = '/plugins/agentic-tool-catalog/events'
|
|
38
|
+
|
|
39
|
+
const EN = {
|
|
40
|
+
'dock.label': 'Agentic Tool Catalog',
|
|
41
|
+
'dock.tip': 'Catalog · {freshness} · {groups} groups · {tools} on-demand tools',
|
|
42
|
+
'panel.title': 'Catalog',
|
|
43
|
+
'panel.close': 'Close',
|
|
44
|
+
'panel.fresh.fresh': 'ready',
|
|
45
|
+
'panel.fresh.refreshing': 'refreshing',
|
|
46
|
+
'panel.fresh.stale': 'stale',
|
|
47
|
+
'panel.fresh.bootstrap': 'initializing',
|
|
48
|
+
'panel.fresh.none': 'unavailable',
|
|
49
|
+
'panel.always': 'Always available',
|
|
50
|
+
'panel.loaded': 'Loaded',
|
|
51
|
+
'panel.pending': 'On demand',
|
|
52
|
+
'panel.groups': 'groups',
|
|
53
|
+
'panel.storage.storage': 'Official Storage',
|
|
54
|
+
'panel.storage.memory': 'Memory only',
|
|
55
|
+
'panel.storage.unavailable': 'Unavailable',
|
|
56
|
+
'panel.generated': 'Catalog generated {time}',
|
|
57
|
+
'panel.never': 'Catalog not generated yet',
|
|
58
|
+
'panel.minutes': '{minutes}m ago',
|
|
59
|
+
'panel.hours': '{hours}h ago',
|
|
60
|
+
'panel.days': '{days}d ago',
|
|
61
|
+
'panel.unavailable': 'Catalog status unavailable.',
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const ZH = {
|
|
65
|
+
'dock.label': 'Agent 工具目录',
|
|
66
|
+
'dock.tip': '目录 · {freshness} · {groups} 组 · {tools} 个按需工具',
|
|
67
|
+
'panel.title': '工具目录',
|
|
68
|
+
'panel.close': '关闭',
|
|
69
|
+
'panel.fresh.fresh': 'ready',
|
|
70
|
+
'panel.fresh.refreshing': '刷新中',
|
|
71
|
+
'panel.fresh.stale': '待刷新',
|
|
72
|
+
'panel.fresh.bootstrap': '初始化中',
|
|
73
|
+
'panel.fresh.none': '不可用',
|
|
74
|
+
'panel.always': '常驻',
|
|
75
|
+
'panel.loaded': '已加载',
|
|
76
|
+
'panel.pending': '待加载',
|
|
77
|
+
'panel.groups': '组',
|
|
78
|
+
'panel.storage.storage': '官方 Storage',
|
|
79
|
+
'panel.storage.memory': '仅内存',
|
|
80
|
+
'panel.storage.unavailable': '不可用',
|
|
81
|
+
'panel.generated': '目录生成于 {time}',
|
|
82
|
+
'panel.never': '目录尚未生成',
|
|
83
|
+
'panel.minutes': '{minutes} 分钟前',
|
|
84
|
+
'panel.hours': '{hours} 小时前',
|
|
85
|
+
'panel.days': '{days} 天前',
|
|
86
|
+
'panel.unavailable': '无法读取目录状态。',
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 样式逐条对齐官方 ContextMeter.module.css(trigger / panel / bar / swatch / row):
|
|
91
|
+
* 半径、内边距、字体、行高、颜色变量都不自定义。
|
|
92
|
+
*/
|
|
93
|
+
const CSS = [
|
|
94
|
+
'.atc-root{flex:none;display:inline-flex;font-size:calc(var(--dsh-content-font-size-secondary,13px) - 1px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px))}',
|
|
95
|
+
'.atc-trigger{display:inline-flex;align-items:center;gap:6px;box-sizing:border-box;padding:1px 8px;border:0;border-radius:var(--dsw-radius-sm);background:0 0;color:var(--dsw-alias-label-tertiary);font:inherit;font-variant-numeric:tabular-nums;line-height:inherit;white-space:nowrap;cursor:pointer;flex:none}',
|
|
96
|
+
'.atc-trigger[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}',
|
|
97
|
+
'@media (hover:hover){.atc-trigger:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}}',
|
|
98
|
+
'.atc-trigger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}',
|
|
99
|
+
'.atc-glyph{flex:none;width:14px;height:14px;display:block}',
|
|
100
|
+
'.atc-count{font-variant-numeric:tabular-nums;color:inherit}',
|
|
101
|
+
'.atc-panel{position:fixed;z-index:1100;box-sizing:border-box;border-radius:var(--dsw-radius-lg);background:var(--dsw-specific-menu);width:min(264px,100vw - 24px);backdrop-filter:var(--dsw-menu-backdrop-filter);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-secondary);border:0;padding:12px;font-size:12px;line-height:20px;overflow:hidden auto}',
|
|
102
|
+
'.atc-head{display:flex;align-items:center;gap:6px}',
|
|
103
|
+
'.atc-title{font-weight:500;color:var(--dsw-alias-label-primary)}',
|
|
104
|
+
'.atc-fresh{color:var(--dsw-alias-label-tertiary)}',
|
|
105
|
+
'.atc-fresh[data-state="refreshing"]{color:var(--dsw-alias-state-warn-primary)}',
|
|
106
|
+
'.atc-fresh[data-state="stale"]{color:var(--dsw-alias-state-warn-primary)}',
|
|
107
|
+
'.atc-error{color:var(--dsw-alias-label-tertiary)}',
|
|
108
|
+
'.atc-figures{margin-left:auto;color:var(--dsw-alias-label-primary);font-weight:500;font-variant-numeric:tabular-nums}',
|
|
109
|
+
'.atc-close{flex:none;display:flex;align-items:center;justify-content:center;width:20px;height:20px;margin-left:2px;border:0;border-radius:var(--dsw-radius-sm);background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:15px;line-height:1;cursor:pointer}',
|
|
110
|
+
'@media (hover:hover){.atc-close:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}}',
|
|
111
|
+
'.atc-bar{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;gap:1px;height:4px;margin:10px 0 12px;display:flex;overflow:hidden}',
|
|
112
|
+
'.atc-segment{flex:none;min-width:2px;height:100%;border-radius:1px;background:var(--meter-tint,var(--dsw-alias-label-tertiary))}',
|
|
113
|
+
'.atc-colorAlways{--meter-tint:var(--dsw-alias-label-tertiary);opacity:.55}',
|
|
114
|
+
'.atc-colorLoaded{--meter-tint:var(--dsw-static-blue-450)}',
|
|
115
|
+
'.atc-colorPending{--meter-tint:var(--dsw-alias-border-l3)}',
|
|
116
|
+
'.atc-rows{margin:6px 0 0}',
|
|
117
|
+
'.atc-row{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:2px 0}',
|
|
118
|
+
'.atc-rowLabel{color:var(--dsw-alias-label-secondary);display:inline-flex;align-items:center;min-width:0}',
|
|
119
|
+
'.atc-swatch{display:inline-block;width:8px;height:8px;margin-right:6px;border-radius:2px;vertical-align:baseline;background:var(--meter-tint)}',
|
|
120
|
+
'.atc-rowValue{margin:0;color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums}',
|
|
121
|
+
'.atc-meta{display:flex;align-items:center;gap:0;margin-top:8px;color:var(--dsw-alias-label-tertiary);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
122
|
+
'@keyframes atc-pulse{0%,100%{opacity:.45}50%{opacity:1}}',
|
|
123
|
+
].join('')
|
|
124
|
+
|
|
125
|
+
function StyleTag() {
|
|
126
|
+
return h('style', { key: 'atc-css' }, CSS)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const num = (value) => Number(value ?? 0)
|
|
130
|
+
|
|
131
|
+
/** 目录 glyph:带索引行的卡片 + 右上角 agent spark;与插件 icon 同一套形状。 */
|
|
132
|
+
function glyph() {
|
|
133
|
+
return h(
|
|
134
|
+
'svg',
|
|
135
|
+
{ className: 'atc-glyph', viewBox: '0 0 14 14', fill: 'none', stroke: 'currentColor', strokeWidth: 1.4, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': 'true' },
|
|
136
|
+
h('path', { d: 'M1.6 3.6h6.9M1.6 6.9h6.9M1.6 10.2h4.5' }),
|
|
137
|
+
h('path', { d: 'M11.3 1.3l.62 1.56 1.56.62-1.56.62-.62 1.56-.62-1.56L9.12 3.48l1.56-.62z', fill: 'currentColor', stroke: 'none', opacity: 0.95 }),
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function ago(ms, t) {
|
|
142
|
+
if (typeof ms !== 'number' || !Number.isFinite(ms)) return t('panel.never')
|
|
143
|
+
const minutes = Math.max(0, Math.floor((Date.now() - ms) / 60000))
|
|
144
|
+
if (minutes < 60) return t('panel.minutes', { minutes })
|
|
145
|
+
const hours = Math.floor(minutes / 60)
|
|
146
|
+
if (hours < 24) return t('panel.hours', { hours })
|
|
147
|
+
return t('panel.days', { days: Math.floor(hours / 24) })
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function statusLabel(snapshot, t) {
|
|
151
|
+
const kind = snapshot?.catalog?.freshness ?? 'none'
|
|
152
|
+
const key = `panel.fresh.${kind}`
|
|
153
|
+
const value = t(key)
|
|
154
|
+
return value === key ? kind : value
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
function CatalogDock(props) {
|
|
159
|
+
const t = typeof props.t === 'function' ? props.t : (key) => key
|
|
160
|
+
const sessionId = typeof props.sessionId === 'string' ? props.sessionId : undefined
|
|
161
|
+
const [open, setOpen] = React.useState(false)
|
|
162
|
+
const [snapshot, setSnapshot] = React.useState(null)
|
|
163
|
+
const [error, setError] = React.useState(false)
|
|
164
|
+
const [anchor, setAnchor] = React.useState(null)
|
|
165
|
+
const triggerRef = React.useRef(null)
|
|
166
|
+
const panelRef = React.useRef(null)
|
|
167
|
+
|
|
168
|
+
const readSnapshot = React.useCallback(async () => {
|
|
169
|
+
if (sessionId === undefined) return
|
|
170
|
+
try {
|
|
171
|
+
const response = await fetch(`${SNAPSHOT_ROUTE}?sessionId=${encodeURIComponent(sessionId)}`, {
|
|
172
|
+
headers: { accept: 'application/json' },
|
|
173
|
+
cache: 'no-store',
|
|
174
|
+
})
|
|
175
|
+
if (!response.ok) throw new Error(`http ${response.status}`)
|
|
176
|
+
const body = await response.json()
|
|
177
|
+
setSnapshot(body?.snapshot ?? null)
|
|
178
|
+
setError(false)
|
|
179
|
+
} catch {
|
|
180
|
+
setError(true)
|
|
181
|
+
}
|
|
182
|
+
}, [sessionId])
|
|
183
|
+
|
|
184
|
+
// 首屏只取一次(dock 的工具数/状态);之后由 SSE 推送,不做轮询。
|
|
185
|
+
React.useEffect(() => {
|
|
186
|
+
if (sessionId !== undefined) readSnapshot()
|
|
187
|
+
}, [sessionId, readSnapshot])
|
|
188
|
+
|
|
189
|
+
// 面板打开时建立 SSE:Host 在目录/会话状态变化时推送完整快照;关闭即断开。
|
|
190
|
+
React.useEffect(() => {
|
|
191
|
+
if (!open || sessionId === undefined) return undefined
|
|
192
|
+
let source
|
|
193
|
+
try {
|
|
194
|
+
source = new EventSource(`${EVENTS_ROUTE}?sessionId=${encodeURIComponent(sessionId)}`)
|
|
195
|
+
} catch {
|
|
196
|
+
readSnapshot()
|
|
197
|
+
return undefined
|
|
198
|
+
}
|
|
199
|
+
const onSnapshot = (event) => {
|
|
200
|
+
try {
|
|
201
|
+
const body = JSON.parse(event.data)
|
|
202
|
+
setSnapshot(body?.snapshot ?? null)
|
|
203
|
+
setError(false)
|
|
204
|
+
} catch {}
|
|
205
|
+
}
|
|
206
|
+
source.addEventListener('snapshot', onSnapshot)
|
|
207
|
+
source.onerror = () => {
|
|
208
|
+
// SSE 断开:关闭连接并退化为一次普通快照读取(不恢复轮询)。
|
|
209
|
+
try { source.close() } catch {}
|
|
210
|
+
setError(true)
|
|
211
|
+
readSnapshot()
|
|
212
|
+
}
|
|
213
|
+
return () => {
|
|
214
|
+
source.removeEventListener('snapshot', onSnapshot)
|
|
215
|
+
try { source.close() } catch {}
|
|
216
|
+
}
|
|
217
|
+
}, [open, sessionId, readSnapshot])
|
|
218
|
+
|
|
219
|
+
const reposition = React.useCallback(() => {
|
|
220
|
+
const el = triggerRef.current
|
|
221
|
+
if (el === null) return
|
|
222
|
+
const rect = el.getBoundingClientRect()
|
|
223
|
+
const width = 264
|
|
224
|
+
const left = Math.max(8, Math.min(rect.left, window.innerWidth - width - 8))
|
|
225
|
+
setAnchor({ left, bottom: Math.max(8, window.innerHeight - rect.top + 8) })
|
|
226
|
+
}, [])
|
|
227
|
+
|
|
228
|
+
React.useEffect(() => {
|
|
229
|
+
if (!open) return undefined
|
|
230
|
+
reposition()
|
|
231
|
+
const onDown = (event) => {
|
|
232
|
+
const target = event.target
|
|
233
|
+
if (triggerRef.current?.contains(target) === true) return
|
|
234
|
+
if (panelRef.current?.contains(target) === true) return
|
|
235
|
+
setOpen(false)
|
|
236
|
+
}
|
|
237
|
+
const onKey = (event) => {
|
|
238
|
+
if (event.key === 'Escape') setOpen(false)
|
|
239
|
+
}
|
|
240
|
+
window.addEventListener('pointerdown', onDown, true)
|
|
241
|
+
window.addEventListener('keydown', onKey)
|
|
242
|
+
window.addEventListener('resize', reposition)
|
|
243
|
+
return () => {
|
|
244
|
+
window.removeEventListener('pointerdown', onDown, true)
|
|
245
|
+
window.removeEventListener('keydown', onKey)
|
|
246
|
+
window.removeEventListener('resize', reposition)
|
|
247
|
+
}
|
|
248
|
+
}, [open, reposition])
|
|
249
|
+
|
|
250
|
+
if (sessionId === undefined || (snapshot === null && !error)) return null
|
|
251
|
+
|
|
252
|
+
const catalog = snapshot?.catalog
|
|
253
|
+
const tools = num(catalog?.total)
|
|
254
|
+
const resident = num(catalog?.resident)
|
|
255
|
+
const groups = Array.isArray(catalog?.groups) ? catalog.groups.length : 0
|
|
256
|
+
const loaded = Array.isArray(snapshot?.session?.loaded) ? snapshot.session.loaded.length : 0
|
|
257
|
+
const pending = Math.max(0, tools - loaded)
|
|
258
|
+
const freshness = error ? 'none' : (catalog?.freshness ?? 'none')
|
|
259
|
+
const tip = error
|
|
260
|
+
? t('panel.unavailable')
|
|
261
|
+
: t('dock.tip', { freshness: statusLabel(snapshot, t), groups, tools })
|
|
262
|
+
const Tooltip = primitives !== undefined && typeof primitives.Tooltip === 'function' ? primitives.Tooltip : null
|
|
263
|
+
const trigger = h(
|
|
264
|
+
'button',
|
|
265
|
+
{
|
|
266
|
+
ref: triggerRef,
|
|
267
|
+
type: 'button',
|
|
268
|
+
className: 'atc-trigger',
|
|
269
|
+
'aria-label': t('dock.label'),
|
|
270
|
+
'aria-expanded': open,
|
|
271
|
+
title: Tooltip === null ? tip : undefined,
|
|
272
|
+
onClick: () => setOpen((value) => !value),
|
|
273
|
+
},
|
|
274
|
+
glyph(),
|
|
275
|
+
h('span', { className: 'atc-count' }, error ? '!' : String(tools)),
|
|
276
|
+
)
|
|
277
|
+
const triggerWithTip = Tooltip === null ? trigger : h(Tooltip, { label: tip, side: 'top' }, trigger)
|
|
278
|
+
|
|
279
|
+
if (!open) {
|
|
280
|
+
return h('div', { className: 'atc-root' }, h(StyleTag, { key: 'style' }), triggerWithTip)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const total = Math.max(1, resident + tools)
|
|
284
|
+
const percent = (value) => `${(value / total) * 100}%`
|
|
285
|
+
const panel = h(
|
|
286
|
+
'div',
|
|
287
|
+
{
|
|
288
|
+
ref: panelRef,
|
|
289
|
+
className: 'atc-panel',
|
|
290
|
+
role: 'dialog',
|
|
291
|
+
'aria-label': t('panel.title'),
|
|
292
|
+
style: anchor === null ? { visibility: 'hidden', left: 8, bottom: 8 } : { left: anchor.left, bottom: anchor.bottom },
|
|
293
|
+
},
|
|
294
|
+
h(
|
|
295
|
+
'div',
|
|
296
|
+
{ className: 'atc-head' },
|
|
297
|
+
h('span', { className: 'atc-title' }, t('panel.title')),
|
|
298
|
+
h('span', { className: 'atc-fresh', 'data-state': freshness }, statusLabel(snapshot, t)),
|
|
299
|
+
h('span', { className: 'atc-figures' }, `${groups} ${t('panel.groups')}`),
|
|
300
|
+
h('button', { type: 'button', className: 'atc-close', 'aria-label': t('panel.close'), onClick: () => setOpen(false) }, '×'),
|
|
301
|
+
),
|
|
302
|
+
error
|
|
303
|
+
? h('div', { className: 'atc-error' }, t('panel.unavailable'))
|
|
304
|
+
: h(
|
|
305
|
+
React.Fragment,
|
|
306
|
+
null,
|
|
307
|
+
h(
|
|
308
|
+
'div',
|
|
309
|
+
{ className: 'atc-bar', role: 'img', 'aria-label': `${resident} ${t('panel.always')} · ${loaded} ${t('panel.loaded')} · ${pending} ${t('panel.pending')}` },
|
|
310
|
+
resident > 0 ? h('div', { className: 'atc-segment atc-colorAlways', style: { width: percent(resident) } }) : null,
|
|
311
|
+
loaded > 0 ? h('div', { className: 'atc-segment atc-colorLoaded', style: { width: percent(loaded) } }) : null,
|
|
312
|
+
pending > 0 ? h('div', { className: 'atc-segment atc-colorPending', style: { width: percent(pending) } }) : null,
|
|
313
|
+
),
|
|
314
|
+
h(
|
|
315
|
+
'dl',
|
|
316
|
+
{ className: 'atc-rows' },
|
|
317
|
+
h('div', { className: 'atc-row' }, h('dt', { className: 'atc-rowLabel' }, h('i', { className: 'atc-swatch atc-colorAlways' }), t('panel.always')), h('dd', { className: 'atc-rowValue' }, String(resident))),
|
|
318
|
+
h('div', { className: 'atc-row' }, h('dt', { className: 'atc-rowLabel' }, h('i', { className: 'atc-swatch atc-colorLoaded' }), t('panel.loaded')), h('dd', { className: 'atc-rowValue' }, String(loaded))),
|
|
319
|
+
h('div', { className: 'atc-row' }, h('dt', { className: 'atc-rowLabel' }, h('i', { className: 'atc-swatch atc-colorPending' }), t('panel.pending')), h('dd', { className: 'atc-rowValue' }, String(pending))),
|
|
320
|
+
),
|
|
321
|
+
h(
|
|
322
|
+
'div',
|
|
323
|
+
{ className: 'atc-meta' },
|
|
324
|
+
typeof snapshot?.generation?.lastOkAt === 'number' && Number.isFinite(snapshot.generation.lastOkAt)
|
|
325
|
+
? t('panel.generated', { time: ago(snapshot.generation.lastOkAt, t) })
|
|
326
|
+
: t('panel.never'),
|
|
327
|
+
),
|
|
328
|
+
),
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
if (ReactDOM !== undefined && typeof ReactDOM.createPortal === 'function') {
|
|
332
|
+
return h(React.Fragment, null, h(StyleTag, { key: 'style' }), h('div', { className: 'atc-root' }, triggerWithTip), ReactDOM.createPortal(panel, document.body))
|
|
333
|
+
}
|
|
334
|
+
return h(
|
|
335
|
+
'div',
|
|
336
|
+
{ className: 'atc-root', style: { position: 'relative' } },
|
|
337
|
+
h(StyleTag, { key: 'style' }),
|
|
338
|
+
triggerWithTip,
|
|
339
|
+
h('div', { style: { position: 'absolute', right: 0, bottom: 'calc(100% + 8px)' } }, panel),
|
|
340
|
+
)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const inject = ['slots', 'locale']
|
|
344
|
+
|
|
345
|
+
function apply(ctx) {
|
|
346
|
+
try {
|
|
347
|
+
ctx.effect(() => ctx.locale.register(NS, { en: EN, zh: ZH }), 'agentic-tool-catalog: dictionaries')
|
|
348
|
+
ctx.slots.inject('conversation.composer.dock', () =>
|
|
349
|
+
ctx.slots.register(
|
|
350
|
+
{
|
|
351
|
+
name: 'conversation.composer.dock',
|
|
352
|
+
id: 'agentic-tool-catalog-status',
|
|
353
|
+
order: 20,
|
|
354
|
+
locale: NS,
|
|
355
|
+
},
|
|
356
|
+
CatalogDock,
|
|
357
|
+
),
|
|
358
|
+
)
|
|
359
|
+
} catch (error) {
|
|
360
|
+
try {
|
|
361
|
+
console.error('[agentic-tool-catalog] client apply failed (degraded, boot kept healthy):', error)
|
|
362
|
+
} catch {}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return { inject, apply }
|
|
367
|
+
},
|
|
368
|
+
})
|