dsh-instruction-memory 1.5.2 → 1.5.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/CHANGELOG.md +27 -0
- package/lib/index.js +32 -27
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,33 @@
|
|
|
7
7
|
> 1.0.13 起由 CI 经 OIDC Trusted Publishing 发布(首个带 provenance 的版本)。
|
|
8
8
|
> 这些历史 tag **不应回填**——npm 上的版本号已被占用。
|
|
9
9
|
|
|
10
|
+
## [1.5.3] — 2026-11-09
|
|
11
|
+
|
|
12
|
+
1.5.2 的发布后修正,两处都是当天发现:一处**判断依据用错**,一处**依赖了初始化顺序**。没有新功能,
|
|
13
|
+
存储格式、注入文本与面板行为不变。
|
|
14
|
+
|
|
15
|
+
### 修复
|
|
16
|
+
|
|
17
|
+
- **背压判断把健康但读得慢的订阅者当成死连接(1.5.2 引入)**:1.5.2 让 `send()` 返回 `res.write()`
|
|
18
|
+
的结果,并在返回 `false` 时立刻 `cleanup()`。但 `write()` 的 `false` 只表示**这一次写入超过了
|
|
19
|
+
socket 缓冲的高水位**——一个健康、但此刻读得慢的客户端同样会拿到 `false`。于是一个大帧(极限存储
|
|
20
|
+
约 3.4 MB)就可能把正常订阅者踢下线。现在返回值不再用作存活判据:心跳只负责让"写失败"这条路走通
|
|
21
|
+
——对端真的消失时,写入会报错并触发该流早已注册的 `close`/`error` 清理;`pushSnapshot` 恢复
|
|
22
|
+
1.5.1 的语义。
|
|
23
|
+
- **`diskRevFrom()` 的声明顺序依赖**:1.5.2 把它定义在 `writeToDisk()` 之后。`apply()` 是同步函数,
|
|
24
|
+
通常情况下没有问题;但"缺主文件时从 `.bak` 恢复"这条路径会在 boot 序列里调用 `writeToDisk()`,
|
|
25
|
+
那是一次**可能先于声明执行**的调用,而 `const` 会落在暂时性死区并抛 `ReferenceError`(保存因此
|
|
26
|
+
失败、`storageError` 报"保存失败")。现在两个 helper 提到 `writeToDisk()` 之前,用顺序消除这个
|
|
27
|
+
假设,而不是依赖"今天恰好先 await"。
|
|
28
|
+
|
|
29
|
+
### 验收
|
|
30
|
+
|
|
31
|
+
- 全套 7 个套件与 `node check-metadata.mjs` 全绿;`contract-test.mjs` 连续独立运行 5 次均 ALL PASS。
|
|
32
|
+
- 1.5.2 的 CI 在 `windows-latest / Node 24` 上失败了 2 条 SSE 断言:
|
|
33
|
+
`sse: a writer that throws on broadcast is fully disposed (response ended)` 与
|
|
34
|
+
`sse: disposing the dead writer does not interrupt a healthy stream`。本版按上面的修正重新验证这两个
|
|
35
|
+
断言所在的路径(死连接清理 + 健康流不受牵连)。
|
|
36
|
+
|
|
10
37
|
## [1.5.2] — 2026-11-09
|
|
11
38
|
|
|
12
39
|
针对 2026-11-09 的代码审查(Host 侧与 Client 侧并行只读审查,逐条复核后落地)。本版**没有新增功能、
|
package/lib/index.js
CHANGED
|
@@ -2238,7 +2238,13 @@ export function apply(ctx) {
|
|
|
2238
2238
|
}
|
|
2239
2239
|
for (const [send, entry] of Array.from(sseStreams)) {
|
|
2240
2240
|
try {
|
|
2241
|
-
|
|
2241
|
+
// The RETURN VALUE is deliberately not read as a liveness signal:
|
|
2242
|
+
// res.write() answers false when THIS write exceeded the socket's
|
|
2243
|
+
// high-water mark, which a healthy but momentarily slow reader
|
|
2244
|
+
// produces too — killing on it tore down live streams. A dead peer
|
|
2245
|
+
// is detected the reliable way instead: the write fails, which fires
|
|
2246
|
+
// the close/error handler this stream has already registered.
|
|
2247
|
+
send(SSE_PING_FRAME)
|
|
2242
2248
|
} catch {
|
|
2243
2249
|
entry.cleanup()
|
|
2244
2250
|
}
|
|
@@ -2268,12 +2274,10 @@ export function apply(ctx) {
|
|
|
2268
2274
|
for (const [send, entry] of Array.from(sseStreams)) {
|
|
2269
2275
|
try {
|
|
2270
2276
|
if (entry.lite === true) {
|
|
2271
|
-
|
|
2272
|
-
// stopped reading. Drop it now rather than queue frames in memory.
|
|
2273
|
-
if (send(litePayload) === false) entry.cleanup()
|
|
2277
|
+
send(litePayload)
|
|
2274
2278
|
} else {
|
|
2275
2279
|
if (fullPayload === null) fullPayload = 'data: ' + JSON.stringify(snapshot()) + '\n\n'
|
|
2276
|
-
|
|
2280
|
+
send(fullPayload)
|
|
2277
2281
|
}
|
|
2278
2282
|
} catch {
|
|
2279
2283
|
// The response is dead: run its full teardown (end + unsubscribe),
|
|
@@ -2449,6 +2453,28 @@ export function apply(ctx) {
|
|
|
2449
2453
|
|
|
2450
2454
|
const storePath = resolveStorePath()
|
|
2451
2455
|
|
|
2456
|
+
/**
|
|
2457
|
+
* The rev a PARSED store carries, or 0 when it carries none usable.
|
|
2458
|
+
*
|
|
2459
|
+
* Defined BEFORE writeToDisk on purpose: the missing-primary recovery path
|
|
2460
|
+
* calls writeToDisk from the boot sequence, and a `const` declared later in
|
|
2461
|
+
* apply() would still be in its temporal dead zone if that write ever ran
|
|
2462
|
+
* before apply() reached the declaration. Ordering it here removes the
|
|
2463
|
+
* question instead of relying on the await that happens to come first today.
|
|
2464
|
+
*/
|
|
2465
|
+
const diskRevFrom = (parsed) => {
|
|
2466
|
+
const rev = parsed !== null && parsed !== undefined && typeof parsed === 'object' ? parsed.rev : null
|
|
2467
|
+
return typeof rev === 'number' && Number.isFinite(rev) && rev > 0 ? Math.round(rev) : 0
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
const readDiskRev = async () => {
|
|
2471
|
+
try {
|
|
2472
|
+
return diskRevFrom(await readStoreFile(storePath))
|
|
2473
|
+
} catch {
|
|
2474
|
+
return 0
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2452
2478
|
/**
|
|
2453
2479
|
* The exact object written to memory.json — and the one an export carries.
|
|
2454
2480
|
*
|
|
@@ -3132,25 +3158,6 @@ export function apply(ctx) {
|
|
|
3132
3158
|
* then silently misses the second save; and (b) deciding whether a save made
|
|
3133
3159
|
* while another process holds the lock would clobber a newer store.
|
|
3134
3160
|
*/
|
|
3135
|
-
/**
|
|
3136
|
-
* The rev a PARSED store carries, or 0 when it carries none usable. Split out
|
|
3137
|
-
* so a caller that already read the file — writeToDisk does, once, inside the
|
|
3138
|
-
* lock — can derive the same number without a second read. Called at request
|
|
3139
|
-
* time, long after apply() finished defining it.
|
|
3140
|
-
*/
|
|
3141
|
-
const diskRevFrom = (parsed) => {
|
|
3142
|
-
const rev = parsed !== null && parsed !== undefined && typeof parsed === 'object' ? parsed.rev : null
|
|
3143
|
-
return typeof rev === 'number' && Number.isFinite(rev) && rev > 0 ? Math.round(rev) : 0
|
|
3144
|
-
}
|
|
3145
|
-
|
|
3146
|
-
const readDiskRev = async () => {
|
|
3147
|
-
try {
|
|
3148
|
-
return diskRevFrom(await readStoreFile(storePath))
|
|
3149
|
-
} catch {
|
|
3150
|
-
return 0
|
|
3151
|
-
}
|
|
3152
|
-
}
|
|
3153
|
-
|
|
3154
3161
|
/**
|
|
3155
3162
|
* Load the store from disk. Most branches only read; two of them also write —
|
|
3156
3163
|
* the missing-primary recovery promotes the backup to memory.json, and the
|
|
@@ -5364,9 +5371,7 @@ export function apply(ctx) {
|
|
|
5364
5371
|
// restarted) instead of hammering the route.
|
|
5365
5372
|
res.write('retry: 5000\n\n')
|
|
5366
5373
|
const lite = isLiteStreamRequest(req)
|
|
5367
|
-
|
|
5368
|
-
// heartbeat act on: false means the peer stopped reading this stream.
|
|
5369
|
-
const send = (payload) => res.write(payload)
|
|
5374
|
+
const send = (payload) => { res.write(payload) }
|
|
5370
5375
|
let closed = false
|
|
5371
5376
|
const cleanup = () => {
|
|
5372
5377
|
closed = true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-instruction-memory",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.3",
|
|
4
4
|
"description": "User-only instruction memory for DSH: long-term instructions maintained in the settings UI and auto-injected into every conversation — the model has no write access. 指令记忆:只由用户在设置页维护的长期指令,自动注入此后每轮对话;模型没有任何写入口。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|