dsh-qq-digest 0.1.0 → 0.2.0
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/README.md +39 -11
- package/install/cordis.patch.fragment.yml +4 -1
- package/lib/index.js +107 -19
- package/package.json +4 -2
- package/scripts/check-all.mjs +169 -0
- package/scripts/check-core-purity.mjs +70 -0
- package/scripts/final-check.mjs +197 -0
- package/scripts/host-smoke.mjs +235 -0
- package/scripts/install.mjs +128 -0
- package/scripts/link-profile.mjs +96 -0
- package/scripts/smoke.mjs +115 -0
- package/scripts/trigger.mjs +67 -0
- package/scripts/verify-dsh.mjs +110 -0
- package/src/core/gate.js +16 -2
- package/src/mcp-server.js +15 -2
- package/src/run-lock.js +35 -3
- package/src/runtime.js +65 -9
- package/src/service.js +57 -7
- package/src/store-file.js +80 -13
package/src/store-file.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 损坏行启动时跳过并计数,不因为一行坏掉就丢整份历史。
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
|
|
13
13
|
import { RECORD_FIELDS, fail } from './core/store.js';
|
|
@@ -23,26 +23,87 @@ export function createFileStore({ dir, now = () => new Date().toISOString() } =
|
|
|
23
23
|
const entries = [];
|
|
24
24
|
let skippedLines = 0;
|
|
25
25
|
let allowed = new Set();
|
|
26
|
+
let stamp = null;
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
function currentStamp() {
|
|
29
|
+
try {
|
|
30
|
+
const stat = statSync(file);
|
|
31
|
+
return `${stat.mtimeMs}:${stat.size}`;
|
|
32
|
+
} catch {
|
|
33
|
+
return 'missing';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 载入时按落盘白名单重建:文件可能被别的进程改过,原样塞进内存等于绕过白名单。 */
|
|
38
|
+
function rebuildRecord(raw) {
|
|
39
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
|
|
40
|
+
if (typeof raw.roomHandle !== 'string' || !/^[0-9a-f]{16}$/.test(raw.roomHandle)) return null;
|
|
41
|
+
|
|
42
|
+
const p = raw.provenance;
|
|
43
|
+
if (!p || typeof p.seqFrom !== 'number' || typeof p.seqTo !== 'number') return null;
|
|
44
|
+
|
|
45
|
+
const draft = {
|
|
46
|
+
roomHandle: raw.roomHandle,
|
|
47
|
+
roomTitle: typeof raw.roomTitle === 'string' ? raw.roomTitle.slice(0, 60) : null,
|
|
48
|
+
digest: sanitizeDigest(raw.digest),
|
|
49
|
+
provenance: {
|
|
50
|
+
roomHandle: raw.roomHandle,
|
|
51
|
+
seqFrom: p.seqFrom,
|
|
52
|
+
seqTo: p.seqTo,
|
|
53
|
+
...(typeof p.messageCount === 'number' ? { messageCount: p.messageCount } : {}),
|
|
54
|
+
},
|
|
55
|
+
trigger: raw.trigger === 'scheduled' ? 'scheduled' : 'manual',
|
|
56
|
+
createdAt: typeof raw.createdAt === 'string' ? raw.createdAt : now(),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return Object.freeze(
|
|
60
|
+
Object.fromEntries(RECORD_FIELDS.filter((key) => key in draft).map((key) => [key, draft[key]])),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function loadFromDisk() {
|
|
65
|
+
entries.length = 0;
|
|
66
|
+
skippedLines = 0;
|
|
67
|
+
try {
|
|
68
|
+
const text = readFileSync(file, 'utf8');
|
|
69
|
+
for (const line of text.split('\n')) {
|
|
70
|
+
const trimmed = line.trim();
|
|
71
|
+
if (trimmed === '') continue;
|
|
72
|
+
try {
|
|
73
|
+
const rebuilt = rebuildRecord(JSON.parse(trimmed));
|
|
74
|
+
if (rebuilt) entries.push(rebuilt);
|
|
75
|
+
else skippedLines += 1;
|
|
76
|
+
} catch {
|
|
77
|
+
skippedLines += 1;
|
|
78
|
+
}
|
|
38
79
|
}
|
|
80
|
+
} catch {
|
|
81
|
+
// 文件不存在 = 空表
|
|
39
82
|
}
|
|
83
|
+
stamp = currentStamp();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 跨进程可见性:MCP 子进程写入的摘要,宿主进程(设置页)也必须看得到。
|
|
88
|
+
* 用 mtime+size 判断有没有变化,变了就整体重载 —— 比 tail 增量简单,
|
|
89
|
+
* 也不会因为读到半行而把一条合法记录判成损坏。
|
|
90
|
+
*/
|
|
91
|
+
function refresh() {
|
|
92
|
+
if (currentStamp() !== stamp) loadFromDisk();
|
|
40
93
|
}
|
|
41
94
|
|
|
95
|
+
loadFromDisk();
|
|
96
|
+
|
|
42
97
|
return {
|
|
43
98
|
dir,
|
|
44
99
|
file,
|
|
45
|
-
|
|
100
|
+
|
|
101
|
+
get skippedLines() {
|
|
102
|
+
refresh();
|
|
103
|
+
return skippedLines;
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
refresh,
|
|
46
107
|
|
|
47
108
|
bindAllowed(handles) {
|
|
48
109
|
allowed = new Set(Array.isArray(handles) ? handles : []);
|
|
@@ -78,14 +139,18 @@ export function createFileStore({ dir, now = () => new Date().toISOString() } =
|
|
|
78
139
|
|
|
79
140
|
appendFileSync(file, `${JSON.stringify(stored)}\n`, 'utf8');
|
|
80
141
|
entries.push(stored);
|
|
142
|
+
// 自己刚写过:记下新的 stamp,避免下一次读取又整体重载一遍。
|
|
143
|
+
stamp = currentStamp();
|
|
81
144
|
return stored;
|
|
82
145
|
},
|
|
83
146
|
|
|
84
147
|
all() {
|
|
148
|
+
refresh();
|
|
85
149
|
return entries.slice();
|
|
86
150
|
},
|
|
87
151
|
|
|
88
152
|
latest(roomHandle) {
|
|
153
|
+
refresh();
|
|
89
154
|
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
90
155
|
if (entries[index].roomHandle === roomHandle) return entries[index];
|
|
91
156
|
}
|
|
@@ -93,12 +158,14 @@ export function createFileStore({ dir, now = () => new Date().toISOString() } =
|
|
|
93
158
|
},
|
|
94
159
|
|
|
95
160
|
latestPerRoom() {
|
|
161
|
+
refresh();
|
|
96
162
|
const map = new Map();
|
|
97
163
|
for (const entry of entries) map.set(entry.roomHandle, entry);
|
|
98
164
|
return [...map.values()];
|
|
99
165
|
},
|
|
100
166
|
|
|
101
167
|
dump() {
|
|
168
|
+
refresh();
|
|
102
169
|
return JSON.stringify(entries);
|
|
103
170
|
},
|
|
104
171
|
};
|