@a9i5k4/dsh-auto-memory 2.1.7 → 2.1.9
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/lib/client.js +18 -2
- package/lib/index.js +89 -2
- package/lib/python-setup.js +23 -8
- package/lib/semantic-js.js +491 -485
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -1223,6 +1223,17 @@ window.__ModuleLoader__.load({
|
|
|
1223
1223
|
var pollPair = useState(null)
|
|
1224
1224
|
var poll = pollPair[0]
|
|
1225
1225
|
var setPoll = pollPair[1]
|
|
1226
|
+
var gpuPair = useState(function () { try { return localStorage.getItem('dsh-auto-memory.pyGpu') === '1' } catch (eG) { return false } })
|
|
1227
|
+
var gpuPref = gpuPair[0]
|
|
1228
|
+
var setGpuPref = function (v) {
|
|
1229
|
+
gpuPair[1](v)
|
|
1230
|
+
try { localStorage.setItem('dsh-auto-memory.pyGpu', v ? '1' : '0') } catch (eG) {}
|
|
1231
|
+
}
|
|
1232
|
+
var actGpu = function () {
|
|
1233
|
+
if (busy) return
|
|
1234
|
+
setBusy(true)
|
|
1235
|
+
apiPost(API.pyDeps, { gpu: gpuPref }).then(function (d) { setData(d); setBusy(false); setPoll(true); var ph = d && d.phase; if (ph === 'ready' || ph === 'error' || ph === 'idle') setPoll(false) }).catch(function () { setBusy(false); setPoll(false) })
|
|
1236
|
+
}
|
|
1226
1237
|
var refresh = function () { apiGet(API.pyStatus).then(function (d) { if (d) setData(d) }).catch(function () {}) }
|
|
1227
1238
|
useEffect(function () {
|
|
1228
1239
|
var alive = true
|
|
@@ -1267,7 +1278,11 @@ window.__ModuleLoader__.load({
|
|
|
1267
1278
|
stepRow(t('pyWizStep2'), data.venvOk ? true : 'run',
|
|
1268
1279
|
(!data.venvOk && (data.pythons || []).some(function (p2) { return p2.status === 'ok' && !p2.isVenv })) ? h('button', Object.assign({}, btnCls, { onClick: function () { act(API.pyVenv, true) } }), t('pyWizCreate')) : null),
|
|
1269
1280
|
stepRow(t('pyWizStep3'), data.depsOk ? true : 'run',
|
|
1270
|
-
(data.venvOk && !data.depsOk) ? h('
|
|
1281
|
+
(data.venvOk && !data.depsOk) ? h('span', { style: { display: 'flex', gap: '8px', alignItems: 'center' } },
|
|
1282
|
+
h('label', { style: { display: 'flex', gap: '4px', alignItems: 'center', fontSize: 'calc(10.5px * var(--dam-scale))' } },
|
|
1283
|
+
h('input', { type: 'checkbox', checked: gpuPref, onChange: function (e) { setGpuPref(e.target.checked) } }),
|
|
1284
|
+
locale === 'zh' ? 'GPU 推理(NVIDIA,需已装 CUDA;否则自动回退 CPU)' : 'GPU inference (NVIDIA + CUDA; auto-falls back to CPU)'),
|
|
1285
|
+
h('button', Object.assign({}, btnCls, { onClick: function () { actGpu() } }), t('pyWizInstall'))) : null),
|
|
1271
1286
|
stepRow(t('pyWizStep4'), data.modelReady ? true : 'run',
|
|
1272
1287
|
(!data.modelReady && !dlActive) ? h('button', Object.assign({}, btnCls, { onClick: function () { act(API.pyModel, true) } }), t('pyWizDownload')) : null,
|
|
1273
1288
|
dlActive ? h('button', { 'data-dam-btn': '', onClick: function () { apiPost(API.pyCancel, {}).then(refresh).catch(function () {}) } }, t('pyWizCancel')) : null),
|
|
@@ -3047,6 +3062,7 @@ window.__ModuleLoader__.load({
|
|
|
3047
3062
|
: 'The built-in JS semantic model (~135MB, one-time) can be downloaded in the background; the semantic tier enables itself once done:'))
|
|
3048
3063
|
sdActions.push(h('button', { 'data-dam-btn': '', 'data-primary': 'true', style: close, onClick: function () {
|
|
3049
3064
|
apiPost(API.semanticDownload, { action: 'start', mirror: 'auto' }).catch(function () {})
|
|
3065
|
+
try { localStorage.setItem('dsh-auto-memory.semDetectSnoozeUntil', String(Date.now() + 24 * 3600 * 1000)) } catch (eSn2) {}
|
|
3050
3066
|
closeDialog()
|
|
3051
3067
|
} }, sdZh ? '立即下载' : 'Download now'))
|
|
3052
3068
|
}
|
|
@@ -3421,7 +3437,7 @@ window.__ModuleLoader__.load({
|
|
|
3421
3437
|
h('option', { value: 'auto' }, t('mAuto')),
|
|
3422
3438
|
h('option', { value: 'cn' }, t('mCn')),
|
|
3423
3439
|
h('option', { value: 'intl' }, t('mIntl'))) : null,
|
|
3424
|
-
ready ? h('button', { 'data-dam-btn': '', onClick: function () { setGuide(''); var n2 = Object.assign({}, cfg); n2.semanticEngineMode = guide; if (guide === 'js') { n2.activationSource = 'js'; n2.contextSinkMode = 'null' } else if (guide === 'python') { n2.activationSource = 'python'; n2.contextSinkMode = 'python' } setCfg(n2); setDirty(true) } }, locale === 'zh' ? '启用并继续' : 'Enable & continue') : null,
|
|
3440
|
+
ready ? h('button', { 'data-dam-btn': '', onClick: function () { setGuide(''); var n2 = Object.assign({}, cfg); n2.semanticEngineMode = guide; if (guide === 'js') { n2.activationSource = 'js'; n2.contextSinkMode = 'null' } else if (guide === 'python') { n2.activationSource = 'python'; n2.contextSinkMode = 'python'; try { var gp = localStorage.getItem('dsh-auto-memory.pyGpu'); if (gp === '1') n2.pythonGpu = true } catch (eL) {} } setCfg(n2); setDirty(true) } }, locale === 'zh' ? '启用并继续' : 'Enable & continue') : null,
|
|
3425
3441
|
!ready && isJs && !dlActive ? h('button', { 'data-dam-btn': '', onClick: startDl }, (dl.phase === 'error' || dl.phase === 'cancelled') ? t('semDlRetry') : t('semDlStart')) : null,
|
|
3426
3442
|
!ready && isJs && dlActive ? h('button', { 'data-dam-btn': '', onClick: cancelDl }, t('semDlCancel')) : null,
|
|
3427
3443
|
h('button', { 'data-dam-btn': '', onClick: function () { setGuide('') } }, t('gotIt'))))
|
package/lib/index.js
CHANGED
|
@@ -2335,6 +2335,78 @@ class MemoryEngine {
|
|
|
2335
2335
|
return true
|
|
2336
2336
|
}
|
|
2337
2337
|
|
|
2338
|
+
/**
|
|
2339
|
+
* F1(M6-CONTRACT 外补丁,2026-08-23):anchor 开启时的记录级压缩。
|
|
2340
|
+
* 按 sidecar 记录序整条保留/移除——今天(标题日期=插件今天)记录无条件保留,其余按字节配额
|
|
2341
|
+
* 从最新往回保留(layer 上限 user 4000/note 3000);被移除记录整条原文写入归档文件
|
|
2342
|
+
* (writeFullRaw,不经 anchor 事务,归档非检索语料)。重组经 store.replace 原子执行:
|
|
2343
|
+
* 候选预检(clean)→被移除 id 自动删除→保留 id/版本不变。永不字符切片。
|
|
2344
|
+
* @returns {Promise<boolean|'not-anchored'>} true=已腾位 false=无法腾位 'not-anchored'=无 sidecar 走旧路径
|
|
2345
|
+
*/
|
|
2346
|
+
async compactAnchoredLayer(store, filePath, layer, today, paths) {
|
|
2347
|
+
const cur = await this.readTextSafe(filePath)
|
|
2348
|
+
if (!cur || !cur.trim()) return false
|
|
2349
|
+
let parsed
|
|
2350
|
+
try { parsed = parseAnchors(Buffer.from(cur, 'utf8')) } catch (_) { return 'not-anchored' }
|
|
2351
|
+
if (!parsed || parsed.status !== 'clean') return false // 脏文件 fail closed:不压缩、不切片
|
|
2352
|
+
const anchored = parsed.records.filter((r) => r.kind === 'anchored')
|
|
2353
|
+
if (!anchored.length) return 'not-anchored'
|
|
2354
|
+
const limit = layer === 'user' ? 4000 : 3000
|
|
2355
|
+
const isTodayHeading = (heading) => /^\d{4}-\d{2}-\d{2}/.test(String(heading || '')) && String(heading).slice(0, 10) === today
|
|
2356
|
+
const buf = Buffer.from(cur, 'utf8')
|
|
2357
|
+
// 配额从尾回溯(F1):新的留;今天记录无条件保留
|
|
2358
|
+
const keep = new Array(anchored.length).fill(false)
|
|
2359
|
+
let keptBytes = 0
|
|
2360
|
+
for (let i = anchored.length - 1; i >= 0; i--) {
|
|
2361
|
+
const rec = anchored[i]
|
|
2362
|
+
const spanBytes = rec.byteEnd - rec.markerByteStart
|
|
2363
|
+
if (isTodayHeading(rec.heading) || keptBytes + spanBytes <= limit) { keep[i] = true; keptBytes += spanBytes }
|
|
2364
|
+
}
|
|
2365
|
+
const removedIdx = []
|
|
2366
|
+
anchored.forEach((rec, i) => { if (!keep[i]) removedIdx.push(i) })
|
|
2367
|
+
if (!removedIdx.length) return false // 无可移除 → 无法腾位(预算照常拒绝)
|
|
2368
|
+
// 有序块重组:legacy 块与 preamble/tail 原样保留,gap 字节逐段拷贝,仅丢弃被移除记录的 [marker..contentEnd]
|
|
2369
|
+
const blocks = []
|
|
2370
|
+
for (const r of parsed.records) {
|
|
2371
|
+
if (r.kind === 'anchored') blocks.push({ start: r.markerByteStart, end: r.byteEnd, keep: keep[anchored.indexOf(r)] })
|
|
2372
|
+
else blocks.push({ start: r.byteStart, end: r.byteEnd, keep: true })
|
|
2373
|
+
}
|
|
2374
|
+
blocks.sort((a, b) => a.start - b.start)
|
|
2375
|
+
const parts = []
|
|
2376
|
+
let cursorB = 0
|
|
2377
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
2378
|
+
const b = blocks[i]
|
|
2379
|
+
if (b.start > cursorB) parts.push(buf.slice(cursorB, b.start).toString('utf8'))
|
|
2380
|
+
if (b.keep) parts.push(buf.slice(b.start, b.end).toString('utf8'))
|
|
2381
|
+
cursorB = b.end
|
|
2382
|
+
}
|
|
2383
|
+
if (cursorB < buf.length) parts.push(buf.slice(cursorB).toString('utf8'))
|
|
2384
|
+
const newText = parts.join('')
|
|
2385
|
+
// 归档:整条原文(writeFullRaw 绕开 anchor 事务;归档文件从不参与解析/索引)
|
|
2386
|
+
const archiveFile = layer === 'user'
|
|
2387
|
+
? path.join(dshHome(), 'memory', 'archived-user.md')
|
|
2388
|
+
: path.join(paths.projectDir, 'archive', 'notes-archived.md')
|
|
2389
|
+
const archiveText = removedIdx.map((i2) => {
|
|
2390
|
+
const r = anchored[i2]
|
|
2391
|
+
return buf.slice(r.markerByteStart, r.byteEnd).toString('utf8')
|
|
2392
|
+
}).join('\n')
|
|
2393
|
+
const existingArchive = await this.readTextSafe(archiveFile)
|
|
2394
|
+
await this.writeFullRaw(archiveFile, (existingArchive ? existingArchive.replace(/\s+$/, '') + '\n' : '') + archiveText + '\n')
|
|
2395
|
+
const rr = await store.replace(filePath, newText)
|
|
2396
|
+
if (!rr.ok) { console.error('[compacted-anchored] replace failed: ' + rr.reason); return false }
|
|
2397
|
+
const freshText = await this.readTextSafe(filePath)
|
|
2398
|
+
if (layer === 'user') this.state.userText = freshText
|
|
2399
|
+
else this.state.notesText = freshText
|
|
2400
|
+
if (this._budgets) {
|
|
2401
|
+
const b = this._budgets.get('today')
|
|
2402
|
+
if (b && b.date === this.memToday()) b[layer] = 0
|
|
2403
|
+
}
|
|
2404
|
+
this._lastCompactAt = Date.now()
|
|
2405
|
+
this.state.loadedAt = Date.now()
|
|
2406
|
+
console.log('[compacted-anchored] ' + layer + ': kept=' + (anchored.length - removedIdx.length) + '/' + anchored.length + ' archived=' + removedIdx.length + ' bytes=' + cur.length + '->' + newText.length)
|
|
2407
|
+
return true
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2338
2410
|
// ---------- 注入渲染(同步,基于缓存) ----------
|
|
2339
2411
|
// 动态记忆 → ctx.systemPrompt.context()(user-role 快照):内容变化才追加新快照,内容不变不重复注入(dsh-agent-loop project() 去重),
|
|
2340
2412
|
// system prompt 不再包含动态内容 → 字节级稳定 → DeepSeek 前缀缓存全程命中(对比 section 方案:动态内容任何变化都从变化点起击穿整个前缀,含全部历史)
|
|
@@ -2381,6 +2453,17 @@ class MemoryEngine {
|
|
|
2381
2453
|
if (cfg.waterLevelAdvisory !== false && !this.isUnattendedNow() && (s.waterLevelRatio || 0) >= (Number(cfg.waterLevelThreshold) || 0.8) && Date.now() - (s.waterLevelAt || 0) < 600000) {
|
|
2382
2454
|
lines.push('\n' + L('snapshotWaterTitle') + '\n' + L('snapshotWaterBody', { pct: Math.round((s.waterLevelRatio || 0) * 100) }))
|
|
2383
2455
|
}
|
|
2456
|
+
// M-CM1 交接白板:PLAN.md 全貌+最新交接账本,动态快照首位(动态层内容,不碰静态字节;敏感段清洗+行边界硬预算截断)
|
|
2457
|
+
if (cfg.handoffEnabled !== false && (s.planText || s.latestHandoffText)) {
|
|
2458
|
+
const planPart = truncateLinesBounded(stripSensitiveSections(sanitizeForInjection(s.planText || '')), Math.max(Number(cfg.handoffPlanChars) || 1200, 200))
|
|
2459
|
+
if (planPart) lines.push('\n' + L('snapshotPlanTitle') + '\n' + planPart)
|
|
2460
|
+
const ledgerPart = truncateLinesBounded(stripSensitiveSections(sanitizeForInjection(s.latestHandoffText || '')), Math.max(Number(cfg.handoffLedgerChars) || 800, 200))
|
|
2461
|
+
if (ledgerPart) lines.push('\n' + L('snapshotHandoffTitle') + '\n' + ledgerPart)
|
|
2462
|
+
}
|
|
2463
|
+
// M-CM4/M-CM5 水位建议:越阈注入交接+派子代理建议(advisory;10 分钟新鲜度;无人值守静默)
|
|
2464
|
+
if (cfg.waterLevelAdvisory !== false && !this.isUnattendedNow() && (s.waterLevelRatio || 0) >= (Number(cfg.waterLevelThreshold) || 0.8) && Date.now() - (s.waterLevelAt || 0) < 600000) {
|
|
2465
|
+
lines.push('\n' + L('snapshotWaterTitle') + '\n' + L('snapshotWaterBody', { pct: Math.round((s.waterLevelRatio || 0) * 100) }))
|
|
2466
|
+
}
|
|
2384
2467
|
let used = 0
|
|
2385
2468
|
const part = (title, text, max) => {
|
|
2386
2469
|
if (!text) return
|
|
@@ -4829,7 +4912,10 @@ export function apply(ctx, config) {
|
|
|
4829
4912
|
{
|
|
4830
4913
|
const pluginDir = path.dirname(fileURLToPath(import.meta.url))
|
|
4831
4914
|
engine._jsSemantic = createJsSemanticEnginePre({ pluginDir })
|
|
4832
|
-
|
|
4915
|
+
// #15 后续/B:JS 模型下载落位用户目录(~/.dsh/models/js-semantic/)——包目录在 npm 更新时被重装,130MB 曾被冲掉
|
|
4916
|
+
const userModelsRoot = path.join((process.env.DSH_HOME && process.env.DSH_HOME.trim()) || path.join(homedir(), '.dsh'), 'models', 'js-semantic')
|
|
4917
|
+
mkdirSync(userModelsRoot, { recursive: true })
|
|
4918
|
+
engine._jsDownload = createSemanticDownloaderPre({ modelsRoot: userModelsRoot })
|
|
4833
4919
|
// 资产探测(semantic-status / 档位解析 / 引导卡共用):共享实现见 semantic-js-pre 的
|
|
4834
4920
|
// probeJsSemanticAssets —— peer 探测与引擎加载走同一套 Node 解析(2026-09-02 issue 修正)。
|
|
4835
4921
|
// _peerExtraDirs = 深度扫描热接入位(semanticDeepDetect 命中后 probe/加载/档位即时生效)。
|
|
@@ -5458,7 +5544,8 @@ export function apply(ctx, config) {
|
|
|
5458
5544
|
handler: async (req, res) => {
|
|
5459
5545
|
if (!isLoopbackRequest(req)) return writeJson(res, 403, { error: 'forbidden: loopback-only' })
|
|
5460
5546
|
if ((req.method || 'POST') !== 'POST') return writeJson(res, 405, { error: 'method not allowed' })
|
|
5461
|
-
|
|
5547
|
+
const body = await readJsonBody(req).catch(() => ({}))
|
|
5548
|
+
try { writeJson(res, 200, await engine._pythonSetup.ensureDeps({ gpu: !!(body && body.gpu) })) } catch (e) { writeJson(res, 500, { error: String(e && e.message || e) }) }
|
|
5462
5549
|
},
|
|
5463
5550
|
},
|
|
5464
5551
|
{
|
package/lib/python-setup.js
CHANGED
|
@@ -35,8 +35,9 @@ const MODEL_SPEC = {
|
|
|
35
35
|
],
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
/** venv 内 pip
|
|
39
|
-
const
|
|
38
|
+
/** venv 内 pip 依赖:#19 实测(int8 档 encode_ids 全程 numpy+onnxruntime,tokenizer 走 Rust 快速路径)——基础集无 torch,venv 体积 ~400MB。GPU 推理开关追加 onnxruntime-gpu(CUDAExecutionProvider)。池化/精度契约冻结自 R@5 0.925 基线,fastembed 注册表无 BGE-M3 且池化契约不同,不可用。 */
|
|
39
|
+
const PIP_DEPS_CPU = ['transformers', 'onnxruntime']
|
|
40
|
+
const PIP_DEPS_GPU_EXTRA = ['onnxruntime-gpu']
|
|
40
41
|
|
|
41
42
|
export function createPythonSetupPre(opts = {}) {
|
|
42
43
|
const dshHomeOf = typeof opts.dshHome === 'function' ? opts.dshHome : () => opts.dshHome || path.join(homedir(), '.dsh')
|
|
@@ -110,7 +111,7 @@ export function createPythonSetupPre(opts = {}) {
|
|
|
110
111
|
return {
|
|
111
112
|
phase: st.phase, error: st.error,
|
|
112
113
|
pythons: st.pythons, chosenPython: st.chosenPython,
|
|
113
|
-
venvOk: st.venvOk, depsOk: st.depsOk, modelReady: st.modelReady,
|
|
114
|
+
venvOk: st.venvOk, depsOk: st.depsOk, modelReady: st.modelReady, wantGpu: !!st.wantGpu,
|
|
114
115
|
modelPath: modelPath(), venvPython: venvPython(),
|
|
115
116
|
modelBytes: st.modelReady ? statSync(modelPath()).size : 0, modelExpectedBytes: MODEL_SPEC.bytes,
|
|
116
117
|
dl: { ...st.dl },
|
|
@@ -136,14 +137,17 @@ export function createPythonSetupPre(opts = {}) {
|
|
|
136
137
|
return snapshot()
|
|
137
138
|
}
|
|
138
139
|
|
|
139
|
-
/** ③deps:venv 内 pip 装 transformers+onnxruntime
|
|
140
|
-
async function ensureDeps() {
|
|
140
|
+
/** ③deps:venv 内 pip 装 transformers+onnxruntime(CPU 基础集);opts.gpu=true 追加 onnxruntime-gpu(用户选 GPU 推理时)。官方源失败切清华镜像。 */
|
|
141
|
+
async function ensureDeps(opts2) {
|
|
142
|
+
const wantGpu = !!(opts2 && opts2.gpu)
|
|
141
143
|
if (!st.venvOk) { st.error = 'venv 未就绪,先执行 venv 步骤'; st.phase = 'error'; return snapshot() }
|
|
142
144
|
st.phase = 'deps'
|
|
145
|
+
st.wantGpu = wantGpu
|
|
146
|
+
const deps = PIP_DEPS_CPU.concat(wantGpu ? PIP_DEPS_GPU_EXTRA : [])
|
|
143
147
|
const runPip = async (extra) => {
|
|
144
148
|
try {
|
|
145
|
-
const r = await execFileP(venvPython(), ['-m', 'pip', 'install', '--quiet'].concat(
|
|
146
|
-
{ timeout:
|
|
149
|
+
const r = await execFileP(venvPython(), ['-m', 'pip', 'install', '--quiet'].concat(deps).concat(extra || []),
|
|
150
|
+
{ timeout: 900000, windowsHide: true, maxBuffer: 4 * 1024 * 1024 })
|
|
147
151
|
return { ok: true }
|
|
148
152
|
} catch (e) { return { ok: false, tail: String((e && (e.stderr || e.message)) || e).slice(-300) } }
|
|
149
153
|
}
|
|
@@ -151,7 +155,18 @@ export function createPythonSetupPre(opts = {}) {
|
|
|
151
155
|
if (!r.ok) r = await runPip(['-i', 'https://pypi.tuna.tsinghua.edu.cn/simple'])
|
|
152
156
|
st.depsOk = !!r.ok
|
|
153
157
|
if (!r.ok) { st.error = '依赖安装失败: ' + (r.tail || ''); st.phase = 'error'; return snapshot() }
|
|
154
|
-
|
|
158
|
+
// GPU 偏好写入 embedding-config.json(worker 读 config['gpu'] 选 CUDA/CPU provider;read-modify-write 不覆盖既有键)
|
|
159
|
+
try {
|
|
160
|
+
const cfgDir = path.join(dshHomeOf(), 'memory', 'semantic')
|
|
161
|
+
mkdirSync(cfgDir, { recursive: true })
|
|
162
|
+
const cfgPath = path.join(cfgDir, 'embedding-config.json')
|
|
163
|
+
let cfg = {}
|
|
164
|
+
try { cfg = JSON.parse(await readFile(cfgPath, 'utf8')) } catch (_) {}
|
|
165
|
+
if (!cfg || typeof cfg !== 'object') cfg = {}
|
|
166
|
+
cfg.gpu = wantGpu
|
|
167
|
+
await writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8')
|
|
168
|
+
} catch (eCfg) { diagOf('python-setup: gpu pref write failed: ' + String(eCfg && eCfg.message || eCfg)) }
|
|
169
|
+
diagOf('python-setup: deps installed into venv (gpu=' + wantGpu + ')')
|
|
155
170
|
return snapshot()
|
|
156
171
|
}
|
|
157
172
|
|