@a9i5k4/dsh-auto-memory 3.0.0 → 3.1.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 +30 -13
- package/README.zh-CN.md +30 -13
- package/docs/FRONTEND-CO-CREATION.md +191 -0
- package/docs/GM53-HOMEPAGE-PROMPT.md +323 -0
- package/docs/HANDBOOK.md +88 -52
- package/docs/HOMEPAGE-CONTENT-FOR-GM53.md +299 -0
- package/docs/PROMO-PROMPT-3.0.md +100 -0
- package/docs/USER-GUIDE.en.md +11 -11
- package/docs/USER-GUIDE.zh-CN.md +11 -11
- package/docs/WHITEPAPER.md +207 -0
- package/docs/screenshots/promo/promo-0-banner-v3.png +0 -0
- package/docs/screenshots/promo/promo-0-banner-v4.png +0 -0
- package/docs/screenshots/promo/promo-1b-auto-recall.png +0 -0
- package/lib/activation-host.js +69 -10
- package/lib/board-mode.js +1 -1
- package/lib/client.js +1697 -285
- package/lib/config-io.js +156 -0
- package/lib/context-bridge.js +3 -0
- package/lib/context-host.js +23 -10
- package/lib/degrade.js +385 -0
- package/lib/dsh-home.js +143 -0
- package/lib/episodic-store.js +142 -18
- package/lib/evidence-store.js +8 -1
- package/lib/fact-store.js +484 -43
- package/lib/hub-io.js +217 -0
- package/lib/index-sync.js +13 -1
- package/lib/index.js +1730 -202
- package/lib/intent-clean-safe.js +258 -40
- package/lib/l0-extract.js +231 -16
- package/lib/m4-corpus.js +8 -2
- package/lib/m7-index-sync-host.js +8 -1
- package/lib/memory-envelope.js +6 -1
- package/lib/memory-hub.js +164 -17
- package/lib/memory-index.js +4 -2
- package/lib/note-status-apply.js +118 -0
- package/lib/note-status.js +204 -0
- package/lib/procedure-store.js +333 -31
- package/lib/procedure-switch.js +38 -0
- package/lib/python-sidecar-client.js +314 -11
- package/lib/recall-fusion.js +83 -12
- package/lib/rules-edit.js +159 -0
- package/lib/semantic-decide.js +41 -8
- package/lib/semantic-js.js +51 -6
- package/lib/shadow-host.js +3 -5
- package/lib/skill-export-host.js +153 -0
- package/lib/skill-export.js +239 -0
- package/lib/storage-manage.js +6 -0
- package/lib/temporal-parse.js +191 -159
- package/lib/tier0-catalog.js +45 -3
- package/lib/wb-contract.js +198 -2
- package/lib/wb-sidecar.js +54 -3
- package/package.json +6 -2
- package/docs/internal/ACCEPT-35-LIVE.md +0 -143
- package/docs/internal/ACCEPTANCE-20260914.md +0 -90
- package/docs/internal/ARCH-REVIEW-BRIEF.md +0 -411
- package/docs/internal/ARCH-REVIEW-REQUEST.md +0 -201
- package/docs/internal/ARCH-REVIEW-ROUND2.md +0 -169
- package/docs/internal/ARCH-REVIEW-ROUND3.md +0 -206
- package/docs/internal/ART-DIRECTION-WIREFRAME.md +0 -181
- package/docs/internal/AUDIT-WB-GRAPH-FULL-20260916.md +0 -314
- package/docs/internal/CONCURRENCY-INVESTIGATION-20260917.md +0 -192
- package/docs/internal/CROSS-SESSION-SEARCH-PATH-DECISION.md +0 -72
- package/docs/internal/CROSS-SESSION-SEARCH-RESEARCH.md +0 -131
- package/docs/internal/CUA-VISION-FIX-NOTES.md +0 -78
- package/docs/internal/DECISIONS-20260914-SESSION.md +0 -269
- package/docs/internal/DESIGN-OVERHAUL-PRE-RESEARCH.md +0 -292
- package/docs/internal/DESIGN-P1-STATE-COMMIT-20260915.md +0 -219
- package/docs/internal/DIRECTION-CHECK-WB-GRAPH-20260916.md +0 -132
- package/docs/internal/FEEDBACK-TO-DSHAPI-RELAY.md +0 -13
- package/docs/internal/GH-DISCUSSION-5732-COMMENT.md +0 -74
- package/docs/internal/GPT-ACCEPTANCE-PROMPT-20260916.md +0 -352
- package/docs/internal/GPT-REVIEW-PROMPT.md +0 -216
- package/docs/internal/GROUP-DIGEST-SETUP.md +0 -62
- package/docs/internal/GROUP-LISTENER-SETUP.md +0 -49
- package/docs/internal/GROUP-WEBHOOK-SETUP.md +0 -93
- package/docs/internal/HANDOFF-TO-ZCODE.md +0 -168
- package/docs/internal/KICKOFF-P0.md +0 -254
- package/docs/internal/MASTER-PLAN-3.0.md +0 -411
- package/docs/internal/MEMORY-MUTATION-AND-INDEX-DESIGN.md +0 -85
- package/docs/internal/MERGE-CONFLICT-SCAN-20260914.md +0 -222
- package/docs/internal/NEXT-VERSION-TODO.md +0 -95
- package/docs/internal/OFFICIAL-DISCUSSION-DRAFT.md +0 -80
- package/docs/internal/PENDING-FIXES-20260916.md +0 -289
- package/docs/internal/RAG-KARPATHY-PROGRAM.md +0 -229
- package/docs/internal/RELEASE-PROCESS.md +0 -99
- package/docs/internal/REPORT-P0-NIGHTLY.md +0 -212
- package/docs/internal/REPORT-P5-ACCEPTANCE.md +0 -31
- package/docs/internal/REPORT-WB-GRAPH-NIGHTLY.md +0 -153
- package/docs/internal/REVIEW-WB-GRAPH-SELF.md +0 -81
- package/docs/internal/ROADMAP-20260917-WEEK.md +0 -305
- package/docs/internal/ROADMAP.md +0 -106
- package/docs/internal/RUN-P0-NIGHTLY.md +0 -227
- package/docs/internal/S10-CONSTRUCTION-HANDOFF-20260917.md +0 -175
- package/docs/internal/S10-GAPS-PLAIN-20260917.md +0 -125
- package/docs/internal/SEMANTIC-ARCHITECTURE-SPEC.md +0 -360
- package/docs/internal/SESSION-FILE-REPAIR-PROTOCOL.md +0 -90
- package/docs/internal/SUBAGENT-REPORT-ROUTING-PRE-RESEARCH.md +0 -261
- package/docs/internal/THREE-LAYER-CONTRACT.md +0 -210
- package/docs/internal/TODO-BACKLOG.md +0 -263
- package/docs/internal/TODO-GRAPH.html +0 -715
- package/docs/internal/TODO-GRAPH.html.bak-20260914-v2 +0 -493
- package/docs/internal/TODO-GRAPH.html.bak-20260915-alsfix +0 -710
- package/docs/internal/TODO-GRAPH.html.bak-20260915-p1 +0 -710
- package/docs/internal/TODO-GRAPH.html.bak-20260915-p6a-rev +0 -703
- package/docs/internal/TODO-GRAPH.html.bak-20260915-wshint +0 -710
- package/docs/internal/TODO-GRAPH.html.bak-20260916-batch +0 -715
- package/docs/internal/WB-FORMAT-CONVENTION.md +0 -112
- package/docs/internal/WB-GRAPH-DECISIONS-20260914.md +0 -71
- package/docs/internal/WB-GRAPH-INTEGRATION-PLAN.md +0 -386
- package/docs/internal/WB-GRAPH-RESEARCH-BRIEF.md +0 -118
- package/docs/internal/WB-GRAPH-RESEARCH-EXTERNAL.md +0 -228
- package/docs/internal/WB-GRAPH-RESEARCH-LOCAL.md +0 -190
- package/docs/internal/reviews/CLAIM-VERIFICATION-20260914.md +0 -56
- package/docs/internal/reviews/PLAN-gpt6astra-round2-20260914.md +0 -787
- package/docs/internal/reviews/REVIEW-gpt6astra-20260914.md +0 -112
- package/docs/internal/reviews/ROUND3-REVIEW-INTEGRATION-20260914.md +0 -230
package/lib/hub-io.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M8 记忆中枢(Memory Hub)持久化 IO —— **带健康度记账的 io 适配器**(#110,2026-09-22)。
|
|
3
|
+
*
|
|
4
|
+
* ── 背景(为什么需要这个模块)──
|
|
5
|
+
* hub 三店(episodes / facts / procedures)的 `io` 由 index.js 内联的 `hubIo()` 提供,
|
|
6
|
+
* 旧实现三个方法各自 `catch (_) {}` 把异常**吞在适配器这一层**:
|
|
7
|
+
*
|
|
8
|
+
* 1. 上层三店在 A-8 里写好的 `try { io.save(snapshot()) } catch (e) { … ok:false … }`
|
|
9
|
+
* **永远走不到 catch 分支** ⇒ store 照样返回 `{ ok:true, persisted:true }`,
|
|
10
|
+
* `persistFailures` 恒为 0、`lastPersistError` 恒为 null ⇒ 三条线全绿而磁盘没写上。
|
|
11
|
+
* 2. 用户侧表现为「记忆看着存上了,重启清零」,日志、计数、面板三处都拿不到信号。
|
|
12
|
+
*
|
|
13
|
+
* 现在把这段逻辑从 index.js 提出来,语义收紧为三条:
|
|
14
|
+
* · `save` / `clear` 失败**照原样抛出**(把「写不进去就是写不进去」交还调用方,
|
|
15
|
+
* 让 A-8 既有的 try/catch 真正生效),**同时**记一次健康度;
|
|
16
|
+
* · `load` 保持「无文件 / 损坏 → 返回 null(空启动)」的既有控制流不变,只追加可观测性;
|
|
17
|
+
* · 每次失败都写进 health(errno → 中文人话 + 累计计数 + 时间戳),由
|
|
18
|
+
* `hubIoHealthSnapshotPre()` 投影给 debugInfo / 诊断面板。
|
|
19
|
+
*
|
|
20
|
+
* 边界(与既有纪律一致):
|
|
21
|
+
* · 只做记账与转发,**不改变** 落盘格式、原子性策略(tmp + rename)与任何调用方契约;
|
|
22
|
+
* · 不引入时钟依赖以外的副作用;`onError` 回调抛错不得影响主路径(自身 try/catch 包住);
|
|
23
|
+
* · 不做 unlink/copy 回退:调用方各自保留自己的原子性策略(同 `fs-retry.js` 的边界声明)。
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { mkdirSync, writeFileSync, renameSync, readFileSync, rmSync } from 'node:fs'
|
|
27
|
+
import path from 'node:path'
|
|
28
|
+
|
|
29
|
+
/** errno → 人话。口径:暴露给前端/工具的原因必须人能看懂,不能只甩机器码。 */
|
|
30
|
+
export const HUB_IO_ERRNO_MESSAGES_V1 = Object.freeze({
|
|
31
|
+
EACCES: '权限被拒绝(目标目录不可写)',
|
|
32
|
+
EPERM: '操作被系统拒绝(权限或安全策略拦截,Windows 下也常见于文件被占用)',
|
|
33
|
+
ENOSPC: '磁盘空间不足',
|
|
34
|
+
EROFS: '目标位于只读位置',
|
|
35
|
+
EBUSY: '文件被其它进程占用',
|
|
36
|
+
ENOENT: '路径不存在(父目录缺失)',
|
|
37
|
+
EISDIR: '该名字被一个目录占着,不是文件',
|
|
38
|
+
ENOTDIR: '路径中间有一段不是目录',
|
|
39
|
+
EEXIST: '临时文件已存在',
|
|
40
|
+
ENAMETOOLONG: '路径过长',
|
|
41
|
+
EMFILE: '进程打开的文件过多',
|
|
42
|
+
EIO: '底层读写错误(磁盘或驱动)',
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
/** 把任意异常翻成一句人话(带 errno 便于排障;无 code 时回落 message)。 */
|
|
46
|
+
export function explainHubIoErrorPre(e) {
|
|
47
|
+
const code = e && e.code ? String(e.code) : ''
|
|
48
|
+
const mapped = code ? HUB_IO_ERRNO_MESSAGES_V1[code] : ''
|
|
49
|
+
if (mapped) return mapped + '(' + code + ')'
|
|
50
|
+
if (code) return '文件系统错误 ' + code
|
|
51
|
+
return String((e && e.message) || e || '未知错误')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 新建一份健康度台账(纯内存,零 IO)。 */
|
|
55
|
+
export function createHubIoHealthPre() {
|
|
56
|
+
return { errors: 0, lastError: null, lastErrorAt: 0, saves: 0, loads: 0, clears: 0, byFile: {} }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 记一次失败,并调用 `onError(key, message)`(节流由调用方决定,本模块不持有定时器)。
|
|
61
|
+
* @returns {string} 人话原因
|
|
62
|
+
*/
|
|
63
|
+
function notePre(health, name, op, e, onError) {
|
|
64
|
+
const now = Date.now()
|
|
65
|
+
const human = explainHubIoErrorPre(e)
|
|
66
|
+
health.errors += 1
|
|
67
|
+
health.lastError = name + ' ' + op + ' 失败:' + human
|
|
68
|
+
health.lastErrorAt = now
|
|
69
|
+
const key = name + ':' + op
|
|
70
|
+
const rec = health.byFile[key] || { count: 0, lastError: null, lastErrorAt: 0 }
|
|
71
|
+
rec.count += 1
|
|
72
|
+
rec.lastError = human
|
|
73
|
+
rec.lastErrorAt = now
|
|
74
|
+
health.byFile[key] = rec
|
|
75
|
+
if (typeof onError === 'function') {
|
|
76
|
+
try { onError(key, 'M8 hub 持久化失败 —— ' + health.lastError + '(累计 ' + health.errors + ' 次)') } catch (_) { /* 记账不得影响主路径 */ }
|
|
77
|
+
}
|
|
78
|
+
return human
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 造一个 hub io 适配器工厂。
|
|
83
|
+
* @param {{dir:string, health?:object, onError?:Function, fsApi?:object}} opts
|
|
84
|
+
* `fsApi` 注入点仅供测试(故障注入),缺省用 node:fs 同步 API。
|
|
85
|
+
* @returns {(name:string) => {save:Function, load:Function, clear:Function}}
|
|
86
|
+
*/
|
|
87
|
+
export function createHubIoPre(opts = {}) {
|
|
88
|
+
const dir = String(opts.dir || '')
|
|
89
|
+
const health = opts.health || createHubIoHealthPre()
|
|
90
|
+
const onError = opts.onError
|
|
91
|
+
const api = opts.fsApi || null
|
|
92
|
+
const mk = (api && api.mkdirSync) || mkdirSync
|
|
93
|
+
const wf = (api && api.writeFileSync) || writeFileSync
|
|
94
|
+
const rn = (api && api.renameSync) || renameSync
|
|
95
|
+
const rf = (api && api.readFileSync) || readFileSync
|
|
96
|
+
const rm = (api && api.rmSync) || rmSync
|
|
97
|
+
|
|
98
|
+
// ★#110(2026-09-22):**批内合并落盘**(消除写放大)。
|
|
99
|
+
// 背景:hub 一次喂数会连续写同一份快照 N 次(每行判据 upsert 一次 ⇒ 一次整份写盘)。
|
|
100
|
+
// 做法:批内只记「最后一次的整份数据」,批末统一原子落盘。**语义无损**——每份快照都是全量,
|
|
101
|
+
// 最后一次即最终状态;store 的内存态始终最新,落盘只是它的投影。
|
|
102
|
+
// 明确声明的代价:批未落盘时进程被杀,本批持久化会丢。这批是「机器切出来的流程观察行」,
|
|
103
|
+
// 源头 judgement-shadow 文件仍在、可重放,**不涉及用户数据**。
|
|
104
|
+
// 失败**不静默**:逐文件记 health(含 errno 人话)并走 onError;返回值把 ok/written/errors 交出去。
|
|
105
|
+
let batchDepth = 0
|
|
106
|
+
const pendingWrites = new Map() // filePath → { name, data }
|
|
107
|
+
|
|
108
|
+
function atomicWrite(file, name, data) {
|
|
109
|
+
mk(dir, { recursive: true })
|
|
110
|
+
const tmp = file + '.tmp'
|
|
111
|
+
wf(tmp, JSON.stringify(data), 'utf8')
|
|
112
|
+
rn(tmp, file)
|
|
113
|
+
}
|
|
114
|
+
function flushPendingPre() {
|
|
115
|
+
let written = 0
|
|
116
|
+
let ok = true
|
|
117
|
+
for (const [file, rec] of pendingWrites) {
|
|
118
|
+
try { atomicWrite(file, rec.name, rec.data); written++ } catch (e) { ok = false }
|
|
119
|
+
}
|
|
120
|
+
pendingWrites.clear()
|
|
121
|
+
return { ok, written, errors: health.errors }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const factory = (name) => {
|
|
125
|
+
const file = path.join(dir, String(name))
|
|
126
|
+
return {
|
|
127
|
+
/**
|
|
128
|
+
* 原子写(tmp + rename)。失败**记健康度后原样抛出** —— 上层 A-8 的 try/catch 依赖这一点。
|
|
129
|
+
* 批内(beginBatch 之后 endBatch 之前)改为**只登记不落盘**,批末一次写。
|
|
130
|
+
*/
|
|
131
|
+
save(data) {
|
|
132
|
+
health.saves += 1
|
|
133
|
+
if (batchDepth > 0) { pendingWrites.set(file, { name, data }); return }
|
|
134
|
+
try {
|
|
135
|
+
atomicWrite(file, name, data)
|
|
136
|
+
} catch (e) {
|
|
137
|
+
notePre(health, name, 'save', e, onError)
|
|
138
|
+
throw e
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
/**
|
|
142
|
+
* 读取。保持既有语义:无文件 / 损坏一律返回 null(空启动,fail closed 幂等恢复);
|
|
143
|
+
* 只有「非 ENOENT 的读取失败」与「JSON 解析失败」才记健康度。
|
|
144
|
+
*/
|
|
145
|
+
load() {
|
|
146
|
+
health.loads += 1
|
|
147
|
+
let raw
|
|
148
|
+
try {
|
|
149
|
+
raw = rf(file, 'utf8')
|
|
150
|
+
} catch (e) {
|
|
151
|
+
if (!(e && e.code === 'ENOENT')) notePre(health, name, 'load', e, onError)
|
|
152
|
+
return null
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
return JSON.parse(raw)
|
|
156
|
+
} catch (e) {
|
|
157
|
+
notePre(health, name, 'parse', e, onError)
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
/** 删除。失败**记健康度后原样抛出**(残留快照会在下次 load 时"复活"已清空的数据)。 */
|
|
162
|
+
clear() {
|
|
163
|
+
health.clears += 1
|
|
164
|
+
// 批内 clear 必须先取消该文件的待写,否则批末会把刚删掉的快照又写回来。
|
|
165
|
+
pendingWrites.delete(file)
|
|
166
|
+
try { rm(file, { force: true }) } catch (e) { notePre(health, name, 'clear', e, onError); throw e }
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// ── 批控制(挂在工厂函数上,调用方:`hubIo.beginBatch()` / `hubIo.endBatch()`)──
|
|
171
|
+
factory.beginBatch = () => { batchDepth += 1; return batchDepth }
|
|
172
|
+
factory.endBatch = () => {
|
|
173
|
+
if (batchDepth > 0) batchDepth -= 1
|
|
174
|
+
if (batchDepth > 0) return { ok: true, written: 0, errors: health.errors, deferred: true }
|
|
175
|
+
return flushPendingPre()
|
|
176
|
+
}
|
|
177
|
+
factory.flushBatch = () => flushPendingPre()
|
|
178
|
+
factory.batchPending = () => pendingWrites.size
|
|
179
|
+
return factory
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 健康度只读投影(供 debugInfo / 诊断面板)。
|
|
184
|
+
* 纪律:只出计数 + 人话原因 + 时间戳,**无路径、无原文**;任何异常都不得打断诊断。
|
|
185
|
+
*/
|
|
186
|
+
export function hubIoHealthSnapshotPre(health) {
|
|
187
|
+
const empty = { errors: 0, lastError: null, lastErrorAt: null, saves: 0, loads: 0, clears: 0, byFile: {}, verdict: 'ok', summary: '三层记忆已正常落盘(本轮无写入失败)' }
|
|
188
|
+
try {
|
|
189
|
+
if (!health || typeof health !== 'object') return empty
|
|
190
|
+
const byFile = {}
|
|
191
|
+
const src = (health.byFile && typeof health.byFile === 'object') ? health.byFile : {}
|
|
192
|
+
for (const k of Object.keys(src)) {
|
|
193
|
+
const r = src[k] || {}
|
|
194
|
+
byFile[k] = {
|
|
195
|
+
count: Number(r.count) || 0,
|
|
196
|
+
lastError: r.lastError ? String(r.lastError) : null,
|
|
197
|
+
lastErrorAt: Number(r.lastErrorAt) || null,
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const errors = Number(health.errors) || 0
|
|
201
|
+
return {
|
|
202
|
+
errors,
|
|
203
|
+
lastError: health.lastError ? String(health.lastError) : null,
|
|
204
|
+
lastErrorAt: Number(health.lastErrorAt) || null,
|
|
205
|
+
saves: Number(health.saves) || 0,
|
|
206
|
+
loads: Number(health.loads) || 0,
|
|
207
|
+
clears: Number(health.clears) || 0,
|
|
208
|
+
byFile,
|
|
209
|
+
verdict: errors === 0 ? 'ok' : 'io-error',
|
|
210
|
+
summary: errors === 0
|
|
211
|
+
? '三层记忆已正常落盘(本轮无写入失败)'
|
|
212
|
+
: '三层记忆有 ' + errors + ' 次落盘/读取失败,最近一次:' + String(health.lastError || '') + '。记忆可能只在内存里,重启会丢。',
|
|
213
|
+
}
|
|
214
|
+
} catch (_) {
|
|
215
|
+
return empty
|
|
216
|
+
}
|
|
217
|
+
}
|
package/lib/index-sync.js
CHANGED
|
@@ -16,7 +16,19 @@ import {
|
|
|
16
16
|
M7_INDEX_POLICY_VERSION_V1,
|
|
17
17
|
} from './m7-wire.js'
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* ★ 页字节预算(2026-09-19 上游 PR #78 / issue #64 同步落地)。
|
|
21
|
+
*
|
|
22
|
+
* **为什么是 252KiB 而不是 256KiB**:`maxPageBytes` 只约束**页 payload**,
|
|
23
|
+
* 而 m7-wire 的 `MAX_LINE_BYTES` 是 **256KiB**,约束的是**整条线帧**(payload + 帧信封 + 换行)。
|
|
24
|
+
* 两者同为 256KiB ⇒ payload 落入约 231B 临界窗口的页 **builder 接受、线上必超限**
|
|
25
|
+
* ⇒ worker 判 `line-oversize` fail-closed 退出。
|
|
26
|
+
* 更糟的是叠加了死锁:① 熔断阈值 3,但每段 `index_sync_begin` 的成功帧把计数清零 ⇒ 峰值 2 永不熔断;
|
|
27
|
+
* ② `syncId` 由 recordCount **确定性派生** ⇒ 重试恒重建同一坏页 ⇒ 每 Segment 重生 worker、索引永不 ready。
|
|
28
|
+
*
|
|
29
|
+
* 故留 **4KiB 余量**(≈ 实测信封 202–230B 的 18 倍)。
|
|
30
|
+
*/
|
|
31
|
+
export const INDEX_SYNC_PAGE_BUDGET_V1 = Object.freeze({ maxRecordsPerPage: 64, maxPageBytes: 252 * 1024 })
|
|
20
32
|
const SCOPE_ORDER_PRE = ['Workspace', 'User']
|
|
21
33
|
|
|
22
34
|
function pagePayloadBytes(syncId, pageNo, pageCount, records) {
|