@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/water-window.js
CHANGED
|
@@ -1,186 +1,263 @@
|
|
|
1
|
-
/** 上下文窗口解析(2.2.4,2026-09-08)。
|
|
2
|
-
*
|
|
3
|
-
* 背景:水位 = 当前上下文占用 / 模型窗口。占用取官方 tokenMeter(与聊天框 context ring 同源),
|
|
4
|
-
* 窗口此前有三条来源:手动覆盖 > 官方 request/context 的 contextWindow > settings.yaml 解析 > fallback。
|
|
5
|
-
* 实测发现两条路同时失效,导致窗口恒为 fallback 131072(真实模型 1M),水位被算成 150%+:
|
|
6
|
-
* 1) 官方 request/context 只在会话早期追加(本会话 2999 个事件里仅 2 条),而旧代码只扫最近 256 条 → 永远扫不到;
|
|
7
|
-
* 2) settings.yaml 的 llm-deepseek 段是 flow 风格 YAML(`{ models: [ { id: x, contextWindow: 1000000, ... } ] }`),
|
|
8
|
-
* 旧解析器只认 block 风格(`- id: x` + 行尾纯数字),整段解析不出来。
|
|
9
|
-
*
|
|
10
|
-
* 本模块把「settings.yaml → 模型窗口映射」与「按 provider/model 选窗口」抽成纯函数,便于 smoke 覆盖。
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* 从 settings.yaml 文本解析 `模型 id → contextWindow` 映射。
|
|
15
|
-
* 同时支持 block 风格(llm-pi-ai 那种 `- id: x` / `contextWindow: N`)与 flow 风格(llm-deepseek 那种 `id: x,` / `contextWindow: N,`)。
|
|
16
|
-
* @param {string} text - settings.yaml 原文。
|
|
17
|
-
* @returns {{byRoute:Record<string,number>,byModel:Record<string,number>}} byRoute 键为 provider/id,byModel 键为 id。
|
|
18
|
-
*/
|
|
19
|
-
export function parseModelWindowsPre(text) {
|
|
20
|
-
const byRoute = {}
|
|
21
|
-
const byModel = {}
|
|
22
|
-
if (!text || typeof text !== 'string') return { byRoute, byModel }
|
|
23
|
-
const lines = String(text).split(/\r?\n/)
|
|
24
|
-
let currentProvider = ''
|
|
25
|
-
let currentId = null
|
|
26
|
-
for (const line of lines) {
|
|
27
|
-
// provider 段(block 风格,2 空格缩进的裸键,如 `opencode-go:`);flow 风格段名同样以冒号结尾但缩进更深
|
|
28
|
-
const pm = line.match(/^ {2}([A-Za-z0-9_.-]+):\s*$/)
|
|
29
|
-
if (pm) { currentProvider = pm[1]; currentId = null; continue }
|
|
30
|
-
// 模型 id:兼容 block(`- id: x`)与 flow(`id: x,`)两种写法,允许行尾逗号。
|
|
31
|
-
//
|
|
32
|
-
// `meituan/LongCat-2.0:free`)
|
|
33
|
-
// 整条 id 行匹配失败,其 contextWindow
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
const im = line.match(/^\s*(?:-\s*)?id:\s*([A-Za-z0-9._:/-]+?)\s*,?\s*$/)
|
|
37
|
-
if (im) { currentId = im[1]; if (byModel[currentId] === undefined) byModel[currentId] = 0; continue }
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
*
|
|
57
|
-
* @param {string}
|
|
58
|
-
* @param {string}
|
|
59
|
-
* @
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* @param {
|
|
89
|
-
* @
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
if (!out.
|
|
107
|
-
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
if (out.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* @
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (out.
|
|
147
|
-
if (out.overflow
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
* @param {
|
|
173
|
-
* @
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
1
|
+
/** 上下文窗口解析(2.2.4,2026-09-08)。
|
|
2
|
+
*
|
|
3
|
+
* 背景:水位 = 当前上下文占用 / 模型窗口。占用取官方 tokenMeter(与聊天框 context ring 同源),
|
|
4
|
+
* 窗口此前有三条来源:手动覆盖 > 官方 request/context 的 contextWindow > settings.yaml 解析 > fallback。
|
|
5
|
+
* 实测发现两条路同时失效,导致窗口恒为 fallback 131072(真实模型 1M),水位被算成 150%+:
|
|
6
|
+
* 1) 官方 request/context 只在会话早期追加(本会话 2999 个事件里仅 2 条),而旧代码只扫最近 256 条 → 永远扫不到;
|
|
7
|
+
* 2) settings.yaml 的 llm-deepseek 段是 flow 风格 YAML(`{ models: [ { id: x, contextWindow: 1000000, ... } ] }`),
|
|
8
|
+
* 旧解析器只认 block 风格(`- id: x` + 行尾纯数字),整段解析不出来。
|
|
9
|
+
*
|
|
10
|
+
* 本模块把「settings.yaml → 模型窗口映射」与「按 provider/model 选窗口」抽成纯函数,便于 smoke 覆盖。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 从 settings.yaml 文本解析 `模型 id → contextWindow` 映射。
|
|
15
|
+
* 同时支持 block 风格(llm-pi-ai 那种 `- id: x` / `contextWindow: N`)与 flow 风格(llm-deepseek 那种 `id: x,` / `contextWindow: N,`)。
|
|
16
|
+
* @param {string} text - settings.yaml 原文。
|
|
17
|
+
* @returns {{byRoute:Record<string,number>,byModel:Record<string,number>}} byRoute 键为 provider/id,byModel 键为 id。
|
|
18
|
+
*/
|
|
19
|
+
export function parseModelWindowsPre(text) {
|
|
20
|
+
const byRoute = {}
|
|
21
|
+
const byModel = {}
|
|
22
|
+
if (!text || typeof text !== 'string') return { byRoute, byModel }
|
|
23
|
+
const lines = String(text).split(/\r?\n/)
|
|
24
|
+
let currentProvider = ''
|
|
25
|
+
let currentId = null
|
|
26
|
+
for (const line of lines) {
|
|
27
|
+
// provider 段(block 风格,2 空格缩进的裸键,如 `opencode-go:`);flow 风格段名同样以冒号结尾但缩进更深
|
|
28
|
+
const pm = line.match(/^ {2}([A-Za-z0-9_.-]+):\s*$/)
|
|
29
|
+
if (pm) { currentProvider = pm[1]; currentId = null; continue }
|
|
30
|
+
// 模型 id:兼容 block(`- id: x`)与 flow(`id: x,`)两种写法,允许行尾逗号。
|
|
31
|
+
// 2026-09-14 移植 PR #31(上游修复,作者 Minervaowl7):字符类须含 `/` ——
|
|
32
|
+
// provider 前缀式 id(`deepseek/deepseek-v4.1-flash`、`z-ai/glm-5.3-flash`、`meituan/LongCat-2.0:free`)
|
|
33
|
+
// 是常见形态;旧字符类不含斜杠 → 整条 id 行匹配失败,其 contextWindow 被**静默丢弃**,
|
|
34
|
+
// 窗口退化为 fallback 131072。实测:真实窗口 1,000,000 的会话被按 131,072 当分母,
|
|
35
|
+
// **水位放大 7.63 倍(12.8% 显示成 98%)**,未达 0.75 阈值即误弹「接续到新会话」确认卡。
|
|
36
|
+
const im = line.match(/^\s*(?:-\s*)?id:\s*([A-Za-z0-9._:/-]+?)\s*,?\s*$/)
|
|
37
|
+
if (im) { currentId = im[1]; if (byModel[currentId] === undefined) byModel[currentId] = 0; continue }
|
|
38
|
+
// 2026-09-14 补齐 PR #31 的第二半:上一轮只移植了「字符类须含 `/`」那一半,漏了本条。
|
|
39
|
+
// id 行存在但格式仍不被识别(带引号/含空格/中文等):重置 currentId,避免紧随其后的
|
|
40
|
+
// contextWindow 被记到上一条模型头上 —— 静默错配比"读不到"更危险:读不到只退化为 fallback,
|
|
41
|
+
// 错配会给出**偏小**的窗口,使水位漏报。
|
|
42
|
+
if (/^\s*(?:-\s*)?id:/.test(line)) { currentId = null; continue }
|
|
43
|
+
// 窗口:允许行尾逗号(flow 风格)
|
|
44
|
+
const cm = line.match(/^\s*contextWindow:\s*([0-9]+)\s*,?\s*$/)
|
|
45
|
+
if (cm && currentId) {
|
|
46
|
+
const value = Number(cm[1])
|
|
47
|
+
const route = currentProvider + '/' + currentId
|
|
48
|
+
if (byRoute[route] === undefined) byRoute[route] = value
|
|
49
|
+
if (!byModel[currentId]) byModel[currentId] = value
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { byRoute, byModel }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 按 provider/model 选窗口:先精确匹配 provider/model,再按 model 名全局兜底。
|
|
57
|
+
* @param {{byRoute:Record<string,number>,byModel:Record<string,number>}} windows - parseModelWindowsPre 结果。
|
|
58
|
+
* @param {string} provider - 路由 provider(可空)。
|
|
59
|
+
* @param {string} model - 模型 id。
|
|
60
|
+
* @returns {number} 窗口 token 数;未命中返回 0。
|
|
61
|
+
*/
|
|
62
|
+
export function pickWindowPre(windows, provider, model) {
|
|
63
|
+
if (!windows || !model) return 0
|
|
64
|
+
const byRoute = windows.byRoute || {}
|
|
65
|
+
const byModel = windows.byModel || {}
|
|
66
|
+
const exact = byRoute[(provider || '') + '/' + model]
|
|
67
|
+
if (Number.isFinite(exact) && exact > 0) return exact
|
|
68
|
+
const direct = byModel[model]
|
|
69
|
+
if (Number.isFinite(direct) && direct > 0) return direct
|
|
70
|
+
// 后缀兜底:路由 id 可能带 provider 前缀或版本后缀差异
|
|
71
|
+
for (const route of Object.keys(byRoute)) {
|
|
72
|
+
if (route.endsWith('/' + model) && byRoute[route] > 0) return byRoute[route]
|
|
73
|
+
}
|
|
74
|
+
for (const id of Object.keys(byModel)) {
|
|
75
|
+
if (id.endsWith('-' + model) && byModel[id] > 0) return byModel[id]
|
|
76
|
+
}
|
|
77
|
+
return 0
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 从会话事件里取最近一条 request/header|request/context 的真实 provider/model(+ 顺带 contextWindow)。
|
|
82
|
+
*
|
|
83
|
+
* 背景(2026-09-08 用户实测):水位卡此前把「窗口来源」标成 settings.yaml 的 agent-default-model(新会话默认模型),
|
|
84
|
+
* 用户在 GUI 里切过默认模型后,当前会话实际跑的模型与卡片显示不符
|
|
85
|
+
* (实测:会话日志 request/header = deepseek-official/deepseek-v4.1-flash-expires-on-0910,卡片却写 opencode-go/deepseek-v4-flash)。
|
|
86
|
+
* 会话自己的请求头才是「这个会话正在用哪个模型」的权威来源;默认模型只作兜底。
|
|
87
|
+
*
|
|
88
|
+
* @param {object[]} events - 会话事件数组。
|
|
89
|
+
* @param {number} maxScan - 最多向前扫多少条(默认全部)。
|
|
90
|
+
* @returns {{provider:string,model:string,contextWindow:number}} 未找到时字段为空/0。
|
|
91
|
+
*/
|
|
92
|
+
export function findSessionModelPre(events, maxScan = 0) {
|
|
93
|
+
const out = { provider: '', model: '', contextWindow: 0, maxTokens: 0 }
|
|
94
|
+
if (!Array.isArray(events) || !events.length) return out
|
|
95
|
+
const floor = maxScan > 0 ? Math.max(0, events.length - maxScan) : 0
|
|
96
|
+
for (let i = events.length - 1; i >= floor; i--) {
|
|
97
|
+
const ev = events[i]
|
|
98
|
+
const t = ev && ev.type
|
|
99
|
+
if (t !== 'request/header' && t !== 'request/context') continue
|
|
100
|
+
const d = (ev && ev.data) || {}
|
|
101
|
+
const cfg = (d.header && d.header.config) || d.config || {}
|
|
102
|
+
const provider = String(cfg.provider || d.provider || '')
|
|
103
|
+
const model = String(cfg.model || d.model || '')
|
|
104
|
+
const cw = Number(d.contextWindow) || 0
|
|
105
|
+
const mt = Number(cfg.maxTokens) || 0
|
|
106
|
+
if (!out.model && model) { out.model = model; out.provider = provider }
|
|
107
|
+
if (!out.contextWindow && cw > 0) out.contextWindow = cw
|
|
108
|
+
// maxTokens = 路由为**本次请求预留的输出预算**,provider 会把它计入 "requested tokens"(实测:
|
|
109
|
+
// 消息 666,044 + 预留 384,000 = 1,050,044 > 上限 1,048,576 → 400 CONTEXT_WINDOW_EXCEEDED)。
|
|
110
|
+
if (!out.maxTokens && mt > 0) out.maxTokens = mt
|
|
111
|
+
if (out.model && out.contextWindow && out.maxTokens) break
|
|
112
|
+
}
|
|
113
|
+
return out
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 水位「硬信号」扫描(2026-09-10,实机取证后新增)。
|
|
118
|
+
*
|
|
119
|
+
* 背景(用户实测:官方压缩又抢在 75% 接续之前):本机 provider 的硬上限是 1,048,576 token,
|
|
120
|
+
* 而**预留输出预算 384,000 也算在请求里** ⇒ 消息实际只能用到约 664,576。插件此前拿
|
|
121
|
+
* `contextWindow`(1,000,000)当分母、拿本地计量当分子,两边都偏乐观,于是 75% 永远到不了,
|
|
122
|
+
* 直到上游直接 400:
|
|
123
|
+
* "This model's maximum context length is 1048576 tokens. However, you requested 1050044 tokens
|
|
124
|
+
* (666044 in the messages, 384000 in the completion)."
|
|
125
|
+
* 这条错误是**唯一权威**的口径来源。本函数把它与 compaction 事件一起扫出来,供上层:
|
|
126
|
+
* ① 解析真实窗口/预留额度/消息实占 → 自我校准分子;
|
|
127
|
+
* ② 一旦发生 overflow 或 compaction,直接当作「已到阈值」触发接续(不再依赖估算)。
|
|
128
|
+
*
|
|
129
|
+
* @param {object[]} events - 会话事件数组。
|
|
130
|
+
* @returns {{reservedTokens:number,overflow:{seq:number,windowTokens:number,requestedTokens:number,messageTokens:number,completionTokens:number,code:string}|null,compactionSeq:number}}
|
|
131
|
+
*/
|
|
132
|
+
export function scanPressureSignalsPre(events) {
|
|
133
|
+
const out = { reservedTokens: 0, overflow: null, compactionSeq: 0 }
|
|
134
|
+
if (!Array.isArray(events) || !events.length) return out
|
|
135
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
136
|
+
const ev = events[i]
|
|
137
|
+
if (!ev) continue
|
|
138
|
+
const seq = Number(ev.seq) || 0
|
|
139
|
+
const t = ev.type
|
|
140
|
+
if (!out.reservedTokens && (t === 'request/header' || t === 'request/context')) {
|
|
141
|
+
const d = (ev.data || {})
|
|
142
|
+
const cfg = (d.header && d.header.config) || d.config || {}
|
|
143
|
+
const mt = Number(cfg.maxTokens) || 0
|
|
144
|
+
if (mt > 0) out.reservedTokens = mt
|
|
145
|
+
}
|
|
146
|
+
if (!out.compactionSeq && (t === 'compaction/start' || t === 'compaction/summary')) out.compactionSeq = seq
|
|
147
|
+
if (out.overflow && out.compactionSeq && out.reservedTokens) break
|
|
148
|
+
if (out.overflow || t !== 'assistant/attempt') continue
|
|
149
|
+
const stream = (ev.data && ev.data.stream) || []
|
|
150
|
+
for (const item of stream) {
|
|
151
|
+
const chunk = (item && item.chunk) || {}
|
|
152
|
+
if (chunk.type !== 'finish') continue
|
|
153
|
+
const failure = (chunk.reason && chunk.reason.failure) || {}
|
|
154
|
+
const code = String(failure.code || '')
|
|
155
|
+
const msg = String(failure.message || '')
|
|
156
|
+
if (code !== 'CONTEXT_WINDOW_EXCEEDED' && !/maximum context length/i.test(msg)) continue
|
|
157
|
+
const win = Number((msg.match(/maximum context length is\s+(\d+)\s+tokens/i) || [])[1]) || 0
|
|
158
|
+
const req = Number((msg.match(/you requested\s+(\d+)\s+tokens/i) || [])[1]) || 0
|
|
159
|
+
const inMsg = Number((msg.match(/\((\d+)\s+in the messages/i) || [])[1]) || 0
|
|
160
|
+
const inComp = Number((msg.match(/(\d+)\s+in the completion\)/i) || [])[1]) || 0
|
|
161
|
+
out.overflow = { seq, windowTokens: win, requestedTokens: req, messageTokens: inMsg, completionTokens: inComp, code: code || 'CONTEXT_WINDOW_EXCEEDED' }
|
|
162
|
+
break
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return out
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* 从会话事件里取最近一条 request/context 的 contextWindow(路由权威值)。
|
|
171
|
+
* 不限制扫描窗口:该事件只在请求头部变化时追加,长会话里往往只存在于开头。
|
|
172
|
+
* @param {object[]} events - 会话事件数组。
|
|
173
|
+
* @param {number} maxScan - 最多向前扫多少条(默认全部;设小值可限制开销)。
|
|
174
|
+
* @returns {number} contextWindow;未找到返回 0。
|
|
175
|
+
*/
|
|
176
|
+
export function findOfficialContextWindowPre(events, maxScan = 0) {
|
|
177
|
+
if (!Array.isArray(events) || !events.length) return 0
|
|
178
|
+
const floor = maxScan > 0 ? Math.max(0, events.length - maxScan) : 0
|
|
179
|
+
for (let i = events.length - 1; i >= floor; i--) {
|
|
180
|
+
const ev = events[i]
|
|
181
|
+
if (ev && ev.type === 'request/context') {
|
|
182
|
+
const cw = Number(ev && ev.data && ev.data.contextWindow)
|
|
183
|
+
return Number.isFinite(cw) && cw > 0 ? cw : 0
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return 0
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 判断「会话模型扫描结果」缓存可否复用(2026-09-14)。
|
|
191
|
+
*
|
|
192
|
+
* checkWaterLevel 把事件扫描结果按会话缓存 5 分钟,但**空结果也被一并缓存**:
|
|
193
|
+
* `if (cached && cached.sid === sid && now - cached.at < 300000 && cached.info) { 复用 }`
|
|
194
|
+
* `cached.info` 恒为对象(即使内容为空) ⇒ 空结果照样命中,并被锁死 5 分钟。
|
|
195
|
+
*
|
|
196
|
+
* 而 `request/header` / `request/context` 是**发起请求时**才追加的事件 —— 实测同一轮内
|
|
197
|
+
* `turn/start`(seq=4) → `request/context`(seq=13) 仅相隔 58ms,而首轮 `agent/pre-step`
|
|
198
|
+
* 的测量早于这两条(step/start 在 request/context 之前约 21ms)。所以首次扫描必然扫不到
|
|
199
|
+
* `request/context`:只能从 `request/header` 拿到 provider/model,`contextWindow` 为 0。
|
|
200
|
+
* 该结果随后被锁死 5 分钟,这 5 分钟里 `official-context`(路由权威口径)始终不可用,
|
|
201
|
+
* 窗口只能靠 settings.yaml 查表;若该模型在 settings.yaml 里查不到(新模型 / 切到未配置的
|
|
202
|
+
* provider),窗口即退化为 fallback 131072,水位被系统性放大。
|
|
203
|
+
*
|
|
204
|
+
* 复用规则:
|
|
205
|
+
* - **非空**(contextWindow > 0):TTL 内直接复用 —— `request/context` 只在会话开头追加、值稳定,
|
|
206
|
+
* 且扫描是 O(n),长会话理应避免重复全量扫描。
|
|
207
|
+
* - **空**:仅在**事件数未增长**时复用 —— 事件数增长意味着可能有新事件写入,值得重扫一次;
|
|
208
|
+
* 未增长则复用,不为每个 pre-step 付出一次 O(n) 扫描。
|
|
209
|
+
*
|
|
210
|
+
* 兼容性:`cached.events` 为本次新增字段,旧缓存对象取到 undefined ⇒ 与 eventsLen 不相等 ⇒
|
|
211
|
+
* 不复用、走重扫 —— 安全降级。
|
|
212
|
+
*
|
|
213
|
+
* @param {{sid:string, at:number, info:object, events:number}|null|undefined} cached - 上次缓存(cached.events = 扫描时的事件数)。
|
|
214
|
+
* @param {string} sid - 当前会话 id。
|
|
215
|
+
* @param {number} eventsLen - 当前事件数组长度。
|
|
216
|
+
* @param {number} now - 当前时间戳(ms)。
|
|
217
|
+
* @param {number} [ttlMs=300000] - 缓存有效期。
|
|
218
|
+
* @returns {boolean} true 表示可复用 cached.info。
|
|
219
|
+
*/
|
|
220
|
+
export function reusableWindowCachePre(cached, sid, eventsLen, now, ttlMs = 300000) {
|
|
221
|
+
if (!cached || !sid) return false
|
|
222
|
+
if (cached.sid !== sid) return false
|
|
223
|
+
if (!(now - Number(cached.at || 0) < ttlMs)) return false
|
|
224
|
+
const info = cached.info
|
|
225
|
+
if (!info) return false
|
|
226
|
+
if (Number(info.contextWindow) > 0) return true
|
|
227
|
+
return Number(cached.events) === Number(eventsLen)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* 判断是否具备「按水位比例触发自动接续」的资格(2026-09-14)。
|
|
232
|
+
*
|
|
233
|
+
* 背景:窗口来源优先级为 手动覆盖 > 官方 request/context > settings.yaml 按「会话真实模型」查表
|
|
234
|
+
* > fallback 131072。而「会话真实模型」只能从会话事件 `request/header` / `request/context` 读取 ——
|
|
235
|
+
* DSH 的 sessionApi 只提供 `selectModel`(只写不读),没有任何查询会话当前模型的接口。
|
|
236
|
+
*
|
|
237
|
+
* 这两个事件都是**发起请求时**才追加的:实测首轮 `turn/start` → `request/header` 之间不存在任何
|
|
238
|
+
* 模型信息,即在 **新会话首轮 `agent/pre-step`** 的测量点上,`findSessionModelPre` 返回
|
|
239
|
+
* `{provider:'',model:'',contextWindow:0,maxTokens:0}` —— **全空**。此时窗口与预留额度只能用
|
|
240
|
+
* settings.yaml 的 `agent-default-model` 推算,而它与会话实际模型可以完全不同
|
|
241
|
+
* (实测同一台机器上 `agent-default-model` 是 `command-code/deepseek-v4.1-flash`,
|
|
242
|
+
* 而某个会话实际跑的是 `deepseek-official/deepseek-flash`,连 `maxTokens` 都不一致)。
|
|
243
|
+
*
|
|
244
|
+
* 用推算出来的分母按比例触发「建新会话 + 注入交接材料 + 切走窗口」这类重动作,风险不对称:
|
|
245
|
+
* 分母偏小会让水位虚高数倍(实测 1,000,000 被当 131,072,放大 7.63 倍),未达阈值即误弹接续卡。
|
|
246
|
+
* 因此模型未知时**只放行硬信号**(`compaction` / `overflow` / 撞硬墙 —— 均为真实事件,不依赖窗口估算);
|
|
247
|
+
* 纯比例判据推迟到轮末再判 —— 届时 `request/header` 已写入,模型已知,判定链条完整。
|
|
248
|
+
*
|
|
249
|
+
* `wl.modelKnown !== true` 采用 fail-closed(2026-09-14 审查修正):字段缺失(undefined/null/0)一律
|
|
250
|
+
* 视为「未知」,只放行硬信号。原先 `=== false` 显式判等本意是让未传字段的旧调用点行为不变,但
|
|
251
|
+
* checkWaterLevel 存在早退路径(handoff 关闭 / win<=0 / 测量中途异常),此时 rt.waterLevelModelKnown
|
|
252
|
+
* 为 undefined → 闸被静默绕过(变异验证:直调 {ratio:0.99, modelKnown:undefined, hard:false} 放行)。
|
|
253
|
+
* fail-closed 后该路径收口为「不按比例 arm」—— 漏判优于误判,与本函数的整体取舍一致;两个 in-repo
|
|
254
|
+
* 调用点都以 `!!(...)` 写入真布尔,正常测量路径行为不变。
|
|
255
|
+
*
|
|
256
|
+
* @param {{ratio:number, modelKnown?:boolean, hard?:boolean}|null|undefined} wl - 水位快照。
|
|
257
|
+
* @returns {boolean} true 表示允许按比例 arm。
|
|
258
|
+
*/
|
|
259
|
+
export function shouldArmAutoContinuePre(wl) {
|
|
260
|
+
if (!wl) return false
|
|
261
|
+
if (wl.modelKnown !== true && !wl.hard) return false
|
|
262
|
+
return true
|
|
263
|
+
}
|