@a9i5k4/dsh-auto-memory 0.1.28 → 0.1.29
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 +16 -0
- package/lib/index.js +29 -3
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -176,6 +176,7 @@ window.__ModuleLoader__.load({
|
|
|
176
176
|
fExtBudget: '外部记忆注入预算(字符)', fExtBudgetHint: '外部记忆来源在上下文中的注入预算。默认 1400(路径模式下影响有限)。',
|
|
177
177
|
fConsolidateMin: '自动沉淀内容门槛(字符)', fConsolidateMinHint: '本轮 user+assistant 总字符低于此值视为寒暄跳过。默认 240。',
|
|
178
178
|
fAway: '暂离阈值(分钟)', fAwayHint: '距上次活动超过该值视为暂离,回归时自动弹出记忆窗口。默认 60。',
|
|
179
|
+
fAutoPopup: '自动弹出记忆窗口', fAutoPopupHint: '暂离/回归时自动弹出记忆窗口(corner)并欢迎;关闭后只能手动打开。默认开。',
|
|
179
180
|
fAutoConsolidate: '自动沉淀(每轮对话结束AI评估)', fAutoConsolidateHint: '关闭后每轮对话结束不再自动调用 AI 评估与写入今日日志。',
|
|
180
181
|
fConsolidate: '自动沉淀间隔(分钟)', fConsolidateHint: '两轮自动沉淀之间的最短间隔。默认 30;非工作时间(22:00-08:00)自动翻倍,避免短时间耗尽每日额度。',
|
|
181
182
|
fConsolidateMax: '自动沉淀每日额度(次)', fConsolidateMaxHint: '每天最多触发自动沉淀的次数,到点后当天不再调用。默认 8。',
|
|
@@ -276,6 +277,7 @@ window.__ModuleLoader__.load({
|
|
|
276
277
|
fExtBudget: 'External memory injection budget (chars)', fExtBudgetHint: 'Budget for external memory sources in context. Default 1400 (limited effect with path mode).',
|
|
277
278
|
fConsolidateMin: 'Auto-consolidation content threshold (chars)', fConsolidateMinHint: 'Turns with fewer combined user+assistant chars are treated as chit-chat and skipped. Default 240.',
|
|
278
279
|
fAway: 'Away threshold (minutes)', fAwayHint: 'Marked away when inactive longer than this; the memory panel auto-opens on return. Default 60.',
|
|
280
|
+
fAutoPopup: 'Auto-open memory panel', fAutoPopupHint: 'Auto-open the memory panel (corner) with a welcome when returning from away; disabled = open manually only. Default on.',
|
|
279
281
|
fAutoConsolidate: 'Auto-consolidation (AI review each turn end)', fAutoConsolidateHint: 'When off, no automatic AI review or daily-log writes at turn end.',
|
|
280
282
|
fConsolidate: 'Auto-consolidate interval (minutes)', fConsolidateHint: 'Minimum gap between auto-consolidations. Default 30; doubled automatically outside work hours (22:00-08:00).',
|
|
281
283
|
fConsolidateMax: 'Daily auto-consolidate quota', fConsolidateMaxHint: 'Max auto-consolidation runs per day; no more runs after the quota is reached. Default 8.',
|
|
@@ -337,6 +339,7 @@ window.__ModuleLoader__.load({
|
|
|
337
339
|
// 暂离检测:优先用 host 定时检测的 away 状态(阈值可配 awayMinutes),回退本地 lastActive 旧逻辑
|
|
338
340
|
var hostAway = false
|
|
339
341
|
var hostAwayReady = false
|
|
342
|
+
var autoPopupEnabled = true
|
|
340
343
|
function isAway() {
|
|
341
344
|
if (hostAwayReady) return hostAway
|
|
342
345
|
var lastSeen = 0
|
|
@@ -346,6 +349,15 @@ window.__ModuleLoader__.load({
|
|
|
346
349
|
|
|
347
350
|
// ───────────────────────── 更新弹窗 / 首次指导 ─────────────────────────
|
|
348
351
|
var CHANGELOG = {
|
|
352
|
+
'0.1.29': { zh: [
|
|
353
|
+
'修复:工作区总览一直显示「未发现带记忆的工作区」——DSH 新版会话持久化改为 session.jsonl.zstd 压缩帧,旧逻辑只认 .jsonl 扫描不到任何会话;现已用 zstd 解压读取首行提取 cwd,并让空的 summary 缓存短时失效,避免空结果被永久固化。',
|
|
354
|
+
'新增:设置页「自动化」新增「自动弹出记忆窗口」开关——关闭后暂离/回归不再自动弹出 corner 问候栏,只能手动打开。',
|
|
355
|
+
'提示:这是近期稳定版(0.1.x 维护线)的发布。实验版(下一大版本)预计未来几周内发布,在此之前稳定版仅做维护性更新。',
|
|
356
|
+
], en: [
|
|
357
|
+
'Fix: Workspace overview kept showing "No memory workspaces found" — DSH now stores sessions as session.jsonl.zstd compressed frames that were not recognized; the overview now decompresses them to read the first line and extract each workspace, and empty summary caches expire instead of being fixed forever.',
|
|
358
|
+
'New: "Auto-open memory panel" toggle in Settings → Automation - turn it off to stop auto-opening the corner greeting on away/return; the panel stays manual-only.',
|
|
359
|
+
'Note: this is a recent stable-line (0.1.x maintenance) release. The experimental next major version is expected within a few weeks; until then the stable line only receives maintenance updates.',
|
|
360
|
+
] },
|
|
349
361
|
'0.1.28': { zh: [
|
|
350
362
|
'脏 token 检查器(prion-scan 四类启发式整合):mojibake GBK 残骸特征表补全至 34 项(与 prion-scan.mjs 逐字一致),写入闸门新增 raw JSON envelope(memoryBlock/"uid"/updatedAt/"role")与 base64 残骸行拒写——外部 AI 工具画像无法再整段混入。',
|
|
351
363
|
'新增「扫描脏 token」:设置页调试中心一键扫描用户级/项目笔记/每日日志/反思,按行区间返回 mojibake / raw JSON / 超长行 / base64 / 重复块报告(只给位置,不含正文)。',
|
|
@@ -2058,6 +2070,7 @@ window.__ModuleLoader__.load({
|
|
|
2058
2070
|
field(t('fAutoConsolidate'), h('input', { type: 'checkbox', checked: cfg.autoConsolidate !== false, onChange: function (e) { set('autoConsolidate', e.target.checked) } }), t('fAutoConsolidateHint')),
|
|
2059
2071
|
field(t('fConsolidate'), h('input', { 'data-dam-input': '', type: 'number', min: 5, value: cfg.autoConsolidateCooldownMinutes === undefined ? 30 : cfg.autoConsolidateCooldownMinutes, onChange: function (e) { set('autoConsolidateCooldownMinutes', Number(e.target.value) || 30) } }), t('fConsolidateHint')),
|
|
2060
2072
|
field(t('fConsolidateMax'), h('input', { 'data-dam-input': '', type: 'number', min: 1, max: 50, value: cfg.autoConsolidateDailyMax === undefined ? 8 : cfg.autoConsolidateDailyMax, onChange: function (e) { set('autoConsolidateDailyMax', Number(e.target.value) || 8) } }), t('fConsolidateMaxHint')),
|
|
2073
|
+
field(t('fAutoPopup'), h('input', { type: 'checkbox', checked: cfg.autoPopupEnabled !== false, onChange: function (e) { set('autoPopupEnabled', e.target.checked) } }), t('fAutoPopupHint')),
|
|
2061
2074
|
field(t('fAway'), h('input', { 'data-dam-input': '', type: 'number', min: 1, value: cfg.awayMinutes || 60, onChange: function (e) { set('awayMinutes', Number(e.target.value) || 60) } }), t('fAwayHint')),
|
|
2062
2075
|
field(t('fAutoSum'), h('input', { 'data-dam-input': '', value: (cfg.autoSummaryTimes || []).join(','), onChange: function (e) { set('autoSummaryTimes', String(e.target.value || '').split(',').map(function (s) { return s.trim() }).filter(Boolean)) } }), t('fAutoSumHint')),
|
|
2063
2076
|
field(t('fDayBoundary'), h('input', { 'data-dam-input': '', type: 'number', min: 0, max: 1439, value: (cfg.dayBoundaryMinutes === undefined ? 450 : cfg.dayBoundaryMinutes), onChange: function (e) { set('dayBoundaryMinutes', parseInt(e.target.value || '0', 10)) } }), t('fDayBoundaryHint')),
|
|
@@ -2109,6 +2122,7 @@ window.__ModuleLoader__.load({
|
|
|
2109
2122
|
applyLocalePref('system')
|
|
2110
2123
|
apiGet(API.config).then(function (d) {
|
|
2111
2124
|
if (d && d.config && d.config.locale) applyLocalePref(d.config.locale)
|
|
2125
|
+
if (d && d.config && typeof d.config.autoPopupEnabled === 'boolean') autoPopupEnabled = d.config.autoPopupEnabled
|
|
2112
2126
|
}).catch(function () {})
|
|
2113
2127
|
// DSH 系统语言变化:跟随更新 + 重渲染 + 重注册入口 label
|
|
2114
2128
|
ctx.on('locale/change', function (snap) {
|
|
@@ -2127,6 +2141,7 @@ window.__ModuleLoader__.load({
|
|
|
2127
2141
|
var prevAwayState = null
|
|
2128
2142
|
function autoOpenOnReturn() {
|
|
2129
2143
|
try {
|
|
2144
|
+
if (autoPopupEnabled === false) { prevAwayState = hostAwayReady ? hostAway : prevAwayState; return }
|
|
2130
2145
|
if (hostAwayReady && prevAwayState === true && hostAway === false) {
|
|
2131
2146
|
// 暂离回归:打开窗口 + 欢迎弹窗(若有待展示总结一并显示)
|
|
2132
2147
|
if (!controller.isOpen()) controller.open()
|
|
@@ -2193,6 +2208,7 @@ window.__ModuleLoader__.load({
|
|
|
2193
2208
|
hostAway = d.away
|
|
2194
2209
|
hostAwayReady = true
|
|
2195
2210
|
}
|
|
2211
|
+
if (d && typeof d.autoPopupEnabled === 'boolean') autoPopupEnabled = d.autoPopupEnabled
|
|
2196
2212
|
if (d && d.pendingSummary && d.pendingSummary.summary) {
|
|
2197
2213
|
var seenKey = 'dsh-auto-memory.seenSummary.' + (d.pendingSummary.date || '') + '.' + (d.pendingSummary.time || '')
|
|
2198
2214
|
try {
|
package/lib/index.js
CHANGED
|
@@ -25,6 +25,10 @@ const execP = promisify(cpExec)
|
|
|
25
25
|
import { homedir } from 'node:os'
|
|
26
26
|
import path from 'node:path'
|
|
27
27
|
import { fileURLToPath } from 'node:url'
|
|
28
|
+
import * as nodeZlib from 'node:zlib'
|
|
29
|
+
|
|
30
|
+
/** zstd 解压(DSH 新版会话持久化 session.jsonl.zstd);Node <22 无此能力时为空,自动回退明文读。 */
|
|
31
|
+
const zstdDec = typeof nodeZlib.zstdDecompressSync === 'function' ? nodeZlib.zstdDecompressSync : null
|
|
28
32
|
|
|
29
33
|
/** Stable cordis plugin name. */
|
|
30
34
|
export const name = 'auto-memory'
|
|
@@ -92,6 +96,8 @@ const DEFAULT_CONFIG = {
|
|
|
92
96
|
autoConsolidateDailyMax: 8,
|
|
93
97
|
/** 暂离阈值(分钟):距上次活动超过该值视为暂离,回归时自动弹出记忆窗口并欢迎。默认 60。 */
|
|
94
98
|
awayMinutes: 60,
|
|
99
|
+
/** 暂离回来自动弹出记忆窗口(corner/问候栏)开关:false=关闭,只能手动打开。默认 true。 */
|
|
100
|
+
autoPopupEnabled: true,
|
|
95
101
|
/** 自动总结时间点(24h "HH:MM" 数组,如 ["12:00","18:00","22:00"]):到点自动生成本时段总结并弹窗展示。空数组=关闭。 */
|
|
96
102
|
autoSummaryTimes: [],
|
|
97
103
|
/** 日界(分钟,从 0 点起算):凌晨在此之前的活儿归前一天日志;默认 450=早上 7:30 后才进入新一天。 */
|
|
@@ -1046,8 +1052,18 @@ class MemoryEngine {
|
|
|
1046
1052
|
}
|
|
1047
1053
|
|
|
1048
1054
|
// ---------- 工作区总览(跨工作区全局总结) ----------
|
|
1049
|
-
/** 读 jsonl 首行(session header)。 */
|
|
1055
|
+
/** 读 jsonl 首行(session header);.jsonl 明文流式读首行,.jsonl.zstd 为 zstd 压缩帧(整块解压后取首行,与 dsh 核心 dsh-session-persistence-jsonl 一致)。 */
|
|
1050
1056
|
readFirstLine(p) {
|
|
1057
|
+
if (p.endsWith('.zstd')) {
|
|
1058
|
+
return readFile(p).then((buf) => {
|
|
1059
|
+
try {
|
|
1060
|
+
const dec = zstdDec ? zstdDec(buf) : null
|
|
1061
|
+
const text = (dec || buf).toString('utf8')
|
|
1062
|
+
const i = text.indexOf('\n')
|
|
1063
|
+
return i >= 0 ? text.slice(0, i) : text
|
|
1064
|
+
} catch (e) { return '' }
|
|
1065
|
+
}).catch(() => '')
|
|
1066
|
+
}
|
|
1051
1067
|
return new Promise((resolve) => {
|
|
1052
1068
|
const rs = createReadStream(p, { encoding: 'utf8' })
|
|
1053
1069
|
let buf = ''
|
|
@@ -1072,7 +1088,7 @@ class MemoryEngine {
|
|
|
1072
1088
|
for (const en of entries) {
|
|
1073
1089
|
if (out.size >= 30) return
|
|
1074
1090
|
if (en.isDirectory()) await walk(path.join(dir, en.name), depth + 1)
|
|
1075
|
-
else if (en.isFile() && en.name.endsWith('.jsonl')) {
|
|
1091
|
+
else if (en.isFile() && (en.name.endsWith('.jsonl.zstd') || en.name.endsWith('.jsonl'))) {
|
|
1076
1092
|
try {
|
|
1077
1093
|
const first = await this.readFirstLine(path.join(dir, en.name))
|
|
1078
1094
|
if (first) {
|
|
@@ -1112,7 +1128,11 @@ class MemoryEngine {
|
|
|
1112
1128
|
const raw = await this.readTextSafe(cacheFile)
|
|
1113
1129
|
if (raw) {
|
|
1114
1130
|
const j = JSON.parse(raw)
|
|
1115
|
-
|
|
1131
|
+
// 空结果只短时信任(≤30 分钟),防止旧版 bug 产出的"空缓存"被永久复用导致永远显示"未发现带记忆的工作区"
|
|
1132
|
+
const ageMs = Date.now() - (Number(j && j.generatedAt) || 0)
|
|
1133
|
+
const list = j && Array.isArray(j.workspaces) ? j.workspaces : null
|
|
1134
|
+
const valid = !!list && (list.length > 0 || (ageMs >= 0 && ageMs <= 30 * 60 * 1000))
|
|
1135
|
+
if (valid && j.graph && Array.isArray(j.graph.topics) && Array.isArray(j.graph.links)) return { workspaces: list, graph: j.graph, cached: true, generatedAt: j.generatedAt }
|
|
1116
1136
|
}
|
|
1117
1137
|
} catch (e) {}
|
|
1118
1138
|
}
|
|
@@ -1125,6 +1145,11 @@ class MemoryEngine {
|
|
|
1125
1145
|
const fallbackItems = mem.logs.flatMap((l) => l.text.split('\n').filter((x) => x.trim().startsWith('- ')).map((x) => x.trim().replace(/^- /, '').slice(0, 120))).slice(-5)
|
|
1126
1146
|
return { path: cwd, name, input: { name, logs: logText.slice(0, 5200) || '(无)', notes: mem.notes || '(无)' }, fallbackItems, logCount: fallbackItems.length, dateRange: mem.logs.length ? (mem.logs[mem.logs.length - 1].date + ' ~ ' + mem.logs[0].date) : '' }
|
|
1127
1147
|
}))).filter(Boolean)
|
|
1148
|
+
if (!records.length) {
|
|
1149
|
+
const empty = { workspaces: [], graph: { topics: [], links: [] }, generatedAt: Date.now() }
|
|
1150
|
+
try { await mkdir(path.dirname(cacheFile), { recursive: true }); await writeFile(cacheFile, JSON.stringify(empty, null, 2), 'utf8') } catch (e) {}
|
|
1151
|
+
return { workspaces: [], graph: empty.graph, cached: false, generatedAt: empty.generatedAt }
|
|
1152
|
+
}
|
|
1128
1153
|
const fallback = records.map((r) => ({ path: r.path, name: r.name, summary: '', items: r.fallbackItems, graphTopics: r.fallbackItems.slice(0, 4).map((label) => ({ label, detail: '' })), logCount: r.logCount, dateRange: r.dateRange }))
|
|
1129
1154
|
let workspaces = fallback
|
|
1130
1155
|
let graph = { topics: [], links: [] }
|
|
@@ -1956,6 +1981,7 @@ class MemoryEngine {
|
|
|
1956
1981
|
// 时间检测:暂离状态 / 待展示的自动总结 / 相关配置
|
|
1957
1982
|
away: !!this.state.away,
|
|
1958
1983
|
awayMinutes: Math.max(Number(this.config.awayMinutes) || 60, 1),
|
|
1984
|
+
autoPopupEnabled: this.config.autoPopupEnabled !== false,
|
|
1959
1985
|
autoSummaryTimes: Array.isArray(this.config.autoSummaryTimes) ? this.config.autoSummaryTimes : [],
|
|
1960
1986
|
pendingSummary: this.state.pendingSummary || null,
|
|
1961
1987
|
autoStats: this.autoStats,
|
package/package.json
CHANGED