dsh-tdai-memory 0.2.7 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -79,6 +79,35 @@ tdai-memory:
79
79
  sendDimensions: false
80
80
  ```
81
81
 
82
+ ## Install
83
+
84
+ ```bash
85
+ dsh plugin --profile web add dsh-tdai-memory
86
+ ```
87
+
88
+ then mount it in `$DSH_HOME/profiles/web/cordis.patch.yml`:
89
+
90
+ ```yaml
91
+ - insert:
92
+ - id: tdai-memory
93
+ name: 'dsh-tdai-memory'
94
+ config: {} # keys can live in settings.yaml instead
95
+ ```
96
+
97
+ and restart `dsh web`. LLM/embedding API keys can be set in the Web UI
98
+ settings page (记忆 / Memory) or directly in `settings.yaml` under
99
+ `tdai-memory:`.
100
+
101
+ > **Note for users**
102
+ > - `dsh plugin` prints "declares no dsh.bundle — installed as a plain
103
+ > dependency" — **expected**: this plugin mounts via `cordis.patch.yml`.
104
+ > - The settings section needs the `dsh-host-apiproxy` namespace allowlist;
105
+ > the plugin patches it automatically on first start — **restart `dsh web`
106
+ > once more** and the section appears. A dsh update overwrites the patch;
107
+ > the next plugin start re-applies it.
108
+ > - Settings changes apply **after a restart** (TdaiCore is built at startup).
109
+ > - Tested against DSH `0.1.0-rc.6`.
110
+
82
111
  ## Known trade-offs
83
112
 
84
113
  - **Extraction model**: `mimo-v2.5` extracts correctly but takes 20-30s per
package/client.js CHANGED
@@ -32,7 +32,7 @@ window.__ModuleLoader__.load({
32
32
  ".__tm_btn{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:8px;padding:6px 14px;font:inherit;font-size:13px;cursor:pointer}" +
33
33
  ".__tm_btn:hover:not(:disabled){border-color:var(--dsw-alias-state-business-primary)}" +
34
34
  ".__tm_btn:disabled{opacity:.5;cursor:default}" +
35
- ".__tm_btnPrimary{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-on-accent)}" +
35
+ ".__tm_btnPrimary{border-color:var(--dsw-alias-state-business-primary, #3964fe);background:var(--dsw-alias-state-business-primary, #3964fe);color:#fff}" +
36
36
  ".__tm_status{font-size:12px;color:var(--dsw-alias-label-tertiary)}" +
37
37
  ".__tm_error{font-size:12px;color:var(--dsw-alias-state-error-primary)}" +
38
38
  ".__tm_unavailable{font-size:13px;color:var(--dsw-alias-label-tertiary)}";
package/index.js CHANGED
@@ -74,6 +74,20 @@ const Config = z.object({
74
74
  toolsEnabled: z.boolean().default(true),
75
75
  });
76
76
 
77
+ /**
78
+ * Whether a derived message is real human input.
79
+ *
80
+ * A `"user"` role alone is not enough: DSH injects synthetic user-role
81
+ * context (system reminders, runtime context, skill catalogs, goal rounds,
82
+ * tool results, …) that must never be treated as user input. Real input is
83
+ * distinguished by `message.source.kind === "user"` (see dsh-llm Message
84
+ * types). Messages without a `source` (older harnesses) are treated as real
85
+ * input to stay compatible.
86
+ */
87
+ function isRealUserMessage(m) {
88
+ return m?.role === "user" && (m.source == null || m.source.kind === "user");
89
+ }
90
+
77
91
  /** Extract plain text from a dsh message content (block array or string). */
78
92
  function extractText(content) {
79
93
  if (typeof content === "string") return content.trim();
@@ -227,11 +241,19 @@ function apply(ctx, config) {
227
241
  async function captureTurn(session, startedAt) {
228
242
  if (!tdaiConfig?.capture.enabled) return;
229
243
  const derived = session.deriveMessages();
230
- const lastUserIdx = derived.map((m) => m.role).lastIndexOf("user");
244
+ // Start from the last REAL user message. DSH injects synthetic user-role
245
+ // context (system reminders, runtime context, skill catalogs, goal
246
+ // rounds, …) whose `source.kind` is not "user"; those must not pollute
247
+ // L0 memory (issue #1).
248
+ const lastUserIdx = derived.findLastIndex(isRealUserMessage);
231
249
  if (lastUserIdx < 0) return;
232
250
  const messages = derived
233
251
  .slice(lastUserIdx)
234
252
  .map((m) => {
253
+ // Keep assistant replies and real user input only; drop synthetic
254
+ // user-role messages (plugin context, tool results, …) injected
255
+ // after the last real user message.
256
+ if (m.role !== "assistant" && !isRealUserMessage(m)) return null;
235
257
  const text = extractText(m.content);
236
258
  if (!text) return null;
237
259
  return {
@@ -268,7 +290,7 @@ function apply(ctx, config) {
268
290
  agent.ctx.on("system-prompt/assemble", async (assembly, context) => {
269
291
  try {
270
292
  if (!tdaiConfig?.recall.enabled) return assembly;
271
- const lastUser = [...session.deriveMessages()].reverse().find((m) => m.role === "user");
293
+ const lastUser = [...session.deriveMessages()].reverse().find(isRealUserMessage);
272
294
  if (!lastUser) return assembly;
273
295
  const text = extractText(lastUser.content);
274
296
  if (!text) return assembly;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-tdai-memory",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "DeepSeek Harness 的 TencentDB Agent Memory 移植:L0 对话捕获 → L1 结构化记忆提取 → L2 场景/L3 画像,自动召回注入 + 记忆/对话搜索工具;复用现有 ~/.memory-tencentdb/memory-tdai 数据;附 Web UI 设置栏。",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/recall-inject.js CHANGED
@@ -38,7 +38,13 @@ function apply(ctx, config) {
38
38
  if (!agent) return assembly;
39
39
  const session = agent.session;
40
40
  if (!session) return assembly;
41
- const lastUser = [...session.deriveMessages()].reverse().find((m) => m.role === "user");
41
+ // Only real human input triggers recall. DSH injects synthetic
42
+ // user-role context (system reminders, runtime context, skill
43
+ // catalogs, …) whose source.kind is not "user"; recalling on those
44
+ // would pollute the prompt with irrelevant memories (issue #1).
45
+ const lastUser = [...session.deriveMessages()]
46
+ .reverse()
47
+ .find((m) => m.role === "user" && (m.source == null || m.source.kind === "user"));
42
48
  if (!lastUser) return assembly;
43
49
  const text = extractText(lastUser.content);
44
50
  if (!text || text.length < config.minUserTextChars) return assembly;