@a9i5k4/dsh-auto-memory 3.1.5 → 3.1.6
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/docs/CONTINUITY-FLOW.md +6 -6
- package/docs/HANDBOOK.md +54 -53
- package/docs/M-CM-PLAN.md +1 -1
- package/docs/M-CM-STATE.md +1 -1
- package/docs/M-CM7-HANDOFF-LAYERED-RETRIEVAL.md +1 -1
- package/docs/M3B-CONTRACT.md +7 -7
- package/docs/M7-AUTONOMOUS-STATE.md +1 -1
- package/docs/M7-TASKSET-DISPATCH.md +1 -1
- package/docs/PROMPT-PACK-LAYERED-RECALL.md +3 -3
- package/docs/PROMPT-SET-STRICT.md +3 -3
- package/docs/RELEASE-GO-NOGO.md +1 -1
- package/docs/STATUS-BOARD.md +1 -1
- package/docs/USER-GUIDE.en.md +21 -21
- package/docs/USER-GUIDE.zh-CN.md +21 -21
- package/docs/WHITEPAPER.md +1 -1
- package/docs/prompts/FIX-AGENT-P12-FULL-REGRESSION.md +1 -1
- package/docs/prompts/LIVE-VERIFY-ZCODE.md +2 -2
- package/docs/prompts/P2-semantic-recall.md +2 -2
- package/docs/prompts/P7-write-fix.md +1 -1
- package/docs/prompts/ZCODE-DROPIN.md +1 -1
- package/lib/acceptance.js +6 -6
- package/lib/activation-host.js +15 -14
- package/lib/activation-inbox-state.js +4 -4
- package/lib/activation-inbox.js +44 -44
- package/lib/board-mode.js +3 -3
- package/lib/client.js +193 -34
- package/lib/config-io.js +12 -12
- package/lib/context-bridge.js +22 -22
- package/lib/context-host.js +24 -23
- package/lib/datadir.js +95 -0
- package/lib/degrade.js +21 -21
- package/lib/dsh-home.js +8 -8
- package/lib/engine-identity.js +12 -12
- package/lib/engine-switch.js +3 -3
- package/lib/episodic-store.js +13 -13
- package/lib/evidence-agg.js +4 -4
- package/lib/evidence-store.js +12 -12
- package/lib/fact-store.js +47 -47
- package/lib/handoff-anchor.js +4 -4
- package/lib/hub-io.js +356 -3
- package/lib/index-sync.js +5 -5
- package/lib/index.js +381 -236
- package/lib/intent-clean-safe.js +3 -3
- package/lib/intent-clean.js +1 -1
- package/lib/l0-extract.js +15 -15
- package/lib/l0-index-sync.js +8 -8
- package/lib/l0-index.js +11 -11
- package/lib/ledger-criteria.js +8 -8
- package/lib/m4-corpus.js +1 -1
- package/lib/m7-index-sync-host.js +2 -2
- package/lib/m7-wire.js +24 -24
- package/lib/memory-anchor.js +1 -1
- package/lib/memory-envelope.js +15 -15
- package/lib/memory-hub.js +95 -50
- package/lib/memory-importance.js +7 -7
- package/lib/memory-mutation.js +5 -5
- package/lib/note-status-apply.js +2 -2
- package/lib/note-status.js +7 -7
- package/lib/policies/activation_policy_v2.json +2 -2
- package/lib/policies/recall_intent_lr_v1.json +1 -1
- package/lib/procedure-observation.js +2 -2
- package/lib/procedure-store.js +117 -28
- package/lib/python-setup.js +6 -5
- package/lib/python-sidecar-client.js +26 -26
- package/lib/recall-fusion.js +12 -12
- package/lib/recall-stats.js +36 -1
- package/lib/rerank-host.js +11 -11
- package/lib/rules-edit.js +9 -9
- package/lib/rules-layer.js +26 -26
- package/lib/semantic-decide.js +7 -7
- package/lib/semantic-js.js +13 -13
- package/lib/shadow-host.js +7 -6
- package/lib/shadow-retrieval.js +40 -40
- package/lib/skill-export-host.js +7 -7
- package/lib/skill-export.js +6 -6
- package/lib/state-commit.js +10 -10
- package/lib/storage-manage.js +6 -6
- package/lib/tier-layer-inject.js +28 -28
- package/lib/tier0-catalog.js +4 -4
- package/lib/wb-contract.js +45 -45
- package/lib/wb-sidecar.js +11 -11
- package/package.json +3 -4
- package/python/m7_activation_features_v2.py +7 -7
- package/python/m7_embedding_v1.py +12 -12
- package/python/policies/activation_policy_v2.json +2 -2
- package/python/policies/decision-record-activation-v2-delta-exp-override-20260824.json +1 -1
- package/python/policies/decision-record-reasoning-kind-admission-20260826.json +2 -2
- package/python/policies/decision-record-stale-gate-per-candidate-20260825.json +2 -2
- package/python/policies/recall_intent_lr_v1.json +1 -1
- package/python/verify_policy_artifact.py +3 -3
- package/python/worker_semantic_v1.py +18 -18
- package/python/worker_v1.py +18 -18
package/lib/context-bridge.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* 零 IO、零依赖(除 node:crypto);不接 Host、不写文件、不启动 Python、不改 prompt。
|
|
4
4
|
*
|
|
5
5
|
* 组成:
|
|
6
|
-
* 1) 策略/预算常量(
|
|
6
|
+
* 1) 策略/预算常量(CONTEXT_BRIDGE_BUDGET_PRE_V1 / 版本化词典)
|
|
7
7
|
* 2) ContextSegmentPre / AuthorizedMemoryRefPre / EvidenceAggregatePre validator
|
|
8
8
|
* 3) ContextPushEnvelopePre validator + canonical identity(observationId)
|
|
9
9
|
* 4) AccessEvidencePre validator + deterministic evidenceId
|
|
@@ -24,13 +24,13 @@ const first32 = (h) => h.slice(0, 32)
|
|
|
24
24
|
const clamp01 = (x) => Math.max(0, Math.min(1, Number(x) || 0))
|
|
25
25
|
|
|
26
26
|
export { NAMESPACE }
|
|
27
|
-
export const CONTEXT_BRIDGE_POLICY_VERSION = '
|
|
28
|
-
export const EVIDENCE_POLICY_VERSION = '
|
|
29
|
-
export const OBSERVATION_PREFIX = '
|
|
30
|
-
export const EVIDENCE_PREFIX = '
|
|
27
|
+
export const CONTEXT_BRIDGE_POLICY_VERSION = 'context_bridge_pre_v1'
|
|
28
|
+
export const EVIDENCE_POLICY_VERSION = 'evidence_pre_v1'
|
|
29
|
+
export const OBSERVATION_PREFIX = 'obs_pre_'
|
|
30
|
+
export const EVIDENCE_PREFIX = 'ev_pre_'
|
|
31
31
|
|
|
32
32
|
/** §4 budget(冻结;变更必须升级 contextPolicyVersion)。 */
|
|
33
|
-
export const
|
|
33
|
+
export const CONTEXT_BRIDGE_BUDGET_PRE_V1 = Object.freeze({
|
|
34
34
|
maxSegments: 8,
|
|
35
35
|
maxInputBytes: 4096,
|
|
36
36
|
maxMemoryRefs: 8,
|
|
@@ -42,16 +42,16 @@ export const CONTEXT_BRIDGE_BUDGET_V1 = Object.freeze({
|
|
|
42
42
|
})
|
|
43
43
|
|
|
44
44
|
/** §6 AccessKindPre 枚举。 */
|
|
45
|
-
export const
|
|
45
|
+
export const ACCESS_KINDS_PRE_V1 = Object.freeze(['seen', 'read', 'cite', 'reuse', 'success', 'correction'])
|
|
46
46
|
|
|
47
47
|
/** §5 ContextAckPre reason 枚举(版本化)。 */
|
|
48
|
-
export const
|
|
48
|
+
export const ACK_REASONS_PRE_V1 = Object.freeze(['ok', 'disabled', 'busy', 'unsupported', 'oversize', 'stale'])
|
|
49
49
|
|
|
50
50
|
/** §7 cite 引用分类器 v1:只认完整 memoryId token(保守;变更必须升级版本)。 */
|
|
51
|
-
export const
|
|
51
|
+
export const CITATION_MEMORY_ID_PATTERN_PRE_V1 = /mem_[0-9a-f]{32}/g
|
|
52
52
|
|
|
53
53
|
/** §7 correction 分类器 v1 冻结词典(用户纠正/拒绝/反例;precision-first)。 */
|
|
54
|
-
export const
|
|
54
|
+
export const CORRECTION_LEXICON_PRE_V1 = Object.freeze([
|
|
55
55
|
'不对', '错了', '不是这样', '反了', '纠正', '过时', '失效', '别再用', '作废',
|
|
56
56
|
'wrong', 'incorrect', 'not right', 'actually no', 'outdated', 'deprecated', 'obsolete', 'disagree',
|
|
57
57
|
])
|
|
@@ -96,7 +96,7 @@ export function validateAuthorizedMemoryRefPre(ref) {
|
|
|
96
96
|
if (typeof ref.recordDigest !== 'string' || !HEX64_RE.test(ref.recordDigest)) p.push('recordDigest')
|
|
97
97
|
if (ref.excerpt !== undefined) {
|
|
98
98
|
if (typeof ref.excerpt !== 'string') p.push('excerpt')
|
|
99
|
-
else if (Buffer.byteLength(ref.excerpt, 'utf8') >
|
|
99
|
+
else if (Buffer.byteLength(ref.excerpt, 'utf8') > CONTEXT_BRIDGE_BUDGET_PRE_V1.excerptBytes) p.push('excerpt-budget')
|
|
100
100
|
}
|
|
101
101
|
if (p.length) return { ok: false, reason: 'invalid:' + p.join(',') }
|
|
102
102
|
return { ok: true, ref }
|
|
@@ -124,7 +124,7 @@ export function validateAccessEvidencePre(ev) {
|
|
|
124
124
|
if (ev.schemaVersion !== 1) p.push('schemaVersion')
|
|
125
125
|
if (ev.namespace !== NAMESPACE) p.push('namespace')
|
|
126
126
|
if (typeof ev.evidenceId !== 'string' || !ev.evidenceId.startsWith(EVIDENCE_PREFIX)) p.push('evidenceId')
|
|
127
|
-
if (!
|
|
127
|
+
if (!ACCESS_KINDS_PRE_V1.includes(ev.kind)) p.push('kind')
|
|
128
128
|
if (typeof ev.memoryId !== 'string' || !MEMORY_ID_RE.test(ev.memoryId)) p.push('memoryId')
|
|
129
129
|
if (typeof ev.anchorId !== 'string' || !ev.anchorId) p.push('anchorId')
|
|
130
130
|
if (!['Session', 'Workspace', 'User'].includes(ev.scope)) p.push('scope')
|
|
@@ -157,13 +157,13 @@ export function validateAccessEvidencePre(ev) {
|
|
|
157
157
|
|
|
158
158
|
/** §4 canonical identity:observationId 由 sessionId+contextVersion+trigger digest+policy 决定。 */
|
|
159
159
|
export function buildObservationId(sessionId, contextVersion, triggerDigest, policyVersion = CONTEXT_BRIDGE_POLICY_VERSION) {
|
|
160
|
-
const parts = ['context-push-v1', String(sessionId || ''), Number(contextVersion) || 0, String(triggerDigest || ''), policyVersion]
|
|
160
|
+
const parts = ['context-push-pre-v1', String(sessionId || ''), Number(contextVersion) || 0, String(triggerDigest || ''), policyVersion]
|
|
161
161
|
return OBSERVATION_PREFIX + first32(sha256Str(JSON.stringify(parts)))
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
/** §9 幂等 identity:evidenceId 由 kind/memory/session 坐标/policy 决定(同事件重放同 id)。 */
|
|
165
165
|
export function buildEvidenceId(input) {
|
|
166
|
-
const parts = ['access-evidence-v1', input.kind, input.memoryId, String(input.sessionId || ''), input.eventSeq | 0,
|
|
166
|
+
const parts = ['access-evidence-pre-v1', input.kind, input.memoryId, String(input.sessionId || ''), input.eventSeq | 0,
|
|
167
167
|
input.nativeSeq === undefined ? null : input.nativeSeq, input.callId || null, input.contextVersion | 0,
|
|
168
168
|
String(input.workspaceKey || ''), input.policyVersion || EVIDENCE_POLICY_VERSION]
|
|
169
169
|
return EVIDENCE_PREFIX + first32(sha256Str(JSON.stringify(parts)))
|
|
@@ -283,7 +283,7 @@ export function createAccessEvidencePre(input) {
|
|
|
283
283
|
* 完整 provenance(无可靠 owner 不建 evidence,§3)→ 每个一条 cite evidence。precision-first。
|
|
284
284
|
*/
|
|
285
285
|
export function createCiteEvidencesFromText(input) {
|
|
286
|
-
const ids = [...new Set(String(input.text == null ? '' : input.text).match(
|
|
286
|
+
const ids = [...new Set(String(input.text == null ? '' : input.text).match(CITATION_MEMORY_ID_PATTERN_PRE_V1) || [])].sort()
|
|
287
287
|
const out = []
|
|
288
288
|
for (const mid of ids) {
|
|
289
289
|
const rec = (input.knownRecords || []).find((r) => r.memoryId === mid)
|
|
@@ -301,7 +301,7 @@ export function createCiteEvidencesFromText(input) {
|
|
|
301
301
|
/** correction 扫描:同一文本同时出现完整 memoryId 且命中纠正词典 → 每个一条 correction。 */
|
|
302
302
|
export function createCorrectionEvidencesFromText(input) {
|
|
303
303
|
const norm = String(input.text == null ? '' : input.text).normalize('NFKC').replace(/[A-Z]/g, (c) => c.toLowerCase())
|
|
304
|
-
if (!
|
|
304
|
+
if (!CORRECTION_LEXICON_PRE_V1.some((w) => norm.includes(w))) return []
|
|
305
305
|
return createCiteEvidencesFromText(input).map((ev) => ({
|
|
306
306
|
...ev,
|
|
307
307
|
kind: 'correction',
|
|
@@ -388,7 +388,7 @@ export function createFakeContextSinkPre(opts = {}) {
|
|
|
388
388
|
frames.push(JSON.parse(JSON.stringify(frame)))
|
|
389
389
|
if (frames.length > capacity) frames.shift()
|
|
390
390
|
stats.accepted++
|
|
391
|
-
return { observationId: id, accepted: true, workerEpoch: 'fake-epoch-v1', reason: 'ok' }
|
|
391
|
+
return { observationId: id, accepted: true, workerEpoch: 'fake-epoch-pre-v1', reason: 'ok' }
|
|
392
392
|
},
|
|
393
393
|
async closeSession() { stats.closedSessions++ },
|
|
394
394
|
async dispose() { frames.length = 0 },
|
|
@@ -401,7 +401,7 @@ export function validateContextAckPre(ack) {
|
|
|
401
401
|
if (typeof ack.observationId !== 'string' || !ack.observationId) return { ok: false, reason: 'observationId' }
|
|
402
402
|
if (typeof ack.accepted !== 'boolean') return { ok: false, reason: 'accepted' }
|
|
403
403
|
if (ack.workerEpoch !== undefined && typeof ack.workerEpoch !== 'string') return { ok: false, reason: 'workerEpoch' }
|
|
404
|
-
if (ack.reason !== undefined && !
|
|
404
|
+
if (ack.reason !== undefined && !ACK_REASONS_PRE_V1.includes(ack.reason)) return { ok: false, reason: 'reason' }
|
|
405
405
|
return { ok: true, ack }
|
|
406
406
|
}
|
|
407
407
|
|
|
@@ -424,7 +424,7 @@ export function buildAuthorizedMemoryRefFromRecord(rec, excerptText) {
|
|
|
424
424
|
* input: {session, cursor, index, trigger, window, memoryRefs, evidence, now, policyVersionGate?, policyVersionLexical?}
|
|
425
425
|
*/
|
|
426
426
|
export function buildContextPushEnvelopePre(input) {
|
|
427
|
-
const B =
|
|
427
|
+
const B = CONTEXT_BRIDGE_BUDGET_PRE_V1
|
|
428
428
|
const drop = []
|
|
429
429
|
const session = input && input.session ? input.session : {}
|
|
430
430
|
const cursor = input && input.cursor ? input.cursor : {}
|
|
@@ -483,8 +483,8 @@ export function buildContextPushEnvelopePre(input) {
|
|
|
483
483
|
evidence: aggs.map((a) => a.aggregate),
|
|
484
484
|
policy: {
|
|
485
485
|
contextPolicyVersion: CONTEXT_BRIDGE_POLICY_VERSION,
|
|
486
|
-
gatePolicyVersion: String(input.policyVersionGate || '
|
|
487
|
-
lexicalPolicyVersion: String(input.policyVersionLexical || '
|
|
486
|
+
gatePolicyVersion: String(input.policyVersionGate || 'gate_pre_v1'),
|
|
487
|
+
lexicalPolicyVersion: String(input.policyVersionLexical || 'lexical_pre_v2'),
|
|
488
488
|
evidencePolicyVersion: EVIDENCE_POLICY_VERSION,
|
|
489
489
|
},
|
|
490
490
|
budget: { maxSegments: B.maxSegments, maxInputBytes: B.maxInputBytes, maxMemoryRefs: B.maxMemoryRefs, maxEvidenceItems: B.maxEvidenceItems },
|
|
@@ -534,7 +534,7 @@ export function createContextPushBridge(opts = {}) {
|
|
|
534
534
|
const sink = opts.sink
|
|
535
535
|
if (!sink || typeof sink.push !== 'function') throw new Error('context-bridge: sink required')
|
|
536
536
|
const signal = opts.signal
|
|
537
|
-
const sent = new BoundedIdSet(
|
|
537
|
+
const sent = new BoundedIdSet(CONTEXT_BRIDGE_BUDGET_PRE_V1.sentObservationIds)
|
|
538
538
|
const inflight = new Map()
|
|
539
539
|
const stats = { sent: 0, accepted: 0, duplicates: 0, superseded: 0, aborted: 0, errors: 0 }
|
|
540
540
|
async function push(frame) {
|
package/lib/context-host.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { memoryDir } from './datadir.js'
|
|
1
2
|
/**
|
|
2
3
|
* M5-3 Context Bridge Host Wiring(docs/M5-CONTRACT.md §12 M5-3)。
|
|
3
4
|
* 桥接 lib/index.js(M2 SessionRuntime/事件流)与 M5-1 纯核心+M5-2 Store:
|
|
@@ -5,7 +6,7 @@
|
|
|
5
6
|
* - accepted Segment → 组装 ContextPushEnvelopePre(trigger/window/memoryRefs/aggregates)
|
|
6
7
|
* → Null/Fake sink(按 config.contextSinkMode 切换)→ push bridge(幂等/latest-wins/abort)
|
|
7
8
|
* - read coverage/cite/correction → AccessEvidencePre → EvidenceEventStore(隐私投影落盘)
|
|
8
|
-
* - memoryRefs 选择复用 M4
|
|
9
|
+
* - memoryRefs 选择复用 M4 lexical_pre_v2 可解释基线与 m4-corpus 授权校验链;
|
|
9
10
|
* M4 Shadow audit 候选绝不追认为 seen/read(本模块不读 shadow audit,结构隔离)。
|
|
10
11
|
* 默认关闭(associativeMemoryEnabled && contextBridgeEnabled 双门):零构造、零 IO、零留存。
|
|
11
12
|
* 本模块自身无 spawn/HTTP;contextSinkMode='python' 仅在 assoc∧bridge∧pythonBackend 三重门下
|
|
@@ -20,8 +21,8 @@ import {
|
|
|
20
21
|
buildContextPushEnvelopePre, buildAuthorizedMemoryRefFromRecord, createAccessEvidencePre,
|
|
21
22
|
createCiteEvidencesFromText, createCorrectionEvidencesFromText, computeReadCoverage,
|
|
22
23
|
createContextPushBridge, createNullContextSinkPre, createFakeContextSinkPre,
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
CORRECTION_LEXICON_PRE_V1,
|
|
25
|
+
CONTEXT_BRIDGE_BUDGET_PRE_V1, CONTEXT_BRIDGE_POLICY_VERSION, EVIDENCE_POLICY_VERSION,
|
|
25
26
|
} from './context-bridge.js'
|
|
26
27
|
import { EvidenceEventStore, rebuildAggregates, workspaceRefOf } from './evidence-store.js'
|
|
27
28
|
import { createPythonContextSinkPre } from './context-sink-python.js'
|
|
@@ -42,11 +43,11 @@ function diagCtx(msg) {
|
|
|
42
43
|
// ★#86-3:统一口径。原实现把 `USERPROFILE || HOME` **直接**当 DSH_HOME(不拼 .dsh)
|
|
43
44
|
// ⇒ 在 Windows 上得到 `C:\Users\X` 而非 `C:\Users\X\.dsh`,与其余 6 处**指向不同目录**。
|
|
44
45
|
// ★同时修掉一个**潜伏 bug**:原代码在 `DSH_HOME` 已设置时仍 `path.join(base, '.dsh', ...)`,
|
|
45
|
-
// 即落成 `$DSH_HOME/.dsh/dsh-auto-memory-diagnose.log`(**多拼一层 .dsh**);
|
|
46
|
+
// 即落成 `$DSH_HOME/.dsh/dsh-auto-memory-pre-diagnose.log`(**多拼一层 .dsh**);
|
|
46
47
|
// 未设置时才恰好正确。resolveDshHomePre() 返回的**已经是 dsh home 本身**,故不再拼 .dsh。
|
|
47
48
|
const base = resolveDshHomePre()
|
|
48
49
|
if (!base) return
|
|
49
|
-
appendFileSync(path.join(base, 'dsh-auto-memory-diagnose.log'),
|
|
50
|
+
appendFileSync(path.join(base, 'dsh-auto-memory-pre-diagnose.log'),
|
|
50
51
|
new Date().toISOString() + ' [ctx-host] ' + String(msg).slice(0, 300) + '\n', 'utf8')
|
|
51
52
|
} catch (e) {}
|
|
52
53
|
}
|
|
@@ -119,7 +120,7 @@ export function createContextHost(opts = {}) {
|
|
|
119
120
|
let bridge = null
|
|
120
121
|
let store = null
|
|
121
122
|
let aggCache = null // {wsRef, miv, list}
|
|
122
|
-
const registry = new CorpusRegistry({ sidecarDir: path.join(
|
|
123
|
+
const registry = new CorpusRegistry({ sidecarDir: path.join(memoryDir('index', dshHome), 'files') })
|
|
123
124
|
|
|
124
125
|
function dshHome() {
|
|
125
126
|
// ★#86-3:统一口径(含 __homedirFn 注入点保留)
|
|
@@ -165,14 +166,14 @@ export function createContextHost(opts = {}) {
|
|
|
165
166
|
}
|
|
166
167
|
function storeFor() {
|
|
167
168
|
if (!store) store = new EvidenceEventStore({
|
|
168
|
-
root:
|
|
169
|
+
root: memoryDir('evidence', dshHome),
|
|
169
170
|
// ★P3-9(组2,2026-09-22):注入统一节流诊断通道;不注入时 store 内回落 console.error。
|
|
170
171
|
onDiag: (key, msg) => { try { diagCtx(msg) } catch (_) {} },
|
|
171
172
|
})
|
|
172
173
|
return store
|
|
173
174
|
}
|
|
174
175
|
function evidenceRootExists() {
|
|
175
|
-
const p =
|
|
176
|
+
const p = memoryDir('evidence', dshHome)
|
|
176
177
|
try { return existsSync(p) } catch (_) { return false }
|
|
177
178
|
}
|
|
178
179
|
function stateFor(runtime) {
|
|
@@ -338,7 +339,7 @@ export function createContextHost(opts = {}) {
|
|
|
338
339
|
const readyPromise = needIndexReady ? ensureIndexReadyFor(paths, corpusSnap) : Promise.resolve({ ready: true })
|
|
339
340
|
// ---- envelope 组装与推送 ----
|
|
340
341
|
const ringItems = runtime.segments ? runtime.segments.snapshot() : []
|
|
341
|
-
const winRaw = ringItems.slice(-
|
|
342
|
+
const winRaw = ringItems.slice(-CONTEXT_BRIDGE_BUDGET_PRE_V1.maxSegments).map((w) => ({
|
|
342
343
|
segmentId: w.id, digest: w.digest, kind: w.kind, eventSeq: w.eventSeq,
|
|
343
344
|
contextVersion: w.contextVersion, ts: w.ts, text: w.text,
|
|
344
345
|
toolName: w.toolName != null ? w.toolName : null,
|
|
@@ -390,7 +391,7 @@ export function createContextHost(opts = {}) {
|
|
|
390
391
|
// D6 融合候选池=词法 kept ∪ 稠密 top-K(语料中找回记录对象;语义强命中但词法零分者由此入选)
|
|
391
392
|
const poolMap = new Map()
|
|
392
393
|
for (const k of ls.kept) if (k && k.memoryId) poolMap.set(k.memoryId, k)
|
|
393
|
-
const denseTop = [...rank.scores.entries()].sort((a, b) => b[1] - a[1]).slice(0,
|
|
394
|
+
const denseTop = [...rank.scores.entries()].sort((a, b) => b[1] - a[1]).slice(0, CONTEXT_BRIDGE_BUDGET_PRE_V1.maxMemoryRefs)
|
|
394
395
|
for (const [mid] of denseTop) {
|
|
395
396
|
if (!poolMap.has(mid)) {
|
|
396
397
|
const rec = (corpusSnap.records || []).find((r) => r && r.memoryId === mid)
|
|
@@ -404,20 +405,20 @@ export function createContextHost(opts = {}) {
|
|
|
404
405
|
}))).map((f) => poolMap.get(f.memoryId)).filter(Boolean)
|
|
405
406
|
}
|
|
406
407
|
const refs = []
|
|
407
|
-
for (const k of keptList.slice(0,
|
|
408
|
+
for (const k of keptList.slice(0, CONTEXT_BRIDGE_BUDGET_PRE_V1.maxMemoryRefs)) {
|
|
408
409
|
const r = buildAuthorizedMemoryRefFromRecord(k, k.text)
|
|
409
410
|
if (r.ok) refs.push(r.ref)
|
|
410
411
|
}
|
|
411
|
-
const aggs = aggregatesFor(wsRef, corpusSnap).slice(0,
|
|
412
|
+
const aggs = aggregatesFor(wsRef, corpusSnap).slice(0, CONTEXT_BRIDGE_BUDGET_PRE_V1.maxEvidenceItems)
|
|
412
413
|
const built = buildContextPushEnvelopePre({
|
|
413
414
|
session: { sessionId: runtime.sessionId || '', agentId: runtime.agentId || '', workspaceKey: paths.workspaceKey, scope: 'Workspace' },
|
|
414
415
|
cursor: { eventSeq: seg.eventSeq, nativeSeq: seg.nativeSeq, contextVersion: runtime.contextVersion },
|
|
415
|
-
index: { memoryIndexVersion: corpusSnap ? corpusSnap.memoryIndexVersion : ('
|
|
416
|
+
index: { memoryIndexVersion: corpusSnap ? corpusSnap.memoryIndexVersion : ('idx_pre_' + '0'.repeat(32)), sourceEpochs: corpusSnap ? corpusSnap.sources.map((s) => s.sourceEpoch) : [] },
|
|
416
417
|
trigger: {
|
|
417
418
|
segmentId: seg.id, digest: seg.digest, kind: seg.kind, eventSeq: seg.eventSeq,
|
|
418
419
|
contextVersion: seg.contextVersion, ts: seg.ts, text: seg.text,
|
|
419
420
|
},
|
|
420
|
-
window: winRaw.slice(0,
|
|
421
|
+
window: winRaw.slice(0, CONTEXT_BRIDGE_BUDGET_PRE_V1.maxSegments),
|
|
421
422
|
memoryRefs: refs,
|
|
422
423
|
evidence: aggs,
|
|
423
424
|
now: Date.now(),
|
|
@@ -506,13 +507,13 @@ export function createContextHost(opts = {}) {
|
|
|
506
507
|
' dense=' + num2(dec._denseTop) + ' margin=' + num2(dec._margin) +
|
|
507
508
|
' ' + (dec.reasonCodes || []).join(',')).slice(0, 160)
|
|
508
509
|
const act = {
|
|
509
|
-
schemaVersion: 1, namespace: 'dsh-auto-memory', kind: 'activation_request',
|
|
510
|
-
activationId: '
|
|
511
|
-
observationId: obs, workerEpoch: 'js-decide-v1',
|
|
510
|
+
schemaVersion: 1, namespace: 'dsh-auto-memory-pre', kind: 'activation_request',
|
|
511
|
+
activationId: 'act_pre_' + (obs.startsWith('obs_pre_') ? obs.slice(8, 40) : 'jsdecide'),
|
|
512
|
+
observationId: obs, workerEpoch: 'js-decide-pre-v1',
|
|
512
513
|
sessionId: session.sessionId || '', agentId: session.agentId || '',
|
|
513
514
|
workspaceKey: session.workspaceKey || '', scope: session.scope || 'Workspace',
|
|
514
515
|
contextVersion: cursor.contextVersion || 0, memoryIndexVersion: built.frame.index ? built.frame.index.memoryIndexVersion : '',
|
|
515
|
-
threshold: { policyVersion: dec.features ? '
|
|
516
|
+
threshold: { policyVersion: dec.features ? 'activation_policy_pre_v2' : 'activation_policy_pre_v2', score: (dec.features && dec.features.intentProb) || 0, threshold: 0.45, reason: jsReason },
|
|
516
517
|
level: 'excerpt',
|
|
517
518
|
candidates: (function () {
|
|
518
519
|
// 2026-08-27 优化③:候选方案档位(balanced=3×40 / dense=6×20 / custom=自定义)
|
|
@@ -523,7 +524,7 @@ export function createContextHost(opts = {}) {
|
|
|
523
524
|
else { n = 3; ex = 40 } // balanced 默认
|
|
524
525
|
return refs.slice(0, n).map(function (r, i) {
|
|
525
526
|
return {
|
|
526
|
-
candidateId: '
|
|
527
|
+
candidateId: 'cand_pre_' + String(obs || 'js').slice(-32) + i,
|
|
527
528
|
memoryId: r.memoryId, anchorId: r.anchorId || '', scope: r.scope || 'Workspace',
|
|
528
529
|
sourceRef: r.sourceRef || '', sourceEpoch: r.sourceEpoch || '', sourceVersion: r.sourceVersion || 1,
|
|
529
530
|
fileDigest: r.fileDigest || '', recordDigest: r.recordDigest || '',
|
|
@@ -551,7 +552,7 @@ export function createContextHost(opts = {}) {
|
|
|
551
552
|
if (actives.length && typeof engine._jsSemanticRank === 'function') {
|
|
552
553
|
try {
|
|
553
554
|
const recs = actives.map((p) => ({ memoryId: String(p.procedureId), text: String(p.title || '') + ' ' + (Array.isArray(p.steps) ? p.steps.join(' ') : String(p.steps || '')) }))
|
|
554
|
-
const miv = '
|
|
555
|
+
const miv = 'idx_pre_' + createHash('sha256').update(recs.map((r) => r.memoryId).join(',')).digest('hex').slice(0, 32)
|
|
555
556
|
const rank = await engine._jsSemanticRank({ memoryIndexVersion: miv, records: recs }, jsDecideQueryText)
|
|
556
557
|
if (rank && rank.scores && rank.scores.size) {
|
|
557
558
|
const [topId, topScore] = [...rank.scores.entries()].sort((a, b) => b[1] - a[1])[0]
|
|
@@ -647,7 +648,7 @@ export function createContextHost(opts = {}) {
|
|
|
647
648
|
let attributedPrefix = 'none'
|
|
648
649
|
try {
|
|
649
650
|
const norm = String(seg.text == null ? '' : seg.text).normalize('NFKC').replace(/[A-Z]/g, (c) => c.toLowerCase())
|
|
650
|
-
lexHits =
|
|
651
|
+
lexHits = CORRECTION_LEXICON_PRE_V1.filter((w) => norm.includes(w)).length
|
|
651
652
|
if (lexHits > 0) {
|
|
652
653
|
const st = storeFor()
|
|
653
654
|
const recent = selectCorrectionAttributionPre({ events: st.loadEvents().events, now: Date.now() })
|
|
@@ -768,7 +769,7 @@ export function createContextHost(opts = {}) {
|
|
|
768
769
|
const JSDECIDE_SHADOW_MAX_LINES = 256
|
|
769
770
|
function jsDecideShadowLog(entry) {
|
|
770
771
|
try {
|
|
771
|
-
const dir =
|
|
772
|
+
const dir = memoryDir('semantic', dshHome)
|
|
772
773
|
const f = path.join(dir, 'js-decide-shadow.jsonl')
|
|
773
774
|
let line = JSON.stringify(entry)
|
|
774
775
|
if (!line) return
|
|
@@ -798,7 +799,7 @@ export function createContextHost(opts = {}) {
|
|
|
798
799
|
capturedPathKeys: [...pathsByKey.keys()].slice(0, 8),
|
|
799
800
|
lastSegmentRuntimeKey: lastSegmentRuntimeKey,
|
|
800
801
|
lastSegmentSessionRef: lastSegmentSessionRef,
|
|
801
|
-
evidenceDirPath:
|
|
802
|
+
evidenceDirPath: memoryDir('evidence', dshHome),
|
|
802
803
|
durableEventsOnDisk: evidenceRootExists() ? storeFor().loadEvents().events.length : 0,
|
|
803
804
|
stats: { ...stats, bridge: { ...b.stats } },
|
|
804
805
|
storeStats: store ? { ...store.stats } : null,
|
package/lib/datadir.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 记忆数据目录的唯一收口(去 pre,2026-09-23)。
|
|
3
|
+
*
|
|
4
|
+
* 历史:目录名原为 `<name>-pre`。去 pre 后统一裸名。
|
|
5
|
+
*
|
|
6
|
+
* 迁移策略:**不 move,只增量补齐**。三条实测依据:
|
|
7
|
+
* ① 改名前活宿主仍在往旧目录写(hub-pre/facts.json 改名后仍有写入)⇒ move 会分脑丢数据;
|
|
8
|
+
* ② **裸名目录可能早已存在**(实测 `memory/hub/` 创建于 09-01、内含 3 个 0 条的 JSON 空壳),
|
|
9
|
+
* 所以「新目录不存在才复制」这个判据会**整条跳过**,导致 56 条 procedures(108 KB)
|
|
10
|
+
* 与 9.3 MB facts 在重启后全部不可见 —— 属数据丢失级缺陷;
|
|
11
|
+
* ③ 因而判据必须是**逐文件语义判据**,而不是「目录是否存在」。
|
|
12
|
+
*
|
|
13
|
+
* 逐文件规则(fail toward duplication, never toward loss):
|
|
14
|
+
* · 目标缺失 → 复制
|
|
15
|
+
* · 目标在、源更「有内容」 → 复制(仅当目标被判定为**空壳**:能解析为 JSON 且集合为空)
|
|
16
|
+
* · 其余 → 跳过(绝不覆盖已有真实数据)
|
|
17
|
+
*
|
|
18
|
+
* 三条纪律:
|
|
19
|
+
* ① 惰性:调用时机与原先的 path.join 完全一致(环境变量已就位,不会误写真实 home)
|
|
20
|
+
* ② 幂等:补齐过的文件不再复制;重复调用零副作用
|
|
21
|
+
* ③ fail-soft:任何异常都不抛,返回原定路径
|
|
22
|
+
*/
|
|
23
|
+
import fs from 'node:fs'
|
|
24
|
+
import path from 'node:path'
|
|
25
|
+
import { resolveDshHomePre } from './dsh-home.js'
|
|
26
|
+
|
|
27
|
+
/** 已处理过的 (base,name) —— 键必须含 home,否则引擎作用域(各自 dshHome)会互相串味。 */
|
|
28
|
+
const migrated = new Set()
|
|
29
|
+
|
|
30
|
+
/** 把 JSON 文件判成「空壳」:能解析,且其中的集合型字段全是空数组/空对象。 */
|
|
31
|
+
function isEmptyShell(file) {
|
|
32
|
+
try {
|
|
33
|
+
const j = JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
34
|
+
if (!j || typeof j !== 'object') return false
|
|
35
|
+
const colls = Object.values(j).filter((v) => Array.isArray(v) || (v && typeof v === 'object'))
|
|
36
|
+
if (colls.length === 0) return false // 没有任何集合字段 ⇒ 不是可判定的 store,保守视为有内容
|
|
37
|
+
return colls.every((v) => (Array.isArray(v) ? v.length === 0 : Object.keys(v).length === 0))
|
|
38
|
+
} catch (_) {
|
|
39
|
+
return false // 解析失败 ⇒ 保守视为有内容,不覆盖
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 逐文件增量补齐:只写「目标缺失」或「目标是空壳」的文件。 */
|
|
44
|
+
function mergeInto(srcDir, dstDir) {
|
|
45
|
+
let copied = 0
|
|
46
|
+
const walk = (rel) => {
|
|
47
|
+
const sAbs = path.join(srcDir, rel)
|
|
48
|
+
for (const e of fs.readdirSync(sAbs, { withFileTypes: true })) {
|
|
49
|
+
const childRel = rel ? path.join(rel, e.name) : e.name
|
|
50
|
+
if (e.isDirectory()) { walk(childRel); continue }
|
|
51
|
+
const s = path.join(srcDir, childRel)
|
|
52
|
+
const d = path.join(dstDir, childRel)
|
|
53
|
+
const dExists = fs.existsSync(d)
|
|
54
|
+
if (dExists && !(s.endsWith('.json') && isEmptyShell(d) && !isEmptyShell(s))) continue
|
|
55
|
+
fs.mkdirSync(path.dirname(d), { recursive: true })
|
|
56
|
+
fs.copyFileSync(s, d)
|
|
57
|
+
copied++
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
walk('')
|
|
61
|
+
return copied
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function memoryDir(name, homeFn) {
|
|
65
|
+
// homeFn 由调用方注入:引擎作用域模块必须传各自的 dshHome(口径与原代码逐字一致)。
|
|
66
|
+
const home = typeof homeFn === 'function' ? homeFn() : resolveDshHomePre()
|
|
67
|
+
const base = path.join(home, 'memory')
|
|
68
|
+
const nw = path.join(base, name)
|
|
69
|
+
const key = base + '\u0000' + name
|
|
70
|
+
if (migrated.has(key)) return nw
|
|
71
|
+
migrated.add(key)
|
|
72
|
+
try {
|
|
73
|
+
const od = path.join(base, name + '-pre')
|
|
74
|
+
if (fs.existsSync(od)) {
|
|
75
|
+
fs.mkdirSync(nw, { recursive: true })
|
|
76
|
+
const n = mergeInto(od, nw)
|
|
77
|
+
if (n > 0) {
|
|
78
|
+
fs.appendFileSync(
|
|
79
|
+
path.join(base, 'datadir-migration.log'),
|
|
80
|
+
new Date().toISOString() + ' merged ' + n + ' file(s) ' + name + '-pre -> ' + name + '\n'
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return nw
|
|
85
|
+
} catch (e) {
|
|
86
|
+
// fail-soft:补齐失败时**回退读旧目录**(读得到历史数据优先于名字干净)。
|
|
87
|
+
try {
|
|
88
|
+
const od = path.join(base, name + '-pre')
|
|
89
|
+
if (fs.existsSync(od)) return od
|
|
90
|
+
} catch (_) { /* ignore */ }
|
|
91
|
+
return nw
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default { memoryDir }
|
package/lib/degrade.js
CHANGED
|
@@ -30,13 +30,13 @@
|
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
/** 身份常量(枚举类常量须配断言兜底 —— 本仓纪律)。 */
|
|
33
|
-
export const
|
|
33
|
+
export const DEGRADE_SCHEMA_PRE_V1 = 'degrade_pre_v1'
|
|
34
34
|
|
|
35
35
|
/** 环形缓冲上限:防内存无界增长。 */
|
|
36
|
-
export const
|
|
36
|
+
export const DEGRADE_CAP_PRE_V1 = 200
|
|
37
37
|
|
|
38
38
|
/** 单条 reason 截断长度:与既有 diag 同口径,且防长文本撑爆状态文件。 */
|
|
39
|
-
export const
|
|
39
|
+
export const DEGRADE_REASON_MAX_PRE_V1 = 200
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* 臂状态枚举(fail-closed 校验)。
|
|
@@ -46,10 +46,10 @@ export const DEGRADE_REASON_MAX_V1 = 200
|
|
|
46
46
|
* - `disabled` 被配置关闭
|
|
47
47
|
* - `unknown` 无法判定(**不得**当作正常,面板应显式呈现)
|
|
48
48
|
*/
|
|
49
|
-
export const
|
|
49
|
+
export const ARM_STATES_PRE_V1 = Object.freeze(['active', 'no-input', 'degraded', 'disabled', 'unknown'])
|
|
50
50
|
|
|
51
51
|
/** 已知降级 kind(仅作文档/断言用,**不限制**调用方传入新 kind)。 */
|
|
52
|
-
export const
|
|
52
|
+
export const DEGRADE_KINDS_PRE_V1 = Object.freeze([
|
|
53
53
|
'semantic-arm', // 语义臂择优失败 → 回退词法
|
|
54
54
|
'evidence-arm', // evidence 聚合失败 → importance 中性
|
|
55
55
|
'l0-sync', // L0 索引同步失败 → 索引陈旧
|
|
@@ -63,7 +63,7 @@ export const DEGRADE_KINDS_V1 = Object.freeze([
|
|
|
63
63
|
* @param {Function} [opts.now] 取时函数(注入便于测试确定性)
|
|
64
64
|
*/
|
|
65
65
|
export function createDegradeSinkPre(opts = {}) {
|
|
66
|
-
const cap = Number.isFinite(opts.cap) && opts.cap > 0 ? Math.floor(opts.cap) :
|
|
66
|
+
const cap = Number.isFinite(opts.cap) && opts.cap > 0 ? Math.floor(opts.cap) : DEGRADE_CAP_PRE_V1
|
|
67
67
|
const now = typeof opts.now === 'function' ? opts.now : () => Date.now()
|
|
68
68
|
|
|
69
69
|
/** @type {Map<string, number>} kind → 累计次数(不受环形淘汰影响) */
|
|
@@ -84,7 +84,7 @@ export function createDegradeSinkPre(opts = {}) {
|
|
|
84
84
|
if (recent.length >= cap) { recent.shift(); evicted += 1 }
|
|
85
85
|
recent.push({
|
|
86
86
|
kind: k,
|
|
87
|
-
reason: String(reason == null ? '' : reason).slice(0,
|
|
87
|
+
reason: String(reason == null ? '' : reason).slice(0, DEGRADE_REASON_MAX_PRE_V1),
|
|
88
88
|
at: now(),
|
|
89
89
|
})
|
|
90
90
|
} catch (_) { /* 元规则:留痕失败不得影响主流程 */ }
|
|
@@ -95,7 +95,7 @@ export function createDegradeSinkPre(opts = {}) {
|
|
|
95
95
|
let out
|
|
96
96
|
try {
|
|
97
97
|
out = {
|
|
98
|
-
schemaVersion:
|
|
98
|
+
schemaVersion: DEGRADE_SCHEMA_PRE_V1,
|
|
99
99
|
updatedAt: new Date(now()).toISOString(),
|
|
100
100
|
counts: Object.fromEntries(counts),
|
|
101
101
|
recent: recent.map((r) => ({ ...r, at: new Date(r.at).toISOString() })),
|
|
@@ -104,7 +104,7 @@ export function createDegradeSinkPre(opts = {}) {
|
|
|
104
104
|
}
|
|
105
105
|
} catch (_) {
|
|
106
106
|
// 快照失败也要给出**结构性合法**的最小对象,不能让读取方拿到 undefined
|
|
107
|
-
out = { schemaVersion:
|
|
107
|
+
out = { schemaVersion: DEGRADE_SCHEMA_PRE_V1, updatedAt: null, counts: {}, recent: [], evicted, cap }
|
|
108
108
|
}
|
|
109
109
|
return out
|
|
110
110
|
}
|
|
@@ -126,14 +126,14 @@ export function createDegradeSinkPre(opts = {}) {
|
|
|
126
126
|
* 这不是"降级记录",而是**状态陈述** —— 目的是让「某条臂没在工作」可见,
|
|
127
127
|
* 而不必靠"每次 recall 都报错"来推断。例:`evidence: 'no-input'` 一眼可见。
|
|
128
128
|
*
|
|
129
|
-
* @param {Record<string, string>} states 臂名 → 状态(须属
|
|
129
|
+
* @param {Record<string, string>} states 臂名 → 状态(须属 ARM_STATES_PRE_V1;非法值归一为 'unknown')
|
|
130
130
|
*/
|
|
131
131
|
export function deriveArmsHealthPre(states) {
|
|
132
132
|
const out = {}
|
|
133
133
|
try {
|
|
134
134
|
for (const [arm, st] of Object.entries(states || {})) {
|
|
135
135
|
const s = String(st == null ? '' : st)
|
|
136
|
-
out[String(arm)] =
|
|
136
|
+
out[String(arm)] = ARM_STATES_PRE_V1.includes(s) ? s : 'unknown'
|
|
137
137
|
}
|
|
138
138
|
} catch (_) { /* fail-soft */ }
|
|
139
139
|
return out
|
|
@@ -192,10 +192,10 @@ export function persistDegradeLedgerPre(p) {
|
|
|
192
192
|
// 故两者**并列而不混用**:各自的 counts/recent 互不干扰。
|
|
193
193
|
|
|
194
194
|
/** 配额探针的身份常量(枚举类常量须配断言兜底 —— 本仓纪律)。 */
|
|
195
|
-
export const
|
|
195
|
+
export const QUOTA_PROBE_SCHEMA_PRE_V1 = 'quota_probe_pre_v1'
|
|
196
196
|
|
|
197
197
|
/** 采样环上限:与降级台账同口径(有界,防内存无界增长)。 */
|
|
198
|
-
export const
|
|
198
|
+
export const QUOTA_PROBE_CAP_PRE_V1 = 200
|
|
199
199
|
|
|
200
200
|
/**
|
|
201
201
|
* 配额判定结论枚举(fail-closed 校验)。
|
|
@@ -204,10 +204,10 @@ export const QUOTA_PROBE_CAP_V1 = 200
|
|
|
204
204
|
* - `balanced` 既有丢弃但未持续、占用也合理
|
|
205
205
|
* - `insufficient-data` **样本不足,不猜**(这是默认值 —— 宁可说不知道)
|
|
206
206
|
*/
|
|
207
|
-
export const
|
|
207
|
+
export const QUOTA_VERDICTS_PRE_V1 = Object.freeze(['under-quota', 'over-quota', 'balanced', 'insufficient-data'])
|
|
208
208
|
|
|
209
209
|
/** 判定阈值(集中声明,便于断言锁定与后续按观测调参)。 */
|
|
210
|
-
export const
|
|
210
|
+
export const QUOTA_THRESHOLDS_PRE_V1 = Object.freeze({
|
|
211
211
|
/** 至少这么多轮采样才敢下结论(少于它一律 insufficient-data)。 */
|
|
212
212
|
minSamples: 8,
|
|
213
213
|
/** 某层"出现丢弃"的轮数占比 ≥ 此值 ⇒ under-quota。 */
|
|
@@ -227,7 +227,7 @@ export const QUOTA_THRESHOLDS_V1 = Object.freeze({
|
|
|
227
227
|
* @param {Function} [opts.now] 取时函数(注入便于测试确定性)
|
|
228
228
|
*/
|
|
229
229
|
export function createQuotaProbePre(opts = {}) {
|
|
230
|
-
const cap = Number.isFinite(opts.cap) && opts.cap > 0 ? Math.floor(opts.cap) :
|
|
230
|
+
const cap = Number.isFinite(opts.cap) && opts.cap > 0 ? Math.floor(opts.cap) : QUOTA_PROBE_CAP_PRE_V1
|
|
231
231
|
const now = typeof opts.now === 'function' ? opts.now : () => Date.now()
|
|
232
232
|
/** @type {Array<object>} 最近采样(有界) */
|
|
233
233
|
let samples = []
|
|
@@ -269,7 +269,7 @@ export function createQuotaProbePre(opts = {}) {
|
|
|
269
269
|
let out
|
|
270
270
|
try {
|
|
271
271
|
out = {
|
|
272
|
-
schemaVersion:
|
|
272
|
+
schemaVersion: QUOTA_PROBE_SCHEMA_PRE_V1,
|
|
273
273
|
updatedAt: new Date(now()).toISOString(),
|
|
274
274
|
samples: samples.map((s) => ({ ...s, at: new Date(s.at).toISOString(), perLayer: { ...s.perLayer } })),
|
|
275
275
|
evicted,
|
|
@@ -277,7 +277,7 @@ export function createQuotaProbePre(opts = {}) {
|
|
|
277
277
|
}
|
|
278
278
|
} catch (_) {
|
|
279
279
|
// 快照失败也要给出**结构性合法**的最小对象,不能让读取方拿到 undefined
|
|
280
|
-
out = { schemaVersion:
|
|
280
|
+
out = { schemaVersion: QUOTA_PROBE_SCHEMA_PRE_V1, updatedAt: null, samples: [], evicted, cap }
|
|
281
281
|
}
|
|
282
282
|
return out
|
|
283
283
|
}
|
|
@@ -300,14 +300,14 @@ export function createQuotaProbePre(opts = {}) {
|
|
|
300
300
|
* 且把"凭什么这么判"的原始数据(dropRate/perLayer/usage)一并返回,供人复核。
|
|
301
301
|
*
|
|
302
302
|
* @param {object} snap `createQuotaProbePre().snapshot()` 的产物
|
|
303
|
-
* @param {object} [opts] 覆盖阈值(默认取
|
|
303
|
+
* @param {object} [opts] 覆盖阈值(默认取 QUOTA_THRESHOLDS_PRE_V1)
|
|
304
304
|
* @returns {{version:string, verdict:string, samples:number, dropRate:number,
|
|
305
305
|
* usage:number, perLayer:object, reasons:string[]}}
|
|
306
306
|
*/
|
|
307
307
|
export function deriveQuotaVerdictPre(snap, opts = {}) {
|
|
308
|
-
const th = { ...
|
|
308
|
+
const th = { ...QUOTA_THRESHOLDS_PRE_V1, ...(opts || {}) }
|
|
309
309
|
const base = {
|
|
310
|
-
version: '
|
|
310
|
+
version: 'quota_verdict_pre_v1',
|
|
311
311
|
verdict: 'insufficient-data',
|
|
312
312
|
samples: 0,
|
|
313
313
|
dropRate: 0,
|
package/lib/dsh-home.js
CHANGED
|
@@ -49,13 +49,13 @@ import os from 'node:os'
|
|
|
49
49
|
import path from 'node:path'
|
|
50
50
|
|
|
51
51
|
/** 环境变量名(集中一处,便于将来改名)。 */
|
|
52
|
-
export const
|
|
52
|
+
export const DSH_HOME_ENV_PRE_V1 = 'DSH_HOME'
|
|
53
53
|
|
|
54
54
|
/** 默认子目录名。 */
|
|
55
|
-
export const
|
|
55
|
+
export const DSH_HOME_DIRNAME_PRE_V1 = '.dsh'
|
|
56
56
|
|
|
57
57
|
/** 全失败时的最后兜底(相对路径,保证返回非空)。 */
|
|
58
|
-
export const
|
|
58
|
+
export const DSH_HOME_FALLBACK_PRE_V1 = '.dsh'
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* 取 home 基准目录(用于拼 `.dsh`)。**永不抛**。
|
|
@@ -90,18 +90,18 @@ export function resolveDshHomePre(override) {
|
|
|
90
90
|
} catch (_) {}
|
|
91
91
|
// ② 环境变量
|
|
92
92
|
try {
|
|
93
|
-
const env = process.env[
|
|
93
|
+
const env = process.env[DSH_HOME_ENV_PRE_V1]
|
|
94
94
|
if (env && String(env).trim()) return String(env).trim()
|
|
95
95
|
} catch (_) {}
|
|
96
96
|
// ③ / ④ 基准目录 + .dsh
|
|
97
97
|
const base = homeBasePre()
|
|
98
98
|
if (base) {
|
|
99
99
|
try {
|
|
100
|
-
return path.join(base,
|
|
100
|
+
return path.join(base, DSH_HOME_DIRNAME_PRE_V1)
|
|
101
101
|
} catch (_) {}
|
|
102
102
|
}
|
|
103
103
|
// ⑤ 最后兜底
|
|
104
|
-
return
|
|
104
|
+
return DSH_HOME_FALLBACK_PRE_V1
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/**
|
|
@@ -127,14 +127,14 @@ export function resolveDshHomeForEnginePre(engine) {
|
|
|
127
127
|
} catch (_) {}
|
|
128
128
|
// ② 环境变量(与原实现同优先级)
|
|
129
129
|
try {
|
|
130
|
-
const env = process.env[
|
|
130
|
+
const env = process.env[DSH_HOME_ENV_PRE_V1]
|
|
131
131
|
if (env && String(env).trim()) return String(env).trim()
|
|
132
132
|
} catch (_) {}
|
|
133
133
|
// ③ 既有注入点 __homedirFn:返回的是「home 基准目录」,仍需拼 .dsh
|
|
134
134
|
try {
|
|
135
135
|
if (typeof e.__homedirFn === 'function') {
|
|
136
136
|
const base = e.__homedirFn()
|
|
137
|
-
if (base && String(base).trim()) return path.join(String(base).trim(),
|
|
137
|
+
if (base && String(base).trim()) return path.join(String(base).trim(), DSH_HOME_DIRNAME_PRE_V1)
|
|
138
138
|
}
|
|
139
139
|
} catch (_) {}
|
|
140
140
|
// ④ 默认链(homedir → USERPROFILE/HOME → '.dsh')
|