dsh-instruction-memory 1.3.3 → 1.4.1
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/CHANGELOG.md +149 -0
- package/README.md +140 -7
- package/docs/screenshot.png +0 -0
- package/lib/client.js +668 -79
- package/lib/index.js +398 -71
- package/lib/mailer.js +932 -0
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,155 @@
|
|
|
7
7
|
> 1.0.13 起由 CI 经 OIDC Trusted Publishing 发布(首个带 provenance 的版本)。
|
|
8
8
|
> 这些历史 tag **不应回填**——npm 上的版本号已被占用。
|
|
9
9
|
|
|
10
|
+
## [1.4.1] — 2026-10-22
|
|
11
|
+
|
|
12
|
+
运行期缺陷(桌面端 / 网页版审计)与安全审查(对 1.4.0 源码)发现的问题修复,
|
|
13
|
+
加上双宿主(网页端 :8080 / 桌面端 :19387,宿主均为 0.2.1-rc.2)适配性审计后的加固。
|
|
14
|
+
|
|
15
|
+
### 修复
|
|
16
|
+
|
|
17
|
+
桌面端 / 网页版审计(宿主 `0.2.1-rc.2`、Electron 44.7.0)发现的三条运行期缺陷:
|
|
18
|
+
|
|
19
|
+
- **指令里出现 `{{…}}` 会让整个 Agent 回合崩溃**:`systemPrompt.section()` 注册时没有关掉宿主的
|
|
20
|
+
模板插值。宿主 `renderPrompt()` 默认对每个 section 做 `{{变量}}` 替换,遇到无法解析的引用直接抛
|
|
21
|
+
`unknown prompt variable "{{x}}" in section "instruction-memory"`;而 agent-loop 在 `step()` 里
|
|
22
|
+
裸调 `renderPrompt()`(无 try/catch),于是一条写着 `proxy_pass {{upstream}};` 的指令
|
|
23
|
+
(Nginx 片段、Vue/Jinja/Handlebars 模板都算)就会终止当前回合。现在注册时显式传
|
|
24
|
+
`interpolate: false`,该 section 按字面量渲染。
|
|
25
|
+
- **桌面端「批量加标签」点击即抛异常**:Electron 44 把 `window.prompt` 替换成
|
|
26
|
+
`function () { throw new Error("prompt() is not supported.") }`,并经 contextBridge 推进主世界,
|
|
27
|
+
`typeof` 仍返回 `'function'`,原来的类型守卫形同虚设。现在调用点用 try/catch 判定
|
|
28
|
+
(`batchTagOutcome()`):prompt 缺失或抛错时改用面板内的行内输入框;用户主动取消(返回 `null`)
|
|
29
|
+
仍按「取消」处理,不会被误判成"环境不支持"。
|
|
30
|
+
- **桌面 profile 缺依赖声明**:`profiles/desktop/package.json` 的 `dsh.profile.bundles` 列了
|
|
31
|
+
`dsh-instruction-memory`,`dependencies` 却没有它、`pnpm-lock.yaml` 也无记录,运行期靠手工
|
|
32
|
+
junction 维持。该 profile 下次 `pnpm install`(插件增删/更新都会触发)会清掉链接,随后 `dsh`
|
|
33
|
+
只在 stderr 打一行 `skipping profile bundle` 就静默跳过,插件在桌面端凭空消失。现在补齐
|
|
34
|
+
`dependencies` 里的 `link:` 声明。
|
|
35
|
+
|
|
36
|
+
安全审查(对 1.4.0 源码)发现的问题:
|
|
37
|
+
|
|
38
|
+
- **并发提交可绕过 30 秒冷却、重复投递**:冷却时间戳原本在 SMTP 会话结束后才写入,
|
|
39
|
+
两个同时到达的请求都能通过检查、各发一封。现在冷却在发送开始前就占位,失败时立即释放。
|
|
40
|
+
- **面板 15 秒超时短于宿主的投递上限**:SMTP 慢时页面报"超时",而邮件仍在路上,
|
|
41
|
+
用户重试就会收到重复邮件。面板改为最多等待 90 秒,宿主新增整封投递上限 `SMTP_TOTAL_TIMEOUT_MS`(60 秒);
|
|
42
|
+
`verify.mjs` 固定两者的大小关系。
|
|
43
|
+
- **配置文件里 `smtp.pass` 写成数字时被静默丢弃**:端口/`secure` 早已按 JSON 标量处理,密码却只接受字符串,
|
|
44
|
+
导致"密码明明填了却认证失败"。现在数字会被字符串化,对象/数组则明确报 `smtp.pass 类型无效`。
|
|
45
|
+
- **`to` 与 `toEncrypted` 同时存在时明文静默优先**:会让"我已加密"变成假象,损坏的密文也不会被发现。
|
|
46
|
+
现在直接拒绝并说明原因;环境变量 `..._FEEDBACK_TO` 仍可临时覆盖。
|
|
47
|
+
- **地址校验过于宽松**:`EMAIL_RE` 原本放行 NUL 等控制字节与非 ASCII 地址,
|
|
48
|
+
而 `buildRawMail` / `sendSmtpMail` 依赖调用方先校验。现在校验排除控制字节,
|
|
49
|
+
非 ASCII 地址明确报错(未实现 SMTPUTF8),且组信与发信入口各自再做一次换行/控制字符断言。
|
|
50
|
+
- **`\bLOGIN\b` 误判 AUTH 机制**:EHLO 中任何含独立单词 `LOGIN` 的扩展都会让客户端错误选择 `AUTH LOGIN`。
|
|
51
|
+
现在只解析 `AUTH` 能力行(RFC 4954)。
|
|
52
|
+
- **响应行缓冲无上限**:行为异常或恶意的服务器可持续发送无 CRLF 数据撑大内存,
|
|
53
|
+
现在上限 64 KiB,超出即断开并报 `SMTP 响应过长`。
|
|
54
|
+
- **超长主题只生成一个 RFC 2047 编码词**:最长约 240 字符,部分客户端无法解码。
|
|
55
|
+
现在按 45 字节切分为多个、每个不超过 75 字符的编码词。
|
|
56
|
+
- **日志脱敏漏掉 SMTP 用户名**:服务器拒绝文本可能回显登录账号,现在用户名也一并隐藏。
|
|
57
|
+
|
|
58
|
+
### 优化(源码审查第二轮)
|
|
59
|
+
|
|
60
|
+
- **相对 `$DSH_HOME` 会让存储路径保持相对**:`resolveStorePath()` 原样返回
|
|
61
|
+
`rel-home\instruction-memory\memory.json`,与 README「设置页显示完整绝对路径」矛盾,
|
|
62
|
+
`dirname()` 还会退化成 `.`,同一份存储在进程内的身份取决于路径写法。现在相对的
|
|
63
|
+
`$DSH_HOME` 在启动时按 cwd 解析为绝对路径(空白仍视为未设置);`install.mjs` 同步修正。
|
|
64
|
+
- **陈旧锁删不掉时保存会永久挂起**:`acquireStoreLock()` 的 `unlink` 失败被吞掉后直接
|
|
65
|
+
`continue`,绕过了下面的 deadline 判断;只要锁文件持续不可删(Windows 句柄未共享删除、
|
|
66
|
+
只读卷),这次保存就永远不返回,后续所有磁盘操作都堵在同一队列里。deadline 现在在每次
|
|
67
|
+
尝试的**顶部**判断,且每次失败都先等待 50 ms 再重试。
|
|
68
|
+
- **面板把每字段上限硬编码**:编辑器写死 120/6000/200 与标签 8/24,而 `snapshot.limits`
|
|
69
|
+
只发布了部分上限——宿主改上限后面板会接受加载器随后截断的文本。现在
|
|
70
|
+
`maxTitle`/`maxContent`/`maxWhen`/`maxTagsPerEntry`/`maxTagLength` 全部随快照发布,
|
|
71
|
+
编辑器按快照取值(对旧宿主仍保留原默认值)。
|
|
72
|
+
- **历史快照只按份数保留,磁盘占用无上限**:满配存储一份快照约 3.6 MB,20 份约 72 MB,
|
|
73
|
+
自定义备份目录指向云盘时等于同量上传。新增按设备的 **32 MB 字节预算**,超出时从最旧
|
|
74
|
+
逐出、最新一份永远保留;策略抽成纯函数 `planHistoryPrune()` 便于单测。
|
|
75
|
+
- **SSE 每帧都推全量快照**:满配时约 1.24 M 字符(中文约 3.4 MB)/帧,且每次保存推给
|
|
76
|
+
每个打开的设置页。设置页现在以 `?lite=1` 订阅,宿主只推 `{ rev, lite: true }`(约 40
|
|
77
|
+
字节),客户端发现 rev 落后再拉 `state`;不带该参数的旧客户端仍收到完整快照,双向兼容。
|
|
78
|
+
- **可访问性**:表单 `<label>` 全部补 `htmlFor`/`id`(此前 0 个关联,点标签不聚焦、读屏
|
|
79
|
+
不关联);「展开全部/收起」从可点击 `div` 改为 `<button aria-expanded>`;错误与提示分别
|
|
80
|
+
补 `role="alert"` / `role="status"`;标签删除按钮补可访问名「移除标签 X」。
|
|
81
|
+
- **编辑器错误提示不可见**:条目编辑器是提前返回的分支,不渲染面板级错误行——空标题保存、
|
|
82
|
+
冲突或写盘失败时 `setError()` 设了消息却什么都不显示。现在编辑器内自带错误行(并带
|
|
83
|
+
`role="alert"`)。
|
|
84
|
+
- **发布包体积**:`docs/screenshot.png` 由 262 KB 压到 97 KB(同尺寸调色板量化),
|
|
85
|
+
整个 tarball 从 435 KB 降到约 270 KB。
|
|
86
|
+
- **发布前跑测试**:`prepublishOnly` 现在执行 `check-metadata.mjs && npm test`,本地
|
|
87
|
+
`npm publish` 不再能绕过测试套件(CI 的 publish.yml 原本就会跑)。
|
|
88
|
+
|
|
89
|
+
### 优化(双宿主审计跟进,宿主 0.2.1-rc.2 / Electron 44)
|
|
90
|
+
|
|
91
|
+
- **破坏性确认的降级策略前后矛盾**:批量删除、集合删除、形象删除、单条删除写的是
|
|
92
|
+
`typeof window.confirm === 'function' && !window.confirm(…)`——没有对话框时条件整体为
|
|
93
|
+
`false`,删除**照常执行**;而版本回滚与整体恢复是「没有对话框就拒绝」。同一类不可恢复
|
|
94
|
+
操作在缺 `window.confirm` 的宿主里行为相反。现在统一走 `askConfirm(message, unavailable)`:
|
|
95
|
+
持久数据类默认 `deny`(无对话框即拒绝,并在面板显示 `confirm.unavailable`,不再静默吞掉
|
|
96
|
+
点击),只有「丢弃未保存草稿」类(退出编辑器、重载源码视图)用 `allow`——那里拒绝会把
|
|
97
|
+
人困在死按钮上,且不涉及已落盘数据,`verify.mjs` 对此有既有守护。
|
|
98
|
+
返回值改为三态 `yes`/`no`/`unavailable`,用户主动取消不会被误报成「环境不支持」;
|
|
99
|
+
`confirm` **存在但调用即抛**(Electron 44 给 `prompt()` 用的正是这种桩)按缺失处理,
|
|
100
|
+
而原先的 `typeof` 判断对这种桩完全失效。
|
|
101
|
+
- **窄窗口双列表单挤成竖排断字**:`.im-grid2` 固定 `1fr 1fr` 且没有任何断点,桌面端把窗口
|
|
102
|
+
拉窄后每列不足 250px,标签与占位文本逐词换行。新增 560px 断点:表单转单列,批量标签
|
|
103
|
+
输入去掉 160px 下限并独占一行(此前会横向溢出)。
|
|
104
|
+
- **双端冒烟没有一条可复现的命令**:新增 `live-host-smoke.mjs`(`npm run smoke:hosts`),
|
|
105
|
+
一次测完 `state` 的 p50/p95、SSE 首事件、注入注册与字符数、以及两端快照的逐字段一致性;
|
|
106
|
+
任一项超阈值(p50 > 50 ms)或快照出现 `injection.pulls` 之外的差异即以退出码 1 失败。
|
|
107
|
+
脚本内全部请求走 Node `http` 直连,**不继承 `http_proxy`**——本机代理指向 `127.0.0.1:9708`,
|
|
108
|
+
连回环请求也转发,审计期间正是它让 SSE 读数从 0.7 ms 虚高到 6 ms。
|
|
109
|
+
`--require-pulls` 把「该宿主从未经历真实 Agent 回合」判为失败,用于桌面端注入链路的验收。
|
|
110
|
+
该脚本**刻意不接入 `npm test`**:CI 没有桌面端,接进去必然常红。
|
|
111
|
+
|
|
112
|
+
### 测试
|
|
113
|
+
|
|
114
|
+
- `verify.mjs` 新增运行期缺陷的回归用例:注册的 section 必须带 `interpolate: false`
|
|
115
|
+
(宿主渲染期崩溃的守卫)、`__batchTagOutcome()` 的四种输入(正常作答 / `null` 取消 /
|
|
116
|
+
抛错 / 非字符串答案)。并在宿主 `0.2.1-rc.2` 的**真实** `renderPrompt()` 上端到端确认:
|
|
117
|
+
带该标志时含 `{{upstream}}` 的指令按字面量渲染,去掉标志则复现原报错。
|
|
118
|
+
- `feedback-test.mjs` 新增上述每一条的回归用例:并发路由(只投递一封、且不额外建连)、
|
|
119
|
+
数字/非法类型 `pass`、`to`+`toEncrypted` 冲突、控制字节与非 ASCII 地址、
|
|
120
|
+
组信头部注入断言、长主题切分与还原、AUTH 机制解析、60 秒总上限、无 CRLF 洪泛的有界缓冲。
|
|
121
|
+
假 SMTP 服务器支持自定义 EHLO 能力行与洪泛选项。
|
|
122
|
+
- 本轮新增:`smoke-test.mjs` 覆盖相对 `$DSH_HOME` 的绝对化与 `planHistoryPrune()` 的
|
|
123
|
+
份数/字节/保底规则;`contract-test.mjs` 断言五个逐字段上限随快照发布且与代码一致,
|
|
124
|
+
并验证 `?lite=1` 的连接帧与变更帧都是紧凑 rev 标记、普通流仍收完整快照;
|
|
125
|
+
`ui-test.mjs` 断言每个字段标签都指向真实控件、校验错误带 `role="alert"`。
|
|
126
|
+
|
|
127
|
+
## [1.4.0] — 2026-10-18
|
|
128
|
+
|
|
129
|
+
### 新增
|
|
130
|
+
|
|
131
|
+
- **设置页「反馈」按钮(可选 SMTP 邮件)**:位于设置卡片最上方,填写问题类型、标题、正文与可选联系方式后,
|
|
132
|
+
由 Host 进程通过 `node:net` / `node:tls` 手写的 SMTP 会话直接发信,**未引入任何第三方依赖**。
|
|
133
|
+
正文自动附带插件版本(运行时读 `package.json`,不硬编码)、Node/DSH 版本、平台与架构、
|
|
134
|
+
界面语言、时区、`User-Agent` 与提交时间,**不附带任何条目正文或形象内容**。
|
|
135
|
+
新增 `lib/mailer.js`(表单校验、环境变量/仓库外 `feedback.json` 配置、MIME 组信、SMTP 会话)
|
|
136
|
+
与 Host 的 `send-feedback` 分发方法;客户端补充加载态、成功/失败提示、30 秒冷却与错误日志。
|
|
137
|
+
收件邮箱、SMTP 主机与凭证只从环境变量或仓库外配置文件读取,**不写入仓库**。
|
|
138
|
+
表单「联系方式」若为纯邮箱地址会自动写入 `Reply-To`,维护者可一键回复反馈者;自由文本不会进入邮件头。
|
|
139
|
+
- **收件邮箱静态加密(可选)**:`feedback.json` 支持 `toEncrypted`(`im1:` + AES-256-GCM 密文,
|
|
140
|
+
scrypt 派生密钥,含认证标签),密钥只从 `DSH_INSTRUCTION_MEMORY_FEEDBACK_SECRET` 读取;
|
|
141
|
+
`node lib/mailer.js encrypt-to <email>` 生成密文。错误提示、页面与日志中的发件/收件地址会被隐藏。
|
|
142
|
+
明确边界:SMTP 信封仍向服务器明文提交收件人,本项只保护配置文件静态副本。
|
|
143
|
+
- **复用已有 SMTP 凭据**:可读取本机共享的 Windows 用户级 `DSH_CRB_SMTP_URL`
|
|
144
|
+
(`smtps://user:pass@host:port`),逐字段优先级为插件专用 `..._SMTP_*` > 该 URL > `feedback.json`;
|
|
145
|
+
`smtp://` 默认 STARTTLS、端口 465 自动按隐式 TLS,特殊字符需 URL 编码,错误信息不回显 URL/密码。
|
|
146
|
+
- 测试新增 `feedback-test.mjs`(纯函数 + 假 SMTP 服务器 + 真实路由端到端 + 加解密/密钥错误用例),
|
|
147
|
+
并在 `ui-test.mjs` 覆盖按钮/表单/本地校验/未配置提示/清空/收起及「反馈位于卡片最上方」。
|
|
148
|
+
README 增加配置、加密与排查说明。
|
|
149
|
+
|
|
150
|
+
### 修复
|
|
151
|
+
|
|
152
|
+
- **开发安装后,插件能运行但不出现在插件市场的「已安装」列表**:宿主只需 junction +
|
|
153
|
+
`dsh.profile.bundles` 就能装载插件,而 dshmarket 的已安装列表只取 profile `package.json`
|
|
154
|
+
的 `dependencies`(`readInstalled()`),因此「开发安装」的插件一直对市场不可见。
|
|
155
|
+
`install.mjs` 现在增加第三步,把 `dsh-instruction-memory: link:<本仓库路径>` 写进 profile
|
|
156
|
+
依赖(`--remove` 同步删除),幂等且与 `pnpm-lock.yaml` 一致。
|
|
157
|
+
仅影响开发安装流程;npm 包内容不变(`files` 不含 `install.mjs`)。
|
|
158
|
+
|
|
10
159
|
## [1.3.3] — 2026-10-16
|
|
11
160
|
|
|
12
161
|
一处注释整理,无代码与行为变更。
|
package/README.md
CHANGED
|
@@ -37,13 +37,14 @@ dsh plugin --profile web remove dsh-instruction-memory
|
|
|
37
37
|
| `lib/index.js` | Host 半:读取/保存记忆,注册 `systemPrompt` 段落,暴露同源 JSON 路由 |
|
|
38
38
|
| `lib/client.js` | Client 半:在 `settings.section` 注册「指令记忆」设置页(手写模块,无需构建) |
|
|
39
39
|
| `cordis.patch.yml` | bundle patch:把 Host 行插入宿主组合 |
|
|
40
|
-
| `install.mjs` | 开发安装脚本:从本仓库检出安装时,把包链接进 web profile 的 `node_modules`(junction
|
|
40
|
+
| `install.mjs` | 开发安装脚本:从本仓库检出安装时,把包链接进 web profile 的 `node_modules`(junction)、注册 `dsh.profile.bundles`,并在 profile `dependencies` 里声明 `link:<本仓库路径>`(缺这一步插件能跑,但插件市场的「已安装」列表看不到它——市场只读 `dependencies`);`--remove` 三步完整回滚,改 profile 前自动备份 |
|
|
41
41
|
| `contract-test.mjs` | 契约测试:驱动真实路由,验证两半之间的响应格式(含配置集、版本历史、源码模式、备份位置) |
|
|
42
42
|
| `ui-test.mjs` | 设置页行为测试:用一个 ~60 行的 React 垫片真实渲染面板、点按钮/填输入框,再断言注入内容随之变化(不出图、不依赖浏览器) |
|
|
43
43
|
| `verify.mjs` | 装载验证:按 DSH 的方式加载两半,并逐项断言 `memory.schema.json` 与代码中的上限一致 |
|
|
44
44
|
| `smoke-test.mjs` | 注入渲染逻辑的单元测试(含「只渲染激活配置集」的隔离断言与 cwd 红线) |
|
|
45
45
|
| `host-range-test.mjs` | 宿主版本范围契约测试:固定矩阵 × 两种 semver 判定模式,并把 `peerDependencies`(**被执行的那个**)、`dsh.engines.dsh`、`dsh.compatibility.dsh` 三处声明锁到同一个生成值 |
|
|
46
46
|
| `lint.mjs` | 语法门禁:`node --check` 解析所有随包发布与本仓库运行的 JS 文件,已并入 `npm test` |
|
|
47
|
+
| `live-host-smoke.mjs` | 双端冒烟:对**正在运行**的 web / 桌面宿主各测 `state` p50/p95、SSE 首事件、注入注册与字符数,并比对两端快照(除 `injection.pulls` 外须逐字段相同);`npm run smoke:hosts`,`--require-pulls` 可把「从未经历真实 Agent 回合」判为失败。**刻意不进 `npm test`**——CI 没有桌面端。请求走 Node `http` 直连,绕开 `http_proxy`(否则回环请求也会被代理转发,读数虚高) |
|
|
47
48
|
| `docs/demo.html` | 离线演示页:桩掉 DSH 外壳、加载真实 Client 半渲染设置页(不随 npm 包发布) |
|
|
48
49
|
| `memory.schema.json` | 手改存储文件用的 JSON Schema(随包发布,并被宿主复制到 `memory.json` 旁边) |
|
|
49
50
|
|
|
@@ -69,6 +70,8 @@ dsh plugin --profile web remove dsh-instruction-memory
|
|
|
69
70
|
多选与上方的筛选/搜索叠加(「全选」只作用于当前可见条目)。整批修改在宿主侧是**单次写入**
|
|
70
71
|
(只产生一次落盘、一次修订号递增与一次同步推送),全部条目已是目标状态时不写盘;批量删除需确认。
|
|
71
72
|
批量的启停/优先级/标签调整不重盖更新时间,因此不会改变同优先级下的注入先后。
|
|
73
|
+
「加标签」在浏览器里用原生输入框;**桌面版(Electron)不提供 `window.prompt`**(调用即抛错),
|
|
74
|
+
此时自动改用面板内的行内输入框(回车提交、Esc 取消),功能不受影响。
|
|
72
75
|
- **用户形象**:除了"要求 AI 怎么做"的指令,你还可以记录**关于你本人**的信息(身份背景、喜好、性格、
|
|
73
76
|
长期追求等),每张形象卡片可单独勾选「使用该形象」——可以一个都不用、只用一个、或同时勾选多个
|
|
74
77
|
(多个会作为同一画像的不同侧面综合)。启用后,形象段会以「## 关于用户(用户形象)」为抬头注入在
|
|
@@ -104,7 +107,8 @@ dsh plugin --profile web remove dsh-instruction-memory
|
|
|
104
107
|
|
|
105
108
|
路径优先级与官方 `@deepseek-ai/dsh-home-paths` 一致:显式 `$DSH_HOME` → `~/.dsh`。
|
|
106
109
|
**空白的 `$DSH_HOME` 视为未设置**,绝不会退回当前工作目录——否则用户换个目录启动
|
|
107
|
-
DSH 就会得到第二份空记忆,看起来像"记忆消失"
|
|
110
|
+
DSH 就会得到第二份空记忆,看起来像"记忆消失"。相对的 `$DSH_HOME`(如 `./data`)会在
|
|
111
|
+
启动时按当前目录解析为绝对路径,因此设置页显示的始终是完整路径。
|
|
108
112
|
|
|
109
113
|
> 1.0.0 之前的构建曾把数据写在宿主进程 cwd 下(`.dsh-instruction-memory.json`)。
|
|
110
114
|
> 首次启动新版本时会自动迁移到上述位置,并把原文件改名为 `*.migrated` 保留。
|
|
@@ -242,6 +246,9 @@ DSH 就会得到第二份空记忆,看起来像"记忆消失"。
|
|
|
242
246
|
- 每台设备**各保留最近 20 份**(按文件名里的设备标签分组),逐出最旧的。两台机器共用一个
|
|
243
247
|
备份目录时,谁也不会把对方的历史剪掉。不符合该命名的文件(手工放的、别的工具写的、
|
|
244
248
|
云盘的冲突副本)**一律不碰**。
|
|
249
|
+
- 份数之外还有**32 MB 字节预算**(同样按设备计算):满配存储(200 条 × 6000 字符)的一份
|
|
250
|
+
快照约 3.6 MB,只按份数保留会到约 72 MB;超出预算时从最旧的开始逐出,但**最新一份永远
|
|
251
|
+
保留**(哪怕它自己就超过预算)。
|
|
245
252
|
- **为什么放在 `history/` 子目录**:`memory.json` 所在目录被 `fs.watch` 监听以做外部改动
|
|
246
253
|
自动刷新,快照绝不能混进去触发重载;子目录不在非递归监听的范围内,文件名过滤也会兜底。
|
|
247
254
|
- 设置页「版本历史」里可展开列表(时间、rev、条目/形象数、体积、是否来自其他设备),
|
|
@@ -381,6 +388,130 @@ DSH 就会得到第二份空记忆,看起来像"记忆消失"。
|
|
|
381
388
|
请勿在不可信网络暴露,并指向下节「经反向代理对外发布」。该判定与路由实际执行的 Host 校验
|
|
382
389
|
是**同一个开关**;快照同时携带 `boundHost`(路由注册时捕获的绑定地址),警告会点名具体地址。
|
|
383
390
|
|
|
391
|
+
## 反馈邮件(可选配置)
|
|
392
|
+
|
|
393
|
+
设置页最上方(第一块)新增「反馈」按钮:填写问题类型、标题、正文与可选联系方式后,由 **Host 进程**
|
|
394
|
+
直接通过 SMTP 把邮件发到维护者邮箱。客户端只提交表单,不接触任何服务凭证;
|
|
395
|
+
未配置时按钮仍可点开,提交会返回一条带配置指引的错误,并保留已填内容以便重试。
|
|
396
|
+
|
|
397
|
+
**隐私边界**:邮件自动附带插件版本、Node/DSH 版本、平台与架构、界面语言、时区、
|
|
398
|
+
`User-Agent` 和提交时间;**绝不附带**任何条目正文、形象内容或整份 `memory.json`。
|
|
399
|
+
|
|
400
|
+
**回复路径**:`From` 固定为认证的发件账号;若「联系方式」是一个纯邮箱地址,Host 会把它写成
|
|
401
|
+
`Reply-To`,维护者点「回复」即可直接回到反馈者(微信、GitHub 用户名等自由文本不会进入邮件头)。
|
|
402
|
+
|
|
403
|
+
配置只认环境变量或仓库外的 JSON 文件,**不要写进 `cordis.patch.yml` 或任何提交进仓库的文件**。
|
|
404
|
+
|
|
405
|
+
### 环境变量(优先)
|
|
406
|
+
|
|
407
|
+
| 变量 | 必填 | 说明 |
|
|
408
|
+
| --- | --- | --- |
|
|
409
|
+
| `DSH_INSTRUCTION_MEMORY_FEEDBACK_TO` | 是 | 收件邮箱(单个地址,仅 ASCII;非 ASCII 地址会明确报错而不是发出乱码头) |
|
|
410
|
+
| `DSH_INSTRUCTION_MEMORY_FEEDBACK_SMTP_HOST` | 是 | SMTP 服务器主机名 |
|
|
411
|
+
| `DSH_INSTRUCTION_MEMORY_FEEDBACK_FROM` | 否 | 发件地址,默认取 SMTP 用户名;部分服务商要求与登录账号同域 |
|
|
412
|
+
| `DSH_INSTRUCTION_MEMORY_FEEDBACK_SMTP_PORT` | 否 | 1–65535,默认按 `_SMTP_SECURE` 取 465 / 587 |
|
|
413
|
+
| `DSH_INSTRUCTION_MEMORY_FEEDBACK_SMTP_SECURE` | 否 | `1/true/yes` 走隐式 TLS(465);默认 `0` 并用 STARTTLS 升级(587) |
|
|
414
|
+
| `DSH_INSTRUCTION_MEMORY_FEEDBACK_SMTP_USER` | 否 | 登录用户名;与密码必须成对出现 |
|
|
415
|
+
| `DSH_INSTRUCTION_MEMORY_FEEDBACK_SMTP_PASS` | 否 | 登录密码 / 应用专用密码 |
|
|
416
|
+
| `DSH_CRB_SMTP_URL` | 否 | 复用的 URL 形凭据(格式见下节),优先级低于上面五个 `..._SMTP_*` 变量 |
|
|
417
|
+
| `DSH_INSTRUCTION_MEMORY_FEEDBACK_CONFIG` | 否 | 配置文件路径,默认 `<存储目录>/feedback.json` |
|
|
418
|
+
| `DSH_INSTRUCTION_MEMORY_FEEDBACK_COOLDOWN_MS` | 否 | 两次发送的最小间隔,默认 `30000`,`0` 关闭 |
|
|
419
|
+
| `DSH_INSTRUCTION_MEMORY_FEEDBACK_SECRET` | 否 | 解密 `toEncrypted` 的密钥,至少 16 个字符;只在环境变量里提供,不要写进配置文件或仓库 |
|
|
420
|
+
|
|
421
|
+
存储目录默认是 `%USERPROFILE%\.dsh\instruction-memory`(受 `DSH_HOME` 影响),
|
|
422
|
+
即默认配置文件为 `…\instruction-memory\feedback.json`。
|
|
423
|
+
|
|
424
|
+
### 复用已有的 `DSH_CRB_SMTP_URL`(可选)
|
|
425
|
+
|
|
426
|
+
如果本机其他工具已经在 **Windows 用户级环境变量** `DSH_CRB_SMTP_URL` 里保存了可用的
|
|
427
|
+
SMTP 凭据,插件可以直接复用,不必再把主机/账号/密码抄一份到本插件的变量或文件里:
|
|
428
|
+
|
|
429
|
+
```
|
|
430
|
+
smtps://用户:密码@smtp.qq.com:465
|
|
431
|
+
smtp://用户:密码@smtp.example.com:587?secure=0&from=bot@example.com
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
- `smtps://` 走隐式 TLS(通常 465);`smtp://` 默认 STARTTLS,可用 `secure=1/0` 覆盖;
|
|
435
|
+
`smtp://` 且端口为 465 时按隐式 TLS 处理(服务商常见约定)。
|
|
436
|
+
- 用户名、密码中的 `@ : / %` 等字符必须 URL 编码,例如 `1234567890%40qq.com`。
|
|
437
|
+
- 逐字段优先级:本插件专用 `..._SMTP_*` 变量 > `DSH_CRB_SMTP_URL` > `feedback.json`。
|
|
438
|
+
因此可以只把密码留在共享 URL 里,其他字段仍用插件变量覆盖。
|
|
439
|
+
- `from` 可用 `?from=` 指定;缺省取 SMTP 用户名(若像邮箱),否则取收件人。
|
|
440
|
+
- 这个变量携带密码:不要写进仓库、不要截图;修改后需要重启 DSH 才会被新进程读到。
|
|
441
|
+
|
|
442
|
+
### 配置文件(与环境变量逐字段合并,环境变量优先)
|
|
443
|
+
|
|
444
|
+
```json
|
|
445
|
+
{
|
|
446
|
+
"to": "feedback@example.com",
|
|
447
|
+
"from": "dsh-bot@example.com",
|
|
448
|
+
"smtp": {
|
|
449
|
+
"host": "smtp.example.com",
|
|
450
|
+
"port": 465,
|
|
451
|
+
"secure": true,
|
|
452
|
+
"user": "dsh-bot@example.com",
|
|
453
|
+
"pass": "应用专用密码"
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
文件权限请自行收紧(Windows 可用 `icacls feedback.json /inheritance:r /grant:r "%USERNAME%:R"`)。
|
|
459
|
+
凭据缺失时只报"未配置",不会把已配置的密码回显到页面或日志。
|
|
460
|
+
|
|
461
|
+
### 收件邮箱加密(可选)
|
|
462
|
+
|
|
463
|
+
如果不想让收件邮箱以明文留在 `feedback.json` 里(例如担心截图、误提交或备份泄漏),
|
|
464
|
+
可以把 `"to"` 换成密文字段 `"toEncrypted"`,密钥只放在环境变量里:
|
|
465
|
+
|
|
466
|
+
```json
|
|
467
|
+
{
|
|
468
|
+
"toEncrypted": "im1:……",
|
|
469
|
+
"smtp": { "host": "smtp.qq.com", "port": 465, "secure": true, "user": "…", "pass": "授权码" }
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
1. 设置密钥环境变量(至少 16 个字符,不能写进仓库):
|
|
474
|
+
`DSH_INSTRUCTION_MEMORY_FEEDBACK_SECRET=……`
|
|
475
|
+
2. 用插件自带的小工具生成密文(密钥从环境变量读取,不会打印到终端):
|
|
476
|
+
`node lib/mailer.js encrypt-to you@example.com`
|
|
477
|
+
3. 把输出的 `im1:…` 粘贴为 `toEncrypted`,删除 `"to"`(同时保留两者会被拒绝),然后重启 DSH
|
|
478
|
+
(环境变量只在启动时读取)。
|
|
479
|
+
|
|
480
|
+
实现是 AES-256-GCM + scrypt 派生密钥,密文带认证标签:改错一个字符或密钥不匹配都会明确报
|
|
481
|
+
`无法解密反馈收件邮箱`,不会静默换收件人。配置文件里同时留下 `to` 和 `toEncrypted` 会被**直接拒绝**
|
|
482
|
+
(否则明文一直有效,加密只是摆设);环境变量 `DSH_INSTRUCTION_MEMORY_FEEDBACK_TO` 仍优先于
|
|
483
|
+
文件里的 `toEncrypted`,用于临时改收件人。
|
|
484
|
+
|
|
485
|
+
**能力边界(请勿误解)**:SMTP 信封必须把收件人明文交给服务器,TLS 只保护传输链路;
|
|
486
|
+
本加密保护的是配置文件**静态副本**,不防"能以同一 Windows 用户身份读取环境变量和文件"的攻击者,
|
|
487
|
+
也不隐藏邮件服务商看到的收件人。需要更强隔离时,应使用独立的 Windows 账户或独立机器运行 DSH。
|
|
488
|
+
`node lib/mailer.js encrypt-to` 生成的密文与密钥绑定,换机/换密钥后需重新生成。
|
|
489
|
+
|
|
490
|
+
国内邮箱示例:QQ 邮箱填 `smtp.qq.com`、端口 465、`"secure": true`,用户名为完整 QQ 邮箱地址,
|
|
491
|
+
密码填在 QQ 邮箱「设置 → 账户」开启 SMTP 后生成的**授权码**(不是登录密码)。
|
|
492
|
+
若收件人是该 QQ 邮箱、发件人另用其他账号,`from` 要与发件账号同域,否则可能被服务端拒收。
|
|
493
|
+
|
|
494
|
+
### 发送行为与排查
|
|
495
|
+
|
|
496
|
+
- 465 端口:直接 TLS;587 端口:EHLO → STARTTLS → 再次 EHLO →(有账号时)`AUTH LOGIN`/`AUTH PLAIN`。
|
|
497
|
+
- **拒绝在明文连接上发送密码**:服务器不提供 STARTTLS 时会直接失败,而不是降级。
|
|
498
|
+
- 主题与正文均为 UTF-8(RFC 2047 编码主题、base64 正文),中文不会乱码。
|
|
499
|
+
- 成功后清空草稿并提示"反馈已发送";失败时保留草稿,页面显示原因,宿主日志记一行脱敏错误。
|
|
500
|
+
- 面板为一次发送最多等待 90 秒,宿主自身的整封投递上限是 60 秒:面板总比宿主晚放弃,
|
|
501
|
+
因此"超时"提示出现时邮件不会再迟到地发出去。
|
|
502
|
+
- 默认 30 秒冷却:同一进程内连续提交会被拒绝(防误触重复发信),可用上面的变量调整;
|
|
503
|
+
冷却在发送**开始**前就占位,所以两个同时到达的请求也只有一个会被投递。
|
|
504
|
+
|
|
505
|
+
常见失败:`尚未配置反馈收件邮箱` → 环境变量/文件未生效(环境变量改完要重启 DSH;
|
|
506
|
+
配置文件每次发送都会重新读取,无需重启);`反馈收件邮箱已加密,但未设置 …SECRET` 或
|
|
507
|
+
`无法解密反馈收件邮箱` → 密钥缺失/不匹配,重新执行 `encrypt-to` 并同步更新环境变量;
|
|
508
|
+
`配置文件同时包含 to 与 toEncrypted` → 删掉明文 `to`;`反馈收件邮箱含非 ASCII 字符`
|
|
509
|
+
→ 换成 ASCII 地址(未实现 SMTPUTF8);`配置文件 smtp.pass 类型无效` → 密码写成 JSON 字符串;
|
|
510
|
+
`SMTP 总耗时超过 60000 ms` → 服务端长时间无响应,检查主机/端口/网络;
|
|
511
|
+
`SMTP 连接失败` → 主机/端口/防火墙;`STARTTLS 失败` → 服务商要求 465 隐式 TLS;
|
|
512
|
+
`认证失败` → 用户名/密码或应用专用密码,部分邮箱需先开启 SMTP 服务;
|
|
513
|
+
`发件地址被拒绝` → 显式设置 `FROM` 为与账号同域的地址。
|
|
514
|
+
|
|
384
515
|
## 请求来源防护(Host / Origin)
|
|
385
516
|
|
|
386
517
|
设置页路由之所以要求 `content-type: application/json`,是因为 HTML 表单无法伪造 JSON body,
|
|
@@ -451,19 +582,21 @@ GUI 与路由本就同主机不同端口),`none`(用户直接导航)放
|
|
|
451
582
|
|
|
452
583
|
| 等级 | 证明什么 | 覆盖 |
|
|
453
584
|
| --- | --- | --- |
|
|
454
|
-
| ① **真实启动** | 在该宿主版本上真的把 DSH 跑起来:插件装载、注入真的进入**组装后的提示**、插件 HTTP 路由可应答、客户端设置页模块被下发(Windows / Node 22) | `0.1.3-alpha.2` · `0.1.5-alpha.1` · `0.1.5-alpha.2` · `0.1.5-rc.1` · `0.1.5-rc.2` · **`0.2.0-rc.1`** ⁽*⁾ · **`0.2.0-rc.2`** ⁽**⁾ |
|
|
455
|
-
| ② **契约核验** | 抓该版本**真实发布的源码**,逐个核验本插件实际用到的每个符号:`systemPrompt.section()`、`assemble()` 是否求值**函数型** `text`、`FILE_REFERENCE=900` / `TOOL_BASH=1000` 仍在(决定 order 950 是否空档)、重名注册是否抛错、`webServer.register()` / `get host()`、`ctx.inject()` / `ctx.effect()`、`dsh.client.inject` 两包在该版本是否存在且声明了 `dsh.client` | 声明范围接住的**全部
|
|
585
|
+
| ① **真实启动** | 在该宿主版本上真的把 DSH 跑起来:插件装载、注入真的进入**组装后的提示**、插件 HTTP 路由可应答、客户端设置页模块被下发(Windows / Node 22) | `0.1.3-alpha.2` · `0.1.5-alpha.1` · `0.1.5-alpha.2` · `0.1.5-rc.1` · `0.1.5-rc.2` · `0.1.5-rc.3` · **`0.2.0-rc.1`** ⁽*⁾ · **`0.2.0-rc.2`** ⁽**⁾ · `0.2.1-alpha.2` ⁽***⁾ · **`0.2.1-alpha.3`** ⁽***⁾ · **`0.2.1-alpha.4`** ⁽***⁾ |
|
|
586
|
+
| ② **契约核验** | 抓该版本**真实发布的源码**,逐个核验本插件实际用到的每个符号:`systemPrompt.section()`、`assemble()` 是否求值**函数型** `text`、`FILE_REFERENCE=900` / `TOOL_BASH=1000` 仍在(决定 order 950 是否空档)、重名注册是否抛错、`webServer.register()` / `get host()`、`ctx.inject()` / `ctx.effect()`、`dsh.client.inject` 两包在该版本是否存在且声明了 `dsh.client` | 声明范围接住的**全部 18 个已发布版本**(`0.1.3-alpha.2` → `0.2.1-alpha.4`) |
|
|
456
587
|
| ③ **真实执行** | 把插件**真的挂载**进该版本的 `@deepseek-ai/cordis`,跑装载 / 路由注册 / 卸载共 9 项断言 | cordis `4.0.2` 与 `4.0.4`(各 9/9) |
|
|
457
588
|
|
|
458
589
|
⁽*⁾ **`0.2.0-rc.1` 的①档有一条要说明**:插件装载、注入进入组装后的提示、HTTP 路由可应答三项**均已实测**(路由 `HTTP 200`;`injection.registered=true`、`chars=437`、**`pulls=38`**——即 0.2.0-rc.1 的 agent loop 真的求值了 38 次函数型 `text`)。第四项"客户端模块被下发"**没有直接抓取**:首页需要鉴权(`/` 返回 `401`),我无法取到 `window.__DSH_BOOT__`。这一项的支撑是间接但强的两条:① `dsh-client-modules` 在合成阶段对 `dsh.client` 声明做硬校验,不合格会抛 `ClientPackageCompositionError` 直接导致启动失败——宿主正常启动即说明声明通过了 0.2.0-rc.1 的校验;② 本插件用到的三个客户端契约包(`-slots` / `-renderer` / `-settings`)相对升级前真正在跑的 `0.1.7-rc.2` **逐字节未变**。**未做人工视觉确认**(没有打开设置页看)。
|
|
459
590
|
|
|
460
591
|
⁽**⁾ **`0.2.0-rc.2` 的①档**:桌面版 `0.2.0-rc.2`(私有发行版 `dsh-desktop-runtime`,见仓库 `dsh-memory-verification/DSH-VERSION-COMPAT.md` §12)上完成过路由 200 + `registered=true` + 注入段落真实出现在会话提示中的活体实测;网页版宿主 `0.2.0-rc.2` 上对本插件路由的 `state` 探测同样返回 200。同样**未做设置页人工视觉确认**。
|
|
461
592
|
|
|
462
|
-
|
|
593
|
+
⁽***⁾ **`0.2.1-alpha.2`(2026-10-10)与 `0.2.1-alpha.3` / `0.2.1-alpha.4`(2026-10-16)的①档**:隔离安装该版本宿主树 + 独立 `DSH_HOME` 装入 npm 正式发布物,真实启动 web 宿主后以 **21 项 HTTP 断言**驱动(CRUD、落盘持久化、SSE 连接帧、`section` 注册/注销、4 项请求门禁、IPv6 同源等价与混源拒绝、30 连发稳定性、清理回基线)——三个版本各 **21/21 PASS**。证据:`dsh-memory-verification/out/compat-1016-live-live-alpha2.json`、`dsh-memory-verification/out/compat-133-live-alpha{3,4}.json`。同样**未做设置页人工视觉确认**。
|
|
594
|
+
|
|
595
|
+
此外 `host-range-test.mjs` 的固定矩阵覆盖 `0.1.6-alpha.2`、`0.1.7-rc.2`、`0.2.0-rc.1`、`0.2.0-rc.2` 与 `0.2.1-alpha.1`~`0.2.1-alpha.4`(回归守卫把最新验证过的宿主逐名钉住),并会**读取本机实际安装的宿主版本**做实时断言。
|
|
463
596
|
|
|
464
|
-
**关于 1.0.10 新增的 `ctx.inject`。** 这是新版唯一的硬依赖增量:`inject` 去掉了 `webServer`,设置页路由改为非阻塞的 `ctx.inject(['webServer'], …)` 子 fiber(原因见上)。为此逐个核验了 **cordis 全部
|
|
597
|
+
**关于 1.0.10 新增的 `ctx.inject`。** 这是新版唯一的硬依赖增量:`inject` 去掉了 `webServer`,设置页路由改为非阻塞的 `ctx.inject(['webServer'], …)` 子 fiber(原因见上)。为此逐个核验了 **cordis 全部 8 个已发布版本**(`4.0.1-rc.1` → `4.0.5-alpha.2`)——**每一个都具备 `ctx.inject`**(其中 4.0.2 / 4.0.4 另做了真实挂载执行,4.0.5-alpha.1 与 4.0.5-alpha.2 为源码符号核验),因此声明范围内不存在因它而失效的宿主版本。
|
|
465
598
|
|
|
466
|
-
**关于声明范围是否"超范围承诺"。** 已对全部 **30
|
|
599
|
+
**关于声明范围是否"超范围承诺"。** 已对全部 **33 个**已发布宿主版本核验(2026-10-05 穷举 30 个;新增版本随发布补充——`0.2.1-alpha.2` 于 2026-10-10、`0.2.1-alpha.3` / `0.2.1-alpha.4` 于 2026-10-16,npm 官方 registry):范围内 **18 个**版本的 API 面**全部齐备,超范围承诺 0 项**,且默认与 `includePrerelease` 两种语义在这 18 个版本上零分歧。范围外的 15 个版本(`0.0.1-rc.1` → `0.1.2-rc.1`)API 面**同样齐备**,但它们**从未真实启动验证过**,所以下限维持不动、不向外放宽——不把没验证过的东西写进声明。
|
|
467
600
|
|
|
468
601
|
**关于 `0.2.1-alpha.1`(2026-10-03 发布,1.0.16 起覆盖)。** 它是前述"未来 minor 预发布版本在默认语义下接不住"的**第一个真实发生案例**:1.0.15 的范围在宿主门禁与市场判定(均用 `includePrerelease`)下放行它,但在 npm/pnpm 默认 peer 解析下拒绝(ERESOLVE);其耦合面(9 个契约点)经最近 10 版本矩阵核验全部齐备,cordis 为 `4.0.5-alpha.1`。1.0.16 新增 `>=0.2.1-alpha.0 <0.3.0-0` 枚举段后两种语义一致放行。**该版本仅有契约层证据,未做真机启动实测。**
|
|
469
602
|
|
package/docs/screenshot.png
CHANGED
|
Binary file
|