@a9i5k4/dsh-auto-memory 2.5.3 → 3.0.1
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 +189 -7
- package/README.zh-CN.md +189 -7
- package/docs/CONTRIBUTORS.html +471 -0
- package/docs/FRONTEND-CO-CREATION.md +191 -0
- package/docs/GM53-HOMEPAGE-PROMPT.md +323 -0
- package/docs/HANDOFF-CRITERIA.md +92 -0
- package/docs/HOMEPAGE-CONTENT-FOR-GM53.md +299 -0
- package/docs/INTEGRATION-ANALYSIS.md +350 -348
- package/docs/PROMO-PROMPT-3.0.md +100 -0
- package/docs/USER-GUIDE.en.md +58 -3
- package/docs/USER-GUIDE.zh-CN.md +59 -4
- package/docs/WHITEPAPER.md +207 -0
- package/docs/internal/ACCEPT-35-LIVE.md +143 -0
- package/docs/internal/ACCEPTANCE-20260914.md +90 -0
- package/docs/internal/ARCH-REVIEW-BRIEF.md +411 -0
- package/docs/internal/ARCH-REVIEW-REQUEST.md +201 -0
- package/docs/internal/ARCH-REVIEW-ROUND2.md +169 -0
- package/docs/internal/ARCH-REVIEW-ROUND3.md +206 -0
- package/docs/internal/ARCHITECTURE-FOR-ZCODE-20260920.md +397 -0
- package/docs/internal/ART-DIRECTION-DEEPSEEK-20260920.md +351 -0
- package/docs/internal/ART-DIRECTION-WIREFRAME.md +191 -181
- package/docs/internal/ART-DIRECTION-WIREFRAME.md.bak-superseded +181 -0
- package/docs/internal/AUDIT-WB-GRAPH-FULL-20260916.md +314 -0
- package/docs/internal/BATTLE-PLAN-20260917.md +871 -0
- package/docs/internal/CONCURRENCY-INVESTIGATION-20260917.md +192 -0
- package/docs/internal/CROSS-SESSION-SEARCH-PATH-DECISION.md +72 -0
- package/docs/internal/CROSS-SESSION-SEARCH-RESEARCH.md +131 -0
- package/docs/internal/DECISIONS-20260914-SESSION.md +269 -0
- package/docs/internal/DESIGN-P1-STATE-COMMIT-20260915.md +219 -0
- package/docs/internal/DIRECTION-CHECK-WB-GRAPH-20260916.md +132 -0
- package/docs/internal/FEATURE-INVENTORY.md +531 -0
- package/docs/internal/FEEDBACK-TO-DSHAPI-RELAY.md +13 -0
- package/docs/internal/G-SERIES-EXECUTION-20260917.md +248 -0
- package/docs/internal/G3-DESIGN-20260918.md +82 -0
- package/docs/internal/G3-DISK-FORMAT-GAP-20260919.md +92 -0
- package/docs/internal/GH-DISCUSSION-5732-COMMENT.md +74 -0
- package/docs/internal/GPT-ACCEPTANCE-PROMPT-20260916.md +352 -0
- package/docs/internal/GPT-REVIEW-PROMPT.md +216 -0
- package/docs/internal/GROUP-WEBHOOK-SETUP.md +33 -0
- package/docs/internal/HANDOFF-TO-ZCODE-20260920.md +309 -0
- package/docs/internal/HERMES-DATA-VERIFICATION-20260919.md +120 -0
- package/docs/internal/HERMES-LEGACY-STATUS-20260919.md +74 -0
- package/docs/internal/ISSUE-55-58-VERIFICATION-20260918.md +175 -0
- package/docs/internal/ISSUE10-FIX-EXECUTION-20260919.md +389 -0
- package/docs/internal/ISSUE10-PLAN-20260919.md +254 -0
- package/docs/internal/ISSUE10B-FORENSICS-20260919.md +468 -0
- package/docs/internal/ISSUE9-PURGE-AND-R1-PLAIN-20260919.md +150 -0
- package/docs/internal/ISSUE9-RESIDUAL-FORENSICS-20260919.md +114 -0
- package/docs/internal/KICKOFF-P0.md +254 -0
- package/docs/internal/LESSON-TO-CANDIDATE-STATUS-20260919.md +79 -0
- package/docs/internal/MASTER-PLAN-3.0.md +411 -0
- package/docs/internal/MEMORY-GOVERNANCE-20260917.md +309 -0
- package/docs/internal/MEMORY-MUTATION-AND-INDEX-DESIGN.md +85 -0
- package/docs/internal/MERGE-CONFLICT-SCAN-20260914.md +222 -0
- package/docs/internal/PENDING-FIXES-20260916.md +289 -0
- package/docs/internal/PRE-FRONTEND-CHECKLIST-20260919.md +705 -0
- package/docs/internal/PRE-FRONTEND-CHECKLIST-20260919.md.bak-s10 +649 -0
- package/docs/internal/PROCEDURAL-MEMORY-AND-APPROVAL-DESIGN-20260918.md +225 -0
- package/docs/internal/PROGRESS-20260917.md +93 -0
- package/docs/internal/PROMPT-GAP-AUDIT-20260920.md +128 -0
- package/docs/internal/R1-DEGRADE-AUDIT-20260918.md +163 -0
- package/docs/internal/R1-READABILITY-FORENSICS-20260919.md +127 -0
- package/docs/internal/R2-EVIDENCE-DEEP-AUDIT-20260918.md +140 -0
- package/docs/internal/R3-DEGRADE-LEDGER-DESIGN-20260918.md +138 -0
- package/docs/internal/R4-RECALL-QUOTA-PLAN-20260918.md +218 -0
- package/docs/internal/RAG-KARPATHY-PROGRAM.md +229 -0
- package/docs/internal/REPORT-P0-NIGHTLY.md +212 -0
- package/docs/internal/REPORT-P5-ACCEPTANCE.md +31 -0
- package/docs/internal/REPORT-WB-GRAPH-NIGHTLY.md +153 -0
- package/docs/internal/RESUME-20260918.md +171 -0
- package/docs/internal/RESUME-20260919.md +104 -0
- package/docs/internal/REVIEW-WB-GRAPH-SELF.md +81 -0
- package/docs/internal/RHINELAB-TO-DEEPSEEK-FEASIBILITY.md +198 -0
- package/docs/internal/ROADMAP-20260917-WEEK.md +439 -0
- package/docs/internal/ROADMAP.md +106 -0
- package/docs/internal/RUN-P0-NIGHTLY.md +227 -0
- package/docs/internal/S10-CONSTRUCTION-HANDOFF-20260917.md +185 -0
- package/docs/internal/S10-GAP-INVENTORY-20260917.md +239 -0
- package/docs/internal/S10-GAPS-PLAIN-20260917.md +125 -0
- package/docs/internal/SEMANTIC-ARCHITECTURE-SPEC.md +360 -0
- package/docs/internal/SESSION-FILE-REPAIR-PROTOCOL.md +90 -0
- package/docs/internal/T6-EXECUTION-20260920.md +130 -0
- package/docs/internal/TELEMETRY-EFFECT-REPORT-DESIGN-20260918.md +146 -0
- package/docs/internal/THESIS-GAP-ANALYSIS-20260918.md +89 -0
- package/docs/internal/THESIS-OUTLINE-20260918.md +147 -0
- package/docs/internal/THREE-LAYER-CONTRACT.md +219 -0
- package/docs/internal/TODO-BACKLOG.md +263 -142
- package/docs/internal/TODO-GRAPH.html +715 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260914-v2 +493 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260915-alsfix +710 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260915-p1 +710 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260915-p6a-rev +703 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260915-wshint +710 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260916-batch +715 -0
- package/docs/internal/UPSTREAM-ISSUE-PR-TRIAGE-20260919.md +297 -0
- package/docs/internal/UPSTREAM-ISSUES-3RD-AUDIT-20260920.md +104 -0
- package/docs/internal/WB-FORMAT-CONVENTION.md +112 -0
- package/docs/internal/WB-GRAPH-DECISIONS-20260914.md +71 -0
- package/docs/internal/reviews/CLAIM-VERIFICATION-20260914.md +56 -0
- package/docs/internal/reviews/PLAN-gpt6astra-round2-20260914.md +787 -0
- package/docs/internal/reviews/REVIEW-gpt6astra-20260914.md +112 -0
- package/docs/internal/reviews/ROUND3-REVIEW-INTEGRATION-20260914.md +230 -0
- package/docs/prompts/M8-3-enable-verify.md +49 -49
- package/docs/screenshots/promo/promo-0-banner-v3.png +0 -0
- package/lib/acceptance.js +71 -0
- package/lib/activation-host.js +153 -18
- package/lib/activation-inbox.js +25 -7
- package/lib/board-mode.js +30 -0
- package/lib/client.js +1758 -90
- package/lib/config-io.js +156 -0
- package/lib/context-bridge.js +5 -2
- package/lib/context-host.js +86 -15
- package/lib/degrade.js +385 -0
- package/lib/dsh-home.js +143 -0
- package/lib/engine-identity.js +149 -0
- package/lib/engine-switch.js +247 -0
- package/lib/episodic-store.js +63 -12
- package/lib/evidence-store.js +10 -3
- package/lib/fact-store.js +22 -3
- package/lib/fs-retry.js +46 -0
- package/lib/index-sync.js +13 -1
- package/lib/index.js +3446 -263
- package/lib/intent-clean-safe.js +258 -0
- package/lib/intent-clean.js +12 -16
- package/lib/l0-extract.js +478 -149
- package/lib/l0-index-sync.js +195 -0
- package/lib/l0-index.js +349 -239
- package/lib/ledger-criteria.js +142 -0
- package/lib/m4-corpus.js +8 -2
- package/lib/m7-index-sync-host.js +73 -5
- package/lib/m7-wire.js +3 -3
- package/lib/memory-anchor.js +56 -1
- package/lib/memory-envelope.js +257 -0
- package/lib/memory-hub.js +138 -13
- package/lib/memory-index.js +4 -2
- package/lib/memory-mutation.js +246 -0
- package/lib/memory-writer.js +204 -24
- package/lib/note-status-apply.js +118 -0
- package/lib/note-status.js +196 -0
- package/lib/procedure-observation.js +48 -0
- package/lib/procedure-store.js +118 -20
- package/lib/python-setup.js +1 -1
- package/lib/python-sidecar-client.js +29 -3
- package/lib/recall-fusion.js +83 -12
- package/lib/rerank-host.js +160 -0
- package/lib/rules-edit.js +159 -0
- package/lib/rules-layer.js +261 -0
- package/lib/semantic-decide.js +41 -8
- package/lib/semantic-js.js +66 -6
- package/lib/shadow-host.js +3 -5
- package/lib/shadow-retrieval.js +3 -3
- package/lib/skill-export-host.js +153 -0
- package/lib/skill-export.js +239 -0
- package/lib/state-commit.js +245 -0
- package/lib/storage-manage.js +6 -0
- package/lib/subagent-gc.js +4 -8
- package/lib/temporal-parse.js +191 -159
- package/lib/tier-layer-inject.js +650 -0
- package/lib/tier0-catalog.js +735 -0
- package/lib/water-window.js +263 -186
- package/lib/wb-contract.js +691 -0
- package/lib/wb-sidecar.js +890 -0
- package/lib/ws-overview-rank.js +2 -2
- package/package.json +1 -1
- package/python/m7_embedding_v1.py +5 -5
- package/python/worker_semantic_v1.py +17 -6
- package/python/worker_v1.py +38 -4
package/lib/config-io.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config-io.js —— 配置文件的**原子写**与**损坏隔离**(上游 issue #82 修复)。
|
|
3
|
+
*
|
|
4
|
+
* ## 背景(#82;已在 pre 线实跑核验:两条路径在优化后的版本里**仍然存在**)
|
|
5
|
+
*
|
|
6
|
+
* ① **写侧非原子**:配置落盘是裸 `writeFileSync`(同步路径)/ 裸 `writeFile`(异步路径)。
|
|
7
|
+
* 进程写到一半被杀、或磁盘写满,会留下**半截 JSON**;下次启动 `JSON.parse` 抛错,
|
|
8
|
+
* 于是直接回落出厂默认 ⇒ **用户此前的设置静默丢失**。
|
|
9
|
+
* 同类裸写还出现在 `embedding-config.json`(语义引擎配置)。
|
|
10
|
+
*
|
|
11
|
+
* ② **读侧静默**:两个 load 函数的 catch 一律 `return _mergeConfigPre(null)`(出厂默认),
|
|
12
|
+
* 错误只塞进 `this._readError` —— **用户看不到「我的配置坏了、已被重置」**。
|
|
13
|
+
* 本仓铁律:fail-soft 必须返回**可观察**信号;静默回落等于「数据丢了但没人知道」。
|
|
14
|
+
*
|
|
15
|
+
* ## 本模块只做两件事,且**不改变既有语义**
|
|
16
|
+
*
|
|
17
|
+
* 1. `writeTextAtomicPreSync` / `writeTextAtomicPre`:写 tmp → `renameSync` 覆盖目标。
|
|
18
|
+
* 同目录 rename 在同一文件系统上是**原子**的 ⇒ 读方永远只见「旧的完整版」或
|
|
19
|
+
* 「新的完整版」,**永远不会看到半截文件**。
|
|
20
|
+
* 2. `readJsonQuarantinePreSync`:解析失败时**先把坏文件改名留存**
|
|
21
|
+
* (`<name>.corrupt-<ts>`)再返回结构化失败。这样 ①用户数据没被覆盖
|
|
22
|
+
* ②诊断面能看见「曾经坏过、坏在哪」。
|
|
23
|
+
*
|
|
24
|
+
* ## 纪律
|
|
25
|
+
* - 零运行时依赖:只用 `node:fs` / `node:fs/promises` / `node:path`。
|
|
26
|
+
* - **fail-soft 但必须留痕**:任何一步失败都不抛,返回结构化结果让调用方处理。
|
|
27
|
+
* - CRLF、无 BOM。
|
|
28
|
+
*/
|
|
29
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs'
|
|
30
|
+
import { writeFile } from 'node:fs/promises'
|
|
31
|
+
import path from 'node:path'
|
|
32
|
+
|
|
33
|
+
/** 临时文件后缀。与既有 hub 落盘的 `.tmp` 同族,便于排查时一眼认出。 */
|
|
34
|
+
export const ATOMIC_TMP_SUFFIX_V1 = '.tmp'
|
|
35
|
+
|
|
36
|
+
/** 损坏留存的默认后缀前缀。 */
|
|
37
|
+
export const CORRUPT_QUARANTINE_PREFIX_V1 = '.corrupt-'
|
|
38
|
+
|
|
39
|
+
/** 时间戳串(用于损坏留存命名)。绝不影响主流程。 */
|
|
40
|
+
function tsPre(now) {
|
|
41
|
+
try {
|
|
42
|
+
const d = typeof now === 'number' ? new Date(now) : new Date()
|
|
43
|
+
const p = (n) => String(n).padStart(2, '0')
|
|
44
|
+
return String(d.getFullYear()) + p(d.getMonth() + 1) + p(d.getDate())
|
|
45
|
+
+ '-' + p(d.getHours()) + p(d.getMinutes()) + p(d.getSeconds())
|
|
46
|
+
} catch (_) {
|
|
47
|
+
return '0'
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 把目标文件挪到同目录的旁路名(**不删除** —— 留证据)。
|
|
53
|
+
* @returns {string} 旁路路径;失败返回空串(fail-soft,绝不抛)。
|
|
54
|
+
*/
|
|
55
|
+
export function quarantineFilePreSync(file, opts = {}) {
|
|
56
|
+
try {
|
|
57
|
+
const suffix = (opts && opts.suffix) || (CORRUPT_QUARANTINE_PREFIX_V1 + tsPre(opts && opts.now))
|
|
58
|
+
const dest = file + suffix
|
|
59
|
+
// 同一秒内坏两次 ⇒ 再加一段随机后缀,避免覆盖上一份证据
|
|
60
|
+
const finalPath = existsSync(dest) ? dest + '.' + Math.random().toString(36).slice(2, 6) : dest
|
|
61
|
+
renameSync(file, finalPath)
|
|
62
|
+
return finalPath
|
|
63
|
+
} catch (_) {
|
|
64
|
+
return ''
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* **同步原子写**:tmp → rename。
|
|
70
|
+
* @returns {{ok: boolean, path?: string, error?: string}} 结构化结果(fail-soft,不抛)。
|
|
71
|
+
*/
|
|
72
|
+
export function writeTextAtomicPreSync(file, text) {
|
|
73
|
+
const tmp = file + ATOMIC_TMP_SUFFIX_V1
|
|
74
|
+
try {
|
|
75
|
+
mkdirSync(path.dirname(file), { recursive: true })
|
|
76
|
+
writeFileSync(tmp, String(text), 'utf8')
|
|
77
|
+
renameSync(tmp, file)
|
|
78
|
+
return { ok: true, path: file }
|
|
79
|
+
} catch (e) {
|
|
80
|
+
// 失败时清掉临时文件,避免在目标目录留垃圾(清理失败也不抛)
|
|
81
|
+
try { rmSync(tmp, { force: true }) } catch (_) {}
|
|
82
|
+
return { ok: false, error: String((e && e.message) || e) }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* **异步原子写**:tmp → rename。语义与同步版一致。
|
|
88
|
+
*
|
|
89
|
+
* 只把**可能较大**的正文写入交给异步 IO;rename 本身极快,保持同步调用。
|
|
90
|
+
* 这样与既有 `persistConfigPre` 的异步口径对齐,不引入额外的 await 链。
|
|
91
|
+
*/
|
|
92
|
+
export async function writeTextAtomicPre(file, text) {
|
|
93
|
+
const tmp = file + ATOMIC_TMP_SUFFIX_V1
|
|
94
|
+
try {
|
|
95
|
+
mkdirSync(path.dirname(file), { recursive: true })
|
|
96
|
+
await writeFile(tmp, String(text), 'utf8')
|
|
97
|
+
renameSync(tmp, file)
|
|
98
|
+
return { ok: true, path: file }
|
|
99
|
+
} catch (e) {
|
|
100
|
+
try { rmSync(tmp, { force: true }) } catch (_) {}
|
|
101
|
+
return { ok: false, error: String((e && e.message) || e) }
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* **读 JSON + 损坏隔离**(同步)。
|
|
107
|
+
*
|
|
108
|
+
* 行为矩阵:
|
|
109
|
+
* - 文件不存在 ⇒ `{ ok:false, missing:true, reason:'ENOENT' }`(不算损坏,不留存)
|
|
110
|
+
* - 解析/读取失败 ⇒ **先留存**坏文件,再 `{ ok:false, corrupted:true, quarantined, reason }`
|
|
111
|
+
* - 正常 ⇒ `{ ok:true, value }`
|
|
112
|
+
*
|
|
113
|
+
* ⚠️ 与旧行为唯一的差别:**解析失败时多了一次 rename**(把原文件挪走)。
|
|
114
|
+
* 之所以必须挪走而非原地不动,是因为调用方随后会回落出厂默认并**覆盖写回** ——
|
|
115
|
+
* 不挪走的话坏文件当场被新配置盖掉,用户数据无从取证。
|
|
116
|
+
*/
|
|
117
|
+
export function readJsonQuarantinePreSync(file, opts = {}) {
|
|
118
|
+
let raw
|
|
119
|
+
try {
|
|
120
|
+
raw = readFileSync(file, 'utf8')
|
|
121
|
+
} catch (e) {
|
|
122
|
+
if (e && e.code === 'ENOENT') return { ok: false, missing: true, reason: 'ENOENT' }
|
|
123
|
+
return { ok: false, corrupted: false, reason: String((e && e.message) || e) }
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
return { ok: true, value: JSON.parse(raw) }
|
|
127
|
+
} catch (e) {
|
|
128
|
+
const reason = String((e && e.message) || e)
|
|
129
|
+
const quarantined = quarantineFilePreSync(file, opts)
|
|
130
|
+
return { ok: false, corrupted: true, quarantined, reason }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 纯函数判据:一段文本是否**看起来像被截断的 JSON**。
|
|
136
|
+
*
|
|
137
|
+
* 用途:调用方先自行 readFileSync 再 parse、拿不到本模块的返回对象时,
|
|
138
|
+
* 可用它给出更准确的诊断文案(「疑似写入被中断」而不是笼统的 parse 失败)。
|
|
139
|
+
*/
|
|
140
|
+
export function looksTruncatedJsonPre(text) {
|
|
141
|
+
const s = String(text == null ? '' : text).trim()
|
|
142
|
+
if (!s) return false
|
|
143
|
+
if (s[0] !== '{' && s[0] !== '[') return false
|
|
144
|
+
try { JSON.parse(s); return false } catch (_) { return true }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** 是否是一个**非空**普通文件(区分「损坏但非空」与「空文件」用)。 */
|
|
148
|
+
export function isNonEmptyFilePreSync(file) {
|
|
149
|
+
try {
|
|
150
|
+
const st = statSync(file)
|
|
151
|
+
return st.isFile() && st.size > 0
|
|
152
|
+
} catch (_) {
|
|
153
|
+
return false
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
package/lib/context-bridge.js
CHANGED
|
@@ -157,7 +157,7 @@ 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-
|
|
160
|
+
const parts = ['context-push-v1', String(sessionId || ''), Number(contextVersion) || 0, String(triggerDigest || ''), policyVersion]
|
|
161
161
|
return OBSERVATION_PREFIX + first32(sha256Str(JSON.stringify(parts)))
|
|
162
162
|
}
|
|
163
163
|
|
|
@@ -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-
|
|
391
|
+
return { observationId: id, accepted: true, workerEpoch: 'fake-epoch-v1', reason: 'ok' }
|
|
392
392
|
},
|
|
393
393
|
async closeSession() { stats.closedSessions++ },
|
|
394
394
|
async dispose() { frames.length = 0 },
|
|
@@ -519,6 +519,9 @@ export class BoundedIdSet {
|
|
|
519
519
|
constructor(capacity) { this.capacity = Math.max(1, Number(capacity) || 256); this._set = new Set() }
|
|
520
520
|
has(id) { return this._set.has(id) }
|
|
521
521
|
add(id) { this._set.add(id); if (this._set.size > this.capacity) { const first = this._set.values().next().value; this._set.delete(first) } }
|
|
522
|
+
/** issue#56(2026-09-19):显式撤销登记。用于「先登记后写盘」的幂等缓存——
|
|
523
|
+
* 写盘失败时必须释放 id,否则该 id 被永久判为重复 ⇒ 瞬时失败后重试恒被拒(证据静默丢失)。 */
|
|
524
|
+
delete(id) { return this._set.delete(id) }
|
|
522
525
|
get size() { return this._set.size }
|
|
523
526
|
clear() { this._set.clear() }
|
|
524
527
|
}
|
package/lib/context-host.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { appendFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
16
16
|
import { createHash } from 'node:crypto'
|
|
17
17
|
import path from 'node:path'
|
|
18
|
+
import { resolveDshHomeForEnginePre, resolveDshHomePre } from './dsh-home.js'
|
|
18
19
|
import {
|
|
19
20
|
buildContextPushEnvelopePre, buildAuthorizedMemoryRefFromRecord, createAccessEvidencePre,
|
|
20
21
|
createCiteEvidencesFromText, createCorrectionEvidencesFromText, computeReadCoverage,
|
|
@@ -37,11 +38,14 @@ const dropLoggedSkip = new Map()
|
|
|
37
38
|
/** M7.5 诊断:ctx-host drop/skip 直写 harness diagnose 日志(此前 try{diag()} 静默吞 ReferenceError)。 */
|
|
38
39
|
function diagCtx(msg) {
|
|
39
40
|
try {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
// ★#86-3:统一口径。原实现把 `USERPROFILE || HOME` **直接**当 DSH_HOME(不拼 .dsh)
|
|
42
|
+
// ⇒ 在 Windows 上得到 `C:\Users\X` 而非 `C:\Users\X\.dsh`,与其余 6 处**指向不同目录**。
|
|
43
|
+
// ★同时修掉一个**潜伏 bug**:原代码在 `DSH_HOME` 已设置时仍 `path.join(base, '.dsh', ...)`,
|
|
44
|
+
// 即落成 `$DSH_HOME/.dsh/dsh-auto-memory-diagnose.log`(**多拼一层 .dsh**);
|
|
45
|
+
// 未设置时才恰好正确。resolveDshHomePre() 返回的**已经是 dsh home 本身**,故不再拼 .dsh。
|
|
46
|
+
const base = resolveDshHomePre()
|
|
43
47
|
if (!base) return
|
|
44
|
-
appendFileSync(path.join(base, '
|
|
48
|
+
appendFileSync(path.join(base, 'dsh-auto-memory-diagnose.log'),
|
|
45
49
|
new Date().toISOString() + ' [ctx-host] ' + String(msg).slice(0, 300) + '\n', 'utf8')
|
|
46
50
|
} catch (e) {}
|
|
47
51
|
}
|
|
@@ -104,6 +108,12 @@ export function createContextHost(opts = {}) {
|
|
|
104
108
|
let lastFrameIdentity = null // 最小投影:observationId 全值(本身是哈希)+计数,无任何文本
|
|
105
109
|
let lastSegmentRuntimeKey = null
|
|
106
110
|
let lastSegmentSessionRef = null
|
|
111
|
+
// C5/I7:最近一次"索引未就绪"降级(可读、可观测;含原因与时间,不含任何语料文本)
|
|
112
|
+
// ★多工作区适配(2026-09-17):改为**按会话分片**。旧实现是单变量,任何会话的 index-not-ready
|
|
113
|
+
// 都写进去,而读取方(`index.js` 的 buildTierLayerInjection)只判 10 分钟时间窗、**不判会话**
|
|
114
|
+
// ⇒ B 会话的"未就绪"会把 A 会话污染十分钟,表现为**跨会话假降级**(A 明明正常却被标注降级)。
|
|
115
|
+
const indexDegradeBySession = new Map()
|
|
116
|
+
let lastIndexDegrade = null // 兼容投影:最近一次(供 debugView 等旧读取方)
|
|
107
117
|
let lastSinkMode = null
|
|
108
118
|
let bridge = null
|
|
109
119
|
let store = null
|
|
@@ -111,10 +121,8 @@ export function createContextHost(opts = {}) {
|
|
|
111
121
|
const registry = new CorpusRegistry({ sidecarDir: path.join(dshHome(), 'memory', 'index', 'files') })
|
|
112
122
|
|
|
113
123
|
function dshHome() {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const base = engine.__homedirFn ? engine.__homedirFn() : (process.env.USERPROFILE || process.env.HOME || '')
|
|
117
|
-
return base ? path.join(base, '.dsh') : '.'
|
|
124
|
+
// ★#86-3:统一口径(含 __homedirFn 注入点保留)
|
|
125
|
+
return resolveDshHomeForEnginePre(engine)
|
|
118
126
|
}
|
|
119
127
|
function effectiveEnabled() {
|
|
120
128
|
return engine.config.associativeMemoryEnabled === true && engine.config.contextBridgeEnabled === true
|
|
@@ -347,14 +355,33 @@ export function createContextHost(opts = {}) {
|
|
|
347
355
|
? Promise.resolve().then(() => engine._jsSemanticRank(corpusSnap, buildObserveWindowText(runtime, seg))).catch(() => null)
|
|
348
356
|
: Promise.resolve(null)
|
|
349
357
|
void Promise.all([readyPromise, c2RankPromise]).then(([readyRes, rank]) => {
|
|
358
|
+
// C5/I7(2026-09-14):索引未就绪**不再静默丢弃注入**。旧行为是 pushDrop('index-not-ready:…')
|
|
359
|
+
// 后直接 return —— 语义索引一卡死,注入面就整段消失且无人知(07:43–08:35 的违规现场)。
|
|
360
|
+
// 新行为 = 显式降级 + fail-open:记一条可读降级(reason 进注入层 `[降级]` 行),本轮按**词法命中**
|
|
361
|
+
// 继续组装并推送 envelope(Tier-0 目录层照常常驻),稠密臂本轮不参与融合(不装懂)。
|
|
362
|
+
let indexNotReady = ''
|
|
350
363
|
if (!(readyRes && readyRes.ready)) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
364
|
+
indexNotReady = String((readyRes && readyRes.reason) || 'unknown')
|
|
365
|
+
stats.indexDegraded = (stats.indexDegraded || 0) + 1
|
|
366
|
+
const sidDeg = String(runtime.sessionId || '')
|
|
367
|
+
const degradeRec = { reason: indexNotReady, at: Date.now(), sessionId: sidDeg }
|
|
368
|
+
// 多工作区适配(2026-09-17):按会话分片写入,读取方按会话取 ⇒ 不再跨会话污染。
|
|
369
|
+
// ★2026-09-20 移植(issue #94⑤ / PR #100):先 delete 再 set 刷新插入序。
|
|
370
|
+
indexDegradeBySession.delete(sidDeg)
|
|
371
|
+
indexDegradeBySession.set(sidDeg, degradeRec)
|
|
372
|
+
if (indexDegradeBySession.size > 32) {
|
|
373
|
+
const oldest = indexDegradeBySession.keys().next().value
|
|
374
|
+
indexDegradeBySession.delete(oldest)
|
|
375
|
+
}
|
|
376
|
+
lastIndexDegrade = degradeRec // 兼容投影(最近一次)
|
|
377
|
+
try { engine._lastIndexDegrade = degradeRec } catch (_) {}
|
|
378
|
+
try { engine._indexDegradeBySession = indexDegradeBySession } catch (_) {}
|
|
379
|
+
pushDrop('index-not-ready:degraded:' + indexNotReady, runtime.contextVersion, runtime.key)
|
|
380
|
+
diagCtx('ctx-host degrade: index-not-ready(' + indexNotReady + ') → 词法命中 + 常驻目录层继续注入')
|
|
354
381
|
}
|
|
355
382
|
if (st.disposed || runtime.disposed) return
|
|
356
383
|
let keptList = ls.kept
|
|
357
|
-
if (rank && rank.scores && rank.scores.size) {
|
|
384
|
+
if (!indexNotReady && rank && rank.scores && rank.scores.size) {
|
|
358
385
|
// D6 融合候选池=词法 kept ∪ 稠密 top-K(语料中找回记录对象;语义强命中但词法零分者由此入选)
|
|
359
386
|
const poolMap = new Map()
|
|
360
387
|
for (const k of ls.kept) if (k && k.memoryId) poolMap.set(k.memoryId, k)
|
|
@@ -419,7 +446,10 @@ export function createContextHost(opts = {}) {
|
|
|
419
446
|
// 2026-08-27 判定观测:按段类型计数(真实数据验证 CoT 触发)
|
|
420
447
|
stats.jsDecideRuns++
|
|
421
448
|
stats.jsDecideByKind[seg.kind] = (stats.jsDecideByKind[seg.kind] || 0) + 1
|
|
422
|
-
|
|
449
|
+
// ★2026-09-20 移植(issue #94④ / PR #100):`|| 5` 把用户显式配置的 0(不冷却)反转成 5。
|
|
450
|
+
// 仅缺失/非有限数才回落默认值。
|
|
451
|
+
const cdRaw = Number(engine.config.jsDecideCooldownRounds)
|
|
452
|
+
const cd = Number.isFinite(cdRaw) && cdRaw >= 0 ? cdRaw : 5
|
|
423
453
|
const stNow = stateFor(runtime)
|
|
424
454
|
if (cd > 0 && stNow._jsDecideNextAt && Date.now() < stNow._jsDecideNextAt) return
|
|
425
455
|
var jsDecideQueryText = buildObserveWindowText(runtime, seg)
|
|
@@ -463,14 +493,21 @@ export function createContextHost(opts = {}) {
|
|
|
463
493
|
const session = built.frame.session || {}
|
|
464
494
|
const cursor = built.frame.cursor || {}
|
|
465
495
|
const obs = built.frame.observationId || ''
|
|
496
|
+
// 2026-09-14 P1-⑮:注入侧 reason 串补 0-1 分值(intent/dense/margin)。
|
|
497
|
+
// 数值段置于 reasonCodes **之前**,160 字符截断只砍代码列表、不砍分值。
|
|
498
|
+
const num2 = (x) => (Number.isFinite(Number(x)) ? Number(x).toFixed(2) : '0.00')
|
|
499
|
+
const jsReason = ('js-decide lane=' + dec.lane + ' ' + (dec.decision || 'emit') +
|
|
500
|
+
' intent=' + num2(dec.features && dec.features.intentProb) +
|
|
501
|
+
' dense=' + num2(dec._denseTop) + ' margin=' + num2(dec._margin) +
|
|
502
|
+
' ' + (dec.reasonCodes || []).join(',')).slice(0, 160)
|
|
466
503
|
const act = {
|
|
467
504
|
schemaVersion: 1, namespace: 'dsh-auto-memory', kind: 'activation_request',
|
|
468
505
|
activationId: 'act_' + (obs.startsWith('obs_') ? obs.slice(8, 40) : 'jsdecide'),
|
|
469
|
-
observationId: obs, workerEpoch: 'js-decide-
|
|
506
|
+
observationId: obs, workerEpoch: 'js-decide-v1',
|
|
470
507
|
sessionId: session.sessionId || '', agentId: session.agentId || '',
|
|
471
508
|
workspaceKey: session.workspaceKey || '', scope: session.scope || 'Workspace',
|
|
472
509
|
contextVersion: cursor.contextVersion || 0, memoryIndexVersion: built.frame.index ? built.frame.index.memoryIndexVersion : '',
|
|
473
|
-
threshold: { policyVersion: dec.features ? 'activation_policy_v2' : 'activation_policy_v2', score: (dec.features && dec.features.intentProb) || 0, threshold: 0.45, reason:
|
|
510
|
+
threshold: { policyVersion: dec.features ? 'activation_policy_v2' : 'activation_policy_v2', score: (dec.features && dec.features.intentProb) || 0, threshold: 0.45, reason: jsReason },
|
|
474
511
|
level: 'excerpt',
|
|
475
512
|
candidates: (function () {
|
|
476
513
|
// 2026-08-27 优化③:候选方案档位(balanced=3×40 / dense=6×20 / custom=自定义)
|
|
@@ -532,7 +569,39 @@ export function createContextHost(opts = {}) {
|
|
|
532
569
|
}
|
|
533
570
|
}
|
|
534
571
|
} catch (_) {}
|
|
572
|
+
// C5 闸门:记下本轮 query(激活请求本身不带 query 文本),供注入层判"是问为什么还是只要索引"
|
|
573
|
+
try {
|
|
574
|
+
const qSid = String((act && act.sessionId) || '')
|
|
575
|
+
const qRec = {
|
|
576
|
+
at: Date.now(),
|
|
577
|
+
sessionId: qSid,
|
|
578
|
+
text: String(jsDecideQueryText || '').slice(0, 1200),
|
|
579
|
+
}
|
|
580
|
+
// 多工作区适配(2026-09-17):按会话分片。旧实现单槽无条件覆盖 ⇒ A 的 query 被 B 顶掉,
|
|
581
|
+
// 读取侧因会话不匹配而退回 triggerText(语义降级)。分片后各会话各记一份。
|
|
582
|
+
if (!engine._lastTierQueryBySession || typeof engine._lastTierQueryBySession.set !== 'function') {
|
|
583
|
+
engine._lastTierQueryBySession = new Map()
|
|
584
|
+
}
|
|
585
|
+
engine._lastTierQueryBySession.set(qSid, qRec)
|
|
586
|
+
if (engine._lastTierQueryBySession.size > 32) {
|
|
587
|
+
const oldest = engine._lastTierQueryBySession.keys().next().value
|
|
588
|
+
engine._lastTierQueryBySession.delete(oldest)
|
|
589
|
+
}
|
|
590
|
+
engine._lastTierQuery = qRec // 兼容投影(最近一次)
|
|
591
|
+
} catch (_) {}
|
|
535
592
|
engine._activationHost.offerExternalActivation(act)
|
|
593
|
+
// 命中投影补上 query(记录发生在 offer 内部;此处补字段,顺序确定)
|
|
594
|
+
// 多工作区适配(2026-09-17):分片条目与兼容投影**都要补**,否则按会话取到的投影缺 question。
|
|
595
|
+
try {
|
|
596
|
+
const actSid = String((act && act.sessionId) || '')
|
|
597
|
+
const qText = String(jsDecideQueryText || '').slice(0, 1200)
|
|
598
|
+
const rec = engine._tierGateHitsBySession && typeof engine._tierGateHitsBySession.get === 'function'
|
|
599
|
+
? engine._tierGateHitsBySession.get(actSid) : null
|
|
600
|
+
if (rec && String(rec.sessionId) === actSid) rec.question = qText
|
|
601
|
+
if (engine._tierGateHits && String(engine._tierGateHits.sessionId) === actSid) {
|
|
602
|
+
engine._tierGateHits.question = qText
|
|
603
|
+
}
|
|
604
|
+
} catch (_) {}
|
|
536
605
|
}).catch(() => {})
|
|
537
606
|
} catch (_) {}
|
|
538
607
|
}
|
|
@@ -723,6 +792,8 @@ export function createContextHost(opts = {}) {
|
|
|
723
792
|
durableEventsOnDisk: evidenceRootExists() ? storeFor().loadEvents().events.length : 0,
|
|
724
793
|
stats: { ...stats, bridge: { ...b.stats } },
|
|
725
794
|
storeStats: store ? { ...store.stats } : null,
|
|
795
|
+
// C5/I7:索引未就绪降级(latest) —— 供面板/注入层解释"为什么这轮只有词法命中"
|
|
796
|
+
indexDegrade: lastIndexDegrade,
|
|
726
797
|
recentDrops: volatileDrops.slice(-4),
|
|
727
798
|
}
|
|
728
799
|
}
|