@a9i5k4/dsh-auto-memory 0.1.2 → 0.1.4

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,35 +1,36 @@
1
1
  # dsh-auto-memory — DSH Auto Memory Plugin / DSH 自动记忆插件
2
2
 
3
- An auto-memory plugin for the DeepSeek Harness Web GUI: three-layer memory (user-level / project notes / daily logs) with automatic injection and retrieval, daily reflections, a visual panel and settings page, plus inheritance of memories from other AI tools.
3
+ An auto-memory plugin for the DeepSeek Harness Web GUI: three-layer memory (user-level / project notes / daily logs) with automatic injection and retrieval, daily reflections, a visual panel and settings page, and inheritance of memories from other AI tools.
4
4
 
5
5
  DSH Web GUI 的记忆插件:三层记忆(用户级 / 项目笔记 / 每日日志)自动注入与检索、每日反思、可视化面板与设置页,支持继承其他 AI 工具的历史记忆。
6
6
 
7
+ [**English**](README.md) | [中文版](README.zh-CN.md)
8
+
7
9
  ---
8
10
 
9
- ## Features / 功能
11
+ ## Features
10
12
 
11
- | Layer / 层 | Location / 位置 | Description / 说明 |
13
+ | Layer | Location | Description |
12
14
  |---|---|---|
13
- | User-level memory / 用户级记忆 | `~/.dsh/memory/MEMORY.md` | Cross-project rules & preferences / 跨项目规则/偏好 |
14
- | Project notes / 项目笔记 | `{workspace}/.dsh-memory/MEMORY.md` | Project conventions & decisions / 项目长期约定、决策 |
15
- | Daily logs / 每日日志 | `{workspace}/.dsh-memory/YYYY-MM-DD.md` | Append-only work log / 每日工作日志 |
16
- | Reflections / 反思 | `{workspace}/.dsh-memory/reflections/YYYY-MM-DD.md` | Daily reflection, auto-prompted / 每日反思(自动提示生成) |
17
-
18
- - **Auto injection / 自动注入**: injects a `<memory_system>` block into every system prompt (user rules + project notes + recent reflections + recent N days of log tails + writing discipline)
19
- - **Daily reflection / 每日反思**: when yesterday has logs but no reflection, the agent is asked to present one at session start
20
- - **Agent tools / Agent 工具**: `memory_log` / `memory_note` / `memory_user` / `memory_recall` / `memory_external` / `memory_maintain` / `memory_status` / `memory_reflect`
21
- - **GUI**: sidebar 「记忆」entry → floating panel (Overview / Logs / Notes / Reflections / Connect / Search); Settings page (Settings → Auto Memory)
22
- - **External memory inheritance / 外部记忆继承**: import memories accumulated by other AI tools (CodeBuddy / Claude Code / Codex / project convention files)
15
+ | User-level memory | `~/.dsh/memory/MEMORY.md` | Cross-project rules & preferences |
16
+ | Project notes | `{workspace}/.dsh-memory/MEMORY.md` | Project conventions & decisions |
17
+ | Daily logs | `{workspace}/.dsh-memory/YYYY-MM-DD.md` | Append-only work log |
18
+ | Reflections | `{workspace}/.dsh-memory/reflections/YYYY-MM-DD.md` | Daily reflection, auto-prompted |
19
+
20
+ - **Auto injection**: injects a `<memory_system>` block into every system prompt (user rules + project notes + recent reflections + recent N days of log tails + writing discipline)
21
+ - **Daily reflection**: when yesterday has logs but no reflection, the agent presents one at session start
22
+ - **Agent tools**: `memory_log` / `memory_note` / `memory_user` / `memory_recall` / `memory_external` / `memory_maintain` / `memory_status` / `memory_reflect`
23
+ - **GUI**: sidebar 「Memory」entry → floating panel (Overview / Logs / Notes / Reflections / Connect / Search); Settings page (Settings → Auto Memory)
24
+ - **UI language**: switch between 中文 / English in Settings Auto Memory UI language
25
+ - **External memory inheritance**: import memories accumulated by other AI tools (CodeBuddy / Claude Code / Codex / project convention files)
23
26
 
24
27
  ---
25
28
 
26
- ## 🚀 Installation (one command) / 安装(NPM 一键)
29
+ ## Installation (one command)
27
30
 
28
31
  > Prerequisite: install [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) and start `dsh web` at least once.
29
- > 前提:已安装 DeepSeek Harness(dsh)并至少启动过一次 `dsh web`。
30
32
 
31
33
  Run in the **profile directory** (`~/.dsh/profiles/web`):
32
- 在 **profile 目录**(`~/.dsh/profiles/web`)下执行:
33
34
 
34
35
  ```bash
35
36
  cd ~/.dsh/profiles/web
@@ -37,43 +38,33 @@ pnpm add @a9i5k4/dsh-auto-memory
37
38
  ```
38
39
 
39
40
  Then edit `package.json` in that directory and append to the `dsh.profile.bundles` array:
40
- 然后编辑该目录下的 `package.json`,在 `dsh.profile.bundles` 数组里追加:
41
41
 
42
42
  ```json
43
43
  "@a9i5k4/dsh-auto-memory"
44
44
  ```
45
45
 
46
- Restart **dsh web** to activate (the 「记忆」entry appears in the sidebar).
47
- 保存后**重启 dsh web**,插件即生效(侧边栏出现「记忆」入口)。
46
+ Restart **dsh web** to activate (the 「Memory」entry appears in the sidebar).
48
47
 
49
48
  > No pnpm? Use npm: `npm install @a9i5k4/dsh-auto-memory`
50
- > 没有 pnpm?用 npm 也行:`npm install @a9i5k4/dsh-auto-memory`
51
49
 
52
50
  ---
53
51
 
54
- ## 🤖 AI-Era Installation (copy-paste for your AI) / AI 时代安装(把这句话直接丢给 AI)
52
+ ## AI-Era Installation (copy-paste for your AI)
55
53
 
56
54
  > It's the AI era — just copy the sentence below to your AI assistant (DeepSeek / Claude / Codex etc.) and it will do the installation for you.
57
- > 现在是 AI 时代,你可以直接把下面这句话复制给你的 AI 助手(DeepSeek / Claude / Codex 等),它会帮你完成安装:
58
55
 
59
56
  ```text
60
57
  Install the npm package @a9i5k4/dsh-auto-memory in the DeepSeek Harness web profile
61
58
  directory ~/.dsh/profiles/web (run "pnpm add @a9i5k4/dsh-auto-memory" or "npm install @a9i5k4/dsh-auto-memory"),
62
59
  append "@a9i5k4/dsh-auto-memory" to the dsh.profile.bundles array in package.json,
63
60
  then restart dsh web to activate the plugin.
64
-
65
- 请在 DeepSeek Harness 的 web profile 目录 ~/.dsh/profiles/web 下安装 npm 包
66
- @a9i5k4/dsh-auto-memory(执行 pnpm add @a9i5k4/dsh-auto-memory 或 npm install),
67
- 然后在 package.json 的 dsh.profile.bundles 数组追加 "@a9i5k4/dsh-auto-memory",
68
- 最后重启 dsh web 使插件生效。
69
61
  ```
70
62
 
71
63
  ---
72
64
 
73
- ## ⚙️ Configuration / 配置
65
+ ## Configuration
74
66
 
75
67
  Defaults (JSON file `~/.dsh/dsh-auto-memory.json`):
76
- 默认值(JSON 文件 `~/.dsh/dsh-auto-memory.json`):
77
68
 
78
69
  ```json
79
70
  {
@@ -83,31 +74,32 @@ Defaults (JSON file `~/.dsh/dsh-auto-memory.json`):
83
74
  "injectBudgetChars": 2400,
84
75
  "recentDaysInjected": 3,
85
76
  "reflectEnabled": true,
86
- "reflectStyle": "auto"
77
+ "reflectStyle": "auto",
78
+ "locale": "zh"
87
79
  }
88
80
  ```
89
81
 
90
- Adjustable in the GUI (Settings → Auto Memory). / 可在 GUI(设置 自动记忆)中调整。
82
+ Adjustable in the GUI (Settings → Auto Memory), including the UI language (zh / en).
91
83
 
92
84
  ---
93
85
 
94
- ## 📁 Structure / 结构
86
+ ## Structure
95
87
 
96
- - `lib/index.js` — Host half: engine, injection, tools, routes (zero runtime deps, Node built-ins only) / Host 半:引擎、注入、工具、路由(零运行时依赖)
97
- - `lib/client.js` — Browser half: memory panel + settings page / 浏览器半:记忆面板 + 设置页
88
+ - `lib/index.js` — Host half: engine, injection, tools, routes (zero runtime deps, Node built-ins only)
89
+ - `lib/client.js` — Browser half: memory panel + settings page (built-in zh/en i18n)
98
90
  - `cordis.patch.yml` — Plugin row (`auto-memory`)
99
91
 
100
92
  ---
101
93
 
102
- ## ⚠️ Limitations / 限制
94
+ ## Limitations
103
95
 
104
- - Memory files are plain-text Markdown; no secrets stored unless explicitly requested. / 记忆文件为明文 Markdown;不存密钥,除非用户明确要求。
105
- - `memory_recall` session search depends on the deployed session-query index; without it, only local search works. / `memory_recall` 的历史会话检索依赖部署的 session-query 索引,未启用时仅本地检索。
106
- - Plugin-set changes require a dsh restart. / 插件集变更需重启 dsh 生效。
96
+ - Memory files are plain-text Markdown; no secrets stored unless explicitly requested.
97
+ - `memory_recall` session search depends on the deployed session-query index; without it, only local search works.
98
+ - Plugin-set changes require a dsh restart.
107
99
 
108
100
  ---
109
101
 
110
- ## 📦 Release Info / 发布信息
102
+ ## Release Info
111
103
 
112
104
  - GitHub: https://github.com/Aik358/dsh-auto-memory
113
105
  - npm: `@a9i5k4/dsh-auto-memory`
@@ -0,0 +1,104 @@
1
+ # dsh-auto-memory — DSH 自动记忆插件
2
+
3
+ DSH Web GUI 的记忆插件:三层记忆(用户级 / 项目笔记 / 每日日志)自动注入与检索、每日反思、可视化面板与设置页,支持继承其他 AI 工具的历史记忆。
4
+
5
+ [English](README.md) | [中文版](README.zh-CN.md)
6
+
7
+ ---
8
+
9
+ ## 功能
10
+
11
+ | 层 | 位置 | 说明 |
12
+ |---|---|---|
13
+ | 用户级记忆 | `~/.dsh/memory/MEMORY.md` | 跨项目规则/偏好(用户明确要求时写) |
14
+ | 项目笔记 | `{工作区}/.dsh-memory/MEMORY.md` | 项目长期约定、决策、架构要点 |
15
+ | 每日日志 | `{工作区}/.dsh-memory/YYYY-MM-DD.md` | append-only 工作日志 |
16
+ | 反思 | `{工作区}/.dsh-memory/reflections/YYYY-MM-DD.md` | 每日反思(自动提示生成) |
17
+
18
+ - **自动注入**:每次组装系统提示词时注入 `<memory_system>` 块(用户规则 + 项目笔记 + 最近反思 + 最近 N 天日志尾部 + 写入纪律)
19
+ - **每日反思**:检测到"昨天有日志但未生成反思"时,会话首轮自动请求 agent 生成昨日反思
20
+ - **Agent 工具**:`memory_log` / `memory_note` / `memory_user` / `memory_recall` / `memory_external` / `memory_maintain` / `memory_status` / `memory_reflect`
21
+ - **GUI**:侧边栏「记忆」入口 → 浮层面板(概览/日志/笔记/反思/接续/检索);设置页(设置 → 自动记忆)
22
+ - **界面语言**:中文 / English 可切换(设置 → 自动记忆 → 界面语言)
23
+ - **外部记忆继承**:接入其他 AI 工具(CodeBuddy / Claude Code / Codex / 项目约定文件)积累的记忆
24
+
25
+ ---
26
+
27
+ ## 安装(NPM 一键)
28
+
29
+ > 前提:已安装 DeepSeek Harness(dsh)并至少启动过一次 `dsh web`。
30
+
31
+ 在 **profile 目录**(`~/.dsh/profiles/web`)下执行:
32
+
33
+ ```bash
34
+ cd ~/.dsh/profiles/web
35
+ pnpm add @a9i5k4/dsh-auto-memory
36
+ ```
37
+
38
+ 然后编辑该目录下的 `package.json`,在 `dsh.profile.bundles` 数组里追加:
39
+
40
+ ```json
41
+ "@a9i5k4/dsh-auto-memory"
42
+ ```
43
+
44
+ 保存后**重启 dsh web**,插件即生效(侧边栏出现「记忆」入口)。
45
+
46
+ > 没有 pnpm?用 npm 也行:`npm install @a9i5k4/dsh-auto-memory`
47
+
48
+ ---
49
+
50
+ ## AI 时代安装(把这句话直接丢给 AI)
51
+
52
+ > 现在是 AI 时代,你可以直接把下面这句话复制给你的 AI 助手(DeepSeek / Claude / Codex 等),它会帮你完成安装:
53
+
54
+ ```text
55
+ 请在 DeepSeek Harness 的 web profile 目录 ~/.dsh/profiles/web 下安装 npm 包
56
+ @a9i5k4/dsh-auto-memory(执行 pnpm add @a9i5k4/dsh-auto-memory 或 npm install),
57
+ 然后在 package.json 的 dsh.profile.bundles 数组追加 "@a9i5k4/dsh-auto-memory",
58
+ 最后重启 dsh web 使插件生效。
59
+ ```
60
+
61
+ ---
62
+
63
+ ## 配置
64
+
65
+ 默认值(JSON 文件 `~/.dsh/dsh-auto-memory.json`):
66
+
67
+ ```json
68
+ {
69
+ "userMemoryDir": "~/.dsh/memory",
70
+ "projectMemoryDir": ".dsh-memory",
71
+ "injectEnabled": true,
72
+ "injectBudgetChars": 2400,
73
+ "recentDaysInjected": 3,
74
+ "reflectEnabled": true,
75
+ "reflectStyle": "auto",
76
+ "locale": "zh"
77
+ }
78
+ ```
79
+
80
+ 可在 GUI(设置 → 自动记忆)中调整,包括界面语言(zh / en)。
81
+
82
+ ---
83
+
84
+ ## 结构
85
+
86
+ - `lib/index.js` — Host 半:引擎、注入、工具、路由(零运行时依赖,仅 node 内置模块)
87
+ - `lib/client.js` — 浏览器半:记忆面板 + 设置页(内置中英双语)
88
+ - `cordis.patch.yml` — 插件行(`auto-memory`)
89
+
90
+ ---
91
+
92
+ ## 限制
93
+
94
+ - 记忆文件为明文 Markdown;不存密钥,除非用户明确要求。
95
+ - `memory_recall` 的历史会话检索依赖部署的 session-query 索引,未启用时仅本地检索。
96
+ - 插件集变更需重启 dsh 生效。
97
+
98
+ ---
99
+
100
+ ## 发布信息
101
+
102
+ - GitHub: https://github.com/Aik358/dsh-auto-memory
103
+ - npm: `@a9i5k4/dsh-auto-memory`
104
+ - License: BSD-3-Clause
package/lib/client.js CHANGED
@@ -90,6 +90,117 @@ window.__ModuleLoader__.load({
90
90
  subscribe: function (fn) { listeners.add(fn); return function () { listeners.delete(fn) } },
91
91
  }
92
92
 
93
+ // ───────────────────────── 国际化 ─────────────────────────
94
+ var I18N = {
95
+ zh: {
96
+ loading: '加载中…',
97
+ memoryPanel: '记忆面板',
98
+ memory: '记忆',
99
+ autoMemory: '自动记忆',
100
+ overview: '概览', logs: '日志', notes: '笔记', reflections: '反思', connect: '接续', search: '检索',
101
+ refresh: '刷新', close: '关闭', dragMove: '拖动移动', dragResize: '拖动调整大小', resetPos: '恢复默认位置',
102
+ generated: '已生成', failed: '失败: ',
103
+ pendingReflection: '待生成反思: ',
104
+ pendingReflectionHint: ' —— 可点下方「一键反思」立即生成。',
105
+ todayWork: '今日工作', logEntries: ' 条日志',
106
+ dailyReflection: '每日反思', notYet: '(还没有)',
107
+ workspace: '工作区',
108
+ reflecting: '反思生成中…', oneClickReflect: '一键反思',
109
+ reflectHint: '用最近日志自动生成反思',
110
+ quickLinks: '快捷入口:日志页签看每日记录 · 笔记页签追加项目笔记 · 接续页签接入其他 AI 记忆 · 检索页签全文搜索。',
111
+ collapseTech: '收起技术信息 ▴', expandTech: '技术信息 ▾',
112
+ userMemory: '用户级记忆', projectNotes: '项目笔记', todayLog: '今日日志', configFile: '配置文件',
113
+ readFailed: '读取失败: ', ok: '正常', refreshTime: '刷新时间', notYetShort: '尚未',
114
+ empty: '(空)', back: '← 返回', view: '查看',
115
+ clickDateViewLog: '点击日期查看当日日志(append-only):',
116
+ appended: '已追加', notesPathLabel: '项目笔记: ',
117
+ notesPlaceholder: '想追加到项目笔记的内容…(保存时自动带日期标题)',
118
+ saving: '保存中…', append: '追加',
119
+ notesHint: '建议直接用对话让 agent 调 memory_note;此处为手动追加。',
120
+ reflectionTitle: '反思 ', generating: '生成中…',
121
+ reflectAutoHint: '自动用最近日志生成反思草稿(便于测试)',
122
+ noReflection: '还没有反思。每天第一次会话时,agent 会主动呈现前一天的工作反思。',
123
+ searchFailed: '检索失败: ', searchPlaceholder: '搜记忆:关键词或自包含描述…', searchBtn: '检索', resultTitle: '结果',
124
+ projectFiles: '项目文件', aiAssistant: 'AI 助手',
125
+ imported: '接入完成', importing: '正在接入 ', importingSuffix: ' 个源…', allImported: '全部接入完成',
126
+ noExternal: '未检测到其他 AI 工具的记忆文件(CodeBuddy/Claude Code/Codex 等)。',
127
+ sessionSource: '会话源:共 ', sessionSourceSuffix: ' 个会话文件,用 memory_recall 按需检索',
128
+ importingOne: '接入中…', importToNotes: '接入项目笔记', importToUser: '接入用户记忆',
129
+ disabled: ' · 已停用', filesCount: ' 个文件 · ',
130
+ connectHint: '把其他 AI 工具(CodeBuddy / Claude Code / Codex / 项目约定文件等)积累的记忆接入当前 DSH 工作。接入后内容写入本地记忆并自动标注来源,后续会随会话自动注入。',
131
+ importAll: '一键接入全部', rescan: '重新扫描',
132
+ styleAuto: '由内容决定', styleLife: '生活化', styleProfessional: '专业性',
133
+ saved: '已保存',
134
+ settingsHeader: '记忆存储与行为设置(保存到 DSH 主目录 dsh-auto-memory.json):',
135
+ fUserDir: '用户记忆目录', fUserDirHint: '跨项目规则存放处,支持 ~ 开头;需有文件写权限。',
136
+ fProjectDir: '项目记忆目录', fProjectDirHint: '相对各工作区的目录名(默认 .dsh-memory)。',
137
+ fInject: '注入记忆上下文', fInjectHint: '每次组装提示词时自动注入 <memory_system> 块。',
138
+ fBudget: '注入预算(字符)', fBudgetHint: '记忆块总预算,超出部分截断。',
139
+ fDays: '注入最近日志天数', fDaysHint: '会话开始时注入最近 N 天的工作日志尾部。',
140
+ fReflect: '每日反思', fReflectHint: '昨天有工作日志时,会话首轮主动呈现昨日反思。',
141
+ fStyle: '反思风格', fStyleHint: '生活化 / 专业性 / 由内容决定。',
142
+ fLocale: '界面语言', fLocaleHint: '切换插件面板与设置页的显示语言。',
143
+ saveSettings: '保存设置',
144
+ zh: '中文', en: 'English',
145
+ },
146
+ en: {
147
+ loading: 'Loading…',
148
+ memoryPanel: 'Memory Panel',
149
+ memory: 'Memory',
150
+ autoMemory: 'Auto Memory',
151
+ overview: 'Overview', logs: 'Logs', notes: 'Notes', reflections: 'Reflections', connect: 'Connect', search: 'Search',
152
+ refresh: 'Refresh', close: 'Close', dragMove: 'Drag to move', dragResize: 'Drag to resize', resetPos: 'Reset position',
153
+ generated: 'Generated', failed: 'Failed: ',
154
+ pendingReflection: 'Pending reflection: ',
155
+ pendingReflectionHint: ' — click "One-click Reflect" below to generate now.',
156
+ todayWork: 'Today', logEntries: ' log entries',
157
+ dailyReflection: 'Daily reflection', notYet: '(none yet)',
158
+ workspace: 'Workspace',
159
+ reflecting: 'Generating…', oneClickReflect: 'One-click Reflect',
160
+ reflectHint: 'Auto-generate reflection from recent logs',
161
+ quickLinks: 'Quick access: Logs tab for daily records · Notes tab to append project notes · Connect tab to import AI memories · Search tab for full-text search.',
162
+ collapseTech: 'Collapse details ▴', expandTech: 'Details ▾',
163
+ userMemory: 'User memory', projectNotes: 'Project notes', todayLog: 'Today log', configFile: 'Config file',
164
+ readFailed: 'Read failed: ', ok: 'OK', refreshTime: 'Refreshed', notYetShort: 'never',
165
+ empty: '(empty)', back: '← Back', view: 'View',
166
+ clickDateViewLog: 'Click a date to view the daily log (append-only):',
167
+ appended: 'Appended', notesPathLabel: 'Project notes: ',
168
+ notesPlaceholder: 'Content to append to project notes…(date heading added on save)',
169
+ saving: 'Saving…', append: 'Append',
170
+ notesHint: 'Tip: ask the agent to call memory_note in chat; this is a manual fallback.',
171
+ reflectionTitle: 'Reflection ', generating: 'Generating…',
172
+ reflectAutoHint: 'Auto-generate a reflection draft from recent logs (for testing)',
173
+ noReflection: "No reflections yet. On the first session each day, the agent presents the previous day's reflection.",
174
+ searchFailed: 'Search failed: ', searchPlaceholder: 'Search memory: keyword or self-contained description…', searchBtn: 'Search', resultTitle: 'Results',
175
+ projectFiles: 'Project files', aiAssistant: 'AI Assistant',
176
+ imported: 'Imported', importing: 'Importing ', importingSuffix: ' sources…', allImported: 'All imported',
177
+ noExternal: 'No memory files found from other AI tools (CodeBuddy/Claude Code/Codex etc.).',
178
+ sessionSource: 'Session source: ', sessionSourceSuffix: ' session files, search on demand with memory_recall',
179
+ importingOne: 'Importing…', importToNotes: 'Import to notes', importToUser: 'Import to user memory',
180
+ disabled: ' · disabled', filesCount: ' files · ',
181
+ connectHint: 'Import memories accumulated by other AI tools (CodeBuddy / Claude Code / Codex / project convention files) into the current DSH workspace. Imported content is written to local memory with its source noted, and is auto-injected in future sessions.',
182
+ importAll: 'Import all', rescan: 'Rescan',
183
+ styleAuto: 'Auto', styleLife: 'Life-style', styleProfessional: 'Professional',
184
+ saved: 'Saved',
185
+ settingsHeader: 'Memory storage & behavior (saved to ~/.dsh/dsh-auto-memory.json):',
186
+ fUserDir: 'User memory dir', fUserDirHint: 'Cross-project rules; supports ~ prefix; needs write permission.',
187
+ fProjectDir: 'Project memory dir', fProjectDirHint: 'Directory name relative to each workspace (default .dsh-memory).',
188
+ fInject: 'Inject memory context', fInjectHint: 'Auto-inject <memory_system> block into every prompt.',
189
+ fBudget: 'Injection budget (chars)', fBudgetHint: 'Total budget for the memory block; excess is truncated.',
190
+ fDays: 'Recent days injected', fDaysHint: 'Inject tails of the last N days of work logs at session start.',
191
+ fReflect: 'Daily reflection', fReflectHint: 'When yesterday has logs, the agent presents the reflection at session start.',
192
+ fStyle: 'Reflection style', fStyleHint: 'Life-style / Professional / Auto.',
193
+ fLocale: 'UI language', fLocaleHint: 'Switch the display language of the panel and settings page.',
194
+ saveSettings: 'Save settings',
195
+ zh: '中文', en: 'English',
196
+ }
197
+ }
198
+ var locale = 'zh'
199
+ var localeListeners = new Set()
200
+ function t(key) { return (I18N[locale] && I18N[locale][key]) || I18N.zh[key] || key }
201
+ function setLocale(l) { if (l !== 'zh' && l !== 'en') return; if (l === locale) return; locale = l; localeListeners.forEach(function (fn) { try { fn(l) } catch (e) {} }) }
202
+ function onLocale(fn) { localeListeners.add(fn); return function () { localeListeners.delete(fn) } }
203
+
93
204
  // ───────────────────────── API ─────────────────────────
94
205
  var API = {
95
206
  state: '/api/dsh-auto-memory/state',
@@ -200,7 +311,7 @@ window.__ModuleLoader__.load({
200
311
  h('div', { className: 'dam-content' }, props.children))
201
312
  }
202
313
 
203
- function Loading() { return h('div', { 'data-dam-muted': '' }, '加载中…') }
314
+ function Loading() { return h('div', { 'data-dam-muted': '' }, t('loading')) }
204
315
 
205
316
  // ───────────────────────── 侧边栏入口 ─────────────────────────
206
317
  function SidebarButton() {
@@ -208,10 +319,10 @@ window.__ModuleLoader__.load({
208
319
  useEffect(function () { return controller.subscribe(tick[1]) }, [])
209
320
  return h('button', {
210
321
  'data-dam-sidebar-btn': '',
211
- title: '记忆面板',
322
+ title: t('memoryPanel'),
212
323
  'data-active': (panelOpen || panelClosing) ? 'true' : undefined,
213
324
  onClick: function () { controller.toggle() },
214
- }, h('span', null, '记忆'))
325
+ }, h('span', null, t('memory')))
215
326
  }
216
327
 
217
328
  // ───────────────────────── 记忆面板 ─────────────────────────
@@ -244,35 +355,34 @@ window.__ModuleLoader__.load({
244
355
  if (reflectBusy) return
245
356
  setReflectBusy(true); setActMsg('')
246
357
  apiPost(API.reflectAuto, {}).then(function (d) {
247
- setActMsg(d.result || '已生成'); setReflectBusy(false)
358
+ setActMsg(d.result || t('generated')); setReflectBusy(false)
248
359
  apiGet(API.state).then(function (s) { if (s) setState(s) }).catch(function () {})
249
- }).catch(function (e) { setActMsg('失败: ' + e.message); setReflectBusy(false) })
360
+ }).catch(function (e) { setActMsg(t('failed') + e.message); setReflectBusy(false) })
250
361
  }
251
362
  return h('div', null,
252
363
  state.pendingReflection
253
- ? h(Banner, null, '待生成反思: ' + state.pendingReflection + ' —— 可点下方「一键反思」立即生成。')
364
+ ? h(Banner, null, t('pendingReflection') + state.pendingReflection + t('pendingReflectionHint'))
254
365
  : null,
255
366
  // 状态行:今日工作 / 反思 / 笔记
256
367
  h('div', { 'data-dam-kv': '' },
257
- h('b', null, '今日工作'), h('span', null, state.todayEntries + ' 条日志'),
258
- h('b', null, '每日反思'), h('span', null, state.latestReflectionDate || '(还没有)'),
259
- h('b', null, '工作区'), h('span', null, state.ws)),
368
+ h('b', null, t('todayWork')), h('span', null, state.todayEntries + t('logEntries')),
369
+ h('b', null, t('dailyReflection')), h('span', null, state.latestReflectionDate || t('notYet')),
370
+ h('b', null, t('workspace')), h('span', null, state.ws)),
260
371
  // 快捷操作
261
372
  h('div', { 'data-dam-row': '' },
262
- h('button', { 'data-dam-btn': '', onClick: oneClickReflect, disabled: reflectBusy }, reflectBusy ? '反思生成中…' : '一键反思'),
263
- h('span', { 'data-dam-hint': '' }, '用最近日志自动生成反思')),
373
+ h('button', { 'data-dam-btn': '', onClick: oneClickReflect, disabled: reflectBusy }, reflectBusy ? t('reflecting') : t('oneClickReflect')),
374
+ h('span', { 'data-dam-hint': '' }, t('reflectHint'))),
264
375
  actMsg ? h('div', { 'data-dam-hint': '' }, actMsg) : null,
265
- h('div', { 'data-dam-hint': '' },
266
- '快捷入口:日志页签看每日记录 · 笔记页签追加项目笔记 · 接续页签接入其他 AI 记忆 · 检索页签全文搜索。'),
376
+ h('div', { 'data-dam-hint': '' }, t('quickLinks')),
267
377
  // 技术细节(折叠)
268
378
  h('div', null,
269
- h('button', { 'data-dam-btn': '', onClick: function () { setShowDetail(!showDetail) } }, showDetail ? '收起技术信息 ▴' : '技术信息 ▾')),
379
+ h('button', { 'data-dam-btn': '', onClick: function () { setShowDetail(!showDetail) } }, showDetail ? t('collapseTech') : t('expandTech'))),
270
380
  showDetail ? h('div', { 'data-dam-kv': '' },
271
- h('b', null, '用户级记忆'), h('span', null, state.userFile + ' (' + fmtSize(state.sizes.user) + ')'),
272
- h('b', null, '项目笔记'), h('span', null, state.notesPath + ' (' + fmtSize(state.sizes.notes) + ')'),
273
- h('b', null, '今日日志'), h('span', null, state.logPath + ' (' + fmtSize(state.sizes.log) + ')'),
274
- h('b', null, '配置文件'), h('span', null, state.configReadError ? ('读取失败: ' + state.configReadError) : '正常'),
275
- h('b', null, '刷新时间'), h('span', null, state.refreshedAt ? new Date(state.refreshedAt).toLocaleString() : '尚未')) : null)
381
+ h('b', null, t('userMemory')), h('span', null, state.userFile + ' (' + fmtSize(state.sizes.user) + ')'),
382
+ h('b', null, t('projectNotes')), h('span', null, state.notesPath + ' (' + fmtSize(state.sizes.notes) + ')'),
383
+ h('b', null, t('todayLog')), h('span', null, state.logPath + ' (' + fmtSize(state.sizes.log) + ')'),
384
+ h('b', null, t('configFile')), h('span', null, state.configReadError ? (t('readFailed') + state.configReadError) : t('ok')),
385
+ h('b', null, t('refreshTime')), h('span', null, state.refreshedAt ? new Date(state.refreshedAt).toLocaleString() : t('notYetShort'))) : null)
276
386
  }
277
387
 
278
388
  function LogsTab() {
@@ -300,14 +410,14 @@ window.__ModuleLoader__.load({
300
410
  var rows = []
301
411
  if (open) {
302
412
  rows.push(h(Card, { title: pathName(open) },
303
- h('div', { 'data-dam-content': '' }, content || '(空)'),
304
- h('button', { 'data-dam-btn': '', onClick: function () { setOpen(null); setContent('') } }, '← 返回')))
413
+ h('div', { 'data-dam-content': '' }, content || t('empty')),
414
+ h('button', { 'data-dam-btn': '', onClick: function () { setOpen(null); setContent('') } }, t('back'))))
305
415
  } else {
306
- rows.push(h('div', { 'data-dam-hint': '' }, '点击日期查看当日日志(append-only):'))
416
+ rows.push(h('div', { 'data-dam-hint': '' }, t('clickDateViewLog')))
307
417
  for (var i = 0; i < data.logs.length; i++) {
308
418
  (function (log) {
309
419
  rows.push(h(Card, { title: log.date + ' · ' + fmtSize(log.size) },
310
- h('button', { 'data-dam-btn': '', onClick: function () { setOpen(log.date + '.md') } }, '查看')))
420
+ h('button', { 'data-dam-btn': '', onClick: function () { setOpen(log.date + '.md') } }, t('view'))))
311
421
  })(data.logs[i])
312
422
  }
313
423
  }
@@ -341,15 +451,15 @@ window.__ModuleLoader__.load({
341
451
  if (!draft.trim() || saving) return
342
452
  setSaving(true); setMsg(''); setErr('')
343
453
  apiPost(API.note, { content: draft.trim() }).then(function (d) {
344
- setMsg(d.result || '已追加 ✓'); setDraft(''); setSaving(false)
454
+ setMsg(d.result || t('appended')); setDraft(''); setSaving(false)
345
455
  }).catch(function (e) { setErr(e.message); setSaving(false) })
346
456
  }
347
457
  return h('div', null,
348
- h('div', { 'data-dam-hint': '' }, '项目笔记: ' + data.notesPath),
349
- h('textarea', { 'data-dam-input': '', rows: 6, placeholder: '想追加到项目笔记的内容…(保存时自动带日期标题)', value: draft, onChange: function (e) { setDraft(e.target.value) } }),
458
+ h('div', { 'data-dam-hint': '' }, t('notesPathLabel') + data.notesPath),
459
+ h('textarea', { 'data-dam-input': '', rows: 6, placeholder: t('notesPlaceholder'), value: draft, onChange: function (e) { setDraft(e.target.value) } }),
350
460
  h('div', { 'data-dam-row': '' },
351
- h('button', { 'data-dam-btn': '', onClick: save, disabled: saving }, saving ? '保存中…' : '追加'),
352
- h('span', { 'data-dam-hint': '' }, '建议直接用对话让 agent 调 memory_note;此处为手动追加。')),
461
+ h('button', { 'data-dam-btn': '', onClick: save, disabled: saving }, saving ? t('saving') : t('append')),
462
+ h('span', { 'data-dam-hint': '' }, t('notesHint'))),
353
463
  msg ? h('div', null, msg) : null,
354
464
  err ? h('div', { 'data-dam-error': '' }, err) : null)
355
465
  }
@@ -386,24 +496,24 @@ window.__ModuleLoader__.load({
386
496
  if (busy) return
387
497
  setBusy(true); setMsg('')
388
498
  apiPost(API.reflectAuto, {}).then(function (d) {
389
- setMsg(d.result || '已生成'); setBusy(false)
499
+ setMsg(d.result || t('generated')); setBusy(false)
390
500
  apiGet(API.list).then(function (dd) { if (dd) setData(dd) }).catch(function () {})
391
- }).catch(function (e) { setMsg('失败: ' + e.message); setBusy(false) })
501
+ }).catch(function (e) { setMsg(t('failed') + e.message); setBusy(false) })
392
502
  }
393
503
  var rows = []
394
504
  if (open) {
395
- rows.push(h(Card, { title: '反思 ' + pathName(open) }, h('div', { 'data-dam-content': '' }, content || '(空)')))
396
- rows.push(h('button', { 'data-dam-btn': '', onClick: function () { setOpen(null); setContent('') } }, '← 返回'))
505
+ rows.push(h(Card, { title: t('reflectionTitle') + pathName(open) }, h('div', { 'data-dam-content': '' }, content || t('empty'))))
506
+ rows.push(h('button', { 'data-dam-btn': '', onClick: function () { setOpen(null); setContent('') } }, t('back')))
397
507
  } else {
398
508
  rows.push(h('div', { 'data-dam-row': '' },
399
- h('button', { 'data-dam-btn': '', onClick: oneClickReflect, disabled: busy }, busy ? '生成中…' : '一键反思'),
400
- h('span', { 'data-dam-hint': '' }, '自动用最近日志生成反思草稿(便于测试)')))
509
+ h('button', { 'data-dam-btn': '', onClick: oneClickReflect, disabled: busy }, busy ? t('generating') : t('oneClickReflect')),
510
+ h('span', { 'data-dam-hint': '' }, t('reflectAutoHint'))))
401
511
  if (msg) rows.push(h('div', null, msg))
402
- if (!data.reflections.length) rows.push(h('div', { 'data-dam-muted': '' }, '还没有反思。每天第一次会话时,agent 会主动呈现前一天的工作反思。'))
512
+ if (!data.reflections.length) rows.push(h('div', { 'data-dam-muted': '' }, t('noReflection')))
403
513
  for (var i = 0; i < data.reflections.length; i++) {
404
514
  (function (r) {
405
515
  rows.push(h(Card, { title: r.date + ' · ' + fmtSize(r.size) },
406
- h('button', { 'data-dam-btn': '', onClick: function () { setOpen('reflections/' + r.name) } }, '查看')))
516
+ h('button', { 'data-dam-btn': '', onClick: function () { setOpen('reflections/' + r.name) } }, t('view'))))
407
517
  })(data.reflections[i])
408
518
  }
409
519
  }
@@ -424,16 +534,16 @@ window.__ModuleLoader__.load({
424
534
  if (!q.trim() || busy) return
425
535
  setBusy(true); setResult(null)
426
536
  apiPost(API.recall, { query: q.trim() }).then(function (d) { setResult(d.result); setBusy(false) })
427
- .catch(function (e) { setResult('检索失败: ' + e.message); setBusy(false) })
537
+ .catch(function (e) { setResult(t('searchFailed') + e.message); setBusy(false) })
428
538
  }
429
539
  return h('div', null,
430
540
  h('div', { 'data-dam-row': '' },
431
- h('input', { 'data-dam-input': '', placeholder: '搜记忆:关键词或自包含描述…', value: q, onChange: function (e) { setQ(e.target.value) }, onKeyDown: function (e) { if (e.key === 'Enter') search() } }),
432
- h('button', { 'data-dam-btn': '', onClick: search, disabled: busy }, '检索')),
433
- result ? h(Card, { title: '结果' }, h('div', { 'data-dam-content': '' }, result)) : null)
541
+ h('input', { 'data-dam-input': '', placeholder: t('searchPlaceholder'), value: q, onChange: function (e) { setQ(e.target.value) }, onKeyDown: function (e) { if (e.key === 'Enter') search() } }),
542
+ h('button', { 'data-dam-btn': '', onClick: search, disabled: busy }, t('searchBtn'))),
543
+ result ? h(Card, { title: t('resultTitle') }, h('div', { 'data-dam-content': '' }, result)) : null)
434
544
  }
435
545
 
436
- var TOOL_LABEL = { workbuddy: 'AI 助手', codebuddy: 'CodeBuddy', claude: 'Claude Code', codex: 'Codex', 'project-files': '项目文件' }
546
+ var TOOL_LABEL = { workbuddy: t('aiAssistant'), codebuddy: 'CodeBuddy', claude: 'Claude Code', codex: 'Codex', 'project-files': t('projectFiles') }
437
547
  function ConnectTab() {
438
548
  var sourcesPair = useState(null)
439
549
  var sources = sourcesPair[0]
@@ -457,40 +567,40 @@ window.__ModuleLoader__.load({
457
567
  setBusy(next); setMsg(''); setErr('')
458
568
  apiPost(API.externalImport, { source: source, target: target }).then(function (d) {
459
569
  var done = Object.assign({}, busy); done[source] = false
460
- setBusy(done); setMsg(d.result || '接入完成')
570
+ setBusy(done); setMsg(d.result || t('imported'))
461
571
  }).catch(function (e) { var done = Object.assign({}, busy); done[source] = false; setBusy(done); setErr(e.message) })
462
572
  }
463
573
  function importAll() {
464
574
  var md = sources.filter(function (s) { return s.kind !== 'sessions' && s.enabled !== false })
465
- setMsg('正在接入 ' + md.length + ' 个源…'); setErr('')
575
+ setMsg(t('importing') + md.length + t('importingSuffix')); setErr('')
466
576
  var chain = Promise.resolve()
467
577
  md.forEach(function (s) { chain = chain.then(function () { return apiPost(API.externalImport, { source: s.id, target: 'project' }) }) })
468
- chain.then(function () { setMsg('全部接入完成 ✓') }).catch(function (e) { setErr(e.message) })
578
+ chain.then(function () { setMsg(t('allImported')) }).catch(function (e) { setErr(e.message) })
469
579
  }
470
580
  var cards = []
471
581
  if (!sources.length) {
472
- cards.push(h('div', { 'data-dam-muted': '' }, '未检测到其他 AI 工具的记忆文件(CodeBuddy/Claude Code/Codex 等)。'))
582
+ cards.push(h('div', { 'data-dam-muted': '' }, t('noExternal')))
473
583
  }
474
584
  for (var i = 0; i < sources.length; i++) {
475
585
  (function (s) {
476
586
  var actions = []
477
587
  if (s.kind === 'sessions') {
478
- actions.push(h('span', { 'data-dam-hint': '' }, '会话源:共 ' + s.fileCount + ' 个会话文件,用 memory_recall 按需检索'))
588
+ actions.push(h('span', { 'data-dam-hint': '' }, t('sessionSource') + s.fileCount + t('sessionSourceSuffix')))
479
589
  } else {
480
- actions.push(h('button', { 'data-dam-btn': '', disabled: !!busy[s.id], onClick: function () { doImport(s.id, 'project') } }, busy[s.id] ? '接入中…' : '接入项目笔记'))
481
- actions.push(h('button', { 'data-dam-btn': '', disabled: !!busy[s.id], onClick: function () { doImport(s.id, 'user') } }, '接入用户记忆'))
590
+ actions.push(h('button', { 'data-dam-btn': '', disabled: !!busy[s.id], onClick: function () { doImport(s.id, 'project') } }, busy[s.id] ? t('importingOne') : t('importToNotes')))
591
+ actions.push(h('button', { 'data-dam-btn': '', disabled: !!busy[s.id], onClick: function () { doImport(s.id, 'user') } }, t('importToUser')))
482
592
  }
483
- cards.push(h(Card, { title: s.name + ' · ' + s.tool + (s.enabled === false ? ' · 已停用' : '') },
484
- h('div', { 'data-dam-hint': '' }, s.kind + ' · ' + s.fileCount + ' 个文件 · ' + fmtSize(s.size)),
593
+ cards.push(h(Card, { title: s.name + ' · ' + s.tool + (s.enabled === false ? t('disabled') : '') },
594
+ h('div', { 'data-dam-hint': '' }, s.kind + ' · ' + s.fileCount + t('filesCount') + fmtSize(s.size)),
485
595
  s.preview ? h('div', { 'data-dam-content': '' }, s.preview) : null,
486
596
  h('div', { 'data-dam-row': '' }, actions)))
487
597
  })(sources[i])
488
598
  }
489
599
  return h('div', null,
490
- h('div', { 'data-dam-hint': '' }, '把其他 AI 工具(CodeBuddy / Claude Code / Codex / 项目约定文件等)积累的记忆接入当前 DSH 工作。接入后内容写入本地记忆并自动标注来源,后续会随会话自动注入。'),
600
+ h('div', { 'data-dam-hint': '' }, t('connectHint')),
491
601
  h('div', { 'data-dam-row': '' },
492
- h('button', { 'data-dam-btn': '', onClick: importAll }, '一键接入全部'),
493
- h('button', { 'data-dam-btn': '', onClick: load }, '重新扫描')),
602
+ h('button', { 'data-dam-btn': '', onClick: importAll }, t('importAll')),
603
+ h('button', { 'data-dam-btn': '', onClick: load }, t('rescan'))),
494
604
  msg ? h('div', null, msg) : null,
495
605
  err ? h('div', { 'data-dam-error': '' }, err) : null,
496
606
  cards)
@@ -541,7 +651,7 @@ window.__ModuleLoader__.load({
541
651
  else if (tab === 'reflections') body = h(ReflectionsTab)
542
652
  else if (tab === 'connect') body = h(ConnectTab)
543
653
  else body = h(SearchTab)
544
- var tabs = [['overview', '概览'], ['logs', '日志'], ['notes', '笔记'], ['reflections', '反思'], ['connect', '接续'], ['search', '检索']]
654
+ var tabs = [['overview', t('overview')], ['logs', t('logs')], ['notes', t('notes')], ['reflections', t('reflections')], ['connect', t('connect')], ['search', t('search')]]
545
655
  var style = {
546
656
  left: g.left + 'px',
547
657
  top: g.top + 'px',
@@ -561,23 +671,24 @@ window.__ModuleLoader__.load({
561
671
  'data-dragging': dragActive ? 'true' : undefined,
562
672
  },
563
673
  h('header', {
564
- title: '拖动移动',
674
+ title: t('dragMove'),
565
675
  onPointerDown: dragMove,
566
676
  },
567
- h('strong', null, '自动记忆'),
677
+ h('strong', null, t('autoMemory')),
568
678
  h('span', { className: 'dam-spacer' }),
569
- h('button', { 'data-dam-btn': '', title: '恢复默认位置', onClick: function () { controller.resetGeom() } }, '⤾'),
570
- h('button', { 'data-dam-btn': '', title: '刷新', onClick: function () { tick[1]() } }, '⟳'),
571
- h('button', { 'data-dam-btn': '', title: '关闭', onClick: function () { controller.close() } }, '✕')),
679
+ h('button', { 'data-dam-btn': '', title: t('resetPos'), onClick: function () { controller.resetGeom() } }, '⤾'),
680
+ h('button', { 'data-dam-btn': '', title: t('refresh'), onClick: function () { tick[1]() } }, '⟳'),
681
+ h('button', { 'data-dam-btn': '', title: t('close'), onClick: function () { controller.close() } }, '✕')),
572
682
  h('div', { 'data-dam-tabs': '' }, tabs.map(function (t) {
573
683
  return h('button', { key: t[0], 'data-dam-tab': '', 'data-active': tab === t[0] ? 'true' : undefined, onClick: function () { setTab(t[0]) } }, t[1])
574
684
  })),
575
685
  h('div', { 'data-dam-body': '' }, body),
576
- h('div', { 'data-dam-resize': '', title: '拖动调整大小', onPointerDown: resizeMove }))
686
+ h('div', { 'data-dam-resize': '', title: t('dragResize'), onPointerDown: resizeMove }))
577
687
  }
578
688
 
579
689
  // ───────────────────────── 设置页 ─────────────────────────
580
- var STYLE_OPTIONS = [['auto', '由内容决定'], ['life', '生活化'], ['professional', '专业性']]
690
+ var STYLE_OPTIONS = [['auto', t('styleAuto')], ['life', t('styleLife')], ['professional', t('styleProfessional')]]
691
+ var LOCALE_OPTIONS = [['zh', t('zh')], ['en', t('en')]]
581
692
  function SettingsPage() {
582
693
  var cfgPair = useState(null)
583
694
  var cfg = cfgPair[0]
@@ -601,7 +712,7 @@ window.__ModuleLoader__.load({
601
712
  function save() {
602
713
  if (busy) return
603
714
  setBusy(true); setMsg(''); setErr('')
604
- apiPost(API.config, cfg).then(function (d) { setCfg(d.config); setMsg('已保存 ✓'); setBusy(false) })
715
+ apiPost(API.config, cfg).then(function (d) { setCfg(d.config); setMsg(t('saved')); setBusy(false) })
605
716
  .catch(function (e) { setErr(e.message); setBusy(false) })
606
717
  }
607
718
  function field(label, control, hint) {
@@ -610,17 +721,19 @@ window.__ModuleLoader__.load({
610
721
  hint ? h('div', { 'data-dam-hint': '' }, hint) : null)
611
722
  }
612
723
  return h('div', null,
613
- h('div', { 'data-dam-hint': '' }, '记忆存储与行为设置(保存到 DSH 主目录 dsh-auto-memory.json):'),
614
- field('用户记忆目录', h('input', { 'data-dam-input': '', value: cfg.userMemoryDir, onChange: function (e) { set('userMemoryDir', e.target.value) } }), '跨项目规则存放处,支持 ~ 开头;需有文件写权限。'),
615
- field('项目记忆目录', h('input', { 'data-dam-input': '', value: cfg.projectMemoryDir, onChange: function (e) { set('projectMemoryDir', e.target.value) } }), '相对各工作区的目录名(默认 .dsh-memory)。'),
616
- field('注入记忆上下文', h('input', { type: 'checkbox', checked: !!cfg.injectEnabled, onChange: function (e) { set('injectEnabled', e.target.checked) } }), '每次组装提示词时自动注入 <memory_system> 块。'),
617
- field('注入预算(字符)', h('input', { 'data-dam-input': '', type: 'number', min: 400, value: cfg.injectBudgetChars, onChange: function (e) { set('injectBudgetChars', Number(e.target.value) || 2400) } }), '记忆块总预算,超出部分截断。'),
618
- field('注入最近日志天数', h('input', { 'data-dam-input': '', type: 'number', min: 1, max: 14, value: cfg.recentDaysInjected, onChange: function (e) { set('recentDaysInjected', Number(e.target.value) || 3) } }), '会话开始时注入最近 N 天的工作日志尾部。'),
619
- field('每日反思', h('input', { type: 'checkbox', checked: !!cfg.reflectEnabled, onChange: function (e) { set('reflectEnabled', e.target.checked) } }), '昨天有工作日志时,会话首轮主动呈现昨日反思。'),
620
- field('反思风格', h('select', { 'data-dam-select': '', value: cfg.reflectStyle, onChange: function (e) { set('reflectStyle', e.target.value) } },
621
- STYLE_OPTIONS.map(function (o) { return h('option', { key: o[0], value: o[0] }, o[1]) })), '生活化 / 专业性 / 由内容决定。'),
724
+ h('div', { 'data-dam-hint': '' }, t('settingsHeader')),
725
+ field(t('fLocale'), h('select', { 'data-dam-select': '', value: cfg.locale || 'zh', onChange: function (e) { set('locale', e.target.value); setLocale(e.target.value) } },
726
+ LOCALE_OPTIONS.map(function (o) { return h('option', { key: o[0], value: o[0] }, o[1]) })), t('fLocaleHint')),
727
+ field(t('fUserDir'), h('input', { 'data-dam-input': '', value: cfg.userMemoryDir, onChange: function (e) { set('userMemoryDir', e.target.value) } }), t('fUserDirHint')),
728
+ field(t('fProjectDir'), h('input', { 'data-dam-input': '', value: cfg.projectMemoryDir, onChange: function (e) { set('projectMemoryDir', e.target.value) } }), t('fProjectDirHint')),
729
+ field(t('fInject'), h('input', { type: 'checkbox', checked: !!cfg.injectEnabled, onChange: function (e) { set('injectEnabled', e.target.checked) } }), t('fInjectHint')),
730
+ field(t('fBudget'), h('input', { 'data-dam-input': '', type: 'number', min: 400, value: cfg.injectBudgetChars, onChange: function (e) { set('injectBudgetChars', Number(e.target.value) || 2400) } }), t('fBudgetHint')),
731
+ field(t('fDays'), h('input', { 'data-dam-input': '', type: 'number', min: 1, max: 14, value: cfg.recentDaysInjected, onChange: function (e) { set('recentDaysInjected', Number(e.target.value) || 3) } }), t('fDaysHint')),
732
+ field(t('fReflect'), h('input', { type: 'checkbox', checked: !!cfg.reflectEnabled, onChange: function (e) { set('reflectEnabled', e.target.checked) } }), t('fReflectHint')),
733
+ field(t('fStyle'), h('select', { 'data-dam-select': '', value: cfg.reflectStyle, onChange: function (e) { set('reflectStyle', e.target.value) } },
734
+ STYLE_OPTIONS.map(function (o) { return h('option', { key: o[0], value: o[0] }, o[1]) })), t('fStyleHint')),
622
735
  h('div', { 'data-dam-row': '' },
623
- h('button', { 'data-dam-btn': '', onClick: save, disabled: busy }, busy ? '保存中…' : '保存设置'),
736
+ h('button', { 'data-dam-btn': '', onClick: save, disabled: busy }, busy ? t('saving') : t('saveSettings')),
624
737
  msg ? h('span', null, msg) : null),
625
738
  err ? h('div', { 'data-dam-error': '' }, err) : null)
626
739
  }
@@ -639,15 +752,29 @@ window.__ModuleLoader__.load({
639
752
  var slots = ctx.slots
640
753
  if (!slots) { console.warn('[dsh-auto-memory] slots service unavailable'); return }
641
754
 
755
+ // 初始化界面语言(从 host 配置读取,持久化到 localStorage 以便即时生效)
756
+ try {
757
+ var savedLocale = localStorage.getItem('dsh-auto-memory.locale')
758
+ if (savedLocale === 'zh' || savedLocale === 'en') locale = savedLocale
759
+ apiGet(API.config).then(function (d) {
760
+ if (d && d.config && d.config.locale) {
761
+ var l = d.config.locale
762
+ if (l !== locale) { locale = l; try { localStorage.setItem('dsh-auto-memory.locale', l) } catch (e2) {}; emit() }
763
+ }
764
+ }).catch(function () {})
765
+ } catch (e1) {}
766
+ // 语言切换时通知所有订阅者重渲染
767
+ onLocale(function () { emit() })
768
+
642
769
  try {
643
770
  slots.inject('sidebar.footer.action', function () {
644
- return slots.register({ name: 'sidebar.footer.action', id: 'auto-memory', order: 5, label: '记忆' }, function () { return h(SidebarButton) })
771
+ return slots.register({ name: 'sidebar.footer.action', id: 'auto-memory', order: 5, label: t('memory') }, function () { return h(SidebarButton) })
645
772
  })
646
773
  slots.inject('shell.overlay', function () {
647
774
  return slots.register({ name: 'shell.overlay', id: 'auto-memory', order: 5 }, function () { return h(MemoryPanel) })
648
775
  })
649
776
  slots.inject('settings.section', function () {
650
- return slots.register({ name: 'settings.section', id: 'auto-memory', order: 25, label: '自动记忆' }, function (props) { return h(SettingsPage, { close: props && props.close }) })
777
+ return slots.register({ name: 'settings.section', id: 'auto-memory', order: 25, label: t('autoMemory') }, function (props) { return h(SettingsPage, { close: props && props.close }) })
651
778
  })
652
779
  } catch (e) {
653
780
  console.warn('[dsh-auto-memory] slot registration failed', e)
package/lib/index.js CHANGED
@@ -61,6 +61,8 @@ const DEFAULT_CONFIG = {
61
61
  reflectEnabled: true,
62
62
  /** 反思风格: auto=由内容决定 / life=生活化 / professional=专业性。 */
63
63
  reflectStyle: 'auto',
64
+ /** UI 语言: zh=中文 / en=English。 */
65
+ locale: 'zh',
64
66
  /** 外部记忆注入预算(字符)。 */
65
67
  externalInjectionChars: 1400,
66
68
  /** 外部记忆源开关(CodeBuddy/Claude Code/Codex/项目约定等)。 */
@@ -322,10 +324,11 @@ class MemoryEngine {
322
324
  }
323
325
  lines.push('\n[记忆写入纪律 — 必须遵守]')
324
326
  lines.push('- 会话开始:若任务与历史工作/历史决策相关,先回顾以上记忆;**遇到不熟悉的代码、领域或项目时,主动调用 memory_recall 检索本机所有 AI 工具的历史记忆(CodeBuddy/Claude Code/Codex 等会话),不要凭空猜测**。')
325
- lines.push('- 新工作区(无历史日志/笔记):主动用 memory_recall 探索本机历史,判断该项目是否曾在其他 AI 工具中工作过;也可调用 memory_external 查看并接入外部记忆。')
327
+ lines.push('- 新工作区(无历史日志/笔记):主动用 memory_recall 探索本机历史,判断该项目是否曾在其他 AI 工具中工作过;也可调用 memory_external 查看并接入外部记忆;检索时在正文中说明"我先查一下之前的记录"。')
326
328
  lines.push('- 完成实质性工作后立即调用 memory_log 追加今日日志(append-only,绝不覆盖):建/改应用、修 bug、写文档、重构、技术选型、用户约定或偏好。')
327
329
  lines.push('- progress 与 memory 一起写:写日志的同时,把有跨会话长期价值的内容一并写入记忆——跨项目规则 → memory_user,仅本项目 → memory_note;两者在同一轮完成,互不冲突、不遗漏。')
328
330
  lines.push('- 只记录有跨会话长期价值的;不记临时信息(搜索结果、临时路径、工具报错)。')
331
+ lines.push('- **记忆操作对用户可见**:调用 memory_log/note/user/reflect 更新记忆时,在同一轮回复的正文中(非工具调用内部)用一句话向用户明文说明——"正在更新哪份文档、加入什么内容";调用 memory_recall/memory_external 检索时,也在正文中说明检索了什么、找到了什么,让用户直观看到记忆在工作。')
329
332
  lines.push('- 用户明确要求长期记住:跨项目规则 → memory_user;仅本项目 → memory_note。')
330
333
  lines.push('- 定期调用 memory_maintain 归档 30 天前日志;不存密钥,除非用户明确要求。')
331
334
  lines.push('- 记忆仅作补充,不替代正常回复与交付物。')
@@ -430,8 +433,8 @@ class MemoryEngine {
430
433
  }
431
434
  }
432
435
  } catch (e) {}
433
- if (!out.length) return '未找到与 "' + query + '" 相关的记忆。'
434
- return out.join('\n')
436
+ if (!out.length) return '[记忆检索] 查询 "' + q + '" —— 未找到相关记忆。'
437
+ return '[记忆检索] "' + q + '":\n' + out.join('\n')
435
438
  }
436
439
 
437
440
  // ---------- 反思 ----------
@@ -922,7 +925,7 @@ export function apply(ctx, config) {
922
925
  const entry = '- ' + nowHm() + ' ' + String(args.note).trim()
923
926
  const body = await engine.appendText(logPath, entry)
924
927
  if (date === todayStr()) { engine.state.logText = body; engine.state.logPath = logPath; engine.state.loadedAt = Date.now() }
925
- return '已追加到 ' + logPath + '\n' + entry
928
+ return '已更新记忆文档: ' + logPath + '\n' + entry
926
929
  }),
927
930
 
928
931
  defineTool('memory_note', '更新当前项目长期笔记 .dsh-memory/MEMORY.md(本项目专属的约定、决策、架构要点)。action=append 追加一段(自动带日期标题);action=replace 整体替换(需先基于注入内容或 memory_recall 结果给出完整新内容)。', {
@@ -940,7 +943,7 @@ export function apply(ctx, config) {
940
943
  body = await engine.appendText(p.notesPath, '\n## ' + todayStr() + '\n' + content)
941
944
  }
942
945
  engine.state.notesText = body; engine.state.loadedAt = Date.now()
943
- return '已更新 ' + p.notesPath
946
+ return '已更新项目笔记: ' + p.notesPath + '\n追加内容:\n' + content
944
947
  }),
945
948
 
946
949
  defineTool('memory_user', '更新用户级记忆 ~/.dsh/memory/MEMORY.md(跨所有项目的长期规则/偏好,用户明确要求记住时用)。action=append 追加;action=replace 整体替换。', {
@@ -958,7 +961,7 @@ export function apply(ctx, config) {
958
961
  body = await engine.appendText(p.userFile, '\n## ' + todayStr() + '\n' + content)
959
962
  }
960
963
  engine.state.userText = body; engine.state.loadedAt = Date.now()
961
- return '已更新 ' + p.userFile
964
+ return '已更新用户级记忆: ' + p.userFile + '\n追加内容:\n' + content
962
965
  }),
963
966
 
964
967
  defineTool('memory_recall', '检索记忆:本地记忆文件(每日日志、项目笔记、用户级记忆、反思)关键词匹配 + 历史 DSH 会话全文检索(如部署启用)。用户提到过去的做法/讨论/决定而当前上下文没有时调用,查询必须自包含。', {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@a9i5k4/dsh-auto-memory",
3
3
  "description": "DSH 自动记忆插件:三层记忆(用户级/项目笔记/每日日志)自动注入与检索、每日反思、可视化面板与设置页,支持继承其他 AI 工具的记忆。",
4
- "version": "0.1.2",
4
+ "version": "0.1.4",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {