import { createHash } from 'node:crypto'; export const statusLabels = { loaded: '已加载', absent: '未检测到加载', removed: '完整正文不在上下文', unknown: '无法确认', }; const textOf = (message) => (message?.content ?? []).filter(block => block.type === 'text').map(block => block.text).join(''); const decode = (value) => value.replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll('&', '&'); // 仅拆解正式入口的规范包装;主正文原样保留,允许正文自己包含闭合标签。 export function parseSkillContent(text, name) { const escapedName = name.replaceAll('&', '&').replaceAll('"', '"').replaceAll('<', '<'); const start = `\n\n`; const separator = '\n\n\n\n'; const end = '\n\n'; if (!text.startsWith(start) || !text.endsWith(end)) return undefined; const split = text.indexOf(separator, start.length); if (split < 0) return undefined; const body = text.slice(split + separator.length, -end.length); if (!body.length) return undefined; const resource = decode(text.slice(start.length, split)); return { body, resource }; } export function eventMessage(event) { if (event.type === 'user/message') return event.data; return event.data?.message; } export function collectHistory(events) { const calls = new Map(); const records = []; const originalCalls = new Set(); let complete = events.every((event, index) => event.seq === index); for (const event of events) { if (event.type === 'tool/call') calls.set(event.data.callId, event.data); if (event.type === 'assistant/message') { for (const block of event.data.message.content ?? []) { if (block.type === 'tool-call') calls.set(block.id, block); } } let message = eventMessage(event); let name; let entry; if (event.type === 'user/message' && message?.source?.kind === 'skill-invocation' && message.source.form === 'instructions') { name = message.source.name; entry = 'user'; } else if (event.type === 'tool/result') { const call = calls.get(message?.toolCallId); if (call?.name !== 'skill' || message.isError || event.data.error) continue; if (originalCalls.has(message.toolCallId)) continue; originalCalls.add(message.toolCallId); try { name = JSON.parse(call.arguments).name; } catch { complete = false; } entry = 'tool'; } else if (event.type === 'tool/ptc-dispatch' && event.data?.name === 'skill') { if (event.data.isError || event.data.error) continue; // 间接调用的日志副本可能经过转换,保留已知身份但不冒充原始完整正文。 try { name = (typeof event.data.arguments === 'string' ? JSON.parse(event.data.arguments) : event.data.arguments)?.name; } catch { complete = false; } message = { id: event.data.subCallId, toolCallId: event.data.rootCallId }; entry = 'tool'; } if (!entry) continue; if (typeof name !== 'string' || !name) { complete = false; continue; } // 替换记录不能充当加载时的完整原文。 const parsed = event.surfaceOp === 'append' ? parseSkillContent(textOf(message), name) : undefined; const digest = createHash('sha256').update(JSON.stringify([name, parsed?.body, parsed?.resource, parsed ? null : event.seq])).digest('hex'); records.push({ name, id: digest, seq: event.seq, time: event.time, messageId: message.id, callId: message.toolCallId, entry, body: parsed?.body ?? null, resource: parsed?.resource ?? null }); } return { records, complete }; } function currentCandidates(record, messages, events) { const descendants = new Set([record.seq]); const messageIds = new Set([record.messageId]); for (const event of events) { if (event.sourceEventSeqs?.some(seq => descendants.has(seq))) { descendants.add(event.seq); let message = eventMessage(event); if (message?.id) messageIds.add(message.id); } } return messages.filter(message => messageIds.has(message.id) || (record.callId && message.role === 'tool' && message.toolCallId === record.callId)); } function containsInstructions(message, record) { const text = textOf(message); const direct = message.id === record.messageId || (record.callId && message.role === 'tool' && message.toolCallId === record.callId); if (direct && text === record.body) return true; const opening = '\n'; const closing = '\n'; const start = text.indexOf(opening); const end = text.lastIndexOf(closing); if (start >= 0 && end >= start + opening.length) { return text.slice(start + opening.length, end).includes(record.body); } if (!text.includes(record.body)) return false; if (!direct || text.includes('') || text.includes(' containsInstructions(message, record)); const retained = evidence.includes(true); const state = retained ? 'loaded' : !messages || record.body === null || evidence.includes(undefined) ? 'unknown' : 'removed'; const reason = state === 'removed' ? (candidates.some(message => message.role === 'tool' && message.toolCallId === record.callId) ? '正文已被裁剪或修改' : '正文已离开当前上下文') : null; const existing = row.versions.find(version => version.id === record.id); if (existing) { existing.loadCount++; existing.seq = record.seq; existing.time = record.time; if (state === 'loaded' || (existing.state !== 'loaded' && state === 'unknown')) { existing.state = state; existing.reason = reason; } } else { row.versions.push({ id: record.id, seq: record.seq, time: record.time, body: record.body, resource: record.resource, entry: record.entry, state, reason, loadCount: 1 }); } rows.set(record.name, row); } const skills = [...rows.values()].map(row => { row.versions.sort((left, right) => right.seq - left.seq); const state = row.versions.some(version => version.state === 'loaded') ? 'loaded' : !complete || row.versions.some(version => version.state === 'unknown') ? 'unknown' : row.versions.length ? 'removed' : 'absent'; return { ...row, state, directoryState: !catalogComplete ? 'unknown' : row.inCatalog ? 'present' : 'absent', sourceState: 'unknown' }; }).sort((left, right) => // 已加载的技能排在前面;同组内按名称排序,状态变化时只调整组间位置。 Number(right.state === 'loaded') - Number(left.state === 'loaded') || left.name.localeCompare(right.name, 'zh-CN')); return { skills, loadedCount: skills.filter(row => row.state === 'loaded').length, uncertain: !complete || !catalogComplete || !messages || skills.some(row => row.state === 'unknown'), historyComplete: complete, catalogComplete, contextComplete: !!messages }; }