build / build-and-scan (push) Waiting to run
文档库:目录改为编号制(01-规范/02-架构设计/03-数据库/04-调整方案/
05-交接单/06-ops/07-scripts/08-skills/09-archive),顶层散文件归入 01-规范/;
INDEX.md 与 docs-manifest.json 重刷(档案 146 篇);旧目录名引用全量对齐。
IM 线:src/im/**(SDK / hub / store / presence / ws / gateway-token)、
src/web/routes/im.ts、src/db/plugin-data/**、src/supervisor/plugin-assembly.ts
及对应 test/**。
插件线:poc/{im-agent-bridge,im-connection-gateway,im-conversation-tabs,
business-plugins-im,carbon-mcp-probe}、src/web/routes/{sessions,overlay-device}.ts、
src/net/relay/{device-grant,instance-credential}.ts。
仓库卫生:清出 40 个历史误入库 / 已改名文件(34 个交接单归档 + 6 个旧结构,
本地均有副本);dsh-server-docs/.gitignore 补 tmp/;交接单不入库(政策)。
49 lines
2.0 KiB
JavaScript
49 lines
2.0 KiB
JavaScript
/**
|
|
* 会话取证 · schema 探测:解压多帧 zstd → 事件类型直方图 + 每类样本键结构(先摸清格式)
|
|
* 会话 schema 探测(只读):输出事件类型直方图 + 少量样本键结构
|
|
* 用法:node probe-schema.mjs <session.jsonl.zstd>
|
|
*/
|
|
import { readFileSync } from 'node:fs'
|
|
import { zstdDecompressSync } from 'node:zlib'
|
|
|
|
const raw = readFileSync(process.argv[2])
|
|
const MAGIC = Buffer.from([0x28, 0xb5, 0x2f, 0xfd])
|
|
const offs = []
|
|
for (let i = 0; i + 4 <= raw.length; i++) if (raw.compare(MAGIC, 0, 4, i, i + 4) === 0) offs.push(i)
|
|
let text = ''
|
|
const frames = offs.length ? offs : [0]
|
|
for (let f = 0; f < frames.length; f++) {
|
|
const s = frames[f], e = f + 1 < frames.length ? frames[f + 1] : raw.length
|
|
try { text += zstdDecompressSync(raw.subarray(s, e)).toString('utf8') } catch {}
|
|
}
|
|
const lines = text.split('\n').filter((l) => l.trim())
|
|
console.log('解压后 %d 行 / %d 字符 / zstd 帧 %d', lines.length, text.length, frames.length)
|
|
|
|
const types = new Map()
|
|
const samples = new Map()
|
|
for (const l of lines) {
|
|
let o
|
|
try { o = JSON.parse(l) } catch { continue }
|
|
const t = o.type ?? o.event ?? o.kind ?? '(no-type)'
|
|
types.set(t, (types.get(t) ?? 0) + 1)
|
|
if (!samples.has(t)) samples.set(t, o)
|
|
}
|
|
console.log('\n=== 事件类型直方图 ===')
|
|
for (const [t, n] of [...types].sort((a, b) => b[1] - a[1])) console.log(' %-28s %d', t, n)
|
|
|
|
console.log('\n=== 每类样本(键 + 截断值) ===')
|
|
for (const [t, o] of samples) {
|
|
console.log('--- %s ---', t)
|
|
const dump = (v, depth = 0) => {
|
|
if (v === null) return 'null'
|
|
if (Array.isArray(v)) return `[${v.length} items${v.length ? ': ' + dump(v[0], depth + 1) : ''}]`
|
|
if (typeof v === 'object') {
|
|
if (depth > 1) return '{…}'
|
|
return '{' + Object.entries(v).slice(0, 10).map(([k, x]) => `${k}: ${dump(x, depth + 1)}`).join(', ') + '}'
|
|
}
|
|
const s = String(v)
|
|
return JSON.stringify(s.length > 90 ? s.slice(0, 90) + '…' : s)
|
|
}
|
|
console.log(' ' + dump(o).slice(0, 1200))
|
|
}
|