@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/activation-host.js
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import { createActivationInboxPre, ActivationInboxRegistry, REFERENCE_TAIL_COOLDOWN_STEPS_V1 } from './activation-inbox-state.js'
|
|
20
20
|
import { createAccessEvidencePre } from './context-bridge.js'
|
|
21
21
|
import path from 'node:path'
|
|
22
|
+
import { resolveDshHomeForEnginePre } from './dsh-home.js'
|
|
22
23
|
import { buildSourceCatalog, loadCorpusSnapshot, CorpusRegistry, canonicalize } from './m4-corpus.js'
|
|
23
24
|
|
|
24
25
|
/** §11 capability 快照版本。 */
|
|
@@ -43,9 +44,18 @@ export function createActivationHost(opts = {}) {
|
|
|
43
44
|
const registry = new ActivationInboxRegistry()
|
|
44
45
|
const corpusRegistry = new CorpusRegistry({ sidecarDir: path.join(dshHome(), 'memory', 'index', 'files') })
|
|
45
46
|
const runtimeState = new Map() // runtime.key → {step, claimed:{packet, inbox}|null, identity:{sessionId,workspaceKey}|null}
|
|
46
|
-
|
|
47
|
+
// ★多工作区适配(2026-09-17):以下两处此前是**单槽**(一个变量只装"最后写入者")。
|
|
48
|
+
// 症状:会话 A 投递 → 槽里是 A;会话 B(或用户点开另一个工作区触发 B)投递 → 槽里变 B;
|
|
49
|
+
// A 下一轮来取 ⇒ 身份不匹配 ⇒ 降级"投影来自其它会话/工作区" ⇒ A **永远不下探 Tier-1**。
|
|
50
|
+
// 现改为按身份分片:投影按 sessionId、miv 按 wsRef。**只增 key 维度,不改任何判定口径**。
|
|
51
|
+
const mivCacheByWs = new Map() // wsRef -> miv(每个工作区各记一份,不再互相踢)
|
|
47
52
|
const pathsByKey = new Map()
|
|
53
|
+
// ★#84(2026-09-20):`≤16 最小投影` —— 原实现在超出上限时**静默 shift**,
|
|
54
|
+
// 于是「诊断面看不到最近事件」没有任何痕迹;排查时会误判成「压根没产生事件」。
|
|
55
|
+
// 现补 `volatileDropped` 计数并具名化上限。
|
|
48
56
|
const volatileEvents = [] // ≤16 最小投影
|
|
57
|
+
let volatileDropped = 0
|
|
58
|
+
const VOLATILE_MAX_V1 = 16
|
|
49
59
|
const stats = { injected: 0, injectedAccepted: 0, injectedRejected: 0, claims: 0, claimFails: 0, rendered: 0, delivered: 0, seenCreated: 0, seenSkippedNoProv: 0, errors: 0 }
|
|
50
60
|
|
|
51
61
|
function effectiveEnabled() {
|
|
@@ -65,14 +75,17 @@ export function createActivationHost(opts = {}) {
|
|
|
65
75
|
return 'invalid'
|
|
66
76
|
}
|
|
67
77
|
function dshHome() {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return base ? path.join(base, '.dsh') : '.'
|
|
78
|
+
// ★#86-3:统一口径。原实现全失败时返回 '.'(**与其余站点不同**);
|
|
79
|
+
// __homedirFn 注入点由 resolveDshHomeForEnginePre 保留,测试兼容。
|
|
80
|
+
return resolveDshHomeForEnginePre(engine)
|
|
72
81
|
}
|
|
73
82
|
function pushEvent(entry) {
|
|
74
83
|
volatileEvents.push({ at: Date.now(), ...entry })
|
|
75
|
-
if (volatileEvents.length >
|
|
84
|
+
if (volatileEvents.length > VOLATILE_MAX_V1) {
|
|
85
|
+
volatileEvents.shift()
|
|
86
|
+
// ★#84:丢弃必须留痕(否则「看不到事件」无法区分「没产生」与「被挤掉」)
|
|
87
|
+
volatileDropped++
|
|
88
|
+
}
|
|
76
89
|
}
|
|
77
90
|
function capturePaths(runtimeKey, p) {
|
|
78
91
|
pathsByKey.set(String(runtimeKey || ''), {
|
|
@@ -84,10 +97,13 @@ export function createActivationHost(opts = {}) {
|
|
|
84
97
|
}
|
|
85
98
|
|
|
86
99
|
|
|
87
|
-
/** 当前 workspace 的 memoryIndexVersion(自有 CorpusRegistry 懒加载;失败 null 并清缓存)。
|
|
100
|
+
/** 当前 workspace 的 memoryIndexVersion(自有 CorpusRegistry 懒加载;失败 null 并清缓存)。
|
|
101
|
+
* ★多工作区适配(2026-09-17):缓存**按 wsRef 分片**。旧实现是单变量 `mivCache`,只认最后一个
|
|
102
|
+
* wsRef —— 用户点开另一个工作区、或另一会话投递时就会把前一个的缓存踢掉,于是两个工作区
|
|
103
|
+
* 来回切换时每次 `currentMiv` 都必然 miss、每次都重算语料快照,并进一步放大 P0-4e
|
|
104
|
+
* ("持续变更 ⇒ 索引永远不就绪")。分片后各工作区各记一份,互不干扰。 */
|
|
88
105
|
function currentMiv(workspaceKey) {
|
|
89
106
|
const ws = canonicalize(workspaceKey)
|
|
90
|
-
const p = null
|
|
91
107
|
for (const [, v] of pathsByKey) {
|
|
92
108
|
if (v.workspaceKey !== ws) continue
|
|
93
109
|
try {
|
|
@@ -99,16 +115,26 @@ export function createActivationHost(opts = {}) {
|
|
|
99
115
|
})
|
|
100
116
|
const res = corpusRegistry.get(catalog)
|
|
101
117
|
if (res && res.ok) {
|
|
102
|
-
|
|
103
|
-
return
|
|
118
|
+
mivCacheByWs.set(ws, res.snapshot.memoryIndexVersion)
|
|
119
|
+
return res.snapshot.memoryIndexVersion
|
|
104
120
|
}
|
|
105
121
|
} catch (_) {}
|
|
106
122
|
break
|
|
107
123
|
}
|
|
108
|
-
|
|
109
|
-
|
|
124
|
+
return mivCacheByWs.get(ws) || null
|
|
125
|
+
}
|
|
126
|
+
function setMiv(workspaceKey, miv) { mivCacheByWs.set(canonicalize(workspaceKey), miv) }
|
|
127
|
+
/** 取某工作区最近一次已知 miv(无则 null)。供上层投影/诊断读取,不再假设"全局只有一个"。 */
|
|
128
|
+
function mivFor(workspaceKey) {
|
|
129
|
+
const ws = canonicalize(workspaceKey)
|
|
130
|
+
return mivCacheByWs.get(ws) || null
|
|
131
|
+
}
|
|
132
|
+
/** 最近写入的一份 miv(兼容投影:给既有诊断面板/老调用方用;多工作区语义请用 mivFor)。 */
|
|
133
|
+
function latestMiv() {
|
|
134
|
+
let last = null
|
|
135
|
+
for (const v of mivCacheByWs.values()) last = v
|
|
136
|
+
return last
|
|
110
137
|
}
|
|
111
|
-
function setMiv(workspaceKey, miv) { mivCache = { wsRef: canonicalize(workspaceKey), miv } }
|
|
112
138
|
|
|
113
139
|
function identityFor(runtime) {
|
|
114
140
|
const sessionId = runtime.sessionId || ''
|
|
@@ -137,11 +163,73 @@ export function createActivationHost(opts = {}) {
|
|
|
137
163
|
return finishInject(box, req)
|
|
138
164
|
} catch (e) { stats.errors++; return { ok: false, reason: 'internal-error' } }
|
|
139
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* C5 闸门输入(2026-09-14 三层契约 §5):把本轮语义命中压成**轻量投影**挂到 engine,
|
|
168
|
+
* 供注入层决定"要不要下探 Tier-1/Tier-2"(未命中就只给 Tier-0 目录层 = 省 token 的关键)。
|
|
169
|
+
* 只存 memoryId/score/摘要/layer/status —— 不复制 packet,不做任何 IO,零 LLM(S9)。
|
|
170
|
+
*
|
|
171
|
+
* T0-2(2026-09-14 P0):**必须同时存版本四元组** `contextVersion / miv / observationId / requestKey`。
|
|
172
|
+
* 旧实现在注入侧只用 `Date.now() - gh.at < 30*60000` 判复用 —— **只查时间不查版本**,
|
|
173
|
+
* 于是「A 快照产生的候选到 B 快照才准备输出」时 A 的正文会进 B 的注入(违反契约 I6「三层同一快照」)。
|
|
174
|
+
* 这里补齐投影字段,判定与 fail-closed 语义在 `lib/tier-layer-inject.js:selectReusableTierHitsPre`。
|
|
175
|
+
* `workspaceKey` 同时补齐:身份门需要它证明投影归属(旧实现压根没存工作区)。
|
|
176
|
+
*/
|
|
177
|
+
function recordTierGateHits(req) {
|
|
178
|
+
try {
|
|
179
|
+
if (!engine || typeof engine !== 'object') return
|
|
180
|
+
const sid = String(req.sessionId || '')
|
|
181
|
+
const lq = engine._lastTierQuery || null
|
|
182
|
+
const q = (lq && Date.now() - (Number(lq.at) || 0) < 120000 && String(lq.sessionId || '') === sid)
|
|
183
|
+
? String(lq.text || '') : String(req.triggerText || '')
|
|
184
|
+
const projection = {
|
|
185
|
+
sessionId: sid,
|
|
186
|
+
agentId: String(req.agentId || ''),
|
|
187
|
+
workspaceKey: String(req.workspaceKey || ''),
|
|
188
|
+
at: Date.now(),
|
|
189
|
+
question: q.slice(0, 1200),
|
|
190
|
+
// —— T0-2 版本四元组(复用前必须逐项比对;缺任一项下游 fail closed 不复用)——
|
|
191
|
+
contextVersion: Number.isInteger(req.contextVersion) ? req.contextVersion : undefined,
|
|
192
|
+
miv: String(req.memoryIndexVersion || '') || undefined,
|
|
193
|
+
observationId: String(req.observationId || '') || undefined,
|
|
194
|
+
requestKey: String(req.requestKey || '') || undefined,
|
|
195
|
+
// 状态过滤(I5/C8)在注入侧做(见 tier-layer-inject-pre 的 filterCurrentHitsPre),
|
|
196
|
+
// 这里**照原样保留 status**:投影是审计面,过滤是准入面,两者不混。
|
|
197
|
+
hits: (Array.isArray(req.candidates) ? req.candidates : []).slice(0, 8).map((c) => ({
|
|
198
|
+
memoryId: String((c && c.memoryId) || ''),
|
|
199
|
+
score: Number(c && c.score),
|
|
200
|
+
excerpt: String((c && c.excerpt) || ''),
|
|
201
|
+
layer: (c && c.layer) || 'log',
|
|
202
|
+
status: (c && c.status) || 'current',
|
|
203
|
+
sourceRef: String((c && c.sourceRef) || ''),
|
|
204
|
+
scope: String((c && c.scope) || ''),
|
|
205
|
+
})),
|
|
206
|
+
}
|
|
207
|
+
// ★多工作区适配(2026-09-17):投影**按会话分片**存放,读取侧按当前会话取。
|
|
208
|
+
// 旧实现是单槽 `engine._tierGateHits = {...}`:会话 A 投递后被 B 覆盖,A 下一轮取到的
|
|
209
|
+
// 是 B 的投影 ⇒ 身份门判 session-mismatch ⇒ A **永远不下探 Tier-1**("谁也没法注入")。
|
|
210
|
+
// 分片后 A/B 各存各的,互不覆盖;仍然**逐项比对四元组**,判定口径一字未改。
|
|
211
|
+
if (!engine._tierGateHitsBySession || typeof engine._tierGateHitsBySession.set !== 'function') {
|
|
212
|
+
engine._tierGateHitsBySession = new Map()
|
|
213
|
+
}
|
|
214
|
+
// ★2026-09-20 移植(issue #94⑤ / PR #100):Map.set 对既有键**不刷新插入序** ⇒
|
|
215
|
+
// keys().next() 淘汰的是「最老创建」而非「最久未用」,最活跃会话反被逐出。
|
|
216
|
+
engine._tierGateHitsBySession.delete(sid)
|
|
217
|
+
engine._tierGateHitsBySession.set(sid, projection)
|
|
218
|
+
// 兼容投影:保留旧字段指向"最近一次投递",供旧诊断面板/测试继续可读(只增不改语义)。
|
|
219
|
+
engine._tierGateHits = projection
|
|
220
|
+
// 有界:最多保留 32 个会话的投影,避免长时间运行后 Map 无限增长。
|
|
221
|
+
if (engine._tierGateHitsBySession.size > 32) {
|
|
222
|
+
const oldest = engine._tierGateHitsBySession.keys().next().value
|
|
223
|
+
engine._tierGateHitsBySession.delete(oldest)
|
|
224
|
+
}
|
|
225
|
+
} catch (_) {}
|
|
226
|
+
}
|
|
140
227
|
function finishInject(box, req) {
|
|
141
228
|
box.setCursor({ contextVersion: req.contextVersion, memoryIndexVersion: req.memoryIndexVersion })
|
|
142
229
|
const r = box.offerActivation(req, { nowStep: stepFor(req.sessionId, req.workspaceKey), currentMemoryIndexVersion: req.memoryIndexVersion })
|
|
143
230
|
if (r.ok) {
|
|
144
231
|
stats.injectedAccepted++
|
|
232
|
+
recordTierGateHits(req)
|
|
145
233
|
// fake 来源「注入即泵」:立刻以请求自声明版本过四重门 claim,并把 claimed 挂到
|
|
146
234
|
// 目标 runtime 态——下一次该 session 的自然 compose 会渲染尾注并 markDelivered(+seen)。
|
|
147
235
|
pumpClaimed(box, req)
|
|
@@ -158,7 +246,10 @@ export function createActivationHost(opts = {}) {
|
|
|
158
246
|
*/
|
|
159
247
|
function pumpClaimed(box, req) {
|
|
160
248
|
const c2 = box.claim({
|
|
161
|
-
|
|
249
|
+
// ★ issue #75 CC-11 修复:用**只读**步号,不再自增。
|
|
250
|
+
// pump 总是紧跟在同键 `stepFor`(offer 时)之后同步调用 ⇒ 读到的就是刚烘焙 TTL 的那个步。
|
|
251
|
+
// 旧实现此处再 `stepFor` 一次 ⇒ 同一步内凭空消耗 2 步 TTL(见 currentStepOf 注释)。
|
|
252
|
+
nowStep: currentStepOf(req.sessionId, req.workspaceKey),
|
|
162
253
|
currentContextVersion: req.contextVersion,
|
|
163
254
|
currentMemoryIndexVersion: req.memoryIndexVersion,
|
|
164
255
|
})
|
|
@@ -215,6 +306,7 @@ export function createActivationHost(opts = {}) {
|
|
|
215
306
|
// 在回合中段必死(cv 每段自增+TTL3 步),Python emit 的时序机制完全相同,同一证据
|
|
216
307
|
// 同一修法。泵是 M6 投递侧机制,不耦合任何语义档(JS/Python 各自独立可用)。
|
|
217
308
|
pumpClaimed(box, req)
|
|
309
|
+
recordTierGateHits(req) // C5 闸门输入(与 fake 档同一投影)
|
|
218
310
|
} else stats.injectedRejected++
|
|
219
311
|
return r
|
|
220
312
|
} catch (e) { stats.errors++; return { ok: false, reason: 'internal-error' } }
|
|
@@ -271,12 +363,39 @@ export function createActivationHost(opts = {}) {
|
|
|
271
363
|
return { ok: true, request }
|
|
272
364
|
}
|
|
273
365
|
const stepsByRuntime = new Map()
|
|
366
|
+
/**
|
|
367
|
+
* 步进计数器的**唯一键格式**(issue#58 修复,2026-09-19)。
|
|
368
|
+
* 旧实现:写入侧用 `sessionId + '|ws:' + workspaceKey`,而删除侧 `disposeRuntime` 用
|
|
369
|
+
* `String(runtimeKey)`(= `session:<sid>`)⇒ **两个键空间永不相交** ⇒ 删除是空操作,
|
|
370
|
+
* `stepsByRuntime` 只增不减(「会话×工作区」一条,进程内永不回收)。
|
|
371
|
+
* 现在格式收敛到这一处,写入与删除共用,杜绝再次漂移。
|
|
372
|
+
*/
|
|
373
|
+
function stepKeyOf(sessionId, workspaceKey) {
|
|
374
|
+
return String(sessionId || '') + '|ws:' + String(workspaceKey || '')
|
|
375
|
+
}
|
|
274
376
|
function stepFor(sessionId, workspaceKey) {
|
|
275
|
-
const key = sessionId
|
|
377
|
+
const key = stepKeyOf(sessionId, workspaceKey)
|
|
276
378
|
const n = (stepsByRuntime.get(key) || 0) + 1
|
|
277
379
|
stepsByRuntime.set(key, n)
|
|
278
380
|
return n
|
|
279
381
|
}
|
|
382
|
+
/**
|
|
383
|
+
* ★ issue #75 CC-11 修复(2026-09-19):**只读**当前步(不自增)。
|
|
384
|
+
*
|
|
385
|
+
* 动机:`stepFor` 是自增器(+1 写回)。而「注入即泵」路径是
|
|
386
|
+
* `finishInject` 先 `offerActivation({nowStep: stepFor(...)})` 烘焙 TTL 起点,
|
|
387
|
+
* 紧接着**同一同步块内** `pumpClaimed` 又调一次 `stepFor` 去 claim
|
|
388
|
+
* ⇒ **offer 与 pump 各消耗 1 步而零时间流逝** ⇒ `expiresAtStep` 按第一次烘焙、
|
|
389
|
+
* 判定却用第二次 ⇒ TTL 窗口凭空少 1 步(实测 ttl=3 的自然投递窗由 2 次机会缩为 1;
|
|
390
|
+
* ttl=2 的 rollback 场景整包必死)。
|
|
391
|
+
*
|
|
392
|
+
* 语义上「同步 offer + pump」是**同一步内**的投递动作,不该消耗 2 步 TTL。
|
|
393
|
+
* 因此给 pump 侧提供只读版本:它总是紧跟在同键的 `stepFor` 之后调用,
|
|
394
|
+
* 读到的必然是刚自增过的那个值(≥1),不会读到 0。
|
|
395
|
+
*/
|
|
396
|
+
function currentStepOf(sessionId, workspaceKey) {
|
|
397
|
+
return stepsByRuntime.get(stepKeyOf(sessionId, workspaceKey)) || 0
|
|
398
|
+
}
|
|
280
399
|
|
|
281
400
|
/**
|
|
282
401
|
* agent/pre-step(§8):先校验当前 cursor/index/TTL 再 claim;claimed packet 存 runtime 态,
|
|
@@ -294,6 +413,8 @@ export function createActivationHost(opts = {}) {
|
|
|
294
413
|
const miv = currentMiv(identity.workspaceKey)
|
|
295
414
|
box.setCursor({ contextVersion: runtime.contextVersion, memoryIndexVersion: miv || undefined })
|
|
296
415
|
const nowStep = stepFor(identity.sessionId, identity.workspaceKey)
|
|
416
|
+
// issue#58:把步进键登记进 runtime 态,供 disposeRuntime 精确回收(否则计数器只增不减)。
|
|
417
|
+
const stepKeyHere = stepKeyOf(identity.sessionId, identity.workspaceKey)
|
|
297
418
|
const c = box.claim({
|
|
298
419
|
nowStep,
|
|
299
420
|
currentContextVersion: runtime.contextVersion,
|
|
@@ -302,6 +423,7 @@ export function createActivationHost(opts = {}) {
|
|
|
302
423
|
let st = runtimeState.get(runtime.key)
|
|
303
424
|
if (!st) { st = { step: 0, claimed: null }; runtimeState.set(runtime.key, st) }
|
|
304
425
|
st.step = nowStep
|
|
426
|
+
st.stepKey = stepKeyHere
|
|
305
427
|
if (c.ok) { st.claimed = { packet: c.packet, inbox: box }; stats.claims++ }
|
|
306
428
|
else {
|
|
307
429
|
// 2026-08-28 浏览器实测修复:claim 失败(none-pending 等)不得清掉已存在的 claimed——
|
|
@@ -386,7 +508,11 @@ export function createActivationHost(opts = {}) {
|
|
|
386
508
|
cooldownSteps: REFERENCE_TAIL_COOLDOWN_STEPS_V1,
|
|
387
509
|
capability: cap,
|
|
388
510
|
sourceMode: sourceMode(),
|
|
389
|
-
|
|
511
|
+
// 多工作区适配(2026-09-17):不再返回"最后一个写入者"的 miv,而是按工作区分片的映射。
|
|
512
|
+
// 同时保留旧字段 `memoryIndexVersion` 作为**兼容投影**(取最近写入的那一份),供既有
|
|
513
|
+
// 诊断面板/测试继续工作 —— 只增字段不改语义,避免开关耦合。
|
|
514
|
+
memoryIndexVersion: latestMiv() || null,
|
|
515
|
+
mivByWorkspace: Object.fromEntries(mivCacheByWs),
|
|
390
516
|
inboxCount: registry.size,
|
|
391
517
|
stats: { ...stats },
|
|
392
518
|
recentEvents: volatileEvents.slice(-4),
|
|
@@ -424,9 +550,18 @@ export function createActivationHost(opts = {}) {
|
|
|
424
550
|
} catch (e) { stats.errors++; return { ok: false, reason: 'internal-error' } }
|
|
425
551
|
}
|
|
426
552
|
|
|
553
|
+
/**
|
|
554
|
+
* 单 runtime 清理。issue#58 修复(2026-09-19):**必须清掉步进计数器**。
|
|
555
|
+
* 旧实现 `stepsByRuntime.delete(String(runtimeKey))` 是坏键(见 `stepKeyOf` 注释)⇒ 空操作。
|
|
556
|
+
* 现在优先用创建时登记的 `stepKey`;同时保留 runtimeKey 形态的删除,
|
|
557
|
+
* 兼容「调用方直接传 step-key」的情况(两条都删,代价为零)。
|
|
558
|
+
*/
|
|
427
559
|
function disposeRuntime(runtimeKey) {
|
|
428
|
-
|
|
429
|
-
|
|
560
|
+
const k = String(runtimeKey)
|
|
561
|
+
const st = runtimeState.get(k)
|
|
562
|
+
runtimeState.delete(k)
|
|
563
|
+
if (st && st.stepKey) stepsByRuntime.delete(st.stepKey)
|
|
564
|
+
stepsByRuntime.delete(k)
|
|
430
565
|
}
|
|
431
566
|
function disposeAll(reason) {
|
|
432
567
|
runtimeState.clear()
|
package/lib/activation-inbox.js
CHANGED
|
@@ -200,14 +200,26 @@ export function validateActivationSkillPre(s) {
|
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
/**
|
|
204
|
-
|
|
203
|
+
/**
|
|
204
|
+
* 单条引用块(provenance 三行身份永不省略;reference 行内容经卫生处理)。
|
|
205
|
+
* 2026-09-14 P1-⑮:补 **Score 行** —— 0-1 相似度与批内排名此前在渲染时被丢弃,
|
|
206
|
+
* 注入侧只看得见 lane/reasonCodes,看不到"到底多像"(用户报告"0-1 相似度排序没了")。
|
|
207
|
+
* 分值与排名都取自 packet 内既有字段(item.score + 排序后的位置),确定性同 build/重渲染;
|
|
208
|
+
* score 非法(非有限数)时整行省略,保持与旧版逐字节一致的降级行为。
|
|
209
|
+
*/
|
|
210
|
+
function renderItemBlock(item, reason, rank, total) {
|
|
205
211
|
const refText = item.reference != null ? sanitizeTailText(item.reference) : ''
|
|
212
|
+
const sc = Number(item.score)
|
|
206
213
|
const lines = [
|
|
207
214
|
TAIL_MARKER_LINE_V1,
|
|
208
215
|
'Source: ' + item.memoryId + ' / ' + item.scope + ' / v' + item.sourceVersion + ' / ' + String(item.recordDigest).slice(0, 16),
|
|
209
216
|
'Reason: ' + reason,
|
|
210
217
|
]
|
|
218
|
+
if (Number.isFinite(sc)) {
|
|
219
|
+
const r = Number.isInteger(rank) && rank > 0 ? rank : null
|
|
220
|
+
const t = Number.isInteger(total) && total > 0 ? total : null
|
|
221
|
+
lines.push('Score: ' + sc.toFixed(2) + (r && t ? ' (rank ' + r + '/' + t + ')' : ''))
|
|
222
|
+
}
|
|
211
223
|
lines.push(refText ? 'Reference: ' + refText : 'Reference: (omitted by budget)')
|
|
212
224
|
return lines.join('\n')
|
|
213
225
|
}
|
|
@@ -256,8 +268,9 @@ export function renderReferenceTail(items, opts = {}) {
|
|
|
256
268
|
if (used + skillCost > budget) skillDropped = true
|
|
257
269
|
else { skillBlock = sb; used += skillCost }
|
|
258
270
|
}
|
|
259
|
-
for (
|
|
260
|
-
const
|
|
271
|
+
for (let rank = 0; rank < sorted.length; rank++) {
|
|
272
|
+
const it = sorted[rank]
|
|
273
|
+
const block = renderItemBlock(it, reason, rank + 1, sorted.length)
|
|
261
274
|
const cost = Buffer.byteLength(block, 'utf8') + 1
|
|
262
275
|
if (used + cost > budget) { dropped.push({ memoryId: it.memoryId, recordDigest: it.recordDigest, reason: 'tail-budget' }); continue }
|
|
263
276
|
blocks.push(block)
|
|
@@ -277,7 +290,7 @@ export function renderReferenceTail(items, opts = {}) {
|
|
|
277
290
|
|
|
278
291
|
/** §5 packetId:由 activationId+contextVersion+indexVersion+exactDigest 确定。 */
|
|
279
292
|
export function buildPacketId(activationId, contextVersion, memoryIndexVersion, exactDigest) {
|
|
280
|
-
const parts = ['reference-tail-packet-
|
|
293
|
+
const parts = ['reference-tail-packet-v1', String(activationId || ''), contextVersion | 0, String(memoryIndexVersion || ''), String(exactDigest || '')]
|
|
281
294
|
return PACKET_ID_PREFIX + first32(sha256Str(JSON.stringify(parts)))
|
|
282
295
|
}
|
|
283
296
|
|
|
@@ -391,7 +404,7 @@ export function buildReferenceTailPacketPre(input) {
|
|
|
391
404
|
*/
|
|
392
405
|
export function makeFakeActivationRequestPre(opts = {}) {
|
|
393
406
|
const seed = String(opts.seed || 'fake-seed')
|
|
394
|
-
const activationId = ACTIVATION_ID_PREFIX + first32(sha256Str('fake-activation-
|
|
407
|
+
const activationId = ACTIVATION_ID_PREFIX + first32(sha256Str('fake-activation-v1\u0000' + seed))
|
|
395
408
|
const records = Array.isArray(opts.records) ? opts.records : []
|
|
396
409
|
const picked = records.slice(0, Math.max(1, Math.min(opts.maxItems || 3, REFERENCE_TAIL_BUDGET_V1.maxCandidates)))
|
|
397
410
|
const candidates = picked.map((rec, i) => ({
|
|
@@ -400,6 +413,8 @@ export function makeFakeActivationRequestPre(opts = {}) {
|
|
|
400
413
|
sourceEpoch: rec.sourceEpoch, sourceVersion: rec.sourceVersion, fileDigest: rec.fileDigest, recordDigest: rec.recordDigest,
|
|
401
414
|
score: typeof rec.score === 'number' ? rec.score : 0.9 - i * 0.05,
|
|
402
415
|
excerpt: rec.excerpt != null ? sanitizeExcerpt(rec.excerpt) : undefined,
|
|
416
|
+
// C8/I5(2026-09-14):fixture 也要能构造"非 current"候选,否则注入侧过滤无从测起。
|
|
417
|
+
status: rec.status || 'current',
|
|
403
418
|
}))
|
|
404
419
|
const now = Number.isFinite(opts.now) ? opts.now : Date.now()
|
|
405
420
|
const ttlSteps = opts.ttlSteps || 2
|
|
@@ -409,7 +424,10 @@ export function makeFakeActivationRequestPre(opts = {}) {
|
|
|
409
424
|
kind: 'activation_request',
|
|
410
425
|
activationId,
|
|
411
426
|
observationId: opts.observationId || ('obs_' + first32(sha256Str('fake-obs\u0000' + seed))),
|
|
412
|
-
|
|
427
|
+
// T0-2(2026-09-14):请求侧可显式给 requestKey(同一次用户请求的稳定标识)。
|
|
428
|
+
// 缺省为空串——下游对它 fail closed("没有就不复用"),不会因此误判为同请求。
|
|
429
|
+
requestKey: opts.requestKey || '',
|
|
430
|
+
workerEpoch: opts.workerEpoch || 'fake-epoch-v1',
|
|
413
431
|
sessionId: opts.sessionId || '',
|
|
414
432
|
agentId: opts.agentId || '',
|
|
415
433
|
workspaceKey: opts.workspaceKey || '',
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* board-mode-pre —— WB-GRAPH 白板线总开关(2026-09-16, board_mode_v1)。
|
|
3
|
+
*
|
|
4
|
+
* 裁定来源: WB-GRAPH-DECISIONS-20260914.md §E + 用户 2026-09-16 凌晨原话:
|
|
5
|
+
* 「一口气全做完,但是线先别着急接。可以先在设置里,或者在接续面板上设置一个按钮,一键切换旧版和新版。」
|
|
6
|
+
*
|
|
7
|
+
* 设计(硬纪律):
|
|
8
|
+
* - boardMode = 'legacy'(默认) | 'graph'(新版看板 dsh-graph + sidecar + 遍历工具)。
|
|
9
|
+
* - **开关解耦**(用户硬性规则): boardMode 只决定"白板线新能力是否激活",
|
|
10
|
+
* 不顺带改变任何其他功能行为; legacy 模式下一切行为与 P5 收官时**字节级一致**。
|
|
11
|
+
* - graph_* 工具(dsh-graph vendor)与 memory_expand/memory_trace(P3)
|
|
12
|
+
* 仅在 boardMode='graph' 时注册; P2 sidecar 仅在 'graph' 时写盘。
|
|
13
|
+
* - fail closed: 非法值一律按 legacy(旧行为), 绝不猜。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const BOARD_MODE_VERSION = 'board_mode_v1'
|
|
17
|
+
export const BOARD_MODES_V1 = Object.freeze(['legacy', 'graph'])
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 解析白板模式。非法/缺省 → legacy(旧行为, fail closed)。
|
|
21
|
+
* @param {string|undefined} raw 配置里的 boardMode 原值
|
|
22
|
+
* @returns {{mode:'legacy'|'graph', graphEnabled:boolean, source:'default'|'config'|'fallback'}}
|
|
23
|
+
*/
|
|
24
|
+
export function resolveBoardModePre(raw) {
|
|
25
|
+
const s = String(raw == null ? '' : raw).trim().toLowerCase()
|
|
26
|
+
if (s === 'graph') return { mode: 'graph', graphEnabled: true, source: 'config' }
|
|
27
|
+
if (s === 'legacy' || s === '') return { mode: 'legacy', graphEnabled: false, source: s === '' ? 'default' : 'config' }
|
|
28
|
+
// 非法值: fallback 到 legacy 并留痕(调用方可把 source==='fallback' 记 diag)
|
|
29
|
+
return { mode: 'legacy', graphEnabled: false, source: 'fallback' }
|
|
30
|
+
}
|