dsh-tdai-memory 0.2.1 → 0.2.2
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/index.js +119 -89
- package/package.json +62 -63
- package/vendor/tdai/utils/checkpoint.js +5 -1
package/index.js
CHANGED
|
@@ -84,74 +84,21 @@ function extractText(content) {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
function apply(ctx, config) {
|
|
87
|
-
// ──
|
|
88
|
-
// TdaiCore is built
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
capture: { enabled: cfg.captureEnabled },
|
|
103
|
-
extraction: { enabled: cfg.extraction.enabled, enableDedup: cfg.extraction.enableDedup },
|
|
104
|
-
recall: { ...cfg.recall },
|
|
105
|
-
embedding: {
|
|
106
|
-
enabled: true,
|
|
107
|
-
provider: "openai",
|
|
108
|
-
baseUrl: cfg.embedding.baseUrl,
|
|
109
|
-
apiKey: cfg.embedding.apiKey,
|
|
110
|
-
model: cfg.embedding.model,
|
|
111
|
-
dimensions: cfg.embedding.dimensions,
|
|
112
|
-
sendDimensions: cfg.embedding.sendDimensions,
|
|
113
|
-
timeoutMs: 10000,
|
|
114
|
-
},
|
|
115
|
-
llm: {
|
|
116
|
-
enabled: true,
|
|
117
|
-
baseUrl: cfg.llm.baseUrl || process.env.TDAI_LLM_BASE_URL || "",
|
|
118
|
-
apiKey: cfg.llm.apiKey || process.env.TDAI_LLM_API_KEY || "",
|
|
119
|
-
model: cfg.llm.model || process.env.TDAI_LLM_MODEL || "deepseek-v4-flash",
|
|
120
|
-
maxTokens: cfg.llm.maxTokens,
|
|
121
|
-
timeoutMs: cfg.llm.timeoutMs,
|
|
122
|
-
},
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// Verbose tdai-internal logging goes to the console only when explicitly
|
|
126
|
-
// enabled; routine diagnostics use the dsh logger.
|
|
127
|
-
const verbose = !!process.env.DSH_TDAI_DEBUG;
|
|
128
|
-
const log = (level, m) => {
|
|
129
|
-
if (verbose) console.error(`[tdai-memory] ${m}`);
|
|
130
|
-
else if (level === "warn") ctx.logger.warn(`[tdai-memory] ${m}`);
|
|
131
|
-
else if (level === "error") ctx.logger.error(`[tdai-memory] ${m}`);
|
|
132
|
-
};
|
|
133
|
-
const logger = {
|
|
134
|
-
debug: (m) => log("debug", m),
|
|
135
|
-
info: (m) => log("info", m),
|
|
136
|
-
warn: (m) => log("warn", m),
|
|
137
|
-
error: (m) => log("error", m),
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const hostAdapter = new StandaloneHostAdapter({
|
|
141
|
-
dataDir,
|
|
142
|
-
llmConfig: {
|
|
143
|
-
baseUrl: tdaiConfig.llm.baseUrl,
|
|
144
|
-
apiKey: tdaiConfig.llm.apiKey,
|
|
145
|
-
model: tdaiConfig.llm.model,
|
|
146
|
-
maxTokens: tdaiConfig.llm.maxTokens,
|
|
147
|
-
timeoutMs: tdaiConfig.llm.timeoutMs,
|
|
148
|
-
},
|
|
149
|
-
logger,
|
|
150
|
-
defaultUserId: "dsh-user",
|
|
151
|
-
platform: "dsh",
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
const core = new TdaiCore({ hostAdapter, config: tdaiConfig, instanceId: "dsh" });
|
|
87
|
+
// ── build-once state (rebuildable) ───────────────────────────────────────
|
|
88
|
+
// TdaiCore is built from the RESOLVED settings value (composition entry as
|
|
89
|
+
// base, `tdai-memory` settings section / settings.yaml as user layer), so
|
|
90
|
+
// keys stored in settings.yaml (llm.apiKey etc.) are honored. The settings
|
|
91
|
+
// inject callback that drives `installSettingsSection`'s setSource is
|
|
92
|
+
// scheduled by cordis as a plugin start — it ALWAYS runs after any 0ms
|
|
93
|
+
// timer — so the no-settings fallback below cannot be allowed to win the
|
|
94
|
+
// race. Instead of racing, we let the fallback build from the entry config
|
|
95
|
+
// first (guaranteeing a working core even without settings), and REBUILD
|
|
96
|
+
// from the resolved settings when the settings mount arrives. The fallback
|
|
97
|
+
// is a one-shot; the settings build always wins afterwards.
|
|
98
|
+
let core = null;
|
|
99
|
+
let tdaiConfig = null;
|
|
100
|
+
let dataDir = "";
|
|
101
|
+
let builtOnce = false;
|
|
155
102
|
|
|
156
103
|
/** turn id of the last turn/end per session (capture trigger). */
|
|
157
104
|
const lastTurnEnd = new Map();
|
|
@@ -170,6 +117,82 @@ function apply(ctx, config) {
|
|
|
170
117
|
// long-lived process runs the pipeline in the background instead.
|
|
171
118
|
const isOneShot = process.argv.some((arg) => arg === "headless");
|
|
172
119
|
|
|
120
|
+
// Verbose tdai-internal logging goes to the console only when explicitly
|
|
121
|
+
// enabled; routine diagnostics use the dsh logger.
|
|
122
|
+
const verbose = !!process.env.DSH_TDAI_DEBUG;
|
|
123
|
+
const log = (level, m) => {
|
|
124
|
+
if (verbose) console.error(`[tdai-memory] ${m}`);
|
|
125
|
+
else if (level === "warn") ctx.logger.warn(`[tdai-memory] ${m}`);
|
|
126
|
+
else if (level === "error") ctx.logger.error(`[tdai-memory] ${m}`);
|
|
127
|
+
};
|
|
128
|
+
const logger = {
|
|
129
|
+
debug: (m) => log("debug", m),
|
|
130
|
+
info: (m) => log("info", m),
|
|
131
|
+
warn: (m) => log("warn", m),
|
|
132
|
+
error: (m) => log("error", m),
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// ── core construction (rebuildable) ──────────────────────────────────────
|
|
136
|
+
async function build(cfg) {
|
|
137
|
+
if (cfg?.llm?.apiKey) {
|
|
138
|
+
log("debug", `build: using settings config (apiKey=${cfg.llm.apiKey.slice(0, 6)}…)`);
|
|
139
|
+
}
|
|
140
|
+
// Tear down the previous core, if any (first build has none).
|
|
141
|
+
const previous = core;
|
|
142
|
+
if (previous) {
|
|
143
|
+
await previous.destroy().catch((error) => {
|
|
144
|
+
log("warn", `previous core destroy failed: ${String(error)}`);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
dataDir = cfg.dataDir || join(os.homedir(), ".memory-tencentdb", "memory-tdai");
|
|
148
|
+
const nextConfig = parseConfig({
|
|
149
|
+
timezone: "system",
|
|
150
|
+
storeBackend: "sqlite",
|
|
151
|
+
capture: { enabled: cfg.captureEnabled },
|
|
152
|
+
extraction: { enabled: cfg.extraction.enabled, enableDedup: cfg.extraction.enableDedup },
|
|
153
|
+
recall: { ...cfg.recall },
|
|
154
|
+
embedding: {
|
|
155
|
+
enabled: true,
|
|
156
|
+
provider: "openai",
|
|
157
|
+
baseUrl: cfg.embedding.baseUrl,
|
|
158
|
+
apiKey: cfg.embedding.apiKey,
|
|
159
|
+
model: cfg.embedding.model,
|
|
160
|
+
dimensions: cfg.embedding.dimensions,
|
|
161
|
+
sendDimensions: cfg.embedding.sendDimensions,
|
|
162
|
+
timeoutMs: 10000,
|
|
163
|
+
},
|
|
164
|
+
llm: {
|
|
165
|
+
enabled: true,
|
|
166
|
+
baseUrl: cfg.llm.baseUrl || process.env.TDAI_LLM_BASE_URL || "",
|
|
167
|
+
apiKey: cfg.llm.apiKey || process.env.TDAI_LLM_API_KEY || "",
|
|
168
|
+
model: cfg.llm.model || process.env.TDAI_LLM_MODEL || "deepseek-v4-flash",
|
|
169
|
+
maxTokens: cfg.llm.maxTokens,
|
|
170
|
+
timeoutMs: cfg.llm.timeoutMs,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
const nextHostAdapter = new StandaloneHostAdapter({
|
|
174
|
+
dataDir,
|
|
175
|
+
llmConfig: {
|
|
176
|
+
baseUrl: nextConfig.llm.baseUrl,
|
|
177
|
+
apiKey: nextConfig.llm.apiKey,
|
|
178
|
+
model: nextConfig.llm.model,
|
|
179
|
+
maxTokens: nextConfig.llm.maxTokens,
|
|
180
|
+
timeoutMs: nextConfig.llm.timeoutMs,
|
|
181
|
+
},
|
|
182
|
+
logger,
|
|
183
|
+
defaultUserId: "dsh-user",
|
|
184
|
+
platform: "dsh",
|
|
185
|
+
});
|
|
186
|
+
const nextCore = new TdaiCore({ hostAdapter: nextHostAdapter, config: nextConfig, instanceId: "dsh" });
|
|
187
|
+
// Publish before initialize so concurrent flush listeners already use the
|
|
188
|
+
// new core; initialize() only sets up stores/pipeline.
|
|
189
|
+
tdaiConfig = nextConfig;
|
|
190
|
+
core = nextCore;
|
|
191
|
+
await nextCore.initialize();
|
|
192
|
+
const key = nextConfig.llm.apiKey || "";
|
|
193
|
+
log("info", `initialized (dataDir=${dataDir}, llm=${nextConfig.llm.model}, apiKey=${key ? `${key.slice(0, 6)}…` : "<empty>"}, embedding=${nextConfig.embedding.model})`);
|
|
194
|
+
}
|
|
195
|
+
|
|
173
196
|
// ── capture: triggered on session/flush (awaitable; runs before process
|
|
174
197
|
// exit in headless, and at every request boundary in web) ─────────────
|
|
175
198
|
ctx.on("session/event", (session, event) => {
|
|
@@ -190,7 +213,7 @@ function apply(ctx, config) {
|
|
|
190
213
|
await captureTurn(session, startedAt);
|
|
191
214
|
if (isOneShot) {
|
|
192
215
|
// Run pending L1 extraction to completion before the process exits.
|
|
193
|
-
await core
|
|
216
|
+
await core?.handleSessionEnd(session.id).catch((error) => {
|
|
194
217
|
ctx.logger.warn(`[tdai-memory] L1 flush failed: ${String(error)}`);
|
|
195
218
|
});
|
|
196
219
|
}
|
|
@@ -200,7 +223,7 @@ function apply(ctx, config) {
|
|
|
200
223
|
});
|
|
201
224
|
|
|
202
225
|
async function captureTurn(session, startedAt) {
|
|
203
|
-
if (!tdaiConfig
|
|
226
|
+
if (!tdaiConfig?.capture.enabled) return;
|
|
204
227
|
const derived = session.deriveMessages();
|
|
205
228
|
const lastUserIdx = derived.map((m) => m.role).lastIndexOf("user");
|
|
206
229
|
if (lastUserIdx < 0) return;
|
|
@@ -242,7 +265,7 @@ function apply(ctx, config) {
|
|
|
242
265
|
if (!agent?.ctx) return;
|
|
243
266
|
agent.ctx.on("system-prompt/assemble", async (assembly, context) => {
|
|
244
267
|
try {
|
|
245
|
-
if (!tdaiConfig
|
|
268
|
+
if (!tdaiConfig?.recall.enabled) return assembly;
|
|
246
269
|
const lastUser = [...session.deriveMessages()].reverse().find((m) => m.role === "user");
|
|
247
270
|
if (!lastUser) return assembly;
|
|
248
271
|
const text = extractText(lastUser.content);
|
|
@@ -319,43 +342,50 @@ function apply(ctx, config) {
|
|
|
319
342
|
|
|
320
343
|
// ── settings-backed configuration ─────────────────────────────────────────
|
|
321
344
|
// The composition entry stays the base layer; a registered `tdai-memory`
|
|
322
|
-
// settings section overlays it (settings.yaml).
|
|
323
|
-
//
|
|
324
|
-
//
|
|
345
|
+
// settings section overlays it (settings.yaml). `installSettingsSection`
|
|
346
|
+
// hands `setSource` a GETTER (`() => scope.get()`), not the config object —
|
|
347
|
+
// we must call it to obtain the resolved settings. The settings mount
|
|
348
|
+
// (cordis inject) always arrives after our fallback timer, so the fallback
|
|
349
|
+
// builds the core from the entry config first (one-shot) and the settings
|
|
350
|
+
// build REBUILDS the core with the resolved values (apiKey included).
|
|
325
351
|
installSettingsSection(ctx, NS, Config, config, {
|
|
326
|
-
setSource: (
|
|
327
|
-
|
|
352
|
+
setSource: (getter) => {
|
|
353
|
+
builtOnce = true;
|
|
354
|
+
void build(getter());
|
|
328
355
|
},
|
|
329
356
|
onChange: () => {
|
|
330
|
-
|
|
357
|
+
ctx.logger.warn("[tdai-memory] settings updated; restart to apply (TdaiCore is built at startup)");
|
|
331
358
|
},
|
|
332
359
|
});
|
|
333
360
|
|
|
361
|
+
// Fallback when no settings service ever mounts: build from the entry
|
|
362
|
+
// config. One-shot — if the settings mount arrives later, its build
|
|
363
|
+
// supersedes this one. Delayed past the 0ms window so headless/web boots
|
|
364
|
+
// are never blocked on this timer.
|
|
365
|
+
setTimeout(() => {
|
|
366
|
+
if (!builtOnce) {
|
|
367
|
+
builtOnce = true;
|
|
368
|
+
void build(config);
|
|
369
|
+
}
|
|
370
|
+
}, 500);
|
|
371
|
+
|
|
334
372
|
// ── lifecycle ────────────────────────────────────────────────────────────
|
|
335
|
-
ctx.effect(
|
|
336
|
-
await core.initialize();
|
|
337
|
-
ctx.logger.info(`[tdai-memory] initialized (dataDir=${dataDir}, llm=${tdaiConfig.llm.model}, embedding=${tdaiConfig.embedding.model})`);
|
|
373
|
+
ctx.effect(() => {
|
|
338
374
|
return async () => {
|
|
339
375
|
// Final flush: run pending L1 extraction for every session seen this
|
|
340
376
|
// process and wait for it to finish (headless exits right after the
|
|
341
377
|
// tree disposes, so this is what makes extraction complete in one-shot
|
|
342
378
|
// runs). Unknown keys are tolerated as no-ops.
|
|
343
379
|
for (const sessionKey of seenSessions) {
|
|
344
|
-
await core
|
|
380
|
+
await core?.handleSessionEnd(sessionKey).catch((error) => {
|
|
345
381
|
ctx.logger.warn(`[tdai-memory] final L1 flush failed for ${sessionKey}: ${String(error)}`);
|
|
346
382
|
});
|
|
347
383
|
}
|
|
348
|
-
await core
|
|
384
|
+
await core?.destroy().catch((error) => {
|
|
349
385
|
ctx.logger.warn(`[tdai-memory] destroy failed: ${String(error)}`);
|
|
350
386
|
});
|
|
351
387
|
};
|
|
352
|
-
}, "tdai-memory.
|
|
353
|
-
}; // end build(cfg)
|
|
354
|
-
|
|
355
|
-
// Fallback when no settings service ever mounts: build from the entry config.
|
|
356
|
-
setTimeout(() => {
|
|
357
|
-
if (!started) build(config);
|
|
358
|
-
}, 0);
|
|
388
|
+
}, "tdai-memory.cleanup()");
|
|
359
389
|
}
|
|
360
390
|
|
|
361
391
|
export { Config, apply, inject, name };
|
package/package.json
CHANGED
|
@@ -1,63 +1,62 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-tdai-memory",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "DeepSeek Harness 的 TencentDB Agent Memory 移植:L0 对话捕获 → L1 结构化记忆提取 → L2 场景/L3 画像,自动召回注入 + 记忆/对话搜索工具;复用现有 ~/.memory-tencentdb/memory-tdai 数据;附 Web UI 设置栏。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js",
|
|
9
|
+
"./client": "./client.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"dsh": {
|
|
13
|
+
"client": {
|
|
14
|
+
"platform": "web"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/Scorp1o117/dsh-tdai-memory",
|
|
18
|
+
"files": [
|
|
19
|
+
"index.js",
|
|
20
|
+
"recall-inject.js",
|
|
21
|
+
"client.js",
|
|
22
|
+
"vendor",
|
|
23
|
+
"README.md",
|
|
24
|
+
"README.zh.md",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"THIRD-PARTY-NOTICES.md"
|
|
27
|
+
],
|
|
28
|
+
"keywords": [
|
|
29
|
+
"dsh",
|
|
30
|
+
"dsh-plugin",
|
|
31
|
+
"deepseek-harness",
|
|
32
|
+
"memory",
|
|
33
|
+
"tencentdb",
|
|
34
|
+
"agent-memory",
|
|
35
|
+
"rag",
|
|
36
|
+
"vector-search",
|
|
37
|
+
"persona"
|
|
38
|
+
],
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": "\u003e=22.18"
|
|
42
|
+
},
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/Scorp1o117/dsh-tdai-memory.git"
|
|
46
|
+
},
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/Scorp1o117/dsh-tdai-memory/issues"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@ai-sdk/openai": "^3.0.53",
|
|
52
|
+
"@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
|
|
53
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
|
|
54
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
55
|
+
"@node-rs/jieba": "^2.0.1",
|
|
56
|
+
"@tencentdb-agent-memory/tcvdb-text": "^0.1.1",
|
|
57
|
+
"ai": "^6.0.164",
|
|
58
|
+
"json5": "^2.2.3",
|
|
59
|
+
"sqlite-vec": "0.1.7-alpha.2",
|
|
60
|
+
"undici": "^8.1.0"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -338,8 +338,12 @@ export class CheckpointManager {
|
|
|
338
338
|
const state = this.getRunnerState(cp, sessionKey);
|
|
339
339
|
let afterTimestamp = state.last_captured_timestamp || 0;
|
|
340
340
|
// Cold-start guard (same logic that was previously in auto-capture.ts)
|
|
341
|
+
// Subtract 1ms from the plugin-start floor: the very first turn may be
|
|
342
|
+
// flushed in the same millisecond the plugin started, and the L0 filter
|
|
343
|
+
// is strict `timestamp > cursor` — an identical timestamp would drop
|
|
344
|
+
// the first turn entirely.
|
|
341
345
|
if (afterTimestamp === 0 && pluginStartTimestamp && pluginStartTimestamp > 0) {
|
|
342
|
-
afterTimestamp = pluginStartTimestamp;
|
|
346
|
+
afterTimestamp = pluginStartTimestamp - 1;
|
|
343
347
|
}
|
|
344
348
|
const result = await fn(afterTimestamp);
|
|
345
349
|
if (result) {
|