@telosmaylx/dsh-session-notify 0.1.2 → 0.1.3
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/LICENSE +21 -21
- package/README.md +202 -196
- package/cordis.patch.yml +10 -0
- package/lib/client.js +1180 -1180
- package/lib/core.js +303 -303
- package/lib/index.js +312 -291
- package/package.json +70 -66
- package/scripts/build.sh +16 -16
- package/scripts/probe-card-render.mjs +100 -100
- package/scripts/probe-client-e2e.mjs +134 -134
- package/scripts/probe-client.mjs +145 -145
- package/scripts/probe-diag-settings.mjs +79 -79
- package/scripts/probe-settings-card.mjs +137 -137
- package/scripts/probe-settings-check.mjs +87 -87
- package/scripts/verify-notice.mjs +110 -110
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* verify-notice.mjs —— 验证 dsh-session-complete-notify 的落盘证据。
|
|
4
|
-
*
|
|
5
|
-
* 用法:
|
|
6
|
-
* node scripts/verify-notice.mjs <session.js.jsonl.zstd 路径>
|
|
7
|
-
* node scripts/verify-notice.mjs # 自动选 ~/.dsh/sessions 下最新会话
|
|
8
|
-
*
|
|
9
|
-
* 输出:该日志中所有 plugin-source(kind=plugin)的 user/message 事件,
|
|
10
|
-
* 以及最近一次 turn/end 之后的尾部事件序列。
|
|
11
|
-
*
|
|
12
|
-
* 说明:存储文件是多帧 zstd(每次写批一帧),Node 的 zstdDecompressSync
|
|
13
|
-
* 只解首帧,因此逐帧解压后拼接。
|
|
14
|
-
*/
|
|
15
|
-
import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs'
|
|
16
|
-
import { join } from 'node:path'
|
|
17
|
-
import { homedir } from 'node:os'
|
|
18
|
-
import { zstdDecompressSync } from 'node:zlib'
|
|
19
|
-
|
|
20
|
-
function* zstdFrames(buffer) {
|
|
21
|
-
let i = 0
|
|
22
|
-
while (i + 4 <= buffer.length) {
|
|
23
|
-
if (buffer[i] !== 0x28 || buffer[i + 1] !== 0xb5 || buffer[i + 2] !== 0x2f || buffer[i + 3] !== 0xfd) throw new Error(`非 zstd 帧头 @${i}`)
|
|
24
|
-
const start = i
|
|
25
|
-
i += 4
|
|
26
|
-
// 粗扫下一个帧头(真实帧边界);帧头里的合法 0x28b52ffd 概率可忽略
|
|
27
|
-
while (i + 4 <= buffer.length) {
|
|
28
|
-
if (buffer[i] === 0x28 && buffer[i + 1] === 0xb5 && buffer[i + 2] === 0x2f && buffer[i + 3] === 0xfd) break
|
|
29
|
-
i += 1
|
|
30
|
-
}
|
|
31
|
-
yield buffer.subarray(start, i)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function newestSessionLog() {
|
|
36
|
-
const root = join(homedir(), '.dsh', 'sessions')
|
|
37
|
-
const found = []
|
|
38
|
-
const walk = (dir) => {
|
|
39
|
-
for (const name of readdirSync(dir)) {
|
|
40
|
-
const full = join(dir, name)
|
|
41
|
-
const stat = statSync(full)
|
|
42
|
-
if (stat.isDirectory()) walk(full)
|
|
43
|
-
else if (name.startsWith('session.jsonl.zstd')) found.push({ full, mtime: stat.mtimeMs })
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
walk(root)
|
|
47
|
-
found.sort((a, b) => b.mtime - a.mtime)
|
|
48
|
-
return found[0]?.full
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const path = process.argv[2] ?? newestSessionLog()
|
|
52
|
-
if (!path) {
|
|
53
|
-
console.error('未找到会话日志(请传入 session.jsonl.zstd 路径)')
|
|
54
|
-
process.exit(1)
|
|
55
|
-
}
|
|
56
|
-
console.log(`日志: ${path}`)
|
|
57
|
-
|
|
58
|
-
let text = ''
|
|
59
|
-
let frames = 0
|
|
60
|
-
for (const frame of zstdFrames(readFileSync(path))) {
|
|
61
|
-
text += zstdDecompressSync(frame).toString('utf8')
|
|
62
|
-
frames += 1
|
|
63
|
-
}
|
|
64
|
-
const lines = text.split('\n').filter(Boolean)
|
|
65
|
-
console.log(`帧数: ${frames},行数: ${lines.length}`)
|
|
66
|
-
|
|
67
|
-
const notices = lines
|
|
68
|
-
.map((l, i) => ({ l, i }))
|
|
69
|
-
.filter(({ l }) => l.includes('"type":"user/message"') && l.includes('"kind":"plugin"'))
|
|
70
|
-
console.log(`\n===== plugin-source 系统消息(${notices.length} 条)=====`)
|
|
71
|
-
for (const n of notices) {
|
|
72
|
-
try {
|
|
73
|
-
const row = JSON.parse(n.l)
|
|
74
|
-
const msg = row.data?.message ?? row.data
|
|
75
|
-
console.log(`row ${n.i} seq ${row.seq}: summary=${JSON.stringify(msg?.source?.summary)} text=${JSON.stringify((msg?.content ?? [])[0]?.text ?? '')}`)
|
|
76
|
-
} catch (e) {
|
|
77
|
-
console.log(`row ${n.i}(无法解析): ${n.l.slice(0, 200)}`)
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
console.log('\n===== turn/end 事件(触发点核验)=====')
|
|
82
|
-
for (const l of lines) {
|
|
83
|
-
if (!l.includes('"type":"turn/end"')) continue
|
|
84
|
-
try {
|
|
85
|
-
const row = JSON.parse(l)
|
|
86
|
-
console.log(`seq ${row.seq} time ${row.time} turn=${row.data?.turn} reason=${JSON.stringify(row.data?.reason ?? {})}`)
|
|
87
|
-
} catch { /* 忽略无法解析行 */ }
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
console.log('\n===== turn/start 事件(计时核验)=====')
|
|
91
|
-
for (const l of lines) {
|
|
92
|
-
if (!l.includes('"type":"turn/start"')) continue
|
|
93
|
-
try {
|
|
94
|
-
const row = JSON.parse(l)
|
|
95
|
-
console.log(`seq ${row.seq} time ${row.time} turn=${row.data?.turn}`)
|
|
96
|
-
} catch { /* 忽略无法解析行 */ }
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
console.log('\n===== 事件尾序列(最近 24 行的 type/seq/来源)=====')
|
|
100
|
-
for (const l of lines.slice(-24)) {
|
|
101
|
-
try {
|
|
102
|
-
const row = JSON.parse(l)
|
|
103
|
-
const data = row.data ?? {}
|
|
104
|
-
const src = data.message?.source
|
|
105
|
-
const extra = src ? ` [source=${src.kind}${src.kind === 'plugin' ? `:${src.plugin}` : ''}]` : ''
|
|
106
|
-
console.log(`seq ${row.seq} ${row.type}${extra}`)
|
|
107
|
-
} catch {
|
|
108
|
-
console.log(`?? ${l.slice(0, 120)}`)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* verify-notice.mjs —— 验证 dsh-session-complete-notify 的落盘证据。
|
|
4
|
+
*
|
|
5
|
+
* 用法:
|
|
6
|
+
* node scripts/verify-notice.mjs <session.js.jsonl.zstd 路径>
|
|
7
|
+
* node scripts/verify-notice.mjs # 自动选 ~/.dsh/sessions 下最新会话
|
|
8
|
+
*
|
|
9
|
+
* 输出:该日志中所有 plugin-source(kind=plugin)的 user/message 事件,
|
|
10
|
+
* 以及最近一次 turn/end 之后的尾部事件序列。
|
|
11
|
+
*
|
|
12
|
+
* 说明:存储文件是多帧 zstd(每次写批一帧),Node 的 zstdDecompressSync
|
|
13
|
+
* 只解首帧,因此逐帧解压后拼接。
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs'
|
|
16
|
+
import { join } from 'node:path'
|
|
17
|
+
import { homedir } from 'node:os'
|
|
18
|
+
import { zstdDecompressSync } from 'node:zlib'
|
|
19
|
+
|
|
20
|
+
function* zstdFrames(buffer) {
|
|
21
|
+
let i = 0
|
|
22
|
+
while (i + 4 <= buffer.length) {
|
|
23
|
+
if (buffer[i] !== 0x28 || buffer[i + 1] !== 0xb5 || buffer[i + 2] !== 0x2f || buffer[i + 3] !== 0xfd) throw new Error(`非 zstd 帧头 @${i}`)
|
|
24
|
+
const start = i
|
|
25
|
+
i += 4
|
|
26
|
+
// 粗扫下一个帧头(真实帧边界);帧头里的合法 0x28b52ffd 概率可忽略
|
|
27
|
+
while (i + 4 <= buffer.length) {
|
|
28
|
+
if (buffer[i] === 0x28 && buffer[i + 1] === 0xb5 && buffer[i + 2] === 0x2f && buffer[i + 3] === 0xfd) break
|
|
29
|
+
i += 1
|
|
30
|
+
}
|
|
31
|
+
yield buffer.subarray(start, i)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function newestSessionLog() {
|
|
36
|
+
const root = join(homedir(), '.dsh', 'sessions')
|
|
37
|
+
const found = []
|
|
38
|
+
const walk = (dir) => {
|
|
39
|
+
for (const name of readdirSync(dir)) {
|
|
40
|
+
const full = join(dir, name)
|
|
41
|
+
const stat = statSync(full)
|
|
42
|
+
if (stat.isDirectory()) walk(full)
|
|
43
|
+
else if (name.startsWith('session.jsonl.zstd')) found.push({ full, mtime: stat.mtimeMs })
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
walk(root)
|
|
47
|
+
found.sort((a, b) => b.mtime - a.mtime)
|
|
48
|
+
return found[0]?.full
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const path = process.argv[2] ?? newestSessionLog()
|
|
52
|
+
if (!path) {
|
|
53
|
+
console.error('未找到会话日志(请传入 session.jsonl.zstd 路径)')
|
|
54
|
+
process.exit(1)
|
|
55
|
+
}
|
|
56
|
+
console.log(`日志: ${path}`)
|
|
57
|
+
|
|
58
|
+
let text = ''
|
|
59
|
+
let frames = 0
|
|
60
|
+
for (const frame of zstdFrames(readFileSync(path))) {
|
|
61
|
+
text += zstdDecompressSync(frame).toString('utf8')
|
|
62
|
+
frames += 1
|
|
63
|
+
}
|
|
64
|
+
const lines = text.split('\n').filter(Boolean)
|
|
65
|
+
console.log(`帧数: ${frames},行数: ${lines.length}`)
|
|
66
|
+
|
|
67
|
+
const notices = lines
|
|
68
|
+
.map((l, i) => ({ l, i }))
|
|
69
|
+
.filter(({ l }) => l.includes('"type":"user/message"') && l.includes('"kind":"plugin"'))
|
|
70
|
+
console.log(`\n===== plugin-source 系统消息(${notices.length} 条)=====`)
|
|
71
|
+
for (const n of notices) {
|
|
72
|
+
try {
|
|
73
|
+
const row = JSON.parse(n.l)
|
|
74
|
+
const msg = row.data?.message ?? row.data
|
|
75
|
+
console.log(`row ${n.i} seq ${row.seq}: summary=${JSON.stringify(msg?.source?.summary)} text=${JSON.stringify((msg?.content ?? [])[0]?.text ?? '')}`)
|
|
76
|
+
} catch (e) {
|
|
77
|
+
console.log(`row ${n.i}(无法解析): ${n.l.slice(0, 200)}`)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log('\n===== turn/end 事件(触发点核验)=====')
|
|
82
|
+
for (const l of lines) {
|
|
83
|
+
if (!l.includes('"type":"turn/end"')) continue
|
|
84
|
+
try {
|
|
85
|
+
const row = JSON.parse(l)
|
|
86
|
+
console.log(`seq ${row.seq} time ${row.time} turn=${row.data?.turn} reason=${JSON.stringify(row.data?.reason ?? {})}`)
|
|
87
|
+
} catch { /* 忽略无法解析行 */ }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log('\n===== turn/start 事件(计时核验)=====')
|
|
91
|
+
for (const l of lines) {
|
|
92
|
+
if (!l.includes('"type":"turn/start"')) continue
|
|
93
|
+
try {
|
|
94
|
+
const row = JSON.parse(l)
|
|
95
|
+
console.log(`seq ${row.seq} time ${row.time} turn=${row.data?.turn}`)
|
|
96
|
+
} catch { /* 忽略无法解析行 */ }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log('\n===== 事件尾序列(最近 24 行的 type/seq/来源)=====')
|
|
100
|
+
for (const l of lines.slice(-24)) {
|
|
101
|
+
try {
|
|
102
|
+
const row = JSON.parse(l)
|
|
103
|
+
const data = row.data ?? {}
|
|
104
|
+
const src = data.message?.source
|
|
105
|
+
const extra = src ? ` [source=${src.kind}${src.kind === 'plugin' ? `:${src.plugin}` : ''}]` : ''
|
|
106
|
+
console.log(`seq ${row.seq} ${row.type}${extra}`)
|
|
107
|
+
} catch {
|
|
108
|
+
console.log(`?? ${l.slice(0, 120)}`)
|
|
109
|
+
}
|
|
110
|
+
}
|