@a9i5k4/dsh-auto-memory 2.1.8 → 2.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
- # dsh-auto-memory — She remembers, unbidden
1
+ # dsh-auto-memory — She remembers, unbidden · 无问自忆
2
2
 
3
- > **Now, across windows, too.**
4
- > Context that survives windows, sessions, and tools
3
+ > **EN** Now, across windows, too. Context that survives windows, sessions, and tools
4
+ > **中文** 无问自忆——该想起的,自己浮现。跨窗口 · 跨会话 · 跨工具,记忆不断线
5
5
 
6
6
  <p align="center">
7
7
  <a href="https://htmlpreview.github.io/?https://github.com/Aik358/dsh-auto-memory/blob/preview/docs/landing/index.html"><strong>🌐 Landing page (full feature tour · data flow · papers · screenshots)</strong></a>
package/README.zh-CN.md CHANGED
@@ -1,7 +1,7 @@
1
- # dsh-auto-memory — 她记得,不必吩咐
1
+ # dsh-auto-memory — 无问自忆 · She remembers, unbidden
2
2
 
3
- > **现在,换窗口也不必。**
4
- > 跨窗口 · 跨会话 · 跨工具,记忆不断线
3
+ > **中文** 现在,换窗口也不必。跨窗口 · 跨会话 · 跨工具,记忆不断线
4
+ > **EN** She remembers, unbidden — now, across windows, too. Context that survives windows, sessions, and tools
5
5
 
6
6
  <p align="center">
7
7
  <a href="https://htmlpreview.github.io/?https://github.com/Aik358/dsh-auto-memory/blob/preview/docs/landing/index.html"><strong>🌐 宣传主页(功能全景 · 数据流 · 论文 · 截图)</strong></a>
package/lib/client.js CHANGED
@@ -262,6 +262,14 @@ window.__ModuleLoader__.load({
262
262
  fWaterWindow: '水位估计窗口(token)', fWaterWindowHint: '会话消息按官方公式(4 字符≈1 token)估算达到该值即视为上下文将满;0=关闭。默认 65536(128K 窗口的保守半量),按所用模型调整。',
263
263
  fWaterAdvisory: '水位交接建议', fWaterAdvisoryHint: '水位越阈时在动态快照注入交接建议(写账本/刷新白板/建议开新窗);无人值守时静默。',
264
264
  fWaterAuto: '水位自动骨架账本', fWaterAutoHint: '越阈时自动写一篇系统骨架账本(每会话一次),防止模型忽视建议时交接材料缺失。',
265
+ fWaterThreshold: '水位建议阈值', fWaterThresholdHint: '水位比例超过该值(0.1-1.5)即触发交接建议/骨架账本;默认 0.8。',
266
+ fHandoffPlan: '白板注入预算(字符)', fHandoffPlanHint: 'PLAN.md 全貌注入动态快照的硬截断预算;全文可经 memory_read 或面板白板页查看。默认 1200。',
267
+ fHandoffLedger: '账本注入预算(字符)', fHandoffLedgerHint: '最新一篇交接账本注入动态快照的硬截断预算。默认 800。',
268
+ fConsSchedule: '定时做梦式固化', fConsScheduleHint: '每天到点自动读最近日志发散提炼长期要点,写入项目笔记/用户级记忆(等价 memory_consolidate)。',
269
+ fConsScheduleTime: '固化触发时间', fConsScheduleTimeHint: 'HH:MM,插件日界内每天一次;默认 09:30。命中时刻需宿主在线。',
270
+ fConsScheduleDays: '固化回看天数', fConsScheduleDaysHint: '定时固化读取最近 N 天日志;默认 7。',
271
+ fMaintSchedule: '定时 30 天蒸馏', fMaintScheduleHint: '每天到点自动把超过 30 天的旧日志蒸馏归档(等价 memory_maintain);无旧日志时零成本跳过。',
272
+ fMaintScheduleTime: '蒸馏触发时间', fMaintScheduleTimeHint: 'HH:MM,默认 10:00(与固化时间错开)。',
265
273
  waterCardTitle: '上下文水位', waterAutoSrc: '自动检测', waterManualSrc: '手动设定', waterThresholdHint: '水位达到 {t} 时注入交接建议并自动补写账本;窗口大小自动取自当前模型(settings.yaml contextWindow),也可在下方手动覆盖。',
266
274
  fStyle: '反思风格', fStyleHint: '生活化 / 专业性 / 由内容决定。',
267
275
  fLocale: '界面语言', fLocaleHint: '默认跟随 DSH 系统语言;也可手动指定中文 / English。', followSystem: '跟随系统语言',
@@ -400,6 +408,14 @@ window.__ModuleLoader__.load({
400
408
  fWaterWindow: 'Water-level window (tokens)', fWaterWindowHint: 'Session messages priced with the official meter (4 chars ≈ 1 token); past this, context is considered nearly full; 0 disables. Default 65536 — a conservative half of a 128K window.',
401
409
  fWaterAdvisory: 'Water-level handoff advisory', fWaterAdvisoryHint: 'Injects a handoff advisory past the threshold (write ledger / refresh plan / suggest a new window); silent in unattended mode.',
402
410
  fWaterAuto: 'Auto skeleton ledger', fWaterAutoHint: 'Past the threshold, auto-writes one system skeleton ledger per session as a safety net if the model ignores the advisory.',
411
+ fWaterThreshold: 'Water-level threshold', fWaterThresholdHint: 'Inject the handoff advisory / skeleton ledger once the ratio passes this value (0.1-1.5); default 0.8.',
412
+ fHandoffPlan: 'Plan injection budget (chars)', fHandoffPlanHint: 'Hard truncation budget for injecting the PLAN.md snapshot into the dynamic memory; full text via memory_read or the Whiteboard tab. Default 1200.',
413
+ fHandoffLedger: 'Ledger injection budget (chars)', fHandoffLedgerHint: 'Hard truncation budget for injecting the latest handoff ledger. Default 800.',
414
+ fConsSchedule: 'Scheduled deep consolidation', fConsScheduleHint: 'Once a day at the set time, read recent logs and distill long-term value into project notes / user memory (same flow as memory_consolidate).',
415
+ fConsScheduleTime: 'Consolidation time', fConsScheduleTimeHint: 'HH:MM, once per plugin day; default 09:30. The host must be online at that moment.',
416
+ fConsScheduleDays: 'Consolidation lookback (days)', fConsScheduleDaysHint: 'How many recent days of logs the scheduled consolidation reads; default 7.',
417
+ fMaintSchedule: 'Scheduled 30-day distill', fMaintScheduleHint: 'Once a day at the set time, distill & archive logs older than 30 days (same flow as memory_maintain); zero cost when nothing to archive.',
418
+ fMaintScheduleTime: 'Distill time', fMaintScheduleTimeHint: 'HH:MM, default 10:00 (staggered from the consolidation time).',
403
419
  waterCardTitle: 'Context water level', waterAutoSrc: 'auto-detected', waterManualSrc: 'manual', waterThresholdHint: 'Past {t} of the window, the handoff advisory is injected and the skeleton ledger is backfilled; the window auto-follows the active model (settings.yaml contextWindow) or your manual override below.',
404
420
  fStyle: 'Reflection style', fStyleHint: 'Life-style / Professional / Auto.',
405
421
  fLocale: 'UI language', fLocaleHint: 'Follows the DSH system language by default; you can also pin Chinese / English.', followSystem: 'Follow system language',
@@ -480,6 +496,17 @@ window.__ModuleLoader__.load({
480
496
 
481
497
  // ───────────────────────── 更新弹窗 / 首次指导 ─────────────────────────
482
498
  var CHANGELOG = {
499
+ '2.2.0': { zh: [
500
+ '★ 外部记忆继承扩展:新增 ZCode / Kimi Code / TRAE 三个来源——自动扫描常见数据目录(~/.zcode、~/.kimi(.kimi-code)、~/.trae 及工作区 .trae/rules),目录存在即出现在「接续」页签,一键链接导入;沿用链接模式,只注入绝对路径,不灌内容。',
501
+ '★ 定时记忆固化:新增「定时做梦式固化」(默认每天 09:30,读最近 7 天日志提炼长期要点)与「定时 30 天蒸馏」(默认 10:00,无旧日志时零成本跳过)——设置 → 自动化 可调。',
502
+ '设置页新增「上下文管理」分区:交接白板/水位感知全套设置归拢一处,并新暴露白板注入预算、账本注入预算、水位阈值三个字段。',
503
+ 'README 中英双语主副标题;动态快照的外部记忆源展示上限 3 → 6。',
504
+ ], en: [
505
+ '★ External memory expanded: three new sources — ZCode / Kimi Code / TRAE. Common data directories (~/.zcode, ~/.kimi(.kimi-code), ~/.trae and workspace .trae/rules) are auto-scanned; sources appear in the Connect tab when present and import as pure path links — link mode, no content dumping.',
506
+ '★ Scheduled consolidation: "deep consolidation" (daily at 09:30 by default, distills long-term value from the last 7 days of logs) and "30-day distill" (10:00, zero cost when nothing to archive) — Settings → Automation.',
507
+ 'Settings gains a "Context management" section: handoff whiteboard + water-level settings in one place, with plan/ledger injection budgets and the water threshold newly exposed.',
508
+ 'Bilingual (EN/中文) main title & subtitle in the READMEs; dynamic snapshot now lists up to 6 external memory sources (was 3).',
509
+ ] },
483
510
  '2.1.0': { zh: [
484
511
  '★ 新增「交接白板」:模型理解项目全貌后自动写 PLAN.md 白板(旧版自动归档),阶段产出写四段式交接账本——任务状态/目标/已试方案与失败原因/进度与下一步;面板新增「白板」页签实时查看。上下文窗口换页不丢线索,对标 GPT-6 Astra 的上下文管理。',
485
512
  '★ 新增「水位感知与交接助产」:按官方公式估算会话 token,窗口自动取自当前模型(如 1M);水位越阈自动注入交接建议并补写账本;memory_recall 新增 handoff/sessions 直达;无人值守静默。',
@@ -3062,6 +3089,7 @@ window.__ModuleLoader__.load({
3062
3089
  : 'The built-in JS semantic model (~135MB, one-time) can be downloaded in the background; the semantic tier enables itself once done:'))
3063
3090
  sdActions.push(h('button', { 'data-dam-btn': '', 'data-primary': 'true', style: close, onClick: function () {
3064
3091
  apiPost(API.semanticDownload, { action: 'start', mirror: 'auto' }).catch(function () {})
3092
+ try { localStorage.setItem('dsh-auto-memory.semDetectSnoozeUntil', String(Date.now() + 24 * 3600 * 1000)) } catch (eSn2) {}
3065
3093
  closeDialog()
3066
3094
  } }, sdZh ? '立即下载' : 'Download now'))
3067
3095
  }
@@ -3339,6 +3367,7 @@ window.__ModuleLoader__.load({
3339
3367
  memoryHub: locale === 'zh' ? '记忆中枢' : 'Memory Hub',
3340
3368
  appearance: locale === 'zh' ? '外观' : 'Appearance', storage: locale === 'zh' ? '存储' : 'Storage',
3341
3369
  injection: locale === 'zh' ? '记忆窗口' : 'Memory window', automation: locale === 'zh' ? '自动化' : 'Automation',
3370
+ context: locale === 'zh' ? '上下文管理' : 'Context management',
3342
3371
  maintenance: locale === 'zh' ? '维护' : 'Maintenance'
3343
3372
  }
3344
3373
  function section(key, title, content) { return h('section', { id: 'dam-settings-' + key, 'data-dam-settings-group': '' }, h('h3', null, title), content) }
@@ -3533,10 +3562,11 @@ window.__ModuleLoader__.load({
3533
3562
  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')),
3534
3563
  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')),
3535
3564
  field(t('fReflect'), h('input', { type: 'checkbox', checked: !!cfg.reflectEnabled, onChange: function (e) { set('reflectEnabled', e.target.checked) } }), t('fReflectHint')),
3536
- field(t('fHandoff'), h('input', { type: 'checkbox', checked: cfg.handoffEnabled !== false, onChange: function (e) { set('handoffEnabled', e.target.checked) } }), t('fHandoffHint')),
3537
- field(t('fWaterWindow'), h('input', { 'data-dam-input': '', type: 'number', min: 0, value: cfg.waterLevelWindowTokens === undefined ? 65536 : cfg.waterLevelWindowTokens, onChange: function (e) { set('waterLevelWindowTokens', Number(e.target.value) || 0) } }), t('fWaterWindowHint')),
3538
- field(t('fWaterAdvisory'), h('input', { type: 'checkbox', checked: cfg.waterLevelAdvisory !== false, onChange: function (e) { set('waterLevelAdvisory', e.target.checked) } }), t('fWaterAdvisoryHint')),
3539
- field(t('fWaterAuto'), h('input', { type: 'checkbox', checked: cfg.waterLevelAutoHandoff !== false, onChange: function (e) { set('waterLevelAutoHandoff', e.target.checked) } }), t('fWaterAutoHint')),
3565
+ field(t('fConsSchedule'), h('input', { type: 'checkbox', checked: cfg.consolidateScheduleEnabled !== false, onChange: function (e) { set('consolidateScheduleEnabled', e.target.checked) } }), t('fConsScheduleHint')),
3566
+ field(t('fConsScheduleTime'), h('input', { 'data-dam-input': '', placeholder: '09:30', value: cfg.consolidateScheduleTime === undefined ? '09:30' : cfg.consolidateScheduleTime, onChange: function (e) { set('consolidateScheduleTime', String(e.target.value || '').trim()) } }), t('fConsScheduleTimeHint')),
3567
+ field(t('fConsScheduleDays'), h('input', { 'data-dam-input': '', type: 'number', min: 1, max: 60, value: cfg.consolidateScheduleDays === undefined ? 7 : cfg.consolidateScheduleDays, onChange: function (e) { set('consolidateScheduleDays', Number(e.target.value) || 7) } }), t('fConsScheduleDaysHint')),
3568
+ field(t('fMaintSchedule'), h('input', { type: 'checkbox', checked: cfg.maintainScheduleEnabled !== false, onChange: function (e) { set('maintainScheduleEnabled', e.target.checked) } }), t('fMaintScheduleHint')),
3569
+ field(t('fMaintScheduleTime'), h('input', { 'data-dam-input': '', placeholder: '10:00', value: cfg.maintainScheduleTime === undefined ? '10:00' : cfg.maintainScheduleTime, onChange: function (e) { set('maintainScheduleTime', String(e.target.value || '').trim()) } }), t('fMaintScheduleTimeHint')),
3540
3570
  field(t('fStyle'), h('select', { 'data-dam-select': '', value: cfg.reflectStyle, onChange: function (e) { set('reflectStyle', e.target.value) } },
3541
3571
  STYLE_IDS.map(function (id) { return h('option', { key: id, value: id }, t('style' + id.charAt(0).toUpperCase() + id.slice(1))) })), t('fStyleHint')),
3542
3572
  field(locale === 'zh' ? '总结/问候默认模型' : 'Summary & greeting model', h('div', { 'data-dam-row': '', style: { flex: 1 } },
@@ -3545,6 +3575,13 @@ window.__ModuleLoader__.load({
3545
3575
  locale === 'zh' ? '用于时段总结、问候语、自动沉淀等 subagent 功能;从检测到的模型中选择,或留空跟随路由默认。保存后生效。' : 'For scheduled summaries, greetings and auto-consolidation subagents; pick a detected model or leave empty for the routing default. Applies after saving.'),
3546
3576
  mdlOpen ? buildModelDrawer() : null,
3547
3577
  ]),
3578
+ section('context', sectionLabels.context, [field(t('fHandoff'), h('input', { type: 'checkbox', checked: cfg.handoffEnabled !== false, onChange: function (e) { set('handoffEnabled', e.target.checked) } }), t('fHandoffHint')),
3579
+ field(t('fHandoffPlan'), h('input', { 'data-dam-input': '', type: 'number', min: 200, value: cfg.handoffPlanChars === undefined ? 1200 : cfg.handoffPlanChars, onChange: function (e) { set('handoffPlanChars', Number(e.target.value) || 1200) } }), t('fHandoffPlanHint')),
3580
+ field(t('fHandoffLedger'), h('input', { 'data-dam-input': '', type: 'number', min: 100, value: cfg.handoffLedgerChars === undefined ? 800 : cfg.handoffLedgerChars, onChange: function (e) { set('handoffLedgerChars', Number(e.target.value) || 800) } }), t('fHandoffLedgerHint')),
3581
+ field(t('fWaterWindow'), h('input', { 'data-dam-input': '', type: 'number', min: 0, value: cfg.waterLevelWindowTokens === undefined ? 65536 : cfg.waterLevelWindowTokens, onChange: function (e) { set('waterLevelWindowTokens', Number(e.target.value) || 0) } }), t('fWaterWindowHint')),
3582
+ field(t('fWaterThreshold'), h('input', { 'data-dam-input': '', type: 'number', min: 0.1, max: 1.5, step: 0.05, value: cfg.waterLevelThreshold === undefined ? 0.8 : cfg.waterLevelThreshold, onChange: function (e) { set('waterLevelThreshold', Number(e.target.value) || 0.8) } }), t('fWaterThresholdHint')),
3583
+ field(t('fWaterAdvisory'), h('input', { type: 'checkbox', checked: cfg.waterLevelAdvisory !== false, onChange: function (e) { set('waterLevelAdvisory', e.target.checked) } }), t('fWaterAdvisoryHint')),
3584
+ field(t('fWaterAuto'), h('input', { type: 'checkbox', checked: cfg.waterLevelAutoHandoff !== false, onChange: function (e) { set('waterLevelAutoHandoff', e.target.checked) } }), t('fWaterAutoHint'))]),
3548
3585
  section('maintenance', sectionLabels.maintenance, [field(t('fVersion'), h('div', { 'data-dam-row': '' },
3549
3586
  h('span', { style: { flex: 1 } }, verInfo ? (verInfo.current || '?') + (verInfo.latest ? ' → ' + verInfo.latest + (verInfo.upToDate ? ' ' + t('upToDate') : ' ' + t('hasUpdate')) : '') : (checkingUpdate ? t('checking') : '—')),
3550
3587
  h('button', { 'data-dam-btn': '', onClick: checkUpdate, disabled: checkingUpdate }, checkingUpdate ? t('checking') : t('checkUpdate')),
package/lib/index.js CHANGED
@@ -64,7 +64,7 @@ const SECTION_ORDER = 10000
64
64
  const NOTICES_URL = 'https://raw.githubusercontent.com/Aik358/dsh-auto-memory/main/notices.json'
65
65
 
66
66
  /** Model-facing announcement (tools + engine). */
67
- export const GUIDANCE = '本机已安装 dsh-auto-memory 插件(集中式自动记忆 + 外部记忆继承):三层本地记忆(用户级 ~/.dsh/memory/MEMORY.md、项目笔记与每日日志 .dsh-memory/)+ 会话自动注入 + 每日反思 + 其他 AI 工具记忆接入。能力:memory_log 追加今日日志(append-only,完成实质性工作后必须调用);memory_note 更新项目笔记;memory_user 更新用户级规则;memory_recall 检索本地记忆 + 外部记忆(WorkBuddy/CodeBuddy/Claude Code/Codex 历史会话与画像)+ 历史 DSH 会话;memory_external 查看/接入外部记忆源;memory_maintain 归档 30 天前日志;memory_reflect 保存每日反思;memory_status 查看状态;memory_consolidate 让 AI 读日志发散提炼长期要点固化进笔记。自动沉淀:每轮对话结束插件自动评估本轮内容并写今日日志/升格长期记忆(寒暄轮跳过,间隔与每日额度可在设置页「自动化」分组调整),无需你手动调 memory_log。主动性纪律:任务开始遇到不熟悉的代码/领域/历史决策时,先 memory_recall 检索本机全部 AI 工具历史,不凭空猜测;新工作区主动探索历史。限制:记忆文件为明文 Markdown;不存密钥除非用户明确要求;外部会话检索为关键词级(非语义);GUI 侧边栏「记忆」面板(含「接续」页签,可查看来源内容、从记忆 prompt 移除已导入段落)与设置页可查看/配置/接入。用户提到「记忆 / 昨天做了什么 / 之前怎么做的 / 每日反思 / 接续 / 其他 AI 的记忆」时即指本插件,请据此协作。'
67
+ export const GUIDANCE = '本机已安装 dsh-auto-memory 插件(集中式自动记忆 + 外部记忆继承):三层本地记忆(用户级 ~/.dsh/memory/MEMORY.md、项目笔记与每日日志 .dsh-memory/)+ 会话自动注入 + 每日反思 + 其他 AI 工具记忆接入。能力:memory_log 追加今日日志(append-only,完成实质性工作后必须调用);memory_note 更新项目笔记;memory_user 更新用户级规则;memory_recall 检索本地记忆 + 外部记忆(WorkBuddy/CodeBuddy/Claude Code/Codex/ZCode/Kimi Code/TRAE 记忆与会话)+ 历史 DSH 会话;memory_external 查看/接入外部记忆源;memory_maintain 归档 30 天前日志;memory_reflect 保存每日反思;memory_status 查看状态;memory_consolidate 让 AI 读日志发散提炼长期要点固化进笔记。自动沉淀:每轮对话结束插件自动评估本轮内容并写今日日志/升格长期记忆(寒暄轮跳过,间隔与每日额度可在设置页「自动化」分组调整),无需你手动调 memory_log。主动性纪律:任务开始遇到不熟悉的代码/领域/历史决策时,先 memory_recall 检索本机全部 AI 工具历史,不凭空猜测;新工作区主动探索历史。限制:记忆文件为明文 Markdown;不存密钥除非用户明确要求;外部会话检索为关键词级(非语义);GUI 侧边栏「记忆」面板(含「接续」页签,可查看来源内容、从记忆 prompt 移除已导入段落)与设置页可查看/配置/接入。用户提到「记忆 / 昨天做了什么 / 之前怎么做的 / 每日反思 / 接续 / 其他 AI 的记忆」时即指本插件,请据此协作。'
68
68
 
69
69
  /** Route family. */
70
70
  export const API = {
@@ -141,6 +141,16 @@ const DEFAULT_CONFIG = {
141
141
  autoConsolidateCooldownMinutes: 30,
142
142
  /** 自动沉淀每日最多调用次数(跨插件实例应只保留一个实例)。 */
143
143
  autoConsolidateDailyMax: 8,
144
+ /** 定时做梦式固化(默认开):每天到点自动跑一次 memory_consolidate 等价流程(读最近日志→发散提炼→项目笔记/用户记忆)。 */
145
+ consolidateScheduleEnabled: true,
146
+ /** 做梦式固化触发时间(HH:MM,插件日界内每天一次;命中时刻需宿主在线)。 */
147
+ consolidateScheduleTime: '09:30',
148
+ /** 做梦式固化回看天数(读最近 N 天日志)。 */
149
+ consolidateScheduleDays: 7,
150
+ /** 定时 30 天蒸馏(默认开):每天到点自动跑一次 memory_maintain 等价流程;无超过 30 天的旧日志时零成本跳过(不调 AI)。 */
151
+ maintainScheduleEnabled: true,
152
+ /** 30 天蒸馏触发时间(HH:MM)。 */
153
+ maintainScheduleTime: '10:00',
144
154
  /** M-CM1 交接白板:PLAN.md(项目全貌快照,模型重写+旧版归档)+四段式交接账本;注入动态快照首位。false=完全不读写。实验特性:出厂默认关闭,设置 → 自动化 → 交接白板 开启。 */
145
155
  handoffEnabled: false,
146
156
  /** 白板 PLAN.md 注入预算(字符,硬截断;全文可经 memory_read/文件读取)。 */
@@ -209,6 +219,12 @@ const DEFAULT_CONFIG = {
209
219
  'workbuddy-sessions': true,
210
220
  'claude-sessions': true,
211
221
  'codex-sessions': true,
222
+ // 2026-09-08 新增:ZCode / Kimi Code / TRAE(自动扫描,目录存在即出源;链接模式,只注入路径不注内容)
223
+ 'zcode-memory': true,
224
+ 'zcode-sessions': true,
225
+ 'kimi-global': true,
226
+ 'kimi-sessions': true,
227
+ 'trae-rules': true,
212
228
  },
213
229
  // ---------- 主动联想记忆实验基线(M0-R 恢复;全部默认关闭,关闭时现有行为零变化) ----------
214
230
  /** 主动联想记忆总开关(观察账本之外的检索/注入行为门)。 */
@@ -2335,6 +2351,78 @@ class MemoryEngine {
2335
2351
  return true
2336
2352
  }
2337
2353
 
2354
+ /**
2355
+ * F1(M6-CONTRACT 外补丁,2026-08-23):anchor 开启时的记录级压缩。
2356
+ * 按 sidecar 记录序整条保留/移除——今天(标题日期=插件今天)记录无条件保留,其余按字节配额
2357
+ * 从最新往回保留(layer 上限 user 4000/note 3000);被移除记录整条原文写入归档文件
2358
+ * (writeFullRaw,不经 anchor 事务,归档非检索语料)。重组经 store.replace 原子执行:
2359
+ * 候选预检(clean)→被移除 id 自动删除→保留 id/版本不变。永不字符切片。
2360
+ * @returns {Promise<boolean|'not-anchored'>} true=已腾位 false=无法腾位 'not-anchored'=无 sidecar 走旧路径
2361
+ */
2362
+ async compactAnchoredLayer(store, filePath, layer, today, paths) {
2363
+ const cur = await this.readTextSafe(filePath)
2364
+ if (!cur || !cur.trim()) return false
2365
+ let parsed
2366
+ try { parsed = parseAnchors(Buffer.from(cur, 'utf8')) } catch (_) { return 'not-anchored' }
2367
+ if (!parsed || parsed.status !== 'clean') return false // 脏文件 fail closed:不压缩、不切片
2368
+ const anchored = parsed.records.filter((r) => r.kind === 'anchored')
2369
+ if (!anchored.length) return 'not-anchored'
2370
+ const limit = layer === 'user' ? 4000 : 3000
2371
+ const isTodayHeading = (heading) => /^\d{4}-\d{2}-\d{2}/.test(String(heading || '')) && String(heading).slice(0, 10) === today
2372
+ const buf = Buffer.from(cur, 'utf8')
2373
+ // 配额从尾回溯(F1):新的留;今天记录无条件保留
2374
+ const keep = new Array(anchored.length).fill(false)
2375
+ let keptBytes = 0
2376
+ for (let i = anchored.length - 1; i >= 0; i--) {
2377
+ const rec = anchored[i]
2378
+ const spanBytes = rec.byteEnd - rec.markerByteStart
2379
+ if (isTodayHeading(rec.heading) || keptBytes + spanBytes <= limit) { keep[i] = true; keptBytes += spanBytes }
2380
+ }
2381
+ const removedIdx = []
2382
+ anchored.forEach((rec, i) => { if (!keep[i]) removedIdx.push(i) })
2383
+ if (!removedIdx.length) return false // 无可移除 → 无法腾位(预算照常拒绝)
2384
+ // 有序块重组:legacy 块与 preamble/tail 原样保留,gap 字节逐段拷贝,仅丢弃被移除记录的 [marker..contentEnd]
2385
+ const blocks = []
2386
+ for (const r of parsed.records) {
2387
+ if (r.kind === 'anchored') blocks.push({ start: r.markerByteStart, end: r.byteEnd, keep: keep[anchored.indexOf(r)] })
2388
+ else blocks.push({ start: r.byteStart, end: r.byteEnd, keep: true })
2389
+ }
2390
+ blocks.sort((a, b) => a.start - b.start)
2391
+ const parts = []
2392
+ let cursorB = 0
2393
+ for (let i = 0; i < blocks.length; i++) {
2394
+ const b = blocks[i]
2395
+ if (b.start > cursorB) parts.push(buf.slice(cursorB, b.start).toString('utf8'))
2396
+ if (b.keep) parts.push(buf.slice(b.start, b.end).toString('utf8'))
2397
+ cursorB = b.end
2398
+ }
2399
+ if (cursorB < buf.length) parts.push(buf.slice(cursorB).toString('utf8'))
2400
+ const newText = parts.join('')
2401
+ // 归档:整条原文(writeFullRaw 绕开 anchor 事务;归档文件从不参与解析/索引)
2402
+ const archiveFile = layer === 'user'
2403
+ ? path.join(dshHome(), 'memory', 'archived-user.md')
2404
+ : path.join(paths.projectDir, 'archive', 'notes-archived.md')
2405
+ const archiveText = removedIdx.map((i2) => {
2406
+ const r = anchored[i2]
2407
+ return buf.slice(r.markerByteStart, r.byteEnd).toString('utf8')
2408
+ }).join('\n')
2409
+ const existingArchive = await this.readTextSafe(archiveFile)
2410
+ await this.writeFullRaw(archiveFile, (existingArchive ? existingArchive.replace(/\s+$/, '') + '\n' : '') + archiveText + '\n')
2411
+ const rr = await store.replace(filePath, newText)
2412
+ if (!rr.ok) { console.error('[compacted-anchored] replace failed: ' + rr.reason); return false }
2413
+ const freshText = await this.readTextSafe(filePath)
2414
+ if (layer === 'user') this.state.userText = freshText
2415
+ else this.state.notesText = freshText
2416
+ if (this._budgets) {
2417
+ const b = this._budgets.get('today')
2418
+ if (b && b.date === this.memToday()) b[layer] = 0
2419
+ }
2420
+ this._lastCompactAt = Date.now()
2421
+ this.state.loadedAt = Date.now()
2422
+ console.log('[compacted-anchored] ' + layer + ': kept=' + (anchored.length - removedIdx.length) + '/' + anchored.length + ' archived=' + removedIdx.length + ' bytes=' + cur.length + '->' + newText.length)
2423
+ return true
2424
+ }
2425
+
2338
2426
  // ---------- 注入渲染(同步,基于缓存) ----------
2339
2427
  // 动态记忆 → ctx.systemPrompt.context()(user-role 快照):内容变化才追加新快照,内容不变不重复注入(dsh-agent-loop project() 去重),
2340
2428
  // system prompt 不再包含动态内容 → 字节级稳定 → DeepSeek 前缀缓存全程命中(对比 section 方案:动态内容任何变化都从变化点起击穿整个前缀,含全部历史)
@@ -2381,6 +2469,17 @@ class MemoryEngine {
2381
2469
  if (cfg.waterLevelAdvisory !== false && !this.isUnattendedNow() && (s.waterLevelRatio || 0) >= (Number(cfg.waterLevelThreshold) || 0.8) && Date.now() - (s.waterLevelAt || 0) < 600000) {
2382
2470
  lines.push('\n' + L('snapshotWaterTitle') + '\n' + L('snapshotWaterBody', { pct: Math.round((s.waterLevelRatio || 0) * 100) }))
2383
2471
  }
2472
+ // M-CM1 交接白板:PLAN.md 全貌+最新交接账本,动态快照首位(动态层内容,不碰静态字节;敏感段清洗+行边界硬预算截断)
2473
+ if (cfg.handoffEnabled !== false && (s.planText || s.latestHandoffText)) {
2474
+ const planPart = truncateLinesBounded(stripSensitiveSections(sanitizeForInjection(s.planText || '')), Math.max(Number(cfg.handoffPlanChars) || 1200, 200))
2475
+ if (planPart) lines.push('\n' + L('snapshotPlanTitle') + '\n' + planPart)
2476
+ const ledgerPart = truncateLinesBounded(stripSensitiveSections(sanitizeForInjection(s.latestHandoffText || '')), Math.max(Number(cfg.handoffLedgerChars) || 800, 200))
2477
+ if (ledgerPart) lines.push('\n' + L('snapshotHandoffTitle') + '\n' + ledgerPart)
2478
+ }
2479
+ // M-CM4/M-CM5 水位建议:越阈注入交接+派子代理建议(advisory;10 分钟新鲜度;无人值守静默)
2480
+ if (cfg.waterLevelAdvisory !== false && !this.isUnattendedNow() && (s.waterLevelRatio || 0) >= (Number(cfg.waterLevelThreshold) || 0.8) && Date.now() - (s.waterLevelAt || 0) < 600000) {
2481
+ lines.push('\n' + L('snapshotWaterTitle') + '\n' + L('snapshotWaterBody', { pct: Math.round((s.waterLevelRatio || 0) * 100) }))
2482
+ }
2384
2483
  let used = 0
2385
2484
  const part = (title, text, max) => {
2386
2485
  if (!text) return
@@ -2411,7 +2510,7 @@ class MemoryEngine {
2411
2510
  const paths = (x.files || []).map((f) => f.path).slice(0, 2).join(' ; ')
2412
2511
  return '· ' + x.name + '(' + x.tool + '): 绝对路径 ' + (paths || '(未知)')
2413
2512
  })
2414
- .slice(0, 3)
2513
+ .slice(0, 6)
2415
2514
  if (ext.length) lines.push('\n' + L('snapshotExternalTitle') + '\n' + ext.join('\n') + '\n需要这些记忆时:直接读取上述绝对路径文件(你有文件读取能力),或用 memory_recall 按需检索;不要凭空猜测其内容。')
2416
2515
  const sess = this.external.cache.filter((x) => x.kind === 'sessions')
2417
2516
  if (sess.length) {
@@ -2447,7 +2546,7 @@ class MemoryEngine {
2447
2546
  lines.push('思维链=本轮推理(用完即焚);铭文=落盘的记忆文件(跨会话永久)。你的记忆更新必须落在铭文层——显式调用工具写盘,不能只"想过"。')
2448
2547
  lines.push('本提醒由框架在每一轮对话开始时重新注入(记忆动态快照变化即刷新,衰减期只有一轮):读写只走工具、路径写死、每轮结束框架自动评估沉淀兜底——记忆无法被绕过,也不依赖自觉。')
2449
2548
  lines.push('\n[记忆写入纪律 — 必须遵守]')
2450
- lines.push('- 会话开始:若任务与历史工作/历史决策相关,先回顾以上记忆;**遇到不熟悉的代码、领域或项目时,主动调用 memory_recall 检索本机所有 AI 工具的历史记忆(WorkBuddy/CodeBuddy/Claude Code/Codex 会话),或直接读取外部记忆标注的绝对路径文件,不要凭空猜测**。')
2549
+ lines.push('- 会话开始:若任务与历史工作/历史决策相关,先回顾以上记忆;**遇到不熟悉的代码、领域或项目时,主动调用 memory_recall 检索本机所有 AI 工具的历史记忆(WorkBuddy/CodeBuddy/Claude Code/Codex/ZCode/Kimi Code/TRAE 会话),或直接读取外部记忆标注的绝对路径文件,不要凭空猜测**。')
2451
2550
  lines.push('- 新工作区(无历史日志/笔记):主动用 memory_recall 探索本机历史,判断该项目是否曾在其他 AI 工具中工作过;也可调用 memory_external 查看并接入外部记忆;检索时在正文中说明"我先查一下之前的记录"。')
2452
2551
  lines.push('- 完成实质性工作后立即调用 memory_log 追加今日日志(append-only,绝不覆盖):建/改应用、修 bug、写文档、重构、技术选型、用户约定或偏好。')
2453
2552
  lines.push('- progress 与 memory 一起写:写日志的同时,把有跨会话长期价值的内容一并写入记忆——跨项目规则 → memory_user,仅本项目 → memory_note;两者在同一轮完成,互不冲突、不遗漏。')
@@ -3276,6 +3375,41 @@ class MemoryEngine {
3276
3375
  }
3277
3376
  }
3278
3377
  }
3378
+ // 4) 定时做梦式固化 + 定时 30 天蒸馏(2026-09-08):模式同自动总结——命中 HH:MM 且当天未跑过才触发;
3379
+ // subagent 需要 agent 承载(取 _lastAgent,tickTime 前面 restoreLastAgent 已兜底恢复),没有则本分钟跳过,下个 tick 重试。
3380
+ const nowS = new Date()
3381
+ const hhmmS = String(nowS.getHours()).padStart(2, '0') + ':' + String(nowS.getMinutes()).padStart(2, '0')
3382
+ const todayS = this.memToday()
3383
+ if (this.config.consolidateScheduleEnabled !== false) {
3384
+ const ct = String(this.config.consolidateScheduleTime || '09:30').trim()
3385
+ const cKey = todayS + '|consolidate|' + ct
3386
+ if (ct && hhmmS === ct && this._consolidateScheduleDone !== cKey && !this._scheduleBusy) {
3387
+ const agentC = this._lastAgent
3388
+ if (agentC && agentC.session) {
3389
+ this._consolidateScheduleDone = cKey
3390
+ this._scheduleBusy = true
3391
+ void this.consolidateMemory(agentC, Math.max(1, Number(this.config.consolidateScheduleDays) || 7))
3392
+ .then((msg) => { diag('scheduled consolidate: ' + String(msg || '').split('\n')[0].slice(0, 120)) })
3393
+ .catch(() => {})
3394
+ .finally(() => { this._scheduleBusy = false })
3395
+ }
3396
+ }
3397
+ }
3398
+ if (this.config.maintainScheduleEnabled !== false) {
3399
+ const mt = String(this.config.maintainScheduleTime || '10:00').trim()
3400
+ const mKey = todayS + '|maintain|' + mt
3401
+ if (mt && hhmmS === mt && this._maintainScheduleDone !== mKey && !this._scheduleBusy) {
3402
+ const agentM = this._lastAgent
3403
+ if (agentM && agentM.session) {
3404
+ this._maintainScheduleDone = mKey
3405
+ this._scheduleBusy = true
3406
+ void this.maintain(30, agentM)
3407
+ .then((msg) => { diag('scheduled maintain: ' + String(msg || '').split('\n')[0].slice(0, 120)) })
3408
+ .catch(() => {})
3409
+ .finally(() => { this._scheduleBusy = false })
3410
+ }
3411
+ }
3412
+ }
3279
3413
  } catch (e) {}
3280
3414
  }
3281
3415
 
@@ -4061,6 +4195,30 @@ class ExternalMemory {
4061
4195
  return out.slice(0, limit)
4062
4196
  }
4063
4197
 
4198
+ /** 递归收集某目录下的 markdown 记忆文件(按 mtime 取最新 N 个)——ZCode/TRAE 等 2026-09-08 新增。 */
4199
+ async listMdFiles(rootDir, limit = 12, maxDepth = 4) {
4200
+ const out = []
4201
+ const walk = async (dir, depth) => {
4202
+ if (depth > maxDepth || out.length >= limit * 3) return
4203
+ let entries
4204
+ try { entries = await readdir(dir, { withFileTypes: true }) } catch (e) { return }
4205
+ for (const e of entries) {
4206
+ if (out.length >= limit * 3) return
4207
+ const full = path.join(dir, e.name)
4208
+ if (e.isDirectory()) await walk(full, depth + 1)
4209
+ else if (e.isFile() && e.name.toLowerCase().endsWith('.md')) {
4210
+ try {
4211
+ const info = await stat(full)
4212
+ out.push({ path: full, size: info.size, mtime: info.mtimeMs })
4213
+ } catch (err) {}
4214
+ }
4215
+ }
4216
+ }
4217
+ await walk(rootDir, 0)
4218
+ out.sort((a, b) => b.mtime - a.mtime)
4219
+ return out.slice(0, limit)
4220
+ }
4221
+
4064
4222
  /** 从单个 jsonl 会话文件提取可检索文本(行数/字节上限,防重)。 */
4065
4223
  async extractSessionText(file, maxLines = 400, maxChars = 60000) {
4066
4224
  let text = ''
@@ -4117,7 +4275,12 @@ class ExternalMemory {
4117
4275
  }
4118
4276
  const pushSessions = async (id, name, tool, rootDir) => {
4119
4277
  if (!this.enabled(id)) return
4120
- const files = await this.listSessionFiles(rootDir)
4278
+ // 2026-09-08:支持候选目录数组(如 Kimi Code 的 .kimi / .kimi-code 变体),存在且非空的才收集
4279
+ const roots = Array.isArray(rootDir) ? rootDir : [rootDir]
4280
+ let files = []
4281
+ for (const r of roots) files = files.concat(await this.listSessionFiles(r))
4282
+ files.sort((a, b) => b.mtime - a.mtime)
4283
+ files = files.slice(0, 20)
4121
4284
  if (!files.length) return
4122
4285
  srcs.push({
4123
4286
  id, name, tool, kind: 'sessions', files,
@@ -4144,6 +4307,22 @@ class ExternalMemory {
4144
4307
  await pushSessions('workbuddy-sessions', 'WorkBuddy 历史会话', 'WorkBuddy', path.join(home, '.workbuddy', 'projects'))
4145
4308
  await pushSessions('claude-sessions', 'Claude Code 历史会话', 'Claude Code', path.join(home, '.claude', 'projects'))
4146
4309
  await pushSessions('codex-sessions', 'Codex 历史会话', 'Codex', path.join(home, '.codex', 'sessions'))
4310
+ // —— 2026-09-08 新增:ZCode / Kimi Code / TRAE(自动扫描,目录存在即出源;链接模式,只注入路径不注内容) ——
4311
+ const zcodeMds = await this.listMdFiles(path.join(home, '.zcode', 'cli', 'memories', 'projects'), 12)
4312
+ await pushMd('zcode-memory', 'ZCode 项目记忆', 'ZCode', 'project', zcodeMds.map((f) => f.path))
4313
+ await pushSessions('zcode-sessions', 'ZCode 历史会话', 'ZCode', path.join(home, '.zcode', 'cli', 'rollout'))
4314
+ await pushMd('kimi-global', 'Kimi Code 全局记忆', 'Kimi Code', 'user', [
4315
+ path.join(home, '.kimi', 'MEMORY.md'), path.join(home, '.kimi-code', 'MEMORY.md'),
4316
+ path.join(home, '.kimi-code', 'memory', 'MEMORY.md'), path.join(home, '.kimicode', 'MEMORY.md'),
4317
+ ])
4318
+ await pushSessions('kimi-sessions', 'Kimi Code 历史会话', 'Kimi Code', [
4319
+ path.join(home, '.kimi', 'sessions'), path.join(home, '.kimi-code', 'sessions'),
4320
+ ])
4321
+ const traeRules = [
4322
+ ...(await globOne(path.join(ws, '.trae', 'rules'), /\.(mdc|md)$/, 10)),
4323
+ ...(await this.listMdFiles(path.join(home, '.trae', 'rules'), 10, 2)).map((f) => f.path),
4324
+ ]
4325
+ await pushMd('trae-rules', 'TRAE 规则', 'TRAE', 'project', traeRules)
4147
4326
 
4148
4327
  this.cache = srcs
4149
4328
  this.cachedAt = Date.now()
@@ -4829,7 +5008,10 @@ export function apply(ctx, config) {
4829
5008
  {
4830
5009
  const pluginDir = path.dirname(fileURLToPath(import.meta.url))
4831
5010
  engine._jsSemantic = createJsSemanticEnginePre({ pluginDir })
4832
- engine._jsDownload = createSemanticDownloaderPre({ modelsRoot: path.join(pluginDir, 'models') })
5011
+ // #15 后续/B:JS 模型下载落位用户目录(~/.dsh/models/js-semantic/)——包目录在 npm 更新时被重装,130MB 曾被冲掉
5012
+ const userModelsRoot = path.join((process.env.DSH_HOME && process.env.DSH_HOME.trim()) || path.join(homedir(), '.dsh'), 'models', 'js-semantic')
5013
+ mkdirSync(userModelsRoot, { recursive: true })
5014
+ engine._jsDownload = createSemanticDownloaderPre({ modelsRoot: userModelsRoot })
4833
5015
  // 资产探测(semantic-status / 档位解析 / 引导卡共用):共享实现见 semantic-js-pre 的
4834
5016
  // probeJsSemanticAssets —— peer 探测与引擎加载走同一套 Node 解析(2026-09-02 issue 修正)。
4835
5017
  // _peerExtraDirs = 深度扫描热接入位(semanticDeepDetect 命中后 probe/加载/档位即时生效)。
@@ -5362,14 +5544,14 @@ export function apply(ctx, config) {
5362
5544
  text: { type: 'string', required: true, description: '完整反思内容:成果回顾 / 教训改进 / 今日可延续要点。' },
5363
5545
  }, async (args, exec) => engine.saveReflection(args.date, args.text, exec.agent)),
5364
5546
 
5365
- defineTool('memory_external', '查看/接入其他 AI 工具(AI 助手/CodeBuddy/Claude Code/Codex/项目约定文件)的记忆。action=list 列出全部检测到的外部记忆源(路径/大小/预览/会话数);action=import 以纯链接模式接入(source 为源 id,target=project 接进项目笔记 / user 接进用户级记忆,只记录源文件路径指针、不写入内容,需要时按需读取;防止外部脏内容混入本地记忆)。首次在新工作区工作、或用户提到其他软件里做过的事时调用。', {
5547
+ defineTool('memory_external', '查看/接入其他 AI 工具(AI 助手/CodeBuddy/Claude Code/Codex/ZCode/Kimi Code/TRAE/项目约定文件)的记忆。action=list 列出全部检测到的外部记忆源(路径/大小/预览/会话数);action=import 以纯链接模式接入(source 为源 id,target=project 接进项目笔记 / user 接进用户级记忆,只记录源文件路径指针、不写入内容,需要时按需读取;防止外部脏内容混入本地记忆)。首次在新工作区工作、或用户提到其他软件里做过的事时调用。', {
5366
5548
  action: { type: 'string', enum: ['list', 'import'], required: true, description: 'list=列出外部记忆源; import=接入指定源。' },
5367
5549
  source: { type: 'string', description: '要接入的源 id(action=import 时必填,来自 list 结果)。' },
5368
5550
  target: { type: 'string', enum: ['project', 'user'], description: '接入目标: project=项目笔记(默认), user=用户级记忆。' },
5369
5551
  }, async (args, exec) => {
5370
5552
  if (args.action === 'list') {
5371
5553
  const list = await engine.external.summarize()
5372
- if (!list.length) return '未检测到其他 AI 工具的记忆文件(可检查 ~/.workbuddy、~/.codebuddy、~/.claude、~/.codex 是否存在)。'
5554
+ if (!list.length) return '未检测到其他 AI 工具的记忆文件(可检查 ~/.workbuddy、~/.codebuddy、~/.claude、~/.codex、~/.zcode、~/.kimi 或 ~/.kimi-code、~/.trae 是否存在)。'
5373
5555
  const lines = []
5374
5556
  lines.push('检测到 ' + list.length + ' 个外部记忆源:')
5375
5557
  for (const s of list) {