Files
admin e6207aa691
build / build-and-scan (push) Waiting to run
chore(仓库对齐): 文档库结构治理 + IM/插件线落地
文档库:目录改为编号制(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/;交接单不入库(政策)。
2026-09-24 07:25:16 +08:00

60 lines
2.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 会话取证 · 逐次工具结果分类(沙箱拒绝 / 文件策略拒绝 / 命令错误 / OK)+ 审批事件时间线
* 精确分类:每次 bash 调用的结果性质(沙箱拒绝 / 命令错误 / 成功)
* 用法:node classify-bash.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 = ''
for (let f = 0; f < (offs.length ? offs : [0]).length; f++) {
const s = offs[f], e = f + 1 < offs.length ? offs[f + 1] : raw.length
try { text += zstdDecompressSync(raw.subarray(s, e)).toString('utf8') } catch {}
}
const evs = []
for (const l of text.split('\n')) { if (!l.trim()) continue; try { evs.push(JSON.parse(l)) } catch {} }
const t = (ms) => new Date(Number(ms)).toLocaleString('zh-CN', { hour12: false, timeZone: 'Asia/Shanghai' })
// tool/call:id → {name, args(截断)}
const callInfo = new Map()
for (const e of evs) {
if (e.type !== 'tool/call') continue
const s = JSON.stringify(e.data ?? {})
const id = (s.match(/"callId":"([^"]+)"/) ?? [])[1]
const name = (s.match(/"name":"([^"]+)"/) ?? [])[1]
if (!id) continue
const cmd = (s.match(/"command":"((?:[^"\\]|\\.)*)"/) ?? [])[1] ?? ''
callInfo.set(id, { name, cmd: cmd.replace(/\\n/g, ' ').slice(0, 90) })
}
// tool/result:id → 文本
const rows = []
for (const e of evs) {
if (e.type !== 'tool/result') continue
const s = JSON.stringify(e.data ?? {})
const id = (s.match(/"toolCallId":"([^"]+)"/) ?? [])[1]
const info = callInfo.get(id) ?? { name: '?', cmd: '' }
const txt = (e.data?.message?.content ?? []).map((c) => (c.content ?? []).map((x) => x.text ?? '').join('')).join('\n')
const flat = txt.replace(/\s+/g, ' ').trim()
let kind = 'OK'
if (/sandbox mode .* is requested but no sandbox backend/.test(flat)) kind = '沙箱拒绝'
else if (/file access denied under/.test(flat)) kind = '文件策略拒绝'
else if (/^Error:|^error:|"error"|Command failed|exit code [1-9]/.test(flat)) kind = '命令错误'
rows.push({ time: e.time, name: info.name, cmd: info.cmd, kind, head: flat.slice(0, 150) })
}
console.log('=== 逐次 tool/result 分类(共 %d)===', rows.length)
for (const r of rows) {
if (r.name !== 'bash' && r.kind === 'OK') continue
console.log(' %s %-6s %-10s %s', t(r.time), r.name, r.kind, (r.cmd || r.head).slice(0, 110))
}
const byKind = {}
for (const r of rows) byKind[r.kind] = (byKind[r.kind] ?? 0) + 1
console.log('\n汇总:', JSON.stringify(byKind))
const sb = rows.filter((r) => r.kind === '沙箱拒绝')
console.log('\n沙箱拒绝时间线: %s → %s(共 %d 次)', t(sb[0]?.time), t(sb[sb.length - 1]?.time), sb.length)
const after = sb.filter((r) => r.time > 1789116 * 1000).length
console.log(' 其中 22:16 切档位之后:', after, '次')