import React, { useState, useSyncExternalStore } from 'react'; const labels = { loaded: '已加载', absent: '未检测到加载', removed: '完整正文不在上下文', unknown: '无法确认' }; const tabId = 'dsh-skill-status'; const empty = Object.freeze({ phase: 'pending' }); export function createStatusStore(call, sessionId) { let value = empty; let controller; let retryTimer; const listeners = new Set(); const publish = next => { value = next; for (const listener of listeners) listener(); }; async function run(signal) { let after; while (!signal.aborted) { try { const response = await call('/api', 'skill-status.read', { sessionId, ...(after ? { after } : {}) }, AbortSignal.any([signal, AbortSignal.timeout(20000)])); if (signal.aborted) return; if (!response.ok || response.value?.sessionId !== sessionId) throw new Error('技能状态响应无效。'); after = response.value.revision; publish({ phase: 'ready', data: response.value }); } catch { if (signal.aborted) return; after = undefined; publish({ phase: 'error' }); await new Promise(resolve => { const finish = () => { clearTimeout(retryTimer); signal.removeEventListener('abort', finish); resolve(); }; retryTimer = setTimeout(finish, 3000); signal.addEventListener('abort', finish, { once: true }); if (signal.aborted) finish(); }); } } } return { getSnapshot: () => value, subscribe(listener) { listeners.add(listener); if (listeners.size === 1) { controller = new AbortController(); void run(controller.signal); } return () => { listeners.delete(listener); if (!listeners.size) { controller?.abort(); value = empty; } }; }, dispose() { controller?.abort(); clearTimeout(retryTimer); listeners.clear(); value = empty; }, }; } export function SkillPanel({ state }) { const [filter, setFilter] = useState(''); if (state.phase === 'pending') return
正在核实技能状态…
; if (state.phase === 'error') return
暂时无法查询技能状态,正在自动重试。
; const data = state.data; const rows = data.skills.filter(row => `${row.name} ${row.description}`.toLocaleLowerCase().includes(filter.toLocaleLowerCase())); return
技能已确认加载 {data.loadedCount} 个

仅检查主助手通过正式入口加载的完整技能正文。

{data.uncertain &&

部分状态无法确认,计数仅包含已核实的技能。

} {data.note &&

{data.note}

} {!rows.length &&

{filter ? '没有匹配的技能。' : data.uncertain ? '目前没有可核实的技能条目。' : '当前没有可用技能或正式加载记录。'}

}
{rows.map(row => )}
; } function SkillRow({ row }) { const [selected, setSelected] = useState(null); const version = row.versions.find(item => item.id === selected) ?? row.versions[0]; const directoryLabel = row.directoryState === 'absent' ? '未在当前技能目录中' : row.directoryState === 'unknown' ? '当前目录无法确认' : null; return
{row.name}{labels[row.state]} {directoryLabel &&

{directoryLabel}

} {row.description &&

{row.description}

} {row.catalogSource &&
查看当前目录来源
{row.catalogSource}
{row.catalogSourceState === 'missing' &&

源文件不存在

} {row.catalogSourceState === 'unknown' &&

来源状态未知

} {!!row.versions.length &&

此处是当前目录提供的来源,加载时的来源以各版本记录为准。

}
} {!version ?

{row.state === 'unknown' ? '现有证据不足以核实加载记录。' : '没有检测到正式入口成功加载的记录。'}

: <>

{version.entry === 'user' ? '用户调用' : 'skill 工具'} · 加载 {version.loadCount} 次 · {labels[version.state]}

{version.state === 'loaded' ? '上下文正文' : '历史正文'}{version.reason ? ` · ${version.reason}` : ''}

{version.state === 'unknown' &&

无法确认这版完整正文是否仍在当前上下文中。

} {version.body === null ?

无法取得加载时的完整正文。

:
{version.body}
}
查看加载时的来源
{version.resource ?? '来源状态未知:历史记录未保留可核实的来源。'}

资源指引不一定包含源文件路径;未取得确切路径时,无法判断源文件是否存在。

}
; } export const inject = ['slots', 'connection', 'sidebarRight', 'sidebarRightTabs']; export function apply(ctx) { const stores = new Map(); const getStore = sessionId => { if (!stores.has(sessionId)) stores.set(sessionId, createStatusStore(ctx.connection.rpc.call.bind(ctx.connection.rpc), sessionId)); return stores.get(sessionId); }; function useStatus(sessionId) { const store = getStore(sessionId); return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot); } function Header({ sessionId }) { const state = useStatus(sessionId); const count = state.phase === 'ready' ? `${state.data.loadedCount}${state.data.uncertain ? ' · ?' : ''}` : state.phase === 'pending' ? '…' : '?'; return ; } function Panel({ sessionId }) { return ; } ctx.effect(() => { const style = document.createElement('style'); style.textContent = CSS_TEXT; document.head.append(style); return () => { style.remove(); for (const store of stores.values()) store.dispose(); stores.clear(); }; }); ctx.effect(() => ctx.sidebarRightTabs.register({ id: tabId, kind: tabId, title: () => '技能' })); ctx.slots.inject('conversation.session.header.utilities', () => ctx.slots.register({ name: 'conversation.session.header.utilities', id: tabId, order: 20 }, Header)); ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({ name: 'sidebar.right.pane.tab', key: tabId }, Panel)); } const CSS_TEXT = ` .dsh-skills { height:100%; overflow:auto; padding:20px; box-sizing:border-box; color:var(--dsw-alias-label-primary); background:var(--dsw-alias-bg-base); font-size:14px; line-height:1.6; } .dsh-skills-trigger { display:inline-flex; align-items:center; gap:6px; border:1px solid var(--dsw-alias-border-l1); border-radius:8px; background:transparent; color:var(--dsw-alias-label-primary); padding:5px 10px; cursor:pointer; font:inherit; font-size:13px; white-space:nowrap; } .dsh-skills-trigger:hover { background:var(--dsw-alias-bg-layer-2); } .dsh-skills-trigger:focus-visible,.dsh-skills summary:focus-visible,.dsh-skills input:focus-visible,.dsh-skills select:focus-visible { outline:2px solid var(--dsw-alias-brand-primary); outline-offset:3px; } .dsh-skills-summary { display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:8px; } .dsh-skills-summary strong { font-size:18px; } .dsh-skills-muted { color:var(--dsw-alias-label-secondary); font-size:12px; } .dsh-skills-notice { color:var(--dsw-alias-state-warn-primary); font-size:13px; } .dsh-skills-search,.dsh-skills-version { display:grid; gap:6px; margin:16px 0; font-size:12px; } .dsh-skills input,.dsh-skills select { min-width:0; width:100%; box-sizing:border-box; padding:8px; border:1px solid var(--dsw-alias-border-l1); border-radius:7px; color:var(--dsw-alias-label-primary); background:var(--dsw-alias-bg-layer-1); font:inherit; } .dsh-skills-row { border-top:1px solid var(--dsw-alias-border-l1); padding:12px 0; overflow-wrap:anywhere; } .dsh-skills-row>summary { cursor:pointer; } .dsh-skills-name { font-weight:600; margin-right:10px; } .dsh-skills-state { font-size:12px; color:var(--dsw-alias-label-secondary); } .dsh-skills-loaded { color:var(--dsw-alias-state-success-primary); } .dsh-skills-unknown { color:var(--dsw-alias-state-warn-primary); } .dsh-skills pre { white-space:pre-wrap; overflow-wrap:anywhere; font:12px/1.7 ui-monospace,monospace; } .dsh-skills-body { max-height:55vh; overflow:auto; padding:12px; background:var(--dsw-alias-bg-layer-1); border:1px solid var(--dsw-alias-border-l1); border-radius:8px; } .dsh-skills-source { margin:12px 0; font-size:12px; } .dsh-skills-source summary { cursor:pointer; color:var(--dsw-alias-brand-primary); } `;