@a9i5k4/dsh-auto-memory 0.1.9 → 0.1.11
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 +38 -86
- package/README.zh-CN.md +41 -83
- package/lib/client.js +129 -49
- package/lib/index.js +115 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
# dsh-auto-memory — DSH Auto Memory Plugin / DSH 自动记忆插件
|
|
2
2
|
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img width="820" alt="dsh-auto-memory banner" src="docs/banner.jpg">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
3
7
|
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, AI period greetings with three-level drawers, auto-consolidation after every turn, smart search, a calendar view and settings page, and inheritance of memories from other AI tools.
|
|
4
8
|
|
|
5
9
|
DSH Web GUI 的记忆插件:三层记忆自动注入与检索、每日反思、AI 时段问候与三级抽屉、每轮自动沉淀、智能检索、日历视图与设置页,支持继承其他 AI 工具的历史记忆。
|
|
6
10
|
|
|
11
|
+
> **Quick install**: `cd ~/.dsh/profiles/web` → `pnpm add @a9i5k4/dsh-auto-memory` → append `"@a9i5k4/dsh-auto-memory"` to `dsh.profile.bundles` in that directory's `package.json` → restart **dsh web** (the 「Memory」entry appears in the sidebar). Full instructions in [Installation](#installation-one-command); no-pnpm alternative: `npm install @a9i5k4/dsh-auto-memory`.
|
|
12
|
+
|
|
7
13
|
[**English**](README.md) | [中文版](README.zh-CN.md)
|
|
8
14
|
|
|
9
15
|
---
|
|
@@ -80,109 +86,42 @@ The Search tab adds a **Smart search** button next to the keyword search:
|
|
|
80
86
|
|
|
81
87
|
---
|
|
82
88
|
|
|
83
|
-
##
|
|
84
|
-
|
|
85
|
-
### 1. Auto-consolidation in action
|
|
86
|
-
|
|
87
|
-
You finish a conversation turn — no memory commands needed. The moment the turn ends, the plugin writes a topic-grouped entry into today's log:
|
|
88
|
-
|
|
89
|
-
```
|
|
90
|
-
── Chat ──────────────────────────────────────────────────
|
|
91
|
-
|
|
92
|
-
You: 我把抽屉的 bug 修好了,顺便加了字号功能,设置页可以调,默认大号。
|
|
89
|
+
## Screenshots
|
|
93
90
|
|
|
94
|
-
|
|
91
|
+
All screenshots below are real captures of the plugin running inside the DSH Web GUI.
|
|
95
92
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
## 修复抽屉bug与字号功能(22:30)
|
|
99
|
-
- 修复了抽屉 bug
|
|
100
|
-
- 新增界面字号功能(设置页,默认大号)
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Long-term decisions are promoted to project notes with a `## YYYY-MM-DD` heading automatically. If the AI call fails, the entry is queued and retried by the 5-minute polling loop.
|
|
104
|
-
|
|
105
|
-
### 2. AI greeting & three-level drawers
|
|
106
|
-
|
|
107
|
-
Click the 「Memory」 button — the floating panel opens with an AI-written greeting, cross-workspace summaries, and drawers titled by the AI summaries themselves:
|
|
108
|
-
|
|
109
|
-
```
|
|
110
|
-
┌──────────────────────────────────────────────────────┐
|
|
111
|
-
│ 自动记忆 ⤾ ⟳ ✕ │
|
|
112
|
-
├──────────────────────────────────────────────────────┤
|
|
113
|
-
│ 概览 │ 日志 │ 笔记 │ 反思 │ 接续 │ 日历 │ 检索 │ 工作区 │
|
|
114
|
-
├──────────────────────────────────────────────────────┤
|
|
115
|
-
│ 晚上好 · 晚上 │
|
|
116
|
-
│ │
|
|
117
|
-
│ 晚上好,这一晚上你收获满满呀。把 Ark9Tools 扩展框架整个 │
|
|
118
|
-
│ 捋了一遍,冒烟测试 18 项全过,还一口气把 dsh-auto-memory │
|
|
119
|
-
│ 推到 0.1.9… │
|
|
120
|
-
│ │
|
|
121
|
-
│ ┌ 工作区总结(跨工作区) ────────────────────────────┐ │
|
|
122
|
-
│ │ dsh_debug · 8-10 ~ 8-14 │ │
|
|
123
|
-
│ │ 主要在做 dsh-auto-memory 插件,逐步发布 v0.1.x… │ │
|
|
124
|
-
│ │ · 8-14: v0.1.9 系列功能落地 │ │
|
|
125
|
-
│ │ · 8-13: 中文乱码与 no-branches 修复 │ │
|
|
126
|
-
│ └────────────────────────────────────────────────┘ │
|
|
127
|
-
│ │
|
|
128
|
-
│ ▼ 下午把 dsh-auto-memory 完整发布出去了… │
|
|
129
|
-
│ 今日下午 · 10 条日志 · 生成于 21:28 │
|
|
130
|
-
│ ▸ 发布与收尾 (3 项) │
|
|
131
|
-
│ ▸ 修复发布过程中的坑 (2 项) │
|
|
132
|
-
│ │
|
|
133
|
-
│ ▼ 晚上搭建了扩展框架… │
|
|
134
|
-
│ 今日晚上 · 7 条日志 · 生成于 21:28 │
|
|
135
|
-
│ ▸ EventBus 与服务容器 (4 项) │
|
|
136
|
-
└──────────────────────────────────────────────────────┘
|
|
137
|
-
```
|
|
93
|
+
### Main interface — the Auto Memory panel (Connect tab)
|
|
138
94
|
|
|
139
|
-
|
|
95
|
+
<img width="720" alt="Auto Memory panel in the DSH web UI" src="docs/screenshots/main-connect-en.png">
|
|
140
96
|
|
|
141
|
-
###
|
|
97
|
+
### Connect tab — inherit memories accumulated by other AI tools
|
|
142
98
|
|
|
143
|
-
|
|
99
|
+
<img width="720" alt="Connect tab" src="docs/screenshots/connect-en.png">
|
|
144
100
|
|
|
145
|
-
|
|
146
|
-
┌──────────────────────────────────────────────────────┐
|
|
147
|
-
│ 检索: [上次发布 npm 包踩了什么坑 ] [检索] [智能检索]│
|
|
148
|
-
├──────────────────────────────────────────────────────┤
|
|
149
|
-
│ ┌ 智能回答 ───────────────────────────────────────┐ │
|
|
150
|
-
│ │ 上次发布踩了三个坑:GitHub 中文 description 乱码 │ │
|
|
151
|
-
│ │ (PowerShell GBK,改用 Node fetch);第一次 push │ │
|
|
152
|
-
│ │ 实际没成功(凭据管理器拦截,需 -c credential.helper=);│ │
|
|
153
|
-
│ │ npm 2FA 需要 Automation 令牌。以上来自 │ │
|
|
154
|
-
│ │ 2026-08-14 日志。 │ │
|
|
155
|
-
│ └────────────────────────────────────────────────┘ │
|
|
156
|
-
│ 关键词: 发布 / 踩坑 / GitHub / npm / 推送 │
|
|
157
|
-
│ · [2026-08-14.md] 发布完成:①GitHub 仓库已建… │
|
|
158
|
-
│ · [reflections/2026-08-14.md] 成果回顾:… │
|
|
159
|
-
└──────────────────────────────────────────────────────┘
|
|
160
|
-
```
|
|
101
|
+
### Overview — AI-written greeting, today's drawers & cross-workspace summaries
|
|
161
102
|
|
|
162
|
-
|
|
103
|
+
<img width="720" alt="Overview tab" src="docs/screenshots/overview-en.png">
|
|
163
104
|
|
|
164
|
-
###
|
|
105
|
+
### Calendar — AI-maintained four-quadrant schedule
|
|
165
106
|
|
|
166
|
-
|
|
107
|
+
<img width="720" alt="Calendar tab" src="docs/screenshots/calendar-en.png">
|
|
167
108
|
|
|
168
|
-
|
|
169
|
-
── Chat ──────────────────────────────────────────────────
|
|
109
|
+
### Reflections — structured daily reviews
|
|
170
110
|
|
|
171
|
-
|
|
111
|
+
<img width="720" alt="Reflections tab" src="docs/screenshots/reflections-en.png">
|
|
172
112
|
|
|
173
|
-
|
|
113
|
+
### Settings — memory storage, day boundary, daily budget, update check
|
|
174
114
|
|
|
175
|
-
|
|
115
|
+
<img width="720" alt="Auto Memory settings" src="docs/screenshots/settings-en.png">
|
|
176
116
|
|
|
177
|
-
|
|
178
|
-
11 12 13 14 [15]
|
|
179
|
-
● 交周报
|
|
180
|
-
```
|
|
117
|
+
## Beyond the screenshots
|
|
181
118
|
|
|
182
|
-
|
|
119
|
+
- **Auto-consolidation**: every finished turn is evaluated by a small subagent and topic-grouped entries are written to today's log automatically (`## 主题(HH:MM)` + bullet points) — no `memory_log` needed for routine work. Long-term value is promoted to project notes / user-level memory, small talk is skipped, failures are queued and retried every 5 minutes (a 15-second heartbeat file proves the loop is alive).
|
|
120
|
+
- **Smart search**: ask in natural language — the AI expands your query into keywords, scans every memory layer, then answers conversationally with sources cited.
|
|
121
|
+
- **Calendar reminders**: pending items are injected into future sessions' system prompts until completed — the AI reminds you without being asked.
|
|
122
|
+
- **One-click update**: the settings page shows your installed version vs. the npm registry latest; registry installs get a one-click update button (pnpm/npm runs under the hood), then restart to apply.
|
|
183
123
|
|
|
184
124
|
---
|
|
185
|
-
|
|
186
125
|
## Installation (one command)
|
|
187
126
|
|
|
188
127
|
> Prerequisite: install [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) and start `dsh web` at least once.
|
|
@@ -204,6 +143,19 @@ Restart **dsh web** to activate (the 「Memory」entry appears in the sidebar).
|
|
|
204
143
|
|
|
205
144
|
> No pnpm? Use npm: `npm install @a9i5k4/dsh-auto-memory`
|
|
206
145
|
|
|
146
|
+
## Updating (check & upgrade)
|
|
147
|
+
|
|
148
|
+
The plugin is a normal npm package, so updating is one command in the same profile directory:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
cd ~/.dsh/profiles/web
|
|
152
|
+
pnpm up @a9i5k4/dsh-auto-memory # or: npm install @a9i5k4/dsh-auto-memory@latest
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Then restart **dsh web**.
|
|
156
|
+
|
|
157
|
+
The Settings → Auto Memory page has a **Check for updates** button that compares your installed version with the latest one on the npm registry (it shows the update command when a newer version exists).
|
|
158
|
+
|
|
207
159
|
---
|
|
208
160
|
|
|
209
161
|
## AI-Era Installation (copy-paste for your AI)
|
package/README.zh-CN.md
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
# dsh-auto-memory — DSH 自动记忆插件
|
|
2
2
|
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img width="820" alt="dsh-auto-memory 宣传图" src="docs/banner.jpg">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
3
7
|
DSH Web GUI 的记忆插件:三层记忆(用户级 / 项目笔记 / 每日日志)自动注入与检索、每日反思、AI 时段问候与三级抽屉、每轮自动沉淀、智能检索、日历视图与设置页,支持继承其他 AI 工具的历史记忆。
|
|
4
8
|
|
|
9
|
+
> **快速安装**:`cd ~/.dsh/profiles/web` → `pnpm add @a9i5k4/dsh-auto-memory` → 在该目录 `package.json` 的 `dsh.profile.bundles` 里追加 `"@a9i5k4/dsh-auto-memory"` → 重启 **dsh web**(侧边栏出现「记忆」入口)。完整步骤见 [安装](#安装npm-一键);没有 pnpm 可用 `npm install @a9i5k4/dsh-auto-memory`。
|
|
10
|
+
|
|
5
11
|
[English](README.md) | [中文版](README.zh-CN.md)
|
|
6
12
|
|
|
7
13
|
---
|
|
@@ -78,109 +84,48 @@ DSH Web GUI 的记忆插件:三层记忆(用户级 / 项目笔记 / 每日
|
|
|
78
84
|
|
|
79
85
|
---
|
|
80
86
|
|
|
81
|
-
##
|
|
87
|
+
## 界面截图
|
|
82
88
|
|
|
83
|
-
|
|
89
|
+
以下都是插件在 DSH Web GUI 中的真实运行截图。
|
|
84
90
|
|
|
85
|
-
|
|
91
|
+
### 主界面 — 「自动记忆」浮窗(接续页签)
|
|
86
92
|
|
|
87
|
-
|
|
88
|
-
── 对话 ──────────────────────────────────────────────────
|
|
93
|
+
<img width="720" alt="自动记忆浮窗" src="docs/screenshots/main-connect-zh.png">
|
|
89
94
|
|
|
90
|
-
|
|
95
|
+
### 接续 — 继承其他 AI 工具积累的记忆
|
|
91
96
|
|
|
92
|
-
|
|
97
|
+
<img width="720" alt="接续页签" src="docs/screenshots/connect-zh.png">
|
|
93
98
|
|
|
94
|
-
|
|
99
|
+
### 概览 — AI 问候语、今日抽屉与跨工作区总结
|
|
95
100
|
|
|
96
|
-
|
|
97
|
-
- 修复了抽屉 bug
|
|
98
|
-
- 新增界面字号功能(设置页,默认大号)
|
|
99
|
-
```
|
|
101
|
+
<img width="720" alt="概览页签" src="docs/screenshots/overview-zh.png">
|
|
100
102
|
|
|
101
|
-
|
|
103
|
+
### 日历 — AI 维护的四象限日程
|
|
102
104
|
|
|
103
|
-
|
|
105
|
+
<img width="720" alt="日历页签" src="docs/screenshots/calendar-zh.png">
|
|
104
106
|
|
|
105
|
-
|
|
107
|
+
### 检索 — 关键词检索与智能检索
|
|
106
108
|
|
|
107
|
-
|
|
108
|
-
┌──────────────────────────────────────────────────────┐
|
|
109
|
-
│ 自动记忆 ⤾ ⟳ ✕ │
|
|
110
|
-
├──────────────────────────────────────────────────────┤
|
|
111
|
-
│ 概览 │ 日志 │ 笔记 │ 反思 │ 接续 │ 日历 │ 检索 │ 工作区 │
|
|
112
|
-
├──────────────────────────────────────────────────────┤
|
|
113
|
-
│ 晚上好 · 晚上 │
|
|
114
|
-
│ │
|
|
115
|
-
│ 晚上好,这一晚上你收获满满呀。把 Ark9Tools 扩展框架整个 │
|
|
116
|
-
│ 捋了一遍,冒烟测试 18 项全过,还一口气把 dsh-auto-memory │
|
|
117
|
-
│ 推到 0.1.9… │
|
|
118
|
-
│ │
|
|
119
|
-
│ ┌ 工作区总结(跨工作区) ────────────────────────────┐ │
|
|
120
|
-
│ │ dsh_debug · 8-10 ~ 8-14 │ │
|
|
121
|
-
│ │ 主要在做 dsh-auto-memory 插件,逐步发布 v0.1.x… │ │
|
|
122
|
-
│ │ · 8-14: v0.1.9 系列功能落地 │ │
|
|
123
|
-
│ │ · 8-13: 中文乱码与 no-branches 修复 │ │
|
|
124
|
-
│ └────────────────────────────────────────────────┘ │
|
|
125
|
-
│ │
|
|
126
|
-
│ ▼ 下午把 dsh-auto-memory 完整发布出去了… │
|
|
127
|
-
│ 今日下午 · 10 条日志 · 生成于 21:28 │
|
|
128
|
-
│ ▸ 发布与收尾 (3 项) │
|
|
129
|
-
│ ▸ 修复发布过程中的坑 (2 项) │
|
|
130
|
-
│ │
|
|
131
|
-
│ ▼ 晚上搭建了扩展框架… │
|
|
132
|
-
│ 今日晚上 · 7 条日志 · 生成于 21:28 │
|
|
133
|
-
│ ▸ EventBus 与服务容器 (4 项) │
|
|
134
|
-
└──────────────────────────────────────────────────────┘
|
|
135
|
-
```
|
|
109
|
+
<img width="720" alt="检索页签" src="docs/screenshots/search-zh.png">
|
|
136
110
|
|
|
137
|
-
|
|
111
|
+
### 反思(英文界面示例)
|
|
138
112
|
|
|
139
|
-
|
|
113
|
+
<img width="720" alt="反思页签" src="docs/screenshots/reflections-en.png">
|
|
140
114
|
|
|
141
|
-
|
|
115
|
+
### 设置 — 记忆存储、日界、每日预算、更新检查
|
|
142
116
|
|
|
143
|
-
|
|
144
|
-
┌──────────────────────────────────────────────────────┐
|
|
145
|
-
│ 检索: [上次发布 npm 包踩了什么坑 ] [检索] [智能检索]│
|
|
146
|
-
├──────────────────────────────────────────────────────┤
|
|
147
|
-
│ ┌ 智能回答 ───────────────────────────────────────┐ │
|
|
148
|
-
│ │ 上次发布踩了三个坑:GitHub 中文 description 乱码 │ │
|
|
149
|
-
│ │ (PowerShell GBK,改用 Node fetch);第一次 push │ │
|
|
150
|
-
│ │ 实际没成功(凭据管理器拦截,需 -c credential.helper=);│ │
|
|
151
|
-
│ │ npm 2FA 需要 Automation 令牌。以上来自 │ │
|
|
152
|
-
│ │ 2026-08-14 日志。 │ │
|
|
153
|
-
│ └────────────────────────────────────────────────┘ │
|
|
154
|
-
│ 关键词: 发布 / 踩坑 / GitHub / npm / 推送 │
|
|
155
|
-
│ · [2026-08-14.md] 发布完成:①GitHub 仓库已建… │
|
|
156
|
-
│ · [reflections/2026-08-14.md] 成果回顾:… │
|
|
157
|
-
└──────────────────────────────────────────────────────┘
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
AI 把你的查询扩散成 3-6 个关键词,扫描全部记忆层,再综合成一段自然语言回答并注明来源——绝不编造记忆里没有的事实。
|
|
161
|
-
|
|
162
|
-
### 4. AI 维护日历
|
|
163
|
-
|
|
164
|
-
对话里提一句 deadline,AI 就写进日历并明文转述:
|
|
165
|
-
|
|
166
|
-
```
|
|
167
|
-
── 对话 ──────────────────────────────────────────────────
|
|
117
|
+
<img width="720" alt="自动记忆设置" src="docs/screenshots/settings-zh.png">
|
|
168
118
|
|
|
169
|
-
|
|
119
|
+
<img width="720" alt="自动记忆设置(调试中心)" src="docs/screenshots/settings-debug-zh.png">
|
|
170
120
|
|
|
171
|
-
|
|
121
|
+
## 截图之外
|
|
172
122
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
● 交周报
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
未完成事项会注入之后每次会话的系统提示词——AI 不用你提醒就会主动提及。
|
|
123
|
+
- **每轮自动沉淀**:每轮对话结束由小代理自动评估,按主题分组写进今日日志(`## 主题(HH:MM)` + 要点列表)——常规工作不需要手动 memory_log。有长期价值的内容自动升格项目笔记 / 用户级记忆;寒暄轮跳过;AI 失败入队,每 5 分钟重试(15 秒心跳文件证明轮询存活)。
|
|
124
|
+
- **智能检索**:自然语言提问,AI 扩成关键词扫描全部记忆层,再综合成带出处的自然语言回答。
|
|
125
|
+
- **日历提醒**:未完成事项注入之后每次会话的系统提示词——AI 不用你提醒就会主动提及。
|
|
126
|
+
- **一键更新**:设置页对比本地版本与 npm registry 最新版;registry 安装的用户可直接「一键更新」(后台自动跑 pnpm/npm),重启后生效。
|
|
181
127
|
|
|
182
128
|
---
|
|
183
|
-
|
|
184
129
|
## 安装(NPM 一键)
|
|
185
130
|
|
|
186
131
|
> 前提:已安装 DeepSeek Harness(dsh)并至少启动过一次 `dsh web`。
|
|
@@ -202,6 +147,19 @@ pnpm add @a9i5k4/dsh-auto-memory
|
|
|
202
147
|
|
|
203
148
|
> 没有 pnpm?用 npm 也行:`npm install @a9i5k4/dsh-auto-memory`
|
|
204
149
|
|
|
150
|
+
## 更新(检查与升级)
|
|
151
|
+
|
|
152
|
+
插件就是普通的 npm 包,更新同样是在 profile 目录里一条命令:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
cd ~/.dsh/profiles/web
|
|
156
|
+
pnpm up @a9i5k4/dsh-auto-memory # 或: npm install @a9i5k4/dsh-auto-memory@latest
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
然后**重启 dsh web** 生效。
|
|
160
|
+
|
|
161
|
+
设置 → 自动记忆 页面有「检查更新」按钮,会拿你当前安装的版本和 npm registry 上的最新版对比(有新版时直接显示更新命令)。
|
|
162
|
+
|
|
205
163
|
---
|
|
206
164
|
|
|
207
165
|
## AI 时代安装(把这句话直接丢给 AI)
|
package/lib/client.js
CHANGED
|
@@ -105,6 +105,14 @@ window.__ModuleLoader__.load({
|
|
|
105
105
|
fBrowse: '浏览…', browseTitle: '选择记忆根目录', fUp: '上级', fSelectDir: '选择此目录',
|
|
106
106
|
pickedDir: '已选择目录:', rememberSave: '(点击「保存设置」后生效)', pickerUnavailable: '系统文件夹选择器不可用,改用内嵌浏览。',
|
|
107
107
|
fDayBoundary: '日界(分钟)', fDayBoundaryHint: '从 0 点起算:凌晨在此之前的活儿归前一天。默认 450=早上 7:30 进入新一天;改 480=8:00;0=按午夜切日。',
|
|
108
|
+
fVersion: '插件版本', checkUpdate: '检查更新', checking: '检查中…', upToDate: '(已是最新)', hasUpdate: '(有新版本)', versionError: '版本检查失败: ', versionCmdHint: '更新命令: cd ~/.dsh/profiles/web && pnpm up @a9i5k4/dsh-auto-memory,然后重启 dsh web 生效。',
|
|
109
|
+
updateNow: '一键更新', updating: '更新中…', updateDone: '更新完成,请重启 dsh web 生效。', updateFailed: '更新失败: ', devLinkHint: '(当前为本地开发链接,请直接同步开发源码)', noProfileHint: '(未找到 dsh profile,无法自动更新)',
|
|
110
|
+
kvVersion: '插件版本', kvUnreadable: '(读不到)', kvPid: 'PID / 启动', kvRestart: 'Host 需重启?', kvRestartYes: '是(代码比进程新)', kvRestartNo: '否',
|
|
111
|
+
kvHeartbeat: '轮询心跳', hbAliveAgo: '存活(上次 ', hbSecAgo: ' 秒前)', hbAlive: '存活', hbNone: '无心跳文件',
|
|
112
|
+
kvQueue: '沉淀重试队列', kvCountSuffix: ' 条', kvToday: '今日沉淀', kvRecentPrefix: ' 条 / 最近 ', kvBusy: '正在沉淀中', kvYes: '是', kvNo: '否',
|
|
113
|
+
kvAvail: '可用', kvUnavail: '不可用', kvDup: '项目笔记重复标题', kvDupCount: ' 个',
|
|
114
|
+
kvMem: '记忆文件', kvMemUser: '用户', kvMemNotes: '笔记', kvMemLog: '日志', kvMemMissing: '缺失',
|
|
115
|
+
kvWs: '当前工作区', kvWsUnknown: '(未取到)', kvApi: 'API 连通', unknown: '(未知)',
|
|
108
116
|
refresh: '刷新', refreshing: '刷新中…', close: '关闭', dragMove: '拖动移动', dragResize: '拖动调整大小', resetPos: '恢复默认位置',
|
|
109
117
|
generated: '已生成', failed: '失败: ',
|
|
110
118
|
generatedAt: '生成于 ',
|
|
@@ -154,7 +162,7 @@ window.__ModuleLoader__.load({
|
|
|
154
162
|
fDays: '注入最近日志天数', fDaysHint: '会话开始时注入最近 N 天的工作日志尾部。',
|
|
155
163
|
fReflect: '每日反思', fReflectHint: '昨天有工作日志时,会话首轮主动呈现昨日反思。',
|
|
156
164
|
fStyle: '反思风格', fStyleHint: '生活化 / 专业性 / 由内容决定。',
|
|
157
|
-
fLocale: '界面语言', fLocaleHint: '
|
|
165
|
+
fLocale: '界面语言', fLocaleHint: '默认跟随 DSH 系统语言;也可手动指定中文 / English。', followSystem: '跟随系统语言',
|
|
158
166
|
saveSettings: '保存设置',
|
|
159
167
|
zh: '中文', en: 'English',
|
|
160
168
|
calendar: '日历', addItem: '添加', save: '保存', cancel: '取消', needTitle: '请填写事项标题', itemTitle: '事项标题…',
|
|
@@ -178,6 +186,14 @@ window.__ModuleLoader__.load({
|
|
|
178
186
|
fBrowse: 'Browse…', browseTitle: 'Choose memory root', fUp: 'Up', fSelectDir: 'Select this folder',
|
|
179
187
|
pickedDir: 'Selected folder:', rememberSave: '(click Save to apply)', pickerUnavailable: 'Native folder picker unavailable, falling back to in-app browser.',
|
|
180
188
|
fDayBoundary: 'Day boundary (minutes)', fDayBoundaryHint: 'Minutes from midnight: work before it counts as the previous day. Default 450 = 7:30 AM starts the new day; 480 = 8:00; 0 = midnight cut.',
|
|
189
|
+
fVersion: 'Plugin version', checkUpdate: 'Check for updates', checking: 'Checking…', upToDate: '(up to date)', hasUpdate: '(update available)', versionError: 'Version check failed: ', versionCmdHint: 'Update: cd ~/.dsh/profiles/web && pnpm up @a9i5k4/dsh-auto-memory, then restart dsh web.',
|
|
190
|
+
updateNow: 'Update now', updating: 'Updating…', updateDone: 'Update finished, restart dsh web to apply.', updateFailed: 'Update failed: ', devLinkHint: '(local dev link, sync source instead)', noProfileHint: '(no dsh profile found, cannot auto-update)',
|
|
191
|
+
kvVersion: 'Plugin version', kvUnreadable: '(unreadable)', kvPid: 'PID / Started', kvRestart: 'Host restart needed?', kvRestartYes: 'yes (code newer than process)', kvRestartNo: 'no',
|
|
192
|
+
kvHeartbeat: 'Heartbeat', hbAliveAgo: 'alive (last ', hbSecAgo: ' s ago)', hbAlive: 'alive', hbNone: 'no heartbeat file',
|
|
193
|
+
kvQueue: 'Consolidation retry queue', kvCountSuffix: ' item(s)', kvToday: 'Consolidated today', kvRecentPrefix: ' item(s) / latest ', kvBusy: 'Consolidating now', kvYes: 'yes', kvNo: 'no',
|
|
194
|
+
kvAvail: 'available', kvUnavail: 'unavailable', kvDup: 'Duplicate note headings', kvDupCount: '',
|
|
195
|
+
kvMem: 'Memory files', kvMemUser: 'user', kvMemNotes: 'notes', kvMemLog: 'log', kvMemMissing: 'missing',
|
|
196
|
+
kvWs: 'Current workspace', kvWsUnknown: '(unknown)', kvApi: 'API probes', unknown: '(unknown)',
|
|
181
197
|
refresh: 'Refresh', refreshing: 'Refreshing…', close: 'Close', dragMove: 'Drag to move', dragResize: 'Drag to resize', resetPos: 'Reset position',
|
|
182
198
|
generated: 'Generated', failed: 'Failed: ',
|
|
183
199
|
generatedAt: 'Generated ',
|
|
@@ -227,7 +243,7 @@ window.__ModuleLoader__.load({
|
|
|
227
243
|
fDays: 'Recent days injected', fDaysHint: 'Inject tails of the last N days of work logs at session start.',
|
|
228
244
|
fReflect: 'Daily reflection', fReflectHint: 'When yesterday has logs, the agent presents the reflection at session start.',
|
|
229
245
|
fStyle: 'Reflection style', fStyleHint: 'Life-style / Professional / Auto.',
|
|
230
|
-
fLocale: 'UI language', fLocaleHint: '
|
|
246
|
+
fLocale: 'UI language', fLocaleHint: 'Follows the DSH system language by default; you can also pin Chinese / English.', followSystem: 'Follow system language',
|
|
231
247
|
saveSettings: 'Save settings',
|
|
232
248
|
zh: '中文', en: 'English',
|
|
233
249
|
calendar: 'Calendar', addItem: 'Add', save: 'Save', cancel: 'Cancel', needTitle: 'Title is required', itemTitle: 'Item title…',
|
|
@@ -241,6 +257,14 @@ window.__ModuleLoader__.load({
|
|
|
241
257
|
}
|
|
242
258
|
}
|
|
243
259
|
var locale = 'zh'
|
|
260
|
+
var localeMode = 'system' // 'system'=跟随 DSH 系统语言 | 'zh' | 'en'
|
|
261
|
+
var sysLocale = 'zh' // DSH 系统语言(跟随模式使用)
|
|
262
|
+
function applyLocalePref(m) {
|
|
263
|
+
if (m !== 'zh' && m !== 'en' && m !== 'system') m = 'system'
|
|
264
|
+
localeMode = m
|
|
265
|
+
var target = m === 'system' ? sysLocale : m
|
|
266
|
+
if (locale !== target) { locale = target; localeListeners.forEach(function (fn) { try { fn() } catch (e) {} }) }
|
|
267
|
+
}
|
|
244
268
|
var sessions = null
|
|
245
269
|
// 当前会话工作区(跟随 GUI 切换工作区):从 sessions 服务拿,供 state/summarize/greet 请求使用
|
|
246
270
|
function currentWs() {
|
|
@@ -272,6 +296,8 @@ window.__ModuleLoader__.load({
|
|
|
272
296
|
debug: '/api/dsh-auto-memory/debug',
|
|
273
297
|
browseDir: '/api/dsh-auto-memory/browse-dir',
|
|
274
298
|
pickDir: '/api/dsh-auto-memory/pick-dir',
|
|
299
|
+
updateCheck: '/api/dsh-auto-memory/update-check',
|
|
300
|
+
update: '/api/dsh-auto-memory/update',
|
|
275
301
|
config: '/api/dsh-auto-memory/config',
|
|
276
302
|
reflect: '/api/dsh-auto-memory/reflect',
|
|
277
303
|
reflectAuto: '/api/dsh-auto-memory/reflect-auto',
|
|
@@ -431,7 +457,8 @@ window.__ModuleLoader__.load({
|
|
|
431
457
|
if (!seenSeg[sname]) continue
|
|
432
458
|
var items = (ps && ps.groups && ps.groups[sname]) || []
|
|
433
459
|
if (!items.length) continue
|
|
434
|
-
|
|
460
|
+
var segEn = { '早晨': 'morning', '上午': 'forenoon', '中午': 'noon', '下午': 'afternoon', '晚上': 'evening' }[sname] || sname
|
|
461
|
+
drawers.push({ key: 'seg-' + sname, label: t('segPrefix') + t('seg' + segEn.charAt(0).toUpperCase() + segEn.slice(1)), hint: '', items: items.map(function (x) { return { time: '', text: x } }), withTime: false, defaultOpen: sname === curSegName, period: sname })
|
|
435
462
|
}
|
|
436
463
|
// 外层:生活化问候
|
|
437
464
|
var totalCount = 0
|
|
@@ -832,12 +859,13 @@ window.__ModuleLoader__.load({
|
|
|
832
859
|
}
|
|
833
860
|
|
|
834
861
|
// ───────────────────────── 日历页签 ─────────────────────────
|
|
862
|
+
var QUADRANT_I18N = { '重要紧急': 'qUrgentImportant', '重要不紧急': 'qImportant', '紧急不重要': 'qUrgent', '不重要不紧急': 'qNone', '未分类': 'qUncategorized' }
|
|
835
863
|
var QUADRANT_STYLE = {
|
|
836
|
-
'重要紧急': { color: 'var(--dsw-alias-state-error-primary, #d64545)'
|
|
837
|
-
'重要不紧急': { color: 'var(--dsw-alias-brand-primary, #4f7cff)'
|
|
838
|
-
'紧急不重要': { color: 'var(--dsw-alias-state-warn-primary, #e6a23c)'
|
|
839
|
-
'不重要不紧急': { color: 'var(--dsw-alias-label-secondary, #8a94a6)'
|
|
840
|
-
'未分类': { color: 'var(--dsw-alias-label-secondary, #8a94a6)'
|
|
864
|
+
'重要紧急': { color: 'var(--dsw-alias-state-error-primary, #d64545)' },
|
|
865
|
+
'重要不紧急': { color: 'var(--dsw-alias-brand-primary, #4f7cff)' },
|
|
866
|
+
'紧急不重要': { color: 'var(--dsw-alias-state-warn-primary, #e6a23c)' },
|
|
867
|
+
'不重要不紧急': { color: 'var(--dsw-alias-label-secondary, #8a94a6)' },
|
|
868
|
+
'未分类': { color: 'var(--dsw-alias-label-secondary, #8a94a6)' },
|
|
841
869
|
}
|
|
842
870
|
function CalendarTab() {
|
|
843
871
|
var dataPair = useState(null)
|
|
@@ -891,7 +919,7 @@ window.__ModuleLoader__.load({
|
|
|
891
919
|
rows.push(h('div', { 'data-dam-row': '', style: { flexWrap: 'wrap' } },
|
|
892
920
|
Object.keys(QUADRANT_STYLE).map(function (q) {
|
|
893
921
|
return h('span', { key: q, style: { fontSize: 'calc(11px * var(--dam-scale))', opacity: 0.8, marginRight: '10px' } },
|
|
894
|
-
h('span', { style: { display: 'inline-block', width: 8, height: 8, borderRadius: 2, background: QUADRANT_STYLE[q].color, marginRight: 4 } }),
|
|
922
|
+
h('span', { style: { display: 'inline-block', width: 8, height: 8, borderRadius: 2, background: QUADRANT_STYLE[q].color, marginRight: 4 } }), t(QUADRANT_I18N[q]))
|
|
895
923
|
})))
|
|
896
924
|
// 星期表头
|
|
897
925
|
rows.push(h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: '3px', marginBottom: '4px' } },
|
|
@@ -964,7 +992,7 @@ window.__ModuleLoader__.load({
|
|
|
964
992
|
h('div', { 'data-dam-row': '' },
|
|
965
993
|
h('input', { 'data-dam-input': '', type: 'time', value: draft.time, onChange: function (e) { var n = Object.assign({}, draft); n.time = e.target.value || '09:00'; setDraft(n) } }),
|
|
966
994
|
h('select', { 'data-dam-select': '', value: draft.quadrant, onChange: function (e) { var n = Object.assign({}, draft); n.quadrant = e.target.value; setDraft(n) } },
|
|
967
|
-
['重要紧急', '重要不紧急', '紧急不重要', '不重要不紧急'].map(function (q) { return h('option', { key: q, value: q },
|
|
995
|
+
['重要紧急', '重要不紧急', '紧急不重要', '不重要不紧急'].map(function (q) { return h('option', { key: q, value: q }, t(QUADRANT_I18N[q])) }))),
|
|
968
996
|
h('div', { 'data-dam-row': '' },
|
|
969
997
|
h('button', { 'data-dam-btn': '', onClick: saveDraft }, t('save')),
|
|
970
998
|
h('button', { 'data-dam-btn': '', onClick: function () { setDraft(null) } }, t('cancel')))))
|
|
@@ -1169,23 +1197,23 @@ window.__ModuleLoader__.load({
|
|
|
1169
1197
|
h('div', { 'data-dam-row': '' },
|
|
1170
1198
|
h('button', { 'data-dam-btn': '', onClick: refresh, disabled: busy }, busy ? t('dbgRefreshing') : t('dbgRefresh')),
|
|
1171
1199
|
h('span', { 'data-dam-hint': '' }, data.host.indexPath || '')),
|
|
1172
|
-
kv('
|
|
1173
|
-
kv('
|
|
1174
|
-
kv('
|
|
1175
|
-
kv('
|
|
1176
|
-
kv('
|
|
1177
|
-
kv('
|
|
1178
|
-
kv('
|
|
1179
|
-
kv('subagents', data.subagents.available ? (data.subagents.providers.join(', ') || '
|
|
1180
|
-
kv('
|
|
1181
|
-
kv('
|
|
1182
|
-
kv('
|
|
1183
|
-
kv('
|
|
1200
|
+
kv(t('kvVersion'), data.host.version || t('kvUnreadable'), false),
|
|
1201
|
+
kv(t('kvPid'), data.host.pid + ' / ' + (data.host.startTime ? new Date(data.host.startTime).toLocaleTimeString() : ''), false),
|
|
1202
|
+
kv(t('kvRestart'), data.host.needsRestart ? t('kvRestartYes') : t('kvRestartNo'), data.host.needsRestart),
|
|
1203
|
+
kv(t('kvHeartbeat'), hb.exists ? (hbAge >= 0 ? t('hbAliveAgo') + hbAge + t('hbSecAgo') : t('hbAlive')) : t('hbNone'), !hb.exists || hbAge > 360),
|
|
1204
|
+
kv(t('kvQueue'), data.autoConsolidate.pendingQueue + t('kvCountSuffix'), data.autoConsolidate.pendingQueue > 0),
|
|
1205
|
+
kv(t('kvToday'), data.autoConsolidate.stats.count + t('kvRecentPrefix') + (data.autoConsolidate.stats.lastAt ? new Date(data.autoConsolidate.stats.lastAt).toLocaleTimeString() : '—'), false),
|
|
1206
|
+
kv(t('kvBusy'), data.autoConsolidate.consolidating ? t('kvYes') : t('kvNo'), data.autoConsolidate.consolidating),
|
|
1207
|
+
kv('subagents', data.subagents.available ? (data.subagents.providers.join(', ') || t('kvAvail')) : t('kvUnavail'), !data.subagents.available),
|
|
1208
|
+
kv(t('kvDup'), data.duplicateHeadings + t('kvDupCount'), data.duplicateHeadings > 0),
|
|
1209
|
+
kv(t('kvMem'), t('kvMemUser') + ' ' + (data.memoryFiles.user.exists ? fmtSize(data.memoryFiles.user.size) : t('kvMemMissing')) + ' · ' + t('kvMemNotes') + ' ' + (data.memoryFiles.notes.exists ? fmtSize(data.memoryFiles.notes.size) : t('kvMemMissing')) + ' · ' + t('kvMemLog') + ' ' + (data.memoryFiles.log.exists ? fmtSize(data.memoryFiles.log.size) : t('kvMemMissing')), !data.memoryFiles.log.exists),
|
|
1210
|
+
kv(t('kvWs'), currentWs() || t('kvWsUnknown'), false),
|
|
1211
|
+
kv(t('kvApi'), probes ? Object.keys(probes).map(function (k) { return k + '=' + (probes[k].status !== undefined ? probes[k].status : probes[k].error) }).join(' ') : '…', apiBad))
|
|
1184
1212
|
}
|
|
1185
1213
|
|
|
1186
1214
|
// ───────────────────────── 设置页 ─────────────────────────
|
|
1187
|
-
var
|
|
1188
|
-
var
|
|
1215
|
+
var STYLE_IDS = ['auto', 'life', 'professional']
|
|
1216
|
+
var LOCALE_IDS_LIST = ['system', 'zh', 'en']
|
|
1189
1217
|
function SettingsPage() {
|
|
1190
1218
|
var tickPair = useTick()
|
|
1191
1219
|
var cfgPair = useState(null)
|
|
@@ -1240,18 +1268,47 @@ window.__ModuleLoader__.load({
|
|
|
1240
1268
|
browseTo(cfg.memoryRoot || '')
|
|
1241
1269
|
})
|
|
1242
1270
|
}
|
|
1271
|
+
var verPair = useState(null)
|
|
1272
|
+
var verInfo = verPair[0]
|
|
1273
|
+
var setVerInfo = verPair[1]
|
|
1274
|
+
var checkingPair = useState(false)
|
|
1275
|
+
var checkingUpdate = checkingPair[0]
|
|
1276
|
+
var setCheckingUpdate = checkingPair[1]
|
|
1277
|
+
var upBusyPair = useState(false)
|
|
1278
|
+
var upBusy = upBusyPair[0]
|
|
1279
|
+
var setUpBusy = upBusyPair[1]
|
|
1280
|
+
var upMsgPair = useState('')
|
|
1281
|
+
var upMsg = upMsgPair[0]
|
|
1282
|
+
var setUpMsg = upMsgPair[1]
|
|
1243
1283
|
useEffect(function () { return controller.subscribe(tickPair[1]) }, [])
|
|
1244
1284
|
useEffect(function () {
|
|
1245
1285
|
var alive = true
|
|
1246
1286
|
apiGet(API.config).then(function (d) { if (alive) setCfg(d.config) }).catch(function (e) { setErr(e.message) })
|
|
1287
|
+
// 打开设置页自动检查更新(host 有 12h 缓存,不重复查网)
|
|
1288
|
+
apiGet(API.updateCheck).then(function (d) { if (alive) setVerInfo(d) }).catch(function () {})
|
|
1247
1289
|
return function () { alive = false }
|
|
1248
1290
|
}, [])
|
|
1249
1291
|
if (!cfg) return err ? h('div', { 'data-dam-error': '' }, err) : h(Loading)
|
|
1250
1292
|
function set(key, value) { var next = Object.assign({}, cfg); next[key] = value; setCfg(next) }
|
|
1293
|
+
function checkUpdate() {
|
|
1294
|
+
if (checkingUpdate) return
|
|
1295
|
+
setCheckingUpdate(true)
|
|
1296
|
+
apiGet(API.updateCheck + '?force=1').then(function (d) { setVerInfo(d); setCheckingUpdate(false) })
|
|
1297
|
+
.catch(function (e) { setVerInfo({ error: e.message }); setCheckingUpdate(false) })
|
|
1298
|
+
}
|
|
1299
|
+
function doUpdate() {
|
|
1300
|
+
if (upBusy) return
|
|
1301
|
+
setUpBusy(true); setUpMsg('')
|
|
1302
|
+
apiPost(API.update, {}).then(function (d) {
|
|
1303
|
+
setUpBusy(false)
|
|
1304
|
+
if (d && d.ok) { setUpMsg(t('updateDone')); setVerInfo(null); checkUpdate() }
|
|
1305
|
+
else setUpMsg(t('updateFailed') + (d && d.message ? d.message : t('unknown')))
|
|
1306
|
+
}).catch(function (e) { setUpBusy(false); setUpMsg(t('updateFailed') + e.message) })
|
|
1307
|
+
}
|
|
1251
1308
|
function save() {
|
|
1252
1309
|
if (busy) return
|
|
1253
1310
|
setBusy(true); setMsg(''); setErr('')
|
|
1254
|
-
apiPost(API.config, cfg).then(function (d) { setCfg(d.config); setMsg(t('saved') + (d.migrated ? ' ' + d.migrated : '')); setBusy(false) })
|
|
1311
|
+
apiPost(API.config, cfg).then(function (d) { setCfg(d.config); setMsg(t('saved') + (d.migrated ? ' ' + d.migrated : '')); setBusy(false); if (d && d.config && d.config.locale) applyLocalePref(d.config.locale) })
|
|
1255
1312
|
.catch(function (e) { setErr(e.message); setBusy(false) })
|
|
1256
1313
|
}
|
|
1257
1314
|
function field(label, control, hint) {
|
|
@@ -1261,8 +1318,8 @@ window.__ModuleLoader__.load({
|
|
|
1261
1318
|
}
|
|
1262
1319
|
return h('div', null,
|
|
1263
1320
|
h('div', { 'data-dam-hint': '' }, t('settingsHeader')),
|
|
1264
|
-
field(t('fLocale'), h('select', { 'data-dam-select': '', value: cfg.locale || '
|
|
1265
|
-
|
|
1321
|
+
field(t('fLocale'), h('select', { 'data-dam-select': '', value: cfg.locale || 'system', onChange: function (e) { set('locale', e.target.value) } },
|
|
1322
|
+
LOCALE_IDS_LIST.map(function (id) { return h('option', { key: id, value: id }, id === 'system' ? t('followSystem') : t(id)) })), t('fLocaleHint')),
|
|
1266
1323
|
field(t('fFontSize'), h('select', { 'data-dam-select': '', value: fontScale, onChange: function (e) { fontScale = e.target.value; try { localStorage.setItem('dsh-auto-memory.fontScale.v2', fontScale) } catch (ee) {}; try { var pp = document.querySelector('[data-dam-panel]'); if (pp) pp.style.setProperty('--dam-scale', FONT_SCALE_VALUES[fontScale] || '1') } catch (ee2) {}; emit() } },
|
|
1267
1324
|
Object.keys(FONT_SCALES).map(function (k) { return h('option', { key: k, value: k }, t('fs' + k.charAt(0).toUpperCase() + k.slice(1))) })), t('fFontSizeHint')),
|
|
1268
1325
|
field(t('fUserDir'), h('input', { 'data-dam-input': '', value: cfg.userMemoryDir, onChange: function (e) { set('userMemoryDir', e.target.value) } }), t('fUserDirHint')),
|
|
@@ -1288,7 +1345,13 @@ window.__ModuleLoader__.load({
|
|
|
1288
1345
|
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')),
|
|
1289
1346
|
field(t('fReflect'), h('input', { type: 'checkbox', checked: !!cfg.reflectEnabled, onChange: function (e) { set('reflectEnabled', e.target.checked) } }), t('fReflectHint')),
|
|
1290
1347
|
field(t('fStyle'), h('select', { 'data-dam-select': '', value: cfg.reflectStyle, onChange: function (e) { set('reflectStyle', e.target.value) } },
|
|
1291
|
-
|
|
1348
|
+
STYLE_IDS.map(function (id) { return h('option', { key: id, value: id }, t('style' + id.charAt(0).toUpperCase() + id.slice(1))) })), t('fStyleHint')),
|
|
1349
|
+
field(t('fVersion'), h('div', { 'data-dam-row': '' },
|
|
1350
|
+
h('span', { style: { flex: 1 } }, verInfo ? (verInfo.current || '?') + (verInfo.latest ? ' → ' + verInfo.latest + (verInfo.upToDate ? ' ' + t('upToDate') : ' ' + t('hasUpdate')) : '') : (checkingUpdate ? t('checking') : '—')),
|
|
1351
|
+
h('button', { 'data-dam-btn': '', onClick: checkUpdate, disabled: checkingUpdate }, checkingUpdate ? t('checking') : t('checkUpdate')),
|
|
1352
|
+
(verInfo && verInfo.latest && !verInfo.upToDate && verInfo.installKind === 'registry') ? h('button', { 'data-dam-btn': '', onClick: doUpdate, disabled: upBusy, style: { marginLeft: '4px' } }, upBusy ? t('updating') : t('updateNow')) : null,
|
|
1353
|
+
upMsg ? h('span', { 'data-dam-hint': '' }, upMsg) : null),
|
|
1354
|
+
t('versionCmdHint') + (verInfo && verInfo.error ? ' ' + t('versionError') + verInfo.error : '') + (verInfo && verInfo.installKind === 'dev-link' ? ' ' + t('devLinkHint') : '') + (verInfo && !verInfo.installKind ? ' ' + t('noProfileHint') : '')),
|
|
1292
1355
|
h('div', { 'data-dam-row': '' },
|
|
1293
1356
|
h('button', { 'data-dam-btn': '', onClick: save, disabled: busy }, busy ? t('saving') : t('saveSettings')),
|
|
1294
1357
|
msg ? h('span', null, msg) : null),
|
|
@@ -1319,33 +1382,50 @@ window.__ModuleLoader__.load({
|
|
|
1319
1382
|
var savedScale = localStorage.getItem('dsh-auto-memory.fontScale.v2')
|
|
1320
1383
|
if (savedScale in FONT_SCALES) fontScale = savedScale
|
|
1321
1384
|
} catch (e3) {}
|
|
1322
|
-
//
|
|
1385
|
+
// 界面语言:默认跟随 DSH 系统语言(config.locale 可手动指定 zh / en / system)
|
|
1323
1386
|
try {
|
|
1324
|
-
var
|
|
1325
|
-
if (
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1387
|
+
var sl0 = ctx.locale && ctx.locale.getLocale ? ctx.locale.getLocale() : null
|
|
1388
|
+
if (sl0 && sl0.active) sysLocale = sl0.active
|
|
1389
|
+
} catch (e) {}
|
|
1390
|
+
applyLocalePref('system')
|
|
1391
|
+
apiGet(API.config).then(function (d) {
|
|
1392
|
+
if (d && d.config && d.config.locale) applyLocalePref(d.config.locale)
|
|
1393
|
+
}).catch(function () {})
|
|
1394
|
+
// DSH 系统语言变化:跟随更新 + 重渲染 + 重注册入口 label
|
|
1395
|
+
ctx.on('locale/change', function (snap) {
|
|
1396
|
+
try {
|
|
1397
|
+
if (snap && snap.active && snap.active !== sysLocale) {
|
|
1398
|
+
sysLocale = snap.active
|
|
1399
|
+
if (localeMode !== 'zh' && localeMode !== 'en') { locale = sysLocale; emit() }
|
|
1400
|
+
refreshSurfaces()
|
|
1330
1401
|
}
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1402
|
+
} catch (e) {}
|
|
1403
|
+
})
|
|
1333
1404
|
// 语言切换时通知所有订阅者重渲染
|
|
1334
1405
|
onLocale(function () { emit() })
|
|
1335
1406
|
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1407
|
+
var surfaceDisposers = []
|
|
1408
|
+
function registerSurfaces() {
|
|
1409
|
+
try {
|
|
1410
|
+
surfaceDisposers.push(slots.inject('sidebar.footer.action', function () {
|
|
1411
|
+
return slots.register({ name: 'sidebar.footer.action', id: 'auto-memory', order: 5, label: t('memory') }, function () { return h(SidebarButton) })
|
|
1412
|
+
}))
|
|
1413
|
+
surfaceDisposers.push(slots.inject('shell.overlay', function () {
|
|
1414
|
+
return slots.register({ name: 'shell.overlay', id: 'auto-memory', order: 5 }, function () { return h(MemoryPanel) })
|
|
1415
|
+
}))
|
|
1416
|
+
surfaceDisposers.push(slots.inject('settings.section', function () {
|
|
1417
|
+
return slots.register({ name: 'settings.section', id: 'auto-memory', order: 25, label: t('autoMemory') }, function (props) { return h(SettingsPage, { close: props && props.close }) })
|
|
1418
|
+
}))
|
|
1419
|
+
} catch (e) {
|
|
1420
|
+
console.warn('[dsh-auto-memory] slot registration failed', e)
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
function refreshSurfaces() {
|
|
1424
|
+
for (var i = 0; i < surfaceDisposers.length; i++) { try { surfaceDisposers[i]() } catch (e) {} }
|
|
1425
|
+
surfaceDisposers = []
|
|
1426
|
+
registerSurfaces()
|
|
1348
1427
|
}
|
|
1428
|
+
registerSurfaces()
|
|
1349
1429
|
console.log('[dsh-auto-memory] client ready: sidebar entry + panel + settings page')
|
|
1350
1430
|
}
|
|
1351
1431
|
|
package/lib/index.js
CHANGED
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
|
|
20
20
|
import { readFile, writeFile, mkdir, readdir, stat, rm, copyFile } from 'node:fs/promises'
|
|
21
21
|
import { createReadStream, existsSync } from 'node:fs'
|
|
22
|
+
import { exec as cpExec } from 'node:child_process'
|
|
23
|
+
import { promisify } from 'node:util'
|
|
24
|
+
const execP = promisify(cpExec)
|
|
22
25
|
import { homedir } from 'node:os'
|
|
23
26
|
import path from 'node:path'
|
|
24
27
|
import { fileURLToPath } from 'node:url'
|
|
@@ -46,6 +49,8 @@ export const API = {
|
|
|
46
49
|
debug: '/api/dsh-auto-memory/debug',
|
|
47
50
|
browseDir: '/api/dsh-auto-memory/browse-dir',
|
|
48
51
|
pickDir: '/api/dsh-auto-memory/pick-dir',
|
|
52
|
+
updateCheck: '/api/dsh-auto-memory/update-check',
|
|
53
|
+
update: '/api/dsh-auto-memory/update',
|
|
49
54
|
config: '/api/dsh-auto-memory/config',
|
|
50
55
|
reflect: '/api/dsh-auto-memory/reflect',
|
|
51
56
|
'reflect-auto': '/api/dsh-auto-memory/reflect-auto',
|
|
@@ -80,8 +85,8 @@ const DEFAULT_CONFIG = {
|
|
|
80
85
|
reflectEnabled: true,
|
|
81
86
|
/** 反思风格: auto=由内容决定 / life=生活化 / professional=专业性。 */
|
|
82
87
|
reflectStyle: 'auto',
|
|
83
|
-
/** UI 语言: zh=中文 / en=English。 */
|
|
84
|
-
locale: '
|
|
88
|
+
/** UI 语言: system=跟随 DSH 系统语言(默认) / zh=中文 / en=English。 */
|
|
89
|
+
locale: 'system',
|
|
85
90
|
/** 外部记忆注入预算(字符)。 */
|
|
86
91
|
externalInjectionChars: 1400,
|
|
87
92
|
/** 外部记忆源开关(AI 助手/CodeBuddy/Claude Code/Codex/项目约定)。 */
|
|
@@ -275,6 +280,73 @@ class MemoryEngine {
|
|
|
275
280
|
}
|
|
276
281
|
}
|
|
277
282
|
|
|
283
|
+
// ---------- 更新检查(registry 自动检查 + profile 探测) ----------
|
|
284
|
+
/** 找 dsh web profile 目录(含 dsh-auto-memory 依赖/bundle 的那个)。 */
|
|
285
|
+
async findProfileDir() {
|
|
286
|
+
try {
|
|
287
|
+
const base = path.join(dshHome(), 'profiles')
|
|
288
|
+
const dirs = await readdir(base, { withFileTypes: true })
|
|
289
|
+
for (const d of dirs) {
|
|
290
|
+
if (!d.isDirectory()) continue
|
|
291
|
+
const pkgFile = path.join(base, d.name, 'package.json')
|
|
292
|
+
const raw = await this.readTextSafe(pkgFile)
|
|
293
|
+
if (!raw) continue
|
|
294
|
+
let pkg
|
|
295
|
+
try { pkg = JSON.parse(raw) } catch (e) { continue }
|
|
296
|
+
const deps = (pkg && pkg.dependencies) || {}
|
|
297
|
+
const bundles = (pkg && pkg.dsh && pkg.dsh.profile && pkg.dsh.profile.bundles) || []
|
|
298
|
+
const key = Object.keys(deps).find((k) => k.includes('dsh-auto-memory'))
|
|
299
|
+
const isLink = !!(key && typeof deps[key] === 'string' && deps[key].startsWith('link:'))
|
|
300
|
+
const isReg = !!deps['@a9i5k4/dsh-auto-memory'] || bundles.indexOf('@a9i5k4/dsh-auto-memory') >= 0
|
|
301
|
+
if (key || bundles.some((b) => b.includes('dsh-auto-memory'))) {
|
|
302
|
+
return {
|
|
303
|
+
dir: path.join(base, d.name),
|
|
304
|
+
installKind: isLink ? 'dev-link' : (isReg ? 'registry' : 'unknown'),
|
|
305
|
+
usesPnpm: existsSync(path.join(base, d.name, 'pnpm-lock.yaml')),
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
} catch (e) {}
|
|
310
|
+
return null
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** 对比本地版本与 npm registry 最新版;结果缓存 12 小时(启动/设置页打开都走缓存,不重复查网)。 */
|
|
314
|
+
async checkUpdate(force) {
|
|
315
|
+
const cacheFile = path.join(dshHome(), 'memory', 'update-check.json')
|
|
316
|
+
const base = { current: '', installKind: 'unknown' }
|
|
317
|
+
try {
|
|
318
|
+
const indexPath = fileURLToPath(import.meta.url)
|
|
319
|
+
const pkgPath = path.join(path.dirname(indexPath), '..', 'package.json')
|
|
320
|
+
const raw = await this.readTextSafe(pkgPath)
|
|
321
|
+
if (raw) { try { base.current = JSON.parse(raw).version || '' } catch (e) {} }
|
|
322
|
+
} catch (e) {}
|
|
323
|
+
const prof = await this.findProfileDir()
|
|
324
|
+
if (prof) base.installKind = prof.installKind
|
|
325
|
+
if (!force) {
|
|
326
|
+
try {
|
|
327
|
+
const cached = await this.readTextSafe(cacheFile)
|
|
328
|
+
if (cached) {
|
|
329
|
+
const j = JSON.parse(cached)
|
|
330
|
+
if (j && j.checkedAt && Date.now() - j.checkedAt < 12 * 3600 * 1000) return Object.assign({}, j, { fromCache: true })
|
|
331
|
+
}
|
|
332
|
+
} catch (e) {}
|
|
333
|
+
}
|
|
334
|
+
const result = Object.assign({}, base, { checkedAt: Date.now(), fromCache: false, latest: '', upToDate: false, error: '' })
|
|
335
|
+
try {
|
|
336
|
+
const rr = await fetch('https://registry.npmjs.org/@a9i5k4%2Fdsh-auto-memory', { signal: AbortSignal.timeout(8000) })
|
|
337
|
+
if (rr.ok) {
|
|
338
|
+
const j = await rr.json()
|
|
339
|
+
result.latest = (j && j['dist-tags'] && j['dist-tags'].latest) || ''
|
|
340
|
+
result.upToDate = !!(result.current && result.latest && result.current === result.latest)
|
|
341
|
+
} else result.error = 'registry HTTP ' + rr.status
|
|
342
|
+
} catch (e) { result.error = String(e && e.message ? e.message : e) }
|
|
343
|
+
try {
|
|
344
|
+
await mkdir(path.dirname(cacheFile), { recursive: true })
|
|
345
|
+
await writeFile(cacheFile, JSON.stringify(result, null, 2), 'utf8')
|
|
346
|
+
} catch (e) {}
|
|
347
|
+
return result
|
|
348
|
+
}
|
|
349
|
+
|
|
278
350
|
// ---------- 读取 ----------
|
|
279
351
|
async readTextSafe(p) {
|
|
280
352
|
if (!p) return ''
|
|
@@ -2052,6 +2124,8 @@ export function apply(ctx, config) {
|
|
|
2052
2124
|
// 生命周期刷新
|
|
2053
2125
|
const refreshAll = (agent) => { void engine.refresh(agent) }
|
|
2054
2126
|
refreshAll()
|
|
2127
|
+
// 自动检查更新:host 启动时查一次 npm registry(结果缓存 12 小时,设置页打开直接读缓存显示)
|
|
2128
|
+
void engine.checkUpdate(false)
|
|
2055
2129
|
ctx.on('agent/session-start', (payload) => {
|
|
2056
2130
|
refreshAll(payload && payload.agent)
|
|
2057
2131
|
// 缓存最近 agent 引用(subagent parent 需要完整 agent 对象)
|
|
@@ -2390,6 +2464,45 @@ export function apply(ctx, config) {
|
|
|
2390
2464
|
} catch (e) { writeJson(res, 200, { native: true, dir: null, error: String(e && e.message ? e.message : e) }) }
|
|
2391
2465
|
},
|
|
2392
2466
|
},
|
|
2467
|
+
{
|
|
2468
|
+
kind: 'exact',
|
|
2469
|
+
path: API.updateCheck,
|
|
2470
|
+
handler: async (req, res) => {
|
|
2471
|
+
if (!isLoopbackRequest(req)) return writeJson(res, 403, { error: 'forbidden: loopback-only' })
|
|
2472
|
+
if ((req.method || 'GET') !== 'GET') return writeJson(res, 405, { error: 'method not allowed' })
|
|
2473
|
+
try {
|
|
2474
|
+
const url = new URL(req.url || '/', 'http://localhost')
|
|
2475
|
+
const force = url.searchParams.get('force') === '1'
|
|
2476
|
+
writeJson(res, 200, await engine.checkUpdate(force))
|
|
2477
|
+
} catch (e) { writeJson(res, 500, { error: String(e && e.message ? e.message : e) }) }
|
|
2478
|
+
},
|
|
2479
|
+
},
|
|
2480
|
+
{
|
|
2481
|
+
kind: 'exact',
|
|
2482
|
+
path: API.update,
|
|
2483
|
+
handler: async (req, res) => {
|
|
2484
|
+
if (!isLoopbackRequest(req)) return writeJson(res, 403, { error: 'forbidden: loopback-only' })
|
|
2485
|
+
if ((req.method || 'POST') !== 'POST') return writeJson(res, 405, { error: 'method not allowed' })
|
|
2486
|
+
// 一键更新:在 profile 目录执行 pnpm up / npm install,完成后提示重启
|
|
2487
|
+
try {
|
|
2488
|
+
const prof = await engine.findProfileDir()
|
|
2489
|
+
if (!prof) return writeJson(res, 200, { ok: false, message: '未找到 dsh profile 目录(未安装插件或已删除)。' })
|
|
2490
|
+
if (prof.installKind === 'dev-link') return writeJson(res, 200, { ok: false, message: '当前为本地开发链接(link:)安装,不适用 npm 更新,请直接同步开发源码。' })
|
|
2491
|
+
if (prof.installKind !== 'registry') return writeJson(res, 200, { ok: false, message: '未检测到 registry 安装方式,无法自动更新。' })
|
|
2492
|
+
const cmd = prof.usesPnpm ? 'pnpm up @a9i5k4/dsh-auto-memory' : 'npm install @a9i5k4/dsh-auto-memory@latest'
|
|
2493
|
+
const out = await execP(cmd, { cwd: prof.dir, timeout: 120000, windowsHide: true })
|
|
2494
|
+
const tail = ((out.stdout || '') + (out.stderr || '')).trim().slice(-1200)
|
|
2495
|
+
writeJson(res, 200, {
|
|
2496
|
+
ok: true,
|
|
2497
|
+
output: tail || '(无输出)',
|
|
2498
|
+
restartHint: '更新完成,请重启 dsh web 生效。',
|
|
2499
|
+
installKind: prof.installKind,
|
|
2500
|
+
})
|
|
2501
|
+
} catch (e) {
|
|
2502
|
+
writeJson(res, 200, { ok: false, message: String(e && e.message ? e.message : e) })
|
|
2503
|
+
}
|
|
2504
|
+
},
|
|
2505
|
+
},
|
|
2393
2506
|
{
|
|
2394
2507
|
kind: 'exact',
|
|
2395
2508
|
path: API.config,
|
package/package.json
CHANGED