dsh-memory-palace 1.7.2-alpha.1 → 1.7.2-alpha.3
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 +14 -9
- package/assets/memory-icon.svg +17 -0
- package/lib/client.js +47 -9
- package/lib/common/records.mjs +3 -2
- package/lib/distill.mjs +2 -2
- package/lib/hybrid/subagent.mjs +3 -3
- package/lib/hybrid/tools.mjs +21 -13
- package/lib/index.js +82 -38
- package/lib/projection.mjs +185 -173
- package/lib/tools.mjs +18 -12
- package/package.json +58 -56
package/README.md
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
把 WorkBuddy 的文件式记忆系统移植进 [DeepSeek Harness](https://www.deepseek.com/harness/) —— 为 Harness 提供**跨会话持久化、人类可直接编辑的 Markdown 记忆**。
|
|
4
4
|
|
|
5
|
-
[](./LICENSE) [](https://awesome-dsh-plugin.com) [](https://www.npmjs.com/package/dsh-memory-palace) [](./LICENSE) [](https://awesome-dsh-plugin.com) [](https://www.npmjs.com/package/dsh-memory-palace) [](https://github.com/deepseek-ai/deepseek-harness)
|
|
6
6
|
|
|
7
7
|
## 特性
|
|
8
8
|
|
|
9
9
|
- **人类可读的真源**:记忆以纯 Markdown 存储,任何编辑器都能直接修改,数据始终属于你。
|
|
10
10
|
- **双层记忆**:用户级(跨项目个人偏好)+ 工作区级(项目约定),互不干扰。
|
|
11
|
-
- **日志自动沉淀**:超过保留期的每日日志自动并入长期记忆,结论不丢。
|
|
12
11
|
- **WorkBuddy / CodeBuddy 桥接**:项目已有对应记忆目录时直接读写,无需重复维护。
|
|
13
12
|
- **记忆工具**:AI 可主动写入、聚合读取、按内容删除(删除需人工确认),写入自带去重。
|
|
14
13
|
- **手动蒸馏**:会话标题栏一键把当前对话或项目记忆提炼成长期记忆。
|
|
15
14
|
- **设置页集成**:全部配置均可在 DSH 设置面板中图形化调整(按板块折叠),无需改配置文件。
|
|
16
15
|
- **自定义指令**:不适合写进记忆的特殊指令可在设置页配置,插件经系统提示词注入。
|
|
17
|
-
- **主动记忆(插件模式)**:以指令引导 AI 在完成任务、修复问题、确定决策、获知偏好时主动落档。
|
|
18
|
-
- **智能模式**:由模型自动提炼每轮新增内容,摘要进日志、长期事实进记忆。
|
|
19
16
|
- **记忆注入**:长期记忆与今日日志以「会话起始快照」常驻上下文、按文件身份各只注入一次 —— 既保证每个步骤都可见,又不破坏前缀缓存、不堆积冗余快照。
|
|
20
|
-
-
|
|
17
|
+
- **记忆模式**
|
|
18
|
+
- **主动记忆(插件模式)**:以指令引导 AI 在完成任务、修复问题、确定决策、获知偏好时主动落档。*即将移除*
|
|
19
|
+
- **智能模式**:由模型自动提炼每轮新增内容,摘要进日志、长期事实进记忆。*即将移除*
|
|
20
|
+
- **混合模式(🔥推荐)**:子代理每轮自动整理日志并去重,长期记忆由主 AI 主动维护,兼顾自动化与可控性。
|
|
21
21
|
- **标准插件包**:经官方插件机制一键安装,无需改动 harness。
|
|
22
22
|
|
|
23
23
|
## 记忆文件布局
|
|
@@ -46,8 +46,6 @@
|
|
|
46
46
|
② 对话历史投影(易变):用户级 + 项目级长期记忆、今日日志——按文件身份各注入一次,压缩后自动重注最新版
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
> **极简模式(静默预设)**:宿主的「模式」= agent 平面预设(`presets/*/agent.cordis.yml`),官方 minimal 用 persona `complete: true` 独占系统提示词,是刻意留白的「裸测环境」(跑分口径)。但本插件注册在 host 平面,各通道对**每个会话**都生效(`complete` 只吞 system 提示词、**不裁工具**),所以必须按会话自行静默。默认 `silentPresets = ["minimal"]` 已覆盖官方极简模式:命中预设的会话里,系统提示词、记忆投影、7 个记忆工具(**schema 一并隐身,零 token 足迹**)、turn-end 自动写入与 hybrid 记忆子代理**全部关闭**,语义等价于该会话没装本插件,不留半注入状态。判据取会话创建头 `agentPreset`(含未显式指定时的部署默认)与预设切换事件;判据缺失一律 fail-open(不静默),不会因识别不到而丢记忆。要静默其它预设,在设置页「静默预设」里加 id(逗号分隔)。
|
|
50
|
-
|
|
51
49
|
**写入(每轮结束)**——经「防闲聊闸门」判定后异步追加:
|
|
52
50
|
|
|
53
51
|
```
|
|
@@ -74,13 +72,13 @@
|
|
|
74
72
|
|
|
75
73
|
## 安装
|
|
76
74
|
|
|
77
|
-
前置要求:已安装 DeepSeek Harness 及其 CLI(`dsh`
|
|
75
|
+
前置要求:已安装 DeepSeek Harness 及其 CLI(`dsh` 命令可用)。
|
|
78
76
|
|
|
79
77
|
方式一:直接通过 GitHub 安装(推荐,`lib/` 构建产物已随仓库分发,装即用)
|
|
80
78
|
|
|
81
79
|
```bash
|
|
82
80
|
dsh plugin --profile web add github:lovezi0/dsh-memory-palace
|
|
83
|
-
# 锁定版本:dsh plugin --profile web add github:lovezi0/dsh-memory-palace#v1.7.2-alpha.
|
|
81
|
+
# 锁定版本:dsh plugin --profile web add github:lovezi0/dsh-memory-palace#v1.7.2-alpha.3
|
|
84
82
|
```
|
|
85
83
|
|
|
86
84
|
方式二:clone 后本地安装(开发 / 修改源码场景)
|
|
@@ -122,6 +120,13 @@ dsh plugin --profile web remove dsh-memory-palace
|
|
|
122
120
|
## 版本历史
|
|
123
121
|
|
|
124
122
|
- **1.7.2**
|
|
123
|
+
- **1.7.2-alpha.3**
|
|
124
|
+
- 🐛修复记忆投影通道并发导致记忆串台的问题 @sunligh91
|
|
125
|
+
- 🐛修复插件工具串台问题
|
|
126
|
+
- **1.7.2-alpha.2**
|
|
127
|
+
- 💪适配deepseek harness 0.1.7-alpha.1
|
|
128
|
+
- 💪add memory plugin icon
|
|
129
|
+
- 💪记忆模式默认为[混合模式] *未来的版本将移除插件模式&智能模式*
|
|
125
130
|
- **1.7.2-alpha.1**
|
|
126
131
|
- 💪dsh Agent 预设[极简模式]时禁用记忆注入、记忆生成、记忆工具
|
|
127
132
|
- **1.7.1**
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<svg width="28" height="28" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<!-- 记忆图标 · DSH 鲸鱼 + 双星芒(源码直出合并)· 28×28 -->
|
|
3
|
+
<!-- 鲸鱼(全精度 path,位置 4,9 → 17×12.75) -->
|
|
4
|
+
<g transform="translate(4 9) scale(0.7340 0.7482) translate(0 -3.52)">
|
|
5
|
+
<path d="M23.0584 4.95203C22.8129 4.83203 22.7074 5.06103 22.5639 5.17704C22.5149 5.21454 22.4734 5.26354 22.4319 5.30854C22.0734 5.69155 21.6543 5.94306 21.1073 5.91306C20.3073 5.86806 19.6243 6.11957 19.0203 6.73158C18.8918 5.97706 18.4652 5.52655 17.8162 5.23754C17.4767 5.08753 17.1332 4.93703 16.8952 4.61052C16.7292 4.37801 16.6837 4.11901 16.6007 3.8635C16.5477 3.70949 16.4952 3.55199 16.3177 3.52549C16.1252 3.49549 16.0497 3.65699 15.9742 3.792C15.6722 4.34401 15.5552 4.95203 15.5667 5.56805C15.5932 6.95359 16.1782 8.05712 17.3407 8.84215C17.4727 8.93215 17.5067 9.02215 17.4652 9.15366C17.3857 9.42416 17.2917 9.68667 17.2087 9.95718C17.1557 10.1297 17.0767 10.1677 16.8917 10.0922C16.2537 9.82568 15.7027 9.43117 15.2156 8.95465C14.3891 8.15513 13.6416 7.2726 12.7096 6.58158C12.4906 6.42007 12.2716 6.27007 12.045 6.12707C11.094 5.20354 12.1696 4.44502 12.4186 4.35501C12.6791 4.26101 12.5091 3.938 11.6675 3.942C10.826 3.9455 10.056 4.22751 9.07446 4.60302C8.93096 4.65952 8.77995 4.70052 8.62545 4.73452C7.73492 4.56552 6.80989 4.52802 5.84386 4.63702C4.02481 4.83953 2.57177 5.69955 1.50373 7.1676C0.220694 8.93215 -0.0813148 10.9372 0.288196 13.0283C0.676708 15.2323 1.80174 17.0569 3.53029 18.4834C5.32285 19.9625 7.38741 20.6875 9.74298 20.5485C11.1735 20.466 12.7661 20.2745 14.5626 18.7539C15.0156 18.9795 15.4912 19.0695 16.2797 19.137C16.8872 19.1935 17.4722 19.107 17.9252 19.013C18.6347 18.8629 18.5857 18.2059 18.3292 18.0854C16.2497 17.1169 16.7062 17.5109 16.2912 17.1919C17.3477 15.9419 18.9618 13.7198 19.4598 10.6942C19.5088 10.3602 19.5713 9.88968 19.5638 9.61917C19.5598 9.45417 19.5978 9.39016 19.7863 9.37116C20.3073 9.31116 20.8128 9.16866 21.2773 8.91315C22.6249 8.17713 23.1684 6.96809 23.2964 5.51905C23.3154 5.29754 23.2924 5.06853 23.0584 4.95203ZM11.3165 17.9954C9.30097 16.4109 8.32344 15.8894 7.91992 15.9119C7.54241 15.9344 7.61042 16.3664 7.69342 16.6479C7.78042 16.9259 7.89342 17.1174 8.05193 17.3614C8.16143 17.5229 8.23694 17.7629 7.94243 17.9434C7.29341 18.3449 6.16487 17.8084 6.11187 17.7819C4.79833 17.0084 3.7003 15.9874 2.92628 14.5908C2.17875 13.2468 1.74474 11.8047 1.67324 10.2657C1.65424 9.89418 1.76374 9.76267 2.13375 9.69517C2.62077 9.60517 3.12278 9.58617 3.6093 9.65767C5.66636 9.95818 7.41741 10.8777 8.88545 12.3348C9.72348 13.1643 10.3575 14.1558 11.0105 15.1243C11.705 16.1529 12.4521 17.1329 13.4036 17.9364C13.7396 18.2179 14.0076 18.4319 14.2641 18.5899C13.4906 18.6764 12.1996 18.6949 11.3165 17.9964V17.9954ZM12.2826 11.7817C12.2826 11.6167 12.4146 11.4852 12.5806 11.4852C12.6181 11.4852 12.6521 11.4927 12.6826 11.5037C12.7241 11.5187 12.7621 11.5412 12.7921 11.5752C12.8451 11.6277 12.8751 11.7027 12.8751 11.7817C12.8751 11.9467 12.7431 12.0782 12.5771 12.0782C12.4111 12.0782 12.2826 11.9467 12.2826 11.7817ZM15.2831 13.3208C15.0906 13.3998 14.8981 13.4673 14.7131 13.4748C14.4261 13.4898 14.1131 13.3733 13.9431 13.2308C13.6791 13.0093 13.4901 12.8853 13.4111 12.4988C13.3771 12.3338 13.3961 12.0782 13.4261 11.9317C13.4941 11.6162 13.4186 11.4137 13.1961 11.2297C13.0151 11.0797 12.7846 11.0382 12.5316 11.0382C12.4371 11.0382 12.3506 10.9967 12.2861 10.9632C12.1806 10.9107 12.0936 10.7792 12.1766 10.6177C12.2031 10.5652 12.3316 10.4377 12.3616 10.4152C12.7051 10.2197 13.1011 10.2837 13.4676 10.4302C13.8071 10.5692 14.0641 10.8242 14.4336 11.1847C14.8111 11.6202 14.8791 11.7402 15.0941 12.0672C15.2641 12.3228 15.4186 12.5853 15.5247 12.8858C15.5887 13.0733 15.5057 13.2268 15.2831 13.3208Z" fill="#000000"/>
|
|
6
|
+
</g>
|
|
7
|
+
<!-- 大星(x.svg 大星全精度 path,位置 19,3 → 6×6,持续闪烁) -->
|
|
8
|
+
<g transform="translate(19 3) scale(0.8286) translate(0.059 -2.790)">
|
|
9
|
+
<path d="M3.80671 9.79513C3.72888 10.0318 3.39417 10.0318 3.31634 9.79513L2.8653 8.42365C2.66042 7.80074 2.17185 7.31217 1.54894 7.1073L0.177462 6.65626C-0.0591539 6.57843 -0.0591539 6.24371 0.177462 6.16589L1.54894 5.71484C2.17185 5.50997 2.66042 5.0214 2.8653 4.39849L3.31634 3.02701C3.39417 2.79039 3.72888 2.79039 3.80671 3.02701L4.25775 4.39849C4.46262 5.0214 4.9512 5.50997 5.57411 5.71484L6.94558 6.16589C7.1822 6.24371 7.1822 6.57843 6.94558 6.65626L5.57411 7.1073C4.9512 7.31217 4.46262 7.80074 4.25775 8.42365L3.80671 9.79513Z" fill="#426EFE"/>
|
|
10
|
+
<animate attributeName="opacity" values="1;0.15;1" keyTimes="0;0.5;1" dur="1.2s" repeatCount="indefinite"/>
|
|
11
|
+
</g>
|
|
12
|
+
<!-- 小星(x.svg 小星全精度 path,位置 23,1 → 4×4,轻闪烁) -->
|
|
13
|
+
<g transform="translate(23 1) scale(0.9837) translate(-5.987 0.033)">
|
|
14
|
+
<path d="M8.15819 3.90034C8.11449 4.03322 7.92653 4.03322 7.88282 3.90034L7.62954 3.13018C7.51449 2.78038 7.24013 2.50602 6.89033 2.39097L6.12016 2.13769C5.98729 2.09398 5.98729 1.90602 6.12016 1.86231L6.89033 1.60903C7.24013 1.49398 7.51449 1.21962 7.62954 0.869819L7.88282 0.0996549C7.92653 -0.0332183 8.11449 -0.0332183 8.15819 0.0996549L8.41148 0.869819C8.52653 1.21962 8.80089 1.49398 9.15069 1.60903L9.92085 1.86231C10.0537 1.90602 10.0537 2.09398 9.92085 2.13769L9.15069 2.39097C8.80089 2.50602 8.52653 2.78038 8.41148 3.13018L8.15819 3.90034Z" fill="#426EFE"/>
|
|
15
|
+
<animate attributeName="opacity" values="1;0.45;1" keyTimes="0;0.5;1" dur="1.6s" repeatCount="indefinite"/>
|
|
16
|
+
</g>
|
|
17
|
+
</svg>
|
package/lib/client.js
CHANGED
|
@@ -41,13 +41,13 @@ window.__ModuleLoader__.load({
|
|
|
41
41
|
wsBudgetLabel: "工作区预算(字符)",
|
|
42
42
|
wsBudgetHint: "工作区 MEMORY.md 的最大字符数",
|
|
43
43
|
summarizeLabel: "轮次结束自动记录",
|
|
44
|
-
summarizeHint: "
|
|
44
|
+
summarizeHint: "三种记忆模式的共用总闸门:关闭后三种模式都不写记忆。插件模式下它作为「agent 主动记忆」主路径失效时的安全网,只留原始文本、不做总结;智能模式走 LLM 智能摘要(总结 + 提炼);混合模式下整个记忆子代理停摆(不做兜底写入)。",
|
|
45
45
|
autoCaptureErrorsLabel: "对话出错时自动记录到记忆",
|
|
46
46
|
autoCaptureErrorsHint: "对话中(含代码运行报错、工具执行失败)出错时,自动把「错误现象」写入对应 MEMORY.md(用户级/项目级);「根因/方案」由 agent 按记忆公民指令主动记。默认开,关闭无需重启。",
|
|
47
47
|
memoryModeLabel: "记忆模式",
|
|
48
48
|
memoryModeHintPlugin: "插件模式:记忆公民指令 + 轮次轻量 + 错误捕获。切换需重启 dsh 生效。",
|
|
49
49
|
memoryModeHintSmart: "智能模式:LLM 智能会话摘要(summary→每日日志 + durable→MEMORY.md)。切换需重启 dsh 生效。",
|
|
50
|
-
memoryModeHintHybrid: "
|
|
50
|
+
memoryModeHintHybrid: "混合模式(默认):记忆子代理自动维护今日日志(章节化、标删去重),agent 主动维护 MEMORY.md(章节化写入、整章节替换、门禁内全量重整)。切换需重启 dsh 生效。",
|
|
51
51
|
reorgCooldownLabel: "重整冷却(天)",
|
|
52
52
|
reorgCooldownHint: "项目级 MEMORY.md 全量重整的冷却天数(距上次重整);与「超出注入预算」双条件同时满足才允许 memory_reorganize。默认 7 天。",
|
|
53
53
|
subagentLogBudgetLabel: "子代理日志回喂预算(字符)",
|
|
@@ -126,13 +126,13 @@ window.__ModuleLoader__.load({
|
|
|
126
126
|
wsBudgetLabel: "Workspace budget (chars)",
|
|
127
127
|
wsBudgetHint: "Max chars for the workspace MEMORY.md",
|
|
128
128
|
summarizeLabel: "Auto-record at turn end",
|
|
129
|
-
summarizeHint: "The shared master gate for
|
|
129
|
+
summarizeHint: "The shared master gate for all three memory modes: when off, none of them writes any memory. In plugin mode it is the safety net when the 'agent proactive memory' main path fails (keeps raw text, no summarization); in smart mode it enables the LLM summarization path (summary + extraction); in hybrid mode the whole memory sub-agent stays idle (no fallback entry either).",
|
|
130
130
|
autoCaptureErrorsLabel: "Auto-record conversation errors",
|
|
131
131
|
autoCaptureErrorsHint: "On in-session errors, auto write the 'error' into the corresponding MEMORY.md (user/project); 'root cause / fix' is recorded proactively by the agent. On by default; off takes effect without restart.",
|
|
132
132
|
memoryModeLabel: "Memory mode",
|
|
133
133
|
memoryModeHintPlugin: "Plugin mode: memory-citizen instructions + turn-end light entries + error capture. Switching requires a dsh restart.",
|
|
134
134
|
memoryModeHintSmart: "Smart mode: LLM conversation summarization (summary -> daily log + durable -> MEMORY.md). Switching requires a dsh restart.",
|
|
135
|
-
memoryModeHintHybrid: "Hybrid mode: a memory sub-agent auto-maintains today's daily log (chaptered, dedupe by strikethrough); the agent proactively maintains MEMORY.md (chaptered writes, section replace, gated full reorganize). Switching requires a dsh restart.",
|
|
135
|
+
memoryModeHintHybrid: "Hybrid mode (default): a memory sub-agent auto-maintains today's daily log (chaptered, dedupe by strikethrough); the agent proactively maintains MEMORY.md (chaptered writes, section replace, gated full reorganize). Switching requires a dsh restart.",
|
|
136
136
|
reorgCooldownLabel: "Reorganize cooldown (days)",
|
|
137
137
|
reorgCooldownHint: "Cooldown days between full reorganizes of the project MEMORY.md; combined with 'over injection budget' both gates must pass for memory_reorganize. Default 7.",
|
|
138
138
|
subagentLogBudgetLabel: "Sub-agent log feed budget (chars)",
|
|
@@ -201,7 +201,7 @@ window.__ModuleLoader__.load({
|
|
|
201
201
|
autoCaptureErrors: v.autoCaptureErrors === false ? "false" : "true",
|
|
202
202
|
distillDebugLog: v.distillDebugLog === false ? "false" : "true",
|
|
203
203
|
summaryTimeoutMs: v.summaryTimeoutMs != null ? String(Math.round((Number(v.summaryTimeoutMs) || 0) / 1000)) : "60",
|
|
204
|
-
memoryMode: v.memoryMode || "
|
|
204
|
+
memoryMode: v.memoryMode || "hybrid",
|
|
205
205
|
summaryModel: v.summaryModel || "",
|
|
206
206
|
summaryMaxTokens: v.summaryMaxTokens != null ? String(v.summaryMaxTokens) : "2000",
|
|
207
207
|
projectMaxTokens: v.projectMaxTokens != null ? String(v.projectMaxTokens) : "8000",
|
|
@@ -701,7 +701,7 @@ window.__ModuleLoader__.load({
|
|
|
701
701
|
? t("memoryModeHintHybrid")
|
|
702
702
|
: t("memoryModeHintPlugin"),
|
|
703
703
|
h("select", {
|
|
704
|
-
value: ["plugin", "smart", "hybrid"].includes(draft.memoryMode) ? draft.memoryMode : "
|
|
704
|
+
value: ["plugin", "smart", "hybrid"].includes(draft.memoryMode) ? draft.memoryMode : "hybrid",
|
|
705
705
|
disabled,
|
|
706
706
|
onChange: (e) => edit("memoryMode", e.target.value),
|
|
707
707
|
style: selectStyle
|
|
@@ -727,7 +727,7 @@ window.__ModuleLoader__.load({
|
|
|
727
727
|
row(t("summaryModelLabel"), t("summaryModelHint"),
|
|
728
728
|
h("select", {
|
|
729
729
|
// 已存储值不在下拉选项内(如旧版双前缀脏数据)→ 归零为「复用当前会话模型」,
|
|
730
|
-
// 避免 select 显示悬空值、保存时又把脏值写回
|
|
730
|
+
// 避免 select 显示悬空值、保存时又把脏值写回 profile 条目 config。
|
|
731
731
|
value: modelOptions.some((o) => o.value === draft.summaryModel) ? draft.summaryModel : "",
|
|
732
732
|
disabled,
|
|
733
733
|
style: selectStyle,
|
|
@@ -757,7 +757,8 @@ window.__ModuleLoader__.load({
|
|
|
757
757
|
|
|
758
758
|
|
|
759
759
|
// ---- v1.2.0 会话标题栏「记忆」按钮(蒸馏入口)----
|
|
760
|
-
// sparkle-twinkle.svg 内联(
|
|
760
|
+
// sparkle-twinkle.svg 内联(client 侧只能 require("react")、不能按包内路径取资源;该文件也不在
|
|
761
|
+
// package.json files 数组内,不随 npm 分发——files 里只多列了 assets/memory-icon.svg 供宿主插件面板读取)。
|
|
761
762
|
// 类名/渐变 id 加 mpd- 前缀防全局冲突;twinkle 动画 keyframes 由注入的样式表提供。
|
|
762
763
|
const SPARKLE_SVG =
|
|
763
764
|
'<svg width="14" height="14" viewBox="0 0 11 10" fill="none" xmlns="http://www.w3.org/2000/svg">' +
|
|
@@ -783,6 +784,30 @@ window.__ModuleLoader__.load({
|
|
|
783
784
|
document.head.appendChild(style);
|
|
784
785
|
}
|
|
785
786
|
|
|
787
|
+
// ---- v1.8.0 设置面板 nav 图标(「记忆」条目)----
|
|
788
|
+
// 宿主 SettingsRoot.navIcon(id) 是**硬编码白名单**(account/models/agent-presets/plugins/archived-sessions),
|
|
789
|
+
// 未列出的第三方 section 一律回退齿轮图标,且 slots 契约(id/order/label)没开 nav 图标位、
|
|
790
|
+
// 官方文档亦无「从 manifest 取 nav 图标」的机制(manifest icon 只进插件管理面板的卡片/行)。
|
|
791
|
+
// 故在插件边界内绕行两步:
|
|
792
|
+
// ① label 返回 React 节点 —— 宿主 ui-slots 的 resolveSlotLabel 是 `typeof label === 'function' ? label() : label`
|
|
793
|
+
// (原样返回、不做字符串强转),SettingsRoot 直接把它当 children 渲染,节点可行;
|
|
794
|
+
// ② 注入 CSS 用 :has() 命中「含本星标的 nav 按钮」,隐藏其**直接子 svg**(即宿主齿轮;星标在 label span 内部,不在命中面)。
|
|
795
|
+
// 失效面:宿主若改结构/去掉 :has 支持 → 退化为「齿轮 + 星标」并排,不报错、不影响功能(有意选的可降级方案)。
|
|
796
|
+
function ensureNavStarStyles() {
|
|
797
|
+
if (typeof document === "undefined") return; // 非浏览器环境(单测/构建期)静默跳过
|
|
798
|
+
injectDistillStyles(); // 星标动画 keyframes 与蒸馏按钮共用同一份样式表
|
|
799
|
+
if (document.getElementById("memory-palace-nav-star-styles")) return;
|
|
800
|
+
const style = document.createElement("style");
|
|
801
|
+
style.id = "memory-palace-nav-star-styles";
|
|
802
|
+
style.textContent = [
|
|
803
|
+
// 星标容器:与宿主 navIcon 同尺寸(16px)、紧贴文字前。
|
|
804
|
+
".mp-nav-star{display:inline-flex;flex:none;align-items:center;margin-right:8px;line-height:0}",
|
|
805
|
+
".mp-nav-star svg{display:block;width:16px;height:16px}",
|
|
806
|
+
"button:has(.mp-nav-star)>svg{display:none!important}"
|
|
807
|
+
].join("");
|
|
808
|
+
document.head.appendChild(style);
|
|
809
|
+
}
|
|
810
|
+
|
|
786
811
|
|
|
787
812
|
// 会话标题栏「记忆」胶囊按钮:点击开合下拉(蒸馏会话/蒸馏项目记忆)→ 自绘确认弹窗 → fetch route → 浏览器通知。
|
|
788
813
|
function DistillButton(props) {
|
|
@@ -1021,13 +1046,26 @@ window.__ModuleLoader__.load({
|
|
|
1021
1046
|
// v1.1.3:设置读写走自有 route(/memory-palace/api),不再 bind settingsScope——
|
|
1022
1047
|
// 非 loopback 下 settingsScope persistence=memory(set() no-op),保存永不落盘。
|
|
1023
1048
|
const controller = new MemoryPalaceController(ctx);
|
|
1049
|
+
// v1.8.0:设置面板 nav 条目换成自家 sparkle-twinkle 星标——宿主无 nav 图标位,
|
|
1050
|
+
// 走「label 传节点 + CSS 隐藏回退齿轮」两步(原理/失效面见 40-sparkle.js 的 ensureNavStarStyles)。
|
|
1051
|
+
ensureNavStarStyles();
|
|
1052
|
+
const navLabel = () =>
|
|
1053
|
+
react.createElement("span", { className: "mp-nav-label" }, [
|
|
1054
|
+
react.createElement("span", {
|
|
1055
|
+
key: "star",
|
|
1056
|
+
className: "mp-nav-star",
|
|
1057
|
+
"aria-hidden": "true",
|
|
1058
|
+
dangerouslySetInnerHTML: { __html: SPARKLE_SVG },
|
|
1059
|
+
}),
|
|
1060
|
+
t("nav"),
|
|
1061
|
+
]);
|
|
1024
1062
|
// v1.6.0-rc1:枚举已配置模型改走 ctx.remote.session.modelCatalog()(ApiProxy 已删),
|
|
1025
1063
|
// 枚举失败则降级为空列表。
|
|
1026
1064
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
1027
1065
|
name: "settings.section",
|
|
1028
1066
|
id: "memory-palace",
|
|
1029
1067
|
order: 20,
|
|
1030
|
-
label:
|
|
1068
|
+
label: navLabel,
|
|
1031
1069
|
locale: NS,
|
|
1032
1070
|
inject: () => controller.inject()
|
|
1033
1071
|
}, (props) => MemoryPalaceSection({ ...props, fetchModels: () => fetchModelOptions(ctx) })));
|
package/lib/common/records.mjs
CHANGED
|
@@ -249,9 +249,10 @@ export function createRecords({ getConfig, paths }) {
|
|
|
249
249
|
|
|
250
250
|
// 错误捕获(目标2):按 scope 落对应 MEMORY.md,条目仅含「错误现象」。
|
|
251
251
|
// (v0.7.1 起不再调 LLM 生成方案——「根因/绕过手法」由 agent 按记忆公民指令场景①主动调 memory_note 记。)
|
|
252
|
-
|
|
252
|
+
// dirs 由调用方(_settle,已按会话 cwd 解析)显式传入——不再自取 paths.writeDirs(),
|
|
253
|
+
// 消除多会话并发时闭包 activeCwd 串台;空 dirs 仍按原语义回落用户级。
|
|
254
|
+
async function captureError(turn, reason, dirs, cwd) {
|
|
253
255
|
const c = cfg();
|
|
254
|
-
const dirs = paths.writeDirs();
|
|
255
256
|
if (!dirs.length && !expandHome(c.userMemoryPath)) return;
|
|
256
257
|
const scope = dirs.length ? "project" : "user";
|
|
257
258
|
const errText = (reason?.message || reason?.error?.message || "(in-session error, no message)")
|
package/lib/distill.mjs
CHANGED
|
@@ -152,7 +152,7 @@ export function createDistill({ ctx, getConfig, paths, records, state }) {
|
|
|
152
152
|
messages = [
|
|
153
153
|
createUserMessage({
|
|
154
154
|
content: [{ type: "text", text: "以下是现有记忆(仅供你增量维护参考,不要当作本次对话内容):\n" + fbText }],
|
|
155
|
-
source: { kind: "plugin
|
|
155
|
+
source: { kind: "plugin:dsh-memory-palace" },
|
|
156
156
|
}),
|
|
157
157
|
...hist,
|
|
158
158
|
];
|
|
@@ -331,7 +331,7 @@ export function createDistill({ ctx, getConfig, paths, records, state }) {
|
|
|
331
331
|
messages: [
|
|
332
332
|
createUserMessage({
|
|
333
333
|
content: [{ type: "text", text: raw }],
|
|
334
|
-
source: { kind: "plugin
|
|
334
|
+
source: { kind: "plugin:dsh-memory-palace" },
|
|
335
335
|
}),
|
|
336
336
|
],
|
|
337
337
|
},
|
package/lib/hybrid/subagent.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import { HYBRID_PROACTIVE, SUBAGENT_SYSTEM } from "./prompts.mjs";
|
|
|
17
17
|
export { HYBRID_PROACTIVE };
|
|
18
18
|
|
|
19
19
|
const MAX_ROUNDS = 6; // 循环轮次上限(含首轮);超限降级
|
|
20
|
-
const MAX_RETRY = 3; // 未知工具触发的催促重试预算(v1.6.3
|
|
20
|
+
const MAX_RETRY = 3; // 未知工具触发的催促重试预算(v1.6.3 定为 3)
|
|
21
21
|
const SURFACE = new Set(["user/message", "assistant/message", "tool/result"]);
|
|
22
22
|
|
|
23
23
|
// 日志文件写锁(全局 promise 链串行化):子 agent 落盘与手动蒸馏按钮/其他写路径并发防护。
|
|
@@ -276,7 +276,7 @@ export async function runMemorySubagent({ ctx, getConfig, paths, state, session,
|
|
|
276
276
|
`请按系统指令处理上面的本轮对话(对话见前文消息):判定是否有实质内容需要写入日志,并执行。${brief}`,
|
|
277
277
|
},
|
|
278
278
|
],
|
|
279
|
-
source: { kind: "plugin
|
|
279
|
+
source: { kind: "plugin:dsh-memory-palace" },
|
|
280
280
|
});
|
|
281
281
|
|
|
282
282
|
const tools = [
|
|
@@ -368,7 +368,7 @@ export async function runMemorySubagent({ ctx, getConfig, paths, state, session,
|
|
|
368
368
|
`本轮唯一可用的写入工具是 log_write_ops(ops 支持 append / new_section / mark_delete);` +
|
|
369
369
|
`需要读取章节时用 log_read_section。请立即重新提交写入。\n` +
|
|
370
370
|
`若你重新判定本轮确实没有值得记录的内容,请输出一行纯文本说明后结束。` }],
|
|
371
|
-
source: { kind: "plugin
|
|
371
|
+
source: { kind: "plugin:dsh-memory-palace" },
|
|
372
372
|
}));
|
|
373
373
|
dbgFail("retry after unknown tool", { retries, names });
|
|
374
374
|
continue;
|
package/lib/hybrid/tools.mjs
CHANGED
|
@@ -71,14 +71,19 @@ function sectionHasEntry(md, section, entry) {
|
|
|
71
71
|
*/
|
|
72
72
|
export function registerHybridTools({ ctx, getConfig, paths, records, state }) {
|
|
73
73
|
const cfg = () => getConfig();
|
|
74
|
+
// 从宿主 execute 第二参(ToolRunContext,defineTool 原样转发)取发起会话 cwd。
|
|
75
|
+
// 用可选链自然得 undefined(勿 `?? null`——null 会绕过 paths 默认参数导致 dirs 恒空)。
|
|
76
|
+
const sessionCwd = (exec) => exec?.agent?.session?.header?.cwd;
|
|
74
77
|
|
|
75
78
|
// 目标文件解析:project = 主目标(dirs[0],多 buddy 目录时与蒸馏按钮同语义只动主目标,
|
|
76
79
|
// 章节级 stale 校验天然防止错位覆写);user = 用户级 MEMORY.md。
|
|
77
|
-
|
|
80
|
+
// cwd 由调用方从 exec.agent.session.header.cwd 透传(多会话并发时 activeCwd 可能是别的
|
|
81
|
+
// 工作区,串台会写错项目);缺省(undefined)回落 paths 工厂默认 = activeCwd,保持旧行为。
|
|
82
|
+
function memFileOf(scope, cwd) {
|
|
78
83
|
if (scope === "user") return { file: expandHome(cfg().userMemoryPath), dirs: [] };
|
|
79
|
-
const dirs = paths.writeDirs();
|
|
84
|
+
const dirs = paths.writeDirs(cwd);
|
|
80
85
|
if (!dirs.length) return null;
|
|
81
|
-
return { file: paths.memoryFileOf(dirs[0]), dirs };
|
|
86
|
+
return { file: paths.memoryFileOf(dirs[0], cwd), dirs };
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
// ---------- memory_write:章节追加/新增(章节化格式规范承载于 description——定案 #11) ----------
|
|
@@ -112,19 +117,20 @@ export function registerHybridTools({ ctx, getConfig, paths, records, state }) {
|
|
|
112
117
|
},
|
|
113
118
|
render: (_args, value) => [{ type: "text", text: value?.message ?? "Saved." }],
|
|
114
119
|
},
|
|
115
|
-
async execute(args) {
|
|
120
|
+
async execute(args, exec) {
|
|
116
121
|
state.recentAgentWrote = true;
|
|
117
122
|
const c = cfg();
|
|
118
123
|
if (!c.enabled) return { ok: false, message: "memory-palace is currently disabled in settings." };
|
|
119
124
|
const section = String(args.section ?? "").trim();
|
|
120
125
|
const entry = String(args.entry ?? "").trim();
|
|
121
126
|
if (!section || !entry) return { ok: false, message: "section 与 entry 均不能为空。" };
|
|
122
|
-
const
|
|
127
|
+
const cwd = sessionCwd(exec);
|
|
128
|
+
const target = memFileOf(args.scope === "user" ? "user" : "project", cwd);
|
|
123
129
|
if (!target) return { ok: false, message: "No active workspace." };
|
|
124
130
|
try {
|
|
125
131
|
let wrote = 0;
|
|
126
132
|
let skipped = 0;
|
|
127
|
-
const files = target.dirs.length ? target.dirs.map((d) => paths.memoryFileOf(d)) : [target.file];
|
|
133
|
+
const files = target.dirs.length ? target.dirs.map((d) => paths.memoryFileOf(d, cwd)) : [target.file];
|
|
128
134
|
for (const file of files) {
|
|
129
135
|
const md = readMdSync(file);
|
|
130
136
|
if (sectionHasEntry(md, section, entry)) {
|
|
@@ -195,14 +201,14 @@ export function registerHybridTools({ ctx, getConfig, paths, records, state }) {
|
|
|
195
201
|
render: (_args, value) =>
|
|
196
202
|
[{ type: "text", text: value?.actual ? `${value.message}\n\n当前实际内容:\n${value.actual}` : value?.message ?? "Done." }],
|
|
197
203
|
},
|
|
198
|
-
async execute(args) {
|
|
204
|
+
async execute(args, exec) {
|
|
199
205
|
state.recentAgentWrote = true;
|
|
200
206
|
const c = cfg();
|
|
201
207
|
if (!c.enabled) return { ok: false, message: "memory-palace is currently disabled in settings.", actual: "" };
|
|
202
208
|
const section = String(args.section ?? "").trim();
|
|
203
209
|
const oldText = String(args.oldText ?? "");
|
|
204
210
|
if (!section || !oldText.trim()) return { ok: false, message: "section 与 oldText 均不能为空。", actual: "" };
|
|
205
|
-
const target = memFileOf(args.scope === "user" ? "user" : "project");
|
|
211
|
+
const target = memFileOf(args.scope === "user" ? "user" : "project", sessionCwd(exec));
|
|
206
212
|
if (!target) return { ok: false, message: "No active workspace.", actual: "" };
|
|
207
213
|
try {
|
|
208
214
|
const file = target.file;
|
|
@@ -251,13 +257,14 @@ export function registerHybridTools({ ctx, getConfig, paths, records, state }) {
|
|
|
251
257
|
},
|
|
252
258
|
render: (_args, value) => [{ type: "text", text: value?.message ?? "Done." }],
|
|
253
259
|
},
|
|
254
|
-
async execute(args) {
|
|
260
|
+
async execute(args, exec) {
|
|
255
261
|
state.recentAgentWrote = true;
|
|
256
262
|
const c = cfg();
|
|
257
263
|
if (!c.enabled) return { ok: false, message: "memory-palace is currently disabled in settings." };
|
|
258
|
-
const
|
|
264
|
+
const cwd = sessionCwd(exec);
|
|
265
|
+
const dirs = paths.writeDirs(cwd);
|
|
259
266
|
if (!dirs.length) return { ok: false, message: "No active workspace." };
|
|
260
|
-
const file = paths.memoryFileOf(dirs[0]);
|
|
267
|
+
const file = paths.memoryFileOf(dirs[0], cwd);
|
|
261
268
|
// 复核双门禁(pre-execute 已拦一道;这里防绕过)
|
|
262
269
|
const gate = checkReorgGate(file, c);
|
|
263
270
|
if (!gate.ok) return { ok: false, message: `重整被门禁拒绝:${gate.reason}` };
|
|
@@ -308,9 +315,10 @@ export function attachHybridGuards(ctx, getConfig, paths, state) {
|
|
|
308
315
|
}
|
|
309
316
|
if (name !== "memory_reorganize") return next();
|
|
310
317
|
const c = getConfig();
|
|
311
|
-
const
|
|
318
|
+
const cwd = exec?.agent?.session?.header?.cwd;
|
|
319
|
+
const dirs = paths.writeDirs(cwd);
|
|
312
320
|
if (!dirs.length) return { kind: "deny", reason: "无活动工作区,无法重整。" };
|
|
313
|
-
const file = paths.memoryFileOf(dirs[0]);
|
|
321
|
+
const file = paths.memoryFileOf(dirs[0], cwd);
|
|
314
322
|
const gate = checkReorgGate(file, c);
|
|
315
323
|
if (!gate.ok) return { kind: "deny", reason: gate.reason };
|
|
316
324
|
const args = (exec.arguments && typeof exec.arguments === "object") ? exec.arguments : {};
|
package/lib/index.js
CHANGED
|
@@ -40,77 +40,120 @@ export const name = "memory-palace";
|
|
|
40
40
|
// v1.6.0-rc1:settingsNamespace() 已删除,namespace 直接用字符串(宿主侧做类型级校验)。
|
|
41
41
|
const MEMORY_PALACE_SETTINGS_NAMESPACE = "memory-palace";
|
|
42
42
|
|
|
43
|
+
// v1.8.0:适配 dsh 0.1.7-alpha.1 契约破坏(DSH-CONTRACT-TRACKING.md §1.6)。
|
|
44
|
+
// - 契约5:MessageSourceMap 删除通用 'plugin' kind → 投影/子代理消息 source 改用宿主
|
|
45
|
+
// v3→v4 迁移约定 `plugin:dsh-memory-palace`(去重判据向后兼容旧形态,防升级前已 resume 的会话重复注入)。
|
|
46
|
+
// - 契约10:SettingsProvider.installSection 整包删除 → ctx.settings 改挂 SettingsForms
|
|
47
|
+
// (describe/replace,写 profile patch)。配置项全部标 `.volatile()`:0.1.7 中「无 volatile 字段的
|
|
48
|
+
// 条目不进设置表单、replace 直接抛错」。volatile HMR 把新值原地提交进同一 Volatile 引用,故
|
|
49
|
+
// source() 每次解包读 config 即得最新值,不再需要旧版 setSource 覆盖层切换。
|
|
50
|
+
// - settings.yaml 已被宿主移除(导入 profile patch 一次)→ distillLogLevel 说明改口。
|
|
43
51
|
export const Config = Schema.object({
|
|
44
|
-
enabled: Schema.boolean().default(true).description("启用 memory-palace 记忆注入与每日日志写入。"),
|
|
52
|
+
enabled: Schema.boolean().default(true).description("启用 memory-palace 记忆注入与每日日志写入。").volatile(),
|
|
45
53
|
// ---- v1.7.2:预设级静默(官方 minimal「裸测环境」口径)----
|
|
46
54
|
// 宿主把「模式」实现为 agent 平面的 preset:minimal 预设用 persona complete:true 独占 system prompt。
|
|
47
55
|
// 但本插件注册在 host 平面(profile bundle 的 insert),各通道对**每个 agent** 都生效,preset 并不会
|
|
48
56
|
// 把它摘掉——故须自行按会话判据静默,否则记忆正文仍会经 E 投影进历史(实测症状)。
|
|
49
|
-
silentPresets: Schema.array(Schema.string()).default(["minimal"]).description("在这些 agent 预设下本插件整体静默(等价 enabled=false):不注入系统提示词与投影、记忆工具对模型隐身、不写日志、不跑记忆子代理。默认 [\"minimal\"](官方极简模式);置空数组 = 关闭该机制。判据取会话创建头 agentPreset 与 agent-preset/selected 事件(宿主公开事实)。"),
|
|
50
|
-
userMemoryPath: Schema.string().default("~/.deepseek-harness/MEMORY.md").description("用户级记忆文件路径(支持 ~ 展开)。"),
|
|
51
|
-
workspaceMemoryDir: Schema.string().default(".deepseek-harness/memory").description("无 buddy 目录时使用的项目级每日日志目录;项目级 MEMORY.md 位于其同级(.deepseek-harness/MEMORY.md)。"),
|
|
52
|
-
dailyLogRetentionDays: Schema.number().default(30).description("每日日志保留天数,过期日志会被蒸馏进 MEMORY.md。"),
|
|
53
|
-
userBudgetChars: Schema.number().default(4000).description("注入系统提示词的用户级记忆长度上限(字符)。"),
|
|
54
|
-
workspaceBudgetChars: Schema.number().default(3000).description("注入系统提示词的工作区级记忆长度上限(字符)。"),
|
|
57
|
+
silentPresets: Schema.array(Schema.string()).default(["minimal"]).description("在这些 agent 预设下本插件整体静默(等价 enabled=false):不注入系统提示词与投影、记忆工具对模型隐身、不写日志、不跑记忆子代理。默认 [\"minimal\"](官方极简模式);置空数组 = 关闭该机制。判据取会话创建头 agentPreset 与 agent-preset/selected 事件(宿主公开事实)。").volatile(),
|
|
58
|
+
userMemoryPath: Schema.string().default("~/.deepseek-harness/MEMORY.md").description("用户级记忆文件路径(支持 ~ 展开)。").volatile(),
|
|
59
|
+
workspaceMemoryDir: Schema.string().default(".deepseek-harness/memory").description("无 buddy 目录时使用的项目级每日日志目录;项目级 MEMORY.md 位于其同级(.deepseek-harness/MEMORY.md)。").volatile(),
|
|
60
|
+
dailyLogRetentionDays: Schema.number().default(30).description("每日日志保留天数,过期日志会被蒸馏进 MEMORY.md。").volatile(),
|
|
61
|
+
userBudgetChars: Schema.number().default(4000).description("注入系统提示词的用户级记忆长度上限(字符)。").volatile(),
|
|
62
|
+
workspaceBudgetChars: Schema.number().default(3000).description("注入系统提示词的工作区级记忆长度上限(字符)。").volatile(),
|
|
55
63
|
// 桥接 WorkBuddy / CodeBuddy 项目记忆:项目已存在这些目录时直接读写,不再单独建 .deepseek-harness/memory/。
|
|
56
|
-
bridgeBuddyMemory: Schema.boolean().default(true).description("检测并直接读写 WorkBuddy / CodeBuddy 项目记忆目录。"),
|
|
57
|
-
buddyWorkspaceMemoryDirs: Schema.array(Schema.string()).default([".workbuddy/memory", ".codebuddy/memory"]).description("要桥接的 buddy 项目记忆目录列表(按优先级,全部已存在目录会同步写入)。"),
|
|
64
|
+
bridgeBuddyMemory: Schema.boolean().default(true).description("检测并直接读写 WorkBuddy / CodeBuddy 项目记忆目录。").volatile(),
|
|
65
|
+
buddyWorkspaceMemoryDirs: Schema.array(Schema.string()).default([".workbuddy/memory", ".codebuddy/memory"]).description("要桥接的 buddy 项目记忆目录列表(按优先级,全部已存在目录会同步写入)。").volatile(),
|
|
58
66
|
// ---- v0.7.1 调整:废弃插件侧 callLlm,主路径 = 记忆公民 prompt 主动记忆;turn/end 仅做轻量兜底 ----
|
|
59
|
-
summarize: Schema.boolean().default(true).description("它是「agent 主动记忆」主路径失效时的安全网,保证实质工作不丢,代价是只留原始文本、不做总结。"),
|
|
60
|
-
autoCaptureErrors: Schema.boolean().default(true).description("对话出错时(含代码运行报错、工具执行失败)自动把『错误现象』写入对应 MEMORY.md(『根因/方案』由 agent 按记忆公民指令场景①主动记;默认开,关闭无需重启)。"),
|
|
67
|
+
summarize: Schema.boolean().default(true).description("它是「agent 主动记忆」主路径失效时的安全网,保证实质工作不丢,代价是只留原始文本、不做总结。").volatile(),
|
|
68
|
+
autoCaptureErrors: Schema.boolean().default(true).description("对话出错时(含代码运行报错、工具执行失败)自动把『错误现象』写入对应 MEMORY.md(『根因/方案』由 agent 按记忆公民指令场景①主动记;默认开,关闭无需重启)。").volatile(),
|
|
61
69
|
// ---- v1.1.0:记忆模式(plugin=记忆公民指令+轮次轻量+错误捕获;smart=LLM 智能会话摘要)----
|
|
62
70
|
// v1.6.0:+hybrid(子 agent 自动维护日志 + agent 主动维护 MEMORY.md),三档并存,切换需重启 dsh。
|
|
63
|
-
memoryMode: Schema.union(["plugin", "smart", "hybrid"]).default("
|
|
64
|
-
summaryModel: Schema.string().default("").description("记忆插件当前使用的模型(智能模式会话摘要 / 手动蒸馏 / hybrid 记忆子代理共用)。留空=复用当前会话 provider/model;也可填 provider/model(如 deepseek/deepseek-chat)固定廉价模型省 token。"),
|
|
65
|
-
summaryTimeoutMs: Schema.number().default(60000).description("蒸馏 LLM 调用的超时(毫秒),超时视为失败并降级;覆盖智能模式摘要与手动蒸馏两条链路;默认 60000(60s)。"),
|
|
66
|
-
distillDebugLog: Schema.boolean().default(false).description("调试开关:向 dsh 服务端 stderr 输出蒸馏 LLM 调用诊断。distillLogLevel=info 时仅输出元数据(模型解析/请求参数/流进度/错误详情,不含文本);distillLogLevel=debug 会额外打印 LLM 原始响应文本(分隔符包裹),仅限受信本地排障开启。"),
|
|
71
|
+
memoryMode: Schema.union(["plugin", "smart", "hybrid"]).default("hybrid").description("记忆模式(默认 hybrid):plugin=记忆公民指令+轮次轻量+错误捕获;smart=LLM 智能会话摘要(summary→每日日志 + durable→MEMORY.md);hybrid=记忆子代理自动维护今日日志(标删去重)+ agent 主动维护 MEMORY.md(章节化写入/整章节替换/门禁内全量重整)。切换需重启 dsh 生效。").volatile(),
|
|
72
|
+
summaryModel: Schema.string().default("").description("记忆插件当前使用的模型(智能模式会话摘要 / 手动蒸馏 / hybrid 记忆子代理共用)。留空=复用当前会话 provider/model;也可填 provider/model(如 deepseek/deepseek-chat)固定廉价模型省 token。").volatile(),
|
|
73
|
+
summaryTimeoutMs: Schema.number().default(60000).description("蒸馏 LLM 调用的超时(毫秒),超时视为失败并降级;覆盖智能模式摘要与手动蒸馏两条链路;默认 60000(60s)。").volatile(),
|
|
74
|
+
distillDebugLog: Schema.boolean().default(false).description("调试开关:向 dsh 服务端 stderr 输出蒸馏 LLM 调用诊断。distillLogLevel=info 时仅输出元数据(模型解析/请求参数/流进度/错误详情,不含文本);distillLogLevel=debug 会额外打印 LLM 原始响应文本(分隔符包裹),仅限受信本地排障开启。").volatile(),
|
|
67
75
|
// ---- v1.4.1:蒸馏 stderr 日志级别(平铺键,不进 UI;默认 info;distillDebugLog=true 时生效) ----
|
|
68
|
-
distillLogLevel: Schema.string().default("info").description("蒸馏 stderr 日志级别:info=仅元数据诊断(默认,不打印 LLM 原始响应);debug=额外打印 LLM 原始响应文本(分隔符包裹),仅限受信本地排障开启。无需 UI 配置,经
|
|
76
|
+
distillLogLevel: Schema.string().default("info").description("蒸馏 stderr 日志级别:info=仅元数据诊断(默认,不打印 LLM 原始响应);debug=额外打印 LLM 原始响应文本(分隔符包裹),仅限受信本地排障开启。无需 UI 配置,经 profile 的 cordis.patch.yml 在 memory-palace 条目 config 下设置 distillLogLevel 键(dsh 0.1.7 起 settings.yaml 已被宿主移除、导入 profile patch)。").volatile(),
|
|
69
77
|
// ---- v1.4.0:智能模式最终输出软预算(v1.4.1 起语义变更:prompt 软约束,思考不受限;实际硬上限由模型自身 maxTokens 决定) ----
|
|
70
|
-
summaryMaxTokens: Schema.number().default(2000).description("会话摘要 LLM 的最终输出软预算(prompt 约束,思考不受限;实际硬上限由模型自身 maxTokens 决定)。默认 2000;可调大以容纳更多 durable 事实。"),
|
|
71
|
-
projectMaxTokens: Schema.number().default(8000).description("手动「蒸馏项目记忆」LLM 的最终输出软预算(prompt 约束,思考不受限;实际硬上限由模型自身 maxTokens 决定)。默认 8000。"),
|
|
78
|
+
summaryMaxTokens: Schema.number().default(2000).description("会话摘要 LLM 的最终输出软预算(prompt 约束,思考不受限;实际硬上限由模型自身 maxTokens 决定)。默认 2000;可调大以容纳更多 durable 事实。").volatile(),
|
|
79
|
+
projectMaxTokens: Schema.number().default(8000).description("手动「蒸馏项目记忆」LLM 的最终输出软预算(prompt 约束,思考不受限;实际硬上限由模型自身 maxTokens 决定)。默认 8000。").volatile(),
|
|
72
80
|
// ---- v1.4.0:蒸馏时回喂存量记忆(特性3,默认关;开启后自动智能模式 turn/end 与手动蒸馏按钮均回喂,delete 仅手动放开) ----
|
|
73
|
-
feedbackEnabled: Schema.boolean().default(false).description("每次蒸馏时把项目级 + 用户级 MEMORY.md 全文(逐行编号、无截断)回喂给 LLM,使其能基于既有记忆做增量维护。delete 仍仅手动蒸馏按钮开放,自动模式跳过(防误删)。hybrid 模式下仅手动触发蒸馏受影响(自动路径走记忆子代理,自带日志回喂)。默认关。"),
|
|
81
|
+
feedbackEnabled: Schema.boolean().default(false).description("每次蒸馏时把项目级 + 用户级 MEMORY.md 全文(逐行编号、无截断)回喂给 LLM,使其能基于既有记忆做增量维护。delete 仍仅手动蒸馏按钮开放,自动模式跳过(防误删)。hybrid 模式下仅手动触发蒸馏受影响(自动路径走记忆子代理,自带日志回喂)。默认关。").volatile(),
|
|
74
82
|
// ---- v1.6.0:hybrid 模式配置(仅 memoryMode=hybrid 时生效) ----
|
|
75
|
-
reorgCooldownDays: Schema.number().default(7).description("hybrid 模式:项目级 MEMORY.md 全量重整的冷却天数(距上次重整)。与「超出注入预算」双条件同时满足才允许 memory_reorganize;时间戳以 HTML 注释落在 MEMORY.md 文件尾。"),
|
|
76
|
-
subagentLogBudget: Schema.number().default(20000).description("hybrid 模式:记忆子代理回喂今日工作日志的字符上限。超出时仅回喂章节目录,子代理用 log_read_section 按需读取章节。"),
|
|
83
|
+
reorgCooldownDays: Schema.number().default(7).description("hybrid 模式:项目级 MEMORY.md 全量重整的冷却天数(距上次重整)。与「超出注入预算」双条件同时满足才允许 memory_reorganize;时间戳以 HTML 注释落在 MEMORY.md 文件尾。").volatile(),
|
|
84
|
+
subagentLogBudget: Schema.number().default(20000).description("hybrid 模式:记忆子代理回喂今日工作日志的字符上限。超出时仅回喂章节目录,子代理用 log_read_section 按需读取章节。").volatile(),
|
|
77
85
|
// ---- v1.7.1(特性3):自定义指令(用户自写,经 system prompt 注入到「记忆分工 prompt」之后) ----
|
|
78
86
|
// 放 system 而非 E 投影:用户配置一次即恒定 → 零缓存成本(见下方 section 的分流原则)。
|
|
79
|
-
customInstructions: Schema.string().default("").description("自定义指令:非空时追加到系统提示词「记忆分工说明」之后,用于承载不适合写进用户记忆的特殊指令。对全部会话与三种记忆模式生效;留空则不注入。"),
|
|
87
|
+
customInstructions: Schema.string().default("").description("自定义指令:非空时追加到系统提示词「记忆分工说明」之后,用于承载不适合写进用户记忆的特殊指令。对全部会话与三种记忆模式生效;留空则不注入。").volatile(),
|
|
80
88
|
});
|
|
81
89
|
|
|
82
90
|
export const inject = ["systemPrompt", "tools", "llm", "webServer", "webRuntime"];
|
|
83
91
|
|
|
92
|
+
/**
|
|
93
|
+
* v1.8.0(dsh 0.1.7 适配):解包 cordis 解析后的 cosmokit Volatile 引用。
|
|
94
|
+
* 0.1.7 起 Config schema 字段标 `.volatile()`,loader 解析后该字段值不再是原始值,
|
|
95
|
+
* 而是 `{ get(): snapshot, [writeSymbol]: fn }` 引用对象(热改时原地更新、不重载 fiber)。
|
|
96
|
+
* 宿主自己的 consumer 一律 `.get()` 读;本插件全部配置经 source() 消费,故在此统一解包。
|
|
97
|
+
* 判定用鸭子类型(cosmokit 跨包不共享私有 write Symbol,不能用它的 isVolatile 判外部对象;
|
|
98
|
+
* 本项目配置字段里没有任何自带 `.get` 方法的值,误判面为零)。递归处理数组/对象。
|
|
99
|
+
* @param {unknown} value 解析后的配置值
|
|
100
|
+
* @returns {unknown} 纯数据快照(与旧宿主行为一致)
|
|
101
|
+
*/
|
|
102
|
+
function unwrapConfig(value) {
|
|
103
|
+
if (value === null || typeof value !== "object") return value;
|
|
104
|
+
if (typeof value.get === "function" && !Array.isArray(value)) return unwrapConfig(value.get());
|
|
105
|
+
if (Array.isArray(value)) return value.map(unwrapConfig);
|
|
106
|
+
const out = {};
|
|
107
|
+
for (const [k, v] of Object.entries(value)) out[k] = unwrapConfig(v);
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
|
|
84
111
|
export function apply(ctx, config) {
|
|
85
112
|
// 设置集成:在 DSH 设置页暴露 memory-palace 配置面板;运行时优先读用户设置覆盖层,
|
|
86
113
|
// 未挂载 settings 服务时回退到 cordis 组合里的 config。
|
|
87
114
|
// v1.6.0-rc1:注册动作挪进下方 ctx.inject(["settings"]) 回调(installSection 是
|
|
88
115
|
// SettingsProvider 实例方法,须等 settings 服务挂载;其内部同步 setSource,时序与旧版等价)。
|
|
89
|
-
|
|
116
|
+
// v1.8.0:配置真源统一 = cordis 注入的 config(0.1.7 起即 profile 条目 config、热改原地生效)。
|
|
117
|
+
let source = () => unwrapConfig(config);
|
|
90
118
|
const baseEntry = { ...config };
|
|
91
119
|
|
|
92
|
-
// ---- v1.1.3:设置读写 route
|
|
93
|
-
// client fetch 同源 /memory-palace/api/settings.get|update,绕过
|
|
94
|
-
//
|
|
120
|
+
// ---- v1.1.3:设置读写 route(真保存)/ v1.8.0:适配 dsh 0.1.7 SettingsForms ----
|
|
121
|
+
// client fetch 同源 /memory-palace/api/settings.get|update,绕过 apiproxy allowlist;
|
|
122
|
+
// handler 内直接走服务端 settings 面 → profile patch 持久化。
|
|
95
123
|
// 写用 replace 语义(整节替换):表单里留空的字段自动回退 base/schema 默认,与「保存=提交整个表单」一致。
|
|
124
|
+
// v1.8.0 适配(dsh 0.1.7-alpha.1):dsh-settings 整包重写为 SettingsForms——
|
|
125
|
+
// ① 不再有 installSection / setSource / onChange:配置的**唯一真源 = profile 条目 config**,
|
|
126
|
+
// 应用值就是 cordis 注入的 config,且热改经 loader 原地生效(volatile 字段经引用更新、
|
|
127
|
+
// 其余字段整 fiber 重载),故 source 恒为 () => config,无需 setSource 切换。
|
|
128
|
+
// ⚠️ cordis loader 解析后 volatile 字段是 cosmokit 引用对象 `{ get(), [write] }`
|
|
129
|
+
// (非原始值!schemastery resolve 对 meta.volatile 的字段 createVolatile 包装)——
|
|
130
|
+
// 所有配置读取必须经下方 unwrapConfig() 解包,否则 cfg.enabled 拿到对象→真值判断全乱。
|
|
131
|
+
// 鸭子判定 typeof value.get === 'function'(cosmokit 的 write Symbol 跨包不共享、
|
|
132
|
+
// 不能用它的 isVolatile 判外部对象;本项目 config 值里没有自带 get() 的字段,零误判面)。
|
|
133
|
+
// ② describe({redactSecrets:true}) 返回 SettingsDescriptor[](ns/schema/value/revision/user),
|
|
134
|
+
// replace/update/mutate 签名保持 (ns, section, expectedRevision)——本插件整节提交走 replace。
|
|
135
|
+
// ③ 前提:Config schema 须显式 .volatile()(0.1.7 只把 volatile 字段投影进表单;
|
|
136
|
+
// 全无 volatile 的条目 describe 不列出、write 直接抛「no volatile fields」)。
|
|
96
137
|
let settingsFace = null;
|
|
97
138
|
ctx.inject(["settings"], (sctx) => {
|
|
98
139
|
const ns = MEMORY_PALACE_SETTINGS_NAMESPACE;
|
|
99
|
-
// v1.6.0-rc1:设置节注册挪到此处(settings 服务就绪后)。installSection 内部会同步
|
|
100
|
-
// 执行 setSource(() => scope.get()),把 source 从 cordis config 切到用户设置覆盖层;
|
|
101
|
-
// owner=ctx 使插件卸载时宿主自动回退 setSource(() => entry)。与旧版自由函数时序等价。
|
|
102
|
-
sctx.settings.installSection(ctx, ns, Config, baseEntry, {
|
|
103
|
-
setSource: (next) => {
|
|
104
|
-
source = next;
|
|
105
|
-
},
|
|
106
|
-
onChange: () => {},
|
|
107
|
-
});
|
|
108
140
|
const viewOf = () => {
|
|
109
141
|
const descriptor = sctx.settings.describe({ redactSecrets: true }).find((candidate) => candidate.ns === ns);
|
|
110
142
|
return descriptor === undefined
|
|
111
143
|
? { value: undefined, user: undefined, revision: undefined }
|
|
112
144
|
: { value: descriptor.value, user: descriptor.user, revision: descriptor.revision };
|
|
113
145
|
};
|
|
146
|
+
// v1.8.0 双栈:旧宿主(0.1.6 线 SettingsProvider)有 installSection,须注册节并把 setSource
|
|
147
|
+
// 切到解析层;新宿主(0.1.7 SettingsForms)无此方法——配置真源即 profile 条目 config,
|
|
148
|
+
// 热改经 loader 生效,source 保持 () => config 即可。describe/replace 两栈签名兼容。
|
|
149
|
+
if (typeof sctx.settings.installSection === "function") {
|
|
150
|
+
sctx.settings.installSection(ctx, ns, Config, baseEntry, {
|
|
151
|
+
setSource: (next) => {
|
|
152
|
+
source = () => unwrapConfig(next());
|
|
153
|
+
},
|
|
154
|
+
onChange: () => {},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
114
157
|
settingsFace = {
|
|
115
158
|
get: viewOf,
|
|
116
159
|
replace: async (section, expectedRevision) => {
|
|
@@ -192,7 +235,7 @@ export function apply(ctx, config) {
|
|
|
192
235
|
registerApi({ ctx, paths, distill, state, getSettingsFace });
|
|
193
236
|
// v1.6.0:hybrid 模式独立模块。必须【无条件注册】——apply() 同步段执行时 settings 服务
|
|
194
237
|
// 尚未挂载(v1.6.0-rc1 起设置节经 SettingsProvider.installSection 在 ctx.inject(["settings"])
|
|
195
|
-
// 回调里注册并同步 setSource),此刻 source() 读的是 cordis
|
|
238
|
+
// 回调里注册并同步 setSource),此刻 source() 读的是 cordis 组合配置(未显式配置时即 schema 默认),
|
|
196
239
|
// 条件注册会导致设置页切 hybrid 后工具/子代理从未注册(v1.6.0 实装踩坑:settings.yaml
|
|
197
240
|
// memoryMode=hybrid 但 registerHybrid 未执行 → 子代理 TypeError 被吞、日志全空)。
|
|
198
241
|
// 运行时分派靠 _settle / proactive 的热 source() 判断;工具注册无副作用(plugin/smart
|
|
@@ -411,7 +454,8 @@ export function apply(ctx, config) {
|
|
|
411
454
|
// v1.7.2:静默会话(极简等预设)不写任何记忆——错误捕获 / 轻量兜底 / smart 摘要 / hybrid 子代理
|
|
412
455
|
// 全部在此一处提前返回(hybrid 分支在其下方,天然被拦)。
|
|
413
456
|
if (isSessionSilent(state.activeSession)) return;
|
|
414
|
-
const
|
|
457
|
+
const cwd = state.activeSession?.header?.cwd ?? null;
|
|
458
|
+
const dirs = paths.writeDirs(cwd);
|
|
415
459
|
if (!dirs.length) return;
|
|
416
460
|
|
|
417
461
|
// 基础闸门(A+D 合并):结构信号(工具/错误/主动记)或 场景关键词 → 开。
|
|
@@ -469,7 +513,7 @@ export function apply(ctx, config) {
|
|
|
469
513
|
// 插件模式:错误捕获(受 autoCaptureErrors 门控)+ 轻量兜底记录。
|
|
470
514
|
if (cfg.autoCaptureErrors && effectiveIsError) {
|
|
471
515
|
const finalErrMsg = errMsg || toolErr || "(in-session error, no message)";
|
|
472
|
-
void records.captureError(capturedTurn, { kind: "error", message: finalErrMsg }).catch(() => {});
|
|
516
|
+
void records.captureError(capturedTurn, { kind: "error", message: finalErrMsg }, dirs, cwd).catch(() => {});
|
|
473
517
|
}
|
|
474
518
|
void records.writeLightEntry(dirs, capturedTurn, effectiveIsError).catch(() => {});
|
|
475
519
|
}
|
package/lib/projection.mjs
CHANGED
|
@@ -1,173 +1,185 @@
|
|
|
1
|
-
// memory-palace E 投影 —— 记忆正文与今日日志的独立注入通道。
|
|
2
|
-
//
|
|
3
|
-
// 背景(v1.6.3 及以前):用户级 + 项目级 MEMORY.md 走 systemPrompt.section,受
|
|
4
|
-
// 「仅首次注入」限制(`alreadyInjected` 打了标记就不撕,只有 compaction 清),
|
|
5
|
-
// 导致记忆正文在 turn 1 的 step 2 之后就消失、全程不再进场。
|
|
6
|
-
//
|
|
7
|
-
// 现在:记忆正文改经 `agent/pre-step` 投影为**独立 user 消息**,常驻对话历史:
|
|
8
|
-
// - 每个 step 都可见(消息在历史里,不依赖注入时机);
|
|
9
|
-
// - v1.7.1 起去重判据由「全文深比较」改为「**文件身份**」(首行 heading,含路径 + 日期):
|
|
10
|
-
// 同一文件只注一次,内容再变也不重注 —— 记忆正文由 agent 自己写入,其内容本就在
|
|
11
|
-
// 上下文(tool/call 参数)里,重注纯属冗余;换文件(跨日期日志)才视为新身份注入;
|
|
12
|
-
// - v1.7.1 起**今日工作日志由 systemPrompt.section 迁入本通道**。日志是 agent 唯一
|
|
13
|
-
// 「不知道自己缺」的内容(hybrid 子代理以独立 LLM stream 写入、不回注主对话),
|
|
14
|
-
// 必须注入;迁出后 section 只剩恒定指令 → system prompt 恒定 → 前缀缓存永久有效
|
|
15
|
-
// (此前日志在 section 里每步求值,一变就让序列最前的整段前缀作废)。
|
|
16
|
-
// - compaction 把消息移出 surface 后,身份判据即判为缺失 → 自动重注**磁盘最新版**,
|
|
17
|
-
// 顺带完成一次「刷新到最新」。
|
|
18
|
-
// - v1.7.2 起**静默会话整体不投影**(isSilent(session)):宿主「模式」是 agent 平面预设,
|
|
19
|
-
// 本插件却在 host 平面,投影对每个 agent 都生效——官方 minimal 预设(persona complete:true
|
|
20
|
-
// 独占 system prompt 的"裸测环境")下必须自行按会话静默,否则记忆正文仍会常驻历史吃 token。
|
|
21
|
-
//
|
|
22
|
-
// 实现照抄宿主 `packages/context/agent-instructions/src/index.ts` 三件套(API 全公开、无第一方特权):
|
|
23
|
-
// 1. `inject: ['sessionProjections']`(服务随 dsh-base 默认装载)
|
|
24
|
-
// 2. `ctx.on('agent/pre-step', async ({agent, messages, step, signal}, next) => {...})`
|
|
25
|
-
// 3. `createUserMessage()`(来自 @deepseek-ai/dsh-llm)
|
|
26
|
-
// 投递方式:同步版——折叠进 `decision.messages`(`toSpliced(lastClaimedIndex + 1, 0, ...pending)`),
|
|
27
|
-
// 不做异步 inbox 刷新(agent-instructions 注释明写异步投影有两个 commit 边界的时序约束,
|
|
28
|
-
// 本版刻意回避;记忆更新后下一步自然带上新内容)。
|
|
29
|
-
//
|
|
30
|
-
// 🔴 契约风险:pre-step 监听器抛错会让**整个 turn 失败**(宿主测试
|
|
31
|
-
// packages/acp/acp/tests/turns.spec.ts:150-156 锚定),故钩子内整体 try-catch,
|
|
32
|
-
// 任何异常降级为「本步不投影」并留一行 stderr 线索,绝不冒泡。详见 DEVELOPMENT.md §7.4。
|
|
33
|
-
import { join } from "node:path";
|
|
34
|
-
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
35
|
-
import { expandHome, toHomeShort, todayISO, readMdSync, budgetClip, stripSmartTag, stripDeletedLines } from "./common/text.mjs";
|
|
36
|
-
|
|
37
|
-
// 投影消息的 source 标注。
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (!
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return decision;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
1
|
+
// memory-palace E 投影 —— 记忆正文与今日日志的独立注入通道。
|
|
2
|
+
//
|
|
3
|
+
// 背景(v1.6.3 及以前):用户级 + 项目级 MEMORY.md 走 systemPrompt.section,受
|
|
4
|
+
// 「仅首次注入」限制(`alreadyInjected` 打了标记就不撕,只有 compaction 清),
|
|
5
|
+
// 导致记忆正文在 turn 1 的 step 2 之后就消失、全程不再进场。
|
|
6
|
+
//
|
|
7
|
+
// 现在:记忆正文改经 `agent/pre-step` 投影为**独立 user 消息**,常驻对话历史:
|
|
8
|
+
// - 每个 step 都可见(消息在历史里,不依赖注入时机);
|
|
9
|
+
// - v1.7.1 起去重判据由「全文深比较」改为「**文件身份**」(首行 heading,含路径 + 日期):
|
|
10
|
+
// 同一文件只注一次,内容再变也不重注 —— 记忆正文由 agent 自己写入,其内容本就在
|
|
11
|
+
// 上下文(tool/call 参数)里,重注纯属冗余;换文件(跨日期日志)才视为新身份注入;
|
|
12
|
+
// - v1.7.1 起**今日工作日志由 systemPrompt.section 迁入本通道**。日志是 agent 唯一
|
|
13
|
+
// 「不知道自己缺」的内容(hybrid 子代理以独立 LLM stream 写入、不回注主对话),
|
|
14
|
+
// 必须注入;迁出后 section 只剩恒定指令 → system prompt 恒定 → 前缀缓存永久有效
|
|
15
|
+
// (此前日志在 section 里每步求值,一变就让序列最前的整段前缀作废)。
|
|
16
|
+
// - compaction 把消息移出 surface 后,身份判据即判为缺失 → 自动重注**磁盘最新版**,
|
|
17
|
+
// 顺带完成一次「刷新到最新」。
|
|
18
|
+
// - v1.7.2 起**静默会话整体不投影**(isSilent(session)):宿主「模式」是 agent 平面预设,
|
|
19
|
+
// 本插件却在 host 平面,投影对每个 agent 都生效——官方 minimal 预设(persona complete:true
|
|
20
|
+
// 独占 system prompt 的"裸测环境")下必须自行按会话静默,否则记忆正文仍会常驻历史吃 token。
|
|
21
|
+
//
|
|
22
|
+
// 实现照抄宿主 `packages/context/agent-instructions/src/index.ts` 三件套(API 全公开、无第一方特权):
|
|
23
|
+
// 1. `inject: ['sessionProjections']`(服务随 dsh-base 默认装载)
|
|
24
|
+
// 2. `ctx.on('agent/pre-step', async ({agent, messages, step, signal}, next) => {...})`
|
|
25
|
+
// 3. `createUserMessage()`(来自 @deepseek-ai/dsh-llm)
|
|
26
|
+
// 投递方式:同步版——折叠进 `decision.messages`(`toSpliced(lastClaimedIndex + 1, 0, ...pending)`),
|
|
27
|
+
// 不做异步 inbox 刷新(agent-instructions 注释明写异步投影有两个 commit 边界的时序约束,
|
|
28
|
+
// 本版刻意回避;记忆更新后下一步自然带上新内容)。
|
|
29
|
+
//
|
|
30
|
+
// 🔴 契约风险:pre-step 监听器抛错会让**整个 turn 失败**(宿主测试
|
|
31
|
+
// packages/acp/acp/tests/turns.spec.ts:150-156 锚定),故钩子内整体 try-catch,
|
|
32
|
+
// 任何异常降级为「本步不投影」并留一行 stderr 线索,绝不冒泡。详见 DEVELOPMENT.md §7.4。
|
|
33
|
+
import { join } from "node:path";
|
|
34
|
+
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
35
|
+
import { expandHome, toHomeShort, todayISO, readMdSync, budgetClip, stripSmartTag, stripDeletedLines } from "./common/text.mjs";
|
|
36
|
+
|
|
37
|
+
// 投影消息的 source 标注。
|
|
38
|
+
// v1.8.0(dsh 0.1.7 适配):宿主删除了通用 catch-all `plugin` kind(MessageSourceMap 不再含
|
|
39
|
+
// 'plugin' 键),改为「每个生产者在自己的模块里声明自有 kind」;v3→v4 会话持久化迁移把第三方
|
|
40
|
+
// 旧源 `{kind:'plugin',plugin:X}` 规范化为 `plugin:X`(见 session-format-v3-to-v4/sources.ts
|
|
41
|
+
// producerKind())。故本插件合规 kind = 'plugin:dsh-memory-palace'。运行时不校验 kind 白名单
|
|
42
|
+
// (createMessage 仅 deepFreeze(structuredClone)),但未知 kind 在 UI 走兜底呈现、且未来宿主
|
|
43
|
+
// 若加校验即炸——按迁移约定写才安全。
|
|
44
|
+
// - form: 'instructions' → 宿主 ContextForm 联合内的合法值(曾拟用 'memory',但其不在该联合内,
|
|
45
|
+
// 属类型不合法;'instructions' 语义接近"指令性内容"且类型安全,UI 按指令样式呈现)。
|
|
46
|
+
const PROJECTION_SOURCE = Object.freeze({
|
|
47
|
+
kind: "plugin:dsh-memory-palace",
|
|
48
|
+
form: "instructions",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// v1.7.1 判据:投影身份 = 消息首行 heading(已编码目标文件路径 + 日期)。
|
|
52
|
+
// 只比身份、不比正文 —— 同一文件投影过一次后,内容再变也不重注。
|
|
53
|
+
function projectionIdentity(msg) {
|
|
54
|
+
const text = msg?.content?.[0]?.text ?? "";
|
|
55
|
+
const i = text.indexOf("\n");
|
|
56
|
+
return i === -1 ? text : text.slice(0, i);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 只在双方都是本插件投影时才比身份 —— 规避真实用户消息与宿主注入消息
|
|
60
|
+
// (runtime context / skill 目录等)因首行文本巧合而误撞。
|
|
61
|
+
// v1.8.0:新消息 kind='plugin:dsh-memory-palace';同时**保留旧 kind='plugin'+plugin 字段的识别**
|
|
62
|
+
// —— 历史会话里已持久化的旧投影消息经宿主 v3→v4 迁移会变成 plugin:dsh-memory-palace,但旧宿主
|
|
63
|
+
// (<0.1.7)与未迁移数据仍是旧形状,双认可防「换 kind 当天在老会话里重复注入」。
|
|
64
|
+
function isOwnProjection(msg) {
|
|
65
|
+
const s = msg?.source;
|
|
66
|
+
if (!s || s.form !== "instructions") return false;
|
|
67
|
+
if (s.kind === "plugin:dsh-memory-palace") return true;
|
|
68
|
+
return s.kind === "plugin" && s.plugin === "dsh-memory-palace";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function sameProjectionIdentity(left, right) {
|
|
72
|
+
return isOwnProjection(left) && isOwnProjection(right)
|
|
73
|
+
&& projectionIdentity(left) === projectionIdentity(right);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 扫描当前 surface,判断该身份是否已经作为本插件消息存在于会话历史中。
|
|
77
|
+
// 存在 → 不重复注入("同一文件只注一次"靠它实现)。
|
|
78
|
+
// compaction 把消息移出 surface 后即判为缺失 → 自动重注磁盘最新版
|
|
79
|
+
// (身份不变、正文刷新,故无需 injectedSessionIds 之类的标记机制)。
|
|
80
|
+
function alreadyOnSurface(session, desired) {
|
|
81
|
+
const nodes = session?.surface?.nodes;
|
|
82
|
+
if (!Array.isArray(nodes)) return false;
|
|
83
|
+
for (const seq of nodes) {
|
|
84
|
+
const event = typeof session.eventAt === "function" ? session.eventAt(seq) : undefined;
|
|
85
|
+
if (event?.type === "user/message" && sameProjectionIdentity(event.data, desired)) return true;
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 构造一条投影消息;内容为空(文件不存在 / 无记忆)时返回 null。
|
|
91
|
+
function buildProjection(text, heading) {
|
|
92
|
+
if (!text) return null;
|
|
93
|
+
return createUserMessage({
|
|
94
|
+
content: [{ type: "text", text: `${heading}\n${text}` }],
|
|
95
|
+
source: PROJECTION_SOURCE,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 收集本步要投影的消息(用户级 / 项目级 MEMORY.md / 今日工作日志,各自独立一条)。
|
|
101
|
+
* 拆成多条独立消息(v1.7.0 定案,v1.7.1 扩展):否则"某一条内容一改,其余连带重发"。
|
|
102
|
+
* v1.7.2:新增 `session` + `isSilent` —— 静默会话(官方 minimal 等预设)整体不投影。
|
|
103
|
+
* @param {{ getConfig: () => object, paths: object, session?: object, isSilent?: (session?: object) => boolean }} deps
|
|
104
|
+
* @returns {object[]} 按 [用户级, 项目级…, 今日日志…] 顺序的消息数组(可能为空)
|
|
105
|
+
*/
|
|
106
|
+
export function buildProjections({ getConfig, paths, session, isSilent }) {
|
|
107
|
+
const cfg = getConfig();
|
|
108
|
+
if (!cfg.enabled) return [];
|
|
109
|
+
// v1.7.2:静默会话(极简模式等)不投影——投影消息是**常驻历史**,比 section 更"重":
|
|
110
|
+
// 它要一路吃 token 直到 compaction,正是"裸测环境"最不该有的东西。
|
|
111
|
+
if (isSilent?.(session)) return [];
|
|
112
|
+
const out = [];
|
|
113
|
+
// 用户级 MEMORY.md
|
|
114
|
+
const userFile = expandHome(cfg.userMemoryPath);
|
|
115
|
+
const userText = readMdSync(userFile);
|
|
116
|
+
if (userText) {
|
|
117
|
+
const clipped = stripSmartTag(stripDeletedLines(budgetClip(userText, cfg.userBudgetChars)));
|
|
118
|
+
const msg = buildProjection(clipped, `# 用户级记忆 (${toHomeShort(userFile)})`);
|
|
119
|
+
if (msg) out.push(msg);
|
|
120
|
+
}
|
|
121
|
+
// 项目级 MEMORY.md:按 readDirs 顺序(dsh 优先叠加 buddy),同一文件路径只取一次。
|
|
122
|
+
// 显式透传当前会话 cwd,避免依赖闭包 activeCwd 造成多会话并发/切换工作区时记忆串台
|
|
123
|
+
const seen = new Set();
|
|
124
|
+
const cwd = session?.header?.cwd;
|
|
125
|
+
for (const dir of paths.readDirs(cwd)) {
|
|
126
|
+
for (const file of paths.memoryReadCandidates(dir, cwd)) {
|
|
127
|
+
if (seen.has(file)) continue;
|
|
128
|
+
seen.add(file);
|
|
129
|
+
const text = readMdSync(file);
|
|
130
|
+
if (!text) continue;
|
|
131
|
+
const clipped = stripSmartTag(stripDeletedLines(budgetClip(text, cfg.workspaceBudgetChars)));
|
|
132
|
+
const msg = buildProjection(clipped, `# 项目级记忆 (${toHomeShort(file)})`);
|
|
133
|
+
if (msg) out.push(msg);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// 今日工作日志(v1.7.1 由 systemPrompt.section 迁入)。heading 沿用原 section 格式,
|
|
137
|
+
// 天然含日期与目录 → 跨天 / 换目录自动成为新身份。预算沿用 workspaceBudgetChars,
|
|
138
|
+
// 与项目级 MEMORY.md 各自独立 budgetClip,互不挤占。
|
|
139
|
+
for (const dir of paths.readDirs(cwd)) {
|
|
140
|
+
const text = readMdSync(join(dir, `${todayISO()}.md`));
|
|
141
|
+
if (!text) continue;
|
|
142
|
+
const clipped = stripSmartTag(stripDeletedLines(budgetClip(text, cfg.workspaceBudgetChars)));
|
|
143
|
+
const msg = buildProjection(clipped, `# 今日工作日志 (${todayISO()} @ ${toHomeShort(dir)})`);
|
|
144
|
+
if (msg) out.push(msg);
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 注册 E 投影钩子。
|
|
151
|
+
* @param {{ ctx: object, getConfig: () => object, paths: object, isSilent?: (session?: object) => boolean }} deps
|
|
152
|
+
*/
|
|
153
|
+
export function registerProjection({ ctx, getConfig, paths, isSilent }) {
|
|
154
|
+
ctx.on("agent/pre-step", async ({ agent, messages, step, signal }, next) => {
|
|
155
|
+
// 先让瀑布流继续,拿到宿主与其他插件合成后的决策——投影要折叠进它的 messages。
|
|
156
|
+
const decision = await next();
|
|
157
|
+
try {
|
|
158
|
+
signal?.throwIfAborted?.();
|
|
159
|
+
if (decision.kind !== "enter") return decision;
|
|
160
|
+
const session = agent?.session;
|
|
161
|
+
// v1.7.2:静默会话(极简模式等)不投影。放在 next() 之后、构造之前,成本最低。
|
|
162
|
+
if (isSilent?.(session)) return decision;
|
|
163
|
+
const desired = buildProjections({ getConfig, paths, session, isSilent });
|
|
164
|
+
if (!desired.length) return decision;
|
|
165
|
+
// 逐条去重(按**文件身份**、非内容):已被本步 messages 覆盖、或已在 surface 上的,跳过。
|
|
166
|
+
const pending = desired.filter((msg) =>
|
|
167
|
+
!messages.some((m) => sameProjectionIdentity(m, msg))
|
|
168
|
+
&& !decision.messages.some((m) => sameProjectionIdentity(m, msg))
|
|
169
|
+
&& !alreadyOnSurface(session, msg));
|
|
170
|
+
if (!pending.length) return decision;
|
|
171
|
+
// 折叠位置:紧跟在"本步被认领的消息"之后,使直接提示在前、驱动追加的 runtime context 在后。
|
|
172
|
+
// 照抄 agent-instructions:lastClaimedIndex 可能为 -1(本步无认领消息),此时插到最前。
|
|
173
|
+
const lastClaimedIndex = decision.messages.findLastIndex((m) => messages.includes(m));
|
|
174
|
+
const entered = decision.messages.toSpliced(lastClaimedIndex + 1, 0, ...pending);
|
|
175
|
+
return { ...decision, messages: entered };
|
|
176
|
+
} catch (error) {
|
|
177
|
+
// 🔴 绝不冒泡:pre-step 抛错会让整个 turn 失败(宿主契约,见文件头)。
|
|
178
|
+
// 契约失效时表现为"记忆正文不注入",这行 warn 是唯一排查线索。
|
|
179
|
+
console.error(
|
|
180
|
+
`[memory-palace] projection skipped (step ${step}): ${error?.message || String(error)}`,
|
|
181
|
+
);
|
|
182
|
+
return decision;
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
package/lib/tools.mjs
CHANGED
|
@@ -11,6 +11,10 @@ import { appendLineDedup, findMatches, removeLineByMatch, recentLogDates } from
|
|
|
11
11
|
*/
|
|
12
12
|
export function registerTools({ ctx, getConfig, paths, state }) {
|
|
13
13
|
const cfg = () => getConfig();
|
|
14
|
+
// 从宿主 execute 第二参(ToolRunContext,defineTool 原样转发)/ pre-execute 事件载荷取
|
|
15
|
+
// 发起会话 cwd——多会话并发时 activeCwd 可能是别的工作区,串台会读写错项目。
|
|
16
|
+
// 缺省(undefined)回落 paths 工厂默认 = activeCwd(勿 `?? null`,null 会绕过默认参数)。
|
|
17
|
+
const sessionCwd = (exec) => exec?.agent?.session?.header?.cwd;
|
|
14
18
|
|
|
15
19
|
// 删除作用域的中文标签。
|
|
16
20
|
function levelLabel(level) {
|
|
@@ -24,7 +28,7 @@ export function registerTools({ ctx, getConfig, paths, state }) {
|
|
|
24
28
|
// - user:用户级 MEMORY.md(~/.deepseek-harness/MEMORY.md)
|
|
25
29
|
// - project:项目级 MEMORY.md(与 memory/ 同级)+ 今日每日日志
|
|
26
30
|
// - daily:今日每日日志 + 最近 3 份历史每日日志(同工作区)
|
|
27
|
-
async function deleteCandidates(level) {
|
|
31
|
+
async function deleteCandidates(level, cwd) {
|
|
28
32
|
const c = cfg();
|
|
29
33
|
const files = [];
|
|
30
34
|
if (level === "user") {
|
|
@@ -32,11 +36,11 @@ export function registerTools({ ctx, getConfig, paths, state }) {
|
|
|
32
36
|
files.push({ file, tier: "用户级", path: toHomeShort(file) });
|
|
33
37
|
return files;
|
|
34
38
|
}
|
|
35
|
-
const dirs = paths.writeDirs();
|
|
39
|
+
const dirs = paths.writeDirs(cwd);
|
|
36
40
|
for (const dir of dirs) {
|
|
37
41
|
const dirShort = toHomeShort(dir);
|
|
38
42
|
if (level === "project") {
|
|
39
|
-
const mem = paths.memoryFileOf(dir);
|
|
43
|
+
const mem = paths.memoryFileOf(dir, cwd);
|
|
40
44
|
files.push({ file: mem, tier: "项目级", path: toHomeShort(mem) });
|
|
41
45
|
}
|
|
42
46
|
// 每日级:今日日志必查;daily 作用域额外纳入最近 3 份历史日志
|
|
@@ -89,17 +93,18 @@ export function registerTools({ ctx, getConfig, paths, state }) {
|
|
|
89
93
|
},
|
|
90
94
|
render: (_args, value) => [{ type: "text", text: value?.message ?? "Saved." }],
|
|
91
95
|
},
|
|
92
|
-
async execute(args) {
|
|
96
|
+
async execute(args, exec) {
|
|
93
97
|
state.recentAgentWrote = true;
|
|
94
98
|
if (!cfg().enabled) return { ok: false, message: "memory-palace is currently disabled in settings." };
|
|
95
|
-
const
|
|
99
|
+
const cwd = sessionCwd(exec);
|
|
100
|
+
const dirs = paths.writeDirs(cwd);
|
|
96
101
|
if (!dirs.length) return { ok: false, message: "No active workspace." };
|
|
97
102
|
try {
|
|
98
103
|
const line = `- ${args.content}`;
|
|
99
104
|
let wrote = 0;
|
|
100
105
|
let skipped = 0;
|
|
101
106
|
for (const dir of dirs) {
|
|
102
|
-
const mem = paths.memoryFileOf(dir);
|
|
107
|
+
const mem = paths.memoryFileOf(dir, cwd);
|
|
103
108
|
if (await appendLineDedup(mem, line)) wrote++;
|
|
104
109
|
else skipped++;
|
|
105
110
|
}
|
|
@@ -202,9 +207,10 @@ export function registerTools({ ctx, getConfig, paths, state }) {
|
|
|
202
207
|
},
|
|
203
208
|
render: (_args, value) => [{ type: "text", text: value?.memory ?? value?.message ?? "No memory." }],
|
|
204
209
|
},
|
|
205
|
-
async execute(args = {}) {
|
|
210
|
+
async execute(args = {}, exec) {
|
|
206
211
|
const c = cfg();
|
|
207
212
|
if (!c.enabled) return { ok: false, message: "memory-palace is currently disabled in settings.", memory: "" };
|
|
213
|
+
const cwd = sessionCwd(exec);
|
|
208
214
|
// v1.7.1:默认 scope='memory'(长期记忆)。刻意不设 'all' —— 默认全量会让 agent
|
|
209
215
|
// 不加思考地把日志(实测单日可达 1.7 万字符)一起吞下,白白吃掉上下文。
|
|
210
216
|
const scope = args?.scope ?? "memory";
|
|
@@ -223,13 +229,13 @@ export function registerTools({ ctx, getConfig, paths, state }) {
|
|
|
223
229
|
const u = readMdSync(userFile);
|
|
224
230
|
if (u) blocks.push(`# 用户级记忆 (${toHomeShort(userFile)})\n${u}`);
|
|
225
231
|
}
|
|
226
|
-
for (const dir of paths.readDirs()) {
|
|
232
|
+
for (const dir of paths.readDirs(cwd)) {
|
|
227
233
|
const dirShort = toHomeShort(dir);
|
|
228
234
|
if (wantProject) {
|
|
229
235
|
// 标题必须用实际读到的 MEMORY.md 路径,而非日志目录 —— dsh 布局下 MEMORY.md 位于
|
|
230
236
|
// 日志目录的上一级,旧实现拿 dir(.../memory)当标题会误导真实路径(buddy 布局
|
|
231
237
|
// MEMORY.md 恰在 memory/ 内所以未暴露)。与 E 投影的 per-file 粒度对齐。
|
|
232
|
-
for (const f of paths.memoryReadCandidates(dir)) {
|
|
238
|
+
for (const f of paths.memoryReadCandidates(dir, cwd)) {
|
|
233
239
|
const t = readMdSync(f);
|
|
234
240
|
if (t) blocks.push(`# 工作区记忆 (${toHomeShort(f)})\n${t}`);
|
|
235
241
|
}
|
|
@@ -328,7 +334,7 @@ export function registerTools({ ctx, getConfig, paths, state }) {
|
|
|
328
334
|
render: (_args, value) =>
|
|
329
335
|
[{ type: "text", text: value?.confirmPrompt || value?.message || (value?.preview ? "Preview only — no deletion performed." : "Deleted.") }],
|
|
330
336
|
},
|
|
331
|
-
async execute(args) {
|
|
337
|
+
async execute(args, exec) {
|
|
332
338
|
state.recentAgentWrote = true;
|
|
333
339
|
const c = cfg();
|
|
334
340
|
if (!c.enabled) {
|
|
@@ -343,7 +349,7 @@ export function registerTools({ ctx, getConfig, paths, state }) {
|
|
|
343
349
|
return { ok: false, preview: false, level, removed: 0, matches: [], details: [], message: "match 不能为空:请提供要删除的记忆文本(子串)。", confirmPrompt: "" };
|
|
344
350
|
}
|
|
345
351
|
|
|
346
|
-
const candidates = await deleteCandidates(level);
|
|
352
|
+
const candidates = await deleteCandidates(level, sessionCwd(exec));
|
|
347
353
|
if (!candidates.length && level !== "user") {
|
|
348
354
|
return { ok: false, preview: false, level, removed: 0, matches: [], details: [], message: "当前没有活动工作区,无法定位项目/每日记忆文件。", confirmPrompt: "" };
|
|
349
355
|
}
|
|
@@ -415,7 +421,7 @@ export function registerTools({ ctx, getConfig, paths, state }) {
|
|
|
415
421
|
const match = (a.match || "").trim();
|
|
416
422
|
if (!match) return next();
|
|
417
423
|
try {
|
|
418
|
-
const candidates = await deleteCandidates(level);
|
|
424
|
+
const candidates = await deleteCandidates(level, sessionCwd(exec));
|
|
419
425
|
const matches = [];
|
|
420
426
|
for (const cand of candidates) {
|
|
421
427
|
const found = findMatches(cand.file, match, cand.tier, cand.path);
|
package/package.json
CHANGED
|
@@ -1,56 +1,58 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dsh-memory-palace",
|
|
3
|
-
"version": "1.7.2-alpha.
|
|
4
|
-
"description": "把 WorkBuddy 的文件式记忆系统移植进 DeepSeek Harness:用户级 + 工作区的人类可读 Markdown 记忆(~/.deepseek-harness/MEMORY.md 与 <cwd>/.deepseek-harness/memory/)。",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "lib/index.js",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": "./lib/index.js",
|
|
9
|
-
"./client": "./lib/client.js",
|
|
10
|
-
"./package.json": "./package.json"
|
|
11
|
-
},
|
|
12
|
-
"files": [
|
|
13
|
-
"lib",
|
|
14
|
-
"cordis.patch.yml",
|
|
15
|
-
"README.md",
|
|
16
|
-
"LICENSE"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
"@deepseek-ai/dsh-client-ui-
|
|
35
|
-
"@deepseek-ai/dsh-client-
|
|
36
|
-
"@deepseek-ai/dsh-client-
|
|
37
|
-
"@deepseek-ai/dsh-
|
|
38
|
-
"@deepseek-ai/dsh-api-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"@deepseek-ai/
|
|
45
|
-
"@deepseek-ai/dsh-
|
|
46
|
-
"@deepseek-ai/dsh-
|
|
47
|
-
"@deepseek-ai/
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-memory-palace",
|
|
3
|
+
"version": "1.7.2-alpha.3",
|
|
4
|
+
"description": "把 WorkBuddy 的文件式记忆系统移植进 DeepSeek Harness:用户级 + 工作区的人类可读 Markdown 记忆(~/.deepseek-harness/MEMORY.md 与 <cwd>/.deepseek-harness/memory/)。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./client": "./lib/client.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"lib",
|
|
14
|
+
"cordis.patch.yml",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"assets/memory-icon.svg"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"registry": "https://registry.npmjs.org/"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/lovezi0/dsh-memory-palace.git"
|
|
25
|
+
},
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"dsh": {
|
|
28
|
+
"bundle": {
|
|
29
|
+
"patch": "./cordis.patch.yml"
|
|
30
|
+
},
|
|
31
|
+
"client": {
|
|
32
|
+
"platform": "web",
|
|
33
|
+
"inject": [
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
35
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
36
|
+
"@deepseek-ai/dsh-client-locale",
|
|
37
|
+
"@deepseek-ai/dsh-client-connection",
|
|
38
|
+
"@deepseek-ai/dsh-api-remotes",
|
|
39
|
+
"@deepseek-ai/dsh-api-session-controller"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@deepseek-ai/cordis": "*",
|
|
45
|
+
"@deepseek-ai/dsh-llm": "^0.1.7-alpha.1",
|
|
46
|
+
"@deepseek-ai/dsh-settings": "^0.1.7-alpha.1",
|
|
47
|
+
"@deepseek-ai/dsh-tools": "^0.1.7-alpha.1",
|
|
48
|
+
"@deepseek-ai/schemastery": "^3.18.3"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@deepseek-ai/dsh-settings": "^0.1.7-alpha.1"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "node scripts/build.mjs",
|
|
55
|
+
"prepublishOnly": "npm run build"
|
|
56
|
+
},
|
|
57
|
+
"icon": "./assets/memory-icon.svg"
|
|
58
|
+
}
|